From 3a65364369f4d832b6687c00641cb5c99e26f38c Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 5 Jun 2026 13:54:44 -0700 Subject: [PATCH 1/2] Add .gitattributes and normalize line endings across repo - set CRLF as the default checkout encoding for this Windows-native project, with LF overrides for cross-platform formats (`.md`, `.json`, `.js`, `.txt`, `.patch`, git config files) - mark `src/PowerShell/ExternalModules/**` as `-text` to preserve vendored files byte-for-byte -- several are UTF-16 LE encoded - the remaining 1,753 staged files are a one-time re-normalization of git's internal LF object storage; `git diff --ignore-all-space` shows only `.gitattributes` as a real change Assisted-By: "claude my eyes right out" --- .gitattributes | 28 + .github/copilot-instructions.md | 384 +- .../winget-log-viewer.instructions.md | 218 +- .github/workflows/spelling.yml | 32 +- .github/workflows/spelling2.yml | 32 +- .gitignore | 2 +- CODEOWNERS | 4 +- CODE_OF_CONDUCT.md | 18 +- LICENSE | 42 +- Localization/Settings/LocConfig.xml | 20 +- NOTICE | 22006 ++++++++-------- README.md | 304 +- azure-pipelines.loc.yml | 122 +- azure-pipelines.yml | 1444 +- cgmanifest.json | 102 +- doc/admx/DesktopAppInstaller.admx | 598 +- doc/admx/en-US/DesktopAppInstaller.adml | 382 +- doc/specs/#1012 - Show dependencies.md | 266 +- ...Management of package type dependencies.md | 232 +- doc/specs/#140 - ZIP Support.md | 178 +- doc/specs/#148 - Repair Support.md | 240 +- doc/specs/#164 - PWA Support.md | 246 +- doc/specs/#888 - Com Api.md | 1752 +- doc/specs/Configuration-COM-API.md | 1610 +- .../package/winget-validation.md | 238 +- doc/windows/package-manager/winget/export.md | 142 +- doc/windows/package-manager/winget/import.md | 124 +- .../package-manager/winget/returnCodes.md | 478 +- samples/ConnectionValidationSample/Program.cs | 232 +- .../WinGetUWPCaller/WinGetUWPCaller/App.cpp | 242 +- .../WinGetUWPCaller/WinGetUWPCaller/App.xaml | 18 +- .../WinGetUWPCaller/MainPage.cpp | 1908 +- .../WinGetUWPCaller/MainPage.h | 158 +- .../WinGetUWPCaller/MainPage.idl | 4 +- .../WinGetUWPCaller/MainPage.xaml | 234 +- .../WinGetUWPCaller/WinGetUWPCaller.vcxproj | 396 +- .../WinGetUWPCaller.vcxproj.filters | 114 +- .../WinGetUWPCaller/packages.config | 8 +- samples/WinGetUWPCaller/WinGetUWPCaller/pch.h | 10 +- .../configuration.schema.0.1.json | 154 +- .../manifests/preview/manifest.0.1.0.json | 624 +- .../v1.0.0/manifest.defaultLocale.1.0.0.json | 284 +- .../v1.0.0/manifest.installer.1.0.0.json | 916 +- .../v1.0.0/manifest.locale.1.0.0.json | 266 +- .../v1.0.0/manifest.singleton.1.0.0.json | 1112 +- .../v1.0.0/manifest.version.1.0.0.json | 92 +- .../v1.1.0/manifest.defaultLocale.1.1.0.json | 360 +- .../v1.1.0/manifest.installer.1.1.0.json | 1342 +- .../v1.1.0/manifest.locale.1.1.0.json | 342 +- .../v1.1.0/manifest.singleton.1.1.0.json | 1614 +- .../v1.1.0/manifest.version.1.1.0.json | 90 +- .../v1.2.0/manifest.defaultLocale.1.2.0.json | 424 +- .../v1.2.0/manifest.installer.1.2.0.json | 1428 +- .../v1.2.0/manifest.locale.1.2.0.json | 406 +- .../v1.2.0/manifest.singleton.1.2.0.json | 1752 +- .../v1.2.0/manifest.version.1.2.0.json | 90 +- .../v1.4.0/manifest.defaultLocale.1.4.0.json | 424 +- .../v1.4.0/manifest.installer.1.4.0.json | 1670 +- .../v1.4.0/manifest.locale.1.4.0.json | 406 +- .../v1.4.0/manifest.singleton.1.4.0.json | 1994 +- .../v1.4.0/manifest.version.1.4.0.json | 90 +- .../v1.5.0/manifest.defaultLocale.1.5.0.json | 560 +- .../v1.5.0/manifest.installer.1.5.0.json | 1670 +- .../v1.5.0/manifest.locale.1.5.0.json | 542 +- .../v1.5.0/manifest.singleton.1.5.0.json | 2128 +- .../v1.5.0/manifest.version.1.5.0.json | 90 +- .../v1.6.0/manifest.defaultLocale.1.6.0.json | 560 +- .../v1.6.0/manifest.installer.1.6.0.json | 1692 +- .../v1.6.0/manifest.locale.1.6.0.json | 542 +- .../v1.6.0/manifest.singleton.1.6.0.json | 2150 +- .../v1.6.0/manifest.version.1.6.0.json | 90 +- .../v1.7.0/manifest.defaultLocale.1.7.0.json | 560 +- .../v1.7.0/manifest.installer.1.7.0.json | 1734 +- .../v1.7.0/manifest.locale.1.7.0.json | 542 +- .../v1.7.0/manifest.singleton.1.7.0.json | 2192 +- .../v1.7.0/manifest.version.1.7.0.json | 90 +- .../v1.9.0/manifest.defaultLocale.1.9.0.json | 2 +- .../v1.9.0/manifest.locale.1.9.0.json | 2 +- src/AppInstallerCLI.sln | 2274 +- src/AppInstallerCLI/AppInstallerCLI.vcxproj | 518 +- .../AppInstallerCLI.vcxproj.filters | 50 +- src/AppInstallerCLI/packages.config | 6 +- .../AppInstallerCLICore.vcxproj | 988 +- .../AppInstallerCLICore.vcxproj.filters | 1156 +- src/AppInstallerCLICore/Argument.cpp | 1294 +- src/AppInstallerCLICore/Argument.h | 534 +- src/AppInstallerCLICore/COMContext.cpp | 212 +- src/AppInstallerCLICore/COMContext.h | 170 +- src/AppInstallerCLICore/ChannelStreams.cpp | 360 +- src/AppInstallerCLICore/ChannelStreams.h | 236 +- src/AppInstallerCLICore/Command.cpp | 2180 +- src/AppInstallerCLICore/Command.h | 316 +- .../Commands/COMCommand.cpp | 32 +- src/AppInstallerCLICore/Commands/COMCommand.h | 44 +- .../Commands/CompleteCommand.cpp | 164 +- .../Commands/CompleteCommand.h | 48 +- .../Commands/ConfigureCommand.cpp | 228 +- .../Commands/ConfigureCommand.h | 50 +- .../Commands/ConfigureListCommand.cpp | 180 +- .../Commands/ConfigureListCommand.h | 48 +- .../Commands/ConfigureShowCommand.cpp | 124 +- .../Commands/ConfigureShowCommand.h | 48 +- .../Commands/ConfigureTestCommand.cpp | 132 +- .../Commands/ConfigureTestCommand.h | 48 +- .../Commands/ConfigureValidateCommand.cpp | 110 +- .../Commands/ConfigureValidateCommand.h | 46 +- .../Commands/DebugCommand.cpp | 1426 +- .../Commands/DebugCommand.h | 266 +- .../Commands/DownloadCommand.cpp | 284 +- .../Commands/DownloadCommand.h | 4 +- .../Commands/DscAdminSettingsResource.cpp | 588 +- .../Commands/DscAdminSettingsResource.h | 50 +- .../Commands/DscCommand.cpp | 204 +- src/AppInstallerCLICore/Commands/DscCommand.h | 54 +- .../Commands/DscCommandBase.cpp | 628 +- .../Commands/DscCommandBase.h | 248 +- .../Commands/DscComposableObject.cpp | 240 +- .../Commands/DscComposableObject.h | 512 +- .../Commands/DscPackageResource.cpp | 1082 +- .../Commands/DscPackageResource.h | 50 +- .../Commands/DscSourceResource.cpp | 1030 +- .../Commands/DscSourceResource.h | 50 +- .../Commands/DscTestFileResource.cpp | 472 +- .../Commands/DscTestFileResource.h | 50 +- .../Commands/DscTestJsonResource.cpp | 416 +- .../Commands/DscTestJsonResource.h | 58 +- .../Commands/DscUserSettingsFileResource.cpp | 514 +- .../Commands/DscUserSettingsFileResource.h | 50 +- .../Commands/ErrorCommand.cpp | 438 +- .../Commands/ErrorCommand.h | 44 +- .../Commands/HashCommand.cpp | 136 +- .../Commands/HashCommand.h | 44 +- .../Commands/ImportCommand.cpp | 4 +- .../Commands/InstallCommand.cpp | 346 +- .../Commands/InstallCommand.h | 54 +- .../Commands/ListCommand.cpp | 206 +- .../Commands/ListCommand.h | 52 +- .../Commands/McpCommand.cpp | 166 +- src/AppInstallerCLICore/Commands/McpCommand.h | 46 +- .../Commands/PinCommand.cpp | 692 +- src/AppInstallerCLICore/Commands/PinCommand.h | 180 +- .../Commands/RepairCommand.cpp | 250 +- .../Commands/RepairCommand.h | 50 +- .../Commands/RootCommand.cpp | 588 +- .../Commands/RootCommand.h | 54 +- .../Commands/SearchCommand.cpp | 204 +- .../Commands/SearchCommand.h | 52 +- .../Commands/SettingsCommand.cpp | 458 +- .../Commands/ShowCommand.cpp | 220 +- .../Commands/ShowCommand.h | 50 +- .../Commands/SourceCommand.cpp | 774 +- .../Commands/SourceCommand.h | 280 +- .../Commands/TestCommand.cpp | 808 +- .../Commands/TestCommand.h | 86 +- .../Commands/UpgradeCommand.cpp | 442 +- .../Commands/UpgradeCommand.h | 50 +- .../Commands/ValidateCommand.cpp | 158 +- .../Commands/ValidateCommand.h | 44 +- src/AppInstallerCLICore/CompletionData.cpp | 426 +- src/AppInstallerCLICore/CompletionData.h | 64 +- .../ConfigurationCommon.cpp | 204 +- src/AppInstallerCLICore/ConfigurationCommon.h | 34 +- .../ConfigurationContext.cpp | 172 +- .../ConfigurationContext.h | 100 +- .../ConfigurationDynamicRuntimeFactory.cpp | 1140 +- ...nfigurationSetProcessorFactoryRemoting.cpp | 890 +- ...igurationWingetDscModuleUnitValidation.cpp | 722 +- ...nfigurationWingetDscModuleUnitValidation.h | 60 +- .../ConfigureExportCommand.cpp | 148 +- .../ConfigureExportCommand.h | 46 +- .../ContextOrchestrator.cpp | 1184 +- src/AppInstallerCLICore/ContextOrchestrator.h | 450 +- src/AppInstallerCLICore/Core.cpp | 120 +- src/AppInstallerCLICore/ExecutionArgs.h | 614 +- src/AppInstallerCLICore/ExecutionContext.cpp | 576 +- src/AppInstallerCLICore/ExecutionContext.h | 422 +- .../ExecutionContextData.h | 606 +- src/AppInstallerCLICore/ExecutionProgress.cpp | 1674 +- src/AppInstallerCLICore/ExecutionProgress.h | 102 +- src/AppInstallerCLICore/ExecutionReporter.cpp | 840 +- src/AppInstallerCLICore/ExecutionReporter.h | 466 +- src/AppInstallerCLICore/Invocation.h | 96 +- src/AppInstallerCLICore/PackageCollection.h | 2 +- src/AppInstallerCLICore/PortableInstaller.cpp | 1088 +- src/AppInstallerCLICore/PortableInstaller.h | 238 +- .../Public/AppInstallerCLICore.h | 30 +- ...ConfigurationSetProcessorFactoryRemoting.h | 140 +- .../Public/ShutdownMonitoring.h | 230 +- src/AppInstallerCLICore/Resources.cpp | 38 +- src/AppInstallerCLICore/Resources.h | 1740 +- src/AppInstallerCLICore/Search/Search.h | 202 +- .../ShutdownMonitoring.cpp | 810 +- src/AppInstallerCLICore/Sixel.cpp | 1436 +- src/AppInstallerCLICore/Sixel.h | 536 +- src/AppInstallerCLICore/TableOutput.h | 436 +- src/AppInstallerCLICore/VTSupport.cpp | 682 +- src/AppInstallerCLICore/VTSupport.h | 466 +- .../Workflows/ArchiveFlow.cpp | 44 +- .../Workflows/ArchiveFlow.h | 8 +- .../Workflows/CompletionFlow.cpp | 442 +- .../Workflows/CompletionFlow.h | 150 +- .../Workflows/ConfigurationFlow.cpp | 6238 ++--- .../Workflows/ConfigurationFlow.h | 338 +- .../Workflows/DependenciesFlow.cpp | 710 +- .../Workflows/DependenciesFlow.h | 138 +- .../Workflows/DependencyNodeProcessor.cpp | 2 +- .../Workflows/DownloadFlow.cpp | 1588 +- .../Workflows/DownloadFlow.h | 190 +- .../Workflows/ImportExportFlow.cpp | 746 +- .../Workflows/InstallFlow.cpp | 2104 +- .../Workflows/InstallFlow.h | 456 +- .../Workflows/MSStoreInstallerHandler.cpp | 942 +- .../Workflows/MSStoreInstallerHandler.h | 112 +- .../Workflows/MsiInstallFlow.cpp | 126 +- .../Workflows/MsiInstallFlow.h | 26 +- .../Workflows/MultiQueryFlow.cpp | 298 +- .../Workflows/MultiQueryFlow.h | 64 +- .../Workflows/PackageTableSortHelper.cpp | 344 +- .../Workflows/PackageTableSortHelper.h | 216 +- src/AppInstallerCLICore/Workflows/PinFlow.cpp | 674 +- src/AppInstallerCLICore/Workflows/PinFlow.h | 124 +- .../Workflows/PortableFlow.cpp | 2 +- .../Workflows/RepairFlow.cpp | 1118 +- .../Workflows/RepairFlow.h | 170 +- .../Workflows/SettingsFlow.cpp | 388 +- .../Workflows/SettingsFlow.h | 98 +- .../ShellExecuteInstallerHandler.cpp | 1250 +- .../Workflows/ShellExecuteInstallerHandler.h | 156 +- .../Workflows/ShowFlow.cpp | 508 +- src/AppInstallerCLICore/Workflows/ShowFlow.h | 164 +- .../Workflows/SourceFlow.cpp | 856 +- .../Workflows/SourceFlow.h | 196 +- .../Workflows/UninstallFlow.cpp | 1000 +- .../Workflows/UpdateFlow.cpp | 756 +- .../Workflows/UpdateFlow.h | 132 +- .../Workflows/WorkflowBase.cpp | 3712 +-- .../Workflows/WorkflowBase.h | 918 +- src/AppInstallerCLICore/packages.config | 8 +- src/AppInstallerCLICore/pch.h | 126 +- .../AppInstallerCLIE2ETests.csproj | 168 +- .../AppShutdownTests.cs | 266 +- src/AppInstallerCLIE2ETests/BaseCommand.cs | 92 +- .../ConfigureCommand.cs | 852 +- .../ConfigureExportCommand.cs | 422 +- .../ConfigureListCommand.cs | 210 +- .../ConfigureProcessorPathCommand.cs | 278 +- .../ConfigureShowCommand.cs | 450 +- .../ConfigureTestCommand.cs | 302 +- .../ConfigureValidateCommand.cs | 602 +- src/AppInstallerCLIE2ETests/Constants.cs | 702 +- .../DSCv3AdminSettingsResourceCommand.cs | 740 +- .../DSCv3PackageResourceCommand.cs | 1246 +- .../DSCv3ResourceTestBase.cs | 380 +- .../DSCv3SourceResourceCommand.cs | 1092 +- .../DSCv3UserSettingsFileResourceCommand.cs | 724 +- .../DownloadCommand.cs | 12 +- src/AppInstallerCLIE2ETests/ErrorCommand.cs | 278 +- .../FeaturesCommand.cs | 122 +- src/AppInstallerCLIE2ETests/GroupPolicy.cs | 618 +- .../GroupPolicyHelper.cs | 1022 +- src/AppInstallerCLIE2ETests/HashCommand.cs | 126 +- .../Helpers/TestCommon.cs | 2628 +- .../Helpers/TestIndex.cs | 398 +- .../Helpers/TestSetup.cs | 480 +- .../Helpers/WinGetSettingsHelper.cs | 648 +- src/AppInstallerCLIE2ETests/ImportCommand.cs | 56 +- .../InprocTestbedTests.cs | 482 +- src/AppInstallerCLIE2ETests/InstallCommand.cs | 1732 +- .../Interop/BaseInterop.cs | 36 +- .../Interop/CheckInstalledStatusInterop.cs | 4 +- .../Interop/ConnectionValidationInterop.cs | 10 +- .../Interop/FindPackagesInterop.cs | 224 +- .../Interop/GroupPolicyForInterop.cs | 492 +- .../Interop/InstallInterop.cs | 1456 +- .../Interop/InstanceInitializersSource.cs | 12 +- .../Interop/InteropSetUpFixture.cs | 30 +- .../Interop/PackageCatalogInterop.cs | 830 +- .../Interop/RepairInterop.cs | 712 +- .../Interop/Shutdown.cs | 268 +- .../Interop/UninstallInterop.cs | 4 +- .../Interop/UpgradeInterop.cs | 154 +- src/AppInstallerCLIE2ETests/ListCommand.cs | 518 +- src/AppInstallerCLIE2ETests/Pinning.cs | 476 +- .../PowerShell/PowerShellHost.cs | 214 +- .../Properties/AssemblyInfo.cs | 14 +- src/AppInstallerCLIE2ETests/README.md | 2 +- src/AppInstallerCLIE2ETests/RepairCommand.cs | 560 +- src/AppInstallerCLIE2ETests/ResumeCommand.cs | 62 +- .../RunCommandException.cs | 92 +- src/AppInstallerCLIE2ETests/SearchCommand.cs | 432 +- src/AppInstallerCLIE2ETests/SetUpFixture.cs | 294 +- src/AppInstallerCLIE2ETests/ShowCommand.cs | 302 +- src/AppInstallerCLIE2ETests/SourceCommand.cs | 790 +- src/AppInstallerCLIE2ETests/Test.runsettings | 86 +- .../ConfigServerUnexpectedExit.yml | 34 +- .../Configuration/Configure_TestRepo.yml | 18 +- .../Configure_TestRepo_DSCv3.yml | 20 +- .../Configure_TestRepo_Location.yml | 14 +- .../Configuration/DependencyCycle.yml | 46 +- .../DependentResources_Failure.yml | 34 +- .../Configuration/DuplicateIdentifiers.yml | 38 +- .../Configuration/GetPSModulePath.yml | 22 +- .../IndependentResources_OneFailure.yml | 28 +- .../Configuration/Init-TestRepository.ps1 | 130 +- .../Configuration/LargeContentStrings.yml | 44 +- .../Configuration/MissingDependency.yml | 44 +- .../TestData/Configuration/ModuleMismatch.yml | 16 +- .../Modules/xE2EMalicious/xE2EMalicious.psd1 | 66 +- .../Modules/xE2EMalicious/xE2EMalicious.psm1 | 172 +- .../xE2ETestResource/xE2ETestResource.psd1 | 80 +- .../xE2ETestResource/xE2ETestResource.psm1 | 714 +- .../TestData/Configuration/NoResourceName.yml | 12 +- .../TestData/Configuration/NoVersion.yml | 2 +- .../PSGallery_NoModule_NoSettings.yml | 12 +- .../Configuration/PSGallery_NoSettings.yml | 12 +- .../Configuration/ResourceCaseInsensitive.yml | 18 +- .../Configuration/ResourceNotFound.yml | 16 +- .../Configuration/ResourcesNotASequence.yml | 4 +- .../Configuration/RunCommandOnSet.yml | 30 +- .../Configuration/ShowDetails_DSCv3.yml | 24 +- .../Configuration/ShowDetails_TestRepo.yml | 12 +- .../ShowDetails_TestRepo_0_3.yml | 18 +- .../TestData/Configuration/UnitNotAMap.yml | 6 +- .../TestData/Configuration/UnknownVersion.yml | 2 +- .../Configuration/Unknown_Processor.yml | 24 +- .../Configuration/WithParameters_0_3.yml | 26 +- .../ImportFile-Bad-UnknownPackage.json | 2 +- .../TestData/IndexPackageManifest.xml | 80 +- ...stArpVersionMapping_OppositeOrder_1.0.yaml | 38 +- ...stArpVersionMapping_OppositeOrder_2.0.yaml | 38 +- ...rpVersionMapping_SameAsPackageVersion.yaml | 36 +- .../TestArpVersionMapping_SameOrder_1.0.yaml | 38 +- .../TestArpVersionMapping_SameOrder_2.0.yaml | 38 +- ...stBurnInstaller.MissingRepairBehavior.yaml | 38 +- .../TestBurnInstaller.ModifyRepair.yaml | 40 +- ...urnInstaller.ModifyRepairWithNoModify.yaml | 40 +- ....UserScopeInstallRepairInAdminContext.yaml | 42 +- .../TestData/Manifests/TestBurnInstaller.yaml | 28 +- .../Manifests/TestExampleInstaller.yaml | 38 +- .../Manifests/TestExeInstaller.1.0.1.0.yaml | 70 +- .../Manifests/TestExeInstaller.1.1.0.0.yaml | 70 +- .../Manifests/TestExeInstaller.2.0.0.0.yaml | 70 +- .../TestExeInstaller.UninstallerRepair.yaml | 40 +- ...staller.UninstallerRepairWithNoRepair.yaml | 40 +- .../TestExeInstaller.WindowsFeature.yaml | 54 +- .../TestData/Manifests/TestExeInstaller.yaml | 70 +- .../Manifests/TestExeInstallerForExport.yaml | 40 +- ...stExeInstaller_CatalogPackageMetadata.yaml | 110 +- ...estExeInstaller_InapplicableOsVersion.yaml | 40 +- .../TestExeInstaller_InstallMSIX.yaml | 40 +- .../Manifests/TestExeInstaller_NoScope.yaml | 40 +- .../TestExeInstaller_Sha256Mismatch.yaml | 38 +- .../TestGoodManifestV1_10-SchemaHeader.yaml | 34 +- .../TestInnoInstaller.InstallerRepair.yaml | 40 +- .../TestData/Manifests/TestInnoInstaller.yaml | 26 +- .../Manifests/TestInstalledStatus.yaml | 52 +- .../Manifests/TestInvalidManifest.yaml | 38 +- .../TestMappingWithArchitectureX64.yaml | 42 +- .../TestMappingWithArchitectureX86.yaml | 42 +- .../Manifests/TestMsiInstaller.Repair.yaml | 32 +- .../TestData/Manifests/TestMsiInstaller.yaml | 24 +- .../TestMsiInstaller_UpgradeCode.yaml | 38 +- .../TestData/Manifests/TestMsixInstaller.yaml | 24 +- ...stMsixInstaller_SignatureHashMismatch.yaml | 24 +- .../TestMsixInstaller_WithSignatureHash.yaml | 24 +- .../Manifests/TestMultipleInstallers.yaml | 98 +- ...stNullsoftInstaller.UninstallerRepair.yaml | 40 +- .../Manifests/TestNullsoftInstaller.yaml | 26 +- .../TestPortableInstaller.2.0.0.0.yaml | 32 +- ...leInstaller.3.0.0.0_UninstallPrevious.yaml | 34 +- .../Manifests/TestPortableInstaller.yaml | 32 +- ...estPortableInstallerUninstallPrevious.yaml | 34 +- .../TestPortableInstaller_WithCommand.yaml | 36 +- .../TestUpgradeAddsDependency.1.0.yaml | 40 +- .../TestUpgradeAddsDependency.2.0.yaml | 46 +- ...estUpgradeAddsDependencyDependent.1.0.yaml | 42 +- .../TestUpgradeAvailableApi.1.0.0.0.yaml | 40 +- .../TestUpgradeAvailableApi.2.0.0.0.yaml | 40 +- .../TestData/Manifests/TestValidManifest.yaml | 38 +- .../Manifests/TestWarningManifest.yaml | 32 +- ...ningManifestV1_10-SchemaHeaderInvalid.yaml | 34 +- ...1_10-SchemaHeaderManifestTypeMismatch.yaml | 34 +- ...ingManifestV1_10-SchemaHeaderNotFound.yaml | 30 +- ...tV1_10-SchemaHeaderURLPatternMismatch.yaml | 34 +- ...festV1_10-SchemaHeaderVersionMismatch.yaml | 34 +- .../Manifests/TestZipInstaller_Exe.yaml | 56 +- ...Installer_Exe_InvalidRelativeFilePath.yaml | 44 +- .../Manifests/TestZipInstaller_Msi.yaml | 40 +- .../Manifests/TestZipInstaller_Msix.yaml | 40 +- ...TestZipInstaller_PackageInstallerInfo.yaml | 46 +- .../TestZipInstaller_Portable.2.0.0.0.yaml | 40 +- .../Manifests/TestZipInstaller_Portable.yaml | 40 +- .../Manifests/T\303\253stExeInstaller.yaml" | 38 +- .../Manifests/ZeroByteFile_CorrectHash.yaml | 32 +- .../Manifests/ZeroByteFile_IncorrectHash.yaml | 32 +- .../TestData/Package/AppxManifest.xml | 94 +- .../TestData/README.md | 16 +- .../TestData/TestRestSource/information | 12 +- .../TestData/localsource.json | 112 +- .../UninstallCommand.cs | 440 +- src/AppInstallerCLIE2ETests/UpgradeCommand.cs | 452 +- .../ValidateCommand.cs | 250 +- .../WinGetUtil/WinGetUtilCompareVersions.cs | 26 +- .../WinGetUtil/WinGetUtilDownload.cs | 4 +- .../WinGetUtilInstallerMetadataCollection.cs | 32 +- .../WinGetUtil/WinGetUtilLog.cs | 24 +- .../WinGetUtil/WinGetUtilManifest.cs | 194 +- .../WinGetUtil/WinGetUtilSQLiteIndex.cs | 4 +- .../WinGetUtil/WinGetUtilWrapper.cs | 328 +- .../AppInstallerCLIPackage.wapproj | 554 +- .../Execute-AppxRecipe.ps1 | 100 +- .../Package.appxmanifest | 316 +- .../Register-WingetdevAutoComplete.ps1 | 16 +- .../Shared/Strings/en-us/Resources.resw | 246 +- .../Shared/Strings/en-us/winget.resw | 7302 ++--- src/AppInstallerCLITests/ARPChanges.cpp | 918 +- .../AppInstallerCLITests.vcxproj | 2266 +- .../AppInstallerCLITests.vcxproj.filters | 2364 +- src/AppInstallerCLITests/Archive.cpp | 68 +- src/AppInstallerCLITests/Argument.cpp | 30 +- src/AppInstallerCLITests/ArpHelper.cpp | 142 +- src/AppInstallerCLITests/Certificates.cpp | 690 +- src/AppInstallerCLITests/Command.cpp | 1380 +- src/AppInstallerCLITests/Completion.cpp | 1104 +- src/AppInstallerCLITests/CompositeSource.cpp | 4078 +-- .../ContextOrchestrator.cpp | 110 +- src/AppInstallerCLITests/Correlation.cpp | 612 +- src/AppInstallerCLITests/CustomHeader.cpp | 304 +- src/AppInstallerCLITests/DateTime.cpp | 186 +- src/AppInstallerCLITests/DownloadFlow.cpp | 282 +- src/AppInstallerCLITests/Downloader.cpp | 342 +- src/AppInstallerCLITests/Errors.cpp | 38 +- .../ExperimentalFeature.cpp | 10 +- src/AppInstallerCLITests/ExportFlow.cpp | 382 +- src/AppInstallerCLITests/FileCache.cpp | 480 +- src/AppInstallerCLITests/FileLogger.cpp | 490 +- src/AppInstallerCLITests/Filesystem.cpp | 988 +- src/AppInstallerCLITests/GroupPolicy.cpp | 828 +- src/AppInstallerCLITests/HashCommand.cpp | 44 +- src/AppInstallerCLITests/HttpClientHelper.cpp | 182 +- src/AppInstallerCLITests/IconExtraction.cpp | 34 +- src/AppInstallerCLITests/ImportFlow.cpp | 704 +- .../InstallDependenciesFlow.cpp | 742 +- src/AppInstallerCLITests/InstallFlow.cpp | 2884 +- .../InstallerMetadataCollectionContext.cpp | 2536 +- src/AppInstallerCLITests/JsonHelper.cpp | 226 +- .../LanguageUtilities.cpp | 44 +- .../MSStoreDownloadFlow.cpp | 1326 +- .../ManifestComparator.cpp | 1904 +- .../MatchCriteriaResolver.cpp | 154 +- src/AppInstallerCLITests/MsiExecArguments.cpp | 426 +- src/AppInstallerCLITests/MsixInfo.cpp | 214 +- .../NameNormalization.cpp | 310 +- .../PackageCollection.cpp | 12 +- .../PackageTableSortHelper.cpp | 792 +- .../PackageTrackingCatalog.cpp | 562 +- .../PackageVersionDataManifest.cpp | 132 +- src/AppInstallerCLITests/PinFlow.cpp | 400 +- src/AppInstallerCLITests/PinningIndex.cpp | 330 +- src/AppInstallerCLITests/PortableIndex.cpp | 402 +- .../PortableInstaller.cpp | 430 +- .../PreIndexedPackageSource.cpp | 456 +- .../PredefinedInstalledSource.cpp | 976 +- src/AppInstallerCLITests/PromptFlow.cpp | 326 +- src/AppInstallerCLITests/Regex.cpp | 172 +- src/AppInstallerCLITests/Registry.cpp | 430 +- src/AppInstallerCLITests/Rest.cpp | 112 +- src/AppInstallerCLITests/RestClient.cpp | 1342 +- .../RestInterface_1_0.cpp | 1290 +- .../RestInterface_1_1.cpp | 1118 +- .../RestInterface_1_10.cpp | 822 +- .../RestInterface_1_4.cpp | 852 +- .../RestInterface_1_5.cpp | 808 +- .../RestInterface_1_6.cpp | 812 +- .../RestInterface_1_7.cpp | 1214 +- .../RestInterface_1_9.cpp | 800 +- .../Run-TestsInPackage.ps1 | 368 +- src/AppInstallerCLITests/Runtime.cpp | 860 +- .../SQLiteDynamicStorage.cpp | 86 +- src/AppInstallerCLITests/SQLiteIndex.cpp | 7930 +++--- .../SQLiteIndexSource.cpp | 570 +- src/AppInstallerCLITests/SQLiteWrapper.cpp | 1712 +- .../SearchRequestSerializer.cpp | 176 +- src/AppInstallerCLITests/Settings.cpp | 618 +- src/AppInstallerCLITests/ShowFlow.cpp | 208 +- src/AppInstallerCLITests/Sixel.cpp | 98 +- .../SortParametersResolution.cpp | 426 +- src/AppInstallerCLITests/SourceFlow.cpp | 368 +- src/AppInstallerCLITests/Sources.cpp | 3078 +-- src/AppInstallerCLITests/Strings.cpp | 762 +- src/AppInstallerCLITests/Synchronization.cpp | 126 +- src/AppInstallerCLITests/TestCommon.cpp | 828 +- src/AppInstallerCLITests/TestCommon.h | 346 +- .../TestConfiguration.cpp | 220 +- src/AppInstallerCLITests/TestConfiguration.h | 356 +- .../TestData/ContainsEscapeControlCode.yaml | 2 +- .../TestData/ContainsTooManyNestedLayers.yaml | 202 +- ...oadFlowTest_DownloadCommandProhibited.yaml | 34 +- .../TestData/DownloadFlowTest_MSStore.yaml | 76 +- .../ImportFile-Good-MachineScope.json | 44 +- .../ImportFile-Good-WithLicenseAgreement.json | 2 +- .../TestData/InputARPData.txt | 222 +- .../InstallFlowTest_AbortsTerminal.yaml | 36 +- .../TestData/InstallFlowTest_EncodedUrl.yaml | 36 +- .../TestData/InstallFlowTest_Exe.yaml | 58 +- .../InstallFlowTest_ExpectedReturnCodes.yaml | 98 +- ...stallFlowTest_InstallLocationRequired.yaml | 40 +- .../InstallFlowTest_InstallationNotes.yaml | 36 +- ...stallFlowTest_InvalidFileCharacterUrl.yaml | 36 +- .../InstallFlowTest_LicenseAgreement.yaml | 50 +- .../TestData/InstallFlowTest_MSStore.yaml | 26 +- .../InstallFlowTest_Msix_DownloadFlow.yaml | 26 +- .../InstallFlowTest_Msix_StreamingFlow.yaml | 28 +- .../InstallFlowTest_MultipleDependencies.yaml | 52 +- ...tallFlowTest_NoApplicableArchitecture.yaml | 32 +- .../InstallFlowTest_NonZeroExitCode.yaml | 46 +- .../TestData/InstallFlowTest_Portable.yaml | 36 +- .../InstallFlowTest_Portable_WithCommand.yaml | 38 +- .../InstallFlowTest_UnknownVersion.yaml | 42 +- .../InstallFlowTest_UnsupportedArguments.yaml | 40 +- .../InstallFlowTest_WindowsFeatures.yaml | 50 +- .../TestData/InstallFlowTest_Zip_Exe.yaml | 50 +- ...llFlowTest_Zip_MissingNestedInstaller.yaml | 46 +- ...p_MultipleNonPortableNestedInstallers.yaml | 42 +- ...owTest_Zip_UnsupportedNestedInstaller.yaml | 46 +- .../InstallerArgTest_Inno_NoSwitches.yaml | 24 +- .../InstallerArgTest_Inno_WithSwitches.yaml | 36 +- .../InstallerArgTest_Msi_NoSwitches.yaml | 26 +- .../InstallerArgTest_Msi_WithSwitches.yaml | 38 +- .../TestData/Installer_Exe_Dependencies.yaml | 46 +- ...ller_Exe_DependenciesMultideclaration.yaml | 52 +- .../Installer_Exe_DependenciesOnRoot.yaml | 46 +- .../TestData/Installer_Msix_WFDependency.yaml | 42 +- .../Manifest-Bad-Channel-NotSupported.yaml | 24 +- .../Manifest-Bad-DifferentCase-UPPER.yaml | 24 +- .../Manifest-Bad-DifferentCase-lower.yaml | 24 +- ...-Bad-DuplicateKey-DifferentCase-lower.yaml | 26 +- ...Bad-DuplicateReturnCode-ExpectedCodes.yaml | 46 +- ...-Bad-DuplicateReturnCode-SuccessCodes.yaml | 46 +- ...anifest-Bad-InstallerTypeExe-NoSilent.yaml | 28 +- ...est-Bad-InstallerTypeExe-NoSilentRoot.yaml | 28 +- ...est-Bad-InstallerTypeExeRoot-NoSilent.yaml | 28 +- ...Bad-InstallerTypeExeRoot-NoSilentRoot.yaml | 28 +- ...erTypePortable-InvalidAppsAndFeatures.yaml | 88 +- ...InstallerTypePortable-InvalidCommands.yaml | 56 +- ...ad-InstallerTypePortable-InvalidScope.yaml | 48 +- ...nstallerTypeZip-DuplicateCommandAlias.yaml | 48 +- ...llerTypeZip-DuplicateRelativeFilePath.yaml | 48 +- ...tallerTypeZip-InvalidRelativeFilePath.yaml | 44 +- ...tallerTypeZip-MissingRelativeFilePath.yaml | 44 +- ...allerTypeZip-MultipleNestedInstallers.yaml | 50 +- ...nstallerTypeZip-NoNestedInstallerFile.yaml | 40 +- ...nstallerTypeZip-NoNestedInstallerType.yaml | 44 +- .../Manifest-Bad-PublisherMissing.yaml | 22 +- .../TestData/Manifest-Bad-SwitchInvalid.yaml | 28 +- ...Manifest-Good-InstallerTypeExe-Silent.yaml | 30 +- ...fest-Good-InstallerTypeExe-SilentRoot.yaml | 30 +- ...fest-Good-InstallerTypeExeRoot-Silent.yaml | 30 +- ...-Good-InstallerTypeExeRoot-SilentRoot.yaml | 30 +- .../TestData/Manifest-Good-Switches.yaml | 32 +- .../Manifest-MSIX-in-AppsAndFeatures.yaml | 2 +- .../TestData/Manifest-MSIX-in-Archive.yaml | 4 +- ...ManifestV1_10-Bad-SchemaHeaderInvalid.yaml | 34 +- ...-Bad-SchemaHeaderManifestTypeMismatch.yaml | 32 +- ...d-SchemaHeaderManifestVersionMismatch.yaml | 34 +- ...anifestV1_10-Bad-SchemaHeaderNotFound.yaml | 30 +- ...10-Bad-SchemaHeaderURLPatternMismatch.yaml | 34 +- ...ManifestV1_10-InstallerAuthentication.yaml | 50 +- .../TestData/ManifestV1_10-Singleton.yaml | 404 +- .../TestData/ManifestV1_2-Singleton.yaml | 354 +- .../TestData/ManifestV1_28-PowerShellDSC.yaml | 42 +- .../TestData/ManifestV1_28-Singleton.yaml | 8 +- .../TestData/ManifestV1_4-Singleton.yaml | 378 +- .../TestData/ManifestV1_5-Singleton.yaml | 390 +- .../TestData/ManifestV1_6-Singleton.yaml | 394 +- .../TestData/ManifestV1_7-Singleton.yaml | 400 +- ...ManifestV1_10-MultiFile-DefaultLocale.yaml | 82 +- .../ManifestV1_10-MultiFile-Installer.yaml | 426 +- .../ManifestV1_10-MultiFile-Locale.yaml | 80 +- .../ManifestV1_10-MultiFile-Version.yaml | 14 +- .../ManifestV1_28-MultiFile-Installer.yaml | 6 +- .../TestData/Node-Merge.yaml | 16 +- .../TestData/Node-Merge2.yaml | 14 +- .../TestData/Node-Types.yaml | 20 +- .../TestData/UpdateFlowTest_Exe.yaml | 36 +- .../UpdateFlowTest_ExeDependencies.yaml | 56 +- .../TestData/UpdateFlowTest_Exe_2.yaml | 36 +- ...UpdateFlowTest_Exe_2_LicenseAgreement.yaml | 52 +- .../UpdateFlowTest_Exe_ARPInstallerType.yaml | 50 +- .../UpdateFlowTest_Exe_UnsupportedArgs.yaml | 52 +- .../UpdateFlowTest_ExpectedReturnCodes.yaml | 100 +- .../TestData/UpdateFlowTest_Msix.yaml | 30 +- .../UpdateFlowTest_Msix_LicenseAgreement.yaml | 46 +- .../TestData/UpdateFlowTest_Portable.yaml | 38 +- .../TestData/UpdateFlowTest_Zip_Exe.yaml | 52 +- src/AppInstallerCLITests/TestHooks.h | 868 +- .../TestRestRequestHandler.cpp | 148 +- .../TestRestRequestHandler.h | 54 +- src/AppInstallerCLITests/TestSettings.cpp | 148 +- src/AppInstallerCLITests/TestSettings.h | 194 +- src/AppInstallerCLITests/TestSource.cpp | 1054 +- src/AppInstallerCLITests/TestSource.h | 436 +- src/AppInstallerCLITests/UninstallFlow.cpp | 424 +- src/AppInstallerCLITests/UpdateFlow.cpp | 2114 +- src/AppInstallerCLITests/UserSettings.cpp | 40 +- src/AppInstallerCLITests/Versions.cpp | 944 +- src/AppInstallerCLITests/WindowsFeature.cpp | 372 +- src/AppInstallerCLITests/WorkFlow.cpp | 378 +- src/AppInstallerCLITests/WorkflowCommon.cpp | 1476 +- src/AppInstallerCLITests/WorkflowCommon.h | 288 +- src/AppInstallerCLITests/Yaml.cpp | 360 +- src/AppInstallerCLITests/YamlManifest.cpp | 3690 +-- src/AppInstallerCLITests/packages.config | 8 +- src/AppInstallerCLITests/pch.h | 2 +- src/AppInstallerCommonCore/AdminSettings.cpp | 956 +- .../AppInstallerCommonCore.vcxproj | 958 +- .../AppInstallerCommonCore.vcxproj.filters | 798 +- .../AppInstallerTelemetry.cpp | 2032 +- src/AppInstallerCommonCore/Architecture.cpp | 572 +- src/AppInstallerCommonCore/Archive.cpp | 158 +- .../Authentication/Authentication.cpp | 58 +- .../WebAccountManagerAuthenticator.cpp | 14 +- .../WebAccountManagerAuthenticator.h | 64 +- src/AppInstallerCommonCore/DODownloader.cpp | 1014 +- src/AppInstallerCommonCore/DODownloader.h | 54 +- src/AppInstallerCommonCore/Debugging.cpp | 208 +- src/AppInstallerCommonCore/Deployment.cpp | 830 +- src/AppInstallerCommonCore/Downloader.cpp | 1380 +- .../ExtensionCatalog.cpp | 142 +- src/AppInstallerCommonCore/FileCache.cpp | 494 +- src/AppInstallerCommonCore/FileLogger.cpp | 468 +- .../FolderFileWatcher.cpp | 134 +- .../HttpClientHelper.cpp | 572 +- .../HttpStream/HttpClientWrapper.cpp | 378 +- .../HttpStream/HttpClientWrapper.h | 100 +- .../HttpStream/HttpLocalCache.cpp | 490 +- .../HttpStream/HttpLocalCache.h | 138 +- .../HttpStream/HttpRandomAccessStream.cpp | 202 +- .../HttpStream/HttpRandomAccessStream.h | 88 +- src/AppInstallerCommonCore/Locale.cpp | 302 +- src/AppInstallerCommonCore/MSStore.cpp | 1032 +- .../MSStoreDownload.cpp | 2386 +- .../Manifest/Manifest.cpp | 484 +- .../Manifest/ManifestCommon.cpp | 2488 +- .../Manifest/ManifestComparator.cpp | 1832 +- .../Manifest/ManifestSchemaValidation.cpp | 924 +- .../Manifest/ManifestValidation.cpp | 1362 +- .../Manifest/ManifestYamlPopulator.cpp | 2930 +- .../Manifest/YamlParser.cpp | 1224 +- .../Manifest/YamlWriter.cpp | 208 +- .../MsiExecArguments.cpp | 1196 +- src/AppInstallerCommonCore/MsixInfo.cpp | 1674 +- .../NameNormalization.cpp | 1056 +- .../NetworkSettings.cpp | 12 +- .../OutputDebugStringLogger.cpp | 100 +- .../PackageVersionDataManifest.cpp | 378 +- src/AppInstallerCommonCore/PathVariable.cpp | 270 +- src/AppInstallerCommonCore/Pin.cpp | 244 +- .../PortableARPEntry.cpp | 246 +- src/AppInstallerCommonCore/Progress.cpp | 310 +- .../Public/AppInstallerArchitecture.h | 100 +- .../Public/AppInstallerDeployment.h | 154 +- .../Public/AppInstallerDownloader.h | 290 +- .../Public/AppInstallerFileLogger.h | 148 +- .../Public/AppInstallerMsixInfo.h | 274 +- .../Public/AppInstallerProgress.h | 294 +- .../Public/AppInstallerRuntime.h | 194 +- .../Public/AppInstallerSynchronization.h | 118 +- .../Public/AppInstallerTelemetry.h | 712 +- .../Public/winget/AdminSettings.h | 102 +- .../Public/winget/Archive.h | 36 +- .../Public/winget/Authentication.h | 284 +- .../Public/winget/Debugging.h | 38 +- .../Public/winget/ExtensionCatalog.h | 104 +- .../Public/winget/FileCache.h | 128 +- .../Public/winget/FolderFileWatcher.h | 70 +- .../Public/winget/HttpClientHelper.h | 110 +- .../Public/winget/Locale.h | 56 +- .../Public/winget/MSStore.h | 98 +- .../Public/winget/MSStoreDownload.h | 122 +- .../Public/winget/Manifest.h | 148 +- .../Public/winget/ManifestCommon.h | 1052 +- .../Public/winget/ManifestComparator.h | 274 +- .../Public/winget/ManifestInstaller.h | 252 +- .../Public/winget/ManifestLocalization.h | 302 +- .../Public/winget/ManifestSchemaValidation.h | 54 +- .../Public/winget/ManifestValidation.h | 452 +- .../Public/winget/ManifestYamlParser.h | 86 +- .../Public/winget/ManifestYamlPopulator.h | 288 +- .../Public/winget/MsiExecArguments.h | 144 +- .../Public/winget/NameNormalization.h | 190 +- .../Public/winget/OutputDebugStringLogger.h | 58 +- .../winget/PackageVersionDataManifest.h | 132 +- .../Public/winget/PathVariable.h | 64 +- .../Public/winget/Pin.h | 236 +- .../Public/winget/PortableARPEntry.h | 120 +- .../Public/winget/PortableFileEntry.h | 160 +- .../Public/winget/Regex.h | 110 +- .../Public/winget/Rest.h | 40 +- .../Public/winget/SelfManagement.h | 42 +- .../Public/winget/Settings.h | 228 +- .../Public/winget/StdErrLogger.h | 62 +- .../Public/winget/TraceLogger.h | 60 +- .../Public/winget/UserSettings.h | 628 +- src/AppInstallerCommonCore/Regex.cpp | 552 +- src/AppInstallerCommonCore/Rest.cpp | 142 +- src/AppInstallerCommonCore/Runtime.cpp | 1220 +- src/AppInstallerCommonCore/SelfManagement.cpp | 112 +- src/AppInstallerCommonCore/Settings.cpp | 1088 +- src/AppInstallerCommonCore/StdErrLogger.cpp | 102 +- .../Synchronization.cpp | 146 +- src/AppInstallerCommonCore/ThreadGlobals.cpp | 120 +- src/AppInstallerCommonCore/TraceLogger.cpp | 98 +- src/AppInstallerCommonCore/UserSettings.cpp | 1478 +- src/AppInstallerCommonCore/packages.config | 8 +- src/AppInstallerCommonCore/pch.h | 232 +- .../ARPCorrelation.cpp | 588 +- .../ARPCorrelationAlgorithms.cpp | 394 +- .../AppInstallerRepositoryCore.vcxproj | 1052 +- ...AppInstallerRepositoryCore.vcxproj.filters | 1644 +- .../ArpVersionValidation.cpp | 178 +- .../CompositeSource.cpp | 3560 +-- .../CompositeSource.h | 128 +- src/AppInstallerRepositoryCore/ISource.h | 246 +- src/AppInstallerRepositoryCore/IconDefs.h | 146 +- .../InstalledFilesCorrelation.cpp | 644 +- .../InstallerMetadataCollectionContext.cpp | 3048 +-- .../ManifestJSONParser.cpp | 218 +- .../MatchCriteriaResolver.cpp | 122 +- .../Microsoft/ARPHelper.cpp | 1176 +- .../Microsoft/ARPHelper.h | 202 +- .../ConfigurableTestSourceFactory.cpp | 646 +- .../Microsoft/ConfigurableTestSourceFactory.h | 52 +- .../Microsoft/PinningIndex.cpp | 422 +- .../Microsoft/PinningIndex.h | 148 +- .../Microsoft/PortableIndex.cpp | 290 +- .../PreIndexedPackageSourceFactory.cpp | 1756 +- .../PreIndexedPackageSourceFactory.h | 66 +- .../PredefinedInstalledSourceFactory.cpp | 1008 +- .../PredefinedInstalledSourceFactory.h | 100 +- .../PredefinedWriteableSourceFactory.cpp | 218 +- .../PredefinedWriteableSourceFactory.h | 72 +- .../Microsoft/README.md | 18 +- .../Microsoft/SQLiteIndex.cpp | 758 +- .../Microsoft/SQLiteIndex.h | 398 +- .../Microsoft/SQLiteIndexSource.cpp | 288 +- .../Microsoft/SQLiteIndexSource.h | 198 +- .../Microsoft/SQLiteIndexSourceV1.cpp | 548 +- .../Microsoft/SQLiteIndexSourceV1.h | 114 +- .../Microsoft/SQLiteIndexSourceV2.cpp | 1228 +- .../Microsoft/SQLiteIndexSourceV2.h | 136 +- .../Microsoft/Schema/1_0/ChannelTable.h | 44 +- .../Microsoft/Schema/1_0/CommandsTable.h | 44 +- .../Microsoft/Schema/1_0/IdTable.h | 44 +- .../Microsoft/Schema/1_0/Interface.h | 138 +- .../Microsoft/Schema/1_0/Interface_1_0.cpp | 1336 +- .../Microsoft/Schema/1_0/ManifestTable.cpp | 1066 +- .../Microsoft/Schema/1_0/ManifestTable.h | 484 +- .../Microsoft/Schema/1_0/MonikerTable.h | 44 +- .../Microsoft/Schema/1_0/NameTable.h | 46 +- .../Microsoft/Schema/1_0/OneToManyTable.cpp | 936 +- .../Microsoft/Schema/1_0/OneToManyTable.h | 324 +- .../Microsoft/Schema/1_0/OneToOneTable.cpp | 434 +- .../Microsoft/Schema/1_0/OneToOneTable.h | 312 +- .../Microsoft/Schema/1_0/PathPartTable.cpp | 906 +- .../Microsoft/Schema/1_0/PathPartTable.h | 138 +- .../Microsoft/Schema/1_0/SearchResultsTable.h | 130 +- .../Schema/1_0/SearchResultsTable_1_0.cpp | 604 +- .../Microsoft/Schema/1_0/TagsTable.h | 44 +- .../Microsoft/Schema/1_0/VersionTable.h | 44 +- .../Microsoft/Schema/1_0/VirtualTableBase.h | 40 +- .../Microsoft/Schema/1_1/Interface.h | 84 +- .../Microsoft/Schema/1_1/Interface_1_1.cpp | 586 +- .../Schema/1_1/ManifestMetadataTable.cpp | 262 +- .../Schema/1_1/ManifestMetadataTable.h | 88 +- .../Schema/1_1/PackageFamilyNameTable.h | 44 +- .../Microsoft/Schema/1_1/ProductCodeTable.h | 44 +- .../Microsoft/Schema/1_1/SearchResultsTable.h | 56 +- .../Schema/1_1/SearchResultsTable_1_1.cpp | 60 +- .../Microsoft/Schema/1_2/Interface.h | 76 +- .../Microsoft/Schema/1_2/Interface_1_2.cpp | 690 +- .../Schema/1_2/NormalizedPackageNameTable.h | 44 +- .../1_2/NormalizedPackagePublisherTable.h | 44 +- .../Microsoft/Schema/1_2/SearchResultsTable.h | 66 +- .../Schema/1_2/SearchResultsTable_1_2.cpp | 76 +- .../Microsoft/Schema/1_3/HashVirtualTable.h | 62 +- .../Microsoft/Schema/1_3/Interface.h | 50 +- .../Microsoft/Schema/1_3/Interface_1_3.cpp | 180 +- .../Schema/1_4/DependenciesTable.cpp | 1154 +- .../Microsoft/Schema/1_4/DependenciesTable.h | 6 +- .../Microsoft/Schema/1_4/Interface.h | 2 +- .../Microsoft/Schema/1_4/Interface_1_4.cpp | 376 +- .../Schema/1_5/ArpVersionVirtualTable.h | 136 +- .../Microsoft/Schema/1_5/Interface.h | 8 +- .../Microsoft/Schema/1_5/Interface_1_5.cpp | 570 +- .../Microsoft/Schema/1_6/Interface.h | 64 +- .../Microsoft/Schema/1_6/Interface_1_6.cpp | 330 +- .../Microsoft/Schema/1_6/SearchResultsTable.h | 56 +- .../Schema/1_6/SearchResultsTable_1_6.cpp | 54 +- .../Microsoft/Schema/1_6/UpgradeCodeTable.h | 46 +- .../Microsoft/Schema/1_7/Interface.h | 60 +- .../Microsoft/Schema/1_7/Interface_1_7.cpp | 228 +- .../Microsoft/Schema/2_0/CommandsTable.h | 42 +- .../Microsoft/Schema/2_0/Interface.h | 196 +- .../Microsoft/Schema/2_0/Interface_2_0.cpp | 1576 +- .../Schema/2_0/NormalizedPackageNameTable.h | 44 +- .../2_0/NormalizedPackagePublisherTable.h | 42 +- .../Schema/2_0/OneToManyTableWithMap.cpp | 900 +- .../Schema/2_0/OneToManyTableWithMap.h | 274 +- .../Schema/2_0/PackageFamilyNameTable.h | 42 +- .../Schema/2_0/PackageUpdateTrackingTable.cpp | 498 +- .../Schema/2_0/PackageUpdateTrackingTable.h | 104 +- .../Microsoft/Schema/2_0/PackagesTable.cpp | 658 +- .../Microsoft/Schema/2_0/PackagesTable.h | 412 +- .../Microsoft/Schema/2_0/ProductCodeTable.h | 42 +- .../Microsoft/Schema/2_0/SearchResultsTable.h | 130 +- .../Schema/2_0/SearchResultsTable_2_0.cpp | 640 +- .../Schema/2_0/SystemReferenceStringTable.cpp | 532 +- .../Schema/2_0/SystemReferenceStringTable.h | 276 +- .../Microsoft/Schema/2_0/TagsTable.h | 42 +- .../Microsoft/Schema/2_0/UpgradeCodeTable.h | 42 +- .../Microsoft/Schema/IPinningIndex.h | 80 +- .../Microsoft/Schema/IPortableIndex.h | 80 +- .../Microsoft/Schema/ISQLiteIndex.cpp | 172 +- .../Microsoft/Schema/ISQLiteIndex.h | 326 +- .../Microsoft/Schema/Pinning_1_0/PinTable.cpp | 390 +- .../Microsoft/Schema/Pinning_1_0/PinTable.h | 86 +- .../Pinning_1_0/PinningIndexInterface.h | 44 +- .../Pinning_1_0/PinningIndexInterface_1_0.cpp | 210 +- .../PortableIndexInterface_1_0.cpp | 180 +- .../Schema/Portable_1_0/PortableTable.cpp | 356 +- .../Schema/Portable_1_0/PortableTable.h | 92 +- .../Microsoft/Schema/SQLiteIndexContextData.h | 100 +- .../PackageDependenciesValidation.cpp | 2 +- .../PackageInstalledStatus.cpp | 494 +- .../PackageTrackingCatalog.cpp | 644 +- .../PackageTrackingCatalogSourceFactory.h | 50 +- .../PackageVersionSelection.cpp | 616 +- .../PinningData.cpp | 432 +- .../Public/winget/ARPCorrelation.h | 308 +- .../Public/winget/ARPCorrelationAlgorithms.h | 124 +- .../Public/winget/InstalledFilesCorrelation.h | 116 +- .../Public/winget/InstalledStatus.h | 128 +- .../InstallerMetadataCollectionContext.h | 374 +- .../Public/winget/ManifestJSONParser.h | 94 +- .../Public/winget/PackageTrackingCatalog.h | 166 +- .../Public/winget/PackageVersionSelection.h | 88 +- .../Public/winget/PinningData.h | 216 +- .../Public/winget/PortableIndex.h | 124 +- .../Public/winget/RepositorySearch.h | 898 +- .../Public/winget/RepositorySource.h | 778 +- .../RepositorySearch.cpp | 510 +- .../RepositorySource.cpp | 2300 +- .../Rest/RestClient.cpp | 496 +- .../Rest/RestClient.h | 110 +- .../Rest/RestInformationCache.cpp | 498 +- .../Rest/RestInformationCache.h | 94 +- .../Rest/RestSource.cpp | 1096 +- .../Rest/RestSource.h | 110 +- .../Rest/RestSourceFactory.cpp | 322 +- .../Rest/RestSourceFactory.h | 54 +- .../Rest/Schema/1_0/Interface.h | 100 +- .../Schema/1_0/Json/ManifestDeserializer.h | 110 +- .../1_0/Json/ManifestDeserializer_1_0.cpp | 1138 +- .../Schema/1_0/Json/SearchRequestSerializer.h | 46 +- .../1_0/Json/SearchRequestSerializer_1_0.cpp | 400 +- .../1_0/Json/SearchResponseDeserializer.h | 38 +- .../Json/SearchResponseDeserializer_1_0.cpp | 256 +- .../Rest/Schema/1_0/RestInterface_1_0.cpp | 612 +- .../Rest/Schema/1_1/Interface.h | 72 +- .../Schema/1_1/Json/ManifestDeserializer.h | 52 +- .../1_1/Json/ManifestDeserializer_1_1.cpp | 590 +- .../Schema/1_1/Json/SearchRequestSerializer.h | 30 +- .../1_1/Json/SearchRequestSerializer_1_1.cpp | 34 +- .../Rest/Schema/1_1/RestInterface_1_1.cpp | 426 +- .../Rest/Schema/1_10/Interface.h | 42 +- .../Schema/1_10/Json/ManifestDeserializer.h | 34 +- .../1_10/Json/ManifestDeserializer_1_10.cpp | 60 +- .../Rest/Schema/1_10/RestInterface_1_10.cpp | 52 +- .../1_28/Json/ManifestDeserializer_1_28.cpp | 12 +- .../Rest/Schema/1_4/Interface.h | 42 +- .../Schema/1_4/Json/ManifestDeserializer.h | 52 +- .../1_4/Json/ManifestDeserializer_1_4.cpp | 466 +- .../1_4/Json/SearchResponseDeserializer.h | 30 +- .../Json/SearchResponseDeserializer_1_4.cpp | 68 +- .../Rest/Schema/1_4/RestInterface_1_4.cpp | 50 +- .../Rest/Schema/1_5/Interface.h | 42 +- .../Schema/1_5/Json/ManifestDeserializer.h | 34 +- .../1_5/Json/ManifestDeserializer_1_5.cpp | 156 +- .../Rest/Schema/1_5/RestInterface_1_5.cpp | 50 +- .../Rest/Schema/1_6/Interface.h | 42 +- .../Schema/1_6/Json/ManifestDeserializer.h | 38 +- .../1_6/Json/ManifestDeserializer_1_6.cpp | 94 +- .../Rest/Schema/1_6/RestInterface_1_6.cpp | 50 +- .../Rest/Schema/1_7/Interface.h | 54 +- .../Schema/1_7/Json/ManifestDeserializer.h | 38 +- .../1_7/Json/ManifestDeserializer_1_7.cpp | 100 +- .../Rest/Schema/1_7/RestInterface_1_7.cpp | 110 +- .../Rest/Schema/1_9/Interface.h | 42 +- .../Schema/1_9/Json/ManifestDeserializer.h | 34 +- .../1_9/Json/ManifestDeserializer_1_9.cpp | 70 +- .../Rest/Schema/1_9/RestInterface_1_9.cpp | 52 +- .../Rest/Schema/AuthenticationInfoParser.cpp | 226 +- .../Rest/Schema/AuthenticationInfoParser.h | 54 +- .../Rest/Schema/CommonRestConstants.h | 62 +- .../Rest/Schema/IRestClient.h | 198 +- .../InformationResponseDeserializer.cpp | 284 +- .../Schema/InformationResponseDeserializer.h | 34 +- .../Rest/Schema/SearchRequestComposer.cpp | 98 +- .../Rest/Schema/SearchRequestComposer.h | 66 +- .../Rest/Schema/SearchResponseParser.cpp | 98 +- .../Rest/Schema/SearchResponseParser.h | 66 +- .../SourceFactory.h | 94 +- src/AppInstallerRepositoryCore/SourceList.cpp | 2142 +- src/AppInstallerRepositoryCore/SourceList.h | 250 +- .../SourcePolicy.cpp | 468 +- src/AppInstallerRepositoryCore/SourcePolicy.h | 52 +- .../SourceUpdateChecks.cpp | 148 +- .../SourceUpdateChecks.h | 40 +- .../packages.config | 8 +- src/AppInstallerRepositoryCore/pch.h | 18 +- .../AppInstallerSharedLib.vcxproj | 848 +- .../AppInstallerSharedLib.vcxproj.filters | 502 +- .../AppInstallerStrings.cpp | 2324 +- .../COMStaticStorage.cpp | 76 +- src/AppInstallerSharedLib/Certificates.cpp | 1650 +- src/AppInstallerSharedLib/Compression.cpp | 186 +- src/AppInstallerSharedLib/DateTime.cpp | 394 +- src/AppInstallerSharedLib/Errors.cpp | 1002 +- src/AppInstallerSharedLib/Filesystem.cpp | 1656 +- src/AppInstallerSharedLib/GroupPolicy.cpp | 924 +- src/AppInstallerSharedLib/ICU/SQLiteICU.h | 22 +- .../JsonSchemaValidation.cpp | 122 +- src/AppInstallerSharedLib/JsonUtil.cpp | 22 +- src/AppInstallerSharedLib/ManagedFile.cpp | 96 +- .../Public/AppInstallerDateTime.h | 140 +- .../Public/AppInstallerErrors.h | 570 +- .../Public/AppInstallerLanguageUtilities.h | 490 +- .../Public/AppInstallerLogging.h | 488 +- .../Public/AppInstallerSHA256.h | 190 +- .../Public/AppInstallerStrings.h | 664 +- .../Public/AppInstallerVersions.h | 598 +- .../Public/winget/AsyncTokens.h | 422 +- .../Public/winget/COMStaticStorage.h | 166 +- .../Public/winget/Certificates.h | 512 +- .../Public/winget/Compression.h | 108 +- .../ConfigurationSetProcessorHandlers.h | 28 +- .../Public/winget/Filesystem.h | 310 +- .../Public/winget/GroupPolicy.h | 488 +- .../winget/IConfigurationStaticsInternals.h | 62 +- .../Public/winget/ILifetimeWatcher.h | 86 +- .../Public/winget/JsonSchemaValidation.h | 44 +- .../Public/winget/JsonUtil.h | 4 +- .../Public/winget/ManagedFile.h | 70 +- .../Public/winget/ModuleCountBase.h | 54 +- .../Public/winget/PathTree.h | 258 +- .../Public/winget/Registry.h | 626 +- .../Public/winget/Resources.h | 298 +- .../Public/winget/Runtime.h | 134 +- .../Public/winget/SQLiteDynamicStorage.h | 114 +- .../Public/winget/SQLiteMetadataTable.h | 134 +- .../Public/winget/SQLiteStatementBuilder.h | 1140 +- .../Public/winget/SQLiteStorageBase.h | 112 +- .../Public/winget/SQLiteTempTable.h | 70 +- .../Public/winget/SQLiteVersion.h | 128 +- .../Public/winget/SQLiteWrapper.h | 952 +- .../Public/winget/Security.h | 76 +- .../Public/winget/Yaml.h | 728 +- src/AppInstallerSharedLib/Registry.cpp | 1198 +- src/AppInstallerSharedLib/Resources.cpp | 304 +- src/AppInstallerSharedLib/Runtime.cpp | 476 +- src/AppInstallerSharedLib/SHA256.cpp | 420 +- .../SQLiteDynamicStorage.cpp | 182 +- .../SQLiteMetadataTable.cpp | 126 +- .../SQLiteStatementBuilder.cpp | 2020 +- .../SQLiteStorageBase.cpp | 370 +- src/AppInstallerSharedLib/SQLiteTempTable.cpp | 86 +- src/AppInstallerSharedLib/SQLiteVersion.cpp | 128 +- src/AppInstallerSharedLib/SQLiteWrapper.cpp | 1148 +- src/AppInstallerSharedLib/Security.cpp | 280 +- .../SharedThreadGlobals.cpp | 90 +- src/AppInstallerSharedLib/Versions.cpp | 1436 +- src/AppInstallerSharedLib/Yaml.cpp | 1772 +- src/AppInstallerSharedLib/YamlWrapper.cpp | 1158 +- src/AppInstallerSharedLib/YamlWrapper.h | 342 +- src/AppInstallerSharedLib/packages.config | 8 +- src/AppInstallerSharedLib/pch.h | 8 +- .../AppInstallerTestExeInstaller.vcxproj | 272 +- ...pInstallerTestExeInstaller.vcxproj.filters | 42 +- src/AppInstallerTestExeInstaller/main.cpp | 1374 +- .../AppInstallerTestMsiInstaller.vdproj | 1418 +- .../AppInstallerTestMsixInstaller.wapproj | 186 +- .../Package.appxmanifest | 98 +- src/COMServer/COMServer.vcxitems | 24 +- .../CertificateResources.h | 28 +- .../CertificateResources.rc | 152 +- .../CertificateResources.vcxitems | 64 +- .../CertificateResources.vcxitems.filters | 80 +- src/CertificateResources/resource.h | 28 +- src/CodeAnalysis.ruleset | 500 +- .../ComInprocTestbed.manifest | 72 +- src/ComInprocTestbed/ComInprocTestbed.vcxproj | 444 +- .../ComInprocTestbed.vcxproj.filters | 100 +- src/ComInprocTestbed/PackageManager.cpp | 402 +- src/ComInprocTestbed/PackageManager.h | 46 +- src/ComInprocTestbed/Tests.cpp | 1266 +- src/ComInprocTestbed/Tests.h | 250 +- src/ComInprocTestbed/main.cpp | 152 +- src/ComInprocTestbed/packages.config | 6 +- src/ComInprocTestbed/pch.h | 44 +- .../ConfigurationRemotingServer.csproj | 102 +- .../EnvironmentChangeListener.cs | 360 +- src/ConfigurationRemotingServer/Program.cs | 624 +- src/Directory.Packages.props | 84 +- src/Get-VcxprojNugetPackageVersions.ps1 | 676 +- .../IndexCreationTool.csproj | 48 +- src/IndexCreationTool/Program.cs | 102 +- .../LocalhostWebServer.csproj | 46 +- src/LocalhostWebServer/Program.cs | 356 +- .../Run-LocalhostWebServer.ps1 | 152 +- src/LocalhostWebServer/Startup.cs | 208 +- src/ManifestSchema/ManifestSchema.h | 160 +- src/ManifestSchema/ManifestSchema.rc | 264 +- src/ManifestSchema/ManifestSchema.vcxitems | 160 +- .../ManifestSchema.vcxitems.filters | 440 +- src/ManifestSchema/resource.h | 28 +- .../Collect-ConfigurationOOPTests.ps1 | 26 +- .../Factory.cpp | 264 +- .../Factory.h | 82 +- ...Management.Configuration.OutOfProc.vcxproj | 854 +- ...nt.Configuration.OutOfProc.vcxproj.filters | 118 +- .../Prepare-ConfigurationOOPTests.ps1 | 70 +- .../Run-ConfigurationOOPTests.ps1 | 138 +- .../dllmain.cpp | 142 +- .../packages.config | 8 +- .../pch.cpp | 8 +- .../pch.h | 26 +- .../Constants/DirectiveConstants.cs | 48 +- .../Helpers/FindDscPackageStateMachine.cs | 372 +- .../DSCv3/Helpers/IDiagnosticsSink.cs | 42 +- .../DSCv3/Helpers/PackageInformation.cs | 146 +- .../DSCv3/Helpers/ProcessExecution.cs | 608 +- .../ProcessExecutionEnvironmentVariable.cs | 68 +- ...ssExecutionEnvironmentVariableValueType.cs | 58 +- .../DSCv3/Helpers/ProcessOutputBatcher.cs | 240 +- .../DSCv3/Helpers/ProcessorPathIntegrity.cs | 404 +- .../DSCv3/Helpers/ProcessorRunSettings.cs | 118 +- .../DSCv3/Helpers/ProcessorSettings.cs | 752 +- .../DSCv3/Helpers/ResourceDetails.cs | 392 +- .../DSCv3/Model/IDSCv3.cs | 152 +- .../DSCv3/Model/IResourceExportItem.cs | 84 +- .../DSCv3/Model/IResourceGetItem.cs | 42 +- .../DSCv3/Model/IResourceListItem.cs | 102 +- .../DSCv3/Model/IResourceSetItem.cs | 42 +- .../DSCv3/Model/IResourceTestItem.cs | 38 +- .../DSCv3/Model/ResourceKind.cs | 90 +- .../DSCv3/Schema_2024_04/DSCv3.cs | 674 +- .../Definitions/ResourceKind.cs | 104 +- .../Metadata/ResourceInstanceResult.cs | 72 +- .../Outputs/ConfigurationDocument.cs | 112 +- .../Schema_2024_04/Outputs/FullItemBase.cs | 254 +- .../Schema_2024_04/Outputs/GetFullItem.cs | 92 +- .../Schema_2024_04/Outputs/GetSimpleItem.cs | 48 +- .../Outputs/ResourceCapability.cs | 120 +- .../Schema_2024_04/Outputs/ResourceItem.cs | 162 +- .../Outputs/ResourceListItem.cs | 192 +- .../Schema_2024_04/Outputs/SetFullItem.cs | 54 +- .../Schema_2024_04/Outputs/SetSimpleItem.cs | 74 +- .../Schema_2024_04/Outputs/TestFullItem.cs | 102 +- .../Schema_2024_04/Outputs/TestSimpleItem.cs | 86 +- .../Set/DSCv3ConfigurationSetProcessor.cs | 166 +- .../Unit/DSCv3ConfigurationUnitProcessor.cs | 216 +- .../DscProcessorHashMismatchException.cs | 52 +- .../Exceptions/ErrorCodes.cs | 168 +- .../FindDscResourceNotFoundException.cs | 80 +- .../GetDscResourceModuleConflict.cs | 86 +- .../GetDscResourceMultipleMatches.cs | 80 +- .../IConfigurationUnitResultException.cs | 64 +- .../Exceptions/ImportModuleException.cs | 102 +- .../Exceptions/InstallDscResourceException.cs | 80 +- .../Exceptions/InvokeDscResourceException.cs | 368 +- .../UnitPropertyUnsupportedException.cs | 102 +- .../UnitSettingConfigRootException.cs | 78 +- .../Extensions/DictionaryExtensions.cs | 124 +- .../Extensions/ExceptionExtensions.cs | 64 +- .../Extensions/HashtableExtensions.cs | 108 +- .../Extensions/JsonObjectExtensions.cs | 144 +- .../Extensions/ValueSetExtensions.cs | 364 +- .../ConfigurationSetProcessorFactoryBase.cs | 364 +- .../Helpers/ConfigurationUnitInternal.cs | 526 +- .../Helpers/DiagnosticInformation.cs | 46 +- .../Helpers/SemanticVersion.cs | 146 +- .../Helpers/StringHelpers.cs | 48 +- .../Helpers/TypeHelpers.cs | 456 +- ....Management.Configuration.Processor.csproj | 278 +- .../Constants/PowerShellConstants.cs | 162 +- .../PowerShell/DscModules/DscModuleV2.cs | 594 +- .../PowerShell/DscModules/IDscModule.cs | 178 +- .../DscResourceInfoInternal.cs | 314 +- .../DscResourcePropertyInfoInternal.cs | 112 +- .../ImplementedAsTypeInternal.cs | 68 +- .../Extensions/PowerShellExtensions.cs | 186 +- .../Helpers/ConfigurationUnitAndModule.cs | 98 +- .../Helpers/ConfigurationUnitAndResource.cs | 204 +- .../PowerShell/Helpers/DscResourcesMap.cs | 448 +- .../PowerShell/Helpers/Factory.cs | 446 +- .../PowerShell/Helpers/IPowerShellGet.cs | 188 +- .../PowerShell/Helpers/PowerShellGetV2.cs | 500 +- .../PowerShell/Helpers/PowerShellHelpers.cs | 166 +- .../HostedEnvironment.cs | 1110 +- .../IProcessorEnvironment.cs | 466 +- .../ProcessorEnvironmentFactory.cs | 218 +- .../PowerShellConfigurationSetProcessor.cs | 618 +- .../PowerShellConfigurationUnitProcessor.cs | 126 +- .../Properties/AssemblyInfo.cs | 54 +- .../DSCv3ConfigurationSetProcessorFactory.cs | 610 +- ...ConfigurationProcessorFactoryProperties.cs | 82 +- .../Public/PathEnvironmentVariableHandler.cs | 138 +- ...owerShellConfigurationProcessorLocation.cs | 78 +- .../PowerShellConfigurationProcessorPolicy.cs | 102 +- .../PowerShellConfigurationProcessorType.cs | 48 +- ...erShellConfigurationSetProcessorFactory.cs | 654 +- .../Set/ConfigurationSetProcessorBase.cs | 518 +- .../Unit/ApplySettingsResult.cs | 88 +- .../Unit/ConfigurationUnitProcessorBase.cs | 616 +- .../Unit/ConfigurationUnitProcessorDetails.cs | 250 +- .../ConfigurationUnitResultInformation.cs | 58 +- .../Unit/ConfigurationUnitSettingDetails.cs | 128 +- .../Unit/GetAllSettingsResult.cs | 92 +- .../Unit/GetAllUnitsResult.cs | 92 +- .../Unit/GetSettingsResult.cs | 90 +- .../Unit/TestSettingsResult.cs | 88 +- ...Management.Configuration.Projection.csproj | 88 +- .../Fixtures/UnitTestCollection.cs | 42 +- .../Fixtures/UnitTestFixture.cs | 244 +- .../ApplyGroupMemberSettingsResultInstance.cs | 92 +- .../ApplyGroupSettingsResultInstance.cs | 90 +- .../Helpers/ApplySettingsResultInstance.cs | 88 +- .../Helpers/ConfigurationEnvironmentData.cs | 176 +- .../Helpers/ConfigurationExtensions.cs | 72 +- .../Helpers/ConfigurationProcessorTestBase.cs | 480 +- .../Helpers/Constants.cs | 100 +- .../Helpers/DiagnosticsEventSink.cs | 120 +- .../Helpers/Errors.cs | 118 +- .../Helpers/FactSkipIfCI.cs | 56 +- .../Helpers/GetAllSettingsResultInstance.cs | 92 +- .../Helpers/GetSettingsResultInstance.cs | 90 +- .../Helpers/InProcAttribute.cs | 52 +- .../Helpers/InProcDiscoverer.cs | 80 +- .../Helpers/OutOfProcAttribute.cs | 74 +- .../Helpers/OutOfProcDiscoverer.cs | 80 +- .../Helpers/PowerShellTestsConstants.cs | 54 +- .../Helpers/TelemetryEvent.cs | 280 +- .../Helpers/TempDirectory.cs | 240 +- .../Helpers/TempFile.cs | 218 +- .../TestConfigurationProcessorFactory.cs | 274 +- .../TestConfigurationSetGroupProcessor.cs | 240 +- .../Helpers/TestConfigurationSetProcessor.cs | 298 +- .../TestConfigurationUnitGroupProcessor.cs | 410 +- .../Helpers/TestConfigurationUnitProcessor.cs | 300 +- .../TestConfigurationUnitProcessorDetails.cs | 140 +- .../TestConfigurationUnitResultInformation.cs | 74 +- .../Helpers/TestDSCv3.cs | 290 +- ...etAllSettingsConfigurationUnitProcessor.cs | 112 +- .../TestGroupSettingsResultInstance.cs | 90 +- .../Helpers/TestResourceExportItem.cs | 86 +- .../Helpers/TestResourceGetItem.cs | 44 +- .../Helpers/TestResourceListItem.cs | 102 +- .../Helpers/TestResourceSetItem.cs | 42 +- .../Helpers/TestResourceTestItem.cs | 42 +- .../Helpers/TestSettingsResultInstance.cs | 88 +- .../Helpers/TheorySkipIfCI.cs | 56 +- ....Management.Configuration.UnitTests.csproj | 160 +- .../xAdminTestResource.psd1 | 74 +- .../xAdminTestResource.psm1 | 58 +- .../xSimpleTestResource.psd1 | 82 +- .../xSimpleTestResource.psm1 | 524 +- .../Tests/ConfigurationDetailsTests.cs | 444 +- .../Tests/ConfigurationHistoryTests.cs | 946 +- .../Tests/ConfigurationMixedElevationTests.cs | 606 +- .../Tests/ConfigurationProcessorApplyTests.cs | 1238 +- .../ConfigurationProcessorFactoryTests.cs | 374 +- .../ConfigurationProcessorGetAllTests.cs | 294 +- .../Tests/ConfigurationProcessorGetTests.cs | 414 +- .../Tests/ConfigurationProcessorGroupTests.cs | 1310 +- .../ConfigurationProcessorTelemetryTests.cs | 478 +- .../Tests/ConfigurationProcessorTestTests.cs | 646 +- .../Tests/ConfigurationSetAuthoringTests.cs | 494 +- .../Tests/ConfigurationSetProcessorTests.cs | 1966 +- .../Tests/ConfigurationUnitInternalTests.cs | 318 +- .../Tests/ConfigurationUnitProcessorTests.cs | 818 +- ...3ProcessorPathIntegrityIntegrationTests.cs | 216 +- .../DSCv3ProcessorPathIntegrityUnitTests.cs | 320 +- .../Tests/DSCv3ProcessorTests.cs | 656 +- .../DscModuleV2SimpleFileResourceTests.cs | 568 +- .../Tests/DscModuleV2Tests.cs | 1246 +- .../Tests/DscResourceMapTests.cs | 356 +- .../Tests/ExceptionExtensionsTests.cs | 132 +- .../Tests/HashtableExtensionsTests.cs | 8 +- .../Tests/OpenConfigurationSetTests.cs | 2770 +- .../Tests/PowerShellHelperTests.cs | 356 +- .../Tests/ProcessorEnvironmentTests.cs | 502 +- .../Tests/SemanticVersionTests.cs | 156 +- .../Tests/ShutdownTests.cs | 370 +- .../Tests/TypeHelpersTests.cs | 424 +- .../Tests/ValueSetExtensionsTests.cs | 922 +- .../ApplyConfigurationSetResult.cpp | 64 +- .../ApplyConfigurationSetResult.h | 58 +- .../ApplyConfigurationUnitResult.cpp | 170 +- .../ApplyConfigurationUnitResult.h | 88 +- .../ApplyGroupSettingsResult.cpp | 94 +- .../ApplyGroupSettingsResult.h | 68 +- .../ArgumentValidation.cpp | 420 +- .../ArgumentValidation.h | 68 +- .../ConfigThreadGlobals.cpp | 54 +- .../ConfigThreadGlobals.h | 52 +- .../ConfigurationChangeData.cpp | 26 +- .../ConfigurationChangeData.h | 54 +- .../ConfigurationConflict.cpp | 32 +- .../ConfigurationConflict.h | 68 +- .../ConfigurationConflictSetting.cpp | 60 +- .../ConfigurationConflictSetting.h | 54 +- .../ConfigurationEnvironment.cpp | 334 +- .../ConfigurationEnvironment.h | 102 +- .../ConfigurationParameter.cpp | 352 +- .../ConfigurationProcessor.cpp | 2420 +- .../ConfigurationProcessor.h | 340 +- .../ConfigurationSequencer.cpp | 346 +- .../ConfigurationSequencer.h | 96 +- .../ConfigurationSet.cpp | 656 +- .../ConfigurationSet.h | 220 +- .../ConfigurationSetApplyProcessor.cpp | 1084 +- .../ConfigurationSetApplyProcessor.h | 216 +- .../ConfigurationSetChangeData.cpp | 152 +- .../ConfigurationSetChangeData.h | 84 +- .../ConfigurationSetParser.cpp | 1170 +- .../ConfigurationSetParser.h | 286 +- .../ConfigurationSetParserError.h | 62 +- .../ConfigurationSetParser_0_1.cpp | 126 +- .../ConfigurationSetParser_0_1.h | 76 +- .../ConfigurationSetParser_0_2.cpp | 76 +- .../ConfigurationSetParser_0_2.h | 66 +- .../ConfigurationSetParser_0_3.cpp | 720 +- .../ConfigurationSetParser_0_3.h | 146 +- .../ConfigurationSetSerializer.cpp | 584 +- .../ConfigurationSetSerializer.h | 112 +- .../ConfigurationSetSerializer_0_2.cpp | 296 +- .../ConfigurationSetSerializer_0_2.h | 62 +- .../ConfigurationSetSerializer_0_3.cpp | 614 +- .../ConfigurationSetSerializer_0_3.h | 62 +- .../ConfigurationSetUtilities.cpp | 276 +- .../ConfigurationSetUtilities.h | 142 +- .../ConfigurationStaticFunctions.cpp | 32 +- .../ConfigurationStaticFunctions.h | 8 +- .../ConfigurationStatus.cpp | 820 +- .../ConfigurationStatus.h | 254 +- .../ConfigurationUnit.cpp | 464 +- .../ConfigurationUnit.h | 186 +- .../ConfigurationUnitResultInformation.cpp | 16 +- .../ConfigurationUnitResultInformation.h | 80 +- .../Database/ConfigurationDatabase.cpp | 940 +- .../Database/ConfigurationDatabase.h | 302 +- .../Database/Schema/0_1/Interface.h | 62 +- .../Database/Schema/0_1/Interface_0_1.cpp | 184 +- .../Database/Schema/0_1/SetInfoTable.cpp | 528 +- .../Database/Schema/0_1/SetInfoTable.h | 94 +- .../Database/Schema/0_1/UnitInfoTable.cpp | 458 +- .../Database/Schema/0_1/UnitInfoTable.h | 64 +- .../Database/Schema/0_2/Interface.h | 58 +- .../Database/Schema/0_2/Interface_0_2.cpp | 166 +- .../Database/Schema/0_2/QueueTable.cpp | 310 +- .../Database/Schema/0_2/QueueTable.h | 82 +- .../Schema/0_3/ChangeListenerTable.cpp | 172 +- .../Database/Schema/0_3/ChangeListenerTable.h | 58 +- .../Database/Schema/0_3/Interface.h | 88 +- .../Database/Schema/0_3/Interface_0_3.cpp | 352 +- .../Database/Schema/0_3/StatusItemTable.cpp | 730 +- .../Database/Schema/0_3/StatusItemTable.h | 108 +- .../Schema/IConfigurationDatabase.cpp | 288 +- .../Database/Schema/IConfigurationDatabase.h | 266 +- .../DefaultSetGroupProcessor.cpp | 342 +- .../DefaultSetGroupProcessor.h | 64 +- .../DiagnosticInformationInstance.cpp | 66 +- .../DiagnosticInformationInstance.h | 52 +- .../ExceptionResultHelpers.h | 64 +- .../Filesystem.cpp | 66 +- .../Filesystem.h | 36 +- .../FindUnitProcessorsOptions.cpp | 86 +- .../FindUnitProcessorsOptions.h | 8 +- .../GetConfigurationSetDetailsResult.cpp | 44 +- .../GetConfigurationSetDetailsResult.h | 52 +- .../GetConfigurationUnitDetailsResult.cpp | 76 +- .../GetConfigurationUnitDetailsResult.h | 62 +- .../GetConfigurationUnitSettingsResult.cpp | 68 +- .../GetConfigurationUnitSettingsResult.h | 54 +- .../Microsoft.Management.Configuration.idl | 2290 +- ...Microsoft.Management.Configuration.vcxproj | 652 +- ...t.Management.Configuration.vcxproj.filters | 770 +- .../Microsoft_Management_Configuration.def | 6 +- .../ParsingMacros.h | 26 +- .../ShutdownSynchronization.cpp | 360 +- .../ShutdownSynchronization.h | 346 +- .../Telemetry/Telemetry.cpp | 826 +- .../Telemetry/Telemetry.h | 254 +- .../TestConfigurationSetResult.cpp | 132 +- .../TestConfigurationSetResult.h | 58 +- .../TestConfigurationUnitResult.cpp | 94 +- .../TestConfigurationUnitResult.h | 66 +- .../TestGroupSettingsResult.cpp | 112 +- .../TestGroupSettingsResult.h | 70 +- .../TestSettingsResult.cpp | 76 +- .../TestSettingsResult.h | 62 +- .../packages.config | 8 +- .../pch.cpp | 6 +- src/Microsoft.Management.Configuration/pch.h | 78 +- ...gement.Deployment.CsWinRTProjection.csproj | 92 +- ....Management.Deployment.InProc.dll.manifest | 230 +- ...osoft.Management.Deployment.InProc.vcxproj | 776 +- ...nagement.Deployment.InProc.vcxproj.filters | 84 +- .../dllmain.cpp | 84 +- .../packages.config | 8 +- .../pch.cpp | 8 +- .../pch.h | 8 +- .../Factory.cpp | 388 +- .../Factory.h | 88 +- ...ft.Management.Deployment.OutOfProc.vcxproj | 850 +- ...ement.Deployment.OutOfProc.vcxproj.filters | 114 +- .../dllmain.cpp | 142 +- .../packages.config | 8 +- .../pch.cpp | 8 +- .../pch.h | 26 +- .../ClassesDefinition.cs | 108 +- .../ActivationFactoryInstanceInitializer.cs | 6 +- .../LocalServerInstanceInitializer.cs | 4 +- .../PolicyEnforcedInstanceInitializer.cs | 76 +- .../Utils/ComUtils.cs | 4 +- .../WinGetProjectionFactory.cs | 12 +- .../AddPackageCatalogOptions.cpp | 154 +- .../AddPackageCatalogOptions.h | 112 +- .../AddPackageCatalogResult.cpp | 48 +- .../AddPackageCatalogResult.h | 56 +- .../CanUnload.cpp | 36 +- .../CatalogPackage.cpp | 368 +- .../CatalogPackage.h | 102 +- .../CheckInstalledStatusResult.cpp | 12 +- .../ComClsids.cpp | 40 +- .../ConnectResult.cpp | 8 +- .../Converters.cpp | 1118 +- .../Converters.h | 416 +- .../CreateCompositePackageCatalogOptions.cpp | 76 +- .../CreateCompositePackageCatalogOptions.h | 78 +- .../DownloadOptions.cpp | 54 +- .../DownloadOptions.h | 142 +- .../FindPackagesOptions.cpp | 68 +- .../FindPackagesOptions.h | 76 +- .../FindPackagesResult.cpp | 2 +- .../FindPackagesResult.h | 2 +- .../Helpers.cpp | 436 +- src/Microsoft.Management.Deployment/Helpers.h | 48 +- .../InstallOptions.cpp | 344 +- .../InstallOptions.h | 166 +- .../InstalledStatus.cpp | 18 +- .../Microsoft.Management.Deployment.vcxproj | 570 +- ...soft.Management.Deployment.vcxproj.filters | 254 +- .../Microsoft_Management_Deployment.def | 6 +- .../PackageCatalog.cpp | 350 +- ...geCatalogConnectionValidationEventArgs.cpp | 36 +- ...kageCatalogConnectionValidationEventArgs.h | 46 +- .../PackageCatalogInfo.cpp | 14 +- .../PackageCatalogProgress.cpp | 314 +- .../PackageCatalogProgress.h | 146 +- .../PackageCatalogReference.cpp | 792 +- .../PackageInstallerInfo.cpp | 48 +- .../PackageInstallerInfo.h | 6 +- .../PackageInstallerInstalledStatus.cpp | 12 +- .../PackageManager.cpp | 2984 +-- .../PackageManager.h | 144 +- .../PackageManager.idl | 3660 +-- .../PackageManagerSettings.cpp | 166 +- .../PackageManagerSettings.h | 70 +- .../PackageMatchFilter.cpp | 102 +- .../PackageMatchFilter.h | 98 +- .../PackageVersionInfo.cpp | 458 +- .../Public/CanUnload.h | 24 +- ...atableMicrosoftManagementDeploymentClass.h | 64 +- .../Public/ComClsids.h | 116 +- .../RefreshPackageCatalogResult.cpp | 52 +- .../RefreshPackageCatalogResult.h | 54 +- .../RemovePackageCatalogOptions.cpp | 70 +- .../RemovePackageCatalogOptions.h | 76 +- .../RemovePackageCatalogResult.cpp | 48 +- .../RemovePackageCatalogResult.h | 54 +- .../RepairOptions.cpp | 244 +- .../RepairOptions.h | 118 +- .../RepairResult.cpp | 88 +- .../RepairResult.h | 72 +- .../UninstallOptions.cpp | 140 +- .../UninstallOptions.h | 94 +- .../packages.config | 8 +- src/Microsoft.Management.Deployment/pch.cpp | 6 +- src/Microsoft.Management.Deployment/pch.h | 30 +- .../CommonFiles/PowerShellCmdlet.cs | 1264 +- src/PowerShell/CommonFiles/StreamType.cs | 98 +- .../CommonFiles/WinGetAssemblyLoadContext.cs | 276 +- .../Repair-WinGetPackage.md | 606 +- .../Cmdlets/AddSourceCmdlet.cs | 184 +- .../AssertWinGetPackageManagerCmdlet.cs | 82 +- .../Cmdlets/Common/FinderCmdlet.cs | 138 +- .../Cmdlets/Common/FinderExtendedCmdlet.cs | 14 +- .../Cmdlets/Common/InstallCmdlet.cs | 8 +- .../Cmdlets/Common/PackageCmdlet.cs | 26 +- .../Common/WinGetPackageManagerCmdlet.cs | 88 +- .../Cmdlets/DisableSettingCmdlet.cs | 92 +- .../Cmdlets/EnableSettingCmdlet.cs | 92 +- .../Cmdlets/ExportPackageCmdlet.cs | 182 +- .../Cmdlets/FindPackageCmdlet.cs | 84 +- .../Cmdlets/GetSourceCmdlet.cs | 80 +- .../Cmdlets/GetVersionCmdlet.cs | 60 +- .../Cmdlets/InstallPackageCmdlet.cs | 132 +- .../Cmdlets/InstallerSelectionCmdlet.cs | 26 +- .../PSObjects/PSPackageFieldMatchOption.cs | 68 +- .../Cmdlets/PSObjects/PSPackageInstallMode.cs | 58 +- .../PSObjects/PSPackageInstallScope.cs | 78 +- .../PSObjects/PSPackageInstallerType.cs | 136 +- .../Cmdlets/PSObjects/PSPackageRepairMode.cs | 58 +- .../PSObjects/PSPackageUninstallMode.cs | 58 +- .../PSObjects/PSProcessorArchitecture.cs | 78 +- .../Cmdlets/PSObjects/PSWindowsPlatform.cs | 88 +- .../Cmdlets/RemoveSourceCmdlet.cs | 78 +- .../Cmdlets/RepairPackageCmdlet.cs | 168 +- .../RepairWinGetPackageManagerCmdlet.cs | 134 +- .../Cmdlets/ResetSourceCmdlet.cs | 108 +- .../Cmdlets/UninstallPackageCmdlet.cs | 154 +- .../Cmdlets/UpdatePackageCmdlet.cs | 116 +- .../Common/Constants.cs | 20 +- .../Microsoft.WinGet.Client.Cmdlets.csproj | 236 +- .../Properties/AssemblyInfo.cs | 32 +- .../Resolver/ModuleInit.cs | 116 +- .../Resolver/WinGetAppDomain.cs | 138 +- .../Attributes/FilterAttribute.cs | 50 +- .../Commands/CliCommand.cs | 290 +- .../Commands/Common/BaseCommand.cs | 56 +- .../Commands/Common/FinderCommand.cs | 428 +- .../Commands/Common/FinderExtendedCommand.cs | 116 +- .../Commands/Common/InstallCommand.cs | 236 +- .../Common/ManagementDeploymentCommand.cs | 200 +- .../Commands/Common/PackageCommand.cs | 372 +- .../Commands/DownloadCommand.cs | 364 +- .../Commands/FinderPackageCommand.cs | 182 +- .../Commands/InstallerPackageCommand.cs | 402 +- .../Commands/RepairPackageCommand.cs | 314 +- .../Commands/SourceCommand.cs | 86 +- .../Commands/UninstallPackageCommand.cs | 262 +- .../Commands/UserSettingsCommand.cs | 532 +- .../Commands/VersionCommand.cs | 72 +- .../Commands/WinGetPackageManagerCommand.cs | 556 +- .../Common/Constants.cs | 138 +- .../Common/ErrorCode.cs | 118 +- .../Common/IntegrityCategory.cs | 148 +- .../Common/Utilities.cs | 450 +- .../Common/WinGetIntegrity.cs | 370 +- .../Exceptions/CatalogConnectException.cs | 56 +- .../Exceptions/FindPackagesException.cs | 74 +- .../Exceptions/InvalidSourceException.cs | 66 +- .../Exceptions/InvalidVersionException.cs | 66 +- .../Exceptions/NoPackageFoundException.cs | 54 +- .../SingleThreadedApartmentException.cs | 54 +- .../Exceptions/UserSettingsReadException.cs | 72 +- .../Exceptions/VagueCriteriaException.cs | 82 +- .../Exceptions/WinGetCLIException.cs | 120 +- .../Exceptions/WinGetCLITimeoutException.cs | 54 +- .../Exceptions/WinGetIntegrityException.cs | 150 +- .../Exceptions/WinGetRepairException.cs | 140 +- .../WinGetRepairPackageException.cs | 158 +- .../WindowsPowerShellNotSupported.cs | 54 +- .../Extensions/CatalogPackageExtensions.cs | 120 +- .../Extensions/ReleaseExtensions.cs | 134 +- .../Helpers/AppxModuleHelper.cs | 1728 +- .../Helpers/DownloadOperationWithProgress.cs | 84 +- .../Helpers/GitHubClient.cs | 442 +- .../Helpers/HttpClientHelper.cs | 230 +- .../Helpers/InstallOperationWithProgress.cs | 106 +- .../Helpers/ManagementDeploymentFactory.cs | 472 +- .../Helpers/OperationWithProgressBase.cs | 220 +- .../Helpers/PSEnumHelpers.cs | 344 +- .../Helpers/PackageManagerWrapper.cs | 378 +- .../Helpers/RepairOperationWithProgress.cs | 88 +- .../Helpers/TempDirectory.cs | 236 +- .../Helpers/TempFile.cs | 224 +- .../Helpers/UninstallOperationWithProgress.cs | 84 +- .../Helpers/WinGetCLICommandBuilder.cs | 296 +- .../Helpers/WinGetCLICommandResult.cs | 150 +- .../Helpers/WinGetVersion.cs | 314 +- .../Helpers/WinRTHelpers.cs | 188 +- .../Helpers/WingetCLIWrapper.cs | 206 +- .../Microsoft.WinGet.Client.Engine.csproj | 286 +- .../PSObjects/PSCatalogPackage.cs | 256 +- .../PSObjects/PSCompareResult.cs | 68 +- .../PSObjects/PSDownloadResult.cs | 232 +- .../PSObjects/PSFoundCatalogPackage.cs | 66 +- .../PSObjects/PSInstallResult.cs | 276 +- .../PSObjects/PSInstalledCatalogPackage.cs | 102 +- .../PSObjects/PSPackageVersionInfo.cs | 208 +- .../PSObjects/PSRepairResult.cs | 276 +- .../PSObjects/PSSourceResult.cs | 118 +- .../PSObjects/PSUninstallResult.cs | 276 +- .../Properties/AssemblyInfo.cs | 42 +- .../Properties/Resources.Designer.cs | 900 +- .../Properties/Resources.resx | 534 +- .../Examples/Sample_AddRemoveSource.ps1 | 48 +- .../Examples/Sample_EnableSettings.ps1 | 36 +- .../Examples/Sample_FindPackage.ps1 | 54 +- .../Examples/Sample_GetPackage.ps1 | 54 +- .../Examples/Sample_GetVersion.ps1 | 32 +- .../Examples/Sample_InstallPackage.ps1 | 50 +- .../Examples/Sample_RepairPackage.ps1 | 44 +- .../Examples/Sample_UninstallPackage.ps1 | 44 +- .../Examples/Sample_UpdatePackage.ps1 | 48 +- .../ModuleFiles/Format.ps1xml | 66 +- .../Microsoft.WinGet.Client/README.md | 116 +- .../Cmdlets/Common/OpenConfiguration.cs | 158 +- .../CompleteWinGetConfigurationCmdlet.cs | 110 +- .../ConfirmWinGetConfigurationCmdlet.cs | 106 +- .../ConvertToWinGetConfigurationYamlCmdlet.cs | 80 +- .../Cmdlets/GetWinGetConfigurationCmdlet.cs | 112 +- .../GetWinGetConfigurationDetailsCmdlet.cs | 108 +- .../InvokeWinGetConfigurationCmdlet.cs | 146 +- .../RemoveWinGetConfigurationHistoryCmdlet.cs | 80 +- .../Cmdlets/StartWinGetConfigurationCmdlet.cs | 120 +- .../Cmdlets/StopWinGetConfigurationCmdlet.cs | 80 +- .../Cmdlets/TestWinGetConfigurationCmdlet.cs | 142 +- .../Helpers/Constants.cs | 48 +- .../Helpers/Utilities.cs | 146 +- ...rosoft.WinGet.Configuration.Cmdlets.csproj | 186 +- .../Properties/AssemblyInfo.cs | 32 +- .../Resolver/ModuleInit.cs | 86 +- .../Commands/ConfigurationCommand.cs | 1470 +- .../Exceptions/ApplyConfigurationException.cs | 86 +- .../Exceptions/ErrorCodes.cs | 94 +- .../Exceptions/ErrorRecordErrorId.cs | 38 +- .../Exceptions/GetDetailsException.cs | 90 +- .../OpenConfigurationSetException.cs | 126 +- .../Extensions/ValueSetExtensions.cs | 66 +- .../ApplyConfigurationSetProgressOutput.cs | 168 +- .../ConfigurationSetProgressOutputBase.cs | 180 +- .../Helpers/ConfigurationUnitInformation.cs | 646 +- ...etConfigurationSetDetailsProgressOutput.cs | 110 +- .../Helpers/OpenConfigurationParameters.cs | 400 +- .../TestConfigurationSetProgressOutput.cs | 116 +- .../Helpers/Utilities.cs | 246 +- ...crosoft.WinGet.Configuration.Engine.csproj | 128 +- .../PSApplyConfigurationSetResult.cs | 90 +- .../PSApplyConfigurationUnitResult.cs | 86 +- .../PSObjects/PSConfigurationJob.cs | 82 +- .../PSObjects/PSConfigurationProcessor.cs | 158 +- .../PSObjects/PSConfigurationSet.cs | 332 +- .../PSObjects/PSConfigurationTestResult.cs | 78 +- .../PSObjects/PSConfigurationUnitState.cs | 78 +- .../PSGetConfigurationDetailsResult.cs | 98 +- .../PSObjects/PSTestConfigurationSetResult.cs | 90 +- .../PSTestConfigurationUnitResult.cs | 64 +- .../PSObjects/PSUnitResult.cs | 264 +- .../PSValidateConfigurationSetResult.cs | 90 +- .../PSValidateConfigurationUnitResult.cs | 50 +- .../Properties/AssemblyInfo.cs | 32 +- .../Resources/Resources.Designer.cs | 1242 +- .../Resources/Resources.resx | 672 +- .../Examples/Sample_InvokeConfiguration.ps1 | 52 +- .../Examples/Sample_StartConfiguration.ps1 | 46 +- .../Microsoft.WinGet.Configuration.psd1 | 80 +- .../Microsoft.WinGet.Configuration/README.md | 154 +- .../Microsoft.WinGet.DSC.psd1 | 278 +- .../Microsoft.WinGet.DSC.psm1 | 1360 +- .../Exceptions/ErrorCodes.cs | 38 +- .../Exceptions/GroupPolicyException.cs | 72 +- .../Extensions/EnumPolicyExtension.cs | 194 +- .../Microsoft.WinGet.SharedLib.csproj | 74 +- .../PolicySettings/Enums.cs | 272 +- .../PolicySettings/GroupPolicy.cs | 246 +- .../PolicySettings/TogglePolicy.cs | 352 +- .../Resources/GroupPolicyResource.Designer.cs | 486 +- .../Resources/GroupPolicyResource.resx | 360 +- .../scripts/Execute-WinGetTests.ps1 | 256 +- .../scripts/Initialize-LocalWinGetModules.ps1 | 500 +- .../WinGetAdminSettingsResourceSample.ps1 | 126 +- .../samples/WinGetPackageManagerSample.ps1 | 254 +- .../samples/WinGetPackageResourceSample.ps1 | 148 +- .../scripts/samples/WinGetSourceSample.ps1 | 158 +- ...WinGetUserSettingsPackageManagerSample.ps1 | 144 +- .../tests/Microsoft.WinGet.Client.Tests.ps1 | 2050 +- .../Microsoft.WinGet.Configuration.Tests.ps1 | 2100 +- .../tests/Microsoft.WinGet.DSC.Tests.ps1 | 494 +- src/PowerShell/tests/RunTests.ps1 | 160 +- src/PureLib/readme.md | 32 +- src/Update-VcxprojNugetPackageVersions.ps1 | 608 +- .../arm64-release-static.cmake | 8 +- src/VcpkgCustomTriplets/arm64-release.cmake | 8 +- src/VcpkgCustomTriplets/arm64.cmake | 6 +- .../x64-release-static.cmake | 8 +- src/VcpkgCustomTriplets/x64-release.cmake | 8 +- src/VcpkgCustomTriplets/x64.cmake | 6 +- .../x86-release-static.cmake | 8 +- src/VcpkgCustomTriplets/x86-release.cmake | 8 +- src/VcpkgCustomTriplets/x86.cmake | 6 +- .../Exceptions/ToolResponseException.cs | 46 +- .../Extensions/PackageListExtensions.cs | 32 +- src/WinGetMCPServer/Program.cs | 70 +- .../Response/PackageResponse.cs | 512 +- src/WinGetMCPServer/Response/ToolResponse.cs | 160 +- src/WinGetMCPServer/ServerConnection.cs | 82 +- src/WinGetMCPServer/WinGetMCPServer.csproj | 144 +- src/WinGetMCPServer/WingetPackageTools.cs | 666 +- src/WinGetSchemas/PackagesSchema.h | 16 +- src/WinGetSchemas/WinGetSchemas.rc | 134 +- src/WinGetSchemas/WinGetSchemas.vcxitems | 56 +- .../WinGetSchemas.vcxitems.filters | 62 +- src/WinGetSchemas/resource.h | 28 +- src/WinGetServer/PropertySheet.props | 30 +- src/WinGetServer/Utils.cpp | 104 +- src/WinGetServer/Utils.h | 28 +- src/WinGetServer/WinGetServer.exe.manifest | 4 +- src/WinGetServer/WinGetServer.idl | 32 +- src/WinGetServer/WinGetServer.vcxproj | 358 +- src/WinGetServer/WinGetServer.vcxproj.filters | 122 +- .../WinGetServerManualActivation_Client.cpp | 496 +- .../WinGetServerManualActivation_Client.h | 16 +- src/WinGetServer/WinMain.cpp | 478 +- src/WinGetServer/packages.config | 6 +- src/WinGetServer/resource.h | 30 +- src/WinGetSourceCreator/Helpers.cs | 424 +- src/WinGetSourceCreator/ManifestTokens.cs | 108 +- .../Model/DynamicInstaller.cs | 176 +- src/WinGetSourceCreator/Model/Installer.cs | 84 +- .../Model/InstallerType.cs | 28 +- .../Model/LocalInstaller.cs | 52 +- src/WinGetSourceCreator/Model/LocalSource.cs | 166 +- src/WinGetSourceCreator/Model/Signature.cs | 58 +- .../Model/SourceInstaller.cs | 80 +- src/WinGetSourceCreator/WinGetLocalSource.cs | 588 +- .../WinGetSourceCreator.csproj | 36 +- src/WinGetTestCommon/WinGetServerInstance.cs | 392 +- src/WinGetTestCommon/WinGetTestCommon.csproj | 20 +- src/WinGetTestCommon/WindowMessage.cs | 58 +- src/WinGetUtil/Exports.cpp | 1334 +- src/WinGetUtil/Source.def | 52 +- src/WinGetUtil/WinGetUtil.h | 636 +- src/WinGetUtil/WinGetUtil.vcxproj | 566 +- src/WinGetUtil/WinGetUtil.vcxproj.filters | 78 +- src/WinGetUtil/packages.config | 8 +- .../APIUnitTests/ManifestUnitTests.cs | 480 +- .../Common/DisplayTestMethodNameAttribute.cs | 66 +- .../Common/FactSkipx64CI.cs | 56 +- .../ManifestEqualityUnitTests.cs | 312 +- .../ManifestUnitTest/V1ManifestReadTest.cs | 1188 +- .../TestCollateral/AllEquality.yaml | 52 +- .../AllEqualityWithDescription.yaml | 54 +- .../TestCollateral/DifferentId.yaml | 52 +- .../ExpectedShadowManifest.yaml | 52 +- .../TestCollateral/OneInstaller.yaml | 34 +- .../TestCollateral/SomeEquality.yaml | 22 +- .../SomeEqualityWithLocalization.yaml | 32 +- .../SomeEqualityWithoutInstallers.yaml | 8 +- .../SomeEqualityWithoutSwitches.yaml | 18 +- .../TestCollateral/Test_yaml_with_bom.yaml | 28 +- .../TestCollateral/Test_yaml_without_bom.yaml | 28 +- ...ifestInfoMissingRequiredPackageLocale.yaml | 40 +- .../TestCollateral/V1ManifestMerged.yaml | 284 +- .../V1ManifestNoLocalization.yaml | 234 +- .../TestCollateral/V1_10ManifestMerged.yaml | 28 +- .../TestCollateral/V1_1ManifestMerged.yaml | 378 +- .../TestCollateral/V1_6ManifestMerged.yaml | 6 +- .../TestCollateral/V1_7ManifestMerged.yaml | 6 +- .../TestCollateral/V1_9ManifestMerged.yaml | 8 +- .../WinGetUtilInterop.UnitTests.csproj | 264 +- .../Api/WinGetSQLiteIndex.cs | 24 +- src/WinGetUtilInterop/Common/Enums.cs | 8 +- src/WinGetUtilInterop/Common/Helpers.cs | 6 +- .../Common/ManifestDiagnostic.cs | 202 +- .../Common/ManifestErrorId.cs | 420 +- .../Interfaces/IWinGetSQLiteIndex.cs | 194 +- .../Manifest/ManifestVersion.cs | 146 +- .../V1/InstallerExpectedReturnCode.cs | 6 +- .../Manifest/V1/ManifestShadow.cs | 48 +- .../scripts/CreateLocalNuget.ps1 | 170 +- .../scripts/WinGetUtilDev.nuspec | 60 +- src/WinGetYamlFuzzing/OneFuzzConfig.json | 74 +- src/WinGetYamlFuzzing/README.md | 40 +- src/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp | 116 +- .../WinGetYamlFuzzing.vcxproj | 222 +- .../WinGetYamlFuzzing.vcxproj.filters | 62 +- src/WinGetYamlFuzzing/dictionary.txt | 36 +- src/WinGetYamlFuzzing/packages.config | 8 +- .../ConfigurationStaticFunctions.cpp | 648 +- src/WindowsPackageManager/Source.def | 30 +- .../WindowsPackageManager.h | 108 +- .../WindowsPackageManager.vcxproj | 890 +- .../WindowsPackageManager.vcxproj.filters | 78 +- src/WindowsPackageManager/main.cpp | 320 +- src/WindowsPackageManager/packages.config | 8 +- src/Xlang/README.md | 38 +- .../UndockedRegFreeWinRT.vcxproj | 662 +- .../UndockedRegFreeWinRT.vcxproj.filters | 120 +- .../UndockedRegFreeWinRT/packages.config | 6 +- .../UndockedRegFreeWinRT/typeresolution.cpp | 10 +- src/binver/Update-BinVer.ps1 | 226 +- src/binver/binver.vcxitems | 50 +- src/binver/binver/resource.h | 28 +- src/binver/binver/version.h | 64 +- src/binver/binver/version.rc | 192 +- src/manifest/shared.manifest | 48 +- src/nuget.config | 18 +- src/targets/EmbeddedCsWinRT.targets | 66 +- .../ReferenceEmbeddedCsWinRTProject.targets | 92 +- templates/e2e-setup.yml | 118 +- templates/e2e-test.template.yml | 104 +- tools/COMTrace/ComTrace.wprp | 98 +- tools/CorrelationTestbed/InSandboxScript.ps1 | 256 +- .../InstallAndCheckCorrelation.sln | 62 +- .../InstallAndCheckCorrelation.cpp | 1522 +- .../InstallAndCheckCorrelation.vcxproj | 334 +- ...InstallAndCheckCorrelation.vcxproj.filters | 54 +- .../packages.config | 8 +- .../Process-CorrelationResults.ps1 | 188 +- tools/CorrelationTestbed/Readme.md | 86 +- .../Test-CorrelationInSandbox.ps1 | 768 +- tools/DevInSandbox/InSandboxScript.ps1 | 62 +- .../Launch-DevPackageInSandbox.ps1 | 432 +- tools/HAMTrace/WER.HostActivityManager.wprp | 536 +- .../IndexComparisonTool.vcxproj | 232 +- .../IndexComparisonTool.vcxproj.filters | 58 +- tools/IndexComparisonTool/WinGetUtil.h | 58 +- tools/IndexComparisonTool/main.cpp | 46 +- tools/IndexComparisonTool/pch.cpp | 6 +- tools/IndexComparisonTool/pch.h | 44 +- .../AppInstallerCaller/App.xaml | 2 +- .../AppInstallerCaller.vcxproj | 380 +- .../AppInstallerCaller.vcxproj.filters | 116 +- .../AppInstallerCaller/MainPage.cpp | 56 +- .../AppInstallerCaller/MainPage.h | 4 +- .../AppInstallerCaller/MainPage.xaml | 2 +- tools/WinGetLogViewer/.gitignore | 6 +- tools/WinGetLogViewer/.vscodeignore | 18 +- tools/WinGetLogViewer/LICENSE | 42 +- tools/WinGetLogViewer/README.md | 192 +- .../language-configuration.json | 12 +- tools/WinGetLogViewer/media/viewer.css | 774 +- tools/WinGetLogViewer/media/viewer.html | 200 +- tools/WinGetLogViewer/package.json | 188 +- tools/WinGetLogViewer/src/extension.ts | 82 +- tools/WinGetLogViewer/src/logParser.ts | 288 +- .../WinGetLogViewer/src/logViewerProvider.ts | 394 +- .../syntaxes/winget-log.tmLanguage.json | 82 +- tools/WinGetLogViewer/tsconfig.json | 30 +- 1753 files changed, 289896 insertions(+), 289868 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..9b82293bbd --- /dev/null +++ b/.gitattributes @@ -0,0 +1,28 @@ +# Default: CRLF on checkout - primary tooling is Visual Studio / PowerShell on Windows. +# Git always stores text as LF in the object store; eol= controls the working tree only. +* text=auto eol=crlf + +# Cross-platform formats - no Windows tooling owns these, LF is safer for external consumers +*.editorconfig text eol=lf +*.gitattributes text eol=lf +*.gitignore text eol=lf +*.json text eol=lf +*.js text eol=lf +*.md text eol=lf +*.patch text eol=lf +*.txt text eol=lf + +# Vendored external modules - preserve byte-for-byte as shipped upstream +# (some files are UTF-16 LE; text=auto would misidentify or corrupt them) +src/PowerShell/ExternalModules/** -text + +# Binary assets - suppress line-ending conversion and text diffs +*.cer binary +*.crt binary +*.dll binary +*.exe binary +*.gif binary +*.ico binary +*.msi binary +*.msix binary +*.png binary diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index aba1cec9c8..cff632ea80 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,192 +1,192 @@ -# WinGet CLI Development Guide - -## Project Overview - -This is the Windows Package Manager (WinGet) CLI client - a native Windows application for discovering and installing packages. The codebase consists of: - -- **C++/WinRT client** (`src/AppInstallerCLI*`) - The main CLI and core logic -- **COM API** (`src/Microsoft.Management.Deployment`) - Public Windows Runtime API for programmatic access -- **PowerShell modules** (`src/PowerShell`) - Microsoft.WinGet.Client and Microsoft.WinGet.Configuration cmdlets -- **Configuration system** - DSC-based system configuration using WinGet - -## Building, Testing, and Running - -### Initial Setup - -Use a configuration file in `.config` as in `winget configure .config/configuration.winget` (alternatives provided for other VS SKUs). - -Manual steps: - -1. Install Visual Studio 2022 with required workloads (see `.vsconfig`) -2. Install Windows SDK 10.0.26100: `winget install Microsoft.WindowsSDK.10.0.26100` -3. Enable developer mode in Windows -4. Run `vcpkg integrate install` from Developer Command Prompt - -### Building - -Open `src\AppInstallerCLI.sln` in Visual Studio and build the solution (Ctrl+Shift+B) or use msbuild.exe to build from the command line. - -The solution uses: -- MSBuild -- vcpkg for C++ dependencies -- NuGet for C++ and .NET dependencies - -### Running/Debugging - -1. Deploy solution: Build > Deploy Solution -2. Run from command line: `wingetdev` -3. For debugging: - - Right-click `AppInstallerCLIPackage` > Properties > Debug tab - - Set Debugger type to "Native Only" for both Application and Background task processes - - Select "Do not launch, but debug my code when it starts" - - Press F5 and run `wingetdev` in a separate terminal - -Entry point: `src/AppInstallerCLI/main.cpp` - -### Testing - -#### C++ Unit Tests (Catch2) -Located in `AppInstallerCLITests` project. After building: - -```powershell -# Run all tests -src\\\AppInstallerCLITests\AppInstallerCLITests.exe - -# Run specific test -src\\\AppInstallerCLITests\AppInstallerCLITests.exe TestName - -# Available options -AppInstallerCLITests.exe --help -``` - -#### .NET Tests -- `Microsoft.WinGet.UnitTests` - PowerShell module tests -- `Microsoft.Management.Configuration.UnitTests` - Configuration system tests -- `WinGetUtilInterop.UnitTests` - Interop layer tests - -#### E2E Tests -`AppInstallerCLIE2ETests` project contains end-to-end integration tests. - -## Architecture - -### Core Components - -**AppInstallerCLICore** - Core CLI logic organized around: -- **ExecutionContext**: State container that flows through workflows. Contains arguments, reporter, flags, and data (ExecutionContextData.h) -- **Workflows**: Composable functions that take ExecutionContext and perform operations (e.g., InstallFlow, UpdateFlow, SearchFlow) -- **Commands**: Parse arguments and orchestrate workflows -- **Reporter**: Handles all user output (ExecutionReporter.h) - -**AppInstallerRepositoryCore** - Package source abstraction: -- Interfaces for different source types (REST, SQLite index, Microsoft Store, composite) -- Search, match, and correlation logic -- Package version selection and dependencies - -**AppInstallerCommonCore** - Shared utilities: -- Manifest parsing (YAML/JSON) -- Settings and group policy -- Telemetry and logging -- HTTP client, downloader, archive handling - -**Microsoft.Management.Deployment** - COM API surface: -- IDL definitions in `PackageManager.idl` -- WinRT projections for external consumption -- Used by PowerShell modules and third-party integrations - -**AppInstallerCLIPackage** - Dev MSIX package definition: -- Models the release package definition as closely as possible. -- Contains localized string resources at src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw - -### Key Patterns - -**Workflow Pattern**: Functions that operate on ExecutionContext: -```cpp -void WorkflowTask(Execution::Context& context) -{ - // Check if already terminated - AICLI_RETURN_IF_TERMINATED(context); - - // Access data - auto& data = context.Get(); - - // Report to user - context.Reporter.Info() << "Doing something"; - - // Store data for next workflow - context.Add(result); - - // Terminate on error - if (failed) - { - AICLI_TERMINATE_CONTEXT(HRESULT); - } -} -``` - -**Source Composition**: Multiple package sources can be composed: -- CompositeSource combines multiple sources with conflict resolution -- Installed source tracks locally installed packages -- Available sources provide packages to install - -**Manifest Schema**: Package manifests use versioned YAML schemas: -- Schema definitions in `schemas/JSON/manifests/` -- Parsing in `AppInstallerCommonCore/Manifest/` -- Multi-file manifests: installer, locale, version, defaultLocale - -## Naming Conventions - -- **Namespace structure**: `AppInstaller::[::]` - - `AppInstaller::CLI::Execution` - CLI execution context - - `AppInstaller::CLI::Workflow` - Workflow functions - - `AppInstaller::Repository` - Repository/source logic - - `AppInstaller::Manifest` - Manifest types - - `AppInstaller::Settings` - User/admin settings - -- **Macros**: Prefixed with `AICLI_` for CLI, `WINGET_` for general -- **Data keys**: ExecutionContextData uses enum keys to type-safely store/retrieve data - -## Windows-Specific Considerations - -- Use Windows-style paths with backslashes (`\`) -- Leverage WinRT APIs via C++/WinRT projections -- COM threading models matter - client uses multi-threaded apartment (MTA) -- Package deployment uses Windows App SDK / MSIX infrastructure -- Requires Windows 10 1809+ (build 17763) - -## Contributing - -- Review `CONTRIBUTING.md` for workflow -- File/discuss issues before starting work -- Specs required for features (stored in `doc/specs/`); see `.github/instructions/specs.instructions.md` for detailed guidance -- Follow existing code style (see `stylecop.json`) -- CI runs on Azure Pipelines (`azure-pipelines.yml`) - -## Useful Commands - -```powershell -# Get WinGet client info -wingetdev --info - -# Show experimental features -wingetdev features - -# Check sources -wingetdev source list -``` - -## Localization - -### Source of truth - -The English resource file `src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw` is the only file contributors should edit for string changes. It feeds the Microsoft localization pipeline. - -The files under `Localization\Resources\\` are **automatically synced from Microsoft's internal localization system and must not be edited**. Any manual edits will be overwritten on the next sync. - -Every string that could be misunderstood without context should have a ``. - -### Triggering retranslation - -- **Changing a string's ``** automatically queues it for retranslation on the next localization sync. -- **Changing only a ``** does NOT trigger retranslation. Comments improve future translations but do not fix existing ones. - -To fix an existing bad translation, a bug has to be filed internally with the localization team. +# WinGet CLI Development Guide + +## Project Overview + +This is the Windows Package Manager (WinGet) CLI client - a native Windows application for discovering and installing packages. The codebase consists of: + +- **C++/WinRT client** (`src/AppInstallerCLI*`) - The main CLI and core logic +- **COM API** (`src/Microsoft.Management.Deployment`) - Public Windows Runtime API for programmatic access +- **PowerShell modules** (`src/PowerShell`) - Microsoft.WinGet.Client and Microsoft.WinGet.Configuration cmdlets +- **Configuration system** - DSC-based system configuration using WinGet + +## Building, Testing, and Running + +### Initial Setup + +Use a configuration file in `.config` as in `winget configure .config/configuration.winget` (alternatives provided for other VS SKUs). + +Manual steps: + +1. Install Visual Studio 2022 with required workloads (see `.vsconfig`) +2. Install Windows SDK 10.0.26100: `winget install Microsoft.WindowsSDK.10.0.26100` +3. Enable developer mode in Windows +4. Run `vcpkg integrate install` from Developer Command Prompt + +### Building + +Open `src\AppInstallerCLI.sln` in Visual Studio and build the solution (Ctrl+Shift+B) or use msbuild.exe to build from the command line. + +The solution uses: +- MSBuild +- vcpkg for C++ dependencies +- NuGet for C++ and .NET dependencies + +### Running/Debugging + +1. Deploy solution: Build > Deploy Solution +2. Run from command line: `wingetdev` +3. For debugging: + - Right-click `AppInstallerCLIPackage` > Properties > Debug tab + - Set Debugger type to "Native Only" for both Application and Background task processes + - Select "Do not launch, but debug my code when it starts" + - Press F5 and run `wingetdev` in a separate terminal + +Entry point: `src/AppInstallerCLI/main.cpp` + +### Testing + +#### C++ Unit Tests (Catch2) +Located in `AppInstallerCLITests` project. After building: + +```powershell +# Run all tests +src\\\AppInstallerCLITests\AppInstallerCLITests.exe + +# Run specific test +src\\\AppInstallerCLITests\AppInstallerCLITests.exe TestName + +# Available options +AppInstallerCLITests.exe --help +``` + +#### .NET Tests +- `Microsoft.WinGet.UnitTests` - PowerShell module tests +- `Microsoft.Management.Configuration.UnitTests` - Configuration system tests +- `WinGetUtilInterop.UnitTests` - Interop layer tests + +#### E2E Tests +`AppInstallerCLIE2ETests` project contains end-to-end integration tests. + +## Architecture + +### Core Components + +**AppInstallerCLICore** - Core CLI logic organized around: +- **ExecutionContext**: State container that flows through workflows. Contains arguments, reporter, flags, and data (ExecutionContextData.h) +- **Workflows**: Composable functions that take ExecutionContext and perform operations (e.g., InstallFlow, UpdateFlow, SearchFlow) +- **Commands**: Parse arguments and orchestrate workflows +- **Reporter**: Handles all user output (ExecutionReporter.h) + +**AppInstallerRepositoryCore** - Package source abstraction: +- Interfaces for different source types (REST, SQLite index, Microsoft Store, composite) +- Search, match, and correlation logic +- Package version selection and dependencies + +**AppInstallerCommonCore** - Shared utilities: +- Manifest parsing (YAML/JSON) +- Settings and group policy +- Telemetry and logging +- HTTP client, downloader, archive handling + +**Microsoft.Management.Deployment** - COM API surface: +- IDL definitions in `PackageManager.idl` +- WinRT projections for external consumption +- Used by PowerShell modules and third-party integrations + +**AppInstallerCLIPackage** - Dev MSIX package definition: +- Models the release package definition as closely as possible. +- Contains localized string resources at src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw + +### Key Patterns + +**Workflow Pattern**: Functions that operate on ExecutionContext: +```cpp +void WorkflowTask(Execution::Context& context) +{ + // Check if already terminated + AICLI_RETURN_IF_TERMINATED(context); + + // Access data + auto& data = context.Get(); + + // Report to user + context.Reporter.Info() << "Doing something"; + + // Store data for next workflow + context.Add(result); + + // Terminate on error + if (failed) + { + AICLI_TERMINATE_CONTEXT(HRESULT); + } +} +``` + +**Source Composition**: Multiple package sources can be composed: +- CompositeSource combines multiple sources with conflict resolution +- Installed source tracks locally installed packages +- Available sources provide packages to install + +**Manifest Schema**: Package manifests use versioned YAML schemas: +- Schema definitions in `schemas/JSON/manifests/` +- Parsing in `AppInstallerCommonCore/Manifest/` +- Multi-file manifests: installer, locale, version, defaultLocale + +## Naming Conventions + +- **Namespace structure**: `AppInstaller::[::]` + - `AppInstaller::CLI::Execution` - CLI execution context + - `AppInstaller::CLI::Workflow` - Workflow functions + - `AppInstaller::Repository` - Repository/source logic + - `AppInstaller::Manifest` - Manifest types + - `AppInstaller::Settings` - User/admin settings + +- **Macros**: Prefixed with `AICLI_` for CLI, `WINGET_` for general +- **Data keys**: ExecutionContextData uses enum keys to type-safely store/retrieve data + +## Windows-Specific Considerations + +- Use Windows-style paths with backslashes (`\`) +- Leverage WinRT APIs via C++/WinRT projections +- COM threading models matter - client uses multi-threaded apartment (MTA) +- Package deployment uses Windows App SDK / MSIX infrastructure +- Requires Windows 10 1809+ (build 17763) + +## Contributing + +- Review `CONTRIBUTING.md` for workflow +- File/discuss issues before starting work +- Specs required for features (stored in `doc/specs/`); see `.github/instructions/specs.instructions.md` for detailed guidance +- Follow existing code style (see `stylecop.json`) +- CI runs on Azure Pipelines (`azure-pipelines.yml`) + +## Useful Commands + +```powershell +# Get WinGet client info +wingetdev --info + +# Show experimental features +wingetdev features + +# Check sources +wingetdev source list +``` + +## Localization + +### Source of truth + +The English resource file `src\AppInstallerCLIPackage\Shared\Strings\en-us\winget.resw` is the only file contributors should edit for string changes. It feeds the Microsoft localization pipeline. + +The files under `Localization\Resources\\` are **automatically synced from Microsoft's internal localization system and must not be edited**. Any manual edits will be overwritten on the next sync. + +Every string that could be misunderstood without context should have a ``. + +### Triggering retranslation + +- **Changing a string's ``** automatically queues it for retranslation on the next localization sync. +- **Changing only a ``** does NOT trigger retranslation. Comments improve future translations but do not fix existing ones. + +To fix an existing bad translation, a bug has to be filed internally with the localization team. diff --git a/.github/instructions/winget-log-viewer.instructions.md b/.github/instructions/winget-log-viewer.instructions.md index a779b55ca7..ee9e4d826d 100644 --- a/.github/instructions/winget-log-viewer.instructions.md +++ b/.github/instructions/winget-log-viewer.instructions.md @@ -1,109 +1,109 @@ ---- -applyTo: "tools/WinGetLogViewer/**" ---- -# WinGet Log Viewer — Copilot Instructions - -This is a VS Code extension that renders WinGet diagnostic log files in a rich WebView custom editor. - -## Log Format - -``` -YYYY-MM-DD HH:MM:SS.mmm [CHAN ] message -YYYY-MM-DD HH:MM:SS.mmm [CHAN ] [SUBCHAN] message ← subchannel variant -``` - -- `` is optional; values: `V`=Verbose, `I`=Info, `W`=Warning, `E`=Error, `C`=Critical -- Channel is padded to 8 chars with spaces inside the brackets -- Subchannel: when a sub-component routes logs through a parent, its original `[CHAN]` tag appears at the start of `message` and is treated as a subchannel -- Older files without `` are also supported -- Main parse regex (used in both `src/logParser.ts` and `media/viewer.js`): - ``` - /^(\d{2,4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(?:<([VIWEC])>\s+)?\[([A-Z]{2,8})\s*\]\s?(.*)$/ - ``` - -## Architecture - -``` -src/ - extension.ts — activate/deactivate; registers provider and openFile command - logParser.ts — parses raw text → LogEntry[]; compiled to out/logParser.js - logViewerProvider.ts — CustomTextEditorProvider; manages WebView lifecycle, follow mode -media/ - viewer.html — WebView HTML template (nonces injected at runtime for CSP) - viewer.css — All styles; --row-height CSS var must match ROW_HEIGHT in viewer.js - viewer.js — All client-side logic (not compiled — served directly) -syntaxes/ - winget-log.tmLanguage.json — TextMate grammar for standard editor syntax highlighting -``` - -## Virtual Scroll Architecture - -- All rows are **uniform height** (`ROW_HEIGHT = 20` px in `viewer.js`, `--row-height: 20px` in `viewer.css`) — these **must stay in sync** -- `displayRows[]` is a flat array built from filtered log entries; continuation lines are promoted to full rows with parent metadata replicated -- `logSpacerTop` and `logSpacerBot` are `
` elements whose `.style.height` creates the illusion of the full list -- `overflow-anchor: none` on `#log-container` is **critical** — without it, when `logSpacerTop` first grows from 0, CSS scroll anchoring cascades and jumps the view to the end -- `scheduledRender` must be declared on its **own line** (not embedded in a comment) or it will be silently undefined - -## WebView CSP Rules - -- No inline `style="..."` attributes in HTML — **always use CSS classes or `element.style.property = value`** via CSSOM -- Script/style nonces are replaced at runtime in `logViewerProvider.ts` -- After any edit to `media/viewer.js`, validate syntax: `node --check media/viewer.js` - -## Follow Mode - -- Uses **Node.js `fs.watchFile({ interval: 500, persistent: false })`**, not VS Code's `createFileSystemWatcher` -- Reason: `ReadDirectoryChangesW` (used by VS Code's watcher) misses events when another process holds the file open — WinGet keeps log files open while writing -- `fs.unwatchFile()` is called in `panel.onDidDispose()` to clean up -- Both `resolveCustomEditor` and `openFile` code paths set up the watcher - -## Ready Handshake - -The WebView sends `{ type: 'ready' }` when its JS has loaded. `logViewerProvider.ts` waits for this message before calling `sendLog()`. This prevents `postMessage` being dropped before the listener is registered. - -## Channel Colors - -| Channel | Color (CSS class) | Emoji proxy in README | -|---------|-------------------|-----------------------| -| `FAIL` | red | ❤️ | -| `CLI` | blue | 💙 | -| `SQL` | purple | 💜 | -| `REPO` | light-blue/cyan | 🩵 | -| `YAML` | yellow | 💛 | -| `CORE` | white/light-gray | 🤍 | -| `TEST` | green | 💚 | -| `CONF` | gray | 🩶 | -| `WORK` | orange | 🧡 | - -## Build & Package - -```bash -# Compile TypeScript (required after any .ts change) -npx tsc - -# Build + package VSIX -npm run package # → winget-log-viewer-.vsix - -# Install locally -code --install-extension winget-log-viewer-0.1.0.vsix -``` - -## .vscodeignore Notes - -- `out/` must **NOT** be in `.vscodeignore` — the compiled JS lives there and is the extension entrypoint -- `src/`, `tsconfig.json`, `out/**/*.map` are excluded (source only, not needed at runtime) - -## Related C++ Files - -The C++ logging infrastructure (in the parent repo) writes the `` level marker: -- `src/AppInstallerCommonCore/FileLogger.cpp` — `ToLogLine()` writes ` ` between timestamp and `[CHAN]` -- `src/AppInstallerSharedLib/AppInstallerLogging.h/.cpp` — `GetLevelChar(Level)` returns V/I/W/E/C -- `src/AppInstallerCLICore/Commands/DebugCommand.h/.cpp` — `LogViewerTestCommand` exercises all viewer features (`winget debug log-viewer [--follow]`) - -## Known Gotchas - -- **Scroll jump at 8–9 wheel clicks**: caused by `overflow-anchor` — fixed with `overflow-anchor: none` on `#log-container` -- **Follow mode not updating**: VS Code's file watcher uses `ReadDirectoryChangesW` and misses events when WinGet holds the file open — use `fs.watchFile()` polling instead -- **`postMessage` dropped on open**: webview JS may not be listening yet — always use the ready-handshake -- **Orphaned JS fragments**: regex replacements can leave stale code that causes silent runtime errors — always validate with `node --check media/viewer.js` -- **CRLF logs**: the parser handles both `\r\n` and `\n` line endings; trailing empty lines are discarded +--- +applyTo: "tools/WinGetLogViewer/**" +--- +# WinGet Log Viewer — Copilot Instructions + +This is a VS Code extension that renders WinGet diagnostic log files in a rich WebView custom editor. + +## Log Format + +``` +YYYY-MM-DD HH:MM:SS.mmm [CHAN ] message +YYYY-MM-DD HH:MM:SS.mmm [CHAN ] [SUBCHAN] message ← subchannel variant +``` + +- `` is optional; values: `V`=Verbose, `I`=Info, `W`=Warning, `E`=Error, `C`=Critical +- Channel is padded to 8 chars with spaces inside the brackets +- Subchannel: when a sub-component routes logs through a parent, its original `[CHAN]` tag appears at the start of `message` and is treated as a subchannel +- Older files without `` are also supported +- Main parse regex (used in both `src/logParser.ts` and `media/viewer.js`): + ``` + /^(\d{2,4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(?:<([VIWEC])>\s+)?\[([A-Z]{2,8})\s*\]\s?(.*)$/ + ``` + +## Architecture + +``` +src/ + extension.ts — activate/deactivate; registers provider and openFile command + logParser.ts — parses raw text → LogEntry[]; compiled to out/logParser.js + logViewerProvider.ts — CustomTextEditorProvider; manages WebView lifecycle, follow mode +media/ + viewer.html — WebView HTML template (nonces injected at runtime for CSP) + viewer.css — All styles; --row-height CSS var must match ROW_HEIGHT in viewer.js + viewer.js — All client-side logic (not compiled — served directly) +syntaxes/ + winget-log.tmLanguage.json — TextMate grammar for standard editor syntax highlighting +``` + +## Virtual Scroll Architecture + +- All rows are **uniform height** (`ROW_HEIGHT = 20` px in `viewer.js`, `--row-height: 20px` in `viewer.css`) — these **must stay in sync** +- `displayRows[]` is a flat array built from filtered log entries; continuation lines are promoted to full rows with parent metadata replicated +- `logSpacerTop` and `logSpacerBot` are `
` elements whose `.style.height` creates the illusion of the full list +- `overflow-anchor: none` on `#log-container` is **critical** — without it, when `logSpacerTop` first grows from 0, CSS scroll anchoring cascades and jumps the view to the end +- `scheduledRender` must be declared on its **own line** (not embedded in a comment) or it will be silently undefined + +## WebView CSP Rules + +- No inline `style="..."` attributes in HTML — **always use CSS classes or `element.style.property = value`** via CSSOM +- Script/style nonces are replaced at runtime in `logViewerProvider.ts` +- After any edit to `media/viewer.js`, validate syntax: `node --check media/viewer.js` + +## Follow Mode + +- Uses **Node.js `fs.watchFile({ interval: 500, persistent: false })`**, not VS Code's `createFileSystemWatcher` +- Reason: `ReadDirectoryChangesW` (used by VS Code's watcher) misses events when another process holds the file open — WinGet keeps log files open while writing +- `fs.unwatchFile()` is called in `panel.onDidDispose()` to clean up +- Both `resolveCustomEditor` and `openFile` code paths set up the watcher + +## Ready Handshake + +The WebView sends `{ type: 'ready' }` when its JS has loaded. `logViewerProvider.ts` waits for this message before calling `sendLog()`. This prevents `postMessage` being dropped before the listener is registered. + +## Channel Colors + +| Channel | Color (CSS class) | Emoji proxy in README | +|---------|-------------------|-----------------------| +| `FAIL` | red | ❤️ | +| `CLI` | blue | 💙 | +| `SQL` | purple | 💜 | +| `REPO` | light-blue/cyan | 🩵 | +| `YAML` | yellow | 💛 | +| `CORE` | white/light-gray | 🤍 | +| `TEST` | green | 💚 | +| `CONF` | gray | 🩶 | +| `WORK` | orange | 🧡 | + +## Build & Package + +```bash +# Compile TypeScript (required after any .ts change) +npx tsc + +# Build + package VSIX +npm run package # → winget-log-viewer-.vsix + +# Install locally +code --install-extension winget-log-viewer-0.1.0.vsix +``` + +## .vscodeignore Notes + +- `out/` must **NOT** be in `.vscodeignore` — the compiled JS lives there and is the extension entrypoint +- `src/`, `tsconfig.json`, `out/**/*.map` are excluded (source only, not needed at runtime) + +## Related C++ Files + +The C++ logging infrastructure (in the parent repo) writes the `` level marker: +- `src/AppInstallerCommonCore/FileLogger.cpp` — `ToLogLine()` writes ` ` between timestamp and `[CHAN]` +- `src/AppInstallerSharedLib/AppInstallerLogging.h/.cpp` — `GetLevelChar(Level)` returns V/I/W/E/C +- `src/AppInstallerCLICore/Commands/DebugCommand.h/.cpp` — `LogViewerTestCommand` exercises all viewer features (`winget debug log-viewer [--follow]`) + +## Known Gotchas + +- **Scroll jump at 8–9 wheel clicks**: caused by `overflow-anchor` — fixed with `overflow-anchor: none` on `#log-container` +- **Follow mode not updating**: VS Code's file watcher uses `ReadDirectoryChangesW` and misses events when WinGet holds the file open — use `fs.watchFile()` polling instead +- **`postMessage` dropped on open**: webview JS may not be listening yet — always use the ready-handshake +- **Orphaned JS fragments**: regex replacements can leave stale code that causes silent runtime errors — always validate with `node --check media/viewer.js` +- **CRLF logs**: the parser handles both `\r\n` and `\n` line endings; trailing empty lines are discarded diff --git a/.github/workflows/spelling.yml b/.github/workflows/spelling.yml index c009898530..0bf62efca4 100644 --- a/.github/workflows/spelling.yml +++ b/.github/workflows/spelling.yml @@ -1,16 +1,16 @@ -# spelling.yml is disabled per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p -name: Workflow should not run! -on: - push: - branches: '' - -jobs: - placeholder: - name: Should be disabled - runs-on: ubuntu-latest - if: false - steps: - - name: Task - run: | - echo 'Running this task would be bad' - exit 1 +# spelling.yml is disabled per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-g86g-chm8-7r2p +name: Workflow should not run! +on: + push: + branches: '' + +jobs: + placeholder: + name: Should be disabled + runs-on: ubuntu-latest + if: false + steps: + - name: Task + run: | + echo 'Running this task would be bad' + exit 1 diff --git a/.github/workflows/spelling2.yml b/.github/workflows/spelling2.yml index 2af9b0cf8c..a8f6816542 100644 --- a/.github/workflows/spelling2.yml +++ b/.github/workflows/spelling2.yml @@ -1,16 +1,16 @@ -# spelling.yml is disabled per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-p8r9-69g4-jwqq -name: Workflow should not run! -on: - push: - branches: '' - -jobs: - placeholder: - name: Should be disabled - runs-on: ubuntu-latest - if: false - steps: - - name: Task - run: | - echo 'Running this task would be bad' - exit 1 +# spelling.yml is disabled per https://github.com/check-spelling/check-spelling/security/advisories/GHSA-p8r9-69g4-jwqq +name: Workflow should not run! +on: + push: + branches: '' + +jobs: + placeholder: + name: Should be disabled + runs-on: ubuntu-latest + if: false + steps: + - name: Task + run: | + echo 'Running this task would be bad' + exit 1 diff --git a/.gitignore b/.gitignore index 28817289a2..de37448c2f 100644 --- a/.gitignore +++ b/.gitignore @@ -353,4 +353,4 @@ src/PowerShell/scripts/Module src/WinGetUtilInterop/scripts/Nuget* # VS Code extension packages -*.vsix +*.vsix diff --git a/CODEOWNERS b/CODEOWNERS index dc0ad3a86e..493a49299b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1,2 +1,2 @@ -* @microsoft/winget-developers - +* @microsoft/winget-developers + diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b3a330dbd0..6353ba2cbd 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,9 +1,9 @@ -# Microsoft Open Source Code of Conduct - -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). - -Resources: - -- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) -- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) -- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns. +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns. diff --git a/LICENSE b/LICENSE index 4b1ad51b2f..21071075c2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,21 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/Localization/Settings/LocConfig.xml b/Localization/Settings/LocConfig.xml index 43e708f42a..2ff29f7774 100644 --- a/Localization/Settings/LocConfig.xml +++ b/Localization/Settings/LocConfig.xml @@ -1,10 +1,10 @@ - - - - - - + + + + + + diff --git a/NOTICE b/NOTICE index 28f355fef5..2a4ee40dc3 100644 --- a/NOTICE +++ b/NOTICE @@ -1,11003 +1,11003 @@ -NOTICES AND INFORMATION -Do Not Translate or Localize - -This software incorporates material from third parties. -Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, -or you may send a check or money order for US $5.00, including the product name, -the open source component name, platform, and version number, to: - -Source Code Compliance Team -Microsoft Corporation -One Microsoft Way -Redmond, WA 98052 -USA - -Notwithstanding any other terms, you may reverse engineer this software to the extent -required to debug changes to any libraries licensed under the GNU Lesser General Public License. - ---------------------------------------------------------- - -Castle.Core 5.1.0 - Apache-2.0 - - -(c) 2004-2022 Castle Project - http://www.castleproject.org -Copyright 2004-2021 Castle Project - http://www.castleproject.org -Copyright (c) 2004-2022 Castle Project - http://www.castleproject.org - -Copyright 2004-2021 Castle Project - http://www.castleproject.org/ - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - ---------------------------------------------------------- - ---------------------------------------------------------- - -NuGet.Frameworks 5.11.0 - Apache-2.0 - - -(c) Microsoft Corporation. - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - - - "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - - - - "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - - - - "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - - - - "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - - - - "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - - - - "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - - - - "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - - - - "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - - - - "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - - - - "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - - (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); - -you may not use this file except in compliance with the License. - -You may obtain a copy of the License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software - -distributed under the License is distributed on an "AS IS" BASIS, - -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - -See the License for the specific language governing permissions and - -limitations under the License. - ---------------------------------------------------------- - ---------------------------------------------------------- - -openssl/openssl 01d5e2318405362b4de5e670c90d9b40a351d053 - Apache-2.0 - - -(c) 2005 WISeKey SA1 -Copyright 2005 Nokia -Copyright 2021 UnionTech -Copyright 2021- IBM Inc. -Copyright IBM Corp. 2018 -Copyright IBM Corp. 2019 -Copyright Nokia 2007-2018 -Copyright Nokia 2007-2019 -Copyright Nokia 2007-2020 -Copyright Siemens AG 2020 -Copyright 2011 Google Inc. -Copyright 2017 Ribose Inc. -(c) 2013 ATT Wi-Fi Services -Copyright 2017 BaishanCloud -Copyright 2013 M. J. Dominus -Copyright 2019 Red Hat, Inc. -Copyright IBM Corp. 2018-2019 -Copyright Patrick Powell 1995 -Copyright (c) 2011, RTFM, Inc. -Copyright Siemens AG 2015-2019 -Copyright Siemens AG 2015-2020 -Copyright Siemens AG 2015-2022 -Copyright Siemens AG 2018-2020 -Copyright Siemens AG 2019-2022 -Copyright 2016 VMS Software, Inc. -Copyright (c) 2004, EdelKey Project -Copyright (c) 2015 CloudFlare, Inc. -Copyright (c) 2015, CloudFlare, Inc. -Copyright (c) 2005 WISeKey SA1 InterU -Copyright (c) 2012, Intel Corporation -Copyright (c) 2014, Intel Corporation -Copyright (c) 2020, Intel Corporation -Copyright (c) 2021, Intel Corporation -Copyright (c) 2002 The OpenTSA Project -Copyright 1998-$YEAR The OpenSSL Authors -Copyright 2004-2014, Akamai Technologies -Copyright 2023The OpenSSL Project Authors -Copyright (c) 2020-2021, Intel Corporation -Copyright 2014 Cryptography Research, Inc. -Copyright 2015 Cryptography Research, Inc. -Copyright 2016 Cryptography Research, Inc. -Copyright 2016 The OpenSSL Project Authors -Copyright 2017 The OpenSSL Project Authors -Copyright 2018 The OpenSSL Project Authors -Copyright 2019 The OpenSSL Project Authors -Copyright 2020 The OpenSSL Project Authors -Copyright 2021 The OpenSSL Project Authors -Copyright 2022 The OpenSSL Project Authors -Copyright 2023 The OpenSSL Project Authors -Copyright (c) 1998-2023 The OpenSSL Project -Copyright (c) 2006, Network Resonance, Inc. -Copyright (c) 2012-2014 Daniel J. Bernstein -copyrighted by the Free Software Foundation -Copyright (c) 2012-2016 Jean-Philippe Aumasson -Copyright 1995-2016 The OpenSSL Project Authors -Copyright 1995-2017 The OpenSSL Project Authors -Copyright 1995-2018 The OpenSSL Project Authors -Copyright 1995-2019 The OpenSSL Project Authors -Copyright 1995-2020 The OpenSSL Project Authors -Copyright 1995-2021 The OpenSSL Project Authors -Copyright 1995-2022 The OpenSSL Project Authors -Copyright 1995-2023 The OpenSSL Project Authors -Copyright 1998-2016 The OpenSSL Project Authors -Copyright 1998-2017 The OpenSSL Project Authors -Copyright 1998-2020 The OpenSSL Project Authors -Copyright 1998-2021 The OpenSSL Project Authors -Copyright 1998-2022 The OpenSSL Project Authors -Copyright 1998-2023 The OpenSSL Project Authors -Copyright 1999-2016 The OpenSSL Project Authors -Copyright 1999-2018 The OpenSSL Project Authors -Copyright 1999-2020 The OpenSSL Project Authors -Copyright 1999-2021 The OpenSSL Project Authors -Copyright 1999-2022 The OpenSSL Project Authors -Copyright 1999-2023 The OpenSSL Project Authors -Copyright 2000-2016 The OpenSSL Project Authors -Copyright 2000-2017 The OpenSSL Project Authors -Copyright 2000-2018 The OpenSSL Project Authors -Copyright 2000-2019 The OpenSSL Project Authors -Copyright 2000-2020 The OpenSSL Project Authors -Copyright 2000-2021 The OpenSSL Project Authors -Copyright 2000-2022 The OpenSSL Project Authors -Copyright 2000-2023 The OpenSSL Project Authors -Copyright 2001-2016 The OpenSSL Project Authors -Copyright 2001-2017 The OpenSSL Project Authors -Copyright 2001-2018 The OpenSSL Project Authors -Copyright 2001-2020 The OpenSSL Project Authors -Copyright 2001-2021 The OpenSSL Project Authors -Copyright 2001-2022 The OpenSSL Project Authors -Copyright 2001-2023 The OpenSSL Project Authors -Copyright 2002-2016 The OpenSSL Project Authors -Copyright 2002-2018 The OpenSSL Project Authors -Copyright 2002-2020 The OpenSSL Project Authors -Copyright 2002-2021 The OpenSSL Project Authors -Copyright 2002-2022 The OpenSSL Project Authors -Copyright 2002-2023 The OpenSSL Project Authors -Copyright 2003-2021 The OpenSSL Project Authors -Copyright 2003-2022 The OpenSSL Project Authors -Copyright 2003-2023 The OpenSSL Project Authors -Copyright 2004-2016 The OpenSSL Project Authors -Copyright 2004-2017 The OpenSSL Project Authors -Copyright 2004-2018 The OpenSSL Project Authors -Copyright 2004-2020 The OpenSSL Project Authors -Copyright 2004-2021 The OpenSSL Project Authors -Copyright 2004-2022 The OpenSSL Project Authors -Copyright 2004-2023 The OpenSSL Project Authors -Copyright 2005-2016 The OpenSSL Project Authors -Copyright 2005-2018 The OpenSSL Project Authors -Copyright 2005-2020 The OpenSSL Project Authors -Copyright 2005-2021 The OpenSSL Project Authors -Copyright 2005-2022 The OpenSSL Project Authors -Copyright 2005-2023 The OpenSSL Project Authors -Copyright 2006-2016 The OpenSSL Project Authors -Copyright 2006-2017 The OpenSSL Project Authors -Copyright 2006-2018 The OpenSSL Project Authors -Copyright 2006-2020 The OpenSSL Project Authors -Copyright 2006-2021 The OpenSSL Project Authors -Copyright 2006-2022 The OpenSSL Project Authors -Copyright 2006-2023 The OpenSSL Project Authors -Copyright 2007-2016 The OpenSSL Project Authors -Copyright 2007-2018 The OpenSSL Project Authors -Copyright 2007-2019 The OpenSSL Project Authors -Copyright 2007-2020 The OpenSSL Project Authors -Copyright 2007-2021 The OpenSSL Project Authors -Copyright 2007-2022 The OpenSSL Project Authors -Copyright 2007-2023 The OpenSSL Project Authors -Copyright 2008-2016 The OpenSSL Project Authors -Copyright 2008-2018 The OpenSSL Project Authors -Copyright 2008-2020 The OpenSSL Project Authors -Copyright 2008-2021 The OpenSSL Project Authors -Copyright 2008-2022 The OpenSSL Project Authors -Copyright 2008-2023 The OpenSSL Project Authors -Copyright 2009-2020 The OpenSSL Project Authors -Copyright 2009-2021 The OpenSSL Project Authors -Copyright 2009-2022 The OpenSSL Project Authors -Copyright 2009-2023 The OpenSSL Project Authors -Copyright 2010-2016 The OpenSSL Project Authors -Copyright 2010-2020 The OpenSSL Project Authors -Copyright 2010-2021 The OpenSSL Project Authors -Copyright 2010-2022 The OpenSSL Project Authors -Copyright 2010-2023 The OpenSSL Project Authors -Copyright 2011-2016 The OpenSSL Project Authors -Copyright 2011-2020 The OpenSSL Project Authors -Copyright 2011-2021 The OpenSSL Project Authors -Copyright 2011-2022 The OpenSSL Project Authors -Copyright 2011-2023 The OpenSSL Project Authors -Copyright 2012, Samuel Neves -Copyright 2012-2016 The OpenSSL Project Authors -Copyright 2012-2020 The OpenSSL Project Authors -Copyright 2012-2021 The OpenSSL Project Authors -Copyright 2012-2022 The OpenSSL Project Authors -Copyright 2012-2023 The OpenSSL Project Authors -Copyright 2013-2017 The OpenSSL Project Authors -Copyright 2013-2018 The OpenSSL Project Authors -Copyright 2013-2020 The OpenSSL Project Authors -Copyright 2013-2021 The OpenSSL Project Authors -Copyright 2013-2022 The OpenSSL Project Authors -Copyright 2013-2023 The OpenSSL Project Authors -Copyright 2014-2016 Cryptography Research, Inc. -Copyright 2014-2016 The OpenSSL Project Authors -Copyright 2014-2017 The OpenSSL Project Authors -Copyright 2014-2018 The OpenSSL Project Authors -Copyright 2014-2020 The OpenSSL Project Authors -Copyright 2014-2021 The OpenSSL Project Authors -Copyright 2014-2022 The OpenSSL Project Authors -Copyright 2014-2023 The OpenSSL Project Authors -Copyright 2015-2016 Cryptography Research, Inc. -Copyright 2015-2016 The OpenSSL Project Authors -Copyright 2015-2017 The OpenSSL Project Authors -Copyright 2015-2018 The OpenSSL Project Authors -Copyright 2015-2020 The OpenSSL Project Authors -Copyright 2015-2021 The OpenSSL Project Authors -Copyright 2015-2022 The OpenSSL Project Authors -Copyright 2015-2023 The OpenSSL Project Authors -Copyright 2016-2016 The OpenSSL Project Authors -Copyright 2016-2017 The OpenSSL Project Authors -Copyright 2016-2018 The OpenSSL Project Authors -Copyright 2016-2019 The OpenSSL Project Authors -Copyright 2016-2020 The OpenSSL Project Authors -Copyright 2016-2021 The OpenSSL Project Authors -Copyright 2016-2022 The OpenSSL Project Authors -Copyright 2016-2023 The OpenSSL Project Authors -Copyright 2017-2018 The OpenSSL Project Authors -Copyright 2017-2019 The OpenSSL Project Authors -Copyright 2017-2020 The OpenSSL Project Authors -Copyright 2017-2021 The OpenSSL Project Authors -Copyright 2017-2022 The OpenSSL Project Authors -Copyright 2017-2023 The OpenSSL Project Authors -Copyright 2018-2019 The OpenSSL Project Authors -Copyright 2018-2020 The OpenSSL Project Authors -Copyright 2018-2021 The OpenSSL Project Authors -Copyright 2018-2022 The OpenSSL Project Authors -Copyright 2018-2023 The OpenSSL Project Authors -Copyright 2019-2020 The OpenSSL Project Authors -Copyright 2019-2021 The OpenSSL Project Authors -Copyright 2019-2022 The OpenSSL Project Authors -Copyright 2019-2023 The OpenSSL Project Authors -Copyright 2020-2021 The OpenSSL Project Authors -Copyright 2020-2022 The OpenSSL Project Authors -Copyright 2020-2023 The OpenSSL Project Authors -Copyright 2021-2022 The OpenSSL Project Authors -Copyright 2021-2023 The OpenSSL Project Authors -Copyright 2022-2023 The OpenSSL Project Authors -Copyright 20xx-20yy The OpenSSL Project Authors -Copyright (c) 2002, Oracle and/or its affiliates -Copyright (c) 2017, Oracle and/or its affiliates -Copyright (c) 2018, Oracle and/or its affiliates -Copyright (c) 2019, Oracle and/or its affiliates -Copyright 1995-$YEAR The OpenSSL Project Authors -Copyright 1998-$YEAR The OpenSSL Project Authors -Copyright 1999-$YEAR The OpenSSL Project Authors -Copyright 2000-$YEAR The OpenSSL Project Authors -Copyright 2020-$YEAR The OpenSSL Project Authors -Copyright (c) 1989 Free Software Foundation, Inc. -Copyright 2017 Ribose Inc. (https://www.ribose.com) -Copyright (c) 1995-1998 Eric A. Young, Tim J. Hudson -Copyright (c) 2008 Andy Polyakov -Copyright 2021 UnionTech (https://www.uniontech.com) -Copyright (c) 2018-2019, Oracle and/or its affiliates -Copyright (c) 2018-2020, Oracle and/or its affiliates -Copyright (c) 2019-2020, Oracle and/or its affiliates -Copyright (c) 2013 by Mark Jason Dominus -Copyright (c) 2017 National Security Research Institute -copyright (c) 2013 by Mark Jason Dominus -Copyright (c) 2004, Richard Levitte -Copyright (c) 2013-2014 Timo Teras -Copyright (c) 2007 KISA(Korea Information Security Agency) -Copyright (c) 2004, 2018, Richard Levitte -Copyright (c) 2016 Viktor Dukhovni -Copyright 2006 NTT (Nippon Telegraph and Telephone Corporation) -Copyright (c) 2005 WISeKey SA1 Internat(onal1)0 WISeKey CertifyID Advanced G1 CA0 -Copyright (c) 2005 WISeKey SA1 International1 0 WISeKey CertifyID Advanced G1 CA0 -Copyright (c) 2004 Kungliga Tekniska Hogskolan (Royal Institute of Technology, Stockholm, Sweden) - - - Apache License - Version 2.0, January 2004 - https://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - ---------------------------------------------------------- - ---------------------------------------------------------- - -StyleCop.Analyzers 1.1.118 - Apache-2.0 AND MIT - - -copyright company -PlaceholderCompany Copyright (c) -Copyright (c) 2015 Dennis Fischer -Copyright (c) 2017 Marcos Lopez C. -Copyright 2014 Giovanni Bassi and Elemar Jr -Copyright (c) Tunnel Vision Laboratories, LLC. -Copyright Tunnel Vision Laboratories, LLC 2015 -copyright tag should contain a non-empty company -Copyright 2015 Tunnel Vision Laboratories, LLC StyleCop DotNetAnalyzers Roslyn Diagnostic - -Copyright (c) Tunnel Vision Laboratories, LLC. All rights reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -these files except in compliance with the License. You may obtain a copy of the -License at - -http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. - ---- - -This project uses other open source projects, which are used under the terms -of the following license(s). - -.NET Compiler Platform ("Roslyn") - - Copyright Microsoft. - - Licensed under the Apache License, Version 2.0 (the "License"); you may not use - these files except in compliance with the License. You may obtain a copy of the - License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software distributed - under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for the - specific language governing permissions and limitations under the License. - -Code Cracker - - Copyright 2014 Giovanni Bassi and Elemar Jr. - - Licensed under the Apache License, Version 2.0 (the "License"); you may not use - these files except in compliance with the License. You may obtain a copy of the - License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software distributed - under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. See the License for the - specific language governing permissions and limitations under the License. - -LightJson - - Copyright (c) 2017 Marcos López C. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in - all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - THE SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Markdig.Signed 0.33.0 - BSD-2-Clause - - - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Web.WebView2 1.0.3485.44 - BSD-3-Clause - - -(c) MEE. -(c) (c) S -(c) Gyw A -(c) Ono Q -(c) 3Y Thaeh -(c) Dyma (c) -(c) Microsoft 2025 -(c) Microsoft Corporation -Copyright Sam Harwell 2011 -Copyright Microsoft Corporation -Copyright (c) Microsoft Corporation -Copyright (c) 2011 The ANTLR Project - -Copyright (C) Microsoft Corporation. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * The name of Microsoft Corporation, or the names of its contributors -may not be used to endorse or promote products derived from this -software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Moq 4.18.2 - BSD-3-Clause - - -Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors - -Copyright (c) . All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -curl/curl 83bedbd730d62b83744cc26fa0433d3f6e2e4cd6 - BSD-3-Clause AND BSD-4-Clause-UC AND ISC AND curl - - -Copyright (C) David Shaw -Copyright (C) Howard Chu -Copyright (C) Max Dymond -(c) ! ISCNTRL (c) ISSPACE -Copyright (C) Jeroen Ooms -Copyright (C) Mark Gaiser -Copyright (c) Evgeny Grin -Daniel Stenberg, , et al. -Copyright (C) Dan Fandrich -Copyright (C) Dorian Craps -Copyright (C) Jan Venekamp -Copyright (c) Dan Fandrich -Copyright (C) Dmitry Karpov -Copyright (C) Jay Satiro, . -Copyright (C) John Malmberg -Copyright (C) Red Hat, Inc. -Copyright (c) John Malmberg -Copyright (c) Red Hat, Inc. -copyright When contributing -Copyright (C) Björn Stenberg -Copyright (C) Michael Forney -Copyright (C) Steve Holme, . -Copyright (C) Viktor Szakats -Copyright (c) Viktor Szakats -Copyright (C) Daniel Stenberg -Copyright (c) Daniel Fandrich -Copyright (c) Daniel Stenberg -Copyright (C) Nicolas Sterchele -Copyright (C) " CURL_COPYRIGHT "\0" -Copyright (C) Jacob Hoffman-Andrews -Copyright (C) 2006-2022 wolfSSL Inc. -Copyright (C) James Fuller, , et al. -Copyright (C) Linus Nielsen Feltzing -Copyright (c) 2001 Alexander Peslyak -Copyright (c) 2006-2022 wolfSSL Inc. -Copyright (C) Daniel Fandrich, et al. -Copyright (C) Vijay Panghal, , et al. -Copyright (C) " LIBCURL_COPYRIGHT "\0" -Copyright (C) Daniel Fandrich, , et al. -Copyright (C) Daniel Stenberg, , et al. -Copyright (C) Simon Josefsson, , et al. -Copyright (C) Evgeny Grin (Karlson2k), . -2022 Free Software Foundation Europe e.V. -Copyright (c) Internet Software Consortium. -Copyright Daniel Stenberg, -Copyright (c) Howard Chu, -Copyright (c) Mandy Wu, -Copyright (C) Bill Nagel , Exacq Technologies -Copyright (c) Bjorn Stenberg, -Copyright (c) Jan Venekamp, -Copyright (c) Mark Gaiser, -Copyright (c) Daniel Stenberg, -Copyright (c) Howard Chu, -Copyright (c) Jay Satiro, -Copyright (c) David Shaw -Copyright (c) Jeroen Ooms -Copyright (c) Hoi-Ho Chan, -Copyright (c) Nick Zitzmann, -Copyright (C) EdelWeb for EdelKey and OpenEvidence -Copyright (c) EdelWeb for EdelKey and OpenEvidence -Copyright (c) James Fuller, -Copyright (c) Dmitry Karpov -Copyright (c) Michael Forney, -Copyright (c) 1996-2022 Internet Software Consortium -Copyright (c) 1998, 1999 Kungliga Tekniska Hogskolan -Copyright (c) Linus Nielsen Feltzing -Copyright (c) Marc Hoersken, -Copyright (c) Max Dymond, -Copyright (c) Simon Josefsson, -Copyright (c) Steve Holme, -Copyright (C) 1996-2022 Internet Software Consortium. -Copyright (c) Linus Nielsen Feltzing, -Copyright (c) Mark Salisbury, -Copyright (c) Vijay Panghal, -Copyright (c) 2001-2004 Damien Miller -Copyright (c) Daniel Fandrich, -Copyright (c) Florin Petriuc, -Copyright (c) Nicolas Sterchele, -Copyright (c) 1983, Regents of the University of California -Copyright (c) Dorian Craps, -Copyright (c) Markus Moeller, -Copyright (c) Jacob Hoffman-Andrews, -Copyright (c) Bill Nagel , Exacq Technologies -Copyright various years The Regents of the University of California -Copyright (C) Daniel Stenberg et al. OS/400 version by P. Monnerat")' -Copyright 2022 Free Software Foundation Europe e.V. -Copyright (c) Daniel Stenberg, , and many contributors -Copyright (c) 1996 - 2024, Daniel Stenberg, , and many contributors -Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Hogskolan (Royal Institute of Technology, Stockholm, Sweden) - -COPYRIGHT AND PERMISSION NOTICE - -Copyright (c) 1996 - 2024, Daniel Stenberg, , and many -contributors, see the THANKS file. - -All rights reserved. - -Permission to use, copy, modify, and distribute this software for any purpose -with or without fee is hereby granted, provided that the above copyright -notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN -NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE -OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of a copyright holder shall not -be used in advertising or otherwise to promote the sale, use or other dealings -in this Software without prior written authorization of the copyright holder. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -c-ares/c-ares fddf01938d3789e06cc1c3774e4cd0c7d2a89976 - HPND - - -Copyright 2008 Google Inc. -Copyright 2005, Google Inc. -Copyright 2006, Google Inc. -Copyright 2007, Google Inc. -Copyright 2008, Google Inc. -Copyright 2013, Google Inc. -Copyright 2015, Google Inc. -Copyright (c) 2012 Xan Lopez -Copyright (c) 2021 Permission -Copyright (c) 2012 Dan Winship -Copyright 2005 Dominick Meglio -Copyright (c) 2012 Paolo Borelli -Copyright (c) 2021 by Brad House -Copyright 1998 by Daniel Stenberg -Copyright 2004 by Daniel Stenberg -Copyright 2005 by Dominick Meglio -Copyright (c) 2012 Christian Persch -Copyright (c) 2017 by John Schember -Copyright (c) 2014, 2015 Google Inc. -Copyright (c) 2004 by Daniel Stenberg -Copyright (c) 2005 by Dominick Meglio -Copyright (c) 2008 by Daniel Stenberg -Copyright (c) 2013 by Daniel Stenberg -Copyright (c) 2016 by Daniel Stenberg -Copyright (c) 2019 by Andrew Selivanov -Copyright (c) 2012, 2016 Philip Withnall -Copyright (c) 2015,2018 Bastien ROUCARIES -Copyright (c) 2004-2009 by Daniel Stenberg -Copyright (c) 2004-2010 by Daniel Stenberg -Copyright (c) 2004-2011 by Daniel Stenberg -Copyright (c) 2004-2017 by Daniel Stenberg -Copyright (c) 2005 - 2010, Daniel Stenberg -Copyright (c) 2005-2013 by Daniel Stenberg -Copyright (c) 2007 - 2018, Daniel Stenberg -Copyright (c) 2007-2013 by Daniel Stenberg -Copyright (c) 2008-2010 by Daniel Stenberg -Copyright (c) 2008-2013 by Daniel Stenberg -Copyright (c) 2009-2013 by Daniel Stenberg -Copyright (c) 2009-2021 by Daniel Stenberg -Copyright (c) 2010-2012 by Daniel Stenberg -Copyright (c) 2010-2013 by Daniel Stenberg -Copyright (c) 2005, 2013 by Dominick Meglio -Copyright (c) 2004 - 2011 by Daniel Stenberg -Copyright (c) 2004 - 2012 by Daniel Stenberg -Copyright (c) 2004 - 2013 by Daniel Stenberg -Copyright (c) 2008 - 2009 by Daniel Stenberg -Copyright (c) 2008 - 2012 by Daniel Stenberg -Copyright (c) 2008 - 2013 by Daniel Stenberg -Copyright (c) 2009 - 2021 by Daniel Stenberg -Copyright (c) 2017 - 2018 by Christian Ammer -Copyright (c) 2010 Jeremy Lal -Copyright (c) 2012 Marko Kreen -Copyright (c) 2012 Zack Weinberg -Copyright (c) 2018 The Android Open Source Project -Copyright 2020 by -Copyright (c) 2011 Daniel Stenberg -Copyright (c) 2013 Daniel Stenberg -Copyright (c) 2008 Benjamin Kosnik -Copyright (c) 1995, 1996, 1997, and 1998 WIDE Project -Copyright (c) 2014 Mike Frysinger -Copyright (c) 2008 Tom Howard -Copyright (c) 2009 Tom Howard -Copyright 2010 by Ben Greear -Copyright 2020 Danny Sonnenschein -Copyright (c) 1996,1999 by Internet Software Consortium -Copyright (c) 1996-1999 by Internet Software Consortium -Copyright (c) 2004 by Internet Systems Consortium, Inc. -Copyright (c) 2009 by Jakub Hrozek -Copyright (c) 2011 Daniel Richard G. -Copyright (c) 2009 Allan Caffee -Copyright (c) 2013 Roy Stogner -Copyright (c) 2017 by John Schember -Copyright (c) 2018 by John Schember -Copyright (c) 2008 Steven G. Johnson -Copyright 1998 by the Massachusetts Institute of Technology -Copyright 2000 by the Massachusetts Institute of Technology -Copyright 1998, 2000 by the Massachusetts Institute of Technology -Copyright 1998, 2011 by the Massachusetts Institute of Technology -Copyright (c) 1987-2001 The Regents of the University of California -Copyright (c) 2008 John Darrington -Copyright (c) 2015 Enrico M. Crisostomo -Copyright 1998, 2011, 2013 by the Massachusetts Institute of Technology - -# c-ares license - -Copyright (c) 2007 - 2018, Daniel Stenberg with many contributors, see AUTHORS -file. - -Copyright 1998 by the Massachusetts Institute of Technology. - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, provided that -the above copyright notice appear in all copies and that both that copyright -notice and this permission notice appear in supporting documentation, and that -the name of M.I.T. not be used in advertising or publicity pertaining to -distribution of the software without specific, written prior permission. -M.I.T. makes no representations about the suitability of this software for any -purpose. It is provided "as is" without express or implied warranty. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -microsoft/xlang cfe510d0d2b07484fea2c6d77163de017738c100 - LicenseRef-scancode-generic-cla AND MIT - - -(c) Microsoft Corporation -Copyright 2017 Two Blue Cubes Ltd. -Copyright (c) Microsoft Corporation -Copyright (c) 2019 Two Blue Cubes Ltd. - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Windows.WDK.Win32Metadata 0.12.8-experimental - LicenseRef-scancode-ms-windows-sdk-win10-net-6 - - -(c) Microsoft 2024 -(c) Microsoft Corporation - -LicenseRef-scancode-ms-windows-sdk-win10-net-6 - ---------------------------------------------------------- - ---------------------------------------------------------- - -coverlet.collector 3.1.2 - MIT - - -Copyright 2008 - 2018 Jb Evain -Copyright Microsoft Corporation -Copyright James Newton-King 2008 - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Humanizer.Core 2.14.1 - MIT - - -Copyright .NET Foundation and Contributors -Copyright (c) .NET Foundation and Contributors - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Json.More.Net 2.0.1.2 - MIT - - -Copyright (c) 2024 Greg Dennis - -MIT License - -Copyright (c) 2024 Greg Dennis - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -JsonPointer.Net 5.0.0 - MIT - - -Copyright (c) 2024 Greg Dennis - -MIT License - -Copyright (c) 2024 Greg Dennis - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -JsonSchema.Net 7.0.4 - MIT - - - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.ApplicationInsights 2.21.0 - MIT - - -(c) Microsoft Corporation - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Bcl.AsyncInterfaces 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.CodeAnalysis.Analyzers 3.3.4 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation -Copyright (c) 2013 Scott Kirkland -Copyright (c) 2012-2014 Mehdi Khalili -Copyright (c) 2013-2014 Omar Khudeira (http://omar.io) - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.CodeAnalysis.Common 4.9.2 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.CodeAnalysis.CSharp 4.9.2 - MIT - - -(c) Microsoft Corporation -Copyright (c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) Microsoft Corporation. Alle Rechte - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.AI.Abstractions 9.7.1 - MIT - - -(c) Microsoft Corporation - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration.Abstractions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration.Binder 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration.CommandLine 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration.EnvironmentVariables 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration.FileExtensions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration.Json 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Configuration.UserSecrets 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.DependencyInjection 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.DependencyInjection.Abstractions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Diagnostics 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Diagnostics.Abstractions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.FileProviders.Abstractions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.FileProviders.Physical 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.FileSystemGlobbing 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Hosting 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Hosting.Abstractions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Logging 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Logging.Abstractions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Logging.Configuration 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Logging.Console 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Logging.Debug 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Logging.EventLog 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Logging.EventSource 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.ObjectPool 8.0.10 - MIT - - -Copyright Jorn Zaefferer -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright (c) 2015, Google Inc. -Copyright (c) 2019 David Fowler -Copyright (c) HTML5 Boilerplate -Copyright 2019 The gRPC Authors -Copyright (c) 2016 Richard Morris -Copyright (c) 1998 John D. Polstra -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2013 - 2018 AngleSharp -Copyright (c) 2000-2013 Julian Seward -Copyright (c) 2011-2021 Twitter, Inc. -Copyright (c) 2014-2018 Michael Daines -Copyright (c) 1996-1998 John D. Polstra -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) .NET Foundation Contributors -Copyright (c) 2011-2021 The Bootstrap Authors -Copyright (c) 2019-2023 The Bootstrap Authors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2019-2020 West Wind Technologies -Copyright (c) 2007 John Birrell (jb@freebsd.org) -Copyright (c) 2011 Alex MacCaw (info@eribium.org) -Copyright (c) Nicolas Gallagher and Jonathan Neal -Copyright (c) 2010-2019 Google LLC. http://angular.io/license -Copyright (c) 2011 Nicolas Gallagher (nicolas@nicolasgallagher.com) -Copyright (c) 1989, 1993 The Regents of the University of California -Copyright (c) 1990, 1993 The Regents of the University of California -Copyright OpenJS Foundation and other contributors, https://openjsf.org -Copyright (c) Sindre Sorhus (https://sindresorhus.com) - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Options 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Options.ConfigurationExtensions 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Extensions.Primitives 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Management.Infrastructure 3.0.0 - MIT - - -(c) Microsoft Corporation - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Management.Infrastructure.CimCmdlets 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. CPowerShell's Microsoft.Management.Infrastructure.CimCmdlets project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Management.Infrastructure.Runtime.Unix 3.0.0 - MIT - - -(c) Microsoft Corporation - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Msix.Utils 2.1.1 - MIT - - - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.NET.ILLink.Tasks 8.0.20 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright 2008 - 2018 Jb Evain -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.NETCore.Platforms 5.0.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright 2012 the V8 project -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -Copyright (c) 1998 Microsoft. To -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2018 Alexander Chermyanin -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) The Internet Society 1997. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) The Internet Society (2003). -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.Commands.Diagnostics 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. PowerShell's Microsoft.PowerShell.Commands.Diagnostics project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.Commands.Management 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. PowerShell's Microsoft.PowerShell.Commands.Management project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.Commands.Utility 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. :PowerShell's Microsoft.PowerShell.Commands.Utility project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.ConsoleHost 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. PowerShell Host - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.CoreCLR.Eventing 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. :PowerShell's Microsoft.PowerShell.CoreCLR.Eventing project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.MarkdownRender 7.2.1 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. PowerShell's Markdown Rendering project PowerShell Markdown Renderer - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.Native 7.4.0 - MIT - - -(c) Microsoft Corporation -Copyright (c) by P.J. Plauger - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.SDK 7.4.6 - MIT - - -(c) Microsoft Corporation -Copyright (c) Microsoft Corporation -(c) Microsoft Corporation. PowerShell SDK -(c) Microsoft Corporation. PowerShell Host -(c) Microsoft Corporation. 1PowerShell's System.Management.Automation project -(c) Microsoft Corporation. :PowerShell's Microsoft.PowerShell.Commands.Utility project -(c) Microsoft Corporation. PowerShell's Microsoft.PowerShell.Commands.Management project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.PowerShell.Security 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. 2PowerShell's Microsoft.PowerShell.Security project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Security.Extensions 1.2.0 - MIT - - - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Win32.Registry 4.7.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Win32.Registry 5.0.0 - MIT - - -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 1991-2020 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Win32.Registry.AccessControl 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Win32.SystemEvents 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Win32.SystemEvents 9.0.0-preview.6.24327.7 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Windows.Compatibility 8.0.10 - MIT - - -(c) Microsoft Corporation - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Windows.CppWinRT 2.0.210503.1 - MIT - - -(c) Microsoft Corporation. -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Windows.CppWinRT 2.0.240405.15 - MIT - - -(c) Microsoft 2024 -(c) Microsoft Corporation -Copyright (c) Microsoft Corporation - - MIT License - - Copyright (c) Microsoft Corporation. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Windows.CppWinRT 2.0.250303.1 - MIT - - -(c) Microsoft 2025 -(c) Microsoft Corporation -Copyright (c) Microsoft Corporation - - MIT License - - Copyright (c) Microsoft Corporation. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Windows.CsWin32 0.3.183 - MIT - - - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Windows.ImplementationLibrary 1.0.250325.1 - MIT - - -(c) Microsoft 2025 -Copyright (c) Microsoft -(c) Microsoft Corporation -Copyright (c) Microsoft Corporation -Copyright (c) 2009-2014 by the contributors - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.WindowsPackageManager.ComInterop 1.8.1911 - MIT - - -(c) Microsoft Corporation - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.WSMan.Management 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. PowerShell's Microsoft.WSMan.Management project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.WSMan.Runtime 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. ,PowerShell's Microsoft.WSMan.Runtime project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -microsoft/correlationvector-cpp cf38d2b44baaf352509ad9980786bc49554c32e4 - MIT - - -Copyright (c) Microsoft Corporation. - - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ---------------------------------------------------------- - ---------------------------------------------------------- - -microsoft/cpprestsdk 411a109150b270f23c8c97fa4ec9a0a4a98cdecf - MIT - - -Copyright (c) Microsoft -Copyright (c) 2014, Peter Thorson -Copyright (c) Microsoft Corporation -Copyright (c) 2011, Micael Hildenborg -Copyright (c) 2004-2008 Rene Nyffenegger -Copyright (c) 1999, 2002 Aladdin Enterprises -Portions Copyright (c) Microsoft Corporation -Copyright (c) 2006 Noel Llopis and Charles Nicholson -Copyright (c) 2008-2009 Bjoern Hoehrmann - -C++ REST SDK - -The MIT License (MIT) - -Copyright (c) Microsoft Corporation - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -microsoft/sfs-client ff315ecfa2ef2953d8a808e51e8a61a4e0759180 - MIT - - -Copyright (c) Microsoft Corporation -Copyright (c) 2013-2022 Niels Lohmann -Copyright (c) 2007 - 2023 Daniel Stenberg -Copyright (c) 1998 Massachusetts Institute of Technology -Copyright (c) 1996 - 2024, Daniel Stenberg, , and many contributors - - MIT License - - Copyright (c) Microsoft Corporation. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE - - ---------------------------------------------------------- - ---------------------------------------------------------- - -ModelContextProtocol 0.3.0-preview.3 - MIT - - -(c) Anthropic and Contributors - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -ModelContextProtocol.Core 0.3.0-preview.3 - MIT - - -(c) Anthropic and Contributors - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -NETStandard.Library 2.0.0 - MIT - - -copyright Unmanaged32Bit Required32Bit -Copyright (c) .NET Foundation and Contributors - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -NETStandard.Library 2.0.3 - MIT - - -copyright Unmanaged32Bit Required32Bit -Copyright (c) .NET Foundation and Contributors - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Newtonsoft.Json 13.0.3 - MIT - - -Copyright James Newton-King 2008 -Copyright (c) 2007 James Newton-King -Copyright (c) James Newton-King 2008 -Copyright James Newton-King 2008 Json.NET - -The MIT License (MIT) - -Copyright (c) 2007 James Newton-King - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Newtonsoft.Json.Bson 1.0.1 - MIT - - -Copyright James Newton-King 2017 -Copyright (c) James Newton-King 2017 - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -nlohmann/json 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 - MIT - - -(c) 2016 -2012 Erik Edlund -2017 Georg Sauthoff -2018 Vitaliy Manushkin -2013-2022 Niels Lohmann -2013-2023 Niels Lohmann -2015-2017 Niels Lohmann -2016-2021 Evan Nemerson -2018 The Abseil Authors -2003-2022, LLVM Project. -2016-2021 Viktor Kirilov -(c) 2013-2022 Niels Lohmann -Copyright 2003-2022, LLVM Project -Copyright 2015-2017 Niels Lohmann -Copyright 2018 The Abseil Authors -Copyright (c) 2009 Florian Loitsch -Copyright 2016-2021 Viktor Kirilov -Copyright (c) 2013-2022 Niels Lohmann -Copyright (c) 2015-2017 Niels Lohmann -copyright (c) 2013-2022 Niels Lohmann -copyright (c) 2013-2023 Niels Lohmann -Copyright (c) 2016-2023 Viktor Kirilov -Copyright 2017 Georg Sauthoff -copyright Niels Lohmann -Copyright 2012 Erik Edlund -Copyright Copyright (c) 2013 - 2023 Niels Lohmann -Copyright 2018 Vitaliy Manushkin -Copyright 2016-2021 Evan Nemerson -Copyright (c) 2012, Erik Edlund -Copyright 2013-2022 Niels Lohmann -Copyright 2013-2023 Niels Lohmann -Copyright 2020 Hannes Domani -Copyright 2008-2009 Bjorn Hoehrmann -Copyright (c) 2013-2019 Niels Lohmann -Copyright (c) 2013-2022 Niels Lohmann -Copyright (c) 2013-2022 Niels Lohmann (https://nlohmann.me) -Copyright (c) 2020 Hannes Domani (https://github.com/ssbssa) -Copyright 2009 Florian Loitsch -Copyright (c) 2009 Florian Loitsch (https://florian.loitsch.com/) -Copyright (c) 2007 Free Software Foundation, Inc. -Copyright (c) 2008-2009 Bjorn Hoehrmann (http://bjoern.hoehrmann.de/) -Copyright (c) 2008-2009 Bjorn Hoehrmann (https://bjoern.hoehrmann.de/) -Copyright (c) 2008-2009 Bjoern Hoehrmann @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa - -MIT License - -Copyright (c) 2013-2022 Niels Lohmann - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -NUnit 3.12.0 - MIT - - -(c) Microsoft 2024 -copyright in NUnit Console -(c) 2019 Charlie Poole, Rob Prouse -Copyright (c) 2002-2014 Charlie Poole -Copyright (c) 2000-2002 Philip A. Craig -Copyright (c) 2019 Charlie Poole, Rob Prouse -Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov - -Copyright (c) 2019 Charlie Poole, Rob Prouse - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -NUnit3TestAdapter 3.15.1 - MIT - - -(c) Microsoft 2025 -Copyright 2008 - 2015 Jb Evain -Copyright 2008 - 2018 Jb Evain -(c) 2019 Charlie Poole, Rob Prouse -Copyright (c) 2019 Charlie Poole, Rob Prouse -Copyright 2011-2019 Charlie Poole, 2014-2019 Terje Sandstrom -Copyright (c) 2011-2019 Charlie Poole, 2014-2019 Terje Sandstrom - -Copyright (c) 2011-2019 Charlie Poole, 2014-2019 Terje Sandstrom - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Octokit 4.0.3 - MIT - - -Copyright GitHub 2017 -Copyright GitHub 2017 GitHub API Octokit - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -PowerShellStandard.Library 5.1.1 - MIT - - -(c) Microsoft Corporation. - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -ronomon/pure fd54913e65338e678440ae66b3b5022ab23b761b - MIT - - -Copyright (c) 2003 Mark Adler -Copyright (c) 2020 Ronomon CC -Copyright 1995-2017 Mark Adler -Copyright (c) 2003 Cosmin Truta -Copyright (c) 1990-2000 Info-ZIP. -Copyright (c) 1998 by Bob Dellaca -Copyright (c) 1995-2003 Mark Adler -Copyright (c) 1995-2008 Mark Adler -Copyright (c) 1995-2016 Mark Adler -Copyright (c) 1995-2017 Mark Adler -Copyright (c) 2002-2013 Mark Adler -Copyright (c) 2003 by Cosmin Truta -Copyright (c) 2003-2010 Mark Adler -Copyright (c) 2004-2017 Mark Adler -Copyright (c) 1996 L. Peter Deutsch -Copyright (c) 1997,99 Borland Corp. -Copyright (c) 2003, 2012 Mark Adler -Copyright (c) 2004, 2005 Mark Adler -Copyright (c) 2004, 2010 Mark Adler -Copyright (c) 2005, 2012 Mark Adler -Copyright (c) 2011, 2016 Mark Adler -Copyright (c) 2007-2008 Even Rouault -Copyright (c) 1998-2005 Gilles Vollant -Copyright (c) 2004, 2005 by Mark Adler -Copyright (c) 1995-1998 Jean-loup Gailly -Copyright (c) 1995-2003 Jean-loup Gailly -Copyright (c) 1995-2003, 2010 Mark Adler -Copyright (c) 1995-2005, 2010 Mark Adler -Copyright (c) 1995-2011, 2016 Mark Adler -Copyright (c) 1995-2016 Jean-loup Gailly -Copyright (c) 1995-2017 Jean-loup Gailly -Copyright (c) 1997,99 Borland Corporation -Copyright (c) 1998 by Andreas R. Kleinert -Copyright (c) 2002-2003 Dmitriy Anisimkov -Copyright (c) 2002-2004 Dmitriy Anisimkov -Copyright (c) 2003, 2012, 2013 Mark Adler -Copyright (c) 2004, 2005, 2012 Mark Adler -Copyright (c) 2004, 2008, 2012 Mark Adler -Copyright (c) 2007, 2008, 2012 Mark Adler -Copyright (c) 1998 by Jacques Nomssi Nzali -(c) 1995-2017 Jean-loup Gailly & Mark Adler -Copyright (c) 1995-2003 by Jean-loup Gailly -Copyright (c) 1998-2010 - by Gilles Vollant -(c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2004, 2008, 2012, 2016 Mark Adler -Copyright 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 1995-2006, 2011, 2016 Jean-loup Gailly -Copyright (c) 1995-2016 Jean-loup Gailly, Mark Adler -Copyright (c) 1995-2017 Jean-Loup Gailly, Mark Adler -Copyright (c) 1995-2017 Jean-loup Gailly, Mark Adler -Copyright (c) 1998,1999,2000 by Jacques Nomssi Nzali -Copyright (c) 2003, 2005, 2008, 2010, 2012 Mark Adler -Copyright (c) 2003 Chris Anderson -Copyright (c) 1995-2003 Jean-loup Gailly and Mark Adler -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 1996 L. Peter Deutsch and Jean-Loup Gailly -Copyright (c) 1998 Brian Raiter -Copyright (c) 1995-2006, 2010, 2011, 2012, 2016 Mark Adler -Copyright (c) 1995-2006, 2010, 2011, 2016 Jean-loup Gailly -Copyright (c) 2009-2010 Mathias Svensson http://result42.com -Copyright (c) 1998, 2007 Brian Raiter -Copyright (c) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler -Copyright (c) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler -Copyright Jean-loup Gailly Osma Ahvenlampi -Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll -Copyright (c) 1997 Christian Michelsen Research AS Advanced Computing -Copyright (c) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler -Copyright (c) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson -Copyright (c) 1995-1996 Jean-loup Gailly, Brian Raiter and Gilles Vollant -Copyright (c) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant -Copyright (c) 1998-2010 Gilles Vollant (minizip) http://www.winimage.com/zLibDll/minizip.html - -The MIT License (MIT) - -Copyright (c) 2020 Ronomon CC - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -runtime.linux-arm.runtime.native.System.IO.Ports 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -runtime.linux-arm64.runtime.native.System.IO.Ports 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -runtime.linux-x64.runtime.native.System.IO.Ports 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -runtime.native.System.Data.SqlClient.sni 4.7.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -runtime.native.System.IO.Ports 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -runtime.osx-arm64.runtime.native.System.IO.Ports 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -runtime.osx-x64.runtime.native.System.IO.Ports 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Semver 2.3.0 - MIT - - -Copyright 2013 Max Hauser, Jeff Walker -Copyright (c) 2013 Max Hauser, Jeff Walker -Copyright 2013 Max Hauser, Jeff Walker VA SemVer - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Buffers 4.5.1 - MIT - - -(c) Microsoft Corporation -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2015 The Chromium Authors -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.CodeDom 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Collections.Immutable 8.0.0 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ComponentModel.Composition 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ComponentModel.Composition.Registration 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Configuration.ConfigurationManager 8.0.1 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Data.Odbc 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Data.OleDb 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Data.SqlClient 4.8.6 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Diagnostics.DiagnosticSource 5.0.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright 2012 the V8 project -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -Copyright (c) 1998 Microsoft. To -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2018 Alexander Chermyanin -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) The Internet Society 1997. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) The Internet Society (2003). -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Diagnostics.DiagnosticSource 8.0.1 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Diagnostics.DiagnosticSource 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Diagnostics.EventLog 6.0.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright 2012 the V8 project -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -Copyright (c) 1998 Microsoft. To -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2018 Alexander Chermyanin -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) The Internet Society 1997. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) The Internet Society (2003). -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Diagnostics.EventLog 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Diagnostics.EventLog 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Diagnostics.PerformanceCounter 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.DirectoryServices 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.DirectoryServices.AccountManagement 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.DirectoryServices.Protocols 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Drawing.Common 8.0.19 - MIT - - -(c) Microsoft Corporation -Copyright (c) Sven Groot (Ookii.org) 2009 -Copyright (c) .NET Foundation and Contributors - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Drawing.Common 9.0.0-preview.6.24327.6 - MIT - - -(c) Microsoft Corporation -Copyright (c) Sven Groot (Ookii.org) 2009 -Copyright (c) .NET Foundation and Contributors - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Formats.Asn1 8.0.1 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.IO.Packaging 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.IO.Pipelines 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.IO.Ports 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Management 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Management.Automation 7.4.6 - MIT - - -(c) Microsoft Corporation -(c) Microsoft Corporation. 1PowerShell's System.Management.Automation project - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Memory 4.5.4 - MIT - - -(c) Microsoft Corporation -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2015 The Chromium Authors -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Net.Http.WinHttpHandler 8.0.2 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Net.ServerSentEvents 10.0.0-preview.4.25258.110 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2024 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Numerics.Vectors 4.4.0 - MIT - - -(c) Microsoft Corporation. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 1991-2017 Unicode, Inc. -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Numerics.Vectors 4.5.0 - MIT - - -(c) 2023 GitHub, Inc. -(c) Microsoft Corporation -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2015 The Chromium Authors -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Private.ServiceModel 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Reflection.Context 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Reflection.DispatchProxy 4.7.1 - MIT - - -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Reflection.Metadata 1.6.0 - MIT - - -(c) 2023 GitHub, Inc. -(c) Microsoft Corporation -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2015 The Chromium Authors -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Reflection.Metadata 8.0.0 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -Gets the Copyright Table -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Runtime.Caching 8.0.1 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Runtime.CompilerServices.Unsafe 4.5.3 - MIT - - -(c) Microsoft Corporation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 1991-2017 Unicode, Inc. -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Runtime.CompilerServices.Unsafe 6.0.0 - MIT - - -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2005-2020 Rich Felker -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 1991-2020 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Runtime.WindowsRuntime 4.6.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Runtime.WindowsRuntime.UI.Xaml 4.6.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2017 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Security.AccessControl 5.0.0 - MIT - - -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 1991-2020 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Security.AccessControl 6.0.1 - MIT - - -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2005-2020 Rich Felker -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 1991-2020 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Security.Cryptography.Pkcs 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Security.Cryptography.ProtectedData 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Security.Cryptography.Xml 8.0.2 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Security.Permissions 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Security.Principal.Windows 5.0.0 - MIT - - -(c) Microsoft Corporation. -Copyright (c) Andrew Arnott -Copyright 2018 Daniel Lemire -Copyright 2012 the V8 project -Copyright (c) .NET Foundation. -Copyright (c) 2011, Google Inc. -Copyright (c) 1998 Microsoft. To -(c) 1997-2005 Sean Eron Anderson. -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 2012-2014, Yann Collet -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2018 Alexander Chermyanin -Portions (c) International Organization -Copyright (c) 2015 The Chromium Authors. -Copyright (c) The Internet Society 1997. -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) .NET Foundation Contributors -Copyright (c) The Internet Society (2003). -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.Duplex 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.Http 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.NetTcp 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.Primitives 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.Security 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceModel.Syndication 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.ServiceProcess.ServiceController 8.0.1 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Speech 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Text.Encoding.CodePages 8.0.0 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Text.Encodings.Web 8.0.0 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Text.Encodings.Web 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Text.Json 9.0.6 - MIT - - -Copyright (c) 2021 -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright (c) 1998 Microsoft -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Portions (c) International Organization for Standardization 1986 -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Threading.AccessControl 9.0.0-preview.6.24327.7 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) 2022 FormatJS -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2015 Andrew Gallant -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright (c) 2018 Nemanja Mijailovic -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2015-2018, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Web.Services.Description 4.10.3 - MIT - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -System.Windows.Extensions 8.0.0 - MIT - - -Copyright (c) Six Labors -(c) Microsoft Corporation -Copyright (c) Andrew Arnott -Copyright 2019 LLVM Project -Copyright 2018 Daniel Lemire -Copyright (c) .NET Foundation -Copyright (c) 2011, Google Inc. -Copyright (c) 2020 Dan Shechter -(c) 1997-2005 Sean Eron Anderson -Copyright (c) 1998 Microsoft. To -Copyright (c) 2022, Wojciech Mula -Copyright (c) 2017 Yoshifumi Kawai -Copyright (c) 2022, Geoff Langdale -Copyright (c) 2005-2020 Rich Felker -Copyright (c) 2012-2021 Yann Collet -Copyright (c) Microsoft Corporation -Copyright (c) 2007 James Newton-King -Copyright (c) 1991-2022 Unicode, Inc. -Copyright (c) 2013-2017, Alfred Klomp -Copyright 2012 the V8 project authors -Copyright (c) 1999 Lucent Technologies -Copyright (c) 2008-2016, Wojciech Mula -Copyright (c) 2011-2020 Microsoft Corp -Copyright (c) 2015-2017, Wojciech Mula -Copyright (c) 2021 csFastFloat authors -Copyright (c) 2005-2007, Nick Galbreath -Copyright (c) 2015 The Chromium Authors -Copyright (c) 2018 Alexander Chermyanin -Copyright (c) The Internet Society 1997 -Portions (c) International Organization -Copyright (c) 2004-2006 Intel Corporation -Copyright (c) 2011-2015 Intel Corporation -Copyright (c) 2013-2017, Milosz Krajewski -Copyright (c) 2016-2017, Matthieu Darbois -Copyright (c) The Internet Society (2003) -Copyright (c) .NET Foundation Contributors -Copyright (c) 2020 Mara Bos -Copyright (c) .NET Foundation and Contributors -Copyright (c) 2012 - present, Victor Zverovich -Copyright (c) 2006 Jb Evain (jbevain@gmail.com) -Copyright (c) 2008-2020 Advanced Micro Devices, Inc. -Copyright (c) 2019 Microsoft Corporation, Daan Leijen -Copyright (c) 2011 Novell, Inc (http://www.novell.com) -Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler -Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) -Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors -Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com -Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. -Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers -Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip -Copyright (c) 1980, 1986, 1993 The Regents of the University of California -Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California -Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass - -The MIT License (MIT) - -Copyright (c) .NET Foundation and Contributors - -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -xunit.runner.visualstudio 2.4.5 - MIT - - -Copyright (c) .NET Foundation -Copyright (c) 2015 .NET Foundation -Copyright (c) Outercurve Foundation -Copyright (c) .NET Foundation and Contributors -Copyright (c) .NET Foundation xUnit.net Runner Utility -Copyright (c) .NET Foundation ,xUnit.net Runner Utility -Copyright (c) .NET Foundation 1xUnit.net Runner Utility -Copyright (c) .NET Foundation xUnit.net Runner Reporters -Copyright (c) .NET Foundation .xUnit.net Runner Reporters -Copyright (c) Outercurve Foundation WrapNonExceptionThrows RSDS -Copyright (c) .NET Foundation and Contributors. Visual Studio 2019 - -Unless otherwise noted, the source code here is covered by the following license: - - Copyright (c) .NET Foundation and Contributors - All Rights Reserved - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - ------------------------ - -The code in src/xunit.runner.visualstudio/Utility/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions was imported from: - https://github.com/dotnet/core-setup/tree/v2.0.1/src/managed/Microsoft.DotNet.PlatformAbstractions - -The code in src/xunit.runner.visualstudio/Utility/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions was imported from: - https://github.com/dotnet/core-setup/tree/v2.0.1/src/managed/Microsoft.Extensions.DependencyModel - -Both sets of code are covered by the following license: - - The MIT License (MIT) - - Copyright (c) 2015 .NET Foundation - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -yaml/libyaml 840b65c40675e2d06bf40405ad3f12dec7f35923 - MIT - - -Copyright (c) 2017-2020 Ingy dot Net -Copyright (c) 2006-2016 Kirill Simonov -Copyright (c) 2017-2020 Ingy dot Net -Copyright (c) 2006-2016 Kirill Simonov - -Copyright (c) 2017-2020 Ingy döt Net -Copyright (c) 2006-2016 Kirill Simonov - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -YamlDotNet 16.3.0 - MIT - - -Copyright (c) Antoine Aubry and contributors -(c) Antoine Aubry and contributors 2008 - 2019 -Copyright (c) Antoine Aubry and contributors 2008 - 2019 - -MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.Management.Infrastructure.Runtime.Win 3.0.0 - - -(c) Microsoft 2024 -(c) Microsoft Corporation -Copyright (c) Microsoft Corporation - -MICROSOFT SOFTWARE LICENSE TERMS -Microsoft.Management.Infrastructure.dll -Microsoft.Management.Infrastructure.Native.dll -Microsoft.Management.Infrastructure.Unmanaged.dll -Mi.dll -Miutils.dll -________________________________________ -IF YOU LIVE IN (OR ARE A BUSINESS WITH A PRINCIPAL PLACE OF BUSINESS IN) THE UNITED STATES, PLEASE READ THE “BINDING ARBITRATION AND CLASS ACTION WAIVER” SECTION BELOW. IT AFFECTS HOW DISPUTES ARE RESOLVED. -________________________________________ - -These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They apply to the software named above and any Microsoft services or software updates (except to the extent such services or updates are accompanied by new or additional terms, in which case those different terms apply prospectively and do not alter your or Microsoft’s rights relating to pre-updated software or services). IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. -1. INSTALLATION AND USE RIGHTS. -a) General. You may install and use any number of copies of the software solely for use with Microsoft PowerShell. -b) Third Party Software. The software may include third party applications that Microsoft, not the third party, licenses to you under this agreement. Any included notices for third party applications are for your information only. -2. DATA COLLECTION. The software may collect information about you and your use of the software and send that to Microsoft. Microsoft may use this information to provide services and improve Microsoft’s products and services. Your opt-out rights, if any, are described in the product documentation. Some features in the software may enable collection of data from users of your applications that access or use the software. If you use these features to enable data collection in your applications, you must comply with applicable law, including getting any required user consent, and maintain a prominent privacy policy that accurately informs users about how you use, collect, and share their data. You can learn more about Microsoft’s data collection and use in the product documentation and the Microsoft Privacy Statement at aka.ms/privacy. You agree to comply with all applicable provisions of the Microsoft Privacy Statement. -a. Processing of Personal Data. To the extent Microsoft is a processor or subprocessor of personal data in connection with the software, Microsoft makes the commitments in the European Union General Data Protection Regulation Terms of the Online Services Terms to all customers effective May 25, 2018, at http://go.microsoft.com/?linkid=9840733. -3. SCOPE OF LICENSE. The software is licensed, not sold. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you will not (and have no right to): -a) work around any technical limitations in the software that only allow you to use it in certain ways; -b) reverse engineer, decompile or disassemble the software; -c) remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software; -d) use the software in any way that is against the law or to create or propagate malware; or -e) share, publish, distribute, or lend the software, provide the software as a stand-alone hosted solution for others to use, or transfer the software or this agreement to any third party. -4. EXPORT RESTRICTIONS. You must comply with all domestic and international export laws and regulations that apply to the software, which include restrictions on destinations, end users, and end use. For further information on export restrictions, visit http://aka.ms/exporting. -5. SUPPORT SERVICES. Microsoft is not obligated under this agreement to provide any support services for the software. Any support provided is “as is”, “with all faults”, and without warranty of any kind. -6. UPDATES. The software may periodically check for updates, and download and install them for you. You may obtain updates only from Microsoft or authorized sources. Microsoft may need to update your system to provide you with updates. You agree to receive these automatic updates without any additional notice. Updates may not include or support all existing software features, services, or peripheral devices. -7. BINDING ARBITRATION AND CLASS ACTION WAIVER. This Section applies if you live in (or, if a business, your principal place of business is in) the United States. If you and Microsoft have a dispute, you and Microsoft agree to try for 60 days to resolve it informally. If you and Microsoft can’t, you and Microsoft agree to binding individual arbitration before the American Arbitration Association under the Federal Arbitration Act (“FAA”), and not to sue in court in front of a judge or jury. Instead, a neutral arbitrator will decide. Class action lawsuits, class-wide arbitrations, private attorney-general actions, and any other proceeding where someone acts in a representative capacity are not allowed; nor is combining individual proceedings without the consent of all parties. The complete Arbitration Agreement contains more terms and is at http://aka.ms/arb-agreement-1. You and Microsoft agree to these terms. -8. TERMINATION. Without prejudice to any other rights, Microsoft may terminate this agreement if you fail to comply with any of its terms or conditions. In such event, you must destroy all copies of the software and all of its component parts. -9. ENTIRE AGREEMENT. This agreement, and any other terms Microsoft may provide for supplements, updates, or third-party applications, is the entire agreement for the software. -10. APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES. If you acquired the software in the United States or Canada, the laws of the state or province where you live (or, if a business, where your principal place of business is located) govern the interpretation of this agreement, claims for its breach, and all other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of laws principles, except that the FAA governs everything related to arbitration. If you acquired the software in any other country, its laws apply, except that the FAA governs everything related to arbitration. If U.S. federal jurisdiction exists, you and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County, Washington for all disputes heard in court (excluding arbitration). If not, you and Microsoft consent to exclusive jurisdiction and venue in the Superior Court of King County, Washington for all disputes heard in court (excluding arbitration). -11. CONSUMER RIGHTS; REGIONAL VARIATIONS. This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state, province, or country. Separate and apart from your relationship with Microsoft, you may also have rights with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state, province, or country do not permit it to do so. For example, if you acquired the software in one of the below regions, or mandatory country law applies, then the following provisions apply to you: -a) Australia. You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights. -b) Canada. If you acquired this software in Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking for and installing updates), or uninstalling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software. -c) Germany and Austria. -i. Warranty. The properly licensed software will perform substantially as described in any Microsoft materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software. -ii. Limitation of Liability. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as well as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law. -Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in breach of such material contractual obligations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other cases of slight negligence, Microsoft will not be liable for slight negligence. -12. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS IS.” YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. -13. LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES. -This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) claims for breach of contract, warranty, guarantee, or condition; strict liability, negligence, or other tort; or any other claim; in each case to the extent permitted by applicable law. -It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your state, province, or country may not allow the exclusion or limitation of incidental, consequential, or other damages. - -Please note: As this software is distributed in Canada, some of the clauses in this agreement are provided below in French. -Remarque: Ce logiciel étant distribué au Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en français. -EXONÉRATION DE GARANTIE. Le logiciel visé par une licence est offert « tel quel ». Toute utilisation de ce logiciel est à votre seule risque et péril. Microsoft n’accorde aucune autre garantie expresse. Vous pouvez bénéficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualité marchande, d’adéquation à un usage particulier et d’absence de contrefaçon sont exclues. -LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES. Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune indemnisation pour les autres dommages, y compris les dommages spéciaux, indirects ou accessoires et pertes de bénéfices. -Cette limitation concerne: -• tout ce qui est relié au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers; et -• les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité stricte, de négligence ou d’une autre faute dans la limite autorisée par la loi en vigueur. -Elle s’applique également, même si Microsoft connaissait ou devrait connaître l’éventualité d’un tel dommage. Si votre pays n’autorise pas l’exclusion ou la limitation de responsabilité pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l’exclusion ci-dessus ne s’appliquera pas à votre égard. -EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d’autres droits prévus par les lois de votre pays. Le présent contrat ne modifie pas les droits que vous confèrent les lois de votre pays si celles-ci ne le permettent pas. - - - ---------------------------------------------------------- - ---------------------------------------------------------- - -Microsoft.NETCore.Platforms 1.1.1 - - -(c) Microsoft Corporation -Copyright (c) .NET Foundation and Contributors - ---------------------------------------------------------- - ---------------------------------------------------------- - -madler/zlib 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf - Zlib - - -Copyright (c) 2003 Mark Adler -Copyright (c) 2018 Mark Adler -(c) Copyright Henrik Ravn 2004 -Copyright (c) Henrik Ravn 2004 -Copyright 1995-2024 Mark Adler -Copyright (c) 2003 Cosmin Truta -Copyright (c) 1990-2000 Info-ZIP. -Copyright (c) 1998 by Bob Dellaca -Copyright (c) 2004 by Henrik Ravn -Copyright (c) 1995-2003 Mark Adler -Copyright (c) 1995-2008 Mark Adler -Copyright (c) 1995-2017 Mark Adler -Copyright (c) 1995-2019 Mark Adler -Copyright (c) 1995-2022 Mark Adler -Copyright (c) 1995-2024 Mark Adler -Copyright (c) 2002-2013 Mark Adler -Copyright (c) 2003 by Cosmin Truta -Copyright (c) 2003-2010 Mark Adler -Copyright (c) 2004-2017 Mark Adler -Copyright (c) 2004-2019 Mark Adler -Copyright (c) 2004-2023 Mark Adler -Copyright (c) 2004-2024 Mark Adler -Copyright (c) 1996 L. Peter Deutsch -Copyright (c) 1997,99 Borland Corp. -Copyright (c) 2003, 2012 Mark Adler -Copyright (c) 2004, 2010 Mark Adler -Copyright (c) 2011, 2016 Mark Adler -Copyright (c) 2007-2008 Even Rouault -Copyright (c) 1998-2005 Gilles Vollant -Copyright (c) 1995-1998 Jean-loup Gailly -Copyright (c) 1995-2003 Jean-loup Gailly -Copyright (c) 1995-2003, 2010 Mark Adler -Copyright (c) 1995-2005, 2010 Mark Adler -Copyright (c) 1995-2011, 2016 Mark Adler -Copyright (c) 1995-2017 Jean-loup Gailly -Copyright (c) 1995-2024 Jean-loup Gailly -Copyright (c) 1997,99 Borland Corporation -Copyright (c) 1998 by Andreas R. Kleinert -Copyright (c) 2002-2003 Dmitriy Anisimkov -Copyright (c) 2002-2004 Dmitriy Anisimkov -Copyright (c) 2003, 2012, 2013 Mark Adler -Copyright (c) 2004, 2005, 2012 Mark Adler -Copyright (c) 2004, 2008, 2012 Mark Adler -Copyright (c) 1998 by Jacques Nomssi Nzali -(c) 1995-2022 Jean-loup Gailly & Mark Adler -(c) 1995-2024 Jean-loup Gailly & Mark Adler -Copyright (c) 1995-2003 by Jean-loup Gailly -Copyright (c) 1998-2010 - by Gilles Vollant -(c) 1995-2017 Jean-loup Gailly and Mark Adler -(c) 1995-2022 Jean-loup Gailly and Mark Adler -(c) 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 2005, 2012, 2018, 2023 Mark Adler -Copyright (c) 2007, 2008, 2012, 2018 Mark Adler -Copyright 1995-2024 Jean-loup Gailly and Mark Adler -Copyright (c) 1995-2006, 2011, 2016 Jean-loup Gailly -Copyright (c) 1995-2017 Jean-Loup Gailly, Mark Adler -Copyright (c) 1995-2024 Jean-loup Gailly, Mark Adler -Copyright (c) 1998,1999,2000 by Jacques Nomssi Nzali -Copyright (c) 2003, 2005, 2008, 2010, 2012 Mark Adler -Copyright (c) 2004, 2008, 2012, 2016, 2019 Mark Adler -Copyright (c) 1995-2003 Jean-loup Gailly and Mark Adler -Copyright (c) 1995-2024 Jean-loup Gailly and Mark Adler -copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler -Copyright (c) 1996 L. Peter Deutsch and Jean-Loup Gailly -Copyright (c) 1995-2006, 2010, 2011, 2016 Jean-loup Gailly -Copyright (c) 2009-2010 Mathias Svensson http://result42.com -Copyright (c) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler -Copyright Jean-loup Gailly Osma Ahvenlampi -Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll -Copyright (c) 1997 Christian Michelsen Research AS Advanced Computing -Copyright (c) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler -Copyright (c) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson -Copyright (c) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant -Copyright (c) 1998-2010 Gilles Vollant (minizip) http://www.winimage.com/zLibDll/minizip.html - -Copyright notice: - - (C) 1995-2022 Jean-loup Gailly and Mark Adler - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jean-loup Gailly Mark Adler - jloup@gzip.org madler@alumni.caltech.edu - - ---------------------------------------------------------- - ---------------------------------------------------------- - -tristanpenman/valijson 0b4771e273a065d437814baf426bcfcafec0f434 - BSD-2-Clause - - -Copyright (c) 2021 Tristan Penman -Copyright (c) 2016, Tristan Penman -Copyright (c) The Internet Society (2005) -Copyright (c) The Internet Society (2006) -Copyright (c) 2011 - 2012 Andrzej Krzemienski -Copyright (c) 2016, Akamai Technologies, Inc. -Copyright (c) 2016 Akamai Technologies Polymorphic -Copyright (c) 2010 IETF Trust and the persons identified as the document authors -Copyright (c) 2012 IETF Trust and the persons identified as the document authors -Copyright (c) 2013 IETF Trust and the persons identified as the document authors - -Copyright (c) 2016, Tristan Penman -Copyright (c) 2016, Akamai Technologies, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - ---------------------------------------------------------- - ---------------------------------------------------------- - -open-source-parsers/jsoncpp 9be589598595963f94ba264d7b416d0533421106 - MIT OR OTHER - - -Copyright (c) 2016 InfoTeCS JSC. -Copyright 2007-2010 The JsonCpp Authors -Copyright 2007-2019 The JsonCpp Authors -Copyright 2007 Baptiste Lepilleur and The JsonCpp Authors -Copyright 2009 Baptiste Lepilleur and The JsonCpp Authors -Copyright 2010 Baptiste Lepilleur and The JsonCpp Authors -Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors -Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors -Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors -Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors - -The JsonCpp library's source code, including accompanying documentation, -tests and demonstration applications, are licensed under the following -conditions... - -Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all -jurisdictions which recognize such a disclaimer. In such jurisdictions, -this software is released into the Public Domain. - -In jurisdictions which do not recognize Public Domain property (e.g. Germany as of -2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and -The JsonCpp Authors, and is released under the terms of the MIT License (see below). - -In jurisdictions which recognize Public Domain property, the user of this -software may choose to accept it either as 1) Public Domain, 2) under the -conditions of the MIT License (see below), or 3) under the terms of dual -Public Domain/MIT License conditions described here, as they choose. - -The MIT License is about as close to Public Domain as a license can get, and is -described in clear, concise terms at: - - http://en.wikipedia.org/wiki/MIT_License - -The full text of the MIT License follows: - -======================================================================== -Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors - -Permission is hereby granted, free of charge, to any person -obtaining a copy of this software and associated documentation -files (the "Software"), to deal in the Software without -restriction, including without limitation the rights to use, copy, -modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS -BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -======================================================================== -(END LICENSE TEXT) - -The MIT license is compatible with both the GPL and commercial -software, affording one all of the rights of Public Domain with the -minor nuisance of being required to keep the above copyright notice -and license text in the source code. Note also that by accepting the -Public Domain "license" you can re-license your copy using whatever -license you like. - - ---------------------------------------------------------- - +NOTICES AND INFORMATION +Do Not Translate or Localize + +This software incorporates material from third parties. +Microsoft makes certain open source code available at https://3rdpartysource.microsoft.com, +or you may send a check or money order for US $5.00, including the product name, +the open source component name, platform, and version number, to: + +Source Code Compliance Team +Microsoft Corporation +One Microsoft Way +Redmond, WA 98052 +USA + +Notwithstanding any other terms, you may reverse engineer this software to the extent +required to debug changes to any libraries licensed under the GNU Lesser General Public License. + +--------------------------------------------------------- + +Castle.Core 5.1.0 - Apache-2.0 + + +(c) 2004-2022 Castle Project - http://www.castleproject.org +Copyright 2004-2021 Castle Project - http://www.castleproject.org +Copyright (c) 2004-2022 Castle Project - http://www.castleproject.org + +Copyright 2004-2021 Castle Project - http://www.castleproject.org/ + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +NuGet.Frameworks 5.11.0 - Apache-2.0 + + +(c) Microsoft Corporation. + +Apache License + +Version 2.0, January 2004 + +http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + + + "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + + + + "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + + + + "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + + + + "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + + + + "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + + + + "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + + + + "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + + + + "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + + + + "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + + + + "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + + You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +Copyright [yyyy] [name of copyright owner] + +Licensed under the Apache License, Version 2.0 (the "License"); + +you may not use this file except in compliance with the License. + +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software + +distributed under the License is distributed on an "AS IS" BASIS, + +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + +See the License for the specific language governing permissions and + +limitations under the License. + +--------------------------------------------------------- + +--------------------------------------------------------- + +openssl/openssl 01d5e2318405362b4de5e670c90d9b40a351d053 - Apache-2.0 + + +(c) 2005 WISeKey SA1 +Copyright 2005 Nokia +Copyright 2021 UnionTech +Copyright 2021- IBM Inc. +Copyright IBM Corp. 2018 +Copyright IBM Corp. 2019 +Copyright Nokia 2007-2018 +Copyright Nokia 2007-2019 +Copyright Nokia 2007-2020 +Copyright Siemens AG 2020 +Copyright 2011 Google Inc. +Copyright 2017 Ribose Inc. +(c) 2013 ATT Wi-Fi Services +Copyright 2017 BaishanCloud +Copyright 2013 M. J. Dominus +Copyright 2019 Red Hat, Inc. +Copyright IBM Corp. 2018-2019 +Copyright Patrick Powell 1995 +Copyright (c) 2011, RTFM, Inc. +Copyright Siemens AG 2015-2019 +Copyright Siemens AG 2015-2020 +Copyright Siemens AG 2015-2022 +Copyright Siemens AG 2018-2020 +Copyright Siemens AG 2019-2022 +Copyright 2016 VMS Software, Inc. +Copyright (c) 2004, EdelKey Project +Copyright (c) 2015 CloudFlare, Inc. +Copyright (c) 2015, CloudFlare, Inc. +Copyright (c) 2005 WISeKey SA1 InterU +Copyright (c) 2012, Intel Corporation +Copyright (c) 2014, Intel Corporation +Copyright (c) 2020, Intel Corporation +Copyright (c) 2021, Intel Corporation +Copyright (c) 2002 The OpenTSA Project +Copyright 1998-$YEAR The OpenSSL Authors +Copyright 2004-2014, Akamai Technologies +Copyright 2023The OpenSSL Project Authors +Copyright (c) 2020-2021, Intel Corporation +Copyright 2014 Cryptography Research, Inc. +Copyright 2015 Cryptography Research, Inc. +Copyright 2016 Cryptography Research, Inc. +Copyright 2016 The OpenSSL Project Authors +Copyright 2017 The OpenSSL Project Authors +Copyright 2018 The OpenSSL Project Authors +Copyright 2019 The OpenSSL Project Authors +Copyright 2020 The OpenSSL Project Authors +Copyright 2021 The OpenSSL Project Authors +Copyright 2022 The OpenSSL Project Authors +Copyright 2023 The OpenSSL Project Authors +Copyright (c) 1998-2023 The OpenSSL Project +Copyright (c) 2006, Network Resonance, Inc. +Copyright (c) 2012-2014 Daniel J. Bernstein +copyrighted by the Free Software Foundation +Copyright (c) 2012-2016 Jean-Philippe Aumasson +Copyright 1995-2016 The OpenSSL Project Authors +Copyright 1995-2017 The OpenSSL Project Authors +Copyright 1995-2018 The OpenSSL Project Authors +Copyright 1995-2019 The OpenSSL Project Authors +Copyright 1995-2020 The OpenSSL Project Authors +Copyright 1995-2021 The OpenSSL Project Authors +Copyright 1995-2022 The OpenSSL Project Authors +Copyright 1995-2023 The OpenSSL Project Authors +Copyright 1998-2016 The OpenSSL Project Authors +Copyright 1998-2017 The OpenSSL Project Authors +Copyright 1998-2020 The OpenSSL Project Authors +Copyright 1998-2021 The OpenSSL Project Authors +Copyright 1998-2022 The OpenSSL Project Authors +Copyright 1998-2023 The OpenSSL Project Authors +Copyright 1999-2016 The OpenSSL Project Authors +Copyright 1999-2018 The OpenSSL Project Authors +Copyright 1999-2020 The OpenSSL Project Authors +Copyright 1999-2021 The OpenSSL Project Authors +Copyright 1999-2022 The OpenSSL Project Authors +Copyright 1999-2023 The OpenSSL Project Authors +Copyright 2000-2016 The OpenSSL Project Authors +Copyright 2000-2017 The OpenSSL Project Authors +Copyright 2000-2018 The OpenSSL Project Authors +Copyright 2000-2019 The OpenSSL Project Authors +Copyright 2000-2020 The OpenSSL Project Authors +Copyright 2000-2021 The OpenSSL Project Authors +Copyright 2000-2022 The OpenSSL Project Authors +Copyright 2000-2023 The OpenSSL Project Authors +Copyright 2001-2016 The OpenSSL Project Authors +Copyright 2001-2017 The OpenSSL Project Authors +Copyright 2001-2018 The OpenSSL Project Authors +Copyright 2001-2020 The OpenSSL Project Authors +Copyright 2001-2021 The OpenSSL Project Authors +Copyright 2001-2022 The OpenSSL Project Authors +Copyright 2001-2023 The OpenSSL Project Authors +Copyright 2002-2016 The OpenSSL Project Authors +Copyright 2002-2018 The OpenSSL Project Authors +Copyright 2002-2020 The OpenSSL Project Authors +Copyright 2002-2021 The OpenSSL Project Authors +Copyright 2002-2022 The OpenSSL Project Authors +Copyright 2002-2023 The OpenSSL Project Authors +Copyright 2003-2021 The OpenSSL Project Authors +Copyright 2003-2022 The OpenSSL Project Authors +Copyright 2003-2023 The OpenSSL Project Authors +Copyright 2004-2016 The OpenSSL Project Authors +Copyright 2004-2017 The OpenSSL Project Authors +Copyright 2004-2018 The OpenSSL Project Authors +Copyright 2004-2020 The OpenSSL Project Authors +Copyright 2004-2021 The OpenSSL Project Authors +Copyright 2004-2022 The OpenSSL Project Authors +Copyright 2004-2023 The OpenSSL Project Authors +Copyright 2005-2016 The OpenSSL Project Authors +Copyright 2005-2018 The OpenSSL Project Authors +Copyright 2005-2020 The OpenSSL Project Authors +Copyright 2005-2021 The OpenSSL Project Authors +Copyright 2005-2022 The OpenSSL Project Authors +Copyright 2005-2023 The OpenSSL Project Authors +Copyright 2006-2016 The OpenSSL Project Authors +Copyright 2006-2017 The OpenSSL Project Authors +Copyright 2006-2018 The OpenSSL Project Authors +Copyright 2006-2020 The OpenSSL Project Authors +Copyright 2006-2021 The OpenSSL Project Authors +Copyright 2006-2022 The OpenSSL Project Authors +Copyright 2006-2023 The OpenSSL Project Authors +Copyright 2007-2016 The OpenSSL Project Authors +Copyright 2007-2018 The OpenSSL Project Authors +Copyright 2007-2019 The OpenSSL Project Authors +Copyright 2007-2020 The OpenSSL Project Authors +Copyright 2007-2021 The OpenSSL Project Authors +Copyright 2007-2022 The OpenSSL Project Authors +Copyright 2007-2023 The OpenSSL Project Authors +Copyright 2008-2016 The OpenSSL Project Authors +Copyright 2008-2018 The OpenSSL Project Authors +Copyright 2008-2020 The OpenSSL Project Authors +Copyright 2008-2021 The OpenSSL Project Authors +Copyright 2008-2022 The OpenSSL Project Authors +Copyright 2008-2023 The OpenSSL Project Authors +Copyright 2009-2020 The OpenSSL Project Authors +Copyright 2009-2021 The OpenSSL Project Authors +Copyright 2009-2022 The OpenSSL Project Authors +Copyright 2009-2023 The OpenSSL Project Authors +Copyright 2010-2016 The OpenSSL Project Authors +Copyright 2010-2020 The OpenSSL Project Authors +Copyright 2010-2021 The OpenSSL Project Authors +Copyright 2010-2022 The OpenSSL Project Authors +Copyright 2010-2023 The OpenSSL Project Authors +Copyright 2011-2016 The OpenSSL Project Authors +Copyright 2011-2020 The OpenSSL Project Authors +Copyright 2011-2021 The OpenSSL Project Authors +Copyright 2011-2022 The OpenSSL Project Authors +Copyright 2011-2023 The OpenSSL Project Authors +Copyright 2012, Samuel Neves +Copyright 2012-2016 The OpenSSL Project Authors +Copyright 2012-2020 The OpenSSL Project Authors +Copyright 2012-2021 The OpenSSL Project Authors +Copyright 2012-2022 The OpenSSL Project Authors +Copyright 2012-2023 The OpenSSL Project Authors +Copyright 2013-2017 The OpenSSL Project Authors +Copyright 2013-2018 The OpenSSL Project Authors +Copyright 2013-2020 The OpenSSL Project Authors +Copyright 2013-2021 The OpenSSL Project Authors +Copyright 2013-2022 The OpenSSL Project Authors +Copyright 2013-2023 The OpenSSL Project Authors +Copyright 2014-2016 Cryptography Research, Inc. +Copyright 2014-2016 The OpenSSL Project Authors +Copyright 2014-2017 The OpenSSL Project Authors +Copyright 2014-2018 The OpenSSL Project Authors +Copyright 2014-2020 The OpenSSL Project Authors +Copyright 2014-2021 The OpenSSL Project Authors +Copyright 2014-2022 The OpenSSL Project Authors +Copyright 2014-2023 The OpenSSL Project Authors +Copyright 2015-2016 Cryptography Research, Inc. +Copyright 2015-2016 The OpenSSL Project Authors +Copyright 2015-2017 The OpenSSL Project Authors +Copyright 2015-2018 The OpenSSL Project Authors +Copyright 2015-2020 The OpenSSL Project Authors +Copyright 2015-2021 The OpenSSL Project Authors +Copyright 2015-2022 The OpenSSL Project Authors +Copyright 2015-2023 The OpenSSL Project Authors +Copyright 2016-2016 The OpenSSL Project Authors +Copyright 2016-2017 The OpenSSL Project Authors +Copyright 2016-2018 The OpenSSL Project Authors +Copyright 2016-2019 The OpenSSL Project Authors +Copyright 2016-2020 The OpenSSL Project Authors +Copyright 2016-2021 The OpenSSL Project Authors +Copyright 2016-2022 The OpenSSL Project Authors +Copyright 2016-2023 The OpenSSL Project Authors +Copyright 2017-2018 The OpenSSL Project Authors +Copyright 2017-2019 The OpenSSL Project Authors +Copyright 2017-2020 The OpenSSL Project Authors +Copyright 2017-2021 The OpenSSL Project Authors +Copyright 2017-2022 The OpenSSL Project Authors +Copyright 2017-2023 The OpenSSL Project Authors +Copyright 2018-2019 The OpenSSL Project Authors +Copyright 2018-2020 The OpenSSL Project Authors +Copyright 2018-2021 The OpenSSL Project Authors +Copyright 2018-2022 The OpenSSL Project Authors +Copyright 2018-2023 The OpenSSL Project Authors +Copyright 2019-2020 The OpenSSL Project Authors +Copyright 2019-2021 The OpenSSL Project Authors +Copyright 2019-2022 The OpenSSL Project Authors +Copyright 2019-2023 The OpenSSL Project Authors +Copyright 2020-2021 The OpenSSL Project Authors +Copyright 2020-2022 The OpenSSL Project Authors +Copyright 2020-2023 The OpenSSL Project Authors +Copyright 2021-2022 The OpenSSL Project Authors +Copyright 2021-2023 The OpenSSL Project Authors +Copyright 2022-2023 The OpenSSL Project Authors +Copyright 20xx-20yy The OpenSSL Project Authors +Copyright (c) 2002, Oracle and/or its affiliates +Copyright (c) 2017, Oracle and/or its affiliates +Copyright (c) 2018, Oracle and/or its affiliates +Copyright (c) 2019, Oracle and/or its affiliates +Copyright 1995-$YEAR The OpenSSL Project Authors +Copyright 1998-$YEAR The OpenSSL Project Authors +Copyright 1999-$YEAR The OpenSSL Project Authors +Copyright 2000-$YEAR The OpenSSL Project Authors +Copyright 2020-$YEAR The OpenSSL Project Authors +Copyright (c) 1989 Free Software Foundation, Inc. +Copyright 2017 Ribose Inc. (https://www.ribose.com) +Copyright (c) 1995-1998 Eric A. Young, Tim J. Hudson +Copyright (c) 2008 Andy Polyakov +Copyright 2021 UnionTech (https://www.uniontech.com) +Copyright (c) 2018-2019, Oracle and/or its affiliates +Copyright (c) 2018-2020, Oracle and/or its affiliates +Copyright (c) 2019-2020, Oracle and/or its affiliates +Copyright (c) 2013 by Mark Jason Dominus +Copyright (c) 2017 National Security Research Institute +copyright (c) 2013 by Mark Jason Dominus +Copyright (c) 2004, Richard Levitte +Copyright (c) 2013-2014 Timo Teras +Copyright (c) 2007 KISA(Korea Information Security Agency) +Copyright (c) 2004, 2018, Richard Levitte +Copyright (c) 2016 Viktor Dukhovni +Copyright 2006 NTT (Nippon Telegraph and Telephone Corporation) +Copyright (c) 2005 WISeKey SA1 Internat(onal1)0 WISeKey CertifyID Advanced G1 CA0 +Copyright (c) 2005 WISeKey SA1 International1 0 WISeKey CertifyID Advanced G1 CA0 +Copyright (c) 2004 Kungliga Tekniska Hogskolan (Royal Institute of Technology, Stockholm, Sweden) + + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + +--------------------------------------------------------- + +--------------------------------------------------------- + +StyleCop.Analyzers 1.1.118 - Apache-2.0 AND MIT + + +copyright company +PlaceholderCompany Copyright (c) +Copyright (c) 2015 Dennis Fischer +Copyright (c) 2017 Marcos Lopez C. +Copyright 2014 Giovanni Bassi and Elemar Jr +Copyright (c) Tunnel Vision Laboratories, LLC. +Copyright Tunnel Vision Laboratories, LLC 2015 +copyright tag should contain a non-empty company +Copyright 2015 Tunnel Vision Laboratories, LLC StyleCop DotNetAnalyzers Roslyn Diagnostic + +Copyright (c) Tunnel Vision Laboratories, LLC. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +these files except in compliance with the License. You may obtain a copy of the +License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software distributed +under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. + +--- + +This project uses other open source projects, which are used under the terms +of the following license(s). + +.NET Compiler Platform ("Roslyn") + + Copyright Microsoft. + + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + these files except in compliance with the License. You may obtain a copy of the + License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for the + specific language governing permissions and limitations under the License. + +Code Cracker + + Copyright 2014 Giovanni Bassi and Elemar Jr. + + Licensed under the Apache License, Version 2.0 (the "License"); you may not use + these files except in compliance with the License. You may obtain a copy of the + License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software distributed + under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR + CONDITIONS OF ANY KIND, either express or implied. See the License for the + specific language governing permissions and limitations under the License. + +LightJson + + Copyright (c) 2017 Marcos López C. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Markdig.Signed 0.33.0 - BSD-2-Clause + + + +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Web.WebView2 1.0.3485.44 - BSD-3-Clause + + +(c) MEE. +(c) (c) S +(c) Gyw A +(c) Ono Q +(c) 3Y Thaeh +(c) Dyma (c) +(c) Microsoft 2025 +(c) Microsoft Corporation +Copyright Sam Harwell 2011 +Copyright Microsoft Corporation +Copyright (c) Microsoft Corporation +Copyright (c) 2011 The ANTLR Project + +Copyright (C) Microsoft Corporation. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * The name of Microsoft Corporation, or the names of its contributors +may not be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Moq 4.18.2 - BSD-3-Clause + + +Copyright (c) 2007, Clarius Consulting, Manas Technology Solutions, InSTEDD, and Contributors + +Copyright (c) . All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +curl/curl 83bedbd730d62b83744cc26fa0433d3f6e2e4cd6 - BSD-3-Clause AND BSD-4-Clause-UC AND ISC AND curl + + +Copyright (C) David Shaw +Copyright (C) Howard Chu +Copyright (C) Max Dymond +(c) ! ISCNTRL (c) ISSPACE +Copyright (C) Jeroen Ooms +Copyright (C) Mark Gaiser +Copyright (c) Evgeny Grin +Daniel Stenberg, , et al. +Copyright (C) Dan Fandrich +Copyright (C) Dorian Craps +Copyright (C) Jan Venekamp +Copyright (c) Dan Fandrich +Copyright (C) Dmitry Karpov +Copyright (C) Jay Satiro, . +Copyright (C) John Malmberg +Copyright (C) Red Hat, Inc. +Copyright (c) John Malmberg +Copyright (c) Red Hat, Inc. +copyright When contributing +Copyright (C) Björn Stenberg +Copyright (C) Michael Forney +Copyright (C) Steve Holme, . +Copyright (C) Viktor Szakats +Copyright (c) Viktor Szakats +Copyright (C) Daniel Stenberg +Copyright (c) Daniel Fandrich +Copyright (c) Daniel Stenberg +Copyright (C) Nicolas Sterchele +Copyright (C) " CURL_COPYRIGHT "\0" +Copyright (C) Jacob Hoffman-Andrews +Copyright (C) 2006-2022 wolfSSL Inc. +Copyright (C) James Fuller, , et al. +Copyright (C) Linus Nielsen Feltzing +Copyright (c) 2001 Alexander Peslyak +Copyright (c) 2006-2022 wolfSSL Inc. +Copyright (C) Daniel Fandrich, et al. +Copyright (C) Vijay Panghal, , et al. +Copyright (C) " LIBCURL_COPYRIGHT "\0" +Copyright (C) Daniel Fandrich, , et al. +Copyright (C) Daniel Stenberg, , et al. +Copyright (C) Simon Josefsson, , et al. +Copyright (C) Evgeny Grin (Karlson2k), . +2022 Free Software Foundation Europe e.V. +Copyright (c) Internet Software Consortium. +Copyright Daniel Stenberg, +Copyright (c) Howard Chu, +Copyright (c) Mandy Wu, +Copyright (C) Bill Nagel , Exacq Technologies +Copyright (c) Bjorn Stenberg, +Copyright (c) Jan Venekamp, +Copyright (c) Mark Gaiser, +Copyright (c) Daniel Stenberg, +Copyright (c) Howard Chu, +Copyright (c) Jay Satiro, +Copyright (c) David Shaw +Copyright (c) Jeroen Ooms +Copyright (c) Hoi-Ho Chan, +Copyright (c) Nick Zitzmann, +Copyright (C) EdelWeb for EdelKey and OpenEvidence +Copyright (c) EdelWeb for EdelKey and OpenEvidence +Copyright (c) James Fuller, +Copyright (c) Dmitry Karpov +Copyright (c) Michael Forney, +Copyright (c) 1996-2022 Internet Software Consortium +Copyright (c) 1998, 1999 Kungliga Tekniska Hogskolan +Copyright (c) Linus Nielsen Feltzing +Copyright (c) Marc Hoersken, +Copyright (c) Max Dymond, +Copyright (c) Simon Josefsson, +Copyright (c) Steve Holme, +Copyright (C) 1996-2022 Internet Software Consortium. +Copyright (c) Linus Nielsen Feltzing, +Copyright (c) Mark Salisbury, +Copyright (c) Vijay Panghal, +Copyright (c) 2001-2004 Damien Miller +Copyright (c) Daniel Fandrich, +Copyright (c) Florin Petriuc, +Copyright (c) Nicolas Sterchele, +Copyright (c) 1983, Regents of the University of California +Copyright (c) Dorian Craps, +Copyright (c) Markus Moeller, +Copyright (c) Jacob Hoffman-Andrews, +Copyright (c) Bill Nagel , Exacq Technologies +Copyright various years The Regents of the University of California +Copyright (C) Daniel Stenberg et al. OS/400 version by P. Monnerat")' +Copyright 2022 Free Software Foundation Europe e.V. +Copyright (c) Daniel Stenberg, , and many contributors +Copyright (c) 1996 - 2024, Daniel Stenberg, , and many contributors +Copyright (c) 1995, 1996, 1997, 1998, 1999 Kungliga Tekniska Hogskolan (Royal Institute of Technology, Stockholm, Sweden) + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1996 - 2024, Daniel Stenberg, , and many +contributors, see the THANKS file. + +All rights reserved. + +Permission to use, copy, modify, and distribute this software for any purpose +with or without fee is hereby granted, provided that the above copyright +notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE +OR OTHER DEALINGS IN THE SOFTWARE. + +Except as contained in this notice, the name of a copyright holder shall not +be used in advertising or otherwise to promote the sale, use or other dealings +in this Software without prior written authorization of the copyright holder. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +c-ares/c-ares fddf01938d3789e06cc1c3774e4cd0c7d2a89976 - HPND + + +Copyright 2008 Google Inc. +Copyright 2005, Google Inc. +Copyright 2006, Google Inc. +Copyright 2007, Google Inc. +Copyright 2008, Google Inc. +Copyright 2013, Google Inc. +Copyright 2015, Google Inc. +Copyright (c) 2012 Xan Lopez +Copyright (c) 2021 Permission +Copyright (c) 2012 Dan Winship +Copyright 2005 Dominick Meglio +Copyright (c) 2012 Paolo Borelli +Copyright (c) 2021 by Brad House +Copyright 1998 by Daniel Stenberg +Copyright 2004 by Daniel Stenberg +Copyright 2005 by Dominick Meglio +Copyright (c) 2012 Christian Persch +Copyright (c) 2017 by John Schember +Copyright (c) 2014, 2015 Google Inc. +Copyright (c) 2004 by Daniel Stenberg +Copyright (c) 2005 by Dominick Meglio +Copyright (c) 2008 by Daniel Stenberg +Copyright (c) 2013 by Daniel Stenberg +Copyright (c) 2016 by Daniel Stenberg +Copyright (c) 2019 by Andrew Selivanov +Copyright (c) 2012, 2016 Philip Withnall +Copyright (c) 2015,2018 Bastien ROUCARIES +Copyright (c) 2004-2009 by Daniel Stenberg +Copyright (c) 2004-2010 by Daniel Stenberg +Copyright (c) 2004-2011 by Daniel Stenberg +Copyright (c) 2004-2017 by Daniel Stenberg +Copyright (c) 2005 - 2010, Daniel Stenberg +Copyright (c) 2005-2013 by Daniel Stenberg +Copyright (c) 2007 - 2018, Daniel Stenberg +Copyright (c) 2007-2013 by Daniel Stenberg +Copyright (c) 2008-2010 by Daniel Stenberg +Copyright (c) 2008-2013 by Daniel Stenberg +Copyright (c) 2009-2013 by Daniel Stenberg +Copyright (c) 2009-2021 by Daniel Stenberg +Copyright (c) 2010-2012 by Daniel Stenberg +Copyright (c) 2010-2013 by Daniel Stenberg +Copyright (c) 2005, 2013 by Dominick Meglio +Copyright (c) 2004 - 2011 by Daniel Stenberg +Copyright (c) 2004 - 2012 by Daniel Stenberg +Copyright (c) 2004 - 2013 by Daniel Stenberg +Copyright (c) 2008 - 2009 by Daniel Stenberg +Copyright (c) 2008 - 2012 by Daniel Stenberg +Copyright (c) 2008 - 2013 by Daniel Stenberg +Copyright (c) 2009 - 2021 by Daniel Stenberg +Copyright (c) 2017 - 2018 by Christian Ammer +Copyright (c) 2010 Jeremy Lal +Copyright (c) 2012 Marko Kreen +Copyright (c) 2012 Zack Weinberg +Copyright (c) 2018 The Android Open Source Project +Copyright 2020 by +Copyright (c) 2011 Daniel Stenberg +Copyright (c) 2013 Daniel Stenberg +Copyright (c) 2008 Benjamin Kosnik +Copyright (c) 1995, 1996, 1997, and 1998 WIDE Project +Copyright (c) 2014 Mike Frysinger +Copyright (c) 2008 Tom Howard +Copyright (c) 2009 Tom Howard +Copyright 2010 by Ben Greear +Copyright 2020 Danny Sonnenschein +Copyright (c) 1996,1999 by Internet Software Consortium +Copyright (c) 1996-1999 by Internet Software Consortium +Copyright (c) 2004 by Internet Systems Consortium, Inc. +Copyright (c) 2009 by Jakub Hrozek +Copyright (c) 2011 Daniel Richard G. +Copyright (c) 2009 Allan Caffee +Copyright (c) 2013 Roy Stogner +Copyright (c) 2017 by John Schember +Copyright (c) 2018 by John Schember +Copyright (c) 2008 Steven G. Johnson +Copyright 1998 by the Massachusetts Institute of Technology +Copyright 2000 by the Massachusetts Institute of Technology +Copyright 1998, 2000 by the Massachusetts Institute of Technology +Copyright 1998, 2011 by the Massachusetts Institute of Technology +Copyright (c) 1987-2001 The Regents of the University of California +Copyright (c) 2008 John Darrington +Copyright (c) 2015 Enrico M. Crisostomo +Copyright 1998, 2011, 2013 by the Massachusetts Institute of Technology + +# c-ares license + +Copyright (c) 2007 - 2018, Daniel Stenberg with many contributors, see AUTHORS +file. + +Copyright 1998 by the Massachusetts Institute of Technology. + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose and without fee is hereby granted, provided that +the above copyright notice appear in all copies and that both that copyright +notice and this permission notice appear in supporting documentation, and that +the name of M.I.T. not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior permission. +M.I.T. makes no representations about the suitability of this software for any +purpose. It is provided "as is" without express or implied warranty. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +microsoft/xlang cfe510d0d2b07484fea2c6d77163de017738c100 - LicenseRef-scancode-generic-cla AND MIT + + +(c) Microsoft Corporation +Copyright 2017 Two Blue Cubes Ltd. +Copyright (c) Microsoft Corporation +Copyright (c) 2019 Two Blue Cubes Ltd. + + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.WDK.Win32Metadata 0.12.8-experimental - LicenseRef-scancode-ms-windows-sdk-win10-net-6 + + +(c) Microsoft 2024 +(c) Microsoft Corporation + +LicenseRef-scancode-ms-windows-sdk-win10-net-6 + +--------------------------------------------------------- + +--------------------------------------------------------- + +coverlet.collector 3.1.2 - MIT + + +Copyright 2008 - 2018 Jb Evain +Copyright Microsoft Corporation +Copyright James Newton-King 2008 + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Humanizer.Core 2.14.1 - MIT + + +Copyright .NET Foundation and Contributors +Copyright (c) .NET Foundation and Contributors + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Json.More.Net 2.0.1.2 - MIT + + +Copyright (c) 2024 Greg Dennis + +MIT License + +Copyright (c) 2024 Greg Dennis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +JsonPointer.Net 5.0.0 - MIT + + +Copyright (c) 2024 Greg Dennis + +MIT License + +Copyright (c) 2024 Greg Dennis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +JsonSchema.Net 7.0.4 - MIT + + + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.ApplicationInsights 2.21.0 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Bcl.AsyncInterfaces 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.CodeAnalysis.Analyzers 3.3.4 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation +Copyright (c) 2013 Scott Kirkland +Copyright (c) 2012-2014 Mehdi Khalili +Copyright (c) 2013-2014 Omar Khudeira (http://omar.io) + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.CodeAnalysis.Common 4.9.2 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.CodeAnalysis.CSharp 4.9.2 - MIT + + +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) Microsoft Corporation. Alle Rechte + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.AI.Abstractions 9.7.1 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.Abstractions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.Binder 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.CommandLine 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.EnvironmentVariables 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.FileExtensions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.Json 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Configuration.UserSecrets 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.DependencyInjection 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.DependencyInjection.Abstractions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Diagnostics 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Diagnostics.Abstractions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.FileProviders.Abstractions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.FileProviders.Physical 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.FileSystemGlobbing 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Hosting 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Hosting.Abstractions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.Abstractions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.Configuration 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.Console 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.Debug 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.EventLog 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Logging.EventSource 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.ObjectPool 8.0.10 - MIT + + +Copyright Jorn Zaefferer +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright (c) 2015, Google Inc. +Copyright (c) 2019 David Fowler +Copyright (c) HTML5 Boilerplate +Copyright 2019 The gRPC Authors +Copyright (c) 2016 Richard Morris +Copyright (c) 1998 John D. Polstra +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2013 - 2018 AngleSharp +Copyright (c) 2000-2013 Julian Seward +Copyright (c) 2011-2021 Twitter, Inc. +Copyright (c) 2014-2018 Michael Daines +Copyright (c) 1996-1998 John D. Polstra +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) .NET Foundation Contributors +Copyright (c) 2011-2021 The Bootstrap Authors +Copyright (c) 2019-2023 The Bootstrap Authors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2019-2020 West Wind Technologies +Copyright (c) 2007 John Birrell (jb@freebsd.org) +Copyright (c) 2011 Alex MacCaw (info@eribium.org) +Copyright (c) Nicolas Gallagher and Jonathan Neal +Copyright (c) 2010-2019 Google LLC. http://angular.io/license +Copyright (c) 2011 Nicolas Gallagher (nicolas@nicolasgallagher.com) +Copyright (c) 1989, 1993 The Regents of the University of California +Copyright (c) 1990, 1993 The Regents of the University of California +Copyright OpenJS Foundation and other contributors, https://openjsf.org +Copyright (c) Sindre Sorhus (https://sindresorhus.com) + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Options 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Options.ConfigurationExtensions 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Extensions.Primitives 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Management.Infrastructure 3.0.0 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Management.Infrastructure.CimCmdlets 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. CPowerShell's Microsoft.Management.Infrastructure.CimCmdlets project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Management.Infrastructure.Runtime.Unix 3.0.0 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Msix.Utils 2.1.1 - MIT + + + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.NET.ILLink.Tasks 8.0.20 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright 2008 - 2018 Jb Evain +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.NETCore.Platforms 5.0.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) Andrew Arnott +Copyright 2018 Daniel Lemire +Copyright 2012 the V8 project +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +Copyright (c) 1998 Microsoft. To +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2018 Alexander Chermyanin +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) The Internet Society 1997. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) The Internet Society (2003). +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.Commands.Diagnostics 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell's Microsoft.PowerShell.Commands.Diagnostics project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.Commands.Management 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell's Microsoft.PowerShell.Commands.Management project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.Commands.Utility 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. :PowerShell's Microsoft.PowerShell.Commands.Utility project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.ConsoleHost 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell Host + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.CoreCLR.Eventing 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. :PowerShell's Microsoft.PowerShell.CoreCLR.Eventing project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.MarkdownRender 7.2.1 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell's Markdown Rendering project PowerShell Markdown Renderer + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.Native 7.4.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) by P.J. Plauger + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.SDK 7.4.6 - MIT + + +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell SDK +(c) Microsoft Corporation. PowerShell Host +(c) Microsoft Corporation. 1PowerShell's System.Management.Automation project +(c) Microsoft Corporation. :PowerShell's Microsoft.PowerShell.Commands.Utility project +(c) Microsoft Corporation. PowerShell's Microsoft.PowerShell.Commands.Management project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.PowerShell.Security 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. 2PowerShell's Microsoft.PowerShell.Security project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Security.Extensions 1.2.0 - MIT + + + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.Registry 4.7.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.Registry 5.0.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 1991-2020 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.Registry.AccessControl 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.SystemEvents 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Win32.SystemEvents 9.0.0-preview.6.24327.7 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.Compatibility 8.0.10 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.CppWinRT 2.0.210503.1 - MIT + + +(c) Microsoft Corporation. +Copyright (c) Microsoft Corporation. + + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.CppWinRT 2.0.240405.15 - MIT + + +(c) Microsoft 2024 +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation + + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.CppWinRT 2.0.250303.1 - MIT + + +(c) Microsoft 2025 +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation + + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.CsWin32 0.3.183 - MIT + + + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Windows.ImplementationLibrary 1.0.250325.1 - MIT + + +(c) Microsoft 2025 +Copyright (c) Microsoft +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation +Copyright (c) 2009-2014 by the contributors + + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.WindowsPackageManager.ComInterop 1.8.1911 - MIT + + +(c) Microsoft Corporation + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.WSMan.Management 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. PowerShell's Microsoft.WSMan.Management project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.WSMan.Runtime 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. ,PowerShell's Microsoft.WSMan.Runtime project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +microsoft/correlationvector-cpp cf38d2b44baaf352509ad9980786bc49554c32e4 - MIT + + +Copyright (c) Microsoft Corporation. + + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +--------------------------------------------------------- + +--------------------------------------------------------- + +microsoft/cpprestsdk 411a109150b270f23c8c97fa4ec9a0a4a98cdecf - MIT + + +Copyright (c) Microsoft +Copyright (c) 2014, Peter Thorson +Copyright (c) Microsoft Corporation +Copyright (c) 2011, Micael Hildenborg +Copyright (c) 2004-2008 Rene Nyffenegger +Copyright (c) 1999, 2002 Aladdin Enterprises +Portions Copyright (c) Microsoft Corporation +Copyright (c) 2006 Noel Llopis and Charles Nicholson +Copyright (c) 2008-2009 Bjoern Hoehrmann + +C++ REST SDK + +The MIT License (MIT) + +Copyright (c) Microsoft Corporation + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +microsoft/sfs-client ff315ecfa2ef2953d8a808e51e8a61a4e0759180 - MIT + + +Copyright (c) Microsoft Corporation +Copyright (c) 2013-2022 Niels Lohmann +Copyright (c) 2007 - 2023 Daniel Stenberg +Copyright (c) 1998 Massachusetts Institute of Technology +Copyright (c) 1996 - 2024, Daniel Stenberg, , and many contributors + + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE + + +--------------------------------------------------------- + +--------------------------------------------------------- + +ModelContextProtocol 0.3.0-preview.3 - MIT + + +(c) Anthropic and Contributors + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +ModelContextProtocol.Core 0.3.0-preview.3 - MIT + + +(c) Anthropic and Contributors + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +NETStandard.Library 2.0.0 - MIT + + +copyright Unmanaged32Bit Required32Bit +Copyright (c) .NET Foundation and Contributors + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +NETStandard.Library 2.0.3 - MIT + + +copyright Unmanaged32Bit Required32Bit +Copyright (c) .NET Foundation and Contributors + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Newtonsoft.Json 13.0.3 - MIT + + +Copyright James Newton-King 2008 +Copyright (c) 2007 James Newton-King +Copyright (c) James Newton-King 2008 +Copyright James Newton-King 2008 Json.NET + +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Newtonsoft.Json.Bson 1.0.1 - MIT + + +Copyright James Newton-King 2017 +Copyright (c) James Newton-King 2017 + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +nlohmann/json 9cca280a4d0ccf0c08f47a99aa71d1b0e52f8d03 - MIT + + +(c) 2016 +2012 Erik Edlund +2017 Georg Sauthoff +2018 Vitaliy Manushkin +2013-2022 Niels Lohmann +2013-2023 Niels Lohmann +2015-2017 Niels Lohmann +2016-2021 Evan Nemerson +2018 The Abseil Authors +2003-2022, LLVM Project. +2016-2021 Viktor Kirilov +(c) 2013-2022 Niels Lohmann +Copyright 2003-2022, LLVM Project +Copyright 2015-2017 Niels Lohmann +Copyright 2018 The Abseil Authors +Copyright (c) 2009 Florian Loitsch +Copyright 2016-2021 Viktor Kirilov +Copyright (c) 2013-2022 Niels Lohmann +Copyright (c) 2015-2017 Niels Lohmann +copyright (c) 2013-2022 Niels Lohmann +copyright (c) 2013-2023 Niels Lohmann +Copyright (c) 2016-2023 Viktor Kirilov +Copyright 2017 Georg Sauthoff +copyright Niels Lohmann +Copyright 2012 Erik Edlund +Copyright Copyright (c) 2013 - 2023 Niels Lohmann +Copyright 2018 Vitaliy Manushkin +Copyright 2016-2021 Evan Nemerson +Copyright (c) 2012, Erik Edlund +Copyright 2013-2022 Niels Lohmann +Copyright 2013-2023 Niels Lohmann +Copyright 2020 Hannes Domani +Copyright 2008-2009 Bjorn Hoehrmann +Copyright (c) 2013-2019 Niels Lohmann +Copyright (c) 2013-2022 Niels Lohmann +Copyright (c) 2013-2022 Niels Lohmann (https://nlohmann.me) +Copyright (c) 2020 Hannes Domani (https://github.com/ssbssa) +Copyright 2009 Florian Loitsch +Copyright (c) 2009 Florian Loitsch (https://florian.loitsch.com/) +Copyright (c) 2007 Free Software Foundation, Inc. +Copyright (c) 2008-2009 Bjorn Hoehrmann (http://bjoern.hoehrmann.de/) +Copyright (c) 2008-2009 Bjorn Hoehrmann (https://bjoern.hoehrmann.de/) +Copyright (c) 2008-2009 Bjoern Hoehrmann @sa http://bjoern.hoehrmann.de/utf-8/decoder/dfa + +MIT License + +Copyright (c) 2013-2022 Niels Lohmann + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +NUnit 3.12.0 - MIT + + +(c) Microsoft 2024 +copyright in NUnit Console +(c) 2019 Charlie Poole, Rob Prouse +Copyright (c) 2002-2014 Charlie Poole +Copyright (c) 2000-2002 Philip A. Craig +Copyright (c) 2019 Charlie Poole, Rob Prouse +Copyright (c) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov + +Copyright (c) 2019 Charlie Poole, Rob Prouse + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +--------------------------------------------------------- + +--------------------------------------------------------- + +NUnit3TestAdapter 3.15.1 - MIT + + +(c) Microsoft 2025 +Copyright 2008 - 2015 Jb Evain +Copyright 2008 - 2018 Jb Evain +(c) 2019 Charlie Poole, Rob Prouse +Copyright (c) 2019 Charlie Poole, Rob Prouse +Copyright 2011-2019 Charlie Poole, 2014-2019 Terje Sandstrom +Copyright (c) 2011-2019 Charlie Poole, 2014-2019 Terje Sandstrom + +Copyright (c) 2011-2019 Charlie Poole, 2014-2019 Terje Sandstrom + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Octokit 4.0.3 - MIT + + +Copyright GitHub 2017 +Copyright GitHub 2017 GitHub API Octokit + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +PowerShellStandard.Library 5.1.1 - MIT + + +(c) Microsoft Corporation. + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +ronomon/pure fd54913e65338e678440ae66b3b5022ab23b761b - MIT + + +Copyright (c) 2003 Mark Adler +Copyright (c) 2020 Ronomon CC +Copyright 1995-2017 Mark Adler +Copyright (c) 2003 Cosmin Truta +Copyright (c) 1990-2000 Info-ZIP. +Copyright (c) 1998 by Bob Dellaca +Copyright (c) 1995-2003 Mark Adler +Copyright (c) 1995-2008 Mark Adler +Copyright (c) 1995-2016 Mark Adler +Copyright (c) 1995-2017 Mark Adler +Copyright (c) 2002-2013 Mark Adler +Copyright (c) 2003 by Cosmin Truta +Copyright (c) 2003-2010 Mark Adler +Copyright (c) 2004-2017 Mark Adler +Copyright (c) 1996 L. Peter Deutsch +Copyright (c) 1997,99 Borland Corp. +Copyright (c) 2003, 2012 Mark Adler +Copyright (c) 2004, 2005 Mark Adler +Copyright (c) 2004, 2010 Mark Adler +Copyright (c) 2005, 2012 Mark Adler +Copyright (c) 2011, 2016 Mark Adler +Copyright (c) 2007-2008 Even Rouault +Copyright (c) 1998-2005 Gilles Vollant +Copyright (c) 2004, 2005 by Mark Adler +Copyright (c) 1995-1998 Jean-loup Gailly +Copyright (c) 1995-2003 Jean-loup Gailly +Copyright (c) 1995-2003, 2010 Mark Adler +Copyright (c) 1995-2005, 2010 Mark Adler +Copyright (c) 1995-2011, 2016 Mark Adler +Copyright (c) 1995-2016 Jean-loup Gailly +Copyright (c) 1995-2017 Jean-loup Gailly +Copyright (c) 1997,99 Borland Corporation +Copyright (c) 1998 by Andreas R. Kleinert +Copyright (c) 2002-2003 Dmitriy Anisimkov +Copyright (c) 2002-2004 Dmitriy Anisimkov +Copyright (c) 2003, 2012, 2013 Mark Adler +Copyright (c) 2004, 2005, 2012 Mark Adler +Copyright (c) 2004, 2008, 2012 Mark Adler +Copyright (c) 2007, 2008, 2012 Mark Adler +Copyright (c) 1998 by Jacques Nomssi Nzali +(c) 1995-2017 Jean-loup Gailly & Mark Adler +Copyright (c) 1995-2003 by Jean-loup Gailly +Copyright (c) 1998-2010 - by Gilles Vollant +(c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2004, 2008, 2012, 2016 Mark Adler +Copyright 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 1995-2006, 2011, 2016 Jean-loup Gailly +Copyright (c) 1995-2016 Jean-loup Gailly, Mark Adler +Copyright (c) 1995-2017 Jean-Loup Gailly, Mark Adler +Copyright (c) 1995-2017 Jean-loup Gailly, Mark Adler +Copyright (c) 1998,1999,2000 by Jacques Nomssi Nzali +Copyright (c) 2003, 2005, 2008, 2010, 2012 Mark Adler +Copyright (c) 2003 Chris Anderson +Copyright (c) 1995-2003 Jean-loup Gailly and Mark Adler +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 1996 L. Peter Deutsch and Jean-Loup Gailly +Copyright (c) 1998 Brian Raiter +Copyright (c) 1995-2006, 2010, 2011, 2012, 2016 Mark Adler +Copyright (c) 1995-2006, 2010, 2011, 2016 Jean-loup Gailly +Copyright (c) 2009-2010 Mathias Svensson http://result42.com +Copyright (c) 1998, 2007 Brian Raiter +Copyright (c) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler +Copyright (c) 2004, 2005, 2010, 2011, 2012, 2013, 2016 Mark Adler +Copyright Jean-loup Gailly Osma Ahvenlampi +Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll +Copyright (c) 1997 Christian Michelsen Research AS Advanced Computing +Copyright (c) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler +Copyright (c) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson +Copyright (c) 1995-1996 Jean-loup Gailly, Brian Raiter and Gilles Vollant +Copyright (c) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant +Copyright (c) 1998-2010 Gilles Vollant (minizip) http://www.winimage.com/zLibDll/minizip.html + +The MIT License (MIT) + +Copyright (c) 2020 Ronomon CC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-arm.runtime.native.System.IO.Ports 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-arm64.runtime.native.System.IO.Ports 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.linux-x64.runtime.native.System.IO.Ports 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.native.System.Data.SqlClient.sni 4.7.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.native.System.IO.Ports 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.osx-arm64.runtime.native.System.IO.Ports 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +runtime.osx-x64.runtime.native.System.IO.Ports 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Semver 2.3.0 - MIT + + +Copyright 2013 Max Hauser, Jeff Walker +Copyright (c) 2013 Max Hauser, Jeff Walker +Copyright 2013 Max Hauser, Jeff Walker VA SemVer + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Buffers 4.5.1 - MIT + + +(c) Microsoft Corporation +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2015 The Chromium Authors +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.CodeDom 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Collections.Immutable 8.0.0 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ComponentModel.Composition 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ComponentModel.Composition.Registration 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Configuration.ConfigurationManager 8.0.1 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Data.Odbc 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Data.OleDb 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Data.SqlClient 4.8.6 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.DiagnosticSource 5.0.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) Andrew Arnott +Copyright 2018 Daniel Lemire +Copyright 2012 the V8 project +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +Copyright (c) 1998 Microsoft. To +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2018 Alexander Chermyanin +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) The Internet Society 1997. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) The Internet Society (2003). +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.DiagnosticSource 8.0.1 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.DiagnosticSource 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.EventLog 6.0.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) Andrew Arnott +Copyright 2018 Daniel Lemire +Copyright 2012 the V8 project +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +Copyright (c) 1998 Microsoft. To +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2018 Alexander Chermyanin +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) The Internet Society 1997. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) The Internet Society (2003). +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.EventLog 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.EventLog 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Diagnostics.PerformanceCounter 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.DirectoryServices 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.DirectoryServices.AccountManagement 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.DirectoryServices.Protocols 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Drawing.Common 8.0.19 - MIT + + +(c) Microsoft Corporation +Copyright (c) Sven Groot (Ookii.org) 2009 +Copyright (c) .NET Foundation and Contributors + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Drawing.Common 9.0.0-preview.6.24327.6 - MIT + + +(c) Microsoft Corporation +Copyright (c) Sven Groot (Ookii.org) 2009 +Copyright (c) .NET Foundation and Contributors + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Formats.Asn1 8.0.1 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.IO.Packaging 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.IO.Pipelines 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.IO.Ports 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Management 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Management.Automation 7.4.6 - MIT + + +(c) Microsoft Corporation +(c) Microsoft Corporation. 1PowerShell's System.Management.Automation project + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Memory 4.5.4 - MIT + + +(c) Microsoft Corporation +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2015 The Chromium Authors +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Net.Http.WinHttpHandler 8.0.2 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Net.ServerSentEvents 10.0.0-preview.4.25258.110 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2024 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Numerics.Vectors 4.4.0 - MIT + + +(c) Microsoft Corporation. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 1991-2017 Unicode, Inc. +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Numerics.Vectors 4.5.0 - MIT + + +(c) 2023 GitHub, Inc. +(c) Microsoft Corporation +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2015 The Chromium Authors +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Private.ServiceModel 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Reflection.Context 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Reflection.DispatchProxy 4.7.1 - MIT + + +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Reflection.Metadata 1.6.0 - MIT + + +(c) 2023 GitHub, Inc. +(c) Microsoft Corporation +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2015 The Chromium Authors +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Reflection.Metadata 8.0.0 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +Gets the Copyright Table +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.Caching 8.0.1 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.CompilerServices.Unsafe 4.5.3 - MIT + + +(c) Microsoft Corporation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 1991-2017 Unicode, Inc. +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.CompilerServices.Unsafe 6.0.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2005-2020 Rich Felker +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 1991-2020 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.WindowsRuntime 4.6.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Runtime.WindowsRuntime.UI.Xaml 4.6.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2017 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.AccessControl 5.0.0 - MIT + + +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 1991-2020 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.AccessControl 6.0.1 - MIT + + +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2005-2020 Rich Felker +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 1991-2020 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Cryptography.Pkcs 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Cryptography.ProtectedData 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Cryptography.Xml 8.0.2 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Permissions 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Security.Principal.Windows 5.0.0 - MIT + + +(c) Microsoft Corporation. +Copyright (c) Andrew Arnott +Copyright 2018 Daniel Lemire +Copyright 2012 the V8 project +Copyright (c) .NET Foundation. +Copyright (c) 2011, Google Inc. +Copyright (c) 1998 Microsoft. To +(c) 1997-2005 Sean Eron Anderson. +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 2012-2014, Yann Collet +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2018 Alexander Chermyanin +Portions (c) International Organization +Copyright (c) 2015 The Chromium Authors. +Copyright (c) The Internet Society 1997. +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) .NET Foundation Contributors +Copyright (c) The Internet Society (2003). +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors. +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers THIS WORK IS PROVIDED AS +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass. To + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Duplex 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Http 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.NetTcp 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Primitives 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Security 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceModel.Syndication 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.ServiceProcess.ServiceController 8.0.1 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Speech 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Text.Encoding.CodePages 8.0.0 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Text.Encodings.Web 8.0.0 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Text.Encodings.Web 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Text.Json 9.0.6 - MIT + + +Copyright (c) 2021 +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright (c) 1998 Microsoft +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Portions (c) International Organization for Standardization 1986 +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang) Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Threading.AccessControl 9.0.0-preview.6.24327.7 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) 2022 FormatJS +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2015 Andrew Gallant +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright (c) 2018 Nemanja Mijailovic +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2015-2018, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Web.Services.Description 4.10.3 - MIT + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2000-2014 The Legion of the Bouncy Castle Inc. (http://www.bouncycastle.org) + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +System.Windows.Extensions 8.0.0 - MIT + + +Copyright (c) Six Labors +(c) Microsoft Corporation +Copyright (c) Andrew Arnott +Copyright 2019 LLVM Project +Copyright 2018 Daniel Lemire +Copyright (c) .NET Foundation +Copyright (c) 2011, Google Inc. +Copyright (c) 2020 Dan Shechter +(c) 1997-2005 Sean Eron Anderson +Copyright (c) 1998 Microsoft. To +Copyright (c) 2022, Wojciech Mula +Copyright (c) 2017 Yoshifumi Kawai +Copyright (c) 2022, Geoff Langdale +Copyright (c) 2005-2020 Rich Felker +Copyright (c) 2012-2021 Yann Collet +Copyright (c) Microsoft Corporation +Copyright (c) 2007 James Newton-King +Copyright (c) 1991-2022 Unicode, Inc. +Copyright (c) 2013-2017, Alfred Klomp +Copyright 2012 the V8 project authors +Copyright (c) 1999 Lucent Technologies +Copyright (c) 2008-2016, Wojciech Mula +Copyright (c) 2011-2020 Microsoft Corp +Copyright (c) 2015-2017, Wojciech Mula +Copyright (c) 2021 csFastFloat authors +Copyright (c) 2005-2007, Nick Galbreath +Copyright (c) 2015 The Chromium Authors +Copyright (c) 2018 Alexander Chermyanin +Copyright (c) The Internet Society 1997 +Portions (c) International Organization +Copyright (c) 2004-2006 Intel Corporation +Copyright (c) 2011-2015 Intel Corporation +Copyright (c) 2013-2017, Milosz Krajewski +Copyright (c) 2016-2017, Matthieu Darbois +Copyright (c) The Internet Society (2003) +Copyright (c) .NET Foundation Contributors +Copyright (c) 2020 Mara Bos +Copyright (c) .NET Foundation and Contributors +Copyright (c) 2012 - present, Victor Zverovich +Copyright (c) 2006 Jb Evain (jbevain@gmail.com) +Copyright (c) 2008-2020 Advanced Micro Devices, Inc. +Copyright (c) 2019 Microsoft Corporation, Daan Leijen +Copyright (c) 2011 Novell, Inc (http://www.novell.com) +Copyright (c) 1995-2022 Jean-loup Gailly and Mark Adler +Copyright (c) 2015 Xamarin, Inc (http://www.xamarin.com) +Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors +Copyright (c) 2014 Ryan Juckett http://www.ryanjuckett.com +Copyright (c) 1990- 1993, 1996 Open Software Foundation, Inc. +Copyright (c) YEAR W3C(r) (MIT, ERCIM, Keio, Beihang). Disclaimers +Copyright (c) 2015 THL A29 Limited, a Tencent company, and Milo Yip +Copyright (c) 1980, 1986, 1993 The Regents of the University of California +Copyright 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 The Regents of the University of California +Copyright (c) 1989 by Hewlett-Packard Company, Palo Alto, Ca. & Digital Equipment Corporation, Maynard, Mass + +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +xunit.runner.visualstudio 2.4.5 - MIT + + +Copyright (c) .NET Foundation +Copyright (c) 2015 .NET Foundation +Copyright (c) Outercurve Foundation +Copyright (c) .NET Foundation and Contributors +Copyright (c) .NET Foundation xUnit.net Runner Utility +Copyright (c) .NET Foundation ,xUnit.net Runner Utility +Copyright (c) .NET Foundation 1xUnit.net Runner Utility +Copyright (c) .NET Foundation xUnit.net Runner Reporters +Copyright (c) .NET Foundation .xUnit.net Runner Reporters +Copyright (c) Outercurve Foundation WrapNonExceptionThrows RSDS +Copyright (c) .NET Foundation and Contributors. Visual Studio 2019 + +Unless otherwise noted, the source code here is covered by the following license: + + Copyright (c) .NET Foundation and Contributors + All Rights Reserved + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +----------------------- + +The code in src/xunit.runner.visualstudio/Utility/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions was imported from: + https://github.com/dotnet/core-setup/tree/v2.0.1/src/managed/Microsoft.DotNet.PlatformAbstractions + +The code in src/xunit.runner.visualstudio/Utility/AssemblyResolution/Microsoft.DotNet.PlatformAbstractions was imported from: + https://github.com/dotnet/core-setup/tree/v2.0.1/src/managed/Microsoft.Extensions.DependencyModel + +Both sets of code are covered by the following license: + + The MIT License (MIT) + + Copyright (c) 2015 .NET Foundation + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +yaml/libyaml 840b65c40675e2d06bf40405ad3f12dec7f35923 - MIT + + +Copyright (c) 2017-2020 Ingy dot Net +Copyright (c) 2006-2016 Kirill Simonov +Copyright (c) 2017-2020 Ingy dot Net +Copyright (c) 2006-2016 Kirill Simonov + +Copyright (c) 2017-2020 Ingy döt Net +Copyright (c) 2006-2016 Kirill Simonov + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +YamlDotNet 16.3.0 - MIT + + +Copyright (c) Antoine Aubry and contributors +(c) Antoine Aubry and contributors 2008 - 2019 +Copyright (c) Antoine Aubry and contributors 2008 - 2019 + +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.Management.Infrastructure.Runtime.Win 3.0.0 + + +(c) Microsoft 2024 +(c) Microsoft Corporation +Copyright (c) Microsoft Corporation + +MICROSOFT SOFTWARE LICENSE TERMS +Microsoft.Management.Infrastructure.dll +Microsoft.Management.Infrastructure.Native.dll +Microsoft.Management.Infrastructure.Unmanaged.dll +Mi.dll +Miutils.dll +________________________________________ +IF YOU LIVE IN (OR ARE A BUSINESS WITH A PRINCIPAL PLACE OF BUSINESS IN) THE UNITED STATES, PLEASE READ THE “BINDING ARBITRATION AND CLASS ACTION WAIVER” SECTION BELOW. IT AFFECTS HOW DISPUTES ARE RESOLVED. +________________________________________ + +These license terms are an agreement between you and Microsoft Corporation (or one of its affiliates). They apply to the software named above and any Microsoft services or software updates (except to the extent such services or updates are accompanied by new or additional terms, in which case those different terms apply prospectively and do not alter your or Microsoft’s rights relating to pre-updated software or services). IF YOU COMPLY WITH THESE LICENSE TERMS, YOU HAVE THE RIGHTS BELOW. BY USING THE SOFTWARE, YOU ACCEPT THESE TERMS. +1. INSTALLATION AND USE RIGHTS. +a) General. You may install and use any number of copies of the software solely for use with Microsoft PowerShell. +b) Third Party Software. The software may include third party applications that Microsoft, not the third party, licenses to you under this agreement. Any included notices for third party applications are for your information only. +2. DATA COLLECTION. The software may collect information about you and your use of the software and send that to Microsoft. Microsoft may use this information to provide services and improve Microsoft’s products and services. Your opt-out rights, if any, are described in the product documentation. Some features in the software may enable collection of data from users of your applications that access or use the software. If you use these features to enable data collection in your applications, you must comply with applicable law, including getting any required user consent, and maintain a prominent privacy policy that accurately informs users about how you use, collect, and share their data. You can learn more about Microsoft’s data collection and use in the product documentation and the Microsoft Privacy Statement at aka.ms/privacy. You agree to comply with all applicable provisions of the Microsoft Privacy Statement. +a. Processing of Personal Data. To the extent Microsoft is a processor or subprocessor of personal data in connection with the software, Microsoft makes the commitments in the European Union General Data Protection Regulation Terms of the Online Services Terms to all customers effective May 25, 2018, at http://go.microsoft.com/?linkid=9840733. +3. SCOPE OF LICENSE. The software is licensed, not sold. Microsoft reserves all other rights. Unless applicable law gives you more rights despite this limitation, you will not (and have no right to): +a) work around any technical limitations in the software that only allow you to use it in certain ways; +b) reverse engineer, decompile or disassemble the software; +c) remove, minimize, block, or modify any notices of Microsoft or its suppliers in the software; +d) use the software in any way that is against the law or to create or propagate malware; or +e) share, publish, distribute, or lend the software, provide the software as a stand-alone hosted solution for others to use, or transfer the software or this agreement to any third party. +4. EXPORT RESTRICTIONS. You must comply with all domestic and international export laws and regulations that apply to the software, which include restrictions on destinations, end users, and end use. For further information on export restrictions, visit http://aka.ms/exporting. +5. SUPPORT SERVICES. Microsoft is not obligated under this agreement to provide any support services for the software. Any support provided is “as is”, “with all faults”, and without warranty of any kind. +6. UPDATES. The software may periodically check for updates, and download and install them for you. You may obtain updates only from Microsoft or authorized sources. Microsoft may need to update your system to provide you with updates. You agree to receive these automatic updates without any additional notice. Updates may not include or support all existing software features, services, or peripheral devices. +7. BINDING ARBITRATION AND CLASS ACTION WAIVER. This Section applies if you live in (or, if a business, your principal place of business is in) the United States. If you and Microsoft have a dispute, you and Microsoft agree to try for 60 days to resolve it informally. If you and Microsoft can’t, you and Microsoft agree to binding individual arbitration before the American Arbitration Association under the Federal Arbitration Act (“FAA”), and not to sue in court in front of a judge or jury. Instead, a neutral arbitrator will decide. Class action lawsuits, class-wide arbitrations, private attorney-general actions, and any other proceeding where someone acts in a representative capacity are not allowed; nor is combining individual proceedings without the consent of all parties. The complete Arbitration Agreement contains more terms and is at http://aka.ms/arb-agreement-1. You and Microsoft agree to these terms. +8. TERMINATION. Without prejudice to any other rights, Microsoft may terminate this agreement if you fail to comply with any of its terms or conditions. In such event, you must destroy all copies of the software and all of its component parts. +9. ENTIRE AGREEMENT. This agreement, and any other terms Microsoft may provide for supplements, updates, or third-party applications, is the entire agreement for the software. +10. APPLICABLE LAW AND PLACE TO RESOLVE DISPUTES. If you acquired the software in the United States or Canada, the laws of the state or province where you live (or, if a business, where your principal place of business is located) govern the interpretation of this agreement, claims for its breach, and all other claims (including consumer protection, unfair competition, and tort claims), regardless of conflict of laws principles, except that the FAA governs everything related to arbitration. If you acquired the software in any other country, its laws apply, except that the FAA governs everything related to arbitration. If U.S. federal jurisdiction exists, you and Microsoft consent to exclusive jurisdiction and venue in the federal court in King County, Washington for all disputes heard in court (excluding arbitration). If not, you and Microsoft consent to exclusive jurisdiction and venue in the Superior Court of King County, Washington for all disputes heard in court (excluding arbitration). +11. CONSUMER RIGHTS; REGIONAL VARIATIONS. This agreement describes certain legal rights. You may have other rights, including consumer rights, under the laws of your state, province, or country. Separate and apart from your relationship with Microsoft, you may also have rights with respect to the party from which you acquired the software. This agreement does not change those other rights if the laws of your state, province, or country do not permit it to do so. For example, if you acquired the software in one of the below regions, or mandatory country law applies, then the following provisions apply to you: +a) Australia. You have statutory guarantees under the Australian Consumer Law and nothing in this agreement is intended to affect those rights. +b) Canada. If you acquired this software in Canada, you may stop receiving updates by turning off the automatic update feature, disconnecting your device from the Internet (if and when you re-connect to the Internet, however, the software will resume checking for and installing updates), or uninstalling the software. The product documentation, if any, may also specify how to turn off updates for your specific device or software. +c) Germany and Austria. +i. Warranty. The properly licensed software will perform substantially as described in any Microsoft materials that accompany the software. However, Microsoft gives no contractual guarantee in relation to the licensed software. +ii. Limitation of Liability. In case of intentional conduct, gross negligence, claims based on the Product Liability Act, as well as, in case of death or personal or physical injury, Microsoft is liable according to the statutory law. +Subject to the foregoing clause ii., Microsoft will only be liable for slight negligence if Microsoft is in breach of such material contractual obligations, the fulfillment of which facilitate the due performance of this agreement, the breach of which would endanger the purpose of this agreement and the compliance with which a party may constantly trust in (so-called "cardinal obligations"). In other cases of slight negligence, Microsoft will not be liable for slight negligence. +12. DISCLAIMER OF WARRANTY. THE SOFTWARE IS LICENSED “AS IS.” YOU BEAR THE RISK OF USING IT. MICROSOFT GIVES NO EXPRESS WARRANTIES, GUARANTEES, OR CONDITIONS. TO THE EXTENT PERMITTED UNDER APPLICABLE LAWS, MICROSOFT EXCLUDES ALL IMPLIED WARRANTIES, INCLUDING MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. +13. LIMITATION ON AND EXCLUSION OF DAMAGES. IF YOU HAVE ANY BASIS FOR RECOVERING DAMAGES DESPITE THE PRECEDING DISCLAIMER OF WARRANTY, YOU CAN RECOVER FROM MICROSOFT AND ITS SUPPLIERS ONLY DIRECT DAMAGES UP TO U.S. $5.00. YOU CANNOT RECOVER ANY OTHER DAMAGES, INCLUDING CONSEQUENTIAL, LOST PROFITS, SPECIAL, INDIRECT OR INCIDENTAL DAMAGES. +This limitation applies to (a) anything related to the software, services, content (including code) on third party Internet sites, or third party applications; and (b) claims for breach of contract, warranty, guarantee, or condition; strict liability, negligence, or other tort; or any other claim; in each case to the extent permitted by applicable law. +It also applies even if Microsoft knew or should have known about the possibility of the damages. The above limitation or exclusion may not apply to you because your state, province, or country may not allow the exclusion or limitation of incidental, consequential, or other damages. + +Please note: As this software is distributed in Canada, some of the clauses in this agreement are provided below in French. +Remarque: Ce logiciel étant distribué au Canada, certaines des clauses dans ce contrat sont fournies ci-dessous en français. +EXONÉRATION DE GARANTIE. Le logiciel visé par une licence est offert « tel quel ». Toute utilisation de ce logiciel est à votre seule risque et péril. Microsoft n’accorde aucune autre garantie expresse. Vous pouvez bénéficier de droits additionnels en vertu du droit local sur la protection des consommateurs, que ce contrat ne peut modifier. La ou elles sont permises par le droit locale, les garanties implicites de qualité marchande, d’adéquation à un usage particulier et d’absence de contrefaçon sont exclues. +LIMITATION DES DOMMAGES-INTÉRÊTS ET EXCLUSION DE RESPONSABILITÉ POUR LES DOMMAGES. Vous pouvez obtenir de Microsoft et de ses fournisseurs une indemnisation en cas de dommages directs uniquement à hauteur de 5,00 $ US. Vous ne pouvez prétendre à aucune indemnisation pour les autres dommages, y compris les dommages spéciaux, indirects ou accessoires et pertes de bénéfices. +Cette limitation concerne: +• tout ce qui est relié au logiciel, aux services ou au contenu (y compris le code) figurant sur des sites Internet tiers ou dans des programmes tiers; et +• les réclamations au titre de violation de contrat ou de garantie, ou au titre de responsabilité stricte, de négligence ou d’une autre faute dans la limite autorisée par la loi en vigueur. +Elle s’applique également, même si Microsoft connaissait ou devrait connaître l’éventualité d’un tel dommage. Si votre pays n’autorise pas l’exclusion ou la limitation de responsabilité pour les dommages indirects, accessoires ou de quelque nature que ce soit, il se peut que la limitation ou l’exclusion ci-dessus ne s’appliquera pas à votre égard. +EFFET JURIDIQUE. Le présent contrat décrit certains droits juridiques. Vous pourriez avoir d’autres droits prévus par les lois de votre pays. Le présent contrat ne modifie pas les droits que vous confèrent les lois de votre pays si celles-ci ne le permettent pas. + + + +--------------------------------------------------------- + +--------------------------------------------------------- + +Microsoft.NETCore.Platforms 1.1.1 + + +(c) Microsoft Corporation +Copyright (c) .NET Foundation and Contributors + +--------------------------------------------------------- + +--------------------------------------------------------- + +madler/zlib 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf - Zlib + + +Copyright (c) 2003 Mark Adler +Copyright (c) 2018 Mark Adler +(c) Copyright Henrik Ravn 2004 +Copyright (c) Henrik Ravn 2004 +Copyright 1995-2024 Mark Adler +Copyright (c) 2003 Cosmin Truta +Copyright (c) 1990-2000 Info-ZIP. +Copyright (c) 1998 by Bob Dellaca +Copyright (c) 2004 by Henrik Ravn +Copyright (c) 1995-2003 Mark Adler +Copyright (c) 1995-2008 Mark Adler +Copyright (c) 1995-2017 Mark Adler +Copyright (c) 1995-2019 Mark Adler +Copyright (c) 1995-2022 Mark Adler +Copyright (c) 1995-2024 Mark Adler +Copyright (c) 2002-2013 Mark Adler +Copyright (c) 2003 by Cosmin Truta +Copyright (c) 2003-2010 Mark Adler +Copyright (c) 2004-2017 Mark Adler +Copyright (c) 2004-2019 Mark Adler +Copyright (c) 2004-2023 Mark Adler +Copyright (c) 2004-2024 Mark Adler +Copyright (c) 1996 L. Peter Deutsch +Copyright (c) 1997,99 Borland Corp. +Copyright (c) 2003, 2012 Mark Adler +Copyright (c) 2004, 2010 Mark Adler +Copyright (c) 2011, 2016 Mark Adler +Copyright (c) 2007-2008 Even Rouault +Copyright (c) 1998-2005 Gilles Vollant +Copyright (c) 1995-1998 Jean-loup Gailly +Copyright (c) 1995-2003 Jean-loup Gailly +Copyright (c) 1995-2003, 2010 Mark Adler +Copyright (c) 1995-2005, 2010 Mark Adler +Copyright (c) 1995-2011, 2016 Mark Adler +Copyright (c) 1995-2017 Jean-loup Gailly +Copyright (c) 1995-2024 Jean-loup Gailly +Copyright (c) 1997,99 Borland Corporation +Copyright (c) 1998 by Andreas R. Kleinert +Copyright (c) 2002-2003 Dmitriy Anisimkov +Copyright (c) 2002-2004 Dmitriy Anisimkov +Copyright (c) 2003, 2012, 2013 Mark Adler +Copyright (c) 2004, 2005, 2012 Mark Adler +Copyright (c) 2004, 2008, 2012 Mark Adler +Copyright (c) 1998 by Jacques Nomssi Nzali +(c) 1995-2022 Jean-loup Gailly & Mark Adler +(c) 1995-2024 Jean-loup Gailly & Mark Adler +Copyright (c) 1995-2003 by Jean-loup Gailly +Copyright (c) 1998-2010 - by Gilles Vollant +(c) 1995-2017 Jean-loup Gailly and Mark Adler +(c) 1995-2022 Jean-loup Gailly and Mark Adler +(c) 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 2005, 2012, 2018, 2023 Mark Adler +Copyright (c) 2007, 2008, 2012, 2018 Mark Adler +Copyright 1995-2024 Jean-loup Gailly and Mark Adler +Copyright (c) 1995-2006, 2011, 2016 Jean-loup Gailly +Copyright (c) 1995-2017 Jean-Loup Gailly, Mark Adler +Copyright (c) 1995-2024 Jean-loup Gailly, Mark Adler +Copyright (c) 1998,1999,2000 by Jacques Nomssi Nzali +Copyright (c) 2003, 2005, 2008, 2010, 2012 Mark Adler +Copyright (c) 2004, 2008, 2012, 2016, 2019 Mark Adler +Copyright (c) 1995-2003 Jean-loup Gailly and Mark Adler +Copyright (c) 1995-2024 Jean-loup Gailly and Mark Adler +copyright (c) 1995-2017 Jean-loup Gailly and Mark Adler +Copyright (c) 1996 L. Peter Deutsch and Jean-Loup Gailly +Copyright (c) 1995-2006, 2010, 2011, 2016 Jean-loup Gailly +Copyright (c) 2009-2010 Mathias Svensson http://result42.com +Copyright (c) 1995-2005, 2014, 2016 Jean-loup Gailly, Mark Adler +Copyright Jean-loup Gailly Osma Ahvenlampi +Copyright 1998-2004 Gilles Vollant - http://www.winimage.com/zLibDll +Copyright (c) 1997 Christian Michelsen Research AS Advanced Computing +Copyright (c) 1995-2003, 2010, 2014, 2016 Jean-loup Gailly, Mark Adler +Copyright (c) 1998 - 2010 Gilles Vollant, Even Rouault, Mathias Svensson +Copyright (c) 1995-2010 Jean-loup Gailly, Brian Raiter and Gilles Vollant +Copyright (c) 1998-2010 Gilles Vollant (minizip) http://www.winimage.com/zLibDll/minizip.html + +Copyright notice: + + (C) 1995-2022 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + +--------------------------------------------------------- + +--------------------------------------------------------- + +tristanpenman/valijson 0b4771e273a065d437814baf426bcfcafec0f434 - BSD-2-Clause + + +Copyright (c) 2021 Tristan Penman +Copyright (c) 2016, Tristan Penman +Copyright (c) The Internet Society (2005) +Copyright (c) The Internet Society (2006) +Copyright (c) 2011 - 2012 Andrzej Krzemienski +Copyright (c) 2016, Akamai Technologies, Inc. +Copyright (c) 2016 Akamai Technologies Polymorphic +Copyright (c) 2010 IETF Trust and the persons identified as the document authors +Copyright (c) 2012 IETF Trust and the persons identified as the document authors +Copyright (c) 2013 IETF Trust and the persons identified as the document authors + +Copyright (c) 2016, Tristan Penman +Copyright (c) 2016, Akamai Technologies, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + +--------------------------------------------------------- + +--------------------------------------------------------- + +open-source-parsers/jsoncpp 9be589598595963f94ba264d7b416d0533421106 - MIT OR OTHER + + +Copyright (c) 2016 InfoTeCS JSC. +Copyright 2007-2010 The JsonCpp Authors +Copyright 2007-2019 The JsonCpp Authors +Copyright 2007 Baptiste Lepilleur and The JsonCpp Authors +Copyright 2009 Baptiste Lepilleur and The JsonCpp Authors +Copyright 2010 Baptiste Lepilleur and The JsonCpp Authors +Copyright 2011 Baptiste Lepilleur and The JsonCpp Authors +Copyright 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +Copyright 2007-2011 Baptiste Lepilleur and The JsonCpp Authors +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors +Copyright (c) 2007-2010 by Baptiste Lepilleur and The JsonCpp Authors + +The JsonCpp library's source code, including accompanying documentation, +tests and demonstration applications, are licensed under the following +conditions... + +Baptiste Lepilleur and The JsonCpp Authors explicitly disclaim copyright in all +jurisdictions which recognize such a disclaimer. In such jurisdictions, +this software is released into the Public Domain. + +In jurisdictions which do not recognize Public Domain property (e.g. Germany as of +2010), this software is Copyright (c) 2007-2010 by Baptiste Lepilleur and +The JsonCpp Authors, and is released under the terms of the MIT License (see below). + +In jurisdictions which recognize Public Domain property, the user of this +software may choose to accept it either as 1) Public Domain, 2) under the +conditions of the MIT License (see below), or 3) under the terms of dual +Public Domain/MIT License conditions described here, as they choose. + +The MIT License is about as close to Public Domain as a license can get, and is +described in clear, concise terms at: + + http://en.wikipedia.org/wiki/MIT_License + +The full text of the MIT License follows: + +======================================================================== +Copyright (c) 2007-2010 Baptiste Lepilleur and The JsonCpp Authors + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS +BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +======================================================================== +(END LICENSE TEXT) + +The MIT license is compatible with both the GPL and commercial +software, affording one all of the rights of Public Domain with the +minor nuisance of being required to keep the above copyright notice +and license text in the source code. Note also that by accepting the +Public Domain "license" you can re-license your copy using whatever +license you like. + + +--------------------------------------------------------- + diff --git a/README.md b/README.md index 0ec2c4d65f..fc84a2beda 100644 --- a/README.md +++ b/README.md @@ -1,152 +1,152 @@ -# ![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 a few methods to get development releases: - -* Install a [Windows 10 or Windows 11 Insider](https://insider.windows.com/) build. -* Manually update using a development build from our [Releases](https://github.com/microsoft/winget-cli/releases) page. -* Use the `Repair-WinGetPackageManager` cmdlet from the [Microsoft.WinGet.Client](https://www.powershellgallery.com/packages/Microsoft.WinGet.Client/) PowerShell module and use the `-IncludePrerelease` parameter. - -> [!NOTE] -> 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). - -## PowerShell Module - -The [Microsoft.WinGet.Client](https://www.powershellgallery.com/packages/Microsoft.WinGet.Client/) PowerShell module provides native cmdlets for interacting with the Windows Package Manager. You can install it from the PowerShell Gallery: - -```powershell -Install-Module -Name Microsoft.WinGet.Client -``` - -Example usage: - -```powershell -# Search for a package -Find-WinGetPackage -Query "Visual Studio Code" - -# Install a package -Install-WinGetPackage -Id Microsoft.VisualStudioCode - -# List installed packages -Get-WinGetPackage - -# Update a package -Update-WinGetPackage -Id Microsoft.VisualStudioCode - -# Repair the WinGet package manager installation -Repair-WinGetPackageManager -``` - -For more information, see the [Microsoft.WinGet.Client](https://www.powershellgallery.com/packages/Microsoft.WinGet.Client/) page on the PowerShell Gallery. - -## 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 - -Please follow our [developer guidance](/doc/Developing.md) to build, run & test the client. - -## 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. - +# ![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 a few methods to get development releases: + +* Install a [Windows 10 or Windows 11 Insider](https://insider.windows.com/) build. +* Manually update using a development build from our [Releases](https://github.com/microsoft/winget-cli/releases) page. +* Use the `Repair-WinGetPackageManager` cmdlet from the [Microsoft.WinGet.Client](https://www.powershellgallery.com/packages/Microsoft.WinGet.Client/) PowerShell module and use the `-IncludePrerelease` parameter. + +> [!NOTE] +> 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). + +## PowerShell Module + +The [Microsoft.WinGet.Client](https://www.powershellgallery.com/packages/Microsoft.WinGet.Client/) PowerShell module provides native cmdlets for interacting with the Windows Package Manager. You can install it from the PowerShell Gallery: + +```powershell +Install-Module -Name Microsoft.WinGet.Client +``` + +Example usage: + +```powershell +# Search for a package +Find-WinGetPackage -Query "Visual Studio Code" + +# Install a package +Install-WinGetPackage -Id Microsoft.VisualStudioCode + +# List installed packages +Get-WinGetPackage + +# Update a package +Update-WinGetPackage -Id Microsoft.VisualStudioCode + +# Repair the WinGet package manager installation +Repair-WinGetPackageManager +``` + +For more information, see the [Microsoft.WinGet.Client](https://www.powershellgallery.com/packages/Microsoft.WinGet.Client/) page on the PowerShell Gallery. + +## 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 + +Please follow our [developer guidance](/doc/Developing.md) to build, run & test the client. + +## 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.loc.yml b/azure-pipelines.loc.yml index 0c3bd9b11e..870c0eccd9 100644 --- a/azure-pipelines.loc.yml +++ b/azure-pipelines.loc.yml @@ -1,61 +1,61 @@ -# -# Localization -# This pipeline uploads English strings files to the localization service, downloads any translated -# files which are available, and checks them into git. This pipeline relies on Microsoft-internal -# resources to run. -# - -# Expects a variable called LocServiceKey to contain the OAuth client secret for Touchdown. - -trigger: none -pr: none - -name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) - -jobs: -- job: Localize - pool: - vmImage: windows-latest - variables: - skipComponentGovernanceDetection: true - tdbuildTeamId: 8343 - steps: - # Upload client resources - - task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@5 - displayName: Send resources to Touchdown Build - inputs: - teamId: $(tdbuildTeamId) - authType: FederatedIdentity - FederatedIdentityServiceConnection: AppInstallerTDBuild - isPreview: false - relativePathRoot: src\AppInstallerCLIPackage\Shared\Strings\en-us - resourceFilePath: '*.resw' - outputDirectoryRoot: localization/Resources/ - - # Upload Group Policy ADML - # Do it as a separate step as we need the result in a different location - - task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@5 - displayName: Send ADML to Touchdown Build - inputs: - teamId: $(tdbuildTeamId) - authType: FederatedIdentity - FederatedIdentityServiceConnection: AppInstallerTDBuild - isPreview: false - relativePathRoot: doc\admx\en-US - resourceFilePath: '*.adml' - outputDirectoryRoot: Localization\Policies\ - - - script: | - cd $(Build.SourcesDirectory) - git add -A - git diff --cached --exit-code - echo ##vso[task.setvariable variable=hasChanges]%errorlevel% - git diff --cached > $(Build.ArtifactStagingDirectory)\LocalizedStrings.patch - displayName: Check for changes and create patch file - - - task: PublishPipelineArtifact@0 - displayName: Publish patch file as artifact - condition: eq(variables['hasChanges'], '1') - inputs: - artifactName: Patch - targetPath: $(Build.ArtifactStagingDirectory) +# +# Localization +# This pipeline uploads English strings files to the localization service, downloads any translated +# files which are available, and checks them into git. This pipeline relies on Microsoft-internal +# resources to run. +# + +# Expects a variable called LocServiceKey to contain the OAuth client secret for Touchdown. + +trigger: none +pr: none + +name: $(BuildDefinitionName)_$(date:yyMM).$(date:dd)$(rev:rrr) + +jobs: +- job: Localize + pool: + vmImage: windows-latest + variables: + skipComponentGovernanceDetection: true + tdbuildTeamId: 8343 + steps: + # Upload client resources + - task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@5 + displayName: Send resources to Touchdown Build + inputs: + teamId: $(tdbuildTeamId) + authType: FederatedIdentity + FederatedIdentityServiceConnection: AppInstallerTDBuild + isPreview: false + relativePathRoot: src\AppInstallerCLIPackage\Shared\Strings\en-us + resourceFilePath: '*.resw' + outputDirectoryRoot: localization/Resources/ + + # Upload Group Policy ADML + # Do it as a separate step as we need the result in a different location + - task: MicrosoftTDBuild.tdbuild-task.tdbuild-task.TouchdownBuildTask@5 + displayName: Send ADML to Touchdown Build + inputs: + teamId: $(tdbuildTeamId) + authType: FederatedIdentity + FederatedIdentityServiceConnection: AppInstallerTDBuild + isPreview: false + relativePathRoot: doc\admx\en-US + resourceFilePath: '*.adml' + outputDirectoryRoot: Localization\Policies\ + + - script: | + cd $(Build.SourcesDirectory) + git add -A + git diff --cached --exit-code + echo ##vso[task.setvariable variable=hasChanges]%errorlevel% + git diff --cached > $(Build.ArtifactStagingDirectory)\LocalizedStrings.patch + displayName: Check for changes and create patch file + + - task: PublishPipelineArtifact@0 + displayName: Publish patch file as artifact + condition: eq(variables['hasChanges'], '1') + inputs: + artifactName: Patch + targetPath: $(Build.ArtifactStagingDirectory) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f343fb4156..f52ded986a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,722 +1,722 @@ -# Commit triggers -trigger: -- master - -# PR triggers -pr: - branches: - include: - - master - paths: - include: - - azure-pipelines.yml - - templates/* - - src/* - - schemas/JSON/manifests/* - -pool: - vmImage: 'windows-2025' - -variables: - solution: 'src\AppInstallerCLI.sln' - EnableDetectorVcpkg: true - -# Do not set the build version for a PR build. - -jobs: -- job: 'GetReleaseTag' - condition: not(eq(variables['Build.Reason'], 'PullRequest')) - variables: - runCodesignValidationInjection: ${{ false }} - skipComponentGovernanceDetection: ${{ true }} - steps: - - task: PowerShell@2 - name: 'GetTag' - displayName: Get Release Tag - inputs: - filePath: 'src\binver\Update-BinVer.ps1' - arguments: '-OutVar' - workingDirectory: 'src' - -# Build job creates artifacts for use in test jobs - -- job: 'Build' - timeoutInMinutes: 120 - dependsOn: 'GetReleaseTag' - condition: always() - - strategy: - matrix: - x86_release: - buildConfiguration: 'Release' - buildPlatform: 'x86' - artifactIdentifier: 'x86release' - x64_release: - buildConfiguration: 'Release' - buildPlatform: 'x64' - artifactIdentifier: 'x64release' - - variables: - BuildVer: $[counter(dependencies.GetReleaseTag.outputs['GetTag.tag'], 1)] - buildOutDir: $(Build.SourcesDirectory)\src\$(buildPlatform)\$(buildConfiguration) - buildOutDirAnyCpu: $(Build.SourcesDirectory)\src\AnyCPU\$(buildConfiguration) - artifactsDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform) - appxPackageDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform)\AppxPackages - - steps: - - task: NuGetToolInstaller@1 - displayName: Install Nuget - - # Restores all projects, including native (vcxproj) projects - - task: NuGetCommand@2 - displayName: Restore Solution - inputs: - restoreSolution: '$(solution)' - - # Restore these UAP packages as https://github.com/NuGet/Home/issues/7796 leads to all UAP packages being skipped for restore. - # Even though they don't need any actual restore action, they need the project.assets.json file to be created and a direct restore does that. - - task: NuGetCommand@2 - displayName: Restore AppInstallerCLIPackage - inputs: - restoreSolution: 'src\AppInstallerCLIPackage\AppInstallerCLIPackage.wapproj' - - - task: NuGetCommand@2 - displayName: Restore AppInstallerTestMsixInstaller - inputs: - restoreSolution: 'src\AppInstallerTestMsixInstaller\AppInstallerTestMsixInstaller.wapproj' - - # Restores only .NET core projects, but is still necessary, as without this the IndexCreationTool and LocalhostWebServer projects fail to build - - task: DotNetCoreCLI@2 - displayName: DotNet Restore - inputs: - command: 'restore' - projects: '**/*.csproj' - - - task: CmdLine@2 - displayName: Enable Vcpkg Install - inputs: - script: | - $(VCPKG_INSTALLATION_ROOT)\vcpkg.exe integrate install - workingDirectory: '$(VCPKG_INSTALLATION_ROOT)' - - - task: PowerShell@2 - displayName: Update Binary Version - condition: not(eq(variables['Build.Reason'], 'PullRequest')) - inputs: - filePath: 'src\binver\Update-BinVer.ps1' - arguments: '-TargetFile binver\binver\version.h -BuildVersion $(BuildVer)' - workingDirectory: 'src' - - # Build all solutions in the root directory. - - task: VSBuild@1 - displayName: Build Solution - inputs: - platform: '$(buildPlatform)' - solution: '$(solution)' - configuration: '$(buildConfiguration)' - msbuildArgs: '/bl:$(artifactsDir)\msbuild.binlog - /p:AppxBundlePlatforms="$(buildPlatform)" - /p:AppxPackageDir="$(appxPackageDir)" - /p:AppxBundle=Always - /p:UapAppxPackageBuildMode=SideloadOnly - /p:WingetCleanIntermediateFiles=true' - maximumCpuCount: true - - - task: MSBuild@1 - displayName: Build MSIX Test Installer File - inputs: - platform: '$(buildPlatform)' - solution: 'src\AppInstallerTestMsixInstaller\AppInstallerTestMsixInstaller.wapproj' - configuration: '$(buildConfiguration)' - msbuildArguments: '/p:AppxPackageOutput="$(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix" - /p:AppxBundle=Never - /p:UapAppxPackageBuildMode=SideLoadOnly - /p:AppxPackageSigningEnabled=false' - maximumCpuCount: true - - - task: CopyFiles@2 - displayName: Copy vcpkg logs - inputs: - SourceFolder: $(VCPKG_INSTALLATION_ROOT)\buildtrees - Contents: '**\*.log' - TargetFolder: '$(artifactsDir)\vcpkgLogs' - condition: succeededOrFailed() - - - task: CopyFiles@2 - displayName: 'Copy specific build artifacts' - inputs: - Contents: | - $(buildOutDir)\WinGetUtil\WinGetUtil.dll - $(buildOutDir)\WinGetUtil\WinGetUtil.pdb - TargetFolder: '$(artifactsDir)' - - - task: PowerShell@2 - displayName: Create Package Layout - inputs: - filePath: 'src\AppInstallerCLIPackage\Execute-AppxRecipe.ps1' - arguments: '-AppxRecipePath AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)\AppInstallerCLIPackage.build.appxrecipe -LayoutPath $(artifactsDir)\DevPackage -Force -Verbose' - workingDirectory: 'src' - - - task: CopyFiles@2 - displayName: 'Copy native binaries for Microsoft.WinGet.Client (net8)' - inputs: - SourceFolder: $(buildOutDir) - Contents: | - Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.dll - Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd - WindowsPackageManager\WindowsPackageManager.dll - UndockedRegFreeWinRT\winrtact.dll - TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net8.0-windows10.0.26100.0\SharedDependencies\$(BuildPlatform) - flattenFolders: true - - - task: CopyFiles@2 - displayName: 'Copy native binaries for Microsoft.WinGet.Client (net48)' - inputs: - SourceFolder: $(buildOutDir) - Contents: | - Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.dll - Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd - WindowsPackageManager\WindowsPackageManager.dll - UndockedRegFreeWinRT\winrtact.dll - TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net48\SharedDependencies\$(BuildPlatform) - flattenFolders: true - - - task: CopyFiles@2 - displayName: 'Copy native binaries for Microsoft.WinGet.Configuration' - inputs: - SourceFolder: $(buildOutDir) - Contents: | - Microsoft.Management.Configuration\Microsoft.Management.Configuration.dll - TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform) - flattenFolders: true - - - task: CopyFiles@2 - displayName: 'Copy managed binaries for Microsoft.WinGet.Configuration in arch specific' - inputs: - SourceFolder: $(buildOutDirAnyCpu) - Contents: | - Microsoft.Management.Configuration.Projection\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.Projection.dll - TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform) - flattenFolders: true - - - task: CopyFiles@2 - displayName: 'Copy Microsoft.WinGet.UnitTests AnyCPU Files' - inputs: - SourceFolder: '$(buildOutDirAnyCpu)\Microsoft.WinGet.UnitTests\net8.0-windows10.0.26100.0' - TargetFolder: '$(artifactsDir)\Microsoft.WinGet.UnitTests\' - CleanTargetFolder: true - OverWrite: true - - - task: CopyFiles@2 - displayName: 'Copy PowerShell AnyCPU Module Files' - inputs: - SourceFolder: '$(buildOutDirAnyCpu)\PowerShell' - TargetFolder: '$(artifactsDir)\PowerShell' - - - task: CopyFiles@2 - displayName: 'Copy binaries' - inputs: - SourceFolder: '$(buildOutDir)' - TargetFolder: '$(artifactsDir)' - Contents: | - AppInstallerCLIE2ETests\** - AppInstallerCLITests\** - ComInprocTestbed\** - Microsoft.Management.Configuration\** - Microsoft.Management.Configuration.UnitTests\** - Microsoft.Management.Configuration.OutOfProc\** - - - task: CopyFiles@2 - displayName: 'Copy Files: WinGetUtilInterop.UnitTests' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\src\WinGetUtilInterop.UnitTests\bin\$(buildPlatform)\$(BuildConfiguration)\net8.0' - TargetFolder: '$(artifactsDir)\WinGetUtilInterop.UnitTests\' - CleanTargetFolder: true - OverWrite: true - - - task: CopyFiles@2 - displayName: 'Copy WinGetUtil to WinGetUtilInterop.UnitTests folder' - inputs: - Contents: | - $(buildOutDir)\WinGetUtil\WinGetUtil.dll - TargetFolder: '$(artifactsDir)\WinGetUtilInterop.UnitTests\' - flattenFolders: true - - - task: CopyFiles@2 - displayName: 'Copy LocalhostWebServer to E2ETests' - inputs: - SourceFolder: '$(buildOutDir)\LocalhostWebServer' - TargetFolder: '$(artifactsDir)\E2ETests\LocalhostWebServer' - - # Invoke E2E setup to generate the TestLocalIndex; could optimize out some of its steps if needed - - template: templates/e2e-setup.yml - parameters: - sourceDir: $(Build.SourcesDirectory) - localhostWebServerArgs: '-BuildRoot $(artifactsDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(Agent.TempDirectory)\TestLocalIndex -LocalSourceJson $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\localsource.json -TestDataPath $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData -ExitBeforeRun' - signingCertOutDir: $(artifactsDir)\E2ETests - - - task: CopyFiles@2 - displayName: 'Copy TestLocalIndex' - inputs: - SourceFolder: '$(Agent.TempDirectory)\TestLocalIndex' - TargetFolder: '$(artifactsDir)\E2ETests\TestLocalIndex' - - - task: CopyFiles@2 - displayName: 'Copy TestData' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\' - TargetFolder: '$(artifactsDir)\E2ETests\TestData' - - - task: CopyFiles@2 - displayName: 'Copy Dev Package Dependencies' - inputs: - SourceFolder: '$(appxPackageDir)\AppInstallerCLIPackage_0.0.2.0_Test\Dependencies\$(buildPlatform)\' - TargetFolder: '$(artifactsDir)\E2ETests\DevPackageDependencies' - - - task: CopyFiles@2 - displayName: 'Copy test scripts to artifacts' - inputs: - Contents: | - $(Build.SourcesDirectory)\src\PowerShell\scripts\Execute-WinGetTests.ps1 - $(Build.SourcesDirectory)\src\PowerShell\tests\** - $(Build.SourcesDirectory)\src\LocalhostWebServer\Run-LocalhostWebServer.ps1 - TargetFolder: '$(artifactsDir)\E2ETests\Scripts' - flattenFolders: true - - - task: PublishPipelineArtifact@1 - displayName: Publish Pipeline Artifacts - inputs: - targetPath: '$(artifactsDir)' - artifact: 'Build.$(artifactIdentifier)' - condition: succeededOrFailed() - - - task: ComponentGovernanceComponentDetection@0 - displayName: Component Governance - inputs: - scanType: 'Register' - verbosity: 'Verbose' - alertWarningLevel: 'High' - -# Test job runs tests using build artifacts - -- job: 'Test' - timeoutInMinutes: 120 - dependsOn: 'Build' - condition: succeeded('Build') - - strategy: - matrix: - x86_release: - buildConfiguration: 'Release' - buildPlatform: 'x86' - artifactIdentifier: 'x86release' - x64_release: - buildConfiguration: 'Release' - buildPlatform: 'x64' - artifactIdentifier: 'x64release' - - variables: - buildOutDir: $(Pipeline.Workspace)\Build.$(artifactIdentifier) - artifactsDir: $(Build.ArtifactStagingDirectory) - packageLayoutDir: $(Pipeline.Workspace)\Build.$(artifactIdentifier)\DevPackage - - steps: - - task: DownloadPipelineArtifact@2 - displayName: 'Download Build Artifacts' - inputs: - artifact: 'Build.$(artifactIdentifier)' - path: '$(buildOutDir)' - - - task: PowerShell@2 - displayName: Install Tests Dependencies - inputs: - targetType: 'inline' - script: | - Get-ChildItem $(buildOutDir)\E2ETests\DevPackageDependencies -Filter *.appx | %{ Add-AppxPackage $_.FullName } - - - task: VisualStudioTestPlatformInstaller@1 - displayName: Prepare VSTest for E2E Tests - inputs: - packageFeedSelector: 'nugetOrg' - - - task: CmdLine@2 - displayName: Start HAM trace - condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) - inputs: - script: 'wpr -start $(Build.SourcesDirectory)\tools\HAMTrace\WER.HostActivityManager.wprp -filemode' - - - powershell: | - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force - Install-Module Microsoft.WinGet.Client -Repository PSGallery -Force - try { Repair-WingetPackageManager -Latest -Verbose } catch { $_.Exception } - Install-WinGetPackage -Id Microsoft.Sysinternals.PsTools -Source winget - displayName: Install Sysinternals PsTools Using Winget - condition: succeededOrFailed() - - - task: CmdLine@2 - displayName: Complete HAM trace - condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) - inputs: - script: 'wpr -stop "$(artifactsDir)\HamTrace.etl"' - - - pwsh: | - $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") - PsExec -accepteula -s -i $(buildOutDir)\AppInstallerCLITests\AppInstallerCLITests.exe -logto $(artifactsDir)\AICLI-Unpackaged-System.log -mdmpto $(artifactsDir)\AICLI-Unpackaged-System.mdmp -s -r junit -o $(artifactsDir)\TEST-AppInstallerCLI-Unpackaged-System.xml - displayName: Run Unit Tests Unpackaged Under System Context - workingDirectory: '$(buildOutDir)\AppInstallerCLITests' - condition: succeededOrFailed() - - - powershell: | - Uninstall-WinGetPackage -Id Microsoft.Sysinternals.PsTools -Source winget - displayName: Clean up Sysinternals PsTools - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: Run Unit Tests Packaged - inputs: - filePath: 'src\AppInstallerCLITests\Run-TestsInPackage.ps1' - arguments: '-Args "~[pips]" -BuildRoot $(buildOutDir) -PackageRoot $(packageLayoutDir) -LogTarget $(artifactsDir)\AICLI-Packaged.log -MdmpTarget $(artifactsDir)\AICLI-Packaged.mdmp -TestResultsTarget $(artifactsDir)\TEST-AppInstallerCLI-Packaged.xml -ScriptWait' - workingDirectory: 'src' - condition: succeededOrFailed() - - - task: PublishTestResults@2 - displayName: Publish Unit Test Results - inputs: - testResultsFormat: 'JUnit' - testResultsFiles: '$(artifactsDir)\TEST-*.xml' - failTaskOnFailedTests: true - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: 'Set program files directory' - inputs: - targetType: 'inline' - script: | - if ("$(buildPlatform)" -eq "x86") { - Write-Host "##vso[task.setvariable variable=platformProgramFiles;]${env:ProgramFiles(x86)}" - } else { - Write-Host "##vso[task.setvariable variable=platformProgramFiles;]${env:ProgramFiles}" - } - condition: succeededOrFailed() - - # Resolves resource strings utilized by InProc E2E tests. - - task: CopyFiles@2 - displayName: 'Copy resources.pri to dotnet directory' - inputs: - SourceFolder: '$(buildOutDir)\DevPackage' - TargetFolder: '$(platformProgramFiles)\dotnet' - Contents: resources.pri - condition: succeededOrFailed() - - # Winmd accessed by test runner process (dotnet.exe) - - task: CopyFiles@2 - displayName: 'Copy winmd to dotnet directory' - inputs: - SourceFolder: '$(buildOutDir)\DevPackage' - TargetFolder: '$(platformProgramFiles)\dotnet' - Contents: Microsoft.Management.Deployment.winmd - condition: succeededOrFailed() - - - template: templates/e2e-setup.yml - parameters: - sourceDir: $(Build.SourcesDirectory) - localhostWebServerArgs: '-BuildRoot $(buildOutDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(buildOutDir)\E2ETests\TestLocalIndex -SourceCert $(buildOutDir)\E2ETests\TestSigningCert.cer' - - - template: templates/e2e-test.template.yml - parameters: - title: "E2E Tests Packaged" - isPackaged: true - filter: "TestCategory!=InProcess&TestCategory!=OutOfProcess" - - - template: templates/e2e-test.template.yml - parameters: - title: "Microsoft.Management.Deployment E2E Tests (In-process)" - isPackaged: false - filter: "TestCategory=InProcess" - - - template: templates/e2e-test.template.yml - parameters: - title: "Microsoft.Management.Deployment E2E Tests (Out-of-process)" - isPackaged: true - filter: "TestCategory=OutOfProcess" - - - task: CopyFiles@2 - displayName: 'Copy E2E Tests Package Log to artifacts folder' - inputs: - SourceFolder: '$(temp)\E2ETestLogs' - TargetFolder: '$(artifactsDir)\PackagedLog' - condition: succeededOrFailed() - - - task: VSTest@2 - displayName: 'Run tests: Microsoft.WinGet.UnitTests' - inputs: - testSelector: 'testAssemblies' - testAssemblyVer2: 'Microsoft.WinGet.UnitTests.dll' - searchFolder: '$(buildOutDir)\Microsoft.WinGet.UnitTests' - codeCoverageEnabled: true - platform: '$(buildPlatform)' - configuration: '$(BuildConfiguration)' - diagnosticsEnabled: true - condition: succeededOrFailed() - - - task: VSTest@2 - displayName: 'Run tests: WinGetUtilInterop.UnitTests' - inputs: - testSelector: 'testAssemblies' - testAssemblyVer2: 'WinGetUtilInterop.UnitTests.dll' - searchFolder: '$(buildOutDir)\WinGetUtilInterop.UnitTests' - codeCoverageEnabled: true - platform: '$(buildPlatform)' - configuration: '$(BuildConfiguration)' - diagnosticsEnabled: true - condition: succeededOrFailed() - - - task: VSTest@2 - displayName: 'Run tests: Microsoft.Management.Configuration.UnitTests (InProc)' - inputs: - testRunTitle: Microsoft.Management.Configuration.UnitTests (InProc) - testSelector: 'testAssemblies' - testAssemblyVer2: '**\Microsoft.Management.Configuration.UnitTests.dll' - searchFolder: '$(buildOutDir)\Microsoft.Management.Configuration.UnitTests' - testFiltercriteria: 'Category=InProc' - codeCoverageEnabled: false - platform: '$(buildPlatform)' - configuration: '$(BuildConfiguration)' - diagnosticsEnabled: true - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: Prepare for Microsoft.Management.Configuration.UnitTests (OutOfProc) - inputs: - filePath: 'src\Microsoft.Management.Configuration.OutOfProc\Prepare-ConfigurationOOPTests.ps1' - arguments: '-BuildOutputPath $(buildOutDir) -PackageLayoutPath $(packageLayoutDir)' - condition: succeededOrFailed() - - - task: VSTest@2 - displayName: 'Run tests: Microsoft.Management.Configuration.UnitTests (OutOfProc)' - inputs: - testRunTitle: Microsoft.Management.Configuration.UnitTests (OutOfProc) - testSelector: 'testAssemblies' - testAssemblyVer2: '**\Microsoft.Management.Configuration.UnitTests.dll' - searchFolder: '$(buildOutDir)\Microsoft.Management.Configuration.UnitTests' - testFiltercriteria: 'Category=OutOfProc' - codeCoverageEnabled: true - platform: '$(buildPlatform)' - configuration: '$(BuildConfiguration)' - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: Collect logs for Microsoft.Management.Configuration.UnitTests (OutOfProc) - inputs: - filePath: 'src\Microsoft.Management.Configuration.OutOfProc\Collect-ConfigurationOOPTests.ps1' - arguments: '-TargetLocation $(artifactsDir)\ConfigOOPTestsLog' - condition: succeededOrFailed() - - - powershell: Get-Process LocalhostWebServer | Stop-Process - displayName: Stop LocalhostWebServer - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: 'Copy GA WinGet Log to artifacts folder' - inputs: - targetType: 'inline' - script: | - $source = "$env:LocalAppData\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir" - $destination = "$(artifactsDir)\GA_WinGet_Logs" - if (Test-Path $source) { - Copy-Item -Path $source -Destination $destination -Recurse -Force - } else { - Write-Host "WinGet logs not found at $source" - } - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: 'Copy Application Event Logs to Artifacts' - inputs: - targetType: 'inline' - script: | - $source = "$env:SystemRoot\System32\winevt\Logs\Application.evtx" - $destination = "$(artifactsDir)\Application.evtx" - if (Test-Path $source) { - Copy-Item -Path $source -Destination $destination -Force - } else { - Write-Host "Application event log not found at $source" - } - condition: succeededOrFailed() - - - task: PublishPipelineArtifact@1 - displayName: Publish Pipeline Artifacts - inputs: - targetPath: '$(artifactsDir)' - artifact: 'Test.$(artifactIdentifier).$(System.JobAttempt)' - condition: succeededOrFailed() - -# Build and test PowerShell module - -- job: 'BuildPowerShellModule' - timeoutInMinutes: 120 - dependsOn: 'Build' - condition: succeeded('Build') - variables: - buildOutDir: $(Pipeline.Workspace)\Build.x64Release - - steps: - - task: DownloadPipelineArtifact@2 - displayName: 'Download Build Artifacts' - - - task: CopyFiles@2 - displayName: 'Copy x64 PowerShell Binaries to Output' - inputs: - SourceFolder: '$(buildOutDir)\PowerShell' - Contents: '**\*' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: CopyFiles@2 - displayName: 'Copy x86 PowerShell Binaries to Output' - inputs: - SourceFolder: '$(Pipeline.Workspace)\Build.x86release\PowerShell' - Contents: '**\*' - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: PowerShell@2 - displayName: Generate Microsoft.WinGet.Client Help Documentation - inputs: - pwsh: true - targetType: inline - script: | - Install-Module -Name platyPS -Force - Import-Module platyPS - New-ExternalHelp -Path '$(Build.SourcesDirectory)\src\PowerShell\Help\Microsoft.WinGet.Client' -OutputPath '$(Build.ArtifactStagingDirectory)\Microsoft.WinGet.Client' - - - task: CopyFiles@2 - displayName: 'Copy Microsoft.WinGet.DSC module to staging directory' - inputs: - SourceFolder: '$(Build.SourcesDirectory)\src\PowerShell\Microsoft.WinGet.DSC' - Contents: '**\*' - TargetFolder: '$(Build.ArtifactStagingDirectory)\Microsoft.WinGet.DSC' - - - task: PowerShell@2 - displayName: Install Tests Dependencies - inputs: - targetType: 'inline' - script: | - Get-ChildItem AppxPackages\AppInstallerCLIPackage_0.0.2.0_Test\Dependencies\x64 -Filter *.appx | %{ Add-AppxPackage $_.FullName } - workingDirectory: $(buildOutDir) - - - template: templates/e2e-setup.yml - parameters: - sourceDir: $(Build.SourcesDirectory) - localhostWebServerArgs: '-BuildRoot $(buildOutDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(buildOutDir)\E2ETests\TestLocalIndex -SourceCert $(buildOutDir)\E2ETests\TestSigningCert.cer' - - - pwsh: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\PesterTest -packageLayoutPath $(buildOutDir)\DevPackage - workingDirectory: $(Build.SourcesDirectory)\src\PowerShell\tests\ - displayName: Run PowerShell 7 Tests - - - powershell: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\WPPesterTest - workingDirectory: $(Build.SourcesDirectory)\src\PowerShell\tests\ - displayName: Run Windows PowerShell Tests - condition: succeededOrFailed() - - - powershell: Get-Process LocalhostWebServer | Stop-Process - displayName: Stop LocalhostWebServer - condition: succeededOrFailed() - - - task: PublishTestResults@2 - displayName: Publish Pester Test Results PowerShell 7 - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '$(Pipeline.Workspace)\PesterTest\Test*.xml' - failTaskOnFailedTests: true - condition: succeededOrFailed() - - - task: PublishTestResults@2 - displayName: Publish Pester Test Results Windows PowerShell - inputs: - testResultsFormat: 'NUnit' - testResultsFiles: '$(Pipeline.Workspace)\WPPesterTest\Test*.xml' - failTaskOnFailedTests: true - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: Copy WinGet Logs - inputs: - pwsh: true - targetType: inline - script: | - $sourceDir = Join-Path $env:LocalAppData Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir - $destinationDir = Join-Path $(Build.ArtifactStagingDirectory) WinGetLogs - Copy-Item -Path $sourceDir -Destination $destinationDir -Recurse -Force - condition: succeededOrFailed() - - - task: PublishPipelineArtifact@1 - displayName: Publish PowerShell Module Artifacts - inputs: - targetPath: '$(Build.ArtifactStagingDirectory)' - condition: succeededOrFailed() - -- job: 'Fuzzing' - timeoutInMinutes: 60 - condition: not(eq(variables['Build.Reason'], 'PullRequest')) - - strategy: - matrix: - x64: - buildConfiguration: 'Fuzzing' - buildPlatform: 'x64' - - variables: - buildOutDir: $(Build.SourcesDirectory)\src\$(buildPlatform)\$(buildConfiguration) - artifactsDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform) - - steps: - - task: NuGetToolInstaller@1 - displayName: Install Nuget - - - task: NuGetCommand@2 - displayName: Restore Solution - inputs: - restoreSolution: '$(solution)' - - - task: CmdLine@2 - displayName: Enable Vcpkg Install - inputs: - script: | - $(VCPKG_INSTALLATION_ROOT)\vcpkg.exe integrate install - workingDirectory: '$(VCPKG_INSTALLATION_ROOT)' - - - task: VSBuild@1 - displayName: Build Fuzzing Artifacts - inputs: - platform: '$(buildPlatform)' - solution: '$(solution)' - configuration: '$(buildConfiguration)' - msbuildArgs: '/bl:$(artifactsDir)\msbuild.binlog' - maximumCpuCount: true - - - task: CopyFiles@2 - displayName: Copy vcpkg logs - inputs: - SourceFolder: $(VCPKG_INSTALLATION_ROOT)\buildtrees - Contents: '**\*.log' - TargetFolder: '$(artifactsDir)\vcpkgLogs' - condition: succeededOrFailed() - - - task: CopyFiles@2 - displayName: Copy Fuzzing Artifacts for Publishing - inputs: - SourceFolder: '$(buildOutDir)\WinGetYamlFuzzing' - Contents: '**' - TargetFolder: '$(artifactsDir)' - - - task: PublishPipelineArtifact@1 - displayName: Publish Fuzzing Artifacts - inputs: - targetPath: '$(artifactsDir)' - condition: succeededOrFailed() - - - task: onefuzz-task@0 - inputs: - onefuzzOSes: 'Windows' - env: - onefuzzDropDirectory: '$(buildOutDir)\WinGetYamlFuzzing' - SYSTEM_ACCESSTOKEN: $(System.AccessToken) +# Commit triggers +trigger: +- master + +# PR triggers +pr: + branches: + include: + - master + paths: + include: + - azure-pipelines.yml + - templates/* + - src/* + - schemas/JSON/manifests/* + +pool: + vmImage: 'windows-2025' + +variables: + solution: 'src\AppInstallerCLI.sln' + EnableDetectorVcpkg: true + +# Do not set the build version for a PR build. + +jobs: +- job: 'GetReleaseTag' + condition: not(eq(variables['Build.Reason'], 'PullRequest')) + variables: + runCodesignValidationInjection: ${{ false }} + skipComponentGovernanceDetection: ${{ true }} + steps: + - task: PowerShell@2 + name: 'GetTag' + displayName: Get Release Tag + inputs: + filePath: 'src\binver\Update-BinVer.ps1' + arguments: '-OutVar' + workingDirectory: 'src' + +# Build job creates artifacts for use in test jobs + +- job: 'Build' + timeoutInMinutes: 120 + dependsOn: 'GetReleaseTag' + condition: always() + + strategy: + matrix: + x86_release: + buildConfiguration: 'Release' + buildPlatform: 'x86' + artifactIdentifier: 'x86release' + x64_release: + buildConfiguration: 'Release' + buildPlatform: 'x64' + artifactIdentifier: 'x64release' + + variables: + BuildVer: $[counter(dependencies.GetReleaseTag.outputs['GetTag.tag'], 1)] + buildOutDir: $(Build.SourcesDirectory)\src\$(buildPlatform)\$(buildConfiguration) + buildOutDirAnyCpu: $(Build.SourcesDirectory)\src\AnyCPU\$(buildConfiguration) + artifactsDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform) + appxPackageDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform)\AppxPackages + + steps: + - task: NuGetToolInstaller@1 + displayName: Install Nuget + + # Restores all projects, including native (vcxproj) projects + - task: NuGetCommand@2 + displayName: Restore Solution + inputs: + restoreSolution: '$(solution)' + + # Restore these UAP packages as https://github.com/NuGet/Home/issues/7796 leads to all UAP packages being skipped for restore. + # Even though they don't need any actual restore action, they need the project.assets.json file to be created and a direct restore does that. + - task: NuGetCommand@2 + displayName: Restore AppInstallerCLIPackage + inputs: + restoreSolution: 'src\AppInstallerCLIPackage\AppInstallerCLIPackage.wapproj' + + - task: NuGetCommand@2 + displayName: Restore AppInstallerTestMsixInstaller + inputs: + restoreSolution: 'src\AppInstallerTestMsixInstaller\AppInstallerTestMsixInstaller.wapproj' + + # Restores only .NET core projects, but is still necessary, as without this the IndexCreationTool and LocalhostWebServer projects fail to build + - task: DotNetCoreCLI@2 + displayName: DotNet Restore + inputs: + command: 'restore' + projects: '**/*.csproj' + + - task: CmdLine@2 + displayName: Enable Vcpkg Install + inputs: + script: | + $(VCPKG_INSTALLATION_ROOT)\vcpkg.exe integrate install + workingDirectory: '$(VCPKG_INSTALLATION_ROOT)' + + - task: PowerShell@2 + displayName: Update Binary Version + condition: not(eq(variables['Build.Reason'], 'PullRequest')) + inputs: + filePath: 'src\binver\Update-BinVer.ps1' + arguments: '-TargetFile binver\binver\version.h -BuildVersion $(BuildVer)' + workingDirectory: 'src' + + # Build all solutions in the root directory. + - task: VSBuild@1 + displayName: Build Solution + inputs: + platform: '$(buildPlatform)' + solution: '$(solution)' + configuration: '$(buildConfiguration)' + msbuildArgs: '/bl:$(artifactsDir)\msbuild.binlog + /p:AppxBundlePlatforms="$(buildPlatform)" + /p:AppxPackageDir="$(appxPackageDir)" + /p:AppxBundle=Always + /p:UapAppxPackageBuildMode=SideloadOnly + /p:WingetCleanIntermediateFiles=true' + maximumCpuCount: true + + - task: MSBuild@1 + displayName: Build MSIX Test Installer File + inputs: + platform: '$(buildPlatform)' + solution: 'src\AppInstallerTestMsixInstaller\AppInstallerTestMsixInstaller.wapproj' + configuration: '$(buildConfiguration)' + msbuildArguments: '/p:AppxPackageOutput="$(Build.ArtifactStagingDirectory)\AppInstallerTestMsixInstaller.msix" + /p:AppxBundle=Never + /p:UapAppxPackageBuildMode=SideLoadOnly + /p:AppxPackageSigningEnabled=false' + maximumCpuCount: true + + - task: CopyFiles@2 + displayName: Copy vcpkg logs + inputs: + SourceFolder: $(VCPKG_INSTALLATION_ROOT)\buildtrees + Contents: '**\*.log' + TargetFolder: '$(artifactsDir)\vcpkgLogs' + condition: succeededOrFailed() + + - task: CopyFiles@2 + displayName: 'Copy specific build artifacts' + inputs: + Contents: | + $(buildOutDir)\WinGetUtil\WinGetUtil.dll + $(buildOutDir)\WinGetUtil\WinGetUtil.pdb + TargetFolder: '$(artifactsDir)' + + - task: PowerShell@2 + displayName: Create Package Layout + inputs: + filePath: 'src\AppInstallerCLIPackage\Execute-AppxRecipe.ps1' + arguments: '-AppxRecipePath AppInstallerCLIPackage\bin\$(buildPlatform)\$(buildConfiguration)\AppInstallerCLIPackage.build.appxrecipe -LayoutPath $(artifactsDir)\DevPackage -Force -Verbose' + workingDirectory: 'src' + + - task: CopyFiles@2 + displayName: 'Copy native binaries for Microsoft.WinGet.Client (net8)' + inputs: + SourceFolder: $(buildOutDir) + Contents: | + Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.dll + Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd + WindowsPackageManager\WindowsPackageManager.dll + UndockedRegFreeWinRT\winrtact.dll + TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net8.0-windows10.0.26100.0\SharedDependencies\$(BuildPlatform) + flattenFolders: true + + - task: CopyFiles@2 + displayName: 'Copy native binaries for Microsoft.WinGet.Client (net48)' + inputs: + SourceFolder: $(buildOutDir) + Contents: | + Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.dll + Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd + WindowsPackageManager\WindowsPackageManager.dll + UndockedRegFreeWinRT\winrtact.dll + TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Client\net48\SharedDependencies\$(BuildPlatform) + flattenFolders: true + + - task: CopyFiles@2 + displayName: 'Copy native binaries for Microsoft.WinGet.Configuration' + inputs: + SourceFolder: $(buildOutDir) + Contents: | + Microsoft.Management.Configuration\Microsoft.Management.Configuration.dll + TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform) + flattenFolders: true + + - task: CopyFiles@2 + displayName: 'Copy managed binaries for Microsoft.WinGet.Configuration in arch specific' + inputs: + SourceFolder: $(buildOutDirAnyCpu) + Contents: | + Microsoft.Management.Configuration.Projection\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.Projection.dll + TargetFolder: $(buildOutDirAnyCpu)\PowerShell\Microsoft.WinGet.Configuration\SharedDependencies\$(BuildPlatform) + flattenFolders: true + + - task: CopyFiles@2 + displayName: 'Copy Microsoft.WinGet.UnitTests AnyCPU Files' + inputs: + SourceFolder: '$(buildOutDirAnyCpu)\Microsoft.WinGet.UnitTests\net8.0-windows10.0.26100.0' + TargetFolder: '$(artifactsDir)\Microsoft.WinGet.UnitTests\' + CleanTargetFolder: true + OverWrite: true + + - task: CopyFiles@2 + displayName: 'Copy PowerShell AnyCPU Module Files' + inputs: + SourceFolder: '$(buildOutDirAnyCpu)\PowerShell' + TargetFolder: '$(artifactsDir)\PowerShell' + + - task: CopyFiles@2 + displayName: 'Copy binaries' + inputs: + SourceFolder: '$(buildOutDir)' + TargetFolder: '$(artifactsDir)' + Contents: | + AppInstallerCLIE2ETests\** + AppInstallerCLITests\** + ComInprocTestbed\** + Microsoft.Management.Configuration\** + Microsoft.Management.Configuration.UnitTests\** + Microsoft.Management.Configuration.OutOfProc\** + + - task: CopyFiles@2 + displayName: 'Copy Files: WinGetUtilInterop.UnitTests' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\src\WinGetUtilInterop.UnitTests\bin\$(buildPlatform)\$(BuildConfiguration)\net8.0' + TargetFolder: '$(artifactsDir)\WinGetUtilInterop.UnitTests\' + CleanTargetFolder: true + OverWrite: true + + - task: CopyFiles@2 + displayName: 'Copy WinGetUtil to WinGetUtilInterop.UnitTests folder' + inputs: + Contents: | + $(buildOutDir)\WinGetUtil\WinGetUtil.dll + TargetFolder: '$(artifactsDir)\WinGetUtilInterop.UnitTests\' + flattenFolders: true + + - task: CopyFiles@2 + displayName: 'Copy LocalhostWebServer to E2ETests' + inputs: + SourceFolder: '$(buildOutDir)\LocalhostWebServer' + TargetFolder: '$(artifactsDir)\E2ETests\LocalhostWebServer' + + # Invoke E2E setup to generate the TestLocalIndex; could optimize out some of its steps if needed + - template: templates/e2e-setup.yml + parameters: + sourceDir: $(Build.SourcesDirectory) + localhostWebServerArgs: '-BuildRoot $(artifactsDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(Agent.TempDirectory)\TestLocalIndex -LocalSourceJson $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\localsource.json -TestDataPath $(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData -ExitBeforeRun' + signingCertOutDir: $(artifactsDir)\E2ETests + + - task: CopyFiles@2 + displayName: 'Copy TestLocalIndex' + inputs: + SourceFolder: '$(Agent.TempDirectory)\TestLocalIndex' + TargetFolder: '$(artifactsDir)\E2ETests\TestLocalIndex' + + - task: CopyFiles@2 + displayName: 'Copy TestData' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\src\AppInstallerCLIE2ETests\TestData\' + TargetFolder: '$(artifactsDir)\E2ETests\TestData' + + - task: CopyFiles@2 + displayName: 'Copy Dev Package Dependencies' + inputs: + SourceFolder: '$(appxPackageDir)\AppInstallerCLIPackage_0.0.2.0_Test\Dependencies\$(buildPlatform)\' + TargetFolder: '$(artifactsDir)\E2ETests\DevPackageDependencies' + + - task: CopyFiles@2 + displayName: 'Copy test scripts to artifacts' + inputs: + Contents: | + $(Build.SourcesDirectory)\src\PowerShell\scripts\Execute-WinGetTests.ps1 + $(Build.SourcesDirectory)\src\PowerShell\tests\** + $(Build.SourcesDirectory)\src\LocalhostWebServer\Run-LocalhostWebServer.ps1 + TargetFolder: '$(artifactsDir)\E2ETests\Scripts' + flattenFolders: true + + - task: PublishPipelineArtifact@1 + displayName: Publish Pipeline Artifacts + inputs: + targetPath: '$(artifactsDir)' + artifact: 'Build.$(artifactIdentifier)' + condition: succeededOrFailed() + + - task: ComponentGovernanceComponentDetection@0 + displayName: Component Governance + inputs: + scanType: 'Register' + verbosity: 'Verbose' + alertWarningLevel: 'High' + +# Test job runs tests using build artifacts + +- job: 'Test' + timeoutInMinutes: 120 + dependsOn: 'Build' + condition: succeeded('Build') + + strategy: + matrix: + x86_release: + buildConfiguration: 'Release' + buildPlatform: 'x86' + artifactIdentifier: 'x86release' + x64_release: + buildConfiguration: 'Release' + buildPlatform: 'x64' + artifactIdentifier: 'x64release' + + variables: + buildOutDir: $(Pipeline.Workspace)\Build.$(artifactIdentifier) + artifactsDir: $(Build.ArtifactStagingDirectory) + packageLayoutDir: $(Pipeline.Workspace)\Build.$(artifactIdentifier)\DevPackage + + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifacts' + inputs: + artifact: 'Build.$(artifactIdentifier)' + path: '$(buildOutDir)' + + - task: PowerShell@2 + displayName: Install Tests Dependencies + inputs: + targetType: 'inline' + script: | + Get-ChildItem $(buildOutDir)\E2ETests\DevPackageDependencies -Filter *.appx | %{ Add-AppxPackage $_.FullName } + + - task: VisualStudioTestPlatformInstaller@1 + displayName: Prepare VSTest for E2E Tests + inputs: + packageFeedSelector: 'nugetOrg' + + - task: CmdLine@2 + displayName: Start HAM trace + condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) + inputs: + script: 'wpr -start $(Build.SourcesDirectory)\tools\HAMTrace\WER.HostActivityManager.wprp -filemode' + + - powershell: | + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force + Install-Module Microsoft.WinGet.Client -Repository PSGallery -Force + try { Repair-WingetPackageManager -Latest -Verbose } catch { $_.Exception } + Install-WinGetPackage -Id Microsoft.Sysinternals.PsTools -Source winget + displayName: Install Sysinternals PsTools Using Winget + condition: succeededOrFailed() + + - task: CmdLine@2 + displayName: Complete HAM trace + condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) + inputs: + script: 'wpr -stop "$(artifactsDir)\HamTrace.etl"' + + - pwsh: | + $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User") + PsExec -accepteula -s -i $(buildOutDir)\AppInstallerCLITests\AppInstallerCLITests.exe -logto $(artifactsDir)\AICLI-Unpackaged-System.log -mdmpto $(artifactsDir)\AICLI-Unpackaged-System.mdmp -s -r junit -o $(artifactsDir)\TEST-AppInstallerCLI-Unpackaged-System.xml + displayName: Run Unit Tests Unpackaged Under System Context + workingDirectory: '$(buildOutDir)\AppInstallerCLITests' + condition: succeededOrFailed() + + - powershell: | + Uninstall-WinGetPackage -Id Microsoft.Sysinternals.PsTools -Source winget + displayName: Clean up Sysinternals PsTools + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: Run Unit Tests Packaged + inputs: + filePath: 'src\AppInstallerCLITests\Run-TestsInPackage.ps1' + arguments: '-Args "~[pips]" -BuildRoot $(buildOutDir) -PackageRoot $(packageLayoutDir) -LogTarget $(artifactsDir)\AICLI-Packaged.log -MdmpTarget $(artifactsDir)\AICLI-Packaged.mdmp -TestResultsTarget $(artifactsDir)\TEST-AppInstallerCLI-Packaged.xml -ScriptWait' + workingDirectory: 'src' + condition: succeededOrFailed() + + - task: PublishTestResults@2 + displayName: Publish Unit Test Results + inputs: + testResultsFormat: 'JUnit' + testResultsFiles: '$(artifactsDir)\TEST-*.xml' + failTaskOnFailedTests: true + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: 'Set program files directory' + inputs: + targetType: 'inline' + script: | + if ("$(buildPlatform)" -eq "x86") { + Write-Host "##vso[task.setvariable variable=platformProgramFiles;]${env:ProgramFiles(x86)}" + } else { + Write-Host "##vso[task.setvariable variable=platformProgramFiles;]${env:ProgramFiles}" + } + condition: succeededOrFailed() + + # Resolves resource strings utilized by InProc E2E tests. + - task: CopyFiles@2 + displayName: 'Copy resources.pri to dotnet directory' + inputs: + SourceFolder: '$(buildOutDir)\DevPackage' + TargetFolder: '$(platformProgramFiles)\dotnet' + Contents: resources.pri + condition: succeededOrFailed() + + # Winmd accessed by test runner process (dotnet.exe) + - task: CopyFiles@2 + displayName: 'Copy winmd to dotnet directory' + inputs: + SourceFolder: '$(buildOutDir)\DevPackage' + TargetFolder: '$(platformProgramFiles)\dotnet' + Contents: Microsoft.Management.Deployment.winmd + condition: succeededOrFailed() + + - template: templates/e2e-setup.yml + parameters: + sourceDir: $(Build.SourcesDirectory) + localhostWebServerArgs: '-BuildRoot $(buildOutDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(buildOutDir)\E2ETests\TestLocalIndex -SourceCert $(buildOutDir)\E2ETests\TestSigningCert.cer' + + - template: templates/e2e-test.template.yml + parameters: + title: "E2E Tests Packaged" + isPackaged: true + filter: "TestCategory!=InProcess&TestCategory!=OutOfProcess" + + - template: templates/e2e-test.template.yml + parameters: + title: "Microsoft.Management.Deployment E2E Tests (In-process)" + isPackaged: false + filter: "TestCategory=InProcess" + + - template: templates/e2e-test.template.yml + parameters: + title: "Microsoft.Management.Deployment E2E Tests (Out-of-process)" + isPackaged: true + filter: "TestCategory=OutOfProcess" + + - task: CopyFiles@2 + displayName: 'Copy E2E Tests Package Log to artifacts folder' + inputs: + SourceFolder: '$(temp)\E2ETestLogs' + TargetFolder: '$(artifactsDir)\PackagedLog' + condition: succeededOrFailed() + + - task: VSTest@2 + displayName: 'Run tests: Microsoft.WinGet.UnitTests' + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'Microsoft.WinGet.UnitTests.dll' + searchFolder: '$(buildOutDir)\Microsoft.WinGet.UnitTests' + codeCoverageEnabled: true + platform: '$(buildPlatform)' + configuration: '$(BuildConfiguration)' + diagnosticsEnabled: true + condition: succeededOrFailed() + + - task: VSTest@2 + displayName: 'Run tests: WinGetUtilInterop.UnitTests' + inputs: + testSelector: 'testAssemblies' + testAssemblyVer2: 'WinGetUtilInterop.UnitTests.dll' + searchFolder: '$(buildOutDir)\WinGetUtilInterop.UnitTests' + codeCoverageEnabled: true + platform: '$(buildPlatform)' + configuration: '$(BuildConfiguration)' + diagnosticsEnabled: true + condition: succeededOrFailed() + + - task: VSTest@2 + displayName: 'Run tests: Microsoft.Management.Configuration.UnitTests (InProc)' + inputs: + testRunTitle: Microsoft.Management.Configuration.UnitTests (InProc) + testSelector: 'testAssemblies' + testAssemblyVer2: '**\Microsoft.Management.Configuration.UnitTests.dll' + searchFolder: '$(buildOutDir)\Microsoft.Management.Configuration.UnitTests' + testFiltercriteria: 'Category=InProc' + codeCoverageEnabled: false + platform: '$(buildPlatform)' + configuration: '$(BuildConfiguration)' + diagnosticsEnabled: true + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: Prepare for Microsoft.Management.Configuration.UnitTests (OutOfProc) + inputs: + filePath: 'src\Microsoft.Management.Configuration.OutOfProc\Prepare-ConfigurationOOPTests.ps1' + arguments: '-BuildOutputPath $(buildOutDir) -PackageLayoutPath $(packageLayoutDir)' + condition: succeededOrFailed() + + - task: VSTest@2 + displayName: 'Run tests: Microsoft.Management.Configuration.UnitTests (OutOfProc)' + inputs: + testRunTitle: Microsoft.Management.Configuration.UnitTests (OutOfProc) + testSelector: 'testAssemblies' + testAssemblyVer2: '**\Microsoft.Management.Configuration.UnitTests.dll' + searchFolder: '$(buildOutDir)\Microsoft.Management.Configuration.UnitTests' + testFiltercriteria: 'Category=OutOfProc' + codeCoverageEnabled: true + platform: '$(buildPlatform)' + configuration: '$(BuildConfiguration)' + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: Collect logs for Microsoft.Management.Configuration.UnitTests (OutOfProc) + inputs: + filePath: 'src\Microsoft.Management.Configuration.OutOfProc\Collect-ConfigurationOOPTests.ps1' + arguments: '-TargetLocation $(artifactsDir)\ConfigOOPTestsLog' + condition: succeededOrFailed() + + - powershell: Get-Process LocalhostWebServer | Stop-Process + displayName: Stop LocalhostWebServer + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: 'Copy GA WinGet Log to artifacts folder' + inputs: + targetType: 'inline' + script: | + $source = "$env:LocalAppData\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir" + $destination = "$(artifactsDir)\GA_WinGet_Logs" + if (Test-Path $source) { + Copy-Item -Path $source -Destination $destination -Recurse -Force + } else { + Write-Host "WinGet logs not found at $source" + } + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: 'Copy Application Event Logs to Artifacts' + inputs: + targetType: 'inline' + script: | + $source = "$env:SystemRoot\System32\winevt\Logs\Application.evtx" + $destination = "$(artifactsDir)\Application.evtx" + if (Test-Path $source) { + Copy-Item -Path $source -Destination $destination -Force + } else { + Write-Host "Application event log not found at $source" + } + condition: succeededOrFailed() + + - task: PublishPipelineArtifact@1 + displayName: Publish Pipeline Artifacts + inputs: + targetPath: '$(artifactsDir)' + artifact: 'Test.$(artifactIdentifier).$(System.JobAttempt)' + condition: succeededOrFailed() + +# Build and test PowerShell module + +- job: 'BuildPowerShellModule' + timeoutInMinutes: 120 + dependsOn: 'Build' + condition: succeeded('Build') + variables: + buildOutDir: $(Pipeline.Workspace)\Build.x64Release + + steps: + - task: DownloadPipelineArtifact@2 + displayName: 'Download Build Artifacts' + + - task: CopyFiles@2 + displayName: 'Copy x64 PowerShell Binaries to Output' + inputs: + SourceFolder: '$(buildOutDir)\PowerShell' + Contents: '**\*' + TargetFolder: '$(Build.ArtifactStagingDirectory)' + + - task: CopyFiles@2 + displayName: 'Copy x86 PowerShell Binaries to Output' + inputs: + SourceFolder: '$(Pipeline.Workspace)\Build.x86release\PowerShell' + Contents: '**\*' + TargetFolder: '$(Build.ArtifactStagingDirectory)' + + - task: PowerShell@2 + displayName: Generate Microsoft.WinGet.Client Help Documentation + inputs: + pwsh: true + targetType: inline + script: | + Install-Module -Name platyPS -Force + Import-Module platyPS + New-ExternalHelp -Path '$(Build.SourcesDirectory)\src\PowerShell\Help\Microsoft.WinGet.Client' -OutputPath '$(Build.ArtifactStagingDirectory)\Microsoft.WinGet.Client' + + - task: CopyFiles@2 + displayName: 'Copy Microsoft.WinGet.DSC module to staging directory' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\src\PowerShell\Microsoft.WinGet.DSC' + Contents: '**\*' + TargetFolder: '$(Build.ArtifactStagingDirectory)\Microsoft.WinGet.DSC' + + - task: PowerShell@2 + displayName: Install Tests Dependencies + inputs: + targetType: 'inline' + script: | + Get-ChildItem AppxPackages\AppInstallerCLIPackage_0.0.2.0_Test\Dependencies\x64 -Filter *.appx | %{ Add-AppxPackage $_.FullName } + workingDirectory: $(buildOutDir) + + - template: templates/e2e-setup.yml + parameters: + sourceDir: $(Build.SourcesDirectory) + localhostWebServerArgs: '-BuildRoot $(buildOutDir)\E2ETests\LocalhostWebServer -StaticFileRoot $(buildOutDir)\E2ETests\TestLocalIndex -SourceCert $(buildOutDir)\E2ETests\TestSigningCert.cer' + + - pwsh: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\PesterTest -packageLayoutPath $(buildOutDir)\DevPackage + workingDirectory: $(Build.SourcesDirectory)\src\PowerShell\tests\ + displayName: Run PowerShell 7 Tests + + - powershell: .\RunTests.ps1 -testModulesPath $(Build.ArtifactStagingDirectory) -outputPath $(Pipeline.Workspace)\WPPesterTest + workingDirectory: $(Build.SourcesDirectory)\src\PowerShell\tests\ + displayName: Run Windows PowerShell Tests + condition: succeededOrFailed() + + - powershell: Get-Process LocalhostWebServer | Stop-Process + displayName: Stop LocalhostWebServer + condition: succeededOrFailed() + + - task: PublishTestResults@2 + displayName: Publish Pester Test Results PowerShell 7 + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(Pipeline.Workspace)\PesterTest\Test*.xml' + failTaskOnFailedTests: true + condition: succeededOrFailed() + + - task: PublishTestResults@2 + displayName: Publish Pester Test Results Windows PowerShell + inputs: + testResultsFormat: 'NUnit' + testResultsFiles: '$(Pipeline.Workspace)\WPPesterTest\Test*.xml' + failTaskOnFailedTests: true + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: Copy WinGet Logs + inputs: + pwsh: true + targetType: inline + script: | + $sourceDir = Join-Path $env:LocalAppData Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir + $destinationDir = Join-Path $(Build.ArtifactStagingDirectory) WinGetLogs + Copy-Item -Path $sourceDir -Destination $destinationDir -Recurse -Force + condition: succeededOrFailed() + + - task: PublishPipelineArtifact@1 + displayName: Publish PowerShell Module Artifacts + inputs: + targetPath: '$(Build.ArtifactStagingDirectory)' + condition: succeededOrFailed() + +- job: 'Fuzzing' + timeoutInMinutes: 60 + condition: not(eq(variables['Build.Reason'], 'PullRequest')) + + strategy: + matrix: + x64: + buildConfiguration: 'Fuzzing' + buildPlatform: 'x64' + + variables: + buildOutDir: $(Build.SourcesDirectory)\src\$(buildPlatform)\$(buildConfiguration) + artifactsDir: $(Build.ArtifactStagingDirectory)\$(buildPlatform) + + steps: + - task: NuGetToolInstaller@1 + displayName: Install Nuget + + - task: NuGetCommand@2 + displayName: Restore Solution + inputs: + restoreSolution: '$(solution)' + + - task: CmdLine@2 + displayName: Enable Vcpkg Install + inputs: + script: | + $(VCPKG_INSTALLATION_ROOT)\vcpkg.exe integrate install + workingDirectory: '$(VCPKG_INSTALLATION_ROOT)' + + - task: VSBuild@1 + displayName: Build Fuzzing Artifacts + inputs: + platform: '$(buildPlatform)' + solution: '$(solution)' + configuration: '$(buildConfiguration)' + msbuildArgs: '/bl:$(artifactsDir)\msbuild.binlog' + maximumCpuCount: true + + - task: CopyFiles@2 + displayName: Copy vcpkg logs + inputs: + SourceFolder: $(VCPKG_INSTALLATION_ROOT)\buildtrees + Contents: '**\*.log' + TargetFolder: '$(artifactsDir)\vcpkgLogs' + condition: succeededOrFailed() + + - task: CopyFiles@2 + displayName: Copy Fuzzing Artifacts for Publishing + inputs: + SourceFolder: '$(buildOutDir)\WinGetYamlFuzzing' + Contents: '**' + TargetFolder: '$(artifactsDir)' + + - task: PublishPipelineArtifact@1 + displayName: Publish Fuzzing Artifacts + inputs: + targetPath: '$(artifactsDir)' + condition: succeededOrFailed() + + - task: onefuzz-task@0 + inputs: + onefuzzOSes: 'Windows' + env: + onefuzzDropDirectory: '$(buildOutDir)\WinGetYamlFuzzing' + SYSTEM_ACCESSTOKEN: $(System.AccessToken) diff --git a/cgmanifest.json b/cgmanifest.json index fc5c3c3447..a8759663f5 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -1,51 +1,51 @@ -{ - "$schema": "https://json.schemastore.org/component-detection-manifest.json", - "Registrations": [ - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/microsoft/cpprestsdk.git", - "commitHash": "411a109150b270f23c8c97fa4ec9a0a4a98cdecf" - } - } - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/ronomon/pure.git", - "commitHash": "fd54913e65338e678440ae66b3b5022ab23b761b" - } - } - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/microsoft/xlang", - "commitHash": "cfe510d0d2b07484fea2c6d77163de017738c100" - } - } - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/microsoft/sfs-client.git", - "commitHash": "0e27525d597c730e71646fd0b15bdc8c8503f24d" - } - } - }, - { - "component": { - "type": "git", - "git": { - "repositoryUrl": "https://github.com/yaml/libyaml.git", - "commitHash": "840b65c40675e2d06bf40405ad3f12dec7f35923" - } - } - } - ], - "Version": 1 -} +{ + "$schema": "https://json.schemastore.org/component-detection-manifest.json", + "Registrations": [ + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/microsoft/cpprestsdk.git", + "commitHash": "411a109150b270f23c8c97fa4ec9a0a4a98cdecf" + } + } + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/ronomon/pure.git", + "commitHash": "fd54913e65338e678440ae66b3b5022ab23b761b" + } + } + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/microsoft/xlang", + "commitHash": "cfe510d0d2b07484fea2c6d77163de017738c100" + } + } + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/microsoft/sfs-client.git", + "commitHash": "0e27525d597c730e71646fd0b15bdc8c8503f24d" + } + } + }, + { + "component": { + "type": "git", + "git": { + "repositoryUrl": "https://github.com/yaml/libyaml.git", + "commitHash": "840b65c40675e2d06bf40405ad3f12dec7f35923" + } + } + } + ], + "Version": 1 +} diff --git a/doc/admx/DesktopAppInstaller.admx b/doc/admx/DesktopAppInstaller.admx index 72ca46953a..b62d5bde1b 100644 --- a/doc/admx/DesktopAppInstaller.admx +++ b/doc/admx/DesktopAppInstaller.admx @@ -1,299 +1,299 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/doc/admx/en-US/DesktopAppInstaller.adml b/doc/admx/en-US/DesktopAppInstaller.adml index 200c8e3c34..2f8dff5150 100644 --- a/doc/admx/en-US/DesktopAppInstaller.adml +++ b/doc/admx/en-US/DesktopAppInstaller.adml @@ -1,191 +1,191 @@ - - - - App Installer - App Installer - - - Desktop App Installer - Enable Windows Package Manager - This policy controls whether the Windows Package Manager can be used by users. - -If you enable or do not configure this setting, users will be able to use the Windows Package Manager. - -If you disable this setting, users will not be able to use the Windows Package Manager. - Enable Windows Package Manager Settings - This policy controls whether users can change their settings. - -If you enable or do not configure this setting, users will be able to change settings for the Windows Package Manager. - -If you disable this setting, users will not be able to change settings for the Windows Package Manager. - Enable Windows Package Manager Experimental Features - This policy controls whether users can enable experimental features in the Windows Package Manager. - -If you enable or do not configure this setting, users will be able to enable experimental features for the Windows Package Manager. - -If you disable this setting, users will not be able to enable experimental features for the Windows Package Manager. - Enable Windows Package Manager Local Manifest Files - This policy controls whether users can install packages with local manifest files. - -If you enable or do not configure this setting, users will be able to install packages with local manifests using the Windows Package Manager. - -If you disable this setting, users will not be able to install packages with local manifests using the Windows Package Manager. - Enable Windows Package Manager Microsoft Store Source Certificate Validation Bypass - This policy controls whether the Windows Package Manager will validate the Microsoft Store certificate hash matches to a known Microsoft Store certificate when initiating a connection to the Microsoft Store Source. -If you enable this policy, the Windows Package Manager will bypass the Microsoft Store certificate validation. - -If you disable this policy, the Windows Package Manager will validate the Microsoft Store certificate used is valid and belongs to the Microsoft Store before communicating with the Microsoft Store source. - -If you do not configure this policy, the Windows Package Manager administrator settings will be adhered to. - Enable Windows Package Manager Hash Override - This policy controls whether or not the Windows Package Manager can be configured to enable the ability override the SHA256 security validation in settings. - -If you enable or do not configure this policy, users will be able to enable the ability override the SHA256 security validation in the Windows Package Manager settings. - -If you disable this policy, users will not be able to enable the ability override the SHA256 security validation in the Windows Package Manager settings. - Enable Windows Package Manager Local Archive Malware Scan Override - This policy controls the ability to override malware vulnerability scans when installing an archive file using a local manifest using the command line arguments. -If you enable this policy, users can override the malware scan when performing a local manifest install of an archive file. - -If you disable this policy, users will be unable to override the malware scan of an archive file when installing using a local manifest. - -If you do not configure this policy, the Windows Package Manager administrator settings will be adhered to. - Enable Windows Package Manager Default Source - This policy controls the default source included with the Windows Package Manager. - -If you do not configure this setting, the default source for the Windows Package Manager will be available and can be removed. - -If you enable this setting, the default source for the Windows Package Manager will be available and cannot be removed. - -If you disable this setting the default source for the Windows Package Manager will not be available. - Enable Windows Package Manager Microsoft Store Source - This policy controls the Microsoft Store source included with the Windows Package Manager. - -If you do not configure this setting, the Microsoft Store source for the Windows Package manager will be available and can be removed. - -If you enable this setting, the Microsoft Store source for the Windows Package Manager will be available and cannot be removed. - -If you disable this setting the Microsoft Store source for the Windows Package Manager will not be available. - Enable Windows Package Manager Font Source - - This policy controls the Font source included with the Windows Package Manager. - - If you do not configure this setting, the Font source for the Windows Package manager will be available and can be removed. - - If you enable this setting, the Font source for the Windows Package Manager will be available and cannot be removed. - - If you disable this setting the Font source for the Windows Package Manager will not be available. - - Set Windows Package Manager Source Auto Update Interval In Minutes - This policy controls the auto-update interval for package-based sources. The default source for Windows Package Manager is configured such that an index of the packages is cached on the local machine. The index is downloaded when a user invokes a command, and the interval has passed. - -If you disable or do not configure this setting, the default interval or the value specified in the Windows Package Manager settings will be used. - -If you enable this setting, the number of minutes specified will be used by the Windows Package Manager. - Enable Windows Package Manager Additional Sources - This policy controls additional sources provided by the enterprise IT administrator. - -If you do not configure this policy, no additional sources will be configured for the Windows Package Manager. - -If you enable this policy, the additional sources will be added to the Windows Package Manager and cannot be removed. The representation for each additional source can be obtained from installed sources using 'winget source export'. - -If you disable this policy, no additional sources can be configured for the Windows Package Manager. - Enable Windows Package Manager Allowed Sources - This policy controls additional sources allowed by the enterprise IT administrator. - -If you do not configure this policy, users will be able to add or remove additional sources other than those configured by policy. - -If you enable this policy, only the sources specified can be added or removed from the Windows Package Manager. The representation for each allowed source can be obtained from installed sources using 'winget source export'. - -If you disable this policy, no additional sources can be configured for the Windows Package Manager. - Enable App Installer ms-appinstaller protocol - This policy controls whether users can install packages from a website that is using the ms-appinstaller protocol. - -If you enable this setting, users will be able to install packages from websites that use this protocol. - -If you disable or do not configure this setting, users will not be able to install packages from websites that use this protocol. - Enable Windows Package Manager command line interfaces - This policy determines if a user can perform an action using the Windows Package Manager through a command line interface (WinGet CLI, or WinGet PowerShell). - - If you disable this policy, users will not be able execute the Windows Package Manager CLI, and PowerShell cmdlets. - - If you enable, or do not configure this policy, users will be able to execute the Windows Package Manager CLI commands, and PowerShell cmdlets. (Provided “Enable App Installer” policy is not disabled). - - This policy does not override the “Enable App Installer” policy. - Enable Windows Package Manager Configuration - This policy controls whether the Windows Package Manager configuration feature can be used by users. - -If you enable or do not configure this setting, users will be able to use the Windows Package Manager configuration feature. - -If you disable this setting, users will not be able to use the Windows Package Manager configuration feature. - Enable Windows Package Manager Proxy command line options - - This policy controls whether the Windows Package Manager usage of proxy can be configured by users through the command line. - - If you enable this setting, users will be able to configure the Windows Package Manager's use of proxy through the command line. - - If you disable or do not configure this setting, users will not be able to to configure the Windows Package Manager's use of proxy through the command line. - Enable MCP Server for Windows Package Manager - - This policy controls whether the Windows Package Manager Model Context Protocol (MCP) server can be used. - - If you enable or do not configure this setting, users will be able to use the Windows Package Manager's MCP server. - - If you disable this setting, users will not be able to to use the Windows Package Manager's MCP server. - Enable Windows Package Manager Configuration Processor Override - - This policy controls whether users can specify a custom DSC processor path via the --processor-path argument in Windows Package Manager configuration commands. - - If you enable this setting, users will be able to specify a custom DSC processor path in configuration commands. - - If you do not configure this setting, users will be able to specify a custom DSC processor path in configuration commands after enabling the related administrator setting. - - If you disable this setting, users will not be able to specify a custom DSC processor path in configuration commands. - Set Windows Package Manager Default Proxy - This policy controls the default proxy used by the Windows Package Manager. - -If you disable or do not configure this setting, no proxy will be used by default. - -If you enable this setting, the specified proxy will be used by default. - Enable App Installer Allowed Zones for MSIX Packages - This policy controls whether App Installer allows installing packages originating from specific URL Zones. A package's origin is determined by its URI and whether a Mart-of-the-Web (MotW) is present. If multiple URIs are involved, all of them are considered; for example, when using a .appinstaller file that involves redirection. - -If you enable this policy, users will be able to install MSIX packages according to the configuration for each zone. - -If you disable or do not configure this policy, users will be able to install MSIX packages from any zone except for Untrusted. - Allow - Block - Enable Microsoft SmartScreen checks for MSIX Packages - This policy controls whether App Installer performs Microsoft SmartScreen checks when installing MSIX packages. - -If you enable or do not configure this policy, the package URI will be evaluated with Microsoft SmartScreen before installation. This check is only done for packages that come from the internet. - -If you disable, Microsoft SmartScreen will not be consulted before installing a package. - - - - Source Auto Update Interval In Minutes - - - Additional Sources: - - - Allowed Sources: - - - - - - - - Local Machine - Intranet - Trusted Sites - Internet - Untrusted Sites - - - - - + + + + App Installer + App Installer + + + Desktop App Installer + Enable Windows Package Manager + This policy controls whether the Windows Package Manager can be used by users. + +If you enable or do not configure this setting, users will be able to use the Windows Package Manager. + +If you disable this setting, users will not be able to use the Windows Package Manager. + Enable Windows Package Manager Settings + This policy controls whether users can change their settings. + +If you enable or do not configure this setting, users will be able to change settings for the Windows Package Manager. + +If you disable this setting, users will not be able to change settings for the Windows Package Manager. + Enable Windows Package Manager Experimental Features + This policy controls whether users can enable experimental features in the Windows Package Manager. + +If you enable or do not configure this setting, users will be able to enable experimental features for the Windows Package Manager. + +If you disable this setting, users will not be able to enable experimental features for the Windows Package Manager. + Enable Windows Package Manager Local Manifest Files + This policy controls whether users can install packages with local manifest files. + +If you enable or do not configure this setting, users will be able to install packages with local manifests using the Windows Package Manager. + +If you disable this setting, users will not be able to install packages with local manifests using the Windows Package Manager. + Enable Windows Package Manager Microsoft Store Source Certificate Validation Bypass + This policy controls whether the Windows Package Manager will validate the Microsoft Store certificate hash matches to a known Microsoft Store certificate when initiating a connection to the Microsoft Store Source. +If you enable this policy, the Windows Package Manager will bypass the Microsoft Store certificate validation. + +If you disable this policy, the Windows Package Manager will validate the Microsoft Store certificate used is valid and belongs to the Microsoft Store before communicating with the Microsoft Store source. + +If you do not configure this policy, the Windows Package Manager administrator settings will be adhered to. + Enable Windows Package Manager Hash Override + This policy controls whether or not the Windows Package Manager can be configured to enable the ability override the SHA256 security validation in settings. + +If you enable or do not configure this policy, users will be able to enable the ability override the SHA256 security validation in the Windows Package Manager settings. + +If you disable this policy, users will not be able to enable the ability override the SHA256 security validation in the Windows Package Manager settings. + Enable Windows Package Manager Local Archive Malware Scan Override + This policy controls the ability to override malware vulnerability scans when installing an archive file using a local manifest using the command line arguments. +If you enable this policy, users can override the malware scan when performing a local manifest install of an archive file. + +If you disable this policy, users will be unable to override the malware scan of an archive file when installing using a local manifest. + +If you do not configure this policy, the Windows Package Manager administrator settings will be adhered to. + Enable Windows Package Manager Default Source + This policy controls the default source included with the Windows Package Manager. + +If you do not configure this setting, the default source for the Windows Package Manager will be available and can be removed. + +If you enable this setting, the default source for the Windows Package Manager will be available and cannot be removed. + +If you disable this setting the default source for the Windows Package Manager will not be available. + Enable Windows Package Manager Microsoft Store Source + This policy controls the Microsoft Store source included with the Windows Package Manager. + +If you do not configure this setting, the Microsoft Store source for the Windows Package manager will be available and can be removed. + +If you enable this setting, the Microsoft Store source for the Windows Package Manager will be available and cannot be removed. + +If you disable this setting the Microsoft Store source for the Windows Package Manager will not be available. + Enable Windows Package Manager Font Source + + This policy controls the Font source included with the Windows Package Manager. + + If you do not configure this setting, the Font source for the Windows Package manager will be available and can be removed. + + If you enable this setting, the Font source for the Windows Package Manager will be available and cannot be removed. + + If you disable this setting the Font source for the Windows Package Manager will not be available. + + Set Windows Package Manager Source Auto Update Interval In Minutes + This policy controls the auto-update interval for package-based sources. The default source for Windows Package Manager is configured such that an index of the packages is cached on the local machine. The index is downloaded when a user invokes a command, and the interval has passed. + +If you disable or do not configure this setting, the default interval or the value specified in the Windows Package Manager settings will be used. + +If you enable this setting, the number of minutes specified will be used by the Windows Package Manager. + Enable Windows Package Manager Additional Sources + This policy controls additional sources provided by the enterprise IT administrator. + +If you do not configure this policy, no additional sources will be configured for the Windows Package Manager. + +If you enable this policy, the additional sources will be added to the Windows Package Manager and cannot be removed. The representation for each additional source can be obtained from installed sources using 'winget source export'. + +If you disable this policy, no additional sources can be configured for the Windows Package Manager. + Enable Windows Package Manager Allowed Sources + This policy controls additional sources allowed by the enterprise IT administrator. + +If you do not configure this policy, users will be able to add or remove additional sources other than those configured by policy. + +If you enable this policy, only the sources specified can be added or removed from the Windows Package Manager. The representation for each allowed source can be obtained from installed sources using 'winget source export'. + +If you disable this policy, no additional sources can be configured for the Windows Package Manager. + Enable App Installer ms-appinstaller protocol + This policy controls whether users can install packages from a website that is using the ms-appinstaller protocol. + +If you enable this setting, users will be able to install packages from websites that use this protocol. + +If you disable or do not configure this setting, users will not be able to install packages from websites that use this protocol. + Enable Windows Package Manager command line interfaces + This policy determines if a user can perform an action using the Windows Package Manager through a command line interface (WinGet CLI, or WinGet PowerShell). + + If you disable this policy, users will not be able execute the Windows Package Manager CLI, and PowerShell cmdlets. + + If you enable, or do not configure this policy, users will be able to execute the Windows Package Manager CLI commands, and PowerShell cmdlets. (Provided “Enable App Installer” policy is not disabled). + + This policy does not override the “Enable App Installer” policy. + Enable Windows Package Manager Configuration + This policy controls whether the Windows Package Manager configuration feature can be used by users. + +If you enable or do not configure this setting, users will be able to use the Windows Package Manager configuration feature. + +If you disable this setting, users will not be able to use the Windows Package Manager configuration feature. + Enable Windows Package Manager Proxy command line options + + This policy controls whether the Windows Package Manager usage of proxy can be configured by users through the command line. + + If you enable this setting, users will be able to configure the Windows Package Manager's use of proxy through the command line. + + If you disable or do not configure this setting, users will not be able to to configure the Windows Package Manager's use of proxy through the command line. + Enable MCP Server for Windows Package Manager + + This policy controls whether the Windows Package Manager Model Context Protocol (MCP) server can be used. + + If you enable or do not configure this setting, users will be able to use the Windows Package Manager's MCP server. + + If you disable this setting, users will not be able to to use the Windows Package Manager's MCP server. + Enable Windows Package Manager Configuration Processor Override + + This policy controls whether users can specify a custom DSC processor path via the --processor-path argument in Windows Package Manager configuration commands. + + If you enable this setting, users will be able to specify a custom DSC processor path in configuration commands. + + If you do not configure this setting, users will be able to specify a custom DSC processor path in configuration commands after enabling the related administrator setting. + + If you disable this setting, users will not be able to specify a custom DSC processor path in configuration commands. + Set Windows Package Manager Default Proxy + This policy controls the default proxy used by the Windows Package Manager. + +If you disable or do not configure this setting, no proxy will be used by default. + +If you enable this setting, the specified proxy will be used by default. + Enable App Installer Allowed Zones for MSIX Packages + This policy controls whether App Installer allows installing packages originating from specific URL Zones. A package's origin is determined by its URI and whether a Mart-of-the-Web (MotW) is present. If multiple URIs are involved, all of them are considered; for example, when using a .appinstaller file that involves redirection. + +If you enable this policy, users will be able to install MSIX packages according to the configuration for each zone. + +If you disable or do not configure this policy, users will be able to install MSIX packages from any zone except for Untrusted. + Allow + Block + Enable Microsoft SmartScreen checks for MSIX Packages + This policy controls whether App Installer performs Microsoft SmartScreen checks when installing MSIX packages. + +If you enable or do not configure this policy, the package URI will be evaluated with Microsoft SmartScreen before installation. This check is only done for packages that come from the internet. + +If you disable, Microsoft SmartScreen will not be consulted before installing a package. + + + + Source Auto Update Interval In Minutes + + + Additional Sources: + + + Allowed Sources: + + + + + + + + Local Machine + Intranet + Trusted Sites + Internet + Untrusted Sites + + + + + diff --git a/doc/specs/#1012 - Show dependencies.md b/doc/specs/#1012 - Show dependencies.md index 8dc05e9983..3d2897588d 100644 --- a/doc/specs/#1012 - Show dependencies.md +++ b/doc/specs/#1012 - Show dependencies.md @@ -1,133 +1,133 @@ ---- -author: Florencia Zanollo fzanollo/t-fzanollo@microsoft.com -created on: 2021-05-28 -last updated: 2021-05-28 -issue id: 1012 ---- - -# Show dependencies - -For [#1012](https://github.com/microsoft/winget-cli/issues/1012) - -## Abstract -Several packages require other packages as dependencies. The Windows Package Manager should be able to support declared dependencies and, as a first step to manage them, inform the user about any required ones. - -## Solution Design -The Windows Package Manager should be able to report package dependency information for each of the four different types of dependencies declared in the [v1.0 manifest schemas](https://github.com/microsoft/winget-cli/blob/master/schemas/JSON/manifests/v1.0.0/). - -* Windows Features -* Windows Libraries -* Package Dependencies (same source) -* External Dependencies - -Only dependencies declared in the manifest/installer will be shown, not the entire dependency graph. - -### install -The install command will enumerate the dependencies of the package version being installed as follows: -``` -> winget install Notepad++ -Found Notepad++ [Notepad++.Notepad++] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -This package requires the following dependencies: - - Windows Feature: - Hyper-V - - Package: - Microsoft.WindowsTerminal -Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe -Successfully verified installer hash -Starting package install... -``` - -### show -The show command will enumerate the dependencies of the package as follows: -``` -> winget show Notepad++ -Found Notepad++ [Notepad++.Notepad++] -Version: 7.9.5 -Publisher: Notepad++ Team -Author: Don Ho -Moniker: notepad++ -Description: Notepad++ is a free (as in “free speech” and also as in “free beer”) source code editor and Notepad replacement that supports several languages. Running in the MS Windows environment, its use is governed by GNU General Public License. -Homepage: https://notepad-plus-plus.org/ -License: GPL-2.0-only -License Url: https://raw.githubusercontent.com/notepad-plus-plus/notepad-plus-plus/v7.9.5/LICENSE -Installer: - Type: Nullsoft - Locale: en-US - Download Url: https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe - SHA256: 4881548cd86491b453520e83c19292c93b9c6ce485a1f9eb9301e3913a9baced - Dependencies: - - Windows Feature: - Hyper-V - - Package: - Microsoft.WindowsTerminal -``` - -### upgrade -The upgrade command will enumerate the dependencies of the package as follows: -``` -> winget upgrade Notepad++ -Found Notepad++ [Notepad++.Notepad++] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -This package requires the following dependencies: - - Windows Feature: - Hyper-V - - Package: - Microsoft.WindowsTerminal -Successfully verified installer hash -Starting package install... -``` -As of now, it will not try to validate nor install any of the dependencies for any package version. - -### uninstall -Uninstall needs more work as we don't have the actual installer to get the dependencies from. This will not be added in this step. - -### validate -Will gather and report dependencies for all of the installers found. Will not check if they are valid nor if there are duplicates. -``` -Manifest has the following dependencies that were not validated; ensure that they are valid: - - Windows Feature: - Hyper-V - - Package: - Microsoft.WindowsTerminal -Manifest validation succeeded. -``` - -### import -Will gather all the dependencies from the packages included in the import and show them together before starting. -``` -The packages found in this import have the following dependencies: - - Windows Feature: - Hyper-V - Containers - - Windows Libraries: - Microsoft.WinJS - - Package: - Microsoft.WindowsTerminal - - External: - JDK-11.0.10 -Found [Notepad++.Notepad++] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8/npp.8.0.Installer.x64.exe -Successfully verified installer hash -Starting package install... -Successfully installed -Found [plex.Plex] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -Successfully verified installer hash -Starting package install... -Successfully installed -``` - -## Capabilities -It's only an informational feature, will not check if the dependency is a valid one, nor if the source is available. -If a dependency is declared more than once (for example when gathering all dependencies in an import) it will only show the highest minimum version needed. - -Keep in mind dependencies can be declared on the root manifest and on each of the installers. If they happen to be declared in both, installer's dependencies will override those of the manifest. With the manifest's dependencies working as a default whenever installer's dependencies are not declared. - -## Future considerations -It may be able to enable/disable this feature using extra options for the command. +--- +author: Florencia Zanollo fzanollo/t-fzanollo@microsoft.com +created on: 2021-05-28 +last updated: 2021-05-28 +issue id: 1012 +--- + +# Show dependencies + +For [#1012](https://github.com/microsoft/winget-cli/issues/1012) + +## Abstract +Several packages require other packages as dependencies. The Windows Package Manager should be able to support declared dependencies and, as a first step to manage them, inform the user about any required ones. + +## Solution Design +The Windows Package Manager should be able to report package dependency information for each of the four different types of dependencies declared in the [v1.0 manifest schemas](https://github.com/microsoft/winget-cli/blob/master/schemas/JSON/manifests/v1.0.0/). + +* Windows Features +* Windows Libraries +* Package Dependencies (same source) +* External Dependencies + +Only dependencies declared in the manifest/installer will be shown, not the entire dependency graph. + +### install +The install command will enumerate the dependencies of the package version being installed as follows: +``` +> winget install Notepad++ +Found Notepad++ [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +This package requires the following dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe +Successfully verified installer hash +Starting package install... +``` + +### show +The show command will enumerate the dependencies of the package as follows: +``` +> winget show Notepad++ +Found Notepad++ [Notepad++.Notepad++] +Version: 7.9.5 +Publisher: Notepad++ Team +Author: Don Ho +Moniker: notepad++ +Description: Notepad++ is a free (as in “free speech” and also as in “free beer”) source code editor and Notepad replacement that supports several languages. Running in the MS Windows environment, its use is governed by GNU General Public License. +Homepage: https://notepad-plus-plus.org/ +License: GPL-2.0-only +License Url: https://raw.githubusercontent.com/notepad-plus-plus/notepad-plus-plus/v7.9.5/LICENSE +Installer: + Type: Nullsoft + Locale: en-US + Download Url: https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe + SHA256: 4881548cd86491b453520e83c19292c93b9c6ce485a1f9eb9301e3913a9baced + Dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +``` + +### upgrade +The upgrade command will enumerate the dependencies of the package as follows: +``` +> winget upgrade Notepad++ +Found Notepad++ [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +This package requires the following dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Successfully verified installer hash +Starting package install... +``` +As of now, it will not try to validate nor install any of the dependencies for any package version. + +### uninstall +Uninstall needs more work as we don't have the actual installer to get the dependencies from. This will not be added in this step. + +### validate +Will gather and report dependencies for all of the installers found. Will not check if they are valid nor if there are duplicates. +``` +Manifest has the following dependencies that were not validated; ensure that they are valid: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Manifest validation succeeded. +``` + +### import +Will gather all the dependencies from the packages included in the import and show them together before starting. +``` +The packages found in this import have the following dependencies: + - Windows Feature: + Hyper-V + Containers + - Windows Libraries: + Microsoft.WinJS + - Package: + Microsoft.WindowsTerminal + - External: + JDK-11.0.10 +Found [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v8/npp.8.0.Installer.x64.exe +Successfully verified installer hash +Starting package install... +Successfully installed +Found [plex.Plex] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Successfully verified installer hash +Starting package install... +Successfully installed +``` + +## Capabilities +It's only an informational feature, will not check if the dependency is a valid one, nor if the source is available. +If a dependency is declared more than once (for example when gathering all dependencies in an import) it will only show the highest minimum version needed. + +Keep in mind dependencies can be declared on the root manifest and on each of the installers. If they happen to be declared in both, installer's dependencies will override those of the manifest. With the manifest's dependencies working as a default whenever installer's dependencies are not declared. + +## Future considerations +It may be able to enable/disable this feature using extra options for the command. diff --git a/doc/specs/#1287 - Management of package type dependencies.md b/doc/specs/#1287 - Management of package type dependencies.md index 603b370c54..3145c04de4 100644 --- a/doc/specs/#1287 - Management of package type dependencies.md +++ b/doc/specs/#1287 - Management of package type dependencies.md @@ -1,116 +1,116 @@ ---- -author: Florencia Zanollo fzanollo/t-fzanollo@microsoft.com -created on: 2021-07-15 -last updated: 2021-07-15 -issue id: 1287 ---- - -# Management of package type dependencies - -For [#1287](https://github.com/microsoft/winget-cli/issues/1287) - -## Abstract -As a new step in the pursue of dependency management, the Windows Package Manager will take care of one of the four types of dependencies (Package) for most of the commands. The underlying logic of the code will prove useful when implementing the rest of the types and commands; since it will manage dependencies' graph building and validation, as well as the correct order of installation. - -## Solution Design -The Windows Package Manager will build the dependencies' graph and corroborate there's not a cyclic dependency, among other validations (depending on the command). - -### Install: -Install command will build the dependency graph at runtime, from installer information. It will report on the other three types of dependencies and manage package type installation/validation (for new/installed dependencies respectively). -As a best effort, in case a cyclic dependency exists, Windows Package Manager will inform the user but try to install in some order; this is because most of the dependencies are at run time so there shouldn't present an issue on install. - -While building the graph, install command will verify: -* Availability: the package declared as dependency will need to be an existing one. -* Installed version: it will check for existing versions of the dependency and update if the minimum required version is bigger than the installed. -* Version: minimum required version will need to be less or equal to the latest available one. - -Information will be shown about failures, existence or installation progress for each of the dependencies required. - -When installing from a local manifest, a dependency source will be required (using --dependency-source parameter), but only if the target has at least one package type dependency declared. - -``` -> winget install Notepad++ -Found Notepad++ [Notepad++.Notepad++] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -This package requires the following dependencies: - - Windows Feature: - Hyper-V - - Package: - Microsoft.WindowsTerminal -Building package type dependencies' graph: - No errors or cyclic dependencies found. -Installing package type dependencies: - Found WindowsTerminal [Microsoft.WindowsTerminal] - This application is licensed to you by its owner. - Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. - Downloading (...) - Successfully verified installer hash - Starting package install... -Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe -Successfully verified installer hash -Starting package install... -``` - -### Import: -Import command will report all first level dependencies beforehand and after will work as install command for each of the packages found, iteratively. Ex. if packages *foo* and *foo1* are part of the import, dependencies for *foo* will be fetch and installed before continuing with *foo1*. - -``` -The packages found in this import have the following dependencies: - - Windows Feature: - Hyper-V - Containers - - Windows Libraries: - Microsoft.WinJS - - Package: - Microsoft.WindowsTerminal - - External: - JDK-11.0.10 -Found [Notepad++.Notepad++] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -Building package type dependencies' graph: - No errors or cyclic dependencies found. -Installing package type dependencies: - Found WindowsTerminal [Microsoft.WindowsTerminal] - This application is licensed to you by its owner. - Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. - Downloading (...) - Successfully verified installer hash - Starting package install... -Found [plex.Plex] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -(...) -``` - -### Update: -* Update one: will also work as install command, we need to check again for dependencies as the new installer can have new ones or bigger minimum required versions. -* Update many: will work iteratively (as import). - -``` -> winget upgrade Notepad++ -Found Notepad++ [Notepad++.Notepad++] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -This package requires the following dependencies: - - Windows Feature: - Hyper-V - - Package: - Microsoft.WindowsTerminal -Building package type dependencies' graph: - No errors or cyclic dependencies found. -Installing package type dependencies: - Found WindowsTerminal [Microsoft.WindowsTerminal] - This application is licensed to you by its owner. - Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. - Downloading (...) - Successfully verified installer hash - Starting package install... -Successfully verified installer hash -Starting package install... -``` - -## Capabilities -Will manage package type dependencies installation for install, import and update commands. -Will not manage or try to install any of the other types of dependencies (Windows Features, Windows Libraries, External). +--- +author: Florencia Zanollo fzanollo/t-fzanollo@microsoft.com +created on: 2021-07-15 +last updated: 2021-07-15 +issue id: 1287 +--- + +# Management of package type dependencies + +For [#1287](https://github.com/microsoft/winget-cli/issues/1287) + +## Abstract +As a new step in the pursue of dependency management, the Windows Package Manager will take care of one of the four types of dependencies (Package) for most of the commands. The underlying logic of the code will prove useful when implementing the rest of the types and commands; since it will manage dependencies' graph building and validation, as well as the correct order of installation. + +## Solution Design +The Windows Package Manager will build the dependencies' graph and corroborate there's not a cyclic dependency, among other validations (depending on the command). + +### Install: +Install command will build the dependency graph at runtime, from installer information. It will report on the other three types of dependencies and manage package type installation/validation (for new/installed dependencies respectively). +As a best effort, in case a cyclic dependency exists, Windows Package Manager will inform the user but try to install in some order; this is because most of the dependencies are at run time so there shouldn't present an issue on install. + +While building the graph, install command will verify: +* Availability: the package declared as dependency will need to be an existing one. +* Installed version: it will check for existing versions of the dependency and update if the minimum required version is bigger than the installed. +* Version: minimum required version will need to be less or equal to the latest available one. + +Information will be shown about failures, existence or installation progress for each of the dependencies required. + +When installing from a local manifest, a dependency source will be required (using --dependency-source parameter), but only if the target has at least one package type dependency declared. + +``` +> winget install Notepad++ +Found Notepad++ [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +This package requires the following dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Building package type dependencies' graph: + No errors or cyclic dependencies found. +Installing package type dependencies: + Found WindowsTerminal [Microsoft.WindowsTerminal] + This application is licensed to you by its owner. + Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. + Downloading (...) + Successfully verified installer hash + Starting package install... +Downloading https://github.com/notepad-plus-plus/notepad-plus-plus/releases/download/v7.9.5/npp.7.9.5.Installer.x64.exe +Successfully verified installer hash +Starting package install... +``` + +### Import: +Import command will report all first level dependencies beforehand and after will work as install command for each of the packages found, iteratively. Ex. if packages *foo* and *foo1* are part of the import, dependencies for *foo* will be fetch and installed before continuing with *foo1*. + +``` +The packages found in this import have the following dependencies: + - Windows Feature: + Hyper-V + Containers + - Windows Libraries: + Microsoft.WinJS + - Package: + Microsoft.WindowsTerminal + - External: + JDK-11.0.10 +Found [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Building package type dependencies' graph: + No errors or cyclic dependencies found. +Installing package type dependencies: + Found WindowsTerminal [Microsoft.WindowsTerminal] + This application is licensed to you by its owner. + Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. + Downloading (...) + Successfully verified installer hash + Starting package install... +Found [plex.Plex] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +(...) +``` + +### Update: +* Update one: will also work as install command, we need to check again for dependencies as the new installer can have new ones or bigger minimum required versions. +* Update many: will work iteratively (as import). + +``` +> winget upgrade Notepad++ +Found Notepad++ [Notepad++.Notepad++] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +This package requires the following dependencies: + - Windows Feature: + Hyper-V + - Package: + Microsoft.WindowsTerminal +Building package type dependencies' graph: + No errors or cyclic dependencies found. +Installing package type dependencies: + Found WindowsTerminal [Microsoft.WindowsTerminal] + This application is licensed to you by its owner. + Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. + Downloading (...) + Successfully verified installer hash + Starting package install... +Successfully verified installer hash +Starting package install... +``` + +## Capabilities +Will manage package type dependencies installation for install, import and update commands. +Will not manage or try to install any of the other types of dependencies (Windows Features, Windows Libraries, External). diff --git a/doc/specs/#140 - ZIP Support.md b/doc/specs/#140 - ZIP Support.md index 4e13762794..bdab14098d 100644 --- a/doc/specs/#140 - ZIP Support.md +++ b/doc/specs/#140 - ZIP Support.md @@ -1,89 +1,89 @@ ---- -author: Ryan Fu @ryfu-msft -created on: 2022-05-24 -last updated: 2022-12-13 -issue id: 140 ---- - -# Support ZIP/Archive InstallerTypes - -For [#140](https://github.com/microsoft/winget-cli/issues/140) - -## Abstract -This spec outlines the design for supporting the installation of packages using archive files. The initial implementation will aim to support only ZIP files. This spec will be updated if support for other archive types such as .tar.gz or .cab files are added. - -## Design Flow/Implementation - -Because ZIP archive files are essentially a compressed directory containing the relevant installer files, the install flow will need to add the following steps: - -1. Perform checks on the downloaded ZIP archive for potential malware or threats. -2. Extract the contents of the ZIP archive to a temporary location. -3. Execute the appropriate install flow using the extracted installer based on the installer type derived from the manifest. - ->NOTE: The initial implementation will first support installing ZIPs that contain only basic installers (msix, msi, or exe), then portable apps. - -## Manifest Changes: -Addition of `NestedInstallerType`: -- Enumeration of supported nested installerTypes contained inside an archive file. - -Addition of `NestedInstallerFile` -- Object containing metadata about a nested installer file contained inside an archive. -- Properties: - - `RelativeFilePath`: The relative path to a nested installer file contained inside an archive. - - `PortableCommandAlias`: The command alias to be used for calling the package. Only applies to a nested portable package. - -These changes would be added to the `Installer` object with `NestedInstallerFiles` representing an array of `NestedInstallerFile`. - -### Manifest Validation: -- Exactly one `NestedInstallerFile` entry must be specified for a non-portable nested installer type. -- Multiple `NestedInstallerFile` entries can be specified for portable nested installer type to support a suite of portable exes that would all be installed under the same package. -- If a ZIP `InstallerType` is specified, a `NestedInstallerType` is required to be specified. - -## Supported Install Scenarios: -The initial implementation of this feature will only support the following installation scenarios: -- Single base installer (exe, msi, msix) contained inside an archive file. -- Single or multiple portable exes bundled as a suite inside an archive file. - -## ZIP Extraction -The extraction of ZIPs will be done using Windows Shell APIs. ZIP files can be represented as a [ShellFolder](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ishellfolder) object that can be used to manage the contents of the ZIP file. File contents are represented as [ShellItems](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ishellitem), which can be handled using the methods exposed by the [IFileOperation interface](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifileoperation). - -In our initial implementation, we will only support extracting the top level archive to a temporary location. This means that the `NestedInstallerFile` must not be contained in any nested archives. The appropriate install flow will proceed based on the specified `NestedInstallerType`. - -> If the community presents enough use cases that require decompressing additional layers, then we will consider extending this functionality and adding a separate manifest entry to override the default behavior of only unzipping the top level archive. - -> Since we are utilizing Windows Shell APIs, we need to ensure that we are not invoking a UI during the extraction. This can be done by [setting the operation flag to not display any UI](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperation-setoperationflags). - -> During implementation, we will also need to ensure that this process can work under SYSTEM context. - -## ZIP Threat Detection -ZIP and other archive file types are known threat vectors for malware in the form of ZIP compression bombs. Two of the most common types of compression bombs are multi-layered (recursive) and single-layered (non-recursive). ZIP bombs rely on a repetition of identical files that have extremely large compression ratios. Some examples include the following: - -1. **Multi-layered (recursive)**: -A ZIP file containing multiple layers of nested ZIP files that achieve high compression ratios when decompressed recursively. This technique is often used to bypass compression ratio checks performed by ZIP parsers for a single layer. - -2. **Single-Layered (non-recursive)** -A ZIP bomb that expands fully after a single round of decompression. The extremely high compression ratio of the ZIP bomb is achieved by overlapping files within the ZIP container. - -In order to protect our users from these possible threats, we will need to scan the ZIP file for malware using [Pure, a static analysis file format checker](https://github.com/ronomon/pure). - -> Pure is licensed under the [MIT license](https://github.com/ronomon/pure/blob/master/LICENSE). - -If the Pure library functions that are called on a given ZIP file detect malware, the process will terminate and a warning will be displayed to the user. The user can include `--ignore-local-archive-malware-scan` to bypass this check and continue installation at their own risk. - - -To minimize unnecessary costs on performance during installation, these checks will only be applied when any of the following conditions are met: -- Hash mismatch was overridden -- Untrusted source (i.e. installing from a local manifest, private source or third-party REST sources) - -## Supporting Nested Portable(s) in an Archive -Currently, information regarding a single installed portable is stored in the ARP entry. In order to support installing a single or multiple portables contained inside an archive file, we will need to create a separate record that will be stored with the extracted files. This record will contain a table capturing the list of files that were created and placed down as well as various metadata to enable us to verify whether they have been modified. This table should replace most of the uninstall-related information that is stored in ARP for a given portable package. - -The table will contain the following information for each item that we create or place down during installation. - -| Metadata | Description | -| ----------- | ----------- | -| Path | Path to the item | -| Flag | Flag to indicate what type of file was placed down (directory, symlink, exe) | -| Hash | Hash for files with contents (exe) | -| SymlinkTarget | Target exe for the created symlink (only applies to symlink files) | - +--- +author: Ryan Fu @ryfu-msft +created on: 2022-05-24 +last updated: 2022-12-13 +issue id: 140 +--- + +# Support ZIP/Archive InstallerTypes + +For [#140](https://github.com/microsoft/winget-cli/issues/140) + +## Abstract +This spec outlines the design for supporting the installation of packages using archive files. The initial implementation will aim to support only ZIP files. This spec will be updated if support for other archive types such as .tar.gz or .cab files are added. + +## Design Flow/Implementation + +Because ZIP archive files are essentially a compressed directory containing the relevant installer files, the install flow will need to add the following steps: + +1. Perform checks on the downloaded ZIP archive for potential malware or threats. +2. Extract the contents of the ZIP archive to a temporary location. +3. Execute the appropriate install flow using the extracted installer based on the installer type derived from the manifest. + +>NOTE: The initial implementation will first support installing ZIPs that contain only basic installers (msix, msi, or exe), then portable apps. + +## Manifest Changes: +Addition of `NestedInstallerType`: +- Enumeration of supported nested installerTypes contained inside an archive file. + +Addition of `NestedInstallerFile` +- Object containing metadata about a nested installer file contained inside an archive. +- Properties: + - `RelativeFilePath`: The relative path to a nested installer file contained inside an archive. + - `PortableCommandAlias`: The command alias to be used for calling the package. Only applies to a nested portable package. + +These changes would be added to the `Installer` object with `NestedInstallerFiles` representing an array of `NestedInstallerFile`. + +### Manifest Validation: +- Exactly one `NestedInstallerFile` entry must be specified for a non-portable nested installer type. +- Multiple `NestedInstallerFile` entries can be specified for portable nested installer type to support a suite of portable exes that would all be installed under the same package. +- If a ZIP `InstallerType` is specified, a `NestedInstallerType` is required to be specified. + +## Supported Install Scenarios: +The initial implementation of this feature will only support the following installation scenarios: +- Single base installer (exe, msi, msix) contained inside an archive file. +- Single or multiple portable exes bundled as a suite inside an archive file. + +## ZIP Extraction +The extraction of ZIPs will be done using Windows Shell APIs. ZIP files can be represented as a [ShellFolder](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ishellfolder) object that can be used to manage the contents of the ZIP file. File contents are represented as [ShellItems](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ishellitem), which can be handled using the methods exposed by the [IFileOperation interface](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nn-shobjidl_core-ifileoperation). + +In our initial implementation, we will only support extracting the top level archive to a temporary location. This means that the `NestedInstallerFile` must not be contained in any nested archives. The appropriate install flow will proceed based on the specified `NestedInstallerType`. + +> If the community presents enough use cases that require decompressing additional layers, then we will consider extending this functionality and adding a separate manifest entry to override the default behavior of only unzipping the top level archive. + +> Since we are utilizing Windows Shell APIs, we need to ensure that we are not invoking a UI during the extraction. This can be done by [setting the operation flag to not display any UI](https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifileoperation-setoperationflags). + +> During implementation, we will also need to ensure that this process can work under SYSTEM context. + +## ZIP Threat Detection +ZIP and other archive file types are known threat vectors for malware in the form of ZIP compression bombs. Two of the most common types of compression bombs are multi-layered (recursive) and single-layered (non-recursive). ZIP bombs rely on a repetition of identical files that have extremely large compression ratios. Some examples include the following: + +1. **Multi-layered (recursive)**: +A ZIP file containing multiple layers of nested ZIP files that achieve high compression ratios when decompressed recursively. This technique is often used to bypass compression ratio checks performed by ZIP parsers for a single layer. + +2. **Single-Layered (non-recursive)** +A ZIP bomb that expands fully after a single round of decompression. The extremely high compression ratio of the ZIP bomb is achieved by overlapping files within the ZIP container. + +In order to protect our users from these possible threats, we will need to scan the ZIP file for malware using [Pure, a static analysis file format checker](https://github.com/ronomon/pure). + +> Pure is licensed under the [MIT license](https://github.com/ronomon/pure/blob/master/LICENSE). + +If the Pure library functions that are called on a given ZIP file detect malware, the process will terminate and a warning will be displayed to the user. The user can include `--ignore-local-archive-malware-scan` to bypass this check and continue installation at their own risk. + + +To minimize unnecessary costs on performance during installation, these checks will only be applied when any of the following conditions are met: +- Hash mismatch was overridden +- Untrusted source (i.e. installing from a local manifest, private source or third-party REST sources) + +## Supporting Nested Portable(s) in an Archive +Currently, information regarding a single installed portable is stored in the ARP entry. In order to support installing a single or multiple portables contained inside an archive file, we will need to create a separate record that will be stored with the extracted files. This record will contain a table capturing the list of files that were created and placed down as well as various metadata to enable us to verify whether they have been modified. This table should replace most of the uninstall-related information that is stored in ARP for a given portable package. + +The table will contain the following information for each item that we create or place down during installation. + +| Metadata | Description | +| ----------- | ----------- | +| Path | Path to the item | +| Flag | Flag to indicate what type of file was placed down (directory, symlink, exe) | +| Hash | Hash for files with contents (exe) | +| SymlinkTarget | Target exe for the created symlink (only applies to symlink files) | + diff --git a/doc/specs/#148 - Repair Support.md b/doc/specs/#148 - Repair Support.md index d3556ef1d7..47adce15d4 100644 --- a/doc/specs/#148 - Repair Support.md +++ b/doc/specs/#148 - Repair Support.md @@ -1,120 +1,120 @@ ---- -author: Madhusudhan Gumbalapura Sudarshan @Madhusudhan-MSFT -created on: 2023-12-01 -last updated: 2024-08-27 -issue id: 148 ---- - -# Repair Feature for Windows Package Manager - -"For [#148](https://github.com/microsoft/winget-cli/issues/148)" - -## Abstract - -This specification outlines the design and implementation of a repair feature for the Windows Package Manager.The repair feature aims to provide a convenient and reliable way for users to fix any issues that may arise with their installed applications, such as corrupted files, missing dependencies, or broken registry entries.The initial implementation will support repair for main installer types, such as Msi, Wix, Msix and MSStore installer types, which have native repair capabilities from their respective frameworks. Additionally, other installer types such as Burn/Exe/Nullsoft/Inno that can specify a custom repair switch in their YAML manifest files can also use the repair feature.The document also outlines the future plans for extending the repair feature to other installer types Portables that do not have a standard repair mechanism. - -## Inspiration - -The motivation for creating a repair feature in the Windows Package Manager is to provide users with a convenient and consistent way to fix their malfunctioning applications, regardless of the installer type. Currently, users have to rely on different methods to repair their applications, such as using the Windows Settings app, the Control Panel, the command line, or the application's own repair tool. These methods are not always available, accessible, or intuitive, and they may vary depending on the application and the installer type. The repair feature aims to provide a unified experience for users to repair their applications, regardless of the installer type. - -## Solution Design - -### Repair Command Syntax -`winget repair [[-q] ...] []` -will initiate the repair of the specified package. The command will display an error message if the application is not installed. The command will attempt to repair the application if it is installed. The command will display a success message if the repair succeeds. The command will display an error message if the repair fails. - -#### Arguments -| Argument | Description | -|-------------|-------------| -| **-q,--query** | The query used to search for an app. | - - -#### Optional Arguments - -| Option | Description | -|--------|-------------| -| **-m, --manifest** | Must be followed by the path to the manifest (YAML) file.You can use the manifest to run the repair experience from a [local manifest file](#local-repair).| -| **--id** | Limits the repair to the specified ID of the application.| -| **--name** | Limits the repair to the specified name of the application.| -| **--moniker** | Limits the repair to the specified moniker of the application.| -| **-v --version** | Limits the repair to the specified version of the application.If not specified, the repair will be applied to the latest version of the application.| -| **--architectures** | Select the architecture | -| **-s --source** | Limits the search to specified source(s).Must be followed by the source name.| -| **-o, --log** | Directs the logging to a log file. You must provide a path to a file that you have the write rights to. | -| **-i --interactive** | Runs the repair in interactive mode.| -| **-h --silent** | Runs the repair in silent mode.| -| **-?, --help** | Get additional help on this command. | -| **--accept-source-agreements** | Accept all source agreements during source operations | -| **--logs, --open-logs** | Open the default logs location. | -| **--locale** | Sets the locale.| - - -### Repair Feature for different Installer Types with Native Repair Capabilities - - Msi, Wix : The repair command will use the built-in repair features of the MSI installer type. It will run the msiexec command with the default [repair options](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec#repair-options) to repair the application using a ShellExecute call. - - Msix : The repair command will use the built-in repair features of the MSIX installer type. It will make an MSIX API call to register the application package, which will attempt to repair the package. - > We can't do the same thing as setting app repair for MSIX app right now because we can't use the private APIs that it uses to do repair operations with winget. - - MSStore : The repair command will make an MSStore API [StartProductInstallAsync](https://learn.microsoft.com/en-us/uwp/api/windows.applicationmodel.store.preview.installcontrol.appinstallmanager.startproductinstallasync?view=winrt-22621) call to repair the application with 'Repair' property of AppInstallOption set to true. - -### Repair Feature for Installer Types that require Custom Repair Switch - - Burn, Exe, Nullsoft & Inno : The custom switch for repair in the YAML manifest file will be used to perform the repair. The repair command will run the installer with the custom switch to repair the application. To have enough flexibility, different options are possible depending on the installer source used for repair. - - Installed Source: - - If the YAML manifest file specifies the `Repair` switch and `Modify` as the `RepairBehavior`, the repair command will use the modify command in the ARP `ModifyPath` registry key, along with the repair switch, through a ShellExecute call, as long as `NoModify` and `NoRepair` ARP registry flags are not set to 1. - - If the YAML manifest file specifies the `Repair` switch and `Uninstaller` as the RepairBehavior, the repair command will use the uninstall command in the ARP `UninstallString` registry key, along with the repair switch, through a ShellExecute call, as long as `NoRepair` APR registry flag is not set to 1. - - Remote Source: If the YAML manifest file specifies the `Repair` switch and `Installer` value for the RepairBehavior, the repair command will obtain the matching installer from the remote source and use the repair switch on the downloaded installer through a ShellExecute call.. - -> If neither switch is specified, the repair command will display an error message. - -> Note: The initial implementation will not support repair for Portables installer type. Based on feedback from the community, we may add the repair feature for these installer type in a future release. - -## Manifest Changes -Addition of `Repair` property to InstallerSwitch -- The `Repair` property is used to set the custom repair option that works with `RepairBehavior` field that controls the different repair behavior. - -Addition of `RepairBehavior` enumerable property to Installer Object -- With the `RepairBehavior` switch, we can adjust the repair behavior by choosing the installer source (Installed/local or remote) and making sure that the proper ARP registry entries are applied to identify the local installer type when carrying out a repair operation using a local installer source. -- The permitted initial values for the `RepairBehavior` switch include: - - Performing a repair using a Installed/Local Installer Source: - - `Modify`: if this option is specified, the repair switch will be applied to the `ModifyPath` ARP command entry, as long as `NoModify` and `NoRepair` ARP registry flags are not set to 1. - - `Uninstaller` : if this option is specified, the repair switch will be applied to the `UninstallString` ARP command entry, as long as `NoRepair` APR registry flag is note set to 1. - - Performing a repair using a Remote Installer Source: - - `Installer` : If this option is specified, the repair switch will be applied to the appropriate installer obtained from the remote installer source. - -## Manifest Validation -- Specifying `Repair` switch without `RepairBehavior` switch will result in an error. - - Specifying `RepairBehavior` switch without `Repair` switch will result in an error. - - `Repair` switch can't be empty when specified. - - `RepairBehavior` switch can't be empty when specified. - -## Handling Elevation Requirement -- The design presumes that the elevation requirements for modification/repair are consistent with those for installation, much like uninstallation. -- The expectations are: - - If a non-elevated session tries to modify a package installed for machine scope, the installer should prompt for elevation, as observed in sample runs. - - If a package installed for user scope attempts a repair operation in an admin context, it is restricted due to possible security risks. - -## Supported Repair Scenarios -- Repair for installed applications of Msi, Wix, Msix and MSStore installer types. -- Repair for the application using the custom repair switch specified in the YAML manifest file for Burn/Exe/Nullsoft/Inno/Wix/Msi installer types. - - The appropriate repair behavior is determined by the combination of the `Repair` switch and the `RepairBehavior` value in the YAML manifest. - -## Potential Issues -- For Msi based installer types - - To repair an Msi-based installer using msiexec platform support, the original installer must be present at the location registered as 'InstallSource' in the ARP registry. If the original installer is moved or renamed, the repair operation will fail, which is consistent with the modify repair through the settings app. This issue is more likely with installers installed via the winget install command, as it removes the installer right after installation to reduce disk footprint by design, making the 'InstallSource' path invalid. However, this issue does not apply if the installer can store the installer in a package cache path for future use and registers that path in 'InstallSource' in the ARP registry at the time of installation. -- For Burn/Exe/Nullsoft/Inno installer types - - the repair command will not work if the installer does not have a custom repair switch specified in the YAML manifest file. - - the repair command will not work if the installed Burn/Exe/Nullsoft/inno installer type doesn't correlate to the remote installer type that has a custom repair switch specified in the YAML manifest file. - -## Future considerations - -- Repair for Portables installer types. The possible options are: - - Download matching installer , uninstall & Install OR - - Download matching installer & re Install to overwrite existing files. -- Repair for Burn/Exe/Nullsoft/Inno installer types without custom repair switch. The possible options are: - - Download matching installer , uninstall & Install OR - - Download matching installer & re Install to overwrite existing files. - -## Resources -- https://learn.microsoft.com/en-us/windows/msix/desktop/managing-your-msix-reset-and-repair -- https://learn.microsoft.com/en-us/windows/win32/msi/reinstalling-a-feature-or-application -- https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec#repair-options -- https://learn.microsoft.com/en-us/uwp/api/windows.applicationmodel.store.preview.installcontrol.appinstallmanager.startproductinstallasync?view=winrt-22621 -- https://learn.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msireinstallproductw +--- +author: Madhusudhan Gumbalapura Sudarshan @Madhusudhan-MSFT +created on: 2023-12-01 +last updated: 2024-08-27 +issue id: 148 +--- + +# Repair Feature for Windows Package Manager + +"For [#148](https://github.com/microsoft/winget-cli/issues/148)" + +## Abstract + +This specification outlines the design and implementation of a repair feature for the Windows Package Manager.The repair feature aims to provide a convenient and reliable way for users to fix any issues that may arise with their installed applications, such as corrupted files, missing dependencies, or broken registry entries.The initial implementation will support repair for main installer types, such as Msi, Wix, Msix and MSStore installer types, which have native repair capabilities from their respective frameworks. Additionally, other installer types such as Burn/Exe/Nullsoft/Inno that can specify a custom repair switch in their YAML manifest files can also use the repair feature.The document also outlines the future plans for extending the repair feature to other installer types Portables that do not have a standard repair mechanism. + +## Inspiration + +The motivation for creating a repair feature in the Windows Package Manager is to provide users with a convenient and consistent way to fix their malfunctioning applications, regardless of the installer type. Currently, users have to rely on different methods to repair their applications, such as using the Windows Settings app, the Control Panel, the command line, or the application's own repair tool. These methods are not always available, accessible, or intuitive, and they may vary depending on the application and the installer type. The repair feature aims to provide a unified experience for users to repair their applications, regardless of the installer type. + +## Solution Design + +### Repair Command Syntax +`winget repair [[-q] ...] []` +will initiate the repair of the specified package. The command will display an error message if the application is not installed. The command will attempt to repair the application if it is installed. The command will display a success message if the repair succeeds. The command will display an error message if the repair fails. + +#### Arguments +| Argument | Description | +|-------------|-------------| +| **-q,--query** | The query used to search for an app. | + + +#### Optional Arguments + +| Option | Description | +|--------|-------------| +| **-m, --manifest** | Must be followed by the path to the manifest (YAML) file.You can use the manifest to run the repair experience from a [local manifest file](#local-repair).| +| **--id** | Limits the repair to the specified ID of the application.| +| **--name** | Limits the repair to the specified name of the application.| +| **--moniker** | Limits the repair to the specified moniker of the application.| +| **-v --version** | Limits the repair to the specified version of the application.If not specified, the repair will be applied to the latest version of the application.| +| **--architectures** | Select the architecture | +| **-s --source** | Limits the search to specified source(s).Must be followed by the source name.| +| **-o, --log** | Directs the logging to a log file. You must provide a path to a file that you have the write rights to. | +| **-i --interactive** | Runs the repair in interactive mode.| +| **-h --silent** | Runs the repair in silent mode.| +| **-?, --help** | Get additional help on this command. | +| **--accept-source-agreements** | Accept all source agreements during source operations | +| **--logs, --open-logs** | Open the default logs location. | +| **--locale** | Sets the locale.| + + +### Repair Feature for different Installer Types with Native Repair Capabilities + - Msi, Wix : The repair command will use the built-in repair features of the MSI installer type. It will run the msiexec command with the default [repair options](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec#repair-options) to repair the application using a ShellExecute call. + - Msix : The repair command will use the built-in repair features of the MSIX installer type. It will make an MSIX API call to register the application package, which will attempt to repair the package. + > We can't do the same thing as setting app repair for MSIX app right now because we can't use the private APIs that it uses to do repair operations with winget. + - MSStore : The repair command will make an MSStore API [StartProductInstallAsync](https://learn.microsoft.com/en-us/uwp/api/windows.applicationmodel.store.preview.installcontrol.appinstallmanager.startproductinstallasync?view=winrt-22621) call to repair the application with 'Repair' property of AppInstallOption set to true. + +### Repair Feature for Installer Types that require Custom Repair Switch + - Burn, Exe, Nullsoft & Inno : The custom switch for repair in the YAML manifest file will be used to perform the repair. The repair command will run the installer with the custom switch to repair the application. To have enough flexibility, different options are possible depending on the installer source used for repair. + - Installed Source: + - If the YAML manifest file specifies the `Repair` switch and `Modify` as the `RepairBehavior`, the repair command will use the modify command in the ARP `ModifyPath` registry key, along with the repair switch, through a ShellExecute call, as long as `NoModify` and `NoRepair` ARP registry flags are not set to 1. + - If the YAML manifest file specifies the `Repair` switch and `Uninstaller` as the RepairBehavior, the repair command will use the uninstall command in the ARP `UninstallString` registry key, along with the repair switch, through a ShellExecute call, as long as `NoRepair` APR registry flag is not set to 1. + - Remote Source: If the YAML manifest file specifies the `Repair` switch and `Installer` value for the RepairBehavior, the repair command will obtain the matching installer from the remote source and use the repair switch on the downloaded installer through a ShellExecute call.. + +> If neither switch is specified, the repair command will display an error message. + +> Note: The initial implementation will not support repair for Portables installer type. Based on feedback from the community, we may add the repair feature for these installer type in a future release. + +## Manifest Changes +Addition of `Repair` property to InstallerSwitch +- The `Repair` property is used to set the custom repair option that works with `RepairBehavior` field that controls the different repair behavior. + +Addition of `RepairBehavior` enumerable property to Installer Object +- With the `RepairBehavior` switch, we can adjust the repair behavior by choosing the installer source (Installed/local or remote) and making sure that the proper ARP registry entries are applied to identify the local installer type when carrying out a repair operation using a local installer source. +- The permitted initial values for the `RepairBehavior` switch include: + - Performing a repair using a Installed/Local Installer Source: + - `Modify`: if this option is specified, the repair switch will be applied to the `ModifyPath` ARP command entry, as long as `NoModify` and `NoRepair` ARP registry flags are not set to 1. + - `Uninstaller` : if this option is specified, the repair switch will be applied to the `UninstallString` ARP command entry, as long as `NoRepair` APR registry flag is note set to 1. + - Performing a repair using a Remote Installer Source: + - `Installer` : If this option is specified, the repair switch will be applied to the appropriate installer obtained from the remote installer source. + +## Manifest Validation +- Specifying `Repair` switch without `RepairBehavior` switch will result in an error. + - Specifying `RepairBehavior` switch without `Repair` switch will result in an error. + - `Repair` switch can't be empty when specified. + - `RepairBehavior` switch can't be empty when specified. + +## Handling Elevation Requirement +- The design presumes that the elevation requirements for modification/repair are consistent with those for installation, much like uninstallation. +- The expectations are: + - If a non-elevated session tries to modify a package installed for machine scope, the installer should prompt for elevation, as observed in sample runs. + - If a package installed for user scope attempts a repair operation in an admin context, it is restricted due to possible security risks. + +## Supported Repair Scenarios +- Repair for installed applications of Msi, Wix, Msix and MSStore installer types. +- Repair for the application using the custom repair switch specified in the YAML manifest file for Burn/Exe/Nullsoft/Inno/Wix/Msi installer types. + - The appropriate repair behavior is determined by the combination of the `Repair` switch and the `RepairBehavior` value in the YAML manifest. + +## Potential Issues +- For Msi based installer types + - To repair an Msi-based installer using msiexec platform support, the original installer must be present at the location registered as 'InstallSource' in the ARP registry. If the original installer is moved or renamed, the repair operation will fail, which is consistent with the modify repair through the settings app. This issue is more likely with installers installed via the winget install command, as it removes the installer right after installation to reduce disk footprint by design, making the 'InstallSource' path invalid. However, this issue does not apply if the installer can store the installer in a package cache path for future use and registers that path in 'InstallSource' in the ARP registry at the time of installation. +- For Burn/Exe/Nullsoft/Inno installer types + - the repair command will not work if the installer does not have a custom repair switch specified in the YAML manifest file. + - the repair command will not work if the installed Burn/Exe/Nullsoft/inno installer type doesn't correlate to the remote installer type that has a custom repair switch specified in the YAML manifest file. + +## Future considerations + +- Repair for Portables installer types. The possible options are: + - Download matching installer , uninstall & Install OR + - Download matching installer & re Install to overwrite existing files. +- Repair for Burn/Exe/Nullsoft/Inno installer types without custom repair switch. The possible options are: + - Download matching installer , uninstall & Install OR + - Download matching installer & re Install to overwrite existing files. + +## Resources +- https://learn.microsoft.com/en-us/windows/msix/desktop/managing-your-msix-reset-and-repair +- https://learn.microsoft.com/en-us/windows/win32/msi/reinstalling-a-feature-or-application +- https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/msiexec#repair-options +- https://learn.microsoft.com/en-us/uwp/api/windows.applicationmodel.store.preview.installcontrol.appinstallmanager.startproductinstallasync?view=winrt-22621 +- https://learn.microsoft.com/en-us/windows/win32/api/msi/nf-msi-msireinstallproductw diff --git a/doc/specs/#164 - PWA Support.md b/doc/specs/#164 - PWA Support.md index 7d316b4a2c..016ee91ae2 100644 --- a/doc/specs/#164 - PWA Support.md +++ b/doc/specs/#164 - PWA Support.md @@ -1,123 +1,123 @@ ---- -author: Amrutha Srinivasan @amrutha95 -created on: 2020-08-19 -last updated: 2020-08-19 -issue id: 164 ---- - -# Spec Title - -For [#164](https://github.com/microsoft/winget-cli/issues/164) - -## Abstract - -This feature adds PWA (Progressive Web Application) support to winget. The goal is to allow users to install and maintain PWAs just like they would any other type of application on winget. As a first step, this would be available as an "experimental" feature to make it available to the community and receive feedback. - -## Inspiration - -PWA adoption is extremely important in the Windows app ecosystem. It is also crucial that they are treated as first class citizens in the app ecosystem, just like any native application. Providing PWA support for winget is key to achieving this goal. - -## Solution Design - -### System requirements - -Windows 10 v2004 or newer -Edge latest Canary build - -### PWA YAML manifest - -The "InstallerType" field in the YAML manifest file specifies the type of the application. A new InstallerType "PWA" will be added to show that a given application is a PWA. - -Each PWA would have its own manifest YAML file. A sample manifest file would look like this: - -``` -Id: FinancialTimesLimited.FinTimes -Version: 1.0.0.0 -Name: AppInstaller FinTimes -Publisher: Financial Times Limited -AppMoniker: fintimes -License: Test -InstallerType: PWA -MinOSVersion: 10.0.19041.0 -Installers: -- Arch: x64 - Url: https://app.ft.com - InstallerType: PWA -ManifestVersion: 0.1.0 -``` - -### Package generation - -A POST call is made to a web service (https://pwabuilder-win-chromium-platform.centralus.cloudapp.azure.com) with a JSON body containing the URL to the PWA. The web service packages the PWA and its assets into an unsigned MSIX. An example of the JSON body would be : - -``` -{ - "url": "https://app.ft.com" -} -``` - -There are many more optional parameters that can be passed to this service. For more information, refer to the test website linked in the Resources section. - -### Package installation - -The unsigned MSIX package and any other dependency packages are added for the current user and the application is silently installed on the user's system. The installation is done by calling the Windows API AddPackageByUriAsync() using the specific deployment options that allow unsigned packages. - -### Flow of the install process - -1. User types the install command, eg. `winget install fintimes` (For the experimental version, the manifest will be passed as an argument) -2. The URL from the manifest YAML file of the Financial Times PWA will be sent as a POST request to an internal web service that will generate an unsigned MSIX package. This MSIX package is downloaded onto the user's system. -3. The generated package will then be silently installed as a Hosted App (Refer to resources for more on the Hostel App Model). -4. The MSIX file is removed from the user's system. - -## UI/UX Design - -Installing a PWA will be similar to installing other application types currently supported by winget, using the install command. The only difference in experience might be if the user passes a -i flag to get a more interactive experience. When this flag is passed, the user will be prompted to allow the PWA to be launched on install. This feature is provided because in order to register a PWA with Edge, it has to be launched once. - -An example of the interactive install would look like this: - -``` ->winget install -i fintimes -Found Financial Times [PWAtest.FinTimes] -This application is licensed to you by its owner. -Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. -Starting package install... - -Successfully installed! Launching app now. This is a necessary step to complete installation. -(App launches on Edge) -``` - -## Capabilities - -### Accessibility - -This should have no direct impact on accessibility. - -### Security - -No direct impact on security. - -### Reliability - -This is not expected to impact reliability. - -### Compatibility - -This is an additional functionality built on top of the existing implementation to allow the install of PWAs. Therefore we don't anticipate any breaking changes to the existing implementation. - -### Performance, Power, and Efficiency - -## Potential Issues - -If the user navigates to the site before launching the installed PWA at least once (i.e. before registration with Edge), Edge might prompt them to install the PWA again. - -## Future considerations - -1. We are looking into where the catalog of PWAs should exist, and the pros and cons of maintaining a separate catalog vs including PWAs on the existing catalog. -2. We're testing this new integration technology with Edge first in 2020 and hope to have this debugged and contributed to the Chromium project early 2021. -3. Sometimes there may be multiple versions of a PWA available. In such scenarios, we may allow a "force" option in the case where a user wants all of them to be installed. -4. There will be a service developed in the future that updates all the PWA manifests whenever there is a breaking change in Edge or in the web service that generates MSIX packages. - -## Resources - -Hosted App Model : https://blogs.windows.com/windowsdeveloper/2020/03/19/hosted-app-model/ -Test Website for generating MSIX for PWA : https://pwabuilder-win-chromium-platform.centralus.cloudapp.azure.com/ +--- +author: Amrutha Srinivasan @amrutha95 +created on: 2020-08-19 +last updated: 2020-08-19 +issue id: 164 +--- + +# Spec Title + +For [#164](https://github.com/microsoft/winget-cli/issues/164) + +## Abstract + +This feature adds PWA (Progressive Web Application) support to winget. The goal is to allow users to install and maintain PWAs just like they would any other type of application on winget. As a first step, this would be available as an "experimental" feature to make it available to the community and receive feedback. + +## Inspiration + +PWA adoption is extremely important in the Windows app ecosystem. It is also crucial that they are treated as first class citizens in the app ecosystem, just like any native application. Providing PWA support for winget is key to achieving this goal. + +## Solution Design + +### System requirements + +Windows 10 v2004 or newer +Edge latest Canary build + +### PWA YAML manifest + +The "InstallerType" field in the YAML manifest file specifies the type of the application. A new InstallerType "PWA" will be added to show that a given application is a PWA. + +Each PWA would have its own manifest YAML file. A sample manifest file would look like this: + +``` +Id: FinancialTimesLimited.FinTimes +Version: 1.0.0.0 +Name: AppInstaller FinTimes +Publisher: Financial Times Limited +AppMoniker: fintimes +License: Test +InstallerType: PWA +MinOSVersion: 10.0.19041.0 +Installers: +- Arch: x64 + Url: https://app.ft.com + InstallerType: PWA +ManifestVersion: 0.1.0 +``` + +### Package generation + +A POST call is made to a web service (https://pwabuilder-win-chromium-platform.centralus.cloudapp.azure.com) with a JSON body containing the URL to the PWA. The web service packages the PWA and its assets into an unsigned MSIX. An example of the JSON body would be : + +``` +{ + "url": "https://app.ft.com" +} +``` + +There are many more optional parameters that can be passed to this service. For more information, refer to the test website linked in the Resources section. + +### Package installation + +The unsigned MSIX package and any other dependency packages are added for the current user and the application is silently installed on the user's system. The installation is done by calling the Windows API AddPackageByUriAsync() using the specific deployment options that allow unsigned packages. + +### Flow of the install process + +1. User types the install command, eg. `winget install fintimes` (For the experimental version, the manifest will be passed as an argument) +2. The URL from the manifest YAML file of the Financial Times PWA will be sent as a POST request to an internal web service that will generate an unsigned MSIX package. This MSIX package is downloaded onto the user's system. +3. The generated package will then be silently installed as a Hosted App (Refer to resources for more on the Hostel App Model). +4. The MSIX file is removed from the user's system. + +## UI/UX Design + +Installing a PWA will be similar to installing other application types currently supported by winget, using the install command. The only difference in experience might be if the user passes a -i flag to get a more interactive experience. When this flag is passed, the user will be prompted to allow the PWA to be launched on install. This feature is provided because in order to register a PWA with Edge, it has to be launched once. + +An example of the interactive install would look like this: + +``` +>winget install -i fintimes +Found Financial Times [PWAtest.FinTimes] +This application is licensed to you by its owner. +Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. +Starting package install... + +Successfully installed! Launching app now. This is a necessary step to complete installation. +(App launches on Edge) +``` + +## Capabilities + +### Accessibility + +This should have no direct impact on accessibility. + +### Security + +No direct impact on security. + +### Reliability + +This is not expected to impact reliability. + +### Compatibility + +This is an additional functionality built on top of the existing implementation to allow the install of PWAs. Therefore we don't anticipate any breaking changes to the existing implementation. + +### Performance, Power, and Efficiency + +## Potential Issues + +If the user navigates to the site before launching the installed PWA at least once (i.e. before registration with Edge), Edge might prompt them to install the PWA again. + +## Future considerations + +1. We are looking into where the catalog of PWAs should exist, and the pros and cons of maintaining a separate catalog vs including PWAs on the existing catalog. +2. We're testing this new integration technology with Edge first in 2020 and hope to have this debugged and contributed to the Chromium project early 2021. +3. Sometimes there may be multiple versions of a PWA available. In such scenarios, we may allow a "force" option in the case where a user wants all of them to be installed. +4. There will be a service developed in the future that updates all the PWA manifests whenever there is a breaking change in Edge or in the web service that generates MSIX packages. + +## Resources + +Hosted App Model : https://blogs.windows.com/windowsdeveloper/2020/03/19/hosted-app-model/ +Test Website for generating MSIX for PWA : https://pwabuilder-win-chromium-platform.centralus.cloudapp.azure.com/ diff --git a/doc/specs/#888 - Com Api.md b/doc/specs/#888 - Com Api.md index 79019ad032..c25e2749a3 100644 --- a/doc/specs/#888 - Com Api.md +++ b/doc/specs/#888 - Com Api.md @@ -1,876 +1,876 @@ -# 1. Background - -The Windows Package Manager currently exposes a command line interface to search for packages, -install them, view progress, and more. This API is designed to provide another way for callers to -make use of that functionality. The API will be preferred by callers that want to receive progress -and completion events, and UWP packages that do not have permission to launch command line processes. -The goal for this api is to provide the full set of install functionality possible using the Windows -Package Manager command line. The command line is documented at -https://docs.microsoft.com/windows/package-manager/winget/ - -# 2. Description - -Windows Package Manager is a package manager for windows applications. It comes with a predefined -repository of applications and users can add new repositories using the winget command line. This -API allows packaged apps with the packageManagement capability and other higher privilege processes -to start, manage, and monitor installation of packages that are listed in Windows Package Manager -repositories. - -# 3. Examples - -Sample member values for the following examples: -m_installAppId = L"Microsoft.VisualStudioCode"; - -## 3.1. Create objects - -Creation of objects has to be done through CoCreateInstance rather than normal winrt initialization -since it's hosted by an out of proc com server. These helper methods will be used in the rest of the -examples. - -```c++ (C++ish pseudocode) - AppInstaller CreateAppInstaller() { - return winrt::create_instance(CLSID_AppInstaller, CLSCTX_ALL); - } - InstallOptions CreateInstallOptions() { - return winrt::create_instance(CLSID_InstallOptions, CLSCTX_ALL); - } - FindPackagesOptions CreateFindPackagesOptions() { - return winrt::create_instance(CLSID_FindPackagesOptions, CLSCTX_ALL); - } - CreateCompositeAppCatalogOptions CreateCreateCompositeAppCatalogOptions() { - return winrt::create_instance(CLSID_CreateCompositeAppCatalogOptions, CLSCTX_ALL); - } - PackageMatchFilter CreatePackageMatchFilter() { - return winrt::create_instance(CLSID_PackageMatchFilter, CLSCTX_ALL); - } -``` - -## 3.2. Search - -The api can be used to search for packages in a catalog known to Windows Package Manager. This can -be used to get availability information or start an install. - -```c++ (C++ish pseudocode) - - // Sample of using synchronous methods on background thread. - CatalogPackage MainPage::FindPackageOnBackgroundThread() - { - PackageManager packageManager = CreatePackageManager(); - PackageCatalogReference catalogRef{ - packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) }; - ConnectResult connectResult = catalogRef.Connect(); - if (connectResult.Status() != ConnectResultStatus::Ok) - { - return nullptr; - } - PackageCatalog catalog = connectResult.PackageCatalog(); - - FindPackagesOptions findPackagesOptions = CreateFindPackagesOptions(); - PackageMatchFilter filter = CreatePackageMatchFilter(); - filter.Field(PackageMatchField::Id); - filter.Option(PackageFieldMatchOption::Equals); - filter.Value(m_installAppId); - findPackagesOptions.Filters().Append(filter); - // We've already switched to a background thread, so do everything synchronously. - FindPackagesResult findPackagesResult{ catalog.FindPackages(findPackagesOptions) }; - - winrt::IVectorView matches = findPackagesResult.Matches(); - if (matches.Size() == 0) - { - return nullptr; - } - return matches.GetAt(0).CatalogPackage(); - } - - // Sample of using async methods. - IAsyncOperation MainPage::FindPackageInCatalogAsync(PackageCatalog catalog, - std::wstring packageId) - { - FindPackagesOptions findPackagesOptions = CreateFindPackagesOptions(); - PackageMatchFilter filter = CreatePackageMatchFilter(); - filter.Field(PackageMatchField::Id); - filter.Option(PackageFieldMatchOption::Equals); - filter.Value(packageId); - findPackagesOptions.Filters().Append(filter); - FindPackagesResult findPackagesResult{ co_await catalog.FindPackagesAsync(findPackagesOptions) }; - - winrt::IVectorView matches = findPackagesResult.Matches(); - if (matches.Size() == 0) - { - co_return nullptr; - } - co_return matches.GetAt(0).CatalogPackage(); - } - - IAsyncOperation MainPage::FindPackageAsync() - { - PackageManager packageManager = CreatePackageManager(); - PackageCatalogReference catalogRef{ - packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) }; - ConnectResult connectResult = catalogRef.Connect(); - if (connectResult.Status() != ConnectResultStatus::Ok) - { - co_return nullptr; - } - PackageCatalog catalog = connectResult.PackageCatalog(); - co_return FindPackageInCatalogAsync(catalog, m_installAppId).get(); - } -``` - -## 3.3. Install - -```c++ (C++ish pseudocode) - - IAsyncOperationWithProgress MainPage::InstallPackage(CatalogPackage package) - { - PackageManager packageManager = CreatePackageManager(); - InstallOptions installOptions = CreateInstallOptions(); - installOptions.PackageInstallScope(PackageInstallScope::Any); - - return packageManager.InstallPackageAsync(package, installOptions); - } - - IAsyncAction UpdateUIProgress( - InstallProgress progress, - winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, - winrt::Windows::UI::Xaml::Controls::TextBlock statusText) - { - co_await winrt::resume_foreground(progressBar.Dispatcher()); - progressBar.Value(progress.DownloadProgress*100); - - std::wstring downloadText{ L"Downloading. " }; - switch (progress.State) - { - case PackageInstallProgressState::Queued: - statusText.Text(L"Queued"); - break; - case PackageInstallProgressState::Downloading: - downloadText += std::to_wstring(progress.BytesDownloaded) + L" bytes of " + std::to_wstring(progress.BytesRequired); - statusText.Text(downloadText); - break; - case PackageInstallProgressState::Installing: - statusText.Text(L"Installing"); - progressBar.IsIndeterminate(true); - break; - case PackageInstallProgressState::PostInstall: - statusText.Text(L"Finishing install"); - break; - case PackageInstallProgressState::Finished: - statusText.Text(L"Finished install."); - progressBar.IsIndeterminate(false); - break; - default: - statusText.Text(L""); - } - co_return; - } - - // This method is called from a background thread. - IAsyncAction UpdateUIForInstall( - IAsyncOperationWithProgress installPackageOperation, - winrt::Windows::UI::Xaml::Controls::Button installButton, - winrt::Windows::UI::Xaml::Controls::Button cancelButton, - winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, - winrt::Windows::UI::Xaml::Controls::TextBlock statusText) - { - if (installPackageOperation) - { - - installPackageOperation.Progress([=]( - IAsyncOperationWithProgress const& /* sender */, - InstallProgress const& progress) - { - UpdateUIProgress(progressBar, statusText, 50, stateStr).get(); - }); - - - winrt::hresult installOperationHr = S_OK; - std::wstring errorMessage{ L"Unknown Error" }; - InstallResult installResult{ nullptr }; - try - { - installResult = co_await installPackageOperation; - } - catch (hresult_canceled const&) - { - errorMessage = L"Cancelled"; - OutputDebugString(L"Operation was cancelled"); - } - catch (...) - { - // Operation failed - // Example: HRESULT_FROM_WIN32(ERROR_DISK_FULL). - installOperationHr = winrt::to_hresult(); - // Example: "There is not enough space on the disk." - errorMessage = winrt::to_message(); - OutputDebugString(L"Operation failed"); - } - - // Switch back to ui thread context. - co_await winrt::resume_foreground(progressBar.Dispatcher()); - - cancelButton.IsEnabled(false); - installButton.IsEnabled(true); - progressBar.IsIndeterminate(false); - - if (installPackageOperation.Status() == AsyncStatus::Canceled) - { - installButton.Content(box_value(L"Retry")); - statusText.Text(L"Install cancelled."); - } - if (installPackageOperation.Status() == AsyncStatus::Error || installResult == nullptr) - { - installButton.Content(box_value(L"Retry")); - statusText.Text(errorMessage); - } - else if (installResult.RebootRequired()) - { - installButton.Content(box_value(L"Install")); - statusText.Text(L"Reboot to finish installation."); - } - else if (installResult.Status() == InstallResultStatus::Ok) - { - installButton.Content(box_value(L"Install")); - statusText.Text(L"Finished."); - } - else - { - installButton.Content(box_value(L"Install")); - statusText.Text(L"Install failed."); - } - } - } - - IAsyncAction MainPage::StartInstall( - winrt::Windows::UI::Xaml::Controls::Button installButton, - winrt::Windows::UI::Xaml::Controls::Button cancelButton, - winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, - winrt::Windows::UI::Xaml::Controls::TextBlock statusText) - { - installButton.IsEnabled(false); - cancelButton.IsEnabled(true); - - co_await winrt::resume_background(); - - PackageManager packageManager = CreatePackageManager(); - PackageCatalogReference catalogRef{ - packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) }; - ConnectResult connectResult = catalogRef.Connect(); - if (connectResult.Status() != ConnectResultStatus::Ok) - { - co_await winrt::resume_foreground(progressBar.Dispatcher()); - statusText.Text(L"Connecting to catalog failed."); - co_return; - } - PackageCatalog catalog = connectResult.PackageCatalog(); - - FindPackagesResult findPackagesResult{ FindPackageOnBackgroundThread(catalog, m_installAppId) }; - - winrt::IVectorView matches = findPackagesResult.Matches(); - if (matches.Size() > 0) - { - m_installPackageOperation = InstallPackage(matches.GetAt(0).CatalogPackage()); - UpdateUIForInstall(m_installPackageOperation, installButton, cancelButton, progressBar, statusText); - } - else - { - co_await winrt::resume_foreground(progressBar.Dispatcher()); - statusText.Text(L"Could not find package."); - co_return; - } - } -``` - -## 3.4.1 Cancel - -The async operation can be stored, or the install code can wait on an event that can be triggered. - -```c++ (C++ish pseudocode) - void MainPage::CancelButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - if (m_installPackageOperation) - { - m_installPackageOperation.Cancel(); - } - } -``` - -## 3.5. Open a catalog by name - -Open a catalog known to the caller. There is no way to use the api to add a catalog, that must be done -on the command line. - -```c++ (C++ish pseudocode) - IAsyncOperation MainPage::FindSourceAsync(std::wstring packageSource) - { - PackageManager packageManager = CreatePackageManager(); - PackageCatalogReference catalogRef{ packageManager.GetPackageCatalogByName(packageSource) }; - if (catalogRef) - { - ConnectResult connectResult{ co_await catalogRef.ConnectAsync() }; - // PackageCatalog will be null if connectResult.ErrorCode() is a failure - PackageCatalog catalog = connectResult.PackageCatalog(); - co_return catalog; - } - } -``` - -# 4 Remarks - -Notes have been added inline throughout the api details. - - -For this api there are multiple similar apis that are -relevant with regard to naming and consistency. There is the Windows Package Manager command line which uses -"source" to describe the various repositories that can host packages and "search" to describe looking up an app. -https://docs.microsoft.com/windows/package-manager/winget/ -There is the Windows::ApplicationModel::PackageCatalog which exists as a Windows API for installing packages -and monitoring their installation progress. -https://docs.microsoft.com/uwp/api/windows.applicationmodel.packagecatalog?view=winrt-19041 -And there is Windows.Management.Deployment.PackageManager which allows packages with the packageManagement -capability to install msix apps and uses "Find" to describe looking up an app -https://docs.microsoft.com/uwp/api/windows.management.deployment.packagemanager?view=winrt-19041 - -This API has aligned with those Windows APIs in using \*Catalog and Find. - -# 5 API Details - -```c# (but really MIDL3) -namespace Microsoft.Management.Deployment -{ - [contractversion(1)] - apicontract WindowsPackageManagerContract{}; - - /// State of the install. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallProgressState - { - /// The install is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from downloading or installing. - Queued, - /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will - /// end the download and prevent the package from installing. - Downloading, - /// The install is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not - /// stop the installation or the post install cleanup. - Installing, - /// The installer has completed and cleanup actions are in progress. Cancellation of the - /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the install. - PostInstall, - /// The operation has completed. - Finished, - }; - - /// Progress object for the install - /// DESIGN NOTE: percentage for the install as a whole is purposefully not included as there is no way to - /// estimate progress when the installer is running. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - struct InstallProgress - { - /// State of the install - PackageInstallProgressState State; - /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes downloaded if known - UInt64 BytesDownloaded; - /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes required if known - UInt64 BytesRequired; - /// Download percentage completed - Double DownloadProgress; - /// Install percentage if known. - Double InstallationProgress; - }; - - /// Status of the Install call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum InstallResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - DownloadError, - InstallError, - ManifestError, - NoApplicableInstallers, - }; - - /// Result of the install - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass InstallResult - { - /// Used by a caller to correlate the install with a caller's data. - String CorrelationData{ get; }; - /// Whether a restart is required to complete the install. - Boolean RebootRequired{ get; }; - - /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED - InstallResultStatus Status{ get; }; - /// Specific error if known, from downloader or installer itself, example ERROR_INSTALL_PACKAGE_REJECTED - HRESULT ExtendedErrorCode{ get; }; - } - - /// IMPLEMENTATION NOTE: SourceOrigin from AppInstallerRepositorySource.h - /// Defines the origin of the package catalog details. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageCatalogOrigin - { - /// Predefined means it came as part of the Windows Package Manager package and cannot be removed. - Predefined, - /// User means it was added by the user and could be removed. - User, - }; - - /// IMPLEMENTATION NOTE: SourceTrustLevel from AppInstallerRepositorySource.h - /// Defines the trust level of the package catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageCatalogTrustLevel - { - None, - Trusted, - }; - - /// IMPLEMENTATION NOTE: SourceDetails from AppInstallerRepositorySource.h - /// Interface for retrieving information about an package catalog without acting on it. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalogInfo - { - /// The package catalog's unique identifier. - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.Winget.Source_8wekyb3d8bbwe" - /// For contoso sample on msdn "contoso" - String Id { get; }; - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name { get; }; - /// The type of the package catalog. - /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". - /// For contoso sample on msdn "Microsoft.PreIndexed.Package" - String Type { get; }; - /// The argument used when adding the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" - /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" - String Argument { get; }; - /// The last time that this package catalog was updated. - Windows.Foundation.DateTime LastUpdateTime { get; }; - /// The origin of the package catalog. - PackageCatalogOrigin Origin { get; }; - /// The trust level of the package catalog - PackageCatalogTrustLevel TrustLevel { get; }; - } - - /// A metadata item of a package version. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageVersionMetadataField - { - /// The InstallerType of an installed package - InstallerType, - /// The Scope of an installed package - InstalledScope, - /// The system path where the package is installed - InstalledLocation, - /// The standard uninstall command; which may be interactive - StandardUninstallCommand, - /// An uninstall command that should be non-interactive - SilentUninstallCommand, - /// The publisher of the package - PublisherDisplayName, - }; - - /// IMPLEMENTATION NOTE: IPackageVersion from AppInstallerRepositorySearch.h - /// A single package version. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageVersionInfo - { - /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from AppInstallerRepositorySearch.h - /// Gets any metadata associated with this package version. - /// Primarily stores data on installed packages. - /// Metadata fields may have no value (e.g. packages that aren't installed will not have an InstalledLocation). - String GetMetadata(PackageVersionMetadataField metadataField); - /// IMPLEMENTATION NOTE: PackageVersionProperty fields from AppInstallerRepositorySearch.h - String Id { get; }; - String DisplayName { get; }; - String Version { get; }; - String Channel { get; }; - /// DESIGN NOTE: RelativePath from AppInstallerRepositorySearch.h is excluded as not needed. - /// String RelativePath; - - /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from AppInstallerRepositorySearch.h - /// PackageFamilyName and ProductCode can have multiple values. - Windows.Foundation.Collections.IVectorView PackageFamilyNames { get; }; - Windows.Foundation.Collections.IVectorView ProductCodes { get; }; - - /// Gets the package catalog where this package version is from. - PackageCatalog PackageCatalog { get; }; - - /// DESIGN NOTE: - /// GetManifest from IPackageVersion in AppInstallerRepositorySearch is not implemented in V1. That class has - /// a lot of fields and no one requesting it. - /// Gets the manifest of this package version. - /// virtual Manifest::Manifest GetManifest() = 0; - } - - /// IMPLEMENTATION NOTE: PackageVersionKey from AppInstallerRepositorySearch.h - /// A key to identify a package version within a package. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageVersionId - { - /// The package catalog id that this version came from. - String PackageCatalogId { get; }; - /// The version. - String Version { get; }; - /// The channel. - String Channel { get; }; - }; - - /// IMPLEMENTATION NOTE: IPackage from AppInstallerRepositorySearch.h - /// A package, potentially containing information about it's local state and the available versions. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass CatalogPackage - { - /// IMPLEMENTATION NOTE: PackageProperty fields from AppInstallerRepositorySearch.h - /// Gets a property of this package. - String Id { get; }; - String Name { get; }; - - /// Gets the installed package information if the package is installed. - PackageVersionInfo InstalledVersion{ get; }; - - /// Gets all available versions of this package. Ordering is not guaranteed. - Windows.Foundation.Collections.IVectorView AvailableVersions { get; }; - - /// Gets the version of this package that will be installed if version is not set in InstallOptions. - PackageVersionInfo DefaultInstallVersion { get; }; - - /// Gets a specific version of this package. - PackageVersionInfo GetPackageVersionInfo(PackageVersionId versionKey); - - /// Gets a value indicating whether an available version is newer than the installed version. - Boolean IsUpdateAvailable { get; }; - - /// DESIGN NOTE: - /// IsSame from IPackage in AppInstallerRepositorySearch is not implemented in V1. - /// Determines if the given IPackage refers to the same package as this one. - /// virtual bool IsSame(const IPackage*) const = 0; - } - - /// IMPLEMENTATION NOTE: CompositeSearchBehavior from AppInstallerRepositorySource.h - /// Search behavior for composite catalogs. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum CompositeSearchBehavior - { - /// Search local catalogs only - LocalCatalogs, - /// Search remote catalogs only, don't check local catalogs for InstalledVersion - RemotePackagesFromRemoteCatalogs, - /// Search remote catalogs, and check local catalogs for InstalledVersion - RemotePackagesFromAllCatalogs, - /// Search both local and remote catalogs. - AllCatalogs, - }; - - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from AppInstallerRepositorySearch.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageFieldMatchOption - { - Equals, - EqualsCaseInsensitive, - StartsWithCaseInsensitive, - ContainsCaseInsensitive, - }; - - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from AppInstallerRepositorySearch.h - /// The field to match on. - /// The values must be declared in order of preference in search results. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageMatchField - { - CatalogDefault, - Id, - Name, - Moniker, - Command, - Tag, - /// DESIGN NOTE: The following PackageFieldMatchOption from AppInstallerRepositorySearch.h are not implemented in V1. - /// PackageFamilyName, - /// ProductCode, - /// NormalizedNameAndPublisher, - }; - - /// IMPLEMENTATION NOTE: PackageMatchFilter from AppInstallerRepositorySearch.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageMatchFilter - { - PackageMatchFilter(); - /// The type of string comparison for matching - PackageFieldMatchOption Option; - /// The field to search - PackageMatchField Field; - /// The value to match - String Value; - /// DESIGN NOTE: "Additional" from RequestMatch AppInstallerRepositorySearch.h is not implemented here. - } - - /// IMPLEMENTATION NOTE: MatchResult from AppInstallerRepositorySearch.h - /// A single result from the search. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass MatchResult - { - /// The package found by the search request. - CatalogPackage CatalogPackage { get; }; - - /// The highest order field on which the package matched the search. - PackageMatchFilter MatchCriteria { get; }; - } - - /// Status of the FindPackages call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum FindPackagesResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions - }; - - /// IMPLEMENTATION NOTE: SearchResult from AppInstallerRepositorySearch.h - /// Search result data returned from FindPackages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass FindPackagesResult - { - /// Error codes - FindPackagesResultStatus Status{ get; }; - - /// The full set of results from the search. - Windows.Foundation.Collections.IVectorView Matches { get; }; - - /// If true, the results were truncated by the given ResultLimit - /// USAGE NOTE: Windows Package Manager does not support result pagination, there is no way to continue - /// getting more results. - Boolean WasLimitExceeded{ get; }; - } - - /// Options for FindPackages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass FindPackagesOptions - { - FindPackagesOptions(); - - /// DESIGN NOTE: - /// This class maps to SearchRequest from AppInstallerRepositorySearch.h - /// That class is a container for data used to filter the available manifests in an package catalog. - /// Its properties can be thought of as: - /// (Query || Selectors...) && Filters... - /// If Query and Selectors are both empty, the starting data set will be the entire database. - /// Everything && Filters... - /// Query is PackageMatchField::CatalogDefault and in the Selector list. - /// USAGE NOTE: Only one selector with PackageMatchField::CatalogDefault is allowed. - - /// Selectors = you have to match at least one selector (if there are no selectors, then nothing is selected) - Windows.Foundation.Collections.IVector Selectors { get; }; - /// Filters = you have to match all filters(if there are no filters, then there is no filtering of selected items) - Windows.Foundation.Collections.IVector Filters{ get; }; - - /// Restricts the length of the returned results to the specified count. - UInt32 ResultLimit; - } - - /// IMPLEMENTATION NOTE: ISource from AppInstallerRepositorySource.h - /// A catalog for searching for packages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalog - { - /// Gets a value indicating whether this package catalog is a composite of other package catalogs, - /// and thus the packages may come from disparate package catalogs as well. - Boolean IsComposite { get; }; - /// The details of the package catalog if it is not a composite. - PackageCatalogInfo Info { get; }; - - /// Searches for Packages in the catalog. - Windows.Foundation.IAsyncOperation FindPackagesAsync(FindPackagesOptions options); - FindPackagesResult FindPackages(FindPackagesOptions options); - } - - /// Status of the Connect call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum ConnectResultStatus - { - Ok, - CatalogError, - }; - - /// Result of the Connect call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass ConnectResult - { - /// Error codes - ConnectResultStatus Status{ get; }; - - PackageCatalog PackageCatalog { get; }; - } - - /// A reference to a catalog that callers can try to Connect. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalogReference - { - /// Gets a value indicating whether this package catalog is a composite of other package catalogs, - /// and thus the packages may come from disparate package catalogs as well. - Boolean IsComposite { get; }; - /// The details of the package catalog if it is not a composite. - PackageCatalogInfo Info { get; }; - - /// Opens a catalog. Required before searching. For remote catalogs (i.e. not Installed and Installing) this - /// may require downloading information from a server. - Windows.Foundation.IAsyncOperation ConnectAsync(); - ConnectResult Connect(); - } - - /// Catalogs with PackageCatalogOrigin Predefined - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PredefinedPackageCatalog - { - OpenWindowsCatalog, - }; - - /// Local Catalogs with PackageCatalogOrigin Predefined - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum LocalPackageCatalog - { - InstalledPackages, - }; - - /// Options for creating a composite catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass CreateCompositePackageCatalogOptions - { - CreateCompositePackageCatalogOptions(); - - /// Create a composite catalog to allow searching a user defined or pre defined source - /// and a local source (Installed packages) together - IVector Catalogs { get; }; - /// Sets the default search behavior if the catalog is a composite catalog. - CompositeSearchBehavior CompositeSearchBehavior; - } - - /// Required install scope for the package. If the package does not have an installer that - /// supports the specified scope the Install call will fail with InstallResultStatus.NoApplicableInstallers - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallScope - { - /// An installer with any install scope is valid. - Any, - /// Only User install scope installers are valid - User, - /// Only System installers will be valid - System, - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallMode - { - /// The default experience for the installer. Installer may show some UI. - Default, - /// Runs the installer in silent mode. This suppresses the installer's UI to the extent - /// possible (installer may still show some required UI). - Silent, - /// Runs the installer in interactive mode. - Interactive, - }; - - /// Options when installing a package. - /// Intended to allow full compatibility with the "winget install" command line interface. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass InstallOptions - { - InstallOptions(); - - /// Optionally specifies the version from the package to install. If unspecified the version matching - /// CatalogPackage.GetLatestVersion() is used. - PackageVersionId PackageVersionId; - - /// Specifies alternate location to install package (if supported). - String PreferredInstallLocation; - /// User or Machine. - PackageInstallScope PackageInstallScope; - /// Silent, Interactive, or Default - PackageInstallMode PackageInstallMode; - /// Directs the logging to a log file. If provided, the installer must have write access to the file - String LogOutputPath; - /// Continues the install even if the hash in the catalog does not match the linked installer. - Boolean AllowHashMismatch; - /// Allows Store installs when Store Client is disabled. - Boolean BypassIsStoreClientBlockedPolicyCheck; - /// A string that will be passed to the installer. - /// IMPLEMENTATION NOTE: maps to "--override" in the winget cmd line - String ReplacementInstallerArguments; - - /// Used by a caller to correlate the install with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - /// A string that will be passed to the source server if using a REST source - String AdditionalPackageCatalogArguments; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageManager - { - PackageManager(); - - /// Get the available catalogs. Each source will have a separate catalog. - /// This does not open the catalog. These catalogs can be used individually or merged with CreateCompositePackageCatalogAsync. - /// IMPLEMENTATION NOTE: This is a list of sources returned by Windows Package Manager source list - Windows.Foundation.Collections.IVectorView GetPackageCatalogs(); - /// Get a built in catalog - PackageCatalogReference GetPredefinedPackageCatalog(PredefinedPackageCatalog predefinedPackageCatalog); - /// Get a built in catalog - PackageCatalogReference GetLocalPackageCatalog(LocalPackageCatalog localPackageCatalog); - /// Get a catalog by a known name - PackageCatalogReference GetPackageCatalogByName(String catalogName); - /// Get a composite catalog to allow searching a user defined or pre defined source and a local source - /// (Installing, Installed) together at the same time. - PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options); - - /// Install the specified package - Windows.Foundation.IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options); - } - - /// Force midl3 to generate vector marshalling info. - declare - { - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - } -} -``` - -# Appendix +# 1. Background + +The Windows Package Manager currently exposes a command line interface to search for packages, +install them, view progress, and more. This API is designed to provide another way for callers to +make use of that functionality. The API will be preferred by callers that want to receive progress +and completion events, and UWP packages that do not have permission to launch command line processes. +The goal for this api is to provide the full set of install functionality possible using the Windows +Package Manager command line. The command line is documented at +https://docs.microsoft.com/windows/package-manager/winget/ + +# 2. Description + +Windows Package Manager is a package manager for windows applications. It comes with a predefined +repository of applications and users can add new repositories using the winget command line. This +API allows packaged apps with the packageManagement capability and other higher privilege processes +to start, manage, and monitor installation of packages that are listed in Windows Package Manager +repositories. + +# 3. Examples + +Sample member values for the following examples: +m_installAppId = L"Microsoft.VisualStudioCode"; + +## 3.1. Create objects + +Creation of objects has to be done through CoCreateInstance rather than normal winrt initialization +since it's hosted by an out of proc com server. These helper methods will be used in the rest of the +examples. + +```c++ (C++ish pseudocode) + AppInstaller CreateAppInstaller() { + return winrt::create_instance(CLSID_AppInstaller, CLSCTX_ALL); + } + InstallOptions CreateInstallOptions() { + return winrt::create_instance(CLSID_InstallOptions, CLSCTX_ALL); + } + FindPackagesOptions CreateFindPackagesOptions() { + return winrt::create_instance(CLSID_FindPackagesOptions, CLSCTX_ALL); + } + CreateCompositeAppCatalogOptions CreateCreateCompositeAppCatalogOptions() { + return winrt::create_instance(CLSID_CreateCompositeAppCatalogOptions, CLSCTX_ALL); + } + PackageMatchFilter CreatePackageMatchFilter() { + return winrt::create_instance(CLSID_PackageMatchFilter, CLSCTX_ALL); + } +``` + +## 3.2. Search + +The api can be used to search for packages in a catalog known to Windows Package Manager. This can +be used to get availability information or start an install. + +```c++ (C++ish pseudocode) + + // Sample of using synchronous methods on background thread. + CatalogPackage MainPage::FindPackageOnBackgroundThread() + { + PackageManager packageManager = CreatePackageManager(); + PackageCatalogReference catalogRef{ + packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) }; + ConnectResult connectResult = catalogRef.Connect(); + if (connectResult.Status() != ConnectResultStatus::Ok) + { + return nullptr; + } + PackageCatalog catalog = connectResult.PackageCatalog(); + + FindPackagesOptions findPackagesOptions = CreateFindPackagesOptions(); + PackageMatchFilter filter = CreatePackageMatchFilter(); + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::Equals); + filter.Value(m_installAppId); + findPackagesOptions.Filters().Append(filter); + // We've already switched to a background thread, so do everything synchronously. + FindPackagesResult findPackagesResult{ catalog.FindPackages(findPackagesOptions) }; + + winrt::IVectorView matches = findPackagesResult.Matches(); + if (matches.Size() == 0) + { + return nullptr; + } + return matches.GetAt(0).CatalogPackage(); + } + + // Sample of using async methods. + IAsyncOperation MainPage::FindPackageInCatalogAsync(PackageCatalog catalog, + std::wstring packageId) + { + FindPackagesOptions findPackagesOptions = CreateFindPackagesOptions(); + PackageMatchFilter filter = CreatePackageMatchFilter(); + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::Equals); + filter.Value(packageId); + findPackagesOptions.Filters().Append(filter); + FindPackagesResult findPackagesResult{ co_await catalog.FindPackagesAsync(findPackagesOptions) }; + + winrt::IVectorView matches = findPackagesResult.Matches(); + if (matches.Size() == 0) + { + co_return nullptr; + } + co_return matches.GetAt(0).CatalogPackage(); + } + + IAsyncOperation MainPage::FindPackageAsync() + { + PackageManager packageManager = CreatePackageManager(); + PackageCatalogReference catalogRef{ + packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) }; + ConnectResult connectResult = catalogRef.Connect(); + if (connectResult.Status() != ConnectResultStatus::Ok) + { + co_return nullptr; + } + PackageCatalog catalog = connectResult.PackageCatalog(); + co_return FindPackageInCatalogAsync(catalog, m_installAppId).get(); + } +``` + +## 3.3. Install + +```c++ (C++ish pseudocode) + + IAsyncOperationWithProgress MainPage::InstallPackage(CatalogPackage package) + { + PackageManager packageManager = CreatePackageManager(); + InstallOptions installOptions = CreateInstallOptions(); + installOptions.PackageInstallScope(PackageInstallScope::Any); + + return packageManager.InstallPackageAsync(package, installOptions); + } + + IAsyncAction UpdateUIProgress( + InstallProgress progress, + winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, + winrt::Windows::UI::Xaml::Controls::TextBlock statusText) + { + co_await winrt::resume_foreground(progressBar.Dispatcher()); + progressBar.Value(progress.DownloadProgress*100); + + std::wstring downloadText{ L"Downloading. " }; + switch (progress.State) + { + case PackageInstallProgressState::Queued: + statusText.Text(L"Queued"); + break; + case PackageInstallProgressState::Downloading: + downloadText += std::to_wstring(progress.BytesDownloaded) + L" bytes of " + std::to_wstring(progress.BytesRequired); + statusText.Text(downloadText); + break; + case PackageInstallProgressState::Installing: + statusText.Text(L"Installing"); + progressBar.IsIndeterminate(true); + break; + case PackageInstallProgressState::PostInstall: + statusText.Text(L"Finishing install"); + break; + case PackageInstallProgressState::Finished: + statusText.Text(L"Finished install."); + progressBar.IsIndeterminate(false); + break; + default: + statusText.Text(L""); + } + co_return; + } + + // This method is called from a background thread. + IAsyncAction UpdateUIForInstall( + IAsyncOperationWithProgress installPackageOperation, + winrt::Windows::UI::Xaml::Controls::Button installButton, + winrt::Windows::UI::Xaml::Controls::Button cancelButton, + winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, + winrt::Windows::UI::Xaml::Controls::TextBlock statusText) + { + if (installPackageOperation) + { + + installPackageOperation.Progress([=]( + IAsyncOperationWithProgress const& /* sender */, + InstallProgress const& progress) + { + UpdateUIProgress(progressBar, statusText, 50, stateStr).get(); + }); + + + winrt::hresult installOperationHr = S_OK; + std::wstring errorMessage{ L"Unknown Error" }; + InstallResult installResult{ nullptr }; + try + { + installResult = co_await installPackageOperation; + } + catch (hresult_canceled const&) + { + errorMessage = L"Cancelled"; + OutputDebugString(L"Operation was cancelled"); + } + catch (...) + { + // Operation failed + // Example: HRESULT_FROM_WIN32(ERROR_DISK_FULL). + installOperationHr = winrt::to_hresult(); + // Example: "There is not enough space on the disk." + errorMessage = winrt::to_message(); + OutputDebugString(L"Operation failed"); + } + + // Switch back to ui thread context. + co_await winrt::resume_foreground(progressBar.Dispatcher()); + + cancelButton.IsEnabled(false); + installButton.IsEnabled(true); + progressBar.IsIndeterminate(false); + + if (installPackageOperation.Status() == AsyncStatus::Canceled) + { + installButton.Content(box_value(L"Retry")); + statusText.Text(L"Install cancelled."); + } + if (installPackageOperation.Status() == AsyncStatus::Error || installResult == nullptr) + { + installButton.Content(box_value(L"Retry")); + statusText.Text(errorMessage); + } + else if (installResult.RebootRequired()) + { + installButton.Content(box_value(L"Install")); + statusText.Text(L"Reboot to finish installation."); + } + else if (installResult.Status() == InstallResultStatus::Ok) + { + installButton.Content(box_value(L"Install")); + statusText.Text(L"Finished."); + } + else + { + installButton.Content(box_value(L"Install")); + statusText.Text(L"Install failed."); + } + } + } + + IAsyncAction MainPage::StartInstall( + winrt::Windows::UI::Xaml::Controls::Button installButton, + winrt::Windows::UI::Xaml::Controls::Button cancelButton, + winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, + winrt::Windows::UI::Xaml::Controls::TextBlock statusText) + { + installButton.IsEnabled(false); + cancelButton.IsEnabled(true); + + co_await winrt::resume_background(); + + PackageManager packageManager = CreatePackageManager(); + PackageCatalogReference catalogRef{ + packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::OpenWindowsCatalog) }; + ConnectResult connectResult = catalogRef.Connect(); + if (connectResult.Status() != ConnectResultStatus::Ok) + { + co_await winrt::resume_foreground(progressBar.Dispatcher()); + statusText.Text(L"Connecting to catalog failed."); + co_return; + } + PackageCatalog catalog = connectResult.PackageCatalog(); + + FindPackagesResult findPackagesResult{ FindPackageOnBackgroundThread(catalog, m_installAppId) }; + + winrt::IVectorView matches = findPackagesResult.Matches(); + if (matches.Size() > 0) + { + m_installPackageOperation = InstallPackage(matches.GetAt(0).CatalogPackage()); + UpdateUIForInstall(m_installPackageOperation, installButton, cancelButton, progressBar, statusText); + } + else + { + co_await winrt::resume_foreground(progressBar.Dispatcher()); + statusText.Text(L"Could not find package."); + co_return; + } + } +``` + +## 3.4.1 Cancel + +The async operation can be stored, or the install code can wait on an event that can be triggered. + +```c++ (C++ish pseudocode) + void MainPage::CancelButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + if (m_installPackageOperation) + { + m_installPackageOperation.Cancel(); + } + } +``` + +## 3.5. Open a catalog by name + +Open a catalog known to the caller. There is no way to use the api to add a catalog, that must be done +on the command line. + +```c++ (C++ish pseudocode) + IAsyncOperation MainPage::FindSourceAsync(std::wstring packageSource) + { + PackageManager packageManager = CreatePackageManager(); + PackageCatalogReference catalogRef{ packageManager.GetPackageCatalogByName(packageSource) }; + if (catalogRef) + { + ConnectResult connectResult{ co_await catalogRef.ConnectAsync() }; + // PackageCatalog will be null if connectResult.ErrorCode() is a failure + PackageCatalog catalog = connectResult.PackageCatalog(); + co_return catalog; + } + } +``` + +# 4 Remarks + +Notes have been added inline throughout the api details. + + +For this api there are multiple similar apis that are +relevant with regard to naming and consistency. There is the Windows Package Manager command line which uses +"source" to describe the various repositories that can host packages and "search" to describe looking up an app. +https://docs.microsoft.com/windows/package-manager/winget/ +There is the Windows::ApplicationModel::PackageCatalog which exists as a Windows API for installing packages +and monitoring their installation progress. +https://docs.microsoft.com/uwp/api/windows.applicationmodel.packagecatalog?view=winrt-19041 +And there is Windows.Management.Deployment.PackageManager which allows packages with the packageManagement +capability to install msix apps and uses "Find" to describe looking up an app +https://docs.microsoft.com/uwp/api/windows.management.deployment.packagemanager?view=winrt-19041 + +This API has aligned with those Windows APIs in using \*Catalog and Find. + +# 5 API Details + +```c# (but really MIDL3) +namespace Microsoft.Management.Deployment +{ + [contractversion(1)] + apicontract WindowsPackageManagerContract{}; + + /// State of the install. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallProgressState + { + /// The install is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from downloading or installing. + Queued, + /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will + /// end the download and prevent the package from installing. + Downloading, + /// The install is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the installation or the post install cleanup. + Installing, + /// The installer has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the install. + PostInstall, + /// The operation has completed. + Finished, + }; + + /// Progress object for the install + /// DESIGN NOTE: percentage for the install as a whole is purposefully not included as there is no way to + /// estimate progress when the installer is running. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + struct InstallProgress + { + /// State of the install + PackageInstallProgressState State; + /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes downloaded if known + UInt64 BytesDownloaded; + /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes required if known + UInt64 BytesRequired; + /// Download percentage completed + Double DownloadProgress; + /// Install percentage if known. + Double InstallationProgress; + }; + + /// Status of the Install call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum InstallResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + DownloadError, + InstallError, + ManifestError, + NoApplicableInstallers, + }; + + /// Result of the install + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass InstallResult + { + /// Used by a caller to correlate the install with a caller's data. + String CorrelationData{ get; }; + /// Whether a restart is required to complete the install. + Boolean RebootRequired{ get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + InstallResultStatus Status{ get; }; + /// Specific error if known, from downloader or installer itself, example ERROR_INSTALL_PACKAGE_REJECTED + HRESULT ExtendedErrorCode{ get; }; + } + + /// IMPLEMENTATION NOTE: SourceOrigin from AppInstallerRepositorySource.h + /// Defines the origin of the package catalog details. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageCatalogOrigin + { + /// Predefined means it came as part of the Windows Package Manager package and cannot be removed. + Predefined, + /// User means it was added by the user and could be removed. + User, + }; + + /// IMPLEMENTATION NOTE: SourceTrustLevel from AppInstallerRepositorySource.h + /// Defines the trust level of the package catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageCatalogTrustLevel + { + None, + Trusted, + }; + + /// IMPLEMENTATION NOTE: SourceDetails from AppInstallerRepositorySource.h + /// Interface for retrieving information about an package catalog without acting on it. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalogInfo + { + /// The package catalog's unique identifier. + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.Winget.Source_8wekyb3d8bbwe" + /// For contoso sample on msdn "contoso" + String Id { get; }; + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name { get; }; + /// The type of the package catalog. + /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". + /// For contoso sample on msdn "Microsoft.PreIndexed.Package" + String Type { get; }; + /// The argument used when adding the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" + /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" + String Argument { get; }; + /// The last time that this package catalog was updated. + Windows.Foundation.DateTime LastUpdateTime { get; }; + /// The origin of the package catalog. + PackageCatalogOrigin Origin { get; }; + /// The trust level of the package catalog + PackageCatalogTrustLevel TrustLevel { get; }; + } + + /// A metadata item of a package version. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageVersionMetadataField + { + /// The InstallerType of an installed package + InstallerType, + /// The Scope of an installed package + InstalledScope, + /// The system path where the package is installed + InstalledLocation, + /// The standard uninstall command; which may be interactive + StandardUninstallCommand, + /// An uninstall command that should be non-interactive + SilentUninstallCommand, + /// The publisher of the package + PublisherDisplayName, + }; + + /// IMPLEMENTATION NOTE: IPackageVersion from AppInstallerRepositorySearch.h + /// A single package version. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageVersionInfo + { + /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from AppInstallerRepositorySearch.h + /// Gets any metadata associated with this package version. + /// Primarily stores data on installed packages. + /// Metadata fields may have no value (e.g. packages that aren't installed will not have an InstalledLocation). + String GetMetadata(PackageVersionMetadataField metadataField); + /// IMPLEMENTATION NOTE: PackageVersionProperty fields from AppInstallerRepositorySearch.h + String Id { get; }; + String DisplayName { get; }; + String Version { get; }; + String Channel { get; }; + /// DESIGN NOTE: RelativePath from AppInstallerRepositorySearch.h is excluded as not needed. + /// String RelativePath; + + /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from AppInstallerRepositorySearch.h + /// PackageFamilyName and ProductCode can have multiple values. + Windows.Foundation.Collections.IVectorView PackageFamilyNames { get; }; + Windows.Foundation.Collections.IVectorView ProductCodes { get; }; + + /// Gets the package catalog where this package version is from. + PackageCatalog PackageCatalog { get; }; + + /// DESIGN NOTE: + /// GetManifest from IPackageVersion in AppInstallerRepositorySearch is not implemented in V1. That class has + /// a lot of fields and no one requesting it. + /// Gets the manifest of this package version. + /// virtual Manifest::Manifest GetManifest() = 0; + } + + /// IMPLEMENTATION NOTE: PackageVersionKey from AppInstallerRepositorySearch.h + /// A key to identify a package version within a package. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageVersionId + { + /// The package catalog id that this version came from. + String PackageCatalogId { get; }; + /// The version. + String Version { get; }; + /// The channel. + String Channel { get; }; + }; + + /// IMPLEMENTATION NOTE: IPackage from AppInstallerRepositorySearch.h + /// A package, potentially containing information about it's local state and the available versions. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass CatalogPackage + { + /// IMPLEMENTATION NOTE: PackageProperty fields from AppInstallerRepositorySearch.h + /// Gets a property of this package. + String Id { get; }; + String Name { get; }; + + /// Gets the installed package information if the package is installed. + PackageVersionInfo InstalledVersion{ get; }; + + /// Gets all available versions of this package. Ordering is not guaranteed. + Windows.Foundation.Collections.IVectorView AvailableVersions { get; }; + + /// Gets the version of this package that will be installed if version is not set in InstallOptions. + PackageVersionInfo DefaultInstallVersion { get; }; + + /// Gets a specific version of this package. + PackageVersionInfo GetPackageVersionInfo(PackageVersionId versionKey); + + /// Gets a value indicating whether an available version is newer than the installed version. + Boolean IsUpdateAvailable { get; }; + + /// DESIGN NOTE: + /// IsSame from IPackage in AppInstallerRepositorySearch is not implemented in V1. + /// Determines if the given IPackage refers to the same package as this one. + /// virtual bool IsSame(const IPackage*) const = 0; + } + + /// IMPLEMENTATION NOTE: CompositeSearchBehavior from AppInstallerRepositorySource.h + /// Search behavior for composite catalogs. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum CompositeSearchBehavior + { + /// Search local catalogs only + LocalCatalogs, + /// Search remote catalogs only, don't check local catalogs for InstalledVersion + RemotePackagesFromRemoteCatalogs, + /// Search remote catalogs, and check local catalogs for InstalledVersion + RemotePackagesFromAllCatalogs, + /// Search both local and remote catalogs. + AllCatalogs, + }; + + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from AppInstallerRepositorySearch.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageFieldMatchOption + { + Equals, + EqualsCaseInsensitive, + StartsWithCaseInsensitive, + ContainsCaseInsensitive, + }; + + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from AppInstallerRepositorySearch.h + /// The field to match on. + /// The values must be declared in order of preference in search results. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageMatchField + { + CatalogDefault, + Id, + Name, + Moniker, + Command, + Tag, + /// DESIGN NOTE: The following PackageFieldMatchOption from AppInstallerRepositorySearch.h are not implemented in V1. + /// PackageFamilyName, + /// ProductCode, + /// NormalizedNameAndPublisher, + }; + + /// IMPLEMENTATION NOTE: PackageMatchFilter from AppInstallerRepositorySearch.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageMatchFilter + { + PackageMatchFilter(); + /// The type of string comparison for matching + PackageFieldMatchOption Option; + /// The field to search + PackageMatchField Field; + /// The value to match + String Value; + /// DESIGN NOTE: "Additional" from RequestMatch AppInstallerRepositorySearch.h is not implemented here. + } + + /// IMPLEMENTATION NOTE: MatchResult from AppInstallerRepositorySearch.h + /// A single result from the search. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass MatchResult + { + /// The package found by the search request. + CatalogPackage CatalogPackage { get; }; + + /// The highest order field on which the package matched the search. + PackageMatchFilter MatchCriteria { get; }; + } + + /// Status of the FindPackages call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum FindPackagesResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions + }; + + /// IMPLEMENTATION NOTE: SearchResult from AppInstallerRepositorySearch.h + /// Search result data returned from FindPackages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass FindPackagesResult + { + /// Error codes + FindPackagesResultStatus Status{ get; }; + + /// The full set of results from the search. + Windows.Foundation.Collections.IVectorView Matches { get; }; + + /// If true, the results were truncated by the given ResultLimit + /// USAGE NOTE: Windows Package Manager does not support result pagination, there is no way to continue + /// getting more results. + Boolean WasLimitExceeded{ get; }; + } + + /// Options for FindPackages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass FindPackagesOptions + { + FindPackagesOptions(); + + /// DESIGN NOTE: + /// This class maps to SearchRequest from AppInstallerRepositorySearch.h + /// That class is a container for data used to filter the available manifests in an package catalog. + /// Its properties can be thought of as: + /// (Query || Selectors...) && Filters... + /// If Query and Selectors are both empty, the starting data set will be the entire database. + /// Everything && Filters... + /// Query is PackageMatchField::CatalogDefault and in the Selector list. + /// USAGE NOTE: Only one selector with PackageMatchField::CatalogDefault is allowed. + + /// Selectors = you have to match at least one selector (if there are no selectors, then nothing is selected) + Windows.Foundation.Collections.IVector Selectors { get; }; + /// Filters = you have to match all filters(if there are no filters, then there is no filtering of selected items) + Windows.Foundation.Collections.IVector Filters{ get; }; + + /// Restricts the length of the returned results to the specified count. + UInt32 ResultLimit; + } + + /// IMPLEMENTATION NOTE: ISource from AppInstallerRepositorySource.h + /// A catalog for searching for packages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalog + { + /// Gets a value indicating whether this package catalog is a composite of other package catalogs, + /// and thus the packages may come from disparate package catalogs as well. + Boolean IsComposite { get; }; + /// The details of the package catalog if it is not a composite. + PackageCatalogInfo Info { get; }; + + /// Searches for Packages in the catalog. + Windows.Foundation.IAsyncOperation FindPackagesAsync(FindPackagesOptions options); + FindPackagesResult FindPackages(FindPackagesOptions options); + } + + /// Status of the Connect call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum ConnectResultStatus + { + Ok, + CatalogError, + }; + + /// Result of the Connect call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass ConnectResult + { + /// Error codes + ConnectResultStatus Status{ get; }; + + PackageCatalog PackageCatalog { get; }; + } + + /// A reference to a catalog that callers can try to Connect. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalogReference + { + /// Gets a value indicating whether this package catalog is a composite of other package catalogs, + /// and thus the packages may come from disparate package catalogs as well. + Boolean IsComposite { get; }; + /// The details of the package catalog if it is not a composite. + PackageCatalogInfo Info { get; }; + + /// Opens a catalog. Required before searching. For remote catalogs (i.e. not Installed and Installing) this + /// may require downloading information from a server. + Windows.Foundation.IAsyncOperation ConnectAsync(); + ConnectResult Connect(); + } + + /// Catalogs with PackageCatalogOrigin Predefined + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PredefinedPackageCatalog + { + OpenWindowsCatalog, + }; + + /// Local Catalogs with PackageCatalogOrigin Predefined + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum LocalPackageCatalog + { + InstalledPackages, + }; + + /// Options for creating a composite catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass CreateCompositePackageCatalogOptions + { + CreateCompositePackageCatalogOptions(); + + /// Create a composite catalog to allow searching a user defined or pre defined source + /// and a local source (Installed packages) together + IVector Catalogs { get; }; + /// Sets the default search behavior if the catalog is a composite catalog. + CompositeSearchBehavior CompositeSearchBehavior; + } + + /// Required install scope for the package. If the package does not have an installer that + /// supports the specified scope the Install call will fail with InstallResultStatus.NoApplicableInstallers + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallScope + { + /// An installer with any install scope is valid. + Any, + /// Only User install scope installers are valid + User, + /// Only System installers will be valid + System, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + /// Options when installing a package. + /// Intended to allow full compatibility with the "winget install" command line interface. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass InstallOptions + { + InstallOptions(); + + /// Optionally specifies the version from the package to install. If unspecified the version matching + /// CatalogPackage.GetLatestVersion() is used. + PackageVersionId PackageVersionId; + + /// Specifies alternate location to install package (if supported). + String PreferredInstallLocation; + /// User or Machine. + PackageInstallScope PackageInstallScope; + /// Silent, Interactive, or Default + PackageInstallMode PackageInstallMode; + /// Directs the logging to a log file. If provided, the installer must have write access to the file + String LogOutputPath; + /// Continues the install even if the hash in the catalog does not match the linked installer. + Boolean AllowHashMismatch; + /// Allows Store installs when Store Client is disabled. + Boolean BypassIsStoreClientBlockedPolicyCheck; + /// A string that will be passed to the installer. + /// IMPLEMENTATION NOTE: maps to "--override" in the winget cmd line + String ReplacementInstallerArguments; + + /// Used by a caller to correlate the install with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + /// A string that will be passed to the source server if using a REST source + String AdditionalPackageCatalogArguments; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageManager + { + PackageManager(); + + /// Get the available catalogs. Each source will have a separate catalog. + /// This does not open the catalog. These catalogs can be used individually or merged with CreateCompositePackageCatalogAsync. + /// IMPLEMENTATION NOTE: This is a list of sources returned by Windows Package Manager source list + Windows.Foundation.Collections.IVectorView GetPackageCatalogs(); + /// Get a built in catalog + PackageCatalogReference GetPredefinedPackageCatalog(PredefinedPackageCatalog predefinedPackageCatalog); + /// Get a built in catalog + PackageCatalogReference GetLocalPackageCatalog(LocalPackageCatalog localPackageCatalog); + /// Get a catalog by a known name + PackageCatalogReference GetPackageCatalogByName(String catalogName); + /// Get a composite catalog to allow searching a user defined or pre defined source and a local source + /// (Installing, Installed) together at the same time. + PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options); + + /// Install the specified package + Windows.Foundation.IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options); + } + + /// Force midl3 to generate vector marshalling info. + declare + { + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + } +} +``` + +# Appendix diff --git a/doc/specs/Configuration-COM-API.md b/doc/specs/Configuration-COM-API.md index d2ee59c44e..092f4028cb 100644 --- a/doc/specs/Configuration-COM-API.md +++ b/doc/specs/Configuration-COM-API.md @@ -1,805 +1,805 @@ - - -Microsoft.Management.Configuration API -=== - -# Background -This API is being added to enable the Developer+ configuration scenarios. It enables interacting with -configuration sets in three contexts: - -1. Loading an existing configuration set from a stream -2. Loading previously applied configuration sets from the local history -3. Authoring a new/editing an existing configuration - -These configuration sets are composed of configuration units, which describe the individual configurable -items and the values to configure. - -Configuration actions consist of: - -1. Test :: Determining whether the system state matches the described state -2. Get :: Extracting the current system state with respect to the configuration scope -3. Set :: Applying the described state to the system - -This API is also intended to support multiple processes watching for state changes, both for the -configuration set lifetimes and the individual configuration unit states. - -# Conceptual pages (How To) - -_(Add conceptual documentation that will go to docs.microsoft.com "how to" page if needed)_ - -# API Pages - - - -## ConfigurationSetState enumeration - -The state of a configuration set in the configuration history. - -| Name | Description | -|-|-| -| Unknown | Primarily used for a configuration set that has not been applied. | -| Pending | The configuration set has been recorded into the history, but has not yet begun applying. | -| InProgress | The configuration set has begun being applied to the system. | -| Completed | The configuration set has completed being applied. | - -## ConfigurationUnitState enumeration - -The state of a configuration unit in the configuration history. - -| Name | Description | -|-|-| -| Unknown | Primarily used for a configuration unit that has not been applied. | -| Pending | The configuration unit has been recorded into the history, but has not yet begun applying. | -| InProgress | The configuration unit has begun being applied to the system. | -| Completed | The configuration unit has completed being applied; the result information will contain additional details. | -| Skipped | The configuration unit was skipped; the result information will contain additional details on the reason. | - -## ConfigurationUnitDetailLevel enumeration - -Defines the level of detail probing that is allowed about a configuration unit. - -| Name | Description | -|-|-| -| Local | Only reads details from local data. | -| Catalog | Will query the catalog information for details, but will not download any modules. | -| Download | Will download modules, but not load them. | -| Load | Will download and load modules for details. | - -## ConfigurationUnitResultInformation class - -Information on a result for a single unit of configuration. - -The class is used both in reporting results through the `ConfigurationSet.ConfigurationSetChange` event as they occur -and in viewing past results via the `ConfigurationUnit.ResultInformation` property on a historical record. - -## IConfigurationUnitSettingDetails interface - -Provides information for a specific configuration unit setting. - -The properties on this interface are useful for creating a rich authoring experience. - -## IConfigurationUnitSettingDetails.Semantics schema - -> _TODO: Define the meaning/schema for this value_ - -## IConfigurationUnitProcessorDetails interface - -Provides information for a specific configuration unit within the runtime. - -The properties on this interface are useful for informing the user about the provenance of the code that is -responsible for processing the configuration unit. - -## ConfigurationUnit class - -A single unit of configuration. - -Represents the smallest actionable configuration element. - -## ConfigurationUnit constructor - -Creates an empty configuration unit for authoring purposes. - -```C# -ConfigurationUnit(); -``` - -## ConfigurationUnit properties - -| Name | Description | -|-|-| -| UnitName | The name of the unit being configured; not a name for this instance. | -| InstanceIdentifier | An identifier used to uniquely identify the instance of a configuration unit on the system. | -| Identifier | The identifier name of this instance within the set. This value is referenced by other unit's `Dependencies`. | -| Dependencies | The identifiers of the configuration units that this unit depends on. | -| Directives | Contains the values that are for use by the configuration system, related to this unit. | -| Settings | Contains the values that are for use by the configuration unit itself. | -| Details | Contains information on the origin of the configuration unit. You must call `ConfigurationProcessor.Get*DetailsAsync` to populate this value. | -| State | The current state of the configuration unit. | -| ResultInformation | Contains information on the result of the latest attempt to apply the configuration unit. | -| ShouldApply | Allows for control over whether this unit should be applied when the set containing it is applied. | - -## ConfigurationUnit.Directives known values - -> _TODO: List of well known directives_ - -## ConfigurationSetChangeEventType enumeration - -The change event type that has occurred for a configuration set change. - -| Name | Description | -|-|-| -| Unknown | For future use if the caller is not aware of newer change types. | -| SetStateChanged | The state of the configuration set has changed. | -| UnitStateChanged | The state of a configuration unit has changed. | - -## ConfigurationSetChangeData class - -The change data sent about changes to a specific set. - -This class is sent to subscribers of the `ConfigurationSet.ConfigurationSetChange` event, containing information -about the specific change that occurred. - -## ConfigurationSet class - -A configuration set contains a collection of configuration units and details about the set. - -Represents a self contained group of configuration units that are operated on together. - -## ConfigurationSet constructors - -Creates an empty configuration set for authoring purposes. - -```C# -ConfigurationSet(); -``` - -Loads a configuration set from the given stream. - -```C# -ConfigurationSet(Windows.Storage.Streams.IInputStream stream); -``` - -## ConfigurationSet.ConfigurationSetChange event - -State changes for this set and it's units are sent to subscribers of this event. - -```C# -event Windows.Foundation.TypedEventHandler ConfigurationSetChange; -``` - -## ConfigurationSet.Serialize method - -Serializes the configuration set to the given output stream. - -```C# -void Serialize(Windows.Storage.Streams.IOutputStream stream); -``` - -## ConfigurationSet.Remove method - -Removes the configuration set from the recorded history, if present. - -```C# -void Remove(); -``` - -You can use this method to remove a configuration set that is no longer relevant. For example, it may have been for -a repository that is no longer being used by the user and future conflicts with it's configuration are not important. - -## ConfigurationSet properties - -| Name | Description | -|-|-| -| Name | The name of the set; if from a file this could be the file name. | -| Origin | The origin of the set; if it came from a repository it could be the remote URL (ex. https://github.com/microsoft/winget-cli.git). | -| InstanceIdentifier | An identifier used to uniquely identify the instance of a configuration set on the system. | -| State | The state that the set is in. | -| InitialIntent | The time that this set was recorded with intent to apply. | -| ApplyBegun | The time that this set was last started to be applied. | -| ApplyEnded | The time that this set was last finished being applied (does not indicate success). | -| ConfigurationUnits | The configuration units that are part of this set. | - -## IConfigurationUnitProcessor interface - -Provides access to a specific configuration unit within the runtime. - -This interface is the primary mechanism used to actually read and write configuration to the system, -but it is not expected that you would use this directly as a consumer of Microsoft.Management.Configuration. - -## IConfigurationSetProcessor interface - -Contains the lifetime of the processing action for a configuration set. - -This interface is used to contain the lifetime of a processing action, -but it is not expected that you would use this directly as a consumer of Microsoft.Management.Configuration. - -## IConfigurationProcessorFactory interface - -Allows different runtimes to provide specialized handling of configuration processing. - -It is not expected that you would use this interface directly, but rather the `ConfigurationProcessor` class. - -_Spec note: A separate binary (written by us) will contain the implementation(s) of this interface._ - -## DiagnosticLevel enumeration - -Indicates the importance of diagnostic information. - -| Name | Description | -|-|-| -| Verbose | Most useful for debugging scenarios; likely too much for general use. | -| Informational | Details that can be useful for understanding what is happening. | -| Warning | Indicates some abnormal condition, but that is not expected to impact functionality. | -| Error | An error has occurred, but this does not necessarily mean that it will halt the operation. | -| Critical | A serious, fatal condition has been encountered. | - -## DiagnosticInformation class - -Contains diagnostic information from the configuration system that can be passed along to the user or log files. -This is not intended as primary information, and is thus not localized. - -## ConfigurationConflictType enumeration - -The type of conflict between configuration sets that was detected. - -| Name | Description | -|-|-| -| Unknown | For future use if the caller is not aware of newer conflict types. | -| MatchingOrigin | Indicates that the first configuration set has a matching name and origin to the second, which has already been applied. | -| IdenticalSetApplied | Indicates that the first configuration set is identical to the second, which has already been applied. | -| SettingsConflict | Indicates a conflict between the settings of two configuration units. | - -## ConfigurationConflictSetting class - -Describes a conflict between a setting of two configuration units. - -## ConfigurationConflict class - -Describes a conflict between two configuration sets. - -## ApplyConfigurationSetFlags enumeration - -Flags to control how a configuration set should be applied to the system. - -| Name | Description | -|-|-| -| None | The configuration set should be applied in the default manner. | -| DoNotOverwriteMatchingOriginSet | Forces a new configuration set instance to be recorded when the set being applied matches a previous set's origin. The default behavior is to assume that the incoming set is an update to the existing set and overwrite it. | - -## ConfigurationChangeEventType enumeration - -The configuration set change event type that has occurred. - -| Name | Description | -|-|-| -| Unknown | For future use if the caller is not aware of newer change types. | -| SetAdded | A new configuration set was recorded in the history with the intent to be applied. | -| SetStateChanged | A configuration set has changed state. | -| SetRemoved | A configuration set has been removed from the history. | - -## ConfigurationChangeEventType class - -The change data sent about changes to sets. - -## ConfigurationProcessor class - -The configuration processor is responsible for the interactions with the system. - -You must use this class to do anything beyond reading configuration sets. It is the entrypoint for all actions that -will interact with the actual system configuration. - -## ConfigurationProcessor constructor - -Creates a configuration processor using the given configuration factory. - -```C# -ConfigurationProcessor(IConfigurationProcessorFactory factory); -``` - -> _TODO: Add details on the mechanics of creating the `IConfigurationProcessorFactory` objects that we provide._ - -## ConfigurationProcessor.CheckForConflicts(Async) method - -Checks for conflicts amongst the configuration sets provided, optionally including the configuration sets already applied to the system. - -```C# -Windows.Foundation.Collections.IVectorView CheckForConflicts(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); - -Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVectorView > CheckForConflictsAsync(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); -``` - -This method should be used on any configuration set that is opened in order to determine if it would cause a conflict -with previously applied configurations. It should be called *after* setting the `Name` and `Origin` in order to determine -if it is a potential update. - -## ConfigurationProcessor.GetSetDetails(Async) method - -Gets the details for all configuration units in a set. - -```C# -void GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); - -Windows.Foundation.IAsyncAction GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); -``` - -This is a convenience/optimization method that will do the same thing as calling `GetUnitDetails(Async)` on each -configuration unit in the set. See `GetUnitDetails(Async)` for more information on what it will do. - -## ConfigurationProcessor.GetUnitDetails(Async) method - -Gets the details for all configuration units in a set. - -```C# -void GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); - -Windows.Foundation.IAsyncAction GetUnitDetailsAsync(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); -``` - -This method will get the details about a specific configuration unit and make them available via `ConfigurationUnit.Details`. -The `detailLevel` parameter allows control over how deeply to probe for details. It is an analog for the amount of -trust to place in the configuration unit processor. - -## ConfigurationProcessor.ApplySet(Async) method - -Applies the configuration set state to the system. - -```C# -ApplyConfigurationSetResult ApplySet(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); - -Windows.Foundation.IAsyncOperationWithProgress ApplySetAsync(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); -``` - -Using the async method and it's progress is more efficient than subscribing to the `ConfigurationSetChange` event before calling this method. - -## ConfigurationProcessor.TestSet(Async) method - -Tests if the system state matches the state described by the configuration set. - -```C# -TestConfigurationSetResult TestSet(ConfigurationSet configurationSet); - -Windows.Foundation.IAsyncOperationWithProgress TestSetAsync(ConfigurationSet configurationSet); -``` - -## ConfigurationProcessor.GetSettings(Async) method - -Gets the current configuration unit settings from the system state. - -```C# -GetConfigurationUnitSettingsResult GetSettings(ConfigurationUnit unit); - -Windows.Foundation.IAsyncOperation GetSettingsAsync(ConfigurationUnit unit); -``` - -## ConfigurationProcessor.Diagnostics event - -Enables listening to internal diagnostics events for logging purposes. - -## ConfigurationProcessor.ConfigurationChange event - -Signals changes to the set of configuration sets in the history, as well as changes to the state of configuration sets in the history. - -## ConfigurationProcessor.GetConfigurationHistory method - -Gets the configuration sets from the recorded history. - -```C# -Windows.Foundation.Collections.IVectorView GetConfigurationHistory(); -``` - -Gets the configuration sets that have already been applied or those recorded with the intent to be applied. This may include in progress sets or those that are waiting to be applied. - -# API Details - -[Link to the MIDL3 file.](../../src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl) - -# Appendix - - - -# Sample - -This sample illustrates some of the expected usage patterns. - -```C# -using Microsoft.Management.Configuration; -using System; -using System.Collections.Generic; -using System.IO; -using System.Reflection; -using Windows.Foundation; -using Windows.Foundation.Collections; -using Windows.Storage; -using Windows.Storage.Streams; - -namespace ConfigurationSample -{ - internal static class Helpers - { - internal static IConfigurationProcessorFactory CreateIConfigurationProcessorFactory() - { - throw new NotImplementedException(); - } - - internal static ConfigurationSet OpenConfigurationSet(string filePath, ConfigurationProcessor processor) - { - var fileOperation = FileRandomAccessStream.OpenAsync(filePath, FileAccessMode.Read); - fileOperation.AsTask().Wait(); - var file = fileOperation.GetResults(); - OpenConfigurationSetResult result = processor.OpenConfigurationSet(file); - - if (result.Set != null) - { - return result.Set; - } - - Console.WriteLine($"Failed opening configuration set: 0x{result.ResultCode:X} at {result.Field}"); - return null; - } - - internal static void SetWatcher(ConfigurationSet set, ConfigurationSetChangeData data) - { - Console.WriteLine($" - Set: {set.Name} [{set.InstanceIdentifier}]"); - Console.WriteLine($" Change: {data.Change}"); - Console.WriteLine($" Set State: {data.SetState}"); - switch (data.Change) - { - case ConfigurationSetChangeEventType.UnitStateChanged: - Console.WriteLine($" Unit: {data.Unit.UnitName} [{data.Unit.InstanceIdentifier}]"); - Console.WriteLine($" Unit State: {data.UnitState}"); - if (data.UnitState == ConfigurationUnitState.Completed && data.ResultInformation.ResultCode != null) - { - Console.WriteLine($" Failure: {data.ResultInformation.Description} [{data.ResultInformation.ResultCode.HResult}]"); - } - break; - } - } - } - - internal class ApplyProgressWatcher - { - private bool isFirstProgress = true; - - internal void Watcher(IAsyncOperationWithProgress operation, ConfigurationSetChangeData data) - { - if (isFirstProgress) - { - isFirstProgress = false; - - // If our first progress callback contains partial results, output them as if they had been called through progress - ApplyConfigurationSetResult partialResult = operation.GetResults(); - - foreach (ApplyConfigurationUnitResult unitResult in partialResult.UnitResults) - { - HandleUnitProgress(unitResult.Unit, unitResult.State, unitResult.ResultInformation); - } - } - - switch (data.Change) - { - case ConfigurationSetChangeEventType.SetStateChanged: - Console.WriteLine($" - Set State: {data.SetState}"); - break; - case ConfigurationSetChangeEventType.UnitStateChanged: - HandleUnitProgress(data.Unit, data.UnitState, data.ResultInformation); - break; - } - } - - private void HandleUnitProgress(ConfigurationUnit unit, ConfigurationUnitState state, ConfigurationUnitResultInformation resultInformation) - { - switch (state) - { - case ConfigurationUnitState.Pending: - break; - case ConfigurationUnitState.InProgress: - case ConfigurationUnitState.Completed: - case ConfigurationUnitState.Skipped: - Console.WriteLine($" - Unit: {unit.UnitName} [{unit.InstanceIdentifier}]"); - Console.WriteLine($" Unit State: {state}"); - if (resultInformation.ResultCode != null) - { - Console.WriteLine($" HRESULT: [0x{resultInformation.ResultCode.HResult:X8}]"); - Console.WriteLine($" Reason: {resultInformation.Description}"); - } - break; - case ConfigurationUnitState.Unknown: - break; - } - } - } - - internal class Program - { - static void LoadAndOutput(string[] args) - { - ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); - - // Open the given configuration file - ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor); - if (configSet == null) - { - return; - } - - // Output some of the information from the set - Console.WriteLine($"Configuration Set: {args[1]}"); - - foreach (ConfigurationUnit unit in configSet.ConfigurationUnits) - { - Console.WriteLine($" - Configuration Unit: {unit.UnitName}"); - if (!string.IsNullOrEmpty(unit.Identifier)) - { - Console.WriteLine($" Identifier: {unit.Identifier}"); - } - Console.WriteLine($" Intent: {unit.Intent}"); - IReadOnlyList dependencies = unit.Dependencies; - if (dependencies.Count > 0) - { - Console.WriteLine(" Dependencies:"); - foreach (string dependency in dependencies) - { - Console.WriteLine($" {dependency}"); - } - } - ValueSet directives = unit.Directives; - if (directives.Count > 0) - { - Console.WriteLine(" Directives:"); - foreach (var directive in unit.Directives) - { - Console.WriteLine($" {directive.Key}: {directive.Value}"); - } - } - ValueSet settings = unit.Settings; - if (settings.Count > 0) - { - Console.WriteLine(" Settings:"); - foreach (var setting in unit.Settings) - { - Console.WriteLine($" {setting.Key}: {setting.Value}"); - } - } - } - } - - static void LoadAndCheckConflicts(string[] args) - { - // Create the factory and processor - ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); - - // Open the given configuration file - ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor); - if (configSet == null) - { - return; - } - - // Set a name and origin for this set so that we can see it in the conflict info - configSet.Name = Path.GetFileName(args[1]); - configSet.Origin = args[1]; - - // Check for conflicts with existing configurations - List configSets = new List() { configSet }; - IList conflicts = processor.CheckForConflicts(configSets, true); - - Console.WriteLine($"Conflicts with Configuration Set: {args[1]}"); - - foreach (ConfigurationConflict conflict in conflicts) - { - Console.WriteLine($" - Conflict: {conflict.Conflict}"); - Console.WriteLine($" First Set: {conflict.FirstSet.Name} [{conflict.FirstSet.Origin}]"); - Console.WriteLine($" Second Set: {conflict.SecondSet.Name} [{conflict.SecondSet.Origin}]"); - if (conflict.Conflict == ConfigurationConflictType.SettingsConflict) - { - Console.WriteLine($" First Unit: {conflict.FirstUnit.UnitName} [{conflict.FirstUnit.InstanceIdentifier}]"); - Console.WriteLine($" Second Unit: {conflict.SecondUnit.UnitName} [{conflict.SecondUnit.InstanceIdentifier}]"); - foreach (ConfigurationConflictSetting setting in conflict.Settings) - { - Console.WriteLine($" - Setting: {setting.Name}"); - Console.WriteLine($" First Value: {setting.FirstValue}"); - Console.WriteLine($" Second Value: {setting.SecondValue}"); - } - } - } - } - - static void LoadAndApply(string[] args) - { - // Create the factory and processor - ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); - - // Open the given configuration file - ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor); - if (configSet == null) - { - return; - } - - Console.WriteLine($"Applying Configuration Set: {args[1]}"); - - ApplyProgressWatcher watcher = new ApplyProgressWatcher(); - - var operation = processor.ApplySetAsync(configSet, ApplyConfigurationSetFlags.None); - operation.Progress = watcher.Watcher; - operation.AsTask().Wait(); - ApplyConfigurationSetResult result = operation.GetResults(); - - Console.WriteLine($" - Done: {result.ResultCode.HResult}"); - } - - static void GetHistoryAndWatchEverything(string[] args) - { - Console.WriteLine("Watching all configuration [press Enter to stop]:"); - - // Create the factory and processor - ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); - - List list = new List(); - - // Attach to the top level change event - processor.ConfigurationChange += (ConfigurationSet incomingSet, ConfigurationChangeData data) => - { - int existingSetIndex = -1; - - lock (list) - { - for (int i = 0; i < list.Count; ++i) - { - if (list[i].InstanceIdentifier == data.InstanceIdentifier) - { - existingSetIndex = i; - break; - } - } - - if (data.Change == ConfigurationChangeEventType.SetAdded || data.Change == ConfigurationChangeEventType.SetStateChanged) - { - if (existingSetIndex == -1) - { - incomingSet.ConfigurationSetChange += Helpers.SetWatcher; - list.Add(incomingSet); - } - } - else // Removed - { - if (existingSetIndex != -1) - { - list[existingSetIndex].ConfigurationSetChange -= Helpers.SetWatcher; - list.RemoveAt(existingSetIndex); - } - } - } - - Console.WriteLine($" - Set: {data.InstanceIdentifier}"); - Console.WriteLine($" Change: {data.Change}"); - }; - - foreach (ConfigurationSet set in processor.GetConfigurationHistory()) - { - int existingSetIndex = -1; - - lock (list) - { - for (int i = 0; i < list.Count; ++i) - { - if (list[i].InstanceIdentifier == set.InstanceIdentifier) - { - existingSetIndex = i; - break; - } - } - - if (existingSetIndex == -1) - { - set.ConfigurationSetChange += Helpers.SetWatcher; - list.Add(set); - } - } - - if (existingSetIndex == -1) - { - Console.WriteLine($" - Set: {set.Name} [{set.InstanceIdentifier}]"); - Console.WriteLine($" State: {set.State}"); - } - } - - // Wait for user to press enter - Console.ReadLine(); - } - - static void Main(string[] args) - { - var method = typeof(Program).GetMethod(args[0], BindingFlags.NonPublic | BindingFlags.Static); - - if (method != null) - { - method.Invoke(null, new object[]{ args }); - } - else - { - Console.WriteLine($"{args[0]} is not a sample"); - } - } - } -} - -``` + + +Microsoft.Management.Configuration API +=== + +# Background +This API is being added to enable the Developer+ configuration scenarios. It enables interacting with +configuration sets in three contexts: + +1. Loading an existing configuration set from a stream +2. Loading previously applied configuration sets from the local history +3. Authoring a new/editing an existing configuration + +These configuration sets are composed of configuration units, which describe the individual configurable +items and the values to configure. + +Configuration actions consist of: + +1. Test :: Determining whether the system state matches the described state +2. Get :: Extracting the current system state with respect to the configuration scope +3. Set :: Applying the described state to the system + +This API is also intended to support multiple processes watching for state changes, both for the +configuration set lifetimes and the individual configuration unit states. + +# Conceptual pages (How To) + +_(Add conceptual documentation that will go to docs.microsoft.com "how to" page if needed)_ + +# API Pages + + + +## ConfigurationSetState enumeration + +The state of a configuration set in the configuration history. + +| Name | Description | +|-|-| +| Unknown | Primarily used for a configuration set that has not been applied. | +| Pending | The configuration set has been recorded into the history, but has not yet begun applying. | +| InProgress | The configuration set has begun being applied to the system. | +| Completed | The configuration set has completed being applied. | + +## ConfigurationUnitState enumeration + +The state of a configuration unit in the configuration history. + +| Name | Description | +|-|-| +| Unknown | Primarily used for a configuration unit that has not been applied. | +| Pending | The configuration unit has been recorded into the history, but has not yet begun applying. | +| InProgress | The configuration unit has begun being applied to the system. | +| Completed | The configuration unit has completed being applied; the result information will contain additional details. | +| Skipped | The configuration unit was skipped; the result information will contain additional details on the reason. | + +## ConfigurationUnitDetailLevel enumeration + +Defines the level of detail probing that is allowed about a configuration unit. + +| Name | Description | +|-|-| +| Local | Only reads details from local data. | +| Catalog | Will query the catalog information for details, but will not download any modules. | +| Download | Will download modules, but not load them. | +| Load | Will download and load modules for details. | + +## ConfigurationUnitResultInformation class + +Information on a result for a single unit of configuration. + +The class is used both in reporting results through the `ConfigurationSet.ConfigurationSetChange` event as they occur +and in viewing past results via the `ConfigurationUnit.ResultInformation` property on a historical record. + +## IConfigurationUnitSettingDetails interface + +Provides information for a specific configuration unit setting. + +The properties on this interface are useful for creating a rich authoring experience. + +## IConfigurationUnitSettingDetails.Semantics schema + +> _TODO: Define the meaning/schema for this value_ + +## IConfigurationUnitProcessorDetails interface + +Provides information for a specific configuration unit within the runtime. + +The properties on this interface are useful for informing the user about the provenance of the code that is +responsible for processing the configuration unit. + +## ConfigurationUnit class + +A single unit of configuration. + +Represents the smallest actionable configuration element. + +## ConfigurationUnit constructor + +Creates an empty configuration unit for authoring purposes. + +```C# +ConfigurationUnit(); +``` + +## ConfigurationUnit properties + +| Name | Description | +|-|-| +| UnitName | The name of the unit being configured; not a name for this instance. | +| InstanceIdentifier | An identifier used to uniquely identify the instance of a configuration unit on the system. | +| Identifier | The identifier name of this instance within the set. This value is referenced by other unit's `Dependencies`. | +| Dependencies | The identifiers of the configuration units that this unit depends on. | +| Directives | Contains the values that are for use by the configuration system, related to this unit. | +| Settings | Contains the values that are for use by the configuration unit itself. | +| Details | Contains information on the origin of the configuration unit. You must call `ConfigurationProcessor.Get*DetailsAsync` to populate this value. | +| State | The current state of the configuration unit. | +| ResultInformation | Contains information on the result of the latest attempt to apply the configuration unit. | +| ShouldApply | Allows for control over whether this unit should be applied when the set containing it is applied. | + +## ConfigurationUnit.Directives known values + +> _TODO: List of well known directives_ + +## ConfigurationSetChangeEventType enumeration + +The change event type that has occurred for a configuration set change. + +| Name | Description | +|-|-| +| Unknown | For future use if the caller is not aware of newer change types. | +| SetStateChanged | The state of the configuration set has changed. | +| UnitStateChanged | The state of a configuration unit has changed. | + +## ConfigurationSetChangeData class + +The change data sent about changes to a specific set. + +This class is sent to subscribers of the `ConfigurationSet.ConfigurationSetChange` event, containing information +about the specific change that occurred. + +## ConfigurationSet class + +A configuration set contains a collection of configuration units and details about the set. + +Represents a self contained group of configuration units that are operated on together. + +## ConfigurationSet constructors + +Creates an empty configuration set for authoring purposes. + +```C# +ConfigurationSet(); +``` + +Loads a configuration set from the given stream. + +```C# +ConfigurationSet(Windows.Storage.Streams.IInputStream stream); +``` + +## ConfigurationSet.ConfigurationSetChange event + +State changes for this set and it's units are sent to subscribers of this event. + +```C# +event Windows.Foundation.TypedEventHandler ConfigurationSetChange; +``` + +## ConfigurationSet.Serialize method + +Serializes the configuration set to the given output stream. + +```C# +void Serialize(Windows.Storage.Streams.IOutputStream stream); +``` + +## ConfigurationSet.Remove method + +Removes the configuration set from the recorded history, if present. + +```C# +void Remove(); +``` + +You can use this method to remove a configuration set that is no longer relevant. For example, it may have been for +a repository that is no longer being used by the user and future conflicts with it's configuration are not important. + +## ConfigurationSet properties + +| Name | Description | +|-|-| +| Name | The name of the set; if from a file this could be the file name. | +| Origin | The origin of the set; if it came from a repository it could be the remote URL (ex. https://github.com/microsoft/winget-cli.git). | +| InstanceIdentifier | An identifier used to uniquely identify the instance of a configuration set on the system. | +| State | The state that the set is in. | +| InitialIntent | The time that this set was recorded with intent to apply. | +| ApplyBegun | The time that this set was last started to be applied. | +| ApplyEnded | The time that this set was last finished being applied (does not indicate success). | +| ConfigurationUnits | The configuration units that are part of this set. | + +## IConfigurationUnitProcessor interface + +Provides access to a specific configuration unit within the runtime. + +This interface is the primary mechanism used to actually read and write configuration to the system, +but it is not expected that you would use this directly as a consumer of Microsoft.Management.Configuration. + +## IConfigurationSetProcessor interface + +Contains the lifetime of the processing action for a configuration set. + +This interface is used to contain the lifetime of a processing action, +but it is not expected that you would use this directly as a consumer of Microsoft.Management.Configuration. + +## IConfigurationProcessorFactory interface + +Allows different runtimes to provide specialized handling of configuration processing. + +It is not expected that you would use this interface directly, but rather the `ConfigurationProcessor` class. + +_Spec note: A separate binary (written by us) will contain the implementation(s) of this interface._ + +## DiagnosticLevel enumeration + +Indicates the importance of diagnostic information. + +| Name | Description | +|-|-| +| Verbose | Most useful for debugging scenarios; likely too much for general use. | +| Informational | Details that can be useful for understanding what is happening. | +| Warning | Indicates some abnormal condition, but that is not expected to impact functionality. | +| Error | An error has occurred, but this does not necessarily mean that it will halt the operation. | +| Critical | A serious, fatal condition has been encountered. | + +## DiagnosticInformation class + +Contains diagnostic information from the configuration system that can be passed along to the user or log files. +This is not intended as primary information, and is thus not localized. + +## ConfigurationConflictType enumeration + +The type of conflict between configuration sets that was detected. + +| Name | Description | +|-|-| +| Unknown | For future use if the caller is not aware of newer conflict types. | +| MatchingOrigin | Indicates that the first configuration set has a matching name and origin to the second, which has already been applied. | +| IdenticalSetApplied | Indicates that the first configuration set is identical to the second, which has already been applied. | +| SettingsConflict | Indicates a conflict between the settings of two configuration units. | + +## ConfigurationConflictSetting class + +Describes a conflict between a setting of two configuration units. + +## ConfigurationConflict class + +Describes a conflict between two configuration sets. + +## ApplyConfigurationSetFlags enumeration + +Flags to control how a configuration set should be applied to the system. + +| Name | Description | +|-|-| +| None | The configuration set should be applied in the default manner. | +| DoNotOverwriteMatchingOriginSet | Forces a new configuration set instance to be recorded when the set being applied matches a previous set's origin. The default behavior is to assume that the incoming set is an update to the existing set and overwrite it. | + +## ConfigurationChangeEventType enumeration + +The configuration set change event type that has occurred. + +| Name | Description | +|-|-| +| Unknown | For future use if the caller is not aware of newer change types. | +| SetAdded | A new configuration set was recorded in the history with the intent to be applied. | +| SetStateChanged | A configuration set has changed state. | +| SetRemoved | A configuration set has been removed from the history. | + +## ConfigurationChangeEventType class + +The change data sent about changes to sets. + +## ConfigurationProcessor class + +The configuration processor is responsible for the interactions with the system. + +You must use this class to do anything beyond reading configuration sets. It is the entrypoint for all actions that +will interact with the actual system configuration. + +## ConfigurationProcessor constructor + +Creates a configuration processor using the given configuration factory. + +```C# +ConfigurationProcessor(IConfigurationProcessorFactory factory); +``` + +> _TODO: Add details on the mechanics of creating the `IConfigurationProcessorFactory` objects that we provide._ + +## ConfigurationProcessor.CheckForConflicts(Async) method + +Checks for conflicts amongst the configuration sets provided, optionally including the configuration sets already applied to the system. + +```C# +Windows.Foundation.Collections.IVectorView CheckForConflicts(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); + +Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVectorView > CheckForConflictsAsync(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); +``` + +This method should be used on any configuration set that is opened in order to determine if it would cause a conflict +with previously applied configurations. It should be called *after* setting the `Name` and `Origin` in order to determine +if it is a potential update. + +## ConfigurationProcessor.GetSetDetails(Async) method + +Gets the details for all configuration units in a set. + +```C# +void GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); + +Windows.Foundation.IAsyncAction GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailLevel detailLevel); +``` + +This is a convenience/optimization method that will do the same thing as calling `GetUnitDetails(Async)` on each +configuration unit in the set. See `GetUnitDetails(Async)` for more information on what it will do. + +## ConfigurationProcessor.GetUnitDetails(Async) method + +Gets the details for all configuration units in a set. + +```C# +void GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); + +Windows.Foundation.IAsyncAction GetUnitDetailsAsync(ConfigurationUnit unit, ConfigurationUnitDetailLevel detailLevel); +``` + +This method will get the details about a specific configuration unit and make them available via `ConfigurationUnit.Details`. +The `detailLevel` parameter allows control over how deeply to probe for details. It is an analog for the amount of +trust to place in the configuration unit processor. + +## ConfigurationProcessor.ApplySet(Async) method + +Applies the configuration set state to the system. + +```C# +ApplyConfigurationSetResult ApplySet(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); + +Windows.Foundation.IAsyncOperationWithProgress ApplySetAsync(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); +``` + +Using the async method and it's progress is more efficient than subscribing to the `ConfigurationSetChange` event before calling this method. + +## ConfigurationProcessor.TestSet(Async) method + +Tests if the system state matches the state described by the configuration set. + +```C# +TestConfigurationSetResult TestSet(ConfigurationSet configurationSet); + +Windows.Foundation.IAsyncOperationWithProgress TestSetAsync(ConfigurationSet configurationSet); +``` + +## ConfigurationProcessor.GetSettings(Async) method + +Gets the current configuration unit settings from the system state. + +```C# +GetConfigurationUnitSettingsResult GetSettings(ConfigurationUnit unit); + +Windows.Foundation.IAsyncOperation GetSettingsAsync(ConfigurationUnit unit); +``` + +## ConfigurationProcessor.Diagnostics event + +Enables listening to internal diagnostics events for logging purposes. + +## ConfigurationProcessor.ConfigurationChange event + +Signals changes to the set of configuration sets in the history, as well as changes to the state of configuration sets in the history. + +## ConfigurationProcessor.GetConfigurationHistory method + +Gets the configuration sets from the recorded history. + +```C# +Windows.Foundation.Collections.IVectorView GetConfigurationHistory(); +``` + +Gets the configuration sets that have already been applied or those recorded with the intent to be applied. This may include in progress sets or those that are waiting to be applied. + +# API Details + +[Link to the MIDL3 file.](../../src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl) + +# Appendix + + + +# Sample + +This sample illustrates some of the expected usage patterns. + +```C# +using Microsoft.Management.Configuration; +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.Storage; +using Windows.Storage.Streams; + +namespace ConfigurationSample +{ + internal static class Helpers + { + internal static IConfigurationProcessorFactory CreateIConfigurationProcessorFactory() + { + throw new NotImplementedException(); + } + + internal static ConfigurationSet OpenConfigurationSet(string filePath, ConfigurationProcessor processor) + { + var fileOperation = FileRandomAccessStream.OpenAsync(filePath, FileAccessMode.Read); + fileOperation.AsTask().Wait(); + var file = fileOperation.GetResults(); + OpenConfigurationSetResult result = processor.OpenConfigurationSet(file); + + if (result.Set != null) + { + return result.Set; + } + + Console.WriteLine($"Failed opening configuration set: 0x{result.ResultCode:X} at {result.Field}"); + return null; + } + + internal static void SetWatcher(ConfigurationSet set, ConfigurationSetChangeData data) + { + Console.WriteLine($" - Set: {set.Name} [{set.InstanceIdentifier}]"); + Console.WriteLine($" Change: {data.Change}"); + Console.WriteLine($" Set State: {data.SetState}"); + switch (data.Change) + { + case ConfigurationSetChangeEventType.UnitStateChanged: + Console.WriteLine($" Unit: {data.Unit.UnitName} [{data.Unit.InstanceIdentifier}]"); + Console.WriteLine($" Unit State: {data.UnitState}"); + if (data.UnitState == ConfigurationUnitState.Completed && data.ResultInformation.ResultCode != null) + { + Console.WriteLine($" Failure: {data.ResultInformation.Description} [{data.ResultInformation.ResultCode.HResult}]"); + } + break; + } + } + } + + internal class ApplyProgressWatcher + { + private bool isFirstProgress = true; + + internal void Watcher(IAsyncOperationWithProgress operation, ConfigurationSetChangeData data) + { + if (isFirstProgress) + { + isFirstProgress = false; + + // If our first progress callback contains partial results, output them as if they had been called through progress + ApplyConfigurationSetResult partialResult = operation.GetResults(); + + foreach (ApplyConfigurationUnitResult unitResult in partialResult.UnitResults) + { + HandleUnitProgress(unitResult.Unit, unitResult.State, unitResult.ResultInformation); + } + } + + switch (data.Change) + { + case ConfigurationSetChangeEventType.SetStateChanged: + Console.WriteLine($" - Set State: {data.SetState}"); + break; + case ConfigurationSetChangeEventType.UnitStateChanged: + HandleUnitProgress(data.Unit, data.UnitState, data.ResultInformation); + break; + } + } + + private void HandleUnitProgress(ConfigurationUnit unit, ConfigurationUnitState state, ConfigurationUnitResultInformation resultInformation) + { + switch (state) + { + case ConfigurationUnitState.Pending: + break; + case ConfigurationUnitState.InProgress: + case ConfigurationUnitState.Completed: + case ConfigurationUnitState.Skipped: + Console.WriteLine($" - Unit: {unit.UnitName} [{unit.InstanceIdentifier}]"); + Console.WriteLine($" Unit State: {state}"); + if (resultInformation.ResultCode != null) + { + Console.WriteLine($" HRESULT: [0x{resultInformation.ResultCode.HResult:X8}]"); + Console.WriteLine($" Reason: {resultInformation.Description}"); + } + break; + case ConfigurationUnitState.Unknown: + break; + } + } + } + + internal class Program + { + static void LoadAndOutput(string[] args) + { + ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); + + // Open the given configuration file + ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor); + if (configSet == null) + { + return; + } + + // Output some of the information from the set + Console.WriteLine($"Configuration Set: {args[1]}"); + + foreach (ConfigurationUnit unit in configSet.ConfigurationUnits) + { + Console.WriteLine($" - Configuration Unit: {unit.UnitName}"); + if (!string.IsNullOrEmpty(unit.Identifier)) + { + Console.WriteLine($" Identifier: {unit.Identifier}"); + } + Console.WriteLine($" Intent: {unit.Intent}"); + IReadOnlyList dependencies = unit.Dependencies; + if (dependencies.Count > 0) + { + Console.WriteLine(" Dependencies:"); + foreach (string dependency in dependencies) + { + Console.WriteLine($" {dependency}"); + } + } + ValueSet directives = unit.Directives; + if (directives.Count > 0) + { + Console.WriteLine(" Directives:"); + foreach (var directive in unit.Directives) + { + Console.WriteLine($" {directive.Key}: {directive.Value}"); + } + } + ValueSet settings = unit.Settings; + if (settings.Count > 0) + { + Console.WriteLine(" Settings:"); + foreach (var setting in unit.Settings) + { + Console.WriteLine($" {setting.Key}: {setting.Value}"); + } + } + } + } + + static void LoadAndCheckConflicts(string[] args) + { + // Create the factory and processor + ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); + + // Open the given configuration file + ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor); + if (configSet == null) + { + return; + } + + // Set a name and origin for this set so that we can see it in the conflict info + configSet.Name = Path.GetFileName(args[1]); + configSet.Origin = args[1]; + + // Check for conflicts with existing configurations + List configSets = new List() { configSet }; + IList conflicts = processor.CheckForConflicts(configSets, true); + + Console.WriteLine($"Conflicts with Configuration Set: {args[1]}"); + + foreach (ConfigurationConflict conflict in conflicts) + { + Console.WriteLine($" - Conflict: {conflict.Conflict}"); + Console.WriteLine($" First Set: {conflict.FirstSet.Name} [{conflict.FirstSet.Origin}]"); + Console.WriteLine($" Second Set: {conflict.SecondSet.Name} [{conflict.SecondSet.Origin}]"); + if (conflict.Conflict == ConfigurationConflictType.SettingsConflict) + { + Console.WriteLine($" First Unit: {conflict.FirstUnit.UnitName} [{conflict.FirstUnit.InstanceIdentifier}]"); + Console.WriteLine($" Second Unit: {conflict.SecondUnit.UnitName} [{conflict.SecondUnit.InstanceIdentifier}]"); + foreach (ConfigurationConflictSetting setting in conflict.Settings) + { + Console.WriteLine($" - Setting: {setting.Name}"); + Console.WriteLine($" First Value: {setting.FirstValue}"); + Console.WriteLine($" Second Value: {setting.SecondValue}"); + } + } + } + } + + static void LoadAndApply(string[] args) + { + // Create the factory and processor + ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); + + // Open the given configuration file + ConfigurationSet configSet = Helpers.OpenConfigurationSet(args[1], processor); + if (configSet == null) + { + return; + } + + Console.WriteLine($"Applying Configuration Set: {args[1]}"); + + ApplyProgressWatcher watcher = new ApplyProgressWatcher(); + + var operation = processor.ApplySetAsync(configSet, ApplyConfigurationSetFlags.None); + operation.Progress = watcher.Watcher; + operation.AsTask().Wait(); + ApplyConfigurationSetResult result = operation.GetResults(); + + Console.WriteLine($" - Done: {result.ResultCode.HResult}"); + } + + static void GetHistoryAndWatchEverything(string[] args) + { + Console.WriteLine("Watching all configuration [press Enter to stop]:"); + + // Create the factory and processor + ConfigurationProcessor processor = new ConfigurationProcessor(Helpers.CreateIConfigurationProcessorFactory()); + + List list = new List(); + + // Attach to the top level change event + processor.ConfigurationChange += (ConfigurationSet incomingSet, ConfigurationChangeData data) => + { + int existingSetIndex = -1; + + lock (list) + { + for (int i = 0; i < list.Count; ++i) + { + if (list[i].InstanceIdentifier == data.InstanceIdentifier) + { + existingSetIndex = i; + break; + } + } + + if (data.Change == ConfigurationChangeEventType.SetAdded || data.Change == ConfigurationChangeEventType.SetStateChanged) + { + if (existingSetIndex == -1) + { + incomingSet.ConfigurationSetChange += Helpers.SetWatcher; + list.Add(incomingSet); + } + } + else // Removed + { + if (existingSetIndex != -1) + { + list[existingSetIndex].ConfigurationSetChange -= Helpers.SetWatcher; + list.RemoveAt(existingSetIndex); + } + } + } + + Console.WriteLine($" - Set: {data.InstanceIdentifier}"); + Console.WriteLine($" Change: {data.Change}"); + }; + + foreach (ConfigurationSet set in processor.GetConfigurationHistory()) + { + int existingSetIndex = -1; + + lock (list) + { + for (int i = 0; i < list.Count; ++i) + { + if (list[i].InstanceIdentifier == set.InstanceIdentifier) + { + existingSetIndex = i; + break; + } + } + + if (existingSetIndex == -1) + { + set.ConfigurationSetChange += Helpers.SetWatcher; + list.Add(set); + } + } + + if (existingSetIndex == -1) + { + Console.WriteLine($" - Set: {set.Name} [{set.InstanceIdentifier}]"); + Console.WriteLine($" State: {set.State}"); + } + } + + // Wait for user to press enter + Console.ReadLine(); + } + + static void Main(string[] args) + { + var method = typeof(Program).GetMethod(args[0], BindingFlags.NonPublic | BindingFlags.Static); + + if (method != null) + { + method.Invoke(null, new object[]{ args }); + } + else + { + Console.WriteLine($"{args[0]} is not a sample"); + } + } + } +} + +``` diff --git a/doc/windows/package-manager/package/winget-validation.md b/doc/windows/package-manager/package/winget-validation.md index a59392adfc..f14163e8ad 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://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.| +## 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 639ac32d7c..71308c76df 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. 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) - +--- +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/import.md b/doc/windows/package-manager/winget/import.md index 045507dc97..4aefa64883 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 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. +--- +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/returnCodes.md b/doc/windows/package-manager/winget/returnCodes.md index 41f647c2f2..8626a4c0a7 100644 --- a/doc/windows/package-manager/winget/returnCodes.md +++ b/doc/windows/package-manager/winget/returnCodes.md @@ -1,239 +1,239 @@ ---- -title: WinGet HRESULT Codes -description: WinGet return codes and their meanings -ms.date: 05/13/2026 -ms.topic: article -ms.localizationpriority: low ---- - -> [!NOTE] -> This file is generated by running: winget error --output - -# Return Codes - -## General Errors - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A150001 | -1978335231 | APPINSTALLER_CLI_ERROR_INTERNAL_ERROR | Internal Error | -| 0x8A150002 | -1978335230 | APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS | Invalid command line arguments | -| 0x8A150003 | -1978335229 | APPINSTALLER_CLI_ERROR_COMMAND_FAILED | Executing command failed | -| 0x8A150004 | -1978335228 | APPINSTALLER_CLI_ERROR_MANIFEST_FAILED | Opening manifest failed | -| 0x8A150005 | -1978335227 | APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED | Cancellation signal received | -| 0x8A150006 | -1978335226 | APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED | Running ShellExecute failed | -| 0x8A150007 | -1978335225 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION | Cannot process manifest. The manifest version is higher than supported. Please update the client. | -| 0x8A150008 | -1978335224 | APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED | Downloading installer failed | -| 0x8A150009 | -1978335223 | APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX | Cannot write to index; it is a higher schema version | -| 0x8A15000A | -1978335222 | APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED | The index is corrupt | -| 0x8A15000B | -1978335221 | APPINSTALLER_CLI_ERROR_SOURCES_INVALID | The configured source information is corrupt | -| 0x8A15000C | -1978335220 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS | The source name is already configured | -| 0x8A15000D | -1978335219 | APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE | The source type is invalid | -| 0x8A15000E | -1978335218 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE | The MSIX file is a bundle, not a package | -| 0x8A15000F | -1978335217 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING | Data required by the source is missing | -| 0x8A150010 | -1978335216 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER | None of the installers are applicable for the current system | -| 0x8A150011 | -1978335215 | APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH | The installer file's hash does not match the manifest | -| 0x8A150012 | -1978335214 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST | The source name does not exist | -| 0x8A150013 | -1978335213 | APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS | The source location is already configured under another name | -| 0x8A150014 | -1978335212 | APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND | No packages found | -| 0x8A150015 | -1978335211 | APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED | No sources are configured | -| 0x8A150016 | -1978335210 | APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND | Multiple packages found matching the criteria | -| 0x8A150017 | -1978335209 | APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND | No manifest found matching the criteria | -| 0x8A150018 | -1978335208 | APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED | Failed to get Public folder from source package | -| 0x8A150019 | -1978335207 | APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN | Command requires administrator privileges to run | -| 0x8A15001A | -1978335206 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE | The source location is not secure | -| 0x8A15001B | -1978335205 | APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY | The Microsoft Store client is blocked by policy | -| 0x8A15001C | -1978335204 | APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY | The Microsoft Store app is blocked by policy | -| 0x8A15001D | -1978335203 | APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED | The feature is currently under development. It can be enabled using winget settings. | -| 0x8A15001E | -1978335202 | APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED | Failed to install the Microsoft Store app | -| 0x8A15001F | -1978335201 | APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD | Failed to perform auto complete | -| 0x8A150020 | -1978335200 | APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED | Failed to initialize YAML parser | -| 0x8A150021 | -1978335199 | APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY | Encountered an invalid YAML key | -| 0x8A150022 | -1978335198 | APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY | Encountered a duplicate YAML key | -| 0x8A150023 | -1978335197 | APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION | Invalid YAML operation | -| 0x8A150024 | -1978335196 | APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED | Failed to build YAML doc | -| 0x8A150025 | -1978335195 | APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE | Invalid YAML emitter state | -| 0x8A150026 | -1978335194 | APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA | Invalid YAML data | -| 0x8A150027 | -1978335193 | APPINSTALLER_CLI_ERROR_LIBYAML_ERROR | LibYAML error | -| 0x8A150028 | -1978335192 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING | Manifest validation succeeded with warning | -| 0x8A150029 | -1978335191 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE | Manifest validation failed | -| 0x8A15002A | -1978335190 | APPINSTALLER_CLI_ERROR_INVALID_MANIFEST | Manifest is invalid | -| 0x8A15002B | -1978335189 | APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE | No applicable update found | -| 0x8A15002C | -1978335188 | APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE | winget upgrade --all completed with failures | -| 0x8A15002D | -1978335187 | APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED | Installer failed security check | -| 0x8A15002E | -1978335186 | APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH | Download size does not match expected content length | -| 0x8A15002F | -1978335185 | APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND | Uninstall command not found | -| 0x8A150030 | -1978335184 | APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED | Running uninstall command failed | -| 0x8A150031 | -1978335183 | APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR | ICU break iterator error | -| 0x8A150032 | -1978335182 | APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR | ICU casemap error | -| 0x8A150033 | -1978335181 | APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR | ICU regex error | -| 0x8A150034 | -1978335180 | APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED | Failed to install one or more imported packages | -| 0x8A150035 | -1978335179 | APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND | Could not find one or more requested packages | -| 0x8A150036 | -1978335178 | APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE | Json file is invalid | -| 0x8A150037 | -1978335177 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE | The source location is not remote | -| 0x8A150038 | -1978335176 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE | The configured rest source is not supported | -| 0x8A150039 | -1978335175 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA | Invalid data returned by rest source | -| 0x8A15003A | -1978335174 | APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY | Operation is blocked by Group Policy | -| 0x8A15003B | -1978335173 | APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR | Rest API internal error | -| 0x8A15003C | -1978335172 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL | Invalid rest source url | -| 0x8A15003D | -1978335171 | APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE | Unsupported MIME type returned by rest API | -| 0x8A15003E | -1978335170 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION | Invalid rest source contract version | -| 0x8A15003F | -1978335169 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE | The source data is corrupted or tampered | -| 0x8A150040 | -1978335168 | APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE | Error reading from the stream | -| 0x8A150041 | -1978335167 | APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED | Package agreements were not agreed to | -| 0x8A150042 | -1978335166 | APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR | Error reading input in prompt | -| 0x8A150043 | -1978335165 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST | The search request is not supported by one or more sources | -| 0x8A150044 | -1978335164 | APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND | The rest API endpoint is not found. | -| 0x8A150045 | -1978335163 | APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED | Failed to open the source. | -| 0x8A150046 | -1978335162 | APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED | Source agreements were not agreed to | -| 0x8A150047 | -1978335161 | APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH | Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. | -| 0x8A150048 | -1978335160 | APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE | Missing resource file | -| 0x8A150049 | -1978335159 | APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED | Running MSI install failed | -| 0x8A15004A | -1978335158 | APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT | Arguments for msiexec are invalid | -| 0x8A15004B | -1978335157 | APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES | Failed to open one or more sources | -| 0x8A15004C | -1978335156 | APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED | Failed to validate dependencies | -| 0x8A15004D | -1978335155 | APPINSTALLER_CLI_ERROR_MISSING_PACKAGE | One or more package is missing | -| 0x8A15004E | -1978335154 | APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN | Invalid table column | -| 0x8A15004F | -1978335153 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER | The upgrade version is not newer than the installed version | -| 0x8A150050 | -1978335152 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN | Upgrade version is unknown and override is not specified | -| 0x8A150051 | -1978335151 | APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR | ICU conversion error | -| 0x8A150052 | -1978335150 | APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED | Failed to install portable package | -| 0x8A150053 | -1978335149 | APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED | Volume does not support reparse points | -| 0x8A150054 | -1978335148 | APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS | Portable package from a different source already exists. | -| 0x8A150055 | -1978335147 | APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY | Unable to create symlink, path points to a directory. | -| 0x8A150056 | -1978335146 | APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION | The installer cannot be run from an administrator context. | -| 0x8A150057 | -1978335145 | APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED | Failed to uninstall portable package | -| 0x8A150058 | -1978335144 | APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED | Failed to validate DisplayVersion values against index. | -| 0x8A150059 | -1978335143 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT | One or more arguments are not supported. | -| 0x8A15005A | -1978335142 | APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL | Embedded null characters are disallowed for SQLite | -| 0x8A15005B | -1978335141 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND | Failed to find the nested installer in the archive. | -| 0x8A15005C | -1978335140 | APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED | Failed to extract archive. | -| 0x8A15005D | -1978335139 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH | Invalid relative file path to nested installer provided. | -| 0x8A15005E | -1978335138 | APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH | The server certificate did not match any of the expected values. | -| 0x8A15005F | -1978335137 | APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED | Install location must be provided. | -| 0x8A150060 | -1978335136 | APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED | Archive malware scan failed. | -| 0x8A150061 | -1978335135 | APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED | Found at least one version of the package installed. | -| 0x8A150062 | -1978335134 | APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS | A pin already exists for the package. | -| 0x8A150063 | -1978335133 | APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST | There is no pin for the package. | -| 0x8A150064 | -1978335132 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX | Unable to open the pin database. | -| 0x8A150065 | -1978335131 | APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED | One or more applications failed to install | -| 0x8A150066 | -1978335130 | APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED | One or more applications failed to uninstall | -| 0x8A150067 | -1978335129 | APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE | One or more queries did not return exactly one match | -| 0x8A150068 | -1978335128 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED | The package has a pin that prevents upgrade. | -| 0x8A150069 | -1978335127 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB | The package currently installed is the stub package | -| 0x8A15006A | -1978335126 | APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED | Application shutdown signal received | -| 0x8A15006B | -1978335125 | APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES | Failed to download package dependencies. | -| 0x8A15006C | -1978335124 | APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED | Failed to download package. Download for offline installation is prohibited. | -| 0x8A15006D | -1978335123 | APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE | A required service is busy or unavailable. Try again later. | -| 0x8A15006E | -1978335122 | APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND | The guid provided does not correspond to a valid resume state. | -| 0x8A15006F | -1978335121 | APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH | The current client version did not match the client version of the saved state. | -| 0x8A150070 | -1978335120 | APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE | The resume state data is invalid. | -| 0x8A150071 | -1978335119 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX | Unable to open the checkpoint database. | -| 0x8A150072 | -1978335118 | APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED | Exceeded max resume limit. | -| 0x8A150073 | -1978335117 | APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO | Invalid authentication info. | -| 0x8A150074 | -1978335116 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED | Authentication method not supported. | -| 0x8A150075 | -1978335115 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED | Authentication failed. | -| 0x8A150076 | -1978335114 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED | Authentication failed. Interactive authentication required. | -| 0x8A150077 | -1978335113 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER | Authentication failed. User cancelled. | -| 0x8A150078 | -1978335112 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT | Authentication failed. Authenticated account is not the desired account. | -| 0x8A150079 | -1978335111 | APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND | Repair command not found. | -| 0x8A15007A | -1978335110 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE | Repair operation is not applicable. | -| 0x8A15007B | -1978335109 | APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED | Repair operation failed. | -| 0x8A15007C | -1978335108 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED | The installer technology in use doesn't support repair. | -| 0x8A15007D | -1978335107 | APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED | Repair operations involving administrator privileges are not permitted on packages installed within the user scope. | -| 0x8A15007E | -1978335106 | APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED | The SQLite connection was terminated to prevent corruption. | -| 0x8A15007F | -1978335105 | APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED | Failed to get Microsoft Store package catalog. | -| 0x8A150080 | -1978335104 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE | No applicable Microsoft Store package found from Microsoft Store package catalog. | -| 0x8A150081 | -1978335103 | APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED | Failed to get Microsoft Store package download information. | -| 0x8A150082 | -1978335102 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE | No applicable Microsoft Store package download information found. | -| 0x8A150083 | -1978335101 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED | Failed to retrieve Microsoft Store package license. | -| 0x8A150084 | -1978335100 | APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED | The Microsoft Store package does not support download. | -| 0x8A150085 | -1978335099 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN | Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have the required privilege. | -| 0x8A150086 | -1978335098 | APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE | Downloaded zero byte installer; ensure that your network connection is working properly. | -| 0x8A150087 | -1978335097 | APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED | Failed installing one or more fonts. | -| 0x8A150088 | -1978335096 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED | Font file is not supported and cannot be installed. | -| 0x8A150089 | -1978335095 | APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED | Font package is already installed. | -| 0x8A15008A | -1978335094 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND | Font file not found. | -| 0x8A15008B | -1978335093 | APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED | Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. | -| 0x8A15008C | -1978335092 | APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED | Font validation failed. | -| 0x8A15008D | -1978335091 | APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED | Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. | -| 0x8A15008E | -1978335090 | APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH | An upgrade is available but uses a different install technology than the current installation | - -## Install errors. - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A150101 | -1978334975 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE | Application is currently running. Exit the application then try again. | -| 0x8A150102 | -1978334974 | APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS | Another installation is already in progress. Try again later. | -| 0x8A150103 | -1978334973 | APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE | One or more file is being used. Exit the application then try again. | -| 0x8A150104 | -1978334972 | APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY | This package has a dependency missing from your system. | -| 0x8A150105 | -1978334971 | APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL | There's no more space on your PC. Make space, then try again. | -| 0x8A150106 | -1978334970 | APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY | There's not enough memory available to install. Close other applications then try again. | -| 0x8A150107 | -1978334969 | APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK | This application requires internet connectivity. Connect to a network then try again. | -| 0x8A150108 | -1978334968 | APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT | This application encountered an error during installation. Contact support. | -| 0x8A150109 | -1978334967 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH | Restart your PC to finish installation. | -| 0x8A15010A | -1978334966 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL | Installation failed. Restart your PC then try again. | -| 0x8A15010B | -1978334965 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED | Your PC will restart to finish installation. | -| 0x8A15010C | -1978334964 | APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER | You cancelled the installation. | -| 0x8A15010D | -1978334963 | APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED | Another version of this application is already installed. | -| 0x8A15010E | -1978334962 | APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE | A higher version of this application is already installed. | -| 0x8A15010F | -1978334961 | APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY | Organization policies are preventing installation. Contact your admin. | -| 0x8A150110 | -1978334960 | APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES | Failed to install package dependencies. | -| 0x8A150111 | -1978334959 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION | Application is currently in use by another application. | -| 0x8A150112 | -1978334958 | APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER | Invalid parameter. | -| 0x8A150113 | -1978334957 | APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED | Package not supported by the system. | -| 0x8A150114 | -1978334956 | APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED | The installer does not support upgrading an existing package. | -| 0x8A150115 | -1978334955 | APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR | Installation failed with a custom installer error. | - -## Check for package installed status - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A150201 | -1978334719 | WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND | The Apps and Features Entry for the package could not be found. | -| 0x0A150202 | 169148930 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE | The install location is not applicable. | -| 0x8A150203 | -1978334717 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND | The install location could not be found. | -| 0x8A150204 | -1978334716 | WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH | The hash of the existing file did not match. | -| 0x8A150205 | -1978334715 | WINGET_INSTALLED_STATUS_FILE_NOT_FOUND | File not found. | -| 0x0A150206 | 169148934 | WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK | The file was found but the hash was not checked. | -| 0x8A150207 | -1978334713 | WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR | The file could not be accessed. | - -## Configuration Errors - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A15C001 | -1978286079 | WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE | The configuration file is invalid. | -| 0x8A15C002 | -1978286078 | WINGET_CONFIG_ERROR_INVALID_YAML | The YAML syntax is invalid. | -| 0x8A15C003 | -1978286077 | WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE | A configuration field has an invalid type. | -| 0x8A15C004 | -1978286076 | WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION | The configuration has an unknown version. | -| 0x8A15C005 | -1978286075 | WINGET_CONFIG_ERROR_SET_APPLY_FAILED | An error occurred while applying the configuration. | -| 0x8A15C006 | -1978286074 | WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER | The configuration contains a duplicate identifier. | -| 0x8A15C007 | -1978286073 | WINGET_CONFIG_ERROR_MISSING_DEPENDENCY | The configuration is missing a dependency. | -| 0x8A15C008 | -1978286072 | WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED | The configuration has an unsatisfied dependency. | -| 0x8A15C009 | -1978286071 | WINGET_CONFIG_ERROR_ASSERTION_FAILED | An assertion for the configuration unit failed. | -| 0x8A15C00A | -1978286070 | WINGET_CONFIG_ERROR_MANUALLY_SKIPPED | The configuration was manually skipped. | -| 0x8A15C00B | -1978286069 | WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED | The user declined to continue execution. | -| 0x8A15C00C | -1978286068 | WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE | The dependency graph contains a cycle which cannot be resolved. | -| 0x8A15C00D | -1978286067 | WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE | The configuration has an invalid field value. | -| 0x8A15C00E | -1978286066 | WINGET_CONFIG_ERROR_MISSING_FIELD | The configuration is missing a field. | -| 0x8A15C00F | -1978286065 | WINGET_CONFIG_ERROR_TEST_FAILED | Some of the configuration units failed while testing their state. | -| 0x8A15C010 | -1978286064 | WINGET_CONFIG_ERROR_TEST_NOT_RUN | Configuration state was not tested. | -| 0x8A15C011 | -1978286063 | WINGET_CONFIG_ERROR_GET_FAILED | The configuration unit failed getting its properties. | -| 0x8A15C012 | -1978286062 | WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND | The specified configuration could not be found. | -| 0x8A15C013 | -1978286061 | WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY | Parameter cannot be passed across integrity boundary. | - -## Configuration Processor Errors - -| Hex | Decimal | Symbol | Description | -|-------------|-------------|-------------|-------------| -| 0x8A15C101 | -1978285823 | WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED | The configuration unit was not installed. | -| 0x8A15C102 | -1978285822 | WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY | The configuration unit could not be found. | -| 0x8A15C103 | -1978285821 | WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES | Multiple matches were found for the configuration unit; specify the module to select the correct one. | -| 0x8A15C104 | -1978285820 | WINGET_CONFIG_ERROR_UNIT_INVOKE_GET | The configuration unit failed while attempting to get the current system state. | -| 0x8A15C105 | -1978285819 | WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST | The configuration unit failed while attempting to test the current system state. | -| 0x8A15C106 | -1978285818 | WINGET_CONFIG_ERROR_UNIT_INVOKE_SET | The configuration unit failed while attempting to apply the desired state. | -| 0x8A15C107 | -1978285817 | WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT | The module for the configuration unit is available in multiple locations with the same version. | -| 0x8A15C108 | -1978285816 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE | Loading the module for the configuration unit failed. | -| 0x8A15C109 | -1978285815 | WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT | The configuration unit returned an unexpected result during execution. | -| 0x8A15C110 | -1978285808 | WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT | A unit contains a setting that requires the config root. | -| 0x8A15C111 | -1978285807 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN | Loading the module for the configuration unit failed because it requires administrator privileges to run. | -| 0x8A15C112 | -1978285806 | WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR | Operation is not supported by the configuration processor. | -| 0x8A15C113 | -1978285805 | WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH | The DSC processor hash provided does not match hash of the target file. | +--- +title: WinGet HRESULT Codes +description: WinGet return codes and their meanings +ms.date: 05/13/2026 +ms.topic: article +ms.localizationpriority: low +--- + +> [!NOTE] +> This file is generated by running: winget error --output + +# Return Codes + +## General Errors + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A150001 | -1978335231 | APPINSTALLER_CLI_ERROR_INTERNAL_ERROR | Internal Error | +| 0x8A150002 | -1978335230 | APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS | Invalid command line arguments | +| 0x8A150003 | -1978335229 | APPINSTALLER_CLI_ERROR_COMMAND_FAILED | Executing command failed | +| 0x8A150004 | -1978335228 | APPINSTALLER_CLI_ERROR_MANIFEST_FAILED | Opening manifest failed | +| 0x8A150005 | -1978335227 | APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED | Cancellation signal received | +| 0x8A150006 | -1978335226 | APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED | Running ShellExecute failed | +| 0x8A150007 | -1978335225 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION | Cannot process manifest. The manifest version is higher than supported. Please update the client. | +| 0x8A150008 | -1978335224 | APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED | Downloading installer failed | +| 0x8A150009 | -1978335223 | APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX | Cannot write to index; it is a higher schema version | +| 0x8A15000A | -1978335222 | APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED | The index is corrupt | +| 0x8A15000B | -1978335221 | APPINSTALLER_CLI_ERROR_SOURCES_INVALID | The configured source information is corrupt | +| 0x8A15000C | -1978335220 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS | The source name is already configured | +| 0x8A15000D | -1978335219 | APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE | The source type is invalid | +| 0x8A15000E | -1978335218 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE | The MSIX file is a bundle, not a package | +| 0x8A15000F | -1978335217 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING | Data required by the source is missing | +| 0x8A150010 | -1978335216 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER | None of the installers are applicable for the current system | +| 0x8A150011 | -1978335215 | APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH | The installer file's hash does not match the manifest | +| 0x8A150012 | -1978335214 | APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST | The source name does not exist | +| 0x8A150013 | -1978335213 | APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS | The source location is already configured under another name | +| 0x8A150014 | -1978335212 | APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND | No packages found | +| 0x8A150015 | -1978335211 | APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED | No sources are configured | +| 0x8A150016 | -1978335210 | APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND | Multiple packages found matching the criteria | +| 0x8A150017 | -1978335209 | APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND | No manifest found matching the criteria | +| 0x8A150018 | -1978335208 | APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED | Failed to get Public folder from source package | +| 0x8A150019 | -1978335207 | APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN | Command requires administrator privileges to run | +| 0x8A15001A | -1978335206 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE | The source location is not secure | +| 0x8A15001B | -1978335205 | APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY | The Microsoft Store client is blocked by policy | +| 0x8A15001C | -1978335204 | APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY | The Microsoft Store app is blocked by policy | +| 0x8A15001D | -1978335203 | APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED | The feature is currently under development. It can be enabled using winget settings. | +| 0x8A15001E | -1978335202 | APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED | Failed to install the Microsoft Store app | +| 0x8A15001F | -1978335201 | APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD | Failed to perform auto complete | +| 0x8A150020 | -1978335200 | APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED | Failed to initialize YAML parser | +| 0x8A150021 | -1978335199 | APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY | Encountered an invalid YAML key | +| 0x8A150022 | -1978335198 | APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY | Encountered a duplicate YAML key | +| 0x8A150023 | -1978335197 | APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION | Invalid YAML operation | +| 0x8A150024 | -1978335196 | APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED | Failed to build YAML doc | +| 0x8A150025 | -1978335195 | APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE | Invalid YAML emitter state | +| 0x8A150026 | -1978335194 | APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA | Invalid YAML data | +| 0x8A150027 | -1978335193 | APPINSTALLER_CLI_ERROR_LIBYAML_ERROR | LibYAML error | +| 0x8A150028 | -1978335192 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING | Manifest validation succeeded with warning | +| 0x8A150029 | -1978335191 | APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE | Manifest validation failed | +| 0x8A15002A | -1978335190 | APPINSTALLER_CLI_ERROR_INVALID_MANIFEST | Manifest is invalid | +| 0x8A15002B | -1978335189 | APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE | No applicable update found | +| 0x8A15002C | -1978335188 | APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE | winget upgrade --all completed with failures | +| 0x8A15002D | -1978335187 | APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED | Installer failed security check | +| 0x8A15002E | -1978335186 | APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH | Download size does not match expected content length | +| 0x8A15002F | -1978335185 | APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND | Uninstall command not found | +| 0x8A150030 | -1978335184 | APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED | Running uninstall command failed | +| 0x8A150031 | -1978335183 | APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR | ICU break iterator error | +| 0x8A150032 | -1978335182 | APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR | ICU casemap error | +| 0x8A150033 | -1978335181 | APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR | ICU regex error | +| 0x8A150034 | -1978335180 | APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED | Failed to install one or more imported packages | +| 0x8A150035 | -1978335179 | APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND | Could not find one or more requested packages | +| 0x8A150036 | -1978335178 | APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE | Json file is invalid | +| 0x8A150037 | -1978335177 | APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE | The source location is not remote | +| 0x8A150038 | -1978335176 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE | The configured rest source is not supported | +| 0x8A150039 | -1978335175 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA | Invalid data returned by rest source | +| 0x8A15003A | -1978335174 | APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY | Operation is blocked by Group Policy | +| 0x8A15003B | -1978335173 | APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR | Rest API internal error | +| 0x8A15003C | -1978335172 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL | Invalid rest source url | +| 0x8A15003D | -1978335171 | APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE | Unsupported MIME type returned by rest API | +| 0x8A15003E | -1978335170 | APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION | Invalid rest source contract version | +| 0x8A15003F | -1978335169 | APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE | The source data is corrupted or tampered | +| 0x8A150040 | -1978335168 | APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE | Error reading from the stream | +| 0x8A150041 | -1978335167 | APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED | Package agreements were not agreed to | +| 0x8A150042 | -1978335166 | APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR | Error reading input in prompt | +| 0x8A150043 | -1978335165 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST | The search request is not supported by one or more sources | +| 0x8A150044 | -1978335164 | APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND | The rest API endpoint is not found. | +| 0x8A150045 | -1978335163 | APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED | Failed to open the source. | +| 0x8A150046 | -1978335162 | APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED | Source agreements were not agreed to | +| 0x8A150047 | -1978335161 | APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH | Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. | +| 0x8A150048 | -1978335160 | APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE | Missing resource file | +| 0x8A150049 | -1978335159 | APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED | Running MSI install failed | +| 0x8A15004A | -1978335158 | APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT | Arguments for msiexec are invalid | +| 0x8A15004B | -1978335157 | APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES | Failed to open one or more sources | +| 0x8A15004C | -1978335156 | APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED | Failed to validate dependencies | +| 0x8A15004D | -1978335155 | APPINSTALLER_CLI_ERROR_MISSING_PACKAGE | One or more package is missing | +| 0x8A15004E | -1978335154 | APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN | Invalid table column | +| 0x8A15004F | -1978335153 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER | The upgrade version is not newer than the installed version | +| 0x8A150050 | -1978335152 | APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN | Upgrade version is unknown and override is not specified | +| 0x8A150051 | -1978335151 | APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR | ICU conversion error | +| 0x8A150052 | -1978335150 | APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED | Failed to install portable package | +| 0x8A150053 | -1978335149 | APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED | Volume does not support reparse points | +| 0x8A150054 | -1978335148 | APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS | Portable package from a different source already exists. | +| 0x8A150055 | -1978335147 | APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY | Unable to create symlink, path points to a directory. | +| 0x8A150056 | -1978335146 | APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION | The installer cannot be run from an administrator context. | +| 0x8A150057 | -1978335145 | APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED | Failed to uninstall portable package | +| 0x8A150058 | -1978335144 | APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED | Failed to validate DisplayVersion values against index. | +| 0x8A150059 | -1978335143 | APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT | One or more arguments are not supported. | +| 0x8A15005A | -1978335142 | APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL | Embedded null characters are disallowed for SQLite | +| 0x8A15005B | -1978335141 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND | Failed to find the nested installer in the archive. | +| 0x8A15005C | -1978335140 | APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED | Failed to extract archive. | +| 0x8A15005D | -1978335139 | APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH | Invalid relative file path to nested installer provided. | +| 0x8A15005E | -1978335138 | APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH | The server certificate did not match any of the expected values. | +| 0x8A15005F | -1978335137 | APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED | Install location must be provided. | +| 0x8A150060 | -1978335136 | APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED | Archive malware scan failed. | +| 0x8A150061 | -1978335135 | APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED | Found at least one version of the package installed. | +| 0x8A150062 | -1978335134 | APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS | A pin already exists for the package. | +| 0x8A150063 | -1978335133 | APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST | There is no pin for the package. | +| 0x8A150064 | -1978335132 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX | Unable to open the pin database. | +| 0x8A150065 | -1978335131 | APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED | One or more applications failed to install | +| 0x8A150066 | -1978335130 | APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED | One or more applications failed to uninstall | +| 0x8A150067 | -1978335129 | APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE | One or more queries did not return exactly one match | +| 0x8A150068 | -1978335128 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED | The package has a pin that prevents upgrade. | +| 0x8A150069 | -1978335127 | APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB | The package currently installed is the stub package | +| 0x8A15006A | -1978335126 | APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED | Application shutdown signal received | +| 0x8A15006B | -1978335125 | APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES | Failed to download package dependencies. | +| 0x8A15006C | -1978335124 | APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED | Failed to download package. Download for offline installation is prohibited. | +| 0x8A15006D | -1978335123 | APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE | A required service is busy or unavailable. Try again later. | +| 0x8A15006E | -1978335122 | APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND | The guid provided does not correspond to a valid resume state. | +| 0x8A15006F | -1978335121 | APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH | The current client version did not match the client version of the saved state. | +| 0x8A150070 | -1978335120 | APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE | The resume state data is invalid. | +| 0x8A150071 | -1978335119 | APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX | Unable to open the checkpoint database. | +| 0x8A150072 | -1978335118 | APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED | Exceeded max resume limit. | +| 0x8A150073 | -1978335117 | APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO | Invalid authentication info. | +| 0x8A150074 | -1978335116 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED | Authentication method not supported. | +| 0x8A150075 | -1978335115 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED | Authentication failed. | +| 0x8A150076 | -1978335114 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED | Authentication failed. Interactive authentication required. | +| 0x8A150077 | -1978335113 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER | Authentication failed. User cancelled. | +| 0x8A150078 | -1978335112 | APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT | Authentication failed. Authenticated account is not the desired account. | +| 0x8A150079 | -1978335111 | APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND | Repair command not found. | +| 0x8A15007A | -1978335110 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE | Repair operation is not applicable. | +| 0x8A15007B | -1978335109 | APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED | Repair operation failed. | +| 0x8A15007C | -1978335108 | APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED | The installer technology in use doesn't support repair. | +| 0x8A15007D | -1978335107 | APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED | Repair operations involving administrator privileges are not permitted on packages installed within the user scope. | +| 0x8A15007E | -1978335106 | APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED | The SQLite connection was terminated to prevent corruption. | +| 0x8A15007F | -1978335105 | APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED | Failed to get Microsoft Store package catalog. | +| 0x8A150080 | -1978335104 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE | No applicable Microsoft Store package found from Microsoft Store package catalog. | +| 0x8A150081 | -1978335103 | APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED | Failed to get Microsoft Store package download information. | +| 0x8A150082 | -1978335102 | APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE | No applicable Microsoft Store package download information found. | +| 0x8A150083 | -1978335101 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED | Failed to retrieve Microsoft Store package license. | +| 0x8A150084 | -1978335100 | APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED | The Microsoft Store package does not support download. | +| 0x8A150085 | -1978335099 | APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN | Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have the required privilege. | +| 0x8A150086 | -1978335098 | APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE | Downloaded zero byte installer; ensure that your network connection is working properly. | +| 0x8A150087 | -1978335097 | APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED | Failed installing one or more fonts. | +| 0x8A150088 | -1978335096 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED | Font file is not supported and cannot be installed. | +| 0x8A150089 | -1978335095 | APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED | Font package is already installed. | +| 0x8A15008A | -1978335094 | APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND | Font file not found. | +| 0x8A15008B | -1978335093 | APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED | Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. | +| 0x8A15008C | -1978335092 | APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED | Font validation failed. | +| 0x8A15008D | -1978335091 | APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED | Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. | +| 0x8A15008E | -1978335090 | APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH | An upgrade is available but uses a different install technology than the current installation | + +## Install errors. + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A150101 | -1978334975 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE | Application is currently running. Exit the application then try again. | +| 0x8A150102 | -1978334974 | APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS | Another installation is already in progress. Try again later. | +| 0x8A150103 | -1978334973 | APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE | One or more file is being used. Exit the application then try again. | +| 0x8A150104 | -1978334972 | APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY | This package has a dependency missing from your system. | +| 0x8A150105 | -1978334971 | APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL | There's no more space on your PC. Make space, then try again. | +| 0x8A150106 | -1978334970 | APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY | There's not enough memory available to install. Close other applications then try again. | +| 0x8A150107 | -1978334969 | APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK | This application requires internet connectivity. Connect to a network then try again. | +| 0x8A150108 | -1978334968 | APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT | This application encountered an error during installation. Contact support. | +| 0x8A150109 | -1978334967 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH | Restart your PC to finish installation. | +| 0x8A15010A | -1978334966 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL | Installation failed. Restart your PC then try again. | +| 0x8A15010B | -1978334965 | APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED | Your PC will restart to finish installation. | +| 0x8A15010C | -1978334964 | APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER | You cancelled the installation. | +| 0x8A15010D | -1978334963 | APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED | Another version of this application is already installed. | +| 0x8A15010E | -1978334962 | APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE | A higher version of this application is already installed. | +| 0x8A15010F | -1978334961 | APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY | Organization policies are preventing installation. Contact your admin. | +| 0x8A150110 | -1978334960 | APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES | Failed to install package dependencies. | +| 0x8A150111 | -1978334959 | APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION | Application is currently in use by another application. | +| 0x8A150112 | -1978334958 | APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER | Invalid parameter. | +| 0x8A150113 | -1978334957 | APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED | Package not supported by the system. | +| 0x8A150114 | -1978334956 | APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED | The installer does not support upgrading an existing package. | +| 0x8A150115 | -1978334955 | APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR | Installation failed with a custom installer error. | + +## Check for package installed status + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A150201 | -1978334719 | WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND | The Apps and Features Entry for the package could not be found. | +| 0x0A150202 | 169148930 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE | The install location is not applicable. | +| 0x8A150203 | -1978334717 | WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND | The install location could not be found. | +| 0x8A150204 | -1978334716 | WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH | The hash of the existing file did not match. | +| 0x8A150205 | -1978334715 | WINGET_INSTALLED_STATUS_FILE_NOT_FOUND | File not found. | +| 0x0A150206 | 169148934 | WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK | The file was found but the hash was not checked. | +| 0x8A150207 | -1978334713 | WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR | The file could not be accessed. | + +## Configuration Errors + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A15C001 | -1978286079 | WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE | The configuration file is invalid. | +| 0x8A15C002 | -1978286078 | WINGET_CONFIG_ERROR_INVALID_YAML | The YAML syntax is invalid. | +| 0x8A15C003 | -1978286077 | WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE | A configuration field has an invalid type. | +| 0x8A15C004 | -1978286076 | WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION | The configuration has an unknown version. | +| 0x8A15C005 | -1978286075 | WINGET_CONFIG_ERROR_SET_APPLY_FAILED | An error occurred while applying the configuration. | +| 0x8A15C006 | -1978286074 | WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER | The configuration contains a duplicate identifier. | +| 0x8A15C007 | -1978286073 | WINGET_CONFIG_ERROR_MISSING_DEPENDENCY | The configuration is missing a dependency. | +| 0x8A15C008 | -1978286072 | WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED | The configuration has an unsatisfied dependency. | +| 0x8A15C009 | -1978286071 | WINGET_CONFIG_ERROR_ASSERTION_FAILED | An assertion for the configuration unit failed. | +| 0x8A15C00A | -1978286070 | WINGET_CONFIG_ERROR_MANUALLY_SKIPPED | The configuration was manually skipped. | +| 0x8A15C00B | -1978286069 | WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED | The user declined to continue execution. | +| 0x8A15C00C | -1978286068 | WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE | The dependency graph contains a cycle which cannot be resolved. | +| 0x8A15C00D | -1978286067 | WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE | The configuration has an invalid field value. | +| 0x8A15C00E | -1978286066 | WINGET_CONFIG_ERROR_MISSING_FIELD | The configuration is missing a field. | +| 0x8A15C00F | -1978286065 | WINGET_CONFIG_ERROR_TEST_FAILED | Some of the configuration units failed while testing their state. | +| 0x8A15C010 | -1978286064 | WINGET_CONFIG_ERROR_TEST_NOT_RUN | Configuration state was not tested. | +| 0x8A15C011 | -1978286063 | WINGET_CONFIG_ERROR_GET_FAILED | The configuration unit failed getting its properties. | +| 0x8A15C012 | -1978286062 | WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND | The specified configuration could not be found. | +| 0x8A15C013 | -1978286061 | WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY | Parameter cannot be passed across integrity boundary. | + +## Configuration Processor Errors + +| Hex | Decimal | Symbol | Description | +|-------------|-------------|-------------|-------------| +| 0x8A15C101 | -1978285823 | WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED | The configuration unit was not installed. | +| 0x8A15C102 | -1978285822 | WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY | The configuration unit could not be found. | +| 0x8A15C103 | -1978285821 | WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES | Multiple matches were found for the configuration unit; specify the module to select the correct one. | +| 0x8A15C104 | -1978285820 | WINGET_CONFIG_ERROR_UNIT_INVOKE_GET | The configuration unit failed while attempting to get the current system state. | +| 0x8A15C105 | -1978285819 | WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST | The configuration unit failed while attempting to test the current system state. | +| 0x8A15C106 | -1978285818 | WINGET_CONFIG_ERROR_UNIT_INVOKE_SET | The configuration unit failed while attempting to apply the desired state. | +| 0x8A15C107 | -1978285817 | WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT | The module for the configuration unit is available in multiple locations with the same version. | +| 0x8A15C108 | -1978285816 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE | Loading the module for the configuration unit failed. | +| 0x8A15C109 | -1978285815 | WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT | The configuration unit returned an unexpected result during execution. | +| 0x8A15C110 | -1978285808 | WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT | A unit contains a setting that requires the config root. | +| 0x8A15C111 | -1978285807 | WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN | Loading the module for the configuration unit failed because it requires administrator privileges to run. | +| 0x8A15C112 | -1978285806 | WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR | Operation is not supported by the configuration processor. | +| 0x8A15C113 | -1978285805 | WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH | The DSC processor hash provided does not match hash of the target file. | diff --git a/samples/ConnectionValidationSample/Program.cs b/samples/ConnectionValidationSample/Program.cs index 2cb05abeb8..b059bba37b 100644 --- a/samples/ConnectionValidationSample/Program.cs +++ b/samples/ConnectionValidationSample/Program.cs @@ -1,116 +1,116 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Sample demonstrating the ConnectionValidationHandler on PackageCatalogReference. -// -// This sample retrieves a named package catalog, installs a connection validation -// callback that shows certificate information, prompts the user to accept or reject -// the certificate, then reports the Connect() result. -// -// Usage: ConnectionValidationSample -// Example: ConnectionValidationSample winget -// -// Requirements: -// - Must run in-process (the ConnectionValidationHandler setter rejects out-of-proc callers). -// - The Microsoft.Management.Deployment COM server must be registered (deploy the dev package -// or install WinGet from the Microsoft Store). - -using Microsoft.Management.Deployment; -using Windows.Security.Cryptography.Certificates; - -if (args.Length == 0) -{ - Console.Error.WriteLine("Usage: ConnectionValidationSample "); - Console.Error.WriteLine("Example: ConnectionValidationSample winget"); - return 1; -} - -string catalogName = args[0]; - -// Use in-proc activation so that ConnectionValidationHandler can be set. -// CsWinRT activates PackageManager in-proc via DllGetActivationFactory from -// Microsoft.Management.Deployment.dll placed alongside this executable. -var packageManager = new PackageManager(); - -var catalogRef = packageManager.GetPackageCatalogByName(catalogName); -if (catalogRef is null) -{ - Console.Error.WriteLine($"No catalog named '{catalogName}' found."); - Console.Error.WriteLine("Use 'winget source list' to see available catalogs."); - return 1; -} - -Console.WriteLine($"Catalog: {catalogRef.Info.Name} ({catalogRef.Info.Argument})"); -Console.WriteLine("Setting connection validation handler..."); - -catalogRef.ConnectionValidationHandler = (PackageCatalogConnectionValidationEventArgs validationArgs) => -{ - Certificate cert = validationArgs.ServerCertificate; - - Console.WriteLine(); - Console.WriteLine("Catalog connection validation for: " + catalogRef.Info.Name); - Console.WriteLine(" at: " + catalogRef.Info.Argument); - Console.WriteLine(); - Console.WriteLine("=== Server Certificate ==="); - Console.WriteLine($" Subject: {cert.Subject}"); - Console.WriteLine($" Issuer: {cert.Issuer}"); - Console.WriteLine($" Valid from: {cert.ValidFrom}"); - Console.WriteLine($" Valid to: {cert.ValidTo}"); - Console.WriteLine($" Serial: {cert.SerialNumber}"); - Console.WriteLine("=========================="); - Console.WriteLine(); - Console.Write("Accept this certificate? [Y/N]: "); - - while (true) - { - string? input = Console.ReadLine()?.Trim().ToUpperInvariant(); - if (input == "Y") - { - Console.WriteLine("Certificate accepted."); - return PackageCatalogConnectionValidationResult.Ok; - } - else if (input == "N") - { - Console.WriteLine("Certificate rejected."); - return PackageCatalogConnectionValidationResult.CertificateRejected; - } - else - { - Console.Write("Please enter Y or N: "); - } - } -}; - -Console.WriteLine("Connecting..."); -ConnectResult result = catalogRef.Connect(); - -Console.WriteLine(); -Console.WriteLine($"Connect result: {result.Status}"); - -if (result.Status == ConnectResultStatus.Ok) -{ - Console.WriteLine($"Successfully connected to '{catalogName}'."); - - // Run a simple search to force a live network call, since Connect() may serve a cached response. - Console.WriteLine("Searching to verify live connection..."); - var findOptions = new FindPackagesOptions(); - findOptions.Selectors.Add(new PackageMatchFilter - { - Field = PackageMatchField.Id, - Option = PackageFieldMatchOption.StartsWithCaseInsensitive, - Value = "Microsoft.", - }); - var searchResult = result.PackageCatalog.FindPackages(findOptions); - Console.WriteLine($"Search result: {searchResult.Status} ({searchResult.Matches.Count} match(es))"); - - return 0; -} -else -{ - Console.Error.WriteLine($"Failed to connect to '{catalogName}'."); - if (result.ExtendedErrorCode is not null) - { - Console.Error.WriteLine($" Error code: 0x{result.ExtendedErrorCode.HResult:X8}"); - } - return 1; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Sample demonstrating the ConnectionValidationHandler on PackageCatalogReference. +// +// This sample retrieves a named package catalog, installs a connection validation +// callback that shows certificate information, prompts the user to accept or reject +// the certificate, then reports the Connect() result. +// +// Usage: ConnectionValidationSample +// Example: ConnectionValidationSample winget +// +// Requirements: +// - Must run in-process (the ConnectionValidationHandler setter rejects out-of-proc callers). +// - The Microsoft.Management.Deployment COM server must be registered (deploy the dev package +// or install WinGet from the Microsoft Store). + +using Microsoft.Management.Deployment; +using Windows.Security.Cryptography.Certificates; + +if (args.Length == 0) +{ + Console.Error.WriteLine("Usage: ConnectionValidationSample "); + Console.Error.WriteLine("Example: ConnectionValidationSample winget"); + return 1; +} + +string catalogName = args[0]; + +// Use in-proc activation so that ConnectionValidationHandler can be set. +// CsWinRT activates PackageManager in-proc via DllGetActivationFactory from +// Microsoft.Management.Deployment.dll placed alongside this executable. +var packageManager = new PackageManager(); + +var catalogRef = packageManager.GetPackageCatalogByName(catalogName); +if (catalogRef is null) +{ + Console.Error.WriteLine($"No catalog named '{catalogName}' found."); + Console.Error.WriteLine("Use 'winget source list' to see available catalogs."); + return 1; +} + +Console.WriteLine($"Catalog: {catalogRef.Info.Name} ({catalogRef.Info.Argument})"); +Console.WriteLine("Setting connection validation handler..."); + +catalogRef.ConnectionValidationHandler = (PackageCatalogConnectionValidationEventArgs validationArgs) => +{ + Certificate cert = validationArgs.ServerCertificate; + + Console.WriteLine(); + Console.WriteLine("Catalog connection validation for: " + catalogRef.Info.Name); + Console.WriteLine(" at: " + catalogRef.Info.Argument); + Console.WriteLine(); + Console.WriteLine("=== Server Certificate ==="); + Console.WriteLine($" Subject: {cert.Subject}"); + Console.WriteLine($" Issuer: {cert.Issuer}"); + Console.WriteLine($" Valid from: {cert.ValidFrom}"); + Console.WriteLine($" Valid to: {cert.ValidTo}"); + Console.WriteLine($" Serial: {cert.SerialNumber}"); + Console.WriteLine("=========================="); + Console.WriteLine(); + Console.Write("Accept this certificate? [Y/N]: "); + + while (true) + { + string? input = Console.ReadLine()?.Trim().ToUpperInvariant(); + if (input == "Y") + { + Console.WriteLine("Certificate accepted."); + return PackageCatalogConnectionValidationResult.Ok; + } + else if (input == "N") + { + Console.WriteLine("Certificate rejected."); + return PackageCatalogConnectionValidationResult.CertificateRejected; + } + else + { + Console.Write("Please enter Y or N: "); + } + } +}; + +Console.WriteLine("Connecting..."); +ConnectResult result = catalogRef.Connect(); + +Console.WriteLine(); +Console.WriteLine($"Connect result: {result.Status}"); + +if (result.Status == ConnectResultStatus.Ok) +{ + Console.WriteLine($"Successfully connected to '{catalogName}'."); + + // Run a simple search to force a live network call, since Connect() may serve a cached response. + Console.WriteLine("Searching to verify live connection..."); + var findOptions = new FindPackagesOptions(); + findOptions.Selectors.Add(new PackageMatchFilter + { + Field = PackageMatchField.Id, + Option = PackageFieldMatchOption.StartsWithCaseInsensitive, + Value = "Microsoft.", + }); + var searchResult = result.PackageCatalog.FindPackages(findOptions); + Console.WriteLine($"Search result: {searchResult.Status} ({searchResult.Matches.Count} match(es))"); + + return 0; +} +else +{ + Console.Error.WriteLine($"Failed to connect to '{catalogName}'."); + if (result.ExtendedErrorCode is not null) + { + Console.Error.WriteLine($" Error code: 0x{result.ExtendedErrorCode.HResult:X8}"); + } + return 1; +} diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/App.cpp b/samples/WinGetUWPCaller/WinGetUWPCaller/App.cpp index a9e9cbc1a2..ed6f7069fa 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/App.cpp +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/App.cpp @@ -1,121 +1,121 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" - -#include "App.h" -#include "MainPage.h" - -using namespace winrt; -using namespace Windows::ApplicationModel; -using namespace Windows::ApplicationModel::Activation; -using namespace Windows::Foundation; -using namespace Windows::UI::Xaml; -using namespace Windows::UI::Xaml::Controls; -using namespace Windows::UI::Xaml::Navigation; -using namespace WinGetUWPCaller; -using namespace WinGetUWPCaller::implementation; - -/// -/// Initializes the singleton application object. This is the first line of authored code -/// executed, and as such is the logical equivalent of main() or WinMain(). -/// -App::App() -{ - InitializeComponent(); - Suspending({ this, &App::OnSuspending }); - -#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION - UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e) - { - if (IsDebuggerPresent()) - { - auto errorMessage = e.Message(); - __debugbreak(); - } - }); -#endif -} - -/// -/// Invoked when the application is launched normally by the end user. Other entry points -/// will be used such as when the application is launched to open a specific file. -/// -/// Details about the launch request and process. -void App::OnLaunched(LaunchActivatedEventArgs const& e) -{ - Frame rootFrame{ nullptr }; - auto content = Window::Current().Content(); - if (content) - { - rootFrame = content.try_as(); - } - - // Do not repeat app initialization when the Window already has content, - // just ensure that the window is active - if (rootFrame == nullptr) - { - // Create a Frame to act as the navigation context and associate it with - // a SuspensionManager key - rootFrame = Frame(); - - rootFrame.NavigationFailed({ this, &App::OnNavigationFailed }); - - if (e.PreviousExecutionState() == ApplicationExecutionState::Terminated) - { - // Restore the saved session state only when appropriate, scheduling the - // final launch steps after the restore is complete - } - - if (e.PrelaunchActivated() == false) - { - if (rootFrame.Content() == nullptr) - { - // When the navigation stack isn't restored navigate to the first page, - // configuring the new page by passing required information as a navigation - // parameter - rootFrame.Navigate(xaml_typename(), box_value(e.Arguments())); - } - // Place the frame in the current Window - Window::Current().Content(rootFrame); - // Ensure the current window is active - Window::Current().Activate(); - } - } - else - { - if (e.PrelaunchActivated() == false) - { - if (rootFrame.Content() == nullptr) - { - // When the navigation stack isn't restored navigate to the first page, - // configuring the new page by passing required information as a navigation - // parameter - rootFrame.Navigate(xaml_typename(), box_value(e.Arguments())); - } - // Ensure the current window is active - Window::Current().Activate(); - } - } -} - -/// -/// Invoked when application execution is being suspended. Application state is saved -/// without knowing whether the application will be terminated or resumed with the contents -/// of memory still intact. -/// -/// The source of the suspend request. -/// Details about the suspend request. -void App::OnSuspending([[maybe_unused]] IInspectable const& sender, [[maybe_unused]] SuspendingEventArgs const& e) -{ - // Save application state and stop any background activity -} - -/// -/// Invoked when Navigation to a certain page fails -/// -/// The Frame which failed navigation -/// Details about the navigation failure -void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e) -{ - throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +#include "App.h" +#include "MainPage.h" + +using namespace winrt; +using namespace Windows::ApplicationModel; +using namespace Windows::ApplicationModel::Activation; +using namespace Windows::Foundation; +using namespace Windows::UI::Xaml; +using namespace Windows::UI::Xaml::Controls; +using namespace Windows::UI::Xaml::Navigation; +using namespace WinGetUWPCaller; +using namespace WinGetUWPCaller::implementation; + +/// +/// Initializes the singleton application object. This is the first line of authored code +/// executed, and as such is the logical equivalent of main() or WinMain(). +/// +App::App() +{ + InitializeComponent(); + Suspending({ this, &App::OnSuspending }); + +#if defined _DEBUG && !defined DISABLE_XAML_GENERATED_BREAK_ON_UNHANDLED_EXCEPTION + UnhandledException([this](IInspectable const&, UnhandledExceptionEventArgs const& e) + { + if (IsDebuggerPresent()) + { + auto errorMessage = e.Message(); + __debugbreak(); + } + }); +#endif +} + +/// +/// Invoked when the application is launched normally by the end user. Other entry points +/// will be used such as when the application is launched to open a specific file. +/// +/// Details about the launch request and process. +void App::OnLaunched(LaunchActivatedEventArgs const& e) +{ + Frame rootFrame{ nullptr }; + auto content = Window::Current().Content(); + if (content) + { + rootFrame = content.try_as(); + } + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == nullptr) + { + // Create a Frame to act as the navigation context and associate it with + // a SuspensionManager key + rootFrame = Frame(); + + rootFrame.NavigationFailed({ this, &App::OnNavigationFailed }); + + if (e.PreviousExecutionState() == ApplicationExecutionState::Terminated) + { + // Restore the saved session state only when appropriate, scheduling the + // final launch steps after the restore is complete + } + + if (e.PrelaunchActivated() == false) + { + if (rootFrame.Content() == nullptr) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Navigate(xaml_typename(), box_value(e.Arguments())); + } + // Place the frame in the current Window + Window::Current().Content(rootFrame); + // Ensure the current window is active + Window::Current().Activate(); + } + } + else + { + if (e.PrelaunchActivated() == false) + { + if (rootFrame.Content() == nullptr) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Navigate(xaml_typename(), box_value(e.Arguments())); + } + // Ensure the current window is active + Window::Current().Activate(); + } + } +} + +/// +/// Invoked when application execution is being suspended. Application state is saved +/// without knowing whether the application will be terminated or resumed with the contents +/// of memory still intact. +/// +/// The source of the suspend request. +/// Details about the suspend request. +void App::OnSuspending([[maybe_unused]] IInspectable const& sender, [[maybe_unused]] SuspendingEventArgs const& e) +{ + // Save application state and stop any background activity +} + +/// +/// Invoked when Navigation to a certain page fails +/// +/// The Frame which failed navigation +/// Details about the navigation failure +void App::OnNavigationFailed(IInspectable const&, NavigationFailedEventArgs const& e) +{ + throw hresult_error(E_FAIL, hstring(L"Failed to load Page ") + e.SourcePageType().Name); +} diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/App.xaml b/samples/WinGetUWPCaller/WinGetUWPCaller/App.xaml index 710944ef82..170665c7b6 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/App.xaml +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/App.xaml @@ -1,9 +1,9 @@ - - - - + + + + diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.cpp b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.cpp index de7419fa91..4784747552 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.cpp +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.cpp @@ -1,954 +1,954 @@ -#include "pch.h" -#include "MainPage.h" -#include "MainPage.g.cpp" - -#include - -using namespace std::chrono_literals; -using namespace std::string_view_literals; -using namespace winrt::Microsoft::Management::Deployment; - -namespace winrt -{ - using namespace Windows::UI::Xaml; - using namespace Windows::Foundation; - using namespace Windows::Foundation::Collections; -} - -namespace winrt::WinGetUWPCaller::implementation -{ - namespace - { - std::wstring ConvertExceptionToStatusString(std::wstring_view context, std::exception_ptr exceptionPtr) - { - std::wostringstream result; - - try - { - std::rethrow_exception(exceptionPtr); - } - catch (const winrt::hresult_error& error) - { - result << context << L" :: " << L"0x" << std::hex << std::setw(8) << std::setfill(L'0') << error.code() << L": " << static_cast(error.message()); - } - catch (const std::exception& exception) - { - result << context << L" :: " << exception.what(); - } - catch (...) - { - result << context << L" :: Unknown exception"; - } - - return std::move(result).str(); - } - - template - std::wstring RunAndReturnStatus(std::wstring_view context, Operation&& operation) - { - try - { - operation(); - } - catch (...) - { - return ConvertExceptionToStatusString(context, std::current_exception()); - } - - return {}; - } - - // Helper object to control button states and status text. - struct BackgroundActionData - { - // This object should be constructed on the foreground thread. - // disabledButtons will be disabled during the operation. - // enabledButtons will be enabled during the operation. - // statusText will be updated with the result. - BackgroundActionData( - std::initializer_list disabledButtons, - Windows::UI::Xaml::Controls::TextBlock statusText) : - m_disabledButtons(disabledButtons), - m_statusText(statusText) - { - if (m_disabledButtons.empty()) - { - throw std::exception("Must specify at least one disabled button."); - } - - for (const auto& button : m_disabledButtons) - { - button.IsEnabled(false); - } - - m_statusText.Text(L""); - } - - BackgroundActionData( - std::initializer_list disabledButtons, - std::initializer_list enabledButtons, - Windows::UI::Xaml::Controls::TextBlock statusText) : - BackgroundActionData(disabledButtons, statusText) - { - m_enabledButtons = enabledButtons; - for (const auto& button : m_enabledButtons) - { - button.IsEnabled(true); - } - } - - // This should be run on the foreground thread. - void Finalize() const - { - for (const auto& button : m_disabledButtons) - { - button.IsEnabled(true); - } - - for (const auto& button : m_enabledButtons) - { - button.IsEnabled(false); - } - - m_statusText.Text(m_status); - } - - template - void RunAndCatchStatus(std::wstring_view context, Operation&& operation) - { - if (m_status.empty()) - { - m_status = RunAndReturnStatus(context, operation); - } - } - - void Status(std::wstring&& value) - { - m_status = std::move(value); - } - - bool Successful() const - { - return m_status.empty(); - } - - Windows::UI::Core::CoreDispatcher Dispatcher() const - { - return m_disabledButtons[0].Dispatcher(); - } - - private: - std::vector m_disabledButtons; - std::vector m_enabledButtons; - Windows::UI::Xaml::Controls::TextBlock m_statusText; - std::wstring m_status; - }; - - std::wstring MakeCompactByteString(uint64_t bytes) - { - static constexpr std::array s_sizeStrings = { L"B"sv, L"KB"sv, L"MB"sv, L"GB"sv }; - static constexpr size_t s_sizeIncrement = 1000; - - size_t sizeIndex = 0; - while (sizeIndex < s_sizeStrings.size() && bytes > s_sizeIncrement) - { - sizeIndex += 1; - bytes /= s_sizeIncrement; - } - - return std::to_wstring(bytes).append(L" ").append(s_sizeStrings[sizeIndex]); - } - } - - MainPage::MainPage() - { - InitializeComponent(); - m_packageCatalogs = winrt::single_threaded_observable_vector(); - m_installedPackages = winrt::single_threaded_observable_vector(); - m_activePackageViews = winrt::single_threaded_observable_vector(); - } - - Windows::Foundation::Collections::IObservableVector MainPage::PackageCatalogs() - { - return m_packageCatalogs; - } - - Windows::Foundation::Collections::IObservableVector MainPage::InstalledPackages() - { - return m_installedPackages; - } - - Windows::Foundation::Collections::IObservableVector MainPage::ActivePackages() - { - return m_activePackageViews; - } - - void MainPage::LoadCatalogsButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - LoadCatalogsAsync(); - } - - void MainPage::CatalogSelectionChangedHandler(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&) - { - m_catalog = nullptr; - } - - void MainPage::SearchButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - FindPackageAsync(); - } - - void MainPage::InstallButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - if (m_packageOperation == nullptr || m_packageOperation.Status() != AsyncStatus::Started) - { - InstallOrUpgradeAsync(false); - } - } - - void MainPage::UpgradeButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - if (m_packageOperation == nullptr || m_packageOperation.Status() != AsyncStatus::Started) - { - InstallOrUpgradeAsync(true); - } - } - - void MainPage::DownloadButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - if (m_packageOperation == nullptr || m_packageOperation.Status() != AsyncStatus::Started) - { - DownloadAsync(); - } - } - - void MainPage::CancelButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - if (m_packageOperation && m_packageOperation.Status() == AsyncStatus::Started) - { - m_packageOperation.Cancel(); - } - } - - void MainPage::RefreshInstalledButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - GetInstalledPackagesAsync(); - } - - void MainPage::UninstallButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - UninstallAsync(); - } - - void MainPage::RefreshActiveButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - GetActivePackagesAsync(); - } - - std::wstring MainPage::EnsurePackageManager(bool forceRecreate) - { - std::lock_guard lock{ m_packageManagerMutex }; - - std::wstring result; - - if (!m_packageManager || forceRecreate) - { - result = RunAndReturnStatus(L"Create PackageManager", [&]() { - m_packageManager = PackageManager{}; - }); - } - - return result; - } - - IAsyncAction MainPage::LoadCatalogsAsync() - { - BackgroundActionData actionData{ { loadCatalogsButton() }, catalogStatusText() }; - - co_await winrt::resume_background(); - - actionData.Status(EnsurePackageManager(true)); - - decltype(m_packageManager.GetPackageCatalogs()) catalogs{ nullptr }; - actionData.RunAndCatchStatus(L"Load Catalogs", [&]() { - catalogs = m_packageManager.GetPackageCatalogs(); - }); - - co_await winrt::resume_foreground(actionData.Dispatcher()); - - m_packageCatalogs.Clear(); - - if (catalogs) - { - for (auto const catalog : catalogs) - { - m_packageCatalogs.Append(catalog); - } - } - - actionData.Finalize(); - } - - IAsyncAction MainPage::FindPackageAsync() - { - hstring queryInput = queryTextBox().Text(); - auto selectedItems = catalogsListBox().SelectedItems(); - int32_t searchType = searchField().SelectedIndex(); - PackageCatalog catalog = m_catalog; - - BackgroundActionData actionData{ { searchButton() }, operationStatusText() }; - - co_await winrt::resume_background(); - - actionData.Status(EnsurePackageManager()); - - if (!catalog) - { - actionData.RunAndCatchStatus(L"Connect catalog(s)", [&]() { - PackageCatalogReference catalogReference{ nullptr }; - - if (selectedItems.Size() == 0) - { - // If no items are selected, we use all implicit catalogs. - CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions; - createCompositePackageCatalogOptions.CompositeSearchBehavior(CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs); - - for (const auto& item : m_packageManager.GetPackageCatalogs()) - { - if (!item.Info().Explicit()) - { - createCompositePackageCatalogOptions.Catalogs().Append(item); - } - } - - catalogReference = m_packageManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); - } - else if (selectedItems.Size() == 1) - { - // If one items is selected, we can directly use this catalog. - catalogReference = selectedItems.GetAt(0).as(); - } - else - { - // If multiple items are selected, we create a composite catalog using those catalogs. - CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions; - createCompositePackageCatalogOptions.CompositeSearchBehavior(CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs); - - for (const auto& item : selectedItems) - { - createCompositePackageCatalogOptions.Catalogs().Append(item.as()); - } - - catalogReference = m_packageManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); - } - - ConnectResult connectResult{ catalogReference.Connect() }; - - switch (connectResult.Status()) - { - case ConnectResultStatus::Ok: break; - case ConnectResultStatus::CatalogError: throw std::exception{ "Catalog connection error." }; - case ConnectResultStatus::SourceAgreementsNotAccepted: throw std::exception{ "Required catalog agreements not accepted." }; - } - - catalog = connectResult.PackageCatalog(); - }); - } - - CatalogPackage package{ nullptr }; - - actionData.RunAndCatchStatus(L"Find package", [&]() { - FindPackagesOptions findPackagesOptions; - PackageMatchFilter filter; - - switch (searchType) - { - case 0: // Generic query - filter.Field(PackageMatchField::CatalogDefault); - filter.Option(PackageFieldMatchOption::ContainsCaseInsensitive); - break; - case 1: // Identifier (case-insensitive) - filter.Field(PackageMatchField::Id); - filter.Option(PackageFieldMatchOption::EqualsCaseInsensitive); - break; - case 2: // Name (substring) - filter.Field(PackageMatchField::Name); - filter.Option(PackageFieldMatchOption::ContainsCaseInsensitive); - break; - } - - filter.Value(queryInput); - findPackagesOptions.Selectors().Append(filter); - FindPackagesResult findPackagesResult = catalog.FindPackages(findPackagesOptions); - - winrt::IVectorView matches = findPackagesResult.Matches(); - if (matches.Size() == 0) - { - throw std::exception{ "No package found matching input" }; - } - else if (matches.Size() > 1) - { - throw std::exception{ "Multiple packages found matching input; refine query." }; - } - else - { - package = matches.GetAt(0).CatalogPackage(); - } - }); - - if (package) - { - // Display the package name using the user's default localization information. - std::wostringstream stream; - stream << L"Found package: " << - static_cast(package.DefaultInstallVersion().GetCatalogPackageMetadata().PackageName()) << L" [" << - static_cast(package.Id()) << L"]"; - actionData.Status(std::move(stream).str()); - } - - co_await winrt::resume_foreground(actionData.Dispatcher()); - - m_catalog = catalog; - m_package = package; - - bool operationButtonsEnabled = static_cast(m_package); - installButton().IsEnabled(operationButtonsEnabled); - upgradeButton().IsEnabled(operationButtonsEnabled); - downloadButton().IsEnabled(operationButtonsEnabled); - - actionData.Finalize(); - } - - IAsyncAction MainPage::InstallOrUpgradeAsync(bool upgrade) - { - PackageManager packageManager = m_packageManager; - CatalogPackage package = m_package; - auto progressBar = operationProgressBar(); - auto statusText = operationStatusText(); - - BackgroundActionData actionData{ { installButton(), upgradeButton(), downloadButton() }, { cancelButton() }, statusText }; - - co_await winrt::resume_background(); - - Windows::Foundation::IAsyncOperationWithProgress packageOperation; - - actionData.RunAndCatchStatus(L"Begin install", [&]() { - InstallOptions installOptions; - - // Passing PackageInstallScope::User causes the install to fail if there's no installer that supports that. - installOptions.PackageInstallScope(PackageInstallScope::Any); - installOptions.PackageInstallMode(PackageInstallMode::Silent); - - if (upgrade) - { - packageOperation = packageManager.UpgradePackageAsync(package, installOptions); - } - else - { - packageOperation = packageManager.InstallPackageAsync(package, installOptions); - } - }); - - actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, - [packageOperation, this]() { m_packageOperation = packageOperation; }); - - actionData.RunAndCatchStatus(L"Set progress handler", [&]() { - packageOperation.Progress([&]( - IAsyncOperationWithProgress const& /* sender */, - InstallProgress const& progress) - { - actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, - [progressBar, statusText, progress]() { - progressBar.Value(progress.DownloadProgress * 100); - - switch (progress.State) - { - case PackageInstallProgressState::Queued: - statusText.Text(L"Queued"); - break; - case PackageInstallProgressState::Downloading: - { - std::wstring downloadText{ L"Downloaded " }; - downloadText += MakeCompactByteString(progress.BytesDownloaded) + L" of " + MakeCompactByteString(progress.BytesRequired); - statusText.Text(downloadText); - } - break; - case PackageInstallProgressState::Installing: - statusText.Text(L"Installer running"); - progressBar.IsIndeterminate(true); - break; - case PackageInstallProgressState::PostInstall: - statusText.Text(L"Post install bookkeeping"); - break; - case PackageInstallProgressState::Finished: - statusText.Text(L"Done"); - progressBar.IsIndeterminate(false); - break; - default: - statusText.Text(L""); - } - }); - }); - }); - - InstallResult installResult{ nullptr }; - - actionData.RunAndCatchStatus(L"Install", [&]() { - installResult = packageOperation.get(); - }); - - if (packageOperation && packageOperation.Status() == AsyncStatus::Canceled) - { - actionData.Status(L"Cancelled"); - } - else if (installResult) - { - // Error handling for the installResult is done by first examining the Status. - // Any status value other than Ok will have additional error detail in the - // ExtendedErrorCode property. This HRESULT value will typically (but not always) - // have the Windows Package Manager facility value (0xA15). The symbolic names and - // meanings of these error codes can be found at: - // https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md - // or by using the winget CLI: - // > winget error 0x8A150049 - // > winget error -- -2146762487 - switch (installResult.Status()) - { - case InstallResultStatus::Ok: - actionData.Status(installResult.RebootRequired() ? L"Reboot required" : L"Done"); - break; - case InstallResultStatus::BlockedByPolicy: - // See installResult.ExtendedErrorCode for more detail. - // This is typically caused by system configuration applied by policy. - actionData.Status(L"Blocked by policy"); - break; - case InstallResultStatus::CatalogError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically an issue with an external service. - actionData.Status(L"Catalog error"); - break; - case InstallResultStatus::InternalError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically an issue with the Windows Package Manager code. - actionData.Status(L"Internal error"); - break; - case InstallResultStatus::InvalidOptions: - // See installResult.ExtendedErrorCode for more detail. - // This is caused by invalid input combinations. - actionData.Status(L"Invalid options"); - break; - case InstallResultStatus::DownloadError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically a transient network error. - actionData.Status(L"Download error"); - break; - case InstallResultStatus::InstallError: - // See installResult.ExtendedErrorCode and installResult.InstallerErrorCode for more detail. - // This is caused by an error in the installer or an issue with the system state. - // InstallerErrorCode is the value returned by the installer technology in use for the install - // attempt and may or may not be an HRESULT. - actionData.Status(L"Installation error"); - break; - case InstallResultStatus::ManifestError: - // See installResult.ExtendedErrorCode for more detail. - // This is an issue with the catalog providing the package. - actionData.Status(L"Manifest error"); - break; - case InstallResultStatus::NoApplicableInstallers: - // No applicable installers were available due the combination of the current system, - // user settings, and parameters provided to the install request. - actionData.Status(L"No applicable installers"); - break; - case InstallResultStatus::NoApplicableUpgrade: - // No upgrade was available due the combination of available versions, the current system, - // user settings, and parameters provided to the upgrade request. - actionData.Status(L"No applicable upgrade"); - break; - case InstallResultStatus::PackageAgreementsNotAccepted: - // The user has not accepted the agreements required by the package. - actionData.Status(L"Package agreements not accepted"); - break; - } - } - - // Switch back to ui thread context. - co_await winrt::resume_foreground(actionData.Dispatcher()); - - progressBar.IsIndeterminate(false); - - actionData.Finalize(); - } - - IAsyncAction MainPage::DownloadAsync() - { - hstring downloadDirectory = downloadDirectoryTextBox().Text(); - PackageManager packageManager = m_packageManager; - CatalogPackage package = m_package; - auto progressBar = operationProgressBar(); - auto statusText = operationStatusText(); - - BackgroundActionData actionData{ { installButton(), upgradeButton(), downloadButton() }, { cancelButton() }, statusText}; - - co_await winrt::resume_background(); - - Windows::Foundation::IAsyncOperationWithProgress packageOperation; - - actionData.RunAndCatchStatus(L"Begin download", [&]() { - DownloadOptions downloadOptions; - - if (!downloadDirectory.empty()) - { - downloadOptions.DownloadDirectory(downloadDirectory); - } - - packageOperation = packageManager.DownloadPackageAsync(package, downloadOptions); - }); - - actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, - [packageOperation, this]() { m_packageOperation = packageOperation; }); - - actionData.RunAndCatchStatus(L"Set progress handler", [&]() { - packageOperation.Progress([&]( - IAsyncOperationWithProgress const& /* sender */, - PackageDownloadProgress const& progress) - { - actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, - [progressBar, statusText, progress]() { - progressBar.Value(progress.DownloadProgress * 100); - - switch (progress.State) - { - case PackageDownloadProgressState::Queued: - statusText.Text(L"Queued"); - break; - case PackageDownloadProgressState::Downloading: - { - std::wstring downloadText{ L"Downloaded " }; - downloadText += MakeCompactByteString(progress.BytesDownloaded) + L" of " + MakeCompactByteString(progress.BytesRequired); - statusText.Text(downloadText); - } - break; - case PackageDownloadProgressState::Finished: - statusText.Text(L"Done"); - progressBar.IsIndeterminate(false); - break; - default: - statusText.Text(L""); - } - }); - }); - }); - - DownloadResult downloadResult{ nullptr }; - - actionData.RunAndCatchStatus(L"Download", [&]() { - downloadResult = packageOperation.get(); - }); - - if (packageOperation && packageOperation.Status() == AsyncStatus::Canceled) - { - actionData.Status(L"Cancelled"); - } - else if (downloadResult) - { - // Error handling for the downloadResult is done by first examining the Status. - // Any status value other than Ok will have additional error detail in the - // ExtendedErrorCode property. This HRESULT value will typically (but not always) - // have the Windows Package Manager facility value (0xA15). The symbolic names and - // meanings of these error codes can be found at: - // https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md - // or by using the winget CLI: - // > winget error 0x8A150049 - // > winget error -- -2146762487 - switch (downloadResult.Status()) - { - case DownloadResultStatus::Ok: - actionData.Status(L"Done"); - break; - case DownloadResultStatus::BlockedByPolicy: - // See installResult.ExtendedErrorCode for more detail. - // This is typically caused by system configuration applied by policy. - actionData.Status(L"Blocked by policy"); - break; - case DownloadResultStatus::CatalogError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically an issue with an external service. - actionData.Status(L"Catalog error"); - break; - case DownloadResultStatus::InternalError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically an issue with the Windows Package Manager code. - actionData.Status(L"Internal error"); - break; - case DownloadResultStatus::InvalidOptions: - // See installResult.ExtendedErrorCode for more detail. - // This is caused by invalid input combinations. - actionData.Status(L"Invalid options"); - break; - case DownloadResultStatus::DownloadError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically a transient network error. - actionData.Status(L"Download error"); - break; - case DownloadResultStatus::ManifestError: - // See installResult.ExtendedErrorCode for more detail. - // This is an issue with the catalog providing the package. - actionData.Status(L"Manifest error"); - break; - case DownloadResultStatus::NoApplicableInstallers: - // No applicable installers were available due the combination of the current system, - // user settings, and parameters provided to the install request. - actionData.Status(L"No applicable installers"); - break; - case DownloadResultStatus::PackageAgreementsNotAccepted: - // The user has not accepted the agreements required by the package. - actionData.Status(L"Package agreements not accepted"); - break; - } - } - - // Switch back to ui thread context. - co_await winrt::resume_foreground(actionData.Dispatcher()); - - progressBar.IsIndeterminate(false); - - actionData.Finalize(); - } - - IAsyncAction MainPage::GetInstalledPackagesAsync() - { - BackgroundActionData actionData{ { refreshInstalledButton() }, installedStatusText() }; - - co_await winrt::resume_background(); - - actionData.Status(EnsurePackageManager()); - - PackageCatalog catalog{ nullptr }; - - actionData.RunAndCatchStatus(L"Connect installed catalog", [&]() { - PackageCatalogReference catalogReference = m_packageManager.GetLocalPackageCatalog(LocalPackageCatalog::InstalledPackages); - ConnectResult connectResult{ catalogReference.Connect() }; - - switch (connectResult.Status()) - { - case ConnectResultStatus::Ok: break; - case ConnectResultStatus::CatalogError: throw std::exception{ "Catalog connection error." }; - } - - catalog = connectResult.PackageCatalog(); - }); - - winrt::IVectorView matches; - - actionData.RunAndCatchStatus(L"Find package", [&]() { - FindPackagesOptions findPackagesOptions; - FindPackagesResult findPackagesResult = catalog.FindPackages(findPackagesOptions); - - matches = findPackagesResult.Matches(); - }); - - co_await winrt::resume_foreground(actionData.Dispatcher()); - - m_installedPackages.Clear(); - - if (matches) - { - for (auto const& match : matches) - { - m_installedPackages.Append(match.CatalogPackage()); - } - } - - actionData.Finalize(); - } - - IAsyncAction MainPage::UninstallAsync() - { - PackageManager packageManager = m_packageManager; - - auto progressBar = uninstallProgressBar(); - auto statusText = uninstallStatusText(); - - IInspectable selectedValue = installedListBox().SelectedValue(); - CatalogPackage package{ nullptr }; - if (selectedValue) - { - package = selectedValue.as(); - } - else - { - statusText.Text(L"Select a package to uninstall"); - co_return; - } - - BackgroundActionData actionData{ { uninstallButton() }, statusText }; - - co_await winrt::resume_background(); - - Windows::Foundation::IAsyncOperationWithProgress packageOperation; - - actionData.RunAndCatchStatus(L"Begin uninstall", [&]() { - UninstallOptions uninstallOptions; - - uninstallOptions.PackageUninstallScope(PackageUninstallScope::Any); - uninstallOptions.PackageUninstallMode(PackageUninstallMode::Silent); - - packageOperation = packageManager.UninstallPackageAsync(package, uninstallOptions); - }); - - actionData.RunAndCatchStatus(L"Set progress handler", [&]() { - packageOperation.Progress([&]( - IAsyncOperationWithProgress const& /* sender */, - UninstallProgress const& progress) - { - actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, - [progressBar, statusText, progress]() { - progressBar.Value(progress.UninstallationProgress * 100); - - switch (progress.State) - { - case PackageUninstallProgressState::Queued: - statusText.Text(L"Queued"); - break; - case PackageUninstallProgressState::Uninstalling: - statusText.Text(L"Uninstaller running"); - progressBar.IsIndeterminate(true); - break; - case PackageUninstallProgressState::PostUninstall: - statusText.Text(L"Post uninstall bookkeeping"); - break; - case PackageUninstallProgressState::Finished: - statusText.Text(L"Done"); - progressBar.IsIndeterminate(false); - break; - default: - statusText.Text(L""); - } - }); - }); - }); - - UninstallResult uninstallResult{ nullptr }; - - actionData.RunAndCatchStatus(L"Uninstall", [&]() { - uninstallResult = packageOperation.get(); - }); - - if (packageOperation && packageOperation.Status() == AsyncStatus::Canceled) - { - actionData.Status(L"Cancelled"); - } - else if (uninstallResult) - { - // Error handling for the installResult is done by first examining the Status. - // Any status value other than Ok will have additional error detail in the - // ExtendedErrorCode property. This HRESULT value will typically (but not always) - // have the Windows Package Manager facility value (0xA15). The symbolic names and - // meanings of these error codes can be found at: - // https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md - // or by using the winget CLI: - // > winget error 0x8A150049 - // > winget error -- -2146762487 - switch (uninstallResult.Status()) - { - case UninstallResultStatus::Ok: - actionData.Status(uninstallResult.RebootRequired() ? L"Reboot required" : L"Done"); - break; - case UninstallResultStatus::BlockedByPolicy: - // See installResult.ExtendedErrorCode for more detail. - // This is typically caused by system configuration applied by policy. - actionData.Status(L"Blocked by policy"); - break; - case UninstallResultStatus::CatalogError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically an issue with an external service. - actionData.Status(L"Catalog error"); - break; - case UninstallResultStatus::InternalError: - // See installResult.ExtendedErrorCode for more detail. - // This is typically an issue with the Windows Package Manager code. - actionData.Status(L"Internal error"); - break; - case UninstallResultStatus::InvalidOptions: - // See installResult.ExtendedErrorCode for more detail. - // This is caused by invalid input combinations. - actionData.Status(L"Invalid options"); - break; - case UninstallResultStatus::UninstallError: - // See installResult.ExtendedErrorCode and installResult.UninstallerErrorCode for more detail. - // This is caused by an error in the uninstaller or an issue with the system state. - // UninstallerErrorCode is the value returned by the uninstaller technology in use for the uninstall - // attempt and may or may not be an HRESULT. - actionData.Status(L"Uninstallation error"); - break; - case UninstallResultStatus::ManifestError: - // See installResult.ExtendedErrorCode for more detail. - // This is an issue with the catalog providing the package. - actionData.Status(L"Manifest error"); - break; - } - } - - // Switch back to ui thread context. - co_await winrt::resume_foreground(actionData.Dispatcher()); - - progressBar.IsIndeterminate(false); - - actionData.Finalize(); - } - - IAsyncAction MainPage::GetActivePackagesAsync() - { - BackgroundActionData actionData{ { activeRefreshButton() }, activeStatusText() }; - - co_await winrt::resume_background(); - - actionData.Status(EnsurePackageManager()); - - PackageCatalog catalog{ nullptr }; - - actionData.RunAndCatchStatus(L"Connect installed catalog", [&]() { - PackageCatalogReference catalogReference = m_packageManager.GetLocalPackageCatalog(LocalPackageCatalog::InstallingPackages); - ConnectResult connectResult{ catalogReference.Connect() }; - - switch (connectResult.Status()) - { - case ConnectResultStatus::Ok: break; - case ConnectResultStatus::CatalogError: throw std::exception{ "Catalog connection error." }; - } - - catalog = connectResult.PackageCatalog(); - }); - - winrt::IVectorView matches; - - actionData.RunAndCatchStatus(L"Find package", [&]() { - FindPackagesOptions findPackagesOptions; - FindPackagesResult findPackagesResult = catalog.FindPackages(findPackagesOptions); - - matches = findPackagesResult.Matches(); - }); - - co_await winrt::resume_foreground(actionData.Dispatcher()); - - m_activePackageViews.Clear(); - - if (matches) - { - for (auto const& match : matches) - { - WinGetUWPCaller::ActivePackageView activeView; - activeView.Package(match.CatalogPackage()); - auto installOperation = m_packageManager.GetInstallProgress(activeView.Package(), nullptr); - if (installOperation) - { - activeView.Dispatcher(actionData.Dispatcher()); - activeView.AsyncOperation(installOperation); - m_activePackageViews.Append(activeView); - } - } - } - - actionData.Finalize(); - } -} +#include "pch.h" +#include "MainPage.h" +#include "MainPage.g.cpp" + +#include + +using namespace std::chrono_literals; +using namespace std::string_view_literals; +using namespace winrt::Microsoft::Management::Deployment; + +namespace winrt +{ + using namespace Windows::UI::Xaml; + using namespace Windows::Foundation; + using namespace Windows::Foundation::Collections; +} + +namespace winrt::WinGetUWPCaller::implementation +{ + namespace + { + std::wstring ConvertExceptionToStatusString(std::wstring_view context, std::exception_ptr exceptionPtr) + { + std::wostringstream result; + + try + { + std::rethrow_exception(exceptionPtr); + } + catch (const winrt::hresult_error& error) + { + result << context << L" :: " << L"0x" << std::hex << std::setw(8) << std::setfill(L'0') << error.code() << L": " << static_cast(error.message()); + } + catch (const std::exception& exception) + { + result << context << L" :: " << exception.what(); + } + catch (...) + { + result << context << L" :: Unknown exception"; + } + + return std::move(result).str(); + } + + template + std::wstring RunAndReturnStatus(std::wstring_view context, Operation&& operation) + { + try + { + operation(); + } + catch (...) + { + return ConvertExceptionToStatusString(context, std::current_exception()); + } + + return {}; + } + + // Helper object to control button states and status text. + struct BackgroundActionData + { + // This object should be constructed on the foreground thread. + // disabledButtons will be disabled during the operation. + // enabledButtons will be enabled during the operation. + // statusText will be updated with the result. + BackgroundActionData( + std::initializer_list disabledButtons, + Windows::UI::Xaml::Controls::TextBlock statusText) : + m_disabledButtons(disabledButtons), + m_statusText(statusText) + { + if (m_disabledButtons.empty()) + { + throw std::exception("Must specify at least one disabled button."); + } + + for (const auto& button : m_disabledButtons) + { + button.IsEnabled(false); + } + + m_statusText.Text(L""); + } + + BackgroundActionData( + std::initializer_list disabledButtons, + std::initializer_list enabledButtons, + Windows::UI::Xaml::Controls::TextBlock statusText) : + BackgroundActionData(disabledButtons, statusText) + { + m_enabledButtons = enabledButtons; + for (const auto& button : m_enabledButtons) + { + button.IsEnabled(true); + } + } + + // This should be run on the foreground thread. + void Finalize() const + { + for (const auto& button : m_disabledButtons) + { + button.IsEnabled(true); + } + + for (const auto& button : m_enabledButtons) + { + button.IsEnabled(false); + } + + m_statusText.Text(m_status); + } + + template + void RunAndCatchStatus(std::wstring_view context, Operation&& operation) + { + if (m_status.empty()) + { + m_status = RunAndReturnStatus(context, operation); + } + } + + void Status(std::wstring&& value) + { + m_status = std::move(value); + } + + bool Successful() const + { + return m_status.empty(); + } + + Windows::UI::Core::CoreDispatcher Dispatcher() const + { + return m_disabledButtons[0].Dispatcher(); + } + + private: + std::vector m_disabledButtons; + std::vector m_enabledButtons; + Windows::UI::Xaml::Controls::TextBlock m_statusText; + std::wstring m_status; + }; + + std::wstring MakeCompactByteString(uint64_t bytes) + { + static constexpr std::array s_sizeStrings = { L"B"sv, L"KB"sv, L"MB"sv, L"GB"sv }; + static constexpr size_t s_sizeIncrement = 1000; + + size_t sizeIndex = 0; + while (sizeIndex < s_sizeStrings.size() && bytes > s_sizeIncrement) + { + sizeIndex += 1; + bytes /= s_sizeIncrement; + } + + return std::to_wstring(bytes).append(L" ").append(s_sizeStrings[sizeIndex]); + } + } + + MainPage::MainPage() + { + InitializeComponent(); + m_packageCatalogs = winrt::single_threaded_observable_vector(); + m_installedPackages = winrt::single_threaded_observable_vector(); + m_activePackageViews = winrt::single_threaded_observable_vector(); + } + + Windows::Foundation::Collections::IObservableVector MainPage::PackageCatalogs() + { + return m_packageCatalogs; + } + + Windows::Foundation::Collections::IObservableVector MainPage::InstalledPackages() + { + return m_installedPackages; + } + + Windows::Foundation::Collections::IObservableVector MainPage::ActivePackages() + { + return m_activePackageViews; + } + + void MainPage::LoadCatalogsButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + LoadCatalogsAsync(); + } + + void MainPage::CatalogSelectionChangedHandler(Windows::Foundation::IInspectable const&, Windows::UI::Xaml::RoutedEventArgs const&) + { + m_catalog = nullptr; + } + + void MainPage::SearchButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + FindPackageAsync(); + } + + void MainPage::InstallButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + if (m_packageOperation == nullptr || m_packageOperation.Status() != AsyncStatus::Started) + { + InstallOrUpgradeAsync(false); + } + } + + void MainPage::UpgradeButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + if (m_packageOperation == nullptr || m_packageOperation.Status() != AsyncStatus::Started) + { + InstallOrUpgradeAsync(true); + } + } + + void MainPage::DownloadButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + if (m_packageOperation == nullptr || m_packageOperation.Status() != AsyncStatus::Started) + { + DownloadAsync(); + } + } + + void MainPage::CancelButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + if (m_packageOperation && m_packageOperation.Status() == AsyncStatus::Started) + { + m_packageOperation.Cancel(); + } + } + + void MainPage::RefreshInstalledButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + GetInstalledPackagesAsync(); + } + + void MainPage::UninstallButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + UninstallAsync(); + } + + void MainPage::RefreshActiveButtonClickHandler(IInspectable const&, RoutedEventArgs const&) + { + GetActivePackagesAsync(); + } + + std::wstring MainPage::EnsurePackageManager(bool forceRecreate) + { + std::lock_guard lock{ m_packageManagerMutex }; + + std::wstring result; + + if (!m_packageManager || forceRecreate) + { + result = RunAndReturnStatus(L"Create PackageManager", [&]() { + m_packageManager = PackageManager{}; + }); + } + + return result; + } + + IAsyncAction MainPage::LoadCatalogsAsync() + { + BackgroundActionData actionData{ { loadCatalogsButton() }, catalogStatusText() }; + + co_await winrt::resume_background(); + + actionData.Status(EnsurePackageManager(true)); + + decltype(m_packageManager.GetPackageCatalogs()) catalogs{ nullptr }; + actionData.RunAndCatchStatus(L"Load Catalogs", [&]() { + catalogs = m_packageManager.GetPackageCatalogs(); + }); + + co_await winrt::resume_foreground(actionData.Dispatcher()); + + m_packageCatalogs.Clear(); + + if (catalogs) + { + for (auto const catalog : catalogs) + { + m_packageCatalogs.Append(catalog); + } + } + + actionData.Finalize(); + } + + IAsyncAction MainPage::FindPackageAsync() + { + hstring queryInput = queryTextBox().Text(); + auto selectedItems = catalogsListBox().SelectedItems(); + int32_t searchType = searchField().SelectedIndex(); + PackageCatalog catalog = m_catalog; + + BackgroundActionData actionData{ { searchButton() }, operationStatusText() }; + + co_await winrt::resume_background(); + + actionData.Status(EnsurePackageManager()); + + if (!catalog) + { + actionData.RunAndCatchStatus(L"Connect catalog(s)", [&]() { + PackageCatalogReference catalogReference{ nullptr }; + + if (selectedItems.Size() == 0) + { + // If no items are selected, we use all implicit catalogs. + CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions; + createCompositePackageCatalogOptions.CompositeSearchBehavior(CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs); + + for (const auto& item : m_packageManager.GetPackageCatalogs()) + { + if (!item.Info().Explicit()) + { + createCompositePackageCatalogOptions.Catalogs().Append(item); + } + } + + catalogReference = m_packageManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); + } + else if (selectedItems.Size() == 1) + { + // If one items is selected, we can directly use this catalog. + catalogReference = selectedItems.GetAt(0).as(); + } + else + { + // If multiple items are selected, we create a composite catalog using those catalogs. + CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions; + createCompositePackageCatalogOptions.CompositeSearchBehavior(CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs); + + for (const auto& item : selectedItems) + { + createCompositePackageCatalogOptions.Catalogs().Append(item.as()); + } + + catalogReference = m_packageManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); + } + + ConnectResult connectResult{ catalogReference.Connect() }; + + switch (connectResult.Status()) + { + case ConnectResultStatus::Ok: break; + case ConnectResultStatus::CatalogError: throw std::exception{ "Catalog connection error." }; + case ConnectResultStatus::SourceAgreementsNotAccepted: throw std::exception{ "Required catalog agreements not accepted." }; + } + + catalog = connectResult.PackageCatalog(); + }); + } + + CatalogPackage package{ nullptr }; + + actionData.RunAndCatchStatus(L"Find package", [&]() { + FindPackagesOptions findPackagesOptions; + PackageMatchFilter filter; + + switch (searchType) + { + case 0: // Generic query + filter.Field(PackageMatchField::CatalogDefault); + filter.Option(PackageFieldMatchOption::ContainsCaseInsensitive); + break; + case 1: // Identifier (case-insensitive) + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::EqualsCaseInsensitive); + break; + case 2: // Name (substring) + filter.Field(PackageMatchField::Name); + filter.Option(PackageFieldMatchOption::ContainsCaseInsensitive); + break; + } + + filter.Value(queryInput); + findPackagesOptions.Selectors().Append(filter); + FindPackagesResult findPackagesResult = catalog.FindPackages(findPackagesOptions); + + winrt::IVectorView matches = findPackagesResult.Matches(); + if (matches.Size() == 0) + { + throw std::exception{ "No package found matching input" }; + } + else if (matches.Size() > 1) + { + throw std::exception{ "Multiple packages found matching input; refine query." }; + } + else + { + package = matches.GetAt(0).CatalogPackage(); + } + }); + + if (package) + { + // Display the package name using the user's default localization information. + std::wostringstream stream; + stream << L"Found package: " << + static_cast(package.DefaultInstallVersion().GetCatalogPackageMetadata().PackageName()) << L" [" << + static_cast(package.Id()) << L"]"; + actionData.Status(std::move(stream).str()); + } + + co_await winrt::resume_foreground(actionData.Dispatcher()); + + m_catalog = catalog; + m_package = package; + + bool operationButtonsEnabled = static_cast(m_package); + installButton().IsEnabled(operationButtonsEnabled); + upgradeButton().IsEnabled(operationButtonsEnabled); + downloadButton().IsEnabled(operationButtonsEnabled); + + actionData.Finalize(); + } + + IAsyncAction MainPage::InstallOrUpgradeAsync(bool upgrade) + { + PackageManager packageManager = m_packageManager; + CatalogPackage package = m_package; + auto progressBar = operationProgressBar(); + auto statusText = operationStatusText(); + + BackgroundActionData actionData{ { installButton(), upgradeButton(), downloadButton() }, { cancelButton() }, statusText }; + + co_await winrt::resume_background(); + + Windows::Foundation::IAsyncOperationWithProgress packageOperation; + + actionData.RunAndCatchStatus(L"Begin install", [&]() { + InstallOptions installOptions; + + // Passing PackageInstallScope::User causes the install to fail if there's no installer that supports that. + installOptions.PackageInstallScope(PackageInstallScope::Any); + installOptions.PackageInstallMode(PackageInstallMode::Silent); + + if (upgrade) + { + packageOperation = packageManager.UpgradePackageAsync(package, installOptions); + } + else + { + packageOperation = packageManager.InstallPackageAsync(package, installOptions); + } + }); + + actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, + [packageOperation, this]() { m_packageOperation = packageOperation; }); + + actionData.RunAndCatchStatus(L"Set progress handler", [&]() { + packageOperation.Progress([&]( + IAsyncOperationWithProgress const& /* sender */, + InstallProgress const& progress) + { + actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, + [progressBar, statusText, progress]() { + progressBar.Value(progress.DownloadProgress * 100); + + switch (progress.State) + { + case PackageInstallProgressState::Queued: + statusText.Text(L"Queued"); + break; + case PackageInstallProgressState::Downloading: + { + std::wstring downloadText{ L"Downloaded " }; + downloadText += MakeCompactByteString(progress.BytesDownloaded) + L" of " + MakeCompactByteString(progress.BytesRequired); + statusText.Text(downloadText); + } + break; + case PackageInstallProgressState::Installing: + statusText.Text(L"Installer running"); + progressBar.IsIndeterminate(true); + break; + case PackageInstallProgressState::PostInstall: + statusText.Text(L"Post install bookkeeping"); + break; + case PackageInstallProgressState::Finished: + statusText.Text(L"Done"); + progressBar.IsIndeterminate(false); + break; + default: + statusText.Text(L""); + } + }); + }); + }); + + InstallResult installResult{ nullptr }; + + actionData.RunAndCatchStatus(L"Install", [&]() { + installResult = packageOperation.get(); + }); + + if (packageOperation && packageOperation.Status() == AsyncStatus::Canceled) + { + actionData.Status(L"Cancelled"); + } + else if (installResult) + { + // Error handling for the installResult is done by first examining the Status. + // Any status value other than Ok will have additional error detail in the + // ExtendedErrorCode property. This HRESULT value will typically (but not always) + // have the Windows Package Manager facility value (0xA15). The symbolic names and + // meanings of these error codes can be found at: + // https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md + // or by using the winget CLI: + // > winget error 0x8A150049 + // > winget error -- -2146762487 + switch (installResult.Status()) + { + case InstallResultStatus::Ok: + actionData.Status(installResult.RebootRequired() ? L"Reboot required" : L"Done"); + break; + case InstallResultStatus::BlockedByPolicy: + // See installResult.ExtendedErrorCode for more detail. + // This is typically caused by system configuration applied by policy. + actionData.Status(L"Blocked by policy"); + break; + case InstallResultStatus::CatalogError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically an issue with an external service. + actionData.Status(L"Catalog error"); + break; + case InstallResultStatus::InternalError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically an issue with the Windows Package Manager code. + actionData.Status(L"Internal error"); + break; + case InstallResultStatus::InvalidOptions: + // See installResult.ExtendedErrorCode for more detail. + // This is caused by invalid input combinations. + actionData.Status(L"Invalid options"); + break; + case InstallResultStatus::DownloadError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically a transient network error. + actionData.Status(L"Download error"); + break; + case InstallResultStatus::InstallError: + // See installResult.ExtendedErrorCode and installResult.InstallerErrorCode for more detail. + // This is caused by an error in the installer or an issue with the system state. + // InstallerErrorCode is the value returned by the installer technology in use for the install + // attempt and may or may not be an HRESULT. + actionData.Status(L"Installation error"); + break; + case InstallResultStatus::ManifestError: + // See installResult.ExtendedErrorCode for more detail. + // This is an issue with the catalog providing the package. + actionData.Status(L"Manifest error"); + break; + case InstallResultStatus::NoApplicableInstallers: + // No applicable installers were available due the combination of the current system, + // user settings, and parameters provided to the install request. + actionData.Status(L"No applicable installers"); + break; + case InstallResultStatus::NoApplicableUpgrade: + // No upgrade was available due the combination of available versions, the current system, + // user settings, and parameters provided to the upgrade request. + actionData.Status(L"No applicable upgrade"); + break; + case InstallResultStatus::PackageAgreementsNotAccepted: + // The user has not accepted the agreements required by the package. + actionData.Status(L"Package agreements not accepted"); + break; + } + } + + // Switch back to ui thread context. + co_await winrt::resume_foreground(actionData.Dispatcher()); + + progressBar.IsIndeterminate(false); + + actionData.Finalize(); + } + + IAsyncAction MainPage::DownloadAsync() + { + hstring downloadDirectory = downloadDirectoryTextBox().Text(); + PackageManager packageManager = m_packageManager; + CatalogPackage package = m_package; + auto progressBar = operationProgressBar(); + auto statusText = operationStatusText(); + + BackgroundActionData actionData{ { installButton(), upgradeButton(), downloadButton() }, { cancelButton() }, statusText}; + + co_await winrt::resume_background(); + + Windows::Foundation::IAsyncOperationWithProgress packageOperation; + + actionData.RunAndCatchStatus(L"Begin download", [&]() { + DownloadOptions downloadOptions; + + if (!downloadDirectory.empty()) + { + downloadOptions.DownloadDirectory(downloadDirectory); + } + + packageOperation = packageManager.DownloadPackageAsync(package, downloadOptions); + }); + + actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, + [packageOperation, this]() { m_packageOperation = packageOperation; }); + + actionData.RunAndCatchStatus(L"Set progress handler", [&]() { + packageOperation.Progress([&]( + IAsyncOperationWithProgress const& /* sender */, + PackageDownloadProgress const& progress) + { + actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, + [progressBar, statusText, progress]() { + progressBar.Value(progress.DownloadProgress * 100); + + switch (progress.State) + { + case PackageDownloadProgressState::Queued: + statusText.Text(L"Queued"); + break; + case PackageDownloadProgressState::Downloading: + { + std::wstring downloadText{ L"Downloaded " }; + downloadText += MakeCompactByteString(progress.BytesDownloaded) + L" of " + MakeCompactByteString(progress.BytesRequired); + statusText.Text(downloadText); + } + break; + case PackageDownloadProgressState::Finished: + statusText.Text(L"Done"); + progressBar.IsIndeterminate(false); + break; + default: + statusText.Text(L""); + } + }); + }); + }); + + DownloadResult downloadResult{ nullptr }; + + actionData.RunAndCatchStatus(L"Download", [&]() { + downloadResult = packageOperation.get(); + }); + + if (packageOperation && packageOperation.Status() == AsyncStatus::Canceled) + { + actionData.Status(L"Cancelled"); + } + else if (downloadResult) + { + // Error handling for the downloadResult is done by first examining the Status. + // Any status value other than Ok will have additional error detail in the + // ExtendedErrorCode property. This HRESULT value will typically (but not always) + // have the Windows Package Manager facility value (0xA15). The symbolic names and + // meanings of these error codes can be found at: + // https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md + // or by using the winget CLI: + // > winget error 0x8A150049 + // > winget error -- -2146762487 + switch (downloadResult.Status()) + { + case DownloadResultStatus::Ok: + actionData.Status(L"Done"); + break; + case DownloadResultStatus::BlockedByPolicy: + // See installResult.ExtendedErrorCode for more detail. + // This is typically caused by system configuration applied by policy. + actionData.Status(L"Blocked by policy"); + break; + case DownloadResultStatus::CatalogError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically an issue with an external service. + actionData.Status(L"Catalog error"); + break; + case DownloadResultStatus::InternalError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically an issue with the Windows Package Manager code. + actionData.Status(L"Internal error"); + break; + case DownloadResultStatus::InvalidOptions: + // See installResult.ExtendedErrorCode for more detail. + // This is caused by invalid input combinations. + actionData.Status(L"Invalid options"); + break; + case DownloadResultStatus::DownloadError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically a transient network error. + actionData.Status(L"Download error"); + break; + case DownloadResultStatus::ManifestError: + // See installResult.ExtendedErrorCode for more detail. + // This is an issue with the catalog providing the package. + actionData.Status(L"Manifest error"); + break; + case DownloadResultStatus::NoApplicableInstallers: + // No applicable installers were available due the combination of the current system, + // user settings, and parameters provided to the install request. + actionData.Status(L"No applicable installers"); + break; + case DownloadResultStatus::PackageAgreementsNotAccepted: + // The user has not accepted the agreements required by the package. + actionData.Status(L"Package agreements not accepted"); + break; + } + } + + // Switch back to ui thread context. + co_await winrt::resume_foreground(actionData.Dispatcher()); + + progressBar.IsIndeterminate(false); + + actionData.Finalize(); + } + + IAsyncAction MainPage::GetInstalledPackagesAsync() + { + BackgroundActionData actionData{ { refreshInstalledButton() }, installedStatusText() }; + + co_await winrt::resume_background(); + + actionData.Status(EnsurePackageManager()); + + PackageCatalog catalog{ nullptr }; + + actionData.RunAndCatchStatus(L"Connect installed catalog", [&]() { + PackageCatalogReference catalogReference = m_packageManager.GetLocalPackageCatalog(LocalPackageCatalog::InstalledPackages); + ConnectResult connectResult{ catalogReference.Connect() }; + + switch (connectResult.Status()) + { + case ConnectResultStatus::Ok: break; + case ConnectResultStatus::CatalogError: throw std::exception{ "Catalog connection error." }; + } + + catalog = connectResult.PackageCatalog(); + }); + + winrt::IVectorView matches; + + actionData.RunAndCatchStatus(L"Find package", [&]() { + FindPackagesOptions findPackagesOptions; + FindPackagesResult findPackagesResult = catalog.FindPackages(findPackagesOptions); + + matches = findPackagesResult.Matches(); + }); + + co_await winrt::resume_foreground(actionData.Dispatcher()); + + m_installedPackages.Clear(); + + if (matches) + { + for (auto const& match : matches) + { + m_installedPackages.Append(match.CatalogPackage()); + } + } + + actionData.Finalize(); + } + + IAsyncAction MainPage::UninstallAsync() + { + PackageManager packageManager = m_packageManager; + + auto progressBar = uninstallProgressBar(); + auto statusText = uninstallStatusText(); + + IInspectable selectedValue = installedListBox().SelectedValue(); + CatalogPackage package{ nullptr }; + if (selectedValue) + { + package = selectedValue.as(); + } + else + { + statusText.Text(L"Select a package to uninstall"); + co_return; + } + + BackgroundActionData actionData{ { uninstallButton() }, statusText }; + + co_await winrt::resume_background(); + + Windows::Foundation::IAsyncOperationWithProgress packageOperation; + + actionData.RunAndCatchStatus(L"Begin uninstall", [&]() { + UninstallOptions uninstallOptions; + + uninstallOptions.PackageUninstallScope(PackageUninstallScope::Any); + uninstallOptions.PackageUninstallMode(PackageUninstallMode::Silent); + + packageOperation = packageManager.UninstallPackageAsync(package, uninstallOptions); + }); + + actionData.RunAndCatchStatus(L"Set progress handler", [&]() { + packageOperation.Progress([&]( + IAsyncOperationWithProgress const& /* sender */, + UninstallProgress const& progress) + { + actionData.Dispatcher().RunAsync(Windows::UI::Core::CoreDispatcherPriority::Normal, + [progressBar, statusText, progress]() { + progressBar.Value(progress.UninstallationProgress * 100); + + switch (progress.State) + { + case PackageUninstallProgressState::Queued: + statusText.Text(L"Queued"); + break; + case PackageUninstallProgressState::Uninstalling: + statusText.Text(L"Uninstaller running"); + progressBar.IsIndeterminate(true); + break; + case PackageUninstallProgressState::PostUninstall: + statusText.Text(L"Post uninstall bookkeeping"); + break; + case PackageUninstallProgressState::Finished: + statusText.Text(L"Done"); + progressBar.IsIndeterminate(false); + break; + default: + statusText.Text(L""); + } + }); + }); + }); + + UninstallResult uninstallResult{ nullptr }; + + actionData.RunAndCatchStatus(L"Uninstall", [&]() { + uninstallResult = packageOperation.get(); + }); + + if (packageOperation && packageOperation.Status() == AsyncStatus::Canceled) + { + actionData.Status(L"Cancelled"); + } + else if (uninstallResult) + { + // Error handling for the installResult is done by first examining the Status. + // Any status value other than Ok will have additional error detail in the + // ExtendedErrorCode property. This HRESULT value will typically (but not always) + // have the Windows Package Manager facility value (0xA15). The symbolic names and + // meanings of these error codes can be found at: + // https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md + // or by using the winget CLI: + // > winget error 0x8A150049 + // > winget error -- -2146762487 + switch (uninstallResult.Status()) + { + case UninstallResultStatus::Ok: + actionData.Status(uninstallResult.RebootRequired() ? L"Reboot required" : L"Done"); + break; + case UninstallResultStatus::BlockedByPolicy: + // See installResult.ExtendedErrorCode for more detail. + // This is typically caused by system configuration applied by policy. + actionData.Status(L"Blocked by policy"); + break; + case UninstallResultStatus::CatalogError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically an issue with an external service. + actionData.Status(L"Catalog error"); + break; + case UninstallResultStatus::InternalError: + // See installResult.ExtendedErrorCode for more detail. + // This is typically an issue with the Windows Package Manager code. + actionData.Status(L"Internal error"); + break; + case UninstallResultStatus::InvalidOptions: + // See installResult.ExtendedErrorCode for more detail. + // This is caused by invalid input combinations. + actionData.Status(L"Invalid options"); + break; + case UninstallResultStatus::UninstallError: + // See installResult.ExtendedErrorCode and installResult.UninstallerErrorCode for more detail. + // This is caused by an error in the uninstaller or an issue with the system state. + // UninstallerErrorCode is the value returned by the uninstaller technology in use for the uninstall + // attempt and may or may not be an HRESULT. + actionData.Status(L"Uninstallation error"); + break; + case UninstallResultStatus::ManifestError: + // See installResult.ExtendedErrorCode for more detail. + // This is an issue with the catalog providing the package. + actionData.Status(L"Manifest error"); + break; + } + } + + // Switch back to ui thread context. + co_await winrt::resume_foreground(actionData.Dispatcher()); + + progressBar.IsIndeterminate(false); + + actionData.Finalize(); + } + + IAsyncAction MainPage::GetActivePackagesAsync() + { + BackgroundActionData actionData{ { activeRefreshButton() }, activeStatusText() }; + + co_await winrt::resume_background(); + + actionData.Status(EnsurePackageManager()); + + PackageCatalog catalog{ nullptr }; + + actionData.RunAndCatchStatus(L"Connect installed catalog", [&]() { + PackageCatalogReference catalogReference = m_packageManager.GetLocalPackageCatalog(LocalPackageCatalog::InstallingPackages); + ConnectResult connectResult{ catalogReference.Connect() }; + + switch (connectResult.Status()) + { + case ConnectResultStatus::Ok: break; + case ConnectResultStatus::CatalogError: throw std::exception{ "Catalog connection error." }; + } + + catalog = connectResult.PackageCatalog(); + }); + + winrt::IVectorView matches; + + actionData.RunAndCatchStatus(L"Find package", [&]() { + FindPackagesOptions findPackagesOptions; + FindPackagesResult findPackagesResult = catalog.FindPackages(findPackagesOptions); + + matches = findPackagesResult.Matches(); + }); + + co_await winrt::resume_foreground(actionData.Dispatcher()); + + m_activePackageViews.Clear(); + + if (matches) + { + for (auto const& match : matches) + { + WinGetUWPCaller::ActivePackageView activeView; + activeView.Package(match.CatalogPackage()); + auto installOperation = m_packageManager.GetInstallProgress(activeView.Package(), nullptr); + if (installOperation) + { + activeView.Dispatcher(actionData.Dispatcher()); + activeView.AsyncOperation(installOperation); + m_activePackageViews.Append(activeView); + } + } + } + + actionData.Finalize(); + } +} diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.h b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.h index 2debcad2ef..ffa7fc6148 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.h +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.h @@ -1,79 +1,79 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "MainPage.g.h" -#include "ActivePackageView.h" -#include -#include - -namespace Deployment = winrt::Microsoft::Management::Deployment; - -namespace winrt::WinGetUWPCaller::implementation -{ - struct MainPage : MainPageT - { - MainPage(); - - Windows::Foundation::Collections::IObservableVector PackageCatalogs(); - Windows::Foundation::Collections::IObservableVector InstalledPackages(); - Windows::Foundation::Collections::IObservableVector ActivePackages(); - - // Select Catalog(s) section - void LoadCatalogsButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - void CatalogSelectionChangedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - - // Package Operations section - void SearchButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - void InstallButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - void UpgradeButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - void DownloadButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - void CancelButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - - // Installed Packages section - void RefreshInstalledButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - void UninstallButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - - // Active Operations section - void RefreshActiveButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); - - private: - // Ensures that the package manager object exists; potentially recreating it if requested. - // Should be called from a background thread. - // Is thread-safe. - // Returns an error string if it fails. - std::wstring EnsurePackageManager(bool forceRecreate = false); - - // Select Catalog(s) section - Windows::Foundation::IAsyncAction LoadCatalogsAsync(); - - // Package Operations section - Windows::Foundation::IAsyncAction FindPackageAsync(); - Windows::Foundation::IAsyncAction InstallOrUpgradeAsync(bool upgrade); - Windows::Foundation::IAsyncAction DownloadAsync(); - - // Installed Packages section - Windows::Foundation::IAsyncAction GetInstalledPackagesAsync(); - Windows::Foundation::IAsyncAction UninstallAsync(); - - // Active Operations section - Windows::Foundation::IAsyncAction GetActivePackagesAsync(); - - // Member fields - Windows::Foundation::Collections::IObservableVector m_packageCatalogs; - Windows::Foundation::Collections::IObservableVector m_installedPackages; - Windows::Foundation::Collections::IObservableVector m_activePackageViews; - - std::mutex m_packageManagerMutex; - Deployment::PackageManager m_packageManager{ nullptr }; - Deployment::PackageCatalog m_catalog{ nullptr }; - Deployment::CatalogPackage m_package{ nullptr }; - Windows::Foundation::IAsyncInfo m_packageOperation; - }; -} - -namespace winrt::WinGetUWPCaller::factory_implementation -{ - struct MainPage : MainPageT - { - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "MainPage.g.h" +#include "ActivePackageView.h" +#include +#include + +namespace Deployment = winrt::Microsoft::Management::Deployment; + +namespace winrt::WinGetUWPCaller::implementation +{ + struct MainPage : MainPageT + { + MainPage(); + + Windows::Foundation::Collections::IObservableVector PackageCatalogs(); + Windows::Foundation::Collections::IObservableVector InstalledPackages(); + Windows::Foundation::Collections::IObservableVector ActivePackages(); + + // Select Catalog(s) section + void LoadCatalogsButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void CatalogSelectionChangedHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + + // Package Operations section + void SearchButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void InstallButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void UpgradeButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void DownloadButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void CancelButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + + // Installed Packages section + void RefreshInstalledButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + void UninstallButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + + // Active Operations section + void RefreshActiveButtonClickHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& args); + + private: + // Ensures that the package manager object exists; potentially recreating it if requested. + // Should be called from a background thread. + // Is thread-safe. + // Returns an error string if it fails. + std::wstring EnsurePackageManager(bool forceRecreate = false); + + // Select Catalog(s) section + Windows::Foundation::IAsyncAction LoadCatalogsAsync(); + + // Package Operations section + Windows::Foundation::IAsyncAction FindPackageAsync(); + Windows::Foundation::IAsyncAction InstallOrUpgradeAsync(bool upgrade); + Windows::Foundation::IAsyncAction DownloadAsync(); + + // Installed Packages section + Windows::Foundation::IAsyncAction GetInstalledPackagesAsync(); + Windows::Foundation::IAsyncAction UninstallAsync(); + + // Active Operations section + Windows::Foundation::IAsyncAction GetActivePackagesAsync(); + + // Member fields + Windows::Foundation::Collections::IObservableVector m_packageCatalogs; + Windows::Foundation::Collections::IObservableVector m_installedPackages; + Windows::Foundation::Collections::IObservableVector m_activePackageViews; + + std::mutex m_packageManagerMutex; + Deployment::PackageManager m_packageManager{ nullptr }; + Deployment::PackageCatalog m_catalog{ nullptr }; + Deployment::CatalogPackage m_package{ nullptr }; + Windows::Foundation::IAsyncInfo m_packageOperation; + }; +} + +namespace winrt::WinGetUWPCaller::factory_implementation +{ + struct MainPage : MainPageT + { + }; +} diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.idl b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.idl index 39e8ac0a62..92a02ea524 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.idl +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.idl @@ -5,7 +5,7 @@ namespace WinGetUWPCaller [default_interface] runtimeclass ActivePackageView : Windows.UI.Xaml.Data.INotifyPropertyChanged { - ActivePackageView(); + ActivePackageView(); Microsoft.Management.Deployment.CatalogPackage Package; Windows.Foundation.IAsyncOperationWithProgress AsyncOperation; @@ -17,7 +17,7 @@ namespace WinGetUWPCaller [default_interface] runtimeclass MainPage : Windows.UI.Xaml.Controls.Page { - MainPage(); + MainPage(); Windows.Foundation.Collections.IObservableVector PackageCatalogs{ get; }; Windows.Foundation.Collections.IObservableVector InstalledPackages{ get; }; diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.xaml b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.xaml index 3282556f19..f8885be9b3 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.xaml +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/MainPage.xaml @@ -1,117 +1,117 @@ - - - - - - - - - - - - - - - - - - - - - - - Select Catalog(s) - - - - Select catalog(s) to use - - - - - - - - - - - Package Operations - Search for a package from the selected catalog(s) and take action on it. - - - - - - - - - - - - - - - - - - - - - - - - - - Installed Packages - View installed package information - - - - - - - - - - - - - - - - - Active Operations - View active package install/upgrade operations - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + Select Catalog(s) + + + + Select catalog(s) to use + + + + + + + + + + + Package Operations + Search for a package from the selected catalog(s) and take action on it. + + + + + + + + + + + + + + + + + + + + + + + + + + Installed Packages + View installed package information + + + + + + + + + + + + + + + + + Active Operations + View active package install/upgrade operations + + + + + + + + + + + + + + + + + + diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj b/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj index dfecf3cc29..b3962df21c 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj @@ -1,199 +1,199 @@ - - - - - true - true - true - true - {37f1fd2a-4d63-45a0-82aa-66ef126cb322} - WinGetUWPCaller - WinGetUWPCaller - en-US - 15.0 - true - Windows Store - 10.0 - 10.0.22000.0 - 10.0.17763.0 - - - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - Application - - - true - true - - - false - true - false - - - - - - - - - False - SHA256 - True - True - x64 - 0 - Always - - - - Use - pch.h - $(IntDir)pch.pch - Level4 - %(AdditionalOptions) /bigobj - - /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) - - - WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - - - false - - - - - _DEBUG;%(PreprocessorDefinitions) - - - - - - - - - NDEBUG;%(PreprocessorDefinitions) - - - true - true - - - - - - - - - - - App.xaml - - - MainPage.xaml - - - - - Designer - - - Designer - - - - - Designer - - - - - - - - - - - - - - - Create - - - App.xaml - - - MainPage.xaml - - - - - - App.xaml - - - MainPage.xaml - - - - - - - - win10-x86 - win10-x64 - win10-arm64 - - - - Microsoft.Management.Deployment.dll - - - - - - ..\packages\Microsoft.WindowsPackageManager.ComInterop.1.8.1911\lib\Microsoft.Management.Deployment.winmd - true - Microsoft.Management.Deployment.dll - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + true + true + true + true + {37f1fd2a-4d63-45a0-82aa-66ef126cb322} + WinGetUWPCaller + WinGetUWPCaller + en-US + 15.0 + true + Windows Store + 10.0 + 10.0.22000.0 + 10.0.17763.0 + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + Application + + + true + true + + + false + true + false + + + + + + + + + False + SHA256 + True + True + x64 + 0 + Always + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + + /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) + + + WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + + + false + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + + App.xaml + + + MainPage.xaml + + + + + Designer + + + Designer + + + + + Designer + + + + + + + + + + + + + + + Create + + + App.xaml + + + MainPage.xaml + + + + + + App.xaml + + + MainPage.xaml + + + + + + + + win10-x86 + win10-x64 + win10-arm64 + + + + Microsoft.Management.Deployment.dll + + + + + + ..\packages\Microsoft.WindowsPackageManager.ComInterop.1.8.1911\lib\Microsoft.Management.Deployment.winmd + true + Microsoft.Management.Deployment.dll + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj.filters b/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj.filters index 5d42a428ca..03e143d693 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj.filters +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/WinGetUWPCaller.vcxproj.filters @@ -1,58 +1,58 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - - - - - - - - - {adeebf4e-70b7-41ae-936b-ded00e6e6777} - - + + + + + + + + + + + + + + + + + + + + + + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + + + + + + + {adeebf4e-70b7-41ae-936b-ded00e6e6777} + + \ No newline at end of file diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/packages.config b/samples/WinGetUWPCaller/WinGetUWPCaller/packages.config index d716d962e4..9213f28aa2 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/packages.config +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/samples/WinGetUWPCaller/WinGetUWPCaller/pch.h b/samples/WinGetUWPCaller/WinGetUWPCaller/pch.h index ad1bd05d51..cdae0f23cc 100644 --- a/samples/WinGetUWPCaller/WinGetUWPCaller/pch.h +++ b/samples/WinGetUWPCaller/WinGetUWPCaller/pch.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include #include #include #include @@ -15,11 +15,11 @@ #include #include #include - -#include -#include + +#include +#include #include -#include +#include #include #include #include diff --git a/schemas/JSON/configuration/configuration.schema.0.1.json b/schemas/JSON/configuration/configuration.schema.0.1.json index 6024af63e3..255ecf2b9f 100644 --- a/schemas/JSON/configuration/configuration.schema.0.1.json +++ b/schemas/JSON/configuration/configuration.schema.0.1.json @@ -1,78 +1,78 @@ -{ - "$id": "https://aka.ms/schemas/dsc/configuration.schema.0.1.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "description": "A representation of a configuration used by an orchestrator for WinDSC.", - "type": "object", - "properties": { - "properties": { - "type": "object", - "properties": { - "assertions": { - "type": "array", - "items": { "$ref": "#/$defs/resource" } - }, - "resources": { - "type": "array", - "items": { "$ref": "#/$defs/resource" } - }, - "parameters": { - "type": "array", - "items": { "$ref": "#/$defs/resource" }, - "description": "Resources that retrieve information via a 'get' operation." - }, - "configurationVersion": { - "type": "string", - "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", - "maxLength": 128, - "default": "0.1.0", - "description": "The configuration syntax version." - } - }, - "required": ["configurationVersion"] - } - }, - - "$defs": { - "resource": { - "type": "object", - "properties": { - "resource": { - "type": "string", - "maxLength": 128, - "description": "The name of the resource." - }, - "id": { - "type": "string", - "maxLength": 128, - "description": "The identifier of this item." - }, - "dependsOn": { - "type": [ "array", "null" ], - "items": { - "type": "string" - }, - "uniqueItems": true, - "description": "The list of resource ids identifying dependencies." - }, - "directives": { - "type": "object", - "properties": { - "module": { - "type": "string", - "maxLength": 128, - "description": "The name of the module." - }, - "description": { - "type": "string", - "maxLength": 512, - "description": "The description of the desired state." - } - }, - "additionalProperties": true - }, - "settings": { "type": "object" } - }, - "required": ["resource"] - } - } +{ + "$id": "https://aka.ms/schemas/dsc/configuration.schema.0.1.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "description": "A representation of a configuration used by an orchestrator for WinDSC.", + "type": "object", + "properties": { + "properties": { + "type": "object", + "properties": { + "assertions": { + "type": "array", + "items": { "$ref": "#/$defs/resource" } + }, + "resources": { + "type": "array", + "items": { "$ref": "#/$defs/resource" } + }, + "parameters": { + "type": "array", + "items": { "$ref": "#/$defs/resource" }, + "description": "Resources that retrieve information via a 'get' operation." + }, + "configurationVersion": { + "type": "string", + "pattern": "^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$", + "maxLength": 128, + "default": "0.1.0", + "description": "The configuration syntax version." + } + }, + "required": ["configurationVersion"] + } + }, + + "$defs": { + "resource": { + "type": "object", + "properties": { + "resource": { + "type": "string", + "maxLength": 128, + "description": "The name of the resource." + }, + "id": { + "type": "string", + "maxLength": 128, + "description": "The identifier of this item." + }, + "dependsOn": { + "type": [ "array", "null" ], + "items": { + "type": "string" + }, + "uniqueItems": true, + "description": "The list of resource ids identifying dependencies." + }, + "directives": { + "type": "object", + "properties": { + "module": { + "type": "string", + "maxLength": 128, + "description": "The name of the module." + }, + "description": { + "type": "string", + "maxLength": 512, + "description": "The description of the desired state." + } + }, + "additionalProperties": true + }, + "settings": { "type": "object" } + }, + "required": ["resource"] + } + } } \ No newline at end of file diff --git a/schemas/JSON/manifests/preview/manifest.0.1.0.json b/schemas/JSON/manifests/preview/manifest.0.1.0.json index 0efb168b56..d25930842b 100644 --- a/schemas/JSON/manifests/preview/manifest.0.1.0.json +++ b/schemas/JSON/manifests/preview/manifest.0.1.0.json @@ -1,313 +1,313 @@ -{ - "$id": "https://aka.ms/winget-manifest.0.1.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A single-file manifest representing a package in winget community repo. v0.1.0 Preview", - "definitions": { - "InstallerType": { - "type": [ "string", "null" ], - "pattern": "^(([Ee][Xx][Ee])|([Mm][Ss][Ii])|([Mm][Ss][Ii][Xx])|([Ii][Nn][Nn][Oo])|([Ww][Ii][Xx])|([Nn][Uu][Ll][Ll][Ss][Oo][Ff][Tt])|([Aa][Pp][Pp][Xx])|([Zz][Ii][Pp])|([Bb][Uu][Rr][Nn]))$", - "description": "InstallerType is required under Installer node if it's not defined in root" - }, - "UpdateBehavior": { - "type": [ "string", "null" ], - "pattern": "^(([Ii][Nn][Ss][Tt][Aa][Ll][Ll])|([Uu][Nn][Ii][Nn][Ss][Tt][Aa][Ll][Ll][Pp][Rr][Ee][Vv][Ii][Oo][Uu][Ss]))$", - "description": "UpdateBehavior is used to specify desired action during package upgrade" - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "Description of the package" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048 - }, - "Homepage": { - "$ref": "#/definitions/Url", - "description": "Homepage is a Url where the user can find more information about the package" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "LicenseUrl provides a link to the license for the user to read" - }, - "InstallerSwitches": { - "type": [ "object", "null" ], - "properties": { - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - }, - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "Language": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Some installers include all localized resources. By specifying a Language switch, winget will pass the value of Language to the installer. This is not yet supported in Preview releases" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Update": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Update is the value that should be passed to the installer when user chooses an upgrade" - } - } - }, - "Installer": { - "type": "object", - "properties": { - "Arch": { - "type": "string", - "pattern": "^(([Aa][Rr][Mm])|([Xx]86)|([Xx]64)|([Aa][Rr][Mm]64)|([Nn][Ee][Uu][Tt][Rr][Aa][Ll]))$", - "description": "Arch is required. The installer architecture" - }, - "Url": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Url is required. The path to the installer." - }, - "Sha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "Language": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 20, - "description": "Language is the specific language of the installer. Language must follow IETF language tag guidelines" - }, - "Scope": { - "type": [ "string", "null" ], - "pattern": "^(([Uu][Ss][Ee][Rr])|([Mm][Aa][Cc][Hh][Ii][Nn][Ee]))$", - "description": "Scope indicates if the installer is per user or per machine. Scope is not yet supported in Preview releases" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "UpdateBehavior": { - "$ref": "#/definitions/UpdateBehavior" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Switches": { - "$ref": "#/definitions/InstallerSwitches" - } - }, - "required": [ - "Arch", - "Url", - "Sha256" - ] - }, - "ManifestLocalization": { - "type": "object", - "properties": { - "Language": { - "type": "string", - "minLength": 2, - "maxLength": 20, - "description": "Language is the specific language of the localization. Language must follow IETF language tag guidelines" - }, - "Description": { - "$ref": "#/definitions/Description" - }, - "Homepage": { - "$ref": "#/definitions/Homepage" - }, - "LicenseUrl": { - "$ref": "#/definitions/LicenseUrl" - } - }, - "required": [ - "Language" - ] - } - }, - "type": "object", - "properties": { - "ManifestVersion": { - "type": "string", - "default": "0.1.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - }, - "Id": { - "type": "string", - "pattern": "^[^\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+\\.[^\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 255, - "description": "Id is a required field. It MUST include the publisher name and package name separated by a period. For example: Publisher.Package" - }, - "Name": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "description": "Name is a required field. The name of the package" - }, - "Version": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "minLength": 1, - "description": "Version is a required field. The version of the package" - }, - "Publisher": { - "type": "string", - "minLength": 1, - "maxLength": 128, - "description": "Publisher is a required field. The legal publisher name" - }, - "AppMoniker": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "AppMoniker is the common name someone may use to search for the package" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Channel a string representing the flight ring. For example: stable, beta, canary" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The person or company responsible for authoring the package" - }, - "License": { - "type": "string", - "minLength": 1, - "maxLength": 1000, - "description": "License is a required field. License provides the type of license the package is provided under" - }, - "MinOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "MinOSVersion uses the Windows version to limit installations on unsupported platforms" - }, - "Tags": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 1000, - "description": "Tags is a comma separated list. They represent strings that user may use to search for the package" - }, - "Commands": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 1000, - "description": "Commands is a comma separated list. They are the common executable or alias that user might type trying to run the package" - }, - "Protocols": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 1000, - "description": "Protocols is a comma separated list. Protocols provides the list of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 1000, - "description": "FileExtensions is a comma separated list. FileExtensions provides the list of extensions the package can support" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "UpdateBehavior": { - "$ref": "#/definitions/UpdateBehavior" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Description": { - "$ref": "#/definitions/Description" - }, - "Homepage": { - "$ref": "#/definitions/Homepage" - }, - "LicenseUrl": { - "$ref": "#/definitions/LicenseUrl" - }, - "Switches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "uniqueItems": true - }, - "Localization": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/ManifestLocalization" - } - } - }, - "required": [ - "Id", - "Name", - "Version", - "Publisher", - "License", - "Installers" - ] +{ + "$id": "https://aka.ms/winget-manifest.0.1.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A single-file manifest representing a package in winget community repo. v0.1.0 Preview", + "definitions": { + "InstallerType": { + "type": [ "string", "null" ], + "pattern": "^(([Ee][Xx][Ee])|([Mm][Ss][Ii])|([Mm][Ss][Ii][Xx])|([Ii][Nn][Nn][Oo])|([Ww][Ii][Xx])|([Nn][Uu][Ll][Ll][Ss][Oo][Ff][Tt])|([Aa][Pp][Pp][Xx])|([Zz][Ii][Pp])|([Bb][Uu][Rr][Nn]))$", + "description": "InstallerType is required under Installer node if it's not defined in root" + }, + "UpdateBehavior": { + "type": [ "string", "null" ], + "pattern": "^(([Ii][Nn][Ss][Tt][Aa][Ll][Ll])|([Uu][Nn][Ii][Nn][Ss][Tt][Aa][Ll][Ll][Pp][Rr][Ee][Vv][Ii][Oo][Uu][Ss]))$", + "description": "UpdateBehavior is used to specify desired action during package upgrade" + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "Description of the package" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048 + }, + "Homepage": { + "$ref": "#/definitions/Url", + "description": "Homepage is a Url where the user can find more information about the package" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "LicenseUrl provides a link to the license for the user to read" + }, + "InstallerSwitches": { + "type": [ "object", "null" ], + "properties": { + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "Language": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Some installers include all localized resources. By specifying a Language switch, winget will pass the value of Language to the installer. This is not yet supported in Preview releases" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Update": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Update is the value that should be passed to the installer when user chooses an upgrade" + } + } + }, + "Installer": { + "type": "object", + "properties": { + "Arch": { + "type": "string", + "pattern": "^(([Aa][Rr][Mm])|([Xx]86)|([Xx]64)|([Aa][Rr][Mm]64)|([Nn][Ee][Uu][Tt][Rr][Aa][Ll]))$", + "description": "Arch is required. The installer architecture" + }, + "Url": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url is required. The path to the installer." + }, + "Sha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "Language": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 20, + "description": "Language is the specific language of the installer. Language must follow IETF language tag guidelines" + }, + "Scope": { + "type": [ "string", "null" ], + "pattern": "^(([Uu][Ss][Ee][Rr])|([Mm][Aa][Cc][Hh][Ii][Nn][Ee]))$", + "description": "Scope indicates if the installer is per user or per machine. Scope is not yet supported in Preview releases" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "UpdateBehavior": { + "$ref": "#/definitions/UpdateBehavior" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Switches": { + "$ref": "#/definitions/InstallerSwitches" + } + }, + "required": [ + "Arch", + "Url", + "Sha256" + ] + }, + "ManifestLocalization": { + "type": "object", + "properties": { + "Language": { + "type": "string", + "minLength": 2, + "maxLength": 20, + "description": "Language is the specific language of the localization. Language must follow IETF language tag guidelines" + }, + "Description": { + "$ref": "#/definitions/Description" + }, + "Homepage": { + "$ref": "#/definitions/Homepage" + }, + "LicenseUrl": { + "$ref": "#/definitions/LicenseUrl" + } + }, + "required": [ + "Language" + ] + } + }, + "type": "object", + "properties": { + "ManifestVersion": { + "type": "string", + "default": "0.1.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + }, + "Id": { + "type": "string", + "pattern": "^[^\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+\\.[^\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 255, + "description": "Id is a required field. It MUST include the publisher name and package name separated by a period. For example: Publisher.Package" + }, + "Name": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Name is a required field. The name of the package" + }, + "Version": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "minLength": 1, + "description": "Version is a required field. The version of the package" + }, + "Publisher": { + "type": "string", + "minLength": 1, + "maxLength": 128, + "description": "Publisher is a required field. The legal publisher name" + }, + "AppMoniker": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "AppMoniker is the common name someone may use to search for the package" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Channel a string representing the flight ring. For example: stable, beta, canary" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The person or company responsible for authoring the package" + }, + "License": { + "type": "string", + "minLength": 1, + "maxLength": 1000, + "description": "License is a required field. License provides the type of license the package is provided under" + }, + "MinOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "MinOSVersion uses the Windows version to limit installations on unsupported platforms" + }, + "Tags": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 1000, + "description": "Tags is a comma separated list. They represent strings that user may use to search for the package" + }, + "Commands": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 1000, + "description": "Commands is a comma separated list. They are the common executable or alias that user might type trying to run the package" + }, + "Protocols": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 1000, + "description": "Protocols is a comma separated list. Protocols provides the list of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 1000, + "description": "FileExtensions is a comma separated list. FileExtensions provides the list of extensions the package can support" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "UpdateBehavior": { + "$ref": "#/definitions/UpdateBehavior" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Description": { + "$ref": "#/definitions/Description" + }, + "Homepage": { + "$ref": "#/definitions/Homepage" + }, + "LicenseUrl": { + "$ref": "#/definitions/LicenseUrl" + }, + "Switches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "uniqueItems": true + }, + "Localization": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/ManifestLocalization" + } + } + }, + "required": [ + "Id", + "Name", + "Version", + "Publisher", + "License", + "Installers" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.0.0/manifest.defaultLocale.1.0.0.json b/schemas/JSON/manifests/v1.0.0/manifest.defaultLocale.1.0.0.json index 4667d64508..8d78e8a3db 100644 --- a/schemas/JSON/manifests/v1.0.0/manifest.defaultLocale.1.0.0.json +++ b/schemas/JSON/manifests/v1.0.0/manifest.defaultLocale.1.0.0.json @@ -1,143 +1,143 @@ -{ - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.0.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.0.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "ManifestType": { - "type": "string", - "default": "defaultLocale", - "const": "defaultLocale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.0.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.0.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.0.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json b/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json index 17d2193bf1..859b418edf 100644 --- a/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json +++ b/schemas/JSON/manifests/v1.0.0/manifest.installer.1.0.0.json @@ -1,459 +1,459 @@ -{ - "$id": "https://aka.ms/winget-manifest.installer.1.0.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.0.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The installer meta-data locale" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "pwa" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[a-z][-a-z0-9\\.\\+]*$", - "maxLength": 2048 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 256, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 128 - }, - "ManifestType": { - "type": "string", - "default": "installer", - "const": "installer", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.0.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "Installers", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.installer.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.0.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "pwa" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[a-z][-a-z0-9\\.\\+]*$", + "maxLength": 2048 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 256, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 128 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.0.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.0.0/manifest.locale.1.0.0.json b/schemas/JSON/manifests/v1.0.0/manifest.locale.1.0.0.json index bd4cff4df4..db164bff00 100644 --- a/schemas/JSON/manifests/v1.0.0/manifest.locale.1.0.0.json +++ b/schemas/JSON/manifests/v1.0.0/manifest.locale.1.0.0.json @@ -1,134 +1,134 @@ -{ - "$id": "https://aka.ms/winget-manifest.locale.1.0.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.0.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package tag" - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "ManifestType": { - "type": "string", - "default": "locale", - "const": "locale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.0.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.locale.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.0.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.0.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json b/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json index 3678103127..984311dd44 100644 --- a/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json +++ b/schemas/JSON/manifests/v1.0.0/manifest.singleton.1.0.0.json @@ -1,557 +1,557 @@ -{ - "$id": "https://aka.ms/winget-manifest.singleton.1.0.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.0.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "pwa" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[a-z][-a-z0-9\\.\\+]*$", - "maxLength": 2048 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 256, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "PackageLocale": { - "$ref": "#/definitions/Locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1 - }, - "ManifestType": { - "type": "string", - "default": "singleton", - "const": "singleton", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.0.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "Installers", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.0.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "pwa" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[a-z][-a-z0-9\\.\\+]*$", + "maxLength": 2048 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 256, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.0.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.0.0/manifest.version.1.0.0.json b/schemas/JSON/manifests/v1.0.0/manifest.version.1.0.0.json index 406752d88b..30dee69b2e 100644 --- a/schemas/JSON/manifests/v1.0.0/manifest.version.1.0.0.json +++ b/schemas/JSON/manifests/v1.0.0/manifest.version.1.0.0.json @@ -1,46 +1,46 @@ -{ - "$id": "https://aka.ms/winget-manifest.version.1.0.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.0.0", - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "DefaultLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The default package meta-data locale" - }, - "ManifestType": { - "type": "string", - "default": "version", - "const": "version", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.0.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "DefaultLocale", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.version.1.0.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.0.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.0.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.1.0/manifest.defaultLocale.1.1.0.json b/schemas/JSON/manifests/v1.1.0/manifest.defaultLocale.1.1.0.json index 5fb483ac7a..7721d046d1 100644 --- a/schemas/JSON/manifests/v1.1.0/manifest.defaultLocale.1.1.0.json +++ b/schemas/JSON/manifests/v1.1.0/manifest.defaultLocale.1.1.0.json @@ -1,181 +1,181 @@ -{ - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.1.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.1.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "ManifestType": { - "type": "string", - "default": "defaultLocale", - "const": "defaultLocale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.1.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.1.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.1.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.1.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.1.0/manifest.installer.1.1.0.json b/schemas/JSON/manifests/v1.1.0/manifest.installer.1.1.0.json index b750747056..6888d80de4 100644 --- a/schemas/JSON/manifests/v1.1.0/manifest.installer.1.1.0.json +++ b/schemas/JSON/manifests/v1.1.0/manifest.installer.1.1.0.json @@ -1,671 +1,671 @@ -{ - "$id": "https://aka.ms/winget-manifest.installer.1.1.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.1.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The installer meta-data locale" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "pwa" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy" - ] - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[a-z][-a-z0-9\\.\\+]*$", - "maxLength": 2048 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 256, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "installer", - "const": "installer", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.1.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.installer.1.1.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.1.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "pwa" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy" + ] + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[a-z][-a-z0-9\\.\\+]*$", + "maxLength": 2048 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 256, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.1.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.1.0/manifest.locale.1.1.0.json b/schemas/JSON/manifests/v1.1.0/manifest.locale.1.1.0.json index 81b298d594..50fce07c44 100644 --- a/schemas/JSON/manifests/v1.1.0/manifest.locale.1.1.0.json +++ b/schemas/JSON/manifests/v1.1.0/manifest.locale.1.1.0.json @@ -1,172 +1,172 @@ -{ - "$id": "https://aka.ms/winget-manifest.locale.1.1.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.1.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "ManifestType": { - "type": "string", - "default": "locale", - "const": "locale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.1.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.locale.1.1.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.1.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.1.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.1.0/manifest.singleton.1.1.0.json b/schemas/JSON/manifests/v1.1.0/manifest.singleton.1.1.0.json index 03ad3db98a..5ea059b1bc 100644 --- a/schemas/JSON/manifests/v1.1.0/manifest.singleton.1.1.0.json +++ b/schemas/JSON/manifests/v1.1.0/manifest.singleton.1.1.0.json @@ -1,807 +1,807 @@ -{ - "$id": "https://aka.ms/winget-manifest.singleton.1.1.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.1.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "pwa" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy" - ] - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[a-z][-a-z0-9\\.\\+]*$", - "maxLength": 2048 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 256, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "PackageLocale": { - "$ref": "#/definitions/Locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1 - }, - "ManifestType": { - "type": "string", - "default": "singleton", - "const": "singleton", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.1.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.1.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.1.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "pwa" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy" + ] + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[a-z][-a-z0-9\\.\\+]*$", + "maxLength": 2048 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 256, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.1.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.1.0/manifest.version.1.1.0.json b/schemas/JSON/manifests/v1.1.0/manifest.version.1.1.0.json index 92ec00289f..e42688af52 100644 --- a/schemas/JSON/manifests/v1.1.0/manifest.version.1.1.0.json +++ b/schemas/JSON/manifests/v1.1.0/manifest.version.1.1.0.json @@ -1,46 +1,46 @@ -{ - "$id": "https://aka.ms/winget-manifest.version.1.1.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.1.0", - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "DefaultLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The default package meta-data locale" - }, - "ManifestType": { - "type": "string", - "default": "version", - "const": "version", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.1.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "DefaultLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.version.1.1.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.1.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.1.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.2.0/manifest.defaultLocale.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.defaultLocale.1.2.0.json index c230b9883b..31bbe812cc 100644 --- a/schemas/JSON/manifests/v1.2.0/manifest.defaultLocale.1.2.0.json +++ b/schemas/JSON/manifests/v1.2.0/manifest.defaultLocale.1.2.0.json @@ -1,213 +1,213 @@ -{ - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.2.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.2.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "ManifestType": { - "type": "string", - "default": "defaultLocale", - "const": "defaultLocale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.2.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.2.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.2.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.2.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json index d9758d46ce..77ce2813d9 100644 --- a/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json +++ b/schemas/JSON/manifests/v1.2.0/manifest.installer.1.2.0.json @@ -1,714 +1,714 @@ -{ - "$id": "https://aka.ms/winget-manifest.installer.1.2.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.2.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The installer meta-data locale" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Url type" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "installer", - "const": "installer", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.2.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.installer.1.2.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.2.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.2.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.2.0/manifest.locale.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.locale.1.2.0.json index 7e644746f3..ef00c2fe70 100644 --- a/schemas/JSON/manifests/v1.2.0/manifest.locale.1.2.0.json +++ b/schemas/JSON/manifests/v1.2.0/manifest.locale.1.2.0.json @@ -1,204 +1,204 @@ -{ - "$id": "https://aka.ms/winget-manifest.locale.1.2.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.2.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "ManifestType": { - "type": "string", - "default": "locale", - "const": "locale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.2.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.locale.1.2.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.2.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.2.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json index b8cb13de2e..1d5d8d0b1e 100644 --- a/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json +++ b/schemas/JSON/manifests/v1.2.0/manifest.singleton.1.2.0.json @@ -1,876 +1,876 @@ -{ - "$id": "https://aka.ms/winget-manifest.singleton.1.2.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.2.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "PackageLocale": { - "$ref": "#/definitions/Locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1 - }, - "ManifestType": { - "type": "string", - "default": "singleton", - "const": "singleton", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.2.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.2.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.2.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.2.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.2.0/manifest.version.1.2.0.json b/schemas/JSON/manifests/v1.2.0/manifest.version.1.2.0.json index 24540a7f9d..e3216ab9bc 100644 --- a/schemas/JSON/manifests/v1.2.0/manifest.version.1.2.0.json +++ b/schemas/JSON/manifests/v1.2.0/manifest.version.1.2.0.json @@ -1,46 +1,46 @@ -{ - "$id": "https://aka.ms/winget-manifest.version.1.2.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.2.0", - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "DefaultLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The default package meta-data locale" - }, - "ManifestType": { - "type": "string", - "default": "version", - "const": "version", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.2.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "DefaultLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.version.1.2.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.2.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,3}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.2.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.4.0/manifest.defaultLocale.1.4.0.json b/schemas/JSON/manifests/v1.4.0/manifest.defaultLocale.1.4.0.json index 6822d3e702..0052adc5b1 100644 --- a/schemas/JSON/manifests/v1.4.0/manifest.defaultLocale.1.4.0.json +++ b/schemas/JSON/manifests/v1.4.0/manifest.defaultLocale.1.4.0.json @@ -1,213 +1,213 @@ -{ - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.4.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.4.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "ManifestType": { - "type": "string", - "default": "defaultLocale", - "const": "defaultLocale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.4.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.4.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.4.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.4.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.4.0/manifest.installer.1.4.0.json b/schemas/JSON/manifests/v1.4.0/manifest.installer.1.4.0.json index 1ca938f73b..8cd13e6944 100644 --- a/schemas/JSON/manifests/v1.4.0/manifest.installer.1.4.0.json +++ b/schemas/JSON/manifests/v1.4.0/manifest.installer.1.4.0.json @@ -1,835 +1,835 @@ -{ - "$id": "https://aka.ms/winget-manifest.installer.1.4.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.4.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The installer meta-data locale" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Url type" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "installer", - "const": "installer", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.4.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.installer.1.4.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.4.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.4.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.4.0/manifest.locale.1.4.0.json b/schemas/JSON/manifests/v1.4.0/manifest.locale.1.4.0.json index 46be2e3fdf..b053ddc8db 100644 --- a/schemas/JSON/manifests/v1.4.0/manifest.locale.1.4.0.json +++ b/schemas/JSON/manifests/v1.4.0/manifest.locale.1.4.0.json @@ -1,204 +1,204 @@ -{ - "$id": "https://aka.ms/winget-manifest.locale.1.4.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.4.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "ManifestType": { - "type": "string", - "default": "locale", - "const": "locale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.4.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.locale.1.4.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.4.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.4.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.4.0/manifest.singleton.1.4.0.json b/schemas/JSON/manifests/v1.4.0/manifest.singleton.1.4.0.json index dcb636fd94..247e16842a 100644 --- a/schemas/JSON/manifests/v1.4.0/manifest.singleton.1.4.0.json +++ b/schemas/JSON/manifests/v1.4.0/manifest.singleton.1.4.0.json @@ -1,997 +1,997 @@ -{ - "$id": "https://aka.ms/winget-manifest.singleton.1.4.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.4.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "PackageLocale": { - "$ref": "#/definitions/Locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1 - }, - "ManifestType": { - "type": "string", - "default": "singleton", - "const": "singleton", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.4.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.4.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.4.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.4.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.4.0/manifest.version.1.4.0.json b/schemas/JSON/manifests/v1.4.0/manifest.version.1.4.0.json index 2ca282ebe3..856006d971 100644 --- a/schemas/JSON/manifests/v1.4.0/manifest.version.1.4.0.json +++ b/schemas/JSON/manifests/v1.4.0/manifest.version.1.4.0.json @@ -1,46 +1,46 @@ -{ - "$id": "https://aka.ms/winget-manifest.version.1.4.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.4.0", - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "DefaultLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The default package meta-data locale" - }, - "ManifestType": { - "type": "string", - "default": "version", - "const": "version", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.4.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "DefaultLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.version.1.4.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.4.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.4.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.5.0/manifest.defaultLocale.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.defaultLocale.1.5.0.json index d73503efc1..1b04390029 100644 --- a/schemas/JSON/manifests/v1.5.0/manifest.defaultLocale.1.5.0.json +++ b/schemas/JSON/manifests/v1.5.0/manifest.defaultLocale.1.5.0.json @@ -1,280 +1,280 @@ -{ - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.5.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.5.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "defaultLocale", - "const": "defaultLocale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.5.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.5.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.5.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.5.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.5.0/manifest.installer.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.installer.1.5.0.json index 833577565a..6357a000c0 100644 --- a/schemas/JSON/manifests/v1.5.0/manifest.installer.1.5.0.json +++ b/schemas/JSON/manifests/v1.5.0/manifest.installer.1.5.0.json @@ -1,835 +1,835 @@ -{ - "$id": "https://aka.ms/winget-manifest.installer.1.5.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.5.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The installer meta-data locale" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Url type" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "installer", - "const": "installer", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.5.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.installer.1.5.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.5.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.5.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.5.0/manifest.locale.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.locale.1.5.0.json index 4cdfaa86ad..d9d759af71 100644 --- a/schemas/JSON/manifests/v1.5.0/manifest.locale.1.5.0.json +++ b/schemas/JSON/manifests/v1.5.0/manifest.locale.1.5.0.json @@ -1,271 +1,271 @@ -{ - "$id": "https://aka.ms/winget-manifest.locale.1.5.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.5.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "locale", - "const": "locale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.5.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.locale.1.5.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.5.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.5.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.5.0/manifest.singleton.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.singleton.1.5.0.json index 0fcf710f03..363291e23b 100644 --- a/schemas/JSON/manifests/v1.5.0/manifest.singleton.1.5.0.json +++ b/schemas/JSON/manifests/v1.5.0/manifest.singleton.1.5.0.json @@ -1,1064 +1,1064 @@ -{ - "$id": "https://aka.ms/winget-manifest.singleton.1.5.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.5.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "PackageLocale": { - "$ref": "#/definitions/Locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1 - }, - "ManifestType": { - "type": "string", - "default": "singleton", - "const": "singleton", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.5.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.5.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.5.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.5.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.5.0/manifest.version.1.5.0.json b/schemas/JSON/manifests/v1.5.0/manifest.version.1.5.0.json index 0f35bbf434..b524ffecd8 100644 --- a/schemas/JSON/manifests/v1.5.0/manifest.version.1.5.0.json +++ b/schemas/JSON/manifests/v1.5.0/manifest.version.1.5.0.json @@ -1,46 +1,46 @@ -{ - "$id": "https://aka.ms/winget-manifest.version.1.5.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.5.0", - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "DefaultLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The default package meta-data locale" - }, - "ManifestType": { - "type": "string", - "default": "version", - "const": "version", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.5.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "DefaultLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.version.1.5.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.5.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.5.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.6.0/manifest.defaultLocale.1.6.0.json b/schemas/JSON/manifests/v1.6.0/manifest.defaultLocale.1.6.0.json index b0a4d49563..e374226e9f 100644 --- a/schemas/JSON/manifests/v1.6.0/manifest.defaultLocale.1.6.0.json +++ b/schemas/JSON/manifests/v1.6.0/manifest.defaultLocale.1.6.0.json @@ -1,280 +1,280 @@ -{ - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.6.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.6.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "defaultLocale", - "const": "defaultLocale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.6.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.6.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.6.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.6.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json b/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json index 5d268259b9..2d963c454f 100644 --- a/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json +++ b/schemas/JSON/manifests/v1.6.0/manifest.installer.1.6.0.json @@ -1,846 +1,846 @@ -{ - "$id": "https://aka.ms/winget-manifest.installer.1.6.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.6.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The installer meta-data locale" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Url type" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious", - "deny" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "DownloadCommandProhibited": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "installer", - "const": "installer", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.6.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.installer.1.6.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.6.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.6.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.6.0/manifest.locale.1.6.0.json b/schemas/JSON/manifests/v1.6.0/manifest.locale.1.6.0.json index b281b91e9c..3052fa6424 100644 --- a/schemas/JSON/manifests/v1.6.0/manifest.locale.1.6.0.json +++ b/schemas/JSON/manifests/v1.6.0/manifest.locale.1.6.0.json @@ -1,271 +1,271 @@ -{ - "$id": "https://aka.ms/winget-manifest.locale.1.6.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.6.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "locale", - "const": "locale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.6.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.locale.1.6.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.6.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.6.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json b/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json index 83ae462983..7e12710112 100644 --- a/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json +++ b/schemas/JSON/manifests/v1.6.0/manifest.singleton.1.6.0.json @@ -1,1075 +1,1075 @@ -{ - "$id": "https://aka.ms/winget-manifest.singleton.1.6.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.6.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious", - "deny" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "DownloadCommandProhibited": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "PackageLocale": { - "$ref": "#/definitions/Locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1 - }, - "ManifestType": { - "type": "string", - "default": "singleton", - "const": "singleton", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.6.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.6.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.6.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.6.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.6.0/manifest.version.1.6.0.json b/schemas/JSON/manifests/v1.6.0/manifest.version.1.6.0.json index fb744b7eb4..631bfc5bc7 100644 --- a/schemas/JSON/manifests/v1.6.0/manifest.version.1.6.0.json +++ b/schemas/JSON/manifests/v1.6.0/manifest.version.1.6.0.json @@ -1,46 +1,46 @@ -{ - "$id": "https://aka.ms/winget-manifest.version.1.6.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.6.0", - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "DefaultLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The default package meta-data locale" - }, - "ManifestType": { - "type": "string", - "default": "version", - "const": "version", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.6.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "DefaultLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.version.1.6.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.6.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.6.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.7.0/manifest.defaultLocale.1.7.0.json b/schemas/JSON/manifests/v1.7.0/manifest.defaultLocale.1.7.0.json index 424024c2fd..483a4c0e8a 100644 --- a/schemas/JSON/manifests/v1.7.0/manifest.defaultLocale.1.7.0.json +++ b/schemas/JSON/manifests/v1.7.0/manifest.defaultLocale.1.7.0.json @@ -1,280 +1,280 @@ -{ - "$id": "https://aka.ms/winget-manifest.defaultlocale.1.7.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.7.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "defaultLocale", - "const": "defaultLocale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.7.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.defaultlocale.1.7.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing a default app metadata in the OWC. v1.7.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "defaultLocale", + "const": "defaultLocale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.7.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.7.0/manifest.installer.1.7.0.json b/schemas/JSON/manifests/v1.7.0/manifest.installer.1.7.0.json index 4940acb829..71fc8063c5 100644 --- a/schemas/JSON/manifests/v1.7.0/manifest.installer.1.7.0.json +++ b/schemas/JSON/manifests/v1.7.0/manifest.installer.1.7.0.json @@ -1,867 +1,867 @@ -{ - "$id": "https://aka.ms/winget-manifest.installer.1.7.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.7.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The installer meta-data locale" - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Url type" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - }, - "Repair" : { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair." - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious", - "deny" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "DownloadCommandProhibited": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." - }, - "RepairBehavior": { - "type": [ "string", "null" ], - "enum": [ - "modify", - "uninstaller", - "installer" - ], - "description": "The repair method" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - }, - "RepairBehavior": { - "$ref": "#/definitions/RepairBehavior" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - }, - "RepairBehavior": { - "$ref": "#/definitions/RepairBehavior" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "installer", - "const": "installer", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.7.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.installer.1.7.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app installers in the OWC. v1.7.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The installer meta-data locale" + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Url type" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Repair" : { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair." + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "RepairBehavior": { + "type": [ "string", "null" ], + "enum": [ + "modify", + "uninstaller", + "installer" + ], + "description": "The repair method" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "installer", + "const": "installer", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.7.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.7.0/manifest.locale.1.7.0.json b/schemas/JSON/manifests/v1.7.0/manifest.locale.1.7.0.json index 2c5c6903ce..db5b783e64 100644 --- a/schemas/JSON/manifests/v1.7.0/manifest.locale.1.7.0.json +++ b/schemas/JSON/manifests/v1.7.0/manifest.locale.1.7.0.json @@ -1,271 +1,271 @@ -{ - "$id": "https://aka.ms/winget-manifest.locale.1.7.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.7.0", - "definitions": { - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "PackageLocale": { - "type": "string", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "ManifestType": { - "type": "string", - "default": "locale", - "const": "locale", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.7.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.locale.1.7.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multiple-file manifest representing app metadata in other locale in the OWC. v1.7.0", + "definitions": { + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "PackageLocale": { + "type": "string", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "ManifestType": { + "type": "string", + "default": "locale", + "const": "locale", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.7.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.7.0/manifest.singleton.1.7.0.json b/schemas/JSON/manifests/v1.7.0/manifest.singleton.1.7.0.json index 15781b3950..6da201e165 100644 --- a/schemas/JSON/manifests/v1.7.0/manifest.singleton.1.7.0.json +++ b/schemas/JSON/manifests/v1.7.0/manifest.singleton.1.7.0.json @@ -1,1096 +1,1096 @@ -{ - "$id": "https://aka.ms/winget-manifest.singleton.1.7.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a single-file manifest representing an app in the OWC. v1.7.0", - "definitions": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "Locale": { - "type": [ "string", "null" ], - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The package meta-data locale" - }, - "Url": { - "type": [ "string", "null" ], - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "Optional Url type" - }, - "Tag": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "Package moniker or tag" - }, - "Agreement": { - "type": "object", - "properties": { - "AgreementLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" - }, - "Agreement": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The agreement text content." - }, - "AgreementUrl": { - "$ref": "#/definitions/Url", - "description": "The agreement URL." - } - } - }, - "Documentation": { - "type": "object", - "properties": { - "DocumentLabel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 100, - "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." - }, - "DocumentUrl": { - "$ref": "#/definitions/Url", - "description": "The documentation URL." - } - } - }, - "Icon": { - "type": "object", - "properties": { - "IconUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The url of the hosted icon file" - }, - "IconFileType": { - "type": "string", - "enum": [ - "png", - "jpeg", - "ico" - ], - "description": "The icon file type" - }, - "IconResolution": { - "type": [ "string", "null" ], - "enum": [ - "custom", - "16x16", - "20x20", - "24x24", - "30x30", - "32x32", - "36x36", - "40x40", - "48x48", - "60x60", - "64x64", - "72x72", - "80x80", - "96x96", - "256x256" - ], - "description": "Optional icon resolution" - }, - "IconTheme": { - "type": [ "string", "null" ], - "enum": [ - "default", - "light", - "dark", - "highContrast" - ], - "description": "Optional icon theme" - }, - "IconSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the icon file" - } - }, - "required": [ - "IconUrl", - "IconFileType" - ] - }, - "Channel": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 16, - "description": "The distribution channel" - }, - "Platform": { - "type": [ "array", "null" ], - "items": { - "title": "Platform", - "type": "string", - "enum": [ - "Windows.Desktop", - "Windows.Universal" - ] - }, - "maxItems": 2, - "uniqueItems": true, - "description": "The installer supported operating system" - }, - "MinimumOSVersion": { - "type": [ "string", "null" ], - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", - "description": "The installer minimum operating system version" - }, - "InstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "zip", - "inno", - "nullsoft", - "wix", - "burn", - "pwa", - "portable" - ], - "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" - }, - "NestedInstallerType": { - "type": [ "string", "null" ], - "enum": [ - "msix", - "msi", - "appx", - "exe", - "inno", - "nullsoft", - "wix", - "burn", - "portable" - ], - "description": "Enumeration of supported nested installer types contained inside an archive file" - }, - "NestedInstallerFiles": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "NestedInstallerFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 512, - "description": "The relative path to the nested installer file" - }, - "PortableCommandAlias": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 40, - "description": "The command alias to be used for calling the package. Only applies to the nested portable package" - } - }, - "required": [ "RelativeFilePath" ], - "description": "A nested installer file contained inside an archive" - }, - "maxItems": 1024, - "description": "List of nested installer files contained inside an archive" - }, - "Architecture": { - "type": "string", - "enum": [ - "x86", - "x64", - "arm", - "arm64", - "neutral" - ], - "description": "The installer target architecture" - }, - "Scope": { - "type": [ "string", "null" ], - "enum": [ - "user", - "machine" - ], - "description": "Scope indicates if the installer is per user or per machine" - }, - "InstallModes": { - "type": [ "array", "null" ], - "items": { - "title": "InstallModes", - "type": "string", - "enum": [ - "interactive", - "silent", - "silentWithProgress" - ] - }, - "maxItems": 3, - "uniqueItems": true, - "description": "List of supported installer modes" - }, - "InstallerSwitches": { - "type": "object", - "properties": { - "Silent": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" - }, - "SilentWithProgress": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" - }, - "Interactive": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" - }, - "InstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Log": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" - }, - "Upgrade": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" - }, - "Custom": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Custom switches will be passed directly to the installer by winget" - }, - "Repair": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 512, - "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair" - } - } - }, - "InstallerReturnCode": { - "type": "integer", - "format": "long", - "not": { - "enum": [ 0 ] - }, - "minimum": -2147483648, - "maximum": 4294967295, - "description": "An exit code that can be returned by the installer after execution" - }, - "InstallerSuccessCodes": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional non-zero installer success exit codes other than known default values by winget" - }, - "ExpectedReturnCodes": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "title": "ExpectedReturnCode", - "properties": { - "InstallerReturnCode": { - "$ref": "#/definitions/InstallerReturnCode" - }, - "ReturnResponse": { - "type": "string", - "enum": [ - "packageInUse", - "packageInUseByApplication", - "installInProgress", - "fileInUse", - "missingDependency", - "diskFull", - "insufficientMemory", - "invalidParameter", - "noNetwork", - "contactSupport", - "rebootRequiredToFinish", - "rebootRequiredForInstall", - "rebootInitiated", - "cancelledByUser", - "alreadyInstalled", - "downgrade", - "blockedByPolicy", - "systemNotSupported", - "custom" - ] - }, - "ReturnResponseUrl": { - "$ref": "#/definitions/Url", - "description": "The return response url to provide additional guidance for expected return codes" - } - }, - "required": [ "InstallerReturnCode", "ReturnResponse" ] - }, - "maxItems": 128, - "description": "Installer exit codes for common errors" - }, - "UpgradeBehavior": { - "type": [ "string", "null" ], - "enum": [ - "install", - "uninstallPrevious", - "deny" - ], - "description": "The upgrade method" - }, - "Commands": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of commands or aliases to run the package" - }, - "Protocols": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "maxLength": 2048 - }, - "maxItems": 64, - "uniqueItems": true, - "description": "List of protocols the package provides a handler for" - }, - "FileExtensions": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 64 - }, - "maxItems": 512, - "uniqueItems": true, - "description": "List of file extensions the package could support" - }, - "Dependencies": { - "type": [ "object", "null" ], - "properties": { - "WindowsFeatures": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows feature dependencies" - }, - "WindowsLibraries": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of Windows library dependencies" - }, - "PackageDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "MinimumVersion": { - "$ref": "#/definitions/PackageVersion" - } - }, - "required": [ "PackageIdentifier" ] - }, - "maxItems": 16, - "description": "List of package dependencies from current source" - }, - "ExternalDependencies": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 128 - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of external package dependencies" - } - } - }, - "PackageFamilyName": { - "type": [ "string", "null" ], - "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", - "maxLength": 255, - "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" - }, - "ProductCode": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 255, - "description": "ProductCode could be used for correlation of packages across sources" - }, - "Capabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer capabilities" - }, - "RestrictedCapabilities": { - "type": [ "array", "null" ], - "items": { - "type": "string", - "minLength": 1, - "maxLength": 40 - }, - "maxItems": 1000, - "uniqueItems": true, - "description": "List of appx or msix installer restricted capabilities" - }, - "Market": { - "type": "string", - "pattern": "^[A-Z]{2}$", - "description": "The installer target market" - }, - "MarketArray": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 256, - "items": { - "$ref": "#/definitions/Market" - }, - "description": "Array of markets" - }, - "Markets": { - "description": "The installer markets", - "type": [ "object", "null" ], - "oneOf": [ - { - "properties": { - "AllowedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "AllowedMarkets" ] - }, - { - "properties": { - "ExcludedMarkets": { - "$ref": "#/definitions/MarketArray" - } - }, - "required": [ "ExcludedMarkets" ] - } - ] - }, - "InstallerAbortsTerminal": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer will abort terminal. Default is false" - }, - "ReleaseDate": { - "type": [ "string", "null" ], - "format": "date", - "description": "The installer release date" - }, - "InstallLocationRequired": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer requires an install location provided" - }, - "RequireExplicitUpgrade": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer should be pinned by default from upgrade" - }, - "DisplayInstallWarnings": { - "type": [ "boolean", "null" ], - "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." - }, - "UnsupportedOSArchitectures": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedOSArchitecture", - "enum": [ - "x86", - "x64", - "arm", - "arm64" - ] - }, - "description": "List of OS architectures the installer does not support" - }, - "UnsupportedArguments": { - "type": [ "array", "null" ], - "uniqueItems": true, - "items": { - "type": "string", - "title": "UnsupportedArgument", - "enum": [ - "log", - "location" - ] - }, - "description": "List of winget arguments the installer does not support" - }, - "AppsAndFeaturesEntry": { - "type": "object", - "properties": { - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The DisplayName registry value" - }, - "Publisher": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "The Publisher registry value" - }, - "DisplayVersion": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 128, - "description": "The DisplayVersion registry value" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "UpgradeCode": { - "$ref": "#/definitions/ProductCode" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - } - }, - "description": "Various key values under installer's ARP entry" - }, - "AppsAndFeaturesEntries": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 128, - "items": { - "$ref": "#/definitions/AppsAndFeaturesEntry" - }, - "description": "List of ARP entries." - }, - "ElevationRequirement": { - "type": [ "string", "null" ], - "enum": [ - "elevationRequired", - "elevationProhibited", - "elevatesSelf" - ], - "description": "The installer's elevation requirement" - }, - "InstallationMetadata": { - "type": "object", - "title": "InstallationMetadata", - "properties": { - "DefaultInstallLocation": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Represents the default installed package location. Used for deeper installation detection." - }, - "Files": { - "type": [ "array", "null" ], - "uniqueItems": true, - "maxItems": 2048, - "items": { - "type": "object", - "title": "InstalledFile", - "properties": { - "RelativeFilePath": { - "type": "string", - "minLength": 1, - "maxLength": 2048, - "description": "The relative path to the installed file." - }, - "FileSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Optional Sha256 of the installed file." - }, - "FileType": { - "type": [ "string", "null" ], - "enum": [ - "launch", - "uninstall", - "other" - ], - "description": "The optional installed file type. If not specified, the file is treated as other." - }, - "InvocationParameter": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 2048, - "description": "Optional parameter for invocable files." - }, - "DisplayName": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 256, - "description": "Optional display name for invocable files." - } - }, - "required": [ "RelativeFilePath" ], - "description": "Represents an installed file." - }, - "description": "List of installed files." - } - }, - "description": "Details about the installation. Used for deeper installation detection." - }, - "DownloadCommandProhibited": { - "type": [ "boolean", "null" ], - "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." - }, - "RepairBehavior": { - "type": [ "string", "null" ], - "enum": [ - "modify", - "uninstaller", - "installer" - ], - "description": "The repair method" - }, - "Installer": { - "type": "object", - "properties": { - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "Architecture": { - "$ref": "#/definitions/Architecture" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallerUrl": { - "type": "string", - "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", - "maxLength": 2048, - "description": "The installer Url" - }, - "InstallerSha256": { - "type": "string", - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "Sha256 is required. Sha256 of the installer" - }, - "SignatureSha256": { - "type": [ "string", "null" ], - "pattern": "^[A-Fa-f0-9]{64}$", - "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - }, - "RepairBehavior": { - "$ref": "#/definitions/RepairBehavior" - } - }, - "required": [ - "Architecture", - "InstallerUrl", - "InstallerSha256" - ] - } - }, - "type": "object", - "properties": { - "PackageIdentifier": { - "$ref": "#/definitions/PackageIdentifier" - }, - "PackageVersion": { - "$ref": "#/definitions/PackageVersion" - }, - "PackageLocale": { - "$ref": "#/definitions/Locale" - }, - "Publisher": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The publisher name" - }, - "PublisherUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher home page" - }, - "PublisherSupportUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher support page" - }, - "PrivacyUrl": { - "$ref": "#/definitions/Url", - "description": "The publisher privacy page or the package privacy page" - }, - "Author": { - "type": [ "string", "null" ], - "minLength": 2, - "maxLength": 256, - "description": "The package author" - }, - "PackageName": { - "type": "string", - "minLength": 2, - "maxLength": 256, - "description": "The package name" - }, - "PackageUrl": { - "$ref": "#/definitions/Url", - "description": "The package home page" - }, - "License": { - "type": "string", - "minLength": 3, - "maxLength": 512, - "description": "The package license" - }, - "LicenseUrl": { - "$ref": "#/definitions/Url", - "description": "The license page" - }, - "Copyright": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 512, - "description": "The package copyright" - }, - "CopyrightUrl": { - "$ref": "#/definitions/Url", - "description": "The package copyright page" - }, - "ShortDescription": { - "type": "string", - "minLength": 3, - "maxLength": 256, - "description": "The short package description" - }, - "Description": { - "type": [ "string", "null" ], - "minLength": 3, - "maxLength": 10000, - "description": "The full package description" - }, - "Moniker": { - "$ref": "#/definitions/Tag", - "description": "The most common package term" - }, - "Tags": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Tag" - }, - "maxItems": 16, - "uniqueItems": true, - "description": "List of additional package search terms" - }, - "Agreements": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Agreement" - }, - "maxItems": 128 - }, - "ReleaseNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The package release notes" - }, - "ReleaseNotesUrl": { - "$ref": "#/definitions/Url", - "description": "The package release notes url" - }, - "PurchaseUrl": { - "$ref": "#/definitions/Url", - "description": "The purchase url for acquiring entitlement for the package." - }, - "InstallationNotes": { - "type": [ "string", "null" ], - "minLength": 1, - "maxLength": 10000, - "description": "The notes displayed to the user upon completion of a package installation." - }, - "Documentations": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Documentation" - }, - "maxItems": 256 - }, - "Icons": { - "type": [ "array", "null" ], - "items": { - "$ref": "#/definitions/Icon" - }, - "maxItems": 1024 - }, - "Channel": { - "$ref": "#/definitions/Channel" - }, - "InstallerLocale": { - "$ref": "#/definitions/Locale" - }, - "Platform": { - "$ref": "#/definitions/Platform" - }, - "MinimumOSVersion": { - "$ref": "#/definitions/MinimumOSVersion" - }, - "InstallerType": { - "$ref": "#/definitions/InstallerType" - }, - "NestedInstallerType": { - "$ref": "#/definitions/NestedInstallerType" - }, - "NestedInstallerFiles": { - "$ref": "#/definitions/NestedInstallerFiles" - }, - "Scope": { - "$ref": "#/definitions/Scope" - }, - "InstallModes": { - "$ref": "#/definitions/InstallModes" - }, - "InstallerSwitches": { - "$ref": "#/definitions/InstallerSwitches" - }, - "InstallerSuccessCodes": { - "$ref": "#/definitions/InstallerSuccessCodes" - }, - "ExpectedReturnCodes": { - "$ref": "#/definitions/ExpectedReturnCodes" - }, - "UpgradeBehavior": { - "$ref": "#/definitions/UpgradeBehavior" - }, - "Commands": { - "$ref": "#/definitions/Commands" - }, - "Protocols": { - "$ref": "#/definitions/Protocols" - }, - "FileExtensions": { - "$ref": "#/definitions/FileExtensions" - }, - "Dependencies": { - "$ref": "#/definitions/Dependencies" - }, - "PackageFamilyName": { - "$ref": "#/definitions/PackageFamilyName" - }, - "ProductCode": { - "$ref": "#/definitions/ProductCode" - }, - "Capabilities": { - "$ref": "#/definitions/Capabilities" - }, - "RestrictedCapabilities": { - "$ref": "#/definitions/RestrictedCapabilities" - }, - "Markets": { - "$ref": "#/definitions/Markets" - }, - "InstallerAbortsTerminal": { - "$ref": "#/definitions/InstallerAbortsTerminal" - }, - "ReleaseDate": { - "$ref": "#/definitions/ReleaseDate" - }, - "InstallLocationRequired": { - "$ref": "#/definitions/InstallLocationRequired" - }, - "RequireExplicitUpgrade": { - "$ref": "#/definitions/RequireExplicitUpgrade" - }, - "DisplayInstallWarnings": { - "$ref": "#/definitions/DisplayInstallWarnings" - }, - "UnsupportedOSArchitectures": { - "$ref": "#/definitions/UnsupportedOSArchitectures" - }, - "UnsupportedArguments": { - "$ref": "#/definitions/UnsupportedArguments" - }, - "AppsAndFeaturesEntries": { - "$ref": "#/definitions/AppsAndFeaturesEntries" - }, - "ElevationRequirement": { - "$ref": "#/definitions/ElevationRequirement" - }, - "InstallationMetadata": { - "$ref": "#/definitions/InstallationMetadata" - }, - "DownloadCommandProhibited": { - "$ref": "#/definitions/DownloadCommandProhibited" - }, - "RepairBehavior": { - "$ref": "#/definitions/RepairBehavior" - }, - "Installers": { - "type": "array", - "items": { - "$ref": "#/definitions/Installer" - }, - "minItems": 1, - "maxItems": 1 - }, - "ManifestType": { - "type": "string", - "default": "singleton", - "const": "singleton", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.7.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "PackageLocale", - "Publisher", - "PackageName", - "License", - "ShortDescription", - "Installers", - "ManifestType", - "ManifestVersion" - ] -} +{ + "$id": "https://aka.ms/winget-manifest.singleton.1.7.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a single-file manifest representing an app in the OWC. v1.7.0", + "definitions": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "Locale": { + "type": [ "string", "null" ], + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The package meta-data locale" + }, + "Url": { + "type": [ "string", "null" ], + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "Optional Url type" + }, + "Tag": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "Package moniker or tag" + }, + "Agreement": { + "type": "object", + "properties": { + "AgreementLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the Agreement. i.e. EULA, AgeRating, etc. This field should be localized. Either Agreement or AgreementUrl is required. When we show the agreements, we would Bold the AgreementLabel" + }, + "Agreement": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The agreement text content." + }, + "AgreementUrl": { + "$ref": "#/definitions/Url", + "description": "The agreement URL." + } + } + }, + "Documentation": { + "type": "object", + "properties": { + "DocumentLabel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 100, + "description": "The label of the documentation for providing software guides such as manuals and troubleshooting URLs." + }, + "DocumentUrl": { + "$ref": "#/definitions/Url", + "description": "The documentation URL." + } + } + }, + "Icon": { + "type": "object", + "properties": { + "IconUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The url of the hosted icon file" + }, + "IconFileType": { + "type": "string", + "enum": [ + "png", + "jpeg", + "ico" + ], + "description": "The icon file type" + }, + "IconResolution": { + "type": [ "string", "null" ], + "enum": [ + "custom", + "16x16", + "20x20", + "24x24", + "30x30", + "32x32", + "36x36", + "40x40", + "48x48", + "60x60", + "64x64", + "72x72", + "80x80", + "96x96", + "256x256" + ], + "description": "Optional icon resolution" + }, + "IconTheme": { + "type": [ "string", "null" ], + "enum": [ + "default", + "light", + "dark", + "highContrast" + ], + "description": "Optional icon theme" + }, + "IconSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the icon file" + } + }, + "required": [ + "IconUrl", + "IconFileType" + ] + }, + "Channel": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 16, + "description": "The distribution channel" + }, + "Platform": { + "type": [ "array", "null" ], + "items": { + "title": "Platform", + "type": "string", + "enum": [ + "Windows.Desktop", + "Windows.Universal" + ] + }, + "maxItems": 2, + "uniqueItems": true, + "description": "The installer supported operating system" + }, + "MinimumOSVersion": { + "type": [ "string", "null" ], + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){0,3}$", + "description": "The installer minimum operating system version" + }, + "InstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "zip", + "inno", + "nullsoft", + "wix", + "burn", + "pwa", + "portable" + ], + "description": "Enumeration of supported installer types. InstallerType is required in either root level or individual Installer level" + }, + "NestedInstallerType": { + "type": [ "string", "null" ], + "enum": [ + "msix", + "msi", + "appx", + "exe", + "inno", + "nullsoft", + "wix", + "burn", + "portable" + ], + "description": "Enumeration of supported nested installer types contained inside an archive file" + }, + "NestedInstallerFiles": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "NestedInstallerFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 512, + "description": "The relative path to the nested installer file" + }, + "PortableCommandAlias": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 40, + "description": "The command alias to be used for calling the package. Only applies to the nested portable package" + } + }, + "required": [ "RelativeFilePath" ], + "description": "A nested installer file contained inside an archive" + }, + "maxItems": 1024, + "description": "List of nested installer files contained inside an archive" + }, + "Architecture": { + "type": "string", + "enum": [ + "x86", + "x64", + "arm", + "arm64", + "neutral" + ], + "description": "The installer target architecture" + }, + "Scope": { + "type": [ "string", "null" ], + "enum": [ + "user", + "machine" + ], + "description": "Scope indicates if the installer is per user or per machine" + }, + "InstallModes": { + "type": [ "array", "null" ], + "items": { + "title": "InstallModes", + "type": "string", + "enum": [ + "interactive", + "silent", + "silentWithProgress" + ] + }, + "maxItems": 3, + "uniqueItems": true, + "description": "List of supported installer modes" + }, + "InstallerSwitches": { + "type": "object", + "properties": { + "Silent": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Silent is the value that should be passed to the installer when user chooses a silent or quiet install" + }, + "SilentWithProgress": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "SilentWithProgress is the value that should be passed to the installer when user chooses a non-interactive install" + }, + "Interactive": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Interactive is the value that should be passed to the installer when user chooses an interactive install" + }, + "InstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "InstallLocation is the value passed to the installer for custom install location. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Log": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Log is the value passed to the installer for custom log file path. token can be included in the switch value so that winget will replace the token with user provided path" + }, + "Upgrade": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "Upgrade is the value that should be passed to the installer when user chooses an upgrade" + }, + "Custom": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Custom switches will be passed directly to the installer by winget" + }, + "Repair": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 512, + "description": "The 'Repair' value must be passed to the installer, ModifyPath ARP command, or uninstaller ARP command when the user opts for a repair" + } + } + }, + "InstallerReturnCode": { + "type": "integer", + "format": "long", + "not": { + "enum": [ 0 ] + }, + "minimum": -2147483648, + "maximum": 4294967295, + "description": "An exit code that can be returned by the installer after execution" + }, + "InstallerSuccessCodes": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional non-zero installer success exit codes other than known default values by winget" + }, + "ExpectedReturnCodes": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "title": "ExpectedReturnCode", + "properties": { + "InstallerReturnCode": { + "$ref": "#/definitions/InstallerReturnCode" + }, + "ReturnResponse": { + "type": "string", + "enum": [ + "packageInUse", + "packageInUseByApplication", + "installInProgress", + "fileInUse", + "missingDependency", + "diskFull", + "insufficientMemory", + "invalidParameter", + "noNetwork", + "contactSupport", + "rebootRequiredToFinish", + "rebootRequiredForInstall", + "rebootInitiated", + "cancelledByUser", + "alreadyInstalled", + "downgrade", + "blockedByPolicy", + "systemNotSupported", + "custom" + ] + }, + "ReturnResponseUrl": { + "$ref": "#/definitions/Url", + "description": "The return response url to provide additional guidance for expected return codes" + } + }, + "required": [ "InstallerReturnCode", "ReturnResponse" ] + }, + "maxItems": 128, + "description": "Installer exit codes for common errors" + }, + "UpgradeBehavior": { + "type": [ "string", "null" ], + "enum": [ + "install", + "uninstallPrevious", + "deny" + ], + "description": "The upgrade method" + }, + "Commands": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of commands or aliases to run the package" + }, + "Protocols": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "maxLength": 2048 + }, + "maxItems": 64, + "uniqueItems": true, + "description": "List of protocols the package provides a handler for" + }, + "FileExtensions": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 64 + }, + "maxItems": 512, + "uniqueItems": true, + "description": "List of file extensions the package could support" + }, + "Dependencies": { + "type": [ "object", "null" ], + "properties": { + "WindowsFeatures": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows feature dependencies" + }, + "WindowsLibraries": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of Windows library dependencies" + }, + "PackageDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "MinimumVersion": { + "$ref": "#/definitions/PackageVersion" + } + }, + "required": [ "PackageIdentifier" ] + }, + "maxItems": 16, + "description": "List of package dependencies from current source" + }, + "ExternalDependencies": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 128 + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of external package dependencies" + } + } + }, + "PackageFamilyName": { + "type": [ "string", "null" ], + "pattern": "^[A-Za-z0-9][-\\.A-Za-z0-9]+_[A-Za-z0-9]{13}$", + "maxLength": 255, + "description": "PackageFamilyName for appx or msix installer. Could be used for correlation of packages across sources" + }, + "ProductCode": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 255, + "description": "ProductCode could be used for correlation of packages across sources" + }, + "Capabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer capabilities" + }, + "RestrictedCapabilities": { + "type": [ "array", "null" ], + "items": { + "type": "string", + "minLength": 1, + "maxLength": 40 + }, + "maxItems": 1000, + "uniqueItems": true, + "description": "List of appx or msix installer restricted capabilities" + }, + "Market": { + "type": "string", + "pattern": "^[A-Z]{2}$", + "description": "The installer target market" + }, + "MarketArray": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 256, + "items": { + "$ref": "#/definitions/Market" + }, + "description": "Array of markets" + }, + "Markets": { + "description": "The installer markets", + "type": [ "object", "null" ], + "oneOf": [ + { + "properties": { + "AllowedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "AllowedMarkets" ] + }, + { + "properties": { + "ExcludedMarkets": { + "$ref": "#/definitions/MarketArray" + } + }, + "required": [ "ExcludedMarkets" ] + } + ] + }, + "InstallerAbortsTerminal": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer will abort terminal. Default is false" + }, + "ReleaseDate": { + "type": [ "string", "null" ], + "format": "date", + "description": "The installer release date" + }, + "InstallLocationRequired": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer requires an install location provided" + }, + "RequireExplicitUpgrade": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer should be pinned by default from upgrade" + }, + "DisplayInstallWarnings": { + "type": [ "boolean", "null" ], + "description": "Indicates whether winget should display a warning message if the install or upgrade is known to interfere with running applications." + }, + "UnsupportedOSArchitectures": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedOSArchitecture", + "enum": [ + "x86", + "x64", + "arm", + "arm64" + ] + }, + "description": "List of OS architectures the installer does not support" + }, + "UnsupportedArguments": { + "type": [ "array", "null" ], + "uniqueItems": true, + "items": { + "type": "string", + "title": "UnsupportedArgument", + "enum": [ + "log", + "location" + ] + }, + "description": "List of winget arguments the installer does not support" + }, + "AppsAndFeaturesEntry": { + "type": "object", + "properties": { + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The DisplayName registry value" + }, + "Publisher": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "The Publisher registry value" + }, + "DisplayVersion": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 128, + "description": "The DisplayVersion registry value" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "UpgradeCode": { + "$ref": "#/definitions/ProductCode" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + } + }, + "description": "Various key values under installer's ARP entry" + }, + "AppsAndFeaturesEntries": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 128, + "items": { + "$ref": "#/definitions/AppsAndFeaturesEntry" + }, + "description": "List of ARP entries." + }, + "ElevationRequirement": { + "type": [ "string", "null" ], + "enum": [ + "elevationRequired", + "elevationProhibited", + "elevatesSelf" + ], + "description": "The installer's elevation requirement" + }, + "InstallationMetadata": { + "type": "object", + "title": "InstallationMetadata", + "properties": { + "DefaultInstallLocation": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Represents the default installed package location. Used for deeper installation detection." + }, + "Files": { + "type": [ "array", "null" ], + "uniqueItems": true, + "maxItems": 2048, + "items": { + "type": "object", + "title": "InstalledFile", + "properties": { + "RelativeFilePath": { + "type": "string", + "minLength": 1, + "maxLength": 2048, + "description": "The relative path to the installed file." + }, + "FileSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Optional Sha256 of the installed file." + }, + "FileType": { + "type": [ "string", "null" ], + "enum": [ + "launch", + "uninstall", + "other" + ], + "description": "The optional installed file type. If not specified, the file is treated as other." + }, + "InvocationParameter": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 2048, + "description": "Optional parameter for invocable files." + }, + "DisplayName": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 256, + "description": "Optional display name for invocable files." + } + }, + "required": [ "RelativeFilePath" ], + "description": "Represents an installed file." + }, + "description": "List of installed files." + } + }, + "description": "Details about the installation. Used for deeper installation detection." + }, + "DownloadCommandProhibited": { + "type": [ "boolean", "null" ], + "description": "Indicates whether the installer is prohibited from being downloaded for offline installation." + }, + "RepairBehavior": { + "type": [ "string", "null" ], + "enum": [ + "modify", + "uninstaller", + "installer" + ], + "description": "The repair method" + }, + "Installer": { + "type": "object", + "properties": { + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "Architecture": { + "$ref": "#/definitions/Architecture" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallerUrl": { + "type": "string", + "pattern": "^([Hh][Tt][Tt][Pp][Ss]?)://.+$", + "maxLength": 2048, + "description": "The installer Url" + }, + "InstallerSha256": { + "type": "string", + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "Sha256 is required. Sha256 of the installer" + }, + "SignatureSha256": { + "type": [ "string", "null" ], + "pattern": "^[A-Fa-f0-9]{64}$", + "description": "SignatureSha256 is recommended for appx or msix. It is the sha256 of signature file inside appx or msix. Could be used during streaming install if applicable" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + } + }, + "required": [ + "Architecture", + "InstallerUrl", + "InstallerSha256" + ] + } + }, + "type": "object", + "properties": { + "PackageIdentifier": { + "$ref": "#/definitions/PackageIdentifier" + }, + "PackageVersion": { + "$ref": "#/definitions/PackageVersion" + }, + "PackageLocale": { + "$ref": "#/definitions/Locale" + }, + "Publisher": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The publisher name" + }, + "PublisherUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher home page" + }, + "PublisherSupportUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher support page" + }, + "PrivacyUrl": { + "$ref": "#/definitions/Url", + "description": "The publisher privacy page or the package privacy page" + }, + "Author": { + "type": [ "string", "null" ], + "minLength": 2, + "maxLength": 256, + "description": "The package author" + }, + "PackageName": { + "type": "string", + "minLength": 2, + "maxLength": 256, + "description": "The package name" + }, + "PackageUrl": { + "$ref": "#/definitions/Url", + "description": "The package home page" + }, + "License": { + "type": "string", + "minLength": 3, + "maxLength": 512, + "description": "The package license" + }, + "LicenseUrl": { + "$ref": "#/definitions/Url", + "description": "The license page" + }, + "Copyright": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 512, + "description": "The package copyright" + }, + "CopyrightUrl": { + "$ref": "#/definitions/Url", + "description": "The package copyright page" + }, + "ShortDescription": { + "type": "string", + "minLength": 3, + "maxLength": 256, + "description": "The short package description" + }, + "Description": { + "type": [ "string", "null" ], + "minLength": 3, + "maxLength": 10000, + "description": "The full package description" + }, + "Moniker": { + "$ref": "#/definitions/Tag", + "description": "The most common package term" + }, + "Tags": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Tag" + }, + "maxItems": 16, + "uniqueItems": true, + "description": "List of additional package search terms" + }, + "Agreements": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Agreement" + }, + "maxItems": 128 + }, + "ReleaseNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The package release notes" + }, + "ReleaseNotesUrl": { + "$ref": "#/definitions/Url", + "description": "The package release notes url" + }, + "PurchaseUrl": { + "$ref": "#/definitions/Url", + "description": "The purchase url for acquiring entitlement for the package." + }, + "InstallationNotes": { + "type": [ "string", "null" ], + "minLength": 1, + "maxLength": 10000, + "description": "The notes displayed to the user upon completion of a package installation." + }, + "Documentations": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Documentation" + }, + "maxItems": 256 + }, + "Icons": { + "type": [ "array", "null" ], + "items": { + "$ref": "#/definitions/Icon" + }, + "maxItems": 1024 + }, + "Channel": { + "$ref": "#/definitions/Channel" + }, + "InstallerLocale": { + "$ref": "#/definitions/Locale" + }, + "Platform": { + "$ref": "#/definitions/Platform" + }, + "MinimumOSVersion": { + "$ref": "#/definitions/MinimumOSVersion" + }, + "InstallerType": { + "$ref": "#/definitions/InstallerType" + }, + "NestedInstallerType": { + "$ref": "#/definitions/NestedInstallerType" + }, + "NestedInstallerFiles": { + "$ref": "#/definitions/NestedInstallerFiles" + }, + "Scope": { + "$ref": "#/definitions/Scope" + }, + "InstallModes": { + "$ref": "#/definitions/InstallModes" + }, + "InstallerSwitches": { + "$ref": "#/definitions/InstallerSwitches" + }, + "InstallerSuccessCodes": { + "$ref": "#/definitions/InstallerSuccessCodes" + }, + "ExpectedReturnCodes": { + "$ref": "#/definitions/ExpectedReturnCodes" + }, + "UpgradeBehavior": { + "$ref": "#/definitions/UpgradeBehavior" + }, + "Commands": { + "$ref": "#/definitions/Commands" + }, + "Protocols": { + "$ref": "#/definitions/Protocols" + }, + "FileExtensions": { + "$ref": "#/definitions/FileExtensions" + }, + "Dependencies": { + "$ref": "#/definitions/Dependencies" + }, + "PackageFamilyName": { + "$ref": "#/definitions/PackageFamilyName" + }, + "ProductCode": { + "$ref": "#/definitions/ProductCode" + }, + "Capabilities": { + "$ref": "#/definitions/Capabilities" + }, + "RestrictedCapabilities": { + "$ref": "#/definitions/RestrictedCapabilities" + }, + "Markets": { + "$ref": "#/definitions/Markets" + }, + "InstallerAbortsTerminal": { + "$ref": "#/definitions/InstallerAbortsTerminal" + }, + "ReleaseDate": { + "$ref": "#/definitions/ReleaseDate" + }, + "InstallLocationRequired": { + "$ref": "#/definitions/InstallLocationRequired" + }, + "RequireExplicitUpgrade": { + "$ref": "#/definitions/RequireExplicitUpgrade" + }, + "DisplayInstallWarnings": { + "$ref": "#/definitions/DisplayInstallWarnings" + }, + "UnsupportedOSArchitectures": { + "$ref": "#/definitions/UnsupportedOSArchitectures" + }, + "UnsupportedArguments": { + "$ref": "#/definitions/UnsupportedArguments" + }, + "AppsAndFeaturesEntries": { + "$ref": "#/definitions/AppsAndFeaturesEntries" + }, + "ElevationRequirement": { + "$ref": "#/definitions/ElevationRequirement" + }, + "InstallationMetadata": { + "$ref": "#/definitions/InstallationMetadata" + }, + "DownloadCommandProhibited": { + "$ref": "#/definitions/DownloadCommandProhibited" + }, + "RepairBehavior": { + "$ref": "#/definitions/RepairBehavior" + }, + "Installers": { + "type": "array", + "items": { + "$ref": "#/definitions/Installer" + }, + "minItems": 1, + "maxItems": 1 + }, + "ManifestType": { + "type": "string", + "default": "singleton", + "const": "singleton", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.7.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "PackageLocale", + "Publisher", + "PackageName", + "License", + "ShortDescription", + "Installers", + "ManifestType", + "ManifestVersion" + ] +} diff --git a/schemas/JSON/manifests/v1.7.0/manifest.version.1.7.0.json b/schemas/JSON/manifests/v1.7.0/manifest.version.1.7.0.json index 30d7faa4ba..57c0d3dd3c 100644 --- a/schemas/JSON/manifests/v1.7.0/manifest.version.1.7.0.json +++ b/schemas/JSON/manifests/v1.7.0/manifest.version.1.7.0.json @@ -1,46 +1,46 @@ -{ - "$id": "https://aka.ms/winget-manifest.version.1.7.0.schema.json", - "$schema": "http://json-schema.org/draft-07/schema#", - "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.7.0", - "type": "object", - "properties": { - "PackageIdentifier": { - "type": "string", - "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", - "maxLength": 128, - "description": "The package unique identifier" - }, - "PackageVersion": { - "type": "string", - "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", - "maxLength": 128, - "description": "The package version" - }, - "DefaultLocale": { - "type": "string", - "default": "en-US", - "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", - "maxLength": 20, - "description": "The default package meta-data locale" - }, - "ManifestType": { - "type": "string", - "default": "version", - "const": "version", - "description": "The manifest type" - }, - "ManifestVersion": { - "type": "string", - "default": "1.7.0", - "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", - "description": "The manifest syntax version" - } - }, - "required": [ - "PackageIdentifier", - "PackageVersion", - "DefaultLocale", - "ManifestType", - "ManifestVersion" - ] +{ + "$id": "https://aka.ms/winget-manifest.version.1.7.0.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "description": "A representation of a multi-file manifest representing an app version in the OWC. v1.7.0", + "type": "object", + "properties": { + "PackageIdentifier": { + "type": "string", + "pattern": "^[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}(\\.[^\\.\\s\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]{1,32}){1,7}$", + "maxLength": 128, + "description": "The package unique identifier" + }, + "PackageVersion": { + "type": "string", + "pattern": "^[^\\\\/:\\*\\?\"<>\\|\\x01-\\x1f]+$", + "maxLength": 128, + "description": "The package version" + }, + "DefaultLocale": { + "type": "string", + "default": "en-US", + "pattern": "^([a-zA-Z]{2,3}|[iI]-[a-zA-Z]+|[xX]-[a-zA-Z]{1,8})(-[a-zA-Z]{1,8})*$", + "maxLength": 20, + "description": "The default package meta-data locale" + }, + "ManifestType": { + "type": "string", + "default": "version", + "const": "version", + "description": "The manifest type" + }, + "ManifestVersion": { + "type": "string", + "default": "1.7.0", + "pattern": "^(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])(\\.(0|[1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])){2}$", + "description": "The manifest syntax version" + } + }, + "required": [ + "PackageIdentifier", + "PackageVersion", + "DefaultLocale", + "ManifestType", + "ManifestVersion" + ] } \ No newline at end of file diff --git a/schemas/JSON/manifests/v1.9.0/manifest.defaultLocale.1.9.0.json b/schemas/JSON/manifests/v1.9.0/manifest.defaultLocale.1.9.0.json index 5b71ed19e6..ecceedaad0 100644 --- a/schemas/JSON/manifests/v1.9.0/manifest.defaultLocale.1.9.0.json +++ b/schemas/JSON/manifests/v1.9.0/manifest.defaultLocale.1.9.0.json @@ -277,4 +277,4 @@ "ManifestType", "ManifestVersion" ] -} +} diff --git a/schemas/JSON/manifests/v1.9.0/manifest.locale.1.9.0.json b/schemas/JSON/manifests/v1.9.0/manifest.locale.1.9.0.json index 96600dd5a2..3ff4797afc 100644 --- a/schemas/JSON/manifests/v1.9.0/manifest.locale.1.9.0.json +++ b/schemas/JSON/manifests/v1.9.0/manifest.locale.1.9.0.json @@ -268,4 +268,4 @@ "ManifestType", "ManifestVersion" ] -} +} diff --git a/src/AppInstallerCLI.sln b/src/AppInstallerCLI.sln index 96505d4af5..61b1f09a87 100644 --- a/src/AppInstallerCLI.sln +++ b/src/AppInstallerCLI.sln @@ -1,1137 +1,1137 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.14.36915.13 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppInstallerCLIPackage", "AppInstallerCLIPackage\AppInstallerCLIPackage.wapproj", "{6AA3791A-0713-4548-A357-87A323E7AC3A}" - ProjectSection(ProjectDependencies) = postProject - {0BA531C8-CF0C-405B-8221-0FE51BA529D1} = {0BA531C8-CF0C-405B-8221-0FE51BA529D1} - {1CC41A9A-AE66-459D-9210-1E572DD7BE69} = {1CC41A9A-AE66-459D-9210-1E572DD7BE69} - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} - {33745E4A-39E2-676F-7E23-50FB43848D25} = {33745E4A-39E2-676F-7E23-50FB43848D25} - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777} = {5B6F90DF-FD19-4BAE-83D9-24DAD128E777} - {6597EB04-D105-49A7-A5A3-D27FE1DF895E} = {6597EB04-D105-49A7-A5A3-D27FE1DF895E} - {CA460806-5E41-4E97-9A3D-1D74B433B663} = {CA460806-5E41-4E97-9A3D-1D74B433B663} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLI", "AppInstallerCLI\AppInstallerCLI.vcxproj", "{5B6F90DF-FD19-4BAE-83D9-24DAD128E777}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLICore", "AppInstallerCLICore\AppInstallerCLICore.vcxproj", "{1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}" - ProjectSection(ProjectDependencies) = postProject - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4} = {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLITests", "AppInstallerCLITests\AppInstallerCLITests.vcxproj", "{89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}" - ProjectSection(ProjectDependencies) = postProject - {6AA3791A-0713-4548-A357-87A323E7AC3A} = {6AA3791A-0713-4548-A357-87A323E7AC3A} - {6CB84692-5994-407D-B9BD-9216AF77FE83} = {6CB84692-5994-407D-B9BD-9216AF77FE83} - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External - Do Not Modify", "External - Do Not Modify", "{60618CAC-2995-4DF9-9914-45C6FC02C995}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerRepositoryCore", "AppInstallerRepositoryCore\AppInstallerRepositoryCore.vcxproj", "{5EB88068-5FB9-4E69-89B2-72DBC5E068F9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project", "Project", "{8D53D749-D51C-46F8-A162-9371AAA6C2E7}" - ProjectSection(SolutionItems) = preProject - ..\azure-pipelines.loc.yml = ..\azure-pipelines.loc.yml - ..\azure-pipelines.nuget.yml = ..\azure-pipelines.nuget.yml - ..\azure-pipelines.yml = ..\azure-pipelines.yml - ..\cgmanifest.json = ..\cgmanifest.json - Get-VcxprojNugetPackageVersions.ps1 = Get-VcxprojNugetPackageVersions.ps1 - ..\README.md = ..\README.md - ..\doc\ReleaseNotes.md = ..\doc\ReleaseNotes.md - Update-VcxprojNugetPackageVersions.ps1 = Update-VcxprojNugetPackageVersions.ps1 - ..\WinGetInProcCom.nuspec = ..\WinGetInProcCom.nuspec - ..\WinGetUtil.nuspec = ..\WinGetUtil.nuspec - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCommonCore", "AppInstallerCommonCore\AppInstallerCommonCore.vcxproj", "{5890D6ED-7C3B-40F3-B436-B54F640D9E65}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetUtil", "WinGetUtil\WinGetUtil.vcxproj", "{FB313532-38B0-4676-9303-AB200AA13576}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerTestExeInstaller", "AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.vcxproj", "{6CB84692-5994-407D-B9BD-9216AF77FE83}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "binver", "binver\binver.vcxitems", "{6E36DDD7-1602-474E-B1D7-D0A7E1D5AD86}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInstallerCLIE2ETests", "AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.csproj", "{3C0269FA-E582-4CA7-9E33-3881A005CA0C}" - ProjectSection(ProjectDependencies) = postProject - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA} = {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA} - {6CB84692-5994-407D-B9BD-9216AF77FE83} = {6CB84692-5994-407D-B9BD-9216AF77FE83} - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8} = {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8} - EndProjectSection -EndProject -Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppInstallerTestMsixInstaller", "AppInstallerTestMsixInstaller\AppInstallerTestMsixInstaller.wapproj", "{3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}" -EndProject -Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "AppInstallerTestMsiInstaller", "AppInstallerTestMsiInstaller\AppInstallerTestMsiInstaller.vdproj", "{C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tool", "Tool", "{EA8CD934-0702-4911-A2C5-A40600E616DE}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IndexCreationTool", "IndexCreationTool\IndexCreationTool.csproj", "{3B8466CF-4FDD-4329-9C80-91321C4AAC99}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Fuzzing", "Fuzzing", "{6D7776A8-42FE-46DD-B0F8-712F35EA0C79}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetYamlFuzzing", "WinGetYamlFuzzing\WinGetYamlFuzzing.vcxproj", "{1622DA16-914F-4F57-A259-D5169003CC8C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalhostWebServer", "LocalhostWebServer\LocalhostWebServer.csproj", "{3BAF989F-7F65-465B-ACE8-BAFE42D1017E}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManifestSchema", "ManifestSchema\ManifestSchema.vcxitems", "{7D05F64D-CE5A-42AA-A2C1-E91458F061CF}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetSchemas", "WinGetSchemas\WinGetSchemas.vcxitems", "{952B513F-8A00-4D74-9271-925AFB3C6252}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "spelling", "spelling", "{2ACDE176-F13F-42FA-8159-C34FA3D37837}" - ProjectSection(SolutionItems) = preProject - ..\.github\actions\spelling\allow.txt = ..\.github\actions\spelling\allow.txt - ..\.github\actions\spelling\excludes.txt = ..\.github\actions\spelling\excludes.txt - ..\.github\actions\spelling\expect.txt = ..\.github\actions\spelling\expect.txt - ..\.github\actions\spelling\patterns.txt = ..\.github\actions\spelling\patterns.txt - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "policy", "policy", "{1A47951F-5C7A-4D6D-BB5F-D77484437940}" - ProjectSection(SolutionItems) = preProject - ..\doc\admx\en-US\DesktopAppInstaller.adml = ..\doc\admx\en-US\DesktopAppInstaller.adml - ..\doc\admx\DesktopAppInstaller.admx = ..\doc\admx\DesktopAppInstaller.admx - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798}" - ProjectSection(SolutionItems) = preProject - CodeAnalysis.ruleset = CodeAnalysis.ruleset - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - Directory.Solution.props = Directory.Solution.props - nuget.config = nuget.config - stylecop.json = stylecop.json - vcpkg.json = vcpkg.json - vcpkg.props = vcpkg.props - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment", "Microsoft.Management.Deployment\Microsoft.Management.Deployment.vcxproj", "{1CC41A9A-AE66-459D-9210-1E572DD7BE69}" - ProjectSection(ProjectDependencies) = postProject - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D} = {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D} - {5890D6ED-7C3B-40F3-B436-B54F640D9E65} = {5890D6ED-7C3B-40F3-B436-B54F640D9E65} - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9} = {5EB88068-5FB9-4E69-89B2-72DBC5E068F9} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetServer", "WinGetServer\WinGetServer.vcxproj", "{2B00D362-AC92-41F3-A8D2-5B1599BDCA01}" - ProjectSection(ProjectDependencies) = postProject - {1CC41A9A-AE66-459D-9210-1E572DD7BE69} = {1CC41A9A-AE66-459D-9210-1E572DD7BE69} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinGetUtilInterop", "WinGetUtilInterop\WinGetUtilInterop.csproj", "{846FB88B-BF1B-4F33-9883-E589CEC99739}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinGetUtilInterop.UnitTests", "WinGetUtilInterop.UnitTests\WinGetUtilInterop.UnitTests.csproj", "{68808357-902B-406C-8C19-E8E26A69DE8A}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsPackageManager", "WindowsPackageManager\WindowsPackageManager.vcxproj", "{2046B5AF-666D-4CE8-8D3E-C32C57908A56}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "COMServer", "COMServer\COMServer.vcxitems", "{409CD681-22A4-469D-88AE-CB5E4836E07A}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment.InProc", "Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.vcxproj", "{9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CertificateResources", "CertificateResources\CertificateResources.vcxitems", "{B0BBBD92-943B-408F-B2B2-DBBAB4A22D23}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerShell", "PowerShell", "{7C218A3E-9BC8-48FF-B91B-BCACD828C0C9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Client.Cmdlets", "PowerShell\Microsoft.WinGet.Client.Cmdlets\Microsoft.WinGet.Client.Cmdlets.csproj", "{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Deployment.Projection", "Microsoft.Management.Deployment.Projection\Microsoft.Management.Deployment.Projection.csproj", "{0B104762-5CD8-47EE-A904-71C1C3F84DCD}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UndockedRegFreeWinRT", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj", "{31ED69A8-5310-45A9-953F-56C351D2C3E1}" - ProjectSection(ProjectDependencies) = postProject - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{8E43F982-40D5-4DF1-9044-C08047B5F43B}" - ProjectSection(SolutionItems) = preProject - ..\templates\e2e-setup.yml = ..\templates\e2e-setup.yml - ..\templates\e2e-test.template.yml = ..\templates\e2e-test.template.yml - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerSharedLib", "AppInstallerSharedLib\AppInstallerSharedLib.vcxproj", "{F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Configuration", "Microsoft.Management.Configuration\Microsoft.Management.Configuration.vcxproj", "{CA460806-5E41-4E97-9A3D-1D74B433B663}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Configuration.Projection", "Microsoft.Management.Configuration.Projection\Microsoft.Management.Configuration.Projection.csproj", "{E8454BF1-2068-4513-A525-ABF55CC8742C}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Configuration.UnitTests", "Microsoft.Management.Configuration.UnitTests\Microsoft.Management.Configuration.UnitTests.csproj", "{EE43C990-7789-4A60-B077-BF0ED3D093A1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Configuration.Processor", "Microsoft.Management.Configuration.Processor\Microsoft.Management.Configuration.Processor.csproj", "{71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationRemotingServer", "ConfigurationRemotingServer\ConfigurationRemotingServer.csproj", "{6597EB04-D105-49A7-A5A3-D27FE1DF895E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Client.Engine", "PowerShell\Microsoft.WinGet.Client.Engine\Microsoft.WinGet.Client.Engine.csproj", "{1F56BECB-D65D-4BBA-8788-6671B251392A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Configuration.Cmdlets", "PowerShell\Microsoft.WinGet.Configuration.Cmdlets\Microsoft.WinGet.Configuration.Cmdlets.csproj", "{167F634B-A3AD-494E-8E67-B888103E35FF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Configuration.Engine", "PowerShell\Microsoft.WinGet.Configuration.Engine\Microsoft.WinGet.Configuration.Engine.csproj", "{C54F80ED-B736-49B0-9BD3-662F57024D01}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Configuration.OutOfProc", "Microsoft.Management.Configuration.OutOfProc\Microsoft.Management.Configuration.OutOfProc.vcxproj", "{2268D5AD-7F2A-485A-8C4B-C574497514C9}" - ProjectSection(ProjectDependencies) = postProject - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinGetSourceCreator", "WinGetSourceCreator\WinGetSourceCreator.csproj", "{52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.SharedLib", "PowerShell\Microsoft.WinGet.SharedLib\Microsoft.WinGet.SharedLib.csproj", "{272B2B0E-40D4-4F0F-B187-519A6EF89B10}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5A52D9FC-0059-4A4A-8196-427A7AA0D1C5}" - ProjectSection(SolutionItems) = preProject - PowerShell\tests\Microsoft.WinGet.Client.Tests.ps1 = PowerShell\tests\Microsoft.WinGet.Client.Tests.ps1 - PowerShell\tests\Microsoft.WinGet.Configuration.Tests.ps1 = PowerShell\tests\Microsoft.WinGet.Configuration.Tests.ps1 - PowerShell\tests\Microsoft.WinGet.DSC.Tests.ps1 = PowerShell\tests\Microsoft.WinGet.DSC.Tests.ps1 - PowerShell\tests\RunTests.ps1 = PowerShell\tests\RunTests.ps1 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgCustomTriplets", "VcpkgCustomTriplets", "{76B26B2C-602A-4AD0-9736-4162D3FCA92A}" - ProjectSection(SolutionItems) = preProject - VcpkgCustomTriplets\arm64-release-static.cmake = VcpkgCustomTriplets\arm64-release-static.cmake - VcpkgCustomTriplets\arm64-release.cmake = VcpkgCustomTriplets\arm64-release.cmake - VcpkgCustomTriplets\arm64.cmake = VcpkgCustomTriplets\arm64.cmake - VcpkgCustomTriplets\common.cmake = VcpkgCustomTriplets\common.cmake - VcpkgCustomTriplets\fuzzing.cmake = VcpkgCustomTriplets\fuzzing.cmake - VcpkgCustomTriplets\x64-fuzzing.cmake = VcpkgCustomTriplets\x64-fuzzing.cmake - VcpkgCustomTriplets\x64-release-static.cmake = VcpkgCustomTriplets\x64-release-static.cmake - VcpkgCustomTriplets\x64-release.cmake = VcpkgCustomTriplets\x64-release.cmake - VcpkgCustomTriplets\x64.cmake = VcpkgCustomTriplets\x64.cmake - VcpkgCustomTriplets\x86-fuzzing.cmake = VcpkgCustomTriplets\x86-fuzzing.cmake - VcpkgCustomTriplets\x86-release-static.cmake = VcpkgCustomTriplets\x86-release-static.cmake - VcpkgCustomTriplets\x86-release.cmake = VcpkgCustomTriplets\x86-release.cmake - VcpkgCustomTriplets\x86.cmake = VcpkgCustomTriplets\x86.cmake - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgPortOverlay", "VcpkgPortOverlay", "{EF18BED6-EBC0-45E9-8D61-4202528E8AAB}" - ProjectSection(SolutionItems) = preProject - VcpkgPortOverlay\CreatePortOverlay.ps1 = VcpkgPortOverlay\CreatePortOverlay.ps1 - VcpkgPortOverlay\README.md = VcpkgPortOverlay\README.md - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment.OutOfProc", "Microsoft.Management.Deployment.OutOfProc\Microsoft.Management.Deployment.OutOfProc.vcxproj", "{0BA531C8-CF0C-405B-8221-0FE51BA529D1}" - ProjectSection(ProjectDependencies) = postProject - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Deployment.CsWinRTProjection", "Microsoft.Management.Deployment.CsWinRTProjection\Microsoft.Management.Deployment.CsWinRTProjection.csproj", "{9406322E-6272-487E-902A-9953889719EA}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "targets", "targets", "{A0B4F808-B190-41C4-97CB-C8EA1932F84F}" - ProjectSection(SolutionItems) = preProject - targets\EmbeddedCsWinRT.targets = targets\EmbeddedCsWinRT.targets - targets\ReferenceEmbeddedCsWinRTProject.targets = targets\ReferenceEmbeddedCsWinRTProject.targets - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PureLib", "PureLib\PureLib.vcxitems", "{A33223D2-550B-4D99-A53D-488B1F68683E}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinGetTestCommon", "WinGetTestCommon\WinGetTestCommon.csproj", "{7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinGetMCPServer", "WinGetMCPServer\WinGetMCPServer.csproj", "{33745E4A-39E2-676F-7E23-50FB43848D25}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{F49C4C89-447E-4D15-B38B-5A8DCFB134AF}" - ProjectSection(SolutionItems) = preProject - PowerShell\scripts\Execute-WinGetTests.ps1 = PowerShell\scripts\Execute-WinGetTests.ps1 - PowerShell\scripts\Initialize-LocalWinGetModules.ps1 = PowerShell\scripts\Initialize-LocalWinGetModules.ps1 - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ComInprocTestbed", "ComInprocTestbed\ComInprocTestbed.vcxproj", "{E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DSC", "DSC", "{40D7CA7F-EB86-4345-9641-AD27180C559D}" - ProjectSection(SolutionItems) = preProject - PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psd1 = PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psd1 - PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psm1 = PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psm1 - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.WinGet.UnitTests", "PowerShell\Microsoft.WinGet.UnitTests\Microsoft.WinGet.UnitTests.csproj", "{5421394F-5619-4E4B-8923-F3FB30D5EFAD}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "copilot", "copilot", "{B978E358-D2BE-4FA7-A21A-6661F3744DD7}" - ProjectSection(SolutionItems) = preProject - ..\.github\copilot-instructions.md = ..\.github\copilot-instructions.md - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{3FF6C881-2548-486E-8D70-7555A90030F5}" - ProjectSection(SolutionItems) = preProject - ..\doc\windows\package-manager\winget\returnCodes.md = ..\doc\windows\package-manager\winget\returnCodes.md - ..\doc\Settings.md = ..\doc\Settings.md - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|ARM64 = Debug|ARM64 - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Fuzzing|ARM64 = Fuzzing|ARM64 - Fuzzing|x64 = Fuzzing|x64 - Fuzzing|x86 = Fuzzing|x86 - Release|ARM64 = Release|ARM64 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - ReleaseStatic|ARM64 = ReleaseStatic|ARM64 - ReleaseStatic|x64 = ReleaseStatic|x64 - ReleaseStatic|x86 = ReleaseStatic|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|ARM64.Build.0 = Debug|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x64.ActiveCfg = Debug|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x64.Build.0 = Debug|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x64.Deploy.0 = Debug|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x86.ActiveCfg = Debug|x86 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x86.Build.0 = Debug|x86 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x86.Deploy.0 = Debug|x86 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Fuzzing|x64.ActiveCfg = Release|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Fuzzing|x86.ActiveCfg = Release|x86 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|ARM64.ActiveCfg = Release|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|ARM64.Build.0 = Release|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|ARM64.Deploy.0 = Release|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x64.ActiveCfg = Release|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x64.Build.0 = Release|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x64.Deploy.0 = Release|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x86.ActiveCfg = Release|x86 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x86.Build.0 = Release|x86 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x86.Deploy.0 = Release|x86 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {6AA3791A-0713-4548-A357-87A323E7AC3A}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|ARM64.Build.0 = Debug|ARM64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x64.ActiveCfg = Debug|x64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x64.Build.0 = Debug|x64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x86.ActiveCfg = Debug|Win32 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x86.Build.0 = Debug|Win32 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Fuzzing|x64.ActiveCfg = Release|x64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Fuzzing|x86.ActiveCfg = Release|Win32 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|ARM64.ActiveCfg = Release|ARM64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|ARM64.Build.0 = Release|ARM64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x64.ActiveCfg = Release|x64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x64.Build.0 = Release|x64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x86.ActiveCfg = Release|Win32 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x86.Build.0 = Release|Win32 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.ReleaseStatic|x86.ActiveCfg = Release|Win32 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|ARM64.Build.0 = Debug|ARM64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x64.ActiveCfg = Debug|x64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x64.Build.0 = Debug|x64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x86.ActiveCfg = Debug|Win32 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x86.Build.0 = Debug|Win32 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Fuzzing|x64.ActiveCfg = Release|x64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Fuzzing|x86.ActiveCfg = Release|Win32 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|ARM64.ActiveCfg = Release|ARM64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|ARM64.Build.0 = Release|ARM64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x64.ActiveCfg = Release|x64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x64.Build.0 = Release|x64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x86.ActiveCfg = Release|Win32 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x86.Build.0 = Release|Win32 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM64.Build.0 = Debug|ARM64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.ActiveCfg = Debug|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.Build.0 = Debug|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.ActiveCfg = Debug|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.Build.0 = Debug|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|ARM64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x86.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM64.ActiveCfg = Release|ARM64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM64.Build.0 = Release|ARM64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.Build.0 = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.ActiveCfg = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.Build.0 = Release|Win32 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.ReleaseStatic|x86.ActiveCfg = Release|Win32 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|ARM64.Build.0 = Debug|ARM64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x64.ActiveCfg = Debug|x64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x64.Build.0 = Debug|x64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x86.ActiveCfg = Debug|Win32 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x86.Build.0 = Debug|Win32 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Fuzzing|x64.ActiveCfg = Release|x64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Fuzzing|x86.ActiveCfg = Release|Win32 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|ARM64.ActiveCfg = Release|ARM64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|ARM64.Build.0 = Release|ARM64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x64.ActiveCfg = Release|x64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x64.Build.0 = Release|x64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x86.ActiveCfg = Release|Win32 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x86.Build.0 = Release|Win32 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|ARM64.Build.0 = Debug|ARM64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x64.ActiveCfg = Debug|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x64.Build.0 = Debug|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x86.ActiveCfg = Debug|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x86.Build.0 = Debug|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x64.Build.0 = Fuzzing|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x86.Build.0 = Fuzzing|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|ARM64.ActiveCfg = Release|ARM64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|ARM64.Build.0 = Release|ARM64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x64.ActiveCfg = Release|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x64.Build.0 = Release|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x86.ActiveCfg = Release|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x86.Build.0 = Release|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {FB313532-38B0-4676-9303-AB200AA13576}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {FB313532-38B0-4676-9303-AB200AA13576}.Debug|ARM64.Build.0 = Debug|ARM64 - {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x64.ActiveCfg = Debug|x64 - {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x64.Build.0 = Debug|x64 - {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x86.ActiveCfg = Debug|Win32 - {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x86.Build.0 = Debug|Win32 - {FB313532-38B0-4676-9303-AB200AA13576}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {FB313532-38B0-4676-9303-AB200AA13576}.Fuzzing|x64.ActiveCfg = Release|x64 - {FB313532-38B0-4676-9303-AB200AA13576}.Fuzzing|x86.ActiveCfg = Release|Win32 - {FB313532-38B0-4676-9303-AB200AA13576}.Release|ARM64.ActiveCfg = Release|ARM64 - {FB313532-38B0-4676-9303-AB200AA13576}.Release|ARM64.Build.0 = Release|ARM64 - {FB313532-38B0-4676-9303-AB200AA13576}.Release|x64.ActiveCfg = Release|x64 - {FB313532-38B0-4676-9303-AB200AA13576}.Release|x64.Build.0 = Release|x64 - {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.ActiveCfg = Release|Win32 - {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.Build.0 = Release|Win32 - {FB313532-38B0-4676-9303-AB200AA13576}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 - {FB313532-38B0-4676-9303-AB200AA13576}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {FB313532-38B0-4676-9303-AB200AA13576}.ReleaseStatic|x86.ActiveCfg = Release|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.Build.0 = Debug|ARM64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.ActiveCfg = Debug|x64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.Build.0 = Debug|x64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.ActiveCfg = Debug|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.Build.0 = Debug|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Fuzzing|x64.ActiveCfg = Release|x64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Fuzzing|x86.ActiveCfg = Release|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.ActiveCfg = Release|ARM64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.Build.0 = Release|ARM64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.ActiveCfg = Release|x64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.Build.0 = Release|x64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.ActiveCfg = Release|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.Build.0 = Release|Win32 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {6CB84692-5994-407D-B9BD-9216AF77FE83}.ReleaseStatic|x86.ActiveCfg = Release|Win32 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|ARM64.ActiveCfg = Debug|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x64.ActiveCfg = Debug|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x64.Build.0 = Debug|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x86.ActiveCfg = Debug|x86 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x86.Build.0 = Debug|x86 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Fuzzing|ARM64.ActiveCfg = Release|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Fuzzing|x64.ActiveCfg = Release|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Fuzzing|x86.ActiveCfg = Release|x86 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|ARM64.ActiveCfg = Release|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x64.ActiveCfg = Release|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x64.Build.0 = Release|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x86.ActiveCfg = Release|x86 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x86.Build.0 = Release|x86 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.ReleaseStatic|ARM64.ActiveCfg = Release|x86 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.Build.0 = Debug|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.Deploy.0 = Debug|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.ActiveCfg = Debug|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.Build.0 = Debug|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.Deploy.0 = Debug|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.ActiveCfg = Debug|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.Build.0 = Debug|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.Deploy.0 = Debug|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Fuzzing|x64.ActiveCfg = Release|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Fuzzing|x86.ActiveCfg = Release|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.ActiveCfg = Release|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.Build.0 = Release|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.Deploy.0 = Release|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.ActiveCfg = Release|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.Build.0 = Release|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.Deploy.0 = Release|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.ActiveCfg = Release|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.Build.0 = Release|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.Deploy.0 = Release|x86 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.ActiveCfg = Debug - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.ActiveCfg = Debug - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.ActiveCfg = Debug - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.ActiveCfg = Release - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.ActiveCfg = Release - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM64.ActiveCfg = Debug|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.ActiveCfg = Debug|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.Build.0 = Debug|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x86.ActiveCfg = Debug|x86 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x86.Build.0 = Debug|x86 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Fuzzing|ARM64.ActiveCfg = Release|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Fuzzing|x64.ActiveCfg = Release|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Fuzzing|x86.ActiveCfg = Release|x86 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|ARM64.ActiveCfg = Release|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x64.ActiveCfg = Release|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x64.Build.0 = Release|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.ActiveCfg = Release|x86 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.Build.0 = Release|x86 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|ARM64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|x64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|x86.ActiveCfg = Fuzzing|Win32 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|ARM64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x64.Build.0 = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x86.Build.0 = Fuzzing|Win32 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Release|ARM64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Release|x64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.Release|x86.ActiveCfg = Fuzzing|Win32 - {1622DA16-914F-4F57-A259-D5169003CC8C}.ReleaseStatic|ARM64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.ReleaseStatic|x64.ActiveCfg = Fuzzing|x64 - {1622DA16-914F-4F57-A259-D5169003CC8C}.ReleaseStatic|x86.ActiveCfg = Fuzzing|Win32 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|ARM64.ActiveCfg = Debug|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x64.ActiveCfg = Debug|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x64.Build.0 = Debug|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x86.ActiveCfg = Debug|x86 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x86.Build.0 = Debug|x86 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Fuzzing|ARM64.ActiveCfg = Release|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Fuzzing|x64.ActiveCfg = Release|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Fuzzing|x86.ActiveCfg = Release|x86 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|ARM64.ActiveCfg = Release|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x64.ActiveCfg = Release|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x64.Build.0 = Release|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x86.ActiveCfg = Release|x86 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x86.Build.0 = Release|x86 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|ARM64.Build.0 = Debug|ARM64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x64.ActiveCfg = Debug|x64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x64.Build.0 = Debug|x64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x86.ActiveCfg = Debug|Win32 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x86.Build.0 = Debug|Win32 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Fuzzing|x64.ActiveCfg = Release|x64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Fuzzing|x86.ActiveCfg = Release|Win32 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|ARM64.ActiveCfg = Release|ARM64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|ARM64.Build.0 = Release|ARM64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x64.ActiveCfg = Release|x64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x64.Build.0 = Release|x64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x86.ActiveCfg = Release|Win32 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x86.Build.0 = Release|Win32 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|ARM64.Build.0 = Debug|ARM64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x64.ActiveCfg = Debug|x64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x64.Build.0 = Debug|x64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x86.ActiveCfg = Debug|Win32 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x86.Build.0 = Debug|Win32 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Fuzzing|x64.ActiveCfg = Release|x64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Fuzzing|x86.ActiveCfg = Release|Win32 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|ARM64.ActiveCfg = Release|ARM64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|ARM64.Build.0 = Release|ARM64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x64.ActiveCfg = Release|x64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x64.Build.0 = Release|x64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x86.ActiveCfg = Release|Win32 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x86.Build.0 = Release|Win32 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|ARM64.Build.0 = Debug|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x64.ActiveCfg = Debug|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x64.Build.0 = Debug|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x86.ActiveCfg = Debug|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x86.Build.0 = Debug|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|ARM64.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|ARM64.Build.0 = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x64.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x64.Build.0 = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x86.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x86.Build.0 = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.ReleaseStatic|ARM64.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.ReleaseStatic|x64.ActiveCfg = Release|Any CPU - {846FB88B-BF1B-4F33-9883-E589CEC99739}.ReleaseStatic|x86.ActiveCfg = Release|Any CPU - {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|ARM64.ActiveCfg = Debug|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x64.ActiveCfg = Debug|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x64.Build.0 = Debug|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x86.ActiveCfg = Debug|x86 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x86.Build.0 = Debug|x86 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Fuzzing|ARM64.ActiveCfg = Release|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Fuzzing|x64.ActiveCfg = Release|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Fuzzing|x86.ActiveCfg = Release|x86 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|ARM64.ActiveCfg = Release|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x64.ActiveCfg = Release|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x64.Build.0 = Release|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x86.ActiveCfg = Release|x86 - {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x86.Build.0 = Release|x86 - {68808357-902B-406C-8C19-E8E26A69DE8A}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {68808357-902B-406C-8C19-E8E26A69DE8A}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|ARM64.Build.0 = Debug|ARM64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x64.ActiveCfg = Debug|x64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x64.Build.0 = Debug|x64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x86.ActiveCfg = Debug|Win32 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x86.Build.0 = Debug|Win32 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Fuzzing|x64.ActiveCfg = Release|x64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Fuzzing|x86.ActiveCfg = Release|Win32 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|ARM64.ActiveCfg = Release|ARM64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|ARM64.Build.0 = Release|ARM64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x64.ActiveCfg = Release|x64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x64.Build.0 = Release|x64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x86.ActiveCfg = Release|Win32 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x86.Build.0 = Release|Win32 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|ARM64.Build.0 = Debug|ARM64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x64.ActiveCfg = Debug|x64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x64.Build.0 = Debug|x64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x86.ActiveCfg = Debug|Win32 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x86.Build.0 = Debug|Win32 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Fuzzing|x64.ActiveCfg = Release|x64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Fuzzing|x86.ActiveCfg = Release|Win32 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|ARM64.ActiveCfg = Release|ARM64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x64.ActiveCfg = Release|x64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x64.Build.0 = Release|x64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x86.ActiveCfg = Release|Win32 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x86.Build.0 = Release|Win32 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM64.Build.0 = Debug|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x64.ActiveCfg = Debug|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x64.Build.0 = Debug|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x86.ActiveCfg = Debug|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x86.Build.0 = Debug|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM64.ActiveCfg = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM64.Build.0 = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x64.ActiveCfg = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x64.Build.0 = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x86.ActiveCfg = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x86.Build.0 = Release|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|ARM64.ActiveCfg = Debug|arm64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x64.ActiveCfg = Debug|x64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x64.Build.0 = Debug|x64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x86.ActiveCfg = Debug|x86 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x86.Build.0 = Debug|x86 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|ARM64.ActiveCfg = Release|arm64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|x64.ActiveCfg = Release|x64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|x86.ActiveCfg = Release|x86 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|ARM64.ActiveCfg = Release|arm64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x64.ActiveCfg = Release|x64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x64.Build.0 = Release|x64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x86.ActiveCfg = Release|x86 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x86.Build.0 = Release|x86 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|arm64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|arm64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|x86 - {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x86.Build.0 = ReleaseStatic|x86 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|ARM64.Build.0 = Debug|ARM64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x64.ActiveCfg = Debug|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x64.Build.0 = Debug|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x86.ActiveCfg = Debug|Win32 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x86.Build.0 = Debug|Win32 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Fuzzing|x64.ActiveCfg = Release|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Fuzzing|x86.ActiveCfg = Release|Win32 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|ARM64.ActiveCfg = Release|ARM64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|ARM64.Build.0 = Release|ARM64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x64.ActiveCfg = Release|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x64.Build.0 = Release|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.ActiveCfg = Release|Win32 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.Build.0 = Release|Win32 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|ARM64.Build.0 = Debug|ARM64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x64.ActiveCfg = Debug|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x64.Build.0 = Debug|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x86.ActiveCfg = Debug|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x86.Build.0 = Debug|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|ARM64.ActiveCfg = Fuzzing|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x64.Build.0 = Fuzzing|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x86.Build.0 = Fuzzing|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|ARM64.ActiveCfg = Release|ARM64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|ARM64.Build.0 = Release|ARM64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x64.ActiveCfg = Release|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x64.Build.0 = Release|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x86.ActiveCfg = Release|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x86.Build.0 = Release|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|ARM64.Build.0 = Debug|ARM64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x64.ActiveCfg = Debug|x64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x64.Build.0 = Debug|x64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x86.ActiveCfg = Debug|Win32 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x86.Build.0 = Debug|Win32 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Fuzzing|x64.ActiveCfg = Release|x64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Fuzzing|x86.ActiveCfg = Release|Win32 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|ARM64.ActiveCfg = Release|ARM64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|ARM64.Build.0 = Release|ARM64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x64.ActiveCfg = Release|x64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x64.Build.0 = Release|x64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x86.ActiveCfg = Release|Win32 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x86.Build.0 = Release|Win32 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|ARM64.Build.0 = Debug|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x64.ActiveCfg = Debug|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x64.Build.0 = Debug|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x86.ActiveCfg = Debug|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x86.Build.0 = Debug|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|ARM64.ActiveCfg = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|ARM64.Build.0 = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x64.ActiveCfg = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x64.Build.0 = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x86.ActiveCfg = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x86.Build.0 = Release|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|ARM64.ActiveCfg = Debug|arm64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|ARM64.Build.0 = Debug|arm64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x64.ActiveCfg = Debug|x64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x64.Build.0 = Debug|x64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x86.ActiveCfg = Debug|x86 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x86.Build.0 = Debug|x86 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Fuzzing|ARM64.ActiveCfg = Release|arm64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Fuzzing|x64.ActiveCfg = Release|x64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Fuzzing|x86.ActiveCfg = Release|x86 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|ARM64.ActiveCfg = Release|arm64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|ARM64.Build.0 = Release|arm64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x64.ActiveCfg = Release|x64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x64.Build.0 = Release|x64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x86.ActiveCfg = Release|x86 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x86.Build.0 = Release|x86 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {EE43C990-7789-4A60-B077-BF0ED3D093A1}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|ARM64.Build.0 = Debug|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x64.ActiveCfg = Debug|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x64.Build.0 = Debug|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x86.ActiveCfg = Debug|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x86.Build.0 = Debug|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|ARM64.ActiveCfg = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|ARM64.Build.0 = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x64.ActiveCfg = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x64.Build.0 = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x86.ActiveCfg = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x86.Build.0 = Release|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|ARM64.ActiveCfg = Debug|arm64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|ARM64.Build.0 = Debug|arm64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x64.ActiveCfg = Debug|x64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x64.Build.0 = Debug|x64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x86.ActiveCfg = Debug|x86 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x86.Build.0 = Debug|x86 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|ARM64.ActiveCfg = Release|arm64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x64.ActiveCfg = Release|x64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x86.ActiveCfg = Release|x86 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|ARM64.ActiveCfg = Release|arm64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|ARM64.Build.0 = Release|arm64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x64.ActiveCfg = Release|x64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x64.Build.0 = Release|x64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x86.ActiveCfg = Release|x86 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x86.Build.0 = Release|x86 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|ARM64.Build.0 = Debug|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x64.Build.0 = Debug|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x86.ActiveCfg = Debug|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x86.Build.0 = Debug|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|ARM64.ActiveCfg = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|ARM64.Build.0 = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x64.ActiveCfg = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x64.Build.0 = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x86.ActiveCfg = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x86.Build.0 = Release|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|ARM64.Build.0 = Debug|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x64.ActiveCfg = Debug|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x64.Build.0 = Debug|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x86.ActiveCfg = Debug|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x86.Build.0 = Debug|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|ARM64.ActiveCfg = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|ARM64.Build.0 = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x64.ActiveCfg = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x64.Build.0 = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x86.ActiveCfg = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x86.Build.0 = Release|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|ARM64.Build.0 = Debug|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x64.ActiveCfg = Debug|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x64.Build.0 = Debug|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x86.ActiveCfg = Debug|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x86.Build.0 = Debug|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|ARM64.ActiveCfg = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|ARM64.Build.0 = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x64.ActiveCfg = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x64.Build.0 = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x86.ActiveCfg = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x86.Build.0 = Release|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|ARM64.Build.0 = Debug|ARM64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x64.ActiveCfg = Debug|x64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x64.Build.0 = Debug|x64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x86.ActiveCfg = Debug|Win32 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x86.Build.0 = Debug|Win32 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Fuzzing|x64.ActiveCfg = Release|x64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Fuzzing|x86.ActiveCfg = Release|Win32 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|ARM64.ActiveCfg = Release|ARM64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|ARM64.Build.0 = Release|ARM64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x64.ActiveCfg = Release|x64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x64.Build.0 = Release|x64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x86.ActiveCfg = Release|Win32 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x86.Build.0 = Release|Win32 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|ARM64.Build.0 = Debug|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x64.ActiveCfg = Debug|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x64.Build.0 = Debug|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x86.ActiveCfg = Debug|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x86.Build.0 = Debug|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|ARM64.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|ARM64.Build.0 = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x64.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x64.Build.0 = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x86.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x86.Build.0 = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.ReleaseStatic|ARM64.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.ReleaseStatic|x64.ActiveCfg = Release|Any CPU - {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.ReleaseStatic|x86.ActiveCfg = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|ARM64.Build.0 = Debug|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x64.ActiveCfg = Debug|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x64.Build.0 = Debug|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x86.ActiveCfg = Debug|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x86.Build.0 = Debug|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|ARM64.ActiveCfg = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|ARM64.Build.0 = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x64.ActiveCfg = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x64.Build.0 = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x86.ActiveCfg = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x86.Build.0 = Release|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.Build.0 = Debug|ARM64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x64.ActiveCfg = Debug|x64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x64.Build.0 = Debug|x64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x86.ActiveCfg = Debug|Win32 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x86.Build.0 = Debug|Win32 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Fuzzing|x64.ActiveCfg = Release|x64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Fuzzing|x86.ActiveCfg = Release|Win32 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|ARM64.ActiveCfg = Release|ARM64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|ARM64.Build.0 = Release|ARM64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x64.ActiveCfg = Release|x64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x64.Build.0 = Release|x64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x86.ActiveCfg = Release|Win32 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x86.Build.0 = Release|Win32 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 - {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 - {9406322E-6272-487E-902A-9953889719EA}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Debug|ARM64.Build.0 = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Debug|x64.ActiveCfg = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Debug|x64.Build.0 = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Debug|x86.ActiveCfg = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Debug|x86.Build.0 = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Fuzzing|ARM64.ActiveCfg = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Fuzzing|x64.ActiveCfg = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Fuzzing|x86.ActiveCfg = Debug|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Release|ARM64.ActiveCfg = Release|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Release|ARM64.Build.0 = Release|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Release|x64.ActiveCfg = Release|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Release|x64.Build.0 = Release|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Release|x86.ActiveCfg = Release|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.Release|x86.Build.0 = Release|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU - {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|ARM64.ActiveCfg = Debug|arm64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|ARM64.Build.0 = Debug|arm64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x64.ActiveCfg = Debug|x64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x64.Build.0 = Debug|x64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x86.ActiveCfg = Debug|x86 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x86.Build.0 = Debug|x86 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Fuzzing|ARM64.ActiveCfg = Release|arm64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Fuzzing|x64.ActiveCfg = Release|x64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Fuzzing|x86.ActiveCfg = Release|x86 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|ARM64.ActiveCfg = Release|arm64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|ARM64.Build.0 = Release|arm64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x64.ActiveCfg = Release|x64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x64.Build.0 = Release|x64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x86.ActiveCfg = Release|x86 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x86.Build.0 = Release|x86 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|ARM64.ActiveCfg = Debug|arm64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|ARM64.Build.0 = Debug|arm64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x64.ActiveCfg = Debug|x64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x64.Build.0 = Debug|x64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x86.ActiveCfg = Debug|x86 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x86.Build.0 = Debug|x86 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Fuzzing|ARM64.ActiveCfg = Release|arm64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Fuzzing|x64.ActiveCfg = Release|x64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Fuzzing|x86.ActiveCfg = Release|x86 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|ARM64.ActiveCfg = Release|arm64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|ARM64.Build.0 = Release|arm64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x64.ActiveCfg = Release|x64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x64.Build.0 = Release|x64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x86.ActiveCfg = Release|x86 - {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x86.Build.0 = Release|x86 - {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|ARM64.Build.0 = Release|arm64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x64.Build.0 = Release|x64 - {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x86.ActiveCfg = Release|x86 - {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x86.Build.0 = Release|x86 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|ARM64.ActiveCfg = Debug|ARM64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|ARM64.Build.0 = Debug|ARM64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x64.ActiveCfg = Debug|x64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x64.Build.0 = Debug|x64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x86.ActiveCfg = Debug|Win32 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x86.Build.0 = Debug|Win32 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x64.ActiveCfg = Release|x64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x86.ActiveCfg = Release|Win32 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|ARM64.ActiveCfg = Release|ARM64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|ARM64.Build.0 = Release|ARM64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x64.ActiveCfg = Release|x64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x64.Build.0 = Release|x64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x86.ActiveCfg = Release|Win32 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x86.Build.0 = Release|Win32 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x64.ActiveCfg = Release|x64 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x86.ActiveCfg = Release|Win32 - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|ARM64.ActiveCfg = Debug|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|ARM64.Build.0 = Debug|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x64.ActiveCfg = Debug|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x64.Build.0 = Debug|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x86.ActiveCfg = Debug|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x86.Build.0 = Debug|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Fuzzing|x64.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Fuzzing|x86.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|ARM64.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|ARM64.Build.0 = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x64.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x64.Build.0 = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x86.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x86.Build.0 = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.ReleaseStatic|ARM64.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.ReleaseStatic|x64.ActiveCfg = Release|Any CPU - {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.ReleaseStatic|x86.ActiveCfg = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {6CB84692-5994-407D-B9BD-9216AF77FE83} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {6E36DDD7-1602-474E-B1D7-D0A7E1D5AD86} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {3C0269FA-E582-4CA7-9E33-3881A005CA0C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {3B8466CF-4FDD-4329-9C80-91321C4AAC99} = {EA8CD934-0702-4911-A2C5-A40600E616DE} - {1622DA16-914F-4F57-A259-D5169003CC8C} = {6D7776A8-42FE-46DD-B0F8-712F35EA0C79} - {3BAF989F-7F65-465B-ACE8-BAFE42D1017E} = {EA8CD934-0702-4911-A2C5-A40600E616DE} - {7D05F64D-CE5A-42AA-A2C1-E91458F061CF} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {952B513F-8A00-4D74-9271-925AFB3C6252} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {2ACDE176-F13F-42FA-8159-C34FA3D37837} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {1A47951F-5C7A-4D6D-BB5F-D77484437940} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {68808357-902B-406C-8C19-E8E26A69DE8A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {409CD681-22A4-469D-88AE-CB5E4836E07A} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {B0BBBD92-943B-408F-B2B2-DBBAB4A22D23} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {31ED69A8-5310-45A9-953F-56C351D2C3E1} = {60618CAC-2995-4DF9-9914-45C6FC02C995} - {8E43F982-40D5-4DF1-9044-C08047B5F43B} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {EE43C990-7789-4A60-B077-BF0ED3D093A1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {1F56BECB-D65D-4BBA-8788-6671B251392A} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {167F634B-A3AD-494E-8E67-B888103E35FF} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {C54F80ED-B736-49B0-9BD3-662F57024D01} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {272B2B0E-40D4-4F0F-B187-519A6EF89B10} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {5A52D9FC-0059-4A4A-8196-427A7AA0D1C5} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {76B26B2C-602A-4AD0-9736-4162D3FCA92A} = {1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798} - {EF18BED6-EBC0-45E9-8D61-4202528E8AAB} = {1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798} - {A0B4F808-B190-41C4-97CB-C8EA1932F84F} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {A33223D2-550B-4D99-A53D-488B1F68683E} = {60618CAC-2995-4DF9-9914-45C6FC02C995} - {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {F49C4C89-447E-4D15-B38B-5A8DCFB134AF} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} - {40D7CA7F-EB86-4345-9641-AD27180C559D} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {5421394F-5619-4E4B-8923-F3FB30D5EFAD} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} - {B978E358-D2BE-4FA7-A21A-6661F3744DD7} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - {3FF6C881-2548-486E-8D70-7555A90030F5} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} - EndGlobalSection - GlobalSection(SharedMSBuildProjectFiles) = preSolution - ManifestSchema\ManifestSchema.vcxitems*{1622da16-914f-4f57-a259-d5169003cc8c}*SharedItemsImports = 4 - WinGetSchemas\WinGetSchemas.vcxitems*{1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d}*SharedItemsImports = 4 - COMServer\COMServer.vcxitems*{1cc41a9a-ae66-459d-9210-1e572dd7be69}*SharedItemsImports = 4 - binver\binver.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 - CertificateResources\CertificateResources.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 - COMServer\COMServer.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 - ManifestSchema\ManifestSchema.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 - WinGetSchemas\WinGetSchemas.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 - COMServer\COMServer.vcxitems*{409cd681-22a4-469d-88ae-cb5e4836e07a}*SharedItemsImports = 9 - CertificateResources\CertificateResources.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 - COMServer\COMServer.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 - ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 - PureLib\PureLib.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 - binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 - CertificateResources\CertificateResources.vcxitems*{5eb88068-5fb9-4e69-89b2-72dbc5e068f9}*SharedItemsImports = 4 - binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 - ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 - CertificateResources\CertificateResources.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 - ManifestSchema\ManifestSchema.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 - WinGetSchemas\WinGetSchemas.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 - WinGetSchemas\WinGetSchemas.vcxitems*{952b513f-8a00-4d74-9271-925afb3c6252}*SharedItemsImports = 9 - PureLib\PureLib.vcxitems*{a33223d2-550b-4d99-a53d-488b1f68683e}*SharedItemsImports = 9 - CertificateResources\CertificateResources.vcxitems*{b0bbbd92-943b-408f-b2b2-dbbab4a22d23}*SharedItemsImports = 9 - binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 - ManifestSchema\ManifestSchema.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.14.36915.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppInstallerCLIPackage", "AppInstallerCLIPackage\AppInstallerCLIPackage.wapproj", "{6AA3791A-0713-4548-A357-87A323E7AC3A}" + ProjectSection(ProjectDependencies) = postProject + {0BA531C8-CF0C-405B-8221-0FE51BA529D1} = {0BA531C8-CF0C-405B-8221-0FE51BA529D1} + {1CC41A9A-AE66-459D-9210-1E572DD7BE69} = {1CC41A9A-AE66-459D-9210-1E572DD7BE69} + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} + {33745E4A-39E2-676F-7E23-50FB43848D25} = {33745E4A-39E2-676F-7E23-50FB43848D25} + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777} = {5B6F90DF-FD19-4BAE-83D9-24DAD128E777} + {6597EB04-D105-49A7-A5A3-D27FE1DF895E} = {6597EB04-D105-49A7-A5A3-D27FE1DF895E} + {CA460806-5E41-4E97-9A3D-1D74B433B663} = {CA460806-5E41-4E97-9A3D-1D74B433B663} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLI", "AppInstallerCLI\AppInstallerCLI.vcxproj", "{5B6F90DF-FD19-4BAE-83D9-24DAD128E777}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLICore", "AppInstallerCLICore\AppInstallerCLICore.vcxproj", "{1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}" + ProjectSection(ProjectDependencies) = postProject + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4} = {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCLITests", "AppInstallerCLITests\AppInstallerCLITests.vcxproj", "{89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}" + ProjectSection(ProjectDependencies) = postProject + {6AA3791A-0713-4548-A357-87A323E7AC3A} = {6AA3791A-0713-4548-A357-87A323E7AC3A} + {6CB84692-5994-407D-B9BD-9216AF77FE83} = {6CB84692-5994-407D-B9BD-9216AF77FE83} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "External - Do Not Modify", "External - Do Not Modify", "{60618CAC-2995-4DF9-9914-45C6FC02C995}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerRepositoryCore", "AppInstallerRepositoryCore\AppInstallerRepositoryCore.vcxproj", "{5EB88068-5FB9-4E69-89B2-72DBC5E068F9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Project", "Project", "{8D53D749-D51C-46F8-A162-9371AAA6C2E7}" + ProjectSection(SolutionItems) = preProject + ..\azure-pipelines.loc.yml = ..\azure-pipelines.loc.yml + ..\azure-pipelines.nuget.yml = ..\azure-pipelines.nuget.yml + ..\azure-pipelines.yml = ..\azure-pipelines.yml + ..\cgmanifest.json = ..\cgmanifest.json + Get-VcxprojNugetPackageVersions.ps1 = Get-VcxprojNugetPackageVersions.ps1 + ..\README.md = ..\README.md + ..\doc\ReleaseNotes.md = ..\doc\ReleaseNotes.md + Update-VcxprojNugetPackageVersions.ps1 = Update-VcxprojNugetPackageVersions.ps1 + ..\WinGetInProcCom.nuspec = ..\WinGetInProcCom.nuspec + ..\WinGetUtil.nuspec = ..\WinGetUtil.nuspec + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerCommonCore", "AppInstallerCommonCore\AppInstallerCommonCore.vcxproj", "{5890D6ED-7C3B-40F3-B436-B54F640D9E65}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetUtil", "WinGetUtil\WinGetUtil.vcxproj", "{FB313532-38B0-4676-9303-AB200AA13576}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerTestExeInstaller", "AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.vcxproj", "{6CB84692-5994-407D-B9BD-9216AF77FE83}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "binver", "binver\binver.vcxitems", "{6E36DDD7-1602-474E-B1D7-D0A7E1D5AD86}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AppInstallerCLIE2ETests", "AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.csproj", "{3C0269FA-E582-4CA7-9E33-3881A005CA0C}" + ProjectSection(ProjectDependencies) = postProject + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA} = {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA} + {6CB84692-5994-407D-B9BD-9216AF77FE83} = {6CB84692-5994-407D-B9BD-9216AF77FE83} + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8} = {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8} + EndProjectSection +EndProject +Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "AppInstallerTestMsixInstaller", "AppInstallerTestMsixInstaller\AppInstallerTestMsixInstaller.wapproj", "{3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}" +EndProject +Project("{54435603-DBB4-11D2-8724-00A0C9A8B90C}") = "AppInstallerTestMsiInstaller", "AppInstallerTestMsiInstaller\AppInstallerTestMsiInstaller.vdproj", "{C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tool", "Tool", "{EA8CD934-0702-4911-A2C5-A40600E616DE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IndexCreationTool", "IndexCreationTool\IndexCreationTool.csproj", "{3B8466CF-4FDD-4329-9C80-91321C4AAC99}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Fuzzing", "Fuzzing", "{6D7776A8-42FE-46DD-B0F8-712F35EA0C79}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetYamlFuzzing", "WinGetYamlFuzzing\WinGetYamlFuzzing.vcxproj", "{1622DA16-914F-4F57-A259-D5169003CC8C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LocalhostWebServer", "LocalhostWebServer\LocalhostWebServer.csproj", "{3BAF989F-7F65-465B-ACE8-BAFE42D1017E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ManifestSchema", "ManifestSchema\ManifestSchema.vcxitems", "{7D05F64D-CE5A-42AA-A2C1-E91458F061CF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetSchemas", "WinGetSchemas\WinGetSchemas.vcxitems", "{952B513F-8A00-4D74-9271-925AFB3C6252}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "spelling", "spelling", "{2ACDE176-F13F-42FA-8159-C34FA3D37837}" + ProjectSection(SolutionItems) = preProject + ..\.github\actions\spelling\allow.txt = ..\.github\actions\spelling\allow.txt + ..\.github\actions\spelling\excludes.txt = ..\.github\actions\spelling\excludes.txt + ..\.github\actions\spelling\expect.txt = ..\.github\actions\spelling\expect.txt + ..\.github\actions\spelling\patterns.txt = ..\.github\actions\spelling\patterns.txt + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "policy", "policy", "{1A47951F-5C7A-4D6D-BB5F-D77484437940}" + ProjectSection(SolutionItems) = preProject + ..\doc\admx\en-US\DesktopAppInstaller.adml = ..\doc\admx\en-US\DesktopAppInstaller.adml + ..\doc\admx\DesktopAppInstaller.admx = ..\doc\admx\DesktopAppInstaller.admx + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798}" + ProjectSection(SolutionItems) = preProject + CodeAnalysis.ruleset = CodeAnalysis.ruleset + Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + Directory.Solution.props = Directory.Solution.props + nuget.config = nuget.config + stylecop.json = stylecop.json + vcpkg.json = vcpkg.json + vcpkg.props = vcpkg.props + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment", "Microsoft.Management.Deployment\Microsoft.Management.Deployment.vcxproj", "{1CC41A9A-AE66-459D-9210-1E572DD7BE69}" + ProjectSection(ProjectDependencies) = postProject + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D} = {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D} + {5890D6ED-7C3B-40F3-B436-B54F640D9E65} = {5890D6ED-7C3B-40F3-B436-B54F640D9E65} + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9} = {5EB88068-5FB9-4E69-89B2-72DBC5E068F9} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WinGetServer", "WinGetServer\WinGetServer.vcxproj", "{2B00D362-AC92-41F3-A8D2-5B1599BDCA01}" + ProjectSection(ProjectDependencies) = postProject + {1CC41A9A-AE66-459D-9210-1E572DD7BE69} = {1CC41A9A-AE66-459D-9210-1E572DD7BE69} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinGetUtilInterop", "WinGetUtilInterop\WinGetUtilInterop.csproj", "{846FB88B-BF1B-4F33-9883-E589CEC99739}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinGetUtilInterop.UnitTests", "WinGetUtilInterop.UnitTests\WinGetUtilInterop.UnitTests.csproj", "{68808357-902B-406C-8C19-E8E26A69DE8A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "WindowsPackageManager", "WindowsPackageManager\WindowsPackageManager.vcxproj", "{2046B5AF-666D-4CE8-8D3E-C32C57908A56}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "COMServer", "COMServer\COMServer.vcxitems", "{409CD681-22A4-469D-88AE-CB5E4836E07A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment.InProc", "Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.InProc.vcxproj", "{9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CertificateResources", "CertificateResources\CertificateResources.vcxitems", "{B0BBBD92-943B-408F-B2B2-DBBAB4A22D23}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PowerShell", "PowerShell", "{7C218A3E-9BC8-48FF-B91B-BCACD828C0C9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Client.Cmdlets", "PowerShell\Microsoft.WinGet.Client.Cmdlets\Microsoft.WinGet.Client.Cmdlets.csproj", "{463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Deployment.Projection", "Microsoft.Management.Deployment.Projection\Microsoft.Management.Deployment.Projection.csproj", "{0B104762-5CD8-47EE-A904-71C1C3F84DCD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UndockedRegFreeWinRT", "Xlang\UndockedRegFreeWinRT\src\UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj", "{31ED69A8-5310-45A9-953F-56C351D2C3E1}" + ProjectSection(ProjectDependencies) = postProject + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "templates", "templates", "{8E43F982-40D5-4DF1-9044-C08047B5F43B}" + ProjectSection(SolutionItems) = preProject + ..\templates\e2e-setup.yml = ..\templates\e2e-setup.yml + ..\templates\e2e-test.template.yml = ..\templates\e2e-test.template.yml + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AppInstallerSharedLib", "AppInstallerSharedLib\AppInstallerSharedLib.vcxproj", "{F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Configuration", "Microsoft.Management.Configuration\Microsoft.Management.Configuration.vcxproj", "{CA460806-5E41-4E97-9A3D-1D74B433B663}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Configuration.Projection", "Microsoft.Management.Configuration.Projection\Microsoft.Management.Configuration.Projection.csproj", "{E8454BF1-2068-4513-A525-ABF55CC8742C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Configuration.UnitTests", "Microsoft.Management.Configuration.UnitTests\Microsoft.Management.Configuration.UnitTests.csproj", "{EE43C990-7789-4A60-B077-BF0ED3D093A1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Configuration.Processor", "Microsoft.Management.Configuration.Processor\Microsoft.Management.Configuration.Processor.csproj", "{71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationRemotingServer", "ConfigurationRemotingServer\ConfigurationRemotingServer.csproj", "{6597EB04-D105-49A7-A5A3-D27FE1DF895E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Client.Engine", "PowerShell\Microsoft.WinGet.Client.Engine\Microsoft.WinGet.Client.Engine.csproj", "{1F56BECB-D65D-4BBA-8788-6671B251392A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Configuration.Cmdlets", "PowerShell\Microsoft.WinGet.Configuration.Cmdlets\Microsoft.WinGet.Configuration.Cmdlets.csproj", "{167F634B-A3AD-494E-8E67-B888103E35FF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.Configuration.Engine", "PowerShell\Microsoft.WinGet.Configuration.Engine\Microsoft.WinGet.Configuration.Engine.csproj", "{C54F80ED-B736-49B0-9BD3-662F57024D01}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Configuration.OutOfProc", "Microsoft.Management.Configuration.OutOfProc\Microsoft.Management.Configuration.OutOfProc.vcxproj", "{2268D5AD-7F2A-485A-8C4B-C574497514C9}" + ProjectSection(ProjectDependencies) = postProject + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinGetSourceCreator", "WinGetSourceCreator\WinGetSourceCreator.csproj", "{52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.WinGet.SharedLib", "PowerShell\Microsoft.WinGet.SharedLib\Microsoft.WinGet.SharedLib.csproj", "{272B2B0E-40D4-4F0F-B187-519A6EF89B10}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5A52D9FC-0059-4A4A-8196-427A7AA0D1C5}" + ProjectSection(SolutionItems) = preProject + PowerShell\tests\Microsoft.WinGet.Client.Tests.ps1 = PowerShell\tests\Microsoft.WinGet.Client.Tests.ps1 + PowerShell\tests\Microsoft.WinGet.Configuration.Tests.ps1 = PowerShell\tests\Microsoft.WinGet.Configuration.Tests.ps1 + PowerShell\tests\Microsoft.WinGet.DSC.Tests.ps1 = PowerShell\tests\Microsoft.WinGet.DSC.Tests.ps1 + PowerShell\tests\RunTests.ps1 = PowerShell\tests\RunTests.ps1 + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgCustomTriplets", "VcpkgCustomTriplets", "{76B26B2C-602A-4AD0-9736-4162D3FCA92A}" + ProjectSection(SolutionItems) = preProject + VcpkgCustomTriplets\arm64-release-static.cmake = VcpkgCustomTriplets\arm64-release-static.cmake + VcpkgCustomTriplets\arm64-release.cmake = VcpkgCustomTriplets\arm64-release.cmake + VcpkgCustomTriplets\arm64.cmake = VcpkgCustomTriplets\arm64.cmake + VcpkgCustomTriplets\common.cmake = VcpkgCustomTriplets\common.cmake + VcpkgCustomTriplets\fuzzing.cmake = VcpkgCustomTriplets\fuzzing.cmake + VcpkgCustomTriplets\x64-fuzzing.cmake = VcpkgCustomTriplets\x64-fuzzing.cmake + VcpkgCustomTriplets\x64-release-static.cmake = VcpkgCustomTriplets\x64-release-static.cmake + VcpkgCustomTriplets\x64-release.cmake = VcpkgCustomTriplets\x64-release.cmake + VcpkgCustomTriplets\x64.cmake = VcpkgCustomTriplets\x64.cmake + VcpkgCustomTriplets\x86-fuzzing.cmake = VcpkgCustomTriplets\x86-fuzzing.cmake + VcpkgCustomTriplets\x86-release-static.cmake = VcpkgCustomTriplets\x86-release-static.cmake + VcpkgCustomTriplets\x86-release.cmake = VcpkgCustomTriplets\x86-release.cmake + VcpkgCustomTriplets\x86.cmake = VcpkgCustomTriplets\x86.cmake + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "VcpkgPortOverlay", "VcpkgPortOverlay", "{EF18BED6-EBC0-45E9-8D61-4202528E8AAB}" + ProjectSection(SolutionItems) = preProject + VcpkgPortOverlay\CreatePortOverlay.ps1 = VcpkgPortOverlay\CreatePortOverlay.ps1 + VcpkgPortOverlay\README.md = VcpkgPortOverlay\README.md + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.Management.Deployment.OutOfProc", "Microsoft.Management.Deployment.OutOfProc\Microsoft.Management.Deployment.OutOfProc.vcxproj", "{0BA531C8-CF0C-405B-8221-0FE51BA529D1}" + ProjectSection(ProjectDependencies) = postProject + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} = {2B00D362-AC92-41F3-A8D2-5B1599BDCA01} + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Management.Deployment.CsWinRTProjection", "Microsoft.Management.Deployment.CsWinRTProjection\Microsoft.Management.Deployment.CsWinRTProjection.csproj", "{9406322E-6272-487E-902A-9953889719EA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "targets", "targets", "{A0B4F808-B190-41C4-97CB-C8EA1932F84F}" + ProjectSection(SolutionItems) = preProject + targets\EmbeddedCsWinRT.targets = targets\EmbeddedCsWinRT.targets + targets\ReferenceEmbeddedCsWinRTProject.targets = targets\ReferenceEmbeddedCsWinRTProject.targets + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PureLib", "PureLib\PureLib.vcxitems", "{A33223D2-550B-4D99-A53D-488B1F68683E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinGetTestCommon", "WinGetTestCommon\WinGetTestCommon.csproj", "{7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{02EA681E-C7D8-13C7-8484-4AC65E1B71E8}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WinGetMCPServer", "WinGetMCPServer\WinGetMCPServer.csproj", "{33745E4A-39E2-676F-7E23-50FB43848D25}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{F49C4C89-447E-4D15-B38B-5A8DCFB134AF}" + ProjectSection(SolutionItems) = preProject + PowerShell\scripts\Execute-WinGetTests.ps1 = PowerShell\scripts\Execute-WinGetTests.ps1 + PowerShell\scripts\Initialize-LocalWinGetModules.ps1 = PowerShell\scripts\Initialize-LocalWinGetModules.ps1 + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ComInprocTestbed", "ComInprocTestbed\ComInprocTestbed.vcxproj", "{E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DSC", "DSC", "{40D7CA7F-EB86-4345-9641-AD27180C559D}" + ProjectSection(SolutionItems) = preProject + PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psd1 = PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psd1 + PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psm1 = PowerShell\Microsoft.WinGet.DSC\Microsoft.WinGet.DSC.psm1 + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.WinGet.UnitTests", "PowerShell\Microsoft.WinGet.UnitTests\Microsoft.WinGet.UnitTests.csproj", "{5421394F-5619-4E4B-8923-F3FB30D5EFAD}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "copilot", "copilot", "{B978E358-D2BE-4FA7-A21A-6661F3744DD7}" + ProjectSection(SolutionItems) = preProject + ..\.github\copilot-instructions.md = ..\.github\copilot-instructions.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "doc", "doc", "{3FF6C881-2548-486E-8D70-7555A90030F5}" + ProjectSection(SolutionItems) = preProject + ..\doc\windows\package-manager\winget\returnCodes.md = ..\doc\windows\package-manager\winget\returnCodes.md + ..\doc\Settings.md = ..\doc\Settings.md + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Fuzzing|ARM64 = Fuzzing|ARM64 + Fuzzing|x64 = Fuzzing|x64 + Fuzzing|x86 = Fuzzing|x86 + Release|ARM64 = Release|ARM64 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + ReleaseStatic|ARM64 = ReleaseStatic|ARM64 + ReleaseStatic|x64 = ReleaseStatic|x64 + ReleaseStatic|x86 = ReleaseStatic|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|ARM64.Build.0 = Debug|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x64.ActiveCfg = Debug|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x64.Build.0 = Debug|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x64.Deploy.0 = Debug|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x86.ActiveCfg = Debug|x86 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x86.Build.0 = Debug|x86 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Debug|x86.Deploy.0 = Debug|x86 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Fuzzing|x64.ActiveCfg = Release|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Fuzzing|x86.ActiveCfg = Release|x86 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|ARM64.ActiveCfg = Release|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|ARM64.Build.0 = Release|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|ARM64.Deploy.0 = Release|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x64.ActiveCfg = Release|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x64.Build.0 = Release|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x64.Deploy.0 = Release|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x86.ActiveCfg = Release|x86 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x86.Build.0 = Release|x86 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.Release|x86.Deploy.0 = Release|x86 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {6AA3791A-0713-4548-A357-87A323E7AC3A}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|ARM64.Build.0 = Debug|ARM64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x64.ActiveCfg = Debug|x64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x64.Build.0 = Debug|x64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x86.ActiveCfg = Debug|Win32 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Debug|x86.Build.0 = Debug|Win32 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Fuzzing|x64.ActiveCfg = Release|x64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Fuzzing|x86.ActiveCfg = Release|Win32 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|ARM64.ActiveCfg = Release|ARM64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|ARM64.Build.0 = Release|ARM64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x64.ActiveCfg = Release|x64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x64.Build.0 = Release|x64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x86.ActiveCfg = Release|Win32 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.Release|x86.Build.0 = Release|Win32 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {5B6F90DF-FD19-4BAE-83D9-24DAD128E777}.ReleaseStatic|x86.ActiveCfg = Release|Win32 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|ARM64.Build.0 = Debug|ARM64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x64.ActiveCfg = Debug|x64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x64.Build.0 = Debug|x64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x86.ActiveCfg = Debug|Win32 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Debug|x86.Build.0 = Debug|Win32 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Fuzzing|x64.ActiveCfg = Release|x64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Fuzzing|x86.ActiveCfg = Release|Win32 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|ARM64.ActiveCfg = Release|ARM64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|ARM64.Build.0 = Release|ARM64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x64.ActiveCfg = Release|x64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x64.Build.0 = Release|x64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x86.ActiveCfg = Release|Win32 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.Release|x86.Build.0 = Release|Win32 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {1C6E0108-2860-4B17-9F7E-FA5C6C1F3D3D}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|ARM64.Build.0 = Debug|ARM64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.ActiveCfg = Debug|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x64.Build.0 = Debug|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.ActiveCfg = Debug|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Debug|x86.Build.0 = Debug|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|ARM64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Fuzzing|x86.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM64.ActiveCfg = Release|ARM64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|ARM64.Build.0 = Release|ARM64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x64.Build.0 = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.ActiveCfg = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.Release|x86.Build.0 = Release|Win32 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230}.ReleaseStatic|x86.ActiveCfg = Release|Win32 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|ARM64.Build.0 = Debug|ARM64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x64.ActiveCfg = Debug|x64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x64.Build.0 = Debug|x64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x86.ActiveCfg = Debug|Win32 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Debug|x86.Build.0 = Debug|Win32 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Fuzzing|x64.ActiveCfg = Release|x64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Fuzzing|x86.ActiveCfg = Release|Win32 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|ARM64.ActiveCfg = Release|ARM64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|ARM64.Build.0 = Release|ARM64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x64.ActiveCfg = Release|x64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x64.Build.0 = Release|x64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x86.ActiveCfg = Release|Win32 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.Release|x86.Build.0 = Release|Win32 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {5EB88068-5FB9-4E69-89B2-72DBC5E068F9}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|ARM64.Build.0 = Debug|ARM64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x64.ActiveCfg = Debug|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x64.Build.0 = Debug|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x86.ActiveCfg = Debug|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Debug|x86.Build.0 = Debug|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|ARM64.ActiveCfg = Release|ARM64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|ARM64.Build.0 = Release|ARM64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x64.ActiveCfg = Release|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x64.Build.0 = Release|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x86.ActiveCfg = Release|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.Release|x86.Build.0 = Release|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {5890D6ED-7C3B-40F3-B436-B54F640D9E65}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {FB313532-38B0-4676-9303-AB200AA13576}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {FB313532-38B0-4676-9303-AB200AA13576}.Debug|ARM64.Build.0 = Debug|ARM64 + {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x64.ActiveCfg = Debug|x64 + {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x64.Build.0 = Debug|x64 + {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x86.ActiveCfg = Debug|Win32 + {FB313532-38B0-4676-9303-AB200AA13576}.Debug|x86.Build.0 = Debug|Win32 + {FB313532-38B0-4676-9303-AB200AA13576}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {FB313532-38B0-4676-9303-AB200AA13576}.Fuzzing|x64.ActiveCfg = Release|x64 + {FB313532-38B0-4676-9303-AB200AA13576}.Fuzzing|x86.ActiveCfg = Release|Win32 + {FB313532-38B0-4676-9303-AB200AA13576}.Release|ARM64.ActiveCfg = Release|ARM64 + {FB313532-38B0-4676-9303-AB200AA13576}.Release|ARM64.Build.0 = Release|ARM64 + {FB313532-38B0-4676-9303-AB200AA13576}.Release|x64.ActiveCfg = Release|x64 + {FB313532-38B0-4676-9303-AB200AA13576}.Release|x64.Build.0 = Release|x64 + {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.ActiveCfg = Release|Win32 + {FB313532-38B0-4676-9303-AB200AA13576}.Release|x86.Build.0 = Release|Win32 + {FB313532-38B0-4676-9303-AB200AA13576}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 + {FB313532-38B0-4676-9303-AB200AA13576}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {FB313532-38B0-4676-9303-AB200AA13576}.ReleaseStatic|x86.ActiveCfg = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|ARM64.Build.0 = Debug|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.ActiveCfg = Debug|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x64.Build.0 = Debug|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.ActiveCfg = Debug|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Debug|x86.Build.0 = Debug|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Fuzzing|x64.ActiveCfg = Release|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Fuzzing|x86.ActiveCfg = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.ActiveCfg = Release|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|ARM64.Build.0 = Release|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.ActiveCfg = Release|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x64.Build.0 = Release|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.ActiveCfg = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.Release|x86.Build.0 = Release|Win32 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {6CB84692-5994-407D-B9BD-9216AF77FE83}.ReleaseStatic|x86.ActiveCfg = Release|Win32 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|ARM64.ActiveCfg = Debug|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x64.ActiveCfg = Debug|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x64.Build.0 = Debug|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x86.ActiveCfg = Debug|x86 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Debug|x86.Build.0 = Debug|x86 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Fuzzing|ARM64.ActiveCfg = Release|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Fuzzing|x64.ActiveCfg = Release|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Fuzzing|x86.ActiveCfg = Release|x86 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|ARM64.ActiveCfg = Release|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x64.ActiveCfg = Release|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x64.Build.0 = Release|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x86.ActiveCfg = Release|x86 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.Release|x86.Build.0 = Release|x86 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.ReleaseStatic|ARM64.ActiveCfg = Release|x86 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {3C0269FA-E582-4CA7-9E33-3881A005CA0C}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.Build.0 = Debug|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|ARM64.Deploy.0 = Debug|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.ActiveCfg = Debug|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.Build.0 = Debug|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x64.Deploy.0 = Debug|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.ActiveCfg = Debug|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.Build.0 = Debug|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Debug|x86.Deploy.0 = Debug|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Fuzzing|x64.ActiveCfg = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Fuzzing|x86.ActiveCfg = Release|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.ActiveCfg = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.Build.0 = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|ARM64.Deploy.0 = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.ActiveCfg = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.Build.0 = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x64.Deploy.0 = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.ActiveCfg = Release|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.Build.0 = Release|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.Release|x86.Deploy.0 = Release|x86 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|ARM64.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x64.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Debug|x86.ActiveCfg = Debug + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Fuzzing|x86.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.Release|x86.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|ARM64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x64.ActiveCfg = Release + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8}.ReleaseStatic|x86.ActiveCfg = Release + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|ARM64.ActiveCfg = Debug|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.ActiveCfg = Debug|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x64.Build.0 = Debug|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x86.ActiveCfg = Debug|x86 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Debug|x86.Build.0 = Debug|x86 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Fuzzing|ARM64.ActiveCfg = Release|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Fuzzing|x64.ActiveCfg = Release|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Fuzzing|x86.ActiveCfg = Release|x86 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|ARM64.ActiveCfg = Release|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x64.ActiveCfg = Release|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x64.Build.0 = Release|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.ActiveCfg = Release|x86 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.Release|x86.Build.0 = Release|x86 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {3B8466CF-4FDD-4329-9C80-91321C4AAC99}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|ARM64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|x64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Debug|x86.ActiveCfg = Fuzzing|Win32 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|ARM64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Release|ARM64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Release|x64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.Release|x86.ActiveCfg = Fuzzing|Win32 + {1622DA16-914F-4F57-A259-D5169003CC8C}.ReleaseStatic|ARM64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.ReleaseStatic|x64.ActiveCfg = Fuzzing|x64 + {1622DA16-914F-4F57-A259-D5169003CC8C}.ReleaseStatic|x86.ActiveCfg = Fuzzing|Win32 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|ARM64.ActiveCfg = Debug|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x64.ActiveCfg = Debug|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x64.Build.0 = Debug|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x86.ActiveCfg = Debug|x86 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Debug|x86.Build.0 = Debug|x86 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Fuzzing|ARM64.ActiveCfg = Release|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Fuzzing|x64.ActiveCfg = Release|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Fuzzing|x86.ActiveCfg = Release|x86 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|ARM64.ActiveCfg = Release|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x64.ActiveCfg = Release|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x64.Build.0 = Release|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x86.ActiveCfg = Release|x86 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.Release|x86.Build.0 = Release|x86 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|ARM64.Build.0 = Debug|ARM64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x64.ActiveCfg = Debug|x64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x64.Build.0 = Debug|x64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x86.ActiveCfg = Debug|Win32 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Debug|x86.Build.0 = Debug|Win32 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Fuzzing|x64.ActiveCfg = Release|x64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Fuzzing|x86.ActiveCfg = Release|Win32 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|ARM64.ActiveCfg = Release|ARM64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|ARM64.Build.0 = Release|ARM64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x64.ActiveCfg = Release|x64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x64.Build.0 = Release|x64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x86.ActiveCfg = Release|Win32 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.Release|x86.Build.0 = Release|Win32 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {1CC41A9A-AE66-459D-9210-1E572DD7BE69}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|ARM64.Build.0 = Debug|ARM64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x64.ActiveCfg = Debug|x64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x64.Build.0 = Debug|x64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x86.ActiveCfg = Debug|Win32 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Debug|x86.Build.0 = Debug|Win32 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Fuzzing|x64.ActiveCfg = Release|x64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Fuzzing|x86.ActiveCfg = Release|Win32 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|ARM64.ActiveCfg = Release|ARM64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|ARM64.Build.0 = Release|ARM64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x64.ActiveCfg = Release|x64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x64.Build.0 = Release|x64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x86.ActiveCfg = Release|Win32 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.Release|x86.Build.0 = Release|Win32 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {2B00D362-AC92-41F3-A8D2-5B1599BDCA01}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|ARM64.Build.0 = Debug|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x64.ActiveCfg = Debug|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x64.Build.0 = Debug|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x86.ActiveCfg = Debug|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Debug|x86.Build.0 = Debug|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|ARM64.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|ARM64.Build.0 = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x64.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x64.Build.0 = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x86.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.Release|x86.Build.0 = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.ReleaseStatic|ARM64.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.ReleaseStatic|x64.ActiveCfg = Release|Any CPU + {846FB88B-BF1B-4F33-9883-E589CEC99739}.ReleaseStatic|x86.ActiveCfg = Release|Any CPU + {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|ARM64.ActiveCfg = Debug|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x64.ActiveCfg = Debug|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x64.Build.0 = Debug|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x86.ActiveCfg = Debug|x86 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Debug|x86.Build.0 = Debug|x86 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Fuzzing|ARM64.ActiveCfg = Release|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Fuzzing|x64.ActiveCfg = Release|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Fuzzing|x86.ActiveCfg = Release|x86 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|ARM64.ActiveCfg = Release|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x64.ActiveCfg = Release|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x64.Build.0 = Release|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x86.ActiveCfg = Release|x86 + {68808357-902B-406C-8C19-E8E26A69DE8A}.Release|x86.Build.0 = Release|x86 + {68808357-902B-406C-8C19-E8E26A69DE8A}.ReleaseStatic|ARM64.ActiveCfg = Release|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {68808357-902B-406C-8C19-E8E26A69DE8A}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|ARM64.Build.0 = Debug|ARM64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x64.ActiveCfg = Debug|x64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x64.Build.0 = Debug|x64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x86.ActiveCfg = Debug|Win32 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Debug|x86.Build.0 = Debug|Win32 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Fuzzing|x64.ActiveCfg = Release|x64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Fuzzing|x86.ActiveCfg = Release|Win32 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|ARM64.ActiveCfg = Release|ARM64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|ARM64.Build.0 = Release|ARM64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x64.ActiveCfg = Release|x64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x64.Build.0 = Release|x64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x86.ActiveCfg = Release|Win32 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.Release|x86.Build.0 = Release|Win32 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|ARM64.Build.0 = Debug|ARM64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x64.ActiveCfg = Debug|x64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x64.Build.0 = Debug|x64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x86.ActiveCfg = Debug|Win32 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Debug|x86.Build.0 = Debug|Win32 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Fuzzing|x64.ActiveCfg = Release|x64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Fuzzing|x86.ActiveCfg = Release|Win32 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|ARM64.ActiveCfg = Release|ARM64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x64.ActiveCfg = Release|x64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x64.Build.0 = Release|x64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x86.ActiveCfg = Release|Win32 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.Release|x86.Build.0 = Release|Win32 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {9AC3C6A4-1875-4D3E-BF9C-C31E81EFF6B4}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|ARM64.Build.0 = Debug|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x64.ActiveCfg = Debug|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x64.Build.0 = Debug|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x86.ActiveCfg = Debug|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Debug|x86.Build.0 = Debug|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM64.ActiveCfg = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|ARM64.Build.0 = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x64.ActiveCfg = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x64.Build.0 = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x86.ActiveCfg = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.Release|x86.Build.0 = Release|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|ARM64.ActiveCfg = Debug|arm64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x64.ActiveCfg = Debug|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x64.Build.0 = Debug|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x86.ActiveCfg = Debug|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Debug|x86.Build.0 = Debug|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|ARM64.ActiveCfg = Release|arm64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|x64.ActiveCfg = Release|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Fuzzing|x86.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|ARM64.ActiveCfg = Release|arm64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x64.ActiveCfg = Release|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x64.Build.0 = Release|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x86.ActiveCfg = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.Release|x86.Build.0 = Release|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|arm64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|arm64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|x86 + {0B104762-5CD8-47EE-A904-71C1C3F84DCD}.ReleaseStatic|x86.Build.0 = ReleaseStatic|x86 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|ARM64.Build.0 = Debug|ARM64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x64.ActiveCfg = Debug|x64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x64.Build.0 = Debug|x64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x86.ActiveCfg = Debug|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Debug|x86.Build.0 = Debug|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Fuzzing|x64.ActiveCfg = Release|x64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Fuzzing|x86.ActiveCfg = Release|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|ARM64.ActiveCfg = Release|ARM64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|ARM64.Build.0 = Release|ARM64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x64.ActiveCfg = Release|x64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x64.Build.0 = Release|x64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.ActiveCfg = Release|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.Release|x86.Build.0 = Release|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {31ED69A8-5310-45A9-953F-56C351D2C3E1}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|ARM64.Build.0 = Debug|ARM64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x64.ActiveCfg = Debug|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x64.Build.0 = Debug|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x86.ActiveCfg = Debug|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Debug|x86.Build.0 = Debug|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|ARM64.ActiveCfg = Fuzzing|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x64.ActiveCfg = Fuzzing|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x64.Build.0 = Fuzzing|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x86.ActiveCfg = Fuzzing|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Fuzzing|x86.Build.0 = Fuzzing|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|ARM64.ActiveCfg = Release|ARM64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|ARM64.Build.0 = Release|ARM64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x64.ActiveCfg = Release|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x64.Build.0 = Release|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x86.ActiveCfg = Release|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.Release|x86.Build.0 = Release|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|ARM64.Build.0 = Debug|ARM64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x64.ActiveCfg = Debug|x64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x64.Build.0 = Debug|x64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x86.ActiveCfg = Debug|Win32 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Debug|x86.Build.0 = Debug|Win32 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Fuzzing|x64.ActiveCfg = Release|x64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Fuzzing|x86.ActiveCfg = Release|Win32 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|ARM64.ActiveCfg = Release|ARM64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|ARM64.Build.0 = Release|ARM64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x64.ActiveCfg = Release|x64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x64.Build.0 = Release|x64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x86.ActiveCfg = Release|Win32 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.Release|x86.Build.0 = Release|Win32 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {CA460806-5E41-4E97-9A3D-1D74B433B663}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|ARM64.Build.0 = Debug|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x64.ActiveCfg = Debug|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x64.Build.0 = Debug|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x86.ActiveCfg = Debug|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Debug|x86.Build.0 = Debug|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|ARM64.ActiveCfg = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|ARM64.Build.0 = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x64.ActiveCfg = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x64.Build.0 = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x86.ActiveCfg = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.Release|x86.Build.0 = Release|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {E8454BF1-2068-4513-A525-ABF55CC8742C}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|ARM64.ActiveCfg = Debug|arm64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|ARM64.Build.0 = Debug|arm64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x64.ActiveCfg = Debug|x64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x64.Build.0 = Debug|x64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x86.ActiveCfg = Debug|x86 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Debug|x86.Build.0 = Debug|x86 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Fuzzing|ARM64.ActiveCfg = Release|arm64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Fuzzing|x64.ActiveCfg = Release|x64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Fuzzing|x86.ActiveCfg = Release|x86 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|ARM64.ActiveCfg = Release|arm64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|ARM64.Build.0 = Release|arm64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x64.ActiveCfg = Release|x64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x64.Build.0 = Release|x64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x86.ActiveCfg = Release|x86 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.Release|x86.Build.0 = Release|x86 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {EE43C990-7789-4A60-B077-BF0ED3D093A1}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|ARM64.Build.0 = Debug|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x64.ActiveCfg = Debug|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x64.Build.0 = Debug|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x86.ActiveCfg = Debug|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Debug|x86.Build.0 = Debug|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|ARM64.ActiveCfg = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|ARM64.Build.0 = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x64.ActiveCfg = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x64.Build.0 = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x86.ActiveCfg = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.Release|x86.Build.0 = Release|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {71FA29AA-9035-468B-A11D-0F0B0F5D5AF4}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|ARM64.ActiveCfg = Debug|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|ARM64.Build.0 = Debug|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x64.ActiveCfg = Debug|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x64.Build.0 = Debug|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x86.ActiveCfg = Debug|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Debug|x86.Build.0 = Debug|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|ARM64.ActiveCfg = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x64.ActiveCfg = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Fuzzing|x86.ActiveCfg = Release|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|ARM64.ActiveCfg = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|ARM64.Build.0 = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x64.ActiveCfg = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x64.Build.0 = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x86.ActiveCfg = Release|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.Release|x86.Build.0 = Release|x86 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {6597EB04-D105-49A7-A5A3-D27FE1DF895E}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|ARM64.Build.0 = Debug|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x64.Build.0 = Debug|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Debug|x86.Build.0 = Debug|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|ARM64.ActiveCfg = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|ARM64.Build.0 = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x64.ActiveCfg = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x64.Build.0 = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x86.ActiveCfg = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.Release|x86.Build.0 = Release|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {1F56BECB-D65D-4BBA-8788-6671B251392A}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|ARM64.Build.0 = Debug|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x64.ActiveCfg = Debug|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x64.Build.0 = Debug|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x86.ActiveCfg = Debug|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Debug|x86.Build.0 = Debug|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|ARM64.ActiveCfg = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|ARM64.Build.0 = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x64.ActiveCfg = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x64.Build.0 = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x86.ActiveCfg = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.Release|x86.Build.0 = Release|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {167F634B-A3AD-494E-8E67-B888103E35FF}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|ARM64.Build.0 = Debug|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x64.ActiveCfg = Debug|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x64.Build.0 = Debug|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x86.ActiveCfg = Debug|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Debug|x86.Build.0 = Debug|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|ARM64.ActiveCfg = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|ARM64.Build.0 = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x64.ActiveCfg = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x64.Build.0 = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x86.ActiveCfg = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.Release|x86.Build.0 = Release|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {C54F80ED-B736-49B0-9BD3-662F57024D01}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|ARM64.Build.0 = Debug|ARM64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x64.ActiveCfg = Debug|x64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x64.Build.0 = Debug|x64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x86.ActiveCfg = Debug|Win32 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Debug|x86.Build.0 = Debug|Win32 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Fuzzing|x64.ActiveCfg = Release|x64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Fuzzing|x86.ActiveCfg = Release|Win32 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|ARM64.ActiveCfg = Release|ARM64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|ARM64.Build.0 = Release|ARM64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x64.ActiveCfg = Release|x64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x64.Build.0 = Release|x64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x86.ActiveCfg = Release|Win32 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.Release|x86.Build.0 = Release|Win32 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {2268D5AD-7F2A-485A-8C4B-C574497514C9}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|ARM64.Build.0 = Debug|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x64.ActiveCfg = Debug|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x64.Build.0 = Debug|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x86.ActiveCfg = Debug|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Debug|x86.Build.0 = Debug|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|ARM64.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|ARM64.Build.0 = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x64.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x64.Build.0 = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x86.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.Release|x86.Build.0 = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.ReleaseStatic|ARM64.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.ReleaseStatic|x64.ActiveCfg = Release|Any CPU + {52EC37D6-088C-40D3-AD0B-BDE8F8DAF9EB}.ReleaseStatic|x86.ActiveCfg = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|ARM64.Build.0 = Debug|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x64.ActiveCfg = Debug|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x64.Build.0 = Debug|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x86.ActiveCfg = Debug|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Debug|x86.Build.0 = Debug|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|ARM64.ActiveCfg = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|ARM64.Build.0 = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x64.ActiveCfg = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x64.Build.0 = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x86.ActiveCfg = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.Release|x86.Build.0 = Release|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {272B2B0E-40D4-4F0F-B187-519A6EF89B10}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|ARM64.Build.0 = Debug|ARM64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x64.ActiveCfg = Debug|x64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x64.Build.0 = Debug|x64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x86.ActiveCfg = Debug|Win32 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Debug|x86.Build.0 = Debug|Win32 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Fuzzing|x64.ActiveCfg = Release|x64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Fuzzing|x86.ActiveCfg = Release|Win32 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|ARM64.ActiveCfg = Release|ARM64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|ARM64.Build.0 = Release|ARM64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x64.ActiveCfg = Release|x64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x64.Build.0 = Release|x64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x86.ActiveCfg = Release|Win32 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.Release|x86.Build.0 = Release|Win32 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|ARM64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|ARM64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|x64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x64.Build.0 = ReleaseStatic|x64 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Win32 + {0BA531C8-CF0C-405B-8221-0FE51BA529D1}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Win32 + {9406322E-6272-487E-902A-9953889719EA}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Debug|ARM64.Build.0 = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Debug|x64.ActiveCfg = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Debug|x64.Build.0 = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Debug|x86.ActiveCfg = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Debug|x86.Build.0 = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Fuzzing|ARM64.ActiveCfg = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Fuzzing|x64.ActiveCfg = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Fuzzing|x86.ActiveCfg = Debug|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Release|ARM64.ActiveCfg = Release|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Release|ARM64.Build.0 = Release|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Release|x64.ActiveCfg = Release|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Release|x64.Build.0 = Release|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Release|x86.ActiveCfg = Release|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.Release|x86.Build.0 = Release|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|ARM64.ActiveCfg = ReleaseStatic|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|ARM64.Build.0 = ReleaseStatic|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x64.ActiveCfg = ReleaseStatic|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU + {9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|ARM64.ActiveCfg = Debug|arm64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|ARM64.Build.0 = Debug|arm64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x64.ActiveCfg = Debug|x64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x64.Build.0 = Debug|x64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x86.ActiveCfg = Debug|x86 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|x86.Build.0 = Debug|x86 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Fuzzing|ARM64.ActiveCfg = Release|arm64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Fuzzing|x64.ActiveCfg = Release|x64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Fuzzing|x86.ActiveCfg = Release|x86 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|ARM64.ActiveCfg = Release|arm64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|ARM64.Build.0 = Release|arm64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x64.ActiveCfg = Release|x64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x64.Build.0 = Release|x64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x86.ActiveCfg = Release|x86 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Release|x86.Build.0 = Release|x86 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|ARM64.ActiveCfg = Debug|arm64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|ARM64.Build.0 = Debug|arm64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x64.ActiveCfg = Debug|x64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x64.Build.0 = Debug|x64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x86.ActiveCfg = Debug|x86 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Debug|x86.Build.0 = Debug|x86 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Fuzzing|ARM64.ActiveCfg = Release|arm64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Fuzzing|x64.ActiveCfg = Release|x64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Fuzzing|x86.ActiveCfg = Release|x86 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|ARM64.ActiveCfg = Release|arm64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|ARM64.Build.0 = Release|arm64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x64.ActiveCfg = Release|x64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x64.Build.0 = Release|x64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x86.ActiveCfg = Release|x86 + {33745E4A-39E2-676F-7E23-50FB43848D25}.Release|x86.Build.0 = Release|x86 + {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|ARM64.ActiveCfg = Release|arm64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|ARM64.Build.0 = Release|arm64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x64.Build.0 = Release|x64 + {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x86.ActiveCfg = Release|x86 + {33745E4A-39E2-676F-7E23-50FB43848D25}.ReleaseStatic|x86.Build.0 = Release|x86 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|ARM64.Build.0 = Debug|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x64.ActiveCfg = Debug|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x64.Build.0 = Debug|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x86.ActiveCfg = Debug|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Debug|x86.Build.0 = Debug|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|ARM64.ActiveCfg = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x64.ActiveCfg = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Fuzzing|x86.ActiveCfg = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|ARM64.ActiveCfg = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|ARM64.Build.0 = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x64.ActiveCfg = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x64.Build.0 = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x86.ActiveCfg = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.Release|x86.Build.0 = Release|Win32 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|ARM64.ActiveCfg = Release|ARM64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x64.ActiveCfg = Release|x64 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94}.ReleaseStatic|x86.ActiveCfg = Release|Win32 + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|ARM64.Build.0 = Debug|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x64.ActiveCfg = Debug|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x64.Build.0 = Debug|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x86.ActiveCfg = Debug|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Debug|x86.Build.0 = Debug|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Fuzzing|ARM64.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Fuzzing|x64.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Fuzzing|x86.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|ARM64.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|ARM64.Build.0 = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x64.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x64.Build.0 = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x86.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.Release|x86.Build.0 = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.ReleaseStatic|ARM64.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.ReleaseStatic|x64.ActiveCfg = Release|Any CPU + {5421394F-5619-4E4B-8923-F3FB30D5EFAD}.ReleaseStatic|x86.ActiveCfg = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {89B1AAB4-2BBC-4B65-9ED7-A01D5CF88230} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {6CB84692-5994-407D-B9BD-9216AF77FE83} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {6E36DDD7-1602-474E-B1D7-D0A7E1D5AD86} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {3C0269FA-E582-4CA7-9E33-3881A005CA0C} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {3E2CBA31-CEBA-4D63-BF52-49C0718E19EA} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {C1624B2F-2BF6-4E28-92FA-1BF85C6B62A8} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {3B8466CF-4FDD-4329-9C80-91321C4AAC99} = {EA8CD934-0702-4911-A2C5-A40600E616DE} + {1622DA16-914F-4F57-A259-D5169003CC8C} = {6D7776A8-42FE-46DD-B0F8-712F35EA0C79} + {3BAF989F-7F65-465B-ACE8-BAFE42D1017E} = {EA8CD934-0702-4911-A2C5-A40600E616DE} + {7D05F64D-CE5A-42AA-A2C1-E91458F061CF} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {952B513F-8A00-4D74-9271-925AFB3C6252} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {2ACDE176-F13F-42FA-8159-C34FA3D37837} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {1A47951F-5C7A-4D6D-BB5F-D77484437940} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {68808357-902B-406C-8C19-E8E26A69DE8A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {409CD681-22A4-469D-88AE-CB5E4836E07A} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {B0BBBD92-943B-408F-B2B2-DBBAB4A22D23} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {463C0EF3-DF38-4C3D-8E7E-D4901E0CDC6C} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {31ED69A8-5310-45A9-953F-56C351D2C3E1} = {60618CAC-2995-4DF9-9914-45C6FC02C995} + {8E43F982-40D5-4DF1-9044-C08047B5F43B} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {EE43C990-7789-4A60-B077-BF0ED3D093A1} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {1F56BECB-D65D-4BBA-8788-6671B251392A} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {167F634B-A3AD-494E-8E67-B888103E35FF} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {C54F80ED-B736-49B0-9BD3-662F57024D01} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {272B2B0E-40D4-4F0F-B187-519A6EF89B10} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {5A52D9FC-0059-4A4A-8196-427A7AA0D1C5} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {76B26B2C-602A-4AD0-9736-4162D3FCA92A} = {1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798} + {EF18BED6-EBC0-45E9-8D61-4202528E8AAB} = {1A5D7A7D-5CB2-47D5-B40D-4E61CAEDC798} + {A0B4F808-B190-41C4-97CB-C8EA1932F84F} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {A33223D2-550B-4D99-A53D-488B1F68683E} = {60618CAC-2995-4DF9-9914-45C6FC02C995} + {7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {F49C4C89-447E-4D15-B38B-5A8DCFB134AF} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {40D7CA7F-EB86-4345-9641-AD27180C559D} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {5421394F-5619-4E4B-8923-F3FB30D5EFAD} = {7C218A3E-9BC8-48FF-B91B-BCACD828C0C9} + {B978E358-D2BE-4FA7-A21A-6661F3744DD7} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + {3FF6C881-2548-486E-8D70-7555A90030F5} = {8D53D749-D51C-46F8-A162-9371AAA6C2E7} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B6FDB70C-A751-422C-ACD1-E35419495857} + EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + ManifestSchema\ManifestSchema.vcxitems*{1622da16-914f-4f57-a259-d5169003cc8c}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d}*SharedItemsImports = 4 + COMServer\COMServer.vcxitems*{1cc41a9a-ae66-459d-9210-1e572dd7be69}*SharedItemsImports = 4 + binver\binver.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 + CertificateResources\CertificateResources.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 + COMServer\COMServer.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 + ManifestSchema\ManifestSchema.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{2046b5af-666d-4ce8-8d3e-c32c57908a56}*SharedItemsImports = 4 + COMServer\COMServer.vcxitems*{409cd681-22a4-469d-88ae-cb5e4836e07a}*SharedItemsImports = 9 + CertificateResources\CertificateResources.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 + COMServer\COMServer.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 + ManifestSchema\ManifestSchema.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 + PureLib\PureLib.vcxitems*{5890d6ed-7c3b-40f3-b436-b54f640d9e65}*SharedItemsImports = 4 + binver\binver.vcxitems*{5b6f90df-fd19-4bae-83d9-24dad128e777}*SharedItemsImports = 4 + CertificateResources\CertificateResources.vcxitems*{5eb88068-5fb9-4e69-89b2-72dbc5e068f9}*SharedItemsImports = 4 + binver\binver.vcxitems*{6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86}*SharedItemsImports = 9 + ManifestSchema\ManifestSchema.vcxitems*{7d05f64d-ce5a-42aa-a2c1-e91458f061cf}*SharedItemsImports = 9 + CertificateResources\CertificateResources.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 + ManifestSchema\ManifestSchema.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{89b1aab4-2bbc-4b65-9ed7-a01d5cf88230}*SharedItemsImports = 4 + WinGetSchemas\WinGetSchemas.vcxitems*{952b513f-8a00-4d74-9271-925afb3c6252}*SharedItemsImports = 9 + PureLib\PureLib.vcxitems*{a33223d2-550b-4d99-a53d-488b1f68683e}*SharedItemsImports = 9 + CertificateResources\CertificateResources.vcxitems*{b0bbbd92-943b-408f-b2b2-dbbab4a22d23}*SharedItemsImports = 9 + binver\binver.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 + ManifestSchema\ManifestSchema.vcxitems*{fb313532-38b0-4676-9303-ab200aa13576}*SharedItemsImports = 4 + EndGlobalSection +EndGlobal diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj b/src/AppInstallerCLI/AppInstallerCLI.vcxproj index 8789427953..d818f0e343 100644 --- a/src/AppInstallerCLI/AppInstallerCLI.vcxproj +++ b/src/AppInstallerCLI/AppInstallerCLI.vcxproj @@ -1,259 +1,259 @@ - - - - - true - true - true - 15.0 - {5b6f90df-fd19-4bae-83d9-24dad128e777} - Win32Proj - AppInstallerCLI - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - Application - - - true - true - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - winget - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - winget - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - winget - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - winget - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - winget - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - winget - - - - - NotUsing - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) - true - true - false - false - stdcpp17 - stdcpp17 - true - true - true - true - false - false - - - Console - false - %(AdditionalDependencies) - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - WIN32;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) - true - false - stdcpp17 - true - true - false - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - true - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - false - false - false - - - Console - true - true - false - %(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - - - - - - - - {2046b5af-666d-4ce8-8d3e-c32c57908a56} - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + true + true + true + 15.0 + {5b6f90df-fd19-4bae-83d9-24dad128e777} + Win32Proj + AppInstallerCLI + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + Application + + + true + true + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + winget + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + winget + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + winget + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + winget + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + winget + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + winget + + + + + NotUsing + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) + true + true + false + false + stdcpp17 + stdcpp17 + true + true + true + true + false + false + + + Console + false + %(AdditionalDependencies) + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + WIN32;%(PreprocessorDefinitions) + $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + true + false + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + true + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\WindowsPackageManager\;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + false + false + false + + + Console + true + true + false + %(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + + + + + + + + {2046b5af-666d-4ce8-8d3e-c32c57908a56} + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + diff --git a/src/AppInstallerCLI/AppInstallerCLI.vcxproj.filters b/src/AppInstallerCLI/AppInstallerCLI.vcxproj.filters index 9afaafe5a0..8546df143d 100644 --- a/src/AppInstallerCLI/AppInstallerCLI.vcxproj.filters +++ b/src/AppInstallerCLI/AppInstallerCLI.vcxproj.filters @@ -1,26 +1,26 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + + \ No newline at end of file diff --git a/src/AppInstallerCLI/packages.config b/src/AppInstallerCLI/packages.config index f229371b33..f32f48b009 100644 --- a/src/AppInstallerCLI/packages.config +++ b/src/AppInstallerCLI/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj index 19a4f62cc3..9d92771cc6 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj @@ -1,494 +1,494 @@ - - - - - true - true - true - 15.0 - {1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d} - Win32Proj - AppInstallerCLICore - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - StaticLibrary - - - true - true - - - false - true - false - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - 5321;%(DisableSpecificWarnings) - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - - - false - Windows - Windows - - - - - WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - false - - - Windows - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {5890d6ed-7c3b-40f3-b436-b54f640d9e65} - - - {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} - - - {ca460806-5e41-4e97-9a3d-1d74b433b663} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - + + + + + true + true + true + 15.0 + {1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d} + Win32Proj + AppInstallerCLICore + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + StaticLibrary + + + true + true + + + false + true + false + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + 5321;%(DisableSpecificWarnings) + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + + + false + Windows + Windows + + + + + WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + false + + + Windows + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {5890d6ed-7c3b-40f3-b436-b54f640d9e65} + + + {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + + {ca460806-5e41-4e97-9a3d-1d74b433b663} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + diff --git a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters index f6e9ca7dd2..753d2451d8 100644 --- a/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters +++ b/src/AppInstallerCLICore/AppInstallerCLICore.vcxproj.filters @@ -1,579 +1,579 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {4b0dcf8b-b4a1-47e5-9c28-e8a3440178e6} - - - {00cc3138-2893-4fc4-8595-d3cf9d26be1c} - - - {fdeb940e-93e2-4bb0-a59c-1e2d1c0588d1} - - - {bd9d8dfe-7a37-4a53-b4e7-ddbd9694c0ab} - - - - - Header Files - - - Commands - - - Commands - - - Header Files - - - Header Files - - - Header Files - - - Public - - - Workflows - - - Workflows - - - Workflows - - - Workflows - - - Commands - - - Commands - - - Commands - - - Workflows - - - Header Files - - - Header Files - - - Commands - - - Header Files - - - Header Files - - - Workflows - - - Commands - - - Header Files - - - Header Files - - - Header Files - - - Commands - - - Commands - - - Commands - - - Commands - - - Header Files - - - Workflows - - - Header Files - - - Workflows - - - Workflows - - - Commands - - - Commands - - - Commands - - - Workflows - - - Commands - - - Header Files - - - Workflows - - - Commands - - - Header Files - - - Header Files - - - Header Files - - - Workflows - - - Commands - - - Workflows - - - Workflows - - - Workflows - - - Workflows - - - Public - - - Workflows - - - Workflows - - - Workflows - - - Header Files - - - Commands - - - Workflows - - - Workflows - - - Header Files - - - Workflows - - - Public - - - Commands - - - Commands - - - Commands - - - Header Files - - - Commands - - - Commands - - - Workflows - - - Header Files - - - Commands - - - Workflows - - - Header Files - - - Header Files - - - Commands - - - Workflows - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Public - - - Commands - - - - - Source Files - - - Commands - - - Commands - - - Source Files - - - Source Files - - - Workflows - - - Workflows - - - Workflows - - - Commands - - - Commands - - - Commands - - - Workflows - - - Workflows - - - Source Files - - - Commands - - - Source Files - - - Source Files - - - Workflows - - - Source Files - - - Commands - - - Source Files - - - Source Files - - - Commands - - - Commands - - - Commands - - - Commands - - - Source Files - - - Workflows - - - Source Files - - - Workflows - - - Workflows - - - Commands - - - Commands - - - Commands - - - Workflows - - - Commands - - - Source Files - - - Workflows - - - Commands - - - Source Files - - - Source Files - - - Commands - - - Workflows - - - Workflows - - - Workflows - - - Workflows - - - Workflows - - - Workflows - - - Workflows - - - Workflows - - - Source Files - - - Workflows - - - Commands - - - Workflows - - - Source Files - - - Workflows - - - Source Files - - - Commands - - - Commands - - - Source Files - - - Commands - - - Commands - - - Workflows - - - Source Files - - - Commands - - - Commands - - - Workflows - - - Source Files - - - Source Files - - - Source Files - - - Commands - - - Workflows - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Commands\Configuration - - - Source Files - - - Commands - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {4b0dcf8b-b4a1-47e5-9c28-e8a3440178e6} + + + {00cc3138-2893-4fc4-8595-d3cf9d26be1c} + + + {fdeb940e-93e2-4bb0-a59c-1e2d1c0588d1} + + + {bd9d8dfe-7a37-4a53-b4e7-ddbd9694c0ab} + + + + + Header Files + + + Commands + + + Commands + + + Header Files + + + Header Files + + + Header Files + + + Public + + + Workflows + + + Workflows + + + Workflows + + + Workflows + + + Commands + + + Commands + + + Commands + + + Workflows + + + Header Files + + + Header Files + + + Commands + + + Header Files + + + Header Files + + + Workflows + + + Commands + + + Header Files + + + Header Files + + + Header Files + + + Commands + + + Commands + + + Commands + + + Commands + + + Header Files + + + Workflows + + + Header Files + + + Workflows + + + Workflows + + + Commands + + + Commands + + + Commands + + + Workflows + + + Commands + + + Header Files + + + Workflows + + + Commands + + + Header Files + + + Header Files + + + Header Files + + + Workflows + + + Commands + + + Workflows + + + Workflows + + + Workflows + + + Workflows + + + Public + + + Workflows + + + Workflows + + + Workflows + + + Header Files + + + Commands + + + Workflows + + + Workflows + + + Header Files + + + Workflows + + + Public + + + Commands + + + Commands + + + Commands + + + Header Files + + + Commands + + + Commands + + + Workflows + + + Header Files + + + Commands + + + Workflows + + + Header Files + + + Header Files + + + Commands + + + Workflows + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Public + + + Commands + + + + + Source Files + + + Commands + + + Commands + + + Source Files + + + Source Files + + + Workflows + + + Workflows + + + Workflows + + + Commands + + + Commands + + + Commands + + + Workflows + + + Workflows + + + Source Files + + + Commands + + + Source Files + + + Source Files + + + Workflows + + + Source Files + + + Commands + + + Source Files + + + Source Files + + + Commands + + + Commands + + + Commands + + + Commands + + + Source Files + + + Workflows + + + Source Files + + + Workflows + + + Workflows + + + Commands + + + Commands + + + Commands + + + Workflows + + + Commands + + + Source Files + + + Workflows + + + Commands + + + Source Files + + + Source Files + + + Commands + + + Workflows + + + Workflows + + + Workflows + + + Workflows + + + Workflows + + + Workflows + + + Workflows + + + Workflows + + + Source Files + + + Workflows + + + Commands + + + Workflows + + + Source Files + + + Workflows + + + Source Files + + + Commands + + + Commands + + + Source Files + + + Commands + + + Commands + + + Workflows + + + Source Files + + + Commands + + + Commands + + + Workflows + + + Source Files + + + Source Files + + + Source Files + + + Commands + + + Workflows + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Commands\Configuration + + + Source Files + + + Commands + + + + + + \ No newline at end of file diff --git a/src/AppInstallerCLICore/Argument.cpp b/src/AppInstallerCLICore/Argument.cpp index 99699bdb0a..850d1d5df9 100644 --- a/src/AppInstallerCLICore/Argument.cpp +++ b/src/AppInstallerCLICore/Argument.cpp @@ -1,647 +1,647 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Argument.h" -#include "Command.h" -#include "Resources.h" -#include -#include - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace Settings; - using namespace AppInstaller::Utility::literals; - - namespace - { - bool ContainsArgumentFromList(const Execution::Args& args, const std::vector& argTypes) - { - return std::any_of(argTypes.begin(), argTypes.end(), [&](Execution::Args::Type arg) { return args.Contains(arg); }); - } - } - - ArgumentCommon ArgumentCommon::ForType(Execution::Args::Type type) - { - // A test ensures that all types are listed here - switch (type) - { - // Args to specify where to get app - case Execution::Args::Type::Query: - return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::MultiQuery: - return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::MultiplePackages }; - case Execution::Args::Type::Manifest: - return { type, "manifest"_liv, 'm', ArgTypeCategory::Manifest }; - - // Query filtering criteria and query behavior - case Execution::Args::Type::Id: - return { type, "id"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Name: - return { type, "name"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Moniker: - return { type, "moniker"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Tag: - return { type, "tag"_liv, ArgTypeCategory::PackageQuery }; - case Execution::Args::Type::Command: - return { type, "command"_liv, "cmd"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Source: - return { type, "source"_liv, 's', ArgTypeCategory::QuerySource }; - case Execution::Args::Type::Count: - return { type, "count"_liv, 'n', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Exact: - return { type, "exact"_liv, 'e', ArgTypeCategory::PackageQuery }; - - // Manifest selection behavior after an app is found - case Execution::Args::Type::Version: - return { type, "version"_liv, 'v', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::Channel: - return { type, "channel"_liv, 'c', ArgTypeCategory::PackageQuery }; - - // Install behavior - case Execution::Args::Type::Interactive: - return { type, "interactive"_liv, 'i', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::Silent: - return { type, "silent"_liv, 'h', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::Locale: - return { type, "locale"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::Log: - return { type, "log"_liv, 'o', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::CustomSwitches: - return { type, "custom"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::Override: - return { type, "override"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::InstallLocation: - return { type, "location"_liv, 'l', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; - case Execution::Args::Type::InstallScope: - return { type, "scope"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::InstallArchitecture: - return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::InstallerArchitecture: // Used for input architecture that does not need applicability check. E.g. Download, Show. - return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::InstallerType: - return { type, "installer-type"_liv, ArgTypeCategory::InstallerSelection }; - case Execution::Args::Type::HashOverride: - return { type, "ignore-security-hash"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::IgnoreLocalArchiveMalwareScan: - return { type, "ignore-local-archive-malware-scan"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::AcceptPackageAgreements: - return { type, "accept-package-agreements"_liv, ArgTypeCategory::InstallerBehavior }; - case Execution::Args::Type::Rename: - return { type, "rename"_liv, 'r' }; - case Execution::Args::Type::NoUpgrade: - return { type, "no-upgrade"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::SkipDependencies: - return { type, "skip-dependencies"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::DependenciesConflict }; - case Execution::Args::Type::DependenciesOnly: - return { type, "dependencies-only"_liv, ArgTypeCategory::InstallerBehavior, ArgTypeExclusiveSet::DependenciesConflict }; - case Execution::Args::Type::AllowReboot: - return { type, "allow-reboot"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - - // Uninstall behavior - case Execution::Args::Type::Purge: - return { type, "purge"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; - case Execution::Args::Type::Preserve: - return { type, "preserve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; - case Execution::Args::Type::ProductCode: - return { type, "product-code"_liv, ArgTypeCategory::SinglePackageQuery }; - case Execution::Args::Type::AllVersions: - return { type, "all-versions"_liv, "all"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::AllAndTargetVersion }; - case Execution::Args::Type::TargetVersion: - return { type, "version"_liv, 'v', ArgTypeCategory::SinglePackageQuery, ArgTypeExclusiveSet::AllAndTargetVersion }; - - // Source Command - case Execution::Args::Type::SourceName: - return { type, "name"_liv, 'n' }; - case Execution::Args::Type::SourceType: - return { type, "type"_liv, 't' }; - case Execution::Args::Type::SourceArg: - return { type, "arg"_liv, 'a' }; - case Execution::Args::Type::ForceSourceReset: - return { type, "force"_liv }; - case Execution::Args::Type::SourceExplicit: - return { type, "explicit"_liv }; - case Execution::Args::Type::SourceTrustLevel: - return { type, "trust-level"_liv }; - case Execution::Args::Type::SourceEditExplicit: - return { type, "explicit"_liv, 'e' }; - case Execution::Args::Type::SourcePriority: - return { type, "priority"_liv, 'p' }; - - // Hash Command - case Execution::Args::Type::HashFile: - return { type, "file"_liv, 'f' }; - case Execution::Args::Type::Msix: - return { type, "msix"_liv, 'm' }; - - // Validate Command - case Execution::Args::Type::ValidateManifest: - return { type, "manifest"_liv }; - case Execution::Args::Type::IgnoreWarnings: - return { type, "ignore-warnings"_liv, "nowarn"_liv}; - - // Complete Command - case Execution::Args::Type::Word: - return { type, "word"_liv }; - case Execution::Args::Type::CommandLine: - return { type, "commandline"_liv }; - case Execution::Args::Type::Position: - return { type, "position"_liv }; - - // Export Command - case Execution::Args::Type::IncludeVersions: - return { type, "include-versions"_liv }; - - // Import Command - case Execution::Args::Type::ImportFile: - return { type, "import-file"_liv, 'i' }; - case Execution::Args::Type::IgnoreUnavailable: - return { type, "ignore-unavailable"_liv }; - case Execution::Args::Type::IgnoreVersions: - return { type, "ignore-versions"_liv }; - - // Setting Command - case Execution::Args::Type::AdminSettingEnable: - return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; - case Execution::Args::Type::AdminSettingDisable: - return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; - case Execution::Args::Type::SettingName: - return { type, "setting"_liv }; - case Execution::Args::Type::SettingValue: - return { type, "value"_liv }; - - // Upgrade command - case Execution::Args::Type::All: - return { type, "all"_liv, 'r', "recurse"_liv, ArgTypeCategory::MultiplePackages }; - case Execution::Args::Type::IncludeUnknown: - return { type, "include-unknown"_liv, 'u', "unknown"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::IncludePinned: - return { type, "include-pinned"_liv, "pinned"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::UninstallPrevious: - return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; - - // Show command - case Execution::Args::Type::ListVersions: - return { type, "versions"_liv }; - - // List command - case Execution::Args::Type::Upgrade: - return { type, "upgrade-available"_liv}; - case Execution::Args::Type::ListDetails: - return { type, "details"_liv }; - case Execution::Args::Type::Sort: - return { type, "sort"_liv }; - case Execution::Args::Type::SortAscending: - return { type, "ascending"_liv, "asc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; - case Execution::Args::Type::SortDescending: - return { type, "descending"_liv, "desc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; - - // Pin command - case Execution::Args::Type::GatedVersion: - return { type, "version"_liv, 'v', ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; - case Execution::Args::Type::BlockingPin: - return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; - case Execution::Args::Type::PinInstalled: - return { type, "installed"_liv, ArgTypeCategory::None }; - - // Error command - case Execution::Args::Type::ErrorInput: - return { type, "input"_liv, ArgTypeCategory::None }; - - // Resume command - case Execution::Args::Type::ResumeId: - return { type, "resume-id"_liv, 'g', ArgTypeCategory::None }; - case Execution::Args::Type::IgnoreResumeLimit: - return { type, "ignore-resume-limit"_liv, ArgTypeCategory::None }; - - // Font command - case Execution::Args::Type::Family: - return { type, "family"_liv, ArgTypeCategory::None }; - case Execution::Args::Type::Details: - return { type, "details"_liv, ArgTypeCategory::None }; - - // Configuration commands - case Execution::Args::Type::ConfigurationFile: - return { type, "file"_liv, 'f', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; - case Execution::Args::Type::ConfigurationAcceptWarning: - return { type, "accept-configuration-agreements"_liv }; - case Execution::Args::Type::ConfigurationSuppressPrologue: - return { type, "suppress-initial-details"_liv }; - case Execution::Args::Type::ExtendedFeaturesEnable: - return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; - case Execution::Args::Type::ExtendedFeaturesDisable: - return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; - case Execution::Args::Type::ConfigurationModulePath: - return { type, "module-path"_liv }; - case Execution::Args::Type::ConfigurationProcessorPath: - return { type, "processor-path"_liv }; - case Execution::Args::Type::ConfigurationExportPackageId: - return { type, "package-id"_liv }; - case Execution::Args::Type::ConfigurationExportModule: - return { type, "module"_liv }; - case Execution::Args::Type::ConfigurationExportResource: - return { type, "resource"_liv }; - case Execution::Args::Type::ConfigurationExportAll: - return { type, "all"_liv, 'r', "recurse"_liv }; - case Execution::Args::Type::ConfigurationHistoryItem: - return { type, "history"_liv, 'h', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; - case Execution::Args::Type::ConfigurationHistoryRemove: - return { type, "remove"_liv }; - case Execution::Args::Type::ConfigurationStatusWatch: - return { type, "live"_liv }; - - // DSCv3 resources - case Execution::Args::Type::DscResourceFunctionGet: - return { type, "get"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionSet: - return { type, "set"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionWhatIf: - return { type, "whatIf"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionTest: - return { type, "test"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionDelete: - return { type, "delete"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionExport: - return { type, "export"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionValidate: - return { type, "validate"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionResolve: - return { type, "resolve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionAdapter: - return { type, "adapter"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionSchema: - return { type, "schema"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - case Execution::Args::Type::DscResourceFunctionManifest: - return { type, "manifest"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; - - // Download command - case Execution::Args::Type::DownloadDirectory: - return { type, "download-directory"_liv, 'd', ArgTypeCategory::None }; - case Execution::Args::Type::Platform: - return { type, "platform"_liv, ArgTypeCategory::None }; - case Execution::Args::Type::SkipMicrosoftStorePackageLicense: - return { type, "skip-microsoft-store-package-license"_liv, "skip-license"_liv, ArgTypeCategory::None }; - case Execution::Args::Type::OSVersion: - return { type, "os-version"_liv, ArgTypeCategory::None }; - - // Common arguments - case Execution::Args::Type::NoVT: - return { type, "no-vt"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::RetroStyle: - return { type, "retro"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::RainbowStyle: - return { type, "rainbow"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::NoProgress: - return { type, "no-progress"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; - case Execution::Args::Type::Help: - return { type, "help"_liv, APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR }; - case Execution::Args::Type::Info: - return { type, "info"_liv }; - case Execution::Args::Type::VerboseLogs: - return { type, "verbose-logs"_liv, "verbose"_liv }; - case Execution::Args::Type::DisableInteractivity: - return { type, "disable-interactivity"_liv }; - case Execution::Args::Type::Wait: - return { type, "wait"_liv }; - case Execution::Args::Type::OpenLogs: - return { type, "open-logs"_liv, "logs"_liv }; - case Execution::Args::Type::Force: - return { type, "force"_liv, ArgTypeCategory::CopyFlagToSubContext }; - case Execution::Args::Type::OutputFile: - return { type, "output"_liv, 'o' }; - case Execution::Args::Type::Correlation: - return { type, "correlation"_liv }; - - case Execution::Args::Type::DependencySource: - return { type, "dependency-source"_liv, ArgTypeCategory::ExtendedSource }; - case Execution::Args::Type::CustomHeader: - return { type, "header"_liv, ArgTypeCategory::ExtendedSource }; - case Execution::Args::Type::AcceptSourceAgreements: - return { type, "accept-source-agreements"_liv, ArgTypeCategory::ExtendedSource }; - - case Execution::Args::Type::Proxy: - return { type, "proxy"_liv, ArgTypeCategory::CopyValueToSubContext, ArgTypeExclusiveSet::Proxy }; - case Execution::Args::Type::NoProxy: - return { type, "no-proxy"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::Proxy }; - - case Execution::Args::Type::ToolVersion: - return { type, "version"_liv, 'v' }; - - // Authentication arguments - case Execution::Args::Type::AuthenticationMode: - return { type, "authentication-mode"_liv, ArgTypeCategory::CopyValueToSubContext }; - case Execution::Args::Type::AuthenticationAccount: - return { type, "authentication-account"_liv, ArgTypeCategory::CopyValueToSubContext }; - - // Used for demonstration purposes - case Execution::Args::Type::ExperimentalArg: - return { type, "arg"_liv }; - - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::vector ArgumentCommon::GetFromExecArgs(const Execution::Args& execArgs) - { - auto argTypes = execArgs.GetTypes(); - std::vector result; - std::transform(argTypes.begin(), argTypes.end(), std::back_inserter(result), ArgumentCommon::ForType); - return result; - } - - Argument Argument::ForType(Execution::Args::Type type) - { - switch (type) - { - case Args::Type::Query: - return Argument{ type, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; - case Args::Type::MultiQuery: - return Argument{ type, Resource::String::MultiQueryArgumentDescription, ArgumentType::Positional }.SetCountLimit(128); - case Args::Type::Manifest: - return Argument{ type, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::BoolAdminSetting::LocalManifestFiles }; - case Args::Type::Id: - return Argument{ type, Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Name: - return Argument{ type, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Moniker: - return Argument{ type, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Tag: - return Argument{ type, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Command: - return Argument{ type, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Source: - return Argument{ type, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; - case Args::Type::DependencySource: - return Argument{ type, Resource::String::DependencySourceArgumentDescription, ArgumentType::Standard }; - case Args::Type::Count: - return Argument{ type, Resource::String::CountArgumentDescription, ArgumentType::Standard }; - case Args::Type::Exact: - return Argument{ type, Resource::String::ExactArgumentDescription, ArgumentType::Flag }; - case Args::Type::Version: - return Argument{ type, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; - case Args::Type::Channel: - return Argument{ type, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; - case Args::Type::Interactive: - return Argument{ type, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; - case Args::Type::Silent: - return Argument{ type, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; - case Args::Type::Locale: - return Argument{ type, Resource::String::LocaleArgumentDescription, ArgumentType::Standard }; - case Args::Type::InstallArchitecture: - return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::InstallerArchitecture: - return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::Log: - return Argument{ type, Resource::String::LogArgumentDescription, ArgumentType::Standard }; - case Args::Type::CustomSwitches: - return Argument{ type, Resource::String::CustomSwitchesArgumentDescription, ArgumentType::Standard }; - case Args::Type::Override: - return Argument{ type, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::InstallLocation: - return Argument{ type, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; - case Args::Type::HashOverride: - return Argument{ type, Resource::String::HashOverrideArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::HashOverride, Settings::BoolAdminSetting::InstallerHashOverride }; - case Args::Type::AcceptPackageAgreements: - return Argument{ type, Resource::String::AcceptPackageAgreementsArgumentDescription, ArgumentType::Flag }; - case Args::Type::NoUpgrade: - return Argument{ type, Resource::String::NoUpgradeArgumentDescription, ArgumentType::Flag }; - case Args::Type::HashFile: - return Argument{ type, Resource::String::FileArgumentDescription, ArgumentType::Positional, true }; - case Args::Type::Msix: - return Argument{ type, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; - case Args::Type::ListVersions: - return Argument{ type, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; - case Args::Type::Help: - return Argument{ type, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; - case Args::Type::SkipDependencies: - return Argument{ type, Resource::String::SkipDependenciesArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::DependenciesOnly: - return Argument{ type, Resource::String::DependenciesOnlyArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::IgnoreLocalArchiveMalwareScan: - return Argument{ type, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride }; - case Args::Type::SourceName: - return Argument{ type, Resource::String::SourceNameArgumentDescription, ArgumentType::Positional, false }; - case Args::Type::SourceArg: - return Argument{ type, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true }; - case Args::Type::SourceType: - return Argument{ type, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; - case Args::Type::SourceExplicit: - return Argument{ type, Resource::String::SourceExplicitArgumentDescription, ArgumentType::Flag }; - case Args::Type::SourceEditExplicit: - return Argument{ type, Resource::String::SourceEditExplicitArgumentDescription, ArgumentType::Standard }; - case Args::Type::SourcePriority: - return Argument{ type, Resource::String::SourcePriorityArgumentDescription, ArgumentType::Standard, ExperimentalFeature::Feature::SourcePriority }; - case Args::Type::SourceTrustLevel: - return Argument{ type, Resource::String::SourceTrustLevelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::ValidateManifest: - return Argument{ type, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; - case Args::Type::IgnoreWarnings: - return Argument{ type, Resource::String::IgnoreWarningsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::NoVT: - return Argument{ type, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::RainbowStyle: - return Argument{ type, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::RetroStyle: - return Argument{ type, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::NoProgress: - return Argument{ type, Resource::String::NoProgressArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; - case Args::Type::VerboseLogs: - return Argument{ type, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; - case Args::Type::CustomHeader: - return Argument{ type, Resource::String::HeaderArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::AcceptSourceAgreements: - return Argument{ type, Resource::String::AcceptSourceAgreementsArgumentDescription, ArgumentType::Flag }; - case Args::Type::AuthenticationMode: - return Argument{ type, Resource::String::AuthenticationModeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::AuthenticationAccount: - return Argument{ type, Resource::String::AuthenticationAccountArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; - case Args::Type::ExperimentalArg: - return Argument{ type, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; - case Args::Type::Rename: - return Argument{ type, Resource::String::RenameArgumentDescription, ArgumentType::Standard, false }; - case Args::Type::Purge: - return Argument{ type, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::Preserve: - return Argument{ type, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::Wait: - return Argument{ type, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::ProductCode: - return Argument{ type, Resource::String::ProductCodeArgumentDescription, ArgumentType::Standard, false }; - case Args::Type::OpenLogs: - return Argument{ type, Resource::String::OpenLogsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::UninstallPrevious: - return Argument{ type, Resource::String::UninstallPreviousArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::Force: - return Argument{ type, Resource::String::ForceArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::DownloadDirectory: - return Argument{ type, Resource::String::DownloadDirectoryArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; - case Args::Type::SkipMicrosoftStorePackageLicense: - return Argument{ type, Resource::String::SkipMicrosoftStorePackageLicenseArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help, false }; - case Args::Type::Platform: - return Argument{ type, Resource::String::PlatformArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; - case Args::Type::InstallerType: - return Argument{ type, Resource::String::InstallerTypeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; - case Args::Type::ResumeId: - return Argument{ type, Resource::String::ResumeIdArgumentDescription, ArgumentType::Standard, true }; - case Args::Type::AllowReboot: - return Argument{ type, Resource::String::AllowRebootArgumentDescription, ArgumentType::Flag }; - case Args::Type::IgnoreResumeLimit: - return Argument{ type, Resource::String::IgnoreResumeLimitArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::Resume }; - case Args::Type::AllVersions: - return Argument{ type, Resource::String::UninstallAllVersionsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - case Args::Type::TargetVersion: - return Argument{ type, Resource::String::TargetVersionArgumentDescription, ArgumentType::Standard }; - case Args::Type::Proxy: - return Argument{ type, Resource::String::ProxyArgumentDescription, ArgumentType::Standard, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; - case Args::Type::NoProxy: - return Argument{ type, Resource::String::NoProxyArgumentDescription, ArgumentType::Flag, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; - case Args::Type::ConfigurationProcessorPath: - return Argument{ type, Resource::String::ConfigurationProcessorPath, ArgumentType::Standard, Argument::Visibility::Help, TogglePolicy::Policy::ConfigurationProcessorPath, BoolAdminSetting::ConfigurationProcessorPath }; - case Args::Type::Family: - return Argument{ type, Resource::String::FontFamilyNameArgumentDescription, ArgumentType::Positional, false }; - case Args::Type::Details: - return Argument{ type, Resource::String::FontDetailsArgumentDescription, ArgumentType::Flag, false }; - case Args::Type::Correlation: - return Argument{ type, Resource::String::CorrelationArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; - case Args::Type::ListDetails: - return Argument{ type, Resource::String::ListDetailsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; - default: - THROW_HR(E_UNEXPECTED); - } - } - - void Argument::GetCommon(std::vector& args) - { - args.push_back(ForType(Args::Type::Help)); - args.push_back(ForType(Args::Type::Wait)); - args.push_back(ForType(Args::Type::OpenLogs)); - args.push_back(ForType(Args::Type::NoVT)); - args.push_back(ForType(Args::Type::RainbowStyle)); - args.push_back(ForType(Args::Type::RetroStyle)); - args.push_back(ForType(Args::Type::NoProgress)); - args.push_back(ForType(Args::Type::VerboseLogs)); - args.push_back(ForType(Args::Type::IgnoreWarnings)); - args.emplace_back(Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); - args.push_back(ForType(Args::Type::Proxy)); - args.push_back(ForType(Args::Type::NoProxy)); - args.push_back(ForType(Args::Type::Correlation)); - } - - std::string Argument::GetUsageString() const - { - std::ostringstream strstr; - if (Alias() != ArgumentCommon::NoAlias) - { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Alias() << ','; - } - if (AlternateName() != Argument::NoAlternateName) - { - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << AlternateName() << ','; - } - strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Name(); - return strstr.str(); - } - - void Argument::ValidateExclusiveArguments(const Execution::Args& args) - { - auto argProperties = ArgumentCommon::GetFromExecArgs(args); - - using ExclusiveSet_t = std::underlying_type_t; - for (ExclusiveSet_t i = 1 + static_cast(ArgTypeExclusiveSet::None); i < static_cast(ArgTypeExclusiveSet::Max); i <<= 1) - { - std::vector argsFromSet; - std::copy_if( - argProperties.begin(), - argProperties.end(), - std::back_inserter(argsFromSet), - [=](const ArgumentCommon& arg) { return static_cast(arg.ExclusiveSet) & i; }); - - if (argsFromSet.size() > 1) - { - // Create a string showing the exclusive args. - std::string argsString; - for (const auto& arg : argsFromSet) - { - if (!argsString.empty()) - { - argsString += '|'; - - } - - argsString += arg.Name; - } - - throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided(Utility::LocIndString{ argsString })); - } - } - } - - void Argument::ValidateArgumentDependency(const Execution::Args& args, Execution::Args::Type type, Execution::Args::Type dependencyArgType) - { - if (args.Contains(type) && !args.Contains(dependencyArgType)) - { - throw CommandException(Resource::String::DependencyArgumentMissing( - Utility::LocIndString{ ArgumentCommon::ForType(type).Name }, - Utility::LocIndString{ ArgumentCommon::ForType(dependencyArgType).Name })); - } - } - - ArgTypeCategory Argument::GetCategoriesPresent(const Execution::Args& args) - { - auto argProperties = ArgumentCommon::GetFromExecArgs(args); - - ArgTypeCategory result = ArgTypeCategory::None; - for (const auto& arg : argProperties) - { - result |= arg.TypeCategory; - } - - return result; - } - - ArgTypeCategory Argument::GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg) - { - const auto categories = GetCategoriesPresent(args); - - // Commands like install require some argument to select a package - if (requirePackageSelectionArg) - { - if (WI_AreAllFlagsClear(categories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery)) - { - throw CommandException(Resource::String::NoPackageSelectionArgumentProvided); - } - } - - // If a manifest is specified, we cannot also have arguments for searching - if (WI_IsFlagSet(categories, ArgTypeCategory::Manifest) && - WI_IsAnyFlagSet(categories, ArgTypeCategory::PackageQuery | ArgTypeCategory::QuerySource)) - { - throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); - } - - // If we have multiple packages, we cannot have arguments that only make sense for a single package - if (WI_IsFlagSet(categories, ArgTypeCategory::MultiplePackages) && - WI_IsAnyFlagSet(categories, ArgTypeCategory::SinglePackageQuery | ArgTypeCategory::SingleInstallerBehavior)) - { - throw CommandException(Resource::String::ArgumentForSinglePackageProvidedWithMultipleQueries); - } - - return categories; - } - - Argument::Visibility Argument::GetVisibility() const - { - if (!ExperimentalFeature::IsEnabled(m_feature)) - { - return Argument::Visibility::Hidden; - } - - if (!GroupPolicies().IsEnabled(m_groupPolicy)) - { - return Argument::Visibility::Hidden; - } - - return m_visibility; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Argument.h" +#include "Command.h" +#include "Resources.h" +#include +#include + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace Settings; + using namespace AppInstaller::Utility::literals; + + namespace + { + bool ContainsArgumentFromList(const Execution::Args& args, const std::vector& argTypes) + { + return std::any_of(argTypes.begin(), argTypes.end(), [&](Execution::Args::Type arg) { return args.Contains(arg); }); + } + } + + ArgumentCommon ArgumentCommon::ForType(Execution::Args::Type type) + { + // A test ensures that all types are listed here + switch (type) + { + // Args to specify where to get app + case Execution::Args::Type::Query: + return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::MultiQuery: + return { type, "query"_liv, 'q', ArgTypeCategory::PackageQuery | ArgTypeCategory::MultiplePackages }; + case Execution::Args::Type::Manifest: + return { type, "manifest"_liv, 'm', ArgTypeCategory::Manifest }; + + // Query filtering criteria and query behavior + case Execution::Args::Type::Id: + return { type, "id"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Name: + return { type, "name"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Moniker: + return { type, "moniker"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Tag: + return { type, "tag"_liv, ArgTypeCategory::PackageQuery }; + case Execution::Args::Type::Command: + return { type, "command"_liv, "cmd"_liv, ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Source: + return { type, "source"_liv, 's', ArgTypeCategory::QuerySource }; + case Execution::Args::Type::Count: + return { type, "count"_liv, 'n', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Exact: + return { type, "exact"_liv, 'e', ArgTypeCategory::PackageQuery }; + + // Manifest selection behavior after an app is found + case Execution::Args::Type::Version: + return { type, "version"_liv, 'v', ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::Channel: + return { type, "channel"_liv, 'c', ArgTypeCategory::PackageQuery }; + + // Install behavior + case Execution::Args::Type::Interactive: + return { type, "interactive"_liv, 'i', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::Silent: + return { type, "silent"_liv, 'h', ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::Locale: + return { type, "locale"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::Log: + return { type, "log"_liv, 'o', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::CustomSwitches: + return { type, "custom"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::Override: + return { type, "override"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::InstallLocation: + return { type, "location"_liv, 'l', ArgTypeCategory::InstallerSelection | ArgTypeCategory::SingleInstallerBehavior }; + case Execution::Args::Type::InstallScope: + return { type, "scope"_liv, ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::InstallArchitecture: + return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::InstallerArchitecture: // Used for input architecture that does not need applicability check. E.g. Download, Show. + return { type, "architecture"_liv, 'a', ArgTypeCategory::InstallerSelection | ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::InstallerType: + return { type, "installer-type"_liv, ArgTypeCategory::InstallerSelection }; + case Execution::Args::Type::HashOverride: + return { type, "ignore-security-hash"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::IgnoreLocalArchiveMalwareScan: + return { type, "ignore-local-archive-malware-scan"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::AcceptPackageAgreements: + return { type, "accept-package-agreements"_liv, ArgTypeCategory::InstallerBehavior }; + case Execution::Args::Type::Rename: + return { type, "rename"_liv, 'r' }; + case Execution::Args::Type::NoUpgrade: + return { type, "no-upgrade"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::SkipDependencies: + return { type, "skip-dependencies"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::DependenciesConflict }; + case Execution::Args::Type::DependenciesOnly: + return { type, "dependencies-only"_liv, ArgTypeCategory::InstallerBehavior, ArgTypeExclusiveSet::DependenciesConflict }; + case Execution::Args::Type::AllowReboot: + return { type, "allow-reboot"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + + // Uninstall behavior + case Execution::Args::Type::Purge: + return { type, "purge"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; + case Execution::Args::Type::Preserve: + return { type, "preserve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PurgePreserve }; + case Execution::Args::Type::ProductCode: + return { type, "product-code"_liv, ArgTypeCategory::SinglePackageQuery }; + case Execution::Args::Type::AllVersions: + return { type, "all-versions"_liv, "all"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::AllAndTargetVersion }; + case Execution::Args::Type::TargetVersion: + return { type, "version"_liv, 'v', ArgTypeCategory::SinglePackageQuery, ArgTypeExclusiveSet::AllAndTargetVersion }; + + // Source Command + case Execution::Args::Type::SourceName: + return { type, "name"_liv, 'n' }; + case Execution::Args::Type::SourceType: + return { type, "type"_liv, 't' }; + case Execution::Args::Type::SourceArg: + return { type, "arg"_liv, 'a' }; + case Execution::Args::Type::ForceSourceReset: + return { type, "force"_liv }; + case Execution::Args::Type::SourceExplicit: + return { type, "explicit"_liv }; + case Execution::Args::Type::SourceTrustLevel: + return { type, "trust-level"_liv }; + case Execution::Args::Type::SourceEditExplicit: + return { type, "explicit"_liv, 'e' }; + case Execution::Args::Type::SourcePriority: + return { type, "priority"_liv, 'p' }; + + // Hash Command + case Execution::Args::Type::HashFile: + return { type, "file"_liv, 'f' }; + case Execution::Args::Type::Msix: + return { type, "msix"_liv, 'm' }; + + // Validate Command + case Execution::Args::Type::ValidateManifest: + return { type, "manifest"_liv }; + case Execution::Args::Type::IgnoreWarnings: + return { type, "ignore-warnings"_liv, "nowarn"_liv}; + + // Complete Command + case Execution::Args::Type::Word: + return { type, "word"_liv }; + case Execution::Args::Type::CommandLine: + return { type, "commandline"_liv }; + case Execution::Args::Type::Position: + return { type, "position"_liv }; + + // Export Command + case Execution::Args::Type::IncludeVersions: + return { type, "include-versions"_liv }; + + // Import Command + case Execution::Args::Type::ImportFile: + return { type, "import-file"_liv, 'i' }; + case Execution::Args::Type::IgnoreUnavailable: + return { type, "ignore-unavailable"_liv }; + case Execution::Args::Type::IgnoreVersions: + return { type, "ignore-versions"_liv }; + + // Setting Command + case Execution::Args::Type::AdminSettingEnable: + return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; + case Execution::Args::Type::AdminSettingDisable: + return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::EnableDisable }; + case Execution::Args::Type::SettingName: + return { type, "setting"_liv }; + case Execution::Args::Type::SettingValue: + return { type, "value"_liv }; + + // Upgrade command + case Execution::Args::Type::All: + return { type, "all"_liv, 'r', "recurse"_liv, ArgTypeCategory::MultiplePackages }; + case Execution::Args::Type::IncludeUnknown: + return { type, "include-unknown"_liv, 'u', "unknown"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::IncludePinned: + return { type, "include-pinned"_liv, "pinned"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::UninstallPrevious: + return { type, "uninstall-previous"_liv, ArgTypeCategory::InstallerBehavior | ArgTypeCategory::CopyFlagToSubContext }; + + // Show command + case Execution::Args::Type::ListVersions: + return { type, "versions"_liv }; + + // List command + case Execution::Args::Type::Upgrade: + return { type, "upgrade-available"_liv}; + case Execution::Args::Type::ListDetails: + return { type, "details"_liv }; + case Execution::Args::Type::Sort: + return { type, "sort"_liv }; + case Execution::Args::Type::SortAscending: + return { type, "ascending"_liv, "asc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; + case Execution::Args::Type::SortDescending: + return { type, "descending"_liv, "desc"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::SortDirection }; + + // Pin command + case Execution::Args::Type::GatedVersion: + return { type, "version"_liv, 'v', ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; + case Execution::Args::Type::BlockingPin: + return { type, "blocking"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::PinType }; + case Execution::Args::Type::PinInstalled: + return { type, "installed"_liv, ArgTypeCategory::None }; + + // Error command + case Execution::Args::Type::ErrorInput: + return { type, "input"_liv, ArgTypeCategory::None }; + + // Resume command + case Execution::Args::Type::ResumeId: + return { type, "resume-id"_liv, 'g', ArgTypeCategory::None }; + case Execution::Args::Type::IgnoreResumeLimit: + return { type, "ignore-resume-limit"_liv, ArgTypeCategory::None }; + + // Font command + case Execution::Args::Type::Family: + return { type, "family"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::Details: + return { type, "details"_liv, ArgTypeCategory::None }; + + // Configuration commands + case Execution::Args::Type::ConfigurationFile: + return { type, "file"_liv, 'f', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; + case Execution::Args::Type::ConfigurationAcceptWarning: + return { type, "accept-configuration-agreements"_liv }; + case Execution::Args::Type::ConfigurationSuppressPrologue: + return { type, "suppress-initial-details"_liv }; + case Execution::Args::Type::ExtendedFeaturesEnable: + return { type, "enable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; + case Execution::Args::Type::ExtendedFeaturesDisable: + return { type, "disable"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::StubType }; + case Execution::Args::Type::ConfigurationModulePath: + return { type, "module-path"_liv }; + case Execution::Args::Type::ConfigurationProcessorPath: + return { type, "processor-path"_liv }; + case Execution::Args::Type::ConfigurationExportPackageId: + return { type, "package-id"_liv }; + case Execution::Args::Type::ConfigurationExportModule: + return { type, "module"_liv }; + case Execution::Args::Type::ConfigurationExportResource: + return { type, "resource"_liv }; + case Execution::Args::Type::ConfigurationExportAll: + return { type, "all"_liv, 'r', "recurse"_liv }; + case Execution::Args::Type::ConfigurationHistoryItem: + return { type, "history"_liv, 'h', ArgTypeCategory::ConfigurationSetChoice, ArgTypeExclusiveSet::ConfigurationSetChoice }; + case Execution::Args::Type::ConfigurationHistoryRemove: + return { type, "remove"_liv }; + case Execution::Args::Type::ConfigurationStatusWatch: + return { type, "live"_liv }; + + // DSCv3 resources + case Execution::Args::Type::DscResourceFunctionGet: + return { type, "get"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionSet: + return { type, "set"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionWhatIf: + return { type, "whatIf"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionTest: + return { type, "test"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionDelete: + return { type, "delete"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionExport: + return { type, "export"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionValidate: + return { type, "validate"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionResolve: + return { type, "resolve"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionAdapter: + return { type, "adapter"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionSchema: + return { type, "schema"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + case Execution::Args::Type::DscResourceFunctionManifest: + return { type, "manifest"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::DscResourceFunction }; + + // Download command + case Execution::Args::Type::DownloadDirectory: + return { type, "download-directory"_liv, 'd', ArgTypeCategory::None }; + case Execution::Args::Type::Platform: + return { type, "platform"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::SkipMicrosoftStorePackageLicense: + return { type, "skip-microsoft-store-package-license"_liv, "skip-license"_liv, ArgTypeCategory::None }; + case Execution::Args::Type::OSVersion: + return { type, "os-version"_liv, ArgTypeCategory::None }; + + // Common arguments + case Execution::Args::Type::NoVT: + return { type, "no-vt"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::RetroStyle: + return { type, "retro"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::RainbowStyle: + return { type, "rainbow"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::NoProgress: + return { type, "no-progress"_liv, ArgTypeCategory::None, ArgTypeExclusiveSet::ProgressBarOption }; + case Execution::Args::Type::Help: + return { type, "help"_liv, APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR }; + case Execution::Args::Type::Info: + return { type, "info"_liv }; + case Execution::Args::Type::VerboseLogs: + return { type, "verbose-logs"_liv, "verbose"_liv }; + case Execution::Args::Type::DisableInteractivity: + return { type, "disable-interactivity"_liv }; + case Execution::Args::Type::Wait: + return { type, "wait"_liv }; + case Execution::Args::Type::OpenLogs: + return { type, "open-logs"_liv, "logs"_liv }; + case Execution::Args::Type::Force: + return { type, "force"_liv, ArgTypeCategory::CopyFlagToSubContext }; + case Execution::Args::Type::OutputFile: + return { type, "output"_liv, 'o' }; + case Execution::Args::Type::Correlation: + return { type, "correlation"_liv }; + + case Execution::Args::Type::DependencySource: + return { type, "dependency-source"_liv, ArgTypeCategory::ExtendedSource }; + case Execution::Args::Type::CustomHeader: + return { type, "header"_liv, ArgTypeCategory::ExtendedSource }; + case Execution::Args::Type::AcceptSourceAgreements: + return { type, "accept-source-agreements"_liv, ArgTypeCategory::ExtendedSource }; + + case Execution::Args::Type::Proxy: + return { type, "proxy"_liv, ArgTypeCategory::CopyValueToSubContext, ArgTypeExclusiveSet::Proxy }; + case Execution::Args::Type::NoProxy: + return { type, "no-proxy"_liv, ArgTypeCategory::CopyFlagToSubContext, ArgTypeExclusiveSet::Proxy }; + + case Execution::Args::Type::ToolVersion: + return { type, "version"_liv, 'v' }; + + // Authentication arguments + case Execution::Args::Type::AuthenticationMode: + return { type, "authentication-mode"_liv, ArgTypeCategory::CopyValueToSubContext }; + case Execution::Args::Type::AuthenticationAccount: + return { type, "authentication-account"_liv, ArgTypeCategory::CopyValueToSubContext }; + + // Used for demonstration purposes + case Execution::Args::Type::ExperimentalArg: + return { type, "arg"_liv }; + + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::vector ArgumentCommon::GetFromExecArgs(const Execution::Args& execArgs) + { + auto argTypes = execArgs.GetTypes(); + std::vector result; + std::transform(argTypes.begin(), argTypes.end(), std::back_inserter(result), ArgumentCommon::ForType); + return result; + } + + Argument Argument::ForType(Execution::Args::Type type) + { + switch (type) + { + case Args::Type::Query: + return Argument{ type, Resource::String::QueryArgumentDescription, ArgumentType::Positional}; + case Args::Type::MultiQuery: + return Argument{ type, Resource::String::MultiQueryArgumentDescription, ArgumentType::Positional }.SetCountLimit(128); + case Args::Type::Manifest: + return Argument{ type, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, Settings::TogglePolicy::Policy::LocalManifestFiles, Settings::BoolAdminSetting::LocalManifestFiles }; + case Args::Type::Id: + return Argument{ type, Resource::String::IdArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Name: + return Argument{ type, Resource::String::NameArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Moniker: + return Argument{ type, Resource::String::MonikerArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Tag: + return Argument{ type, Resource::String::TagArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Command: + return Argument{ type, Resource::String::CommandArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Source: + return Argument{ type, Resource::String::SourceArgumentDescription, ArgumentType::Standard }; + case Args::Type::DependencySource: + return Argument{ type, Resource::String::DependencySourceArgumentDescription, ArgumentType::Standard }; + case Args::Type::Count: + return Argument{ type, Resource::String::CountArgumentDescription, ArgumentType::Standard }; + case Args::Type::Exact: + return Argument{ type, Resource::String::ExactArgumentDescription, ArgumentType::Flag }; + case Args::Type::Version: + return Argument{ type, Resource::String::VersionArgumentDescription, ArgumentType::Standard }; + case Args::Type::Channel: + return Argument{ type, Resource::String::ChannelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; + case Args::Type::Interactive: + return Argument{ type, Resource::String::InteractiveArgumentDescription, ArgumentType::Flag }; + case Args::Type::Silent: + return Argument{ type, Resource::String::SilentArgumentDescription, ArgumentType::Flag }; + case Args::Type::Locale: + return Argument{ type, Resource::String::LocaleArgumentDescription, ArgumentType::Standard }; + case Args::Type::InstallArchitecture: + return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::InstallerArchitecture: + return Argument{ type, Resource::String::ArchitectureArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::Log: + return Argument{ type, Resource::String::LogArgumentDescription, ArgumentType::Standard }; + case Args::Type::CustomSwitches: + return Argument{ type, Resource::String::CustomSwitchesArgumentDescription, ArgumentType::Standard }; + case Args::Type::Override: + return Argument{ type, Resource::String::OverrideArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::InstallLocation: + return Argument{ type, Resource::String::LocationArgumentDescription, ArgumentType::Standard }; + case Args::Type::HashOverride: + return Argument{ type, Resource::String::HashOverrideArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::HashOverride, Settings::BoolAdminSetting::InstallerHashOverride }; + case Args::Type::AcceptPackageAgreements: + return Argument{ type, Resource::String::AcceptPackageAgreementsArgumentDescription, ArgumentType::Flag }; + case Args::Type::NoUpgrade: + return Argument{ type, Resource::String::NoUpgradeArgumentDescription, ArgumentType::Flag }; + case Args::Type::HashFile: + return Argument{ type, Resource::String::FileArgumentDescription, ArgumentType::Positional, true }; + case Args::Type::Msix: + return Argument{ type, Resource::String::MsixArgumentDescription, ArgumentType::Flag }; + case Args::Type::ListVersions: + return Argument{ type, Resource::String::VersionsArgumentDescription, ArgumentType::Flag }; + case Args::Type::Help: + return Argument{ type, Resource::String::HelpArgumentDescription, ArgumentType::Flag }; + case Args::Type::SkipDependencies: + return Argument{ type, Resource::String::SkipDependenciesArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::DependenciesOnly: + return Argument{ type, Resource::String::DependenciesOnlyArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::IgnoreLocalArchiveMalwareScan: + return Argument{ type, Resource::String::IgnoreLocalArchiveMalwareScanArgumentDescription, ArgumentType::Flag, Settings::TogglePolicy::Policy::LocalArchiveMalwareScanOverride, Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride }; + case Args::Type::SourceName: + return Argument{ type, Resource::String::SourceNameArgumentDescription, ArgumentType::Positional, false }; + case Args::Type::SourceArg: + return Argument{ type, Resource::String::SourceArgArgumentDescription, ArgumentType::Positional, true }; + case Args::Type::SourceType: + return Argument{ type, Resource::String::SourceTypeArgumentDescription, ArgumentType::Positional }; + case Args::Type::SourceExplicit: + return Argument{ type, Resource::String::SourceExplicitArgumentDescription, ArgumentType::Flag }; + case Args::Type::SourceEditExplicit: + return Argument{ type, Resource::String::SourceEditExplicitArgumentDescription, ArgumentType::Standard }; + case Args::Type::SourcePriority: + return Argument{ type, Resource::String::SourcePriorityArgumentDescription, ArgumentType::Standard, ExperimentalFeature::Feature::SourcePriority }; + case Args::Type::SourceTrustLevel: + return Argument{ type, Resource::String::SourceTrustLevelArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::ValidateManifest: + return Argument{ type, Resource::String::ValidateManifestArgumentDescription, ArgumentType::Positional, true }; + case Args::Type::IgnoreWarnings: + return Argument{ type, Resource::String::IgnoreWarningsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::NoVT: + return Argument{ type, Resource::String::NoVTArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::RainbowStyle: + return Argument{ type, Resource::String::RainbowArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::RetroStyle: + return Argument{ type, Resource::String::RetroArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::NoProgress: + return Argument{ type, Resource::String::NoProgressArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }; + case Args::Type::VerboseLogs: + return Argument{ type, Resource::String::VerboseLogsArgumentDescription, ArgumentType::Flag }; + case Args::Type::CustomHeader: + return Argument{ type, Resource::String::HeaderArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::AcceptSourceAgreements: + return Argument{ type, Resource::String::AcceptSourceAgreementsArgumentDescription, ArgumentType::Flag }; + case Args::Type::AuthenticationMode: + return Argument{ type, Resource::String::AuthenticationModeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::AuthenticationAccount: + return Argument{ type, Resource::String::AuthenticationAccountArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }; + case Args::Type::ExperimentalArg: + return Argument{ type, Resource::String::ExperimentalArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::ExperimentalArg }; + case Args::Type::Rename: + return Argument{ type, Resource::String::RenameArgumentDescription, ArgumentType::Standard, false }; + case Args::Type::Purge: + return Argument{ type, Resource::String::PurgeArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Preserve: + return Argument{ type, Resource::String::PreserveArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Wait: + return Argument{ type, Resource::String::WaitArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::ProductCode: + return Argument{ type, Resource::String::ProductCodeArgumentDescription, ArgumentType::Standard, false }; + case Args::Type::OpenLogs: + return Argument{ type, Resource::String::OpenLogsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::UninstallPrevious: + return Argument{ type, Resource::String::UninstallPreviousArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::Force: + return Argument{ type, Resource::String::ForceArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::DownloadDirectory: + return Argument{ type, Resource::String::DownloadDirectoryArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; + case Args::Type::SkipMicrosoftStorePackageLicense: + return Argument{ type, Resource::String::SkipMicrosoftStorePackageLicenseArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help, false }; + case Args::Type::Platform: + return Argument{ type, Resource::String::PlatformArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; + case Args::Type::InstallerType: + return Argument{ type, Resource::String::InstallerTypeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help, false }; + case Args::Type::ResumeId: + return Argument{ type, Resource::String::ResumeIdArgumentDescription, ArgumentType::Standard, true }; + case Args::Type::AllowReboot: + return Argument{ type, Resource::String::AllowRebootArgumentDescription, ArgumentType::Flag }; + case Args::Type::IgnoreResumeLimit: + return Argument{ type, Resource::String::IgnoreResumeLimitArgumentDescription, ArgumentType::Flag, ExperimentalFeature::Feature::Resume }; + case Args::Type::AllVersions: + return Argument{ type, Resource::String::UninstallAllVersionsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + case Args::Type::TargetVersion: + return Argument{ type, Resource::String::TargetVersionArgumentDescription, ArgumentType::Standard }; + case Args::Type::Proxy: + return Argument{ type, Resource::String::ProxyArgumentDescription, ArgumentType::Standard, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; + case Args::Type::NoProxy: + return Argument{ type, Resource::String::NoProxyArgumentDescription, ArgumentType::Flag, TogglePolicy::Policy::ProxyCommandLineOptions, BoolAdminSetting::ProxyCommandLineOptions }; + case Args::Type::ConfigurationProcessorPath: + return Argument{ type, Resource::String::ConfigurationProcessorPath, ArgumentType::Standard, Argument::Visibility::Help, TogglePolicy::Policy::ConfigurationProcessorPath, BoolAdminSetting::ConfigurationProcessorPath }; + case Args::Type::Family: + return Argument{ type, Resource::String::FontFamilyNameArgumentDescription, ArgumentType::Positional, false }; + case Args::Type::Details: + return Argument{ type, Resource::String::FontDetailsArgumentDescription, ArgumentType::Flag, false }; + case Args::Type::Correlation: + return Argument{ type, Resource::String::CorrelationArgumentDescription, ArgumentType::Standard, Argument::Visibility::Hidden }; + case Args::Type::ListDetails: + return Argument{ type, Resource::String::ListDetailsArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }; + default: + THROW_HR(E_UNEXPECTED); + } + } + + void Argument::GetCommon(std::vector& args) + { + args.push_back(ForType(Args::Type::Help)); + args.push_back(ForType(Args::Type::Wait)); + args.push_back(ForType(Args::Type::OpenLogs)); + args.push_back(ForType(Args::Type::NoVT)); + args.push_back(ForType(Args::Type::RainbowStyle)); + args.push_back(ForType(Args::Type::RetroStyle)); + args.push_back(ForType(Args::Type::NoProgress)); + args.push_back(ForType(Args::Type::VerboseLogs)); + args.push_back(ForType(Args::Type::IgnoreWarnings)); + args.emplace_back(Args::Type::DisableInteractivity, Resource::String::DisableInteractivityArgumentDescription, ArgumentType::Flag, false); + args.push_back(ForType(Args::Type::Proxy)); + args.push_back(ForType(Args::Type::NoProxy)); + args.push_back(ForType(Args::Type::Correlation)); + } + + std::string Argument::GetUsageString() const + { + std::ostringstream strstr; + if (Alias() != ArgumentCommon::NoAlias) + { + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Alias() << ','; + } + if (AlternateName() != Argument::NoAlternateName) + { + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << AlternateName() << ','; + } + strstr << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << Name(); + return strstr.str(); + } + + void Argument::ValidateExclusiveArguments(const Execution::Args& args) + { + auto argProperties = ArgumentCommon::GetFromExecArgs(args); + + using ExclusiveSet_t = std::underlying_type_t; + for (ExclusiveSet_t i = 1 + static_cast(ArgTypeExclusiveSet::None); i < static_cast(ArgTypeExclusiveSet::Max); i <<= 1) + { + std::vector argsFromSet; + std::copy_if( + argProperties.begin(), + argProperties.end(), + std::back_inserter(argsFromSet), + [=](const ArgumentCommon& arg) { return static_cast(arg.ExclusiveSet) & i; }); + + if (argsFromSet.size() > 1) + { + // Create a string showing the exclusive args. + std::string argsString; + for (const auto& arg : argsFromSet) + { + if (!argsString.empty()) + { + argsString += '|'; + + } + + argsString += arg.Name; + } + + throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided(Utility::LocIndString{ argsString })); + } + } + } + + void Argument::ValidateArgumentDependency(const Execution::Args& args, Execution::Args::Type type, Execution::Args::Type dependencyArgType) + { + if (args.Contains(type) && !args.Contains(dependencyArgType)) + { + throw CommandException(Resource::String::DependencyArgumentMissing( + Utility::LocIndString{ ArgumentCommon::ForType(type).Name }, + Utility::LocIndString{ ArgumentCommon::ForType(dependencyArgType).Name })); + } + } + + ArgTypeCategory Argument::GetCategoriesPresent(const Execution::Args& args) + { + auto argProperties = ArgumentCommon::GetFromExecArgs(args); + + ArgTypeCategory result = ArgTypeCategory::None; + for (const auto& arg : argProperties) + { + result |= arg.TypeCategory; + } + + return result; + } + + ArgTypeCategory Argument::GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg) + { + const auto categories = GetCategoriesPresent(args); + + // Commands like install require some argument to select a package + if (requirePackageSelectionArg) + { + if (WI_AreAllFlagsClear(categories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::SinglePackageQuery)) + { + throw CommandException(Resource::String::NoPackageSelectionArgumentProvided); + } + } + + // If a manifest is specified, we cannot also have arguments for searching + if (WI_IsFlagSet(categories, ArgTypeCategory::Manifest) && + WI_IsAnyFlagSet(categories, ArgTypeCategory::PackageQuery | ArgTypeCategory::QuerySource)) + { + throw CommandException(Resource::String::BothManifestAndSearchQueryProvided); + } + + // If we have multiple packages, we cannot have arguments that only make sense for a single package + if (WI_IsFlagSet(categories, ArgTypeCategory::MultiplePackages) && + WI_IsAnyFlagSet(categories, ArgTypeCategory::SinglePackageQuery | ArgTypeCategory::SingleInstallerBehavior)) + { + throw CommandException(Resource::String::ArgumentForSinglePackageProvidedWithMultipleQueries); + } + + return categories; + } + + Argument::Visibility Argument::GetVisibility() const + { + if (!ExperimentalFeature::IsEnabled(m_feature)) + { + return Argument::Visibility::Hidden; + } + + if (!GroupPolicies().IsEnabled(m_groupPolicy)) + { + return Argument::Visibility::Hidden; + } + + return m_visibility; + } +} diff --git a/src/AppInstallerCLICore/Argument.h b/src/AppInstallerCLICore/Argument.h index c1a573db6a..cd1f0416b1 100644 --- a/src/AppInstallerCLICore/Argument.h +++ b/src/AppInstallerCLICore/Argument.h @@ -1,267 +1,267 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" -#include "Resources.h" -#include -#include -#include -#include -#include - -#include -#include - - -#define APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR '-' -#define APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING "-" -#define APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR '=' -#define APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR '?' -#define APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING "?" -#define APPINSTALLER_CLI_HELP_ARGUMENT APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::Utility::literals; - - // The type of argument. - enum class ArgumentType - { - // Argument requires specifying the name before the value. - Standard, - // Argument value can be specified alone; position indicates argument name. - Positional, - // Only argument name can be specified and indicates a bool value. - Flag, - }; - - // Categories an arg type can belong to. - // Used to reason about the arguments present without having to repeat the same - // lists every time. - enum class ArgTypeCategory - { - None = 0, - // The --manifest argument. - Manifest = 0x1, - // Arguments for querying or selecting a package. - // E.g.: --query - PackageQuery = 0x2, - // Arguments for querying or selecting a package, which do not work for multiple packages. - // E.g.: --version - SinglePackageQuery = 0x4, - // Arguments for installer or uninstaller selection. - // E.g.: --scope - InstallerSelection = 0x8, - // Arguments for installer or uninstaller behavior. - // E.g.: --interactive - InstallerBehavior = 0x10, - // Arguments for installer or uninstaller behavior, which do not work for multiple packages. - // E.g.: --override - SingleInstallerBehavior = 0x20, - // Arguments for selecting or interacting with the source used for initial querying - // E.g.: --header - QuerySource = 0x40, - // Arguments that only make sense when talking about multiple packages - MultiplePackages = 0x80, - // Flag arguments that should be copied over when creating a sub-context - CopyFlagToSubContext = 0x100, - // Arguments with associated values that should be copied over when creating a sub-context - CopyValueToSubContext = 0x200, - // Arguments for selecting or interacting with dependencies or setting specific source behaviors - // E.g.: --dependency-source - // E.g.: --accept-source-agreements - ExtendedSource = 0x400, - // Arguments for selecting a configuration set (file or history). - ConfigurationSetChoice = 0x800, - }; - - DEFINE_ENUM_FLAG_OPERATORS(ArgTypeCategory); - - // Exclusive sets an argument can belong to. - // Only one argument from each exclusive set is allowed at a time. - enum class ArgTypeExclusiveSet : uint32_t - { - None = 0x0, - ProgressBarOption = 0x1, - EnableDisable = 0x2, - PurgePreserve = 0x4, - PinType = 0x8, - StubType = 0x10, - Proxy = 0x20, - AllAndTargetVersion = 0x40, - ConfigurationSetChoice = 0x80, - DscResourceFunction = 0x100, - DependenciesConflict = 0x200, - SortDirection = 0x400, - - // This must always be at the end - Max - }; - - DEFINE_ENUM_FLAG_OPERATORS(ArgTypeExclusiveSet); - - // An argument to a command; containing only data that is common to all its uses. - // Argument extends this by adding command-specific values, like help strings. - struct ArgumentCommon - { - // Defines an argument with no alias. - constexpr static char NoAlias = '\0'; - - ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, char alias, Utility::LocIndView alternateName, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) - : Type(execArgType), Name(name), Alias(alias), AlternateName(alternateName), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} - - ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, char alias, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) - : Type(execArgType), Name(name), Alias(alias), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} - - ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, Utility::LocIndView alternateName, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) - : Type(execArgType), Name(name), Alias(NoAlias), AlternateName(alternateName), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} - - ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) - : Type(execArgType), Name(name), Alias(NoAlias), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} - - // Gets the argument for the given type. - static ArgumentCommon ForType(Execution::Args::Type execArgType); - - static std::vector GetFromExecArgs(const Execution::Args& execArgs); - - Execution::Args::Type Type; - Utility::LocIndView Name; - char Alias; - Utility::LocIndView AlternateName; - ArgTypeCategory TypeCategory; - ArgTypeExclusiveSet ExclusiveSet; - }; - - // An argument to a command. - struct Argument - { - // Controls the visibility of the field. - enum class Visibility - { - // Shown in the example. - Example, - // Shown only in the table below the example. - Help, - // Not shown in help. - Hidden, - }; - - // Defines an argument with no alternate name - constexpr static std::string_view NoAlternateName = ""; - - Argument(Execution::Args::Type execArgType, Resource::StringId desc) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, bool required) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_required(required) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_required(required) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required) {} - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Constructors for arguments with custom names and aliases to use in tests - Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : - m_argCommon(execArgType, Utility::LocIndView{ name }, alias), m_desc(std::move(desc)), m_type(type) {} - - Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : - m_argCommon(execArgType, Utility::LocIndView{ name }, alias, Utility::LocIndView{ alternateName }), m_desc(std::move(desc)), m_type(type) {} -#endif - - ~Argument() = default; - - Argument(const Argument&) = default; - Argument& operator=(const Argument&) = default; - - Argument(Argument&&) = default; - Argument& operator=(Argument&&) = default; - - // Gets the argument for the given type. - static Argument ForType(Execution::Args::Type type); - - // Gets the common arguments for all commands. - static void GetCommon(std::vector& args); - - // Static argument validation helpers; throw CommandException when validation fails. - - // Requires that at most one argument from the list is present. - static void ValidateExclusiveArguments(const Execution::Args& args); - - // Requires that if an argument depends on another one, it is not present without the dependency. - static void ValidateArgumentDependency(const Execution::Args& args, Execution::Args::Type type, Execution::Args::Type dependencyArgType); - - static ArgTypeCategory GetCategoriesPresent(const Execution::Args& arg); - - // Requires that arguments meet common requirements - static ArgTypeCategory GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg = true); - static void ValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg = true) { std::ignore = GetCategoriesAndValidateCommonArguments(args, requirePackageSelectionArg); } - - // Gets the argument usage string in the format of "-alias,--name". - std::string GetUsageString() const; - - // Arguments are not localized at this time. - Utility::LocIndView Name() const { return m_argCommon.Name; } - char Alias() const { return m_argCommon.Alias; } - std::string_view AlternateName() const { return m_argCommon.AlternateName; } - Execution::Args::Type ExecArgType() const { return m_argCommon.Type; } - const Resource::StringId& Description() const { return m_desc; } - bool Required() const { return m_required; } - ArgumentType Type() const { return m_type; } - size_t Limit() const { return m_countLimit; } - Argument::Visibility GetVisibility() const; - Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } - Settings::TogglePolicy::Policy GroupPolicy() const { return m_groupPolicy; } - Settings::BoolAdminSetting AdminSetting() const { return m_adminSetting; } - - Argument& SetRequired(bool required) { m_required = required; return *this; } - Argument& SetCountLimit(size_t countLimit) { m_countLimit = countLimit; return *this; } - - private: - // Constructors that set a Feature or Policy are private to force callers to go through the ForType() function. - // This helps keep it all in one place to reduce chances of missing it somewhere. - Argument(Execution::Args::Type execArgType, Resource::StringId desc, Settings::ExperimentalFeature::Feature feature) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_feature(feature) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, bool required, Settings::ExperimentalFeature::Feature feature) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_required(required), m_feature(feature) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_feature(feature) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::ExperimentalFeature::Feature feature) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_feature(feature) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required, Settings::ExperimentalFeature::Feature feature) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_required(required), m_feature(feature) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required, Settings::ExperimentalFeature::Feature feature) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required), m_feature(feature) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - - Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : - m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_feature(feature), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} - - ArgumentCommon m_argCommon; - Resource::StringId m_desc; - bool m_required = false; - ArgumentType m_type = ArgumentType::Standard; - Argument::Visibility m_visibility = Argument::Visibility::Example; - size_t m_countLimit = 1; - Settings::ExperimentalFeature::Feature m_feature = Settings::ExperimentalFeature::Feature::None; - Settings::TogglePolicy::Policy m_groupPolicy = Settings::TogglePolicy::Policy::None; - Settings::BoolAdminSetting m_adminSetting = Settings::BoolAdminSetting::Unknown; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" +#include "Resources.h" +#include +#include +#include +#include +#include + +#include +#include + + +#define APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR '-' +#define APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING "-" +#define APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR '=' +#define APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_CHAR '?' +#define APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING "?" +#define APPINSTALLER_CLI_HELP_ARGUMENT APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING APPINSTALLER_CLI_HELP_ARGUMENT_TEXT_STRING + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::Utility::literals; + + // The type of argument. + enum class ArgumentType + { + // Argument requires specifying the name before the value. + Standard, + // Argument value can be specified alone; position indicates argument name. + Positional, + // Only argument name can be specified and indicates a bool value. + Flag, + }; + + // Categories an arg type can belong to. + // Used to reason about the arguments present without having to repeat the same + // lists every time. + enum class ArgTypeCategory + { + None = 0, + // The --manifest argument. + Manifest = 0x1, + // Arguments for querying or selecting a package. + // E.g.: --query + PackageQuery = 0x2, + // Arguments for querying or selecting a package, which do not work for multiple packages. + // E.g.: --version + SinglePackageQuery = 0x4, + // Arguments for installer or uninstaller selection. + // E.g.: --scope + InstallerSelection = 0x8, + // Arguments for installer or uninstaller behavior. + // E.g.: --interactive + InstallerBehavior = 0x10, + // Arguments for installer or uninstaller behavior, which do not work for multiple packages. + // E.g.: --override + SingleInstallerBehavior = 0x20, + // Arguments for selecting or interacting with the source used for initial querying + // E.g.: --header + QuerySource = 0x40, + // Arguments that only make sense when talking about multiple packages + MultiplePackages = 0x80, + // Flag arguments that should be copied over when creating a sub-context + CopyFlagToSubContext = 0x100, + // Arguments with associated values that should be copied over when creating a sub-context + CopyValueToSubContext = 0x200, + // Arguments for selecting or interacting with dependencies or setting specific source behaviors + // E.g.: --dependency-source + // E.g.: --accept-source-agreements + ExtendedSource = 0x400, + // Arguments for selecting a configuration set (file or history). + ConfigurationSetChoice = 0x800, + }; + + DEFINE_ENUM_FLAG_OPERATORS(ArgTypeCategory); + + // Exclusive sets an argument can belong to. + // Only one argument from each exclusive set is allowed at a time. + enum class ArgTypeExclusiveSet : uint32_t + { + None = 0x0, + ProgressBarOption = 0x1, + EnableDisable = 0x2, + PurgePreserve = 0x4, + PinType = 0x8, + StubType = 0x10, + Proxy = 0x20, + AllAndTargetVersion = 0x40, + ConfigurationSetChoice = 0x80, + DscResourceFunction = 0x100, + DependenciesConflict = 0x200, + SortDirection = 0x400, + + // This must always be at the end + Max + }; + + DEFINE_ENUM_FLAG_OPERATORS(ArgTypeExclusiveSet); + + // An argument to a command; containing only data that is common to all its uses. + // Argument extends this by adding command-specific values, like help strings. + struct ArgumentCommon + { + // Defines an argument with no alias. + constexpr static char NoAlias = '\0'; + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, char alias, Utility::LocIndView alternateName, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(alias), AlternateName(alternateName), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, char alias, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(alias), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, Utility::LocIndView alternateName, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(NoAlias), AlternateName(alternateName), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + ArgumentCommon(Execution::Args::Type execArgType, Utility::LocIndView name, ArgTypeCategory typeCategory = ArgTypeCategory::None, ArgTypeExclusiveSet exclusiveSet = ArgTypeExclusiveSet::None) + : Type(execArgType), Name(name), Alias(NoAlias), TypeCategory(typeCategory), ExclusiveSet(exclusiveSet) {} + + // Gets the argument for the given type. + static ArgumentCommon ForType(Execution::Args::Type execArgType); + + static std::vector GetFromExecArgs(const Execution::Args& execArgs); + + Execution::Args::Type Type; + Utility::LocIndView Name; + char Alias; + Utility::LocIndView AlternateName; + ArgTypeCategory TypeCategory; + ArgTypeExclusiveSet ExclusiveSet; + }; + + // An argument to a command. + struct Argument + { + // Controls the visibility of the field. + enum class Visibility + { + // Shown in the example. + Example, + // Shown only in the table below the example. + Help, + // Not shown in help. + Hidden, + }; + + // Defines an argument with no alternate name + constexpr static std::string_view NoAlternateName = ""; + + Argument(Execution::Args::Type execArgType, Resource::StringId desc) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, bool required) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_required(required) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_required(required) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required) {} + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Constructors for arguments with custom names and aliases to use in tests + Argument(std::string_view name, char alias, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : + m_argCommon(execArgType, Utility::LocIndView{ name }, alias), m_desc(std::move(desc)), m_type(type) {} + + Argument(std::string_view name, char alias, std::string_view alternateName, Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type) : + m_argCommon(execArgType, Utility::LocIndView{ name }, alias, Utility::LocIndView{ alternateName }), m_desc(std::move(desc)), m_type(type) {} +#endif + + ~Argument() = default; + + Argument(const Argument&) = default; + Argument& operator=(const Argument&) = default; + + Argument(Argument&&) = default; + Argument& operator=(Argument&&) = default; + + // Gets the argument for the given type. + static Argument ForType(Execution::Args::Type type); + + // Gets the common arguments for all commands. + static void GetCommon(std::vector& args); + + // Static argument validation helpers; throw CommandException when validation fails. + + // Requires that at most one argument from the list is present. + static void ValidateExclusiveArguments(const Execution::Args& args); + + // Requires that if an argument depends on another one, it is not present without the dependency. + static void ValidateArgumentDependency(const Execution::Args& args, Execution::Args::Type type, Execution::Args::Type dependencyArgType); + + static ArgTypeCategory GetCategoriesPresent(const Execution::Args& arg); + + // Requires that arguments meet common requirements + static ArgTypeCategory GetCategoriesAndValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg = true); + static void ValidateCommonArguments(const Execution::Args& args, bool requirePackageSelectionArg = true) { std::ignore = GetCategoriesAndValidateCommonArguments(args, requirePackageSelectionArg); } + + // Gets the argument usage string in the format of "-alias,--name". + std::string GetUsageString() const; + + // Arguments are not localized at this time. + Utility::LocIndView Name() const { return m_argCommon.Name; } + char Alias() const { return m_argCommon.Alias; } + std::string_view AlternateName() const { return m_argCommon.AlternateName; } + Execution::Args::Type ExecArgType() const { return m_argCommon.Type; } + const Resource::StringId& Description() const { return m_desc; } + bool Required() const { return m_required; } + ArgumentType Type() const { return m_type; } + size_t Limit() const { return m_countLimit; } + Argument::Visibility GetVisibility() const; + Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } + Settings::TogglePolicy::Policy GroupPolicy() const { return m_groupPolicy; } + Settings::BoolAdminSetting AdminSetting() const { return m_adminSetting; } + + Argument& SetRequired(bool required) { m_required = required; return *this; } + Argument& SetCountLimit(size_t countLimit) { m_countLimit = countLimit; return *this; } + + private: + // Constructors that set a Feature or Policy are private to force callers to go through the ForType() function. + // This helps keep it all in one place to reduce chances of missing it somewhere. + Argument(Execution::Args::Type execArgType, Resource::StringId desc, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_feature(feature) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, bool required, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_required(required), m_feature(feature) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_feature(feature) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_feature(feature) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, bool required, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_required(required), m_feature(feature) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, bool required, Settings::ExperimentalFeature::Feature feature) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_required(required), m_feature(feature) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Argument::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_visibility(visibility), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} + + Argument(Execution::Args::Type execArgType, Resource::StringId desc, ArgumentType type, Settings::ExperimentalFeature::Feature feature, Settings::TogglePolicy::Policy groupPolicy, Settings::BoolAdminSetting adminSetting) : + m_argCommon(ArgumentCommon::ForType(execArgType)), m_desc(std::move(desc)), m_type(type), m_feature(feature), m_groupPolicy(groupPolicy), m_adminSetting(adminSetting) {} + + ArgumentCommon m_argCommon; + Resource::StringId m_desc; + bool m_required = false; + ArgumentType m_type = ArgumentType::Standard; + Argument::Visibility m_visibility = Argument::Visibility::Example; + size_t m_countLimit = 1; + Settings::ExperimentalFeature::Feature m_feature = Settings::ExperimentalFeature::Feature::None; + Settings::TogglePolicy::Policy m_groupPolicy = Settings::TogglePolicy::Policy::None; + Settings::BoolAdminSetting m_adminSetting = Settings::BoolAdminSetting::Unknown; + }; +} diff --git a/src/AppInstallerCLICore/COMContext.cpp b/src/AppInstallerCLICore/COMContext.cpp index f24a853437..bd7bea1dbd 100644 --- a/src/AppInstallerCLICore/COMContext.cpp +++ b/src/AppInstallerCLICore/COMContext.cpp @@ -1,106 +1,106 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "COMContext.h" -#include -#include -#include - -namespace AppInstaller::CLI::Execution -{ - static constexpr std::string_view s_comLogFileNamePrefix = "WinGetCOM"sv; - - NullStream::NullStream() - { - m_nullOut.reset(new std::ostream(&m_nullStreamBuf)); - m_nullIn.reset(new std::istream(&m_nullStreamBuf)); - } - - void COMContext::AddProgressCallbackFunction(ProgressCallBackFunction&& f) - { - std::lock_guard lock{ m_callbackLock }; - m_comProgressCallbacks.push_back(std::move(f)); - } - - void COMContext::FireCallbacks(ReportType reportType, uint64_t current, uint64_t maximum, ProgressType progressType, ::AppInstaller::CLI::Workflow::ExecutionStage executionPhase) - { - // Lock around iterating through the list. Callbacks should not do long running tasks. - std::lock_guard lock{ m_callbackLock }; - for (auto& callback : m_comProgressCallbacks) - { - callback(reportType, current, maximum, progressType, executionPhase); - } - }; - - void COMContext::BeginProgress() - { - FireCallbacks(ReportType::BeginProgress, 0, 0, ProgressType::None, m_executionStage); - }; - - void COMContext::OnProgress(uint64_t current, uint64_t maximum, ProgressType progressType) - { - FireCallbacks(ReportType::Progressing, current, maximum, progressType, m_executionStage); - } - - void COMContext::SetProgressMessage(std::string_view) - { - // TODO: Consider sending message to COM progress - } - - void COMContext::EndProgress(bool) - { - FireCallbacks(ReportType::EndProgress, 0, 0, ProgressType::None, m_executionStage); - }; - - void COMContext::SetExecutionStage(CLI::Workflow::ExecutionStage executionStage) - { - m_executionStage = executionStage; - FireCallbacks(ReportType::ExecutionPhaseUpdate, 0, 0, ProgressType::None, m_executionStage); - GetThreadGlobals().GetTelemetryLogger().SetExecutionStage(static_cast(m_executionStage)); - } - - void COMContext::SetContextLoggers(const std::wstring_view telemetryCorrelationJson, const std::string& caller) - { - m_correlationData = telemetryCorrelationJson; - - std::unique_ptr setThreadGlobalsToPreviousState = this->SetForCurrentThread(); - - SetLoggers(); - GetThreadGlobals().GetTelemetryLogger().SetTelemetryCorrelationJson(telemetryCorrelationJson); - GetThreadGlobals().GetTelemetryLogger().SetCaller(caller); - GetThreadGlobals().GetTelemetryLogger().LogStartup(true); - } - - std::wstring_view COMContext::GetCorrelationJson() - { - return m_correlationData; - } - - void COMContext::SetLoggers(std::optional channel, std::optional level) - { - // Set up debug string logging during initialization - Logging::OutputDebugStringLogger::Add(); - Logging::Log().SetEnabledChannels(Logging::Channel::All); - Logging::Log().SetLevel(Logging::Level::Verbose); - - Logging::Log().SetEnabledChannels(channel.has_value() ? channel.value() : Settings::User().Get()); - Logging::Log().SetLevel(level.has_value() ? level.value() : Settings::User().Get()); - - // TODO: Log to file for COM API calls only when debugging in visual studio - Logging::FileLogger::Add(s_comLogFileNamePrefix); - - Logging::OutputDebugStringLogger::Remove(); - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (!Settings::User().Get()) -#endif - { - // Initiate the background cleanup of the log file location. - Logging::FileLogger::BeginCleanup(); - } - - Logging::TraceLogger::Add(); - - Logging::EnableWilFailureTelemetry(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "COMContext.h" +#include +#include +#include + +namespace AppInstaller::CLI::Execution +{ + static constexpr std::string_view s_comLogFileNamePrefix = "WinGetCOM"sv; + + NullStream::NullStream() + { + m_nullOut.reset(new std::ostream(&m_nullStreamBuf)); + m_nullIn.reset(new std::istream(&m_nullStreamBuf)); + } + + void COMContext::AddProgressCallbackFunction(ProgressCallBackFunction&& f) + { + std::lock_guard lock{ m_callbackLock }; + m_comProgressCallbacks.push_back(std::move(f)); + } + + void COMContext::FireCallbacks(ReportType reportType, uint64_t current, uint64_t maximum, ProgressType progressType, ::AppInstaller::CLI::Workflow::ExecutionStage executionPhase) + { + // Lock around iterating through the list. Callbacks should not do long running tasks. + std::lock_guard lock{ m_callbackLock }; + for (auto& callback : m_comProgressCallbacks) + { + callback(reportType, current, maximum, progressType, executionPhase); + } + }; + + void COMContext::BeginProgress() + { + FireCallbacks(ReportType::BeginProgress, 0, 0, ProgressType::None, m_executionStage); + }; + + void COMContext::OnProgress(uint64_t current, uint64_t maximum, ProgressType progressType) + { + FireCallbacks(ReportType::Progressing, current, maximum, progressType, m_executionStage); + } + + void COMContext::SetProgressMessage(std::string_view) + { + // TODO: Consider sending message to COM progress + } + + void COMContext::EndProgress(bool) + { + FireCallbacks(ReportType::EndProgress, 0, 0, ProgressType::None, m_executionStage); + }; + + void COMContext::SetExecutionStage(CLI::Workflow::ExecutionStage executionStage) + { + m_executionStage = executionStage; + FireCallbacks(ReportType::ExecutionPhaseUpdate, 0, 0, ProgressType::None, m_executionStage); + GetThreadGlobals().GetTelemetryLogger().SetExecutionStage(static_cast(m_executionStage)); + } + + void COMContext::SetContextLoggers(const std::wstring_view telemetryCorrelationJson, const std::string& caller) + { + m_correlationData = telemetryCorrelationJson; + + std::unique_ptr setThreadGlobalsToPreviousState = this->SetForCurrentThread(); + + SetLoggers(); + GetThreadGlobals().GetTelemetryLogger().SetTelemetryCorrelationJson(telemetryCorrelationJson); + GetThreadGlobals().GetTelemetryLogger().SetCaller(caller); + GetThreadGlobals().GetTelemetryLogger().LogStartup(true); + } + + std::wstring_view COMContext::GetCorrelationJson() + { + return m_correlationData; + } + + void COMContext::SetLoggers(std::optional channel, std::optional level) + { + // Set up debug string logging during initialization + Logging::OutputDebugStringLogger::Add(); + Logging::Log().SetEnabledChannels(Logging::Channel::All); + Logging::Log().SetLevel(Logging::Level::Verbose); + + Logging::Log().SetEnabledChannels(channel.has_value() ? channel.value() : Settings::User().Get()); + Logging::Log().SetLevel(level.has_value() ? level.value() : Settings::User().Get()); + + // TODO: Log to file for COM API calls only when debugging in visual studio + Logging::FileLogger::Add(s_comLogFileNamePrefix); + + Logging::OutputDebugStringLogger::Remove(); + +#ifndef AICLI_DISABLE_TEST_HOOKS + if (!Settings::User().Get()) +#endif + { + // Initiate the background cleanup of the log file location. + Logging::FileLogger::BeginCleanup(); + } + + Logging::TraceLogger::Add(); + + Logging::EnableWilFailureTelemetry(); + } +} diff --git a/src/AppInstallerCLICore/COMContext.h b/src/AppInstallerCLICore/COMContext.h index 9e4d136a52..36218d1676 100644 --- a/src/AppInstallerCLICore/COMContext.h +++ b/src/AppInstallerCLICore/COMContext.h @@ -1,85 +1,85 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AppInstallerProgress.h" -#include "ExecutionContext.h" -#include "Workflows/WorkflowBase.h" - -namespace AppInstaller::CLI::Execution -{ - enum class ReportType : uint32_t - { - ExecutionPhaseUpdate, - BeginProgress, - Progressing, - EndProgress, - }; - - class NullStreamBuf : public std::streambuf {}; - - struct NullStream - { - NullStream(); - - ~NullStream() = default; - - protected: - NullStreamBuf m_nullStreamBuf; - std::unique_ptr m_nullOut; - std::unique_ptr m_nullIn; - }; - - typedef std::function ProgressCallBackFunction; - - // NullStream constructs the Stream parameters for Context constructor - // Hence, NullStream should always precede Context in base class order of COMContext's inheritance - struct COMContext : IProgressSink, NullStream, CLI::Execution::Context - { - // When no Console streams need involvement, construct NullStreams instead to pass to Context - COMContext() : NullStream(), CLI::Execution::Context(*m_nullOut, *m_nullIn) - { - Reporter.SetChannel(Reporter::Channel::Disabled); - Reporter.SetProgressSink(this); - SetFlags(CLI::Execution::ContextFlag::DisableInteractivity); - } - - COMContext(std::ostream& out, std::istream& in) : CLI::Execution::Context(out, in) - { - Reporter.SetProgressSink(this); - SetFlags(CLI::Execution::ContextFlag::DisableInteractivity); - } - - ~COMContext() = default; - - // IProgressSink - void BeginProgress() override; - void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; - void SetProgressMessage(std::string_view message) override; - void EndProgress(bool) override; - - //Execution::Context - void SetExecutionStage(CLI::Workflow::ExecutionStage executionPhase); - - CLI::Workflow::ExecutionStage GetExecutionStage() const { return m_executionStage; } - - void AddProgressCallbackFunction(ProgressCallBackFunction&& f); - - // Set Diagnostic and Telemetry loggers, Wil failure callback - // This should be called only once per COM Server instance - static void SetLoggers(std::optional channel = std::nullopt, std::optional level = std::nullopt); - - // Set COM call context for diagnostic and telemetry loggers - // This should be called for every COMContext object instance - void SetContextLoggers(const std::wstring_view telemetryCorrelationJson, const std::string& caller); - - std::wstring_view GetCorrelationJson(); - - private: - void FireCallbacks(ReportType reportType, uint64_t current, uint64_t maximum, ProgressType progressType, ::AppInstaller::CLI::Workflow::ExecutionStage executionPhase); - - CLI::Workflow::ExecutionStage m_executionStage = CLI::Workflow::ExecutionStage::Initial; - std::vector m_comProgressCallbacks; - std::wstring m_correlationData = L""; - std::mutex m_callbackLock; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AppInstallerProgress.h" +#include "ExecutionContext.h" +#include "Workflows/WorkflowBase.h" + +namespace AppInstaller::CLI::Execution +{ + enum class ReportType : uint32_t + { + ExecutionPhaseUpdate, + BeginProgress, + Progressing, + EndProgress, + }; + + class NullStreamBuf : public std::streambuf {}; + + struct NullStream + { + NullStream(); + + ~NullStream() = default; + + protected: + NullStreamBuf m_nullStreamBuf; + std::unique_ptr m_nullOut; + std::unique_ptr m_nullIn; + }; + + typedef std::function ProgressCallBackFunction; + + // NullStream constructs the Stream parameters for Context constructor + // Hence, NullStream should always precede Context in base class order of COMContext's inheritance + struct COMContext : IProgressSink, NullStream, CLI::Execution::Context + { + // When no Console streams need involvement, construct NullStreams instead to pass to Context + COMContext() : NullStream(), CLI::Execution::Context(*m_nullOut, *m_nullIn) + { + Reporter.SetChannel(Reporter::Channel::Disabled); + Reporter.SetProgressSink(this); + SetFlags(CLI::Execution::ContextFlag::DisableInteractivity); + } + + COMContext(std::ostream& out, std::istream& in) : CLI::Execution::Context(out, in) + { + Reporter.SetProgressSink(this); + SetFlags(CLI::Execution::ContextFlag::DisableInteractivity); + } + + ~COMContext() = default; + + // IProgressSink + void BeginProgress() override; + void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; + void SetProgressMessage(std::string_view message) override; + void EndProgress(bool) override; + + //Execution::Context + void SetExecutionStage(CLI::Workflow::ExecutionStage executionPhase); + + CLI::Workflow::ExecutionStage GetExecutionStage() const { return m_executionStage; } + + void AddProgressCallbackFunction(ProgressCallBackFunction&& f); + + // Set Diagnostic and Telemetry loggers, Wil failure callback + // This should be called only once per COM Server instance + static void SetLoggers(std::optional channel = std::nullopt, std::optional level = std::nullopt); + + // Set COM call context for diagnostic and telemetry loggers + // This should be called for every COMContext object instance + void SetContextLoggers(const std::wstring_view telemetryCorrelationJson, const std::string& caller); + + std::wstring_view GetCorrelationJson(); + + private: + void FireCallbacks(ReportType reportType, uint64_t current, uint64_t maximum, ProgressType progressType, ::AppInstaller::CLI::Workflow::ExecutionStage executionPhase); + + CLI::Workflow::ExecutionStage m_executionStage = CLI::Workflow::ExecutionStage::Initial; + std::vector m_comProgressCallbacks; + std::wstring m_correlationData = L""; + std::mutex m_callbackLock; + }; +} diff --git a/src/AppInstallerCLICore/ChannelStreams.cpp b/src/AppInstallerCLICore/ChannelStreams.cpp index 7095df2fd3..55c65f3329 100644 --- a/src/AppInstallerCLICore/ChannelStreams.cpp +++ b/src/AppInstallerCLICore/ChannelStreams.cpp @@ -1,180 +1,180 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ChannelStreams.h" - - -namespace AppInstaller::CLI::Execution -{ - using namespace VirtualTerminal; - -#ifndef AICLI_DISABLE_TEST_HOOKS - static std::optional* s_consoleWidthOverride = nullptr; - - void TestHook_SetConsoleWidth_Override(std::optional* value) - { - s_consoleWidthOverride = value; - } -#endif - - std::optional GetConsoleWidth() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_consoleWidthOverride) - { - return *s_consoleWidthOverride; - } -#endif - CONSOLE_SCREEN_BUFFER_INFO consoleInfo{}; - if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) - { - return static_cast(consoleInfo.dwSize.X); - } - else - { - return std::nullopt; - } - } - - BaseStream::BaseStream(std::ostream& out, bool enabled, bool VTEnabled) : - m_out(out), m_enabled(enabled), m_VTEnabled(VTEnabled) {} - - BaseStream& BaseStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) - { - if (m_enabled) - { - f(m_out); - } - return *this; - } - - BaseStream& BaseStream::operator<<(const Sequence& sequence) - { - if (m_enabled && m_VTEnabled) - { - m_VTUpdated = true; - m_out << sequence; - } - return *this; - } - - BaseStream& BaseStream::operator<<(const ConstructedSequence& sequence) - { - if (m_enabled && m_VTEnabled) - { - m_VTUpdated = true; - m_out << sequence; - } - return *this; - } - - void BaseStream::SetVTEnabled(bool enabled) - { - m_VTEnabled = enabled; - } - - void BaseStream::RestoreDefault() - { - if (m_VTUpdated) - { - Write(TextFormat::Default, true); - } - } - - void BaseStream::Disable() - { - m_enabled = false; - } - - std::ostream& BaseStream::Get() - { - return m_out; - } - - OutputStream::OutputStream(BaseStream& out, bool enabled, bool VTEnabled) : - m_out(out), - m_enabled(enabled), - m_VTEnabled(VTEnabled) - {} - - void OutputStream::AddFormat(const Sequence& sequence) - { - m_format.Append(sequence); - } - - void OutputStream::ClearFormat() - { - m_format.Clear(); - } - - void OutputStream::ApplyFormat() - { - // Only apply format if m_applyFormatAtOne == 1 coming into this function. - if (m_applyFormatAtOne) - { - if (!--m_applyFormatAtOne) - { - m_out << m_format; - } - } - } - - OutputStream& OutputStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) - { - if (m_enabled) - { - m_out << f; - } - - return *this; - } - - OutputStream& OutputStream::operator<<(const Sequence& sequence) - { - if (m_enabled && m_VTEnabled) - { - // Apply format as normal to ensure that any previous format doesn't bleed through. - ApplyFormat(); - - m_out << sequence; - - // An incoming sequence will be valid for 1 "standard" output after this one. - // We set this to 2 to make that happen, because when it is 1, we will output - // the format for the current OutputStream. - m_applyFormatAtOne = 2; - } - - return *this; - } - - OutputStream& OutputStream::operator<<(const ConstructedSequence& sequence) - { - if (m_enabled && m_VTEnabled) - { - // Apply format as normal to ensure that any previous format doesn't bleed through. - ApplyFormat(); - - m_out << sequence; - // An incoming sequence will be valid for 1 "standard" output after this one. - // We set this to 2 to make that happen, because when it is 1, we will output - // the format for the current OutputStream. - m_applyFormatAtOne = 2; - } - - return *this; - } - - OutputStream& OutputStream::operator<<(const std::filesystem::path& path) - { - if (m_enabled) - { - if (m_VTEnabled) - { - ApplyFormat(); - } - m_out << path.u8string(); - } - return *this; - - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ChannelStreams.h" + + +namespace AppInstaller::CLI::Execution +{ + using namespace VirtualTerminal; + +#ifndef AICLI_DISABLE_TEST_HOOKS + static std::optional* s_consoleWidthOverride = nullptr; + + void TestHook_SetConsoleWidth_Override(std::optional* value) + { + s_consoleWidthOverride = value; + } +#endif + + std::optional GetConsoleWidth() + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_consoleWidthOverride) + { + return *s_consoleWidthOverride; + } +#endif + CONSOLE_SCREEN_BUFFER_INFO consoleInfo{}; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) + { + return static_cast(consoleInfo.dwSize.X); + } + else + { + return std::nullopt; + } + } + + BaseStream::BaseStream(std::ostream& out, bool enabled, bool VTEnabled) : + m_out(out), m_enabled(enabled), m_VTEnabled(VTEnabled) {} + + BaseStream& BaseStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) + { + if (m_enabled) + { + f(m_out); + } + return *this; + } + + BaseStream& BaseStream::operator<<(const Sequence& sequence) + { + if (m_enabled && m_VTEnabled) + { + m_VTUpdated = true; + m_out << sequence; + } + return *this; + } + + BaseStream& BaseStream::operator<<(const ConstructedSequence& sequence) + { + if (m_enabled && m_VTEnabled) + { + m_VTUpdated = true; + m_out << sequence; + } + return *this; + } + + void BaseStream::SetVTEnabled(bool enabled) + { + m_VTEnabled = enabled; + } + + void BaseStream::RestoreDefault() + { + if (m_VTUpdated) + { + Write(TextFormat::Default, true); + } + } + + void BaseStream::Disable() + { + m_enabled = false; + } + + std::ostream& BaseStream::Get() + { + return m_out; + } + + OutputStream::OutputStream(BaseStream& out, bool enabled, bool VTEnabled) : + m_out(out), + m_enabled(enabled), + m_VTEnabled(VTEnabled) + {} + + void OutputStream::AddFormat(const Sequence& sequence) + { + m_format.Append(sequence); + } + + void OutputStream::ClearFormat() + { + m_format.Clear(); + } + + void OutputStream::ApplyFormat() + { + // Only apply format if m_applyFormatAtOne == 1 coming into this function. + if (m_applyFormatAtOne) + { + if (!--m_applyFormatAtOne) + { + m_out << m_format; + } + } + } + + OutputStream& OutputStream::operator<<(std::ostream& (__cdecl* f)(std::ostream&)) + { + if (m_enabled) + { + m_out << f; + } + + return *this; + } + + OutputStream& OutputStream::operator<<(const Sequence& sequence) + { + if (m_enabled && m_VTEnabled) + { + // Apply format as normal to ensure that any previous format doesn't bleed through. + ApplyFormat(); + + m_out << sequence; + + // An incoming sequence will be valid for 1 "standard" output after this one. + // We set this to 2 to make that happen, because when it is 1, we will output + // the format for the current OutputStream. + m_applyFormatAtOne = 2; + } + + return *this; + } + + OutputStream& OutputStream::operator<<(const ConstructedSequence& sequence) + { + if (m_enabled && m_VTEnabled) + { + // Apply format as normal to ensure that any previous format doesn't bleed through. + ApplyFormat(); + + m_out << sequence; + // An incoming sequence will be valid for 1 "standard" output after this one. + // We set this to 2 to make that happen, because when it is 1, we will output + // the format for the current OutputStream. + m_applyFormatAtOne = 2; + } + + return *this; + } + + OutputStream& OutputStream::operator<<(const std::filesystem::path& path) + { + if (m_enabled) + { + if (m_VTEnabled) + { + ApplyFormat(); + } + m_out << path.u8string(); + } + return *this; + + } +} diff --git a/src/AppInstallerCLICore/ChannelStreams.h b/src/AppInstallerCLICore/ChannelStreams.h index 8e168e380e..b858aba73e 100644 --- a/src/AppInstallerCLICore/ChannelStreams.h +++ b/src/AppInstallerCLICore/ChannelStreams.h @@ -1,118 +1,118 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Resources.h" -#include "VTSupport.h" -#include - -#include -#include -#include - - -namespace AppInstaller::CLI::Execution -{ - // Gets the current console width, or std::nullopt if stdout is not attached to a console - // (e.g. redirected to a file or pipe). Callers that receive nullopt should not truncate output. - std::optional GetConsoleWidth(); - - // The base stream for all channels. - struct BaseStream - { - BaseStream(std::ostream& out, bool enabled, bool VTEnabled); - - template - BaseStream& operator<<(const T& t) - { - Write(t, false); - return *this; - } - - BaseStream& operator<<(std::ostream& (__cdecl* f)(std::ostream&)); - BaseStream& operator<<(const VirtualTerminal::Sequence& sequence); - BaseStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence); - - void SetVTEnabled(bool enabled); - - void RestoreDefault(); - - void Disable(); - - std::ostream& Get(); - - private: - template - void Write(const T& t, bool bypass) - { - if (bypass || m_enabled) - { - m_out << t; - } - }; - - std::ostream& m_out; - std::atomic_bool m_enabled; - std::atomic_bool m_VTUpdated; - bool m_VTEnabled; - }; - - // Holds output formatting information. - struct OutputStream - { - OutputStream(BaseStream& out, bool enabled, bool VTEnabled = true); - - // Adds a format to the current value. - void AddFormat(const VirtualTerminal::Sequence& sequence); - - // Clears the current format value. - void ClearFormat(); - - template - OutputStream& operator<<(const T& t) - { - // You've found your way here because you tried to output a type that may not localized. - // In order to ensure that all output is localized, only the types with specializations of - // details::IsApprovedForOutput above can be output. - // * If your string is a simple message, it should be put in - // /src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw - // and referenced in /src/AppInstallerCLICore/Resources.h. Then either output the - // Resource::StringId, or load it manually and output the Resource::LocString. - // * If your string is *definitely* localization independent, you can tag it as such with - // the Utility::LocInd(View/String) types. - // * If your string came from outside of the source code, it is best to store it in a - // Utility::NormalizedString so that it has a normalized representation. This also - // informs the output that there is no localized version to use. - // TODO: This assertion is currently only applied to placeholders in localized strings. - // Convert the rest of the code base and uncomment to enforce localization. - //static_assert(details::IsApprovedForOutput>::value, "This type may not be localized, see comment for more information"); - if (m_enabled) - { - if (m_VTEnabled) - { - ApplyFormat(); - } - m_out << t; - } - return *this; - } - - OutputStream& operator<<(std::ostream& (__cdecl* f)(std::ostream&)); - OutputStream& operator<<(const VirtualTerminal::Sequence& sequence); - OutputStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence); - OutputStream& operator<<(const std::filesystem::path& path); - - inline bool IsEnabled() { - return m_enabled; - } - - private: - // Applies the format for the stream. - void ApplyFormat(); - - BaseStream& m_out; - bool m_enabled; - bool m_VTEnabled; - size_t m_applyFormatAtOne = 1; - VirtualTerminal::ConstructedSequence m_format; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Resources.h" +#include "VTSupport.h" +#include + +#include +#include +#include + + +namespace AppInstaller::CLI::Execution +{ + // Gets the current console width, or std::nullopt if stdout is not attached to a console + // (e.g. redirected to a file or pipe). Callers that receive nullopt should not truncate output. + std::optional GetConsoleWidth(); + + // The base stream for all channels. + struct BaseStream + { + BaseStream(std::ostream& out, bool enabled, bool VTEnabled); + + template + BaseStream& operator<<(const T& t) + { + Write(t, false); + return *this; + } + + BaseStream& operator<<(std::ostream& (__cdecl* f)(std::ostream&)); + BaseStream& operator<<(const VirtualTerminal::Sequence& sequence); + BaseStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence); + + void SetVTEnabled(bool enabled); + + void RestoreDefault(); + + void Disable(); + + std::ostream& Get(); + + private: + template + void Write(const T& t, bool bypass) + { + if (bypass || m_enabled) + { + m_out << t; + } + }; + + std::ostream& m_out; + std::atomic_bool m_enabled; + std::atomic_bool m_VTUpdated; + bool m_VTEnabled; + }; + + // Holds output formatting information. + struct OutputStream + { + OutputStream(BaseStream& out, bool enabled, bool VTEnabled = true); + + // Adds a format to the current value. + void AddFormat(const VirtualTerminal::Sequence& sequence); + + // Clears the current format value. + void ClearFormat(); + + template + OutputStream& operator<<(const T& t) + { + // You've found your way here because you tried to output a type that may not localized. + // In order to ensure that all output is localized, only the types with specializations of + // details::IsApprovedForOutput above can be output. + // * If your string is a simple message, it should be put in + // /src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw + // and referenced in /src/AppInstallerCLICore/Resources.h. Then either output the + // Resource::StringId, or load it manually and output the Resource::LocString. + // * If your string is *definitely* localization independent, you can tag it as such with + // the Utility::LocInd(View/String) types. + // * If your string came from outside of the source code, it is best to store it in a + // Utility::NormalizedString so that it has a normalized representation. This also + // informs the output that there is no localized version to use. + // TODO: This assertion is currently only applied to placeholders in localized strings. + // Convert the rest of the code base and uncomment to enforce localization. + //static_assert(details::IsApprovedForOutput>::value, "This type may not be localized, see comment for more information"); + if (m_enabled) + { + if (m_VTEnabled) + { + ApplyFormat(); + } + m_out << t; + } + return *this; + } + + OutputStream& operator<<(std::ostream& (__cdecl* f)(std::ostream&)); + OutputStream& operator<<(const VirtualTerminal::Sequence& sequence); + OutputStream& operator<<(const VirtualTerminal::ConstructedSequence& sequence); + OutputStream& operator<<(const std::filesystem::path& path); + + inline bool IsEnabled() { + return m_enabled; + } + + private: + // Applies the format for the stream. + void ApplyFormat(); + + BaseStream& m_out; + bool m_enabled; + bool m_VTEnabled; + size_t m_applyFormatAtOne = 1; + VirtualTerminal::ConstructedSequence m_format; + }; +} diff --git a/src/AppInstallerCLICore/Command.cpp b/src/AppInstallerCLICore/Command.cpp index b24ce3050d..591f110473 100644 --- a/src/AppInstallerCLICore/Command.cpp +++ b/src/AppInstallerCLICore/Command.cpp @@ -1,1090 +1,1090 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Command.h" -#include "Resources.h" -#include "Sixel.h" -#include -#include -#include -#include -#include - -using namespace std::string_view_literals; -using namespace AppInstaller::Utility::literals; -using namespace AppInstaller::Settings; - -namespace AppInstaller::CLI -{ - namespace - { - constexpr Utility::LocIndView s_Command_ArgName_SilentAndInteractive = "silent|interactive"_liv; - - void LaunchLogsIfRequested(Execution::Context& context) - { - try - { - if (context.Args.Contains(Execution::Args::Type::OpenLogs)) - { - // TODO: Consider possibly adding functionality that if the context contains 'Execution::Args::Type::Log' to open the path provided for the log - // The above was omitted initially as a security precaution to ensure that user input to '--log' wouldn't be passed directly to ShellExecute - ShellExecute(NULL, NULL, Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation).wstring().c_str(), NULL, NULL, SW_SHOWNORMAL); - } - } - CATCH_LOG(); - } - } - - Command::Command( - std::string_view name, - std::vector aliases, - std::string_view parent, - Command::Visibility visibility, - Settings::ExperimentalFeature::Feature feature, - Settings::TogglePolicy::Policy groupPolicy, - CommandOutputFlags outputFlags) : - m_name(name), m_aliases(std::move(aliases)), m_visibility(visibility), m_feature(feature), m_groupPolicy(groupPolicy), m_outputFlags(outputFlags) - { - if (!parent.empty()) - { - m_fullName.reserve(parent.length() + 1 + name.length()); - m_fullName = parent; - m_fullName += ParentSplitChar; - m_fullName += name; - } - else - { - m_fullName = name; - } - } - - void Command::OutputIntroHeader(Execution::Reporter& reporter) const - { - auto infoOut = reporter.Info(); - VirtualTerminal::ConstructedSequence indent; - - if (reporter.SixelsEnabled()) - { - try - { - std::filesystem::path imagePath = Runtime::GetPathTo(Runtime::PathName::ImageAssets); - - if (!imagePath.empty()) - { - // This image matches the target pixel size. If changing the target size, choose the most appropriate image. - imagePath /= "AppList.targetsize-40.png"; - - VirtualTerminal::Sixel::Image wingetIcon{ imagePath }; - - // Using a height of 2 to match the two lines of header. - UINT imageHeightCells = 2; - UINT imageWidthCells = 2 * imageHeightCells; - - wingetIcon.RenderSizeInCells(imageWidthCells, imageHeightCells); - wingetIcon.RenderTo(infoOut); - - indent = VirtualTerminal::Cursor::Position::Forward(static_cast(imageWidthCells)); - infoOut << VirtualTerminal::Cursor::Position::Up(static_cast(imageHeightCells) - 1); - } - } - CATCH_LOG(); - } - - auto productName = Runtime::IsReleaseBuild() ? Resource::String::WindowsPackageManager : Resource::String::WindowsPackageManagerPreview; - infoOut << indent << productName(Runtime::GetClientVersion()) << std::endl - << indent << Resource::String::MainCopyrightNotice << std::endl; - } - - void Command::OutputHelp(Execution::Reporter& reporter, const CommandException* exception) const - { - // Header - OutputIntroHeader(reporter); - reporter.EmptyLine(); - - // Error if given - if (exception) - { - reporter.Error() << exception->Message() << std::endl << std::endl; - } - - // Description - auto infoOut = reporter.Info(); - infoOut << - LongDescription() << std::endl << - std::endl; - - // Example usage for this command - // First create the command chain for output - std::string commandChain = FullName(); - size_t firstSplit = commandChain.find_first_of(ParentSplitChar); - if (firstSplit == std::string::npos) - { - commandChain.clear(); - } - else - { - commandChain = commandChain.substr(firstSplit + 1); - for (char& c : commandChain) - { - if (c == ParentSplitChar) - { - c = ' '; - } - } - } - - // Output the command preamble and command chain - infoOut << Resource::String::Usage("winget"_liv, Utility::LocIndView{ commandChain }); - - auto commandAliases = Aliases(); - auto commands = GetVisibleCommands(); - auto arguments = GetVisibleArguments(); - - bool hasArguments = false; - bool hasOptions = false; - - // Output the command token, made optional if arguments are present. - if (!commands.empty()) - { - infoOut << ' '; - - if (!arguments.empty()) - { - infoOut << '['; - } - - infoOut << '<' << Resource::String::Command << '>'; - - if (!arguments.empty()) - { - infoOut << ']'; - } - } - - // Arguments are required by a test to have all positionals first. - for (const auto& arg : arguments) - { - if (arg.Type() == ArgumentType::Positional) - { - hasArguments = true; - - infoOut << ' '; - - if (!arg.Required()) - { - infoOut << '['; - } - - infoOut << '['; - - if (arg.Alias() == ArgumentCommon::NoAlias) - { - infoOut << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name(); - } - else - { - infoOut << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias(); - } - - infoOut << "] <"_liv << arg.Name() << '>'; - - if (arg.Limit() > 1) - { - infoOut << "..."_liv; - } - - if (!arg.Required()) - { - infoOut << ']'; - } - } - else - { - hasOptions = true; - infoOut << " [<"_liv << Resource::String::Options << ">]"_liv; - break; - } - } - - infoOut << - std::endl << - std::endl; - - if (!commandAliases.empty()) - { - infoOut << Resource::String::AvailableCommandAliases << std::endl; - - for (const auto& commandAlias : commandAliases) - { - infoOut << " "_liv << Execution::HelpCommandEmphasis << commandAlias << std::endl; - } - infoOut << std::endl; - } - - if (!commands.empty()) - { - if (Name() == FullName()) - { - infoOut << Resource::String::AvailableCommands << std::endl; - } - else - { - infoOut << Resource::String::AvailableSubcommands << std::endl; - } - - size_t maxCommandNameLength = 0; - for (const auto& command : commands) - { - maxCommandNameLength = std::max(maxCommandNameLength, command->Name().length()); - } - - for (const auto& command : commands) - { - size_t fillChars = (maxCommandNameLength - command->Name().length()) + 2; - infoOut << " "_liv << Execution::HelpCommandEmphasis << command->Name() << Utility::LocIndString{ std::string(fillChars, ' ') } << command->ShortDescription() << std::endl; - } - - infoOut << - std::endl << - Resource::String::HelpForDetails - << " ["_liv << APPINSTALLER_CLI_HELP_ARGUMENT << ']' << std::endl; - } - - if (!arguments.empty()) - { - if (!commands.empty()) - { - infoOut << std::endl; - } - - std::vector argNames; - size_t maxArgNameLength = 0; - for (const auto& arg : arguments) - { - argNames.emplace_back(arg.GetUsageString()); - maxArgNameLength = std::max(maxArgNameLength, argNames.back().length()); - } - - if (hasArguments) - { - infoOut << Resource::String::AvailableArguments << std::endl; - - size_t i = 0; - for (const auto& arg : arguments) - { - const std::string& argName = argNames[i++]; - if (arg.Type() == ArgumentType::Positional) - { - size_t fillChars = (maxArgNameLength - argName.length()) + 2; - infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; - } - } - } - - if (hasOptions) - { - if (hasArguments) - { - infoOut << std::endl; - } - - infoOut << Resource::String::AvailableOptions << std::endl; - - size_t i = 0; - for (const auto& arg : arguments) - { - const std::string& argName = argNames[i++]; - if (arg.Type() != ArgumentType::Positional) - { - size_t fillChars = (maxArgNameLength - argName.length()) + 2; - infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; - } - } - } - } - - // Finally, the link to the documentation pages - auto helpLink = HelpLink(); - if (!helpLink.empty()) - { - infoOut << std::endl << Resource::String::HelpLinkPreamble(helpLink) << std::endl; - } - } - - std::unique_ptr Command::FindSubCommand(Invocation& inv) const - { - auto itr = inv.begin(); - if (itr == inv.end() || (*itr)[0] == APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR) - { - // No more command arguments to check, so no command to find - return {}; - } - - auto commands = GetCommands(); - if (commands.empty()) - { - // No more subcommands - return {}; - } - - for (auto& command : commands) - { - if ( - Utility::CaseInsensitiveEquals(*itr, command->Name()) || - Utility::CaseInsensitiveContains(command->Aliases(), *itr) - ) - { - if (!ExperimentalFeature::IsEnabled(command->Feature())) - { - auto feature = ExperimentalFeature::GetFeature(command->Feature()); - AICLI_LOG(CLI, Error, << "Trying to use command: " << *itr << " without enabling feature " << feature.JsonName()); - throw CommandException(Resource::String::FeatureDisabledMessage(feature.JsonName())); - } - - if (!Settings::GroupPolicies().IsEnabled(command->GroupPolicy())) - { - auto policy = TogglePolicy::GetPolicy(command->GroupPolicy()); - AICLI_LOG(CLI, Error, << "Trying to use command: " << *itr << " disabled by group policy " << policy.RegValueName()); - throw GroupPolicyException(command->GroupPolicy()); - } - - AICLI_LOG(CLI, Info, << "Found subcommand: " << *itr); - inv.consume(itr); - return std::move(command); - } - } - - // The command has opted-in to be executed when it has subcommands and the next token is a positional parameter value - if (m_selectCurrentCommandIfUnrecognizedSubcommandFound) - { - return {}; - } - - // TODO: If we get to a large number of commands, do a fuzzy search much like git - throw CommandException(Resource::String::UnrecognizedCommand(Utility::LocIndView{ *itr })); - } - - // The argument parsing state machine. - // It is broken out to enable completion to process arguments, ignore errors, - // and determine the likely state of the word to be completed. - struct ParseArgumentsStateMachine - { - ParseArgumentsStateMachine(Invocation& inv, Execution::Args& execArgs, std::vector arguments); - - ParseArgumentsStateMachine(const ParseArgumentsStateMachine&) = delete; - ParseArgumentsStateMachine& operator=(const ParseArgumentsStateMachine&) = delete; - - ParseArgumentsStateMachine(ParseArgumentsStateMachine&&) = default; - ParseArgumentsStateMachine& operator=(ParseArgumentsStateMachine&&) = default; - - // Processes the next argument from the invocation. - // Returns true if there was an argument to process; - // returns false if there were none. - bool Step(); - - // Throws if there was an error during the prior step. - void ThrowIfError() const; - - // The current state of the state machine. - // An empty state indicates that the next argument can be anything. - struct State - { - State() = default; - State(Execution::Args::Type type, std::string_view arg) : m_type(type), m_arg(arg) {} - State(CommandException ce) : m_exception(std::move(ce)) {} - - // If set, indicates that the next argument is a value for this type. - const std::optional& Type() const { return m_type; } - - // The actual argument string associated with Type. - const Utility::LocIndString& Arg() const { return m_arg; } - - // If set, indicates that the last argument produced an error. - const std::optional& Exception() const { return m_exception; } - - private: - std::optional m_type; - Utility::LocIndString m_arg; - std::optional m_exception; - }; - - const State& GetState() const { return m_state; } - - bool OnlyPositionalRemain() const { return m_onlyPositionalArgumentsRemain; } - - // Gets the next positional argument, or nullptr if there is not one. - const CLI::Argument* NextPositional(); - - const std::vector& Arguments() const { return m_arguments; } - - private: - State StepInternal(); - - void ProcessAdjoinedValue(Execution::Args::Type type, std::string_view value); - - Invocation& m_invocation; - Execution::Args& m_executionArgs; - std::vector m_arguments; - - Invocation::iterator m_invocationItr; - std::vector::iterator m_positionalSearchItr; - bool m_onlyPositionalArgumentsRemain = false; - - State m_state; - }; - - ParseArgumentsStateMachine::ParseArgumentsStateMachine(Invocation& inv, Execution::Args& execArgs, std::vector arguments) : - m_invocation(inv), - m_executionArgs(execArgs), - m_arguments(std::move(arguments)), - m_invocationItr(m_invocation.begin()), - m_positionalSearchItr(m_arguments.begin()) - { - } - - bool ParseArgumentsStateMachine::Step() - { - if (m_invocationItr == m_invocation.end()) - { - return false; - } - - m_state = StepInternal(); - return true; - } - - void ParseArgumentsStateMachine::ThrowIfError() const - { - if (m_state.Exception()) - { - throw m_state.Exception().value(); - } - // If the next argument was to be a value, but none was provided, convert it to an exception. - else if (m_state.Type() && m_invocationItr == m_invocation.end()) - { - throw CommandException(Resource::String::MissingArgumentError(m_state.Arg())); - } - } - - const CLI::Argument* ParseArgumentsStateMachine::NextPositional() - { - // Find the next appropriate positional arg if the current itr isn't one or has hit its limit. - while (m_positionalSearchItr != m_arguments.end() && - (m_positionalSearchItr->Type() != ArgumentType::Positional || m_executionArgs.GetCount(m_positionalSearchItr->ExecArgType()) == m_positionalSearchItr->Limit())) - { - ++m_positionalSearchItr; - } - - if (m_positionalSearchItr == m_arguments.end()) - { - return nullptr; - } - - return &*m_positionalSearchItr; - } - - // Parse arguments as such: - // 1. If argument starts with a single -, only the single character alias is considered. - // a. If the named argument alias (a) needs a VALUE, it can be provided in these ways: - // -a=VALUE - // -a VALUE - // b. If the argument is a flag, additional characters after are treated as if they start - // with a -, repeatedly until the end of the argument is reached. Fails if non-flags hit. - // 2. If the argument starts with a double --, only the full name is considered. - // a. If the named argument (arg) needs a VALUE, it can be provided in these ways: - // --arg=VALUE - // --arg VALUE - // 3. If the argument does not start with any -, it is considered the next positional argument. - // 4. If the argument is only a double --, all further arguments are only considered as positional. - ParseArgumentsStateMachine::State ParseArgumentsStateMachine::StepInternal() - { - auto currArg = Utility::LocIndView{ *m_invocationItr }; - ++m_invocationItr; - - // If the previous step indicated a value was needed, set it and forget it. - if (m_state.Type()) - { - m_executionArgs.AddArg(m_state.Type().value(), currArg); - return {}; - } - - // This is a positional argument - if (m_onlyPositionalArgumentsRemain || currArg.empty() || currArg[0] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR) - { - const CLI::Argument* nextPositional = NextPositional(); - if (!nextPositional) - { - return CommandException(Resource::String::ExtraPositionalError(currArg)); - } - - m_executionArgs.AddArg(nextPositional->ExecArgType(), currArg); - } - // The currentArg must not be empty, and starts with a - - else if (currArg.length() == 1) - { - return CommandException(Resource::String::InvalidArgumentSpecifierError(currArg)); - } - // Now it must be at least 2 chars - else if (currArg[1] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR) - { - // Parse the single character alias argument - char currChar = currArg[1]; - - auto itr = std::find_if(m_arguments.begin(), m_arguments.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); }); - if (itr == m_arguments.end()) - { - return CommandException(Resource::String::InvalidAliasError(currArg)); - } - - if (itr->Type() == ArgumentType::Flag) - { - m_executionArgs.AddArg(itr->ExecArgType()); - - for (size_t i = 2; i < currArg.length(); ++i) - { - currChar = currArg[i]; - - auto itr2 = std::find_if(m_arguments.begin(), m_arguments.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); }); - if (itr2 == m_arguments.end()) - { - return CommandException(Resource::String::AdjoinedNotFoundError(currArg)); - } - else if (itr2->Type() != ArgumentType::Flag) - { - return CommandException(Resource::String::AdjoinedNotFlagError(currArg)); - } - else - { - m_executionArgs.AddArg(itr2->ExecArgType()); - } - } - } - else if (currArg.length() > 2) - { - if (currArg[2] == APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR) - { - ProcessAdjoinedValue(itr->ExecArgType(), currArg.substr(3)); - } - else - { - return CommandException(Resource::String::SingleCharAfterDashError(currArg)); - } - } - else - { - return { itr->ExecArgType(), currArg }; - } - } - // The currentArg is at least 2 chars, both of which are -- - else if (currArg.length() == 2) - { - m_onlyPositionalArgumentsRemain = true; - } - // The currentArg is more than 2 chars, both of which are -- - else - { - // This is an arg name, find it and process its value if needed. - // Skip the double arg identifier chars. - size_t argStart = currArg.find_first_not_of(APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR); - std::string_view argName = currArg.substr(argStart); - bool argFound = false; - - bool hasValue = false; - std::string_view argValue; - size_t splitChar = argName.find_first_of(APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR); - if (splitChar != std::string::npos) - { - hasValue = true; - argValue = argName.substr(splitChar + 1); - argName = argName.substr(0, splitChar); - } - - for (const auto& arg : m_arguments) - { - if ( - Utility::CaseInsensitiveEquals(argName, arg.Name()) || - Utility::CaseInsensitiveEquals(argName, arg.AlternateName()) - ) - { - if (arg.Type() == ArgumentType::Flag) - { - if (hasValue) - { - return CommandException(Resource::String::FlagContainAdjoinedError(currArg)); - } - - m_executionArgs.AddArg(arg.ExecArgType()); - } - else if (hasValue) - { - ProcessAdjoinedValue(arg.ExecArgType(), argValue); - } - else - { - return { arg.ExecArgType(), currArg }; - } - argFound = true; - break; - } - } - - if (!argFound) - { - return CommandException(Resource::String::InvalidNameError(currArg)); - } - } - - // If we get here, the next argument can be anything again. - return {}; - } - - void ParseArgumentsStateMachine::ProcessAdjoinedValue(Execution::Args::Type type, std::string_view value) - { - // If the adjoined value is wrapped in quotes, strip them off. - if (value.length() >= 2 && value[0] == '"' && value[value.length() - 1] == '"') - { - value = value.substr(1, value.length() - 2); - } - - m_executionArgs.AddArg(type, std::string{ value }); - } - - void Command::ParseArguments(Invocation& inv, Execution::Args& execArgs) const - { - auto definedArgs = GetArguments(); - Argument::GetCommon(definedArgs); - - ParseArgumentsStateMachine stateMachine{ inv, execArgs, std::move(definedArgs) }; - - while (stateMachine.Step()) - { - stateMachine.ThrowIfError(); - } - - // Special handling for multi-query arguments: - execArgs.MakeMultiQueryContainUniqueValues(); - execArgs.MoveMultiQueryToSingleQueryIfNeeded(); - } - - void Command::ValidateArguments(Execution::Args& execArgs) const - { - // If help is asked for, don't bother validating anything else - if (execArgs.Contains(Execution::Args::Type::Help)) - { - return; - } - - // Common arguments need to be validated with command arguments, as there may be common arguments blocked by Experimental Feature or Group Policy - auto allArgs = GetArguments(); - Argument::GetCommon(allArgs); - - for (const auto& arg : allArgs) - { - if (!Settings::GroupPolicies().IsEnabled(arg.GroupPolicy()) && execArgs.Contains(arg.ExecArgType())) - { - auto policy = TogglePolicy::GetPolicy(arg.GroupPolicy()); - AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " disabled by group policy " << policy.RegValueName()); - throw GroupPolicyException(arg.GroupPolicy()); - } - - if (arg.AdminSetting() != BoolAdminSetting::Unknown && !Settings::IsAdminSettingEnabled(arg.AdminSetting()) && execArgs.Contains(arg.ExecArgType())) - { - auto setting = Settings::AdminSettingToString(arg.AdminSetting()); - AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " disabled by admin setting " << setting); - throw CommandException(Resource::String::FeatureDisabledByAdminSettingMessage(setting)); - } - - if (!ExperimentalFeature::IsEnabled(arg.Feature()) && execArgs.Contains(arg.ExecArgType())) - { - auto feature = ExperimentalFeature::GetFeature(arg.Feature()); - AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " without enabling feature " << feature.JsonName()); - throw CommandException(Resource::String::FeatureDisabledMessage(feature.JsonName())); - } - - if (arg.Required() && !execArgs.Contains(arg.ExecArgType())) - { - throw CommandException(Resource::String::RequiredArgError(arg.Name())); - } - - if (arg.Limit() < execArgs.GetCount(arg.ExecArgType())) - { - throw CommandException(Resource::String::TooManyArgError(arg.Name())); - } - } - - if (execArgs.Contains(Execution::Args::Type::Silent) && execArgs.Contains(Execution::Args::Type::Interactive)) - { - throw CommandException(Resource::String::TooManyBehaviorsError(s_Command_ArgName_SilentAndInteractive)); - } - - if (execArgs.Contains(Execution::Args::Type::CustomHeader) && !execArgs.Contains(Execution::Args::Type::Source) && - !execArgs.Contains(Execution::Args::Type::SourceName)) - { - throw CommandException(Resource::String::HeaderArgumentNotApplicableWithoutSource(Argument::ForType(Execution::Args::Type::CustomHeader).Name())); - } - - if (execArgs.Contains(Execution::Args::Type::Count)) - { - try - { - int countRequested = std::stoi(std::string(execArgs.GetArg(Execution::Args::Type::Count))); - if (countRequested < 1 || countRequested > 1000) - { - throw CommandException(Resource::String::CountOutOfBoundsError); - } - } - catch (...) - { - throw CommandException(Resource::String::CountOutOfBoundsError); - } - } - - if (execArgs.Contains(Execution::Args::Type::InstallArchitecture)) - { - Utility::Architecture selectedArch = Utility::ConvertToArchitectureEnum(std::string(execArgs.GetArg(Execution::Args::Type::InstallArchitecture))); - if ((selectedArch == Utility::Architecture::Unknown) || (Utility::IsApplicableArchitecture(selectedArch) == Utility::InapplicableArchitecture)) - { - std::vector applicableArchitectures; - for (Utility::Architecture i : Utility::GetApplicableArchitectures()) - { - applicableArchitectures.emplace_back(Utility::ToString(i)); - } - - auto validOptions = Utility::Join(", "_liv, applicableArchitectures); - throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::InstallArchitecture).Name(), validOptions)); - } - } - - if (execArgs.Contains(Execution::Args::Type::InstallerArchitecture)) - { - Utility::Architecture selectedArch = Utility::ConvertToArchitectureEnum(std::string(execArgs.GetArg(Execution::Args::Type::InstallerArchitecture))); - if (selectedArch == Utility::Architecture::Unknown) - { - std::vector applicableArchitectures; - for (Utility::Architecture i : Utility::GetAllArchitectures()) - { - applicableArchitectures.emplace_back(Utility::ToString(i)); - } - - auto validOptions = Utility::Join(", "_liv, applicableArchitectures); - throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::InstallerArchitecture).Name(), validOptions)); - } - } - - if (execArgs.Contains(Execution::Args::Type::Locale)) - { - if (!Locale::IsWellFormedBcp47Tag(execArgs.GetArg(Execution::Args::Type::Locale))) - { - throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues(Argument::ForType(Execution::Args::Type::Locale).Name())); - } - } - - if (execArgs.Contains(Execution::Args::Type::InstallScope)) - { - if (Manifest::ConvertToScopeEnum(execArgs.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Unknown) - { - auto validOptions = Utility::Join(", "_liv, std::vector{ "user"_lis, "machine"_lis }); - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::InstallScope).Name, validOptions)); - } - } - - if (execArgs.Contains(Execution::Args::Type::InstallerType)) - { - Manifest::InstallerTypeEnum selectedInstallerType = Manifest::ConvertToInstallerTypeEnum(std::string(execArgs.GetArg(Execution::Args::Type::InstallerType))); - if (selectedInstallerType == Manifest::InstallerTypeEnum::Unknown) - { - throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues(Argument::ForType(Execution::Args::Type::InstallerType).Name())); - } - } - - if (execArgs.Contains(Execution::Args::Type::AuthenticationMode)) - { - if (Authentication::ConvertToAuthenticationMode(execArgs.GetArg(Execution::Args::Type::AuthenticationMode)) == Authentication::AuthenticationMode::Unknown) - { - auto validOptions = Utility::Join(", "_liv, std::vector{ "interactive"_lis, "silentPreferred"_lis, "silent"_lis }); - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AuthenticationMode).Name, validOptions)); - } - } - - if (execArgs.Contains(Execution::Args::Type::Sort)) - { - for (const auto& arg : *execArgs.GetArgs(Execution::Args::Type::Sort)) - { - if (!Settings::ConvertToSortField(arg)) - { - auto validOptions = Utility::Join(", "_liv, std::vector{ - "name"_lis, "id"_lis, "version"_lis, "source"_lis, "available"_lis, "relevance"_lis }); - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::Sort).Name, validOptions)); - } - } - } - - Argument::ValidateExclusiveArguments(execArgs); - - ValidateArgumentsInternal(execArgs); - } - - // Completion can produce one of several things if the completion context is appropriate: - // 1. Sub commands, if the context is immediately after this command. - // 2. Argument names, if a value is not expected. - // 3. Argument values, if one is expected. - void Command::Complete(Execution::Context& context) const - { - CompletionData& data = context.Get(); - const std::string& word = data.Word(); - - // The word we are to complete is directly after the command, thus it's sub-commands are potentials. - if (data.BeforeWord().begin() == data.BeforeWord().end()) - { - for (const auto& command : GetCommands()) - { - if (word.empty() || Utility::CaseInsensitiveStartsWith(command->Name(), word)) - { - context.Reporter.Completion() << command->Name() << std::endl; - } - // Allow for command aliases to be auto-completed - if (!(command->Aliases()).empty() && !word.empty()) - { - for (const auto& commandAlias : command->Aliases()) - { - if (Utility::CaseInsensitiveStartsWith(commandAlias, word)) - { - context.Reporter.Completion() << commandAlias << std::endl; - } - } - } - } - } - - // Consume what remains, if any, of the preceding values to determine what type the word is. - auto definedArgs = GetArguments(); - Argument::GetCommon(definedArgs); - - ParseArgumentsStateMachine stateMachine{ data.BeforeWord(), context.Args, std::move(definedArgs) }; - - // We don't care if there are errors along the way, just do the best that can be done and try to - // complete whatever would be next if the bad strings were simply ignored. To do that we just spin - // through the state until we reach our word. - while (stateMachine.Step()); - - const auto& state = stateMachine.GetState(); - - // This means that anything is possible, so argument names are on the table. - if (!state.Type() && !stateMachine.OnlyPositionalRemain()) - { - // Use argument names if: - // 1. word is empty - // 2. word is just "-" - // 3. word starts with "--" - if (word.empty() || - word == APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING || - Utility::CaseInsensitiveStartsWith(word, APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING)) - { - for (const auto& arg : stateMachine.Arguments()) - { - if (word.length() <= 2 || Utility::CaseInsensitiveStartsWith(arg.Name(), word.substr(2))) - { - context.Reporter.Completion() << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name() << std::endl; - } - } - } - // Use argument aliases if the word is already one; allow cycling through them. - else if (Utility::CaseInsensitiveStartsWith(word, APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING) && word.length() == 2) - { - for (const auto& arg : stateMachine.Arguments()) - { - if (arg.Alias() != ArgumentCommon::NoAlias) - { - context.Reporter.Completion() << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << std::endl; - } - } - } - } - - std::optional typeToComplete = state.Type(); - - // We are not waiting on an argument value, so the next could be a positional if the incoming word is not an argument name. - // If there is one, offer to complete it. - if (!typeToComplete && (word.empty() || word[0] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR)) - { - const auto* nextPositional = stateMachine.NextPositional(); - if (nextPositional) - { - typeToComplete = nextPositional->ExecArgType(); - } - } - - // To enable more complete scenarios, also attempt to parse any arguments after the word to complete. - // This will allow these later values to affect the result of the completion (for instance, if a specific source is listed). - { - ParseArgumentsStateMachine afterWordStateMachine{ data.AfterWord(), context.Args, stateMachine.Arguments() }; - while (afterWordStateMachine.Step()); - } - - // Let the derived command take over supplying context sensitive argument value. - if (typeToComplete) - { - Complete(context, typeToComplete.value()); - } - } - - void Command::Complete(Execution::Context&, Execution::Args::Type) const - { - // Derived commands must supply context sensitive argument values. - } - - void Command::Execute(Execution::Context& context) const - { - // Block any execution if winget is disabled by policy. - // Override the function to bypass this. - if (!Settings::GroupPolicies().IsEnabled(Settings::TogglePolicy::Policy::WinGet)) - { - AICLI_LOG(CLI, Error, << "WinGet is disabled by group policy"); - throw Settings::GroupPolicyException::GroupPolicyException(Settings::TogglePolicy::Policy::WinGet); - } - - AICLI_LOG(CLI, Info, << "Executing command: " << Name()); - if (context.Args.Contains(Execution::Args::Type::Help)) - { - OutputHelp(context.Reporter); - } - else - { - ExecuteInternal(context); - } - - // NOTE: Reboot logic will still run even if the context is terminated (not including unhandled exceptions). - if (context.Args.Contains(Execution::Args::Type::AllowReboot) && - WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::RebootRequired)) - { - context.Reporter.Warn() << Resource::String::InitiatingReboot << std::endl; - - if (context.Args.Contains(Execution::Args::Type::Wait)) - { - context.Reporter.PromptForEnter(); - } - - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::RegisterResume)) - { - // RegisterResume context flag assumes we already wrote to the RunOnce registry. - // Since we are about to initiate a restart, this is no longer needed as a safety net. - Reboot::UnregisterRestartForWER(); - - context.ClearFlags(Execution::ContextFlag::RegisterResume); - } - - context.ClearFlags(Execution::ContextFlag::RebootRequired); - - if (!Reboot::InitiateReboot()) - { - context.Reporter.Error() << Resource::String::FailedToInitiateReboot << std::endl; - } - } - else - { - LaunchLogsIfRequested(context); - - if (context.Args.Contains(Execution::Args::Type::Wait)) - { - context.Reporter.PromptForEnter(); - } - } - } - - void Command::Resume(Execution::Context& context) const - { - context.Reporter.Error() << Resource::String::CommandDoesNotSupportResumeMessage << std::endl; - AICLI_TERMINATE_CONTEXT(E_NOTIMPL); - } - - void Command::SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value) - { - m_selectCurrentCommandIfUnrecognizedSubcommandFound = value; - } - - void Command::ValidateArgumentsInternal(Execution::Args&) const - { - // Do nothing by default. - // Commands may not need any extra validation. - } - - void Command::ExecuteInternal(Execution::Context& context) const - { - context.Reporter.Error() << Resource::String::PendingWorkError << std::endl; - THROW_HR(E_NOTIMPL); - } - - Command::Visibility Command::GetVisibility() const - { - if (!ExperimentalFeature::IsEnabled(m_feature)) - { - return Command::Visibility::Hidden; - } - - if (!Settings::GroupPolicies().IsEnabled(m_groupPolicy)) - { - return Command::Visibility::Hidden; - } - - return m_visibility; - } - - std::vector> Command::GetVisibleCommands() const - { - auto commands = GetCommands(); - - commands.erase( - std::remove_if( - commands.begin(), commands.end(), - [](const std::unique_ptr& command) { return command->GetVisibility() == Command::Visibility::Hidden; }), - commands.end()); - - return commands; - } - - std::vector Command::GetVisibleArguments() const - { - auto arguments = GetArguments(); - Argument::GetCommon(arguments); - - arguments.erase( - std::remove_if( - arguments.begin(), arguments.end(), - [](const Argument& arg) { return arg.GetVisibility() == Argument::Visibility::Hidden; }), - arguments.end()); - - return arguments; - } - - void ExecuteWithoutLoggingSuccess(Execution::Context& context, Command* command) - { - try - { - if (!Settings::User().GetWarnings().empty() && - !WI_IsFlagSet(command->GetOutputFlags(), CommandOutputFlags::IgnoreSettingsWarnings)) - { - context.Reporter.Warn() << Resource::String::SettingsWarnings << std::endl; - } - - command->Execute(context); - } - catch (...) - { - context.SetTerminationHR(Workflow::HandleException(context, std::current_exception())); - - LaunchLogsIfRequested(context); - } - } - - int Execute(Execution::Context& context, std::unique_ptr& command) - { - ExecuteWithoutLoggingSuccess(context, command.get()); - - if (SUCCEEDED(context.GetTerminationHR())) - { - Logging::Telemetry().LogCommandSuccess(command->FullName()); - } - - return context.GetTerminationHR(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Command.h" +#include "Resources.h" +#include "Sixel.h" +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Settings; + +namespace AppInstaller::CLI +{ + namespace + { + constexpr Utility::LocIndView s_Command_ArgName_SilentAndInteractive = "silent|interactive"_liv; + + void LaunchLogsIfRequested(Execution::Context& context) + { + try + { + if (context.Args.Contains(Execution::Args::Type::OpenLogs)) + { + // TODO: Consider possibly adding functionality that if the context contains 'Execution::Args::Type::Log' to open the path provided for the log + // The above was omitted initially as a security precaution to ensure that user input to '--log' wouldn't be passed directly to ShellExecute + ShellExecute(NULL, NULL, Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation).wstring().c_str(), NULL, NULL, SW_SHOWNORMAL); + } + } + CATCH_LOG(); + } + } + + Command::Command( + std::string_view name, + std::vector aliases, + std::string_view parent, + Command::Visibility visibility, + Settings::ExperimentalFeature::Feature feature, + Settings::TogglePolicy::Policy groupPolicy, + CommandOutputFlags outputFlags) : + m_name(name), m_aliases(std::move(aliases)), m_visibility(visibility), m_feature(feature), m_groupPolicy(groupPolicy), m_outputFlags(outputFlags) + { + if (!parent.empty()) + { + m_fullName.reserve(parent.length() + 1 + name.length()); + m_fullName = parent; + m_fullName += ParentSplitChar; + m_fullName += name; + } + else + { + m_fullName = name; + } + } + + void Command::OutputIntroHeader(Execution::Reporter& reporter) const + { + auto infoOut = reporter.Info(); + VirtualTerminal::ConstructedSequence indent; + + if (reporter.SixelsEnabled()) + { + try + { + std::filesystem::path imagePath = Runtime::GetPathTo(Runtime::PathName::ImageAssets); + + if (!imagePath.empty()) + { + // This image matches the target pixel size. If changing the target size, choose the most appropriate image. + imagePath /= "AppList.targetsize-40.png"; + + VirtualTerminal::Sixel::Image wingetIcon{ imagePath }; + + // Using a height of 2 to match the two lines of header. + UINT imageHeightCells = 2; + UINT imageWidthCells = 2 * imageHeightCells; + + wingetIcon.RenderSizeInCells(imageWidthCells, imageHeightCells); + wingetIcon.RenderTo(infoOut); + + indent = VirtualTerminal::Cursor::Position::Forward(static_cast(imageWidthCells)); + infoOut << VirtualTerminal::Cursor::Position::Up(static_cast(imageHeightCells) - 1); + } + } + CATCH_LOG(); + } + + auto productName = Runtime::IsReleaseBuild() ? Resource::String::WindowsPackageManager : Resource::String::WindowsPackageManagerPreview; + infoOut << indent << productName(Runtime::GetClientVersion()) << std::endl + << indent << Resource::String::MainCopyrightNotice << std::endl; + } + + void Command::OutputHelp(Execution::Reporter& reporter, const CommandException* exception) const + { + // Header + OutputIntroHeader(reporter); + reporter.EmptyLine(); + + // Error if given + if (exception) + { + reporter.Error() << exception->Message() << std::endl << std::endl; + } + + // Description + auto infoOut = reporter.Info(); + infoOut << + LongDescription() << std::endl << + std::endl; + + // Example usage for this command + // First create the command chain for output + std::string commandChain = FullName(); + size_t firstSplit = commandChain.find_first_of(ParentSplitChar); + if (firstSplit == std::string::npos) + { + commandChain.clear(); + } + else + { + commandChain = commandChain.substr(firstSplit + 1); + for (char& c : commandChain) + { + if (c == ParentSplitChar) + { + c = ' '; + } + } + } + + // Output the command preamble and command chain + infoOut << Resource::String::Usage("winget"_liv, Utility::LocIndView{ commandChain }); + + auto commandAliases = Aliases(); + auto commands = GetVisibleCommands(); + auto arguments = GetVisibleArguments(); + + bool hasArguments = false; + bool hasOptions = false; + + // Output the command token, made optional if arguments are present. + if (!commands.empty()) + { + infoOut << ' '; + + if (!arguments.empty()) + { + infoOut << '['; + } + + infoOut << '<' << Resource::String::Command << '>'; + + if (!arguments.empty()) + { + infoOut << ']'; + } + } + + // Arguments are required by a test to have all positionals first. + for (const auto& arg : arguments) + { + if (arg.Type() == ArgumentType::Positional) + { + hasArguments = true; + + infoOut << ' '; + + if (!arg.Required()) + { + infoOut << '['; + } + + infoOut << '['; + + if (arg.Alias() == ArgumentCommon::NoAlias) + { + infoOut << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name(); + } + else + { + infoOut << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias(); + } + + infoOut << "] <"_liv << arg.Name() << '>'; + + if (arg.Limit() > 1) + { + infoOut << "..."_liv; + } + + if (!arg.Required()) + { + infoOut << ']'; + } + } + else + { + hasOptions = true; + infoOut << " [<"_liv << Resource::String::Options << ">]"_liv; + break; + } + } + + infoOut << + std::endl << + std::endl; + + if (!commandAliases.empty()) + { + infoOut << Resource::String::AvailableCommandAliases << std::endl; + + for (const auto& commandAlias : commandAliases) + { + infoOut << " "_liv << Execution::HelpCommandEmphasis << commandAlias << std::endl; + } + infoOut << std::endl; + } + + if (!commands.empty()) + { + if (Name() == FullName()) + { + infoOut << Resource::String::AvailableCommands << std::endl; + } + else + { + infoOut << Resource::String::AvailableSubcommands << std::endl; + } + + size_t maxCommandNameLength = 0; + for (const auto& command : commands) + { + maxCommandNameLength = std::max(maxCommandNameLength, command->Name().length()); + } + + for (const auto& command : commands) + { + size_t fillChars = (maxCommandNameLength - command->Name().length()) + 2; + infoOut << " "_liv << Execution::HelpCommandEmphasis << command->Name() << Utility::LocIndString{ std::string(fillChars, ' ') } << command->ShortDescription() << std::endl; + } + + infoOut << + std::endl << + Resource::String::HelpForDetails + << " ["_liv << APPINSTALLER_CLI_HELP_ARGUMENT << ']' << std::endl; + } + + if (!arguments.empty()) + { + if (!commands.empty()) + { + infoOut << std::endl; + } + + std::vector argNames; + size_t maxArgNameLength = 0; + for (const auto& arg : arguments) + { + argNames.emplace_back(arg.GetUsageString()); + maxArgNameLength = std::max(maxArgNameLength, argNames.back().length()); + } + + if (hasArguments) + { + infoOut << Resource::String::AvailableArguments << std::endl; + + size_t i = 0; + for (const auto& arg : arguments) + { + const std::string& argName = argNames[i++]; + if (arg.Type() == ArgumentType::Positional) + { + size_t fillChars = (maxArgNameLength - argName.length()) + 2; + infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; + } + } + } + + if (hasOptions) + { + if (hasArguments) + { + infoOut << std::endl; + } + + infoOut << Resource::String::AvailableOptions << std::endl; + + size_t i = 0; + for (const auto& arg : arguments) + { + const std::string& argName = argNames[i++]; + if (arg.Type() != ArgumentType::Positional) + { + size_t fillChars = (maxArgNameLength - argName.length()) + 2; + infoOut << " "_liv << Execution::HelpArgumentEmphasis << argName << Utility::LocIndString{ std::string(fillChars, ' ') } << arg.Description() << std::endl; + } + } + } + } + + // Finally, the link to the documentation pages + auto helpLink = HelpLink(); + if (!helpLink.empty()) + { + infoOut << std::endl << Resource::String::HelpLinkPreamble(helpLink) << std::endl; + } + } + + std::unique_ptr Command::FindSubCommand(Invocation& inv) const + { + auto itr = inv.begin(); + if (itr == inv.end() || (*itr)[0] == APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR) + { + // No more command arguments to check, so no command to find + return {}; + } + + auto commands = GetCommands(); + if (commands.empty()) + { + // No more subcommands + return {}; + } + + for (auto& command : commands) + { + if ( + Utility::CaseInsensitiveEquals(*itr, command->Name()) || + Utility::CaseInsensitiveContains(command->Aliases(), *itr) + ) + { + if (!ExperimentalFeature::IsEnabled(command->Feature())) + { + auto feature = ExperimentalFeature::GetFeature(command->Feature()); + AICLI_LOG(CLI, Error, << "Trying to use command: " << *itr << " without enabling feature " << feature.JsonName()); + throw CommandException(Resource::String::FeatureDisabledMessage(feature.JsonName())); + } + + if (!Settings::GroupPolicies().IsEnabled(command->GroupPolicy())) + { + auto policy = TogglePolicy::GetPolicy(command->GroupPolicy()); + AICLI_LOG(CLI, Error, << "Trying to use command: " << *itr << " disabled by group policy " << policy.RegValueName()); + throw GroupPolicyException(command->GroupPolicy()); + } + + AICLI_LOG(CLI, Info, << "Found subcommand: " << *itr); + inv.consume(itr); + return std::move(command); + } + } + + // The command has opted-in to be executed when it has subcommands and the next token is a positional parameter value + if (m_selectCurrentCommandIfUnrecognizedSubcommandFound) + { + return {}; + } + + // TODO: If we get to a large number of commands, do a fuzzy search much like git + throw CommandException(Resource::String::UnrecognizedCommand(Utility::LocIndView{ *itr })); + } + + // The argument parsing state machine. + // It is broken out to enable completion to process arguments, ignore errors, + // and determine the likely state of the word to be completed. + struct ParseArgumentsStateMachine + { + ParseArgumentsStateMachine(Invocation& inv, Execution::Args& execArgs, std::vector arguments); + + ParseArgumentsStateMachine(const ParseArgumentsStateMachine&) = delete; + ParseArgumentsStateMachine& operator=(const ParseArgumentsStateMachine&) = delete; + + ParseArgumentsStateMachine(ParseArgumentsStateMachine&&) = default; + ParseArgumentsStateMachine& operator=(ParseArgumentsStateMachine&&) = default; + + // Processes the next argument from the invocation. + // Returns true if there was an argument to process; + // returns false if there were none. + bool Step(); + + // Throws if there was an error during the prior step. + void ThrowIfError() const; + + // The current state of the state machine. + // An empty state indicates that the next argument can be anything. + struct State + { + State() = default; + State(Execution::Args::Type type, std::string_view arg) : m_type(type), m_arg(arg) {} + State(CommandException ce) : m_exception(std::move(ce)) {} + + // If set, indicates that the next argument is a value for this type. + const std::optional& Type() const { return m_type; } + + // The actual argument string associated with Type. + const Utility::LocIndString& Arg() const { return m_arg; } + + // If set, indicates that the last argument produced an error. + const std::optional& Exception() const { return m_exception; } + + private: + std::optional m_type; + Utility::LocIndString m_arg; + std::optional m_exception; + }; + + const State& GetState() const { return m_state; } + + bool OnlyPositionalRemain() const { return m_onlyPositionalArgumentsRemain; } + + // Gets the next positional argument, or nullptr if there is not one. + const CLI::Argument* NextPositional(); + + const std::vector& Arguments() const { return m_arguments; } + + private: + State StepInternal(); + + void ProcessAdjoinedValue(Execution::Args::Type type, std::string_view value); + + Invocation& m_invocation; + Execution::Args& m_executionArgs; + std::vector m_arguments; + + Invocation::iterator m_invocationItr; + std::vector::iterator m_positionalSearchItr; + bool m_onlyPositionalArgumentsRemain = false; + + State m_state; + }; + + ParseArgumentsStateMachine::ParseArgumentsStateMachine(Invocation& inv, Execution::Args& execArgs, std::vector arguments) : + m_invocation(inv), + m_executionArgs(execArgs), + m_arguments(std::move(arguments)), + m_invocationItr(m_invocation.begin()), + m_positionalSearchItr(m_arguments.begin()) + { + } + + bool ParseArgumentsStateMachine::Step() + { + if (m_invocationItr == m_invocation.end()) + { + return false; + } + + m_state = StepInternal(); + return true; + } + + void ParseArgumentsStateMachine::ThrowIfError() const + { + if (m_state.Exception()) + { + throw m_state.Exception().value(); + } + // If the next argument was to be a value, but none was provided, convert it to an exception. + else if (m_state.Type() && m_invocationItr == m_invocation.end()) + { + throw CommandException(Resource::String::MissingArgumentError(m_state.Arg())); + } + } + + const CLI::Argument* ParseArgumentsStateMachine::NextPositional() + { + // Find the next appropriate positional arg if the current itr isn't one or has hit its limit. + while (m_positionalSearchItr != m_arguments.end() && + (m_positionalSearchItr->Type() != ArgumentType::Positional || m_executionArgs.GetCount(m_positionalSearchItr->ExecArgType()) == m_positionalSearchItr->Limit())) + { + ++m_positionalSearchItr; + } + + if (m_positionalSearchItr == m_arguments.end()) + { + return nullptr; + } + + return &*m_positionalSearchItr; + } + + // Parse arguments as such: + // 1. If argument starts with a single -, only the single character alias is considered. + // a. If the named argument alias (a) needs a VALUE, it can be provided in these ways: + // -a=VALUE + // -a VALUE + // b. If the argument is a flag, additional characters after are treated as if they start + // with a -, repeatedly until the end of the argument is reached. Fails if non-flags hit. + // 2. If the argument starts with a double --, only the full name is considered. + // a. If the named argument (arg) needs a VALUE, it can be provided in these ways: + // --arg=VALUE + // --arg VALUE + // 3. If the argument does not start with any -, it is considered the next positional argument. + // 4. If the argument is only a double --, all further arguments are only considered as positional. + ParseArgumentsStateMachine::State ParseArgumentsStateMachine::StepInternal() + { + auto currArg = Utility::LocIndView{ *m_invocationItr }; + ++m_invocationItr; + + // If the previous step indicated a value was needed, set it and forget it. + if (m_state.Type()) + { + m_executionArgs.AddArg(m_state.Type().value(), currArg); + return {}; + } + + // This is a positional argument + if (m_onlyPositionalArgumentsRemain || currArg.empty() || currArg[0] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR) + { + const CLI::Argument* nextPositional = NextPositional(); + if (!nextPositional) + { + return CommandException(Resource::String::ExtraPositionalError(currArg)); + } + + m_executionArgs.AddArg(nextPositional->ExecArgType(), currArg); + } + // The currentArg must not be empty, and starts with a - + else if (currArg.length() == 1) + { + return CommandException(Resource::String::InvalidArgumentSpecifierError(currArg)); + } + // Now it must be at least 2 chars + else if (currArg[1] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR) + { + // Parse the single character alias argument + char currChar = currArg[1]; + + auto itr = std::find_if(m_arguments.begin(), m_arguments.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); }); + if (itr == m_arguments.end()) + { + return CommandException(Resource::String::InvalidAliasError(currArg)); + } + + if (itr->Type() == ArgumentType::Flag) + { + m_executionArgs.AddArg(itr->ExecArgType()); + + for (size_t i = 2; i < currArg.length(); ++i) + { + currChar = currArg[i]; + + auto itr2 = std::find_if(m_arguments.begin(), m_arguments.end(), [&](const Argument& arg) { return (currChar == arg.Alias()); }); + if (itr2 == m_arguments.end()) + { + return CommandException(Resource::String::AdjoinedNotFoundError(currArg)); + } + else if (itr2->Type() != ArgumentType::Flag) + { + return CommandException(Resource::String::AdjoinedNotFlagError(currArg)); + } + else + { + m_executionArgs.AddArg(itr2->ExecArgType()); + } + } + } + else if (currArg.length() > 2) + { + if (currArg[2] == APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR) + { + ProcessAdjoinedValue(itr->ExecArgType(), currArg.substr(3)); + } + else + { + return CommandException(Resource::String::SingleCharAfterDashError(currArg)); + } + } + else + { + return { itr->ExecArgType(), currArg }; + } + } + // The currentArg is at least 2 chars, both of which are -- + else if (currArg.length() == 2) + { + m_onlyPositionalArgumentsRemain = true; + } + // The currentArg is more than 2 chars, both of which are -- + else + { + // This is an arg name, find it and process its value if needed. + // Skip the double arg identifier chars. + size_t argStart = currArg.find_first_not_of(APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR); + std::string_view argName = currArg.substr(argStart); + bool argFound = false; + + bool hasValue = false; + std::string_view argValue; + size_t splitChar = argName.find_first_of(APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR); + if (splitChar != std::string::npos) + { + hasValue = true; + argValue = argName.substr(splitChar + 1); + argName = argName.substr(0, splitChar); + } + + for (const auto& arg : m_arguments) + { + if ( + Utility::CaseInsensitiveEquals(argName, arg.Name()) || + Utility::CaseInsensitiveEquals(argName, arg.AlternateName()) + ) + { + if (arg.Type() == ArgumentType::Flag) + { + if (hasValue) + { + return CommandException(Resource::String::FlagContainAdjoinedError(currArg)); + } + + m_executionArgs.AddArg(arg.ExecArgType()); + } + else if (hasValue) + { + ProcessAdjoinedValue(arg.ExecArgType(), argValue); + } + else + { + return { arg.ExecArgType(), currArg }; + } + argFound = true; + break; + } + } + + if (!argFound) + { + return CommandException(Resource::String::InvalidNameError(currArg)); + } + } + + // If we get here, the next argument can be anything again. + return {}; + } + + void ParseArgumentsStateMachine::ProcessAdjoinedValue(Execution::Args::Type type, std::string_view value) + { + // If the adjoined value is wrapped in quotes, strip them off. + if (value.length() >= 2 && value[0] == '"' && value[value.length() - 1] == '"') + { + value = value.substr(1, value.length() - 2); + } + + m_executionArgs.AddArg(type, std::string{ value }); + } + + void Command::ParseArguments(Invocation& inv, Execution::Args& execArgs) const + { + auto definedArgs = GetArguments(); + Argument::GetCommon(definedArgs); + + ParseArgumentsStateMachine stateMachine{ inv, execArgs, std::move(definedArgs) }; + + while (stateMachine.Step()) + { + stateMachine.ThrowIfError(); + } + + // Special handling for multi-query arguments: + execArgs.MakeMultiQueryContainUniqueValues(); + execArgs.MoveMultiQueryToSingleQueryIfNeeded(); + } + + void Command::ValidateArguments(Execution::Args& execArgs) const + { + // If help is asked for, don't bother validating anything else + if (execArgs.Contains(Execution::Args::Type::Help)) + { + return; + } + + // Common arguments need to be validated with command arguments, as there may be common arguments blocked by Experimental Feature or Group Policy + auto allArgs = GetArguments(); + Argument::GetCommon(allArgs); + + for (const auto& arg : allArgs) + { + if (!Settings::GroupPolicies().IsEnabled(arg.GroupPolicy()) && execArgs.Contains(arg.ExecArgType())) + { + auto policy = TogglePolicy::GetPolicy(arg.GroupPolicy()); + AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " disabled by group policy " << policy.RegValueName()); + throw GroupPolicyException(arg.GroupPolicy()); + } + + if (arg.AdminSetting() != BoolAdminSetting::Unknown && !Settings::IsAdminSettingEnabled(arg.AdminSetting()) && execArgs.Contains(arg.ExecArgType())) + { + auto setting = Settings::AdminSettingToString(arg.AdminSetting()); + AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " disabled by admin setting " << setting); + throw CommandException(Resource::String::FeatureDisabledByAdminSettingMessage(setting)); + } + + if (!ExperimentalFeature::IsEnabled(arg.Feature()) && execArgs.Contains(arg.ExecArgType())) + { + auto feature = ExperimentalFeature::GetFeature(arg.Feature()); + AICLI_LOG(CLI, Error, << "Trying to use argument: " << arg.Name() << " without enabling feature " << feature.JsonName()); + throw CommandException(Resource::String::FeatureDisabledMessage(feature.JsonName())); + } + + if (arg.Required() && !execArgs.Contains(arg.ExecArgType())) + { + throw CommandException(Resource::String::RequiredArgError(arg.Name())); + } + + if (arg.Limit() < execArgs.GetCount(arg.ExecArgType())) + { + throw CommandException(Resource::String::TooManyArgError(arg.Name())); + } + } + + if (execArgs.Contains(Execution::Args::Type::Silent) && execArgs.Contains(Execution::Args::Type::Interactive)) + { + throw CommandException(Resource::String::TooManyBehaviorsError(s_Command_ArgName_SilentAndInteractive)); + } + + if (execArgs.Contains(Execution::Args::Type::CustomHeader) && !execArgs.Contains(Execution::Args::Type::Source) && + !execArgs.Contains(Execution::Args::Type::SourceName)) + { + throw CommandException(Resource::String::HeaderArgumentNotApplicableWithoutSource(Argument::ForType(Execution::Args::Type::CustomHeader).Name())); + } + + if (execArgs.Contains(Execution::Args::Type::Count)) + { + try + { + int countRequested = std::stoi(std::string(execArgs.GetArg(Execution::Args::Type::Count))); + if (countRequested < 1 || countRequested > 1000) + { + throw CommandException(Resource::String::CountOutOfBoundsError); + } + } + catch (...) + { + throw CommandException(Resource::String::CountOutOfBoundsError); + } + } + + if (execArgs.Contains(Execution::Args::Type::InstallArchitecture)) + { + Utility::Architecture selectedArch = Utility::ConvertToArchitectureEnum(std::string(execArgs.GetArg(Execution::Args::Type::InstallArchitecture))); + if ((selectedArch == Utility::Architecture::Unknown) || (Utility::IsApplicableArchitecture(selectedArch) == Utility::InapplicableArchitecture)) + { + std::vector applicableArchitectures; + for (Utility::Architecture i : Utility::GetApplicableArchitectures()) + { + applicableArchitectures.emplace_back(Utility::ToString(i)); + } + + auto validOptions = Utility::Join(", "_liv, applicableArchitectures); + throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::InstallArchitecture).Name(), validOptions)); + } + } + + if (execArgs.Contains(Execution::Args::Type::InstallerArchitecture)) + { + Utility::Architecture selectedArch = Utility::ConvertToArchitectureEnum(std::string(execArgs.GetArg(Execution::Args::Type::InstallerArchitecture))); + if (selectedArch == Utility::Architecture::Unknown) + { + std::vector applicableArchitectures; + for (Utility::Architecture i : Utility::GetAllArchitectures()) + { + applicableArchitectures.emplace_back(Utility::ToString(i)); + } + + auto validOptions = Utility::Join(", "_liv, applicableArchitectures); + throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::InstallerArchitecture).Name(), validOptions)); + } + } + + if (execArgs.Contains(Execution::Args::Type::Locale)) + { + if (!Locale::IsWellFormedBcp47Tag(execArgs.GetArg(Execution::Args::Type::Locale))) + { + throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues(Argument::ForType(Execution::Args::Type::Locale).Name())); + } + } + + if (execArgs.Contains(Execution::Args::Type::InstallScope)) + { + if (Manifest::ConvertToScopeEnum(execArgs.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Unknown) + { + auto validOptions = Utility::Join(", "_liv, std::vector{ "user"_lis, "machine"_lis }); + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::InstallScope).Name, validOptions)); + } + } + + if (execArgs.Contains(Execution::Args::Type::InstallerType)) + { + Manifest::InstallerTypeEnum selectedInstallerType = Manifest::ConvertToInstallerTypeEnum(std::string(execArgs.GetArg(Execution::Args::Type::InstallerType))); + if (selectedInstallerType == Manifest::InstallerTypeEnum::Unknown) + { + throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues(Argument::ForType(Execution::Args::Type::InstallerType).Name())); + } + } + + if (execArgs.Contains(Execution::Args::Type::AuthenticationMode)) + { + if (Authentication::ConvertToAuthenticationMode(execArgs.GetArg(Execution::Args::Type::AuthenticationMode)) == Authentication::AuthenticationMode::Unknown) + { + auto validOptions = Utility::Join(", "_liv, std::vector{ "interactive"_lis, "silentPreferred"_lis, "silent"_lis }); + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AuthenticationMode).Name, validOptions)); + } + } + + if (execArgs.Contains(Execution::Args::Type::Sort)) + { + for (const auto& arg : *execArgs.GetArgs(Execution::Args::Type::Sort)) + { + if (!Settings::ConvertToSortField(arg)) + { + auto validOptions = Utility::Join(", "_liv, std::vector{ + "name"_lis, "id"_lis, "version"_lis, "source"_lis, "available"_lis, "relevance"_lis }); + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::Sort).Name, validOptions)); + } + } + } + + Argument::ValidateExclusiveArguments(execArgs); + + ValidateArgumentsInternal(execArgs); + } + + // Completion can produce one of several things if the completion context is appropriate: + // 1. Sub commands, if the context is immediately after this command. + // 2. Argument names, if a value is not expected. + // 3. Argument values, if one is expected. + void Command::Complete(Execution::Context& context) const + { + CompletionData& data = context.Get(); + const std::string& word = data.Word(); + + // The word we are to complete is directly after the command, thus it's sub-commands are potentials. + if (data.BeforeWord().begin() == data.BeforeWord().end()) + { + for (const auto& command : GetCommands()) + { + if (word.empty() || Utility::CaseInsensitiveStartsWith(command->Name(), word)) + { + context.Reporter.Completion() << command->Name() << std::endl; + } + // Allow for command aliases to be auto-completed + if (!(command->Aliases()).empty() && !word.empty()) + { + for (const auto& commandAlias : command->Aliases()) + { + if (Utility::CaseInsensitiveStartsWith(commandAlias, word)) + { + context.Reporter.Completion() << commandAlias << std::endl; + } + } + } + } + } + + // Consume what remains, if any, of the preceding values to determine what type the word is. + auto definedArgs = GetArguments(); + Argument::GetCommon(definedArgs); + + ParseArgumentsStateMachine stateMachine{ data.BeforeWord(), context.Args, std::move(definedArgs) }; + + // We don't care if there are errors along the way, just do the best that can be done and try to + // complete whatever would be next if the bad strings were simply ignored. To do that we just spin + // through the state until we reach our word. + while (stateMachine.Step()); + + const auto& state = stateMachine.GetState(); + + // This means that anything is possible, so argument names are on the table. + if (!state.Type() && !stateMachine.OnlyPositionalRemain()) + { + // Use argument names if: + // 1. word is empty + // 2. word is just "-" + // 3. word starts with "--" + if (word.empty() || + word == APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING || + Utility::CaseInsensitiveStartsWith(word, APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING)) + { + for (const auto& arg : stateMachine.Arguments()) + { + if (word.length() <= 2 || Utility::CaseInsensitiveStartsWith(arg.Name(), word.substr(2))) + { + context.Reporter.Completion() << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Name() << std::endl; + } + } + } + // Use argument aliases if the word is already one; allow cycling through them. + else if (Utility::CaseInsensitiveStartsWith(word, APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_STRING) && word.length() == 2) + { + for (const auto& arg : stateMachine.Arguments()) + { + if (arg.Alias() != ArgumentCommon::NoAlias) + { + context.Reporter.Completion() << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << arg.Alias() << std::endl; + } + } + } + } + + std::optional typeToComplete = state.Type(); + + // We are not waiting on an argument value, so the next could be a positional if the incoming word is not an argument name. + // If there is one, offer to complete it. + if (!typeToComplete && (word.empty() || word[0] != APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR)) + { + const auto* nextPositional = stateMachine.NextPositional(); + if (nextPositional) + { + typeToComplete = nextPositional->ExecArgType(); + } + } + + // To enable more complete scenarios, also attempt to parse any arguments after the word to complete. + // This will allow these later values to affect the result of the completion (for instance, if a specific source is listed). + { + ParseArgumentsStateMachine afterWordStateMachine{ data.AfterWord(), context.Args, stateMachine.Arguments() }; + while (afterWordStateMachine.Step()); + } + + // Let the derived command take over supplying context sensitive argument value. + if (typeToComplete) + { + Complete(context, typeToComplete.value()); + } + } + + void Command::Complete(Execution::Context&, Execution::Args::Type) const + { + // Derived commands must supply context sensitive argument values. + } + + void Command::Execute(Execution::Context& context) const + { + // Block any execution if winget is disabled by policy. + // Override the function to bypass this. + if (!Settings::GroupPolicies().IsEnabled(Settings::TogglePolicy::Policy::WinGet)) + { + AICLI_LOG(CLI, Error, << "WinGet is disabled by group policy"); + throw Settings::GroupPolicyException::GroupPolicyException(Settings::TogglePolicy::Policy::WinGet); + } + + AICLI_LOG(CLI, Info, << "Executing command: " << Name()); + if (context.Args.Contains(Execution::Args::Type::Help)) + { + OutputHelp(context.Reporter); + } + else + { + ExecuteInternal(context); + } + + // NOTE: Reboot logic will still run even if the context is terminated (not including unhandled exceptions). + if (context.Args.Contains(Execution::Args::Type::AllowReboot) && + WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::RebootRequired)) + { + context.Reporter.Warn() << Resource::String::InitiatingReboot << std::endl; + + if (context.Args.Contains(Execution::Args::Type::Wait)) + { + context.Reporter.PromptForEnter(); + } + + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::RegisterResume)) + { + // RegisterResume context flag assumes we already wrote to the RunOnce registry. + // Since we are about to initiate a restart, this is no longer needed as a safety net. + Reboot::UnregisterRestartForWER(); + + context.ClearFlags(Execution::ContextFlag::RegisterResume); + } + + context.ClearFlags(Execution::ContextFlag::RebootRequired); + + if (!Reboot::InitiateReboot()) + { + context.Reporter.Error() << Resource::String::FailedToInitiateReboot << std::endl; + } + } + else + { + LaunchLogsIfRequested(context); + + if (context.Args.Contains(Execution::Args::Type::Wait)) + { + context.Reporter.PromptForEnter(); + } + } + } + + void Command::Resume(Execution::Context& context) const + { + context.Reporter.Error() << Resource::String::CommandDoesNotSupportResumeMessage << std::endl; + AICLI_TERMINATE_CONTEXT(E_NOTIMPL); + } + + void Command::SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value) + { + m_selectCurrentCommandIfUnrecognizedSubcommandFound = value; + } + + void Command::ValidateArgumentsInternal(Execution::Args&) const + { + // Do nothing by default. + // Commands may not need any extra validation. + } + + void Command::ExecuteInternal(Execution::Context& context) const + { + context.Reporter.Error() << Resource::String::PendingWorkError << std::endl; + THROW_HR(E_NOTIMPL); + } + + Command::Visibility Command::GetVisibility() const + { + if (!ExperimentalFeature::IsEnabled(m_feature)) + { + return Command::Visibility::Hidden; + } + + if (!Settings::GroupPolicies().IsEnabled(m_groupPolicy)) + { + return Command::Visibility::Hidden; + } + + return m_visibility; + } + + std::vector> Command::GetVisibleCommands() const + { + auto commands = GetCommands(); + + commands.erase( + std::remove_if( + commands.begin(), commands.end(), + [](const std::unique_ptr& command) { return command->GetVisibility() == Command::Visibility::Hidden; }), + commands.end()); + + return commands; + } + + std::vector Command::GetVisibleArguments() const + { + auto arguments = GetArguments(); + Argument::GetCommon(arguments); + + arguments.erase( + std::remove_if( + arguments.begin(), arguments.end(), + [](const Argument& arg) { return arg.GetVisibility() == Argument::Visibility::Hidden; }), + arguments.end()); + + return arguments; + } + + void ExecuteWithoutLoggingSuccess(Execution::Context& context, Command* command) + { + try + { + if (!Settings::User().GetWarnings().empty() && + !WI_IsFlagSet(command->GetOutputFlags(), CommandOutputFlags::IgnoreSettingsWarnings)) + { + context.Reporter.Warn() << Resource::String::SettingsWarnings << std::endl; + } + + command->Execute(context); + } + catch (...) + { + context.SetTerminationHR(Workflow::HandleException(context, std::current_exception())); + + LaunchLogsIfRequested(context); + } + } + + int Execute(Execution::Context& context, std::unique_ptr& command) + { + ExecuteWithoutLoggingSuccess(context, command.get()); + + if (SUCCEEDED(context.GetTerminationHR())) + { + Logging::Telemetry().LogCommandSuccess(command->FullName()); + } + + return context.GetTerminationHR(); + } +} diff --git a/src/AppInstallerCLICore/Command.h b/src/AppInstallerCLICore/Command.h index ebc5db47e0..dcf5aa6b47 100644 --- a/src/AppInstallerCLICore/Command.h +++ b/src/AppInstallerCLICore/Command.h @@ -1,158 +1,158 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Argument.h" -#include "ExecutionContext.h" -#include "Invocation.h" -#include "Resources.h" -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::CLI -{ - struct CommandException - { - CommandException(Resource::LocString message) : m_message(std::move(message)) {} - const Utility::LocIndString Message() const { return m_message; } - - private: - Resource::LocString m_message; - }; - - // Flags to control the behavior of the command output. - enum class CommandOutputFlags : int - { - None = 0x0, - IgnoreSettingsWarnings = 0x1, - }; - - DEFINE_ENUM_FLAG_OPERATORS(CommandOutputFlags); - - struct Command - { - // Controls the visibility of the field. - enum class Visibility - { - // Shown in help. - Show, - // Not shown in help. - Hidden, - }; - - Command(std::string_view name, std::string_view parent) : - Command(name, {}, parent) {} - Command(std::string_view name, std::vector aliases, std::string_view parent) : - Command(name, aliases, parent, Settings::ExperimentalFeature::Feature::None) {} - Command(std::string_view name, std::string_view parent, CommandOutputFlags outputFlags) : - Command(name, {}, parent, Command::Visibility::Show, Settings::ExperimentalFeature::Feature::None, Settings::TogglePolicy::Policy::None, outputFlags) {} - Command(std::string_view name, std::vector aliases, std::string_view parent, Command::Visibility visibility) : - Command(name, aliases, parent, visibility, Settings::ExperimentalFeature::Feature::None) {} - Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature) : - Command(name, {}, parent, Command::Visibility::Show, feature) {} - Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature, CommandOutputFlags outputFlags) : - Command(name, {}, parent, Command::Visibility::Show, feature, Settings::TogglePolicy::Policy::None, outputFlags) {} - Command(std::string_view name, std::vector aliases, std::string_view parent, Settings::ExperimentalFeature::Feature feature) : - Command(name, aliases, parent, Command::Visibility::Show, feature) {} - Command(std::string_view name, std::vector aliases, std::string_view parent, Settings::TogglePolicy::Policy groupPolicy) : - Command(name, aliases, parent, Command::Visibility::Show, Settings::ExperimentalFeature::Feature::None, groupPolicy, CommandOutputFlags::None) {} - Command(std::string_view name, std::vector aliases, std::string_view parent, Command::Visibility visibility, Settings::ExperimentalFeature::Feature feature) : - Command(name, aliases, parent, visibility, feature, Settings::TogglePolicy::Policy::None, CommandOutputFlags::None) {} - Command(std::string_view name, std::vector aliases, std::string_view parent, Command::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy) : - Command(name, aliases, parent, visibility, Settings::ExperimentalFeature::Feature::None, groupPolicy, CommandOutputFlags::None) {} - - Command(std::string_view name, - std::vector aliases, - std::string_view parent, - Command::Visibility visibility, - Settings::ExperimentalFeature::Feature feature, - Settings::TogglePolicy::Policy groupPolicy, - CommandOutputFlags outputFlags); - - virtual ~Command() = default; - - Command(const Command&) = default; - Command& operator=(const Command&) = default; - - Command(Command&&) = default; - Command& operator=(Command&&) = default; - - // The character used to split between commands and their parents in FullName. - constexpr static char ParentSplitChar = ':'; - - std::string_view Name() const { return m_name; } - const std::vector& Aliases() const& { return m_aliases; } - const std::string& FullName() const { return m_fullName; } - Command::Visibility GetVisibility() const; - Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } - Settings::TogglePolicy::Policy GroupPolicy() const { return m_groupPolicy; } - CommandOutputFlags GetOutputFlags() const { return m_outputFlags; } - - virtual std::vector> GetCommands() const { return {}; } - virtual std::vector GetArguments() const { return {}; } - std::vector> GetVisibleCommands() const; - std::vector GetVisibleArguments() const; - - virtual Resource::LocString ShortDescription() const = 0; - virtual Resource::LocString LongDescription() const = 0; - - virtual void OutputIntroHeader(Execution::Reporter& reporter) const; - virtual void OutputHelp(Execution::Reporter& reporter, const CommandException* exception = nullptr) const; - virtual Utility::LocIndView HelpLink() const { return {}; } - - virtual std::unique_ptr FindSubCommand(Invocation& inv) const; - virtual void ParseArguments(Invocation& inv, Execution::Args& execArgs) const; - virtual void ValidateArguments(Execution::Args& execArgs) const; - - virtual void Complete(Execution::Context& context) const; - virtual void Complete(Execution::Context& context, Execution::Args::Type valueType) const; - - virtual void Execute(Execution::Context& context) const; - - virtual void Resume(Execution::Context& context) const; - - protected: - void SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value); - - virtual void ValidateArgumentsInternal(Execution::Args& execArgs) const; - virtual void ExecuteInternal(Execution::Context& context) const; - - private: - std::string_view m_name; - std::string m_fullName; - std::vector m_aliases; - Command::Visibility m_visibility; - Settings::ExperimentalFeature::Feature m_feature; - Settings::TogglePolicy::Policy m_groupPolicy; - CommandOutputFlags m_outputFlags; - bool m_selectCurrentCommandIfUnrecognizedSubcommandFound = false; - std::string m_commandArguments; - }; - - template - Container InitializeFromMoveOnly(std::initializer_list il) - { - using Value = typename Container::value_type; - Container result; - - for (const auto& v : il) - { - result.emplace_back(std::move(*const_cast(&v))); - } - - return result; - } - - void ExecuteWithoutLoggingSuccess(Execution::Context& context, Command* command); - - int Execute(Execution::Context& context, std::unique_ptr& command); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Argument.h" +#include "ExecutionContext.h" +#include "Invocation.h" +#include "Resources.h" +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::CLI +{ + struct CommandException + { + CommandException(Resource::LocString message) : m_message(std::move(message)) {} + const Utility::LocIndString Message() const { return m_message; } + + private: + Resource::LocString m_message; + }; + + // Flags to control the behavior of the command output. + enum class CommandOutputFlags : int + { + None = 0x0, + IgnoreSettingsWarnings = 0x1, + }; + + DEFINE_ENUM_FLAG_OPERATORS(CommandOutputFlags); + + struct Command + { + // Controls the visibility of the field. + enum class Visibility + { + // Shown in help. + Show, + // Not shown in help. + Hidden, + }; + + Command(std::string_view name, std::string_view parent) : + Command(name, {}, parent) {} + Command(std::string_view name, std::vector aliases, std::string_view parent) : + Command(name, aliases, parent, Settings::ExperimentalFeature::Feature::None) {} + Command(std::string_view name, std::string_view parent, CommandOutputFlags outputFlags) : + Command(name, {}, parent, Command::Visibility::Show, Settings::ExperimentalFeature::Feature::None, Settings::TogglePolicy::Policy::None, outputFlags) {} + Command(std::string_view name, std::vector aliases, std::string_view parent, Command::Visibility visibility) : + Command(name, aliases, parent, visibility, Settings::ExperimentalFeature::Feature::None) {} + Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature) : + Command(name, {}, parent, Command::Visibility::Show, feature) {} + Command(std::string_view name, std::string_view parent, Settings::ExperimentalFeature::Feature feature, CommandOutputFlags outputFlags) : + Command(name, {}, parent, Command::Visibility::Show, feature, Settings::TogglePolicy::Policy::None, outputFlags) {} + Command(std::string_view name, std::vector aliases, std::string_view parent, Settings::ExperimentalFeature::Feature feature) : + Command(name, aliases, parent, Command::Visibility::Show, feature) {} + Command(std::string_view name, std::vector aliases, std::string_view parent, Settings::TogglePolicy::Policy groupPolicy) : + Command(name, aliases, parent, Command::Visibility::Show, Settings::ExperimentalFeature::Feature::None, groupPolicy, CommandOutputFlags::None) {} + Command(std::string_view name, std::vector aliases, std::string_view parent, Command::Visibility visibility, Settings::ExperimentalFeature::Feature feature) : + Command(name, aliases, parent, visibility, feature, Settings::TogglePolicy::Policy::None, CommandOutputFlags::None) {} + Command(std::string_view name, std::vector aliases, std::string_view parent, Command::Visibility visibility, Settings::TogglePolicy::Policy groupPolicy) : + Command(name, aliases, parent, visibility, Settings::ExperimentalFeature::Feature::None, groupPolicy, CommandOutputFlags::None) {} + + Command(std::string_view name, + std::vector aliases, + std::string_view parent, + Command::Visibility visibility, + Settings::ExperimentalFeature::Feature feature, + Settings::TogglePolicy::Policy groupPolicy, + CommandOutputFlags outputFlags); + + virtual ~Command() = default; + + Command(const Command&) = default; + Command& operator=(const Command&) = default; + + Command(Command&&) = default; + Command& operator=(Command&&) = default; + + // The character used to split between commands and their parents in FullName. + constexpr static char ParentSplitChar = ':'; + + std::string_view Name() const { return m_name; } + const std::vector& Aliases() const& { return m_aliases; } + const std::string& FullName() const { return m_fullName; } + Command::Visibility GetVisibility() const; + Settings::ExperimentalFeature::Feature Feature() const { return m_feature; } + Settings::TogglePolicy::Policy GroupPolicy() const { return m_groupPolicy; } + CommandOutputFlags GetOutputFlags() const { return m_outputFlags; } + + virtual std::vector> GetCommands() const { return {}; } + virtual std::vector GetArguments() const { return {}; } + std::vector> GetVisibleCommands() const; + std::vector GetVisibleArguments() const; + + virtual Resource::LocString ShortDescription() const = 0; + virtual Resource::LocString LongDescription() const = 0; + + virtual void OutputIntroHeader(Execution::Reporter& reporter) const; + virtual void OutputHelp(Execution::Reporter& reporter, const CommandException* exception = nullptr) const; + virtual Utility::LocIndView HelpLink() const { return {}; } + + virtual std::unique_ptr FindSubCommand(Invocation& inv) const; + virtual void ParseArguments(Invocation& inv, Execution::Args& execArgs) const; + virtual void ValidateArguments(Execution::Args& execArgs) const; + + virtual void Complete(Execution::Context& context) const; + virtual void Complete(Execution::Context& context, Execution::Args::Type valueType) const; + + virtual void Execute(Execution::Context& context) const; + + virtual void Resume(Execution::Context& context) const; + + protected: + void SelectCurrentCommandIfUnrecognizedSubcommandFound(bool value); + + virtual void ValidateArgumentsInternal(Execution::Args& execArgs) const; + virtual void ExecuteInternal(Execution::Context& context) const; + + private: + std::string_view m_name; + std::string m_fullName; + std::vector m_aliases; + Command::Visibility m_visibility; + Settings::ExperimentalFeature::Feature m_feature; + Settings::TogglePolicy::Policy m_groupPolicy; + CommandOutputFlags m_outputFlags; + bool m_selectCurrentCommandIfUnrecognizedSubcommandFound = false; + std::string m_commandArguments; + }; + + template + Container InitializeFromMoveOnly(std::initializer_list il) + { + using Value = typename Container::value_type; + Container result; + + for (const auto& v : il) + { + result.emplace_back(std::move(*const_cast(&v))); + } + + return result; + } + + void ExecuteWithoutLoggingSuccess(Execution::Context& context, Command* command); + + int Execute(Execution::Context& context, std::unique_ptr& command); +} diff --git a/src/AppInstallerCLICore/Commands/COMCommand.cpp b/src/AppInstallerCLICore/Commands/COMCommand.cpp index d50c47869d..9f40bc4365 100644 --- a/src/AppInstallerCLICore/Commands/COMCommand.cpp +++ b/src/AppInstallerCLICore/Commands/COMCommand.cpp @@ -7,7 +7,7 @@ #include "Workflows/PromptFlow.h" #include "Workflows/UninstallFlow.h" #include "Workflows/WorkflowBase.h" -#include "Workflows/DependenciesFlow.h" +#include "Workflows/DependenciesFlow.h" #include "Workflows/RepairFlow.h" namespace AppInstaller::CLI @@ -20,7 +20,7 @@ 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 << @@ -42,18 +42,18 @@ namespace AppInstaller::CLI } // IMPORTANT: To use this command, the caller should have already retrieved the InstalledPackageVersion and added it to the Context Data - void COMUninstallCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::UninstallSinglePackage; - } - - // IMPORTANT: To use this command, the caller should have already retrieved the InstalledPackageVersion and added it to the Context Data - void COMRepairCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::InitializeInstallerDownloadAuthenticatorsMap << - Workflow::SelectApplicableInstallerIfNecessary << - Workflow::RepairSinglePackage; - } + void COMUninstallCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::UninstallSinglePackage; + } + + // IMPORTANT: To use this command, the caller should have already retrieved the InstalledPackageVersion and added it to the Context Data + void COMRepairCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::InitializeInstallerDownloadAuthenticatorsMap << + Workflow::SelectApplicableInstallerIfNecessary << + Workflow::RepairSinglePackage; + } } diff --git a/src/AppInstallerCLICore/Commands/COMCommand.h b/src/AppInstallerCLICore/Commands/COMCommand.h index a928bb51a0..f4cc0dd1ef 100644 --- a/src/AppInstallerCLICore/Commands/COMCommand.h +++ b/src/AppInstallerCLICore/Commands/COMCommand.h @@ -9,9 +9,9 @@ namespace AppInstaller::CLI struct COMDownloadCommand final : public Command { constexpr static std::string_view CommandName = "download"sv; - COMDownloadCommand(std::string_view parent) : Command(CommandName, parent) {} - - CLI::Resource::LocString ShortDescription() const override { return {}; } + COMDownloadCommand(std::string_view parent) : Command(CommandName, parent) {} + + CLI::Resource::LocString ShortDescription() const override { return {}; } CLI::Resource::LocString LongDescription() const override { return {}; } protected: @@ -22,9 +22,9 @@ namespace AppInstaller::CLI struct COMInstallCommand final : public Command { constexpr static std::string_view CommandName = "install"sv; - COMInstallCommand(std::string_view parent) : Command(CommandName, parent) {} - - CLI::Resource::LocString ShortDescription() const override { return {}; } + COMInstallCommand(std::string_view parent) : Command(CommandName, parent) {} + + CLI::Resource::LocString ShortDescription() const override { return {}; } CLI::Resource::LocString LongDescription() const override { return {}; } protected: @@ -35,25 +35,25 @@ namespace AppInstaller::CLI struct COMUninstallCommand final : public Command { constexpr static std::string_view CommandName = "uninstall"sv; - COMUninstallCommand(std::string_view parent) : Command(CommandName, parent) {} - - CLI::Resource::LocString ShortDescription() const override { return {}; } + COMUninstallCommand(std::string_view parent) : Command(CommandName, parent) {} + + CLI::Resource::LocString ShortDescription() const override { return {}; } + CLI::Resource::LocString LongDescription() const override { return {}; } + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // IMPORTANT: To use this command, the caller should have already retrieved the InstalledPackageVersion and added it to the Context Data + struct COMRepairCommand final : public Command + { + constexpr static std::string_view CommandName = "repair"sv; + COMRepairCommand(std::string_view parent) : Command(CommandName, parent) {} + + CLI::Resource::LocString ShortDescription() const override { return {}; } CLI::Resource::LocString LongDescription() const override { return {}; } protected: void ExecuteInternal(Execution::Context& context) const override; - }; - - // IMPORTANT: To use this command, the caller should have already retrieved the InstalledPackageVersion and added it to the Context Data - struct COMRepairCommand final : public Command - { - constexpr static std::string_view CommandName = "repair"sv; - COMRepairCommand(std::string_view parent) : Command(CommandName, parent) {} - - CLI::Resource::LocString ShortDescription() const override { return {}; } - CLI::Resource::LocString LongDescription() const override { return {}; } - - protected: - void ExecuteInternal(Execution::Context& context) const override; }; } diff --git a/src/AppInstallerCLICore/Commands/CompleteCommand.cpp b/src/AppInstallerCLICore/Commands/CompleteCommand.cpp index c364cc933b..4d8dd3b62d 100644 --- a/src/AppInstallerCLICore/Commands/CompleteCommand.cpp +++ b/src/AppInstallerCLICore/Commands/CompleteCommand.cpp @@ -1,82 +1,82 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "CompleteCommand.h" -#include "RootCommand.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace std::string_view_literals; - using namespace Execution; - - std::vector CompleteCommand::GetArguments() const - { - return { - Argument{ Args::Type::Word, Resource::String::WordArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, - Argument{ Args::Type::CommandLine, Resource::String::CommandLineArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, - Argument{ Args::Type::Position, Resource::String::PositionArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, - }; - } - - Resource::LocString CompleteCommand::ShortDescription() const - { - return { Resource::String::CompleteCommandShortDescription }; - } - - Resource::LocString CompleteCommand::LongDescription() const - { - return { Resource::String::CompleteCommandLongDescription }; - } - - Utility::LocIndView CompleteCommand::HelpLink() const - { - return "https://aka.ms/winget-command-complete"_liv; - } - - void CompleteCommand::ExecuteInternal(Execution::Context& context) const - { - try - { - CompletionData data{ - context.Args.GetArg(Args::Type::Word), - context.Args.GetArg(Args::Type::CommandLine), - context.Args.GetArg(Args::Type::Position) }; - - std::unique_ptr command = std::make_unique(); - - std::unique_ptr subCommand = command->FindSubCommand(data.BeforeWord()); - while (subCommand) - { - command = std::move(subCommand); - subCommand = command->FindSubCommand(data.BeforeWord()); - } - - // Create a new Context to execute the Complete from - auto subContextPtr = context.CreateSubContext(); - Context& subContext = *subContextPtr; - auto previousThreadGlobals = subContext.SetForCurrentThread(); - - subContext.Reporter.SetChannel(Execution::Reporter::Channel::Completion); - subContext.Add(std::move(data)); - - // Disable all telemetry while doing a completion - Logging::DisableTelemetryScope disable; - - AICLI_LOG(CLI, Info, << "Complete handing off to command " << command->FullName()); - command->Complete(subContext); - } - catch (const CommandException& ce) - { - AICLI_LOG(CLI, Info, << "Error encountered during completion, ignoring: " << ce.Message()); - } - catch (const Settings::GroupPolicyException& e) - { - AICLI_LOG(CLI, Info, << "Error encountered during completion, ignoring: Blocked by Group Policy " << Settings::TogglePolicy::GetPolicy(e.Policy()).RegValueName()); - } - catch (...) - { - AICLI_LOG(CLI, Info, << "Error encountered during completion, ignoring..."); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "CompleteCommand.h" +#include "RootCommand.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace std::string_view_literals; + using namespace Execution; + + std::vector CompleteCommand::GetArguments() const + { + return { + Argument{ Args::Type::Word, Resource::String::WordArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, + Argument{ Args::Type::CommandLine, Resource::String::CommandLineArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, + Argument{ Args::Type::Position, Resource::String::PositionArgumentDescription, ArgumentType::Standard, Argument::Visibility::Example, true }, + }; + } + + Resource::LocString CompleteCommand::ShortDescription() const + { + return { Resource::String::CompleteCommandShortDescription }; + } + + Resource::LocString CompleteCommand::LongDescription() const + { + return { Resource::String::CompleteCommandLongDescription }; + } + + Utility::LocIndView CompleteCommand::HelpLink() const + { + return "https://aka.ms/winget-command-complete"_liv; + } + + void CompleteCommand::ExecuteInternal(Execution::Context& context) const + { + try + { + CompletionData data{ + context.Args.GetArg(Args::Type::Word), + context.Args.GetArg(Args::Type::CommandLine), + context.Args.GetArg(Args::Type::Position) }; + + std::unique_ptr command = std::make_unique(); + + std::unique_ptr subCommand = command->FindSubCommand(data.BeforeWord()); + while (subCommand) + { + command = std::move(subCommand); + subCommand = command->FindSubCommand(data.BeforeWord()); + } + + // Create a new Context to execute the Complete from + auto subContextPtr = context.CreateSubContext(); + Context& subContext = *subContextPtr; + auto previousThreadGlobals = subContext.SetForCurrentThread(); + + subContext.Reporter.SetChannel(Execution::Reporter::Channel::Completion); + subContext.Add(std::move(data)); + + // Disable all telemetry while doing a completion + Logging::DisableTelemetryScope disable; + + AICLI_LOG(CLI, Info, << "Complete handing off to command " << command->FullName()); + command->Complete(subContext); + } + catch (const CommandException& ce) + { + AICLI_LOG(CLI, Info, << "Error encountered during completion, ignoring: " << ce.Message()); + } + catch (const Settings::GroupPolicyException& e) + { + AICLI_LOG(CLI, Info, << "Error encountered during completion, ignoring: Blocked by Group Policy " << Settings::TogglePolicy::GetPolicy(e.Policy()).RegValueName()); + } + catch (...) + { + AICLI_LOG(CLI, Info, << "Error encountered during completion, ignoring..."); + } + } +} diff --git a/src/AppInstallerCLICore/Commands/CompleteCommand.h b/src/AppInstallerCLICore/Commands/CompleteCommand.h index f766f4ef4f..4f3e94cace 100644 --- a/src/AppInstallerCLICore/Commands/CompleteCommand.h +++ b/src/AppInstallerCLICore/Commands/CompleteCommand.h @@ -1,24 +1,24 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - // Command to enable tab completion scenarios, including allowing them to - // be context sensitive in their data output. - struct CompleteCommand final : public Command - { - CompleteCommand(std::string_view parent) : Command("complete", {}, parent, Visibility::Hidden) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + // Command to enable tab completion scenarios, including allowing them to + // be context sensitive in their data output. + struct CompleteCommand final : public Command + { + CompleteCommand(std::string_view parent) : Command("complete", {}, parent, Visibility::Hidden) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp index 7b95fd0ed7..3322bceded 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ConfigureCommand.cpp @@ -1,114 +1,114 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigureCommand.h" -#include "ConfigureListCommand.h" -#include "ConfigureShowCommand.h" -#include "ConfigureTestCommand.h" -#include "ConfigureValidateCommand.h" -#include "ConfigureExportCommand.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include "ConfigurationCommon.h" - -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI -{ - ConfigureCommand::ConfigureCommand(std::string_view parent) : - Command("configure", { "configuration", "dsc"}, parent, Settings::TogglePolicy::Policy::Configuration) - { - SelectCurrentCommandIfUnrecognizedSubcommandFound(true); - } - - std::vector> ConfigureCommand::GetCommands() const - { - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - }); - } - - std::vector ConfigureCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional }, - Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, - Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), - Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ConfigurationAcceptWarning, Resource::String::ConfigurationAcceptWarningArgumentDescription, ArgumentType::Flag }, - Argument{ Execution::Args::Type::ConfigurationSuppressPrologue, Resource::String::ConfigurationSuppressPrologueArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ExtendedFeaturesEnable, Resource::String::ExtendedFeaturesEnableMessage, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ExtendedFeaturesDisable, Resource::String::ExtendedFeaturesDisableMessage, ArgumentType::Flag, Argument::Visibility::Help }, - }; - } - - Resource::LocString ConfigureCommand::ShortDescription() const - { - return { Resource::String::ConfigureCommandShortDescription }; - } - - Resource::LocString ConfigureCommand::LongDescription() const - { - return { Resource::String::ConfigureCommandLongDescription }; - } - - Utility::LocIndView ConfigureCommand::HelpLink() const - { - return "https://aka.ms/winget-command-configure"_liv; - } - - void ConfigureCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesEnable)) - { - context << - EnableExtendedFeatures; - } - else if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) - { - context << - DisableExtendedFeatures; - } - else - { - context << - VerifyIsFullPackage << - VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << - CreateConfigurationProcessorWithoutFactory << - OpenConfigurationSet << - CreateConfigurationProcessor << - ShowConfigurationSet << - ShowConfigurationSetConflicts << - ConfirmConfigurationProcessing(true) << - ApplyConfigurationSet; - } - } - - void ConfigureCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - if (execArgs.Contains(Execution::Args::Type::ExtendedFeaturesEnable) || - execArgs.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) - { - if (execArgs.GetArgsCount() > 1) - { - throw CommandException(Resource::String::ExtendedFeaturesEnableArgumentError); - } - } - else - { - Configuration::ValidateCommonArguments(execArgs, true); - } - } - - void ConfigureCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const - { - if (argType == Execution::Args::Type::ConfigurationHistoryItem) - { - context << CompleteConfigurationHistoryItem; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureCommand.h" +#include "ConfigureListCommand.h" +#include "ConfigureShowCommand.h" +#include "ConfigureTestCommand.h" +#include "ConfigureValidateCommand.h" +#include "ConfigureExportCommand.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include "ConfigurationCommon.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + ConfigureCommand::ConfigureCommand(std::string_view parent) : + Command("configure", { "configuration", "dsc"}, parent, Settings::TogglePolicy::Policy::Configuration) + { + SelectCurrentCommandIfUnrecognizedSubcommandFound(true); + } + + std::vector> ConfigureCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + std::vector ConfigureCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional }, + Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, + Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), + Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ConfigurationAcceptWarning, Resource::String::ConfigurationAcceptWarningArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::ConfigurationSuppressPrologue, Resource::String::ConfigurationSuppressPrologueArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ExtendedFeaturesEnable, Resource::String::ExtendedFeaturesEnableMessage, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ExtendedFeaturesDisable, Resource::String::ExtendedFeaturesDisableMessage, ArgumentType::Flag, Argument::Visibility::Help }, + }; + } + + Resource::LocString ConfigureCommand::ShortDescription() const + { + return { Resource::String::ConfigureCommandShortDescription }; + } + + Resource::LocString ConfigureCommand::LongDescription() const + { + return { Resource::String::ConfigureCommandLongDescription }; + } + + Utility::LocIndView ConfigureCommand::HelpLink() const + { + return "https://aka.ms/winget-command-configure"_liv; + } + + void ConfigureCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesEnable)) + { + context << + EnableExtendedFeatures; + } + else if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) + { + context << + DisableExtendedFeatures; + } + else + { + context << + VerifyIsFullPackage << + VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << + CreateConfigurationProcessorWithoutFactory << + OpenConfigurationSet << + CreateConfigurationProcessor << + ShowConfigurationSet << + ShowConfigurationSetConflicts << + ConfirmConfigurationProcessing(true) << + ApplyConfigurationSet; + } + } + + void ConfigureCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::ExtendedFeaturesEnable) || + execArgs.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) + { + if (execArgs.GetArgsCount() > 1) + { + throw CommandException(Resource::String::ExtendedFeaturesEnableArgumentError); + } + } + else + { + Configuration::ValidateCommonArguments(execArgs, true); + } + } + + void ConfigureCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const + { + if (argType == Execution::Args::Type::ConfigurationHistoryItem) + { + context << CompleteConfigurationHistoryItem; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureCommand.h b/src/AppInstallerCLICore/Commands/ConfigureCommand.h index 094695caaf..6b873c12a1 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureCommand.h +++ b/src/AppInstallerCLICore/Commands/ConfigureCommand.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ConfigureCommand final : public Command - { - ConfigureCommand(std::string_view parent); - - std::vector> GetCommands() const override; - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void Complete(Execution::Context& context, Execution::Args::Type argType) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureCommand final : public Command + { + ConfigureCommand(std::string_view parent); + + std::vector> GetCommands() const override; + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void Complete(Execution::Context& context, Execution::Args::Type argType) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureListCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureListCommand.cpp index 093963ba11..1425cb6b5f 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureListCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ConfigureListCommand.cpp @@ -1,90 +1,90 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigureListCommand.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include "ConfigurationCommon.h" - -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI -{ - std::vector ConfigureListCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard }, - Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ConfigurationHistoryRemove, Resource::String::ConfigurationHistoryRemoveArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ConfigurationStatusWatch, Resource::String::ConfigurationStatusWatchArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }, - }; - } - - Resource::LocString ConfigureListCommand::ShortDescription() const - { - return { Resource::String::ConfigureListCommandShortDescription }; - } - - Resource::LocString ConfigureListCommand::LongDescription() const - { - return { Resource::String::ConfigureListCommandLongDescription }; - } - - Utility::LocIndView ConfigureListCommand::HelpLink() const - { - return "https://aka.ms/winget-command-configure#list"_liv; - } - - void ConfigureListCommand::ExecuteInternal(Execution::Context& context) const - { - context << - VerifyIsFullPackage << - CreateConfigurationProcessorWithoutFactory << - GetConfigurationSetHistory; - - if (context.Args.Contains(Execution::Args::Type::ConfigurationHistoryItem)) - { - context << SelectSetFromHistory; - - if (context.Args.Contains(Execution::Args::Type::OutputFile)) - { - context << SerializeConfigurationSetHistory; - } - - if (context.Args.Contains(Execution::Args::Type::ConfigurationHistoryRemove)) - { - context << RemoveConfigurationSetHistory; - } - else - { - context << ShowSingleConfigurationSetHistory; - } - } - else if (context.Args.Contains(Execution::Args::Type::ConfigurationStatusWatch)) - { - context << MonitorConfigurationStatus; - } - else - { - context << ShowConfigurationSetHistory; - } - } - - void ConfigureListCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - if ((execArgs.Contains(Execution::Args::Type::ConfigurationHistoryRemove) || - execArgs.Contains(Execution::Args::Type::OutputFile)) && - !execArgs.Contains(Execution::Args::Type::ConfigurationHistoryItem)) - { - throw CommandException(Resource::String::RequiredArgError(ArgumentCommon::ForType(Execution::Args::Type::ConfigurationHistoryItem).Name)); - } - } - - void ConfigureListCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const - { - if (argType == Execution::Args::Type::ConfigurationHistoryItem) - { - context << CompleteConfigurationHistoryItem; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureListCommand.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include "ConfigurationCommon.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + std::vector ConfigureListCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard }, + Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ConfigurationHistoryRemove, Resource::String::ConfigurationHistoryRemoveArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ConfigurationStatusWatch, Resource::String::ConfigurationStatusWatchArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden }, + }; + } + + Resource::LocString ConfigureListCommand::ShortDescription() const + { + return { Resource::String::ConfigureListCommandShortDescription }; + } + + Resource::LocString ConfigureListCommand::LongDescription() const + { + return { Resource::String::ConfigureListCommandLongDescription }; + } + + Utility::LocIndView ConfigureListCommand::HelpLink() const + { + return "https://aka.ms/winget-command-configure#list"_liv; + } + + void ConfigureListCommand::ExecuteInternal(Execution::Context& context) const + { + context << + VerifyIsFullPackage << + CreateConfigurationProcessorWithoutFactory << + GetConfigurationSetHistory; + + if (context.Args.Contains(Execution::Args::Type::ConfigurationHistoryItem)) + { + context << SelectSetFromHistory; + + if (context.Args.Contains(Execution::Args::Type::OutputFile)) + { + context << SerializeConfigurationSetHistory; + } + + if (context.Args.Contains(Execution::Args::Type::ConfigurationHistoryRemove)) + { + context << RemoveConfigurationSetHistory; + } + else + { + context << ShowSingleConfigurationSetHistory; + } + } + else if (context.Args.Contains(Execution::Args::Type::ConfigurationStatusWatch)) + { + context << MonitorConfigurationStatus; + } + else + { + context << ShowConfigurationSetHistory; + } + } + + void ConfigureListCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if ((execArgs.Contains(Execution::Args::Type::ConfigurationHistoryRemove) || + execArgs.Contains(Execution::Args::Type::OutputFile)) && + !execArgs.Contains(Execution::Args::Type::ConfigurationHistoryItem)) + { + throw CommandException(Resource::String::RequiredArgError(ArgumentCommon::ForType(Execution::Args::Type::ConfigurationHistoryItem).Name)); + } + } + + void ConfigureListCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const + { + if (argType == Execution::Args::Type::ConfigurationHistoryItem) + { + context << CompleteConfigurationHistoryItem; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureListCommand.h b/src/AppInstallerCLICore/Commands/ConfigureListCommand.h index 0c1653f8c4..b4852e27d3 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureListCommand.h +++ b/src/AppInstallerCLICore/Commands/ConfigureListCommand.h @@ -1,24 +1,24 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ConfigureListCommand final : public Command - { - ConfigureListCommand(std::string_view parent) : Command("list", { "ls" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void Complete(Execution::Context& context, Execution::Args::Type argType) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureListCommand final : public Command + { + ConfigureListCommand(std::string_view parent) : Command("list", { "ls" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void Complete(Execution::Context& context, Execution::Args::Type argType) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureShowCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureShowCommand.cpp index d2eb77c75d..bdf189ea3b 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureShowCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ConfigureShowCommand.cpp @@ -1,62 +1,62 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigureShowCommand.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include "ConfigurationCommon.h" - -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI -{ - std::vector ConfigureShowCommand::GetArguments() const - { - return { - // Required for now, make exclusive when history implemented - Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional }, - Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, - Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), - Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - }; - } - - Resource::LocString ConfigureShowCommand::ShortDescription() const - { - return { Resource::String::ConfigureShowCommandShortDescription }; - } - - Resource::LocString ConfigureShowCommand::LongDescription() const - { - return { Resource::String::ConfigureShowCommandLongDescription }; - } - - Utility::LocIndView ConfigureShowCommand::HelpLink() const - { - return "https://aka.ms/winget-command-configure#show"_liv; - } - - void ConfigureShowCommand::ExecuteInternal(Execution::Context& context) const - { - context << - VerifyIsFullPackage << - VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << - CreateConfigurationProcessorWithoutFactory << - OpenConfigurationSet << - CreateConfigurationProcessor << - ShowConfigurationSet; - } - - void ConfigureShowCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Configuration::ValidateCommonArguments(execArgs, true); - } - - void ConfigureShowCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const - { - if (argType == Execution::Args::Type::ConfigurationHistoryItem) - { - context << CompleteConfigurationHistoryItem; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureShowCommand.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include "ConfigurationCommon.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + std::vector ConfigureShowCommand::GetArguments() const + { + return { + // Required for now, make exclusive when history implemented + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional }, + Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, + Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), + Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + }; + } + + Resource::LocString ConfigureShowCommand::ShortDescription() const + { + return { Resource::String::ConfigureShowCommandShortDescription }; + } + + Resource::LocString ConfigureShowCommand::LongDescription() const + { + return { Resource::String::ConfigureShowCommandLongDescription }; + } + + Utility::LocIndView ConfigureShowCommand::HelpLink() const + { + return "https://aka.ms/winget-command-configure#show"_liv; + } + + void ConfigureShowCommand::ExecuteInternal(Execution::Context& context) const + { + context << + VerifyIsFullPackage << + VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << + CreateConfigurationProcessorWithoutFactory << + OpenConfigurationSet << + CreateConfigurationProcessor << + ShowConfigurationSet; + } + + void ConfigureShowCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Configuration::ValidateCommonArguments(execArgs, true); + } + + void ConfigureShowCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const + { + if (argType == Execution::Args::Type::ConfigurationHistoryItem) + { + context << CompleteConfigurationHistoryItem; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureShowCommand.h b/src/AppInstallerCLICore/Commands/ConfigureShowCommand.h index b85870be28..cfd20da2d7 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureShowCommand.h +++ b/src/AppInstallerCLICore/Commands/ConfigureShowCommand.h @@ -1,24 +1,24 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ConfigureShowCommand final : public Command - { - ConfigureShowCommand(std::string_view parent) : Command("show", { "view" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void Complete(Execution::Context& context, Execution::Args::Type argType) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureShowCommand final : public Command + { + ConfigureShowCommand(std::string_view parent) : Command("show", { "view" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void Complete(Execution::Context& context, Execution::Args::Type argType) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp index 6fd522d889..6f465609bb 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ConfigureTestCommand.cpp @@ -1,66 +1,66 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigureTestCommand.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include "ConfigurationCommon.h" - -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI -{ - std::vector ConfigureTestCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional }, - Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, - Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), - Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ConfigurationSuppressPrologue, Resource::String::ConfigurationSuppressPrologueArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ConfigurationAcceptWarning, Resource::String::ConfigurationAcceptWarningArgumentDescription, ArgumentType::Flag }, - }; - } - - Resource::LocString ConfigureTestCommand::ShortDescription() const - { - return { Resource::String::ConfigureTestCommandShortDescription }; - } - - Resource::LocString ConfigureTestCommand::LongDescription() const - { - return { Resource::String::ConfigureTestCommandLongDescription }; - } - - Utility::LocIndView ConfigureTestCommand::HelpLink() const - { - return "https://aka.ms/winget-command-configure#test"_liv; - } - - void ConfigureTestCommand::ExecuteInternal(Execution::Context& context) const - { - context << - VerifyIsFullPackage << - VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << - CreateConfigurationProcessorWithoutFactory << - OpenConfigurationSet << - CreateConfigurationProcessor << - ShowConfigurationSet << - ShowConfigurationSetConflicts << - ConfirmConfigurationProcessing(false) << - TestConfigurationSet; - } - - void ConfigureTestCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Configuration::ValidateCommonArguments(execArgs, true); - } - - void ConfigureTestCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const - { - if (argType == Execution::Args::Type::ConfigurationHistoryItem) - { - context << CompleteConfigurationHistoryItem; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureTestCommand.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include "ConfigurationCommon.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + std::vector ConfigureTestCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional }, + Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, + Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), + Argument{ Execution::Args::Type::ConfigurationHistoryItem, Resource::String::ConfigurationHistoryItemArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ConfigurationSuppressPrologue, Resource::String::ConfigurationSuppressPrologueArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ConfigurationAcceptWarning, Resource::String::ConfigurationAcceptWarningArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString ConfigureTestCommand::ShortDescription() const + { + return { Resource::String::ConfigureTestCommandShortDescription }; + } + + Resource::LocString ConfigureTestCommand::LongDescription() const + { + return { Resource::String::ConfigureTestCommandLongDescription }; + } + + Utility::LocIndView ConfigureTestCommand::HelpLink() const + { + return "https://aka.ms/winget-command-configure#test"_liv; + } + + void ConfigureTestCommand::ExecuteInternal(Execution::Context& context) const + { + context << + VerifyIsFullPackage << + VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << + CreateConfigurationProcessorWithoutFactory << + OpenConfigurationSet << + CreateConfigurationProcessor << + ShowConfigurationSet << + ShowConfigurationSetConflicts << + ConfirmConfigurationProcessing(false) << + TestConfigurationSet; + } + + void ConfigureTestCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Configuration::ValidateCommonArguments(execArgs, true); + } + + void ConfigureTestCommand::Complete(Execution::Context& context, Execution::Args::Type argType) const + { + if (argType == Execution::Args::Type::ConfigurationHistoryItem) + { + context << CompleteConfigurationHistoryItem; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureTestCommand.h b/src/AppInstallerCLICore/Commands/ConfigureTestCommand.h index 1c866bc7a6..b02af66f1c 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureTestCommand.h +++ b/src/AppInstallerCLICore/Commands/ConfigureTestCommand.h @@ -1,24 +1,24 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ConfigureTestCommand final : public Command - { - ConfigureTestCommand(std::string_view parent) : Command("test", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void Complete(Execution::Context& context, Execution::Args::Type argType) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureTestCommand final : public Command + { + ConfigureTestCommand(std::string_view parent) : Command("test", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void Complete(Execution::Context& context, Execution::Args::Type argType) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp index 8997934052..9d6123ed6d 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.cpp @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigureValidateCommand.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include "ConfigurationCommon.h" - -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI -{ - std::vector ConfigureValidateCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional, true }, - Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, - Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), - }; - } - - Resource::LocString ConfigureValidateCommand::ShortDescription() const - { - return { Resource::String::ConfigureValidateCommandShortDescription }; - } - - Resource::LocString ConfigureValidateCommand::LongDescription() const - { - return { Resource::String::ConfigureValidateCommandLongDescription }; - } - - Utility::LocIndView ConfigureValidateCommand::HelpLink() const - { - return "https://aka.ms/winget-command-configure#validate"_liv; - } - - void ConfigureValidateCommand::ExecuteInternal(Execution::Context& context) const - { - context << - VerifyIsFullPackage << - VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << - CreateConfigurationProcessorWithoutFactory << - OpenConfigurationSet << - CreateConfigurationProcessor << - ValidateConfigurationSetSemantics << - ValidateConfigurationSetUnitProcessors << - ValidateConfigurationSetUnitContents << - ValidateAllGoodMessage; - } - - void ConfigureValidateCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Configuration::ValidateCommonArguments(execArgs); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureValidateCommand.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include "ConfigurationCommon.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + std::vector ConfigureValidateCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::ConfigurationFile, Resource::String::ConfigurationFileArgumentDescription, ArgumentType::Positional, true }, + Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath, ArgumentType::Positional }, + Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), + }; + } + + Resource::LocString ConfigureValidateCommand::ShortDescription() const + { + return { Resource::String::ConfigureValidateCommandShortDescription }; + } + + Resource::LocString ConfigureValidateCommand::LongDescription() const + { + return { Resource::String::ConfigureValidateCommandLongDescription }; + } + + Utility::LocIndView ConfigureValidateCommand::HelpLink() const + { + return "https://aka.ms/winget-command-configure#validate"_liv; + } + + void ConfigureValidateCommand::ExecuteInternal(Execution::Context& context) const + { + context << + VerifyIsFullPackage << + VerifyFileOrUri(Execution::Args::Type::ConfigurationFile) << + CreateConfigurationProcessorWithoutFactory << + OpenConfigurationSet << + CreateConfigurationProcessor << + ValidateConfigurationSetSemantics << + ValidateConfigurationSetUnitProcessors << + ValidateConfigurationSetUnitContents << + ValidateAllGoodMessage; + } + + void ConfigureValidateCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Configuration::ValidateCommonArguments(execArgs); + } +} diff --git a/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.h b/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.h index b409b886a2..24753240f1 100644 --- a/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.h +++ b/src/AppInstallerCLICore/Commands/ConfigureValidateCommand.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ConfigureValidateCommand final : public Command - { - ConfigureValidateCommand(std::string_view parent) : Command("validate", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureValidateCommand final : public Command + { + ConfigureValidateCommand(std::string_view parent) : Command("validate", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DebugCommand.cpp b/src/AppInstallerCLICore/Commands/DebugCommand.cpp index d650eb2bb7..af596c91c5 100644 --- a/src/AppInstallerCLICore/Commands/DebugCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DebugCommand.cpp @@ -1,713 +1,713 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" - -#if _DEBUG -#include "DebugCommand.h" -#include "Public/ConfigurationSetProcessorFactoryRemoting.h" -#include -#include -#include -#include "AppInstallerDownloader.h" -#include "Sixel.h" -#include - -using namespace AppInstaller::CLI::Execution; - -namespace AppInstaller::CLI -{ - namespace - { - std::string MakeInterfaceNameAttribute(std::wstring_view name) - { - std::string result = Utility::ConvertToUTF8(name); - Utility::FindAndReplace(result, "<", "<"); - Utility::FindAndReplace(result, ">", ">"); - return result; - } - - std::string MakeIIDAttribute(const winrt::guid& guid) - { - std::string result; - wchar_t buffer[256]; - - if (StringFromGUID2(guid, buffer, ARRAYSIZE(buffer))) - { - result = AppInstaller::Utility::ConvertToUTF8(buffer); - result = result.substr(1, result.length() - 2); - } - else - { - result = "error"; - } - - return result; - } - - template - void OutputProxyStubInterfaceRegistration(Execution::Context& context) - { - context.Reporter.Info() << "()) << "\" InterfaceId=\"" << MakeIIDAttribute(winrt::guid_of()) << "\" />" << std::endl; - } - - template - void OutputIIDMapping(Execution::Context& context) - { - context.Reporter.Info() << Utility::ConvertToUTF8(winrt::name_of()) << " == " << winrt::guid_of() << std::endl; - } - } - - std::vector> DebugCommand::GetCommands() const - { - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - }); - } - - Resource::LocString DebugCommand::ShortDescription() const - { - return Utility::LocIndString("Debug only dev commands"sv); - } - - Resource::LocString DebugCommand::LongDescription() const - { - return Utility::LocIndString("Commands that are useful in debugging and development."sv); - } - - void DebugCommand::ExecuteInternal(Execution::Context& context) const - { - OutputHelp(context.Reporter); - } - - Resource::LocString DumpProxyStubRegistrationsCommand::ShortDescription() const - { - return Utility::LocIndString("Dump proxy-stub registrations"sv); - } - - Resource::LocString DumpProxyStubRegistrationsCommand::LongDescription() const - { - return Utility::LocIndString("Dump proxy-stub registrations for WinRT interfaces to be place in the manifest."sv); - } - - void DumpProxyStubRegistrationsCommand::ExecuteInternal(Execution::Context& context) const - { - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration>(context); - OutputProxyStubInterfaceRegistration(context); - OutputProxyStubInterfaceRegistration(context); - OutputProxyStubInterfaceRegistration(context); - OutputProxyStubInterfaceRegistration(context); - OutputProxyStubInterfaceRegistration(context); - OutputProxyStubInterfaceRegistration(context); - OutputProxyStubInterfaceRegistration(context); - OutputProxyStubInterfaceRegistration(context); - - // TODO: Fix the layering inversion created by the COM deployment API (probably in order to operate winget.exe against the COM server). - // Then this code can just have a CppWinRT reference to the deployment API and spit out the interface registrations just like for configuration. - HMODULE module = nullptr; - if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(&MakeInterfaceNameAttribute), &module)) - { - return; - } - - // TODO: Have a PRIVATE export from WindowsPackageManager that returns a set of names and IIDs to include from the Deployment API surface - } - - Resource::LocString DumpInterestingIIDsCommand::ShortDescription() const - { - return Utility::LocIndString("Dump some IIDs"sv); - } - - Resource::LocString DumpInterestingIIDsCommand::LongDescription() const - { - return Utility::LocIndString("Dump some IIDs that might be useful."sv); - } - - void DumpInterestingIIDsCommand::ExecuteInternal(Execution::Context& context) const - { - OutputIIDMapping(context); - OutputIIDMapping(context); - OutputIIDMapping(context); - } - - Resource::LocString DumpErrorResourceCommand::ShortDescription() const - { - return Utility::LocIndString("Dump error resources"sv); - } - - Resource::LocString DumpErrorResourceCommand::LongDescription() const - { - return Utility::LocIndString("Dump the error information as resources."sv); - } - - void DumpErrorResourceCommand::ExecuteInternal(Execution::Context& context) const - { - auto info = context.Reporter.Info(); - - // - // Another installation is already in progress. Try again later. - // - for (const auto& error : Errors::GetWinGetErrors()) - { - info << - " Symbol() << "\" xml:space=\"preserve\">\n" - " " << error->GetDescription() << "\n" - " " << std::endl; - } - } - -#define WINGET_DEBUG_SIXEL_FILE Args::Type::Manifest -#define WINGET_DEBUG_SIXEL_ASPECT_RATIO Args::Type::AcceptPackageAgreements -#define WINGET_DEBUG_SIXEL_TRANSPARENT Args::Type::AcceptSourceAgreements -#define WINGET_DEBUG_SIXEL_COLOR_COUNT Args::Type::ConfigurationAcceptWarning -#define WINGET_DEBUG_SIXEL_WIDTH Args::Type::AdminSettingEnable -#define WINGET_DEBUG_SIXEL_HEIGHT Args::Type::AllowReboot -#define WINGET_DEBUG_SIXEL_STRETCH Args::Type::AllVersions -#define WINGET_DEBUG_SIXEL_REPEAT Args::Type::Name -#define WINGET_DEBUG_SIXEL_OUT_FILE Args::Type::BlockingPin - - std::vector ShowSixelCommand::GetArguments() const - { - return { - Argument{ "file", 'f', WINGET_DEBUG_SIXEL_FILE, Resource::String::SourceListUpdatedNever, ArgumentType::Positional }, - Argument{ "aspect-ratio", 'a', WINGET_DEBUG_SIXEL_ASPECT_RATIO, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - Argument{ "transparent", 't', WINGET_DEBUG_SIXEL_TRANSPARENT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument{ "color-count", 'c', WINGET_DEBUG_SIXEL_COLOR_COUNT, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - Argument{ "width", 'w', WINGET_DEBUG_SIXEL_WIDTH, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - Argument{ "height", 'h', WINGET_DEBUG_SIXEL_HEIGHT, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - Argument{ "stretch", 's', WINGET_DEBUG_SIXEL_STRETCH, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument{ "repeat", 'r', WINGET_DEBUG_SIXEL_REPEAT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument{ "out-file", 'o', WINGET_DEBUG_SIXEL_OUT_FILE, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - }; - } - - Resource::LocString ShowSixelCommand::ShortDescription() const - { - return Utility::LocIndString("Output an image with sixels"sv); - } - - Resource::LocString ShowSixelCommand::LongDescription() const - { - return Utility::LocIndString("Outputs an image from a file using sixel format."sv); - } - - void ShowSixelCommand::ExecuteInternal(Execution::Context& context) const - { - using namespace VirtualTerminal; - std::unique_ptr sixelImagePtr; - - std::string imageUrl{ context.Args.GetArg(WINGET_DEBUG_SIXEL_FILE) }; - - if (Utility::IsUrlRemote(imageUrl)) - { - auto imageStream = std::make_unique(); - ProgressCallback emptyCallback; - Utility::DownloadToStream(imageUrl, *imageStream, Utility::DownloadType::Manifest, emptyCallback); - - sixelImagePtr = std::make_unique(*imageStream, Manifest::IconFileTypeEnum::Unknown); - } - else - { - sixelImagePtr = std::make_unique(Utility::ConvertToUTF16(imageUrl)); - } - - Sixel::Image& sixelImage = *sixelImagePtr.get(); - - if (context.Args.Contains(WINGET_DEBUG_SIXEL_ASPECT_RATIO)) - { - switch (context.Args.GetArg(WINGET_DEBUG_SIXEL_ASPECT_RATIO)[0]) - { - case '1': - sixelImage.AspectRatio(Sixel::AspectRatio::OneToOne); - break; - case '2': - sixelImage.AspectRatio(Sixel::AspectRatio::TwoToOne); - break; - case '3': - sixelImage.AspectRatio(Sixel::AspectRatio::ThreeToOne); - break; - case '5': - sixelImage.AspectRatio(Sixel::AspectRatio::FiveToOne); - break; - } - } - - sixelImage.Transparency(context.Args.Contains(WINGET_DEBUG_SIXEL_TRANSPARENT)); - - if (context.Args.Contains(WINGET_DEBUG_SIXEL_COLOR_COUNT)) - { - sixelImage.ColorCount(std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_SIXEL_COLOR_COUNT) })); - } - - if (context.Args.Contains(WINGET_DEBUG_SIXEL_WIDTH) && context.Args.Contains(WINGET_DEBUG_SIXEL_HEIGHT)) - { - sixelImage.RenderSizeInCells( - std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_SIXEL_WIDTH) }), - std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_SIXEL_HEIGHT) })); - } - - sixelImage.StretchSourceToFill(context.Args.Contains(WINGET_DEBUG_SIXEL_STRETCH)); - - sixelImage.UseRepeatSequence(context.Args.Contains(WINGET_DEBUG_SIXEL_REPEAT)); - - if (context.Args.Contains(WINGET_DEBUG_SIXEL_OUT_FILE)) - { - std::ofstream stream{ Utility::ConvertToUTF16(context.Args.GetArg(WINGET_DEBUG_SIXEL_OUT_FILE)) }; - stream << sixelImage.Render().Get(); - } - else - { - OutputStream stream = context.Reporter.GetOutputStream(Reporter::Level::Info); - stream.ClearFormat(); - sixelImage.RenderTo(stream); - - // Force a new line to show entire image - stream << std::endl; - } - } - -#define WINGET_DEBUG_PROGRESS_SIXEL Args::Type::Manifest -#define WINGET_DEBUG_PROGRESS_DISABLED Args::Type::GatedVersion -#define WINGET_DEBUG_PROGRESS_HIDE Args::Type::AcceptPackageAgreements -#define WINGET_DEBUG_PROGRESS_TIME Args::Type::AcceptSourceAgreements -#define WINGET_DEBUG_PROGRESS_MESSAGE Args::Type::ConfigurationAcceptWarning -#define WINGET_DEBUG_PROGRESS_PERCENT Args::Type::AllowReboot -#define WINGET_DEBUG_PROGRESS_POST Args::Type::AllVersions - - std::vector ProgressCommand::GetArguments() const - { - return { - Argument{ "sixel", 's', WINGET_DEBUG_PROGRESS_SIXEL, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument{ "disabled", 'd', WINGET_DEBUG_PROGRESS_DISABLED, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument{ "hide", 'h', WINGET_DEBUG_PROGRESS_HIDE, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument{ "time", 't', WINGET_DEBUG_PROGRESS_TIME, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - Argument{ "message", 'm', WINGET_DEBUG_PROGRESS_MESSAGE, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - Argument{ "percent", 'p', WINGET_DEBUG_PROGRESS_PERCENT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument{ "post", 0, WINGET_DEBUG_PROGRESS_POST, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, - }; - } - - Resource::LocString ProgressCommand::ShortDescription() const - { - return Utility::LocIndString("Show progress"sv); - } - - Resource::LocString ProgressCommand::LongDescription() const - { - return Utility::LocIndString("Show progress with various controls to emulate different behaviors."sv); - } - - void ProgressCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(WINGET_DEBUG_PROGRESS_SIXEL)) - { - context.Reporter.SetStyle(Settings::VisualStyle::Sixel); - } - - if (context.Args.Contains(WINGET_DEBUG_PROGRESS_DISABLED)) - { - context.Reporter.SetStyle(Settings::VisualStyle::Disabled); - } - - auto progress = context.Reporter.BeginAsyncProgress(context.Args.Contains(WINGET_DEBUG_PROGRESS_HIDE)); - - if (context.Args.Contains(WINGET_DEBUG_PROGRESS_MESSAGE)) - { - progress->Callback().SetProgressMessage(context.Args.GetArg(WINGET_DEBUG_PROGRESS_MESSAGE)); - } - - bool sendProgress = context.Args.Contains(WINGET_DEBUG_PROGRESS_PERCENT); - - UINT timeInSeconds = 3600; - if (context.Args.Contains(WINGET_DEBUG_PROGRESS_TIME)) - { - timeInSeconds = std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_PROGRESS_TIME) }); - } - - UINT ticks = timeInSeconds * 10; - for (UINT i = 0; i < ticks; ++i) - { - if (sendProgress) - { - progress->Callback().OnProgress(i, ticks, ProgressType::Bytes); - } - - if (progress->Callback().IsCancelledBy(CancelReason::Any)) - { - sendProgress = false; - break; - } - - std::this_thread::sleep_for(100ms); - } - - if (sendProgress) - { - progress->Callback().OnProgress(ticks, ticks, ProgressType::Bytes); - } - - progress.reset(); - - if (context.Args.Contains(WINGET_DEBUG_PROGRESS_POST)) - { - context.Reporter.Info() << context.Args.GetArg(WINGET_DEBUG_PROGRESS_POST) << std::endl; - } - } - - std::vector GetSignerCommand::GetArguments() const - { - return { - Argument{ "file", 'f', Args::Type::Manifest, Resource::String::SourceListUpdatedNever, ArgumentType::Positional }, - }; - } - - Resource::LocString GetSignerCommand::ShortDescription() const - { - return Utility::LocIndString("Get signer information"sv); - } - - Resource::LocString GetSignerCommand::LongDescription() const - { - return Utility::LocIndString("Gets the signing information for a given path."sv); - } - - void GetSignerCommand::ExecuteInternal(Execution::Context& context) const - { - std::string subject = Certificates::GetAuthenticodeSubject(context.Args.GetArg(Args::Type::Manifest)); - - context.Reporter.Info() << "Subject: " << subject << std::endl; - } - -// ── LogViewerTestCommand ───────────────────────────────────────────────────── - -#define WINGET_DEBUG_LOG_VIEWER_FOLLOW Args::Type::Force - - std::vector LogViewerTestCommand::GetArguments() const - { - return { - Argument{ "follow", 'f', WINGET_DEBUG_LOG_VIEWER_FOLLOW, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - }; - } - - Resource::LocString LogViewerTestCommand::ShortDescription() const - { - return Utility::LocIndString("Emit test logs for the log viewer extension"sv); - } - - Resource::LocString LogViewerTestCommand::LongDescription() const - { - return Utility::LocIndString( - "Emits log entries exercising every channel, level, subchannel, continuation line, " - "and long-line feature of the WinGet Log Viewer VS Code extension. " - "Use --follow to stream additional log lines every 3 seconds (up to 100 iterations)."sv); - } - - void LogViewerTestCommand::ExecuteInternal(Execution::Context& context) const - { - // Ensure all channels and the most verbose level are active so every test entry lands in the file. - auto& logger = AppInstaller::Logging::Log(); - logger.EnableChannel(AppInstaller::Logging::Channel::All); - logger.SetLevel(AppInstaller::Logging::Level::Verbose); - - // ── All five levels on CLI ──────────────────────────────────────────── - AICLI_LOG(CLI, Verbose, << "Log viewer test: Verbose level message"); - AICLI_LOG(CLI, Info, << "Log viewer test: Info level message"); - AICLI_LOG(CLI, Warning, << "Log viewer test: Warning level message"); - AICLI_LOG(CLI, Error, << "Log viewer test: Error level message"); - AICLI_LOG(CLI, Crit, << "Log viewer test: Critical level message"); - - // ── One Info entry on every channel ────────────────────────────────── - AICLI_LOG(Fail, Info, << "Log viewer test: Failure channel"); - AICLI_LOG(SQL, Info, << "Log viewer test: SQL channel"); - AICLI_LOG(Repo, Info, << "Log viewer test: Repository channel"); - AICLI_LOG(YAML, Info, << "Log viewer test: YAML channel"); - AICLI_LOG(Core, Info, << "Log viewer test: Core channel"); - AICLI_LOG(Test, Info, << "Log viewer test: Test channel"); - AICLI_LOG(Config, Info, << "Log viewer test: Configuration channel"); - AICLI_LOG(Workflow, Info, << "Log viewer test: Workflow channel"); - - // ── Subchannel simulation (sub-component logs routed through CLI) ───── - AICLI_LOG(CLI, Info, << "[SQL ] Subchannel test: database query initiated for package lookup"); - AICLI_LOG(CLI, Info, << "[REPO] Subchannel test: fetching package metadata from remote source"); - - // ── Continuation lines (newlines in the message become continuation rows) ── - AICLI_LOG(Core, Warning, << "Package installation encountered multiple issues:\n" - " - Dependency 'vcredist' version 14.0.30704 not found in any configured source\n" - " - Insufficient disk space on C:\\ (requires 512 MB, available 203 MB)\n" - " - Installation directory is read-only: C:\\Program Files\\TestPackage\\1.0.0"); - - // ── Long line (should require horizontal scroll or wrap in the viewer) ─ - AICLI_LOG(Workflow, Info, << "Resolving full package dependency graph: The following packages are required " - "as dependencies and will be installed in sequence if not already present on the system: " - "Microsoft.VCRedist.2015+.x64 (>= 14.0.30704), Microsoft.DotNet.Runtime.7 (>= 7.0.14), " - "Microsoft.WebView2.Runtime (>= 113.0.1774.35), Microsoft.WindowsAppRuntime.1.4 (>= 1.4.231219000). " - "Total estimated download size: 847 MB across 4 installers."); - - context.Reporter.Info() << "Log viewer test burst complete. Open the WinGet log file to review all viewer features." << std::endl; - - if (!context.Args.Contains(WINGET_DEBUG_LOG_VIEWER_FOLLOW)) - { - return; - } - - // ── Follow mode: stream log lines every 3 seconds ──────────────────── - context.Reporter.Info() << "Follow mode active (up to 100 iterations). Press Ctrl-C to stop." << std::endl; - - struct FollowEntry { AppInstaller::Logging::Channel Channel; AppInstaller::Logging::Level Level; std::string_view Message; }; - static constexpr FollowEntry s_entries[] = - { - { AppInstaller::Logging::Channel::CLI, AppInstaller::Logging::Level::Info, "Follow: searching for available package updates" }, - { AppInstaller::Logging::Channel::Repo, AppInstaller::Logging::Level::Info, "Follow: refreshing source index from remote endpoint" }, - { AppInstaller::Logging::Channel::SQL, AppInstaller::Logging::Level::Verbose, "Follow: executing SELECT query on packages table" }, - { AppInstaller::Logging::Channel::Core, AppInstaller::Logging::Level::Info, "Follow: applying version comparison for upgrade eligibility" }, - { AppInstaller::Logging::Channel::YAML, AppInstaller::Logging::Level::Verbose, "Follow: parsing manifest for Microsoft.TestPackage 2.1.0" }, - { AppInstaller::Logging::Channel::Workflow, AppInstaller::Logging::Level::Info, "Follow: evaluating installer selection policy for current architecture" }, - { AppInstaller::Logging::Channel::Config, AppInstaller::Logging::Level::Info, "Follow: reading configuration resource state from DSC provider" }, - { AppInstaller::Logging::Channel::CLI, AppInstaller::Logging::Level::Warning, "Follow: package is pinned to a specific version, skipping upgrade" }, - { AppInstaller::Logging::Channel::Core, AppInstaller::Logging::Level::Info, "Follow: SHA-256 hash verification passed for downloaded installer" }, - { AppInstaller::Logging::Channel::CLI, AppInstaller::Logging::Level::Info, "[SQL ] Follow: subchannel activity routed through CLI during follow" }, - }; - - auto progress = context.Reporter.BeginAsyncProgress(true); - - for (int i = 1; i <= 100; ++i) - { - // Wait 3 seconds, checking for cancellation every 100 ms. - for (int t = 0; t < 30; ++t) - { - if (progress->Callback().IsCancelledBy(CancelReason::Any)) { return; } - std::this_thread::sleep_for(100ms); - } - - // Emit the cycling entry for this iteration. - const auto& e = s_entries[static_cast(i) % ARRAYSIZE(s_entries)]; - const std::string msg = std::string(e.Message) + " [" + std::to_string(i) + "/100]"; - logger.Write(e.Channel, e.Level, msg); - - // Every 5 iterations: multi-line status summary (continuation lines). - if (i % 5 == 0) - { - AICLI_LOG(Core, Info, << "Follow iteration " << i << " status summary:\n" - " Packages checked: " << (i * 7) << "\n" - " Updates available: " << (i % 3) << "\n" - " Sources refreshed: 2"); - } - - // Every 20 iterations: simulated error with a stack trace (continuation lines + HRESULT). - if (i % 20 == 0) - { - AICLI_LOG(Fail, Error, << "Simulated transient error at follow iteration " << i << " [HRESULT 0x80070005]:\n" - " at AppInstaller::Repository::SourceList::OpenSource(std::string_view)\n" - " at AppInstaller::CLI::Workflow::OpenSourcesForSearch(Context&)\n" - " at AppInstaller::CLI::LogViewerTestCommand::ExecuteInternal(Context&)"); - } - } - - context.Reporter.Info() << "Follow mode complete (100 iterations)." << std::endl; - } - -#define WINGET_DEBUG_DSC_RESOURCE_RESOURCE Args::Type::SourceName -#define WINGET_DEBUG_DSC_RESOURCE_EXPORT Args::Type::AllVersions - - std::vector DebugDscResourceCommand::GetArguments() const - { - return { - Argument{ "resource", 'r', WINGET_DEBUG_DSC_RESOURCE_RESOURCE, Resource::String::SourceListUpdatedNever, ArgumentType::Positional }, - Argument{ "export", 'e', WINGET_DEBUG_DSC_RESOURCE_EXPORT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, - Argument::ForType(Args::Type::ConfigurationProcessorPath), - }; - } - - Resource::LocString DebugDscResourceCommand::ShortDescription() const - { - return Utility::LocIndString("Run DSCv3 resource actions"sv); - } - - Resource::LocString DebugDscResourceCommand::LongDescription() const - { - return Utility::LocIndString("Directly invokes DSCv3 resource functions through WinGet's infrastructure, without requiring a full configuration document."sv); - } - - void DebugDscResourceCommand::ExecuteInternal(Execution::Context& context) const - { - using namespace winrt::Microsoft::Management::Configuration; - using namespace winrt::Windows::Foundation::Collections; - - if (!context.Args.Contains(WINGET_DEBUG_DSC_RESOURCE_EXPORT)) - { - OutputHelp(context.Reporter); - return; - } - - std::string resourceName{ context.Args.GetArg(WINGET_DEBUG_DSC_RESOURCE_RESOURCE) }; - - context.Reporter.Info() << "Creating OOP DSCv3 processor factory..." << std::endl; - - IConfigurationSetProcessorFactory factory; - if (Runtime::IsRunningWithLimitedToken()) - { - factory = ConfigurationRemoting::CreateDynamicRuntimeFactory(ConfigurationRemoting::ProcessorEngine::DSCv3); - } - else - { - factory = ConfigurationRemoting::CreateOutOfProcessFactory(ConfigurationRemoting::ProcessorEngine::DSCv3); - } - - auto factoryMap = factory.as>(); - - if (context.Args.Contains(Args::Type::ConfigurationProcessorPath)) - { - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePath), Utility::ConvertToUTF16(context.Args.GetArg(Args::Type::ConfigurationProcessorPath))); - } - else - { - // Run the state machine to locate dsc.exe. - for (;;) - { - winrt::hstring nextTransition = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FindDscStateMachine)); - AICLI_LOG(CLI, Info, << "FindDscStateMachine: " << Utility::ConvertToUTF8(nextTransition)); - - if (nextTransition == L"Found") - { - break; - } - else if (nextTransition == L"NotFound") - { - context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); - } - else - { - // InstallStable/InstallPreview etc. are not supported in debug mode; install DSCv3 manually. - context.Reporter.Error() << "DSCv3 unavailable (" << Utility::ConvertToUTF8(nextTransition) << "); use --processor-path or install DSCv3." << std::endl; - AICLI_TERMINATE_CONTEXT(E_NOTIMPL); - } - } - } - - if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose)) - { - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled), L"True"); - } - - // Build and configure the processor. - ConfigurationProcessor processor{ factory }; - processor.Caller(L"winget-debug"); - - if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose)) - { - processor.MinimumLevel(DiagnosticLevel::Verbose); - } - - processor.Diagnostics([&context](const winrt::Windows::Foundation::IInspectable&, const IDiagnosticInformation& diag) - { - Logging::Level level = Logging::Level::Info; - switch (diag.Level()) - { - case DiagnosticLevel::Verbose: level = Logging::Level::Verbose; break; - case DiagnosticLevel::Informational: level = Logging::Level::Info; break; - case DiagnosticLevel::Warning: level = Logging::Level::Warning; break; - case DiagnosticLevel::Error: level = Logging::Level::Error; break; - case DiagnosticLevel::Critical: level = Logging::Level::Crit; break; - } - context.GetThreadGlobals().GetDiagnosticLogger().Write(Logging::Channel::Config, level, Utility::ConvertToUTF8(diag.Message())); - }); - - // Construct a minimal ConfigurationUnit for the named resource. - ConfigurationUnit unit; - unit.Type(Utility::ConvertToUTF16(resourceName)); - unit.Identifier(L"debug-item"); - unit.Intent(ConfigurationUnitIntent::Inform); - - context.Reporter.Info() << "Exporting resource: " << resourceName << std::endl; - - auto progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationExportingUnit()); - - GetAllConfigurationUnitsResult exportResult = nullptr; - { - auto exportAction = processor.GetAllUnitsAsync(unit); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { exportAction.Cancel(); }); - exportResult = exportAction.get(); - } - - progressScope.reset(); - - HRESULT hr = exportResult.ResultInformation().ResultCode(); - if (FAILED(hr)) - { - auto description = exportResult.ResultInformation().Description(); - context.Reporter.Error() << "Export failed (0x" << Logging::SetHRFormat << hr << "): "; - if (!description.empty()) - { - context.Reporter.Error() << Utility::ConvertToUTF8(description); - } - context.Reporter.Error() << std::endl; - AICLI_TERMINATE_CONTEXT(hr); - } - - auto units = exportResult.Units(); - context.Reporter.Info() << "Exported " << units.Size() << " instance(s):" << std::endl; - - for (const auto& resultUnit : units) - { - context.Reporter.Info() << " Type: " << Utility::ConvertToUTF8(resultUnit.Type()) << std::endl; - context.Reporter.Info() << " Identifier: " << Utility::ConvertToUTF8(resultUnit.Identifier()) << std::endl; - - auto settings = resultUnit.Settings(); - if (settings && settings.Size() > 0) - { - context.Reporter.Info() << " Settings:" << std::endl; - for (const auto& [key, value] : settings) - { - auto prop = value.try_as(); - if (prop) - { - std::string valueStr; - switch (prop.Type()) - { - case winrt::Windows::Foundation::PropertyType::String: - valueStr = Utility::ConvertToUTF8(prop.GetString()); - break; - case winrt::Windows::Foundation::PropertyType::Boolean: - valueStr = prop.GetBoolean() ? "true" : "false"; - break; - case winrt::Windows::Foundation::PropertyType::Int32: - valueStr = std::to_string(prop.GetInt32()); - break; - case winrt::Windows::Foundation::PropertyType::Int64: - valueStr = std::to_string(prop.GetInt64()); - break; - default: - valueStr = "(unsupported type)"; - break; - } - context.Reporter.Info() << " " << Utility::ConvertToUTF8(key) << ": " << valueStr << std::endl; - } - } - } - - context.Reporter.Info() << std::endl; - } - } -} - -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +#if _DEBUG +#include "DebugCommand.h" +#include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include +#include +#include +#include "AppInstallerDownloader.h" +#include "Sixel.h" +#include + +using namespace AppInstaller::CLI::Execution; + +namespace AppInstaller::CLI +{ + namespace + { + std::string MakeInterfaceNameAttribute(std::wstring_view name) + { + std::string result = Utility::ConvertToUTF8(name); + Utility::FindAndReplace(result, "<", "<"); + Utility::FindAndReplace(result, ">", ">"); + return result; + } + + std::string MakeIIDAttribute(const winrt::guid& guid) + { + std::string result; + wchar_t buffer[256]; + + if (StringFromGUID2(guid, buffer, ARRAYSIZE(buffer))) + { + result = AppInstaller::Utility::ConvertToUTF8(buffer); + result = result.substr(1, result.length() - 2); + } + else + { + result = "error"; + } + + return result; + } + + template + void OutputProxyStubInterfaceRegistration(Execution::Context& context) + { + context.Reporter.Info() << "()) << "\" InterfaceId=\"" << MakeIIDAttribute(winrt::guid_of()) << "\" />" << std::endl; + } + + template + void OutputIIDMapping(Execution::Context& context) + { + context.Reporter.Info() << Utility::ConvertToUTF8(winrt::name_of()) << " == " << winrt::guid_of() << std::endl; + } + } + + std::vector> DebugCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + Resource::LocString DebugCommand::ShortDescription() const + { + return Utility::LocIndString("Debug only dev commands"sv); + } + + Resource::LocString DebugCommand::LongDescription() const + { + return Utility::LocIndString("Commands that are useful in debugging and development."sv); + } + + void DebugCommand::ExecuteInternal(Execution::Context& context) const + { + OutputHelp(context.Reporter); + } + + Resource::LocString DumpProxyStubRegistrationsCommand::ShortDescription() const + { + return Utility::LocIndString("Dump proxy-stub registrations"sv); + } + + Resource::LocString DumpProxyStubRegistrationsCommand::LongDescription() const + { + return Utility::LocIndString("Dump proxy-stub registrations for WinRT interfaces to be place in the manifest."sv); + } + + void DumpProxyStubRegistrationsCommand::ExecuteInternal(Execution::Context& context) const + { + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration>(context); + OutputProxyStubInterfaceRegistration(context); + OutputProxyStubInterfaceRegistration(context); + OutputProxyStubInterfaceRegistration(context); + OutputProxyStubInterfaceRegistration(context); + OutputProxyStubInterfaceRegistration(context); + OutputProxyStubInterfaceRegistration(context); + OutputProxyStubInterfaceRegistration(context); + OutputProxyStubInterfaceRegistration(context); + + // TODO: Fix the layering inversion created by the COM deployment API (probably in order to operate winget.exe against the COM server). + // Then this code can just have a CppWinRT reference to the deployment API and spit out the interface registrations just like for configuration. + HMODULE module = nullptr; + if (!GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(&MakeInterfaceNameAttribute), &module)) + { + return; + } + + // TODO: Have a PRIVATE export from WindowsPackageManager that returns a set of names and IIDs to include from the Deployment API surface + } + + Resource::LocString DumpInterestingIIDsCommand::ShortDescription() const + { + return Utility::LocIndString("Dump some IIDs"sv); + } + + Resource::LocString DumpInterestingIIDsCommand::LongDescription() const + { + return Utility::LocIndString("Dump some IIDs that might be useful."sv); + } + + void DumpInterestingIIDsCommand::ExecuteInternal(Execution::Context& context) const + { + OutputIIDMapping(context); + OutputIIDMapping(context); + OutputIIDMapping(context); + } + + Resource::LocString DumpErrorResourceCommand::ShortDescription() const + { + return Utility::LocIndString("Dump error resources"sv); + } + + Resource::LocString DumpErrorResourceCommand::LongDescription() const + { + return Utility::LocIndString("Dump the error information as resources."sv); + } + + void DumpErrorResourceCommand::ExecuteInternal(Execution::Context& context) const + { + auto info = context.Reporter.Info(); + + // + // Another installation is already in progress. Try again later. + // + for (const auto& error : Errors::GetWinGetErrors()) + { + info << + " Symbol() << "\" xml:space=\"preserve\">\n" + " " << error->GetDescription() << "\n" + " " << std::endl; + } + } + +#define WINGET_DEBUG_SIXEL_FILE Args::Type::Manifest +#define WINGET_DEBUG_SIXEL_ASPECT_RATIO Args::Type::AcceptPackageAgreements +#define WINGET_DEBUG_SIXEL_TRANSPARENT Args::Type::AcceptSourceAgreements +#define WINGET_DEBUG_SIXEL_COLOR_COUNT Args::Type::ConfigurationAcceptWarning +#define WINGET_DEBUG_SIXEL_WIDTH Args::Type::AdminSettingEnable +#define WINGET_DEBUG_SIXEL_HEIGHT Args::Type::AllowReboot +#define WINGET_DEBUG_SIXEL_STRETCH Args::Type::AllVersions +#define WINGET_DEBUG_SIXEL_REPEAT Args::Type::Name +#define WINGET_DEBUG_SIXEL_OUT_FILE Args::Type::BlockingPin + + std::vector ShowSixelCommand::GetArguments() const + { + return { + Argument{ "file", 'f', WINGET_DEBUG_SIXEL_FILE, Resource::String::SourceListUpdatedNever, ArgumentType::Positional }, + Argument{ "aspect-ratio", 'a', WINGET_DEBUG_SIXEL_ASPECT_RATIO, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + Argument{ "transparent", 't', WINGET_DEBUG_SIXEL_TRANSPARENT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument{ "color-count", 'c', WINGET_DEBUG_SIXEL_COLOR_COUNT, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + Argument{ "width", 'w', WINGET_DEBUG_SIXEL_WIDTH, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + Argument{ "height", 'h', WINGET_DEBUG_SIXEL_HEIGHT, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + Argument{ "stretch", 's', WINGET_DEBUG_SIXEL_STRETCH, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument{ "repeat", 'r', WINGET_DEBUG_SIXEL_REPEAT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument{ "out-file", 'o', WINGET_DEBUG_SIXEL_OUT_FILE, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + }; + } + + Resource::LocString ShowSixelCommand::ShortDescription() const + { + return Utility::LocIndString("Output an image with sixels"sv); + } + + Resource::LocString ShowSixelCommand::LongDescription() const + { + return Utility::LocIndString("Outputs an image from a file using sixel format."sv); + } + + void ShowSixelCommand::ExecuteInternal(Execution::Context& context) const + { + using namespace VirtualTerminal; + std::unique_ptr sixelImagePtr; + + std::string imageUrl{ context.Args.GetArg(WINGET_DEBUG_SIXEL_FILE) }; + + if (Utility::IsUrlRemote(imageUrl)) + { + auto imageStream = std::make_unique(); + ProgressCallback emptyCallback; + Utility::DownloadToStream(imageUrl, *imageStream, Utility::DownloadType::Manifest, emptyCallback); + + sixelImagePtr = std::make_unique(*imageStream, Manifest::IconFileTypeEnum::Unknown); + } + else + { + sixelImagePtr = std::make_unique(Utility::ConvertToUTF16(imageUrl)); + } + + Sixel::Image& sixelImage = *sixelImagePtr.get(); + + if (context.Args.Contains(WINGET_DEBUG_SIXEL_ASPECT_RATIO)) + { + switch (context.Args.GetArg(WINGET_DEBUG_SIXEL_ASPECT_RATIO)[0]) + { + case '1': + sixelImage.AspectRatio(Sixel::AspectRatio::OneToOne); + break; + case '2': + sixelImage.AspectRatio(Sixel::AspectRatio::TwoToOne); + break; + case '3': + sixelImage.AspectRatio(Sixel::AspectRatio::ThreeToOne); + break; + case '5': + sixelImage.AspectRatio(Sixel::AspectRatio::FiveToOne); + break; + } + } + + sixelImage.Transparency(context.Args.Contains(WINGET_DEBUG_SIXEL_TRANSPARENT)); + + if (context.Args.Contains(WINGET_DEBUG_SIXEL_COLOR_COUNT)) + { + sixelImage.ColorCount(std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_SIXEL_COLOR_COUNT) })); + } + + if (context.Args.Contains(WINGET_DEBUG_SIXEL_WIDTH) && context.Args.Contains(WINGET_DEBUG_SIXEL_HEIGHT)) + { + sixelImage.RenderSizeInCells( + std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_SIXEL_WIDTH) }), + std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_SIXEL_HEIGHT) })); + } + + sixelImage.StretchSourceToFill(context.Args.Contains(WINGET_DEBUG_SIXEL_STRETCH)); + + sixelImage.UseRepeatSequence(context.Args.Contains(WINGET_DEBUG_SIXEL_REPEAT)); + + if (context.Args.Contains(WINGET_DEBUG_SIXEL_OUT_FILE)) + { + std::ofstream stream{ Utility::ConvertToUTF16(context.Args.GetArg(WINGET_DEBUG_SIXEL_OUT_FILE)) }; + stream << sixelImage.Render().Get(); + } + else + { + OutputStream stream = context.Reporter.GetOutputStream(Reporter::Level::Info); + stream.ClearFormat(); + sixelImage.RenderTo(stream); + + // Force a new line to show entire image + stream << std::endl; + } + } + +#define WINGET_DEBUG_PROGRESS_SIXEL Args::Type::Manifest +#define WINGET_DEBUG_PROGRESS_DISABLED Args::Type::GatedVersion +#define WINGET_DEBUG_PROGRESS_HIDE Args::Type::AcceptPackageAgreements +#define WINGET_DEBUG_PROGRESS_TIME Args::Type::AcceptSourceAgreements +#define WINGET_DEBUG_PROGRESS_MESSAGE Args::Type::ConfigurationAcceptWarning +#define WINGET_DEBUG_PROGRESS_PERCENT Args::Type::AllowReboot +#define WINGET_DEBUG_PROGRESS_POST Args::Type::AllVersions + + std::vector ProgressCommand::GetArguments() const + { + return { + Argument{ "sixel", 's', WINGET_DEBUG_PROGRESS_SIXEL, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument{ "disabled", 'd', WINGET_DEBUG_PROGRESS_DISABLED, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument{ "hide", 'h', WINGET_DEBUG_PROGRESS_HIDE, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument{ "time", 't', WINGET_DEBUG_PROGRESS_TIME, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + Argument{ "message", 'm', WINGET_DEBUG_PROGRESS_MESSAGE, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + Argument{ "percent", 'p', WINGET_DEBUG_PROGRESS_PERCENT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument{ "post", 0, WINGET_DEBUG_PROGRESS_POST, Resource::String::SourceListUpdatedNever, ArgumentType::Standard }, + }; + } + + Resource::LocString ProgressCommand::ShortDescription() const + { + return Utility::LocIndString("Show progress"sv); + } + + Resource::LocString ProgressCommand::LongDescription() const + { + return Utility::LocIndString("Show progress with various controls to emulate different behaviors."sv); + } + + void ProgressCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(WINGET_DEBUG_PROGRESS_SIXEL)) + { + context.Reporter.SetStyle(Settings::VisualStyle::Sixel); + } + + if (context.Args.Contains(WINGET_DEBUG_PROGRESS_DISABLED)) + { + context.Reporter.SetStyle(Settings::VisualStyle::Disabled); + } + + auto progress = context.Reporter.BeginAsyncProgress(context.Args.Contains(WINGET_DEBUG_PROGRESS_HIDE)); + + if (context.Args.Contains(WINGET_DEBUG_PROGRESS_MESSAGE)) + { + progress->Callback().SetProgressMessage(context.Args.GetArg(WINGET_DEBUG_PROGRESS_MESSAGE)); + } + + bool sendProgress = context.Args.Contains(WINGET_DEBUG_PROGRESS_PERCENT); + + UINT timeInSeconds = 3600; + if (context.Args.Contains(WINGET_DEBUG_PROGRESS_TIME)) + { + timeInSeconds = std::stoul(std::string{ context.Args.GetArg(WINGET_DEBUG_PROGRESS_TIME) }); + } + + UINT ticks = timeInSeconds * 10; + for (UINT i = 0; i < ticks; ++i) + { + if (sendProgress) + { + progress->Callback().OnProgress(i, ticks, ProgressType::Bytes); + } + + if (progress->Callback().IsCancelledBy(CancelReason::Any)) + { + sendProgress = false; + break; + } + + std::this_thread::sleep_for(100ms); + } + + if (sendProgress) + { + progress->Callback().OnProgress(ticks, ticks, ProgressType::Bytes); + } + + progress.reset(); + + if (context.Args.Contains(WINGET_DEBUG_PROGRESS_POST)) + { + context.Reporter.Info() << context.Args.GetArg(WINGET_DEBUG_PROGRESS_POST) << std::endl; + } + } + + std::vector GetSignerCommand::GetArguments() const + { + return { + Argument{ "file", 'f', Args::Type::Manifest, Resource::String::SourceListUpdatedNever, ArgumentType::Positional }, + }; + } + + Resource::LocString GetSignerCommand::ShortDescription() const + { + return Utility::LocIndString("Get signer information"sv); + } + + Resource::LocString GetSignerCommand::LongDescription() const + { + return Utility::LocIndString("Gets the signing information for a given path."sv); + } + + void GetSignerCommand::ExecuteInternal(Execution::Context& context) const + { + std::string subject = Certificates::GetAuthenticodeSubject(context.Args.GetArg(Args::Type::Manifest)); + + context.Reporter.Info() << "Subject: " << subject << std::endl; + } + +// ── LogViewerTestCommand ───────────────────────────────────────────────────── + +#define WINGET_DEBUG_LOG_VIEWER_FOLLOW Args::Type::Force + + std::vector LogViewerTestCommand::GetArguments() const + { + return { + Argument{ "follow", 'f', WINGET_DEBUG_LOG_VIEWER_FOLLOW, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + }; + } + + Resource::LocString LogViewerTestCommand::ShortDescription() const + { + return Utility::LocIndString("Emit test logs for the log viewer extension"sv); + } + + Resource::LocString LogViewerTestCommand::LongDescription() const + { + return Utility::LocIndString( + "Emits log entries exercising every channel, level, subchannel, continuation line, " + "and long-line feature of the WinGet Log Viewer VS Code extension. " + "Use --follow to stream additional log lines every 3 seconds (up to 100 iterations)."sv); + } + + void LogViewerTestCommand::ExecuteInternal(Execution::Context& context) const + { + // Ensure all channels and the most verbose level are active so every test entry lands in the file. + auto& logger = AppInstaller::Logging::Log(); + logger.EnableChannel(AppInstaller::Logging::Channel::All); + logger.SetLevel(AppInstaller::Logging::Level::Verbose); + + // ── All five levels on CLI ──────────────────────────────────────────── + AICLI_LOG(CLI, Verbose, << "Log viewer test: Verbose level message"); + AICLI_LOG(CLI, Info, << "Log viewer test: Info level message"); + AICLI_LOG(CLI, Warning, << "Log viewer test: Warning level message"); + AICLI_LOG(CLI, Error, << "Log viewer test: Error level message"); + AICLI_LOG(CLI, Crit, << "Log viewer test: Critical level message"); + + // ── One Info entry on every channel ────────────────────────────────── + AICLI_LOG(Fail, Info, << "Log viewer test: Failure channel"); + AICLI_LOG(SQL, Info, << "Log viewer test: SQL channel"); + AICLI_LOG(Repo, Info, << "Log viewer test: Repository channel"); + AICLI_LOG(YAML, Info, << "Log viewer test: YAML channel"); + AICLI_LOG(Core, Info, << "Log viewer test: Core channel"); + AICLI_LOG(Test, Info, << "Log viewer test: Test channel"); + AICLI_LOG(Config, Info, << "Log viewer test: Configuration channel"); + AICLI_LOG(Workflow, Info, << "Log viewer test: Workflow channel"); + + // ── Subchannel simulation (sub-component logs routed through CLI) ───── + AICLI_LOG(CLI, Info, << "[SQL ] Subchannel test: database query initiated for package lookup"); + AICLI_LOG(CLI, Info, << "[REPO] Subchannel test: fetching package metadata from remote source"); + + // ── Continuation lines (newlines in the message become continuation rows) ── + AICLI_LOG(Core, Warning, << "Package installation encountered multiple issues:\n" + " - Dependency 'vcredist' version 14.0.30704 not found in any configured source\n" + " - Insufficient disk space on C:\\ (requires 512 MB, available 203 MB)\n" + " - Installation directory is read-only: C:\\Program Files\\TestPackage\\1.0.0"); + + // ── Long line (should require horizontal scroll or wrap in the viewer) ─ + AICLI_LOG(Workflow, Info, << "Resolving full package dependency graph: The following packages are required " + "as dependencies and will be installed in sequence if not already present on the system: " + "Microsoft.VCRedist.2015+.x64 (>= 14.0.30704), Microsoft.DotNet.Runtime.7 (>= 7.0.14), " + "Microsoft.WebView2.Runtime (>= 113.0.1774.35), Microsoft.WindowsAppRuntime.1.4 (>= 1.4.231219000). " + "Total estimated download size: 847 MB across 4 installers."); + + context.Reporter.Info() << "Log viewer test burst complete. Open the WinGet log file to review all viewer features." << std::endl; + + if (!context.Args.Contains(WINGET_DEBUG_LOG_VIEWER_FOLLOW)) + { + return; + } + + // ── Follow mode: stream log lines every 3 seconds ──────────────────── + context.Reporter.Info() << "Follow mode active (up to 100 iterations). Press Ctrl-C to stop." << std::endl; + + struct FollowEntry { AppInstaller::Logging::Channel Channel; AppInstaller::Logging::Level Level; std::string_view Message; }; + static constexpr FollowEntry s_entries[] = + { + { AppInstaller::Logging::Channel::CLI, AppInstaller::Logging::Level::Info, "Follow: searching for available package updates" }, + { AppInstaller::Logging::Channel::Repo, AppInstaller::Logging::Level::Info, "Follow: refreshing source index from remote endpoint" }, + { AppInstaller::Logging::Channel::SQL, AppInstaller::Logging::Level::Verbose, "Follow: executing SELECT query on packages table" }, + { AppInstaller::Logging::Channel::Core, AppInstaller::Logging::Level::Info, "Follow: applying version comparison for upgrade eligibility" }, + { AppInstaller::Logging::Channel::YAML, AppInstaller::Logging::Level::Verbose, "Follow: parsing manifest for Microsoft.TestPackage 2.1.0" }, + { AppInstaller::Logging::Channel::Workflow, AppInstaller::Logging::Level::Info, "Follow: evaluating installer selection policy for current architecture" }, + { AppInstaller::Logging::Channel::Config, AppInstaller::Logging::Level::Info, "Follow: reading configuration resource state from DSC provider" }, + { AppInstaller::Logging::Channel::CLI, AppInstaller::Logging::Level::Warning, "Follow: package is pinned to a specific version, skipping upgrade" }, + { AppInstaller::Logging::Channel::Core, AppInstaller::Logging::Level::Info, "Follow: SHA-256 hash verification passed for downloaded installer" }, + { AppInstaller::Logging::Channel::CLI, AppInstaller::Logging::Level::Info, "[SQL ] Follow: subchannel activity routed through CLI during follow" }, + }; + + auto progress = context.Reporter.BeginAsyncProgress(true); + + for (int i = 1; i <= 100; ++i) + { + // Wait 3 seconds, checking for cancellation every 100 ms. + for (int t = 0; t < 30; ++t) + { + if (progress->Callback().IsCancelledBy(CancelReason::Any)) { return; } + std::this_thread::sleep_for(100ms); + } + + // Emit the cycling entry for this iteration. + const auto& e = s_entries[static_cast(i) % ARRAYSIZE(s_entries)]; + const std::string msg = std::string(e.Message) + " [" + std::to_string(i) + "/100]"; + logger.Write(e.Channel, e.Level, msg); + + // Every 5 iterations: multi-line status summary (continuation lines). + if (i % 5 == 0) + { + AICLI_LOG(Core, Info, << "Follow iteration " << i << " status summary:\n" + " Packages checked: " << (i * 7) << "\n" + " Updates available: " << (i % 3) << "\n" + " Sources refreshed: 2"); + } + + // Every 20 iterations: simulated error with a stack trace (continuation lines + HRESULT). + if (i % 20 == 0) + { + AICLI_LOG(Fail, Error, << "Simulated transient error at follow iteration " << i << " [HRESULT 0x80070005]:\n" + " at AppInstaller::Repository::SourceList::OpenSource(std::string_view)\n" + " at AppInstaller::CLI::Workflow::OpenSourcesForSearch(Context&)\n" + " at AppInstaller::CLI::LogViewerTestCommand::ExecuteInternal(Context&)"); + } + } + + context.Reporter.Info() << "Follow mode complete (100 iterations)." << std::endl; + } + +#define WINGET_DEBUG_DSC_RESOURCE_RESOURCE Args::Type::SourceName +#define WINGET_DEBUG_DSC_RESOURCE_EXPORT Args::Type::AllVersions + + std::vector DebugDscResourceCommand::GetArguments() const + { + return { + Argument{ "resource", 'r', WINGET_DEBUG_DSC_RESOURCE_RESOURCE, Resource::String::SourceListUpdatedNever, ArgumentType::Positional }, + Argument{ "export", 'e', WINGET_DEBUG_DSC_RESOURCE_EXPORT, Resource::String::SourceListUpdatedNever, ArgumentType::Flag }, + Argument::ForType(Args::Type::ConfigurationProcessorPath), + }; + } + + Resource::LocString DebugDscResourceCommand::ShortDescription() const + { + return Utility::LocIndString("Run DSCv3 resource actions"sv); + } + + Resource::LocString DebugDscResourceCommand::LongDescription() const + { + return Utility::LocIndString("Directly invokes DSCv3 resource functions through WinGet's infrastructure, without requiring a full configuration document."sv); + } + + void DebugDscResourceCommand::ExecuteInternal(Execution::Context& context) const + { + using namespace winrt::Microsoft::Management::Configuration; + using namespace winrt::Windows::Foundation::Collections; + + if (!context.Args.Contains(WINGET_DEBUG_DSC_RESOURCE_EXPORT)) + { + OutputHelp(context.Reporter); + return; + } + + std::string resourceName{ context.Args.GetArg(WINGET_DEBUG_DSC_RESOURCE_RESOURCE) }; + + context.Reporter.Info() << "Creating OOP DSCv3 processor factory..." << std::endl; + + IConfigurationSetProcessorFactory factory; + if (Runtime::IsRunningWithLimitedToken()) + { + factory = ConfigurationRemoting::CreateDynamicRuntimeFactory(ConfigurationRemoting::ProcessorEngine::DSCv3); + } + else + { + factory = ConfigurationRemoting::CreateOutOfProcessFactory(ConfigurationRemoting::ProcessorEngine::DSCv3); + } + + auto factoryMap = factory.as>(); + + if (context.Args.Contains(Args::Type::ConfigurationProcessorPath)) + { + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePath), Utility::ConvertToUTF16(context.Args.GetArg(Args::Type::ConfigurationProcessorPath))); + } + else + { + // Run the state machine to locate dsc.exe. + for (;;) + { + winrt::hstring nextTransition = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FindDscStateMachine)); + AICLI_LOG(CLI, Info, << "FindDscStateMachine: " << Utility::ConvertToUTF8(nextTransition)); + + if (nextTransition == L"Found") + { + break; + } + else if (nextTransition == L"NotFound") + { + context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + } + else + { + // InstallStable/InstallPreview etc. are not supported in debug mode; install DSCv3 manually. + context.Reporter.Error() << "DSCv3 unavailable (" << Utility::ConvertToUTF8(nextTransition) << "); use --processor-path or install DSCv3." << std::endl; + AICLI_TERMINATE_CONTEXT(E_NOTIMPL); + } + } + } + + if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose)) + { + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled), L"True"); + } + + // Build and configure the processor. + ConfigurationProcessor processor{ factory }; + processor.Caller(L"winget-debug"); + + if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose)) + { + processor.MinimumLevel(DiagnosticLevel::Verbose); + } + + processor.Diagnostics([&context](const winrt::Windows::Foundation::IInspectable&, const IDiagnosticInformation& diag) + { + Logging::Level level = Logging::Level::Info; + switch (diag.Level()) + { + case DiagnosticLevel::Verbose: level = Logging::Level::Verbose; break; + case DiagnosticLevel::Informational: level = Logging::Level::Info; break; + case DiagnosticLevel::Warning: level = Logging::Level::Warning; break; + case DiagnosticLevel::Error: level = Logging::Level::Error; break; + case DiagnosticLevel::Critical: level = Logging::Level::Crit; break; + } + context.GetThreadGlobals().GetDiagnosticLogger().Write(Logging::Channel::Config, level, Utility::ConvertToUTF8(diag.Message())); + }); + + // Construct a minimal ConfigurationUnit for the named resource. + ConfigurationUnit unit; + unit.Type(Utility::ConvertToUTF16(resourceName)); + unit.Identifier(L"debug-item"); + unit.Intent(ConfigurationUnitIntent::Inform); + + context.Reporter.Info() << "Exporting resource: " << resourceName << std::endl; + + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationExportingUnit()); + + GetAllConfigurationUnitsResult exportResult = nullptr; + { + auto exportAction = processor.GetAllUnitsAsync(unit); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { exportAction.Cancel(); }); + exportResult = exportAction.get(); + } + + progressScope.reset(); + + HRESULT hr = exportResult.ResultInformation().ResultCode(); + if (FAILED(hr)) + { + auto description = exportResult.ResultInformation().Description(); + context.Reporter.Error() << "Export failed (0x" << Logging::SetHRFormat << hr << "): "; + if (!description.empty()) + { + context.Reporter.Error() << Utility::ConvertToUTF8(description); + } + context.Reporter.Error() << std::endl; + AICLI_TERMINATE_CONTEXT(hr); + } + + auto units = exportResult.Units(); + context.Reporter.Info() << "Exported " << units.Size() << " instance(s):" << std::endl; + + for (const auto& resultUnit : units) + { + context.Reporter.Info() << " Type: " << Utility::ConvertToUTF8(resultUnit.Type()) << std::endl; + context.Reporter.Info() << " Identifier: " << Utility::ConvertToUTF8(resultUnit.Identifier()) << std::endl; + + auto settings = resultUnit.Settings(); + if (settings && settings.Size() > 0) + { + context.Reporter.Info() << " Settings:" << std::endl; + for (const auto& [key, value] : settings) + { + auto prop = value.try_as(); + if (prop) + { + std::string valueStr; + switch (prop.Type()) + { + case winrt::Windows::Foundation::PropertyType::String: + valueStr = Utility::ConvertToUTF8(prop.GetString()); + break; + case winrt::Windows::Foundation::PropertyType::Boolean: + valueStr = prop.GetBoolean() ? "true" : "false"; + break; + case winrt::Windows::Foundation::PropertyType::Int32: + valueStr = std::to_string(prop.GetInt32()); + break; + case winrt::Windows::Foundation::PropertyType::Int64: + valueStr = std::to_string(prop.GetInt64()); + break; + default: + valueStr = "(unsupported type)"; + break; + } + context.Reporter.Info() << " " << Utility::ConvertToUTF8(key) << ": " << valueStr << std::endl; + } + } + } + + context.Reporter.Info() << std::endl; + } + } +} + +#endif diff --git a/src/AppInstallerCLICore/Commands/DebugCommand.h b/src/AppInstallerCLICore/Commands/DebugCommand.h index 1c0fc2eecb..907f874a13 100644 --- a/src/AppInstallerCLICore/Commands/DebugCommand.h +++ b/src/AppInstallerCLICore/Commands/DebugCommand.h @@ -1,133 +1,133 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -#if _DEBUG - -namespace AppInstaller::CLI -{ - // Command that is only available with debug builds to aid development. - // Don't create localized strings for use here. - struct DebugCommand final : public Command - { - DebugCommand(std::string_view parent) : Command("debug", {}, parent, Visibility::Hidden) {} - - std::vector> GetCommands() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Outputs the proxy stub registrations for the manifest. - struct DumpProxyStubRegistrationsCommand final : public Command - { - DumpProxyStubRegistrationsCommand(std::string_view parent) : Command("dump-proxystub-reg", {}, parent) {} - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Outputs some IIDs. - struct DumpInterestingIIDsCommand final : public Command - { - DumpInterestingIIDsCommand(std::string_view parent) : Command("dump-iids", {}, parent) {} - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Outputs the errors as resources. - struct DumpErrorResourceCommand final : public Command - { - DumpErrorResourceCommand(std::string_view parent) : Command("dump-error-resource", {}, parent) {} - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Outputs a sixel image. - struct ShowSixelCommand final : public Command - { - ShowSixelCommand(std::string_view parent) : Command("sixel", {}, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Invokes progress display. - struct ProgressCommand final : public Command - { - ProgressCommand(std::string_view parent) : Command("progress", {}, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Directly invokes a DSCv3 resource function through WinGet's infrastructure. - struct DebugDscResourceCommand final : public Command - { - DebugDscResourceCommand(std::string_view parent) : Command("dsc-resource", {}, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Invokes signing information collection for a path. - struct GetSignerCommand final : public Command - { - GetSignerCommand(std::string_view parent) : Command("get-signer", {}, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Tests the log viewer extension by emitting logs that exercise all channels, levels, subchannels, - // continuation lines, long lines, and optionally a streaming follow mode. - struct LogViewerTestCommand final : public Command - { - LogViewerTestCommand(std::string_view parent) : Command("log-viewer", {}, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; -} - -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +#if _DEBUG + +namespace AppInstaller::CLI +{ + // Command that is only available with debug builds to aid development. + // Don't create localized strings for use here. + struct DebugCommand final : public Command + { + DebugCommand(std::string_view parent) : Command("debug", {}, parent, Visibility::Hidden) {} + + std::vector> GetCommands() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Outputs the proxy stub registrations for the manifest. + struct DumpProxyStubRegistrationsCommand final : public Command + { + DumpProxyStubRegistrationsCommand(std::string_view parent) : Command("dump-proxystub-reg", {}, parent) {} + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Outputs some IIDs. + struct DumpInterestingIIDsCommand final : public Command + { + DumpInterestingIIDsCommand(std::string_view parent) : Command("dump-iids", {}, parent) {} + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Outputs the errors as resources. + struct DumpErrorResourceCommand final : public Command + { + DumpErrorResourceCommand(std::string_view parent) : Command("dump-error-resource", {}, parent) {} + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Outputs a sixel image. + struct ShowSixelCommand final : public Command + { + ShowSixelCommand(std::string_view parent) : Command("sixel", {}, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Invokes progress display. + struct ProgressCommand final : public Command + { + ProgressCommand(std::string_view parent) : Command("progress", {}, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Directly invokes a DSCv3 resource function through WinGet's infrastructure. + struct DebugDscResourceCommand final : public Command + { + DebugDscResourceCommand(std::string_view parent) : Command("dsc-resource", {}, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Invokes signing information collection for a path. + struct GetSignerCommand final : public Command + { + GetSignerCommand(std::string_view parent) : Command("get-signer", {}, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Tests the log viewer extension by emitting logs that exercise all channels, levels, subchannels, + // continuation lines, long lines, and optionally a streaming follow mode. + struct LogViewerTestCommand final : public Command + { + LogViewerTestCommand(std::string_view parent) : Command("log-viewer", {}, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} + +#endif diff --git a/src/AppInstallerCLICore/Commands/DownloadCommand.cpp b/src/AppInstallerCLICore/Commands/DownloadCommand.cpp index e7b92a228f..21854d985c 100644 --- a/src/AppInstallerCLICore/Commands/DownloadCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DownloadCommand.cpp @@ -1,142 +1,142 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DownloadCommand.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/DownloadFlow.h" -#include "Workflows/InstallFlow.h" -#include "Workflows/PromptFlow.h" -#include "Resources.h" -#include -#include - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::CLI::Workflow; - using namespace AppInstaller::Utility::literals; - - std::vector DownloadCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::DownloadDirectory), - Argument::ForType(Args::Type::Manifest), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Version), - Argument::ForType(Args::Type::Channel), - Argument::ForType(Args::Type::Source), - Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Args::Type::InstallerArchitecture), - Argument::ForType(Args::Type::InstallerType), - Argument::ForType(Args::Type::Exact), - Argument::ForType(Args::Type::Locale), - Argument::ForType(Args::Type::HashOverride), - Argument::ForType(Args::Type::SkipDependencies), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptPackageAgreements), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument::ForType(Args::Type::SkipMicrosoftStorePackageLicense), - Argument::ForType(Args::Type::Platform), - Argument{ Args::Type::OSVersion, Resource::String::OSVersionDescription, ArgumentType::Standard, Argument::Visibility::Help }, - }; - } - - Resource::LocString DownloadCommand::ShortDescription() const - { - return { Resource::String::DownloadCommandShortDescription }; - } - - Resource::LocString DownloadCommand::LongDescription() const - { - return { Resource::String::DownloadCommandLongDescription }; - } - - void DownloadCommand::Complete(Context& context, Args::Type valueType) const - { - switch (valueType) - { - case Args::Type::Query: - case Args::Type::Manifest: - case Args::Type::Id: - case Args::Type::Name: - case Args::Type::Moniker: - case Args::Type::Version: - case Args::Type::Channel: - case Args::Type::Source: - context << - Workflow::CompleteWithSingleSemanticsForValue(valueType); - break; - case Args::Type::InstallerArchitecture: - case Args::Type::Locale: - // May well move to CompleteWithSingleSemanticsForValue, - // but for now output nothing. - context << - Workflow::CompleteWithEmptySet; - break; - case Args::Type::Log: - case Args::Type::DownloadDirectory: - // Intentionally output nothing to allow pass through to filesystem. - break; - } - } - - Utility::LocIndView DownloadCommand::HelpLink() const - { - return "https://aka.ms/winget-command-download"_liv; - } - - void DownloadCommand::ValidateArgumentsInternal(Args& execArgs) const - { - Argument::ValidateCommonArguments(execArgs); - - if (execArgs.Contains(Execution::Args::Type::Platform)) - { - Manifest::PlatformEnum selectedPlatform = Manifest::ConvertToPlatformEnumForMSStoreDownload(execArgs.GetArg(Execution::Args::Type::Platform)); - if (selectedPlatform == Manifest::PlatformEnum::Unknown) - { - auto validOptions = Utility::Join(", "_liv, std::vector{ - "Windows.Universal"_lis, "Windows.Desktop"_lis, "Windows.IoT"_lis, "Windows.Team"_lis, "Windows.Holographic"_lis - }); - throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::Platform).Name(), validOptions)); - } - } - } - - void DownloadCommand::ExecuteInternal(Context& context) const - { - context.SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); - - context << Workflow::InitializeInstallerDownloadAuthenticatorsMap; - - if (context.Args.Contains(Execution::Args::Type::Manifest)) - { - context << - Workflow::ReportExecutionStage(ExecutionStage::Discovery) << - Workflow::GetManifestFromArg; - } - else - { - context << - Workflow::ReportExecutionStage(ExecutionStage::Discovery) << - Workflow::OpenSource() << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Download) << - Workflow::GetManifestFromPackage(false); - } - - context << - Workflow::SetDownloadDirectory << - Workflow::SelectInstaller << - Workflow::EnsureApplicableInstaller << - Workflow::ReportIdentityAndInstallationDisclaimer << - Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << - Workflow::DownloadPackageDependencies << - Workflow::DownloadInstaller; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DownloadCommand.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/DownloadFlow.h" +#include "Workflows/InstallFlow.h" +#include "Workflows/PromptFlow.h" +#include "Resources.h" +#include +#include + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + using namespace AppInstaller::Utility::literals; + + std::vector DownloadCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::DownloadDirectory), + Argument::ForType(Args::Type::Manifest), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Version), + Argument::ForType(Args::Type::Channel), + Argument::ForType(Args::Type::Source), + Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Args::Type::InstallerArchitecture), + Argument::ForType(Args::Type::InstallerType), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::Locale), + Argument::ForType(Args::Type::HashOverride), + Argument::ForType(Args::Type::SkipDependencies), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptPackageAgreements), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::SkipMicrosoftStorePackageLicense), + Argument::ForType(Args::Type::Platform), + Argument{ Args::Type::OSVersion, Resource::String::OSVersionDescription, ArgumentType::Standard, Argument::Visibility::Help }, + }; + } + + Resource::LocString DownloadCommand::ShortDescription() const + { + return { Resource::String::DownloadCommandShortDescription }; + } + + Resource::LocString DownloadCommand::LongDescription() const + { + return { Resource::String::DownloadCommandLongDescription }; + } + + void DownloadCommand::Complete(Context& context, Args::Type valueType) const + { + switch (valueType) + { + case Args::Type::Query: + case Args::Type::Manifest: + case Args::Type::Id: + case Args::Type::Name: + case Args::Type::Moniker: + case Args::Type::Version: + case Args::Type::Channel: + case Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValue(valueType); + break; + case Args::Type::InstallerArchitecture: + case Args::Type::Locale: + // May well move to CompleteWithSingleSemanticsForValue, + // but for now output nothing. + context << + Workflow::CompleteWithEmptySet; + break; + case Args::Type::Log: + case Args::Type::DownloadDirectory: + // Intentionally output nothing to allow pass through to filesystem. + break; + } + } + + Utility::LocIndView DownloadCommand::HelpLink() const + { + return "https://aka.ms/winget-command-download"_liv; + } + + void DownloadCommand::ValidateArgumentsInternal(Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + + if (execArgs.Contains(Execution::Args::Type::Platform)) + { + Manifest::PlatformEnum selectedPlatform = Manifest::ConvertToPlatformEnumForMSStoreDownload(execArgs.GetArg(Execution::Args::Type::Platform)); + if (selectedPlatform == Manifest::PlatformEnum::Unknown) + { + auto validOptions = Utility::Join(", "_liv, std::vector{ + "Windows.Universal"_lis, "Windows.Desktop"_lis, "Windows.IoT"_lis, "Windows.Team"_lis, "Windows.Holographic"_lis + }); + throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::Platform).Name(), validOptions)); + } + } + } + + void DownloadCommand::ExecuteInternal(Context& context) const + { + context.SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); + + context << Workflow::InitializeInstallerDownloadAuthenticatorsMap; + + if (context.Args.Contains(Execution::Args::Type::Manifest)) + { + context << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << + Workflow::GetManifestFromArg; + } + else + { + context << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << + Workflow::OpenSource() << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Download) << + Workflow::GetManifestFromPackage(false); + } + + context << + Workflow::SetDownloadDirectory << + Workflow::SelectInstaller << + Workflow::EnsureApplicableInstaller << + Workflow::ReportIdentityAndInstallationDisclaimer << + Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << + Workflow::DownloadPackageDependencies << + Workflow::DownloadInstaller; + } +} diff --git a/src/AppInstallerCLICore/Commands/DownloadCommand.h b/src/AppInstallerCLICore/Commands/DownloadCommand.h index a6b8f1806e..a01cd7f32c 100644 --- a/src/AppInstallerCLICore/Commands/DownloadCommand.h +++ b/src/AppInstallerCLICore/Commands/DownloadCommand.h @@ -12,8 +12,8 @@ namespace AppInstaller::CLI std::vector GetArguments() const override; Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - + Resource::LocString LongDescription() const override; + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; Utility::LocIndView HelpLink() const override; diff --git a/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.cpp b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.cpp index 761ee8d523..cbc232d113 100644 --- a/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.cpp @@ -1,294 +1,294 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscAdminSettingsResource.h" -#include "DscComposableObject.h" -#include "Resources.h" -#include - -using namespace AppInstaller::Utility::literals; -using namespace AppInstaller::Repository; - -namespace AppInstaller::CLI -{ - namespace AdminSettingsDetails - { - // The base for admin settings. - struct IAdminSetting - { - virtual ~IAdminSetting() = default; - - // Gets the name of the setting. - virtual Utility::LocIndView SettingName() const = 0; - - // Tests the value. - // Returns true if in the desired state; false if not. - virtual bool Test() const = 0; - - // Sets the value. - // Returns true if the value could be set; false if not. - virtual bool Set() const = 0; - }; - - // A boolean based admin setting - struct AdminSetting_Bool : public IAdminSetting - { - AdminSetting_Bool(Settings::BoolAdminSetting setting, bool value) : m_setting(setting), m_value(value) {} - - Utility::LocIndView SettingName() const override - { - return Settings::AdminSettingToString(m_setting); - } - - bool Test() const override - { - return Settings::IsAdminSettingEnabled(m_setting) == m_value; - } - - bool Set() const override - { - return m_value ? Settings::EnableAdminSetting(m_setting) : Settings::DisableAdminSetting(m_setting); - } - - private: - Settings::BoolAdminSetting m_setting; - bool m_value; - }; - - // A string based admin setting - struct AdminSetting_String : public IAdminSetting - { - AdminSetting_String(Settings::StringAdminSetting setting, std::optional value) : m_setting(setting), m_value(std::move(value)) {} - - Utility::LocIndView SettingName() const override - { - return Settings::AdminSettingToString(m_setting); - } - - bool Test() const override - { - return Settings::GetAdminSetting(m_setting) == m_value; - } - - bool Set() const override - { - return m_value ? Settings::SetAdminSetting(m_setting, m_value.value()) : Settings::ResetAdminSetting(m_setting); - } - - private: - Settings::StringAdminSetting m_setting; - std::optional m_value; - }; - } - - namespace - { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(SettingsProperty, Json::Value, Settings, "settings", Resource::String::DscResourcePropertyDescriptionAdminSettingsSettings); - - using AdminSettingsResourceObject = DscComposableObject; - - struct AdminSettingsFunctionData - { - AdminSettingsFunctionData() = default; - - AdminSettingsFunctionData(const std::optional& json) : - Input(json) - { - } - - const AdminSettingsResourceObject Input; - AdminSettingsResourceObject Output; - std::vector> InputSettings; - - // Converts the input settings into the appropriate settings object. - void ParseSettings() - { - if (Input.Settings()) - { - const Json::Value& inputSettings = Input.Settings().value(); - for (const auto& property : inputSettings.getMemberNames()) - { - auto boolSetting = Settings::StringToBoolAdminSetting(property); - if (boolSetting != Settings::BoolAdminSetting::Unknown) - { - bool value = inputSettings[property].asBool(); - AICLI_LOG(Config, Info, << "Bool admin setting: " << property << " => " << (value ? "true" : "false")); - InputSettings.emplace_back(std::make_unique(boolSetting, value)); - continue; - } - - auto stringSetting = Settings::StringToStringAdminSetting(property); - if (stringSetting != Settings::StringAdminSetting::Unknown) - { - const auto& propertyNode = inputSettings[property]; - std::optional value; - - if (propertyNode.isNull()) - { - AICLI_LOG(Config, Info, << "String admin setting: " << property << " => null"); - } - else - { - value = propertyNode.asString(); - AICLI_LOG(Config, Info, << "String admin setting: " << property << " => `" << value.value() << "`"); - } - - InputSettings.emplace_back(std::make_unique(stringSetting, std::move(value))); - continue; - } - - AICLI_LOG(Config, Warning, << "Unknown admin setting: " << property); - } - } - } - - // Fills the Output object with the current state - void Get() - { - Json::Value adminSettings{ Json::objectValue }; - - for (const auto& setting : Settings::GetAllBoolAdminSettings()) - { - auto str = std::string{ Settings::AdminSettingToString(setting) }; - adminSettings[str] = Settings::IsAdminSettingEnabled(setting); - } - - for (const auto& setting : Settings::GetAllStringAdminSettings()) - { - auto name = std::string{ Settings::AdminSettingToString(setting) }; - auto value = Settings::GetAdminSetting(setting); - if (value) - { - adminSettings[name] = value.value(); - } - } - - Output.Settings(std::move(adminSettings)); - } - - // Determines if the current Output values match the Input values state. - bool Test() - { - for (const auto& setting : InputSettings) - { - if (!setting->Test()) - { - return false; - } - } - - return true; - } - - // Sets all of the input settings. - void Set() - { - for (const auto& setting : InputSettings) - { - if (!setting->Test()) - { - if (!setting->Set()) - { - auto message = Resource::String::DisabledByGroupPolicy(setting->SettingName()); - THROW_HR_MSG(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, "%hs", message.get().c_str()); - } - } - } - } - - Json::Value DiffJson(bool inDesiredState) - { - Json::Value result{ Json::ValueType::arrayValue }; - - if (!inDesiredState) - { - result.append(std::string{ SettingsProperty::Name() }); - } - - return result; - } - }; - } - - DscAdminSettingsResource::DscAdminSettingsResource(std::string_view parent) : - DscCommandBase(parent, "admin-settings", DscResourceKind::Resource, - DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, - DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::ReturnsStateAndDiff) - { - } - - Resource::LocString DscAdminSettingsResource::ShortDescription() const - { - return Resource::String::DscAdminSettingsResourceShortDescription; - } - - Resource::LocString DscAdminSettingsResource::LongDescription() const - { - return Resource::String::DscAdminSettingsResourceLongDescription; - } - - std::string DscAdminSettingsResource::ResourceType() const - { - return "AdminSettings"; - } - - void DscAdminSettingsResource::ResourceFunctionGet(Execution::Context& context) const - { - AdminSettingsFunctionData data; - data.Get(); - WriteJsonOutputLine(context, data.Output.ToJson()); - } - - void DscAdminSettingsResource::ResourceFunctionSet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - AdminSettingsFunctionData data{ json }; - - data.ParseSettings(); - - bool inDesiredState = data.Test(); - if (!inDesiredState) - { - Workflow::EnsureRunningAsAdmin(context); - - if (context.IsTerminated()) - { - return; - } - - data.Set(); - } - - data.Get(); - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, data.DiffJson(inDesiredState)); - } - } - - void DscAdminSettingsResource::ResourceFunctionTest(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - AdminSettingsFunctionData data{ json }; - - data.ParseSettings(); - - data.Get(); - data.Output.InDesiredState(data.Test()); - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, data.DiffJson(data.Output.InDesiredState().value())); - } - } - - void DscAdminSettingsResource::ResourceFunctionExport(Execution::Context& context) const - { - ResourceFunctionGet(context); - } - - void DscAdminSettingsResource::ResourceFunctionSchema(Execution::Context& context) const - { - WriteJsonOutputLine(context, AdminSettingsResourceObject::Schema(ResourceType())); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscAdminSettingsResource.h" +#include "DscComposableObject.h" +#include "Resources.h" +#include + +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI +{ + namespace AdminSettingsDetails + { + // The base for admin settings. + struct IAdminSetting + { + virtual ~IAdminSetting() = default; + + // Gets the name of the setting. + virtual Utility::LocIndView SettingName() const = 0; + + // Tests the value. + // Returns true if in the desired state; false if not. + virtual bool Test() const = 0; + + // Sets the value. + // Returns true if the value could be set; false if not. + virtual bool Set() const = 0; + }; + + // A boolean based admin setting + struct AdminSetting_Bool : public IAdminSetting + { + AdminSetting_Bool(Settings::BoolAdminSetting setting, bool value) : m_setting(setting), m_value(value) {} + + Utility::LocIndView SettingName() const override + { + return Settings::AdminSettingToString(m_setting); + } + + bool Test() const override + { + return Settings::IsAdminSettingEnabled(m_setting) == m_value; + } + + bool Set() const override + { + return m_value ? Settings::EnableAdminSetting(m_setting) : Settings::DisableAdminSetting(m_setting); + } + + private: + Settings::BoolAdminSetting m_setting; + bool m_value; + }; + + // A string based admin setting + struct AdminSetting_String : public IAdminSetting + { + AdminSetting_String(Settings::StringAdminSetting setting, std::optional value) : m_setting(setting), m_value(std::move(value)) {} + + Utility::LocIndView SettingName() const override + { + return Settings::AdminSettingToString(m_setting); + } + + bool Test() const override + { + return Settings::GetAdminSetting(m_setting) == m_value; + } + + bool Set() const override + { + return m_value ? Settings::SetAdminSetting(m_setting, m_value.value()) : Settings::ResetAdminSetting(m_setting); + } + + private: + Settings::StringAdminSetting m_setting; + std::optional m_value; + }; + } + + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(SettingsProperty, Json::Value, Settings, "settings", Resource::String::DscResourcePropertyDescriptionAdminSettingsSettings); + + using AdminSettingsResourceObject = DscComposableObject; + + struct AdminSettingsFunctionData + { + AdminSettingsFunctionData() = default; + + AdminSettingsFunctionData(const std::optional& json) : + Input(json) + { + } + + const AdminSettingsResourceObject Input; + AdminSettingsResourceObject Output; + std::vector> InputSettings; + + // Converts the input settings into the appropriate settings object. + void ParseSettings() + { + if (Input.Settings()) + { + const Json::Value& inputSettings = Input.Settings().value(); + for (const auto& property : inputSettings.getMemberNames()) + { + auto boolSetting = Settings::StringToBoolAdminSetting(property); + if (boolSetting != Settings::BoolAdminSetting::Unknown) + { + bool value = inputSettings[property].asBool(); + AICLI_LOG(Config, Info, << "Bool admin setting: " << property << " => " << (value ? "true" : "false")); + InputSettings.emplace_back(std::make_unique(boolSetting, value)); + continue; + } + + auto stringSetting = Settings::StringToStringAdminSetting(property); + if (stringSetting != Settings::StringAdminSetting::Unknown) + { + const auto& propertyNode = inputSettings[property]; + std::optional value; + + if (propertyNode.isNull()) + { + AICLI_LOG(Config, Info, << "String admin setting: " << property << " => null"); + } + else + { + value = propertyNode.asString(); + AICLI_LOG(Config, Info, << "String admin setting: " << property << " => `" << value.value() << "`"); + } + + InputSettings.emplace_back(std::make_unique(stringSetting, std::move(value))); + continue; + } + + AICLI_LOG(Config, Warning, << "Unknown admin setting: " << property); + } + } + } + + // Fills the Output object with the current state + void Get() + { + Json::Value adminSettings{ Json::objectValue }; + + for (const auto& setting : Settings::GetAllBoolAdminSettings()) + { + auto str = std::string{ Settings::AdminSettingToString(setting) }; + adminSettings[str] = Settings::IsAdminSettingEnabled(setting); + } + + for (const auto& setting : Settings::GetAllStringAdminSettings()) + { + auto name = std::string{ Settings::AdminSettingToString(setting) }; + auto value = Settings::GetAdminSetting(setting); + if (value) + { + adminSettings[name] = value.value(); + } + } + + Output.Settings(std::move(adminSettings)); + } + + // Determines if the current Output values match the Input values state. + bool Test() + { + for (const auto& setting : InputSettings) + { + if (!setting->Test()) + { + return false; + } + } + + return true; + } + + // Sets all of the input settings. + void Set() + { + for (const auto& setting : InputSettings) + { + if (!setting->Test()) + { + if (!setting->Set()) + { + auto message = Resource::String::DisabledByGroupPolicy(setting->SettingName()); + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, "%hs", message.get().c_str()); + } + } + } + } + + Json::Value DiffJson(bool inDesiredState) + { + Json::Value result{ Json::ValueType::arrayValue }; + + if (!inDesiredState) + { + result.append(std::string{ SettingsProperty::Name() }); + } + + return result; + } + }; + } + + DscAdminSettingsResource::DscAdminSettingsResource(std::string_view parent) : + DscCommandBase(parent, "admin-settings", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::ReturnsStateAndDiff) + { + } + + Resource::LocString DscAdminSettingsResource::ShortDescription() const + { + return Resource::String::DscAdminSettingsResourceShortDescription; + } + + Resource::LocString DscAdminSettingsResource::LongDescription() const + { + return Resource::String::DscAdminSettingsResourceLongDescription; + } + + std::string DscAdminSettingsResource::ResourceType() const + { + return "AdminSettings"; + } + + void DscAdminSettingsResource::ResourceFunctionGet(Execution::Context& context) const + { + AdminSettingsFunctionData data; + data.Get(); + WriteJsonOutputLine(context, data.Output.ToJson()); + } + + void DscAdminSettingsResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + AdminSettingsFunctionData data{ json }; + + data.ParseSettings(); + + bool inDesiredState = data.Test(); + if (!inDesiredState) + { + Workflow::EnsureRunningAsAdmin(context); + + if (context.IsTerminated()) + { + return; + } + + data.Set(); + } + + data.Get(); + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson(inDesiredState)); + } + } + + void DscAdminSettingsResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + AdminSettingsFunctionData data{ json }; + + data.ParseSettings(); + + data.Get(); + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson(data.Output.InDesiredState().value())); + } + } + + void DscAdminSettingsResource::ResourceFunctionExport(Execution::Context& context) const + { + ResourceFunctionGet(context); + } + + void DscAdminSettingsResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, AdminSettingsResourceObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.h b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.h index 0ad9330cb1..47299733e6 100644 --- a/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.h +++ b/src/AppInstallerCLICore/Commands/DscAdminSettingsResource.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DscCommandBase.h" - -namespace AppInstaller::CLI -{ - // A resource for managing admin settings. - struct DscAdminSettingsResource : public DscCommandBase - { - DscAdminSettingsResource(std::string_view parent); - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - std::string ResourceType() const override; - - void ResourceFunctionGet(Execution::Context& context) const override; - void ResourceFunctionSet(Execution::Context& context) const override; - void ResourceFunctionTest(Execution::Context& context) const override; - void ResourceFunctionExport(Execution::Context& context) const override; - void ResourceFunctionSchema(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A resource for managing admin settings. + struct DscAdminSettingsResource : public DscCommandBase + { + DscAdminSettingsResource(std::string_view parent); + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionTest(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscCommand.cpp b/src/AppInstallerCLICore/Commands/DscCommand.cpp index 3b88245e83..345b5e499c 100644 --- a/src/AppInstallerCLICore/Commands/DscCommand.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommand.cpp @@ -1,102 +1,102 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscCommand.h" -#include "DscPackageResource.h" -#include "DscUserSettingsFileResource.h" -#include "DscSourceResource.h" -#include "DscAdminSettingsResource.h" - -#ifndef AICLI_DISABLE_TEST_HOOKS -#include "DscTestFileResource.h" -#include "DscTestJsonResource.h" -#endif - -namespace AppInstaller::CLI -{ - namespace - { - Argument GetOutputFileArgument() - { - return { Execution::Args::Type::OutputFile, Resource::String::OutputDirectoryArgumentDescription, ArgumentType::Standard }; - } - } - - DscCommand::DscCommand(std::string_view parent) : - Command(StaticName(), parent) - { - } - - std::vector> DscCommand::GetCommands() const - { - // These should all derive from DscCommandBase - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), -#ifndef AICLI_DISABLE_TEST_HOOKS - std::make_unique(FullName()), - std::make_unique(FullName()), -#endif - }); - } - - std::vector DscCommand::GetArguments() const - { - std::vector result; - - result.emplace_back(Execution::Args::Type::DscResourceFunctionManifest, Resource::String::DscResourceFunctionDescriptionManifest, ArgumentType::Flag); - result.emplace_back(GetOutputFileArgument()); - - return result; - } - - Resource::LocString DscCommand::ShortDescription() const - { - return { Resource::String::DscCommandShortDescription }; - } - - Resource::LocString DscCommand::LongDescription() const - { - return { Resource::String::DscCommandLongDescription }; - } - - Utility::LocIndView DscCommand::HelpLink() const - { - return "https://aka.ms/winget-dsc-resources"_liv; - } - - void DscCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionManifest)) - { - std::filesystem::path outputDirectory{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }; - std::filesystem::create_directories(outputDirectory); - - std::string filePrefix = Utility::ToLower(DscCommandBase::ModuleName()); - - for (const auto& command : GetCommands()) - { - DscCommandBase* commandBase = static_cast(command.get()); - - std::filesystem::path outputPath = outputDirectory; - outputPath /= std::string{ filePrefix }.append(".").append(commandBase->Name()).append(".dsc.resource.json"); - commandBase->WriteManifest(context, outputPath); - } - } - else - { - OutputHelp(context.Reporter); - } - } - - void DscCommand::ValidateArgumentsInternal(Execution::Args& args) const - { - if (args.Contains(Execution::Args::Type::DscResourceFunctionManifest) && - !args.Contains(Execution::Args::Type::OutputFile)) - { - throw CommandException(Resource::String::RequiredArgError(GetOutputFileArgument().Name())); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscCommand.h" +#include "DscPackageResource.h" +#include "DscUserSettingsFileResource.h" +#include "DscSourceResource.h" +#include "DscAdminSettingsResource.h" + +#ifndef AICLI_DISABLE_TEST_HOOKS +#include "DscTestFileResource.h" +#include "DscTestJsonResource.h" +#endif + +namespace AppInstaller::CLI +{ + namespace + { + Argument GetOutputFileArgument() + { + return { Execution::Args::Type::OutputFile, Resource::String::OutputDirectoryArgumentDescription, ArgumentType::Standard }; + } + } + + DscCommand::DscCommand(std::string_view parent) : + Command(StaticName(), parent) + { + } + + std::vector> DscCommand::GetCommands() const + { + // These should all derive from DscCommandBase + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), +#ifndef AICLI_DISABLE_TEST_HOOKS + std::make_unique(FullName()), + std::make_unique(FullName()), +#endif + }); + } + + std::vector DscCommand::GetArguments() const + { + std::vector result; + + result.emplace_back(Execution::Args::Type::DscResourceFunctionManifest, Resource::String::DscResourceFunctionDescriptionManifest, ArgumentType::Flag); + result.emplace_back(GetOutputFileArgument()); + + return result; + } + + Resource::LocString DscCommand::ShortDescription() const + { + return { Resource::String::DscCommandShortDescription }; + } + + Resource::LocString DscCommand::LongDescription() const + { + return { Resource::String::DscCommandLongDescription }; + } + + Utility::LocIndView DscCommand::HelpLink() const + { + return "https://aka.ms/winget-dsc-resources"_liv; + } + + void DscCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionManifest)) + { + std::filesystem::path outputDirectory{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }; + std::filesystem::create_directories(outputDirectory); + + std::string filePrefix = Utility::ToLower(DscCommandBase::ModuleName()); + + for (const auto& command : GetCommands()) + { + DscCommandBase* commandBase = static_cast(command.get()); + + std::filesystem::path outputPath = outputDirectory; + outputPath /= std::string{ filePrefix }.append(".").append(commandBase->Name()).append(".dsc.resource.json"); + commandBase->WriteManifest(context, outputPath); + } + } + else + { + OutputHelp(context.Reporter); + } + } + + void DscCommand::ValidateArgumentsInternal(Execution::Args& args) const + { + if (args.Contains(Execution::Args::Type::DscResourceFunctionManifest) && + !args.Contains(Execution::Args::Type::OutputFile)) + { + throw CommandException(Resource::String::RequiredArgError(GetOutputFileArgument().Name())); + } + } +} diff --git a/src/AppInstallerCLICore/Commands/DscCommand.h b/src/AppInstallerCLICore/Commands/DscCommand.h index ace4459fae..d9443f53d1 100644 --- a/src/AppInstallerCLICore/Commands/DscCommand.h +++ b/src/AppInstallerCLICore/Commands/DscCommand.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" -#include - -namespace AppInstaller::CLI -{ - struct DscCommand final : public Command - { - DscCommand(std::string_view parent); - - static constexpr std::string_view StaticName() { return "dscv3"sv; }; - - std::vector> GetCommands() const override; - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& args) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include + +namespace AppInstaller::CLI +{ + struct DscCommand final : public Command + { + DscCommand(std::string_view parent); + + static constexpr std::string_view StaticName() { return "dscv3"sv; }; + + std::vector> GetCommands() const override; + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& args) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp index 254351a21e..a9fd884db0 100644 --- a/src/AppInstallerCLICore/Commands/DscCommandBase.cpp +++ b/src/AppInstallerCLICore/Commands/DscCommandBase.cpp @@ -1,314 +1,314 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscCommandBase.h" -#include "DscCommand.h" -#include -#include - -#define WINGET_DSC_FUNCTION_FOREACH(_macro_) \ - _macro_(Get); \ - _macro_(Set); \ - _macro_(WhatIf); \ - _macro_(Test); \ - _macro_(Delete); \ - _macro_(Export); \ - _macro_(Validate); \ - _macro_(Resolve); \ - _macro_(Adapter); \ - _macro_(Schema); \ - -namespace AppInstaller::CLI -{ - namespace - { - std::string GetFunctionManifestString(DscFunctions function) - { - THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function)); - - switch (function) - { - case DscFunctions::Get: return "get"; - case DscFunctions::Set: return "set"; - case DscFunctions::WhatIf: return "whatIf"; - case DscFunctions::Test: return "test"; - case DscFunctions::Delete: return "delete"; - case DscFunctions::Export: return "export"; - case DscFunctions::Validate: return "validate"; - case DscFunctions::Resolve: return "resolve"; - case DscFunctions::Adapter: return "adapter"; - case DscFunctions::Schema: return "schema"; - } - - THROW_HR(E_NOTIMPL); - } - - std::string GetFunctionArgumentString(DscFunctions function) - { - return std::string{ "--" } + GetFunctionManifestString(function); - } - - bool FunctionSpecifiesInput(DscFunctions function) - { - switch (function) - { - case DscFunctions::Get: - case DscFunctions::Set: - case DscFunctions::WhatIf: - case DscFunctions::Test: - case DscFunctions::Delete: - case DscFunctions::Export: - case DscFunctions::Validate: - case DscFunctions::Resolve: - return true; - } - - return false; - } - - bool FunctionIsSetLike(DscFunctions function) - { - switch (function) - { - case DscFunctions::Set: - case DscFunctions::WhatIf: - return true; - } - - return false; - } - - bool FunctionSpecifiesReturn(DscFunctions function) - { - switch (function) - { - case DscFunctions::Set: - case DscFunctions::WhatIf: - case DscFunctions::Test: - return true; - } - - return false; - } - - std::optional GetReturnType(DscFunctionModifiers modifiers) - { - if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsStateAndDiff)) - { - return "stateAndDiff"; - } - - if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsState)) - { - return "state"; - } - - return std::nullopt; - } - - Json::Value CreateJsonDefinitionFor(std::string_view name, DscFunctions function, DscFunctionModifiers modifiers) - { - THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function)); - THROW_HR_IF(E_NOTIMPL, function == DscFunctions::Adapter); - - Json::Value result{ Json::ValueType::objectValue }; - -#ifndef USE_PROD_CLSIDS - result["executable"] = "wingetdev"; -#else - result["executable"] = "winget"; -#endif - - Json::Value args{ Json::ValueType::arrayValue }; - args.append(std::string{ DscCommand::StaticName() }); - args.append(std::string{ name }); - args.append(GetFunctionArgumentString(function)); - result["args"] = std::move(args); - - if (FunctionSpecifiesInput(function)) - { - result["input"] = "stdin"; - } - - if (FunctionIsSetLike(function)) - { - if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ImplementsPretest)) - { - result["implementsPretest"] = true; - } - - if (WI_IsFlagSet(modifiers, DscFunctionModifiers::HandlesExist)) - { - result["handlesExist"] = true; - } - } - - if (FunctionSpecifiesReturn(function)) - { - std::optional returnType = GetReturnType(modifiers); - - if (returnType) - { - result["return"] = returnType.value(); - } - } - - if (function == DscFunctions::Schema) - { - Json::Value newResult{ Json::ValueType::objectValue }; - newResult["command"] = std::move(result); - result = std::move(newResult); - } - - return result; - } - } - - DscCommandBase::DscCommandBase(std::string_view parent, std::string_view resourceName, DscResourceKind kind, DscFunctions functions, DscFunctionModifiers modifiers) : - Command(resourceName, parent, CommandOutputFlags::IgnoreSettingsWarnings), m_kind(kind), m_functions(functions), m_modifiers(modifiers) - { - // Limits on current implementation - THROW_HR_IF(E_NOTIMPL, kind != DscResourceKind::Resource); - THROW_HR_IF(E_NOTIMPL, WI_IsFlagSet(functions, DscFunctions::Adapter)); - } - - std::vector DscCommandBase::GetArguments() const - { - std::vector result; - -#define WINGET_DSC_FUNCTION_ARGUMENT(_function_) \ - if (WI_IsFlagSet(m_functions, DscFunctions::_function_)) \ - { \ - result.emplace_back(Execution::Args::Type::DscResourceFunction ## _function_, Resource::String::DscResourceFunctionDescription ## _function_, ArgumentType::Flag); \ - } - - WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_ARGUMENT); - -#undef WINGET_DSC_FUNCTION_ARGUMENT - - result.emplace_back(Execution::Args::Type::DscResourceFunctionManifest, Resource::String::DscResourceFunctionDescriptionManifest, ArgumentType::Flag); - result.emplace_back(Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Standard); - - return result; - } - - Utility::LocIndView DscCommandBase::HelpLink() const - { - return "https://aka.ms/winget-dsc-resources"_liv; - } - - void DscCommandBase::ExecuteInternal(Execution::Context& context) const - { - context.Reporter.SetChannel(Execution::Reporter::Channel::Json); - Logging::StdErrLogger::Add(); - -#define WINGET_DSC_FUNCTION_ARGUMENT(_function_) \ - if (context.Args.Contains(Execution::Args::Type::DscResourceFunction ## _function_)) \ - { \ - return ResourceFunction ## _function_(context); \ - } - - WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_ARGUMENT); - WINGET_DSC_FUNCTION_ARGUMENT(Manifest); - -#undef WINGET_DSC_FUNCTION_ARGUMENT - } - -#define WINGET_DSC_FUNCTION_METHOD(_function_) \ - void DscCommandBase::ResourceFunction ## _function_(Execution::Context&) const \ - { \ - THROW_HR(E_NOTIMPL); \ - } \ - - WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_METHOD); - - void DscCommandBase::WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const - { - Json::Value json{ Json::ValueType::objectValue }; - - // TODO: Move to release schema when released (there should be an aka.ms link as well, but it wasn't active yet) - //json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/resource/manifest.json"; - json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json"; - json["type"] = std::string{ ModuleName() } + '/' + ResourceType(); - json["description"] = LongDescription().get(); - json["version"] = Runtime::GetClientVersion().get(); - - Json::Value tags{ Json::ValueType::arrayValue }; - tags.append("WinGet"); - json["tags"] = std::move(tags); - -#define WINGET_DSC_FUNCTION_MANIFEST(_function_) \ - if (WI_IsFlagSet(m_functions, DscFunctions::_function_)) \ - { \ - json[GetFunctionManifestString(DscFunctions::_function_)] = CreateJsonDefinitionFor(Name(), DscFunctions::_function_, m_modifiers); \ - } - - WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_MANIFEST); - -#undef WINGET_DSC_FUNCTION_MANIFEST - - Json::StreamWriterBuilder writerBuilder; - writerBuilder.settings_["indentation"] = " "; - std::string jsonString = Json::writeString(writerBuilder, json); - - if (!filePath.empty()) - { - std::ofstream stream{ filePath, std::ios::binary }; - stream.write(jsonString.c_str(), jsonString.length()); - } - else - { - context.Reporter.Json() << jsonString; - } - } - - void DscCommandBase::ResourceFunctionManifest(Execution::Context& context) const - { - std::filesystem::path path; - if (context.Args.Contains(Execution::Args::Type::OutputFile)) - { - path = std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }; - } - WriteManifest(context, path); - } - -#undef WINGET_DSC_FUNCTION_METHOD - - std::optional DscCommandBase::GetJsonFromInput(Execution::Context& context, bool terminateContextOnError) const - { - // Don't attempt to read from an interactive stream as this will just block - if (!context.Reporter.InputStreamIsInteractive()) - { - AICLI_LOG(CLI, Verbose, << "Reading Json from input stream..."); - - Json::Value result; - Json::CharReaderBuilder builder; - Json::String errors; - if (Json::parseFromStream(builder, context.Reporter.RawInputStream(), &result, &errors)) - { - AICLI_LOG(CLI, Info, << "Json from input stream:\n" << Json::writeString(Json::StreamWriterBuilder{}, result)); - return result; - } - - AICLI_LOG(CLI, Error, << "Failed to read input JSON: " << errors); - } - - if (terminateContextOnError) - { - AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, std::nullopt); - } - else - { - return std::nullopt; - } - } - - void DscCommandBase::WriteJsonOutputLine(Execution::Context& context, const Json::Value& value) const - { - Json::StreamWriterBuilder writerBuilder; - writerBuilder.settings_["indentation"] = ""; - writerBuilder.settings_["commentStyle"] = "None"; - writerBuilder.settings_["emitUTF8"] = true; - context.Reporter.Json() << Json::writeString(writerBuilder, value) << std::endl; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscCommandBase.h" +#include "DscCommand.h" +#include +#include + +#define WINGET_DSC_FUNCTION_FOREACH(_macro_) \ + _macro_(Get); \ + _macro_(Set); \ + _macro_(WhatIf); \ + _macro_(Test); \ + _macro_(Delete); \ + _macro_(Export); \ + _macro_(Validate); \ + _macro_(Resolve); \ + _macro_(Adapter); \ + _macro_(Schema); \ + +namespace AppInstaller::CLI +{ + namespace + { + std::string GetFunctionManifestString(DscFunctions function) + { + THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function)); + + switch (function) + { + case DscFunctions::Get: return "get"; + case DscFunctions::Set: return "set"; + case DscFunctions::WhatIf: return "whatIf"; + case DscFunctions::Test: return "test"; + case DscFunctions::Delete: return "delete"; + case DscFunctions::Export: return "export"; + case DscFunctions::Validate: return "validate"; + case DscFunctions::Resolve: return "resolve"; + case DscFunctions::Adapter: return "adapter"; + case DscFunctions::Schema: return "schema"; + } + + THROW_HR(E_NOTIMPL); + } + + std::string GetFunctionArgumentString(DscFunctions function) + { + return std::string{ "--" } + GetFunctionManifestString(function); + } + + bool FunctionSpecifiesInput(DscFunctions function) + { + switch (function) + { + case DscFunctions::Get: + case DscFunctions::Set: + case DscFunctions::WhatIf: + case DscFunctions::Test: + case DscFunctions::Delete: + case DscFunctions::Export: + case DscFunctions::Validate: + case DscFunctions::Resolve: + return true; + } + + return false; + } + + bool FunctionIsSetLike(DscFunctions function) + { + switch (function) + { + case DscFunctions::Set: + case DscFunctions::WhatIf: + return true; + } + + return false; + } + + bool FunctionSpecifiesReturn(DscFunctions function) + { + switch (function) + { + case DscFunctions::Set: + case DscFunctions::WhatIf: + case DscFunctions::Test: + return true; + } + + return false; + } + + std::optional GetReturnType(DscFunctionModifiers modifiers) + { + if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsStateAndDiff)) + { + return "stateAndDiff"; + } + + if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ReturnsState)) + { + return "state"; + } + + return std::nullopt; + } + + Json::Value CreateJsonDefinitionFor(std::string_view name, DscFunctions function, DscFunctionModifiers modifiers) + { + THROW_HR_IF(E_INVALIDARG, !WI_IsSingleFlagSet(function)); + THROW_HR_IF(E_NOTIMPL, function == DscFunctions::Adapter); + + Json::Value result{ Json::ValueType::objectValue }; + +#ifndef USE_PROD_CLSIDS + result["executable"] = "wingetdev"; +#else + result["executable"] = "winget"; +#endif + + Json::Value args{ Json::ValueType::arrayValue }; + args.append(std::string{ DscCommand::StaticName() }); + args.append(std::string{ name }); + args.append(GetFunctionArgumentString(function)); + result["args"] = std::move(args); + + if (FunctionSpecifiesInput(function)) + { + result["input"] = "stdin"; + } + + if (FunctionIsSetLike(function)) + { + if (WI_IsFlagSet(modifiers, DscFunctionModifiers::ImplementsPretest)) + { + result["implementsPretest"] = true; + } + + if (WI_IsFlagSet(modifiers, DscFunctionModifiers::HandlesExist)) + { + result["handlesExist"] = true; + } + } + + if (FunctionSpecifiesReturn(function)) + { + std::optional returnType = GetReturnType(modifiers); + + if (returnType) + { + result["return"] = returnType.value(); + } + } + + if (function == DscFunctions::Schema) + { + Json::Value newResult{ Json::ValueType::objectValue }; + newResult["command"] = std::move(result); + result = std::move(newResult); + } + + return result; + } + } + + DscCommandBase::DscCommandBase(std::string_view parent, std::string_view resourceName, DscResourceKind kind, DscFunctions functions, DscFunctionModifiers modifiers) : + Command(resourceName, parent, CommandOutputFlags::IgnoreSettingsWarnings), m_kind(kind), m_functions(functions), m_modifiers(modifiers) + { + // Limits on current implementation + THROW_HR_IF(E_NOTIMPL, kind != DscResourceKind::Resource); + THROW_HR_IF(E_NOTIMPL, WI_IsFlagSet(functions, DscFunctions::Adapter)); + } + + std::vector DscCommandBase::GetArguments() const + { + std::vector result; + +#define WINGET_DSC_FUNCTION_ARGUMENT(_function_) \ + if (WI_IsFlagSet(m_functions, DscFunctions::_function_)) \ + { \ + result.emplace_back(Execution::Args::Type::DscResourceFunction ## _function_, Resource::String::DscResourceFunctionDescription ## _function_, ArgumentType::Flag); \ + } + + WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_ARGUMENT); + +#undef WINGET_DSC_FUNCTION_ARGUMENT + + result.emplace_back(Execution::Args::Type::DscResourceFunctionManifest, Resource::String::DscResourceFunctionDescriptionManifest, ArgumentType::Flag); + result.emplace_back(Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, ArgumentType::Standard); + + return result; + } + + Utility::LocIndView DscCommandBase::HelpLink() const + { + return "https://aka.ms/winget-dsc-resources"_liv; + } + + void DscCommandBase::ExecuteInternal(Execution::Context& context) const + { + context.Reporter.SetChannel(Execution::Reporter::Channel::Json); + Logging::StdErrLogger::Add(); + +#define WINGET_DSC_FUNCTION_ARGUMENT(_function_) \ + if (context.Args.Contains(Execution::Args::Type::DscResourceFunction ## _function_)) \ + { \ + return ResourceFunction ## _function_(context); \ + } + + WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_ARGUMENT); + WINGET_DSC_FUNCTION_ARGUMENT(Manifest); + +#undef WINGET_DSC_FUNCTION_ARGUMENT + } + +#define WINGET_DSC_FUNCTION_METHOD(_function_) \ + void DscCommandBase::ResourceFunction ## _function_(Execution::Context&) const \ + { \ + THROW_HR(E_NOTIMPL); \ + } \ + + WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_METHOD); + + void DscCommandBase::WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const + { + Json::Value json{ Json::ValueType::objectValue }; + + // TODO: Move to release schema when released (there should be an aka.ms link as well, but it wasn't active yet) + //json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/v3/bundled/resource/manifest.json"; + json["$schema"] = "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json"; + json["type"] = std::string{ ModuleName() } + '/' + ResourceType(); + json["description"] = LongDescription().get(); + json["version"] = Runtime::GetClientVersion().get(); + + Json::Value tags{ Json::ValueType::arrayValue }; + tags.append("WinGet"); + json["tags"] = std::move(tags); + +#define WINGET_DSC_FUNCTION_MANIFEST(_function_) \ + if (WI_IsFlagSet(m_functions, DscFunctions::_function_)) \ + { \ + json[GetFunctionManifestString(DscFunctions::_function_)] = CreateJsonDefinitionFor(Name(), DscFunctions::_function_, m_modifiers); \ + } + + WINGET_DSC_FUNCTION_FOREACH(WINGET_DSC_FUNCTION_MANIFEST); + +#undef WINGET_DSC_FUNCTION_MANIFEST + + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = " "; + std::string jsonString = Json::writeString(writerBuilder, json); + + if (!filePath.empty()) + { + std::ofstream stream{ filePath, std::ios::binary }; + stream.write(jsonString.c_str(), jsonString.length()); + } + else + { + context.Reporter.Json() << jsonString; + } + } + + void DscCommandBase::ResourceFunctionManifest(Execution::Context& context) const + { + std::filesystem::path path; + if (context.Args.Contains(Execution::Args::Type::OutputFile)) + { + path = std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }; + } + WriteManifest(context, path); + } + +#undef WINGET_DSC_FUNCTION_METHOD + + std::optional DscCommandBase::GetJsonFromInput(Execution::Context& context, bool terminateContextOnError) const + { + // Don't attempt to read from an interactive stream as this will just block + if (!context.Reporter.InputStreamIsInteractive()) + { + AICLI_LOG(CLI, Verbose, << "Reading Json from input stream..."); + + Json::Value result; + Json::CharReaderBuilder builder; + Json::String errors; + if (Json::parseFromStream(builder, context.Reporter.RawInputStream(), &result, &errors)) + { + AICLI_LOG(CLI, Info, << "Json from input stream:\n" << Json::writeString(Json::StreamWriterBuilder{}, result)); + return result; + } + + AICLI_LOG(CLI, Error, << "Failed to read input JSON: " << errors); + } + + if (terminateContextOnError) + { + AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, std::nullopt); + } + else + { + return std::nullopt; + } + } + + void DscCommandBase::WriteJsonOutputLine(Execution::Context& context, const Json::Value& value) const + { + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = ""; + writerBuilder.settings_["commentStyle"] = "None"; + writerBuilder.settings_["emitUTF8"] = true; + context.Reporter.Json() << Json::writeString(writerBuilder, value) << std::endl; + } +} diff --git a/src/AppInstallerCLICore/Commands/DscCommandBase.h b/src/AppInstallerCLICore/Commands/DscCommandBase.h index d28cbad65c..be529fceb3 100644 --- a/src/AppInstallerCLICore/Commands/DscCommandBase.h +++ b/src/AppInstallerCLICore/Commands/DscCommandBase.h @@ -1,124 +1,124 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" -#include -#include - -#ifndef USE_PROD_CLSIDS -#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet.Dev" -#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet.Dev" -#else -#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet" -#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet" -#endif - -namespace AppInstaller::CLI -{ - // The kind of resource that this command is implementing. - enum class DscResourceKind - { - // Standard resource - Resource, - // Group resource - Group, - // Adapter resource - Adapter, - // Import resource - Import, - }; - - // The functions that a DSC resource can provide. - enum class DscFunctions - { - None = 0x000, - // Gets the current state; should always be implemented. - Get = 0x001, - // Sets the state; should always be implemented. - Set = 0x002, - // Produces the output of Set without modifying state. - WhatIf = 0x004, - // Determines if the current state matches the given state. - Test = 0x008, - // Deletes the given state. - Delete = 0x010, - // Gets all instances of the resource. - Export = 0x020, - // Required for a Group resource, ignored for all others. - Validate = 0x040, - // Required for an Import resource. - Resolve = 0x080, - // Required for an Adapter resource. - Adapter = 0x100, - // Gets the schema for the resource's properties. - Schema = 0x200, - }; - - DEFINE_ENUM_FLAG_OPERATORS(DscFunctions); - - // Behavior changes for DSC functions. - enum class DscFunctionModifiers - { - None = 0x00, - // The resource implements a check during Set (and WhatIf) to determine if already in the correct state. - // If not provided, DSC will ensure that the state is tested beforehand. - ImplementsPretest = 0x01, - // The resource will act on the `_exist` property during Set (and WhatIf). - // If not provided, the resource should implement Delete. - HandlesExist = 0x02, - // Functions that may return state information (set, what-if, test) return only the state. - ReturnsState = 0x04, - // Functions that may return state information (set, what-if, test) return the state and property difference. - ReturnsStateAndDiff = 0x08, - }; - - DEFINE_ENUM_FLAG_OPERATORS(DscFunctionModifiers); - - // Provides infrastructure for DSC commands to be implemented. - struct DscCommandBase : public Command - { - DscCommandBase(std::string_view parent, std::string_view resourceName, DscResourceKind kind, DscFunctions functions, DscFunctionModifiers modifiers); - - std::vector GetArguments() const override; - - Utility::LocIndView HelpLink() const override; - - static constexpr std::string_view ModuleName() - { - return WINGET_DSCV3_MODULE_NAME; - } - - // Writes the manifest for the command to the file path. - // If the path is empty, writes the manifest to the output stream. - void WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - - // Gets the resource specific type name. - virtual std::string ResourceType() const = 0; - - virtual void ResourceFunctionGet(Execution::Context& context) const; - virtual void ResourceFunctionSet(Execution::Context& context) const; - virtual void ResourceFunctionWhatIf(Execution::Context& context) const; - virtual void ResourceFunctionTest(Execution::Context& context) const; - virtual void ResourceFunctionDelete(Execution::Context& context) const; - virtual void ResourceFunctionExport(Execution::Context& context) const; - virtual void ResourceFunctionValidate(Execution::Context& context) const; - virtual void ResourceFunctionResolve(Execution::Context& context) const; - virtual void ResourceFunctionAdapter(Execution::Context& context) const; - virtual void ResourceFunctionSchema(Execution::Context& context) const; - virtual void ResourceFunctionManifest(Execution::Context& context) const; - - // Parses a JSON object from stdin. - std::optional GetJsonFromInput(Execution::Context& context, bool terminateContextOnError = true) const; - - // Writes the value to the context output. - void WriteJsonOutputLine(Execution::Context& context, const Json::Value& value) const; - - private: - DscResourceKind m_kind = DscResourceKind::Resource; - DscFunctions m_functions = DscFunctions::None; - DscFunctionModifiers m_modifiers = DscFunctionModifiers::None; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include +#include + +#ifndef USE_PROD_CLSIDS +#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet.Dev" +#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet.Dev" +#else +#define WINGET_DSCV3_MODULE_NAME "Microsoft.WinGet" +#define WINGET_DSCV3_MODULE_NAME_WIDE L"Microsoft.WinGet" +#endif + +namespace AppInstaller::CLI +{ + // The kind of resource that this command is implementing. + enum class DscResourceKind + { + // Standard resource + Resource, + // Group resource + Group, + // Adapter resource + Adapter, + // Import resource + Import, + }; + + // The functions that a DSC resource can provide. + enum class DscFunctions + { + None = 0x000, + // Gets the current state; should always be implemented. + Get = 0x001, + // Sets the state; should always be implemented. + Set = 0x002, + // Produces the output of Set without modifying state. + WhatIf = 0x004, + // Determines if the current state matches the given state. + Test = 0x008, + // Deletes the given state. + Delete = 0x010, + // Gets all instances of the resource. + Export = 0x020, + // Required for a Group resource, ignored for all others. + Validate = 0x040, + // Required for an Import resource. + Resolve = 0x080, + // Required for an Adapter resource. + Adapter = 0x100, + // Gets the schema for the resource's properties. + Schema = 0x200, + }; + + DEFINE_ENUM_FLAG_OPERATORS(DscFunctions); + + // Behavior changes for DSC functions. + enum class DscFunctionModifiers + { + None = 0x00, + // The resource implements a check during Set (and WhatIf) to determine if already in the correct state. + // If not provided, DSC will ensure that the state is tested beforehand. + ImplementsPretest = 0x01, + // The resource will act on the `_exist` property during Set (and WhatIf). + // If not provided, the resource should implement Delete. + HandlesExist = 0x02, + // Functions that may return state information (set, what-if, test) return only the state. + ReturnsState = 0x04, + // Functions that may return state information (set, what-if, test) return the state and property difference. + ReturnsStateAndDiff = 0x08, + }; + + DEFINE_ENUM_FLAG_OPERATORS(DscFunctionModifiers); + + // Provides infrastructure for DSC commands to be implemented. + struct DscCommandBase : public Command + { + DscCommandBase(std::string_view parent, std::string_view resourceName, DscResourceKind kind, DscFunctions functions, DscFunctionModifiers modifiers); + + std::vector GetArguments() const override; + + Utility::LocIndView HelpLink() const override; + + static constexpr std::string_view ModuleName() + { + return WINGET_DSCV3_MODULE_NAME; + } + + // Writes the manifest for the command to the file path. + // If the path is empty, writes the manifest to the output stream. + void WriteManifest(Execution::Context& context, const std::filesystem::path& filePath) const; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + + // Gets the resource specific type name. + virtual std::string ResourceType() const = 0; + + virtual void ResourceFunctionGet(Execution::Context& context) const; + virtual void ResourceFunctionSet(Execution::Context& context) const; + virtual void ResourceFunctionWhatIf(Execution::Context& context) const; + virtual void ResourceFunctionTest(Execution::Context& context) const; + virtual void ResourceFunctionDelete(Execution::Context& context) const; + virtual void ResourceFunctionExport(Execution::Context& context) const; + virtual void ResourceFunctionValidate(Execution::Context& context) const; + virtual void ResourceFunctionResolve(Execution::Context& context) const; + virtual void ResourceFunctionAdapter(Execution::Context& context) const; + virtual void ResourceFunctionSchema(Execution::Context& context) const; + virtual void ResourceFunctionManifest(Execution::Context& context) const; + + // Parses a JSON object from stdin. + std::optional GetJsonFromInput(Execution::Context& context, bool terminateContextOnError = true) const; + + // Writes the value to the context output. + void WriteJsonOutputLine(Execution::Context& context, const Json::Value& value) const; + + private: + DscResourceKind m_kind = DscResourceKind::Resource; + DscFunctions m_functions = DscFunctions::None; + DscFunctionModifiers m_modifiers = DscFunctionModifiers::None; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.cpp b/src/AppInstallerCLICore/Commands/DscComposableObject.cpp index 7f2f9525b8..541e872e4a 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.cpp +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.cpp @@ -1,120 +1,120 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscComposableObject.h" - -namespace AppInstaller::CLI -{ - namespace - { - std::string GetTypeString(Json::ValueType type) - { - switch (type) - { - case Json::nullValue: - return "null"; - case Json::intValue: - case Json::uintValue: - case Json::realValue: - return "number"; - case Json::stringValue: - return "string"; - case Json::booleanValue: - return "boolean"; - case Json::arrayValue: - return "array"; - case Json::objectValue: - return "object"; - default: - THROW_HR(E_UNEXPECTED); - } - } - } - - namespace details - { - const Json::Value* GetProperty(const Json::Value& object, std::string_view name) - { - return object.find(name.data(), name.data() + name.length()); - } - - void AddProperty(Json::Value& object, std::string_view name, std::optional&& value) - { - if (value) - { - object[std::string{ name }] = std::move(value).value(); - } - } - - Json::Value GetBaseSchema(const std::string& title) - { - Json::Value result{ Json::ValueType::objectValue }; - - result["$schema"] = "http://json-schema.org/draft-07/schema#"; - result["title"] = title; - result["type"] = "object"; - result["additionalProperties"] = false; - - return result; - } - - void AddPropertySchema( - Json::Value& object, - std::string_view name, - DscComposablePropertyFlag flags, - Json::ValueType type, - std::string_view description, - const std::vector& enumValues, - const std::optional& defaultValue) - { - Json::Value& propertiesObject = object["properties"]; - - if (propertiesObject.isNull()) - { - propertiesObject = Json::Value{ Json::ValueType::objectValue }; - } - - std::string nameString{ name }; - - Json::Value property{ Json::ValueType::objectValue }; - - if (type != Json::ValueType::objectValue) - { - property["type"] = GetTypeString(type); - } - - property["description"] = std::string{ description }; - - if (!enumValues.empty()) - { - Json::Value enumArray{ Json::ValueType::arrayValue }; - - for (const std::string& enumValue : enumValues) - { - enumArray.append(enumValue); - } - - property["enum"] = std::move(enumArray); - } - - if (defaultValue) - { - property["default"] = defaultValue.value(); - } - - propertiesObject[nameString] = std::move(property); - - if (WI_IsFlagSet(flags, DscComposablePropertyFlag::Required)) - { - Json::Value& requiredArray = object["required"]; - - if (requiredArray.isNull()) - { - requiredArray = Json::Value{ Json::ValueType::arrayValue }; - } - - requiredArray.append(nameString); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscComposableObject.h" + +namespace AppInstaller::CLI +{ + namespace + { + std::string GetTypeString(Json::ValueType type) + { + switch (type) + { + case Json::nullValue: + return "null"; + case Json::intValue: + case Json::uintValue: + case Json::realValue: + return "number"; + case Json::stringValue: + return "string"; + case Json::booleanValue: + return "boolean"; + case Json::arrayValue: + return "array"; + case Json::objectValue: + return "object"; + default: + THROW_HR(E_UNEXPECTED); + } + } + } + + namespace details + { + const Json::Value* GetProperty(const Json::Value& object, std::string_view name) + { + return object.find(name.data(), name.data() + name.length()); + } + + void AddProperty(Json::Value& object, std::string_view name, std::optional&& value) + { + if (value) + { + object[std::string{ name }] = std::move(value).value(); + } + } + + Json::Value GetBaseSchema(const std::string& title) + { + Json::Value result{ Json::ValueType::objectValue }; + + result["$schema"] = "http://json-schema.org/draft-07/schema#"; + result["title"] = title; + result["type"] = "object"; + result["additionalProperties"] = false; + + return result; + } + + void AddPropertySchema( + Json::Value& object, + std::string_view name, + DscComposablePropertyFlag flags, + Json::ValueType type, + std::string_view description, + const std::vector& enumValues, + const std::optional& defaultValue) + { + Json::Value& propertiesObject = object["properties"]; + + if (propertiesObject.isNull()) + { + propertiesObject = Json::Value{ Json::ValueType::objectValue }; + } + + std::string nameString{ name }; + + Json::Value property{ Json::ValueType::objectValue }; + + if (type != Json::ValueType::objectValue) + { + property["type"] = GetTypeString(type); + } + + property["description"] = std::string{ description }; + + if (!enumValues.empty()) + { + Json::Value enumArray{ Json::ValueType::arrayValue }; + + for (const std::string& enumValue : enumValues) + { + enumArray.append(enumValue); + } + + property["enum"] = std::move(enumArray); + } + + if (defaultValue) + { + property["default"] = defaultValue.value(); + } + + propertiesObject[nameString] = std::move(property); + + if (WI_IsFlagSet(flags, DscComposablePropertyFlag::Required)) + { + Json::Value& requiredArray = object["required"]; + + if (requiredArray.isNull()) + { + requiredArray = Json::Value{ Json::ValueType::arrayValue }; + } + + requiredArray.append(nameString); + } + } + } +} diff --git a/src/AppInstallerCLICore/Commands/DscComposableObject.h b/src/AppInstallerCLICore/Commands/DscComposableObject.h index 6563749768..88fb24674c 100644 --- a/src/AppInstallerCLICore/Commands/DscComposableObject.h +++ b/src/AppInstallerCLICore/Commands/DscComposableObject.h @@ -1,256 +1,256 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include "Resources.h" -#include -#include -#include -#include - -using namespace std::string_view_literals; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI -{ - // Flags that define how to treat properties. - enum DscComposablePropertyFlag - { - None = 0x0, - Required = 0x1, - CopyToOutput = 0x2, - }; - - DEFINE_ENUM_FLAG_OPERATORS(DscComposablePropertyFlag); - - namespace details - { - // Gets a property or null if not present. - const Json::Value* GetProperty(const Json::Value& object, std::string_view name); - - // Adds the given property and value to the object, if provided. - void AddProperty(Json::Value& object, std::string_view name, std::optional&& value); - - // Gets the default schema object. - Json::Value GetBaseSchema(const std::string& title); - - // Adds a property to the schema object. - void AddPropertySchema( - Json::Value& object, - std::string_view name, - DscComposablePropertyFlag flags, - Json::ValueType type, - std::string_view description, - const std::vector& enumValues, - const std::optional& defaultValue); - } - - template - struct GetJsonTypeValue - { - static_assert(false, "Implement for this type."); - }; - - template <> - struct GetJsonTypeValue - { - static bool Get(const Json::Value& value) - { - return value.asBool(); - } - - static Json::ValueType SchemaType() - { - return Json::ValueType::booleanValue; - } - }; - - template <> - struct GetJsonTypeValue - { - static std::string Get(const Json::Value& value) - { - return value.asString(); - } - - static Json::ValueType SchemaType() - { - return Json::ValueType::stringValue; - } - }; - - template <> - struct GetJsonTypeValue - { - static int32_t Get(const Json::Value& value) - { - return value.asInt(); - } - - static Json::ValueType SchemaType() - { - return Json::ValueType::intValue; - } - }; - - template <> - struct GetJsonTypeValue - { - static Json::Value Get(const Json::Value& value) - { - return value; - } - - static Json::ValueType SchemaType() - { - return Json::ValueType::objectValue; - } - }; - - // Template useful for composing objects for DSC resources. - // Properties should be of the shape: - // - // struct Property - // { - // using PropertyType = { bool, std::string }; - // static std::string_view Name(); - // static void FromJson(Property*, const Json::Value*); - // static std::optional ToJson(const Property*); - // - // const PropertyType& PROPERTY_NAME() const; - // void PROPERTY_NAME(const PropertyType&); - // } - template - struct DscComposableObject : public Property... - { - DscComposableObject() = default; - - DscComposableObject(const std::optional& input, bool ignoreFieldRequirements = false) - { - THROW_HR_IF(E_POINTER, !input && !ignoreFieldRequirements); - if (input) - { - FromJson(input.value(), ignoreFieldRequirements); - } - } - - // Read values for each property - void FromJson(const Json::Value& input, bool ignoreFieldRequirements = false) - { - (FoldHelper{}, ..., Property::FromJson(this, details::GetProperty(input, Property::Name()), ignoreFieldRequirements)); - } - - // Populate JSON object with properties. - Json::Value ToJson() - { - Json::Value result{ Json::ValueType::objectValue }; - (FoldHelper{}, ..., details::AddProperty(result, Property::Name(), Property::ToJson(this))); - return result; - } - - // Copies the appropriate values to a new object for output. - DscComposableObject CopyForOutput() const - { - DscComposableObject result; - (FoldHelper{}, ..., Property::CopyForOutput(this, &result)); - return result; - } - - // Get the JSON Schema for this object - static Json::Value Schema(const std::string& title) - { - Json::Value result = details::GetBaseSchema(title); - (FoldHelper{}, ..., details::AddPropertySchema(result, Property::Name(), Property::Flags, GetJsonTypeValue::SchemaType(), Property::Description(), Property::EnumValues(), Property::Default())); - return result; - } - }; - - template - struct DscComposableProperty - { - using PropertyType = PropertyTypeT; - static constexpr DscComposablePropertyFlag Flags = PropertyFlags; - - static void FromJson(Derived* self, const Json::Value* value, bool ignoreFieldRequirements) - { - if (value && !value->isNull()) - { - self->m_value = GetJsonTypeValue::Get(*value); - } - else - { - if (!ignoreFieldRequirements && WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::Required)) - { - THROW_HR_MSG(WINGET_CONFIG_ERROR_MISSING_FIELD, "Required property `%hs` not provided.", Derived::Name().data()); - } - else - { - self->m_value = std::nullopt; - } - } - } - - static std::optional ToJson(const Derived* self) - { - if constexpr (WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::Required)) - { - THROW_HR_IF(WINGET_CONFIG_ERROR_MISSING_FIELD, !self->m_value); - return self->m_value.value(); - } - else - { - return self->m_value ? std::optional{ self->m_value.value() } : std::nullopt; - } - } - - static void CopyForOutput(const Derived* self, Derived* other) - { - if constexpr (WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::CopyToOutput)) - { - other->m_value = self->m_value; - } - } - - protected: - std::optional m_value; - }; - -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ - struct _property_type_ : public DscComposableProperty<_property_type_, _value_type_, _flags_> \ - { \ - static std::string_view Name() { return _json_name_; } \ - static Resource::LocString Description() { return _description_; } \ - static std::vector EnumValues() { return std::vector _enum_vals_; } \ - static std::optional Default() { return _default_; } \ - std::optional& _property_name_() { return m_value; } \ - const std::optional& _property_name_() const { return m_value; } \ - void _property_name_(std::optional value) { m_value = std::move(value); } \ - -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ - }; - -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(_property_type_, _value_type_, _property_name_, _json_name_, _description_) \ - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, {}, {}) - -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) \ - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, {}, {}) - -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(_property_type_, _value_type_, _property_name_, _json_name_, _description_, _default_) \ - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, {}, _default_) - -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(_property_type_, _value_type_, _property_name_, _json_name_, _description_, _enum_vals_, _default_) \ - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, _enum_vals_, _default_) - -#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM_FLAGS(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) - - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(StandardExistProperty, bool, Exist, "_exist", DscComposablePropertyFlag::None, Resource::String::DscResourcePropertyDescriptionExist, {}, {}) - bool ShouldExist() const { return m_value.value_or(true); } - }; - - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(StandardInDesiredStateProperty, bool, InDesiredState, "_inDesiredState", Resource::String::DscResourcePropertyDescriptionInDesiredState); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include "Resources.h" +#include +#include +#include +#include + +using namespace std::string_view_literals; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI +{ + // Flags that define how to treat properties. + enum DscComposablePropertyFlag + { + None = 0x0, + Required = 0x1, + CopyToOutput = 0x2, + }; + + DEFINE_ENUM_FLAG_OPERATORS(DscComposablePropertyFlag); + + namespace details + { + // Gets a property or null if not present. + const Json::Value* GetProperty(const Json::Value& object, std::string_view name); + + // Adds the given property and value to the object, if provided. + void AddProperty(Json::Value& object, std::string_view name, std::optional&& value); + + // Gets the default schema object. + Json::Value GetBaseSchema(const std::string& title); + + // Adds a property to the schema object. + void AddPropertySchema( + Json::Value& object, + std::string_view name, + DscComposablePropertyFlag flags, + Json::ValueType type, + std::string_view description, + const std::vector& enumValues, + const std::optional& defaultValue); + } + + template + struct GetJsonTypeValue + { + static_assert(false, "Implement for this type."); + }; + + template <> + struct GetJsonTypeValue + { + static bool Get(const Json::Value& value) + { + return value.asBool(); + } + + static Json::ValueType SchemaType() + { + return Json::ValueType::booleanValue; + } + }; + + template <> + struct GetJsonTypeValue + { + static std::string Get(const Json::Value& value) + { + return value.asString(); + } + + static Json::ValueType SchemaType() + { + return Json::ValueType::stringValue; + } + }; + + template <> + struct GetJsonTypeValue + { + static int32_t Get(const Json::Value& value) + { + return value.asInt(); + } + + static Json::ValueType SchemaType() + { + return Json::ValueType::intValue; + } + }; + + template <> + struct GetJsonTypeValue + { + static Json::Value Get(const Json::Value& value) + { + return value; + } + + static Json::ValueType SchemaType() + { + return Json::ValueType::objectValue; + } + }; + + // Template useful for composing objects for DSC resources. + // Properties should be of the shape: + // + // struct Property + // { + // using PropertyType = { bool, std::string }; + // static std::string_view Name(); + // static void FromJson(Property*, const Json::Value*); + // static std::optional ToJson(const Property*); + // + // const PropertyType& PROPERTY_NAME() const; + // void PROPERTY_NAME(const PropertyType&); + // } + template + struct DscComposableObject : public Property... + { + DscComposableObject() = default; + + DscComposableObject(const std::optional& input, bool ignoreFieldRequirements = false) + { + THROW_HR_IF(E_POINTER, !input && !ignoreFieldRequirements); + if (input) + { + FromJson(input.value(), ignoreFieldRequirements); + } + } + + // Read values for each property + void FromJson(const Json::Value& input, bool ignoreFieldRequirements = false) + { + (FoldHelper{}, ..., Property::FromJson(this, details::GetProperty(input, Property::Name()), ignoreFieldRequirements)); + } + + // Populate JSON object with properties. + Json::Value ToJson() + { + Json::Value result{ Json::ValueType::objectValue }; + (FoldHelper{}, ..., details::AddProperty(result, Property::Name(), Property::ToJson(this))); + return result; + } + + // Copies the appropriate values to a new object for output. + DscComposableObject CopyForOutput() const + { + DscComposableObject result; + (FoldHelper{}, ..., Property::CopyForOutput(this, &result)); + return result; + } + + // Get the JSON Schema for this object + static Json::Value Schema(const std::string& title) + { + Json::Value result = details::GetBaseSchema(title); + (FoldHelper{}, ..., details::AddPropertySchema(result, Property::Name(), Property::Flags, GetJsonTypeValue::SchemaType(), Property::Description(), Property::EnumValues(), Property::Default())); + return result; + } + }; + + template + struct DscComposableProperty + { + using PropertyType = PropertyTypeT; + static constexpr DscComposablePropertyFlag Flags = PropertyFlags; + + static void FromJson(Derived* self, const Json::Value* value, bool ignoreFieldRequirements) + { + if (value && !value->isNull()) + { + self->m_value = GetJsonTypeValue::Get(*value); + } + else + { + if (!ignoreFieldRequirements && WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::Required)) + { + THROW_HR_MSG(WINGET_CONFIG_ERROR_MISSING_FIELD, "Required property `%hs` not provided.", Derived::Name().data()); + } + else + { + self->m_value = std::nullopt; + } + } + } + + static std::optional ToJson(const Derived* self) + { + if constexpr (WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::Required)) + { + THROW_HR_IF(WINGET_CONFIG_ERROR_MISSING_FIELD, !self->m_value); + return self->m_value.value(); + } + else + { + return self->m_value ? std::optional{ self->m_value.value() } : std::nullopt; + } + } + + static void CopyForOutput(const Derived* self, Derived* other) + { + if constexpr (WI_IsFlagSet(PropertyFlags, DscComposablePropertyFlag::CopyToOutput)) + { + other->m_value = self->m_value; + } + } + + protected: + std::optional m_value; + }; + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ + struct _property_type_ : public DscComposableProperty<_property_type_, _value_type_, _flags_> \ + { \ + static std::string_view Name() { return _json_name_; } \ + static Resource::LocString Description() { return _description_; } \ + static std::vector EnumValues() { return std::vector _enum_vals_; } \ + static std::optional Default() { return _default_; } \ + std::optional& _property_name_() { return m_value; } \ + const std::optional& _property_name_() const { return m_value; } \ + void _property_name_(std::optional value) { m_value = std::move(value); } \ + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ + }; + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(_property_type_, _value_type_, _property_name_, _json_name_, _description_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, {}, {}) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, {}, {}) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(_property_type_, _value_type_, _property_name_, _json_name_, _description_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, {}, _default_) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(_property_type_, _value_type_, _property_name_, _json_name_, _description_, _enum_vals_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, DscComposablePropertyFlag::None, _description_, _enum_vals_, _default_) + +#define WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM_FLAGS(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) \ + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL(_property_type_, _value_type_, _property_name_, _json_name_, _flags_, _description_, _enum_vals_, _default_) + + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_IMPL_START(StandardExistProperty, bool, Exist, "_exist", DscComposablePropertyFlag::None, Resource::String::DscResourcePropertyDescriptionExist, {}, {}) + bool ShouldExist() const { return m_value.value_or(true); } + }; + + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(StandardInDesiredStateProperty, bool, InDesiredState, "_inDesiredState", Resource::String::DscResourcePropertyDescriptionInDesiredState); +} diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp index 69f9633c1e..d43aac207d 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.cpp @@ -1,541 +1,541 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscPackageResource.h" -#include "DscComposableObject.h" -#include "Resources.h" -#include "Workflows/WorkflowBase.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/InstallFlow.h" -#include "Workflows/UninstallFlow.h" -#include "Workflows/UpdateFlow.h" -#include - -using namespace AppInstaller::Utility::literals; -using namespace AppInstaller::Repository; - -namespace AppInstaller::CLI -{ - namespace - { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(IdProperty, std::string, Identifier, "id", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageId); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SourceProperty, std::string, Source, "source", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageSource); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(VersionProperty, std::string, Version, "version", Resource::String::DscResourcePropertyDescriptionPackageVersion); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(MatchOptionProperty, std::string, MatchOption, "matchOption", Resource::String::DscResourcePropertyDescriptionPackageMatchOption, ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(UseLatestProperty, bool, UseLatest, "useLatest", Resource::String::DscResourcePropertyDescriptionPackageUseLatest, false); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(InstallModeProperty, std::string, InstallMode, "installMode", Resource::String::DscResourcePropertyDescriptionPackageInstallMode, ({ "default", "silent", "interactive" }), "silent"); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionAcceptAgreements); - - // TODO: To support Scope on this resource: - // 1. Change the installed source to pull in all package info for both scopes by default - // 2. Change the installed source open in workflows to always open for everything, regardless of scope - // 3. Improve correlation handling if needed for cross-scope package installations - // 4. Update the test EXE installer to handle being installed for both scopes - using PackageResourceObject = DscComposableObject; - - std::optional ToMatchType(const std::optional& value) - { - if (!value) - { - return std::nullopt; - } - - std::string lowerValue = Utility::ToLower(value.value()); - - if (lowerValue == "equals") - { - return MatchType::Exact; - } - else if (lowerValue == "equals""case""insensitive") - { - return MatchType::CaseInsensitive; - } - else if (lowerValue == "starts""with""case""insensitive") - { - return MatchType::StartsWith; - } - else if (lowerValue == "contains""case""insensitive") - { - return MatchType::Substring; - } - - THROW_HR(E_INVALIDARG); - } - - struct PackageFunctionData - { - PackageFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) : - Input(json, ignoreFieldRequirements), - ParentContext(context) - { - Reset(); - } - - const PackageResourceObject Input; - PackageResourceObject Output; - Execution::Context& ParentContext; - std::unique_ptr SubContext; - - // Reset the state that is modified by Get - void Reset() - { - Output = Input.CopyForOutput(); - - SubContext = ParentContext.CreateSubContext(); - SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity); - - if (Input.AcceptAgreements().value_or(false)) - { - SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); - SubContext->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - - std::string installMode = Utility::ToLower(Input.InstallMode().value_or("default")); - if (installMode == "silent") - { - SubContext->Args.AddArg(Execution::Args::Type::Silent); - } - else if (installMode == "interactive") - { - SubContext->Args.AddArg(Execution::Args::Type::Interactive); - } - else if (installMode != "default") - { - THROW_HR(E_INVALIDARG); - } - } - - void PrepareSubContextInputs() - { - if (Input.Source()) - { - SubContext->Args.AddArg(Execution::Args::Type::Source, Input.Source().value()); - } - } - - // Fills the Output object with the current state - bool Get() - { - PrepareSubContextInputs(); - - *SubContext << - Workflow::ReportExecutionStage(Workflow::ExecutionStage::Discovery) << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(*SubContext), false, CompositeSearchBehavior::AllPackages); - - if (SubContext->IsTerminated()) - { - ParentContext.Terminate(SubContext->GetTerminationHR()); - return false; - } - - // Do a manual search of the now opened source - Source& source = SubContext->Get(); - MatchType matchType = ToMatchType(Input.MatchOption()).value_or(MatchType::CaseInsensitive); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, Input.Identifier().value())); - - SearchResult result = source.Search(request); - SubContext->Add(result); - - if (result.Matches.empty()) - { - Output.Exist(false); - } - else if (result.Matches.size() > 1) - { - AICLI_LOG(Config, Warning, << "Found " << result.Matches.size() << " matches when searching for '" << Input.Identifier().value() << "'"); - Output.Exist(false); - } - else - { - auto& package = result.Matches.front().Package; - SubContext->Add(package); - - auto installedPackage = package->GetInstalled(); - - // Fill Output and SubContext - Output.Exist(static_cast(installedPackage)); - Output.Identifier(package->GetProperty(PackageProperty::Id)); - - if (installedPackage) - { - auto versionKeys = installedPackage->GetVersionKeys(); - AICLI_LOG(CLI, Verbose, << "Package::Get found " << versionKeys.size() << " installed versions"); - - std::shared_ptr installedVersion; - - // Find the specific version provided if possible - if (Input.Version()) - { - Utility::Version inputVersion{ Input.Version().value() }; - - for (const auto& key : versionKeys) - { - if (inputVersion == Utility::Version{ key.Version }) - { - installedVersion = installedPackage->GetVersion(key); - break; - } - } - } - - if (!installedVersion) - { - installedVersion = installedPackage->GetLatestVersion(); - } - - if (installedVersion) - { - Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); - } - - auto data = Repository::GetLatestApplicableVersion(package); - Output.UseLatest(!data.UpdateAvailable); - } - } - - AICLI_LOG(CLI, Verbose, << "Package::Get found:\n" << Json::writeString(Json::StreamWriterBuilder{}, Output.ToJson())); - return true; - } - - void Uninstall() - { - AICLI_LOG(CLI, Verbose, << "Package::Uninstall invoked"); - - if (Input.Version()) - { - SubContext->Args.AddArg(Execution::Args::Type::TargetVersion, Input.Version().value()); - } - else - { - SubContext->Args.AddArg(Execution::Args::Type::AllVersions); - } - - *SubContext << - Workflow::UninstallSinglePackage; - - if (SubContext->IsTerminated()) - { - ParentContext.Terminate(SubContext->GetTerminationHR()); - return; - } - - Output.Exist(false); - Output.Version(std::nullopt); - Output.UseLatest(std::nullopt); - } - - void Install(bool allowDowngrade = false) - { - AICLI_LOG(CLI, Verbose, << "Package::Install invoked"); - - if (Input.Version()) - { - SubContext->Args.AddArg(Execution::Args::Type::Version, Input.Version().value()); - } - - *SubContext << - Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install, allowDowngrade) << - Workflow::InstallSinglePackage; - - if (SubContext->IsTerminated()) - { - ParentContext.Terminate(SubContext->GetTerminationHR()); - return; - } - - Output.Exist(true); - - Output.Version(std::nullopt); - if (SubContext->Contains(Execution::Data::PackageVersion)) - { - const auto& packageVersion = SubContext->Get(); - if (packageVersion) - { - Output.Version(packageVersion->GetProperty(Repository::PackageVersionProperty::Version)); - } - } - - Output.UseLatest(std::nullopt); - } - - void Reinstall() - { - AICLI_LOG(CLI, Verbose, << "Package::Reinstall invoked"); - - SubContext->Args.AddArg(Execution::Args::Type::UninstallPrevious); - - Install(true); - } - - // Determines if the current Output values match the Input values state. - bool Test() - { - // Need to populate Output before calling - THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); - - if (Input.ShouldExist()) - { - if (Output.Exist().value()) - { - AICLI_LOG(CLI, Verbose, << "Package::Test needed to inspect these properties: Version(" << TestVersion() << "), Latest(" << TestLatest() << ")"); - return TestVersion() && TestLatest(); - } - else - { - AICLI_LOG(CLI, Verbose, << "Package::Test was false because the package was not installed"); - return false; - } - } - else - { - AICLI_LOG(CLI, Verbose, << "Package::Test desired the package to not exist, and it " << (Output.Exist().value() ? "did" : "did not")); - return !Output.Exist().value(); - } - } - - Json::Value DiffJson() - { - // Need to populate Output before calling - THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); - - Json::Value result{ Json::ValueType::arrayValue }; - - if (Input.ShouldExist() != Output.Exist().value()) - { - result.append(std::string{ StandardExistProperty::Name() }); - } - else - { - if (!TestVersion()) - { - result.append(std::string{ VersionProperty::Name() }); - } - - if (!TestLatest()) - { - result.append(std::string{ UseLatestProperty::Name() }); - } - } - - return result; - } - - bool TestVersion() - { - if (Input.Version()) - { - if (Output.Version()) - { - return Utility::Version{ Input.Version().value() } == Utility::Version{ Output.Version().value() }; - } - else - { - return false; - } - } - else - { - return true; - } - } - - bool TestLatest() - { - if (Input.UseLatest() && Input.UseLatest().value()) - { - if (Output.UseLatest()) - { - return Output.UseLatest().value(); - } - else - { - return false; - } - } - else - { - return true; - } - } - }; - } - - DscPackageResource::DscPackageResource(std::string_view parent) : - DscCommandBase(parent, "package", DscResourceKind::Resource, - DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, - DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) - { - } - - Resource::LocString DscPackageResource::ShortDescription() const - { - return Resource::String::DscPackageResourceShortDescription; - } - - Resource::LocString DscPackageResource::LongDescription() const - { - return Resource::String::DscPackageResourceLongDescription; - } - - std::string DscPackageResource::ResourceType() const - { - return "Package"; - } - - void DscPackageResource::ResourceFunctionGet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - PackageFunctionData data{ context, json }; - - if (!data.Get()) - { - return; - } - - WriteJsonOutputLine(context, data.Output.ToJson()); - } - } - - void DscPackageResource::ResourceFunctionSet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - PackageFunctionData data{ context, json }; - - if (!data.Get()) - { - return; - } - - // Capture the diff before updating the output - auto diff = data.DiffJson(); - - if (!data.Test()) - { - if (data.Input.ShouldExist()) - { - if (data.Output.Exist().value()) - { - if (!data.TestLatest()) - { - // Install will swap to update flow - AICLI_LOG(CLI, Info, << "Installing package to update to latest"); - data.Install(); - } - else // (!data.TestVersion()) - { - Utility::Version inputVersion{ data.Input.Version().value() }; - Utility::Version outputVersion{ data.Output.Version().value() }; - - if (outputVersion < inputVersion) - { - // Install will swap to update flow - AICLI_LOG(CLI, Info, << "Installing package to update to desired version"); - data.Install(); - } - else - { - AICLI_LOG(CLI, Info, << "Reinstalling package to downgrade to desired version"); - data.Reinstall(); - } - } - } - else - { - AICLI_LOG(CLI, Info, << "Installing package as it was not found"); - data.Install(); - } - } - else - { - AICLI_LOG(CLI, Info, << "Uninstalling package as desired"); - data.Uninstall(); - } - - if (data.SubContext->IsTerminated()) - { - return; - } - } - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, diff); - } - } - - void DscPackageResource::ResourceFunctionTest(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - PackageFunctionData data{ context, json }; - - if (!data.Get()) - { - return; - } - - data.Output.InDesiredState(data.Test()); - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, data.DiffJson()); - } - } - - void DscPackageResource::ResourceFunctionExport(Execution::Context& context) const - { - auto json = GetJsonFromInput(context, false); - PackageFunctionData data{ context, json, true }; - - data.PrepareSubContextInputs(); - - if (!data.Input.UseLatest().value_or(true)) - { - data.SubContext->Args.AddArg(Execution::Args::Type::IncludeVersions); - } - - data.SubContext->Args.AddArg(Execution::Args::Type::ConfigurationExportAll); - - *data.SubContext << - Workflow::SearchSourceForPackageExport; - - if (data.SubContext->IsTerminated()) - { - context.Terminate(data.SubContext->GetTerminationHR()); - return; - } - - const auto& packageCollection = data.SubContext->Get(); - - for (const auto& source : packageCollection.Sources) - { - for (const auto& package : source.Packages) - { - PackageResourceObject output; - - output.Identifier(package.Id); - output.Source(source.Details.Name); - - if (!package.VersionAndChannel.GetVersion().IsEmpty()) - { - output.Version(package.VersionAndChannel.GetVersion().ToString()); - } - - // TODO: Exporting scope requires one or more of the following: - // 1. Support for "OrUnknown" scope variants during Set (and workflows) - // 2. Tracking scope intent as we do for some other installer properties - // 3. Checking for the availability of the current scope in the package - - WriteJsonOutputLine(context, output.ToJson()); - } - } - } - - void DscPackageResource::ResourceFunctionSchema(Execution::Context& context) const - { - WriteJsonOutputLine(context, PackageResourceObject::Schema(ResourceType())); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscPackageResource.h" +#include "DscComposableObject.h" +#include "Resources.h" +#include "Workflows/WorkflowBase.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/InstallFlow.h" +#include "Workflows/UninstallFlow.h" +#include "Workflows/UpdateFlow.h" +#include + +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI +{ + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(IdProperty, std::string, Identifier, "id", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageId); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SourceProperty, std::string, Source, "source", DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionPackageSource); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(VersionProperty, std::string, Version, "version", Resource::String::DscResourcePropertyDescriptionPackageVersion); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(MatchOptionProperty, std::string, MatchOption, "matchOption", Resource::String::DscResourcePropertyDescriptionPackageMatchOption, ({ "equals", "equalsCaseInsensitive", "startsWithCaseInsensitive", "containsCaseInsensitive" }), "equalsCaseInsensitive"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_DEFAULT(UseLatestProperty, bool, UseLatest, "useLatest", Resource::String::DscResourcePropertyDescriptionPackageUseLatest, false); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(InstallModeProperty, std::string, InstallMode, "installMode", Resource::String::DscResourcePropertyDescriptionPackageInstallMode, ({ "default", "silent", "interactive" }), "silent"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionAcceptAgreements); + + // TODO: To support Scope on this resource: + // 1. Change the installed source to pull in all package info for both scopes by default + // 2. Change the installed source open in workflows to always open for everything, regardless of scope + // 3. Improve correlation handling if needed for cross-scope package installations + // 4. Update the test EXE installer to handle being installed for both scopes + using PackageResourceObject = DscComposableObject; + + std::optional ToMatchType(const std::optional& value) + { + if (!value) + { + return std::nullopt; + } + + std::string lowerValue = Utility::ToLower(value.value()); + + if (lowerValue == "equals") + { + return MatchType::Exact; + } + else if (lowerValue == "equals""case""insensitive") + { + return MatchType::CaseInsensitive; + } + else if (lowerValue == "starts""with""case""insensitive") + { + return MatchType::StartsWith; + } + else if (lowerValue == "contains""case""insensitive") + { + return MatchType::Substring; + } + + THROW_HR(E_INVALIDARG); + } + + struct PackageFunctionData + { + PackageFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) : + Input(json, ignoreFieldRequirements), + ParentContext(context) + { + Reset(); + } + + const PackageResourceObject Input; + PackageResourceObject Output; + Execution::Context& ParentContext; + std::unique_ptr SubContext; + + // Reset the state that is modified by Get + void Reset() + { + Output = Input.CopyForOutput(); + + SubContext = ParentContext.CreateSubContext(); + SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity); + + if (Input.AcceptAgreements().value_or(false)) + { + SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); + SubContext->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + + std::string installMode = Utility::ToLower(Input.InstallMode().value_or("default")); + if (installMode == "silent") + { + SubContext->Args.AddArg(Execution::Args::Type::Silent); + } + else if (installMode == "interactive") + { + SubContext->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (installMode != "default") + { + THROW_HR(E_INVALIDARG); + } + } + + void PrepareSubContextInputs() + { + if (Input.Source()) + { + SubContext->Args.AddArg(Execution::Args::Type::Source, Input.Source().value()); + } + } + + // Fills the Output object with the current state + bool Get() + { + PrepareSubContextInputs(); + + *SubContext << + Workflow::ReportExecutionStage(Workflow::ExecutionStage::Discovery) << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(*SubContext), false, CompositeSearchBehavior::AllPackages); + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + return false; + } + + // Do a manual search of the now opened source + Source& source = SubContext->Get(); + MatchType matchType = ToMatchType(Input.MatchOption()).value_or(MatchType::CaseInsensitive); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, Input.Identifier().value())); + + SearchResult result = source.Search(request); + SubContext->Add(result); + + if (result.Matches.empty()) + { + Output.Exist(false); + } + else if (result.Matches.size() > 1) + { + AICLI_LOG(Config, Warning, << "Found " << result.Matches.size() << " matches when searching for '" << Input.Identifier().value() << "'"); + Output.Exist(false); + } + else + { + auto& package = result.Matches.front().Package; + SubContext->Add(package); + + auto installedPackage = package->GetInstalled(); + + // Fill Output and SubContext + Output.Exist(static_cast(installedPackage)); + Output.Identifier(package->GetProperty(PackageProperty::Id)); + + if (installedPackage) + { + auto versionKeys = installedPackage->GetVersionKeys(); + AICLI_LOG(CLI, Verbose, << "Package::Get found " << versionKeys.size() << " installed versions"); + + std::shared_ptr installedVersion; + + // Find the specific version provided if possible + if (Input.Version()) + { + Utility::Version inputVersion{ Input.Version().value() }; + + for (const auto& key : versionKeys) + { + if (inputVersion == Utility::Version{ key.Version }) + { + installedVersion = installedPackage->GetVersion(key); + break; + } + } + } + + if (!installedVersion) + { + installedVersion = installedPackage->GetLatestVersion(); + } + + if (installedVersion) + { + Output.Version(installedVersion->GetProperty(PackageVersionProperty::Version)); + } + + auto data = Repository::GetLatestApplicableVersion(package); + Output.UseLatest(!data.UpdateAvailable); + } + } + + AICLI_LOG(CLI, Verbose, << "Package::Get found:\n" << Json::writeString(Json::StreamWriterBuilder{}, Output.ToJson())); + return true; + } + + void Uninstall() + { + AICLI_LOG(CLI, Verbose, << "Package::Uninstall invoked"); + + if (Input.Version()) + { + SubContext->Args.AddArg(Execution::Args::Type::TargetVersion, Input.Version().value()); + } + else + { + SubContext->Args.AddArg(Execution::Args::Type::AllVersions); + } + + *SubContext << + Workflow::UninstallSinglePackage; + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + return; + } + + Output.Exist(false); + Output.Version(std::nullopt); + Output.UseLatest(std::nullopt); + } + + void Install(bool allowDowngrade = false) + { + AICLI_LOG(CLI, Verbose, << "Package::Install invoked"); + + if (Input.Version()) + { + SubContext->Args.AddArg(Execution::Args::Type::Version, Input.Version().value()); + } + + *SubContext << + Workflow::SelectSinglePackageVersionForInstallOrUpgrade(Workflow::OperationType::Install, allowDowngrade) << + Workflow::InstallSinglePackage; + + if (SubContext->IsTerminated()) + { + ParentContext.Terminate(SubContext->GetTerminationHR()); + return; + } + + Output.Exist(true); + + Output.Version(std::nullopt); + if (SubContext->Contains(Execution::Data::PackageVersion)) + { + const auto& packageVersion = SubContext->Get(); + if (packageVersion) + { + Output.Version(packageVersion->GetProperty(Repository::PackageVersionProperty::Version)); + } + } + + Output.UseLatest(std::nullopt); + } + + void Reinstall() + { + AICLI_LOG(CLI, Verbose, << "Package::Reinstall invoked"); + + SubContext->Args.AddArg(Execution::Args::Type::UninstallPrevious); + + Install(true); + } + + // Determines if the current Output values match the Input values state. + bool Test() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + if (Input.ShouldExist()) + { + if (Output.Exist().value()) + { + AICLI_LOG(CLI, Verbose, << "Package::Test needed to inspect these properties: Version(" << TestVersion() << "), Latest(" << TestLatest() << ")"); + return TestVersion() && TestLatest(); + } + else + { + AICLI_LOG(CLI, Verbose, << "Package::Test was false because the package was not installed"); + return false; + } + } + else + { + AICLI_LOG(CLI, Verbose, << "Package::Test desired the package to not exist, and it " << (Output.Exist().value() ? "did" : "did not")); + return !Output.Exist().value(); + } + } + + Json::Value DiffJson() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + Json::Value result{ Json::ValueType::arrayValue }; + + if (Input.ShouldExist() != Output.Exist().value()) + { + result.append(std::string{ StandardExistProperty::Name() }); + } + else + { + if (!TestVersion()) + { + result.append(std::string{ VersionProperty::Name() }); + } + + if (!TestLatest()) + { + result.append(std::string{ UseLatestProperty::Name() }); + } + } + + return result; + } + + bool TestVersion() + { + if (Input.Version()) + { + if (Output.Version()) + { + return Utility::Version{ Input.Version().value() } == Utility::Version{ Output.Version().value() }; + } + else + { + return false; + } + } + else + { + return true; + } + } + + bool TestLatest() + { + if (Input.UseLatest() && Input.UseLatest().value()) + { + if (Output.UseLatest()) + { + return Output.UseLatest().value(); + } + else + { + return false; + } + } + else + { + return true; + } + } + }; + } + + DscPackageResource::DscPackageResource(std::string_view parent) : + DscCommandBase(parent, "package", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) + { + } + + Resource::LocString DscPackageResource::ShortDescription() const + { + return Resource::String::DscPackageResourceShortDescription; + } + + Resource::LocString DscPackageResource::LongDescription() const + { + return Resource::String::DscPackageResourceLongDescription; + } + + std::string DscPackageResource::ResourceType() const + { + return "Package"; + } + + void DscPackageResource::ResourceFunctionGet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + PackageFunctionData data{ context, json }; + + if (!data.Get()) + { + return; + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscPackageResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + PackageFunctionData data{ context, json }; + + if (!data.Get()) + { + return; + } + + // Capture the diff before updating the output + auto diff = data.DiffJson(); + + if (!data.Test()) + { + if (data.Input.ShouldExist()) + { + if (data.Output.Exist().value()) + { + if (!data.TestLatest()) + { + // Install will swap to update flow + AICLI_LOG(CLI, Info, << "Installing package to update to latest"); + data.Install(); + } + else // (!data.TestVersion()) + { + Utility::Version inputVersion{ data.Input.Version().value() }; + Utility::Version outputVersion{ data.Output.Version().value() }; + + if (outputVersion < inputVersion) + { + // Install will swap to update flow + AICLI_LOG(CLI, Info, << "Installing package to update to desired version"); + data.Install(); + } + else + { + AICLI_LOG(CLI, Info, << "Reinstalling package to downgrade to desired version"); + data.Reinstall(); + } + } + } + else + { + AICLI_LOG(CLI, Info, << "Installing package as it was not found"); + data.Install(); + } + } + else + { + AICLI_LOG(CLI, Info, << "Uninstalling package as desired"); + data.Uninstall(); + } + + if (data.SubContext->IsTerminated()) + { + return; + } + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, diff); + } + } + + void DscPackageResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + PackageFunctionData data{ context, json }; + + if (!data.Get()) + { + return; + } + + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson()); + } + } + + void DscPackageResource::ResourceFunctionExport(Execution::Context& context) const + { + auto json = GetJsonFromInput(context, false); + PackageFunctionData data{ context, json, true }; + + data.PrepareSubContextInputs(); + + if (!data.Input.UseLatest().value_or(true)) + { + data.SubContext->Args.AddArg(Execution::Args::Type::IncludeVersions); + } + + data.SubContext->Args.AddArg(Execution::Args::Type::ConfigurationExportAll); + + *data.SubContext << + Workflow::SearchSourceForPackageExport; + + if (data.SubContext->IsTerminated()) + { + context.Terminate(data.SubContext->GetTerminationHR()); + return; + } + + const auto& packageCollection = data.SubContext->Get(); + + for (const auto& source : packageCollection.Sources) + { + for (const auto& package : source.Packages) + { + PackageResourceObject output; + + output.Identifier(package.Id); + output.Source(source.Details.Name); + + if (!package.VersionAndChannel.GetVersion().IsEmpty()) + { + output.Version(package.VersionAndChannel.GetVersion().ToString()); + } + + // TODO: Exporting scope requires one or more of the following: + // 1. Support for "OrUnknown" scope variants during Set (and workflows) + // 2. Tracking scope intent as we do for some other installer properties + // 3. Checking for the availability of the current scope in the package + + WriteJsonOutputLine(context, output.ToJson()); + } + } + } + + void DscPackageResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, PackageResourceObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscPackageResource.h b/src/AppInstallerCLICore/Commands/DscPackageResource.h index 3bc0787096..e5df6c9d55 100644 --- a/src/AppInstallerCLICore/Commands/DscPackageResource.h +++ b/src/AppInstallerCLICore/Commands/DscPackageResource.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DscCommandBase.h" - -namespace AppInstaller::CLI -{ - // A resource for managing package state. - struct DscPackageResource : public DscCommandBase - { - DscPackageResource(std::string_view parent); - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - std::string ResourceType() const override; - - void ResourceFunctionGet(Execution::Context& context) const override; - void ResourceFunctionSet(Execution::Context& context) const override; - void ResourceFunctionTest(Execution::Context& context) const override; - void ResourceFunctionExport(Execution::Context& context) const override; - void ResourceFunctionSchema(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A resource for managing package state. + struct DscPackageResource : public DscCommandBase + { + DscPackageResource(std::string_view parent); + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionTest(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscSourceResource.cpp b/src/AppInstallerCLICore/Commands/DscSourceResource.cpp index 461c2c0efd..733a254879 100644 --- a/src/AppInstallerCLICore/Commands/DscSourceResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscSourceResource.cpp @@ -1,515 +1,515 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscSourceResource.h" -#include "DscComposableObject.h" -#include "Resources.h" -#include "Workflows/SourceFlow.h" -#include -#include - -using namespace AppInstaller::Utility::literals; -using namespace AppInstaller::Repository; - -namespace AppInstaller::CLI -{ - namespace - { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(NameProperty, std::string, SourceName, "name", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionSourceName); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ArgumentProperty, std::string, Argument, "argument", Resource::String::DscResourcePropertyDescriptionSourceArgument); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(TypeProperty, std::string, Type, "type", Resource::String::DscResourcePropertyDescriptionSourceType); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(TrustLevelProperty, std::string, TrustLevel, "trustLevel", Resource::String::DscResourcePropertyDescriptionSourceTrustLevel, ({ "undefined", "none", "trusted" }), "undefined"); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ExplicitProperty, bool, Explicit, "explicit", Resource::String::DscResourcePropertyDescriptionSourceExplicit); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionAcceptAgreements); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(PriorityProperty, int32_t, Priority, "priority", Resource::String::DscResourcePropertyDescriptionSourcePriority); - - using SourceResourceObject = DscComposableObject; - - std::string TrustLevelStringFromFlags(SourceTrustLevel trustLevel) - { - return WI_IsFlagSet(trustLevel, SourceTrustLevel::Trusted) ? "trusted" : "none"; - } - - // The values as the resource uses them. - enum class ResourceTrustLevel - { - Undefined, - Invalid, - None, - Trusted - }; - - ResourceTrustLevel EffectiveTrustLevel(const std::optional& input) - { - if (!input) - { - return ResourceTrustLevel::Undefined; - } - - std::string inputValue = Utility::ToLower(input.value()); - if (inputValue == "undefined") - { - return ResourceTrustLevel::Undefined; - } - else if (inputValue == "none") - { - return ResourceTrustLevel::None; - } - else if (inputValue == "trusted") - { - return ResourceTrustLevel::Trusted; - } - else - { - return ResourceTrustLevel::Invalid; - } - } - - struct SourceFunctionData - { - SourceFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) : - Input(json, ignoreFieldRequirements), - ParentContext(context) - { - Reset(); - } - - const SourceResourceObject Input; - SourceResourceObject Output; - Execution::Context& ParentContext; - std::unique_ptr SubContext; - - // Reset the state that is modified by Get - void Reset() - { - Output = Input.CopyForOutput(); - - SubContext = ParentContext.CreateSubContext(); - SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity); - - if (Input.AcceptAgreements().value_or(false)) - { - SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); - } - } - - // Fills the Output object with the current state - void Get() - { - auto currentSources = Repository::Source::GetCurrentSources(); - const std::string& name = Input.SourceName().value(); - - Output.Exist(false); - - for (auto const& source : currentSources) - { - if (Utility::ICUCaseInsensitiveEquals(source.Name, name)) - { - Output.Exist(true); - Output.Argument(source.Arg); - Output.Type(source.Type); - Output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel)); - Output.Explicit(source.Explicit); - - if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)) - { - Output.Priority(source.Priority); - } - - std::vector sources; - sources.emplace_back(source); - SubContext->Add(std::move(sources)); - break; - } - } - - AICLI_LOG(CLI, Verbose, << "Source::Get found:\n" << Json::writeString(Json::StreamWriterBuilder{}, Output.ToJson())); - } - - void Add() - { - AICLI_LOG(CLI, Verbose, << "Source::Add invoked"); - - if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) - { - SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); - } - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !Input.Argument().has_value()); - SubContext->Args.AddArg(Execution::Args::Type::SourceArg, Input.Argument().value()); - - if (Input.Type()) - { - SubContext->Args.AddArg(Execution::Args::Type::SourceType, Input.Type().value()); - } - - ResourceTrustLevel effectiveTrustLevel = EffectiveTrustLevel(Input.TrustLevel()); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, effectiveTrustLevel == ResourceTrustLevel::Invalid); - if (effectiveTrustLevel == ResourceTrustLevel::Trusted) - { - SubContext->Args.AddArg(Execution::Args::Type::SourceTrustLevel, TrustLevelStringFromFlags(SourceTrustLevel::Trusted)); - } - - if (Input.Explicit().value_or(false)) - { - SubContext->Args.AddArg(Execution::Args::Type::SourceExplicit); - } - - std::string priorityString; - if (Input.Priority()) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, !Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)); - priorityString = std::to_string(Input.Priority().value()); - SubContext->Args.AddArg(Execution::Args::Type::SourcePriority, priorityString); - } - - *SubContext << - Workflow::EnsureRunningAsAdmin << - Workflow::CreateSourceForSourceAdd << - Workflow::AddSource; - } - - void Remove() - { - AICLI_LOG(CLI, Verbose, << "Source::Remove invoked"); - - if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) - { - SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); - } - - *SubContext << - Workflow::EnsureRunningAsAdmin << - Workflow::RemoveSources; - } - - void Edit() - { - AICLI_LOG(CLI, Verbose, << "Source::Edit invoked"); - - if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) - { - SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); - } - - std::string explicitString; - if (Input.Explicit()) - { - explicitString = Utility::ConvertBoolToString(Input.Explicit().value()); - SubContext->Args.AddArg(Execution::Args::Type::SourceEditExplicit, explicitString); - } - - std::string priorityString; - if (Input.Priority()) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, !Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)); - priorityString = std::to_string(Input.Priority().value()); - SubContext->Args.AddArg(Execution::Args::Type::SourcePriority, priorityString); - } - - *SubContext << - Workflow::EnsureRunningAsAdmin << - Workflow::EditSources; - } - - void Replace() - { - AICLI_LOG(CLI, Verbose, << "Source::Replace invoked"); - - // Check to see if we can use an edit rather than a complete replacement - if (TestArgument() && TestType() && TestTrustLevel()) - { - // Implies that the failing portion of Test was in the editable Explicit or Priority properties - Edit(); - } - else - { - Remove(); - Add(); - } - } - - // Determines if the current Output values match the Input values state. - bool Test() - { - // Need to populate Output before calling - THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); - - if (Input.ShouldExist()) - { - if (Output.Exist().value()) - { - AICLI_LOG(CLI, Verbose, << "Source::Test needed to inspect these properties: Argument(" << TestArgument() << - "), Type(" << TestType() << "), TrustLevel(" << TestTrustLevel() << "), Explicit(" << TestExplicit() << "), Priority(" << TestPriority() << ")"); - return TestArgument() && TestType() && TestTrustLevel() && TestExplicit() && TestPriority(); - } - else - { - AICLI_LOG(CLI, Verbose, << "Source::Test was false because the source is not present"); - return false; - } - } - else - { - AICLI_LOG(CLI, Verbose, << "Source::Test desired the source to not exist, and it " << (Output.Exist().value() ? "did" : "did not")); - return !Output.Exist().value(); - } - } - - Json::Value DiffJson() - { - // Need to populate Output before calling - THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); - - Json::Value result{ Json::ValueType::arrayValue }; - - if (Input.ShouldExist() != Output.Exist().value()) - { - result.append(std::string{ StandardExistProperty::Name() }); - } - else - { - if (!TestArgument()) - { - result.append(std::string{ ArgumentProperty::Name() }); - } - - if (!TestType()) - { - result.append(std::string{ TypeProperty::Name() }); - } - - if (!TestTrustLevel()) - { - result.append(std::string{ TrustLevelProperty::Name() }); - } - - if (!TestExplicit()) - { - result.append(std::string{ ExplicitProperty::Name() }); - } - - if (!TestPriority()) - { - result.append(std::string{ PriorityProperty::Name() }); - } - } - - return result; - } - - bool TestArgument() - { - if (Input.Argument()) - { - if (Output.Argument()) - { - return Input.Argument().value() == Output.Argument().value(); - } - else - { - return false; - } - } - else - { - return true; - } - } - - bool TestType() - { - if (Input.Type()) - { - if (Output.Type()) - { - return Utility::CaseInsensitiveEquals(Input.Type().value(), Output.Type().value()); - } - else - { - return false; - } - } - else - { - return true; - } - } - - bool TestTrustLevel() - { - auto inputTrustLevel = EffectiveTrustLevel(Input.TrustLevel()); - - if (inputTrustLevel != ResourceTrustLevel::Undefined) - { - return inputTrustLevel == EffectiveTrustLevel(Output.TrustLevel()); - } - else - { - return true; - } - } - - bool TestExplicit() - { - if (Input.Explicit()) - { - if (Output.Explicit()) - { - return Input.Explicit().value() == Output.Explicit().value(); - } - else - { - return false; - } - } - else - { - return true; - } - } - - bool TestPriority() - { - if (Input.Priority()) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, !Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)); - if (Output.Priority()) - { - return Input.Priority().value() == Output.Priority().value(); - } - else - { - return false; - } - } - else - { - return true; - } - } - }; - } - - DscSourceResource::DscSourceResource(std::string_view parent) : - DscCommandBase(parent, "source", DscResourceKind::Resource, - DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, - DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) - { - } - - Resource::LocString DscSourceResource::ShortDescription() const - { - return Resource::String::DscSourceResourceShortDescription; - } - - Resource::LocString DscSourceResource::LongDescription() const - { - return Resource::String::DscSourceResourceLongDescription; - } - - std::string DscSourceResource::ResourceType() const - { - return "Source"; - } - - void DscSourceResource::ResourceFunctionGet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - SourceFunctionData data{ context, json }; - - data.Get(); - - WriteJsonOutputLine(context, data.Output.ToJson()); - } - } - - void DscSourceResource::ResourceFunctionSet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - SourceFunctionData data{ context, json }; - - data.Get(); - - // Capture the diff before updating the output - auto diff = data.DiffJson(); - - if (!data.Test()) - { - if (data.Input.ShouldExist()) - { - if (data.Output.Exist().value()) - { - AICLI_LOG(CLI, Info, << "Replacing source with new information"); - data.Replace(); - } - else - { - AICLI_LOG(CLI, Info, << "Adding source as it was not found"); - data.Add(); - } - } - else - { - AICLI_LOG(CLI, Info, << "Removing source as desired"); - data.Remove(); - } - - if (data.SubContext->IsTerminated()) - { - data.ParentContext.Terminate(data.SubContext->GetTerminationHR()); - return; - } - - data.Reset(); - data.Get(); - } - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, diff); - } - } - - void DscSourceResource::ResourceFunctionTest(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - SourceFunctionData data{ context, json }; - - data.Get(); - data.Output.InDesiredState(data.Test()); - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, data.DiffJson()); - } - } - - void DscSourceResource::ResourceFunctionExport(Execution::Context& context) const - { - auto currentSources = Repository::Source::GetCurrentSources(); - - for (auto const& source : currentSources) - { - SourceResourceObject output; - output.SourceName(source.Name); - output.Argument(source.Arg); - output.Type(source.Type); - output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel)); - output.Explicit(source.Explicit); - - if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)) - { - output.Priority(source.Priority); - } - - WriteJsonOutputLine(context, output.ToJson()); - } - } - - void DscSourceResource::ResourceFunctionSchema(Execution::Context& context) const - { - WriteJsonOutputLine(context, SourceResourceObject::Schema(ResourceType())); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscSourceResource.h" +#include "DscComposableObject.h" +#include "Resources.h" +#include "Workflows/SourceFlow.h" +#include +#include + +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI +{ + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(NameProperty, std::string, SourceName, "name", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionSourceName); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ArgumentProperty, std::string, Argument, "argument", Resource::String::DscResourcePropertyDescriptionSourceArgument); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(TypeProperty, std::string, Type, "type", Resource::String::DscResourcePropertyDescriptionSourceType); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(TrustLevelProperty, std::string, TrustLevel, "trustLevel", Resource::String::DscResourcePropertyDescriptionSourceTrustLevel, ({ "undefined", "none", "trusted" }), "undefined"); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ExplicitProperty, bool, Explicit, "explicit", Resource::String::DscResourcePropertyDescriptionSourceExplicit); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(AcceptAgreementsProperty, bool, AcceptAgreements, "acceptAgreements", Resource::String::DscResourcePropertyDescriptionAcceptAgreements); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(PriorityProperty, int32_t, Priority, "priority", Resource::String::DscResourcePropertyDescriptionSourcePriority); + + using SourceResourceObject = DscComposableObject; + + std::string TrustLevelStringFromFlags(SourceTrustLevel trustLevel) + { + return WI_IsFlagSet(trustLevel, SourceTrustLevel::Trusted) ? "trusted" : "none"; + } + + // The values as the resource uses them. + enum class ResourceTrustLevel + { + Undefined, + Invalid, + None, + Trusted + }; + + ResourceTrustLevel EffectiveTrustLevel(const std::optional& input) + { + if (!input) + { + return ResourceTrustLevel::Undefined; + } + + std::string inputValue = Utility::ToLower(input.value()); + if (inputValue == "undefined") + { + return ResourceTrustLevel::Undefined; + } + else if (inputValue == "none") + { + return ResourceTrustLevel::None; + } + else if (inputValue == "trusted") + { + return ResourceTrustLevel::Trusted; + } + else + { + return ResourceTrustLevel::Invalid; + } + } + + struct SourceFunctionData + { + SourceFunctionData(Execution::Context& context, const std::optional& json, bool ignoreFieldRequirements = false) : + Input(json, ignoreFieldRequirements), + ParentContext(context) + { + Reset(); + } + + const SourceResourceObject Input; + SourceResourceObject Output; + Execution::Context& ParentContext; + std::unique_ptr SubContext; + + // Reset the state that is modified by Get + void Reset() + { + Output = Input.CopyForOutput(); + + SubContext = ParentContext.CreateSubContext(); + SubContext->SetFlags(Execution::ContextFlag::DisableInteractivity); + + if (Input.AcceptAgreements().value_or(false)) + { + SubContext->Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); + } + } + + // Fills the Output object with the current state + void Get() + { + auto currentSources = Repository::Source::GetCurrentSources(); + const std::string& name = Input.SourceName().value(); + + Output.Exist(false); + + for (auto const& source : currentSources) + { + if (Utility::ICUCaseInsensitiveEquals(source.Name, name)) + { + Output.Exist(true); + Output.Argument(source.Arg); + Output.Type(source.Type); + Output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel)); + Output.Explicit(source.Explicit); + + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)) + { + Output.Priority(source.Priority); + } + + std::vector sources; + sources.emplace_back(source); + SubContext->Add(std::move(sources)); + break; + } + } + + AICLI_LOG(CLI, Verbose, << "Source::Get found:\n" << Json::writeString(Json::StreamWriterBuilder{}, Output.ToJson())); + } + + void Add() + { + AICLI_LOG(CLI, Verbose, << "Source::Add invoked"); + + if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) + { + SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); + } + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !Input.Argument().has_value()); + SubContext->Args.AddArg(Execution::Args::Type::SourceArg, Input.Argument().value()); + + if (Input.Type()) + { + SubContext->Args.AddArg(Execution::Args::Type::SourceType, Input.Type().value()); + } + + ResourceTrustLevel effectiveTrustLevel = EffectiveTrustLevel(Input.TrustLevel()); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, effectiveTrustLevel == ResourceTrustLevel::Invalid); + if (effectiveTrustLevel == ResourceTrustLevel::Trusted) + { + SubContext->Args.AddArg(Execution::Args::Type::SourceTrustLevel, TrustLevelStringFromFlags(SourceTrustLevel::Trusted)); + } + + if (Input.Explicit().value_or(false)) + { + SubContext->Args.AddArg(Execution::Args::Type::SourceExplicit); + } + + std::string priorityString; + if (Input.Priority()) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, !Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)); + priorityString = std::to_string(Input.Priority().value()); + SubContext->Args.AddArg(Execution::Args::Type::SourcePriority, priorityString); + } + + *SubContext << + Workflow::EnsureRunningAsAdmin << + Workflow::CreateSourceForSourceAdd << + Workflow::AddSource; + } + + void Remove() + { + AICLI_LOG(CLI, Verbose, << "Source::Remove invoked"); + + if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) + { + SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); + } + + *SubContext << + Workflow::EnsureRunningAsAdmin << + Workflow::RemoveSources; + } + + void Edit() + { + AICLI_LOG(CLI, Verbose, << "Source::Edit invoked"); + + if (!SubContext->Args.Contains(Execution::Args::Type::SourceName)) + { + SubContext->Args.AddArg(Execution::Args::Type::SourceName, Input.SourceName().value()); + } + + std::string explicitString; + if (Input.Explicit()) + { + explicitString = Utility::ConvertBoolToString(Input.Explicit().value()); + SubContext->Args.AddArg(Execution::Args::Type::SourceEditExplicit, explicitString); + } + + std::string priorityString; + if (Input.Priority()) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, !Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)); + priorityString = std::to_string(Input.Priority().value()); + SubContext->Args.AddArg(Execution::Args::Type::SourcePriority, priorityString); + } + + *SubContext << + Workflow::EnsureRunningAsAdmin << + Workflow::EditSources; + } + + void Replace() + { + AICLI_LOG(CLI, Verbose, << "Source::Replace invoked"); + + // Check to see if we can use an edit rather than a complete replacement + if (TestArgument() && TestType() && TestTrustLevel()) + { + // Implies that the failing portion of Test was in the editable Explicit or Priority properties + Edit(); + } + else + { + Remove(); + Add(); + } + } + + // Determines if the current Output values match the Input values state. + bool Test() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + if (Input.ShouldExist()) + { + if (Output.Exist().value()) + { + AICLI_LOG(CLI, Verbose, << "Source::Test needed to inspect these properties: Argument(" << TestArgument() << + "), Type(" << TestType() << "), TrustLevel(" << TestTrustLevel() << "), Explicit(" << TestExplicit() << "), Priority(" << TestPriority() << ")"); + return TestArgument() && TestType() && TestTrustLevel() && TestExplicit() && TestPriority(); + } + else + { + AICLI_LOG(CLI, Verbose, << "Source::Test was false because the source is not present"); + return false; + } + } + else + { + AICLI_LOG(CLI, Verbose, << "Source::Test desired the source to not exist, and it " << (Output.Exist().value() ? "did" : "did not")); + return !Output.Exist().value(); + } + } + + Json::Value DiffJson() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + Json::Value result{ Json::ValueType::arrayValue }; + + if (Input.ShouldExist() != Output.Exist().value()) + { + result.append(std::string{ StandardExistProperty::Name() }); + } + else + { + if (!TestArgument()) + { + result.append(std::string{ ArgumentProperty::Name() }); + } + + if (!TestType()) + { + result.append(std::string{ TypeProperty::Name() }); + } + + if (!TestTrustLevel()) + { + result.append(std::string{ TrustLevelProperty::Name() }); + } + + if (!TestExplicit()) + { + result.append(std::string{ ExplicitProperty::Name() }); + } + + if (!TestPriority()) + { + result.append(std::string{ PriorityProperty::Name() }); + } + } + + return result; + } + + bool TestArgument() + { + if (Input.Argument()) + { + if (Output.Argument()) + { + return Input.Argument().value() == Output.Argument().value(); + } + else + { + return false; + } + } + else + { + return true; + } + } + + bool TestType() + { + if (Input.Type()) + { + if (Output.Type()) + { + return Utility::CaseInsensitiveEquals(Input.Type().value(), Output.Type().value()); + } + else + { + return false; + } + } + else + { + return true; + } + } + + bool TestTrustLevel() + { + auto inputTrustLevel = EffectiveTrustLevel(Input.TrustLevel()); + + if (inputTrustLevel != ResourceTrustLevel::Undefined) + { + return inputTrustLevel == EffectiveTrustLevel(Output.TrustLevel()); + } + else + { + return true; + } + } + + bool TestExplicit() + { + if (Input.Explicit()) + { + if (Output.Explicit()) + { + return Input.Explicit().value() == Output.Explicit().value(); + } + else + { + return false; + } + } + else + { + return true; + } + } + + bool TestPriority() + { + if (Input.Priority()) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, !Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)); + if (Output.Priority()) + { + return Input.Priority().value() == Output.Priority().value(); + } + else + { + return false; + } + } + else + { + return true; + } + } + }; + } + + DscSourceResource::DscSourceResource(std::string_view parent) : + DscCommandBase(parent, "source", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) + { + } + + Resource::LocString DscSourceResource::ShortDescription() const + { + return Resource::String::DscSourceResourceShortDescription; + } + + Resource::LocString DscSourceResource::LongDescription() const + { + return Resource::String::DscSourceResourceLongDescription; + } + + std::string DscSourceResource::ResourceType() const + { + return "Source"; + } + + void DscSourceResource::ResourceFunctionGet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + SourceFunctionData data{ context, json }; + + data.Get(); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscSourceResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + SourceFunctionData data{ context, json }; + + data.Get(); + + // Capture the diff before updating the output + auto diff = data.DiffJson(); + + if (!data.Test()) + { + if (data.Input.ShouldExist()) + { + if (data.Output.Exist().value()) + { + AICLI_LOG(CLI, Info, << "Replacing source with new information"); + data.Replace(); + } + else + { + AICLI_LOG(CLI, Info, << "Adding source as it was not found"); + data.Add(); + } + } + else + { + AICLI_LOG(CLI, Info, << "Removing source as desired"); + data.Remove(); + } + + if (data.SubContext->IsTerminated()) + { + data.ParentContext.Terminate(data.SubContext->GetTerminationHR()); + return; + } + + data.Reset(); + data.Get(); + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, diff); + } + } + + void DscSourceResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + SourceFunctionData data{ context, json }; + + data.Get(); + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson()); + } + } + + void DscSourceResource::ResourceFunctionExport(Execution::Context& context) const + { + auto currentSources = Repository::Source::GetCurrentSources(); + + for (auto const& source : currentSources) + { + SourceResourceObject output; + output.SourceName(source.Name); + output.Argument(source.Arg); + output.Type(source.Type); + output.TrustLevel(TrustLevelStringFromFlags(source.TrustLevel)); + output.Explicit(source.Explicit); + + if (Settings::ExperimentalFeature::IsEnabled(Settings::ExperimentalFeature::Feature::SourcePriority)) + { + output.Priority(source.Priority); + } + + WriteJsonOutputLine(context, output.ToJson()); + } + } + + void DscSourceResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, SourceResourceObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscSourceResource.h b/src/AppInstallerCLICore/Commands/DscSourceResource.h index 236ef470d9..19ca73efcf 100644 --- a/src/AppInstallerCLICore/Commands/DscSourceResource.h +++ b/src/AppInstallerCLICore/Commands/DscSourceResource.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DscCommandBase.h" - -namespace AppInstaller::CLI -{ - // A resource for managing source configuration. - struct DscSourceResource : public DscCommandBase - { - DscSourceResource(std::string_view parent); - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - std::string ResourceType() const override; - - void ResourceFunctionGet(Execution::Context& context) const override; - void ResourceFunctionSet(Execution::Context& context) const override; - void ResourceFunctionTest(Execution::Context& context) const override; - void ResourceFunctionExport(Execution::Context& context) const override; - void ResourceFunctionSchema(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A resource for managing source configuration. + struct DscSourceResource : public DscCommandBase + { + DscSourceResource(std::string_view parent); + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionTest(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp index 63a59ea04f..a8873bb936 100644 --- a/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestFileResource.cpp @@ -1,236 +1,236 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscTestFileResource.h" -#include "DscComposableObject.h" - -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI -{ - namespace - { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PathProperty, std::string, Path, "path", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The absolute path to a file."_lis); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ContentProperty, std::string, Content, "content", "The content of the file."_lis); - - using TestFileObject = DscComposableObject; - - struct TestFileFunctionData - { - TestFileFunctionData(const std::optional& json) : Input(json), Output(Input.CopyForOutput()) - { - Path = Utility::ConvertToUTF16(Input.Path().value()); - THROW_HR_IF(E_INVALIDARG, !Path.is_absolute()); - } - - TestFileObject Input; - TestFileObject Output; - std::filesystem::path Path; - - // Fills the Output object with the current state - void Get() - { - if (std::filesystem::exists(Path) && std::filesystem::is_regular_file(Path)) - { - Output.Exist(true); - - std::ifstream stream{ Path, std::ios::binary }; - Output.Content(Utility::ReadEntireStream(stream)); - } - else - { - Output.Exist(false); - } - } - - // Determines if the current Output values match the Input values state. - bool Test() - { - // Need to populate Output before calling - THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); - - if (Input.ShouldExist()) - { - if (Output.Exist().value()) - { - return ContentMatches(); - } - else - { - return false; - } - } - else - { - return !Output.Exist().value(); - } - } - - Json::Value DiffJson() - { - // Need to populate Output before calling - THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); - - Json::Value result{ Json::ValueType::arrayValue }; - - if (Input.ShouldExist() != Output.Exist().value()) - { - result.append(std::string{ StandardExistProperty::Name() }); - } - else - { - if (!ContentMatches()) - { - result.append(std::string{ ContentProperty::Name() }); - } - } - - return result; - } - - private: - bool ContentMatches() - { - bool hasInput = Input.Content().has_value() && !Input.Content().value().empty(); - bool hasOutput = Output.Content().has_value() && !Output.Content().value().empty(); - - return - (hasInput && hasOutput && Input.Content().value() == Output.Content().value()) || - (!hasInput && !hasOutput); - } - }; - } - - DscTestFileResource::DscTestFileResource(std::string_view parent) : - DscCommandBase(parent, "test-file", DscResourceKind::Resource, - DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, - DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) - { - } - - Resource::LocString DscTestFileResource::ShortDescription() const - { - return "[TEST] File content resource"_lis; - } - - Resource::LocString DscTestFileResource::LongDescription() const - { - return "[TEST] This resource is only available for tests. It provides file content configuration."_lis; - } - - std::string DscTestFileResource::ResourceType() const - { - return "TestFile"; - } - - void DscTestFileResource::ResourceFunctionGet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - TestFileFunctionData data{ json }; - - data.Get(); - - WriteJsonOutputLine(context, data.Output.ToJson()); - } - } - - void DscTestFileResource::ResourceFunctionSet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - TestFileFunctionData data{ json }; - - data.Get(); - - if (!data.Test()) - { - bool exists = std::filesystem::exists(data.Path); - if (exists) - { - // Don't delete a directory or other special files in this test resource - THROW_WIN32_IF(ERROR_DIRECTORY_NOT_SUPPORTED, !std::filesystem::is_regular_file(data.Path)); - } - - if (data.Input.ShouldExist()) - { - std::filesystem::create_directories(data.Path.parent_path()); - - std::ofstream stream{ data.Path, std::ios::binary | std::ios::trunc }; - if (data.Input.Content()) - { - stream.write(data.Input.Content().value().c_str(), data.Input.Content().value().length()); - } - } - else if (exists) - { - std::filesystem::remove(data.Path); - } - } - - // Capture the diff before updating the output - auto diff = data.DiffJson(); - - data.Output.Exist(data.Input.ShouldExist()); - if (data.Output.Exist().value()) - { - data.Output.Content(data.Input.Content().value_or("")); - } - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, diff); - } - } - - void DscTestFileResource::ResourceFunctionTest(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - TestFileFunctionData data{ json }; - - data.Get(); - data.Output.InDesiredState(data.Test()); - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, data.DiffJson()); - } - } - - void DscTestFileResource::ResourceFunctionExport(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - TestFileFunctionData data{ json }; - - if (std::filesystem::exists(data.Path)) - { - if (std::filesystem::is_regular_file(data.Path)) - { - data.Get(); - WriteJsonOutputLine(context, data.Output.ToJson()); - } - else if (std::filesystem::is_directory(data.Path)) - { - for (const auto& file : std::filesystem::directory_iterator{ data.Path }) - { - if (std::filesystem::is_regular_file(file)) - { - TestFileObject output; - output.Path(file.path().u8string()); - - std::ifstream stream{ file.path(), std::ios::binary}; - output.Content(Utility::ReadEntireStream(stream)); - - WriteJsonOutputLine(context, output.ToJson()); - } - } - } - } - } - } - - void DscTestFileResource::ResourceFunctionSchema(Execution::Context& context) const - { - WriteJsonOutputLine(context, TestFileObject::Schema(ResourceType())); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscTestFileResource.h" +#include "DscComposableObject.h" + +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI +{ + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PathProperty, std::string, Path, "path", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The absolute path to a file."_lis); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ContentProperty, std::string, Content, "content", "The content of the file."_lis); + + using TestFileObject = DscComposableObject; + + struct TestFileFunctionData + { + TestFileFunctionData(const std::optional& json) : Input(json), Output(Input.CopyForOutput()) + { + Path = Utility::ConvertToUTF16(Input.Path().value()); + THROW_HR_IF(E_INVALIDARG, !Path.is_absolute()); + } + + TestFileObject Input; + TestFileObject Output; + std::filesystem::path Path; + + // Fills the Output object with the current state + void Get() + { + if (std::filesystem::exists(Path) && std::filesystem::is_regular_file(Path)) + { + Output.Exist(true); + + std::ifstream stream{ Path, std::ios::binary }; + Output.Content(Utility::ReadEntireStream(stream)); + } + else + { + Output.Exist(false); + } + } + + // Determines if the current Output values match the Input values state. + bool Test() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + if (Input.ShouldExist()) + { + if (Output.Exist().value()) + { + return ContentMatches(); + } + else + { + return false; + } + } + else + { + return !Output.Exist().value(); + } + } + + Json::Value DiffJson() + { + // Need to populate Output before calling + THROW_HR_IF(E_UNEXPECTED, !Output.Exist().has_value()); + + Json::Value result{ Json::ValueType::arrayValue }; + + if (Input.ShouldExist() != Output.Exist().value()) + { + result.append(std::string{ StandardExistProperty::Name() }); + } + else + { + if (!ContentMatches()) + { + result.append(std::string{ ContentProperty::Name() }); + } + } + + return result; + } + + private: + bool ContentMatches() + { + bool hasInput = Input.Content().has_value() && !Input.Content().value().empty(); + bool hasOutput = Output.Content().has_value() && !Output.Content().value().empty(); + + return + (hasInput && hasOutput && Input.Content().value() == Output.Content().value()) || + (!hasInput && !hasOutput); + } + }; + } + + DscTestFileResource::DscTestFileResource(std::string_view parent) : + DscCommandBase(parent, "test-file", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) + { + } + + Resource::LocString DscTestFileResource::ShortDescription() const + { + return "[TEST] File content resource"_lis; + } + + Resource::LocString DscTestFileResource::LongDescription() const + { + return "[TEST] This resource is only available for tests. It provides file content configuration."_lis; + } + + std::string DscTestFileResource::ResourceType() const + { + return "TestFile"; + } + + void DscTestFileResource::ResourceFunctionGet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + TestFileFunctionData data{ json }; + + data.Get(); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscTestFileResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + TestFileFunctionData data{ json }; + + data.Get(); + + if (!data.Test()) + { + bool exists = std::filesystem::exists(data.Path); + if (exists) + { + // Don't delete a directory or other special files in this test resource + THROW_WIN32_IF(ERROR_DIRECTORY_NOT_SUPPORTED, !std::filesystem::is_regular_file(data.Path)); + } + + if (data.Input.ShouldExist()) + { + std::filesystem::create_directories(data.Path.parent_path()); + + std::ofstream stream{ data.Path, std::ios::binary | std::ios::trunc }; + if (data.Input.Content()) + { + stream.write(data.Input.Content().value().c_str(), data.Input.Content().value().length()); + } + } + else if (exists) + { + std::filesystem::remove(data.Path); + } + } + + // Capture the diff before updating the output + auto diff = data.DiffJson(); + + data.Output.Exist(data.Input.ShouldExist()); + if (data.Output.Exist().value()) + { + data.Output.Content(data.Input.Content().value_or("")); + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, diff); + } + } + + void DscTestFileResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + TestFileFunctionData data{ json }; + + data.Get(); + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson()); + } + } + + void DscTestFileResource::ResourceFunctionExport(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + TestFileFunctionData data{ json }; + + if (std::filesystem::exists(data.Path)) + { + if (std::filesystem::is_regular_file(data.Path)) + { + data.Get(); + WriteJsonOutputLine(context, data.Output.ToJson()); + } + else if (std::filesystem::is_directory(data.Path)) + { + for (const auto& file : std::filesystem::directory_iterator{ data.Path }) + { + if (std::filesystem::is_regular_file(file)) + { + TestFileObject output; + output.Path(file.path().u8string()); + + std::ifstream stream{ file.path(), std::ios::binary}; + output.Content(Utility::ReadEntireStream(stream)); + + WriteJsonOutputLine(context, output.ToJson()); + } + } + } + } + } + } + + void DscTestFileResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, TestFileObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscTestFileResource.h b/src/AppInstallerCLICore/Commands/DscTestFileResource.h index 966a0f2761..a854ca8e09 100644 --- a/src/AppInstallerCLICore/Commands/DscTestFileResource.h +++ b/src/AppInstallerCLICore/Commands/DscTestFileResource.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DscCommandBase.h" - -namespace AppInstaller::CLI -{ - // A test resource implementing file content configuration. - struct DscTestFileResource : public DscCommandBase - { - DscTestFileResource(std::string_view parent); - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - std::string ResourceType() const override; - - void ResourceFunctionGet(Execution::Context& context) const override; - void ResourceFunctionSet(Execution::Context& context) const override; - void ResourceFunctionTest(Execution::Context& context) const override; - void ResourceFunctionExport(Execution::Context& context) const override; - void ResourceFunctionSchema(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A test resource implementing file content configuration. + struct DscTestFileResource : public DscCommandBase + { + DscTestFileResource(std::string_view parent); + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionTest(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp index 595ffb704b..4b37cd0227 100644 --- a/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscTestJsonResource.cpp @@ -1,208 +1,208 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscTestJsonResource.h" -#include "DscComposableObject.h" -#include - -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI -{ - namespace - { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PropertyProperty, std::string, Property, "property", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The JSON property name."_lis); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ValueProperty, Json::Value, Value, "value", "The value for the JSON property."_lis); - - using TestJsonObject = DscComposableObject; - - struct TestJsonFunctionData - { - TestJsonFunctionData() - { - InitializeFileData(); - } - - TestJsonFunctionData(const std::optional& json) : Input(json), Output(Input.CopyForOutput()) - { - InitializeFileData(); - } - - TestJsonObject Input; - TestJsonObject Output; - std::filesystem::path FilePath; - Json::Value RootValue; - - static std::filesystem::path GetFilePath() - { - std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); - result /= "test-json-file.json"; - return result; - } - - // Fills the Output object with the current state - void Get() - { - const std::string& propertyName = Input.Property().value(); - const Json::Value* propertyValue = RootValue.find(propertyName.data(), propertyName.data() + propertyName.length()); - - if (propertyValue) - { - Output.Exist(true); - - Output.Value(*propertyValue); - } - else - { - Output.Exist(false); - } - } - - private: - void InitializeFileData() - { - FilePath = GetFilePath(); - RootValue = GetJsonFromFile(); - } - - Json::Value GetJsonFromFile() const - { - Json::Value result; - Json::CharReaderBuilder builder; - Json::String errors; - - std::ifstream stream{ FilePath, std::ios::binary }; - - if (stream) - { - if (!Json::parseFromStream(builder, stream, &result, &errors)) - { - AICLI_LOG(CLI, Warning, << "Failed to read test JSON file: " << errors); - result = Json::Value{}; - } - } - else - { - AICLI_LOG(CLI, Warning, << "Couldn't open test JSON file: " << FilePath); - } - - return result; - } - }; - } - - DscTestJsonResource::DscTestJsonResource(std::string_view parent) : - DscCommandBase(parent, "test-json", DscResourceKind::Resource, - DscFunctions::Get | DscFunctions::Set | DscFunctions::Export | DscFunctions::Schema, - DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsState) - { - } - - std::vector DscTestJsonResource::GetArguments() const - { - auto result = DscCommandBase::GetArguments(); - result.emplace_back(Execution::Args::Type::DscResourceFunctionDelete, Resource::String::DscResourceFunctionDescriptionDelete, ArgumentType::Flag); - return result; - } - - Resource::LocString DscTestJsonResource::ShortDescription() const - { - return "[TEST] JSON content resource"_lis; - } - - Resource::LocString DscTestJsonResource::LongDescription() const - { - return "[TEST] This resource is only available for tests. It provides JSON content configuration of a well known file."_lis; - } - - void DscTestJsonResource::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionDelete)) - { - std::filesystem::remove_all(TestJsonFunctionData::GetFilePath()); - return; - } - - DscCommandBase::ExecuteInternal(context); - } - - std::string DscTestJsonResource::ResourceType() const - { - return "TestJSON"; - } - - void DscTestJsonResource::ResourceFunctionGet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - TestJsonFunctionData data{ json }; - - data.Get(); - - WriteJsonOutputLine(context, data.Output.ToJson()); - } - } - - void DscTestJsonResource::ResourceFunctionSet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - TestJsonFunctionData data{ json }; - - data.Get(); - - if (data.RootValue.isNull()) - { - data.RootValue = Json::Value{ Json::objectValue }; - } - - if (data.Input.ShouldExist()) - { - data.RootValue[data.Input.Property().value()] = data.Input.Value().value_or(Json::Value{ Json::nullValue }); - data.Output.Exist(true); - data.Output.Value(data.RootValue[data.Input.Property().value()]); - } - else if (data.Output.Exist().value()) - { - data.RootValue.removeMember(data.Input.Property().value()); - data.Output.Exist(false); - } - - std::ofstream stream{ data.FilePath, std::ios::binary }; - - Json::StreamWriterBuilder writerBuilder; - writerBuilder.settings_["indentation"] = " "; - - stream << Json::writeString(writerBuilder, data.RootValue); - - WriteJsonOutputLine(context, data.Output.ToJson()); - } - } - - void DscTestJsonResource::ResourceFunctionExport(Execution::Context& context) const - { - TestJsonFunctionData data; - - if (data.RootValue.isObject()) - { - for (const auto& member : data.RootValue.getMemberNames()) - { - const Json::Value* memberValue = data.RootValue.find(member.data(), member.data() + member.length()); - - if (memberValue) - { - TestJsonObject output; - output.Property(member); - output.Value(*memberValue); - - WriteJsonOutputLine(context, output.ToJson()); - } - } - } - } - - void DscTestJsonResource::ResourceFunctionSchema(Execution::Context& context) const - { - WriteJsonOutputLine(context, TestJsonObject::Schema(ResourceType())); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscTestJsonResource.h" +#include "DscComposableObject.h" +#include + +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI +{ + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(PropertyProperty, std::string, Property, "property", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, "The JSON property name."_lis); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY(ValueProperty, Json::Value, Value, "value", "The value for the JSON property."_lis); + + using TestJsonObject = DscComposableObject; + + struct TestJsonFunctionData + { + TestJsonFunctionData() + { + InitializeFileData(); + } + + TestJsonFunctionData(const std::optional& json) : Input(json), Output(Input.CopyForOutput()) + { + InitializeFileData(); + } + + TestJsonObject Input; + TestJsonObject Output; + std::filesystem::path FilePath; + Json::Value RootValue; + + static std::filesystem::path GetFilePath() + { + std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); + result /= "test-json-file.json"; + return result; + } + + // Fills the Output object with the current state + void Get() + { + const std::string& propertyName = Input.Property().value(); + const Json::Value* propertyValue = RootValue.find(propertyName.data(), propertyName.data() + propertyName.length()); + + if (propertyValue) + { + Output.Exist(true); + + Output.Value(*propertyValue); + } + else + { + Output.Exist(false); + } + } + + private: + void InitializeFileData() + { + FilePath = GetFilePath(); + RootValue = GetJsonFromFile(); + } + + Json::Value GetJsonFromFile() const + { + Json::Value result; + Json::CharReaderBuilder builder; + Json::String errors; + + std::ifstream stream{ FilePath, std::ios::binary }; + + if (stream) + { + if (!Json::parseFromStream(builder, stream, &result, &errors)) + { + AICLI_LOG(CLI, Warning, << "Failed to read test JSON file: " << errors); + result = Json::Value{}; + } + } + else + { + AICLI_LOG(CLI, Warning, << "Couldn't open test JSON file: " << FilePath); + } + + return result; + } + }; + } + + DscTestJsonResource::DscTestJsonResource(std::string_view parent) : + DscCommandBase(parent, "test-json", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsState) + { + } + + std::vector DscTestJsonResource::GetArguments() const + { + auto result = DscCommandBase::GetArguments(); + result.emplace_back(Execution::Args::Type::DscResourceFunctionDelete, Resource::String::DscResourceFunctionDescriptionDelete, ArgumentType::Flag); + return result; + } + + Resource::LocString DscTestJsonResource::ShortDescription() const + { + return "[TEST] JSON content resource"_lis; + } + + Resource::LocString DscTestJsonResource::LongDescription() const + { + return "[TEST] This resource is only available for tests. It provides JSON content configuration of a well known file."_lis; + } + + void DscTestJsonResource::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::DscResourceFunctionDelete)) + { + std::filesystem::remove_all(TestJsonFunctionData::GetFilePath()); + return; + } + + DscCommandBase::ExecuteInternal(context); + } + + std::string DscTestJsonResource::ResourceType() const + { + return "TestJSON"; + } + + void DscTestJsonResource::ResourceFunctionGet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + TestJsonFunctionData data{ json }; + + data.Get(); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscTestJsonResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + TestJsonFunctionData data{ json }; + + data.Get(); + + if (data.RootValue.isNull()) + { + data.RootValue = Json::Value{ Json::objectValue }; + } + + if (data.Input.ShouldExist()) + { + data.RootValue[data.Input.Property().value()] = data.Input.Value().value_or(Json::Value{ Json::nullValue }); + data.Output.Exist(true); + data.Output.Value(data.RootValue[data.Input.Property().value()]); + } + else if (data.Output.Exist().value()) + { + data.RootValue.removeMember(data.Input.Property().value()); + data.Output.Exist(false); + } + + std::ofstream stream{ data.FilePath, std::ios::binary }; + + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = " "; + + stream << Json::writeString(writerBuilder, data.RootValue); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + } + + void DscTestJsonResource::ResourceFunctionExport(Execution::Context& context) const + { + TestJsonFunctionData data; + + if (data.RootValue.isObject()) + { + for (const auto& member : data.RootValue.getMemberNames()) + { + const Json::Value* memberValue = data.RootValue.find(member.data(), member.data() + member.length()); + + if (memberValue) + { + TestJsonObject output; + output.Property(member); + output.Value(*memberValue); + + WriteJsonOutputLine(context, output.ToJson()); + } + } + } + } + + void DscTestJsonResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, TestJsonObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscTestJsonResource.h b/src/AppInstallerCLICore/Commands/DscTestJsonResource.h index 040ba4dfba..27ed13aa88 100644 --- a/src/AppInstallerCLICore/Commands/DscTestJsonResource.h +++ b/src/AppInstallerCLICore/Commands/DscTestJsonResource.h @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DscCommandBase.h" - -namespace AppInstaller::CLI -{ - // A test resource implementing JSON content configuration of a well known file. - // This is exists to enable an input-less export, which is required in DSC v3.0.0 - struct DscTestJsonResource : public DscCommandBase - { - DscTestJsonResource(std::string_view parent); - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - - std::string ResourceType() const override; - - void ResourceFunctionGet(Execution::Context& context) const override; - void ResourceFunctionSet(Execution::Context& context) const override; - void ResourceFunctionExport(Execution::Context& context) const override; - void ResourceFunctionSchema(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A test resource implementing JSON content configuration of a well known file. + // This is exists to enable an input-less export, which is required in DSC v3.0.0 + struct DscTestJsonResource : public DscCommandBase + { + DscTestJsonResource(std::string_view parent); + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.cpp b/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.cpp index 1dfa846070..1a0a9ba306 100644 --- a/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.cpp +++ b/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.cpp @@ -1,257 +1,257 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DscUserSettingsFileResource.h" -#include "DscComposableObject.h" -#include "Resources.h" -#include "AppInstallerStrings.h" - -using namespace AppInstaller::Utility::literals; -using namespace AppInstaller::Settings; - -#define ACTION_FULL "Full" -#define ACTION_PARTIAL "Partial" - -namespace AppInstaller::CLI -{ - namespace - { - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SettingsProperty, Json::Value, Settings, "settings", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionUserSettingsFileSettings); - WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(ActionProperty, std::string, Action, "action", Resource::String::DscResourcePropertyDescriptionUserSettingsFileAction, ({ ACTION_PARTIAL, ACTION_FULL }), ACTION_PARTIAL); - - using UserSettingsFileResourceObject = DscComposableObject; - - struct UserSettingsFileFunctionData - { - UserSettingsFileFunctionData() - : UserSettingsFileFunctionData(std::nullopt, true) - { - } - - UserSettingsFileFunctionData(const std::optional& json, bool ignoreFieldRequirements = false) : - Input(json, ignoreFieldRequirements), - _userSettingsPath(UserSettings::SettingsFilePath()) - { - } - - const UserSettingsFileResourceObject Input; - UserSettingsFileResourceObject Output; - - void Get() - { - Output.Settings(GetUserSettings()); - } - - bool Test() - { - return GetResolvedInput() == Output.Settings(); - } - - Json::Value DiffJson() - { - Json::Value result{ Json::ValueType::arrayValue }; - - if (!Test()) - { - result.append(std::string{ SettingsProperty::Name() }); - } - - return result; - } - - const Json::Value& GetResolvedInput() - { - THROW_HR_IF(E_UNEXPECTED, !Input.Settings().has_value()); - if (!_resolvedInputUserSettings) - { - if(Input.Action().has_value() && Utility::CaseInsensitiveEquals(Input.Action().value(), ACTION_FULL)) - { - Output.Action(ACTION_FULL); - _resolvedInputUserSettings = Input.Settings(); - } - else - { - Output.Action(ACTION_PARTIAL); - _resolvedInputUserSettings = MergeUserSettingsFiles(*Input.Settings()); - } - } - - return *_resolvedInputUserSettings; - } - - bool WriteOutput() - { - THROW_HR_IF(E_UNEXPECTED, !Output.Settings().has_value()); - std::ofstream file(_userSettingsPath, std::ios::binary); - if (file) - { - Json::StreamWriterBuilder writer; - writer["indentation"] = " "; - file << Json::writeString(writer, *Output.Settings()); - return true; - } - - AICLI_LOG(Config, Error, << "Failed to open or create user settings file: " << _userSettingsPath); - return false; - } - - private: - std::filesystem::path _userSettingsPath; - std::optional _userSettings; - std::optional _resolvedInputUserSettings; - - Json::Value MergeUserSettingsFiles(const Json::Value& overlay) - { - Json::Value mergedUserSettingsFile = GetUserSettings(); - MergeUserSettingsFiles(mergedUserSettingsFile, overlay); - return mergedUserSettingsFile; - } - - // Merges the overlay settings into the target settings. - void MergeUserSettingsFiles(Json::Value& target, const Json::Value& overlay) - { - // If either is not an object, we can't merge. - if (!overlay.isObject() || !target.isObject()) - { - return; - } - - // Iterate through the overlay settings and merge them into the target. - for (const auto& overlayKey : overlay.getMemberNames()) - { - const Json::Value& overlayValue = overlay[overlayKey]; - if (target.isMember(overlayKey)) - { - Json::Value& targetValue = target[overlayKey]; - if (targetValue.isObject() && overlayValue.isObject()) - { - // Recursively merge the objects. - MergeUserSettingsFiles(targetValue, overlayValue); - } - else - { - // Replace the value in the target. - // Note: Arrays are not merged, they are replaced. - target[overlayKey] = overlayValue; - } - } - else - { - // Add the overlay key to the target. - target[overlayKey] = overlayValue; - } - } - } - - const Json::Value& GetUserSettings() - { - if (!_userSettings) - { - _userSettings = Json::objectValue; - std::ifstream file(_userSettingsPath, std::ios::binary); - if (file) - { - Json::CharReaderBuilder builder; - std::string errs; - Json::Value jsonRoot; - if (Json::parseFromStream(builder, file, &jsonRoot, &errs)) - { - _userSettings = jsonRoot; - } - else - { - AICLI_LOG(Config, Warning, << "Failed to parse user settings file: " << _userSettingsPath << ", error: " << errs); - } - } - else - { - AICLI_LOG(Config, Warning, << "Failed to open user settings file: " << _userSettingsPath); - } - } - - return *_userSettings; - } - }; - } - - DscUserSettingsFileResource::DscUserSettingsFileResource(std::string_view parent) : - DscCommandBase(parent, "user-settings-file", DscResourceKind::Resource, - DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, - DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) - { - } - - Resource::LocString DscUserSettingsFileResource::ShortDescription() const - { - return Resource::String::DscUserSettingsFileShortDescription; - } - - Resource::LocString DscUserSettingsFileResource::LongDescription() const - { - return Resource::String::DscUserSettingsFileLongDescription; - } - - std::string DscUserSettingsFileResource::ResourceType() const - { - return "UserSettingsFile"; - } - - void DscUserSettingsFileResource::ResourceFunctionGet(Execution::Context& context) const - { - ResourceFunctionExport(context); - } - - void DscUserSettingsFileResource::ResourceFunctionSet(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - UserSettingsFileFunctionData data{ json }; - - data.Get(); - - // Capture the diff before updating the output - auto diff = data.DiffJson(); - - if (!data.Test()) - { - data.Output.Settings(data.GetResolvedInput()); - if (!data.WriteOutput()) - { - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED)); - return; - } - } - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, diff); - } - } - - void DscUserSettingsFileResource::ResourceFunctionTest(Execution::Context& context) const - { - if (auto json = GetJsonFromInput(context)) - { - UserSettingsFileFunctionData data{ json }; - - data.Get(); - data.Output.InDesiredState(data.Test()); - - WriteJsonOutputLine(context, data.Output.ToJson()); - WriteJsonOutputLine(context, data.DiffJson()); - } - } - - void DscUserSettingsFileResource::ResourceFunctionExport(Execution::Context& context) const - { - UserSettingsFileFunctionData data; - - data.Get(); - - WriteJsonOutputLine(context, data.Output.ToJson()); - } - - void DscUserSettingsFileResource::ResourceFunctionSchema(Execution::Context& context) const - { - WriteJsonOutputLine(context, UserSettingsFileResourceObject::Schema(ResourceType())); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DscUserSettingsFileResource.h" +#include "DscComposableObject.h" +#include "Resources.h" +#include "AppInstallerStrings.h" + +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Settings; + +#define ACTION_FULL "Full" +#define ACTION_PARTIAL "Partial" + +namespace AppInstaller::CLI +{ + namespace + { + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_FLAGS(SettingsProperty, Json::Value, Settings, "settings", DscComposablePropertyFlag::Required | DscComposablePropertyFlag::CopyToOutput, Resource::String::DscResourcePropertyDescriptionUserSettingsFileSettings); + WINGET_DSC_DEFINE_COMPOSABLE_PROPERTY_ENUM(ActionProperty, std::string, Action, "action", Resource::String::DscResourcePropertyDescriptionUserSettingsFileAction, ({ ACTION_PARTIAL, ACTION_FULL }), ACTION_PARTIAL); + + using UserSettingsFileResourceObject = DscComposableObject; + + struct UserSettingsFileFunctionData + { + UserSettingsFileFunctionData() + : UserSettingsFileFunctionData(std::nullopt, true) + { + } + + UserSettingsFileFunctionData(const std::optional& json, bool ignoreFieldRequirements = false) : + Input(json, ignoreFieldRequirements), + _userSettingsPath(UserSettings::SettingsFilePath()) + { + } + + const UserSettingsFileResourceObject Input; + UserSettingsFileResourceObject Output; + + void Get() + { + Output.Settings(GetUserSettings()); + } + + bool Test() + { + return GetResolvedInput() == Output.Settings(); + } + + Json::Value DiffJson() + { + Json::Value result{ Json::ValueType::arrayValue }; + + if (!Test()) + { + result.append(std::string{ SettingsProperty::Name() }); + } + + return result; + } + + const Json::Value& GetResolvedInput() + { + THROW_HR_IF(E_UNEXPECTED, !Input.Settings().has_value()); + if (!_resolvedInputUserSettings) + { + if(Input.Action().has_value() && Utility::CaseInsensitiveEquals(Input.Action().value(), ACTION_FULL)) + { + Output.Action(ACTION_FULL); + _resolvedInputUserSettings = Input.Settings(); + } + else + { + Output.Action(ACTION_PARTIAL); + _resolvedInputUserSettings = MergeUserSettingsFiles(*Input.Settings()); + } + } + + return *_resolvedInputUserSettings; + } + + bool WriteOutput() + { + THROW_HR_IF(E_UNEXPECTED, !Output.Settings().has_value()); + std::ofstream file(_userSettingsPath, std::ios::binary); + if (file) + { + Json::StreamWriterBuilder writer; + writer["indentation"] = " "; + file << Json::writeString(writer, *Output.Settings()); + return true; + } + + AICLI_LOG(Config, Error, << "Failed to open or create user settings file: " << _userSettingsPath); + return false; + } + + private: + std::filesystem::path _userSettingsPath; + std::optional _userSettings; + std::optional _resolvedInputUserSettings; + + Json::Value MergeUserSettingsFiles(const Json::Value& overlay) + { + Json::Value mergedUserSettingsFile = GetUserSettings(); + MergeUserSettingsFiles(mergedUserSettingsFile, overlay); + return mergedUserSettingsFile; + } + + // Merges the overlay settings into the target settings. + void MergeUserSettingsFiles(Json::Value& target, const Json::Value& overlay) + { + // If either is not an object, we can't merge. + if (!overlay.isObject() || !target.isObject()) + { + return; + } + + // Iterate through the overlay settings and merge them into the target. + for (const auto& overlayKey : overlay.getMemberNames()) + { + const Json::Value& overlayValue = overlay[overlayKey]; + if (target.isMember(overlayKey)) + { + Json::Value& targetValue = target[overlayKey]; + if (targetValue.isObject() && overlayValue.isObject()) + { + // Recursively merge the objects. + MergeUserSettingsFiles(targetValue, overlayValue); + } + else + { + // Replace the value in the target. + // Note: Arrays are not merged, they are replaced. + target[overlayKey] = overlayValue; + } + } + else + { + // Add the overlay key to the target. + target[overlayKey] = overlayValue; + } + } + } + + const Json::Value& GetUserSettings() + { + if (!_userSettings) + { + _userSettings = Json::objectValue; + std::ifstream file(_userSettingsPath, std::ios::binary); + if (file) + { + Json::CharReaderBuilder builder; + std::string errs; + Json::Value jsonRoot; + if (Json::parseFromStream(builder, file, &jsonRoot, &errs)) + { + _userSettings = jsonRoot; + } + else + { + AICLI_LOG(Config, Warning, << "Failed to parse user settings file: " << _userSettingsPath << ", error: " << errs); + } + } + else + { + AICLI_LOG(Config, Warning, << "Failed to open user settings file: " << _userSettingsPath); + } + } + + return *_userSettings; + } + }; + } + + DscUserSettingsFileResource::DscUserSettingsFileResource(std::string_view parent) : + DscCommandBase(parent, "user-settings-file", DscResourceKind::Resource, + DscFunctions::Get | DscFunctions::Set | DscFunctions::Test | DscFunctions::Export | DscFunctions::Schema, + DscFunctionModifiers::ImplementsPretest | DscFunctionModifiers::HandlesExist | DscFunctionModifiers::ReturnsStateAndDiff) + { + } + + Resource::LocString DscUserSettingsFileResource::ShortDescription() const + { + return Resource::String::DscUserSettingsFileShortDescription; + } + + Resource::LocString DscUserSettingsFileResource::LongDescription() const + { + return Resource::String::DscUserSettingsFileLongDescription; + } + + std::string DscUserSettingsFileResource::ResourceType() const + { + return "UserSettingsFile"; + } + + void DscUserSettingsFileResource::ResourceFunctionGet(Execution::Context& context) const + { + ResourceFunctionExport(context); + } + + void DscUserSettingsFileResource::ResourceFunctionSet(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + UserSettingsFileFunctionData data{ json }; + + data.Get(); + + // Capture the diff before updating the output + auto diff = data.DiffJson(); + + if (!data.Test()) + { + data.Output.Settings(data.GetResolvedInput()); + if (!data.WriteOutput()) + { + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED)); + return; + } + } + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, diff); + } + } + + void DscUserSettingsFileResource::ResourceFunctionTest(Execution::Context& context) const + { + if (auto json = GetJsonFromInput(context)) + { + UserSettingsFileFunctionData data{ json }; + + data.Get(); + data.Output.InDesiredState(data.Test()); + + WriteJsonOutputLine(context, data.Output.ToJson()); + WriteJsonOutputLine(context, data.DiffJson()); + } + } + + void DscUserSettingsFileResource::ResourceFunctionExport(Execution::Context& context) const + { + UserSettingsFileFunctionData data; + + data.Get(); + + WriteJsonOutputLine(context, data.Output.ToJson()); + } + + void DscUserSettingsFileResource::ResourceFunctionSchema(Execution::Context& context) const + { + WriteJsonOutputLine(context, UserSettingsFileResourceObject::Schema(ResourceType())); + } +} diff --git a/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.h b/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.h index a8d1699efd..bc7def97a6 100644 --- a/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.h +++ b/src/AppInstallerCLICore/Commands/DscUserSettingsFileResource.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DscCommandBase.h" - -namespace AppInstaller::CLI -{ - // A resource for managing user settings file. - struct DscUserSettingsFileResource : public DscCommandBase - { - DscUserSettingsFileResource(std::string_view parent); - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - std::string ResourceType() const override; - - void ResourceFunctionGet(Execution::Context& context) const override; - void ResourceFunctionSet(Execution::Context& context) const override; - void ResourceFunctionTest(Execution::Context& context) const override; - void ResourceFunctionExport(Execution::Context& context) const override; - void ResourceFunctionSchema(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DscCommandBase.h" + +namespace AppInstaller::CLI +{ + // A resource for managing user settings file. + struct DscUserSettingsFileResource : public DscCommandBase + { + DscUserSettingsFileResource(std::string_view parent); + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + std::string ResourceType() const override; + + void ResourceFunctionGet(Execution::Context& context) const override; + void ResourceFunctionSet(Execution::Context& context) const override; + void ResourceFunctionTest(Execution::Context& context) const override; + void ResourceFunctionExport(Execution::Context& context) const override; + void ResourceFunctionSchema(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ErrorCommand.cpp b/src/AppInstallerCLICore/Commands/ErrorCommand.cpp index 387fd35937..716561eb13 100644 --- a/src/AppInstallerCLICore/Commands/ErrorCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ErrorCommand.cpp @@ -1,219 +1,219 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ErrorCommand.h" -#include "AppInstallerStrings.h" -#include "AppInstallerErrors.h" -#include "VTSupport.h" - -#include - -namespace AppInstaller::CLI -{ - namespace - { - // 0x12345678 : SYMBOL_VALUE - // Descriptive text - void OutputHResultInformation(Execution::Context& context, const Errors::HResultInformation& error) - { - auto info = context.Reporter.Info(); - info << VirtualTerminal::TextFormat::Foreground::Bright << "0x" << VirtualTerminal::TextFormat::Foreground::Bright << Logging::SetHRFormat << error.Value(); - if (!error.Symbol().empty()) - { - info << " : "_liv << VirtualTerminal::TextFormat::Foreground::BrightCyan << error.Symbol(); - } - info << std::endl; - info << error.GetDescription() << std::endl; - } - - struct ErrorGroup - { - std::string_view Name; - WORD MinCode; // HRESULT_CODE, inclusive - WORD MaxCode; // HRESULT_CODE, inclusive - }; - - // Groups matching the sections in the published returnCodes.md documentation. - constexpr ErrorGroup s_errorGroups[] = - { - { "General Errors", 0x0001, 0x00FF }, - { "Install errors.", 0x0100, 0x01FF }, - { "Check for package installed status", 0x0200, 0x02FF }, - { "Configuration Errors", 0xC000, 0xC0FF }, - { "Configuration Processor Errors", 0xC100, 0xC1FF }, - }; - - constexpr std::string_view s_uncategorizedGroupName = "Uncategorized"; - - // Writes the markdown table for a group of errors to the given stream. - void WriteMarkdownGroup(std::ostream& out, std::string_view groupName, std::vector& errors) - { - if (errors.empty()) - { - return; - } - - std::sort(errors.begin(), errors.end(), - [](const Errors::HResultInformation* a, const Errors::HResultInformation* b) { return HRESULT_CODE(a->Value()) < HRESULT_CODE(b->Value()); }); - - out << "\n## " << groupName << "\n\n"; - out << "| Hex | Decimal | Symbol | Description |\n"; - out << "|-------------|-------------|-------------|-------------|\n"; - - for (const auto* error : errors) - { - HRESULT hr = error->Value(); - char hexBuf[11]; - sprintf_s(hexBuf, "0x%08X", static_cast(hr)); - - out << "| " << hexBuf - << " | " << static_cast(hr) - << " | " << error->Symbol() - << " | " << error->GetDescription() - << " |\n"; - } - } - - void OutputErrorMarkdown(Execution::Context& context) - { - auto allErrors = Errors::GetWinGetErrors(); - - // Categorize each error into its group; the last slot holds uncategorized errors. - static constexpr size_t s_errorGroupsCount = ARRAYSIZE(s_errorGroups); - std::vector> groupedErrors(s_errorGroupsCount + 1); - - for (const auto& error : allErrors) - { - HRESULT hr = error->Value(); - WORD code = static_cast(HRESULT_CODE(hr)); - - bool placed = false; - for (size_t i = 0; i < s_errorGroupsCount; ++i) - { - if (code >= s_errorGroups[i].MinCode && code <= s_errorGroups[i].MaxCode) - { - groupedErrors[i].push_back(error.get()); - placed = true; - break; - } - } - - if (!placed) - { - groupedErrors[s_errorGroupsCount].push_back(error.get()); - } - } - - std::filesystem::path outputPath{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }; - std::ofstream out{ outputPath }; - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED), !out); - - std::time_t now = std::time(nullptr); - std::tm tm{}; - localtime_s(&tm, &now); - char dateBuf[11]; - strftime(dateBuf, sizeof(dateBuf), "%m/%d/%Y", &tm); - - out << - "---\n" - "title: WinGet HRESULT Codes\n" - "description: WinGet return codes and their meanings\n" - "ms.date: " << dateBuf << "\n" - "ms.topic: article\n" - "ms.localizationpriority: low\n" - "---\n" - "\n" - "> [!NOTE]\n" - "> This file is generated by running: winget error --output \n" - "\n" - "# Return Codes\n"; - - for (size_t i = 0; i < ARRAYSIZE(s_errorGroups); ++i) - { - WriteMarkdownGroup(out, s_errorGroups[i].Name, groupedErrors[i]); - } - - WriteMarkdownGroup(out, s_uncategorizedGroupName, groupedErrors[s_errorGroupsCount]); - } - } - - std::vector ErrorCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::ErrorInput, Resource::String::ErrorInputArgumentDescription, ArgumentType::Positional }, - Argument{ Execution::Args::Type::OutputFile, Resource::String::ErrorOutputFileArgumentDescription, ArgumentType::Standard }, - }; - } - - Resource::LocString ErrorCommand::ShortDescription() const - { - return { Resource::String::ErrorCommandShortDescription }; - } - - Resource::LocString ErrorCommand::LongDescription() const - { - return { Resource::String::ErrorCommandLongDescription }; - } - - void ErrorCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - if (execArgs.Contains(Execution::Args::Type::OutputFile) && execArgs.Contains(Execution::Args::Type::ErrorInput)) - { - throw CommandException(Resource::String::ErrorOutputFileConflictsWithInput); - } - - if (!execArgs.Contains(Execution::Args::Type::OutputFile) && !execArgs.Contains(Execution::Args::Type::ErrorInput)) - { - throw CommandException(Resource::String::ErrorRequiresInputOrOutputFile); - } - } - - void ErrorCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::OutputFile)) - { - OutputErrorMarkdown(context); - return; - } - - std::string input{ context.Args.GetArg(Execution::Args::Type::ErrorInput) }; - - const char* begin = input.c_str(); - char* end = nullptr; - errno = 0; - HRESULT errorNumber = strtol(begin, &end, 0); - - if (errno == ERANGE) - { - errno = 0; - unsigned long unsignedError = strtoul(begin, &end, 0); - - if (errno == ERANGE) - { - context.Reporter.Error() << Resource::String::ErrorNumberIsTooLarge << std::endl; - AICLI_TERMINATE_CONTEXT(E_INVALIDARG); - } - - errorNumber = static_cast(unsignedError); - } - - // If the entire string was consumed as a number, treat it as an HRESULT - if (static_cast(end - begin) == input.length()) - { - auto error = Errors::HResultInformation::Find(errorNumber); - if (error) - { - OutputHResultInformation(context, *error); - } - } - // otherwise, treat it as a string and search our error list - else - { - auto errors = Errors::HResultInformation::Find(Utility::Trim(input)); - for (const auto& error : errors) - { - OutputHResultInformation(context, *error); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ErrorCommand.h" +#include "AppInstallerStrings.h" +#include "AppInstallerErrors.h" +#include "VTSupport.h" + +#include + +namespace AppInstaller::CLI +{ + namespace + { + // 0x12345678 : SYMBOL_VALUE + // Descriptive text + void OutputHResultInformation(Execution::Context& context, const Errors::HResultInformation& error) + { + auto info = context.Reporter.Info(); + info << VirtualTerminal::TextFormat::Foreground::Bright << "0x" << VirtualTerminal::TextFormat::Foreground::Bright << Logging::SetHRFormat << error.Value(); + if (!error.Symbol().empty()) + { + info << " : "_liv << VirtualTerminal::TextFormat::Foreground::BrightCyan << error.Symbol(); + } + info << std::endl; + info << error.GetDescription() << std::endl; + } + + struct ErrorGroup + { + std::string_view Name; + WORD MinCode; // HRESULT_CODE, inclusive + WORD MaxCode; // HRESULT_CODE, inclusive + }; + + // Groups matching the sections in the published returnCodes.md documentation. + constexpr ErrorGroup s_errorGroups[] = + { + { "General Errors", 0x0001, 0x00FF }, + { "Install errors.", 0x0100, 0x01FF }, + { "Check for package installed status", 0x0200, 0x02FF }, + { "Configuration Errors", 0xC000, 0xC0FF }, + { "Configuration Processor Errors", 0xC100, 0xC1FF }, + }; + + constexpr std::string_view s_uncategorizedGroupName = "Uncategorized"; + + // Writes the markdown table for a group of errors to the given stream. + void WriteMarkdownGroup(std::ostream& out, std::string_view groupName, std::vector& errors) + { + if (errors.empty()) + { + return; + } + + std::sort(errors.begin(), errors.end(), + [](const Errors::HResultInformation* a, const Errors::HResultInformation* b) { return HRESULT_CODE(a->Value()) < HRESULT_CODE(b->Value()); }); + + out << "\n## " << groupName << "\n\n"; + out << "| Hex | Decimal | Symbol | Description |\n"; + out << "|-------------|-------------|-------------|-------------|\n"; + + for (const auto* error : errors) + { + HRESULT hr = error->Value(); + char hexBuf[11]; + sprintf_s(hexBuf, "0x%08X", static_cast(hr)); + + out << "| " << hexBuf + << " | " << static_cast(hr) + << " | " << error->Symbol() + << " | " << error->GetDescription() + << " |\n"; + } + } + + void OutputErrorMarkdown(Execution::Context& context) + { + auto allErrors = Errors::GetWinGetErrors(); + + // Categorize each error into its group; the last slot holds uncategorized errors. + static constexpr size_t s_errorGroupsCount = ARRAYSIZE(s_errorGroups); + std::vector> groupedErrors(s_errorGroupsCount + 1); + + for (const auto& error : allErrors) + { + HRESULT hr = error->Value(); + WORD code = static_cast(HRESULT_CODE(hr)); + + bool placed = false; + for (size_t i = 0; i < s_errorGroupsCount; ++i) + { + if (code >= s_errorGroups[i].MinCode && code <= s_errorGroups[i].MaxCode) + { + groupedErrors[i].push_back(error.get()); + placed = true; + break; + } + } + + if (!placed) + { + groupedErrors[s_errorGroupsCount].push_back(error.get()); + } + } + + std::filesystem::path outputPath{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }; + std::ofstream out{ outputPath }; + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED), !out); + + std::time_t now = std::time(nullptr); + std::tm tm{}; + localtime_s(&tm, &now); + char dateBuf[11]; + strftime(dateBuf, sizeof(dateBuf), "%m/%d/%Y", &tm); + + out << + "---\n" + "title: WinGet HRESULT Codes\n" + "description: WinGet return codes and their meanings\n" + "ms.date: " << dateBuf << "\n" + "ms.topic: article\n" + "ms.localizationpriority: low\n" + "---\n" + "\n" + "> [!NOTE]\n" + "> This file is generated by running: winget error --output \n" + "\n" + "# Return Codes\n"; + + for (size_t i = 0; i < ARRAYSIZE(s_errorGroups); ++i) + { + WriteMarkdownGroup(out, s_errorGroups[i].Name, groupedErrors[i]); + } + + WriteMarkdownGroup(out, s_uncategorizedGroupName, groupedErrors[s_errorGroupsCount]); + } + } + + std::vector ErrorCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::ErrorInput, Resource::String::ErrorInputArgumentDescription, ArgumentType::Positional }, + Argument{ Execution::Args::Type::OutputFile, Resource::String::ErrorOutputFileArgumentDescription, ArgumentType::Standard }, + }; + } + + Resource::LocString ErrorCommand::ShortDescription() const + { + return { Resource::String::ErrorCommandShortDescription }; + } + + Resource::LocString ErrorCommand::LongDescription() const + { + return { Resource::String::ErrorCommandLongDescription }; + } + + void ErrorCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::OutputFile) && execArgs.Contains(Execution::Args::Type::ErrorInput)) + { + throw CommandException(Resource::String::ErrorOutputFileConflictsWithInput); + } + + if (!execArgs.Contains(Execution::Args::Type::OutputFile) && !execArgs.Contains(Execution::Args::Type::ErrorInput)) + { + throw CommandException(Resource::String::ErrorRequiresInputOrOutputFile); + } + } + + void ErrorCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::OutputFile)) + { + OutputErrorMarkdown(context); + return; + } + + std::string input{ context.Args.GetArg(Execution::Args::Type::ErrorInput) }; + + const char* begin = input.c_str(); + char* end = nullptr; + errno = 0; + HRESULT errorNumber = strtol(begin, &end, 0); + + if (errno == ERANGE) + { + errno = 0; + unsigned long unsignedError = strtoul(begin, &end, 0); + + if (errno == ERANGE) + { + context.Reporter.Error() << Resource::String::ErrorNumberIsTooLarge << std::endl; + AICLI_TERMINATE_CONTEXT(E_INVALIDARG); + } + + errorNumber = static_cast(unsignedError); + } + + // If the entire string was consumed as a number, treat it as an HRESULT + if (static_cast(end - begin) == input.length()) + { + auto error = Errors::HResultInformation::Find(errorNumber); + if (error) + { + OutputHResultInformation(context, *error); + } + } + // otherwise, treat it as a string and search our error list + else + { + auto errors = Errors::HResultInformation::Find(Utility::Trim(input)); + for (const auto& error : errors) + { + OutputHResultInformation(context, *error); + } + } + } +} diff --git a/src/AppInstallerCLICore/Commands/ErrorCommand.h b/src/AppInstallerCLICore/Commands/ErrorCommand.h index af5336583e..109bbb2028 100644 --- a/src/AppInstallerCLICore/Commands/ErrorCommand.h +++ b/src/AppInstallerCLICore/Commands/ErrorCommand.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - // This command makes it easier to get information on winget errors. - struct ErrorCommand final : public Command - { - ErrorCommand(std::string_view parent) : Command("error", { "err" }, parent, Visibility::Hidden) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + // This command makes it easier to get information on winget errors. + struct ErrorCommand final : public Command + { + ErrorCommand(std::string_view parent) : Command("error", { "err" }, parent, Visibility::Hidden) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/HashCommand.cpp b/src/AppInstallerCLICore/Commands/HashCommand.cpp index 0d6e01589e..bb9e0bea53 100644 --- a/src/AppInstallerCLICore/Commands/HashCommand.cpp +++ b/src/AppInstallerCLICore/Commands/HashCommand.cpp @@ -1,68 +1,68 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "HashCommand.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -#include - -namespace AppInstaller::CLI -{ - using namespace std::string_view_literals; - using namespace Utility::literals; - - std::vector HashCommand::GetArguments() const - { - return { - Argument::ForType(Execution::Args::Type::HashFile), - Argument::ForType(Execution::Args::Type::Msix), - }; - } - - Resource::LocString HashCommand::ShortDescription() const - { - return { Resource::String::HashCommandShortDescription }; - } - - Resource::LocString HashCommand::LongDescription() const - { - return { Resource::String::HashCommandLongDescription }; - } - - Utility::LocIndView HashCommand::HelpLink() const - { - return "https://aka.ms/winget-command-hash"_liv; - } - - void HashCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::VerifyFile(Execution::Args::Type::HashFile) << - [](Execution::Context& context) - { - auto inputFile = context.Args.GetArg(Execution::Args::Type::HashFile); - std::ifstream inStream{ Utility::ConvertToUTF16(inputFile), std::ifstream::binary }; - - context.Reporter.Info() << "InstallerSha256: "_liv << Utility::LocIndString{ Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHash(inStream)) } << std::endl; - - if (context.Args.Contains(Execution::Args::Type::Msix)) - { - try - { - Msix::MsixInfo msixInfo{ inputFile }; - auto signatureHash = msixInfo.GetSignatureHash(); - - context.Reporter.Info() << "SignatureSha256: "_liv << Utility::LocIndString{ Utility::SHA256::ConvertToString(signatureHash) } << std::endl; - } - catch (const wil::ResultException& re) - { - context.Reporter.Warn() << - Resource::String::MsixSignatureHashFailed << std::endl << - Resource::String::VerifyFileSignedMsix << std::endl; - AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); - } - } - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "HashCommand.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +#include + +namespace AppInstaller::CLI +{ + using namespace std::string_view_literals; + using namespace Utility::literals; + + std::vector HashCommand::GetArguments() const + { + return { + Argument::ForType(Execution::Args::Type::HashFile), + Argument::ForType(Execution::Args::Type::Msix), + }; + } + + Resource::LocString HashCommand::ShortDescription() const + { + return { Resource::String::HashCommandShortDescription }; + } + + Resource::LocString HashCommand::LongDescription() const + { + return { Resource::String::HashCommandLongDescription }; + } + + Utility::LocIndView HashCommand::HelpLink() const + { + return "https://aka.ms/winget-command-hash"_liv; + } + + void HashCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::VerifyFile(Execution::Args::Type::HashFile) << + [](Execution::Context& context) + { + auto inputFile = context.Args.GetArg(Execution::Args::Type::HashFile); + std::ifstream inStream{ Utility::ConvertToUTF16(inputFile), std::ifstream::binary }; + + context.Reporter.Info() << "InstallerSha256: "_liv << Utility::LocIndString{ Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHash(inStream)) } << std::endl; + + if (context.Args.Contains(Execution::Args::Type::Msix)) + { + try + { + Msix::MsixInfo msixInfo{ inputFile }; + auto signatureHash = msixInfo.GetSignatureHash(); + + context.Reporter.Info() << "SignatureSha256: "_liv << Utility::LocIndString{ Utility::SHA256::ConvertToString(signatureHash) } << std::endl; + } + catch (const wil::ResultException& re) + { + context.Reporter.Warn() << + Resource::String::MsixSignatureHashFailed << std::endl << + Resource::String::VerifyFileSignedMsix << std::endl; + AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); + } + } + }; + } +} diff --git a/src/AppInstallerCLICore/Commands/HashCommand.h b/src/AppInstallerCLICore/Commands/HashCommand.h index 75b58d7acc..2d56296069 100644 --- a/src/AppInstallerCLICore/Commands/HashCommand.h +++ b/src/AppInstallerCLICore/Commands/HashCommand.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct HashCommand final : public Command - { - HashCommand(std::string_view parent) : Command("hash", parent) {} - - virtual std::vector GetArguments() const override; - - virtual Resource::LocString ShortDescription() const override; - virtual Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct HashCommand final : public Command + { + HashCommand(std::string_view parent) : Command("hash", parent) {} + + virtual std::vector GetArguments() const override; + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ImportCommand.cpp b/src/AppInstallerCLICore/Commands/ImportCommand.cpp index fefc0a6679..39e4d9b4ab 100644 --- a/src/AppInstallerCLICore/Commands/ImportCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ImportCommand.cpp @@ -1,7 +1,7 @@ // 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" @@ -42,7 +42,7 @@ 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) << diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.cpp b/src/AppInstallerCLICore/Commands/InstallCommand.cpp index 15aeaec7ad..e96139186e 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.cpp +++ b/src/AppInstallerCLICore/Commands/InstallCommand.cpp @@ -1,173 +1,173 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerRuntime.h" -#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" -#include "Workflows/ResumeFlow.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::CLI::Workflow; - using namespace AppInstaller::Manifest; - using namespace AppInstaller::Utility::literals; - - std::vector InstallCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::MultiQuery), - Argument::ForType(Args::Type::Manifest), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Version), - Argument::ForType(Args::Type::Channel), - Argument::ForType(Args::Type::Source), - Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Args::Type::InstallArchitecture), - Argument::ForType(Args::Type::InstallerType), - Argument::ForType(Args::Type::Exact), - Argument::ForType(Args::Type::Interactive), - Argument::ForType(Args::Type::Silent), - Argument::ForType(Args::Type::Locale), - Argument::ForType(Args::Type::Log), - Argument::ForType(Args::Type::CustomSwitches), - Argument::ForType(Args::Type::Override), - Argument::ForType(Args::Type::InstallLocation), - Argument::ForType(Args::Type::HashOverride), - Argument::ForType(Args::Type::AllowReboot), - Argument::ForType(Args::Type::SkipDependencies), - Argument::ForType(Args::Type::DependenciesOnly), - Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan), - Argument::ForType(Args::Type::DependencySource), - Argument::ForType(Args::Type::AcceptPackageAgreements), - Argument::ForType(Args::Type::NoUpgrade), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument::ForType(Args::Type::Rename), - Argument::ForType(Args::Type::UninstallPrevious), - Argument::ForType(Args::Type::Force), - Argument{ Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden}, - }; - } - - Resource::LocString InstallCommand::ShortDescription() const - { - return { Resource::String::InstallCommandShortDescription }; - } - - Resource::LocString InstallCommand::LongDescription() const - { - return { Resource::String::InstallCommandLongDescription }; - } - - void InstallCommand::Complete(Context& context, Args::Type valueType) const - { - switch (valueType) - { - case Args::Type::MultiQuery: - case Args::Type::Manifest: - case Args::Type::Id: - case Args::Type::Name: - case Args::Type::Moniker: - case Args::Type::Version: - case Args::Type::Channel: - case Args::Type::Source: - context << - Workflow::CompleteWithSingleSemanticsForValue(valueType); - break; - case Args::Type::InstallArchitecture: - case Args::Type::Locale: - // May well move to CompleteWithSingleSemanticsForValue, - // but for now output nothing. - context << - Workflow::CompleteWithEmptySet; - break; - case Args::Type::Log: - case Args::Type::InstallLocation: - // Intentionally output nothing to allow pass through to filesystem. - break; - } - } - - Utility::LocIndView InstallCommand::HelpLink() const - { - return "https://aka.ms/winget-command-install"_liv; - } - - void InstallCommand::ValidateArgumentsInternal(Args& execArgs) const - { - Argument::ValidateCommonArguments(execArgs); - } - - void InstallCommand::Resume(Context& context) const - { - // TODO: Load context data from checkpoint for install command. - ExecuteInternal(context); - } - - void InstallCommand::ExecuteInternal(Context& context) const - { - context.SetFlags(ContextFlag::ShowSearchResultsOnPartialFailure); - - context << InitializeInstallerDownloadAuthenticatorsMap; - - if (context.Args.Contains(Execution::Args::Type::Manifest)) - { - context << - ReportExecutionStage(ExecutionStage::Discovery) << - GetManifestFromArg << - SelectInstaller << - EnsureApplicableInstaller << - Checkpoint("PreInstallCheckpoint", {}) << // TODO: Capture context data - InstallSinglePackage; - } - else - { - context << - ReportExecutionStage(ExecutionStage::Discovery) << - OpenSource(); - - if (!context.Args.Contains(Execution::Args::Type::Force)) - { - context << - OpenCompositeSource(DetermineInstalledSource(context), false, Repository::CompositeSearchBehavior::AvailablePackages); - } - - if (context.Args.Contains(Execution::Args::Type::MultiQuery)) - { - ProcessMultiplePackages::Flags flags = ProcessMultiplePackages::Flags::None; - if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) - { - flags = ProcessMultiplePackages::Flags::IgnoreDependencies; - } - else if (context.Args.Contains(Execution::Args::Type::DependenciesOnly)) - { - flags = ProcessMultiplePackages::Flags::DependenciesOnly; - } - - context << - GetMultiSearchRequests << - SearchSubContextsForSingle() << - ReportExecutionStage(ExecutionStage::Execution) << - ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED, flags); - } - else - { - context << - Checkpoint("PreInstallCheckpoint", {}) << // TODO: Capture context data - InstallOrUpgradeSinglePackage(OperationType::Install); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerRuntime.h" +#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" +#include "Workflows/ResumeFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + using namespace AppInstaller::Manifest; + using namespace AppInstaller::Utility::literals; + + std::vector InstallCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::MultiQuery), + Argument::ForType(Args::Type::Manifest), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Version), + Argument::ForType(Args::Type::Channel), + Argument::ForType(Args::Type::Source), + Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Args::Type::InstallArchitecture), + Argument::ForType(Args::Type::InstallerType), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::Interactive), + Argument::ForType(Args::Type::Silent), + Argument::ForType(Args::Type::Locale), + Argument::ForType(Args::Type::Log), + Argument::ForType(Args::Type::CustomSwitches), + Argument::ForType(Args::Type::Override), + Argument::ForType(Args::Type::InstallLocation), + Argument::ForType(Args::Type::HashOverride), + Argument::ForType(Args::Type::AllowReboot), + Argument::ForType(Args::Type::SkipDependencies), + Argument::ForType(Args::Type::DependenciesOnly), + Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan), + Argument::ForType(Args::Type::DependencySource), + Argument::ForType(Args::Type::AcceptPackageAgreements), + Argument::ForType(Args::Type::NoUpgrade), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::Rename), + Argument::ForType(Args::Type::UninstallPrevious), + Argument::ForType(Args::Type::Force), + Argument{ Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag, Argument::Visibility::Hidden}, + }; + } + + Resource::LocString InstallCommand::ShortDescription() const + { + return { Resource::String::InstallCommandShortDescription }; + } + + Resource::LocString InstallCommand::LongDescription() const + { + return { Resource::String::InstallCommandLongDescription }; + } + + void InstallCommand::Complete(Context& context, Args::Type valueType) const + { + switch (valueType) + { + case Args::Type::MultiQuery: + case Args::Type::Manifest: + case Args::Type::Id: + case Args::Type::Name: + case Args::Type::Moniker: + case Args::Type::Version: + case Args::Type::Channel: + case Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValue(valueType); + break; + case Args::Type::InstallArchitecture: + case Args::Type::Locale: + // May well move to CompleteWithSingleSemanticsForValue, + // but for now output nothing. + context << + Workflow::CompleteWithEmptySet; + break; + case Args::Type::Log: + case Args::Type::InstallLocation: + // Intentionally output nothing to allow pass through to filesystem. + break; + } + } + + Utility::LocIndView InstallCommand::HelpLink() const + { + return "https://aka.ms/winget-command-install"_liv; + } + + void InstallCommand::ValidateArgumentsInternal(Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void InstallCommand::Resume(Context& context) const + { + // TODO: Load context data from checkpoint for install command. + ExecuteInternal(context); + } + + void InstallCommand::ExecuteInternal(Context& context) const + { + context.SetFlags(ContextFlag::ShowSearchResultsOnPartialFailure); + + context << InitializeInstallerDownloadAuthenticatorsMap; + + if (context.Args.Contains(Execution::Args::Type::Manifest)) + { + context << + ReportExecutionStage(ExecutionStage::Discovery) << + GetManifestFromArg << + SelectInstaller << + EnsureApplicableInstaller << + Checkpoint("PreInstallCheckpoint", {}) << // TODO: Capture context data + InstallSinglePackage; + } + else + { + context << + ReportExecutionStage(ExecutionStage::Discovery) << + OpenSource(); + + if (!context.Args.Contains(Execution::Args::Type::Force)) + { + context << + OpenCompositeSource(DetermineInstalledSource(context), false, Repository::CompositeSearchBehavior::AvailablePackages); + } + + if (context.Args.Contains(Execution::Args::Type::MultiQuery)) + { + ProcessMultiplePackages::Flags flags = ProcessMultiplePackages::Flags::None; + if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) + { + flags = ProcessMultiplePackages::Flags::IgnoreDependencies; + } + else if (context.Args.Contains(Execution::Args::Type::DependenciesOnly)) + { + flags = ProcessMultiplePackages::Flags::DependenciesOnly; + } + + context << + GetMultiSearchRequests << + SearchSubContextsForSingle() << + ReportExecutionStage(ExecutionStage::Execution) << + ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED, flags); + } + else + { + context << + Checkpoint("PreInstallCheckpoint", {}) << // TODO: Capture context data + InstallOrUpgradeSinglePackage(OperationType::Install); + } + } + } +} diff --git a/src/AppInstallerCLICore/Commands/InstallCommand.h b/src/AppInstallerCLICore/Commands/InstallCommand.h index 190c984661..c8a39aed20 100644 --- a/src/AppInstallerCLICore/Commands/InstallCommand.h +++ b/src/AppInstallerCLICore/Commands/InstallCommand.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct InstallCommand final : public Command - { - InstallCommand(std::string_view parent) : Command("install", { "add" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - void Resume(Execution::Context& context) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct InstallCommand final : public Command + { + InstallCommand(std::string_view parent) : Command("install", { "add" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + void Resume(Execution::Context& context) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ListCommand.cpp b/src/AppInstallerCLICore/Commands/ListCommand.cpp index a9a8d49165..fc57f03e3a 100644 --- a/src/AppInstallerCLICore/Commands/ListCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ListCommand.cpp @@ -1,103 +1,103 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ListCommand.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Workflow; - using namespace std::string_view_literals; - - std::vector ListCommand::GetArguments() const - { - // Compile-time count of sort field values, used to cap --sort argument count. - static constexpr size_t s_sortFieldCount = AppInstaller::GetExponentialEnumValuesCount(Settings::SortField::None); - - return { - Argument::ForType(Execution::Args::Type::Query), - Argument::ForType(Execution::Args::Type::Id), - Argument::ForType(Execution::Args::Type::Name), - Argument::ForType(Execution::Args::Type::Moniker), - Argument::ForType(Execution::Args::Type::Source), - Argument::ForType(Execution::Args::Type::Tag), - Argument::ForType(Execution::Args::Type::Command), - Argument::ForType(Execution::Args::Type::Count), - Argument::ForType(Execution::Args::Type::Exact), - Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Execution::Args::Type::CustomHeader), - Argument::ForType(Execution::Args::Type::AuthenticationMode), - Argument::ForType(Execution::Args::Type::AuthenticationAccount), - Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), - Argument{ Execution::Args::Type::Upgrade, Resource::String::UpgradeArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::IncludeUnknown, Resource::String::IncludeUnknownInListArgumentDescription, ArgumentType::Flag }, - Argument{ Execution::Args::Type::IncludePinned, Resource::String::IncludePinnedInListArgumentDescription, ArgumentType::Flag}, - Argument::ForType(Execution::Args::Type::ListDetails), - Argument{ Execution::Args::Type::Sort, Resource::String::SortArgumentDescription, ArgumentType::Standard }.SetCountLimit(s_sortFieldCount), - Argument{ Execution::Args::Type::SortAscending, Resource::String::SortAscendingArgumentDescription, ArgumentType::Flag }, - Argument{ Execution::Args::Type::SortDescending, Resource::String::SortDescendingArgumentDescription, ArgumentType::Flag }, - }; - } - - Resource::LocString ListCommand::ShortDescription() const - { - return { Resource::String::ListCommandShortDescription }; - } - - Resource::LocString ListCommand::LongDescription() const - { - return { Resource::String::ListCommandLongDescription }; - } - - void ListCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const - { - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Source: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView ListCommand::HelpLink() const - { - return "https://aka.ms/winget-command-list"_liv; - } - - void ListCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Argument::ValidateArgumentDependency(execArgs, Execution::Args::Type::IncludeUnknown, Execution::Args::Type::Upgrade); - Argument::ValidateArgumentDependency(execArgs, Execution::Args::Type::IncludePinned, Execution::Args::Type::Upgrade); - } - - void ListCommand::ExecuteInternal(Execution::Context& context) const - { - context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); - - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(context)) << - Workflow::SearchSourceForMany << - Workflow::HandleSearchResultFailures << - Workflow::EnsureMatchesFromSearchResult(OperationType::List) << - Workflow::ReportListResult(context.Args.Contains(Execution::Args::Type::Upgrade)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ListCommand.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Workflow; + using namespace std::string_view_literals; + + std::vector ListCommand::GetArguments() const + { + // Compile-time count of sort field values, used to cap --sort argument count. + static constexpr size_t s_sortFieldCount = AppInstaller::GetExponentialEnumValuesCount(Settings::SortField::None); + + return { + Argument::ForType(Execution::Args::Type::Query), + Argument::ForType(Execution::Args::Type::Id), + Argument::ForType(Execution::Args::Type::Name), + Argument::ForType(Execution::Args::Type::Moniker), + Argument::ForType(Execution::Args::Type::Source), + Argument::ForType(Execution::Args::Type::Tag), + Argument::ForType(Execution::Args::Type::Command), + Argument::ForType(Execution::Args::Type::Count), + Argument::ForType(Execution::Args::Type::Exact), + Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Execution::Args::Type::CustomHeader), + Argument::ForType(Execution::Args::Type::AuthenticationMode), + Argument::ForType(Execution::Args::Type::AuthenticationAccount), + Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), + Argument{ Execution::Args::Type::Upgrade, Resource::String::UpgradeArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::IncludeUnknown, Resource::String::IncludeUnknownInListArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::IncludePinned, Resource::String::IncludePinnedInListArgumentDescription, ArgumentType::Flag}, + Argument::ForType(Execution::Args::Type::ListDetails), + Argument{ Execution::Args::Type::Sort, Resource::String::SortArgumentDescription, ArgumentType::Standard }.SetCountLimit(s_sortFieldCount), + Argument{ Execution::Args::Type::SortAscending, Resource::String::SortAscendingArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::SortDescending, Resource::String::SortDescendingArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString ListCommand::ShortDescription() const + { + return { Resource::String::ListCommandShortDescription }; + } + + Resource::LocString ListCommand::LongDescription() const + { + return { Resource::String::ListCommandLongDescription }; + } + + void ListCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const + { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView ListCommand::HelpLink() const + { + return "https://aka.ms/winget-command-list"_liv; + } + + void ListCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Argument::ValidateArgumentDependency(execArgs, Execution::Args::Type::IncludeUnknown, Execution::Args::Type::Upgrade); + Argument::ValidateArgumentDependency(execArgs, Execution::Args::Type::IncludePinned, Execution::Args::Type::Upgrade); + } + + void ListCommand::ExecuteInternal(Execution::Context& context) const + { + context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); + + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Workflow::DetermineInstalledSource(context)) << + Workflow::SearchSourceForMany << + Workflow::HandleSearchResultFailures << + Workflow::EnsureMatchesFromSearchResult(OperationType::List) << + Workflow::ReportListResult(context.Args.Contains(Execution::Args::Type::Upgrade)); + } +} diff --git a/src/AppInstallerCLICore/Commands/ListCommand.h b/src/AppInstallerCLICore/Commands/ListCommand.h index 36e7c0ca54..3fdf892918 100644 --- a/src/AppInstallerCLICore/Commands/ListCommand.h +++ b/src/AppInstallerCLICore/Commands/ListCommand.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - // Command to get the set of installed packages on the system. - struct ListCommand final : public Command - { - ListCommand(std::string_view parent) : Command("list", { "ls" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + // Command to get the set of installed packages on the system. + struct ListCommand final : public Command + { + ListCommand(std::string_view parent) : Command("list", { "ls" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/McpCommand.cpp b/src/AppInstallerCLICore/Commands/McpCommand.cpp index 42e1f07de5..e33ae163be 100644 --- a/src/AppInstallerCLICore/Commands/McpCommand.cpp +++ b/src/AppInstallerCLICore/Commands/McpCommand.cpp @@ -1,83 +1,83 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "McpCommand.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include - -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI -{ - McpCommand::McpCommand(std::string_view parent) : - Command("mcp", {}, parent, Settings::TogglePolicy::Policy::McpServer) - { - } - - std::vector McpCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::ExtendedFeaturesEnable, Resource::String::ExtendedFeaturesEnableMessage, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::ExtendedFeaturesDisable, Resource::String::ExtendedFeaturesDisableMessage, ArgumentType::Flag, Argument::Visibility::Help }, - }; - } - - Resource::LocString McpCommand::ShortDescription() const - { - return { Resource::String::McpCommandShortDescription }; - } - - Resource::LocString McpCommand::LongDescription() const - { - return { Resource::String::McpCommandLongDescription }; - } - - Utility::LocIndView McpCommand::HelpLink() const - { - return "https://aka.ms/winget-command-mcp"_liv; - } - - void McpCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesEnable)) - { - context << - EnableExtendedFeatures; - } - else if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) - { - context << - DisableExtendedFeatures; - } - else - { - context << - VerifyIsFullPackage << - [](Execution::Context& context) - { - std::filesystem::path exePath = Runtime::GetPathTo(Runtime::PathName::MCPExecutable); - std::string jsonCompatiblePath = exePath.u8string(); - Utility::FindAndReplace(jsonCompatiblePath, "\\", "\\\\"); - - context.Reporter.Info() << Resource::String::McpConfigurationPreamble << - R"( - "winget-mcp": { - "type": "stdio", - "command": ")" << jsonCompatiblePath << R"(" - })" << std::endl; - }; - } - } - - void McpCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - if (execArgs.Contains(Execution::Args::Type::ExtendedFeaturesEnable) || - execArgs.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) - { - if (execArgs.GetArgsCount() > 1) - { - throw CommandException(Resource::String::ExtendedFeaturesEnableArgumentError); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "McpCommand.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + McpCommand::McpCommand(std::string_view parent) : + Command("mcp", {}, parent, Settings::TogglePolicy::Policy::McpServer) + { + } + + std::vector McpCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::ExtendedFeaturesEnable, Resource::String::ExtendedFeaturesEnableMessage, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::ExtendedFeaturesDisable, Resource::String::ExtendedFeaturesDisableMessage, ArgumentType::Flag, Argument::Visibility::Help }, + }; + } + + Resource::LocString McpCommand::ShortDescription() const + { + return { Resource::String::McpCommandShortDescription }; + } + + Resource::LocString McpCommand::LongDescription() const + { + return { Resource::String::McpCommandLongDescription }; + } + + Utility::LocIndView McpCommand::HelpLink() const + { + return "https://aka.ms/winget-command-mcp"_liv; + } + + void McpCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesEnable)) + { + context << + EnableExtendedFeatures; + } + else if (context.Args.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) + { + context << + DisableExtendedFeatures; + } + else + { + context << + VerifyIsFullPackage << + [](Execution::Context& context) + { + std::filesystem::path exePath = Runtime::GetPathTo(Runtime::PathName::MCPExecutable); + std::string jsonCompatiblePath = exePath.u8string(); + Utility::FindAndReplace(jsonCompatiblePath, "\\", "\\\\"); + + context.Reporter.Info() << Resource::String::McpConfigurationPreamble << + R"( + "winget-mcp": { + "type": "stdio", + "command": ")" << jsonCompatiblePath << R"(" + })" << std::endl; + }; + } + } + + void McpCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::ExtendedFeaturesEnable) || + execArgs.Contains(Execution::Args::Type::ExtendedFeaturesDisable)) + { + if (execArgs.GetArgsCount() > 1) + { + throw CommandException(Resource::String::ExtendedFeaturesEnableArgumentError); + } + } + } +} diff --git a/src/AppInstallerCLICore/Commands/McpCommand.h b/src/AppInstallerCLICore/Commands/McpCommand.h index a5fa034ab9..c2d82586d7 100644 --- a/src/AppInstallerCLICore/Commands/McpCommand.h +++ b/src/AppInstallerCLICore/Commands/McpCommand.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct McpCommand final : public Command - { - McpCommand(std::string_view parent); - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct McpCommand final : public Command + { + McpCommand(std::string_view parent); + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/PinCommand.cpp b/src/AppInstallerCLICore/Commands/PinCommand.cpp index 9d9219d68f..33df634aa7 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.cpp +++ b/src/AppInstallerCLICore/Commands/PinCommand.cpp @@ -1,346 +1,346 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PinCommand.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/PinFlow.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::CLI::Workflow; - using namespace AppInstaller::Utility::literals; - using namespace std::string_view_literals; - - Utility::LocIndView s_PinCommand_HelpLink = "https://aka.ms/winget-command-pin"_liv; - - std::vector> PinCommand::GetCommands() const - { - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - }); - } - - Resource::LocString PinCommand::ShortDescription() const - { - return { Resource::String::PinCommandShortDescription }; - } - - Resource::LocString PinCommand::LongDescription() const - { - return { Resource::String::PinCommandLongDescription }; - } - - Utility::LocIndView PinCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinCommand::ExecuteInternal(Execution::Context& context) const - { - OutputHelp(context.Reporter); - } - - std::vector PinAddCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Tag), - Argument::ForType(Args::Type::Command), - Argument::ForType(Args::Type::Exact), - Argument{ Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard }, - Argument::ForType(Args::Type::Source), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument::ForType(Args::Type::Force), - Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, - Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, - }; - } - - Resource::LocString PinAddCommand::ShortDescription() const - { - return { Resource::String::PinAddCommandShortDescription }; - } - - Resource::LocString PinAddCommand::LongDescription() const - { - return { Resource::String::PinAddCommandLongDescription }; - } - - void PinAddCommand::Complete(Execution::Context& context, Args::Type valueType) const - { - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Source: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView PinAddCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinAddCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Argument::ValidateCommonArguments(execArgs); - } - - void PinAddCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::Id)) - { - // When we are given an ID, just pin that available package without checking for installed. - // This helps when there are matching issues, for example due to multiple side-by-side installs. - context << - Workflow::OpenSource(); - } - else - { - // If not working from just ID, try matching a single installed package - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - } - - context << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << - Workflow::GetInstalledPackageVersion << - Workflow::ReportPackageIdentity << - Workflow::OpenPinningIndex() << - Workflow::AddPin; - } - - std::vector PinRemoveCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Source), - Argument::ForType(Args::Type::Tag), - Argument::ForType(Args::Type::Command), - Argument::ForType(Args::Type::Exact), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, - }; - } - - Resource::LocString PinRemoveCommand::ShortDescription() const - { - return { Resource::String::PinRemoveCommandShortDescription }; - } - - Resource::LocString PinRemoveCommand::LongDescription() const - { - return { Resource::String::PinRemoveCommandLongDescription }; - } - - void PinRemoveCommand::Complete(Execution::Context& context, Args::Type valueType) const - { - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Source: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView PinRemoveCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinRemoveCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::Id)) - { - // When we are given an ID, just un-pin that available package without checking for installed. - // This helps when there are matching issues, for example due to multiple side-by-side installs. - context << - Workflow::OpenSource(); - } - else - { - // If not working from just ID, try matching a single installed package - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - } - - context << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << - Workflow::GetInstalledPackageVersion << - Workflow::ReportPackageIdentity << - Workflow::OpenPinningIndex() << - Workflow::SearchPin << - Workflow::RemovePin; - } - - std::vector PinListCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Source), - Argument::ForType(Args::Type::Tag), - Argument::ForType(Args::Type::Command), - Argument::ForType(Args::Type::Exact), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::AcceptSourceAgreements), - }; - } - - Resource::LocString PinListCommand::ShortDescription() const - { - return { Resource::String::PinListCommandShortDescription }; - } - - Resource::LocString PinListCommand::LongDescription() const - { - return { Resource::String::PinListCommandLongDescription }; - } - - void PinListCommand::Complete(Execution::Context& context, Args::Type valueType) const - { - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Source: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView PinListCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinListCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::OpenPinningIndex(/* readOnly */ true) << - Workflow::GetAllPins << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, false, Repository::CompositeSearchBehavior::AllPackages) << - Workflow::ReportPins; - } - - std::vector PinResetCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Force), - Argument::ForType(Args::Type::Source), - }; - } - - Resource::LocString PinResetCommand::ShortDescription() const - { - return { Resource::String::PinResetCommandShortDescription }; - } - - Resource::LocString PinResetCommand::LongDescription() const - { - return { Resource::String::PinResetCommandLongDescription }; - } - - Utility::LocIndView PinResetCommand::HelpLink() const - { - return s_PinCommand_HelpLink; - } - - void PinResetCommand::ExecuteInternal(Execution::Context& context) const - { - - if (context.Args.Contains(Execution::Args::Type::Force)) - { - context << - Workflow::OpenPinningIndex() << - Workflow::ResetAllPins; - } - else - { - AICLI_LOG(CLI, Info, << "--force argument is not present"); - context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; - - context << - Workflow::OpenPinningIndex(/* readOnly */ true) << - Workflow::GetAllPins << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << - Workflow::ReportPins; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinCommand.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/PinFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + using namespace AppInstaller::Utility::literals; + using namespace std::string_view_literals; + + Utility::LocIndView s_PinCommand_HelpLink = "https://aka.ms/winget-command-pin"_liv; + + std::vector> PinCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + Resource::LocString PinCommand::ShortDescription() const + { + return { Resource::String::PinCommandShortDescription }; + } + + Resource::LocString PinCommand::LongDescription() const + { + return { Resource::String::PinCommandLongDescription }; + } + + Utility::LocIndView PinCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinCommand::ExecuteInternal(Execution::Context& context) const + { + OutputHelp(context.Reporter); + } + + std::vector PinAddCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument{ Args::Type::GatedVersion, Resource::String::GatedVersionArgumentDescription, ArgumentType::Standard }, + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::Force), + Argument{ Args::Type::BlockingPin, Resource::String::PinAddBlockingArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString PinAddCommand::ShortDescription() const + { + return { Resource::String::PinAddCommandShortDescription }; + } + + Resource::LocString PinAddCommand::LongDescription() const + { + return { Resource::String::PinAddCommandLongDescription }; + } + + void PinAddCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView PinAddCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinAddCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void PinAddCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Id)) + { + // When we are given an ID, just pin that available package without checking for installed. + // This helps when there are matching issues, for example due to multiple side-by-side installs. + context << + Workflow::OpenSource(); + } + else + { + // If not working from just ID, try matching a single installed package + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + } + + context << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << + Workflow::GetInstalledPackageVersion << + Workflow::ReportPackageIdentity << + Workflow::OpenPinningIndex() << + Workflow::AddPin; + } + + std::vector PinRemoveCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument{ Args::Type::PinInstalled, Resource::String::PinInstalledArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString PinRemoveCommand::ShortDescription() const + { + return { Resource::String::PinRemoveCommandShortDescription }; + } + + Resource::LocString PinRemoveCommand::LongDescription() const + { + return { Resource::String::PinRemoveCommandLongDescription }; + } + + void PinRemoveCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView PinRemoveCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinRemoveCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Id)) + { + // When we are given an ID, just un-pin that available package without checking for installed. + // This helps when there are matching issues, for example due to multiple side-by-side installs. + context << + Workflow::OpenSource(); + } + else + { + // If not working from just ID, try matching a single installed package + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + } + + context << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Pin) << + Workflow::GetInstalledPackageVersion << + Workflow::ReportPackageIdentity << + Workflow::OpenPinningIndex() << + Workflow::SearchPin << + Workflow::RemovePin; + } + + std::vector PinListCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Source), + Argument::ForType(Args::Type::Tag), + Argument::ForType(Args::Type::Command), + Argument::ForType(Args::Type::Exact), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::AcceptSourceAgreements), + }; + } + + Resource::LocString PinListCommand::ShortDescription() const + { + return { Resource::String::PinListCommandShortDescription }; + } + + Resource::LocString PinListCommand::LongDescription() const + { + return { Resource::String::PinListCommandLongDescription }; + } + + void PinListCommand::Complete(Execution::Context& context, Args::Type valueType) const + { + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Source: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView PinListCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinListCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::OpenPinningIndex(/* readOnly */ true) << + Workflow::GetAllPins << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, false, Repository::CompositeSearchBehavior::AllPackages) << + Workflow::ReportPins; + } + + std::vector PinResetCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Force), + Argument::ForType(Args::Type::Source), + }; + } + + Resource::LocString PinResetCommand::ShortDescription() const + { + return { Resource::String::PinResetCommandShortDescription }; + } + + Resource::LocString PinResetCommand::LongDescription() const + { + return { Resource::String::PinResetCommandLongDescription }; + } + + Utility::LocIndView PinResetCommand::HelpLink() const + { + return s_PinCommand_HelpLink; + } + + void PinResetCommand::ExecuteInternal(Execution::Context& context) const + { + + if (context.Args.Contains(Execution::Args::Type::Force)) + { + context << + Workflow::OpenPinningIndex() << + Workflow::ResetAllPins; + } + else + { + AICLI_LOG(CLI, Info, << "--force argument is not present"); + context.Reporter.Info() << Resource::String::PinResetUseForceArg << std::endl; + + context << + Workflow::OpenPinningIndex(/* readOnly */ true) << + Workflow::GetAllPins << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed) << + Workflow::ReportPins; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/PinCommand.h b/src/AppInstallerCLICore/Commands/PinCommand.h index 285e66eaa7..fefedcbe77 100644 --- a/src/AppInstallerCLICore/Commands/PinCommand.h +++ b/src/AppInstallerCLICore/Commands/PinCommand.h @@ -1,90 +1,90 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" -#include - -namespace AppInstaller::CLI -{ - struct PinCommand final : public Command - { - PinCommand(std::string_view parent) : Command("pin", {} /* aliases */, parent) {} - - std::vector> GetCommands() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinAddCommand final : public Command - { - PinAddCommand(std::string_view parent) : Command("add", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinRemoveCommand final : public Command - { - PinRemoveCommand(std::string_view parent) : Command("remove", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinListCommand final : public Command - { - PinListCommand(std::string_view parent) : Command("list", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct PinResetCommand final : public Command - { - PinResetCommand(std::string_view parent) : Command("reset", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include + +namespace AppInstaller::CLI +{ + struct PinCommand final : public Command + { + PinCommand(std::string_view parent) : Command("pin", {} /* aliases */, parent) {} + + std::vector> GetCommands() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinAddCommand final : public Command + { + PinAddCommand(std::string_view parent) : Command("add", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinRemoveCommand final : public Command + { + PinRemoveCommand(std::string_view parent) : Command("remove", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinListCommand final : public Command + { + PinListCommand(std::string_view parent) : Command("list", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct PinResetCommand final : public Command + { + PinResetCommand(std::string_view parent) : Command("reset", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/RepairCommand.cpp b/src/AppInstallerCLICore/Commands/RepairCommand.cpp index 3df1a6a0e6..1afeded43b 100644 --- a/src/AppInstallerCLICore/Commands/RepairCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RepairCommand.cpp @@ -1,125 +1,125 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RepairCommand.h" -#include "Workflows/RepairFlow.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/DownloadFlow.h" -#include "Workflows/InstallFlow.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::CLI::Workflow; - - std::vector RepairCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::Query), // -q - Argument::ForType(Args::Type::Manifest), // -m - Argument::ForType(Args::Type::Id), // -id - Argument::ForType(Args::Type::Name), // -n - Argument::ForType(Args::Type::Channel), - Argument::ForType(Args::Type::Moniker), // -mn - Argument::ForType(Args::Type::TargetVersion), // -v - Argument::ForType(Args::Type::ProductCode), - Argument::ForType(Args::Type::InstallArchitecture), // -arch - Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Args::Type::Source), // -s - Argument::ForType(Args::Type::Interactive), // -i - Argument::ForType(Args::Type::Silent), // -h - Argument::ForType(Args::Type::Log), // -o - Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan), // -ignore-local-archive-malware-scan - Argument::ForType(Args::Type::AcceptSourceAgreements), // -accept-source-agreements - Argument::ForType(Args::Type::AcceptPackageAgreements), - Argument::ForType(Args::Type::Locale), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument::ForType(Args::Type::Force), - Argument::ForType(Args::Type::HashOverride), - Argument::ForType(Args::Type::Exact), - }; - } - - Resource::LocString RepairCommand::ShortDescription() const - { - return { Resource::String::RepairCommandShortDescription }; - } - - Resource::LocString RepairCommand::LongDescription() const - { - return { Resource::String::RepairCommandLongDescription }; - } - - void RepairCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const - { - if (valueType == Execution::Args::Type::Manifest || - valueType == Execution::Args::Type::Log) - { - // Intentionally output nothing to allow pass through to filesystem. - return; - } - - context << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::TargetVersion: - case Execution::Args::Type::Channel: - case Execution::Args::Type::Source: - context << - Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - } - } - - Utility::LocIndView RepairCommand::HelpLink() const - { - // TODO: point to the right place - return "https://aka.ms/winget-command-repair"_liv; - } - - void RepairCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Argument::ValidateCommonArguments(execArgs); - } - - void RepairCommand::ExecuteInternal(Execution::Context& context) const - { - context.SetFlags(Execution::ContextFlag::InstallerExecutionUseRepair); - - context << - Workflow::InitializeInstallerDownloadAuthenticatorsMap << - Workflow::ReportExecutionStage(ExecutionStage::Discovery) << - Workflow::OpenSource() << - Workflow::OpenCompositeSource(DetermineInstalledSource(context)); - - if (context.Args.Contains(Args::Type::Manifest)) - { - context << - Workflow::GetManifestFromArg << - Workflow::ReportManifestIdentity << - Workflow::SearchSourceUsingManifest << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair); - } - else - { - context << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair) << - Workflow::ReportPackageIdentity; - } - - context << - Workflow::GetInstalledPackageVersion << - Workflow::SelectApplicableInstallerIfNecessary << - Workflow::RepairSinglePackage; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RepairCommand.h" +#include "Workflows/RepairFlow.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/DownloadFlow.h" +#include "Workflows/InstallFlow.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + + std::vector RepairCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::Query), // -q + Argument::ForType(Args::Type::Manifest), // -m + Argument::ForType(Args::Type::Id), // -id + Argument::ForType(Args::Type::Name), // -n + Argument::ForType(Args::Type::Channel), + Argument::ForType(Args::Type::Moniker), // -mn + Argument::ForType(Args::Type::TargetVersion), // -v + Argument::ForType(Args::Type::ProductCode), + Argument::ForType(Args::Type::InstallArchitecture), // -arch + Argument{ Execution::Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Args::Type::Source), // -s + Argument::ForType(Args::Type::Interactive), // -i + Argument::ForType(Args::Type::Silent), // -h + Argument::ForType(Args::Type::Log), // -o + Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan), // -ignore-local-archive-malware-scan + Argument::ForType(Args::Type::AcceptSourceAgreements), // -accept-source-agreements + Argument::ForType(Args::Type::AcceptPackageAgreements), + Argument::ForType(Args::Type::Locale), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument::ForType(Args::Type::Force), + Argument::ForType(Args::Type::HashOverride), + Argument::ForType(Args::Type::Exact), + }; + } + + Resource::LocString RepairCommand::ShortDescription() const + { + return { Resource::String::RepairCommandShortDescription }; + } + + Resource::LocString RepairCommand::LongDescription() const + { + return { Resource::String::RepairCommandLongDescription }; + } + + void RepairCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const + { + if (valueType == Execution::Args::Type::Manifest || + valueType == Execution::Args::Type::Log) + { + // Intentionally output nothing to allow pass through to filesystem. + return; + } + + context << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::TargetVersion: + case Execution::Args::Type::Channel: + case Execution::Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + } + } + + Utility::LocIndView RepairCommand::HelpLink() const + { + // TODO: point to the right place + return "https://aka.ms/winget-command-repair"_liv; + } + + void RepairCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void RepairCommand::ExecuteInternal(Execution::Context& context) const + { + context.SetFlags(Execution::ContextFlag::InstallerExecutionUseRepair); + + context << + Workflow::InitializeInstallerDownloadAuthenticatorsMap << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << + Workflow::OpenSource() << + Workflow::OpenCompositeSource(DetermineInstalledSource(context)); + + if (context.Args.Contains(Args::Type::Manifest)) + { + context << + Workflow::GetManifestFromArg << + Workflow::ReportManifestIdentity << + Workflow::SearchSourceUsingManifest << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair); + } + else + { + context << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Repair) << + Workflow::ReportPackageIdentity; + } + + context << + Workflow::GetInstalledPackageVersion << + Workflow::SelectApplicableInstallerIfNecessary << + Workflow::RepairSinglePackage; + } +} diff --git a/src/AppInstallerCLICore/Commands/RepairCommand.h b/src/AppInstallerCLICore/Commands/RepairCommand.h index 58da8c80c5..fd934fcde5 100644 --- a/src/AppInstallerCLICore/Commands/RepairCommand.h +++ b/src/AppInstallerCLICore/Commands/RepairCommand.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct RepairCommand final : public Command - { - RepairCommand(std::string_view parent) : Command("repair", { "fix" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct RepairCommand final : public Command + { + RepairCommand(std::string_view parent) : Command("repair", { "fix" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/RootCommand.cpp b/src/AppInstallerCLICore/Commands/RootCommand.cpp index 39dc8a8ac2..1f472e3676 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.cpp +++ b/src/AppInstallerCLICore/Commands/RootCommand.cpp @@ -1,294 +1,294 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RootCommand.h" -#include - -#include "InstallCommand.h" -#include "ShowCommand.h" -#include "SourceCommand.h" -#include "SearchCommand.h" -#include "ListCommand.h" -#include "UpgradeCommand.h" -#include "UninstallCommand.h" -#include "HashCommand.h" -#include "ValidateCommand.h" -#include "SettingsCommand.h" -#include "FeaturesCommand.h" -#include "FontCommand.h" -#include "ExperimentalCommand.h" -#include "CompleteCommand.h" -#include "ExportCommand.h" -#include "ImportCommand.h" -#include "PinCommand.h" -#include "ConfigureCommand.h" -#include "DebugCommand.h" -#include "TestCommand.h" -#include "DownloadCommand.h" -#include "ErrorCommand.h" -#include "ResumeCommand.h" -#include "RepairCommand.h" -#include "DscCommand.h" -#include "McpCommand.h" - -#include "Resources.h" -#include "TableOutput.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::Utility::literals; - using namespace Settings; - - namespace - { - void OutputGroupPolicySourceList(Execution::Context& context, const std::vector& sources, Resource::StringId header) - { - Execution::TableOutput<3> sourcesTable{ context.Reporter, { header, Resource::String::SourceListType, Resource::String::SourceListArg } }; - for (const auto& source : sources) - { - sourcesTable.OutputLine({ source.Name, source.Type, source.Arg }); - } - - sourcesTable.Complete(); - } - - void OutputGroupPolicies(Execution::Context& context) - { - const auto& groupPolicies = Settings::GroupPolicies(); - - // Get the state of policies that are a simple enabled/disabled toggle - std::map activePolicies; - for (const auto& togglePolicy : Settings::TogglePolicy::GetAllPolicies()) - { - auto state = groupPolicies.GetState(togglePolicy.GetPolicy()); - if (state != Settings::PolicyState::NotConfigured) - { - activePolicies[togglePolicy.GetPolicy()] = state; - } - } - - // The source update interval is the only ValuePolicy that is not gated by a TogglePolicy. - // We need to output the table if there is a TogglePolicy configured or if this one is configured. - // We can rework this when more policies are added. - auto sourceAutoUpdateIntervalPolicy = groupPolicies.GetValue(); - - if (!activePolicies.empty() || sourceAutoUpdateIntervalPolicy.has_value()) - { - auto info = context.Reporter.Info(); - info << std::endl; - - Execution::TableOutput<2> policiesTable{ context.Reporter, { Resource::String::PoliciesPolicy, Resource::String::StateHeader } }; - - // Output the toggle policies. - for (const auto& activePolicy : activePolicies) - { - auto policy = Settings::TogglePolicy::GetPolicy(activePolicy.first); - policiesTable.OutputLine({ - Resource::LocString{ policy.PolicyName() }.get(), - Resource::LocString{ activePolicy.second == Settings::PolicyState::Enabled ? Resource::String::StateEnabled : Resource::String::StateDisabled }.get() }); - } - - // Output the update interval in the same table if needed. - if (sourceAutoUpdateIntervalPolicy.has_value()) - { - policiesTable.OutputLine({ - Resource::LocString{ AppInstaller::StringResource::String::PolicySourceAutoUpdateInterval }, - std::to_string(sourceAutoUpdateIntervalPolicy.value()) }); - } - - policiesTable.Complete(); - - // Output the additional and allowed sources as separate tables. - if (groupPolicies.GetState(Settings::TogglePolicy::Policy::AdditionalSources) == Settings::PolicyState::Enabled) - { - info << std::endl; - auto sources = groupPolicies.GetValue(); - if (sources.has_value() && !sources->empty()) - { - OutputGroupPolicySourceList(context, sources.value(), Resource::String::SourceListAdditionalSource); - } - } - - if (groupPolicies.GetState(Settings::TogglePolicy::Policy::AllowedSources) == Settings::PolicyState::Enabled) - { - info << std::endl; - auto sources = groupPolicies.GetValue(); - if (sources.has_value() && !sources->empty()) - { - OutputGroupPolicySourceList(context, sources.value(), Resource::String::SourceListAllowedSource); - } - } - info << std::endl; - } - } - - void OutputAdminSettings(Execution::Context& context) - { - Execution::TableOutput<2> adminSettingsTable{ context.Reporter, { Resource::String::AdminSettingHeader, Resource::String::StateHeader } }; - - // Output the admin settings. - for (const auto& setting : Settings::GetAllBoolAdminSettings()) - { - adminSettingsTable.OutputLine({ - std::string{ AdminSettingToString(setting)}, - Resource::LocString{ IsAdminSettingEnabled(setting) ? Resource::String::StateEnabled : Resource::String::StateDisabled } - }); - } - for (const auto& setting : Settings::GetAllStringAdminSettings()) - { - auto settingValue = GetAdminSetting(setting); - adminSettingsTable.OutputLine({ - std::string{ AdminSettingToString(setting)}, - settingValue ? Utility::LocIndString{ settingValue.value() } : Resource::LocString{ Resource::String::StateDisabled } - }); - } - adminSettingsTable.Complete(); - } - - void OutputKeyDirectories(Execution::Context& context) - { - Execution::TableOutput<2> keyDirectories{ context.Reporter, { Resource::String::KeyDirectoriesHeader, {} } }; - keyDirectories.OutputLine({ Resource::LocString{ Resource::String::Logs }, Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation, true).u8string() }); - keyDirectories.OutputLine({ Resource::LocString{ Resource::String::UserSettings }, UserSettings::SettingsFilePath(true).u8string() }); - keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableLinksUser }, Runtime::GetPathTo(Runtime::PathName::PortableLinksUserLocation, true).u8string() }); - keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableLinksMachine }, Runtime::GetPathTo(Runtime::PathName::PortableLinksMachineLocation, true).u8string() }); - keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableRootUser }, Runtime::GetPathTo(Runtime::PathName::PortablePackageUserRoot, true).u8string() }); - 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; - } - - void OutputLinks(Execution::Context& context) - { - Execution::TableOutput<2> links{ context.Reporter, { Resource::String::Links, {} } }; - links.OutputLine({ Resource::LocString{ Resource::String::PrivacyStatement }, "https://aka.ms/winget-privacy" }); - links.OutputLine({ Resource::LocString{ Resource::String::LicenseAgreement }, "https://aka.ms/winget-license" }); - links.OutputLine({ Resource::LocString{ Resource::String::ThirdPartSoftwareNotices }, "https://aka.ms/winget-3rdPartyNotice" }); - links.OutputLine({ Resource::LocString{ Resource::String::MainHomepage }, "https://aka.ms/winget" }); - links.OutputLine({ Resource::LocString{ Resource::String::WindowsStoreTerms }, "https://www.microsoft.com/en-us/storedocs/terms-of-sale" }); - links.Complete(); - context.Reporter.Info() << std::endl; - } - } - - std::vector> RootCommand::GetCommands() const - { - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), -#if _DEBUG - std::make_unique(FullName()), -#endif -#ifndef AICLI_DISABLE_TEST_HOOKS - std::make_unique(FullName()), -#endif - }); - } - - std::vector RootCommand::GetArguments() const - { - return - { - Argument{ Execution::Args::Type::ToolVersion, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, - }; - } - - Resource::LocString RootCommand::ShortDescription() const - { - return {}; - } - - Resource::LocString RootCommand::LongDescription() const - { - return { Resource::String::ToolDescription }; - } - - Utility::LocIndView RootCommand::HelpLink() const - { - return "https://aka.ms/winget-command-help"_liv; - } - - void RootCommand::Execute(Execution::Context& context) const - { - AICLI_LOG(CLI, Info, << "Executing command: " << Name()); - if (context.Args.Contains(Execution::Args::Type::Help)) - { - OutputHelp(context.Reporter); - } - else - { - ExecuteInternal(context); - } - - if (context.Args.Contains(Execution::Args::Type::OpenLogs)) - { - ShellExecute(NULL, NULL, Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation).wstring().c_str(), NULL, NULL, SW_SHOWNORMAL); - } - - if (context.Args.Contains(Execution::Args::Type::Wait)) - { - context.Reporter.PromptForEnter(); - } - } - - void RootCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::Info)) - { - OutputIntroHeader(context.Reporter); - - auto info = context.Reporter.Info(); - - info << std::endl << - "Windows: "_liv << Runtime::GetOSVersion() << std::endl; - - info << Resource::String::SystemArchitecture(Utility::ToString(Utility::GetSystemArchitecture())) << std::endl; - - if (Runtime::IsRunningInPackagedContext()) - { - info << Resource::String::Package(Runtime::GetPackageVersion()) << std::endl; - }; - - info << std::endl; - - OutputKeyDirectories(context); - OutputLinks(context); - OutputGroupPolicies(context); - OutputAdminSettings(context); - } - else if (context.Args.Contains(Execution::Args::Type::ToolVersion)) - { - context.Reporter.Info() << 'v' << Runtime::GetClientVersion() << std::endl; - } - else - { - OutputHelp(context.Reporter); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RootCommand.h" +#include + +#include "InstallCommand.h" +#include "ShowCommand.h" +#include "SourceCommand.h" +#include "SearchCommand.h" +#include "ListCommand.h" +#include "UpgradeCommand.h" +#include "UninstallCommand.h" +#include "HashCommand.h" +#include "ValidateCommand.h" +#include "SettingsCommand.h" +#include "FeaturesCommand.h" +#include "FontCommand.h" +#include "ExperimentalCommand.h" +#include "CompleteCommand.h" +#include "ExportCommand.h" +#include "ImportCommand.h" +#include "PinCommand.h" +#include "ConfigureCommand.h" +#include "DebugCommand.h" +#include "TestCommand.h" +#include "DownloadCommand.h" +#include "ErrorCommand.h" +#include "ResumeCommand.h" +#include "RepairCommand.h" +#include "DscCommand.h" +#include "McpCommand.h" + +#include "Resources.h" +#include "TableOutput.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::Utility::literals; + using namespace Settings; + + namespace + { + void OutputGroupPolicySourceList(Execution::Context& context, const std::vector& sources, Resource::StringId header) + { + Execution::TableOutput<3> sourcesTable{ context.Reporter, { header, Resource::String::SourceListType, Resource::String::SourceListArg } }; + for (const auto& source : sources) + { + sourcesTable.OutputLine({ source.Name, source.Type, source.Arg }); + } + + sourcesTable.Complete(); + } + + void OutputGroupPolicies(Execution::Context& context) + { + const auto& groupPolicies = Settings::GroupPolicies(); + + // Get the state of policies that are a simple enabled/disabled toggle + std::map activePolicies; + for (const auto& togglePolicy : Settings::TogglePolicy::GetAllPolicies()) + { + auto state = groupPolicies.GetState(togglePolicy.GetPolicy()); + if (state != Settings::PolicyState::NotConfigured) + { + activePolicies[togglePolicy.GetPolicy()] = state; + } + } + + // The source update interval is the only ValuePolicy that is not gated by a TogglePolicy. + // We need to output the table if there is a TogglePolicy configured or if this one is configured. + // We can rework this when more policies are added. + auto sourceAutoUpdateIntervalPolicy = groupPolicies.GetValue(); + + if (!activePolicies.empty() || sourceAutoUpdateIntervalPolicy.has_value()) + { + auto info = context.Reporter.Info(); + info << std::endl; + + Execution::TableOutput<2> policiesTable{ context.Reporter, { Resource::String::PoliciesPolicy, Resource::String::StateHeader } }; + + // Output the toggle policies. + for (const auto& activePolicy : activePolicies) + { + auto policy = Settings::TogglePolicy::GetPolicy(activePolicy.first); + policiesTable.OutputLine({ + Resource::LocString{ policy.PolicyName() }.get(), + Resource::LocString{ activePolicy.second == Settings::PolicyState::Enabled ? Resource::String::StateEnabled : Resource::String::StateDisabled }.get() }); + } + + // Output the update interval in the same table if needed. + if (sourceAutoUpdateIntervalPolicy.has_value()) + { + policiesTable.OutputLine({ + Resource::LocString{ AppInstaller::StringResource::String::PolicySourceAutoUpdateInterval }, + std::to_string(sourceAutoUpdateIntervalPolicy.value()) }); + } + + policiesTable.Complete(); + + // Output the additional and allowed sources as separate tables. + if (groupPolicies.GetState(Settings::TogglePolicy::Policy::AdditionalSources) == Settings::PolicyState::Enabled) + { + info << std::endl; + auto sources = groupPolicies.GetValue(); + if (sources.has_value() && !sources->empty()) + { + OutputGroupPolicySourceList(context, sources.value(), Resource::String::SourceListAdditionalSource); + } + } + + if (groupPolicies.GetState(Settings::TogglePolicy::Policy::AllowedSources) == Settings::PolicyState::Enabled) + { + info << std::endl; + auto sources = groupPolicies.GetValue(); + if (sources.has_value() && !sources->empty()) + { + OutputGroupPolicySourceList(context, sources.value(), Resource::String::SourceListAllowedSource); + } + } + info << std::endl; + } + } + + void OutputAdminSettings(Execution::Context& context) + { + Execution::TableOutput<2> adminSettingsTable{ context.Reporter, { Resource::String::AdminSettingHeader, Resource::String::StateHeader } }; + + // Output the admin settings. + for (const auto& setting : Settings::GetAllBoolAdminSettings()) + { + adminSettingsTable.OutputLine({ + std::string{ AdminSettingToString(setting)}, + Resource::LocString{ IsAdminSettingEnabled(setting) ? Resource::String::StateEnabled : Resource::String::StateDisabled } + }); + } + for (const auto& setting : Settings::GetAllStringAdminSettings()) + { + auto settingValue = GetAdminSetting(setting); + adminSettingsTable.OutputLine({ + std::string{ AdminSettingToString(setting)}, + settingValue ? Utility::LocIndString{ settingValue.value() } : Resource::LocString{ Resource::String::StateDisabled } + }); + } + adminSettingsTable.Complete(); + } + + void OutputKeyDirectories(Execution::Context& context) + { + Execution::TableOutput<2> keyDirectories{ context.Reporter, { Resource::String::KeyDirectoriesHeader, {} } }; + keyDirectories.OutputLine({ Resource::LocString{ Resource::String::Logs }, Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation, true).u8string() }); + keyDirectories.OutputLine({ Resource::LocString{ Resource::String::UserSettings }, UserSettings::SettingsFilePath(true).u8string() }); + keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableLinksUser }, Runtime::GetPathTo(Runtime::PathName::PortableLinksUserLocation, true).u8string() }); + keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableLinksMachine }, Runtime::GetPathTo(Runtime::PathName::PortableLinksMachineLocation, true).u8string() }); + keyDirectories.OutputLine({ Resource::LocString{ Resource::String::PortableRootUser }, Runtime::GetPathTo(Runtime::PathName::PortablePackageUserRoot, true).u8string() }); + 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; + } + + void OutputLinks(Execution::Context& context) + { + Execution::TableOutput<2> links{ context.Reporter, { Resource::String::Links, {} } }; + links.OutputLine({ Resource::LocString{ Resource::String::PrivacyStatement }, "https://aka.ms/winget-privacy" }); + links.OutputLine({ Resource::LocString{ Resource::String::LicenseAgreement }, "https://aka.ms/winget-license" }); + links.OutputLine({ Resource::LocString{ Resource::String::ThirdPartSoftwareNotices }, "https://aka.ms/winget-3rdPartyNotice" }); + links.OutputLine({ Resource::LocString{ Resource::String::MainHomepage }, "https://aka.ms/winget" }); + links.OutputLine({ Resource::LocString{ Resource::String::WindowsStoreTerms }, "https://www.microsoft.com/en-us/storedocs/terms-of-sale" }); + links.Complete(); + context.Reporter.Info() << std::endl; + } + } + + std::vector> RootCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), +#if _DEBUG + std::make_unique(FullName()), +#endif +#ifndef AICLI_DISABLE_TEST_HOOKS + std::make_unique(FullName()), +#endif + }); + } + + std::vector RootCommand::GetArguments() const + { + return + { + Argument{ Execution::Args::Type::ToolVersion, Resource::String::ToolVersionArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::Info, Resource::String::ToolInfoArgumentDescription, ArgumentType::Flag, Argument::Visibility::Help }, + }; + } + + Resource::LocString RootCommand::ShortDescription() const + { + return {}; + } + + Resource::LocString RootCommand::LongDescription() const + { + return { Resource::String::ToolDescription }; + } + + Utility::LocIndView RootCommand::HelpLink() const + { + return "https://aka.ms/winget-command-help"_liv; + } + + void RootCommand::Execute(Execution::Context& context) const + { + AICLI_LOG(CLI, Info, << "Executing command: " << Name()); + if (context.Args.Contains(Execution::Args::Type::Help)) + { + OutputHelp(context.Reporter); + } + else + { + ExecuteInternal(context); + } + + if (context.Args.Contains(Execution::Args::Type::OpenLogs)) + { + ShellExecute(NULL, NULL, Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation).wstring().c_str(), NULL, NULL, SW_SHOWNORMAL); + } + + if (context.Args.Contains(Execution::Args::Type::Wait)) + { + context.Reporter.PromptForEnter(); + } + } + + void RootCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Info)) + { + OutputIntroHeader(context.Reporter); + + auto info = context.Reporter.Info(); + + info << std::endl << + "Windows: "_liv << Runtime::GetOSVersion() << std::endl; + + info << Resource::String::SystemArchitecture(Utility::ToString(Utility::GetSystemArchitecture())) << std::endl; + + if (Runtime::IsRunningInPackagedContext()) + { + info << Resource::String::Package(Runtime::GetPackageVersion()) << std::endl; + }; + + info << std::endl; + + OutputKeyDirectories(context); + OutputLinks(context); + OutputGroupPolicies(context); + OutputAdminSettings(context); + } + else if (context.Args.Contains(Execution::Args::Type::ToolVersion)) + { + context.Reporter.Info() << 'v' << Runtime::GetClientVersion() << std::endl; + } + else + { + OutputHelp(context.Reporter); + } + } +} diff --git a/src/AppInstallerCLICore/Commands/RootCommand.h b/src/AppInstallerCLICore/Commands/RootCommand.h index c73a812f7b..af615ec780 100644 --- a/src/AppInstallerCLICore/Commands/RootCommand.h +++ b/src/AppInstallerCLICore/Commands/RootCommand.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct RootCommand final : public Command - { - constexpr static std::string_view CommandName = "root"sv; - - RootCommand() : Command(CommandName, {}) {} - - std::vector> GetCommands() const override; - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - void Execute(Execution::Context& context) const override; - - protected: - virtual void ExecuteInternal(Execution::Context& context) const; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct RootCommand final : public Command + { + constexpr static std::string_view CommandName = "root"sv; + + RootCommand() : Command(CommandName, {}) {} + + std::vector> GetCommands() const override; + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + void Execute(Execution::Context& context) const override; + + protected: + virtual void ExecuteInternal(Execution::Context& context) const; + }; +} diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.cpp b/src/AppInstallerCLICore/Commands/SearchCommand.cpp index 0ae547d302..32b47b93b5 100644 --- a/src/AppInstallerCLICore/Commands/SearchCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SearchCommand.cpp @@ -1,102 +1,102 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchCommand.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::CLI::Workflow; - using namespace std::string_view_literals; - - std::vector SearchCommand::GetArguments() const - { - return { - Argument::ForType(Execution::Args::Type::Query), - Argument::ForType(Execution::Args::Type::Id), - Argument::ForType(Execution::Args::Type::Name), - Argument::ForType(Execution::Args::Type::Moniker), - Argument::ForType(Execution::Args::Type::Tag), - Argument::ForType(Execution::Args::Type::Command), - Argument::ForType(Execution::Args::Type::Source), - Argument::ForType(Execution::Args::Type::Count), - Argument::ForType(Execution::Args::Type::Exact), - Argument::ForType(Execution::Args::Type::CustomHeader), - Argument::ForType(Execution::Args::Type::AuthenticationMode), - Argument::ForType(Execution::Args::Type::AuthenticationAccount), - Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), - Argument::ForType(Execution::Args::Type::ListVersions), - }; - } - - Resource::LocString SearchCommand::ShortDescription() const - { - return { Resource::String::SearchCommandShortDescription }; - } - - Resource::LocString SearchCommand::LongDescription() const - { - return { Resource::String::SearchCommandLongDescription }; - } - - void SearchCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const - { - switch (valueType) - { - case Execution::Args::Type::Query: - context << - Workflow::OpenSource() << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForManyCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - case Execution::Args::Type::Source: - context << - Workflow::CompleteWithSingleSemanticsForValue(valueType); - break; - } - } - - Utility::LocIndView SearchCommand::HelpLink() const - { - return "https://aka.ms/winget-command-search"_liv; - } - - void SearchCommand::ValidateArgumentsInternal(Args& execArgs) const - { - Argument::ValidateCommonArguments(execArgs); - } - - void SearchCommand::ExecuteInternal(Context& context) const - { - context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); - - context << - Workflow::OpenSource() << - Workflow::SearchSourceForMany << - Workflow::HandleSearchResultFailures; - - if (context.Args.Contains(Execution::Args::Type::ListVersions)) - { - context << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Search) << - Workflow::ReportPackageIdentity << - Workflow::ShowAppVersions; - } - else - { - context << - Workflow::EnsureMatchesFromSearchResult(OperationType::Search) << - Workflow::ReportSearchResult; - } - - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchCommand.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + using namespace std::string_view_literals; + + std::vector SearchCommand::GetArguments() const + { + return { + Argument::ForType(Execution::Args::Type::Query), + Argument::ForType(Execution::Args::Type::Id), + Argument::ForType(Execution::Args::Type::Name), + Argument::ForType(Execution::Args::Type::Moniker), + Argument::ForType(Execution::Args::Type::Tag), + Argument::ForType(Execution::Args::Type::Command), + Argument::ForType(Execution::Args::Type::Source), + Argument::ForType(Execution::Args::Type::Count), + Argument::ForType(Execution::Args::Type::Exact), + Argument::ForType(Execution::Args::Type::CustomHeader), + Argument::ForType(Execution::Args::Type::AuthenticationMode), + Argument::ForType(Execution::Args::Type::AuthenticationAccount), + Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), + Argument::ForType(Execution::Args::Type::ListVersions), + }; + } + + Resource::LocString SearchCommand::ShortDescription() const + { + return { Resource::String::SearchCommandShortDescription }; + } + + Resource::LocString SearchCommand::LongDescription() const + { + return { Resource::String::SearchCommandLongDescription }; + } + + void SearchCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const + { + switch (valueType) + { + case Execution::Args::Type::Query: + context << + Workflow::OpenSource() << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForManyCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + case Execution::Args::Type::Source: + context << + Workflow::CompleteWithSingleSemanticsForValue(valueType); + break; + } + } + + Utility::LocIndView SearchCommand::HelpLink() const + { + return "https://aka.ms/winget-command-search"_liv; + } + + void SearchCommand::ValidateArgumentsInternal(Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void SearchCommand::ExecuteInternal(Context& context) const + { + context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); + + context << + Workflow::OpenSource() << + Workflow::SearchSourceForMany << + Workflow::HandleSearchResultFailures; + + if (context.Args.Contains(Execution::Args::Type::ListVersions)) + { + context << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Search) << + Workflow::ReportPackageIdentity << + Workflow::ShowAppVersions; + } + else + { + context << + Workflow::EnsureMatchesFromSearchResult(OperationType::Search) << + Workflow::ReportSearchResult; + } + + } +} diff --git a/src/AppInstallerCLICore/Commands/SearchCommand.h b/src/AppInstallerCLICore/Commands/SearchCommand.h index 289147c422..2674a7dbf5 100644 --- a/src/AppInstallerCLICore/Commands/SearchCommand.h +++ b/src/AppInstallerCLICore/Commands/SearchCommand.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - - struct SearchCommand final : public Command - { - SearchCommand(std::string_view parent) : Command("search", { "find" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + + struct SearchCommand final : public Command + { + SearchCommand(std::string_view parent) : Command("search", { "find" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp index e27b69e26c..05889803a6 100644 --- a/src/AppInstallerCLICore/Commands/SettingsCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SettingsCommand.cpp @@ -1,229 +1,229 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SettingsCommand.h" -#include "Workflows/WorkflowBase.h" -#include "Workflows/SettingsFlow.h" - -namespace AppInstaller::CLI -{ - using namespace Utility::literals; - using namespace AppInstaller::Settings; - using namespace std::string_view_literals; - - namespace - { - Utility::LocIndView s_SettingsCommand_HelpLink = "https://aka.ms/winget-settings"_liv; - } - - std::vector> SettingsCommand::GetCommands() const - { - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - }); - } - - std::vector SettingsCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::AdminSettingEnable, Resource::String::AdminSettingEnableDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument{ Execution::Args::Type::AdminSettingDisable, Resource::String::AdminSettingDisableDescription, ArgumentType::Standard, Argument::Visibility::Help }, - }; - } - - Resource::LocString SettingsCommand::ShortDescription() const - { - return { Resource::String::SettingsCommandShortDescription }; - } - - Resource::LocString SettingsCommand::LongDescription() const - { - return { Resource::String::SettingsCommandLongDescription }; - } - - Utility::LocIndView SettingsCommand::HelpLink() const - { - return s_SettingsCommand_HelpLink; - } - - void SettingsCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - // Get admin setting string for all available options except Unknown - std::vector adminSettingList; - for (auto setting : GetAllSequentialEnumValues(BoolAdminSetting::Unknown)) - { - adminSettingList.emplace_back(AdminSettingToString(setting)); - } - - Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); - - if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingEnable))) - { - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingEnable).Name, validOptions)); - } - - if (execArgs.Contains(Execution::Args::Type::AdminSettingDisable) && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingDisable))) - { - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingDisable).Name, validOptions)); - } - } - - void SettingsCommand::ExecuteInternal(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::AdminSettingEnable)) - { - context << - Workflow::EnsureRunningAsAdmin << - Workflow::EnableAdminSetting; - - } - else if (context.Args.Contains(Execution::Args::Type::AdminSettingDisable)) - { - context << - Workflow::EnsureRunningAsAdmin << - Workflow::DisableAdminSetting; - } - else - { - context << Workflow::OpenUserSetting; - } - } - - Resource::LocString SettingsExportCommand::ShortDescription() const - { - return { Resource::String::SettingsExportCommandShortDescription }; - } - - Resource::LocString SettingsExportCommand::LongDescription() const - { - return { Resource::String::SettingsExportCommandLongDescription }; - } - - Utility::LocIndView SettingsExportCommand::HelpLink() const - { - return s_SettingsCommand_HelpLink; - } - - void SettingsExportCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::ExportSettings; - } - - std::vector SettingsSetCommand::GetArguments() const - { - return { - Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional, true }, - Argument { Execution::Args::Type::SettingValue, Resource::String::SettingValueArgumentDescription, ArgumentType::Positional, true }, - }; - } - - Resource::LocString SettingsSetCommand::ShortDescription() const - { - return { Resource::String::SettingsSetCommandShortDescription }; - } - - Resource::LocString SettingsSetCommand::LongDescription() const - { - return { Resource::String::SettingsSetCommandLongDescription }; - } - - Utility::LocIndView SettingsSetCommand::HelpLink() const - { - return s_SettingsCommand_HelpLink; - } - - void SettingsSetCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - // Get admin setting string for all available options except Unknown - std::vector adminSettingList; - for (auto setting : GetAllSequentialEnumValues(StringAdminSetting::Unknown)) - { - adminSettingList.emplace_back(AdminSettingToString(setting)); - } - - Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); - - if (StringAdminSetting::Unknown == StringToStringAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName))) - { - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name, validOptions)); - } - } - - void SettingsSetCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::EnsureRunningAsAdmin << - Workflow::SetAdminSetting; - } - - std::vector SettingsResetCommand::GetArguments() const - { - return { - Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional }, - Argument { Execution::Args::Type::All, Resource::String::ResetAllAdminSettingsArgumentDescription, ArgumentType::Flag }, - }; - } - - Resource::LocString SettingsResetCommand::ShortDescription() const - { - return { Resource::String::SettingsResetCommandShortDescription }; - } - - Resource::LocString SettingsResetCommand::LongDescription() const - { - return { Resource::String::SettingsResetCommandLongDescription }; - } - - Utility::LocIndView SettingsResetCommand::HelpLink() const - { - return s_SettingsCommand_HelpLink; - } - - void SettingsResetCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - if (execArgs.Contains(Execution::Args::Type::All)) - { - if (execArgs.Contains(Execution::Args::Type::SettingName)) - { - throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided("all|setting"_liv)); - } - - return; - } - - if (!execArgs.Contains(Execution::Args::Type::SettingName)) - { - throw CommandException(Resource::String::RequiredArgError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name)); - } - - // Get admin setting string for all available options except Unknown. - // We accept both bool and string settings - std::vector adminSettingList; - for (auto setting : GetAllSequentialEnumValues(BoolAdminSetting::Unknown)) - { - adminSettingList.emplace_back(AdminSettingToString(setting)); - } - for (auto setting : GetAllSequentialEnumValues(StringAdminSetting::Unknown)) - { - adminSettingList.emplace_back(AdminSettingToString(setting)); - } - - Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); - - if (StringAdminSetting::Unknown == StringToStringAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName)) - && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName))) - { - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name, validOptions)); - } - } - - void SettingsResetCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::EnsureRunningAsAdmin << - (context.Args.Contains(Execution::Args::Type::All) ? Workflow::ResetAllAdminSettings : Workflow::ResetAdminSetting); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SettingsCommand.h" +#include "Workflows/WorkflowBase.h" +#include "Workflows/SettingsFlow.h" + +namespace AppInstaller::CLI +{ + using namespace Utility::literals; + using namespace AppInstaller::Settings; + using namespace std::string_view_literals; + + namespace + { + Utility::LocIndView s_SettingsCommand_HelpLink = "https://aka.ms/winget-settings"_liv; + } + + std::vector> SettingsCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + std::vector SettingsCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::AdminSettingEnable, Resource::String::AdminSettingEnableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument{ Execution::Args::Type::AdminSettingDisable, Resource::String::AdminSettingDisableDescription, ArgumentType::Standard, Argument::Visibility::Help }, + }; + } + + Resource::LocString SettingsCommand::ShortDescription() const + { + return { Resource::String::SettingsCommandShortDescription }; + } + + Resource::LocString SettingsCommand::LongDescription() const + { + return { Resource::String::SettingsCommandLongDescription }; + } + + Utility::LocIndView SettingsCommand::HelpLink() const + { + return s_SettingsCommand_HelpLink; + } + + void SettingsCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + // Get admin setting string for all available options except Unknown + std::vector adminSettingList; + for (auto setting : GetAllSequentialEnumValues(BoolAdminSetting::Unknown)) + { + adminSettingList.emplace_back(AdminSettingToString(setting)); + } + + Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); + + if (execArgs.Contains(Execution::Args::Type::AdminSettingEnable) && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingEnable))) + { + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingEnable).Name, validOptions)); + } + + if (execArgs.Contains(Execution::Args::Type::AdminSettingDisable) && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::AdminSettingDisable))) + { + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::AdminSettingDisable).Name, validOptions)); + } + } + + void SettingsCommand::ExecuteInternal(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::AdminSettingEnable)) + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::EnableAdminSetting; + + } + else if (context.Args.Contains(Execution::Args::Type::AdminSettingDisable)) + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::DisableAdminSetting; + } + else + { + context << Workflow::OpenUserSetting; + } + } + + Resource::LocString SettingsExportCommand::ShortDescription() const + { + return { Resource::String::SettingsExportCommandShortDescription }; + } + + Resource::LocString SettingsExportCommand::LongDescription() const + { + return { Resource::String::SettingsExportCommandLongDescription }; + } + + Utility::LocIndView SettingsExportCommand::HelpLink() const + { + return s_SettingsCommand_HelpLink; + } + + void SettingsExportCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::ExportSettings; + } + + std::vector SettingsSetCommand::GetArguments() const + { + return { + Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional, true }, + Argument { Execution::Args::Type::SettingValue, Resource::String::SettingValueArgumentDescription, ArgumentType::Positional, true }, + }; + } + + Resource::LocString SettingsSetCommand::ShortDescription() const + { + return { Resource::String::SettingsSetCommandShortDescription }; + } + + Resource::LocString SettingsSetCommand::LongDescription() const + { + return { Resource::String::SettingsSetCommandLongDescription }; + } + + Utility::LocIndView SettingsSetCommand::HelpLink() const + { + return s_SettingsCommand_HelpLink; + } + + void SettingsSetCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + // Get admin setting string for all available options except Unknown + std::vector adminSettingList; + for (auto setting : GetAllSequentialEnumValues(StringAdminSetting::Unknown)) + { + adminSettingList.emplace_back(AdminSettingToString(setting)); + } + + Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); + + if (StringAdminSetting::Unknown == StringToStringAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName))) + { + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name, validOptions)); + } + } + + void SettingsSetCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::SetAdminSetting; + } + + std::vector SettingsResetCommand::GetArguments() const + { + return { + Argument { Execution::Args::Type::SettingName, Resource::String::SettingNameArgumentDescription, ArgumentType::Positional }, + Argument { Execution::Args::Type::All, Resource::String::ResetAllAdminSettingsArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString SettingsResetCommand::ShortDescription() const + { + return { Resource::String::SettingsResetCommandShortDescription }; + } + + Resource::LocString SettingsResetCommand::LongDescription() const + { + return { Resource::String::SettingsResetCommandLongDescription }; + } + + Utility::LocIndView SettingsResetCommand::HelpLink() const + { + return s_SettingsCommand_HelpLink; + } + + void SettingsResetCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::All)) + { + if (execArgs.Contains(Execution::Args::Type::SettingName)) + { + throw CommandException(Resource::String::MultipleExclusiveArgumentsProvided("all|setting"_liv)); + } + + return; + } + + if (!execArgs.Contains(Execution::Args::Type::SettingName)) + { + throw CommandException(Resource::String::RequiredArgError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name)); + } + + // Get admin setting string for all available options except Unknown. + // We accept both bool and string settings + std::vector adminSettingList; + for (auto setting : GetAllSequentialEnumValues(BoolAdminSetting::Unknown)) + { + adminSettingList.emplace_back(AdminSettingToString(setting)); + } + for (auto setting : GetAllSequentialEnumValues(StringAdminSetting::Unknown)) + { + adminSettingList.emplace_back(AdminSettingToString(setting)); + } + + Utility::LocIndString validOptions = Join(", "_liv, adminSettingList); + + if (StringAdminSetting::Unknown == StringToStringAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName)) + && BoolAdminSetting::Unknown == StringToBoolAdminSetting(execArgs.GetArg(Execution::Args::Type::SettingName))) + { + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SettingName).Name, validOptions)); + } + } + + void SettingsResetCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::EnsureRunningAsAdmin << + (context.Args.Contains(Execution::Args::Type::All) ? Workflow::ResetAllAdminSettings : Workflow::ResetAdminSetting); + } +} diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.cpp b/src/AppInstallerCLICore/Commands/ShowCommand.cpp index 853f0afcdc..796711bf19 100644 --- a/src/AppInstallerCLICore/Commands/ShowCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ShowCommand.cpp @@ -1,110 +1,110 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ShowCommand.h" -#include "Workflows/ShowFlow.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::CLI::Workflow; - - std::vector ShowCommand::GetArguments() const - { - return { - Argument::ForType(Execution::Args::Type::Query), - // The manifest argument from Argument::ForType can be blocked by Group Policy but we don't want that here - Argument{ Execution::Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Execution::Args::Type::Id), - Argument::ForType(Execution::Args::Type::Name), - Argument::ForType(Execution::Args::Type::Moniker), - Argument::ForType(Execution::Args::Type::Version), - Argument::ForType(Execution::Args::Type::Channel), - Argument::ForType(Execution::Args::Type::Source), - Argument::ForType(Execution::Args::Type::Exact), - Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Execution::Args::Type::InstallerArchitecture), - Argument::ForType(Execution::Args::Type::InstallerType), - Argument::ForType(Execution::Args::Type::Locale), - Argument::ForType(Execution::Args::Type::ListVersions), - Argument::ForType(Execution::Args::Type::CustomHeader), - Argument::ForType(Execution::Args::Type::AuthenticationMode), - Argument::ForType(Execution::Args::Type::AuthenticationAccount), - Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), - }; - } - - Resource::LocString ShowCommand::ShortDescription() const - { - return { Resource::String::ShowCommandShortDescription }; - } - - Resource::LocString ShowCommand::LongDescription() const - { - return { Resource::String::ShowCommandLongDescription }; - } - - void ShowCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const - { - switch (valueType) - { - case Args::Type::InstallerArchitecture: - case Args::Type::Locale: - // May well move to CompleteWithSingleSemanticsForValue, - // but for now output nothing. - context << - Workflow::CompleteWithEmptySet; - break; - default: - context << - Workflow::CompleteWithSingleSemanticsForValue(valueType); - } - } - - Utility::LocIndView ShowCommand::HelpLink() const - { - return "https://aka.ms/winget-command-show"_liv; - } - - void ShowCommand::ValidateArgumentsInternal(Args& execArgs) const - { - Argument::ValidateCommonArguments(execArgs); - } - - void ShowCommand::ExecuteInternal(Execution::Context& context) const - { - context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); - - if (context.Args.Contains(Execution::Args::Type::ListVersions)) - { - if (context.Args.Contains(Execution::Args::Type::Manifest)) - { - context << - Workflow::GetManifestFromArg << - Workflow::ReportManifestIdentity << - Workflow::ShowManifestVersion; - } - else - { - context << - Workflow::OpenSource() << - Workflow::SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Show) << - Workflow::ReportPackageIdentity << - Workflow::ShowAppVersions; - } - } - else - { - context << - GetManifest( /* considerPins */ false) << - Workflow::ReportManifestIdentity << - Workflow::SelectInstaller << - Workflow::ShowManifestInfo; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ShowCommand.h" +#include "Workflows/ShowFlow.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::CLI::Workflow; + + std::vector ShowCommand::GetArguments() const + { + return { + Argument::ForType(Execution::Args::Type::Query), + // The manifest argument from Argument::ForType can be blocked by Group Policy but we don't want that here + Argument{ Execution::Args::Type::Manifest, Resource::String::ManifestArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Execution::Args::Type::Id), + Argument::ForType(Execution::Args::Type::Name), + Argument::ForType(Execution::Args::Type::Moniker), + Argument::ForType(Execution::Args::Type::Version), + Argument::ForType(Execution::Args::Type::Channel), + Argument::ForType(Execution::Args::Type::Source), + Argument::ForType(Execution::Args::Type::Exact), + Argument{ Args::Type::InstallScope, Resource::String::InstallScopeDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Execution::Args::Type::InstallerArchitecture), + Argument::ForType(Execution::Args::Type::InstallerType), + Argument::ForType(Execution::Args::Type::Locale), + Argument::ForType(Execution::Args::Type::ListVersions), + Argument::ForType(Execution::Args::Type::CustomHeader), + Argument::ForType(Execution::Args::Type::AuthenticationMode), + Argument::ForType(Execution::Args::Type::AuthenticationAccount), + Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), + }; + } + + Resource::LocString ShowCommand::ShortDescription() const + { + return { Resource::String::ShowCommandShortDescription }; + } + + Resource::LocString ShowCommand::LongDescription() const + { + return { Resource::String::ShowCommandLongDescription }; + } + + void ShowCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const + { + switch (valueType) + { + case Args::Type::InstallerArchitecture: + case Args::Type::Locale: + // May well move to CompleteWithSingleSemanticsForValue, + // but for now output nothing. + context << + Workflow::CompleteWithEmptySet; + break; + default: + context << + Workflow::CompleteWithSingleSemanticsForValue(valueType); + } + } + + Utility::LocIndView ShowCommand::HelpLink() const + { + return "https://aka.ms/winget-command-show"_liv; + } + + void ShowCommand::ValidateArgumentsInternal(Args& execArgs) const + { + Argument::ValidateCommonArguments(execArgs); + } + + void ShowCommand::ExecuteInternal(Execution::Context& context) const + { + context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); + + if (context.Args.Contains(Execution::Args::Type::ListVersions)) + { + if (context.Args.Contains(Execution::Args::Type::Manifest)) + { + context << + Workflow::GetManifestFromArg << + Workflow::ReportManifestIdentity << + Workflow::ShowManifestVersion; + } + else + { + context << + Workflow::OpenSource() << + Workflow::SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Show) << + Workflow::ReportPackageIdentity << + Workflow::ShowAppVersions; + } + } + else + { + context << + GetManifest( /* considerPins */ false) << + Workflow::ReportManifestIdentity << + Workflow::SelectInstaller << + Workflow::ShowManifestInfo; + } + } +} diff --git a/src/AppInstallerCLICore/Commands/ShowCommand.h b/src/AppInstallerCLICore/Commands/ShowCommand.h index 2106c0ea27..e03e9aa29e 100644 --- a/src/AppInstallerCLICore/Commands/ShowCommand.h +++ b/src/AppInstallerCLICore/Commands/ShowCommand.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ShowCommand final : public Command - { - ShowCommand(std::string_view parent) : Command("show", { "view" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(AppInstaller::CLI::Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ShowCommand final : public Command + { + ShowCommand(std::string_view parent) : Command("show", { "view" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(AppInstaller::CLI::Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.cpp b/src/AppInstallerCLICore/Commands/SourceCommand.cpp index a4a7b4654e..aa820c9277 100644 --- a/src/AppInstallerCLICore/Commands/SourceCommand.cpp +++ b/src/AppInstallerCLICore/Commands/SourceCommand.cpp @@ -1,387 +1,387 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SourceCommand.h" -#include "Workflows/CompletionFlow.h" -#include "Workflows/SourceFlow.h" -#include "Workflows/WorkflowBase.h" -#include "Resources.h" - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace std::string_view_literals; - - namespace - { - void ValidateSourcePriorityArgument(const Args& execArgs) - { - if (execArgs.Contains(Execution::Args::Type::SourcePriority)) - { - std::string_view priorityArg = execArgs.GetArg(Execution::Args::Type::SourcePriority); - auto convertedArg = Utility::TryConvertStringToInt32(priorityArg); - if (!convertedArg.has_value()) - { - throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues(Argument::ForType(Execution::Args::Type::SourcePriority).Name())); - } - } - } - } - - Utility::LocIndView s_SourceCommand_HelpLink = "https://aka.ms/winget-command-source"_liv; - - std::vector> SourceCommand::GetCommands() const - { - return InitializeFromMoveOnly>>({ - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - }); - } - - Resource::LocString SourceCommand::ShortDescription() const - { - return { Resource::String::SourceCommandShortDescription }; - } - - Resource::LocString SourceCommand::LongDescription() const - { - return { Resource::String::SourceCommandLongDescription }; - } - - Utility::LocIndView SourceCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceCommand::ExecuteInternal(Context& context) const - { - OutputHelp(context.Reporter); - } - - std::vector SourceAddCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::SourceName).SetRequired(true), - Argument::ForType(Args::Type::SourceArg), - Argument::ForType(Args::Type::SourceType), - Argument::ForType(Args::Type::SourceTrustLevel), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument::ForType(Args::Type::SourceExplicit), - Argument::ForType(Args::Type::SourcePriority), - }; - } - - Resource::LocString SourceAddCommand::ShortDescription() const - { - return { Resource::String::SourceAddCommandShortDescription }; - } - - Resource::LocString SourceAddCommand::LongDescription() const - { - return { Resource::String::SourceAddCommandLongDescription }; - } - - Utility::LocIndView SourceAddCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceAddCommand::ValidateArgumentsInternal(Args& execArgs) const - { - if (execArgs.Contains(Execution::Args::Type::SourceTrustLevel)) - { - try - { - std::string trustLevelArg = std::string{ execArgs.GetArg(Execution::Args::Type::SourceTrustLevel) }; - - for (auto trustLevel : Utility::Split(trustLevelArg, '|', true)) - { - Repository::ConvertToSourceTrustLevelEnum(trustLevel); - } - } - catch (...) - { - auto validOptions = std::vector{ - Utility::LocIndString{ Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::None) }, - Utility::LocIndString{ Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::Trusted) } }; - throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SourceTrustLevel).Name, Utility::Join(","_liv, validOptions))); - } - } - - ValidateSourcePriorityArgument(execArgs); - } - - void SourceAddCommand::ExecuteInternal(Context& context) const - { - // Note: Group Policy for allowed sources is enforced at the RepositoryCore level - // as we need to validate the source data and handle sources that were already added. - context << - Workflow::EnsureRunningAsAdmin << - Workflow::GetSourceList << - Workflow::CheckSourceListAgainstAdd << - Workflow::CreateSourceForSourceAdd << - Workflow::AddSource; - } - - std::vector SourceListCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::SourceName), - }; - } - - Resource::LocString SourceListCommand::ShortDescription() const - { - return { Resource::String::SourceListCommandShortDescription }; - } - - Resource::LocString SourceListCommand::LongDescription() const - { - return { Resource::String::SourceListCommandLongDescription }; - } - - void SourceListCommand::Complete(Context& context, Args::Type valueType) const - { - if (valueType == Args::Type::SourceName) - { - context << - Workflow::CompleteSourceName; - } - } - - Utility::LocIndView SourceListCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceListCommand::ExecuteInternal(Context& context) const - { - context << - Workflow::GetSourceListWithFilter << - Workflow::ListSources; - } - - std::vector SourceUpdateCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::SourceName), - }; - } - - Resource::LocString SourceUpdateCommand::ShortDescription() const - { - return { Resource::String::SourceUpdateCommandShortDescription }; - } - - Resource::LocString SourceUpdateCommand::LongDescription() const - { - return { Resource::String::SourceUpdateCommandLongDescription }; - } - - void SourceUpdateCommand::Complete(Context& context, Args::Type valueType) const - { - if (valueType == Args::Type::SourceName) - { - context << - Workflow::CompleteSourceName; - } - } - - Utility::LocIndView SourceUpdateCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceUpdateCommand::ExecuteInternal(Context& context) const - { - context << - Workflow::GetSourceListWithFilter << - Workflow::UpdateSources; - } - - std::vector SourceRemoveCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::SourceName).SetRequired(true), - }; - } - - Resource::LocString SourceRemoveCommand::ShortDescription() const - { - return { Resource::String::SourceRemoveCommandShortDescription }; - } - - Resource::LocString SourceRemoveCommand::LongDescription() const - { - return { Resource::String::SourceRemoveCommandLongDescription }; - } - - void SourceRemoveCommand::Complete(Context& context, Args::Type valueType) const - { - if (valueType == Args::Type::SourceName) - { - context << - Workflow::CompleteSourceName; - } - } - - Utility::LocIndView SourceRemoveCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceRemoveCommand::ExecuteInternal(Context& context) const - { - // Note: Group Policy for unremovable sources is enforced at the RepositoryCore. - context << - Workflow::EnsureRunningAsAdmin << - Workflow::GetSourceListWithFilter << - Workflow::RemoveSources; - } - - std::vector SourceResetCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::SourceName), - Argument{ Args::Type::ForceSourceReset, Resource::String::SourceResetForceArgumentDescription, ArgumentType::Flag }, - }; - } - - Resource::LocString SourceResetCommand::ShortDescription() const - { - return { Resource::String::SourceResetCommandShortDescription }; - } - - Resource::LocString SourceResetCommand::LongDescription() const - { - return { Resource::String::SourceResetCommandLongDescription }; - } - - void SourceResetCommand::Complete(Context& context, Args::Type valueType) const - { - if (valueType == Args::Type::SourceName) - { - context << - Workflow::CompleteSourceName; - } - } - - Utility::LocIndView SourceResetCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceResetCommand::ExecuteInternal(Context& context) const - { - if (context.Args.Contains(Args::Type::SourceName)) - { - context << - Workflow::EnsureRunningAsAdmin << - Workflow::ResetNamedSource; - } - else - { - context << - Workflow::EnsureRunningAsAdmin << - Workflow::QueryUserForSourceReset << - Workflow::ResetAllSources; - } - } - - std::vector SourceExportCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::SourceName), - }; - } - - Resource::LocString SourceExportCommand::ShortDescription() const - { - return { Resource::String::SourceExportCommandShortDescription }; - } - - Resource::LocString SourceExportCommand::LongDescription() const - { - return { Resource::String::SourceExportCommandLongDescription }; - } - - void SourceExportCommand::Complete(Context& context, Args::Type valueType) const - { - if (valueType == Args::Type::SourceName) - { - context << - Workflow::CompleteSourceName; - } - } - - Utility::LocIndView SourceExportCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceExportCommand::ExecuteInternal(Context& context) const - { - context << - Workflow::GetSourceListWithFilter << - Workflow::ExportSourceList; - } - - // Source Edit Command - - std::vector SourceEditCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::SourceName).SetRequired(true), - Argument::ForType(Args::Type::SourceEditExplicit), - Argument::ForType(Args::Type::SourcePriority), - }; - } - - Resource::LocString SourceEditCommand::ShortDescription() const - { - return { Resource::String::SourceEditCommandShortDescription }; - } - - Resource::LocString SourceEditCommand::LongDescription() const - { - return { Resource::String::SourceEditCommandLongDescription }; - } - - Utility::LocIndView SourceEditCommand::HelpLink() const - { - return s_SourceCommand_HelpLink; - } - - void SourceEditCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - if (execArgs.Contains(Execution::Args::Type::SourceEditExplicit)) - { - std::string_view explicitArg = execArgs.GetArg(Execution::Args::Type::SourceEditExplicit); - auto convertedArg = Utility::TryConvertStringToBool(explicitArg); - if (!convertedArg.has_value()) - { - auto validOptions = Utility::Join(", "_liv, std::vector{ - "true"_lis, - "false"_lis, - }); - throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::SourceEditExplicit).Name(), validOptions)); - } - } - - ValidateSourcePriorityArgument(execArgs); - } - - void SourceEditCommand::ExecuteInternal(Context& context) const - { - context << - Workflow::EnsureRunningAsAdmin << - Workflow::GetSourceListWithFilter << - Workflow::EditSources; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SourceCommand.h" +#include "Workflows/CompletionFlow.h" +#include "Workflows/SourceFlow.h" +#include "Workflows/WorkflowBase.h" +#include "Resources.h" + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace std::string_view_literals; + + namespace + { + void ValidateSourcePriorityArgument(const Args& execArgs) + { + if (execArgs.Contains(Execution::Args::Type::SourcePriority)) + { + std::string_view priorityArg = execArgs.GetArg(Execution::Args::Type::SourcePriority); + auto convertedArg = Utility::TryConvertStringToInt32(priorityArg); + if (!convertedArg.has_value()) + { + throw CommandException(Resource::String::InvalidArgumentValueErrorWithoutValidValues(Argument::ForType(Execution::Args::Type::SourcePriority).Name())); + } + } + } + } + + Utility::LocIndView s_SourceCommand_HelpLink = "https://aka.ms/winget-command-source"_liv; + + std::vector> SourceCommand::GetCommands() const + { + return InitializeFromMoveOnly>>({ + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + Resource::LocString SourceCommand::ShortDescription() const + { + return { Resource::String::SourceCommandShortDescription }; + } + + Resource::LocString SourceCommand::LongDescription() const + { + return { Resource::String::SourceCommandLongDescription }; + } + + Utility::LocIndView SourceCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceCommand::ExecuteInternal(Context& context) const + { + OutputHelp(context.Reporter); + } + + std::vector SourceAddCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::SourceName).SetRequired(true), + Argument::ForType(Args::Type::SourceArg), + Argument::ForType(Args::Type::SourceType), + Argument::ForType(Args::Type::SourceTrustLevel), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::SourceExplicit), + Argument::ForType(Args::Type::SourcePriority), + }; + } + + Resource::LocString SourceAddCommand::ShortDescription() const + { + return { Resource::String::SourceAddCommandShortDescription }; + } + + Resource::LocString SourceAddCommand::LongDescription() const + { + return { Resource::String::SourceAddCommandLongDescription }; + } + + Utility::LocIndView SourceAddCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceAddCommand::ValidateArgumentsInternal(Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::SourceTrustLevel)) + { + try + { + std::string trustLevelArg = std::string{ execArgs.GetArg(Execution::Args::Type::SourceTrustLevel) }; + + for (auto trustLevel : Utility::Split(trustLevelArg, '|', true)) + { + Repository::ConvertToSourceTrustLevelEnum(trustLevel); + } + } + catch (...) + { + auto validOptions = std::vector{ + Utility::LocIndString{ Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::None) }, + Utility::LocIndString{ Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::Trusted) } }; + throw CommandException(Resource::String::InvalidArgumentValueError(ArgumentCommon::ForType(Execution::Args::Type::SourceTrustLevel).Name, Utility::Join(","_liv, validOptions))); + } + } + + ValidateSourcePriorityArgument(execArgs); + } + + void SourceAddCommand::ExecuteInternal(Context& context) const + { + // Note: Group Policy for allowed sources is enforced at the RepositoryCore level + // as we need to validate the source data and handle sources that were already added. + context << + Workflow::EnsureRunningAsAdmin << + Workflow::GetSourceList << + Workflow::CheckSourceListAgainstAdd << + Workflow::CreateSourceForSourceAdd << + Workflow::AddSource; + } + + std::vector SourceListCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::SourceName), + }; + } + + Resource::LocString SourceListCommand::ShortDescription() const + { + return { Resource::String::SourceListCommandShortDescription }; + } + + Resource::LocString SourceListCommand::LongDescription() const + { + return { Resource::String::SourceListCommandLongDescription }; + } + + void SourceListCommand::Complete(Context& context, Args::Type valueType) const + { + if (valueType == Args::Type::SourceName) + { + context << + Workflow::CompleteSourceName; + } + } + + Utility::LocIndView SourceListCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceListCommand::ExecuteInternal(Context& context) const + { + context << + Workflow::GetSourceListWithFilter << + Workflow::ListSources; + } + + std::vector SourceUpdateCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::SourceName), + }; + } + + Resource::LocString SourceUpdateCommand::ShortDescription() const + { + return { Resource::String::SourceUpdateCommandShortDescription }; + } + + Resource::LocString SourceUpdateCommand::LongDescription() const + { + return { Resource::String::SourceUpdateCommandLongDescription }; + } + + void SourceUpdateCommand::Complete(Context& context, Args::Type valueType) const + { + if (valueType == Args::Type::SourceName) + { + context << + Workflow::CompleteSourceName; + } + } + + Utility::LocIndView SourceUpdateCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceUpdateCommand::ExecuteInternal(Context& context) const + { + context << + Workflow::GetSourceListWithFilter << + Workflow::UpdateSources; + } + + std::vector SourceRemoveCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::SourceName).SetRequired(true), + }; + } + + Resource::LocString SourceRemoveCommand::ShortDescription() const + { + return { Resource::String::SourceRemoveCommandShortDescription }; + } + + Resource::LocString SourceRemoveCommand::LongDescription() const + { + return { Resource::String::SourceRemoveCommandLongDescription }; + } + + void SourceRemoveCommand::Complete(Context& context, Args::Type valueType) const + { + if (valueType == Args::Type::SourceName) + { + context << + Workflow::CompleteSourceName; + } + } + + Utility::LocIndView SourceRemoveCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceRemoveCommand::ExecuteInternal(Context& context) const + { + // Note: Group Policy for unremovable sources is enforced at the RepositoryCore. + context << + Workflow::EnsureRunningAsAdmin << + Workflow::GetSourceListWithFilter << + Workflow::RemoveSources; + } + + std::vector SourceResetCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::SourceName), + Argument{ Args::Type::ForceSourceReset, Resource::String::SourceResetForceArgumentDescription, ArgumentType::Flag }, + }; + } + + Resource::LocString SourceResetCommand::ShortDescription() const + { + return { Resource::String::SourceResetCommandShortDescription }; + } + + Resource::LocString SourceResetCommand::LongDescription() const + { + return { Resource::String::SourceResetCommandLongDescription }; + } + + void SourceResetCommand::Complete(Context& context, Args::Type valueType) const + { + if (valueType == Args::Type::SourceName) + { + context << + Workflow::CompleteSourceName; + } + } + + Utility::LocIndView SourceResetCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceResetCommand::ExecuteInternal(Context& context) const + { + if (context.Args.Contains(Args::Type::SourceName)) + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::ResetNamedSource; + } + else + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::QueryUserForSourceReset << + Workflow::ResetAllSources; + } + } + + std::vector SourceExportCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::SourceName), + }; + } + + Resource::LocString SourceExportCommand::ShortDescription() const + { + return { Resource::String::SourceExportCommandShortDescription }; + } + + Resource::LocString SourceExportCommand::LongDescription() const + { + return { Resource::String::SourceExportCommandLongDescription }; + } + + void SourceExportCommand::Complete(Context& context, Args::Type valueType) const + { + if (valueType == Args::Type::SourceName) + { + context << + Workflow::CompleteSourceName; + } + } + + Utility::LocIndView SourceExportCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceExportCommand::ExecuteInternal(Context& context) const + { + context << + Workflow::GetSourceListWithFilter << + Workflow::ExportSourceList; + } + + // Source Edit Command + + std::vector SourceEditCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::SourceName).SetRequired(true), + Argument::ForType(Args::Type::SourceEditExplicit), + Argument::ForType(Args::Type::SourcePriority), + }; + } + + Resource::LocString SourceEditCommand::ShortDescription() const + { + return { Resource::String::SourceEditCommandShortDescription }; + } + + Resource::LocString SourceEditCommand::LongDescription() const + { + return { Resource::String::SourceEditCommandLongDescription }; + } + + Utility::LocIndView SourceEditCommand::HelpLink() const + { + return s_SourceCommand_HelpLink; + } + + void SourceEditCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + if (execArgs.Contains(Execution::Args::Type::SourceEditExplicit)) + { + std::string_view explicitArg = execArgs.GetArg(Execution::Args::Type::SourceEditExplicit); + auto convertedArg = Utility::TryConvertStringToBool(explicitArg); + if (!convertedArg.has_value()) + { + auto validOptions = Utility::Join(", "_liv, std::vector{ + "true"_lis, + "false"_lis, + }); + throw CommandException(Resource::String::InvalidArgumentValueError(Argument::ForType(Execution::Args::Type::SourceEditExplicit).Name(), validOptions)); + } + } + + ValidateSourcePriorityArgument(execArgs); + } + + void SourceEditCommand::ExecuteInternal(Context& context) const + { + context << + Workflow::EnsureRunningAsAdmin << + Workflow::GetSourceListWithFilter << + Workflow::EditSources; + } +} diff --git a/src/AppInstallerCLICore/Commands/SourceCommand.h b/src/AppInstallerCLICore/Commands/SourceCommand.h index deda3f03a6..9875158995 100644 --- a/src/AppInstallerCLICore/Commands/SourceCommand.h +++ b/src/AppInstallerCLICore/Commands/SourceCommand.h @@ -1,140 +1,140 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct SourceCommand final : public Command - { - SourceCommand(std::string_view parent) : Command("source", parent) {} - - std::vector> GetCommands() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct SourceAddCommand final : public Command - { - SourceAddCommand(std::string_view parent) : Command("add", {}, parent, Settings::TogglePolicy::Policy::AllowedSources) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct SourceListCommand final : public Command - { - SourceListCommand(std::string_view parent) : Command("list", { "ls" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct SourceUpdateCommand final : public Command - { - SourceUpdateCommand(std::string_view parent) : Command("update", { "refresh" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct SourceRemoveCommand final : public Command - { - // We can remove user or default sources, so this is not gated by any single policy. - SourceRemoveCommand(std::string_view parent) : Command("remove", { "rm" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct SourceResetCommand final : public Command - { - SourceResetCommand(std::string_view parent) : Command("reset", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct SourceExportCommand final : public Command - { - SourceExportCommand(std::string_view parent) : Command("export", parent, CommandOutputFlags::IgnoreSettingsWarnings) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - struct SourceEditCommand final : public Command - { - SourceEditCommand(std::string_view parent) : Command("edit", { "config", "set" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct SourceCommand final : public Command + { + SourceCommand(std::string_view parent) : Command("source", parent) {} + + std::vector> GetCommands() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SourceAddCommand final : public Command + { + SourceAddCommand(std::string_view parent) : Command("add", {}, parent, Settings::TogglePolicy::Policy::AllowedSources) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SourceListCommand final : public Command + { + SourceListCommand(std::string_view parent) : Command("list", { "ls" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SourceUpdateCommand final : public Command + { + SourceUpdateCommand(std::string_view parent) : Command("update", { "refresh" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SourceRemoveCommand final : public Command + { + // We can remove user or default sources, so this is not gated by any single policy. + SourceRemoveCommand(std::string_view parent) : Command("remove", { "rm" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SourceResetCommand final : public Command + { + SourceResetCommand(std::string_view parent) : Command("reset", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SourceExportCommand final : public Command + { + SourceExportCommand(std::string_view parent) : Command("export", parent, CommandOutputFlags::IgnoreSettingsWarnings) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + struct SourceEditCommand final : public Command + { + SourceEditCommand(std::string_view parent) : Command("edit", { "config", "set" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/TestCommand.cpp b/src/AppInstallerCLICore/Commands/TestCommand.cpp index ebcbcf0bf4..beeb53f2f7 100644 --- a/src/AppInstallerCLICore/Commands/TestCommand.cpp +++ b/src/AppInstallerCLICore/Commands/TestCommand.cpp @@ -1,404 +1,404 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" - -#ifndef AICLI_DISABLE_TEST_HOOKS - -#include "TestCommand.h" -#include "AppInstallerRuntime.h" -#include "TableOutput.h" -#include "Public/ConfigurationSetProcessorFactoryRemoting.h" -#include "Public/ShutdownMonitoring.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include -#include -#include - -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI -{ - namespace - { - void LogAndReport(Execution::Context& context, std::string_view message) - { - context.Reporter.Info() << message << std::endl; - AICLI_LOG(CLI, Info, << message); - } - - HRESULT WaitForShutdown(Execution::Context& context) - { - LogAndReport(context, "Waiting for app shutdown event"); - if (!ShutdownMonitoring::ServerShutdownSynchronization::WaitForShutdown(300000)) - { - LogAndReport(context, "Failed getting app shutdown event"); - return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR; - } - - LogAndReport(context, "Succeeded waiting for app shutdown event"); - return S_OK; - } - - HRESULT AppShutdownWindowMessage(Execution::Context& context) - { - auto windowHandle = ShutdownMonitoring::TerminationSignalHandler::Instance()->GetWindowHandle(); - - if (windowHandle == NULL) - { - LogAndReport(context, "Window was not created"); - return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR; - } - - if (context.Args.Contains(Execution::Args::Type::Force)) - { - LogAndReport(context, "Sending WM_QUERYENDSESSION message"); - THROW_LAST_ERROR_IF(!SendMessageTimeout( - windowHandle, - WM_QUERYENDSESSION, - NULL, - ENDSESSION_CLOSEAPP, - (SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT), - 5000, - NULL)); - } - - HRESULT hr = WaitForShutdown(context); - - if (context.Args.Contains(Execution::Args::Type::Force)) - { - LogAndReport(context, "Sending WM_ENDSESSION message"); - THROW_LAST_ERROR_IF(!SendMessageTimeout( - windowHandle, - WM_ENDSESSION, - NULL, - ENDSESSION_CLOSEAPP, - (SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT), - 10000, - NULL)); - } - - return hr; - } - - void AppShutdownTestSystemBlockNewWork(CancelReason reason) - { - AICLI_LOG(CLI, Info, << "AppShutdownTestSystemBlockNewWork :: " << reason); - } - - void AppShutdownTestSystemBeginShutdown(CancelReason reason) - { - AICLI_LOG(CLI, Info, << "AppShutdownTestSystemBeginShutdown :: " << reason); - } - - void AppShutdownTestSystemWait() - { - AICLI_LOG(CLI, Info, << "AppShutdownTestSystemWait"); - } - - void EnsureDSCv3Processor(Execution::Context& context) - { - auto& configurationSet = context.Get().Set(); - configurationSet.Environment().ProcessorIdentifier(L"dscv3"); - } - - void InvokeGetAllUnits(Execution::Context& context) - { - auto& configurationContext = context.Get(); - - winrt::Microsoft::Management::Configuration::ConfigurationUnit unit; - unit.Type(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ConfigurationExportResource))); - - auto result = configurationContext.Processor().GetAllUnits(unit); - - if (FAILED(result.ResultInformation().ResultCode())) - { - context.Reporter.Error() << "Failed to export: " << WINGET_OSTREAM_FORMAT_HRESULT(result.ResultInformation().ResultCode()) << std::endl; - AICLI_TERMINATE_CONTEXT(result.ResultInformation().ResultCode()); - } - - for (const auto& resultUnit : result.Units()) - { - configurationContext.Set().Units().Append(resultUnit); - } - } - - // Command to directly invoke the export flow. - struct TestConfigurationExportCommand final : public Command - { - TestConfigurationExportCommand(std::string_view parent) : Command("config-export-units", {}, parent) {} - - std::vector GetArguments() const override - { - return { - Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, true }, - Argument{ Execution::Args::Type::ConfigurationExportResource, Resource::String::ConfigureExportResource }, - }; - } - - Resource::LocString ShortDescription() const override - { - return "Run config export"_lis; - } - - Resource::LocString LongDescription() const override - { - return "Runs the GetAllUnits configuration method to test export on a DSC v3 directly."_lis; - } - - protected: - void ExecuteInternal(Execution::Context& context) const override - { - context << - VerifyIsFullPackage << - CreateConfigurationProcessorWithoutFactory << - CreateOrOpenConfigurationSet{ "0.3" } << - EnsureDSCv3Processor << - CreateConfigurationProcessor << - InvokeGetAllUnits << - WriteConfigFile; - } - }; - - void InvokeFindUnitProcessors(Execution::Context& context) - { - auto& configurationContext = context.Get(); - - winrt::Microsoft::Management::Configuration::FindUnitProcessorsOptions findOptions; - - if (context.Args.Contains(Execution::Args::Type::InstallLocation)) - { - findOptions.SearchPaths(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::InstallLocation))); - findOptions.SearchPathsExclusive(true); - findOptions.UnitDetailFlags(winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailFlags::Local); - } - - auto result = configurationContext.Processor().FindUnitProcessors(findOptions); - - if (result.Size() > 0) - { - Execution::TableOutput<2> table(context.Reporter, - { - "Type"_lis, - "Description"_lis - }); - - for (const auto& resultUnitProcessor : result) - { - table.OutputLine({ - Utility::ConvertToUTF8(resultUnitProcessor.UnitType()), - Utility::ConvertToUTF8(resultUnitProcessor.UnitDescription()) - }); - } - - table.Complete(); - } - else - { - context.Reporter.Info() << "No unit processors found."_lis << std::endl; - } - } - - // Command to directly invoke find unit processors. - struct TestConfigurationFindUnitProcessorsCommand final : public Command - { - TestConfigurationFindUnitProcessorsCommand(std::string_view parent) : Command("config-find-unit-processors", {}, parent) {} - - std::vector GetArguments() const override - { - return { - Argument{ Execution::Args::Type::InstallLocation, Resource::String::LocationArgumentDescription }, - }; - } - - Resource::LocString ShortDescription() const override - { - return "Run find unit processors"_lis; - } - - Resource::LocString LongDescription() const override - { - return "Runs find unit processors. Search paths could be provided."_lis; - } - - protected: - void ExecuteInternal(Execution::Context& context) const override - { - context << - VerifyIsFullPackage << - CreateConfigurationProcessorWithoutFactory << - CreateOrOpenConfigurationSet{ "0.3" } << - EnsureDSCv3Processor << - CreateConfigurationProcessor << - InvokeFindUnitProcessors; - } - }; - - struct TestCanUnloadNowCommand final : public Command - { - TestCanUnloadNowCommand(std::string_view parent) : Command("can-unload-now", {}, parent, Visibility::Hidden) {} - - Resource::LocString ShortDescription() const override - { - return "Test DllCanUnloadNow"_lis; - } - - Resource::LocString LongDescription() const override - { - return "Verifies that the function that implements the inproc DllCanUnloadNow properly blocks unload due to static storage object."_lis; - } - - protected: - void ExecuteInternal(Execution::Context& context) const override - { - Repository::Source source{ Repository::PredefinedSource::Installed }; - - ProgressCallback progress; - source.Open(progress); - - HMODULE self = GetModuleHandle(L"WindowsPackageManager.dll"); - if (!self) - { - LogAndReport(context, "Couldn't get WindowsPackageManager module"); - return; - } - - auto WindowsPackageManagerInProcModuleTerminate = reinterpret_cast(GetProcAddress(self, "WindowsPackageManagerInProcModuleTerminate")); - - // Report the object counts, attempt to terminate, report the object counts again - ReportObjectCounts(context); - LogAndReport(context, WindowsPackageManagerInProcModuleTerminate() ? "DllCanUnloadNow" : "DllCannotUnloadNow"); - ReportObjectCounts(context); - } - - private: - void ReportObjectCounts(Execution::Context& context) const - { - std::ostringstream stream; - stream << "Internal objects: " << GetInternalObjectCount() << '\n'; - stream << "External objects: " << GetExternalObjectCount(); - - LogAndReport(context, stream.str()); - } - - uint32_t GetInternalObjectCount() const - { - return winrt::get_module_lock().operator unsigned int(); - } - - unsigned long GetExternalObjectCount() const - { - auto module = Microsoft::WRL::GetModuleBase(); - return module ? module->GetObjectCount() : 0; - } - }; - - struct TestTerminateTerminationSignalHandler final : public Command - { - TestTerminateTerminationSignalHandler(std::string_view parent) : Command("term-signal-handler", {}, parent, Visibility::Hidden) {} - - Resource::LocString ShortDescription() const override - { - return "Test TerminationSignalHandler thread"_lis; - } - - Resource::LocString LongDescription() const override - { - return "Forces the TerminationSignalHandler static object to be destroyed so that the thread behavior can be observed."_lis; - } - - protected: - void ExecuteInternal(Execution::Context& context) const override - { - // Destroy the one created by standard execution - // We join on the window thread, so if this never exits we have failed the test. - winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().TryRemove(L"WindowsPackageManager.TerminationSignalHandler"); - - // Create a new instance - auto instance = ShutdownMonitoring::TerminationSignalHandler::Instance(); - - if (instance->GetWindowHandle() == nullptr) - { - LogAndReport(context, "Didn't get a window handle"); - } - else - { - LogAndReport(context, "Got a window handle"); - } - } - }; - } - - std::vector> TestCommand::GetCommands() const - { - return InitializeFromMoveOnly>>( - { - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - std::make_unique(FullName()), - }); - } - - void TestCommand::ExecuteInternal(Execution::Context& context) const - { - UNREFERENCED_PARAMETER(context); - Sleep(INFINITE); - } - - Resource::LocString TestCommand::ShortDescription() const - { - return Utility::LocIndString("Waits infinitely"sv); - } - - Resource::LocString TestCommand::LongDescription() const - { - return Utility::LocIndString("Waits infinitely. Use this if you want winget to wait forever while something is going on"sv); - } - - std::vector TestAppShutdownCommand::GetArguments() const - { - return { - Argument::ForType(Execution::Args::Type::Force) - }; - } - - void TestAppShutdownCommand::ExecuteInternal(Execution::Context& context) const - { - HRESULT hr = E_FAIL; - - ShutdownMonitoring::ServerShutdownSynchronization::ComponentSystem appShutdownTestSystem{}; - appShutdownTestSystem.BlockNewWork = AppShutdownTestSystemBlockNewWork; - appShutdownTestSystem.BeginShutdown = AppShutdownTestSystemBeginShutdown; - appShutdownTestSystem.Wait = AppShutdownTestSystemWait; - - ShutdownMonitoring::ServerShutdownSynchronization::AddComponent(appShutdownTestSystem); - - // Only package context and admin won't create the window message. - if (!Runtime::IsRunningInPackagedContext() || !Runtime::IsRunningAsAdmin()) - { - hr = AppShutdownWindowMessage(context); - } - else - { - hr = WaitForShutdown(context); - } - - AICLI_TERMINATE_CONTEXT(hr); - } - - Resource::LocString TestAppShutdownCommand::ShortDescription() const - { - return Utility::LocIndString("Test command to verify appshutdown event."sv); - } - - Resource::LocString TestAppShutdownCommand::LongDescription() const - { - return Utility::LocIndString("Test command for appshutdown. Verifies the window was created and waits for the app shutdown event"sv); - } - -} - -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +#ifndef AICLI_DISABLE_TEST_HOOKS + +#include "TestCommand.h" +#include "AppInstallerRuntime.h" +#include "TableOutput.h" +#include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include "Public/ShutdownMonitoring.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include +#include +#include + +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI +{ + namespace + { + void LogAndReport(Execution::Context& context, std::string_view message) + { + context.Reporter.Info() << message << std::endl; + AICLI_LOG(CLI, Info, << message); + } + + HRESULT WaitForShutdown(Execution::Context& context) + { + LogAndReport(context, "Waiting for app shutdown event"); + if (!ShutdownMonitoring::ServerShutdownSynchronization::WaitForShutdown(300000)) + { + LogAndReport(context, "Failed getting app shutdown event"); + return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR; + } + + LogAndReport(context, "Succeeded waiting for app shutdown event"); + return S_OK; + } + + HRESULT AppShutdownWindowMessage(Execution::Context& context) + { + auto windowHandle = ShutdownMonitoring::TerminationSignalHandler::Instance()->GetWindowHandle(); + + if (windowHandle == NULL) + { + LogAndReport(context, "Window was not created"); + return APPINSTALLER_CLI_ERROR_INTERNAL_ERROR; + } + + if (context.Args.Contains(Execution::Args::Type::Force)) + { + LogAndReport(context, "Sending WM_QUERYENDSESSION message"); + THROW_LAST_ERROR_IF(!SendMessageTimeout( + windowHandle, + WM_QUERYENDSESSION, + NULL, + ENDSESSION_CLOSEAPP, + (SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT), + 5000, + NULL)); + } + + HRESULT hr = WaitForShutdown(context); + + if (context.Args.Contains(Execution::Args::Type::Force)) + { + LogAndReport(context, "Sending WM_ENDSESSION message"); + THROW_LAST_ERROR_IF(!SendMessageTimeout( + windowHandle, + WM_ENDSESSION, + NULL, + ENDSESSION_CLOSEAPP, + (SMTO_ABORTIFHUNG | SMTO_ERRORONEXIT), + 10000, + NULL)); + } + + return hr; + } + + void AppShutdownTestSystemBlockNewWork(CancelReason reason) + { + AICLI_LOG(CLI, Info, << "AppShutdownTestSystemBlockNewWork :: " << reason); + } + + void AppShutdownTestSystemBeginShutdown(CancelReason reason) + { + AICLI_LOG(CLI, Info, << "AppShutdownTestSystemBeginShutdown :: " << reason); + } + + void AppShutdownTestSystemWait() + { + AICLI_LOG(CLI, Info, << "AppShutdownTestSystemWait"); + } + + void EnsureDSCv3Processor(Execution::Context& context) + { + auto& configurationSet = context.Get().Set(); + configurationSet.Environment().ProcessorIdentifier(L"dscv3"); + } + + void InvokeGetAllUnits(Execution::Context& context) + { + auto& configurationContext = context.Get(); + + winrt::Microsoft::Management::Configuration::ConfigurationUnit unit; + unit.Type(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ConfigurationExportResource))); + + auto result = configurationContext.Processor().GetAllUnits(unit); + + if (FAILED(result.ResultInformation().ResultCode())) + { + context.Reporter.Error() << "Failed to export: " << WINGET_OSTREAM_FORMAT_HRESULT(result.ResultInformation().ResultCode()) << std::endl; + AICLI_TERMINATE_CONTEXT(result.ResultInformation().ResultCode()); + } + + for (const auto& resultUnit : result.Units()) + { + configurationContext.Set().Units().Append(resultUnit); + } + } + + // Command to directly invoke the export flow. + struct TestConfigurationExportCommand final : public Command + { + TestConfigurationExportCommand(std::string_view parent) : Command("config-export-units", {}, parent) {} + + std::vector GetArguments() const override + { + return { + Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, true }, + Argument{ Execution::Args::Type::ConfigurationExportResource, Resource::String::ConfigureExportResource }, + }; + } + + Resource::LocString ShortDescription() const override + { + return "Run config export"_lis; + } + + Resource::LocString LongDescription() const override + { + return "Runs the GetAllUnits configuration method to test export on a DSC v3 directly."_lis; + } + + protected: + void ExecuteInternal(Execution::Context& context) const override + { + context << + VerifyIsFullPackage << + CreateConfigurationProcessorWithoutFactory << + CreateOrOpenConfigurationSet{ "0.3" } << + EnsureDSCv3Processor << + CreateConfigurationProcessor << + InvokeGetAllUnits << + WriteConfigFile; + } + }; + + void InvokeFindUnitProcessors(Execution::Context& context) + { + auto& configurationContext = context.Get(); + + winrt::Microsoft::Management::Configuration::FindUnitProcessorsOptions findOptions; + + if (context.Args.Contains(Execution::Args::Type::InstallLocation)) + { + findOptions.SearchPaths(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::InstallLocation))); + findOptions.SearchPathsExclusive(true); + findOptions.UnitDetailFlags(winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailFlags::Local); + } + + auto result = configurationContext.Processor().FindUnitProcessors(findOptions); + + if (result.Size() > 0) + { + Execution::TableOutput<2> table(context.Reporter, + { + "Type"_lis, + "Description"_lis + }); + + for (const auto& resultUnitProcessor : result) + { + table.OutputLine({ + Utility::ConvertToUTF8(resultUnitProcessor.UnitType()), + Utility::ConvertToUTF8(resultUnitProcessor.UnitDescription()) + }); + } + + table.Complete(); + } + else + { + context.Reporter.Info() << "No unit processors found."_lis << std::endl; + } + } + + // Command to directly invoke find unit processors. + struct TestConfigurationFindUnitProcessorsCommand final : public Command + { + TestConfigurationFindUnitProcessorsCommand(std::string_view parent) : Command("config-find-unit-processors", {}, parent) {} + + std::vector GetArguments() const override + { + return { + Argument{ Execution::Args::Type::InstallLocation, Resource::String::LocationArgumentDescription }, + }; + } + + Resource::LocString ShortDescription() const override + { + return "Run find unit processors"_lis; + } + + Resource::LocString LongDescription() const override + { + return "Runs find unit processors. Search paths could be provided."_lis; + } + + protected: + void ExecuteInternal(Execution::Context& context) const override + { + context << + VerifyIsFullPackage << + CreateConfigurationProcessorWithoutFactory << + CreateOrOpenConfigurationSet{ "0.3" } << + EnsureDSCv3Processor << + CreateConfigurationProcessor << + InvokeFindUnitProcessors; + } + }; + + struct TestCanUnloadNowCommand final : public Command + { + TestCanUnloadNowCommand(std::string_view parent) : Command("can-unload-now", {}, parent, Visibility::Hidden) {} + + Resource::LocString ShortDescription() const override + { + return "Test DllCanUnloadNow"_lis; + } + + Resource::LocString LongDescription() const override + { + return "Verifies that the function that implements the inproc DllCanUnloadNow properly blocks unload due to static storage object."_lis; + } + + protected: + void ExecuteInternal(Execution::Context& context) const override + { + Repository::Source source{ Repository::PredefinedSource::Installed }; + + ProgressCallback progress; + source.Open(progress); + + HMODULE self = GetModuleHandle(L"WindowsPackageManager.dll"); + if (!self) + { + LogAndReport(context, "Couldn't get WindowsPackageManager module"); + return; + } + + auto WindowsPackageManagerInProcModuleTerminate = reinterpret_cast(GetProcAddress(self, "WindowsPackageManagerInProcModuleTerminate")); + + // Report the object counts, attempt to terminate, report the object counts again + ReportObjectCounts(context); + LogAndReport(context, WindowsPackageManagerInProcModuleTerminate() ? "DllCanUnloadNow" : "DllCannotUnloadNow"); + ReportObjectCounts(context); + } + + private: + void ReportObjectCounts(Execution::Context& context) const + { + std::ostringstream stream; + stream << "Internal objects: " << GetInternalObjectCount() << '\n'; + stream << "External objects: " << GetExternalObjectCount(); + + LogAndReport(context, stream.str()); + } + + uint32_t GetInternalObjectCount() const + { + return winrt::get_module_lock().operator unsigned int(); + } + + unsigned long GetExternalObjectCount() const + { + auto module = Microsoft::WRL::GetModuleBase(); + return module ? module->GetObjectCount() : 0; + } + }; + + struct TestTerminateTerminationSignalHandler final : public Command + { + TestTerminateTerminationSignalHandler(std::string_view parent) : Command("term-signal-handler", {}, parent, Visibility::Hidden) {} + + Resource::LocString ShortDescription() const override + { + return "Test TerminationSignalHandler thread"_lis; + } + + Resource::LocString LongDescription() const override + { + return "Forces the TerminationSignalHandler static object to be destroyed so that the thread behavior can be observed."_lis; + } + + protected: + void ExecuteInternal(Execution::Context& context) const override + { + // Destroy the one created by standard execution + // We join on the window thread, so if this never exits we have failed the test. + winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().TryRemove(L"WindowsPackageManager.TerminationSignalHandler"); + + // Create a new instance + auto instance = ShutdownMonitoring::TerminationSignalHandler::Instance(); + + if (instance->GetWindowHandle() == nullptr) + { + LogAndReport(context, "Didn't get a window handle"); + } + else + { + LogAndReport(context, "Got a window handle"); + } + } + }; + } + + std::vector> TestCommand::GetCommands() const + { + return InitializeFromMoveOnly>>( + { + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + std::make_unique(FullName()), + }); + } + + void TestCommand::ExecuteInternal(Execution::Context& context) const + { + UNREFERENCED_PARAMETER(context); + Sleep(INFINITE); + } + + Resource::LocString TestCommand::ShortDescription() const + { + return Utility::LocIndString("Waits infinitely"sv); + } + + Resource::LocString TestCommand::LongDescription() const + { + return Utility::LocIndString("Waits infinitely. Use this if you want winget to wait forever while something is going on"sv); + } + + std::vector TestAppShutdownCommand::GetArguments() const + { + return { + Argument::ForType(Execution::Args::Type::Force) + }; + } + + void TestAppShutdownCommand::ExecuteInternal(Execution::Context& context) const + { + HRESULT hr = E_FAIL; + + ShutdownMonitoring::ServerShutdownSynchronization::ComponentSystem appShutdownTestSystem{}; + appShutdownTestSystem.BlockNewWork = AppShutdownTestSystemBlockNewWork; + appShutdownTestSystem.BeginShutdown = AppShutdownTestSystemBeginShutdown; + appShutdownTestSystem.Wait = AppShutdownTestSystemWait; + + ShutdownMonitoring::ServerShutdownSynchronization::AddComponent(appShutdownTestSystem); + + // Only package context and admin won't create the window message. + if (!Runtime::IsRunningInPackagedContext() || !Runtime::IsRunningAsAdmin()) + { + hr = AppShutdownWindowMessage(context); + } + else + { + hr = WaitForShutdown(context); + } + + AICLI_TERMINATE_CONTEXT(hr); + } + + Resource::LocString TestAppShutdownCommand::ShortDescription() const + { + return Utility::LocIndString("Test command to verify appshutdown event."sv); + } + + Resource::LocString TestAppShutdownCommand::LongDescription() const + { + return Utility::LocIndString("Test command for appshutdown. Verifies the window was created and waits for the app shutdown event"sv); + } + +} + +#endif diff --git a/src/AppInstallerCLICore/Commands/TestCommand.h b/src/AppInstallerCLICore/Commands/TestCommand.h index 969b01bc23..b518063c1c 100644 --- a/src/AppInstallerCLICore/Commands/TestCommand.h +++ b/src/AppInstallerCLICore/Commands/TestCommand.h @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -#ifndef AICLI_DISABLE_TEST_HOOKS - -namespace AppInstaller::CLI -{ - // Command: winget test - // Convenient command for debugging. Waits infinitely. - // Use this if you want to debug things that happen out side of workflows or modify locally to do whatever you need. - struct TestCommand final : public Command - { - TestCommand(std::string_view parent) : Command("test", {}, parent, Visibility::Hidden) {} - - std::vector> GetCommands() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; - - // Command: winget test appshutdown - // Verifies the window was created and waits for the app shutdown event. - // Used in E2E. - struct TestAppShutdownCommand final : public Command - { - TestAppShutdownCommand(std::string_view parent) : Command("appshutdown", {}, parent, Visibility::Hidden) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; -} - -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +#ifndef AICLI_DISABLE_TEST_HOOKS + +namespace AppInstaller::CLI +{ + // Command: winget test + // Convenient command for debugging. Waits infinitely. + // Use this if you want to debug things that happen out side of workflows or modify locally to do whatever you need. + struct TestCommand final : public Command + { + TestCommand(std::string_view parent) : Command("test", {}, parent, Visibility::Hidden) {} + + std::vector> GetCommands() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; + + // Command: winget test appshutdown + // Verifies the window was created and waits for the app shutdown event. + // Used in E2E. + struct TestAppShutdownCommand final : public Command + { + TestAppShutdownCommand(std::string_view parent) : Command("appshutdown", {}, parent, Visibility::Hidden) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} + +#endif diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp index 3e99f531c2..bf3292b9ef 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.cpp @@ -1,221 +1,221 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#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" -#include "Workflows/WorkflowBase.h" -#include "Workflows/DependenciesFlow.h" -#include "Resources.h" -#include - -namespace AppInstaller::CLI -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::Manifest; - using namespace AppInstaller::CLI::Workflow; - using namespace AppInstaller::Utility::literals; - - namespace - { - // Determines whether we should list available upgrades, instead - // of performing an upgrade - bool ShouldListUpgrade(const Execution::Args& args, ArgTypeCategory argCategories = ArgTypeCategory::None) - { - if (argCategories == ArgTypeCategory::None) - { - argCategories = Argument::GetCategoriesPresent(args); - } - - // Valid arguments for list are only those related to the sources and which packages to include (e.g. --include-unknown). - // Instead of checking for them, we check that there aren't any other arguments present. - return !args.Contains(Args::Type::All) && - WI_AreAllFlagsClear(argCategories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::InstallerBehavior); - } - } - - std::vector UpgradeCommand::GetArguments() const - { - return { - Argument::ForType(Args::Type::MultiQuery), // -q - Argument::ForType(Args::Type::Manifest), // -m - Argument::ForType(Args::Type::Id), - Argument::ForType(Args::Type::Name), - Argument::ForType(Args::Type::Moniker), - Argument::ForType(Args::Type::Version), // -v - Argument::ForType(Args::Type::Channel), - Argument::ForType(Args::Type::Source), // -s - Argument::ForType(Args::Type::Exact), // -e - Argument::ForType(Args::Type::Interactive), // -i - Argument::ForType(Args::Type::Silent), // -h - Argument::ForType(Args::Type::Purge), - Argument::ForType(Args::Type::Log), // -o - Argument::ForType(Args::Type::CustomSwitches), - Argument::ForType(Args::Type::Override), - Argument::ForType(Args::Type::InstallLocation), // -l - Argument{ Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, - Argument::ForType(Args::Type::InstallArchitecture), // -a - Argument::ForType(Args::Type::InstallerType), - Argument::ForType(Args::Type::Locale), - Argument::ForType(Args::Type::HashOverride), - Argument::ForType(Args::Type::AllowReboot), - Argument::ForType(Args::Type::SkipDependencies), - Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan), - Argument::ForType(Args::Type::AcceptPackageAgreements), - Argument::ForType(Args::Type::AcceptSourceAgreements), - Argument::ForType(Args::Type::CustomHeader), - Argument::ForType(Args::Type::AuthenticationMode), - Argument::ForType(Args::Type::AuthenticationAccount), - Argument{ Args::Type::All, Resource::String::UpdateAllArgumentDescription, ArgumentType::Flag }, - Argument{ Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag }, - Argument{ Args::Type::IncludePinned, Resource::String::IncludePinnedArgumentDescription, ArgumentType::Flag}, - Argument::ForType(Args::Type::UninstallPrevious), - Argument::ForType(Args::Type::Force), - }; - } - - Resource::LocString UpgradeCommand::ShortDescription() const - { - return { Resource::String::UpgradeCommandShortDescription }; - } - - Resource::LocString UpgradeCommand::LongDescription() const - { - return { Resource::String::UpgradeCommandLongDescription }; - } - - void UpgradeCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const - { - if (valueType == Execution::Args::Type::Manifest || - valueType == Execution::Args::Type::Log || - valueType == Execution::Args::Type::Override || - valueType == Execution::Args::Type::InstallLocation) - { - // Intentionally output nothing to allow pass through to filesystem. - return; - } - - context << - OpenSource() << - OpenCompositeSource(Repository::PredefinedSource::Installed); - - switch (valueType) - { - case Execution::Args::Type::MultiQuery: - context << - RequireCompletionWordNonEmpty << - SearchSourceForManyCompletion << - CompleteWithMatchedField; - break; - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Version: - case Execution::Args::Type::Channel: - case Execution::Args::Type::Source: - context << - CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); - break; - case Args::Type::InstallArchitecture: - case Args::Type::Locale: - // May well move to CompleteWithSingleSemanticsForValue, - // but for now output nothing. - context << - Workflow::CompleteWithEmptySet; - break; - } - } - - Utility::LocIndView UpgradeCommand::HelpLink() const - { - return "https://aka.ms/winget-command-upgrade"_liv; - } - - void UpgradeCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - const auto argCategories = Argument::GetCategoriesAndValidateCommonArguments(execArgs, /* requirePackageSelectionArg */ false); - - if (!ShouldListUpgrade(execArgs, argCategories) && - WI_IsFlagClear(argCategories, ArgTypeCategory::PackageQuery) && - WI_IsFlagSet(argCategories, ArgTypeCategory::SingleInstallerBehavior)) - { - throw CommandException(Resource::String::InvalidArgumentWithoutQueryError); - } - } - - void UpgradeCommand::ExecuteInternal(Execution::Context& context) const - { - context.SetFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); - - // Only allow for source failures when doing a list of available upgrades. - // We have to set it now to allow for source open failures to also just warn. - if (ShouldListUpgrade(context.Args)) - { - context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); - } - - context << - InitializeInstallerDownloadAuthenticatorsMap << - ReportExecutionStage(ExecutionStage::Discovery) << - OpenSource() << - OpenCompositeSource(DetermineInstalledSource(context)); - - if (ShouldListUpgrade(context.Args)) - { - // Upgrade with no args list packages with updates available - context << - SearchSourceForMany << - HandleSearchResultFailures << - EnsureMatchesFromSearchResult(OperationType::Upgrade) << - ReportListResult(true); - } - else if (context.Args.Contains(Execution::Args::Type::All)) - { - // --all switch updates all packages found - context << - SearchSourceForMany << - HandleSearchResultFailures << - EnsureMatchesFromSearchResult(OperationType::Upgrade) << - ReportListResult(true) << - UpdateAllApplicable; - } - else if (context.Args.Contains(Execution::Args::Type::Manifest)) - { - // --manifest case where new manifest is provided - context << - GetManifestFromArg << - SearchSourceUsingManifest << - EnsureOneMatchFromSearchResult(OperationType::Upgrade) << - GetInstalledPackageVersion << - EnsureUpdateVersionApplicable << - SelectInstaller << - EnsureApplicableInstaller << - InstallSinglePackage; - } - else - { - // The remaining case: search for specific packages to update - if (!context.Args.Contains(Execution::Args::Type::MultiQuery)) - { - context << InstallOrUpgradeSinglePackage(OperationType::Upgrade); - } - else - { - ProcessMultiplePackages::Flags flags = ProcessMultiplePackages::Flags::None; - if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) - { - flags = ProcessMultiplePackages::Flags::IgnoreDependencies; - } - - context << - GetMultiSearchRequests << - SearchSubContextsForSingle(OperationType::Upgrade) << - ReportExecutionStage(ExecutionStage::Execution) << - ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED, flags); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#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" +#include "Workflows/WorkflowBase.h" +#include "Workflows/DependenciesFlow.h" +#include "Resources.h" +#include + +namespace AppInstaller::CLI +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::Manifest; + using namespace AppInstaller::CLI::Workflow; + using namespace AppInstaller::Utility::literals; + + namespace + { + // Determines whether we should list available upgrades, instead + // of performing an upgrade + bool ShouldListUpgrade(const Execution::Args& args, ArgTypeCategory argCategories = ArgTypeCategory::None) + { + if (argCategories == ArgTypeCategory::None) + { + argCategories = Argument::GetCategoriesPresent(args); + } + + // Valid arguments for list are only those related to the sources and which packages to include (e.g. --include-unknown). + // Instead of checking for them, we check that there aren't any other arguments present. + return !args.Contains(Args::Type::All) && + WI_AreAllFlagsClear(argCategories, ArgTypeCategory::Manifest | ArgTypeCategory::PackageQuery | ArgTypeCategory::InstallerBehavior); + } + } + + std::vector UpgradeCommand::GetArguments() const + { + return { + Argument::ForType(Args::Type::MultiQuery), // -q + Argument::ForType(Args::Type::Manifest), // -m + Argument::ForType(Args::Type::Id), + Argument::ForType(Args::Type::Name), + Argument::ForType(Args::Type::Moniker), + Argument::ForType(Args::Type::Version), // -v + Argument::ForType(Args::Type::Channel), + Argument::ForType(Args::Type::Source), // -s + Argument::ForType(Args::Type::Exact), // -e + Argument::ForType(Args::Type::Interactive), // -i + Argument::ForType(Args::Type::Silent), // -h + Argument::ForType(Args::Type::Purge), + Argument::ForType(Args::Type::Log), // -o + Argument::ForType(Args::Type::CustomSwitches), + Argument::ForType(Args::Type::Override), + Argument::ForType(Args::Type::InstallLocation), // -l + Argument{ Args::Type::InstallScope, Resource::String::InstalledScopeArgumentDescription, ArgumentType::Standard, Argument::Visibility::Help }, + Argument::ForType(Args::Type::InstallArchitecture), // -a + Argument::ForType(Args::Type::InstallerType), + Argument::ForType(Args::Type::Locale), + Argument::ForType(Args::Type::HashOverride), + Argument::ForType(Args::Type::AllowReboot), + Argument::ForType(Args::Type::SkipDependencies), + Argument::ForType(Args::Type::IgnoreLocalArchiveMalwareScan), + Argument::ForType(Args::Type::AcceptPackageAgreements), + Argument::ForType(Args::Type::AcceptSourceAgreements), + Argument::ForType(Args::Type::CustomHeader), + Argument::ForType(Args::Type::AuthenticationMode), + Argument::ForType(Args::Type::AuthenticationAccount), + Argument{ Args::Type::All, Resource::String::UpdateAllArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::IncludeUnknown, Resource::String::IncludeUnknownArgumentDescription, ArgumentType::Flag }, + Argument{ Args::Type::IncludePinned, Resource::String::IncludePinnedArgumentDescription, ArgumentType::Flag}, + Argument::ForType(Args::Type::UninstallPrevious), + Argument::ForType(Args::Type::Force), + }; + } + + Resource::LocString UpgradeCommand::ShortDescription() const + { + return { Resource::String::UpgradeCommandShortDescription }; + } + + Resource::LocString UpgradeCommand::LongDescription() const + { + return { Resource::String::UpgradeCommandLongDescription }; + } + + void UpgradeCommand::Complete(Execution::Context& context, Execution::Args::Type valueType) const + { + if (valueType == Execution::Args::Type::Manifest || + valueType == Execution::Args::Type::Log || + valueType == Execution::Args::Type::Override || + valueType == Execution::Args::Type::InstallLocation) + { + // Intentionally output nothing to allow pass through to filesystem. + return; + } + + context << + OpenSource() << + OpenCompositeSource(Repository::PredefinedSource::Installed); + + switch (valueType) + { + case Execution::Args::Type::MultiQuery: + context << + RequireCompletionWordNonEmpty << + SearchSourceForManyCompletion << + CompleteWithMatchedField; + break; + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Version: + case Execution::Args::Type::Channel: + case Execution::Args::Type::Source: + context << + CompleteWithSingleSemanticsForValueUsingExistingSource(valueType); + break; + case Args::Type::InstallArchitecture: + case Args::Type::Locale: + // May well move to CompleteWithSingleSemanticsForValue, + // but for now output nothing. + context << + Workflow::CompleteWithEmptySet; + break; + } + } + + Utility::LocIndView UpgradeCommand::HelpLink() const + { + return "https://aka.ms/winget-command-upgrade"_liv; + } + + void UpgradeCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + const auto argCategories = Argument::GetCategoriesAndValidateCommonArguments(execArgs, /* requirePackageSelectionArg */ false); + + if (!ShouldListUpgrade(execArgs, argCategories) && + WI_IsFlagClear(argCategories, ArgTypeCategory::PackageQuery) && + WI_IsFlagSet(argCategories, ArgTypeCategory::SingleInstallerBehavior)) + { + throw CommandException(Resource::String::InvalidArgumentWithoutQueryError); + } + } + + void UpgradeCommand::ExecuteInternal(Execution::Context& context) const + { + context.SetFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); + + // Only allow for source failures when doing a list of available upgrades. + // We have to set it now to allow for source open failures to also just warn. + if (ShouldListUpgrade(context.Args)) + { + context.SetFlags(Execution::ContextFlag::TreatSourceFailuresAsWarning); + } + + context << + InitializeInstallerDownloadAuthenticatorsMap << + ReportExecutionStage(ExecutionStage::Discovery) << + OpenSource() << + OpenCompositeSource(DetermineInstalledSource(context)); + + if (ShouldListUpgrade(context.Args)) + { + // Upgrade with no args list packages with updates available + context << + SearchSourceForMany << + HandleSearchResultFailures << + EnsureMatchesFromSearchResult(OperationType::Upgrade) << + ReportListResult(true); + } + else if (context.Args.Contains(Execution::Args::Type::All)) + { + // --all switch updates all packages found + context << + SearchSourceForMany << + HandleSearchResultFailures << + EnsureMatchesFromSearchResult(OperationType::Upgrade) << + ReportListResult(true) << + UpdateAllApplicable; + } + else if (context.Args.Contains(Execution::Args::Type::Manifest)) + { + // --manifest case where new manifest is provided + context << + GetManifestFromArg << + SearchSourceUsingManifest << + EnsureOneMatchFromSearchResult(OperationType::Upgrade) << + GetInstalledPackageVersion << + EnsureUpdateVersionApplicable << + SelectInstaller << + EnsureApplicableInstaller << + InstallSinglePackage; + } + else + { + // The remaining case: search for specific packages to update + if (!context.Args.Contains(Execution::Args::Type::MultiQuery)) + { + context << InstallOrUpgradeSinglePackage(OperationType::Upgrade); + } + else + { + ProcessMultiplePackages::Flags flags = ProcessMultiplePackages::Flags::None; + if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) + { + flags = ProcessMultiplePackages::Flags::IgnoreDependencies; + } + + context << + GetMultiSearchRequests << + SearchSubContextsForSingle(OperationType::Upgrade) << + ReportExecutionStage(ExecutionStage::Execution) << + ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED, flags); + } + } + } +} diff --git a/src/AppInstallerCLICore/Commands/UpgradeCommand.h b/src/AppInstallerCLICore/Commands/UpgradeCommand.h index 090cedebb8..fc7624a81b 100644 --- a/src/AppInstallerCLICore/Commands/UpgradeCommand.h +++ b/src/AppInstallerCLICore/Commands/UpgradeCommand.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct UpgradeCommand final : public Command - { - UpgradeCommand(std::string_view parent) : Command("upgrade", { "update" }, parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct UpgradeCommand final : public Command + { + UpgradeCommand(std::string_view parent) : Command("upgrade", { "update" }, parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp index ad23874be6..cd2141aa6e 100644 --- a/src/AppInstallerCLICore/Commands/ValidateCommand.cpp +++ b/src/AppInstallerCLICore/Commands/ValidateCommand.cpp @@ -1,79 +1,79 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ValidateCommand.h" -#include "Workflows/WorkflowBase.h" -#include "Workflows/DependenciesFlow.h" -#include "Resources.h" -#include - -namespace AppInstaller::CLI -{ - using namespace std::string_view_literals; - using namespace AppInstaller::Manifest; - - std::vector ValidateCommand::GetArguments() const - { - return { - Argument::ForType(Execution::Args::Type::ValidateManifest), - }; - } - - Resource::LocString ValidateCommand::ShortDescription() const - { - return Resource::LocString{ Resource::String::ValidateCommandShortDescription }; - } - - Resource::LocString ValidateCommand::LongDescription() const - { - return Resource::LocString{ Resource::String::ValidateCommandLongDescription }; - } - - Utility::LocIndView ValidateCommand::HelpLink() const - { - return "https://aka.ms/winget-command-validate"_liv; - } - - void ValidateCommand::ExecuteInternal(Execution::Context& context) const - { - context << - Workflow::VerifyPath(Execution::Args::Type::ValidateManifest) << - [](Execution::Context& context) - { - auto inputFile = Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ValidateManifest)); - - try - { - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.SchemaHeaderValidationAsWarning = true; - validateOption.ThrowOnWarning = !(context.Args.Contains(Execution::Args::Type::IgnoreWarnings)); - auto manifest = YamlParser::CreateFromPath(inputFile, validateOption); - - context.Add(manifest); - context << - Workflow::GetInstallersDependenciesFromManifest << - Workflow::ReportDependencies(Resource::String::ValidateCommandReportDependencies); - - context.Reporter.Info() << Resource::String::ManifestValidationSuccess << std::endl; - } - catch (const ManifestException& e) - { - HRESULT hr = S_OK; - if (e.IsWarningOnly()) - { - context.Reporter.Warn() << Resource::String::ManifestValidationWarning << std::endl; - hr = APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING; - } - else - { - context.Reporter.Error() << Resource::String::ManifestValidationFail << std::endl; - hr = APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE; - } - - context.Reporter.Info() << e.GetManifestErrorMessage() << std::endl; - AICLI_TERMINATE_CONTEXT(hr); - } - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ValidateCommand.h" +#include "Workflows/WorkflowBase.h" +#include "Workflows/DependenciesFlow.h" +#include "Resources.h" +#include + +namespace AppInstaller::CLI +{ + using namespace std::string_view_literals; + using namespace AppInstaller::Manifest; + + std::vector ValidateCommand::GetArguments() const + { + return { + Argument::ForType(Execution::Args::Type::ValidateManifest), + }; + } + + Resource::LocString ValidateCommand::ShortDescription() const + { + return Resource::LocString{ Resource::String::ValidateCommandShortDescription }; + } + + Resource::LocString ValidateCommand::LongDescription() const + { + return Resource::LocString{ Resource::String::ValidateCommandLongDescription }; + } + + Utility::LocIndView ValidateCommand::HelpLink() const + { + return "https://aka.ms/winget-command-validate"_liv; + } + + void ValidateCommand::ExecuteInternal(Execution::Context& context) const + { + context << + Workflow::VerifyPath(Execution::Args::Type::ValidateManifest) << + [](Execution::Context& context) + { + auto inputFile = Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ValidateManifest)); + + try + { + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.SchemaHeaderValidationAsWarning = true; + validateOption.ThrowOnWarning = !(context.Args.Contains(Execution::Args::Type::IgnoreWarnings)); + auto manifest = YamlParser::CreateFromPath(inputFile, validateOption); + + context.Add(manifest); + context << + Workflow::GetInstallersDependenciesFromManifest << + Workflow::ReportDependencies(Resource::String::ValidateCommandReportDependencies); + + context.Reporter.Info() << Resource::String::ManifestValidationSuccess << std::endl; + } + catch (const ManifestException& e) + { + HRESULT hr = S_OK; + if (e.IsWarningOnly()) + { + context.Reporter.Warn() << Resource::String::ManifestValidationWarning << std::endl; + hr = APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING; + } + else + { + context.Reporter.Error() << Resource::String::ManifestValidationFail << std::endl; + hr = APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE; + } + + context.Reporter.Info() << e.GetManifestErrorMessage() << std::endl; + AICLI_TERMINATE_CONTEXT(hr); + } + }; + } +} diff --git a/src/AppInstallerCLICore/Commands/ValidateCommand.h b/src/AppInstallerCLICore/Commands/ValidateCommand.h index 6c69cf4482..a0e2877eb9 100644 --- a/src/AppInstallerCLICore/Commands/ValidateCommand.h +++ b/src/AppInstallerCLICore/Commands/ValidateCommand.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ValidateCommand final : public Command - { - ValidateCommand(std::string_view parent) : Command("validate", parent) {} - - virtual std::vector GetArguments() const override; - - virtual Resource::LocString ShortDescription() const override; - virtual Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ValidateCommand final : public Command + { + ValidateCommand(std::string_view parent) : Command("validate", parent) {} + + virtual std::vector GetArguments() const override; + + virtual Resource::LocString ShortDescription() const override; + virtual Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + }; +} diff --git a/src/AppInstallerCLICore/CompletionData.cpp b/src/AppInstallerCLICore/CompletionData.cpp index a39632257a..b36d0b612f 100644 --- a/src/AppInstallerCLICore/CompletionData.cpp +++ b/src/AppInstallerCLICore/CompletionData.cpp @@ -1,213 +1,213 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "CompletionData.h" -#include "Resources.h" -#include -#include - -namespace AppInstaller::CLI -{ - using namespace std::string_view_literals; - using namespace Utility::literals; - - // Completion takes in the following values: - // Word :: The token from the command line that is being targeted for completion. - // This value may have quotes surrounding it, and will need to be removed in such a case. - // CommandLine :: The full command line that contains the word to be completed. - // This value has the fully quoted strings, as well as escaped quotations if needed. - // Position :: The position of the cursor within the command line. - // - // Completions here will not attempt to take exact cursor position into account; meaning if the cursor - // is in the middle of the word, it is not different than at the beginning or end. This functionality - // could be added later. - CompletionData::CompletionData(std::string_view word, std::string_view commandLine, std::string_view position) - { - m_word = word; - - AICLI_LOG(CLI, Info, << "Completing word '" << m_word << '\''); - - // Determine position as an integer - size_t cursor = wil::safe_cast(std::stoull(std::string{ position })); - - AICLI_LOG(CLI, Info, << "Cursor position starts at '" << cursor << '\''); - - // First, move the cursor from the UTF-8 grapheme position to the UTF-8 byte position. - // This simplifies the rest of the code. - cursor = Utility::UTF8Substring(commandLine, 0, cursor).length(); - - AICLI_LOG(CLI, Info, << "Cursor position moved to '" << cursor << '\''); - - std::vector argsBeforeWord; - std::vector argsAfterWord; - - // If the word is empty, we must determine where the split is. We operate as PowerShell does; the cursor - // being at the front of a token results in an empty word and an insertion rather than a replacement. - // If the user put spaces at the front of the statement, this can lead to the position being out of sorts; - // PowerShell sends the cursor position, but does not include leading spaces in the AST output. If the - // user puts too many spaces at the front we will be unable to determine the true location. - if (m_word.empty()) - { - // The cursor is past the end, so everything is before the word. - if (cursor >= commandLine.length()) - { - // Move the position to the end in case it was extended past it. - ParseInto(commandLine, argsBeforeWord, true); - } - // The cursor is not past the end; ensure that the preceding character is whitespace or move the - // position back until it is. This is far from foolproof, but until we have evidence otherwise, - // very few users are likely to put any spaces at the front of their statements, let alone many. - else - { - for (; cursor > 0 && !std::isspace(static_cast(commandLine[cursor - 1])); --cursor); - - AICLI_LOG(CLI, Info, << "Cursor position moved to '" << cursor << '\''); - - // If we actually hit the front of the string, something bad probably happened. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, cursor == 0); - - ParseInto(commandLine.substr(0, cursor), argsBeforeWord, true); - ParseInto(commandLine.substr(cursor), argsAfterWord, false); - } - } - // If the word is not empty, the cursor is either in the middle of a token, or at the end of one. - // The value will be replaced, and we will remove it from the args here. - else - { - std::vector allArgs; - ParseInto(commandLine, allArgs, true); - - // Find the word amongst the arguments - std::vector wordIndices; - for (size_t i = 0; i < allArgs.size(); ++i) - { - if (m_word == allArgs[i]) - { - wordIndices.push_back(i); - } - } - - // If we didn't find a matching string, we probably made some bad assumptions. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, wordIndices.empty()); - - // If we find an exact match only once, we can just split on that. - size_t wordIndexForSplit = wordIndices[0]; - - // If we found more than one match, we have to rely on the position to - // determine which argument is the word in question. - if (wordIndices.size() > 1) - { - // Escape the word and search for it in the command line. - std::string escapedWord = m_word; - Utility::FindAndReplace(escapedWord, "\"", "\"\""); - - std::vector escapedIndices; - for (size_t offset = 0; offset < commandLine.length();) - { - size_t pos = commandLine.find(escapedWord, offset); - - if (pos == std::string::npos) - { - break; - } - - escapedIndices.push_back(pos); - offset = pos + escapedWord.length(); - } - - // If these are out of sync we don't have much hope. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, wordIndices.size() != escapedIndices.size()); - - // Find the closest one to the position. This can be fooled as above if there is - // leading whitespace in the statement. But it is the best we can do. - size_t indexToUse = std::numeric_limits::max(); - size_t distanceToCursor = std::numeric_limits::max(); - - for (size_t i = 0; i < escapedIndices.size(); ++i) - { - size_t lowerBound = escapedIndices[i]; - size_t upperBound = lowerBound + escapedWord.length(); - size_t distance = 0; - - // The cursor is square in the middle of this location, this is the one. - if (cursor > lowerBound && cursor <= upperBound) - { - indexToUse = i; - break; - } - else if (cursor <= lowerBound) - { - distance = lowerBound - cursor; - } - else // cursor > upperBound - { - distance = cursor - upperBound; - } - - if (distance < distanceToCursor) - { - indexToUse = i; - distanceToCursor = distance; - } - } - - // It really would be unexpected to not find a closest one. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, indexToUse == std::numeric_limits::max()); - - wordIndexForSplit = wordIndices[indexToUse]; - } - - std::vector* moveTarget = &argsBeforeWord; - for (size_t i = 0; i < allArgs.size(); ++i) - { - if (i == wordIndexForSplit) - { - // Intentionally leave the matched arg behind. - moveTarget = &argsAfterWord; - } - else - { - moveTarget->emplace_back(std::move(allArgs[i])); - } - } - } - - // Move the arguments into an Invocation for future use. - m_argsBeforeWord = std::make_unique(std::move(argsBeforeWord)); - m_argsAfterWord = std::make_unique(std::move(argsAfterWord)); - - AICLI_LOG(CLI, Info, << "Completion invoked for arguments:" << [&]() { - std::stringstream strstr; - for (const auto& arg : *m_argsBeforeWord) - { - strstr << " '" << arg << '\''; - } - if (m_word.empty()) - { - strstr << " << [insert] >> "; - } - else - { - strstr << " << [replace] '" << m_word << "' >> "; - } - for (const auto& arg : *m_argsAfterWord) - { - strstr << " '" << arg << '\''; - } - return strstr.str(); - }()); - } - - void CompletionData::ParseInto(std::string_view line, std::vector& args, bool skipFirst) - { - std::wstring commandLineW = Utility::ConvertToUTF16(line); - int argc = 0; - wil::unique_hlocal_ptr argv{ CommandLineToArgvW(commandLineW.c_str(), &argc) }; - THROW_LAST_ERROR_IF_NULL(argv); - - for (int i = (skipFirst ? 1 : 0); i < argc; ++i) - { - args.emplace_back(Utility::ConvertToUTF8(argv.get()[i])); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "CompletionData.h" +#include "Resources.h" +#include +#include + +namespace AppInstaller::CLI +{ + using namespace std::string_view_literals; + using namespace Utility::literals; + + // Completion takes in the following values: + // Word :: The token from the command line that is being targeted for completion. + // This value may have quotes surrounding it, and will need to be removed in such a case. + // CommandLine :: The full command line that contains the word to be completed. + // This value has the fully quoted strings, as well as escaped quotations if needed. + // Position :: The position of the cursor within the command line. + // + // Completions here will not attempt to take exact cursor position into account; meaning if the cursor + // is in the middle of the word, it is not different than at the beginning or end. This functionality + // could be added later. + CompletionData::CompletionData(std::string_view word, std::string_view commandLine, std::string_view position) + { + m_word = word; + + AICLI_LOG(CLI, Info, << "Completing word '" << m_word << '\''); + + // Determine position as an integer + size_t cursor = wil::safe_cast(std::stoull(std::string{ position })); + + AICLI_LOG(CLI, Info, << "Cursor position starts at '" << cursor << '\''); + + // First, move the cursor from the UTF-8 grapheme position to the UTF-8 byte position. + // This simplifies the rest of the code. + cursor = Utility::UTF8Substring(commandLine, 0, cursor).length(); + + AICLI_LOG(CLI, Info, << "Cursor position moved to '" << cursor << '\''); + + std::vector argsBeforeWord; + std::vector argsAfterWord; + + // If the word is empty, we must determine where the split is. We operate as PowerShell does; the cursor + // being at the front of a token results in an empty word and an insertion rather than a replacement. + // If the user put spaces at the front of the statement, this can lead to the position being out of sorts; + // PowerShell sends the cursor position, but does not include leading spaces in the AST output. If the + // user puts too many spaces at the front we will be unable to determine the true location. + if (m_word.empty()) + { + // The cursor is past the end, so everything is before the word. + if (cursor >= commandLine.length()) + { + // Move the position to the end in case it was extended past it. + ParseInto(commandLine, argsBeforeWord, true); + } + // The cursor is not past the end; ensure that the preceding character is whitespace or move the + // position back until it is. This is far from foolproof, but until we have evidence otherwise, + // very few users are likely to put any spaces at the front of their statements, let alone many. + else + { + for (; cursor > 0 && !std::isspace(static_cast(commandLine[cursor - 1])); --cursor); + + AICLI_LOG(CLI, Info, << "Cursor position moved to '" << cursor << '\''); + + // If we actually hit the front of the string, something bad probably happened. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, cursor == 0); + + ParseInto(commandLine.substr(0, cursor), argsBeforeWord, true); + ParseInto(commandLine.substr(cursor), argsAfterWord, false); + } + } + // If the word is not empty, the cursor is either in the middle of a token, or at the end of one. + // The value will be replaced, and we will remove it from the args here. + else + { + std::vector allArgs; + ParseInto(commandLine, allArgs, true); + + // Find the word amongst the arguments + std::vector wordIndices; + for (size_t i = 0; i < allArgs.size(); ++i) + { + if (m_word == allArgs[i]) + { + wordIndices.push_back(i); + } + } + + // If we didn't find a matching string, we probably made some bad assumptions. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, wordIndices.empty()); + + // If we find an exact match only once, we can just split on that. + size_t wordIndexForSplit = wordIndices[0]; + + // If we found more than one match, we have to rely on the position to + // determine which argument is the word in question. + if (wordIndices.size() > 1) + { + // Escape the word and search for it in the command line. + std::string escapedWord = m_word; + Utility::FindAndReplace(escapedWord, "\"", "\"\""); + + std::vector escapedIndices; + for (size_t offset = 0; offset < commandLine.length();) + { + size_t pos = commandLine.find(escapedWord, offset); + + if (pos == std::string::npos) + { + break; + } + + escapedIndices.push_back(pos); + offset = pos + escapedWord.length(); + } + + // If these are out of sync we don't have much hope. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, wordIndices.size() != escapedIndices.size()); + + // Find the closest one to the position. This can be fooled as above if there is + // leading whitespace in the statement. But it is the best we can do. + size_t indexToUse = std::numeric_limits::max(); + size_t distanceToCursor = std::numeric_limits::max(); + + for (size_t i = 0; i < escapedIndices.size(); ++i) + { + size_t lowerBound = escapedIndices[i]; + size_t upperBound = lowerBound + escapedWord.length(); + size_t distance = 0; + + // The cursor is square in the middle of this location, this is the one. + if (cursor > lowerBound && cursor <= upperBound) + { + indexToUse = i; + break; + } + else if (cursor <= lowerBound) + { + distance = lowerBound - cursor; + } + else // cursor > upperBound + { + distance = cursor - upperBound; + } + + if (distance < distanceToCursor) + { + indexToUse = i; + distanceToCursor = distance; + } + } + + // It really would be unexpected to not find a closest one. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, indexToUse == std::numeric_limits::max()); + + wordIndexForSplit = wordIndices[indexToUse]; + } + + std::vector* moveTarget = &argsBeforeWord; + for (size_t i = 0; i < allArgs.size(); ++i) + { + if (i == wordIndexForSplit) + { + // Intentionally leave the matched arg behind. + moveTarget = &argsAfterWord; + } + else + { + moveTarget->emplace_back(std::move(allArgs[i])); + } + } + } + + // Move the arguments into an Invocation for future use. + m_argsBeforeWord = std::make_unique(std::move(argsBeforeWord)); + m_argsAfterWord = std::make_unique(std::move(argsAfterWord)); + + AICLI_LOG(CLI, Info, << "Completion invoked for arguments:" << [&]() { + std::stringstream strstr; + for (const auto& arg : *m_argsBeforeWord) + { + strstr << " '" << arg << '\''; + } + if (m_word.empty()) + { + strstr << " << [insert] >> "; + } + else + { + strstr << " << [replace] '" << m_word << "' >> "; + } + for (const auto& arg : *m_argsAfterWord) + { + strstr << " '" << arg << '\''; + } + return strstr.str(); + }()); + } + + void CompletionData::ParseInto(std::string_view line, std::vector& args, bool skipFirst) + { + std::wstring commandLineW = Utility::ConvertToUTF16(line); + int argc = 0; + wil::unique_hlocal_ptr argv{ CommandLineToArgvW(commandLineW.c_str(), &argc) }; + THROW_LAST_ERROR_IF_NULL(argv); + + for (int i = (skipFirst ? 1 : 0); i < argc; ++i) + { + args.emplace_back(Utility::ConvertToUTF8(argv.get()[i])); + } + } +} diff --git a/src/AppInstallerCLICore/CompletionData.h b/src/AppInstallerCLICore/CompletionData.h index e87c9013c3..dad91ddb66 100644 --- a/src/AppInstallerCLICore/CompletionData.h +++ b/src/AppInstallerCLICore/CompletionData.h @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionArgs.h" -#include "Invocation.h" - -#include -#include -#include -#include - - -namespace AppInstaller::CLI -{ - // Data created by CompleteCommand to be consumed by other commands in order - // to provide context sensitive results. - struct CompletionData - { - CompletionData(std::string_view word, std::string_view commandLine, std::string_view position); - - const std::string& Word() const { return m_word; } - Invocation& BeforeWord() const { return *m_argsBeforeWord; } - Invocation& AfterWord() const { return *m_argsAfterWord; } - - private: - static void ParseInto(std::string_view line, std::vector& args, bool skipFirst); - - std::string m_word; - std::unique_ptr m_argsBeforeWord; - std::unique_ptr m_argsAfterWord; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionArgs.h" +#include "Invocation.h" + +#include +#include +#include +#include + + +namespace AppInstaller::CLI +{ + // Data created by CompleteCommand to be consumed by other commands in order + // to provide context sensitive results. + struct CompletionData + { + CompletionData(std::string_view word, std::string_view commandLine, std::string_view position); + + const std::string& Word() const { return m_word; } + Invocation& BeforeWord() const { return *m_argsBeforeWord; } + Invocation& AfterWord() const { return *m_argsAfterWord; } + + private: + static void ParseInto(std::string_view line, std::vector& args, bool skipFirst); + + std::string m_word; + std::unique_ptr m_argsBeforeWord; + std::unique_ptr m_argsAfterWord; + }; +} diff --git a/src/AppInstallerCLICore/ConfigurationCommon.cpp b/src/AppInstallerCLICore/ConfigurationCommon.cpp index 7111757493..21b4453a8a 100644 --- a/src/AppInstallerCLICore/ConfigurationCommon.cpp +++ b/src/AppInstallerCLICore/ConfigurationCommon.cpp @@ -1,102 +1,102 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationCommon.h" -#include "ExecutionArgs.h" -#include -#include "Command.h" -#include -#include - -namespace AppInstaller::CLI -{ - using namespace winrt::Microsoft::Management::Configuration; - - namespace - { - constexpr std::string_view s_ModulePath_Default = "default"; - constexpr std::string_view s_ModulePath_CurrentUser = "currentuser"; - constexpr std::string_view s_ModulePath_AllUsers = "allusers"; - - struct ModulePathInfo - { - SetProcessorFactory::PwshConfigurationProcessorLocation location; - std::optional customLocation; - }; - - ModulePathInfo GetModulePathInfo(Execution::Args& execArgs) - { - if (execArgs.Contains(Execution::Args::Type::ConfigurationModulePath)) - { - auto modulePath = execArgs.GetArg(Execution::Args::Type::ConfigurationModulePath); - - if (Utility::CaseInsensitiveEquals(modulePath, s_ModulePath_Default)) - { - return { SetProcessorFactory::PwshConfigurationProcessorLocation::Default, {} }; - } - else if (Utility::CaseInsensitiveEquals(modulePath, s_ModulePath_CurrentUser)) - { - return { SetProcessorFactory::PwshConfigurationProcessorLocation::CurrentUser, {} }; - } - else if (Utility::CaseInsensitiveEquals(modulePath, s_ModulePath_AllUsers)) - { - return { SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers, {} }; - } - else - { - return { SetProcessorFactory::PwshConfigurationProcessorLocation::Custom, std::string(execArgs.GetArg(Execution::Args::Type::ConfigurationModulePath)) }; - } - } - - std::filesystem::path defaultModuleRoot = Settings::User().Get(); - - if (!defaultModuleRoot.empty()) - { - return { SetProcessorFactory::PwshConfigurationProcessorLocation::Custom, defaultModuleRoot.u8string() }; - } - - return { SetProcessorFactory::PwshConfigurationProcessorLocation::WinGetModulePath, {} }; - } - } - - namespace Configuration - { - void ValidateCommonArguments(Execution::Args& execArgs, bool requireConfigurationSetChoice) - { - auto modulePath = GetModulePathInfo(execArgs); - - if (modulePath.location == SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers && !Runtime::IsRunningAsAdmin()) - { - throw CommandException(Resource::String::ConfigurationAllUsersElevated); - } - - if (modulePath.location == SetProcessorFactory::PwshConfigurationProcessorLocation::Custom) - { - auto path = std::filesystem::path{ modulePath.customLocation.value() }; - if (!path.is_absolute()) - { - throw CommandException(Resource::String::ConfigurationModulePathArgError); - } - } - - if (requireConfigurationSetChoice && - !WI_IsFlagSet(Argument::GetCategoriesPresent(execArgs), ArgTypeCategory::ConfigurationSetChoice)) - { - throw CommandException(Resource::String::RequiredArgError("file"_liv)); - } - } - - void SetModulePath(Execution::Context& context, IConfigurationSetProcessorFactory const& factory) - { - auto pwshFactory = factory.as(); - auto modulePath = GetModulePathInfo(context.Args); - - if (modulePath.location == SetProcessorFactory::PwshConfigurationProcessorLocation::Custom) - { - pwshFactory.CustomLocation(winrt::to_hstring(modulePath.customLocation.value())); - } - - pwshFactory.Location(modulePath.location); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationCommon.h" +#include "ExecutionArgs.h" +#include +#include "Command.h" +#include +#include + +namespace AppInstaller::CLI +{ + using namespace winrt::Microsoft::Management::Configuration; + + namespace + { + constexpr std::string_view s_ModulePath_Default = "default"; + constexpr std::string_view s_ModulePath_CurrentUser = "currentuser"; + constexpr std::string_view s_ModulePath_AllUsers = "allusers"; + + struct ModulePathInfo + { + SetProcessorFactory::PwshConfigurationProcessorLocation location; + std::optional customLocation; + }; + + ModulePathInfo GetModulePathInfo(Execution::Args& execArgs) + { + if (execArgs.Contains(Execution::Args::Type::ConfigurationModulePath)) + { + auto modulePath = execArgs.GetArg(Execution::Args::Type::ConfigurationModulePath); + + if (Utility::CaseInsensitiveEquals(modulePath, s_ModulePath_Default)) + { + return { SetProcessorFactory::PwshConfigurationProcessorLocation::Default, {} }; + } + else if (Utility::CaseInsensitiveEquals(modulePath, s_ModulePath_CurrentUser)) + { + return { SetProcessorFactory::PwshConfigurationProcessorLocation::CurrentUser, {} }; + } + else if (Utility::CaseInsensitiveEquals(modulePath, s_ModulePath_AllUsers)) + { + return { SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers, {} }; + } + else + { + return { SetProcessorFactory::PwshConfigurationProcessorLocation::Custom, std::string(execArgs.GetArg(Execution::Args::Type::ConfigurationModulePath)) }; + } + } + + std::filesystem::path defaultModuleRoot = Settings::User().Get(); + + if (!defaultModuleRoot.empty()) + { + return { SetProcessorFactory::PwshConfigurationProcessorLocation::Custom, defaultModuleRoot.u8string() }; + } + + return { SetProcessorFactory::PwshConfigurationProcessorLocation::WinGetModulePath, {} }; + } + } + + namespace Configuration + { + void ValidateCommonArguments(Execution::Args& execArgs, bool requireConfigurationSetChoice) + { + auto modulePath = GetModulePathInfo(execArgs); + + if (modulePath.location == SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers && !Runtime::IsRunningAsAdmin()) + { + throw CommandException(Resource::String::ConfigurationAllUsersElevated); + } + + if (modulePath.location == SetProcessorFactory::PwshConfigurationProcessorLocation::Custom) + { + auto path = std::filesystem::path{ modulePath.customLocation.value() }; + if (!path.is_absolute()) + { + throw CommandException(Resource::String::ConfigurationModulePathArgError); + } + } + + if (requireConfigurationSetChoice && + !WI_IsFlagSet(Argument::GetCategoriesPresent(execArgs), ArgTypeCategory::ConfigurationSetChoice)) + { + throw CommandException(Resource::String::RequiredArgError("file"_liv)); + } + } + + void SetModulePath(Execution::Context& context, IConfigurationSetProcessorFactory const& factory) + { + auto pwshFactory = factory.as(); + auto modulePath = GetModulePathInfo(context.Args); + + if (modulePath.location == SetProcessorFactory::PwshConfigurationProcessorLocation::Custom) + { + pwshFactory.CustomLocation(winrt::to_hstring(modulePath.customLocation.value())); + } + + pwshFactory.Location(modulePath.location); + } + } +} diff --git a/src/AppInstallerCLICore/ConfigurationCommon.h b/src/AppInstallerCLICore/ConfigurationCommon.h index b63f87b25a..d4689ff142 100644 --- a/src/AppInstallerCLICore/ConfigurationCommon.h +++ b/src/AppInstallerCLICore/ConfigurationCommon.h @@ -1,17 +1,17 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" -#include - -namespace AppInstaller::CLI -{ - namespace Configuration - { - // Validates common arguments between configuration commands. - void ValidateCommonArguments(Execution::Args& execArgs, bool requireConfigurationSetChoice = false); - - // Sets the module path to install modules in the set processor. - void SetModulePath(Execution::Context& context, winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory const& factory); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" +#include + +namespace AppInstaller::CLI +{ + namespace Configuration + { + // Validates common arguments between configuration commands. + void ValidateCommonArguments(Execution::Args& execArgs, bool requireConfigurationSetChoice = false); + + // Sets the module path to install modules in the set processor. + void SetModulePath(Execution::Context& context, winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory const& factory); + } +} diff --git a/src/AppInstallerCLICore/ConfigurationContext.cpp b/src/AppInstallerCLICore/ConfigurationContext.cpp index f16f351ff4..401947fbbe 100644 --- a/src/AppInstallerCLICore/ConfigurationContext.cpp +++ b/src/AppInstallerCLICore/ConfigurationContext.cpp @@ -1,86 +1,86 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationContext.h" -#include - -using namespace winrt::Microsoft::Management::Configuration; - -namespace AppInstaller::CLI::Execution -{ - namespace details - { - struct ConfigurationContextData - { - ConfigurationProcessor Processor = nullptr; - ConfigurationSet Set = nullptr; - std::vector History; - }; - } - - ConfigurationContext::ConfigurationContext() : m_data(std::make_unique()) - { - } - - ConfigurationContext::~ConfigurationContext() = default; - - ConfigurationContext::ConfigurationContext(ConfigurationContext&&) = default; - ConfigurationContext& ConfigurationContext::operator=(ConfigurationContext&&) = default; - - ConfigurationProcessor& ConfigurationContext::Processor() - { - return m_data->Processor; - } - - const ConfigurationProcessor& ConfigurationContext::Processor() const - { - return m_data->Processor; - } - - void ConfigurationContext::Processor(const ConfigurationProcessor& value) - { - m_data->Processor = value; - } - - void ConfigurationContext::Processor(ConfigurationProcessor&& value) - { - m_data->Processor = std::move(value); - } - - ConfigurationSet& ConfigurationContext::Set() - { - return m_data->Set; - } - - const ConfigurationSet& ConfigurationContext::Set() const - { - return m_data->Set; - } - - void ConfigurationContext::Set(const ConfigurationSet& value) - { - m_data->Set = value; - } - - void ConfigurationContext::Set(ConfigurationSet&& value) - { - m_data->Set = std::move(value); - } - - std::vector& ConfigurationContext::History() - { - return m_data->History; - } - - const std::vector& ConfigurationContext::History() const - { - return m_data->History; - } - - void ConfigurationContext::History(const winrt::Windows::Foundation::Collections::IVector& value) - { - std::vector history{ value.Size() }; - value.GetMany(0, history); - m_data->History = std::move(history); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationContext.h" +#include + +using namespace winrt::Microsoft::Management::Configuration; + +namespace AppInstaller::CLI::Execution +{ + namespace details + { + struct ConfigurationContextData + { + ConfigurationProcessor Processor = nullptr; + ConfigurationSet Set = nullptr; + std::vector History; + }; + } + + ConfigurationContext::ConfigurationContext() : m_data(std::make_unique()) + { + } + + ConfigurationContext::~ConfigurationContext() = default; + + ConfigurationContext::ConfigurationContext(ConfigurationContext&&) = default; + ConfigurationContext& ConfigurationContext::operator=(ConfigurationContext&&) = default; + + ConfigurationProcessor& ConfigurationContext::Processor() + { + return m_data->Processor; + } + + const ConfigurationProcessor& ConfigurationContext::Processor() const + { + return m_data->Processor; + } + + void ConfigurationContext::Processor(const ConfigurationProcessor& value) + { + m_data->Processor = value; + } + + void ConfigurationContext::Processor(ConfigurationProcessor&& value) + { + m_data->Processor = std::move(value); + } + + ConfigurationSet& ConfigurationContext::Set() + { + return m_data->Set; + } + + const ConfigurationSet& ConfigurationContext::Set() const + { + return m_data->Set; + } + + void ConfigurationContext::Set(const ConfigurationSet& value) + { + m_data->Set = value; + } + + void ConfigurationContext::Set(ConfigurationSet&& value) + { + m_data->Set = std::move(value); + } + + std::vector& ConfigurationContext::History() + { + return m_data->History; + } + + const std::vector& ConfigurationContext::History() const + { + return m_data->History; + } + + void ConfigurationContext::History(const winrt::Windows::Foundation::Collections::IVector& value) + { + std::vector history{ value.Size() }; + value.GetMany(0, history); + m_data->History = std::move(history); + } +} diff --git a/src/AppInstallerCLICore/ConfigurationContext.h b/src/AppInstallerCLICore/ConfigurationContext.h index c1cfb83b68..f9ad029735 100644 --- a/src/AppInstallerCLICore/ConfigurationContext.h +++ b/src/AppInstallerCLICore/ConfigurationContext.h @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace winrt::Microsoft::Management::Configuration -{ - struct ConfigurationProcessor; - struct ConfigurationSet; -} - -namespace AppInstaller::CLI::Execution -{ - namespace details - { - struct ConfigurationContextData; - } - - struct ConfigurationContext - { - using ConfigurationSet = winrt::Microsoft::Management::Configuration::ConfigurationSet; - - ConfigurationContext(); - ~ConfigurationContext(); - - ConfigurationContext(ConfigurationContext&) = delete; - ConfigurationContext& operator=(ConfigurationContext&) = delete; - - ConfigurationContext(ConfigurationContext&&); - ConfigurationContext& operator=(ConfigurationContext&&); - - winrt::Microsoft::Management::Configuration::ConfigurationProcessor& Processor(); - const winrt::Microsoft::Management::Configuration::ConfigurationProcessor& Processor() const; - void Processor(const winrt::Microsoft::Management::Configuration::ConfigurationProcessor& value); - void Processor(winrt::Microsoft::Management::Configuration::ConfigurationProcessor&& value); - - ConfigurationSet& Set(); - const ConfigurationSet& Set() const; - void Set(const ConfigurationSet& value); - void Set(ConfigurationSet&& value); - - std::vector& History(); - const std::vector& History() const; - void History(const winrt::Windows::Foundation::Collections::IVector& value); - - private: - std::unique_ptr m_data; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace winrt::Microsoft::Management::Configuration +{ + struct ConfigurationProcessor; + struct ConfigurationSet; +} + +namespace AppInstaller::CLI::Execution +{ + namespace details + { + struct ConfigurationContextData; + } + + struct ConfigurationContext + { + using ConfigurationSet = winrt::Microsoft::Management::Configuration::ConfigurationSet; + + ConfigurationContext(); + ~ConfigurationContext(); + + ConfigurationContext(ConfigurationContext&) = delete; + ConfigurationContext& operator=(ConfigurationContext&) = delete; + + ConfigurationContext(ConfigurationContext&&); + ConfigurationContext& operator=(ConfigurationContext&&); + + winrt::Microsoft::Management::Configuration::ConfigurationProcessor& Processor(); + const winrt::Microsoft::Management::Configuration::ConfigurationProcessor& Processor() const; + void Processor(const winrt::Microsoft::Management::Configuration::ConfigurationProcessor& value); + void Processor(winrt::Microsoft::Management::Configuration::ConfigurationProcessor&& value); + + ConfigurationSet& Set(); + const ConfigurationSet& Set() const; + void Set(const ConfigurationSet& value); + void Set(ConfigurationSet&& value); + + std::vector& History(); + const std::vector& History() const; + void History(const winrt::Windows::Foundation::Collections::IVector& value); + + private: + std::unique_ptr m_data; + }; +} diff --git a/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp b/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp index d2e98dd2c9..7105b861a7 100644 --- a/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp +++ b/src/AppInstallerCLICore/ConfigurationDynamicRuntimeFactory.cpp @@ -1,570 +1,570 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/ConfigurationSetProcessorFactoryRemoting.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace winrt::Windows::Foundation; -using namespace winrt::Microsoft::Management::Configuration; -using namespace winrt::Windows::Storage; - -namespace AppInstaller::CLI::ConfigurationRemoting -{ - namespace anonymous - { -#ifndef AICLI_DISABLE_TEST_HOOKS - constexpr std::wstring_view EnableTestModeTestGuid = L"1e62d683-2999-44e7-81f7-6f8f35e8d731"; - constexpr std::wstring_view ForceHighIntegrityLevelUnitsTestGuid = L"f698d20f-3584-4f28-bc75-28037e08e651"; - constexpr std::wstring_view EnableRestrictedIntegrityLevelTestGuid = L"5cae3226-185f-4289-815c-3c089d238dc6"; - - // Checks the configuration set metadata for a specific test guid that controls the behavior flow. - bool GetConfigurationSetMetadataOverride(const ConfigurationSet& configurationSet, const std::wstring_view& testGuid) - { - auto metadataOverride = configurationSet.Metadata().TryLookup(testGuid); - if (metadataOverride) - { - auto metadataOverrideProperty = metadataOverride.try_as(); - if (metadataOverrideProperty && metadataOverrideProperty.Type() == PropertyType::Boolean) - { - return metadataOverrideProperty.GetBoolean(); - } - } - - return false; - } -#endif - - // This is implemented completely in the packaged context for now, if we want to make it more configurable, we will probably want to move it to configuration and - // have this implementation leverage that one with an event handler for the packaged specifics. - // TODO: Add SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties and pass values along to sets on creation - // In turn, any properties must only be set via the command line (or eventual UI requests to the user). - struct DynamicFactory : winrt::implements, winrt::cloaked>, WinRT::LifetimeWatcherBase - { - DynamicFactory(ProcessorEngine processorEngine); - - IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet); - - winrt::event_token Diagnostics(const EventHandler& handler); - void Diagnostics(const winrt::event_token& token) noexcept; - - DiagnosticLevel MinimumLevel(); - void MinimumLevel(DiagnosticLevel value); - - HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); - - IConfigurationSetProcessorFactory& DefaultFactory(); - - void SendDiagnostics(const IDiagnosticInformation& information); - - Collections::IVectorView AdditionalModulePaths() const - { - THROW_HR(E_NOTIMPL); - } - - void AdditionalModulePaths(const Collections::IVectorView&) - { - THROW_HR(E_NOTIMPL); - } - - SetProcessorFactory::PwshConfigurationProcessorPolicy Policy() const - { - THROW_HR(E_NOTIMPL); - } - - void Policy(SetProcessorFactory::PwshConfigurationProcessorPolicy) - { - THROW_HR(E_NOTIMPL); - } - - SetProcessorFactory::PwshConfigurationProcessorLocation Location() const - { - return m_location; - } - - void Location(SetProcessorFactory::PwshConfigurationProcessorLocation value) - { - auto pwshFactory = m_defaultRemoteFactory.as(); - pwshFactory.Location(value); - m_location = value; - } - - winrt::hstring CustomLocation() const - { - return m_customLocation; - } - - void CustomLocation(winrt::hstring value) - { - auto pwshFactory = m_defaultRemoteFactory.as(); - pwshFactory.CustomLocation(value); - m_customLocation = value; - } - - // Implement a subset of IMap to enable property bag semantics - uint32_t Size() { THROW_HR(E_NOTIMPL); } - void Clear() { THROW_HR(E_NOTIMPL); } - Collections::IMapView GetView() { THROW_HR(E_NOTIMPL); } - bool HasKey(winrt::hstring) { THROW_HR(E_NOTIMPL); } - void Remove(winrt::hstring) { THROW_HR(E_NOTIMPL); } - - bool Insert(winrt::hstring key, winrt::hstring value) - { - auto result = m_defaultRemoteFactory.as>().Insert(key, value); - m_factoryMapValues[key] = value; - return result; - } - - winrt::hstring Lookup(winrt::hstring key) - { - return m_defaultRemoteFactory.as>().Lookup(key); - } - - ProcessorEngine Engine() const - { - return m_processorEngine; - } - - std::optional GetFactoryMapValue(winrt::hstring key) - { - auto itr = m_factoryMapValues.find(key); - return itr != m_factoryMapValues.end() ? std::make_optional(itr->second) : std::nullopt; - } - - private: - IConfigurationSetProcessorFactory m_defaultRemoteFactory; - winrt::event> m_diagnostics; - IConfigurationSetProcessorFactory::Diagnostics_revoker m_factoryDiagnosticsEventRevoker; - std::mutex m_diagnosticsMutex; - DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational; - SetProcessorFactory::PwshConfigurationProcessorLocation m_location = SetProcessorFactory::PwshConfigurationProcessorLocation::Default; - winrt::hstring m_customLocation; - ProcessorEngine m_processorEngine; - std::map m_factoryMapValues; - }; - - struct DynamicProcessorInfo - { - IConfigurationSetProcessorFactory Factory; - IConfigurationSetProcessor Processor; - IConfigurationSetProcessorFactory::Diagnostics_revoker DiagnosticsEventRevoker; - }; - - struct DynamicSetProcessor : winrt::implements - { - using ProcessorMap = std::map; - - DynamicSetProcessor(winrt::com_ptr dynamicFactory, IConfigurationSetProcessor defaultRemoteSetProcessor, const ConfigurationSet& configurationSet) : - m_dynamicFactory(std::move(dynamicFactory)), m_configurationSet(configurationSet) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (m_configurationSet) - { - m_enableTestMode = GetConfigurationSetMetadataOverride(m_configurationSet, EnableTestModeTestGuid); - m_enableRestrictedIntegrityLevel = GetConfigurationSetMetadataOverride(m_configurationSet, EnableRestrictedIntegrityLevelTestGuid); - m_forceHighIntegrityLevelUnits = GetConfigurationSetMetadataOverride(m_configurationSet, ForceHighIntegrityLevelUnitsTestGuid); - } - - m_currentIntegrityLevel = m_enableTestMode ? Security::IntegrityLevel::Medium : Security::GetEffectiveIntegrityLevel(); -#else - m_currentIntegrityLevel = Security::GetEffectiveIntegrityLevel(); -#endif - - m_setIntegrityLevel = m_currentIntegrityLevel; - - if (m_configurationSet) - { - m_setIntegrityLevel = SecurityContextToIntegrityLevel(m_configurationSet.Environment().Context()); - - // Check for multiple integrity level requirements - bool multipleIntegrityLevels = false; - bool higherIntegrityLevelsThanCurrent = false; - for (const auto& environment : m_configurationSet.GetUnitEnvironments()) - { - auto integrityLevel = SecurityContextToIntegrityLevel(environment.Context()); - if (integrityLevel != m_currentIntegrityLevel) - { - multipleIntegrityLevels = true; - - if (ToIntegral(m_currentIntegrityLevel) < ToIntegral(integrityLevel)) - { - higherIntegrityLevelsThanCurrent = true; - break; - } - } - } - - // Prevent supplied parameters from crossing integrity levels - for (const auto& parameter : m_configurationSet.Parameters()) - { - if (parameter.ProvidedValue() != nullptr) - { - THROW_HR_IF(WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY, higherIntegrityLevelsThanCurrent || (multipleIntegrityLevels && parameter.IsSecure())); - } - } - } - - m_setProcessors.emplace(m_currentIntegrityLevel, DynamicProcessorInfo{ m_dynamicFactory->DefaultFactory(), defaultRemoteSetProcessor}); - } - - IConfigurationUnitProcessorDetails GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) - { - // Always get processor details from the current integrity level - return m_setProcessors[m_currentIntegrityLevel].Processor.GetUnitProcessorDetails(unit, detailFlags); - } - - // Creates a configuration unit processor for the given unit. - IConfigurationUnitProcessor CreateUnitProcessor(const ConfigurationUnit& unit) - { - // Determine and create set processors for all required integrity levels. - // Doing this here avoids creating them if the only call is going to be for details (ex. `configure show`) - std::call_once(m_createUnitSetProcessorsOnce, - [&]() - { - if (m_configurationSet) - { - for (const auto& environment : m_configurationSet.GetUnitEnvironments()) - { - Security::IntegrityLevel requiredIntegrityLevel = SecurityContextToIntegrityLevel(environment.Context()); - - if (m_setProcessors.find(requiredIntegrityLevel) == m_setProcessors.end()) - { - CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); - } - } - } - }); - - // Create set and unit processor for current unit. -#ifndef AICLI_DISABLE_TEST_HOOKS - Security::IntegrityLevel requiredIntegrityLevel = m_forceHighIntegrityLevelUnits ? Security::IntegrityLevel::High : GetIntegrityLevelForUnit(unit); -#else - Security::IntegrityLevel requiredIntegrityLevel = GetIntegrityLevelForUnit(unit); -#endif - - auto itr = m_setProcessors.find(requiredIntegrityLevel); - if (itr == m_setProcessors.end()) - { - THROW_WIN32_IF_MSG(ERROR_NOT_SUPPORTED, !m_configurationSet, "Using configuration unit integrity level other than current level without a configuration set is not supported."); - itr = CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); - } - - return itr->second.Processor.CreateUnitProcessor(unit); - } - - Collections::IVector FindUnitProcessors(const FindUnitProcessorsOptions& findOptions) - { - IFindUnitProcessorsSetProcessor findUnitProcessorsSetProcessor; - - if (m_setProcessors[m_currentIntegrityLevel].Processor.try_as(findUnitProcessorsSetProcessor)) - { - return findUnitProcessorsSetProcessor.FindUnitProcessors(findOptions); - } - else - { - AICLI_LOG(Config, Error, << "Set Processor does not support FindUnitProcessors operation"); - THROW_HR(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR); - } - } - - private: - // Converts the string representation of SecurityContext to the target integrity level for this instance - Security::IntegrityLevel SecurityContextToIntegrityLevel(SecurityContext securityContext) - { - switch (securityContext) - { - case SecurityContext::Current: - return m_setIntegrityLevel; - case SecurityContext::Restricted: -#ifndef AICLI_DISABLE_TEST_HOOKS - if (m_enableRestrictedIntegrityLevel) - { - return Security::IntegrityLevel::Medium; - } - else -#endif - { - // Not supporting elevated callers downgrading at the moment. - THROW_WIN32(ERROR_NOT_SUPPORTED); - - // Technically this means the default level of the user token, so if UAC is disabled it would be the only integrity level (aka current). - // return Security::IntegrityLevel::Medium; - } - case SecurityContext::Elevated: - return Security::IntegrityLevel::High; - default: - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - } - - // Gets the integrity level that the given unit should be run at - Security::IntegrityLevel GetIntegrityLevelForUnit(const ConfigurationUnit& unit) - { - return SecurityContextToIntegrityLevel(unit.Environment().Context()); - } - - // Serializes the set properties to be sent to the remote server - std::string SerializeSetProperties() - { - Json::Value json{ Json::ValueType::objectValue }; - - json["path"] = winrt::to_string(m_configurationSet.Path()); - - std::string locationString; - switch (m_dynamicFactory->Location()) - { - case SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers: - locationString = "AllUsers"; - break; - case SetProcessorFactory::PwshConfigurationProcessorLocation::CurrentUser: - locationString = "CurrentUser"; - break; - case SetProcessorFactory::PwshConfigurationProcessorLocation::Custom: - locationString = Utility::ConvertToUTF8(m_dynamicFactory->CustomLocation()); - break; - case SetProcessorFactory::PwshConfigurationProcessorLocation::Default: - break; - } - - if (!locationString.empty()) - { - json["modulePath"] = locationString; - } - - // Ensure that we always pass a path to the executable - if (m_dynamicFactory->Engine() == ProcessorEngine::DSCv3) - { - winrt::hstring dscExecutablePathPropertyName = ToHString(PropertyName::DscExecutablePath); - std::optional dscExecutablePath = m_dynamicFactory->GetFactoryMapValue(dscExecutablePathPropertyName); - bool usingFoundPath = false; - - if (!dscExecutablePath) - { - dscExecutablePath = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePath)); - usingFoundPath = true; - } - - if (dscExecutablePath->empty()) - { - // This is backstop to prevent a case where dsc.exe not found. - AICLI_LOG(Config, Error, << "Could not find dsc.exe, it must be provided by the user."); - THROW_WIN32(ERROR_FILE_NOT_FOUND); - } - - json["processorPath"] = Utility::ConvertToUTF8(dscExecutablePath.value()); - - if (usingFoundPath) - { - // FoundDscExecutablePathHash/IsAlias are computed on the remote side alongside FoundDscExecutablePath. - winrt::hstring pathHash = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePathHash)); - if (!pathHash.empty()) - { - json["processorPathHash"] = Utility::ConvertToUTF8(pathHash); - } - - winrt::hstring pathIsAlias = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePathIsAlias)); - if (!pathIsAlias.empty()) - { - json["processorPathIsAlias"] = (pathIsAlias == L"true"); - } - } - else - { - // DscExecutablePathHash/IsAlias are set locally via the audit block. - auto pathHash = m_dynamicFactory->GetFactoryMapValue(ToHString(PropertyName::DscExecutablePathHash)); - if (pathHash) - { - json["processorPathHash"] = Utility::ConvertToUTF8(pathHash.value()); - } - - auto pathIsAlias = m_dynamicFactory->GetFactoryMapValue(ToHString(PropertyName::DscExecutablePathIsAlias)); - if (pathIsAlias) - { - json["processorPathIsAlias"] = (pathIsAlias.value() == L"true"); - } - } - } - - Json::StreamWriterBuilder writerBuilder; - writerBuilder.settings_["indentation"] = "\t"; - return Json::writeString(writerBuilder, json); - } - - /// - /// Creates a separate configuration set containing high integrity units and returns the serialized string value. - /// - /// Serialized string value. - std::string SerializeHighIntegrityLevelSet() - { - ConfigurationSet highIntegritySet; - highIntegritySet.SchemaVersion(m_configurationSet.SchemaVersion()); - highIntegritySet.Metadata(m_configurationSet.Metadata()); - highIntegritySet.Parameters(m_configurationSet.Parameters()); - highIntegritySet.Variables(m_configurationSet.Variables()); - - std::vector highIntegrityUnits; - auto units = m_configurationSet.Units(); - - for (auto unit : units) - { - if (unit.IsActive() && GetIntegrityLevelForUnit(unit) == Security::IntegrityLevel::High) - { - highIntegrityUnits.emplace_back(unit); - } - } - - highIntegritySet.Units(std::move(highIntegrityUnits)); - - // Serialize high integrity set and return output string. - Streams::InMemoryRandomAccessStream memoryStream; - highIntegritySet.Serialize(memoryStream); - - Streams::DataReader reader(memoryStream.GetInputStreamAt(0)); - THROW_HR_IF(E_UNEXPECTED, memoryStream.Size() > std::numeric_limits::max()); - uint32_t streamSize = (uint32_t)memoryStream.Size(); - std::vector bytes; - bytes.resize(streamSize); - reader.LoadAsync(streamSize); - reader.ReadBytes(bytes); - reader.DetachStream(); - memoryStream.Close(); - - return { bytes.begin(), bytes.end() }; - } - - ProcessorMap::iterator CreateSetProcessorForIntegrityLevel(Security::IntegrityLevel integrityLevel) - { - IConfigurationSetProcessorFactory factory; - IConfigurationSetProcessorFactory::Diagnostics_revoker factoryDiagnosticsEventRevoker; - - // If we got here, the only option is that the current integrity level is not High. - if (integrityLevel == Security::IntegrityLevel::High) - { - bool useRunAs = true; -#ifndef AICLI_DISABLE_TEST_HOOKS - useRunAs = !m_enableTestMode; -#endif - - factory = CreateOutOfProcessFactory(m_dynamicFactory->Engine(), useRunAs, SerializeSetProperties(), SerializeHighIntegrityLevelSet()); - } - else - { - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - - if (factory) - { - factory.MinimumLevel(m_dynamicFactory->MinimumLevel()); - factoryDiagnosticsEventRevoker = factory.Diagnostics(winrt::auto_revoke, - [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) - { - if (auto strong_this{ weak_this.get() }) - { - strong_this->m_dynamicFactory->SendDiagnostics(information); - } - }); - - winrt::hstring propertyName = ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled); - if (auto propertyValue = m_dynamicFactory->GetFactoryMapValue(propertyName)) - { - factory.as>().Insert(propertyName, propertyValue.value()); - } - } - - return m_setProcessors.emplace(integrityLevel, DynamicProcessorInfo{ factory, factory.CreateSetProcessor(m_configurationSet), std::move(factoryDiagnosticsEventRevoker) }).first; - } - - winrt::com_ptr m_dynamicFactory; - Security::IntegrityLevel m_currentIntegrityLevel; - Security::IntegrityLevel m_setIntegrityLevel; - ProcessorMap m_setProcessors; - ConfigurationSet m_configurationSet; - std::once_flag m_createUnitSetProcessorsOnce; - -#ifndef AICLI_DISABLE_TEST_HOOKS - bool m_enableTestMode = false; - bool m_enableRestrictedIntegrityLevel = false; - bool m_forceHighIntegrityLevelUnits = false; -#endif - }; - - DynamicFactory::DynamicFactory(ProcessorEngine processorEngine) - { - m_processorEngine = processorEngine; - m_defaultRemoteFactory = CreateOutOfProcessFactory(processorEngine); - - if (m_defaultRemoteFactory) - { - m_factoryDiagnosticsEventRevoker = m_defaultRemoteFactory.Diagnostics(winrt::auto_revoke, - [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) - { - if (auto strong_this{ weak_this.get() }) - { - strong_this->SendDiagnostics(information); - } - }); - } - } - - IConfigurationSetProcessor DynamicFactory::CreateSetProcessor(const ConfigurationSet& configurationSet) - { - return winrt::make(get_strong(), m_defaultRemoteFactory.CreateSetProcessor(configurationSet), configurationSet); - } - - winrt::event_token DynamicFactory::Diagnostics(const EventHandler& handler) - { - return m_diagnostics.add(handler); - } - - void DynamicFactory::Diagnostics(const winrt::event_token& token) noexcept - { - m_diagnostics.remove(token); - } - - DiagnosticLevel DynamicFactory::MinimumLevel() - { - return m_minimumLevel; - } - - void DynamicFactory::MinimumLevel(DiagnosticLevel value) - { - m_minimumLevel = value; - - if (m_defaultRemoteFactory) - { - m_defaultRemoteFactory.MinimumLevel(value); - } - } - - HRESULT STDMETHODCALLTYPE DynamicFactory::SetLifetimeWatcher(IUnknown* watcher) - { - return WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); - } - - IConfigurationSetProcessorFactory& DynamicFactory::DefaultFactory() - { - return m_defaultRemoteFactory; - } - - void DynamicFactory::SendDiagnostics(const IDiagnosticInformation& information) try - { - if (information.Level() >= m_minimumLevel) - { - std::lock_guard lock{ m_diagnosticsMutex }; - m_diagnostics(*this, information); - } - } - // While diagnostics can be important, a failure to send them should not cause additional issues. - catch (...) {} - } - - winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateDynamicRuntimeFactory(ProcessorEngine processorEngine) - { - return winrt::make(processorEngine); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt::Windows::Foundation; +using namespace winrt::Microsoft::Management::Configuration; +using namespace winrt::Windows::Storage; + +namespace AppInstaller::CLI::ConfigurationRemoting +{ + namespace anonymous + { +#ifndef AICLI_DISABLE_TEST_HOOKS + constexpr std::wstring_view EnableTestModeTestGuid = L"1e62d683-2999-44e7-81f7-6f8f35e8d731"; + constexpr std::wstring_view ForceHighIntegrityLevelUnitsTestGuid = L"f698d20f-3584-4f28-bc75-28037e08e651"; + constexpr std::wstring_view EnableRestrictedIntegrityLevelTestGuid = L"5cae3226-185f-4289-815c-3c089d238dc6"; + + // Checks the configuration set metadata for a specific test guid that controls the behavior flow. + bool GetConfigurationSetMetadataOverride(const ConfigurationSet& configurationSet, const std::wstring_view& testGuid) + { + auto metadataOverride = configurationSet.Metadata().TryLookup(testGuid); + if (metadataOverride) + { + auto metadataOverrideProperty = metadataOverride.try_as(); + if (metadataOverrideProperty && metadataOverrideProperty.Type() == PropertyType::Boolean) + { + return metadataOverrideProperty.GetBoolean(); + } + } + + return false; + } +#endif + + // This is implemented completely in the packaged context for now, if we want to make it more configurable, we will probably want to move it to configuration and + // have this implementation leverage that one with an event handler for the packaged specifics. + // TODO: Add SetProcessorFactory::IPwshConfigurationSetProcessorFactoryProperties and pass values along to sets on creation + // In turn, any properties must only be set via the command line (or eventual UI requests to the user). + struct DynamicFactory : winrt::implements, winrt::cloaked>, WinRT::LifetimeWatcherBase + { + DynamicFactory(ProcessorEngine processorEngine); + + IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet); + + winrt::event_token Diagnostics(const EventHandler& handler); + void Diagnostics(const winrt::event_token& token) noexcept; + + DiagnosticLevel MinimumLevel(); + void MinimumLevel(DiagnosticLevel value); + + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); + + IConfigurationSetProcessorFactory& DefaultFactory(); + + void SendDiagnostics(const IDiagnosticInformation& information); + + Collections::IVectorView AdditionalModulePaths() const + { + THROW_HR(E_NOTIMPL); + } + + void AdditionalModulePaths(const Collections::IVectorView&) + { + THROW_HR(E_NOTIMPL); + } + + SetProcessorFactory::PwshConfigurationProcessorPolicy Policy() const + { + THROW_HR(E_NOTIMPL); + } + + void Policy(SetProcessorFactory::PwshConfigurationProcessorPolicy) + { + THROW_HR(E_NOTIMPL); + } + + SetProcessorFactory::PwshConfigurationProcessorLocation Location() const + { + return m_location; + } + + void Location(SetProcessorFactory::PwshConfigurationProcessorLocation value) + { + auto pwshFactory = m_defaultRemoteFactory.as(); + pwshFactory.Location(value); + m_location = value; + } + + winrt::hstring CustomLocation() const + { + return m_customLocation; + } + + void CustomLocation(winrt::hstring value) + { + auto pwshFactory = m_defaultRemoteFactory.as(); + pwshFactory.CustomLocation(value); + m_customLocation = value; + } + + // Implement a subset of IMap to enable property bag semantics + uint32_t Size() { THROW_HR(E_NOTIMPL); } + void Clear() { THROW_HR(E_NOTIMPL); } + Collections::IMapView GetView() { THROW_HR(E_NOTIMPL); } + bool HasKey(winrt::hstring) { THROW_HR(E_NOTIMPL); } + void Remove(winrt::hstring) { THROW_HR(E_NOTIMPL); } + + bool Insert(winrt::hstring key, winrt::hstring value) + { + auto result = m_defaultRemoteFactory.as>().Insert(key, value); + m_factoryMapValues[key] = value; + return result; + } + + winrt::hstring Lookup(winrt::hstring key) + { + return m_defaultRemoteFactory.as>().Lookup(key); + } + + ProcessorEngine Engine() const + { + return m_processorEngine; + } + + std::optional GetFactoryMapValue(winrt::hstring key) + { + auto itr = m_factoryMapValues.find(key); + return itr != m_factoryMapValues.end() ? std::make_optional(itr->second) : std::nullopt; + } + + private: + IConfigurationSetProcessorFactory m_defaultRemoteFactory; + winrt::event> m_diagnostics; + IConfigurationSetProcessorFactory::Diagnostics_revoker m_factoryDiagnosticsEventRevoker; + std::mutex m_diagnosticsMutex; + DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational; + SetProcessorFactory::PwshConfigurationProcessorLocation m_location = SetProcessorFactory::PwshConfigurationProcessorLocation::Default; + winrt::hstring m_customLocation; + ProcessorEngine m_processorEngine; + std::map m_factoryMapValues; + }; + + struct DynamicProcessorInfo + { + IConfigurationSetProcessorFactory Factory; + IConfigurationSetProcessor Processor; + IConfigurationSetProcessorFactory::Diagnostics_revoker DiagnosticsEventRevoker; + }; + + struct DynamicSetProcessor : winrt::implements + { + using ProcessorMap = std::map; + + DynamicSetProcessor(winrt::com_ptr dynamicFactory, IConfigurationSetProcessor defaultRemoteSetProcessor, const ConfigurationSet& configurationSet) : + m_dynamicFactory(std::move(dynamicFactory)), m_configurationSet(configurationSet) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (m_configurationSet) + { + m_enableTestMode = GetConfigurationSetMetadataOverride(m_configurationSet, EnableTestModeTestGuid); + m_enableRestrictedIntegrityLevel = GetConfigurationSetMetadataOverride(m_configurationSet, EnableRestrictedIntegrityLevelTestGuid); + m_forceHighIntegrityLevelUnits = GetConfigurationSetMetadataOverride(m_configurationSet, ForceHighIntegrityLevelUnitsTestGuid); + } + + m_currentIntegrityLevel = m_enableTestMode ? Security::IntegrityLevel::Medium : Security::GetEffectiveIntegrityLevel(); +#else + m_currentIntegrityLevel = Security::GetEffectiveIntegrityLevel(); +#endif + + m_setIntegrityLevel = m_currentIntegrityLevel; + + if (m_configurationSet) + { + m_setIntegrityLevel = SecurityContextToIntegrityLevel(m_configurationSet.Environment().Context()); + + // Check for multiple integrity level requirements + bool multipleIntegrityLevels = false; + bool higherIntegrityLevelsThanCurrent = false; + for (const auto& environment : m_configurationSet.GetUnitEnvironments()) + { + auto integrityLevel = SecurityContextToIntegrityLevel(environment.Context()); + if (integrityLevel != m_currentIntegrityLevel) + { + multipleIntegrityLevels = true; + + if (ToIntegral(m_currentIntegrityLevel) < ToIntegral(integrityLevel)) + { + higherIntegrityLevelsThanCurrent = true; + break; + } + } + } + + // Prevent supplied parameters from crossing integrity levels + for (const auto& parameter : m_configurationSet.Parameters()) + { + if (parameter.ProvidedValue() != nullptr) + { + THROW_HR_IF(WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY, higherIntegrityLevelsThanCurrent || (multipleIntegrityLevels && parameter.IsSecure())); + } + } + } + + m_setProcessors.emplace(m_currentIntegrityLevel, DynamicProcessorInfo{ m_dynamicFactory->DefaultFactory(), defaultRemoteSetProcessor}); + } + + IConfigurationUnitProcessorDetails GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) + { + // Always get processor details from the current integrity level + return m_setProcessors[m_currentIntegrityLevel].Processor.GetUnitProcessorDetails(unit, detailFlags); + } + + // Creates a configuration unit processor for the given unit. + IConfigurationUnitProcessor CreateUnitProcessor(const ConfigurationUnit& unit) + { + // Determine and create set processors for all required integrity levels. + // Doing this here avoids creating them if the only call is going to be for details (ex. `configure show`) + std::call_once(m_createUnitSetProcessorsOnce, + [&]() + { + if (m_configurationSet) + { + for (const auto& environment : m_configurationSet.GetUnitEnvironments()) + { + Security::IntegrityLevel requiredIntegrityLevel = SecurityContextToIntegrityLevel(environment.Context()); + + if (m_setProcessors.find(requiredIntegrityLevel) == m_setProcessors.end()) + { + CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); + } + } + } + }); + + // Create set and unit processor for current unit. +#ifndef AICLI_DISABLE_TEST_HOOKS + Security::IntegrityLevel requiredIntegrityLevel = m_forceHighIntegrityLevelUnits ? Security::IntegrityLevel::High : GetIntegrityLevelForUnit(unit); +#else + Security::IntegrityLevel requiredIntegrityLevel = GetIntegrityLevelForUnit(unit); +#endif + + auto itr = m_setProcessors.find(requiredIntegrityLevel); + if (itr == m_setProcessors.end()) + { + THROW_WIN32_IF_MSG(ERROR_NOT_SUPPORTED, !m_configurationSet, "Using configuration unit integrity level other than current level without a configuration set is not supported."); + itr = CreateSetProcessorForIntegrityLevel(requiredIntegrityLevel); + } + + return itr->second.Processor.CreateUnitProcessor(unit); + } + + Collections::IVector FindUnitProcessors(const FindUnitProcessorsOptions& findOptions) + { + IFindUnitProcessorsSetProcessor findUnitProcessorsSetProcessor; + + if (m_setProcessors[m_currentIntegrityLevel].Processor.try_as(findUnitProcessorsSetProcessor)) + { + return findUnitProcessorsSetProcessor.FindUnitProcessors(findOptions); + } + else + { + AICLI_LOG(Config, Error, << "Set Processor does not support FindUnitProcessors operation"); + THROW_HR(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR); + } + } + + private: + // Converts the string representation of SecurityContext to the target integrity level for this instance + Security::IntegrityLevel SecurityContextToIntegrityLevel(SecurityContext securityContext) + { + switch (securityContext) + { + case SecurityContext::Current: + return m_setIntegrityLevel; + case SecurityContext::Restricted: +#ifndef AICLI_DISABLE_TEST_HOOKS + if (m_enableRestrictedIntegrityLevel) + { + return Security::IntegrityLevel::Medium; + } + else +#endif + { + // Not supporting elevated callers downgrading at the moment. + THROW_WIN32(ERROR_NOT_SUPPORTED); + + // Technically this means the default level of the user token, so if UAC is disabled it would be the only integrity level (aka current). + // return Security::IntegrityLevel::Medium; + } + case SecurityContext::Elevated: + return Security::IntegrityLevel::High; + default: + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + } + + // Gets the integrity level that the given unit should be run at + Security::IntegrityLevel GetIntegrityLevelForUnit(const ConfigurationUnit& unit) + { + return SecurityContextToIntegrityLevel(unit.Environment().Context()); + } + + // Serializes the set properties to be sent to the remote server + std::string SerializeSetProperties() + { + Json::Value json{ Json::ValueType::objectValue }; + + json["path"] = winrt::to_string(m_configurationSet.Path()); + + std::string locationString; + switch (m_dynamicFactory->Location()) + { + case SetProcessorFactory::PwshConfigurationProcessorLocation::AllUsers: + locationString = "AllUsers"; + break; + case SetProcessorFactory::PwshConfigurationProcessorLocation::CurrentUser: + locationString = "CurrentUser"; + break; + case SetProcessorFactory::PwshConfigurationProcessorLocation::Custom: + locationString = Utility::ConvertToUTF8(m_dynamicFactory->CustomLocation()); + break; + case SetProcessorFactory::PwshConfigurationProcessorLocation::Default: + break; + } + + if (!locationString.empty()) + { + json["modulePath"] = locationString; + } + + // Ensure that we always pass a path to the executable + if (m_dynamicFactory->Engine() == ProcessorEngine::DSCv3) + { + winrt::hstring dscExecutablePathPropertyName = ToHString(PropertyName::DscExecutablePath); + std::optional dscExecutablePath = m_dynamicFactory->GetFactoryMapValue(dscExecutablePathPropertyName); + bool usingFoundPath = false; + + if (!dscExecutablePath) + { + dscExecutablePath = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePath)); + usingFoundPath = true; + } + + if (dscExecutablePath->empty()) + { + // This is backstop to prevent a case where dsc.exe not found. + AICLI_LOG(Config, Error, << "Could not find dsc.exe, it must be provided by the user."); + THROW_WIN32(ERROR_FILE_NOT_FOUND); + } + + json["processorPath"] = Utility::ConvertToUTF8(dscExecutablePath.value()); + + if (usingFoundPath) + { + // FoundDscExecutablePathHash/IsAlias are computed on the remote side alongside FoundDscExecutablePath. + winrt::hstring pathHash = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePathHash)); + if (!pathHash.empty()) + { + json["processorPathHash"] = Utility::ConvertToUTF8(pathHash); + } + + winrt::hstring pathIsAlias = m_dynamicFactory->Lookup(ToHString(PropertyName::FoundDscExecutablePathIsAlias)); + if (!pathIsAlias.empty()) + { + json["processorPathIsAlias"] = (pathIsAlias == L"true"); + } + } + else + { + // DscExecutablePathHash/IsAlias are set locally via the audit block. + auto pathHash = m_dynamicFactory->GetFactoryMapValue(ToHString(PropertyName::DscExecutablePathHash)); + if (pathHash) + { + json["processorPathHash"] = Utility::ConvertToUTF8(pathHash.value()); + } + + auto pathIsAlias = m_dynamicFactory->GetFactoryMapValue(ToHString(PropertyName::DscExecutablePathIsAlias)); + if (pathIsAlias) + { + json["processorPathIsAlias"] = (pathIsAlias.value() == L"true"); + } + } + } + + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = "\t"; + return Json::writeString(writerBuilder, json); + } + + /// + /// Creates a separate configuration set containing high integrity units and returns the serialized string value. + /// + /// Serialized string value. + std::string SerializeHighIntegrityLevelSet() + { + ConfigurationSet highIntegritySet; + highIntegritySet.SchemaVersion(m_configurationSet.SchemaVersion()); + highIntegritySet.Metadata(m_configurationSet.Metadata()); + highIntegritySet.Parameters(m_configurationSet.Parameters()); + highIntegritySet.Variables(m_configurationSet.Variables()); + + std::vector highIntegrityUnits; + auto units = m_configurationSet.Units(); + + for (auto unit : units) + { + if (unit.IsActive() && GetIntegrityLevelForUnit(unit) == Security::IntegrityLevel::High) + { + highIntegrityUnits.emplace_back(unit); + } + } + + highIntegritySet.Units(std::move(highIntegrityUnits)); + + // Serialize high integrity set and return output string. + Streams::InMemoryRandomAccessStream memoryStream; + highIntegritySet.Serialize(memoryStream); + + Streams::DataReader reader(memoryStream.GetInputStreamAt(0)); + THROW_HR_IF(E_UNEXPECTED, memoryStream.Size() > std::numeric_limits::max()); + uint32_t streamSize = (uint32_t)memoryStream.Size(); + std::vector bytes; + bytes.resize(streamSize); + reader.LoadAsync(streamSize); + reader.ReadBytes(bytes); + reader.DetachStream(); + memoryStream.Close(); + + return { bytes.begin(), bytes.end() }; + } + + ProcessorMap::iterator CreateSetProcessorForIntegrityLevel(Security::IntegrityLevel integrityLevel) + { + IConfigurationSetProcessorFactory factory; + IConfigurationSetProcessorFactory::Diagnostics_revoker factoryDiagnosticsEventRevoker; + + // If we got here, the only option is that the current integrity level is not High. + if (integrityLevel == Security::IntegrityLevel::High) + { + bool useRunAs = true; +#ifndef AICLI_DISABLE_TEST_HOOKS + useRunAs = !m_enableTestMode; +#endif + + factory = CreateOutOfProcessFactory(m_dynamicFactory->Engine(), useRunAs, SerializeSetProperties(), SerializeHighIntegrityLevelSet()); + } + else + { + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + + if (factory) + { + factory.MinimumLevel(m_dynamicFactory->MinimumLevel()); + factoryDiagnosticsEventRevoker = factory.Diagnostics(winrt::auto_revoke, + [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) + { + if (auto strong_this{ weak_this.get() }) + { + strong_this->m_dynamicFactory->SendDiagnostics(information); + } + }); + + winrt::hstring propertyName = ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled); + if (auto propertyValue = m_dynamicFactory->GetFactoryMapValue(propertyName)) + { + factory.as>().Insert(propertyName, propertyValue.value()); + } + } + + return m_setProcessors.emplace(integrityLevel, DynamicProcessorInfo{ factory, factory.CreateSetProcessor(m_configurationSet), std::move(factoryDiagnosticsEventRevoker) }).first; + } + + winrt::com_ptr m_dynamicFactory; + Security::IntegrityLevel m_currentIntegrityLevel; + Security::IntegrityLevel m_setIntegrityLevel; + ProcessorMap m_setProcessors; + ConfigurationSet m_configurationSet; + std::once_flag m_createUnitSetProcessorsOnce; + +#ifndef AICLI_DISABLE_TEST_HOOKS + bool m_enableTestMode = false; + bool m_enableRestrictedIntegrityLevel = false; + bool m_forceHighIntegrityLevelUnits = false; +#endif + }; + + DynamicFactory::DynamicFactory(ProcessorEngine processorEngine) + { + m_processorEngine = processorEngine; + m_defaultRemoteFactory = CreateOutOfProcessFactory(processorEngine); + + if (m_defaultRemoteFactory) + { + m_factoryDiagnosticsEventRevoker = m_defaultRemoteFactory.Diagnostics(winrt::auto_revoke, + [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) + { + if (auto strong_this{ weak_this.get() }) + { + strong_this->SendDiagnostics(information); + } + }); + } + } + + IConfigurationSetProcessor DynamicFactory::CreateSetProcessor(const ConfigurationSet& configurationSet) + { + return winrt::make(get_strong(), m_defaultRemoteFactory.CreateSetProcessor(configurationSet), configurationSet); + } + + winrt::event_token DynamicFactory::Diagnostics(const EventHandler& handler) + { + return m_diagnostics.add(handler); + } + + void DynamicFactory::Diagnostics(const winrt::event_token& token) noexcept + { + m_diagnostics.remove(token); + } + + DiagnosticLevel DynamicFactory::MinimumLevel() + { + return m_minimumLevel; + } + + void DynamicFactory::MinimumLevel(DiagnosticLevel value) + { + m_minimumLevel = value; + + if (m_defaultRemoteFactory) + { + m_defaultRemoteFactory.MinimumLevel(value); + } + } + + HRESULT STDMETHODCALLTYPE DynamicFactory::SetLifetimeWatcher(IUnknown* watcher) + { + return WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } + + IConfigurationSetProcessorFactory& DynamicFactory::DefaultFactory() + { + return m_defaultRemoteFactory; + } + + void DynamicFactory::SendDiagnostics(const IDiagnosticInformation& information) try + { + if (information.Level() >= m_minimumLevel) + { + std::lock_guard lock{ m_diagnosticsMutex }; + m_diagnostics(*this, information); + } + } + // While diagnostics can be important, a failure to send them should not cause additional issues. + catch (...) {} + } + + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateDynamicRuntimeFactory(ProcessorEngine processorEngine) + { + return winrt::make(processorEngine); + } +} diff --git a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp index cc7e570257..dcd9221e0e 100644 --- a/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp +++ b/src/AppInstallerCLICore/ConfigurationSetProcessorFactoryRemoting.cpp @@ -1,445 +1,445 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/ConfigurationSetProcessorFactoryRemoting.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace winrt::Windows::Foundation; -using namespace winrt::Microsoft::Management::Configuration; -using namespace std::string_view_literals; - -namespace AppInstaller::CLI::ConfigurationRemoting -{ - namespace - { - // The executable file name for the remote server process. - constexpr std::wstring_view s_RemoteServerFileName = L"DotNet\\ConfigurationRemotingServer.exe"sv; - - constexpr std::wstring_view s_ProcessorEngine_PowerShell = L"pwsh"sv; - constexpr std::wstring_view s_ProcessorEngine_DSCv3 = L"dscv3"sv; - - // The string used to divide the arguments sent to the remote server - constexpr std::wstring_view s_ArgumentsDivider = L"\n~~~~~~\n"sv; - - // A helper with a convenient function that we use to receive the remote factory object. - struct RemoteFactoryCallback : winrt::implements - { - RemoteFactoryCallback() - { - m_initEvent.create(); - } - - ConfigurationUnit CreateConfigurationUnit() - { - THROW_HR(E_NOTIMPL); - } - - ConfigurationSet CreateConfigurationSet() - { - THROW_HR(E_NOTIMPL); - } - - IAsyncOperation CreateConfigurationSetProcessorFactoryAsync(winrt::hstring handler) - { - // TODO: Ensure calling process has same package identity - std::wstringstream stringStream{ std::wstring{ static_cast(handler) } }; - stringStream >> m_result; - m_initEvent.SetEvent(); - return nullptr; - } - - ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory factory) - { - // TODO: Ensure calling process has same package identity - m_factory = factory; - m_initEvent.SetEvent(); - return nullptr; - } - - bool IsConfigurationAvailable() - { - THROW_HR(E_NOTIMPL); - } - - IAsyncActionWithProgress EnsureConfigurationAvailableAsync() - { - THROW_HR(E_NOTIMPL); - } - - IConfigurationSetProcessorFactory Wait(HANDLE process) - { - HANDLE waitHandles[2]; - waitHandles[0] = m_initEvent.get(); - waitHandles[1] = process; - - for (;;) - { - // Wait up to 10 seconds for the server to complete initialization. - // This time is fairly arbitrary, although it does correspond with the maximum time for a COM fast rundown. - DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, 10000); - THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); - - // The init event was signaled. - if (waitResult == WAIT_OBJECT_0) - { - break; - } - - // Don't break things if the process is being debugged - if (waitResult == WAIT_TIMEOUT && IsDebuggerPresent()) - { - continue; - } - - // If the process exited, then try to use the exit code. - DWORD processExitCode = 0; - if (waitResult == (WAIT_OBJECT_0 + 1) && GetExitCodeProcess(process, &processExitCode) && FAILED(processExitCode)) - { - THROW_HR(static_cast(processExitCode)); - } - else - { - // The server timed out or didn't have a failed exit code. - THROW_HR(E_FAIL); - } - } - - THROW_IF_FAILED(m_result); - - // Double-check the result - THROW_HR_IF(E_POINTER, !m_factory); - return m_factory; - } - - private: - IConfigurationSetProcessorFactory m_factory; - HRESULT m_result = S_OK; - wil::unique_event m_initEvent; - }; - - // Represents a remote factory object that was created from a specific process. - struct RemoteFactory : winrt::implements, winrt::cloaked>, WinRT::LifetimeWatcherBase - { - RemoteFactory(ProcessorEngine processorEngine, bool useRunAs, const std::string& properties, const std::string& restrictions) - { - AICLI_LOG(Config, Verbose, << "Launching process for configuration processing..."); - - // Create our callback and marshal it - auto callback = winrt::make_self(); - - wil::com_ptr stream; - THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); - - THROW_IF_FAILED(CoMarshalInterface(stream.get(), winrt::guid_of(), reinterpret_cast<::IUnknown*>(winrt::get_abi(callback.as())), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); - - ULARGE_INTEGER streamSize{}; - THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); - - ULONG bufferSize = static_cast(streamSize.QuadPart); - std::vector buffer; - buffer.resize(bufferSize); - - THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - ULONG bytesRead = 0; - THROW_IF_FAILED(stream->Read(&buffer[0], bufferSize, &bytesRead)); - THROW_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); - - std::wstring marshalledCallback = Utility::ConvertToUTF16(Utility::ConvertToHexString(buffer)); - - // Create the event that the remote process will wait on to keep the object alive. - std::wstring completionEventName = Utility::CreateNewGuidNameWString(); - m_completionEvent.create(wil::EventOptions::None, completionEventName.c_str()); - auto completeEventIfFailureDuringConstruction = wil::scope_exit([&]() { m_completionEvent.SetEvent(); }); - - // This will be presented to the user so it must be formatted nicely. - // Arguments are: - // server.exe - // - // Optionally, we may also place additional data that limits what the server may do as: - // ~~~~~~ - // { "JSON properties" } - // ~~~~~~ - // YAML configuration set definition - std::wostringstream argumentsStream; - argumentsStream << s_RemoteServerFileName << L' ' << marshalledCallback << L' ' << completionEventName << L' ' << GetCurrentProcessId() << L' ' << ToString(processorEngine); - - if (!properties.empty() && !restrictions.empty()) - { - argumentsStream << L' ' << s_ArgumentsDivider << Utility::ConvertToUTF16(properties) << s_ArgumentsDivider << Utility::ConvertToUTF16(restrictions); - } - - std::wstring arguments = argumentsStream.str(); - - std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot); - serverPath /= s_RemoteServerFileName; - std::wstring serverPathString = serverPath.wstring(); - - // Per documentation, the maximum length is 32767 *counting* the null. - THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() > 32766); - THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, arguments.length() > 32766); - // Overflow safe since we verify that each of the individual strings is also small. - // +1 for the space between the path and args. - THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() + 1 + arguments.length() > 32766); - - SHELLEXECUTEINFOW execInfo = { 0 }; - execInfo.cbSize = sizeof(execInfo); - execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI | SEE_MASK_NO_CONSOLE; - execInfo.lpFile = serverPath.c_str(); - execInfo.lpParameters = arguments.c_str(); - execInfo.nShow = SW_HIDE; - - if (useRunAs) - { - execInfo.lpVerb = L"runas"; - } - - THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess); - - wil::unique_process_handle process{ execInfo.hProcess }; - AICLI_LOG(Config, Verbose, << " Configuration remote PID is " << GetProcessId(process.get())); - - m_remoteFactory = callback->Wait(process.get()); - AICLI_LOG(Config, Verbose, << "... configuration processing connection established."); - - completeEventIfFailureDuringConstruction.release(); - } - - ~RemoteFactory() - { - m_completionEvent.SetEvent(); - } - - IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet) - { - return m_remoteFactory.CreateSetProcessor(configurationSet); - } - - winrt::event_token Diagnostics(const EventHandler& handler) - { - return m_remoteFactory.Diagnostics(handler); - } - - void Diagnostics(const winrt::event_token& token) noexcept - { - m_remoteFactory.Diagnostics(token); - } - - DiagnosticLevel MinimumLevel() - { - return m_remoteFactory.MinimumLevel(); - } - - void MinimumLevel(DiagnosticLevel value) - { - m_remoteFactory.MinimumLevel(value); - } - - Collections::IVectorView AdditionalModulePaths() const - { - return m_additionalModulePaths.GetView(); - } - - void AdditionalModulePaths(const Collections::IVectorView& value) - { - // Extract all values from incoming view - std::vector newModulePaths{ value.Size() }; - value.GetMany(0, newModulePaths); - - // Create a copy for remote and set remote module paths - std::vector newRemotePaths{ newModulePaths }; - m_remoteAdditionalModulePaths = winrt::single_threaded_vector(std::move(newRemotePaths)); - m_remoteFactory.as().AdditionalModulePaths(m_remoteAdditionalModulePaths.GetView()); - - // Store the updated module paths that we were given - m_additionalModulePaths = winrt::single_threaded_vector(std::move(newModulePaths)); - } - - SetProcessorFactory::PwshConfigurationProcessorPolicy Policy() const - { - return m_remoteFactory.as().Policy(); - } - - void Policy(SetProcessorFactory::PwshConfigurationProcessorPolicy value) - { - m_remoteFactory.as().Policy(value); - } - - SetProcessorFactory::PwshConfigurationProcessorLocation Location() const - { - return m_remoteFactory.as().Location(); - } - - void Location(SetProcessorFactory::PwshConfigurationProcessorLocation value) - { - m_remoteFactory.as().Location(value); - } - - winrt::hstring CustomLocation() const - { - return m_remoteFactory.as().CustomLocation(); - } - - void CustomLocation(winrt::hstring value) - { - m_remoteFactory.as().CustomLocation(value); - } - - // Implement a subset of IMap to enable property bag semantics - uint32_t Size() { THROW_HR(E_NOTIMPL); } - void Clear() { THROW_HR(E_NOTIMPL); } - Collections::IMapView GetView() { THROW_HR(E_NOTIMPL); } - bool HasKey(winrt::hstring) { THROW_HR(E_NOTIMPL); } - void Remove(winrt::hstring) { THROW_HR(E_NOTIMPL); } - - bool Insert(winrt::hstring key, winrt::hstring value) - { - auto map = m_remoteFactory.try_as>(); - return map ? map.Insert(key, value) : false; - } - - winrt::hstring Lookup(winrt::hstring key) - { - auto map = m_remoteFactory.try_as>(); - return map ? map.Lookup(key) : winrt::hstring{}; - } - - HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher) - { - return WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); - } - - private: - IConfigurationSetProcessorFactory m_remoteFactory; - wil::unique_event m_completionEvent; - Collections::IVector m_additionalModulePaths{ winrt::single_threaded_vector() }; - Collections::IVector m_remoteAdditionalModulePaths{ winrt::single_threaded_vector() }; - }; - } - - IConfigurationSetProcessorFactory CreateOutOfProcessFactory(ProcessorEngine processorEngine, bool useRunAs, const std::string& properties, const std::string& restrictions) - { - return winrt::make(processorEngine, useRunAs, properties, restrictions); - } - - ProcessorEngine DetermineProcessorEngine(ConfigurationSet set) - { - Utility::Version schemaVersion{ Utility::ConvertToUTF8(set.SchemaVersion()) }; - - if (schemaVersion <= Utility::Version{ "0.3" }) - { - ProcessorEngine result = ProcessorEngine::Unknown; - - std::wstring processorIdentifier = Utility::ToLower(set.Environment().ProcessorIdentifier()); - if (processorIdentifier.empty() || processorIdentifier == s_ProcessorEngine_PowerShell) - { - // Default to PowerShell - result = ProcessorEngine::PowerShell; - } - else if (processorIdentifier == s_ProcessorEngine_DSCv3) - { - result = ProcessorEngine::DSCv3; - } - else - { - AICLI_LOG(Config, Warning, << "Unknown processor: " << Utility::ConvertToUTF8(processorIdentifier)); - } - - return result; - } - else - { - // Intentionally fail out here until a decision is made. - THROW_HR(E_NOTIMPL); - } - } - - std::wstring_view ToString(ProcessorEngine value) - { - switch (value) - { - case ProcessorEngine::PowerShell: - return s_ProcessorEngine_PowerShell; - case ProcessorEngine::DSCv3: - return s_ProcessorEngine_DSCv3; - default: - THROW_HR(E_UNEXPECTED); - } - } - - winrt::hstring ToHString(PropertyName name) - { - switch (name) - { - case PropertyName::DscExecutablePath: return L"DscExecutablePath"; - case PropertyName::FoundDscExecutablePath: return L"FoundDscExecutablePath"; - case PropertyName::DiagnosticTraceEnabled: return L"DiagnosticTraceEnabled"; - case PropertyName::FindDscStateMachine: return L"FindDscStateMachine"; - case PropertyName::DscExecutablePathHash: return L"DscExecutablePathHash"; - case PropertyName::DscExecutablePathIsAlias: return L"DscExecutablePathIsAlias"; - case PropertyName::FoundDscExecutablePathHash: return L"FoundDscExecutablePathHash"; - case PropertyName::FoundDscExecutablePathIsAlias: return L"FoundDscExecutablePathIsAlias"; - } - THROW_HR(E_UNEXPECTED); - } -} - -HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, LPWSTR staticsCallback, LPWSTR completionEventName, DWORD parentProcessId) try -{ - { - wil::com_ptr globalOptions; - RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); - RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); - RETURN_IF_FAILED(globalOptions->Set(COMGLB_UNMARSHALING_POLICY, COMGLB_UNMARSHALING_POLICY_STRONG)); - RETURN_IF_FAILED(globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY)); - } - - using namespace AppInstaller; - using namespace AppInstaller::CLI::ConfigurationRemoting; - - RETURN_HR_IF(E_POINTER, !staticsCallback); - - auto callbackBytes = Utility::ParseFromHexString(Utility::ConvertToUTF8(staticsCallback)); - RETURN_HR_IF(E_INVALIDARG, callbackBytes.size() > (1 << 15)); - - wil::com_ptr stream; - RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); - RETURN_IF_FAILED(stream->Write(&callbackBytes[0], static_cast(callbackBytes.size()), nullptr)); - RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - - wil::com_ptr<::IUnknown> output; - RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), winrt::guid_of(), reinterpret_cast(&output))); - - IConfigurationStatics callback{ output.detach(), winrt::take_ownership_from_abi }; - - if (FAILED(result)) - { - std::ignore = callback.CreateConfigurationSetProcessorFactoryAsync(std::to_wstring(result)); - } - else - { - IConfigurationSetProcessorFactory factoryObject; - winrt::copy_from_abi(factoryObject, factory); - std::ignore = callback.CreateConfigurationProcessor(factoryObject); - } - - // Wait until the caller releases the object (signalling the event) or the parent process exits - wil::unique_event completionEvent; - completionEvent.open(completionEventName); - wil::unique_process_handle parentProcess{ OpenProcess(SYNCHRONIZE, FALSE, parentProcessId) }; - - HANDLE waitHandles[2]; - waitHandles[0] = completionEvent.get(); - waitHandles[1] = parentProcess.get(); - - std::ignore = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); - - return S_OK; -} -CATCH_RETURN(); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt::Windows::Foundation; +using namespace winrt::Microsoft::Management::Configuration; +using namespace std::string_view_literals; + +namespace AppInstaller::CLI::ConfigurationRemoting +{ + namespace + { + // The executable file name for the remote server process. + constexpr std::wstring_view s_RemoteServerFileName = L"DotNet\\ConfigurationRemotingServer.exe"sv; + + constexpr std::wstring_view s_ProcessorEngine_PowerShell = L"pwsh"sv; + constexpr std::wstring_view s_ProcessorEngine_DSCv3 = L"dscv3"sv; + + // The string used to divide the arguments sent to the remote server + constexpr std::wstring_view s_ArgumentsDivider = L"\n~~~~~~\n"sv; + + // A helper with a convenient function that we use to receive the remote factory object. + struct RemoteFactoryCallback : winrt::implements + { + RemoteFactoryCallback() + { + m_initEvent.create(); + } + + ConfigurationUnit CreateConfigurationUnit() + { + THROW_HR(E_NOTIMPL); + } + + ConfigurationSet CreateConfigurationSet() + { + THROW_HR(E_NOTIMPL); + } + + IAsyncOperation CreateConfigurationSetProcessorFactoryAsync(winrt::hstring handler) + { + // TODO: Ensure calling process has same package identity + std::wstringstream stringStream{ std::wstring{ static_cast(handler) } }; + stringStream >> m_result; + m_initEvent.SetEvent(); + return nullptr; + } + + ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory factory) + { + // TODO: Ensure calling process has same package identity + m_factory = factory; + m_initEvent.SetEvent(); + return nullptr; + } + + bool IsConfigurationAvailable() + { + THROW_HR(E_NOTIMPL); + } + + IAsyncActionWithProgress EnsureConfigurationAvailableAsync() + { + THROW_HR(E_NOTIMPL); + } + + IConfigurationSetProcessorFactory Wait(HANDLE process) + { + HANDLE waitHandles[2]; + waitHandles[0] = m_initEvent.get(); + waitHandles[1] = process; + + for (;;) + { + // Wait up to 10 seconds for the server to complete initialization. + // This time is fairly arbitrary, although it does correspond with the maximum time for a COM fast rundown. + DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, 10000); + THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); + + // The init event was signaled. + if (waitResult == WAIT_OBJECT_0) + { + break; + } + + // Don't break things if the process is being debugged + if (waitResult == WAIT_TIMEOUT && IsDebuggerPresent()) + { + continue; + } + + // If the process exited, then try to use the exit code. + DWORD processExitCode = 0; + if (waitResult == (WAIT_OBJECT_0 + 1) && GetExitCodeProcess(process, &processExitCode) && FAILED(processExitCode)) + { + THROW_HR(static_cast(processExitCode)); + } + else + { + // The server timed out or didn't have a failed exit code. + THROW_HR(E_FAIL); + } + } + + THROW_IF_FAILED(m_result); + + // Double-check the result + THROW_HR_IF(E_POINTER, !m_factory); + return m_factory; + } + + private: + IConfigurationSetProcessorFactory m_factory; + HRESULT m_result = S_OK; + wil::unique_event m_initEvent; + }; + + // Represents a remote factory object that was created from a specific process. + struct RemoteFactory : winrt::implements, winrt::cloaked>, WinRT::LifetimeWatcherBase + { + RemoteFactory(ProcessorEngine processorEngine, bool useRunAs, const std::string& properties, const std::string& restrictions) + { + AICLI_LOG(Config, Verbose, << "Launching process for configuration processing..."); + + // Create our callback and marshal it + auto callback = winrt::make_self(); + + wil::com_ptr stream; + THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + + THROW_IF_FAILED(CoMarshalInterface(stream.get(), winrt::guid_of(), reinterpret_cast<::IUnknown*>(winrt::get_abi(callback.as())), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); + + ULARGE_INTEGER streamSize{}; + THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); + + ULONG bufferSize = static_cast(streamSize.QuadPart); + std::vector buffer; + buffer.resize(bufferSize); + + THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + ULONG bytesRead = 0; + THROW_IF_FAILED(stream->Read(&buffer[0], bufferSize, &bytesRead)); + THROW_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); + + std::wstring marshalledCallback = Utility::ConvertToUTF16(Utility::ConvertToHexString(buffer)); + + // Create the event that the remote process will wait on to keep the object alive. + std::wstring completionEventName = Utility::CreateNewGuidNameWString(); + m_completionEvent.create(wil::EventOptions::None, completionEventName.c_str()); + auto completeEventIfFailureDuringConstruction = wil::scope_exit([&]() { m_completionEvent.SetEvent(); }); + + // This will be presented to the user so it must be formatted nicely. + // Arguments are: + // server.exe + // + // Optionally, we may also place additional data that limits what the server may do as: + // ~~~~~~ + // { "JSON properties" } + // ~~~~~~ + // YAML configuration set definition + std::wostringstream argumentsStream; + argumentsStream << s_RemoteServerFileName << L' ' << marshalledCallback << L' ' << completionEventName << L' ' << GetCurrentProcessId() << L' ' << ToString(processorEngine); + + if (!properties.empty() && !restrictions.empty()) + { + argumentsStream << L' ' << s_ArgumentsDivider << Utility::ConvertToUTF16(properties) << s_ArgumentsDivider << Utility::ConvertToUTF16(restrictions); + } + + std::wstring arguments = argumentsStream.str(); + + std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot); + serverPath /= s_RemoteServerFileName; + std::wstring serverPathString = serverPath.wstring(); + + // Per documentation, the maximum length is 32767 *counting* the null. + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() > 32766); + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, arguments.length() > 32766); + // Overflow safe since we verify that each of the individual strings is also small. + // +1 for the space between the path and args. + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, serverPathString.length() + 1 + arguments.length() > 32766); + + SHELLEXECUTEINFOW execInfo = { 0 }; + execInfo.cbSize = sizeof(execInfo); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_FLAG_NO_UI | SEE_MASK_NO_CONSOLE; + execInfo.lpFile = serverPath.c_str(); + execInfo.lpParameters = arguments.c_str(); + execInfo.nShow = SW_HIDE; + + if (useRunAs) + { + execInfo.lpVerb = L"runas"; + } + + THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess); + + wil::unique_process_handle process{ execInfo.hProcess }; + AICLI_LOG(Config, Verbose, << " Configuration remote PID is " << GetProcessId(process.get())); + + m_remoteFactory = callback->Wait(process.get()); + AICLI_LOG(Config, Verbose, << "... configuration processing connection established."); + + completeEventIfFailureDuringConstruction.release(); + } + + ~RemoteFactory() + { + m_completionEvent.SetEvent(); + } + + IConfigurationSetProcessor CreateSetProcessor(const ConfigurationSet& configurationSet) + { + return m_remoteFactory.CreateSetProcessor(configurationSet); + } + + winrt::event_token Diagnostics(const EventHandler& handler) + { + return m_remoteFactory.Diagnostics(handler); + } + + void Diagnostics(const winrt::event_token& token) noexcept + { + m_remoteFactory.Diagnostics(token); + } + + DiagnosticLevel MinimumLevel() + { + return m_remoteFactory.MinimumLevel(); + } + + void MinimumLevel(DiagnosticLevel value) + { + m_remoteFactory.MinimumLevel(value); + } + + Collections::IVectorView AdditionalModulePaths() const + { + return m_additionalModulePaths.GetView(); + } + + void AdditionalModulePaths(const Collections::IVectorView& value) + { + // Extract all values from incoming view + std::vector newModulePaths{ value.Size() }; + value.GetMany(0, newModulePaths); + + // Create a copy for remote and set remote module paths + std::vector newRemotePaths{ newModulePaths }; + m_remoteAdditionalModulePaths = winrt::single_threaded_vector(std::move(newRemotePaths)); + m_remoteFactory.as().AdditionalModulePaths(m_remoteAdditionalModulePaths.GetView()); + + // Store the updated module paths that we were given + m_additionalModulePaths = winrt::single_threaded_vector(std::move(newModulePaths)); + } + + SetProcessorFactory::PwshConfigurationProcessorPolicy Policy() const + { + return m_remoteFactory.as().Policy(); + } + + void Policy(SetProcessorFactory::PwshConfigurationProcessorPolicy value) + { + m_remoteFactory.as().Policy(value); + } + + SetProcessorFactory::PwshConfigurationProcessorLocation Location() const + { + return m_remoteFactory.as().Location(); + } + + void Location(SetProcessorFactory::PwshConfigurationProcessorLocation value) + { + m_remoteFactory.as().Location(value); + } + + winrt::hstring CustomLocation() const + { + return m_remoteFactory.as().CustomLocation(); + } + + void CustomLocation(winrt::hstring value) + { + m_remoteFactory.as().CustomLocation(value); + } + + // Implement a subset of IMap to enable property bag semantics + uint32_t Size() { THROW_HR(E_NOTIMPL); } + void Clear() { THROW_HR(E_NOTIMPL); } + Collections::IMapView GetView() { THROW_HR(E_NOTIMPL); } + bool HasKey(winrt::hstring) { THROW_HR(E_NOTIMPL); } + void Remove(winrt::hstring) { THROW_HR(E_NOTIMPL); } + + bool Insert(winrt::hstring key, winrt::hstring value) + { + auto map = m_remoteFactory.try_as>(); + return map ? map.Insert(key, value) : false; + } + + winrt::hstring Lookup(winrt::hstring key) + { + auto map = m_remoteFactory.try_as>(); + return map ? map.Lookup(key) : winrt::hstring{}; + } + + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher) + { + return WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } + + private: + IConfigurationSetProcessorFactory m_remoteFactory; + wil::unique_event m_completionEvent; + Collections::IVector m_additionalModulePaths{ winrt::single_threaded_vector() }; + Collections::IVector m_remoteAdditionalModulePaths{ winrt::single_threaded_vector() }; + }; + } + + IConfigurationSetProcessorFactory CreateOutOfProcessFactory(ProcessorEngine processorEngine, bool useRunAs, const std::string& properties, const std::string& restrictions) + { + return winrt::make(processorEngine, useRunAs, properties, restrictions); + } + + ProcessorEngine DetermineProcessorEngine(ConfigurationSet set) + { + Utility::Version schemaVersion{ Utility::ConvertToUTF8(set.SchemaVersion()) }; + + if (schemaVersion <= Utility::Version{ "0.3" }) + { + ProcessorEngine result = ProcessorEngine::Unknown; + + std::wstring processorIdentifier = Utility::ToLower(set.Environment().ProcessorIdentifier()); + if (processorIdentifier.empty() || processorIdentifier == s_ProcessorEngine_PowerShell) + { + // Default to PowerShell + result = ProcessorEngine::PowerShell; + } + else if (processorIdentifier == s_ProcessorEngine_DSCv3) + { + result = ProcessorEngine::DSCv3; + } + else + { + AICLI_LOG(Config, Warning, << "Unknown processor: " << Utility::ConvertToUTF8(processorIdentifier)); + } + + return result; + } + else + { + // Intentionally fail out here until a decision is made. + THROW_HR(E_NOTIMPL); + } + } + + std::wstring_view ToString(ProcessorEngine value) + { + switch (value) + { + case ProcessorEngine::PowerShell: + return s_ProcessorEngine_PowerShell; + case ProcessorEngine::DSCv3: + return s_ProcessorEngine_DSCv3; + default: + THROW_HR(E_UNEXPECTED); + } + } + + winrt::hstring ToHString(PropertyName name) + { + switch (name) + { + case PropertyName::DscExecutablePath: return L"DscExecutablePath"; + case PropertyName::FoundDscExecutablePath: return L"FoundDscExecutablePath"; + case PropertyName::DiagnosticTraceEnabled: return L"DiagnosticTraceEnabled"; + case PropertyName::FindDscStateMachine: return L"FindDscStateMachine"; + case PropertyName::DscExecutablePathHash: return L"DscExecutablePathHash"; + case PropertyName::DscExecutablePathIsAlias: return L"DscExecutablePathIsAlias"; + case PropertyName::FoundDscExecutablePathHash: return L"FoundDscExecutablePathHash"; + case PropertyName::FoundDscExecutablePathIsAlias: return L"FoundDscExecutablePathIsAlias"; + } + THROW_HR(E_UNEXPECTED); + } +} + +HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, LPWSTR staticsCallback, LPWSTR completionEventName, DWORD parentProcessId) try +{ + { + wil::com_ptr globalOptions; + RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_UNMARSHALING_POLICY, COMGLB_UNMARSHALING_POLICY_STRONG)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY)); + } + + using namespace AppInstaller; + using namespace AppInstaller::CLI::ConfigurationRemoting; + + RETURN_HR_IF(E_POINTER, !staticsCallback); + + auto callbackBytes = Utility::ParseFromHexString(Utility::ConvertToUTF8(staticsCallback)); + RETURN_HR_IF(E_INVALIDARG, callbackBytes.size() > (1 << 15)); + + wil::com_ptr stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + RETURN_IF_FAILED(stream->Write(&callbackBytes[0], static_cast(callbackBytes.size()), nullptr)); + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + + wil::com_ptr<::IUnknown> output; + RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), winrt::guid_of(), reinterpret_cast(&output))); + + IConfigurationStatics callback{ output.detach(), winrt::take_ownership_from_abi }; + + if (FAILED(result)) + { + std::ignore = callback.CreateConfigurationSetProcessorFactoryAsync(std::to_wstring(result)); + } + else + { + IConfigurationSetProcessorFactory factoryObject; + winrt::copy_from_abi(factoryObject, factory); + std::ignore = callback.CreateConfigurationProcessor(factoryObject); + } + + // Wait until the caller releases the object (signalling the event) or the parent process exits + wil::unique_event completionEvent; + completionEvent.open(completionEventName); + wil::unique_process_handle parentProcess{ OpenProcess(SYNCHRONIZE, FALSE, parentProcessId) }; + + HANDLE waitHandles[2]; + waitHandles[0] = completionEvent.get(); + waitHandles[1] = parentProcess.get(); + + std::ignore = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); + + return S_OK; +} +CATCH_RETURN(); diff --git a/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.cpp b/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.cpp index ea9f74a97c..9455573983 100644 --- a/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.cpp +++ b/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.cpp @@ -1,361 +1,361 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationWingetDscModuleUnitValidation.h" -#include "ExecutionContext.h" -#include - -using namespace winrt::Microsoft::Management::Configuration; -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Foundation::Collections; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI::Configuration -{ - namespace - { - constexpr static std::string_view UnitType_WinGetSource = "WinGetSource"sv; - constexpr static std::string_view UnitType_WinGetPackage = "WinGetPackage"sv; - - constexpr static std::string_view WellKnownSourceName_WinGet = "winget"sv; - constexpr static std::string_view WellKnownSourceName_MSStore = "msstore"sv; - constexpr static std::string_view WellKnownSourceName_WinGetFont = "winget-font"sv; - - constexpr static std::string_view ValueSetKey_TreatAsArray = "treatAsArray"sv; - - constexpr static std::string_view WinGetSourceValueSetKey_Name = "name"sv; - constexpr static std::string_view WinGetSourceValueSetKey_Type = "type"sv; - constexpr static std::string_view WinGetSourceValueSetKey_Arg = "argument"sv; - constexpr static std::string_view WinGetSourceValueSetKey_Ensure = "ensure"sv; - constexpr static std::string_view WinGetSourceValueSetKey_Ensure_Present = "present"sv; - - constexpr static std::string_view WinGetPackageValueSetKey_Id = "id"sv; - constexpr static std::string_view WinGetPackageValueSetKey_Version = "version"sv; - constexpr static std::string_view WinGetPackageValueSetKey_Source = "source"sv; - constexpr static std::string_view WinGetPackageValueSetKey_UseLatest = "useLatest"sv; - - struct WinGetSource - { - std::string Name; - std::string Type; - std::string Arg; - bool Present = true; - - bool Empty() - { - return Name.empty() && Arg.empty() && Type.empty(); - } - }; - - std::string GetPropertyValueAsString(const winrt::Windows::Foundation::IInspectable& value) - { - IPropertyValue propertyValue = value.try_as(); - if (propertyValue && propertyValue.Type() == PropertyType::String) - { - return Utility::ConvertToUTF8(propertyValue.GetString()); - } - - return {}; - } - - bool GetPropertyValueAsBoolean(const winrt::Windows::Foundation::IInspectable& value, bool defaultIfFailed = false) - { - IPropertyValue propertyValue = value.try_as(); - if (propertyValue && propertyValue.Type() == PropertyType::Boolean) - { - return propertyValue.GetBoolean(); - } - - return defaultIfFailed; - } - - WinGetSource ParseWinGetSourceFromSettings(const ValueSet& settings) - { - WinGetSource result; - - // Iterate through the value set as Powershell variables are case-insensitive. - for (auto const& settingsPair : settings) - { - auto settingsKey = Utility::ConvertToUTF8(settingsPair.Key()); - - if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Name, settingsKey)) - { - result.Name = GetPropertyValueAsString(settingsPair.Value()); - } - else if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Type, settingsKey)) - { - result.Type = GetPropertyValueAsString(settingsPair.Value()); - } - else if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Arg, settingsKey)) - { - result.Arg = GetPropertyValueAsString(settingsPair.Value()); - } - else if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Ensure, settingsKey)) - { - result.Present = Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Ensure_Present, GetPropertyValueAsString(settingsPair.Value())); - } - } - - return result; - } - - bool IsWellKnownSourceName(std::string_view sourceName) - { - return Utility::CaseInsensitiveEquals(WellKnownSourceName_WinGet, sourceName) || - Utility::CaseInsensitiveEquals(WellKnownSourceName_MSStore, sourceName) || - Utility::CaseInsensitiveEquals(WellKnownSourceName_WinGetFont, sourceName); - } - - bool ValidateWellKnownSource(const WinGetSource& source) - { - static std::vector wellKnownSourceDetails = - { - Repository::Source{ Repository::WellKnownSource::WinGet }.GetDetails(), - Repository::Source{ Repository::WellKnownSource::MicrosoftStore }.GetDetails(), - Repository::Source{ Repository::WellKnownSource::WinGetFont }.GetDetails(), - }; - - for (auto const& wellKnownSource : wellKnownSourceDetails) - { - if (Utility::CaseInsensitiveEquals(wellKnownSource.Name, source.Name) && - Utility::CaseInsensitiveEquals(wellKnownSource.Arg, source.Arg) && - Utility::CaseInsensitiveEquals(wellKnownSource.Type, source.Type)) - { - return true; - } - } - - return false; - } - - struct WinGetPackage - { - std::string Id; - std::string Version; - std::string Source; - bool UseLatest = false; - - bool Empty() - { - return Id.empty() && Version.empty() && Source.empty(); - } - }; - - WinGetPackage ParseWinGetPackageFromSettings(const ValueSet& settings) - { - // Iterate through the value set as Powershell variables are case-insensitive. - WinGetPackage result; - for (auto const& settingsPair : settings) - { - auto settingsKey = Utility::ConvertToUTF8(settingsPair.Key()); - if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_Id, settingsKey)) - { - result.Id = GetPropertyValueAsString(settingsPair.Value()); - } - else if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_Version, settingsKey)) - { - result.Version = GetPropertyValueAsString(settingsPair.Value()); - } - else if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_Source, settingsKey)) - { - result.Source = GetPropertyValueAsString(settingsPair.Value()); - } - else if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_UseLatest, settingsKey)) - { - result.UseLatest = GetPropertyValueAsBoolean(settingsPair.Value()); - } - } - - return result; - } - } - - bool WingetDscModuleUnitValidator::ValidateConfigurationSetUnit(Execution::Context& context, const ConfigurationUnit& unit) - { - bool foundIssues = false; - auto details = unit.Details(); - auto unitType = Utility::ConvertToUTF8(details.UnitType()); - auto unitIntent = unit.Intent(); - - if (Utility::CaseInsensitiveEquals(UnitType_WinGetSource, unitType)) - { - auto source = ParseWinGetSourceFromSettings(unit.Settings()); - - // Validate basic semantics. - if (source.Name.empty()) - { - AICLI_LOG(Config, Error, << "WinGetSource unit missing required arg: Name"); - context.Reporter.Error() << Resource::String::WinGetResourceUnitMissingRequiredArg(Utility::LocIndView{ UnitType_WinGetSource }, "Name"_liv) << std::endl; - foundIssues = true; - } - if (source.Arg.empty() && source.Present) - { - AICLI_LOG(Config, Error, << "WinGetSource unit missing required arg: Argument"); - context.Reporter.Error() << Resource::String::WinGetResourceUnitMissingRequiredArg(Utility::LocIndView{ UnitType_WinGetSource }, "Argument"_liv) << std::endl; - foundIssues = true; - } - - // Validate well known source or process 3rd party source. - if (IsWellKnownSourceName(source.Name)) - { - if (!ValidateWellKnownSource(source)) - { - AICLI_LOG(Config, Warning, << "WinGetSource conflicts with a well known source. Source: " << source.Name); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitKnownSourceConfliction(Utility::LocIndView{ source.Name }) << std::endl; - foundIssues = true; - } - } - else - { - if (unitIntent == ConfigurationUnitIntent::Assert) - { - AICLI_LOG(Config, Warning, << "Asserting on 3rd party source: " << source.Name); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitThirdPartySourceAssertion(Utility::LocIndView{ source.Name }) << std::endl; - foundIssues = true; - } - else if (unitIntent == ConfigurationUnitIntent::Apply) - { - // Add to dependency source map so it can be validated with later WinGetPackage source - m_dependenciesSourceAndUnitIdMap.emplace(Utility::FoldCase(std::string_view{ source.Name }), Utility::FoldCase(Utility::NormalizedString{ unit.Identifier() })); - } - } - } - else if (Utility::CaseInsensitiveEquals(UnitType_WinGetPackage, unitType)) - { - auto package = ParseWinGetPackageFromSettings(unit.Settings()); - if (package.Empty()) - { - AICLI_LOG(Config, Warning, << "Failed to parse WinGetPackage or empty content."); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitEmptyContent(Utility::LocIndView{ UnitType_WinGetPackage }) << std::endl; - foundIssues = true; - } - // Validate basic semantics. - if (package.Id.empty()) - { - AICLI_LOG(Config, Error, << "WinGetPackage unit missing required arg: Id"); - context.Reporter.Error() << Resource::String::WinGetResourceUnitMissingRequiredArg(Utility::LocIndView{ UnitType_WinGetPackage }, "Id"_liv) << std::endl; - foundIssues = true; - } - if (package.Source.empty()) - { - AICLI_LOG(Config, Warning, << "WinGetPackage unit missing recommended arg: Source"); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitMissingRecommendedArg(Utility::LocIndView{ UnitType_WinGetPackage }, "Source"_liv) << std::endl; - foundIssues = true; - } - if (package.UseLatest && !package.Version.empty()) - { - AICLI_LOG(Config, Warning, << "WinGetPackage unit both UseLatest and Version declared. Package: " << package.Id); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitBothPackageVersionAndUseLatest(Utility::LocIndView{ package.Id }) << std::endl; - foundIssues = true; - } - // Validate dependency source is configured. - if (!package.Source.empty() && !IsWellKnownSourceName(package.Source)) - { - if (unitIntent == ConfigurationUnitIntent::Assert) - { - AICLI_LOG(Config, Warning, << "Asserting on a package that depends on a 3rd party source. Package: " << package.Id << " Source: " << package.Source); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitThirdPartySourceAssertionForPackage(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; - foundIssues = true; - } - else - { - auto dependencySourceItr = m_dependenciesSourceAndUnitIdMap.find(Utility::FoldCase(std::string_view{ package.Source })); - if (dependencySourceItr == m_dependenciesSourceAndUnitIdMap.end()) - { - AICLI_LOG(Config, Warning, << "WinGetPackage depends on a 3rd party source not previously configured. Package: " << package.Id << " Source: " << package.Source); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitDependencySourceNotConfigured(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; - foundIssues = true; - } - else - { - bool foundInUnitDependencies = false; - for (auto const& entry : unit.Dependencies()) - { - // The map contains normalized string, so just use direct comparison; - if (dependencySourceItr->second == Utility::FoldCase(Utility::NormalizedString{ entry })) - { - foundInUnitDependencies = true; - break; - } - } - if (!foundInUnitDependencies) - { - AICLI_LOG(Config, Warning, << "WinGetPackage depends on a 3rd party source. It is recommended to add the WinGetSources unit configuring the source to the unit's dependsOn list. Package: " << package.Id << " Source: " << package.Source); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitDependencySourceNotDeclaredAsDependency(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; - foundIssues = true; - } - } - } - } - // Validate package is found and version available. - try - { - Repository::Source source{ package.Source }; - if (!source) - { - AICLI_LOG(Config, Warning, << "Failed to open WinGet source. Package: " << package.Id << " Source: " << package.Source); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageSourceOpenFailed(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; - foundIssues = true; - } - else - { - source.SetCaller("winget-cli-configuration-unit-module-validation"); - ProgressCallback empty; - source.Open(empty); - Repository::SearchRequest searchRequest; - searchRequest.Filters.emplace_back(Repository::PackageMatchFilter{ Repository::PackageMatchField::Id, Repository::MatchType::CaseInsensitive, package.Id }); - auto searchResult = source.Search(searchRequest); - if (searchResult.Matches.size() == 0) - { - AICLI_LOG(Config, Warning, << "WinGetPackage not found: " << package.Id); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageNotFound(Utility::LocIndView{ package.Id }) << std::endl; - foundIssues = true; - } - else if (searchResult.Matches.size() > 1) - { - AICLI_LOG(Config, Warning, << "More than one WinGetPackage found: " << package.Id); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageMultipleFound(Utility::LocIndView{ package.Id }) << std::endl; - foundIssues = true; - } - else - { - if (!package.Version.empty()) - { - std::shared_ptr availablePackage = searchResult.Matches.at(0).Package->GetAvailable().at(0); - auto versionKeys = availablePackage->GetVersionKeys(); - bool foundVersion = false; - for (auto const& versionKey : versionKeys) - { - if (versionKey.Version == Utility::NormalizedString(package.Version)) - { - foundVersion = true; - break; - } - } - if (!foundVersion) - { - AICLI_LOG(Config, Warning, << "WinGetPackage version not found. Package: " << package.Id << " Version: " << package.Version); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageVersionNotFound(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Version }) << std::endl; - foundIssues = true; - } - if (versionKeys.size() == 1) - { - AICLI_LOG(Config, Warning, << "WinGetPackage version specified with only one version available: " << package.Id); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitPackageVersionSpecifiedWithOnlyOnePackageVersion(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Version }) << std::endl; - foundIssues = true; - } - } - } - } - } - catch (...) - { - AICLI_LOG(Config, Warning, << "Failed to validate WinGetPackage: " << package.Id); - context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackage(Utility::LocIndView{ package.Id }) << std::endl; - foundIssues = true; - } - } - - return !foundIssues; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationWingetDscModuleUnitValidation.h" +#include "ExecutionContext.h" +#include + +using namespace winrt::Microsoft::Management::Configuration; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Configuration +{ + namespace + { + constexpr static std::string_view UnitType_WinGetSource = "WinGetSource"sv; + constexpr static std::string_view UnitType_WinGetPackage = "WinGetPackage"sv; + + constexpr static std::string_view WellKnownSourceName_WinGet = "winget"sv; + constexpr static std::string_view WellKnownSourceName_MSStore = "msstore"sv; + constexpr static std::string_view WellKnownSourceName_WinGetFont = "winget-font"sv; + + constexpr static std::string_view ValueSetKey_TreatAsArray = "treatAsArray"sv; + + constexpr static std::string_view WinGetSourceValueSetKey_Name = "name"sv; + constexpr static std::string_view WinGetSourceValueSetKey_Type = "type"sv; + constexpr static std::string_view WinGetSourceValueSetKey_Arg = "argument"sv; + constexpr static std::string_view WinGetSourceValueSetKey_Ensure = "ensure"sv; + constexpr static std::string_view WinGetSourceValueSetKey_Ensure_Present = "present"sv; + + constexpr static std::string_view WinGetPackageValueSetKey_Id = "id"sv; + constexpr static std::string_view WinGetPackageValueSetKey_Version = "version"sv; + constexpr static std::string_view WinGetPackageValueSetKey_Source = "source"sv; + constexpr static std::string_view WinGetPackageValueSetKey_UseLatest = "useLatest"sv; + + struct WinGetSource + { + std::string Name; + std::string Type; + std::string Arg; + bool Present = true; + + bool Empty() + { + return Name.empty() && Arg.empty() && Type.empty(); + } + }; + + std::string GetPropertyValueAsString(const winrt::Windows::Foundation::IInspectable& value) + { + IPropertyValue propertyValue = value.try_as(); + if (propertyValue && propertyValue.Type() == PropertyType::String) + { + return Utility::ConvertToUTF8(propertyValue.GetString()); + } + + return {}; + } + + bool GetPropertyValueAsBoolean(const winrt::Windows::Foundation::IInspectable& value, bool defaultIfFailed = false) + { + IPropertyValue propertyValue = value.try_as(); + if (propertyValue && propertyValue.Type() == PropertyType::Boolean) + { + return propertyValue.GetBoolean(); + } + + return defaultIfFailed; + } + + WinGetSource ParseWinGetSourceFromSettings(const ValueSet& settings) + { + WinGetSource result; + + // Iterate through the value set as Powershell variables are case-insensitive. + for (auto const& settingsPair : settings) + { + auto settingsKey = Utility::ConvertToUTF8(settingsPair.Key()); + + if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Name, settingsKey)) + { + result.Name = GetPropertyValueAsString(settingsPair.Value()); + } + else if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Type, settingsKey)) + { + result.Type = GetPropertyValueAsString(settingsPair.Value()); + } + else if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Arg, settingsKey)) + { + result.Arg = GetPropertyValueAsString(settingsPair.Value()); + } + else if (Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Ensure, settingsKey)) + { + result.Present = Utility::CaseInsensitiveEquals(WinGetSourceValueSetKey_Ensure_Present, GetPropertyValueAsString(settingsPair.Value())); + } + } + + return result; + } + + bool IsWellKnownSourceName(std::string_view sourceName) + { + return Utility::CaseInsensitiveEquals(WellKnownSourceName_WinGet, sourceName) || + Utility::CaseInsensitiveEquals(WellKnownSourceName_MSStore, sourceName) || + Utility::CaseInsensitiveEquals(WellKnownSourceName_WinGetFont, sourceName); + } + + bool ValidateWellKnownSource(const WinGetSource& source) + { + static std::vector wellKnownSourceDetails = + { + Repository::Source{ Repository::WellKnownSource::WinGet }.GetDetails(), + Repository::Source{ Repository::WellKnownSource::MicrosoftStore }.GetDetails(), + Repository::Source{ Repository::WellKnownSource::WinGetFont }.GetDetails(), + }; + + for (auto const& wellKnownSource : wellKnownSourceDetails) + { + if (Utility::CaseInsensitiveEquals(wellKnownSource.Name, source.Name) && + Utility::CaseInsensitiveEquals(wellKnownSource.Arg, source.Arg) && + Utility::CaseInsensitiveEquals(wellKnownSource.Type, source.Type)) + { + return true; + } + } + + return false; + } + + struct WinGetPackage + { + std::string Id; + std::string Version; + std::string Source; + bool UseLatest = false; + + bool Empty() + { + return Id.empty() && Version.empty() && Source.empty(); + } + }; + + WinGetPackage ParseWinGetPackageFromSettings(const ValueSet& settings) + { + // Iterate through the value set as Powershell variables are case-insensitive. + WinGetPackage result; + for (auto const& settingsPair : settings) + { + auto settingsKey = Utility::ConvertToUTF8(settingsPair.Key()); + if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_Id, settingsKey)) + { + result.Id = GetPropertyValueAsString(settingsPair.Value()); + } + else if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_Version, settingsKey)) + { + result.Version = GetPropertyValueAsString(settingsPair.Value()); + } + else if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_Source, settingsKey)) + { + result.Source = GetPropertyValueAsString(settingsPair.Value()); + } + else if (Utility::CaseInsensitiveEquals(WinGetPackageValueSetKey_UseLatest, settingsKey)) + { + result.UseLatest = GetPropertyValueAsBoolean(settingsPair.Value()); + } + } + + return result; + } + } + + bool WingetDscModuleUnitValidator::ValidateConfigurationSetUnit(Execution::Context& context, const ConfigurationUnit& unit) + { + bool foundIssues = false; + auto details = unit.Details(); + auto unitType = Utility::ConvertToUTF8(details.UnitType()); + auto unitIntent = unit.Intent(); + + if (Utility::CaseInsensitiveEquals(UnitType_WinGetSource, unitType)) + { + auto source = ParseWinGetSourceFromSettings(unit.Settings()); + + // Validate basic semantics. + if (source.Name.empty()) + { + AICLI_LOG(Config, Error, << "WinGetSource unit missing required arg: Name"); + context.Reporter.Error() << Resource::String::WinGetResourceUnitMissingRequiredArg(Utility::LocIndView{ UnitType_WinGetSource }, "Name"_liv) << std::endl; + foundIssues = true; + } + if (source.Arg.empty() && source.Present) + { + AICLI_LOG(Config, Error, << "WinGetSource unit missing required arg: Argument"); + context.Reporter.Error() << Resource::String::WinGetResourceUnitMissingRequiredArg(Utility::LocIndView{ UnitType_WinGetSource }, "Argument"_liv) << std::endl; + foundIssues = true; + } + + // Validate well known source or process 3rd party source. + if (IsWellKnownSourceName(source.Name)) + { + if (!ValidateWellKnownSource(source)) + { + AICLI_LOG(Config, Warning, << "WinGetSource conflicts with a well known source. Source: " << source.Name); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitKnownSourceConfliction(Utility::LocIndView{ source.Name }) << std::endl; + foundIssues = true; + } + } + else + { + if (unitIntent == ConfigurationUnitIntent::Assert) + { + AICLI_LOG(Config, Warning, << "Asserting on 3rd party source: " << source.Name); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitThirdPartySourceAssertion(Utility::LocIndView{ source.Name }) << std::endl; + foundIssues = true; + } + else if (unitIntent == ConfigurationUnitIntent::Apply) + { + // Add to dependency source map so it can be validated with later WinGetPackage source + m_dependenciesSourceAndUnitIdMap.emplace(Utility::FoldCase(std::string_view{ source.Name }), Utility::FoldCase(Utility::NormalizedString{ unit.Identifier() })); + } + } + } + else if (Utility::CaseInsensitiveEquals(UnitType_WinGetPackage, unitType)) + { + auto package = ParseWinGetPackageFromSettings(unit.Settings()); + if (package.Empty()) + { + AICLI_LOG(Config, Warning, << "Failed to parse WinGetPackage or empty content."); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitEmptyContent(Utility::LocIndView{ UnitType_WinGetPackage }) << std::endl; + foundIssues = true; + } + // Validate basic semantics. + if (package.Id.empty()) + { + AICLI_LOG(Config, Error, << "WinGetPackage unit missing required arg: Id"); + context.Reporter.Error() << Resource::String::WinGetResourceUnitMissingRequiredArg(Utility::LocIndView{ UnitType_WinGetPackage }, "Id"_liv) << std::endl; + foundIssues = true; + } + if (package.Source.empty()) + { + AICLI_LOG(Config, Warning, << "WinGetPackage unit missing recommended arg: Source"); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitMissingRecommendedArg(Utility::LocIndView{ UnitType_WinGetPackage }, "Source"_liv) << std::endl; + foundIssues = true; + } + if (package.UseLatest && !package.Version.empty()) + { + AICLI_LOG(Config, Warning, << "WinGetPackage unit both UseLatest and Version declared. Package: " << package.Id); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitBothPackageVersionAndUseLatest(Utility::LocIndView{ package.Id }) << std::endl; + foundIssues = true; + } + // Validate dependency source is configured. + if (!package.Source.empty() && !IsWellKnownSourceName(package.Source)) + { + if (unitIntent == ConfigurationUnitIntent::Assert) + { + AICLI_LOG(Config, Warning, << "Asserting on a package that depends on a 3rd party source. Package: " << package.Id << " Source: " << package.Source); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitThirdPartySourceAssertionForPackage(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; + foundIssues = true; + } + else + { + auto dependencySourceItr = m_dependenciesSourceAndUnitIdMap.find(Utility::FoldCase(std::string_view{ package.Source })); + if (dependencySourceItr == m_dependenciesSourceAndUnitIdMap.end()) + { + AICLI_LOG(Config, Warning, << "WinGetPackage depends on a 3rd party source not previously configured. Package: " << package.Id << " Source: " << package.Source); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitDependencySourceNotConfigured(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; + foundIssues = true; + } + else + { + bool foundInUnitDependencies = false; + for (auto const& entry : unit.Dependencies()) + { + // The map contains normalized string, so just use direct comparison; + if (dependencySourceItr->second == Utility::FoldCase(Utility::NormalizedString{ entry })) + { + foundInUnitDependencies = true; + break; + } + } + if (!foundInUnitDependencies) + { + AICLI_LOG(Config, Warning, << "WinGetPackage depends on a 3rd party source. It is recommended to add the WinGetSources unit configuring the source to the unit's dependsOn list. Package: " << package.Id << " Source: " << package.Source); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitDependencySourceNotDeclaredAsDependency(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; + foundIssues = true; + } + } + } + } + // Validate package is found and version available. + try + { + Repository::Source source{ package.Source }; + if (!source) + { + AICLI_LOG(Config, Warning, << "Failed to open WinGet source. Package: " << package.Id << " Source: " << package.Source); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageSourceOpenFailed(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Source }) << std::endl; + foundIssues = true; + } + else + { + source.SetCaller("winget-cli-configuration-unit-module-validation"); + ProgressCallback empty; + source.Open(empty); + Repository::SearchRequest searchRequest; + searchRequest.Filters.emplace_back(Repository::PackageMatchFilter{ Repository::PackageMatchField::Id, Repository::MatchType::CaseInsensitive, package.Id }); + auto searchResult = source.Search(searchRequest); + if (searchResult.Matches.size() == 0) + { + AICLI_LOG(Config, Warning, << "WinGetPackage not found: " << package.Id); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageNotFound(Utility::LocIndView{ package.Id }) << std::endl; + foundIssues = true; + } + else if (searchResult.Matches.size() > 1) + { + AICLI_LOG(Config, Warning, << "More than one WinGetPackage found: " << package.Id); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageMultipleFound(Utility::LocIndView{ package.Id }) << std::endl; + foundIssues = true; + } + else + { + if (!package.Version.empty()) + { + std::shared_ptr availablePackage = searchResult.Matches.at(0).Package->GetAvailable().at(0); + auto versionKeys = availablePackage->GetVersionKeys(); + bool foundVersion = false; + for (auto const& versionKey : versionKeys) + { + if (versionKey.Version == Utility::NormalizedString(package.Version)) + { + foundVersion = true; + break; + } + } + if (!foundVersion) + { + AICLI_LOG(Config, Warning, << "WinGetPackage version not found. Package: " << package.Id << " Version: " << package.Version); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackageVersionNotFound(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Version }) << std::endl; + foundIssues = true; + } + if (versionKeys.size() == 1) + { + AICLI_LOG(Config, Warning, << "WinGetPackage version specified with only one version available: " << package.Id); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitPackageVersionSpecifiedWithOnlyOnePackageVersion(Utility::LocIndView{ package.Id }, Utility::LocIndView{ package.Version }) << std::endl; + foundIssues = true; + } + } + } + } + } + catch (...) + { + AICLI_LOG(Config, Warning, << "Failed to validate WinGetPackage: " << package.Id); + context.Reporter.Warn() << Resource::String::WinGetResourceUnitFailedToValidatePackage(Utility::LocIndView{ package.Id }) << std::endl; + foundIssues = true; + } + } + + return !foundIssues; + } +} diff --git a/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.h b/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.h index 3a553ce175..42d0fd6dd8 100644 --- a/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.h +++ b/src/AppInstallerCLICore/ConfigurationWingetDscModuleUnitValidation.h @@ -1,30 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace winrt::Microsoft::Management::Configuration -{ - struct ConfigurationUnit; -} - -namespace AppInstaller::CLI::Execution -{ - struct Context; -} - -namespace AppInstaller::CLI::Configuration -{ - using namespace std::string_view_literals; - - struct WingetDscModuleUnitValidator - { - bool ValidateConfigurationSetUnit(AppInstaller::CLI::Execution::Context& context, const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); - - std::string_view ModuleName() { return "Microsoft.WinGet.DSC"sv; }; - - private: - std::map m_dependenciesSourceAndUnitIdMap; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace winrt::Microsoft::Management::Configuration +{ + struct ConfigurationUnit; +} + +namespace AppInstaller::CLI::Execution +{ + struct Context; +} + +namespace AppInstaller::CLI::Configuration +{ + using namespace std::string_view_literals; + + struct WingetDscModuleUnitValidator + { + bool ValidateConfigurationSetUnit(AppInstaller::CLI::Execution::Context& context, const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); + + std::string_view ModuleName() { return "Microsoft.WinGet.DSC"sv; }; + + private: + std::map m_dependenciesSourceAndUnitIdMap; + }; +} diff --git a/src/AppInstallerCLICore/ConfigureExportCommand.cpp b/src/AppInstallerCLICore/ConfigureExportCommand.cpp index 3eb0b0f498..ad42911bd2 100644 --- a/src/AppInstallerCLICore/ConfigureExportCommand.cpp +++ b/src/AppInstallerCLICore/ConfigureExportCommand.cpp @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigureExportCommand.h" -#include "Workflows/ConfigurationFlow.h" -#include "Workflows/MSStoreInstallerHandler.h" -#include "ConfigurationCommon.h" - -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI -{ - std::vector ConfigureExportCommand::GetArguments() const - { - return { - Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, true }, - Argument{ Execution::Args::Type::ConfigurationExportPackageId, Resource::String::ConfigureExportPackageId }, - Argument{ Execution::Args::Type::ConfigurationExportModule, Resource::String::ConfigureExportModule }, - Argument{ Execution::Args::Type::ConfigurationExportResource, Resource::String::ConfigureExportResource }, - Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath }, - Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), - Argument{ Execution::Args::Type::Source, Resource::String::ExportSourceArgumentDescription, ArgumentType::Standard }, - Argument{ Execution::Args::Type::IncludeVersions, Resource::String::ExportIncludeVersionsArgumentDescription, ArgumentType::Flag }, - Argument{ Execution::Args::Type::ConfigurationExportAll, Resource::String::ConfigureExportAll, ArgumentType::Flag }, - Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), - }; - } - - Resource::LocString ConfigureExportCommand::ShortDescription() const - { - return { Resource::String::ConfigureExportCommandShortDescription }; - } - - Resource::LocString ConfigureExportCommand::LongDescription() const - { - return { Resource::String::ConfigureExportCommandLongDescription }; - } - - Utility::LocIndView ConfigureExportCommand::HelpLink() const - { - return "https://aka.ms/winget-command-configure#export"_liv; - } - - void ConfigureExportCommand::ExecuteInternal(Execution::Context& context) const - { - context << - VerifyIsFullPackage << - CreateConfigurationProcessorWithoutFactory << - CreateOrOpenConfigurationSet{ "0.3", context.Args.Contains(Execution::Args::Type::ConfigurationExportAll) } << - CreateConfigurationProcessor << - PopulateConfigurationSetForExport << - WriteConfigFile; - } - - void ConfigureExportCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const - { - Configuration::ValidateCommonArguments(execArgs); - - if (!execArgs.Contains(Execution::Args::Type::ConfigurationExportModule, Execution::Args::Type::ConfigurationExportResource) && - !execArgs.Contains(Execution::Args::Type::ConfigurationExportPackageId) && - !execArgs.Contains(Execution::Args::Type::ConfigurationExportAll)) - { - throw CommandException(Resource::String::ConfigureExportArgumentRequiredError); - } - - if (execArgs.Contains(Execution::Args::Type::ConfigurationExportAll) && - (execArgs.Contains(Execution::Args::Type::ConfigurationExportPackageId) || - execArgs.Contains(Execution::Args::Type::ConfigurationExportModule) || - execArgs.Contains(Execution::Args::Type::ConfigurationExportResource))) - { - throw CommandException(Resource::String::ConfigureExportArgumentConflictWithAllError); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigureExportCommand.h" +#include "Workflows/ConfigurationFlow.h" +#include "Workflows/MSStoreInstallerHandler.h" +#include "ConfigurationCommon.h" + +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI +{ + std::vector ConfigureExportCommand::GetArguments() const + { + return { + Argument{ Execution::Args::Type::OutputFile, Resource::String::OutputFileArgumentDescription, true }, + Argument{ Execution::Args::Type::ConfigurationExportPackageId, Resource::String::ConfigureExportPackageId }, + Argument{ Execution::Args::Type::ConfigurationExportModule, Resource::String::ConfigureExportModule }, + Argument{ Execution::Args::Type::ConfigurationExportResource, Resource::String::ConfigureExportResource }, + Argument{ Execution::Args::Type::ConfigurationModulePath, Resource::String::ConfigurationModulePath }, + Argument::ForType(Execution::Args::Type::ConfigurationProcessorPath), + Argument{ Execution::Args::Type::Source, Resource::String::ExportSourceArgumentDescription, ArgumentType::Standard }, + Argument{ Execution::Args::Type::IncludeVersions, Resource::String::ExportIncludeVersionsArgumentDescription, ArgumentType::Flag }, + Argument{ Execution::Args::Type::ConfigurationExportAll, Resource::String::ConfigureExportAll, ArgumentType::Flag }, + Argument::ForType(Execution::Args::Type::AcceptSourceAgreements), + }; + } + + Resource::LocString ConfigureExportCommand::ShortDescription() const + { + return { Resource::String::ConfigureExportCommandShortDescription }; + } + + Resource::LocString ConfigureExportCommand::LongDescription() const + { + return { Resource::String::ConfigureExportCommandLongDescription }; + } + + Utility::LocIndView ConfigureExportCommand::HelpLink() const + { + return "https://aka.ms/winget-command-configure#export"_liv; + } + + void ConfigureExportCommand::ExecuteInternal(Execution::Context& context) const + { + context << + VerifyIsFullPackage << + CreateConfigurationProcessorWithoutFactory << + CreateOrOpenConfigurationSet{ "0.3", context.Args.Contains(Execution::Args::Type::ConfigurationExportAll) } << + CreateConfigurationProcessor << + PopulateConfigurationSetForExport << + WriteConfigFile; + } + + void ConfigureExportCommand::ValidateArgumentsInternal(Execution::Args& execArgs) const + { + Configuration::ValidateCommonArguments(execArgs); + + if (!execArgs.Contains(Execution::Args::Type::ConfigurationExportModule, Execution::Args::Type::ConfigurationExportResource) && + !execArgs.Contains(Execution::Args::Type::ConfigurationExportPackageId) && + !execArgs.Contains(Execution::Args::Type::ConfigurationExportAll)) + { + throw CommandException(Resource::String::ConfigureExportArgumentRequiredError); + } + + if (execArgs.Contains(Execution::Args::Type::ConfigurationExportAll) && + (execArgs.Contains(Execution::Args::Type::ConfigurationExportPackageId) || + execArgs.Contains(Execution::Args::Type::ConfigurationExportModule) || + execArgs.Contains(Execution::Args::Type::ConfigurationExportResource))) + { + throw CommandException(Resource::String::ConfigureExportArgumentConflictWithAllError); + } + } +} diff --git a/src/AppInstallerCLICore/ConfigureExportCommand.h b/src/AppInstallerCLICore/ConfigureExportCommand.h index 41a58c372a..8962d383e6 100644 --- a/src/AppInstallerCLICore/ConfigureExportCommand.h +++ b/src/AppInstallerCLICore/ConfigureExportCommand.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" - -namespace AppInstaller::CLI -{ - struct ConfigureExportCommand final : public Command - { - ConfigureExportCommand(std::string_view parent) : Command("export", parent) {} - - std::vector GetArguments() const override; - - Resource::LocString ShortDescription() const override; - Resource::LocString LongDescription() const override; - - Utility::LocIndView HelpLink() const override; - - protected: - void ExecuteInternal(Execution::Context& context) const override; - void ValidateArgumentsInternal(Execution::Args& execArgs) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" + +namespace AppInstaller::CLI +{ + struct ConfigureExportCommand final : public Command + { + ConfigureExportCommand(std::string_view parent) : Command("export", parent) {} + + std::vector GetArguments() const override; + + Resource::LocString ShortDescription() const override; + Resource::LocString LongDescription() const override; + + Utility::LocIndView HelpLink() const override; + + protected: + void ExecuteInternal(Execution::Context& context) const override; + void ValidateArgumentsInternal(Execution::Args& execArgs) const override; + }; +} diff --git a/src/AppInstallerCLICore/ContextOrchestrator.cpp b/src/AppInstallerCLICore/ContextOrchestrator.cpp index b576ffc9f7..c2f48d2765 100644 --- a/src/AppInstallerCLICore/ContextOrchestrator.cpp +++ b/src/AppInstallerCLICore/ContextOrchestrator.cpp @@ -1,592 +1,592 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ExecutionContext.h" -#include "ContextOrchestrator.h" -#include "COMContext.h" -#include "Commands/COMCommand.h" -#include "Public/ShutdownMonitoring.h" -#include "winget/UserSettings.h" -#include - -namespace AppInstaller::CLI::Execution -{ - namespace - { - // Operation command queue used by install, uninstall and repair commands. - constexpr static std::string_view OperationCommandQueueName = "operation"sv; - - // Callback function used by worker threads in the queue. - // context must be a pointer to a queue item. - void CALLBACK OrchestratorQueueWorkCallback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_WORK) - { - auto queueItem = reinterpret_cast(context); - auto queue = queueItem->GetCurrentQueue(); - if (queue) - { - queue->RunItem(queueItem->GetId()); - } - } - - // Get command queue name based on command name. - std::string_view GetCommandQueueName(std::string_view commandName) - { - if (commandName == COMInstallCommand::CommandName || commandName == COMUninstallCommand::CommandName || commandName == COMRepairCommand::CommandName) - { - return OperationCommandQueueName; - } - - return commandName; - } - } - - ContextOrchestrator& ContextOrchestrator::Instance() - { - static ContextOrchestrator s_instance; - return s_instance; - } - - ContextOrchestrator::ContextOrchestrator() : ContextOrchestrator(std::thread::hardware_concurrency()) {} - - ContextOrchestrator::ContextOrchestrator(unsigned int hardwareConcurrency) - { - ProgressCallback progress; - m_installingWriteableSource = Repository::Source(Repository::PredefinedSource::Installing); - m_installingWriteableSource.Open(progress); - - // Decide how many threads to use for each command. - // We always allow only one install at a time. - // For download, if we can find the number of supported concurrent threads, - // use that as the maximum (up to 3); otherwise use a single thread. - const UINT32 maxDownloadThreads = 3; - const UINT32 operationThreads = 1; - const UINT32 downloadThreads = std::min(hardwareConcurrency > 1 ? hardwareConcurrency - 1 : 1, maxDownloadThreads); - - AddCommandQueue(COMDownloadCommand::CommandName, downloadThreads); - AddCommandQueue(OperationCommandQueueName, operationThreads); - } - - void ContextOrchestrator::AddCommandQueue(std::string_view commandName, UINT32 allowedThreads) - { - std::lock_guard lockQueue{ m_queueLock }; - m_commandQueues.emplace(commandName, std::make_unique(*this, commandName, allowedThreads)); - } - - _Requires_lock_held_(m_queueLock) - std::shared_ptr ContextOrchestrator::FindById(const OrchestratorQueueItemId& comparisonQueueItemId) - { - for (const auto& queue : m_commandQueues) - { - auto item = queue.second->FindById(comparisonQueueItemId); - if (item) - { - return item; - } - } - - return {}; - } - - void ContextOrchestrator::EnqueueAndRunItem(const std::shared_ptr& item) - { - std::lock_guard lockQueue{ m_queueLock }; - - if (item->IsOnFirstCommand()) - { - // Directly error on attempting to enqueue first time - THROW_HR_IF(ToHRESULT(m_disabledReason), !m_enabled); - - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING), FindById(item->GetId())); - - // Log the beginning of the item - item->GetContext().GetThreadGlobals().GetTelemetryLogger().LogCommand(item->GetItemCommandName()); - } - else if (!m_enabled) - { - // On subsequent command enqueues, cancel and complete the item - item->GetContext().Cancel(m_disabledReason, true); - item->HandleItemCompletion(*this); - } - - std::string commandQueueName{ GetCommandQueueName(item->GetNextCommand().Name()) }; - m_commandQueues.at(commandQueueName)->EnqueueAndRunItem(item); - } - - void ContextOrchestrator::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state) - { - std::lock_guard lockQueue{ m_queueLock }; - for (const auto& queue : m_commandQueues) - { - if (queue.second->RemoveItemInState(item, state, true)) - { - return; - } - } - } - - void ContextOrchestrator::CancelQueueItem(const OrchestratorQueueItem& item) - { - // Always cancel the item, even if it isn't running yet, to get the terminationHR set correctly. - item.GetContext().Cancel(CancelReason::Abort, true); - - RemoveItemInState(item, OrchestratorQueueItemState::Queued); - } - - std::shared_ptr ContextOrchestrator::GetQueueItem(const OrchestratorQueueItemId& queueItemId) - { - std::lock_guard lock{ m_queueLock }; - - return FindById(queueItemId); - } - - void ContextOrchestrator::AddItemManifestToInstallingSource(const OrchestratorQueueItem& queueItem) - { - if (queueItem.IsApplicableForInstallingSource()) - { - const auto& manifest = queueItem.GetContext().Get(); - m_installingWriteableSource.AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); - } - } - - void ContextOrchestrator::RemoveItemManifestFromInstallingSource(const OrchestratorQueueItem& queueItem) - { - if (queueItem.IsApplicableForInstallingSource()) - { - const auto& manifest = queueItem.GetContext().Get(); - m_installingWriteableSource.RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); - } - } - - void ContextOrchestrator::RegisterForShutdownSynchronization() - { - static std::once_flag registerComponentOnceFlag; - std::call_once(registerComponentOnceFlag, - [&]() - { - using namespace ShutdownMonitoring; - - ServerShutdownSynchronization::ComponentSystem component; - component.BlockNewWork = StaticDisable; - component.BeginShutdown = StaticCancelQueuedItems; - component.Wait = StaticWaitForRunningItems; - - ServerShutdownSynchronization::AddComponent(component); - }); - } - - void ContextOrchestrator::StaticDisable(CancelReason reason) - { - Instance().Disable(reason); - } - - void ContextOrchestrator::StaticCancelQueuedItems(CancelReason reason) - { - Instance().CancelQueuedItems(reason); - } - - void ContextOrchestrator::StaticWaitForRunningItems() - { - Instance().WaitForRunningItems(); - } - - void ContextOrchestrator::Disable(CancelReason reason) - { - std::lock_guard lock{ m_queueLock }; - m_enabled = false; - m_disabledReason = reason; - } - - void ContextOrchestrator::CancelQueuedItems(CancelReason reason) - { - std::lock_guard lock{ m_queueLock }; - for (const auto& queue : m_commandQueues) - { - queue.second->CancelAllItems(reason); - } - } - - void ContextOrchestrator::WaitForRunningItems() - { - std::lock_guard lock{ m_queueLock }; - for (const auto& queue : m_commandQueues) - { - queue.second->WaitForEmptyQueue(); - } - } - - bool ContextOrchestrator::WaitForRunningItems(DWORD timeoutMilliseconds) - { - std::lock_guard lock{ m_queueLock }; - for (const auto& queue : m_commandQueues) - { - if (!queue.second->WaitForEmptyQueue(timeoutMilliseconds)) - { - return false; - } - } - - return true; - } - - std::string ContextOrchestrator::GetStatusString() - { - std::ostringstream stream; - - std::lock_guard lock{ m_queueLock }; - - if (!m_enabled) - { - stream << "Disabled due to " << ToIntegral(m_disabledReason) << std::endl; - } - - for (const auto& queue : m_commandQueues) - { - stream << queue.second->GetStatusString(); - } - - return stream.str(); - } - - _Requires_lock_held_(m_itemLock) - std::deque>::iterator OrchestratorQueue::FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId) - { - return std::find_if(m_queueItems.begin(), m_queueItems.end(), [&comparisonQueueItemId](const std::shared_ptr& item) {return (item->GetId().IsSame(comparisonQueueItemId)); }); - } - - _Requires_lock_held_(m_itemLock) - std::shared_ptr OrchestratorQueue::FindById(const OrchestratorQueueItemId& comparisonQueueItemId) - { - auto itr = FindIteratorById(comparisonQueueItemId); - if (itr != m_queueItems.end()) - { - return *itr; - } - - return {}; - } - - void OrchestratorQueue::EnqueueItem(const std::shared_ptr& item) - { - { - std::lock_guard lockQueue{ m_itemLock }; - m_queueItems.push_back(item); - m_queueEmpty.ResetEvent(); - } - - // Add the package to the Installing source so that it can be queried using the Source interface. - // Only do this the first time the item is queued. - if (item->IsOnFirstCommand()) - { - try - { - m_orchestrator.AddItemManifestToInstallingSource(*item); - } - catch (...) - { - std::lock_guard lockQueue{ m_itemLock }; - auto itr = FindIteratorById(item->GetId()); - if (itr != m_queueItems.end()) - { - m_queueItems.erase(itr); - - if (m_queueItems.empty()) - { - m_queueEmpty.SetEvent(); - } - } - throw; - } - } - - { - std::lock_guard lockQueue{ m_itemLock }; - item->SetState(OrchestratorQueueItemState::Queued); - } - } - - OrchestratorQueue::OrchestratorQueue(ContextOrchestrator& orchestrator, std::string_view commandName, UINT32 allowedThreads) : - m_orchestrator(orchestrator), m_commandName(commandName), m_allowedThreads(allowedThreads) - { - m_threadPool.reset(CreateThreadpool(nullptr)); - THROW_LAST_ERROR_IF_NULL(m_threadPool); - m_threadPoolCleanupGroup.reset(CreateThreadpoolCleanupGroup()); - THROW_LAST_ERROR_IF_NULL(m_threadPoolCleanupGroup); - InitializeThreadpoolEnvironment(&m_threadPoolCallbackEnviron); - SetThreadpoolCallbackPool(&m_threadPoolCallbackEnviron, m_threadPool.get()); - SetThreadpoolCallbackCleanupGroup(&m_threadPoolCallbackEnviron, m_threadPoolCleanupGroup.get(), nullptr); - - SetThreadpoolThreadMaximum(m_threadPool.get(), m_allowedThreads); - THROW_LAST_ERROR_IF(!SetThreadpoolThreadMinimum(m_threadPool.get(), 1)); - } - - OrchestratorQueue::~OrchestratorQueue() - { - CloseThreadpoolCleanupGroupMembers(m_threadPoolCleanupGroup.get(), false, nullptr); - } - - void OrchestratorQueue::EnqueueAndRunItem(const std::shared_ptr& item) - { - EnqueueItem(item); - - item->SetCurrentQueue(this); - auto work = CreateThreadpoolWork(OrchestratorQueueWorkCallback, item.get(), &m_threadPoolCallbackEnviron); - SubmitThreadpoolWork(work); - } - - void OrchestratorQueue::RunItem(const OrchestratorQueueItemId& itemId) - { - try - { - std::shared_ptr item; - bool isCancelled = false; - - // Try to find the item in the queue. - { - std::lock_guard lockQueue{ m_itemLock }; - item = FindById(itemId); - - if (!item) - { - // Item should be in the queue; this shouldn't happen. - return; - } - - // Only run if the item is queued and not cancelled. - if (item->GetState() == OrchestratorQueueItemState::Queued) - { - // Mark it as running so that it cannot be cancelled by other threads. - item->SetState(OrchestratorQueueItemState::Running); - } - else if (item->GetState() == OrchestratorQueueItemState::Cancelled) - { - isCancelled = true; - } - } - - if (isCancelled) - { - // Do this separate from above block as the Remove function needs to manage the lock. - RemoveItemInState(*item, OrchestratorQueueItemState::Cancelled, true); - } - - // Get the item's command and execute it. - HRESULT exceptionHR = S_OK; - try - { - std::unique_ptr command = item->PopNextCommand(); - - std::unique_ptr setThreadGlobalsToPreviousState = item->GetContext().SetForCurrentThread(); - - command->ValidateArguments(item->GetContext().Args); - - item->GetContext().EnableSignalTerminationHandler(); - - ::AppInstaller::CLI::ExecuteWithoutLoggingSuccess(item->GetContext(), command.get()); - } - WINGET_CATCH_STORE(exceptionHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - - if (FAILED(exceptionHR)) - { - // Set the termination hr directly from any exception that escaped so that the context always - // has the result of the operation no matter how it failed. - item->GetContext().SetTerminationHR(exceptionHR); - } - - item->GetContext().EnableSignalTerminationHandler(false); - - if (FAILED(item->GetContext().GetTerminationHR()) || item->IsComplete()) - { - if (SUCCEEDED(item->GetContext().GetTerminationHR())) - { - item->GetContext().GetThreadGlobals().GetTelemetryLogger().LogCommandSuccess(item->GetItemCommandName()); - } - - RemoveItemInState(*item, OrchestratorQueueItemState::Running, true); - } - else - { - // Remove item from this queue and add it to the queue for the next command. - RemoveItemInState(*item, OrchestratorQueueItemState::Running, false); - m_orchestrator.EnqueueAndRunItem(item); - } - } - catch (...) - { - } - } - - void OrchestratorQueue::CancelAllItems(CancelReason reason) - { - std::lock_guard lockQueue{ m_itemLock }; - - for (auto itr = m_queueItems.begin(); itr != m_queueItems.end(); itr++) - { - auto& item = *itr; - - item->GetContext().Cancel(reason, true); - - // This mimics ContextOrchestrator::CancelQueueItem, which speeds up the process of cancelling queued items - if (item->GetState() == OrchestratorQueueItemState::Queued) - { - item->SetState(OrchestratorQueueItemState::Cancelled); - item->HandleItemCompletion(m_orchestrator); - } - } - } - - void OrchestratorQueue::WaitForEmptyQueue() - { - m_queueEmpty.wait(); - } - - bool OrchestratorQueue::WaitForEmptyQueue(DWORD timeoutMilliseconds) - { - return m_queueEmpty.wait(timeoutMilliseconds); - } - - std::string OrchestratorQueue::GetStatusString() - { - std::ostringstream stream; - stream << m_commandName << '[' << m_allowedThreads << "]\n"; - - std::map stateCounts; - stateCounts[OrchestratorQueueItemState::NotQueued] = 0; - stateCounts[OrchestratorQueueItemState::Queued] = 0; - stateCounts[OrchestratorQueueItemState::Running] = 0; - stateCounts[OrchestratorQueueItemState::Cancelled] = 0; - - { - std::lock_guard lock{ m_itemLock }; - - for (const auto& item : m_queueItems) - { - stateCounts[item->GetState()] += 1; - } - } - - for (const auto& stateCount : stateCounts) - { - stream << " " << ToString(stateCount.first) << " : " << stateCount.second << std::endl; - } - - return stream.str(); - } - - bool OrchestratorQueue::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state, bool isGlobalRemove) - { - // OrchestratorQueueItemState::Running items should only be removed by the thread that ran the item. - // Queued items can be removed by any thread. - // NotQueued items should not be removed since, if found in the queue, they are in the process of being queued by another thread. - bool foundItem = false; - - { - std::lock_guard lockQueue{ m_itemLock }; - - // Look for the item. It's ok if the item is not found since multiple listeners may try to remove the same item. - auto itr = FindIteratorById(item.GetId()); - if (itr != m_queueItems.end() && (*itr)->GetState() == state) - { - foundItem = true; - - // The item must only be removed from the queue by the thread that runs - // it, because the callback uses it. If any other thread tries to remove - // it, we simply mark it as cancelled. - if (state == OrchestratorQueueItemState::Running || state == OrchestratorQueueItemState::Cancelled) - { - (*itr)->SetCurrentQueue(nullptr); - m_queueItems.erase(itr); - - if (m_queueItems.empty()) - { - m_queueEmpty.SetEvent(); - } - } - else if (state == OrchestratorQueueItemState::Queued) - { - (*itr)->SetState(OrchestratorQueueItemState::Cancelled); - } - } - } - - if (foundItem && isGlobalRemove) - { - item.HandleItemCompletion(m_orchestrator); - } - - return foundItem; - } - - bool OrchestratorQueueItemId::IsSame(const OrchestratorQueueItemId& comparedId) const - { - return ((GetPackageId() == comparedId.GetPackageId()) && - (GetSourceId() == comparedId.GetSourceId())); - } - - std::string_view OrchestratorQueueItem::GetItemCommandName() const - { - // The goal is that these should match the winget.exe commands for easy correlation. - switch (m_operationType) - { - case PackageOperationType::Search: return "root:search"sv; - case PackageOperationType::Install: return "root:install"sv; - case PackageOperationType::Upgrade: return "root:upgrade"sv; - case PackageOperationType::Uninstall: return "root:uninstall"sv; - case PackageOperationType::Download: return "root:download"sv; - case PackageOperationType::Repair: return "root:repair"sv; - default: return "unknown"; - } - } - - void OrchestratorQueueItem::HandleItemCompletion(ContextOrchestrator& orchestrator) const - { - orchestrator.RemoveItemManifestFromInstallingSource(*this); - GetCompletedEvent().SetEvent(); - } - - std::unique_ptr OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context, bool isUpgrade) - { - std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), isUpgrade ? PackageOperationType::Upgrade : PackageOperationType::Install); - item->AddCommand(std::make_unique<::AppInstaller::CLI::COMDownloadCommand>(RootCommand::CommandName)); - item->AddCommand(std::make_unique<::AppInstaller::CLI::COMInstallCommand>(RootCommand::CommandName)); - return item; - } - - std::unique_ptr OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) - { - std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Uninstall); - item->AddCommand(std::make_unique<::AppInstaller::CLI::COMUninstallCommand>(RootCommand::CommandName)); - return item; - } - - std::unique_ptr OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) - { - std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Search); - return item; - } - - std::unique_ptr OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) - { - std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Download); - item->AddCommand(std::make_unique<::AppInstaller::CLI::COMDownloadCommand>(RootCommand::CommandName)); - return item; - } - - std::unique_ptr OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) - { - std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Repair); - item->AddCommand(std::make_unique<::AppInstaller::CLI::COMRepairCommand>(RootCommand::CommandName)); - return item; - } - - std::string_view ToString(OrchestratorQueueItemState state) - { - switch (state) - { - case OrchestratorQueueItemState::NotQueued: return "NotQueued"; - case OrchestratorQueueItemState::Queued: return "Queued"; - case OrchestratorQueueItemState::Running: return "Running"; - case OrchestratorQueueItemState::Cancelled: return "Cancelled"; - default: return "Unknown"; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ExecutionContext.h" +#include "ContextOrchestrator.h" +#include "COMContext.h" +#include "Commands/COMCommand.h" +#include "Public/ShutdownMonitoring.h" +#include "winget/UserSettings.h" +#include + +namespace AppInstaller::CLI::Execution +{ + namespace + { + // Operation command queue used by install, uninstall and repair commands. + constexpr static std::string_view OperationCommandQueueName = "operation"sv; + + // Callback function used by worker threads in the queue. + // context must be a pointer to a queue item. + void CALLBACK OrchestratorQueueWorkCallback(PTP_CALLBACK_INSTANCE, PVOID context, PTP_WORK) + { + auto queueItem = reinterpret_cast(context); + auto queue = queueItem->GetCurrentQueue(); + if (queue) + { + queue->RunItem(queueItem->GetId()); + } + } + + // Get command queue name based on command name. + std::string_view GetCommandQueueName(std::string_view commandName) + { + if (commandName == COMInstallCommand::CommandName || commandName == COMUninstallCommand::CommandName || commandName == COMRepairCommand::CommandName) + { + return OperationCommandQueueName; + } + + return commandName; + } + } + + ContextOrchestrator& ContextOrchestrator::Instance() + { + static ContextOrchestrator s_instance; + return s_instance; + } + + ContextOrchestrator::ContextOrchestrator() : ContextOrchestrator(std::thread::hardware_concurrency()) {} + + ContextOrchestrator::ContextOrchestrator(unsigned int hardwareConcurrency) + { + ProgressCallback progress; + m_installingWriteableSource = Repository::Source(Repository::PredefinedSource::Installing); + m_installingWriteableSource.Open(progress); + + // Decide how many threads to use for each command. + // We always allow only one install at a time. + // For download, if we can find the number of supported concurrent threads, + // use that as the maximum (up to 3); otherwise use a single thread. + const UINT32 maxDownloadThreads = 3; + const UINT32 operationThreads = 1; + const UINT32 downloadThreads = std::min(hardwareConcurrency > 1 ? hardwareConcurrency - 1 : 1, maxDownloadThreads); + + AddCommandQueue(COMDownloadCommand::CommandName, downloadThreads); + AddCommandQueue(OperationCommandQueueName, operationThreads); + } + + void ContextOrchestrator::AddCommandQueue(std::string_view commandName, UINT32 allowedThreads) + { + std::lock_guard lockQueue{ m_queueLock }; + m_commandQueues.emplace(commandName, std::make_unique(*this, commandName, allowedThreads)); + } + + _Requires_lock_held_(m_queueLock) + std::shared_ptr ContextOrchestrator::FindById(const OrchestratorQueueItemId& comparisonQueueItemId) + { + for (const auto& queue : m_commandQueues) + { + auto item = queue.second->FindById(comparisonQueueItemId); + if (item) + { + return item; + } + } + + return {}; + } + + void ContextOrchestrator::EnqueueAndRunItem(const std::shared_ptr& item) + { + std::lock_guard lockQueue{ m_queueLock }; + + if (item->IsOnFirstCommand()) + { + // Directly error on attempting to enqueue first time + THROW_HR_IF(ToHRESULT(m_disabledReason), !m_enabled); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSTALL_ALREADY_RUNNING), FindById(item->GetId())); + + // Log the beginning of the item + item->GetContext().GetThreadGlobals().GetTelemetryLogger().LogCommand(item->GetItemCommandName()); + } + else if (!m_enabled) + { + // On subsequent command enqueues, cancel and complete the item + item->GetContext().Cancel(m_disabledReason, true); + item->HandleItemCompletion(*this); + } + + std::string commandQueueName{ GetCommandQueueName(item->GetNextCommand().Name()) }; + m_commandQueues.at(commandQueueName)->EnqueueAndRunItem(item); + } + + void ContextOrchestrator::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state) + { + std::lock_guard lockQueue{ m_queueLock }; + for (const auto& queue : m_commandQueues) + { + if (queue.second->RemoveItemInState(item, state, true)) + { + return; + } + } + } + + void ContextOrchestrator::CancelQueueItem(const OrchestratorQueueItem& item) + { + // Always cancel the item, even if it isn't running yet, to get the terminationHR set correctly. + item.GetContext().Cancel(CancelReason::Abort, true); + + RemoveItemInState(item, OrchestratorQueueItemState::Queued); + } + + std::shared_ptr ContextOrchestrator::GetQueueItem(const OrchestratorQueueItemId& queueItemId) + { + std::lock_guard lock{ m_queueLock }; + + return FindById(queueItemId); + } + + void ContextOrchestrator::AddItemManifestToInstallingSource(const OrchestratorQueueItem& queueItem) + { + if (queueItem.IsApplicableForInstallingSource()) + { + const auto& manifest = queueItem.GetContext().Get(); + m_installingWriteableSource.AddPackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + } + } + + void ContextOrchestrator::RemoveItemManifestFromInstallingSource(const OrchestratorQueueItem& queueItem) + { + if (queueItem.IsApplicableForInstallingSource()) + { + const auto& manifest = queueItem.GetContext().Get(); + m_installingWriteableSource.RemovePackageVersion(manifest, std::filesystem::path{ manifest.Id + '.' + manifest.Version }); + } + } + + void ContextOrchestrator::RegisterForShutdownSynchronization() + { + static std::once_flag registerComponentOnceFlag; + std::call_once(registerComponentOnceFlag, + [&]() + { + using namespace ShutdownMonitoring; + + ServerShutdownSynchronization::ComponentSystem component; + component.BlockNewWork = StaticDisable; + component.BeginShutdown = StaticCancelQueuedItems; + component.Wait = StaticWaitForRunningItems; + + ServerShutdownSynchronization::AddComponent(component); + }); + } + + void ContextOrchestrator::StaticDisable(CancelReason reason) + { + Instance().Disable(reason); + } + + void ContextOrchestrator::StaticCancelQueuedItems(CancelReason reason) + { + Instance().CancelQueuedItems(reason); + } + + void ContextOrchestrator::StaticWaitForRunningItems() + { + Instance().WaitForRunningItems(); + } + + void ContextOrchestrator::Disable(CancelReason reason) + { + std::lock_guard lock{ m_queueLock }; + m_enabled = false; + m_disabledReason = reason; + } + + void ContextOrchestrator::CancelQueuedItems(CancelReason reason) + { + std::lock_guard lock{ m_queueLock }; + for (const auto& queue : m_commandQueues) + { + queue.second->CancelAllItems(reason); + } + } + + void ContextOrchestrator::WaitForRunningItems() + { + std::lock_guard lock{ m_queueLock }; + for (const auto& queue : m_commandQueues) + { + queue.second->WaitForEmptyQueue(); + } + } + + bool ContextOrchestrator::WaitForRunningItems(DWORD timeoutMilliseconds) + { + std::lock_guard lock{ m_queueLock }; + for (const auto& queue : m_commandQueues) + { + if (!queue.second->WaitForEmptyQueue(timeoutMilliseconds)) + { + return false; + } + } + + return true; + } + + std::string ContextOrchestrator::GetStatusString() + { + std::ostringstream stream; + + std::lock_guard lock{ m_queueLock }; + + if (!m_enabled) + { + stream << "Disabled due to " << ToIntegral(m_disabledReason) << std::endl; + } + + for (const auto& queue : m_commandQueues) + { + stream << queue.second->GetStatusString(); + } + + return stream.str(); + } + + _Requires_lock_held_(m_itemLock) + std::deque>::iterator OrchestratorQueue::FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId) + { + return std::find_if(m_queueItems.begin(), m_queueItems.end(), [&comparisonQueueItemId](const std::shared_ptr& item) {return (item->GetId().IsSame(comparisonQueueItemId)); }); + } + + _Requires_lock_held_(m_itemLock) + std::shared_ptr OrchestratorQueue::FindById(const OrchestratorQueueItemId& comparisonQueueItemId) + { + auto itr = FindIteratorById(comparisonQueueItemId); + if (itr != m_queueItems.end()) + { + return *itr; + } + + return {}; + } + + void OrchestratorQueue::EnqueueItem(const std::shared_ptr& item) + { + { + std::lock_guard lockQueue{ m_itemLock }; + m_queueItems.push_back(item); + m_queueEmpty.ResetEvent(); + } + + // Add the package to the Installing source so that it can be queried using the Source interface. + // Only do this the first time the item is queued. + if (item->IsOnFirstCommand()) + { + try + { + m_orchestrator.AddItemManifestToInstallingSource(*item); + } + catch (...) + { + std::lock_guard lockQueue{ m_itemLock }; + auto itr = FindIteratorById(item->GetId()); + if (itr != m_queueItems.end()) + { + m_queueItems.erase(itr); + + if (m_queueItems.empty()) + { + m_queueEmpty.SetEvent(); + } + } + throw; + } + } + + { + std::lock_guard lockQueue{ m_itemLock }; + item->SetState(OrchestratorQueueItemState::Queued); + } + } + + OrchestratorQueue::OrchestratorQueue(ContextOrchestrator& orchestrator, std::string_view commandName, UINT32 allowedThreads) : + m_orchestrator(orchestrator), m_commandName(commandName), m_allowedThreads(allowedThreads) + { + m_threadPool.reset(CreateThreadpool(nullptr)); + THROW_LAST_ERROR_IF_NULL(m_threadPool); + m_threadPoolCleanupGroup.reset(CreateThreadpoolCleanupGroup()); + THROW_LAST_ERROR_IF_NULL(m_threadPoolCleanupGroup); + InitializeThreadpoolEnvironment(&m_threadPoolCallbackEnviron); + SetThreadpoolCallbackPool(&m_threadPoolCallbackEnviron, m_threadPool.get()); + SetThreadpoolCallbackCleanupGroup(&m_threadPoolCallbackEnviron, m_threadPoolCleanupGroup.get(), nullptr); + + SetThreadpoolThreadMaximum(m_threadPool.get(), m_allowedThreads); + THROW_LAST_ERROR_IF(!SetThreadpoolThreadMinimum(m_threadPool.get(), 1)); + } + + OrchestratorQueue::~OrchestratorQueue() + { + CloseThreadpoolCleanupGroupMembers(m_threadPoolCleanupGroup.get(), false, nullptr); + } + + void OrchestratorQueue::EnqueueAndRunItem(const std::shared_ptr& item) + { + EnqueueItem(item); + + item->SetCurrentQueue(this); + auto work = CreateThreadpoolWork(OrchestratorQueueWorkCallback, item.get(), &m_threadPoolCallbackEnviron); + SubmitThreadpoolWork(work); + } + + void OrchestratorQueue::RunItem(const OrchestratorQueueItemId& itemId) + { + try + { + std::shared_ptr item; + bool isCancelled = false; + + // Try to find the item in the queue. + { + std::lock_guard lockQueue{ m_itemLock }; + item = FindById(itemId); + + if (!item) + { + // Item should be in the queue; this shouldn't happen. + return; + } + + // Only run if the item is queued and not cancelled. + if (item->GetState() == OrchestratorQueueItemState::Queued) + { + // Mark it as running so that it cannot be cancelled by other threads. + item->SetState(OrchestratorQueueItemState::Running); + } + else if (item->GetState() == OrchestratorQueueItemState::Cancelled) + { + isCancelled = true; + } + } + + if (isCancelled) + { + // Do this separate from above block as the Remove function needs to manage the lock. + RemoveItemInState(*item, OrchestratorQueueItemState::Cancelled, true); + } + + // Get the item's command and execute it. + HRESULT exceptionHR = S_OK; + try + { + std::unique_ptr command = item->PopNextCommand(); + + std::unique_ptr setThreadGlobalsToPreviousState = item->GetContext().SetForCurrentThread(); + + command->ValidateArguments(item->GetContext().Args); + + item->GetContext().EnableSignalTerminationHandler(); + + ::AppInstaller::CLI::ExecuteWithoutLoggingSuccess(item->GetContext(), command.get()); + } + WINGET_CATCH_STORE(exceptionHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + + if (FAILED(exceptionHR)) + { + // Set the termination hr directly from any exception that escaped so that the context always + // has the result of the operation no matter how it failed. + item->GetContext().SetTerminationHR(exceptionHR); + } + + item->GetContext().EnableSignalTerminationHandler(false); + + if (FAILED(item->GetContext().GetTerminationHR()) || item->IsComplete()) + { + if (SUCCEEDED(item->GetContext().GetTerminationHR())) + { + item->GetContext().GetThreadGlobals().GetTelemetryLogger().LogCommandSuccess(item->GetItemCommandName()); + } + + RemoveItemInState(*item, OrchestratorQueueItemState::Running, true); + } + else + { + // Remove item from this queue and add it to the queue for the next command. + RemoveItemInState(*item, OrchestratorQueueItemState::Running, false); + m_orchestrator.EnqueueAndRunItem(item); + } + } + catch (...) + { + } + } + + void OrchestratorQueue::CancelAllItems(CancelReason reason) + { + std::lock_guard lockQueue{ m_itemLock }; + + for (auto itr = m_queueItems.begin(); itr != m_queueItems.end(); itr++) + { + auto& item = *itr; + + item->GetContext().Cancel(reason, true); + + // This mimics ContextOrchestrator::CancelQueueItem, which speeds up the process of cancelling queued items + if (item->GetState() == OrchestratorQueueItemState::Queued) + { + item->SetState(OrchestratorQueueItemState::Cancelled); + item->HandleItemCompletion(m_orchestrator); + } + } + } + + void OrchestratorQueue::WaitForEmptyQueue() + { + m_queueEmpty.wait(); + } + + bool OrchestratorQueue::WaitForEmptyQueue(DWORD timeoutMilliseconds) + { + return m_queueEmpty.wait(timeoutMilliseconds); + } + + std::string OrchestratorQueue::GetStatusString() + { + std::ostringstream stream; + stream << m_commandName << '[' << m_allowedThreads << "]\n"; + + std::map stateCounts; + stateCounts[OrchestratorQueueItemState::NotQueued] = 0; + stateCounts[OrchestratorQueueItemState::Queued] = 0; + stateCounts[OrchestratorQueueItemState::Running] = 0; + stateCounts[OrchestratorQueueItemState::Cancelled] = 0; + + { + std::lock_guard lock{ m_itemLock }; + + for (const auto& item : m_queueItems) + { + stateCounts[item->GetState()] += 1; + } + } + + for (const auto& stateCount : stateCounts) + { + stream << " " << ToString(stateCount.first) << " : " << stateCount.second << std::endl; + } + + return stream.str(); + } + + bool OrchestratorQueue::RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state, bool isGlobalRemove) + { + // OrchestratorQueueItemState::Running items should only be removed by the thread that ran the item. + // Queued items can be removed by any thread. + // NotQueued items should not be removed since, if found in the queue, they are in the process of being queued by another thread. + bool foundItem = false; + + { + std::lock_guard lockQueue{ m_itemLock }; + + // Look for the item. It's ok if the item is not found since multiple listeners may try to remove the same item. + auto itr = FindIteratorById(item.GetId()); + if (itr != m_queueItems.end() && (*itr)->GetState() == state) + { + foundItem = true; + + // The item must only be removed from the queue by the thread that runs + // it, because the callback uses it. If any other thread tries to remove + // it, we simply mark it as cancelled. + if (state == OrchestratorQueueItemState::Running || state == OrchestratorQueueItemState::Cancelled) + { + (*itr)->SetCurrentQueue(nullptr); + m_queueItems.erase(itr); + + if (m_queueItems.empty()) + { + m_queueEmpty.SetEvent(); + } + } + else if (state == OrchestratorQueueItemState::Queued) + { + (*itr)->SetState(OrchestratorQueueItemState::Cancelled); + } + } + } + + if (foundItem && isGlobalRemove) + { + item.HandleItemCompletion(m_orchestrator); + } + + return foundItem; + } + + bool OrchestratorQueueItemId::IsSame(const OrchestratorQueueItemId& comparedId) const + { + return ((GetPackageId() == comparedId.GetPackageId()) && + (GetSourceId() == comparedId.GetSourceId())); + } + + std::string_view OrchestratorQueueItem::GetItemCommandName() const + { + // The goal is that these should match the winget.exe commands for easy correlation. + switch (m_operationType) + { + case PackageOperationType::Search: return "root:search"sv; + case PackageOperationType::Install: return "root:install"sv; + case PackageOperationType::Upgrade: return "root:upgrade"sv; + case PackageOperationType::Uninstall: return "root:uninstall"sv; + case PackageOperationType::Download: return "root:download"sv; + case PackageOperationType::Repair: return "root:repair"sv; + default: return "unknown"; + } + } + + void OrchestratorQueueItem::HandleItemCompletion(ContextOrchestrator& orchestrator) const + { + orchestrator.RemoveItemManifestFromInstallingSource(*this); + GetCompletedEvent().SetEvent(); + } + + std::unique_ptr OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context, bool isUpgrade) + { + std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), isUpgrade ? PackageOperationType::Upgrade : PackageOperationType::Install); + item->AddCommand(std::make_unique<::AppInstaller::CLI::COMDownloadCommand>(RootCommand::CommandName)); + item->AddCommand(std::make_unique<::AppInstaller::CLI::COMInstallCommand>(RootCommand::CommandName)); + return item; + } + + std::unique_ptr OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) + { + std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Uninstall); + item->AddCommand(std::make_unique<::AppInstaller::CLI::COMUninstallCommand>(RootCommand::CommandName)); + return item; + } + + std::unique_ptr OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) + { + std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Search); + return item; + } + + std::unique_ptr OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) + { + std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Download); + item->AddCommand(std::make_unique<::AppInstaller::CLI::COMDownloadCommand>(RootCommand::CommandName)); + return item; + } + + std::unique_ptr OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr context) + { + std::unique_ptr item = std::make_unique(OrchestratorQueueItemId(std::move(packageId), std::move(sourceId)), std::move(context), PackageOperationType::Repair); + item->AddCommand(std::make_unique<::AppInstaller::CLI::COMRepairCommand>(RootCommand::CommandName)); + return item; + } + + std::string_view ToString(OrchestratorQueueItemState state) + { + switch (state) + { + case OrchestratorQueueItemState::NotQueued: return "NotQueued"; + case OrchestratorQueueItemState::Queued: return "Queued"; + case OrchestratorQueueItemState::Running: return "Running"; + case OrchestratorQueueItemState::Cancelled: return "Cancelled"; + default: return "Unknown"; + } + } +} diff --git a/src/AppInstallerCLICore/ContextOrchestrator.h b/src/AppInstallerCLICore/ContextOrchestrator.h index 7f89166a8c..073bd70ccf 100644 --- a/src/AppInstallerCLICore/ContextOrchestrator.h +++ b/src/AppInstallerCLICore/ContextOrchestrator.h @@ -1,225 +1,225 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "ExecutionReporter.h" -#include "ExecutionArgs.h" -#include "ExecutionContextData.h" -#include "CompletionData.h" -#include "Command.h" -#include "COMContext.h" -#include -#include -#include - -namespace AppInstaller::CLI::Execution -{ - enum class OrchestratorQueueItemState - { - // Created but not yet queued - NotQueued, - // Queued and waiting to be run - Queued, - // Running in the thread pool - Running, - // Cancelled before it was run; will be deleted when we try to run it - Cancelled - }; - - std::string_view ToString(OrchestratorQueueItemState state); - - struct OrchestratorQueueItemId - { - OrchestratorQueueItemId(std::wstring packageId, std::wstring sourceId) : m_packageId(std::move(packageId)), m_sourceId(std::move(sourceId)) {} - std::wstring_view GetPackageId() const { return m_packageId; } - std::wstring_view GetSourceId() const { return m_sourceId; } - - bool IsSame(const OrchestratorQueueItemId& comparisonQueueItemId) const; - private: - std::wstring m_packageId; - std::wstring m_sourceId; - }; - - struct OrchestratorQueue; - - enum class PackageOperationType - { - Search, - Install, - Upgrade, - Uninstall, - Download, - Repair, - }; - - struct ContextOrchestrator; - - struct OrchestratorQueueItem - { - OrchestratorQueueItem(OrchestratorQueueItemId id, std::unique_ptr context, PackageOperationType operationType) : - m_id(std::move(id)), m_context(std::move(context)), m_operationType(operationType) {} - - OrchestratorQueueItemState GetState() const { return m_state; } - void SetState(OrchestratorQueueItemState state) { m_state = state; } - - OrchestratorQueue* GetCurrentQueue() const { return m_currentQueue; } - void SetCurrentQueue(OrchestratorQueue* currentQueue) { m_currentQueue = currentQueue; } - - COMContext& GetContext() const { return *m_context; } - const wil::unique_event& GetCompletedEvent() const { return m_completedEvent; } - const OrchestratorQueueItemId& GetId() const { return m_id; } - - void AddCommand(std::unique_ptr command) { m_commands.push_back(std::move(command)); } - const Command& GetNextCommand() const { return *m_commands.front(); } - std::unique_ptr PopNextCommand() - { - m_isOnFirstCommand = false; - std::unique_ptr command = std::move(m_commands.front()); - m_commands.pop_front(); - return command; - } - - bool IsOnFirstCommand() const { return m_isOnFirstCommand; } - bool IsComplete() const { return m_commands.empty(); } - bool IsApplicableForInstallingSource() const { return m_operationType == PackageOperationType::Install || m_operationType == PackageOperationType::Upgrade; } - PackageOperationType GetPackageOperationType() const { return m_operationType; } - std::string_view GetItemCommandName() const; - - void HandleItemCompletion(ContextOrchestrator& orchestrator) const; - - private: - OrchestratorQueueItemState m_state = OrchestratorQueueItemState::NotQueued; - std::unique_ptr m_context; - wil::unique_event m_completedEvent{ wil::EventOptions::ManualReset }; - OrchestratorQueueItemId m_id; - std::deque> m_commands; - bool m_isOnFirstCommand = true; - OrchestratorQueue* m_currentQueue = nullptr; - PackageOperationType m_operationType; - }; - - struct OrchestratorQueueItemFactory - { - // Create queue item for install/upgrade - static std::unique_ptr CreateItemForInstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context, bool isUpgrade); - // Create queue item for uninstall - static std::unique_ptr CreateItemForUninstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); - // Create queue item for finding existing entry from the orchestrator queue - static std::unique_ptr CreateItemForSearch(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); - // Create queue item for download - static std::unique_ptr CreateItemForDownload(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); - // Create queue item for repair - static std::unique_ptr CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); - }; - - struct ContextOrchestrator - { - ContextOrchestrator(); - ContextOrchestrator(unsigned int hardwareConcurrency); - static ContextOrchestrator& Instance(); - - void EnqueueAndRunItem(const std::shared_ptr& queueItem); - void CancelQueueItem(const OrchestratorQueueItem& item); - - std::shared_ptr GetQueueItem(const OrchestratorQueueItemId& queueItemId); - - void AddItemManifestToInstallingSource(const OrchestratorQueueItem& queueItem); - void RemoveItemManifestFromInstallingSource(const OrchestratorQueueItem& queueItem); - - // Functions for ServerShutdownSynchronization::ComponentSystem registration - static void RegisterForShutdownSynchronization(); - static void StaticDisable(CancelReason reason); - static void StaticCancelQueuedItems(CancelReason reason); - static void StaticWaitForRunningItems(); - - void Disable(CancelReason reason); - void CancelQueuedItems(CancelReason reason); - void WaitForRunningItems(); - - // Waits for running items to complete; waits up to full time out in *each* queue. - // Returns true to indicate all queues are empty before the timeout. - bool WaitForRunningItems(DWORD timeoutMilliseconds); - - // Gets a string that represents the current state of the orchestrator. - std::string GetStatusString(); - - private: - std::mutex m_queueLock; - bool m_enabled = true; - CancelReason m_disabledReason = CancelReason::None; - void AddCommandQueue(std::string_view commandName, UINT32 allowedThreads); - void RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state); - - _Requires_lock_held_(m_queueLock) - std::shared_ptr FindById(const OrchestratorQueueItemId& queueItemId); - - Repository::Source m_installingWriteableSource; - std::map> m_commandQueues; - }; - - // One of the queues used by the orchestrator. - // All items in the queue execute the same command. - // The queue allows multiple items to run at the same time, up to a limit. - struct OrchestratorQueue - { - OrchestratorQueue(ContextOrchestrator& orchestrator, std::string_view commandName, UINT32 allowedThreads); - ~OrchestratorQueue(); - - // Name of the command this queue can execute - std::string_view CommandName() const { return m_commandName; } - - // Enqueues an item to be run when there are threads available. - void EnqueueAndRunItem(const std::shared_ptr& item); - - // Removes an item by id, provided that it is in the given state. - // Returns true if an item was removed. - // The item can be removed globally from the orchestrator, or from just this queue. - bool RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state, bool isGlobalRemove); - - // Finds an item by id, if it is in the queue. - _Requires_lock_held_(m_itemLock) - std::shared_ptr FindById(const OrchestratorQueueItemId& queueItemId); - - // Runs a single item from the queue. - void RunItem(const OrchestratorQueueItemId& itemId); - - // Cancels and "removes" all items in the queue. - void CancelAllItems(CancelReason reason); - - // Waits until the empty queue event is signaled. - void WaitForEmptyQueue(); - - // Waits until the empty queue event is signaled. - // Returns true to indicate the queue is empty before the timeout. - bool WaitForEmptyQueue(DWORD timeoutMilliseconds); - - // Gets a string that represents the current state of the queue. - std::string GetStatusString(); - - private: - // Enqueues an item. - void EnqueueItem(const std::shared_ptr& item); - - _Requires_lock_held_(m_itemLock) - std::deque>::iterator FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId); - - ContextOrchestrator& m_orchestrator; - std::string_view m_commandName; - - // Number of threads allowed to run items in this queue. - const UINT32 m_allowedThreads; - - // Thread pool for this queue, and associated objects. - // All work items will be added to the callback environment, and the cleanup group - // will manage their closing. - // See https://docs.microsoft.com/windows/win32/procthread/using-the-thread-pool-functions - TP_CALLBACK_ENVIRON m_threadPoolCallbackEnviron; - wil::unique_any m_threadPool; - wil::unique_any m_threadPoolCleanupGroup; - - std::mutex m_itemLock; - std::deque> m_queueItems; - wil::slim_event_manual_reset m_queueEmpty{ true }; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "ExecutionReporter.h" +#include "ExecutionArgs.h" +#include "ExecutionContextData.h" +#include "CompletionData.h" +#include "Command.h" +#include "COMContext.h" +#include +#include +#include + +namespace AppInstaller::CLI::Execution +{ + enum class OrchestratorQueueItemState + { + // Created but not yet queued + NotQueued, + // Queued and waiting to be run + Queued, + // Running in the thread pool + Running, + // Cancelled before it was run; will be deleted when we try to run it + Cancelled + }; + + std::string_view ToString(OrchestratorQueueItemState state); + + struct OrchestratorQueueItemId + { + OrchestratorQueueItemId(std::wstring packageId, std::wstring sourceId) : m_packageId(std::move(packageId)), m_sourceId(std::move(sourceId)) {} + std::wstring_view GetPackageId() const { return m_packageId; } + std::wstring_view GetSourceId() const { return m_sourceId; } + + bool IsSame(const OrchestratorQueueItemId& comparisonQueueItemId) const; + private: + std::wstring m_packageId; + std::wstring m_sourceId; + }; + + struct OrchestratorQueue; + + enum class PackageOperationType + { + Search, + Install, + Upgrade, + Uninstall, + Download, + Repair, + }; + + struct ContextOrchestrator; + + struct OrchestratorQueueItem + { + OrchestratorQueueItem(OrchestratorQueueItemId id, std::unique_ptr context, PackageOperationType operationType) : + m_id(std::move(id)), m_context(std::move(context)), m_operationType(operationType) {} + + OrchestratorQueueItemState GetState() const { return m_state; } + void SetState(OrchestratorQueueItemState state) { m_state = state; } + + OrchestratorQueue* GetCurrentQueue() const { return m_currentQueue; } + void SetCurrentQueue(OrchestratorQueue* currentQueue) { m_currentQueue = currentQueue; } + + COMContext& GetContext() const { return *m_context; } + const wil::unique_event& GetCompletedEvent() const { return m_completedEvent; } + const OrchestratorQueueItemId& GetId() const { return m_id; } + + void AddCommand(std::unique_ptr command) { m_commands.push_back(std::move(command)); } + const Command& GetNextCommand() const { return *m_commands.front(); } + std::unique_ptr PopNextCommand() + { + m_isOnFirstCommand = false; + std::unique_ptr command = std::move(m_commands.front()); + m_commands.pop_front(); + return command; + } + + bool IsOnFirstCommand() const { return m_isOnFirstCommand; } + bool IsComplete() const { return m_commands.empty(); } + bool IsApplicableForInstallingSource() const { return m_operationType == PackageOperationType::Install || m_operationType == PackageOperationType::Upgrade; } + PackageOperationType GetPackageOperationType() const { return m_operationType; } + std::string_view GetItemCommandName() const; + + void HandleItemCompletion(ContextOrchestrator& orchestrator) const; + + private: + OrchestratorQueueItemState m_state = OrchestratorQueueItemState::NotQueued; + std::unique_ptr m_context; + wil::unique_event m_completedEvent{ wil::EventOptions::ManualReset }; + OrchestratorQueueItemId m_id; + std::deque> m_commands; + bool m_isOnFirstCommand = true; + OrchestratorQueue* m_currentQueue = nullptr; + PackageOperationType m_operationType; + }; + + struct OrchestratorQueueItemFactory + { + // Create queue item for install/upgrade + static std::unique_ptr CreateItemForInstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context, bool isUpgrade); + // Create queue item for uninstall + static std::unique_ptr CreateItemForUninstall(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); + // Create queue item for finding existing entry from the orchestrator queue + static std::unique_ptr CreateItemForSearch(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); + // Create queue item for download + static std::unique_ptr CreateItemForDownload(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); + // Create queue item for repair + static std::unique_ptr CreateItemForRepair(std::wstring packageId, std::wstring sourceId, std::unique_ptr context); + }; + + struct ContextOrchestrator + { + ContextOrchestrator(); + ContextOrchestrator(unsigned int hardwareConcurrency); + static ContextOrchestrator& Instance(); + + void EnqueueAndRunItem(const std::shared_ptr& queueItem); + void CancelQueueItem(const OrchestratorQueueItem& item); + + std::shared_ptr GetQueueItem(const OrchestratorQueueItemId& queueItemId); + + void AddItemManifestToInstallingSource(const OrchestratorQueueItem& queueItem); + void RemoveItemManifestFromInstallingSource(const OrchestratorQueueItem& queueItem); + + // Functions for ServerShutdownSynchronization::ComponentSystem registration + static void RegisterForShutdownSynchronization(); + static void StaticDisable(CancelReason reason); + static void StaticCancelQueuedItems(CancelReason reason); + static void StaticWaitForRunningItems(); + + void Disable(CancelReason reason); + void CancelQueuedItems(CancelReason reason); + void WaitForRunningItems(); + + // Waits for running items to complete; waits up to full time out in *each* queue. + // Returns true to indicate all queues are empty before the timeout. + bool WaitForRunningItems(DWORD timeoutMilliseconds); + + // Gets a string that represents the current state of the orchestrator. + std::string GetStatusString(); + + private: + std::mutex m_queueLock; + bool m_enabled = true; + CancelReason m_disabledReason = CancelReason::None; + void AddCommandQueue(std::string_view commandName, UINT32 allowedThreads); + void RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state); + + _Requires_lock_held_(m_queueLock) + std::shared_ptr FindById(const OrchestratorQueueItemId& queueItemId); + + Repository::Source m_installingWriteableSource; + std::map> m_commandQueues; + }; + + // One of the queues used by the orchestrator. + // All items in the queue execute the same command. + // The queue allows multiple items to run at the same time, up to a limit. + struct OrchestratorQueue + { + OrchestratorQueue(ContextOrchestrator& orchestrator, std::string_view commandName, UINT32 allowedThreads); + ~OrchestratorQueue(); + + // Name of the command this queue can execute + std::string_view CommandName() const { return m_commandName; } + + // Enqueues an item to be run when there are threads available. + void EnqueueAndRunItem(const std::shared_ptr& item); + + // Removes an item by id, provided that it is in the given state. + // Returns true if an item was removed. + // The item can be removed globally from the orchestrator, or from just this queue. + bool RemoveItemInState(const OrchestratorQueueItem& item, OrchestratorQueueItemState state, bool isGlobalRemove); + + // Finds an item by id, if it is in the queue. + _Requires_lock_held_(m_itemLock) + std::shared_ptr FindById(const OrchestratorQueueItemId& queueItemId); + + // Runs a single item from the queue. + void RunItem(const OrchestratorQueueItemId& itemId); + + // Cancels and "removes" all items in the queue. + void CancelAllItems(CancelReason reason); + + // Waits until the empty queue event is signaled. + void WaitForEmptyQueue(); + + // Waits until the empty queue event is signaled. + // Returns true to indicate the queue is empty before the timeout. + bool WaitForEmptyQueue(DWORD timeoutMilliseconds); + + // Gets a string that represents the current state of the queue. + std::string GetStatusString(); + + private: + // Enqueues an item. + void EnqueueItem(const std::shared_ptr& item); + + _Requires_lock_held_(m_itemLock) + std::deque>::iterator FindIteratorById(const OrchestratorQueueItemId& comparisonQueueItemId); + + ContextOrchestrator& m_orchestrator; + std::string_view m_commandName; + + // Number of threads allowed to run items in this queue. + const UINT32 m_allowedThreads; + + // Thread pool for this queue, and associated objects. + // All work items will be added to the callback environment, and the cleanup group + // will manage their closing. + // See https://docs.microsoft.com/windows/win32/procthread/using-the-thread-pool-functions + TP_CALLBACK_ENVIRON m_threadPoolCallbackEnviron; + wil::unique_any m_threadPool; + wil::unique_any m_threadPoolCleanupGroup; + + std::mutex m_itemLock; + std::deque> m_queueItems; + wil::slim_event_manual_reset m_queueEmpty{ true }; + }; +} diff --git a/src/AppInstallerCLICore/Core.cpp b/src/AppInstallerCLICore/Core.cpp index a1d5c98eec..a5890bd9cf 100644 --- a/src/AppInstallerCLICore/Core.cpp +++ b/src/AppInstallerCLICore/Core.cpp @@ -9,7 +9,7 @@ #include "Commands/InstallCommand.h" #include "COMContext.h" #include -#include +#include #include "Public/ShutdownMonitoring.h" #ifndef AICLI_DISABLE_TEST_HOOKS @@ -47,76 +47,76 @@ namespace AppInstaller::CLI private: UINT m_previousCP = 0; - }; - - void __CRTDECL abort_signal_handler(int) + }; + + void __CRTDECL abort_signal_handler(int) { #ifndef AICLI_DISABLE_TEST_HOOKS if (Settings::User().Get()) { Debugging::WriteMinidump(); } -#endif +#endif std::_Exit(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); - } - - wil::slim_event_manual_reset& GetMainWaitEvent() - { - static wil::slim_event_manual_reset s_mainWait; - return s_mainWait; - } - - void WaitOnMainWaitEvent() - { - GetMainWaitEvent().wait(5000); - } - - void RegisterShutdownBlocker() + } + + wil::slim_event_manual_reset& GetMainWaitEvent() { - ShutdownMonitoring::ServerShutdownSynchronization::ComponentSystem main{}; - main.Wait = WaitOnMainWaitEvent; + static wil::slim_event_manual_reset s_mainWait; + return s_mainWait; + } + + void WaitOnMainWaitEvent() + { + GetMainWaitEvent().wait(5000); + } + + void RegisterShutdownBlocker() + { + ShutdownMonitoring::ServerShutdownSynchronization::ComponentSystem main{}; + main.Wait = WaitOnMainWaitEvent; ShutdownMonitoring::ServerShutdownSynchronization::AddComponent(main); } } int CoreMain(int argc, wchar_t const** argv) try - { - // This prevents the OS package management from terminating the CLI process before it has had a chance to gracefully exit. - RegisterShutdownBlocker(); - auto signalMainExit = wil::scope_exit([]() { GetMainWaitEvent().SetEvent(); }); - - std::signal(SIGABRT, abort_signal_handler); + { + // This prevents the OS package management from terminating the CLI process before it has had a chance to gracefully exit. + RegisterShutdownBlocker(); + auto signalMainExit = wil::scope_exit([]() { GetMainWaitEvent().SetEvent(); }); + + std::signal(SIGABRT, abort_signal_handler); init_apartment(); -#ifndef AICLI_DISABLE_TEST_HOOKS - // We have to do this here so the auto minidump config initialization gets caught +#ifndef AICLI_DISABLE_TEST_HOOKS + // We have to do this here so the auto minidump config initialization gets caught Logging::OutputDebugStringLogger::Add(); Logging::Log().SetEnabledChannels(Logging::Channel::All); - Logging::Log().SetLevel(Logging::Level::Verbose); + Logging::Log().SetLevel(Logging::Level::Verbose); if (Settings::User().Get()) { Debugging::EnableSelfInitiatedMinidump(); - } - + } + Logging::OutputDebugStringLogger::Remove(); #endif Logging::UseGlobalTelemetryLoggerActivityIdOnly(); Execution::Context context; - auto previousThreadGlobals = context.SetForCurrentThread(); - - // Set up debug string logging during initialization + auto previousThreadGlobals = context.SetForCurrentThread(); + + // Set up debug string logging during initialization Logging::OutputDebugStringLogger::Add(); Logging::Log().SetEnabledChannels(Logging::Channel::All); Logging::Log().SetLevel(Logging::Level::Verbose); Logging::Log().SetEnabledChannels(Settings::User().Get()); Logging::Log().SetLevel(Settings::User().Get()); - Logging::FileLogger::Add(); + Logging::FileLogger::Add(); Logging::OutputDebugStringLogger::Remove(); Logging::EnableWilFailureTelemetry(); @@ -160,13 +160,13 @@ namespace AppInstaller::CLI std::unique_ptr command = std::make_unique(); try - { - // Block CLI execution if WinGetCommandLineInterfaces is disabled by Policy - if (!Settings::GroupPolicies().IsEnabled(Settings::TogglePolicy::Policy::WinGetCommandLineInterfaces)) - { - AICLI_LOG(CLI, Error, << "WinGet is disabled by group policy"); - throw Settings::GroupPolicyException(Settings::TogglePolicy::Policy::WinGetCommandLineInterfaces); - } + { + // Block CLI execution if WinGetCommandLineInterfaces is disabled by Policy + if (!Settings::GroupPolicies().IsEnabled(Settings::TogglePolicy::Policy::WinGetCommandLineInterfaces)) + { + AICLI_LOG(CLI, Error, << "WinGet is disabled by group policy"); + throw Settings::GroupPolicyException(Settings::TogglePolicy::Policy::WinGetCommandLineInterfaces); + } std::unique_ptr subCommand = command->FindSubCommand(invocation); while (subCommand) @@ -195,10 +195,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()); + } + catch (...) + { + return Workflow::HandleException(context, std::current_exception()); } return Execute(context, command); @@ -212,39 +212,39 @@ namespace AppInstaller::CLI void ServerInitialize() { -#ifndef AICLI_DISABLE_TEST_HOOKS - // We have to do this here so the auto minidump config initialization gets caught +#ifndef AICLI_DISABLE_TEST_HOOKS + // We have to do this here so the auto minidump config initialization gets caught Logging::OutputDebugStringLogger::Add(); Logging::Log().SetEnabledChannels(Logging::Channel::All); - Logging::Log().SetLevel(Logging::Level::Verbose); + Logging::Log().SetLevel(Logging::Level::Verbose); if (Settings::User().Get()) { Debugging::EnableSelfInitiatedMinidump(); - } - + } + Logging::OutputDebugStringLogger::Remove(); #endif AppInstaller::CLI::Execution::COMContext::SetLoggers(); - } - + } + void InProcInitialize() { -#ifndef AICLI_DISABLE_TEST_HOOKS - // We have to do this here so the auto minidump config initialization gets caught +#ifndef AICLI_DISABLE_TEST_HOOKS + // We have to do this here so the auto minidump config initialization gets caught Logging::OutputDebugStringLogger::Add(); Logging::Log().SetEnabledChannels(Logging::Channel::All); - Logging::Log().SetLevel(Logging::Level::Verbose); + Logging::Log().SetLevel(Logging::Level::Verbose); if (Settings::User().Get()) { Debugging::EnableSelfInitiatedMinidump(); - } - + } + Logging::OutputDebugStringLogger::Remove(); #endif - + // Explicitly set default channel and level before user settings from PackageManagerSettings AppInstaller::CLI::Execution::COMContext::SetLoggers(AppInstaller::Logging::Channel::Defaults, AppInstaller::Logging::Level::Info); } diff --git a/src/AppInstallerCLICore/ExecutionArgs.h b/src/AppInstallerCLICore/ExecutionArgs.h index 675576a9d7..e1e6355cb9 100644 --- a/src/AppInstallerCLICore/ExecutionArgs.h +++ b/src/AppInstallerCLICore/ExecutionArgs.h @@ -1,307 +1,307 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller::CLI::Execution -{ - struct Args - { - enum class Type : uint32_t - { - // Args to specify where to get app - Query, // Query to be performed against index - MultiQuery, // Like query, but can take multiple values - Manifest, // Provide the app manifest directly - - // Query filtering criteria and query behavior - Id, - Name, - Moniker, - Tag, - Command, - Source, // Index source to be queried against - Count, // Maximum query results - Exact, // Exact match required - - // Manifest selection behavior after an app is found - Version, - Channel, - - // Install behavior - Interactive, - Silent, - Locale, - Log, - CustomSwitches, // CustomSwitches args are args passed to the installer in addition to any defined in the manifest - Override, // Override args are (and the only args) directly passed to installer - InstallLocation, - InstallScope, - InstallArchitecture, - InstallerArchitecture, - InstallerType, - HashOverride, // Ignore hash mismatches - SkipDependencies, // Skip dependencies - DependenciesOnly, // Install only dependencies, not the target package - IgnoreLocalArchiveMalwareScan, // Ignore the local malware scan on archive files - AcceptPackageAgreements, // Accept all license agreements for packages - Rename, // Renames the file of the executable. Only applies to the portable installerType - NoUpgrade, // Install flow should not try to convert to upgrade flow upon finding existing installed version - AllowReboot, // Allows the reboot flow to proceed if applicable - - // Uninstall behavior - Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType. - Preserve, // Retains any files and directories created by the portable exe. - ProductCode, // Uninstalls using the product code as the identifier. - AllVersions, // Uninstall all versions of the package - TargetVersion, // The specific version to target - - //Source Command - SourceName, - SourceType, - SourceArg, - ForceSourceReset, - SourceExplicit, - SourceTrustLevel, - SourcePriority, - SourceEditExplicit, - - //Hash Command - HashFile, - Msix, // Flag to indicate the input file is msix - - //Validate Command - ValidateManifest, - IgnoreWarnings, - - // Complete Command - Word, - CommandLine, - Position, - - // Export Command - IncludeVersions, - - // Import Command - ImportFile, - IgnoreUnavailable, - IgnoreVersions, - - // Download Command - DownloadDirectory, - SkipMicrosoftStorePackageLicense, - Platform, - OSVersion, - - // Setting Command - AdminSettingEnable, - AdminSettingDisable, - SettingName, - SettingValue, - - // Upgrade command - All, // Used in Update command to update all installed packages to latest - IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions - IncludePinned, // Used in Upgrade command to allow upgrades to pinned packages (only for pinning type of pins) - UninstallPrevious, // Used in Upgrade command to override the default manifest behavior to UninstallPrevious - - // Show command - ListVersions, // Used in Show command to list all available versions of an app - - // List Command - Upgrade, // Used in List command to only show versions with upgrades - ListDetails, - Sort, // Sort output by field (repeatable: --sort name --sort id) - SortAscending, // Sort output in ascending order - SortDescending, // Sort output in descending order - - // Pin command - GatedVersion, // Differs from Version in that this supports wildcards - BlockingPin, - PinInstalled, - - // Error command - ErrorInput, - - // Resume Command - ResumeId, - IgnoreResumeLimit, - - // Font Command - Family, - Details, - - // Stub package (extended features) - ExtendedFeaturesEnable, - ExtendedFeaturesDisable, - - // Configuration - ConfigurationFile, - ConfigurationAcceptWarning, - ConfigurationSuppressPrologue, - ConfigurationProcessorPath, - ConfigurationModulePath, - ConfigurationExportPackageId, - ConfigurationExportModule, - ConfigurationExportResource, - ConfigurationExportAll, - ConfigurationHistoryItem, - ConfigurationHistoryRemove, - ConfigurationStatusWatch, - - // DSCv3 resources - DscResourceFunctionGet, - DscResourceFunctionSet, - DscResourceFunctionWhatIf, - DscResourceFunctionTest, - DscResourceFunctionDelete, - DscResourceFunctionExport, - DscResourceFunctionValidate, - DscResourceFunctionResolve, - DscResourceFunctionAdapter, - DscResourceFunctionSchema, - DscResourceFunctionManifest, - - // Common arguments - NoVT, // Disable VirtualTerminal outputs - RetroStyle, // Makes progress display as retro - RainbowStyle, // Makes progress display as a rainbow - NoProgress, // Disables progress bar and spinner - Help, // Show command usage - Info, // Show general info about WinGet - VerboseLogs, // Increases winget logging level to verbose - DisableInteractivity, // Disable interactive prompts - Wait, // Prompts the user to press any key before exiting - OpenLogs, // Opens the default logs directory after executing the command - Force, // Forces the execution of the workflow with non security related issues - OutputFile, - Correlation, - - DependencySource, // Index source to be queried against for finding dependencies - CustomHeader, // Optional Rest source header - AcceptSourceAgreements, // Accept all source agreements - - AuthenticationMode, // Authentication mode (silent, silentPreferred or interactive) - AuthenticationAccount, // Authentication account to be used - - // Network Behavior - Proxy, // Set a proxy to use in this execution - NoProxy, // Do not use the default proxy - - ToolVersion, - - // Used for demonstration purposes - ExperimentalArg, - - // This should always be at the end - Max - }; - - template), bool> = true> - bool Contains(T... arg) const - { - return (... && (m_parsedArgs.count(arg) != 0)); - } - - const std::vector* GetArgs(Type arg) const - { - auto itr = m_parsedArgs.find(arg); - return (itr == m_parsedArgs.end() ? nullptr : &(itr->second)); - } - - std::string_view GetArg(Type arg) const - { - auto itr = m_parsedArgs.find(arg); - - if (itr == m_parsedArgs.end()) - { - return {}; - } - - return itr->second[0]; - } - - size_t GetCount(Type arg) const - { - auto args = GetArgs(arg); - return (args ? args->size() : 0); - } - - bool AddArg(Type arg) - { - return m_parsedArgs[arg].empty(); - } - - void AddArg(Type arg, std::string value) - { - m_parsedArgs[arg].emplace_back(std::move(value)); - } - - void AddArg(Type arg, std::string_view value) - { - m_parsedArgs[arg].emplace_back(value); - } - - bool Empty() - { - return m_parsedArgs.empty(); - } - - size_t GetArgsCount() const - { - return m_parsedArgs.size(); - } - - std::vector GetTypes() const - { - std::vector types; - - for (auto const& i : m_parsedArgs) - { - types.emplace_back(i.first); - } - - return types; - } - - // If the user passes the same value multiple times inside a MultiQuery, operations will be repeated - // Since there currently is not a way to include search options within a MultiQuery, processing duplicates - // does not make sense within a single invocation - void MakeMultiQueryContainUniqueValues() - { - auto itr = m_parsedArgs.find(Type::MultiQuery); - - // If there is not a value in MultiQuery, or there is only one value, it is presumed to be unique - if (itr == m_parsedArgs.end() || itr->second.size() == 1) - { - return; - } - - std::set querySet; - std::vector& queryStrings = itr->second; - - queryStrings.erase(std::remove_if(queryStrings.begin(), queryStrings.end(), [&](const std::string value) { return !querySet.insert(value).second; }), queryStrings.end()); - } - - // If we get a single value for multi-query, we remove the argument and add it back as a single query. - // This way the rest of the code can assume that if there is a MultiQuery we will always have multiple values, - // and if there is a single one it will be in the Query type. - void MoveMultiQueryToSingleQueryIfNeeded() - { - auto itr = m_parsedArgs.find(Type::MultiQuery); - if (itr != m_parsedArgs.end() && itr->second.size() == 1) - { - // A test ensures that commands don't have both Query and MultiQuery arguments, - // so if we had a MultiQuery value, we can be sure there is no Query value - m_parsedArgs[Type::Query].emplace_back(std::move(itr->second[0])); - m_parsedArgs.erase(itr); - } - } - - private: - std::map> m_parsedArgs; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller::CLI::Execution +{ + struct Args + { + enum class Type : uint32_t + { + // Args to specify where to get app + Query, // Query to be performed against index + MultiQuery, // Like query, but can take multiple values + Manifest, // Provide the app manifest directly + + // Query filtering criteria and query behavior + Id, + Name, + Moniker, + Tag, + Command, + Source, // Index source to be queried against + Count, // Maximum query results + Exact, // Exact match required + + // Manifest selection behavior after an app is found + Version, + Channel, + + // Install behavior + Interactive, + Silent, + Locale, + Log, + CustomSwitches, // CustomSwitches args are args passed to the installer in addition to any defined in the manifest + Override, // Override args are (and the only args) directly passed to installer + InstallLocation, + InstallScope, + InstallArchitecture, + InstallerArchitecture, + InstallerType, + HashOverride, // Ignore hash mismatches + SkipDependencies, // Skip dependencies + DependenciesOnly, // Install only dependencies, not the target package + IgnoreLocalArchiveMalwareScan, // Ignore the local malware scan on archive files + AcceptPackageAgreements, // Accept all license agreements for packages + Rename, // Renames the file of the executable. Only applies to the portable installerType + NoUpgrade, // Install flow should not try to convert to upgrade flow upon finding existing installed version + AllowReboot, // Allows the reboot flow to proceed if applicable + + // Uninstall behavior + Purge, // Removes all files and directories related to a package during an uninstall. Only applies to the portable installerType. + Preserve, // Retains any files and directories created by the portable exe. + ProductCode, // Uninstalls using the product code as the identifier. + AllVersions, // Uninstall all versions of the package + TargetVersion, // The specific version to target + + //Source Command + SourceName, + SourceType, + SourceArg, + ForceSourceReset, + SourceExplicit, + SourceTrustLevel, + SourcePriority, + SourceEditExplicit, + + //Hash Command + HashFile, + Msix, // Flag to indicate the input file is msix + + //Validate Command + ValidateManifest, + IgnoreWarnings, + + // Complete Command + Word, + CommandLine, + Position, + + // Export Command + IncludeVersions, + + // Import Command + ImportFile, + IgnoreUnavailable, + IgnoreVersions, + + // Download Command + DownloadDirectory, + SkipMicrosoftStorePackageLicense, + Platform, + OSVersion, + + // Setting Command + AdminSettingEnable, + AdminSettingDisable, + SettingName, + SettingValue, + + // Upgrade command + All, // Used in Update command to update all installed packages to latest + IncludeUnknown, // Used in Upgrade command to allow upgrades of packages with unknown versions + IncludePinned, // Used in Upgrade command to allow upgrades to pinned packages (only for pinning type of pins) + UninstallPrevious, // Used in Upgrade command to override the default manifest behavior to UninstallPrevious + + // Show command + ListVersions, // Used in Show command to list all available versions of an app + + // List Command + Upgrade, // Used in List command to only show versions with upgrades + ListDetails, + Sort, // Sort output by field (repeatable: --sort name --sort id) + SortAscending, // Sort output in ascending order + SortDescending, // Sort output in descending order + + // Pin command + GatedVersion, // Differs from Version in that this supports wildcards + BlockingPin, + PinInstalled, + + // Error command + ErrorInput, + + // Resume Command + ResumeId, + IgnoreResumeLimit, + + // Font Command + Family, + Details, + + // Stub package (extended features) + ExtendedFeaturesEnable, + ExtendedFeaturesDisable, + + // Configuration + ConfigurationFile, + ConfigurationAcceptWarning, + ConfigurationSuppressPrologue, + ConfigurationProcessorPath, + ConfigurationModulePath, + ConfigurationExportPackageId, + ConfigurationExportModule, + ConfigurationExportResource, + ConfigurationExportAll, + ConfigurationHistoryItem, + ConfigurationHistoryRemove, + ConfigurationStatusWatch, + + // DSCv3 resources + DscResourceFunctionGet, + DscResourceFunctionSet, + DscResourceFunctionWhatIf, + DscResourceFunctionTest, + DscResourceFunctionDelete, + DscResourceFunctionExport, + DscResourceFunctionValidate, + DscResourceFunctionResolve, + DscResourceFunctionAdapter, + DscResourceFunctionSchema, + DscResourceFunctionManifest, + + // Common arguments + NoVT, // Disable VirtualTerminal outputs + RetroStyle, // Makes progress display as retro + RainbowStyle, // Makes progress display as a rainbow + NoProgress, // Disables progress bar and spinner + Help, // Show command usage + Info, // Show general info about WinGet + VerboseLogs, // Increases winget logging level to verbose + DisableInteractivity, // Disable interactive prompts + Wait, // Prompts the user to press any key before exiting + OpenLogs, // Opens the default logs directory after executing the command + Force, // Forces the execution of the workflow with non security related issues + OutputFile, + Correlation, + + DependencySource, // Index source to be queried against for finding dependencies + CustomHeader, // Optional Rest source header + AcceptSourceAgreements, // Accept all source agreements + + AuthenticationMode, // Authentication mode (silent, silentPreferred or interactive) + AuthenticationAccount, // Authentication account to be used + + // Network Behavior + Proxy, // Set a proxy to use in this execution + NoProxy, // Do not use the default proxy + + ToolVersion, + + // Used for demonstration purposes + ExperimentalArg, + + // This should always be at the end + Max + }; + + template), bool> = true> + bool Contains(T... arg) const + { + return (... && (m_parsedArgs.count(arg) != 0)); + } + + const std::vector* GetArgs(Type arg) const + { + auto itr = m_parsedArgs.find(arg); + return (itr == m_parsedArgs.end() ? nullptr : &(itr->second)); + } + + std::string_view GetArg(Type arg) const + { + auto itr = m_parsedArgs.find(arg); + + if (itr == m_parsedArgs.end()) + { + return {}; + } + + return itr->second[0]; + } + + size_t GetCount(Type arg) const + { + auto args = GetArgs(arg); + return (args ? args->size() : 0); + } + + bool AddArg(Type arg) + { + return m_parsedArgs[arg].empty(); + } + + void AddArg(Type arg, std::string value) + { + m_parsedArgs[arg].emplace_back(std::move(value)); + } + + void AddArg(Type arg, std::string_view value) + { + m_parsedArgs[arg].emplace_back(value); + } + + bool Empty() + { + return m_parsedArgs.empty(); + } + + size_t GetArgsCount() const + { + return m_parsedArgs.size(); + } + + std::vector GetTypes() const + { + std::vector types; + + for (auto const& i : m_parsedArgs) + { + types.emplace_back(i.first); + } + + return types; + } + + // If the user passes the same value multiple times inside a MultiQuery, operations will be repeated + // Since there currently is not a way to include search options within a MultiQuery, processing duplicates + // does not make sense within a single invocation + void MakeMultiQueryContainUniqueValues() + { + auto itr = m_parsedArgs.find(Type::MultiQuery); + + // If there is not a value in MultiQuery, or there is only one value, it is presumed to be unique + if (itr == m_parsedArgs.end() || itr->second.size() == 1) + { + return; + } + + std::set querySet; + std::vector& queryStrings = itr->second; + + queryStrings.erase(std::remove_if(queryStrings.begin(), queryStrings.end(), [&](const std::string value) { return !querySet.insert(value).second; }), queryStrings.end()); + } + + // If we get a single value for multi-query, we remove the argument and add it back as a single query. + // This way the rest of the code can assume that if there is a MultiQuery we will always have multiple values, + // and if there is a single one it will be in the Query type. + void MoveMultiQueryToSingleQueryIfNeeded() + { + auto itr = m_parsedArgs.find(Type::MultiQuery); + if (itr != m_parsedArgs.end() && itr->second.size() == 1) + { + // A test ensures that commands don't have both Query and MultiQuery arguments, + // so if we had a MultiQuery value, we can be sure there is no Query value + m_parsedArgs[Type::Query].emplace_back(std::move(itr->second[0])); + m_parsedArgs.erase(itr); + } + } + + private: + std::map> m_parsedArgs; + }; +} diff --git a/src/AppInstallerCLICore/ExecutionContext.cpp b/src/AppInstallerCLICore/ExecutionContext.cpp index 8e88ef39ff..1cf9726e69 100644 --- a/src/AppInstallerCLICore/ExecutionContext.cpp +++ b/src/AppInstallerCLICore/ExecutionContext.cpp @@ -1,288 +1,288 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerRuntime.h" -#include "Argument.h" -#include "COMContext.h" -#include "Command.h" -#include "ExecutionContext.h" -#include "Public/ShutdownMonitoring.h" -#include -#include -#include -#include - -using namespace AppInstaller::Checkpoints; - -namespace AppInstaller::CLI::Execution -{ - using namespace Settings; - - namespace - { - bool ShouldRemoveCheckpointDatabase(HRESULT hr) - { - switch (hr) - { - case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL: - case APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED: - case APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH: - return false; - default: - return true; - } - } - } - - Context::~Context() - { - if (Settings::ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Resume)) - { - if (m_checkpointManager && (!IsTerminated() || ShouldRemoveCheckpointDatabase(GetTerminationHR()))) - { - m_checkpointManager->CleanUpDatabase(); - AppInstaller::Reboot::UnregisterRestartForWER(); - } - } - - if (m_disableSignalTerminationHandlerOnExit) - { - EnableSignalTerminationHandler(false); - } - } - - Context Context::CreateEmptyContext() - { - AppInstaller::ThreadLocalStorage::WingetThreadGlobals threadGlobals; - return Context(Reporter, threadGlobals); - } - - std::unique_ptr Context::CreateSubContext() - { - auto clone = std::make_unique(Reporter, *m_threadGlobals); - clone->m_flags = m_flags; - clone->m_executingCommand = m_executingCommand; - // If the parent is hooked up to the CTRL signal, have the clone be as well - if (m_disableSignalTerminationHandlerOnExit) - { - clone->EnableSignalTerminationHandler(); - } - CopyArgsToSubContext(clone.get()); - CopyDataToSubContext(clone.get()); - return clone; - } - - void Context::CopyArgsToSubContext(Context* subContext) - { - auto argProperties = ArgumentCommon::GetFromExecArgs(Args); - for (const auto& arg : argProperties) - { - if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyFlagToSubContext)) - { - subContext->Args.AddArg(arg.Type); - } - else if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyValueToSubContext)) - { - subContext->Args.AddArg(arg.Type, Args.GetArg(arg.Type)); - } - } - } - - 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) - { - ShutdownMonitoring::TerminationSignalHandler::EnableListener(enabled, this); - m_disableSignalTerminationHandlerOnExit = enabled; - } - - void Context::UpdateForArgs() - { - // Change logging level to Info if Verbose not requested - if (Args.Contains(Args::Type::VerboseLogs)) - { - Logging::Log().SetLevel(Logging::Level::Verbose); - } - - // Disable warnings if requested - if (Args.Contains(Args::Type::IgnoreWarnings)) - { - Reporter.SetLevelMask(Reporter::Level::Warning, false); - } - - // Set proxy - if (Args.Contains(Args::Type::Proxy)) - { - Network().SetProxyUri(std::string{ Args.GetArg(Args::Type::Proxy) }); - } - else if (Args.Contains(Args::Type::NoProxy)) - { - Network().SetProxyUri(std::nullopt); - } - - // Set visual style - if (Args.Contains(Args::Type::NoProgress)) - { - Reporter.SetStyle(VisualStyle::Disabled); - } - else if (Args.Contains(Args::Type::NoVT)) - { - Reporter.SetStyle(VisualStyle::NoVT); - } - else if (Args.Contains(Args::Type::RetroStyle)) - { - Reporter.SetStyle(VisualStyle::Retro); - } - else if (Args.Contains(Args::Type::RainbowStyle)) - { - Reporter.SetStyle(VisualStyle::Rainbow); - } - else - { - Reporter.SetStyle(User().Get()); - } - } - - void Context::Terminate(HRESULT hr, std::string_view file, size_t line) - { - if (hr == APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED) - { - ++m_CtrlSignalCount; - // Use a more recognizable error - hr = E_ABORT; - - // If things aren't terminating fast enough for the user, they will probably press CTRL+C again. - // In that case, we should forcibly terminate. - // Unless we want to spin a separate thread for all work, we have to just exit here. - if (m_CtrlSignalCount >= 2) - { - Reporter.CloseOutputStream(true); - Logging::Telemetry().LogCommandTermination(hr, file, line); - std::exit(hr); - } - } - else if (hr == APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED) - { - AICLI_LOG(CLI, Info, << "Got app termination signal"); - hr = E_ABORT; - } - - Logging::Telemetry().LogCommandTermination(hr, file, line); - - if (!m_isTerminated) - { - SetTerminationHR(hr); - } - } - - void Context::SetTerminationHR(HRESULT hr) - { - m_terminationHR = hr; - m_isTerminated = true; - } - - void Context::Cancel(CancelReason reason, bool bypassUser) - { - HRESULT hr = ToHRESULT(reason); - - Terminate(hr); - Reporter.CancelInProgressTask(bypassUser, reason); - } - - void Context::SetExecutionStage(Workflow::ExecutionStage stage) - { - if (m_executionStage == stage) - { - return; - } - else if (m_executionStage > stage) - { - THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), "Reporting ExecutionStage to an earlier Stage: current[%d], new[%d]", ToIntegral(m_executionStage), ToIntegral(stage)); - } - - m_executionStage = stage; - GetThreadGlobals().GetTelemetryLogger().SetExecutionStage(static_cast(m_executionStage)); - } - - AppInstaller::ThreadLocalStorage::WingetThreadGlobals& Context::GetThreadGlobals() - { - return *m_threadGlobals; - } - - std::shared_ptr Context::GetSharedThreadGlobals() - { - return m_threadGlobals; - } - - std::unique_ptr Context::SetForCurrentThread() - { - return m_threadGlobals->SetForCurrentThread(); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - bool Context::ShouldExecuteWorkflowTask(const Workflow::WorkflowTask& task) - { - return (m_shouldExecuteWorkflowTask ? m_shouldExecuteWorkflowTask(task) : true); - } -#endif - - void ContextEnumBasedVariantMapActionCallback(const void* map, Data data, EnumBasedVariantMapAction action) - { - switch (action) - { - case EnumBasedVariantMapAction::Add: - AICLI_LOG(Workflow, Verbose, << "Setting data item: " << data); - break; - case EnumBasedVariantMapAction::Contains: - AICLI_LOG(Workflow, Verbose, << "Checking data item: " << data); - break; - case EnumBasedVariantMapAction::Get: - AICLI_LOG(Workflow, Verbose, << "Getting data item: " << data); - break; - } - - UNREFERENCED_PARAMETER(map); - } - - std::string Context::GetResumeId() - { - return m_checkpointManager->GetResumeId(); - } - - std::optional> Context::LoadCheckpoint(const std::string& resumeId) - { - m_checkpointManager = std::make_unique(resumeId); - return m_checkpointManager->GetAutomaticCheckpoint(); - } - - std::vector> Context::GetCheckpoints() - { - return m_checkpointManager->GetCheckpoints(); - } - - void Context::Checkpoint(std::string_view checkpointName, std::vector contextData) - { - UNREFERENCED_PARAMETER(checkpointName); - UNREFERENCED_PARAMETER(contextData); - - if (!m_checkpointManager) - { - m_checkpointManager = std::make_unique(); - m_checkpointManager->CreateAutomaticCheckpoint(*this); - - // Register for restart only when we first call checkpoint to support restarting from an unexpected shutdown. - AppInstaller::Reboot::RegisterRestartForWER("resume -g " + GetResumeId()); - } - - // TODO: Capture context data for checkpoint. - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerRuntime.h" +#include "Argument.h" +#include "COMContext.h" +#include "Command.h" +#include "ExecutionContext.h" +#include "Public/ShutdownMonitoring.h" +#include +#include +#include +#include + +using namespace AppInstaller::Checkpoints; + +namespace AppInstaller::CLI::Execution +{ + using namespace Settings; + + namespace + { + bool ShouldRemoveCheckpointDatabase(HRESULT hr) + { + switch (hr) + { + case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL: + case APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED: + case APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH: + return false; + default: + return true; + } + } + } + + Context::~Context() + { + if (Settings::ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::Resume)) + { + if (m_checkpointManager && (!IsTerminated() || ShouldRemoveCheckpointDatabase(GetTerminationHR()))) + { + m_checkpointManager->CleanUpDatabase(); + AppInstaller::Reboot::UnregisterRestartForWER(); + } + } + + if (m_disableSignalTerminationHandlerOnExit) + { + EnableSignalTerminationHandler(false); + } + } + + Context Context::CreateEmptyContext() + { + AppInstaller::ThreadLocalStorage::WingetThreadGlobals threadGlobals; + return Context(Reporter, threadGlobals); + } + + std::unique_ptr Context::CreateSubContext() + { + auto clone = std::make_unique(Reporter, *m_threadGlobals); + clone->m_flags = m_flags; + clone->m_executingCommand = m_executingCommand; + // If the parent is hooked up to the CTRL signal, have the clone be as well + if (m_disableSignalTerminationHandlerOnExit) + { + clone->EnableSignalTerminationHandler(); + } + CopyArgsToSubContext(clone.get()); + CopyDataToSubContext(clone.get()); + return clone; + } + + void Context::CopyArgsToSubContext(Context* subContext) + { + auto argProperties = ArgumentCommon::GetFromExecArgs(Args); + for (const auto& arg : argProperties) + { + if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyFlagToSubContext)) + { + subContext->Args.AddArg(arg.Type); + } + else if (WI_IsFlagSet(arg.TypeCategory, ArgTypeCategory::CopyValueToSubContext)) + { + subContext->Args.AddArg(arg.Type, Args.GetArg(arg.Type)); + } + } + } + + 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) + { + ShutdownMonitoring::TerminationSignalHandler::EnableListener(enabled, this); + m_disableSignalTerminationHandlerOnExit = enabled; + } + + void Context::UpdateForArgs() + { + // Change logging level to Info if Verbose not requested + if (Args.Contains(Args::Type::VerboseLogs)) + { + Logging::Log().SetLevel(Logging::Level::Verbose); + } + + // Disable warnings if requested + if (Args.Contains(Args::Type::IgnoreWarnings)) + { + Reporter.SetLevelMask(Reporter::Level::Warning, false); + } + + // Set proxy + if (Args.Contains(Args::Type::Proxy)) + { + Network().SetProxyUri(std::string{ Args.GetArg(Args::Type::Proxy) }); + } + else if (Args.Contains(Args::Type::NoProxy)) + { + Network().SetProxyUri(std::nullopt); + } + + // Set visual style + if (Args.Contains(Args::Type::NoProgress)) + { + Reporter.SetStyle(VisualStyle::Disabled); + } + else if (Args.Contains(Args::Type::NoVT)) + { + Reporter.SetStyle(VisualStyle::NoVT); + } + else if (Args.Contains(Args::Type::RetroStyle)) + { + Reporter.SetStyle(VisualStyle::Retro); + } + else if (Args.Contains(Args::Type::RainbowStyle)) + { + Reporter.SetStyle(VisualStyle::Rainbow); + } + else + { + Reporter.SetStyle(User().Get()); + } + } + + void Context::Terminate(HRESULT hr, std::string_view file, size_t line) + { + if (hr == APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED) + { + ++m_CtrlSignalCount; + // Use a more recognizable error + hr = E_ABORT; + + // If things aren't terminating fast enough for the user, they will probably press CTRL+C again. + // In that case, we should forcibly terminate. + // Unless we want to spin a separate thread for all work, we have to just exit here. + if (m_CtrlSignalCount >= 2) + { + Reporter.CloseOutputStream(true); + Logging::Telemetry().LogCommandTermination(hr, file, line); + std::exit(hr); + } + } + else if (hr == APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED) + { + AICLI_LOG(CLI, Info, << "Got app termination signal"); + hr = E_ABORT; + } + + Logging::Telemetry().LogCommandTermination(hr, file, line); + + if (!m_isTerminated) + { + SetTerminationHR(hr); + } + } + + void Context::SetTerminationHR(HRESULT hr) + { + m_terminationHR = hr; + m_isTerminated = true; + } + + void Context::Cancel(CancelReason reason, bool bypassUser) + { + HRESULT hr = ToHRESULT(reason); + + Terminate(hr); + Reporter.CancelInProgressTask(bypassUser, reason); + } + + void Context::SetExecutionStage(Workflow::ExecutionStage stage) + { + if (m_executionStage == stage) + { + return; + } + else if (m_executionStage > stage) + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), "Reporting ExecutionStage to an earlier Stage: current[%d], new[%d]", ToIntegral(m_executionStage), ToIntegral(stage)); + } + + m_executionStage = stage; + GetThreadGlobals().GetTelemetryLogger().SetExecutionStage(static_cast(m_executionStage)); + } + + AppInstaller::ThreadLocalStorage::WingetThreadGlobals& Context::GetThreadGlobals() + { + return *m_threadGlobals; + } + + std::shared_ptr Context::GetSharedThreadGlobals() + { + return m_threadGlobals; + } + + std::unique_ptr Context::SetForCurrentThread() + { + return m_threadGlobals->SetForCurrentThread(); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + bool Context::ShouldExecuteWorkflowTask(const Workflow::WorkflowTask& task) + { + return (m_shouldExecuteWorkflowTask ? m_shouldExecuteWorkflowTask(task) : true); + } +#endif + + void ContextEnumBasedVariantMapActionCallback(const void* map, Data data, EnumBasedVariantMapAction action) + { + switch (action) + { + case EnumBasedVariantMapAction::Add: + AICLI_LOG(Workflow, Verbose, << "Setting data item: " << data); + break; + case EnumBasedVariantMapAction::Contains: + AICLI_LOG(Workflow, Verbose, << "Checking data item: " << data); + break; + case EnumBasedVariantMapAction::Get: + AICLI_LOG(Workflow, Verbose, << "Getting data item: " << data); + break; + } + + UNREFERENCED_PARAMETER(map); + } + + std::string Context::GetResumeId() + { + return m_checkpointManager->GetResumeId(); + } + + std::optional> Context::LoadCheckpoint(const std::string& resumeId) + { + m_checkpointManager = std::make_unique(resumeId); + return m_checkpointManager->GetAutomaticCheckpoint(); + } + + std::vector> Context::GetCheckpoints() + { + return m_checkpointManager->GetCheckpoints(); + } + + void Context::Checkpoint(std::string_view checkpointName, std::vector contextData) + { + UNREFERENCED_PARAMETER(checkpointName); + UNREFERENCED_PARAMETER(contextData); + + if (!m_checkpointManager) + { + m_checkpointManager = std::make_unique(); + m_checkpointManager->CreateAutomaticCheckpoint(*this); + + // Register for restart only when we first call checkpoint to support restarting from an unexpected shutdown. + AppInstaller::Reboot::RegisterRestartForWER("resume -g " + GetResumeId()); + } + + // TODO: Capture context data for checkpoint. + } +} diff --git a/src/AppInstallerCLICore/ExecutionContext.h b/src/AppInstallerCLICore/ExecutionContext.h index 6ad8b8e217..a966cc2cd6 100644 --- a/src/AppInstallerCLICore/ExecutionContext.h +++ b/src/AppInstallerCLICore/ExecutionContext.h @@ -1,211 +1,211 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winget/ThreadGlobals.h" -#include "ExecutionReporter.h" -#include "ExecutionArgs.h" -#include "ExecutionContextData.h" -#include "CompletionData.h" -#include "CheckpointManager.h" -#include -#include - -#include - -#define WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) catch (const wil::ResultException& re) { exceptionHR = re.GetErrorCode(); } -#define WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) catch (const winrt::hresult_error& hre) { exceptionHR = hre.code(); } -#define WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::CLI::CommandException&) { exceptionHR = APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS; } -#define WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::Settings::GroupPolicyException&) { exceptionHR = APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY; } -#define WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) catch (const std::exception&) { exceptionHR = genericHR; } -#define WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR) catch (...) { exceptionHR = genericHR; } -#define WINGET_CATCH_STORE(exceptionHR, genericHR) \ - WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) \ - WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) \ - WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR) - -// Terminates the Context with some logging to indicate the location. -// Also returns from the current function. -#define AICLI_TERMINATE_CONTEXT_ARGS(_context_,_hr_,_ret_) \ - do { \ - _context_.Terminate(_hr_, __FILE__, __LINE__); \ - return _ret_; \ - } while(0,0) - -// Terminates the Context named 'context' with some logging to indicate the location. -// Also returns from the current function. -#define AICLI_TERMINATE_CONTEXT(_hr_) AICLI_TERMINATE_CONTEXT_ARGS(context,_hr_,) - -// Terminates the Context named 'context' with some logging to indicate the location. -// Also returns the specified value from the current function. -#define AICLI_TERMINATE_CONTEXT_RETURN(_hr_,_ret_) AICLI_TERMINATE_CONTEXT_ARGS(context,_hr_,_ret_) - -// Returns if the context is terminated. -#define AICLI_RETURN_IF_TERMINATED(_context_) if ((_context_).IsTerminated()) { return; } - -namespace AppInstaller::CLI -{ - struct Command; -} - -namespace AppInstaller::CLI::Workflow -{ - struct WorkflowTask; - enum class ExecutionStage : uint32_t; -} - -namespace AppInstaller::CLI::Execution -{ - // bit masks used as Context flags - enum class ContextFlag : int - { - None = 0x0, - InstallerExecutionUseUpdate = 0x1, - InstallerHashMatched = 0x2, - InstallerTrusted = 0x4, - // Allows a failure in a single source to generate a warning rather than an error. - // TODO: Remove when the source interface is refactored. - TreatSourceFailuresAsWarning = 0x8, - ShowSearchResultsOnPartialFailure = 0x10, - DisableInteractivity = 0x40, - BypassIsStoreClientBlockedPolicyCheck = 0x80, - InstallerDownloadOnly = 0x100, - Resume = 0x200, - RebootRequired = 0x400, - RegisterResume = 0x800, - InstallerExecutionUseRepair = 0x1000, - }; - - DEFINE_ENUM_FLAG_OPERATORS(ContextFlag); - - // Callback to log data actions. - void ContextEnumBasedVariantMapActionCallback(const void* map, Data data, EnumBasedVariantMapAction action); - - // The context within which all commands execute. - // Contains input/output via Execution::Reporter and - // arguments via Execution::Args. - struct Context : EnumBasedVariantMap, ICancellable - { - Context() = default; - Context(std::ostream& out, std::istream& in) : Reporter(out, in) {} - - // Constructor for creating a sub-context. - Context(Execution::Reporter& reporter, ThreadLocalStorage::WingetThreadGlobals& threadGlobals) : - Reporter(reporter, Execution::Reporter::clone_t{}), - m_threadGlobals(std::make_shared(threadGlobals, ThreadLocalStorage::WingetThreadGlobals::create_sub_thread_globals_t{})) {} - - virtual ~Context(); - - // The path for console input/output for all functionality. - Reporter Reporter; - - // The arguments given to execute with. - Args Args; - - // Creates a empty context, inheriting - Context CreateEmptyContext(); - - // Creates a child of this context. - virtual std::unique_ptr CreateSubContext(); - - // Enables reception of CTRL signals and window messages. - void EnableSignalTerminationHandler(bool enabled = true); - - // Applies changes based on the parsed args. - void UpdateForArgs(); - - // Returns a value indicating whether the context is terminated. - bool IsTerminated() const { return m_isTerminated; } - - // Resets the context to a nonterminated state. - void ResetTermination() { m_terminationHR = S_OK; m_isTerminated = false; } - - // Gets the HRESULT reason for the termination. - HRESULT GetTerminationHR() const { return m_terminationHR; } - - // Set the context to the terminated state. - void Terminate(HRESULT hr, std::string_view file = {}, size_t line = {}); - - // Set the termination hr of the context. - void SetTerminationHR(HRESULT hr); - - // Cancel the context; this terminates it as well as informing any in progress task to stop cooperatively. - // Multiple attempts with CancelReason::CancelSignal may cause the process to simply exit. - // The bypassUser indicates whether the user should be asked for cancellation (does not currently have any effect). - void Cancel(CancelReason reason, bool bypassUser = false) override; - - // Gets context flags - ContextFlag GetFlags() const - { - return m_flags; - } - - // Set context flags - void SetFlags(ContextFlag flags) - { - WI_SetAllFlags(m_flags, flags); - } - - // Clear context flags - void ClearFlags(ContextFlag flags) - { - WI_ClearAllFlags(m_flags, flags); - } - - virtual void SetExecutionStage(Workflow::ExecutionStage stage); - - // Get Globals for Current Context - ThreadLocalStorage::WingetThreadGlobals& GetThreadGlobals(); - std::shared_ptr GetSharedThreadGlobals(); - - std::unique_ptr SetForCurrentThread(); - - // Gets the executing command - AppInstaller::CLI::Command* GetExecutingCommand() { return m_executingCommand; } - - // Sets the executing command - void SetExecutingCommand(AppInstaller::CLI::Command* command) { m_executingCommand = command; } - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Enable tests to override behavior - bool ShouldExecuteWorkflowTask(const Workflow::WorkflowTask& task); -#endif - - // Returns the resume id. - std::string GetResumeId(); - - // Called by the resume command. Loads the checkpoint manager with the resume id and returns the automatic checkpoint. - std::optional> LoadCheckpoint(const std::string& resumeId); - - // Returns data checkpoints in the order of latest checkpoint to earliest. - std::vector> GetCheckpoints(); - - // Creates a checkpoint for the provided context data. - void Checkpoint(std::string_view checkpointName, std::vector contextData); - - protected: - // 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. - std::function m_shouldExecuteWorkflowTask; - - private: - DestructionToken m_disableSignalTerminationHandlerOnExit = false; - bool m_isTerminated = false; - HRESULT m_terminationHR = S_OK; - size_t m_CtrlSignalCount = 0; - ContextFlag m_flags = ContextFlag::None; - Workflow::ExecutionStage m_executionStage = Workflow::ExecutionStage::Initial; - std::shared_ptr m_threadGlobals = std::make_shared(); - AppInstaller::CLI::Command* m_executingCommand = nullptr; - std::unique_ptr m_checkpointManager; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winget/ThreadGlobals.h" +#include "ExecutionReporter.h" +#include "ExecutionArgs.h" +#include "ExecutionContextData.h" +#include "CompletionData.h" +#include "CheckpointManager.h" +#include +#include + +#include + +#define WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) catch (const wil::ResultException& re) { exceptionHR = re.GetErrorCode(); } +#define WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) catch (const winrt::hresult_error& hre) { exceptionHR = hre.code(); } +#define WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::CLI::CommandException&) { exceptionHR = APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS; } +#define WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) catch (const ::AppInstaller::Settings::GroupPolicyException&) { exceptionHR = APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY; } +#define WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) catch (const std::exception&) { exceptionHR = genericHR; } +#define WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR) catch (...) { exceptionHR = genericHR; } +#define WINGET_CATCH_STORE(exceptionHR, genericHR) \ + WINGET_CATCH_RESULT_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_HRESULT_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_COMMAND_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_POLICY_EXCEPTION_STORE(exceptionHR) \ + WINGET_CATCH_STD_EXCEPTION_STORE(exceptionHR, genericHR) \ + WINGET_CATCH_ALL_EXCEPTION_STORE(exceptionHR, genericHR) + +// Terminates the Context with some logging to indicate the location. +// Also returns from the current function. +#define AICLI_TERMINATE_CONTEXT_ARGS(_context_,_hr_,_ret_) \ + do { \ + _context_.Terminate(_hr_, __FILE__, __LINE__); \ + return _ret_; \ + } while(0,0) + +// Terminates the Context named 'context' with some logging to indicate the location. +// Also returns from the current function. +#define AICLI_TERMINATE_CONTEXT(_hr_) AICLI_TERMINATE_CONTEXT_ARGS(context,_hr_,) + +// Terminates the Context named 'context' with some logging to indicate the location. +// Also returns the specified value from the current function. +#define AICLI_TERMINATE_CONTEXT_RETURN(_hr_,_ret_) AICLI_TERMINATE_CONTEXT_ARGS(context,_hr_,_ret_) + +// Returns if the context is terminated. +#define AICLI_RETURN_IF_TERMINATED(_context_) if ((_context_).IsTerminated()) { return; } + +namespace AppInstaller::CLI +{ + struct Command; +} + +namespace AppInstaller::CLI::Workflow +{ + struct WorkflowTask; + enum class ExecutionStage : uint32_t; +} + +namespace AppInstaller::CLI::Execution +{ + // bit masks used as Context flags + enum class ContextFlag : int + { + None = 0x0, + InstallerExecutionUseUpdate = 0x1, + InstallerHashMatched = 0x2, + InstallerTrusted = 0x4, + // Allows a failure in a single source to generate a warning rather than an error. + // TODO: Remove when the source interface is refactored. + TreatSourceFailuresAsWarning = 0x8, + ShowSearchResultsOnPartialFailure = 0x10, + DisableInteractivity = 0x40, + BypassIsStoreClientBlockedPolicyCheck = 0x80, + InstallerDownloadOnly = 0x100, + Resume = 0x200, + RebootRequired = 0x400, + RegisterResume = 0x800, + InstallerExecutionUseRepair = 0x1000, + }; + + DEFINE_ENUM_FLAG_OPERATORS(ContextFlag); + + // Callback to log data actions. + void ContextEnumBasedVariantMapActionCallback(const void* map, Data data, EnumBasedVariantMapAction action); + + // The context within which all commands execute. + // Contains input/output via Execution::Reporter and + // arguments via Execution::Args. + struct Context : EnumBasedVariantMap, ICancellable + { + Context() = default; + Context(std::ostream& out, std::istream& in) : Reporter(out, in) {} + + // Constructor for creating a sub-context. + Context(Execution::Reporter& reporter, ThreadLocalStorage::WingetThreadGlobals& threadGlobals) : + Reporter(reporter, Execution::Reporter::clone_t{}), + m_threadGlobals(std::make_shared(threadGlobals, ThreadLocalStorage::WingetThreadGlobals::create_sub_thread_globals_t{})) {} + + virtual ~Context(); + + // The path for console input/output for all functionality. + Reporter Reporter; + + // The arguments given to execute with. + Args Args; + + // Creates a empty context, inheriting + Context CreateEmptyContext(); + + // Creates a child of this context. + virtual std::unique_ptr CreateSubContext(); + + // Enables reception of CTRL signals and window messages. + void EnableSignalTerminationHandler(bool enabled = true); + + // Applies changes based on the parsed args. + void UpdateForArgs(); + + // Returns a value indicating whether the context is terminated. + bool IsTerminated() const { return m_isTerminated; } + + // Resets the context to a nonterminated state. + void ResetTermination() { m_terminationHR = S_OK; m_isTerminated = false; } + + // Gets the HRESULT reason for the termination. + HRESULT GetTerminationHR() const { return m_terminationHR; } + + // Set the context to the terminated state. + void Terminate(HRESULT hr, std::string_view file = {}, size_t line = {}); + + // Set the termination hr of the context. + void SetTerminationHR(HRESULT hr); + + // Cancel the context; this terminates it as well as informing any in progress task to stop cooperatively. + // Multiple attempts with CancelReason::CancelSignal may cause the process to simply exit. + // The bypassUser indicates whether the user should be asked for cancellation (does not currently have any effect). + void Cancel(CancelReason reason, bool bypassUser = false) override; + + // Gets context flags + ContextFlag GetFlags() const + { + return m_flags; + } + + // Set context flags + void SetFlags(ContextFlag flags) + { + WI_SetAllFlags(m_flags, flags); + } + + // Clear context flags + void ClearFlags(ContextFlag flags) + { + WI_ClearAllFlags(m_flags, flags); + } + + virtual void SetExecutionStage(Workflow::ExecutionStage stage); + + // Get Globals for Current Context + ThreadLocalStorage::WingetThreadGlobals& GetThreadGlobals(); + std::shared_ptr GetSharedThreadGlobals(); + + std::unique_ptr SetForCurrentThread(); + + // Gets the executing command + AppInstaller::CLI::Command* GetExecutingCommand() { return m_executingCommand; } + + // Sets the executing command + void SetExecutingCommand(AppInstaller::CLI::Command* command) { m_executingCommand = command; } + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Enable tests to override behavior + bool ShouldExecuteWorkflowTask(const Workflow::WorkflowTask& task); +#endif + + // Returns the resume id. + std::string GetResumeId(); + + // Called by the resume command. Loads the checkpoint manager with the resume id and returns the automatic checkpoint. + std::optional> LoadCheckpoint(const std::string& resumeId); + + // Returns data checkpoints in the order of latest checkpoint to earliest. + std::vector> GetCheckpoints(); + + // Creates a checkpoint for the provided context data. + void Checkpoint(std::string_view checkpointName, std::vector contextData); + + protected: + // 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. + std::function m_shouldExecuteWorkflowTask; + + private: + DestructionToken m_disableSignalTerminationHandlerOnExit = false; + bool m_isTerminated = false; + HRESULT m_terminationHR = S_OK; + size_t m_CtrlSignalCount = 0; + ContextFlag m_flags = ContextFlag::None; + Workflow::ExecutionStage m_executionStage = Workflow::ExecutionStage::Initial; + std::shared_ptr m_threadGlobals = std::make_shared(); + AppInstaller::CLI::Command* m_executingCommand = nullptr; + std::unique_ptr m_checkpointManager; + }; +} diff --git a/src/AppInstallerCLICore/ExecutionContextData.h b/src/AppInstallerCLICore/ExecutionContextData.h index 1796ff5000..4609893dc2 100644 --- a/src/AppInstallerCLICore/ExecutionContextData.h +++ b/src/AppInstallerCLICore/ExecutionContextData.h @@ -1,303 +1,303 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include "CompletionData.h" -#include "PackageCollection.h" -#include "PortableInstaller.h" -#include "Workflows/WorkflowBase.h" -#include "ConfigurationContext.h" - -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::CLI::Execution -{ - // Names a piece of data stored in the context by a workflow step. - // Must start at 0 to enable direct access to variant in Context. - // Max must be last and unused. - enum class Data : size_t - { - Source, - SearchRequest, // Only set for multiple installs - SearchResult, - SourceList, - Package, - Manifest, - PackageVersion, - Installer, - DownloadHashInfo, - InstallerPath, - LogPath, - InstallerArgs, - OperationReturnCode, - CompletionData, - InstalledPackageVersion, - UninstallString, - PackageFamilyNames, - ProductCodes, - // On export: A collection of packages to be exported to a file - // On import: A collection of packages read from a file - PackageCollection, - // When installing multiple packages at once (upgrade all, import, install with multiple args, dependencies): - // A collection of sub-contexts, each of which handles the installation of a single package. - PackageSubContexts, - // On import: Sources for the imported packages - Sources, - ARPCorrelationData, - CorrelatedAppsAndFeaturesEntries, - Dependencies, - DependencySource, - AllowedArchitectures, - AllowUnknownScope, - PortableInstaller, - PinningData, - Pins, - ConfigurationContext, - DownloadDirectory, - ModifyPath, - RepairString, - MsixDigests, - InstallerDownloadAuthenticators, - Max - }; - - struct Context; - - namespace details - { - template - struct DataMapping - { - // value_t type specifies the type of this data - }; - - template <> - struct DataMapping - { - using value_t = Repository::Source; - }; - - template <> - struct DataMapping - { - using value_t = Repository::SearchRequest; - }; - - template <> - struct DataMapping - { - using value_t = Repository::SearchResult; - }; - - template <> - struct DataMapping - { - using value_t = std::vector; - }; - - template <> - struct DataMapping - { - using value_t = std::shared_ptr; - }; - - template <> - struct DataMapping - { - using value_t = Manifest::Manifest; - }; - - template <> - struct DataMapping - { - using value_t = std::shared_ptr; - }; - - template <> - struct DataMapping - { - using value_t = std::optional; - }; - - template <> - struct DataMapping - { - using value_t = std::pair, Utility::DownloadResult>; - }; - - template <> - struct DataMapping - { - using value_t = std::filesystem::path; - }; - - template <> - struct DataMapping - { - using value_t = std::filesystem::path; - }; - - template <> - struct DataMapping - { - using value_t = std::string; - }; - - template <> - struct DataMapping - { - using value_t = DWORD; - }; - - template <> - struct DataMapping - { - using value_t = CLI::CompletionData; - }; - - template <> - struct DataMapping - { - using value_t = std::shared_ptr; - }; - - template <> - struct DataMapping - { - using value_t = std::string; - }; - - template <> - struct DataMapping - { - using value_t = std::vector; - }; - - template <> - struct DataMapping - { - using value_t = std::vector; - }; - - template <> - struct DataMapping - { - using value_t = CLI::PackageCollection; - }; - - template <> - struct DataMapping - { - using value_t = std::vector>; - }; - - template <> - struct DataMapping - { - using value_t = std::vector; - }; - - template <> - struct DataMapping - { - using value_t = Repository::Correlation::ARPCorrelationData; - }; - - template <> - struct DataMapping - { - using value_t = std::vector; - }; - - template <> - struct DataMapping - { - using value_t = Manifest::DependencyList; - }; - - template <> - struct DataMapping - { - using value_t = Repository::Source; - }; - - template <> - struct DataMapping - { - using value_t = std::vector; - }; - - template <> - struct DataMapping - { - using value_t = bool; - }; - - template <> - struct DataMapping - { - using value_t = CLI::Portable::PortableInstaller; - }; - - template <> - struct DataMapping - { - using value_t = Pinning::PinningData; - }; - - template <> - struct DataMapping - { - using value_t = std::vector; - }; - - template <> - struct DataMapping - { - using value_t = ConfigurationContext; - }; - - template <> - struct DataMapping - { - using value_t = std::filesystem::path; - }; - - template<> - struct DataMapping - { - using value_t = std::string; - }; - - template<> - struct DataMapping - { - using value_t = std::string; - }; - - template<> - struct DataMapping - { - // 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>; - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include "CompletionData.h" +#include "PackageCollection.h" +#include "PortableInstaller.h" +#include "Workflows/WorkflowBase.h" +#include "ConfigurationContext.h" + +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::CLI::Execution +{ + // Names a piece of data stored in the context by a workflow step. + // Must start at 0 to enable direct access to variant in Context. + // Max must be last and unused. + enum class Data : size_t + { + Source, + SearchRequest, // Only set for multiple installs + SearchResult, + SourceList, + Package, + Manifest, + PackageVersion, + Installer, + DownloadHashInfo, + InstallerPath, + LogPath, + InstallerArgs, + OperationReturnCode, + CompletionData, + InstalledPackageVersion, + UninstallString, + PackageFamilyNames, + ProductCodes, + // On export: A collection of packages to be exported to a file + // On import: A collection of packages read from a file + PackageCollection, + // When installing multiple packages at once (upgrade all, import, install with multiple args, dependencies): + // A collection of sub-contexts, each of which handles the installation of a single package. + PackageSubContexts, + // On import: Sources for the imported packages + Sources, + ARPCorrelationData, + CorrelatedAppsAndFeaturesEntries, + Dependencies, + DependencySource, + AllowedArchitectures, + AllowUnknownScope, + PortableInstaller, + PinningData, + Pins, + ConfigurationContext, + DownloadDirectory, + ModifyPath, + RepairString, + MsixDigests, + InstallerDownloadAuthenticators, + Max + }; + + struct Context; + + namespace details + { + template + struct DataMapping + { + // value_t type specifies the type of this data + }; + + template <> + struct DataMapping + { + using value_t = Repository::Source; + }; + + template <> + struct DataMapping + { + using value_t = Repository::SearchRequest; + }; + + template <> + struct DataMapping + { + using value_t = Repository::SearchResult; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; + + template <> + struct DataMapping + { + using value_t = std::shared_ptr; + }; + + template <> + struct DataMapping + { + using value_t = Manifest::Manifest; + }; + + template <> + struct DataMapping + { + using value_t = std::shared_ptr; + }; + + template <> + struct DataMapping + { + using value_t = std::optional; + }; + + template <> + struct DataMapping + { + using value_t = std::pair, Utility::DownloadResult>; + }; + + template <> + struct DataMapping + { + using value_t = std::filesystem::path; + }; + + template <> + struct DataMapping + { + using value_t = std::filesystem::path; + }; + + template <> + struct DataMapping + { + using value_t = std::string; + }; + + template <> + struct DataMapping + { + using value_t = DWORD; + }; + + template <> + struct DataMapping + { + using value_t = CLI::CompletionData; + }; + + template <> + struct DataMapping + { + using value_t = std::shared_ptr; + }; + + template <> + struct DataMapping + { + using value_t = std::string; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; + + template <> + struct DataMapping + { + using value_t = CLI::PackageCollection; + }; + + template <> + struct DataMapping + { + using value_t = std::vector>; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; + + template <> + struct DataMapping + { + using value_t = Repository::Correlation::ARPCorrelationData; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; + + template <> + struct DataMapping + { + using value_t = Manifest::DependencyList; + }; + + template <> + struct DataMapping + { + using value_t = Repository::Source; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; + + template <> + struct DataMapping + { + using value_t = bool; + }; + + template <> + struct DataMapping + { + using value_t = CLI::Portable::PortableInstaller; + }; + + template <> + struct DataMapping + { + using value_t = Pinning::PinningData; + }; + + template <> + struct DataMapping + { + using value_t = std::vector; + }; + + template <> + struct DataMapping + { + using value_t = ConfigurationContext; + }; + + template <> + struct DataMapping + { + using value_t = std::filesystem::path; + }; + + template<> + struct DataMapping + { + using value_t = std::string; + }; + + template<> + struct DataMapping + { + using value_t = std::string; + }; + + template<> + struct DataMapping + { + // 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/ExecutionProgress.cpp b/src/AppInstallerCLICore/ExecutionProgress.cpp index 56d38ab190..2886e51b0e 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.cpp +++ b/src/AppInstallerCLICore/ExecutionProgress.cpp @@ -1,837 +1,837 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ExecutionProgress.h" -#include "VTSupport.h" -#include "AppInstallerRuntime.h" -#include "Sixel.h" - -using namespace AppInstaller::Settings; -using namespace AppInstaller::CLI::VirtualTerminal; -using namespace std::string_view_literals; - -namespace AppInstaller::CLI::Execution -{ - namespace - { - static constexpr size_t s_ProgressBarCellWidth = 30; - - struct BytesFormatData - { - uint64_t PowerOfTwo; - std::string_view Name; - }; - - BytesFormatData s_bytesFormatData[] = - { - // Multi-terabyte installers should be fairly rare for the foreseeable future... - { 40, "TB"sv }, - { 30, "GB"sv }, - { 20, "MB"sv }, - { 10, "KB"sv }, - { 0, "B"sv }, - }; - - const BytesFormatData& GetFormatForSize(uint64_t bytes) - { - for (const auto& format : s_bytesFormatData) - { - if (bytes > (1ull << format.PowerOfTwo)) - { - return format; - } - } - - // Just to make the compiler happy, return the last in the list if we get here. - return s_bytesFormatData[ARRAYSIZE(s_bytesFormatData) - 1]; - } - - void OutputBytes(BaseStream& out, uint64_t byteCount) - { - const BytesFormatData& bfd = GetFormatForSize(byteCount); - - uint64_t integralAmount = byteCount >> bfd.PowerOfTwo; - uint64_t remainder = byteCount & ((1ull << bfd.PowerOfTwo) - 1); - size_t remainderDigits = 0; - - if (integralAmount < 10) - { - remainder *= 100; - remainderDigits = 2; - } - else if (integralAmount < 100) - { - remainder *= 10; - remainderDigits = 1; - } - else if (integralAmount < 1000) - { - // Put an extra space to ensure a consistent 4 chars per numeric output - out << ' '; - } - - out << integralAmount; - - if (remainderDigits) - { - remainder = remainder >> bfd.PowerOfTwo; - out << '.' << std::setw(remainderDigits) << std::setfill('0') << remainder; - } - - out << ' ' << bfd.Name; - } - - void SetColor(BaseStream& out, const TextFormat::Color& color, bool foregroundOnly) - { - out << TextFormat::Foreground::Extended(color); - - if (!foregroundOnly) - { - constexpr uint8_t divisor = 3; - - auto reduced = color; - reduced.R /= divisor; - reduced.G /= divisor; - reduced.B /= divisor; - - out << TextFormat::Background::Extended(reduced); - } - } - - void SetRainbowColor(BaseStream& out, size_t i, size_t max, bool foregroundOnly) - { - TextFormat::Color rainbow[] = - { - { 0xff, 0x00, 0x00 }, - { 0xff, 0x77, 0x00 }, - { 0xff, 0xdd, 0x00 }, - { 0x00, 0xff, 0x00 }, - { 0x00, 0x00, 0xff }, - { 0x8a, 0x2b, 0xe2 }, - { 0xc7, 0x7d, 0xf3 }, - }; - - double target = (static_cast(i) / (max - 1)) * (ARRAYSIZE(rainbow) - 1); - size_t lower = static_cast(std::floor(target)); - const auto& lowerVal = rainbow[lower]; - TextFormat::Color result; - - if (lower == (ARRAYSIZE(rainbow) - 1)) - { - result = lowerVal; - } - else - { - double upperContribution = target - lower; - -#define AICLI_AVERAGE(v) static_cast(((lowerVal.v * (1.0 - upperContribution)) + (rainbow[lower + 1].v * upperContribution))) - result = { AICLI_AVERAGE(R), AICLI_AVERAGE(G), AICLI_AVERAGE(B) }; - } - - SetColor(out, result, foregroundOnly); - } - } - - // Shared functionality for progress visualizers. - struct ProgressVisualizerBase - { - ProgressVisualizerBase(BaseStream& stream, bool enableVT) : - m_out(stream), m_enableVT(enableVT) {} - - void SetMessage(std::string_view message) - { - std::atomic_store(&m_message, std::make_shared(message)); - } - - std::shared_ptr Message() - { - return std::atomic_load(&m_message); - } - - protected: - BaseStream& m_out; - - bool VT_Enabled() const { return m_enableVT; } - - void ClearLine() - { - if (VT_Enabled()) - { - m_out << TextModification::EraseLineEntirely << '\r'; - } - else - { - auto consoleWidth = GetConsoleWidth(); - if (consoleWidth.has_value()) - { - m_out << '\r' << std::string(*consoleWidth, ' ') << '\r'; - } - else - { - m_out << '\n'; - } - } - } - - private: - bool m_enableVT = false; - std::shared_ptr m_message; - }; - - // Shared functionality for progress visualizers. - struct CharacterProgressVisualizerBase : public ProgressVisualizerBase - { - CharacterProgressVisualizerBase(BaseStream& stream, bool enableVT, VisualStyle style) : - ProgressVisualizerBase(stream, enableVT && style != AppInstaller::Settings::VisualStyle::NoVT), m_style(style) {} - - protected: - Settings::VisualStyle m_style = AppInstaller::Settings::VisualStyle::Accent; - - // Applies the selected visual style. - void ApplyStyle(size_t i, size_t max, bool foregroundOnly) - { - if (!VT_Enabled()) - { - // Either no style set or VT disabled - return; - } - switch (m_style) - { - case VisualStyle::Retro: - m_out << TextFormat::Default; - break; - case VisualStyle::Accent: - SetColor(m_out, TextFormat::Color::GetAccentColor(), foregroundOnly); - break; - case VisualStyle::Rainbow: - SetRainbowColor(m_out, i, max, foregroundOnly); - break; - default: - LOG_HR(E_UNEXPECTED); - } - } - }; - - // Displays an indefinite spinner via a character. - struct CharacterIndefiniteSpinner : public CharacterProgressVisualizerBase, public IIndefiniteSpinner - { - CharacterIndefiniteSpinner(BaseStream& stream, bool enableVT, VisualStyle style) : - CharacterProgressVisualizerBase(stream, enableVT, style) {} - - void ShowSpinner() override - { - if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) - { - m_spinnerRunning = true; - m_spinnerJob = std::async(std::launch::async, &CharacterIndefiniteSpinner::ShowSpinnerInternal, this); - } - } - - void StopSpinner() override - { - if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) - { - m_canceled = true; - m_spinnerJob.get(); - } - } - - void SetMessage(std::string_view message) override - { - ProgressVisualizerBase::SetMessage(message); - } - - std::shared_ptr Message() override - { - return ProgressVisualizerBase::Message(); - } - - private: - std::atomic m_canceled = false; - std::atomic m_spinnerRunning = false; - std::future m_spinnerJob; - - void ShowSpinnerInternal() - { - char spinnerChars[] = { '-', '\\', '|', '/' }; - - // First wait for a small amount of time to enable a fast task to skip - // showing anything, or a progress task to skip straight to progress. - Sleep(100); - - if (!m_canceled) - { - if (VT_Enabled()) - { - // Additional VT-based progress reporting, for terminals that support it - m_out << Progress::Construct(Progress::State::Indeterminate); - } - - // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. - std::string_view indent = " "; - std::shared_ptr message = ProgressVisualizerBase::Message(); - size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0; - - for (size_t i = 0; !m_canceled; ++i) - { - constexpr size_t repetitionCount = 20; - ApplyStyle(i % repetitionCount, repetitionCount, true); - m_out << '\r' << indent << spinnerChars[i % ARRAYSIZE(spinnerChars)]; - m_out.RestoreDefault(); - - std::shared_ptr newMessage = ProgressVisualizerBase::Message(); - std::string eraser; - if (newMessage) - { - size_t newLength = Utility::UTF8ColumnWidth(*newMessage); - - if (newLength < messageLength) - { - eraser = std::string(messageLength - newLength, ' '); - } - - message = newMessage; - messageLength = newLength; - } - - m_out << ' ' << (message ? *message : std::string{}) << eraser << std::flush; - Sleep(250); - } - - ClearLine(); - - if (VT_Enabled()) - { - m_out << Progress::Construct(Progress::State::None); - } - } - - m_canceled = false; - m_spinnerRunning = false; - } - }; - - // Displays progress via character output. - class CharacterProgressBar : public CharacterProgressVisualizerBase, public IProgressBar - { - public: - CharacterProgressBar(BaseStream& stream, bool enableVT, VisualStyle style) : - CharacterProgressVisualizerBase(stream, enableVT, style) {} - - void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override - { - if (current < m_lastCurrent) - { - ClearLine(); - } - - // TODO: Progress bar does not currently use message - if (VT_Enabled()) - { - ShowProgressWithVT(current, maximum, type); - } - else - { - ShowProgressNoVT(current, maximum, type); - } - - m_lastCurrent = current; - m_isVisible = true; - } - - void EndProgress(bool hideProgressWhenDone) override - { - if (m_isVisible) - { - if (hideProgressWhenDone) - { - ClearLine(); - } - else - { - m_out << std::endl; - } - - if (VT_Enabled()) - { - // We always clear the VT-based progress bar, even if hideProgressWhenDone is false - // since it would be confusing for users if progress continues to be shown after winget exits - // (it is typically not automatically cleared by terminals on process exit) - m_out << Progress::Construct(Progress::State::None); - } - - m_isVisible = false; - } - } - - private: - std::atomic m_isVisible = false; - uint64_t m_lastCurrent = 0; - - void ShowProgressNoVT(uint64_t current, uint64_t maximum, ProgressType type) - { - m_out << "\r "; - - if (maximum) - { - const char* const blockOn = u8"\x2588"; - const char* const blockOff = u8"\x2592"; - constexpr size_t blockWidth = 30; - - double percentage = static_cast(current) / maximum; - size_t blocksOn = static_cast(std::floor(percentage * blockWidth)); - - for (size_t i = 0; i < blocksOn; ++i) - { - m_out << blockOn; - } - - for (size_t i = 0; i < blockWidth - blocksOn; ++i) - { - m_out << blockOff; - } - - m_out << " "; - - switch (type) - { - case AppInstaller::ProgressType::Bytes: - OutputBytes(m_out, current); - m_out << " / "; - OutputBytes(m_out, maximum); - break; - case AppInstaller::ProgressType::Percent: - default: - m_out << static_cast(percentage * 100) << '%'; - break; - } - } - else - { - switch (type) - { - case AppInstaller::ProgressType::Bytes: - OutputBytes(m_out, current); - break; - case AppInstaller::ProgressType::Percent: - m_out << current << '%'; - break; - default: - m_out << current << " unknowns"; - break; - } - } - } - - void ShowProgressWithVT(uint64_t current, uint64_t maximum, ProgressType type) - { - m_out << TextFormat::Default; - - m_out << "\r "; - - if (maximum) - { - const char* const blocks[] = - { - u8" ", // block off - u8"\x258F", // block 1/8 - u8"\x258E", // block 2/8 - u8"\x258D", // block 3/8 - u8"\x258C", // block 4/8 - u8"\x258B", // block 5/8 - u8"\x258A", // block 6/8 - u8"\x2589", // block 7/8 - u8"\x2588" // block on - }; - const char* const blockOn = blocks[8]; - const char* const blockOff = blocks[0]; - constexpr size_t blockWidth = s_ProgressBarCellWidth; - - double percentage = static_cast(current) / maximum; - size_t blocksOn = static_cast(std::floor(percentage * blockWidth)); - size_t partialBlockIndex = static_cast((percentage * blockWidth - blocksOn) * 8); - TextFormat::Color accent = TextFormat::Color::GetAccentColor(); - - for (size_t i = 0; i < blockWidth; ++i) - { - ApplyStyle(i, blockWidth, false); - - if (i < blocksOn) - { - m_out << blockOn; - } - else if (i == blocksOn) - { - m_out << blocks[partialBlockIndex]; - } - else - { - m_out << blockOff; - } - } - - m_out << TextFormat::Default; - - m_out << " "; - - switch (type) - { - case AppInstaller::ProgressType::Bytes: - OutputBytes(m_out, current); - m_out << " / "; - OutputBytes(m_out, maximum); - break; - case AppInstaller::ProgressType::Percent: - default: - m_out << static_cast(percentage * 100) << '%'; - break; - } - - // Additional VT-based progress reporting, for terminals that support it - m_out << Progress::Construct(Progress::State::Normal, static_cast(percentage * 100)); - } - else - { - switch (type) - { - case AppInstaller::ProgressType::Bytes: - OutputBytes(m_out, current); - break; - case AppInstaller::ProgressType::Percent: - m_out << current << '%'; - break; - default: - m_out << current << " unknowns"; - break; - } - } - } - }; - - // Displays an indefinite spinner via a sixel. - struct SixelIndefiniteSpinner : public ProgressVisualizerBase, public IIndefiniteSpinner - { - SixelIndefiniteSpinner(BaseStream& stream, bool enableVT) : - ProgressVisualizerBase(stream, enableVT) - { - Sixel::RenderControls& renderControls = m_compositor.Controls(); - renderControls.RenderSizeInCells(2, 1); - - // Create palette from full image - std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets); - THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty()); - - // This image matches the target pixel size. If changing the target size, choose the most appropriate image. - Sixel::ImageSource wingetIcon{ imageAssetsRoot / "AppList.targetsize-20.png" }; - wingetIcon.Resize(renderControls); - Sixel::Palette palette = wingetIcon.CreatePalette(renderControls); - - m_folder = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/folders_only.png" }; - m_arrow = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/arrow_only.png" }; - - m_folder.Resize(renderControls); - m_folder.ApplyPalette(palette); - - Sixel::RenderControls arrowControls = renderControls; - arrowControls.InterpolationMode = Sixel::InterpolationMode::Linear; - m_arrow.Resize(arrowControls); - m_arrow.ApplyPalette(palette); - - m_compositor.Palette(std::move(palette)); - m_compositor.AddView(m_arrow.Copy()); - m_compositor.AddView(m_folder.Copy()); - } - - void ShowSpinner() override - { - if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) - { - m_spinnerRunning = true; - m_spinnerJob = std::async(std::launch::async, &SixelIndefiniteSpinner::ShowSpinnerInternal, this); - } - } - - void StopSpinner() override - { - if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) - { - m_canceled = true; - m_spinnerJob.get(); - } - } - - void SetMessage(std::string_view message) override - { - ProgressVisualizerBase::SetMessage(message); - } - - std::shared_ptr Message() override - { - return ProgressVisualizerBase::Message(); - } - - private: - std::atomic m_canceled = false; - std::atomic m_spinnerRunning = false; - std::future m_spinnerJob; - Sixel::ImageSource m_folder; - Sixel::ImageSource m_arrow; - Sixel::Compositor m_compositor; - - void ShowSpinnerInternal() - { - // First wait for a small amount of time to enable a fast task to skip - // showing anything, or a progress task to skip straight to progress. - Sleep(100); - - if (!m_canceled) - { - // Additional VT-based progress reporting, for terminals that support it - m_out << Progress::Construct(Progress::State::Indeterminate); - - // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. - std::string_view indent = " "; - std::shared_ptr message = ProgressVisualizerBase::Message(); - size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0; - - UINT imageHeight = m_compositor.Controls().PixelHeight; - - for (size_t i = 0; !m_canceled; ++i) - { - m_out << '\r' << indent; - - // Move arrow down one pixel each time - m_compositor[0].Translate(0, i % imageHeight, true); - m_compositor.RenderTo(m_out); - - message = ProgressVisualizerBase::Message(); - size_t newLength = (message ? Utility::UTF8ColumnWidth(*message) : 0); - - std::string eraser; - if (newLength < messageLength) - { - eraser = std::string(messageLength - newLength, ' '); - } - - messageLength = newLength; - - m_out << VirtualTerminal::Cursor::Position::Forward(3) << (message ? *message : std::string{}) << eraser << std::flush; - Sleep(100); - } - - ClearLine(); - - m_out << Progress::Construct(Progress::State::None); - } - - m_canceled = false; - m_spinnerRunning = false; - } - }; - - // Displays progress with a sixel image. - class SixelProgressBar : public ProgressVisualizerBase, public IProgressBar - { - public: - SixelProgressBar(BaseStream& stream, bool enableVT) : - ProgressVisualizerBase(stream, enableVT) - { - static constexpr UINT s_colorsForBelt = 20; - - Sixel::RenderControls imageRenderControls; - imageRenderControls.RenderSizeInCells(2, 1); - - // This image matches the target pixel size. If changing the target size, choose the most appropriate image. - std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets); - THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty()); - - m_icon = Sixel::ImageSource{ imageAssetsRoot / "AppList.targetsize-20.png" }; - m_icon.Resize(imageRenderControls); - imageRenderControls.ColorCount = Sixel::Palette::MaximumColorCount - s_colorsForBelt; - Sixel::Palette iconPalette = m_icon.CreatePalette(imageRenderControls); - - // TODO: Move to real location - m_belt = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/conveyor.png" }; - m_belt.Resize(imageRenderControls); - imageRenderControls.ColorCount = s_colorsForBelt; - imageRenderControls.InterpolationMode = Sixel::InterpolationMode::Linear; - Sixel::Palette beltPalette = m_belt.CreatePalette(imageRenderControls); - - Sixel::Palette combinedPalette{ iconPalette, beltPalette }; - - m_icon.ApplyPalette(combinedPalette); - m_belt.ApplyPalette(combinedPalette); - - m_compositor.Palette(std::move(combinedPalette)); - m_compositor.AddView(m_icon.Copy()); - m_compositor.AddView(m_belt.Copy()); - m_compositor.Controls().TransparencyEnabled = false; - m_compositor.Controls().RenderSizeInCells(s_ProgressBarCellWidth, 1); - } - - void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override - { - if (current < m_lastCurrent) - { - ClearLine(); - } - - m_out << TextFormat::Default; - - m_out << "\r "; - - if (maximum) - { - - double percentage = static_cast(current) / maximum; - - // Translate icon so that its leading edge is the progress line - INT translation = static_cast((percentage * m_compositor.Controls().PixelWidth) - m_compositor[0].Width()); - - m_compositor[0].Translate(translation, 0, false); - m_compositor[1].Translate(translation, 0, true); - m_compositor.RenderTo(m_out); - - m_out << VirtualTerminal::Cursor::Position::Forward(s_ProgressBarCellWidth + 2); - - switch (type) - { - case AppInstaller::ProgressType::Bytes: - OutputBytes(m_out, current); - m_out << " / "; - OutputBytes(m_out, maximum); - break; - case AppInstaller::ProgressType::Percent: - default: - m_out << static_cast(percentage * 100) << '%'; - break; - } - - // Additional VT-based progress reporting, for terminals that support it - m_out << Progress::Construct(Progress::State::Normal, static_cast(percentage * 100)); - } - else - { - switch (type) - { - case AppInstaller::ProgressType::Bytes: - OutputBytes(m_out, current); - break; - case AppInstaller::ProgressType::Percent: - m_out << current << '%'; - break; - default: - m_out << current << " unknowns"; - break; - } - } - - m_lastCurrent = current; - m_isVisible = true; - } - - void EndProgress(bool hideProgressWhenDone) override - { - if (m_isVisible) - { - if (hideProgressWhenDone) - { - ClearLine(); - } - else - { - m_out << std::endl; - } - - if (VT_Enabled()) - { - // We always clear the VT-based progress bar, even if hideProgressWhenDone is false - // since it would be confusing for users if progress continues to be shown after winget exits - // (it is typically not automatically cleared by terminals on process exit) - m_out << Progress::Construct(Progress::State::None); - } - - m_isVisible = false; - } - } - - private: - std::atomic m_isVisible = false; - uint64_t m_lastCurrent = 0; - Sixel::ImageSource m_icon; - Sixel::ImageSource m_belt; - Sixel::Compositor m_compositor; - }; - - std::unique_ptr IIndefiniteSpinner::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function& sixelSupported) - { - std::unique_ptr result; - - switch (style) - { - case VisualStyle::NoVT: - case VisualStyle::Retro: - case VisualStyle::Accent: - case VisualStyle::Rainbow: - result = std::make_unique(stream, enableVT, style); - break; - case VisualStyle::Sixel: - if (sixelSupported()) - { - try - { - result = std::make_unique(stream, enableVT); - } - CATCH_LOG(); - } - - if (!result) - { - result = std::make_unique(stream, enableVT, VisualStyle::Accent); - } - break; - case VisualStyle::Disabled: - break; - default: - THROW_HR(E_NOTIMPL); - } - - return result; - } - - std::unique_ptr IProgressBar::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function& sixelSupported) - { - std::unique_ptr result; - - switch (style) - { - case VisualStyle::NoVT: - case VisualStyle::Retro: - case VisualStyle::Accent: - case VisualStyle::Rainbow: - result = std::make_unique(stream, enableVT, style); - break; - case VisualStyle::Sixel: - if (sixelSupported()) - { - try - { - result = std::make_unique(stream, enableVT); - } - CATCH_LOG(); - } - - if (!result) - { - result = std::make_unique(stream, enableVT, VisualStyle::Accent); - } - break; - case VisualStyle::Disabled: - break; - default: - THROW_HR(E_NOTIMPL); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ExecutionProgress.h" +#include "VTSupport.h" +#include "AppInstallerRuntime.h" +#include "Sixel.h" + +using namespace AppInstaller::Settings; +using namespace AppInstaller::CLI::VirtualTerminal; +using namespace std::string_view_literals; + +namespace AppInstaller::CLI::Execution +{ + namespace + { + static constexpr size_t s_ProgressBarCellWidth = 30; + + struct BytesFormatData + { + uint64_t PowerOfTwo; + std::string_view Name; + }; + + BytesFormatData s_bytesFormatData[] = + { + // Multi-terabyte installers should be fairly rare for the foreseeable future... + { 40, "TB"sv }, + { 30, "GB"sv }, + { 20, "MB"sv }, + { 10, "KB"sv }, + { 0, "B"sv }, + }; + + const BytesFormatData& GetFormatForSize(uint64_t bytes) + { + for (const auto& format : s_bytesFormatData) + { + if (bytes > (1ull << format.PowerOfTwo)) + { + return format; + } + } + + // Just to make the compiler happy, return the last in the list if we get here. + return s_bytesFormatData[ARRAYSIZE(s_bytesFormatData) - 1]; + } + + void OutputBytes(BaseStream& out, uint64_t byteCount) + { + const BytesFormatData& bfd = GetFormatForSize(byteCount); + + uint64_t integralAmount = byteCount >> bfd.PowerOfTwo; + uint64_t remainder = byteCount & ((1ull << bfd.PowerOfTwo) - 1); + size_t remainderDigits = 0; + + if (integralAmount < 10) + { + remainder *= 100; + remainderDigits = 2; + } + else if (integralAmount < 100) + { + remainder *= 10; + remainderDigits = 1; + } + else if (integralAmount < 1000) + { + // Put an extra space to ensure a consistent 4 chars per numeric output + out << ' '; + } + + out << integralAmount; + + if (remainderDigits) + { + remainder = remainder >> bfd.PowerOfTwo; + out << '.' << std::setw(remainderDigits) << std::setfill('0') << remainder; + } + + out << ' ' << bfd.Name; + } + + void SetColor(BaseStream& out, const TextFormat::Color& color, bool foregroundOnly) + { + out << TextFormat::Foreground::Extended(color); + + if (!foregroundOnly) + { + constexpr uint8_t divisor = 3; + + auto reduced = color; + reduced.R /= divisor; + reduced.G /= divisor; + reduced.B /= divisor; + + out << TextFormat::Background::Extended(reduced); + } + } + + void SetRainbowColor(BaseStream& out, size_t i, size_t max, bool foregroundOnly) + { + TextFormat::Color rainbow[] = + { + { 0xff, 0x00, 0x00 }, + { 0xff, 0x77, 0x00 }, + { 0xff, 0xdd, 0x00 }, + { 0x00, 0xff, 0x00 }, + { 0x00, 0x00, 0xff }, + { 0x8a, 0x2b, 0xe2 }, + { 0xc7, 0x7d, 0xf3 }, + }; + + double target = (static_cast(i) / (max - 1)) * (ARRAYSIZE(rainbow) - 1); + size_t lower = static_cast(std::floor(target)); + const auto& lowerVal = rainbow[lower]; + TextFormat::Color result; + + if (lower == (ARRAYSIZE(rainbow) - 1)) + { + result = lowerVal; + } + else + { + double upperContribution = target - lower; + +#define AICLI_AVERAGE(v) static_cast(((lowerVal.v * (1.0 - upperContribution)) + (rainbow[lower + 1].v * upperContribution))) + result = { AICLI_AVERAGE(R), AICLI_AVERAGE(G), AICLI_AVERAGE(B) }; + } + + SetColor(out, result, foregroundOnly); + } + } + + // Shared functionality for progress visualizers. + struct ProgressVisualizerBase + { + ProgressVisualizerBase(BaseStream& stream, bool enableVT) : + m_out(stream), m_enableVT(enableVT) {} + + void SetMessage(std::string_view message) + { + std::atomic_store(&m_message, std::make_shared(message)); + } + + std::shared_ptr Message() + { + return std::atomic_load(&m_message); + } + + protected: + BaseStream& m_out; + + bool VT_Enabled() const { return m_enableVT; } + + void ClearLine() + { + if (VT_Enabled()) + { + m_out << TextModification::EraseLineEntirely << '\r'; + } + else + { + auto consoleWidth = GetConsoleWidth(); + if (consoleWidth.has_value()) + { + m_out << '\r' << std::string(*consoleWidth, ' ') << '\r'; + } + else + { + m_out << '\n'; + } + } + } + + private: + bool m_enableVT = false; + std::shared_ptr m_message; + }; + + // Shared functionality for progress visualizers. + struct CharacterProgressVisualizerBase : public ProgressVisualizerBase + { + CharacterProgressVisualizerBase(BaseStream& stream, bool enableVT, VisualStyle style) : + ProgressVisualizerBase(stream, enableVT && style != AppInstaller::Settings::VisualStyle::NoVT), m_style(style) {} + + protected: + Settings::VisualStyle m_style = AppInstaller::Settings::VisualStyle::Accent; + + // Applies the selected visual style. + void ApplyStyle(size_t i, size_t max, bool foregroundOnly) + { + if (!VT_Enabled()) + { + // Either no style set or VT disabled + return; + } + switch (m_style) + { + case VisualStyle::Retro: + m_out << TextFormat::Default; + break; + case VisualStyle::Accent: + SetColor(m_out, TextFormat::Color::GetAccentColor(), foregroundOnly); + break; + case VisualStyle::Rainbow: + SetRainbowColor(m_out, i, max, foregroundOnly); + break; + default: + LOG_HR(E_UNEXPECTED); + } + } + }; + + // Displays an indefinite spinner via a character. + struct CharacterIndefiniteSpinner : public CharacterProgressVisualizerBase, public IIndefiniteSpinner + { + CharacterIndefiniteSpinner(BaseStream& stream, bool enableVT, VisualStyle style) : + CharacterProgressVisualizerBase(stream, enableVT, style) {} + + void ShowSpinner() override + { + if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) + { + m_spinnerRunning = true; + m_spinnerJob = std::async(std::launch::async, &CharacterIndefiniteSpinner::ShowSpinnerInternal, this); + } + } + + void StopSpinner() override + { + if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) + { + m_canceled = true; + m_spinnerJob.get(); + } + } + + void SetMessage(std::string_view message) override + { + ProgressVisualizerBase::SetMessage(message); + } + + std::shared_ptr Message() override + { + return ProgressVisualizerBase::Message(); + } + + private: + std::atomic m_canceled = false; + std::atomic m_spinnerRunning = false; + std::future m_spinnerJob; + + void ShowSpinnerInternal() + { + char spinnerChars[] = { '-', '\\', '|', '/' }; + + // First wait for a small amount of time to enable a fast task to skip + // showing anything, or a progress task to skip straight to progress. + Sleep(100); + + if (!m_canceled) + { + if (VT_Enabled()) + { + // Additional VT-based progress reporting, for terminals that support it + m_out << Progress::Construct(Progress::State::Indeterminate); + } + + // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. + std::string_view indent = " "; + std::shared_ptr message = ProgressVisualizerBase::Message(); + size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0; + + for (size_t i = 0; !m_canceled; ++i) + { + constexpr size_t repetitionCount = 20; + ApplyStyle(i % repetitionCount, repetitionCount, true); + m_out << '\r' << indent << spinnerChars[i % ARRAYSIZE(spinnerChars)]; + m_out.RestoreDefault(); + + std::shared_ptr newMessage = ProgressVisualizerBase::Message(); + std::string eraser; + if (newMessage) + { + size_t newLength = Utility::UTF8ColumnWidth(*newMessage); + + if (newLength < messageLength) + { + eraser = std::string(messageLength - newLength, ' '); + } + + message = newMessage; + messageLength = newLength; + } + + m_out << ' ' << (message ? *message : std::string{}) << eraser << std::flush; + Sleep(250); + } + + ClearLine(); + + if (VT_Enabled()) + { + m_out << Progress::Construct(Progress::State::None); + } + } + + m_canceled = false; + m_spinnerRunning = false; + } + }; + + // Displays progress via character output. + class CharacterProgressBar : public CharacterProgressVisualizerBase, public IProgressBar + { + public: + CharacterProgressBar(BaseStream& stream, bool enableVT, VisualStyle style) : + CharacterProgressVisualizerBase(stream, enableVT, style) {} + + void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override + { + if (current < m_lastCurrent) + { + ClearLine(); + } + + // TODO: Progress bar does not currently use message + if (VT_Enabled()) + { + ShowProgressWithVT(current, maximum, type); + } + else + { + ShowProgressNoVT(current, maximum, type); + } + + m_lastCurrent = current; + m_isVisible = true; + } + + void EndProgress(bool hideProgressWhenDone) override + { + if (m_isVisible) + { + if (hideProgressWhenDone) + { + ClearLine(); + } + else + { + m_out << std::endl; + } + + if (VT_Enabled()) + { + // We always clear the VT-based progress bar, even if hideProgressWhenDone is false + // since it would be confusing for users if progress continues to be shown after winget exits + // (it is typically not automatically cleared by terminals on process exit) + m_out << Progress::Construct(Progress::State::None); + } + + m_isVisible = false; + } + } + + private: + std::atomic m_isVisible = false; + uint64_t m_lastCurrent = 0; + + void ShowProgressNoVT(uint64_t current, uint64_t maximum, ProgressType type) + { + m_out << "\r "; + + if (maximum) + { + const char* const blockOn = u8"\x2588"; + const char* const blockOff = u8"\x2592"; + constexpr size_t blockWidth = 30; + + double percentage = static_cast(current) / maximum; + size_t blocksOn = static_cast(std::floor(percentage * blockWidth)); + + for (size_t i = 0; i < blocksOn; ++i) + { + m_out << blockOn; + } + + for (size_t i = 0; i < blockWidth - blocksOn; ++i) + { + m_out << blockOff; + } + + m_out << " "; + + switch (type) + { + case AppInstaller::ProgressType::Bytes: + OutputBytes(m_out, current); + m_out << " / "; + OutputBytes(m_out, maximum); + break; + case AppInstaller::ProgressType::Percent: + default: + m_out << static_cast(percentage * 100) << '%'; + break; + } + } + else + { + switch (type) + { + case AppInstaller::ProgressType::Bytes: + OutputBytes(m_out, current); + break; + case AppInstaller::ProgressType::Percent: + m_out << current << '%'; + break; + default: + m_out << current << " unknowns"; + break; + } + } + } + + void ShowProgressWithVT(uint64_t current, uint64_t maximum, ProgressType type) + { + m_out << TextFormat::Default; + + m_out << "\r "; + + if (maximum) + { + const char* const blocks[] = + { + u8" ", // block off + u8"\x258F", // block 1/8 + u8"\x258E", // block 2/8 + u8"\x258D", // block 3/8 + u8"\x258C", // block 4/8 + u8"\x258B", // block 5/8 + u8"\x258A", // block 6/8 + u8"\x2589", // block 7/8 + u8"\x2588" // block on + }; + const char* const blockOn = blocks[8]; + const char* const blockOff = blocks[0]; + constexpr size_t blockWidth = s_ProgressBarCellWidth; + + double percentage = static_cast(current) / maximum; + size_t blocksOn = static_cast(std::floor(percentage * blockWidth)); + size_t partialBlockIndex = static_cast((percentage * blockWidth - blocksOn) * 8); + TextFormat::Color accent = TextFormat::Color::GetAccentColor(); + + for (size_t i = 0; i < blockWidth; ++i) + { + ApplyStyle(i, blockWidth, false); + + if (i < blocksOn) + { + m_out << blockOn; + } + else if (i == blocksOn) + { + m_out << blocks[partialBlockIndex]; + } + else + { + m_out << blockOff; + } + } + + m_out << TextFormat::Default; + + m_out << " "; + + switch (type) + { + case AppInstaller::ProgressType::Bytes: + OutputBytes(m_out, current); + m_out << " / "; + OutputBytes(m_out, maximum); + break; + case AppInstaller::ProgressType::Percent: + default: + m_out << static_cast(percentage * 100) << '%'; + break; + } + + // Additional VT-based progress reporting, for terminals that support it + m_out << Progress::Construct(Progress::State::Normal, static_cast(percentage * 100)); + } + else + { + switch (type) + { + case AppInstaller::ProgressType::Bytes: + OutputBytes(m_out, current); + break; + case AppInstaller::ProgressType::Percent: + m_out << current << '%'; + break; + default: + m_out << current << " unknowns"; + break; + } + } + } + }; + + // Displays an indefinite spinner via a sixel. + struct SixelIndefiniteSpinner : public ProgressVisualizerBase, public IIndefiniteSpinner + { + SixelIndefiniteSpinner(BaseStream& stream, bool enableVT) : + ProgressVisualizerBase(stream, enableVT) + { + Sixel::RenderControls& renderControls = m_compositor.Controls(); + renderControls.RenderSizeInCells(2, 1); + + // Create palette from full image + std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets); + THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty()); + + // This image matches the target pixel size. If changing the target size, choose the most appropriate image. + Sixel::ImageSource wingetIcon{ imageAssetsRoot / "AppList.targetsize-20.png" }; + wingetIcon.Resize(renderControls); + Sixel::Palette palette = wingetIcon.CreatePalette(renderControls); + + m_folder = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/folders_only.png" }; + m_arrow = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/arrow_only.png" }; + + m_folder.Resize(renderControls); + m_folder.ApplyPalette(palette); + + Sixel::RenderControls arrowControls = renderControls; + arrowControls.InterpolationMode = Sixel::InterpolationMode::Linear; + m_arrow.Resize(arrowControls); + m_arrow.ApplyPalette(palette); + + m_compositor.Palette(std::move(palette)); + m_compositor.AddView(m_arrow.Copy()); + m_compositor.AddView(m_folder.Copy()); + } + + void ShowSpinner() override + { + if (!m_spinnerJob.valid() && !m_spinnerRunning && !m_canceled) + { + m_spinnerRunning = true; + m_spinnerJob = std::async(std::launch::async, &SixelIndefiniteSpinner::ShowSpinnerInternal, this); + } + } + + void StopSpinner() override + { + if (!m_canceled && m_spinnerJob.valid() && m_spinnerRunning) + { + m_canceled = true; + m_spinnerJob.get(); + } + } + + void SetMessage(std::string_view message) override + { + ProgressVisualizerBase::SetMessage(message); + } + + std::shared_ptr Message() override + { + return ProgressVisualizerBase::Message(); + } + + private: + std::atomic m_canceled = false; + std::atomic m_spinnerRunning = false; + std::future m_spinnerJob; + Sixel::ImageSource m_folder; + Sixel::ImageSource m_arrow; + Sixel::Compositor m_compositor; + + void ShowSpinnerInternal() + { + // First wait for a small amount of time to enable a fast task to skip + // showing anything, or a progress task to skip straight to progress. + Sleep(100); + + if (!m_canceled) + { + // Additional VT-based progress reporting, for terminals that support it + m_out << Progress::Construct(Progress::State::Indeterminate); + + // Indent two spaces for the spinner, but three here so that we can overwrite it in the loop. + std::string_view indent = " "; + std::shared_ptr message = ProgressVisualizerBase::Message(); + size_t messageLength = message ? Utility::UTF8ColumnWidth(*message) : 0; + + UINT imageHeight = m_compositor.Controls().PixelHeight; + + for (size_t i = 0; !m_canceled; ++i) + { + m_out << '\r' << indent; + + // Move arrow down one pixel each time + m_compositor[0].Translate(0, i % imageHeight, true); + m_compositor.RenderTo(m_out); + + message = ProgressVisualizerBase::Message(); + size_t newLength = (message ? Utility::UTF8ColumnWidth(*message) : 0); + + std::string eraser; + if (newLength < messageLength) + { + eraser = std::string(messageLength - newLength, ' '); + } + + messageLength = newLength; + + m_out << VirtualTerminal::Cursor::Position::Forward(3) << (message ? *message : std::string{}) << eraser << std::flush; + Sleep(100); + } + + ClearLine(); + + m_out << Progress::Construct(Progress::State::None); + } + + m_canceled = false; + m_spinnerRunning = false; + } + }; + + // Displays progress with a sixel image. + class SixelProgressBar : public ProgressVisualizerBase, public IProgressBar + { + public: + SixelProgressBar(BaseStream& stream, bool enableVT) : + ProgressVisualizerBase(stream, enableVT) + { + static constexpr UINT s_colorsForBelt = 20; + + Sixel::RenderControls imageRenderControls; + imageRenderControls.RenderSizeInCells(2, 1); + + // This image matches the target pixel size. If changing the target size, choose the most appropriate image. + std::filesystem::path imageAssetsRoot = Runtime::GetPathTo(Runtime::PathName::ImageAssets); + THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, imageAssetsRoot.empty()); + + m_icon = Sixel::ImageSource{ imageAssetsRoot / "AppList.targetsize-20.png" }; + m_icon.Resize(imageRenderControls); + imageRenderControls.ColorCount = Sixel::Palette::MaximumColorCount - s_colorsForBelt; + Sixel::Palette iconPalette = m_icon.CreatePalette(imageRenderControls); + + // TODO: Move to real location + m_belt = Sixel::ImageSource{ imageAssetsRoot / "progress-sixel/conveyor.png" }; + m_belt.Resize(imageRenderControls); + imageRenderControls.ColorCount = s_colorsForBelt; + imageRenderControls.InterpolationMode = Sixel::InterpolationMode::Linear; + Sixel::Palette beltPalette = m_belt.CreatePalette(imageRenderControls); + + Sixel::Palette combinedPalette{ iconPalette, beltPalette }; + + m_icon.ApplyPalette(combinedPalette); + m_belt.ApplyPalette(combinedPalette); + + m_compositor.Palette(std::move(combinedPalette)); + m_compositor.AddView(m_icon.Copy()); + m_compositor.AddView(m_belt.Copy()); + m_compositor.Controls().TransparencyEnabled = false; + m_compositor.Controls().RenderSizeInCells(s_ProgressBarCellWidth, 1); + } + + void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) override + { + if (current < m_lastCurrent) + { + ClearLine(); + } + + m_out << TextFormat::Default; + + m_out << "\r "; + + if (maximum) + { + + double percentage = static_cast(current) / maximum; + + // Translate icon so that its leading edge is the progress line + INT translation = static_cast((percentage * m_compositor.Controls().PixelWidth) - m_compositor[0].Width()); + + m_compositor[0].Translate(translation, 0, false); + m_compositor[1].Translate(translation, 0, true); + m_compositor.RenderTo(m_out); + + m_out << VirtualTerminal::Cursor::Position::Forward(s_ProgressBarCellWidth + 2); + + switch (type) + { + case AppInstaller::ProgressType::Bytes: + OutputBytes(m_out, current); + m_out << " / "; + OutputBytes(m_out, maximum); + break; + case AppInstaller::ProgressType::Percent: + default: + m_out << static_cast(percentage * 100) << '%'; + break; + } + + // Additional VT-based progress reporting, for terminals that support it + m_out << Progress::Construct(Progress::State::Normal, static_cast(percentage * 100)); + } + else + { + switch (type) + { + case AppInstaller::ProgressType::Bytes: + OutputBytes(m_out, current); + break; + case AppInstaller::ProgressType::Percent: + m_out << current << '%'; + break; + default: + m_out << current << " unknowns"; + break; + } + } + + m_lastCurrent = current; + m_isVisible = true; + } + + void EndProgress(bool hideProgressWhenDone) override + { + if (m_isVisible) + { + if (hideProgressWhenDone) + { + ClearLine(); + } + else + { + m_out << std::endl; + } + + if (VT_Enabled()) + { + // We always clear the VT-based progress bar, even if hideProgressWhenDone is false + // since it would be confusing for users if progress continues to be shown after winget exits + // (it is typically not automatically cleared by terminals on process exit) + m_out << Progress::Construct(Progress::State::None); + } + + m_isVisible = false; + } + } + + private: + std::atomic m_isVisible = false; + uint64_t m_lastCurrent = 0; + Sixel::ImageSource m_icon; + Sixel::ImageSource m_belt; + Sixel::Compositor m_compositor; + }; + + std::unique_ptr IIndefiniteSpinner::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function& sixelSupported) + { + std::unique_ptr result; + + switch (style) + { + case VisualStyle::NoVT: + case VisualStyle::Retro: + case VisualStyle::Accent: + case VisualStyle::Rainbow: + result = std::make_unique(stream, enableVT, style); + break; + case VisualStyle::Sixel: + if (sixelSupported()) + { + try + { + result = std::make_unique(stream, enableVT); + } + CATCH_LOG(); + } + + if (!result) + { + result = std::make_unique(stream, enableVT, VisualStyle::Accent); + } + break; + case VisualStyle::Disabled: + break; + default: + THROW_HR(E_NOTIMPL); + } + + return result; + } + + std::unique_ptr IProgressBar::CreateForStyle(BaseStream& stream, bool enableVT, VisualStyle style, const std::function& sixelSupported) + { + std::unique_ptr result; + + switch (style) + { + case VisualStyle::NoVT: + case VisualStyle::Retro: + case VisualStyle::Accent: + case VisualStyle::Rainbow: + result = std::make_unique(stream, enableVT, style); + break; + case VisualStyle::Sixel: + if (sixelSupported()) + { + try + { + result = std::make_unique(stream, enableVT); + } + CATCH_LOG(); + } + + if (!result) + { + result = std::make_unique(stream, enableVT, VisualStyle::Accent); + } + break; + case VisualStyle::Disabled: + break; + default: + THROW_HR(E_NOTIMPL); + } + + return result; + } +} diff --git a/src/AppInstallerCLICore/ExecutionProgress.h b/src/AppInstallerCLICore/ExecutionProgress.h index 7085ff3154..6c922b89ea 100644 --- a/src/AppInstallerCLICore/ExecutionProgress.h +++ b/src/AppInstallerCLICore/ExecutionProgress.h @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ChannelStreams.h" -#include -#include -#include - -#include -#include -#include -#include - -namespace AppInstaller::CLI::Execution -{ - // Displays an indefinite spinner. - struct IIndefiniteSpinner - { - virtual ~IIndefiniteSpinner() = default; - - // Set the message for the spinner. - virtual void SetMessage(std::string_view message) = 0; - - // Get the current message for the spinner. - virtual std::shared_ptr Message() = 0; - - // Show the indefinite spinner. - virtual void ShowSpinner() = 0; - - // Stop showing the indefinite spinner. - virtual void StopSpinner() = 0; - - // Creates an indefinite spinner for the given style. - static std::unique_ptr CreateForStyle(BaseStream& stream, bool enableVT, AppInstaller::Settings::VisualStyle style, const std::function& sixelSupported); - }; - - // Displays a progress bar. - struct IProgressBar - { - virtual ~IProgressBar() = default; - - // Show progress with the given values. - virtual void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) = 0; - - // Stop showing progress. - virtual void EndProgress(bool hideProgressWhenDone) = 0; - - // Creates a progress bar for the given style. - static std::unique_ptr CreateForStyle(BaseStream& stream, bool enableVT, AppInstaller::Settings::VisualStyle style, const std::function& sixelSupported); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ChannelStreams.h" +#include +#include +#include + +#include +#include +#include +#include + +namespace AppInstaller::CLI::Execution +{ + // Displays an indefinite spinner. + struct IIndefiniteSpinner + { + virtual ~IIndefiniteSpinner() = default; + + // Set the message for the spinner. + virtual void SetMessage(std::string_view message) = 0; + + // Get the current message for the spinner. + virtual std::shared_ptr Message() = 0; + + // Show the indefinite spinner. + virtual void ShowSpinner() = 0; + + // Stop showing the indefinite spinner. + virtual void StopSpinner() = 0; + + // Creates an indefinite spinner for the given style. + static std::unique_ptr CreateForStyle(BaseStream& stream, bool enableVT, AppInstaller::Settings::VisualStyle style, const std::function& sixelSupported); + }; + + // Displays a progress bar. + struct IProgressBar + { + virtual ~IProgressBar() = default; + + // Show progress with the given values. + virtual void ShowProgress(uint64_t current, uint64_t maximum, ProgressType type) = 0; + + // Stop showing progress. + virtual void EndProgress(bool hideProgressWhenDone) = 0; + + // Creates a progress bar for the given style. + static std::unique_ptr CreateForStyle(BaseStream& stream, bool enableVT, AppInstaller::Settings::VisualStyle style, const std::function& sixelSupported); + }; +} diff --git a/src/AppInstallerCLICore/ExecutionReporter.cpp b/src/AppInstallerCLICore/ExecutionReporter.cpp index 16222345f9..6bf720e6e9 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.cpp +++ b/src/AppInstallerCLICore/ExecutionReporter.cpp @@ -1,420 +1,420 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ExecutionReporter.h" -#include - - -namespace AppInstaller::CLI::Execution -{ - using namespace Settings; - using namespace VirtualTerminal; - - const Sequence& HelpCommandEmphasis = TextFormat::Foreground::Bright; - const Sequence& HelpArgumentEmphasis = TextFormat::Foreground::Bright; - const Sequence& ManifestInfoEmphasis = TextFormat::Foreground::Bright; - const Sequence& SourceInfoEmphasis = TextFormat::Foreground::Bright; - const Sequence& NameEmphasis = TextFormat::Foreground::BrightCyan; - const Sequence& IdEmphasis = TextFormat::Foreground::BrightCyan; - const Sequence& UrlEmphasis = TextFormat::Foreground::BrightBlue; - const Sequence& PromptEmphasis = TextFormat::Foreground::Bright; - const Sequence& ConvertToUpgradeFlowEmphasis = TextFormat::Foreground::BrightYellow; - const Sequence& ConfigurationIntentEmphasis = TextFormat::Foreground::Bright; - const Sequence& ConfigurationUnitEmphasis = TextFormat::Foreground::BrightCyan; - const Sequence& AuthenticationEmphasis = TextFormat::Foreground::BrightYellow; - - namespace - { - DWORD GetStdHandleType(DWORD stdHandle) - { - DWORD result = FILE_TYPE_UNKNOWN; - - HANDLE handle = GetStdHandle(stdHandle); - if (handle != INVALID_HANDLE_VALUE && handle != NULL) - { - result = GetFileType(handle); - } - - return result; - } - } - - Reporter::Reporter() : - Reporter(std::cout, std::cin) - { - m_outStreamFileType = GetStdHandleType(STD_OUTPUT_HANDLE); - m_inStreamFileType = GetStdHandleType(STD_INPUT_HANDLE); - } - - Reporter::Reporter(std::ostream& outStream, std::istream& inStream) : - Reporter(std::make_shared(outStream, true, ConsoleModeRestore::Instance().IsVTEnabled()), inStream) - { - SetProgressSink(this); - } - - Reporter::Reporter(std::shared_ptr outStream, std::istream& inStream) : - m_out(outStream), - m_in(inStream) - { - // Only create spinner and progress bar when stdout is attached to a console. - // When output is redirected to a file or pipe, suppress all progress output - // so it does not appear in the redirected stream. - if (GetConsoleWidth().has_value()) - { - auto sixelSupported = [&]() { return SixelsSupported(); }; - m_spinner = IIndefiniteSpinner::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), VisualStyle::Accent, sixelSupported); - m_progressBar = IProgressBar::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), VisualStyle::Accent, sixelSupported); - } - - SetProgressSink(this); - } - - Reporter::~Reporter() - { - this->CloseOutputStream(); - } - - Reporter::Reporter(const Reporter& other, clone_t) : - Reporter(other.m_out, other.m_in) - { - m_outStreamFileType = other.m_outStreamFileType; - m_inStreamFileType = other.m_inStreamFileType; - - SetChannel(other.m_channel); - - if (other.m_style.has_value()) - { - SetStyle(*other.m_style); - } - } - - std::optional Reporter::GetPrimaryDeviceAttributes() - { - if (ConsoleModeRestore::Instance().IsVTEnabled()) - { - return PrimaryDeviceAttributes{ m_out->Get(), m_in }; - } - else - { - return std::nullopt; - } - } - - OutputStream Reporter::GetOutputStream(Level level) - { - // If the level is not enabled, return a default stream which is disabled - if (WI_AreAllFlagsClear(m_enabledLevels, level)) - { - return OutputStream(*m_out, false, false); - } - - OutputStream result = GetBasicOutputStream(); - - switch (level) - { - case Level::Verbose: - result.AddFormat(TextFormat::Default); - break; - case Level::Info: - result.AddFormat(TextFormat::Default); - break; - case Level::Warning: - result.AddFormat(TextFormat::Foreground::BrightYellow); - break; - case Level::Error: - result.AddFormat(TextFormat::Foreground::BrightRed); - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return result; - } - - OutputStream Reporter::GetBasicOutputStream() - { - return { *m_out, m_channel == Channel::Output }; - } - - void Reporter::SetChannel(Channel channel) - { - m_channel = channel; - - if (m_channel != Channel::Output) - { - // Disable progress for non-output channels - m_spinner.reset(); - m_progressBar.reset(); - } - } - - void Reporter::SetStyle(VisualStyle style) - { - m_style = style; - - if (m_channel == Channel::Output && GetConsoleWidth().has_value()) - { - auto sixelSupported = [&]() { return SixelsSupported(); }; - m_spinner = IIndefiniteSpinner::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), style, sixelSupported); - m_progressBar = IProgressBar::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), style, sixelSupported); - } - - if (style == VisualStyle::NoVT) - { - m_out->SetVTEnabled(false); - } - } - - std::istream& Reporter::RawInputStream() - { - return m_in; - } - - bool Reporter::InputStreamIsInteractive() const - { - AICLI_LOG(CLI, Verbose, << "Reporter::m_inStreamFileType is " << m_inStreamFileType); - return m_inStreamFileType == FILE_TYPE_CHAR; - } - - bool Reporter::PromptForBoolResponse(Resource::LocString message, Level level, bool resultIfDisabled) - { - auto out = GetOutputStream(level); - - if (!out.IsEnabled()) - { - return resultIfDisabled; - } - - const std::vector options - { - BoolPromptOption{ Resource::String::PromptOptionYes, 'Y', true }, - BoolPromptOption{ Resource::String::PromptOptionNo, 'N', false }, - }; - - out << message << std::endl; - - // Try prompting until we get a recognized option - for (;;) - { - // Output all options - for (size_t i = 0; i < options.size(); ++i) - { - out << PromptEmphasis << "[" + options[i].Hotkey.get() + "] " + options[i].Label.get(); - - if (i + 1 == options.size()) - { - out << PromptEmphasis << ": "; - } - else - { - out << " "; - } - } - - // Read the response - std::string response; - if (!std::getline(m_in, response)) - { - THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR); - } - - // Find the matching option ignoring whitespace - Utility::Trim(response); - for (const auto& option : options) - { - if (Utility::CaseInsensitiveEquals(response, option.Label) || - Utility::CaseInsensitiveEquals(response, option.Hotkey)) - { - return option.Value; - } - } - } - } - - void Reporter::PromptForEnter(Level level) - { - auto out = GetOutputStream(level); - if (!out.IsEnabled()) - { - return; - } - - out << std::endl << Resource::String::PressEnterToContinue << std::endl; - m_in.get(); - } - - std::filesystem::path Reporter::PromptForPath(Resource::LocString message, Level level, std::filesystem::path resultIfDisabled) - { - auto out = GetOutputStream(level); - - if (!out.IsEnabled()) - { - return resultIfDisabled; - } - - // Try prompting until we get a valid answer - for (;;) - { - out << message << ' '; - - // Read the response - std::string response; - if (!std::getline(m_in, response)) - { - THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR); - } - - // Validate the path - std::filesystem::path path{ response }; - if (path.is_absolute()) - { - return path; - } - } - - } - - void Reporter::ShowIndefiniteProgress(bool running) - { - if (m_spinner) - { - if (running) - { - m_spinner->ShowSpinner(); - } - else - { - m_spinner->StopSpinner(); - } - } - } - - void Reporter::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) - { - ShowIndefiniteProgress(false); - if (m_progressBar) - { - m_progressBar->ShowProgress(current, maximum, type); - } - } - - void Reporter::SetProgressMessage(std::string_view message) - { - if (m_spinner) - { - m_spinner->SetMessage(message); - } - } - - void Reporter::BeginProgress() - { - GetBasicOutputStream() << VirtualTerminal::Cursor::Visibility::DisableShow; - ShowIndefiniteProgress(true); - }; - - void Reporter::EndProgress(bool hideProgressWhenDone) - { - ShowIndefiniteProgress(false); - if (m_progressBar) - { - m_progressBar->EndProgress(hideProgressWhenDone); - } - SetProgressMessage({}); - GetBasicOutputStream() << VirtualTerminal::Cursor::Visibility::EnableShow; - }; - - Reporter::AsyncProgressScope::AsyncProgressScope(Reporter& reporter, IProgressSink* sink, bool hideProgressWhenDone) : - m_reporter(reporter), m_callback(sink) - { - reporter.SetProgressCallback(&m_callback); - sink->BeginProgress(); - m_hideProgressWhenDone = hideProgressWhenDone; - } - - Reporter::AsyncProgressScope::~AsyncProgressScope() - { - m_reporter.get().SetProgressCallback(nullptr); - m_callback.GetSink()->EndProgress(m_hideProgressWhenDone); - } - - ProgressCallback& Reporter::AsyncProgressScope::Callback() - { - return m_callback; - } - - IProgressCallback* Reporter::AsyncProgressScope::operator->() - { - return &m_callback; - } - - bool Reporter::AsyncProgressScope::HideProgressWhenDone() const - { - return m_hideProgressWhenDone; - } - - void Reporter::AsyncProgressScope::HideProgressWhenDone(bool value) - { - m_hideProgressWhenDone.store(value); - } - - std::unique_ptr Reporter::BeginAsyncProgress(bool hideProgressWhenDone) - { - return std::make_unique(*this, m_progressSink.load(), hideProgressWhenDone); - } - - void Reporter::SetProgressCallback(ProgressCallback* callback) - { - auto lock = m_progressCallbackLock.lock_exclusive(); - // Attempting two progress operations at the same time; not supported. - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_progressCallback != nullptr && callback != nullptr); - m_progressCallback = callback; - } - - void Reporter::CancelInProgressTask(bool force, CancelReason reason) - { - // TODO: Maybe ask the user if they really want to cancel? - UNREFERENCED_PARAMETER(force); - auto lock = m_progressCallbackLock.lock_shared(); - ProgressCallback* callback = m_progressCallback.load(); - if (callback) - { - if (!callback->IsCancelledBy(CancelReason::Any)) - { - callback->SetProgressMessage(Resource::String::CancellingOperation()); - callback->Cancel(reason); - } - } - } - - void Reporter::CloseOutputStream(bool forceDisable) - { - if (forceDisable) - { - m_out->Disable(); - } - m_out->RestoreDefault(); - } - - void Reporter::SetLevelMask(Level reporterLevel, bool setEnabled) { - - if (setEnabled) - { - WI_SetAllFlags(m_enabledLevels, reporterLevel); - } - else - { - WI_ClearAllFlags(m_enabledLevels, reporterLevel); - } - } - - bool Reporter::SixelsSupported() - { - auto attributes = GetPrimaryDeviceAttributes(); - return (attributes ? attributes->Supports(PrimaryDeviceAttributes::Extension::Sixel) : false); - } - - bool Reporter::SixelsEnabled() - { - return Settings::User().Get() && SixelsSupported(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ExecutionReporter.h" +#include + + +namespace AppInstaller::CLI::Execution +{ + using namespace Settings; + using namespace VirtualTerminal; + + const Sequence& HelpCommandEmphasis = TextFormat::Foreground::Bright; + const Sequence& HelpArgumentEmphasis = TextFormat::Foreground::Bright; + const Sequence& ManifestInfoEmphasis = TextFormat::Foreground::Bright; + const Sequence& SourceInfoEmphasis = TextFormat::Foreground::Bright; + const Sequence& NameEmphasis = TextFormat::Foreground::BrightCyan; + const Sequence& IdEmphasis = TextFormat::Foreground::BrightCyan; + const Sequence& UrlEmphasis = TextFormat::Foreground::BrightBlue; + const Sequence& PromptEmphasis = TextFormat::Foreground::Bright; + const Sequence& ConvertToUpgradeFlowEmphasis = TextFormat::Foreground::BrightYellow; + const Sequence& ConfigurationIntentEmphasis = TextFormat::Foreground::Bright; + const Sequence& ConfigurationUnitEmphasis = TextFormat::Foreground::BrightCyan; + const Sequence& AuthenticationEmphasis = TextFormat::Foreground::BrightYellow; + + namespace + { + DWORD GetStdHandleType(DWORD stdHandle) + { + DWORD result = FILE_TYPE_UNKNOWN; + + HANDLE handle = GetStdHandle(stdHandle); + if (handle != INVALID_HANDLE_VALUE && handle != NULL) + { + result = GetFileType(handle); + } + + return result; + } + } + + Reporter::Reporter() : + Reporter(std::cout, std::cin) + { + m_outStreamFileType = GetStdHandleType(STD_OUTPUT_HANDLE); + m_inStreamFileType = GetStdHandleType(STD_INPUT_HANDLE); + } + + Reporter::Reporter(std::ostream& outStream, std::istream& inStream) : + Reporter(std::make_shared(outStream, true, ConsoleModeRestore::Instance().IsVTEnabled()), inStream) + { + SetProgressSink(this); + } + + Reporter::Reporter(std::shared_ptr outStream, std::istream& inStream) : + m_out(outStream), + m_in(inStream) + { + // Only create spinner and progress bar when stdout is attached to a console. + // When output is redirected to a file or pipe, suppress all progress output + // so it does not appear in the redirected stream. + if (GetConsoleWidth().has_value()) + { + auto sixelSupported = [&]() { return SixelsSupported(); }; + m_spinner = IIndefiniteSpinner::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), VisualStyle::Accent, sixelSupported); + m_progressBar = IProgressBar::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), VisualStyle::Accent, sixelSupported); + } + + SetProgressSink(this); + } + + Reporter::~Reporter() + { + this->CloseOutputStream(); + } + + Reporter::Reporter(const Reporter& other, clone_t) : + Reporter(other.m_out, other.m_in) + { + m_outStreamFileType = other.m_outStreamFileType; + m_inStreamFileType = other.m_inStreamFileType; + + SetChannel(other.m_channel); + + if (other.m_style.has_value()) + { + SetStyle(*other.m_style); + } + } + + std::optional Reporter::GetPrimaryDeviceAttributes() + { + if (ConsoleModeRestore::Instance().IsVTEnabled()) + { + return PrimaryDeviceAttributes{ m_out->Get(), m_in }; + } + else + { + return std::nullopt; + } + } + + OutputStream Reporter::GetOutputStream(Level level) + { + // If the level is not enabled, return a default stream which is disabled + if (WI_AreAllFlagsClear(m_enabledLevels, level)) + { + return OutputStream(*m_out, false, false); + } + + OutputStream result = GetBasicOutputStream(); + + switch (level) + { + case Level::Verbose: + result.AddFormat(TextFormat::Default); + break; + case Level::Info: + result.AddFormat(TextFormat::Default); + break; + case Level::Warning: + result.AddFormat(TextFormat::Foreground::BrightYellow); + break; + case Level::Error: + result.AddFormat(TextFormat::Foreground::BrightRed); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + OutputStream Reporter::GetBasicOutputStream() + { + return { *m_out, m_channel == Channel::Output }; + } + + void Reporter::SetChannel(Channel channel) + { + m_channel = channel; + + if (m_channel != Channel::Output) + { + // Disable progress for non-output channels + m_spinner.reset(); + m_progressBar.reset(); + } + } + + void Reporter::SetStyle(VisualStyle style) + { + m_style = style; + + if (m_channel == Channel::Output && GetConsoleWidth().has_value()) + { + auto sixelSupported = [&]() { return SixelsSupported(); }; + m_spinner = IIndefiniteSpinner::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), style, sixelSupported); + m_progressBar = IProgressBar::CreateForStyle(*m_out, ConsoleModeRestore::Instance().IsVTEnabled(), style, sixelSupported); + } + + if (style == VisualStyle::NoVT) + { + m_out->SetVTEnabled(false); + } + } + + std::istream& Reporter::RawInputStream() + { + return m_in; + } + + bool Reporter::InputStreamIsInteractive() const + { + AICLI_LOG(CLI, Verbose, << "Reporter::m_inStreamFileType is " << m_inStreamFileType); + return m_inStreamFileType == FILE_TYPE_CHAR; + } + + bool Reporter::PromptForBoolResponse(Resource::LocString message, Level level, bool resultIfDisabled) + { + auto out = GetOutputStream(level); + + if (!out.IsEnabled()) + { + return resultIfDisabled; + } + + const std::vector options + { + BoolPromptOption{ Resource::String::PromptOptionYes, 'Y', true }, + BoolPromptOption{ Resource::String::PromptOptionNo, 'N', false }, + }; + + out << message << std::endl; + + // Try prompting until we get a recognized option + for (;;) + { + // Output all options + for (size_t i = 0; i < options.size(); ++i) + { + out << PromptEmphasis << "[" + options[i].Hotkey.get() + "] " + options[i].Label.get(); + + if (i + 1 == options.size()) + { + out << PromptEmphasis << ": "; + } + else + { + out << " "; + } + } + + // Read the response + std::string response; + if (!std::getline(m_in, response)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR); + } + + // Find the matching option ignoring whitespace + Utility::Trim(response); + for (const auto& option : options) + { + if (Utility::CaseInsensitiveEquals(response, option.Label) || + Utility::CaseInsensitiveEquals(response, option.Hotkey)) + { + return option.Value; + } + } + } + } + + void Reporter::PromptForEnter(Level level) + { + auto out = GetOutputStream(level); + if (!out.IsEnabled()) + { + return; + } + + out << std::endl << Resource::String::PressEnterToContinue << std::endl; + m_in.get(); + } + + std::filesystem::path Reporter::PromptForPath(Resource::LocString message, Level level, std::filesystem::path resultIfDisabled) + { + auto out = GetOutputStream(level); + + if (!out.IsEnabled()) + { + return resultIfDisabled; + } + + // Try prompting until we get a valid answer + for (;;) + { + out << message << ' '; + + // Read the response + std::string response; + if (!std::getline(m_in, response)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR); + } + + // Validate the path + std::filesystem::path path{ response }; + if (path.is_absolute()) + { + return path; + } + } + + } + + void Reporter::ShowIndefiniteProgress(bool running) + { + if (m_spinner) + { + if (running) + { + m_spinner->ShowSpinner(); + } + else + { + m_spinner->StopSpinner(); + } + } + } + + void Reporter::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) + { + ShowIndefiniteProgress(false); + if (m_progressBar) + { + m_progressBar->ShowProgress(current, maximum, type); + } + } + + void Reporter::SetProgressMessage(std::string_view message) + { + if (m_spinner) + { + m_spinner->SetMessage(message); + } + } + + void Reporter::BeginProgress() + { + GetBasicOutputStream() << VirtualTerminal::Cursor::Visibility::DisableShow; + ShowIndefiniteProgress(true); + }; + + void Reporter::EndProgress(bool hideProgressWhenDone) + { + ShowIndefiniteProgress(false); + if (m_progressBar) + { + m_progressBar->EndProgress(hideProgressWhenDone); + } + SetProgressMessage({}); + GetBasicOutputStream() << VirtualTerminal::Cursor::Visibility::EnableShow; + }; + + Reporter::AsyncProgressScope::AsyncProgressScope(Reporter& reporter, IProgressSink* sink, bool hideProgressWhenDone) : + m_reporter(reporter), m_callback(sink) + { + reporter.SetProgressCallback(&m_callback); + sink->BeginProgress(); + m_hideProgressWhenDone = hideProgressWhenDone; + } + + Reporter::AsyncProgressScope::~AsyncProgressScope() + { + m_reporter.get().SetProgressCallback(nullptr); + m_callback.GetSink()->EndProgress(m_hideProgressWhenDone); + } + + ProgressCallback& Reporter::AsyncProgressScope::Callback() + { + return m_callback; + } + + IProgressCallback* Reporter::AsyncProgressScope::operator->() + { + return &m_callback; + } + + bool Reporter::AsyncProgressScope::HideProgressWhenDone() const + { + return m_hideProgressWhenDone; + } + + void Reporter::AsyncProgressScope::HideProgressWhenDone(bool value) + { + m_hideProgressWhenDone.store(value); + } + + std::unique_ptr Reporter::BeginAsyncProgress(bool hideProgressWhenDone) + { + return std::make_unique(*this, m_progressSink.load(), hideProgressWhenDone); + } + + void Reporter::SetProgressCallback(ProgressCallback* callback) + { + auto lock = m_progressCallbackLock.lock_exclusive(); + // Attempting two progress operations at the same time; not supported. + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_progressCallback != nullptr && callback != nullptr); + m_progressCallback = callback; + } + + void Reporter::CancelInProgressTask(bool force, CancelReason reason) + { + // TODO: Maybe ask the user if they really want to cancel? + UNREFERENCED_PARAMETER(force); + auto lock = m_progressCallbackLock.lock_shared(); + ProgressCallback* callback = m_progressCallback.load(); + if (callback) + { + if (!callback->IsCancelledBy(CancelReason::Any)) + { + callback->SetProgressMessage(Resource::String::CancellingOperation()); + callback->Cancel(reason); + } + } + } + + void Reporter::CloseOutputStream(bool forceDisable) + { + if (forceDisable) + { + m_out->Disable(); + } + m_out->RestoreDefault(); + } + + void Reporter::SetLevelMask(Level reporterLevel, bool setEnabled) { + + if (setEnabled) + { + WI_SetAllFlags(m_enabledLevels, reporterLevel); + } + else + { + WI_ClearAllFlags(m_enabledLevels, reporterLevel); + } + } + + bool Reporter::SixelsSupported() + { + auto attributes = GetPrimaryDeviceAttributes(); + return (attributes ? attributes->Supports(PrimaryDeviceAttributes::Extension::Sixel) : false); + } + + bool Reporter::SixelsEnabled() + { + return Settings::User().Get() && SixelsSupported(); + } +} diff --git a/src/AppInstallerCLICore/ExecutionReporter.h b/src/AppInstallerCLICore/ExecutionReporter.h index 20babda255..d4598b8d67 100644 --- a/src/AppInstallerCLICore/ExecutionReporter.h +++ b/src/AppInstallerCLICore/ExecutionReporter.h @@ -1,233 +1,233 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionProgress.h" -#include "ChannelStreams.h" -#include "Resources.h" -#include "VTSupport.h" -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::CLI::Execution -{ -#define WINGET_OSTREAM_FORMAT_HRESULT(hr) "0x" << Logging::SetHRFormat << hr - - // One of the options available to the users when prompting for something. - struct BoolPromptOption - { - BoolPromptOption(Resource::StringId labelId, char hotkey, bool value) - : Label(labelId), Hotkey(std::string(1, hotkey)), Value(value) {} - - Utility::LocIndString Hotkey; - Resource::LocString Label; - - // Value associated with this option. - bool Value; - }; - - // Reporter should be the central place to show workflow status to user. - struct Reporter : public IProgressSink - { - // The channel that the reporter is targeting. - // Based on commands/arguments, only one of these channels can be chosen. - enum class Channel - { - Output, - Completion, - Json, - Disabled, - }; - - // The level for the Output channel. - enum class Level : uint32_t - { - None = 0x0, - Verbose = 0x1, - Info = 0x2, - Warning = 0x4, - Error = 0x8, - All = Verbose | Info | Warning | Error, - }; - - Reporter(); - Reporter(std::ostream& outStream, std::istream& inStream); - Reporter(const Reporter&) = delete; - Reporter& operator=(const Reporter&) = delete; - - Reporter(Reporter&&) = default; - Reporter& operator=(Reporter&&) = default; - - // Request that a clone be constructed from the given reporter. - struct clone_t {}; - Reporter(const Reporter& other, clone_t); - - ~Reporter(); - - // Gets the primary device attributes if available. - std::optional GetPrimaryDeviceAttributes(); - - // Get a stream for verbose output. - OutputStream Verbose() { return GetOutputStream(Level::Verbose); } - - // Get a stream for informational output. - OutputStream Info() { return GetOutputStream(Level::Info); } - - // Get a stream for warning output. - OutputStream Warn() { return GetOutputStream(Level::Warning); } - - // Get a stream for error output. - OutputStream Error() { return GetOutputStream(Level::Error); } - - // Get a stream for outputting completion words. - OutputStream Completion() { return OutputStream(*m_out, m_channel == Channel::Completion, false); } - - // Get a stream for outputting completion words. - OutputStream Json() { return OutputStream(*m_out, m_channel == Channel::Json, false); } - - // Gets a stream for output of the given level. - OutputStream GetOutputStream(Level level); - - void EmptyLine() { GetBasicOutputStream() << std::endl; } - - // Sets the channel that will be reported to. - // Only do this once and as soon as the channel is determined. - void SetChannel(Channel channel); - - // Sets the visual style (mostly for progress currently) - void SetStyle(AppInstaller::Settings::VisualStyle style); - - // Get the raw input stream. - std::istream& RawInputStream(); - - // Check if the input stream is interactive or not. - bool InputStreamIsInteractive() const; - - // Prompts the user, return true if they consented. - bool PromptForBoolResponse(Resource::LocString message, Level level = Level::Info, bool resultIfDisabled = false); - - // Prompts the user, continues when Enter is pressed - void PromptForEnter(Level level = Level::Info); - - // Prompts the user for a path. - std::filesystem::path PromptForPath(Resource::LocString message, Level level = Level::Info, std::filesystem::path resultIfDisabled = std::filesystem::path::path()); - - // IProgressSink - void BeginProgress() override; - void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; - void SetProgressMessage(std::string_view message) override; - void EndProgress(bool hideProgressWhenDone) override; - - // Contains the objects used for async progress and the lifetime of said progress. - struct AsyncProgressScope - { - AsyncProgressScope(Reporter& reporter, IProgressSink* sink, bool hideProgressWhenDone); - ~AsyncProgressScope(); - - AsyncProgressScope(const AsyncProgressScope&) = delete; - AsyncProgressScope& operator=(const AsyncProgressScope&) = delete; - - AsyncProgressScope(AsyncProgressScope&&) = delete; - AsyncProgressScope& operator=(AsyncProgressScope&&) = delete; - - ProgressCallback& Callback(); - IProgressCallback* operator->(); - - bool HideProgressWhenDone() const; - void HideProgressWhenDone(bool value); - - private: - std::reference_wrapper m_reporter; - ProgressCallback m_callback; - std::atomic_bool m_hideProgressWhenDone; - }; - - // Runs the given callable of type: auto(IProgressCallback&) - template - auto ExecuteWithProgress(F&& f, bool hideProgressWhenDone = false) - { - auto progressScope = BeginAsyncProgress(hideProgressWhenDone); - return f(progressScope->Callback()); - } - - // Begins an asynchronous progress operation. - std::unique_ptr BeginAsyncProgress(bool hideProgressWhenDone = false); - - // Sets the in progress callback. - void SetProgressCallback(ProgressCallback* callback); - - // Cancels the in progress task. - void CancelInProgressTask(bool force, CancelReason reason); - - void CloseOutputStream(bool forceDisable = false); - - void SetProgressSink(IProgressSink* sink) - { - m_progressSink = sink; - } - - bool IsLevelEnabled(Level reporterLevel) - { - return WI_AreAllFlagsSet(m_enabledLevels, reporterLevel); - } - - void SetLevelMask(Level reporterLevel, bool setEnabled = true); - - // Determines if sixels are supported by the current instance. - bool SixelsSupported(); - - // Determines if sixels are enabled; they must be both supported and enabled by user settings. - bool SixelsEnabled(); - - private: - Reporter(std::shared_ptr outStream, std::istream& inStream); - // Gets a stream for output for internal use. - OutputStream GetBasicOutputStream(); - - // Used to show indefinite progress. Currently an indefinite spinner is the form of - // showing indefinite progress. - // running: shows indefinite progress if set to true, stops indefinite progress if set to false - void ShowIndefiniteProgress(bool running); - - Channel m_channel = Channel::Output; - std::shared_ptr m_out; - std::istream& m_in; - std::optional m_style; - std::unique_ptr m_spinner; - std::unique_ptr m_progressBar; - wil::srwlock m_progressCallbackLock; - std::atomic m_progressCallback; - std::atomic m_progressSink; - DWORD m_outStreamFileType = FILE_TYPE_UNKNOWN; - DWORD m_inStreamFileType = FILE_TYPE_UNKNOWN; - - // Enable all levels by default - Level m_enabledLevels = Level::All; - }; - - DEFINE_ENUM_FLAG_OPERATORS(Reporter::Level); - - // Indirection to enable change without tracking down every place - extern const VirtualTerminal::Sequence& HelpCommandEmphasis; - extern const VirtualTerminal::Sequence& HelpArgumentEmphasis; - extern const VirtualTerminal::Sequence& ManifestInfoEmphasis; - extern const VirtualTerminal::Sequence& SourceInfoEmphasis; - extern const VirtualTerminal::Sequence& NameEmphasis; - extern const VirtualTerminal::Sequence& IdEmphasis; - extern const VirtualTerminal::Sequence& UrlEmphasis; - extern const VirtualTerminal::Sequence& PromptEmphasis; - extern const VirtualTerminal::Sequence& ConvertToUpgradeFlowEmphasis; - extern const VirtualTerminal::Sequence& ConfigurationIntentEmphasis; - extern const VirtualTerminal::Sequence& ConfigurationUnitEmphasis; - extern const VirtualTerminal::Sequence& AuthenticationEmphasis; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionProgress.h" +#include "ChannelStreams.h" +#include "Resources.h" +#include "VTSupport.h" +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::CLI::Execution +{ +#define WINGET_OSTREAM_FORMAT_HRESULT(hr) "0x" << Logging::SetHRFormat << hr + + // One of the options available to the users when prompting for something. + struct BoolPromptOption + { + BoolPromptOption(Resource::StringId labelId, char hotkey, bool value) + : Label(labelId), Hotkey(std::string(1, hotkey)), Value(value) {} + + Utility::LocIndString Hotkey; + Resource::LocString Label; + + // Value associated with this option. + bool Value; + }; + + // Reporter should be the central place to show workflow status to user. + struct Reporter : public IProgressSink + { + // The channel that the reporter is targeting. + // Based on commands/arguments, only one of these channels can be chosen. + enum class Channel + { + Output, + Completion, + Json, + Disabled, + }; + + // The level for the Output channel. + enum class Level : uint32_t + { + None = 0x0, + Verbose = 0x1, + Info = 0x2, + Warning = 0x4, + Error = 0x8, + All = Verbose | Info | Warning | Error, + }; + + Reporter(); + Reporter(std::ostream& outStream, std::istream& inStream); + Reporter(const Reporter&) = delete; + Reporter& operator=(const Reporter&) = delete; + + Reporter(Reporter&&) = default; + Reporter& operator=(Reporter&&) = default; + + // Request that a clone be constructed from the given reporter. + struct clone_t {}; + Reporter(const Reporter& other, clone_t); + + ~Reporter(); + + // Gets the primary device attributes if available. + std::optional GetPrimaryDeviceAttributes(); + + // Get a stream for verbose output. + OutputStream Verbose() { return GetOutputStream(Level::Verbose); } + + // Get a stream for informational output. + OutputStream Info() { return GetOutputStream(Level::Info); } + + // Get a stream for warning output. + OutputStream Warn() { return GetOutputStream(Level::Warning); } + + // Get a stream for error output. + OutputStream Error() { return GetOutputStream(Level::Error); } + + // Get a stream for outputting completion words. + OutputStream Completion() { return OutputStream(*m_out, m_channel == Channel::Completion, false); } + + // Get a stream for outputting completion words. + OutputStream Json() { return OutputStream(*m_out, m_channel == Channel::Json, false); } + + // Gets a stream for output of the given level. + OutputStream GetOutputStream(Level level); + + void EmptyLine() { GetBasicOutputStream() << std::endl; } + + // Sets the channel that will be reported to. + // Only do this once and as soon as the channel is determined. + void SetChannel(Channel channel); + + // Sets the visual style (mostly for progress currently) + void SetStyle(AppInstaller::Settings::VisualStyle style); + + // Get the raw input stream. + std::istream& RawInputStream(); + + // Check if the input stream is interactive or not. + bool InputStreamIsInteractive() const; + + // Prompts the user, return true if they consented. + bool PromptForBoolResponse(Resource::LocString message, Level level = Level::Info, bool resultIfDisabled = false); + + // Prompts the user, continues when Enter is pressed + void PromptForEnter(Level level = Level::Info); + + // Prompts the user for a path. + std::filesystem::path PromptForPath(Resource::LocString message, Level level = Level::Info, std::filesystem::path resultIfDisabled = std::filesystem::path::path()); + + // IProgressSink + void BeginProgress() override; + void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; + void SetProgressMessage(std::string_view message) override; + void EndProgress(bool hideProgressWhenDone) override; + + // Contains the objects used for async progress and the lifetime of said progress. + struct AsyncProgressScope + { + AsyncProgressScope(Reporter& reporter, IProgressSink* sink, bool hideProgressWhenDone); + ~AsyncProgressScope(); + + AsyncProgressScope(const AsyncProgressScope&) = delete; + AsyncProgressScope& operator=(const AsyncProgressScope&) = delete; + + AsyncProgressScope(AsyncProgressScope&&) = delete; + AsyncProgressScope& operator=(AsyncProgressScope&&) = delete; + + ProgressCallback& Callback(); + IProgressCallback* operator->(); + + bool HideProgressWhenDone() const; + void HideProgressWhenDone(bool value); + + private: + std::reference_wrapper m_reporter; + ProgressCallback m_callback; + std::atomic_bool m_hideProgressWhenDone; + }; + + // Runs the given callable of type: auto(IProgressCallback&) + template + auto ExecuteWithProgress(F&& f, bool hideProgressWhenDone = false) + { + auto progressScope = BeginAsyncProgress(hideProgressWhenDone); + return f(progressScope->Callback()); + } + + // Begins an asynchronous progress operation. + std::unique_ptr BeginAsyncProgress(bool hideProgressWhenDone = false); + + // Sets the in progress callback. + void SetProgressCallback(ProgressCallback* callback); + + // Cancels the in progress task. + void CancelInProgressTask(bool force, CancelReason reason); + + void CloseOutputStream(bool forceDisable = false); + + void SetProgressSink(IProgressSink* sink) + { + m_progressSink = sink; + } + + bool IsLevelEnabled(Level reporterLevel) + { + return WI_AreAllFlagsSet(m_enabledLevels, reporterLevel); + } + + void SetLevelMask(Level reporterLevel, bool setEnabled = true); + + // Determines if sixels are supported by the current instance. + bool SixelsSupported(); + + // Determines if sixels are enabled; they must be both supported and enabled by user settings. + bool SixelsEnabled(); + + private: + Reporter(std::shared_ptr outStream, std::istream& inStream); + // Gets a stream for output for internal use. + OutputStream GetBasicOutputStream(); + + // Used to show indefinite progress. Currently an indefinite spinner is the form of + // showing indefinite progress. + // running: shows indefinite progress if set to true, stops indefinite progress if set to false + void ShowIndefiniteProgress(bool running); + + Channel m_channel = Channel::Output; + std::shared_ptr m_out; + std::istream& m_in; + std::optional m_style; + std::unique_ptr m_spinner; + std::unique_ptr m_progressBar; + wil::srwlock m_progressCallbackLock; + std::atomic m_progressCallback; + std::atomic m_progressSink; + DWORD m_outStreamFileType = FILE_TYPE_UNKNOWN; + DWORD m_inStreamFileType = FILE_TYPE_UNKNOWN; + + // Enable all levels by default + Level m_enabledLevels = Level::All; + }; + + DEFINE_ENUM_FLAG_OPERATORS(Reporter::Level); + + // Indirection to enable change without tracking down every place + extern const VirtualTerminal::Sequence& HelpCommandEmphasis; + extern const VirtualTerminal::Sequence& HelpArgumentEmphasis; + extern const VirtualTerminal::Sequence& ManifestInfoEmphasis; + extern const VirtualTerminal::Sequence& SourceInfoEmphasis; + extern const VirtualTerminal::Sequence& NameEmphasis; + extern const VirtualTerminal::Sequence& IdEmphasis; + extern const VirtualTerminal::Sequence& UrlEmphasis; + extern const VirtualTerminal::Sequence& PromptEmphasis; + extern const VirtualTerminal::Sequence& ConvertToUpgradeFlowEmphasis; + extern const VirtualTerminal::Sequence& ConfigurationIntentEmphasis; + extern const VirtualTerminal::Sequence& ConfigurationUnitEmphasis; + extern const VirtualTerminal::Sequence& AuthenticationEmphasis; +} diff --git a/src/AppInstallerCLICore/Invocation.h b/src/AppInstallerCLICore/Invocation.h index ffb66af06b..be1be3bff1 100644 --- a/src/AppInstallerCLICore/Invocation.h +++ b/src/AppInstallerCLICore/Invocation.h @@ -1,48 +1,48 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::CLI -{ - // Contains the raw command line arguments and functionality to iterate and consume them. - struct Invocation - { - Invocation(std::vector&& args) : m_args(std::move(args)) {} - - struct iterator - { - iterator(size_t arg, std::vector& args) : m_arg(arg), m_args(args) {} - - iterator(const iterator&) = default; - iterator& operator=(const iterator&) = default; - - iterator operator++() { return { ++m_arg, m_args }; } - iterator operator++(int) { return { m_arg++, m_args }; } - iterator operator--() { return { --m_arg, m_args }; } - iterator operator--(int) { return { m_arg--, m_args }; } - - bool operator==(const iterator& other) const { return m_arg == other.m_arg; } - bool operator!=(const iterator& other) const { return m_arg != other.m_arg; } - - const std::string& operator*() const { return m_args[m_arg]; } - const std::string* operator->() const { return &(m_args[m_arg]); } - - size_t index() const { return m_arg; } - - private: - size_t m_arg; - std::vector& m_args; - }; - - size_t size() const { return m_args.size(); } - iterator begin() { return { m_currentFirstArg, m_args }; } - iterator end() { return { m_args.size(), m_args }; } - void consume(const iterator& i) { m_currentFirstArg = i.index() + 1; } - - private: - std::vector m_args; - size_t m_currentFirstArg = 0; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::CLI +{ + // Contains the raw command line arguments and functionality to iterate and consume them. + struct Invocation + { + Invocation(std::vector&& args) : m_args(std::move(args)) {} + + struct iterator + { + iterator(size_t arg, std::vector& args) : m_arg(arg), m_args(args) {} + + iterator(const iterator&) = default; + iterator& operator=(const iterator&) = default; + + iterator operator++() { return { ++m_arg, m_args }; } + iterator operator++(int) { return { m_arg++, m_args }; } + iterator operator--() { return { --m_arg, m_args }; } + iterator operator--(int) { return { m_arg--, m_args }; } + + bool operator==(const iterator& other) const { return m_arg == other.m_arg; } + bool operator!=(const iterator& other) const { return m_arg != other.m_arg; } + + const std::string& operator*() const { return m_args[m_arg]; } + const std::string* operator->() const { return &(m_args[m_arg]); } + + size_t index() const { return m_arg; } + + private: + size_t m_arg; + std::vector& m_args; + }; + + size_t size() const { return m_args.size(); } + iterator begin() { return { m_currentFirstArg, m_args }; } + iterator end() { return { m_args.size(), m_args }; } + void consume(const iterator& i) { m_currentFirstArg = i.index() + 1; } + + private: + std::vector m_args; + size_t m_currentFirstArg = 0; + }; +} diff --git a/src/AppInstallerCLICore/PackageCollection.h b/src/AppInstallerCLICore/PackageCollection.h index 1f13f04983..7b087b3e41 100644 --- a/src/AppInstallerCLICore/PackageCollection.h +++ b/src/AppInstallerCLICore/PackageCollection.h @@ -79,4 +79,4 @@ namespace AppInstaller::CLI // Tries to parse a JSON into a collection of packages. ParseResult TryParseJson(const Json::Value& root); } -} +} diff --git a/src/AppInstallerCLICore/PortableInstaller.cpp b/src/AppInstallerCLICore/PortableInstaller.cpp index 71b1bc2176..1e3498e55a 100644 --- a/src/AppInstallerCLICore/PortableInstaller.cpp +++ b/src/AppInstallerCLICore/PortableInstaller.cpp @@ -1,544 +1,544 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ExecutionContext.h" -#include "PortableInstaller.h" -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller::Utility; -using namespace AppInstaller::Registry; -using namespace AppInstaller::Registry::Portable; -using namespace AppInstaller::Registry::Environment; -using namespace AppInstaller::Repository; -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::Repository::Microsoft::Schema; -using namespace AppInstaller::CLI::Workflow; - -namespace AppInstaller::CLI::Portable -{ - std::filesystem::path GetPortableLinksLocation(Manifest::ScopeEnum scope) - { - if (scope == Manifest::ScopeEnum::Machine) - { - return Runtime::GetPathTo(Runtime::PathName::PortableLinksMachineLocation); - } - else - { - return Runtime::GetPathTo(Runtime::PathName::PortableLinksUserLocation); - } - } - - std::filesystem::path GetPortableInstallRoot(Manifest::ScopeEnum scope, Utility::Architecture arch) - { - if (scope == Manifest::ScopeEnum::Machine) - { - if (arch == Utility::Architecture::X86) - { - return Runtime::GetPathTo(Runtime::PathName::PortablePackageMachineRootX86); - } - else - { - return Runtime::GetPathTo(Runtime::PathName::PortablePackageMachineRoot); - } - } - else - { - return Runtime::GetPathTo(Runtime::PathName::PortablePackageUserRoot); - } - } - - bool VerifyPortableFile(AppInstaller::Portable::PortableFileEntry& entry) - { - std::filesystem::path filePath = entry.GetFilePath(); - PortableFileType fileType = entry.FileType; - - if (fileType == PortableFileType::File) - { - if (std::filesystem::exists(filePath)) - { - SHA256::HashBuffer fileHash = SHA256::ComputeHashFromFile(filePath); - if (!SHA256::AreEqual(fileHash, SHA256::ConvertToBytes(entry.SHA256))) - { - AICLI_LOG(CLI, Warning, << "File hash does not match ARP Entry. Expected: " << entry.SHA256 << " Actual: " << SHA256::ConvertToString(fileHash)); - return false; - } - } - } - else if (fileType == PortableFileType::Symlink) - { - std::filesystem::path symlinkTargetPath{ AppInstaller::Utility::ConvertToUTF16(entry.SymlinkTarget) }; - if (Filesystem::SymlinkExists(filePath) && !Filesystem::VerifySymlink(filePath, symlinkTargetPath)) - { - AICLI_LOG(CLI, Warning, << "Symlink target does not match ARP Entry. Expected: " << symlinkTargetPath << " Actual: " << std::filesystem::read_symlink(filePath)); - return false; - } - } - - return true; - } - - void PortableInstaller::InstallFile(AppInstaller::Portable::PortableFileEntry& entry) - { - PortableFileType fileType = entry.FileType; - std::filesystem::path filePath = entry.GetFilePath(); - - if (entry.FileType == PortableFileType::File) - { - if (std::filesystem::exists(filePath)) - { - AICLI_LOG(Core, Info, << "Removing existing portable file at: " << filePath); - std::filesystem::remove(filePath); - } - - AICLI_LOG(Core, Info, << "Moving portable exe to: " << filePath); - - if (!RecordToIndex) - { - CommitToARPEntry(PortableValueName::PortableTargetFullPath, filePath); - CommitToARPEntry(PortableValueName::SHA256, entry.SHA256); - } - - Filesystem::RenameFile(entry.CurrentPath, filePath); - } - else if (fileType == PortableFileType::Directory) - { - if (Filesystem::IsSameVolume(entry.CurrentPath, filePath)) - { - AICLI_LOG(Core, Info, << "Renaming directory to: " << filePath); - Filesystem::RenameFile(entry.CurrentPath, filePath); - } - else - { - // Copy directory instead of renaming as there is a known issue with renaming across drives. - AICLI_LOG(Core, Info, << "Copying directory to: " << filePath); - std::filesystem::copy(entry.CurrentPath, filePath, std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive); - } - } - else if (entry.FileType == PortableFileType::Symlink) - { - std::filesystem::path symlinkTargetPath{ Utility::ConvertToUTF16(entry.SymlinkTarget) }; - - if (BinariesDependOnPath && !InstallDirectoryAddedToPath) - { - // Scenario indicated by 'ArchiveBinariesDependOnPath' manifest entry. - // Skip symlink creation for portables dependent on binaries that require the install directory to be added to PATH. - std::filesystem::path installDirectory = symlinkTargetPath.parent_path(); - AddToPathVariable(installDirectory); - AICLI_LOG(Core, Info, << "Install directory added to PATH: " << installDirectory); - CommitToARPEntry(PortableValueName::InstallDirectoryAddedToPath, InstallDirectoryAddedToPath = true); - } - else if (!InstallDirectoryAddedToPath) - { - std::filesystem::file_status status = std::filesystem::status(filePath); - if (std::filesystem::is_directory(status)) - { - AICLI_LOG(CLI, Info, << "Unable to create symlink. '" << filePath << "points to an existing directory."); - THROW_HR(APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY); - } - - if (!RecordToIndex) - { - CommitToARPEntry(PortableValueName::PortableSymlinkFullPath, filePath); - } - - if (std::filesystem::remove(filePath)) - { - AICLI_LOG(CLI, Info, << "Removed existing file at " << filePath); - m_stream << Resource::String::OverwritingExistingFileAtMessage(Utility::LocIndView{ filePath.u8string() }) << std::endl; - } - - if (Filesystem::CreateSymlink(symlinkTargetPath, filePath)) - { - AICLI_LOG(Core, Info, << "Symlink created at: " << filePath << " with target path: " << symlinkTargetPath); - } - else - { - // If symlink creation fails, resort to adding the package directory to PATH. - AICLI_LOG(Core, Info, << "Failed to create symlink at: " << filePath); - AddToPathVariable(symlinkTargetPath.parent_path()); - CommitToARPEntry(PortableValueName::InstallDirectoryAddedToPath, InstallDirectoryAddedToPath = true); - } - } - m_stream << Resource::String::PortableAliasAdded << ' ' << filePath.stem() << std::endl; - } - } - - void PortableInstaller::RemoveFile(AppInstaller::Portable::PortableFileEntry& entry) - { - const auto& filePath = entry.GetFilePath(); - PortableFileType fileType = entry.FileType; - - if (fileType == PortableFileType::File && std::filesystem::exists(filePath)) - { - AICLI_LOG(CLI, Info, << "Deleting portable exe at: " << filePath); - std::filesystem::remove(filePath); - } - else if (fileType == PortableFileType::Symlink) - { - if (Filesystem::SymlinkExists(filePath)) - { - AICLI_LOG(CLI, Info, << "Deleting portable symlink at: " << filePath); - std::filesystem::remove(filePath); - } - else if (InstallDirectoryAddedToPath) - { - // If symlink doesn't exist, check if install directory was added to PATH directly and remove. - RemoveFromPathVariable(std::filesystem::path(Utility::ConvertToUTF16(entry.SymlinkTarget)).parent_path()); - } - } - else if (fileType == PortableFileType::Symlink && Filesystem::SymlinkExists(filePath)) - { - AICLI_LOG(CLI, Info, << "Deleting portable symlink at: " << filePath); - std::filesystem::remove(filePath); - } - else if (fileType == PortableFileType::Directory && std::filesystem::exists(filePath)) - { - AICLI_LOG(CLI, Info, << "Removing directory at " << filePath); - std::filesystem::remove_all(filePath); - } - } - - // TODO: Optimize by applying the difference between expected and desired state. - void PortableInstaller::ApplyDesiredState() - { - std::filesystem::path existingIndexPath = InstallLocation / GetPortableIndexFileName(); - if (std::filesystem::exists(existingIndexPath)) - { - bool deleteIndex = false; - { - PortableIndex existingIndex = PortableIndex::Open(existingIndexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); - - for (auto expectedEntry : m_expectedEntries) - { - RemoveFile(expectedEntry); - existingIndex.RemovePortableFile(expectedEntry); - } - - deleteIndex = existingIndex.IsEmpty(); - } - - if (deleteIndex) - { - std::filesystem::remove(existingIndexPath); - AICLI_LOG(CLI, Info, << "Portable index deleted: " << existingIndexPath); - } - } - else - { - for (auto expectedEntry : m_expectedEntries) - { - RemoveFile(expectedEntry); - } - } - - // Check if existing install location differs from the target install location for proper cleanup. - if (!TargetInstallLocation.empty() && TargetInstallLocation != InstallLocation) - { - RemoveInstallDirectory(); - } - - if (RecordToIndex) - { - std::filesystem::path targetIndexPath = TargetInstallLocation / GetPortableIndexFileName(); - PortableIndex targetIndex = std::filesystem::exists(targetIndexPath) ? - PortableIndex::Open(targetIndexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : - PortableIndex::CreateNew(targetIndexPath.u8string()); - - for (auto desiredEntry : m_desiredEntries) - { - targetIndex.AddOrUpdatePortableFile(desiredEntry); - InstallFile(desiredEntry); - } - } - else - { - for (auto desiredEntry : m_desiredEntries) - { - InstallFile(desiredEntry); - } - } - } - - bool PortableInstaller::VerifyExpectedState() - { - for (auto entry : m_expectedEntries) - { - if (!VerifyPortableFile(entry)) - { - AICLI_LOG(CLI, Info, << "Portable file has been modified: " << entry.GetFilePath()); - return false; - } - } - - return true; - } - - void PortableInstaller::Install(Workflow::OperationType operation) - { - // If the operation is an install, the ARP entry should be created first so that a catastrophic failure - // leaves the system in a state where an uninstall may be possible - if (operation == Workflow::OperationType::Install) - { - RegisterARPEntry(); - } - - CreateTargetInstallDirectory(); - - ApplyDesiredState(); - - if (!InstallDirectoryAddedToPath) - { - AddToPathVariable(GetPortableLinksLocation(GetScope())); - } - - // If the operation is an upgrade, the ARP entry should be created last so that a catastrophic failure - // leaves the system in a state where an upgrade can be re-attempted - if (operation == Workflow::OperationType::Upgrade) - { - RegisterARPEntry(); - } - } - - void PortableInstaller::Uninstall() - { - ApplyDesiredState(); - - RemoveInstallDirectory(); - - if (!InstallDirectoryAddedToPath) - { - RemoveFromPathVariable(GetPortableLinksLocation(GetScope())); - } - - m_portableARPEntry.Delete(); - AICLI_LOG(CLI, Info, << "PortableARPEntry deleted."); - } - - void PortableInstaller::CreateTargetInstallDirectory() - { - if (std::filesystem::create_directories(TargetInstallLocation)) - { - AICLI_LOG(Core, Info, << "Created target install directory: " << TargetInstallLocation); - CommitToARPEntry(PortableValueName::InstallDirectoryCreated, true); - } - - CommitToARPEntry(PortableValueName::InstallLocation, TargetInstallLocation); - } - - void PortableInstaller::RemoveInstallDirectory() - { - if (std::filesystem::exists(InstallLocation) && InstallDirectoryCreated) - { - if (Purge) - { - m_stream << Resource::String::PurgeInstallDirectory << std::endl; - const auto& removedFilesCount = std::filesystem::remove_all(InstallLocation); - AICLI_LOG(CLI, Info, << "Purged install location directory. Deleted " << removedFilesCount << " files or directories"); - } - else - { - if (std::filesystem::is_empty(InstallLocation)) - { - AICLI_LOG(CLI, Info, << "Removing empty install directory: " << InstallLocation); - std::filesystem::remove(InstallLocation); - } - else - { - AICLI_LOG(CLI, Info, << "Unable to remove install directory as there are remaining files in: " << InstallLocation); - m_stream << Resource::String::FilesRemainInInstallDirectory(Utility::LocIndView{ InstallLocation.u8string() }) << std::endl; - } - } - } - } - - void PortableInstaller::AddToPathVariable(std::filesystem::path value) - { - // Ensure the preferred separator format - value.make_preferred(); - - if (PathVariable(GetScope()).Append(value)) - { - AICLI_LOG(Core, Info, << "Appending portable target directory to PATH registry: " << value); - m_stream << Resource::String::ModifiedPathRequiresShellRestart << std::endl; - } - else - { - AICLI_LOG(CLI, Info, << "Portable target directory already exists in PATH registry: " << value); - } - } - - void PortableInstaller::RemoveFromPathVariable(std::filesystem::path value) - { - if (std::filesystem::exists(value) && !std::filesystem::is_empty(value)) - { - AICLI_LOG(Core, Info, << "Install directory is not empty: " << value); - } - else - { - // Attempt to remove both the original and the preferred format to ensure removal - // Necessary for handling old path values associated with winget-cli#5033 - if (PathVariable(GetScope()).Remove(value) || PathVariable(GetScope()).Remove(value.make_preferred())) - { - InstallDirectoryAddedToPath = false; - AICLI_LOG(CLI, Info, << "Removed target directory from PATH registry: " << value); - } - else - { - AICLI_LOG(CLI, Info, << "Target directory not removed from PATH registry: " << value); - } - } - } - - void PortableInstaller::SetAppsAndFeaturesMetadata(const Manifest::Manifest& manifest, const std::vector& entries) - { - AppInstaller::Manifest::AppsAndFeaturesEntry entry; - if (!entries.empty()) - { - entry = entries[0]; - } - - if (entry.DisplayName.empty()) - { - entry.DisplayName = manifest.CurrentLocalization.Get(); - } - if (entry.DisplayVersion.empty()) - { - entry.DisplayVersion = manifest.Version; - } - if (entry.Publisher.empty()) - { - entry.Publisher = manifest.CurrentLocalization.Get(); - } - - DisplayName = entry.DisplayName; - DisplayVersion = entry.DisplayVersion; - Publisher = entry.Publisher; - InstallDate = Utility::GetCurrentDateForARP(); - URLInfoAbout = manifest.CurrentLocalization.Get(); - HelpLink = manifest.CurrentLocalization.Get(); - } - - AppInstaller::Manifest::AppsAndFeaturesEntry PortableInstaller::GetAppsAndFeaturesEntry() - { - Manifest::AppsAndFeaturesEntry entry; - - entry.DisplayName = DisplayName; - entry.Publisher = Publisher; - entry.DisplayVersion = DisplayVersion; - entry.InstallerType = Manifest::InstallerTypeEnum::Portable; - entry.ProductCode = GetProductCode(); - - return entry; - } - - void PortableInstaller::SetExpectedState() - { - const auto& indexPath = InstallLocation / GetPortableIndexFileName(); - - if (std::filesystem::exists(indexPath)) - { - PortableIndex portableIndex = PortableIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); - m_expectedEntries = portableIndex.GetAllPortableFiles(); - } - else - { - std::filesystem::path targetFullPath = PortableTargetFullPath; - std::filesystem::path symlinkFullPath = PortableSymlinkFullPath; - - // Order matters here so that file entries are removed before symlink entries during uninstall from registry. - // This is to ensure that the directory is fully uninstalled before attempting to remove from PATH registry. - if (!targetFullPath.empty()) - { - m_expectedEntries.emplace_back(std::move(PortableFileEntry::CreateFileEntry({}, targetFullPath, SHA256))); - } - - if (!symlinkFullPath.empty()) - { - m_expectedEntries.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkFullPath, targetFullPath))); - } - } - } - - PortableInstaller::PortableInstaller(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode) : - m_portableARPEntry(PortableARPEntry(scope, arch, productCode)) - { - if (ARPEntryExists()) - { - DisplayName = GetStringValue(PortableValueName::DisplayName); - DisplayVersion = GetStringValue(PortableValueName::DisplayVersion); - HelpLink = GetStringValue(PortableValueName::HelpLink); - InstallDate = GetStringValue(PortableValueName::InstallDate); - Publisher = GetStringValue(PortableValueName::Publisher); - SHA256 = GetStringValue(PortableValueName::SHA256); - URLInfoAbout = GetStringValue(PortableValueName::URLInfoAbout); - UninstallString = GetStringValue(PortableValueName::UninstallString); - WinGetInstallerType = GetStringValue(PortableValueName::WinGetInstallerType); - WinGetPackageIdentifier = GetStringValue(PortableValueName::WinGetPackageIdentifier); - WinGetSourceIdentifier = GetStringValue(PortableValueName::WinGetSourceIdentifier); - InstallLocation = GetPathValue(PortableValueName::InstallLocation); - PortableSymlinkFullPath = GetPathValue(PortableValueName::PortableSymlinkFullPath); - PortableTargetFullPath = GetPathValue(PortableValueName::PortableTargetFullPath); - InstallDirectoryAddedToPath = GetBoolValue(PortableValueName::InstallDirectoryAddedToPath); - InstallDirectoryCreated = GetBoolValue(PortableValueName::InstallDirectoryCreated); - } - - SetExpectedState(); - } - - void PortableInstaller::RegisterARPEntry() - { - CommitToARPEntry(PortableValueName::WinGetPackageIdentifier, WinGetPackageIdentifier); - CommitToARPEntry(PortableValueName::WinGetSourceIdentifier, WinGetSourceIdentifier); - CommitToARPEntry(PortableValueName::UninstallString, "winget uninstall --product-code " + GetProductCode()); - CommitToARPEntry(PortableValueName::WinGetInstallerType, InstallerTypeToString(Manifest::InstallerTypeEnum::Portable)); - CommitToARPEntry(PortableValueName::DisplayName, DisplayName); - CommitToARPEntry(PortableValueName::DisplayVersion, DisplayVersion); - CommitToARPEntry(PortableValueName::Publisher, Publisher); - CommitToARPEntry(PortableValueName::InstallDate, InstallDate); - CommitToARPEntry(PortableValueName::URLInfoAbout, URLInfoAbout); - CommitToARPEntry(PortableValueName::HelpLink, HelpLink); - } - - std::string PortableInstaller::GetStringValue(PortableValueName valueName) - { - if (m_portableARPEntry[valueName].has_value()) - { - return m_portableARPEntry[valueName]->GetValue(); - } - else - { - return {}; - } - } - - std::filesystem::path PortableInstaller::GetPathValue(PortableValueName valueName) - { - if (m_portableARPEntry[valueName].has_value()) - { - return m_portableARPEntry[valueName]->GetValue(); - } - { - return {}; - } - } - - bool PortableInstaller::GetBoolValue(PortableValueName valueName) - { - if (m_portableARPEntry[valueName].has_value()) - { - return m_portableARPEntry[valueName]->GetValue(); - } - else - { - return false; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ExecutionContext.h" +#include "PortableInstaller.h" +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller::Utility; +using namespace AppInstaller::Registry; +using namespace AppInstaller::Registry::Portable; +using namespace AppInstaller::Registry::Environment; +using namespace AppInstaller::Repository; +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Repository::Microsoft::Schema; +using namespace AppInstaller::CLI::Workflow; + +namespace AppInstaller::CLI::Portable +{ + std::filesystem::path GetPortableLinksLocation(Manifest::ScopeEnum scope) + { + if (scope == Manifest::ScopeEnum::Machine) + { + return Runtime::GetPathTo(Runtime::PathName::PortableLinksMachineLocation); + } + else + { + return Runtime::GetPathTo(Runtime::PathName::PortableLinksUserLocation); + } + } + + std::filesystem::path GetPortableInstallRoot(Manifest::ScopeEnum scope, Utility::Architecture arch) + { + if (scope == Manifest::ScopeEnum::Machine) + { + if (arch == Utility::Architecture::X86) + { + return Runtime::GetPathTo(Runtime::PathName::PortablePackageMachineRootX86); + } + else + { + return Runtime::GetPathTo(Runtime::PathName::PortablePackageMachineRoot); + } + } + else + { + return Runtime::GetPathTo(Runtime::PathName::PortablePackageUserRoot); + } + } + + bool VerifyPortableFile(AppInstaller::Portable::PortableFileEntry& entry) + { + std::filesystem::path filePath = entry.GetFilePath(); + PortableFileType fileType = entry.FileType; + + if (fileType == PortableFileType::File) + { + if (std::filesystem::exists(filePath)) + { + SHA256::HashBuffer fileHash = SHA256::ComputeHashFromFile(filePath); + if (!SHA256::AreEqual(fileHash, SHA256::ConvertToBytes(entry.SHA256))) + { + AICLI_LOG(CLI, Warning, << "File hash does not match ARP Entry. Expected: " << entry.SHA256 << " Actual: " << SHA256::ConvertToString(fileHash)); + return false; + } + } + } + else if (fileType == PortableFileType::Symlink) + { + std::filesystem::path symlinkTargetPath{ AppInstaller::Utility::ConvertToUTF16(entry.SymlinkTarget) }; + if (Filesystem::SymlinkExists(filePath) && !Filesystem::VerifySymlink(filePath, symlinkTargetPath)) + { + AICLI_LOG(CLI, Warning, << "Symlink target does not match ARP Entry. Expected: " << symlinkTargetPath << " Actual: " << std::filesystem::read_symlink(filePath)); + return false; + } + } + + return true; + } + + void PortableInstaller::InstallFile(AppInstaller::Portable::PortableFileEntry& entry) + { + PortableFileType fileType = entry.FileType; + std::filesystem::path filePath = entry.GetFilePath(); + + if (entry.FileType == PortableFileType::File) + { + if (std::filesystem::exists(filePath)) + { + AICLI_LOG(Core, Info, << "Removing existing portable file at: " << filePath); + std::filesystem::remove(filePath); + } + + AICLI_LOG(Core, Info, << "Moving portable exe to: " << filePath); + + if (!RecordToIndex) + { + CommitToARPEntry(PortableValueName::PortableTargetFullPath, filePath); + CommitToARPEntry(PortableValueName::SHA256, entry.SHA256); + } + + Filesystem::RenameFile(entry.CurrentPath, filePath); + } + else if (fileType == PortableFileType::Directory) + { + if (Filesystem::IsSameVolume(entry.CurrentPath, filePath)) + { + AICLI_LOG(Core, Info, << "Renaming directory to: " << filePath); + Filesystem::RenameFile(entry.CurrentPath, filePath); + } + else + { + // Copy directory instead of renaming as there is a known issue with renaming across drives. + AICLI_LOG(Core, Info, << "Copying directory to: " << filePath); + std::filesystem::copy(entry.CurrentPath, filePath, std::filesystem::copy_options::overwrite_existing | std::filesystem::copy_options::recursive); + } + } + else if (entry.FileType == PortableFileType::Symlink) + { + std::filesystem::path symlinkTargetPath{ Utility::ConvertToUTF16(entry.SymlinkTarget) }; + + if (BinariesDependOnPath && !InstallDirectoryAddedToPath) + { + // Scenario indicated by 'ArchiveBinariesDependOnPath' manifest entry. + // Skip symlink creation for portables dependent on binaries that require the install directory to be added to PATH. + std::filesystem::path installDirectory = symlinkTargetPath.parent_path(); + AddToPathVariable(installDirectory); + AICLI_LOG(Core, Info, << "Install directory added to PATH: " << installDirectory); + CommitToARPEntry(PortableValueName::InstallDirectoryAddedToPath, InstallDirectoryAddedToPath = true); + } + else if (!InstallDirectoryAddedToPath) + { + std::filesystem::file_status status = std::filesystem::status(filePath); + if (std::filesystem::is_directory(status)) + { + AICLI_LOG(CLI, Info, << "Unable to create symlink. '" << filePath << "points to an existing directory."); + THROW_HR(APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY); + } + + if (!RecordToIndex) + { + CommitToARPEntry(PortableValueName::PortableSymlinkFullPath, filePath); + } + + if (std::filesystem::remove(filePath)) + { + AICLI_LOG(CLI, Info, << "Removed existing file at " << filePath); + m_stream << Resource::String::OverwritingExistingFileAtMessage(Utility::LocIndView{ filePath.u8string() }) << std::endl; + } + + if (Filesystem::CreateSymlink(symlinkTargetPath, filePath)) + { + AICLI_LOG(Core, Info, << "Symlink created at: " << filePath << " with target path: " << symlinkTargetPath); + } + else + { + // If symlink creation fails, resort to adding the package directory to PATH. + AICLI_LOG(Core, Info, << "Failed to create symlink at: " << filePath); + AddToPathVariable(symlinkTargetPath.parent_path()); + CommitToARPEntry(PortableValueName::InstallDirectoryAddedToPath, InstallDirectoryAddedToPath = true); + } + } + m_stream << Resource::String::PortableAliasAdded << ' ' << filePath.stem() << std::endl; + } + } + + void PortableInstaller::RemoveFile(AppInstaller::Portable::PortableFileEntry& entry) + { + const auto& filePath = entry.GetFilePath(); + PortableFileType fileType = entry.FileType; + + if (fileType == PortableFileType::File && std::filesystem::exists(filePath)) + { + AICLI_LOG(CLI, Info, << "Deleting portable exe at: " << filePath); + std::filesystem::remove(filePath); + } + else if (fileType == PortableFileType::Symlink) + { + if (Filesystem::SymlinkExists(filePath)) + { + AICLI_LOG(CLI, Info, << "Deleting portable symlink at: " << filePath); + std::filesystem::remove(filePath); + } + else if (InstallDirectoryAddedToPath) + { + // If symlink doesn't exist, check if install directory was added to PATH directly and remove. + RemoveFromPathVariable(std::filesystem::path(Utility::ConvertToUTF16(entry.SymlinkTarget)).parent_path()); + } + } + else if (fileType == PortableFileType::Symlink && Filesystem::SymlinkExists(filePath)) + { + AICLI_LOG(CLI, Info, << "Deleting portable symlink at: " << filePath); + std::filesystem::remove(filePath); + } + else if (fileType == PortableFileType::Directory && std::filesystem::exists(filePath)) + { + AICLI_LOG(CLI, Info, << "Removing directory at " << filePath); + std::filesystem::remove_all(filePath); + } + } + + // TODO: Optimize by applying the difference between expected and desired state. + void PortableInstaller::ApplyDesiredState() + { + std::filesystem::path existingIndexPath = InstallLocation / GetPortableIndexFileName(); + if (std::filesystem::exists(existingIndexPath)) + { + bool deleteIndex = false; + { + PortableIndex existingIndex = PortableIndex::Open(existingIndexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); + + for (auto expectedEntry : m_expectedEntries) + { + RemoveFile(expectedEntry); + existingIndex.RemovePortableFile(expectedEntry); + } + + deleteIndex = existingIndex.IsEmpty(); + } + + if (deleteIndex) + { + std::filesystem::remove(existingIndexPath); + AICLI_LOG(CLI, Info, << "Portable index deleted: " << existingIndexPath); + } + } + else + { + for (auto expectedEntry : m_expectedEntries) + { + RemoveFile(expectedEntry); + } + } + + // Check if existing install location differs from the target install location for proper cleanup. + if (!TargetInstallLocation.empty() && TargetInstallLocation != InstallLocation) + { + RemoveInstallDirectory(); + } + + if (RecordToIndex) + { + std::filesystem::path targetIndexPath = TargetInstallLocation / GetPortableIndexFileName(); + PortableIndex targetIndex = std::filesystem::exists(targetIndexPath) ? + PortableIndex::Open(targetIndexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite) : + PortableIndex::CreateNew(targetIndexPath.u8string()); + + for (auto desiredEntry : m_desiredEntries) + { + targetIndex.AddOrUpdatePortableFile(desiredEntry); + InstallFile(desiredEntry); + } + } + else + { + for (auto desiredEntry : m_desiredEntries) + { + InstallFile(desiredEntry); + } + } + } + + bool PortableInstaller::VerifyExpectedState() + { + for (auto entry : m_expectedEntries) + { + if (!VerifyPortableFile(entry)) + { + AICLI_LOG(CLI, Info, << "Portable file has been modified: " << entry.GetFilePath()); + return false; + } + } + + return true; + } + + void PortableInstaller::Install(Workflow::OperationType operation) + { + // If the operation is an install, the ARP entry should be created first so that a catastrophic failure + // leaves the system in a state where an uninstall may be possible + if (operation == Workflow::OperationType::Install) + { + RegisterARPEntry(); + } + + CreateTargetInstallDirectory(); + + ApplyDesiredState(); + + if (!InstallDirectoryAddedToPath) + { + AddToPathVariable(GetPortableLinksLocation(GetScope())); + } + + // If the operation is an upgrade, the ARP entry should be created last so that a catastrophic failure + // leaves the system in a state where an upgrade can be re-attempted + if (operation == Workflow::OperationType::Upgrade) + { + RegisterARPEntry(); + } + } + + void PortableInstaller::Uninstall() + { + ApplyDesiredState(); + + RemoveInstallDirectory(); + + if (!InstallDirectoryAddedToPath) + { + RemoveFromPathVariable(GetPortableLinksLocation(GetScope())); + } + + m_portableARPEntry.Delete(); + AICLI_LOG(CLI, Info, << "PortableARPEntry deleted."); + } + + void PortableInstaller::CreateTargetInstallDirectory() + { + if (std::filesystem::create_directories(TargetInstallLocation)) + { + AICLI_LOG(Core, Info, << "Created target install directory: " << TargetInstallLocation); + CommitToARPEntry(PortableValueName::InstallDirectoryCreated, true); + } + + CommitToARPEntry(PortableValueName::InstallLocation, TargetInstallLocation); + } + + void PortableInstaller::RemoveInstallDirectory() + { + if (std::filesystem::exists(InstallLocation) && InstallDirectoryCreated) + { + if (Purge) + { + m_stream << Resource::String::PurgeInstallDirectory << std::endl; + const auto& removedFilesCount = std::filesystem::remove_all(InstallLocation); + AICLI_LOG(CLI, Info, << "Purged install location directory. Deleted " << removedFilesCount << " files or directories"); + } + else + { + if (std::filesystem::is_empty(InstallLocation)) + { + AICLI_LOG(CLI, Info, << "Removing empty install directory: " << InstallLocation); + std::filesystem::remove(InstallLocation); + } + else + { + AICLI_LOG(CLI, Info, << "Unable to remove install directory as there are remaining files in: " << InstallLocation); + m_stream << Resource::String::FilesRemainInInstallDirectory(Utility::LocIndView{ InstallLocation.u8string() }) << std::endl; + } + } + } + } + + void PortableInstaller::AddToPathVariable(std::filesystem::path value) + { + // Ensure the preferred separator format + value.make_preferred(); + + if (PathVariable(GetScope()).Append(value)) + { + AICLI_LOG(Core, Info, << "Appending portable target directory to PATH registry: " << value); + m_stream << Resource::String::ModifiedPathRequiresShellRestart << std::endl; + } + else + { + AICLI_LOG(CLI, Info, << "Portable target directory already exists in PATH registry: " << value); + } + } + + void PortableInstaller::RemoveFromPathVariable(std::filesystem::path value) + { + if (std::filesystem::exists(value) && !std::filesystem::is_empty(value)) + { + AICLI_LOG(Core, Info, << "Install directory is not empty: " << value); + } + else + { + // Attempt to remove both the original and the preferred format to ensure removal + // Necessary for handling old path values associated with winget-cli#5033 + if (PathVariable(GetScope()).Remove(value) || PathVariable(GetScope()).Remove(value.make_preferred())) + { + InstallDirectoryAddedToPath = false; + AICLI_LOG(CLI, Info, << "Removed target directory from PATH registry: " << value); + } + else + { + AICLI_LOG(CLI, Info, << "Target directory not removed from PATH registry: " << value); + } + } + } + + void PortableInstaller::SetAppsAndFeaturesMetadata(const Manifest::Manifest& manifest, const std::vector& entries) + { + AppInstaller::Manifest::AppsAndFeaturesEntry entry; + if (!entries.empty()) + { + entry = entries[0]; + } + + if (entry.DisplayName.empty()) + { + entry.DisplayName = manifest.CurrentLocalization.Get(); + } + if (entry.DisplayVersion.empty()) + { + entry.DisplayVersion = manifest.Version; + } + if (entry.Publisher.empty()) + { + entry.Publisher = manifest.CurrentLocalization.Get(); + } + + DisplayName = entry.DisplayName; + DisplayVersion = entry.DisplayVersion; + Publisher = entry.Publisher; + InstallDate = Utility::GetCurrentDateForARP(); + URLInfoAbout = manifest.CurrentLocalization.Get(); + HelpLink = manifest.CurrentLocalization.Get(); + } + + AppInstaller::Manifest::AppsAndFeaturesEntry PortableInstaller::GetAppsAndFeaturesEntry() + { + Manifest::AppsAndFeaturesEntry entry; + + entry.DisplayName = DisplayName; + entry.Publisher = Publisher; + entry.DisplayVersion = DisplayVersion; + entry.InstallerType = Manifest::InstallerTypeEnum::Portable; + entry.ProductCode = GetProductCode(); + + return entry; + } + + void PortableInstaller::SetExpectedState() + { + const auto& indexPath = InstallLocation / GetPortableIndexFileName(); + + if (std::filesystem::exists(indexPath)) + { + PortableIndex portableIndex = PortableIndex::Open(indexPath.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); + m_expectedEntries = portableIndex.GetAllPortableFiles(); + } + else + { + std::filesystem::path targetFullPath = PortableTargetFullPath; + std::filesystem::path symlinkFullPath = PortableSymlinkFullPath; + + // Order matters here so that file entries are removed before symlink entries during uninstall from registry. + // This is to ensure that the directory is fully uninstalled before attempting to remove from PATH registry. + if (!targetFullPath.empty()) + { + m_expectedEntries.emplace_back(std::move(PortableFileEntry::CreateFileEntry({}, targetFullPath, SHA256))); + } + + if (!symlinkFullPath.empty()) + { + m_expectedEntries.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkFullPath, targetFullPath))); + } + } + } + + PortableInstaller::PortableInstaller(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode) : + m_portableARPEntry(PortableARPEntry(scope, arch, productCode)) + { + if (ARPEntryExists()) + { + DisplayName = GetStringValue(PortableValueName::DisplayName); + DisplayVersion = GetStringValue(PortableValueName::DisplayVersion); + HelpLink = GetStringValue(PortableValueName::HelpLink); + InstallDate = GetStringValue(PortableValueName::InstallDate); + Publisher = GetStringValue(PortableValueName::Publisher); + SHA256 = GetStringValue(PortableValueName::SHA256); + URLInfoAbout = GetStringValue(PortableValueName::URLInfoAbout); + UninstallString = GetStringValue(PortableValueName::UninstallString); + WinGetInstallerType = GetStringValue(PortableValueName::WinGetInstallerType); + WinGetPackageIdentifier = GetStringValue(PortableValueName::WinGetPackageIdentifier); + WinGetSourceIdentifier = GetStringValue(PortableValueName::WinGetSourceIdentifier); + InstallLocation = GetPathValue(PortableValueName::InstallLocation); + PortableSymlinkFullPath = GetPathValue(PortableValueName::PortableSymlinkFullPath); + PortableTargetFullPath = GetPathValue(PortableValueName::PortableTargetFullPath); + InstallDirectoryAddedToPath = GetBoolValue(PortableValueName::InstallDirectoryAddedToPath); + InstallDirectoryCreated = GetBoolValue(PortableValueName::InstallDirectoryCreated); + } + + SetExpectedState(); + } + + void PortableInstaller::RegisterARPEntry() + { + CommitToARPEntry(PortableValueName::WinGetPackageIdentifier, WinGetPackageIdentifier); + CommitToARPEntry(PortableValueName::WinGetSourceIdentifier, WinGetSourceIdentifier); + CommitToARPEntry(PortableValueName::UninstallString, "winget uninstall --product-code " + GetProductCode()); + CommitToARPEntry(PortableValueName::WinGetInstallerType, InstallerTypeToString(Manifest::InstallerTypeEnum::Portable)); + CommitToARPEntry(PortableValueName::DisplayName, DisplayName); + CommitToARPEntry(PortableValueName::DisplayVersion, DisplayVersion); + CommitToARPEntry(PortableValueName::Publisher, Publisher); + CommitToARPEntry(PortableValueName::InstallDate, InstallDate); + CommitToARPEntry(PortableValueName::URLInfoAbout, URLInfoAbout); + CommitToARPEntry(PortableValueName::HelpLink, HelpLink); + } + + std::string PortableInstaller::GetStringValue(PortableValueName valueName) + { + if (m_portableARPEntry[valueName].has_value()) + { + return m_portableARPEntry[valueName]->GetValue(); + } + else + { + return {}; + } + } + + std::filesystem::path PortableInstaller::GetPathValue(PortableValueName valueName) + { + if (m_portableARPEntry[valueName].has_value()) + { + return m_portableARPEntry[valueName]->GetValue(); + } + { + return {}; + } + } + + bool PortableInstaller::GetBoolValue(PortableValueName valueName) + { + if (m_portableARPEntry[valueName].has_value()) + { + return m_portableARPEntry[valueName]->GetValue(); + } + else + { + return false; + } + } +} diff --git a/src/AppInstallerCLICore/PortableInstaller.h b/src/AppInstallerCLICore/PortableInstaller.h index affdcc8756..9f282efcd0 100644 --- a/src/AppInstallerCLICore/PortableInstaller.h +++ b/src/AppInstallerCLICore/PortableInstaller.h @@ -1,119 +1,119 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winget/PortableARPEntry.h" -#include "winget/PortableFileEntry.h" -#include -#include - -using namespace AppInstaller::Registry::Portable; - -namespace AppInstaller::CLI::Portable -{ - std::filesystem::path GetPortableLinksLocation(Manifest::ScopeEnum scope); - - std::filesystem::path GetPortableInstallRoot(Manifest::ScopeEnum scope, Utility::Architecture arch); - - // Object representation of the metadata and functionality required for installing a Portable package. - struct PortableInstaller - { - // These values are initialized based on the values from the entry in ARP - std::string DisplayName; - std::string DisplayVersion; - std::string HelpLink; - std::string InstallDate; - std::filesystem::path InstallLocation; - std::filesystem::path PortableSymlinkFullPath; - std::filesystem::path PortableTargetFullPath; - std::string Publisher; - std::string SHA256; - std::string URLInfoAbout; - std::string UninstallString; - std::string WinGetInstallerType; - std::string WinGetPackageIdentifier; - std::string WinGetSourceIdentifier; - bool InstallDirectoryCreated = false; - bool BinariesDependOnPath = false; - // If we fail to create a symlink, add install directory to PATH variable - bool InstallDirectoryAddedToPath = false; - - bool IsUpdate = false; - bool Purge = false; - bool RecordToIndex = false; - - // This is the incoming target install location determined from the context args. - std::filesystem::path TargetInstallLocation; - - PortableInstaller(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode); - - bool VerifyExpectedState(); - - void SetDesiredState(const std::vector& desiredEntries) - { - m_desiredEntries = desiredEntries; - }; - - void PrepareForCleanUp() - { - m_expectedEntries = m_desiredEntries; - m_desiredEntries = {}; - } - - void Install(AppInstaller::CLI::Workflow::OperationType operation = Workflow::OperationType::Install); - - void Uninstall(); - - template - void CommitToARPEntry(PortableValueName valueName, T value) - { - m_portableARPEntry.SetValue(valueName, value); - } - - std::filesystem::path GetPortableIndexFileName() - { - return Utility::ConvertToUTF16(GetProductCode() + ".db"); - } - - Manifest::ScopeEnum GetScope() { return m_portableARPEntry.GetScope(); }; - - Utility::Architecture GetArch() { return m_portableARPEntry.GetArchitecture(); }; - - std::string GetProductCode() { return m_portableARPEntry.GetProductCode(); }; - - bool ARPEntryExists() { return m_portableARPEntry.Exists(); }; - - std::string GetOutputMessage() - { - return m_stream.str(); - } - - void SetAppsAndFeaturesMetadata( - const Manifest::Manifest& manifest, - const std::vector& entries); - - AppInstaller::Manifest::AppsAndFeaturesEntry GetAppsAndFeaturesEntry(); - - private: - PortableARPEntry m_portableARPEntry; - std::vector m_desiredEntries; - std::vector m_expectedEntries; - std::stringstream m_stream; - - std::string GetStringValue(PortableValueName valueName); - std::filesystem::path GetPathValue(PortableValueName valueName); - bool GetBoolValue(PortableValueName valueName); - - void SetExpectedState(); - void RegisterARPEntry(); - - void ApplyDesiredState(); - void InstallFile(AppInstaller::Portable::PortableFileEntry& desiredState); - void RemoveFile(AppInstaller::Portable::PortableFileEntry& desiredState); - - void CreateTargetInstallDirectory(); - void RemoveInstallDirectory(); - - void AddToPathVariable(std::filesystem::path value); - void RemoveFromPathVariable(std::filesystem::path value); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winget/PortableARPEntry.h" +#include "winget/PortableFileEntry.h" +#include +#include + +using namespace AppInstaller::Registry::Portable; + +namespace AppInstaller::CLI::Portable +{ + std::filesystem::path GetPortableLinksLocation(Manifest::ScopeEnum scope); + + std::filesystem::path GetPortableInstallRoot(Manifest::ScopeEnum scope, Utility::Architecture arch); + + // Object representation of the metadata and functionality required for installing a Portable package. + struct PortableInstaller + { + // These values are initialized based on the values from the entry in ARP + std::string DisplayName; + std::string DisplayVersion; + std::string HelpLink; + std::string InstallDate; + std::filesystem::path InstallLocation; + std::filesystem::path PortableSymlinkFullPath; + std::filesystem::path PortableTargetFullPath; + std::string Publisher; + std::string SHA256; + std::string URLInfoAbout; + std::string UninstallString; + std::string WinGetInstallerType; + std::string WinGetPackageIdentifier; + std::string WinGetSourceIdentifier; + bool InstallDirectoryCreated = false; + bool BinariesDependOnPath = false; + // If we fail to create a symlink, add install directory to PATH variable + bool InstallDirectoryAddedToPath = false; + + bool IsUpdate = false; + bool Purge = false; + bool RecordToIndex = false; + + // This is the incoming target install location determined from the context args. + std::filesystem::path TargetInstallLocation; + + PortableInstaller(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode); + + bool VerifyExpectedState(); + + void SetDesiredState(const std::vector& desiredEntries) + { + m_desiredEntries = desiredEntries; + }; + + void PrepareForCleanUp() + { + m_expectedEntries = m_desiredEntries; + m_desiredEntries = {}; + } + + void Install(AppInstaller::CLI::Workflow::OperationType operation = Workflow::OperationType::Install); + + void Uninstall(); + + template + void CommitToARPEntry(PortableValueName valueName, T value) + { + m_portableARPEntry.SetValue(valueName, value); + } + + std::filesystem::path GetPortableIndexFileName() + { + return Utility::ConvertToUTF16(GetProductCode() + ".db"); + } + + Manifest::ScopeEnum GetScope() { return m_portableARPEntry.GetScope(); }; + + Utility::Architecture GetArch() { return m_portableARPEntry.GetArchitecture(); }; + + std::string GetProductCode() { return m_portableARPEntry.GetProductCode(); }; + + bool ARPEntryExists() { return m_portableARPEntry.Exists(); }; + + std::string GetOutputMessage() + { + return m_stream.str(); + } + + void SetAppsAndFeaturesMetadata( + const Manifest::Manifest& manifest, + const std::vector& entries); + + AppInstaller::Manifest::AppsAndFeaturesEntry GetAppsAndFeaturesEntry(); + + private: + PortableARPEntry m_portableARPEntry; + std::vector m_desiredEntries; + std::vector m_expectedEntries; + std::stringstream m_stream; + + std::string GetStringValue(PortableValueName valueName); + std::filesystem::path GetPathValue(PortableValueName valueName); + bool GetBoolValue(PortableValueName valueName); + + void SetExpectedState(); + void RegisterARPEntry(); + + void ApplyDesiredState(); + void InstallFile(AppInstaller::Portable::PortableFileEntry& desiredState); + void RemoveFile(AppInstaller::Portable::PortableFileEntry& desiredState); + + void CreateTargetInstallDirectory(); + void RemoveInstallDirectory(); + + void AddToPathVariable(std::filesystem::path value); + void RemoveFromPathVariable(std::filesystem::path value); + }; +} diff --git a/src/AppInstallerCLICore/Public/AppInstallerCLICore.h b/src/AppInstallerCLICore/Public/AppInstallerCLICore.h index 9353b3beef..fa1d41dfd1 100644 --- a/src/AppInstallerCLICore/Public/AppInstallerCLICore.h +++ b/src/AppInstallerCLICore/Public/AppInstallerCLICore.h @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -namespace AppInstaller::CLI -{ - // The core function to act against command line input. - int CoreMain(int argc, wchar_t const** argv); - - // Initializes the Windows Package Manager COM server. - void ServerInitialize(); - - // Initializations for InProc invocation. - void InProcInitialize(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +namespace AppInstaller::CLI +{ + // The core function to act against command line input. + int CoreMain(int argc, wchar_t const** argv); + + // Initializes the Windows Package Manager COM server. + void ServerInitialize(); + + // Initializations for InProc invocation. + void InProcInitialize(); +} diff --git a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h index ed3ec7a34a..3cd3cdc1fb 100644 --- a/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h +++ b/src/AppInstallerCLICore/Public/ConfigurationSetProcessorFactoryRemoting.h @@ -1,70 +1,70 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::CLI::ConfigurationRemoting -{ - // The processor engine being used by the factory. - enum class ProcessorEngine - { - // An unknown processor. - Unknown, - // Uses PowerShell DSC v2. - PowerShell, - // Uses DSC v3. - DSCv3, - }; - - std::wstring_view ToString(ProcessorEngine value); - - // Determines the appropriate processor engine to use for the given configuration set. - ProcessorEngine DetermineProcessorEngine(winrt::Microsoft::Management::Configuration::ConfigurationSet set); - - // Creates a factory in another process - winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateOutOfProcessFactory(ProcessorEngine processorEngine, bool useRunAs = false, const std::string& properties = {}, const std::string& restrictions = {}); - - // Creates a factory that can route configurations to the appropriate internal factory. - winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateDynamicRuntimeFactory(ProcessorEngine processorEngine); - - // The property names used with IMap property semantics of remote factories. - enum class PropertyName - { - // The path to the dsc.exe executable. - // Read / Write - DscExecutablePath, - // The path to the dsc.exe executable, as discovered. - // Read only. - FoundDscExecutablePath, - // Whether to request detailed traces from the processor. - // Read / Write - DiagnosticTraceEnabled, - // Getting this value pumps the state machine to determine the best DSC to use. - // We must respond to the value it returns to properly transition states. - // Read only. - FindDscStateMachine, - // The SHA256 hash of the DscExecutablePath content: file bytes for regular executables, or - // raw reparse data buffer bytes for app execution aliases. - // Must be set alongside DscExecutablePath when a custom processor path is used. - // Read / Write - DscExecutablePathHash, - // Whether DscExecutablePath refers to an app execution alias - // (e.g., a path under %LOCALAPPDATA%\Microsoft\WindowsApps). - // Read / Write - DscExecutablePathIsAlias, - // The SHA256 hash of the FoundDscExecutablePath content. - // Computed alongside FoundDscExecutablePath; available after FoundDscExecutablePath is queried. - // Read only. - FoundDscExecutablePathHash, - // Whether FoundDscExecutablePath refers to an app execution alias. - // Read only. - FoundDscExecutablePathIsAlias, - }; - - // Gets the string for a property name. - winrt::hstring ToHString(PropertyName name); -} - -// Export for use by the out of process factory server to report its initialization. -HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, LPWSTR staticsCallback, LPWSTR completionEventName, DWORD parentProcessId); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::CLI::ConfigurationRemoting +{ + // The processor engine being used by the factory. + enum class ProcessorEngine + { + // An unknown processor. + Unknown, + // Uses PowerShell DSC v2. + PowerShell, + // Uses DSC v3. + DSCv3, + }; + + std::wstring_view ToString(ProcessorEngine value); + + // Determines the appropriate processor engine to use for the given configuration set. + ProcessorEngine DetermineProcessorEngine(winrt::Microsoft::Management::Configuration::ConfigurationSet set); + + // Creates a factory in another process + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateOutOfProcessFactory(ProcessorEngine processorEngine, bool useRunAs = false, const std::string& properties = {}, const std::string& restrictions = {}); + + // Creates a factory that can route configurations to the appropriate internal factory. + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory CreateDynamicRuntimeFactory(ProcessorEngine processorEngine); + + // The property names used with IMap property semantics of remote factories. + enum class PropertyName + { + // The path to the dsc.exe executable. + // Read / Write + DscExecutablePath, + // The path to the dsc.exe executable, as discovered. + // Read only. + FoundDscExecutablePath, + // Whether to request detailed traces from the processor. + // Read / Write + DiagnosticTraceEnabled, + // Getting this value pumps the state machine to determine the best DSC to use. + // We must respond to the value it returns to properly transition states. + // Read only. + FindDscStateMachine, + // The SHA256 hash of the DscExecutablePath content: file bytes for regular executables, or + // raw reparse data buffer bytes for app execution aliases. + // Must be set alongside DscExecutablePath when a custom processor path is used. + // Read / Write + DscExecutablePathHash, + // Whether DscExecutablePath refers to an app execution alias + // (e.g., a path under %LOCALAPPDATA%\Microsoft\WindowsApps). + // Read / Write + DscExecutablePathIsAlias, + // The SHA256 hash of the FoundDscExecutablePath content. + // Computed alongside FoundDscExecutablePath; available after FoundDscExecutablePath is queried. + // Read only. + FoundDscExecutablePathHash, + // Whether FoundDscExecutablePath refers to an app execution alias. + // Read only. + FoundDscExecutablePathIsAlias, + }; + + // Gets the string for a property name. + winrt::hstring ToHString(PropertyName name); +} + +// Export for use by the out of process factory server to report its initialization. +HRESULT WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(HRESULT result, void* factory, LPWSTR staticsCallback, LPWSTR completionEventName, DWORD parentProcessId); diff --git a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h index 2d1f4da53f..47ea63c350 100644 --- a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h +++ b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h @@ -1,115 +1,115 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::ShutdownMonitoring -{ - // Type to contain the CTRL signal and window messages handler. - struct TerminationSignalHandler - { - TerminationSignalHandler(); - - ~TerminationSignalHandler(); - - // Gets the singleton handler. - static std::shared_ptr Instance(); - - // Add a termination listener. - void AddListener(ICancellable* cancellable); - - // Remove a termination listener. - void RemoveListener(ICancellable* cancellable); - - // Add or remove the listener based on `enabled`. - static void EnableListener(bool enabled, ICancellable* cancellable); - - // Gets whether the signal handler is enabled. - static bool Enabled(); - - // Sets whether the signal handler is enabled; the default is true. - // When set to false, the signal handler instance will not create signal listeners when created. - static void Enabled(bool enabled); - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Gets the window handle for the message window. - HWND GetWindowHandle() const; -#endif - - private: - void StartAppShutdown(); - - static BOOL WINAPI StaticCtrlHandlerFunction(DWORD ctrlType); - - static LRESULT WINAPI WindowMessageProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - - BOOL CtrlHandlerFunction(DWORD ctrlType); - - // Terminates the currently attached contexts. - // Returns FALSE if no contexts attached; TRUE otherwise. - BOOL InformListeners(CancelReason reason, bool force); - - void CreateWindowAndStartMessageLoop(); - - std::mutex m_listenersLock; - std::vector m_listeners; - wil::unique_event m_messageQueueReady; - wil::unique_hwnd m_windowHandle; - std::thread m_windowThread; - }; - - // Coordinates shutdown across server components - struct ServerShutdownSynchronization - { - using ShutdownCompleteCallback = void (*)(); - - // Initializes the monitoring system and sets up a callback to be invoked when shutdown is completed. - static void Initialize(ShutdownCompleteCallback callback, bool createTerminationSignalHandler = true); - - // "Interface" for a single component to synchronize with. - struct ComponentSystem - { - // Initiate the shutdown process. - // Components are expected to set flags to prevent any further work from beginning and return as quickly as possible. - void (*BlockNewWork)(CancelReason reason) = nullptr; - - // Components are expected to cancel active or pending work as asynchronously as possible. - void (*BeginShutdown)(CancelReason reason) = nullptr; - - // Components wait until all active and pending work have completed their - void (*Wait)() = nullptr; - }; - - // Adds a component to the system. - static void AddComponent(const ComponentSystem& component); - - // Waits for the shutdown to complete. - static bool WaitForShutdown(std::optional timeout = std::nullopt); - - private: - ServerShutdownSynchronization() = default; - ~ServerShutdownSynchronization(); - - friend TerminationSignalHandler; - - static ServerShutdownSynchronization& Instance(); - - // Runs the actual shutdown process and invokes the callback. - void SynchronizeShutdown(CancelReason reason); - - // Listens for a termination signal. - void Signal(CancelReason reason); - - ShutdownCompleteCallback m_callback = nullptr; - std::mutex m_componentsLock; - std::vector m_components; - std::mutex m_threadLock; - std::thread m_shutdownThread; - wil::slim_event_manual_reset m_shutdownComplete; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::ShutdownMonitoring +{ + // Type to contain the CTRL signal and window messages handler. + struct TerminationSignalHandler + { + TerminationSignalHandler(); + + ~TerminationSignalHandler(); + + // Gets the singleton handler. + static std::shared_ptr Instance(); + + // Add a termination listener. + void AddListener(ICancellable* cancellable); + + // Remove a termination listener. + void RemoveListener(ICancellable* cancellable); + + // Add or remove the listener based on `enabled`. + static void EnableListener(bool enabled, ICancellable* cancellable); + + // Gets whether the signal handler is enabled. + static bool Enabled(); + + // Sets whether the signal handler is enabled; the default is true. + // When set to false, the signal handler instance will not create signal listeners when created. + static void Enabled(bool enabled); + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Gets the window handle for the message window. + HWND GetWindowHandle() const; +#endif + + private: + void StartAppShutdown(); + + static BOOL WINAPI StaticCtrlHandlerFunction(DWORD ctrlType); + + static LRESULT WINAPI WindowMessageProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + + BOOL CtrlHandlerFunction(DWORD ctrlType); + + // Terminates the currently attached contexts. + // Returns FALSE if no contexts attached; TRUE otherwise. + BOOL InformListeners(CancelReason reason, bool force); + + void CreateWindowAndStartMessageLoop(); + + std::mutex m_listenersLock; + std::vector m_listeners; + wil::unique_event m_messageQueueReady; + wil::unique_hwnd m_windowHandle; + std::thread m_windowThread; + }; + + // Coordinates shutdown across server components + struct ServerShutdownSynchronization + { + using ShutdownCompleteCallback = void (*)(); + + // Initializes the monitoring system and sets up a callback to be invoked when shutdown is completed. + static void Initialize(ShutdownCompleteCallback callback, bool createTerminationSignalHandler = true); + + // "Interface" for a single component to synchronize with. + struct ComponentSystem + { + // Initiate the shutdown process. + // Components are expected to set flags to prevent any further work from beginning and return as quickly as possible. + void (*BlockNewWork)(CancelReason reason) = nullptr; + + // Components are expected to cancel active or pending work as asynchronously as possible. + void (*BeginShutdown)(CancelReason reason) = nullptr; + + // Components wait until all active and pending work have completed their + void (*Wait)() = nullptr; + }; + + // Adds a component to the system. + static void AddComponent(const ComponentSystem& component); + + // Waits for the shutdown to complete. + static bool WaitForShutdown(std::optional timeout = std::nullopt); + + private: + ServerShutdownSynchronization() = default; + ~ServerShutdownSynchronization(); + + friend TerminationSignalHandler; + + static ServerShutdownSynchronization& Instance(); + + // Runs the actual shutdown process and invokes the callback. + void SynchronizeShutdown(CancelReason reason); + + // Listens for a termination signal. + void Signal(CancelReason reason); + + ShutdownCompleteCallback m_callback = nullptr; + std::mutex m_componentsLock; + std::vector m_components; + std::mutex m_threadLock; + std::thread m_shutdownThread; + wil::slim_event_manual_reset m_shutdownComplete; + }; +} diff --git a/src/AppInstallerCLICore/Resources.cpp b/src/AppInstallerCLICore/Resources.cpp index 6a08ec05b3..e436d2b364 100644 --- a/src/AppInstallerCLICore/Resources.cpp +++ b/src/AppInstallerCLICore/Resources.cpp @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Resources.h" - -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI::Resource -{ - Utility::LocIndView GetFixedString(FixedString fs) - { - switch (fs) - { - case FixedString::ProductName: return "Windows Package Manager"_liv; - } - - THROW_HR(E_UNEXPECTED); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Resources.h" + +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Resource +{ + Utility::LocIndView GetFixedString(FixedString fs) + { + switch (fs) + { + case FixedString::ProductName: return "Windows Package Manager"_liv; + } + + THROW_HR(E_UNEXPECTED); + } +} diff --git a/src/AppInstallerCLICore/Resources.h b/src/AppInstallerCLICore/Resources.h index a305f82b93..b35f9b3dc9 100644 --- a/src/AppInstallerCLICore/Resources.h +++ b/src/AppInstallerCLICore/Resources.h @@ -1,870 +1,870 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include - -#include - -namespace AppInstaller::CLI::Resource -{ - using AppInstaller::StringResource::StringId; - using AppInstaller::Resource::LocString; - - // Resource string identifiers. - // This list can mostly be generated by the following PowerShell: - // > [xml]$res = Get-Content - // > $res.root.data.name | % { "WINGET_DEFINE_RESOURCE_STRINGID($_);" } - // - struct String - { - WINGET_DEFINE_RESOURCE_STRINGID(AcceptPackageAgreementsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AcceptSourceAgreementsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFlagError); - WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFoundError); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisabled); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisableDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnabled); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingHeader); - WINGET_DEFINE_RESOURCE_STRINGID(AllowRebootArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ArchitectureArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScan); - WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScanOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(ArgumentForSinglePackageProvidedWithMultipleQueries); - WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationAccountArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationModeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommandAliases); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableHeader); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands); - WINGET_DEFINE_RESOURCE_STRINGID(AvailableUpgrades); - WINGET_DEFINE_RESOURCE_STRINGID(BothManifestAndSearchQueryProvided); - WINGET_DEFINE_RESOURCE_STRINGID(Cancelled); - WINGET_DEFINE_RESOURCE_STRINGID(CancellingOperation); - WINGET_DEFINE_RESOURCE_STRINGID(ChannelArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ClientVersionMismatchError); - WINGET_DEFINE_RESOURCE_STRINGID(Command); - WINGET_DEFINE_RESOURCE_STRINGID(CommandArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CommandDoesNotSupportResumeMessage); - WINGET_DEFINE_RESOURCE_STRINGID(CommandLineArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CommandRequiresAdmin); - WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAcceptWarningArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAllUsersElevated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApply); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApplyingUnit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAssert); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDescriptionWasTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportAddingToFile); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailedToGetUnitProcessors); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportingUnit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModule); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModuleFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportSuccessful); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitStart); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToTest); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidType); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidValue); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldMissing); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileEmpty); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalidYAML); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileVersionUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingDetails); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingResourceSettings); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingUnitProcessors); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryEmpty); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryRemoveArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInDesiredState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInform); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInitializing); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackage); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackageFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationLocal); - 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(ConfigurationNoTestRun); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotInDesiredState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPath); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAudit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditHash); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditIsAlias); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditPath); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditSignature); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditUnsigned); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathHashVerificationFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateCompleted); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateInProgress); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStatePending); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationStatusWatchArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSuccessfullyApplied); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuppressPrologueArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnexpectedTestResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitAssertHadNegativeResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedConfigSet); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringGet); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringSet); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringTest); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedInternal); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedPrecondition); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedSystemState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedUnitProcessing); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasDuplicateIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasMissingDependency); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitImportModuleAdmin); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitIsPartOfDependencyCycle); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitManuallySkipped); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleConflict); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleImportFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleNotProvidedWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitMultipleMatches); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNeedsPrereleaseWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFoundInModule); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotInCatalogWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotPublicWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToDependency); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToFailedAssert); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitReturnedInvalidResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSettingConfigRoot); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateCompleted); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateInProgress); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStatePending); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateSkipped); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationValidationFoundNoIssues); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptApply); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptTest); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningSetViewTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningValueTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportAll); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentConflictWithAllError); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentRequiredError); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportModule); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportPackageId); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportResource); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitInstallDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyBegun); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyEnded); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListFirstApplied); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListName); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListOrigin); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListPath); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResult); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResultDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListState); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListUnit); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ConvertInstallFlowToUpgrade); - WINGET_DEFINE_RESOURCE_STRINGID(CorrelationArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); - WINGET_DEFINE_RESOURCE_STRINGID(CustomSwitchesArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowContainsLoop); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowDownload); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowInstall); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoInstallerFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMatches); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMinVersion); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoSuitableInstallerFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowPackageVersionNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceTooManyMatches); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementError); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementExitMessage); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesSkippedMessage); - WINGET_DEFINE_RESOURCE_STRINGID(DependencyArgumentMissing); - WINGET_DEFINE_RESOURCE_STRINGID(DependencySourceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DisableAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(DisabledByGroupPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(DisableInteractivityArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(Done); - WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DownloadDirectoryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(Downloading); - WINGET_DEFINE_RESOURCE_STRINGID(DscCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionGet); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSet); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionWhatIf); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionTest); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionDelete); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionExport); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionValidate); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionResolve); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionAdapter); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSchema); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionManifest); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionExist); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionInDesiredState); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAcceptAgreements); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAdminSettingsSettings); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageId); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageSource); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageVersion); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageScope); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageMatchOption); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode); - WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileSettings); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileAction); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceName); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceArgument); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceType); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceTrustLevel); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceExplicit); - WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourcePriority); - WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorInputArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorNumberIsTooLarge); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileConflictsWithInput); - WINGET_DEFINE_RESOURCE_STRINGID(ErrorRequiresInputOrOutputFile); - WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportedPackageRequiresLicenseAgreement); - WINGET_DEFINE_RESOURCE_STRINGID(ExportIncludeVersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExportSourceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisableMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisablingMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableArgumentError); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnablingMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesNotEnabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(ExternalDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ExtractingArchive); - WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeature); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToInitiateReboot); - WINGET_DEFINE_RESOURCE_STRINGID(FailedToRefreshPathWarning); - WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledByAdminSettingMessage); - WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesDisabled); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesEnabled); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesFeature); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesLink); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessage); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByBuild); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesProperty); - WINGET_DEFINE_RESOURCE_STRINGID(FeaturesStatus); - WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FileNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); - WINGET_DEFINE_RESOURCE_STRINGID(FontAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(FontCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontFace); - WINGET_DEFINE_RESOURCE_STRINGID(FontFaces); - WINGET_DEFINE_RESOURCE_STRINGID(FontFamily); - WINGET_DEFINE_RESOURCE_STRINGID(FontFamilyNameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontFileNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(FontDetailsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontFilePaths); - WINGET_DEFINE_RESOURCE_STRINGID(FontInstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(FontPackage); - WINGET_DEFINE_RESOURCE_STRINGID(FontRollbackFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatus); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatusCorrupt); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatusOK); - WINGET_DEFINE_RESOURCE_STRINGID(FontStatusUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(FontTitle); - WINGET_DEFINE_RESOURCE_STRINGID(FontUninstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontValidationFailed); - WINGET_DEFINE_RESOURCE_STRINGID(FontVersion); - WINGET_DEFINE_RESOURCE_STRINGID(FontWinGetSupported); - WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(GatedVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(GetManifestResultVersionNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HashOverrideArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableForNonRestSourceWarning); - WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableWithoutSource); - WINGET_DEFINE_RESOURCE_STRINGID(HelpArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(HelpForDetails); - WINGET_DEFINE_RESOURCE_STRINGID(HelpLinkPreamble); - WINGET_DEFINE_RESOURCE_STRINGID(IdArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IgnoreLocalArchiveMalwareScanArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IgnoreResumeLimitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IgnoreWarningsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandReportDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportFileHasInvalidSchema); - WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnorePackageVersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedInListArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownInListArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided); - WINGET_DEFINE_RESOURCE_STRINGID(InitiatingReboot); - WINGET_DEFINE_RESOURCE_STRINGID(InstallAbandoned); - WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer1); - WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer2); - WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimerMSStore); - WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); - WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); - 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); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedVirusScan); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedWithCode); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchAdminBlock); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchError); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerRequiresInstallLocation); - WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); - WINGET_DEFINE_RESOURCE_STRINGID(InstallersRequireInstallLocation); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerZeroByteFile); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeBlockedByPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCancelledByUser); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeContactSupport); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCustomError); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDiskFull); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDowngrade); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeFileInUse); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInstallInProgress); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInsufficientMemory); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInvalidParameter); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeMissingDependency); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeNoNetwork); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUse); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUseByApplication); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootInitiated); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredForInstall); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredToFinish); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeSystemNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall); - WINGET_DEFINE_RESOURCE_STRINGID(InstallFullPackageDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallLocationNotProvided); - WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallStubPackageDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InstallWaitingOnAnother); - WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentSpecifierError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueErrorWithoutValidValues); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentWithoutQueryError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidJsonFile); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidNameError); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidPathToNestedInstaller); - WINGET_DEFINE_RESOURCE_STRINGID(KeyDirectoriesHeader); - WINGET_DEFINE_RESOURCE_STRINGID(LicenseAgreement); - WINGET_DEFINE_RESOURCE_STRINGID(Links); - WINGET_DEFINE_RESOURCE_STRINGID(ListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ListDetailsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(LocaleArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(LocationArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(LogArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(Logs); - WINGET_DEFINE_RESOURCE_STRINGID(MainCopyrightNotice); - WINGET_DEFINE_RESOURCE_STRINGID(MainHomepage); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationFail); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationWarning); - WINGET_DEFINE_RESOURCE_STRINGID(McpCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(McpCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(McpConfigurationPreamble); - WINGET_DEFINE_RESOURCE_STRINGID(MissingArgumentError); - WINGET_DEFINE_RESOURCE_STRINGID(ModifiedPathRequiresShellRestart); - WINGET_DEFINE_RESOURCE_STRINGID(MonikerArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(MsixArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreAppBlocked); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadAuthenticationNotice); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadDependencyPackages); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfo); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfoFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicense); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseForbidden); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMainPackages); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMultiplePackagesNotice); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadNoApplicablePackageFound); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloaded); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashMismatch); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashVerified); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadRenameNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallOrUpdateFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallTryGetEntitlement); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreRepairFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MSStoreStoreClientBlocked); - WINGET_DEFINE_RESOURCE_STRINGID(MultipleExclusiveArgumentsProvided); - WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound); - WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFound); - WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFoundFilteredBySourcePriority); - WINGET_DEFINE_RESOURCE_STRINGID(MultipleUnsupportedNestedInstallersSpecified); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFoundMultiple); - WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); - WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(NoAdminRepairForUserScopePackage); - WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); - WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); - WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledFontFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledPackageFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoPackageFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoPackageSelectionArgumentProvided); - WINGET_DEFINE_RESOURCE_STRINGID(NoPackagesFoundInImportFile); - WINGET_DEFINE_RESOURCE_STRINGID(NoProxyArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NoProgressArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NoRepairInfoFound); - WINGET_DEFINE_RESOURCE_STRINGID(Notes); - WINGET_DEFINE_RESOURCE_STRINGID(NoUninstallInfoFound); - WINGET_DEFINE_RESOURCE_STRINGID(NoUpgradeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(NoVTArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OpenLogsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatch); - WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatchHelp); - WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoSourceDefined); - WINGET_DEFINE_RESOURCE_STRINGID(Options); - WINGET_DEFINE_RESOURCE_STRINGID(OSVersionDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OutputDirectoryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(OverwritingExistingFileAtMessage); - WINGET_DEFINE_RESOURCE_STRINGID(Package); - WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsNotAgreedTo); - WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt); - WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled); - WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(PackageIsPinned); - WINGET_DEFINE_RESOURCE_STRINGID(PackageRequiresDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); - WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinAdded); - WINGET_DEFINE_RESOURCE_STRINGID(PinAlreadyExists); - WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex); - WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist); - WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting); - WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg); - WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledSource); - WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist); - WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinRemovedSuccessfully); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful); - WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); - WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); - WINGET_DEFINE_RESOURCE_STRINGID(PinType); - WINGET_DEFINE_RESOURCE_STRINGID(PinVersion); - WINGET_DEFINE_RESOURCE_STRINGID(PlatformArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(PortableAliasAdded); - WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(PortableInstallFailed); - WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksMachine); - WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksUser); - WINGET_DEFINE_RESOURCE_STRINGID(PortablePackageAlreadyExists); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRegistryCollisionOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot86); - WINGET_DEFINE_RESOURCE_STRINGID(PortableRootUser); - WINGET_DEFINE_RESOURCE_STRINGID(PositionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PreserveArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PressEnterToContinue); - WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); - WINGET_DEFINE_RESOURCE_STRINGID(ProductCodeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PromptForInstallRoot); - WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); - WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); - WINGET_DEFINE_RESOURCE_STRINGID(PromptToProceed); - WINGET_DEFINE_RESOURCE_STRINGID(ProxyArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(PurgeInstallDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverridden); - WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverrideRequired); - WINGET_DEFINE_RESOURCE_STRINGID(RelatedLink); - WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RepairAbandoned); - WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(RepairDifferentInstallTechnology); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFailedWithCode); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowNoMatchingVersion); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowRepairSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowReturnCodeSystemNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowStartingPackageRepair); - WINGET_DEFINE_RESOURCE_STRINGID(RepairOperationNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(ReparsePointsNotSupportedError); - WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityForAgreements); - WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound); - WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); - WINGET_DEFINE_RESOURCE_STRINGID(ReservedFilenameError); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdNotFoundError); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeLimitExceeded); - WINGET_DEFINE_RESOURCE_STRINGID(ResumeStateDataNotFoundError); - WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureError); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorListMatches); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorNoMatches); - WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureWarning); - WINGET_DEFINE_RESOURCE_STRINGID(SearchId); - WINGET_DEFINE_RESOURCE_STRINGID(SearchMatch); - WINGET_DEFINE_RESOURCE_STRINGID(SearchName); - WINGET_DEFINE_RESOURCE_STRINGID(SearchSource); - WINGET_DEFINE_RESOURCE_STRINGID(SearchTruncated); - WINGET_DEFINE_RESOURCE_STRINGID(SearchVersion); - WINGET_DEFINE_RESOURCE_STRINGID(SeeLineAndColumn); - WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingFailed); - WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingSucceeded); - WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); - WINGET_DEFINE_RESOURCE_STRINGID(SettingNameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningField); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningValue); - WINGET_DEFINE_RESOURCE_STRINGID(SettingValueArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowChannel); - WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAgreements); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAuthor); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelChannel); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyright); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyrightUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDocumentation); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelExternalDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallationNotes); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstaller); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerLocale); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerOfflineDistributionSupported); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerProductId); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerReleaseDate); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerSha256); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerType); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicense); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicenseUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelMoniker); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPrivacyUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisher); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherSupportUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPurchaseUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotes); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotesUrl); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelTags); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelVersion); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsFeaturesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsLibrariesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListAvailableUpgrades); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledArchitecture); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocale); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocation); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledScope); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledSource); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstallerCategory); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListLocalIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListPackageFamilyName); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListProductCode); - WINGET_DEFINE_RESOURCE_STRINGID(ShowListUpgradeCode); - WINGET_DEFINE_RESOURCE_STRINGID(ShowVersion); - WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SingleCharAfterDashError); - WINGET_DEFINE_RESOURCE_STRINGID(SortArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SortAscendingArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SortDescendingArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SkipDependenciesArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyMessage); - WINGET_DEFINE_RESOURCE_STRINGID(SkipMicrosoftStorePackageLicenseArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentArg); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentName); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsMatch); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddBegin); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddFailedAuthenticationNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAddOpenSourceFailed); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsMarketMessage); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsNotAgreedTo); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsPrompt); - WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsTitle); - WINGET_DEFINE_RESOURCE_STRINGID(SourceArgArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditExplicitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNewValue); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNoChanges); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOldValue); - WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOne); - WINGET_DEFINE_RESOURCE_STRINGID(SourceExplicitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListAdditionalSource); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListAllowedSource); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListArg); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListData); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListExplicit); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListField); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListIdentifier); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListName); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoneFound); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoSources); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListPriority); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListTrustLevel); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListType); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdated); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdatedNever); - WINGET_DEFINE_RESOURCE_STRINGID(SourceListValue); - WINGET_DEFINE_RESOURCE_STRINGID(SourceNameArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenFailedSuggestion); - WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenPredefinedFailedSuggestion); - WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenWithFailedUpdate); - WINGET_DEFINE_RESOURCE_STRINGID(SourcePriorityArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveAll); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveOne); - WINGET_DEFINE_RESOURCE_STRINGID(SourceRequiresAuthentication); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetAll); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetForceArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetListAndOverridePreamble); - WINGET_DEFINE_RESOURCE_STRINGID(SourceResetOne); - WINGET_DEFINE_RESOURCE_STRINGID(SourceTrustLevelArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceTypeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateAll); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateOne); - WINGET_DEFINE_RESOURCE_STRINGID(StateDisabled); - WINGET_DEFINE_RESOURCE_STRINGID(StateEnabled); - WINGET_DEFINE_RESOURCE_STRINGID(StateHeader); - WINGET_DEFINE_RESOURCE_STRINGID(SystemArchitecture); - WINGET_DEFINE_RESOURCE_STRINGID(TagArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(TargetVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ThankYou); - WINGET_DEFINE_RESOURCE_STRINGID(ThirdPartSoftwareNotices); - WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ToolVersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(TooManyArgError); - WINGET_DEFINE_RESOURCE_STRINGID(TooManyBehaviorsError); - WINGET_DEFINE_RESOURCE_STRINGID(UnableToPurgeInstallDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(Unavailable); - WINGET_DEFINE_RESOURCE_STRINGID(UnexpectedErrorExecutingCommand); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallAbandoned); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallAllVersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandReportDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedDueToMultipleVersions); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedWithCode); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowStartingPackageUninstall); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowUninstallSuccess); - WINGET_DEFINE_RESOURCE_STRINGID(UninstallPreviousArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UnrecognizedCommand); - WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedArgument); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateAllArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFound); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFoundReason); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable); - WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicableReason); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeAvailableForPinned); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByManifest); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByPinCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeInstallTechnologyMismatchCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); - WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); - WINGET_DEFINE_RESOURCE_STRINGID(UriNotWellFormed); - WINGET_DEFINE_RESOURCE_STRINGID(UriSchemeNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(Usage); - WINGET_DEFINE_RESOURCE_STRINGID(UserSettings); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandReportDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandShortDescription); - WINGET_DEFINE_RESOURCE_STRINGID(ValidateManifestArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(VerboseLogsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedIsDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedNotExist); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileSignedMsix); - WINGET_DEFINE_RESOURCE_STRINGID(VerifyPathFailedNotExist); - WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(WaitArgumentDescription); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeatureNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeaturesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsLibrariesDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManager); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManagerPreview); - WINGET_DEFINE_RESOURCE_STRINGID(WindowsStoreTerms); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitBothPackageVersionAndUseLatest); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotConfigured); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotDeclaredAsDependency); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitEmptyContent); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackage); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageMultipleFound); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageSourceOpenFailed); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageVersionNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitKnownSourceConfliction); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRecommendedArg); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRequiredArg); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitPackageVersionSpecifiedWithOnlyOnePackageVersion); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertion); - WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertionForPackage); - WINGET_DEFINE_RESOURCE_STRINGID(WordArgumentDescription); - }; - - // Fixed strings are not localized, but we use a similar system to prevent duplication - enum class FixedString - { - ProductName, - }; - - Utility::LocIndView GetFixedString(FixedString fs); -} - -inline std::ostream& operator<<(std::ostream& out, AppInstaller::CLI::Resource::FixedString fs) -{ - return (out << GetFixedString(fs)); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include + +#include + +namespace AppInstaller::CLI::Resource +{ + using AppInstaller::StringResource::StringId; + using AppInstaller::Resource::LocString; + + // Resource string identifiers. + // This list can mostly be generated by the following PowerShell: + // > [xml]$res = Get-Content + // > $res.root.data.name | % { "WINGET_DEFINE_RESOURCE_STRINGID($_);" } + // + struct String + { + WINGET_DEFINE_RESOURCE_STRINGID(AcceptPackageAgreementsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AcceptSourceAgreementsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFlagError); + WINGET_DEFINE_RESOURCE_STRINGID(AdjoinedNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingDisableDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingEnableDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AdminSettingHeader); + WINGET_DEFINE_RESOURCE_STRINGID(AllowRebootArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ArchitectureArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScan); + WINGET_DEFINE_RESOURCE_STRINGID(ArchiveFailedMalwareScanOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(ArgumentForSinglePackageProvidedWithMultipleQueries); + WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationAccountArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AuthenticationModeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableArguments); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommandAliases); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableCommands); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableHeader); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableOptions); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableSubcommands); + WINGET_DEFINE_RESOURCE_STRINGID(AvailableUpgrades); + WINGET_DEFINE_RESOURCE_STRINGID(BothManifestAndSearchQueryProvided); + WINGET_DEFINE_RESOURCE_STRINGID(Cancelled); + WINGET_DEFINE_RESOURCE_STRINGID(CancellingOperation); + WINGET_DEFINE_RESOURCE_STRINGID(ChannelArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ClientVersionMismatchError); + WINGET_DEFINE_RESOURCE_STRINGID(Command); + WINGET_DEFINE_RESOURCE_STRINGID(CommandArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CommandDoesNotSupportResumeMessage); + WINGET_DEFINE_RESOURCE_STRINGID(CommandLineArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CommandRequiresAdmin); + WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CompleteCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAcceptWarningArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAllUsersElevated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationApplyingUnit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationAssert); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationDescriptionWasTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportAddingToFile); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportFailedToGetUnitProcessors); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportingUnit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModule); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportInstallRequiredModuleFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportSuccessful); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitStart); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationExportUnitFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToGetDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFailedToTest); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidType); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldInvalidValue); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFieldMissing); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileEmpty); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalid); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileInvalidYAML); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationFileVersionUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingDetails); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingResourceSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationGettingUnitProcessors); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryEmpty); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryItemNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationHistoryRemoveArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInDesiredState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInform); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInitializing); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackage); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationInstallDscPackageFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationLocal); + 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(ConfigurationNoTestRun); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationNotInDesiredState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAudit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditHash); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditIsAlias); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditSignature); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathAuditUnsigned); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationProcessorPathHashVerificationFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationReadingConfigFile); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateCompleted); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStatePending); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSetStateUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationStatusWatchArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuccessfullyApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSuccessfullyApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationSuppressPrologueArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnexpectedTestResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitAssertHadNegativeResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedConfigSet); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringGet); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringSet); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedDuringTest); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedInternal); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedPrecondition); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedSystemState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitFailedUnitProcessing); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasDuplicateIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitHasMissingDependency); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitImportModuleAdmin); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitIsPartOfDependencyCycle); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitManuallySkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleConflict); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleImportFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitModuleNotProvidedWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitMultipleMatches); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNeedsPrereleaseWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotFoundInModule); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotInCatalogWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotPublicWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToDependency); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitNotRunDueToFailedAssert); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitReturnedInvalidResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSettingConfigRoot); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateCompleted); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStatePending); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateSkipped); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationUnitStateUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationValidationFoundNoIssues); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWaitingOnAnother); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarning); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptApply); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningPromptTest); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningSetViewTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigurationWarningValueTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportAll); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentConflictWithAllError); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportArgumentRequiredError); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportModule); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportPackageId); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportResource); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureExportUnitInstallDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyBegun); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListApplyEnded); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListFirstApplied); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListName); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListOrigin); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListPath); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResult); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListResultDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListState); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureListUnit); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureTestCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConfigureValidateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ConvertInstallFlowToUpgrade); + WINGET_DEFINE_RESOURCE_STRINGID(CorrelationArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CountArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(CountOutOfBoundsError); + WINGET_DEFINE_RESOURCE_STRINGID(CustomSwitchesArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowContainsLoop); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowDownload); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowInstall); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoInstallerFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMatches); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoMinVersion); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowNoSuitableInstallerFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowPackageVersionNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesFlowSourceTooManyMatches); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementError); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesManagementExitMessage); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesSkippedMessage); + WINGET_DEFINE_RESOURCE_STRINGID(DependencyArgumentMissing); + WINGET_DEFINE_RESOURCE_STRINGID(DependencySourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DisableAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(DisabledByGroupPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(DisableInteractivityArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Done); + WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DownloadCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DownloadDirectoryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Downloading); + WINGET_DEFINE_RESOURCE_STRINGID(DscCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscAdminSettingsResourceLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscPackageResourceLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionGet); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSet); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionWhatIf); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionTest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionDelete); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionExport); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionValidate); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionResolve); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionAdapter); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionSchema); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourceFunctionDescriptionManifest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionExist); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionInDesiredState); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAcceptAgreements); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionAdminSettingsSettings); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageId); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageSource); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageVersion); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageScope); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageMatchOption); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageUseLatest); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionPackageInstallMode); + WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscUserSettingsFileLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileSettings); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionUserSettingsFileAction); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceName); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceArgument); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceType); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceTrustLevel); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourceExplicit); + WINGET_DEFINE_RESOURCE_STRINGID(DscResourcePropertyDescriptionSourcePriority); + WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DscSourceResourceLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(EnableAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(EnableWindowsFeaturesSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(EnablingWindowsFeature); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorInputArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorNumberIsTooLarge); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorOutputFileConflictsWithInput); + WINGET_DEFINE_RESOURCE_STRINGID(ErrorRequiresInputOrOutputFile); + WINGET_DEFINE_RESOURCE_STRINGID(ExactArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExperimentalCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportedPackageRequiresLicenseAgreement); + WINGET_DEFINE_RESOURCE_STRINGID(ExportIncludeVersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExportSourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisableMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesDisablingMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableArgumentError); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnableMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesEnablingMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExtendedFeaturesNotEnabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(ExternalDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ExtractArchiveSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ExtractingArchive); + WINGET_DEFINE_RESOURCE_STRINGID(ExtraPositionalError); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeature); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToEnableWindowsFeatureOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToInitiateReboot); + WINGET_DEFINE_RESOURCE_STRINGID(FailedToRefreshPathWarning); + WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledByAdminSettingMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeatureDisabledMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesFeature); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesLink); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessage); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByBuild); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesMessageDisabledByPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesProperty); + WINGET_DEFINE_RESOURCE_STRINGID(FeaturesStatus); + WINGET_DEFINE_RESOURCE_STRINGID(FileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FileNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(FilesRemainInInstallDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(FlagContainAdjoinedError); + WINGET_DEFINE_RESOURCE_STRINGID(FontAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(FontCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFace); + WINGET_DEFINE_RESOURCE_STRINGID(FontFaces); + WINGET_DEFINE_RESOURCE_STRINGID(FontFamily); + WINGET_DEFINE_RESOURCE_STRINGID(FontFamilyNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFileNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(FontDetailsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontFilePaths); + WINGET_DEFINE_RESOURCE_STRINGID(FontInstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(FontPackage); + WINGET_DEFINE_RESOURCE_STRINGID(FontRollbackFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatus); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatusCorrupt); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatusOK); + WINGET_DEFINE_RESOURCE_STRINGID(FontStatusUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(FontTitle); + WINGET_DEFINE_RESOURCE_STRINGID(FontUninstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontValidationFailed); + WINGET_DEFINE_RESOURCE_STRINGID(FontVersion); + WINGET_DEFINE_RESOURCE_STRINGID(FontWinGetSupported); + WINGET_DEFINE_RESOURCE_STRINGID(ForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(GatedVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(GetManifestResultVersionNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(HashCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HashCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HashOverrideArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableForNonRestSourceWarning); + WINGET_DEFINE_RESOURCE_STRINGID(HeaderArgumentNotApplicableWithoutSource); + WINGET_DEFINE_RESOURCE_STRINGID(HelpArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(HelpForDetails); + WINGET_DEFINE_RESOURCE_STRINGID(HelpLinkPreamble); + WINGET_DEFINE_RESOURCE_STRINGID(IdArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IgnoreLocalArchiveMalwareScanArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IgnoreResumeLimitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IgnoreWarningsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandReportDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ImportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportFileHasInvalidSchema); + WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnorePackageVersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportIgnoreUnavailableArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ImportInstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ImportSourceNotInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncludePinnedInListArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncludeUnknownInListArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(IncompatibleArgumentsProvided); + WINGET_DEFINE_RESOURCE_STRINGID(InitiatingReboot); + WINGET_DEFINE_RESOURCE_STRINGID(InstallAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer1); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimer2); + WINGET_DEFINE_RESOURCE_STRINGID(InstallationDisclaimerMSStore); + WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageNotAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstalledPackageVersionNotAvailable); + 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); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedVirusScan); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedWithCode); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchAdminBlock); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchError); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashMismatchOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerHashVerified); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerLogAvailable); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerProhibitsElevation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerRequiresInstallLocation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallersAbortTerminal); + WINGET_DEFINE_RESOURCE_STRINGID(InstallersRequireInstallLocation); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerZeroByteFile); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowInstallSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowRegistrationDeferred); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeBlockedByPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCancelledByUser); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeContactSupport); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeCustomError); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDiskFull); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeDowngrade); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeFileInUse); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInstallInProgress); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInsufficientMemory); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeInvalidParameter); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeMissingDependency); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeNoNetwork); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUse); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodePackageInUseByApplication); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootInitiated); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredForInstall); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeRebootRequiredToFinish); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowReturnCodeSystemNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFlowStartingPackageInstall); + WINGET_DEFINE_RESOURCE_STRINGID(InstallFullPackageDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallLocationNotProvided); + WINGET_DEFINE_RESOURCE_STRINGID(InstallScopeDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallStubPackageDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InstallWaitingOnAnother); + WINGET_DEFINE_RESOURCE_STRINGID(InteractiveArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidAliasError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentSpecifierError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentValueErrorWithoutValidValues); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidArgumentWithoutQueryError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidJsonFile); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidNameError); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidPathToNestedInstaller); + WINGET_DEFINE_RESOURCE_STRINGID(KeyDirectoriesHeader); + WINGET_DEFINE_RESOURCE_STRINGID(LicenseAgreement); + WINGET_DEFINE_RESOURCE_STRINGID(Links); + WINGET_DEFINE_RESOURCE_STRINGID(ListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ListDetailsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LocaleArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LocationArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(LogArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(Logs); + WINGET_DEFINE_RESOURCE_STRINGID(MainCopyrightNotice); + WINGET_DEFINE_RESOURCE_STRINGID(MainHomepage); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationFail); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(ManifestValidationWarning); + WINGET_DEFINE_RESOURCE_STRINGID(McpCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(McpCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(McpConfigurationPreamble); + WINGET_DEFINE_RESOURCE_STRINGID(MissingArgumentError); + WINGET_DEFINE_RESOURCE_STRINGID(ModifiedPathRequiresShellRestart); + WINGET_DEFINE_RESOURCE_STRINGID(MonikerArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MsixArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreAppBlocked); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadAuthenticationNotice); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadDependencyPackages); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfo); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetDownloadInfoFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicense); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseForbidden); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadGetLicenseSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMainPackages); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadMultiplePackagesNotice); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadNoApplicablePackageFound); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloaded); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageDownloadSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashMismatch); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadPackageHashVerified); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreDownloadRenameNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallOrUpdateFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreInstallTryGetEntitlement); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreRepairFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MSStoreStoreClientBlocked); + WINGET_DEFINE_RESOURCE_STRINGID(MultipleExclusiveArgumentsProvided); + WINGET_DEFINE_RESOURCE_STRINGID(MultipleInstalledPackagesFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiplePackagesFoundFilteredBySourcePriority); + WINGET_DEFINE_RESOURCE_STRINGID(MultipleUnsupportedNestedInstallersSpecified); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQueryPackageNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MultiQuerySearchFoundMultiple); + WINGET_DEFINE_RESOURCE_STRINGID(NameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSpecified); + WINGET_DEFINE_RESOURCE_STRINGID(NestedInstallerNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(NoAdminRepairForUserScopePackage); + WINGET_DEFINE_RESOURCE_STRINGID(NoApplicableInstallers); + WINGET_DEFINE_RESOURCE_STRINGID(NoExperimentalFeaturesMessage); + WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledFontFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoInstalledPackageFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoPackageFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoPackageSelectionArgumentProvided); + WINGET_DEFINE_RESOURCE_STRINGID(NoPackagesFoundInImportFile); + WINGET_DEFINE_RESOURCE_STRINGID(NoProxyArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NoProgressArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NoRepairInfoFound); + WINGET_DEFINE_RESOURCE_STRINGID(Notes); + WINGET_DEFINE_RESOURCE_STRINGID(NoUninstallInfoFound); + WINGET_DEFINE_RESOURCE_STRINGID(NoUpgradeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(NoVTArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OpenLogsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatch); + WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoMatchHelp); + WINGET_DEFINE_RESOURCE_STRINGID(OpenSourceFailedNoSourceDefined); + WINGET_DEFINE_RESOURCE_STRINGID(Options); + WINGET_DEFINE_RESOURCE_STRINGID(OSVersionDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OutputDirectoryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OutputFileArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OverrideArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(OverwritingExistingFileAtMessage); + WINGET_DEFINE_RESOURCE_STRINGID(Package); + WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsNotAgreedTo); + WINGET_DEFINE_RESOURCE_STRINGID(PackageAgreementsPrompt); + WINGET_DEFINE_RESOURCE_STRINGID(PackageAlreadyInstalled); + WINGET_DEFINE_RESOURCE_STRINGID(PackageDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(PackageIsPinned); + WINGET_DEFINE_RESOURCE_STRINGID(PackageRequiresDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(PendingWorkError); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddBlockingArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAddCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinAdded); + WINGET_DEFINE_RESOURCE_STRINGID(PinAlreadyExists); + WINGET_DEFINE_RESOURCE_STRINGID(PinCannotOpenIndex); + WINGET_DEFINE_RESOURCE_STRINGID(PinCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinDoesNotExist); + WINGET_DEFINE_RESOURCE_STRINGID(PinExistsOverwriting); + WINGET_DEFINE_RESOURCE_STRINGID(PinExistsUseForceArg); + WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinInstalledSource); + WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinNoPinsExist); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinRemovedSuccessfully); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetSuccessful); + WINGET_DEFINE_RESOURCE_STRINGID(PinResettingAll); + WINGET_DEFINE_RESOURCE_STRINGID(PinResetUseForceArg); + WINGET_DEFINE_RESOURCE_STRINGID(PinType); + WINGET_DEFINE_RESOURCE_STRINGID(PinVersion); + WINGET_DEFINE_RESOURCE_STRINGID(PlatformArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PoliciesPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(PortableAliasAdded); + WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(PortableHashMismatchOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(PortableInstallFailed); + WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksMachine); + WINGET_DEFINE_RESOURCE_STRINGID(PortableLinksUser); + WINGET_DEFINE_RESOURCE_STRINGID(PortablePackageAlreadyExists); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRegistryCollisionOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRoot86); + WINGET_DEFINE_RESOURCE_STRINGID(PortableRootUser); + WINGET_DEFINE_RESOURCE_STRINGID(PositionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PreserveArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PressEnterToContinue); + WINGET_DEFINE_RESOURCE_STRINGID(PrivacyStatement); + WINGET_DEFINE_RESOURCE_STRINGID(ProductCodeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PromptForInstallRoot); + WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionNo); + WINGET_DEFINE_RESOURCE_STRINGID(PromptOptionYes); + WINGET_DEFINE_RESOURCE_STRINGID(PromptToProceed); + WINGET_DEFINE_RESOURCE_STRINGID(ProxyArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PurgeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(PurgeInstallDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(QueryArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RainbowArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverridden); + WINGET_DEFINE_RESOURCE_STRINGID(RebootRequiredToEnableWindowsFeatureOverrideRequired); + WINGET_DEFINE_RESOURCE_STRINGID(RelatedLink); + WINGET_DEFINE_RESOURCE_STRINGID(RenameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(RepairDifferentInstallTechnology); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFailedWithCode); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowNoMatchingVersion); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowRepairSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowReturnCodeSystemNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(RepairFlowStartingPackageRepair); + WINGET_DEFINE_RESOURCE_STRINGID(RepairOperationNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(ReparsePointsNotSupportedError); + WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityForAgreements); + WINGET_DEFINE_RESOURCE_STRINGID(ReportIdentityFound); + WINGET_DEFINE_RESOURCE_STRINGID(RequiredArgError); + WINGET_DEFINE_RESOURCE_STRINGID(ReservedFilenameError); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAdminSettingSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResetAllAdminSettingsSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeIdNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeLimitExceeded); + WINGET_DEFINE_RESOURCE_STRINGID(ResumeStateDataNotFoundError); + WINGET_DEFINE_RESOURCE_STRINGID(RetroArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureError); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorListMatches); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureErrorNoMatches); + WINGET_DEFINE_RESOURCE_STRINGID(SearchFailureWarning); + WINGET_DEFINE_RESOURCE_STRINGID(SearchId); + WINGET_DEFINE_RESOURCE_STRINGID(SearchMatch); + WINGET_DEFINE_RESOURCE_STRINGID(SearchName); + WINGET_DEFINE_RESOURCE_STRINGID(SearchSource); + WINGET_DEFINE_RESOURCE_STRINGID(SearchTruncated); + WINGET_DEFINE_RESOURCE_STRINGID(SearchVersion); + WINGET_DEFINE_RESOURCE_STRINGID(SeeLineAndColumn); + WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingFailed); + WINGET_DEFINE_RESOURCE_STRINGID(SetAdminSettingSucceeded); + WINGET_DEFINE_RESOURCE_STRINGID(SettingLoadFailure); + WINGET_DEFINE_RESOURCE_STRINGID(SettingNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsSetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningField); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarnings); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningValue); + WINGET_DEFINE_RESOURCE_STRINGID(SettingValueArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowChannel); + WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAgreements); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelAuthor); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelChannel); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyright); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelCopyrightUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelDocumentation); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelExternalDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallationNotes); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstaller); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerLocale); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerOfflineDistributionSupported); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerProductId); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerReleaseDate); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerSha256); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerType); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelInstallerUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicense); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelLicenseUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelMoniker); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPackageUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPrivacyUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisher); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherSupportUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPublisherUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelPurchaseUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotes); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelReleaseNotesUrl); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelTags); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelVersion); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsFeaturesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowLabelWindowsLibrariesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListAvailableUpgrades); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledArchitecture); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocale); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledLocation); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledScope); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstalledSource); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListInstallerCategory); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListLocalIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListPackageFamilyName); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListProductCode); + WINGET_DEFINE_RESOURCE_STRINGID(ShowListUpgradeCode); + WINGET_DEFINE_RESOURCE_STRINGID(ShowVersion); + WINGET_DEFINE_RESOURCE_STRINGID(SilentArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SingleCharAfterDashError); + WINGET_DEFINE_RESOURCE_STRINGID(SortArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SortAscendingArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SortDescendingArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SkipDependenciesArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(DependenciesOnlyMessage); + WINGET_DEFINE_RESOURCE_STRINGID(SkipMicrosoftStorePackageLicenseArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentArg); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsDifferentName); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddAlreadyExistsMatch); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddBegin); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddFailedAuthenticationNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAddOpenSourceFailed); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsMarketMessage); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsNotAgreedTo); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsPrompt); + WINGET_DEFINE_RESOURCE_STRINGID(SourceAgreementsTitle); + WINGET_DEFINE_RESOURCE_STRINGID(SourceArgArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditExplicitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNewValue); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditNoChanges); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOldValue); + WINGET_DEFINE_RESOURCE_STRINGID(SourceEditOne); + WINGET_DEFINE_RESOURCE_STRINGID(SourceExplicitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceExportCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListAdditionalSource); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListAllowedSource); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListArg); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListData); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListExplicit); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListField); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListIdentifier); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListName); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoneFound); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListNoSources); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListPriority); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListTrustLevel); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListType); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdated); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListUpdatedNever); + WINGET_DEFINE_RESOURCE_STRINGID(SourceListValue); + WINGET_DEFINE_RESOURCE_STRINGID(SourceNameArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenFailedSuggestion); + WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenPredefinedFailedSuggestion); + WINGET_DEFINE_RESOURCE_STRINGID(SourceOpenWithFailedUpdate); + WINGET_DEFINE_RESOURCE_STRINGID(SourcePriorityArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveAll); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRemoveOne); + WINGET_DEFINE_RESOURCE_STRINGID(SourceRequiresAuthentication); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetAll); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetForceArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetListAndOverridePreamble); + WINGET_DEFINE_RESOURCE_STRINGID(SourceResetOne); + WINGET_DEFINE_RESOURCE_STRINGID(SourceTrustLevelArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceTypeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateAll); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(SourceUpdateOne); + WINGET_DEFINE_RESOURCE_STRINGID(StateDisabled); + WINGET_DEFINE_RESOURCE_STRINGID(StateEnabled); + WINGET_DEFINE_RESOURCE_STRINGID(StateHeader); + WINGET_DEFINE_RESOURCE_STRINGID(SystemArchitecture); + WINGET_DEFINE_RESOURCE_STRINGID(TagArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(TargetVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ThankYou); + WINGET_DEFINE_RESOURCE_STRINGID(ThirdPartSoftwareNotices); + WINGET_DEFINE_RESOURCE_STRINGID(ToolDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ToolInfoArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ToolVersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(TooManyArgError); + WINGET_DEFINE_RESOURCE_STRINGID(TooManyBehaviorsError); + WINGET_DEFINE_RESOURCE_STRINGID(UnableToPurgeInstallDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(Unavailable); + WINGET_DEFINE_RESOURCE_STRINGID(UnexpectedErrorExecutingCommand); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallAbandoned); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallAllVersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandReportDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedDueToMultipleVersions); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFailedWithCode); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowStartingPackageUninstall); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallFlowUninstallSuccess); + WINGET_DEFINE_RESOURCE_STRINGID(UninstallPreviousArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UnrecognizedCommand); + WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedArgument); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateAllArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFound); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNoPackagesFoundReason); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicable); + WINGET_DEFINE_RESOURCE_STRINGID(UpdateNotApplicableReason); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeAvailableForPinned); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByManifest); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeBlockedByPinCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnology); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeDifferentInstallTechnologyInNewerVersions); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeInstallTechnologyMismatchCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeIsPinned); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradePinnedByUserCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeRequireExplicitCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionCount); + WINGET_DEFINE_RESOURCE_STRINGID(UpgradeUnknownVersionExplanation); + WINGET_DEFINE_RESOURCE_STRINGID(UriNotWellFormed); + WINGET_DEFINE_RESOURCE_STRINGID(UriSchemeNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(Usage); + WINGET_DEFINE_RESOURCE_STRINGID(UserSettings); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandLongDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandReportDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateCommandShortDescription); + WINGET_DEFINE_RESOURCE_STRINGID(ValidateManifestArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VerboseLogsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedIsDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileFailedNotExist); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyFileSignedMsix); + WINGET_DEFINE_RESOURCE_STRINGID(VerifyPathFailedNotExist); + WINGET_DEFINE_RESOURCE_STRINGID(VersionArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(VersionsArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(WaitArgumentDescription); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeatureNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsFeaturesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsLibrariesDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManager); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsPackageManagerPreview); + WINGET_DEFINE_RESOURCE_STRINGID(WindowsStoreTerms); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitBothPackageVersionAndUseLatest); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotConfigured); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitDependencySourceNotDeclaredAsDependency); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitEmptyContent); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackage); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageMultipleFound); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageSourceOpenFailed); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitFailedToValidatePackageVersionNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitKnownSourceConfliction); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRecommendedArg); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitMissingRequiredArg); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitPackageVersionSpecifiedWithOnlyOnePackageVersion); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertion); + WINGET_DEFINE_RESOURCE_STRINGID(WinGetResourceUnitThirdPartySourceAssertionForPackage); + WINGET_DEFINE_RESOURCE_STRINGID(WordArgumentDescription); + }; + + // Fixed strings are not localized, but we use a similar system to prevent duplication + enum class FixedString + { + ProductName, + }; + + Utility::LocIndView GetFixedString(FixedString fs); +} + +inline std::ostream& operator<<(std::ostream& out, AppInstaller::CLI::Resource::FixedString fs) +{ + return (out << GetFixedString(fs)); +} diff --git a/src/AppInstallerCLICore/Search/Search.h b/src/AppInstallerCLICore/Search/Search.h index a16e36cc57..b6a68f28c3 100644 --- a/src/AppInstallerCLICore/Search/Search.h +++ b/src/AppInstallerCLICore/Search/Search.h @@ -1,101 +1,101 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -// TODO: This code is expected to eventually be placed into its own DLL to support the CLI -// and OOP COM server for use by OS integration points. -// For now we will just maintain the ABI with helper C++ wrappers for client use. - -// "C" ABI - -using AICLIString = wchar_t*; -using AICLICString = wchar_t const*; -using RepositoryHandle = void*; -using ApplicationHandle = void*; -using ManifestHandle = void*; - -winrt::hresult aicliGetApplicationById(RepositoryHandle repo, AICLICString id, ApplicationHandle* app); - -winrt::hresult aicliGetManifestByVersion(ApplicationHandle app, AICLICString version, ManifestHandle* man); - -winrt::hresult aicliGetManifestContents(ManifestHandle man, AICLICString* contents); - -void aicliFreeRepository(RepositoryHandle repo); -void aicliFreeApplication(ApplicationHandle app); -void aicliFreeManifest(ManifestHandle manifest); - -// C++ wrapper - -namespace AppInstaller::CLI -{ - namespace details - { - struct RepositoryDeleter - { - void operator()(RepositoryHandle repo) { aicliFreeRepository(repo); } - }; - - struct ApplicationDeleter - { - void operator()(ApplicationHandle app) { aicliFreeApplication(app); } - }; - - struct ManifestDeleter - { - void operator()(ManifestHandle manifest) { aicliFreeManifest(manifest); } - }; - - using unique_repository = std::unique_ptr, RepositoryDeleter>; - using unique_application = std::unique_ptr, ApplicationDeleter>; - using unique_manifest = std::unique_ptr, ManifestDeleter>; - - template - Result CallABIFunc(Func&& f, Args&&... args) - { - Result result{}; - winrt::check_hresult(f(std::forward(args)..., &result)); - return result; - } - } - - struct Manifest - { - Manifest() = default; - Manifest(ManifestHandle mh) : m_man(mh) {} - ~Manifest() = default; - - Manifest(const Manifest&) = delete; - Manifest& operator=(const Manifest&) = delete; - - Manifest(Manifest&&) = default; - Manifest& operator=(Manifest&&) = default; - - AICLICString GetManifestContents() const { return details::CallABIFunc(aicliGetManifestContents, m_man.get()); } - - private: - details::unique_manifest m_man; - }; - - struct Application - { - Application() = default; - Application(ApplicationHandle ah) : m_app(ah) {} - ~Application() = default; - - Application(const Application&) = delete; - Application& operator=(const Application&) = delete; - - Application(Application&&) = default; - Application& operator=(Application&&) = default; - - static Application GetById(AICLICString id) { return details::CallABIFunc(aicliGetApplicationById, nullptr, id); } - - Manifest GetManifestByVersion(AICLICString version) const { return details::CallABIFunc(aicliGetManifestByVersion, m_app.get(), version); } - - private: - details::unique_application m_app; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +// TODO: This code is expected to eventually be placed into its own DLL to support the CLI +// and OOP COM server for use by OS integration points. +// For now we will just maintain the ABI with helper C++ wrappers for client use. + +// "C" ABI + +using AICLIString = wchar_t*; +using AICLICString = wchar_t const*; +using RepositoryHandle = void*; +using ApplicationHandle = void*; +using ManifestHandle = void*; + +winrt::hresult aicliGetApplicationById(RepositoryHandle repo, AICLICString id, ApplicationHandle* app); + +winrt::hresult aicliGetManifestByVersion(ApplicationHandle app, AICLICString version, ManifestHandle* man); + +winrt::hresult aicliGetManifestContents(ManifestHandle man, AICLICString* contents); + +void aicliFreeRepository(RepositoryHandle repo); +void aicliFreeApplication(ApplicationHandle app); +void aicliFreeManifest(ManifestHandle manifest); + +// C++ wrapper + +namespace AppInstaller::CLI +{ + namespace details + { + struct RepositoryDeleter + { + void operator()(RepositoryHandle repo) { aicliFreeRepository(repo); } + }; + + struct ApplicationDeleter + { + void operator()(ApplicationHandle app) { aicliFreeApplication(app); } + }; + + struct ManifestDeleter + { + void operator()(ManifestHandle manifest) { aicliFreeManifest(manifest); } + }; + + using unique_repository = std::unique_ptr, RepositoryDeleter>; + using unique_application = std::unique_ptr, ApplicationDeleter>; + using unique_manifest = std::unique_ptr, ManifestDeleter>; + + template + Result CallABIFunc(Func&& f, Args&&... args) + { + Result result{}; + winrt::check_hresult(f(std::forward(args)..., &result)); + return result; + } + } + + struct Manifest + { + Manifest() = default; + Manifest(ManifestHandle mh) : m_man(mh) {} + ~Manifest() = default; + + Manifest(const Manifest&) = delete; + Manifest& operator=(const Manifest&) = delete; + + Manifest(Manifest&&) = default; + Manifest& operator=(Manifest&&) = default; + + AICLICString GetManifestContents() const { return details::CallABIFunc(aicliGetManifestContents, m_man.get()); } + + private: + details::unique_manifest m_man; + }; + + struct Application + { + Application() = default; + Application(ApplicationHandle ah) : m_app(ah) {} + ~Application() = default; + + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + Application(Application&&) = default; + Application& operator=(Application&&) = default; + + static Application GetById(AICLICString id) { return details::CallABIFunc(aicliGetApplicationById, nullptr, id); } + + Manifest GetManifestByVersion(AICLICString version) const { return details::CallABIFunc(aicliGetManifestByVersion, m_app.get(), version); } + + private: + details::unique_application m_app; + }; +} diff --git a/src/AppInstallerCLICore/ShutdownMonitoring.cpp b/src/AppInstallerCLICore/ShutdownMonitoring.cpp index 655d8814af..319e75a129 100644 --- a/src/AppInstallerCLICore/ShutdownMonitoring.cpp +++ b/src/AppInstallerCLICore/ShutdownMonitoring.cpp @@ -1,405 +1,405 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/ShutdownMonitoring.h" -#include -#include -#include -#include - -using namespace std::chrono_literals; - -namespace AppInstaller::ShutdownMonitoring -{ - static std::atomic_bool s_TerminationSignalHandlerEnabled = true; - - std::shared_ptr TerminationSignalHandler::Instance() - { - struct Singleton : public WinRT::COMStaticStorageBase - { - Singleton() : COMStaticStorageBase(L"WindowsPackageManager.TerminationSignalHandler") {} - }; - - static Singleton s_instance; - return s_instance.Get(); - } - - void TerminationSignalHandler::AddListener(ICancellable* cancellable) - { - std::lock_guard lock{ m_listenersLock }; - - auto itr = std::find(m_listeners.begin(), m_listeners.end(), cancellable); - THROW_HR_IF(E_NOT_VALID_STATE, itr != m_listeners.end()); - m_listeners.push_back(cancellable); - } - - void TerminationSignalHandler::RemoveListener(ICancellable* cancellable) - { - std::lock_guard lock{ m_listenersLock }; - - auto itr = std::find(m_listeners.begin(), m_listeners.end(), cancellable); - if (itr == m_listeners.end()) - { - AICLI_LOG(CLI, Warning, << "TerminationSignalHandler::RemoveListener did not find requested object"); - } - else - { - m_listeners.erase(itr); - } - } - - void TerminationSignalHandler::EnableListener(bool enabled, ICancellable* cancellable) - { - if (enabled) - { - Instance()->AddListener(cancellable); - } - else - { - Instance()->RemoveListener(cancellable); - } - } - - bool TerminationSignalHandler::Enabled() - { - return s_TerminationSignalHandlerEnabled; - } - - void TerminationSignalHandler::Enabled(bool enabled) - { - s_TerminationSignalHandlerEnabled = enabled; - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - HWND TerminationSignalHandler::GetWindowHandle() const - { - return m_windowHandle.get(); - } -#endif - - TerminationSignalHandler::TerminationSignalHandler() - { - if (!s_TerminationSignalHandlerEnabled) - { - AICLI_LOG(CLI, Info, << "TerminationSignalHandler is disabled, skipping creation of signal listeners"); - return; - } - - // Create message only window. - m_messageQueueReady.create(); - m_windowThread = std::thread(&TerminationSignalHandler::CreateWindowAndStartMessageLoop, this); - if (!m_messageQueueReady.wait(100)) - { - AICLI_LOG(CLI, Warning, << "Timeout creating winget window"); - } - - // Set up ctrl-c handler. - LOG_IF_WIN32_BOOL_FALSE(SetConsoleCtrlHandler(StaticCtrlHandlerFunction, TRUE)); - } - - TerminationSignalHandler::~TerminationSignalHandler() - { - SetConsoleCtrlHandler(StaticCtrlHandlerFunction, FALSE); - - // std::thread requires that any managed thread (joinable) be joined or detached before destructing - if (m_windowThread.joinable()) - { - if (m_windowHandle) - { - // Inform the thread that it should stop. - PostMessageW(m_windowHandle.get(), WM_DESTROY, 0, 0); - } - - m_windowThread.join(); - } - } - - void TerminationSignalHandler::StartAppShutdown() - { - AICLI_LOG(CLI, Info, << "Initiating shutdown procedure"); - - // Lifetime manager sends CTRL-C after the WM_QUERYENDSESSION is processed. - // If we disable the CTRL-C handler, the default handler will kill us. - InformListeners(CancelReason::AppShutdown, true); - } - - BOOL WINAPI TerminationSignalHandler::StaticCtrlHandlerFunction(DWORD ctrlType) - { - return Instance()->CtrlHandlerFunction(ctrlType); - } - - LRESULT WINAPI TerminationSignalHandler::WindowMessageProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) - { - switch (uMsg) - { - case WM_QUERYENDSESSION: - AICLI_LOG(CLI, Verbose, << "Received WM_QUERYENDSESSION"); - Instance()->StartAppShutdown(); - return TRUE; - case WM_ENDSESSION: - case WM_CLOSE: - AICLI_LOG(CLI, Verbose, << "Received window message type: " << uMsg); - // We delay as long as needed during the WM_ENDSESSION as we will be terminated on return. - ServerShutdownSynchronization::WaitForShutdown(); - DestroyWindow(hWnd); - break; - case WM_DESTROY: - PostQuitMessage(0); - break; - default: - return DefWindowProc(hWnd, uMsg, wParam, lParam); - } - return FALSE; - } - - BOOL TerminationSignalHandler::CtrlHandlerFunction(DWORD ctrlType) - { - // TODO: Move this to be logged per active context when we have thread static globals - AICLI_LOG(CLI, Info, << "Got CTRL type: " << ctrlType); - - switch (ctrlType) - { - case CTRL_C_EVENT: - case CTRL_BREAK_EVENT: - return InformListeners(CancelReason::CtrlCSignal, false); - // According to MSDN, we should never receive these due to having gdi32/user32 loaded in our process. - // But handle them as a force terminate anyway. - case CTRL_CLOSE_EVENT: - case CTRL_LOGOFF_EVENT: - case CTRL_SHUTDOWN_EVENT: - return InformListeners(CancelReason::CtrlCSignal, true); - default: - return FALSE; - } - } - - // Terminates the currently attached contexts. - // Returns FALSE if no contexts attached; TRUE otherwise. - BOOL TerminationSignalHandler::InformListeners(CancelReason reason, bool force) - { - BOOL result = FALSE; - - { - std::lock_guard lock{ m_listenersLock }; - result = m_listeners.empty() ? FALSE : TRUE; - - for (auto& listener : m_listeners) - { - listener->Cancel(reason, force); - } - } - - // Notify shutdown synchronization as well - ServerShutdownSynchronization::Instance().Signal(reason); - - return result; - } - - void TerminationSignalHandler::CreateWindowAndStartMessageLoop() - { - PCWSTR windowClass = L"wingetWindow"; - HINSTANCE hInstance = GetModuleHandle(NULL); - if (hInstance == NULL) - { - LOG_LAST_ERROR_MSG("Failed getting module handle"); - return; - } - - WNDCLASSEX wcex = {}; - wcex.cbSize = sizeof(wcex); - - wcex.style = CS_NOCLOSE; - wcex.lpfnWndProc = TerminationSignalHandler::WindowMessageProcedure; - wcex.cbClsExtra = 0; - wcex.cbWndExtra = 0; - wcex.hInstance = hInstance; - wcex.lpszClassName = windowClass; - - if (!RegisterClassEx(&wcex)) - { - LOG_LAST_ERROR_MSG("Failed registering window class"); - return; - } - - // Unregister the window class on exiting the thread - auto classUnregister = wil::scope_exit([&]() - { - UnregisterClassW(windowClass, hInstance); - }); - - m_windowHandle = wil::unique_hwnd(CreateWindow( - windowClass, - L"WingetMessageOnlyWindow", - WS_OVERLAPPEDWINDOW, - 0, /* x */ - 0, /* y */ - 0, /* nWidth */ - 0, /* nHeight */ - NULL, /* hWndParent */ - NULL, /* hMenu */ - hInstance, - NULL)); /* lpParam */ - - HWND windowHandle = m_windowHandle.get(); - if (windowHandle == nullptr) - { - LOG_LAST_ERROR_MSG("Failed creating window"); - return; - } - - // We must destroy the window first so that the class unregister can succeed - auto destroyWindow = wil::scope_exit([&]() - { - DestroyWindow(windowHandle); - }); - - ShowWindow(windowHandle, SW_HIDE); - - // Force message queue to be created. - MSG msg; - PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); - m_messageQueueReady.SetEvent(); - - // Message loop, we send WM_DESTROY to terminate it - BOOL getMessageResult; - while ((getMessageResult = GetMessage(&msg, windowHandle, 0, 0)) != 0) - { - if (getMessageResult == -1) - { - LOG_LAST_ERROR(); - break; - } - else if (msg.message == WM_DESTROY) - { - break; - } - else - { - DispatchMessage(&msg); - } - } - } - - void ServerShutdownSynchronization::Initialize(ShutdownCompleteCallback callback, bool createTerminationSignalHandler) - { - Instance().m_callback = callback; - - // Force the creation of the TerminationSignalHandler singleton so that the process can listen for termination signals even if - // it never attempts to run anything that explicitly registers for cancellation callbacks. - if (createTerminationSignalHandler) - { - TerminationSignalHandler::Instance(); - } - } - - void ServerShutdownSynchronization::AddComponent(const ComponentSystem& component) - { - ServerShutdownSynchronization& instance = Instance(); - std::lock_guard lock{ instance.m_componentsLock }; - - for (const auto& item : instance.m_components) - { - if (item.BlockNewWork == component.BlockNewWork || - item.BeginShutdown == component.BeginShutdown || - item.Wait == component.Wait) - { - return; - } - } - - instance.m_components.push_back(component); - } - - bool ServerShutdownSynchronization::WaitForShutdown(std::optional timeout) - { - ServerShutdownSynchronization& instance = Instance(); - - if (timeout) - { - return instance.m_shutdownComplete.wait(timeout.value()); - } - else - { - { - std::lock_guard lock{ instance.m_threadLock }; - if (!instance.m_shutdownThread.joinable()) - { - AICLI_LOG(Core, Warning, << "Attempt to wait for shutdown when shutdown has not been initiated."); - return false; - } - } - - return instance.m_shutdownComplete.wait(); - } - } - - void ServerShutdownSynchronization::Signal(CancelReason reason) - { - std::lock_guard lock{ m_threadLock }; - - if (!m_shutdownThread.joinable()) - { - m_shutdownThread = std::thread(&ServerShutdownSynchronization::SynchronizeShutdown, this, reason); - } - } - - ServerShutdownSynchronization::~ServerShutdownSynchronization() - { - if (m_shutdownThread.joinable()) - { - m_shutdownThread.detach(); - } - } - - ServerShutdownSynchronization& ServerShutdownSynchronization::Instance() - { - static ServerShutdownSynchronization s_instance; - return s_instance; - } - - void ServerShutdownSynchronization::SynchronizeShutdown(CancelReason reason) try - { - auto setShutdownComplete = wil::scope_exit([this]() { this->m_shutdownComplete.SetEvent(); }); - - std::vector components; - { - std::lock_guard lock{ m_componentsLock }; - components = m_components; - } - - AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: BlockNewWork"); - for (const auto& component : components) - { - if (component.BlockNewWork) - { - component.BlockNewWork(reason); - } - } - - AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: BeginShutdown"); - for (const auto& component : components) - { - if (component.BeginShutdown) - { - component.BeginShutdown(reason); - } - } - - AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: Wait"); - for (const auto& component : components) - { - if (component.Wait) - { - component.Wait(); - } - } - - AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: ShutdownCompleteCallback"); - ShutdownCompleteCallback callback = m_callback; - if (callback) - { - callback(); - } - } - CATCH_LOG(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/ShutdownMonitoring.h" +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace AppInstaller::ShutdownMonitoring +{ + static std::atomic_bool s_TerminationSignalHandlerEnabled = true; + + std::shared_ptr TerminationSignalHandler::Instance() + { + struct Singleton : public WinRT::COMStaticStorageBase + { + Singleton() : COMStaticStorageBase(L"WindowsPackageManager.TerminationSignalHandler") {} + }; + + static Singleton s_instance; + return s_instance.Get(); + } + + void TerminationSignalHandler::AddListener(ICancellable* cancellable) + { + std::lock_guard lock{ m_listenersLock }; + + auto itr = std::find(m_listeners.begin(), m_listeners.end(), cancellable); + THROW_HR_IF(E_NOT_VALID_STATE, itr != m_listeners.end()); + m_listeners.push_back(cancellable); + } + + void TerminationSignalHandler::RemoveListener(ICancellable* cancellable) + { + std::lock_guard lock{ m_listenersLock }; + + auto itr = std::find(m_listeners.begin(), m_listeners.end(), cancellable); + if (itr == m_listeners.end()) + { + AICLI_LOG(CLI, Warning, << "TerminationSignalHandler::RemoveListener did not find requested object"); + } + else + { + m_listeners.erase(itr); + } + } + + void TerminationSignalHandler::EnableListener(bool enabled, ICancellable* cancellable) + { + if (enabled) + { + Instance()->AddListener(cancellable); + } + else + { + Instance()->RemoveListener(cancellable); + } + } + + bool TerminationSignalHandler::Enabled() + { + return s_TerminationSignalHandlerEnabled; + } + + void TerminationSignalHandler::Enabled(bool enabled) + { + s_TerminationSignalHandlerEnabled = enabled; + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + HWND TerminationSignalHandler::GetWindowHandle() const + { + return m_windowHandle.get(); + } +#endif + + TerminationSignalHandler::TerminationSignalHandler() + { + if (!s_TerminationSignalHandlerEnabled) + { + AICLI_LOG(CLI, Info, << "TerminationSignalHandler is disabled, skipping creation of signal listeners"); + return; + } + + // Create message only window. + m_messageQueueReady.create(); + m_windowThread = std::thread(&TerminationSignalHandler::CreateWindowAndStartMessageLoop, this); + if (!m_messageQueueReady.wait(100)) + { + AICLI_LOG(CLI, Warning, << "Timeout creating winget window"); + } + + // Set up ctrl-c handler. + LOG_IF_WIN32_BOOL_FALSE(SetConsoleCtrlHandler(StaticCtrlHandlerFunction, TRUE)); + } + + TerminationSignalHandler::~TerminationSignalHandler() + { + SetConsoleCtrlHandler(StaticCtrlHandlerFunction, FALSE); + + // std::thread requires that any managed thread (joinable) be joined or detached before destructing + if (m_windowThread.joinable()) + { + if (m_windowHandle) + { + // Inform the thread that it should stop. + PostMessageW(m_windowHandle.get(), WM_DESTROY, 0, 0); + } + + m_windowThread.join(); + } + } + + void TerminationSignalHandler::StartAppShutdown() + { + AICLI_LOG(CLI, Info, << "Initiating shutdown procedure"); + + // Lifetime manager sends CTRL-C after the WM_QUERYENDSESSION is processed. + // If we disable the CTRL-C handler, the default handler will kill us. + InformListeners(CancelReason::AppShutdown, true); + } + + BOOL WINAPI TerminationSignalHandler::StaticCtrlHandlerFunction(DWORD ctrlType) + { + return Instance()->CtrlHandlerFunction(ctrlType); + } + + LRESULT WINAPI TerminationSignalHandler::WindowMessageProcedure(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + switch (uMsg) + { + case WM_QUERYENDSESSION: + AICLI_LOG(CLI, Verbose, << "Received WM_QUERYENDSESSION"); + Instance()->StartAppShutdown(); + return TRUE; + case WM_ENDSESSION: + case WM_CLOSE: + AICLI_LOG(CLI, Verbose, << "Received window message type: " << uMsg); + // We delay as long as needed during the WM_ENDSESSION as we will be terminated on return. + ServerShutdownSynchronization::WaitForShutdown(); + DestroyWindow(hWnd); + break; + case WM_DESTROY: + PostQuitMessage(0); + break; + default: + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + return FALSE; + } + + BOOL TerminationSignalHandler::CtrlHandlerFunction(DWORD ctrlType) + { + // TODO: Move this to be logged per active context when we have thread static globals + AICLI_LOG(CLI, Info, << "Got CTRL type: " << ctrlType); + + switch (ctrlType) + { + case CTRL_C_EVENT: + case CTRL_BREAK_EVENT: + return InformListeners(CancelReason::CtrlCSignal, false); + // According to MSDN, we should never receive these due to having gdi32/user32 loaded in our process. + // But handle them as a force terminate anyway. + case CTRL_CLOSE_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + return InformListeners(CancelReason::CtrlCSignal, true); + default: + return FALSE; + } + } + + // Terminates the currently attached contexts. + // Returns FALSE if no contexts attached; TRUE otherwise. + BOOL TerminationSignalHandler::InformListeners(CancelReason reason, bool force) + { + BOOL result = FALSE; + + { + std::lock_guard lock{ m_listenersLock }; + result = m_listeners.empty() ? FALSE : TRUE; + + for (auto& listener : m_listeners) + { + listener->Cancel(reason, force); + } + } + + // Notify shutdown synchronization as well + ServerShutdownSynchronization::Instance().Signal(reason); + + return result; + } + + void TerminationSignalHandler::CreateWindowAndStartMessageLoop() + { + PCWSTR windowClass = L"wingetWindow"; + HINSTANCE hInstance = GetModuleHandle(NULL); + if (hInstance == NULL) + { + LOG_LAST_ERROR_MSG("Failed getting module handle"); + return; + } + + WNDCLASSEX wcex = {}; + wcex.cbSize = sizeof(wcex); + + wcex.style = CS_NOCLOSE; + wcex.lpfnWndProc = TerminationSignalHandler::WindowMessageProcedure; + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = hInstance; + wcex.lpszClassName = windowClass; + + if (!RegisterClassEx(&wcex)) + { + LOG_LAST_ERROR_MSG("Failed registering window class"); + return; + } + + // Unregister the window class on exiting the thread + auto classUnregister = wil::scope_exit([&]() + { + UnregisterClassW(windowClass, hInstance); + }); + + m_windowHandle = wil::unique_hwnd(CreateWindow( + windowClass, + L"WingetMessageOnlyWindow", + WS_OVERLAPPEDWINDOW, + 0, /* x */ + 0, /* y */ + 0, /* nWidth */ + 0, /* nHeight */ + NULL, /* hWndParent */ + NULL, /* hMenu */ + hInstance, + NULL)); /* lpParam */ + + HWND windowHandle = m_windowHandle.get(); + if (windowHandle == nullptr) + { + LOG_LAST_ERROR_MSG("Failed creating window"); + return; + } + + // We must destroy the window first so that the class unregister can succeed + auto destroyWindow = wil::scope_exit([&]() + { + DestroyWindow(windowHandle); + }); + + ShowWindow(windowHandle, SW_HIDE); + + // Force message queue to be created. + MSG msg; + PeekMessage(&msg, NULL, WM_USER, WM_USER, PM_NOREMOVE); + m_messageQueueReady.SetEvent(); + + // Message loop, we send WM_DESTROY to terminate it + BOOL getMessageResult; + while ((getMessageResult = GetMessage(&msg, windowHandle, 0, 0)) != 0) + { + if (getMessageResult == -1) + { + LOG_LAST_ERROR(); + break; + } + else if (msg.message == WM_DESTROY) + { + break; + } + else + { + DispatchMessage(&msg); + } + } + } + + void ServerShutdownSynchronization::Initialize(ShutdownCompleteCallback callback, bool createTerminationSignalHandler) + { + Instance().m_callback = callback; + + // Force the creation of the TerminationSignalHandler singleton so that the process can listen for termination signals even if + // it never attempts to run anything that explicitly registers for cancellation callbacks. + if (createTerminationSignalHandler) + { + TerminationSignalHandler::Instance(); + } + } + + void ServerShutdownSynchronization::AddComponent(const ComponentSystem& component) + { + ServerShutdownSynchronization& instance = Instance(); + std::lock_guard lock{ instance.m_componentsLock }; + + for (const auto& item : instance.m_components) + { + if (item.BlockNewWork == component.BlockNewWork || + item.BeginShutdown == component.BeginShutdown || + item.Wait == component.Wait) + { + return; + } + } + + instance.m_components.push_back(component); + } + + bool ServerShutdownSynchronization::WaitForShutdown(std::optional timeout) + { + ServerShutdownSynchronization& instance = Instance(); + + if (timeout) + { + return instance.m_shutdownComplete.wait(timeout.value()); + } + else + { + { + std::lock_guard lock{ instance.m_threadLock }; + if (!instance.m_shutdownThread.joinable()) + { + AICLI_LOG(Core, Warning, << "Attempt to wait for shutdown when shutdown has not been initiated."); + return false; + } + } + + return instance.m_shutdownComplete.wait(); + } + } + + void ServerShutdownSynchronization::Signal(CancelReason reason) + { + std::lock_guard lock{ m_threadLock }; + + if (!m_shutdownThread.joinable()) + { + m_shutdownThread = std::thread(&ServerShutdownSynchronization::SynchronizeShutdown, this, reason); + } + } + + ServerShutdownSynchronization::~ServerShutdownSynchronization() + { + if (m_shutdownThread.joinable()) + { + m_shutdownThread.detach(); + } + } + + ServerShutdownSynchronization& ServerShutdownSynchronization::Instance() + { + static ServerShutdownSynchronization s_instance; + return s_instance; + } + + void ServerShutdownSynchronization::SynchronizeShutdown(CancelReason reason) try + { + auto setShutdownComplete = wil::scope_exit([this]() { this->m_shutdownComplete.SetEvent(); }); + + std::vector components; + { + std::lock_guard lock{ m_componentsLock }; + components = m_components; + } + + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: BlockNewWork"); + for (const auto& component : components) + { + if (component.BlockNewWork) + { + component.BlockNewWork(reason); + } + } + + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: BeginShutdown"); + for (const auto& component : components) + { + if (component.BeginShutdown) + { + component.BeginShutdown(reason); + } + } + + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: Wait"); + for (const auto& component : components) + { + if (component.Wait) + { + component.Wait(); + } + } + + AICLI_LOG(CLI, Verbose, << "ServerShutdownSynchronization :: ShutdownCompleteCallback"); + ShutdownCompleteCallback callback = m_callback; + if (callback) + { + callback(); + } + } + CATCH_LOG(); +} diff --git a/src/AppInstallerCLICore/Sixel.cpp b/src/AppInstallerCLICore/Sixel.cpp index 7bd84c1480..ee8589414d 100644 --- a/src/AppInstallerCLICore/Sixel.cpp +++ b/src/AppInstallerCLICore/Sixel.cpp @@ -1,718 +1,718 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Sixel.h" -#include -#include -#include -#include - -namespace AppInstaller::CLI::VirtualTerminal::Sixel -{ - namespace anon - { - wil::com_ptr CreateFactory() - { - wil::com_ptr result; - THROW_IF_FAILED(CoCreateInstance( - CLSID_WICImagingFactory, - NULL, - CLSCTX_INPROC_SERVER, - IID_PPV_ARGS(&result))); - return result; - } - - UINT AspectRatioMultiplier(AspectRatio aspectRatio) - { - switch (aspectRatio) - { - case AspectRatio::OneToOne: - return 1; - case AspectRatio::TwoToOne: - return 2; - case AspectRatio::ThreeToOne: - return 3; - case AspectRatio::FiveToOne: - return 5; - default: - THROW_HR(E_INVALIDARG); - } - } - - // Forces the given bitmap source to evaluate - wil::com_ptr CacheToBitmap(IWICImagingFactory* factory, IWICBitmapSource* sourceImage) - { - wil::com_ptr result; - THROW_IF_FAILED(factory->CreateBitmapFromSource(sourceImage, WICBitmapCacheOnLoad, &result)); - return result; - } - - // Convert [0, 255] => [0, 100] - UINT32 ByteToPercent(BYTE input) - { - return (static_cast(input) * 100 + 127) / 255; - } - - // Contains the state for a rendering pass. - struct RenderState - { - RenderState( - const Palette& palette, - const std::vector& views, - const RenderControls& renderControls) : - m_palette(palette), - m_views(views), - m_renderControls(renderControls) - { - // Create render buffers - m_enabledColors.resize(m_palette.Size()); - m_sixelBuffer.resize(m_palette.Size() * m_renderControls.PixelWidth); - } - - enum class State - { - Initial, - Pixels, - Final, - Terminated, - }; - - // Advances the render state machine, returning true if `Current` will return a new sequence and false when it will not. - bool Advance() - { - std::stringstream stream; - - switch (m_currentState) - { - case State::Initial: - // Initial device control string - stream << AICLI_VT_ESCAPE << 'P' << ToIntegral(m_renderControls.AspectRatio) << ";1;q"; - - for (size_t i = 0; i < m_palette.Size(); ++i) - { - // 2 is RGB color space, with values from 0 to 100 - stream << '#' << i << ";2;"; - - WICColor currentColor = m_palette[i]; - BYTE red = (currentColor >> 16) & 0xFF; - BYTE green = (currentColor >> 8) & 0xFF; - BYTE blue = (currentColor) & 0xFF; - - stream << ByteToPercent(red) << ';' << ByteToPercent(green) << ';' << ByteToPercent(blue); - } - - m_currentState = State::Pixels; - break; - case State::Pixels: - { - // Disable all colors and set all characters to empty (0x3F) - memset(m_enabledColors.data(), 0, m_enabledColors.size()); - memset(m_sixelBuffer.data(), 0x3F, m_sixelBuffer.size()); - - // Convert indexed pixel data into per-color sixel lines - UINT rowsToProcess = std::min(RenderControls::PixelsPerSixel, m_renderControls.PixelHeight - m_currentPixelRow); - - for (UINT rowOffset = 0; rowOffset < rowsToProcess; ++rowOffset) - { - // The least significant bit is the top of the sixel - char sixelBit = 1 << rowOffset; - UINT currentRow = m_currentPixelRow + rowOffset; - - for (UINT i = 0; i < m_renderControls.PixelWidth; ++i) - { - const BYTE* pixelPtr = nullptr; - size_t colorIndex = 0; - - for (const ImageView& view : m_views) - { - pixelPtr = view.GetPixel(i, currentRow); - - if (pixelPtr) - { - colorIndex = *pixelPtr; - - // Stop on the first non-transparent pixel we find - if (((m_palette[colorIndex] >> 24) & 0xFF) != 0) - { - break; - } - } - } - - if (pixelPtr) - { - m_enabledColors[colorIndex] = 1; - m_sixelBuffer[(colorIndex * m_renderControls.PixelWidth) + i] += sixelBit; - } - } - } - - // Output all sixel color lines - bool firstOfRow = true; - - for (size_t i = 0; i < m_enabledColors.size(); ++i) - { - if (m_enabledColors[i]) - { - if (m_renderControls.TransparencyEnabled) - { - // Don't output color if transparent - WICColor currentColor = m_palette[i]; - BYTE alpha = (currentColor >> 24) & 0xFF; - if (alpha == 0) - { - continue; - } - } - - if (firstOfRow) - { - firstOfRow = false; - } - else - { - // The carriage return operator resets for another color pass. - stream << '$'; - } - - stream << '#' << i; - - const char* colorRow = &m_sixelBuffer[i * m_renderControls.PixelWidth]; - - if (m_renderControls.UseRepeatSequence) - { - char currentChar = colorRow[0]; - UINT repeatCount = 1; - - for (UINT j = 1; j <= m_renderControls.PixelWidth; ++j) - { - // Force processing of a final null character to handle flushing the line - const char nextChar = (j == m_renderControls.PixelWidth ? 0 : colorRow[j]); - - if (nextChar == currentChar) - { - ++repeatCount; - } - else - { - if (repeatCount > 2) - { - stream << '!' << repeatCount; - } - else if (repeatCount == 2) - { - stream << currentChar; - } - - stream << currentChar; - - currentChar = nextChar; - repeatCount = 1; - } - } - } - else - { - stream << std::string_view{ colorRow, m_renderControls.PixelWidth }; - } - } - } - - // The new line operator sets up for the next sixel row - stream << '-'; - - m_currentPixelRow += rowsToProcess; - if (m_currentPixelRow >= m_renderControls.PixelHeight) - { - m_currentState = State::Final; - } - } - break; - case State::Final: - stream << AICLI_VT_ESCAPE << '\\'; - m_currentState = State::Terminated; - break; - case State::Terminated: - m_currentSequence.clear(); - return false; - } - - m_currentSequence = std::move(stream).str(); - return true; - } - - Sequence Current() const - { - return Sequence{ m_currentSequence }; - } - - private: - const Palette& m_palette; - const std::vector& m_views; - const RenderControls& m_renderControls; - - State m_currentState = State::Initial; - std::vector m_enabledColors; - std::vector m_sixelBuffer; - UINT m_currentPixelRow = 0; - // TODO-C++20: Replace with a view from the stringstream - std::string m_currentSequence; - }; - } - - Palette::Palette(IWICImagingFactory* factory, IWICBitmapSource* bitmapSource, UINT colorCount, bool transparencyEnabled) : - m_factory(factory) - { - THROW_IF_FAILED(m_factory->CreatePalette(&m_paletteObject)); - - THROW_IF_FAILED(m_paletteObject->InitializeFromBitmap(bitmapSource, colorCount, transparencyEnabled)); - - // Extract the palette for render use - UINT actualColorCount = 0; - THROW_IF_FAILED(m_paletteObject->GetColorCount(&actualColorCount)); - - m_palette.resize(actualColorCount); - THROW_IF_FAILED(m_paletteObject->GetColors(actualColorCount, m_palette.data(), &actualColorCount)); - } - - Palette::Palette(const Palette& first, const Palette& second) - { - auto firstPalette = first.m_palette; - auto secondPalette = second.m_palette; - std::sort(firstPalette.begin(), firstPalette.end()); - std::sort(secondPalette.begin(), secondPalette.end()); - - // Construct a union of the two palettes - std::set_union(firstPalette.begin(), firstPalette.end(), secondPalette.begin(), secondPalette.end(), std::back_inserter(m_palette)); - THROW_HR_IF(E_INVALIDARG, m_palette.size() > MaximumColorCount); - - m_factory = first.m_factory; - THROW_IF_FAILED(m_factory->CreatePalette(&m_paletteObject)); - THROW_IF_FAILED(m_paletteObject->InitializeCustom(m_palette.data(), static_cast(m_palette.size()))); - } - - IWICPalette* Palette::Get() const - { - return m_paletteObject.get(); - } - - size_t Palette::Size() const - { - return m_palette.size(); - } - - WICColor& Palette::operator[](size_t index) - { - return m_palette[index]; - } - - WICColor Palette::operator[](size_t index) const - { - return m_palette[index]; - } - - ImageView::ImageView(UINT width, UINT height, UINT stride, UINT byteCount, BYTE* bytes) : - m_viewWidth(width), m_viewHeight(height), m_viewStride(stride), m_viewByteCount(byteCount), m_viewBytes(bytes) - {} - - ImageView ImageView::Lock(IWICBitmap* imageSource) - { - WICPixelFormatGUID pixelFormat{}; - THROW_IF_FAILED(imageSource->GetPixelFormat(&pixelFormat)); - THROW_HR_IF(ERROR_INVALID_STATE, GUID_WICPixelFormat8bppIndexed != pixelFormat); - - ImageView result; - - UINT sourceX = 0; - UINT sourceY = 0; - THROW_IF_FAILED(imageSource->GetSize(&sourceX, &sourceY)); - THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, - sourceX > static_cast(std::numeric_limits::max()) || sourceY > static_cast(std::numeric_limits::max())); - - WICRect rect{}; - rect.Width = static_cast(sourceX); - rect.Height = static_cast(sourceY); - - THROW_IF_FAILED(imageSource->Lock(&rect, WICBitmapLockRead, &result.m_lockedImage)); - THROW_IF_FAILED(result.m_lockedImage->GetSize(&result.m_viewWidth, &result.m_viewHeight)); - THROW_IF_FAILED(result.m_lockedImage->GetStride(&result.m_viewStride)); - THROW_IF_FAILED(result.m_lockedImage->GetDataPointer(&result.m_viewByteCount, &result.m_viewBytes)); - - return result; - } - - ImageView ImageView::Copy(IWICBitmapSource* imageSource) - { - WICPixelFormatGUID pixelFormat{}; - THROW_IF_FAILED(imageSource->GetPixelFormat(&pixelFormat)); - THROW_HR_IF(ERROR_INVALID_STATE, GUID_WICPixelFormat8bppIndexed != pixelFormat); - - ImageView result; - - THROW_IF_FAILED(imageSource->GetSize(&result.m_viewWidth, &result.m_viewHeight)); - THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, - result.m_viewWidth > static_cast(std::numeric_limits::max()) || result.m_viewHeight > static_cast(std::numeric_limits::max())); - - result.m_viewStride = result.m_viewWidth; - result.m_viewByteCount = result.m_viewStride * result.m_viewHeight; - result.m_copiedImage = std::make_unique(result.m_viewByteCount); - result.m_viewBytes = result.m_copiedImage.get(); - - THROW_IF_FAILED(imageSource->CopyPixels(nullptr, result.m_viewStride, result.m_viewByteCount, result.m_viewBytes)); - - return result; - } - - void ImageView::Translate(INT x, INT y, bool tile) - { - m_tile = tile; - - if (m_tile) - { - m_translateX = static_cast(m_viewWidth - (x % static_cast(m_viewWidth))); - m_translateY = static_cast(m_viewHeight - (y % static_cast(m_viewHeight))); - } - else - { - m_translateX = static_cast(-x); - m_translateY = static_cast(-y); - } - } - - const BYTE* ImageView::GetPixel(UINT x, UINT y) const - { - UINT translatedX = x + m_translateX; - UINT tileCountX = translatedX / m_viewWidth; - UINT viewX = translatedX % m_viewWidth; - if (tileCountX && !m_tile) - { - return nullptr; - } - - UINT translatedY = y + m_translateY; - UINT tileCountY = translatedY / m_viewHeight; - UINT viewY = translatedY % m_viewHeight; - if (tileCountY && !m_tile) - { - return nullptr; - } - - return m_viewBytes + (static_cast(viewY) * m_viewStride) + viewX; - } - - UINT ImageView::Width() const - { - return m_viewWidth; - } - - UINT ImageView::Height() const - { - return m_viewHeight; - } - - void RenderControls::RenderSizeInCells(UINT width, UINT height) - { - PixelWidth = width * CellWidthInPixels; - - // We don't want to overdraw the row below, so our height must be the largest multiple of 6 that fits in Y cells. - UINT yInPixels = height * CellHeightInPixels; - PixelHeight = yInPixels - (yInPixels % PixelsPerSixel); - } - - ImageSource::ImageSource(const std::filesystem::path& imageFilePath) - { - m_factory = anon::CreateFactory(); - - wil::com_ptr decoder; - THROW_IF_FAILED(m_factory->CreateDecoderFromFilename(imageFilePath.c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder)); - - wil::com_ptr decodedFrame; - THROW_IF_FAILED(decoder->GetFrame(0, &decodedFrame)); - - m_sourceImage = anon::CacheToBitmap(m_factory.get(), decodedFrame.get()); - } - - ImageSource::ImageSource(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding) : - ImageSource(Utility::ReadEntireStreamAsByteArray(imageStream), imageEncoding) - { - } - - ImageSource::ImageSource(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding) - { - m_factory = anon::CreateFactory(); - - wil::com_ptr stream; - THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); - - ULONG written = 0; - THROW_IF_FAILED(stream->Write(imageBytes.data(), static_cast(imageBytes.size()), &written)); - THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - - wil::com_ptr decoder; - bool initializeDecoder = true; - - switch (imageEncoding) - { - case Manifest::IconFileTypeEnum::Unknown: - THROW_IF_FAILED(m_factory->CreateDecoderFromStream(stream.get(), NULL, WICDecodeMetadataCacheOnDemand, &decoder)); - initializeDecoder = false; - break; - case Manifest::IconFileTypeEnum::Jpeg: - THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatJpeg, NULL, &decoder)); - break; - case Manifest::IconFileTypeEnum::Png: - THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatPng, NULL, &decoder)); - break; - case Manifest::IconFileTypeEnum::Ico: - THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatIco, NULL, &decoder)); - break; - default: - THROW_HR(E_UNEXPECTED); - } - - if (initializeDecoder) - { - THROW_IF_FAILED(decoder->Initialize(stream.get(), WICDecodeMetadataCacheOnDemand)); - } - - wil::com_ptr decodedFrame; - THROW_IF_FAILED(decoder->GetFrame(0, &decodedFrame)); - - m_sourceImage = anon::CacheToBitmap(m_factory.get(), decodedFrame.get()); - } - - void ImageSource::Resize(UINT pixelWidth, UINT pixelHeight, AspectRatio targetRenderRatio, bool stretchToFill, InterpolationMode interpolationMode) - { - if ((pixelWidth && pixelHeight) || targetRenderRatio != AspectRatio::OneToOne) - { - UINT targetX = pixelWidth; - UINT targetY = pixelHeight; - - if (!stretchToFill) - { - // We need to calculate which of the sizes needs to be reduced - UINT sourceImageX = 0; - UINT sourceImageY = 0; - THROW_IF_FAILED(m_sourceImage->GetSize(&sourceImageX, &sourceImageY)); - - double doubleTargetX = targetX; - double doubleTargetY = targetY; - double doubleSourceImageX = sourceImageX; - double doubleSourceImageY = sourceImageY; - - double scaleFactorX = doubleTargetX / doubleSourceImageX; - double targetY_scaledForX = sourceImageY * scaleFactorX; - if (targetY_scaledForX > doubleTargetY) - { - // Scaling to make X fill would make Y to large, so we must scale to fill Y - targetX = static_cast(sourceImageX * (doubleTargetY / doubleSourceImageY)); - } - else - { - // Scaling to make X fill kept Y under target - targetY = static_cast(targetY_scaledForX); - } - } - - // Apply aspect ratio scaling - targetY /= anon::AspectRatioMultiplier(targetRenderRatio); - - wil::com_ptr scaler; - THROW_IF_FAILED(m_factory->CreateBitmapScaler(&scaler)); - - THROW_IF_FAILED(scaler->Initialize(m_sourceImage.get(), targetX, targetY, ToEnum(ToIntegral(interpolationMode)))); - m_sourceImage = anon::CacheToBitmap(m_factory.get(), scaler.get()); - } - } - - void ImageSource::Resize(const RenderControls& controls) - { - Resize(controls.PixelWidth, controls.PixelHeight, controls.AspectRatio, controls.StretchSourceToFill, controls.InterpolationMode); - } - - Palette ImageSource::CreatePalette(UINT colorCount, bool transparencyEnabled) const - { - return { m_factory.get(), m_sourceImage.get(), colorCount, transparencyEnabled }; - } - - Palette ImageSource::CreatePalette(const RenderControls& controls) const - { - return CreatePalette(controls.ColorCount, controls.TransparencyEnabled); - } - - void ImageSource::ApplyPalette(const Palette& palette) - { - // Convert to 8bpp indexed - wil::com_ptr converter; - THROW_IF_FAILED(m_factory->CreateFormatConverter(&converter)); - - // TODO: Determine a better value or enable it to be set - constexpr double s_alphaThreshold = 0.5; - - THROW_IF_FAILED(converter->Initialize(m_sourceImage.get(), GUID_WICPixelFormat8bppIndexed, WICBitmapDitherTypeErrorDiffusion, palette.Get(), s_alphaThreshold, WICBitmapPaletteTypeCustom)); - m_sourceImage = anon::CacheToBitmap(m_factory.get(), converter.get()); - } - - ImageView ImageSource::Lock() const - { - return ImageView::Lock(m_sourceImage.get()); - } - - ImageView ImageSource::Copy() const - { - return ImageView::Copy(m_sourceImage.get()); - } - - void Compositor::Palette(Sixel::Palette palette) - { - m_palette = std::move(palette); - } - - void Compositor::AddView(ImageView&& view) - { - m_views.emplace_back(std::move(view)); - } - - size_t Compositor::ViewCount() const - { - return m_views.size(); - } - - ImageView& Compositor::operator[](size_t index) - { - return m_views[index]; - } - - const ImageView& Compositor::operator[](size_t index) const - { - return m_views[index]; - } - - RenderControls& Compositor::Controls() - { - return m_renderControls; - } - - const RenderControls& Compositor::Controls() const - { - return m_renderControls; - } - - ConstructedSequence Compositor::Render() - { - anon::RenderState renderState{ m_palette, m_views, m_renderControls }; - - std::stringstream result; - - while (renderState.Advance()) - { - result << renderState.Current().Get(); - } - - return ConstructedSequence{ std::move(result).str() }; - } - - void Compositor::RenderTo(Execution::BaseStream& stream) - { - anon::RenderState renderState{ m_palette, m_views, m_renderControls }; - - while (renderState.Advance()) - { - stream << renderState.Current(); - } - } - - void Compositor::RenderTo(Execution::OutputStream& stream) - { - anon::RenderState renderState{ m_palette, m_views, m_renderControls }; - - while (renderState.Advance()) - { - stream << renderState.Current(); - } - } - - Image::Image(const std::filesystem::path& imageFilePath) : - m_imageSource(imageFilePath) - {} - - Image::Image(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding) : - m_imageSource(imageStream, imageEncoding) - {} - - Image::Image(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding) : - m_imageSource(imageBytes, imageEncoding) - {} - - Image& Image::AspectRatio(Sixel::AspectRatio aspectRatio) - { - m_renderControls.AspectRatio = aspectRatio; - return *this; - } - - Image& Image::Transparency(bool transparencyEnabled) - { - m_renderControls.TransparencyEnabled = transparencyEnabled; - return *this; - } - - Image& Image::ColorCount(UINT colorCount) - { - THROW_HR_IF(E_INVALIDARG, colorCount > Palette::MaximumColorCount || colorCount < 2); - m_renderControls.ColorCount = colorCount; - return *this; - } - - Image& Image::RenderSizeInPixels(UINT width, UINT height) - { - m_renderControls.PixelWidth = width; - m_renderControls.PixelHeight = height; - return *this; - } - - Image& Image::RenderSizeInCells(UINT width, UINT height) - { - m_renderControls.RenderSizeInCells(width, height); - return *this; - } - - Image& Image::StretchSourceToFill(bool stretchSourceToFill) - { - m_renderControls.StretchSourceToFill = stretchSourceToFill; - return *this; - } - - Image& Image::UseRepeatSequence(bool useRepeatSequence) - { - m_renderControls.UseRepeatSequence = useRepeatSequence; - return *this; - } - - ConstructedSequence Image::Render() - { - return CreateCompositor().second.Render(); - } - - void Image::RenderTo(Execution::OutputStream& stream) - { - CreateCompositor().second.RenderTo(stream); - } - - std::pair Image::CreateCompositor() - { - ImageSource localSource{ m_imageSource }; - localSource.Resize(m_renderControls); - - Palette palette{ localSource.CreatePalette(m_renderControls) }; - localSource.ApplyPalette(palette); - - ImageView view{ localSource.Lock() }; - - Compositor compositor; - compositor.Palette(std::move(palette)); - compositor.AddView(std::move(view)); - compositor.Controls() = m_renderControls; - - return { std::move(localSource), std::move(compositor) }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Sixel.h" +#include +#include +#include +#include + +namespace AppInstaller::CLI::VirtualTerminal::Sixel +{ + namespace anon + { + wil::com_ptr CreateFactory() + { + wil::com_ptr result; + THROW_IF_FAILED(CoCreateInstance( + CLSID_WICImagingFactory, + NULL, + CLSCTX_INPROC_SERVER, + IID_PPV_ARGS(&result))); + return result; + } + + UINT AspectRatioMultiplier(AspectRatio aspectRatio) + { + switch (aspectRatio) + { + case AspectRatio::OneToOne: + return 1; + case AspectRatio::TwoToOne: + return 2; + case AspectRatio::ThreeToOne: + return 3; + case AspectRatio::FiveToOne: + return 5; + default: + THROW_HR(E_INVALIDARG); + } + } + + // Forces the given bitmap source to evaluate + wil::com_ptr CacheToBitmap(IWICImagingFactory* factory, IWICBitmapSource* sourceImage) + { + wil::com_ptr result; + THROW_IF_FAILED(factory->CreateBitmapFromSource(sourceImage, WICBitmapCacheOnLoad, &result)); + return result; + } + + // Convert [0, 255] => [0, 100] + UINT32 ByteToPercent(BYTE input) + { + return (static_cast(input) * 100 + 127) / 255; + } + + // Contains the state for a rendering pass. + struct RenderState + { + RenderState( + const Palette& palette, + const std::vector& views, + const RenderControls& renderControls) : + m_palette(palette), + m_views(views), + m_renderControls(renderControls) + { + // Create render buffers + m_enabledColors.resize(m_palette.Size()); + m_sixelBuffer.resize(m_palette.Size() * m_renderControls.PixelWidth); + } + + enum class State + { + Initial, + Pixels, + Final, + Terminated, + }; + + // Advances the render state machine, returning true if `Current` will return a new sequence and false when it will not. + bool Advance() + { + std::stringstream stream; + + switch (m_currentState) + { + case State::Initial: + // Initial device control string + stream << AICLI_VT_ESCAPE << 'P' << ToIntegral(m_renderControls.AspectRatio) << ";1;q"; + + for (size_t i = 0; i < m_palette.Size(); ++i) + { + // 2 is RGB color space, with values from 0 to 100 + stream << '#' << i << ";2;"; + + WICColor currentColor = m_palette[i]; + BYTE red = (currentColor >> 16) & 0xFF; + BYTE green = (currentColor >> 8) & 0xFF; + BYTE blue = (currentColor) & 0xFF; + + stream << ByteToPercent(red) << ';' << ByteToPercent(green) << ';' << ByteToPercent(blue); + } + + m_currentState = State::Pixels; + break; + case State::Pixels: + { + // Disable all colors and set all characters to empty (0x3F) + memset(m_enabledColors.data(), 0, m_enabledColors.size()); + memset(m_sixelBuffer.data(), 0x3F, m_sixelBuffer.size()); + + // Convert indexed pixel data into per-color sixel lines + UINT rowsToProcess = std::min(RenderControls::PixelsPerSixel, m_renderControls.PixelHeight - m_currentPixelRow); + + for (UINT rowOffset = 0; rowOffset < rowsToProcess; ++rowOffset) + { + // The least significant bit is the top of the sixel + char sixelBit = 1 << rowOffset; + UINT currentRow = m_currentPixelRow + rowOffset; + + for (UINT i = 0; i < m_renderControls.PixelWidth; ++i) + { + const BYTE* pixelPtr = nullptr; + size_t colorIndex = 0; + + for (const ImageView& view : m_views) + { + pixelPtr = view.GetPixel(i, currentRow); + + if (pixelPtr) + { + colorIndex = *pixelPtr; + + // Stop on the first non-transparent pixel we find + if (((m_palette[colorIndex] >> 24) & 0xFF) != 0) + { + break; + } + } + } + + if (pixelPtr) + { + m_enabledColors[colorIndex] = 1; + m_sixelBuffer[(colorIndex * m_renderControls.PixelWidth) + i] += sixelBit; + } + } + } + + // Output all sixel color lines + bool firstOfRow = true; + + for (size_t i = 0; i < m_enabledColors.size(); ++i) + { + if (m_enabledColors[i]) + { + if (m_renderControls.TransparencyEnabled) + { + // Don't output color if transparent + WICColor currentColor = m_palette[i]; + BYTE alpha = (currentColor >> 24) & 0xFF; + if (alpha == 0) + { + continue; + } + } + + if (firstOfRow) + { + firstOfRow = false; + } + else + { + // The carriage return operator resets for another color pass. + stream << '$'; + } + + stream << '#' << i; + + const char* colorRow = &m_sixelBuffer[i * m_renderControls.PixelWidth]; + + if (m_renderControls.UseRepeatSequence) + { + char currentChar = colorRow[0]; + UINT repeatCount = 1; + + for (UINT j = 1; j <= m_renderControls.PixelWidth; ++j) + { + // Force processing of a final null character to handle flushing the line + const char nextChar = (j == m_renderControls.PixelWidth ? 0 : colorRow[j]); + + if (nextChar == currentChar) + { + ++repeatCount; + } + else + { + if (repeatCount > 2) + { + stream << '!' << repeatCount; + } + else if (repeatCount == 2) + { + stream << currentChar; + } + + stream << currentChar; + + currentChar = nextChar; + repeatCount = 1; + } + } + } + else + { + stream << std::string_view{ colorRow, m_renderControls.PixelWidth }; + } + } + } + + // The new line operator sets up for the next sixel row + stream << '-'; + + m_currentPixelRow += rowsToProcess; + if (m_currentPixelRow >= m_renderControls.PixelHeight) + { + m_currentState = State::Final; + } + } + break; + case State::Final: + stream << AICLI_VT_ESCAPE << '\\'; + m_currentState = State::Terminated; + break; + case State::Terminated: + m_currentSequence.clear(); + return false; + } + + m_currentSequence = std::move(stream).str(); + return true; + } + + Sequence Current() const + { + return Sequence{ m_currentSequence }; + } + + private: + const Palette& m_palette; + const std::vector& m_views; + const RenderControls& m_renderControls; + + State m_currentState = State::Initial; + std::vector m_enabledColors; + std::vector m_sixelBuffer; + UINT m_currentPixelRow = 0; + // TODO-C++20: Replace with a view from the stringstream + std::string m_currentSequence; + }; + } + + Palette::Palette(IWICImagingFactory* factory, IWICBitmapSource* bitmapSource, UINT colorCount, bool transparencyEnabled) : + m_factory(factory) + { + THROW_IF_FAILED(m_factory->CreatePalette(&m_paletteObject)); + + THROW_IF_FAILED(m_paletteObject->InitializeFromBitmap(bitmapSource, colorCount, transparencyEnabled)); + + // Extract the palette for render use + UINT actualColorCount = 0; + THROW_IF_FAILED(m_paletteObject->GetColorCount(&actualColorCount)); + + m_palette.resize(actualColorCount); + THROW_IF_FAILED(m_paletteObject->GetColors(actualColorCount, m_palette.data(), &actualColorCount)); + } + + Palette::Palette(const Palette& first, const Palette& second) + { + auto firstPalette = first.m_palette; + auto secondPalette = second.m_palette; + std::sort(firstPalette.begin(), firstPalette.end()); + std::sort(secondPalette.begin(), secondPalette.end()); + + // Construct a union of the two palettes + std::set_union(firstPalette.begin(), firstPalette.end(), secondPalette.begin(), secondPalette.end(), std::back_inserter(m_palette)); + THROW_HR_IF(E_INVALIDARG, m_palette.size() > MaximumColorCount); + + m_factory = first.m_factory; + THROW_IF_FAILED(m_factory->CreatePalette(&m_paletteObject)); + THROW_IF_FAILED(m_paletteObject->InitializeCustom(m_palette.data(), static_cast(m_palette.size()))); + } + + IWICPalette* Palette::Get() const + { + return m_paletteObject.get(); + } + + size_t Palette::Size() const + { + return m_palette.size(); + } + + WICColor& Palette::operator[](size_t index) + { + return m_palette[index]; + } + + WICColor Palette::operator[](size_t index) const + { + return m_palette[index]; + } + + ImageView::ImageView(UINT width, UINT height, UINT stride, UINT byteCount, BYTE* bytes) : + m_viewWidth(width), m_viewHeight(height), m_viewStride(stride), m_viewByteCount(byteCount), m_viewBytes(bytes) + {} + + ImageView ImageView::Lock(IWICBitmap* imageSource) + { + WICPixelFormatGUID pixelFormat{}; + THROW_IF_FAILED(imageSource->GetPixelFormat(&pixelFormat)); + THROW_HR_IF(ERROR_INVALID_STATE, GUID_WICPixelFormat8bppIndexed != pixelFormat); + + ImageView result; + + UINT sourceX = 0; + UINT sourceY = 0; + THROW_IF_FAILED(imageSource->GetSize(&sourceX, &sourceY)); + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, + sourceX > static_cast(std::numeric_limits::max()) || sourceY > static_cast(std::numeric_limits::max())); + + WICRect rect{}; + rect.Width = static_cast(sourceX); + rect.Height = static_cast(sourceY); + + THROW_IF_FAILED(imageSource->Lock(&rect, WICBitmapLockRead, &result.m_lockedImage)); + THROW_IF_FAILED(result.m_lockedImage->GetSize(&result.m_viewWidth, &result.m_viewHeight)); + THROW_IF_FAILED(result.m_lockedImage->GetStride(&result.m_viewStride)); + THROW_IF_FAILED(result.m_lockedImage->GetDataPointer(&result.m_viewByteCount, &result.m_viewBytes)); + + return result; + } + + ImageView ImageView::Copy(IWICBitmapSource* imageSource) + { + WICPixelFormatGUID pixelFormat{}; + THROW_IF_FAILED(imageSource->GetPixelFormat(&pixelFormat)); + THROW_HR_IF(ERROR_INVALID_STATE, GUID_WICPixelFormat8bppIndexed != pixelFormat); + + ImageView result; + + THROW_IF_FAILED(imageSource->GetSize(&result.m_viewWidth, &result.m_viewHeight)); + THROW_WIN32_IF(ERROR_BUFFER_OVERFLOW, + result.m_viewWidth > static_cast(std::numeric_limits::max()) || result.m_viewHeight > static_cast(std::numeric_limits::max())); + + result.m_viewStride = result.m_viewWidth; + result.m_viewByteCount = result.m_viewStride * result.m_viewHeight; + result.m_copiedImage = std::make_unique(result.m_viewByteCount); + result.m_viewBytes = result.m_copiedImage.get(); + + THROW_IF_FAILED(imageSource->CopyPixels(nullptr, result.m_viewStride, result.m_viewByteCount, result.m_viewBytes)); + + return result; + } + + void ImageView::Translate(INT x, INT y, bool tile) + { + m_tile = tile; + + if (m_tile) + { + m_translateX = static_cast(m_viewWidth - (x % static_cast(m_viewWidth))); + m_translateY = static_cast(m_viewHeight - (y % static_cast(m_viewHeight))); + } + else + { + m_translateX = static_cast(-x); + m_translateY = static_cast(-y); + } + } + + const BYTE* ImageView::GetPixel(UINT x, UINT y) const + { + UINT translatedX = x + m_translateX; + UINT tileCountX = translatedX / m_viewWidth; + UINT viewX = translatedX % m_viewWidth; + if (tileCountX && !m_tile) + { + return nullptr; + } + + UINT translatedY = y + m_translateY; + UINT tileCountY = translatedY / m_viewHeight; + UINT viewY = translatedY % m_viewHeight; + if (tileCountY && !m_tile) + { + return nullptr; + } + + return m_viewBytes + (static_cast(viewY) * m_viewStride) + viewX; + } + + UINT ImageView::Width() const + { + return m_viewWidth; + } + + UINT ImageView::Height() const + { + return m_viewHeight; + } + + void RenderControls::RenderSizeInCells(UINT width, UINT height) + { + PixelWidth = width * CellWidthInPixels; + + // We don't want to overdraw the row below, so our height must be the largest multiple of 6 that fits in Y cells. + UINT yInPixels = height * CellHeightInPixels; + PixelHeight = yInPixels - (yInPixels % PixelsPerSixel); + } + + ImageSource::ImageSource(const std::filesystem::path& imageFilePath) + { + m_factory = anon::CreateFactory(); + + wil::com_ptr decoder; + THROW_IF_FAILED(m_factory->CreateDecoderFromFilename(imageFilePath.c_str(), NULL, GENERIC_READ, WICDecodeMetadataCacheOnDemand, &decoder)); + + wil::com_ptr decodedFrame; + THROW_IF_FAILED(decoder->GetFrame(0, &decodedFrame)); + + m_sourceImage = anon::CacheToBitmap(m_factory.get(), decodedFrame.get()); + } + + ImageSource::ImageSource(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding) : + ImageSource(Utility::ReadEntireStreamAsByteArray(imageStream), imageEncoding) + { + } + + ImageSource::ImageSource(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding) + { + m_factory = anon::CreateFactory(); + + wil::com_ptr stream; + THROW_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + + ULONG written = 0; + THROW_IF_FAILED(stream->Write(imageBytes.data(), static_cast(imageBytes.size()), &written)); + THROW_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + + wil::com_ptr decoder; + bool initializeDecoder = true; + + switch (imageEncoding) + { + case Manifest::IconFileTypeEnum::Unknown: + THROW_IF_FAILED(m_factory->CreateDecoderFromStream(stream.get(), NULL, WICDecodeMetadataCacheOnDemand, &decoder)); + initializeDecoder = false; + break; + case Manifest::IconFileTypeEnum::Jpeg: + THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatJpeg, NULL, &decoder)); + break; + case Manifest::IconFileTypeEnum::Png: + THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatPng, NULL, &decoder)); + break; + case Manifest::IconFileTypeEnum::Ico: + THROW_IF_FAILED(m_factory->CreateDecoder(GUID_ContainerFormatIco, NULL, &decoder)); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + if (initializeDecoder) + { + THROW_IF_FAILED(decoder->Initialize(stream.get(), WICDecodeMetadataCacheOnDemand)); + } + + wil::com_ptr decodedFrame; + THROW_IF_FAILED(decoder->GetFrame(0, &decodedFrame)); + + m_sourceImage = anon::CacheToBitmap(m_factory.get(), decodedFrame.get()); + } + + void ImageSource::Resize(UINT pixelWidth, UINT pixelHeight, AspectRatio targetRenderRatio, bool stretchToFill, InterpolationMode interpolationMode) + { + if ((pixelWidth && pixelHeight) || targetRenderRatio != AspectRatio::OneToOne) + { + UINT targetX = pixelWidth; + UINT targetY = pixelHeight; + + if (!stretchToFill) + { + // We need to calculate which of the sizes needs to be reduced + UINT sourceImageX = 0; + UINT sourceImageY = 0; + THROW_IF_FAILED(m_sourceImage->GetSize(&sourceImageX, &sourceImageY)); + + double doubleTargetX = targetX; + double doubleTargetY = targetY; + double doubleSourceImageX = sourceImageX; + double doubleSourceImageY = sourceImageY; + + double scaleFactorX = doubleTargetX / doubleSourceImageX; + double targetY_scaledForX = sourceImageY * scaleFactorX; + if (targetY_scaledForX > doubleTargetY) + { + // Scaling to make X fill would make Y to large, so we must scale to fill Y + targetX = static_cast(sourceImageX * (doubleTargetY / doubleSourceImageY)); + } + else + { + // Scaling to make X fill kept Y under target + targetY = static_cast(targetY_scaledForX); + } + } + + // Apply aspect ratio scaling + targetY /= anon::AspectRatioMultiplier(targetRenderRatio); + + wil::com_ptr scaler; + THROW_IF_FAILED(m_factory->CreateBitmapScaler(&scaler)); + + THROW_IF_FAILED(scaler->Initialize(m_sourceImage.get(), targetX, targetY, ToEnum(ToIntegral(interpolationMode)))); + m_sourceImage = anon::CacheToBitmap(m_factory.get(), scaler.get()); + } + } + + void ImageSource::Resize(const RenderControls& controls) + { + Resize(controls.PixelWidth, controls.PixelHeight, controls.AspectRatio, controls.StretchSourceToFill, controls.InterpolationMode); + } + + Palette ImageSource::CreatePalette(UINT colorCount, bool transparencyEnabled) const + { + return { m_factory.get(), m_sourceImage.get(), colorCount, transparencyEnabled }; + } + + Palette ImageSource::CreatePalette(const RenderControls& controls) const + { + return CreatePalette(controls.ColorCount, controls.TransparencyEnabled); + } + + void ImageSource::ApplyPalette(const Palette& palette) + { + // Convert to 8bpp indexed + wil::com_ptr converter; + THROW_IF_FAILED(m_factory->CreateFormatConverter(&converter)); + + // TODO: Determine a better value or enable it to be set + constexpr double s_alphaThreshold = 0.5; + + THROW_IF_FAILED(converter->Initialize(m_sourceImage.get(), GUID_WICPixelFormat8bppIndexed, WICBitmapDitherTypeErrorDiffusion, palette.Get(), s_alphaThreshold, WICBitmapPaletteTypeCustom)); + m_sourceImage = anon::CacheToBitmap(m_factory.get(), converter.get()); + } + + ImageView ImageSource::Lock() const + { + return ImageView::Lock(m_sourceImage.get()); + } + + ImageView ImageSource::Copy() const + { + return ImageView::Copy(m_sourceImage.get()); + } + + void Compositor::Palette(Sixel::Palette palette) + { + m_palette = std::move(palette); + } + + void Compositor::AddView(ImageView&& view) + { + m_views.emplace_back(std::move(view)); + } + + size_t Compositor::ViewCount() const + { + return m_views.size(); + } + + ImageView& Compositor::operator[](size_t index) + { + return m_views[index]; + } + + const ImageView& Compositor::operator[](size_t index) const + { + return m_views[index]; + } + + RenderControls& Compositor::Controls() + { + return m_renderControls; + } + + const RenderControls& Compositor::Controls() const + { + return m_renderControls; + } + + ConstructedSequence Compositor::Render() + { + anon::RenderState renderState{ m_palette, m_views, m_renderControls }; + + std::stringstream result; + + while (renderState.Advance()) + { + result << renderState.Current().Get(); + } + + return ConstructedSequence{ std::move(result).str() }; + } + + void Compositor::RenderTo(Execution::BaseStream& stream) + { + anon::RenderState renderState{ m_palette, m_views, m_renderControls }; + + while (renderState.Advance()) + { + stream << renderState.Current(); + } + } + + void Compositor::RenderTo(Execution::OutputStream& stream) + { + anon::RenderState renderState{ m_palette, m_views, m_renderControls }; + + while (renderState.Advance()) + { + stream << renderState.Current(); + } + } + + Image::Image(const std::filesystem::path& imageFilePath) : + m_imageSource(imageFilePath) + {} + + Image::Image(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding) : + m_imageSource(imageStream, imageEncoding) + {} + + Image::Image(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding) : + m_imageSource(imageBytes, imageEncoding) + {} + + Image& Image::AspectRatio(Sixel::AspectRatio aspectRatio) + { + m_renderControls.AspectRatio = aspectRatio; + return *this; + } + + Image& Image::Transparency(bool transparencyEnabled) + { + m_renderControls.TransparencyEnabled = transparencyEnabled; + return *this; + } + + Image& Image::ColorCount(UINT colorCount) + { + THROW_HR_IF(E_INVALIDARG, colorCount > Palette::MaximumColorCount || colorCount < 2); + m_renderControls.ColorCount = colorCount; + return *this; + } + + Image& Image::RenderSizeInPixels(UINT width, UINT height) + { + m_renderControls.PixelWidth = width; + m_renderControls.PixelHeight = height; + return *this; + } + + Image& Image::RenderSizeInCells(UINT width, UINT height) + { + m_renderControls.RenderSizeInCells(width, height); + return *this; + } + + Image& Image::StretchSourceToFill(bool stretchSourceToFill) + { + m_renderControls.StretchSourceToFill = stretchSourceToFill; + return *this; + } + + Image& Image::UseRepeatSequence(bool useRepeatSequence) + { + m_renderControls.UseRepeatSequence = useRepeatSequence; + return *this; + } + + ConstructedSequence Image::Render() + { + return CreateCompositor().second.Render(); + } + + void Image::RenderTo(Execution::OutputStream& stream) + { + CreateCompositor().second.RenderTo(stream); + } + + std::pair Image::CreateCompositor() + { + ImageSource localSource{ m_imageSource }; + localSource.Resize(m_renderControls); + + Palette palette{ localSource.CreatePalette(m_renderControls) }; + localSource.ApplyPalette(palette); + + ImageView view{ localSource.Lock() }; + + Compositor compositor; + compositor.Palette(std::move(palette)); + compositor.AddView(std::move(view)); + compositor.Controls() = m_renderControls; + + return { std::move(localSource), std::move(compositor) }; + } +} diff --git a/src/AppInstallerCLICore/Sixel.h b/src/AppInstallerCLICore/Sixel.h index 308353c608..63f33d4492 100644 --- a/src/AppInstallerCLICore/Sixel.h +++ b/src/AppInstallerCLICore/Sixel.h @@ -1,268 +1,268 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ChannelStreams.h" -#include "VTSupport.h" -#include -#include -#include -#include - -namespace AppInstaller::CLI::VirtualTerminal::Sixel -{ - // Determines the height to width ratio of the pixels that make up a sixel (a sixel is 6 pixels tall and 1 pixel wide). - // Note that each cell is always a height of 20 and a width of 10, regardless of the screen resolution of the terminal. - // The 2:1 ratio will then result in each sixel being 12 of the 20 pixels of a cell. - enum class AspectRatio - { - OneToOne = 7, - TwoToOne = 0, - ThreeToOne = 3, - FiveToOne = 2, - }; - - // Determines the algorithm used when resizing the image. - enum class InterpolationMode - { - NearestNeighbor = WICBitmapInterpolationModeNearestNeighbor, - Linear = WICBitmapInterpolationModeLinear, - Cubic = WICBitmapInterpolationModeCubic, - Fant = WICBitmapInterpolationModeFant, - HighQualityCubic = WICBitmapInterpolationModeHighQualityCubic, - }; - - // Contains the palette used by a sixel image. - struct Palette - { - // Limit to 256 both as the defacto maximum supported colors and to enable always using 8bpp indexed pixel format. - static constexpr UINT MaximumColorCount = 256; - - // Creates an empty palette. - Palette() = default; - - // Create a palette from the given source image, color count, transparency setting. - Palette(IWICImagingFactory* factory, IWICBitmapSource* bitmapSource, UINT colorCount, bool transparencyEnabled); - - // Create a palette combining the two palettes. Throws an exception if there are more than MaximumColorCount unique - // colors between the two. This can be avoided by intentionally dividing the available colors between the palettes - // when creating them. - Palette(const Palette& first, const Palette& second); - - // Gets the WIC palette object. - IWICPalette* Get() const; - - // Gets the color count for the palette. - size_t Size() const; - - // Gets the color at the given index in the palette. - WICColor& operator[](size_t index); - WICColor operator[](size_t index) const; - - private: - wil::com_ptr m_factory; - wil::com_ptr m_paletteObject; - std::vector m_palette; - }; - - // Allows access to the pixel data of an image source. - // Can be configured to translate and/or tile the view. - struct ImageView - { - // Creates a non-owning view using the given data. - ImageView(UINT width, UINT height, UINT stride, UINT byteCount, BYTE* bytes); - - // Create a view by locking a bitmap. - // This must be used from the same thread as the bitmap. - static ImageView Lock(IWICBitmap* imageSource); - - // Create a view by copying the pixels from the image. - static ImageView Copy(IWICBitmapSource* imageSource); - - // Translate the view by the given pixel counts. - // The pixel at [0, 0] of the original will be at [x, y]. - // If tile is true, the view will % coordinates outside of its dimensions back into its own view. - // If tile is false, coordinates outside of the view will be null. - void Translate(INT x, INT y, bool tile); - - // Gets the pixel of the view at the given coordinate. - // Returns null if the coordinate is outside of the view. - const BYTE* GetPixel(UINT x, UINT y) const; - - // Get the dimensions of the view. - UINT Width() const; - UINT Height() const; - - private: - ImageView() = default; - - bool m_tile = false; - UINT m_translateX = 0; - UINT m_translateY = 0; - - wil::com_ptr m_lockedImage; - std::unique_ptr m_copiedImage; - - UINT m_viewWidth = 0; - UINT m_viewHeight = 0; - UINT m_viewStride = 0; - UINT m_viewByteCount = 0; - BYTE* m_viewBytes = nullptr; - }; - - // The set of values that defines the rendered output. - struct RenderControls - { - // Yes, its right there in the name but the compiler can't read... - static constexpr UINT PixelsPerSixel = 6; - - // Each cell is always a height of 20 and a width of 10, regardless of the screen resolution of the terminal. - static constexpr UINT CellHeightInPixels = 20; - static constexpr UINT CellWidthInPixels = 10; - - Sixel::AspectRatio AspectRatio = AspectRatio::OneToOne; - bool TransparencyEnabled = true; - bool StretchSourceToFill = false; - bool UseRepeatSequence = false; - UINT ColorCount = Palette::MaximumColorCount; - UINT PixelWidth = 0; - UINT PixelHeight = 0; - Sixel::InterpolationMode InterpolationMode = InterpolationMode::HighQualityCubic; - - // The resulting sixel image will render to this size in terminal cells, - // consuming as much as possible of the given size without going over. - void RenderSizeInCells(UINT width, UINT height); - }; - - // Contains an image that can be manipulated and rendered to sixels. - struct ImageSource - { - // Create an image source from a file. - explicit ImageSource(const std::filesystem::path& imageFilePath); - - // Create an empty image source. - ImageSource() = default; - - // Create an image source from a stream. - ImageSource(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding); - - // Create an image source from bytes. - ImageSource(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding); - - // Resize the image to the given width and height, factoring in the target aspect ratio for rendering. - // If stretchToFill is true, the resulting image will be both the given width and height. - // If false, the resulting image will be at most the given width or height while preserving the aspect ratio. - void Resize(UINT pixelWidth, UINT pixelHeight, AspectRatio targetRenderRatio, bool stretchToFill = false, InterpolationMode interpolationMode = InterpolationMode::HighQualityCubic); - - // Resizes the image using the given render controls. - void Resize(const RenderControls& controls); - - // Creates a palette from the current image. - Palette CreatePalette(UINT colorCount, bool transparencyEnabled) const; - - // Creates a palette from the current image. - Palette CreatePalette(const RenderControls& controls) const; - - // Converts the image to be 8bpp indexed for the given palette. - void ApplyPalette(const Palette& palette); - - // Create a view by locking the image source. - // This must be used from the same thread as the image source. - ImageView Lock() const; - - // Create a view by copying the pixels from the image source. - ImageView Copy() const; - - private: - wil::com_ptr m_factory; - wil::com_ptr m_sourceImage; - }; - - // Allows one or more image sources to be rendered to a sixel output. - struct Compositor - { - // Create an empty compositor. - Compositor() = default; - - // Set the palette to be used by the compositor. - void Palette(Palette palette); - - // Adds a new view to the compositor. Each successive view will be behind all of the others. - void AddView(ImageView&& view); - - // Gets the number of views in the compositor. - size_t ViewCount() const; - - // Gets the color at the given index in the palette. - ImageView& operator[](size_t index); - const ImageView& operator[](size_t index) const; - - // Get the render controls for the compositor. - RenderControls& Controls(); - const RenderControls& Controls() const; - - // Render to sixel format for storage / use multiple times. - ConstructedSequence Render(); - - // Renders to sixel format directly to the stream. - void RenderTo(Execution::BaseStream& stream); - - // Renders to sixel format directly to the stream. - void RenderTo(Execution::OutputStream& stream); - - private: - RenderControls m_renderControls; - Sixel::Palette m_palette; - std::vector m_views; - }; - - // A helpful wrapper around the sixel image primitives that makes rendering a single image easier. - struct Image - { - // Create an image from a file. - Image(const std::filesystem::path& imageFilePath); - - // Create an image from a stream. - Image(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding); - - // Create an image from bytes. - Image(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding); - - // Set the aspect ratio of the result. - Image& AspectRatio(AspectRatio aspectRatio); - - // Determine whether transparency is enabled. - // This will affect whether transparent pixels are rendered or not. - Image& Transparency(bool transparencyEnabled); - - // If transparency is enabled, one of the colors will be reserved for it. - Image& ColorCount(UINT colorCount); - - // The resulting sixel image will render to this size in terminal cell pixels. - Image& RenderSizeInPixels(UINT width, UINT height); - - // The resulting sixel image will render to this size in terminal cells, - // consuming as much as possible of the given size without going over. - Image& RenderSizeInCells(UINT width, UINT height); - - // Only affects the scaling of the image that occurs when render size is set. - // When true, the source image will be stretched to fill the target size. - // When false, the source image will be scaled while keeping its original aspect ratio. - Image& StretchSourceToFill(bool stretchSourceToFill); - - // Compresses the output using repeat sequences. - Image& UseRepeatSequence(bool useRepeatSequence); - - // Render to sixel format for storage / use multiple times. - ConstructedSequence Render(); - - // Renders to sixel format directly to the output stream. - void RenderTo(Execution::OutputStream& stream); - - private: - // Creates a compositor for the image using the current render controls. - std::pair CreateCompositor(); - - ImageSource m_imageSource; - RenderControls m_renderControls; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ChannelStreams.h" +#include "VTSupport.h" +#include +#include +#include +#include + +namespace AppInstaller::CLI::VirtualTerminal::Sixel +{ + // Determines the height to width ratio of the pixels that make up a sixel (a sixel is 6 pixels tall and 1 pixel wide). + // Note that each cell is always a height of 20 and a width of 10, regardless of the screen resolution of the terminal. + // The 2:1 ratio will then result in each sixel being 12 of the 20 pixels of a cell. + enum class AspectRatio + { + OneToOne = 7, + TwoToOne = 0, + ThreeToOne = 3, + FiveToOne = 2, + }; + + // Determines the algorithm used when resizing the image. + enum class InterpolationMode + { + NearestNeighbor = WICBitmapInterpolationModeNearestNeighbor, + Linear = WICBitmapInterpolationModeLinear, + Cubic = WICBitmapInterpolationModeCubic, + Fant = WICBitmapInterpolationModeFant, + HighQualityCubic = WICBitmapInterpolationModeHighQualityCubic, + }; + + // Contains the palette used by a sixel image. + struct Palette + { + // Limit to 256 both as the defacto maximum supported colors and to enable always using 8bpp indexed pixel format. + static constexpr UINT MaximumColorCount = 256; + + // Creates an empty palette. + Palette() = default; + + // Create a palette from the given source image, color count, transparency setting. + Palette(IWICImagingFactory* factory, IWICBitmapSource* bitmapSource, UINT colorCount, bool transparencyEnabled); + + // Create a palette combining the two palettes. Throws an exception if there are more than MaximumColorCount unique + // colors between the two. This can be avoided by intentionally dividing the available colors between the palettes + // when creating them. + Palette(const Palette& first, const Palette& second); + + // Gets the WIC palette object. + IWICPalette* Get() const; + + // Gets the color count for the palette. + size_t Size() const; + + // Gets the color at the given index in the palette. + WICColor& operator[](size_t index); + WICColor operator[](size_t index) const; + + private: + wil::com_ptr m_factory; + wil::com_ptr m_paletteObject; + std::vector m_palette; + }; + + // Allows access to the pixel data of an image source. + // Can be configured to translate and/or tile the view. + struct ImageView + { + // Creates a non-owning view using the given data. + ImageView(UINT width, UINT height, UINT stride, UINT byteCount, BYTE* bytes); + + // Create a view by locking a bitmap. + // This must be used from the same thread as the bitmap. + static ImageView Lock(IWICBitmap* imageSource); + + // Create a view by copying the pixels from the image. + static ImageView Copy(IWICBitmapSource* imageSource); + + // Translate the view by the given pixel counts. + // The pixel at [0, 0] of the original will be at [x, y]. + // If tile is true, the view will % coordinates outside of its dimensions back into its own view. + // If tile is false, coordinates outside of the view will be null. + void Translate(INT x, INT y, bool tile); + + // Gets the pixel of the view at the given coordinate. + // Returns null if the coordinate is outside of the view. + const BYTE* GetPixel(UINT x, UINT y) const; + + // Get the dimensions of the view. + UINT Width() const; + UINT Height() const; + + private: + ImageView() = default; + + bool m_tile = false; + UINT m_translateX = 0; + UINT m_translateY = 0; + + wil::com_ptr m_lockedImage; + std::unique_ptr m_copiedImage; + + UINT m_viewWidth = 0; + UINT m_viewHeight = 0; + UINT m_viewStride = 0; + UINT m_viewByteCount = 0; + BYTE* m_viewBytes = nullptr; + }; + + // The set of values that defines the rendered output. + struct RenderControls + { + // Yes, its right there in the name but the compiler can't read... + static constexpr UINT PixelsPerSixel = 6; + + // Each cell is always a height of 20 and a width of 10, regardless of the screen resolution of the terminal. + static constexpr UINT CellHeightInPixels = 20; + static constexpr UINT CellWidthInPixels = 10; + + Sixel::AspectRatio AspectRatio = AspectRatio::OneToOne; + bool TransparencyEnabled = true; + bool StretchSourceToFill = false; + bool UseRepeatSequence = false; + UINT ColorCount = Palette::MaximumColorCount; + UINT PixelWidth = 0; + UINT PixelHeight = 0; + Sixel::InterpolationMode InterpolationMode = InterpolationMode::HighQualityCubic; + + // The resulting sixel image will render to this size in terminal cells, + // consuming as much as possible of the given size without going over. + void RenderSizeInCells(UINT width, UINT height); + }; + + // Contains an image that can be manipulated and rendered to sixels. + struct ImageSource + { + // Create an image source from a file. + explicit ImageSource(const std::filesystem::path& imageFilePath); + + // Create an empty image source. + ImageSource() = default; + + // Create an image source from a stream. + ImageSource(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding); + + // Create an image source from bytes. + ImageSource(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding); + + // Resize the image to the given width and height, factoring in the target aspect ratio for rendering. + // If stretchToFill is true, the resulting image will be both the given width and height. + // If false, the resulting image will be at most the given width or height while preserving the aspect ratio. + void Resize(UINT pixelWidth, UINT pixelHeight, AspectRatio targetRenderRatio, bool stretchToFill = false, InterpolationMode interpolationMode = InterpolationMode::HighQualityCubic); + + // Resizes the image using the given render controls. + void Resize(const RenderControls& controls); + + // Creates a palette from the current image. + Palette CreatePalette(UINT colorCount, bool transparencyEnabled) const; + + // Creates a palette from the current image. + Palette CreatePalette(const RenderControls& controls) const; + + // Converts the image to be 8bpp indexed for the given palette. + void ApplyPalette(const Palette& palette); + + // Create a view by locking the image source. + // This must be used from the same thread as the image source. + ImageView Lock() const; + + // Create a view by copying the pixels from the image source. + ImageView Copy() const; + + private: + wil::com_ptr m_factory; + wil::com_ptr m_sourceImage; + }; + + // Allows one or more image sources to be rendered to a sixel output. + struct Compositor + { + // Create an empty compositor. + Compositor() = default; + + // Set the palette to be used by the compositor. + void Palette(Palette palette); + + // Adds a new view to the compositor. Each successive view will be behind all of the others. + void AddView(ImageView&& view); + + // Gets the number of views in the compositor. + size_t ViewCount() const; + + // Gets the color at the given index in the palette. + ImageView& operator[](size_t index); + const ImageView& operator[](size_t index) const; + + // Get the render controls for the compositor. + RenderControls& Controls(); + const RenderControls& Controls() const; + + // Render to sixel format for storage / use multiple times. + ConstructedSequence Render(); + + // Renders to sixel format directly to the stream. + void RenderTo(Execution::BaseStream& stream); + + // Renders to sixel format directly to the stream. + void RenderTo(Execution::OutputStream& stream); + + private: + RenderControls m_renderControls; + Sixel::Palette m_palette; + std::vector m_views; + }; + + // A helpful wrapper around the sixel image primitives that makes rendering a single image easier. + struct Image + { + // Create an image from a file. + Image(const std::filesystem::path& imageFilePath); + + // Create an image from a stream. + Image(std::istream& imageStream, Manifest::IconFileTypeEnum imageEncoding); + + // Create an image from bytes. + Image(const std::vector& imageBytes, Manifest::IconFileTypeEnum imageEncoding); + + // Set the aspect ratio of the result. + Image& AspectRatio(AspectRatio aspectRatio); + + // Determine whether transparency is enabled. + // This will affect whether transparent pixels are rendered or not. + Image& Transparency(bool transparencyEnabled); + + // If transparency is enabled, one of the colors will be reserved for it. + Image& ColorCount(UINT colorCount); + + // The resulting sixel image will render to this size in terminal cell pixels. + Image& RenderSizeInPixels(UINT width, UINT height); + + // The resulting sixel image will render to this size in terminal cells, + // consuming as much as possible of the given size without going over. + Image& RenderSizeInCells(UINT width, UINT height); + + // Only affects the scaling of the image that occurs when render size is set. + // When true, the source image will be stretched to fill the target size. + // When false, the source image will be scaled while keeping its original aspect ratio. + Image& StretchSourceToFill(bool stretchSourceToFill); + + // Compresses the output using repeat sequences. + Image& UseRepeatSequence(bool useRepeatSequence); + + // Render to sixel format for storage / use multiple times. + ConstructedSequence Render(); + + // Renders to sixel format directly to the output stream. + void RenderTo(Execution::OutputStream& stream); + + private: + // Creates a compositor for the image using the current render controls. + std::pair CreateCompositor(); + + ImageSource m_imageSource; + RenderControls m_renderControls; + }; +} diff --git a/src/AppInstallerCLICore/TableOutput.h b/src/AppInstallerCLICore/TableOutput.h index 03a3e1bfe4..6bfb59848b 100644 --- a/src/AppInstallerCLICore/TableOutput.h +++ b/src/AppInstallerCLICore/TableOutput.h @@ -1,218 +1,218 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionReporter.h" -#include "Resources.h" - -#include -#include -#include -#include - - -namespace AppInstaller::CLI::Execution -{ - // Enables output data in a table format. - // TODO: Improve for use with sparse data. - template - struct TableOutput - { - using header_t = std::array; - using line_t = std::array; - - TableOutput(Reporter& reporter, header_t&& header) : - m_reporter(reporter), - m_hasConsole(GetConsoleWidth().has_value()) - { - for (size_t i = 0; i < FieldCount; ++i) - { - m_columns[i].Name = std::move(header[i]); - m_columns[i].MinLength = Utility::UTF8ColumnWidth(m_columns[i].Name.get()); - m_columns[i].MaxLength = 0; - } - } - - void OutputLine(line_t&& line) - { - m_empty = false; - - // Always buffer every row so that column widths are computed from the full dataset - // before any output is written. This guarantees that the widest value in any column - // is always fully visible and columns are perfectly aligned, whether output goes to - // a console or is redirected. Complete() triggers the actual output. - m_buffer.emplace_back(std::move(line)); - } - - void Complete() - { - if (!m_empty) - { - EvaluateAndFlushBuffer(); - } - } - - bool IsEmpty() - { - return m_empty; - } - - private: - // A column in the table. - struct Column - { - Resource::LocString Name; - size_t MinLength = 0; - size_t MaxLength = 0; - bool SpaceAfter = true; - }; - - Reporter& m_reporter; - std::array m_columns; - std::vector m_buffer; - bool m_bufferEvaluated = false; - bool m_empty = true; - bool m_hasConsole = false; - - void EvaluateAndFlushBuffer() - { - if (m_bufferEvaluated) - { - return; - } - - // Determine the maximum length for all columns - for (const auto& line : m_buffer) - { - for (size_t i = 0; i < FieldCount; ++i) - { - m_columns[i].MaxLength = std::max(m_columns[i].MaxLength, Utility::UTF8ColumnWidth(line[i])); - } - } - - // If there are actually columns with data, then also bring in the minimum size - for (size_t i = 0; i < FieldCount; ++i) - { - if (m_columns[i].MaxLength) - { - m_columns[i].MaxLength = std::max(m_columns[i].MaxLength, m_columns[i].MinLength); - } - } - - // Only output the extra space if: - // 1. Not the last field - m_columns[FieldCount - 1].SpaceAfter = false; - - // 2. Not empty (taken care of by not doing anything if empty) - // 3. There are non-empty fields after - for (size_t i = FieldCount - 1; i > 0; --i) - { - if (m_columns[i].MaxLength) - { - break; - } - else - { - m_columns[i - 1].SpaceAfter = false; - } - } - - // Determine the total width required to not truncate any columns - size_t totalRequired = 0; - - for (size_t i = 0; i < FieldCount; ++i) - { - totalRequired += m_columns[i].MaxLength + (m_columns[i].SpaceAfter ? 1 : 0); - } - - auto consoleWidthOpt = GetConsoleWidth(); - - // If there is a console and the total space would be too big, shrink columns. - // We don't want to use the last column, lest we auto-wrap. - // When there is no console (e.g. output redirected to a file), skip truncation entirely. - if (consoleWidthOpt && totalRequired >= *consoleWidthOpt) - { - size_t extra = (totalRequired - *consoleWidthOpt) + 1; - - while (extra) - { - size_t targetIndex = 0; - size_t targetVal = m_columns[0].MaxLength; - for (size_t j = 1; j < FieldCount; ++j) - { - if (m_columns[j].MaxLength > targetVal) - { - targetIndex = j; - targetVal = m_columns[j].MaxLength; - } - } - m_columns[targetIndex].MaxLength -= 1; - extra -= 1; - } - - totalRequired = *consoleWidthOpt - 1; - } - - // Header line - line_t headerLine; - - for (size_t i = 0; i < FieldCount; ++i) - { - headerLine[i] = m_columns[i].Name.get(); - } - - OutputLineToStream(headerLine); - - m_reporter.Info() << std::string(totalRequired, '-') << std::endl; - - for (const auto& line : m_buffer) - { - OutputLineToStream(line); - } - - m_bufferEvaluated = true; - } - - void OutputLineToStream(const line_t& line) - { - auto out = m_reporter.Info(); - - for (size_t i = 0; i < FieldCount; ++i) - { - const auto& col = m_columns[i]; - - if (col.MaxLength) - { - size_t valueLength = Utility::UTF8ColumnWidth(line[i]); - - if (valueLength > col.MaxLength) - { - size_t actualWidth; - out << Utility::UTF8TrimRightToColumnWidth(line[i], col.MaxLength - 1, actualWidth) << "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (…) character - - // Some characters take 2 unit space, the trimmed string length might be 1 less than the expected length. - if (actualWidth != col.MaxLength - 1) - { - out << ' '; - } - - if (col.SpaceAfter) - { - out << ' '; - } - } - else - { - out << line[i]; - - if (col.SpaceAfter) - { - out << std::string(col.MaxLength - valueLength + 1, ' '); - } - } - } - } - - out << std::endl; - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionReporter.h" +#include "Resources.h" + +#include +#include +#include +#include + + +namespace AppInstaller::CLI::Execution +{ + // Enables output data in a table format. + // TODO: Improve for use with sparse data. + template + struct TableOutput + { + using header_t = std::array; + using line_t = std::array; + + TableOutput(Reporter& reporter, header_t&& header) : + m_reporter(reporter), + m_hasConsole(GetConsoleWidth().has_value()) + { + for (size_t i = 0; i < FieldCount; ++i) + { + m_columns[i].Name = std::move(header[i]); + m_columns[i].MinLength = Utility::UTF8ColumnWidth(m_columns[i].Name.get()); + m_columns[i].MaxLength = 0; + } + } + + void OutputLine(line_t&& line) + { + m_empty = false; + + // Always buffer every row so that column widths are computed from the full dataset + // before any output is written. This guarantees that the widest value in any column + // is always fully visible and columns are perfectly aligned, whether output goes to + // a console or is redirected. Complete() triggers the actual output. + m_buffer.emplace_back(std::move(line)); + } + + void Complete() + { + if (!m_empty) + { + EvaluateAndFlushBuffer(); + } + } + + bool IsEmpty() + { + return m_empty; + } + + private: + // A column in the table. + struct Column + { + Resource::LocString Name; + size_t MinLength = 0; + size_t MaxLength = 0; + bool SpaceAfter = true; + }; + + Reporter& m_reporter; + std::array m_columns; + std::vector m_buffer; + bool m_bufferEvaluated = false; + bool m_empty = true; + bool m_hasConsole = false; + + void EvaluateAndFlushBuffer() + { + if (m_bufferEvaluated) + { + return; + } + + // Determine the maximum length for all columns + for (const auto& line : m_buffer) + { + for (size_t i = 0; i < FieldCount; ++i) + { + m_columns[i].MaxLength = std::max(m_columns[i].MaxLength, Utility::UTF8ColumnWidth(line[i])); + } + } + + // If there are actually columns with data, then also bring in the minimum size + for (size_t i = 0; i < FieldCount; ++i) + { + if (m_columns[i].MaxLength) + { + m_columns[i].MaxLength = std::max(m_columns[i].MaxLength, m_columns[i].MinLength); + } + } + + // Only output the extra space if: + // 1. Not the last field + m_columns[FieldCount - 1].SpaceAfter = false; + + // 2. Not empty (taken care of by not doing anything if empty) + // 3. There are non-empty fields after + for (size_t i = FieldCount - 1; i > 0; --i) + { + if (m_columns[i].MaxLength) + { + break; + } + else + { + m_columns[i - 1].SpaceAfter = false; + } + } + + // Determine the total width required to not truncate any columns + size_t totalRequired = 0; + + for (size_t i = 0; i < FieldCount; ++i) + { + totalRequired += m_columns[i].MaxLength + (m_columns[i].SpaceAfter ? 1 : 0); + } + + auto consoleWidthOpt = GetConsoleWidth(); + + // If there is a console and the total space would be too big, shrink columns. + // We don't want to use the last column, lest we auto-wrap. + // When there is no console (e.g. output redirected to a file), skip truncation entirely. + if (consoleWidthOpt && totalRequired >= *consoleWidthOpt) + { + size_t extra = (totalRequired - *consoleWidthOpt) + 1; + + while (extra) + { + size_t targetIndex = 0; + size_t targetVal = m_columns[0].MaxLength; + for (size_t j = 1; j < FieldCount; ++j) + { + if (m_columns[j].MaxLength > targetVal) + { + targetIndex = j; + targetVal = m_columns[j].MaxLength; + } + } + m_columns[targetIndex].MaxLength -= 1; + extra -= 1; + } + + totalRequired = *consoleWidthOpt - 1; + } + + // Header line + line_t headerLine; + + for (size_t i = 0; i < FieldCount; ++i) + { + headerLine[i] = m_columns[i].Name.get(); + } + + OutputLineToStream(headerLine); + + m_reporter.Info() << std::string(totalRequired, '-') << std::endl; + + for (const auto& line : m_buffer) + { + OutputLineToStream(line); + } + + m_bufferEvaluated = true; + } + + void OutputLineToStream(const line_t& line) + { + auto out = m_reporter.Info(); + + for (size_t i = 0; i < FieldCount; ++i) + { + const auto& col = m_columns[i]; + + if (col.MaxLength) + { + size_t valueLength = Utility::UTF8ColumnWidth(line[i]); + + if (valueLength > col.MaxLength) + { + size_t actualWidth; + out << Utility::UTF8TrimRightToColumnWidth(line[i], col.MaxLength - 1, actualWidth) << "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (…) character + + // Some characters take 2 unit space, the trimmed string length might be 1 less than the expected length. + if (actualWidth != col.MaxLength - 1) + { + out << ' '; + } + + if (col.SpaceAfter) + { + out << ' '; + } + } + else + { + out << line[i]; + + if (col.SpaceAfter) + { + out << std::string(col.MaxLength - valueLength + 1, ' '); + } + } + } + } + + out << std::endl; + } + }; +} diff --git a/src/AppInstallerCLICore/VTSupport.cpp b/src/AppInstallerCLICore/VTSupport.cpp index 3f3fcbc705..318684e127 100644 --- a/src/AppInstallerCLICore/VTSupport.cpp +++ b/src/AppInstallerCLICore/VTSupport.cpp @@ -1,341 +1,341 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "VTSupport.h" -#include -#include - -namespace AppInstaller::CLI::VirtualTerminal -{ - namespace - { - TextFormat::Color GetAccentColorFromSystem() - { - using namespace winrt::Windows::UI::ViewManagement; - - UISettings settings; - auto color = settings.GetColorValue(UIColorType::Accent); - return { color.R, color.G, color.B }; - } - - bool InitializeMode(DWORD handle, DWORD& previousMode, std::initializer_list modeModifierFallbacks, DWORD disabledFlags = 0) - { - HANDLE hStd = GetStdHandle(handle); - if (hStd == INVALID_HANDLE_VALUE) - { - LOG_LAST_ERROR(); - } - else if (hStd == NULL) - { - AICLI_LOG(CLI, Info, << "VT not enabled due to null handle [" << handle << "]"); - } - else - { - if (!GetConsoleMode(hStd, &previousMode)) - { - // If the user redirects output, the handle will be invalid for this function. - // Don't log it in that case. - LOG_LAST_ERROR_IF(GetLastError() != ERROR_INVALID_HANDLE); - } - else - { - for (DWORD mode : modeModifierFallbacks) - { - DWORD outMode = (previousMode & ~disabledFlags) | mode; - if (!SetConsoleMode(hStd, outMode)) - { - // Even if it is a different error, log it and try to carry on. - LOG_LAST_ERROR_IF(GetLastError() != STATUS_INVALID_PARAMETER); - } - else - { - return true; - } - } - } - } - - return false; - } - - // Extracts a VT sequence, expected one of the form ESCAPE + prefix + result + suffix, returning the result part. - std::string ExtractSequence(std::istream& inStream, std::string_view prefix, std::string_view suffix) - { - // Force discovery of available input - std::ignore = inStream.peek(); - - static constexpr std::streamsize s_bufferSize = 1024; - char buffer[s_bufferSize]; - std::streamsize bytesRead = inStream.readsome(buffer, s_bufferSize); - THROW_HR_IF(E_UNEXPECTED, bytesRead >= s_bufferSize); - - std::string_view resultView{ buffer, static_cast(bytesRead) }; - size_t escapeIndex = resultView.find(AICLI_VT_ESCAPE[0]); - if (escapeIndex == std::string_view::npos) - { - return {}; - } - - resultView = resultView.substr(escapeIndex); - size_t overheadLength = 1 + prefix.length() + suffix.length(); - if (resultView.length() <= overheadLength || - resultView.substr(1, prefix.length()) != prefix || - resultView.substr(resultView.length() - suffix.length()) != suffix) - { - return {}; - } - - return std::string{ resultView.substr(1 + prefix.length(), resultView.length() - overheadLength) }; - } - } - - ConsoleModeRestoreBase::ConsoleModeRestoreBase(DWORD handle) : m_handle(handle) {} - - ConsoleModeRestoreBase::~ConsoleModeRestoreBase() - { - if (m_token) - { - LOG_LAST_ERROR_IF(!SetConsoleMode(GetStdHandle(m_handle), m_previousMode)); - m_token = false; - } - } - - ConsoleModeRestore::ConsoleModeRestore() : ConsoleModeRestoreBase(STD_OUTPUT_HANDLE) - { - m_token = InitializeMode(STD_OUTPUT_HANDLE, m_previousMode, { ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN, ENABLE_VIRTUAL_TERMINAL_PROCESSING }); - } - - const ConsoleModeRestore& ConsoleModeRestore::Instance() - { - static ConsoleModeRestore s_instance; - return s_instance; - } - - ConsoleInputModeRestore::ConsoleInputModeRestore() : ConsoleModeRestoreBase(STD_INPUT_HANDLE) - { - m_token = InitializeMode(STD_INPUT_HANDLE, m_previousMode, { ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT }, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); - } - - void ConstructedSequence::Append(const Sequence& sequence) - { - if (!sequence.Get().empty()) - { - m_str += sequence.Get(); - Set(m_str); - } - } - - void ConstructedSequence::Clear() - { - m_str.clear(); - Set(m_str); - } - -// The beginning of a Control Sequence Introducer -#define AICLI_VT_CSI AICLI_VT_ESCAPE "[" - -// The beginning of an Operating system command -#define AICLI_VT_OSC AICLI_VT_ESCAPE "]" - - PrimaryDeviceAttributes::PrimaryDeviceAttributes(std::ostream& outStream, std::istream& inStream) - { - try - { - ConsoleInputModeRestore inputMode; - if (!inputMode.IsVTEnabled()) - { - return; - } - - // Send DA1 Primary Device Attributes request - outStream << AICLI_VT_CSI << "0c"; - outStream.flush(); - - // Response is of the form AICLI_VT_CSI ? ; ( ;)* c - std::string sequence = ExtractSequence(inStream, "[?", "c"); - std::vector values = Utility::Split(sequence, ';'); - - if (!values.empty()) - { - m_conformanceLevel = std::stoul(values[0]); - } - - for (size_t i = 1; i < values.size(); ++i) - { - m_extensions |= 1ull << std::stoul(values[i]); - } - } - CATCH_LOG(); - } - - bool PrimaryDeviceAttributes::Supports(Extension extension) const - { - uint64_t extensionMask = 1ull << ToIntegral(extension); - return (m_extensions & extensionMask) == extensionMask; - } - - namespace Cursor - { - namespace Position - { - ConstructedSequence Up(int16_t cells) - { - THROW_HR_IF(E_INVALIDARG, cells < 0); - std::ostringstream result; - result << AICLI_VT_CSI << cells << 'A'; - return ConstructedSequence{ std::move(result).str() }; - } - - ConstructedSequence Down(int16_t cells) - { - THROW_HR_IF(E_INVALIDARG, cells < 0); - std::ostringstream result; - result << AICLI_VT_CSI << cells << 'B'; - return ConstructedSequence{ std::move(result).str() }; - } - - ConstructedSequence Forward(int16_t cells) - { - THROW_HR_IF(E_INVALIDARG, cells < 0); - std::ostringstream result; - result << AICLI_VT_CSI << cells << 'C'; - return ConstructedSequence{ std::move(result).str() }; - } - - ConstructedSequence Backward(int16_t cells) - { - THROW_HR_IF(E_INVALIDARG, cells < 0); - std::ostringstream result; - result << AICLI_VT_CSI << cells << 'D'; - return ConstructedSequence{ std::move(result).str() }; - } - } - - namespace Visibility - { - const Sequence EnableBlink{ AICLI_VT_CSI "?12h" }; - const Sequence DisableBlink{ AICLI_VT_CSI "?12l" }; - const Sequence EnableShow{ AICLI_VT_CSI "?25h" }; - const Sequence DisableShow{ AICLI_VT_CSI "?25l" }; - } - } - - namespace TextFormat - { -// Define a text formatting sequence with an integer id -#define AICLI_VT_TEXTFORMAT(_id_) AICLI_VT_CSI #_id_ "m" - - const Sequence Default{ AICLI_VT_TEXTFORMAT(0) }; - const Sequence Negative{ AICLI_VT_TEXTFORMAT(7) }; - - Color Color::GetAccentColor() - { - static Color accent{ GetAccentColorFromSystem() }; - return accent; - } - - namespace Foreground - { - const Sequence Bright{ AICLI_VT_TEXTFORMAT(1) }; - const Sequence NoBright{ AICLI_VT_TEXTFORMAT(22) }; - - const Sequence BrightRed{ AICLI_VT_TEXTFORMAT(91) }; - const Sequence BrightGreen{ AICLI_VT_TEXTFORMAT(92) }; - const Sequence BrightYellow{ AICLI_VT_TEXTFORMAT(93) }; - const Sequence BrightBlue{ AICLI_VT_TEXTFORMAT(94) }; - const Sequence BrightMagenta{ AICLI_VT_TEXTFORMAT(95) }; - const Sequence BrightCyan{ AICLI_VT_TEXTFORMAT(96) }; - const Sequence BrightWhite{ AICLI_VT_TEXTFORMAT(97) }; - - ConstructedSequence Extended(const Color& color) - { - std::ostringstream result; - result << AICLI_VT_CSI "38;2;" << static_cast(color.R) << ';' << static_cast(color.G) << ';' << static_cast(color.B) << 'm'; - return ConstructedSequence{ std::move(result).str() }; - } - } - - namespace Background - { - ConstructedSequence Extended(const Color& color) - { - std::ostringstream result; - result << AICLI_VT_CSI "48;2;" << static_cast(color.R) << ';' << static_cast(color.G) << ';' << static_cast(color.B) << 'm'; - return ConstructedSequence{ std::move(result).str() }; - } - } - - ConstructedSequence Hyperlink(const std::string& text, const std::string& ref) - { - std::ostringstream result; - result << AICLI_VT_OSC "8;;" << ref << AICLI_VT_ESCAPE << "\\" << text << AICLI_VT_OSC << "8;;" << AICLI_VT_ESCAPE << "\\"; - return ConstructedSequence{ std::move(result).str() }; - } - } - - namespace TextModification - { - const Sequence EraseLineForward{ AICLI_VT_CSI "0K" }; - const Sequence EraseLineBackward{ AICLI_VT_CSI "1K" }; - const Sequence EraseLineEntirely{ AICLI_VT_CSI "2K" }; - } - - namespace Progress - { - ConstructedSequence Construct(State state, std::optional percentage) - { - // See https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC - - THROW_HR_IF(E_BOUNDS, percentage.has_value() && percentage > 100u); - - // Workaround some quirks in the Windows Terminal implementation of the progress OSC sequence - switch (state) - { - case State::None: - case State::Indeterminate: - // Windows Terminal does not recognize the OSC sequence if the progress value is left out. - // As a workaround, we can specify an arbitrary value since it does not matter for None and Indeterminate states. - percentage = percentage.value_or(0); - break; - case State::Normal: - case State::Error: - case State::Paused: - // Windows Terminal does not support switching progress states without also setting a progress value at the same time, - // so we disallow this case for now. - THROW_HR_IF(E_INVALIDARG, !percentage.has_value()); - break; - } - - int stateId; - switch (state) - { - case State::None: - stateId = 0; - break; - case State::Indeterminate: - stateId = 3; - break; - case State::Normal: - stateId = 1; - break; - case State::Error: - stateId = 2; - break; - case State::Paused: - stateId = 4; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - std::ostringstream result; - result << AICLI_VT_OSC "9;4;" << stateId << ";"; - if (percentage.has_value()) - { - result << percentage.value(); - } - result << AICLI_VT_ESCAPE << "\\"; - return ConstructedSequence{ std::move(result).str() }; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "VTSupport.h" +#include +#include + +namespace AppInstaller::CLI::VirtualTerminal +{ + namespace + { + TextFormat::Color GetAccentColorFromSystem() + { + using namespace winrt::Windows::UI::ViewManagement; + + UISettings settings; + auto color = settings.GetColorValue(UIColorType::Accent); + return { color.R, color.G, color.B }; + } + + bool InitializeMode(DWORD handle, DWORD& previousMode, std::initializer_list modeModifierFallbacks, DWORD disabledFlags = 0) + { + HANDLE hStd = GetStdHandle(handle); + if (hStd == INVALID_HANDLE_VALUE) + { + LOG_LAST_ERROR(); + } + else if (hStd == NULL) + { + AICLI_LOG(CLI, Info, << "VT not enabled due to null handle [" << handle << "]"); + } + else + { + if (!GetConsoleMode(hStd, &previousMode)) + { + // If the user redirects output, the handle will be invalid for this function. + // Don't log it in that case. + LOG_LAST_ERROR_IF(GetLastError() != ERROR_INVALID_HANDLE); + } + else + { + for (DWORD mode : modeModifierFallbacks) + { + DWORD outMode = (previousMode & ~disabledFlags) | mode; + if (!SetConsoleMode(hStd, outMode)) + { + // Even if it is a different error, log it and try to carry on. + LOG_LAST_ERROR_IF(GetLastError() != STATUS_INVALID_PARAMETER); + } + else + { + return true; + } + } + } + } + + return false; + } + + // Extracts a VT sequence, expected one of the form ESCAPE + prefix + result + suffix, returning the result part. + std::string ExtractSequence(std::istream& inStream, std::string_view prefix, std::string_view suffix) + { + // Force discovery of available input + std::ignore = inStream.peek(); + + static constexpr std::streamsize s_bufferSize = 1024; + char buffer[s_bufferSize]; + std::streamsize bytesRead = inStream.readsome(buffer, s_bufferSize); + THROW_HR_IF(E_UNEXPECTED, bytesRead >= s_bufferSize); + + std::string_view resultView{ buffer, static_cast(bytesRead) }; + size_t escapeIndex = resultView.find(AICLI_VT_ESCAPE[0]); + if (escapeIndex == std::string_view::npos) + { + return {}; + } + + resultView = resultView.substr(escapeIndex); + size_t overheadLength = 1 + prefix.length() + suffix.length(); + if (resultView.length() <= overheadLength || + resultView.substr(1, prefix.length()) != prefix || + resultView.substr(resultView.length() - suffix.length()) != suffix) + { + return {}; + } + + return std::string{ resultView.substr(1 + prefix.length(), resultView.length() - overheadLength) }; + } + } + + ConsoleModeRestoreBase::ConsoleModeRestoreBase(DWORD handle) : m_handle(handle) {} + + ConsoleModeRestoreBase::~ConsoleModeRestoreBase() + { + if (m_token) + { + LOG_LAST_ERROR_IF(!SetConsoleMode(GetStdHandle(m_handle), m_previousMode)); + m_token = false; + } + } + + ConsoleModeRestore::ConsoleModeRestore() : ConsoleModeRestoreBase(STD_OUTPUT_HANDLE) + { + m_token = InitializeMode(STD_OUTPUT_HANDLE, m_previousMode, { ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN, ENABLE_VIRTUAL_TERMINAL_PROCESSING }); + } + + const ConsoleModeRestore& ConsoleModeRestore::Instance() + { + static ConsoleModeRestore s_instance; + return s_instance; + } + + ConsoleInputModeRestore::ConsoleInputModeRestore() : ConsoleModeRestoreBase(STD_INPUT_HANDLE) + { + m_token = InitializeMode(STD_INPUT_HANDLE, m_previousMode, { ENABLE_EXTENDED_FLAGS | ENABLE_VIRTUAL_TERMINAL_INPUT }, ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT); + } + + void ConstructedSequence::Append(const Sequence& sequence) + { + if (!sequence.Get().empty()) + { + m_str += sequence.Get(); + Set(m_str); + } + } + + void ConstructedSequence::Clear() + { + m_str.clear(); + Set(m_str); + } + +// The beginning of a Control Sequence Introducer +#define AICLI_VT_CSI AICLI_VT_ESCAPE "[" + +// The beginning of an Operating system command +#define AICLI_VT_OSC AICLI_VT_ESCAPE "]" + + PrimaryDeviceAttributes::PrimaryDeviceAttributes(std::ostream& outStream, std::istream& inStream) + { + try + { + ConsoleInputModeRestore inputMode; + if (!inputMode.IsVTEnabled()) + { + return; + } + + // Send DA1 Primary Device Attributes request + outStream << AICLI_VT_CSI << "0c"; + outStream.flush(); + + // Response is of the form AICLI_VT_CSI ? ; ( ;)* c + std::string sequence = ExtractSequence(inStream, "[?", "c"); + std::vector values = Utility::Split(sequence, ';'); + + if (!values.empty()) + { + m_conformanceLevel = std::stoul(values[0]); + } + + for (size_t i = 1; i < values.size(); ++i) + { + m_extensions |= 1ull << std::stoul(values[i]); + } + } + CATCH_LOG(); + } + + bool PrimaryDeviceAttributes::Supports(Extension extension) const + { + uint64_t extensionMask = 1ull << ToIntegral(extension); + return (m_extensions & extensionMask) == extensionMask; + } + + namespace Cursor + { + namespace Position + { + ConstructedSequence Up(int16_t cells) + { + THROW_HR_IF(E_INVALIDARG, cells < 0); + std::ostringstream result; + result << AICLI_VT_CSI << cells << 'A'; + return ConstructedSequence{ std::move(result).str() }; + } + + ConstructedSequence Down(int16_t cells) + { + THROW_HR_IF(E_INVALIDARG, cells < 0); + std::ostringstream result; + result << AICLI_VT_CSI << cells << 'B'; + return ConstructedSequence{ std::move(result).str() }; + } + + ConstructedSequence Forward(int16_t cells) + { + THROW_HR_IF(E_INVALIDARG, cells < 0); + std::ostringstream result; + result << AICLI_VT_CSI << cells << 'C'; + return ConstructedSequence{ std::move(result).str() }; + } + + ConstructedSequence Backward(int16_t cells) + { + THROW_HR_IF(E_INVALIDARG, cells < 0); + std::ostringstream result; + result << AICLI_VT_CSI << cells << 'D'; + return ConstructedSequence{ std::move(result).str() }; + } + } + + namespace Visibility + { + const Sequence EnableBlink{ AICLI_VT_CSI "?12h" }; + const Sequence DisableBlink{ AICLI_VT_CSI "?12l" }; + const Sequence EnableShow{ AICLI_VT_CSI "?25h" }; + const Sequence DisableShow{ AICLI_VT_CSI "?25l" }; + } + } + + namespace TextFormat + { +// Define a text formatting sequence with an integer id +#define AICLI_VT_TEXTFORMAT(_id_) AICLI_VT_CSI #_id_ "m" + + const Sequence Default{ AICLI_VT_TEXTFORMAT(0) }; + const Sequence Negative{ AICLI_VT_TEXTFORMAT(7) }; + + Color Color::GetAccentColor() + { + static Color accent{ GetAccentColorFromSystem() }; + return accent; + } + + namespace Foreground + { + const Sequence Bright{ AICLI_VT_TEXTFORMAT(1) }; + const Sequence NoBright{ AICLI_VT_TEXTFORMAT(22) }; + + const Sequence BrightRed{ AICLI_VT_TEXTFORMAT(91) }; + const Sequence BrightGreen{ AICLI_VT_TEXTFORMAT(92) }; + const Sequence BrightYellow{ AICLI_VT_TEXTFORMAT(93) }; + const Sequence BrightBlue{ AICLI_VT_TEXTFORMAT(94) }; + const Sequence BrightMagenta{ AICLI_VT_TEXTFORMAT(95) }; + const Sequence BrightCyan{ AICLI_VT_TEXTFORMAT(96) }; + const Sequence BrightWhite{ AICLI_VT_TEXTFORMAT(97) }; + + ConstructedSequence Extended(const Color& color) + { + std::ostringstream result; + result << AICLI_VT_CSI "38;2;" << static_cast(color.R) << ';' << static_cast(color.G) << ';' << static_cast(color.B) << 'm'; + return ConstructedSequence{ std::move(result).str() }; + } + } + + namespace Background + { + ConstructedSequence Extended(const Color& color) + { + std::ostringstream result; + result << AICLI_VT_CSI "48;2;" << static_cast(color.R) << ';' << static_cast(color.G) << ';' << static_cast(color.B) << 'm'; + return ConstructedSequence{ std::move(result).str() }; + } + } + + ConstructedSequence Hyperlink(const std::string& text, const std::string& ref) + { + std::ostringstream result; + result << AICLI_VT_OSC "8;;" << ref << AICLI_VT_ESCAPE << "\\" << text << AICLI_VT_OSC << "8;;" << AICLI_VT_ESCAPE << "\\"; + return ConstructedSequence{ std::move(result).str() }; + } + } + + namespace TextModification + { + const Sequence EraseLineForward{ AICLI_VT_CSI "0K" }; + const Sequence EraseLineBackward{ AICLI_VT_CSI "1K" }; + const Sequence EraseLineEntirely{ AICLI_VT_CSI "2K" }; + } + + namespace Progress + { + ConstructedSequence Construct(State state, std::optional percentage) + { + // See https://conemu.github.io/en/AnsiEscapeCodes.html#ConEmu_specific_OSC + + THROW_HR_IF(E_BOUNDS, percentage.has_value() && percentage > 100u); + + // Workaround some quirks in the Windows Terminal implementation of the progress OSC sequence + switch (state) + { + case State::None: + case State::Indeterminate: + // Windows Terminal does not recognize the OSC sequence if the progress value is left out. + // As a workaround, we can specify an arbitrary value since it does not matter for None and Indeterminate states. + percentage = percentage.value_or(0); + break; + case State::Normal: + case State::Error: + case State::Paused: + // Windows Terminal does not support switching progress states without also setting a progress value at the same time, + // so we disallow this case for now. + THROW_HR_IF(E_INVALIDARG, !percentage.has_value()); + break; + } + + int stateId; + switch (state) + { + case State::None: + stateId = 0; + break; + case State::Indeterminate: + stateId = 3; + break; + case State::Normal: + stateId = 1; + break; + case State::Error: + stateId = 2; + break; + case State::Paused: + stateId = 4; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + std::ostringstream result; + result << AICLI_VT_OSC "9;4;" << stateId << ";"; + if (percentage.has_value()) + { + result << percentage.value(); + } + result << AICLI_VT_ESCAPE << "\\"; + return ConstructedSequence{ std::move(result).str() }; + } + } +} diff --git a/src/AppInstallerCLICore/VTSupport.h b/src/AppInstallerCLICore/VTSupport.h index e345260337..4fb807d6ad 100644 --- a/src/AppInstallerCLICore/VTSupport.h +++ b/src/AppInstallerCLICore/VTSupport.h @@ -1,233 +1,233 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include -#include -#include -#include - - -// The escape character that begins all VT sequences -#define AICLI_VT_ESCAPE "\x1b" - -namespace AppInstaller::CLI::VirtualTerminal -{ - // RAII class to enable VT support and restore the console mode. - struct ConsoleModeRestoreBase - { - ConsoleModeRestoreBase(DWORD handle); - ~ConsoleModeRestoreBase(); - - ConsoleModeRestoreBase(const ConsoleModeRestoreBase&) = delete; - ConsoleModeRestoreBase& operator=(const ConsoleModeRestoreBase&) = delete; - - ConsoleModeRestoreBase(ConsoleModeRestoreBase&&) = default; - ConsoleModeRestoreBase& operator=(ConsoleModeRestoreBase&&) = default; - - // Returns true if VT support has been enabled for the console. - bool IsVTEnabled() const { return m_token; } - - protected: - DestructionToken m_token = false; - DWORD m_handle = 0; - DWORD m_previousMode = 0; - }; - - // RAII class to enable VT output support and restore the console mode. - struct ConsoleModeRestore : public ConsoleModeRestoreBase - { - ConsoleModeRestore(const ConsoleModeRestore&) = delete; - ConsoleModeRestore& operator=(const ConsoleModeRestore&) = delete; - - ConsoleModeRestore(ConsoleModeRestore&&) = default; - ConsoleModeRestore& operator=(ConsoleModeRestore&&) = default; - - // Gets the singleton. - static const ConsoleModeRestore& Instance(); - - private: - ConsoleModeRestore(); - }; - - // RAII class to enable VT input support and restore the console mode. - struct ConsoleInputModeRestore : public ConsoleModeRestoreBase - { - ConsoleInputModeRestore(); - - ConsoleInputModeRestore(const ConsoleInputModeRestore&) = delete; - ConsoleInputModeRestore& operator=(const ConsoleInputModeRestore&) = delete; - - ConsoleInputModeRestore(ConsoleInputModeRestore&&) = default; - ConsoleInputModeRestore& operator=(ConsoleInputModeRestore&&) = default; - }; - - // The base for all VT sequences. - struct Sequence - { - constexpr Sequence() = default; - explicit constexpr Sequence(std::string_view c) : m_chars(c) {} - - std::string_view Get() const { return m_chars; } - - protected: - void Set(const std::string& s) { m_chars = s; } - - private: - std::string_view m_chars; - }; - - // A VT sequence that is constructed at runtime. - struct ConstructedSequence : public Sequence - { - ConstructedSequence() { Set(m_str); } - explicit ConstructedSequence(std::string s) : m_str(std::move(s)) { Set(m_str); } - - ConstructedSequence(const ConstructedSequence& other) : m_str(other.m_str) { Set(m_str); } - ConstructedSequence& operator=(const ConstructedSequence& other) { m_str = other.m_str; Set(m_str); return *this; } - - ConstructedSequence(ConstructedSequence&& other) noexcept : m_str(std::move(other.m_str)) { Set(m_str); } - ConstructedSequence& operator=(ConstructedSequence&& other) noexcept { m_str = std::move(other.m_str); Set(m_str); return *this; } - - void Append(const Sequence& sequence); - - void Clear(); - - private: - std::string m_str; - }; - - // Below are mapped to the sequences described here: - // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences - - // Contains the response to a DA1 (Primary Device Attributes) request. - struct PrimaryDeviceAttributes - { - // Queries the device attributes on creation. - PrimaryDeviceAttributes(std::ostream& outStream, std::istream& inStream); - - // The extensions that a device may support. - enum class Extension - { - Columns132 = 1, - PrinterPort = 2, - Sixel = 4, - SelectiveErase = 6, - SoftCharacterSet = 7, - UserDefinedKeys = 8, - NationalReplacementCharacterSets = 9, - SoftCharacterSet2 = 12, - EightBitInterface = 14, - TechnicalCharacterSet = 15, - WindowingCapability = 18, - HorizontalScrolling = 21, - ColorText = 22, - Greek = 23, - Turkish = 24, - RectangularAreaOperations = 28, - TextMacros = 32, - ISO_Latin2CharacterSet = 42, - PC_Term = 44, - SoftKeyMap = 45, - ASCII_Emulation = 46, - }; - - // Determines if the given extension is supported. - bool Supports(Extension extension) const; - - private: - uint32_t m_conformanceLevel = 0; - uint64_t m_extensions = 0; - }; - - namespace Cursor - { - namespace Position - { - ConstructedSequence Up(int16_t cells); - ConstructedSequence Down(int16_t cells); - ConstructedSequence Forward(int16_t cells); - ConstructedSequence Backward(int16_t cells); - } - - namespace Visibility - { - extern const Sequence EnableBlink; - extern const Sequence DisableBlink; - extern const Sequence EnableShow; - extern const Sequence DisableShow; - } - } - - namespace TextFormat - { - // Returns all attributes to the default state prior to modification - extern const Sequence Default; - - // Swaps foreground and background colors - extern const Sequence Negative; - - // A color, used in constructed sequences. - struct Color - { - uint8_t R; - uint8_t G; - uint8_t B; - - static Color GetAccentColor(); - }; - - namespace Foreground - { - // Applies brightness/intensity flag to foreground color - extern const Sequence Bright; - // Removes brightness/intensity flag from foreground color - extern const Sequence NoBright; - - extern const Sequence BrightRed; - extern const Sequence BrightGreen; - extern const Sequence BrightYellow; - extern const Sequence BrightBlue; - extern const Sequence BrightMagenta; - extern const Sequence BrightCyan; - extern const Sequence BrightWhite; - - ConstructedSequence Extended(const Color& color); - } - - namespace Background - { - ConstructedSequence Extended(const Color& color); - } - - ConstructedSequence Hyperlink(const std::string& text, const std::string& ref); - } - - namespace TextModification - { - extern const Sequence EraseLineForward; - extern const Sequence EraseLineBackward; - extern const Sequence EraseLineEntirely; - } - - namespace Progress - { - enum class State - { - None, - Indeterminate, - Normal, - Paused, - Error - }; - - ConstructedSequence Construct(State state, std::optional percentage = std::nullopt); - } -} - -inline std::ostream& operator<<(std::ostream& o, const AppInstaller::CLI::VirtualTerminal::Sequence& s) -{ - return (o << s.Get()); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include +#include +#include +#include + + +// The escape character that begins all VT sequences +#define AICLI_VT_ESCAPE "\x1b" + +namespace AppInstaller::CLI::VirtualTerminal +{ + // RAII class to enable VT support and restore the console mode. + struct ConsoleModeRestoreBase + { + ConsoleModeRestoreBase(DWORD handle); + ~ConsoleModeRestoreBase(); + + ConsoleModeRestoreBase(const ConsoleModeRestoreBase&) = delete; + ConsoleModeRestoreBase& operator=(const ConsoleModeRestoreBase&) = delete; + + ConsoleModeRestoreBase(ConsoleModeRestoreBase&&) = default; + ConsoleModeRestoreBase& operator=(ConsoleModeRestoreBase&&) = default; + + // Returns true if VT support has been enabled for the console. + bool IsVTEnabled() const { return m_token; } + + protected: + DestructionToken m_token = false; + DWORD m_handle = 0; + DWORD m_previousMode = 0; + }; + + // RAII class to enable VT output support and restore the console mode. + struct ConsoleModeRestore : public ConsoleModeRestoreBase + { + ConsoleModeRestore(const ConsoleModeRestore&) = delete; + ConsoleModeRestore& operator=(const ConsoleModeRestore&) = delete; + + ConsoleModeRestore(ConsoleModeRestore&&) = default; + ConsoleModeRestore& operator=(ConsoleModeRestore&&) = default; + + // Gets the singleton. + static const ConsoleModeRestore& Instance(); + + private: + ConsoleModeRestore(); + }; + + // RAII class to enable VT input support and restore the console mode. + struct ConsoleInputModeRestore : public ConsoleModeRestoreBase + { + ConsoleInputModeRestore(); + + ConsoleInputModeRestore(const ConsoleInputModeRestore&) = delete; + ConsoleInputModeRestore& operator=(const ConsoleInputModeRestore&) = delete; + + ConsoleInputModeRestore(ConsoleInputModeRestore&&) = default; + ConsoleInputModeRestore& operator=(ConsoleInputModeRestore&&) = default; + }; + + // The base for all VT sequences. + struct Sequence + { + constexpr Sequence() = default; + explicit constexpr Sequence(std::string_view c) : m_chars(c) {} + + std::string_view Get() const { return m_chars; } + + protected: + void Set(const std::string& s) { m_chars = s; } + + private: + std::string_view m_chars; + }; + + // A VT sequence that is constructed at runtime. + struct ConstructedSequence : public Sequence + { + ConstructedSequence() { Set(m_str); } + explicit ConstructedSequence(std::string s) : m_str(std::move(s)) { Set(m_str); } + + ConstructedSequence(const ConstructedSequence& other) : m_str(other.m_str) { Set(m_str); } + ConstructedSequence& operator=(const ConstructedSequence& other) { m_str = other.m_str; Set(m_str); return *this; } + + ConstructedSequence(ConstructedSequence&& other) noexcept : m_str(std::move(other.m_str)) { Set(m_str); } + ConstructedSequence& operator=(ConstructedSequence&& other) noexcept { m_str = std::move(other.m_str); Set(m_str); return *this; } + + void Append(const Sequence& sequence); + + void Clear(); + + private: + std::string m_str; + }; + + // Below are mapped to the sequences described here: + // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences + + // Contains the response to a DA1 (Primary Device Attributes) request. + struct PrimaryDeviceAttributes + { + // Queries the device attributes on creation. + PrimaryDeviceAttributes(std::ostream& outStream, std::istream& inStream); + + // The extensions that a device may support. + enum class Extension + { + Columns132 = 1, + PrinterPort = 2, + Sixel = 4, + SelectiveErase = 6, + SoftCharacterSet = 7, + UserDefinedKeys = 8, + NationalReplacementCharacterSets = 9, + SoftCharacterSet2 = 12, + EightBitInterface = 14, + TechnicalCharacterSet = 15, + WindowingCapability = 18, + HorizontalScrolling = 21, + ColorText = 22, + Greek = 23, + Turkish = 24, + RectangularAreaOperations = 28, + TextMacros = 32, + ISO_Latin2CharacterSet = 42, + PC_Term = 44, + SoftKeyMap = 45, + ASCII_Emulation = 46, + }; + + // Determines if the given extension is supported. + bool Supports(Extension extension) const; + + private: + uint32_t m_conformanceLevel = 0; + uint64_t m_extensions = 0; + }; + + namespace Cursor + { + namespace Position + { + ConstructedSequence Up(int16_t cells); + ConstructedSequence Down(int16_t cells); + ConstructedSequence Forward(int16_t cells); + ConstructedSequence Backward(int16_t cells); + } + + namespace Visibility + { + extern const Sequence EnableBlink; + extern const Sequence DisableBlink; + extern const Sequence EnableShow; + extern const Sequence DisableShow; + } + } + + namespace TextFormat + { + // Returns all attributes to the default state prior to modification + extern const Sequence Default; + + // Swaps foreground and background colors + extern const Sequence Negative; + + // A color, used in constructed sequences. + struct Color + { + uint8_t R; + uint8_t G; + uint8_t B; + + static Color GetAccentColor(); + }; + + namespace Foreground + { + // Applies brightness/intensity flag to foreground color + extern const Sequence Bright; + // Removes brightness/intensity flag from foreground color + extern const Sequence NoBright; + + extern const Sequence BrightRed; + extern const Sequence BrightGreen; + extern const Sequence BrightYellow; + extern const Sequence BrightBlue; + extern const Sequence BrightMagenta; + extern const Sequence BrightCyan; + extern const Sequence BrightWhite; + + ConstructedSequence Extended(const Color& color); + } + + namespace Background + { + ConstructedSequence Extended(const Color& color); + } + + ConstructedSequence Hyperlink(const std::string& text, const std::string& ref); + } + + namespace TextModification + { + extern const Sequence EraseLineForward; + extern const Sequence EraseLineBackward; + extern const Sequence EraseLineEntirely; + } + + namespace Progress + { + enum class State + { + None, + Indeterminate, + Normal, + Paused, + Error + }; + + ConstructedSequence Construct(State state, std::optional percentage = std::nullopt); + } +} + +inline std::ostream& operator<<(std::ostream& o, const AppInstaller::CLI::VirtualTerminal::Sequence& s) +{ + return (o << s.Get()); +} diff --git a/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp b/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp index 9f2325323b..912b394ee2 100644 --- a/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ArchiveFlow.cpp @@ -2,10 +2,10 @@ // Licensed under the MIT License. #include "pch.h" #include "ArchiveFlow.h" -#include "PortableFlow.h" +#include "PortableFlow.h" #include "ShellExecuteInstallerHandler.h" #include -#include +#include #include using namespace AppInstaller::Manifest; @@ -43,25 +43,25 @@ namespace AppInstaller::CLI::Workflow } } } - } - - void ExtractFilesFromArchive(Execution::Context& context) - { + } + + void ExtractFilesFromArchive(Execution::Context& context) + { const auto& installerPath = context.Get(); std::filesystem::path destinationFolder = installerPath.parent_path() / s_Extracted; - std::filesystem::create_directory(destinationFolder); - + std::filesystem::create_directory(destinationFolder); + AICLI_LOG(CLI, Info, << "Extracting archive to: " << destinationFolder); - context.Reporter.Info() << Resource::String::ExtractingArchive << std::endl; - - if (Settings::User().Get() == Archive::ExtractionMethod::Tar) - { - context << ShellExecuteExtractArchive(installerPath, destinationFolder); - } - else - { - HRESULT result = AppInstaller::Archive::TryExtractArchive(installerPath, destinationFolder); - + context.Reporter.Info() << Resource::String::ExtractingArchive << std::endl; + + if (Settings::User().Get() == Archive::ExtractionMethod::Tar) + { + context << ShellExecuteExtractArchive(installerPath, destinationFolder); + } + else + { + HRESULT result = AppInstaller::Archive::TryExtractArchive(installerPath, destinationFolder); + if (SUCCEEDED(result)) { AICLI_LOG(CLI, Info, << "Successfully extracted archive"); @@ -72,9 +72,9 @@ namespace AppInstaller::CLI::Workflow AICLI_LOG(CLI, Info, << "Failed to extract archive with code " << result); context.Reporter.Error() << Resource::String::ExtractArchiveFailed << std::endl; AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); - } - } - } + } + } + } void VerifyAndSetNestedInstaller(Execution::Context& context) { @@ -146,4 +146,4 @@ namespace AppInstaller::CLI::Workflow } } } -} +} diff --git a/src/AppInstallerCLICore/Workflows/ArchiveFlow.h b/src/AppInstallerCLICore/Workflows/ArchiveFlow.h index 0bc8209d8f..ce76bc3431 100644 --- a/src/AppInstallerCLICore/Workflows/ArchiveFlow.h +++ b/src/AppInstallerCLICore/Workflows/ArchiveFlow.h @@ -10,12 +10,12 @@ namespace AppInstaller::CLI::Workflow // Inputs: InstallerPath // Outputs: None void ScanArchiveFromLocalManifest(Execution::Context& context); - + // Extracts the files from an archive // Required Args: None // Inputs: InstallerPath - // Outputs: None - void ExtractFilesFromArchive(Execution::Context& context); + // Outputs: None + void ExtractFilesFromArchive(Execution::Context& context); // Verifies that the NestedInstaller exists and sets the InstallerPath // Required Args: None @@ -28,4 +28,4 @@ namespace AppInstaller::CLI::Workflow // Inputs: Installer, InstallerPath // Outputs: None void EnsureValidNestedInstallerMetadataForArchiveInstall(Execution::Context& context); -} +} diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp index 2090335e46..15bdb58ff3 100644 --- a/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/CompletionFlow.cpp @@ -1,221 +1,221 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "CompletionFlow.h" - -namespace AppInstaller::CLI::Workflow -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::Utility::literals; - - namespace - { - // Outputs the completion string, wrapping it in quotes if needed. - void OutputCompletionString(Execution::OutputStream& stream, std::string_view value) - { - if (value.find_first_of(' ') != std::string_view::npos) - { - stream << '"' << value << '"' << std::endl; - } - else - { - stream << value << std::endl; - } - } - } - - void CompleteSourceName(Execution::Context& context) - { - const std::string& word = context.Get().Word(); - auto stream = context.Reporter.Completion(); - - for (const auto& source : Repository::Source::GetCurrentSources()) - { - if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(source.Name, word)) - { - OutputCompletionString(stream, source.Name); - } - } - } - - void RequireCompletionWordNonEmpty(Execution::Context& context) - { - if (context.Get().Word().empty()) - { - AICLI_LOG(CLI, Verbose, << "Completion word empty, cannot complete"); - AICLI_TERMINATE_CONTEXT(E_NOT_SET); - } - } - - void CompleteWithMatchedField(Execution::Context& context) - { - auto& searchResult = context.Get(); - auto stream = context.Reporter.Completion(); - - for (size_t i = 0; i < searchResult.Matches.size(); ++i) - { - if (searchResult.Matches[i].MatchCriteria.Value.empty()) - { - OutputCompletionString(stream, searchResult.Matches[i].Package->GetProperty(Repository::PackageProperty::Id)); - } - else - { - OutputCompletionString(stream, searchResult.Matches[i].MatchCriteria.Value); - } - } - } - - void CompleteWithSearchResultVersions(Execution::Context& context) - { - const std::string& word = context.Get().Word(); - auto stream = context.Reporter.Completion(); - - for (const auto& ap : context.Get()->GetAvailable()) - { - for (const auto& vc : ap->GetVersionKeys()) - { - if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) - { - OutputCompletionString(stream, vc.Version); - } - } - } - } - - void CompleteWithSearchResultInstalledVersions(Execution::Context& context) - { - const std::string& word = context.Get().Word(); - auto stream = context.Reporter.Completion(); - - auto installedPackage = context.Get()->GetInstalled(); - - if (installedPackage) - { - for (const auto& vc : installedPackage->GetVersionKeys()) - { - if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) - { - OutputCompletionString(stream, vc.Version); - } - } - } - } - - void CompleteWithSearchResultChannels(Execution::Context& context) - { - const std::string& word = context.Get().Word(); - auto stream = context.Reporter.Completion(); - - std::vector channels; - - for (const auto& ap : context.Get()->GetAvailable()) - { - for (const auto& vc : ap->GetVersionKeys()) - { - if ((word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Channel, word)) && - std::find(channels.begin(), channels.end(), vc.Channel) == channels.end()) - { - channels.emplace_back(vc.Channel); - } - } - } - - for (const auto& c : channels) - { - OutputCompletionString(stream, c); - } - } - - void CompleteWithSingleSemanticsForValue::operator()(Execution::Context& context) const - { - switch (m_type) - { - case Execution::Args::Type::Query: - case Execution::Args::Type::MultiQuery: - case Execution::Args::Type::Id: - case Execution::Args::Type::Name: - case Execution::Args::Type::Moniker: - case Execution::Args::Type::Tag: - case Execution::Args::Type::Command: - case Execution::Args::Type::Version: - case Execution::Args::Type::Channel: - context << - Workflow::OpenSource(); - break; - } - - context << CompleteWithSingleSemanticsForValueUsingExistingSource(m_type); - } - - void CompleteWithSingleSemanticsForValueUsingExistingSource::operator()(Execution::Context& context) const - { - switch (m_type) - { - case Execution::Args::Type::Query: - case Execution::Args::Type::MultiQuery: - context << - Workflow::RequireCompletionWordNonEmpty << - Workflow::SearchSourceForSingleCompletion << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Manifest: - // Intentionally output none to enable pass through to filesystem. - break; - case Execution::Args::Type::Id: - context << - Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Id) << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Name: - context << - Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Name) << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Moniker: - context << - Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Moniker) << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Tag: - context << - Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Tag) << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Command: - context << - Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Command) << - Workflow::CompleteWithMatchedField; - break; - case Execution::Args::Type::Version: - // Here we require that the standard search finds a single entry, and we list those versions. - context << - Workflow::SearchSourceForSingle << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Completion) << - Workflow::CompleteWithSearchResultVersions; - break; - case Execution::Args::Type::TargetVersion: - // Here we require that the standard search finds a single entry, and we list the installed versions. - context << - Workflow::SearchSourceForSingle << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Completion) << - Workflow::CompleteWithSearchResultInstalledVersions; - break; - case Execution::Args::Type::Channel: - // Here we require that the standard search finds a single entry, and we list those channels. - context << - Workflow::SearchSourceForSingle << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Completion) << - Workflow::CompleteWithSearchResultChannels; - break; - case Execution::Args::Type::Source: - context << - Workflow::CompleteSourceName; - break; - } - } - - void CompleteWithEmptySet(Execution::Context& context) - { - context.Reporter.Completion() << std::endl; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "CompletionFlow.h" + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::Utility::literals; + + namespace + { + // Outputs the completion string, wrapping it in quotes if needed. + void OutputCompletionString(Execution::OutputStream& stream, std::string_view value) + { + if (value.find_first_of(' ') != std::string_view::npos) + { + stream << '"' << value << '"' << std::endl; + } + else + { + stream << value << std::endl; + } + } + } + + void CompleteSourceName(Execution::Context& context) + { + const std::string& word = context.Get().Word(); + auto stream = context.Reporter.Completion(); + + for (const auto& source : Repository::Source::GetCurrentSources()) + { + if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(source.Name, word)) + { + OutputCompletionString(stream, source.Name); + } + } + } + + void RequireCompletionWordNonEmpty(Execution::Context& context) + { + if (context.Get().Word().empty()) + { + AICLI_LOG(CLI, Verbose, << "Completion word empty, cannot complete"); + AICLI_TERMINATE_CONTEXT(E_NOT_SET); + } + } + + void CompleteWithMatchedField(Execution::Context& context) + { + auto& searchResult = context.Get(); + auto stream = context.Reporter.Completion(); + + for (size_t i = 0; i < searchResult.Matches.size(); ++i) + { + if (searchResult.Matches[i].MatchCriteria.Value.empty()) + { + OutputCompletionString(stream, searchResult.Matches[i].Package->GetProperty(Repository::PackageProperty::Id)); + } + else + { + OutputCompletionString(stream, searchResult.Matches[i].MatchCriteria.Value); + } + } + } + + void CompleteWithSearchResultVersions(Execution::Context& context) + { + const std::string& word = context.Get().Word(); + auto stream = context.Reporter.Completion(); + + for (const auto& ap : context.Get()->GetAvailable()) + { + for (const auto& vc : ap->GetVersionKeys()) + { + if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) + { + OutputCompletionString(stream, vc.Version); + } + } + } + } + + void CompleteWithSearchResultInstalledVersions(Execution::Context& context) + { + const std::string& word = context.Get().Word(); + auto stream = context.Reporter.Completion(); + + auto installedPackage = context.Get()->GetInstalled(); + + if (installedPackage) + { + for (const auto& vc : installedPackage->GetVersionKeys()) + { + if (word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Version, word)) + { + OutputCompletionString(stream, vc.Version); + } + } + } + } + + void CompleteWithSearchResultChannels(Execution::Context& context) + { + const std::string& word = context.Get().Word(); + auto stream = context.Reporter.Completion(); + + std::vector channels; + + for (const auto& ap : context.Get()->GetAvailable()) + { + for (const auto& vc : ap->GetVersionKeys()) + { + if ((word.empty() || Utility::ICUCaseInsensitiveStartsWith(vc.Channel, word)) && + std::find(channels.begin(), channels.end(), vc.Channel) == channels.end()) + { + channels.emplace_back(vc.Channel); + } + } + } + + for (const auto& c : channels) + { + OutputCompletionString(stream, c); + } + } + + void CompleteWithSingleSemanticsForValue::operator()(Execution::Context& context) const + { + switch (m_type) + { + case Execution::Args::Type::Query: + case Execution::Args::Type::MultiQuery: + case Execution::Args::Type::Id: + case Execution::Args::Type::Name: + case Execution::Args::Type::Moniker: + case Execution::Args::Type::Tag: + case Execution::Args::Type::Command: + case Execution::Args::Type::Version: + case Execution::Args::Type::Channel: + context << + Workflow::OpenSource(); + break; + } + + context << CompleteWithSingleSemanticsForValueUsingExistingSource(m_type); + } + + void CompleteWithSingleSemanticsForValueUsingExistingSource::operator()(Execution::Context& context) const + { + switch (m_type) + { + case Execution::Args::Type::Query: + case Execution::Args::Type::MultiQuery: + context << + Workflow::RequireCompletionWordNonEmpty << + Workflow::SearchSourceForSingleCompletion << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Manifest: + // Intentionally output none to enable pass through to filesystem. + break; + case Execution::Args::Type::Id: + context << + Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Id) << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Name: + context << + Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Name) << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Moniker: + context << + Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Moniker) << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Tag: + context << + Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Tag) << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Command: + context << + Workflow::SearchSourceForCompletionField(Repository::PackageMatchField::Command) << + Workflow::CompleteWithMatchedField; + break; + case Execution::Args::Type::Version: + // Here we require that the standard search finds a single entry, and we list those versions. + context << + Workflow::SearchSourceForSingle << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Completion) << + Workflow::CompleteWithSearchResultVersions; + break; + case Execution::Args::Type::TargetVersion: + // Here we require that the standard search finds a single entry, and we list the installed versions. + context << + Workflow::SearchSourceForSingle << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Completion) << + Workflow::CompleteWithSearchResultInstalledVersions; + break; + case Execution::Args::Type::Channel: + // Here we require that the standard search finds a single entry, and we list those channels. + context << + Workflow::SearchSourceForSingle << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Completion) << + Workflow::CompleteWithSearchResultChannels; + break; + case Execution::Args::Type::Source: + context << + Workflow::CompleteSourceName; + break; + } + } + + void CompleteWithEmptySet(Execution::Context& context) + { + context.Reporter.Completion() << std::endl; + } +} diff --git a/src/AppInstallerCLICore/Workflows/CompletionFlow.h b/src/AppInstallerCLICore/Workflows/CompletionFlow.h index 9161c71946..e9da7040c0 100644 --- a/src/AppInstallerCLICore/Workflows/CompletionFlow.h +++ b/src/AppInstallerCLICore/Workflows/CompletionFlow.h @@ -1,75 +1,75 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" -#include "WorkflowBase.h" -#include - -namespace AppInstaller::CLI::Workflow -{ - // Outputs completion possibilities for the source name argument. - // Required Args: None - // Inputs: CompletionData - // Outputs: None - void CompleteSourceName(Execution::Context& context); - - // Terminates the context if the completion word is empty. - // Required Args: None - // Inputs: CompletionData - // Outputs: None - void RequireCompletionWordNonEmpty(Execution::Context& context); - - // Outputs the matched field for the results. - // Required Args: None - // Inputs: SearchResult - // Outputs: None - void CompleteWithMatchedField(Execution::Context& context); - - // Outputs the versions available for the single search result. - // Required Args: None - // Inputs: CompletionData, SearchResult - // Outputs: None - void CompleteWithSearchResultVersions(Execution::Context& context); - - // Outputs the channels available for the single search result. - // Required Args: None - // Inputs: CompletionData, SearchResult - // Outputs: None - void CompleteWithSearchResultChannels(Execution::Context& context); - - // Executes the appropriate completion flow for the given argument in the context of a command - // that targets a single manifest (ex. show or install). - // Required Args: None - // Inputs: CompletionData - // Outputs: None - struct CompleteWithSingleSemanticsForValue : public WorkflowTask - { - CompleteWithSingleSemanticsForValue(Execution::Args::Type type) : WorkflowTask("CompleteWithSingleSemanticsForValue"), m_type(type) {} - - void operator()(Execution::Context& context) const override; - - private: - Execution::Args::Type m_type; - }; - - // Executes the appropriate completion flow for the given argument in the context of a command - // that targets a single manifest (ex. show or install), using the already open source. - // Required Args: None - // Inputs: CompletionData, Source - // Outputs: None - struct CompleteWithSingleSemanticsForValueUsingExistingSource : public WorkflowTask - { - CompleteWithSingleSemanticsForValueUsingExistingSource(Execution::Args::Type type) : WorkflowTask("CompleteWithSingleSemanticsForValueUsingExistingSource"), m_type(type) {} - - void operator()(Execution::Context& context) const override; - - private: - Execution::Args::Type m_type; - }; - - // Outputs an empty line to indicate that there are no completions. - // Required Args: None - // Inputs: None - // Outputs: None - void CompleteWithEmptySet(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" +#include "WorkflowBase.h" +#include + +namespace AppInstaller::CLI::Workflow +{ + // Outputs completion possibilities for the source name argument. + // Required Args: None + // Inputs: CompletionData + // Outputs: None + void CompleteSourceName(Execution::Context& context); + + // Terminates the context if the completion word is empty. + // Required Args: None + // Inputs: CompletionData + // Outputs: None + void RequireCompletionWordNonEmpty(Execution::Context& context); + + // Outputs the matched field for the results. + // Required Args: None + // Inputs: SearchResult + // Outputs: None + void CompleteWithMatchedField(Execution::Context& context); + + // Outputs the versions available for the single search result. + // Required Args: None + // Inputs: CompletionData, SearchResult + // Outputs: None + void CompleteWithSearchResultVersions(Execution::Context& context); + + // Outputs the channels available for the single search result. + // Required Args: None + // Inputs: CompletionData, SearchResult + // Outputs: None + void CompleteWithSearchResultChannels(Execution::Context& context); + + // Executes the appropriate completion flow for the given argument in the context of a command + // that targets a single manifest (ex. show or install). + // Required Args: None + // Inputs: CompletionData + // Outputs: None + struct CompleteWithSingleSemanticsForValue : public WorkflowTask + { + CompleteWithSingleSemanticsForValue(Execution::Args::Type type) : WorkflowTask("CompleteWithSingleSemanticsForValue"), m_type(type) {} + + void operator()(Execution::Context& context) const override; + + private: + Execution::Args::Type m_type; + }; + + // Executes the appropriate completion flow for the given argument in the context of a command + // that targets a single manifest (ex. show or install), using the already open source. + // Required Args: None + // Inputs: CompletionData, Source + // Outputs: None + struct CompleteWithSingleSemanticsForValueUsingExistingSource : public WorkflowTask + { + CompleteWithSingleSemanticsForValueUsingExistingSource(Execution::Args::Type type) : WorkflowTask("CompleteWithSingleSemanticsForValueUsingExistingSource"), m_type(type) {} + + void operator()(Execution::Context& context) const override; + + private: + Execution::Args::Type m_type; + }; + + // Outputs an empty line to indicate that there are no completions. + // Required Args: None + // Inputs: None + // Outputs: None + void CompleteWithEmptySet(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp index 8d39ea5f1a..5cf513823a 100644 --- a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.cpp @@ -1,3119 +1,3119 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationFlow.h" -#include "ImportExportFlow.h" -#include "PromptFlow.h" -#include "TableOutput.h" -#include "MSStoreInstallerHandler.h" -#include "Public/ConfigurationSetProcessorFactoryRemoting.h" -#include "ConfigurationCommon.h" -#include "ConfigurationWingetDscModuleUnitValidation.h" -#include "Commands/DscCommandBase.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller::CLI::Execution; -using namespace winrt::Microsoft::Management::Configuration; -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Foundation::Collections; -using namespace winrt::Windows::Storage; -using namespace AppInstaller::Utility::literals; -using namespace AppInstaller::SelfManagement; - -namespace AppInstaller::CLI::Workflow -{ -#ifndef AICLI_DISABLE_TEST_HOOKS - IConfigurationSetProcessorFactory s_override_IConfigurationSetProcessorFactory; - - void SetTestConfigurationSetProcessorFactory(IConfigurationSetProcessorFactory factory) - { - s_override_IConfigurationSetProcessorFactory = std::move(factory); - } -#endif - - namespace anon - { - static const AppInstaller::Utility::Version s_MinimumSchemaVersionModuleNameRequiredInType = { "0.3" }; - - constexpr std::wstring_view s_Directive_Description = L"description"; - constexpr std::wstring_view s_Directive_Module = L"module"; - constexpr std::wstring_view s_Directive_AllowPrerelease = L"allowPrerelease"; - - constexpr std::wstring_view s_Unit_WinGetPackage = L"WinGetPackage"; - constexpr std::wstring_view s_Unit_WinGetSource = L"WinGetSource"; - - constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Package"; - constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Source"; - constexpr std::wstring_view s_UnitType_WinGetUserSettingsFile_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/UserSettingsFile"; - constexpr std::wstring_view s_UnitType_WinGetAdminSettings_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/AdminSettings"; - - constexpr std::wstring_view s_Module_WinGetClient = L"Microsoft.WinGet.DSC"; - - constexpr std::wstring_view s_Setting_WinGetPackage_Id = L"id"; - constexpr std::wstring_view s_Setting_WinGetPackage_Source = L"source"; - constexpr std::wstring_view s_Setting_WinGetPackage_Version = L"version"; - - constexpr std::wstring_view s_Setting_WinGetSource_Name = L"name"; - constexpr std::wstring_view s_Setting_WinGetSource_Arg = L"argument"; - constexpr std::wstring_view s_Setting_WinGetSource_Type = L"type"; - constexpr std::wstring_view s_Setting_WinGetSource_TrustLevel = L"trustLevel"; - constexpr std::wstring_view s_Setting_WinGetSource_Explicit = L"explicit"; - constexpr std::wstring_view s_Setting_WinGetSource_Priority = L"priority"; - - constexpr std::wstring_view s_Predefined_PowerShell_PackageId = L"Microsoft.PowerShell"; - constexpr std::wstring_view s_Predefined_PowerShell_PackageSource = L"winget"; - - constexpr std::string_view s_DscPackage_StoreId_Stable = "9NVTPZWRC6KQ"; - constexpr std::string_view s_DscPackage_StoreId_Preview = "9PCX3HX4HZ0Z"; - - struct PredefinedResourceInfo - { - std::wstring_view UnitType; - bool ElevationRequired = false; - - PredefinedResourceInfo(std::wstring_view unitType) : UnitType(unitType) {} - PredefinedResourceInfo(std::wstring_view unitType, bool elevationRequired) : UnitType(unitType), ElevationRequired(elevationRequired) {} - }; - - struct PredefinedResource - { - // RequiredModule could be empty, meaning no required modules needed. - std::wstring_view RequiredModule; - - std::vector ResourceInfos; - }; - - std::vector PredefinedResourcesForExport() - { - return { - { {}, { { s_UnitType_WinGetUserSettingsFile_DSCv3 }, { s_UnitType_WinGetAdminSettings_DSCv3, true } } }, - { L"Microsoft.Windows.Settings", { { L"Microsoft.Windows.Settings/WindowsSettings", true } } }, - }; - } - - std::vector PackageSettingsExclusionList() - { - return { - L"Microsoft.WinGet/", - L"Microsoft.WinGet.Dev/", - }; - }; - - // Returns unit type prefixes that identify resources known to be shipped with DSC. - // These are used both to exclude the resources themselves and to discover the DSC - // installation location, so that any co-located resources are also excluded. - std::vector DscShippedResourcePrefixes() - { - return { - L"Microsoft.DSC/", - L"Microsoft.DSC.Debug/", - L"Microsoft.DSC.Transitional/", - L"Microsoft/OSInfo", - }; - } - - // Returns unit type prefixes for DSC-shipped resources that are still allowed to - // appear in the exported configuration. All other DSC-shipped resources are excluded by default. - std::vector DscShippedResourcesAllowList() - { - // Currently empty: all DSC-shipped resources are excluded from export by default. - return {}; - } - - Logging::Level ConvertLevel(DiagnosticLevel level) - { - switch (level) - { - case DiagnosticLevel::Verbose: return Logging::Level::Verbose; - case DiagnosticLevel::Informational: return Logging::Level::Info; - case DiagnosticLevel::Warning: return Logging::Level::Warning; - case DiagnosticLevel::Error: return Logging::Level::Error; - case DiagnosticLevel::Critical: return Logging::Level::Crit; - } - - return Logging::Level::Info; - } - - // Audit information gathered about a custom processor path. - struct ProcessorPathInfo - { - bool IsAlias = false; - std::string HashString; - std::string SigningSubject; - }; - - // Collects audit information for the given processor path. - // Throws on access failure so the caller is prevented from using an unverifiable path. - ProcessorPathInfo CollectProcessorPathInfo(const std::filesystem::path& processorPath) - { - ProcessorPathInfo result; - const std::wstring& pathStr = processorPath.wstring(); - - // Attempt to open the file for reading without FILE_FLAG_OPEN_REPARSE_POINT. - // App execution aliases (IO_REPARSE_TAG_APPEXECLINK) cannot be opened for reading - // this way and will fail with ERROR_CANT_ACCESS_FILE. - wil::unique_hfile fileHandle{ CreateFileW( - pathStr.c_str(), - GENERIC_READ, - FILE_SHARE_READ, - nullptr, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - nullptr) }; - - if (!fileHandle) - { - DWORD lastError = GetLastError(); - THROW_WIN32_IF(lastError, lastError != ERROR_CANT_ACCESS_FILE); - - // Re-open with FILE_FLAG_OPEN_REPARSE_POINT to inspect the reparse data. - wil::unique_hfile reparseHandle{ CreateFileW( - pathStr.c_str(), - 0, - FILE_SHARE_READ, - nullptr, - OPEN_EXISTING, - FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, - nullptr) }; - THROW_LAST_ERROR_IF(!reparseHandle); - - // Retrieve the reparse point data. - std::vector reparseBuffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); - DWORD bytesReturned = 0; - THROW_LAST_ERROR_IF(!DeviceIoControl( - reparseHandle.get(), - FSCTL_GET_REPARSE_POINT, - nullptr, - 0, - reparseBuffer.data(), - static_cast(reparseBuffer.size()), - &bytesReturned, - nullptr)); - - // Confirm it is specifically an app execution alias, not another reparse type. - THROW_HR_IF(E_INVALIDARG, bytesReturned < sizeof(DWORD)); - DWORD reparseTag = *reinterpret_cast(reparseBuffer.data()); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_REPARSE_TAG_MISMATCH), reparseTag != IO_REPARSE_TAG_APPEXECLINK); - - result.IsAlias = true; - result.HashString = Utility::SHA256::ConvertToString( - Utility::SHA256::ComputeHash(reparseBuffer.data(), bytesReturned)); - } - else - { - // Regular file: hash the file bytes using the shared SHA256 utility. - result.HashString = Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHashFromHandle(fileHandle.get())); - - // Attempt to extract signing info (handles both embedded and catalog signatures). - try - { - result.SigningSubject = Certificates::GetAuthenticodeSubject(processorPath); - } - catch (...) - { - AICLI_LOG(Config, Warning, << "Failed to retrieve signing info for processor path"); - } - } - - return result; - } - - DiagnosticLevel ConvertLevel(Logging::Level level) - { - switch (level) - { - case Logging::Level::Verbose: return DiagnosticLevel::Verbose; - case Logging::Level::Info: return DiagnosticLevel::Informational; - case Logging::Level::Warning: return DiagnosticLevel::Warning; - case Logging::Level::Error: return DiagnosticLevel::Error; - case Logging::Level::Crit: return DiagnosticLevel::Critical; - } - - return DiagnosticLevel::Informational; - } - - Resource::StringId ToResource(ConfigurationUnitIntent intent) - { - switch (intent) - { - case ConfigurationUnitIntent::Assert: return Resource::String::ConfigurationAssert; - case ConfigurationUnitIntent::Inform: return Resource::String::ConfigurationInform; - case ConfigurationUnitIntent::Apply: return Resource::String::ConfigurationApply; - default: return Resource::StringId::Empty(); - } - } - - void InstallDscPackage(Execution::Context& context, std::string_view productId, std::unique_ptr& progressScope) - { - progressScope.reset(); - - context.Reporter.Info() << Resource::String::ConfigurationInstallDscPackage << std::endl; - - auto installDscContextPtr = context.CreateSubContext(); - Execution::Context& installDscContext = *installDscContextPtr; - auto previousThreadGlobals = installDscContext.SetForCurrentThread(); - - Manifest::ManifestInstaller dscInstaller; - dscInstaller.ProductId = productId; - - installDscContext.Add(std::move(dscInstaller)); - installDscContext.Args.AddArg(Execution::Args::Type::InstallScope, Manifest::ScopeToString(Manifest::ScopeEnum::User)); - installDscContext.Args.AddArg(Execution::Args::Type::Silent); - installDscContext.Args.AddArg(Execution::Args::Type::Force); - - installDscContext << MSStoreInstall; - - if (installDscContext.IsTerminated()) - { - AICLI_LOG(Config, Error, << "Failed to install dsc v3 package: " << productId); - context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; - THROW_WIN32(ERROR_FILE_NOT_FOUND); - } - - progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); - } - - IConfigurationSetProcessorFactory CreateConfigurationSetProcessorFactory(Execution::Context& context) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - // Test could override the entire workflow task, but that may require keeping more in sync than simply setting the factory. - if (s_override_IConfigurationSetProcessorFactory) - { - return s_override_IConfigurationSetProcessorFactory; - } -#endif - - auto progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); - - // The configuration set must have already been opened to create the proper factory. - THROW_WIN32_IF(ERROR_INVALID_STATE, !context.Contains(Data::ConfigurationContext)); - const auto& configurationContext = context.Get(); - THROW_WIN32_IF(ERROR_INVALID_STATE, !configurationContext.Set()); - - IConfigurationSetProcessorFactory factory; - ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configurationContext.Set()); - - THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, processorEngine == ConfigurationRemoting::ProcessorEngine::Unknown); - - // Since downgrading is not currently supported, only use dynamic if running limited. - if (Runtime::IsRunningWithLimitedToken()) - { - factory = ConfigurationRemoting::CreateDynamicRuntimeFactory(processorEngine); - } - else - { - factory = ConfigurationRemoting::CreateOutOfProcessFactory(processorEngine); - } - - if (processorEngine == ConfigurationRemoting::ProcessorEngine::PowerShell) - { - Configuration::SetModulePath(context, factory); - } - else if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3) - { - auto factoryMap = factory.as>(); - - if (context.Args.Contains(Args::Type::ConfigurationProcessorPath)) - { - progressScope.reset(); - - const auto& processorPathArg = context.Args.GetArg(Args::Type::ConfigurationProcessorPath); - std::filesystem::path processorPath{ Utility::ConvertToUTF16(processorPathArg) }; - - // Collect audit information; throws if the path cannot be opened or hashed. - auto pathInfo = anon::CollectProcessorPathInfo(processorPath); - - // Output audit information to the user as a warning since this is a non-default path. - constexpr std::string_view s_indent = " "sv; - context.Reporter.Info() << Resource::String::ConfigurationProcessorPathAudit << std::endl; - context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditPath(Utility::LocIndString{ processorPathArg }) << std::endl; - context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditHash(Utility::LocIndString{ pathInfo.HashString }) << std::endl; - if (pathInfo.IsAlias) - { - context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditIsAlias << std::endl; - } - else if (!pathInfo.SigningSubject.empty()) - { - context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditSignature(Utility::LocIndString{ pathInfo.SigningSubject }) << std::endl; - } - else - { - context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditUnsigned << std::endl; - } - - AICLI_LOG(Config, Info, << "Processor path audit - Path: " << processorPathArg << ", Hash: " << pathInfo.HashString << ", IsAlias: " << pathInfo.IsAlias); - - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePath), processorPath.wstring()); - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePathHash), Utility::ConvertToUTF16(pathInfo.HashString)); - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePathIsAlias), pathInfo.IsAlias ? L"true" : L"false"); - - progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); - } - else - { - for (;;) - { - // Get the next transition for the state machine - winrt::hstring nextTransition = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FindDscStateMachine)); - AICLI_LOG(Config, Verbose, << "FindDscStateMachine returned " << Utility::ConvertToUTF8(nextTransition)); - - if (nextTransition == L"Found") - { - break; - } - else if (nextTransition == L"InstallStable") - { - AICLI_LOG(Config, Info, << "Installing stable DSC package from store..."); - InstallDscPackage(context, s_DscPackage_StoreId_Stable, progressScope); - } - else if (nextTransition == L"InstallPreview") - { - AICLI_LOG(Config, Info, << "Installing preview DSC package from store..."); - InstallDscPackage(context, s_DscPackage_StoreId_Preview, progressScope); - } - else if (nextTransition == L"NotFound") - { - AICLI_LOG(Config, Error, << "Failed to find appropriate dsc v3 package, it must be provided by the user."); - context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; - THROW_WIN32(ERROR_FILE_NOT_FOUND); - } - else - { - AICLI_LOG(Config, Error, << "FindDscStateMachine returned unknown value `" << Utility::ConvertToUTF8(nextTransition) << "`"); - THROW_HR(E_UNEXPECTED); - } - } - } - - if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose)) - { - factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled), L"True"); - } - } - - return factory; - } - - void ConfigureProcessorForUse(Execution::Context& context, ConfigurationProcessor&& processor) - { - // Set the processor to the current level of the logging. - processor.MinimumLevel(anon::ConvertLevel(Logging::Log().GetLevel())); - processor.Caller(L"winget"); - // Use same activity as the overall winget command - processor.ActivityIdentifier(*Logging::Telemetry().GetActivityId()); - // Apply winget telemetry setting to configuration - processor.GenerateTelemetryEvents(!Settings::User().Get()); - - // Route the configuration diagnostics into the context's diagnostics logging - processor.Diagnostics([&context](const winrt::Windows::Foundation::IInspectable&, const IDiagnosticInformation& diagnostics) - { - context.GetThreadGlobals().GetDiagnosticLogger().Write(Logging::Channel::Config, anon::ConvertLevel(diagnostics.Level()), Utility::ConvertToUTF8(diagnostics.Message())); - }); - - if (context.Contains(Data::ConfigurationContext)) - { - context.Get().Processor(std::move(processor)); - } - else - { - ConfigurationContext configurationContext; - configurationContext.Processor(std::move(processor)); - - context.Add(std::move(configurationContext)); - } - } - - winrt::hstring GetValueSetString(const ValueSet& valueSet, std::wstring_view value) - { - if (valueSet.HasKey(value)) - { - auto object = valueSet.Lookup(value); - IPropertyValue property = object.try_as(); - if (property && property.Type() == PropertyType::String) - { - return property.GetString(); - } - } - - return {}; - } - - std::optional GetValueSetBool(const ValueSet& valueSet, std::wstring_view value) - { - if (valueSet.HasKey(value)) - { - auto object = valueSet.Lookup(value); - IPropertyValue property = object.try_as(); - if (property && property.Type() == PropertyType::Boolean) - { - return property.GetBoolean(); - } - } - - return {}; - } - - // Contains the output functions and tracks whether any fields needed to be truncated. - struct OutputHelper - { - OutputHelper(Execution::Context& context) : m_context(context) {} - - size_t ValuesTruncated = 0; - - // Converts a string from the configuration API surface for output. - // All strings coming from the API are external data and not localizable by us. - Utility::LocIndString ConvertForOutput(const std::string& input, size_t maxLines) - { - bool truncated = false; - auto lines = Utility::SplitIntoLines(input); - - if (maxLines == 1 && lines.size() > 1) - { - // If the limit was one line, don't allow line breaks but do allow a second line of overflow - lines.resize(1); - maxLines = 2; - truncated = true; - } - - if (Utility::LimitOutputLines(lines, GetConsoleWidth().value_or(120), maxLines)) - { - truncated = true; - } - - if (truncated) - { - ++ValuesTruncated; - } - - return Utility::LocIndString{ Utility::Join("\n", lines) }; - } - - Utility::LocIndString ConvertForOutput(const winrt::hstring& input, size_t maxLines) - { - return ConvertForOutput(Utility::ConvertToUTF8(input), maxLines); - } - - Utility::LocIndString ConvertIdentifier(const winrt::hstring& input) - { - return ConvertForOutput(input, 1); - } - - Utility::LocIndString ConvertURI(const winrt::hstring& input) - { - return ConvertForOutput(input, 1); - } - - Utility::LocIndString ConvertValue(const winrt::hstring& input) - { - return ConvertForOutput(input, 5); - } - - Utility::LocIndString ConvertDetailsIdentifier(const winrt::hstring& input) - { - return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 1); - } - - Utility::LocIndString ConvertDetailsURI(const winrt::hstring& input) - { - return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 1); - } - - Utility::LocIndString ConvertDetailsValue(const winrt::hstring& input) - { - return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 5); - } - - void OutputValueWithTruncationWarningIfNeeded(const winrt::hstring& input) - { - size_t truncatedBefore = ValuesTruncated; - m_context.Reporter.Info() << ConvertValue(input) << '\n'; - - if (ValuesTruncated > truncatedBefore) - { - m_context.Reporter.Warn() << Resource::String::ConfigurationWarningValueTruncated << std::endl; - } - } - - void OutputPropertyValue(const IPropertyValue property) - { - switch (property.Type()) - { - case PropertyType::String: - m_context.Reporter.Info() << ' '; - OutputValueWithTruncationWarningIfNeeded(property.GetString()); - break; - case PropertyType::Boolean: - m_context.Reporter.Info() << ' ' << (property.GetBoolean() ? Utility::LocIndView("true") : Utility::LocIndView("false")) << '\n'; - break; - case PropertyType::Int64: - m_context.Reporter.Info() << ' ' << property.GetInt64() << '\n'; - break; - default: - m_context.Reporter.Info() << " [Debug:PropertyType="_liv << property.Type() << "]\n"_liv; - break; - } - } - - void OutputValueSetAsArray(const ValueSet& valueSetArray, size_t indent) - { - Utility::LocIndString indentString{ std::string(indent, ' ') }; - - std::vector> arrayValues; - for (const auto& arrayValue : valueSetArray) - { - if (arrayValue.Key() != L"treatAsArray") - { - arrayValues.emplace_back(std::make_pair(std::stoi(arrayValue.Key().c_str()), arrayValue.Value())); - } - } - - std::sort( - arrayValues.begin(), - arrayValues.end(), - [](const std::pair& a, const std::pair& b) - { - return a.first < b.first; - }); - - for (const auto& arrayValue : arrayValues) - { - auto arrayObject = arrayValue.second; - IPropertyValue arrayProperty = arrayObject.try_as(); - - m_context.Reporter.Info() << indentString << "-"; - if (arrayProperty) - { - OutputPropertyValue(arrayProperty); - } - else - { - ValueSet arraySubset = arrayObject.as(); - auto size = arraySubset.Size(); - if (size > 0) - { - // First one is special. - auto first = arraySubset.First().Current(); - m_context.Reporter.Info() << ' ' << ConvertIdentifier(first.Key()) << ':'; - - auto object = first.Value(); - IPropertyValue property = object.try_as(); - if (property) - { - OutputPropertyValue(property); - } - else - { - // If not an IPropertyValue, it must be a ValueSet - ValueSet subset = object.as(); - m_context.Reporter.Info() << '\n'; - OutputValueSet(subset, indent + 4); - } - - if (size > 1) - { - arraySubset.Remove(first.Key()); - OutputValueSet(arraySubset, indent + 2); - arraySubset.Insert(first.Key(), first.Value()); - } - } - } - } - } - - void OutputValueSet(const ValueSet& valueSet, size_t indent) - { - Utility::LocIndString indentString{ std::string(indent, ' ') }; - - for (const auto& value : valueSet) - { - m_context.Reporter.Info() << indentString << ConvertIdentifier(value.Key()) << ':'; - - auto object = value.Value(); - - IPropertyValue property = object.try_as(); - if (property) - { - OutputPropertyValue(property); - } - else - { - // If not an IPropertyValue, it must be a ValueSet - ValueSet subset = object.as(); - m_context.Reporter.Info() << '\n'; - if (subset.HasKey(L"treatAsArray")) - { - OutputValueSetAsArray(subset, indent + 2); - } - else - { - OutputValueSet(subset, indent + 2); - } - } - } - } - - void OutputConfigurationUnitHeader(const ConfigurationUnit& unit, const winrt::hstring& name) - { - m_context.Reporter.Info() << ConfigurationUnitEmphasis << ConvertIdentifier(name); - - if (unit.Environment().Context() == SecurityContext::Elevated) - { - // Shield - m_context.Reporter.Info() << "\xF0\x9F\x9B\xA1 "_liv; - } - - winrt::hstring identifier = unit.Identifier(); - if (!identifier.empty()) - { - m_context.Reporter.Info() << " ["_liv << ConvertIdentifier(identifier) << ']'; - } - - m_context.Reporter.Info() << '\n'; - } - - void OutputConfigurationUnitInformation(const ConfigurationUnit& unit) - { - IConfigurationUnitProcessorDetails details = unit.Details(); - ValueSet metadata = unit.Metadata(); - - if (details) - { - // -- Sample output when IConfigurationUnitProcessorDetails present -- - // UnitType [Identifier] - // UnitDocumentationUri - // Description - // "Module": ModuleName "by" Author / Publisher (IsLocal / ModuleSource) - // "Signed by": SigningCertificateChain (leaf subject CN) - // PublishedModuleUri / ModuleDocumentationUri - // ModuleDescription - OutputConfigurationUnitHeader(unit, details.UnitType()); - - auto unitDocumentationUri = details.UnitDocumentationUri(); - if (unitDocumentationUri) - { - m_context.Reporter.Info() << " "_liv << ConvertDetailsURI(unitDocumentationUri.DisplayUri()) << '\n'; - } - - winrt::hstring unitDescriptionFromDetails = details.UnitDescription(); - if (!unitDescriptionFromDetails.empty()) - { - m_context.Reporter.Info() << " "_liv << ConvertDetailsValue(unitDescriptionFromDetails) << '\n'; - } - - auto unitDescriptionFromDirectives = GetValueSetString(metadata, s_Directive_Description); - if (!unitDescriptionFromDirectives.empty()) - { - m_context.Reporter.Info() << " "_liv; - OutputValueWithTruncationWarningIfNeeded(unitDescriptionFromDirectives); - } - - auto author = ConvertDetailsIdentifier(details.Author()); - if (author.empty()) - { - author = ConvertDetailsIdentifier(details.Publisher()); - } - - auto moduleName = ConvertDetailsIdentifier(details.ModuleName()); - if (!moduleName.empty()) - { - if (details.IsLocal()) - { - m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationModuleWithDetails(moduleName, author, Resource::String::ConfigurationLocal) << '\n'; - } - else - { - m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationModuleWithDetails(moduleName, author, ConvertDetailsIdentifier(details.ModuleSource())) << '\n'; - } - } - - // TODO: Currently the signature information is only for the top files. Maybe each item should be tagged? - // TODO: Output signing information with additional details (like whether the certificate is trusted). Doing this with the validate command - // seems like a good time, as that will also need to do the check in order to inform the user on the validation. - // Just saying "Signed By: Foo" is going to lead to a false sense of trust if the signature is valid but not actually trusted. - - auto moduleUri = details.PublishedModuleUri(); - if (!moduleUri) - { - moduleUri = details.ModuleDocumentationUri(); - } - if (moduleUri) - { - m_context.Reporter.Info() << " "_liv << ConvertDetailsURI(moduleUri.DisplayUri()) << '\n'; - } - - winrt::hstring moduleDescription = details.ModuleDescription(); - if (!moduleDescription.empty()) - { - m_context.Reporter.Info() << " "_liv << ConvertDetailsValue(moduleDescription) << '\n'; - } - } - else - { - // -- Sample output when no IConfigurationUnitProcessorDetails present -- - // Type [identifier] - // Description (from directives) - // "Module": module - OutputConfigurationUnitHeader(unit, unit.Type()); - - auto description = GetValueSetString(metadata, s_Directive_Description); - if (!description.empty()) - { - m_context.Reporter.Info() << " "_liv; - OutputValueWithTruncationWarningIfNeeded(description); - } - - auto module = GetValueSetString(metadata, s_Directive_Module); - if (!module.empty()) - { - m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationModuleNameOnly(ConvertIdentifier(module)) << '\n'; - } - } - - // -- Sample output footer -- - // Dependencies: dep1, dep2, ... - // Settings: - // <... settings splat> - auto dependencies = unit.Dependencies(); - if (dependencies.Size() > 0) - { - std::ostringstream allDependencies; - for (const winrt::hstring& dependency : dependencies) - { - allDependencies << ' ' << ConvertIdentifier(dependency); - } - m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationDependencies(Utility::LocIndString{ std::move(allDependencies).str() }) << '\n'; - } - - ValueSet settings = unit.Settings(); - if (settings.Size() > 0) - { - m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationSettings << '\n'; - OutputValueSet(settings, 4); - } - - m_context.Reporter.Info() << std::flush; - } - - private: - Execution::Context& m_context; - }; - - void OutputConfigurationUnitHeader(Execution::Context& context, const ConfigurationUnit& unit, const winrt::hstring& name) - { - OutputHelper helper{ context }; - helper.OutputConfigurationUnitHeader(unit, name); - } - - void LogFailedGetConfigurationUnitDetails(const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation) - { - if (FAILED(resultInformation.ResultCode())) - { - AICLI_LOG(Config, Error, << "Failed to get unit details for " << Utility::ConvertToUTF8(unit.Type()) << " : 0x" << - Logging::SetHRFormat << resultInformation.ResultCode() << '\n' << Utility::ConvertToUTF8(resultInformation.Description()) << '\n' << - Utility::ConvertToUTF8(resultInformation.Details())); - } - } - - struct UnitFailedMessageData - { - Utility::LocIndString Message; - bool ShowDescription = true; - }; - - // TODO: We may need a detailed result code to enable the internal error to be exposed. - // Additionally, some of the processor exceptions that generate these errors should be enlightened to produce better, localized descriptions. - UnitFailedMessageData GetUnitFailedData(const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation) - { - int32_t resultCode = resultInformation.ResultCode(); - - switch (resultCode) - { - case WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER: return { Resource::String::ConfigurationUnitHasDuplicateIdentifier(Utility::LocIndString{ Utility::ConvertToUTF8(unit.Identifier()) }), false }; - case WINGET_CONFIG_ERROR_MISSING_DEPENDENCY: return { Resource::String::ConfigurationUnitHasMissingDependency(Utility::LocIndString{ Utility::ConvertToUTF8(resultInformation.Details()) }), false }; - case WINGET_CONFIG_ERROR_ASSERTION_FAILED: return { Resource::String::ConfigurationUnitAssertHadNegativeResult(), false }; - case WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED: return { Resource::String::ConfigurationUnitNotFoundInModule(), false }; - case WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY: return { Resource::String::ConfigurationUnitNotFound(), false }; - case WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES: return { Resource::String::ConfigurationUnitMultipleMatches(), false }; - case WINGET_CONFIG_ERROR_UNIT_INVOKE_GET: return { Resource::String::ConfigurationUnitFailedDuringGet(), true }; - case WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST: return { Resource::String::ConfigurationUnitFailedDuringTest(), true }; - case WINGET_CONFIG_ERROR_UNIT_INVOKE_SET: return { Resource::String::ConfigurationUnitFailedDuringSet(), true }; - case WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT: return { Resource::String::ConfigurationUnitModuleConflict(), false }; - case WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE: return { Resource::String::ConfigurationUnitModuleImportFailed(), false }; - case WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT: return { Resource::String::ConfigurationUnitReturnedInvalidResult(), false }; - case WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT: return { Resource::String::ConfigurationUnitSettingConfigRoot(), false }; - case WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN: return { Resource::String::ConfigurationUnitImportModuleAdmin(), false }; - } - - switch (resultInformation.ResultSource()) - { - case ConfigurationUnitResultSource::ConfigurationSet: return { Resource::String::ConfigurationUnitFailedConfigSet(resultCode), true }; - case ConfigurationUnitResultSource::Internal: return { Resource::String::ConfigurationUnitFailedInternal(resultCode), true }; - case ConfigurationUnitResultSource::Precondition: return { Resource::String::ConfigurationUnitFailedPrecondition(resultCode), true }; - case ConfigurationUnitResultSource::SystemState: return { Resource::String::ConfigurationUnitFailedSystemState(resultCode), true }; - case ConfigurationUnitResultSource::UnitProcessing: return { Resource::String::ConfigurationUnitFailedUnitProcessing(resultCode), true }; - } - - // All other errors use a generic message - return { Resource::String::ConfigurationUnitFailed(resultCode), true }; - } - - Utility::LocIndString GetUnitSkippedMessage(const IConfigurationUnitResultInformation& resultInformation) - { - int32_t resultCode = resultInformation.ResultCode(); - - switch (resultInformation.ResultCode()) - { - case WINGET_CONFIG_ERROR_MANUALLY_SKIPPED: return Resource::String::ConfigurationUnitManuallySkipped(); - case WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED: return Resource::String::ConfigurationUnitNotRunDueToDependency(); - case WINGET_CONFIG_ERROR_ASSERTION_FAILED: return Resource::String::ConfigurationUnitNotRunDueToFailedAssert(); - } - - // If new cases arise and are not handled here, at least have a generic backstop message. - return Resource::String::ConfigurationUnitSkipped(resultCode); - } - - void OutputUnitRunFailure(Context& context, const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation) - { - std::string description = Utility::Trim(Utility::ConvertToUTF8(resultInformation.Description())); - - AICLI_LOG_LARGE_STRING(Config, Error, << "Configuration unit " << Utility::ConvertToUTF8(unit.Type()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] failed with code 0x" - << Logging::SetHRFormat << resultInformation.ResultCode() << " and error message:\n" << description, Utility::ConvertToUTF8(resultInformation.Details())); - - UnitFailedMessageData messageData = GetUnitFailedData(unit, resultInformation); - auto error = context.Reporter.Error(); - error << " "_liv << messageData.Message << std::endl; - - if (messageData.ShowDescription && !description.empty()) - { - constexpr size_t maximumDescriptionLines = 3; - size_t consoleWidth = GetConsoleWidth().value_or(120); - std::vector lines = Utility::SplitIntoLines(description, maximumDescriptionLines + 1); - bool wasLimited = Utility::LimitOutputLines(lines, consoleWidth, maximumDescriptionLines); - - for (const auto& line : lines) - { - error << line << std::endl; - } - - if (wasLimited || !resultInformation.Details().empty()) - { - error << Resource::String::ConfigurationDescriptionWasTruncated << std::endl; - } - } - } - - // Coordinates an active progress scope and cancellation of the operation. - template - struct ProgressCancellationUnification - { - ProgressCancellationUnification(std::unique_ptr&& progressScope, const OperationT& operation) : - m_progressScope(std::move(progressScope)), m_operation(operation) - { - SetCancellationFunction(); - } - - void Reset() - { - m_cancelScope.reset(); - m_progressScope.reset(); - } - - Reporter::AsyncProgressScope& Progress() const { return *m_progressScope; } - - void Progress(std::unique_ptr&& progressScope) - { - m_cancelScope.reset(); - m_progressScope = std::move(progressScope); - SetCancellationFunction(); - } - - OperationT& Operation() const { return m_operation; } - - private: - void SetCancellationFunction() - { - if (m_progressScope) - { - m_cancelScope = m_progressScope->Callback().SetCancellationFunction([this]() { m_operation.Cancel(); }); - } - } - - std::unique_ptr m_progressScope; - OperationT m_operation; - IProgressCallback::CancelFunctionRemoval m_cancelScope; - }; - - template - ProgressCancellationUnification CreateProgressCancellationUnification( - std::unique_ptr&& progressScope, - const Operation& operation) - { - return { std::move(progressScope), operation }; - } - - // The base type for progress reporting - template - struct ConfigurationSetProgressOutputBase - { - using Operation = IAsyncOperationWithProgress; - - ConfigurationSetProgressOutputBase(Context& context, const Operation& operation) : - m_context(context), m_unification({}, operation) - { - operation.Progress([&](const Operation& operation, const ProgressType& data) - { - Progress(operation, data); - }); - } - - virtual void Progress(const Operation& operation, const ProgressType& data) = 0; - - protected: - void MarkCompleted(const ConfigurationUnit& unit) - { - winrt::guid unitInstance = unit.InstanceIdentifier(); - m_unitsCompleted.insert(unitInstance); - } - - bool UnitHasPreviouslyCompleted(const ConfigurationUnit& unit) - { - winrt::guid unitInstance = unit.InstanceIdentifier(); - return m_unitsCompleted.count(unitInstance) != 0; - } - - // Sends VT progress to the console - void OutputUnitCompletionProgress() - { - // TODO: Change progress reporting to enable separation of spinner and VT progress reporting - // Preferably we want to be able to have: - // 1. Spinner with indefinite progress VT before set application begins - // 2. 1/N VT progress reporting for configuration units while also showing a spinner for the unit itself - } - - void BeginProgress() - { - m_unification.Progress(m_context.Reporter.BeginAsyncProgress(true)); - } - - void EndProgress() - { - m_unification.Reset(); - } - - Context& m_context; - - private: - ProgressCancellationUnification m_unification; - std::set m_unitsCompleted; - }; - - // Helper to handle progress callbacks from ApplyConfigurationSetAsync - struct ApplyConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase - { - using Operation = ConfigurationSetProgressOutputBase::Operation; - - ApplyConfigurationSetProgressOutput(Context& context, const Operation& operation) : - ConfigurationSetProgressOutputBase(context, operation) - { - } - - void Progress(const Operation& operation, const ConfigurationSetChangeData& data) override - { - auto threadContext = m_context.SetForCurrentThread(); - - if (m_isFirstProgress) - { - HandleUnreportedProgress(operation.GetResults()); - } - - switch (data.Change()) - { - case ConfigurationSetChangeEventType::SetStateChanged: - { - switch (data.SetState()) - { - case ConfigurationSetState::Pending: - m_context.Reporter.Info() << Resource::String::ConfigurationWaitingOnAnother << std::endl; - BeginProgress(); - break; - case ConfigurationSetState::InProgress: - EndProgress(); - break; - case ConfigurationSetState::Completed: - EndProgress(); - break; - } - } - break; - case ConfigurationSetChangeEventType::UnitStateChanged: - HandleUnitProgress(data.Unit(), data.UnitState(), data.ResultInformation()); - break; - } - } - - // If no progress has been reported, this function will report the given results - void HandleUnreportedProgress(const ApplyConfigurationSetResult& result) - { - if (m_isFirstProgress) - { - m_isFirstProgress = false; - - for (const ApplyConfigurationUnitResult& unitResult : result.UnitResults()) - { - HandleUnitProgress(unitResult.Unit(), unitResult.State(), unitResult.ResultInformation()); - } - } - } - - private: - void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationUnitState state, const IConfigurationUnitResultInformation& resultInformation) - { - if (UnitHasPreviouslyCompleted(unit)) - { - return; - } - - switch (state) - { - case ConfigurationUnitState::Pending: - // The unreported progress handler may send pending units, just ignore them - break; - case ConfigurationUnitState::InProgress: - OutputUnitInProgressIfNeeded(unit); - BeginProgress(); - break; - case ConfigurationUnitState::Completed: - OutputUnitInProgressIfNeeded(unit); - EndProgress(); - if (SUCCEEDED(resultInformation.ResultCode())) - { - m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationUnitSuccessfullyApplied << std::endl; - } - else - { - OutputUnitRunFailure(m_context, unit, resultInformation); - } - MarkCompleted(unit); - OutputUnitCompletionProgress(); - break; - case ConfigurationUnitState::Skipped: - OutputUnitInProgressIfNeeded(unit); - AICLI_LOG(Config, Warning, << "Configuration unit " << Utility::ConvertToUTF8(unit.Type()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] was skipped with code 0x" - << Logging::SetHRFormat << resultInformation.ResultCode()); - m_context.Reporter.Warn() << " "_liv << GetUnitSkippedMessage(resultInformation) << std::endl; - MarkCompleted(unit); - OutputUnitCompletionProgress(); - break; - } - } - - void OutputUnitInProgressIfNeeded(const ConfigurationUnit& unit) - { - winrt::guid unitInstance = unit.InstanceIdentifier(); - if (m_unitsSeen.count(unitInstance) == 0) - { - m_unitsSeen.insert(unitInstance); - - OutputConfigurationUnitHeader(m_context, unit, unit.Details() ? unit.Details().UnitType() : unit.Type()); - } - } - - std::set m_unitsSeen; - bool m_isFirstProgress = true; - }; - - // Helper to handle progress callbacks from TestConfigurationSetAsync - struct TestConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase - { - using Operation = ConfigurationSetProgressOutputBase::Operation; - - TestConfigurationSetProgressOutput(Context& context, const Operation& operation) : - ConfigurationSetProgressOutputBase(context, operation) - { - // Start the spinner for the first unit being tested since we only receive completions - BeginProgress(); - } - - void Progress(const Operation& operation, const TestConfigurationUnitResult& data) override - { - auto threadContext = m_context.SetForCurrentThread(); - - if (m_isFirstProgress) - { - HandleUnreportedProgress(operation.GetResults()); - } - - HandleUnitProgress(data.Unit(), data.TestResult(), data.ResultInformation()); - } - - // If no progress has been reported, this function will report the given results - void HandleUnreportedProgress(const TestConfigurationSetResult& result) - { - if (m_isFirstProgress) - { - m_isFirstProgress = false; - - for (const TestConfigurationUnitResult& unitResult : result.UnitResults()) - { - HandleUnitProgress(unitResult.Unit(), unitResult.TestResult(), unitResult.ResultInformation()); - } - } - } - - private: - void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationTestResult testResult, const IConfigurationUnitResultInformation& resultInformation) - { - if (UnitHasPreviouslyCompleted(unit)) - { - return; - } - - EndProgress(); - - OutputConfigurationUnitHeader(m_context, unit, unit.Details() ? unit.Details().UnitType() : unit.Type()); - - switch (testResult) - { - case ConfigurationTestResult::Failed: - OutputUnitRunFailure(m_context, unit, resultInformation); - break; - case ConfigurationTestResult::Negative: - m_context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationNotInDesiredState << std::endl; - break; - case ConfigurationTestResult::NotRun: - m_context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationNoTestRun << std::endl; - break; - case ConfigurationTestResult::Positive: - m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationInDesiredState << std::endl; - break; - default: // ConfigurationTestResult::Unknown - m_context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(testResult)) << std::endl; - break; - } - - MarkCompleted(unit); - OutputUnitCompletionProgress(); - BeginProgress(); - } - - bool m_isFirstProgress = true; - }; - - std::string GetNormalizedIdentifier(const winrt::hstring& identifier) - { - return Utility::FoldCase(Utility::NormalizedString{ identifier }); - } - - // Get unit validation order. Make sure dependency units are before units depending on them. - std::vector GetConfigurationSetUnitValidationOrder(winrt::Windows::Foundation::Collections::IVectorView units) - { - // Create id to index map for easier processing. - std::map idToUnitIndex; - for (uint32_t i = 0; i < units.Size(); ++i) - { - auto id = GetNormalizedIdentifier(units.GetAt(i).Identifier()); - if (!id.empty()) - { - idToUnitIndex.emplace(std::move(id), i); - } - } - - // We do not need to worry about duplicate id, missing dependency or loops - // since dependency integrity is already validated in earlier semantic checks. - - std::vector validationOrder; - - std::function addUnitToValidationOrder = - [&](const ConfigurationUnit& unit, uint32_t index) - { - if (std::find(validationOrder.begin(), validationOrder.end(), index) == validationOrder.end()) - { - for (auto const& dependencyId : unit.Dependencies()) - { - auto dependencyIndex = idToUnitIndex.find(GetNormalizedIdentifier(dependencyId))->second; - addUnitToValidationOrder(units.GetAt(dependencyIndex), dependencyIndex); - } - validationOrder.emplace_back(index); - } - }; - - for (uint32_t i = 0; i < units.Size(); ++i) - { - addUnitToValidationOrder(units.GetAt(i), i); - } - - THROW_HR_IF(E_UNEXPECTED, units.Size() != validationOrder.size()); - - return validationOrder; - } - - void SetNameAndOrigin(ConfigurationSet& set, std::filesystem::path& absolutePath) - { - // TODO: Consider how to properly determine a good value for name and origin. - set.Name(absolutePath.filename().wstring()); - set.Origin(absolutePath.parent_path().wstring()); - set.Path(absolutePath.wstring()); - } - - void OpenConfigurationSet(Execution::Context& context, const std::string& argPath, bool allowRemote) - { - auto progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationReadingConfigFile()); - - std::wstring argPathWide = Utility::ConvertToUTF16(argPath); - bool isRemote = Utility::IsUrlRemote(argPath); - std::filesystem::path absolutePath; - Streams::IInputStream inputStream = nullptr; - - if (isRemote) - { - if (!allowRemote) - { - AICLI_LOG(Config, Error, << "Remote files are not supported"); - AICLI_TERMINATE_CONTEXT(ERROR_NOT_SUPPORTED); - } - - std::ostringstream stringStream; - ProgressCallback emptyCallback; - Utility::DownloadToStream(argPath, stringStream, Utility::DownloadType::ConfigurationFile, emptyCallback); - - auto strContent = stringStream.str(); - std::vector byteContent{ strContent.begin(), strContent.end() }; - - Streams::InMemoryRandomAccessStream memoryStream; - Streams::DataWriter streamWriter{ memoryStream }; - streamWriter.WriteBytes(byteContent); - streamWriter.StoreAsync().get(); - streamWriter.DetachStream(); - memoryStream.Seek(0); - inputStream = memoryStream; - } - else - { - absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPathWide }); - auto openAction = Streams::FileRandomAccessStream::OpenAsync(absolutePath.wstring(), FileAccessMode::Read); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); }); - inputStream = openAction.get(); - } - - OpenConfigurationSetResult openResult = nullptr; - { - auto openAction = context.Get().Processor().OpenConfigurationSetAsync(inputStream); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); }); - openResult = openAction.get(); - } - - progressScope.reset(); - - if (FAILED_LOG(static_cast(openResult.ResultCode().value))) - { - AICLI_LOG(Config, Error, << "Failed to open configuration set at " << (isRemote ? argPath : absolutePath.u8string()) << " with error 0x" << Logging::SetHRFormat << static_cast(openResult.ResultCode().value)); - - switch (openResult.ResultCode()) - { - case WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE: - context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidType(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; - break; - case WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE: - context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidValue(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }, Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl; - break; - case WINGET_CONFIG_ERROR_MISSING_FIELD: - context.Reporter.Error() << Resource::String::ConfigurationFieldMissing(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; - break; - case WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION: - context.Reporter.Error() << Resource::String::ConfigurationFileVersionUnknown(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl; - break; - case WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE: - case WINGET_CONFIG_ERROR_INVALID_YAML: - default: - context.Reporter.Error() << Resource::String::ConfigurationFileInvalidYAML << std::endl; - break; - } - - if (openResult.Line() != 0) - { - context.Reporter.Error() << Resource::String::SeeLineAndColumn(openResult.Line(), openResult.Column()) << std::endl; - } - - AICLI_TERMINATE_CONTEXT(openResult.ResultCode()); - } - - ConfigurationSet result = openResult.Set(); - - // Fill out the information about the set based on it coming from a file. - if (isRemote) - { - result.Name(Utility::GetFileNameFromURI(argPath).wstring()); - result.Origin(argPathWide); - // Do not set path. This means ${WinGetConfigRoot} not supported in remote configs. - } - else - { - SetNameAndOrigin(result, absolutePath); - } - - context.Get().Set(result); - } - - ConfigurationUnit CreateConfigurationUnitFromModuleResource(std::string_view moduleName, std::string_view resourceName, std::string_view descriptionResourceName, const Utility::Version& schemaVersion) - { - std::wstring moduleNameWide = Utility::ConvertToUTF16(moduleName); - std::wstring resourceNameWide = Utility::ConvertToUTF16(resourceName); - - ConfigurationUnit unit; - unit.Type(schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType ? moduleNameWide + L'/' + resourceNameWide : resourceNameWide); - unit.Identifier(unit.Type() + L'_' + Utility::ConvertToUTF16(Utility::GetRandomString())); - - ValueSet directives; - directives.Insert(s_Directive_Module, PropertyValue::CreateString(moduleNameWide)); - - Utility::LocIndString description; - if (!descriptionResourceName.empty()) - { - description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ descriptionResourceName }); - } - else - { - description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ resourceName }); - } - - directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); - unit.Metadata(directives); - - return unit; - } - - ConfigurationUnit CreateConfigurationUnitFromUnitType(std::wstring_view unitType, std::string_view descriptionResourceName = "") - { - ConfigurationUnit unit; - unit.Type(unitType); - unit.Identifier(unit.Type() + L'_' + Utility::ConvertToUTF16(Utility::GetRandomString())); - - ValueSet directives; - Utility::LocIndString description; - if (!descriptionResourceName.empty()) - { - description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ descriptionResourceName }); - } - else - { - description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ Utility::ConvertToUTF8(unitType) }); - } - - directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); - unit.Metadata(directives); - - return unit; - } - - ConfigurationUnit CreatePowerShellPackageUnit() - { - ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(s_UnitType_WinGetPackage_DSCv3, "Microsoft.PowerShell"); - - ValueSet settings; - settings.Insert(s_Setting_WinGetPackage_Id, PropertyValue::CreateString(s_Predefined_PowerShell_PackageId)); - settings.Insert(s_Setting_WinGetPackage_Source, PropertyValue::CreateString(s_Predefined_PowerShell_PackageSource)); - unit.Settings(settings); - - return unit; - } - - ValueSet CreateValueSetFromStringVector(const std::vector& values) - { - ValueSet result; - size_t index = 0; - - for (const auto& value : values) - { - std::wostringstream strstr; - strstr << index++; - result.Insert(strstr.str(), PropertyValue::CreateString(value)); - } - - result.Insert(L"treatAsArray", PropertyValue::CreateBoolean(true)); - return result; - } - - // TODO: This is a workaround unit to ensure v2 dsc resource modules. Move to dsc v3 resource when available. - ConfigurationUnit CreateRequiredModuleUnit(std::wstring_view moduleName, const ConfigurationUnit& dependentUnit) - { - std::wstring moduleNameString{ moduleName }; - - ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(L"Microsoft.DSC.Transitional/RunCommandOnSet", Utility::ConvertToUTF8(moduleName)); - - ValueSet settings; - settings.Insert(L"executable", PropertyValue::CreateString(L"pwsh")); - std::vector arguments = - { - L"-NoProfile", - L"-NoLogo", - L"-Command", - L"if (-not (Get-Module -ListAvailable -Name " + moduleNameString + L")) { Install-Module -Name " + moduleNameString + L" -Confirm:$False -Force -AllowPrerelease -AllowClobber }" - }; - settings.Insert(L"arguments", CreateValueSetFromStringVector(arguments)); - unit.Settings(settings); - - unit.Dependencies().Append(dependentUnit.Identifier()); - - return unit; - } - - std::wstring GetWinGetSourceUnitType(const ConfigurationContext& configContext) - { - Utility::Version schemaVersion = { Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) }; - ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configContext.Set()); - - if (schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType) - { - if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3) - { - return std::wstring{ s_UnitType_WinGetSource_DSCv3 }; - } - else - { - return std::wstring{ s_Module_WinGetClient } + L'/' + std::wstring{ s_Unit_WinGetSource }; - } - } - else - { - return std::wstring{ s_Unit_WinGetSource }; - } - } - - ConfigurationUnit CreateWinGetSourceUnit(const PackageCollection::Source& source, std::wstring_view unitType) - { - std::string sourceUnitId = source.Details.Name + '_' + source.Details.Type; - std::wstring sourceUnitIdWide = Utility::ConvertToUTF16(sourceUnitId); - - ConfigurationUnit unit; - unit.Type(unitType); - unit.Identifier(sourceUnitIdWide); - unit.Intent(ConfigurationUnitIntent::Apply); - - auto description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ sourceUnitId }); - - ValueSet directives; - directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); - unit.Metadata(directives); - - ValueSet settings; - settings.Insert(s_Setting_WinGetSource_Name, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Name))); - settings.Insert(s_Setting_WinGetSource_Arg, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Arg))); - settings.Insert(s_Setting_WinGetSource_Type, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Type))); - if (WI_IsFlagSet(source.Details.TrustLevel, Repository::SourceTrustLevel::Trusted)) - { - settings.Insert(s_Setting_WinGetSource_TrustLevel, PropertyValue::CreateString(L"trusted")); - } - if (source.Details.Explicit) - { - settings.Insert(s_Setting_WinGetSource_Explicit, PropertyValue::CreateBoolean(true)); - } - if (source.Details.Priority != 0) - { - settings.Insert(s_Setting_WinGetSource_Priority, PropertyValue::CreateInt32(source.Details.Priority)); - } - unit.Settings(settings); - - unit.Environment().Context(SecurityContext::Elevated); - - return unit; - } - - std::wstring GetWinGetPackageUnitType(const ConfigurationContext& configContext) - { - Utility::Version schemaVersion = { Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) }; - ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configContext.Set()); - - if (schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType) - { - if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3) - { - return std::wstring{ s_UnitType_WinGetPackage_DSCv3 }; - } - else - { - return std::wstring{ s_Module_WinGetClient } + L'/' + std::wstring{ s_Unit_WinGetPackage }; - } - } - else - { - return std::wstring{ s_Unit_WinGetPackage }; - } - } - - ConfigurationUnit CreateWinGetPackageUnit(const PackageCollection::Package& package, const PackageCollection::Source& source, bool includeVersion, const ConfigurationUnit& dependentUnit, std::wstring_view unitType) - { - std::wstring packageIdWide = Utility::ConvertToUTF16(package.Id); - std::wstring sourceNameWide = Utility::ConvertToUTF16(source.Details.Name); - - ConfigurationUnit unit; - unit.Type(unitType); - unit.Identifier(sourceNameWide + L'_' + packageIdWide); - unit.Intent(ConfigurationUnitIntent::Apply); - - auto description = Resource::String::ConfigureExportUnitInstallDescription(package.Id); - - ValueSet directives; - directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); - unit.Metadata(directives); - - ValueSet settings; - settings.Insert(s_Setting_WinGetPackage_Id, PropertyValue::CreateString(packageIdWide)); - settings.Insert(s_Setting_WinGetPackage_Source, PropertyValue::CreateString(sourceNameWide)); - if (includeVersion) - { - settings.Insert(s_Setting_WinGetPackage_Version, PropertyValue::CreateString(Utility::ConvertToUTF16(package.VersionAndChannel.GetVersion().ToString()))); - } - unit.Settings(settings); - - // TODO: We may consider setting security environment based on installer elevation requirements? - - // Add dependency if needed. - if (dependentUnit) - { - auto dependencies = winrt::single_threaded_vector(); - dependencies.Append(dependentUnit.Identifier()); - unit.Dependencies(std::move(dependencies)); - } - - return unit; - } - - ApplyConfigurationUnitResult ApplyUnit(Execution::Context& context, ConfigurationUnit& unit) - { - unit.Intent(ConfigurationUnitIntent::Apply); - - auto progressScope = context.Reporter.BeginAsyncProgress(true); - - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationApplyingUnit()); - - ApplyConfigurationUnitResult applyResult = nullptr; - { - auto applyAction = context.Get().Processor().ApplyUnitAsync(unit); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { applyAction.Cancel(); }); - applyResult = applyAction.get(); - } - - progressScope.reset(); - return applyResult; - } - - GetConfigurationUnitSettingsResult GetUnitSettings(Execution::Context& context, ConfigurationUnit& unit) - { - // This assumes there are no required properties for Get, but for example WinGetPackage requires the Id. - // It is obviously wrong and will be wrong until Export is implemented for DSC v2 and a proper way to inform - // about input to winget configure export is implemented. Drink the kool-aid and transcend. - unit.Intent(ConfigurationUnitIntent::Inform); - - auto progressScope = context.Reporter.BeginAsyncProgress(true); - - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationGettingResourceSettings()); - - GetConfigurationUnitSettingsResult getResult = nullptr; - { - auto getAction = context.Get().Processor().GetUnitSettingsAsync(unit); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { getAction.Cancel(); }); - getResult = getAction.get(); - } - - progressScope.reset(); - return getResult; - } - - GetAllConfigurationUnitsResult GetAllUnits(Execution::Context& context, ConfigurationUnit& unit) - { - unit.Intent(ConfigurationUnitIntent::Inform); - - auto progressScope = context.Reporter.BeginAsyncProgress(true); - - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationExportingUnit()); - - GetAllConfigurationUnitsResult getResult = nullptr; - { - auto getAction = context.Get().Processor().GetAllUnitsAsync(unit); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { getAction.Cancel(); }); - getResult = getAction.get(); - } - - progressScope.reset(); - return getResult; - } - - std::vector ExportUnit(Execution::Context& context, ConfigurationUnit& unit, bool throwOnFailure = false) - { - std::vector result; - - context.Reporter.Info() << Resource::String::ConfigurationExportUnitStart(Utility::LocIndView{ Utility::ConvertToUTF8(unit.Type()) }) << std::endl; - - // Try export first - auto exportResult = GetAllUnits(context, unit); - auto exportResultCode = exportResult.ResultInformation().ResultCode(); - if (SUCCEEDED(exportResultCode)) - { - for (auto resultUnit : exportResult.Units()) - { - result.emplace_back(std::move(resultUnit)); - } - } - else - { - AICLI_LOG(Config, Warning, << "Failed GetAllUnits. Will try GetUnitSettings."); - LogFailedGetConfigurationUnitDetails(unit, exportResult.ResultInformation()); - - // Try GetUnitSettings if export failed. - auto getResult = GetUnitSettings(context, unit); - auto getResultCode = getResult.ResultInformation().ResultCode(); - if (getResultCode == WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY) - { - // Retry if it fails with not found in the case the module is a pre-released one. - AICLI_LOG(Config, Info, << "Failed GetUnitSettings because module not found. Will try allow prerelease."); - auto directives = unit.Metadata(); - directives.Insert(s_Directive_AllowPrerelease, PropertyValue::CreateBoolean(true)); - unit.Metadata(directives); - - getResult = GetUnitSettings(context, unit); - } - - if (FAILED(getResult.ResultInformation().ResultCode())) - { - AICLI_LOG(Config, Error, << "Failed Get Unit Settings"); - LogFailedGetConfigurationUnitDetails(unit, getResult.ResultInformation()); - - if (throwOnFailure) - { - context.Reporter.Error() << Resource::String::ConfigurationExportUnitFailed << std::endl; - OutputUnitRunFailure(context, unit, getResult.ResultInformation()); - THROW_HR(WINGET_CONFIG_ERROR_GET_FAILED); - } - else - { - context.Reporter.Warn() << Resource::String::ConfigurationExportUnitFailed << std::endl; - } - } - else - { - unit.Settings(getResult.Settings()); - result.emplace_back(unit); - } - } - - return result; - } - - void AddDependentUnit(std::vector& units, const ConfigurationUnit& dependentUnit) - { - for (auto& unit : units) - { - unit.Dependencies().Append(dependentUnit.Identifier()); - } - } - - void AddElevatedEnvironment(std::vector& units) - { - for (auto& unit : units) - { - unit.Environment().Context(SecurityContext::Elevated); - } - } - - std::vector GetAllUnitProcessors3(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - std::vector result; - - // Only supported by dsc v3 processor. - if (ConfigurationRemoting::ProcessorEngine::DSCv3 == ConfigurationRemoting::DetermineProcessorEngine(configContext.Set())) - { - auto progressScope = context.Reporter.BeginAsyncProgress(true); - - progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationGettingUnitProcessors()); - - { - FindUnitProcessorsOptions findOptions; - findOptions.UnitDetailFlags(ConfigurationUnitDetailFlags::Local); - auto findAction = context.Get().Processor().FindUnitProcessorsAsync(findOptions); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { findAction.Cancel(); }); - for (auto unitProcessor : findAction.get()) - { - IConfigurationUnitProcessorDetails3 processor3; - if (unitProcessor.try_as(processor3)) - { - result.emplace_back(std::move(processor3)); - } - } - } - - progressScope.reset(); - } - - return result; - } - - void ExportPredefinedResources(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - - // PowerShell package needs to be present for certain predefined modules to work. - ConfigurationUnit powerShellPackageUnit = CreatePowerShellPackageUnit(); - configContext.Set().Units().Append(powerShellPackageUnit); - - // Apply the unit to make sure it's on the system. - context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ "Microsoft PowerShell Package" }) << std::endl; - auto applyPowerShellResult = ApplyUnit(context, powerShellPackageUnit); - if (FAILED(applyPowerShellResult.ResultInformation().ResultCode())) - { - AICLI_LOG(Config, Warning, << "Failed to ensure module. [Microsoft PowerShell Package] Related settings may not be exported."); - LogFailedGetConfigurationUnitDetails(powerShellPackageUnit, applyPowerShellResult.ResultInformation()); - context.Reporter.Warn() << Resource::String::ConfigurationExportInstallRequiredModuleFailed << std::endl; - } - - for (const auto& resources : PredefinedResourcesForExport()) - { - std::optional requiredModuleUnit; - - if (!resources.RequiredModule.empty()) - { - requiredModuleUnit = CreateRequiredModuleUnit(resources.RequiredModule, powerShellPackageUnit); - - // Apply the unit to make sure it's on the system. - context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ Utility::ConvertToUTF8(resources.RequiredModule) }) << std::endl; - auto applyResult = ApplyUnit(context, requiredModuleUnit.value()); - if (SUCCEEDED(applyResult.ResultInformation().ResultCode())) - { - configContext.Set().Units().Append(requiredModuleUnit.value()); - } - else - { - AICLI_LOG(Config, Warning, << "Failed to ensure module. [" << Utility::ConvertToUTF8(resources.RequiredModule) << "] Related settings will not be exported."); - LogFailedGetConfigurationUnitDetails(requiredModuleUnit.value(), applyResult.ResultInformation()); - context.Reporter.Warn() << Resource::String::ConfigurationExportInstallRequiredModuleFailed << std::endl; - continue; - } - } - - for (const auto& resourceInfo : resources.ResourceInfos) - { - auto resourceUnit = CreateConfigurationUnitFromUnitType(resourceInfo.UnitType); - auto exportedUnits = ExportUnit(context, resourceUnit); - - if (requiredModuleUnit) - { - AddDependentUnit(exportedUnits, requiredModuleUnit.value()); - } - - // The dynamic processor factory does not support operating elevated units without a set. - // Luckily the Get/Export for all PreDefinedResources do not require elevation. - // Here we add elevation environment to exported results. - if (resourceInfo.ElevationRequired) - { - AddElevatedEnvironment(exportedUnits); - } - - for (auto exportedUnit : exportedUnits) - { - configContext.Set().Units().Append(std::move(exportedUnit)); - } - } - } - } - - // Contains a tree of all unit processors by their path. - struct UnitProcessorTree - { - private: - struct Node - { - // Packages whose installed location is at this node - std::vector Packages; - - // Units whose location is at this node. - std::vector Units; - }; - - Filesystem::PathTree m_pathTree; - - Node& FindNodeForFilePath(const winrt::hstring& filePath) - { - std::filesystem::path path{ std::wstring{ filePath } }; - return m_pathTree.FindOrInsert(path.parent_path()); - } - - public: - UnitProcessorTree(std::vector&& unitProcessors) - { - for (auto&& unit : unitProcessors) - { - winrt::hstring unitPath = unit.Path(); - AICLI_LOG(Config, Verbose, << "Found unit `" << Utility::ConvertToUTF8(unit.UnitType()) << "` at: " << Utility::ConvertToUTF8(unitPath)); - Node& node = FindNodeForFilePath(unitPath); - node.Units.emplace_back(std::move(unit)); - } - } - - void PlacePackage(const PackageCollection::Package& package) - { - Node* node = m_pathTree.Find(package.InstalledLocation); - if (node) - { - node->Packages.emplace_back(package); - } - } - - std::vector GetResourcesForPackage(const PackageCollection::Package& package) const - { - std::vector result; - - m_pathTree.VisitIf( - package.InstalledLocation, - [&](const Node& node) - { - for (const auto& unit : node.Units) - { - result.emplace_back(unit); - } - }, - [](const Node& node) - { - return node.Packages.empty(); - }); - - return result; - } - }; - - void ProcessPackagesForConfigurationExportAll(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - std::wstring sourceUnitType = GetWinGetSourceUnitType(configContext); - std::wstring packageUnitType = GetWinGetPackageUnitType(configContext); - - // This will be later used by per package settings export. - std::vector unitProcessors; - try - { - unitProcessors = GetAllUnitProcessors3(context); - } - catch (...) - { - AICLI_LOG(Config, Warning, << "Finding unit processors failed. Individual package settings will not be exported."); - context.Reporter.Warn() << Resource::String::ConfigurationExportFailedToGetUnitProcessors << std::endl; - } - - auto exclusionList = PackageSettingsExclusionList(); - - // Filter out processors in exclusion list. - for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */) - { - std::wstring unitType{ itr->UnitType() }; - bool processorRemoved = false; - for (const auto& exclusionItem : exclusionList) - { - if (Utility::CaseInsensitiveStartsWith(unitType, exclusionItem)) - { - AICLI_LOG(Config, Verbose, << "Filtering excluded resource `" << Utility::ConvertToUTF8(itr->UnitType()) << "` from export"); - itr = unitProcessors.erase(itr); - processorRemoved = true; - break; - } - } - - if (!processorRemoved) - { - itr++; - } - } - - // Filter out DSC-shipped resources and any resources co-located with them. - // First pass: find the parent directory of each known DSC resource to identify the DSC - // installation location(s), handling both packaged and unpackaged (PATH-based) DSC installs. - // Second pass: remove any resource that either matches a known DSC prefix or resides in one - // of those locations, unless the resource type appears in DscShippedResourcesAllowList(). - { - const auto dscPrefixes = DscShippedResourcePrefixes(); - const auto dscAllowList = DscShippedResourcesAllowList(); - - std::set dscLocations; - for (const auto& processor : unitProcessors) - { - std::wstring unitType{ processor.UnitType() }; - for (const auto& prefix : dscPrefixes) - { - if (Utility::CaseInsensitiveStartsWith(unitType, prefix)) - { - std::filesystem::path location = std::filesystem::weakly_canonical( - std::filesystem::path{ std::wstring{ processor.Path() } }.parent_path()); - dscLocations.emplace(std::move(location)); - break; - } - } - } - - for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */) - { - std::wstring unitType{ itr->UnitType() }; - - // Check the allow list first. - bool inAllowList = false; - for (const auto& allowedPrefix : dscAllowList) - { - if (Utility::CaseInsensitiveStartsWith(unitType, allowedPrefix)) - { - inAllowList = true; - break; - } - } - - if (inAllowList) - { - ++itr; - continue; - } - - // Check if the resource type is a known DSC-shipped prefix. - bool isDscResource = false; - for (const auto& prefix : dscPrefixes) - { - if (Utility::CaseInsensitiveStartsWith(unitType, prefix)) - { - isDscResource = true; - break; - } - } - - // If not matched by prefix, check whether it shares a location with a known DSC resource. - if (!isDscResource && !dscLocations.empty()) - { - std::filesystem::path location = std::filesystem::weakly_canonical( - std::filesystem::path{ std::wstring{ itr->Path() } }.parent_path()); - - if (dscLocations.find(location) != dscLocations.end()) - { - isDscResource = true; - } - } - - if (isDscResource) - { - AICLI_LOG(Config, Verbose, << "Filtering DSC-shipped resource `" << Utility::ConvertToUTF8(itr->UnitType()) << "` from export"); - itr = unitProcessors.erase(itr); - } - else - { - ++itr; - } - } - } - - // Build a tree of the unit processors and place packages onto it to indicate nearest ownership. - UnitProcessorTree unitProcessorTree{ std::move(unitProcessors) }; - - for (const auto& source : context.Get().Sources) - { - for (const auto& package : source.Packages) - { - unitProcessorTree.PlacePackage(package); - } - } - - for (const auto& source : context.Get().Sources) - { - // Create WinGetSource unit - ConfigurationUnit sourceUnit = anon::CreateWinGetSourceUnit(source, sourceUnitType); - configContext.Set().Units().Append(sourceUnit); - - for (const auto& package : source.Packages) - { - AICLI_LOG(Config, Verbose, << "Exporting package `" << package.Id << "` at: " << package.InstalledLocation); - - auto packageUnit = anon::CreateWinGetPackageUnit(package, source, context.Args.Contains(Args::Type::IncludeVersions), sourceUnit, packageUnitType); - configContext.Set().Units().Append(packageUnit); - - // Try package settings export. - auto unitsForPackage = unitProcessorTree.GetResourcesForPackage(package); - for (const auto& unit : unitsForPackage) - { - winrt::hstring unitType = unit.UnitType(); - AICLI_LOG(Config, Verbose, << " exporting unit `" << Utility::ConvertToUTF8(unitType)); - - ConfigurationUnit configUnit = anon::CreateConfigurationUnitFromUnitType( - unitType, - Utility::ConvertToUTF8(packageUnit.Identifier())); - - auto exportedUnits = anon::ExportUnit(context, configUnit); - anon::AddDependentUnit(exportedUnits, packageUnit); - - for (const auto& exportedUnit : exportedUnits) - { - configContext.Set().Units().Append(exportedUnit); - } - } - } - } - } - - void ProcessPackagesForConfigurationExportSingle(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - - // When exporting single WinGetPackage unit, the WinGetPackage unit can be used as a dependent unit for following configuration unit. - std::optional singlePackageUnit; - - if (context.Args.Contains(Execution::Args::Type::ConfigurationExportPackageId)) - { - const auto& exportSources = context.Get().Sources; - // There should be 1 package under 1 source. - THROW_HR_IF(E_UNEXPECTED, exportSources.size() != 1 || exportSources[0].Packages.size() != 1); - - ConfigurationUnit sourceUnit = anon::CreateWinGetSourceUnit(exportSources[0], GetWinGetSourceUnitType(configContext)); - configContext.Set().Units().Append(sourceUnit); - - singlePackageUnit = anon::CreateWinGetPackageUnit(exportSources[0].Packages[0], exportSources[0], context.Args.Contains(Args::Type::IncludeVersions), sourceUnit, GetWinGetPackageUnitType(configContext)); - configContext.Set().Units().Append(singlePackageUnit.value()); - } - - if (context.Args.Contains(Execution::Args::Type::ConfigurationExportModule, Execution::Args::Type::ConfigurationExportResource)) - { - auto configUnit = anon::CreateConfigurationUnitFromModuleResource( - context.Args.GetArg(Args::Type::ConfigurationExportModule), - context.Args.GetArg(Args::Type::ConfigurationExportResource), - singlePackageUnit ? Utility::ConvertToUTF8(singlePackageUnit->Identifier()) : "", - Utility::Version{ Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) }); - - auto exportedUnits = anon::ExportUnit(context, configUnit, true); - - if (singlePackageUnit) - { - anon::AddDependentUnit(exportedUnits, singlePackageUnit.value()); - } - - for (auto exportedUnit : exportedUnits) - { - configContext.Set().Units().Append(exportedUnit); - } - } - } - - bool HistorySetMatchesInput(const ConfigurationSet& set, const std::string& foldedInput) - { - if (foldedInput.empty()) - { - return false; - } - - if (Utility::FoldCase(Utility::NormalizedString{ set.Name() }) == foldedInput) - { - return true; - } - - std::ostringstream identifierStream; - identifierStream << set.InstanceIdentifier(); - std::string identifier = identifierStream.str(); - THROW_HR_IF(E_UNEXPECTED, identifier.empty()); - - std::size_t startPosition = 0; - if (identifier[0] == '{' && foldedInput[0] != '{') - { - startPosition = 1; - } - - std::string_view identifierView = identifier; - identifierView = identifierView.substr(startPosition); - - return Utility::CaseInsensitiveStartsWith(identifierView, foldedInput); - } - - Resource::LocString ToLocString(ConfigurationSetState state) - { - switch (state) - { - case ConfigurationSetState::Pending: - return Resource::String::ConfigurationSetStatePending; - case ConfigurationSetState::InProgress: - return Resource::String::ConfigurationSetStateInProgress; - case ConfigurationSetState::Completed: - return Resource::String::ConfigurationSetStateCompleted; - case ConfigurationSetState::Unknown: - default: - return Resource::String::ConfigurationSetStateUnknown; - } - } - - Resource::LocString ToLocString(ConfigurationUnitState state) - { - switch (state) - { - case ConfigurationUnitState::Pending: - return Resource::String::ConfigurationUnitStatePending; - case ConfigurationUnitState::InProgress: - return Resource::String::ConfigurationUnitStateInProgress; - case ConfigurationUnitState::Completed: - return Resource::String::ConfigurationUnitStateCompleted; - case ConfigurationUnitState::Skipped: - return Resource::String::ConfigurationUnitStateSkipped; - case ConfigurationUnitState::Unknown: - default: - return Resource::String::ConfigurationUnitStateUnknown; - } - } - - std::string_view ToString(ConfigurationChangeEventType type) - { - switch (type) - { - case ConfigurationChangeEventType::SetAdded: - return "SetAdded"; - case ConfigurationChangeEventType::SetStateChanged: - return "SetStateChanged"; - case ConfigurationChangeEventType::SetRemoved: - return "SetRemoved"; - case ConfigurationChangeEventType::Unknown: - default: - return "Unknown"; - } - } - - std::string_view ToString(ConfigurationUnitResultSource source) - { - switch (source) - { - case ConfigurationUnitResultSource::Internal: - return "Internal"; - case ConfigurationUnitResultSource::ConfigurationSet: - return "ConfigurationSet"; - case ConfigurationUnitResultSource::UnitProcessing: - return "UnitProcessing"; - case ConfigurationUnitResultSource::SystemState: - return "SystemState"; - case ConfigurationUnitResultSource::Precondition: - return "Precondition"; - case ConfigurationUnitResultSource::None: - default: - return "None"; - } - } - } - - void CreateConfigurationProcessor(Context& context) - { - anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ anon::CreateConfigurationSetProcessorFactory(context) }); - } - - void CreateConfigurationProcessorWithoutFactory(Execution::Context& context) - { - anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ IConfigurationSetProcessorFactory{ nullptr } }); - } - - void OpenConfigurationSet(Context& context) - { - if (context.Args.Contains(Args::Type::ConfigurationFile)) - { - std::string argPath{ context.Args.GetArg(Args::Type::ConfigurationFile) }; - anon::OpenConfigurationSet(context, argPath, true); - } - else - { - THROW_HR_IF(E_UNEXPECTED, !context.Args.Contains(Args::Type::ConfigurationHistoryItem)); - - context << - GetConfigurationSetHistory << - SelectSetFromHistory; - } - } - - void CreateOrOpenConfigurationSet::operator()(Context& context) const - { - std::string argPath{ context.Args.GetArg(Args::Type::OutputFile) }; - - if (std::filesystem::exists(argPath) && !m_createAlways) - { - anon::OpenConfigurationSet(context, argPath, false); - } - else - { - ConfigurationSet set; - set.SchemaVersion(Utility::ConvertToUTF16(m_defaultSchemaVersion)); - set.Environment().ProcessorIdentifier(ConfigurationRemoting::ToString(ConfigurationRemoting::ProcessorEngine::DSCv3)); - - std::wstring argPathWide = Utility::ConvertToUTF16(argPath); - auto absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPathWide }); - anon::SetNameAndOrigin(set, absolutePath); - - context.Get().Set(set); - } - } - - void ShowConfigurationSet(Context& context) - { - ConfigurationContext& configContext = context.Get(); - - if (configContext.Set().Units().Size() == 0) - { - context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl; - // This isn't an error termination, but there is no reason to proceed. - AICLI_TERMINATE_CONTEXT(S_FALSE); - } - - auto gettingDetailString = Resource::String::ConfigurationGettingDetails(); - auto progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(gettingDetailString); - - auto getDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::ReadOnly); - auto unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getDetailsOperation); - - bool suppressDetailsOutput = context.Args.Contains(Args::Type::ConfigurationSuppressPrologue); - anon::OutputHelper outputHelper{ context }; - uint32_t unitsShown = 0; - - if (!suppressDetailsOutput) - { - getDetailsOperation.Progress([&](const IAsyncOperationWithProgress& operation, const GetConfigurationUnitDetailsResult&) - { - auto threadContext = context.SetForCurrentThread(); - - unification.Reset(); - - auto unitResults = operation.GetResults().UnitResults(); - for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown) - { - GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown); - anon::LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation()); - outputHelper.OutputConfigurationUnitInformation(unitResult.Unit()); - } - - progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(gettingDetailString); - unification.Progress(std::move(progressScope)); - }); - } - - HRESULT hr = S_OK; - GetConfigurationSetDetailsResult result = nullptr; - - try - { - result = getDetailsOperation.get(); - } - catch (...) - { - hr = LOG_CAUGHT_EXCEPTION(); - } - - unification.Reset(); - - if (context.IsTerminated()) - { - // The context should only be terminated on us due to cancellation - context.Reporter.Error() << Resource::String::Cancelled << std::endl; - return; - } - - if (FAILED(hr)) - { - // Failing to get details might not be fatal, warn about it but proceed - context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl; - } - - // Handle any missing progress callbacks that are in the results - if (result && !suppressDetailsOutput) - { - auto unitResults = result.UnitResults(); - if (unitResults) - { - for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown) - { - GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown); - anon::LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation()); - outputHelper.OutputConfigurationUnitInformation(unitResult.Unit()); - } - } - } - - // Handle any units that are NOT in the results (due to an exception part of the way through) - if (!suppressDetailsOutput) - { - auto allUnits = configContext.Set().Units(); - for (unitsShown; unitsShown < allUnits.Size(); ++unitsShown) - { - ConfigurationUnit unit = allUnits.GetAt(unitsShown); - outputHelper.OutputConfigurationUnitInformation(unit); - } - } - - if (outputHelper.ValuesTruncated) - { - // Using error to make this stand out from other warnings - context.Reporter.Error() << Resource::String::ConfigurationWarningSetViewTruncated << std::endl; - } - } - - void ShowConfigurationSetConflicts(Execution::Context& context) - { - UNREFERENCED_PARAMETER(context); - } - - void ConfirmConfigurationProcessing::operator()(Execution::Context& context) const - { - context.Reporter.Warn() << Resource::String::ConfigurationWarning << std::endl; - - if (!context.Args.Contains(Args::Type::ConfigurationAcceptWarning)) - { - context << RequireInteractivity(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED); - if (context.IsTerminated()) - { - return; - } - - auto promptString = m_isApply ? Resource::String::ConfigurationWarningPromptApply : Resource::String::ConfigurationWarningPromptTest; - if (!context.Reporter.PromptForBoolResponse(promptString, Reporter::Level::Warning, false)) - { - AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED); - } - } - } - - void ApplyConfigurationSet(Execution::Context& context) - { - ApplyConfigurationSetResult result = nullptr; - ConfigurationContext& configContext = context.Get(); - - { - auto applyOperation = configContext.Processor().ApplySetAsync(configContext.Set(), ApplyConfigurationSetFlags::None); - anon::ApplyConfigurationSetProgressOutput progress{ context, applyOperation }; - - result = applyOperation.get(); - progress.HandleUnreportedProgress(result); - } - - if (FAILED(result.ResultCode())) - { - context.Reporter.Error() << Resource::String::ConfigurationFailedToApply << std::endl; - - // TODO: Summarize failed configuration units, especially if we put more output for each one during execution - - AICLI_TERMINATE_CONTEXT(result.ResultCode()); - } - else - { - context.Reporter.Info() << Resource::String::ConfigurationSuccessfullyApplied << std::endl; - } - } - - void TestConfigurationSet(Execution::Context& context) - { - TestConfigurationSetResult result = nullptr; - ConfigurationContext& configContext = context.Get(); - - { - auto testOperation = configContext.Processor().TestSetAsync(configContext.Set()); - anon::TestConfigurationSetProgressOutput progress{ context, testOperation }; - - result = testOperation.get(); - progress.HandleUnreportedProgress(result); - } - - switch (result.TestResult()) - { - case ConfigurationTestResult::Failed: - context.Reporter.Error() << Resource::String::ConfigurationFailedToTest << std::endl; - AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_FAILED); - break; - case ConfigurationTestResult::Negative: - context.Reporter.Warn() << Resource::String::ConfigurationNotInDesiredState << std::endl; - context.SetTerminationHR(S_FALSE); - break; - case ConfigurationTestResult::NotRun: - context.Reporter.Warn() << Resource::String::ConfigurationNoTestRun << std::endl; - AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_NOT_RUN); - break; - case ConfigurationTestResult::Positive: - context.Reporter.Info() << Resource::String::ConfigurationInDesiredState << std::endl; - break; - default: // ConfigurationTestResult::Unknown - context.Reporter.Error() << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(result.TestResult())) << std::endl; - AICLI_TERMINATE_CONTEXT(E_FAIL); - break; - } - } - - void ValidateConfigurationSetSemantics(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - - if (configContext.Set().Units().Size() == 0) - { - context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl; - // This isn't an error termination, but there is no reason to proceed. - AICLI_TERMINATE_CONTEXT(S_FALSE); - } - - ApplyConfigurationSetResult result = configContext.Processor().ApplySet(configContext.Set(), ApplyConfigurationSetFlags::PerformConsistencyCheckOnly); - - if (FAILED(result.ResultCode())) - { - for (const auto& unitResult : result.UnitResults()) - { - IConfigurationUnitResultInformation resultInformation = unitResult.ResultInformation(); - winrt::hresult resultCode = resultInformation.ResultCode(); - - if (FAILED(resultCode)) - { - ConfigurationUnit unit = unitResult.Unit(); - - anon::OutputConfigurationUnitHeader(context, unit, unit.Type()); - - switch (resultCode) - { - case WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER: - context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitHasDuplicateIdentifier(Utility::LocIndString{ Utility::ConvertToUTF8(unit.Identifier()) }) << std::endl; - break; - case WINGET_CONFIG_ERROR_MISSING_DEPENDENCY: - context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitHasMissingDependency(Utility::LocIndString{ Utility::ConvertToUTF8(resultInformation.Details()) }) << std::endl; - break; - case WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED: - context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitIsPartOfDependencyCycle << std::endl; - break; - default: - context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitFailed(static_cast(resultCode)) << std::endl; - break; - } - } - } - - AICLI_TERMINATE_CONTEXT(result.ResultCode()); - } - } - - void ValidateConfigurationSetUnitProcessors(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - - // TODO: We could optimize this by creating a set with unique resource units - - // First get the local details - auto gettingDetailString = Resource::String::ConfigurationGettingDetails(); - auto progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(gettingDetailString); - - auto getLocalDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Local); - auto unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getLocalDetailsOperation); - - HRESULT getLocalHR = S_OK; - GetConfigurationSetDetailsResult getLocalResult = nullptr; - - try - { - getLocalResult = getLocalDetailsOperation.get(); - } - catch (...) - { - getLocalHR = LOG_CAUGHT_EXCEPTION(); - } - - unification.Reset(); - - if (context.IsTerminated()) - { - // The context should only be terminated on us due to cancellation - context.Reporter.Error() << Resource::String::Cancelled << std::endl; - return; - } - - if (FAILED(getLocalHR)) - { - // Failing to get details might not be fatal, warn about it but proceed - context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl; - } - - // Next get the details from the catalog - progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(gettingDetailString); - - auto getCatalogDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Catalog); - unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getCatalogDetailsOperation); - - HRESULT getCatalogHR = S_OK; - GetConfigurationSetDetailsResult getCatalogResult = nullptr; - - try - { - getCatalogResult = getCatalogDetailsOperation.get(); - } - catch (...) - { - getCatalogHR = LOG_CAUGHT_EXCEPTION(); - } - - unification.Reset(); - - if (context.IsTerminated()) - { - // The context should only be terminated on us due to cancellation - context.Reporter.Error() << Resource::String::Cancelled << std::endl; - return; - } - - if (FAILED(getCatalogHR)) - { - // Failing to get the catalog details means that we can't really get give much of a meaningful response. - context.Reporter.Error() << Resource::String::ConfigurationFailedToGetDetails << std::endl; - AICLI_TERMINATE_CONTEXT(getCatalogHR); - } - - auto units = configContext.Set().Units(); - auto localUnitResults = getLocalResult ? getLocalResult.UnitResults() : nullptr; - if (localUnitResults && units.Size() != localUnitResults.Size()) - { - AICLI_LOG(Config, Error, << "The details result size did not match the set size: Set[" << units.Size() << "], Local[" << localUnitResults.Size() << "]"); - THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED); - } - - auto catalogUnitResults = getCatalogResult.UnitResults(); - if (units.Size() != catalogUnitResults.Size()) - { - AICLI_LOG(Config, Error, << "The details result sizes did not match the set size: Set[" << units.Size() << "], Catalog[" << catalogUnitResults.Size() << "]"); - THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED); - } - - bool foundIssue = false; - - // Now that we have the entire set of local and catalog details, process each unit - for (uint32_t i = 0; i < units.Size(); ++i) - { - const ConfigurationUnit& unit = units.GetAt(i); - GetConfigurationUnitDetailsResult localUnitResult = localUnitResults ? localUnitResults.GetAt(i) : nullptr; - GetConfigurationUnitDetailsResult catalogUnitResult = catalogUnitResults.GetAt(i); - IConfigurationUnitProcessorDetails catalogDetails = catalogUnitResult.Details(); - - bool needsHeader = true; - auto outputHeaderIfNeeded = [&]() - { - if (needsHeader) - { - anon::OutputConfigurationUnitHeader(context, unit, unit.Type()); - - needsHeader = false; - foundIssue = true; - } - }; - - if (anon::GetValueSetString(unit.Metadata(), anon::s_Directive_Module).empty()) - { - outputHeaderIfNeeded(); - context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitModuleNotProvidedWarning << std::endl; - } - - if (catalogDetails) - { - // Warn if unit is not public - if (!catalogDetails.IsPublic()) - { - outputHeaderIfNeeded(); - context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNotPublicWarning << std::endl; - } - - // Since it is available, no more checks are needed - continue; - } - // Everything below here is due to not finding in the catalog search - - if (FAILED(catalogUnitResult.ResultInformation().ResultCode())) - { - outputHeaderIfNeeded(); - anon::OutputUnitRunFailure(context, unit, catalogUnitResult.ResultInformation()); - continue; - } - - // If not already prerelease, try with prerelease and warn if found - std::optional allowPrereleaseDirective = anon::GetValueSetBool(unit.Metadata(), anon::s_Directive_AllowPrerelease); - if (!allowPrereleaseDirective || !allowPrereleaseDirective.value()) - { - // Check if the configuration unit is prerelease but the author forgot it - ConfigurationUnit clone = unit.Copy(); - clone.Metadata().Insert(anon::s_Directive_AllowPrerelease, PropertyValue::CreateBoolean(true)); - - progressScope = context.Reporter.BeginAsyncProgress(true); - progressScope->Callback().SetProgressMessage(gettingDetailString); - - auto getUnitDetailsOperation = configContext.Processor().GetUnitDetailsAsync(clone, ConfigurationUnitDetailFlags::Catalog); - auto unitUnification = anon::CreateProgressCancellationUnification(std::move(progressScope), getUnitDetailsOperation); - - IConfigurationUnitProcessorDetails prereleaseDetails; - - try - { - prereleaseDetails = getUnitDetailsOperation.get().Details(); - } - CATCH_LOG(); - - unification.Reset(); - - if (prereleaseDetails) - { - outputHeaderIfNeeded(); - context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNeedsPrereleaseWarning << std::endl; - continue; - } - } - - // If module is local, warn that we couldn't find it in the catalog - if (localUnitResult && localUnitResult.Details()) - { - outputHeaderIfNeeded(); - context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNotInCatalogWarning << std::endl; - continue; - } - - // Finally, error that we couldn't find it at all - outputHeaderIfNeeded(); - context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitNotFound << std::endl; - } - - if (foundIssue) - { - // Indicate that it was not a total success - AICLI_TERMINATE_CONTEXT(S_FALSE); - } - } - - void ValidateConfigurationSetUnitContents(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - auto units = configContext.Set().Units(); - auto validationOrder = anon::GetConfigurationSetUnitValidationOrder(units.GetView()); - - Configuration::WingetDscModuleUnitValidator wingetUnitValidator; - - bool foundIssues = false; - for (const auto index : validationOrder) - { - const ConfigurationUnit& unit = units.GetAt(index); - auto moduleName = Utility::ConvertToUTF8(unit.Details().ModuleName()); - if (Utility::CaseInsensitiveEquals(wingetUnitValidator.ModuleName(), moduleName)) - { - bool result = wingetUnitValidator.ValidateConfigurationSetUnit(context, unit); - if (!result) - { - foundIssues = true; - } - } - } - - if (foundIssues) - { - // Indicate that it was not a total success - AICLI_TERMINATE_CONTEXT(S_FALSE); - } - } - - void ValidateAllGoodMessage(Execution::Context& context) - { - context.Reporter.Info() << Resource::String::ConfigurationValidationFoundNoIssues << std::endl; - } - - void SearchSourceForPackageExport(Execution::Context& context) - { - if (!context.Args.Contains(Args::Type::ConfigurationExportAll) && !context.Args.Contains(Args::Type::ConfigurationExportPackageId)) - { - // No package export needed. - return; - } - - context << - OpenSource() << - OpenCompositeSource(Repository::PredefinedSource::Installed); - - if (context.Args.Contains(Args::Type::ConfigurationExportAll)) - { - context << - SearchSourceForMany << - HandleSearchResultFailures << - EnsureMatchesFromSearchResult(OperationType::Export) << - SelectVersionsToExport; - } - else if (context.Args.Contains(Args::Type::ConfigurationExportPackageId)) - { - context.Args.AddArg(Args::Type::Id, context.Args.GetArg(Args::Type::ConfigurationExportPackageId)); - context << - SearchSourceForSingle << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(OperationType::Export) << - SelectVersionsToExport; - } - } - - void PopulateConfigurationSetForExport(Execution::Context& context) - { - bool isExportAll = context.Args.Contains(Execution::Args::Type::ConfigurationExportAll); - - if (isExportAll) - { - context << - anon::ExportPredefinedResources << - SearchSourceForPackageExport << - anon::ProcessPackagesForConfigurationExportAll; - } - else - { - context << - SearchSourceForPackageExport << - anon::ProcessPackagesForConfigurationExportSingle; - } - } - - void WriteConfigFile(Execution::Context& context) - { - try - { - std::string argPath{ context.Args.GetArg(Args::Type::OutputFile) }; - - context.Reporter.Info() << Resource::String::ConfigurationExportAddingToFile(Utility::LocIndView{ argPath }) << std::endl; - - auto tempFilePath = Runtime::GetNewTempFilePath(); - - { - std::ofstream tempStream{ tempFilePath }; - tempStream << "# Created using winget configure export " << Runtime::GetClientVersion().get() << std::endl; - } - - auto openAction = Streams::FileRandomAccessStream::OpenAsync( - tempFilePath.wstring(), - FileAccessMode::ReadWrite); - - auto stream = openAction.get(); - stream.Seek(stream.Size()); - - ConfigurationContext& configContext = context.Get(); - configContext.Set().Serialize(openAction.get()); - - auto absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPath }); - std::filesystem::rename(tempFilePath, absolutePath); - - context.Reporter.Info() << Resource::String::ConfigurationExportSuccessful << std::endl; - } - catch (...) - { - context.Reporter.Error() << Resource::String::ConfigurationExportFailed << std::endl; - throw; - } - } - - void GetConfigurationSetHistory(Execution::Context& context) - { - auto progressScope = context.Reporter.BeginAsyncProgress(true); - - ConfigurationContext& configContext = context.Get(); - configContext.History(configContext.Processor().GetConfigurationHistory()); - } - - void ShowConfigurationSetHistory(Execution::Context& context) - { - const auto& history = context.Get().History(); - - if (history.empty()) - { - context.Reporter.Info() << Resource::String::ConfigurationHistoryEmpty << std::endl; - } - else - { - TableOutput<4> historyTable{ context.Reporter, { Resource::String::ConfigureListIdentifier, Resource::String::ConfigureListName, Resource::String::ConfigureListState, Resource::String::ConfigureListOrigin } }; - - for (const auto& set : history) - { - winrt::hstring origin = set.Path(); - if (origin.empty()) - { - origin = set.Origin(); - } - - historyTable.OutputLine({ Utility::ConvertGuidToString(set.InstanceIdentifier()), Utility::ConvertToUTF8(set.Name()), anon::ToLocString(set.State()), Utility::ConvertToUTF8(origin)}); - } - - historyTable.Complete(); - } - } - - void SelectSetFromHistory(Execution::Context& context) - { - ConfigurationContext& configContext = context.Get(); - ConfigurationSet selectedSet{ nullptr }; - - std::string foldedInput = Utility::FoldCase(context.Args.GetArg(Execution::Args::Type::ConfigurationHistoryItem)); - - for (const ConfigurationSet& historySet : configContext.History()) - { - if (anon::HistorySetMatchesInput(historySet, foldedInput)) - { - if (selectedSet) - { - selectedSet = nullptr; - break; - } - else - { - selectedSet = historySet; - } - } - } - - if (!selectedSet) - { - context.Reporter.Warn() << Resource::String::ConfigurationHistoryItemNotFound << std::endl; - context << ShowConfigurationSetHistory; - AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND); - } - - configContext.Set(std::move(selectedSet)); - } - - void RemoveConfigurationSetHistory(Execution::Context& context) - { - auto progressScope = context.Reporter.BeginAsyncProgress(true); - context.Get().Set().Remove(); - } - - void SerializeConfigurationSetHistory(Execution::Context& context) - { - auto progressScope = context.Reporter.BeginAsyncProgress(true); - std::filesystem::path absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }); - auto openAction = Streams::FileRandomAccessStream::OpenAsync(absolutePath.wstring(), FileAccessMode::ReadWrite, StorageOpenOptions::None, Streams::FileOpenDisposition::CreateAlways); - auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); }); - auto outputStream = openAction.get(); - - context.Get().Set().Serialize(outputStream); - } - - void ShowSingleConfigurationSetHistory(Execution::Context& context) - { - const auto& set = context.Get().Set(); - - // Output a table with name/value pairs for some of the set's properties. Example: - // - // Field Value - // ---------------------------------------------------- - // Identifier {7D5CF50E-F3C6-4333-BFE6-5A806F9EBA4E} - // Name Test Name - // Origin Test Origin - // Path Test Path - // State Completed - // First Applied 2024-07-16 21:15:13.000 - // Apply Begun 2024-07-16 21:15:13.000 - // Apply Ended 2024-07-16 21:15:13.000 - Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue }); - - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListIdentifier }, Utility::ConvertGuidToString(set.InstanceIdentifier()) }); - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListName }, Utility::ConvertToUTF8(set.Name()) }); - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListOrigin }, Utility::ConvertToUTF8(set.Origin()) }); - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListPath }, Utility::ConvertToUTF8(set.Path()) }); - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListState }, anon::ToLocString(set.State()) }); - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListFirstApplied }, Utility::TimePointToString(winrt::clock::to_sys(set.FirstApply())) }); - - auto applyBegun = set.ApplyBegun(); - if (applyBegun != winrt::clock::time_point{}) - { - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyBegun }, Utility::TimePointToString(winrt::clock::to_sys(applyBegun)) }); - } - - auto applyEnded = set.ApplyEnded(); - if (applyEnded != winrt::clock::time_point{}) - { - table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyEnded }, Utility::TimePointToString(winrt::clock::to_sys(applyEnded)) }); - } - - table.Complete(); - - context.Reporter.Info() << std::endl; - - // Output a table with unit state information. Groups are represented by indentation beneath their parent unit. Example: - // - // Unit State Result Details - // ------------------------------------------------------------ - // Module/Resource [Name] Completed 0x00000000 - // Module2/Resource [Group] Completed 0x00000000 - // |-Module3/Resource [Child1] Completed 0x00000000 - // |---Module4/Resource2 Completed 0x80004005 I failed :( - // |-Module3/Resource [Child2] Completed 0x00000000 - Execution::TableOutput<4> unitTable(context.Reporter, { Resource::String::ConfigureListUnit, Resource::String::ConfigureListState, Resource::String::ConfigureListResult, Resource::String::ConfigureListResultDescription }); - - struct UnitSiblings - { - size_t Depth = 0; - size_t Current = 0; - std::vector Siblings; - }; - - std::vector stack; - - { - UnitSiblings initial; - auto units = set.Units(); - initial.Siblings.resize(units.Size()); - units.GetMany(0, initial.Siblings); - stack.emplace_back(std::move(initial)); - } - - // Each item on the stack is a list of sibling units. - // Each iteration, we process the Current sibling from the group on top of the stack. - // If it is a group, we add its children as a new stack item to be processed next. - while (!stack.empty()) - { - UnitSiblings& currentSiblings = stack.back(); - - if (currentSiblings.Current >= currentSiblings.Siblings.size()) - { - stack.pop_back(); - continue; - } - - ConfigurationUnit& currentUnit = currentSiblings.Siblings[currentSiblings.Current++]; - - std::ostringstream unitStream; - - if (currentSiblings.Depth) - { - unitStream << '|' << std::string((currentSiblings.Depth * 2) - 1, '-'); - } - - unitStream << Utility::ConvertToUTF8(currentUnit.Type()); - - auto identifier = currentUnit.Identifier(); - if (!identifier.empty()) - { - unitStream << " [" << Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(identifier)) << ']'; - } - - auto resultInformation = currentUnit.ResultInformation(); - std::ostringstream resultStream; - std::string resultDetails; - - if (resultInformation) - { - resultStream << "0x" << Logging::SetHRFormat << resultInformation.ResultCode(); - - auto description = resultInformation.Description(); - if (description.empty()) - { - description = resultInformation.Details(); - } - - resultDetails = Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(description)); - } - - unitTable.OutputLine({ std::move(unitStream).str(), anon::ToLocString(currentUnit.State()), std::move(resultStream).str(), std::move(resultDetails) }); - - if (currentUnit.IsGroup()) - { - UnitSiblings unitChildren; - unitChildren.Depth = currentSiblings.Depth + 1; - auto units = currentUnit.Units(); - unitChildren.Siblings.resize(units.Size()); - units.GetMany(0, unitChildren.Siblings); - stack.emplace_back(std::move(unitChildren)); - } - } - - unitTable.Complete(); - } - - void CompleteConfigurationHistoryItem(Execution::Context& context) - { - const std::string& word = context.Get().Word(); - auto stream = context.Reporter.Completion(); - - for (const auto& historyItem : ConfigurationProcessor{ IConfigurationSetProcessorFactory{ nullptr } }.GetConfigurationHistory()) - { - std::ostringstream identifierStream; - identifierStream << historyItem.InstanceIdentifier(); - std::string identifier = identifierStream.str(); - - if (word.empty() || Utility::CaseInsensitiveContainsSubstring(identifier, word)) - { - stream << '"' << identifier << '"' << std::endl; - } - - std::string name = Utility::ConvertToUTF8(historyItem.Name()); - - if (word.empty() || Utility::CaseInsensitiveStartsWith(name, word)) - { - stream << '"' << name << '"' << std::endl; - } - } - } - - void MonitorConfigurationStatus(Execution::Context& context) - { - auto& configurationContext = context.Get(); - - std::mutex activeSetMutex; - ConfigurationSet activeSet{ nullptr }; - decltype(activeSet.ConfigurationSetChange(winrt::auto_revoke, nullptr)) activeSetRevoker; - - auto setChangeHandler = [&](const ConfigurationSet& set, const ConfigurationSetChangeData& changeData) - { - if (changeData.Change() == ConfigurationSetChangeEventType::SetStateChanged) - { - context.Reporter.Info() << "(SetStateChanged) " << set.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.SetState()) << std::endl; - } - else if (changeData.Change() == ConfigurationSetChangeEventType::UnitStateChanged) - { - context.Reporter.Info() << "(UnitStateChanged) " << changeData.Unit().InstanceIdentifier() << " :: " << anon::ToLocString(changeData.UnitState()) << std::endl; - - auto resultInformation = changeData.ResultInformation(); - if (resultInformation) - { - context.Reporter.Info() << " [" << anon::ToString(resultInformation.ResultSource()) << "] :: 0x" << Logging::SetHRFormat << resultInformation.ResultCode() << std::endl; - } - } - }; - - auto setActiveSet = [&](const ConfigurationSet& set, bool force) - { - std::lock_guard lock{ activeSetMutex }; - - if (force || !activeSet) - { - activeSet = set; - activeSetRevoker = activeSet.ConfigurationSetChange(winrt::auto_revoke, setChangeHandler); - } - }; - - auto processorRevoker = configurationContext.Processor().ConfigurationChange(winrt::auto_revoke, [&](const ConfigurationSet& set, const ConfigurationChangeData& changeData) - { - context.Reporter.Info() << '[' << anon::ToString(changeData.Change()) << "] " << changeData.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.State()) << std::endl; - - if (changeData.Change() == ConfigurationChangeEventType::SetStateChanged && changeData.State() == ConfigurationSetState::InProgress) - { - setActiveSet(set, true); - } - }); - - for (ConfigurationSet& historySet : configurationContext.History()) - { - if (historySet.State() == ConfigurationSetState::InProgress) - { - setActiveSet(historySet, false); - } - } - - for (;;) - { - std::this_thread::sleep_for(250ms); - if (context.IsTerminated()) - { - return; - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationFlow.h" +#include "ImportExportFlow.h" +#include "PromptFlow.h" +#include "TableOutput.h" +#include "MSStoreInstallerHandler.h" +#include "Public/ConfigurationSetProcessorFactoryRemoting.h" +#include "ConfigurationCommon.h" +#include "ConfigurationWingetDscModuleUnitValidation.h" +#include "Commands/DscCommandBase.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller::CLI::Execution; +using namespace winrt::Microsoft::Management::Configuration; +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Storage; +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::SelfManagement; + +namespace AppInstaller::CLI::Workflow +{ +#ifndef AICLI_DISABLE_TEST_HOOKS + IConfigurationSetProcessorFactory s_override_IConfigurationSetProcessorFactory; + + void SetTestConfigurationSetProcessorFactory(IConfigurationSetProcessorFactory factory) + { + s_override_IConfigurationSetProcessorFactory = std::move(factory); + } +#endif + + namespace anon + { + static const AppInstaller::Utility::Version s_MinimumSchemaVersionModuleNameRequiredInType = { "0.3" }; + + constexpr std::wstring_view s_Directive_Description = L"description"; + constexpr std::wstring_view s_Directive_Module = L"module"; + constexpr std::wstring_view s_Directive_AllowPrerelease = L"allowPrerelease"; + + constexpr std::wstring_view s_Unit_WinGetPackage = L"WinGetPackage"; + constexpr std::wstring_view s_Unit_WinGetSource = L"WinGetSource"; + + constexpr std::wstring_view s_UnitType_WinGetPackage_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Package"; + constexpr std::wstring_view s_UnitType_WinGetSource_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/Source"; + constexpr std::wstring_view s_UnitType_WinGetUserSettingsFile_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/UserSettingsFile"; + constexpr std::wstring_view s_UnitType_WinGetAdminSettings_DSCv3 = WINGET_DSCV3_MODULE_NAME_WIDE L"/AdminSettings"; + + constexpr std::wstring_view s_Module_WinGetClient = L"Microsoft.WinGet.DSC"; + + constexpr std::wstring_view s_Setting_WinGetPackage_Id = L"id"; + constexpr std::wstring_view s_Setting_WinGetPackage_Source = L"source"; + constexpr std::wstring_view s_Setting_WinGetPackage_Version = L"version"; + + constexpr std::wstring_view s_Setting_WinGetSource_Name = L"name"; + constexpr std::wstring_view s_Setting_WinGetSource_Arg = L"argument"; + constexpr std::wstring_view s_Setting_WinGetSource_Type = L"type"; + constexpr std::wstring_view s_Setting_WinGetSource_TrustLevel = L"trustLevel"; + constexpr std::wstring_view s_Setting_WinGetSource_Explicit = L"explicit"; + constexpr std::wstring_view s_Setting_WinGetSource_Priority = L"priority"; + + constexpr std::wstring_view s_Predefined_PowerShell_PackageId = L"Microsoft.PowerShell"; + constexpr std::wstring_view s_Predefined_PowerShell_PackageSource = L"winget"; + + constexpr std::string_view s_DscPackage_StoreId_Stable = "9NVTPZWRC6KQ"; + constexpr std::string_view s_DscPackage_StoreId_Preview = "9PCX3HX4HZ0Z"; + + struct PredefinedResourceInfo + { + std::wstring_view UnitType; + bool ElevationRequired = false; + + PredefinedResourceInfo(std::wstring_view unitType) : UnitType(unitType) {} + PredefinedResourceInfo(std::wstring_view unitType, bool elevationRequired) : UnitType(unitType), ElevationRequired(elevationRequired) {} + }; + + struct PredefinedResource + { + // RequiredModule could be empty, meaning no required modules needed. + std::wstring_view RequiredModule; + + std::vector ResourceInfos; + }; + + std::vector PredefinedResourcesForExport() + { + return { + { {}, { { s_UnitType_WinGetUserSettingsFile_DSCv3 }, { s_UnitType_WinGetAdminSettings_DSCv3, true } } }, + { L"Microsoft.Windows.Settings", { { L"Microsoft.Windows.Settings/WindowsSettings", true } } }, + }; + } + + std::vector PackageSettingsExclusionList() + { + return { + L"Microsoft.WinGet/", + L"Microsoft.WinGet.Dev/", + }; + }; + + // Returns unit type prefixes that identify resources known to be shipped with DSC. + // These are used both to exclude the resources themselves and to discover the DSC + // installation location, so that any co-located resources are also excluded. + std::vector DscShippedResourcePrefixes() + { + return { + L"Microsoft.DSC/", + L"Microsoft.DSC.Debug/", + L"Microsoft.DSC.Transitional/", + L"Microsoft/OSInfo", + }; + } + + // Returns unit type prefixes for DSC-shipped resources that are still allowed to + // appear in the exported configuration. All other DSC-shipped resources are excluded by default. + std::vector DscShippedResourcesAllowList() + { + // Currently empty: all DSC-shipped resources are excluded from export by default. + return {}; + } + + Logging::Level ConvertLevel(DiagnosticLevel level) + { + switch (level) + { + case DiagnosticLevel::Verbose: return Logging::Level::Verbose; + case DiagnosticLevel::Informational: return Logging::Level::Info; + case DiagnosticLevel::Warning: return Logging::Level::Warning; + case DiagnosticLevel::Error: return Logging::Level::Error; + case DiagnosticLevel::Critical: return Logging::Level::Crit; + } + + return Logging::Level::Info; + } + + // Audit information gathered about a custom processor path. + struct ProcessorPathInfo + { + bool IsAlias = false; + std::string HashString; + std::string SigningSubject; + }; + + // Collects audit information for the given processor path. + // Throws on access failure so the caller is prevented from using an unverifiable path. + ProcessorPathInfo CollectProcessorPathInfo(const std::filesystem::path& processorPath) + { + ProcessorPathInfo result; + const std::wstring& pathStr = processorPath.wstring(); + + // Attempt to open the file for reading without FILE_FLAG_OPEN_REPARSE_POINT. + // App execution aliases (IO_REPARSE_TAG_APPEXECLINK) cannot be opened for reading + // this way and will fail with ERROR_CANT_ACCESS_FILE. + wil::unique_hfile fileHandle{ CreateFileW( + pathStr.c_str(), + GENERIC_READ, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + nullptr) }; + + if (!fileHandle) + { + DWORD lastError = GetLastError(); + THROW_WIN32_IF(lastError, lastError != ERROR_CANT_ACCESS_FILE); + + // Re-open with FILE_FLAG_OPEN_REPARSE_POINT to inspect the reparse data. + wil::unique_hfile reparseHandle{ CreateFileW( + pathStr.c_str(), + 0, + FILE_SHARE_READ, + nullptr, + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + nullptr) }; + THROW_LAST_ERROR_IF(!reparseHandle); + + // Retrieve the reparse point data. + std::vector reparseBuffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE); + DWORD bytesReturned = 0; + THROW_LAST_ERROR_IF(!DeviceIoControl( + reparseHandle.get(), + FSCTL_GET_REPARSE_POINT, + nullptr, + 0, + reparseBuffer.data(), + static_cast(reparseBuffer.size()), + &bytesReturned, + nullptr)); + + // Confirm it is specifically an app execution alias, not another reparse type. + THROW_HR_IF(E_INVALIDARG, bytesReturned < sizeof(DWORD)); + DWORD reparseTag = *reinterpret_cast(reparseBuffer.data()); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_REPARSE_TAG_MISMATCH), reparseTag != IO_REPARSE_TAG_APPEXECLINK); + + result.IsAlias = true; + result.HashString = Utility::SHA256::ConvertToString( + Utility::SHA256::ComputeHash(reparseBuffer.data(), bytesReturned)); + } + else + { + // Regular file: hash the file bytes using the shared SHA256 utility. + result.HashString = Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHashFromHandle(fileHandle.get())); + + // Attempt to extract signing info (handles both embedded and catalog signatures). + try + { + result.SigningSubject = Certificates::GetAuthenticodeSubject(processorPath); + } + catch (...) + { + AICLI_LOG(Config, Warning, << "Failed to retrieve signing info for processor path"); + } + } + + return result; + } + + DiagnosticLevel ConvertLevel(Logging::Level level) + { + switch (level) + { + case Logging::Level::Verbose: return DiagnosticLevel::Verbose; + case Logging::Level::Info: return DiagnosticLevel::Informational; + case Logging::Level::Warning: return DiagnosticLevel::Warning; + case Logging::Level::Error: return DiagnosticLevel::Error; + case Logging::Level::Crit: return DiagnosticLevel::Critical; + } + + return DiagnosticLevel::Informational; + } + + Resource::StringId ToResource(ConfigurationUnitIntent intent) + { + switch (intent) + { + case ConfigurationUnitIntent::Assert: return Resource::String::ConfigurationAssert; + case ConfigurationUnitIntent::Inform: return Resource::String::ConfigurationInform; + case ConfigurationUnitIntent::Apply: return Resource::String::ConfigurationApply; + default: return Resource::StringId::Empty(); + } + } + + void InstallDscPackage(Execution::Context& context, std::string_view productId, std::unique_ptr& progressScope) + { + progressScope.reset(); + + context.Reporter.Info() << Resource::String::ConfigurationInstallDscPackage << std::endl; + + auto installDscContextPtr = context.CreateSubContext(); + Execution::Context& installDscContext = *installDscContextPtr; + auto previousThreadGlobals = installDscContext.SetForCurrentThread(); + + Manifest::ManifestInstaller dscInstaller; + dscInstaller.ProductId = productId; + + installDscContext.Add(std::move(dscInstaller)); + installDscContext.Args.AddArg(Execution::Args::Type::InstallScope, Manifest::ScopeToString(Manifest::ScopeEnum::User)); + installDscContext.Args.AddArg(Execution::Args::Type::Silent); + installDscContext.Args.AddArg(Execution::Args::Type::Force); + + installDscContext << MSStoreInstall; + + if (installDscContext.IsTerminated()) + { + AICLI_LOG(Config, Error, << "Failed to install dsc v3 package: " << productId); + context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; + THROW_WIN32(ERROR_FILE_NOT_FOUND); + } + + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); + } + + IConfigurationSetProcessorFactory CreateConfigurationSetProcessorFactory(Execution::Context& context) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + // Test could override the entire workflow task, but that may require keeping more in sync than simply setting the factory. + if (s_override_IConfigurationSetProcessorFactory) + { + return s_override_IConfigurationSetProcessorFactory; + } +#endif + + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); + + // The configuration set must have already been opened to create the proper factory. + THROW_WIN32_IF(ERROR_INVALID_STATE, !context.Contains(Data::ConfigurationContext)); + const auto& configurationContext = context.Get(); + THROW_WIN32_IF(ERROR_INVALID_STATE, !configurationContext.Set()); + + IConfigurationSetProcessorFactory factory; + ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configurationContext.Set()); + + THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, processorEngine == ConfigurationRemoting::ProcessorEngine::Unknown); + + // Since downgrading is not currently supported, only use dynamic if running limited. + if (Runtime::IsRunningWithLimitedToken()) + { + factory = ConfigurationRemoting::CreateDynamicRuntimeFactory(processorEngine); + } + else + { + factory = ConfigurationRemoting::CreateOutOfProcessFactory(processorEngine); + } + + if (processorEngine == ConfigurationRemoting::ProcessorEngine::PowerShell) + { + Configuration::SetModulePath(context, factory); + } + else if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3) + { + auto factoryMap = factory.as>(); + + if (context.Args.Contains(Args::Type::ConfigurationProcessorPath)) + { + progressScope.reset(); + + const auto& processorPathArg = context.Args.GetArg(Args::Type::ConfigurationProcessorPath); + std::filesystem::path processorPath{ Utility::ConvertToUTF16(processorPathArg) }; + + // Collect audit information; throws if the path cannot be opened or hashed. + auto pathInfo = anon::CollectProcessorPathInfo(processorPath); + + // Output audit information to the user as a warning since this is a non-default path. + constexpr std::string_view s_indent = " "sv; + context.Reporter.Info() << Resource::String::ConfigurationProcessorPathAudit << std::endl; + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditPath(Utility::LocIndString{ processorPathArg }) << std::endl; + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditHash(Utility::LocIndString{ pathInfo.HashString }) << std::endl; + if (pathInfo.IsAlias) + { + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditIsAlias << std::endl; + } + else if (!pathInfo.SigningSubject.empty()) + { + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditSignature(Utility::LocIndString{ pathInfo.SigningSubject }) << std::endl; + } + else + { + context.Reporter.Info() << s_indent << Resource::String::ConfigurationProcessorPathAuditUnsigned << std::endl; + } + + AICLI_LOG(Config, Info, << "Processor path audit - Path: " << processorPathArg << ", Hash: " << pathInfo.HashString << ", IsAlias: " << pathInfo.IsAlias); + + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePath), processorPath.wstring()); + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePathHash), Utility::ConvertToUTF16(pathInfo.HashString)); + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DscExecutablePathIsAlias), pathInfo.IsAlias ? L"true" : L"false"); + + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationInitializing()); + } + else + { + for (;;) + { + // Get the next transition for the state machine + winrt::hstring nextTransition = factoryMap.Lookup(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::FindDscStateMachine)); + AICLI_LOG(Config, Verbose, << "FindDscStateMachine returned " << Utility::ConvertToUTF8(nextTransition)); + + if (nextTransition == L"Found") + { + break; + } + else if (nextTransition == L"InstallStable") + { + AICLI_LOG(Config, Info, << "Installing stable DSC package from store..."); + InstallDscPackage(context, s_DscPackage_StoreId_Stable, progressScope); + } + else if (nextTransition == L"InstallPreview") + { + AICLI_LOG(Config, Info, << "Installing preview DSC package from store..."); + InstallDscPackage(context, s_DscPackage_StoreId_Preview, progressScope); + } + else if (nextTransition == L"NotFound") + { + AICLI_LOG(Config, Error, << "Failed to find appropriate dsc v3 package, it must be provided by the user."); + context.Reporter.Error() << Resource::String::ConfigurationInstallDscPackageFailed << std::endl; + THROW_WIN32(ERROR_FILE_NOT_FOUND); + } + else + { + AICLI_LOG(Config, Error, << "FindDscStateMachine returned unknown value `" << Utility::ConvertToUTF8(nextTransition) << "`"); + THROW_HR(E_UNEXPECTED); + } + } + } + + if (Logging::Log().IsEnabled(Logging::Channel::Config, Logging::Level::Verbose)) + { + factoryMap.Insert(ConfigurationRemoting::ToHString(ConfigurationRemoting::PropertyName::DiagnosticTraceEnabled), L"True"); + } + } + + return factory; + } + + void ConfigureProcessorForUse(Execution::Context& context, ConfigurationProcessor&& processor) + { + // Set the processor to the current level of the logging. + processor.MinimumLevel(anon::ConvertLevel(Logging::Log().GetLevel())); + processor.Caller(L"winget"); + // Use same activity as the overall winget command + processor.ActivityIdentifier(*Logging::Telemetry().GetActivityId()); + // Apply winget telemetry setting to configuration + processor.GenerateTelemetryEvents(!Settings::User().Get()); + + // Route the configuration diagnostics into the context's diagnostics logging + processor.Diagnostics([&context](const winrt::Windows::Foundation::IInspectable&, const IDiagnosticInformation& diagnostics) + { + context.GetThreadGlobals().GetDiagnosticLogger().Write(Logging::Channel::Config, anon::ConvertLevel(diagnostics.Level()), Utility::ConvertToUTF8(diagnostics.Message())); + }); + + if (context.Contains(Data::ConfigurationContext)) + { + context.Get().Processor(std::move(processor)); + } + else + { + ConfigurationContext configurationContext; + configurationContext.Processor(std::move(processor)); + + context.Add(std::move(configurationContext)); + } + } + + winrt::hstring GetValueSetString(const ValueSet& valueSet, std::wstring_view value) + { + if (valueSet.HasKey(value)) + { + auto object = valueSet.Lookup(value); + IPropertyValue property = object.try_as(); + if (property && property.Type() == PropertyType::String) + { + return property.GetString(); + } + } + + return {}; + } + + std::optional GetValueSetBool(const ValueSet& valueSet, std::wstring_view value) + { + if (valueSet.HasKey(value)) + { + auto object = valueSet.Lookup(value); + IPropertyValue property = object.try_as(); + if (property && property.Type() == PropertyType::Boolean) + { + return property.GetBoolean(); + } + } + + return {}; + } + + // Contains the output functions and tracks whether any fields needed to be truncated. + struct OutputHelper + { + OutputHelper(Execution::Context& context) : m_context(context) {} + + size_t ValuesTruncated = 0; + + // Converts a string from the configuration API surface for output. + // All strings coming from the API are external data and not localizable by us. + Utility::LocIndString ConvertForOutput(const std::string& input, size_t maxLines) + { + bool truncated = false; + auto lines = Utility::SplitIntoLines(input); + + if (maxLines == 1 && lines.size() > 1) + { + // If the limit was one line, don't allow line breaks but do allow a second line of overflow + lines.resize(1); + maxLines = 2; + truncated = true; + } + + if (Utility::LimitOutputLines(lines, GetConsoleWidth().value_or(120), maxLines)) + { + truncated = true; + } + + if (truncated) + { + ++ValuesTruncated; + } + + return Utility::LocIndString{ Utility::Join("\n", lines) }; + } + + Utility::LocIndString ConvertForOutput(const winrt::hstring& input, size_t maxLines) + { + return ConvertForOutput(Utility::ConvertToUTF8(input), maxLines); + } + + Utility::LocIndString ConvertIdentifier(const winrt::hstring& input) + { + return ConvertForOutput(input, 1); + } + + Utility::LocIndString ConvertURI(const winrt::hstring& input) + { + return ConvertForOutput(input, 1); + } + + Utility::LocIndString ConvertValue(const winrt::hstring& input) + { + return ConvertForOutput(input, 5); + } + + Utility::LocIndString ConvertDetailsIdentifier(const winrt::hstring& input) + { + return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 1); + } + + Utility::LocIndString ConvertDetailsURI(const winrt::hstring& input) + { + return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 1); + } + + Utility::LocIndString ConvertDetailsValue(const winrt::hstring& input) + { + return ConvertForOutput(Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(input)), 5); + } + + void OutputValueWithTruncationWarningIfNeeded(const winrt::hstring& input) + { + size_t truncatedBefore = ValuesTruncated; + m_context.Reporter.Info() << ConvertValue(input) << '\n'; + + if (ValuesTruncated > truncatedBefore) + { + m_context.Reporter.Warn() << Resource::String::ConfigurationWarningValueTruncated << std::endl; + } + } + + void OutputPropertyValue(const IPropertyValue property) + { + switch (property.Type()) + { + case PropertyType::String: + m_context.Reporter.Info() << ' '; + OutputValueWithTruncationWarningIfNeeded(property.GetString()); + break; + case PropertyType::Boolean: + m_context.Reporter.Info() << ' ' << (property.GetBoolean() ? Utility::LocIndView("true") : Utility::LocIndView("false")) << '\n'; + break; + case PropertyType::Int64: + m_context.Reporter.Info() << ' ' << property.GetInt64() << '\n'; + break; + default: + m_context.Reporter.Info() << " [Debug:PropertyType="_liv << property.Type() << "]\n"_liv; + break; + } + } + + void OutputValueSetAsArray(const ValueSet& valueSetArray, size_t indent) + { + Utility::LocIndString indentString{ std::string(indent, ' ') }; + + std::vector> arrayValues; + for (const auto& arrayValue : valueSetArray) + { + if (arrayValue.Key() != L"treatAsArray") + { + arrayValues.emplace_back(std::make_pair(std::stoi(arrayValue.Key().c_str()), arrayValue.Value())); + } + } + + std::sort( + arrayValues.begin(), + arrayValues.end(), + [](const std::pair& a, const std::pair& b) + { + return a.first < b.first; + }); + + for (const auto& arrayValue : arrayValues) + { + auto arrayObject = arrayValue.second; + IPropertyValue arrayProperty = arrayObject.try_as(); + + m_context.Reporter.Info() << indentString << "-"; + if (arrayProperty) + { + OutputPropertyValue(arrayProperty); + } + else + { + ValueSet arraySubset = arrayObject.as(); + auto size = arraySubset.Size(); + if (size > 0) + { + // First one is special. + auto first = arraySubset.First().Current(); + m_context.Reporter.Info() << ' ' << ConvertIdentifier(first.Key()) << ':'; + + auto object = first.Value(); + IPropertyValue property = object.try_as(); + if (property) + { + OutputPropertyValue(property); + } + else + { + // If not an IPropertyValue, it must be a ValueSet + ValueSet subset = object.as(); + m_context.Reporter.Info() << '\n'; + OutputValueSet(subset, indent + 4); + } + + if (size > 1) + { + arraySubset.Remove(first.Key()); + OutputValueSet(arraySubset, indent + 2); + arraySubset.Insert(first.Key(), first.Value()); + } + } + } + } + } + + void OutputValueSet(const ValueSet& valueSet, size_t indent) + { + Utility::LocIndString indentString{ std::string(indent, ' ') }; + + for (const auto& value : valueSet) + { + m_context.Reporter.Info() << indentString << ConvertIdentifier(value.Key()) << ':'; + + auto object = value.Value(); + + IPropertyValue property = object.try_as(); + if (property) + { + OutputPropertyValue(property); + } + else + { + // If not an IPropertyValue, it must be a ValueSet + ValueSet subset = object.as(); + m_context.Reporter.Info() << '\n'; + if (subset.HasKey(L"treatAsArray")) + { + OutputValueSetAsArray(subset, indent + 2); + } + else + { + OutputValueSet(subset, indent + 2); + } + } + } + } + + void OutputConfigurationUnitHeader(const ConfigurationUnit& unit, const winrt::hstring& name) + { + m_context.Reporter.Info() << ConfigurationUnitEmphasis << ConvertIdentifier(name); + + if (unit.Environment().Context() == SecurityContext::Elevated) + { + // Shield + m_context.Reporter.Info() << "\xF0\x9F\x9B\xA1 "_liv; + } + + winrt::hstring identifier = unit.Identifier(); + if (!identifier.empty()) + { + m_context.Reporter.Info() << " ["_liv << ConvertIdentifier(identifier) << ']'; + } + + m_context.Reporter.Info() << '\n'; + } + + void OutputConfigurationUnitInformation(const ConfigurationUnit& unit) + { + IConfigurationUnitProcessorDetails details = unit.Details(); + ValueSet metadata = unit.Metadata(); + + if (details) + { + // -- Sample output when IConfigurationUnitProcessorDetails present -- + // UnitType [Identifier] + // UnitDocumentationUri + // Description + // "Module": ModuleName "by" Author / Publisher (IsLocal / ModuleSource) + // "Signed by": SigningCertificateChain (leaf subject CN) + // PublishedModuleUri / ModuleDocumentationUri + // ModuleDescription + OutputConfigurationUnitHeader(unit, details.UnitType()); + + auto unitDocumentationUri = details.UnitDocumentationUri(); + if (unitDocumentationUri) + { + m_context.Reporter.Info() << " "_liv << ConvertDetailsURI(unitDocumentationUri.DisplayUri()) << '\n'; + } + + winrt::hstring unitDescriptionFromDetails = details.UnitDescription(); + if (!unitDescriptionFromDetails.empty()) + { + m_context.Reporter.Info() << " "_liv << ConvertDetailsValue(unitDescriptionFromDetails) << '\n'; + } + + auto unitDescriptionFromDirectives = GetValueSetString(metadata, s_Directive_Description); + if (!unitDescriptionFromDirectives.empty()) + { + m_context.Reporter.Info() << " "_liv; + OutputValueWithTruncationWarningIfNeeded(unitDescriptionFromDirectives); + } + + auto author = ConvertDetailsIdentifier(details.Author()); + if (author.empty()) + { + author = ConvertDetailsIdentifier(details.Publisher()); + } + + auto moduleName = ConvertDetailsIdentifier(details.ModuleName()); + if (!moduleName.empty()) + { + if (details.IsLocal()) + { + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationModuleWithDetails(moduleName, author, Resource::String::ConfigurationLocal) << '\n'; + } + else + { + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationModuleWithDetails(moduleName, author, ConvertDetailsIdentifier(details.ModuleSource())) << '\n'; + } + } + + // TODO: Currently the signature information is only for the top files. Maybe each item should be tagged? + // TODO: Output signing information with additional details (like whether the certificate is trusted). Doing this with the validate command + // seems like a good time, as that will also need to do the check in order to inform the user on the validation. + // Just saying "Signed By: Foo" is going to lead to a false sense of trust if the signature is valid but not actually trusted. + + auto moduleUri = details.PublishedModuleUri(); + if (!moduleUri) + { + moduleUri = details.ModuleDocumentationUri(); + } + if (moduleUri) + { + m_context.Reporter.Info() << " "_liv << ConvertDetailsURI(moduleUri.DisplayUri()) << '\n'; + } + + winrt::hstring moduleDescription = details.ModuleDescription(); + if (!moduleDescription.empty()) + { + m_context.Reporter.Info() << " "_liv << ConvertDetailsValue(moduleDescription) << '\n'; + } + } + else + { + // -- Sample output when no IConfigurationUnitProcessorDetails present -- + // Type [identifier] + // Description (from directives) + // "Module": module + OutputConfigurationUnitHeader(unit, unit.Type()); + + auto description = GetValueSetString(metadata, s_Directive_Description); + if (!description.empty()) + { + m_context.Reporter.Info() << " "_liv; + OutputValueWithTruncationWarningIfNeeded(description); + } + + auto module = GetValueSetString(metadata, s_Directive_Module); + if (!module.empty()) + { + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationModuleNameOnly(ConvertIdentifier(module)) << '\n'; + } + } + + // -- Sample output footer -- + // Dependencies: dep1, dep2, ... + // Settings: + // <... settings splat> + auto dependencies = unit.Dependencies(); + if (dependencies.Size() > 0) + { + std::ostringstream allDependencies; + for (const winrt::hstring& dependency : dependencies) + { + allDependencies << ' ' << ConvertIdentifier(dependency); + } + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationDependencies(Utility::LocIndString{ std::move(allDependencies).str() }) << '\n'; + } + + ValueSet settings = unit.Settings(); + if (settings.Size() > 0) + { + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationSettings << '\n'; + OutputValueSet(settings, 4); + } + + m_context.Reporter.Info() << std::flush; + } + + private: + Execution::Context& m_context; + }; + + void OutputConfigurationUnitHeader(Execution::Context& context, const ConfigurationUnit& unit, const winrt::hstring& name) + { + OutputHelper helper{ context }; + helper.OutputConfigurationUnitHeader(unit, name); + } + + void LogFailedGetConfigurationUnitDetails(const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation) + { + if (FAILED(resultInformation.ResultCode())) + { + AICLI_LOG(Config, Error, << "Failed to get unit details for " << Utility::ConvertToUTF8(unit.Type()) << " : 0x" << + Logging::SetHRFormat << resultInformation.ResultCode() << '\n' << Utility::ConvertToUTF8(resultInformation.Description()) << '\n' << + Utility::ConvertToUTF8(resultInformation.Details())); + } + } + + struct UnitFailedMessageData + { + Utility::LocIndString Message; + bool ShowDescription = true; + }; + + // TODO: We may need a detailed result code to enable the internal error to be exposed. + // Additionally, some of the processor exceptions that generate these errors should be enlightened to produce better, localized descriptions. + UnitFailedMessageData GetUnitFailedData(const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation) + { + int32_t resultCode = resultInformation.ResultCode(); + + switch (resultCode) + { + case WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER: return { Resource::String::ConfigurationUnitHasDuplicateIdentifier(Utility::LocIndString{ Utility::ConvertToUTF8(unit.Identifier()) }), false }; + case WINGET_CONFIG_ERROR_MISSING_DEPENDENCY: return { Resource::String::ConfigurationUnitHasMissingDependency(Utility::LocIndString{ Utility::ConvertToUTF8(resultInformation.Details()) }), false }; + case WINGET_CONFIG_ERROR_ASSERTION_FAILED: return { Resource::String::ConfigurationUnitAssertHadNegativeResult(), false }; + case WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED: return { Resource::String::ConfigurationUnitNotFoundInModule(), false }; + case WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY: return { Resource::String::ConfigurationUnitNotFound(), false }; + case WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES: return { Resource::String::ConfigurationUnitMultipleMatches(), false }; + case WINGET_CONFIG_ERROR_UNIT_INVOKE_GET: return { Resource::String::ConfigurationUnitFailedDuringGet(), true }; + case WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST: return { Resource::String::ConfigurationUnitFailedDuringTest(), true }; + case WINGET_CONFIG_ERROR_UNIT_INVOKE_SET: return { Resource::String::ConfigurationUnitFailedDuringSet(), true }; + case WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT: return { Resource::String::ConfigurationUnitModuleConflict(), false }; + case WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE: return { Resource::String::ConfigurationUnitModuleImportFailed(), false }; + case WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT: return { Resource::String::ConfigurationUnitReturnedInvalidResult(), false }; + case WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT: return { Resource::String::ConfigurationUnitSettingConfigRoot(), false }; + case WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN: return { Resource::String::ConfigurationUnitImportModuleAdmin(), false }; + } + + switch (resultInformation.ResultSource()) + { + case ConfigurationUnitResultSource::ConfigurationSet: return { Resource::String::ConfigurationUnitFailedConfigSet(resultCode), true }; + case ConfigurationUnitResultSource::Internal: return { Resource::String::ConfigurationUnitFailedInternal(resultCode), true }; + case ConfigurationUnitResultSource::Precondition: return { Resource::String::ConfigurationUnitFailedPrecondition(resultCode), true }; + case ConfigurationUnitResultSource::SystemState: return { Resource::String::ConfigurationUnitFailedSystemState(resultCode), true }; + case ConfigurationUnitResultSource::UnitProcessing: return { Resource::String::ConfigurationUnitFailedUnitProcessing(resultCode), true }; + } + + // All other errors use a generic message + return { Resource::String::ConfigurationUnitFailed(resultCode), true }; + } + + Utility::LocIndString GetUnitSkippedMessage(const IConfigurationUnitResultInformation& resultInformation) + { + int32_t resultCode = resultInformation.ResultCode(); + + switch (resultInformation.ResultCode()) + { + case WINGET_CONFIG_ERROR_MANUALLY_SKIPPED: return Resource::String::ConfigurationUnitManuallySkipped(); + case WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED: return Resource::String::ConfigurationUnitNotRunDueToDependency(); + case WINGET_CONFIG_ERROR_ASSERTION_FAILED: return Resource::String::ConfigurationUnitNotRunDueToFailedAssert(); + } + + // If new cases arise and are not handled here, at least have a generic backstop message. + return Resource::String::ConfigurationUnitSkipped(resultCode); + } + + void OutputUnitRunFailure(Context& context, const ConfigurationUnit& unit, const IConfigurationUnitResultInformation& resultInformation) + { + std::string description = Utility::Trim(Utility::ConvertToUTF8(resultInformation.Description())); + + AICLI_LOG_LARGE_STRING(Config, Error, << "Configuration unit " << Utility::ConvertToUTF8(unit.Type()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] failed with code 0x" + << Logging::SetHRFormat << resultInformation.ResultCode() << " and error message:\n" << description, Utility::ConvertToUTF8(resultInformation.Details())); + + UnitFailedMessageData messageData = GetUnitFailedData(unit, resultInformation); + auto error = context.Reporter.Error(); + error << " "_liv << messageData.Message << std::endl; + + if (messageData.ShowDescription && !description.empty()) + { + constexpr size_t maximumDescriptionLines = 3; + size_t consoleWidth = GetConsoleWidth().value_or(120); + std::vector lines = Utility::SplitIntoLines(description, maximumDescriptionLines + 1); + bool wasLimited = Utility::LimitOutputLines(lines, consoleWidth, maximumDescriptionLines); + + for (const auto& line : lines) + { + error << line << std::endl; + } + + if (wasLimited || !resultInformation.Details().empty()) + { + error << Resource::String::ConfigurationDescriptionWasTruncated << std::endl; + } + } + } + + // Coordinates an active progress scope and cancellation of the operation. + template + struct ProgressCancellationUnification + { + ProgressCancellationUnification(std::unique_ptr&& progressScope, const OperationT& operation) : + m_progressScope(std::move(progressScope)), m_operation(operation) + { + SetCancellationFunction(); + } + + void Reset() + { + m_cancelScope.reset(); + m_progressScope.reset(); + } + + Reporter::AsyncProgressScope& Progress() const { return *m_progressScope; } + + void Progress(std::unique_ptr&& progressScope) + { + m_cancelScope.reset(); + m_progressScope = std::move(progressScope); + SetCancellationFunction(); + } + + OperationT& Operation() const { return m_operation; } + + private: + void SetCancellationFunction() + { + if (m_progressScope) + { + m_cancelScope = m_progressScope->Callback().SetCancellationFunction([this]() { m_operation.Cancel(); }); + } + } + + std::unique_ptr m_progressScope; + OperationT m_operation; + IProgressCallback::CancelFunctionRemoval m_cancelScope; + }; + + template + ProgressCancellationUnification CreateProgressCancellationUnification( + std::unique_ptr&& progressScope, + const Operation& operation) + { + return { std::move(progressScope), operation }; + } + + // The base type for progress reporting + template + struct ConfigurationSetProgressOutputBase + { + using Operation = IAsyncOperationWithProgress; + + ConfigurationSetProgressOutputBase(Context& context, const Operation& operation) : + m_context(context), m_unification({}, operation) + { + operation.Progress([&](const Operation& operation, const ProgressType& data) + { + Progress(operation, data); + }); + } + + virtual void Progress(const Operation& operation, const ProgressType& data) = 0; + + protected: + void MarkCompleted(const ConfigurationUnit& unit) + { + winrt::guid unitInstance = unit.InstanceIdentifier(); + m_unitsCompleted.insert(unitInstance); + } + + bool UnitHasPreviouslyCompleted(const ConfigurationUnit& unit) + { + winrt::guid unitInstance = unit.InstanceIdentifier(); + return m_unitsCompleted.count(unitInstance) != 0; + } + + // Sends VT progress to the console + void OutputUnitCompletionProgress() + { + // TODO: Change progress reporting to enable separation of spinner and VT progress reporting + // Preferably we want to be able to have: + // 1. Spinner with indefinite progress VT before set application begins + // 2. 1/N VT progress reporting for configuration units while also showing a spinner for the unit itself + } + + void BeginProgress() + { + m_unification.Progress(m_context.Reporter.BeginAsyncProgress(true)); + } + + void EndProgress() + { + m_unification.Reset(); + } + + Context& m_context; + + private: + ProgressCancellationUnification m_unification; + std::set m_unitsCompleted; + }; + + // Helper to handle progress callbacks from ApplyConfigurationSetAsync + struct ApplyConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase + { + using Operation = ConfigurationSetProgressOutputBase::Operation; + + ApplyConfigurationSetProgressOutput(Context& context, const Operation& operation) : + ConfigurationSetProgressOutputBase(context, operation) + { + } + + void Progress(const Operation& operation, const ConfigurationSetChangeData& data) override + { + auto threadContext = m_context.SetForCurrentThread(); + + if (m_isFirstProgress) + { + HandleUnreportedProgress(operation.GetResults()); + } + + switch (data.Change()) + { + case ConfigurationSetChangeEventType::SetStateChanged: + { + switch (data.SetState()) + { + case ConfigurationSetState::Pending: + m_context.Reporter.Info() << Resource::String::ConfigurationWaitingOnAnother << std::endl; + BeginProgress(); + break; + case ConfigurationSetState::InProgress: + EndProgress(); + break; + case ConfigurationSetState::Completed: + EndProgress(); + break; + } + } + break; + case ConfigurationSetChangeEventType::UnitStateChanged: + HandleUnitProgress(data.Unit(), data.UnitState(), data.ResultInformation()); + break; + } + } + + // If no progress has been reported, this function will report the given results + void HandleUnreportedProgress(const ApplyConfigurationSetResult& result) + { + if (m_isFirstProgress) + { + m_isFirstProgress = false; + + for (const ApplyConfigurationUnitResult& unitResult : result.UnitResults()) + { + HandleUnitProgress(unitResult.Unit(), unitResult.State(), unitResult.ResultInformation()); + } + } + } + + private: + void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationUnitState state, const IConfigurationUnitResultInformation& resultInformation) + { + if (UnitHasPreviouslyCompleted(unit)) + { + return; + } + + switch (state) + { + case ConfigurationUnitState::Pending: + // The unreported progress handler may send pending units, just ignore them + break; + case ConfigurationUnitState::InProgress: + OutputUnitInProgressIfNeeded(unit); + BeginProgress(); + break; + case ConfigurationUnitState::Completed: + OutputUnitInProgressIfNeeded(unit); + EndProgress(); + if (SUCCEEDED(resultInformation.ResultCode())) + { + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationUnitSuccessfullyApplied << std::endl; + } + else + { + OutputUnitRunFailure(m_context, unit, resultInformation); + } + MarkCompleted(unit); + OutputUnitCompletionProgress(); + break; + case ConfigurationUnitState::Skipped: + OutputUnitInProgressIfNeeded(unit); + AICLI_LOG(Config, Warning, << "Configuration unit " << Utility::ConvertToUTF8(unit.Type()) << "[" << Utility::ConvertToUTF8(unit.Identifier()) << "] was skipped with code 0x" + << Logging::SetHRFormat << resultInformation.ResultCode()); + m_context.Reporter.Warn() << " "_liv << GetUnitSkippedMessage(resultInformation) << std::endl; + MarkCompleted(unit); + OutputUnitCompletionProgress(); + break; + } + } + + void OutputUnitInProgressIfNeeded(const ConfigurationUnit& unit) + { + winrt::guid unitInstance = unit.InstanceIdentifier(); + if (m_unitsSeen.count(unitInstance) == 0) + { + m_unitsSeen.insert(unitInstance); + + OutputConfigurationUnitHeader(m_context, unit, unit.Details() ? unit.Details().UnitType() : unit.Type()); + } + } + + std::set m_unitsSeen; + bool m_isFirstProgress = true; + }; + + // Helper to handle progress callbacks from TestConfigurationSetAsync + struct TestConfigurationSetProgressOutput final : public ConfigurationSetProgressOutputBase + { + using Operation = ConfigurationSetProgressOutputBase::Operation; + + TestConfigurationSetProgressOutput(Context& context, const Operation& operation) : + ConfigurationSetProgressOutputBase(context, operation) + { + // Start the spinner for the first unit being tested since we only receive completions + BeginProgress(); + } + + void Progress(const Operation& operation, const TestConfigurationUnitResult& data) override + { + auto threadContext = m_context.SetForCurrentThread(); + + if (m_isFirstProgress) + { + HandleUnreportedProgress(operation.GetResults()); + } + + HandleUnitProgress(data.Unit(), data.TestResult(), data.ResultInformation()); + } + + // If no progress has been reported, this function will report the given results + void HandleUnreportedProgress(const TestConfigurationSetResult& result) + { + if (m_isFirstProgress) + { + m_isFirstProgress = false; + + for (const TestConfigurationUnitResult& unitResult : result.UnitResults()) + { + HandleUnitProgress(unitResult.Unit(), unitResult.TestResult(), unitResult.ResultInformation()); + } + } + } + + private: + void HandleUnitProgress(const ConfigurationUnit& unit, ConfigurationTestResult testResult, const IConfigurationUnitResultInformation& resultInformation) + { + if (UnitHasPreviouslyCompleted(unit)) + { + return; + } + + EndProgress(); + + OutputConfigurationUnitHeader(m_context, unit, unit.Details() ? unit.Details().UnitType() : unit.Type()); + + switch (testResult) + { + case ConfigurationTestResult::Failed: + OutputUnitRunFailure(m_context, unit, resultInformation); + break; + case ConfigurationTestResult::Negative: + m_context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationNotInDesiredState << std::endl; + break; + case ConfigurationTestResult::NotRun: + m_context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationNoTestRun << std::endl; + break; + case ConfigurationTestResult::Positive: + m_context.Reporter.Info() << " "_liv << Resource::String::ConfigurationInDesiredState << std::endl; + break; + default: // ConfigurationTestResult::Unknown + m_context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(testResult)) << std::endl; + break; + } + + MarkCompleted(unit); + OutputUnitCompletionProgress(); + BeginProgress(); + } + + bool m_isFirstProgress = true; + }; + + std::string GetNormalizedIdentifier(const winrt::hstring& identifier) + { + return Utility::FoldCase(Utility::NormalizedString{ identifier }); + } + + // Get unit validation order. Make sure dependency units are before units depending on them. + std::vector GetConfigurationSetUnitValidationOrder(winrt::Windows::Foundation::Collections::IVectorView units) + { + // Create id to index map for easier processing. + std::map idToUnitIndex; + for (uint32_t i = 0; i < units.Size(); ++i) + { + auto id = GetNormalizedIdentifier(units.GetAt(i).Identifier()); + if (!id.empty()) + { + idToUnitIndex.emplace(std::move(id), i); + } + } + + // We do not need to worry about duplicate id, missing dependency or loops + // since dependency integrity is already validated in earlier semantic checks. + + std::vector validationOrder; + + std::function addUnitToValidationOrder = + [&](const ConfigurationUnit& unit, uint32_t index) + { + if (std::find(validationOrder.begin(), validationOrder.end(), index) == validationOrder.end()) + { + for (auto const& dependencyId : unit.Dependencies()) + { + auto dependencyIndex = idToUnitIndex.find(GetNormalizedIdentifier(dependencyId))->second; + addUnitToValidationOrder(units.GetAt(dependencyIndex), dependencyIndex); + } + validationOrder.emplace_back(index); + } + }; + + for (uint32_t i = 0; i < units.Size(); ++i) + { + addUnitToValidationOrder(units.GetAt(i), i); + } + + THROW_HR_IF(E_UNEXPECTED, units.Size() != validationOrder.size()); + + return validationOrder; + } + + void SetNameAndOrigin(ConfigurationSet& set, std::filesystem::path& absolutePath) + { + // TODO: Consider how to properly determine a good value for name and origin. + set.Name(absolutePath.filename().wstring()); + set.Origin(absolutePath.parent_path().wstring()); + set.Path(absolutePath.wstring()); + } + + void OpenConfigurationSet(Execution::Context& context, const std::string& argPath, bool allowRemote) + { + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationReadingConfigFile()); + + std::wstring argPathWide = Utility::ConvertToUTF16(argPath); + bool isRemote = Utility::IsUrlRemote(argPath); + std::filesystem::path absolutePath; + Streams::IInputStream inputStream = nullptr; + + if (isRemote) + { + if (!allowRemote) + { + AICLI_LOG(Config, Error, << "Remote files are not supported"); + AICLI_TERMINATE_CONTEXT(ERROR_NOT_SUPPORTED); + } + + std::ostringstream stringStream; + ProgressCallback emptyCallback; + Utility::DownloadToStream(argPath, stringStream, Utility::DownloadType::ConfigurationFile, emptyCallback); + + auto strContent = stringStream.str(); + std::vector byteContent{ strContent.begin(), strContent.end() }; + + Streams::InMemoryRandomAccessStream memoryStream; + Streams::DataWriter streamWriter{ memoryStream }; + streamWriter.WriteBytes(byteContent); + streamWriter.StoreAsync().get(); + streamWriter.DetachStream(); + memoryStream.Seek(0); + inputStream = memoryStream; + } + else + { + absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPathWide }); + auto openAction = Streams::FileRandomAccessStream::OpenAsync(absolutePath.wstring(), FileAccessMode::Read); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); }); + inputStream = openAction.get(); + } + + OpenConfigurationSetResult openResult = nullptr; + { + auto openAction = context.Get().Processor().OpenConfigurationSetAsync(inputStream); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); }); + openResult = openAction.get(); + } + + progressScope.reset(); + + if (FAILED_LOG(static_cast(openResult.ResultCode().value))) + { + AICLI_LOG(Config, Error, << "Failed to open configuration set at " << (isRemote ? argPath : absolutePath.u8string()) << " with error 0x" << Logging::SetHRFormat << static_cast(openResult.ResultCode().value)); + + switch (openResult.ResultCode()) + { + case WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE: + context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidType(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE: + context.Reporter.Error() << Resource::String::ConfigurationFieldInvalidValue(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }, Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_MISSING_FIELD: + context.Reporter.Error() << Resource::String::ConfigurationFieldMissing(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Field()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION: + context.Reporter.Error() << Resource::String::ConfigurationFileVersionUnknown(Utility::LocIndString{ Utility::ConvertToUTF8(openResult.Value()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE: + case WINGET_CONFIG_ERROR_INVALID_YAML: + default: + context.Reporter.Error() << Resource::String::ConfigurationFileInvalidYAML << std::endl; + break; + } + + if (openResult.Line() != 0) + { + context.Reporter.Error() << Resource::String::SeeLineAndColumn(openResult.Line(), openResult.Column()) << std::endl; + } + + AICLI_TERMINATE_CONTEXT(openResult.ResultCode()); + } + + ConfigurationSet result = openResult.Set(); + + // Fill out the information about the set based on it coming from a file. + if (isRemote) + { + result.Name(Utility::GetFileNameFromURI(argPath).wstring()); + result.Origin(argPathWide); + // Do not set path. This means ${WinGetConfigRoot} not supported in remote configs. + } + else + { + SetNameAndOrigin(result, absolutePath); + } + + context.Get().Set(result); + } + + ConfigurationUnit CreateConfigurationUnitFromModuleResource(std::string_view moduleName, std::string_view resourceName, std::string_view descriptionResourceName, const Utility::Version& schemaVersion) + { + std::wstring moduleNameWide = Utility::ConvertToUTF16(moduleName); + std::wstring resourceNameWide = Utility::ConvertToUTF16(resourceName); + + ConfigurationUnit unit; + unit.Type(schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType ? moduleNameWide + L'/' + resourceNameWide : resourceNameWide); + unit.Identifier(unit.Type() + L'_' + Utility::ConvertToUTF16(Utility::GetRandomString())); + + ValueSet directives; + directives.Insert(s_Directive_Module, PropertyValue::CreateString(moduleNameWide)); + + Utility::LocIndString description; + if (!descriptionResourceName.empty()) + { + description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ descriptionResourceName }); + } + else + { + description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ resourceName }); + } + + directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); + unit.Metadata(directives); + + return unit; + } + + ConfigurationUnit CreateConfigurationUnitFromUnitType(std::wstring_view unitType, std::string_view descriptionResourceName = "") + { + ConfigurationUnit unit; + unit.Type(unitType); + unit.Identifier(unit.Type() + L'_' + Utility::ConvertToUTF16(Utility::GetRandomString())); + + ValueSet directives; + Utility::LocIndString description; + if (!descriptionResourceName.empty()) + { + description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ descriptionResourceName }); + } + else + { + description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ Utility::ConvertToUTF8(unitType) }); + } + + directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); + unit.Metadata(directives); + + return unit; + } + + ConfigurationUnit CreatePowerShellPackageUnit() + { + ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(s_UnitType_WinGetPackage_DSCv3, "Microsoft.PowerShell"); + + ValueSet settings; + settings.Insert(s_Setting_WinGetPackage_Id, PropertyValue::CreateString(s_Predefined_PowerShell_PackageId)); + settings.Insert(s_Setting_WinGetPackage_Source, PropertyValue::CreateString(s_Predefined_PowerShell_PackageSource)); + unit.Settings(settings); + + return unit; + } + + ValueSet CreateValueSetFromStringVector(const std::vector& values) + { + ValueSet result; + size_t index = 0; + + for (const auto& value : values) + { + std::wostringstream strstr; + strstr << index++; + result.Insert(strstr.str(), PropertyValue::CreateString(value)); + } + + result.Insert(L"treatAsArray", PropertyValue::CreateBoolean(true)); + return result; + } + + // TODO: This is a workaround unit to ensure v2 dsc resource modules. Move to dsc v3 resource when available. + ConfigurationUnit CreateRequiredModuleUnit(std::wstring_view moduleName, const ConfigurationUnit& dependentUnit) + { + std::wstring moduleNameString{ moduleName }; + + ConfigurationUnit unit = CreateConfigurationUnitFromUnitType(L"Microsoft.DSC.Transitional/RunCommandOnSet", Utility::ConvertToUTF8(moduleName)); + + ValueSet settings; + settings.Insert(L"executable", PropertyValue::CreateString(L"pwsh")); + std::vector arguments = + { + L"-NoProfile", + L"-NoLogo", + L"-Command", + L"if (-not (Get-Module -ListAvailable -Name " + moduleNameString + L")) { Install-Module -Name " + moduleNameString + L" -Confirm:$False -Force -AllowPrerelease -AllowClobber }" + }; + settings.Insert(L"arguments", CreateValueSetFromStringVector(arguments)); + unit.Settings(settings); + + unit.Dependencies().Append(dependentUnit.Identifier()); + + return unit; + } + + std::wstring GetWinGetSourceUnitType(const ConfigurationContext& configContext) + { + Utility::Version schemaVersion = { Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) }; + ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configContext.Set()); + + if (schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType) + { + if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3) + { + return std::wstring{ s_UnitType_WinGetSource_DSCv3 }; + } + else + { + return std::wstring{ s_Module_WinGetClient } + L'/' + std::wstring{ s_Unit_WinGetSource }; + } + } + else + { + return std::wstring{ s_Unit_WinGetSource }; + } + } + + ConfigurationUnit CreateWinGetSourceUnit(const PackageCollection::Source& source, std::wstring_view unitType) + { + std::string sourceUnitId = source.Details.Name + '_' + source.Details.Type; + std::wstring sourceUnitIdWide = Utility::ConvertToUTF16(sourceUnitId); + + ConfigurationUnit unit; + unit.Type(unitType); + unit.Identifier(sourceUnitIdWide); + unit.Intent(ConfigurationUnitIntent::Apply); + + auto description = Resource::String::ConfigureExportUnitDescription(Utility::LocIndView{ sourceUnitId }); + + ValueSet directives; + directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); + unit.Metadata(directives); + + ValueSet settings; + settings.Insert(s_Setting_WinGetSource_Name, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Name))); + settings.Insert(s_Setting_WinGetSource_Arg, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Arg))); + settings.Insert(s_Setting_WinGetSource_Type, PropertyValue::CreateString(Utility::ConvertToUTF16(source.Details.Type))); + if (WI_IsFlagSet(source.Details.TrustLevel, Repository::SourceTrustLevel::Trusted)) + { + settings.Insert(s_Setting_WinGetSource_TrustLevel, PropertyValue::CreateString(L"trusted")); + } + if (source.Details.Explicit) + { + settings.Insert(s_Setting_WinGetSource_Explicit, PropertyValue::CreateBoolean(true)); + } + if (source.Details.Priority != 0) + { + settings.Insert(s_Setting_WinGetSource_Priority, PropertyValue::CreateInt32(source.Details.Priority)); + } + unit.Settings(settings); + + unit.Environment().Context(SecurityContext::Elevated); + + return unit; + } + + std::wstring GetWinGetPackageUnitType(const ConfigurationContext& configContext) + { + Utility::Version schemaVersion = { Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) }; + ConfigurationRemoting::ProcessorEngine processorEngine = ConfigurationRemoting::DetermineProcessorEngine(configContext.Set()); + + if (schemaVersion >= s_MinimumSchemaVersionModuleNameRequiredInType) + { + if (processorEngine == ConfigurationRemoting::ProcessorEngine::DSCv3) + { + return std::wstring{ s_UnitType_WinGetPackage_DSCv3 }; + } + else + { + return std::wstring{ s_Module_WinGetClient } + L'/' + std::wstring{ s_Unit_WinGetPackage }; + } + } + else + { + return std::wstring{ s_Unit_WinGetPackage }; + } + } + + ConfigurationUnit CreateWinGetPackageUnit(const PackageCollection::Package& package, const PackageCollection::Source& source, bool includeVersion, const ConfigurationUnit& dependentUnit, std::wstring_view unitType) + { + std::wstring packageIdWide = Utility::ConvertToUTF16(package.Id); + std::wstring sourceNameWide = Utility::ConvertToUTF16(source.Details.Name); + + ConfigurationUnit unit; + unit.Type(unitType); + unit.Identifier(sourceNameWide + L'_' + packageIdWide); + unit.Intent(ConfigurationUnitIntent::Apply); + + auto description = Resource::String::ConfigureExportUnitInstallDescription(package.Id); + + ValueSet directives; + directives.Insert(s_Directive_Description, PropertyValue::CreateString(winrt::to_hstring(description.get()))); + unit.Metadata(directives); + + ValueSet settings; + settings.Insert(s_Setting_WinGetPackage_Id, PropertyValue::CreateString(packageIdWide)); + settings.Insert(s_Setting_WinGetPackage_Source, PropertyValue::CreateString(sourceNameWide)); + if (includeVersion) + { + settings.Insert(s_Setting_WinGetPackage_Version, PropertyValue::CreateString(Utility::ConvertToUTF16(package.VersionAndChannel.GetVersion().ToString()))); + } + unit.Settings(settings); + + // TODO: We may consider setting security environment based on installer elevation requirements? + + // Add dependency if needed. + if (dependentUnit) + { + auto dependencies = winrt::single_threaded_vector(); + dependencies.Append(dependentUnit.Identifier()); + unit.Dependencies(std::move(dependencies)); + } + + return unit; + } + + ApplyConfigurationUnitResult ApplyUnit(Execution::Context& context, ConfigurationUnit& unit) + { + unit.Intent(ConfigurationUnitIntent::Apply); + + auto progressScope = context.Reporter.BeginAsyncProgress(true); + + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationApplyingUnit()); + + ApplyConfigurationUnitResult applyResult = nullptr; + { + auto applyAction = context.Get().Processor().ApplyUnitAsync(unit); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { applyAction.Cancel(); }); + applyResult = applyAction.get(); + } + + progressScope.reset(); + return applyResult; + } + + GetConfigurationUnitSettingsResult GetUnitSettings(Execution::Context& context, ConfigurationUnit& unit) + { + // This assumes there are no required properties for Get, but for example WinGetPackage requires the Id. + // It is obviously wrong and will be wrong until Export is implemented for DSC v2 and a proper way to inform + // about input to winget configure export is implemented. Drink the kool-aid and transcend. + unit.Intent(ConfigurationUnitIntent::Inform); + + auto progressScope = context.Reporter.BeginAsyncProgress(true); + + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationGettingResourceSettings()); + + GetConfigurationUnitSettingsResult getResult = nullptr; + { + auto getAction = context.Get().Processor().GetUnitSettingsAsync(unit); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { getAction.Cancel(); }); + getResult = getAction.get(); + } + + progressScope.reset(); + return getResult; + } + + GetAllConfigurationUnitsResult GetAllUnits(Execution::Context& context, ConfigurationUnit& unit) + { + unit.Intent(ConfigurationUnitIntent::Inform); + + auto progressScope = context.Reporter.BeginAsyncProgress(true); + + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationExportingUnit()); + + GetAllConfigurationUnitsResult getResult = nullptr; + { + auto getAction = context.Get().Processor().GetAllUnitsAsync(unit); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { getAction.Cancel(); }); + getResult = getAction.get(); + } + + progressScope.reset(); + return getResult; + } + + std::vector ExportUnit(Execution::Context& context, ConfigurationUnit& unit, bool throwOnFailure = false) + { + std::vector result; + + context.Reporter.Info() << Resource::String::ConfigurationExportUnitStart(Utility::LocIndView{ Utility::ConvertToUTF8(unit.Type()) }) << std::endl; + + // Try export first + auto exportResult = GetAllUnits(context, unit); + auto exportResultCode = exportResult.ResultInformation().ResultCode(); + if (SUCCEEDED(exportResultCode)) + { + for (auto resultUnit : exportResult.Units()) + { + result.emplace_back(std::move(resultUnit)); + } + } + else + { + AICLI_LOG(Config, Warning, << "Failed GetAllUnits. Will try GetUnitSettings."); + LogFailedGetConfigurationUnitDetails(unit, exportResult.ResultInformation()); + + // Try GetUnitSettings if export failed. + auto getResult = GetUnitSettings(context, unit); + auto getResultCode = getResult.ResultInformation().ResultCode(); + if (getResultCode == WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY) + { + // Retry if it fails with not found in the case the module is a pre-released one. + AICLI_LOG(Config, Info, << "Failed GetUnitSettings because module not found. Will try allow prerelease."); + auto directives = unit.Metadata(); + directives.Insert(s_Directive_AllowPrerelease, PropertyValue::CreateBoolean(true)); + unit.Metadata(directives); + + getResult = GetUnitSettings(context, unit); + } + + if (FAILED(getResult.ResultInformation().ResultCode())) + { + AICLI_LOG(Config, Error, << "Failed Get Unit Settings"); + LogFailedGetConfigurationUnitDetails(unit, getResult.ResultInformation()); + + if (throwOnFailure) + { + context.Reporter.Error() << Resource::String::ConfigurationExportUnitFailed << std::endl; + OutputUnitRunFailure(context, unit, getResult.ResultInformation()); + THROW_HR(WINGET_CONFIG_ERROR_GET_FAILED); + } + else + { + context.Reporter.Warn() << Resource::String::ConfigurationExportUnitFailed << std::endl; + } + } + else + { + unit.Settings(getResult.Settings()); + result.emplace_back(unit); + } + } + + return result; + } + + void AddDependentUnit(std::vector& units, const ConfigurationUnit& dependentUnit) + { + for (auto& unit : units) + { + unit.Dependencies().Append(dependentUnit.Identifier()); + } + } + + void AddElevatedEnvironment(std::vector& units) + { + for (auto& unit : units) + { + unit.Environment().Context(SecurityContext::Elevated); + } + } + + std::vector GetAllUnitProcessors3(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + std::vector result; + + // Only supported by dsc v3 processor. + if (ConfigurationRemoting::ProcessorEngine::DSCv3 == ConfigurationRemoting::DetermineProcessorEngine(configContext.Set())) + { + auto progressScope = context.Reporter.BeginAsyncProgress(true); + + progressScope->Callback().SetProgressMessage(Resource::String::ConfigurationGettingUnitProcessors()); + + { + FindUnitProcessorsOptions findOptions; + findOptions.UnitDetailFlags(ConfigurationUnitDetailFlags::Local); + auto findAction = context.Get().Processor().FindUnitProcessorsAsync(findOptions); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { findAction.Cancel(); }); + for (auto unitProcessor : findAction.get()) + { + IConfigurationUnitProcessorDetails3 processor3; + if (unitProcessor.try_as(processor3)) + { + result.emplace_back(std::move(processor3)); + } + } + } + + progressScope.reset(); + } + + return result; + } + + void ExportPredefinedResources(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + + // PowerShell package needs to be present for certain predefined modules to work. + ConfigurationUnit powerShellPackageUnit = CreatePowerShellPackageUnit(); + configContext.Set().Units().Append(powerShellPackageUnit); + + // Apply the unit to make sure it's on the system. + context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ "Microsoft PowerShell Package" }) << std::endl; + auto applyPowerShellResult = ApplyUnit(context, powerShellPackageUnit); + if (FAILED(applyPowerShellResult.ResultInformation().ResultCode())) + { + AICLI_LOG(Config, Warning, << "Failed to ensure module. [Microsoft PowerShell Package] Related settings may not be exported."); + LogFailedGetConfigurationUnitDetails(powerShellPackageUnit, applyPowerShellResult.ResultInformation()); + context.Reporter.Warn() << Resource::String::ConfigurationExportInstallRequiredModuleFailed << std::endl; + } + + for (const auto& resources : PredefinedResourcesForExport()) + { + std::optional requiredModuleUnit; + + if (!resources.RequiredModule.empty()) + { + requiredModuleUnit = CreateRequiredModuleUnit(resources.RequiredModule, powerShellPackageUnit); + + // Apply the unit to make sure it's on the system. + context.Reporter.Info() << Resource::String::ConfigurationExportInstallRequiredModule(Utility::LocIndView{ Utility::ConvertToUTF8(resources.RequiredModule) }) << std::endl; + auto applyResult = ApplyUnit(context, requiredModuleUnit.value()); + if (SUCCEEDED(applyResult.ResultInformation().ResultCode())) + { + configContext.Set().Units().Append(requiredModuleUnit.value()); + } + else + { + AICLI_LOG(Config, Warning, << "Failed to ensure module. [" << Utility::ConvertToUTF8(resources.RequiredModule) << "] Related settings will not be exported."); + LogFailedGetConfigurationUnitDetails(requiredModuleUnit.value(), applyResult.ResultInformation()); + context.Reporter.Warn() << Resource::String::ConfigurationExportInstallRequiredModuleFailed << std::endl; + continue; + } + } + + for (const auto& resourceInfo : resources.ResourceInfos) + { + auto resourceUnit = CreateConfigurationUnitFromUnitType(resourceInfo.UnitType); + auto exportedUnits = ExportUnit(context, resourceUnit); + + if (requiredModuleUnit) + { + AddDependentUnit(exportedUnits, requiredModuleUnit.value()); + } + + // The dynamic processor factory does not support operating elevated units without a set. + // Luckily the Get/Export for all PreDefinedResources do not require elevation. + // Here we add elevation environment to exported results. + if (resourceInfo.ElevationRequired) + { + AddElevatedEnvironment(exportedUnits); + } + + for (auto exportedUnit : exportedUnits) + { + configContext.Set().Units().Append(std::move(exportedUnit)); + } + } + } + } + + // Contains a tree of all unit processors by their path. + struct UnitProcessorTree + { + private: + struct Node + { + // Packages whose installed location is at this node + std::vector Packages; + + // Units whose location is at this node. + std::vector Units; + }; + + Filesystem::PathTree m_pathTree; + + Node& FindNodeForFilePath(const winrt::hstring& filePath) + { + std::filesystem::path path{ std::wstring{ filePath } }; + return m_pathTree.FindOrInsert(path.parent_path()); + } + + public: + UnitProcessorTree(std::vector&& unitProcessors) + { + for (auto&& unit : unitProcessors) + { + winrt::hstring unitPath = unit.Path(); + AICLI_LOG(Config, Verbose, << "Found unit `" << Utility::ConvertToUTF8(unit.UnitType()) << "` at: " << Utility::ConvertToUTF8(unitPath)); + Node& node = FindNodeForFilePath(unitPath); + node.Units.emplace_back(std::move(unit)); + } + } + + void PlacePackage(const PackageCollection::Package& package) + { + Node* node = m_pathTree.Find(package.InstalledLocation); + if (node) + { + node->Packages.emplace_back(package); + } + } + + std::vector GetResourcesForPackage(const PackageCollection::Package& package) const + { + std::vector result; + + m_pathTree.VisitIf( + package.InstalledLocation, + [&](const Node& node) + { + for (const auto& unit : node.Units) + { + result.emplace_back(unit); + } + }, + [](const Node& node) + { + return node.Packages.empty(); + }); + + return result; + } + }; + + void ProcessPackagesForConfigurationExportAll(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + std::wstring sourceUnitType = GetWinGetSourceUnitType(configContext); + std::wstring packageUnitType = GetWinGetPackageUnitType(configContext); + + // This will be later used by per package settings export. + std::vector unitProcessors; + try + { + unitProcessors = GetAllUnitProcessors3(context); + } + catch (...) + { + AICLI_LOG(Config, Warning, << "Finding unit processors failed. Individual package settings will not be exported."); + context.Reporter.Warn() << Resource::String::ConfigurationExportFailedToGetUnitProcessors << std::endl; + } + + auto exclusionList = PackageSettingsExclusionList(); + + // Filter out processors in exclusion list. + for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */) + { + std::wstring unitType{ itr->UnitType() }; + bool processorRemoved = false; + for (const auto& exclusionItem : exclusionList) + { + if (Utility::CaseInsensitiveStartsWith(unitType, exclusionItem)) + { + AICLI_LOG(Config, Verbose, << "Filtering excluded resource `" << Utility::ConvertToUTF8(itr->UnitType()) << "` from export"); + itr = unitProcessors.erase(itr); + processorRemoved = true; + break; + } + } + + if (!processorRemoved) + { + itr++; + } + } + + // Filter out DSC-shipped resources and any resources co-located with them. + // First pass: find the parent directory of each known DSC resource to identify the DSC + // installation location(s), handling both packaged and unpackaged (PATH-based) DSC installs. + // Second pass: remove any resource that either matches a known DSC prefix or resides in one + // of those locations, unless the resource type appears in DscShippedResourcesAllowList(). + { + const auto dscPrefixes = DscShippedResourcePrefixes(); + const auto dscAllowList = DscShippedResourcesAllowList(); + + std::set dscLocations; + for (const auto& processor : unitProcessors) + { + std::wstring unitType{ processor.UnitType() }; + for (const auto& prefix : dscPrefixes) + { + if (Utility::CaseInsensitiveStartsWith(unitType, prefix)) + { + std::filesystem::path location = std::filesystem::weakly_canonical( + std::filesystem::path{ std::wstring{ processor.Path() } }.parent_path()); + dscLocations.emplace(std::move(location)); + break; + } + } + } + + for (auto itr = unitProcessors.begin(); itr != unitProcessors.end(); /* itr incremented in the logic */) + { + std::wstring unitType{ itr->UnitType() }; + + // Check the allow list first. + bool inAllowList = false; + for (const auto& allowedPrefix : dscAllowList) + { + if (Utility::CaseInsensitiveStartsWith(unitType, allowedPrefix)) + { + inAllowList = true; + break; + } + } + + if (inAllowList) + { + ++itr; + continue; + } + + // Check if the resource type is a known DSC-shipped prefix. + bool isDscResource = false; + for (const auto& prefix : dscPrefixes) + { + if (Utility::CaseInsensitiveStartsWith(unitType, prefix)) + { + isDscResource = true; + break; + } + } + + // If not matched by prefix, check whether it shares a location with a known DSC resource. + if (!isDscResource && !dscLocations.empty()) + { + std::filesystem::path location = std::filesystem::weakly_canonical( + std::filesystem::path{ std::wstring{ itr->Path() } }.parent_path()); + + if (dscLocations.find(location) != dscLocations.end()) + { + isDscResource = true; + } + } + + if (isDscResource) + { + AICLI_LOG(Config, Verbose, << "Filtering DSC-shipped resource `" << Utility::ConvertToUTF8(itr->UnitType()) << "` from export"); + itr = unitProcessors.erase(itr); + } + else + { + ++itr; + } + } + } + + // Build a tree of the unit processors and place packages onto it to indicate nearest ownership. + UnitProcessorTree unitProcessorTree{ std::move(unitProcessors) }; + + for (const auto& source : context.Get().Sources) + { + for (const auto& package : source.Packages) + { + unitProcessorTree.PlacePackage(package); + } + } + + for (const auto& source : context.Get().Sources) + { + // Create WinGetSource unit + ConfigurationUnit sourceUnit = anon::CreateWinGetSourceUnit(source, sourceUnitType); + configContext.Set().Units().Append(sourceUnit); + + for (const auto& package : source.Packages) + { + AICLI_LOG(Config, Verbose, << "Exporting package `" << package.Id << "` at: " << package.InstalledLocation); + + auto packageUnit = anon::CreateWinGetPackageUnit(package, source, context.Args.Contains(Args::Type::IncludeVersions), sourceUnit, packageUnitType); + configContext.Set().Units().Append(packageUnit); + + // Try package settings export. + auto unitsForPackage = unitProcessorTree.GetResourcesForPackage(package); + for (const auto& unit : unitsForPackage) + { + winrt::hstring unitType = unit.UnitType(); + AICLI_LOG(Config, Verbose, << " exporting unit `" << Utility::ConvertToUTF8(unitType)); + + ConfigurationUnit configUnit = anon::CreateConfigurationUnitFromUnitType( + unitType, + Utility::ConvertToUTF8(packageUnit.Identifier())); + + auto exportedUnits = anon::ExportUnit(context, configUnit); + anon::AddDependentUnit(exportedUnits, packageUnit); + + for (const auto& exportedUnit : exportedUnits) + { + configContext.Set().Units().Append(exportedUnit); + } + } + } + } + } + + void ProcessPackagesForConfigurationExportSingle(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + + // When exporting single WinGetPackage unit, the WinGetPackage unit can be used as a dependent unit for following configuration unit. + std::optional singlePackageUnit; + + if (context.Args.Contains(Execution::Args::Type::ConfigurationExportPackageId)) + { + const auto& exportSources = context.Get().Sources; + // There should be 1 package under 1 source. + THROW_HR_IF(E_UNEXPECTED, exportSources.size() != 1 || exportSources[0].Packages.size() != 1); + + ConfigurationUnit sourceUnit = anon::CreateWinGetSourceUnit(exportSources[0], GetWinGetSourceUnitType(configContext)); + configContext.Set().Units().Append(sourceUnit); + + singlePackageUnit = anon::CreateWinGetPackageUnit(exportSources[0].Packages[0], exportSources[0], context.Args.Contains(Args::Type::IncludeVersions), sourceUnit, GetWinGetPackageUnitType(configContext)); + configContext.Set().Units().Append(singlePackageUnit.value()); + } + + if (context.Args.Contains(Execution::Args::Type::ConfigurationExportModule, Execution::Args::Type::ConfigurationExportResource)) + { + auto configUnit = anon::CreateConfigurationUnitFromModuleResource( + context.Args.GetArg(Args::Type::ConfigurationExportModule), + context.Args.GetArg(Args::Type::ConfigurationExportResource), + singlePackageUnit ? Utility::ConvertToUTF8(singlePackageUnit->Identifier()) : "", + Utility::Version{ Utility::ConvertToUTF8(configContext.Set().SchemaVersion()) }); + + auto exportedUnits = anon::ExportUnit(context, configUnit, true); + + if (singlePackageUnit) + { + anon::AddDependentUnit(exportedUnits, singlePackageUnit.value()); + } + + for (auto exportedUnit : exportedUnits) + { + configContext.Set().Units().Append(exportedUnit); + } + } + } + + bool HistorySetMatchesInput(const ConfigurationSet& set, const std::string& foldedInput) + { + if (foldedInput.empty()) + { + return false; + } + + if (Utility::FoldCase(Utility::NormalizedString{ set.Name() }) == foldedInput) + { + return true; + } + + std::ostringstream identifierStream; + identifierStream << set.InstanceIdentifier(); + std::string identifier = identifierStream.str(); + THROW_HR_IF(E_UNEXPECTED, identifier.empty()); + + std::size_t startPosition = 0; + if (identifier[0] == '{' && foldedInput[0] != '{') + { + startPosition = 1; + } + + std::string_view identifierView = identifier; + identifierView = identifierView.substr(startPosition); + + return Utility::CaseInsensitiveStartsWith(identifierView, foldedInput); + } + + Resource::LocString ToLocString(ConfigurationSetState state) + { + switch (state) + { + case ConfigurationSetState::Pending: + return Resource::String::ConfigurationSetStatePending; + case ConfigurationSetState::InProgress: + return Resource::String::ConfigurationSetStateInProgress; + case ConfigurationSetState::Completed: + return Resource::String::ConfigurationSetStateCompleted; + case ConfigurationSetState::Unknown: + default: + return Resource::String::ConfigurationSetStateUnknown; + } + } + + Resource::LocString ToLocString(ConfigurationUnitState state) + { + switch (state) + { + case ConfigurationUnitState::Pending: + return Resource::String::ConfigurationUnitStatePending; + case ConfigurationUnitState::InProgress: + return Resource::String::ConfigurationUnitStateInProgress; + case ConfigurationUnitState::Completed: + return Resource::String::ConfigurationUnitStateCompleted; + case ConfigurationUnitState::Skipped: + return Resource::String::ConfigurationUnitStateSkipped; + case ConfigurationUnitState::Unknown: + default: + return Resource::String::ConfigurationUnitStateUnknown; + } + } + + std::string_view ToString(ConfigurationChangeEventType type) + { + switch (type) + { + case ConfigurationChangeEventType::SetAdded: + return "SetAdded"; + case ConfigurationChangeEventType::SetStateChanged: + return "SetStateChanged"; + case ConfigurationChangeEventType::SetRemoved: + return "SetRemoved"; + case ConfigurationChangeEventType::Unknown: + default: + return "Unknown"; + } + } + + std::string_view ToString(ConfigurationUnitResultSource source) + { + switch (source) + { + case ConfigurationUnitResultSource::Internal: + return "Internal"; + case ConfigurationUnitResultSource::ConfigurationSet: + return "ConfigurationSet"; + case ConfigurationUnitResultSource::UnitProcessing: + return "UnitProcessing"; + case ConfigurationUnitResultSource::SystemState: + return "SystemState"; + case ConfigurationUnitResultSource::Precondition: + return "Precondition"; + case ConfigurationUnitResultSource::None: + default: + return "None"; + } + } + } + + void CreateConfigurationProcessor(Context& context) + { + anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ anon::CreateConfigurationSetProcessorFactory(context) }); + } + + void CreateConfigurationProcessorWithoutFactory(Execution::Context& context) + { + anon::ConfigureProcessorForUse(context, ConfigurationProcessor{ IConfigurationSetProcessorFactory{ nullptr } }); + } + + void OpenConfigurationSet(Context& context) + { + if (context.Args.Contains(Args::Type::ConfigurationFile)) + { + std::string argPath{ context.Args.GetArg(Args::Type::ConfigurationFile) }; + anon::OpenConfigurationSet(context, argPath, true); + } + else + { + THROW_HR_IF(E_UNEXPECTED, !context.Args.Contains(Args::Type::ConfigurationHistoryItem)); + + context << + GetConfigurationSetHistory << + SelectSetFromHistory; + } + } + + void CreateOrOpenConfigurationSet::operator()(Context& context) const + { + std::string argPath{ context.Args.GetArg(Args::Type::OutputFile) }; + + if (std::filesystem::exists(argPath) && !m_createAlways) + { + anon::OpenConfigurationSet(context, argPath, false); + } + else + { + ConfigurationSet set; + set.SchemaVersion(Utility::ConvertToUTF16(m_defaultSchemaVersion)); + set.Environment().ProcessorIdentifier(ConfigurationRemoting::ToString(ConfigurationRemoting::ProcessorEngine::DSCv3)); + + std::wstring argPathWide = Utility::ConvertToUTF16(argPath); + auto absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPathWide }); + anon::SetNameAndOrigin(set, absolutePath); + + context.Get().Set(set); + } + } + + void ShowConfigurationSet(Context& context) + { + ConfigurationContext& configContext = context.Get(); + + if (configContext.Set().Units().Size() == 0) + { + context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl; + // This isn't an error termination, but there is no reason to proceed. + AICLI_TERMINATE_CONTEXT(S_FALSE); + } + + auto gettingDetailString = Resource::String::ConfigurationGettingDetails(); + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + + auto getDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::ReadOnly); + auto unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getDetailsOperation); + + bool suppressDetailsOutput = context.Args.Contains(Args::Type::ConfigurationSuppressPrologue); + anon::OutputHelper outputHelper{ context }; + uint32_t unitsShown = 0; + + if (!suppressDetailsOutput) + { + getDetailsOperation.Progress([&](const IAsyncOperationWithProgress& operation, const GetConfigurationUnitDetailsResult&) + { + auto threadContext = context.SetForCurrentThread(); + + unification.Reset(); + + auto unitResults = operation.GetResults().UnitResults(); + for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown) + { + GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown); + anon::LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation()); + outputHelper.OutputConfigurationUnitInformation(unitResult.Unit()); + } + + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + unification.Progress(std::move(progressScope)); + }); + } + + HRESULT hr = S_OK; + GetConfigurationSetDetailsResult result = nullptr; + + try + { + result = getDetailsOperation.get(); + } + catch (...) + { + hr = LOG_CAUGHT_EXCEPTION(); + } + + unification.Reset(); + + if (context.IsTerminated()) + { + // The context should only be terminated on us due to cancellation + context.Reporter.Error() << Resource::String::Cancelled << std::endl; + return; + } + + if (FAILED(hr)) + { + // Failing to get details might not be fatal, warn about it but proceed + context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl; + } + + // Handle any missing progress callbacks that are in the results + if (result && !suppressDetailsOutput) + { + auto unitResults = result.UnitResults(); + if (unitResults) + { + for (unitsShown; unitsShown < unitResults.Size(); ++unitsShown) + { + GetConfigurationUnitDetailsResult unitResult = unitResults.GetAt(unitsShown); + anon::LogFailedGetConfigurationUnitDetails(unitResult.Unit(), unitResult.ResultInformation()); + outputHelper.OutputConfigurationUnitInformation(unitResult.Unit()); + } + } + } + + // Handle any units that are NOT in the results (due to an exception part of the way through) + if (!suppressDetailsOutput) + { + auto allUnits = configContext.Set().Units(); + for (unitsShown; unitsShown < allUnits.Size(); ++unitsShown) + { + ConfigurationUnit unit = allUnits.GetAt(unitsShown); + outputHelper.OutputConfigurationUnitInformation(unit); + } + } + + if (outputHelper.ValuesTruncated) + { + // Using error to make this stand out from other warnings + context.Reporter.Error() << Resource::String::ConfigurationWarningSetViewTruncated << std::endl; + } + } + + void ShowConfigurationSetConflicts(Execution::Context& context) + { + UNREFERENCED_PARAMETER(context); + } + + void ConfirmConfigurationProcessing::operator()(Execution::Context& context) const + { + context.Reporter.Warn() << Resource::String::ConfigurationWarning << std::endl; + + if (!context.Args.Contains(Args::Type::ConfigurationAcceptWarning)) + { + context << RequireInteractivity(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED); + if (context.IsTerminated()) + { + return; + } + + auto promptString = m_isApply ? Resource::String::ConfigurationWarningPromptApply : Resource::String::ConfigurationWarningPromptTest; + if (!context.Reporter.PromptForBoolResponse(promptString, Reporter::Level::Warning, false)) + { + AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED); + } + } + } + + void ApplyConfigurationSet(Execution::Context& context) + { + ApplyConfigurationSetResult result = nullptr; + ConfigurationContext& configContext = context.Get(); + + { + auto applyOperation = configContext.Processor().ApplySetAsync(configContext.Set(), ApplyConfigurationSetFlags::None); + anon::ApplyConfigurationSetProgressOutput progress{ context, applyOperation }; + + result = applyOperation.get(); + progress.HandleUnreportedProgress(result); + } + + if (FAILED(result.ResultCode())) + { + context.Reporter.Error() << Resource::String::ConfigurationFailedToApply << std::endl; + + // TODO: Summarize failed configuration units, especially if we put more output for each one during execution + + AICLI_TERMINATE_CONTEXT(result.ResultCode()); + } + else + { + context.Reporter.Info() << Resource::String::ConfigurationSuccessfullyApplied << std::endl; + } + } + + void TestConfigurationSet(Execution::Context& context) + { + TestConfigurationSetResult result = nullptr; + ConfigurationContext& configContext = context.Get(); + + { + auto testOperation = configContext.Processor().TestSetAsync(configContext.Set()); + anon::TestConfigurationSetProgressOutput progress{ context, testOperation }; + + result = testOperation.get(); + progress.HandleUnreportedProgress(result); + } + + switch (result.TestResult()) + { + case ConfigurationTestResult::Failed: + context.Reporter.Error() << Resource::String::ConfigurationFailedToTest << std::endl; + AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_FAILED); + break; + case ConfigurationTestResult::Negative: + context.Reporter.Warn() << Resource::String::ConfigurationNotInDesiredState << std::endl; + context.SetTerminationHR(S_FALSE); + break; + case ConfigurationTestResult::NotRun: + context.Reporter.Warn() << Resource::String::ConfigurationNoTestRun << std::endl; + AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_TEST_NOT_RUN); + break; + case ConfigurationTestResult::Positive: + context.Reporter.Info() << Resource::String::ConfigurationInDesiredState << std::endl; + break; + default: // ConfigurationTestResult::Unknown + context.Reporter.Error() << Resource::String::ConfigurationUnexpectedTestResult(ToIntegral(result.TestResult())) << std::endl; + AICLI_TERMINATE_CONTEXT(E_FAIL); + break; + } + } + + void ValidateConfigurationSetSemantics(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + + if (configContext.Set().Units().Size() == 0) + { + context.Reporter.Warn() << Resource::String::ConfigurationFileEmpty << std::endl; + // This isn't an error termination, but there is no reason to proceed. + AICLI_TERMINATE_CONTEXT(S_FALSE); + } + + ApplyConfigurationSetResult result = configContext.Processor().ApplySet(configContext.Set(), ApplyConfigurationSetFlags::PerformConsistencyCheckOnly); + + if (FAILED(result.ResultCode())) + { + for (const auto& unitResult : result.UnitResults()) + { + IConfigurationUnitResultInformation resultInformation = unitResult.ResultInformation(); + winrt::hresult resultCode = resultInformation.ResultCode(); + + if (FAILED(resultCode)) + { + ConfigurationUnit unit = unitResult.Unit(); + + anon::OutputConfigurationUnitHeader(context, unit, unit.Type()); + + switch (resultCode) + { + case WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitHasDuplicateIdentifier(Utility::LocIndString{ Utility::ConvertToUTF8(unit.Identifier()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_MISSING_DEPENDENCY: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitHasMissingDependency(Utility::LocIndString{ Utility::ConvertToUTF8(resultInformation.Details()) }) << std::endl; + break; + case WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitIsPartOfDependencyCycle << std::endl; + break; + default: + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitFailed(static_cast(resultCode)) << std::endl; + break; + } + } + } + + AICLI_TERMINATE_CONTEXT(result.ResultCode()); + } + } + + void ValidateConfigurationSetUnitProcessors(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + + // TODO: We could optimize this by creating a set with unique resource units + + // First get the local details + auto gettingDetailString = Resource::String::ConfigurationGettingDetails(); + auto progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + + auto getLocalDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Local); + auto unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getLocalDetailsOperation); + + HRESULT getLocalHR = S_OK; + GetConfigurationSetDetailsResult getLocalResult = nullptr; + + try + { + getLocalResult = getLocalDetailsOperation.get(); + } + catch (...) + { + getLocalHR = LOG_CAUGHT_EXCEPTION(); + } + + unification.Reset(); + + if (context.IsTerminated()) + { + // The context should only be terminated on us due to cancellation + context.Reporter.Error() << Resource::String::Cancelled << std::endl; + return; + } + + if (FAILED(getLocalHR)) + { + // Failing to get details might not be fatal, warn about it but proceed + context.Reporter.Warn() << Resource::String::ConfigurationFailedToGetDetails << std::endl; + } + + // Next get the details from the catalog + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + + auto getCatalogDetailsOperation = configContext.Processor().GetSetDetailsAsync(configContext.Set(), ConfigurationUnitDetailFlags::Catalog); + unification = anon::CreateProgressCancellationUnification(std::move(progressScope), getCatalogDetailsOperation); + + HRESULT getCatalogHR = S_OK; + GetConfigurationSetDetailsResult getCatalogResult = nullptr; + + try + { + getCatalogResult = getCatalogDetailsOperation.get(); + } + catch (...) + { + getCatalogHR = LOG_CAUGHT_EXCEPTION(); + } + + unification.Reset(); + + if (context.IsTerminated()) + { + // The context should only be terminated on us due to cancellation + context.Reporter.Error() << Resource::String::Cancelled << std::endl; + return; + } + + if (FAILED(getCatalogHR)) + { + // Failing to get the catalog details means that we can't really get give much of a meaningful response. + context.Reporter.Error() << Resource::String::ConfigurationFailedToGetDetails << std::endl; + AICLI_TERMINATE_CONTEXT(getCatalogHR); + } + + auto units = configContext.Set().Units(); + auto localUnitResults = getLocalResult ? getLocalResult.UnitResults() : nullptr; + if (localUnitResults && units.Size() != localUnitResults.Size()) + { + AICLI_LOG(Config, Error, << "The details result size did not match the set size: Set[" << units.Size() << "], Local[" << localUnitResults.Size() << "]"); + THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED); + } + + auto catalogUnitResults = getCatalogResult.UnitResults(); + if (units.Size() != catalogUnitResults.Size()) + { + AICLI_LOG(Config, Error, << "The details result sizes did not match the set size: Set[" << units.Size() << "], Catalog[" << catalogUnitResults.Size() << "]"); + THROW_HR(WINGET_CONFIG_ERROR_ASSERTION_FAILED); + } + + bool foundIssue = false; + + // Now that we have the entire set of local and catalog details, process each unit + for (uint32_t i = 0; i < units.Size(); ++i) + { + const ConfigurationUnit& unit = units.GetAt(i); + GetConfigurationUnitDetailsResult localUnitResult = localUnitResults ? localUnitResults.GetAt(i) : nullptr; + GetConfigurationUnitDetailsResult catalogUnitResult = catalogUnitResults.GetAt(i); + IConfigurationUnitProcessorDetails catalogDetails = catalogUnitResult.Details(); + + bool needsHeader = true; + auto outputHeaderIfNeeded = [&]() + { + if (needsHeader) + { + anon::OutputConfigurationUnitHeader(context, unit, unit.Type()); + + needsHeader = false; + foundIssue = true; + } + }; + + if (anon::GetValueSetString(unit.Metadata(), anon::s_Directive_Module).empty()) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitModuleNotProvidedWarning << std::endl; + } + + if (catalogDetails) + { + // Warn if unit is not public + if (!catalogDetails.IsPublic()) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNotPublicWarning << std::endl; + } + + // Since it is available, no more checks are needed + continue; + } + // Everything below here is due to not finding in the catalog search + + if (FAILED(catalogUnitResult.ResultInformation().ResultCode())) + { + outputHeaderIfNeeded(); + anon::OutputUnitRunFailure(context, unit, catalogUnitResult.ResultInformation()); + continue; + } + + // If not already prerelease, try with prerelease and warn if found + std::optional allowPrereleaseDirective = anon::GetValueSetBool(unit.Metadata(), anon::s_Directive_AllowPrerelease); + if (!allowPrereleaseDirective || !allowPrereleaseDirective.value()) + { + // Check if the configuration unit is prerelease but the author forgot it + ConfigurationUnit clone = unit.Copy(); + clone.Metadata().Insert(anon::s_Directive_AllowPrerelease, PropertyValue::CreateBoolean(true)); + + progressScope = context.Reporter.BeginAsyncProgress(true); + progressScope->Callback().SetProgressMessage(gettingDetailString); + + auto getUnitDetailsOperation = configContext.Processor().GetUnitDetailsAsync(clone, ConfigurationUnitDetailFlags::Catalog); + auto unitUnification = anon::CreateProgressCancellationUnification(std::move(progressScope), getUnitDetailsOperation); + + IConfigurationUnitProcessorDetails prereleaseDetails; + + try + { + prereleaseDetails = getUnitDetailsOperation.get().Details(); + } + CATCH_LOG(); + + unification.Reset(); + + if (prereleaseDetails) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNeedsPrereleaseWarning << std::endl; + continue; + } + } + + // If module is local, warn that we couldn't find it in the catalog + if (localUnitResult && localUnitResult.Details()) + { + outputHeaderIfNeeded(); + context.Reporter.Warn() << " "_liv << Resource::String::ConfigurationUnitNotInCatalogWarning << std::endl; + continue; + } + + // Finally, error that we couldn't find it at all + outputHeaderIfNeeded(); + context.Reporter.Error() << " "_liv << Resource::String::ConfigurationUnitNotFound << std::endl; + } + + if (foundIssue) + { + // Indicate that it was not a total success + AICLI_TERMINATE_CONTEXT(S_FALSE); + } + } + + void ValidateConfigurationSetUnitContents(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + auto units = configContext.Set().Units(); + auto validationOrder = anon::GetConfigurationSetUnitValidationOrder(units.GetView()); + + Configuration::WingetDscModuleUnitValidator wingetUnitValidator; + + bool foundIssues = false; + for (const auto index : validationOrder) + { + const ConfigurationUnit& unit = units.GetAt(index); + auto moduleName = Utility::ConvertToUTF8(unit.Details().ModuleName()); + if (Utility::CaseInsensitiveEquals(wingetUnitValidator.ModuleName(), moduleName)) + { + bool result = wingetUnitValidator.ValidateConfigurationSetUnit(context, unit); + if (!result) + { + foundIssues = true; + } + } + } + + if (foundIssues) + { + // Indicate that it was not a total success + AICLI_TERMINATE_CONTEXT(S_FALSE); + } + } + + void ValidateAllGoodMessage(Execution::Context& context) + { + context.Reporter.Info() << Resource::String::ConfigurationValidationFoundNoIssues << std::endl; + } + + void SearchSourceForPackageExport(Execution::Context& context) + { + if (!context.Args.Contains(Args::Type::ConfigurationExportAll) && !context.Args.Contains(Args::Type::ConfigurationExportPackageId)) + { + // No package export needed. + return; + } + + context << + OpenSource() << + OpenCompositeSource(Repository::PredefinedSource::Installed); + + if (context.Args.Contains(Args::Type::ConfigurationExportAll)) + { + context << + SearchSourceForMany << + HandleSearchResultFailures << + EnsureMatchesFromSearchResult(OperationType::Export) << + SelectVersionsToExport; + } + else if (context.Args.Contains(Args::Type::ConfigurationExportPackageId)) + { + context.Args.AddArg(Args::Type::Id, context.Args.GetArg(Args::Type::ConfigurationExportPackageId)); + context << + SearchSourceForSingle << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(OperationType::Export) << + SelectVersionsToExport; + } + } + + void PopulateConfigurationSetForExport(Execution::Context& context) + { + bool isExportAll = context.Args.Contains(Execution::Args::Type::ConfigurationExportAll); + + if (isExportAll) + { + context << + anon::ExportPredefinedResources << + SearchSourceForPackageExport << + anon::ProcessPackagesForConfigurationExportAll; + } + else + { + context << + SearchSourceForPackageExport << + anon::ProcessPackagesForConfigurationExportSingle; + } + } + + void WriteConfigFile(Execution::Context& context) + { + try + { + std::string argPath{ context.Args.GetArg(Args::Type::OutputFile) }; + + context.Reporter.Info() << Resource::String::ConfigurationExportAddingToFile(Utility::LocIndView{ argPath }) << std::endl; + + auto tempFilePath = Runtime::GetNewTempFilePath(); + + { + std::ofstream tempStream{ tempFilePath }; + tempStream << "# Created using winget configure export " << Runtime::GetClientVersion().get() << std::endl; + } + + auto openAction = Streams::FileRandomAccessStream::OpenAsync( + tempFilePath.wstring(), + FileAccessMode::ReadWrite); + + auto stream = openAction.get(); + stream.Seek(stream.Size()); + + ConfigurationContext& configContext = context.Get(); + configContext.Set().Serialize(openAction.get()); + + auto absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ argPath }); + std::filesystem::rename(tempFilePath, absolutePath); + + context.Reporter.Info() << Resource::String::ConfigurationExportSuccessful << std::endl; + } + catch (...) + { + context.Reporter.Error() << Resource::String::ConfigurationExportFailed << std::endl; + throw; + } + } + + void GetConfigurationSetHistory(Execution::Context& context) + { + auto progressScope = context.Reporter.BeginAsyncProgress(true); + + ConfigurationContext& configContext = context.Get(); + configContext.History(configContext.Processor().GetConfigurationHistory()); + } + + void ShowConfigurationSetHistory(Execution::Context& context) + { + const auto& history = context.Get().History(); + + if (history.empty()) + { + context.Reporter.Info() << Resource::String::ConfigurationHistoryEmpty << std::endl; + } + else + { + TableOutput<4> historyTable{ context.Reporter, { Resource::String::ConfigureListIdentifier, Resource::String::ConfigureListName, Resource::String::ConfigureListState, Resource::String::ConfigureListOrigin } }; + + for (const auto& set : history) + { + winrt::hstring origin = set.Path(); + if (origin.empty()) + { + origin = set.Origin(); + } + + historyTable.OutputLine({ Utility::ConvertGuidToString(set.InstanceIdentifier()), Utility::ConvertToUTF8(set.Name()), anon::ToLocString(set.State()), Utility::ConvertToUTF8(origin)}); + } + + historyTable.Complete(); + } + } + + void SelectSetFromHistory(Execution::Context& context) + { + ConfigurationContext& configContext = context.Get(); + ConfigurationSet selectedSet{ nullptr }; + + std::string foldedInput = Utility::FoldCase(context.Args.GetArg(Execution::Args::Type::ConfigurationHistoryItem)); + + for (const ConfigurationSet& historySet : configContext.History()) + { + if (anon::HistorySetMatchesInput(historySet, foldedInput)) + { + if (selectedSet) + { + selectedSet = nullptr; + break; + } + else + { + selectedSet = historySet; + } + } + } + + if (!selectedSet) + { + context.Reporter.Warn() << Resource::String::ConfigurationHistoryItemNotFound << std::endl; + context << ShowConfigurationSetHistory; + AICLI_TERMINATE_CONTEXT(WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND); + } + + configContext.Set(std::move(selectedSet)); + } + + void RemoveConfigurationSetHistory(Execution::Context& context) + { + auto progressScope = context.Reporter.BeginAsyncProgress(true); + context.Get().Set().Remove(); + } + + void SerializeConfigurationSetHistory(Execution::Context& context) + { + auto progressScope = context.Reporter.BeginAsyncProgress(true); + std::filesystem::path absolutePath = std::filesystem::weakly_canonical(std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::OutputFile)) }); + auto openAction = Streams::FileRandomAccessStream::OpenAsync(absolutePath.wstring(), FileAccessMode::ReadWrite, StorageOpenOptions::None, Streams::FileOpenDisposition::CreateAlways); + auto cancellationScope = progressScope->Callback().SetCancellationFunction([&]() { openAction.Cancel(); }); + auto outputStream = openAction.get(); + + context.Get().Set().Serialize(outputStream); + } + + void ShowSingleConfigurationSetHistory(Execution::Context& context) + { + const auto& set = context.Get().Set(); + + // Output a table with name/value pairs for some of the set's properties. Example: + // + // Field Value + // ---------------------------------------------------- + // Identifier {7D5CF50E-F3C6-4333-BFE6-5A806F9EBA4E} + // Name Test Name + // Origin Test Origin + // Path Test Path + // State Completed + // First Applied 2024-07-16 21:15:13.000 + // Apply Begun 2024-07-16 21:15:13.000 + // Apply Ended 2024-07-16 21:15:13.000 + Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue }); + + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListIdentifier }, Utility::ConvertGuidToString(set.InstanceIdentifier()) }); + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListName }, Utility::ConvertToUTF8(set.Name()) }); + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListOrigin }, Utility::ConvertToUTF8(set.Origin()) }); + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListPath }, Utility::ConvertToUTF8(set.Path()) }); + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListState }, anon::ToLocString(set.State()) }); + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListFirstApplied }, Utility::TimePointToString(winrt::clock::to_sys(set.FirstApply())) }); + + auto applyBegun = set.ApplyBegun(); + if (applyBegun != winrt::clock::time_point{}) + { + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyBegun }, Utility::TimePointToString(winrt::clock::to_sys(applyBegun)) }); + } + + auto applyEnded = set.ApplyEnded(); + if (applyEnded != winrt::clock::time_point{}) + { + table.OutputLine({ Resource::LocString{ Resource::String::ConfigureListApplyEnded }, Utility::TimePointToString(winrt::clock::to_sys(applyEnded)) }); + } + + table.Complete(); + + context.Reporter.Info() << std::endl; + + // Output a table with unit state information. Groups are represented by indentation beneath their parent unit. Example: + // + // Unit State Result Details + // ------------------------------------------------------------ + // Module/Resource [Name] Completed 0x00000000 + // Module2/Resource [Group] Completed 0x00000000 + // |-Module3/Resource [Child1] Completed 0x00000000 + // |---Module4/Resource2 Completed 0x80004005 I failed :( + // |-Module3/Resource [Child2] Completed 0x00000000 + Execution::TableOutput<4> unitTable(context.Reporter, { Resource::String::ConfigureListUnit, Resource::String::ConfigureListState, Resource::String::ConfigureListResult, Resource::String::ConfigureListResultDescription }); + + struct UnitSiblings + { + size_t Depth = 0; + size_t Current = 0; + std::vector Siblings; + }; + + std::vector stack; + + { + UnitSiblings initial; + auto units = set.Units(); + initial.Siblings.resize(units.Size()); + units.GetMany(0, initial.Siblings); + stack.emplace_back(std::move(initial)); + } + + // Each item on the stack is a list of sibling units. + // Each iteration, we process the Current sibling from the group on top of the stack. + // If it is a group, we add its children as a new stack item to be processed next. + while (!stack.empty()) + { + UnitSiblings& currentSiblings = stack.back(); + + if (currentSiblings.Current >= currentSiblings.Siblings.size()) + { + stack.pop_back(); + continue; + } + + ConfigurationUnit& currentUnit = currentSiblings.Siblings[currentSiblings.Current++]; + + std::ostringstream unitStream; + + if (currentSiblings.Depth) + { + unitStream << '|' << std::string((currentSiblings.Depth * 2) - 1, '-'); + } + + unitStream << Utility::ConvertToUTF8(currentUnit.Type()); + + auto identifier = currentUnit.Identifier(); + if (!identifier.empty()) + { + unitStream << " [" << Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(identifier)) << ']'; + } + + auto resultInformation = currentUnit.ResultInformation(); + std::ostringstream resultStream; + std::string resultDetails; + + if (resultInformation) + { + resultStream << "0x" << Logging::SetHRFormat << resultInformation.ResultCode(); + + auto description = resultInformation.Description(); + if (description.empty()) + { + description = resultInformation.Details(); + } + + resultDetails = Utility::ConvertControlCodesToPictures(Utility::ConvertToUTF8(description)); + } + + unitTable.OutputLine({ std::move(unitStream).str(), anon::ToLocString(currentUnit.State()), std::move(resultStream).str(), std::move(resultDetails) }); + + if (currentUnit.IsGroup()) + { + UnitSiblings unitChildren; + unitChildren.Depth = currentSiblings.Depth + 1; + auto units = currentUnit.Units(); + unitChildren.Siblings.resize(units.Size()); + units.GetMany(0, unitChildren.Siblings); + stack.emplace_back(std::move(unitChildren)); + } + } + + unitTable.Complete(); + } + + void CompleteConfigurationHistoryItem(Execution::Context& context) + { + const std::string& word = context.Get().Word(); + auto stream = context.Reporter.Completion(); + + for (const auto& historyItem : ConfigurationProcessor{ IConfigurationSetProcessorFactory{ nullptr } }.GetConfigurationHistory()) + { + std::ostringstream identifierStream; + identifierStream << historyItem.InstanceIdentifier(); + std::string identifier = identifierStream.str(); + + if (word.empty() || Utility::CaseInsensitiveContainsSubstring(identifier, word)) + { + stream << '"' << identifier << '"' << std::endl; + } + + std::string name = Utility::ConvertToUTF8(historyItem.Name()); + + if (word.empty() || Utility::CaseInsensitiveStartsWith(name, word)) + { + stream << '"' << name << '"' << std::endl; + } + } + } + + void MonitorConfigurationStatus(Execution::Context& context) + { + auto& configurationContext = context.Get(); + + std::mutex activeSetMutex; + ConfigurationSet activeSet{ nullptr }; + decltype(activeSet.ConfigurationSetChange(winrt::auto_revoke, nullptr)) activeSetRevoker; + + auto setChangeHandler = [&](const ConfigurationSet& set, const ConfigurationSetChangeData& changeData) + { + if (changeData.Change() == ConfigurationSetChangeEventType::SetStateChanged) + { + context.Reporter.Info() << "(SetStateChanged) " << set.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.SetState()) << std::endl; + } + else if (changeData.Change() == ConfigurationSetChangeEventType::UnitStateChanged) + { + context.Reporter.Info() << "(UnitStateChanged) " << changeData.Unit().InstanceIdentifier() << " :: " << anon::ToLocString(changeData.UnitState()) << std::endl; + + auto resultInformation = changeData.ResultInformation(); + if (resultInformation) + { + context.Reporter.Info() << " [" << anon::ToString(resultInformation.ResultSource()) << "] :: 0x" << Logging::SetHRFormat << resultInformation.ResultCode() << std::endl; + } + } + }; + + auto setActiveSet = [&](const ConfigurationSet& set, bool force) + { + std::lock_guard lock{ activeSetMutex }; + + if (force || !activeSet) + { + activeSet = set; + activeSetRevoker = activeSet.ConfigurationSetChange(winrt::auto_revoke, setChangeHandler); + } + }; + + auto processorRevoker = configurationContext.Processor().ConfigurationChange(winrt::auto_revoke, [&](const ConfigurationSet& set, const ConfigurationChangeData& changeData) + { + context.Reporter.Info() << '[' << anon::ToString(changeData.Change()) << "] " << changeData.InstanceIdentifier() << " :: " << anon::ToLocString(changeData.State()) << std::endl; + + if (changeData.Change() == ConfigurationChangeEventType::SetStateChanged && changeData.State() == ConfigurationSetState::InProgress) + { + setActiveSet(set, true); + } + }); + + for (ConfigurationSet& historySet : configurationContext.History()) + { + if (historySet.State() == ConfigurationSetState::InProgress) + { + setActiveSet(historySet, false); + } + } + + for (;;) + { + std::this_thread::sleep_for(250ms); + if (context.IsTerminated()) + { + return; + } + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h index 060dd54a9e..ee468ee678 100644 --- a/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h +++ b/src/AppInstallerCLICore/Workflows/ConfigurationFlow.h @@ -1,169 +1,169 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Creates a configuration processor with a processor factory for full functionality. - // Required Args: None - // Inputs: None - // Outputs: ConfigurationProcessor - void CreateConfigurationProcessor(Execution::Context& context); - - // Creates a configuration processor without a processor factory for reduced functionality. - // Required Args: None - // Inputs: None - // Outputs: ConfigurationProcessor - void CreateConfigurationProcessorWithoutFactory(Execution::Context& context); - - // Opens the configuration set. - // Required Args: ConfigurationFile - // Inputs: ConfigurationProcessor - // Outputs: ConfigurationSet - void OpenConfigurationSet(Execution::Context& context); - - // Creates or opens the configuration set. - // Required Args: OutputFile - // Inputs: ConfigurationProcessor - // Outputs: ConfigurationSet - struct CreateOrOpenConfigurationSet : public WorkflowTask - { - CreateOrOpenConfigurationSet(std::string defaultSchemaVersion, bool createAlways = false) : - WorkflowTask("CreateOrOpenConfigurationSet"), m_defaultSchemaVersion(std::move(defaultSchemaVersion)), m_createAlways(createAlways) {} - - void operator()(Execution::Context& context) const override; - - private: - std::string m_defaultSchemaVersion; - bool m_createAlways = false; - }; - - // Outputs the configuration set. - // Required Args: None - // Inputs: ConfigurationSet - // Outputs: None - void ShowConfigurationSet(Execution::Context& context); - - // Outputs the configuration set. - // Required Args: None - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void ShowConfigurationSetConflicts(Execution::Context& context); - - // Handles confirming the configuration set processing should proceed. - // Required Args: None - // Inputs: None - // Outputs: None - struct ConfirmConfigurationProcessing : public WorkflowTask - { - ConfirmConfigurationProcessing(bool isApply) : WorkflowTask("ConfirmConfigurationProcessing"), m_isApply(isApply) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_isApply; - }; - - // Applies the configuration set, showing progress as it proceeds. - // Required Args: None - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void ApplyConfigurationSet(Execution::Context& context); - - // Tests the configuration set state, showing progress as it proceeds. - // Required Args: None - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void TestConfigurationSet(Execution::Context& context); - - // Validates the configuration set semantically. - // Required Args: None - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void ValidateConfigurationSetSemantics(Execution::Context& context); - - // Validates that the unit processors referenced by the set are valid/available/etc. - // Required Args: None - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void ValidateConfigurationSetUnitProcessors(Execution::Context& context); - - // Validates that specific unit contents referenced by the set are valid/available/etc. - // Required Args: None - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void ValidateConfigurationSetUnitContents(Execution::Context& context); - - // Outputs the final message stating that no issues were found. - // Required Args: None - // Inputs: None - // Outputs: None - void ValidateAllGoodMessage(Execution::Context& context); - - // Search source for package(s) to be exported in configuration file. - // Required Args: None - // Inputs: None - // Outputs: PackageCollection - void SearchSourceForPackageExport(Execution::Context& context); - - // Adds configuration unit(s) with the winget package and/or exports resource given to configuration set. - // Required Args: None - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void PopulateConfigurationSetForExport(Execution::Context& context); - - // Write the configuration file. - // Required Args: OutputFile - // Inputs: ConfigurationProcessor, ConfigurationSet - // Outputs: None - void WriteConfigFile(Execution::Context& context); - - // Gets the configuration set history. - // Required Args: None - // Inputs: ConfigurationProcessor - // Outputs: ConfigurationSetHistory - void GetConfigurationSetHistory(Execution::Context& context); - - // Outputs the configuration set history. - // Required Args: None - // Inputs: ConfigurationSetHistory - // Outputs: None - void ShowConfigurationSetHistory(Execution::Context& context); - - // Selects a specific configuration set history item. - // Required Args: ConfigurationHistoryItem - // Inputs: ConfigurationSetHistory - // Outputs: ConfigurationSet - void SelectSetFromHistory(Execution::Context& context); - - // Removes the configuration set from history. - // Required Args: None - // Inputs: ConfigurationSet - // Outputs: None - void RemoveConfigurationSetHistory(Execution::Context& context); - - // Write the configuration set history item to a file. - // Required Args: OutputFile - // Inputs: ConfigurationSet - // Outputs: None - void SerializeConfigurationSetHistory(Execution::Context& context); - - // Outputs a single configuration set (from history). - // Required Args: None - // Inputs: ConfigurationSet - // Outputs: None - void ShowSingleConfigurationSetHistory(Execution::Context& context); - - // Completes the configuration history item. - // Required Args: None - // Inputs: None - // Outputs: None - void CompleteConfigurationHistoryItem(Execution::Context& context); - - // Monitors configuration status. - // Required Args: None - // Inputs: None - // Outputs: None - void MonitorConfigurationStatus(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Creates a configuration processor with a processor factory for full functionality. + // Required Args: None + // Inputs: None + // Outputs: ConfigurationProcessor + void CreateConfigurationProcessor(Execution::Context& context); + + // Creates a configuration processor without a processor factory for reduced functionality. + // Required Args: None + // Inputs: None + // Outputs: ConfigurationProcessor + void CreateConfigurationProcessorWithoutFactory(Execution::Context& context); + + // Opens the configuration set. + // Required Args: ConfigurationFile + // Inputs: ConfigurationProcessor + // Outputs: ConfigurationSet + void OpenConfigurationSet(Execution::Context& context); + + // Creates or opens the configuration set. + // Required Args: OutputFile + // Inputs: ConfigurationProcessor + // Outputs: ConfigurationSet + struct CreateOrOpenConfigurationSet : public WorkflowTask + { + CreateOrOpenConfigurationSet(std::string defaultSchemaVersion, bool createAlways = false) : + WorkflowTask("CreateOrOpenConfigurationSet"), m_defaultSchemaVersion(std::move(defaultSchemaVersion)), m_createAlways(createAlways) {} + + void operator()(Execution::Context& context) const override; + + private: + std::string m_defaultSchemaVersion; + bool m_createAlways = false; + }; + + // Outputs the configuration set. + // Required Args: None + // Inputs: ConfigurationSet + // Outputs: None + void ShowConfigurationSet(Execution::Context& context); + + // Outputs the configuration set. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ShowConfigurationSetConflicts(Execution::Context& context); + + // Handles confirming the configuration set processing should proceed. + // Required Args: None + // Inputs: None + // Outputs: None + struct ConfirmConfigurationProcessing : public WorkflowTask + { + ConfirmConfigurationProcessing(bool isApply) : WorkflowTask("ConfirmConfigurationProcessing"), m_isApply(isApply) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_isApply; + }; + + // Applies the configuration set, showing progress as it proceeds. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ApplyConfigurationSet(Execution::Context& context); + + // Tests the configuration set state, showing progress as it proceeds. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void TestConfigurationSet(Execution::Context& context); + + // Validates the configuration set semantically. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ValidateConfigurationSetSemantics(Execution::Context& context); + + // Validates that the unit processors referenced by the set are valid/available/etc. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ValidateConfigurationSetUnitProcessors(Execution::Context& context); + + // Validates that specific unit contents referenced by the set are valid/available/etc. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void ValidateConfigurationSetUnitContents(Execution::Context& context); + + // Outputs the final message stating that no issues were found. + // Required Args: None + // Inputs: None + // Outputs: None + void ValidateAllGoodMessage(Execution::Context& context); + + // Search source for package(s) to be exported in configuration file. + // Required Args: None + // Inputs: None + // Outputs: PackageCollection + void SearchSourceForPackageExport(Execution::Context& context); + + // Adds configuration unit(s) with the winget package and/or exports resource given to configuration set. + // Required Args: None + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void PopulateConfigurationSetForExport(Execution::Context& context); + + // Write the configuration file. + // Required Args: OutputFile + // Inputs: ConfigurationProcessor, ConfigurationSet + // Outputs: None + void WriteConfigFile(Execution::Context& context); + + // Gets the configuration set history. + // Required Args: None + // Inputs: ConfigurationProcessor + // Outputs: ConfigurationSetHistory + void GetConfigurationSetHistory(Execution::Context& context); + + // Outputs the configuration set history. + // Required Args: None + // Inputs: ConfigurationSetHistory + // Outputs: None + void ShowConfigurationSetHistory(Execution::Context& context); + + // Selects a specific configuration set history item. + // Required Args: ConfigurationHistoryItem + // Inputs: ConfigurationSetHistory + // Outputs: ConfigurationSet + void SelectSetFromHistory(Execution::Context& context); + + // Removes the configuration set from history. + // Required Args: None + // Inputs: ConfigurationSet + // Outputs: None + void RemoveConfigurationSetHistory(Execution::Context& context); + + // Write the configuration set history item to a file. + // Required Args: OutputFile + // Inputs: ConfigurationSet + // Outputs: None + void SerializeConfigurationSetHistory(Execution::Context& context); + + // Outputs a single configuration set (from history). + // Required Args: None + // Inputs: ConfigurationSet + // Outputs: None + void ShowSingleConfigurationSetHistory(Execution::Context& context); + + // Completes the configuration history item. + // Required Args: None + // Inputs: None + // Outputs: None + void CompleteConfigurationHistoryItem(Execution::Context& context); + + // Monitors configuration status. + // Required Args: None + // Inputs: None + // Outputs: None + void MonitorConfigurationStatus(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp index 0581d48d07..089091fc1e 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.cpp @@ -1,355 +1,355 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "DependenciesFlow.h" -#include -#include "InstallFlow.h" -#include "winget\DependenciesGraph.h" -#include "DependencyNodeProcessor.h" -#include "ShellExecuteInstallerHandler.h" - -using namespace AppInstaller::Repository; -using namespace AppInstaller::Manifest; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - // Contains all the information needed to install a dependency package. - struct DependencyPackageCandidate - { - DependencyPackageCandidate( - std::shared_ptr&& packageVersion, - std::shared_ptr&& installedPackageVersion, - Manifest::Manifest&& manifest, - Manifest::ManifestInstaller&& installer) - : PackageVersion(std::move(packageVersion)), InstalledPackageVersion(std::move(installedPackageVersion)), Manifest(std::move(manifest)), Installer(std::move(installer)) { } - - std::shared_ptr PackageVersion; - std::shared_ptr InstalledPackageVersion; - Manifest::Manifest Manifest; - Manifest::ManifestInstaller Installer; - }; - } - - void ReportDependencies::operator()(Execution::Context& context) const - { - auto info = context.Reporter.Info(); - - const auto& dependencies = context.Get(); - if (dependencies.HasAny()) - { - info << m_messageId << std::endl; - - if (dependencies.HasAnyOf(DependencyType::WindowsFeature)) - { - info << " - " << Resource::String::WindowsFeaturesDependencies << std::endl; - dependencies.ApplyToType(DependencyType::WindowsFeature, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); - } - - if (dependencies.HasAnyOf(DependencyType::WindowsLibrary)) - { - info << " - " << Resource::String::WindowsLibrariesDependencies << std::endl; - dependencies.ApplyToType(DependencyType::WindowsLibrary, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); - } - - if (dependencies.HasAnyOf(DependencyType::Package)) - { - info << " - " << Resource::String::PackageDependencies << std::endl; - dependencies.ApplyToType(DependencyType::Package, [&info](Dependency dependency) - { - info << " " << dependency.Id(); - if (dependency.MinVersion) - { - info << " [>= " << dependency.MinVersion.value().ToString() << "]"; - } - info << std::endl; - }); - } - - if (dependencies.HasAnyOf(DependencyType::External)) - { - context.Reporter.Warn() << " - " << Resource::String::ExternalDependencies << std::endl; - dependencies.ApplyToType(DependencyType::External, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); - } - } - } - - void GetInstallersDependenciesFromManifest(Execution::Context& context) - { - const auto& manifest = context.Get(); - DependencyList allDependencies; - - for (const auto& installer : manifest.Installers) - { - allDependencies.Add(installer.Dependencies); - } - - context.Add(std::move(allDependencies)); - } - - void GetDependenciesFromInstaller(Execution::Context& context) - { - const auto& installer = context.Get(); - if (installer) - { - context.Add(installer->Dependencies); - } - } - - void GetDependenciesInfoForUninstall(Execution::Context& context) - { - // TODO make best effort to get the correct installer information, it may be better to have a record of installations and save the correct installers - context.Add(DependencyList()); // sending empty list of dependencies for now - } - - void OpenDependencySource(Execution::Context& context) - { - if (context.Contains(Execution::Data::PackageVersion)) - { - const auto& packageVersion = context.Get(); - context.Add(packageVersion->GetSource()); - } - else - { - // install from manifest requires --dependency-source to be set - context << - Workflow::OpenSource(true); - } - - if (WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) - { - // Installed source is not needed when only downloading the installer. - context << - Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true, Repository::CompositeSearchBehavior::AvailablePackages); - } - } - - void EnableWindowsFeaturesDependencies(Execution::Context& context) - { - const auto& rootDependencies = context.Get()->Dependencies; - - if (rootDependencies.Empty() || !rootDependencies.HasAnyOf(DependencyType::WindowsFeature)) - { - return; - } - - context << Workflow::EnsureRunningAsAdmin; - - if (context.IsTerminated()) - { - return; - } - - bool isCancelled = false; - bool enableFeaturesFailed = false; - bool rebootRequired = false; - bool force = context.Args.Contains(Execution::Args::Type::Force); - - rootDependencies.ApplyToType(DependencyType::WindowsFeature, [&context, &isCancelled, &enableFeaturesFailed, &force, &rebootRequired](Dependency dependency) - { - if (enableFeaturesFailed && !force || isCancelled) - { - return; - } - - auto featureName = dependency.Id(); - - auto featureContextPtr = context.CreateSubContext(); - Execution::Context& featureContext = *featureContextPtr; - auto previousThreadGlobals = featureContext.SetForCurrentThread(); - - featureContext << Workflow::ShellExecuteEnableWindowsFeature(featureName); - - if (featureContext.IsTerminated()) - { - isCancelled = true; - return; - } - - Utility::LocIndView locIndFeatureName{ featureName }; - DWORD result = featureContext.Get(); - - if (result == ERROR_SUCCESS) - { - AICLI_LOG(Core, Info, << "Successfully enabled [" << featureName << "]"); - } - else if (result == E_INVALIDARG) - { - AICLI_LOG(Core, Warning, << "Invalid Windows Feature name [" << featureName << "]"); - enableFeaturesFailed = true; - featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl; - } - else if (result == 0x800f080c) // DISMAPI_E_UNKNOWN_FEATURE - { - AICLI_LOG(Core, Warning, << "Windows Feature [" << featureName << "] does not exist"); - enableFeaturesFailed = true; - featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl; - } - else if (result == ERROR_SUCCESS_REBOOT_REQUIRED) - { - AICLI_LOG(Core, Info, << "Reboot required for [" << featureName << "]"); - rebootRequired = true; - } - else - { - AICLI_LOG(Core, Error, << "Failed to enable Windows Feature [" << featureName << "] with exit code: " << result); - enableFeaturesFailed = true; - featureContext.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeature(locIndFeatureName, result) << std::endl; - } - }); - - if (isCancelled) - { - context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - - if (enableFeaturesFailed) - { - if (force) - { - context.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeatureOverridden << std::endl; - } - else - { - context.Reporter.Error() << Resource::String::FailedToEnableWindowsFeatureOverrideRequired << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); - } - } - else - { - if (rebootRequired) - { - if (force) - { - context.Reporter.Warn() << Resource::String::RebootRequiredToEnableWindowsFeatureOverridden << std::endl; - } - else - { - context.Reporter.Error() << Resource::String::RebootRequiredToEnableWindowsFeatureOverrideRequired << std::endl; - context.SetFlags(Execution::ContextFlag::RegisterResume); - context.SetFlags(Execution::ContextFlag::RebootRequired); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL); - } - } - else - { - context.Reporter.Info() << Resource::String::EnableWindowsFeaturesSuccess << std::endl; - } - } - } - - void CreateDependencySubContexts::operator()(Execution::Context& context) const - { - if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) - { - return; - } - - auto info = context.Reporter.Info(); - auto error = context.Reporter.Error(); - const auto& rootManifest = context.Get(); - - Dependency rootAsDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); - - const auto& rootInstaller = context.Get(); - const auto& rootDependencies = rootInstaller->Dependencies; - - if (rootDependencies.Empty()) - { - // If there's no dependencies there's nothing to do aside of logging the outcome - return; - } - - context << OpenDependencySource; - if (context.IsTerminated()) - { - info << Resource::String::DependenciesFlowSourceNotFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); - } - - std::map idToPackageMap; - bool foundError = false; - DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, - [&](Dependency node) - { - DependencyNodeProcessor nodeProcessor(context); - - auto result = nodeProcessor.EvaluateDependencies(node); - DependencyList list = nodeProcessor.GetDependencyList(); - foundError = foundError || (result == DependencyNodeProcessorResult::Error); - - if (result == DependencyNodeProcessorResult::Success) - { - DependencyPackageCandidate dependencyPackageCandidate{ - std::move(nodeProcessor.GetPackageLatestVersion()), - std::move(nodeProcessor.GetPackageInstalledVersion()), - std::move(nodeProcessor.GetManifest()), - std::move(nodeProcessor.GetPreferredInstaller()) }; - - idToPackageMap.emplace(node.Id(), std::move(dependencyPackageCandidate)); - }; - - return list; - }); - - dependencyGraph.BuildGraph(); - - if (foundError) - { - error << Resource::String::DependenciesManagementExitMessage << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY); - } - - if (dependencyGraph.HasLoop()) - { - context.Reporter.Warn() << Resource::String::DependenciesFlowContainsLoop; - } - - const auto& installationOrder = dependencyGraph.GetInstallationOrder(); - - std::vector> dependencyPackageContexts; - - for (auto const& node : installationOrder) - { - auto itr = idToPackageMap.find(node.Id()); - // if the package was already installed (with a useful version) or is the root - // then there will be no installer for it on the map. - if (itr != idToPackageMap.end()) - { - auto dependencyContextPtr = context.CreateSubContext(); - Execution::Context& dependencyContext = *dependencyContextPtr; - auto previousThreadGlobals = dependencyContext.SetForCurrentThread(); - - Logging::Telemetry().LogSelectedInstaller( - static_cast(itr->second.Installer.Arch), - itr->second.Installer.Url, - Manifest::InstallerTypeToString(itr->second.Installer.EffectiveInstallerType()), - Manifest::ScopeToString(itr->second.Installer.Scope), - itr->second.Installer.Locale); - - Logging::Telemetry().LogManifestFields( - itr->second.Manifest.Id, - itr->second.Manifest.DefaultLocalization.Get(), - itr->second.Manifest.Version); - - // Extract the data needed for installing - dependencyContext.Add(itr->second.PackageVersion); - dependencyContext.Add(itr->second.Manifest); - dependencyContext.Add(itr->second.InstalledPackageVersion); - dependencyContext.Add(itr->second.Installer); - - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) - { - dependencyContext.Add(context.Get() / L"Dependencies"); - } - - dependencyPackageContexts.emplace_back(std::move(dependencyContextPtr)); - } - } - - context.Add(std::move(dependencyPackageContexts)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "DependenciesFlow.h" +#include +#include "InstallFlow.h" +#include "winget\DependenciesGraph.h" +#include "DependencyNodeProcessor.h" +#include "ShellExecuteInstallerHandler.h" + +using namespace AppInstaller::Repository; +using namespace AppInstaller::Manifest; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + // Contains all the information needed to install a dependency package. + struct DependencyPackageCandidate + { + DependencyPackageCandidate( + std::shared_ptr&& packageVersion, + std::shared_ptr&& installedPackageVersion, + Manifest::Manifest&& manifest, + Manifest::ManifestInstaller&& installer) + : PackageVersion(std::move(packageVersion)), InstalledPackageVersion(std::move(installedPackageVersion)), Manifest(std::move(manifest)), Installer(std::move(installer)) { } + + std::shared_ptr PackageVersion; + std::shared_ptr InstalledPackageVersion; + Manifest::Manifest Manifest; + Manifest::ManifestInstaller Installer; + }; + } + + void ReportDependencies::operator()(Execution::Context& context) const + { + auto info = context.Reporter.Info(); + + const auto& dependencies = context.Get(); + if (dependencies.HasAny()) + { + info << m_messageId << std::endl; + + if (dependencies.HasAnyOf(DependencyType::WindowsFeature)) + { + info << " - " << Resource::String::WindowsFeaturesDependencies << std::endl; + dependencies.ApplyToType(DependencyType::WindowsFeature, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); + } + + if (dependencies.HasAnyOf(DependencyType::WindowsLibrary)) + { + info << " - " << Resource::String::WindowsLibrariesDependencies << std::endl; + dependencies.ApplyToType(DependencyType::WindowsLibrary, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); + } + + if (dependencies.HasAnyOf(DependencyType::Package)) + { + info << " - " << Resource::String::PackageDependencies << std::endl; + dependencies.ApplyToType(DependencyType::Package, [&info](Dependency dependency) + { + info << " " << dependency.Id(); + if (dependency.MinVersion) + { + info << " [>= " << dependency.MinVersion.value().ToString() << "]"; + } + info << std::endl; + }); + } + + if (dependencies.HasAnyOf(DependencyType::External)) + { + context.Reporter.Warn() << " - " << Resource::String::ExternalDependencies << std::endl; + dependencies.ApplyToType(DependencyType::External, [&info](Dependency dependency) {info << " " << dependency.Id() << std::endl; }); + } + } + } + + void GetInstallersDependenciesFromManifest(Execution::Context& context) + { + const auto& manifest = context.Get(); + DependencyList allDependencies; + + for (const auto& installer : manifest.Installers) + { + allDependencies.Add(installer.Dependencies); + } + + context.Add(std::move(allDependencies)); + } + + void GetDependenciesFromInstaller(Execution::Context& context) + { + const auto& installer = context.Get(); + if (installer) + { + context.Add(installer->Dependencies); + } + } + + void GetDependenciesInfoForUninstall(Execution::Context& context) + { + // TODO make best effort to get the correct installer information, it may be better to have a record of installations and save the correct installers + context.Add(DependencyList()); // sending empty list of dependencies for now + } + + void OpenDependencySource(Execution::Context& context) + { + if (context.Contains(Execution::Data::PackageVersion)) + { + const auto& packageVersion = context.Get(); + context.Add(packageVersion->GetSource()); + } + else + { + // install from manifest requires --dependency-source to be set + context << + Workflow::OpenSource(true); + } + + if (WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + { + // Installed source is not needed when only downloading the installer. + context << + Workflow::OpenCompositeSource(Repository::PredefinedSource::Installed, true, Repository::CompositeSearchBehavior::AvailablePackages); + } + } + + void EnableWindowsFeaturesDependencies(Execution::Context& context) + { + const auto& rootDependencies = context.Get()->Dependencies; + + if (rootDependencies.Empty() || !rootDependencies.HasAnyOf(DependencyType::WindowsFeature)) + { + return; + } + + context << Workflow::EnsureRunningAsAdmin; + + if (context.IsTerminated()) + { + return; + } + + bool isCancelled = false; + bool enableFeaturesFailed = false; + bool rebootRequired = false; + bool force = context.Args.Contains(Execution::Args::Type::Force); + + rootDependencies.ApplyToType(DependencyType::WindowsFeature, [&context, &isCancelled, &enableFeaturesFailed, &force, &rebootRequired](Dependency dependency) + { + if (enableFeaturesFailed && !force || isCancelled) + { + return; + } + + auto featureName = dependency.Id(); + + auto featureContextPtr = context.CreateSubContext(); + Execution::Context& featureContext = *featureContextPtr; + auto previousThreadGlobals = featureContext.SetForCurrentThread(); + + featureContext << Workflow::ShellExecuteEnableWindowsFeature(featureName); + + if (featureContext.IsTerminated()) + { + isCancelled = true; + return; + } + + Utility::LocIndView locIndFeatureName{ featureName }; + DWORD result = featureContext.Get(); + + if (result == ERROR_SUCCESS) + { + AICLI_LOG(Core, Info, << "Successfully enabled [" << featureName << "]"); + } + else if (result == E_INVALIDARG) + { + AICLI_LOG(Core, Warning, << "Invalid Windows Feature name [" << featureName << "]"); + enableFeaturesFailed = true; + featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl; + } + else if (result == 0x800f080c) // DISMAPI_E_UNKNOWN_FEATURE + { + AICLI_LOG(Core, Warning, << "Windows Feature [" << featureName << "] does not exist"); + enableFeaturesFailed = true; + featureContext.Reporter.Warn() << Resource::String::WindowsFeatureNotFound(locIndFeatureName) << std::endl; + } + else if (result == ERROR_SUCCESS_REBOOT_REQUIRED) + { + AICLI_LOG(Core, Info, << "Reboot required for [" << featureName << "]"); + rebootRequired = true; + } + else + { + AICLI_LOG(Core, Error, << "Failed to enable Windows Feature [" << featureName << "] with exit code: " << result); + enableFeaturesFailed = true; + featureContext.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeature(locIndFeatureName, result) << std::endl; + } + }); + + if (isCancelled) + { + context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + + if (enableFeaturesFailed) + { + if (force) + { + context.Reporter.Warn() << Resource::String::FailedToEnableWindowsFeatureOverridden << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::FailedToEnableWindowsFeatureOverrideRequired << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); + } + } + else + { + if (rebootRequired) + { + if (force) + { + context.Reporter.Warn() << Resource::String::RebootRequiredToEnableWindowsFeatureOverridden << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::RebootRequiredToEnableWindowsFeatureOverrideRequired << std::endl; + context.SetFlags(Execution::ContextFlag::RegisterResume); + context.SetFlags(Execution::ContextFlag::RebootRequired); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL); + } + } + else + { + context.Reporter.Info() << Resource::String::EnableWindowsFeaturesSuccess << std::endl; + } + } + } + + void CreateDependencySubContexts::operator()(Execution::Context& context) const + { + if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) + { + return; + } + + auto info = context.Reporter.Info(); + auto error = context.Reporter.Error(); + const auto& rootManifest = context.Get(); + + Dependency rootAsDependency = Dependency(DependencyType::Package, rootManifest.Id, rootManifest.Version); + + const auto& rootInstaller = context.Get(); + const auto& rootDependencies = rootInstaller->Dependencies; + + if (rootDependencies.Empty()) + { + // If there's no dependencies there's nothing to do aside of logging the outcome + return; + } + + context << OpenDependencySource; + if (context.IsTerminated()) + { + info << Resource::String::DependenciesFlowSourceNotFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); + } + + std::map idToPackageMap; + bool foundError = false; + DependencyGraph dependencyGraph(rootAsDependency, rootDependencies, + [&](Dependency node) + { + DependencyNodeProcessor nodeProcessor(context); + + auto result = nodeProcessor.EvaluateDependencies(node); + DependencyList list = nodeProcessor.GetDependencyList(); + foundError = foundError || (result == DependencyNodeProcessorResult::Error); + + if (result == DependencyNodeProcessorResult::Success) + { + DependencyPackageCandidate dependencyPackageCandidate{ + std::move(nodeProcessor.GetPackageLatestVersion()), + std::move(nodeProcessor.GetPackageInstalledVersion()), + std::move(nodeProcessor.GetManifest()), + std::move(nodeProcessor.GetPreferredInstaller()) }; + + idToPackageMap.emplace(node.Id(), std::move(dependencyPackageCandidate)); + }; + + return list; + }); + + dependencyGraph.BuildGraph(); + + if (foundError) + { + error << Resource::String::DependenciesManagementExitMessage << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY); + } + + if (dependencyGraph.HasLoop()) + { + context.Reporter.Warn() << Resource::String::DependenciesFlowContainsLoop; + } + + const auto& installationOrder = dependencyGraph.GetInstallationOrder(); + + std::vector> dependencyPackageContexts; + + for (auto const& node : installationOrder) + { + auto itr = idToPackageMap.find(node.Id()); + // if the package was already installed (with a useful version) or is the root + // then there will be no installer for it on the map. + if (itr != idToPackageMap.end()) + { + auto dependencyContextPtr = context.CreateSubContext(); + Execution::Context& dependencyContext = *dependencyContextPtr; + auto previousThreadGlobals = dependencyContext.SetForCurrentThread(); + + Logging::Telemetry().LogSelectedInstaller( + static_cast(itr->second.Installer.Arch), + itr->second.Installer.Url, + Manifest::InstallerTypeToString(itr->second.Installer.EffectiveInstallerType()), + Manifest::ScopeToString(itr->second.Installer.Scope), + itr->second.Installer.Locale); + + Logging::Telemetry().LogManifestFields( + itr->second.Manifest.Id, + itr->second.Manifest.DefaultLocalization.Get(), + itr->second.Manifest.Version); + + // Extract the data needed for installing + dependencyContext.Add(itr->second.PackageVersion); + dependencyContext.Add(itr->second.Manifest); + dependencyContext.Add(itr->second.InstalledPackageVersion); + dependencyContext.Add(itr->second.Installer); + + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + { + dependencyContext.Add(context.Get() / L"Dependencies"); + } + + dependencyPackageContexts.emplace_back(std::move(dependencyContextPtr)); + } + } + + context.Add(std::move(dependencyPackageContexts)); + } +} diff --git a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h index 38812f657b..bcba4f86d0 100644 --- a/src/AppInstallerCLICore/Workflows/DependenciesFlow.h +++ b/src/AppInstallerCLICore/Workflows/DependenciesFlow.h @@ -1,70 +1,70 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Shows information about dependencies. - // Required Args: message to use at the beginning, before outputting dependencies - // Inputs: Dependencies - // Outputs: None - struct ReportDependencies : public WorkflowTask - { - ReportDependencies(AppInstaller::StringResource::StringId messageId) : - WorkflowTask("ReportDependencies"), m_messageId(messageId) {} - - void operator()(Execution::Context& context) const override; - - private: - AppInstaller::StringResource::StringId m_messageId; - }; - - // Gathers all installers dependencies from manifest. - // Required Args: None - // Inputs: Manifest - // Outputs: Dependencies - void GetInstallersDependenciesFromManifest(Execution::Context& context); - - // Gathers package dependencies information from installer. - // Required Args: None - // Inputs: Installer - // Outputs: Dependencies - void GetDependenciesFromInstaller(Execution::Context& context); - - // TODO: - // Gathers dependencies information for the uninstall command. - // Required Args: None - // Inputs: None - // Outputs: Dependencies - void GetDependenciesInfoForUninstall(Execution::Context& context); - - // Builds the dependency graph and creates the sub contexts for each package dependency. - // Required Args: None - // Inputs: Manifest, Installer and DependencySource - // Outputs: Dependencies - struct CreateDependencySubContexts : public WorkflowTask - { - CreateDependencySubContexts( - AppInstaller::StringResource::StringId dependencyReportMessage) : - WorkflowTask("CreateDependencySubContexts"), - m_dependencyReportMessage(dependencyReportMessage) {} - - void operator()(Execution::Context& context) const override; - - private: - AppInstaller::StringResource::StringId m_dependencyReportMessage; - }; - - // Sets up the source used to get the dependencies. - // Required Args: None - // Inputs: PackageVersion, Manifest - // Outputs: DependencySource - void OpenDependencySource(Execution::Context& context); - - // Enables the Windows Feature dependencies. - // Required Args: None - // Inputs: Manifest, Installer - // Outputs: None - void EnableWindowsFeaturesDependencies(Execution::Context& context); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Shows information about dependencies. + // Required Args: message to use at the beginning, before outputting dependencies + // Inputs: Dependencies + // Outputs: None + struct ReportDependencies : public WorkflowTask + { + ReportDependencies(AppInstaller::StringResource::StringId messageId) : + WorkflowTask("ReportDependencies"), m_messageId(messageId) {} + + void operator()(Execution::Context& context) const override; + + private: + AppInstaller::StringResource::StringId m_messageId; + }; + + // Gathers all installers dependencies from manifest. + // Required Args: None + // Inputs: Manifest + // Outputs: Dependencies + void GetInstallersDependenciesFromManifest(Execution::Context& context); + + // Gathers package dependencies information from installer. + // Required Args: None + // Inputs: Installer + // Outputs: Dependencies + void GetDependenciesFromInstaller(Execution::Context& context); + + // TODO: + // Gathers dependencies information for the uninstall command. + // Required Args: None + // Inputs: None + // Outputs: Dependencies + void GetDependenciesInfoForUninstall(Execution::Context& context); + + // Builds the dependency graph and creates the sub contexts for each package dependency. + // Required Args: None + // Inputs: Manifest, Installer and DependencySource + // Outputs: Dependencies + struct CreateDependencySubContexts : public WorkflowTask + { + CreateDependencySubContexts( + AppInstaller::StringResource::StringId dependencyReportMessage) : + WorkflowTask("CreateDependencySubContexts"), + m_dependencyReportMessage(dependencyReportMessage) {} + + void operator()(Execution::Context& context) const override; + + private: + AppInstaller::StringResource::StringId m_dependencyReportMessage; + }; + + // Sets up the source used to get the dependencies. + // Required Args: None + // Inputs: PackageVersion, Manifest + // Outputs: DependencySource + void OpenDependencySource(Execution::Context& context); + + // Enables the Windows Feature dependencies. + // Required Args: None + // Inputs: Manifest, Installer + // Outputs: None + void EnableWindowsFeaturesDependencies(Execution::Context& context); } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp index ad24e298f5..04bbc84a1b 100644 --- a/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp +++ b/src/AppInstallerCLICore/Workflows/DependencyNodeProcessor.cpp @@ -112,4 +112,4 @@ namespace AppInstaller::CLI::Workflow m_dependenciesList = m_installer.Dependencies; return DependencyNodeProcessorResult::Success; } -} +} diff --git a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp index 3339ace469..cd24892c44 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.cpp @@ -1,794 +1,794 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DownloadFlow.h" -#include "MSStoreInstallerHandler.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::CLI::Workflow -{ - using namespace AppInstaller::Manifest; - using namespace AppInstaller::Repository; - using namespace AppInstaller::Utility; - using namespace AppInstaller::Settings; - using namespace std::string_view_literals; - - 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) - { - const auto& manifest = context.Get(); - - std::filesystem::path tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); - tempInstallerPath /= Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); - - std::filesystem::create_directories(tempInstallerPath); - - return tempInstallerPath; - } - - // Get the file extension to be used for the installer file. - std::wstring_view GetInstallerFileExtension(Execution::Context& context) - { - const auto& installer = context.Get(); - switch (installer->BaseInstallerType) - { - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Portable: - return L".exe"sv; - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Wix: - return L".msi"sv; - case InstallerTypeEnum::Msix: - // Note: We may need to distinguish between .msix and .msixbundle in the future. - return L".msix"sv; - case InstallerTypeEnum::Zip: - return L".zip"sv; - case InstallerTypeEnum::Font: - { - const auto& fileName = GetFileNameFromURI(installer->Url); - if (fileName.has_extension()) - { - return fileName.extension().c_str(); - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - // Gets a file name that should not be able to ShellExecute. - std::filesystem::path GetInstallerPreHashValidationFileName(Execution::Context& context) - { - return { SHA256::ConvertToString(context.Get()->Sha256) }; - } - - // Gets the file name that can be used to ShellExecute the file. - std::filesystem::path GetInstallerPostHashValidationFileName(Execution::Context& context) - { - // Get file name from download URI - std::filesystem::path filename = GetFileNameFromURI(context.Get()->Url); - std::wstring_view installerExtension = GetInstallerFileExtension(context); - - // Assuming that we find a safe stem value in the URI, use it. - // This should be extremely common, but just in case fall back to the older name style. - if (filename.has_stem() && ((filename.wstring().size() + installerExtension.size()) < MAX_PATH)) - { - filename = filename.stem(); - } - else - { - const auto& manifest = context.Get(); - filename = Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); - } - - filename += installerExtension; - - // Make file name suitable for file system path - filename = Utility::ConvertToUTF16(Utility::MakeSuitablePathPart(filename.u8string())); - - return filename; - } - - // Gets the file name for the downloaded installer in the format of {id}_{version}_{architecture}_{scope}_{installerType}_{locale}. - std::filesystem::path GetInstallerDownloadOnlyFileName(Execution::Context& context, const std::wstring_view& extension = {}) - { - const auto& manifest = context.Get(); - const auto& installer = context.Get().value(); - - std::string packageName = manifest.CurrentLocalization.Get(); - std::string architecture{ ToString(installer.Arch) }; - std::string installerType{ InstallerTypeToString(installer.EffectiveInstallerType()) }; - - std::string fileName = packageName; - - if (!Version(manifest.Version).IsUnknown()) - { - fileName += '_' + manifest.Version; - } - - if (installer.Scope != ScopeEnum::Unknown) - { - fileName += '_' + std::string{ ScopeToString(installer.Scope) }; - } - - fileName += '_' + architecture + '_' + installerType; - - std::string locale = !installer.Locale.empty() ? installer.Locale : manifest.CurrentLocalization.Locale; - if (!locale.empty()) - { - fileName += '_' + locale; - } - - std::filesystem::path fileNamePath = Utility::ConvertToUTF16(fileName); - - if (!extension.empty()) - { - fileNamePath += extension; - } - else - { - fileNamePath += GetInstallerFileExtension(context); - } - - // Make file name suitable for file system path - fileNamePath = Utility::ConvertToUTF16(Utility::MakeSuitablePathPart(fileNamePath.u8string())); - return fileNamePath; - } - - // Try to remove the installer file, ignoring any errors. - void RemoveInstallerFile(const std::filesystem::path& path) - { - try - { - std::filesystem::remove(path); - - // It is assumed that the parent of the installer path will always be a directory - // If it isn't, then something went severely wrong. However, we will check that - // it is a directory here just to be safe. If it is an empty directory, remove it. - - if (std::filesystem::is_directory(path.parent_path()) && - std::filesystem::is_empty(path.parent_path())) - { - std::filesystem::remove(path.parent_path()); - } - } - catch (const std::exception& e) - { - AICLI_LOG(CLI, Warning, << "Failed to remove installer file. Reason: " << e.what()); - } - catch (...) - { - AICLI_LOG(CLI, Warning, << "Failed to remove installer file. Reason unknown."); - } - - } - - // Checks the file hash for an existing installer file. - // Returns true if the file exists and its hash matches, false otherwise. - // If the hash does not match, deletes the file. - bool ExistingInstallerFileHasHashMatch(const SHA256::HashBuffer& expectedHash, const std::filesystem::path& filePath, SHA256::HashDetails& fileHashDetails) - { - if (std::filesystem::exists(filePath)) - { - AICLI_LOG(CLI, Info, << "Found existing installer file at '" << filePath << "'. Verifying file hash."); - std::ifstream inStream{ filePath, std::ifstream::binary }; - fileHashDetails = SHA256::ComputeHashDetails(inStream); - - if (SHA256::AreEqual(expectedHash, fileHashDetails.Hash)) - { - return true; - } - - AICLI_LOG(CLI, Info, << "Hash does not match. Removing existing installer file " << filePath); - RemoveInstallerFile(filePath); - } - - 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) - { - // Check if file was already downloaded. - // This may happen after a failed installation or if the download was done - // separately before, e.g. on COM scenarios. - context << - ReportExecutionStage(ExecutionStage::Download) << - CheckForExistingInstaller; - - if (context.IsTerminated()) - { - return; - } - - bool installerDownloadOnly = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly); - - // CheckForExistingInstaller will set the InstallerPath if found - if (!context.Contains(Execution::Data::InstallerPath)) - { - const auto& installer = context.Get().value(); - switch (installer.BaseInstallerType) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Portable: - case InstallerTypeEnum::Wix: - case InstallerTypeEnum::Font: - case InstallerTypeEnum::Zip: - context << DownloadInstallerFile; - break; - case InstallerTypeEnum::Msix: - // If the signature hash is provided in the manifest and we are doing an install, - // we can just verify signature hash without a full download and do a streaming install. - // Even if we have the signature hash, we still do a full download if InstallerDownloadOnly - // flag is set, or if we need to use a proxy (as deployment APIs won't use proxy for us). - // Finally, we require the digest API for streaming install as well. - if (installer.SignatureSha256.empty() - || installerDownloadOnly - || Network().GetProxyUri() - || !Deployment::IsExpectedDigestsSupported()) - { - context << DownloadInstallerFile; - } - else - { - context << GetMsixSignatureHash; - } - break; - case InstallerTypeEnum::MSStore: - if (installerDownloadOnly) - { - context << - MSStoreDownload << - ExportManifest; - } - - return; - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - context << - VerifyInstallerHash << - RenameDownloadedInstaller << - UpdateInstallerFileMotwIfApplicable; - - if (installerDownloadOnly) - { - context << ExportManifest; - } - } - - void CheckForExistingInstaller(Execution::Context& context) - { - const auto& installer = context.Get().value(); - if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore) - { - // No installer is downloaded in this case - return; - } - - // Try looking for the file with and without extension. - auto installerPath = GetInstallerBaseDownloadPath(context); - auto installerFilename = GetInstallerPreHashValidationFileName(context); - SHA256::HashDetails fileHashDetails; - if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHashDetails)) - { - installerFilename = GetInstallerPostHashValidationFileName(context); - if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHashDetails)) - { - // No match - return; - } - } - - AICLI_LOG(CLI, Info, << "Existing installer file hash matches. Will use existing installer."); - context.Add(installerPath / installerFilename); - context.Add(std::make_pair(installer.Sha256, - DownloadResult{ std::move(fileHashDetails.Hash), fileHashDetails.SizeInBytes })); - } - - void GetInstallerDownloadPath(Execution::Context& context) - { - if (!context.Contains(Execution::Data::InstallerPath)) - { - auto tempInstallerPath = GetInstallerBaseDownloadPath(context); - tempInstallerPath /= GetInstallerPreHashValidationFileName(context); - AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); - context.Add(std::move(tempInstallerPath)); - } - } - - void DownloadInstallerFile(Execution::Context& context) - { - context << GetInstallerDownloadPath; - if (context.IsTerminated()) - { - return; - } - - const auto& installer = context.Get().value(); - const auto& installerPath = context.Get(); - - Utility::DownloadInfo downloadInfo{}; - downloadInfo.DisplayName = Resource::GetFixedString(Resource::FixedString::ProductName); - // 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; - - constexpr int MaxRetryCount = 2; - constexpr std::chrono::seconds maximumWaitTimeAllowed = 60s; - for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) - { - bool success = false; - try - { - downloadResult = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download, - installer.Url, - installerPath, - Utility::DownloadType::Installer, - std::placeholders::_1, - downloadInfo)); - - // User cancelled. - if (downloadResult.Sha256Hash.empty()) - { - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - - if (downloadResult.SizeInBytes == 0) - { - AICLI_LOG(CLI, Info, << "Got zero byte file; retrying download after a short wait..."); - std::this_thread::sleep_for(5s); - } - else - { - success = true; - } - } - catch (const ServiceUnavailableException& sue) - { - if (retryCount < MaxRetryCount - 1) - { - auto waitSecondsForRetry = sue.RetryAfter(); - if (waitSecondsForRetry > maximumWaitTimeAllowed) - { - throw; - } - - bool waitCompleted = context.Reporter.ExecuteWithProgress([&waitSecondsForRetry](IProgressCallback& progress) - { - return ProgressCallback::Wait(progress, waitSecondsForRetry); - }); - - if (!waitCompleted) - { - break; - } - } - else - { - throw; - } - } - catch (...) - { - if (retryCount < MaxRetryCount - 1) - { - AICLI_LOG(CLI, Info, << "Failed to download, waiting a bit and retry. Url: " << installer.Url); - Sleep(500); - } - else - { - throw; - } - } - - if (success) - { - break; - } - } - - context.Add(std::make_pair(installer.Sha256, downloadResult)); - } - - void GetMsixSignatureHash(Execution::Context& context) - { - // We use this when the server won't support streaming install to swap to download. - bool downloadInstead = false; - - try - { - const auto& installer = context.Get().value(); - - // Signature hash is only used for streaming installs, which don't use proxy - Msix::MsixInfo msixInfo(installer.Url); - - DownloadResult hashInfo{ msixInfo.GetSignatureHash() }; - // Value is ASCII for MSIXSTRM - // A sentinel value to indicate that this is a streaming hash rather than a download. - // The primary purpose is to prevent us from falling into the code path for zero byte files. - hashInfo.SizeInBytes = 0x4D5349585354524D; - - context.Add(std::make_pair(installer.SignatureSha256, hashInfo)); - context.Add({ std::make_pair(installer.Url, msixInfo.GetDigest()) }); - } - catch (...) - { - AICLI_LOG(CLI, Info, << "Failed to get msix signature hash, fall back to direct download."); - downloadInstead = true; - } - - if (downloadInstead) - { - context << DownloadInstallerFile; - } - } - - void VerifyInstallerHash(Execution::Context& context) - { - const auto& [expectedHash, downloadResult] = context.Get(); - - if (!std::equal( - expectedHash.begin(), - expectedHash.end(), - downloadResult.Sha256Hash.begin())) - { - bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::HashOverride); - - const auto& manifest = context.Get(); - Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, expectedHash, downloadResult.Sha256Hash, overrideHashMismatch, downloadResult.SizeInBytes, downloadResult.ContentType); - - if (downloadResult.SizeInBytes == 0) - { - context.Reporter.Error() << Resource::String::InstallerZeroByteFile << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE); - } - - // If running as admin, do not allow the user to override the hash failure. - if (Runtime::IsRunningAsAdmin()) - { - context.Reporter.Error() << Resource::String::InstallerHashMismatchAdminBlock << std::endl; - } - else if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::InstallerHashOverride)) - { - context.Reporter.Error() << Resource::String::InstallerHashMismatchError << std::endl; - } - else if (overrideHashMismatch) - { - context.Reporter.Warn() << Resource::String::InstallerHashMismatchOverridden << std::endl; - return; - } - else - { - context.Reporter.Error() << Resource::String::InstallerHashMismatchOverrideRequired << std::endl; - } - - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); - } - else - { - AICLI_LOG(CLI, Info, << "Installer hash verified"); - context.Reporter.Info() << Resource::String::InstallerHashVerified << std::endl; - - context.SetFlags(Execution::ContextFlag::InstallerHashMatched); - - if (context.Contains(Execution::Data::PackageVersion) && - context.Get()->GetSource() && - WI_IsFlagSet(context.Get()->GetSource().GetDetails().TrustLevel, SourceTrustLevel::Trusted)) - { - context.SetFlags(Execution::ContextFlag::InstallerTrusted); - } - } - } - - void UpdateInstallerFileMotwIfApplicable(Execution::Context& context) - { - // An initial MotW is always set to URLZONE_INTERNET at the time the file is downloaded. - // This function may change that to URLZONE_TRUSTED if appropriate - if (context.Contains(Execution::Data::InstallerPath)) - { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted)) - { - // We know the installer already went through multiple scans and we can trust it. - Utility::ApplyMotwIfApplicable(context.Get(), URLZONE_TRUSTED); - } - else if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerHashMatched)) - { - // IAttachmentExecute performs some additional scans before setting MotW, for example invoking anti-virus. - // A policy can be set to always mark files from a given domain as trusted, so only do this - // on installers with the right hash to prevent trusting unknown installers. - const auto& installer = context.Get(); - HRESULT hr = Utility::ApplyMotwUsingIAttachmentExecuteIfApplicable(context.Get(), installer.value().Url, URLZONE_INTERNET); - - // Not using SUCCEEDED(hr) to check since there are cases file is missing after a successful scan - if (hr != S_OK) - { - switch (hr) - { - case INET_E_SECURITY_PROBLEM: - context.Reporter.Error() << Resource::String::InstallerBlockedByPolicy << std::endl; - break; - case E_FAIL: - context.Reporter.Error() << Resource::String::InstallerFailedVirusScan << std::endl; - break; - default: - context.Reporter.Error() << Resource::String::InstallerFailedSecurityCheck << std::endl; - } - - AICLI_LOG(Fail, Error, << "Installer failed security check. Url: " << installer.value().Url << " Result: " << WINGET_OSTREAM_FORMAT_HRESULT(hr)); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED); - } - } - } - } - - void ReverifyInstallerHash(Execution::Context& context) - { - const auto& installer = context.Get().value(); - - if (context.Contains(Execution::Data::InstallerPath)) - { - // Get the hash from the installer file - const auto& installerPath = context.Get(); - std::ifstream inStream{ installerPath, std::ifstream::binary }; - auto existingFileHashDetails = SHA256::ComputeHashDetails(inStream); - context.Add(std::make_pair(installer.Sha256, - DownloadResult{ existingFileHashDetails.Hash, existingFileHashDetails.SizeInBytes })); - } - else if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore) - { - // No installer file in this case - return; - } - else if (installer.EffectiveInstallerType() == InstallerTypeEnum::Msix && !installer.SignatureSha256.empty()) - { - // We didn't download the installer file before. Just verify the signature hash again. - context << GetMsixSignatureHash; - } - else - { - // No installer downloaded - AICLI_LOG(CLI, Error, << "Installer file not found."); - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); - } - - context << VerifyInstallerHash; - } - - void RenameDownloadedInstaller(Execution::Context& context) - { - if (!context.Contains(Execution::Data::InstallerPath)) - { - // No installer downloaded, no need to rename anything. - return; - } - - auto& installerPath = context.Get(); - std::filesystem::path renamedDownloadedInstaller; - - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) - { - THROW_HR_IF(E_UNEXPECTED, !context.Contains(Execution::Data::DownloadDirectory)); - - std::filesystem::path downloadDirectory = context.Get(); - - if (!std::filesystem::exists(downloadDirectory)) - { - std::filesystem::create_directories(downloadDirectory); - } - else - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !std::filesystem::is_directory(downloadDirectory)); - } - - renamedDownloadedInstaller = downloadDirectory / GetInstallerDownloadOnlyFileName(context); - Filesystem::RenameFile(installerPath, renamedDownloadedInstaller); - context.Reporter.Info() << Resource::String::InstallerDownloaded(Utility::LocIndView{ renamedDownloadedInstaller.u8string() }) << std::endl; - } - else - { - renamedDownloadedInstaller = installerPath; - renamedDownloadedInstaller.replace_filename(GetInstallerPostHashValidationFileName(context)); - - if (installerPath == renamedDownloadedInstaller) - { - // In case we are reusing an existing downloaded file - return; - } - - Filesystem::RenameFile(installerPath, renamedDownloadedInstaller); - } - - installerPath.assign(renamedDownloadedInstaller); - AICLI_LOG(CLI, Info, << "Successfully renamed downloaded installer. Path: " << installerPath); - } - - void RemoveInstaller(Execution::Context& context) - { - // Path may not be present if installed from a URL for MSIX - if (context.Contains(Execution::Data::InstallerPath)) - { - const auto& path = context.Get(); - AICLI_LOG(CLI, Info, << "Removing installer: " << path); - RemoveInstallerFile(path); - } - } - - void SetDownloadDirectory(Execution::Context& context) - { - if (!WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) - { - return; - } - - if (context.Args.Contains(Execution::Args::Type::DownloadDirectory)) - { - context.Add(std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::DownloadDirectory)) }); - } - else - { - std::filesystem::path downloadsDirectory = Settings::User().Get(); - - if (downloadsDirectory.empty()) - { - downloadsDirectory = AppInstaller::Runtime::GetPathTo(AppInstaller::Runtime::PathName::UserProfileDownloads); - } - - const auto& manifest = context.Get(); - std::string packageDownloadFolderName = manifest.Id; - if (!Utility::Version{ manifest.Version }.IsUnknown()) - { - packageDownloadFolderName += '_' + manifest.Version; - } - context.Add(downloadsDirectory / Utility::ConvertToUTF16(packageDownloadFolderName)); - } - } - - void ExportManifest(Execution::Context& context) - { - const auto& downloadDirectory = context.Get(); - const auto& manifest = context.Get(); - const auto& installer = context.Get(); - - std::filesystem::path manifestFileName = GetInstallerDownloadOnlyFileName(context, L".yaml"); - auto manifestDownloadPath = downloadDirectory / manifestFileName; - YamlWriter::OutputYamlFile(manifest, installer.value(), manifestDownloadPath); - AICLI_LOG(CLI, Info, << "Successfully generated manifest yaml. Path: " << manifestDownloadPath); - } - - void EnsureSupportForDownload(Execution::Context& context) - { - // No checks needed if not download installer only. - if (WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) - { - return; - } - - const auto& installer = context.Get(); - - if (installer->DownloadCommandProhibited) - { - context.Reporter.Error() << Resource::String::InstallerDownloadCommandProhibited << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED); - } - } - - void InitializeInstallerDownloadAuthenticatorsMap(Execution::Context& context) - { - context.Add(std::make_shared>()); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DownloadFlow.h" +#include "MSStoreInstallerHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::Manifest; + using namespace AppInstaller::Repository; + using namespace AppInstaller::Utility; + using namespace AppInstaller::Settings; + using namespace std::string_view_literals; + + 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) + { + const auto& manifest = context.Get(); + + std::filesystem::path tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); + tempInstallerPath /= Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); + + std::filesystem::create_directories(tempInstallerPath); + + return tempInstallerPath; + } + + // Get the file extension to be used for the installer file. + std::wstring_view GetInstallerFileExtension(Execution::Context& context) + { + const auto& installer = context.Get(); + switch (installer->BaseInstallerType) + { + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Portable: + return L".exe"sv; + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: + return L".msi"sv; + case InstallerTypeEnum::Msix: + // Note: We may need to distinguish between .msix and .msixbundle in the future. + return L".msix"sv; + case InstallerTypeEnum::Zip: + return L".zip"sv; + case InstallerTypeEnum::Font: + { + const auto& fileName = GetFileNameFromURI(installer->Url); + if (fileName.has_extension()) + { + return fileName.extension().c_str(); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + // Gets a file name that should not be able to ShellExecute. + std::filesystem::path GetInstallerPreHashValidationFileName(Execution::Context& context) + { + return { SHA256::ConvertToString(context.Get()->Sha256) }; + } + + // Gets the file name that can be used to ShellExecute the file. + std::filesystem::path GetInstallerPostHashValidationFileName(Execution::Context& context) + { + // Get file name from download URI + std::filesystem::path filename = GetFileNameFromURI(context.Get()->Url); + std::wstring_view installerExtension = GetInstallerFileExtension(context); + + // Assuming that we find a safe stem value in the URI, use it. + // This should be extremely common, but just in case fall back to the older name style. + if (filename.has_stem() && ((filename.wstring().size() + installerExtension.size()) < MAX_PATH)) + { + filename = filename.stem(); + } + else + { + const auto& manifest = context.Get(); + filename = Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); + } + + filename += installerExtension; + + // Make file name suitable for file system path + filename = Utility::ConvertToUTF16(Utility::MakeSuitablePathPart(filename.u8string())); + + return filename; + } + + // Gets the file name for the downloaded installer in the format of {id}_{version}_{architecture}_{scope}_{installerType}_{locale}. + std::filesystem::path GetInstallerDownloadOnlyFileName(Execution::Context& context, const std::wstring_view& extension = {}) + { + const auto& manifest = context.Get(); + const auto& installer = context.Get().value(); + + std::string packageName = manifest.CurrentLocalization.Get(); + std::string architecture{ ToString(installer.Arch) }; + std::string installerType{ InstallerTypeToString(installer.EffectiveInstallerType()) }; + + std::string fileName = packageName; + + if (!Version(manifest.Version).IsUnknown()) + { + fileName += '_' + manifest.Version; + } + + if (installer.Scope != ScopeEnum::Unknown) + { + fileName += '_' + std::string{ ScopeToString(installer.Scope) }; + } + + fileName += '_' + architecture + '_' + installerType; + + std::string locale = !installer.Locale.empty() ? installer.Locale : manifest.CurrentLocalization.Locale; + if (!locale.empty()) + { + fileName += '_' + locale; + } + + std::filesystem::path fileNamePath = Utility::ConvertToUTF16(fileName); + + if (!extension.empty()) + { + fileNamePath += extension; + } + else + { + fileNamePath += GetInstallerFileExtension(context); + } + + // Make file name suitable for file system path + fileNamePath = Utility::ConvertToUTF16(Utility::MakeSuitablePathPart(fileNamePath.u8string())); + return fileNamePath; + } + + // Try to remove the installer file, ignoring any errors. + void RemoveInstallerFile(const std::filesystem::path& path) + { + try + { + std::filesystem::remove(path); + + // It is assumed that the parent of the installer path will always be a directory + // If it isn't, then something went severely wrong. However, we will check that + // it is a directory here just to be safe. If it is an empty directory, remove it. + + if (std::filesystem::is_directory(path.parent_path()) && + std::filesystem::is_empty(path.parent_path())) + { + std::filesystem::remove(path.parent_path()); + } + } + catch (const std::exception& e) + { + AICLI_LOG(CLI, Warning, << "Failed to remove installer file. Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(CLI, Warning, << "Failed to remove installer file. Reason unknown."); + } + + } + + // Checks the file hash for an existing installer file. + // Returns true if the file exists and its hash matches, false otherwise. + // If the hash does not match, deletes the file. + bool ExistingInstallerFileHasHashMatch(const SHA256::HashBuffer& expectedHash, const std::filesystem::path& filePath, SHA256::HashDetails& fileHashDetails) + { + if (std::filesystem::exists(filePath)) + { + AICLI_LOG(CLI, Info, << "Found existing installer file at '" << filePath << "'. Verifying file hash."); + std::ifstream inStream{ filePath, std::ifstream::binary }; + fileHashDetails = SHA256::ComputeHashDetails(inStream); + + if (SHA256::AreEqual(expectedHash, fileHashDetails.Hash)) + { + return true; + } + + AICLI_LOG(CLI, Info, << "Hash does not match. Removing existing installer file " << filePath); + RemoveInstallerFile(filePath); + } + + 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) + { + // Check if file was already downloaded. + // This may happen after a failed installation or if the download was done + // separately before, e.g. on COM scenarios. + context << + ReportExecutionStage(ExecutionStage::Download) << + CheckForExistingInstaller; + + if (context.IsTerminated()) + { + return; + } + + bool installerDownloadOnly = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly); + + // CheckForExistingInstaller will set the InstallerPath if found + if (!context.Contains(Execution::Data::InstallerPath)) + { + const auto& installer = context.Get().value(); + switch (installer.BaseInstallerType) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Portable: + case InstallerTypeEnum::Wix: + case InstallerTypeEnum::Font: + case InstallerTypeEnum::Zip: + context << DownloadInstallerFile; + break; + case InstallerTypeEnum::Msix: + // If the signature hash is provided in the manifest and we are doing an install, + // we can just verify signature hash without a full download and do a streaming install. + // Even if we have the signature hash, we still do a full download if InstallerDownloadOnly + // flag is set, or if we need to use a proxy (as deployment APIs won't use proxy for us). + // Finally, we require the digest API for streaming install as well. + if (installer.SignatureSha256.empty() + || installerDownloadOnly + || Network().GetProxyUri() + || !Deployment::IsExpectedDigestsSupported()) + { + context << DownloadInstallerFile; + } + else + { + context << GetMsixSignatureHash; + } + break; + case InstallerTypeEnum::MSStore: + if (installerDownloadOnly) + { + context << + MSStoreDownload << + ExportManifest; + } + + return; + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + context << + VerifyInstallerHash << + RenameDownloadedInstaller << + UpdateInstallerFileMotwIfApplicable; + + if (installerDownloadOnly) + { + context << ExportManifest; + } + } + + void CheckForExistingInstaller(Execution::Context& context) + { + const auto& installer = context.Get().value(); + if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore) + { + // No installer is downloaded in this case + return; + } + + // Try looking for the file with and without extension. + auto installerPath = GetInstallerBaseDownloadPath(context); + auto installerFilename = GetInstallerPreHashValidationFileName(context); + SHA256::HashDetails fileHashDetails; + if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHashDetails)) + { + installerFilename = GetInstallerPostHashValidationFileName(context); + if (!ExistingInstallerFileHasHashMatch(installer.Sha256, installerPath / installerFilename, fileHashDetails)) + { + // No match + return; + } + } + + AICLI_LOG(CLI, Info, << "Existing installer file hash matches. Will use existing installer."); + context.Add(installerPath / installerFilename); + context.Add(std::make_pair(installer.Sha256, + DownloadResult{ std::move(fileHashDetails.Hash), fileHashDetails.SizeInBytes })); + } + + void GetInstallerDownloadPath(Execution::Context& context) + { + if (!context.Contains(Execution::Data::InstallerPath)) + { + auto tempInstallerPath = GetInstallerBaseDownloadPath(context); + tempInstallerPath /= GetInstallerPreHashValidationFileName(context); + AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); + context.Add(std::move(tempInstallerPath)); + } + } + + void DownloadInstallerFile(Execution::Context& context) + { + context << GetInstallerDownloadPath; + if (context.IsTerminated()) + { + return; + } + + const auto& installer = context.Get().value(); + const auto& installerPath = context.Get(); + + Utility::DownloadInfo downloadInfo{}; + downloadInfo.DisplayName = Resource::GetFixedString(Resource::FixedString::ProductName); + // 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; + + constexpr int MaxRetryCount = 2; + constexpr std::chrono::seconds maximumWaitTimeAllowed = 60s; + for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) + { + bool success = false; + try + { + downloadResult = context.Reporter.ExecuteWithProgress(std::bind(Utility::Download, + installer.Url, + installerPath, + Utility::DownloadType::Installer, + std::placeholders::_1, + downloadInfo)); + + // User cancelled. + if (downloadResult.Sha256Hash.empty()) + { + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + + if (downloadResult.SizeInBytes == 0) + { + AICLI_LOG(CLI, Info, << "Got zero byte file; retrying download after a short wait..."); + std::this_thread::sleep_for(5s); + } + else + { + success = true; + } + } + catch (const ServiceUnavailableException& sue) + { + if (retryCount < MaxRetryCount - 1) + { + auto waitSecondsForRetry = sue.RetryAfter(); + if (waitSecondsForRetry > maximumWaitTimeAllowed) + { + throw; + } + + bool waitCompleted = context.Reporter.ExecuteWithProgress([&waitSecondsForRetry](IProgressCallback& progress) + { + return ProgressCallback::Wait(progress, waitSecondsForRetry); + }); + + if (!waitCompleted) + { + break; + } + } + else + { + throw; + } + } + catch (...) + { + if (retryCount < MaxRetryCount - 1) + { + AICLI_LOG(CLI, Info, << "Failed to download, waiting a bit and retry. Url: " << installer.Url); + Sleep(500); + } + else + { + throw; + } + } + + if (success) + { + break; + } + } + + context.Add(std::make_pair(installer.Sha256, downloadResult)); + } + + void GetMsixSignatureHash(Execution::Context& context) + { + // We use this when the server won't support streaming install to swap to download. + bool downloadInstead = false; + + try + { + const auto& installer = context.Get().value(); + + // Signature hash is only used for streaming installs, which don't use proxy + Msix::MsixInfo msixInfo(installer.Url); + + DownloadResult hashInfo{ msixInfo.GetSignatureHash() }; + // Value is ASCII for MSIXSTRM + // A sentinel value to indicate that this is a streaming hash rather than a download. + // The primary purpose is to prevent us from falling into the code path for zero byte files. + hashInfo.SizeInBytes = 0x4D5349585354524D; + + context.Add(std::make_pair(installer.SignatureSha256, hashInfo)); + context.Add({ std::make_pair(installer.Url, msixInfo.GetDigest()) }); + } + catch (...) + { + AICLI_LOG(CLI, Info, << "Failed to get msix signature hash, fall back to direct download."); + downloadInstead = true; + } + + if (downloadInstead) + { + context << DownloadInstallerFile; + } + } + + void VerifyInstallerHash(Execution::Context& context) + { + const auto& [expectedHash, downloadResult] = context.Get(); + + if (!std::equal( + expectedHash.begin(), + expectedHash.end(), + downloadResult.Sha256Hash.begin())) + { + bool overrideHashMismatch = context.Args.Contains(Execution::Args::Type::HashOverride); + + const auto& manifest = context.Get(); + Logging::Telemetry().LogInstallerHashMismatch(manifest.Id, manifest.Version, manifest.Channel, expectedHash, downloadResult.Sha256Hash, overrideHashMismatch, downloadResult.SizeInBytes, downloadResult.ContentType); + + if (downloadResult.SizeInBytes == 0) + { + context.Reporter.Error() << Resource::String::InstallerZeroByteFile << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE); + } + + // If running as admin, do not allow the user to override the hash failure. + if (Runtime::IsRunningAsAdmin()) + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchAdminBlock << std::endl; + } + else if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::InstallerHashOverride)) + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchError << std::endl; + } + else if (overrideHashMismatch) + { + context.Reporter.Warn() << Resource::String::InstallerHashMismatchOverridden << std::endl; + return; + } + else + { + context.Reporter.Error() << Resource::String::InstallerHashMismatchOverrideRequired << std::endl; + } + + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); + } + else + { + AICLI_LOG(CLI, Info, << "Installer hash verified"); + context.Reporter.Info() << Resource::String::InstallerHashVerified << std::endl; + + context.SetFlags(Execution::ContextFlag::InstallerHashMatched); + + if (context.Contains(Execution::Data::PackageVersion) && + context.Get()->GetSource() && + WI_IsFlagSet(context.Get()->GetSource().GetDetails().TrustLevel, SourceTrustLevel::Trusted)) + { + context.SetFlags(Execution::ContextFlag::InstallerTrusted); + } + } + } + + void UpdateInstallerFileMotwIfApplicable(Execution::Context& context) + { + // An initial MotW is always set to URLZONE_INTERNET at the time the file is downloaded. + // This function may change that to URLZONE_TRUSTED if appropriate + if (context.Contains(Execution::Data::InstallerPath)) + { + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted)) + { + // We know the installer already went through multiple scans and we can trust it. + Utility::ApplyMotwIfApplicable(context.Get(), URLZONE_TRUSTED); + } + else if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerHashMatched)) + { + // IAttachmentExecute performs some additional scans before setting MotW, for example invoking anti-virus. + // A policy can be set to always mark files from a given domain as trusted, so only do this + // on installers with the right hash to prevent trusting unknown installers. + const auto& installer = context.Get(); + HRESULT hr = Utility::ApplyMotwUsingIAttachmentExecuteIfApplicable(context.Get(), installer.value().Url, URLZONE_INTERNET); + + // Not using SUCCEEDED(hr) to check since there are cases file is missing after a successful scan + if (hr != S_OK) + { + switch (hr) + { + case INET_E_SECURITY_PROBLEM: + context.Reporter.Error() << Resource::String::InstallerBlockedByPolicy << std::endl; + break; + case E_FAIL: + context.Reporter.Error() << Resource::String::InstallerFailedVirusScan << std::endl; + break; + default: + context.Reporter.Error() << Resource::String::InstallerFailedSecurityCheck << std::endl; + } + + AICLI_LOG(Fail, Error, << "Installer failed security check. Url: " << installer.value().Url << " Result: " << WINGET_OSTREAM_FORMAT_HRESULT(hr)); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED); + } + } + } + } + + void ReverifyInstallerHash(Execution::Context& context) + { + const auto& installer = context.Get().value(); + + if (context.Contains(Execution::Data::InstallerPath)) + { + // Get the hash from the installer file + const auto& installerPath = context.Get(); + std::ifstream inStream{ installerPath, std::ifstream::binary }; + auto existingFileHashDetails = SHA256::ComputeHashDetails(inStream); + context.Add(std::make_pair(installer.Sha256, + DownloadResult{ existingFileHashDetails.Hash, existingFileHashDetails.SizeInBytes })); + } + else if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore) + { + // No installer file in this case + return; + } + else if (installer.EffectiveInstallerType() == InstallerTypeEnum::Msix && !installer.SignatureSha256.empty()) + { + // We didn't download the installer file before. Just verify the signature hash again. + context << GetMsixSignatureHash; + } + else + { + // No installer downloaded + AICLI_LOG(CLI, Error, << "Installer file not found."); + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + } + + context << VerifyInstallerHash; + } + + void RenameDownloadedInstaller(Execution::Context& context) + { + if (!context.Contains(Execution::Data::InstallerPath)) + { + // No installer downloaded, no need to rename anything. + return; + } + + auto& installerPath = context.Get(); + std::filesystem::path renamedDownloadedInstaller; + + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + { + THROW_HR_IF(E_UNEXPECTED, !context.Contains(Execution::Data::DownloadDirectory)); + + std::filesystem::path downloadDirectory = context.Get(); + + if (!std::filesystem::exists(downloadDirectory)) + { + std::filesystem::create_directories(downloadDirectory); + } + else + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !std::filesystem::is_directory(downloadDirectory)); + } + + renamedDownloadedInstaller = downloadDirectory / GetInstallerDownloadOnlyFileName(context); + Filesystem::RenameFile(installerPath, renamedDownloadedInstaller); + context.Reporter.Info() << Resource::String::InstallerDownloaded(Utility::LocIndView{ renamedDownloadedInstaller.u8string() }) << std::endl; + } + else + { + renamedDownloadedInstaller = installerPath; + renamedDownloadedInstaller.replace_filename(GetInstallerPostHashValidationFileName(context)); + + if (installerPath == renamedDownloadedInstaller) + { + // In case we are reusing an existing downloaded file + return; + } + + Filesystem::RenameFile(installerPath, renamedDownloadedInstaller); + } + + installerPath.assign(renamedDownloadedInstaller); + AICLI_LOG(CLI, Info, << "Successfully renamed downloaded installer. Path: " << installerPath); + } + + void RemoveInstaller(Execution::Context& context) + { + // Path may not be present if installed from a URL for MSIX + if (context.Contains(Execution::Data::InstallerPath)) + { + const auto& path = context.Get(); + AICLI_LOG(CLI, Info, << "Removing installer: " << path); + RemoveInstallerFile(path); + } + } + + void SetDownloadDirectory(Execution::Context& context) + { + if (!WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + { + return; + } + + if (context.Args.Contains(Execution::Args::Type::DownloadDirectory)) + { + context.Add(std::filesystem::path{ Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::DownloadDirectory)) }); + } + else + { + std::filesystem::path downloadsDirectory = Settings::User().Get(); + + if (downloadsDirectory.empty()) + { + downloadsDirectory = AppInstaller::Runtime::GetPathTo(AppInstaller::Runtime::PathName::UserProfileDownloads); + } + + const auto& manifest = context.Get(); + std::string packageDownloadFolderName = manifest.Id; + if (!Utility::Version{ manifest.Version }.IsUnknown()) + { + packageDownloadFolderName += '_' + manifest.Version; + } + context.Add(downloadsDirectory / Utility::ConvertToUTF16(packageDownloadFolderName)); + } + } + + void ExportManifest(Execution::Context& context) + { + const auto& downloadDirectory = context.Get(); + const auto& manifest = context.Get(); + const auto& installer = context.Get(); + + std::filesystem::path manifestFileName = GetInstallerDownloadOnlyFileName(context, L".yaml"); + auto manifestDownloadPath = downloadDirectory / manifestFileName; + YamlWriter::OutputYamlFile(manifest, installer.value(), manifestDownloadPath); + AICLI_LOG(CLI, Info, << "Successfully generated manifest yaml. Path: " << manifestDownloadPath); + } + + void EnsureSupportForDownload(Execution::Context& context) + { + // No checks needed if not download installer only. + if (WI_IsFlagClear(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + { + return; + } + + const auto& installer = context.Get(); + + if (installer->DownloadCommandProhibited) + { + context.Reporter.Error() << Resource::String::InstallerDownloadCommandProhibited << std::endl; + 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 8a3b4ab8d4..31dee73b37 100644 --- a/src/AppInstallerCLICore/Workflows/DownloadFlow.h +++ b/src/AppInstallerCLICore/Workflows/DownloadFlow.h @@ -1,95 +1,95 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Composite flow that chooses what to do based on the installer type. - // Required Args: None - // Inputs: Manifest, Installer - // Outputs: None - void DownloadInstaller(Execution::Context& context); - - // Check if the desired installer has already been downloaded. - // Required Args: None - // Inputs: Manifest, Installer - // Outputs: HashPair, InstallerPath (only if found) - void CheckForExistingInstaller(Execution::Context& context); - - // Computes the download path for the installer file. Does nothing if already determined - // Required Args: None - // Inputs: Installer, Manifest - // Outputs: InstallerPath - void GetInstallerDownloadPath(Execution::Context& context); - - // Downloads the file referenced by the Installer. - // This workflow task is also used by MSStoreDownload task. - // Required Args: None - // Inputs: Installer, Manifest - // Outputs: HashPair, InstallerPath - void DownloadInstallerFile(Execution::Context& context); - - // Computes the hash of the MSIX signature file. - // Required Args: None - // Inputs: Installer - // Outputs: HashPair - void GetMsixSignatureHash(Execution::Context& context); - - // Re-verify the installer hash. This is used in Com install commands where download and install are in separate phases. - // Required Args: None - // Inputs: InstallerPath, Installer - // Outputs: HashPair - void ReverifyInstallerHash(Execution::Context& context); - - // Verifies that the downloaded installer hash matches the hash in the manifest. - // Required Args: None - // Inputs: HashPair - // Outputs: None - void VerifyInstallerHash(Execution::Context& context); - - // Update Motw of the downloaded installer if applicable - // Required Args: None - // Inputs: HashPair, InstallerPath?, SourceId? - // Outputs: None - void UpdateInstallerFileMotwIfApplicable(Execution::Context& context); - - // This method appends appropriate extension to the downloaded installer. - // ShellExecute uses file extension to launch the installer appropriately. - // Required Args: None - // Inputs: Installer, InstallerPath - // Modifies: InstallerPath - // Outputs: None - void RenameDownloadedInstaller(Execution::Context& context); - - // Deletes the installer file. - // Required Args: None - // Inputs: InstallerPath - // Outputs: None - void RemoveInstaller(Execution::Context& context); - - // Sets the target download directory location if applicable. - // Required Args: None - // Inputs: Manifest - // Outputs: None - void SetDownloadDirectory(Execution::Context& context); - - // Exports the manifest yaml file for the downloaded package installer. Only applies to the 'winget download' command. - // Required Args: None - // Inputs: Manifest, Installer, DownloadDirectory - // Outputs: None - void ExportManifest(Execution::Context& context); - - // This method ensures requirements of download for later offline installation. - // Required Args: None - // 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); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Composite flow that chooses what to do based on the installer type. + // Required Args: None + // Inputs: Manifest, Installer + // Outputs: None + void DownloadInstaller(Execution::Context& context); + + // Check if the desired installer has already been downloaded. + // Required Args: None + // Inputs: Manifest, Installer + // Outputs: HashPair, InstallerPath (only if found) + void CheckForExistingInstaller(Execution::Context& context); + + // Computes the download path for the installer file. Does nothing if already determined + // Required Args: None + // Inputs: Installer, Manifest + // Outputs: InstallerPath + void GetInstallerDownloadPath(Execution::Context& context); + + // Downloads the file referenced by the Installer. + // This workflow task is also used by MSStoreDownload task. + // Required Args: None + // Inputs: Installer, Manifest + // Outputs: HashPair, InstallerPath + void DownloadInstallerFile(Execution::Context& context); + + // Computes the hash of the MSIX signature file. + // Required Args: None + // Inputs: Installer + // Outputs: HashPair + void GetMsixSignatureHash(Execution::Context& context); + + // Re-verify the installer hash. This is used in Com install commands where download and install are in separate phases. + // Required Args: None + // Inputs: InstallerPath, Installer + // Outputs: HashPair + void ReverifyInstallerHash(Execution::Context& context); + + // Verifies that the downloaded installer hash matches the hash in the manifest. + // Required Args: None + // Inputs: HashPair + // Outputs: None + void VerifyInstallerHash(Execution::Context& context); + + // Update Motw of the downloaded installer if applicable + // Required Args: None + // Inputs: HashPair, InstallerPath?, SourceId? + // Outputs: None + void UpdateInstallerFileMotwIfApplicable(Execution::Context& context); + + // This method appends appropriate extension to the downloaded installer. + // ShellExecute uses file extension to launch the installer appropriately. + // Required Args: None + // Inputs: Installer, InstallerPath + // Modifies: InstallerPath + // Outputs: None + void RenameDownloadedInstaller(Execution::Context& context); + + // Deletes the installer file. + // Required Args: None + // Inputs: InstallerPath + // Outputs: None + void RemoveInstaller(Execution::Context& context); + + // Sets the target download directory location if applicable. + // Required Args: None + // Inputs: Manifest + // Outputs: None + void SetDownloadDirectory(Execution::Context& context); + + // Exports the manifest yaml file for the downloaded package installer. Only applies to the 'winget download' command. + // Required Args: None + // Inputs: Manifest, Installer, DownloadDirectory + // Outputs: None + void ExportManifest(Execution::Context& context); + + // This method ensures requirements of download for later offline installation. + // Required Args: None + // 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/ImportExportFlow.cpp b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp index 01956d3a82..0564002fec 100644 --- a/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ImportExportFlow.cpp @@ -1,373 +1,373 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "InstallFlow.h" -#include "ImportExportFlow.h" -#include "UpdateFlow.h" -#include "PackageCollection.h" -#include "DependenciesFlow.h" -#include "WorkflowBase.h" -#include -#include -#include -#include - -namespace AppInstaller::CLI::Workflow -{ - using namespace AppInstaller::Repository; - - namespace - { - SourceDetails GetSourceDetails(const SourceDetails& source) - { - return source; - } - - SourceDetails GetSourceDetails(const PackageCollection::Source& source) - { - return source.Details; - } - - SourceDetails GetSourceDetails(const Repository::Source& source) - { - return source.GetDetails(); - } - - // Creates a predicate that determines whether a source matches a description in a SourceDetails. - template - std::function GetSourceDetailsEquivalencePredicate(const SourceDetails& details) - { - return [&](const T& source) - { - SourceDetails sourceDetails = GetSourceDetails(source); - return sourceDetails.Type == details.Type && sourceDetails.Identifier == details.Identifier; - }; - } - - // Finds a source equivalent to the one specified. - template - typename std::vector::const_iterator FindSource(const std::vector& sources, const SourceDetails& details) - { - return std::find_if(sources.begin(), sources.end(), GetSourceDetailsEquivalencePredicate(details)); - } - - // Finds a source equivalent to the one specified. - template - typename std::vector::iterator FindSource(std::vector& sources, const SourceDetails& details) - { - return std::find_if(sources.begin(), sources.end(), GetSourceDetailsEquivalencePredicate(details)); - } - - // Gets the available version of an installed package. - // If requested, checks that the installed version is available and reports a warning if it is not. - std::shared_ptr GetAvailableVersionForInstalledPackage( - Execution::Context& context, - std::shared_ptr package, - Utility::LocIndView version, - Utility::LocIndView channel, - bool checkVersion) - { - std::shared_ptr availableVersions = GetAvailableVersionsForInstalledVersion(package); - - if (!checkVersion) - { - return availableVersions->GetLatestVersion(); - } - - auto availablePackageVersion = availableVersions->GetVersion({ "", version, channel }); - if (!availablePackageVersion) - { - availablePackageVersion = availableVersions->GetLatestVersion(); - if (availablePackageVersion) - { - // Warn installed version is not available. - AICLI_LOG( - CLI, - Info, - << "Installed package version is not available." - << " Package Id [" << availablePackageVersion->GetProperty(PackageVersionProperty::Id) << "], Version [" << version << "], Channel [" << channel << "]" - << ". Found Version [" << availablePackageVersion->GetProperty(PackageVersionProperty::Version) << "], Channel [" << availablePackageVersion->GetProperty(PackageVersionProperty::Version) << "]"); - context.Reporter.Warn() << Resource::String::InstalledPackageVersionNotAvailable(availablePackageVersion->GetProperty(PackageVersionProperty::Id), version, channel) << std::endl; - } - } - - return availablePackageVersion; - } - } - - void SelectVersionsToExport(Execution::Context& context) - { - const auto& searchResult = context.Get(); - const bool includeVersions = context.Args.Contains(Execution::Args::Type::IncludeVersions); - PackageCollection exportedPackages; - exportedPackages.ClientVersion = Runtime::GetClientVersion().get(); - auto& exportedSources = exportedPackages.Sources; - for (const auto& packageMatch : searchResult.Matches) - { - auto installedPackageVersion = GetInstalledVersion(packageMatch.Package); - auto version = installedPackageVersion->GetProperty(PackageVersionProperty::Version); - auto channel = installedPackageVersion->GetProperty(PackageVersionProperty::Channel); - - // Find an available version of this package to determine its source. - auto availablePackageVersion = GetAvailableVersionForInstalledPackage(context, packageMatch.Package, version, channel, includeVersions); - if (!availablePackageVersion) - { - // Report package not found and move to next package. - AICLI_LOG(CLI, Warning, << "No available version of package [" << installedPackageVersion->GetProperty(PackageVersionProperty::Name) << "] was found to export"); - context.Reporter.Warn() << Resource::String::InstalledPackageNotAvailable(installedPackageVersion->GetProperty(PackageVersionProperty::Name)) << std::endl; - continue; - } - - const auto& sourceDetails = availablePackageVersion->GetSource().GetDetails(); - AICLI_LOG(CLI, Info, - << "Installed package is available. Package Id [" << availablePackageVersion->GetProperty(PackageVersionProperty::Id) << "], Source [" << sourceDetails.Identifier << "]"); - - if (!availablePackageVersion->GetManifest().DefaultLocalization.Get().empty()) - { - // Report that the package requires accepting license terms - AICLI_LOG(CLI, Warning, << "Package [" << installedPackageVersion->GetProperty(PackageVersionProperty::Name) << "] requires license agreement to install"); - context.Reporter.Warn() << Resource::String::ExportedPackageRequiresLicenseAgreement(installedPackageVersion->GetProperty(PackageVersionProperty::Name)) << std::endl; - } - - // Find the exported source for this package - auto sourceItr = FindSource(exportedSources, sourceDetails); - if (sourceItr == exportedSources.end()) - { - exportedSources.emplace_back(sourceDetails); - sourceItr = std::prev(exportedSources.end()); - } - - // Take the Id from the available package because that is the one used in the source, - // but take the exported version from the installed package if needed. - PackageCollection::Package exportPackage; - exportPackage.Id = availablePackageVersion->GetProperty(PackageVersionProperty::Id); - - const auto& installedMetadata = installedPackageVersion->GetMetadata(); - - auto locationItr = installedMetadata.find(PackageVersionMetadata::InstalledLocation); - if (locationItr != installedMetadata.end()) - { - exportPackage.InstalledLocation = Utility::ConvertToUTF16(locationItr->second); - } - - auto overrideItr = installedMetadata.find(PackageVersionMetadata::InitialOverrideArguments); - if (overrideItr != installedMetadata.end()) - { - exportPackage.InitialOverrideArgs = overrideItr->second; - } - - auto customItr = installedMetadata.find(PackageVersionMetadata::InitialCustomSwitches); - if (customItr != installedMetadata.end()) - { - exportPackage.InitialCustomSwitches = customItr->second; - } - - if (includeVersions) - { - exportPackage.VersionAndChannel = { version.get(), channel.get() }; - } - - sourceItr->Packages.emplace_back(std::move(exportPackage)); - } - - context.Add(std::move(exportedPackages)); - } - - void WriteImportFile(Execution::Context& context) - { - auto packages = PackagesJson::CreateJson(context.Get()); - - std::filesystem::path outputFilePath{ context.Args.GetArg(Execution::Args::Type::OutputFile) }; - - // GetFileAttributesW returns INVALID_FILE_ATTRIBUTES for nonexistent files, so no separate exists() check is needed. - DWORD attrs = GetFileAttributesW(outputFilePath.c_str()); - bool isHidden = (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_HIDDEN)); - - // Open the file directly without changing its attributes: - // - For an existing hidden file, use TRUNCATE_EXISTING to clear its content while preserving its attributes. - // - Otherwise, use CREATE_ALWAYS to create a new file or overwrite an existing one. - DWORD creationDisposition = isHidden ? TRUNCATE_EXISTING : CREATE_ALWAYS; - wil::unique_hfile fileHandle{ CreateFileW(outputFilePath.c_str(), GENERIC_WRITE, 0, nullptr, creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr) }; - THROW_LAST_ERROR_IF(!fileHandle); - - Json::StreamWriterBuilder writerBuilder; - std::string jsonContent = Json::writeString(writerBuilder, packages); - Filesystem::WriteStringToFile(fileHandle.get(), jsonContent); - } - - void ReadImportFile(Execution::Context& context) - { - std::ifstream importFile(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ImportFile))); - THROW_LAST_ERROR_IF(importFile.fail()); - - Json::Value jsonRoot; - Json::CharReaderBuilder builder; - Json::String errors; - if (!Json::parseFromStream(builder, importFile, &jsonRoot, &errors)) - { - AICLI_LOG(CLI, Error, << "Failed to read JSON: " << errors); - context.Reporter.Error() << Resource::String::InvalidJsonFile << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); - } - - PackagesJson::ParseResult parseResult = PackagesJson::TryParseJson(jsonRoot); - if (parseResult.Result != PackagesJson::ParseResult::Type::Success) - { - context.Reporter.Error() << Resource::String::InvalidJsonFile << std::endl; - if (parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema || - parseResult.Result == PackagesJson::ParseResult::Type::UnrecognizedSchema) - { - context.Reporter.Error() << Resource::String::ImportFileHasInvalidSchema << std::endl; - } - else if (parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed) - { - context.Reporter.Error() << parseResult.Errors << std::endl; - } - - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); - } - - PackageCollection& packages = parseResult.Packages; - if (packages.Sources.empty()) - { - AICLI_LOG(CLI, Warning, << "No packages to install"); - context.Reporter.Info() << Resource::String::NoPackagesFoundInImportFile << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); - } - - if (context.Args.Contains(Execution::Args::Type::IgnoreVersions)) - { - // Strip out all the version information as we don't need it. - for (auto& source : packages.Sources) - { - for (auto& package : source.Packages) - { - package.VersionAndChannel = {}; - } - } - } - - context.Add(std::move(packages)); - } - - void OpenSourcesForImport(Execution::Context& context) - { - auto availableSources = Repository::Source::GetCurrentSources(); - for (auto& requiredSource : context.Get().Sources) - { - // Find the installed source matching the one described in the collection. - AICLI_LOG(CLI, Info, << "Looking for source [" << requiredSource.Details.Identifier << "]"); - auto matchingSource = FindSource(availableSources, requiredSource.Details); - if (matchingSource != availableSources.end()) - { - requiredSource.Details.Name = matchingSource->Name; - } - else - { - AICLI_LOG(CLI, Error, << "Missing required source: " << requiredSource.Details.Name); - context.Reporter.Warn() - << Resource::String::ImportSourceNotInstalled(Utility::LocIndView{ requiredSource.Details.Name }) - << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); - } - - context << Workflow::OpenNamedSourceForSources(requiredSource.Details.Name); - if (context.IsTerminated()) - { - return; - } - } - } - - void GetSearchRequestsForImport(Execution::Context& context) - { - const auto& sources = context.Get(); - std::vector> packageSubContexts; - - // Look for the packages needed from each source independently. - // If a package is available from multiple sources, this ensures we will get it from the right one. - for (auto& requiredSource : context.Get().Sources) - { - // Find the required source among the open sources. This must exist as we already found them. - auto sourceItr = FindSource(sources, requiredSource.Details); - if (sourceItr == sources.end()) - { - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); - } - - // Search for all the packages in the source. - // Each search is done in a sub context to search everything regardless of previous failures. - Repository::Source source{ context.Get(), *sourceItr, CompositeSearchBehavior::AllPackages }; - AICLI_LOG(CLI, Info, << "Identifying packages requested from source [" << requiredSource.Details.Identifier << "]"); - for (const auto& packageRequest : requiredSource.Packages) - { - AICLI_LOG(CLI, Info, << "Searching for package [" << packageRequest.Id << "]"); - - // Search for the current package - SearchRequest searchRequest; - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, packageRequest.Id.get())); - - auto searchContextPtr = context.CreateSubContext(); - Execution::Context& searchContext = *searchContextPtr; - auto previousThreadGlobals = searchContext.SetForCurrentThread(); - - searchContext.Add(source); - searchContext.Add(std::move(searchRequest)); - - if (packageRequest.Scope != Manifest::ScopeEnum::Unknown) - { - // TODO: In the future, it would be better to not have to convert back and forth from a string - searchContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(packageRequest.Scope)); - } - - auto versionString = packageRequest.VersionAndChannel.GetVersion().ToString(); - if (!versionString.empty()) - { - searchContext.Args.AddArg(Execution::Args::Type::Version, versionString); - } - - auto channelString = packageRequest.VersionAndChannel.GetChannel().ToString(); - if (!channelString.empty()) - { - searchContext.Args.AddArg(Execution::Args::Type::Channel, channelString); - } - - if (!packageRequest.InitialOverrideArgs.empty()) - { - searchContext.Args.AddArg(Execution::Args::Type::Override, packageRequest.InitialOverrideArgs); - } - - if (!packageRequest.InitialCustomSwitches.empty()) - { - searchContext.Args.AddArg(Execution::Args::Type::CustomSwitches, packageRequest.InitialCustomSwitches); - } - - packageSubContexts.emplace_back(std::move(searchContextPtr)); - } - } - - context.Add(std::move(packageSubContexts)); - } - - void InstallImportedPackages(Execution::Context& context) - { - // Inform all dependencies here. During SubContexts processing, dependencies are ignored. - auto& packageSubContexts = context.Get(); - Manifest::DependencyList allDependencies; - for (auto& packageContext : packageSubContexts) - { - allDependencies.Add(packageContext->Get().value().Dependencies); - } - context.Add(allDependencies); - - context << - ReportDependencies(Resource::String::ImportCommandReportDependencies) << - ProcessMultiplePackages( - Resource::String::ImportCommandReportDependencies, APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED, ProcessMultiplePackages::Flags::IgnoreDependencies); - - if (context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED) - { - context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "InstallFlow.h" +#include "ImportExportFlow.h" +#include "UpdateFlow.h" +#include "PackageCollection.h" +#include "DependenciesFlow.h" +#include "WorkflowBase.h" +#include +#include +#include +#include + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::Repository; + + namespace + { + SourceDetails GetSourceDetails(const SourceDetails& source) + { + return source; + } + + SourceDetails GetSourceDetails(const PackageCollection::Source& source) + { + return source.Details; + } + + SourceDetails GetSourceDetails(const Repository::Source& source) + { + return source.GetDetails(); + } + + // Creates a predicate that determines whether a source matches a description in a SourceDetails. + template + std::function GetSourceDetailsEquivalencePredicate(const SourceDetails& details) + { + return [&](const T& source) + { + SourceDetails sourceDetails = GetSourceDetails(source); + return sourceDetails.Type == details.Type && sourceDetails.Identifier == details.Identifier; + }; + } + + // Finds a source equivalent to the one specified. + template + typename std::vector::const_iterator FindSource(const std::vector& sources, const SourceDetails& details) + { + return std::find_if(sources.begin(), sources.end(), GetSourceDetailsEquivalencePredicate(details)); + } + + // Finds a source equivalent to the one specified. + template + typename std::vector::iterator FindSource(std::vector& sources, const SourceDetails& details) + { + return std::find_if(sources.begin(), sources.end(), GetSourceDetailsEquivalencePredicate(details)); + } + + // Gets the available version of an installed package. + // If requested, checks that the installed version is available and reports a warning if it is not. + std::shared_ptr GetAvailableVersionForInstalledPackage( + Execution::Context& context, + std::shared_ptr package, + Utility::LocIndView version, + Utility::LocIndView channel, + bool checkVersion) + { + std::shared_ptr availableVersions = GetAvailableVersionsForInstalledVersion(package); + + if (!checkVersion) + { + return availableVersions->GetLatestVersion(); + } + + auto availablePackageVersion = availableVersions->GetVersion({ "", version, channel }); + if (!availablePackageVersion) + { + availablePackageVersion = availableVersions->GetLatestVersion(); + if (availablePackageVersion) + { + // Warn installed version is not available. + AICLI_LOG( + CLI, + Info, + << "Installed package version is not available." + << " Package Id [" << availablePackageVersion->GetProperty(PackageVersionProperty::Id) << "], Version [" << version << "], Channel [" << channel << "]" + << ". Found Version [" << availablePackageVersion->GetProperty(PackageVersionProperty::Version) << "], Channel [" << availablePackageVersion->GetProperty(PackageVersionProperty::Version) << "]"); + context.Reporter.Warn() << Resource::String::InstalledPackageVersionNotAvailable(availablePackageVersion->GetProperty(PackageVersionProperty::Id), version, channel) << std::endl; + } + } + + return availablePackageVersion; + } + } + + void SelectVersionsToExport(Execution::Context& context) + { + const auto& searchResult = context.Get(); + const bool includeVersions = context.Args.Contains(Execution::Args::Type::IncludeVersions); + PackageCollection exportedPackages; + exportedPackages.ClientVersion = Runtime::GetClientVersion().get(); + auto& exportedSources = exportedPackages.Sources; + for (const auto& packageMatch : searchResult.Matches) + { + auto installedPackageVersion = GetInstalledVersion(packageMatch.Package); + auto version = installedPackageVersion->GetProperty(PackageVersionProperty::Version); + auto channel = installedPackageVersion->GetProperty(PackageVersionProperty::Channel); + + // Find an available version of this package to determine its source. + auto availablePackageVersion = GetAvailableVersionForInstalledPackage(context, packageMatch.Package, version, channel, includeVersions); + if (!availablePackageVersion) + { + // Report package not found and move to next package. + AICLI_LOG(CLI, Warning, << "No available version of package [" << installedPackageVersion->GetProperty(PackageVersionProperty::Name) << "] was found to export"); + context.Reporter.Warn() << Resource::String::InstalledPackageNotAvailable(installedPackageVersion->GetProperty(PackageVersionProperty::Name)) << std::endl; + continue; + } + + const auto& sourceDetails = availablePackageVersion->GetSource().GetDetails(); + AICLI_LOG(CLI, Info, + << "Installed package is available. Package Id [" << availablePackageVersion->GetProperty(PackageVersionProperty::Id) << "], Source [" << sourceDetails.Identifier << "]"); + + if (!availablePackageVersion->GetManifest().DefaultLocalization.Get().empty()) + { + // Report that the package requires accepting license terms + AICLI_LOG(CLI, Warning, << "Package [" << installedPackageVersion->GetProperty(PackageVersionProperty::Name) << "] requires license agreement to install"); + context.Reporter.Warn() << Resource::String::ExportedPackageRequiresLicenseAgreement(installedPackageVersion->GetProperty(PackageVersionProperty::Name)) << std::endl; + } + + // Find the exported source for this package + auto sourceItr = FindSource(exportedSources, sourceDetails); + if (sourceItr == exportedSources.end()) + { + exportedSources.emplace_back(sourceDetails); + sourceItr = std::prev(exportedSources.end()); + } + + // Take the Id from the available package because that is the one used in the source, + // but take the exported version from the installed package if needed. + PackageCollection::Package exportPackage; + exportPackage.Id = availablePackageVersion->GetProperty(PackageVersionProperty::Id); + + const auto& installedMetadata = installedPackageVersion->GetMetadata(); + + auto locationItr = installedMetadata.find(PackageVersionMetadata::InstalledLocation); + if (locationItr != installedMetadata.end()) + { + exportPackage.InstalledLocation = Utility::ConvertToUTF16(locationItr->second); + } + + auto overrideItr = installedMetadata.find(PackageVersionMetadata::InitialOverrideArguments); + if (overrideItr != installedMetadata.end()) + { + exportPackage.InitialOverrideArgs = overrideItr->second; + } + + auto customItr = installedMetadata.find(PackageVersionMetadata::InitialCustomSwitches); + if (customItr != installedMetadata.end()) + { + exportPackage.InitialCustomSwitches = customItr->second; + } + + if (includeVersions) + { + exportPackage.VersionAndChannel = { version.get(), channel.get() }; + } + + sourceItr->Packages.emplace_back(std::move(exportPackage)); + } + + context.Add(std::move(exportedPackages)); + } + + void WriteImportFile(Execution::Context& context) + { + auto packages = PackagesJson::CreateJson(context.Get()); + + std::filesystem::path outputFilePath{ context.Args.GetArg(Execution::Args::Type::OutputFile) }; + + // GetFileAttributesW returns INVALID_FILE_ATTRIBUTES for nonexistent files, so no separate exists() check is needed. + DWORD attrs = GetFileAttributesW(outputFilePath.c_str()); + bool isHidden = (attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_HIDDEN)); + + // Open the file directly without changing its attributes: + // - For an existing hidden file, use TRUNCATE_EXISTING to clear its content while preserving its attributes. + // - Otherwise, use CREATE_ALWAYS to create a new file or overwrite an existing one. + DWORD creationDisposition = isHidden ? TRUNCATE_EXISTING : CREATE_ALWAYS; + wil::unique_hfile fileHandle{ CreateFileW(outputFilePath.c_str(), GENERIC_WRITE, 0, nullptr, creationDisposition, FILE_ATTRIBUTE_NORMAL, nullptr) }; + THROW_LAST_ERROR_IF(!fileHandle); + + Json::StreamWriterBuilder writerBuilder; + std::string jsonContent = Json::writeString(writerBuilder, packages); + Filesystem::WriteStringToFile(fileHandle.get(), jsonContent); + } + + void ReadImportFile(Execution::Context& context) + { + std::ifstream importFile(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::ImportFile))); + THROW_LAST_ERROR_IF(importFile.fail()); + + Json::Value jsonRoot; + Json::CharReaderBuilder builder; + Json::String errors; + if (!Json::parseFromStream(builder, importFile, &jsonRoot, &errors)) + { + AICLI_LOG(CLI, Error, << "Failed to read JSON: " << errors); + context.Reporter.Error() << Resource::String::InvalidJsonFile << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); + } + + PackagesJson::ParseResult parseResult = PackagesJson::TryParseJson(jsonRoot); + if (parseResult.Result != PackagesJson::ParseResult::Type::Success) + { + context.Reporter.Error() << Resource::String::InvalidJsonFile << std::endl; + if (parseResult.Result == PackagesJson::ParseResult::Type::MissingSchema || + parseResult.Result == PackagesJson::ParseResult::Type::UnrecognizedSchema) + { + context.Reporter.Error() << Resource::String::ImportFileHasInvalidSchema << std::endl; + } + else if (parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed) + { + context.Reporter.Error() << parseResult.Errors << std::endl; + } + + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); + } + + PackageCollection& packages = parseResult.Packages; + if (packages.Sources.empty()) + { + AICLI_LOG(CLI, Warning, << "No packages to install"); + context.Reporter.Info() << Resource::String::NoPackagesFoundInImportFile << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); + } + + if (context.Args.Contains(Execution::Args::Type::IgnoreVersions)) + { + // Strip out all the version information as we don't need it. + for (auto& source : packages.Sources) + { + for (auto& package : source.Packages) + { + package.VersionAndChannel = {}; + } + } + } + + context.Add(std::move(packages)); + } + + void OpenSourcesForImport(Execution::Context& context) + { + auto availableSources = Repository::Source::GetCurrentSources(); + for (auto& requiredSource : context.Get().Sources) + { + // Find the installed source matching the one described in the collection. + AICLI_LOG(CLI, Info, << "Looking for source [" << requiredSource.Details.Identifier << "]"); + auto matchingSource = FindSource(availableSources, requiredSource.Details); + if (matchingSource != availableSources.end()) + { + requiredSource.Details.Name = matchingSource->Name; + } + else + { + AICLI_LOG(CLI, Error, << "Missing required source: " << requiredSource.Details.Name); + context.Reporter.Warn() + << Resource::String::ImportSourceNotInstalled(Utility::LocIndView{ requiredSource.Details.Name }) + << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); + } + + context << Workflow::OpenNamedSourceForSources(requiredSource.Details.Name); + if (context.IsTerminated()) + { + return; + } + } + } + + void GetSearchRequestsForImport(Execution::Context& context) + { + const auto& sources = context.Get(); + std::vector> packageSubContexts; + + // Look for the packages needed from each source independently. + // If a package is available from multiple sources, this ensures we will get it from the right one. + for (auto& requiredSource : context.Get().Sources) + { + // Find the required source among the open sources. This must exist as we already found them. + auto sourceItr = FindSource(sources, requiredSource.Details); + if (sourceItr == sources.end()) + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR); + } + + // Search for all the packages in the source. + // Each search is done in a sub context to search everything regardless of previous failures. + Repository::Source source{ context.Get(), *sourceItr, CompositeSearchBehavior::AllPackages }; + AICLI_LOG(CLI, Info, << "Identifying packages requested from source [" << requiredSource.Details.Identifier << "]"); + for (const auto& packageRequest : requiredSource.Packages) + { + AICLI_LOG(CLI, Info, << "Searching for package [" << packageRequest.Id << "]"); + + // Search for the current package + SearchRequest searchRequest; + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, packageRequest.Id.get())); + + auto searchContextPtr = context.CreateSubContext(); + Execution::Context& searchContext = *searchContextPtr; + auto previousThreadGlobals = searchContext.SetForCurrentThread(); + + searchContext.Add(source); + searchContext.Add(std::move(searchRequest)); + + if (packageRequest.Scope != Manifest::ScopeEnum::Unknown) + { + // TODO: In the future, it would be better to not have to convert back and forth from a string + searchContext.Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(packageRequest.Scope)); + } + + auto versionString = packageRequest.VersionAndChannel.GetVersion().ToString(); + if (!versionString.empty()) + { + searchContext.Args.AddArg(Execution::Args::Type::Version, versionString); + } + + auto channelString = packageRequest.VersionAndChannel.GetChannel().ToString(); + if (!channelString.empty()) + { + searchContext.Args.AddArg(Execution::Args::Type::Channel, channelString); + } + + if (!packageRequest.InitialOverrideArgs.empty()) + { + searchContext.Args.AddArg(Execution::Args::Type::Override, packageRequest.InitialOverrideArgs); + } + + if (!packageRequest.InitialCustomSwitches.empty()) + { + searchContext.Args.AddArg(Execution::Args::Type::CustomSwitches, packageRequest.InitialCustomSwitches); + } + + packageSubContexts.emplace_back(std::move(searchContextPtr)); + } + } + + context.Add(std::move(packageSubContexts)); + } + + void InstallImportedPackages(Execution::Context& context) + { + // Inform all dependencies here. During SubContexts processing, dependencies are ignored. + auto& packageSubContexts = context.Get(); + Manifest::DependencyList allDependencies; + for (auto& packageContext : packageSubContexts) + { + allDependencies.Add(packageContext->Get().value().Dependencies); + } + context.Add(allDependencies); + + context << + ReportDependencies(Resource::String::ImportCommandReportDependencies) << + ProcessMultiplePackages( + Resource::String::ImportCommandReportDependencies, APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED, ProcessMultiplePackages::Flags::IgnoreDependencies); + + if (context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED) + { + context.Reporter.Error() << Resource::String::ImportInstallFailed << std::endl; + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp index 252d76125a..e52234a139 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.cpp @@ -1,1052 +1,1052 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "InstallFlow.h" -#include "DownloadFlow.h" -#include "FontFlow.h" -#include "UninstallFlow.h" -#include "UpdateFlow.h" -#include "ResumeFlow.h" -#include "ShowFlow.h" -#include "Resources.h" -#include "ShellExecuteInstallerHandler.h" -#include "MSStoreInstallerHandler.h" -#include "MsiInstallFlow.h" -#include "ArchiveFlow.h" -#include "PortableFlow.h" -#include "WorkflowBase.h" -#include "DependenciesFlow.h" -#include "PromptFlow.h" -#include "SourceFlow.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Foundation::Collections; -using namespace winrt::Windows::Management::Deployment; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Registry::Environment; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - bool MightWriteToARP(InstallerTypeEnum type) - { - switch (type) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Wix: - return true; - default: - return false; - } - } - - bool ShouldUseDirectMSIInstall(InstallerTypeEnum type, bool isSilentInstall) - { - return DoesInstallerTypeUseMsiProperties(type) && - (isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI)); - } - - bool ShouldErrorForUnsupportedArgument(UnsupportedArgumentEnum arg) - { - switch (arg) - { - case UnsupportedArgumentEnum::Location: - return true; - default: - return false; - } - } - - Execution::Args::Type GetUnsupportedArgumentType(UnsupportedArgumentEnum unsupportedArgument) - { - Execution::Args::Type execArg; - - switch (unsupportedArgument) - { - case UnsupportedArgumentEnum::Log: - execArg = Execution::Args::Type::Log; - break; - case UnsupportedArgumentEnum::Location: - execArg = Execution::Args::Type::InstallLocation; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return execArg; - } - - struct ExpectedReturnCode - { - ExpectedReturnCode(ExpectedReturnCodeEnum installerReturnCode, HRESULT hr, Resource::StringId message) : - InstallerReturnCode(installerReturnCode), HResult(hr), Message(message) {} - - static ExpectedReturnCode GetExpectedReturnCode(ExpectedReturnCodeEnum returnCode) - { - switch (returnCode) - { - case ExpectedReturnCodeEnum::PackageInUse: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, Resource::String::InstallFlowReturnCodePackageInUse); - case ExpectedReturnCodeEnum::PackageInUseByApplication: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION, Resource::String::InstallFlowReturnCodePackageInUseByApplication); - case ExpectedReturnCodeEnum::InstallInProgress: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS, Resource::String::InstallFlowReturnCodeInstallInProgress); - case ExpectedReturnCodeEnum::FileInUse: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE, Resource::String::InstallFlowReturnCodeFileInUse); - case ExpectedReturnCodeEnum::MissingDependency: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY, Resource::String::InstallFlowReturnCodeMissingDependency); - case ExpectedReturnCodeEnum::DiskFull: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL, Resource::String::InstallFlowReturnCodeDiskFull); - case ExpectedReturnCodeEnum::InsufficientMemory: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY, Resource::String::InstallFlowReturnCodeInsufficientMemory); - case ExpectedReturnCodeEnum::InvalidParameter: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER, Resource::String::InstallFlowReturnCodeInvalidParameter); - case ExpectedReturnCodeEnum::NoNetwork: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK, Resource::String::InstallFlowReturnCodeNoNetwork); - case ExpectedReturnCodeEnum::ContactSupport: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT, Resource::String::InstallFlowReturnCodeContactSupport); - case ExpectedReturnCodeEnum::RebootRequiredToFinish: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH, Resource::String::InstallFlowReturnCodeRebootRequiredToFinish); - case ExpectedReturnCodeEnum::RebootRequiredForInstall: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, Resource::String::InstallFlowReturnCodeRebootRequiredForInstall); - case ExpectedReturnCodeEnum::RebootInitiated: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED, Resource::String::InstallFlowReturnCodeRebootInitiated); - case ExpectedReturnCodeEnum::CancelledByUser: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER, Resource::String::InstallFlowReturnCodeCancelledByUser); - case ExpectedReturnCodeEnum::AlreadyInstalled: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED, Resource::String::InstallFlowReturnCodeAlreadyInstalled); - case ExpectedReturnCodeEnum::Downgrade: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE, Resource::String::InstallFlowReturnCodeDowngrade); - case ExpectedReturnCodeEnum::BlockedByPolicy: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY, Resource::String::InstallFlowReturnCodeBlockedByPolicy); - case ExpectedReturnCodeEnum::SystemNotSupported: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, Resource::String::InstallFlowReturnCodeSystemNotSupported); - case ExpectedReturnCodeEnum::Custom: - return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR, Resource::String::InstallFlowReturnCodeCustomError); - default: - THROW_HR(E_UNEXPECTED); - } - } - - ExpectedReturnCodeEnum InstallerReturnCode; - HRESULT HResult; - Resource::StringId Message; - }; - - void CheckForOnlyDependencies(Execution::Context& context) - { - if (context.Args.Contains(Execution::Args::Type::DependenciesOnly)) - { - context.Reporter.Info() << Resource::String::DependenciesOnlyMessage << std::endl; - // We want the context to terminate, but successfully. - context.SetTerminationHR(S_OK); - } - } - } - - namespace details - { - // Runs the installer via ShellExecute. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void ShellExecuteInstall(Execution::Context& context) - { - context << - GetInstallerArgs << - ShellExecuteInstallImpl << - ReportInstallerResult("ShellExecute"sv, APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED); - } - - // Runs an MSI installer directly via MSI APIs. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void DirectMSIInstall(Execution::Context& context) - { - context << - GetInstallerArgs << - DirectMSIInstallImpl << - ReportInstallerResult("MsiInstallProduct"sv, APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED); - } - - // Deploys the MSIX. - // Required Args: None - // Inputs: Manifest?, Installer || InstallerPath - // Outputs: None - void MsixInstall(Execution::Context& context) - { - std::string uri; - Deployment::Options deploymentOptions; - if (context.Contains(Execution::Data::InstallerPath)) - { - uri = context.Get().u8string(); - } - else - { - uri = context.Get()->Url; - deploymentOptions.ExpectedDigests = context.Get(); - } - - deploymentOptions.SkipReputationCheck = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted); - - bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; - - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - - bool registrationDeferred = false; - - try - { - registrationDeferred = context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) - { - if (isMachineScope) - { - return Deployment::AddPackageMachineScope(uri, deploymentOptions, callback); - } - else - { - return Deployment::AddPackageWithDeferredFallback(uri, deploymentOptions, callback); - } - }); - } - catch (const wil::ResultException& re) - { - // There was a bug in the deployment provision API when called in a packaged context, - // fixed in 10.0.26100.0. On older OS versions, convert any failure under these conditions - // to the error that was previously always returned. - if (isMachineScope && Runtime::IsRunningInPackagedContext() && - !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) - { - context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; - context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - AICLI_LOG(CLI, Error, << "Device wide install for msix type is not supported in packaged context on this OS version. Error: " << re.GetErrorCode()); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); - } - - context.Add(re.GetErrorCode()); - context << ReportInstallerResult("MSIX"sv, re.GetErrorCode(), /* isHResult */ true); - return; - } - - if (registrationDeferred) - { - context.Reporter.Warn() << Resource::String::InstallFlowRegistrationDeferred << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; - } - } - - // Runs the flow for installing a Portable package. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void PortableInstall(Execution::Context& context) - { - context << - InitializePortableInstaller << - VerifyPackageAndSourceMatch << - PortableInstallImpl << - ReportInstallerResult("Portable"sv, APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED, true); - } - - // Runs the flow for installing a package from an archive. - // Required Args: None - // Inputs: Installer, InstallerPath, Manifest - // Outputs: None - void ArchiveInstall(Execution::Context& context) - { - context << - ScanArchiveFromLocalManifest << - ExtractFilesFromArchive << - VerifyAndSetNestedInstaller << - ExecuteInstallerForType(context.Get().value().NestedInstallerType); - } - - // Runs the flow for installing a font package. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void FontInstall(Execution::Context& context) - { - context << - GetInstallerArgs << - FontInstallImpl << - ReportInstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, true); - } - } - - bool ExemptFromSingleInstallLocking(InstallerTypeEnum type) - { - switch (type) - { - // MSStore installs are always MSIX based; MSIX installs are safe to run in parallel. - case InstallerTypeEnum::Msix: - case InstallerTypeEnum::MSStore: - return true; - default: - return false; - } - } - - void EnsureApplicableInstaller(Execution::Context& context) - { - const auto& installer = context.Get(); - - if (!installer.has_value()) - { - context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); - } - - context << - EnsureSupportForDownload << - EnsureSupportForInstall; - } - - void CheckForUnsupportedArgs(Execution::Context& context) - { - bool messageDisplayed = false; - const auto& unsupportedArgs = context.Get()->UnsupportedArguments; - for (auto unsupportedArg : unsupportedArgs) - { - const auto& unsupportedArgType = GetUnsupportedArgumentType(unsupportedArg); - if (context.Args.Contains(unsupportedArgType)) - { - if (!messageDisplayed) - { - context.Reporter.Warn() << Resource::String::UnsupportedArgument << std::endl; - messageDisplayed = true; - } - - const auto& executingCommand = context.GetExecutingCommand(); - if (executingCommand != nullptr) - { - const auto& commandArguments = executingCommand->GetArguments(); - for (const auto& argument : commandArguments) - { - if (unsupportedArgType == argument.ExecArgType()) - { - const auto& usageString = argument.GetUsageString(); - if (ShouldErrorForUnsupportedArgument(unsupportedArg)) - { - context.Reporter.Error() << usageString << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); - } - else - { - context.Reporter.Warn() << usageString << std::endl; - break; - } - } - } - } - } - } - } - - void ShowInstallationDisclaimer(Execution::Context& context) - { - auto installerType = context.Get().value().EffectiveInstallerType(); - - if (installerType == InstallerTypeEnum::MSStore) - { - context.Reporter.Info() << Execution::PromptEmphasis << Resource::String::InstallationDisclaimerMSStore << std::endl; - } - else - { - context.Reporter.Info() << - Resource::String::InstallationDisclaimer1 << std::endl << - Resource::String::InstallationDisclaimer2 << std::endl; - } - } - - void DisplayInstallationNotes(Execution::Context& context) - { - if (!Settings::User().Get()) - { - const auto& manifest = context.Get(); - auto installationNotes = manifest.CurrentLocalization.Get(); - - if (!installationNotes.empty()) - { - context.Reporter.Info() << Resource::String::Notes(installationNotes) << std::endl; - } - } - } - - void ExecuteInstallerForType::operator()(Execution::Context& context) const - { - bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); - UpdateBehaviorEnum updateBehavior = context.Get().value().UpdateBehavior; - bool doUninstallPrevious = isUpdate && (updateBehavior == UpdateBehaviorEnum::UninstallPrevious || context.Args.Contains(Execution::Args::Type::UninstallPrevious)); - - Synchronization::CrossProcessInstallLock lock; - if (!ExemptFromSingleInstallLocking(m_installerType)) - { - // Acquire install lock; if the operation is cancelled it will return false so we will also return. - if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) - { - callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); - return lock.Acquire(callback); - })) - { - AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire install lock due to cancellation"); - return; - } - } - - switch (m_installerType) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Wix: - if (doUninstallPrevious) - { - context << - GetUninstallInfo << - ExecuteUninstaller; - context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); - } - if (ShouldUseDirectMSIInstall(m_installerType, context.Args.Contains(Execution::Args::Type::Silent))) - { - context << details::DirectMSIInstall; - } - else - { - context << details::ShellExecuteInstall; - } - break; - case InstallerTypeEnum::Msix: - context << details::MsixInstall; - break; - case InstallerTypeEnum::MSStore: - context << - EnsureStorePolicySatisfied << - (isUpdate ? MSStoreUpdate : MSStoreInstall); - break; - case InstallerTypeEnum::Portable: - if (doUninstallPrevious) - { - context << - GetUninstallInfo << - ExecuteUninstaller; - context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); - } - context << details::PortableInstall; - break; - case InstallerTypeEnum::Zip: - context << details::ArchiveInstall; - break; - case InstallerTypeEnum::Font: - context << details::FontInstall; - break; - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context) - { - // Admin is required for machine scope install for installer types like portable, msix and msstore. - auto installerType = context.Get().value().EffectiveInstallerType(); - - if (Manifest::DoesInstallerTypeRequireAdminForMachineScopeInstall(installerType)) - { - Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); - if (scope == Manifest::ScopeEnum::Machine) - { - context << Workflow::EnsureRunningAsAdmin; - } - } - } - - void ExecuteInstaller(Execution::Context& context) - { - context << Workflow::ExecuteInstallerForType(context.Get().value().BaseInstallerType); - } - - void ReportInstallerResult::operator()(Execution::Context& context) const - { - bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); - - DWORD installResult = context.Get(); - const auto& additionalSuccessCodes = context.Get()->InstallerSuccessCodes; - if (installResult != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult) == additionalSuccessCodes.end())) - { - HRESULT terminationHR = m_hr; - const auto& expectedReturnCodes = context.Get()->ExpectedReturnCodes; - auto expectedReturnCodeItr = expectedReturnCodes.find(installResult); - if (expectedReturnCodeItr != expectedReturnCodes.end() && expectedReturnCodeItr->second.ReturnResponseEnum != ExpectedReturnCodeEnum::Unknown) - { - auto returnCode = ExpectedReturnCode::GetExpectedReturnCode(expectedReturnCodeItr->second.ReturnResponseEnum); - terminationHR = returnCode.HResult; - - switch (terminationHR) - { - case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH: - // REBOOT_REQUIRED_TO_FINISH is treated as a success since installation has completed but is pending a reboot. - context.SetFlags(ContextFlag::RebootRequired); - context.Reporter.Warn() << returnCode.Message << std::endl; - terminationHR = S_OK; - break; - case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL: - // REBOOT_REQUIRED_FOR_INSTALL is treated as an error since installation has not yet completed. - context.SetFlags(ContextFlag::RebootRequired); - // TODO: Add separate workflow to handle restart registration for resume. - context.SetFlags(ContextFlag::RegisterResume); - break; - } - - if (FAILED(terminationHR)) - { - context.Reporter.Error() << returnCode.Message << std::endl; - const auto& returnResponseUrl = expectedReturnCodeItr->second.ReturnResponseUrl; - if (!returnResponseUrl.empty()) - { - context.Reporter.Error() << Resource::String::RelatedLink << ' ' << returnResponseUrl << std::endl; - } - } - } - - if (FAILED(terminationHR)) - { - const auto& manifest = context.Get(); - - if (isRepair) - { - Logging::Telemetry().LogRepairFailure(manifest.Id, manifest.Version, m_installerType, installResult); - } - else - { - Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, m_installerType, installResult); - } - - if (m_isHResult) - { - context.Reporter.Error() - << Resource::String::InstallerFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(installResult) }) - << std::endl; - } - else - { - context.Reporter.Error() - << Resource::String::InstallerFailedWithCode(installResult) - << std::endl; - } - - // Show installer log path if exists - if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get())) - { - auto installerLogPath = Utility::LocIndString{ context.Get().u8string() }; - context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; - } - - AICLI_TERMINATE_CONTEXT(terminationHR); - } - } - else - { - if (isRepair) - { - context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; - } - } - } - - void ReportIdentityAndInstallationDisclaimer(Execution::Context& context) - { - context << - Workflow::ReportManifestIdentityWithVersion() << - Workflow::ShowInstallationDisclaimer; - } - - void InstallPackageInstaller(Execution::Context& context) - { - context << - Workflow::ReportExecutionStage(ExecutionStage::PreExecution) << - Workflow::SnapshotARPEntries << - Workflow::ReportExecutionStage(ExecutionStage::Execution) << - Workflow::ExecuteInstaller << - Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << - Workflow::ReportARPChanges << - Workflow::RecordInstall << - Workflow::ForceInstalledCacheUpdate << - Workflow::RemoveInstaller << - Workflow::DisplayInstallationNotes; - } - - void InstallDependencies(Execution::Context& context) - { - using Flags = ProcessMultiplePackages::Flags; - - if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) - { - context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl; - return; - } - - context << - GetDependenciesFromInstaller << - ReportDependencies(Resource::String::PackageRequiresDependencies) << - EnableWindowsFeaturesDependencies << - ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable); - } - - void DownloadPackageDependencies(Execution::Context& context) - { - using Flags = ProcessMultiplePackages::Flags; - - if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) - { - context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl; - return; - } - - context << - GetDependenciesFromInstaller << - ReportDependencies(Resource::String::PackageRequiresDependencies) << - CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) << - ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::DownloadOnly); - } - - void InstallSinglePackage(Execution::Context& context) - { - context << - Workflow::CheckForUnsupportedArgs << - Workflow::ReportIdentityAndInstallationDisclaimer << - Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << - Workflow::CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) << - Workflow::InstallDependencies << - CheckForOnlyDependencies << - Workflow::DownloadInstaller << - Workflow::InstallPackageInstaller << - Workflow::RegisterStartupAfterReboot(); - } - - void EnsureSupportForInstall(Execution::Context& context) - { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) - { - return; - } - - const auto& installer = context.Get(); - - // This check is only necessary for the Repair workflow when operating on an installer with RepairBehavior set to Installer. - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair)) - { - if (installer->RepairBehavior != RepairBehaviorEnum::Installer) - { - return; - } - - // At present, the installer repair behavior scenario is restricted to Exe, Inno, Nullsoft, and Burn installer types. - if (!DoesInstallerTypeRequireRepairBehaviorForRepair(installer->EffectiveInstallerType())) - { - return; - } - } - - // This installer cannot be run elevated, but we are running elevated. - // Implementation of de-elevation is complex; simply block for now. - if (installer->ElevationRequirement == ElevationRequirementEnum::ElevationProhibited && Runtime::IsRunningAsAdmin()) - { - AICLI_LOG(CLI, Error, << "The installer cannot be run from an administrator context."); - context.Reporter.Error() << Resource::String::InstallerProhibitsElevation << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION); - } - - // This installer cannot be used to upgrade the currently installed application - // Because the upgrade mechanism may be package-specific, simply block. - bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); - UpdateBehaviorEnum updateBehavior = installer->UpdateBehavior; - if (isUpdate && (updateBehavior == UpdateBehaviorEnum::Deny)) - { - AICLI_LOG(CLI, Error, << "Manifest specifies update behavior is denied. The attempt will be cancelled."); - context.Reporter.Error() << Resource::String::UpgradeBlockedByManifest << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED); - } - - context << - Workflow::EnsureRunningAsAdminForMachineScopeInstall << - Workflow::EnsureSupportForPortableInstall << - Workflow::EnsureValidNestedInstallerMetadataForArchiveInstall; - } - - ProcessMultiplePackages::ProcessMultiplePackages( - StringResource::StringId dependenciesReportMessage, - HRESULT resultOnFailure, - Flags flags, - std::vector&& ignorableInstallResults) : - WorkflowTask("ProcessMultiplePackages"), - m_dependenciesReportMessage(dependenciesReportMessage), - m_resultOnFailure(resultOnFailure), - m_ignorableInstallResults(std::move(ignorableInstallResults)) - { - // Inverted - m_ensurePackageAgreements = !WI_IsFlagSet(flags, Flags::SkipPackageAgreements); - - m_ignorePackageDependencies = WI_IsFlagSet(flags, Flags::IgnoreDependencies); - m_stopOnFailure = WI_IsFlagSet(flags, Flags::StopOnFailure); - m_refreshPathVariable = WI_IsFlagSet(flags, Flags::RefreshPathVariable); - m_downloadOnly = WI_IsFlagSet(flags, Flags::DownloadOnly); - m_dependenciesOnly = WI_IsFlagSet(flags, Flags::DependenciesOnly); - } - - void ProcessMultiplePackages::operator()(Execution::Context& context) const - { - if (!context.Contains(Execution::Data::PackageSubContexts)) - { - return; - } - - bool downloadInstallerOnly = m_downloadOnly ? true : WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly); - - // Show all prompts needed for every package before installing anything - context << Workflow::ShowPromptsForMultiplePackages(m_ensurePackageAgreements, downloadInstallerOnly); - - if (context.IsTerminated()) - { - return; - } - - auto& packageSubContexts = context.Get(); - - // Report dependencies - if (!m_ignorePackageDependencies) - { - DependencyList allDependencies; - - for (auto& packageContext : packageSubContexts) - { - allDependencies.Add(packageContext->Get().value().Dependencies); - } - - if (!allDependencies.Empty()) - { - if (downloadInstallerOnly) - { - context.Reporter.Info() << Resource::String::DependenciesFlowDownload << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::DependenciesFlowInstall << std::endl; - } - } - - context.Add(allDependencies); - context << Workflow::ReportDependencies(m_dependenciesReportMessage); - } - - bool allSucceeded = true; - size_t packagesCount = packageSubContexts.size(); - size_t packagesProgress = 0; - - if (m_dependenciesOnly) - { - context.Reporter.Info() << Resource::String::DependenciesOnlyMessage << std::endl; - } - - for (auto& packageContext : packageSubContexts) - { - packagesProgress++; - context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; - - // We want to do best effort to install all packages regardless of previous failures - Execution::Context& currentContext = *packageContext; - auto previousThreadGlobals = currentContext.SetForCurrentThread(); - - currentContext << Workflow::ReportIdentityAndInstallationDisclaimer; - - // Prevent individual exceptions from breaking out of the loop - try - { - // Handle dependencies if requested. - if (!m_ignorePackageDependencies && !downloadInstallerOnly) - { - currentContext << - Workflow::EnableWindowsFeaturesDependencies << - Workflow::CreateDependencySubContexts(m_dependenciesReportMessage) << - Workflow::ProcessMultiplePackages(m_dependenciesReportMessage, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable); - } - - if (!m_dependenciesOnly) - { - currentContext << Workflow::DownloadInstaller; - - if (!downloadInstallerOnly) - { - currentContext << Workflow::InstallPackageInstaller; - } - } - } - catch (...) - { - currentContext.SetTerminationHR(Workflow::HandleException(currentContext, std::current_exception())); - } - - if (m_refreshPathVariable) - { - if (RefreshPathVariableForCurrentProcess()) - { - AICLI_LOG(CLI, Info, << "Successfully refreshed process PATH environment variable."); - } - else - { - AICLI_LOG(CLI, Warning, << "Failed to refresh process PATH environment variable."); - context.Reporter.Warn() << Resource::String::FailedToRefreshPathWarning << std::endl; - } - } - - currentContext.Reporter.Info() << std::endl; - - if (currentContext.IsTerminated()) - { - if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) - { - // This means that the subcontext being terminated is due to an overall abort - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return; - } - - if (m_ignorableInstallResults.end() == std::find(m_ignorableInstallResults.begin(), m_ignorableInstallResults.end(), currentContext.GetTerminationHR())) - { - allSucceeded = false; - if (m_stopOnFailure) - { - break; - } - } - } - } - - if (!allSucceeded) - { - AICLI_TERMINATE_CONTEXT(m_resultOnFailure); - } - } - - void SnapshotARPEntries(Execution::Context& context) try - { - // Ensure that installer type might actually write to ARP, otherwise this is a waste of time - auto installer = context.Get(); - - if (installer && MightWriteToARP(installer->EffectiveInstallerType())) - { - Repository::Correlation::ARPCorrelationData data; - data.CapturePreInstallSnapshot(); - context.Add(std::move(data)); - } - } - CATCH_LOG() - - void ReportARPChanges(Execution::Context& context) try - { - if (!context.Contains(Execution::Data::ARPCorrelationData)) - { - return; - } - - // If the installer claims to have a PackageFamilyName, and that family name is currently registered for the user, - // let that be the correlated item and skip any attempt at further ARP correlation. - const auto& installer = context.Get(); - - if (installer && !installer->PackageFamilyName.empty() && Deployment::IsRegistered(installer->PackageFamilyName)) - { - return; - } - - const auto& manifest = context.Get(); - auto& arpCorrelationData = context.Get(); - - arpCorrelationData.CapturePostInstallSnapshot(); - auto correlationResult = arpCorrelationData.CorrelateForNewlyInstalled(manifest); - - // Store the ARP entry found to match the package to record it in the tracking catalog later - if (correlationResult.Package) - { - std::vector entries; - - auto metadata = correlationResult.Package->GetMetadata(); - - AppsAndFeaturesEntry baseEntry; - - // Display name and publisher are also available as multi properties, but - // for ARP there will always be only 0 or 1 values. - baseEntry.DisplayName = correlationResult.Package->GetProperty(PackageVersionProperty::Name).get(); - baseEntry.Publisher = correlationResult.Package->GetProperty(PackageVersionProperty::Publisher).get(); - baseEntry.DisplayVersion = correlationResult.Package->GetProperty(PackageVersionProperty::Version).get(); - baseEntry.InstallerType = Manifest::ConvertToInstallerTypeEnum(metadata[PackageVersionMetadata::InstalledType]); - - auto productCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - for (auto&& productCode : productCodes) - { - AppsAndFeaturesEntry entry = baseEntry; - entry.ProductCode = std::move(productCode).get(); - entries.push_back(std::move(entry)); - } - - auto upgradeCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::UpgradeCode); - for (auto&& upgradeCode : upgradeCodes) - { - AppsAndFeaturesEntry entry = baseEntry; - entry.UpgradeCode = std::move(upgradeCode).get(); - entries.push_back(std::move(entry)); - } - - context.Add(std::move(entries)); - } - - // We can only get the source identifier from an active source - std::string sourceIdentifier; - if (context.Contains(Execution::Data::PackageVersion)) - { - sourceIdentifier = context.Get()->GetProperty(PackageVersionProperty::SourceIdentifier); - } - - IPackageVersion::Metadata arpEntryMetadata; - if (correlationResult.Package) - { - arpEntryMetadata = correlationResult.Package->GetMetadata(); - } - - Logging::Telemetry().LogSuccessfulInstallARPChange( - sourceIdentifier, - manifest.Id, - manifest.Version, - manifest.Channel, - correlationResult.ChangesToARP, - correlationResult.MatchesInARP, - correlationResult.CountOfIntersectionOfChangesAndMatches, - correlationResult.Package ? static_cast(correlationResult.Package->GetProperty(PackageVersionProperty::Name)) : "", - correlationResult.Package ? static_cast(correlationResult.Package->GetProperty(PackageVersionProperty::Version)) : "", - correlationResult.Package ? static_cast(correlationResult.Package->GetProperty(PackageVersionProperty::Publisher)) : "", - correlationResult.Package ? static_cast(arpEntryMetadata[PackageVersionMetadata::InstalledLocale]) : "" - ); - } - CATCH_LOG(); - - void RecordInstall(Context& context) - { - // Local manifest installs won't have a package version, and tracking them doesn't provide much - // value currently. If we ever do use our own database as a primary source of packages that we - // maintain, this decision will probably have to be reconsidered. - if (!context.Contains(Data::PackageVersion)) - { - return; - } - - auto manifest = context.Get(); - - // If we have determined an ARP entry matches the installed package, - // we set its product code in the manifest we record to ensure we can - // find it in the future. - // Note that this may overwrite existing information. - if (context.Contains(Data::CorrelatedAppsAndFeaturesEntries)) - { - // Use a new Installer entry - manifest.Installers.emplace_back(); - manifest.Installers.back().AppsAndFeaturesEntries = context.Get(); - } - - auto trackingCatalog = context.Get()->GetSource().GetTrackingCatalog(); - - auto version = trackingCatalog.RecordInstall( - manifest, - context.Get().value(), - WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerExecutionUseUpdate)); - - // Record user intent values. Command args takes precedence. Then previous user intent values. - Repository::IPackageVersion::Metadata installedMetadata; - if (context.Contains(Data::InstalledPackageVersion) && context.Get()) - { - installedMetadata = context.Get()->GetMetadata(); - } - - bool isUpdate = WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerExecutionUseUpdate); - - if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) - { - version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, context.Args.GetArg(Execution::Args::Type::InstallArchitecture)); - } - else - { - auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); - if (itr != installedMetadata.end()) - { - version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, itr->second); - } - } - - if (context.Args.Contains(Execution::Args::Type::Locale)) - { - version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, context.Args.GetArg(Execution::Args::Type::Locale)); - } - else - { - auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentLocale); - if (itr != installedMetadata.end()) - { - version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, itr->second); - } - } - - // InitialOverrideArguments and InitialCustomSwitches capture the args from the original install. - // They are set only on fresh install and preserved (not updated) on upgrade. - if (!isUpdate) - { - if (context.Args.Contains(Execution::Args::Type::Override)) - { - version.SetMetadata(Repository::PackageVersionMetadata::InitialOverrideArguments, context.Args.GetArg(Execution::Args::Type::Override)); - } - - if (context.Args.Contains(Execution::Args::Type::CustomSwitches)) - { - version.SetMetadata(Repository::PackageVersionMetadata::InitialCustomSwitches, context.Args.GetArg(Execution::Args::Type::CustomSwitches)); - } - } - else - { - auto overrideItr = installedMetadata.find(Repository::PackageVersionMetadata::InitialOverrideArguments); - if (overrideItr != installedMetadata.end()) - { - version.SetMetadata(Repository::PackageVersionMetadata::InitialOverrideArguments, overrideItr->second); - } - - auto customItr = installedMetadata.find(Repository::PackageVersionMetadata::InitialCustomSwitches); - if (customItr != installedMetadata.end()) - { - version.SetMetadata(Repository::PackageVersionMetadata::InitialCustomSwitches, customItr->second); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "InstallFlow.h" +#include "DownloadFlow.h" +#include "FontFlow.h" +#include "UninstallFlow.h" +#include "UpdateFlow.h" +#include "ResumeFlow.h" +#include "ShowFlow.h" +#include "Resources.h" +#include "ShellExecuteInstallerHandler.h" +#include "MSStoreInstallerHandler.h" +#include "MsiInstallFlow.h" +#include "ArchiveFlow.h" +#include "PortableFlow.h" +#include "WorkflowBase.h" +#include "DependenciesFlow.h" +#include "PromptFlow.h" +#include "SourceFlow.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Windows::Management::Deployment; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Registry::Environment; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + bool MightWriteToARP(InstallerTypeEnum type) + { + switch (type) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Wix: + return true; + default: + return false; + } + } + + bool ShouldUseDirectMSIInstall(InstallerTypeEnum type, bool isSilentInstall) + { + return DoesInstallerTypeUseMsiProperties(type) && + (isSilentInstall || ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::DirectMSI)); + } + + bool ShouldErrorForUnsupportedArgument(UnsupportedArgumentEnum arg) + { + switch (arg) + { + case UnsupportedArgumentEnum::Location: + return true; + default: + return false; + } + } + + Execution::Args::Type GetUnsupportedArgumentType(UnsupportedArgumentEnum unsupportedArgument) + { + Execution::Args::Type execArg; + + switch (unsupportedArgument) + { + case UnsupportedArgumentEnum::Log: + execArg = Execution::Args::Type::Log; + break; + case UnsupportedArgumentEnum::Location: + execArg = Execution::Args::Type::InstallLocation; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return execArg; + } + + struct ExpectedReturnCode + { + ExpectedReturnCode(ExpectedReturnCodeEnum installerReturnCode, HRESULT hr, Resource::StringId message) : + InstallerReturnCode(installerReturnCode), HResult(hr), Message(message) {} + + static ExpectedReturnCode GetExpectedReturnCode(ExpectedReturnCodeEnum returnCode) + { + switch (returnCode) + { + case ExpectedReturnCodeEnum::PackageInUse: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, Resource::String::InstallFlowReturnCodePackageInUse); + case ExpectedReturnCodeEnum::PackageInUseByApplication: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION, Resource::String::InstallFlowReturnCodePackageInUseByApplication); + case ExpectedReturnCodeEnum::InstallInProgress: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS, Resource::String::InstallFlowReturnCodeInstallInProgress); + case ExpectedReturnCodeEnum::FileInUse: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE, Resource::String::InstallFlowReturnCodeFileInUse); + case ExpectedReturnCodeEnum::MissingDependency: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY, Resource::String::InstallFlowReturnCodeMissingDependency); + case ExpectedReturnCodeEnum::DiskFull: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL, Resource::String::InstallFlowReturnCodeDiskFull); + case ExpectedReturnCodeEnum::InsufficientMemory: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY, Resource::String::InstallFlowReturnCodeInsufficientMemory); + case ExpectedReturnCodeEnum::InvalidParameter: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER, Resource::String::InstallFlowReturnCodeInvalidParameter); + case ExpectedReturnCodeEnum::NoNetwork: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK, Resource::String::InstallFlowReturnCodeNoNetwork); + case ExpectedReturnCodeEnum::ContactSupport: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT, Resource::String::InstallFlowReturnCodeContactSupport); + case ExpectedReturnCodeEnum::RebootRequiredToFinish: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH, Resource::String::InstallFlowReturnCodeRebootRequiredToFinish); + case ExpectedReturnCodeEnum::RebootRequiredForInstall: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, Resource::String::InstallFlowReturnCodeRebootRequiredForInstall); + case ExpectedReturnCodeEnum::RebootInitiated: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED, Resource::String::InstallFlowReturnCodeRebootInitiated); + case ExpectedReturnCodeEnum::CancelledByUser: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER, Resource::String::InstallFlowReturnCodeCancelledByUser); + case ExpectedReturnCodeEnum::AlreadyInstalled: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED, Resource::String::InstallFlowReturnCodeAlreadyInstalled); + case ExpectedReturnCodeEnum::Downgrade: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE, Resource::String::InstallFlowReturnCodeDowngrade); + case ExpectedReturnCodeEnum::BlockedByPolicy: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY, Resource::String::InstallFlowReturnCodeBlockedByPolicy); + case ExpectedReturnCodeEnum::SystemNotSupported: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, Resource::String::InstallFlowReturnCodeSystemNotSupported); + case ExpectedReturnCodeEnum::Custom: + return ExpectedReturnCode(returnCode, APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR, Resource::String::InstallFlowReturnCodeCustomError); + default: + THROW_HR(E_UNEXPECTED); + } + } + + ExpectedReturnCodeEnum InstallerReturnCode; + HRESULT HResult; + Resource::StringId Message; + }; + + void CheckForOnlyDependencies(Execution::Context& context) + { + if (context.Args.Contains(Execution::Args::Type::DependenciesOnly)) + { + context.Reporter.Info() << Resource::String::DependenciesOnlyMessage << std::endl; + // We want the context to terminate, but successfully. + context.SetTerminationHR(S_OK); + } + } + } + + namespace details + { + // Runs the installer via ShellExecute. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void ShellExecuteInstall(Execution::Context& context) + { + context << + GetInstallerArgs << + ShellExecuteInstallImpl << + ReportInstallerResult("ShellExecute"sv, APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED); + } + + // Runs an MSI installer directly via MSI APIs. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void DirectMSIInstall(Execution::Context& context) + { + context << + GetInstallerArgs << + DirectMSIInstallImpl << + ReportInstallerResult("MsiInstallProduct"sv, APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED); + } + + // Deploys the MSIX. + // Required Args: None + // Inputs: Manifest?, Installer || InstallerPath + // Outputs: None + void MsixInstall(Execution::Context& context) + { + std::string uri; + Deployment::Options deploymentOptions; + if (context.Contains(Execution::Data::InstallerPath)) + { + uri = context.Get().u8string(); + } + else + { + uri = context.Get()->Url; + deploymentOptions.ExpectedDigests = context.Get(); + } + + deploymentOptions.SkipReputationCheck = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerTrusted); + + bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; + + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + bool registrationDeferred = false; + + try + { + registrationDeferred = context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) + { + if (isMachineScope) + { + return Deployment::AddPackageMachineScope(uri, deploymentOptions, callback); + } + else + { + return Deployment::AddPackageWithDeferredFallback(uri, deploymentOptions, callback); + } + }); + } + catch (const wil::ResultException& re) + { + // There was a bug in the deployment provision API when called in a packaged context, + // fixed in 10.0.26100.0. On older OS versions, convert any failure under these conditions + // to the error that was previously always returned. + if (isMachineScope && Runtime::IsRunningInPackagedContext() && + !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) + { + context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; + context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + AICLI_LOG(CLI, Error, << "Device wide install for msix type is not supported in packaged context on this OS version. Error: " << re.GetErrorCode()); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + + context.Add(re.GetErrorCode()); + context << ReportInstallerResult("MSIX"sv, re.GetErrorCode(), /* isHResult */ true); + return; + } + + if (registrationDeferred) + { + context.Reporter.Warn() << Resource::String::InstallFlowRegistrationDeferred << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + } + } + + // Runs the flow for installing a Portable package. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void PortableInstall(Execution::Context& context) + { + context << + InitializePortableInstaller << + VerifyPackageAndSourceMatch << + PortableInstallImpl << + ReportInstallerResult("Portable"sv, APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED, true); + } + + // Runs the flow for installing a package from an archive. + // Required Args: None + // Inputs: Installer, InstallerPath, Manifest + // Outputs: None + void ArchiveInstall(Execution::Context& context) + { + context << + ScanArchiveFromLocalManifest << + ExtractFilesFromArchive << + VerifyAndSetNestedInstaller << + ExecuteInstallerForType(context.Get().value().NestedInstallerType); + } + + // Runs the flow for installing a font package. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void FontInstall(Execution::Context& context) + { + context << + GetInstallerArgs << + FontInstallImpl << + ReportInstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, true); + } + } + + bool ExemptFromSingleInstallLocking(InstallerTypeEnum type) + { + switch (type) + { + // MSStore installs are always MSIX based; MSIX installs are safe to run in parallel. + case InstallerTypeEnum::Msix: + case InstallerTypeEnum::MSStore: + return true; + default: + return false; + } + } + + void EnsureApplicableInstaller(Execution::Context& context) + { + const auto& installer = context.Get(); + + if (!installer.has_value()) + { + context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); + } + + context << + EnsureSupportForDownload << + EnsureSupportForInstall; + } + + void CheckForUnsupportedArgs(Execution::Context& context) + { + bool messageDisplayed = false; + const auto& unsupportedArgs = context.Get()->UnsupportedArguments; + for (auto unsupportedArg : unsupportedArgs) + { + const auto& unsupportedArgType = GetUnsupportedArgumentType(unsupportedArg); + if (context.Args.Contains(unsupportedArgType)) + { + if (!messageDisplayed) + { + context.Reporter.Warn() << Resource::String::UnsupportedArgument << std::endl; + messageDisplayed = true; + } + + const auto& executingCommand = context.GetExecutingCommand(); + if (executingCommand != nullptr) + { + const auto& commandArguments = executingCommand->GetArguments(); + for (const auto& argument : commandArguments) + { + if (unsupportedArgType == argument.ExecArgType()) + { + const auto& usageString = argument.GetUsageString(); + if (ShouldErrorForUnsupportedArgument(unsupportedArg)) + { + context.Reporter.Error() << usageString << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); + } + else + { + context.Reporter.Warn() << usageString << std::endl; + break; + } + } + } + } + } + } + } + + void ShowInstallationDisclaimer(Execution::Context& context) + { + auto installerType = context.Get().value().EffectiveInstallerType(); + + if (installerType == InstallerTypeEnum::MSStore) + { + context.Reporter.Info() << Execution::PromptEmphasis << Resource::String::InstallationDisclaimerMSStore << std::endl; + } + else + { + context.Reporter.Info() << + Resource::String::InstallationDisclaimer1 << std::endl << + Resource::String::InstallationDisclaimer2 << std::endl; + } + } + + void DisplayInstallationNotes(Execution::Context& context) + { + if (!Settings::User().Get()) + { + const auto& manifest = context.Get(); + auto installationNotes = manifest.CurrentLocalization.Get(); + + if (!installationNotes.empty()) + { + context.Reporter.Info() << Resource::String::Notes(installationNotes) << std::endl; + } + } + } + + void ExecuteInstallerForType::operator()(Execution::Context& context) const + { + bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + UpdateBehaviorEnum updateBehavior = context.Get().value().UpdateBehavior; + bool doUninstallPrevious = isUpdate && (updateBehavior == UpdateBehaviorEnum::UninstallPrevious || context.Args.Contains(Execution::Args::Type::UninstallPrevious)); + + Synchronization::CrossProcessInstallLock lock; + if (!ExemptFromSingleInstallLocking(m_installerType)) + { + // Acquire install lock; if the operation is cancelled it will return false so we will also return. + if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) + { + callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); + return lock.Acquire(callback); + })) + { + AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire install lock due to cancellation"); + return; + } + } + + switch (m_installerType) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Wix: + if (doUninstallPrevious) + { + context << + GetUninstallInfo << + ExecuteUninstaller; + context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); + } + if (ShouldUseDirectMSIInstall(m_installerType, context.Args.Contains(Execution::Args::Type::Silent))) + { + context << details::DirectMSIInstall; + } + else + { + context << details::ShellExecuteInstall; + } + break; + case InstallerTypeEnum::Msix: + context << details::MsixInstall; + break; + case InstallerTypeEnum::MSStore: + context << + EnsureStorePolicySatisfied << + (isUpdate ? MSStoreUpdate : MSStoreInstall); + break; + case InstallerTypeEnum::Portable: + if (doUninstallPrevious) + { + context << + GetUninstallInfo << + ExecuteUninstaller; + context.ClearFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); + } + context << details::PortableInstall; + break; + case InstallerTypeEnum::Zip: + context << details::ArchiveInstall; + break; + case InstallerTypeEnum::Font: + context << details::FontInstall; + break; + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context) + { + // Admin is required for machine scope install for installer types like portable, msix and msstore. + auto installerType = context.Get().value().EffectiveInstallerType(); + + if (Manifest::DoesInstallerTypeRequireAdminForMachineScopeInstall(installerType)) + { + Manifest::ScopeEnum scope = ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + if (scope == Manifest::ScopeEnum::Machine) + { + context << Workflow::EnsureRunningAsAdmin; + } + } + } + + void ExecuteInstaller(Execution::Context& context) + { + context << Workflow::ExecuteInstallerForType(context.Get().value().BaseInstallerType); + } + + void ReportInstallerResult::operator()(Execution::Context& context) const + { + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); + + DWORD installResult = context.Get(); + const auto& additionalSuccessCodes = context.Get()->InstallerSuccessCodes; + if (installResult != 0 && (std::find(additionalSuccessCodes.begin(), additionalSuccessCodes.end(), installResult) == additionalSuccessCodes.end())) + { + HRESULT terminationHR = m_hr; + const auto& expectedReturnCodes = context.Get()->ExpectedReturnCodes; + auto expectedReturnCodeItr = expectedReturnCodes.find(installResult); + if (expectedReturnCodeItr != expectedReturnCodes.end() && expectedReturnCodeItr->second.ReturnResponseEnum != ExpectedReturnCodeEnum::Unknown) + { + auto returnCode = ExpectedReturnCode::GetExpectedReturnCode(expectedReturnCodeItr->second.ReturnResponseEnum); + terminationHR = returnCode.HResult; + + switch (terminationHR) + { + case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH: + // REBOOT_REQUIRED_TO_FINISH is treated as a success since installation has completed but is pending a reboot. + context.SetFlags(ContextFlag::RebootRequired); + context.Reporter.Warn() << returnCode.Message << std::endl; + terminationHR = S_OK; + break; + case APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL: + // REBOOT_REQUIRED_FOR_INSTALL is treated as an error since installation has not yet completed. + context.SetFlags(ContextFlag::RebootRequired); + // TODO: Add separate workflow to handle restart registration for resume. + context.SetFlags(ContextFlag::RegisterResume); + break; + } + + if (FAILED(terminationHR)) + { + context.Reporter.Error() << returnCode.Message << std::endl; + const auto& returnResponseUrl = expectedReturnCodeItr->second.ReturnResponseUrl; + if (!returnResponseUrl.empty()) + { + context.Reporter.Error() << Resource::String::RelatedLink << ' ' << returnResponseUrl << std::endl; + } + } + } + + if (FAILED(terminationHR)) + { + const auto& manifest = context.Get(); + + if (isRepair) + { + Logging::Telemetry().LogRepairFailure(manifest.Id, manifest.Version, m_installerType, installResult); + } + else + { + Logging::Telemetry().LogInstallerFailure(manifest.Id, manifest.Version, manifest.Channel, m_installerType, installResult); + } + + if (m_isHResult) + { + context.Reporter.Error() + << Resource::String::InstallerFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(installResult) }) + << std::endl; + } + else + { + context.Reporter.Error() + << Resource::String::InstallerFailedWithCode(installResult) + << std::endl; + } + + // Show installer log path if exists + if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get())) + { + auto installerLogPath = Utility::LocIndString{ context.Get().u8string() }; + context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; + } + + AICLI_TERMINATE_CONTEXT(terminationHR); + } + } + else + { + if (isRepair) + { + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + } + } + } + + void ReportIdentityAndInstallationDisclaimer(Execution::Context& context) + { + context << + Workflow::ReportManifestIdentityWithVersion() << + Workflow::ShowInstallationDisclaimer; + } + + void InstallPackageInstaller(Execution::Context& context) + { + context << + Workflow::ReportExecutionStage(ExecutionStage::PreExecution) << + Workflow::SnapshotARPEntries << + Workflow::ReportExecutionStage(ExecutionStage::Execution) << + Workflow::ExecuteInstaller << + Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << + Workflow::ReportARPChanges << + Workflow::RecordInstall << + Workflow::ForceInstalledCacheUpdate << + Workflow::RemoveInstaller << + Workflow::DisplayInstallationNotes; + } + + void InstallDependencies(Execution::Context& context) + { + using Flags = ProcessMultiplePackages::Flags; + + if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) + { + context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl; + return; + } + + context << + GetDependenciesFromInstaller << + ReportDependencies(Resource::String::PackageRequiresDependencies) << + EnableWindowsFeaturesDependencies << + ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable); + } + + void DownloadPackageDependencies(Execution::Context& context) + { + using Flags = ProcessMultiplePackages::Flags; + + if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) + { + context.Reporter.Warn() << Resource::String::DependenciesSkippedMessage << std::endl; + return; + } + + context << + GetDependenciesFromInstaller << + ReportDependencies(Resource::String::PackageRequiresDependencies) << + CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) << + ProcessMultiplePackages(Resource::String::PackageRequiresDependencies, APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::DownloadOnly); + } + + void InstallSinglePackage(Execution::Context& context) + { + context << + Workflow::CheckForUnsupportedArgs << + Workflow::ReportIdentityAndInstallationDisclaimer << + Workflow::ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << + Workflow::CreateDependencySubContexts(Resource::String::PackageRequiresDependencies) << + Workflow::InstallDependencies << + CheckForOnlyDependencies << + Workflow::DownloadInstaller << + Workflow::InstallPackageInstaller << + Workflow::RegisterStartupAfterReboot(); + } + + void EnsureSupportForInstall(Execution::Context& context) + { + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly)) + { + return; + } + + const auto& installer = context.Get(); + + // This check is only necessary for the Repair workflow when operating on an installer with RepairBehavior set to Installer. + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair)) + { + if (installer->RepairBehavior != RepairBehaviorEnum::Installer) + { + return; + } + + // At present, the installer repair behavior scenario is restricted to Exe, Inno, Nullsoft, and Burn installer types. + if (!DoesInstallerTypeRequireRepairBehaviorForRepair(installer->EffectiveInstallerType())) + { + return; + } + } + + // This installer cannot be run elevated, but we are running elevated. + // Implementation of de-elevation is complex; simply block for now. + if (installer->ElevationRequirement == ElevationRequirementEnum::ElevationProhibited && Runtime::IsRunningAsAdmin()) + { + AICLI_LOG(CLI, Error, << "The installer cannot be run from an administrator context."); + context.Reporter.Error() << Resource::String::InstallerProhibitsElevation << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION); + } + + // This installer cannot be used to upgrade the currently installed application + // Because the upgrade mechanism may be package-specific, simply block. + bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + UpdateBehaviorEnum updateBehavior = installer->UpdateBehavior; + if (isUpdate && (updateBehavior == UpdateBehaviorEnum::Deny)) + { + AICLI_LOG(CLI, Error, << "Manifest specifies update behavior is denied. The attempt will be cancelled."); + context.Reporter.Error() << Resource::String::UpgradeBlockedByManifest << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED); + } + + context << + Workflow::EnsureRunningAsAdminForMachineScopeInstall << + Workflow::EnsureSupportForPortableInstall << + Workflow::EnsureValidNestedInstallerMetadataForArchiveInstall; + } + + ProcessMultiplePackages::ProcessMultiplePackages( + StringResource::StringId dependenciesReportMessage, + HRESULT resultOnFailure, + Flags flags, + std::vector&& ignorableInstallResults) : + WorkflowTask("ProcessMultiplePackages"), + m_dependenciesReportMessage(dependenciesReportMessage), + m_resultOnFailure(resultOnFailure), + m_ignorableInstallResults(std::move(ignorableInstallResults)) + { + // Inverted + m_ensurePackageAgreements = !WI_IsFlagSet(flags, Flags::SkipPackageAgreements); + + m_ignorePackageDependencies = WI_IsFlagSet(flags, Flags::IgnoreDependencies); + m_stopOnFailure = WI_IsFlagSet(flags, Flags::StopOnFailure); + m_refreshPathVariable = WI_IsFlagSet(flags, Flags::RefreshPathVariable); + m_downloadOnly = WI_IsFlagSet(flags, Flags::DownloadOnly); + m_dependenciesOnly = WI_IsFlagSet(flags, Flags::DependenciesOnly); + } + + void ProcessMultiplePackages::operator()(Execution::Context& context) const + { + if (!context.Contains(Execution::Data::PackageSubContexts)) + { + return; + } + + bool downloadInstallerOnly = m_downloadOnly ? true : WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerDownloadOnly); + + // Show all prompts needed for every package before installing anything + context << Workflow::ShowPromptsForMultiplePackages(m_ensurePackageAgreements, downloadInstallerOnly); + + if (context.IsTerminated()) + { + return; + } + + auto& packageSubContexts = context.Get(); + + // Report dependencies + if (!m_ignorePackageDependencies) + { + DependencyList allDependencies; + + for (auto& packageContext : packageSubContexts) + { + allDependencies.Add(packageContext->Get().value().Dependencies); + } + + if (!allDependencies.Empty()) + { + if (downloadInstallerOnly) + { + context.Reporter.Info() << Resource::String::DependenciesFlowDownload << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::DependenciesFlowInstall << std::endl; + } + } + + context.Add(allDependencies); + context << Workflow::ReportDependencies(m_dependenciesReportMessage); + } + + bool allSucceeded = true; + size_t packagesCount = packageSubContexts.size(); + size_t packagesProgress = 0; + + if (m_dependenciesOnly) + { + context.Reporter.Info() << Resource::String::DependenciesOnlyMessage << std::endl; + } + + for (auto& packageContext : packageSubContexts) + { + packagesProgress++; + context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; + + // We want to do best effort to install all packages regardless of previous failures + Execution::Context& currentContext = *packageContext; + auto previousThreadGlobals = currentContext.SetForCurrentThread(); + + currentContext << Workflow::ReportIdentityAndInstallationDisclaimer; + + // Prevent individual exceptions from breaking out of the loop + try + { + // Handle dependencies if requested. + if (!m_ignorePackageDependencies && !downloadInstallerOnly) + { + currentContext << + Workflow::EnableWindowsFeaturesDependencies << + Workflow::CreateDependencySubContexts(m_dependenciesReportMessage) << + Workflow::ProcessMultiplePackages(m_dependenciesReportMessage, APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, Flags::IgnoreDependencies | Flags::StopOnFailure | Flags::RefreshPathVariable); + } + + if (!m_dependenciesOnly) + { + currentContext << Workflow::DownloadInstaller; + + if (!downloadInstallerOnly) + { + currentContext << Workflow::InstallPackageInstaller; + } + } + } + catch (...) + { + currentContext.SetTerminationHR(Workflow::HandleException(currentContext, std::current_exception())); + } + + if (m_refreshPathVariable) + { + if (RefreshPathVariableForCurrentProcess()) + { + AICLI_LOG(CLI, Info, << "Successfully refreshed process PATH environment variable."); + } + else + { + AICLI_LOG(CLI, Warning, << "Failed to refresh process PATH environment variable."); + context.Reporter.Warn() << Resource::String::FailedToRefreshPathWarning << std::endl; + } + } + + currentContext.Reporter.Info() << std::endl; + + if (currentContext.IsTerminated()) + { + if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) + { + // This means that the subcontext being terminated is due to an overall abort + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + return; + } + + if (m_ignorableInstallResults.end() == std::find(m_ignorableInstallResults.begin(), m_ignorableInstallResults.end(), currentContext.GetTerminationHR())) + { + allSucceeded = false; + if (m_stopOnFailure) + { + break; + } + } + } + } + + if (!allSucceeded) + { + AICLI_TERMINATE_CONTEXT(m_resultOnFailure); + } + } + + void SnapshotARPEntries(Execution::Context& context) try + { + // Ensure that installer type might actually write to ARP, otherwise this is a waste of time + auto installer = context.Get(); + + if (installer && MightWriteToARP(installer->EffectiveInstallerType())) + { + Repository::Correlation::ARPCorrelationData data; + data.CapturePreInstallSnapshot(); + context.Add(std::move(data)); + } + } + CATCH_LOG() + + void ReportARPChanges(Execution::Context& context) try + { + if (!context.Contains(Execution::Data::ARPCorrelationData)) + { + return; + } + + // If the installer claims to have a PackageFamilyName, and that family name is currently registered for the user, + // let that be the correlated item and skip any attempt at further ARP correlation. + const auto& installer = context.Get(); + + if (installer && !installer->PackageFamilyName.empty() && Deployment::IsRegistered(installer->PackageFamilyName)) + { + return; + } + + const auto& manifest = context.Get(); + auto& arpCorrelationData = context.Get(); + + arpCorrelationData.CapturePostInstallSnapshot(); + auto correlationResult = arpCorrelationData.CorrelateForNewlyInstalled(manifest); + + // Store the ARP entry found to match the package to record it in the tracking catalog later + if (correlationResult.Package) + { + std::vector entries; + + auto metadata = correlationResult.Package->GetMetadata(); + + AppsAndFeaturesEntry baseEntry; + + // Display name and publisher are also available as multi properties, but + // for ARP there will always be only 0 or 1 values. + baseEntry.DisplayName = correlationResult.Package->GetProperty(PackageVersionProperty::Name).get(); + baseEntry.Publisher = correlationResult.Package->GetProperty(PackageVersionProperty::Publisher).get(); + baseEntry.DisplayVersion = correlationResult.Package->GetProperty(PackageVersionProperty::Version).get(); + baseEntry.InstallerType = Manifest::ConvertToInstallerTypeEnum(metadata[PackageVersionMetadata::InstalledType]); + + auto productCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + for (auto&& productCode : productCodes) + { + AppsAndFeaturesEntry entry = baseEntry; + entry.ProductCode = std::move(productCode).get(); + entries.push_back(std::move(entry)); + } + + auto upgradeCodes = correlationResult.Package->GetMultiProperty(PackageVersionMultiProperty::UpgradeCode); + for (auto&& upgradeCode : upgradeCodes) + { + AppsAndFeaturesEntry entry = baseEntry; + entry.UpgradeCode = std::move(upgradeCode).get(); + entries.push_back(std::move(entry)); + } + + context.Add(std::move(entries)); + } + + // We can only get the source identifier from an active source + std::string sourceIdentifier; + if (context.Contains(Execution::Data::PackageVersion)) + { + sourceIdentifier = context.Get()->GetProperty(PackageVersionProperty::SourceIdentifier); + } + + IPackageVersion::Metadata arpEntryMetadata; + if (correlationResult.Package) + { + arpEntryMetadata = correlationResult.Package->GetMetadata(); + } + + Logging::Telemetry().LogSuccessfulInstallARPChange( + sourceIdentifier, + manifest.Id, + manifest.Version, + manifest.Channel, + correlationResult.ChangesToARP, + correlationResult.MatchesInARP, + correlationResult.CountOfIntersectionOfChangesAndMatches, + correlationResult.Package ? static_cast(correlationResult.Package->GetProperty(PackageVersionProperty::Name)) : "", + correlationResult.Package ? static_cast(correlationResult.Package->GetProperty(PackageVersionProperty::Version)) : "", + correlationResult.Package ? static_cast(correlationResult.Package->GetProperty(PackageVersionProperty::Publisher)) : "", + correlationResult.Package ? static_cast(arpEntryMetadata[PackageVersionMetadata::InstalledLocale]) : "" + ); + } + CATCH_LOG(); + + void RecordInstall(Context& context) + { + // Local manifest installs won't have a package version, and tracking them doesn't provide much + // value currently. If we ever do use our own database as a primary source of packages that we + // maintain, this decision will probably have to be reconsidered. + if (!context.Contains(Data::PackageVersion)) + { + return; + } + + auto manifest = context.Get(); + + // If we have determined an ARP entry matches the installed package, + // we set its product code in the manifest we record to ensure we can + // find it in the future. + // Note that this may overwrite existing information. + if (context.Contains(Data::CorrelatedAppsAndFeaturesEntries)) + { + // Use a new Installer entry + manifest.Installers.emplace_back(); + manifest.Installers.back().AppsAndFeaturesEntries = context.Get(); + } + + auto trackingCatalog = context.Get()->GetSource().GetTrackingCatalog(); + + auto version = trackingCatalog.RecordInstall( + manifest, + context.Get().value(), + WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerExecutionUseUpdate)); + + // Record user intent values. Command args takes precedence. Then previous user intent values. + Repository::IPackageVersion::Metadata installedMetadata; + if (context.Contains(Data::InstalledPackageVersion) && context.Get()) + { + installedMetadata = context.Get()->GetMetadata(); + } + + bool isUpdate = WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerExecutionUseUpdate); + + if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) + { + version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, context.Args.GetArg(Execution::Args::Type::InstallArchitecture)); + } + else + { + auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); + if (itr != installedMetadata.end()) + { + version.SetMetadata(Repository::PackageVersionMetadata::UserIntentArchitecture, itr->second); + } + } + + if (context.Args.Contains(Execution::Args::Type::Locale)) + { + version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, context.Args.GetArg(Execution::Args::Type::Locale)); + } + else + { + auto itr = installedMetadata.find(Repository::PackageVersionMetadata::UserIntentLocale); + if (itr != installedMetadata.end()) + { + version.SetMetadata(Repository::PackageVersionMetadata::UserIntentLocale, itr->second); + } + } + + // InitialOverrideArguments and InitialCustomSwitches capture the args from the original install. + // They are set only on fresh install and preserved (not updated) on upgrade. + if (!isUpdate) + { + if (context.Args.Contains(Execution::Args::Type::Override)) + { + version.SetMetadata(Repository::PackageVersionMetadata::InitialOverrideArguments, context.Args.GetArg(Execution::Args::Type::Override)); + } + + if (context.Args.Contains(Execution::Args::Type::CustomSwitches)) + { + version.SetMetadata(Repository::PackageVersionMetadata::InitialCustomSwitches, context.Args.GetArg(Execution::Args::Type::CustomSwitches)); + } + } + else + { + auto overrideItr = installedMetadata.find(Repository::PackageVersionMetadata::InitialOverrideArguments); + if (overrideItr != installedMetadata.end()) + { + version.SetMetadata(Repository::PackageVersionMetadata::InitialOverrideArguments, overrideItr->second); + } + + auto customItr = installedMetadata.find(Repository::PackageVersionMetadata::InitialCustomSwitches); + if (customItr != installedMetadata.end()) + { + version.SetMetadata(Repository::PackageVersionMetadata::InitialCustomSwitches, customItr->second); + } + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/InstallFlow.h b/src/AppInstallerCLICore/Workflows/InstallFlow.h index 2f2c056ab8..36e17b7817 100644 --- a/src/AppInstallerCLICore/Workflows/InstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/InstallFlow.h @@ -1,228 +1,228 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" -#include - -namespace AppInstaller::CLI::Workflow -{ - using namespace std::string_view_literals; - - // Token specified in installer args will be replaced by proper value. - static constexpr std::string_view ARG_TOKEN_LOGPATH = ""sv; - static constexpr std::string_view ARG_TOKEN_INSTALLPATH = ""sv; - - // Determines if an installer type is allowed to install/uninstall in parallel. - bool ExemptFromSingleInstallLocking(AppInstaller::Manifest::InstallerTypeEnum type); - - namespace details - { - // These single type install flows should remain "internal" and only ExecuteInstallerForType should be used externally - // so that all installs can properly handle single install locking. - - // Runs the installer via ShellExecute. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void ShellExecuteInstall(Execution::Context& context); - - // Runs an MSI installer directly via MSI APIs. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void DirectMSIInstall(Execution::Context& context); - - // Deploys the MSIX. - // Required Args: None - // Inputs: Manifest?, Installer || InstallerPath - // Outputs: None - void MsixInstall(Execution::Context& context); - - // Runs the flow for installing a Portable package. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void PortableInstall(Execution::Context& context); - - // Runs the flow for installing a package from an archive. - // Required Args: None - // Inputs: Installer, InstallerPath, Manifest - // Outputs: None - void ArchiveInstall(Execution::Context& context); - - // Runs the flow for installing a font package. - // Required Args: None - // Inputs: Installer, InstallerPath, Manifest - // Outputs: None - void FontInstall(Execution::Context& context); - } - - // Ensures that there is an applicable installer. - // Required Args: None - // Inputs: Installer - // Outputs: None - void EnsureApplicableInstaller(Execution::Context& context); - - // Shows the installation disclaimer. - // Required Args: None - // Inputs: None - // Outputs: None - void ShowInstallationDisclaimer(Execution::Context& context); - - // Displays the installations notes after a successful install. - // Required Args: None - // Inputs: InstallationNotes - // Outputs: None - void DisplayInstallationNotes(Execution::Context& context); - - // Checks if there are any included arguments that are not supported for the package. - // Required Args: None - // Inputs: Installer - // Outputs: None - void CheckForUnsupportedArgs(Execution::Context& context); - - // Admin is required for machine scope install for installer types like portable, msix and msstore. - // Required Args: None - // Inputs: Installer - // Outputs: None - void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context); - - // Composite flow that chooses what to do based on the installer type. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - void ExecuteInstaller(Execution::Context& context); - - // Composite flow that chooses what to do based on the installer type. - // Required Args: None - // Inputs: Installer, InstallerPath - // Outputs: None - struct ExecuteInstallerForType : public WorkflowTask - { - ExecuteInstallerForType(Manifest::InstallerTypeEnum installerType) : WorkflowTask("ExecuteInstallerForType"), m_installerType(installerType) {} - - void operator()(Execution::Context& context) const override; - - private: - Manifest::InstallerTypeEnum m_installerType; - }; - - // Verifies parameters for install to ensure success. - // Required Args: None - // Inputs: - // Outputs: None - void EnsureSupportForInstall(Execution::Context& context); - - // Reports the return code returned by the installer. - // Required Args: None - // Inputs: Manifest, Installer, InstallerResult - // Outputs: None - struct ReportInstallerResult : public WorkflowTask - { - ReportInstallerResult(std::string_view installerType, HRESULT hr, bool isHResult = false) : - WorkflowTask("ReportInstallerResult"), - m_installerType(installerType), - m_hr(hr), - m_isHResult(isHResult) {} - - void operator()(Execution::Context& context) const override; - - private: - // Installer type used when reporting failures. - std::string_view m_installerType; - // Result to return if the installer failed. - HRESULT m_hr; - // Whether the installer result is an HRESULT. This guides how we show it. - bool m_isHResult; - }; - - // Reports manifest identity and shows installation disclaimer - // Required Args: None - // Inputs: Manifest - // Outputs: None - void ReportIdentityAndInstallationDisclaimer(Execution::Context& context); - - // Installs a specific package installer. See also InstallSinglePackage & ProcessMultiplePackages - // Required Args: None - // Inputs: InstallerPath, Manifest, Installer, PackageVersion, InstalledPackageVersion? - // Outputs: None - void InstallPackageInstaller(Execution::Context& context); - - // Installs the dependencies for a specific package. CreateDependencySubContexts should have been called before this task. - // Required Args: None - // Inputs: InstallerPath, Manifest, Installer, PackageVersion, InstalledPackageVersion? - // Outputs: None - void InstallDependencies(Execution::Context& context); - - // Downloads all of the package dependencies of a specific package. Only used in the 'winget download' and COM download flows. - // Required Args: none - // Inputs: Manifest, Installer - // Outputs: None - void DownloadPackageDependencies(Execution::Context& context); - - // Installs a single package. This also does the reporting, user interaction, and installer download - // for single-package installation. - // RequiredArgs: None - // Inputs: Manifest, Installer, PackageVersion, InstalledPackageVersion? - // Outputs: None - void InstallSinglePackage(Execution::Context& context); - - // Processes multiple packages by handling download and/or install. This also does the reporting and user interaction needed. - // Required Args: None - // Inputs: PackageSubContexts - // Outputs: None - struct ProcessMultiplePackages : public WorkflowTask - { - // Flags to signal change from default behavior of the task. - enum class Flags : uint32_t - { - None = 0x00, - SkipPackageAgreements = 0x01, - IgnoreDependencies = 0x02, - StopOnFailure = 0x04, - RefreshPathVariable = 0x08, - DownloadOnly = 0x10, - DependenciesOnly = 0x20, - }; - - ProcessMultiplePackages( - StringResource::StringId dependenciesReportMessage, - HRESULT resultOnFailure, - Flags flags = Flags::None, - std::vector&& ignorableInstallResults = {}); - - void operator()(Execution::Context& context) const override; - - private: - HRESULT m_resultOnFailure; - std::vector m_ignorableInstallResults; - StringResource::StringId m_dependenciesReportMessage; - bool m_ignorePackageDependencies; - bool m_ensurePackageAgreements; - bool m_stopOnFailure; - bool m_refreshPathVariable; - bool m_downloadOnly; - bool m_dependenciesOnly; - }; - - DEFINE_ENUM_FLAG_OPERATORS(ProcessMultiplePackages::Flags); - - // Stores the existing set of packages in ARP. - // Required Args: None - // Inputs: Installer - // Outputs: ARPSnapshot - void SnapshotARPEntries(Execution::Context& context); - - // Reports on the changes between the stored ARPSnapshot and the current values, - // and stores the product code of the ARP entry found for the package. - // Required Args: None - // Inputs: ARPSnapshot?, Manifest, PackageVersion - // Outputs: CorrelatedAppsAndFeaturesEntries? - void ReportARPChanges(Execution::Context& context); - - // Records the installation to the tracking catalog. - // Required Args: None - // Inputs: PackageVersion?, Manifest, Installer, CorrelatedAppsAndFeaturesEntries? - // Outputs: None - void RecordInstall(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" +#include + +namespace AppInstaller::CLI::Workflow +{ + using namespace std::string_view_literals; + + // Token specified in installer args will be replaced by proper value. + static constexpr std::string_view ARG_TOKEN_LOGPATH = ""sv; + static constexpr std::string_view ARG_TOKEN_INSTALLPATH = ""sv; + + // Determines if an installer type is allowed to install/uninstall in parallel. + bool ExemptFromSingleInstallLocking(AppInstaller::Manifest::InstallerTypeEnum type); + + namespace details + { + // These single type install flows should remain "internal" and only ExecuteInstallerForType should be used externally + // so that all installs can properly handle single install locking. + + // Runs the installer via ShellExecute. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void ShellExecuteInstall(Execution::Context& context); + + // Runs an MSI installer directly via MSI APIs. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void DirectMSIInstall(Execution::Context& context); + + // Deploys the MSIX. + // Required Args: None + // Inputs: Manifest?, Installer || InstallerPath + // Outputs: None + void MsixInstall(Execution::Context& context); + + // Runs the flow for installing a Portable package. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void PortableInstall(Execution::Context& context); + + // Runs the flow for installing a package from an archive. + // Required Args: None + // Inputs: Installer, InstallerPath, Manifest + // Outputs: None + void ArchiveInstall(Execution::Context& context); + + // Runs the flow for installing a font package. + // Required Args: None + // Inputs: Installer, InstallerPath, Manifest + // Outputs: None + void FontInstall(Execution::Context& context); + } + + // Ensures that there is an applicable installer. + // Required Args: None + // Inputs: Installer + // Outputs: None + void EnsureApplicableInstaller(Execution::Context& context); + + // Shows the installation disclaimer. + // Required Args: None + // Inputs: None + // Outputs: None + void ShowInstallationDisclaimer(Execution::Context& context); + + // Displays the installations notes after a successful install. + // Required Args: None + // Inputs: InstallationNotes + // Outputs: None + void DisplayInstallationNotes(Execution::Context& context); + + // Checks if there are any included arguments that are not supported for the package. + // Required Args: None + // Inputs: Installer + // Outputs: None + void CheckForUnsupportedArgs(Execution::Context& context); + + // Admin is required for machine scope install for installer types like portable, msix and msstore. + // Required Args: None + // Inputs: Installer + // Outputs: None + void EnsureRunningAsAdminForMachineScopeInstall(Execution::Context& context); + + // Composite flow that chooses what to do based on the installer type. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + void ExecuteInstaller(Execution::Context& context); + + // Composite flow that chooses what to do based on the installer type. + // Required Args: None + // Inputs: Installer, InstallerPath + // Outputs: None + struct ExecuteInstallerForType : public WorkflowTask + { + ExecuteInstallerForType(Manifest::InstallerTypeEnum installerType) : WorkflowTask("ExecuteInstallerForType"), m_installerType(installerType) {} + + void operator()(Execution::Context& context) const override; + + private: + Manifest::InstallerTypeEnum m_installerType; + }; + + // Verifies parameters for install to ensure success. + // Required Args: None + // Inputs: + // Outputs: None + void EnsureSupportForInstall(Execution::Context& context); + + // Reports the return code returned by the installer. + // Required Args: None + // Inputs: Manifest, Installer, InstallerResult + // Outputs: None + struct ReportInstallerResult : public WorkflowTask + { + ReportInstallerResult(std::string_view installerType, HRESULT hr, bool isHResult = false) : + WorkflowTask("ReportInstallerResult"), + m_installerType(installerType), + m_hr(hr), + m_isHResult(isHResult) {} + + void operator()(Execution::Context& context) const override; + + private: + // Installer type used when reporting failures. + std::string_view m_installerType; + // Result to return if the installer failed. + HRESULT m_hr; + // Whether the installer result is an HRESULT. This guides how we show it. + bool m_isHResult; + }; + + // Reports manifest identity and shows installation disclaimer + // Required Args: None + // Inputs: Manifest + // Outputs: None + void ReportIdentityAndInstallationDisclaimer(Execution::Context& context); + + // Installs a specific package installer. See also InstallSinglePackage & ProcessMultiplePackages + // Required Args: None + // Inputs: InstallerPath, Manifest, Installer, PackageVersion, InstalledPackageVersion? + // Outputs: None + void InstallPackageInstaller(Execution::Context& context); + + // Installs the dependencies for a specific package. CreateDependencySubContexts should have been called before this task. + // Required Args: None + // Inputs: InstallerPath, Manifest, Installer, PackageVersion, InstalledPackageVersion? + // Outputs: None + void InstallDependencies(Execution::Context& context); + + // Downloads all of the package dependencies of a specific package. Only used in the 'winget download' and COM download flows. + // Required Args: none + // Inputs: Manifest, Installer + // Outputs: None + void DownloadPackageDependencies(Execution::Context& context); + + // Installs a single package. This also does the reporting, user interaction, and installer download + // for single-package installation. + // RequiredArgs: None + // Inputs: Manifest, Installer, PackageVersion, InstalledPackageVersion? + // Outputs: None + void InstallSinglePackage(Execution::Context& context); + + // Processes multiple packages by handling download and/or install. This also does the reporting and user interaction needed. + // Required Args: None + // Inputs: PackageSubContexts + // Outputs: None + struct ProcessMultiplePackages : public WorkflowTask + { + // Flags to signal change from default behavior of the task. + enum class Flags : uint32_t + { + None = 0x00, + SkipPackageAgreements = 0x01, + IgnoreDependencies = 0x02, + StopOnFailure = 0x04, + RefreshPathVariable = 0x08, + DownloadOnly = 0x10, + DependenciesOnly = 0x20, + }; + + ProcessMultiplePackages( + StringResource::StringId dependenciesReportMessage, + HRESULT resultOnFailure, + Flags flags = Flags::None, + std::vector&& ignorableInstallResults = {}); + + void operator()(Execution::Context& context) const override; + + private: + HRESULT m_resultOnFailure; + std::vector m_ignorableInstallResults; + StringResource::StringId m_dependenciesReportMessage; + bool m_ignorePackageDependencies; + bool m_ensurePackageAgreements; + bool m_stopOnFailure; + bool m_refreshPathVariable; + bool m_downloadOnly; + bool m_dependenciesOnly; + }; + + DEFINE_ENUM_FLAG_OPERATORS(ProcessMultiplePackages::Flags); + + // Stores the existing set of packages in ARP. + // Required Args: None + // Inputs: Installer + // Outputs: ARPSnapshot + void SnapshotARPEntries(Execution::Context& context); + + // Reports on the changes between the stored ARPSnapshot and the current values, + // and stores the product code of the ARP entry found for the package. + // Required Args: None + // Inputs: ARPSnapshot?, Manifest, PackageVersion + // Outputs: CorrelatedAppsAndFeaturesEntries? + void ReportARPChanges(Execution::Context& context); + + // Records the installation to the tracking catalog. + // Required Args: None + // Inputs: PackageVersion?, Manifest, Installer, CorrelatedAppsAndFeaturesEntries? + // Outputs: None + void RecordInstall(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp index cfbb47fa9a..7811f5b2fe 100644 --- a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.cpp @@ -1,471 +1,471 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "MSStoreInstallerHandler.h" -#include "WorkflowBase.h" -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::CLI::Workflow -{ - void DownloadInstallerFile(Execution::Context& context); -} - -namespace AppInstaller::CLI::Workflow -{ - using namespace AppInstaller::MSStore; - using namespace AppInstaller::SelfManagement; - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Foundation::Collections; - using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; - - namespace - { - Utility::LocIndString GetErrorCodeString(const HRESULT errorCode) - { - std::ostringstream ssError; - ssError << WINGET_OSTREAM_FORMAT_HRESULT(errorCode); - return Utility::LocIndString{ ssError.str() }; - } - - HRESULT EnsureStorePolicySatisfiedImpl(const std::wstring& productId, bool bypassPolicy) - { - constexpr std::wstring_view s_StoreClientName = L"Microsoft.WindowsStore"sv; - constexpr std::wstring_view s_StoreClientPublisher = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"sv; - - // Policy check - AppInstallManager installManager; - - if (!bypassPolicy && installManager.IsStoreBlockedByPolicyAsync(s_StoreClientName, s_StoreClientPublisher).get()) - { - AICLI_LOG(CLI, Error, << "Store client is blocked by policy. MSStore execution failed."); - return APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY; - } - - if (!installManager.GetIsAppAllowedToInstallAsync(productId).get()) - { - AICLI_LOG(CLI, Error, << "App is blocked by policy. MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId)); - return APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY; - } - - return S_OK; - } - - void AppInstallerUpdate(bool preferStub, bool bypassPolicy, Execution::Context& context) - { - auto appInstId = std::wstring{ s_AppInstallerProductId }; - THROW_IF_FAILED(EnsureStorePolicySatisfiedImpl(appInstId, bypassPolicy)); - SetStubPreferred(preferStub); - - auto installOperation = MSStoreOperation(MSStoreOperationType::Update, appInstId, Manifest::ScopeEnum::User, true, true); - - HRESULT hr = S_OK; - context.Reporter.ExecuteWithProgress( - [&](IProgressCallback& progress) - { - hr = installOperation.StartAndWaitForOperation(progress); - }); - - THROW_IF_FAILED(hr); - } - - HRESULT DownloadMSStorePackageFile(const MSStore::MSStoreDownloadFile& downloadFile, const std::filesystem::path& downloadDirectory, Execution::Context& context) - { - try - { - // Create a sub context to execute the package download - auto subContextPtr = context.CreateSubContext(); - Execution::Context& subContext = *subContextPtr; - auto previousThreadGlobals = subContext.SetForCurrentThread(); - - // Populate Installer and temp download path for sub context - Manifest::ManifestInstaller installer; - installer.Url = downloadFile.Url; - installer.Sha256 = downloadFile.Sha256; - subContext.Add(std::move(installer)); - - auto tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); - tempInstallerPath /= Utility::SHA256::ConvertToString(downloadFile.Sha256); - AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); - subContext.Add(tempInstallerPath); - - subContext << Workflow::DownloadInstallerFile; - if (subContext.IsTerminated()) - { - RETURN_HR(subContext.GetTerminationHR()); - } - - // Verify hash - const auto& hashPair = subContext.Get(); - if (std::equal(hashPair.first.begin(), hashPair.first.end(), hashPair.second.Sha256Hash.begin())) - { - AICLI_LOG(CLI, Info, << "Microsoft Store package hash verified"); - subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageHashVerified << std::endl; - // Trust direct download from Store if hash matched - Utility::ApplyMotwIfApplicable(tempInstallerPath, URLZONE_TRUSTED); - } - else - { - if (!subContext.Args.Contains(Execution::Args::Type::HashOverride)) - { - AICLI_LOG(CLI, Error, << "Microsoft Store package hash mismatch"); - subContext.Reporter.Error() << Resource::String::MSStoreDownloadPackageHashMismatch << std::endl; - RETURN_HR(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); - } - else - { - AICLI_LOG(CLI, Warning, << "Microsoft Store package hash mismatch, but overridden."); - subContext.Reporter.Warn() << Resource::String::MSStoreDownloadPackageHashMismatch << std::endl; - } - } - - auto renamedDownloadedPackage = downloadDirectory / Utility::ConvertToUTF16(downloadFile.FileName); - Filesystem::RenameFile(tempInstallerPath, renamedDownloadedPackage); - subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageDownloaded(Utility::LocIndView{ renamedDownloadedPackage.u8string() }) << std::endl; - - return S_OK; - } - catch (...) - { - AICLI_LOG(CLI, Error, << "Microsoft Store package download failed. File: " << downloadFile.FileName); - context.Reporter.Error() << Resource::String::MSStoreDownloadPackageDownloadFailed(Utility::LocIndView{ downloadFile.FileName }) << std::endl; - RETURN_HR(APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED); - } - } - } - - void MSStoreInstall(Execution::Context& context) - { - auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); - auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); - bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); - bool force = context.Args.Contains(Execution::Args::Type::Force); - - auto installOperation = MSStoreOperation(MSStoreOperationType::Install, productId, scope, isSilentMode, force); - - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - - HRESULT hr = S_OK; - context.Reporter.ExecuteWithProgress( - [&](IProgressCallback& progress) - { - hr = installOperation.StartAndWaitForOperation(progress); - }); - - if (SUCCEEDED(hr)) - { - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; - } - else - { - if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) - { - context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; - context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - } - else - { - auto errorCodeString = GetErrorCodeString(hr); - context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; - context.Add(hr); - AICLI_LOG(CLI, Error, << "MSStore install failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); - } - - AICLI_TERMINATE_CONTEXT(hr); - } - } - - void MSStoreUpdate(Execution::Context& context) - { - bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); - auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); - auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); - bool force = context.Args.Contains(Execution::Args::Type::Force); - - auto installOperation = MSStoreOperation(MSStoreOperationType::Update, productId, scope, isSilentMode, force); - - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - - HRESULT hr = S_OK; - context.Reporter.ExecuteWithProgress( - [&](IProgressCallback& progress) - { - hr = installOperation.StartAndWaitForOperation(progress); - }); - - if (SUCCEEDED(hr)) - { - context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; - } - else - { - if (hr == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) - { - context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl - << Resource::String::UpdateNotApplicableReason << std::endl; - } - else - { - auto errorCodeString = GetErrorCodeString(hr); - context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; - context.Add(hr); - AICLI_LOG(CLI, Error, << "MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); - } - - AICLI_TERMINATE_CONTEXT(hr); - } - } - - void MSStoreRepair(Execution::Context& context) - { - auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); - auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); - bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); - bool force = context.Args.Contains(Execution::Args::Type::Force); - - auto repairOperation = MSStoreOperation(MSStoreOperationType::Repair, productId, scope, isSilentMode, force); - - context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; - - HRESULT hr = S_OK; - context.Reporter.ExecuteWithProgress( - [&](IProgressCallback& progress) - { - hr = repairOperation.StartAndWaitForOperation(progress); - }); - - if (SUCCEEDED(hr)) - { - context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; - } - else - { - if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) - { - context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; - context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - } - else - { - auto errorCodeString = GetErrorCodeString(hr); - context.Reporter.Error() << Resource::String::MSStoreRepairFailed(errorCodeString) << std::endl; - context.Add(hr); - AICLI_LOG(CLI, Error, << "MSStore repair failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); - } - - AICLI_TERMINATE_CONTEXT(hr); - } - } - - void MSStoreDownload(Execution::Context& context) - { - if (context.Args.Contains(Execution::Args::Type::Rename)) - { - context.Reporter.Warn() << Resource::String::MSStoreDownloadRenameNotSupported << std::endl; - } - - // Authentication notice - context.Reporter.Warn() << Resource::String::MSStoreDownloadAuthenticationNotice << std::endl; - context.Reporter.Warn() << Resource::String::MSStoreDownloadMultiplePackagesNotice << std::endl; - - const auto& installer = context.Get().value(); - - Utility::Architecture requiredArchitecture = Utility::Architecture::Unknown; - Manifest::PlatformEnum requiredPlatform = Manifest::PlatformEnum::Unknown; - std::string requiredLocale; - if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture)) - { - requiredArchitecture = Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture)); - } - if (context.Args.Contains(Execution::Args::Type::Platform)) - { - requiredPlatform = Manifest::ConvertToPlatformEnumForMSStoreDownload(context.Args.GetArg(Execution::Args::Type::Platform)); - } - if (context.Args.Contains(Execution::Args::Type::Locale)) - { - requiredLocale = context.Args.GetArg(Execution::Args::Type::Locale); - } - - MSStoreDownloadContext downloadContext{ installer.ProductId, requiredArchitecture, requiredPlatform, requiredLocale, GetAuthenticationArguments(context) }; - - if (context.Args.Contains(Execution::Args::Type::OSVersion)) - { - Utility::UInt64Version targetOSVersion{ std::string{ context.Args.GetArg(Execution::Args::Type::OSVersion) } }; - downloadContext.TargetOSVersion(std::move(targetOSVersion)); - } - - MSStoreDownloadInfo downloadInfo; - try - { - context.Reporter.Info() << Resource::String::MSStoreDownloadGetDownloadInfo << std::endl; - - downloadInfo = downloadContext.GetDownloadInfo(); - } - catch (const wil::ResultException& re) - { - AICLI_LOG(CLI, Error, << "Getting MSStore package download info failed. Error code: " << re.GetErrorCode()); - - switch (re.GetErrorCode()) - { - case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE: - case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE: - context.Reporter.Error() << Resource::String::MSStoreDownloadNoApplicablePackageFound << std::endl; - break; - case APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED: - context.Reporter.Error() << Resource::String::MSStoreDownloadPackageDownloadNotSupported << std::endl; - break; - default: - context.Reporter.Error() << Resource::String::MSStoreDownloadGetDownloadInfoFailed << std::endl; - } - - AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); - } - - bool skipDependencies = context.Args.Contains(Execution::Args::Type::SkipDependencies); - - // Prepare directories - std::filesystem::path downloadDirectory = context.Get(); - std::filesystem::path dependenciesDirectory = downloadDirectory / L"Dependencies"; - - // Create directories if needed. - auto directoryToCreate = (skipDependencies || downloadInfo.DependencyPackages.empty()) ? downloadDirectory : dependenciesDirectory; - if (!std::filesystem::exists(directoryToCreate)) - { - std::filesystem::create_directories(directoryToCreate); - } - else - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !std::filesystem::is_directory(directoryToCreate)); - } - - // Download dependency packages - if (!skipDependencies) - { - AICLI_LOG(CLI, Info, << "Downloading MSStore dependency packages"); - context.Reporter.Info() << Resource::String::MSStoreDownloadDependencyPackages << std::endl; - - for (auto const& dependencyPackage : downloadInfo.DependencyPackages) - { - THROW_IF_FAILED(DownloadMSStorePackageFile(dependencyPackage, dependenciesDirectory, context)); - } - } - - // Download main packages - AICLI_LOG(CLI, Info, << "Downloading MSStore main packages"); - context.Reporter.Info() << Resource::String::MSStoreDownloadMainPackages << std::endl; - for (auto const& mainPackage : downloadInfo.MainPackages) - { - THROW_IF_FAILED(DownloadMSStorePackageFile(mainPackage, downloadDirectory, context)); - } - - context.Reporter.Info() << Resource::String::MSStoreDownloadPackageDownloadSuccess << std::endl; - - // Get license - if (!context.Args.Contains(Execution::Args::Type::SkipMicrosoftStorePackageLicense)) - { - AICLI_LOG(CLI, Info, << "Getting MSStore package license"); - context.Reporter.Info() << Resource::String::MSStoreDownloadGetLicense << std::endl; - - std::vector licenseContent; - try - { - licenseContent = downloadContext.GetLicense(downloadInfo.ContentId); - } - catch (const wil::ResultException& re) - { - if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN) - { - AICLI_LOG(CLI, Warning, << "Getting MSStore package license failed. The Microsoft Entra Id account does not have privilege."); - context.Reporter.Warn() << Resource::String::MSStoreDownloadGetLicenseForbidden << std::endl; - } - else - { - AICLI_LOG(CLI, Warning, << "Getting MSStore package license failed. Error code: " << re.GetErrorCode()); - context.Reporter.Warn() << Resource::String::MSStoreDownloadGetLicenseFailed << std::endl; - } - - AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); - } - - std::filesystem::path licenseFilePath = downloadDirectory / Utility::ConvertToUTF16(installer.ProductId + "_License.xml"); - std::ofstream licenseFile(licenseFilePath, std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); - licenseFile.write((const char *)&licenseContent[0], licenseContent.size()); - licenseFile.flush(); - licenseFile.close(); - - AICLI_LOG(CLI, Info, << "Getting MSStore package license success"); - context.Reporter.Info() << Resource::String::MSStoreDownloadGetLicenseSuccess(Utility::LocIndView{ licenseFilePath.u8string() }) << std::endl; - } - } - - void EnsureStorePolicySatisfied(Execution::Context& context) - { - auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); - bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - - HRESULT hr = EnsureStorePolicySatisfiedImpl(productId, bypassStorePolicy); - if (FAILED(hr)) - { - if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY) - { - context.Reporter.Error() << Resource::String::MSStoreStoreClientBlocked << std::endl; - } - else if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY) - { - context.Reporter.Error() << Resource::String::MSStoreAppBlocked << std::endl; - } - - AICLI_TERMINATE_CONTEXT(hr); - } - } - - void VerifyIsFullPackage(Execution::Context& context) - { - if (IsStubPackage()) - { - context.Reporter.Error() << Resource::String::ExtendedFeaturesNotEnabledMessage << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); - } - } - - void EnableExtendedFeatures(Execution::Context& context) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - AppInstallerUpdate(false, true, context); -#else - if (IsStubPackage()) - { - context.Reporter.Info() << Resource::String::ExtendedFeaturesEnablingMessage << std::endl; - bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - AppInstallerUpdate(false, bypassStorePolicy, context); - } - else - { - context.Reporter.Info() << Resource::String::ExtendedFeaturesEnabledMessage << std::endl; - } -#endif - } - - void DisableExtendedFeatures(Execution::Context& context) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - AppInstallerUpdate(true, true, context); -#else - if (!IsStubPackage()) - { - context.Reporter.Info() << Resource::String::ExtendedFeaturesDisablingMessage << std::endl; - bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - AppInstallerUpdate(true, bypassStorePolicy, context); - } - else - { - context.Reporter.Info() << Resource::String::ExtendedFeaturesDisabledMessage << std::endl; - } -#endif - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "MSStoreInstallerHandler.h" +#include "WorkflowBase.h" +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::CLI::Workflow +{ + void DownloadInstallerFile(Execution::Context& context); +} + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::MSStore; + using namespace AppInstaller::SelfManagement; + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Foundation::Collections; + using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; + + namespace + { + Utility::LocIndString GetErrorCodeString(const HRESULT errorCode) + { + std::ostringstream ssError; + ssError << WINGET_OSTREAM_FORMAT_HRESULT(errorCode); + return Utility::LocIndString{ ssError.str() }; + } + + HRESULT EnsureStorePolicySatisfiedImpl(const std::wstring& productId, bool bypassPolicy) + { + constexpr std::wstring_view s_StoreClientName = L"Microsoft.WindowsStore"sv; + constexpr std::wstring_view s_StoreClientPublisher = L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US"sv; + + // Policy check + AppInstallManager installManager; + + if (!bypassPolicy && installManager.IsStoreBlockedByPolicyAsync(s_StoreClientName, s_StoreClientPublisher).get()) + { + AICLI_LOG(CLI, Error, << "Store client is blocked by policy. MSStore execution failed."); + return APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY; + } + + if (!installManager.GetIsAppAllowedToInstallAsync(productId).get()) + { + AICLI_LOG(CLI, Error, << "App is blocked by policy. MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId)); + return APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY; + } + + return S_OK; + } + + void AppInstallerUpdate(bool preferStub, bool bypassPolicy, Execution::Context& context) + { + auto appInstId = std::wstring{ s_AppInstallerProductId }; + THROW_IF_FAILED(EnsureStorePolicySatisfiedImpl(appInstId, bypassPolicy)); + SetStubPreferred(preferStub); + + auto installOperation = MSStoreOperation(MSStoreOperationType::Update, appInstId, Manifest::ScopeEnum::User, true, true); + + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) + { + hr = installOperation.StartAndWaitForOperation(progress); + }); + + THROW_IF_FAILED(hr); + } + + HRESULT DownloadMSStorePackageFile(const MSStore::MSStoreDownloadFile& downloadFile, const std::filesystem::path& downloadDirectory, Execution::Context& context) + { + try + { + // Create a sub context to execute the package download + auto subContextPtr = context.CreateSubContext(); + Execution::Context& subContext = *subContextPtr; + auto previousThreadGlobals = subContext.SetForCurrentThread(); + + // Populate Installer and temp download path for sub context + Manifest::ManifestInstaller installer; + installer.Url = downloadFile.Url; + installer.Sha256 = downloadFile.Sha256; + subContext.Add(std::move(installer)); + + auto tempInstallerPath = Runtime::GetPathTo(Runtime::PathName::Temp); + tempInstallerPath /= Utility::SHA256::ConvertToString(downloadFile.Sha256); + AICLI_LOG(CLI, Info, << "Generated temp download path: " << tempInstallerPath); + subContext.Add(tempInstallerPath); + + subContext << Workflow::DownloadInstallerFile; + if (subContext.IsTerminated()) + { + RETURN_HR(subContext.GetTerminationHR()); + } + + // Verify hash + const auto& hashPair = subContext.Get(); + if (std::equal(hashPair.first.begin(), hashPair.first.end(), hashPair.second.Sha256Hash.begin())) + { + AICLI_LOG(CLI, Info, << "Microsoft Store package hash verified"); + subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageHashVerified << std::endl; + // Trust direct download from Store if hash matched + Utility::ApplyMotwIfApplicable(tempInstallerPath, URLZONE_TRUSTED); + } + else + { + if (!subContext.Args.Contains(Execution::Args::Type::HashOverride)) + { + AICLI_LOG(CLI, Error, << "Microsoft Store package hash mismatch"); + subContext.Reporter.Error() << Resource::String::MSStoreDownloadPackageHashMismatch << std::endl; + RETURN_HR(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH); + } + else + { + AICLI_LOG(CLI, Warning, << "Microsoft Store package hash mismatch, but overridden."); + subContext.Reporter.Warn() << Resource::String::MSStoreDownloadPackageHashMismatch << std::endl; + } + } + + auto renamedDownloadedPackage = downloadDirectory / Utility::ConvertToUTF16(downloadFile.FileName); + Filesystem::RenameFile(tempInstallerPath, renamedDownloadedPackage); + subContext.Reporter.Info() << Resource::String::MSStoreDownloadPackageDownloaded(Utility::LocIndView{ renamedDownloadedPackage.u8string() }) << std::endl; + + return S_OK; + } + catch (...) + { + AICLI_LOG(CLI, Error, << "Microsoft Store package download failed. File: " << downloadFile.FileName); + context.Reporter.Error() << Resource::String::MSStoreDownloadPackageDownloadFailed(Utility::LocIndView{ downloadFile.FileName }) << std::endl; + RETURN_HR(APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED); + } + } + } + + void MSStoreInstall(Execution::Context& context) + { + auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); + auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); + bool force = context.Args.Contains(Execution::Args::Type::Force); + + auto installOperation = MSStoreOperation(MSStoreOperationType::Install, productId, scope, isSilentMode, force); + + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) + { + hr = installOperation.StartAndWaitForOperation(progress); + }); + + if (SUCCEEDED(hr)) + { + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + } + else + { + if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) + { + context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; + context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + } + else + { + auto errorCodeString = GetErrorCodeString(hr); + context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; + context.Add(hr); + AICLI_LOG(CLI, Error, << "MSStore install failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); + } + + AICLI_TERMINATE_CONTEXT(hr); + } + } + + void MSStoreUpdate(Execution::Context& context) + { + bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); + auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); + auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + bool force = context.Args.Contains(Execution::Args::Type::Force); + + auto installOperation = MSStoreOperation(MSStoreOperationType::Update, productId, scope, isSilentMode, force); + + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) + { + hr = installOperation.StartAndWaitForOperation(progress); + }); + + if (SUCCEEDED(hr)) + { + context.Reporter.Info() << Resource::String::InstallFlowInstallSuccess << std::endl; + } + else + { + if (hr == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) + { + context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl + << Resource::String::UpdateNotApplicableReason << std::endl; + } + else + { + auto errorCodeString = GetErrorCodeString(hr); + context.Reporter.Error() << Resource::String::MSStoreInstallOrUpdateFailed(errorCodeString) << std::endl; + context.Add(hr); + AICLI_LOG(CLI, Error, << "MSStore execution failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); + } + + AICLI_TERMINATE_CONTEXT(hr); + } + } + + void MSStoreRepair(Execution::Context& context) + { + auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); + auto scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + bool isSilentMode = context.Args.Contains(Execution::Args::Type::Silent); + bool force = context.Args.Contains(Execution::Args::Type::Force); + + auto repairOperation = MSStoreOperation(MSStoreOperationType::Repair, productId, scope, isSilentMode, force); + + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + HRESULT hr = S_OK; + context.Reporter.ExecuteWithProgress( + [&](IProgressCallback& progress) + { + hr = repairOperation.StartAndWaitForOperation(progress); + }); + + if (SUCCEEDED(hr)) + { + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + else + { + if (hr == APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED) + { + context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; + context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + } + else + { + auto errorCodeString = GetErrorCodeString(hr); + context.Reporter.Error() << Resource::String::MSStoreRepairFailed(errorCodeString) << std::endl; + context.Add(hr); + AICLI_LOG(CLI, Error, << "MSStore repair failed. ProductId: " << Utility::ConvertToUTF8(productId) << " HResult: " << errorCodeString); + } + + AICLI_TERMINATE_CONTEXT(hr); + } + } + + void MSStoreDownload(Execution::Context& context) + { + if (context.Args.Contains(Execution::Args::Type::Rename)) + { + context.Reporter.Warn() << Resource::String::MSStoreDownloadRenameNotSupported << std::endl; + } + + // Authentication notice + context.Reporter.Warn() << Resource::String::MSStoreDownloadAuthenticationNotice << std::endl; + context.Reporter.Warn() << Resource::String::MSStoreDownloadMultiplePackagesNotice << std::endl; + + const auto& installer = context.Get().value(); + + Utility::Architecture requiredArchitecture = Utility::Architecture::Unknown; + Manifest::PlatformEnum requiredPlatform = Manifest::PlatformEnum::Unknown; + std::string requiredLocale; + if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture)) + { + requiredArchitecture = Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture)); + } + if (context.Args.Contains(Execution::Args::Type::Platform)) + { + requiredPlatform = Manifest::ConvertToPlatformEnumForMSStoreDownload(context.Args.GetArg(Execution::Args::Type::Platform)); + } + if (context.Args.Contains(Execution::Args::Type::Locale)) + { + requiredLocale = context.Args.GetArg(Execution::Args::Type::Locale); + } + + MSStoreDownloadContext downloadContext{ installer.ProductId, requiredArchitecture, requiredPlatform, requiredLocale, GetAuthenticationArguments(context) }; + + if (context.Args.Contains(Execution::Args::Type::OSVersion)) + { + Utility::UInt64Version targetOSVersion{ std::string{ context.Args.GetArg(Execution::Args::Type::OSVersion) } }; + downloadContext.TargetOSVersion(std::move(targetOSVersion)); + } + + MSStoreDownloadInfo downloadInfo; + try + { + context.Reporter.Info() << Resource::String::MSStoreDownloadGetDownloadInfo << std::endl; + + downloadInfo = downloadContext.GetDownloadInfo(); + } + catch (const wil::ResultException& re) + { + AICLI_LOG(CLI, Error, << "Getting MSStore package download info failed. Error code: " << re.GetErrorCode()); + + switch (re.GetErrorCode()) + { + case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE: + case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE: + context.Reporter.Error() << Resource::String::MSStoreDownloadNoApplicablePackageFound << std::endl; + break; + case APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED: + context.Reporter.Error() << Resource::String::MSStoreDownloadPackageDownloadNotSupported << std::endl; + break; + default: + context.Reporter.Error() << Resource::String::MSStoreDownloadGetDownloadInfoFailed << std::endl; + } + + AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); + } + + bool skipDependencies = context.Args.Contains(Execution::Args::Type::SkipDependencies); + + // Prepare directories + std::filesystem::path downloadDirectory = context.Get(); + std::filesystem::path dependenciesDirectory = downloadDirectory / L"Dependencies"; + + // Create directories if needed. + auto directoryToCreate = (skipDependencies || downloadInfo.DependencyPackages.empty()) ? downloadDirectory : dependenciesDirectory; + if (!std::filesystem::exists(directoryToCreate)) + { + std::filesystem::create_directories(directoryToCreate); + } + else + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_CANNOT_MAKE), !std::filesystem::is_directory(directoryToCreate)); + } + + // Download dependency packages + if (!skipDependencies) + { + AICLI_LOG(CLI, Info, << "Downloading MSStore dependency packages"); + context.Reporter.Info() << Resource::String::MSStoreDownloadDependencyPackages << std::endl; + + for (auto const& dependencyPackage : downloadInfo.DependencyPackages) + { + THROW_IF_FAILED(DownloadMSStorePackageFile(dependencyPackage, dependenciesDirectory, context)); + } + } + + // Download main packages + AICLI_LOG(CLI, Info, << "Downloading MSStore main packages"); + context.Reporter.Info() << Resource::String::MSStoreDownloadMainPackages << std::endl; + for (auto const& mainPackage : downloadInfo.MainPackages) + { + THROW_IF_FAILED(DownloadMSStorePackageFile(mainPackage, downloadDirectory, context)); + } + + context.Reporter.Info() << Resource::String::MSStoreDownloadPackageDownloadSuccess << std::endl; + + // Get license + if (!context.Args.Contains(Execution::Args::Type::SkipMicrosoftStorePackageLicense)) + { + AICLI_LOG(CLI, Info, << "Getting MSStore package license"); + context.Reporter.Info() << Resource::String::MSStoreDownloadGetLicense << std::endl; + + std::vector licenseContent; + try + { + licenseContent = downloadContext.GetLicense(downloadInfo.ContentId); + } + catch (const wil::ResultException& re) + { + if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN) + { + AICLI_LOG(CLI, Warning, << "Getting MSStore package license failed. The Microsoft Entra Id account does not have privilege."); + context.Reporter.Warn() << Resource::String::MSStoreDownloadGetLicenseForbidden << std::endl; + } + else + { + AICLI_LOG(CLI, Warning, << "Getting MSStore package license failed. Error code: " << re.GetErrorCode()); + context.Reporter.Warn() << Resource::String::MSStoreDownloadGetLicenseFailed << std::endl; + } + + AICLI_TERMINATE_CONTEXT(re.GetErrorCode()); + } + + std::filesystem::path licenseFilePath = downloadDirectory / Utility::ConvertToUTF16(installer.ProductId + "_License.xml"); + std::ofstream licenseFile(licenseFilePath, std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); + licenseFile.write((const char *)&licenseContent[0], licenseContent.size()); + licenseFile.flush(); + licenseFile.close(); + + AICLI_LOG(CLI, Info, << "Getting MSStore package license success"); + context.Reporter.Info() << Resource::String::MSStoreDownloadGetLicenseSuccess(Utility::LocIndView{ licenseFilePath.u8string() }) << std::endl; + } + } + + void EnsureStorePolicySatisfied(Execution::Context& context) + { + auto productId = Utility::ConvertToUTF16(context.Get()->ProductId); + bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + + HRESULT hr = EnsureStorePolicySatisfiedImpl(productId, bypassStorePolicy); + if (FAILED(hr)) + { + if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY) + { + context.Reporter.Error() << Resource::String::MSStoreStoreClientBlocked << std::endl; + } + else if (hr == APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY) + { + context.Reporter.Error() << Resource::String::MSStoreAppBlocked << std::endl; + } + + AICLI_TERMINATE_CONTEXT(hr); + } + } + + void VerifyIsFullPackage(Execution::Context& context) + { + if (IsStubPackage()) + { + context.Reporter.Error() << Resource::String::ExtendedFeaturesNotEnabledMessage << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + } + + void EnableExtendedFeatures(Execution::Context& context) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + AppInstallerUpdate(false, true, context); +#else + if (IsStubPackage()) + { + context.Reporter.Info() << Resource::String::ExtendedFeaturesEnablingMessage << std::endl; + bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + AppInstallerUpdate(false, bypassStorePolicy, context); + } + else + { + context.Reporter.Info() << Resource::String::ExtendedFeaturesEnabledMessage << std::endl; + } +#endif + } + + void DisableExtendedFeatures(Execution::Context& context) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + AppInstallerUpdate(true, true, context); +#else + if (!IsStubPackage()) + { + context.Reporter.Info() << Resource::String::ExtendedFeaturesDisablingMessage << std::endl; + bool bypassStorePolicy = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + AppInstallerUpdate(true, bypassStorePolicy, context); + } + else + { + context.Reporter.Info() << Resource::String::ExtendedFeaturesDisabledMessage << std::endl; + } +#endif + } +} diff --git a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h index c98d227ed4..393a7b4c10 100644 --- a/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h +++ b/src/AppInstallerCLICore/Workflows/MSStoreInstallerHandler.h @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -// MSStoreInstallerHandler handles msstore installers. -namespace AppInstaller::CLI::Workflow -{ - // Deploys the Store app. - // Required Args: None - // Inputs: Installer - // Outputs: None - void MSStoreInstall(Execution::Context& context); - - // Updates the Store app if applicable. - // Required Args: None - // Inputs: Installer - // Outputs: None - void MSStoreUpdate(Execution::Context& context); - - // Attempt to repair the installation of an Store app that is already installed - // Required Args: None - // Inputs: Installer - // Outputs: None - void MSStoreRepair(Execution::Context& context); - - // Downloads the Store app installer. - // Required Args: None - // Inputs: Installer - // Outputs: None - void MSStoreDownload(Execution::Context& context); - - // Ensure the Store app is not blocked by policy. - // Required Args: None - // Inputs: Installer - // Outputs: None - void EnsureStorePolicySatisfied(Execution::Context& context); - - // Verifies the full package is installed. - // Required Args: None - // Inputs: None - // Outputs: None - void VerifyIsFullPackage(Execution::Context& context); - - // Change stub preference to full and installs full package if needed. - // Required Args: None - // Inputs: None - // Outputs: None - void EnableExtendedFeatures(Execution::Context& context); - - // Change stub preference to stub and installs stub package if needed. - // Required Args: None - // Inputs: None - // Outputs: None - void DisableExtendedFeatures(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +// MSStoreInstallerHandler handles msstore installers. +namespace AppInstaller::CLI::Workflow +{ + // Deploys the Store app. + // Required Args: None + // Inputs: Installer + // Outputs: None + void MSStoreInstall(Execution::Context& context); + + // Updates the Store app if applicable. + // Required Args: None + // Inputs: Installer + // Outputs: None + void MSStoreUpdate(Execution::Context& context); + + // Attempt to repair the installation of an Store app that is already installed + // Required Args: None + // Inputs: Installer + // Outputs: None + void MSStoreRepair(Execution::Context& context); + + // Downloads the Store app installer. + // Required Args: None + // Inputs: Installer + // Outputs: None + void MSStoreDownload(Execution::Context& context); + + // Ensure the Store app is not blocked by policy. + // Required Args: None + // Inputs: Installer + // Outputs: None + void EnsureStorePolicySatisfied(Execution::Context& context); + + // Verifies the full package is installed. + // Required Args: None + // Inputs: None + // Outputs: None + void VerifyIsFullPackage(Execution::Context& context); + + // Change stub preference to full and installs full package if needed. + // Required Args: None + // Inputs: None + // Outputs: None + void EnableExtendedFeatures(Execution::Context& context); + + // Change stub preference to stub and installs stub package if needed. + // Required Args: None + // Inputs: None + // Outputs: None + void DisableExtendedFeatures(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp b/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp index a17b8657e7..cc583899b5 100644 --- a/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/MsiInstallFlow.cpp @@ -1,63 +1,63 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "MsiInstallFlow.h" -#include -#include - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - std::optional InvokeMsiInstallProduct(const std::filesystem::path& installerPath, const Msi::MsiParsedArguments& msiArgs, IProgressCallback&) - { - if (msiArgs.LogFile) - { - THROW_IF_WIN32_ERROR(MsiEnableLogW(msiArgs.LogMode, msiArgs.LogFile->c_str(), msiArgs.LogAttributes)); - } - else - { - // Disable logging - THROW_IF_WIN32_ERROR(MsiEnableLogW(0, nullptr, 0)); - } - - // Returns old UI level. We don't need to reset it so we ignore it. - MsiSetInternalUI(msiArgs.UILevel, nullptr); - - // TODO: Use progress callback - return MsiInstallProductW(installerPath.c_str(), msiArgs.Properties.c_str()); - } - } - - void DirectMSIInstallImpl(Execution::Context& context) - { - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - - const auto& installer = context.Get(); - const std::filesystem::path& installerPath = context.Get(); - - Msi::MsiParsedArguments parsedArgs = Msi::ParseMSIArguments(context.Get()); - - // Inform of elevation requirements - if (!Runtime::IsRunningAsAdmin() && installer->ElevationRequirement == Manifest::ElevationRequirementEnum::ElevatesSelf) - { - context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl; - } - - auto installResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeMsiInstallProduct, - installerPath, - parsedArgs, - std::placeholders::_1)); - - if (!installResult) - { - context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else - { - context.Add(installResult.value()); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "MsiInstallFlow.h" +#include +#include + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + std::optional InvokeMsiInstallProduct(const std::filesystem::path& installerPath, const Msi::MsiParsedArguments& msiArgs, IProgressCallback&) + { + if (msiArgs.LogFile) + { + THROW_IF_WIN32_ERROR(MsiEnableLogW(msiArgs.LogMode, msiArgs.LogFile->c_str(), msiArgs.LogAttributes)); + } + else + { + // Disable logging + THROW_IF_WIN32_ERROR(MsiEnableLogW(0, nullptr, 0)); + } + + // Returns old UI level. We don't need to reset it so we ignore it. + MsiSetInternalUI(msiArgs.UILevel, nullptr); + + // TODO: Use progress callback + return MsiInstallProductW(installerPath.c_str(), msiArgs.Properties.c_str()); + } + } + + void DirectMSIInstallImpl(Execution::Context& context) + { + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + + const auto& installer = context.Get(); + const std::filesystem::path& installerPath = context.Get(); + + Msi::MsiParsedArguments parsedArgs = Msi::ParseMSIArguments(context.Get()); + + // Inform of elevation requirements + if (!Runtime::IsRunningAsAdmin() && installer->ElevationRequirement == Manifest::ElevationRequirementEnum::ElevatesSelf) + { + context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl; + } + + auto installResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeMsiInstallProduct, + installerPath, + parsedArgs, + std::placeholders::_1)); + + if (!installResult) + { + context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add(installResult.value()); + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/MsiInstallFlow.h b/src/AppInstallerCLICore/Workflows/MsiInstallFlow.h index 4516c8b00a..6d74afe4ad 100644 --- a/src/AppInstallerCLICore/Workflows/MsiInstallFlow.h +++ b/src/AppInstallerCLICore/Workflows/MsiInstallFlow.h @@ -1,13 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Ensures that there is an applicable installer. - // Required Args: None - // Inputs: InstallerArgs, Installer, InstallerPath, Manifest - // Outputs: OperationReturnCode - void DirectMSIInstallImpl(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Ensures that there is an applicable installer. + // Required Args: None + // Inputs: InstallerArgs, Installer, InstallerPath, Manifest + // Outputs: OperationReturnCode + void DirectMSIInstallImpl(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp index ddb863a1b7..c673b13f3d 100644 --- a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.cpp @@ -1,149 +1,149 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "MultiQueryFlow.h" -#include "UpdateFlow.h" - -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - Utility::LocIndString GetPackageStringFromSearchRequest(const SearchRequest& searchRequest) - { - if (searchRequest.Query) - { - return Utility::LocIndString{ searchRequest.Query->Value }; - } - - if (!searchRequest.Inclusions.empty()) - { - return Utility::LocIndString{ searchRequest.Inclusions[0].Value }; - } - - if (!searchRequest.Filters.empty()) - { - return Utility::LocIndString{ searchRequest.Filters[0].Value }; - } - - return ""_lis; - } - } - - void GetMultiSearchRequests(Execution::Context& context) - { - std::vector> packageSubContexts; - 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(); - - searchContext.Add(source); - searchContext.Args.AddArg(Execution::Args::Type::Query, query); - - AICLI_LOG(CLI, Info, << "Creating search query for package [" << query << "]"); - searchContext << GetSearchRequestForSingle; - - packageSubContexts.emplace_back(std::move(searchContextPtr)); - } - - context.Add(std::move(packageSubContexts)); - } - - void SearchSubContextsForSingle::operator()(Execution::Context& context) const - { - std::vector> packageSubContexts; - bool foundAll = true; - - for (auto& searchContextPtr : context.Get()) - { - auto& searchContext = *searchContextPtr; - SearchRequest searchRequest = searchContext.Get(); - searchContext.Add(searchContext.Get().Search(searchRequest)); - - switch (m_operationType) - { - case OperationType::Install: - case OperationType::Upgrade: - searchContext << Workflow::SelectSinglePackageVersionForInstallOrUpgrade(m_operationType); - break; - case OperationType::Uninstall: - searchContext << - Workflow::HandleSearchResultFailures << - Workflow::EnsureOneMatchFromSearchResult(m_operationType); - break; - default: - THROW_HR(E_UNEXPECTED); - } - - if (searchContext.IsTerminated()) - { - if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) - { - // This means that the subcontext being terminated is due to an overall abort - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return; - } - else - { - // We already reported the error from the sub-context, but we repeat it here because - // for multi-queries we can a bit more verbose as the queries here are easier to report. - auto packageString = GetPackageStringFromSearchRequest(searchRequest); - auto searchTerminationHR = searchContext.GetTerminationHR(); - if (searchTerminationHR == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE || - searchTerminationHR == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED) - { - AICLI_LOG(CLI, Info, << "Package is already installed: [" << packageString << "]"); - context.Reporter.Info() << Resource::String::MultiQueryPackageAlreadyInstalled(packageString) << std::endl; - continue; - } - else - { - if (searchTerminationHR == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND) - { - AICLI_LOG(CLI, Info, << "Package not found for query: [" << packageString << "]"); - context.Reporter.Warn() << Resource::String::MultiQueryPackageNotFound(packageString) << std::endl; - } - else if (searchTerminationHR == APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND) - { - AICLI_LOG(CLI, Info, << "Multiple packages found for query: [" << packageString << "]"); - context.Reporter.Warn() << Resource::String::MultiQuerySearchFoundMultiple(packageString) << std::endl; - } - else - { - AICLI_LOG(CLI, Info, << "Search failed for query: [" << packageString << "]"); - context.Reporter.Info() << Resource::String::MultiQuerySearchFailed(packageString) << std::endl; - } - - // Keep searching for the remaining packages and only fail at the end. - foundAll = false; - continue; - } - } - } - - packageSubContexts.emplace_back(std::move(searchContextPtr)); - } - - if (!foundAll) - { - AICLI_LOG(CLI, Info, << "Not all queries returned one result"); - if (context.Args.Contains(Execution::Args::Type::IgnoreUnavailable)) - { - AICLI_LOG(CLI, Info, << "Ignoring unavailable packages due to command line argument"); - } - else - { - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); - } - } - - context.Add(std::move(packageSubContexts)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "MultiQueryFlow.h" +#include "UpdateFlow.h" + +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + Utility::LocIndString GetPackageStringFromSearchRequest(const SearchRequest& searchRequest) + { + if (searchRequest.Query) + { + return Utility::LocIndString{ searchRequest.Query->Value }; + } + + if (!searchRequest.Inclusions.empty()) + { + return Utility::LocIndString{ searchRequest.Inclusions[0].Value }; + } + + if (!searchRequest.Filters.empty()) + { + return Utility::LocIndString{ searchRequest.Filters[0].Value }; + } + + return ""_lis; + } + } + + void GetMultiSearchRequests(Execution::Context& context) + { + std::vector> packageSubContexts; + 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(); + + searchContext.Add(source); + searchContext.Args.AddArg(Execution::Args::Type::Query, query); + + AICLI_LOG(CLI, Info, << "Creating search query for package [" << query << "]"); + searchContext << GetSearchRequestForSingle; + + packageSubContexts.emplace_back(std::move(searchContextPtr)); + } + + context.Add(std::move(packageSubContexts)); + } + + void SearchSubContextsForSingle::operator()(Execution::Context& context) const + { + std::vector> packageSubContexts; + bool foundAll = true; + + for (auto& searchContextPtr : context.Get()) + { + auto& searchContext = *searchContextPtr; + SearchRequest searchRequest = searchContext.Get(); + searchContext.Add(searchContext.Get().Search(searchRequest)); + + switch (m_operationType) + { + case OperationType::Install: + case OperationType::Upgrade: + searchContext << Workflow::SelectSinglePackageVersionForInstallOrUpgrade(m_operationType); + break; + case OperationType::Uninstall: + searchContext << + Workflow::HandleSearchResultFailures << + Workflow::EnsureOneMatchFromSearchResult(m_operationType); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + if (searchContext.IsTerminated()) + { + if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) + { + // This means that the subcontext being terminated is due to an overall abort + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + return; + } + else + { + // We already reported the error from the sub-context, but we repeat it here because + // for multi-queries we can a bit more verbose as the queries here are easier to report. + auto packageString = GetPackageStringFromSearchRequest(searchRequest); + auto searchTerminationHR = searchContext.GetTerminationHR(); + if (searchTerminationHR == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE || + searchTerminationHR == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED) + { + AICLI_LOG(CLI, Info, << "Package is already installed: [" << packageString << "]"); + context.Reporter.Info() << Resource::String::MultiQueryPackageAlreadyInstalled(packageString) << std::endl; + continue; + } + else + { + if (searchTerminationHR == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND) + { + AICLI_LOG(CLI, Info, << "Package not found for query: [" << packageString << "]"); + context.Reporter.Warn() << Resource::String::MultiQueryPackageNotFound(packageString) << std::endl; + } + else if (searchTerminationHR == APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND) + { + AICLI_LOG(CLI, Info, << "Multiple packages found for query: [" << packageString << "]"); + context.Reporter.Warn() << Resource::String::MultiQuerySearchFoundMultiple(packageString) << std::endl; + } + else + { + AICLI_LOG(CLI, Info, << "Search failed for query: [" << packageString << "]"); + context.Reporter.Info() << Resource::String::MultiQuerySearchFailed(packageString) << std::endl; + } + + // Keep searching for the remaining packages and only fail at the end. + foundAll = false; + continue; + } + } + } + + packageSubContexts.emplace_back(std::move(searchContextPtr)); + } + + if (!foundAll) + { + AICLI_LOG(CLI, Info, << "Not all queries returned one result"); + if (context.Args.Contains(Execution::Args::Type::IgnoreUnavailable)) + { + AICLI_LOG(CLI, Info, << "Ignoring unavailable packages due to command line argument"); + } + else + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); + } + } + + context.Add(std::move(packageSubContexts)); + } +} diff --git a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h index a79a241071..5c26ff2e7a 100644 --- a/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h +++ b/src/AppInstallerCLICore/Workflows/MultiQueryFlow.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -// Workflow tasks related to dealing with multiple package queries at once. - -namespace AppInstaller::CLI::Workflow -{ - // Gets the search requests for multiple queries from the command line. - // Required Args: None - // Inputs: Source - // Outputs: PackageSubContexts - // SubContext Inputs: None - // SubContext Outputs: Source, SearchRequest - void GetMultiSearchRequests(Execution::Context& context); - - // Performs searches on each of the sub-contexts with the semantics of targeting a single package for each one. - // Required Args: a value indicating the purpose of the search - // Inputs: PackageSubContexts - // Outputs: None - // SubContext Inputs: Source, SearchRequest - // SubContext Outputs: SearchResult - struct SearchSubContextsForSingle : public WorkflowTask - { - SearchSubContextsForSingle(OperationType operation = OperationType::Install) : WorkflowTask("SearchSubContextsForSingle"), m_operationType(operation) {} - - void operator()(Execution::Context& context) const override; - - private: - OperationType m_operationType; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +// Workflow tasks related to dealing with multiple package queries at once. + +namespace AppInstaller::CLI::Workflow +{ + // Gets the search requests for multiple queries from the command line. + // Required Args: None + // Inputs: Source + // Outputs: PackageSubContexts + // SubContext Inputs: None + // SubContext Outputs: Source, SearchRequest + void GetMultiSearchRequests(Execution::Context& context); + + // Performs searches on each of the sub-contexts with the semantics of targeting a single package for each one. + // Required Args: a value indicating the purpose of the search + // Inputs: PackageSubContexts + // Outputs: None + // SubContext Inputs: Source, SearchRequest + // SubContext Outputs: SearchResult + struct SearchSubContextsForSingle : public WorkflowTask + { + SearchSubContextsForSingle(OperationType operation = OperationType::Install) : WorkflowTask("SearchSubContextsForSingle"), m_operationType(operation) {} + + void operator()(Execution::Context& context) const override; + + private: + OperationType m_operationType; + }; } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.cpp b/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.cpp index 3f6b59f4d8..ea4dd5c112 100644 --- a/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.cpp +++ b/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.cpp @@ -1,172 +1,172 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackageTableSortHelper.h" -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - using namespace Settings; - - SortablePackageEntry::SortablePackageEntry( - size_t originalIndex, - std::string_view name, - std::string_view id, - std::string_view installedVersion, - std::string_view availableVersion, - std::string_view source, - SortField fieldMask) - : OriginalIndex(originalIndex) - { - if (WI_IsFlagSet(fieldMask, SortField::Name)) - { - FoldedName = Utility::FoldCase(name); - } - if (WI_IsFlagSet(fieldMask, SortField::Id)) - { - FoldedId = Utility::FoldCase(id); - } - if (WI_IsFlagSet(fieldMask, SortField::Source)) - { - FoldedSource = Utility::FoldCase(source); - } - if (WI_IsFlagSet(fieldMask, SortField::Version)) - { - ParsedInstalledVersion = Utility::Version{ std::string{ installedVersion } }; - } - if (WI_IsFlagSet(fieldMask, SortField::Available)) - { - if (!availableVersion.empty()) - { - ParsedAvailableVersion = Utility::Version{ std::string{ availableVersion } }; - } - } - } - - SortField ComputeSortFieldMask(const std::vector& sortFields) - { - SortField mask = SortField::None; - for (const auto& f : sortFields) - { - mask |= f; - } - return mask; - } - - SortParameters::SortParameters(const Execution::Context& context) - { - if (context.Args.Contains(Execution::Args::Type::Sort)) - { - for (const auto& arg : *context.Args.GetArgs(Execution::Args::Type::Sort)) - { - auto field = ConvertToSortField(arg); - WI_ASSERT(field.has_value()); - if (field.has_value()) - { - Fields.emplace_back(field.value()); - } - } - } - else - { - Fields = User().Get(); - - if (Fields.empty()) - { - bool hasQuery = context.Args.Contains(Execution::Args::Type::Query) || - context.Args.Contains(Execution::Args::Type::MultiQuery); - - if (hasQuery) - { - // Preserve relevance ordering when a free-text query is present - // and no explicit sort preference is configured. - return; // ShouldSort remains false - } - - // No settings, no query — default to name sort. - Fields.emplace_back(SortField::Name); - } - } - - // Relevance-only means preserve source ordering. - if (Fields.size() == 1 && Fields[0] == SortField::Relevance) - { - return; // ShouldSort remains false - } - - // Resolve direction: CLI flags override settings - Direction = context.Args.Contains(Execution::Args::Type::SortDescending) ? SortDirection::Descending : - context.Args.Contains(Execution::Args::Type::SortAscending) ? SortDirection::Ascending : - User().Get(); - - ShouldSort = true; - } - - int CompareByField(const SortablePackageEntry& a, const SortablePackageEntry& b, SortField field) - { - switch (field) - { - case SortField::Name: - return a.FoldedName.compare(b.FoldedName); - case SortField::Id: - return a.FoldedId.compare(b.FoldedId); - case SortField::Version: - { - if (a.ParsedInstalledVersion < b.ParsedInstalledVersion) return -1; - if (b.ParsedInstalledVersion < a.ParsedInstalledVersion) return 1; - return 0; - } - case SortField::Source: - return a.FoldedSource.compare(b.FoldedSource); - case SortField::Available: - { - bool aHas = a.ParsedAvailableVersion.has_value(); - bool bHas = b.ParsedAvailableVersion.has_value(); - - // Has-version sorts before no-version in ascending order. - if (aHas != bHas) - { - return aHas ? -1 : 1; - } - - // Both have versions — compare normally. - if (aHas && bHas) - { - if (a.ParsedAvailableVersion.value() < b.ParsedAvailableVersion.value()) return -1; - if (b.ParsedAvailableVersion.value() < a.ParsedAvailableVersion.value()) return 1; - } - return 0; - } - case SortField::Relevance: - // Relevance has no precomputed sort key — preserve original ordering. - return 0; - default: - WI_ASSERT(false); - return 0; - } - } - - void SortEntries( - std::vector& entries, - const SortParameters& sortParams) - { - if (entries.size() <= 1 || !sortParams.ShouldSort) - { - return; - } - - std::stable_sort(entries.begin(), entries.end(), - [&sortParams](const SortablePackageEntry& a, const SortablePackageEntry& b) - { - for (const auto& field : sortParams.Fields) - { - int cmp = CompareByField(a, b, field); - if (cmp != 0) - { - return sortParams.Direction == SortDirection::Ascending ? (cmp < 0) : (cmp > 0); - } - } - return false; - }); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageTableSortHelper.h" +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + using namespace Settings; + + SortablePackageEntry::SortablePackageEntry( + size_t originalIndex, + std::string_view name, + std::string_view id, + std::string_view installedVersion, + std::string_view availableVersion, + std::string_view source, + SortField fieldMask) + : OriginalIndex(originalIndex) + { + if (WI_IsFlagSet(fieldMask, SortField::Name)) + { + FoldedName = Utility::FoldCase(name); + } + if (WI_IsFlagSet(fieldMask, SortField::Id)) + { + FoldedId = Utility::FoldCase(id); + } + if (WI_IsFlagSet(fieldMask, SortField::Source)) + { + FoldedSource = Utility::FoldCase(source); + } + if (WI_IsFlagSet(fieldMask, SortField::Version)) + { + ParsedInstalledVersion = Utility::Version{ std::string{ installedVersion } }; + } + if (WI_IsFlagSet(fieldMask, SortField::Available)) + { + if (!availableVersion.empty()) + { + ParsedAvailableVersion = Utility::Version{ std::string{ availableVersion } }; + } + } + } + + SortField ComputeSortFieldMask(const std::vector& sortFields) + { + SortField mask = SortField::None; + for (const auto& f : sortFields) + { + mask |= f; + } + return mask; + } + + SortParameters::SortParameters(const Execution::Context& context) + { + if (context.Args.Contains(Execution::Args::Type::Sort)) + { + for (const auto& arg : *context.Args.GetArgs(Execution::Args::Type::Sort)) + { + auto field = ConvertToSortField(arg); + WI_ASSERT(field.has_value()); + if (field.has_value()) + { + Fields.emplace_back(field.value()); + } + } + } + else + { + Fields = User().Get(); + + if (Fields.empty()) + { + bool hasQuery = context.Args.Contains(Execution::Args::Type::Query) || + context.Args.Contains(Execution::Args::Type::MultiQuery); + + if (hasQuery) + { + // Preserve relevance ordering when a free-text query is present + // and no explicit sort preference is configured. + return; // ShouldSort remains false + } + + // No settings, no query — default to name sort. + Fields.emplace_back(SortField::Name); + } + } + + // Relevance-only means preserve source ordering. + if (Fields.size() == 1 && Fields[0] == SortField::Relevance) + { + return; // ShouldSort remains false + } + + // Resolve direction: CLI flags override settings + Direction = context.Args.Contains(Execution::Args::Type::SortDescending) ? SortDirection::Descending : + context.Args.Contains(Execution::Args::Type::SortAscending) ? SortDirection::Ascending : + User().Get(); + + ShouldSort = true; + } + + int CompareByField(const SortablePackageEntry& a, const SortablePackageEntry& b, SortField field) + { + switch (field) + { + case SortField::Name: + return a.FoldedName.compare(b.FoldedName); + case SortField::Id: + return a.FoldedId.compare(b.FoldedId); + case SortField::Version: + { + if (a.ParsedInstalledVersion < b.ParsedInstalledVersion) return -1; + if (b.ParsedInstalledVersion < a.ParsedInstalledVersion) return 1; + return 0; + } + case SortField::Source: + return a.FoldedSource.compare(b.FoldedSource); + case SortField::Available: + { + bool aHas = a.ParsedAvailableVersion.has_value(); + bool bHas = b.ParsedAvailableVersion.has_value(); + + // Has-version sorts before no-version in ascending order. + if (aHas != bHas) + { + return aHas ? -1 : 1; + } + + // Both have versions — compare normally. + if (aHas && bHas) + { + if (a.ParsedAvailableVersion.value() < b.ParsedAvailableVersion.value()) return -1; + if (b.ParsedAvailableVersion.value() < a.ParsedAvailableVersion.value()) return 1; + } + return 0; + } + case SortField::Relevance: + // Relevance has no precomputed sort key — preserve original ordering. + return 0; + default: + WI_ASSERT(false); + return 0; + } + } + + void SortEntries( + std::vector& entries, + const SortParameters& sortParams) + { + if (entries.size() <= 1 || !sortParams.ShouldSort) + { + return; + } + + std::stable_sort(entries.begin(), entries.end(), + [&sortParams](const SortablePackageEntry& a, const SortablePackageEntry& b) + { + for (const auto& field : sortParams.Fields) + { + int cmp = CompareByField(a, b, field); + if (cmp != 0) + { + return sortParams.Direction == SortDirection::Ascending ? (cmp < 0) : (cmp > 0); + } + } + return false; + }); + } +} diff --git a/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.h b/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.h index 018dc55c15..a93d25d57c 100644 --- a/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.h +++ b/src/AppInstallerCLICore/Workflows/PackageTableSortHelper.h @@ -1,108 +1,108 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include -#include - -namespace AppInstaller::CLI::Execution { struct Context; } - -namespace AppInstaller::CLI::Workflow -{ - // Lightweight sortable representation of a package row with precomputed sort keys. - // Decoupled from ICompositePackage/IPackageVersion to ease unit testing. - struct SortablePackageEntry - { - size_t OriginalIndex = 0; - - // Precomputed case-folded sort keys - std::string FoldedName; - std::string FoldedId; - std::string FoldedSource; - - // Precomputed parsed versions - Utility::Version ParsedInstalledVersion; - std::optional ParsedAvailableVersion; - - SortablePackageEntry() = default; - - SortablePackageEntry( - size_t originalIndex, - std::string_view name, - std::string_view id, - std::string_view installedVersion, - std::string_view availableVersion, - std::string_view source, - Settings::SortField fieldMask); - }; - - // Compares two sortable entries by the given field using precomputed sort keys. - // Returns negative if a < b, positive if a > b, 0 if equal. - int CompareByField(const SortablePackageEntry& a, const SortablePackageEntry& b, Settings::SortField field); - - // Computes a bitmask of all sort fields so the constructor can skip unused fields. - Settings::SortField ComputeSortFieldMask(const std::vector& sortFields); - - // Result of sort parameter resolution. If ShouldSort is false, the caller should - // preserve the current ordering (relevance or no-op). - struct SortParameters - { - bool ShouldSort = false; - std::vector Fields; - Settings::SortDirection Direction = Settings::SortDirection::Ascending; - - // Default constructor leaves ShouldSort=false. Used by unit tests to exercise - // sort algorithm logic independently from CLI/settings parameter resolution. - SortParameters() = default; - - // Resolves the effective sort parameters by reading CLI args, user settings, - // and query context directly from the execution context. - // Resolution order: explicit --sort args > settings > query-aware default. - explicit SortParameters(const Execution::Context& context); - }; - - // Sorts a vector of sortable entries using the resolved sort parameters. - void SortEntries( - std::vector& entries, - const SortParameters& sortParams); - - // Sorts a vector of arbitrary items by projecting each into a SortablePackageEntry - // via a caller-supplied converter, sorting the projections, then reordering the - // source vector to match. The converter signature is: - // SortablePackageEntry converter(const T& item, size_t index) - // The caller is responsible for pre-computing the SortFieldMask and capturing - // it in the converter closure so the SortablePackageEntry constructor only - // initializes the fields actually needed for comparison. - template - void SortBy( - std::vector& items, - Converter&& converter, - const SortParameters& sortParams) - { - if (items.size() <= 1 || !sortParams.ShouldSort) - { - return; - } - - std::vector entries; - entries.reserve(items.size()); - for (size_t i = 0; i < items.size(); ++i) - { - entries.push_back(converter(items[i], i)); - } - - SortEntries(entries, sortParams); - - std::vector sorted; - sorted.reserve(items.size()); - for (const auto& entry : entries) - { - sorted.push_back(std::move(items[entry.OriginalIndex])); - } - items = std::move(sorted); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include +#include + +namespace AppInstaller::CLI::Execution { struct Context; } + +namespace AppInstaller::CLI::Workflow +{ + // Lightweight sortable representation of a package row with precomputed sort keys. + // Decoupled from ICompositePackage/IPackageVersion to ease unit testing. + struct SortablePackageEntry + { + size_t OriginalIndex = 0; + + // Precomputed case-folded sort keys + std::string FoldedName; + std::string FoldedId; + std::string FoldedSource; + + // Precomputed parsed versions + Utility::Version ParsedInstalledVersion; + std::optional ParsedAvailableVersion; + + SortablePackageEntry() = default; + + SortablePackageEntry( + size_t originalIndex, + std::string_view name, + std::string_view id, + std::string_view installedVersion, + std::string_view availableVersion, + std::string_view source, + Settings::SortField fieldMask); + }; + + // Compares two sortable entries by the given field using precomputed sort keys. + // Returns negative if a < b, positive if a > b, 0 if equal. + int CompareByField(const SortablePackageEntry& a, const SortablePackageEntry& b, Settings::SortField field); + + // Computes a bitmask of all sort fields so the constructor can skip unused fields. + Settings::SortField ComputeSortFieldMask(const std::vector& sortFields); + + // Result of sort parameter resolution. If ShouldSort is false, the caller should + // preserve the current ordering (relevance or no-op). + struct SortParameters + { + bool ShouldSort = false; + std::vector Fields; + Settings::SortDirection Direction = Settings::SortDirection::Ascending; + + // Default constructor leaves ShouldSort=false. Used by unit tests to exercise + // sort algorithm logic independently from CLI/settings parameter resolution. + SortParameters() = default; + + // Resolves the effective sort parameters by reading CLI args, user settings, + // and query context directly from the execution context. + // Resolution order: explicit --sort args > settings > query-aware default. + explicit SortParameters(const Execution::Context& context); + }; + + // Sorts a vector of sortable entries using the resolved sort parameters. + void SortEntries( + std::vector& entries, + const SortParameters& sortParams); + + // Sorts a vector of arbitrary items by projecting each into a SortablePackageEntry + // via a caller-supplied converter, sorting the projections, then reordering the + // source vector to match. The converter signature is: + // SortablePackageEntry converter(const T& item, size_t index) + // The caller is responsible for pre-computing the SortFieldMask and capturing + // it in the converter closure so the SortablePackageEntry constructor only + // initializes the fields actually needed for comparison. + template + void SortBy( + std::vector& items, + Converter&& converter, + const SortParameters& sortParams) + { + if (items.size() <= 1 || !sortParams.ShouldSort) + { + return; + } + + std::vector entries; + entries.reserve(items.size()); + for (size_t i = 0; i < items.size(); ++i) + { + entries.push_back(converter(items[i], i)); + } + + SortEntries(entries, sortParams); + + std::vector sorted; + sorted.reserve(items.size()); + for (const auto& entry : entries) + { + sorted.push_back(std::move(items[entry.OriginalIndex])); + } + items = std::move(sorted); + } +} diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.cpp b/src/AppInstallerCLICore/Workflows/PinFlow.cpp index ace205fc44..b2522b6e0a 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PinFlow.cpp @@ -1,338 +1,338 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Resources.h" -#include "PinFlow.h" -#include "TableOutput.h" -#include -#include -#include - -using namespace AppInstaller::Repository; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - // Creates a Pin appropriate for the context based on the arguments provided - Pinning::Pin CreatePin(Execution::Context& context, const Pinning::PinKey& pinKey) - { - if (context.Args.Contains(Execution::Args::Type::GatedVersion)) - { - return Pinning::Pin::CreateGatingPin(pinKey, context.Args.GetArg(Execution::Args::Type::GatedVersion)); - } - else if (context.Args.Contains(Execution::Args::Type::BlockingPin)) - { - return Pinning::Pin::CreateBlockingPin(pinKey); - } - else - { - return Pinning::Pin::CreatePinningPin(pinKey); - } - } - - void GetPinKeysForInstalled(const std::shared_ptr& installedVersion, std::set& pinKeys) - { - auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); - std::vector propertyStrings; - - if (Manifest::DoesInstallerTypeUsePackageFamilyName(installedType)) - { - propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); - } - else if (Manifest::DoesInstallerTypeUseProductCode(installedType)) - { - propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - } - - for (const auto& value : propertyStrings) - { - pinKeys.emplace(Pinning::PinKey::GetPinKeyForInstalled(value)); - } - } - - std::set GetPinKeysForPackage(Execution::Context& context) - { - auto package = context.Get(); - - std::set pinKeys; - - if (context.Args.Contains(Execution::Args::Type::PinInstalled)) - { - auto installedVersion = GetInstalledVersion(package); - if (installedVersion) - { - GetPinKeysForInstalled(installedVersion, pinKeys); - } - } - else - { - auto availablePackages = package->GetAvailable(); - for (const auto& availablePackage : availablePackages) - { - pinKeys.emplace( - availablePackage->GetProperty(PackageProperty::Id).get(), - availablePackage->GetSource().GetIdentifier()); - } - } - - return pinKeys; - } - - // Gets a search request that can be used to find the installed package that corresponds with a pin. - SearchRequest GetSearchRequestForPin(const Pinning::PinKey& pinKey) - { - SearchRequest searchRequest; - if (pinKey.IsForInstalled()) - { - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pinKey.PackageId)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pinKey.PackageId)); - } - else - { - searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pinKey.PackageId); - } - - return searchRequest; - } - } - - void OpenPinningIndex::operator()(Execution::Context& context) const - { - auto pinningData = Pinning::PinningData{ m_readOnly ? Pinning::PinningData::Disposition::ReadOnly : Pinning::PinningData::Disposition::ReadWrite }; - if (!m_readOnly && !pinningData) - { - AICLI_LOG(CLI, Error, << "Unable to open pinning index."); - context.Reporter.Error() << Resource::String::PinCannotOpenIndex << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX); - } - - context.Add(std::move(pinningData)); - } - - void GetAllPins(Execution::Context& context) - { - AICLI_LOG(CLI, Info, << "Getting all existing pins"); - context.Add(context.Get().GetAllPins()); - } - - void SearchPin(Execution::Context& context) - { - auto pinKeys = GetPinKeysForPackage(context); - - auto package = context.Get(); - auto pinningData = context.Get(); - - std::vector pins; - for (const auto& pinKey : pinKeys) - { - auto pin = pinningData.GetPin(pinKey); - if (pin) - { - pins.emplace_back(std::move(pin.value())); - } - } - - context.Add(std::move(pins)); - } - - void AddPin(Execution::Context& context) - { - auto pinKeys = GetPinKeysForPackage(context); - - auto package = context.Get(); - auto pinningData = context.Get(); - auto installedVersion = context.Get(); - - std::vector pinsToAddOrUpdate; - for (const auto& pinKey : pinKeys) - { - auto pin = CreatePin(context, pinKey); - AICLI_LOG(CLI, Info, << "Evaluating Pin " << pin.ToString()); - - auto existingPin = pinningData.GetPin(pinKey); - if (existingPin) - { - Utility::LocIndString packageNameToReport; - if (pinKey.IsForInstalled() && installedVersion) - { - packageNameToReport = installedVersion->GetProperty(PackageVersionProperty::Name); - } - else - { - auto availableVersion = GetAvailablePackageFromSource(package, pinKey.SourceId)->GetLatestVersion(); - if (availableVersion) - { - packageNameToReport = availableVersion->GetProperty(PackageVersionProperty::Name); - } - } - - // Pin already exists. - // If it is the same, we do nothing. If it is different, check for the --force arg - if (pin == existingPin) - { - AICLI_LOG(CLI, Info, << "Pin already exists"); - context.Reporter.Info() << Resource::String::PinAlreadyExists(packageNameToReport) << std::endl; - continue; - } - - AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId); - if (context.Args.Contains(Execution::Args::Type::Force)) - { - AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); - context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageNameToReport) << std::endl; - pinsToAddOrUpdate.push_back(std::move(pin)); - } - else - { - context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageNameToReport) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); - } - } - else - { - pinsToAddOrUpdate.push_back(std::move(pin)); - } - } - - if (!pinsToAddOrUpdate.empty()) - { - for (const auto& pin : pinsToAddOrUpdate) - - { - pinningData.AddOrUpdatePin(pin); - } - - context.Reporter.Info() << Resource::String::PinAdded << std::endl; - } - } - - void RemovePin(Execution::Context& context) - { - auto package = context.Get(); - auto pins = context.Get(); - - auto pinningData = context.Get(); - bool pinExists = false; - - // Note that if a source was specified in the command line, - // that will be the only one we get version keys from. - // So, we remove pins from all sources unless one was provided. - for (const auto& pin : pins) - { - AICLI_LOG(CLI, Info, << "Removing Pin " << pin.GetKey().ToString()); - pinningData.RemovePin(pin.GetKey()); - pinExists = true; - } - - if (!pinExists) - { - AICLI_LOG(CLI, Warning, << "Pin does not exist"); - context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); - } - - context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl; - } - - void ReportPins(Execution::Context& context) - { - const auto& pins = context.Get(); - if (pins.empty()) - { - context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; - return; - } - - Execution::TableOutput<6> table(context.Reporter, - { - Resource::String::SearchName, - Resource::String::SearchId, - Resource::String::SearchVersion, - Resource::String::SearchSource, - Resource::String::PinType, - Resource::String::PinVersion, - }); - - const auto& source = context.Get(); - for (const auto& pin : pins) - { - const auto& pinKey = pin.GetKey(); - auto searchRequest = GetSearchRequestForPin(pin.GetKey()); - auto searchResult = source.Search(searchRequest); - for (const auto& match : searchResult.Matches) - { - Utility::LocIndString packageName; - Utility::LocIndString sourceName; - Utility::LocIndString version; - - if (pinKey.IsForInstalled()) - { - sourceName = Resource::LocString{ Resource::String::PinInstalledSource }; - } - else - { - // This ensures we get the info from the right source if it exists on multiple - auto availablePackage = GetAvailablePackageFromSource(match.Package, pinKey.SourceId); - if (availablePackage) - { - auto availableVersion = availablePackage->GetLatestVersion(); - if (availableVersion) - { - packageName = availableVersion->GetProperty(PackageVersionProperty::Name); - sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName); - } - } - } - - auto installedVersion = GetInstalledVersion(match.Package); - if (installedVersion) - { - packageName = installedVersion->GetProperty(PackageVersionProperty::Name); - version = installedVersion->GetProperty(PackageVersionProperty::Version); - } - - table.OutputLine({ - packageName, - pinKey.PackageId, - version, - sourceName, - std::string{ ToString(pin.GetType()) }, - pin.GetGatedVersion().ToString(), - }); - } - } - - table.Complete(); - } - - void ResetAllPins(Execution::Context& context) - { - AICLI_LOG(CLI, Info, << "Resetting all pins"); - context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; - - std::string sourceId; - if (context.Args.Contains(Execution::Args::Type::Source)) - { - auto sourceName = context.Args.GetArg(Execution::Args::Type::Source); - auto sources = Source::GetCurrentSources(); - for (const auto& source : sources) - { - if (Utility::CaseInsensitiveEquals(source.Name, sourceName)) - { - sourceId = source.Identifier; - break; - } - } - } - - if (context.Get().ResetAllPins(sourceId)) - { - context.Reporter.Info() << Resource::String::PinResetSuccessful << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; - } - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Resources.h" +#include "PinFlow.h" +#include "TableOutput.h" +#include +#include +#include + +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + // Creates a Pin appropriate for the context based on the arguments provided + Pinning::Pin CreatePin(Execution::Context& context, const Pinning::PinKey& pinKey) + { + if (context.Args.Contains(Execution::Args::Type::GatedVersion)) + { + return Pinning::Pin::CreateGatingPin(pinKey, context.Args.GetArg(Execution::Args::Type::GatedVersion)); + } + else if (context.Args.Contains(Execution::Args::Type::BlockingPin)) + { + return Pinning::Pin::CreateBlockingPin(pinKey); + } + else + { + return Pinning::Pin::CreatePinningPin(pinKey); + } + } + + void GetPinKeysForInstalled(const std::shared_ptr& installedVersion, std::set& pinKeys) + { + auto installedType = Manifest::ConvertToInstallerTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); + std::vector propertyStrings; + + if (Manifest::DoesInstallerTypeUsePackageFamilyName(installedType)) + { + propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + } + else if (Manifest::DoesInstallerTypeUseProductCode(installedType)) + { + propertyStrings = installedVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + } + + for (const auto& value : propertyStrings) + { + pinKeys.emplace(Pinning::PinKey::GetPinKeyForInstalled(value)); + } + } + + std::set GetPinKeysForPackage(Execution::Context& context) + { + auto package = context.Get(); + + std::set pinKeys; + + if (context.Args.Contains(Execution::Args::Type::PinInstalled)) + { + auto installedVersion = GetInstalledVersion(package); + if (installedVersion) + { + GetPinKeysForInstalled(installedVersion, pinKeys); + } + } + else + { + auto availablePackages = package->GetAvailable(); + for (const auto& availablePackage : availablePackages) + { + pinKeys.emplace( + availablePackage->GetProperty(PackageProperty::Id).get(), + availablePackage->GetSource().GetIdentifier()); + } + } + + return pinKeys; + } + + // Gets a search request that can be used to find the installed package that corresponds with a pin. + SearchRequest GetSearchRequestForPin(const Pinning::PinKey& pinKey) + { + SearchRequest searchRequest; + if (pinKey.IsForInstalled()) + { + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pinKey.PackageId)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pinKey.PackageId)); + } + else + { + searchRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, pinKey.PackageId); + } + + return searchRequest; + } + } + + void OpenPinningIndex::operator()(Execution::Context& context) const + { + auto pinningData = Pinning::PinningData{ m_readOnly ? Pinning::PinningData::Disposition::ReadOnly : Pinning::PinningData::Disposition::ReadWrite }; + if (!m_readOnly && !pinningData) + { + AICLI_LOG(CLI, Error, << "Unable to open pinning index."); + context.Reporter.Error() << Resource::String::PinCannotOpenIndex << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX); + } + + context.Add(std::move(pinningData)); + } + + void GetAllPins(Execution::Context& context) + { + AICLI_LOG(CLI, Info, << "Getting all existing pins"); + context.Add(context.Get().GetAllPins()); + } + + void SearchPin(Execution::Context& context) + { + auto pinKeys = GetPinKeysForPackage(context); + + auto package = context.Get(); + auto pinningData = context.Get(); + + std::vector pins; + for (const auto& pinKey : pinKeys) + { + auto pin = pinningData.GetPin(pinKey); + if (pin) + { + pins.emplace_back(std::move(pin.value())); + } + } + + context.Add(std::move(pins)); + } + + void AddPin(Execution::Context& context) + { + auto pinKeys = GetPinKeysForPackage(context); + + auto package = context.Get(); + auto pinningData = context.Get(); + auto installedVersion = context.Get(); + + std::vector pinsToAddOrUpdate; + for (const auto& pinKey : pinKeys) + { + auto pin = CreatePin(context, pinKey); + AICLI_LOG(CLI, Info, << "Evaluating Pin " << pin.ToString()); + + auto existingPin = pinningData.GetPin(pinKey); + if (existingPin) + { + Utility::LocIndString packageNameToReport; + if (pinKey.IsForInstalled() && installedVersion) + { + packageNameToReport = installedVersion->GetProperty(PackageVersionProperty::Name); + } + else + { + auto availableVersion = GetAvailablePackageFromSource(package, pinKey.SourceId)->GetLatestVersion(); + if (availableVersion) + { + packageNameToReport = availableVersion->GetProperty(PackageVersionProperty::Name); + } + } + + // Pin already exists. + // If it is the same, we do nothing. If it is different, check for the --force arg + if (pin == existingPin) + { + AICLI_LOG(CLI, Info, << "Pin already exists"); + context.Reporter.Info() << Resource::String::PinAlreadyExists(packageNameToReport) << std::endl; + continue; + } + + AICLI_LOG(CLI, Info, << "Another pin already exists for the package for source " << pinKey.SourceId); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Overwriting pin due to --force argument"); + context.Reporter.Warn() << Resource::String::PinExistsOverwriting(packageNameToReport) << std::endl; + pinsToAddOrUpdate.push_back(std::move(pin)); + } + else + { + context.Reporter.Error() << Resource::String::PinExistsUseForceArg(packageNameToReport) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + } + } + else + { + pinsToAddOrUpdate.push_back(std::move(pin)); + } + } + + if (!pinsToAddOrUpdate.empty()) + { + for (const auto& pin : pinsToAddOrUpdate) + + { + pinningData.AddOrUpdatePin(pin); + } + + context.Reporter.Info() << Resource::String::PinAdded << std::endl; + } + } + + void RemovePin(Execution::Context& context) + { + auto package = context.Get(); + auto pins = context.Get(); + + auto pinningData = context.Get(); + bool pinExists = false; + + // Note that if a source was specified in the command line, + // that will be the only one we get version keys from. + // So, we remove pins from all sources unless one was provided. + for (const auto& pin : pins) + { + AICLI_LOG(CLI, Info, << "Removing Pin " << pin.GetKey().ToString()); + pinningData.RemovePin(pin.GetKey()); + pinExists = true; + } + + if (!pinExists) + { + AICLI_LOG(CLI, Warning, << "Pin does not exist"); + context.Reporter.Warn() << Resource::String::PinDoesNotExist(package->GetProperty(PackageProperty::Name)) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); + } + + context.Reporter.Info() << Resource::String::PinRemovedSuccessfully << std::endl; + } + + void ReportPins(Execution::Context& context) + { + const auto& pins = context.Get(); + if (pins.empty()) + { + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; + return; + } + + Execution::TableOutput<6> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchVersion, + Resource::String::SearchSource, + Resource::String::PinType, + Resource::String::PinVersion, + }); + + const auto& source = context.Get(); + for (const auto& pin : pins) + { + const auto& pinKey = pin.GetKey(); + auto searchRequest = GetSearchRequestForPin(pin.GetKey()); + auto searchResult = source.Search(searchRequest); + for (const auto& match : searchResult.Matches) + { + Utility::LocIndString packageName; + Utility::LocIndString sourceName; + Utility::LocIndString version; + + if (pinKey.IsForInstalled()) + { + sourceName = Resource::LocString{ Resource::String::PinInstalledSource }; + } + else + { + // This ensures we get the info from the right source if it exists on multiple + auto availablePackage = GetAvailablePackageFromSource(match.Package, pinKey.SourceId); + if (availablePackage) + { + auto availableVersion = availablePackage->GetLatestVersion(); + if (availableVersion) + { + packageName = availableVersion->GetProperty(PackageVersionProperty::Name); + sourceName = availableVersion->GetProperty(PackageVersionProperty::SourceName); + } + } + } + + auto installedVersion = GetInstalledVersion(match.Package); + if (installedVersion) + { + packageName = installedVersion->GetProperty(PackageVersionProperty::Name); + version = installedVersion->GetProperty(PackageVersionProperty::Version); + } + + table.OutputLine({ + packageName, + pinKey.PackageId, + version, + sourceName, + std::string{ ToString(pin.GetType()) }, + pin.GetGatedVersion().ToString(), + }); + } + } + + table.Complete(); + } + + void ResetAllPins(Execution::Context& context) + { + AICLI_LOG(CLI, Info, << "Resetting all pins"); + context.Reporter.Info() << Resource::String::PinResettingAll << std::endl; + + std::string sourceId; + if (context.Args.Contains(Execution::Args::Type::Source)) + { + auto sourceName = context.Args.GetArg(Execution::Args::Type::Source); + auto sources = Source::GetCurrentSources(); + for (const auto& source : sources) + { + if (Utility::CaseInsensitiveEquals(source.Name, sourceName)) + { + sourceId = source.Identifier; + break; + } + } + } + + if (context.Get().ResetAllPins(sourceId)) + { + context.Reporter.Info() << Resource::String::PinResetSuccessful << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::PinNoPinsExist << std::endl; + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLICore/Workflows/PinFlow.h b/src/AppInstallerCLICore/Workflows/PinFlow.h index 2e0e55a5db..e22f49f7e7 100644 --- a/src/AppInstallerCLICore/Workflows/PinFlow.h +++ b/src/AppInstallerCLICore/Workflows/PinFlow.h @@ -1,62 +1,62 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Opens the pinning index for use in future operations. - // Required Args: None - // Inputs: None - // Outputs: PinningIndex - struct OpenPinningIndex : public WorkflowTask - { - OpenPinningIndex(bool readOnly = false) : WorkflowTask("OpenPinningIndex"), m_readOnly(readOnly) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_readOnly; - }; - - // Gets all the pins from the index. - // Required Args: None - // Inputs: PinningIndex - // Outputs: Pins - void GetAllPins(Execution::Context& context); - - // Searches for all the pins associated with a package. - // There may be several if a package is available from multiple sources - // or if the pin is for the installed package. - // Required Args: None - // Inputs: PinningIndex, Package - // Outputs: Pins - void SearchPin(Execution::Context& context); - - // Adds a pin for the current package. - // A separate pin will be added for each source. - // Required Args: None - // Inputs: PinningIndex, Package, InstalledVersion? - // Outputs: None - void AddPin(Execution::Context& context); - - // Removes all the pins associated with a package. - // Required Args: None - // Inputs: PinningIndex, Package, InstalledPackageVersion - // Outputs: None - void RemovePin(Execution::Context& context); - - // Report the pins in a table. - // This includes searching for the corresponding installed packages - // to be able to show more info, like the package name. - // Required Args: None - // Inputs: Pins - // Outputs: None - void ReportPins(Execution::Context& context); - - // Resets all the existing pins. - // Required Args: None - // Inputs: None - // Outputs: None - void ResetAllPins(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Opens the pinning index for use in future operations. + // Required Args: None + // Inputs: None + // Outputs: PinningIndex + struct OpenPinningIndex : public WorkflowTask + { + OpenPinningIndex(bool readOnly = false) : WorkflowTask("OpenPinningIndex"), m_readOnly(readOnly) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_readOnly; + }; + + // Gets all the pins from the index. + // Required Args: None + // Inputs: PinningIndex + // Outputs: Pins + void GetAllPins(Execution::Context& context); + + // Searches for all the pins associated with a package. + // There may be several if a package is available from multiple sources + // or if the pin is for the installed package. + // Required Args: None + // Inputs: PinningIndex, Package + // Outputs: Pins + void SearchPin(Execution::Context& context); + + // Adds a pin for the current package. + // A separate pin will be added for each source. + // Required Args: None + // Inputs: PinningIndex, Package, InstalledVersion? + // Outputs: None + void AddPin(Execution::Context& context); + + // Removes all the pins associated with a package. + // Required Args: None + // Inputs: PinningIndex, Package, InstalledPackageVersion + // Outputs: None + void RemovePin(Execution::Context& context); + + // Report the pins in a table. + // This includes searching for the corresponding installed packages + // to be able to show more info, like the package name. + // Required Args: None + // Inputs: Pins + // Outputs: None + void ReportPins(Execution::Context& context); + + // Resets all the existing pins. + // Required Args: None + // Inputs: None + // Outputs: None + void ResetAllPins(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/PortableFlow.cpp b/src/AppInstallerCLICore/Workflows/PortableFlow.cpp index 3f8f852f17..7d58b77110 100644 --- a/src/AppInstallerCLICore/Workflows/PortableFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/PortableFlow.cpp @@ -273,7 +273,7 @@ namespace AppInstaller::CLI::Workflow } } - portableInstaller.Install(installType); + portableInstaller.Install(installType); context.Add({ portableInstaller.GetAppsAndFeaturesEntry() }); context.Add(ERROR_SUCCESS); context.Reporter.Warn() << portableInstaller.GetOutputMessage(); diff --git a/src/AppInstallerCLICore/Workflows/RepairFlow.cpp b/src/AppInstallerCLICore/Workflows/RepairFlow.cpp index 77e0b29abd..404c0e3c79 100644 --- a/src/AppInstallerCLICore/Workflows/RepairFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/RepairFlow.cpp @@ -1,559 +1,559 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RepairFlow.h" -#include "Workflows/ShellExecuteInstallerHandler.h" -#include "Workflows/WorkflowBase.h" -#include "Workflows/DownloadFlow.h" -#include "Workflows/ArchiveFlow.h" -#include "Workflows/InstallFlow.h" -#include "Workflows/PromptFlow.h" -#include "winget/ManifestCommon.h" -#include "AppInstallerDeployment.h" -#include "AppInstallerMsixInfo.h" -#include "AppInstallerSynchronization.h" -#include "MSStoreInstallerHandler.h" -#include -#include - -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Msix; -using namespace AppInstaller::Repository; -using namespace AppInstaller::CLI::Execution; - -namespace AppInstaller::CLI::Workflow -{ - // Internal implementation details - namespace - { - - void SetUninstallStringInContext(Execution::Context& context) - { - const auto& installedPackageVersion = context.Get(); - IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); - - // Default to silent unless it is not present or interactivity is requested - auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand); - - if ((!context.Args.Contains(Execution::Args::Type::Silent) && uninstallCommandItr == packageMetadata.end()) - || context.Args.Contains(Execution::Args::Type::Interactive)) - { - auto interactiveItr = packageMetadata.find(PackageVersionMetadata::StandardUninstallCommand); - if (interactiveItr != packageMetadata.end()) - { - uninstallCommandItr = interactiveItr; - } - } - - if (uninstallCommandItr == packageMetadata.end()) - { - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - - context.Add(uninstallCommandItr->second); - } - - void SetModifyPathInContext(Execution::Context& context) - { - const auto& installedPackageVersion = context.Get(); - IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); - - // Default to silent unless it is not present or interactivity is requested - auto modifyPathItr = packageMetadata.find(PackageVersionMetadata::StandardModifyCommand); - if (modifyPathItr == packageMetadata.end()) - { - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - - context.Add(modifyPathItr->second); - } - - void SetProductCodesInContext(Execution::Context& context) - { - const auto& installedPackageVersion = context.Get(); - auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - - if (productCodes.empty()) - { - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - - context.Add(productCodes); - } - - void SetPackageFamilyNamesInContext(Execution::Context& context) - { - const auto& installedPackageVersion = context.Get(); - - auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); - if (packageFamilyNames.empty()) - { - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - - context.Add(packageFamilyNames); - } - - InstallerTypeEnum GetInstalledType(Execution::Context& context) - { - const auto& installedPackage = context.Get(); - std::string installedType = installedPackage->GetMetadata()[PackageVersionMetadata::InstalledType]; - return ConvertToInstallerTypeEnum(installedType); - } - - void ApplicabilityCheckForInstalledPackage(Execution::Context& context) - { - // Installed Package repair applicability check - const auto& installedPackageVersion = context.Get(); - - const std::string installerType = context.Get()->GetMetadata()[PackageVersionMetadata::InstalledType]; - InstallerTypeEnum installerTypeEnum = ConvertToInstallerTypeEnum(installerType); - - if (installerTypeEnum == InstallerTypeEnum::Portable || installerTypeEnum == InstallerTypeEnum::Unknown) - { - context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); - } - - IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); - - auto noModifyItr = packageMetadata.find(PackageVersionMetadata::NoModify); - std::string noModifyARPFlag = noModifyItr != packageMetadata.end() ? noModifyItr->second : std::string(); - - auto noRepairItr = packageMetadata.find(PackageVersionMetadata::NoRepair); - std::string noRepairARPFlag = noRepairItr != packageMetadata.end() ? noRepairItr->second : std::string(); - - if (Utility::IsDwordFlagSet(noModifyARPFlag) || Utility::IsDwordFlagSet(noRepairARPFlag)) - { - context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); - } - } - - void ApplicabilityCheckForAvailablePackage(Execution::Context& context) - { - // Skip the Available Package applicability check for MSI and MSIX repair as they aren't needed. - if (!context.Contains(Execution::Data::Installer)) - { - return; - } - - // Selected Installer repair applicability check - auto installerType = context.Get()->EffectiveInstallerType(); - auto repairBehavior = context.Get()->RepairBehavior; - - if (installerType == InstallerTypeEnum::Portable || installerType == InstallerTypeEnum::Unknown) - { - context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); - } - - // Repair behavior is required for Burn, Inno, Nullsoft, Exe installers - if (DoesInstallerTypeRequireRepairBehaviorForRepair(installerType) && - repairBehavior == RepairBehaviorEnum::Unknown) - { - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - } - - void HandleModifyRepairBehavior(Execution::Context& context, std::string& repairCommand) - { - SetModifyPathInContext(context); - repairCommand += context.Get(); - } - - void HandleInstallerRepairBehavior(Execution::Context& context, InstallerTypeEnum installerType) - { - context << - ShowInstallationDisclaimer << - ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << - DownloadInstaller; - - if (installerType == InstallerTypeEnum::Zip) - { - context << - ScanArchiveFromLocalManifest << - ExtractFilesFromArchive << - VerifyAndSetNestedInstaller; - } - } - - void HandleUninstallerRepairBehavior(Execution::Context& context, std::string& repairCommand) - { - SetUninstallStringInContext(context); - repairCommand += context.Get(); - } - - void GenerateRepairString(Execution::Context& context) - { - const auto& installer = context.Get(); - auto installerType = installer->BaseInstallerType; - auto repairBehavior = installer->RepairBehavior; - - std::string repairCommand; - - switch (repairBehavior) - { - case RepairBehaviorEnum::Modify: - HandleModifyRepairBehavior(context, repairCommand); - break; - case RepairBehaviorEnum::Installer: - HandleInstallerRepairBehavior(context, installerType); - break; - case RepairBehaviorEnum::Uninstaller: - HandleUninstallerRepairBehavior(context, repairCommand); - break; - case RepairBehaviorEnum::Unknown: - default: - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - - context << - GetInstallerArgs; - - // If the repair behavior is set to 'Installer', we can proceed with the repair command as is. - // For repair behaviors other than 'Installer', subsequent steps will be necessary to prepare the repair command. - if (repairBehavior == RepairBehaviorEnum::Installer) - { - return; - } - - if (repairCommand.empty()) - { - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - - repairCommand += " "; - repairCommand += context.Get(); - context.Add(repairCommand); - } - - bool IsInstallerMappingRequired(Execution::Context& context) - { - InstallerTypeEnum installerTypeEnum = GetInstalledType(context); - - switch (installerTypeEnum) - { - case InstallerTypeEnum::Msi: - return false; - case InstallerTypeEnum::Msix: - // For MSIX packages that are from the Microsoft Store, selecting an installer is required. - if (context.Contains(Execution::Data::Package)) - { - auto availablePackages = context.Get()->GetAvailable(); - - if (availablePackages.size() == 1 && availablePackages[0]->GetSource() == WellKnownSource::MicrosoftStore) - { - return true; - } - } - - // For MSIX packages that are not from the Microsoft Store, selecting an installer is not required. - return false; - default: - return true; - } - } - - void HandleModifyOrUninstallerRepair(Execution::Context& context, RepairBehaviorEnum repairBehavior) - { - context << - ShellExecuteRepairImpl << - ReportRepairResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); - } - - void HandleInstallerRepair(Execution::Context& context, RepairBehaviorEnum repairBehavior) - { - context << - ShellExecuteInstallImpl << - ReportInstallerResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); - } - } - - void RunRepairForRepairBehaviorBasedInstaller(Execution::Context& context) - { - const auto& installer = context.Get(); - auto repairBehavior = installer->RepairBehavior; - - switch (repairBehavior) - { - case RepairBehaviorEnum::Modify: - case RepairBehaviorEnum::Uninstaller: - HandleModifyOrUninstallerRepair(context, repairBehavior); - break; - case RepairBehaviorEnum::Installer: - HandleInstallerRepair(context, repairBehavior); - break; - default: - context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); - } - } - - void RepairMsiBasedInstaller(Execution::Context& context) - { - context << - ShellExecuteMsiExecRepair << - ReportRepairResult("MsiExec", APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); - } - - void RepairApplicabilityCheck(Execution::Context& context) - { - context << - ApplicabilityCheckForInstalledPackage << - ApplicabilityCheckForAvailablePackage; - } - - void ExecuteRepair(Execution::Context& context) - { - InstallerTypeEnum installerTypeEnum = context.Contains(Execution::Data::Installer) ? context.Get()->EffectiveInstallerType() : GetInstalledType(context); - - Synchronization::CrossProcessInstallLock lock; - - if (!ExemptFromSingleInstallLocking(installerTypeEnum)) - { - // Acquire the lock , if the operation is cancelled it will return false so we will also return. - if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) - { - callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); - return lock.Acquire(callback); - })) - { - AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire repair lock due to cancellation"); - return; - } - } - - switch (installerTypeEnum) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Nullsoft: - { - context << - RunRepairForRepairBehaviorBasedInstaller; - } - break; - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Wix: - { - context << - RepairMsiBasedInstaller; - } - break; - case InstallerTypeEnum::Msix: - { - context << - RepairMsixPackage; - } - break; - case InstallerTypeEnum::MSStore: - { - context << - EnsureStorePolicySatisfied << - MSStoreRepair; - } - break; - case InstallerTypeEnum::Portable: - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - void GetRepairInfo(Execution::Context& context) - { - InstallerTypeEnum installerTypeEnum = context.Contains(Execution::Data::Installer) ? context.Get()->BaseInstallerType : GetInstalledType(context); - - switch (installerTypeEnum) - { - // Exe based installers, for installed package all gets mapped to exe extension. - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Nullsoft: - { - context << - GenerateRepairString; - } - break; - // MSI based installers, for installed package all gets mapped to msi extension. - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Wix: - { - context << - SetProductCodesInContext; - } - break; - // MSIX based installers, msix. - case InstallerTypeEnum::Msix: - { - context << - SetPackageFamilyNamesInContext; - } - break; - case InstallerTypeEnum::MSStore: - break; - case InstallerTypeEnum::Portable: - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - void RepairMsixPackage(Execution::Context& context) - { - bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; - - const auto& packageFamilyNames = context.Get(); - context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; - - for (const auto& packageFamilyName : packageFamilyNames) - { - auto packageFullName = Msix::GetPackageFullNameFromFamilyName(packageFamilyName); - - if (!packageFullName.has_value()) - { - AICLI_LOG(CLI, Warning, << "No package found with family name: " << packageFamilyName); - continue; - } - - AICLI_LOG(CLI, Info, << "Repairing package: " << packageFullName.value()); - - try - { - if (!isMachineScope) - { - // Best effort repair by registering the package. - context.Reporter.ExecuteWithProgress(std::bind(Deployment::RegisterPackage, packageFamilyName, std::placeholders::_1)); - } - else - { - context.Reporter.Error() << Resource::String::RepairFlowReturnCodeSystemNotSupported << std::endl; - context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - AICLI_LOG(CLI, Error, << "Device wide repair for msix type is not supported."); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); - } - } - catch (const wil::ResultException& re) - { - context.Add(re.GetErrorCode()); - context << ReportRepairResult("MSIX", re.GetErrorCode(), true); - return; - } - } - - context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; - } - - void RepairSinglePackage(Execution::Context& context) - { - context << - RepairApplicabilityCheck << - GetRepairInfo << - ReportExecutionStage(ExecutionStage::Execution) << - ExecuteRepair << - ReportExecutionStage(ExecutionStage::PostExecution); - } - - void SelectApplicablePackageVersion(Execution::Context& context) - { - // If the repair flow is initiated with manifest, then we don't need to select the applicable package version. - if (context.Args.Contains(Args::Type::Manifest)) - { - return; - } - - const auto& installedPackage = context.Get(); - - Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); - if (installedVersion.IsUnknown()) - { - context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); - } - - std::string_view requestedVersion = context.Args.Contains(Execution::Args::Type::TargetVersion) ? context.Args.GetArg(Execution::Args::Type::TargetVersion) : installedVersion.ToString(); - // If it's Store source with only one version unknown, use the unknown version for available version mapping. - const auto& package = context.Get(); - auto packageVersions = GetAvailableVersionsForInstalledVersion(package, installedPackage); - auto versionKeys = packageVersions->GetVersionKeys(); - if (versionKeys.size() == 1) - { - auto packageVersion = packageVersions->GetVersion(versionKeys.at(0)); - if (packageVersion->GetSource().IsWellKnownSource(WellKnownSource::MicrosoftStore) && - Utility::Version{ packageVersion->GetProperty(PackageVersionProperty::Version) }.IsUnknown()) - { - requestedVersion = ""; - } - } - - context << - GetManifestWithVersionFromPackage( - requestedVersion, - context.Args.GetArg(Execution::Args::Type::Channel), false); - } - - void SelectApplicableInstallerIfNecessary(Execution::Context& context) - { - // For MSI installers, the platform provides built-in support for repair via msiexec, hence no need to select an installer. - // Similarly, for MSIX packages that are not from the Microsoft Store, selecting an installer is not required. - if (IsInstallerMappingRequired(context)) - { - context << - SelectApplicablePackageVersion << - SelectInstaller << - EnsureApplicableInstaller; - } - } - - void ReportRepairResult::operator()(Execution::Context& context) const - { - DWORD repairResult = context.Get(); - - if (repairResult != 0) - { - auto& repairPackage = context.Contains(Execution::Data::PackageVersion) ? - context.Get() : - context.Get(); - - Logging::Telemetry().LogRepairFailure( - repairPackage->GetProperty(PackageVersionProperty::Id), - repairPackage->GetProperty(PackageVersionProperty::Version), - m_repairType, - repairResult); - - if (m_isHResult) - { - context.Reporter.Error() - << Resource::String::RepairFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(repairResult) }) - << std::endl; - } - else - { - context.Reporter.Error() - << Resource::String::RepairFailedWithCode(repairResult) - << std::endl; - } - - // Show log path if available - if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get())) - { - auto installerLogPath = Utility::LocIndString{ context.Get().u8string() }; - context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; - } - - AICLI_TERMINATE_CONTEXT(m_hr); - } - else - { - context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RepairFlow.h" +#include "Workflows/ShellExecuteInstallerHandler.h" +#include "Workflows/WorkflowBase.h" +#include "Workflows/DownloadFlow.h" +#include "Workflows/ArchiveFlow.h" +#include "Workflows/InstallFlow.h" +#include "Workflows/PromptFlow.h" +#include "winget/ManifestCommon.h" +#include "AppInstallerDeployment.h" +#include "AppInstallerMsixInfo.h" +#include "AppInstallerSynchronization.h" +#include "MSStoreInstallerHandler.h" +#include +#include + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Msix; +using namespace AppInstaller::Repository; +using namespace AppInstaller::CLI::Execution; + +namespace AppInstaller::CLI::Workflow +{ + // Internal implementation details + namespace + { + + void SetUninstallStringInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get(); + IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); + + // Default to silent unless it is not present or interactivity is requested + auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand); + + if ((!context.Args.Contains(Execution::Args::Type::Silent) && uninstallCommandItr == packageMetadata.end()) + || context.Args.Contains(Execution::Args::Type::Interactive)) + { + auto interactiveItr = packageMetadata.find(PackageVersionMetadata::StandardUninstallCommand); + if (interactiveItr != packageMetadata.end()) + { + uninstallCommandItr = interactiveItr; + } + } + + if (uninstallCommandItr == packageMetadata.end()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add(uninstallCommandItr->second); + } + + void SetModifyPathInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get(); + IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); + + // Default to silent unless it is not present or interactivity is requested + auto modifyPathItr = packageMetadata.find(PackageVersionMetadata::StandardModifyCommand); + if (modifyPathItr == packageMetadata.end()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add(modifyPathItr->second); + } + + void SetProductCodesInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get(); + auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + + if (productCodes.empty()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add(productCodes); + } + + void SetPackageFamilyNamesInContext(Execution::Context& context) + { + const auto& installedPackageVersion = context.Get(); + + auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + if (packageFamilyNames.empty()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context.Add(packageFamilyNames); + } + + InstallerTypeEnum GetInstalledType(Execution::Context& context) + { + const auto& installedPackage = context.Get(); + std::string installedType = installedPackage->GetMetadata()[PackageVersionMetadata::InstalledType]; + return ConvertToInstallerTypeEnum(installedType); + } + + void ApplicabilityCheckForInstalledPackage(Execution::Context& context) + { + // Installed Package repair applicability check + const auto& installedPackageVersion = context.Get(); + + const std::string installerType = context.Get()->GetMetadata()[PackageVersionMetadata::InstalledType]; + InstallerTypeEnum installerTypeEnum = ConvertToInstallerTypeEnum(installerType); + + if (installerTypeEnum == InstallerTypeEnum::Portable || installerTypeEnum == InstallerTypeEnum::Unknown) + { + context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); + } + + IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); + + auto noModifyItr = packageMetadata.find(PackageVersionMetadata::NoModify); + std::string noModifyARPFlag = noModifyItr != packageMetadata.end() ? noModifyItr->second : std::string(); + + auto noRepairItr = packageMetadata.find(PackageVersionMetadata::NoRepair); + std::string noRepairARPFlag = noRepairItr != packageMetadata.end() ? noRepairItr->second : std::string(); + + if (Utility::IsDwordFlagSet(noModifyARPFlag) || Utility::IsDwordFlagSet(noRepairARPFlag)) + { + context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); + } + } + + void ApplicabilityCheckForAvailablePackage(Execution::Context& context) + { + // Skip the Available Package applicability check for MSI and MSIX repair as they aren't needed. + if (!context.Contains(Execution::Data::Installer)) + { + return; + } + + // Selected Installer repair applicability check + auto installerType = context.Get()->EffectiveInstallerType(); + auto repairBehavior = context.Get()->RepairBehavior; + + if (installerType == InstallerTypeEnum::Portable || installerType == InstallerTypeEnum::Unknown) + { + context.Reporter.Error() << Resource::String::RepairOperationNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED); + } + + // Repair behavior is required for Burn, Inno, Nullsoft, Exe installers + if (DoesInstallerTypeRequireRepairBehaviorForRepair(installerType) && + repairBehavior == RepairBehaviorEnum::Unknown) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + } + + void HandleModifyRepairBehavior(Execution::Context& context, std::string& repairCommand) + { + SetModifyPathInContext(context); + repairCommand += context.Get(); + } + + void HandleInstallerRepairBehavior(Execution::Context& context, InstallerTypeEnum installerType) + { + context << + ShowInstallationDisclaimer << + ShowPromptsForSinglePackage(/* ensureAcceptance */ true) << + DownloadInstaller; + + if (installerType == InstallerTypeEnum::Zip) + { + context << + ScanArchiveFromLocalManifest << + ExtractFilesFromArchive << + VerifyAndSetNestedInstaller; + } + } + + void HandleUninstallerRepairBehavior(Execution::Context& context, std::string& repairCommand) + { + SetUninstallStringInContext(context); + repairCommand += context.Get(); + } + + void GenerateRepairString(Execution::Context& context) + { + const auto& installer = context.Get(); + auto installerType = installer->BaseInstallerType; + auto repairBehavior = installer->RepairBehavior; + + std::string repairCommand; + + switch (repairBehavior) + { + case RepairBehaviorEnum::Modify: + HandleModifyRepairBehavior(context, repairCommand); + break; + case RepairBehaviorEnum::Installer: + HandleInstallerRepairBehavior(context, installerType); + break; + case RepairBehaviorEnum::Uninstaller: + HandleUninstallerRepairBehavior(context, repairCommand); + break; + case RepairBehaviorEnum::Unknown: + default: + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + context << + GetInstallerArgs; + + // If the repair behavior is set to 'Installer', we can proceed with the repair command as is. + // For repair behaviors other than 'Installer', subsequent steps will be necessary to prepare the repair command. + if (repairBehavior == RepairBehaviorEnum::Installer) + { + return; + } + + if (repairCommand.empty()) + { + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + + repairCommand += " "; + repairCommand += context.Get(); + context.Add(repairCommand); + } + + bool IsInstallerMappingRequired(Execution::Context& context) + { + InstallerTypeEnum installerTypeEnum = GetInstalledType(context); + + switch (installerTypeEnum) + { + case InstallerTypeEnum::Msi: + return false; + case InstallerTypeEnum::Msix: + // For MSIX packages that are from the Microsoft Store, selecting an installer is required. + if (context.Contains(Execution::Data::Package)) + { + auto availablePackages = context.Get()->GetAvailable(); + + if (availablePackages.size() == 1 && availablePackages[0]->GetSource() == WellKnownSource::MicrosoftStore) + { + return true; + } + } + + // For MSIX packages that are not from the Microsoft Store, selecting an installer is not required. + return false; + default: + return true; + } + } + + void HandleModifyOrUninstallerRepair(Execution::Context& context, RepairBehaviorEnum repairBehavior) + { + context << + ShellExecuteRepairImpl << + ReportRepairResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); + } + + void HandleInstallerRepair(Execution::Context& context, RepairBehaviorEnum repairBehavior) + { + context << + ShellExecuteInstallImpl << + ReportInstallerResult(RepairBehaviorToString(repairBehavior), APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); + } + } + + void RunRepairForRepairBehaviorBasedInstaller(Execution::Context& context) + { + const auto& installer = context.Get(); + auto repairBehavior = installer->RepairBehavior; + + switch (repairBehavior) + { + case RepairBehaviorEnum::Modify: + case RepairBehaviorEnum::Uninstaller: + HandleModifyOrUninstallerRepair(context, repairBehavior); + break; + case RepairBehaviorEnum::Installer: + HandleInstallerRepair(context, repairBehavior); + break; + default: + context.Reporter.Error() << Resource::String::NoRepairInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND); + } + } + + void RepairMsiBasedInstaller(Execution::Context& context) + { + context << + ShellExecuteMsiExecRepair << + ReportRepairResult("MsiExec", APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED); + } + + void RepairApplicabilityCheck(Execution::Context& context) + { + context << + ApplicabilityCheckForInstalledPackage << + ApplicabilityCheckForAvailablePackage; + } + + void ExecuteRepair(Execution::Context& context) + { + InstallerTypeEnum installerTypeEnum = context.Contains(Execution::Data::Installer) ? context.Get()->EffectiveInstallerType() : GetInstalledType(context); + + Synchronization::CrossProcessInstallLock lock; + + if (!ExemptFromSingleInstallLocking(installerTypeEnum)) + { + // Acquire the lock , if the operation is cancelled it will return false so we will also return. + if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) + { + callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); + return lock.Acquire(callback); + })) + { + AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire repair lock due to cancellation"); + return; + } + } + + switch (installerTypeEnum) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + { + context << + RunRepairForRepairBehaviorBasedInstaller; + } + break; + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: + { + context << + RepairMsiBasedInstaller; + } + break; + case InstallerTypeEnum::Msix: + { + context << + RepairMsixPackage; + } + break; + case InstallerTypeEnum::MSStore: + { + context << + EnsureStorePolicySatisfied << + MSStoreRepair; + } + break; + case InstallerTypeEnum::Portable: + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void GetRepairInfo(Execution::Context& context) + { + InstallerTypeEnum installerTypeEnum = context.Contains(Execution::Data::Installer) ? context.Get()->BaseInstallerType : GetInstalledType(context); + + switch (installerTypeEnum) + { + // Exe based installers, for installed package all gets mapped to exe extension. + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + { + context << + GenerateRepairString; + } + break; + // MSI based installers, for installed package all gets mapped to msi extension. + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: + { + context << + SetProductCodesInContext; + } + break; + // MSIX based installers, msix. + case InstallerTypeEnum::Msix: + { + context << + SetPackageFamilyNamesInContext; + } + break; + case InstallerTypeEnum::MSStore: + break; + case InstallerTypeEnum::Portable: + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void RepairMsixPackage(Execution::Context& context) + { + bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; + + const auto& packageFamilyNames = context.Get(); + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + for (const auto& packageFamilyName : packageFamilyNames) + { + auto packageFullName = Msix::GetPackageFullNameFromFamilyName(packageFamilyName); + + if (!packageFullName.has_value()) + { + AICLI_LOG(CLI, Warning, << "No package found with family name: " << packageFamilyName); + continue; + } + + AICLI_LOG(CLI, Info, << "Repairing package: " << packageFullName.value()); + + try + { + if (!isMachineScope) + { + // Best effort repair by registering the package. + context.Reporter.ExecuteWithProgress(std::bind(Deployment::RegisterPackage, packageFamilyName, std::placeholders::_1)); + } + else + { + context.Reporter.Error() << Resource::String::RepairFlowReturnCodeSystemNotSupported << std::endl; + context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + AICLI_LOG(CLI, Error, << "Device wide repair for msix type is not supported."); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + } + catch (const wil::ResultException& re) + { + context.Add(re.GetErrorCode()); + context << ReportRepairResult("MSIX", re.GetErrorCode(), true); + return; + } + } + + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + + void RepairSinglePackage(Execution::Context& context) + { + context << + RepairApplicabilityCheck << + GetRepairInfo << + ReportExecutionStage(ExecutionStage::Execution) << + ExecuteRepair << + ReportExecutionStage(ExecutionStage::PostExecution); + } + + void SelectApplicablePackageVersion(Execution::Context& context) + { + // If the repair flow is initiated with manifest, then we don't need to select the applicable package version. + if (context.Args.Contains(Args::Type::Manifest)) + { + return; + } + + const auto& installedPackage = context.Get(); + + Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); + if (installedVersion.IsUnknown()) + { + context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); + } + + std::string_view requestedVersion = context.Args.Contains(Execution::Args::Type::TargetVersion) ? context.Args.GetArg(Execution::Args::Type::TargetVersion) : installedVersion.ToString(); + // If it's Store source with only one version unknown, use the unknown version for available version mapping. + const auto& package = context.Get(); + auto packageVersions = GetAvailableVersionsForInstalledVersion(package, installedPackage); + auto versionKeys = packageVersions->GetVersionKeys(); + if (versionKeys.size() == 1) + { + auto packageVersion = packageVersions->GetVersion(versionKeys.at(0)); + if (packageVersion->GetSource().IsWellKnownSource(WellKnownSource::MicrosoftStore) && + Utility::Version{ packageVersion->GetProperty(PackageVersionProperty::Version) }.IsUnknown()) + { + requestedVersion = ""; + } + } + + context << + GetManifestWithVersionFromPackage( + requestedVersion, + context.Args.GetArg(Execution::Args::Type::Channel), false); + } + + void SelectApplicableInstallerIfNecessary(Execution::Context& context) + { + // For MSI installers, the platform provides built-in support for repair via msiexec, hence no need to select an installer. + // Similarly, for MSIX packages that are not from the Microsoft Store, selecting an installer is not required. + if (IsInstallerMappingRequired(context)) + { + context << + SelectApplicablePackageVersion << + SelectInstaller << + EnsureApplicableInstaller; + } + } + + void ReportRepairResult::operator()(Execution::Context& context) const + { + DWORD repairResult = context.Get(); + + if (repairResult != 0) + { + auto& repairPackage = context.Contains(Execution::Data::PackageVersion) ? + context.Get() : + context.Get(); + + Logging::Telemetry().LogRepairFailure( + repairPackage->GetProperty(PackageVersionProperty::Id), + repairPackage->GetProperty(PackageVersionProperty::Version), + m_repairType, + repairResult); + + if (m_isHResult) + { + context.Reporter.Error() + << Resource::String::RepairFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(repairResult) }) + << std::endl; + } + else + { + context.Reporter.Error() + << Resource::String::RepairFailedWithCode(repairResult) + << std::endl; + } + + // Show log path if available + if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get())) + { + auto installerLogPath = Utility::LocIndString{ context.Get().u8string() }; + context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; + } + + AICLI_TERMINATE_CONTEXT(m_hr); + } + else + { + context.Reporter.Info() << Resource::String::RepairFlowRepairSuccess << std::endl; + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/RepairFlow.h b/src/AppInstallerCLICore/Workflows/RepairFlow.h index 5c15d9f666..86139a37e9 100644 --- a/src/AppInstallerCLICore/Workflows/RepairFlow.h +++ b/src/AppInstallerCLICore/Workflows/RepairFlow.h @@ -1,85 +1,85 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Execute the repair operation for RepairBehavior based installers. - // RequiredArgs:None - // Inputs: RepairBehavior, RepairString - // Outputs:None - void RunRepairForRepairBehaviorBasedInstaller(Execution::Context& context); - - // Execute the repair operation for MSI based installers. - // RequiredArgs:None - // Inputs: ProductCodes - // Outputs:None - void RepairMsiBasedInstaller(Execution::Context& context); - - // Applicability check for repair operation. - // RequiredArgs:None - // Inputs:InstalledPackageVersion, NoModify ?, NoRepair ? - // Outputs:None - void RepairApplicabilityCheck(Execution::Context& context); - - // Execute the repair operation. - // RequiredArgs:None - // Inputs: InstallerType, RepairBehavior ?, RepairString? , ProductCodes?, PackageFamilyNames? - // Outputs:None - void ExecuteRepair(Execution::Context& context); - - // Obtains the necessary information for repair operation. - // RequiredArgs:None - // Inputs:InstallerType - // Outputs:RepairString?, ProductCodes?, PackageFamilyNames? - void GetRepairInfo(Execution::Context& context); - - // Perform the repair operation for the MSIX NonStore package. - // RequiredArgs:None - // Inputs:PackageFamilyNames , InstallScope? - // Outputs:None - void RepairMsixPackage(Execution::Context& context); - - // Select the applicable package version by matching the installed package version with the available package version. - // RequiredArgs:None - // Inputs: Package,InstalledPackageVersion, AvailablePackageVersions - // Outputs:Manifest, PackageVersion, Installer - void SelectApplicablePackageVersion(Execution::Context& context); - - /// - /// Select the applicable installer for the installed package if necessary. - // RequiredArgs:None - // Inputs: Package,InstalledPackageVersion, AvailablePackageVersions - // Outputs:Manifest, PackageVersion, Installer - void SelectApplicableInstallerIfNecessary(Execution::Context& context); - - // Perform the repair operation for the single package. - // RequiredArgs:None - // Inputs: SearchResult, InstalledPackage, ApplicableInstaller - // Outputs:None - void RepairSinglePackage(Execution::Context& context); - - // Reports the result of the repair. - // Required Args: None - // Inputs: None - // Outputs: None - struct ReportRepairResult : public WorkflowTask - { - ReportRepairResult(std::string_view repairType, HRESULT hr, bool isHResult = false) : - WorkflowTask("ReportRepairResult"), - m_repairType(repairType), - m_hr(hr), - m_isHResult(isHResult) {} - - void operator()(Execution::Context& context) const override; - - private: - // Repair type used for reporting failure. - std::string_view m_repairType; - // Result to return if the repair fails. - HRESULT m_hr; - // Whether the result is an HRESULT. - bool m_isHResult; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Execute the repair operation for RepairBehavior based installers. + // RequiredArgs:None + // Inputs: RepairBehavior, RepairString + // Outputs:None + void RunRepairForRepairBehaviorBasedInstaller(Execution::Context& context); + + // Execute the repair operation for MSI based installers. + // RequiredArgs:None + // Inputs: ProductCodes + // Outputs:None + void RepairMsiBasedInstaller(Execution::Context& context); + + // Applicability check for repair operation. + // RequiredArgs:None + // Inputs:InstalledPackageVersion, NoModify ?, NoRepair ? + // Outputs:None + void RepairApplicabilityCheck(Execution::Context& context); + + // Execute the repair operation. + // RequiredArgs:None + // Inputs: InstallerType, RepairBehavior ?, RepairString? , ProductCodes?, PackageFamilyNames? + // Outputs:None + void ExecuteRepair(Execution::Context& context); + + // Obtains the necessary information for repair operation. + // RequiredArgs:None + // Inputs:InstallerType + // Outputs:RepairString?, ProductCodes?, PackageFamilyNames? + void GetRepairInfo(Execution::Context& context); + + // Perform the repair operation for the MSIX NonStore package. + // RequiredArgs:None + // Inputs:PackageFamilyNames , InstallScope? + // Outputs:None + void RepairMsixPackage(Execution::Context& context); + + // Select the applicable package version by matching the installed package version with the available package version. + // RequiredArgs:None + // Inputs: Package,InstalledPackageVersion, AvailablePackageVersions + // Outputs:Manifest, PackageVersion, Installer + void SelectApplicablePackageVersion(Execution::Context& context); + + /// + /// Select the applicable installer for the installed package if necessary. + // RequiredArgs:None + // Inputs: Package,InstalledPackageVersion, AvailablePackageVersions + // Outputs:Manifest, PackageVersion, Installer + void SelectApplicableInstallerIfNecessary(Execution::Context& context); + + // Perform the repair operation for the single package. + // RequiredArgs:None + // Inputs: SearchResult, InstalledPackage, ApplicableInstaller + // Outputs:None + void RepairSinglePackage(Execution::Context& context); + + // Reports the result of the repair. + // Required Args: None + // Inputs: None + // Outputs: None + struct ReportRepairResult : public WorkflowTask + { + ReportRepairResult(std::string_view repairType, HRESULT hr, bool isHResult = false) : + WorkflowTask("ReportRepairResult"), + m_repairType(repairType), + m_hr(hr), + m_isHResult(isHResult) {} + + void operator()(Execution::Context& context) const override; + + private: + // Repair type used for reporting failure. + std::string_view m_repairType; + // Result to return if the repair fails. + HRESULT m_hr; + // Whether the result is an HRESULT. + bool m_isHResult; + }; +} diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp b/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp index f730d5208d..837bbf55d8 100644 --- a/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/SettingsFlow.cpp @@ -1,194 +1,194 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Resources.h" -#include "SettingsFlow.h" -#include -#include -#include - -namespace AppInstaller::CLI::Workflow -{ - using namespace AppInstaller::Settings; - using namespace AppInstaller::Utility; - - namespace - { - struct ExportSettingsJson - { - ExportSettingsJson() - { - root["$schema"] = "https://aka.ms/winget-settings-export.schema.json"; - root["adminSettings"] = Json::ValueType::objectValue; - root["userSettingsFile"] = UserSettings::SettingsFilePath().u8string(); - } - - void AddAdminSetting(BoolAdminSetting setting) - { - auto str = std::string{ Settings::AdminSettingToString(setting) }; - root["adminSettings"][str] = Settings::IsAdminSettingEnabled(setting); - } - - void AddAdminSetting(StringAdminSetting setting) - { - auto name = std::string{ Settings::AdminSettingToString(setting) }; - auto value = Settings::GetAdminSetting(setting); - if (value) - { - root["adminSettings"][name] = value.value(); - } - } - - std::string ToJsonString() const - { - Json::StreamWriterBuilder writerBuilder; - writerBuilder.settings_["indentation"] = ""; - return Json::writeString(writerBuilder, root); - } - - private: - Json::Value root{ Json::ValueType::objectValue }; - }; - } - - void EnableAdminSetting(Execution::Context& context) - { - auto adminSettingString = context.Args.GetArg(Execution::Args::Type::AdminSettingEnable); - BoolAdminSetting adminSetting = Settings::StringToBoolAdminSetting(adminSettingString); - if (Settings::EnableAdminSetting(adminSetting)) - { - context.Reporter.Info() << Resource::String::AdminSettingEnabled(AdminSettingToString(adminSetting)) << std::endl; - } - else - { - context.Reporter.Error() << Resource::String::EnableAdminSettingFailed(AdminSettingToString(adminSetting)) << std::endl; - } - } - - void DisableAdminSetting(Execution::Context& context) - { - auto adminSettingString = context.Args.GetArg(Execution::Args::Type::AdminSettingDisable); - BoolAdminSetting adminSetting = Settings::StringToBoolAdminSetting(adminSettingString); - if (Settings::DisableAdminSetting(adminSetting)) - { - context.Reporter.Info() << Resource::String::AdminSettingDisabled(AdminSettingToString(adminSetting)) << std::endl; - } - else - { - context.Reporter.Error() << Resource::String::DisableAdminSettingFailed(AdminSettingToString(adminSetting)) << std::endl; - } - } - - void SetAdminSetting(Execution::Context& context) - { - auto adminSettingName = context.Args.GetArg(Execution::Args::Type::SettingName); - auto adminSettingValue = context.Args.GetArg(Execution::Args::Type::SettingValue); - StringAdminSetting adminSetting = Settings::StringToStringAdminSetting(adminSettingName); - if (Settings::SetAdminSetting(adminSetting, adminSettingValue)) - { - context.Reporter.Info() << Resource::String::SetAdminSettingSucceeded(LocIndString{ adminSettingName }, LocIndString{ adminSettingValue }) << std::endl; - } - else - { - context.Reporter.Error() << Resource::String::SetAdminSettingFailed(LocIndString{ adminSettingName }) << std::endl; - } - } - - void ResetAdminSetting(Execution::Context& context) - { - auto adminSettingName = context.Args.GetArg(Execution::Args::Type::SettingName); - - // Try as both bool and string setting as we don't know the type - auto boolAdminSetting = Settings::StringToBoolAdminSetting(adminSettingName); - auto stringAdminSetting = Settings::StringToStringAdminSetting(adminSettingName); - - if ((boolAdminSetting != Settings::BoolAdminSetting::Unknown && Settings::DisableAdminSetting(boolAdminSetting)) - || (stringAdminSetting != Settings::StringAdminSetting::Unknown && Settings::ResetAdminSetting(stringAdminSetting))) - { - context.Reporter.Info() << Resource::String::ResetAdminSettingSucceeded(LocIndString{ adminSettingName }) << std::endl; - } - else - { - context.Reporter.Error() << Resource::String::ResetAdminSettingFailed(LocIndString{ adminSettingName }) << std::endl; - } - } - - void ResetAllAdminSettings(Execution::Context& context) - { - Settings::ResetAllAdminSettings(); - context.Reporter.Info() << Resource::String::ResetAllAdminSettingsSucceeded << std::endl; - } - - void OpenUserSetting(Execution::Context& context) - { - // Show warnings only when the setting command is executed. - if (!User().GetWarnings().empty()) - { - context.Reporter.Warn() << Resource::String::SettingLoadFailure << std::endl; - for (const auto& warning : User().GetWarnings()) - { - auto warn = context.Reporter.Warn(); - warn << warning.Message; - if (!warning.Path.empty()) - { - if (warning.IsFieldWarning) - { - warn << ' ' << Resource::String::SettingsWarningField(warning.Path); - } - else - { - warn << ' ' << warning.Path; - } - } - - if (!warning.Data.empty()) - { - if (warning.IsFieldWarning) - { - warn << ' ' << Resource::String::SettingsWarningValue(warning.Data); - } - else - { - warn << - std::endl << - warning.Data; - } - } - - warn << std::endl; - } - } - - User().PrepareToShellExecuteFile(); - - auto filePathUTF16 = UserSettings::SettingsFilePath().wstring(); - - // Some versions of windows will fail if no file extension association exists, other will pop up the dialog - // to make the user pick their default. - // Kudos to the terminal team for this workaround. - HINSTANCE res = ShellExecuteW(nullptr, nullptr, filePathUTF16.c_str(), nullptr, nullptr, SW_SHOW); - if (static_cast(reinterpret_cast(res)) <= 32) - { - // User doesn't have file type association. Default to notepad - AICLI_LOG(CLI, Info, << "Json file type association not found, using notepad.exe"); - ShellExecuteW(nullptr, nullptr, L"notepad", filePathUTF16.c_str(), nullptr, SW_SHOW); - } - } - - void ExportSettings(Execution::Context& context) - { - ExportSettingsJson exportSettingsJson; - - for (const auto& setting : GetAllBoolAdminSettings()) - { - exportSettingsJson.AddAdminSetting(setting); - } - - for (const auto& setting : GetAllStringAdminSettings()) - { - exportSettingsJson.AddAdminSetting(setting); - } - - context.Reporter.Info() << exportSettingsJson.ToJsonString() << std::endl; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Resources.h" +#include "SettingsFlow.h" +#include +#include +#include + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::Settings; + using namespace AppInstaller::Utility; + + namespace + { + struct ExportSettingsJson + { + ExportSettingsJson() + { + root["$schema"] = "https://aka.ms/winget-settings-export.schema.json"; + root["adminSettings"] = Json::ValueType::objectValue; + root["userSettingsFile"] = UserSettings::SettingsFilePath().u8string(); + } + + void AddAdminSetting(BoolAdminSetting setting) + { + auto str = std::string{ Settings::AdminSettingToString(setting) }; + root["adminSettings"][str] = Settings::IsAdminSettingEnabled(setting); + } + + void AddAdminSetting(StringAdminSetting setting) + { + auto name = std::string{ Settings::AdminSettingToString(setting) }; + auto value = Settings::GetAdminSetting(setting); + if (value) + { + root["adminSettings"][name] = value.value(); + } + } + + std::string ToJsonString() const + { + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = ""; + return Json::writeString(writerBuilder, root); + } + + private: + Json::Value root{ Json::ValueType::objectValue }; + }; + } + + void EnableAdminSetting(Execution::Context& context) + { + auto adminSettingString = context.Args.GetArg(Execution::Args::Type::AdminSettingEnable); + BoolAdminSetting adminSetting = Settings::StringToBoolAdminSetting(adminSettingString); + if (Settings::EnableAdminSetting(adminSetting)) + { + context.Reporter.Info() << Resource::String::AdminSettingEnabled(AdminSettingToString(adminSetting)) << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::EnableAdminSettingFailed(AdminSettingToString(adminSetting)) << std::endl; + } + } + + void DisableAdminSetting(Execution::Context& context) + { + auto adminSettingString = context.Args.GetArg(Execution::Args::Type::AdminSettingDisable); + BoolAdminSetting adminSetting = Settings::StringToBoolAdminSetting(adminSettingString); + if (Settings::DisableAdminSetting(adminSetting)) + { + context.Reporter.Info() << Resource::String::AdminSettingDisabled(AdminSettingToString(adminSetting)) << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::DisableAdminSettingFailed(AdminSettingToString(adminSetting)) << std::endl; + } + } + + void SetAdminSetting(Execution::Context& context) + { + auto adminSettingName = context.Args.GetArg(Execution::Args::Type::SettingName); + auto adminSettingValue = context.Args.GetArg(Execution::Args::Type::SettingValue); + StringAdminSetting adminSetting = Settings::StringToStringAdminSetting(adminSettingName); + if (Settings::SetAdminSetting(adminSetting, adminSettingValue)) + { + context.Reporter.Info() << Resource::String::SetAdminSettingSucceeded(LocIndString{ adminSettingName }, LocIndString{ adminSettingValue }) << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::SetAdminSettingFailed(LocIndString{ adminSettingName }) << std::endl; + } + } + + void ResetAdminSetting(Execution::Context& context) + { + auto adminSettingName = context.Args.GetArg(Execution::Args::Type::SettingName); + + // Try as both bool and string setting as we don't know the type + auto boolAdminSetting = Settings::StringToBoolAdminSetting(adminSettingName); + auto stringAdminSetting = Settings::StringToStringAdminSetting(adminSettingName); + + if ((boolAdminSetting != Settings::BoolAdminSetting::Unknown && Settings::DisableAdminSetting(boolAdminSetting)) + || (stringAdminSetting != Settings::StringAdminSetting::Unknown && Settings::ResetAdminSetting(stringAdminSetting))) + { + context.Reporter.Info() << Resource::String::ResetAdminSettingSucceeded(LocIndString{ adminSettingName }) << std::endl; + } + else + { + context.Reporter.Error() << Resource::String::ResetAdminSettingFailed(LocIndString{ adminSettingName }) << std::endl; + } + } + + void ResetAllAdminSettings(Execution::Context& context) + { + Settings::ResetAllAdminSettings(); + context.Reporter.Info() << Resource::String::ResetAllAdminSettingsSucceeded << std::endl; + } + + void OpenUserSetting(Execution::Context& context) + { + // Show warnings only when the setting command is executed. + if (!User().GetWarnings().empty()) + { + context.Reporter.Warn() << Resource::String::SettingLoadFailure << std::endl; + for (const auto& warning : User().GetWarnings()) + { + auto warn = context.Reporter.Warn(); + warn << warning.Message; + if (!warning.Path.empty()) + { + if (warning.IsFieldWarning) + { + warn << ' ' << Resource::String::SettingsWarningField(warning.Path); + } + else + { + warn << ' ' << warning.Path; + } + } + + if (!warning.Data.empty()) + { + if (warning.IsFieldWarning) + { + warn << ' ' << Resource::String::SettingsWarningValue(warning.Data); + } + else + { + warn << + std::endl << + warning.Data; + } + } + + warn << std::endl; + } + } + + User().PrepareToShellExecuteFile(); + + auto filePathUTF16 = UserSettings::SettingsFilePath().wstring(); + + // Some versions of windows will fail if no file extension association exists, other will pop up the dialog + // to make the user pick their default. + // Kudos to the terminal team for this workaround. + HINSTANCE res = ShellExecuteW(nullptr, nullptr, filePathUTF16.c_str(), nullptr, nullptr, SW_SHOW); + if (static_cast(reinterpret_cast(res)) <= 32) + { + // User doesn't have file type association. Default to notepad + AICLI_LOG(CLI, Info, << "Json file type association not found, using notepad.exe"); + ShellExecuteW(nullptr, nullptr, L"notepad", filePathUTF16.c_str(), nullptr, SW_SHOW); + } + } + + void ExportSettings(Execution::Context& context) + { + ExportSettingsJson exportSettingsJson; + + for (const auto& setting : GetAllBoolAdminSettings()) + { + exportSettingsJson.AddAdminSetting(setting); + } + + for (const auto& setting : GetAllStringAdminSettings()) + { + exportSettingsJson.AddAdminSetting(setting); + } + + context.Reporter.Info() << exportSettingsJson.ToJsonString() << std::endl; + } +} diff --git a/src/AppInstallerCLICore/Workflows/SettingsFlow.h b/src/AppInstallerCLICore/Workflows/SettingsFlow.h index c937e88adf..fe1c705f0c 100644 --- a/src/AppInstallerCLICore/Workflows/SettingsFlow.h +++ b/src/AppInstallerCLICore/Workflows/SettingsFlow.h @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Enables an admin setting. - // Required Args: AdminSettingEnable - // Inputs: None - // Outputs: None - void EnableAdminSetting(Execution::Context& context); - - // Disables an admin setting. - // Required Args: AdminSettingDisable - // Inputs: None - // Outputs: None - void DisableAdminSetting(Execution::Context& context); - - // Sets the value of an admin setting. - // Required Args: SettingName, SettingValue - // Inputs: None - // Outputs: None - void SetAdminSetting(Execution::Context& context); - - // Resets an admin setting to the default. - // Required Args: SettingName - // Inputs: None - // Outputs: None - void ResetAdminSetting(Execution::Context& context); - - // Resets all admin settings to the default. - // Required Args: None - // Inputs: None - // Outputs: None - void ResetAllAdminSettings(Execution::Context& context); - - // Opens the user settings. - // Required Args: None - // Inputs: None - // Outputs: None - void OpenUserSetting(Execution::Context& context); - - // Lists the state of settings. - // Required Args: None - // Inputs: None - // Outputs: None - void ExportSettings(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Enables an admin setting. + // Required Args: AdminSettingEnable + // Inputs: None + // Outputs: None + void EnableAdminSetting(Execution::Context& context); + + // Disables an admin setting. + // Required Args: AdminSettingDisable + // Inputs: None + // Outputs: None + void DisableAdminSetting(Execution::Context& context); + + // Sets the value of an admin setting. + // Required Args: SettingName, SettingValue + // Inputs: None + // Outputs: None + void SetAdminSetting(Execution::Context& context); + + // Resets an admin setting to the default. + // Required Args: SettingName + // Inputs: None + // Outputs: None + void ResetAdminSetting(Execution::Context& context); + + // Resets all admin settings to the default. + // Required Args: None + // Inputs: None + // Outputs: None + void ResetAllAdminSettings(Execution::Context& context); + + // Opens the user settings. + // Required Args: None + // Inputs: None + // Outputs: None + void OpenUserSetting(Execution::Context& context); + + // Lists the state of settings. + // Required Args: None + // Inputs: None + // Outputs: None + void ExportSettings(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp index 66871e39fa..b232d8d51f 100644 --- a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp +++ b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.cpp @@ -1,626 +1,626 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ShellExecuteInstallerHandler.h" -#include -#include -#include - -using namespace AppInstaller::CLI; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - // ShellExecutes the given path. - std::optional InvokeShellExecuteEx(const std::filesystem::path& filePath, const std::string& args, bool useRunAs, int show, IProgressCallback& progress) - { - AICLI_LOG(CLI, Info, << "Starting: '" << filePath.u8string() << "' with arguments '" << args << '\''); - - SHELLEXECUTEINFOW execInfo = { 0 }; - execInfo.cbSize = sizeof(execInfo); - execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - execInfo.lpFile = filePath.c_str(); - std::wstring argsUtf16 = Utility::ConvertToUTF16(args); - execInfo.lpParameters = argsUtf16.c_str(); - execInfo.nShow = show; - - // This installer must be run elevated, but we are not currently. - // Have ShellExecute elevate the installer since it won't do so itself. - if (useRunAs) - { - execInfo.lpVerb = L"runas"; - } - - THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess); - - wil::unique_process_handle process{ execInfo.hProcess }; - - // Wait for installation to finish - while (!progress.IsCancelledBy(CancelReason::User)) - { - DWORD waitResult = WaitForSingleObject(process.get(), 250); - if (waitResult == WAIT_OBJECT_0) - { - break; - } - if (waitResult != WAIT_TIMEOUT) - { - THROW_LAST_ERROR_MSG("Unexpected WaitForSingleObjectResult: %lu", waitResult); - } - } - - if (progress.IsCancelledBy(CancelReason::Any)) - { - return {}; - } - else - { - DWORD exitCode = 0; - GetExitCodeProcess(process.get(), &exitCode); - return exitCode; - } - } - - std::optional InvokeShellExecute(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress) - { - // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete. - // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. - return InvokeShellExecuteEx(filePath, args, false, SW_SHOW, progress); - } - - // Gets the escaped installer args. - std::string GetInstallerArgsTemplate(Execution::Context& context) - { - bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); - bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); - - const auto& installer = context.Get(); - const auto& installerSwitches = installer->Switches; - std::string installerArgs = {}; - - // Construct install experience arg. - // SilentWithProgress is default, so look for it first. - auto experienceArgsItr = installerSwitches.find(InstallerSwitchType::SilentWithProgress); - - if (context.Args.Contains(Execution::Args::Type::Interactive)) - { - // If interactive requested, always use Interactive (or nothing). If the installer supports - // interactive it is usually the default, and thus it is cumbersome to put a blank entry in - // the manifest. - experienceArgsItr = installerSwitches.find(InstallerSwitchType::Interactive); - } - // If no SilentWithProgress exists, or Silent requested, try to find Silent. - else if (experienceArgsItr == installerSwitches.end() || context.Args.Contains(Execution::Args::Type::Silent)) - { - auto silentItr = installerSwitches.find(InstallerSwitchType::Silent); - // If Silent requested, but doesn't exist, then continue using SilentWithProgress. - if (silentItr != installerSwitches.end()) - { - experienceArgsItr = silentItr; - } - } - - if (experienceArgsItr != installerSwitches.end()) - { - installerArgs += experienceArgsItr->second; - } - - // Construct log path arg. - if (installerSwitches.find(InstallerSwitchType::Log) != installerSwitches.end()) - { - installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Log); - } - - // Construct repair arg. Custom switches and other args are not applicable for repair scenario so we can return here. - if (isRepair) - { - if (installerSwitches.find(InstallerSwitchType::Repair) != installerSwitches.end()) - { - installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Repair); - } - - return installerArgs; - } - - // Construct custom arg. - if (installerSwitches.find(InstallerSwitchType::Custom) != installerSwitches.end()) - { - installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Custom); - } - - // Construct custom arg passed in by cli arg - if (context.Args.Contains(Execution::Args::Type::CustomSwitches)) - { - std::string_view customSwitches = context.Args.GetArg(Execution::Args::Type::CustomSwitches); - // Since these arguments are appended to the installer at runtime, it doesn't make sense to append them if empty or whitespace - if (!Utility::IsEmptyOrWhitespace(customSwitches)) - { - installerArgs += ' ' + std::string{ customSwitches }; - } - } - - // Construct update arg if applicable - if (isUpdate && installerSwitches.find(InstallerSwitchType::Update) != installerSwitches.end()) - { - installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Update); - } - - // Construct install location arg if necessary. - if (context.Args.Contains(Execution::Args::Type::InstallLocation) && - installerSwitches.find(InstallerSwitchType::InstallLocation) != installerSwitches.end()) - { - installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::InstallLocation); - } - - return installerArgs; - } - - // Applies values to the template. - void PopulateInstallerArgsTemplate(Execution::Context& context, std::string& installerArgs) - { - // Populate with value from command line or temp path. - std::string logPath; - if (context.Args.Contains(Execution::Args::Type::Log)) - { - logPath = context.Args.GetArg(Execution::Args::Type::Log); - } - else - { - const auto& manifest = context.Get(); - const Logging::LogNameStrategy logNameStrategy = Settings::User().Get(); - auto path = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); - - switch (logNameStrategy) - { - case Logging::LogNameStrategy::Manifest: - // Use manifest ID and version for log file name - // Results in \.-.log - path /= Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); - path += '-'; - path += Utility::GetCurrentTimeForFilename(true); - break; - case Logging::LogNameStrategy::Timestamp: - // Use only timestamp for log file name - // Results in \.log - path /= Utility::GetCurrentTimeForFilename(true); - break; - case Logging::LogNameStrategy::Guid: - // Use a GUID for log file name - // Results in \.log - path /= Utility::CreateNewGuidNameWString(); - break; - case Logging::LogNameStrategy::ShortGuid: - // Use the first 8 characters of a GUID for log file name - // Results in \.log - path /= Utility::CreateNewGuidNameWString().substr(0, 8); - break; - default: - // This should never happen due to validation when reading settings, but handle it just in case. - AICLI_LOG(CLI, Error, << "Unknown log naming strategy."); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ShellExecuteInstallerHandler.h" +#include +#include +#include + +using namespace AppInstaller::CLI; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + // ShellExecutes the given path. + std::optional InvokeShellExecuteEx(const std::filesystem::path& filePath, const std::string& args, bool useRunAs, int show, IProgressCallback& progress) + { + AICLI_LOG(CLI, Info, << "Starting: '" << filePath.u8string() << "' with arguments '" << args << '\''); + + SHELLEXECUTEINFOW execInfo = { 0 }; + execInfo.cbSize = sizeof(execInfo); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + execInfo.lpFile = filePath.c_str(); + std::wstring argsUtf16 = Utility::ConvertToUTF16(args); + execInfo.lpParameters = argsUtf16.c_str(); + execInfo.nShow = show; + + // This installer must be run elevated, but we are not currently. + // Have ShellExecute elevate the installer since it won't do so itself. + if (useRunAs) + { + execInfo.lpVerb = L"runas"; + } + + THROW_LAST_ERROR_IF(!ShellExecuteExW(&execInfo) || !execInfo.hProcess); + + wil::unique_process_handle process{ execInfo.hProcess }; + + // Wait for installation to finish + while (!progress.IsCancelledBy(CancelReason::User)) + { + DWORD waitResult = WaitForSingleObject(process.get(), 250); + if (waitResult == WAIT_OBJECT_0) + { + break; + } + if (waitResult != WAIT_TIMEOUT) + { + THROW_LAST_ERROR_MSG("Unexpected WaitForSingleObjectResult: %lu", waitResult); + } + } + + if (progress.IsCancelledBy(CancelReason::Any)) + { + return {}; + } + else + { + DWORD exitCode = 0; + GetExitCodeProcess(process.get(), &exitCode); + return exitCode; + } + } + + std::optional InvokeShellExecute(const std::filesystem::path& filePath, const std::string& args, IProgressCallback& progress) + { + // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete. + // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. + return InvokeShellExecuteEx(filePath, args, false, SW_SHOW, progress); + } + + // Gets the escaped installer args. + std::string GetInstallerArgsTemplate(Execution::Context& context) + { + bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); + + const auto& installer = context.Get(); + const auto& installerSwitches = installer->Switches; + std::string installerArgs = {}; + + // Construct install experience arg. + // SilentWithProgress is default, so look for it first. + auto experienceArgsItr = installerSwitches.find(InstallerSwitchType::SilentWithProgress); + + if (context.Args.Contains(Execution::Args::Type::Interactive)) + { + // If interactive requested, always use Interactive (or nothing). If the installer supports + // interactive it is usually the default, and thus it is cumbersome to put a blank entry in + // the manifest. + experienceArgsItr = installerSwitches.find(InstallerSwitchType::Interactive); + } + // If no SilentWithProgress exists, or Silent requested, try to find Silent. + else if (experienceArgsItr == installerSwitches.end() || context.Args.Contains(Execution::Args::Type::Silent)) + { + auto silentItr = installerSwitches.find(InstallerSwitchType::Silent); + // If Silent requested, but doesn't exist, then continue using SilentWithProgress. + if (silentItr != installerSwitches.end()) + { + experienceArgsItr = silentItr; + } + } + + if (experienceArgsItr != installerSwitches.end()) + { + installerArgs += experienceArgsItr->second; + } + + // Construct log path arg. + if (installerSwitches.find(InstallerSwitchType::Log) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Log); + } + + // Construct repair arg. Custom switches and other args are not applicable for repair scenario so we can return here. + if (isRepair) + { + if (installerSwitches.find(InstallerSwitchType::Repair) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Repair); + } + + return installerArgs; + } + + // Construct custom arg. + if (installerSwitches.find(InstallerSwitchType::Custom) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Custom); + } + + // Construct custom arg passed in by cli arg + if (context.Args.Contains(Execution::Args::Type::CustomSwitches)) + { + std::string_view customSwitches = context.Args.GetArg(Execution::Args::Type::CustomSwitches); + // Since these arguments are appended to the installer at runtime, it doesn't make sense to append them if empty or whitespace + if (!Utility::IsEmptyOrWhitespace(customSwitches)) + { + installerArgs += ' ' + std::string{ customSwitches }; + } + } + + // Construct update arg if applicable + if (isUpdate && installerSwitches.find(InstallerSwitchType::Update) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::Update); + } + + // Construct install location arg if necessary. + if (context.Args.Contains(Execution::Args::Type::InstallLocation) && + installerSwitches.find(InstallerSwitchType::InstallLocation) != installerSwitches.end()) + { + installerArgs += ' ' + installerSwitches.at(InstallerSwitchType::InstallLocation); + } + + return installerArgs; + } + + // Applies values to the template. + void PopulateInstallerArgsTemplate(Execution::Context& context, std::string& installerArgs) + { + // Populate with value from command line or temp path. + std::string logPath; + if (context.Args.Contains(Execution::Args::Type::Log)) + { + logPath = context.Args.GetArg(Execution::Args::Type::Log); + } + else + { + const auto& manifest = context.Get(); + const Logging::LogNameStrategy logNameStrategy = Settings::User().Get(); + auto path = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); + + switch (logNameStrategy) + { + case Logging::LogNameStrategy::Manifest: + // Use manifest ID and version for log file name + // Results in \.-.log + path /= Utility::ConvertToUTF16(manifest.Id + '.' + manifest.Version); + path += '-'; + path += Utility::GetCurrentTimeForFilename(true); + break; + case Logging::LogNameStrategy::Timestamp: + // Use only timestamp for log file name + // Results in \.log + path /= Utility::GetCurrentTimeForFilename(true); + break; + case Logging::LogNameStrategy::Guid: + // Use a GUID for log file name + // Results in \.log + path /= Utility::CreateNewGuidNameWString(); + break; + case Logging::LogNameStrategy::ShortGuid: + // Use the first 8 characters of a GUID for log file name + // Results in \.log + path /= Utility::CreateNewGuidNameWString().substr(0, 8); + break; + default: + // This should never happen due to validation when reading settings, but handle it just in case. + AICLI_LOG(CLI, Error, << "Unknown log naming strategy."); THROW_HR(E_UNEXPECTED); - break; - } - - // Add the extension to the log file regardless of naming strategy - path += Logging::FileLogger::DefaultExt(); - - logPath = path.u8string(); - } - - if (Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_LOGPATH), logPath)) - { - context.Add(Utility::ConvertToUTF16(logPath)); - } - - // Populate with value from command line. - if (context.Args.Contains(Execution::Args::Type::InstallLocation)) - { - Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), context.Args.GetArg(Execution::Args::Type::InstallLocation)); - } - - // Todo: language token support will be implemented later - } - - // Gets the arguments for uninstalling an MSI with MsiExec - std::string GetMsiExecUninstallArgs(Execution::Context& context, const Utility::LocIndString& productCode) - { - std::string args = "/x" + productCode.get(); - - // https://learn.microsoft.com/en-us/windows/win32/msi/standard-installer-command-line-options - if (context.Args.Contains(Execution::Args::Type::Silent)) - { - args += " /quiet /norestart"; - } - else if (!context.Args.Contains(Execution::Args::Type::Interactive)) - { - args += " /passive /norestart"; - } - - return args; - } - - // Gets the arguments for repairing an MSI with MsiExec - std::string GetMsiExecRepairArgs(Execution::Context& context, const Utility::LocIndString& productCode) - { - // https://learn.microsoft.com/en-us/windows/win32/msi/command-line-options - // Available Options for '/f [p|o|e|d|c|a|u|m|s|v] ' - // Default parameter for '/f' is 'omus' - // o - Reinstall all files regardless of version - // m - Rewrite all required registry entries (This is the default option) - // u - Rewrite all required user-specific registry entries (This is the default option) - // s - Overwrite all existing shortcuts (This is the default option) - std::string args = "/f " + productCode.get(); - - // https://learn.microsoft.com/en-us/windows/win32/msi/standard-installer-command-line-options - if (context.Args.Contains(Execution::Args::Type::Silent)) - { - args += " /quiet /norestart"; - } - else if (!context.Args.Contains(Execution::Args::Type::Interactive)) - { - args += " /passive /norestart"; - } - - return args; - } - } - - void ShellExecuteInstallImpl(Execution::Context& context) - { - bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); - - if (isRepair) - { - context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; - } - - const auto& installer = context.Get(); - const std::string& installerArgs = context.Get(); - - // Inform of elevation requirements - bool isElevated = Runtime::IsRunningAsAdmin(); - - // The installer will run elevated, either by direct request or through the installer itself doing so. - if ((installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired || - installer->ElevationRequirement == ElevationRequirementEnum::ElevatesSelf) - && !isElevated) - { - context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl; - } - - // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete. - // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. - auto installResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecuteEx, - context.Get(), - installerArgs, - installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired && !isElevated, - SW_SHOW, - std::placeholders::_1)); - - if (!installResult) - { - if (isRepair) - { - context.Reporter.Warn() << Resource::String::RepairAbandoned << std::endl; - } - else - { - context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; - } - - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else - { - context.Add(installResult.value()); - } - } - - void GetInstallerArgs(Execution::Context& context) - { - // If override switch is specified, use the override value as installer args. - if (context.Args.Contains(Execution::Args::Type::Override)) - { - context.Add(std::string{ context.Args.GetArg(Execution::Args::Type::Override) }); - return; - } - - std::string installerArgs = GetInstallerArgsTemplate(context); - - PopulateInstallerArgsTemplate(context, installerArgs); - - AICLI_LOG(CLI, Info, << "Installer args: " << installerArgs); - context.Add(std::move(installerArgs)); - } - - void ShellExecuteUninstallImpl(Execution::Context& context) - { - context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; - std::wstring commandUtf16 = Utility::ConvertToUTF16(context.Get()); - - // Parse the command string as application and command line for CreateProcess - wil::unique_cotaskmem_string app = nullptr; - wil::unique_cotaskmem_string args = nullptr; - THROW_IF_FAILED(SHEvaluateSystemCommandTemplate(commandUtf16.c_str(), &app, NULL, &args)); - - auto uninstallResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecute, - std::filesystem::path(app.get()), - Utility::ConvertToUTF8(args.get()), - std::placeholders::_1)); - - if (!uninstallResult) - { - context.Reporter.Warn() << Resource::String::UninstallAbandoned << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else - { - context.Add(uninstallResult.value()); - } - } - - void ShellExecuteRepairImpl(Execution::Context& context) - { - context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; - - std::wstring commandUtf16 = Utility::ConvertToUTF16(context.Get()); - - // When running as admin, block attempt to repair user scope installed package. - // [NOTE:] This check is to address the security concern related to above scenario. - if (Runtime::IsRunningAsAdmin()) - { - auto installedPackageVersion = context.Get(); - const std::string installedScopeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledScope]; - auto scopeEnum = ConvertToScopeEnum(installedScopeString); - - if (scopeEnum == ScopeEnum::User) - { - context.Reporter.Error() << Resource::String::NoAdminRepairForUserScopePackage << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); - } - } - - // Parse the command string as application and command line for CreateProcess - wil::unique_cotaskmem_string app = nullptr; - wil::unique_cotaskmem_string args = nullptr; - THROW_IF_FAILED(SHEvaluateSystemCommandTemplate(commandUtf16.c_str(), &app, NULL, &args)); - - auto repairResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecute, - std::filesystem::path(app.get()), - Utility::ConvertToUTF8(args.get()), - std::placeholders::_1)); - - if (!repairResult) - { - context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else - { - context.Add(repairResult.value()); - } - } - - void ShellExecuteMsiExecUninstall(Execution::Context& context) - { - const auto& productCodes = context.Get(); - context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; - - const std::filesystem::path msiexecPath{ ExpandEnvironmentVariables(L"%windir%\\system32\\msiexec.exe") }; - - for (const auto& productCode : productCodes) - { - AICLI_LOG(CLI, Info, << "Removing: " << productCode); - auto uninstallResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecute, - msiexecPath, - GetMsiExecUninstallArgs(context, productCode), - std::placeholders::_1)); - - if (!uninstallResult) - { - context.Reporter.Error() << Resource::String::UninstallAbandoned << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else - { - context.Add(uninstallResult.value()); - } - } - } - - void ShellExecuteMsiExecRepair(Execution::Context& context) - { - const auto& productCodes = context.Get(); - context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; - - const std::filesystem::path msiexecPath{ ExpandEnvironmentVariables(L"%windir%\\system32\\msiexec.exe") }; - - for (const auto& productCode : productCodes) - { - AICLI_LOG(CLI, Info, << "Repairing: " << productCode); - auto repairResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecute, - msiexecPath, - GetMsiExecRepairArgs(context, productCode), - std::placeholders::_1)); - - if (!repairResult) - { - context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else - { - context.Add(repairResult.value()); - } - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - std::optional s_EnableWindowsFeatureResult_Override{}; - - void TestHook_SetEnableWindowsFeatureResult_Override(std::optional&& result) - { - s_EnableWindowsFeatureResult_Override = std::move(result); - } - - std::optional s_DoesWindowsFeatureExistResult_Override{}; - - void TestHook_SetDoesWindowsFeatureExistResult_Override(std::optional&& result) - { - s_DoesWindowsFeatureExistResult_Override = std::move(result); - } -#endif - - std::filesystem::path GetDismExecutablePath() - { - return AppInstaller::Filesystem::GetExpandedPath("%windir%\\system32\\dism.exe"); - } - - std::optional DoesWindowsFeatureExist(Execution::Context& context, std::string_view featureName) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_DoesWindowsFeatureExistResult_Override) - { - return s_DoesWindowsFeatureExistResult_Override; - } -#endif - - std::string args = "/Online /Get-FeatureInfo /FeatureName:" + std::string{ featureName }; - auto dismExecPath = GetDismExecutablePath(); - - auto getFeatureInfoResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecuteEx, - dismExecPath, - args, - false, - SW_HIDE, - std::placeholders::_1)); - - return getFeatureInfoResult; - } - - std::optional EnableWindowsFeature(Execution::Context& context, std::string_view featureName) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_EnableWindowsFeatureResult_Override) - { - return s_EnableWindowsFeatureResult_Override; - } -#endif - - std::string args = "/Online /Enable-Feature /NoRestart /FeatureName:" + std::string{ featureName }; - auto dismExecPath = GetDismExecutablePath(); - - AICLI_LOG(Core, Info, << "Enabling Windows Feature [" << featureName << "]"); - - auto enableFeatureResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecuteEx, - dismExecPath, - args, - false, - SW_HIDE, - std::placeholders::_1)); - - return enableFeatureResult; - } - - void ShellExecuteEnableWindowsFeature::operator()(Execution::Context& context) const - { - if (!Utility::IsValidWindowsFeaturePattern(m_featureName)) - { - context.Add(static_cast(E_INVALIDARG)); - return; - } - - Utility::LocIndView locIndFeatureName{ m_featureName }; - - std::optional doesFeatureExistResult = DoesWindowsFeatureExist(context, m_featureName); - - if (!doesFeatureExistResult) - { - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else if (doesFeatureExistResult.value() != ERROR_SUCCESS) - { - context.Add(doesFeatureExistResult.value()); - return; - } - - context.Reporter.Info() << Resource::String::EnablingWindowsFeature(locIndFeatureName) << std::endl; - - std::optional enableFeatureResult = EnableWindowsFeature(context, m_featureName); - - if (!enableFeatureResult) - { - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - else - { - context.Add(enableFeatureResult.value()); - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - std::optional s_ExtractArchiveWithTarResult_Override{}; - - void TestHook_SetExtractArchiveWithTarResult_Override(std::optional&& result) - { - s_ExtractArchiveWithTarResult_Override = std::move(result); - } -#endif - - void ShellExecuteExtractArchive::operator()(Execution::Context& context) const - { - auto tarExecPath = AppInstaller::Filesystem::GetExpandedPath("%windir%\\system32\\tar.exe"); - - std::string args = "-xf \"" + m_archivePath.u8string() + "\" -C \"" + m_destPath.u8string() + "\""; - - std::optional extractArchiveResult; -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_ExtractArchiveWithTarResult_Override) - { - extractArchiveResult = *s_ExtractArchiveWithTarResult_Override; - } - else -#endif - { - extractArchiveResult = context.Reporter.ExecuteWithProgress( - std::bind(InvokeShellExecuteEx, - tarExecPath, - args, - false, - SW_HIDE, - std::placeholders::_1)); - } - - if (!extractArchiveResult) - { - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - - if (extractArchiveResult.value() == ERROR_SUCCESS) - { - AICLI_LOG(CLI, Info, << "Successfully extracted archive"); - context.Reporter.Info() << Resource::String::ExtractArchiveSucceeded << std::endl; - } - else - { - AICLI_LOG(CLI, Info, << "Failed to extract archive with exit code " << extractArchiveResult.value()); - context.Reporter.Error() << Resource::String::ExtractArchiveFailed << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); - } - } -} + break; + } + + // Add the extension to the log file regardless of naming strategy + path += Logging::FileLogger::DefaultExt(); + + logPath = path.u8string(); + } + + if (Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_LOGPATH), logPath)) + { + context.Add(Utility::ConvertToUTF16(logPath)); + } + + // Populate with value from command line. + if (context.Args.Contains(Execution::Args::Type::InstallLocation)) + { + Utility::FindAndReplace(installerArgs, std::string(ARG_TOKEN_INSTALLPATH), context.Args.GetArg(Execution::Args::Type::InstallLocation)); + } + + // Todo: language token support will be implemented later + } + + // Gets the arguments for uninstalling an MSI with MsiExec + std::string GetMsiExecUninstallArgs(Execution::Context& context, const Utility::LocIndString& productCode) + { + std::string args = "/x" + productCode.get(); + + // https://learn.microsoft.com/en-us/windows/win32/msi/standard-installer-command-line-options + if (context.Args.Contains(Execution::Args::Type::Silent)) + { + args += " /quiet /norestart"; + } + else if (!context.Args.Contains(Execution::Args::Type::Interactive)) + { + args += " /passive /norestart"; + } + + return args; + } + + // Gets the arguments for repairing an MSI with MsiExec + std::string GetMsiExecRepairArgs(Execution::Context& context, const Utility::LocIndString& productCode) + { + // https://learn.microsoft.com/en-us/windows/win32/msi/command-line-options + // Available Options for '/f [p|o|e|d|c|a|u|m|s|v] ' + // Default parameter for '/f' is 'omus' + // o - Reinstall all files regardless of version + // m - Rewrite all required registry entries (This is the default option) + // u - Rewrite all required user-specific registry entries (This is the default option) + // s - Overwrite all existing shortcuts (This is the default option) + std::string args = "/f " + productCode.get(); + + // https://learn.microsoft.com/en-us/windows/win32/msi/standard-installer-command-line-options + if (context.Args.Contains(Execution::Args::Type::Silent)) + { + args += " /quiet /norestart"; + } + else if (!context.Args.Contains(Execution::Args::Type::Interactive)) + { + args += " /passive /norestart"; + } + + return args; + } + } + + void ShellExecuteInstallImpl(Execution::Context& context) + { + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); + + if (isRepair) + { + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::InstallFlowStartingPackageInstall << std::endl; + } + + const auto& installer = context.Get(); + const std::string& installerArgs = context.Get(); + + // Inform of elevation requirements + bool isElevated = Runtime::IsRunningAsAdmin(); + + // The installer will run elevated, either by direct request or through the installer itself doing so. + if ((installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired || + installer->ElevationRequirement == ElevationRequirementEnum::ElevatesSelf) + && !isElevated) + { + context.Reporter.Warn() << Resource::String::InstallerElevationExpected << std::endl; + } + + // Some installers force UI. Setting to SW_HIDE will hide installer UI and installation will never complete. + // Verified setting to SW_SHOW does not hurt silent mode since no UI will be shown. + auto installResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecuteEx, + context.Get(), + installerArgs, + installer->ElevationRequirement == ElevationRequirementEnum::ElevationRequired && !isElevated, + SW_SHOW, + std::placeholders::_1)); + + if (!installResult) + { + if (isRepair) + { + context.Reporter.Warn() << Resource::String::RepairAbandoned << std::endl; + } + else + { + context.Reporter.Warn() << Resource::String::InstallAbandoned << std::endl; + } + + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add(installResult.value()); + } + } + + void GetInstallerArgs(Execution::Context& context) + { + // If override switch is specified, use the override value as installer args. + if (context.Args.Contains(Execution::Args::Type::Override)) + { + context.Add(std::string{ context.Args.GetArg(Execution::Args::Type::Override) }); + return; + } + + std::string installerArgs = GetInstallerArgsTemplate(context); + + PopulateInstallerArgsTemplate(context, installerArgs); + + AICLI_LOG(CLI, Info, << "Installer args: " << installerArgs); + context.Add(std::move(installerArgs)); + } + + void ShellExecuteUninstallImpl(Execution::Context& context) + { + context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; + std::wstring commandUtf16 = Utility::ConvertToUTF16(context.Get()); + + // Parse the command string as application and command line for CreateProcess + wil::unique_cotaskmem_string app = nullptr; + wil::unique_cotaskmem_string args = nullptr; + THROW_IF_FAILED(SHEvaluateSystemCommandTemplate(commandUtf16.c_str(), &app, NULL, &args)); + + auto uninstallResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecute, + std::filesystem::path(app.get()), + Utility::ConvertToUTF8(args.get()), + std::placeholders::_1)); + + if (!uninstallResult) + { + context.Reporter.Warn() << Resource::String::UninstallAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add(uninstallResult.value()); + } + } + + void ShellExecuteRepairImpl(Execution::Context& context) + { + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + std::wstring commandUtf16 = Utility::ConvertToUTF16(context.Get()); + + // When running as admin, block attempt to repair user scope installed package. + // [NOTE:] This check is to address the security concern related to above scenario. + if (Runtime::IsRunningAsAdmin()) + { + auto installedPackageVersion = context.Get(); + const std::string installedScopeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledScope]; + auto scopeEnum = ConvertToScopeEnum(installedScopeString); + + if (scopeEnum == ScopeEnum::User) + { + context.Reporter.Error() << Resource::String::NoAdminRepairForUserScopePackage << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); + } + } + + // Parse the command string as application and command line for CreateProcess + wil::unique_cotaskmem_string app = nullptr; + wil::unique_cotaskmem_string args = nullptr; + THROW_IF_FAILED(SHEvaluateSystemCommandTemplate(commandUtf16.c_str(), &app, NULL, &args)); + + auto repairResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecute, + std::filesystem::path(app.get()), + Utility::ConvertToUTF8(args.get()), + std::placeholders::_1)); + + if (!repairResult) + { + context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add(repairResult.value()); + } + } + + void ShellExecuteMsiExecUninstall(Execution::Context& context) + { + const auto& productCodes = context.Get(); + context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; + + const std::filesystem::path msiexecPath{ ExpandEnvironmentVariables(L"%windir%\\system32\\msiexec.exe") }; + + for (const auto& productCode : productCodes) + { + AICLI_LOG(CLI, Info, << "Removing: " << productCode); + auto uninstallResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecute, + msiexecPath, + GetMsiExecUninstallArgs(context, productCode), + std::placeholders::_1)); + + if (!uninstallResult) + { + context.Reporter.Error() << Resource::String::UninstallAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add(uninstallResult.value()); + } + } + } + + void ShellExecuteMsiExecRepair(Execution::Context& context) + { + const auto& productCodes = context.Get(); + context.Reporter.Info() << Resource::String::RepairFlowStartingPackageRepair << std::endl; + + const std::filesystem::path msiexecPath{ ExpandEnvironmentVariables(L"%windir%\\system32\\msiexec.exe") }; + + for (const auto& productCode : productCodes) + { + AICLI_LOG(CLI, Info, << "Repairing: " << productCode); + auto repairResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecute, + msiexecPath, + GetMsiExecRepairArgs(context, productCode), + std::placeholders::_1)); + + if (!repairResult) + { + context.Reporter.Error() << Resource::String::RepairAbandoned << std::endl; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add(repairResult.value()); + } + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + std::optional s_EnableWindowsFeatureResult_Override{}; + + void TestHook_SetEnableWindowsFeatureResult_Override(std::optional&& result) + { + s_EnableWindowsFeatureResult_Override = std::move(result); + } + + std::optional s_DoesWindowsFeatureExistResult_Override{}; + + void TestHook_SetDoesWindowsFeatureExistResult_Override(std::optional&& result) + { + s_DoesWindowsFeatureExistResult_Override = std::move(result); + } +#endif + + std::filesystem::path GetDismExecutablePath() + { + return AppInstaller::Filesystem::GetExpandedPath("%windir%\\system32\\dism.exe"); + } + + std::optional DoesWindowsFeatureExist(Execution::Context& context, std::string_view featureName) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_DoesWindowsFeatureExistResult_Override) + { + return s_DoesWindowsFeatureExistResult_Override; + } +#endif + + std::string args = "/Online /Get-FeatureInfo /FeatureName:" + std::string{ featureName }; + auto dismExecPath = GetDismExecutablePath(); + + auto getFeatureInfoResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecuteEx, + dismExecPath, + args, + false, + SW_HIDE, + std::placeholders::_1)); + + return getFeatureInfoResult; + } + + std::optional EnableWindowsFeature(Execution::Context& context, std::string_view featureName) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_EnableWindowsFeatureResult_Override) + { + return s_EnableWindowsFeatureResult_Override; + } +#endif + + std::string args = "/Online /Enable-Feature /NoRestart /FeatureName:" + std::string{ featureName }; + auto dismExecPath = GetDismExecutablePath(); + + AICLI_LOG(Core, Info, << "Enabling Windows Feature [" << featureName << "]"); + + auto enableFeatureResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecuteEx, + dismExecPath, + args, + false, + SW_HIDE, + std::placeholders::_1)); + + return enableFeatureResult; + } + + void ShellExecuteEnableWindowsFeature::operator()(Execution::Context& context) const + { + if (!Utility::IsValidWindowsFeaturePattern(m_featureName)) + { + context.Add(static_cast(E_INVALIDARG)); + return; + } + + Utility::LocIndView locIndFeatureName{ m_featureName }; + + std::optional doesFeatureExistResult = DoesWindowsFeatureExist(context, m_featureName); + + if (!doesFeatureExistResult) + { + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else if (doesFeatureExistResult.value() != ERROR_SUCCESS) + { + context.Add(doesFeatureExistResult.value()); + return; + } + + context.Reporter.Info() << Resource::String::EnablingWindowsFeature(locIndFeatureName) << std::endl; + + std::optional enableFeatureResult = EnableWindowsFeature(context, m_featureName); + + if (!enableFeatureResult) + { + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + else + { + context.Add(enableFeatureResult.value()); + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + std::optional s_ExtractArchiveWithTarResult_Override{}; + + void TestHook_SetExtractArchiveWithTarResult_Override(std::optional&& result) + { + s_ExtractArchiveWithTarResult_Override = std::move(result); + } +#endif + + void ShellExecuteExtractArchive::operator()(Execution::Context& context) const + { + auto tarExecPath = AppInstaller::Filesystem::GetExpandedPath("%windir%\\system32\\tar.exe"); + + std::string args = "-xf \"" + m_archivePath.u8string() + "\" -C \"" + m_destPath.u8string() + "\""; + + std::optional extractArchiveResult; +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_ExtractArchiveWithTarResult_Override) + { + extractArchiveResult = *s_ExtractArchiveWithTarResult_Override; + } + else +#endif + { + extractArchiveResult = context.Reporter.ExecuteWithProgress( + std::bind(InvokeShellExecuteEx, + tarExecPath, + args, + false, + SW_HIDE, + std::placeholders::_1)); + } + + if (!extractArchiveResult) + { + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + + if (extractArchiveResult.value() == ERROR_SUCCESS) + { + AICLI_LOG(CLI, Info, << "Successfully extracted archive"); + context.Reporter.Info() << Resource::String::ExtractArchiveSucceeded << std::endl; + } + else + { + AICLI_LOG(CLI, Info, << "Failed to extract archive with exit code " << extractArchiveResult.value()); + context.Reporter.Error() << Resource::String::ExtractArchiveFailed << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h index 8dc3cfb70e..830698c705 100644 --- a/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h +++ b/src/AppInstallerCLICore/Workflows/ShellExecuteInstallerHandler.h @@ -1,78 +1,78 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "ExecutionContext.h" - -#include -#include - -// ShellExecuteInstallerHandler handles installers run through ShellExecute. -// Exe, Wix, Nullsoft, Msi and Inno should be handled by this installer handler. -namespace AppInstaller::CLI::Workflow -{ - // Install is done through invoking ShellExecute on downloaded installer. - // Required Args: None - // Inputs: Manifest?, InstallerPath, InstallerArgs - // Outputs: OperationReturnCode - void ShellExecuteInstallImpl(Execution::Context& context); - - // Uninstall is done through invoking ShellExecute on uninstall string. - // Required Args: None - // Inputs: UninstallString - // Outputs: OperationReturnCode - void ShellExecuteUninstallImpl(Execution::Context& context); - - // Removes the MSI - // Required Args: None - // Inputs: ProductCodes - // Output: None - void ShellExecuteMsiExecUninstall(Execution::Context& context); - - // Gets the installer args from the context. - // Required Args: None - // Inputs: Manifest?, Installer, InstallerPath - // Outputs: InstallerArgs - void GetInstallerArgs(Execution::Context& context); - - // Repair is done through invoking ShellExecute on downloaded installer. - // Required Args: None - // Inputs: Manifest?, InstallerPath, InstallerArgs - // Outputs: OperationReturnCode - void ShellExecuteRepairImpl(Execution::Context& context); - - // Repair the MSI - // Required Args: None - // Inputs: ProductCodes - // Output: None - void ShellExecuteMsiExecRepair(Execution::Context& context); - - // Enables the Windows Feature dependency by invoking ShellExecute on the DISM executable. - // Required Args: None - // Inputs: Windows Feature dependency - // Outputs: None - struct ShellExecuteEnableWindowsFeature : public WorkflowTask - { - ShellExecuteEnableWindowsFeature(std::string_view featureName) : WorkflowTask("ShellExecuteEnableWindowsFeature"), m_featureName(featureName) {} - - void operator()(Execution::Context& context) const override; - - private: - std::string_view m_featureName; - }; - - // Extracts the installer archive using the tar executable. - // Required Args: None - // Inputs: InstallerPath - // Outputs: None - struct ShellExecuteExtractArchive : public WorkflowTask - { - ShellExecuteExtractArchive(const std::filesystem::path& archivePath, const std::filesystem::path& destPath) : WorkflowTask("ShellExecuteExtractArchive"), m_archivePath(archivePath), m_destPath(destPath) {} - - void operator()(Execution::Context& context) const override; - - private: - std::filesystem::path m_archivePath; - std::filesystem::path m_destPath; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "ExecutionContext.h" + +#include +#include + +// ShellExecuteInstallerHandler handles installers run through ShellExecute. +// Exe, Wix, Nullsoft, Msi and Inno should be handled by this installer handler. +namespace AppInstaller::CLI::Workflow +{ + // Install is done through invoking ShellExecute on downloaded installer. + // Required Args: None + // Inputs: Manifest?, InstallerPath, InstallerArgs + // Outputs: OperationReturnCode + void ShellExecuteInstallImpl(Execution::Context& context); + + // Uninstall is done through invoking ShellExecute on uninstall string. + // Required Args: None + // Inputs: UninstallString + // Outputs: OperationReturnCode + void ShellExecuteUninstallImpl(Execution::Context& context); + + // Removes the MSI + // Required Args: None + // Inputs: ProductCodes + // Output: None + void ShellExecuteMsiExecUninstall(Execution::Context& context); + + // Gets the installer args from the context. + // Required Args: None + // Inputs: Manifest?, Installer, InstallerPath + // Outputs: InstallerArgs + void GetInstallerArgs(Execution::Context& context); + + // Repair is done through invoking ShellExecute on downloaded installer. + // Required Args: None + // Inputs: Manifest?, InstallerPath, InstallerArgs + // Outputs: OperationReturnCode + void ShellExecuteRepairImpl(Execution::Context& context); + + // Repair the MSI + // Required Args: None + // Inputs: ProductCodes + // Output: None + void ShellExecuteMsiExecRepair(Execution::Context& context); + + // Enables the Windows Feature dependency by invoking ShellExecute on the DISM executable. + // Required Args: None + // Inputs: Windows Feature dependency + // Outputs: None + struct ShellExecuteEnableWindowsFeature : public WorkflowTask + { + ShellExecuteEnableWindowsFeature(std::string_view featureName) : WorkflowTask("ShellExecuteEnableWindowsFeature"), m_featureName(featureName) {} + + void operator()(Execution::Context& context) const override; + + private: + std::string_view m_featureName; + }; + + // Extracts the installer archive using the tar executable. + // Required Args: None + // Inputs: InstallerPath + // Outputs: None + struct ShellExecuteExtractArchive : public WorkflowTask + { + ShellExecuteExtractArchive(const std::filesystem::path& archivePath, const std::filesystem::path& destPath) : WorkflowTask("ShellExecuteExtractArchive"), m_archivePath(archivePath), m_destPath(destPath) {} + + void operator()(Execution::Context& context) const override; + + private: + std::filesystem::path m_archivePath; + std::filesystem::path m_destPath; + }; +} diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp index d0c7e212ab..eb872b2110 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.cpp @@ -1,254 +1,254 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "ShowFlow.h" -#include -#include "TableOutput.h" - -using namespace AppInstaller::Repository; -using namespace AppInstaller::CLI; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - void ShowSingleLineField(Execution::OutputStream& outputStream, StringResource::StringId label, const Manifest::Manifest::string_t& value, bool indent = false) - { - Workflow::ShowSingleLineField(outputStream, label, LocIndView{ value }, indent ? 1 : 0); - } - - void ShowMultiLineField(Execution::OutputStream& outputStream, StringResource::StringId label, const Manifest::Manifest::string_t& value) - { - Workflow::ShowMultiLineField(outputStream, label, LocIndView{ value }); - } - - void ShowAgreements(Execution::OutputStream& outputStream, const std::vector& agreements) { - - if (agreements.empty()) { - return; - } - - outputStream << Execution::ManifestInfoEmphasis << Resource::String::ShowLabelAgreements << std::endl; - for (const auto& agreement : agreements) { - - if (!agreement.Label.empty()) - { - outputStream << " "_liv << Execution::ManifestInfoEmphasis << agreement.Label << ": "_liv; - } - - if (!agreement.AgreementText.empty()) - { - outputStream << agreement.AgreementText << std::endl; - } - - if (!agreement.AgreementUrl.empty()) - { - outputStream << agreement.AgreementUrl << std::endl; - } - } - } - } - - namespace details - { - LocIndView GetIndentFor(size_t indentLevel) - { - static constexpr std::array s_indents{ ""_liv, " "_liv, " "_liv, " "_liv }; - return s_indents.at(indentLevel); - } - } - - void ShowAgreementsInfo(Execution::Context& context) - { - const auto& manifest = context.Get(); - auto info = context.Reporter.Info(); - - ShowSingleLineField(info, Resource::String::ShowLabelVersion, manifest.Version); - ShowSingleLineField(info, Resource::String::ShowLabelPublisher, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPublisherUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPublisherSupportUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelAuthor, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPackageUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelLicense, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelLicenseUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPrivacyUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelCopyright, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelCopyrightUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPurchaseUrl, manifest.CurrentLocalization.Get()); - ShowAgreements(info, manifest.CurrentLocalization.Get()); - - } - - void ShowManifestInfo(Execution::Context& context) - { - context << ShowPackageInfo << ShowInstallerInfo; - } - - void ShowPackageInfo(Execution::Context& context) - { - const auto& manifest = context.Get(); - auto info = context.Reporter.Info(); - // Get description from manifest so we can see if it is empty later - auto description = manifest.CurrentLocalization.Get(); - - // TODO: Come up with a prettier format - ShowSingleLineField(info, Resource::String::ShowLabelVersion, manifest.Version); - ShowSingleLineField(info, Resource::String::ShowLabelPublisher, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPublisherUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPublisherSupportUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelAuthor, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelMoniker, manifest.Moniker); - ShowMultiLineField(info, Resource::String::ShowLabelDescription, description.empty() ? manifest.CurrentLocalization.Get() : description); - ShowSingleLineField(info, Resource::String::ShowLabelPackageUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelLicense, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelLicenseUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPrivacyUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelCopyright, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelCopyrightUrl, manifest.CurrentLocalization.Get()); - ShowMultiLineField(info, Resource::String::ShowLabelReleaseNotes, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelReleaseNotesUrl, manifest.CurrentLocalization.Get()); - ShowSingleLineField(info, Resource::String::ShowLabelPurchaseUrl, manifest.CurrentLocalization.Get()); - ShowMultiLineField(info, Resource::String::ShowLabelInstallationNotes, manifest.CurrentLocalization.Get()); - const auto& documentations = manifest.CurrentLocalization.Get(); - if (!documentations.empty()) - { - context.Reporter.Info() << Execution::ManifestInfoEmphasis << Resource::String::ShowLabelDocumentation << std::endl; - for (const auto& documentation : documentations) - { - if (!documentation.DocumentUrl.empty()) - { - info << " "_liv; - if (!documentation.DocumentLabel.empty()) - { - info << Execution::ManifestInfoEmphasis << documentation.DocumentLabel << ": "_liv; - } - - info << documentation.DocumentUrl << std::endl; - } - } - } - ShowMultiValueField(info, Resource::String::ShowLabelTags, manifest.CurrentLocalization.Get()); - ShowAgreements(info, manifest.CurrentLocalization.Get()); - } - - void ShowInstallerInfo(Execution::Context& context) - { - const auto& installer = context.Get(); - auto info = context.Reporter.Info(); - - info << Execution::ManifestInfoEmphasis << Resource::String::ShowLabelInstaller << std::endl; - if (installer) - { - Manifest::InstallerTypeEnum effectiveInstallerType = installer->EffectiveInstallerType(); - Manifest::InstallerTypeEnum baseInstallerType = installer->BaseInstallerType; - std::string shownInstallerType; - shownInstallerType = Manifest::InstallerTypeToString(effectiveInstallerType); - if (effectiveInstallerType != baseInstallerType) - { - shownInstallerType += " ("_liv; - shownInstallerType += Manifest::InstallerTypeToString(baseInstallerType); - shownInstallerType += ')'; - } - ShowSingleLineField(info, Resource::String::ShowLabelInstallerType, shownInstallerType, true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerLocale, installer->Locale, true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerUrl, installer->Url, true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerSha256, (installer->Sha256.empty()) ? "" : Utility::SHA256::ConvertToString(installer->Sha256), true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerProductId, installer->ProductId, true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerReleaseDate, installer->ReleaseDate, true); - ShowSingleLineField(info, Resource::String::ShowLabelInstallerOfflineDistributionSupported, Utility::ConvertBoolToString(!installer->DownloadCommandProhibited), true); - - const auto& dependencies = installer->Dependencies; - - if (dependencies.HasAny()) - { - info << Execution::ManifestInfoEmphasis << " "_liv << Resource::String::ShowLabelDependencies << ' ' << std::endl; - - if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsFeature)) - { - info << " - "_liv << Resource::String::ShowLabelWindowsFeaturesDependencies << ' ' << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::WindowsFeature, [&info](Manifest::Dependency dependency) {info << " "_liv << dependency.Id() << std::endl; }); - } - - if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsLibrary)) - { - info << " - "_liv << Resource::String::ShowLabelWindowsLibrariesDependencies << ' ' << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::WindowsLibrary, [&info](Manifest::Dependency dependency) {info << " "_liv << dependency.Id() << std::endl; }); - } - - if (dependencies.HasAnyOf(Manifest::DependencyType::Package)) - { - info << " - "_liv << Resource::String::ShowLabelPackageDependencies << ' ' << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::Package, [&info](Manifest::Dependency dependency) - { - info << " "_liv << dependency.Id(); - if (dependency.MinVersion) - { - info << " [>= " << dependency.MinVersion.value().ToString() << "]"; - } - info << std::endl; - }); - } - - if (dependencies.HasAnyOf(Manifest::DependencyType::External)) - { - info << " - "_liv << Resource::String::ShowLabelExternalDependencies << ' ' << std::endl; - dependencies.ApplyToType(Manifest::DependencyType::External, [&info](Manifest::Dependency dependency) {info << " "_liv << dependency.Id() << std::endl; }); - } - } - } - else - { - context.Reporter.Warn() << " "_liv << Resource::String::NoApplicableInstallers << std::endl; - } - } - - void ShowManifestVersion(Execution::Context& context) - { - const auto& manifest = context.Get(); - Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); - table.OutputLine({ manifest.Version, manifest.Channel }); - table.Complete(); - } - - void GetManifest::operator()(Execution::Context& context) const - { - if (context.Args.Contains(Execution::Args::Type::Manifest)) - { - context << - GetManifestFromArg; - } - else - { - context << - OpenSource() << - SearchSourceForSingle << - HandleSearchResultFailures << - EnsureOneMatchFromSearchResult(OperationType::Show) << - GetManifestFromPackage(m_considerPins); - } - } - - void ShowSingleLineField(Execution::OutputStream& outputStream, StringResource::StringId label, LocIndView value, size_t indentLevel) - { - if (value.empty()) - { - return; - } - - outputStream << details::GetIndentFor(indentLevel) << Execution::ManifestInfoEmphasis << label << ' ' << value << '\n'; - } - - void ShowMultiLineField(Execution::OutputStream& outputStream, StringResource::StringId label, LocIndView value, size_t indentLevel) - { - if (value.empty()) - { - return; - } - - // Treat the lines as separate values for the field - ShowMultiValueField(outputStream, label, Split(value, '\n'), indentLevel); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "ShowFlow.h" +#include +#include "TableOutput.h" + +using namespace AppInstaller::Repository; +using namespace AppInstaller::CLI; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + void ShowSingleLineField(Execution::OutputStream& outputStream, StringResource::StringId label, const Manifest::Manifest::string_t& value, bool indent = false) + { + Workflow::ShowSingleLineField(outputStream, label, LocIndView{ value }, indent ? 1 : 0); + } + + void ShowMultiLineField(Execution::OutputStream& outputStream, StringResource::StringId label, const Manifest::Manifest::string_t& value) + { + Workflow::ShowMultiLineField(outputStream, label, LocIndView{ value }); + } + + void ShowAgreements(Execution::OutputStream& outputStream, const std::vector& agreements) { + + if (agreements.empty()) { + return; + } + + outputStream << Execution::ManifestInfoEmphasis << Resource::String::ShowLabelAgreements << std::endl; + for (const auto& agreement : agreements) { + + if (!agreement.Label.empty()) + { + outputStream << " "_liv << Execution::ManifestInfoEmphasis << agreement.Label << ": "_liv; + } + + if (!agreement.AgreementText.empty()) + { + outputStream << agreement.AgreementText << std::endl; + } + + if (!agreement.AgreementUrl.empty()) + { + outputStream << agreement.AgreementUrl << std::endl; + } + } + } + } + + namespace details + { + LocIndView GetIndentFor(size_t indentLevel) + { + static constexpr std::array s_indents{ ""_liv, " "_liv, " "_liv, " "_liv }; + return s_indents.at(indentLevel); + } + } + + void ShowAgreementsInfo(Execution::Context& context) + { + const auto& manifest = context.Get(); + auto info = context.Reporter.Info(); + + ShowSingleLineField(info, Resource::String::ShowLabelVersion, manifest.Version); + ShowSingleLineField(info, Resource::String::ShowLabelPublisher, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPublisherUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPublisherSupportUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelAuthor, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPackageUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelLicense, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelLicenseUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPrivacyUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelCopyright, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelCopyrightUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPurchaseUrl, manifest.CurrentLocalization.Get()); + ShowAgreements(info, manifest.CurrentLocalization.Get()); + + } + + void ShowManifestInfo(Execution::Context& context) + { + context << ShowPackageInfo << ShowInstallerInfo; + } + + void ShowPackageInfo(Execution::Context& context) + { + const auto& manifest = context.Get(); + auto info = context.Reporter.Info(); + // Get description from manifest so we can see if it is empty later + auto description = manifest.CurrentLocalization.Get(); + + // TODO: Come up with a prettier format + ShowSingleLineField(info, Resource::String::ShowLabelVersion, manifest.Version); + ShowSingleLineField(info, Resource::String::ShowLabelPublisher, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPublisherUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPublisherSupportUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelAuthor, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelMoniker, manifest.Moniker); + ShowMultiLineField(info, Resource::String::ShowLabelDescription, description.empty() ? manifest.CurrentLocalization.Get() : description); + ShowSingleLineField(info, Resource::String::ShowLabelPackageUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelLicense, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelLicenseUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPrivacyUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelCopyright, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelCopyrightUrl, manifest.CurrentLocalization.Get()); + ShowMultiLineField(info, Resource::String::ShowLabelReleaseNotes, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelReleaseNotesUrl, manifest.CurrentLocalization.Get()); + ShowSingleLineField(info, Resource::String::ShowLabelPurchaseUrl, manifest.CurrentLocalization.Get()); + ShowMultiLineField(info, Resource::String::ShowLabelInstallationNotes, manifest.CurrentLocalization.Get()); + const auto& documentations = manifest.CurrentLocalization.Get(); + if (!documentations.empty()) + { + context.Reporter.Info() << Execution::ManifestInfoEmphasis << Resource::String::ShowLabelDocumentation << std::endl; + for (const auto& documentation : documentations) + { + if (!documentation.DocumentUrl.empty()) + { + info << " "_liv; + if (!documentation.DocumentLabel.empty()) + { + info << Execution::ManifestInfoEmphasis << documentation.DocumentLabel << ": "_liv; + } + + info << documentation.DocumentUrl << std::endl; + } + } + } + ShowMultiValueField(info, Resource::String::ShowLabelTags, manifest.CurrentLocalization.Get()); + ShowAgreements(info, manifest.CurrentLocalization.Get()); + } + + void ShowInstallerInfo(Execution::Context& context) + { + const auto& installer = context.Get(); + auto info = context.Reporter.Info(); + + info << Execution::ManifestInfoEmphasis << Resource::String::ShowLabelInstaller << std::endl; + if (installer) + { + Manifest::InstallerTypeEnum effectiveInstallerType = installer->EffectiveInstallerType(); + Manifest::InstallerTypeEnum baseInstallerType = installer->BaseInstallerType; + std::string shownInstallerType; + shownInstallerType = Manifest::InstallerTypeToString(effectiveInstallerType); + if (effectiveInstallerType != baseInstallerType) + { + shownInstallerType += " ("_liv; + shownInstallerType += Manifest::InstallerTypeToString(baseInstallerType); + shownInstallerType += ')'; + } + ShowSingleLineField(info, Resource::String::ShowLabelInstallerType, shownInstallerType, true); + ShowSingleLineField(info, Resource::String::ShowLabelInstallerLocale, installer->Locale, true); + ShowSingleLineField(info, Resource::String::ShowLabelInstallerUrl, installer->Url, true); + ShowSingleLineField(info, Resource::String::ShowLabelInstallerSha256, (installer->Sha256.empty()) ? "" : Utility::SHA256::ConvertToString(installer->Sha256), true); + ShowSingleLineField(info, Resource::String::ShowLabelInstallerProductId, installer->ProductId, true); + ShowSingleLineField(info, Resource::String::ShowLabelInstallerReleaseDate, installer->ReleaseDate, true); + ShowSingleLineField(info, Resource::String::ShowLabelInstallerOfflineDistributionSupported, Utility::ConvertBoolToString(!installer->DownloadCommandProhibited), true); + + const auto& dependencies = installer->Dependencies; + + if (dependencies.HasAny()) + { + info << Execution::ManifestInfoEmphasis << " "_liv << Resource::String::ShowLabelDependencies << ' ' << std::endl; + + if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsFeature)) + { + info << " - "_liv << Resource::String::ShowLabelWindowsFeaturesDependencies << ' ' << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::WindowsFeature, [&info](Manifest::Dependency dependency) {info << " "_liv << dependency.Id() << std::endl; }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::WindowsLibrary)) + { + info << " - "_liv << Resource::String::ShowLabelWindowsLibrariesDependencies << ' ' << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::WindowsLibrary, [&info](Manifest::Dependency dependency) {info << " "_liv << dependency.Id() << std::endl; }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::Package)) + { + info << " - "_liv << Resource::String::ShowLabelPackageDependencies << ' ' << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::Package, [&info](Manifest::Dependency dependency) + { + info << " "_liv << dependency.Id(); + if (dependency.MinVersion) + { + info << " [>= " << dependency.MinVersion.value().ToString() << "]"; + } + info << std::endl; + }); + } + + if (dependencies.HasAnyOf(Manifest::DependencyType::External)) + { + info << " - "_liv << Resource::String::ShowLabelExternalDependencies << ' ' << std::endl; + dependencies.ApplyToType(Manifest::DependencyType::External, [&info](Manifest::Dependency dependency) {info << " "_liv << dependency.Id() << std::endl; }); + } + } + } + else + { + context.Reporter.Warn() << " "_liv << Resource::String::NoApplicableInstallers << std::endl; + } + } + + void ShowManifestVersion(Execution::Context& context) + { + const auto& manifest = context.Get(); + Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); + table.OutputLine({ manifest.Version, manifest.Channel }); + table.Complete(); + } + + void GetManifest::operator()(Execution::Context& context) const + { + if (context.Args.Contains(Execution::Args::Type::Manifest)) + { + context << + GetManifestFromArg; + } + else + { + context << + OpenSource() << + SearchSourceForSingle << + HandleSearchResultFailures << + EnsureOneMatchFromSearchResult(OperationType::Show) << + GetManifestFromPackage(m_considerPins); + } + } + + void ShowSingleLineField(Execution::OutputStream& outputStream, StringResource::StringId label, LocIndView value, size_t indentLevel) + { + if (value.empty()) + { + return; + } + + outputStream << details::GetIndentFor(indentLevel) << Execution::ManifestInfoEmphasis << label << ' ' << value << '\n'; + } + + void ShowMultiLineField(Execution::OutputStream& outputStream, StringResource::StringId label, LocIndView value, size_t indentLevel) + { + if (value.empty()) + { + return; + } + + // Treat the lines as separate values for the field + ShowMultiValueField(outputStream, label, Split(value, '\n'), indentLevel); + } +} diff --git a/src/AppInstallerCLICore/Workflows/ShowFlow.h b/src/AppInstallerCLICore/Workflows/ShowFlow.h index 182f06d3ff..618a1795cb 100644 --- a/src/AppInstallerCLICore/Workflows/ShowFlow.h +++ b/src/AppInstallerCLICore/Workflows/ShowFlow.h @@ -1,82 +1,82 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" -#include -#include -#include - -namespace AppInstaller::CLI::Workflow -{ - // Shows information on an application; this is only the information for package agreements - // Required Args: None - // Inputs: Manifest - // Outputs: None - void ShowAgreementsInfo(Execution::Context& context); - - // Shows information on an application. - // Required Args: None - // Inputs: Manifest, Installer - // Outputs: None - void ShowManifestInfo(Execution::Context& context); - - // Shows information on a package; this is only the information common to all installers. - // Required Args: None - // Inputs: Manifest - // Outputs: None - void ShowPackageInfo(Execution::Context& context); - - // Shows information on an installer - // Required Args: None - // Inputs: Installer - // Outputs: None - void ShowInstallerInfo(Execution::Context& context); - - // Shows the version for the specific manifest. - // Required Args: None - // Inputs: Manifest - // Outputs: None - void ShowManifestVersion(Execution::Context& context); - - // Composite flow that produces a manifest; either from one given on the command line or by searching. - // Required Args: None - // Inputs: None - // Outputs: Manifest - struct GetManifest : public WorkflowTask - { - GetManifest(bool considerPins) : WorkflowTask("GetManifest"), m_considerPins(considerPins) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_considerPins; - }; - - // Reusable helpers for `show` style line output - namespace details - { - Utility::LocIndView GetIndentFor(size_t i); - } - - void ShowSingleLineField(Execution::OutputStream& outputStream, StringResource::StringId label, Utility::LocIndView value, size_t indentLevel = 0); - - void ShowMultiLineField(Execution::OutputStream& outputStream, StringResource::StringId label, Utility::LocIndView value, size_t indentLevel = 0); - - template - void ShowMultiValueField(Execution::OutputStream& outputStream, StringResource::StringId label, const Container& values, size_t indentLevel = 0) - { - if (values.empty()) - { - return; - } - - bool isMultiItem = values.size() > 1; - outputStream << details::GetIndentFor(indentLevel) << Execution::ManifestInfoEmphasis << label; - outputStream << (isMultiItem ? '\n' : ' '); - - for (const auto& value : values) - { - outputStream << details::GetIndentFor(isMultiItem ? indentLevel + 1 : 0) << value << '\n'; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" +#include +#include +#include + +namespace AppInstaller::CLI::Workflow +{ + // Shows information on an application; this is only the information for package agreements + // Required Args: None + // Inputs: Manifest + // Outputs: None + void ShowAgreementsInfo(Execution::Context& context); + + // Shows information on an application. + // Required Args: None + // Inputs: Manifest, Installer + // Outputs: None + void ShowManifestInfo(Execution::Context& context); + + // Shows information on a package; this is only the information common to all installers. + // Required Args: None + // Inputs: Manifest + // Outputs: None + void ShowPackageInfo(Execution::Context& context); + + // Shows information on an installer + // Required Args: None + // Inputs: Installer + // Outputs: None + void ShowInstallerInfo(Execution::Context& context); + + // Shows the version for the specific manifest. + // Required Args: None + // Inputs: Manifest + // Outputs: None + void ShowManifestVersion(Execution::Context& context); + + // Composite flow that produces a manifest; either from one given on the command line or by searching. + // Required Args: None + // Inputs: None + // Outputs: Manifest + struct GetManifest : public WorkflowTask + { + GetManifest(bool considerPins) : WorkflowTask("GetManifest"), m_considerPins(considerPins) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_considerPins; + }; + + // Reusable helpers for `show` style line output + namespace details + { + Utility::LocIndView GetIndentFor(size_t i); + } + + void ShowSingleLineField(Execution::OutputStream& outputStream, StringResource::StringId label, Utility::LocIndView value, size_t indentLevel = 0); + + void ShowMultiLineField(Execution::OutputStream& outputStream, StringResource::StringId label, Utility::LocIndView value, size_t indentLevel = 0); + + template + void ShowMultiValueField(Execution::OutputStream& outputStream, StringResource::StringId label, const Container& values, size_t indentLevel = 0) + { + if (values.empty()) + { + return; + } + + bool isMultiItem = values.size() > 1; + outputStream << details::GetIndentFor(indentLevel) << Execution::ManifestInfoEmphasis << label; + outputStream << (isMultiItem ? '\n' : ' '); + + for (const auto& value : values) + { + outputStream << details::GetIndentFor(isMultiItem ? indentLevel + 1 : 0) << value << '\n'; + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp index 4e0e67f86b..acccb9a421 100644 --- a/src/AppInstallerCLICore/Workflows/SourceFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/SourceFlow.cpp @@ -1,428 +1,428 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Resources.h" -#include "SourceFlow.h" -#include "PromptFlow.h" -#include "TableOutput.h" -#include "WorkflowBase.h" - -namespace AppInstaller::CLI::Workflow -{ - using namespace AppInstaller::CLI::Execution; - using namespace AppInstaller::Settings; - using namespace AppInstaller::Utility::literals; - - void GetSourceList(Execution::Context& context) - { - context.Add(Repository::Source::GetCurrentSources()); - } - - void GetSourceListWithFilter(Execution::Context& context) - { - auto currentSources = Repository::Source::GetCurrentSources(); - if (context.Args.Contains(Args::Type::SourceName)) - { - auto name = Utility::LocIndString{ context.Args.GetArg(Args::Type::SourceName) }; - - for (auto const& source : currentSources) - { - if (Utility::ICUCaseInsensitiveEquals(source.Name, name)) - { - std::vector sources; - sources.emplace_back(source); - context.Add(std::move(sources)); - return; - } - } - - context.Reporter.Error() << Resource::String::SourceListNoneFound(name) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); - } - else - { - context.Add(std::move(currentSources)); - } - } - - void CheckSourceListAgainstAdd(Execution::Context& context) - { - auto sourceList = context.Get(); - std::string_view name = context.Args.GetArg(Args::Type::SourceName); - std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); - std::string_view type = context.Args.GetArg(Args::Type::SourceType); - - // In the absence of a specified type, the default is Microsoft.PreIndexed.Package for comparison. - // The default type assignment to the source takes place during the add operation (Source::Add in Repository.cpp). - // This is necessary for the comparison to function correctly; otherwise, it would allow the addition of multiple - // sources with different names but the same argument for all default type cases. - // For example, the following commands would be allowed, but they acts as different alias to same source: - // winget source add "mysource1" "https:\\mysource" --trust - level trusted - // winget source add "mysource2" "https:\\mysource" --trust - level trusted - if (type.empty()) - { - type = Repository::Source::GetDefaultSourceType(); - } - - for (const auto& details : sourceList) - { - if (Utility::ICUCaseInsensitiveEquals(details.Name, name)) - { - if (details.Arg == arg) - { - // Name and arg match, indicate this to the user and bail. - context.Reporter.Info() << Resource::String::SourceAddAlreadyExistsMatch << std::endl << - " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); - } - else - { - context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentArg << std::endl << - " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); - } - } - - if (!details.Arg.empty() && details.Arg == arg && details.Type == type) - { - context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentName << std::endl << - " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS); - } - } - } - - void AddSource(Execution::Context& context) - { - auto& sourceToAdd = context.Get(); - auto details = sourceToAdd.GetDetails(); - - context.Reporter.Info() << - Resource::String::SourceAddBegin << std::endl << - " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; - - auto addFunction = [&](IProgressCallback& progress)->bool { return sourceToAdd.Add(progress); }; - if (!context.Reporter.ExecuteWithProgress(addFunction)) - { - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::Done << std::endl; - } - } - - void CreateSourceForSourceAdd(Execution::Context& context) - { - try - { - std::string_view name = context.Args.GetArg(Args::Type::SourceName); - std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); - std::string_view type = context.Args.GetArg(Args::Type::SourceType); - - Repository::SourceTrustLevel trustLevel = Repository::SourceTrustLevel::None; - if (context.Args.Contains(Execution::Args::Type::SourceTrustLevel)) - { - std::vector trustLevelArgs = Utility::Split(std::string{ context.Args.GetArg(Execution::Args::Type::SourceTrustLevel) }, '|', true); - trustLevel = Repository::ConvertToSourceTrustLevelFlag(trustLevelArgs); - } - - Repository::SourceEdit additionalProperties; - - if (context.Args.Contains(Args::Type::SourceExplicit)) - { - additionalProperties.Explicit = true; - } - - if (context.Args.Contains(Args::Type::SourcePriority)) - { - additionalProperties.Priority = Utility::TryConvertStringToInt32(context.Args.GetArg(Args::Type::SourcePriority)); - } - - Repository::Source sourceToAdd{ name, arg, type, trustLevel, additionalProperties}; - - if (context.Args.Contains(Execution::Args::Type::CustomHeader)) - { - std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) }; - if (!sourceToAdd.SetCustomHeader(customHeader)) - { - context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; - } - } - - if (sourceToAdd.GetInformation().Authentication.Type == Authentication::AuthenticationType::Unknown) - { - context.Reporter.Error() << Resource::String::SourceAddFailedAuthenticationNotSupported << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); - } - - context << Workflow::HandleSourceAgreements(sourceToAdd); - if (context.IsTerminated()) - { - return; - } - - context.Add(std::move(sourceToAdd)); - } - catch (...) - { - context.Reporter.Error() << Resource::String::SourceAddOpenSourceFailed << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED); - } - } - - void ListSources(Execution::Context& context) - { - const std::vector& sources = context.Get(); - - if (context.Args.Contains(Args::Type::SourceName)) - { - // If a source name was specified, list full details of the one and only source. - const Repository::SourceDetails& source = sources[0]; - - Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue }); - - table.OutputLine({ Resource::LocString(Resource::String::SourceListName), source.Name }); - table.OutputLine({ Resource::LocString(Resource::String::SourceListType), source.Type }); - table.OutputLine({ Resource::LocString(Resource::String::SourceListArg), source.Arg }); - table.OutputLine({ Resource::LocString(Resource::String::SourceListData), source.Data }); - table.OutputLine({ Resource::LocString(Resource::String::SourceListIdentifier), source.Identifier }); - table.OutputLine({ Resource::LocString(Resource::String::SourceListTrustLevel), Repository::GetSourceTrustLevelForDisplay(source.TrustLevel)}); - table.OutputLine({ Resource::LocString(Resource::String::SourceListExplicit), std::string{ Utility::ConvertBoolToString(source.Explicit) } }); - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) - { - table.OutputLine({ Resource::LocString(Resource::String::SourceListPriority), std::to_string(source.Priority) }); - } - - if (source.LastUpdateTime == Utility::ConvertUnixEpochToSystemClock(0)) - { - table.OutputLine({ - Resource::LocString(Resource::String::SourceListUpdated), - Resource::LocString(Resource::String::SourceListUpdatedNever) - }); - } - else - { - std::ostringstream strstr; - strstr << source.LastUpdateTime; - table.OutputLine({ Resource::LocString(Resource::String::SourceListUpdated), strstr.str() }); - } - - table.Complete(); - } - else - { - if (sources.empty()) - { - context.Reporter.Info() << Resource::String::SourceListNoSources << std::endl; - } - else - { - Execution::TableOutput<3> table(context.Reporter, { Resource::String::SourceListName, Resource::String::SourceListArg, Resource::String::SourceListExplicit }); - for (const auto& source : sources) - { - table.OutputLine({ source.Name, source.Arg, std::string{ Utility::ConvertBoolToString(source.Explicit) }}); - } - table.Complete(); - } - } - } - - void UpdateSources(Execution::Context& context) - { - if (!context.Args.Contains(Args::Type::SourceName)) - { - context.Reporter.Info() << Resource::String::SourceUpdateAll << std::endl; - } - - const std::vector& sources = context.Get(); - - for (const auto& sd : sources) - { - Repository::Source source{ sd.Name }; - context.Reporter.Info() << Resource::String::SourceUpdateOne(Utility::LocIndView{ sd.Name }) << std::endl; - auto updateFunction = [&](IProgressCallback& progress)->std::vector { return source.Update(progress); }; - auto sourceDetails = context.Reporter.ExecuteWithProgress(updateFunction); - if (!sourceDetails.empty()) - { - if (std::chrono::system_clock::now() < sourceDetails[0].DoNotUpdateBefore) - { - context.Reporter.Warn() << Resource::String::Unavailable << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - } - } - else - { - context.Reporter.Info() << Resource::String::Done << std::endl; - } - } - } - - void RemoveSources(Execution::Context& context) - { - // TODO: We currently only allow removing a single source. If that changes, - // we need to check all sources with the Group Policy before removing any of them. - if (!context.Args.Contains(Args::Type::SourceName)) - { - context.Reporter.Info() << Resource::String::SourceRemoveAll << std::endl; - } - - const std::vector& sources = context.Get(); - for (const auto& sd : sources) - { - Repository::Source source{ sd.Name }; - context.Reporter.Info() << Resource::String::SourceRemoveOne(Utility::LocIndView{ sd.Name }) << std::endl; - auto removeFunction = [&](IProgressCallback& progress)->bool { return source.Remove(progress); }; - if (context.Reporter.ExecuteWithProgress(removeFunction)) - { - context.Reporter.Info() << Resource::String::Done << std::endl; - } - else - { - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - } - } - } - - void EditSources(Execution::Context& context) - { - // We are assuming there is only one match, as SourceName is a required parameter. - const std::vector& sources = context.Get(); - - for (const auto& sd : sources) - { - // Get the current source with this name. - Repository::Source targetSource{ sd.Name }; - auto oldExplicitValue = sd.Explicit; - auto oldPriorityValue = sd.Priority; - - Repository::SourceEdit edits; - - if (context.Args.Contains(Execution::Args::Type::SourceEditExplicit)) - { - edits.Explicit = Utility::TryConvertStringToBool(context.Args.GetArg(Execution::Args::Type::SourceEditExplicit)); - } - - if (context.Args.Contains(Execution::Args::Type::SourcePriority)) - { - edits.Priority = Utility::TryConvertStringToInt32(context.Args.GetArg(Execution::Args::Type::SourcePriority)); - } - - if (!targetSource.RequiresChanges(edits)) - { - context.Reporter.Info() << Resource::String::SourceEditNoChanges(Utility::LocIndView{ sd.Name }) << std::endl; - continue; - } - - context.Reporter.Info() << Resource::String::SourceEditOne(Utility::LocIndView{ sd.Name }) << std::endl; - targetSource.Edit(edits); - - // Output changed source information table. The name of the source being edited is listed prior to the edits. - Execution::TableOutput<3> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceEditOldValue, Resource::String::SourceEditNewValue }); - - if (edits.Explicit) - { - table.OutputLine({ Resource::LocString(Resource::String::SourceListExplicit), std::string{ Utility::ConvertBoolToString(oldExplicitValue) }, std::string{ Utility::ConvertBoolToString(edits.Explicit.value()) } }); - } - - if (edits.Priority) - { - table.OutputLine({ Resource::LocString(Resource::String::SourceListPriority), std::to_string(oldPriorityValue), std::to_string(edits.Priority.value()) }); - } - - table.Complete(); - } - } - - void QueryUserForSourceReset(Execution::Context& context) - { - if (!context.Args.Contains(Execution::Args::Type::ForceSourceReset)) - { - context << GetSourceListWithFilter; - const std::vector& sources = context.Get(); - - if (!sources.empty()) - { - context.Reporter.Info() << Resource::String::SourceResetListAndOverridePreamble << std::endl; - - context << ListSources; - AICLI_TERMINATE_CONTEXT(E_ABORT); - } - } - } - - void ResetSourceList(Execution::Context& context) - { - const std::vector& sources = context.Get(); - - for (const auto& source : sources) - { - context.Reporter.Info() << Resource::String::SourceResetOne(Utility::LocIndView{ source.Name }); - Repository::Source::DropSource(source.Name); - context.Reporter.Info() << Resource::String::Done << std::endl; - } - } - - void ResetNamedSource(Execution::Context& context) - { - std::string_view name = context.Args.GetArg(Args::Type::SourceName); - - context.Reporter.Info() << Resource::String::SourceResetOne(Utility::LocIndView{ name }) << std::endl; - if (!Repository::Source::DropSource(name)) - { - context.Reporter.Error() << Resource::String::SourceListNoneFound(Utility::LocIndView{ name }) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); - } - - context.Reporter.Info() << Resource::String::Done << std::endl; - } - - void ResetAllSources(Execution::Context& context) - { - context.Reporter.Info() << Resource::String::SourceResetAll; - Repository::Source::DropSource({}); - context.Reporter.Info() << Resource::String::Done << std::endl; - } - - void ExportSourceList(Execution::Context& context) - { - const std::vector& sources = context.Get(); - - if (sources.empty()) - { - context.Reporter.Info() << Resource::String::SourceListNoSources << std::endl; - } - else - { - for (const auto& source : sources) - { - SourceFromPolicy s; - s.Name = source.Name; - s.Type = source.Type; - s.Arg = source.Arg; - s.Data = source.Data; - s.Identifier = source.Identifier; - - std::vector sourceTrustLevels = Repository::SourceTrustLevelFlagToList(source.TrustLevel); - s.TrustLevel = std::vector(sourceTrustLevels.begin(), sourceTrustLevels.end()); - s.Explicit = source.Explicit; - - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) - { - s.Priority = source.Priority; - } - - context.Reporter.Info() << s.ToJsonString() << std::endl; - } - } - } - - void ForceInstalledCacheUpdate(Execution::Context&) - { - // Creating this object is currently sufficient to mark the cache as needing an update for the next time it is opened. - Repository::Source ignore{ Repository::PredefinedSource::InstalledForceCacheUpdate }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Resources.h" +#include "SourceFlow.h" +#include "PromptFlow.h" +#include "TableOutput.h" +#include "WorkflowBase.h" + +namespace AppInstaller::CLI::Workflow +{ + using namespace AppInstaller::CLI::Execution; + using namespace AppInstaller::Settings; + using namespace AppInstaller::Utility::literals; + + void GetSourceList(Execution::Context& context) + { + context.Add(Repository::Source::GetCurrentSources()); + } + + void GetSourceListWithFilter(Execution::Context& context) + { + auto currentSources = Repository::Source::GetCurrentSources(); + if (context.Args.Contains(Args::Type::SourceName)) + { + auto name = Utility::LocIndString{ context.Args.GetArg(Args::Type::SourceName) }; + + for (auto const& source : currentSources) + { + if (Utility::ICUCaseInsensitiveEquals(source.Name, name)) + { + std::vector sources; + sources.emplace_back(source); + context.Add(std::move(sources)); + return; + } + } + + context.Reporter.Error() << Resource::String::SourceListNoneFound(name) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); + } + else + { + context.Add(std::move(currentSources)); + } + } + + void CheckSourceListAgainstAdd(Execution::Context& context) + { + auto sourceList = context.Get(); + std::string_view name = context.Args.GetArg(Args::Type::SourceName); + std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); + std::string_view type = context.Args.GetArg(Args::Type::SourceType); + + // In the absence of a specified type, the default is Microsoft.PreIndexed.Package for comparison. + // The default type assignment to the source takes place during the add operation (Source::Add in Repository.cpp). + // This is necessary for the comparison to function correctly; otherwise, it would allow the addition of multiple + // sources with different names but the same argument for all default type cases. + // For example, the following commands would be allowed, but they acts as different alias to same source: + // winget source add "mysource1" "https:\\mysource" --trust - level trusted + // winget source add "mysource2" "https:\\mysource" --trust - level trusted + if (type.empty()) + { + type = Repository::Source::GetDefaultSourceType(); + } + + for (const auto& details : sourceList) + { + if (Utility::ICUCaseInsensitiveEquals(details.Name, name)) + { + if (details.Arg == arg) + { + // Name and arg match, indicate this to the user and bail. + context.Reporter.Info() << Resource::String::SourceAddAlreadyExistsMatch << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); + } + else + { + context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentArg << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); + } + } + + if (!details.Arg.empty() && details.Arg == arg && details.Type == type) + { + context.Reporter.Error() << Resource::String::SourceAddAlreadyExistsDifferentName << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS); + } + } + } + + void AddSource(Execution::Context& context) + { + auto& sourceToAdd = context.Get(); + auto details = sourceToAdd.GetDetails(); + + context.Reporter.Info() << + Resource::String::SourceAddBegin << std::endl << + " "_liv << details.Name << " -> "_liv << details.Arg << std::endl; + + auto addFunction = [&](IProgressCallback& progress)->bool { return sourceToAdd.Add(progress); }; + if (!context.Reporter.ExecuteWithProgress(addFunction)) + { + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::Done << std::endl; + } + } + + void CreateSourceForSourceAdd(Execution::Context& context) + { + try + { + std::string_view name = context.Args.GetArg(Args::Type::SourceName); + std::string_view arg = context.Args.GetArg(Args::Type::SourceArg); + std::string_view type = context.Args.GetArg(Args::Type::SourceType); + + Repository::SourceTrustLevel trustLevel = Repository::SourceTrustLevel::None; + if (context.Args.Contains(Execution::Args::Type::SourceTrustLevel)) + { + std::vector trustLevelArgs = Utility::Split(std::string{ context.Args.GetArg(Execution::Args::Type::SourceTrustLevel) }, '|', true); + trustLevel = Repository::ConvertToSourceTrustLevelFlag(trustLevelArgs); + } + + Repository::SourceEdit additionalProperties; + + if (context.Args.Contains(Args::Type::SourceExplicit)) + { + additionalProperties.Explicit = true; + } + + if (context.Args.Contains(Args::Type::SourcePriority)) + { + additionalProperties.Priority = Utility::TryConvertStringToInt32(context.Args.GetArg(Args::Type::SourcePriority)); + } + + Repository::Source sourceToAdd{ name, arg, type, trustLevel, additionalProperties}; + + if (context.Args.Contains(Execution::Args::Type::CustomHeader)) + { + std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) }; + if (!sourceToAdd.SetCustomHeader(customHeader)) + { + context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; + } + } + + if (sourceToAdd.GetInformation().Authentication.Type == Authentication::AuthenticationType::Unknown) + { + context.Reporter.Error() << Resource::String::SourceAddFailedAuthenticationNotSupported << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + } + + context << Workflow::HandleSourceAgreements(sourceToAdd); + if (context.IsTerminated()) + { + return; + } + + context.Add(std::move(sourceToAdd)); + } + catch (...) + { + context.Reporter.Error() << Resource::String::SourceAddOpenSourceFailed << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED); + } + } + + void ListSources(Execution::Context& context) + { + const std::vector& sources = context.Get(); + + if (context.Args.Contains(Args::Type::SourceName)) + { + // If a source name was specified, list full details of the one and only source. + const Repository::SourceDetails& source = sources[0]; + + Execution::TableOutput<2> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceListValue }); + + table.OutputLine({ Resource::LocString(Resource::String::SourceListName), source.Name }); + table.OutputLine({ Resource::LocString(Resource::String::SourceListType), source.Type }); + table.OutputLine({ Resource::LocString(Resource::String::SourceListArg), source.Arg }); + table.OutputLine({ Resource::LocString(Resource::String::SourceListData), source.Data }); + table.OutputLine({ Resource::LocString(Resource::String::SourceListIdentifier), source.Identifier }); + table.OutputLine({ Resource::LocString(Resource::String::SourceListTrustLevel), Repository::GetSourceTrustLevelForDisplay(source.TrustLevel)}); + table.OutputLine({ Resource::LocString(Resource::String::SourceListExplicit), std::string{ Utility::ConvertBoolToString(source.Explicit) } }); + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) + { + table.OutputLine({ Resource::LocString(Resource::String::SourceListPriority), std::to_string(source.Priority) }); + } + + if (source.LastUpdateTime == Utility::ConvertUnixEpochToSystemClock(0)) + { + table.OutputLine({ + Resource::LocString(Resource::String::SourceListUpdated), + Resource::LocString(Resource::String::SourceListUpdatedNever) + }); + } + else + { + std::ostringstream strstr; + strstr << source.LastUpdateTime; + table.OutputLine({ Resource::LocString(Resource::String::SourceListUpdated), strstr.str() }); + } + + table.Complete(); + } + else + { + if (sources.empty()) + { + context.Reporter.Info() << Resource::String::SourceListNoSources << std::endl; + } + else + { + Execution::TableOutput<3> table(context.Reporter, { Resource::String::SourceListName, Resource::String::SourceListArg, Resource::String::SourceListExplicit }); + for (const auto& source : sources) + { + table.OutputLine({ source.Name, source.Arg, std::string{ Utility::ConvertBoolToString(source.Explicit) }}); + } + table.Complete(); + } + } + } + + void UpdateSources(Execution::Context& context) + { + if (!context.Args.Contains(Args::Type::SourceName)) + { + context.Reporter.Info() << Resource::String::SourceUpdateAll << std::endl; + } + + const std::vector& sources = context.Get(); + + for (const auto& sd : sources) + { + Repository::Source source{ sd.Name }; + context.Reporter.Info() << Resource::String::SourceUpdateOne(Utility::LocIndView{ sd.Name }) << std::endl; + auto updateFunction = [&](IProgressCallback& progress)->std::vector { return source.Update(progress); }; + auto sourceDetails = context.Reporter.ExecuteWithProgress(updateFunction); + if (!sourceDetails.empty()) + { + if (std::chrono::system_clock::now() < sourceDetails[0].DoNotUpdateBefore) + { + context.Reporter.Warn() << Resource::String::Unavailable << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + } + } + else + { + context.Reporter.Info() << Resource::String::Done << std::endl; + } + } + } + + void RemoveSources(Execution::Context& context) + { + // TODO: We currently only allow removing a single source. If that changes, + // we need to check all sources with the Group Policy before removing any of them. + if (!context.Args.Contains(Args::Type::SourceName)) + { + context.Reporter.Info() << Resource::String::SourceRemoveAll << std::endl; + } + + const std::vector& sources = context.Get(); + for (const auto& sd : sources) + { + Repository::Source source{ sd.Name }; + context.Reporter.Info() << Resource::String::SourceRemoveOne(Utility::LocIndView{ sd.Name }) << std::endl; + auto removeFunction = [&](IProgressCallback& progress)->bool { return source.Remove(progress); }; + if (context.Reporter.ExecuteWithProgress(removeFunction)) + { + context.Reporter.Info() << Resource::String::Done << std::endl; + } + else + { + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + } + } + } + + void EditSources(Execution::Context& context) + { + // We are assuming there is only one match, as SourceName is a required parameter. + const std::vector& sources = context.Get(); + + for (const auto& sd : sources) + { + // Get the current source with this name. + Repository::Source targetSource{ sd.Name }; + auto oldExplicitValue = sd.Explicit; + auto oldPriorityValue = sd.Priority; + + Repository::SourceEdit edits; + + if (context.Args.Contains(Execution::Args::Type::SourceEditExplicit)) + { + edits.Explicit = Utility::TryConvertStringToBool(context.Args.GetArg(Execution::Args::Type::SourceEditExplicit)); + } + + if (context.Args.Contains(Execution::Args::Type::SourcePriority)) + { + edits.Priority = Utility::TryConvertStringToInt32(context.Args.GetArg(Execution::Args::Type::SourcePriority)); + } + + if (!targetSource.RequiresChanges(edits)) + { + context.Reporter.Info() << Resource::String::SourceEditNoChanges(Utility::LocIndView{ sd.Name }) << std::endl; + continue; + } + + context.Reporter.Info() << Resource::String::SourceEditOne(Utility::LocIndView{ sd.Name }) << std::endl; + targetSource.Edit(edits); + + // Output changed source information table. The name of the source being edited is listed prior to the edits. + Execution::TableOutput<3> table(context.Reporter, { Resource::String::SourceListField, Resource::String::SourceEditOldValue, Resource::String::SourceEditNewValue }); + + if (edits.Explicit) + { + table.OutputLine({ Resource::LocString(Resource::String::SourceListExplicit), std::string{ Utility::ConvertBoolToString(oldExplicitValue) }, std::string{ Utility::ConvertBoolToString(edits.Explicit.value()) } }); + } + + if (edits.Priority) + { + table.OutputLine({ Resource::LocString(Resource::String::SourceListPriority), std::to_string(oldPriorityValue), std::to_string(edits.Priority.value()) }); + } + + table.Complete(); + } + } + + void QueryUserForSourceReset(Execution::Context& context) + { + if (!context.Args.Contains(Execution::Args::Type::ForceSourceReset)) + { + context << GetSourceListWithFilter; + const std::vector& sources = context.Get(); + + if (!sources.empty()) + { + context.Reporter.Info() << Resource::String::SourceResetListAndOverridePreamble << std::endl; + + context << ListSources; + AICLI_TERMINATE_CONTEXT(E_ABORT); + } + } + } + + void ResetSourceList(Execution::Context& context) + { + const std::vector& sources = context.Get(); + + for (const auto& source : sources) + { + context.Reporter.Info() << Resource::String::SourceResetOne(Utility::LocIndView{ source.Name }); + Repository::Source::DropSource(source.Name); + context.Reporter.Info() << Resource::String::Done << std::endl; + } + } + + void ResetNamedSource(Execution::Context& context) + { + std::string_view name = context.Args.GetArg(Args::Type::SourceName); + + context.Reporter.Info() << Resource::String::SourceResetOne(Utility::LocIndView{ name }) << std::endl; + if (!Repository::Source::DropSource(name)) + { + context.Reporter.Error() << Resource::String::SourceListNoneFound(Utility::LocIndView{ name }) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); + } + + context.Reporter.Info() << Resource::String::Done << std::endl; + } + + void ResetAllSources(Execution::Context& context) + { + context.Reporter.Info() << Resource::String::SourceResetAll; + Repository::Source::DropSource({}); + context.Reporter.Info() << Resource::String::Done << std::endl; + } + + void ExportSourceList(Execution::Context& context) + { + const std::vector& sources = context.Get(); + + if (sources.empty()) + { + context.Reporter.Info() << Resource::String::SourceListNoSources << std::endl; + } + else + { + for (const auto& source : sources) + { + SourceFromPolicy s; + s.Name = source.Name; + s.Type = source.Type; + s.Arg = source.Arg; + s.Data = source.Data; + s.Identifier = source.Identifier; + + std::vector sourceTrustLevels = Repository::SourceTrustLevelFlagToList(source.TrustLevel); + s.TrustLevel = std::vector(sourceTrustLevels.begin(), sourceTrustLevels.end()); + s.Explicit = source.Explicit; + + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) + { + s.Priority = source.Priority; + } + + context.Reporter.Info() << s.ToJsonString() << std::endl; + } + } + } + + void ForceInstalledCacheUpdate(Execution::Context&) + { + // Creating this object is currently sufficient to mark the cache as needing an update for the next time it is opened. + Repository::Source ignore{ Repository::PredefinedSource::InstalledForceCacheUpdate }; + } +} diff --git a/src/AppInstallerCLICore/Workflows/SourceFlow.h b/src/AppInstallerCLICore/Workflows/SourceFlow.h index 4d771e72d4..5f21571ba9 100644 --- a/src/AppInstallerCLICore/Workflows/SourceFlow.h +++ b/src/AppInstallerCLICore/Workflows/SourceFlow.h @@ -1,98 +1,98 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Command.h" -#include "ExecutionContext.h" - -namespace AppInstaller::CLI::Workflow -{ - // Gets the current source list. - // Required Args: None - // Inputs: None - // Outputs: SourceList - void GetSourceList(Execution::Context& context); - - // Gets the source list, filtering it if SourceName is present. - // Required Args: None - // Inputs: None - // Outputs: SourceList - void GetSourceListWithFilter(Execution::Context& context); - - // Checks the source list against the inputs to ensure a successful add after this. - // Required Args: SourceName, SourceArg - // Inputs: SourceList - // Outputs: None - void CheckSourceListAgainstAdd(Execution::Context& context); - - // Adds the source. - // Required Args: SourceName, SourceArg - // Inputs: None - // Outputs: None - void AddSource(Execution::Context& context); - - // Opens a source before source add command. - // Required Args: SourceName, SourceArg - // Inputs: None - // Outputs: Source - void CreateSourceForSourceAdd(Execution::Context& context); - - // Lists the sources in SourceList. - // Required Args: None - // Inputs: SourceList - // Outputs: None - void ListSources(Execution::Context& context); - - // Updates the sources in SourceList. - // Required Args: None - // Inputs: SourceList - // Outputs: None - void UpdateSources(Execution::Context& context); - - // Removes the sources in SourceList. - // Required Args: None - // Inputs: SourceList - // Outputs: None - void RemoveSources(Execution::Context& context); - - // Asks the user if they are ok with dropping the sources in SourceList. - // Required Args: None - // Inputs: SourceList - // Outputs: None - void QueryUserForSourceReset(Execution::Context& context); - - // Drops the sources in SourceList. - // Required Args: None - // Inputs: SourceList - // Outputs: None - void ResetSourceList(Execution::Context& context); - - // Drops a single source by name, including hidden tombstoned default sources. - // Required Args: SourceName - // Inputs: None - // Outputs: None - void ResetNamedSource(Execution::Context& context); - - // Drops all sources. - // Required Args: None - // Inputs: None - // Outputs: None - void ResetAllSources(Execution::Context& context); - - // Lists the sources in SourceList in a format appropriate for using in Group Policy - // Required Args: None - // Inputs: SourceList - // Outputs: None - void ExportSourceList(Execution::Context& context); - - // Forces an update to the cache of installed packages. - // Required Args: None - // Inputs: None - // Outputs: None - void ForceInstalledCacheUpdate(Execution::Context& context); - - // Edits a source in SourceList. - // Required Args: SourceName - // Inputs: SourceList - // Outputs: None - void EditSources(Execution::Context& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Command.h" +#include "ExecutionContext.h" + +namespace AppInstaller::CLI::Workflow +{ + // Gets the current source list. + // Required Args: None + // Inputs: None + // Outputs: SourceList + void GetSourceList(Execution::Context& context); + + // Gets the source list, filtering it if SourceName is present. + // Required Args: None + // Inputs: None + // Outputs: SourceList + void GetSourceListWithFilter(Execution::Context& context); + + // Checks the source list against the inputs to ensure a successful add after this. + // Required Args: SourceName, SourceArg + // Inputs: SourceList + // Outputs: None + void CheckSourceListAgainstAdd(Execution::Context& context); + + // Adds the source. + // Required Args: SourceName, SourceArg + // Inputs: None + // Outputs: None + void AddSource(Execution::Context& context); + + // Opens a source before source add command. + // Required Args: SourceName, SourceArg + // Inputs: None + // Outputs: Source + void CreateSourceForSourceAdd(Execution::Context& context); + + // Lists the sources in SourceList. + // Required Args: None + // Inputs: SourceList + // Outputs: None + void ListSources(Execution::Context& context); + + // Updates the sources in SourceList. + // Required Args: None + // Inputs: SourceList + // Outputs: None + void UpdateSources(Execution::Context& context); + + // Removes the sources in SourceList. + // Required Args: None + // Inputs: SourceList + // Outputs: None + void RemoveSources(Execution::Context& context); + + // Asks the user if they are ok with dropping the sources in SourceList. + // Required Args: None + // Inputs: SourceList + // Outputs: None + void QueryUserForSourceReset(Execution::Context& context); + + // Drops the sources in SourceList. + // Required Args: None + // Inputs: SourceList + // Outputs: None + void ResetSourceList(Execution::Context& context); + + // Drops a single source by name, including hidden tombstoned default sources. + // Required Args: SourceName + // Inputs: None + // Outputs: None + void ResetNamedSource(Execution::Context& context); + + // Drops all sources. + // Required Args: None + // Inputs: None + // Outputs: None + void ResetAllSources(Execution::Context& context); + + // Lists the sources in SourceList in a format appropriate for using in Group Policy + // Required Args: None + // Inputs: SourceList + // Outputs: None + void ExportSourceList(Execution::Context& context); + + // Forces an update to the cache of installed packages. + // Required Args: None + // Inputs: None + // Outputs: None + void ForceInstalledCacheUpdate(Execution::Context& context); + + // Edits a source in SourceList. + // Required Args: SourceName + // Inputs: SourceList + // Outputs: None + void EditSources(Execution::Context& context); +} diff --git a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp index bd6e105a62..5a93c372d5 100644 --- a/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UninstallFlow.cpp @@ -1,500 +1,500 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "UninstallFlow.h" -#include "InstallFlow.h" -#include "FontFlow.h" -#include "WorkflowBase.h" -#include "DependenciesFlow.h" -#include "ShellExecuteInstallerHandler.h" -#include "AppInstallerMsixInfo.h" -#include "PortableFlow.h" -#include -#include -#include -#include - -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Msix; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Registry; -using namespace AppInstaller::CLI::Portable; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - // Helper for RecordUninstall - struct UninstallCorrelatedSources - { - struct Item - { - Utility::LocIndString Identifier; - Source FromSource; - std::string SourceIdentifier; - }; - - void AddIfRemoteAndNotPresent(Source&& source, const Utility::LocIndString& identifier) - { - const auto details = source.GetDetails(); - if (!source.ContainsAvailablePackages()) - { - return; - } - - for (const auto& item : Items) - { - if (item.SourceIdentifier == details.Identifier) - { - return; - } - } - - Items.emplace_back(Item{ identifier, std::move(source), details.Identifier }); - } - - void AddIfRemoteAndNotPresent(const std::shared_ptr& packageVersion) - { - AddIfRemoteAndNotPresent(packageVersion->GetSource(), packageVersion->GetProperty(PackageVersionProperty::Id)); - } - - void AddIfRemoteAndNotPresent(const std::shared_ptr& package) - { - AddIfRemoteAndNotPresent(package->GetSource(), package->GetProperty(PackageProperty::Id)); - } - - std::vector Items; - }; - } - - void UninstallSinglePackage(Execution::Context& context) - { - std::shared_ptr package = context.Get(); - std::shared_ptr installed = package->GetInstalled(); - std::vector installedVersionKeys; - if (installed) - { - installedVersionKeys = installed->GetVersionKeys(); - } - - // Handle multiple installed versions when we have been told to uninstall all of them. - if (installedVersionKeys.size() > 1 && context.Args.Contains(Execution::Args::Type::AllVersions)) - { - bool allSucceeded = true; - size_t versionsCount = installedVersionKeys.size(); - size_t versionsProgress = 0; - - for (const auto& key : installedVersionKeys) - { - context.Reporter.Info() << '(' << ++versionsProgress << '/' << versionsCount << ") "_liv; - - // We want to do best effort to uninstall all versions regardless of previous failures - auto subContextPtr = context.CreateSubContext(); - Execution::Context& uninstallContext = *subContextPtr; - auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); - - uninstallContext.Add(package); - uninstallContext.Add(installed->GetVersion(key)); - - // Prevent individual exceptions from breaking out of the loop - try - { - uninstallContext << - Workflow::UninstallSinglePackageVersion; - } - catch (...) - { - uninstallContext.SetTerminationHR(Workflow::HandleException(uninstallContext, std::current_exception())); - } - - uninstallContext.Reporter.Info() << std::endl; - - if (uninstallContext.IsTerminated()) - { - if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) - { - // This means that the subcontext being terminated is due to an overall abort - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return; - } - - allSucceeded = false; - } - } - - if (!allSucceeded) - { - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED); - } - } - else if (installedVersionKeys.size() > 1 && !context.Args.Contains(Execution::Args::Type::TargetVersion)) - { - context.Reporter.Error() << Resource::String::UninstallFailedDueToMultipleVersions << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND); - } - else - { - context << - Workflow::GetInstalledPackageVersion << - Workflow::UninstallSinglePackageVersion; - } - } - - void UninstallSinglePackageVersion(Execution::Context& context) - { - context << - Workflow::ReportInstalledPackageVersionIdentity << - Workflow::EnsureSupportForUninstall << - Workflow::GetUninstallInfo << - Workflow::GetDependenciesInfoForUninstall << - Workflow::ReportDependencies(Resource::String::UninstallCommandReportDependencies) << - Workflow::ReportExecutionStage(ExecutionStage::Execution) << - Workflow::ExecuteUninstaller << - Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << - Workflow::RecordUninstall; - } - - void UninstallMultiplePackages(Execution::Context& context) - { - bool allSucceeded = true; - size_t packagesCount = context.Get().size(); - size_t packagesProgress = 0; - - for (auto& packageContext : context.Get()) - { - packagesProgress++; - context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; - - // We want to do best effort to uninstall all packages regardless of previous failures - Execution::Context& uninstallContext = *packageContext; - auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); - - // Prevent individual exceptions from breaking out of the loop - try - { - uninstallContext << - Workflow::ReportPackageIdentity << - Workflow::UninstallSinglePackage; - } - catch (...) - { - uninstallContext.SetTerminationHR(Workflow::HandleException(uninstallContext, std::current_exception())); - } - - uninstallContext.Reporter.Info() << std::endl; - - if (uninstallContext.IsTerminated()) - { - if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) - { - // This means that the subcontext being terminated is due to an overall abort - context.Reporter.Info() << Resource::String::Cancelled << std::endl; - return; - } - - allSucceeded = false; - } - } - - if (!allSucceeded) - { - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED); - } - } - - void GetUninstallInfo(Execution::Context& context) - { - auto installedPackageVersion = context.Get(); - - if (!installedPackageVersion) - { - AICLI_LOG(CLI, Verbose, << "No installed package version; cannot get uninstall information."); - return; - } - - const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; - switch (ConvertToInstallerTypeEnum(installedTypeString)) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Nullsoft: - { - IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); - - // Default to silent unless it is not present or interactivity is requested - auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand); - if (uninstallCommandItr == packageMetadata.end() || context.Args.Contains(Execution::Args::Type::Interactive)) - { - auto interactiveItr = packageMetadata.find(PackageVersionMetadata::StandardUninstallCommand); - if (interactiveItr != packageMetadata.end()) - { - uninstallCommandItr = interactiveItr; - } - } - - if (uninstallCommandItr == packageMetadata.end()) - { - context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); - } - - context.Add(uninstallCommandItr->second); - break; - } - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Wix: - { - // Uninstall strings for MSI don't include UI level (/q) needed to avoid interactivity, - // so we handle them differently. - auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - if (productCodes.empty()) - { - context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); - } - - context.Add(std::move(productCodes)); - break; - } - case InstallerTypeEnum::Msix: - case InstallerTypeEnum::MSStore: - { - auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); - if (packageFamilyNames.empty()) - { - context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); - } - - context.Add(packageFamilyNames); - break; - } - case InstallerTypeEnum::Portable: - { - auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - if (productCodes.empty()) - { - context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); - } - - const std::string installedScope = context.Get()->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]; - const std::string installedArch = context.Get()->GetMetadata()[Repository::PackageVersionMetadata::InstalledArchitecture]; - - PortableInstaller portableInstaller = PortableInstaller( - Manifest::ConvertToScopeEnum(installedScope), - Utility::ConvertToArchitectureEnum(installedArch), - productCodes[0]); - context.Add(std::move(portableInstaller)); - break; - } - case InstallerTypeEnum::Font: - break; - - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - void ExecuteUninstaller(Execution::Context& context) - { - auto installedPackageVersion = context.Get(); - - if (!installedPackageVersion) - { - AICLI_LOG(CLI, Verbose, << "No installed package version; cannot uninstall."); - return; - } - - const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; - InstallerTypeEnum installerType = ConvertToInstallerTypeEnum(installedTypeString); - - Synchronization::CrossProcessInstallLock lock; - if (!ExemptFromSingleInstallLocking(installerType)) - { - // Acquire install lock; if the operation is cancelled it will return false so we will also return. - if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) - { - callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); - return lock.Acquire(callback); - })) - { - AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire install lock due to cancellation"); - return; - } - } - - switch (installerType) - { - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Nullsoft: - context << - Workflow::ShellExecuteUninstallImpl << - ReportUninstallerResult("UninstallString", APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED); - break; - case InstallerTypeEnum::Msi: - case InstallerTypeEnum::Wix: - context << - Workflow::ShellExecuteMsiExecUninstall << - ReportUninstallerResult("MsiExec", APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED); - break; - case InstallerTypeEnum::Msix: - case InstallerTypeEnum::MSStore: - context << Workflow::MsixUninstall; - break; - case InstallerTypeEnum::Portable: - context << - Workflow::PortableUninstallImpl << - ReportUninstallerResult("PortableUninstall"sv, APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED, true); - break; - case InstallerTypeEnum::Font: - context << - Workflow::FontUninstallImpl << - ReportUninstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED, true); - break; - default: - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - void MsixUninstall(Execution::Context& context) - { - bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; - - const auto& packageFamilyNames = context.Get(); - context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; - - for (const auto& packageFamilyName : packageFamilyNames) - { - auto packageFullName = Msix::GetPackageFullNameFromFamilyName(packageFamilyName); - if (!packageFullName.has_value()) - { - AICLI_LOG(CLI, Warning, << "No package found with family name: " << packageFamilyName); - continue; - } - - AICLI_LOG(CLI, Info, << "Removing MSIX package: " << packageFullName.value()); - try - { - if (isMachineScope) - { - context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackageMachineScope, packageFamilyName, packageFullName.value(), std::placeholders::_1)); - } - else - { - context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackage, packageFullName.value(), winrt::Windows::Management::Deployment::RemovalOptions::None, std::placeholders::_1)); - } - } - catch (const wil::ResultException& re) - { - // There was a bug in the deployment deprovision API when called in a packaged context, - // fixed in 10.0.26100.0. On older OS versions, convert any failure under these conditions - // to the error that was previously always returned. - if (isMachineScope && Runtime::IsRunningInPackagedContext() && - !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) - { - context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; - context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); - AICLI_LOG(CLI, Error, << "Device wide uninstall for msix type is not supported in packaged context on this OS version. Error: " << re.GetErrorCode()); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); - } - - context.Add(re.GetErrorCode()); - context << ReportUninstallerResult("MSIXUninstall"sv, re.GetErrorCode(), /* isHResult */ true); - return; - } - } - - context.Reporter.Info() << Resource::String::UninstallFlowUninstallSuccess << std::endl; - } - - void RecordUninstall(Context& context) - { - // In order to report an uninstall to every correlated tracking catalog, we first need to find them all. - auto package = context.Get(); - UninstallCorrelatedSources correlatedSources; - - // Start with the installed version - correlatedSources.AddIfRemoteAndNotPresent(GetInstalledVersion(package)); - - // Then look through all available versions - for (const auto& availablePackage : package->GetAvailable()) - { - correlatedSources.AddIfRemoteAndNotPresent(availablePackage); - } - - // Finally record the uninstall for each found value - for (const auto& item : correlatedSources.Items) - { - auto trackingCatalog = item.FromSource.GetTrackingCatalog(); - trackingCatalog.RecordUninstall(item.Identifier); - } - } - - void ReportUninstallerResult::operator()(Execution::Context& context) const - { - DWORD uninstallResult = context.Get(); - if (uninstallResult != 0) - { - const auto installedPackageVersion = context.Get(); - Logging::Telemetry().LogUninstallerFailure( - installedPackageVersion->GetProperty(PackageVersionProperty::Id), - installedPackageVersion->GetProperty(PackageVersionProperty::Version), - m_uninstallerType, - uninstallResult); - - if (m_isHResult) - { - context.Reporter.Error() - << Resource::String::UninstallFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(uninstallResult) }) - << std::endl; - } - else - { - context.Reporter.Error() - << Resource::String::UninstallFailedWithCode(uninstallResult) - << std::endl; - } - - // Show installer log path if exists - if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get())) - { - auto installerLogPath = Utility::LocIndString{ context.Get().u8string() }; - context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; - } - - AICLI_TERMINATE_CONTEXT(m_hr); - } - else - { - context.Reporter.Info() << Resource::String::UninstallFlowUninstallSuccess << std::endl; - } - } - - void EnsureSupportForUninstall(Execution::Context& context) - { - auto installedPackageVersion = context.Get(); - const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; - auto installedType = ConvertToInstallerTypeEnum(installedTypeString); - if (installedType == InstallerTypeEnum::Portable) - { - const std::string installedScope = installedPackageVersion->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]; - if (Manifest::ConvertToScopeEnum(installedScope) == Manifest::ScopeEnum::Machine) - { - context << EnsureRunningAsAdmin; - } - } - else if (installedType == InstallerTypeEnum::Msix) - { - if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) - { - context << EnsureRunningAsAdmin; - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "UninstallFlow.h" +#include "InstallFlow.h" +#include "FontFlow.h" +#include "WorkflowBase.h" +#include "DependenciesFlow.h" +#include "ShellExecuteInstallerHandler.h" +#include "AppInstallerMsixInfo.h" +#include "PortableFlow.h" +#include +#include +#include +#include + +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Msix; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Registry; +using namespace AppInstaller::CLI::Portable; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + // Helper for RecordUninstall + struct UninstallCorrelatedSources + { + struct Item + { + Utility::LocIndString Identifier; + Source FromSource; + std::string SourceIdentifier; + }; + + void AddIfRemoteAndNotPresent(Source&& source, const Utility::LocIndString& identifier) + { + const auto details = source.GetDetails(); + if (!source.ContainsAvailablePackages()) + { + return; + } + + for (const auto& item : Items) + { + if (item.SourceIdentifier == details.Identifier) + { + return; + } + } + + Items.emplace_back(Item{ identifier, std::move(source), details.Identifier }); + } + + void AddIfRemoteAndNotPresent(const std::shared_ptr& packageVersion) + { + AddIfRemoteAndNotPresent(packageVersion->GetSource(), packageVersion->GetProperty(PackageVersionProperty::Id)); + } + + void AddIfRemoteAndNotPresent(const std::shared_ptr& package) + { + AddIfRemoteAndNotPresent(package->GetSource(), package->GetProperty(PackageProperty::Id)); + } + + std::vector Items; + }; + } + + void UninstallSinglePackage(Execution::Context& context) + { + std::shared_ptr package = context.Get(); + std::shared_ptr installed = package->GetInstalled(); + std::vector installedVersionKeys; + if (installed) + { + installedVersionKeys = installed->GetVersionKeys(); + } + + // Handle multiple installed versions when we have been told to uninstall all of them. + if (installedVersionKeys.size() > 1 && context.Args.Contains(Execution::Args::Type::AllVersions)) + { + bool allSucceeded = true; + size_t versionsCount = installedVersionKeys.size(); + size_t versionsProgress = 0; + + for (const auto& key : installedVersionKeys) + { + context.Reporter.Info() << '(' << ++versionsProgress << '/' << versionsCount << ") "_liv; + + // We want to do best effort to uninstall all versions regardless of previous failures + auto subContextPtr = context.CreateSubContext(); + Execution::Context& uninstallContext = *subContextPtr; + auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); + + uninstallContext.Add(package); + uninstallContext.Add(installed->GetVersion(key)); + + // Prevent individual exceptions from breaking out of the loop + try + { + uninstallContext << + Workflow::UninstallSinglePackageVersion; + } + catch (...) + { + uninstallContext.SetTerminationHR(Workflow::HandleException(uninstallContext, std::current_exception())); + } + + uninstallContext.Reporter.Info() << std::endl; + + if (uninstallContext.IsTerminated()) + { + if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) + { + // This means that the subcontext being terminated is due to an overall abort + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + return; + } + + allSucceeded = false; + } + } + + if (!allSucceeded) + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED); + } + } + else if (installedVersionKeys.size() > 1 && !context.Args.Contains(Execution::Args::Type::TargetVersion)) + { + context.Reporter.Error() << Resource::String::UninstallFailedDueToMultipleVersions << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND); + } + else + { + context << + Workflow::GetInstalledPackageVersion << + Workflow::UninstallSinglePackageVersion; + } + } + + void UninstallSinglePackageVersion(Execution::Context& context) + { + context << + Workflow::ReportInstalledPackageVersionIdentity << + Workflow::EnsureSupportForUninstall << + Workflow::GetUninstallInfo << + Workflow::GetDependenciesInfoForUninstall << + Workflow::ReportDependencies(Resource::String::UninstallCommandReportDependencies) << + Workflow::ReportExecutionStage(ExecutionStage::Execution) << + Workflow::ExecuteUninstaller << + Workflow::ReportExecutionStage(ExecutionStage::PostExecution) << + Workflow::RecordUninstall; + } + + void UninstallMultiplePackages(Execution::Context& context) + { + bool allSucceeded = true; + size_t packagesCount = context.Get().size(); + size_t packagesProgress = 0; + + for (auto& packageContext : context.Get()) + { + packagesProgress++; + context.Reporter.Info() << '(' << packagesProgress << '/' << packagesCount << ") "_liv; + + // We want to do best effort to uninstall all packages regardless of previous failures + Execution::Context& uninstallContext = *packageContext; + auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); + + // Prevent individual exceptions from breaking out of the loop + try + { + uninstallContext << + Workflow::ReportPackageIdentity << + Workflow::UninstallSinglePackage; + } + catch (...) + { + uninstallContext.SetTerminationHR(Workflow::HandleException(uninstallContext, std::current_exception())); + } + + uninstallContext.Reporter.Info() << std::endl; + + if (uninstallContext.IsTerminated()) + { + if (context.IsTerminated() && context.GetTerminationHR() == E_ABORT) + { + // This means that the subcontext being terminated is due to an overall abort + context.Reporter.Info() << Resource::String::Cancelled << std::endl; + return; + } + + allSucceeded = false; + } + } + + if (!allSucceeded) + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED); + } + } + + void GetUninstallInfo(Execution::Context& context) + { + auto installedPackageVersion = context.Get(); + + if (!installedPackageVersion) + { + AICLI_LOG(CLI, Verbose, << "No installed package version; cannot get uninstall information."); + return; + } + + const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; + switch (ConvertToInstallerTypeEnum(installedTypeString)) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + { + IPackageVersion::Metadata packageMetadata = installedPackageVersion->GetMetadata(); + + // Default to silent unless it is not present or interactivity is requested + auto uninstallCommandItr = packageMetadata.find(PackageVersionMetadata::SilentUninstallCommand); + if (uninstallCommandItr == packageMetadata.end() || context.Args.Contains(Execution::Args::Type::Interactive)) + { + auto interactiveItr = packageMetadata.find(PackageVersionMetadata::StandardUninstallCommand); + if (interactiveItr != packageMetadata.end()) + { + uninstallCommandItr = interactiveItr; + } + } + + if (uninstallCommandItr == packageMetadata.end()) + { + context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); + } + + context.Add(uninstallCommandItr->second); + break; + } + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: + { + // Uninstall strings for MSI don't include UI level (/q) needed to avoid interactivity, + // so we handle them differently. + auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + if (productCodes.empty()) + { + context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); + } + + context.Add(std::move(productCodes)); + break; + } + case InstallerTypeEnum::Msix: + case InstallerTypeEnum::MSStore: + { + auto packageFamilyNames = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + if (packageFamilyNames.empty()) + { + context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); + } + + context.Add(packageFamilyNames); + break; + } + case InstallerTypeEnum::Portable: + { + auto productCodes = installedPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + if (productCodes.empty()) + { + context.Reporter.Error() << Resource::String::NoUninstallInfoFound << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND); + } + + const std::string installedScope = context.Get()->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]; + const std::string installedArch = context.Get()->GetMetadata()[Repository::PackageVersionMetadata::InstalledArchitecture]; + + PortableInstaller portableInstaller = PortableInstaller( + Manifest::ConvertToScopeEnum(installedScope), + Utility::ConvertToArchitectureEnum(installedArch), + productCodes[0]); + context.Add(std::move(portableInstaller)); + break; + } + case InstallerTypeEnum::Font: + break; + + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void ExecuteUninstaller(Execution::Context& context) + { + auto installedPackageVersion = context.Get(); + + if (!installedPackageVersion) + { + AICLI_LOG(CLI, Verbose, << "No installed package version; cannot uninstall."); + return; + } + + const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; + InstallerTypeEnum installerType = ConvertToInstallerTypeEnum(installedTypeString); + + Synchronization::CrossProcessInstallLock lock; + if (!ExemptFromSingleInstallLocking(installerType)) + { + // Acquire install lock; if the operation is cancelled it will return false so we will also return. + if (!context.Reporter.ExecuteWithProgress([&](IProgressCallback& callback) + { + callback.SetProgressMessage(Resource::String::InstallWaitingOnAnother()); + return lock.Acquire(callback); + })) + { + AICLI_LOG(CLI, Info, << "Abandoning attempt to acquire install lock due to cancellation"); + return; + } + } + + switch (installerType) + { + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + context << + Workflow::ShellExecuteUninstallImpl << + ReportUninstallerResult("UninstallString", APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED); + break; + case InstallerTypeEnum::Msi: + case InstallerTypeEnum::Wix: + context << + Workflow::ShellExecuteMsiExecUninstall << + ReportUninstallerResult("MsiExec", APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED); + break; + case InstallerTypeEnum::Msix: + case InstallerTypeEnum::MSStore: + context << Workflow::MsixUninstall; + break; + case InstallerTypeEnum::Portable: + context << + Workflow::PortableUninstallImpl << + ReportUninstallerResult("PortableUninstall"sv, APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED, true); + break; + case InstallerTypeEnum::Font: + context << + Workflow::FontUninstallImpl << + ReportUninstallerResult("Font"sv, APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED, true); + break; + default: + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + void MsixUninstall(Execution::Context& context) + { + bool isMachineScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine; + + const auto& packageFamilyNames = context.Get(); + context.Reporter.Info() << Resource::String::UninstallFlowStartingPackageUninstall << std::endl; + + for (const auto& packageFamilyName : packageFamilyNames) + { + auto packageFullName = Msix::GetPackageFullNameFromFamilyName(packageFamilyName); + if (!packageFullName.has_value()) + { + AICLI_LOG(CLI, Warning, << "No package found with family name: " << packageFamilyName); + continue; + } + + AICLI_LOG(CLI, Info, << "Removing MSIX package: " << packageFullName.value()); + try + { + if (isMachineScope) + { + context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackageMachineScope, packageFamilyName, packageFullName.value(), std::placeholders::_1)); + } + else + { + context.Reporter.ExecuteWithProgress(std::bind(Deployment::RemovePackage, packageFullName.value(), winrt::Windows::Management::Deployment::RemovalOptions::None, std::placeholders::_1)); + } + } + catch (const wil::ResultException& re) + { + // There was a bug in the deployment deprovision API when called in a packaged context, + // fixed in 10.0.26100.0. On older OS versions, convert any failure under these conditions + // to the error that was previously always returned. + if (isMachineScope && Runtime::IsRunningInPackagedContext() && + !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) + { + context.Reporter.Error() << Resource::String::InstallFlowReturnCodeSystemNotSupported << std::endl; + context.Add(static_cast(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED)); + AICLI_LOG(CLI, Error, << "Device wide uninstall for msix type is not supported in packaged context on this OS version. Error: " << re.GetErrorCode()); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + + context.Add(re.GetErrorCode()); + context << ReportUninstallerResult("MSIXUninstall"sv, re.GetErrorCode(), /* isHResult */ true); + return; + } + } + + context.Reporter.Info() << Resource::String::UninstallFlowUninstallSuccess << std::endl; + } + + void RecordUninstall(Context& context) + { + // In order to report an uninstall to every correlated tracking catalog, we first need to find them all. + auto package = context.Get(); + UninstallCorrelatedSources correlatedSources; + + // Start with the installed version + correlatedSources.AddIfRemoteAndNotPresent(GetInstalledVersion(package)); + + // Then look through all available versions + for (const auto& availablePackage : package->GetAvailable()) + { + correlatedSources.AddIfRemoteAndNotPresent(availablePackage); + } + + // Finally record the uninstall for each found value + for (const auto& item : correlatedSources.Items) + { + auto trackingCatalog = item.FromSource.GetTrackingCatalog(); + trackingCatalog.RecordUninstall(item.Identifier); + } + } + + void ReportUninstallerResult::operator()(Execution::Context& context) const + { + DWORD uninstallResult = context.Get(); + if (uninstallResult != 0) + { + const auto installedPackageVersion = context.Get(); + Logging::Telemetry().LogUninstallerFailure( + installedPackageVersion->GetProperty(PackageVersionProperty::Id), + installedPackageVersion->GetProperty(PackageVersionProperty::Version), + m_uninstallerType, + uninstallResult); + + if (m_isHResult) + { + context.Reporter.Error() + << Resource::String::UninstallFailedWithCode(Utility::LocIndView{ GetUserPresentableMessage(uninstallResult) }) + << std::endl; + } + else + { + context.Reporter.Error() + << Resource::String::UninstallFailedWithCode(uninstallResult) + << std::endl; + } + + // Show installer log path if exists + if (context.Contains(Execution::Data::LogPath) && std::filesystem::exists(context.Get())) + { + auto installerLogPath = Utility::LocIndString{ context.Get().u8string() }; + context.Reporter.Info() << Resource::String::InstallerLogAvailable(installerLogPath) << std::endl; + } + + AICLI_TERMINATE_CONTEXT(m_hr); + } + else + { + context.Reporter.Info() << Resource::String::UninstallFlowUninstallSuccess << std::endl; + } + } + + void EnsureSupportForUninstall(Execution::Context& context) + { + auto installedPackageVersion = context.Get(); + const std::string installedTypeString = installedPackageVersion->GetMetadata()[PackageVersionMetadata::InstalledType]; + auto installedType = ConvertToInstallerTypeEnum(installedTypeString); + if (installedType == InstallerTypeEnum::Portable) + { + const std::string installedScope = installedPackageVersion->GetMetadata()[Repository::PackageVersionMetadata::InstalledScope]; + if (Manifest::ConvertToScopeEnum(installedScope) == Manifest::ScopeEnum::Machine) + { + context << EnsureRunningAsAdmin; + } + } + else if (installedType == InstallerTypeEnum::Msix) + { + if (Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)) == Manifest::ScopeEnum::Machine) + { + context << EnsureRunningAsAdmin; + } + } + } +} diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp index fbdeddfb76..2e8f35100d 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.cpp @@ -1,378 +1,378 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowBase.h" -#include "DependenciesFlow.h" -#include "InstallFlow.h" -#include "UpdateFlow.h" -#include -#include -#include - -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::Pinning; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - bool IsUpdateVersionAvailable(const Utility::Version& installedVersion, const Utility::Version& updateVersion) - { - return installedVersion < updateVersion; - } - - void AddToPackageSubContextsIfNotPresent(std::vector>& packageSubContexts, std::unique_ptr packageContext) - { - for (auto const& existing : packageSubContexts) - { - if (existing->Get().Id == packageContext->Get().Id && - existing->Get().Version == packageContext->Get().Version && - existing->Get()->GetProperty(PackageVersionProperty::SourceIdentifier) == packageContext->Get()->GetProperty(PackageVersionProperty::SourceIdentifier)) - { - return; - } - } - - packageSubContexts.emplace_back(std::move(packageContext)); - } - } - - void SelectLatestApplicableVersion::operator()(Execution::Context& context) const - { - auto package = context.Get(); - auto installedPackage = context.Get(); - const bool reportVersionNotFound = m_isSinglePackage; - - bool isUpgrade = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);; - Utility::Version installedVersion; - if (isUpgrade) - { - installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); - } - - Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, isUpgrade ? installedPackage->GetMetadata() : IPackageVersion::Metadata{})); - bool versionFound = false; - bool installedTypeInapplicable = false; - bool packagePinned = false; - - if (isUpgrade && installedVersion.IsUnknown() && !context.Args.Contains(Execution::Args::Type::IncludeUnknown)) - { - // the package has an unknown version and the user did not request to upgrade it anyway - if (reportVersionNotFound) - { - context.Reporter.Info() << Resource::String::UpgradeUnknownVersionExplanation << std::endl; - } - - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); - } - - // If we are updating a single package or we got the --include-pinned flag, - // we include packages with Pinning pins - const bool includePinned = m_isSinglePackage || context.Args.Contains(Execution::Args::Type::IncludePinned); - - PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(includePinned ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins, GetInstalledVersion(package)); - - // The version keys should have already been sorted by version - auto availableVersions = GetAvailableVersionsForInstalledVersion(package); - const auto& versionKeys = availableVersions->GetVersionKeys(); - // Assume that no update versions are applicable - bool upgradeVersionAvailable = false; - for (const auto& key : versionKeys) - { - // Check Applicable Version - if (!isUpgrade || IsUpdateVersionAvailable(installedVersion, Utility::Version(key.Version))) - { - // The only way to enter this portion of the statement with isUpgrade is if the version is available - if (isUpgrade) - { - AICLI_LOG(CLI, Verbose, << "Updating from [" << installedVersion.ToString() << "] to [" << key.Version << "]"); - upgradeVersionAvailable = true; - } - - auto packageVersion = availableVersions->GetVersion(key); - - // Check if the package is pinned - PinType pinType = evaluator.EvaluatePinType(packageVersion); - if (pinType != Pinning::PinType::Unknown) - { - AICLI_LOG(CLI, Info, << "Package [" << package->GetProperty(PackageProperty::Id) << " with Version[" << key.Version << "] from Source[" << key.SourceId << "] has a Pin with type[" << ToString(pinType) << "]"); - if (context.Args.Contains(Execution::Args::Type::Force)) - { - AICLI_LOG(CLI, Info, << "Ignoring pin due to --force argument"); - } - else - { - packagePinned = true; - continue; - } - } - - auto manifest = packageVersion->GetManifest(); - - // Check applicable Installer - auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(manifest); - if (!installer.has_value()) - { - // If there is at least one installer whose only reason is InstalledType. - auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType); - if (onlyInstalledType != inapplicabilities.end()) - { - installedTypeInapplicable = true; - } - - continue; - } - - Logging::Telemetry().LogSelectedInstaller( - static_cast(installer->Arch), - installer->Url, - Manifest::InstallerTypeToString(installer->EffectiveInstallerType()), - Manifest::ScopeToString(installer->Scope), - installer->Locale); - - Logging::Telemetry().LogManifestFields( - manifest.Id, - manifest.DefaultLocalization.Get(), - manifest.Version); - - // Since we already did installer selection, just populate the context Data - manifest.ApplyLocale(installer->Locale); - context.Add(std::move(manifest)); - context.Add(std::move(packageVersion)); - context.Add(std::move(installer)); - - versionFound = true; - break; - } - else - { - // Any following versions are not applicable - break; - } - } - - if (!versionFound) - { - if (reportVersionNotFound) - { - if (installedTypeInapplicable) - { - context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions << std::endl; - } - else if (packagePinned) - { - context.Reporter.Info() << Resource::String::UpgradeIsPinned << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); - } - else if (isUpgrade) - { - if (!upgradeVersionAvailable) - { - // This is the case when no newer versions are available in a configured source - context.Reporter.Info() << Resource::String::UpdateNoPackagesFound << std::endl - << Resource::String::UpdateNoPackagesFoundReason << std::endl; - } - else - { - // This is the case when newer versions are available in a configured source, but none are applicable due to OS Version, user requirement, etc. - context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl - << Resource::String::UpdateNotApplicableReason << std::endl; - } - } - else - { - context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; - } - } - if (isUpgrade && installedTypeInapplicable) - { - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH); - } - - AICLI_TERMINATE_CONTEXT(isUpgrade ? APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE : APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); - } - } - - void EnsureUpdateVersionApplicable(Execution::Context& context) - { - auto installedPackage = context.Get(); - Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); - Utility::Version updateVersion(context.Get().Version); - - if (!IsUpdateVersionAvailable(installedVersion, updateVersion)) - { - context.Reporter.Info() << Resource::String::UpdateNoPackagesFound << std::endl - << Resource::String::UpdateNoPackagesFoundReason << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); - } - } - - void UpdateAllApplicable(Execution::Context& context) - { - const auto& matches = context.Get().Matches; - std::vector> packageSubContexts; - bool updateAllFoundUpdate = false; - int packagesWithUnknownVersionSkipped = 0; - int packagesThatRequireExplicitSkipped = 0; - int packagesSkippedInstallTechnologyMismatch = 0; - - for (const auto& match : matches) - { - // We want to do best effort to update all applicable updates regardless on previous update failure - auto updateContextPtr = context.CreateSubContext(); - Execution::Context& updateContext = *updateContextPtr; - auto previousThreadGlobals = updateContext.SetForCurrentThread(); - auto installedVersion = GetInstalledVersion(match.Package); - - updateContext.Add(match.Package); - - // Filter out packages with unknown installed versions - if (Utility::Version(installedVersion->GetProperty(PackageVersionProperty::Version)).IsUnknown() && - !context.Args.Contains(Execution::Args::Type::IncludeUnknown)) - { - // we don't know what the package's version is and the user didn't ask to upgrade it anyway. - AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) << " as it has unknown installed version"); - ++packagesWithUnknownVersionSkipped; - continue; - } - - updateContext << - Workflow::GetInstalledPackageVersion << - Workflow::ReportExecutionStage(ExecutionStage::Discovery) << - SelectLatestApplicableVersion(false); - - if (updateContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH) - { - AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) - << " as available upgrades use a different install technology"); - ++packagesSkippedInstallTechnologyMismatch; - continue; - } - - if (updateContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) - { - continue; - } - - // Filter out packages that require explicit upgrade. - // User-defined pins are handled when selecting the version to use. - auto installedMetadata = updateContext.Get()->GetMetadata(); - auto pinnedState = ConvertToPinTypeEnum(installedMetadata[PackageVersionMetadata::PinnedState]); - if (pinnedState == PinType::PinnedByManifest) - { - // Note that for packages pinned by the manifest - // this does not consider whether the update to be installed has - // RequireExplicitUpgrade. While this has the downside of not working with - // packages installed from another source, it ensures consistency with the - // list of available updates (there we don't have the selected installer) - // and at most we will update each package like this once. - AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) << " as it requires explicit upgrade"); - ++packagesThatRequireExplicitSkipped; - continue; - } - - updateAllFoundUpdate = true; - - AddToPackageSubContextsIfNotPresent(packageSubContexts, std::move(updateContextPtr)); - } - - if (updateAllFoundUpdate) - { - context.Add(std::move(packageSubContexts)); - context.Reporter.Info() << std::endl; - - ProcessMultiplePackages::Flags flags = ProcessMultiplePackages::Flags::None; - if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) - { - flags = ProcessMultiplePackages::Flags::IgnoreDependencies; - } - - context << - ProcessMultiplePackages( - Resource::String::PackageRequiresDependencies, - APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE, - flags, - { APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE }); - } - - if (packagesWithUnknownVersionSkipped > 0) - { - AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version"); - context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl; - } - - if (packagesThatRequireExplicitSkipped > 0) - { - AICLI_LOG(CLI, Info, << packagesThatRequireExplicitSkipped << " package(s) skipped due to requiring explicit upgrade"); - context.Reporter.Info() << Resource::String::UpgradeRequireExplicitCount(packagesThatRequireExplicitSkipped) << std::endl; - } - - if (packagesSkippedInstallTechnologyMismatch > 0) - { - AICLI_LOG(CLI, Info, << packagesSkippedInstallTechnologyMismatch << " package(s) skipped due to install technology mismatch"); - context.Reporter.Info() << Resource::String::UpgradeInstallTechnologyMismatchCount(packagesSkippedInstallTechnologyMismatch) << std::endl; - } - } - - void SelectSinglePackageVersionForInstallOrUpgrade::operator()(Execution::Context& context) const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_operationType != OperationType::Install && m_operationType != OperationType::Upgrade); - - context << - HandleSearchResultFailures << - EnsureOneMatchFromSearchResult(m_operationType) << - GetInstalledPackageVersion; - - if ( m_operationType != OperationType::Upgrade && - context.Contains(Execution::Data::InstalledPackageVersion) && - context.Get() != nullptr ) - { - if (context.Args.Contains(Execution::Args::Type::NoUpgrade)) - { - AICLI_LOG(CLI, Warning, << "Found installed package, exiting installation."); - context.Reporter.Warn() << Resource::String::PackageAlreadyInstalled << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED); - } - else - { - AICLI_LOG(CLI, Info, << "Found installed package, converting to upgrade flow"); - context.Reporter.Info() << Execution::ConvertToUpgradeFlowEmphasis << Resource::String::ConvertInstallFlowToUpgrade << std::endl; - context.SetFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); - m_operationType = OperationType::Upgrade; - } - } - - if (context.Args.Contains(Execution::Args::Type::Version)) - { - // If version specified, use the version and verify applicability - context << GetManifestFromPackage(/* considerPins */ true); - - if (m_operationType == OperationType::Upgrade && !m_allowDowngrade) - { - context << EnsureUpdateVersionApplicable; - } - - context << SelectInstaller; - } - else - { - // Iterate through available versions to find latest applicable version. - // This step also populates Manifest and Installer in context data. - context << SelectLatestApplicableVersion(true); - } - - context << EnsureApplicableInstaller; - } - - void InstallOrUpgradeSinglePackage::operator()(Execution::Context& context) const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_operationType != OperationType::Install && m_operationType != OperationType::Upgrade); - - context << - SearchSourceForSingle << - SelectSinglePackageVersionForInstallOrUpgrade(m_operationType) << - InstallSinglePackage; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowBase.h" +#include "DependenciesFlow.h" +#include "InstallFlow.h" +#include "UpdateFlow.h" +#include +#include +#include + +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Pinning; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + bool IsUpdateVersionAvailable(const Utility::Version& installedVersion, const Utility::Version& updateVersion) + { + return installedVersion < updateVersion; + } + + void AddToPackageSubContextsIfNotPresent(std::vector>& packageSubContexts, std::unique_ptr packageContext) + { + for (auto const& existing : packageSubContexts) + { + if (existing->Get().Id == packageContext->Get().Id && + existing->Get().Version == packageContext->Get().Version && + existing->Get()->GetProperty(PackageVersionProperty::SourceIdentifier) == packageContext->Get()->GetProperty(PackageVersionProperty::SourceIdentifier)) + { + return; + } + } + + packageSubContexts.emplace_back(std::move(packageContext)); + } + } + + void SelectLatestApplicableVersion::operator()(Execution::Context& context) const + { + auto package = context.Get(); + auto installedPackage = context.Get(); + const bool reportVersionNotFound = m_isSinglePackage; + + bool isUpgrade = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate);; + Utility::Version installedVersion; + if (isUpgrade) + { + installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); + } + + Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, isUpgrade ? installedPackage->GetMetadata() : IPackageVersion::Metadata{})); + bool versionFound = false; + bool installedTypeInapplicable = false; + bool packagePinned = false; + + if (isUpgrade && installedVersion.IsUnknown() && !context.Args.Contains(Execution::Args::Type::IncludeUnknown)) + { + // the package has an unknown version and the user did not request to upgrade it anyway + if (reportVersionNotFound) + { + context.Reporter.Info() << Resource::String::UpgradeUnknownVersionExplanation << std::endl; + } + + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); + } + + // If we are updating a single package or we got the --include-pinned flag, + // we include packages with Pinning pins + const bool includePinned = m_isSinglePackage || context.Args.Contains(Execution::Args::Type::IncludePinned); + + PinningData pinningData{ PinningData::Disposition::ReadOnly }; + auto evaluator = pinningData.CreatePinStateEvaluator(includePinned ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins, GetInstalledVersion(package)); + + // The version keys should have already been sorted by version + auto availableVersions = GetAvailableVersionsForInstalledVersion(package); + const auto& versionKeys = availableVersions->GetVersionKeys(); + // Assume that no update versions are applicable + bool upgradeVersionAvailable = false; + for (const auto& key : versionKeys) + { + // Check Applicable Version + if (!isUpgrade || IsUpdateVersionAvailable(installedVersion, Utility::Version(key.Version))) + { + // The only way to enter this portion of the statement with isUpgrade is if the version is available + if (isUpgrade) + { + AICLI_LOG(CLI, Verbose, << "Updating from [" << installedVersion.ToString() << "] to [" << key.Version << "]"); + upgradeVersionAvailable = true; + } + + auto packageVersion = availableVersions->GetVersion(key); + + // Check if the package is pinned + PinType pinType = evaluator.EvaluatePinType(packageVersion); + if (pinType != Pinning::PinType::Unknown) + { + AICLI_LOG(CLI, Info, << "Package [" << package->GetProperty(PackageProperty::Id) << " with Version[" << key.Version << "] from Source[" << key.SourceId << "] has a Pin with type[" << ToString(pinType) << "]"); + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Ignoring pin due to --force argument"); + } + else + { + packagePinned = true; + continue; + } + } + + auto manifest = packageVersion->GetManifest(); + + // Check applicable Installer + auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(manifest); + if (!installer.has_value()) + { + // If there is at least one installer whose only reason is InstalledType. + auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType); + if (onlyInstalledType != inapplicabilities.end()) + { + installedTypeInapplicable = true; + } + + continue; + } + + Logging::Telemetry().LogSelectedInstaller( + static_cast(installer->Arch), + installer->Url, + Manifest::InstallerTypeToString(installer->EffectiveInstallerType()), + Manifest::ScopeToString(installer->Scope), + installer->Locale); + + Logging::Telemetry().LogManifestFields( + manifest.Id, + manifest.DefaultLocalization.Get(), + manifest.Version); + + // Since we already did installer selection, just populate the context Data + manifest.ApplyLocale(installer->Locale); + context.Add(std::move(manifest)); + context.Add(std::move(packageVersion)); + context.Add(std::move(installer)); + + versionFound = true; + break; + } + else + { + // Any following versions are not applicable + break; + } + } + + if (!versionFound) + { + if (reportVersionNotFound) + { + if (installedTypeInapplicable) + { + context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions << std::endl; + } + else if (packagePinned) + { + context.Reporter.Info() << Resource::String::UpgradeIsPinned << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); + } + else if (isUpgrade) + { + if (!upgradeVersionAvailable) + { + // This is the case when no newer versions are available in a configured source + context.Reporter.Info() << Resource::String::UpdateNoPackagesFound << std::endl + << Resource::String::UpdateNoPackagesFoundReason << std::endl; + } + else + { + // This is the case when newer versions are available in a configured source, but none are applicable due to OS Version, user requirement, etc. + context.Reporter.Info() << Resource::String::UpdateNotApplicable << std::endl + << Resource::String::UpdateNotApplicableReason << std::endl; + } + } + else + { + context.Reporter.Error() << Resource::String::NoApplicableInstallers << std::endl; + } + } + if (isUpgrade && installedTypeInapplicable) + { + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH); + } + + AICLI_TERMINATE_CONTEXT(isUpgrade ? APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE : APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); + } + } + + void EnsureUpdateVersionApplicable(Execution::Context& context) + { + auto installedPackage = context.Get(); + Utility::Version installedVersion = Utility::Version(installedPackage->GetProperty(PackageVersionProperty::Version)); + Utility::Version updateVersion(context.Get().Version); + + if (!IsUpdateVersionAvailable(installedVersion, updateVersion)) + { + context.Reporter.Info() << Resource::String::UpdateNoPackagesFound << std::endl + << Resource::String::UpdateNoPackagesFoundReason << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); + } + } + + void UpdateAllApplicable(Execution::Context& context) + { + const auto& matches = context.Get().Matches; + std::vector> packageSubContexts; + bool updateAllFoundUpdate = false; + int packagesWithUnknownVersionSkipped = 0; + int packagesThatRequireExplicitSkipped = 0; + int packagesSkippedInstallTechnologyMismatch = 0; + + for (const auto& match : matches) + { + // We want to do best effort to update all applicable updates regardless on previous update failure + auto updateContextPtr = context.CreateSubContext(); + Execution::Context& updateContext = *updateContextPtr; + auto previousThreadGlobals = updateContext.SetForCurrentThread(); + auto installedVersion = GetInstalledVersion(match.Package); + + updateContext.Add(match.Package); + + // Filter out packages with unknown installed versions + if (Utility::Version(installedVersion->GetProperty(PackageVersionProperty::Version)).IsUnknown() && + !context.Args.Contains(Execution::Args::Type::IncludeUnknown)) + { + // we don't know what the package's version is and the user didn't ask to upgrade it anyway. + AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) << " as it has unknown installed version"); + ++packagesWithUnknownVersionSkipped; + continue; + } + + updateContext << + Workflow::GetInstalledPackageVersion << + Workflow::ReportExecutionStage(ExecutionStage::Discovery) << + SelectLatestApplicableVersion(false); + + if (updateContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH) + { + AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) + << " as available upgrades use a different install technology"); + ++packagesSkippedInstallTechnologyMismatch; + continue; + } + + if (updateContext.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE) + { + continue; + } + + // Filter out packages that require explicit upgrade. + // User-defined pins are handled when selecting the version to use. + auto installedMetadata = updateContext.Get()->GetMetadata(); + auto pinnedState = ConvertToPinTypeEnum(installedMetadata[PackageVersionMetadata::PinnedState]); + if (pinnedState == PinType::PinnedByManifest) + { + // Note that for packages pinned by the manifest + // this does not consider whether the update to be installed has + // RequireExplicitUpgrade. While this has the downside of not working with + // packages installed from another source, it ensures consistency with the + // list of available updates (there we don't have the selected installer) + // and at most we will update each package like this once. + AICLI_LOG(CLI, Info, << "Skipping " << match.Package->GetProperty(PackageProperty::Id) << " as it requires explicit upgrade"); + ++packagesThatRequireExplicitSkipped; + continue; + } + + updateAllFoundUpdate = true; + + AddToPackageSubContextsIfNotPresent(packageSubContexts, std::move(updateContextPtr)); + } + + if (updateAllFoundUpdate) + { + context.Add(std::move(packageSubContexts)); + context.Reporter.Info() << std::endl; + + ProcessMultiplePackages::Flags flags = ProcessMultiplePackages::Flags::None; + if (Settings::User().Get() || context.Args.Contains(Execution::Args::Type::SkipDependencies)) + { + flags = ProcessMultiplePackages::Flags::IgnoreDependencies; + } + + context << + ProcessMultiplePackages( + Resource::String::PackageRequiresDependencies, + APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE, + flags, + { APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE }); + } + + if (packagesWithUnknownVersionSkipped > 0) + { + AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version"); + context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl; + } + + if (packagesThatRequireExplicitSkipped > 0) + { + AICLI_LOG(CLI, Info, << packagesThatRequireExplicitSkipped << " package(s) skipped due to requiring explicit upgrade"); + context.Reporter.Info() << Resource::String::UpgradeRequireExplicitCount(packagesThatRequireExplicitSkipped) << std::endl; + } + + if (packagesSkippedInstallTechnologyMismatch > 0) + { + AICLI_LOG(CLI, Info, << packagesSkippedInstallTechnologyMismatch << " package(s) skipped due to install technology mismatch"); + context.Reporter.Info() << Resource::String::UpgradeInstallTechnologyMismatchCount(packagesSkippedInstallTechnologyMismatch) << std::endl; + } + } + + void SelectSinglePackageVersionForInstallOrUpgrade::operator()(Execution::Context& context) const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_operationType != OperationType::Install && m_operationType != OperationType::Upgrade); + + context << + HandleSearchResultFailures << + EnsureOneMatchFromSearchResult(m_operationType) << + GetInstalledPackageVersion; + + if ( m_operationType != OperationType::Upgrade && + context.Contains(Execution::Data::InstalledPackageVersion) && + context.Get() != nullptr ) + { + if (context.Args.Contains(Execution::Args::Type::NoUpgrade)) + { + AICLI_LOG(CLI, Warning, << "Found installed package, exiting installation."); + context.Reporter.Warn() << Resource::String::PackageAlreadyInstalled << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED); + } + else + { + AICLI_LOG(CLI, Info, << "Found installed package, converting to upgrade flow"); + context.Reporter.Info() << Execution::ConvertToUpgradeFlowEmphasis << Resource::String::ConvertInstallFlowToUpgrade << std::endl; + context.SetFlags(Execution::ContextFlag::InstallerExecutionUseUpdate); + m_operationType = OperationType::Upgrade; + } + } + + if (context.Args.Contains(Execution::Args::Type::Version)) + { + // If version specified, use the version and verify applicability + context << GetManifestFromPackage(/* considerPins */ true); + + if (m_operationType == OperationType::Upgrade && !m_allowDowngrade) + { + context << EnsureUpdateVersionApplicable; + } + + context << SelectInstaller; + } + else + { + // Iterate through available versions to find latest applicable version. + // This step also populates Manifest and Installer in context data. + context << SelectLatestApplicableVersion(true); + } + + context << EnsureApplicableInstaller; + } + + void InstallOrUpgradeSinglePackage::operator()(Execution::Context& context) const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_operationType != OperationType::Install && m_operationType != OperationType::Upgrade); + + context << + SearchSourceForSingle << + SelectSinglePackageVersionForInstallOrUpgrade(m_operationType) << + InstallSinglePackage; + } +} diff --git a/src/AppInstallerCLICore/Workflows/UpdateFlow.h b/src/AppInstallerCLICore/Workflows/UpdateFlow.h index 28a294f954..cb84b3452d 100644 --- a/src/AppInstallerCLICore/Workflows/UpdateFlow.h +++ b/src/AppInstallerCLICore/Workflows/UpdateFlow.h @@ -1,66 +1,66 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionContext.h" -#include "WorkflowBase.h" - -namespace AppInstaller::CLI::Workflow -{ - // Iterates through all available versions from a package and find latest applicable version - // Required Args: bool indicating whether to report update not found - // Inputs: InstalledPackageVersion?, Package - // Outputs: Manifest?, Installer? - struct SelectLatestApplicableVersion : public WorkflowTask - { - SelectLatestApplicableVersion(bool isSinglePackage) : - WorkflowTask("SelectLatestApplicableUpdate"), m_isSinglePackage(isSinglePackage) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_isSinglePackage; - }; - - // Ensures the update package has higher version than installed - // Required Args: None - // Inputs: Manifest, InstalledPackageVersion - // Outputs: None - void EnsureUpdateVersionApplicable(Execution::Context& context); - - // Update all packages from SearchResult to latest if applicable - // Required Args: None - // Inputs: SearchResult - // Outputs: None - void UpdateAllApplicable(Execution::Context& context); - - // Select single package version for install or upgrade - // Required Args: bool indicating whether the flow is for upgrade - // Inputs: Source, SearchResult - // Outputs: None - struct SelectSinglePackageVersionForInstallOrUpgrade : public WorkflowTask - { - SelectSinglePackageVersionForInstallOrUpgrade(OperationType operation, bool allowDowngrade = false) : - WorkflowTask("SelectSinglePackageVersionForInstallOrUpgrade"), m_operationType(operation), m_allowDowngrade(allowDowngrade) {} - - void operator()(Execution::Context& context) const override; - - private: - mutable OperationType m_operationType; - bool m_allowDowngrade; - }; - - // Install or upgrade a single package - // Required Args: bool indicating whether the flow is for upgrade - // Inputs: Source - // Outputs: None - struct InstallOrUpgradeSinglePackage : public WorkflowTask - { - InstallOrUpgradeSinglePackage(OperationType operation) : - WorkflowTask("InstallOrUpgradeSinglePackage"), m_operationType(operation) {} - - void operator()(Execution::Context& context) const override; - - private: - mutable OperationType m_operationType; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionContext.h" +#include "WorkflowBase.h" + +namespace AppInstaller::CLI::Workflow +{ + // Iterates through all available versions from a package and find latest applicable version + // Required Args: bool indicating whether to report update not found + // Inputs: InstalledPackageVersion?, Package + // Outputs: Manifest?, Installer? + struct SelectLatestApplicableVersion : public WorkflowTask + { + SelectLatestApplicableVersion(bool isSinglePackage) : + WorkflowTask("SelectLatestApplicableUpdate"), m_isSinglePackage(isSinglePackage) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_isSinglePackage; + }; + + // Ensures the update package has higher version than installed + // Required Args: None + // Inputs: Manifest, InstalledPackageVersion + // Outputs: None + void EnsureUpdateVersionApplicable(Execution::Context& context); + + // Update all packages from SearchResult to latest if applicable + // Required Args: None + // Inputs: SearchResult + // Outputs: None + void UpdateAllApplicable(Execution::Context& context); + + // Select single package version for install or upgrade + // Required Args: bool indicating whether the flow is for upgrade + // Inputs: Source, SearchResult + // Outputs: None + struct SelectSinglePackageVersionForInstallOrUpgrade : public WorkflowTask + { + SelectSinglePackageVersionForInstallOrUpgrade(OperationType operation, bool allowDowngrade = false) : + WorkflowTask("SelectSinglePackageVersionForInstallOrUpgrade"), m_operationType(operation), m_allowDowngrade(allowDowngrade) {} + + void operator()(Execution::Context& context) const override; + + private: + mutable OperationType m_operationType; + bool m_allowDowngrade; + }; + + // Install or upgrade a single package + // Required Args: bool indicating whether the flow is for upgrade + // Inputs: Source + // Outputs: None + struct InstallOrUpgradeSinglePackage : public WorkflowTask + { + InstallOrUpgradeSinglePackage(OperationType operation) : + WorkflowTask("InstallOrUpgradeSinglePackage"), m_operationType(operation) {} + + void operator()(Execution::Context& context) const override; + + private: + mutable OperationType m_operationType; + }; +} diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp index dc60a6da58..4641d99359 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.cpp @@ -1,1856 +1,1856 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowBase.h" -#include "ExecutionContext.h" -#include "PackageTableSortHelper.h" -#include "PromptFlow.h" -#include "ShowFlow.h" -#include "Sixel.h" -#include "TableOutput.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -EXTERN_C IMAGE_DOS_HEADER __ImageBase; - -using namespace std::string_literals; -using namespace AppInstaller::Utility::literals; -using namespace AppInstaller::Pinning; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Settings; -using namespace winrt::Windows::Foundation; - -namespace AppInstaller::CLI::Workflow -{ - namespace - { - std::string GetMatchCriteriaDescriptor(const ResultMatch& match) - { - if (match.MatchCriteria.Field != PackageMatchField::Id && match.MatchCriteria.Field != PackageMatchField::Name) - { - std::string result{ ToString(match.MatchCriteria.Field) }; - result += ": "; - result += match.MatchCriteria.Value; - return result; - } - else - { - return {}; - } - } - - void ReportIdentity( - Execution::Context& context, - Utility::LocIndView prefix, - std::optional label, - std::string_view name, - std::string_view id, - std::string_view version = {}, - Execution::Reporter::Level level = Execution::Reporter::Level::Info) - { - auto out = context.Reporter.GetOutputStream(level); - out << prefix; - if (label) - { - out << *label << ' '; - } - out << Execution::NameEmphasis << name << " ["_liv << Execution::IdEmphasis << id << ']'; - - if (!version.empty()) - { - out << ' ' << Resource::String::ShowVersion << ' ' << version; - } - - out << std::endl; - } - - bool IsSecondIconResolutionBetter(Manifest::IconResolutionEnum current, Manifest::IconResolutionEnum alternative) - { - static constexpr std::array s_iconResolutionOrder - { - 9, // Unknown - 8, // Custom - 15, // Square16 - 14, // Square20 - 13, // Square24 - 12, // Square30 - 11, // Square32 - 10, // Square36 - 6, // Square40 - 5, // Square48 - 4, // Square60 - 3, // Square64 - 2, // Square72 - 0, // Square80 - 1, // Square96 - 7, // Square256 - }; - - return s_iconResolutionOrder.at(ToIntegral(alternative)) < s_iconResolutionOrder.at(ToIntegral(current)); - } - - // Determines icon fit given two options. - // Targets an 80x80 icon as the best resolution for this use case. - // TODO: Consider theme based on current background color. - bool IsSecondIconBetter(const Manifest::Icon& current, const Manifest::Icon& alternative) - { - return IsSecondIconResolutionBetter(current.Resolution, alternative.Resolution); - } - - bool IsSecondIconBetter(const ExtractedIconInfo& current, const ExtractedIconInfo& alternative) - { - return IsSecondIconResolutionBetter(current.IconResolution, alternative.IconResolution); - } - - void ShowIcon(Execution::OutputStream& outputStream, VirtualTerminal::Sixel::Image& icon) - { - // Using a height of 4 arbitrarily; allow width up to the entire console. - UINT imageHeightCells = 4; - UINT imageWidthCells = static_cast(Execution::GetConsoleWidth().value_or(120)); - - icon.RenderSizeInCells(imageWidthCells, imageHeightCells); - icon.RenderTo(outputStream); - - // Force the final sixel line to not be overwritten - outputStream << std::endl; - } - - void ShowManifestIcon(Execution::Context& context, const Manifest::Manifest& manifest) try - { - if (!context.Reporter.SixelsEnabled()) - { - return; - } - - auto icons = manifest.CurrentLocalization.Get(); - const Manifest::Icon* bestFitIcon = nullptr; - - for (const auto& icon : icons) - { - if (!bestFitIcon || IsSecondIconBetter(*bestFitIcon, icon)) - { - bestFitIcon = &icon; - } - } - - if (!bestFitIcon) - { - return; - } - - // Use a cache to hold the icons - auto splitUri = Utility::SplitFileNameFromURI(bestFitIcon->Url); - Caching::FileCache fileCache{ Caching::FileCache::Type::Icon, Utility::SHA256::ConvertToString(bestFitIcon->Sha256), { splitUri.first } }; - auto iconStream = fileCache.GetFile(splitUri.second, bestFitIcon->Sha256); - - VirtualTerminal::Sixel::Image sixelIcon{ *iconStream, bestFitIcon->FileType }; - auto infoOut = context.Reporter.Info(); - - ShowIcon(infoOut, sixelIcon); - } - CATCH_LOG(); - - void ShowExtractedIcon(Execution::OutputStream& outputStream, const std::vector& icons) try - { - const ExtractedIconInfo* bestFitIcon = nullptr; - - for (const auto& icon : icons) - { - if (!bestFitIcon || IsSecondIconBetter(*bestFitIcon, icon)) - { - bestFitIcon = &icon; - } - } - - if (!bestFitIcon) - { - return; - } - - VirtualTerminal::Sixel::Image sixelIcon{ bestFitIcon->IconContent, bestFitIcon->IconFileType }; - ShowIcon(outputStream, sixelIcon); - } - CATCH_LOG(); - - Repository::Source OpenNamedSource(Execution::Context& context, Utility::LocIndView sourceName) - { - Repository::Source source; - - try - { - source = Source{ sourceName }; - - if (!source) - { - std::vector sources = Source::GetCurrentSources(); - - if (!sourceName.empty() && !sources.empty()) - { - // A bad name was given, try to help. - context.Reporter.Error() << Resource::String::OpenSourceFailedNoMatch(sourceName) << std::endl; - context.Reporter.Info() << Resource::String::OpenSourceFailedNoMatchHelp << std::endl; - for (const auto& details : sources) - { - context.Reporter.Info() << " "_liv << details.Name << std::endl; - } - - AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, {}); - } - else - { - // Even if a name was given, there are no sources - context.Reporter.Error() << Resource::String::OpenSourceFailedNoSourceDefined << std::endl; - AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED, {}); - } - } - - if (context.Args.Contains(Execution::Args::Type::CustomHeader)) - { - std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) }; - if (!source.SetCustomHeader(customHeader)) - { - context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; - } - } - - auto openFunction = [&](IProgressCallback& progress)->std::vector - { - source.SetCaller("winget-cli"); - source.SetAuthenticationArguments(GetAuthenticationArguments(context)); - source.SetThreadGlobals(context.GetSharedThreadGlobals()); - return source.Open(progress); - }; - auto updateFailures = context.Reporter.ExecuteWithProgress(openFunction, true); - - // We'll only report the source update failure as warning and continue - for (const auto& s : updateFailures) - { - context.Reporter.Warn() << Resource::String::SourceOpenWithFailedUpdate(Utility::LocIndView{ s.Name }) << std::endl; - } - - // Report sources that may need authentication - if (source.IsComposite()) - { - for (const auto& s : source.GetAvailableSources()) - { - if (s.GetInformation().Authentication.Type != Authentication::AuthenticationType::None) - { - context.Reporter.Info() << Execution::AuthenticationEmphasis << Resource::String::SourceRequiresAuthentication(Utility::LocIndView{ s.GetDetails().Name }) << std::endl; - } - } - } - else if (source.GetInformation().Authentication.Type != Authentication::AuthenticationType::None) - { - context.Reporter.Info() << Execution::AuthenticationEmphasis << Resource::String::SourceRequiresAuthentication(Utility::LocIndView{ source.GetDetails().Name }) << std::endl; - } - } - catch (const wil::ResultException& re) - { - context.Reporter.Error() << Resource::String::SourceOpenFailedSuggestion << std::endl; - if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES) - { - // Since we know there must have been multiple errors here, just fail the context rather - // than trying to get one of the exceptions back out. - AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, {}); - } - else - { - throw; - } - } - catch (...) - { - context.Reporter.Error() << Resource::String::SourceOpenFailedSuggestion << std::endl; - throw; - } - - return source; - } - - void SearchSourceApplyFilters(Execution::Context& context, SearchRequest& searchRequest, MatchType matchType) - { - const auto& args = context.Args; - - if (args.Contains(Execution::Args::Type::Id)) - { - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, args.GetArg(Execution::Args::Type::Id))); - } - - if (args.Contains(Execution::Args::Type::Name)) - { - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, args.GetArg(Execution::Args::Type::Name))); - } - - if (args.Contains(Execution::Args::Type::Moniker)) - { - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, args.GetArg(Execution::Args::Type::Moniker))); - } - - if (args.Contains(Execution::Args::Type::ProductCode)) - { - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, matchType, args.GetArg(Execution::Args::Type::ProductCode))); - } - - if (args.Contains(Execution::Args::Type::Tag)) - { - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Tag, matchType, args.GetArg(Execution::Args::Type::Tag))); - } - - if (args.Contains(Execution::Args::Type::Command)) - { - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Command, matchType, args.GetArg(Execution::Args::Type::Command))); - } - - if (args.Contains(Execution::Args::Type::Count)) - { - searchRequest.MaximumResults = std::stoi(std::string(args.GetArg(Execution::Args::Type::Count))); - } - } - - // Data shown on a line of a table displaying installed packages - struct InstalledPackagesTableLine - { - InstalledPackagesTableLine( - std::shared_ptr package, - std::shared_ptr installedVersion, - const Utility::LocIndString& availableVersion, - const Utility::LocIndString& source) - : Package(std::move(package)), InstalledPackageVersion(std::move(installedVersion)), AvailableVersion(availableVersion), Source(source) - { - Name = InstalledPackageVersion->GetProperty(PackageVersionProperty::Name); - Id = Package->GetProperty(PackageProperty::Id); - InstalledVersion = InstalledPackageVersion->GetProperty(PackageVersionProperty::Version); - } - - std::shared_ptr Package; - std::shared_ptr InstalledPackageVersion; - - Utility::LocIndString Name; - Utility::LocIndString Id; - Utility::LocIndString InstalledVersion; - Utility::LocIndString AvailableVersion; - Utility::LocIndString Source; - }; - - void OutputInstalledPackagesTable(Execution::Context& context, std::vector& lines) - { - Execution::TableOutput<5> table(context.Reporter, - { - Resource::String::SearchName, - Resource::String::SearchId, - Resource::String::SearchVersion, - Resource::String::AvailableHeader, - Resource::String::SearchSource - }); - - for (auto& line : lines) - { - table.OutputLine({ - line.Name, - line.Id, - line.InstalledVersion, - line.AvailableVersion, - line.Source - }); - } - - table.Complete(); - } - - void ShowMetadataField( - Execution::OutputStream& outputStream, - StringResource::StringId label, - const IPackageVersion::Metadata& metadata, - PackageVersionMetadata field) - { - auto itr = metadata.find(field); - if (itr != metadata.end()) - { - ShowSingleLineField(outputStream, label, Utility::LocIndView{ itr->second }); - } - } - - // Outputs every package "line" with many details, with a format similar to the `show` command. - void OutputInstalledPackagesDetails(Execution::Context& context, std::vector& lines) - { - auto info = context.Reporter.Info(); - size_t packageIndex = 0; - size_t totalLines = lines.size(); - bool shouldGetIcon = context.Reporter.SixelsEnabled(); - for (const auto& line : lines) - { - // Identity header including package count indicator if multiple lines provided - if (totalLines > 1) - { - info << '(' << ++packageIndex << '/' << totalLines << ") "_liv; - } - - ReportIdentity(context, {}, std::nullopt, line.Name, line.Id); - - auto metadata = line.InstalledPackageVersion->GetMetadata(); - auto productCodes = line.InstalledPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - - if (shouldGetIcon && !productCodes.empty()) - { - Manifest::ScopeEnum scope = Manifest::ScopeEnum::Unknown; - - auto itr = metadata.find(PackageVersionMetadata::InstalledScope); - if (itr != metadata.end()) - { - scope = Manifest::ConvertToScopeEnum(itr->second); - } - - auto icons = ExtractIconFromArpEntry(productCodes[0], scope); - ShowExtractedIcon(info, icons); - } - - ShowSingleLineField(info, Resource::String::ShowLabelVersion, line.InstalledVersion); - ShowSingleLineField(info, Resource::String::ShowLabelChannel, line.InstalledPackageVersion->GetProperty(PackageVersionProperty::Channel)); - ShowSingleLineField(info, Resource::String::ShowLabelPublisher, line.InstalledPackageVersion->GetProperty(PackageVersionProperty::Publisher)); - auto localIdentifier = line.InstalledPackageVersion->GetProperty(PackageVersionProperty::Id); - if (line.Id != localIdentifier) - { - ShowSingleLineField(info, Resource::String::ShowListLocalIdentifier, localIdentifier); - } - - ShowMultiValueField(info, Resource::String::ShowListPackageFamilyName, line.InstalledPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName)); - ShowMultiValueField(info, Resource::String::ShowListProductCode, productCodes); - ShowMultiValueField(info, Resource::String::ShowListUpgradeCode, line.InstalledPackageVersion->GetMultiProperty(PackageVersionMultiProperty::UpgradeCode)); - - ShowMetadataField(info, Resource::String::ShowListInstallerCategory, metadata, PackageVersionMetadata::InstalledType); - ShowMetadataField(info, Resource::String::ShowListInstalledScope, metadata, PackageVersionMetadata::InstalledScope); - ShowMetadataField(info, Resource::String::ShowListInstalledArchitecture, metadata, PackageVersionMetadata::InstalledArchitecture); - ShowMetadataField(info, Resource::String::ShowListInstalledLocale, metadata, PackageVersionMetadata::InstalledLocale); - ShowMetadataField(info, Resource::String::ShowListInstalledLocation, metadata, PackageVersionMetadata::InstalledLocation); - - auto source = line.InstalledPackageVersion->GetSource(); - if (source.ContainsAvailablePackages()) - { - ShowSingleLineField(info, Resource::String::ShowListInstalledSource, Utility::LocIndView{ source.GetDetails().Name }); - } - - Utility::Version currentVersion{ line.InstalledVersion }; - bool hasUpgradeVersion = false; - for (const auto& available : line.Package->GetAvailable()) - { - auto latestAvailable = available->GetLatestVersion(); - auto availableVersion = latestAvailable->GetProperty(PackageVersionProperty::Version); - if (Utility::Version{ availableVersion } > currentVersion) - { - if (!hasUpgradeVersion) - { - hasUpgradeVersion = true; - info << details::GetIndentFor(0) << Execution::ManifestInfoEmphasis << Resource::String::ShowListAvailableUpgrades << '\n'; - } - - info << details::GetIndentFor(1) << Utility::LocIndView{ available->GetSource().GetDetails().Name } << - " ["_liv << availableVersion << "]\n"_liv; - } - } - - // FUTURE: We could also pull data from the tracking database to show some things that we store there specifically. - } - } - - // Sorts a vector of InstalledPackagesTableLine according to the user's sort preferences. - // Resolution order: CLI args (--sort) > settings (output.sortOrder) > query-aware default. - void SortInstalledPackagesTableLines(Execution::Context& context, std::vector& lines) - { - if (lines.size() <= 1) - { - return; - } - - const SortParameters params(context); - - // Not strictly required — SortBy handles this internally — but avoids - // constructing the SortablePackageEntry vector when no sorting is needed. - if (!params.ShouldSort) - { - return; - } - - const SortField mask = ComputeSortFieldMask(params.Fields); - SortBy(lines, - [mask](const InstalledPackagesTableLine& line, size_t index) { - return SortablePackageEntry( - index, - line.Name.get(), - line.Id.get(), - line.InstalledVersion.get(), - line.AvailableVersion.get(), - line.Source.get(), - mask); - }, - params); - } - - void OutputInstalledPackages(Execution::Context& context, std::vector& lines) - { - SortInstalledPackagesTableLines(context, lines); - - if (context.Args.Contains(Execution::Args::Type::ListDetails)) - { - OutputInstalledPackagesDetails(context, lines); - } - else - { - OutputInstalledPackagesTable(context, lines); - } - } - } - - bool WorkflowTask::operator==(const WorkflowTask& other) const - { - if (m_isFunc && other.m_isFunc) - { - return m_func == other.m_func; - } - else if (!m_isFunc && !other.m_isFunc) - { - return m_name == other.m_name; - } - else - { - return false; - } - } - - void WorkflowTask::operator()(Execution::Context& context) const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_isFunc); - m_func(context); - } - - void WorkflowTask::Log() const - { - if (m_isFunc) - { - // Using `00000001`80000000` as base address default when loading dll into windbg as dump file. - AICLI_LOG(Workflow, Verbose, << "Running task: 0x" << m_func << " [ln 00000001`80000000+" << std::hex << (reinterpret_cast(m_func) - reinterpret_cast(&__ImageBase)) << "]"); - } - else - { - AICLI_LOG(Workflow, Verbose, << "Running task: " << m_name); - } - } - - Repository::PredefinedSource DetermineInstalledSource(const Execution::Context& context) - { - Repository::PredefinedSource installedSource = Repository::PredefinedSource::Installed; - Manifest::ScopeEnum scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); - if (scope == Manifest::ScopeEnum::Machine) - { - installedSource = Repository::PredefinedSource::InstalledMachine; - } - else if (scope == Manifest::ScopeEnum::User) - { - installedSource = Repository::PredefinedSource::InstalledUser; - } - - return installedSource; - } - - Authentication::AuthenticationArguments GetAuthenticationArguments(const Execution::Context& context) - { - AppInstaller::Authentication::AuthenticationArguments authArgs; - - if (context.Args.Contains(Execution::Args::Type::AuthenticationMode)) - { - authArgs.Mode = Authentication::ConvertToAuthenticationMode(context.Args.GetArg(Execution::Args::Type::AuthenticationMode)); - } - else - { - // If user did not specify authentication mode, determine based on if disable interactivity flag exists. - authArgs.Mode = context.Args.Contains(Execution::Args::Type::DisableInteractivity) ? Authentication::AuthenticationMode::Silent : Authentication::AuthenticationMode::SilentPreferred; - } - - if (context.Args.Contains(Execution::Args::Type::AuthenticationAccount)) - { - authArgs.AuthenticationAccount = context.Args.GetArg(Execution::Args::Type::AuthenticationAccount); - } - - AICLI_LOG(CLI, Info, << "Created authentication arguments. Mode: " << Authentication::AuthenticationModeToString(authArgs.Mode) << ", Account: " << authArgs.AuthenticationAccount); - - return authArgs; - } - - HRESULT HandleException(Execution::Context* context, std::exception_ptr exception) - { - try - { - std::rethrow_exception(exception); - } - // Exceptions that may occur in the process of executing an arbitrary command - catch (const wil::ResultException& re) - { - // Even though they are logged at their source, log again here for completeness. - Logging::Telemetry().LogException(Logging::FailureTypeEnum::ResultException, re.what()); - if (context) - { - context->Reporter.Error() << - Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << - GetUserPresentableMessage(re) << std::endl; - } - return re.GetErrorCode(); - } - catch (const winrt::hresult_error& hre) - { - std::string message = GetUserPresentableMessage(hre); - Logging::Telemetry().LogException(Logging::FailureTypeEnum::WinrtHResultError, message); - if (context) - { - context->Reporter.Error() << - Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << - message << std::endl; - } - return hre.code(); - } - catch (const Settings::GroupPolicyException& e) - { - if (context) - { - auto policy = Settings::TogglePolicy::GetPolicy(e.Policy()); - auto policyNameId = policy.PolicyName(); - context->Reporter.Error() << Resource::String::DisabledByGroupPolicy(policyNameId) << std::endl; - } - return APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY; - } - catch (const std::exception& e) - { - Logging::Telemetry().LogException(Logging::FailureTypeEnum::StdException, e.what()); - if (context) - { - context->Reporter.Error() << - Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << - GetUserPresentableMessage(e) << std::endl; - } - return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - Logging::Telemetry().LogException(Logging::FailureTypeEnum::Unknown, {}); - if (context) - { - context->Reporter.Error() << - Resource::String::UnexpectedErrorExecutingCommand << " ???"_liv << std::endl; - } - return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; - } - - return E_UNEXPECTED; - } - - HRESULT HandleException(Execution::Context& context, std::exception_ptr exception) - { - return HandleException(&context, exception); - } - - AppInstaller::Manifest::ManifestComparator::Options GetManifestComparatorOptions(const Execution::Context& context, const IPackageVersion::Metadata& metadata) - { - AppInstaller::Manifest::ManifestComparator::Options options; - bool getAllowedArchitecturesFromMetadata = false; - - if (context.Contains(Execution::Data::AllowedArchitectures)) - { - // Com caller can directly set allowed architectures - options.AllowedArchitectures = context.Get(); - } - else if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) - { - // Arguments provided in command line - options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallArchitecture))); - } - else if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture)) - { - // Arguments provided in command line. Also skips applicability check. - options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture))); - options.SkipApplicabilityCheck = true; - } - else - { - getAllowedArchitecturesFromMetadata = true; - } - - if (context.Args.Contains(Execution::Args::Type::InstallerType)) - { - options.RequestedInstallerType = Manifest::ConvertToInstallerTypeEnum(std::string(context.Args.GetArg(Execution::Args::Type::InstallerType))); - } - - if (context.Args.Contains(Execution::Args::Type::InstallScope)) - { - options.RequestedInstallerScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); - } - - if (context.Contains(Execution::Data::AllowUnknownScope)) - { - options.AllowUnknownScope = context.Get(); - } - - if (context.Args.Contains(Execution::Args::Type::Locale)) - { - options.RequestedInstallerLocale = context.Args.GetArg(Execution::Args::Type::Locale); - } - - Repository::GetManifestComparatorOptionsFromMetadata(options, metadata, getAllowedArchitecturesFromMetadata); - - return options; - } - - void OpenSource::operator()(Execution::Context& context) const - { - std::string_view sourceName; - if (m_forDependencies) - { - if (context.Args.Contains(Execution::Args::Type::DependencySource)) - { - sourceName = context.Args.GetArg(Execution::Args::Type::DependencySource); - } - } - else - { - if (context.Args.Contains(Execution::Args::Type::Source)) - { - sourceName = context.Args.GetArg(Execution::Args::Type::Source); - } - } - - auto source = OpenNamedSource(context, Utility::LocIndView{ sourceName }); - if (context.IsTerminated()) - { - return; - } - - context << HandleSourceAgreements(source); - if (context.IsTerminated()) - { - return; - } - - if (m_forDependencies) - { - context.Add(std::move(source)); - } - else - { - context.Add(std::move(source)); - } - } - - void OpenNamedSourceForSources::operator()(Execution::Context& context) const - { - auto source = OpenNamedSource(context, m_sourceName); - if (context.IsTerminated()) - { - return; - } - - context << HandleSourceAgreements(source); - if (context.IsTerminated()) - { - return; - } - - if (!context.Contains(Execution::Data::Sources)) - { - context.Add({ std::move(source) }); - } - else - { - context.Get().emplace_back(std::move(source)); - } - } - - void OpenPredefinedSource::operator()(Execution::Context& context) const - { - Repository::Source source; - try - { - source = Source{ m_predefinedSource }; - - // A well known predefined source should return a value. - THROW_HR_IF(E_UNEXPECTED, !source); - - auto openFunction = [&](IProgressCallback& progress)->std::vector - { - return source.Open(progress); - }; - context.Reporter.ExecuteWithProgress(openFunction, true); - } - catch (...) - { - context.Reporter.Error() << Resource::String::SourceOpenPredefinedFailedSuggestion << std::endl; - throw; - } - - if (m_forDependencies) - { - context.Add(std::move(source)); - } - else - { - context.Add(std::move(source)); - } - } - - void OpenCompositeSource::operator()(Execution::Context& context) const - { - // Get the already open source for use as the available. - Repository::Source availableSource; - if (m_forDependencies) - { - availableSource = context.Get(); - } - else - { - availableSource = context.Get(); - } - - // Open the predefined source. - context << OpenPredefinedSource(m_predefinedSource, m_forDependencies); - - // Create the composite source from the two. - Repository::Source source; - if (m_forDependencies) - { - source = context.Get(); - } - else - { - source = context.Get(); - } - - Repository::Source compositeSource{ source, availableSource, m_searchBehavior }; - - // Overwrite the source with the composite. - if (m_forDependencies) - { - context.Add(std::move(compositeSource)); - } - else - { - context.Add(std::move(compositeSource)); - } - } - - void SearchSourceForMany(Execution::Context& context) - { - const auto& args = context.Args; - - MatchType matchType = MatchType::Substring; - if (args.Contains(Execution::Args::Type::Exact)) - { - matchType = MatchType::Exact; - } - - SearchRequest searchRequest; - - if (args.Contains(Execution::Args::Type::Query)) - { - searchRequest.Query.emplace(RequestMatch(matchType, args.GetArg(Execution::Args::Type::Query))); - } - - SearchSourceApplyFilters(context, searchRequest, matchType); - - Logging::Telemetry().LogSearchRequest( - "many", - args.GetArg(Execution::Args::Type::Query), - args.GetArg(Execution::Args::Type::Id), - args.GetArg(Execution::Args::Type::Name), - args.GetArg(Execution::Args::Type::Moniker), - args.GetArg(Execution::Args::Type::Tag), - args.GetArg(Execution::Args::Type::Command), - searchRequest.MaximumResults, - searchRequest.ToString()); - - context.Add(context.Get().Search(searchRequest)); - } - - void GetSearchRequestForSingle(Execution::Context& context) - { - const auto& args = context.Args; - - MatchType matchType = MatchType::CaseInsensitive; - if (args.Contains(Execution::Args::Type::Exact)) - { - matchType = MatchType::Exact; - } - - SearchRequest searchRequest; - // Note: MultiQuery when we need search for single is handled with one sub-context per query. - if (args.Contains(Execution::Args::Type::Query)) - { - std::string_view query = args.GetArg(Execution::Args::Type::Query); - - // Regardless of match type, always use an exact match for the system reference strings. - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, query)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, query)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, query)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, query)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, query)); - } - - SearchSourceApplyFilters(context, searchRequest, matchType); - - context.Add(std::move(searchRequest)); - } - - void SearchSourceForSingle(Execution::Context& context) - { - const auto& args = context.Args; - context << GetSearchRequestForSingle; - if (!context.IsTerminated()) - { - const auto& searchRequest = context.Get(); - - Logging::Telemetry().LogSearchRequest( - "single", - args.GetArg(Execution::Args::Type::Query), - args.GetArg(Execution::Args::Type::Id), - args.GetArg(Execution::Args::Type::Name), - args.GetArg(Execution::Args::Type::Moniker), - args.GetArg(Execution::Args::Type::Tag), - args.GetArg(Execution::Args::Type::Command), - searchRequest.MaximumResults, - searchRequest.ToString()); - - context.Add(context.Get().Search(searchRequest)); - } - } - - void SearchSourceForManyCompletion(Execution::Context& context) - { - MatchType matchType = MatchType::StartsWith; - - SearchRequest searchRequest; - std::string_view query = context.Get().Word(); - searchRequest.Query.emplace(RequestMatch(matchType, query)); - - SearchSourceApplyFilters(context, searchRequest, matchType); - - context.Add(context.Get().Search(searchRequest)); - } - - void SearchSourceForSingleCompletion(Execution::Context& context) - { - MatchType matchType = MatchType::StartsWith; - - SearchRequest searchRequest; - std::string_view query = context.Get().Word(); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, query)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, query)); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, query)); - - SearchSourceApplyFilters(context, searchRequest, matchType); - - context.Add(context.Get().Search(searchRequest)); - } - - void SearchSourceForCompletionField::operator()(Execution::Context& context) const - { - const std::string& word = context.Get().Word(); - - SearchRequest searchRequest; - searchRequest.Inclusions.emplace_back(PackageMatchFilter(m_field, MatchType::StartsWith, word)); - - // If filters are provided, be generous with the search no matter the intended result. - SearchSourceApplyFilters(context, searchRequest, MatchType::Substring); - - context.Add(context.Get().Search(searchRequest)); - } - - void ReportSearchResult(Execution::Context& context) - { - auto& searchResult = context.Get(); - - bool sourceIsComposite = context.Get().IsComposite(); - Execution::TableOutput<5> table(context.Reporter, - { - Resource::String::SearchName, - Resource::String::SearchId, - Resource::String::SearchVersion, - Resource::String::SearchMatch, - Resource::String::SearchSource - }); - - for (size_t i = 0; i < searchResult.Matches.size(); ++i) - { - auto latestVersion = GetAllAvailableVersions(searchResult.Matches[i].Package)->GetLatestVersion(); - - table.OutputLine({ - latestVersion->GetProperty(PackageVersionProperty::Name), - latestVersion->GetProperty(PackageVersionProperty::Id), - latestVersion->GetProperty(PackageVersionProperty::Version), - GetMatchCriteriaDescriptor(searchResult.Matches[i]), - sourceIsComposite ? static_cast(latestVersion->GetProperty(PackageVersionProperty::SourceName)) : ""s - }); - } - - table.Complete(); - - if (searchResult.Truncated) - { - context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; - } - } - - void HandleSearchResultFailures(Execution::Context& context) - { - const auto& searchResult = context.Get(); - - if (!searchResult.Failures.empty()) - { - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::TreatSourceFailuresAsWarning)) - { - auto warn = context.Reporter.Warn(); - for (const auto& failure : searchResult.Failures) - { - warn << Resource::String::SearchFailureWarning(Utility::LocIndView{ failure.SourceName }) << std::endl; - } - } - else - { - HRESULT overallHR = S_OK; - auto error = context.Reporter.Error(); - for (const auto& failure : searchResult.Failures) - { - error << Resource::String::SearchFailureError(Utility::LocIndView{ failure.SourceName }) << std::endl; - HRESULT failureHR = HandleException(context, failure.Exception); - - // Just take first failure for now - if (overallHR == S_OK) - { - overallHR = failureHR; - } - } - - if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::ShowSearchResultsOnPartialFailure)) - { - if (searchResult.Matches.empty()) - { - context.Reporter.Info() << std::endl << Resource::String::SearchFailureErrorNoMatches << std::endl; - } - else - { - context.Reporter.Info() << std::endl << Resource::String::SearchFailureErrorListMatches << std::endl; - context << ReportMultiplePackageFoundResultWithSource; - } - } - - context.SetTerminationHR(overallHR); - } - } - } - - void ReportMultiplePackageFoundResult(Execution::Context& context) - { - auto& searchResult = context.Get(); - - Execution::TableOutput<2> table(context.Reporter, - { - Resource::String::SearchName, - Resource::String::SearchId - }); - - for (size_t i = 0; i < searchResult.Matches.size(); ++i) - { - auto package = searchResult.Matches[i].Package; - - table.OutputLine({ - package->GetProperty(PackageProperty::Name), - package->GetProperty(PackageProperty::Id) - }); - } - - table.Complete(); - - if (searchResult.Truncated) - { - context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; - } - } - - void ReportMultiplePackageFoundResultWithSource(Execution::Context& context) - { - auto& searchResult = context.Get(); - - Execution::TableOutput<3> table(context.Reporter, - { - Resource::String::SearchName, - Resource::String::SearchId, - Resource::String::SearchSource - }); - - for (size_t i = 0; i < searchResult.Matches.size(); ++i) - { - auto package = searchResult.Matches[i].Package; - - std::string sourceName; - auto available = package->GetAvailable(); - if (!available.empty()) - { - auto source = available[0]->GetSource(); - if (source) - { - sourceName = source.GetDetails().Name; - } - } - - table.OutputLine({ - package->GetProperty(PackageProperty::Name), - package->GetProperty(PackageProperty::Id), - std::move(sourceName) - }); - } - - table.Complete(); - - if (searchResult.Truncated) - { - context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; - } - } - - void ReportListResult::operator()(Execution::Context& context) const - { - auto& searchResult = context.Get(); - - std::vector lines; - std::vector linesForExplicitUpgrade; - std::vector linesForPins; - - int availableUpgradesCount = 0; - - // We will show a line with a summary for skipped and pinned packages at the end. - // The strings suggest using a --include-unknown/pinned argument, so we should - // ensure that the count is 0 when using the arguments. - int packagesWithUnknownVersionSkipped = 0; - int packagesWithUserPinsSkipped = 0; - - auto &source = context.Get(); - bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; - bool sourceFilterProvided = context.Args.Contains(Execution::Args::Type::Source); - - PinBehavior pinBehavior; - if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::Force)) - { - // For listing upgrades, show the version we would upgrade to with the given pins. - pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; - } - else - { - // For listing installed apps or if we are ignoring pins due to --force, show the latest available. - pinBehavior = PinBehavior::IgnorePins; - } - - PinningData pinningData{ PinningData::Disposition::ReadOnly }; - - for (const auto& match : searchResult.Matches) - { - auto installedPackage = match.Package->GetInstalled(); - if (!installedPackage) - { - continue; - } - - // We only want to evaluate update availability for the latest version. - bool isFirstInstalledVersion = true; - - for (const auto& installedVersionKey : installedPackage->GetVersionKeys()) - { - bool isFirstInstalledVersionLocal = isFirstInstalledVersion; - isFirstInstalledVersion = false; - - auto installedVersion = installedPackage->GetVersion(installedVersionKey); - - auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, installedVersion); - auto availableVersions = GetAvailableVersionsForInstalledVersion(match.Package, installedVersion); - - auto latestVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); - bool updateAvailable = isFirstInstalledVersionLocal && evaluator.IsUpdate(latestVersion); - bool updateIsPinned = false; - - if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::IncludeUnknown) && Utility::Version(installedVersion->GetProperty(PackageVersionProperty::Version)).IsUnknown() && updateAvailable) - { - // We are only showing upgrades, and the user did not request to include packages with unknown versions. - ++packagesWithUnknownVersionSkipped; - continue; - } - - if (m_onlyShowUpgrades && !updateAvailable && isFirstInstalledVersionLocal) - { - // Reuse the evaluator to check if there is an update outside of the pinning - auto unpinnedLatestVersion = availableVersions->GetLatestVersion(); - bool updateAvailableWithoutPins = evaluator.IsUpdate(unpinnedLatestVersion); - - if (updateAvailableWithoutPins) - { - // When given the --include-pinned argument, report blocking and gating pins in a separate table. - // Otherwise, simply show a count of them - if (context.Args.Contains(Execution::Args::Type::IncludePinned)) - { - updateIsPinned = true; - - // Override these so we generate the table line below. - latestVersion = std::move(unpinnedLatestVersion); - updateAvailable = true; - } - else - { - ++packagesWithUserPinsSkipped; - continue; - } - } - } - - // When --source is given, only show packages that have a correlation (available version) - // in the specified source. Packages with no available match are not from that source. - if (sourceFilterProvided && !latestVersion) - { - continue; - } - - if (updateAvailable || !m_onlyShowUpgrades) - { - Utility::LocIndString availableVersion, sourceName; - - if (latestVersion) - { - // Always show the source for correlated packages - sourceName = latestVersion->GetProperty(PackageVersionProperty::SourceName); - - if (updateAvailable) - { - availableVersion = latestVersion->GetProperty(PackageVersionProperty::Version); - availableUpgradesCount++; - } - } - - // Output using the local PackageName instead of the name in the manifest, to prevent confusion for packages that add multiple - // Add/Remove Programs entries. - // TODO: De-duplicate this list, and only show (by default) one entry per matched package. - InstalledPackagesTableLine line( - match.Package, - installedVersion, - availableVersion, - shouldShowSource ? sourceName : Utility::LocIndString() - ); - - auto pinnedState = ConvertToPinTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::PinnedState]); - if (updateIsPinned) - { - linesForPins.push_back(std::move(line)); - } - else if (m_onlyShowUpgrades && pinnedState == PinType::PinnedByManifest) - { - linesForExplicitUpgrade.push_back(std::move(line)); - } - else - { - lines.push_back(std::move(line)); - } - } - } - } - - OutputInstalledPackages(context, lines); - - if (lines.empty()) - { - context.Reporter.Info() << Resource::String::NoInstalledPackageFound << std::endl; - } - else - { - if (searchResult.Truncated) - { - context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; - } - - if (m_onlyShowUpgrades) - { - context.Reporter.Info() << Resource::String::AvailableUpgrades(availableUpgradesCount) << std::endl; - } - } - - if (!linesForExplicitUpgrade.empty()) - { - context.Reporter.Info() << std::endl << Resource::String::UpgradeAvailableForPinned << std::endl; - OutputInstalledPackages(context, linesForExplicitUpgrade); - } - - if (!linesForPins.empty()) - { - context.Reporter.Info() << std::endl << Resource::String::UpgradeBlockedByPinCount(linesForPins.size()) << std::endl; - OutputInstalledPackages(context, linesForPins); - } - - if (m_onlyShowUpgrades) - { - if (packagesWithUnknownVersionSkipped > 0) - { - AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version"); - context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl; - } - - if (packagesWithUserPinsSkipped > 0) - { - AICLI_LOG(CLI, Info, << packagesWithUserPinsSkipped << " package(s) skipped due to user pins"); - context.Reporter.Info() << Resource::String::UpgradePinnedByUserCount(packagesWithUserPinsSkipped) << std::endl; - } - } - } - - void EnsureMatchesFromSearchResult::operator()(Execution::Context& context) const - { - auto& searchResult = context.Get(); - - Logging::Telemetry().LogSearchResultCount(searchResult.Matches.size()); - - if (searchResult.Matches.size() == 0) - { - Logging::Telemetry().LogNoAppMatch(); - - switch (m_operationType) - { - // These search purposes require a package to be found in the Installed Packages - case OperationType::Export: - case OperationType::List: - case OperationType::Uninstall: - case OperationType::Pin: - case OperationType::Upgrade: - case OperationType::Repair: - context.Reporter.Info() << Resource::String::NoInstalledPackageFound << std::endl; - break; - case OperationType::Completion: - case OperationType::Install: - case OperationType::Search: - case OperationType::Show: - case OperationType::Download: - default: - context.Reporter.Info() << Resource::String::NoPackageFound << std::endl; - break; - } - - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); - } - } - - void EnsureOneMatchFromSearchResult::operator()(Execution::Context& context) const - { - context << - EnsureMatchesFromSearchResult(m_operationType); - - if (!context.IsTerminated()) - { - auto& searchResult = context.Get(); - - bool operationTargetsInstalled = m_operationType == OperationType::Upgrade || m_operationType == OperationType::Uninstall || - m_operationType == OperationType::Repair || m_operationType == OperationType::Export; - - // Try limiting results to highest priority sources - if (searchResult.Matches.size() > 1 && !operationTargetsInstalled && - ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) - { - // Find the set of matches that have the highest priority - std::vector highestPriorityMatches; - std::optional highestPriority; - - for (const auto& match : searchResult.Matches) - { - std::optional priority = GetSourcePriority(match.Package); - - // Optional provides overloads that make empty less than valued and empties equal. - if (highestPriority < priority) - { - // Current priority is higher; reset. - highestPriority = priority; - highestPriorityMatches.clear(); - } - else if (highestPriority == priority) - { - // Priority is equal, add to the list. - } - else - { - // Current priority is lower, ignore the match. - continue; - } - - highestPriorityMatches.emplace_back(match); - } - - if (highestPriorityMatches.size() < searchResult.Matches.size()) - { - AICLI_LOG(CLI, Info, << "Replacing search results with only those from the highest priority [" << (highestPriority ? std::to_string(highestPriority.value()) : "none"s) << "]."); - searchResult.Matches = std::move(highestPriorityMatches); - context.Reporter.Warn() << Resource::String::MultiplePackagesFoundFilteredBySourcePriority << std::endl; - } - } - - if (searchResult.Matches.size() > 1) - { - Logging::Telemetry().LogMultiAppMatch(); - - if (operationTargetsInstalled) - { - context.Reporter.Warn() << Resource::String::MultipleInstalledPackagesFound << std::endl; - context << ReportMultiplePackageFoundResult; - } - else - { - context.Reporter.Warn() << Resource::String::MultiplePackagesFound << std::endl; - context << ReportMultiplePackageFoundResultWithSource; - } - - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND); - } - - std::shared_ptr package = searchResult.Matches.at(0).Package; - Logging::Telemetry().LogAppFound(package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); - - context.Add(std::move(package)); - } - } - - void GetManifestWithVersionFromPackage::operator()(Execution::Context& context) const - { - PackageVersionKey key("", m_version, m_channel); - - std::shared_ptr package = context.Get(); - std::shared_ptr requestedVersion; - auto availableVersions = GetAvailableVersionsForInstalledVersion(package); - - if (m_considerPins) - { - bool isPinned = false; - - PinBehavior pinBehavior; - if (context.Args.Contains(Execution::Args::Type::Force)) - { - // --force ignores any pins - pinBehavior = PinBehavior::IgnorePins; - } - else - { - pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; - } - - PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, GetInstalledVersion(package)); - - // TODO: The logic here will probably have to get more difficult once we support channels - if (Utility::IsEmptyOrWhitespace(m_version) && Utility::IsEmptyOrWhitespace(m_channel)) - { - requestedVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); - - if (!requestedVersion) - { - // Check whether we didn't find the latest version because it was pinned or because there wasn't one - auto latestVersion = availableVersions->GetLatestVersion(); - if (latestVersion) - { - isPinned = true; - } - } - } - else - { - requestedVersion = availableVersions->GetVersion(key); - isPinned = evaluator.EvaluatePinType(requestedVersion) != PinType::Unknown; - } - - if (isPinned) - { - if (context.Args.Contains(Execution::Args::Type::Force)) - { - AICLI_LOG(CLI, Info, << "Ignoring pin on package due to --force argument"); - } - else - { - AICLI_LOG(CLI, Error, << "The requested package version is unavailable because of a pin"); - context.Reporter.Error() << Resource::String::PackageIsPinned << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); - } - } - } - else - { - // The simple case: Just look up the requested version - requestedVersion = availableVersions->GetVersion(key); - } - - std::optional manifest; - if (requestedVersion) - { - manifest = requestedVersion->GetManifest(); - } - - if (!manifest) - { - std::ostringstream ssVersionInfo; - if (!m_version.empty()) - { - ssVersionInfo << m_version; - } - if (!m_channel.empty()) - { - ssVersionInfo << '[' << m_channel << ']'; - } - - context.Reporter.Error() << Resource::String::GetManifestResultVersionNotFound(Utility::LocIndView{ ssVersionInfo.str()}) << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); - } - - Logging::Telemetry().LogManifestFields(manifest->Id, manifest->DefaultLocalization.Get(), manifest->Version); - - std::string targetLocale; - if (context.Args.Contains(Execution::Args::Type::Locale)) - { - targetLocale = context.Args.GetArg(Execution::Args::Type::Locale); - } - manifest->ApplyLocale(targetLocale); - - context.Add(std::move(manifest.value())); - context.Add(std::move(requestedVersion)); - } - - void GetManifestFromPackage::operator()(Execution::Context& context) const - { - context << GetManifestWithVersionFromPackage( - context.Args.GetArg(Execution::Args::Type::Version), - context.Args.GetArg(Execution::Args::Type::Channel), - m_considerPins); - } - - void VerifyFile::operator()(Execution::Context& context) const - { - std::filesystem::path path = Utility::ConvertToUTF16(context.Args.GetArg(m_arg)); - - if (!std::filesystem::exists(path)) - { - context.Reporter.Error() << Resource::String::VerifyFileFailedNotExist(Utility::LocIndView{ path.u8string() }) << std::endl; - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); - } - - if (std::filesystem::is_directory(path)) - { - context.Reporter.Error() << Resource::String::VerifyFileFailedIsDirectory(Utility::LocIndView{ path.u8string() }) << std::endl; - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED)); - } - } - - void VerifyPath::operator()(Execution::Context& context) const - { - std::filesystem::path path = Utility::ConvertToUTF16(context.Args.GetArg(m_arg)); - - if (!std::filesystem::exists(path)) - { - context.Reporter.Error() << Resource::String::VerifyPathFailedNotExist(Utility::LocIndView{ path.u8string() }) << std::endl; - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)); - } - } - - void VerifyFileOrUri::operator()(Execution::Context& context) const - { - // Argument requirement is handled elsewhere. - if (!context.Args.Contains(m_arg)) - { - return; - } - - auto path = context.Args.GetArg(m_arg); - - // try uri first - Uri pathAsUri = nullptr; - try - { - pathAsUri = Uri{ Utility::ConvertToUTF16(path) }; - } - catch (...) {} - - if (pathAsUri) - { - if (pathAsUri.Suspicious()) - { - context.Reporter.Error() << Resource::String::UriNotWellFormed(Utility::LocIndView{ path }) << std::endl; - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - // SchemeName() always returns lower case - else if (L"file" == pathAsUri.SchemeName() && !Utility::CaseInsensitiveStartsWith(path, "file:")) - { - // Uri constructor is smart enough to parse an absolute local file path to file uri. - // In this case, we should continue with VerifyFile. - context << VerifyFile(m_arg); - } - else if (std::find(m_supportedSchemes.begin(), m_supportedSchemes.end(), pathAsUri.SchemeName()) != m_supportedSchemes.end()) - { - // Scheme supported. - return; - } - else - { - context.Reporter.Error() << Resource::String::UriSchemeNotSupported(Utility::LocIndView{ path }) << std::endl; - AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - else - { - context << VerifyFile(m_arg); - } - } - - void GetManifestFromArg(Execution::Context& context) - { - Logging::Telemetry().LogIsManifestLocal(true); - - context << - VerifyPath(Execution::Args::Type::Manifest) << - [](Execution::Context& context) - { - Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::Manifest))); - Logging::Telemetry().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get(), manifest.Version); - - std::string targetLocale; - if (context.Args.Contains(Execution::Args::Type::Locale)) - { - targetLocale = context.Args.GetArg(Execution::Args::Type::Locale); - } - manifest.ApplyLocale(targetLocale); - - context.Add(std::move(manifest)); - }; - } - - void ReportPackageIdentity(Execution::Context& context) - { - auto package = context.Get(); - ReportIdentity(context, {}, Resource::String::ReportIdentityFound, package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); - } - - void ReportInstalledPackageVersionIdentity(Execution::Context& context) - { - auto package = context.Get(); - auto version = context.Get(); - ReportIdentity(context, {}, Resource::String::ReportIdentityFound, version->GetProperty(PackageVersionProperty::Name), package ? package->GetProperty(PackageProperty::Id) : version->GetProperty(PackageVersionProperty::Id)); - } - - void ReportManifestIdentity(Execution::Context& context) - { - const auto& manifest = context.Get(); - ReportIdentity(context, {}, Resource::String::ReportIdentityFound, manifest.CurrentLocalization.Get(), manifest.Id); - ShowManifestIcon(context, manifest); - } - - void ReportManifestIdentityWithVersion::operator()(Execution::Context& context) const - { - const auto& manifest = context.Get(); - ReportIdentity(context, m_prefix, m_label, manifest.CurrentLocalization.Get(), manifest.Id, manifest.Version, m_level); - ShowManifestIcon(context, manifest); - } - - void SelectInstaller(Execution::Context& context) - { - bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); - bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); - - IPackageVersion::Metadata installationMetadata; - - if (isUpdate || isRepair) - { - installationMetadata = context.Get()->GetMetadata(); - } - - Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, installationMetadata)); - auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(context.Get()); - - if (!installer.has_value()) - { - auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType); - if (onlyInstalledType != inapplicabilities.end()) - { - if (isRepair) - { - context.Reporter.Info() << Resource::String::RepairDifferentInstallTechnology << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE); - } - else - { - context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnology << std::endl; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); - } - } - } - - if (installer.has_value()) - { - Logging::Telemetry().LogSelectedInstaller( - static_cast(installer->Arch), - installer->Url, - Manifest::InstallerTypeToString(installer->EffectiveInstallerType()), - Manifest::ScopeToString(installer->Scope), - installer->Locale); - } - - context.Add(installer); - } - - void EnsureRunningAsAdmin(Execution::Context& context) - { - if (!Runtime::IsRunningAsAdmin()) - { - context.Reporter.Error() << Resource::String::CommandRequiresAdmin; - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN); - } - } - - void EnsureFeatureEnabled::operator()(Execution::Context& context) const - { - if (!Settings::ExperimentalFeature::IsEnabled(m_feature)) - { - context.Reporter.Error() - << Resource::String::FeatureDisabledMessage(Utility::LocIndView{ Settings::ExperimentalFeature::GetFeature(m_feature).JsonName() }) - << std::endl; - AICLI_LOG(CLI, Error, << Settings::ExperimentalFeature::GetFeature(m_feature).Name() << " feature is disabled. Execution cancelled."); - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED); - } - } - - void SearchSourceUsingManifest(Execution::Context& context) - { - const auto& manifest = context.Get(); - auto source = context.Get(); - - // First try search using ProductId or PackageFamilyName - for (const auto& installer : manifest.Installers) - { - SearchRequest searchRequest; - if (!installer.PackageFamilyName.empty()) - { - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, installer.PackageFamilyName)); - } - else if (!installer.ProductCode.empty()) - { - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, installer.ProductCode)); - } - else if (installer.EffectiveInstallerType() == Manifest::InstallerTypeEnum::Portable) - { - const auto& productCode = Utility::MakeSuitablePathPart(manifest.Id + '_' + source.GetIdentifier()); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::CaseInsensitive, Utility::Normalize(productCode))); - } - else if (installer.EffectiveInstallerType() == Manifest::InstallerTypeEnum::Font) - { - // Font Packages match by Package Id first. - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id)); - } - - if (!searchRequest.Inclusions.empty()) - { - auto searchResult = source.Search(searchRequest); - - if (!searchResult.Matches.empty()) - { - context.Add(std::move(searchResult)); - return; - } - } - } - - // If we cannot find a package using PackageFamilyName or ProductId, try manifest Id and Name pair - SearchRequest searchRequest; - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id)); - - // In case there are same Ids from different sources, filter the result using package name - for (const auto& localization : manifest.Localizations) - { - const auto& localizedPackageName = localization.Get(); - if (!localizedPackageName.empty()) - { - searchRequest.Filters.emplace_back(PackageMatchField::Name, MatchType::CaseInsensitive, localizedPackageName); - } - } - - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::CaseInsensitive, manifest.DefaultLocalization.Get())); - - context.Add(source.Search(searchRequest)); - } - - void GetInstalledPackageVersion(Execution::Context& context) - { - std::shared_ptr installed = context.Get()->GetInstalled(); - - if (installed) - { - // TODO: This may need to be expanded dramatically to enable targeting across a variety of dimensions (architecture, etc.) - // Alternatively, if we make it easier to see the fully unique package identifiers, we may avoid that need. - if (context.Args.Contains(Execution::Args::Type::TargetVersion)) - { - Repository::PackageVersionKey versionKey{ "", context.Args.GetArg(Execution::Args::Type::TargetVersion) , "" }; - std::shared_ptr installedVersion = installed->GetVersion(versionKey); - - if (!installedVersion) - { - context.Reporter.Error() << Resource::String::GetManifestResultVersionNotFound(Utility::LocIndView{ versionKey.Version }) << std::endl; - // This error maintains consistency with passing an available version to commands - AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); - } - - context.Add(std::move(installedVersion)); - } - else - { - context.Add(installed->GetLatestVersion()); - } - } - else - { - context.Add(nullptr); - } - } - - void ReportExecutionStage::operator()(Execution::Context& context) const - { - context.SetExecutionStage(m_stage); - } - - void ShowAppVersions(Execution::Context& context) - { - auto versions = GetAllAvailableVersions(context.Get())->GetVersionKeys(); - - Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); - for (const auto& version : versions) - { - table.OutputLine({ version.Version, version.Channel }); - } - table.Complete(); - } -} - -AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, AppInstaller::CLI::Workflow::WorkflowTask::Func f) -{ - return (context << AppInstaller::CLI::Workflow::WorkflowTask(f)); -} - -AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, const AppInstaller::CLI::Workflow::WorkflowTask& task) -{ - if (!context.IsTerminated() || task.ExecuteAlways()) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (context.ShouldExecuteWorkflowTask(task)) -#endif - { - task.Log(); - task(context); - } - } - return context; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowBase.h" +#include "ExecutionContext.h" +#include "PackageTableSortHelper.h" +#include "PromptFlow.h" +#include "ShowFlow.h" +#include "Sixel.h" +#include "TableOutput.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +EXTERN_C IMAGE_DOS_HEADER __ImageBase; + +using namespace std::string_literals; +using namespace AppInstaller::Utility::literals; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Settings; +using namespace winrt::Windows::Foundation; + +namespace AppInstaller::CLI::Workflow +{ + namespace + { + std::string GetMatchCriteriaDescriptor(const ResultMatch& match) + { + if (match.MatchCriteria.Field != PackageMatchField::Id && match.MatchCriteria.Field != PackageMatchField::Name) + { + std::string result{ ToString(match.MatchCriteria.Field) }; + result += ": "; + result += match.MatchCriteria.Value; + return result; + } + else + { + return {}; + } + } + + void ReportIdentity( + Execution::Context& context, + Utility::LocIndView prefix, + std::optional label, + std::string_view name, + std::string_view id, + std::string_view version = {}, + Execution::Reporter::Level level = Execution::Reporter::Level::Info) + { + auto out = context.Reporter.GetOutputStream(level); + out << prefix; + if (label) + { + out << *label << ' '; + } + out << Execution::NameEmphasis << name << " ["_liv << Execution::IdEmphasis << id << ']'; + + if (!version.empty()) + { + out << ' ' << Resource::String::ShowVersion << ' ' << version; + } + + out << std::endl; + } + + bool IsSecondIconResolutionBetter(Manifest::IconResolutionEnum current, Manifest::IconResolutionEnum alternative) + { + static constexpr std::array s_iconResolutionOrder + { + 9, // Unknown + 8, // Custom + 15, // Square16 + 14, // Square20 + 13, // Square24 + 12, // Square30 + 11, // Square32 + 10, // Square36 + 6, // Square40 + 5, // Square48 + 4, // Square60 + 3, // Square64 + 2, // Square72 + 0, // Square80 + 1, // Square96 + 7, // Square256 + }; + + return s_iconResolutionOrder.at(ToIntegral(alternative)) < s_iconResolutionOrder.at(ToIntegral(current)); + } + + // Determines icon fit given two options. + // Targets an 80x80 icon as the best resolution for this use case. + // TODO: Consider theme based on current background color. + bool IsSecondIconBetter(const Manifest::Icon& current, const Manifest::Icon& alternative) + { + return IsSecondIconResolutionBetter(current.Resolution, alternative.Resolution); + } + + bool IsSecondIconBetter(const ExtractedIconInfo& current, const ExtractedIconInfo& alternative) + { + return IsSecondIconResolutionBetter(current.IconResolution, alternative.IconResolution); + } + + void ShowIcon(Execution::OutputStream& outputStream, VirtualTerminal::Sixel::Image& icon) + { + // Using a height of 4 arbitrarily; allow width up to the entire console. + UINT imageHeightCells = 4; + UINT imageWidthCells = static_cast(Execution::GetConsoleWidth().value_or(120)); + + icon.RenderSizeInCells(imageWidthCells, imageHeightCells); + icon.RenderTo(outputStream); + + // Force the final sixel line to not be overwritten + outputStream << std::endl; + } + + void ShowManifestIcon(Execution::Context& context, const Manifest::Manifest& manifest) try + { + if (!context.Reporter.SixelsEnabled()) + { + return; + } + + auto icons = manifest.CurrentLocalization.Get(); + const Manifest::Icon* bestFitIcon = nullptr; + + for (const auto& icon : icons) + { + if (!bestFitIcon || IsSecondIconBetter(*bestFitIcon, icon)) + { + bestFitIcon = &icon; + } + } + + if (!bestFitIcon) + { + return; + } + + // Use a cache to hold the icons + auto splitUri = Utility::SplitFileNameFromURI(bestFitIcon->Url); + Caching::FileCache fileCache{ Caching::FileCache::Type::Icon, Utility::SHA256::ConvertToString(bestFitIcon->Sha256), { splitUri.first } }; + auto iconStream = fileCache.GetFile(splitUri.second, bestFitIcon->Sha256); + + VirtualTerminal::Sixel::Image sixelIcon{ *iconStream, bestFitIcon->FileType }; + auto infoOut = context.Reporter.Info(); + + ShowIcon(infoOut, sixelIcon); + } + CATCH_LOG(); + + void ShowExtractedIcon(Execution::OutputStream& outputStream, const std::vector& icons) try + { + const ExtractedIconInfo* bestFitIcon = nullptr; + + for (const auto& icon : icons) + { + if (!bestFitIcon || IsSecondIconBetter(*bestFitIcon, icon)) + { + bestFitIcon = &icon; + } + } + + if (!bestFitIcon) + { + return; + } + + VirtualTerminal::Sixel::Image sixelIcon{ bestFitIcon->IconContent, bestFitIcon->IconFileType }; + ShowIcon(outputStream, sixelIcon); + } + CATCH_LOG(); + + Repository::Source OpenNamedSource(Execution::Context& context, Utility::LocIndView sourceName) + { + Repository::Source source; + + try + { + source = Source{ sourceName }; + + if (!source) + { + std::vector sources = Source::GetCurrentSources(); + + if (!sourceName.empty() && !sources.empty()) + { + // A bad name was given, try to help. + context.Reporter.Error() << Resource::String::OpenSourceFailedNoMatch(sourceName) << std::endl; + context.Reporter.Info() << Resource::String::OpenSourceFailedNoMatchHelp << std::endl; + for (const auto& details : sources) + { + context.Reporter.Info() << " "_liv << details.Name << std::endl; + } + + AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, {}); + } + else + { + // Even if a name was given, there are no sources + context.Reporter.Error() << Resource::String::OpenSourceFailedNoSourceDefined << std::endl; + AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED, {}); + } + } + + if (context.Args.Contains(Execution::Args::Type::CustomHeader)) + { + std::string customHeader{ context.Args.GetArg(Execution::Args::Type::CustomHeader) }; + if (!source.SetCustomHeader(customHeader)) + { + context.Reporter.Warn() << Resource::String::HeaderArgumentNotApplicableForNonRestSourceWarning << std::endl; + } + } + + auto openFunction = [&](IProgressCallback& progress)->std::vector + { + source.SetCaller("winget-cli"); + source.SetAuthenticationArguments(GetAuthenticationArguments(context)); + source.SetThreadGlobals(context.GetSharedThreadGlobals()); + return source.Open(progress); + }; + auto updateFailures = context.Reporter.ExecuteWithProgress(openFunction, true); + + // We'll only report the source update failure as warning and continue + for (const auto& s : updateFailures) + { + context.Reporter.Warn() << Resource::String::SourceOpenWithFailedUpdate(Utility::LocIndView{ s.Name }) << std::endl; + } + + // Report sources that may need authentication + if (source.IsComposite()) + { + for (const auto& s : source.GetAvailableSources()) + { + if (s.GetInformation().Authentication.Type != Authentication::AuthenticationType::None) + { + context.Reporter.Info() << Execution::AuthenticationEmphasis << Resource::String::SourceRequiresAuthentication(Utility::LocIndView{ s.GetDetails().Name }) << std::endl; + } + } + } + else if (source.GetInformation().Authentication.Type != Authentication::AuthenticationType::None) + { + context.Reporter.Info() << Execution::AuthenticationEmphasis << Resource::String::SourceRequiresAuthentication(Utility::LocIndView{ source.GetDetails().Name }) << std::endl; + } + } + catch (const wil::ResultException& re) + { + context.Reporter.Error() << Resource::String::SourceOpenFailedSuggestion << std::endl; + if (re.GetErrorCode() == APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES) + { + // Since we know there must have been multiple errors here, just fail the context rather + // than trying to get one of the exceptions back out. + AICLI_TERMINATE_CONTEXT_RETURN(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, {}); + } + else + { + throw; + } + } + catch (...) + { + context.Reporter.Error() << Resource::String::SourceOpenFailedSuggestion << std::endl; + throw; + } + + return source; + } + + void SearchSourceApplyFilters(Execution::Context& context, SearchRequest& searchRequest, MatchType matchType) + { + const auto& args = context.Args; + + if (args.Contains(Execution::Args::Type::Id)) + { + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, args.GetArg(Execution::Args::Type::Id))); + } + + if (args.Contains(Execution::Args::Type::Name)) + { + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, args.GetArg(Execution::Args::Type::Name))); + } + + if (args.Contains(Execution::Args::Type::Moniker)) + { + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, args.GetArg(Execution::Args::Type::Moniker))); + } + + if (args.Contains(Execution::Args::Type::ProductCode)) + { + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, matchType, args.GetArg(Execution::Args::Type::ProductCode))); + } + + if (args.Contains(Execution::Args::Type::Tag)) + { + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Tag, matchType, args.GetArg(Execution::Args::Type::Tag))); + } + + if (args.Contains(Execution::Args::Type::Command)) + { + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Command, matchType, args.GetArg(Execution::Args::Type::Command))); + } + + if (args.Contains(Execution::Args::Type::Count)) + { + searchRequest.MaximumResults = std::stoi(std::string(args.GetArg(Execution::Args::Type::Count))); + } + } + + // Data shown on a line of a table displaying installed packages + struct InstalledPackagesTableLine + { + InstalledPackagesTableLine( + std::shared_ptr package, + std::shared_ptr installedVersion, + const Utility::LocIndString& availableVersion, + const Utility::LocIndString& source) + : Package(std::move(package)), InstalledPackageVersion(std::move(installedVersion)), AvailableVersion(availableVersion), Source(source) + { + Name = InstalledPackageVersion->GetProperty(PackageVersionProperty::Name); + Id = Package->GetProperty(PackageProperty::Id); + InstalledVersion = InstalledPackageVersion->GetProperty(PackageVersionProperty::Version); + } + + std::shared_ptr Package; + std::shared_ptr InstalledPackageVersion; + + Utility::LocIndString Name; + Utility::LocIndString Id; + Utility::LocIndString InstalledVersion; + Utility::LocIndString AvailableVersion; + Utility::LocIndString Source; + }; + + void OutputInstalledPackagesTable(Execution::Context& context, std::vector& lines) + { + Execution::TableOutput<5> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchVersion, + Resource::String::AvailableHeader, + Resource::String::SearchSource + }); + + for (auto& line : lines) + { + table.OutputLine({ + line.Name, + line.Id, + line.InstalledVersion, + line.AvailableVersion, + line.Source + }); + } + + table.Complete(); + } + + void ShowMetadataField( + Execution::OutputStream& outputStream, + StringResource::StringId label, + const IPackageVersion::Metadata& metadata, + PackageVersionMetadata field) + { + auto itr = metadata.find(field); + if (itr != metadata.end()) + { + ShowSingleLineField(outputStream, label, Utility::LocIndView{ itr->second }); + } + } + + // Outputs every package "line" with many details, with a format similar to the `show` command. + void OutputInstalledPackagesDetails(Execution::Context& context, std::vector& lines) + { + auto info = context.Reporter.Info(); + size_t packageIndex = 0; + size_t totalLines = lines.size(); + bool shouldGetIcon = context.Reporter.SixelsEnabled(); + for (const auto& line : lines) + { + // Identity header including package count indicator if multiple lines provided + if (totalLines > 1) + { + info << '(' << ++packageIndex << '/' << totalLines << ") "_liv; + } + + ReportIdentity(context, {}, std::nullopt, line.Name, line.Id); + + auto metadata = line.InstalledPackageVersion->GetMetadata(); + auto productCodes = line.InstalledPackageVersion->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + + if (shouldGetIcon && !productCodes.empty()) + { + Manifest::ScopeEnum scope = Manifest::ScopeEnum::Unknown; + + auto itr = metadata.find(PackageVersionMetadata::InstalledScope); + if (itr != metadata.end()) + { + scope = Manifest::ConvertToScopeEnum(itr->second); + } + + auto icons = ExtractIconFromArpEntry(productCodes[0], scope); + ShowExtractedIcon(info, icons); + } + + ShowSingleLineField(info, Resource::String::ShowLabelVersion, line.InstalledVersion); + ShowSingleLineField(info, Resource::String::ShowLabelChannel, line.InstalledPackageVersion->GetProperty(PackageVersionProperty::Channel)); + ShowSingleLineField(info, Resource::String::ShowLabelPublisher, line.InstalledPackageVersion->GetProperty(PackageVersionProperty::Publisher)); + auto localIdentifier = line.InstalledPackageVersion->GetProperty(PackageVersionProperty::Id); + if (line.Id != localIdentifier) + { + ShowSingleLineField(info, Resource::String::ShowListLocalIdentifier, localIdentifier); + } + + ShowMultiValueField(info, Resource::String::ShowListPackageFamilyName, line.InstalledPackageVersion->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName)); + ShowMultiValueField(info, Resource::String::ShowListProductCode, productCodes); + ShowMultiValueField(info, Resource::String::ShowListUpgradeCode, line.InstalledPackageVersion->GetMultiProperty(PackageVersionMultiProperty::UpgradeCode)); + + ShowMetadataField(info, Resource::String::ShowListInstallerCategory, metadata, PackageVersionMetadata::InstalledType); + ShowMetadataField(info, Resource::String::ShowListInstalledScope, metadata, PackageVersionMetadata::InstalledScope); + ShowMetadataField(info, Resource::String::ShowListInstalledArchitecture, metadata, PackageVersionMetadata::InstalledArchitecture); + ShowMetadataField(info, Resource::String::ShowListInstalledLocale, metadata, PackageVersionMetadata::InstalledLocale); + ShowMetadataField(info, Resource::String::ShowListInstalledLocation, metadata, PackageVersionMetadata::InstalledLocation); + + auto source = line.InstalledPackageVersion->GetSource(); + if (source.ContainsAvailablePackages()) + { + ShowSingleLineField(info, Resource::String::ShowListInstalledSource, Utility::LocIndView{ source.GetDetails().Name }); + } + + Utility::Version currentVersion{ line.InstalledVersion }; + bool hasUpgradeVersion = false; + for (const auto& available : line.Package->GetAvailable()) + { + auto latestAvailable = available->GetLatestVersion(); + auto availableVersion = latestAvailable->GetProperty(PackageVersionProperty::Version); + if (Utility::Version{ availableVersion } > currentVersion) + { + if (!hasUpgradeVersion) + { + hasUpgradeVersion = true; + info << details::GetIndentFor(0) << Execution::ManifestInfoEmphasis << Resource::String::ShowListAvailableUpgrades << '\n'; + } + + info << details::GetIndentFor(1) << Utility::LocIndView{ available->GetSource().GetDetails().Name } << + " ["_liv << availableVersion << "]\n"_liv; + } + } + + // FUTURE: We could also pull data from the tracking database to show some things that we store there specifically. + } + } + + // Sorts a vector of InstalledPackagesTableLine according to the user's sort preferences. + // Resolution order: CLI args (--sort) > settings (output.sortOrder) > query-aware default. + void SortInstalledPackagesTableLines(Execution::Context& context, std::vector& lines) + { + if (lines.size() <= 1) + { + return; + } + + const SortParameters params(context); + + // Not strictly required — SortBy handles this internally — but avoids + // constructing the SortablePackageEntry vector when no sorting is needed. + if (!params.ShouldSort) + { + return; + } + + const SortField mask = ComputeSortFieldMask(params.Fields); + SortBy(lines, + [mask](const InstalledPackagesTableLine& line, size_t index) { + return SortablePackageEntry( + index, + line.Name.get(), + line.Id.get(), + line.InstalledVersion.get(), + line.AvailableVersion.get(), + line.Source.get(), + mask); + }, + params); + } + + void OutputInstalledPackages(Execution::Context& context, std::vector& lines) + { + SortInstalledPackagesTableLines(context, lines); + + if (context.Args.Contains(Execution::Args::Type::ListDetails)) + { + OutputInstalledPackagesDetails(context, lines); + } + else + { + OutputInstalledPackagesTable(context, lines); + } + } + } + + bool WorkflowTask::operator==(const WorkflowTask& other) const + { + if (m_isFunc && other.m_isFunc) + { + return m_func == other.m_func; + } + else if (!m_isFunc && !other.m_isFunc) + { + return m_name == other.m_name; + } + else + { + return false; + } + } + + void WorkflowTask::operator()(Execution::Context& context) const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_isFunc); + m_func(context); + } + + void WorkflowTask::Log() const + { + if (m_isFunc) + { + // Using `00000001`80000000` as base address default when loading dll into windbg as dump file. + AICLI_LOG(Workflow, Verbose, << "Running task: 0x" << m_func << " [ln 00000001`80000000+" << std::hex << (reinterpret_cast(m_func) - reinterpret_cast(&__ImageBase)) << "]"); + } + else + { + AICLI_LOG(Workflow, Verbose, << "Running task: " << m_name); + } + } + + Repository::PredefinedSource DetermineInstalledSource(const Execution::Context& context) + { + Repository::PredefinedSource installedSource = Repository::PredefinedSource::Installed; + Manifest::ScopeEnum scope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + if (scope == Manifest::ScopeEnum::Machine) + { + installedSource = Repository::PredefinedSource::InstalledMachine; + } + else if (scope == Manifest::ScopeEnum::User) + { + installedSource = Repository::PredefinedSource::InstalledUser; + } + + return installedSource; + } + + Authentication::AuthenticationArguments GetAuthenticationArguments(const Execution::Context& context) + { + AppInstaller::Authentication::AuthenticationArguments authArgs; + + if (context.Args.Contains(Execution::Args::Type::AuthenticationMode)) + { + authArgs.Mode = Authentication::ConvertToAuthenticationMode(context.Args.GetArg(Execution::Args::Type::AuthenticationMode)); + } + else + { + // If user did not specify authentication mode, determine based on if disable interactivity flag exists. + authArgs.Mode = context.Args.Contains(Execution::Args::Type::DisableInteractivity) ? Authentication::AuthenticationMode::Silent : Authentication::AuthenticationMode::SilentPreferred; + } + + if (context.Args.Contains(Execution::Args::Type::AuthenticationAccount)) + { + authArgs.AuthenticationAccount = context.Args.GetArg(Execution::Args::Type::AuthenticationAccount); + } + + AICLI_LOG(CLI, Info, << "Created authentication arguments. Mode: " << Authentication::AuthenticationModeToString(authArgs.Mode) << ", Account: " << authArgs.AuthenticationAccount); + + return authArgs; + } + + HRESULT HandleException(Execution::Context* context, std::exception_ptr exception) + { + try + { + std::rethrow_exception(exception); + } + // Exceptions that may occur in the process of executing an arbitrary command + catch (const wil::ResultException& re) + { + // Even though they are logged at their source, log again here for completeness. + Logging::Telemetry().LogException(Logging::FailureTypeEnum::ResultException, re.what()); + if (context) + { + context->Reporter.Error() << + Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << + GetUserPresentableMessage(re) << std::endl; + } + return re.GetErrorCode(); + } + catch (const winrt::hresult_error& hre) + { + std::string message = GetUserPresentableMessage(hre); + Logging::Telemetry().LogException(Logging::FailureTypeEnum::WinrtHResultError, message); + if (context) + { + context->Reporter.Error() << + Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << + message << std::endl; + } + return hre.code(); + } + catch (const Settings::GroupPolicyException& e) + { + if (context) + { + auto policy = Settings::TogglePolicy::GetPolicy(e.Policy()); + auto policyNameId = policy.PolicyName(); + context->Reporter.Error() << Resource::String::DisabledByGroupPolicy(policyNameId) << std::endl; + } + return APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY; + } + catch (const std::exception& e) + { + Logging::Telemetry().LogException(Logging::FailureTypeEnum::StdException, e.what()); + if (context) + { + context->Reporter.Error() << + Resource::String::UnexpectedErrorExecutingCommand << ' ' << std::endl << + GetUserPresentableMessage(e) << std::endl; + } + return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + Logging::Telemetry().LogException(Logging::FailureTypeEnum::Unknown, {}); + if (context) + { + context->Reporter.Error() << + Resource::String::UnexpectedErrorExecutingCommand << " ???"_liv << std::endl; + } + return APPINSTALLER_CLI_ERROR_COMMAND_FAILED; + } + + return E_UNEXPECTED; + } + + HRESULT HandleException(Execution::Context& context, std::exception_ptr exception) + { + return HandleException(&context, exception); + } + + AppInstaller::Manifest::ManifestComparator::Options GetManifestComparatorOptions(const Execution::Context& context, const IPackageVersion::Metadata& metadata) + { + AppInstaller::Manifest::ManifestComparator::Options options; + bool getAllowedArchitecturesFromMetadata = false; + + if (context.Contains(Execution::Data::AllowedArchitectures)) + { + // Com caller can directly set allowed architectures + options.AllowedArchitectures = context.Get(); + } + else if (context.Args.Contains(Execution::Args::Type::InstallArchitecture)) + { + // Arguments provided in command line + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallArchitecture))); + } + else if (context.Args.Contains(Execution::Args::Type::InstallerArchitecture)) + { + // Arguments provided in command line. Also skips applicability check. + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(context.Args.GetArg(Execution::Args::Type::InstallerArchitecture))); + options.SkipApplicabilityCheck = true; + } + else + { + getAllowedArchitecturesFromMetadata = true; + } + + if (context.Args.Contains(Execution::Args::Type::InstallerType)) + { + options.RequestedInstallerType = Manifest::ConvertToInstallerTypeEnum(std::string(context.Args.GetArg(Execution::Args::Type::InstallerType))); + } + + if (context.Args.Contains(Execution::Args::Type::InstallScope)) + { + options.RequestedInstallerScope = Manifest::ConvertToScopeEnum(context.Args.GetArg(Execution::Args::Type::InstallScope)); + } + + if (context.Contains(Execution::Data::AllowUnknownScope)) + { + options.AllowUnknownScope = context.Get(); + } + + if (context.Args.Contains(Execution::Args::Type::Locale)) + { + options.RequestedInstallerLocale = context.Args.GetArg(Execution::Args::Type::Locale); + } + + Repository::GetManifestComparatorOptionsFromMetadata(options, metadata, getAllowedArchitecturesFromMetadata); + + return options; + } + + void OpenSource::operator()(Execution::Context& context) const + { + std::string_view sourceName; + if (m_forDependencies) + { + if (context.Args.Contains(Execution::Args::Type::DependencySource)) + { + sourceName = context.Args.GetArg(Execution::Args::Type::DependencySource); + } + } + else + { + if (context.Args.Contains(Execution::Args::Type::Source)) + { + sourceName = context.Args.GetArg(Execution::Args::Type::Source); + } + } + + auto source = OpenNamedSource(context, Utility::LocIndView{ sourceName }); + if (context.IsTerminated()) + { + return; + } + + context << HandleSourceAgreements(source); + if (context.IsTerminated()) + { + return; + } + + if (m_forDependencies) + { + context.Add(std::move(source)); + } + else + { + context.Add(std::move(source)); + } + } + + void OpenNamedSourceForSources::operator()(Execution::Context& context) const + { + auto source = OpenNamedSource(context, m_sourceName); + if (context.IsTerminated()) + { + return; + } + + context << HandleSourceAgreements(source); + if (context.IsTerminated()) + { + return; + } + + if (!context.Contains(Execution::Data::Sources)) + { + context.Add({ std::move(source) }); + } + else + { + context.Get().emplace_back(std::move(source)); + } + } + + void OpenPredefinedSource::operator()(Execution::Context& context) const + { + Repository::Source source; + try + { + source = Source{ m_predefinedSource }; + + // A well known predefined source should return a value. + THROW_HR_IF(E_UNEXPECTED, !source); + + auto openFunction = [&](IProgressCallback& progress)->std::vector + { + return source.Open(progress); + }; + context.Reporter.ExecuteWithProgress(openFunction, true); + } + catch (...) + { + context.Reporter.Error() << Resource::String::SourceOpenPredefinedFailedSuggestion << std::endl; + throw; + } + + if (m_forDependencies) + { + context.Add(std::move(source)); + } + else + { + context.Add(std::move(source)); + } + } + + void OpenCompositeSource::operator()(Execution::Context& context) const + { + // Get the already open source for use as the available. + Repository::Source availableSource; + if (m_forDependencies) + { + availableSource = context.Get(); + } + else + { + availableSource = context.Get(); + } + + // Open the predefined source. + context << OpenPredefinedSource(m_predefinedSource, m_forDependencies); + + // Create the composite source from the two. + Repository::Source source; + if (m_forDependencies) + { + source = context.Get(); + } + else + { + source = context.Get(); + } + + Repository::Source compositeSource{ source, availableSource, m_searchBehavior }; + + // Overwrite the source with the composite. + if (m_forDependencies) + { + context.Add(std::move(compositeSource)); + } + else + { + context.Add(std::move(compositeSource)); + } + } + + void SearchSourceForMany(Execution::Context& context) + { + const auto& args = context.Args; + + MatchType matchType = MatchType::Substring; + if (args.Contains(Execution::Args::Type::Exact)) + { + matchType = MatchType::Exact; + } + + SearchRequest searchRequest; + + if (args.Contains(Execution::Args::Type::Query)) + { + searchRequest.Query.emplace(RequestMatch(matchType, args.GetArg(Execution::Args::Type::Query))); + } + + SearchSourceApplyFilters(context, searchRequest, matchType); + + Logging::Telemetry().LogSearchRequest( + "many", + args.GetArg(Execution::Args::Type::Query), + args.GetArg(Execution::Args::Type::Id), + args.GetArg(Execution::Args::Type::Name), + args.GetArg(Execution::Args::Type::Moniker), + args.GetArg(Execution::Args::Type::Tag), + args.GetArg(Execution::Args::Type::Command), + searchRequest.MaximumResults, + searchRequest.ToString()); + + context.Add(context.Get().Search(searchRequest)); + } + + void GetSearchRequestForSingle(Execution::Context& context) + { + const auto& args = context.Args; + + MatchType matchType = MatchType::CaseInsensitive; + if (args.Contains(Execution::Args::Type::Exact)) + { + matchType = MatchType::Exact; + } + + SearchRequest searchRequest; + // Note: MultiQuery when we need search for single is handled with one sub-context per query. + if (args.Contains(Execution::Args::Type::Query)) + { + std::string_view query = args.GetArg(Execution::Args::Type::Query); + + // Regardless of match type, always use an exact match for the system reference strings. + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, query)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, query)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, query)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, query)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, query)); + } + + SearchSourceApplyFilters(context, searchRequest, matchType); + + context.Add(std::move(searchRequest)); + } + + void SearchSourceForSingle(Execution::Context& context) + { + const auto& args = context.Args; + context << GetSearchRequestForSingle; + if (!context.IsTerminated()) + { + const auto& searchRequest = context.Get(); + + Logging::Telemetry().LogSearchRequest( + "single", + args.GetArg(Execution::Args::Type::Query), + args.GetArg(Execution::Args::Type::Id), + args.GetArg(Execution::Args::Type::Name), + args.GetArg(Execution::Args::Type::Moniker), + args.GetArg(Execution::Args::Type::Tag), + args.GetArg(Execution::Args::Type::Command), + searchRequest.MaximumResults, + searchRequest.ToString()); + + context.Add(context.Get().Search(searchRequest)); + } + } + + void SearchSourceForManyCompletion(Execution::Context& context) + { + MatchType matchType = MatchType::StartsWith; + + SearchRequest searchRequest; + std::string_view query = context.Get().Word(); + searchRequest.Query.emplace(RequestMatch(matchType, query)); + + SearchSourceApplyFilters(context, searchRequest, matchType); + + context.Add(context.Get().Search(searchRequest)); + } + + void SearchSourceForSingleCompletion(Execution::Context& context) + { + MatchType matchType = MatchType::StartsWith; + + SearchRequest searchRequest; + std::string_view query = context.Get().Word(); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, matchType, query)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, matchType, query)); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, matchType, query)); + + SearchSourceApplyFilters(context, searchRequest, matchType); + + context.Add(context.Get().Search(searchRequest)); + } + + void SearchSourceForCompletionField::operator()(Execution::Context& context) const + { + const std::string& word = context.Get().Word(); + + SearchRequest searchRequest; + searchRequest.Inclusions.emplace_back(PackageMatchFilter(m_field, MatchType::StartsWith, word)); + + // If filters are provided, be generous with the search no matter the intended result. + SearchSourceApplyFilters(context, searchRequest, MatchType::Substring); + + context.Add(context.Get().Search(searchRequest)); + } + + void ReportSearchResult(Execution::Context& context) + { + auto& searchResult = context.Get(); + + bool sourceIsComposite = context.Get().IsComposite(); + Execution::TableOutput<5> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchVersion, + Resource::String::SearchMatch, + Resource::String::SearchSource + }); + + for (size_t i = 0; i < searchResult.Matches.size(); ++i) + { + auto latestVersion = GetAllAvailableVersions(searchResult.Matches[i].Package)->GetLatestVersion(); + + table.OutputLine({ + latestVersion->GetProperty(PackageVersionProperty::Name), + latestVersion->GetProperty(PackageVersionProperty::Id), + latestVersion->GetProperty(PackageVersionProperty::Version), + GetMatchCriteriaDescriptor(searchResult.Matches[i]), + sourceIsComposite ? static_cast(latestVersion->GetProperty(PackageVersionProperty::SourceName)) : ""s + }); + } + + table.Complete(); + + if (searchResult.Truncated) + { + context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; + } + } + + void HandleSearchResultFailures(Execution::Context& context) + { + const auto& searchResult = context.Get(); + + if (!searchResult.Failures.empty()) + { + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::TreatSourceFailuresAsWarning)) + { + auto warn = context.Reporter.Warn(); + for (const auto& failure : searchResult.Failures) + { + warn << Resource::String::SearchFailureWarning(Utility::LocIndView{ failure.SourceName }) << std::endl; + } + } + else + { + HRESULT overallHR = S_OK; + auto error = context.Reporter.Error(); + for (const auto& failure : searchResult.Failures) + { + error << Resource::String::SearchFailureError(Utility::LocIndView{ failure.SourceName }) << std::endl; + HRESULT failureHR = HandleException(context, failure.Exception); + + // Just take first failure for now + if (overallHR == S_OK) + { + overallHR = failureHR; + } + } + + if (WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::ShowSearchResultsOnPartialFailure)) + { + if (searchResult.Matches.empty()) + { + context.Reporter.Info() << std::endl << Resource::String::SearchFailureErrorNoMatches << std::endl; + } + else + { + context.Reporter.Info() << std::endl << Resource::String::SearchFailureErrorListMatches << std::endl; + context << ReportMultiplePackageFoundResultWithSource; + } + } + + context.SetTerminationHR(overallHR); + } + } + } + + void ReportMultiplePackageFoundResult(Execution::Context& context) + { + auto& searchResult = context.Get(); + + Execution::TableOutput<2> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId + }); + + for (size_t i = 0; i < searchResult.Matches.size(); ++i) + { + auto package = searchResult.Matches[i].Package; + + table.OutputLine({ + package->GetProperty(PackageProperty::Name), + package->GetProperty(PackageProperty::Id) + }); + } + + table.Complete(); + + if (searchResult.Truncated) + { + context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; + } + } + + void ReportMultiplePackageFoundResultWithSource(Execution::Context& context) + { + auto& searchResult = context.Get(); + + Execution::TableOutput<3> table(context.Reporter, + { + Resource::String::SearchName, + Resource::String::SearchId, + Resource::String::SearchSource + }); + + for (size_t i = 0; i < searchResult.Matches.size(); ++i) + { + auto package = searchResult.Matches[i].Package; + + std::string sourceName; + auto available = package->GetAvailable(); + if (!available.empty()) + { + auto source = available[0]->GetSource(); + if (source) + { + sourceName = source.GetDetails().Name; + } + } + + table.OutputLine({ + package->GetProperty(PackageProperty::Name), + package->GetProperty(PackageProperty::Id), + std::move(sourceName) + }); + } + + table.Complete(); + + if (searchResult.Truncated) + { + context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; + } + } + + void ReportListResult::operator()(Execution::Context& context) const + { + auto& searchResult = context.Get(); + + std::vector lines; + std::vector linesForExplicitUpgrade; + std::vector linesForPins; + + int availableUpgradesCount = 0; + + // We will show a line with a summary for skipped and pinned packages at the end. + // The strings suggest using a --include-unknown/pinned argument, so we should + // ensure that the count is 0 when using the arguments. + int packagesWithUnknownVersionSkipped = 0; + int packagesWithUserPinsSkipped = 0; + + auto &source = context.Get(); + bool shouldShowSource = source.IsComposite() && source.GetAvailableSources().size() > 1; + bool sourceFilterProvided = context.Args.Contains(Execution::Args::Type::Source); + + PinBehavior pinBehavior; + if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::Force)) + { + // For listing upgrades, show the version we would upgrade to with the given pins. + pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + } + else + { + // For listing installed apps or if we are ignoring pins due to --force, show the latest available. + pinBehavior = PinBehavior::IgnorePins; + } + + PinningData pinningData{ PinningData::Disposition::ReadOnly }; + + for (const auto& match : searchResult.Matches) + { + auto installedPackage = match.Package->GetInstalled(); + if (!installedPackage) + { + continue; + } + + // We only want to evaluate update availability for the latest version. + bool isFirstInstalledVersion = true; + + for (const auto& installedVersionKey : installedPackage->GetVersionKeys()) + { + bool isFirstInstalledVersionLocal = isFirstInstalledVersion; + isFirstInstalledVersion = false; + + auto installedVersion = installedPackage->GetVersion(installedVersionKey); + + auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, installedVersion); + auto availableVersions = GetAvailableVersionsForInstalledVersion(match.Package, installedVersion); + + auto latestVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); + bool updateAvailable = isFirstInstalledVersionLocal && evaluator.IsUpdate(latestVersion); + bool updateIsPinned = false; + + if (m_onlyShowUpgrades && !context.Args.Contains(Execution::Args::Type::IncludeUnknown) && Utility::Version(installedVersion->GetProperty(PackageVersionProperty::Version)).IsUnknown() && updateAvailable) + { + // We are only showing upgrades, and the user did not request to include packages with unknown versions. + ++packagesWithUnknownVersionSkipped; + continue; + } + + if (m_onlyShowUpgrades && !updateAvailable && isFirstInstalledVersionLocal) + { + // Reuse the evaluator to check if there is an update outside of the pinning + auto unpinnedLatestVersion = availableVersions->GetLatestVersion(); + bool updateAvailableWithoutPins = evaluator.IsUpdate(unpinnedLatestVersion); + + if (updateAvailableWithoutPins) + { + // When given the --include-pinned argument, report blocking and gating pins in a separate table. + // Otherwise, simply show a count of them + if (context.Args.Contains(Execution::Args::Type::IncludePinned)) + { + updateIsPinned = true; + + // Override these so we generate the table line below. + latestVersion = std::move(unpinnedLatestVersion); + updateAvailable = true; + } + else + { + ++packagesWithUserPinsSkipped; + continue; + } + } + } + + // When --source is given, only show packages that have a correlation (available version) + // in the specified source. Packages with no available match are not from that source. + if (sourceFilterProvided && !latestVersion) + { + continue; + } + + if (updateAvailable || !m_onlyShowUpgrades) + { + Utility::LocIndString availableVersion, sourceName; + + if (latestVersion) + { + // Always show the source for correlated packages + sourceName = latestVersion->GetProperty(PackageVersionProperty::SourceName); + + if (updateAvailable) + { + availableVersion = latestVersion->GetProperty(PackageVersionProperty::Version); + availableUpgradesCount++; + } + } + + // Output using the local PackageName instead of the name in the manifest, to prevent confusion for packages that add multiple + // Add/Remove Programs entries. + // TODO: De-duplicate this list, and only show (by default) one entry per matched package. + InstalledPackagesTableLine line( + match.Package, + installedVersion, + availableVersion, + shouldShowSource ? sourceName : Utility::LocIndString() + ); + + auto pinnedState = ConvertToPinTypeEnum(installedVersion->GetMetadata()[PackageVersionMetadata::PinnedState]); + if (updateIsPinned) + { + linesForPins.push_back(std::move(line)); + } + else if (m_onlyShowUpgrades && pinnedState == PinType::PinnedByManifest) + { + linesForExplicitUpgrade.push_back(std::move(line)); + } + else + { + lines.push_back(std::move(line)); + } + } + } + } + + OutputInstalledPackages(context, lines); + + if (lines.empty()) + { + context.Reporter.Info() << Resource::String::NoInstalledPackageFound << std::endl; + } + else + { + if (searchResult.Truncated) + { + context.Reporter.Info() << '<' << Resource::String::SearchTruncated << '>' << std::endl; + } + + if (m_onlyShowUpgrades) + { + context.Reporter.Info() << Resource::String::AvailableUpgrades(availableUpgradesCount) << std::endl; + } + } + + if (!linesForExplicitUpgrade.empty()) + { + context.Reporter.Info() << std::endl << Resource::String::UpgradeAvailableForPinned << std::endl; + OutputInstalledPackages(context, linesForExplicitUpgrade); + } + + if (!linesForPins.empty()) + { + context.Reporter.Info() << std::endl << Resource::String::UpgradeBlockedByPinCount(linesForPins.size()) << std::endl; + OutputInstalledPackages(context, linesForPins); + } + + if (m_onlyShowUpgrades) + { + if (packagesWithUnknownVersionSkipped > 0) + { + AICLI_LOG(CLI, Info, << packagesWithUnknownVersionSkipped << " package(s) skipped due to unknown installed version"); + context.Reporter.Info() << Resource::String::UpgradeUnknownVersionCount(packagesWithUnknownVersionSkipped) << std::endl; + } + + if (packagesWithUserPinsSkipped > 0) + { + AICLI_LOG(CLI, Info, << packagesWithUserPinsSkipped << " package(s) skipped due to user pins"); + context.Reporter.Info() << Resource::String::UpgradePinnedByUserCount(packagesWithUserPinsSkipped) << std::endl; + } + } + } + + void EnsureMatchesFromSearchResult::operator()(Execution::Context& context) const + { + auto& searchResult = context.Get(); + + Logging::Telemetry().LogSearchResultCount(searchResult.Matches.size()); + + if (searchResult.Matches.size() == 0) + { + Logging::Telemetry().LogNoAppMatch(); + + switch (m_operationType) + { + // These search purposes require a package to be found in the Installed Packages + case OperationType::Export: + case OperationType::List: + case OperationType::Uninstall: + case OperationType::Pin: + case OperationType::Upgrade: + case OperationType::Repair: + context.Reporter.Info() << Resource::String::NoInstalledPackageFound << std::endl; + break; + case OperationType::Completion: + case OperationType::Install: + case OperationType::Search: + case OperationType::Show: + case OperationType::Download: + default: + context.Reporter.Info() << Resource::String::NoPackageFound << std::endl; + break; + } + + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); + } + } + + void EnsureOneMatchFromSearchResult::operator()(Execution::Context& context) const + { + context << + EnsureMatchesFromSearchResult(m_operationType); + + if (!context.IsTerminated()) + { + auto& searchResult = context.Get(); + + bool operationTargetsInstalled = m_operationType == OperationType::Upgrade || m_operationType == OperationType::Uninstall || + m_operationType == OperationType::Repair || m_operationType == OperationType::Export; + + // Try limiting results to highest priority sources + if (searchResult.Matches.size() > 1 && !operationTargetsInstalled && + ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) + { + // Find the set of matches that have the highest priority + std::vector highestPriorityMatches; + std::optional highestPriority; + + for (const auto& match : searchResult.Matches) + { + std::optional priority = GetSourcePriority(match.Package); + + // Optional provides overloads that make empty less than valued and empties equal. + if (highestPriority < priority) + { + // Current priority is higher; reset. + highestPriority = priority; + highestPriorityMatches.clear(); + } + else if (highestPriority == priority) + { + // Priority is equal, add to the list. + } + else + { + // Current priority is lower, ignore the match. + continue; + } + + highestPriorityMatches.emplace_back(match); + } + + if (highestPriorityMatches.size() < searchResult.Matches.size()) + { + AICLI_LOG(CLI, Info, << "Replacing search results with only those from the highest priority [" << (highestPriority ? std::to_string(highestPriority.value()) : "none"s) << "]."); + searchResult.Matches = std::move(highestPriorityMatches); + context.Reporter.Warn() << Resource::String::MultiplePackagesFoundFilteredBySourcePriority << std::endl; + } + } + + if (searchResult.Matches.size() > 1) + { + Logging::Telemetry().LogMultiAppMatch(); + + if (operationTargetsInstalled) + { + context.Reporter.Warn() << Resource::String::MultipleInstalledPackagesFound << std::endl; + context << ReportMultiplePackageFoundResult; + } + else + { + context.Reporter.Warn() << Resource::String::MultiplePackagesFound << std::endl; + context << ReportMultiplePackageFoundResultWithSource; + } + + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND); + } + + std::shared_ptr package = searchResult.Matches.at(0).Package; + Logging::Telemetry().LogAppFound(package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); + + context.Add(std::move(package)); + } + } + + void GetManifestWithVersionFromPackage::operator()(Execution::Context& context) const + { + PackageVersionKey key("", m_version, m_channel); + + std::shared_ptr package = context.Get(); + std::shared_ptr requestedVersion; + auto availableVersions = GetAvailableVersionsForInstalledVersion(package); + + if (m_considerPins) + { + bool isPinned = false; + + PinBehavior pinBehavior; + if (context.Args.Contains(Execution::Args::Type::Force)) + { + // --force ignores any pins + pinBehavior = PinBehavior::IgnorePins; + } + else + { + pinBehavior = context.Args.Contains(Execution::Args::Type::IncludePinned) ? PinBehavior::IncludePinned : PinBehavior::ConsiderPins; + } + + PinningData pinningData{ PinningData::Disposition::ReadOnly }; + auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, GetInstalledVersion(package)); + + // TODO: The logic here will probably have to get more difficult once we support channels + if (Utility::IsEmptyOrWhitespace(m_version) && Utility::IsEmptyOrWhitespace(m_channel)) + { + requestedVersion = evaluator.GetLatestAvailableVersionForPins(availableVersions); + + if (!requestedVersion) + { + // Check whether we didn't find the latest version because it was pinned or because there wasn't one + auto latestVersion = availableVersions->GetLatestVersion(); + if (latestVersion) + { + isPinned = true; + } + } + } + else + { + requestedVersion = availableVersions->GetVersion(key); + isPinned = evaluator.EvaluatePinType(requestedVersion) != PinType::Unknown; + } + + if (isPinned) + { + if (context.Args.Contains(Execution::Args::Type::Force)) + { + AICLI_LOG(CLI, Info, << "Ignoring pin on package due to --force argument"); + } + else + { + AICLI_LOG(CLI, Error, << "The requested package version is unavailable because of a pin"); + context.Reporter.Error() << Resource::String::PackageIsPinned << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED); + } + } + } + else + { + // The simple case: Just look up the requested version + requestedVersion = availableVersions->GetVersion(key); + } + + std::optional manifest; + if (requestedVersion) + { + manifest = requestedVersion->GetManifest(); + } + + if (!manifest) + { + std::ostringstream ssVersionInfo; + if (!m_version.empty()) + { + ssVersionInfo << m_version; + } + if (!m_channel.empty()) + { + ssVersionInfo << '[' << m_channel << ']'; + } + + context.Reporter.Error() << Resource::String::GetManifestResultVersionNotFound(Utility::LocIndView{ ssVersionInfo.str()}) << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); + } + + Logging::Telemetry().LogManifestFields(manifest->Id, manifest->DefaultLocalization.Get(), manifest->Version); + + std::string targetLocale; + if (context.Args.Contains(Execution::Args::Type::Locale)) + { + targetLocale = context.Args.GetArg(Execution::Args::Type::Locale); + } + manifest->ApplyLocale(targetLocale); + + context.Add(std::move(manifest.value())); + context.Add(std::move(requestedVersion)); + } + + void GetManifestFromPackage::operator()(Execution::Context& context) const + { + context << GetManifestWithVersionFromPackage( + context.Args.GetArg(Execution::Args::Type::Version), + context.Args.GetArg(Execution::Args::Type::Channel), + m_considerPins); + } + + void VerifyFile::operator()(Execution::Context& context) const + { + std::filesystem::path path = Utility::ConvertToUTF16(context.Args.GetArg(m_arg)); + + if (!std::filesystem::exists(path)) + { + context.Reporter.Error() << Resource::String::VerifyFileFailedNotExist(Utility::LocIndView{ path.u8string() }) << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); + } + + if (std::filesystem::is_directory(path)) + { + context.Reporter.Error() << Resource::String::VerifyFileFailedIsDirectory(Utility::LocIndView{ path.u8string() }) << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED)); + } + } + + void VerifyPath::operator()(Execution::Context& context) const + { + std::filesystem::path path = Utility::ConvertToUTF16(context.Args.GetArg(m_arg)); + + if (!std::filesystem::exists(path)) + { + context.Reporter.Error() << Resource::String::VerifyPathFailedNotExist(Utility::LocIndView{ path.u8string() }) << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)); + } + } + + void VerifyFileOrUri::operator()(Execution::Context& context) const + { + // Argument requirement is handled elsewhere. + if (!context.Args.Contains(m_arg)) + { + return; + } + + auto path = context.Args.GetArg(m_arg); + + // try uri first + Uri pathAsUri = nullptr; + try + { + pathAsUri = Uri{ Utility::ConvertToUTF16(path) }; + } + catch (...) {} + + if (pathAsUri) + { + if (pathAsUri.Suspicious()) + { + context.Reporter.Error() << Resource::String::UriNotWellFormed(Utility::LocIndView{ path }) << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + // SchemeName() always returns lower case + else if (L"file" == pathAsUri.SchemeName() && !Utility::CaseInsensitiveStartsWith(path, "file:")) + { + // Uri constructor is smart enough to parse an absolute local file path to file uri. + // In this case, we should continue with VerifyFile. + context << VerifyFile(m_arg); + } + else if (std::find(m_supportedSchemes.begin(), m_supportedSchemes.end(), pathAsUri.SchemeName()) != m_supportedSchemes.end()) + { + // Scheme supported. + return; + } + else + { + context.Reporter.Error() << Resource::String::UriSchemeNotSupported(Utility::LocIndView{ path }) << std::endl; + AICLI_TERMINATE_CONTEXT(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + else + { + context << VerifyFile(m_arg); + } + } + + void GetManifestFromArg(Execution::Context& context) + { + Logging::Telemetry().LogIsManifestLocal(true); + + context << + VerifyPath(Execution::Args::Type::Manifest) << + [](Execution::Context& context) + { + Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(Utility::ConvertToUTF16(context.Args.GetArg(Execution::Args::Type::Manifest))); + Logging::Telemetry().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get(), manifest.Version); + + std::string targetLocale; + if (context.Args.Contains(Execution::Args::Type::Locale)) + { + targetLocale = context.Args.GetArg(Execution::Args::Type::Locale); + } + manifest.ApplyLocale(targetLocale); + + context.Add(std::move(manifest)); + }; + } + + void ReportPackageIdentity(Execution::Context& context) + { + auto package = context.Get(); + ReportIdentity(context, {}, Resource::String::ReportIdentityFound, package->GetProperty(PackageProperty::Name), package->GetProperty(PackageProperty::Id)); + } + + void ReportInstalledPackageVersionIdentity(Execution::Context& context) + { + auto package = context.Get(); + auto version = context.Get(); + ReportIdentity(context, {}, Resource::String::ReportIdentityFound, version->GetProperty(PackageVersionProperty::Name), package ? package->GetProperty(PackageProperty::Id) : version->GetProperty(PackageVersionProperty::Id)); + } + + void ReportManifestIdentity(Execution::Context& context) + { + const auto& manifest = context.Get(); + ReportIdentity(context, {}, Resource::String::ReportIdentityFound, manifest.CurrentLocalization.Get(), manifest.Id); + ShowManifestIcon(context, manifest); + } + + void ReportManifestIdentityWithVersion::operator()(Execution::Context& context) const + { + const auto& manifest = context.Get(); + ReportIdentity(context, m_prefix, m_label, manifest.CurrentLocalization.Get(), manifest.Id, manifest.Version, m_level); + ShowManifestIcon(context, manifest); + } + + void SelectInstaller(Execution::Context& context) + { + bool isUpdate = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseUpdate); + bool isRepair = WI_IsFlagSet(context.GetFlags(), Execution::ContextFlag::InstallerExecutionUseRepair); + + IPackageVersion::Metadata installationMetadata; + + if (isUpdate || isRepair) + { + installationMetadata = context.Get()->GetMetadata(); + } + + Manifest::ManifestComparator manifestComparator(GetManifestComparatorOptions(context, installationMetadata)); + auto [installer, inapplicabilities] = manifestComparator.GetPreferredInstaller(context.Get()); + + if (!installer.has_value()) + { + auto onlyInstalledType = std::find(inapplicabilities.begin(), inapplicabilities.end(), Manifest::InapplicabilityFlags::InstalledType); + if (onlyInstalledType != inapplicabilities.end()) + { + if (isRepair) + { + context.Reporter.Info() << Resource::String::RepairDifferentInstallTechnology << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE); + } + else + { + context.Reporter.Info() << Resource::String::UpgradeDifferentInstallTechnology << std::endl; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); + } + } + } + + if (installer.has_value()) + { + Logging::Telemetry().LogSelectedInstaller( + static_cast(installer->Arch), + installer->Url, + Manifest::InstallerTypeToString(installer->EffectiveInstallerType()), + Manifest::ScopeToString(installer->Scope), + installer->Locale); + } + + context.Add(installer); + } + + void EnsureRunningAsAdmin(Execution::Context& context) + { + if (!Runtime::IsRunningAsAdmin()) + { + context.Reporter.Error() << Resource::String::CommandRequiresAdmin; + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN); + } + } + + void EnsureFeatureEnabled::operator()(Execution::Context& context) const + { + if (!Settings::ExperimentalFeature::IsEnabled(m_feature)) + { + context.Reporter.Error() + << Resource::String::FeatureDisabledMessage(Utility::LocIndView{ Settings::ExperimentalFeature::GetFeature(m_feature).JsonName() }) + << std::endl; + AICLI_LOG(CLI, Error, << Settings::ExperimentalFeature::GetFeature(m_feature).Name() << " feature is disabled. Execution cancelled."); + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED); + } + } + + void SearchSourceUsingManifest(Execution::Context& context) + { + const auto& manifest = context.Get(); + auto source = context.Get(); + + // First try search using ProductId or PackageFamilyName + for (const auto& installer : manifest.Installers) + { + SearchRequest searchRequest; + if (!installer.PackageFamilyName.empty()) + { + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, installer.PackageFamilyName)); + } + else if (!installer.ProductCode.empty()) + { + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, installer.ProductCode)); + } + else if (installer.EffectiveInstallerType() == Manifest::InstallerTypeEnum::Portable) + { + const auto& productCode = Utility::MakeSuitablePathPart(manifest.Id + '_' + source.GetIdentifier()); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::CaseInsensitive, Utility::Normalize(productCode))); + } + else if (installer.EffectiveInstallerType() == Manifest::InstallerTypeEnum::Font) + { + // Font Packages match by Package Id first. + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id)); + } + + if (!searchRequest.Inclusions.empty()) + { + auto searchResult = source.Search(searchRequest); + + if (!searchResult.Matches.empty()) + { + context.Add(std::move(searchResult)); + return; + } + } + } + + // If we cannot find a package using PackageFamilyName or ProductId, try manifest Id and Name pair + SearchRequest searchRequest; + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id)); + + // In case there are same Ids from different sources, filter the result using package name + for (const auto& localization : manifest.Localizations) + { + const auto& localizedPackageName = localization.Get(); + if (!localizedPackageName.empty()) + { + searchRequest.Filters.emplace_back(PackageMatchField::Name, MatchType::CaseInsensitive, localizedPackageName); + } + } + + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::CaseInsensitive, manifest.DefaultLocalization.Get())); + + context.Add(source.Search(searchRequest)); + } + + void GetInstalledPackageVersion(Execution::Context& context) + { + std::shared_ptr installed = context.Get()->GetInstalled(); + + if (installed) + { + // TODO: This may need to be expanded dramatically to enable targeting across a variety of dimensions (architecture, etc.) + // Alternatively, if we make it easier to see the fully unique package identifiers, we may avoid that need. + if (context.Args.Contains(Execution::Args::Type::TargetVersion)) + { + Repository::PackageVersionKey versionKey{ "", context.Args.GetArg(Execution::Args::Type::TargetVersion) , "" }; + std::shared_ptr installedVersion = installed->GetVersion(versionKey); + + if (!installedVersion) + { + context.Reporter.Error() << Resource::String::GetManifestResultVersionNotFound(Utility::LocIndView{ versionKey.Version }) << std::endl; + // This error maintains consistency with passing an available version to commands + AICLI_TERMINATE_CONTEXT(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); + } + + context.Add(std::move(installedVersion)); + } + else + { + context.Add(installed->GetLatestVersion()); + } + } + else + { + context.Add(nullptr); + } + } + + void ReportExecutionStage::operator()(Execution::Context& context) const + { + context.SetExecutionStage(m_stage); + } + + void ShowAppVersions(Execution::Context& context) + { + auto versions = GetAllAvailableVersions(context.Get())->GetVersionKeys(); + + Execution::TableOutput<2> table(context.Reporter, { Resource::String::ShowVersion, Resource::String::ShowChannel }); + for (const auto& version : versions) + { + table.OutputLine({ version.Version, version.Channel }); + } + table.Complete(); + } +} + +AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, AppInstaller::CLI::Workflow::WorkflowTask::Func f) +{ + return (context << AppInstaller::CLI::Workflow::WorkflowTask(f)); +} + +AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, const AppInstaller::CLI::Workflow::WorkflowTask& task) +{ + if (!context.IsTerminated() || task.ExecuteAlways()) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (context.ShouldExecuteWorkflowTask(task)) +#endif + { + task.Log(); + task(context); + } + } + return context; +} diff --git a/src/AppInstallerCLICore/Workflows/WorkflowBase.h b/src/AppInstallerCLICore/Workflows/WorkflowBase.h index f08cd39c7a..7d9cba0ae3 100644 --- a/src/AppInstallerCLICore/Workflows/WorkflowBase.h +++ b/src/AppInstallerCLICore/Workflows/WorkflowBase.h @@ -1,459 +1,459 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ExecutionArgs.h" -#include "ExecutionReporter.h" -#include -#include -#include -#include -#include - -#include -#include - -namespace AppInstaller::CLI::Execution -{ - struct Context; -} - -namespace AppInstaller::CLI::Workflow -{ - // Values are ordered in a typical workflow stages - enum class ExecutionStage : uint32_t - { - Initial = 0, - ParseArgs = 1000, - Discovery = 2000, - Download = 3000, - PreExecution = 3500, - Execution = 4000, - PostExecution = 5000, - }; - - enum class OperationType - { - Completion, - Export, - Install, - List, - Pin, - Search, - Show, - Uninstall, - Upgrade, - Download, - Repair, - }; - - // A task in the workflow. - struct WorkflowTask - { - using Func = void (*)(Execution::Context&); - - WorkflowTask(Func f) : m_isFunc(true), m_func(f) {} - WorkflowTask(std::string_view name, bool executeAlways = false) : m_name(name), m_executeAlways(executeAlways) {} - - virtual ~WorkflowTask() = default; - - WorkflowTask(const WorkflowTask&) = default; - WorkflowTask& operator=(const WorkflowTask&) = default; - - WorkflowTask(WorkflowTask&&) = default; - WorkflowTask& operator=(WorkflowTask&&) = default; - - bool operator==(const WorkflowTask& other) const; - - virtual void operator()(Execution::Context& context) const; - - const std::string& GetName() const { return m_name; } - bool IsFunction() const { return m_isFunc; } - Func Function() const { return m_func; } - bool ExecuteAlways() const { return m_executeAlways; } - void Log() const; - - private: - bool m_isFunc = false; - Func m_func = nullptr; - std::string m_name; - bool m_executeAlways = false; - }; - - // Helper to determine installed source to use based on context input. - Repository::PredefinedSource DetermineInstalledSource(const Execution::Context& context); - - // Helper to create authentication arguments from context input. - Authentication::AuthenticationArguments GetAuthenticationArguments(const Execution::Context& context); - - // Helper to report exceptions and return the HRESULT. - // If context is null, no output will be attempted. - HRESULT HandleException(Execution::Context* context, std::exception_ptr exception); - - // Helper to report exceptions and return the HRESULT. - HRESULT HandleException(Execution::Context& context, std::exception_ptr exception); - - // Fills the options from the given context and metadata. - AppInstaller::Manifest::ManifestComparator::Options GetManifestComparatorOptions(const Execution::Context& context, const Repository::IPackageVersion::Metadata& metadata); - - // Creates the source object. - // Required Args: None - // Inputs: None - // Outputs: Source - struct OpenSource : public WorkflowTask - { - OpenSource(bool forDependencies = false) : WorkflowTask("OpenSource"), m_forDependencies(forDependencies) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_forDependencies; - }; - - // Creates a source object for a source specified by name, and adds it to the list of open sources. - // Required Args: None - // Inputs: Sources? - // Outputs: Sources - struct OpenNamedSourceForSources : public WorkflowTask - { - OpenNamedSourceForSources(std::string_view sourceName) : WorkflowTask("OpenNamedSourceForSources"), m_sourceName(sourceName) {} - - void operator()(Execution::Context& context) const override; - - private: - Utility::LocIndView m_sourceName; - }; - - // Creates a source object for a predefined source. - // Required Args: None - // Inputs: None - // Outputs: Source - struct OpenPredefinedSource : public WorkflowTask - { - OpenPredefinedSource(Repository::PredefinedSource source, bool forDependencies = false) : WorkflowTask("OpenPredefinedSource"), m_predefinedSource(source), m_forDependencies(forDependencies) {} - - void operator()(Execution::Context& context) const override; - - private: - Repository::PredefinedSource m_predefinedSource; - bool m_forDependencies; - }; - - // Creates a composite source from the given predefined source and the existing source. - // Required Args: None - // Inputs: Source - // Outputs: Source - struct OpenCompositeSource : public WorkflowTask - { - OpenCompositeSource( - Repository::PredefinedSource source, - bool forDependencies = false, - Repository::CompositeSearchBehavior searchBehavior = Repository::CompositeSearchBehavior::Installed) : - WorkflowTask("OpenCompositeSource"), m_predefinedSource(source), m_forDependencies(forDependencies), m_searchBehavior(searchBehavior) {} - - void operator()(Execution::Context& context) const override; - - private: - Repository::PredefinedSource m_predefinedSource; - bool m_forDependencies; - Repository::CompositeSearchBehavior m_searchBehavior; - }; - - // Performs a search on the source. - // Required Args: None - // Inputs: Source - // Outputs: SearchResult - void SearchSourceForMany(Execution::Context& context); - - // Creates a search request object with the semantics of targeting a single package. - // Required Args: None - // Inputs: Query, search filters (Id, Name, etc.) - // Outputs: SearchRequest - void GetSearchRequestForSingle(Execution::Context& context); - - // Performs a search on the source with the semantics of targeting a single package. - // Required Args: None - // Inputs: Source, SearchRequest - // Outputs: SearchResult - void SearchSourceForSingle(Execution::Context& context); - - // Performs a search on the source with the semantics of targeting many packages, - // but for completion purposes. - // Required Args: None - // Inputs: Source, CompletionData - // Outputs: SearchResult - void SearchSourceForManyCompletion(Execution::Context& context); - - // Performs a search on the source with the semantics of targeting a single package, - // but for completion purposes. - // Required Args: None - // Inputs: Source, CompletionData - // Outputs: SearchResult - void SearchSourceForSingleCompletion(Execution::Context& context); - - // Searches the source for the specific field as a completion. - // Required Args: None - // Inputs: CompletionData, Source - // Outputs: None - struct SearchSourceForCompletionField : public WorkflowTask - { - SearchSourceForCompletionField(Repository::PackageMatchField field) : WorkflowTask("SearchSourceForCompletionField"), m_field(field) {} - - void operator()(Execution::Context& context) const override; - - private: - Repository::PackageMatchField m_field; - }; - - // Outputs the search results. - // Required Args: None - // Inputs: SearchResult - // Outputs: None - void ReportSearchResult(Execution::Context& context); - - // Outputs the search results as the list command would show. - // Required Args: None - // Inputs: SearchResult - // Outputs: None - struct ReportListResult : public WorkflowTask - { - ReportListResult(bool onlyShowUpgrades = false) : WorkflowTask("ReportListResult"), m_onlyShowUpgrades(onlyShowUpgrades) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_onlyShowUpgrades; - }; - - // Handles failures in the SearchResult either by warning or failing. - // Required Args: None - // Inputs: SearchResult - // Outputs: None - void HandleSearchResultFailures(Execution::Context& context); - - // Outputs the search results when multiple packages found but only one expected. - // Required Args: None - // Inputs: SearchResult - // Outputs: None - void ReportMultiplePackageFoundResult(Execution::Context& context); - - // Outputs the search results when multiple packages found but only one expected. - // Required Args: None - // Inputs: SearchResult - // Outputs: None - void ReportMultiplePackageFoundResultWithSource(Execution::Context& context); - - // Ensures that there is at least one result in the search. - // Required Args: bool indicating if the search result is from installed source - // Inputs: SearchResult - // Outputs: None - struct EnsureMatchesFromSearchResult : public WorkflowTask - { - EnsureMatchesFromSearchResult(OperationType operation) : - WorkflowTask("EnsureMatchesFromSearchResult"), m_operationType(operation) {} - - void operator()(Execution::Context& context) const override; - - private: - OperationType m_operationType; - }; - - // Ensures that there is only one result in the search. - // Required Args: bool indicating if the search result is from installed source - // Inputs: SearchResult - // Outputs: Package - struct EnsureOneMatchFromSearchResult : public WorkflowTask - { - EnsureOneMatchFromSearchResult(OperationType operation) : - WorkflowTask("EnsureOneMatchFromSearchResult"), m_operationType(operation) {} - - void operator()(Execution::Context& context) const override; - - private: - OperationType m_operationType; - }; - - // Gets the manifest from package. - // Required Args: Version and channel; can be empty. A flag indicating whether to consider package pins - // Inputs: Package - // Outputs: Manifest, PackageVersion - struct GetManifestWithVersionFromPackage : public WorkflowTask - { - GetManifestWithVersionFromPackage(std::string_view version, std::string_view channel, bool considerPins) : - WorkflowTask("GetManifestWithVersionFromPackage"), m_version(version), m_channel(channel), m_considerPins(considerPins) {} - - GetManifestWithVersionFromPackage(const Utility::VersionAndChannel& versionAndChannel, bool considerPins) : - GetManifestWithVersionFromPackage(versionAndChannel.GetVersion().ToString(), versionAndChannel.GetChannel().ToString(), considerPins) {} - - void operator()(Execution::Context& context) const override; - - private: - std::string_view m_version; - std::string_view m_channel; - bool m_considerPins; - }; - - // Gets the manifest from package. - // Required Args: A value indicating whether to consider pins - // Inputs: Package. Optionally Version and Channel - // Outputs: Manifest, PackageVersion - struct GetManifestFromPackage : public WorkflowTask - { - GetManifestFromPackage(bool considerPins) : WorkflowTask("GetManifestFromPackage"), m_considerPins(considerPins) {} - - void operator()(Execution::Context& context) const override; - - private: - bool m_considerPins; - }; - - // Ensures the file exists and is not a directory. - // Required Args: the one given - // Inputs: None - // Outputs: None - struct VerifyFile : public WorkflowTask - { - VerifyFile(Execution::Args::Type arg) : WorkflowTask("VerifyFile"), m_arg(arg) {} - - void operator()(Execution::Context& context) const override; - - private: - Execution::Args::Type m_arg; - }; - - // Ensures the path exists. - // Required Args: the one given - // Inputs: None - // Outputs: None - struct VerifyPath : public WorkflowTask - { - VerifyPath(Execution::Args::Type arg) : WorkflowTask("VerifyPath"), m_arg(arg) {} - - void operator()(Execution::Context& context) const override; - - private: - Execution::Args::Type m_arg; - }; - - // Ensures the local file exists and is not a directory. Or it's a Uri. Default only https is supported at the moment. - // Required Args: the one given - // Inputs: None - // Outputs: None - struct VerifyFileOrUri : public WorkflowTask - { - VerifyFileOrUri(Execution::Args::Type arg, std::vector supportedSchemes = { L"https" }) : - WorkflowTask("VerifyFileOrUri"), m_arg(arg), m_supportedSchemes(std::move(supportedSchemes)) {} - - void operator()(Execution::Context& context) const override; - - private: - Execution::Args::Type m_arg; - std::vector m_supportedSchemes; - }; - - // Opens the manifest file provided on the command line. - // Required Args: Manifest - // Inputs: None - // Outputs: Manifest - void GetManifestFromArg(Execution::Context& context); - - // Reports the search result's package identity. - // Required Args: None - // Inputs: Package - // Outputs: None - void ReportPackageIdentity(Execution::Context& context); - - // Reports the installed package version identity. - // Required Args: None - // Inputs: InstalledPackageVersion - // Outputs: None - void ReportInstalledPackageVersionIdentity(Execution::Context& context); - - // Reports the manifest's identity. - // Required Args: None - // Inputs: Manifest - // Outputs: None - void ReportManifestIdentity(Execution::Context& context); - - // Reports the manifest's identity with version. - // Required Args: None - // Inputs: Manifest - // Outputs: None - struct ReportManifestIdentityWithVersion : public WorkflowTask - { - ReportManifestIdentityWithVersion(Utility::LocIndView prefix, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : - WorkflowTask("ReportManifestIdentityWithVersion"), m_prefix(prefix), m_level(level) {} - ReportManifestIdentityWithVersion(Resource::StringId label = Resource::String::ReportIdentityFound, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : - WorkflowTask("ReportManifestIdentityWithVersion"), m_label(label), m_level(level) {} - - void operator()(Execution::Context& context) const override; - - private: - Utility::LocIndView m_prefix; - std::optional m_label; - Execution::Reporter::Level m_level; - }; - - // Selects the installer from the manifest, if one is applicable. - // Required Args: None - // Inputs: Manifest - // Outputs: Installer - void SelectInstaller(Execution::Context& context); - - // Ensures that the process is running as admin. - // Required Args: None - // Inputs: None - // Outputs: None - void EnsureRunningAsAdmin(Execution::Context& context); - - // Ensures that the feature is enabled. - // Required Args: the desired feature - // Inputs: None - // Outputs: None - struct EnsureFeatureEnabled : public WorkflowTask - { - EnsureFeatureEnabled(Settings::ExperimentalFeature::Feature feature) : WorkflowTask("EnsureFeatureEnabled"), m_feature(feature) {} - - void operator()(Execution::Context& context) const override; - - private: - Settings::ExperimentalFeature::Feature m_feature; - }; - - // Performs a search on the source with the semantics of targeting packages matching input manifest - // Required Args: None - // Inputs: Source, Manifest - // Outputs: SearchResult - void SearchSourceUsingManifest(Execution::Context& context); - - // Gets the installed package version - // Required Args: None - // Inputs: Package - // Outputs: InstalledPackageVersion - void GetInstalledPackageVersion(Execution::Context& context); - - // Shows all versions for an application. - // Required Args: None - // Inputs: SearchResult [only operates on first match] - // Outputs: None - void ShowAppVersions(Execution::Context& context); - - // Reports execution stage in a workflow - // Required Args: ExecutionStage - // Inputs: ExecutionStage? - // Outputs: ExecutionStage - struct ReportExecutionStage : public WorkflowTask - { - ReportExecutionStage(ExecutionStage stage) : WorkflowTask("ReportExecutionStage"), m_stage(stage) {} - - void operator()(Execution::Context& context) const override; - - private: - ExecutionStage m_stage; - }; -} - -// Passes the context to the function if it has not been terminated; returns the context. -AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, AppInstaller::CLI::Workflow::WorkflowTask::Func f); - -// Passes the context to the task if it has not been terminated; returns the context. -AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, const AppInstaller::CLI::Workflow::WorkflowTask& task); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ExecutionArgs.h" +#include "ExecutionReporter.h" +#include +#include +#include +#include +#include + +#include +#include + +namespace AppInstaller::CLI::Execution +{ + struct Context; +} + +namespace AppInstaller::CLI::Workflow +{ + // Values are ordered in a typical workflow stages + enum class ExecutionStage : uint32_t + { + Initial = 0, + ParseArgs = 1000, + Discovery = 2000, + Download = 3000, + PreExecution = 3500, + Execution = 4000, + PostExecution = 5000, + }; + + enum class OperationType + { + Completion, + Export, + Install, + List, + Pin, + Search, + Show, + Uninstall, + Upgrade, + Download, + Repair, + }; + + // A task in the workflow. + struct WorkflowTask + { + using Func = void (*)(Execution::Context&); + + WorkflowTask(Func f) : m_isFunc(true), m_func(f) {} + WorkflowTask(std::string_view name, bool executeAlways = false) : m_name(name), m_executeAlways(executeAlways) {} + + virtual ~WorkflowTask() = default; + + WorkflowTask(const WorkflowTask&) = default; + WorkflowTask& operator=(const WorkflowTask&) = default; + + WorkflowTask(WorkflowTask&&) = default; + WorkflowTask& operator=(WorkflowTask&&) = default; + + bool operator==(const WorkflowTask& other) const; + + virtual void operator()(Execution::Context& context) const; + + const std::string& GetName() const { return m_name; } + bool IsFunction() const { return m_isFunc; } + Func Function() const { return m_func; } + bool ExecuteAlways() const { return m_executeAlways; } + void Log() const; + + private: + bool m_isFunc = false; + Func m_func = nullptr; + std::string m_name; + bool m_executeAlways = false; + }; + + // Helper to determine installed source to use based on context input. + Repository::PredefinedSource DetermineInstalledSource(const Execution::Context& context); + + // Helper to create authentication arguments from context input. + Authentication::AuthenticationArguments GetAuthenticationArguments(const Execution::Context& context); + + // Helper to report exceptions and return the HRESULT. + // If context is null, no output will be attempted. + HRESULT HandleException(Execution::Context* context, std::exception_ptr exception); + + // Helper to report exceptions and return the HRESULT. + HRESULT HandleException(Execution::Context& context, std::exception_ptr exception); + + // Fills the options from the given context and metadata. + AppInstaller::Manifest::ManifestComparator::Options GetManifestComparatorOptions(const Execution::Context& context, const Repository::IPackageVersion::Metadata& metadata); + + // Creates the source object. + // Required Args: None + // Inputs: None + // Outputs: Source + struct OpenSource : public WorkflowTask + { + OpenSource(bool forDependencies = false) : WorkflowTask("OpenSource"), m_forDependencies(forDependencies) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_forDependencies; + }; + + // Creates a source object for a source specified by name, and adds it to the list of open sources. + // Required Args: None + // Inputs: Sources? + // Outputs: Sources + struct OpenNamedSourceForSources : public WorkflowTask + { + OpenNamedSourceForSources(std::string_view sourceName) : WorkflowTask("OpenNamedSourceForSources"), m_sourceName(sourceName) {} + + void operator()(Execution::Context& context) const override; + + private: + Utility::LocIndView m_sourceName; + }; + + // Creates a source object for a predefined source. + // Required Args: None + // Inputs: None + // Outputs: Source + struct OpenPredefinedSource : public WorkflowTask + { + OpenPredefinedSource(Repository::PredefinedSource source, bool forDependencies = false) : WorkflowTask("OpenPredefinedSource"), m_predefinedSource(source), m_forDependencies(forDependencies) {} + + void operator()(Execution::Context& context) const override; + + private: + Repository::PredefinedSource m_predefinedSource; + bool m_forDependencies; + }; + + // Creates a composite source from the given predefined source and the existing source. + // Required Args: None + // Inputs: Source + // Outputs: Source + struct OpenCompositeSource : public WorkflowTask + { + OpenCompositeSource( + Repository::PredefinedSource source, + bool forDependencies = false, + Repository::CompositeSearchBehavior searchBehavior = Repository::CompositeSearchBehavior::Installed) : + WorkflowTask("OpenCompositeSource"), m_predefinedSource(source), m_forDependencies(forDependencies), m_searchBehavior(searchBehavior) {} + + void operator()(Execution::Context& context) const override; + + private: + Repository::PredefinedSource m_predefinedSource; + bool m_forDependencies; + Repository::CompositeSearchBehavior m_searchBehavior; + }; + + // Performs a search on the source. + // Required Args: None + // Inputs: Source + // Outputs: SearchResult + void SearchSourceForMany(Execution::Context& context); + + // Creates a search request object with the semantics of targeting a single package. + // Required Args: None + // Inputs: Query, search filters (Id, Name, etc.) + // Outputs: SearchRequest + void GetSearchRequestForSingle(Execution::Context& context); + + // Performs a search on the source with the semantics of targeting a single package. + // Required Args: None + // Inputs: Source, SearchRequest + // Outputs: SearchResult + void SearchSourceForSingle(Execution::Context& context); + + // Performs a search on the source with the semantics of targeting many packages, + // but for completion purposes. + // Required Args: None + // Inputs: Source, CompletionData + // Outputs: SearchResult + void SearchSourceForManyCompletion(Execution::Context& context); + + // Performs a search on the source with the semantics of targeting a single package, + // but for completion purposes. + // Required Args: None + // Inputs: Source, CompletionData + // Outputs: SearchResult + void SearchSourceForSingleCompletion(Execution::Context& context); + + // Searches the source for the specific field as a completion. + // Required Args: None + // Inputs: CompletionData, Source + // Outputs: None + struct SearchSourceForCompletionField : public WorkflowTask + { + SearchSourceForCompletionField(Repository::PackageMatchField field) : WorkflowTask("SearchSourceForCompletionField"), m_field(field) {} + + void operator()(Execution::Context& context) const override; + + private: + Repository::PackageMatchField m_field; + }; + + // Outputs the search results. + // Required Args: None + // Inputs: SearchResult + // Outputs: None + void ReportSearchResult(Execution::Context& context); + + // Outputs the search results as the list command would show. + // Required Args: None + // Inputs: SearchResult + // Outputs: None + struct ReportListResult : public WorkflowTask + { + ReportListResult(bool onlyShowUpgrades = false) : WorkflowTask("ReportListResult"), m_onlyShowUpgrades(onlyShowUpgrades) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_onlyShowUpgrades; + }; + + // Handles failures in the SearchResult either by warning or failing. + // Required Args: None + // Inputs: SearchResult + // Outputs: None + void HandleSearchResultFailures(Execution::Context& context); + + // Outputs the search results when multiple packages found but only one expected. + // Required Args: None + // Inputs: SearchResult + // Outputs: None + void ReportMultiplePackageFoundResult(Execution::Context& context); + + // Outputs the search results when multiple packages found but only one expected. + // Required Args: None + // Inputs: SearchResult + // Outputs: None + void ReportMultiplePackageFoundResultWithSource(Execution::Context& context); + + // Ensures that there is at least one result in the search. + // Required Args: bool indicating if the search result is from installed source + // Inputs: SearchResult + // Outputs: None + struct EnsureMatchesFromSearchResult : public WorkflowTask + { + EnsureMatchesFromSearchResult(OperationType operation) : + WorkflowTask("EnsureMatchesFromSearchResult"), m_operationType(operation) {} + + void operator()(Execution::Context& context) const override; + + private: + OperationType m_operationType; + }; + + // Ensures that there is only one result in the search. + // Required Args: bool indicating if the search result is from installed source + // Inputs: SearchResult + // Outputs: Package + struct EnsureOneMatchFromSearchResult : public WorkflowTask + { + EnsureOneMatchFromSearchResult(OperationType operation) : + WorkflowTask("EnsureOneMatchFromSearchResult"), m_operationType(operation) {} + + void operator()(Execution::Context& context) const override; + + private: + OperationType m_operationType; + }; + + // Gets the manifest from package. + // Required Args: Version and channel; can be empty. A flag indicating whether to consider package pins + // Inputs: Package + // Outputs: Manifest, PackageVersion + struct GetManifestWithVersionFromPackage : public WorkflowTask + { + GetManifestWithVersionFromPackage(std::string_view version, std::string_view channel, bool considerPins) : + WorkflowTask("GetManifestWithVersionFromPackage"), m_version(version), m_channel(channel), m_considerPins(considerPins) {} + + GetManifestWithVersionFromPackage(const Utility::VersionAndChannel& versionAndChannel, bool considerPins) : + GetManifestWithVersionFromPackage(versionAndChannel.GetVersion().ToString(), versionAndChannel.GetChannel().ToString(), considerPins) {} + + void operator()(Execution::Context& context) const override; + + private: + std::string_view m_version; + std::string_view m_channel; + bool m_considerPins; + }; + + // Gets the manifest from package. + // Required Args: A value indicating whether to consider pins + // Inputs: Package. Optionally Version and Channel + // Outputs: Manifest, PackageVersion + struct GetManifestFromPackage : public WorkflowTask + { + GetManifestFromPackage(bool considerPins) : WorkflowTask("GetManifestFromPackage"), m_considerPins(considerPins) {} + + void operator()(Execution::Context& context) const override; + + private: + bool m_considerPins; + }; + + // Ensures the file exists and is not a directory. + // Required Args: the one given + // Inputs: None + // Outputs: None + struct VerifyFile : public WorkflowTask + { + VerifyFile(Execution::Args::Type arg) : WorkflowTask("VerifyFile"), m_arg(arg) {} + + void operator()(Execution::Context& context) const override; + + private: + Execution::Args::Type m_arg; + }; + + // Ensures the path exists. + // Required Args: the one given + // Inputs: None + // Outputs: None + struct VerifyPath : public WorkflowTask + { + VerifyPath(Execution::Args::Type arg) : WorkflowTask("VerifyPath"), m_arg(arg) {} + + void operator()(Execution::Context& context) const override; + + private: + Execution::Args::Type m_arg; + }; + + // Ensures the local file exists and is not a directory. Or it's a Uri. Default only https is supported at the moment. + // Required Args: the one given + // Inputs: None + // Outputs: None + struct VerifyFileOrUri : public WorkflowTask + { + VerifyFileOrUri(Execution::Args::Type arg, std::vector supportedSchemes = { L"https" }) : + WorkflowTask("VerifyFileOrUri"), m_arg(arg), m_supportedSchemes(std::move(supportedSchemes)) {} + + void operator()(Execution::Context& context) const override; + + private: + Execution::Args::Type m_arg; + std::vector m_supportedSchemes; + }; + + // Opens the manifest file provided on the command line. + // Required Args: Manifest + // Inputs: None + // Outputs: Manifest + void GetManifestFromArg(Execution::Context& context); + + // Reports the search result's package identity. + // Required Args: None + // Inputs: Package + // Outputs: None + void ReportPackageIdentity(Execution::Context& context); + + // Reports the installed package version identity. + // Required Args: None + // Inputs: InstalledPackageVersion + // Outputs: None + void ReportInstalledPackageVersionIdentity(Execution::Context& context); + + // Reports the manifest's identity. + // Required Args: None + // Inputs: Manifest + // Outputs: None + void ReportManifestIdentity(Execution::Context& context); + + // Reports the manifest's identity with version. + // Required Args: None + // Inputs: Manifest + // Outputs: None + struct ReportManifestIdentityWithVersion : public WorkflowTask + { + ReportManifestIdentityWithVersion(Utility::LocIndView prefix, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : + WorkflowTask("ReportManifestIdentityWithVersion"), m_prefix(prefix), m_level(level) {} + ReportManifestIdentityWithVersion(Resource::StringId label = Resource::String::ReportIdentityFound, Execution::Reporter::Level level = Execution::Reporter::Level::Info) : + WorkflowTask("ReportManifestIdentityWithVersion"), m_label(label), m_level(level) {} + + void operator()(Execution::Context& context) const override; + + private: + Utility::LocIndView m_prefix; + std::optional m_label; + Execution::Reporter::Level m_level; + }; + + // Selects the installer from the manifest, if one is applicable. + // Required Args: None + // Inputs: Manifest + // Outputs: Installer + void SelectInstaller(Execution::Context& context); + + // Ensures that the process is running as admin. + // Required Args: None + // Inputs: None + // Outputs: None + void EnsureRunningAsAdmin(Execution::Context& context); + + // Ensures that the feature is enabled. + // Required Args: the desired feature + // Inputs: None + // Outputs: None + struct EnsureFeatureEnabled : public WorkflowTask + { + EnsureFeatureEnabled(Settings::ExperimentalFeature::Feature feature) : WorkflowTask("EnsureFeatureEnabled"), m_feature(feature) {} + + void operator()(Execution::Context& context) const override; + + private: + Settings::ExperimentalFeature::Feature m_feature; + }; + + // Performs a search on the source with the semantics of targeting packages matching input manifest + // Required Args: None + // Inputs: Source, Manifest + // Outputs: SearchResult + void SearchSourceUsingManifest(Execution::Context& context); + + // Gets the installed package version + // Required Args: None + // Inputs: Package + // Outputs: InstalledPackageVersion + void GetInstalledPackageVersion(Execution::Context& context); + + // Shows all versions for an application. + // Required Args: None + // Inputs: SearchResult [only operates on first match] + // Outputs: None + void ShowAppVersions(Execution::Context& context); + + // Reports execution stage in a workflow + // Required Args: ExecutionStage + // Inputs: ExecutionStage? + // Outputs: ExecutionStage + struct ReportExecutionStage : public WorkflowTask + { + ReportExecutionStage(ExecutionStage stage) : WorkflowTask("ReportExecutionStage"), m_stage(stage) {} + + void operator()(Execution::Context& context) const override; + + private: + ExecutionStage m_stage; + }; +} + +// Passes the context to the function if it has not been terminated; returns the context. +AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, AppInstaller::CLI::Workflow::WorkflowTask::Func f); + +// Passes the context to the task if it has not been terminated; returns the context. +AppInstaller::CLI::Execution::Context& operator<<(AppInstaller::CLI::Execution::Context& context, const AppInstaller::CLI::Workflow::WorkflowTask& task); diff --git a/src/AppInstallerCLICore/packages.config b/src/AppInstallerCLICore/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/AppInstallerCLICore/packages.config +++ b/src/AppInstallerCLICore/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/AppInstallerCLICore/pch.h b/src/AppInstallerCLICore/pch.h index 0d10bef71f..4c7dcadc31 100644 --- a/src/AppInstallerCLICore/pch.h +++ b/src/AppInstallerCLICore/pch.h @@ -1,63 +1,63 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#define NOMINMAX -#include -#include -#include -#include -#include - -#pragma warning( push ) -#pragma warning ( disable : 4458 4100 6031 4702 26439 ) -#include -#include -#include -#include -#pragma warning( pop ) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#pragma warning( push ) -#pragma warning ( disable : 6001 6285 6340 6388 26451 ) -#include -#include -#include -#include -#include -#include -#pragma warning( pop ) - -#include -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define NOMINMAX +#include +#include +#include +#include +#include + +#pragma warning( push ) +#pragma warning ( disable : 4458 4100 6031 4702 26439 ) +#include +#include +#include +#include +#pragma warning( pop ) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#pragma warning( push ) +#pragma warning ( disable : 6001 6285 6340 6388 26451 ) +#include +#include +#include +#include +#include +#include +#pragma warning( pop ) + +#include +#include +#include diff --git a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj index 7377357811..5221e84792 100644 --- a/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj +++ b/src/AppInstallerCLIE2ETests/AppInstallerCLIE2ETests.csproj @@ -1,84 +1,84 @@ - - - - net8.0-windows - $(SolutionDir)$(Platform)\$(Configuration)\AppInstallerCLIE2ETests\ - false - x64;x86 - Library - $(OutDir)\AppInstallerCLIE2ETests.xml - false - - - - - - 1591 - - - - true - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - 10.0.26100.0 - - - - - PreserveNewest - - - - - - PreserveNewest - - - - - - - - - False - - - False - - - - - False - - - - - - PreserveNewest - - - - - - - - - - + + + + net8.0-windows + $(SolutionDir)$(Platform)\$(Configuration)\AppInstallerCLIE2ETests\ + false + x64;x86 + Library + $(OutDir)\AppInstallerCLIE2ETests.xml + false + + + + + + 1591 + + + + true + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + 10.0.26100.0 + + + + + PreserveNewest + + + + + + PreserveNewest + + + + + + + + + False + + + False + + + + + False + + + + + + PreserveNewest + + + + + + + + + + diff --git a/src/AppInstallerCLIE2ETests/AppShutdownTests.cs b/src/AppInstallerCLIE2ETests/AppShutdownTests.cs index 7c83c13549..be143201be 100644 --- a/src/AppInstallerCLIE2ETests/AppShutdownTests.cs +++ b/src/AppInstallerCLIE2ETests/AppShutdownTests.cs @@ -1,133 +1,133 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System; - using System.Diagnostics; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using System.Xml; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// `test appshutdown` command tests. - /// - public class AppShutdownTests - { - /// - /// Runs winget test appshutdown and register the application to force a WM_QUERYENDSESSION message. - /// - [Test] - [Ignore("This test relied on a signal to terminate that was determined to be problematic. We may need OS fixes to test it when elevated.")] - public void RegisterApplicationTest() - { - if (!TestSetup.Parameters.PackagedContext) - { - Assert.Ignore("Not packaged context."); - } - - if (!TestCommon.ExecutingAsAdministrator && TestCommon.IsCIEnvironment) - { - Assert.Ignore("This test won't work on Window Server as non-admin"); - } - - if (!Environment.Is64BitProcess) - { - // My guess is that HAM terminates us faster after the CTRL-C on x86... - Assert.Ignore("This test is flaky when run as x86."); - } - - if (string.IsNullOrEmpty(TestSetup.Parameters.AICLIPackagePath)) - { - throw new NullReferenceException("AICLIPackagePath"); - } - - var appxManifest = Path.Combine(TestSetup.Parameters.AICLIPackagePath, "AppxManifest.xml"); - if (!File.Exists(appxManifest)) - { - throw new FileNotFoundException(appxManifest); - } - - // In order to registering the application we need a higher version number and pass the force app shutdown flag. - // Doing it the long way. - var xmlDoc = new XmlDocument(); - XmlNamespaceManager namespaces = new XmlNamespaceManager(xmlDoc.NameTable); - namespaces.AddNamespace("n", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); - xmlDoc.Load(appxManifest); - var identityNode = xmlDoc.SelectSingleNode("/n:Package/n:Identity", namespaces); - if (identityNode == null) - { - throw new NullReferenceException("Identity node"); - } - - var versionAttribute = identityNode.Attributes["Version"]; - if (versionAttribute == null) - { - throw new NullReferenceException("Version attribute"); - } - - var ogVersion = new Version(versionAttribute.Value); - var newVersion = new Version(ogVersion.Major, ogVersion.Minor, ogVersion.Build, ogVersion.Revision + 1); - versionAttribute.Value = newVersion.ToString(); - xmlDoc.Save(appxManifest); - - // This just waits for the app termination event. - var testCmdTask = new Task(() => - { - return TestCommon.RunAICLICommand("test", "appshutdown --verbose", timeOut: 300000, throwOnTimeout: false); - }); - - // Register the app with the updated version. - var registerTask = new Task(() => - { - return TestCommon.InstallMsixRegister(TestSetup.Parameters.AICLIPackagePath, true, false); - }); - - // Give it a little time. - testCmdTask.Start(); - Thread.Sleep(30000); - registerTask.Start(); - - Task.WaitAll(new Task[] { testCmdTask, registerTask }, 360000); - - // The ctrl-c command terminates the batch file before the exit code file gets created. - // Look for the output. - Assert.True(testCmdTask.Result.StdOut.Contains("Succeeded waiting for app shutdown event")); - } - - /// - /// Runs winget test can-unload-now expecting that it cannot be unloaded. - /// - [Test] - public void CanUnloadNowTest() - { - var result = TestCommon.RunAICLICommand("test", "can-unload-now --verbose"); - - var lines = result.StdOut.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); - - Assert.AreEqual(5, lines.Length); - Assert.True(lines[0].Contains("Internal objects:")); - Assert.False(lines[0].Contains("Internal objects: 0")); - Assert.True(lines[1].Contains("External objects: 0")); - Assert.True(lines[2].Contains("DllCanUnloadNow")); - Assert.True(lines[3].Contains("Internal objects: 0")); - Assert.True(lines[4].Contains("External objects: 0")); - } - - /// - /// Runs winget test term-signal-handler to check for proper thread termination. - /// - [Test] - public void TermSignalHandler() - { - var result = TestCommon.RunAICLICommand("test", "term-signal-handler --verbose"); - Assert.True(result.StdOut.Contains("Got a window handle")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using System.Xml; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `test appshutdown` command tests. + /// + public class AppShutdownTests + { + /// + /// Runs winget test appshutdown and register the application to force a WM_QUERYENDSESSION message. + /// + [Test] + [Ignore("This test relied on a signal to terminate that was determined to be problematic. We may need OS fixes to test it when elevated.")] + public void RegisterApplicationTest() + { + if (!TestSetup.Parameters.PackagedContext) + { + Assert.Ignore("Not packaged context."); + } + + if (!TestCommon.ExecutingAsAdministrator && TestCommon.IsCIEnvironment) + { + Assert.Ignore("This test won't work on Window Server as non-admin"); + } + + if (!Environment.Is64BitProcess) + { + // My guess is that HAM terminates us faster after the CTRL-C on x86... + Assert.Ignore("This test is flaky when run as x86."); + } + + if (string.IsNullOrEmpty(TestSetup.Parameters.AICLIPackagePath)) + { + throw new NullReferenceException("AICLIPackagePath"); + } + + var appxManifest = Path.Combine(TestSetup.Parameters.AICLIPackagePath, "AppxManifest.xml"); + if (!File.Exists(appxManifest)) + { + throw new FileNotFoundException(appxManifest); + } + + // In order to registering the application we need a higher version number and pass the force app shutdown flag. + // Doing it the long way. + var xmlDoc = new XmlDocument(); + XmlNamespaceManager namespaces = new XmlNamespaceManager(xmlDoc.NameTable); + namespaces.AddNamespace("n", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); + xmlDoc.Load(appxManifest); + var identityNode = xmlDoc.SelectSingleNode("/n:Package/n:Identity", namespaces); + if (identityNode == null) + { + throw new NullReferenceException("Identity node"); + } + + var versionAttribute = identityNode.Attributes["Version"]; + if (versionAttribute == null) + { + throw new NullReferenceException("Version attribute"); + } + + var ogVersion = new Version(versionAttribute.Value); + var newVersion = new Version(ogVersion.Major, ogVersion.Minor, ogVersion.Build, ogVersion.Revision + 1); + versionAttribute.Value = newVersion.ToString(); + xmlDoc.Save(appxManifest); + + // This just waits for the app termination event. + var testCmdTask = new Task(() => + { + return TestCommon.RunAICLICommand("test", "appshutdown --verbose", timeOut: 300000, throwOnTimeout: false); + }); + + // Register the app with the updated version. + var registerTask = new Task(() => + { + return TestCommon.InstallMsixRegister(TestSetup.Parameters.AICLIPackagePath, true, false); + }); + + // Give it a little time. + testCmdTask.Start(); + Thread.Sleep(30000); + registerTask.Start(); + + Task.WaitAll(new Task[] { testCmdTask, registerTask }, 360000); + + // The ctrl-c command terminates the batch file before the exit code file gets created. + // Look for the output. + Assert.True(testCmdTask.Result.StdOut.Contains("Succeeded waiting for app shutdown event")); + } + + /// + /// Runs winget test can-unload-now expecting that it cannot be unloaded. + /// + [Test] + public void CanUnloadNowTest() + { + var result = TestCommon.RunAICLICommand("test", "can-unload-now --verbose"); + + var lines = result.StdOut.Split('\n', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries); + + Assert.AreEqual(5, lines.Length); + Assert.True(lines[0].Contains("Internal objects:")); + Assert.False(lines[0].Contains("Internal objects: 0")); + Assert.True(lines[1].Contains("External objects: 0")); + Assert.True(lines[2].Contains("DllCanUnloadNow")); + Assert.True(lines[3].Contains("Internal objects: 0")); + Assert.True(lines[4].Contains("External objects: 0")); + } + + /// + /// Runs winget test term-signal-handler to check for proper thread termination. + /// + [Test] + public void TermSignalHandler() + { + var result = TestCommon.RunAICLICommand("test", "term-signal-handler --verbose"); + Assert.True(result.StdOut.Contains("Got a window handle")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/BaseCommand.cs b/src/AppInstallerCLIE2ETests/BaseCommand.cs index faa07c2dd0..d96fee18e3 100644 --- a/src/AppInstallerCLIE2ETests/BaseCommand.cs +++ b/src/AppInstallerCLIE2ETests/BaseCommand.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Base command. - /// - public class BaseCommand - { - /// - /// Set up. - /// - [OneTimeSetUp] - public void BaseSetup() - { - this.ResetTestSource(); - } - - /// - /// Tear down. - /// - [OneTimeTearDown] - public void BaseTeardown() - { - TestCommon.TearDownTestSource(); - } - - /// - /// Reset test source. - /// - /// Use group policy from test source. - public void ResetTestSource(bool useGroupPolicyForTestSource = false) - { - // TODO: If/when cert pinning is implemented on the packaged index source, useGroupPolicyForTestSource should be set to default true - // to enable testing it by default. Until then, leaving this here... - TestCommon.SetupTestSource(useGroupPolicyForTestSource); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Base command. + /// + public class BaseCommand + { + /// + /// Set up. + /// + [OneTimeSetUp] + public void BaseSetup() + { + this.ResetTestSource(); + } + + /// + /// Tear down. + /// + [OneTimeTearDown] + public void BaseTeardown() + { + TestCommon.TearDownTestSource(); + } + + /// + /// Reset test source. + /// + /// Use group policy from test source. + public void ResetTestSource(bool useGroupPolicyForTestSource = false) + { + // TODO: If/when cert pinning is implemented on the packaged index source, useGroupPolicyForTestSource should be set to default true + // to enable testing it by default. Until then, leaving this here... + TestCommon.SetupTestSource(useGroupPolicyForTestSource); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ConfigureCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureCommand.cs index f90da50627..521f4ace0b 100644 --- a/src/AppInstallerCLIE2ETests/ConfigureCommand.cs +++ b/src/AppInstallerCLIE2ETests/ConfigureCommand.cs @@ -1,426 +1,426 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System; - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// `Configure` command tests. - /// - public class ConfigureCommand - { - private const string CommandAndAgreementsAndVerbose = "configure --accept-configuration-agreements --verbose"; - - /// - /// Ensures that the test resources manifests are present. - /// - public static void EnsureTestResourcePresence() - { - DSCv3ResourceTestBase.EnsureTestResourcePresence(); - } - - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - this.DeleteResourceArtifacts(); - EnsureTestResourcePresence(); - TestCommon.SetupTestSource(false); - } - - /// - /// Teardown done once after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTeardown() - { - this.DeleteResourceArtifacts(); - TestCommon.TearDownTestSource(); - } - - /// - /// Simple test to confirm that a resource without a module specified can be discovered in the PSGallery. - /// Intentionally has no settings to force a failure, but only after acquiring the module. - /// - [Test] - [Ignore("PS Gallery tests are unreliable.")] - public void ConfigureFromGallery() - { - TestCommon.EnsureModuleState(Constants.GalleryTestModuleName, present: false); - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoModule_NoSettings.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); - Assert.True(result.StdOut.Contains("The configuration unit failed while attempting to test the current system state.")); - } - - /// - /// Simple test to confirm that a resource with a module specified can be discovered in a local repository that doesn't support resource discovery. - /// - [Test] - public void ConfigureFromTestRepo() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); - FileAssert.Exists(targetFilePath); - Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); - - Assert.True(Directory.Exists( - Path.Combine( - TestCommon.GetExpectedModulePath(TestCommon.TestModuleLocation.Default), - Constants.SimpleTestModuleName))); - } - - /// - /// Simple test to confirm that the module was installed to the location specified in the DefaultModuleRoot settings. - /// - [Test] - public void ConfigureFromTestRepo_DefaultModuleRootSetting() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - string moduleTestDir = TestCommon.GetRandomTestDir(); - WinGetSettingsHelper.ConfigureConfigureBehavior(Constants.DefaultModuleRoot, moduleTestDir); - - string args = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo_Location.yml"); - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, args); - - WinGetSettingsHelper.ConfigureConfigureBehavior(Constants.DefaultModuleRoot, string.Empty); - bool moduleExists = Directory.Exists(Path.Combine(moduleTestDir, Constants.SimpleTestModuleName)); - if (moduleExists) - { - // Clean test directory to avoid impacting other tests. - Directory.Delete(moduleTestDir, true); - } - - Assert.AreEqual(0, result.ExitCode); - Assert.True(moduleExists); - } - - /// - /// Simple test to confirm that the module was installed in the right location. - /// - /// Location to pass. - [TestCase(TestCommon.TestModuleLocation.CurrentUser)] - [TestCase(TestCommon.TestModuleLocation.AllUsers)] - [TestCase(TestCommon.TestModuleLocation.WinGetModulePath)] - [TestCase(TestCommon.TestModuleLocation.Custom)] - [TestCase(TestCommon.TestModuleLocation.Default)] - public void ConfigureFromTestRepo_Location(TestCommon.TestModuleLocation location) - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - - string args = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo_Location.yml"); - if (location == TestCommon.TestModuleLocation.CurrentUser) - { - args += " --module-path currentuser"; - } - else if (location == TestCommon.TestModuleLocation.AllUsers) - { - args += " --module-path allusers"; - } - else if (location == TestCommon.TestModuleLocation.Default) - { - args += " --module-path default"; - } - else if (location == TestCommon.TestModuleLocation.Custom) - { - args += " --module-path " + TestCommon.GetExpectedModulePath(location); - } - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, args); - Assert.AreEqual(0, result.ExitCode); - - Assert.True(Directory.Exists(Path.Combine( - TestCommon.GetExpectedModulePath(location), - Constants.SimpleTestModuleName))); - } - - /// - /// One resource fails, but the other is not dependent and should be executed. - /// - [Test] - public void IndependentResourceWithSingleFailure() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\IndependentResources_OneFailure.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\IndependentResources_OneFailure.txt"); - FileAssert.Exists(targetFilePath); - Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); - } - - /// - /// One resource fails, and the dependent resource should not be executed. - /// - [Test] - public void DependentResourceWithFailure() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\DependentResources_Failure.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\DependentResources_Failure.txt"); - FileAssert.DoesNotExist(targetFilePath); - } - - /// - /// The configuration server unexpectedly exits. Winget should continue to operate properly. - /// - [Test] - public void ConfigServerUnexpectedExit() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\ConfigServerUnexpectedExit.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\ConfigServerUnexpectedExit.txt"); - FileAssert.DoesNotExist(targetFilePath); - } - - /// - /// Resource name case-insensitive test. - /// - [Test] - public void ResourceCaseInsensitive() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\ResourceCaseInsensitive.yml")); - Assert.AreEqual(0, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\ResourceCaseInsensitive.txt"); - FileAssert.Exists(targetFilePath); - Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); - } - - /// - /// Simple test to configure from an https configuration file. - /// - [Test] - public void ConfigureFromHttpsConfigurationFile() - { - string args = $"{Constants.TestSourceUrl}/TestData/Configuration/Configure_TestRepo_Location.yml"; - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, args); - Assert.AreEqual(0, result.ExitCode); - } - - /// - /// Runs a configuration, then changes the state and runs it again from history. - /// - [Test] - public void ConfigureFromHistory() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); - FileAssert.Exists(targetFilePath); - Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); - - File.WriteAllText(targetFilePath, "Changed contents!"); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor("Configure_TestRepo.yml"); - result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, $"-h {guid}"); - Assert.AreEqual(0, result.ExitCode); - - FileAssert.Exists(targetFilePath); - Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); - } - - /// - /// Specifies the module path to an "elevated" server. - /// - [Test] - public void SpecifyModulePathToHighIntegrityServer() - { - string configFile = TestCommon.GetTestDataFile("Configuration\\GetPSModulePath.yml"); - string testDirectory = TestCommon.GetRandomTestDir(); - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, $"{configFile} --module-path \"{testDirectory}\""); - Assert.AreEqual(0, result.ExitCode); - - string testFile = Path.Join(TestCommon.GetTestDataFile("Configuration"), "PSModulePath.txt"); - Assert.True(File.Exists(testFile)); - string testFileContents = File.ReadAllText(testFile); - Assert.True(testFileContents.StartsWith(testDirectory)); - } - - /// - /// Runs a DSCv3 configuration, then changes the state and runs it again from history. - /// - [Test] - public void ConfigureThroughHistory_DSCv3() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")); - Assert.AreEqual(0, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.txt"); - FileAssert.Exists(targetFilePath); - Assert.AreEqual("DSCv3 Contents!", File.ReadAllText(targetFilePath)); - - File.WriteAllText(targetFilePath, "Changed contents!"); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor("ShowDetails_DSCv3.yml"); - result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, $"-h {guid}"); - Assert.AreEqual(0, result.ExitCode); - - FileAssert.Exists(targetFilePath); - Assert.AreEqual("DSCv3 Contents!", File.ReadAllText(targetFilePath)); - } - - /// - /// Ensures that the test file resource schema function works. - /// - [Test] - public void TestFileResourceSchema() - { - var result = TestCommon.RunAICLICommand("dscv3 test-file", "--schema"); - Assert.AreEqual(0, result.ExitCode); - - var lines = result.StdOut.Split("\r\n", StringSplitOptions.RemoveEmptyEntries); - Assert.AreEqual(1, lines.Length); - } - - /// - /// Export all with specific package id. - /// - [Test] - public void DSCv3_Export() - { - // Reset state - var result = TestCommon.RunAICLICommand("dscv3 test-json", "--delete"); - Assert.AreEqual(0, result.ExitCode); - - // Configure properties - string propertyName1 = "prop1"; - string propertyName2 = "prop2"; - string propertyValue1 = "val1"; - string propertyValue2 = "val2"; - - string propertySetFormatString = "{{ \"property\": \"{0}\", \"value\": \"{1}\" }}"; - - result = TestCommon.RunAICLICommand("dscv3 test-json", "--set", string.Format(propertySetFormatString, propertyName1, propertyValue1)); - Assert.AreEqual(0, result.ExitCode); - - result = TestCommon.RunAICLICommand("dscv3 test-json", "--set", string.Format(propertySetFormatString, propertyName2, propertyValue2)); - Assert.AreEqual(0, result.ExitCode); - - // Export - var exportDir = TestCommon.GetRandomTestDir(); - var exportFile = Path.Combine(exportDir, "exported.yml"); - - result = TestCommon.RunAICLICommand("test config-export-units", $"-o {exportFile} --resource Microsoft.WinGet.Dev/TestJSON --verbose"); - Assert.AreEqual(0, result.ExitCode); - - Assert.True(File.Exists(exportFile)); - string exportText = File.ReadAllText(exportFile); - Assert.True(exportText.Contains("Microsoft.WinGet.Dev/TestJSON")); - Assert.True(exportText.Contains(propertyName1)); - Assert.True(exportText.Contains(propertyName2)); - Assert.True(exportText.Contains(propertyValue1)); - Assert.True(exportText.Contains(propertyValue2)); - } - - /// - /// Simple test to confirm that a resource with a module specified can be discovered in a local repository that doesn't support resource discovery. - /// - [Test] - public void ConfigureFromTestRepo_DSCv3() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: true, repository: Constants.TestRepoName); - this.DeleteResourceArtifacts(); - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo_DSCv3.yml"), timeOut: 300000); - Assert.AreEqual(0, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); - FileAssert.Exists(targetFilePath); - Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); - } - - /// - /// Find unit processors tests. - /// - [Test] - public void ConfigureFindUnitProcessors() - { - // Find all unit processors. - var result = TestCommon.RunAICLICommand("test config-find-unit-processors", string.Empty, timeOut: 300000); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains("Microsoft/OSInfo")); - - // Setup TestExeInstaller with dsc resources. - var installDir = TestCommon.GetRandomTestDir(); - result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --override \"/InstallDir {installDir} /GenerateDscResourceFiles\""); - Assert.AreEqual(0, result.ExitCode); - - // Find unit processors filtering to install location. - result = TestCommon.RunAICLICommand("test config-find-unit-processors", $"-l {installDir}"); - Assert.AreEqual(0, result.ExitCode); - Assert.False(result.StdOut.Contains("Microsoft/OSInfo")); - Assert.True(result.StdOut.Contains("AppInstallerTest/TestResource")); - - // Find unit processors filtering to unknown location. - var unknownDir = TestCommon.GetRandomTestDir(); - result = TestCommon.RunAICLICommand("test config-find-unit-processors", $"-l {unknownDir}"); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains("No unit processors found.")); - - // Clean up - result = TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(0, result.ExitCode); - } - - /// - /// RunCommandOnSet test. - /// - [Test] - public void RunCommandOnSetResourceTest() - { - var testDir = TestCommon.GetRandomTestDir(); - var testConfigFile = Path.Combine(testDir, "RunCommandOnSet.yml"); - File.Copy(TestCommon.GetTestDataFile("Configuration\\RunCommandOnSet.yml"), testConfigFile); - - var content = File.ReadAllText(testConfigFile); - content = content.Replace("", testDir); - File.WriteAllText(testConfigFile, content); - - var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, testConfigFile, timeOut: 300000); - Assert.AreEqual(0, result.ExitCode); - - // Verify test file created. - string targetFilePath = Path.Combine(testDir, "TestFile.txt"); - FileAssert.Exists(targetFilePath); - string testContent = File.ReadAllText(targetFilePath); - Assert.True(testContent.Contains("TestContent")); - } - - private void DeleteResourceArtifacts() - { - // Delete all .txt files in the test directory; they are placed there by the tests - foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) - { - File.Delete(file); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure` command tests. + /// + public class ConfigureCommand + { + private const string CommandAndAgreementsAndVerbose = "configure --accept-configuration-agreements --verbose"; + + /// + /// Ensures that the test resources manifests are present. + /// + public static void EnsureTestResourcePresence() + { + DSCv3ResourceTestBase.EnsureTestResourcePresence(); + } + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + this.DeleteResourceArtifacts(); + EnsureTestResourcePresence(); + TestCommon.SetupTestSource(false); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + this.DeleteResourceArtifacts(); + TestCommon.TearDownTestSource(); + } + + /// + /// Simple test to confirm that a resource without a module specified can be discovered in the PSGallery. + /// Intentionally has no settings to force a failure, but only after acquiring the module. + /// + [Test] + [Ignore("PS Gallery tests are unreliable.")] + public void ConfigureFromGallery() + { + TestCommon.EnsureModuleState(Constants.GalleryTestModuleName, present: false); + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoModule_NoSettings.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); + Assert.True(result.StdOut.Contains("The configuration unit failed while attempting to test the current system state.")); + } + + /// + /// Simple test to confirm that a resource with a module specified can be discovered in a local repository that doesn't support resource discovery. + /// + [Test] + public void ConfigureFromTestRepo() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); + FileAssert.Exists(targetFilePath); + Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); + + Assert.True(Directory.Exists( + Path.Combine( + TestCommon.GetExpectedModulePath(TestCommon.TestModuleLocation.Default), + Constants.SimpleTestModuleName))); + } + + /// + /// Simple test to confirm that the module was installed to the location specified in the DefaultModuleRoot settings. + /// + [Test] + public void ConfigureFromTestRepo_DefaultModuleRootSetting() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + string moduleTestDir = TestCommon.GetRandomTestDir(); + WinGetSettingsHelper.ConfigureConfigureBehavior(Constants.DefaultModuleRoot, moduleTestDir); + + string args = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo_Location.yml"); + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, args); + + WinGetSettingsHelper.ConfigureConfigureBehavior(Constants.DefaultModuleRoot, string.Empty); + bool moduleExists = Directory.Exists(Path.Combine(moduleTestDir, Constants.SimpleTestModuleName)); + if (moduleExists) + { + // Clean test directory to avoid impacting other tests. + Directory.Delete(moduleTestDir, true); + } + + Assert.AreEqual(0, result.ExitCode); + Assert.True(moduleExists); + } + + /// + /// Simple test to confirm that the module was installed in the right location. + /// + /// Location to pass. + [TestCase(TestCommon.TestModuleLocation.CurrentUser)] + [TestCase(TestCommon.TestModuleLocation.AllUsers)] + [TestCase(TestCommon.TestModuleLocation.WinGetModulePath)] + [TestCase(TestCommon.TestModuleLocation.Custom)] + [TestCase(TestCommon.TestModuleLocation.Default)] + public void ConfigureFromTestRepo_Location(TestCommon.TestModuleLocation location) + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + + string args = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo_Location.yml"); + if (location == TestCommon.TestModuleLocation.CurrentUser) + { + args += " --module-path currentuser"; + } + else if (location == TestCommon.TestModuleLocation.AllUsers) + { + args += " --module-path allusers"; + } + else if (location == TestCommon.TestModuleLocation.Default) + { + args += " --module-path default"; + } + else if (location == TestCommon.TestModuleLocation.Custom) + { + args += " --module-path " + TestCommon.GetExpectedModulePath(location); + } + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, args); + Assert.AreEqual(0, result.ExitCode); + + Assert.True(Directory.Exists(Path.Combine( + TestCommon.GetExpectedModulePath(location), + Constants.SimpleTestModuleName))); + } + + /// + /// One resource fails, but the other is not dependent and should be executed. + /// + [Test] + public void IndependentResourceWithSingleFailure() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\IndependentResources_OneFailure.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\IndependentResources_OneFailure.txt"); + FileAssert.Exists(targetFilePath); + Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); + } + + /// + /// One resource fails, and the dependent resource should not be executed. + /// + [Test] + public void DependentResourceWithFailure() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\DependentResources_Failure.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\DependentResources_Failure.txt"); + FileAssert.DoesNotExist(targetFilePath); + } + + /// + /// The configuration server unexpectedly exits. Winget should continue to operate properly. + /// + [Test] + public void ConfigServerUnexpectedExit() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\ConfigServerUnexpectedExit.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_APPLY_FAILED, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\ConfigServerUnexpectedExit.txt"); + FileAssert.DoesNotExist(targetFilePath); + } + + /// + /// Resource name case-insensitive test. + /// + [Test] + public void ResourceCaseInsensitive() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\ResourceCaseInsensitive.yml")); + Assert.AreEqual(0, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\ResourceCaseInsensitive.txt"); + FileAssert.Exists(targetFilePath); + Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); + } + + /// + /// Simple test to configure from an https configuration file. + /// + [Test] + public void ConfigureFromHttpsConfigurationFile() + { + string args = $"{Constants.TestSourceUrl}/TestData/Configuration/Configure_TestRepo_Location.yml"; + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, args); + Assert.AreEqual(0, result.ExitCode); + } + + /// + /// Runs a configuration, then changes the state and runs it again from history. + /// + [Test] + public void ConfigureFromHistory() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); + FileAssert.Exists(targetFilePath); + Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); + + File.WriteAllText(targetFilePath, "Changed contents!"); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor("Configure_TestRepo.yml"); + result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, $"-h {guid}"); + Assert.AreEqual(0, result.ExitCode); + + FileAssert.Exists(targetFilePath); + Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); + } + + /// + /// Specifies the module path to an "elevated" server. + /// + [Test] + public void SpecifyModulePathToHighIntegrityServer() + { + string configFile = TestCommon.GetTestDataFile("Configuration\\GetPSModulePath.yml"); + string testDirectory = TestCommon.GetRandomTestDir(); + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, $"{configFile} --module-path \"{testDirectory}\""); + Assert.AreEqual(0, result.ExitCode); + + string testFile = Path.Join(TestCommon.GetTestDataFile("Configuration"), "PSModulePath.txt"); + Assert.True(File.Exists(testFile)); + string testFileContents = File.ReadAllText(testFile); + Assert.True(testFileContents.StartsWith(testDirectory)); + } + + /// + /// Runs a DSCv3 configuration, then changes the state and runs it again from history. + /// + [Test] + public void ConfigureThroughHistory_DSCv3() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")); + Assert.AreEqual(0, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.txt"); + FileAssert.Exists(targetFilePath); + Assert.AreEqual("DSCv3 Contents!", File.ReadAllText(targetFilePath)); + + File.WriteAllText(targetFilePath, "Changed contents!"); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor("ShowDetails_DSCv3.yml"); + result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, $"-h {guid}"); + Assert.AreEqual(0, result.ExitCode); + + FileAssert.Exists(targetFilePath); + Assert.AreEqual("DSCv3 Contents!", File.ReadAllText(targetFilePath)); + } + + /// + /// Ensures that the test file resource schema function works. + /// + [Test] + public void TestFileResourceSchema() + { + var result = TestCommon.RunAICLICommand("dscv3 test-file", "--schema"); + Assert.AreEqual(0, result.ExitCode); + + var lines = result.StdOut.Split("\r\n", StringSplitOptions.RemoveEmptyEntries); + Assert.AreEqual(1, lines.Length); + } + + /// + /// Export all with specific package id. + /// + [Test] + public void DSCv3_Export() + { + // Reset state + var result = TestCommon.RunAICLICommand("dscv3 test-json", "--delete"); + Assert.AreEqual(0, result.ExitCode); + + // Configure properties + string propertyName1 = "prop1"; + string propertyName2 = "prop2"; + string propertyValue1 = "val1"; + string propertyValue2 = "val2"; + + string propertySetFormatString = "{{ \"property\": \"{0}\", \"value\": \"{1}\" }}"; + + result = TestCommon.RunAICLICommand("dscv3 test-json", "--set", string.Format(propertySetFormatString, propertyName1, propertyValue1)); + Assert.AreEqual(0, result.ExitCode); + + result = TestCommon.RunAICLICommand("dscv3 test-json", "--set", string.Format(propertySetFormatString, propertyName2, propertyValue2)); + Assert.AreEqual(0, result.ExitCode); + + // Export + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + + result = TestCommon.RunAICLICommand("test config-export-units", $"-o {exportFile} --resource Microsoft.WinGet.Dev/TestJSON --verbose"); + Assert.AreEqual(0, result.ExitCode); + + Assert.True(File.Exists(exportFile)); + string exportText = File.ReadAllText(exportFile); + Assert.True(exportText.Contains("Microsoft.WinGet.Dev/TestJSON")); + Assert.True(exportText.Contains(propertyName1)); + Assert.True(exportText.Contains(propertyName2)); + Assert.True(exportText.Contains(propertyValue1)); + Assert.True(exportText.Contains(propertyValue2)); + } + + /// + /// Simple test to confirm that a resource with a module specified can be discovered in a local repository that doesn't support resource discovery. + /// + [Test] + public void ConfigureFromTestRepo_DSCv3() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: true, repository: Constants.TestRepoName); + this.DeleteResourceArtifacts(); + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo_DSCv3.yml"), timeOut: 300000); + Assert.AreEqual(0, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); + FileAssert.Exists(targetFilePath); + Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); + } + + /// + /// Find unit processors tests. + /// + [Test] + public void ConfigureFindUnitProcessors() + { + // Find all unit processors. + var result = TestCommon.RunAICLICommand("test config-find-unit-processors", string.Empty, timeOut: 300000); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains("Microsoft/OSInfo")); + + // Setup TestExeInstaller with dsc resources. + var installDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --override \"/InstallDir {installDir} /GenerateDscResourceFiles\""); + Assert.AreEqual(0, result.ExitCode); + + // Find unit processors filtering to install location. + result = TestCommon.RunAICLICommand("test config-find-unit-processors", $"-l {installDir}"); + Assert.AreEqual(0, result.ExitCode); + Assert.False(result.StdOut.Contains("Microsoft/OSInfo")); + Assert.True(result.StdOut.Contains("AppInstallerTest/TestResource")); + + // Find unit processors filtering to unknown location. + var unknownDir = TestCommon.GetRandomTestDir(); + result = TestCommon.RunAICLICommand("test config-find-unit-processors", $"-l {unknownDir}"); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains("No unit processors found.")); + + // Clean up + result = TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(0, result.ExitCode); + } + + /// + /// RunCommandOnSet test. + /// + [Test] + public void RunCommandOnSetResourceTest() + { + var testDir = TestCommon.GetRandomTestDir(); + var testConfigFile = Path.Combine(testDir, "RunCommandOnSet.yml"); + File.Copy(TestCommon.GetTestDataFile("Configuration\\RunCommandOnSet.yml"), testConfigFile); + + var content = File.ReadAllText(testConfigFile); + content = content.Replace("", testDir); + File.WriteAllText(testConfigFile, content); + + var result = TestCommon.RunAICLICommand(CommandAndAgreementsAndVerbose, testConfigFile, timeOut: 300000); + Assert.AreEqual(0, result.ExitCode); + + // Verify test file created. + string targetFilePath = Path.Combine(testDir, "TestFile.txt"); + FileAssert.Exists(targetFilePath); + string testContent = File.ReadAllText(targetFilePath); + Assert.True(testContent.Contains("TestContent")); + } + + private void DeleteResourceArtifacts() + { + // Delete all .txt files in the test directory; they are placed there by the tests + foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) + { + File.Delete(file); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ConfigureExportCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureExportCommand.cs index 2616b1c14c..99e84bf924 100644 --- a/src/AppInstallerCLIE2ETests/ConfigureExportCommand.cs +++ b/src/AppInstallerCLIE2ETests/ConfigureExportCommand.cs @@ -1,211 +1,211 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - using NUnit.Framework.Internal; - - /// - /// `Configure export` command tests. - /// - public class ConfigureExportCommand - { - private const string Command = "configure export"; - private const string ShowCommand = "configure show"; - - private string previousPathValue = string.Empty; - - /// - /// Set up. - /// - [OneTimeSetUp] - public void BaseSetup() - { - TestCommon.SetupTestSource(false); - var installDir = TestCommon.GetRandomTestDir(); - TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestPackageExport -v 1.0.0.0 --silent -l {installDir}"); - this.previousPathValue = System.Environment.GetEnvironmentVariable("PATH"); - - // The installer puts DSCv3 resources in both locations - System.Environment.SetEnvironmentVariable("PATH", this.previousPathValue + ";" + installDir + ";" + installDir + "\\SubDirectory"); - DSCv3ResourceTestBase.EnsureTestResourcePresence(); - } - - /// - /// Tear down. - /// - [OneTimeTearDown] - public void BaseTeardown() - { - TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestPackageExport"); - TestCommon.TearDownTestSource(); - if (!string.IsNullOrEmpty(this.previousPathValue)) - { - System.Environment.SetEnvironmentVariable("PATH", this.previousPathValue); - } - } - - /// - /// Export a specific package. - /// - [Test] - public void ExportTestPackage() - { - var exportDir = TestCommon.GetRandomTestDir(); - var exportFile = Path.Combine(exportDir, "exported.yml"); - var result = TestCommon.RunAICLICommand(Command, $"--package-id AppInstallerTest.TestPackageExport -o {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(File.Exists(exportFile)); - - // Check exported file is readable and validate content - var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); - Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); - Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); - - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); - Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); - Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); - } - - /// - /// Export a specific package with related configuration. - /// - [Test] - public void ExportTestPackageWithPackageSettings() - { - var exportDir = TestCommon.GetRandomTestDir(); - var exportFile = Path.Combine(exportDir, "exported.yml"); - var result = TestCommon.RunAICLICommand(Command, $"--package-id AppInstallerTest.TestPackageExport --module AppInstallerTest --resource TestResource -o {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(File.Exists(exportFile)); - - // Check exported file is readable and validate content - var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); - Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); - Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); - - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); - Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); - Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); - - Assert.True(showResult.StdOut.Contains("AppInstallerTest/TestResource")); - Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_AppInstallerTest.TestPackageExport")); - Assert.True(showResult.StdOut.Contains("data: TestData")); - } - - /// - /// Export a specific package with version. - /// - [Test] - public void ExportTestPackageWithVersion() - { - var exportDir = TestCommon.GetRandomTestDir(); - var exportFile = Path.Combine(exportDir, "exported.yml"); - var result = TestCommon.RunAICLICommand(Command, $"--package-id AppInstallerTest.TestPackageExport --include-versions -o {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(File.Exists(exportFile)); - - // Check exported file is readable and validate content - var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); - Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); - Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); - - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); - Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); - Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); - Assert.True(showResult.StdOut.Contains("version: 1.0.0.0")); - } - - /// - /// Export all. - /// - [Test] - [Ignore("DSC 3.2 design changes ", Until = "2026-05-10")] - public void ExportAll() - { - var exportDir = TestCommon.GetRandomTestDir(); - var exportFile = Path.Combine(exportDir, "exported.yml"); - var result = TestCommon.RunAICLICommand(Command, $"--all --verbose -o {exportFile}", timeOut: 1200000); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(File.Exists(exportFile)); - - // Check exported file is readable and validate content - var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}", timeOut: 1200000); - Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); - - Assert.True(showResult.StdOut.Contains("Microsoft.PowerShell")); - - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/UserSettingsFile")); - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/AdminSettings")); - Assert.True(showResult.StdOut.Contains("Microsoft.Windows.Settings/WindowsSettings")); - - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); - Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); - Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); - - Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); - Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); - Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); - Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); - Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); - - Assert.True(showResult.StdOut.Contains("AppInstallerTest/TestResource")); - Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_AppInstallerTest.TestPackageExport")); - Assert.True(showResult.StdOut.Contains("data: TestData")); - - Assert.True(showResult.StdOut.Contains("AppInstallerTest/TestResource_SubDirectory")); - Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_AppInstallerTest.TestPackageExport")); - Assert.True(showResult.StdOut.Contains("data: TestData")); - } - - /// - /// Export a specific package that's not installed. - /// - [Test] - public void ExportFailedWithNotFoundPackage() - { - var exportDir = TestCommon.GetRandomTestDir(); - var exportFile = Path.Combine(exportDir, "exported.yml"); - var result = TestCommon.RunAICLICommand(Command, $"--package-id NotFound.NotFound -o {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - } - - /// - /// Export all with specific package id. - /// - [Test] - public void ExportFailedWithAllAndSpecificPackage() - { - var exportDir = TestCommon.GetRandomTestDir(); - var exportFile = Path.Combine(exportDir, "exported.yml"); - var result = TestCommon.RunAICLICommand(Command, $"--all --package-id AppInstallerTest.TestPackageExport -o {exportFile}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + using NUnit.Framework.Internal; + + /// + /// `Configure export` command tests. + /// + public class ConfigureExportCommand + { + private const string Command = "configure export"; + private const string ShowCommand = "configure show"; + + private string previousPathValue = string.Empty; + + /// + /// Set up. + /// + [OneTimeSetUp] + public void BaseSetup() + { + TestCommon.SetupTestSource(false); + var installDir = TestCommon.GetRandomTestDir(); + TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestPackageExport -v 1.0.0.0 --silent -l {installDir}"); + this.previousPathValue = System.Environment.GetEnvironmentVariable("PATH"); + + // The installer puts DSCv3 resources in both locations + System.Environment.SetEnvironmentVariable("PATH", this.previousPathValue + ";" + installDir + ";" + installDir + "\\SubDirectory"); + DSCv3ResourceTestBase.EnsureTestResourcePresence(); + } + + /// + /// Tear down. + /// + [OneTimeTearDown] + public void BaseTeardown() + { + TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestPackageExport"); + TestCommon.TearDownTestSource(); + if (!string.IsNullOrEmpty(this.previousPathValue)) + { + System.Environment.SetEnvironmentVariable("PATH", this.previousPathValue); + } + } + + /// + /// Export a specific package. + /// + [Test] + public void ExportTestPackage() + { + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + var result = TestCommon.RunAICLICommand(Command, $"--package-id AppInstallerTest.TestPackageExport -o {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(File.Exists(exportFile)); + + // Check exported file is readable and validate content + var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); + Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); + Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); + + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); + Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); + Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); + } + + /// + /// Export a specific package with related configuration. + /// + [Test] + public void ExportTestPackageWithPackageSettings() + { + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + var result = TestCommon.RunAICLICommand(Command, $"--package-id AppInstallerTest.TestPackageExport --module AppInstallerTest --resource TestResource -o {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(File.Exists(exportFile)); + + // Check exported file is readable and validate content + var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); + Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); + Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); + + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); + Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); + Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); + + Assert.True(showResult.StdOut.Contains("AppInstallerTest/TestResource")); + Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_AppInstallerTest.TestPackageExport")); + Assert.True(showResult.StdOut.Contains("data: TestData")); + } + + /// + /// Export a specific package with version. + /// + [Test] + public void ExportTestPackageWithVersion() + { + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + var result = TestCommon.RunAICLICommand(Command, $"--package-id AppInstallerTest.TestPackageExport --include-versions -o {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(File.Exists(exportFile)); + + // Check exported file is readable and validate content + var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); + Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); + Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); + + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); + Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); + Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); + Assert.True(showResult.StdOut.Contains("version: 1.0.0.0")); + } + + /// + /// Export all. + /// + [Test] + [Ignore("DSC 3.2 design changes ", Until = "2026-05-10")] + public void ExportAll() + { + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + var result = TestCommon.RunAICLICommand(Command, $"--all --verbose -o {exportFile}", timeOut: 1200000); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(File.Exists(exportFile)); + + // Check exported file is readable and validate content + var showResult = TestCommon.RunAICLICommand(ShowCommand, $"-f {exportFile}", timeOut: 1200000); + Assert.AreEqual(Constants.ErrorCode.S_OK, showResult.ExitCode); + + Assert.True(showResult.StdOut.Contains("Microsoft.PowerShell")); + + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/UserSettingsFile")); + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/AdminSettings")); + Assert.True(showResult.StdOut.Contains("Microsoft.Windows.Settings/WindowsSettings")); + + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Source")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_{Constants.TestSourceType}]")); + Assert.True(showResult.StdOut.Contains($"type: {Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains($"argument: {Constants.TestSourceUrl}")); + Assert.True(showResult.StdOut.Contains($"name: {Constants.TestSourceName}")); + + Assert.True(showResult.StdOut.Contains("Microsoft.WinGet.Dev/Package")); + Assert.True(showResult.StdOut.Contains($"[{Constants.TestSourceName}_AppInstallerTest.TestPackageExport]")); + Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_{Constants.TestSourceType}")); + Assert.True(showResult.StdOut.Contains("id: AppInstallerTest.TestPackageExport")); + Assert.True(showResult.StdOut.Contains($"source: {Constants.TestSourceName}")); + + Assert.True(showResult.StdOut.Contains("AppInstallerTest/TestResource")); + Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_AppInstallerTest.TestPackageExport")); + Assert.True(showResult.StdOut.Contains("data: TestData")); + + Assert.True(showResult.StdOut.Contains("AppInstallerTest/TestResource_SubDirectory")); + Assert.True(showResult.StdOut.Contains($"Dependencies: {Constants.TestSourceName}_AppInstallerTest.TestPackageExport")); + Assert.True(showResult.StdOut.Contains("data: TestData")); + } + + /// + /// Export a specific package that's not installed. + /// + [Test] + public void ExportFailedWithNotFoundPackage() + { + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + var result = TestCommon.RunAICLICommand(Command, $"--package-id NotFound.NotFound -o {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + } + + /// + /// Export all with specific package id. + /// + [Test] + public void ExportFailedWithAllAndSpecificPackage() + { + var exportDir = TestCommon.GetRandomTestDir(); + var exportFile = Path.Combine(exportDir, "exported.yml"); + var result = TestCommon.RunAICLICommand(Command, $"--all --package-id AppInstallerTest.TestPackageExport -o {exportFile}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ConfigureListCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureListCommand.cs index 8effa9ac84..bd7128305e 100644 --- a/src/AppInstallerCLIE2ETests/ConfigureListCommand.cs +++ b/src/AppInstallerCLIE2ETests/ConfigureListCommand.cs @@ -1,105 +1,105 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// `Configure list` command tests. - /// - public class ConfigureListCommand - { - private const string ConfigureWithAgreementsAndVerbose = "configure --accept-configuration-agreements --verbose"; - private const string ConfigureTestRepoFile = "Configure_TestRepo.yml"; - - /// - /// Teardown done once after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTeardown() - { - this.DeleteTxtFiles(); - } - - /// - /// Applies a configuration, then verifies that it is in the overall list. - /// - [Test] - public void ListAllConfigurations() - { - var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - result = TestCommon.RunAICLICommand("configure list", "--verbose"); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains(ConfigureTestRepoFile)); - } - - /// - /// Applies a configuration (to ensure at least one exists), gets the overall list, then the details about the first configuration. - /// - [Test] - public void ListSpecificConfiguration() - { - var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor(ConfigureTestRepoFile); - result = TestCommon.RunAICLICommand("configure list", $"-h {guid}"); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains(guid)); - Assert.True(result.StdOut.Contains(ConfigureTestRepoFile)); - } - - /// - /// Applies a configuration (to ensure at least one exists), gets the overall list, then the removes the first configuration. - /// - [Test] - public void RemoveConfiguration() - { - var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor(ConfigureTestRepoFile); - result = TestCommon.RunAICLICommand("configure list", $"-h {guid} --remove"); - Assert.AreEqual(0, result.ExitCode); - - result = TestCommon.RunAICLICommand("configure list", "--verbose"); - Assert.AreEqual(0, result.ExitCode); - Assert.False(result.StdOut.Contains(guid)); - } - - /// - /// Applies a configuration (to ensure at least one exists), gets the overall list, then the outputs the first configuration. - /// - [Test] - public void OutputConfiguration() - { - var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor(ConfigureTestRepoFile); - string tempFile = TestCommon.GetRandomTestFile(".yml"); - result = TestCommon.RunAICLICommand("configure list", $"-h {guid} --output {tempFile}"); - Assert.AreEqual(0, result.ExitCode); - - result = TestCommon.RunAICLICommand("configure validate", $"--verbose {tempFile}"); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - } - - private void DeleteTxtFiles() - { - // Delete all .txt files in the test directory; they are placed there by the tests - foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) - { - File.Delete(file); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure list` command tests. + /// + public class ConfigureListCommand + { + private const string ConfigureWithAgreementsAndVerbose = "configure --accept-configuration-agreements --verbose"; + private const string ConfigureTestRepoFile = "Configure_TestRepo.yml"; + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + this.DeleteTxtFiles(); + } + + /// + /// Applies a configuration, then verifies that it is in the overall list. + /// + [Test] + public void ListAllConfigurations() + { + var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + result = TestCommon.RunAICLICommand("configure list", "--verbose"); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains(ConfigureTestRepoFile)); + } + + /// + /// Applies a configuration (to ensure at least one exists), gets the overall list, then the details about the first configuration. + /// + [Test] + public void ListSpecificConfiguration() + { + var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor(ConfigureTestRepoFile); + result = TestCommon.RunAICLICommand("configure list", $"-h {guid}"); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains(guid)); + Assert.True(result.StdOut.Contains(ConfigureTestRepoFile)); + } + + /// + /// Applies a configuration (to ensure at least one exists), gets the overall list, then the removes the first configuration. + /// + [Test] + public void RemoveConfiguration() + { + var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor(ConfigureTestRepoFile); + result = TestCommon.RunAICLICommand("configure list", $"-h {guid} --remove"); + Assert.AreEqual(0, result.ExitCode); + + result = TestCommon.RunAICLICommand("configure list", "--verbose"); + Assert.AreEqual(0, result.ExitCode); + Assert.False(result.StdOut.Contains(guid)); + } + + /// + /// Applies a configuration (to ensure at least one exists), gets the overall list, then the outputs the first configuration. + /// + [Test] + public void OutputConfiguration() + { + var result = TestCommon.RunAICLICommand(ConfigureWithAgreementsAndVerbose, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor(ConfigureTestRepoFile); + string tempFile = TestCommon.GetRandomTestFile(".yml"); + result = TestCommon.RunAICLICommand("configure list", $"-h {guid} --output {tempFile}"); + Assert.AreEqual(0, result.ExitCode); + + result = TestCommon.RunAICLICommand("configure validate", $"--verbose {tempFile}"); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + } + + private void DeleteTxtFiles() + { + // Delete all .txt files in the test directory; they are placed there by the tests + foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) + { + File.Delete(file); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ConfigureProcessorPathCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureProcessorPathCommand.cs index 0b4fe54e77..f1d0a1d7a0 100644 --- a/src/AppInstallerCLIE2ETests/ConfigureProcessorPathCommand.cs +++ b/src/AppInstallerCLIE2ETests/ConfigureProcessorPathCommand.cs @@ -1,139 +1,139 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System; - using System.IO; - using System.Text.RegularExpressions; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// E2E tests for the `--processor-path` argument of the configure command. - /// These tests verify that audit information (path, hash, signing) is shown - /// when a custom DSC processor path is provided. - /// - public class ConfigureProcessorPathCommand - { - private const string Command = "configure"; - private const string ConfigurationProcessorPath = "ConfigurationProcessorPath"; - - // DSC app execution alias paths (stable then preview). - private static readonly string[] DscAliasCandidates = new[] - { - Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - @"Microsoft\WindowsApps\Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe\dsc.exe"), - Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - @"Microsoft\WindowsApps\Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe\dsc.exe"), - }; - - /// - /// Enables the LocalProcessorPath admin setting before each test. - /// - [SetUp] - public void SetUp() - { - WinGetSettingsHelper.EnableAdminSetting(ConfigurationProcessorPath); - } - - /// - /// Disables the LocalProcessorPath admin setting after each test. - /// - [TearDown] - public void TearDown() - { - WinGetSettingsHelper.DisableAdminSetting(ConfigurationProcessorPath); - } - - /// - /// Verifies that when the DSC executable is provided as the processor path, - /// the audit header, file path, SHA256 hash, and app-execution-alias marker - /// all appear in the output. - /// - [Test] - public void ProcessorPath_AuditOutput_ShowsPathAndHash() - { - string processorPath = FindDscExePath(); - if (processorPath == null) - { - Assert.Ignore("DSC is not installed; skipping processor path audit test."); - return; - } - - string configFile = TestCommon.GetTestDataFile(@"Configuration\ShowDetails_DSCv3.yml"); - - var result = TestCommon.RunAICLICommand( - Command, - $"--accept-configuration-agreements --processor-path \"{processorPath}\" \"{configFile}\" --no-progress"); - - // Audit header must appear regardless of whether the configure succeeds or fails, - // because audit output happens during factory setup before DSC is invoked. - Assert.True(result.StdOut.Contains("Custom processor path:"), $"Expected audit header in output. StdOut: {result.StdOut}"); - Assert.True(result.StdOut.Contains($" Path: {processorPath}"), $"Expected path in audit output. StdOut: {result.StdOut}"); - Assert.True(result.StdOut.Contains(" Hash: "), $"Expected hash in audit output. StdOut: {result.StdOut}"); - - // dsc.exe is an app execution alias; the alias marker must be present. - Assert.True(result.StdOut.Contains("Type: App execution alias"), $"Expected app execution alias marker. StdOut: {result.StdOut}"); - } - - /// - /// Verifies that the hash in the audit output is a 64-character lowercase hex string - /// (SHA256 of the file or reparse buffer contents). - /// - [Test] - public void ProcessorPath_AuditOutput_HashIsValidSHA256() - { - string processorPath = FindDscExePath(); - if (processorPath == null) - { - Assert.Ignore("DSC is not installed; skipping processor path hash format test."); - return; - } - - string configFile = TestCommon.GetTestDataFile(@"Configuration\ShowDetails_DSCv3.yml"); - - var result = TestCommon.RunAICLICommand( - Command, - $"--accept-configuration-agreements --processor-path \"{processorPath}\" \"{configFile}\" --no-progress"); - - Assert.True(result.StdOut.Contains(" Hash: "), $"Expected hash in audit output. StdOut: {result.StdOut}"); - - // Extract the hash value from " Hash: " - int hashLabelIndex = result.StdOut.IndexOf(" Hash: "); - Assert.That(hashLabelIndex, Is.GreaterThanOrEqualTo(0)); - - int hashStart = hashLabelIndex + " Hash: ".Length; - int hashEnd = result.StdOut.IndexOfAny(new[] { '\r', '\n' }, hashStart); - string hashValue = hashEnd > hashStart - ? result.StdOut.Substring(hashStart, hashEnd - hashStart).Trim() - : result.StdOut.Substring(hashStart).Trim(); - - Assert.AreEqual(64, hashValue.Length, $"Expected 64-character SHA256 hash, got: '{hashValue}'"); - Assert.True( - Regex.IsMatch(hashValue, "^[0-9a-f]{64}$"), - $"Expected lowercase hex hash, got: '{hashValue}'"); - } - - /// - /// Gets the path to dsc.exe (app execution alias), or null if not installed. - /// - private static string FindDscExePath() - { - foreach (string candidate in DscAliasCandidates) - { - if (File.Exists(candidate)) - { - return candidate; - } - } - - return null; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.IO; + using System.Text.RegularExpressions; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// E2E tests for the `--processor-path` argument of the configure command. + /// These tests verify that audit information (path, hash, signing) is shown + /// when a custom DSC processor path is provided. + /// + public class ConfigureProcessorPathCommand + { + private const string Command = "configure"; + private const string ConfigurationProcessorPath = "ConfigurationProcessorPath"; + + // DSC app execution alias paths (stable then preview). + private static readonly string[] DscAliasCandidates = new[] + { + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Microsoft\WindowsApps\Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe\dsc.exe"), + Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Microsoft\WindowsApps\Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe\dsc.exe"), + }; + + /// + /// Enables the LocalProcessorPath admin setting before each test. + /// + [SetUp] + public void SetUp() + { + WinGetSettingsHelper.EnableAdminSetting(ConfigurationProcessorPath); + } + + /// + /// Disables the LocalProcessorPath admin setting after each test. + /// + [TearDown] + public void TearDown() + { + WinGetSettingsHelper.DisableAdminSetting(ConfigurationProcessorPath); + } + + /// + /// Verifies that when the DSC executable is provided as the processor path, + /// the audit header, file path, SHA256 hash, and app-execution-alias marker + /// all appear in the output. + /// + [Test] + public void ProcessorPath_AuditOutput_ShowsPathAndHash() + { + string processorPath = FindDscExePath(); + if (processorPath == null) + { + Assert.Ignore("DSC is not installed; skipping processor path audit test."); + return; + } + + string configFile = TestCommon.GetTestDataFile(@"Configuration\ShowDetails_DSCv3.yml"); + + var result = TestCommon.RunAICLICommand( + Command, + $"--accept-configuration-agreements --processor-path \"{processorPath}\" \"{configFile}\" --no-progress"); + + // Audit header must appear regardless of whether the configure succeeds or fails, + // because audit output happens during factory setup before DSC is invoked. + Assert.True(result.StdOut.Contains("Custom processor path:"), $"Expected audit header in output. StdOut: {result.StdOut}"); + Assert.True(result.StdOut.Contains($" Path: {processorPath}"), $"Expected path in audit output. StdOut: {result.StdOut}"); + Assert.True(result.StdOut.Contains(" Hash: "), $"Expected hash in audit output. StdOut: {result.StdOut}"); + + // dsc.exe is an app execution alias; the alias marker must be present. + Assert.True(result.StdOut.Contains("Type: App execution alias"), $"Expected app execution alias marker. StdOut: {result.StdOut}"); + } + + /// + /// Verifies that the hash in the audit output is a 64-character lowercase hex string + /// (SHA256 of the file or reparse buffer contents). + /// + [Test] + public void ProcessorPath_AuditOutput_HashIsValidSHA256() + { + string processorPath = FindDscExePath(); + if (processorPath == null) + { + Assert.Ignore("DSC is not installed; skipping processor path hash format test."); + return; + } + + string configFile = TestCommon.GetTestDataFile(@"Configuration\ShowDetails_DSCv3.yml"); + + var result = TestCommon.RunAICLICommand( + Command, + $"--accept-configuration-agreements --processor-path \"{processorPath}\" \"{configFile}\" --no-progress"); + + Assert.True(result.StdOut.Contains(" Hash: "), $"Expected hash in audit output. StdOut: {result.StdOut}"); + + // Extract the hash value from " Hash: " + int hashLabelIndex = result.StdOut.IndexOf(" Hash: "); + Assert.That(hashLabelIndex, Is.GreaterThanOrEqualTo(0)); + + int hashStart = hashLabelIndex + " Hash: ".Length; + int hashEnd = result.StdOut.IndexOfAny(new[] { '\r', '\n' }, hashStart); + string hashValue = hashEnd > hashStart + ? result.StdOut.Substring(hashStart, hashEnd - hashStart).Trim() + : result.StdOut.Substring(hashStart).Trim(); + + Assert.AreEqual(64, hashValue.Length, $"Expected 64-character SHA256 hash, got: '{hashValue}'"); + Assert.True( + Regex.IsMatch(hashValue, "^[0-9a-f]{64}$"), + $"Expected lowercase hex hash, got: '{hashValue}'"); + } + + /// + /// Gets the path to dsc.exe (app execution alias), or null if not installed. + /// + private static string FindDscExePath() + { + foreach (string candidate in DscAliasCandidates) + { + if (File.Exists(candidate)) + { + return candidate; + } + } + + return null; + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs index d0a31d33e9..3649d20a47 100644 --- a/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs +++ b/src/AppInstallerCLIE2ETests/ConfigureShowCommand.cs @@ -1,225 +1,225 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Win32; - using NUnit.Framework; - - /// - /// `Configure show` command tests. - /// - public class ConfigureShowCommand - { - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - this.DeleteResourceArtifacts(); - ConfigureCommand.EnsureTestResourcePresence(); - } - - /// - /// One time teardown. - /// - [OneTimeTearDown] - public void OneTimeTearDown() - { - this.DeleteResourceArtifacts(); - } - - /// - /// Simple test to confirm that a resource without a module specified can be discovered in the PSGallery. - /// - [Test] - [Ignore("PS Gallery tests are unreliable.")] - public void ShowDetailsFromGallery() - { - TestCommon.EnsureModuleState(Constants.GalleryTestModuleName, present: false); - - var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\PSGallery_NoModule_NoSettings.yml")} --verbose", timeOut: 120000); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.PSGalleryName)); - } - - /// - /// Simple test to confirm that a resource with a module specified can be discovered in a local repository that doesn't support resource discovery. - /// - [Test] - public void ShowDetailsFromTestRepo() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - - var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")} --verbose"); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.TestRepoName)); - } - - /// - /// Simple test to confirm that a resource that is already locally available shows that way. - /// - /// The location the module should be before running. - [TestCase(TestCommon.TestModuleLocation.CurrentUser)] - [TestCase(TestCommon.TestModuleLocation.AllUsers)] - [TestCase(TestCommon.TestModuleLocation.WinGetModulePath)] - [TestCase(TestCommon.TestModuleLocation.Custom)] - public void ShowDetailsFromLocal(TestCommon.TestModuleLocation location) - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: true, repository: Constants.TestRepoName, location: location); - - string args = $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")} --verbose"; - if (location == TestCommon.TestModuleLocation.Custom) - { - args += " --module-path " + TestCommon.GetExpectedModulePath(location); - } - - var result = TestCommon.RunAICLICommand("configure show", args); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.LocalModuleDescriptor)); - } - - /// - /// A schema 0.3 config file is allowed with the experimental feature. - /// - [Test] - public void ShowDetails_Schema0_3_Succeeds() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - - var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo_0_3.yml")} --verbose"); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.TestRepoName)); - } - - /// - /// A schema 0.3 config file with parameters is blocked. - /// - [Test] - public void ShowDetails_Schema0_3_Parameters() - { - var result = TestCommon.RunAICLICommand("configure show", TestCommon.GetTestDataFile("Configuration\\WithParameters_0_3.yml")); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains("Failed to get detailed information about the configuration.")); - } - - /// - /// Simple test to show details from a https configuration file. - /// - [Test] - public void ShowDetailsFromHttpsConfigurationFile() - { - var result = TestCommon.RunAICLICommand("configure show", $"{Constants.TestSourceUrl}/TestData/Configuration/ShowDetails_TestRepo.yml --verbose"); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.TestRepoName)); - } - - /// - /// This test ensures that there is not significant overflow from large strings in the configuration file. - /// - [Test] - public void ShowTruncatedDetailsAndFileContent() - { - var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\LargeContentStrings.yml")} --verbose"); - Assert.AreEqual(0, result.ExitCode); - Assert.True(result.StdOut.Contains("")); - Assert.True(result.StdOut.Contains("Some of the data present in the configuration file was truncated for this output; inspect the file contents for the complete content.")); - Assert.False(result.StdOut.Contains("Line5")); - } - - /// - /// Runs a configuration, then shows it from history. - /// - [Test] - public void ShowFromHistory() - { - var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --verbose", TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor("Configure_TestRepo.yml"); - result = TestCommon.RunAICLICommand("configure show", $"-h {guid}"); - Assert.AreEqual(0, result.ExitCode); - } - - /// - /// Runs a configuration, then shows it from history. - /// - [Test] - public void ShowWithBadProcessorIdentifier() - { - var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\Unknown_Processor.yml")} --verbose"); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); - } - - /// - /// Simple test to confirm that a resource is discoverable with DSC v3. - /// - [Test] - public void ShowDetails_DSCv3() - { - var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")} --verbose"); - Assert.AreEqual(0, result.ExitCode); - - var outputLines = result.StdOut.Split('\n'); - int startLine = -1; - for (int i = 0; i < outputLines.Length; ++i) - { - if (outputLines[i].Trim() == "Microsoft.WinGet.Dev/TestFile [Test File]") - { - startLine = i; - } - } - - Assert.AreNotEqual(-1, startLine); - Assert.LessOrEqual(3, outputLines.Length - startLine); - - // outputLines[1] should contain the discovered resource string if working properly. - Assert.AreEqual("Description 1.", outputLines[startLine + 2].Trim()); - } - - /// - /// Runs a DSCv3 configuration, then shows it from history. - /// - [Test] - public void ShowFromHistory_DSCv3() - { - var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --verbose", TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")); - Assert.AreEqual(0, result.ExitCode); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor("ShowDetails_DSCv3.yml"); - result = TestCommon.RunAICLICommand("configure show", $"-h {guid} --"); - Assert.AreEqual(0, result.ExitCode); - - var outputLines = result.StdOut.Split('\n'); - int startLine = -1; - for (int i = 0; i < outputLines.Length; ++i) - { - if (outputLines[i].Trim() == "Microsoft.WinGet.Dev/TestFile [Test File]") - { - startLine = i; - } - } - - Assert.AreNotEqual(-1, startLine); - Assert.LessOrEqual(3, outputLines.Length - startLine); - - // outputLines[1] should contain the discovered resource string if working properly. - Assert.AreEqual("Description 1.", outputLines[startLine + 2].Trim()); - } - - private void DeleteResourceArtifacts() - { - // Delete all .txt files in the test directory; they are placed there by the tests - foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) - { - File.Delete(file); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Win32; + using NUnit.Framework; + + /// + /// `Configure show` command tests. + /// + public class ConfigureShowCommand + { + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + this.DeleteResourceArtifacts(); + ConfigureCommand.EnsureTestResourcePresence(); + } + + /// + /// One time teardown. + /// + [OneTimeTearDown] + public void OneTimeTearDown() + { + this.DeleteResourceArtifacts(); + } + + /// + /// Simple test to confirm that a resource without a module specified can be discovered in the PSGallery. + /// + [Test] + [Ignore("PS Gallery tests are unreliable.")] + public void ShowDetailsFromGallery() + { + TestCommon.EnsureModuleState(Constants.GalleryTestModuleName, present: false); + + var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\PSGallery_NoModule_NoSettings.yml")} --verbose", timeOut: 120000); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.PSGalleryName)); + } + + /// + /// Simple test to confirm that a resource with a module specified can be discovered in a local repository that doesn't support resource discovery. + /// + [Test] + public void ShowDetailsFromTestRepo() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + + var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")} --verbose"); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.TestRepoName)); + } + + /// + /// Simple test to confirm that a resource that is already locally available shows that way. + /// + /// The location the module should be before running. + [TestCase(TestCommon.TestModuleLocation.CurrentUser)] + [TestCase(TestCommon.TestModuleLocation.AllUsers)] + [TestCase(TestCommon.TestModuleLocation.WinGetModulePath)] + [TestCase(TestCommon.TestModuleLocation.Custom)] + public void ShowDetailsFromLocal(TestCommon.TestModuleLocation location) + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: true, repository: Constants.TestRepoName, location: location); + + string args = $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")} --verbose"; + if (location == TestCommon.TestModuleLocation.Custom) + { + args += " --module-path " + TestCommon.GetExpectedModulePath(location); + } + + var result = TestCommon.RunAICLICommand("configure show", args); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.LocalModuleDescriptor)); + } + + /// + /// A schema 0.3 config file is allowed with the experimental feature. + /// + [Test] + public void ShowDetails_Schema0_3_Succeeds() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + + var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo_0_3.yml")} --verbose"); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.TestRepoName)); + } + + /// + /// A schema 0.3 config file with parameters is blocked. + /// + [Test] + public void ShowDetails_Schema0_3_Parameters() + { + var result = TestCommon.RunAICLICommand("configure show", TestCommon.GetTestDataFile("Configuration\\WithParameters_0_3.yml")); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains("Failed to get detailed information about the configuration.")); + } + + /// + /// Simple test to show details from a https configuration file. + /// + [Test] + public void ShowDetailsFromHttpsConfigurationFile() + { + var result = TestCommon.RunAICLICommand("configure show", $"{Constants.TestSourceUrl}/TestData/Configuration/ShowDetails_TestRepo.yml --verbose"); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.TestRepoName)); + } + + /// + /// This test ensures that there is not significant overflow from large strings in the configuration file. + /// + [Test] + public void ShowTruncatedDetailsAndFileContent() + { + var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\LargeContentStrings.yml")} --verbose"); + Assert.AreEqual(0, result.ExitCode); + Assert.True(result.StdOut.Contains("")); + Assert.True(result.StdOut.Contains("Some of the data present in the configuration file was truncated for this output; inspect the file contents for the complete content.")); + Assert.False(result.StdOut.Contains("Line5")); + } + + /// + /// Runs a configuration, then shows it from history. + /// + [Test] + public void ShowFromHistory() + { + var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --verbose", TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor("Configure_TestRepo.yml"); + result = TestCommon.RunAICLICommand("configure show", $"-h {guid}"); + Assert.AreEqual(0, result.ExitCode); + } + + /// + /// Runs a configuration, then shows it from history. + /// + [Test] + public void ShowWithBadProcessorIdentifier() + { + var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\Unknown_Processor.yml")} --verbose"); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); + } + + /// + /// Simple test to confirm that a resource is discoverable with DSC v3. + /// + [Test] + public void ShowDetails_DSCv3() + { + var result = TestCommon.RunAICLICommand("configure show", $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")} --verbose"); + Assert.AreEqual(0, result.ExitCode); + + var outputLines = result.StdOut.Split('\n'); + int startLine = -1; + for (int i = 0; i < outputLines.Length; ++i) + { + if (outputLines[i].Trim() == "Microsoft.WinGet.Dev/TestFile [Test File]") + { + startLine = i; + } + } + + Assert.AreNotEqual(-1, startLine); + Assert.LessOrEqual(3, outputLines.Length - startLine); + + // outputLines[1] should contain the discovered resource string if working properly. + Assert.AreEqual("Description 1.", outputLines[startLine + 2].Trim()); + } + + /// + /// Runs a DSCv3 configuration, then shows it from history. + /// + [Test] + public void ShowFromHistory_DSCv3() + { + var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --verbose", TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")); + Assert.AreEqual(0, result.ExitCode); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor("ShowDetails_DSCv3.yml"); + result = TestCommon.RunAICLICommand("configure show", $"-h {guid} --"); + Assert.AreEqual(0, result.ExitCode); + + var outputLines = result.StdOut.Split('\n'); + int startLine = -1; + for (int i = 0; i < outputLines.Length; ++i) + { + if (outputLines[i].Trim() == "Microsoft.WinGet.Dev/TestFile [Test File]") + { + startLine = i; + } + } + + Assert.AreNotEqual(-1, startLine); + Assert.LessOrEqual(3, outputLines.Length - startLine); + + // outputLines[1] should contain the discovered resource string if working properly. + Assert.AreEqual("Description 1.", outputLines[startLine + 2].Trim()); + } + + private void DeleteResourceArtifacts() + { + // Delete all .txt files in the test directory; they are placed there by the tests + foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) + { + File.Delete(file); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ConfigureTestCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureTestCommand.cs index 8589f5ff76..ca49d7a566 100644 --- a/src/AppInstallerCLIE2ETests/ConfigureTestCommand.cs +++ b/src/AppInstallerCLIE2ETests/ConfigureTestCommand.cs @@ -1,151 +1,151 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Win32; - using NUnit.Framework; - - /// - /// `Configure test` command tests. - /// - public class ConfigureTestCommand - { - private const string CommandAndAgreements = "configure test --accept-configuration-agreements"; - - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - this.DeleteResourceArtifacts(); - ConfigureCommand.EnsureTestResourcePresence(); - } - - /// - /// Teardown done once after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTeardown() - { - this.DeleteResourceArtifacts(); - } - - /// - /// Checks for a resource not in the desired state. - /// - [Test] - public void ConfigureTest_NotInDesiredState() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - this.DeleteResourceArtifacts(); - - var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); - Assert.True(result.StdOut.Contains("Module: xE2ETestResource")); // Details from the resource should appear if the initial details are shown - } - - /// - /// Checks for a resource in a desired state. - /// - [Test] - public void ConfigureTest_InDesiredState() - { - TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); - this.DeleteResourceArtifacts(); - - // Set up the expected state - File.WriteAllText(TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"), "Contents!"); - - var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("System is in the described configuration state.")); - Assert.True(result.StdOut.Contains("Module: xE2ETestResource")); // Details from the resource should appear if the initial details are shown - } - - /// - /// One resource fails. - /// - [Test] - public void ConfigureTest_TestFailure() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\IndependentResources_OneFailure.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_TEST_FAILED, result.ExitCode); - Assert.True(result.StdOut.Contains("Some of the configuration units failed while testing their state.")); - Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); - } - - /// - /// Test from https configuration file. - /// - [Test] - public void ConfigureTest_HttpsConfigurationFile() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreements, $"{Constants.TestSourceUrl}/TestData/Configuration/Configure_TestRepo_Location.yml"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("System is in the described configuration state.")); - } - - /// - /// Runs a configuration, then tests it from history. - /// - [Test] - public void TestFromHistory() - { - var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --verbose", TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - - // The configuration creates a file next to itself with the given contents - string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); - FileAssert.Exists(targetFilePath); - Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); - - string guid = TestCommon.GetConfigurationInstanceIdentifierFor("Configure_TestRepo.yml"); - result = TestCommon.RunAICLICommand(CommandAndAgreements, $"-h {guid}"); - Assert.AreEqual(0, result.ExitCode); - - File.WriteAllText(targetFilePath, "Changed contents!"); - - result = TestCommon.RunAICLICommand(CommandAndAgreements, $"-h {guid}"); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - } - - /// - /// Simple test to confirm that a resource is testable with DSC v3. - /// - [Test] - public void ConfigureTest_DSCv3() - { - var result = TestCommon.RunAICLICommand(CommandAndAgreements, $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")} --verbose"); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); - } - - /// - /// Tests that --suppress-initial-details will suppress the initial details output. - /// - [Test] - public void ConfigureTest_SuppressInitialDetails() - { - var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --suppress-initial-details", TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(0, result.ExitCode); - Assert.False(result.StdOut.Contains("Module: xE2ETestResource")); // Details from the resource should not appear if the initial details are suppressed - } - - private void DeleteResourceArtifacts() - { - // Delete all .txt files in the test directory; they are placed there by the tests - foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) - { - File.Delete(file); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Win32; + using NUnit.Framework; + + /// + /// `Configure test` command tests. + /// + public class ConfigureTestCommand + { + private const string CommandAndAgreements = "configure test --accept-configuration-agreements"; + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + this.DeleteResourceArtifacts(); + ConfigureCommand.EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + this.DeleteResourceArtifacts(); + } + + /// + /// Checks for a resource not in the desired state. + /// + [Test] + public void ConfigureTest_NotInDesiredState() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + this.DeleteResourceArtifacts(); + + var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); + Assert.True(result.StdOut.Contains("Module: xE2ETestResource")); // Details from the resource should appear if the initial details are shown + } + + /// + /// Checks for a resource in a desired state. + /// + [Test] + public void ConfigureTest_InDesiredState() + { + TestCommon.EnsureModuleState(Constants.SimpleTestModuleName, present: false); + this.DeleteResourceArtifacts(); + + // Set up the expected state + File.WriteAllText(TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"), "Contents!"); + + var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("System is in the described configuration state.")); + Assert.True(result.StdOut.Contains("Module: xE2ETestResource")); // Details from the resource should appear if the initial details are shown + } + + /// + /// One resource fails. + /// + [Test] + public void ConfigureTest_TestFailure() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreements, TestCommon.GetTestDataFile("Configuration\\IndependentResources_OneFailure.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_TEST_FAILED, result.ExitCode); + Assert.True(result.StdOut.Contains("Some of the configuration units failed while testing their state.")); + Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); + } + + /// + /// Test from https configuration file. + /// + [Test] + public void ConfigureTest_HttpsConfigurationFile() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreements, $"{Constants.TestSourceUrl}/TestData/Configuration/Configure_TestRepo_Location.yml"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("System is in the described configuration state.")); + } + + /// + /// Runs a configuration, then tests it from history. + /// + [Test] + public void TestFromHistory() + { + var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --verbose", TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + + // The configuration creates a file next to itself with the given contents + string targetFilePath = TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.txt"); + FileAssert.Exists(targetFilePath); + Assert.AreEqual("Contents!", File.ReadAllText(targetFilePath)); + + string guid = TestCommon.GetConfigurationInstanceIdentifierFor("Configure_TestRepo.yml"); + result = TestCommon.RunAICLICommand(CommandAndAgreements, $"-h {guid}"); + Assert.AreEqual(0, result.ExitCode); + + File.WriteAllText(targetFilePath, "Changed contents!"); + + result = TestCommon.RunAICLICommand(CommandAndAgreements, $"-h {guid}"); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + } + + /// + /// Simple test to confirm that a resource is testable with DSC v3. + /// + [Test] + public void ConfigureTest_DSCv3() + { + var result = TestCommon.RunAICLICommand(CommandAndAgreements, $"{TestCommon.GetTestDataFile("Configuration\\ShowDetails_DSCv3.yml")} --verbose"); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("System is not in the described configuration state.")); + } + + /// + /// Tests that --suppress-initial-details will suppress the initial details output. + /// + [Test] + public void ConfigureTest_SuppressInitialDetails() + { + var result = TestCommon.RunAICLICommand("configure --accept-configuration-agreements --suppress-initial-details", TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(0, result.ExitCode); + Assert.False(result.StdOut.Contains("Module: xE2ETestResource")); // Details from the resource should not appear if the initial details are suppressed + } + + private void DeleteResourceArtifacts() + { + // Delete all .txt files in the test directory; they are placed there by the tests + foreach (string file in Directory.GetFiles(TestCommon.GetTestDataFile("Configuration"), "*.txt")) + { + File.Delete(file); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ConfigureValidateCommand.cs b/src/AppInstallerCLIE2ETests/ConfigureValidateCommand.cs index 9e9a03a95a..6c8dcd677e 100644 --- a/src/AppInstallerCLIE2ETests/ConfigureValidateCommand.cs +++ b/src/AppInstallerCLIE2ETests/ConfigureValidateCommand.cs @@ -1,301 +1,301 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// `Configure validate` command tests. - /// - public class ConfigureValidateCommand - { - private const string Command = "configure validate"; - - /// - /// Set up. - /// - [OneTimeSetUp] - public void BaseSetup() - { - TestCommon.SetupTestSource(false); - } - - /// - /// Tear down. - /// - [OneTimeTearDown] - public void BaseTeardown() - { - TestCommon.TearDownTestSource(); - } - - /// - /// The configuration file is empty. - /// - [Test] - public void EmptyFile() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\Empty.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_YAML, result.ExitCode); - } - - /// - /// The configuration file is not configuration YAML. - /// - [Test] - public void NotConfigurationYAML() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NotConfig.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_FIELD, result.ExitCode); - Assert.True(result.StdOut.Contains("$schema")); - Assert.True(result.StdOut.Contains("missing")); - } - - /// - /// The configuration file does not specify the schema version. - /// - [Test] - public void NoVersion() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NoVersion.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_FIELD, result.ExitCode); - Assert.True(result.StdOut.Contains("configurationVersion")); - Assert.True(result.StdOut.Contains("missing")); - } - - /// - /// The configuration file schema version is not known. - /// - [Test] - public void UnknownVersion() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\UnknownVersion.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, result.ExitCode); - Assert.True(result.StdOut.Contains("Configuration file version")); - Assert.True(result.StdOut.Contains("is not known.")); - } - - /// - /// The resources node is not the correct type in YAML. - /// - [Test] - public void ResourcesIsWrongType() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ResourcesNotASequence.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_TYPE, result.ExitCode); - Assert.True(result.StdOut.Contains("resources")); - Assert.True(result.StdOut.Contains("wrong type")); - } - - /// - /// The unit node is not the correct type in YAML. - /// - [Test] - public void UnitIsWrongType() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\UnitNotAMap.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_TYPE, result.ExitCode); - Assert.True(result.StdOut.Contains("resources[0]")); - Assert.True(result.StdOut.Contains("wrong type")); - } - - /// - /// The resource name is missing. - /// - [Test] - public void NoResourceName() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NoResourceName.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); - Assert.True(result.StdOut.Contains("resource")); - Assert.True(result.StdOut.Contains("invalid value")); - Assert.True(result.StdOut.Contains("Module/")); - } - - /// - /// The resource name module does not match the directives module. - /// - [Test] - public void ModuleMismatch() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ModuleMismatch.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); - Assert.True(result.StdOut.Contains("module")); - Assert.True(result.StdOut.Contains("invalid value")); - Assert.True(result.StdOut.Contains("DifferentModule")); - } - - /// - /// The configuration contains multiple resources with the same identifier. - /// - [Test] - public void DuplicateIdentifiers() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\DuplicateIdentifiers.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_DUPLICATE_IDENTIFIER, result.ExitCode); - Assert.True(result.StdOut.Contains("The configuration contains the identifier `same` multiple times.")); - Assert.False(result.StdOut.Contains("NotMentioned")); - } - - /// - /// The configuration does not contain the dependency. - /// - [Test] - public void MissingDependency() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\MissingDependency.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_DEPENDENCY, result.ExitCode); - Assert.True(result.StdOut.Contains("The dependency `missing` was not found within the configuration.")); - Assert.False(result.StdOut.Contains("xE2ETestResource")); - } - - /// - /// The configuration contains a dependency cycle. - /// - [Test] - public void DependencyCycle() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\DependencyCycle.yml")); - Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_DEPENDENCY_CYCLE, result.ExitCode); - Assert.True(result.StdOut.Contains("This configuration unit is part of a dependency cycle.")); - Assert.False(result.StdOut.Contains("NotMentioned")); - } - - /// - /// The configuration unit is not available in a public catalog. - /// - [Test] - public void ResourceIsNotPublic() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("not available publicly")); - } - - /// - /// The configuration unit is not found. - /// - [Test] - public void ResourceIsNotFound() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ResourceNotFound.yml")); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("The configuration unit could not be found.")); - } - - /// - /// The module was not provided. - /// - [Test] - public void ModuleNotProvided() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoModule_NoSettings.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("The module was not provided.")); - } - - /// - /// No issues detected (yet). - /// - [Test] - public void NoIssuesDetected() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoSettings.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Validation found no issues.")); - } - - /// - /// No issues detected (yet) from https configuration file. - /// - [Test] - public void NoIssuesDetected_HttpsConfigurationFile() - { - var result = TestCommon.RunAICLICommand(Command, $"{Constants.TestSourceUrl}/TestData/Configuration/PSGallery_NoSettings.yml", timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Validation found no issues.")); - } - - /// - /// No issues detected from WinGet resource units. - /// - [Test] - public void NoIssuesDetected_WinGetDscResource() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_Good.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Validation found no issues.")); - } - - /// - /// No issues detected from WinGet resource units. - /// - [Test] - public void ValidateWinGetDscResource_DependencySourceMissing() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_DependencySourceMissing.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package depends on a third-party source not previously configured. Package Id: AppInstallerTest.TestExeInstaller; Source: TestSource")); - } - - /// - /// No issues detected from WinGet resource units. - /// - [Test] - public void ValidateWinGetDscResource_PackageNotFound() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_PackageNotFound.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package cannot be validated. Package not found. Package Id: AppInstallerTest.DoesNotExist")); - } - - /// - /// No issues detected from WinGet resource units. - /// - [Test] - public void ValidateWinGetDscResource_PackageVersionNotFound() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_PackageVersionNotFound.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package cannot be validated. Package version not found. Package Id: AppInstallerTest.TestExeInstaller; Version 101.0.101.0")); - } - - /// - /// No issues detected from WinGet resource units. - /// - [Test] - public void ValidateWinGetDscResource_SourceOpenFailed() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_SourceOpenFailed.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package cannot be validated. Source open failed. Package Id: AppInstallerTest.TestExeInstaller; Source: TestSourceV2")); - } - - /// - /// No issues detected from WinGet resource units. - /// - [Test] - public void ValidateWinGetDscResource_VersionSpecifiedWithOnlyOneVersionAvailable() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_VersionSpecifiedWithOnlyOneVersionAvailable.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package specified with a specific version while only one package version is available. Package Id: AppInstallerTest.TestValidManifest; Version: 1.0.0.0")); - } - - /// - /// No issues detected from WinGet resource units. - /// - [Test] - public void ValidateWinGetDscResource_VersionSpecifiedWithUseLatest() - { - var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_VersionSpecifiedWithUseLatest.yml"), timeOut: 120000); - Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); - Assert.True(result.StdOut.Contains("WinGetPackage declares both UseLatest and Version. Package Id: AppInstallerTest.TestExeInstaller")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure validate` command tests. + /// + public class ConfigureValidateCommand + { + private const string Command = "configure validate"; + + /// + /// Set up. + /// + [OneTimeSetUp] + public void BaseSetup() + { + TestCommon.SetupTestSource(false); + } + + /// + /// Tear down. + /// + [OneTimeTearDown] + public void BaseTeardown() + { + TestCommon.TearDownTestSource(); + } + + /// + /// The configuration file is empty. + /// + [Test] + public void EmptyFile() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\Empty.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_YAML, result.ExitCode); + } + + /// + /// The configuration file is not configuration YAML. + /// + [Test] + public void NotConfigurationYAML() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NotConfig.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_FIELD, result.ExitCode); + Assert.True(result.StdOut.Contains("$schema")); + Assert.True(result.StdOut.Contains("missing")); + } + + /// + /// The configuration file does not specify the schema version. + /// + [Test] + public void NoVersion() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NoVersion.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_FIELD, result.ExitCode); + Assert.True(result.StdOut.Contains("configurationVersion")); + Assert.True(result.StdOut.Contains("missing")); + } + + /// + /// The configuration file schema version is not known. + /// + [Test] + public void UnknownVersion() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\UnknownVersion.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, result.ExitCode); + Assert.True(result.StdOut.Contains("Configuration file version")); + Assert.True(result.StdOut.Contains("is not known.")); + } + + /// + /// The resources node is not the correct type in YAML. + /// + [Test] + public void ResourcesIsWrongType() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ResourcesNotASequence.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_TYPE, result.ExitCode); + Assert.True(result.StdOut.Contains("resources")); + Assert.True(result.StdOut.Contains("wrong type")); + } + + /// + /// The unit node is not the correct type in YAML. + /// + [Test] + public void UnitIsWrongType() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\UnitNotAMap.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_TYPE, result.ExitCode); + Assert.True(result.StdOut.Contains("resources[0]")); + Assert.True(result.StdOut.Contains("wrong type")); + } + + /// + /// The resource name is missing. + /// + [Test] + public void NoResourceName() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\NoResourceName.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); + Assert.True(result.StdOut.Contains("resource")); + Assert.True(result.StdOut.Contains("invalid value")); + Assert.True(result.StdOut.Contains("Module/")); + } + + /// + /// The resource name module does not match the directives module. + /// + [Test] + public void ModuleMismatch() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ModuleMismatch.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_INVALID_FIELD_VALUE, result.ExitCode); + Assert.True(result.StdOut.Contains("module")); + Assert.True(result.StdOut.Contains("invalid value")); + Assert.True(result.StdOut.Contains("DifferentModule")); + } + + /// + /// The configuration contains multiple resources with the same identifier. + /// + [Test] + public void DuplicateIdentifiers() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\DuplicateIdentifiers.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_DUPLICATE_IDENTIFIER, result.ExitCode); + Assert.True(result.StdOut.Contains("The configuration contains the identifier `same` multiple times.")); + Assert.False(result.StdOut.Contains("NotMentioned")); + } + + /// + /// The configuration does not contain the dependency. + /// + [Test] + public void MissingDependency() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\MissingDependency.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_MISSING_DEPENDENCY, result.ExitCode); + Assert.True(result.StdOut.Contains("The dependency `missing` was not found within the configuration.")); + Assert.False(result.StdOut.Contains("xE2ETestResource")); + } + + /// + /// The configuration contains a dependency cycle. + /// + [Test] + public void DependencyCycle() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\DependencyCycle.yml")); + Assert.AreEqual(Constants.ErrorCode.CONFIG_ERROR_SET_DEPENDENCY_CYCLE, result.ExitCode); + Assert.True(result.StdOut.Contains("This configuration unit is part of a dependency cycle.")); + Assert.False(result.StdOut.Contains("NotMentioned")); + } + + /// + /// The configuration unit is not available in a public catalog. + /// + [Test] + public void ResourceIsNotPublic() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\Configure_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("not available publicly")); + } + + /// + /// The configuration unit is not found. + /// + [Test] + public void ResourceIsNotFound() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\ResourceNotFound.yml")); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("The configuration unit could not be found.")); + } + + /// + /// The module was not provided. + /// + [Test] + public void ModuleNotProvided() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoModule_NoSettings.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("The module was not provided.")); + } + + /// + /// No issues detected (yet). + /// + [Test] + public void NoIssuesDetected() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\PSGallery_NoSettings.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Validation found no issues.")); + } + + /// + /// No issues detected (yet) from https configuration file. + /// + [Test] + public void NoIssuesDetected_HttpsConfigurationFile() + { + var result = TestCommon.RunAICLICommand(Command, $"{Constants.TestSourceUrl}/TestData/Configuration/PSGallery_NoSettings.yml", timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Validation found no issues.")); + } + + /// + /// No issues detected from WinGet resource units. + /// + [Test] + public void NoIssuesDetected_WinGetDscResource() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_Good.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Validation found no issues.")); + } + + /// + /// No issues detected from WinGet resource units. + /// + [Test] + public void ValidateWinGetDscResource_DependencySourceMissing() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_DependencySourceMissing.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package depends on a third-party source not previously configured. Package Id: AppInstallerTest.TestExeInstaller; Source: TestSource")); + } + + /// + /// No issues detected from WinGet resource units. + /// + [Test] + public void ValidateWinGetDscResource_PackageNotFound() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_PackageNotFound.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package cannot be validated. Package not found. Package Id: AppInstallerTest.DoesNotExist")); + } + + /// + /// No issues detected from WinGet resource units. + /// + [Test] + public void ValidateWinGetDscResource_PackageVersionNotFound() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_PackageVersionNotFound.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package cannot be validated. Package version not found. Package Id: AppInstallerTest.TestExeInstaller; Version 101.0.101.0")); + } + + /// + /// No issues detected from WinGet resource units. + /// + [Test] + public void ValidateWinGetDscResource_SourceOpenFailed() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_SourceOpenFailed.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package cannot be validated. Source open failed. Package Id: AppInstallerTest.TestExeInstaller; Source: TestSourceV2")); + } + + /// + /// No issues detected from WinGet resource units. + /// + [Test] + public void ValidateWinGetDscResource_VersionSpecifiedWithOnlyOneVersionAvailable() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_VersionSpecifiedWithOnlyOneVersionAvailable.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("WinGetPackage configuration unit package specified with a specific version while only one package version is available. Package Id: AppInstallerTest.TestValidManifest; Version: 1.0.0.0")); + } + + /// + /// No issues detected from WinGet resource units. + /// + [Test] + public void ValidateWinGetDscResource_VersionSpecifiedWithUseLatest() + { + var result = TestCommon.RunAICLICommand(Command, TestCommon.GetTestDataFile("Configuration\\WinGetDscResourceValidate_VersionSpecifiedWithUseLatest.yml"), timeOut: 120000); + Assert.AreEqual(Constants.ErrorCode.S_FALSE, result.ExitCode); + Assert.True(result.StdOut.Contains("WinGetPackage declares both UseLatest and Version. Package Id: AppInstallerTest.TestExeInstaller")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Constants.cs b/src/AppInstallerCLIE2ETests/Constants.cs index ddc4911a39..16f8e4f33d 100644 --- a/src/AppInstallerCLIE2ETests/Constants.cs +++ b/src/AppInstallerCLIE2ETests/Constants.cs @@ -1,351 +1,351 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - /// - /// Constants. - /// - public class Constants - { -#pragma warning disable SA1600 // ElementsMustBeDocumented -#pragma warning disable SA1310 // Field names should not contain underscore - - // Runtime test parameters - public const string PackagedContextParameter = "PackagedContext"; - public const string AICLIPathParameter = "AICLIPath"; - public const string AICLIPackagePathParameter = "AICLIPackagePath"; - public const string VerboseLoggingParameter = "VerboseLogging"; - public const string LooseFileRegistrationParameter = "LooseFileRegistration"; - public const string InvokeCommandInDesktopPackageParameter = "InvokeCommandInDesktopPackage"; - public const string StaticFileRootPathParameter = "StaticFileRootPath"; - public const string LocalServerCertPathParameter = "LocalServerCertPath"; - public const string ExeInstallerPathParameter = "ExeTestInstallerPath"; - public const string MsiInstallerPathParameter = "MsiTestInstallerPath"; - public const string MsiInstallerV2PathParameter = "MsiTestInstallerV2Path"; - public const string MsixInstallerPathParameter = "MsixTestInstallerPath"; - public const string FontPathParameter = "FontTestPath"; - public const string PackageCertificatePathParameter = "PackageCertificatePath"; - public const string PowerShellModulePathParameter = "PowerShellModulePath"; - public const string SkipTestSourceParameter = "SkipTestSource"; - public const string ForcedExperimentalFeaturesParameter = "ForcedExperimentalFeatures"; - public const string InprocTestbedPathParameter = "InprocTestbedPath"; - public const string InprocTestbedUseTestPackageParameter = "InprocTestbedUseTestPackage"; - - // Test Sources - public const string DefaultWingetSourceName = @"winget"; - public const string DefaultWingetSourceUrl = @"https://winget.azureedge.net/cache"; - public const string DefaultMSStoreSourceName = @"msstore"; - public const string DefaultMSStoreSourceUrl = @"https://storeedgefd.dsx.mp.microsoft.com/v9.0"; - public const string DefaultMSStoreSourceType = "Microsoft.Rest"; - public const string DefaultMSStoreSourceIdentifier = "StoreEdgeFD"; - public const string TestSourceName = @"TestSource"; - public const string TestAlternateSourceName = @"TestSource2"; - public const string TestSourceUrl = @"https://localhost:5001/TestKit"; - public const string TestSourceType = "Microsoft.PreIndexed.Package"; - public const string TestSourceIdentifier = @"WingetE2E.Tests_8wekyb3d8bbwe"; - public const string RestTestSourceName = @"TestRestSource"; - public const string RestTestSourceUrl = @"https://localhost:5001/TestKit/TestData/TestRestSource"; - public const string RestTestSourceType = "Microsoft.Rest"; - - public const string AICLIPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; - public const string AICLIPackageName = "WinGetDevCLI"; - public const string AICLIPackagePublisherHash = "8wekyb3d8bbwe"; - public const string AICLIAppId = "WinGetDev"; - - public const string TestPackage = "TëstPackage.msix"; - public const string ExeInstaller = "AppInstallerTestExeInstaller"; - public const string MsiInstaller = "AppInstallerTestMsiInstaller"; - public const string MsixInstaller = "AppInstallerTestMsixInstaller"; - public const string ZipInstaller = "AppInstallerTestZipInstaller"; - public const string ExeInstallerFileName = "AppInstallerTestExeInstaller.exe"; - public const string MsiInstallerFileName = "AppInstallerTestMsiInstaller.msi"; - public const string MsiInstallerV2FileName = "AppInstallerTestMsiInstallerV2.msi"; - public const string MsixInstallerFileName = "AppInstallerTestMsixInstaller.msix"; - public const string ZipInstallerFileName = "AppInstallerTestZipInstaller.zip"; - public const string FontFileName = "AppInstallerTestFont.ttf"; - public const string ModifyRepairInstaller = "AppInstallerTest.TestModifyRepair"; - public const string IndexPackage = "source.msix"; - public const string MakeAppx = "makeappx.exe"; - public const string SignTool = "signtool.exe"; - public const string IndexCreationTool = "IndexCreationTool"; - public const string WinGetUtil = "WinGetUtil"; - public const string E2ETestLogsPathPackaged = @"Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir"; - public const string E2ETestLogsPathUnpackaged = @"WinGet\defaultState"; - public const string CheckpointDirectoryPackaged = @"Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\Checkpoints"; - public const string CheckpointDirectoryUnpackaged = @"Microsoft\WinGet\State\defaultState\Checkpoints"; - - // Installer filename - public const string TestCommandExe = "testCommand.exe"; - public const string AppInstallerTestExeInstallerExe = "AppInstallerTestExeInstaller.exe"; - public const string AppInstallerTestMsiInstallerMsi = "AppInstallerTestMsiInstaller.msi"; - public const string AppInstallerTestZipInstallerZip = "AppInstallerTestZipInstaller.zip"; - - // Test installers' package IDs - public const string ExeInstallerPackageId = "AppInstallerTest.TestExeInstaller"; - public const string MsiInstallerPackageId = "AppInstallerTest.TestMsiInstaller"; - public const string MsixInstallerPackageId = "AppInstallerTest.TestMsixInstaller"; - public const string PortableExePackageId = "AppInstallerTest.TestPortableExe"; - public const string PortableExeWithCommandPackageId = "AppInstallerTest.TestPortableExeWithCommand"; - - public const string ExeInstalledDefaultProductCode = "{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}"; - public const string MsiInstallerProductCode = "{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}"; - public const string MsixInstallerName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; - public const string MsixInstallerPackageFamilyName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e_8wekyb3d8bbwe"; - - public const string TestExeInstalledFileName = "TestExeInstalled.txt"; - public const string TestExeUninstallerFileName = "UninstallTestExe.bat"; - public const string TestExeUninstalledFileName = "TestExeUninstalled.txt"; - public const string TestExeRepairCompletedFileName = "TestExeRepairCompleted.txt"; - - // PowerShell Cmdlets - public const string FindCmdlet = "Find-WinGetPackage"; - public const string GetCmdlet = "Get-WinGetPackage"; - public const string GetSourceCmdlet = "Get-WinGetSource"; - public const string InstallCmdlet = "Install-WinGetPackage"; - public const string UninstallCmdlet = "Uninstall-WinGetPackage"; - public const string UpdateCmdlet = "Update-WinGetPackage"; - - public const string WindowsPackageManagerServer = "WindowsPackageManagerServer"; - - // Locations - public const string LocalAppData = "LocalAppData"; - public const string Dependencies = "Dependencies"; - - // Package dir - public const string PortableExePackageDirName = $"{PortableExePackageId}_{TestSourceIdentifier}"; - public const string PortableExeWithCommandPackageDirName = $"{PortableExeWithCommandPackageId}_{TestSourceIdentifier}"; - - // Registry keys - public const string WinGetPackageIdentifier = "WinGetPackageIdentifier"; - public const string WinGetSourceIdentifier = "WinGetSourceIdentifier"; - public const string UninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; - public const string PathSubKey_User = @"Environment"; - public const string PathSubKey_Machine = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; - public const string FontsSubKey = @"Software\Microsoft\Windows NT\CurrentVersion\Fonts"; - - // User settings - public const string ArchiveExtractionMethod = "archiveExtractionMethod"; - public const string PortablePackageUserRoot = "portablePackageUserRoot"; - public const string PortablePackageMachineRoot = "portablePackageMachineRoot"; - public const string InstallBehaviorScope = "scope"; - public const string InstallerTypes = "installerTypes"; - public const string DefaultModuleRoot = "defaultModuleRoot"; - - // Configuration - public const string PSGalleryName = "PSGallery"; - public const string TestRepoName = "AppInstallerCLIE2ETestsRepo"; - public const string GalleryTestModuleName = "XmlContentDsc"; - public const string SimpleTestModuleName = "xE2ETestResource"; - public const string LocalModuleDescriptor = "[Local]"; - public const string TestRegistryPath = "Software\\Microsoft\\WinGet\\Tests"; - - // Group Policy Error Message - public const string BlockByWinGetPolicyErrorMessage = "This operation is disabled by Group Policy : Enable Windows Package Manager"; - - /// - /// Error codes. - /// - public class ErrorCode - { - public const int S_OK = 0; - public const int S_FALSE = 1; - public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); - public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003); - public const int E_INVALIDARG = unchecked((int)0x80070057); - public const int ERROR_NO_RANGES_PROCESSED = unchecked((int)0x80070138); - public const int OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY = unchecked((int)0x8051100F); - public const int ERROR_OLD_WIN_VERSION = unchecked((int)0x8007047E); - public const int HTTP_E_STATUS_NOT_FOUND = unchecked((int)0x80190194); - public const int E_ABORT = unchecked((int)0x80004004); - - // AICLI custom HRESULTs - public const int ERROR_INTERNAL_ERROR = unchecked((int)0x8A150001); - public const int ERROR_INVALID_CL_ARGUMENTS = unchecked((int)0x8A150002); - public const int ERROR_COMMAND_FAILED = unchecked((int)0x8A150003); - public const int ERROR_MANIFEST_FAILED = unchecked((int)0x8A150004); - public const int ERROR_CTRL_SIGNAL_RECEIVED = unchecked((int)0x8A150005); - public const int ERROR_SHELLEXEC_INSTALL_FAILED = unchecked((int)0x8A150006); - public const int ERROR_UNSUPPORTED_MANIFESTVERSION = unchecked((int)0x8A150007); - public const int ERROR_DOWNLOAD_FAILED = unchecked((int)0x8A150008); - public const int ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX = unchecked((int)0x8A150009); - public const int ERROR_INDEX_INTEGRITY_COMPROMISED = unchecked((int)0x8A15000A); - public const int ERROR_SOURCES_INVALID = unchecked((int)0x8A15000B); - public const int ERROR_SOURCE_NAME_ALREADY_EXISTS = unchecked((int)0x8A15000C); - public const int ERROR_INVALID_SOURCE_TYPE = unchecked((int)0x8A15000D); - public const int ERROR_PACKAGE_IS_BUNDLE = unchecked((int)0x8A15000E); - public const int ERROR_SOURCE_DATA_MISSING = unchecked((int)0x8A15000F); - public const int ERROR_NO_APPLICABLE_INSTALLER = unchecked((int)0x8A150010); - public const int ERROR_INSTALLER_HASH_MISMATCH = unchecked((int)0x8A150011); - public const int ERROR_SOURCE_NAME_DOES_NOT_EXIST = unchecked((int)0x8A150012); - public const int ERROR_SOURCE_ARG_ALREADY_EXISTS = unchecked((int)0x8A150013); - public const int ERROR_NO_APPLICATIONS_FOUND = unchecked((int)0x8A150014); - public const int ERROR_NO_SOURCES_DEFINED = unchecked((int)0x8A150015); - public const int ERROR_MULTIPLE_APPLICATIONS_FOUND = unchecked((int)0x8A150016); - public const int ERROR_NO_MANIFEST_FOUND = unchecked((int)0x8A150017); - public const int ERROR_EXTENSION_PUBLIC_FAILED = unchecked((int)0x8A150018); - public const int ERROR_COMMAND_REQUIRES_ADMIN = unchecked((int)0x8A150019); - public const int ERROR_SOURCE_NOT_SECURE = unchecked((int)0x8A15001A); - public const int ERROR_MSSTORE_BLOCKED_BY_POLICY = unchecked((int)0x8A15001B); - public const int ERROR_MSSTORE_APP_BLOCKED_BY_POLICY = unchecked((int)0x8A15001C); - public const int ERROR_EXPERIMENTAL_FEATURE_DISABLED = unchecked((int)0x8A15001D); - public const int ERROR_MSSTORE_INSTALL_FAILED = unchecked((int)0x8A15001E); - public const int ERROR_COMPLETE_INPUT_BAD = unchecked((int)0x8A15001F); - public const int ERROR_YAML_INIT_FAILED = unchecked((int)0x8A150020); - public const int ERROR_INVALID_MAPPING_KEY = unchecked((int)0x8A150021); - public const int ERROR_DUPLICATE_MAPPING_KEY = unchecked((int)0x8A150022); - public const int ERROR_YAML_INVALID_OPERATION = unchecked((int)0x8A150023); - public const int ERROR_YAML_DOC_BUILD_FAILED = unchecked((int)0x8A150024); - public const int ERROR_YAML_INVALID_EMITTER_STATE = unchecked((int)0x8A150025); - public const int ERROR_YAML_INVALID_DATA = unchecked((int)0x8A150026); - public const int ERROR_LIBYAML_ERROR = unchecked((int)0x8A150027); - public const int ERROR_MANIFEST_VALIDATION_WARNING = unchecked((int)0x8A150028); - public const int ERROR_MANIFEST_VALIDATION_FAILURE = unchecked((int)0x8A150029); - public const int ERROR_INVALID_MANIFEST = unchecked((int)0x8A15002A); - public const int ERROR_UPDATE_NOT_APPLICABLE = unchecked((int)0x8A15002B); - public const int ERROR_UPDATE_ALL_HAS_FAILURE = unchecked((int)0x8A15002C); - public const int ERROR_INSTALLER_SECURITY_CHECK_FAILED = unchecked((int)0x8A15002D); - public const int ERROR_DOWNLOAD_SIZE_MISMATCH = unchecked((int)0x8A15002E); - public const int ERROR_NO_UNINSTALL_INFO_FOUND = unchecked((int)0x8A15002F); - public const int ERROR_EXEC_UNINSTALL_COMMAND_FAILED = unchecked((int)0x8A150030); - public const int ERROR_ICU_BREAK_ITERATOR_ERROR = unchecked((int)0x8A150031); - public const int ERROR_ICU_CASEMAP_ERROR = unchecked((int)0x8A150032); - public const int ERROR_ICU_REGEX_ERROR = unchecked((int)0x8A150033); - public const int ERROR_IMPORT_INSTALL_FAILED = unchecked((int)0x8A150034); - public const int ERROR_NOT_ALL_PACKAGES_FOUND = unchecked((int)0x8A150035); - public const int ERROR_JSON_INVALID_FILE = unchecked((int)0x8A150036); - public const int ERROR_SOURCE_NOT_REMOTE = unchecked((int)0x8A150037); - public const int ERROR_UNSUPPORTED_RESTSOURCE = unchecked((int)0x8A150038); - public const int ERROR_RESTSOURCE_INVALID_DATA = unchecked((int)0x8A150039); - public const int ERROR_BLOCKED_BY_POLICY = unchecked((int)0x8A15003A); - public const int ERROR_RESTAPI_INTERNAL_ERROR = unchecked((int)0x8A15003B); - public const int ERROR_RESTSOURCE_INVALID_URL = unchecked((int)0x8A15003C); - public const int ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE = unchecked((int)0x8A15003D); - public const int ERROR_RESTSOURCE_INVALID_VERSION = unchecked((int)0x8A15003E); - public const int ERROR_SOURCE_DATA_INTEGRITY_FAILURE = unchecked((int)0x8A15003F); - public const int ERROR_STREAM_READ_FAILURE = unchecked((int)0x8A150040); - public const int ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED = unchecked((int)0x8A150041); - public const int ERROR_PROMPT_INPUT_ERROR = unchecked((int)0x8A150042); - public const int ERROR_UNSUPPORTED_SOURCE_REQUEST = unchecked((int)0x8A150043); - public const int ERROR_RESTAPI_ENDPOINT_NOT_FOUND = unchecked((int)0x8A150044); - public const int ERROR_SOURCE_OPEN_FAILED = unchecked((int)0x8A150045); - public const int ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED = unchecked((int)0x8A150046); - public const int ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH = unchecked((int)0x8A150047); - public const int ERROR_MISSING_RESOURCE_FILE = unchecked((int)0x8A150048); - public const int ERROR_MSI_INSTALL_FAILED = unchecked((int)0x8A150049); - public const int ERROR_INVALID_MSIEXEC_ARGUMENT = unchecked((int)0x8A15004A); - public const int ERROR_FAILED_TO_OPEN_ALL_SOURCES = unchecked((int)0x8A15004B); - public const int ERROR_DEPENDENCIES_VALIDATION_FAILED = unchecked((int)0x8A15004C); - public const int ERROR_MISSING_PACKAGE = unchecked((int)0x8A15004D); - public const int ERROR_INVALID_TABLE_COLUMN = unchecked((int)0x8A15004E); - public const int ERROR_UPGRADE_VERSION_NOT_NEWER = unchecked((int)0x8A15004F); - public const int ERROR_UPGRADE_VERSION_UNKNOWN = unchecked((int)0x8A150050); - public const int ERROR_ICU_CONVERSION_ERROR = unchecked((int)0x8A150051); - public const int ERROR_PORTABLE_INSTALL_FAILED = unchecked((int)0x8A150052); - public const int ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED = unchecked((int)0x8A150053); - public const int ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS = unchecked((int)0x8A150054); - public const int ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY = unchecked((int)0x8A150055); - public const int ERROR_INSTALLER_PROHIBITS_ELEVATION = unchecked((int)0x8A150056); - public const int ERROR_PORTABLE_UNINSTALL_FAILED = unchecked((int)0x8A150057); - public const int ERROR_ARP_VERSION_VALIDATION_FAILED = unchecked((int)0x8A150058); - public const int ERROR_UNSUPPORTED_ARGUMENT = unchecked((int)0x8A150059); - public const int ERROR_BIND_WITH_EMBEDDED_NULL = unchecked((int)0x8A15005A); - public const int ERROR_NESTEDINSTALLER_NOT_FOUND = unchecked((int)0x8A15005B); - public const int ERROR_EXTRACT_ARCHIVE_FAILED = unchecked((int)0x8A15005C); - public const int ERROR_NESTEDINSTALLER_INVALID_PATH = unchecked((int)0x8A15005D); - public const int ERROR_PINNED_CERTIFICATE_MISMATCH = unchecked((int)0x8A15005E); - public const int ERROR_INSTALL_LOCATION_REQUIRED = unchecked((int)0x8A15005F); - public const int ERROR_ARCHIVE_SCAN_FAILED = unchecked((int)0x8A150060); - public const int ERROR_PACKAGE_ALREADY_INSTALLED = unchecked((int)0x8A150061); - public const int ERROR_PIN_ALREADY_EXISTS = unchecked((int)0x8A150062); - public const int ERROR_PIN_DOES_NOT_EXIST = unchecked((int)0x8A150063); - public const int ERROR_CANNOT_OPEN_PINNING_INDEX = unchecked((int)0x8A150064); - public const int ERROR_MULTIPLE_INSTALL_FAILED = unchecked((int)0x8A150065); - public const int ERROR_MULTIPLE_UNINSTALL_FAILED = unchecked((int)0x8A150066); - public const int ERROR_NOT_ALL_QUERIES_FOUND_SINGLE = unchecked((int)0x8A150067); - public const int ERROR_PACKAGE_IS_PINNED = unchecked((int)0x8A150068); - public const int ERROR_PACKAGE_IS_STUB = unchecked((int)0x8A150069); - public const int ERROR_APPTERMINATION_RECEIVED = unchecked((int)0x8A15006A); - public const int ERROR_DOWNLOAD_DEPENDENCIES = unchecked((int)0x8A15006B); - public const int ERROR_DOWNLOAD_COMMAND_PROHIBITED = unchecked((int)0x8A15006C); - public const int ERROR_SERVICE_UNAVAILABLE = unchecked((int)0x8A15006D); - public const int ERROR_RESUME_ID_NOT_FOUND = unchecked((int)0x8A15006E); - public const int ERROR_CLIENT_VERSION_MISMATCH = unchecked((int)0x8A15006F); - public const int ERROR_INVALID_RESUME_STATE = unchecked((int)0x8A150070); - public const int ERROR_CANNOT_OPEN_CHECKPOINT_INDEX = unchecked((int)0x8A150071); - - public const int ERROR_NO_REPAIR_INFO_FOUND = unchecked((int)0x8A150079); - public const int ERROR_REPAIR_NOT_SUPPORTED = unchecked((int)0x8A15007C); - public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D); - - public const int ERROR_INSTALLER_ZERO_BYTE_FILE = unchecked((int)0x8A150086); - public const int ERROR_FONT_INSTALL_FAILED = unchecked((int)0x8A150087); - public const int ERROR_FONT_FILE_NOT_SUPPORTED = unchecked((int)0x8A150088); - - public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); - public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); - public const int ERROR_INSTALL_FILE_IN_USE = unchecked((int)0x8A150103); - public const int ERROR_INSTALL_MISSING_DEPENDENCY = unchecked((int)0x8A150104); - public const int ERROR_INSTALL_DISK_FULL = unchecked((int)0x8A150105); - public const int ERROR_INSTALL_INSUFFICIENT_MEMORY = unchecked((int)0x8A150106); - public const int ERROR_INSTALL_NO_NETWORK = unchecked((int)0x8A150107); - public const int ERROR_INSTALL_CONTACT_SUPPORT = unchecked((int)0x8A150108); - public const int ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH = unchecked((int)0x8A150109); - public const int ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL = unchecked((int)0x8A15010A); - public const int ERROR_INSTALL_REBOOT_INITIATED = unchecked((int)0x8A15010B); - public const int ERROR_INSTALL_CANCELLED_BY_USER = unchecked((int)0x8A15010C); - public const int ERROR_INSTALL_ALREADY_INSTALLED = unchecked((int)0x8A15010D); - public const int ERROR_INSTALL_DOWNGRADE = unchecked((int)0x8A15010E); - public const int ERROR_INSTALL_BLOCKED_BY_POLICY = unchecked((int)0x8A15010F); - public const int ERROR_INSTALL_DEPENDENCIES = unchecked((int)0x8A150110); - public const int ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION = unchecked((int)0x8A150111); - public const int ERROR_INSTALL_INVALID_PARAMETER = unchecked((int)0x8A150112); - public const int ERROR_INSTALL_SYSTEM_NOT_SUPPORTED = unchecked((int)0x8A150113); - public const int APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED = unchecked((int)0x8A150114); - - public const int INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND = unchecked((int)0x8A150201); - public const int INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE = unchecked((int)0x0A150202); - public const int INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND = unchecked((int)0x8A150203); - public const int INSTALLED_STATUS_FILE_HASH_MISMATCH = unchecked((int)0x8A150204); - public const int INSTALLED_STATUS_FILE_NOT_FOUND = unchecked((int)0x8A150205); - public const int INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK = unchecked((int)0x0A150206); - public const int INSTALLED_STATUS_FILE_ACCESS_ERROR = unchecked((int)0x8A150207); - - public const int CONFIG_ERROR_INVALID_CONFIGURATION_FILE = unchecked((int)0x8A15C001); - public const int CONFIG_ERROR_INVALID_YAML = unchecked((int)0x8A15C002); - public const int CONFIG_ERROR_INVALID_FIELD_TYPE = unchecked((int)0x8A15C003); - public const int CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION = unchecked((int)0x8A15C004); - public const int CONFIG_ERROR_SET_APPLY_FAILED = unchecked((int)0x8A15C005); - public const int CONFIG_ERROR_DUPLICATE_IDENTIFIER = unchecked((int)0x8A15C006); - public const int CONFIG_ERROR_MISSING_DEPENDENCY = unchecked((int)0x8A15C007); - public const int CONFIG_ERROR_DEPENDENCY_UNSATISFIED = unchecked((int)0x8A15C008); - public const int CONFIG_ERROR_ASSERTION_FAILED = unchecked((int)0x8A15C009); - public const int CONFIG_ERROR_MANUALLY_SKIPPED = unchecked((int)0x8A15C00A); - public const int CONFIG_ERROR_WARNING_NOT_ACCEPTED = unchecked((int)0x8A15C00B); - public const int CONFIG_ERROR_SET_DEPENDENCY_CYCLE = unchecked((int)0x8A15C00C); - public const int CONFIG_ERROR_INVALID_FIELD_VALUE = unchecked((int)0x8A15C00D); - public const int CONFIG_ERROR_MISSING_FIELD = unchecked((int)0x8A15C00E); - public const int CONFIG_ERROR_TEST_FAILED = unchecked((int)0x8A15C00F); - public const int CONFIG_ERROR_TEST_NOT_RUN = unchecked((int)0x8A15C010); - public const int WINGET_CONFIG_ERROR_GET_FAILED = unchecked((int)0x8A15C011); - - public const int CONFIG_ERROR_UNIT_NOT_INSTALLED = unchecked((int)0x8A15C101); - public const int CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY = unchecked((int)0x8A15C102); - public const int CONFIG_ERROR_UNIT_MULTIPLE_MATCHES = unchecked((int)0x8A15C103); - public const int CONFIG_ERROR_UNIT_INVOKE_GET = unchecked((int)0x8A15C104); - public const int CONFIG_ERROR_UNIT_INVOKE_TEST = unchecked((int)0x8A15C105); - public const int CONFIG_ERROR_UNIT_INVOKE_SET = unchecked((int)0x8A15C106); - public const int CONFIG_ERROR_UNIT_MODULE_CONFLICT = unchecked((int)0x8A15C107); - public const int CONFIG_ERROR_UNIT_IMPORT_MODULE = unchecked((int)0x8A15C108); - public const int CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT = unchecked((int)0x8A15C109); - public const int CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT = unchecked((int)0x8A15C110); - public const int CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN = unchecked((int)0x8A15C111); - } - -#pragma warning restore SA1310 // Field names should not contain underscore -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + /// + /// Constants. + /// + public class Constants + { +#pragma warning disable SA1600 // ElementsMustBeDocumented +#pragma warning disable SA1310 // Field names should not contain underscore + + // Runtime test parameters + public const string PackagedContextParameter = "PackagedContext"; + public const string AICLIPathParameter = "AICLIPath"; + public const string AICLIPackagePathParameter = "AICLIPackagePath"; + public const string VerboseLoggingParameter = "VerboseLogging"; + public const string LooseFileRegistrationParameter = "LooseFileRegistration"; + public const string InvokeCommandInDesktopPackageParameter = "InvokeCommandInDesktopPackage"; + public const string StaticFileRootPathParameter = "StaticFileRootPath"; + public const string LocalServerCertPathParameter = "LocalServerCertPath"; + public const string ExeInstallerPathParameter = "ExeTestInstallerPath"; + public const string MsiInstallerPathParameter = "MsiTestInstallerPath"; + public const string MsiInstallerV2PathParameter = "MsiTestInstallerV2Path"; + public const string MsixInstallerPathParameter = "MsixTestInstallerPath"; + public const string FontPathParameter = "FontTestPath"; + public const string PackageCertificatePathParameter = "PackageCertificatePath"; + public const string PowerShellModulePathParameter = "PowerShellModulePath"; + public const string SkipTestSourceParameter = "SkipTestSource"; + public const string ForcedExperimentalFeaturesParameter = "ForcedExperimentalFeatures"; + public const string InprocTestbedPathParameter = "InprocTestbedPath"; + public const string InprocTestbedUseTestPackageParameter = "InprocTestbedUseTestPackage"; + + // Test Sources + public const string DefaultWingetSourceName = @"winget"; + public const string DefaultWingetSourceUrl = @"https://winget.azureedge.net/cache"; + public const string DefaultMSStoreSourceName = @"msstore"; + public const string DefaultMSStoreSourceUrl = @"https://storeedgefd.dsx.mp.microsoft.com/v9.0"; + public const string DefaultMSStoreSourceType = "Microsoft.Rest"; + public const string DefaultMSStoreSourceIdentifier = "StoreEdgeFD"; + public const string TestSourceName = @"TestSource"; + public const string TestAlternateSourceName = @"TestSource2"; + public const string TestSourceUrl = @"https://localhost:5001/TestKit"; + public const string TestSourceType = "Microsoft.PreIndexed.Package"; + public const string TestSourceIdentifier = @"WingetE2E.Tests_8wekyb3d8bbwe"; + public const string RestTestSourceName = @"TestRestSource"; + public const string RestTestSourceUrl = @"https://localhost:5001/TestKit/TestData/TestRestSource"; + public const string RestTestSourceType = "Microsoft.Rest"; + + public const string AICLIPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; + public const string AICLIPackageName = "WinGetDevCLI"; + public const string AICLIPackagePublisherHash = "8wekyb3d8bbwe"; + public const string AICLIAppId = "WinGetDev"; + + public const string TestPackage = "TëstPackage.msix"; + public const string ExeInstaller = "AppInstallerTestExeInstaller"; + public const string MsiInstaller = "AppInstallerTestMsiInstaller"; + public const string MsixInstaller = "AppInstallerTestMsixInstaller"; + public const string ZipInstaller = "AppInstallerTestZipInstaller"; + public const string ExeInstallerFileName = "AppInstallerTestExeInstaller.exe"; + public const string MsiInstallerFileName = "AppInstallerTestMsiInstaller.msi"; + public const string MsiInstallerV2FileName = "AppInstallerTestMsiInstallerV2.msi"; + public const string MsixInstallerFileName = "AppInstallerTestMsixInstaller.msix"; + public const string ZipInstallerFileName = "AppInstallerTestZipInstaller.zip"; + public const string FontFileName = "AppInstallerTestFont.ttf"; + public const string ModifyRepairInstaller = "AppInstallerTest.TestModifyRepair"; + public const string IndexPackage = "source.msix"; + public const string MakeAppx = "makeappx.exe"; + public const string SignTool = "signtool.exe"; + public const string IndexCreationTool = "IndexCreationTool"; + public const string WinGetUtil = "WinGetUtil"; + public const string E2ETestLogsPathPackaged = @"Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir"; + public const string E2ETestLogsPathUnpackaged = @"WinGet\defaultState"; + public const string CheckpointDirectoryPackaged = @"Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\Checkpoints"; + public const string CheckpointDirectoryUnpackaged = @"Microsoft\WinGet\State\defaultState\Checkpoints"; + + // Installer filename + public const string TestCommandExe = "testCommand.exe"; + public const string AppInstallerTestExeInstallerExe = "AppInstallerTestExeInstaller.exe"; + public const string AppInstallerTestMsiInstallerMsi = "AppInstallerTestMsiInstaller.msi"; + public const string AppInstallerTestZipInstallerZip = "AppInstallerTestZipInstaller.zip"; + + // Test installers' package IDs + public const string ExeInstallerPackageId = "AppInstallerTest.TestExeInstaller"; + public const string MsiInstallerPackageId = "AppInstallerTest.TestMsiInstaller"; + public const string MsixInstallerPackageId = "AppInstallerTest.TestMsixInstaller"; + public const string PortableExePackageId = "AppInstallerTest.TestPortableExe"; + public const string PortableExeWithCommandPackageId = "AppInstallerTest.TestPortableExeWithCommand"; + + public const string ExeInstalledDefaultProductCode = "{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}"; + public const string MsiInstallerProductCode = "{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}"; + public const string MsixInstallerName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; + public const string MsixInstallerPackageFamilyName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e_8wekyb3d8bbwe"; + + public const string TestExeInstalledFileName = "TestExeInstalled.txt"; + public const string TestExeUninstallerFileName = "UninstallTestExe.bat"; + public const string TestExeUninstalledFileName = "TestExeUninstalled.txt"; + public const string TestExeRepairCompletedFileName = "TestExeRepairCompleted.txt"; + + // PowerShell Cmdlets + public const string FindCmdlet = "Find-WinGetPackage"; + public const string GetCmdlet = "Get-WinGetPackage"; + public const string GetSourceCmdlet = "Get-WinGetSource"; + public const string InstallCmdlet = "Install-WinGetPackage"; + public const string UninstallCmdlet = "Uninstall-WinGetPackage"; + public const string UpdateCmdlet = "Update-WinGetPackage"; + + public const string WindowsPackageManagerServer = "WindowsPackageManagerServer"; + + // Locations + public const string LocalAppData = "LocalAppData"; + public const string Dependencies = "Dependencies"; + + // Package dir + public const string PortableExePackageDirName = $"{PortableExePackageId}_{TestSourceIdentifier}"; + public const string PortableExeWithCommandPackageDirName = $"{PortableExeWithCommandPackageId}_{TestSourceIdentifier}"; + + // Registry keys + public const string WinGetPackageIdentifier = "WinGetPackageIdentifier"; + public const string WinGetSourceIdentifier = "WinGetSourceIdentifier"; + public const string UninstallSubKey = @"Software\Microsoft\Windows\CurrentVersion\Uninstall"; + public const string PathSubKey_User = @"Environment"; + public const string PathSubKey_Machine = @"SYSTEM\CurrentControlSet\Control\Session Manager\Environment"; + public const string FontsSubKey = @"Software\Microsoft\Windows NT\CurrentVersion\Fonts"; + + // User settings + public const string ArchiveExtractionMethod = "archiveExtractionMethod"; + public const string PortablePackageUserRoot = "portablePackageUserRoot"; + public const string PortablePackageMachineRoot = "portablePackageMachineRoot"; + public const string InstallBehaviorScope = "scope"; + public const string InstallerTypes = "installerTypes"; + public const string DefaultModuleRoot = "defaultModuleRoot"; + + // Configuration + public const string PSGalleryName = "PSGallery"; + public const string TestRepoName = "AppInstallerCLIE2ETestsRepo"; + public const string GalleryTestModuleName = "XmlContentDsc"; + public const string SimpleTestModuleName = "xE2ETestResource"; + public const string LocalModuleDescriptor = "[Local]"; + public const string TestRegistryPath = "Software\\Microsoft\\WinGet\\Tests"; + + // Group Policy Error Message + public const string BlockByWinGetPolicyErrorMessage = "This operation is disabled by Group Policy : Enable Windows Package Manager"; + + /// + /// Error codes. + /// + public class ErrorCode + { + public const int S_OK = 0; + public const int S_FALSE = 1; + public const int ERROR_FILE_NOT_FOUND = unchecked((int)0x80070002); + public const int ERROR_PATH_NOT_FOUND = unchecked((int)0x80070003); + public const int E_INVALIDARG = unchecked((int)0x80070057); + public const int ERROR_NO_RANGES_PROCESSED = unchecked((int)0x80070138); + public const int OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY = unchecked((int)0x8051100F); + public const int ERROR_OLD_WIN_VERSION = unchecked((int)0x8007047E); + public const int HTTP_E_STATUS_NOT_FOUND = unchecked((int)0x80190194); + public const int E_ABORT = unchecked((int)0x80004004); + + // AICLI custom HRESULTs + public const int ERROR_INTERNAL_ERROR = unchecked((int)0x8A150001); + public const int ERROR_INVALID_CL_ARGUMENTS = unchecked((int)0x8A150002); + public const int ERROR_COMMAND_FAILED = unchecked((int)0x8A150003); + public const int ERROR_MANIFEST_FAILED = unchecked((int)0x8A150004); + public const int ERROR_CTRL_SIGNAL_RECEIVED = unchecked((int)0x8A150005); + public const int ERROR_SHELLEXEC_INSTALL_FAILED = unchecked((int)0x8A150006); + public const int ERROR_UNSUPPORTED_MANIFESTVERSION = unchecked((int)0x8A150007); + public const int ERROR_DOWNLOAD_FAILED = unchecked((int)0x8A150008); + public const int ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX = unchecked((int)0x8A150009); + public const int ERROR_INDEX_INTEGRITY_COMPROMISED = unchecked((int)0x8A15000A); + public const int ERROR_SOURCES_INVALID = unchecked((int)0x8A15000B); + public const int ERROR_SOURCE_NAME_ALREADY_EXISTS = unchecked((int)0x8A15000C); + public const int ERROR_INVALID_SOURCE_TYPE = unchecked((int)0x8A15000D); + public const int ERROR_PACKAGE_IS_BUNDLE = unchecked((int)0x8A15000E); + public const int ERROR_SOURCE_DATA_MISSING = unchecked((int)0x8A15000F); + public const int ERROR_NO_APPLICABLE_INSTALLER = unchecked((int)0x8A150010); + public const int ERROR_INSTALLER_HASH_MISMATCH = unchecked((int)0x8A150011); + public const int ERROR_SOURCE_NAME_DOES_NOT_EXIST = unchecked((int)0x8A150012); + public const int ERROR_SOURCE_ARG_ALREADY_EXISTS = unchecked((int)0x8A150013); + public const int ERROR_NO_APPLICATIONS_FOUND = unchecked((int)0x8A150014); + public const int ERROR_NO_SOURCES_DEFINED = unchecked((int)0x8A150015); + public const int ERROR_MULTIPLE_APPLICATIONS_FOUND = unchecked((int)0x8A150016); + public const int ERROR_NO_MANIFEST_FOUND = unchecked((int)0x8A150017); + public const int ERROR_EXTENSION_PUBLIC_FAILED = unchecked((int)0x8A150018); + public const int ERROR_COMMAND_REQUIRES_ADMIN = unchecked((int)0x8A150019); + public const int ERROR_SOURCE_NOT_SECURE = unchecked((int)0x8A15001A); + public const int ERROR_MSSTORE_BLOCKED_BY_POLICY = unchecked((int)0x8A15001B); + public const int ERROR_MSSTORE_APP_BLOCKED_BY_POLICY = unchecked((int)0x8A15001C); + public const int ERROR_EXPERIMENTAL_FEATURE_DISABLED = unchecked((int)0x8A15001D); + public const int ERROR_MSSTORE_INSTALL_FAILED = unchecked((int)0x8A15001E); + public const int ERROR_COMPLETE_INPUT_BAD = unchecked((int)0x8A15001F); + public const int ERROR_YAML_INIT_FAILED = unchecked((int)0x8A150020); + public const int ERROR_INVALID_MAPPING_KEY = unchecked((int)0x8A150021); + public const int ERROR_DUPLICATE_MAPPING_KEY = unchecked((int)0x8A150022); + public const int ERROR_YAML_INVALID_OPERATION = unchecked((int)0x8A150023); + public const int ERROR_YAML_DOC_BUILD_FAILED = unchecked((int)0x8A150024); + public const int ERROR_YAML_INVALID_EMITTER_STATE = unchecked((int)0x8A150025); + public const int ERROR_YAML_INVALID_DATA = unchecked((int)0x8A150026); + public const int ERROR_LIBYAML_ERROR = unchecked((int)0x8A150027); + public const int ERROR_MANIFEST_VALIDATION_WARNING = unchecked((int)0x8A150028); + public const int ERROR_MANIFEST_VALIDATION_FAILURE = unchecked((int)0x8A150029); + public const int ERROR_INVALID_MANIFEST = unchecked((int)0x8A15002A); + public const int ERROR_UPDATE_NOT_APPLICABLE = unchecked((int)0x8A15002B); + public const int ERROR_UPDATE_ALL_HAS_FAILURE = unchecked((int)0x8A15002C); + public const int ERROR_INSTALLER_SECURITY_CHECK_FAILED = unchecked((int)0x8A15002D); + public const int ERROR_DOWNLOAD_SIZE_MISMATCH = unchecked((int)0x8A15002E); + public const int ERROR_NO_UNINSTALL_INFO_FOUND = unchecked((int)0x8A15002F); + public const int ERROR_EXEC_UNINSTALL_COMMAND_FAILED = unchecked((int)0x8A150030); + public const int ERROR_ICU_BREAK_ITERATOR_ERROR = unchecked((int)0x8A150031); + public const int ERROR_ICU_CASEMAP_ERROR = unchecked((int)0x8A150032); + public const int ERROR_ICU_REGEX_ERROR = unchecked((int)0x8A150033); + public const int ERROR_IMPORT_INSTALL_FAILED = unchecked((int)0x8A150034); + public const int ERROR_NOT_ALL_PACKAGES_FOUND = unchecked((int)0x8A150035); + public const int ERROR_JSON_INVALID_FILE = unchecked((int)0x8A150036); + public const int ERROR_SOURCE_NOT_REMOTE = unchecked((int)0x8A150037); + public const int ERROR_UNSUPPORTED_RESTSOURCE = unchecked((int)0x8A150038); + public const int ERROR_RESTSOURCE_INVALID_DATA = unchecked((int)0x8A150039); + public const int ERROR_BLOCKED_BY_POLICY = unchecked((int)0x8A15003A); + public const int ERROR_RESTAPI_INTERNAL_ERROR = unchecked((int)0x8A15003B); + public const int ERROR_RESTSOURCE_INVALID_URL = unchecked((int)0x8A15003C); + public const int ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE = unchecked((int)0x8A15003D); + public const int ERROR_RESTSOURCE_INVALID_VERSION = unchecked((int)0x8A15003E); + public const int ERROR_SOURCE_DATA_INTEGRITY_FAILURE = unchecked((int)0x8A15003F); + public const int ERROR_STREAM_READ_FAILURE = unchecked((int)0x8A150040); + public const int ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED = unchecked((int)0x8A150041); + public const int ERROR_PROMPT_INPUT_ERROR = unchecked((int)0x8A150042); + public const int ERROR_UNSUPPORTED_SOURCE_REQUEST = unchecked((int)0x8A150043); + public const int ERROR_RESTAPI_ENDPOINT_NOT_FOUND = unchecked((int)0x8A150044); + public const int ERROR_SOURCE_OPEN_FAILED = unchecked((int)0x8A150045); + public const int ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED = unchecked((int)0x8A150046); + public const int ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH = unchecked((int)0x8A150047); + public const int ERROR_MISSING_RESOURCE_FILE = unchecked((int)0x8A150048); + public const int ERROR_MSI_INSTALL_FAILED = unchecked((int)0x8A150049); + public const int ERROR_INVALID_MSIEXEC_ARGUMENT = unchecked((int)0x8A15004A); + public const int ERROR_FAILED_TO_OPEN_ALL_SOURCES = unchecked((int)0x8A15004B); + public const int ERROR_DEPENDENCIES_VALIDATION_FAILED = unchecked((int)0x8A15004C); + public const int ERROR_MISSING_PACKAGE = unchecked((int)0x8A15004D); + public const int ERROR_INVALID_TABLE_COLUMN = unchecked((int)0x8A15004E); + public const int ERROR_UPGRADE_VERSION_NOT_NEWER = unchecked((int)0x8A15004F); + public const int ERROR_UPGRADE_VERSION_UNKNOWN = unchecked((int)0x8A150050); + public const int ERROR_ICU_CONVERSION_ERROR = unchecked((int)0x8A150051); + public const int ERROR_PORTABLE_INSTALL_FAILED = unchecked((int)0x8A150052); + public const int ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED = unchecked((int)0x8A150053); + public const int ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS = unchecked((int)0x8A150054); + public const int ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY = unchecked((int)0x8A150055); + public const int ERROR_INSTALLER_PROHIBITS_ELEVATION = unchecked((int)0x8A150056); + public const int ERROR_PORTABLE_UNINSTALL_FAILED = unchecked((int)0x8A150057); + public const int ERROR_ARP_VERSION_VALIDATION_FAILED = unchecked((int)0x8A150058); + public const int ERROR_UNSUPPORTED_ARGUMENT = unchecked((int)0x8A150059); + public const int ERROR_BIND_WITH_EMBEDDED_NULL = unchecked((int)0x8A15005A); + public const int ERROR_NESTEDINSTALLER_NOT_FOUND = unchecked((int)0x8A15005B); + public const int ERROR_EXTRACT_ARCHIVE_FAILED = unchecked((int)0x8A15005C); + public const int ERROR_NESTEDINSTALLER_INVALID_PATH = unchecked((int)0x8A15005D); + public const int ERROR_PINNED_CERTIFICATE_MISMATCH = unchecked((int)0x8A15005E); + public const int ERROR_INSTALL_LOCATION_REQUIRED = unchecked((int)0x8A15005F); + public const int ERROR_ARCHIVE_SCAN_FAILED = unchecked((int)0x8A150060); + public const int ERROR_PACKAGE_ALREADY_INSTALLED = unchecked((int)0x8A150061); + public const int ERROR_PIN_ALREADY_EXISTS = unchecked((int)0x8A150062); + public const int ERROR_PIN_DOES_NOT_EXIST = unchecked((int)0x8A150063); + public const int ERROR_CANNOT_OPEN_PINNING_INDEX = unchecked((int)0x8A150064); + public const int ERROR_MULTIPLE_INSTALL_FAILED = unchecked((int)0x8A150065); + public const int ERROR_MULTIPLE_UNINSTALL_FAILED = unchecked((int)0x8A150066); + public const int ERROR_NOT_ALL_QUERIES_FOUND_SINGLE = unchecked((int)0x8A150067); + public const int ERROR_PACKAGE_IS_PINNED = unchecked((int)0x8A150068); + public const int ERROR_PACKAGE_IS_STUB = unchecked((int)0x8A150069); + public const int ERROR_APPTERMINATION_RECEIVED = unchecked((int)0x8A15006A); + public const int ERROR_DOWNLOAD_DEPENDENCIES = unchecked((int)0x8A15006B); + public const int ERROR_DOWNLOAD_COMMAND_PROHIBITED = unchecked((int)0x8A15006C); + public const int ERROR_SERVICE_UNAVAILABLE = unchecked((int)0x8A15006D); + public const int ERROR_RESUME_ID_NOT_FOUND = unchecked((int)0x8A15006E); + public const int ERROR_CLIENT_VERSION_MISMATCH = unchecked((int)0x8A15006F); + public const int ERROR_INVALID_RESUME_STATE = unchecked((int)0x8A150070); + public const int ERROR_CANNOT_OPEN_CHECKPOINT_INDEX = unchecked((int)0x8A150071); + + public const int ERROR_NO_REPAIR_INFO_FOUND = unchecked((int)0x8A150079); + public const int ERROR_REPAIR_NOT_SUPPORTED = unchecked((int)0x8A15007C); + public const int ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED = unchecked((int)0x8A15007D); + + public const int ERROR_INSTALLER_ZERO_BYTE_FILE = unchecked((int)0x8A150086); + public const int ERROR_FONT_INSTALL_FAILED = unchecked((int)0x8A150087); + public const int ERROR_FONT_FILE_NOT_SUPPORTED = unchecked((int)0x8A150088); + + public const int ERROR_INSTALL_PACKAGE_IN_USE = unchecked((int)0x8A150101); + public const int ERROR_INSTALL_INSTALL_IN_PROGRESS = unchecked((int)0x8A150102); + public const int ERROR_INSTALL_FILE_IN_USE = unchecked((int)0x8A150103); + public const int ERROR_INSTALL_MISSING_DEPENDENCY = unchecked((int)0x8A150104); + public const int ERROR_INSTALL_DISK_FULL = unchecked((int)0x8A150105); + public const int ERROR_INSTALL_INSUFFICIENT_MEMORY = unchecked((int)0x8A150106); + public const int ERROR_INSTALL_NO_NETWORK = unchecked((int)0x8A150107); + public const int ERROR_INSTALL_CONTACT_SUPPORT = unchecked((int)0x8A150108); + public const int ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH = unchecked((int)0x8A150109); + public const int ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL = unchecked((int)0x8A15010A); + public const int ERROR_INSTALL_REBOOT_INITIATED = unchecked((int)0x8A15010B); + public const int ERROR_INSTALL_CANCELLED_BY_USER = unchecked((int)0x8A15010C); + public const int ERROR_INSTALL_ALREADY_INSTALLED = unchecked((int)0x8A15010D); + public const int ERROR_INSTALL_DOWNGRADE = unchecked((int)0x8A15010E); + public const int ERROR_INSTALL_BLOCKED_BY_POLICY = unchecked((int)0x8A15010F); + public const int ERROR_INSTALL_DEPENDENCIES = unchecked((int)0x8A150110); + public const int ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION = unchecked((int)0x8A150111); + public const int ERROR_INSTALL_INVALID_PARAMETER = unchecked((int)0x8A150112); + public const int ERROR_INSTALL_SYSTEM_NOT_SUPPORTED = unchecked((int)0x8A150113); + public const int APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED = unchecked((int)0x8A150114); + + public const int INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND = unchecked((int)0x8A150201); + public const int INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE = unchecked((int)0x0A150202); + public const int INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND = unchecked((int)0x8A150203); + public const int INSTALLED_STATUS_FILE_HASH_MISMATCH = unchecked((int)0x8A150204); + public const int INSTALLED_STATUS_FILE_NOT_FOUND = unchecked((int)0x8A150205); + public const int INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK = unchecked((int)0x0A150206); + public const int INSTALLED_STATUS_FILE_ACCESS_ERROR = unchecked((int)0x8A150207); + + public const int CONFIG_ERROR_INVALID_CONFIGURATION_FILE = unchecked((int)0x8A15C001); + public const int CONFIG_ERROR_INVALID_YAML = unchecked((int)0x8A15C002); + public const int CONFIG_ERROR_INVALID_FIELD_TYPE = unchecked((int)0x8A15C003); + public const int CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION = unchecked((int)0x8A15C004); + public const int CONFIG_ERROR_SET_APPLY_FAILED = unchecked((int)0x8A15C005); + public const int CONFIG_ERROR_DUPLICATE_IDENTIFIER = unchecked((int)0x8A15C006); + public const int CONFIG_ERROR_MISSING_DEPENDENCY = unchecked((int)0x8A15C007); + public const int CONFIG_ERROR_DEPENDENCY_UNSATISFIED = unchecked((int)0x8A15C008); + public const int CONFIG_ERROR_ASSERTION_FAILED = unchecked((int)0x8A15C009); + public const int CONFIG_ERROR_MANUALLY_SKIPPED = unchecked((int)0x8A15C00A); + public const int CONFIG_ERROR_WARNING_NOT_ACCEPTED = unchecked((int)0x8A15C00B); + public const int CONFIG_ERROR_SET_DEPENDENCY_CYCLE = unchecked((int)0x8A15C00C); + public const int CONFIG_ERROR_INVALID_FIELD_VALUE = unchecked((int)0x8A15C00D); + public const int CONFIG_ERROR_MISSING_FIELD = unchecked((int)0x8A15C00E); + public const int CONFIG_ERROR_TEST_FAILED = unchecked((int)0x8A15C00F); + public const int CONFIG_ERROR_TEST_NOT_RUN = unchecked((int)0x8A15C010); + public const int WINGET_CONFIG_ERROR_GET_FAILED = unchecked((int)0x8A15C011); + + public const int CONFIG_ERROR_UNIT_NOT_INSTALLED = unchecked((int)0x8A15C101); + public const int CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY = unchecked((int)0x8A15C102); + public const int CONFIG_ERROR_UNIT_MULTIPLE_MATCHES = unchecked((int)0x8A15C103); + public const int CONFIG_ERROR_UNIT_INVOKE_GET = unchecked((int)0x8A15C104); + public const int CONFIG_ERROR_UNIT_INVOKE_TEST = unchecked((int)0x8A15C105); + public const int CONFIG_ERROR_UNIT_INVOKE_SET = unchecked((int)0x8A15C106); + public const int CONFIG_ERROR_UNIT_MODULE_CONFLICT = unchecked((int)0x8A15C107); + public const int CONFIG_ERROR_UNIT_IMPORT_MODULE = unchecked((int)0x8A15C108); + public const int CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT = unchecked((int)0x8A15C109); + public const int CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT = unchecked((int)0x8A15C110); + public const int CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN = unchecked((int)0x8A15C111); + } + +#pragma warning restore SA1310 // Field names should not contain underscore +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/AppInstallerCLIE2ETests/DSCv3AdminSettingsResourceCommand.cs b/src/AppInstallerCLIE2ETests/DSCv3AdminSettingsResourceCommand.cs index 11543e2c6b..71ba28ac2f 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3AdminSettingsResourceCommand.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3AdminSettingsResourceCommand.cs @@ -1,370 +1,370 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.Collections.Generic; - using System.Text.Json; - using System.Text.Json.Nodes; - using System.Text.Json.Serialization; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// `Configure` command tests. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public class DSCv3AdminSettingsResourceCommand : DSCv3ResourceTestBase - { - private const string AdminSettingsResource = "admin-settings"; - private const string SettingsPropertyName = "settings"; - - // Bool settings - private const string BypassCertificatePinningForMicrosoftStore = "BypassCertificatePinningForMicrosoftStore"; - private const string InstallerHashOverride = "InstallerHashOverride"; - private const string LocalArchiveMalwareScanOverride = "LocalArchiveMalwareScanOverride"; - private const string LocalManifestFiles = "LocalManifestFiles"; - private const string ProxyCommandLineOptions = "ProxyCommandLineOptions"; - - // String settings - private const string DefaultProxy = "DefaultProxy"; - - // Not a setting - private const string NotAnAdminSettingName = "NotAnAdminSetting"; - - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - EnsureTestResourcePresence(); - } - - /// - /// Teardown done once after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTeardown() - { - ResetAllSettings(); - GroupPolicyHelper.DeleteExistingPolicies(); - } - - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - ResetAllSettings(); - GroupPolicyHelper.DeleteExistingPolicies(); - } - - /// - /// Calls `get` on the `admin-settings` resource with the value not present. - /// - /// The resource function to invoke. - [TestCase(GetFunction)] - [TestCase(ExportFunction)] - public void AdminSettings_Get(string function) - { - var result = RunDSCv3Command(AdminSettingsResource, function, null); - AssertSuccessfulResourceRun(ref result); - - AdminSettingsResourceData output = GetSingleOutputLineAs(result.StdOut); - Assert.IsNotNull(output); - Assert.IsNotNull(output.Settings); - Assert.IsTrue(output.Settings.ContainsKey(LocalManifestFiles)); - Assert.IsFalse(output.Settings.ContainsKey(DefaultProxy)); - Assert.IsFalse(output.Settings.ContainsKey(NotAnAdminSettingName)); - } - - /// - /// Calls `test` on the `admin-settings` resource with a bool setting. - /// - /// The setting to test. - [TestCase(BypassCertificatePinningForMicrosoftStore)] - [TestCase(InstallerHashOverride)] - [TestCase(LocalArchiveMalwareScanOverride)] - [TestCase(LocalManifestFiles)] - [TestCase(ProxyCommandLineOptions)] - public void AdminSettings_Test_BoolSetting(string settingName) - { - AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; - - resourceData.Settings[settingName] = true; - var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfBoolSetting(ref result, settingName, false, true); - - resourceData.Settings[settingName] = false; - result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfBoolSetting(ref result, settingName, false, false); - - Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {settingName}").ExitCode); - - resourceData.Settings[settingName] = false; - result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfBoolSetting(ref result, settingName, true, false); - - resourceData.Settings[settingName] = true; - result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfBoolSetting(ref result, settingName, true, true); - } - - /// - /// Calls `test` on the `admin-settings` resource with a string setting. - /// - /// The setting to test. - [TestCase(DefaultProxy)] - public void AdminSettings_Test_StringSetting(string settingName) - { - const string testValue = "A string to test"; - const string differentTestValue = "A different value"; - - AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; - - resourceData.Settings[settingName] = null; - var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfStringSetting(ref result, settingName, null, null); - - resourceData.Settings[settingName] = testValue; - result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfStringSetting(ref result, settingName, null, testValue); - - Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings set", $"{settingName} \"{testValue}\"").ExitCode); - - resourceData.Settings[settingName] = null; - result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfStringSetting(ref result, settingName, testValue, null); - - resourceData.Settings[settingName] = testValue; - result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfStringSetting(ref result, settingName, testValue, testValue); - - resourceData.Settings[settingName] = differentTestValue; - result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertTestOfStringSetting(ref result, settingName, testValue, differentTestValue); - } - - /// - /// Calls `test` on the `admin-settings` resource with a complex input. - /// - [Test] - public void AdminSettings_Test_Complex() - { - Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {LocalArchiveMalwareScanOverride}").ExitCode); - Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {LocalManifestFiles}").ExitCode); - - AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; - resourceData.Settings[LocalArchiveMalwareScanOverride] = true; - resourceData.Settings[BypassCertificatePinningForMicrosoftStore] = false; - resourceData.Settings[DefaultProxy] = null; - - var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.IsTrue(output.InDesiredState); - Assert.IsNotNull(output.Settings); - - AssertDiffState(diff, []); - } - - /// - /// Calls `set` on the `admin-settings` resource with a bool setting. - /// - /// The setting to test. - [TestCase(BypassCertificatePinningForMicrosoftStore)] - [TestCase(InstallerHashOverride)] - [TestCase(LocalArchiveMalwareScanOverride)] - [TestCase(LocalManifestFiles)] - [TestCase(ProxyCommandLineOptions)] - public void AdminSettings_Set_BoolSetting(string settingName) - { - AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; - - resourceData.Settings[settingName] = true; - var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfBoolSetting(ref result, settingName, false, true); - - result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfBoolSetting(ref result, settingName, true, true); - - resourceData.Settings[settingName] = false; - result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfBoolSetting(ref result, settingName, true, false); - - result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfBoolSetting(ref result, settingName, false, false); - } - - /// - /// Calls `set` on the `admin-settings` resource with a string setting. - /// - /// The setting to test. - [TestCase(DefaultProxy)] - public void AdminSettings_Set_StringSetting(string settingName) - { - const string testValue = "A string to test"; - const string differentTestValue = "A different value"; - - AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; - - resourceData.Settings[settingName] = null; - var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfStringSetting(ref result, settingName, null, null); - - resourceData.Settings[settingName] = testValue; - result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfStringSetting(ref result, settingName, null, testValue); - - resourceData.Settings[settingName] = testValue; - result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfStringSetting(ref result, settingName, testValue, testValue); - - resourceData.Settings[settingName] = differentTestValue; - result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfStringSetting(ref result, settingName, testValue, differentTestValue); - - resourceData.Settings[settingName] = null; - result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSetOfStringSetting(ref result, settingName, differentTestValue, null); - } - - /// - /// Calls `set` on the `admin-settings` resource with a complex input. - /// - [Test] - public void AdminSettings_Set_Complex() - { - const string testValue = "A string to test"; - - AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; - resourceData.Settings[LocalArchiveMalwareScanOverride] = true; - resourceData.Settings[BypassCertificatePinningForMicrosoftStore] = false; - resourceData.Settings[DefaultProxy] = testValue; - - var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.IsNotNull(output.Settings); - Assert.AreEqual(JsonValueKind.True, output.Settings[LocalArchiveMalwareScanOverride].AsValue().GetValueKind()); - Assert.AreEqual(JsonValueKind.False, output.Settings[BypassCertificatePinningForMicrosoftStore].AsValue().GetValueKind()); - Assert.AreEqual(testValue, output.Settings[DefaultProxy].AsValue().GetValue()); - - AssertDiffState(diff, [ SettingsPropertyName ]); - } - - /// - /// Calls `set` on the `admin-settings` resource attempting to change a setting with group policy enabled. - /// - [Test] - public void AdminSettings_Set_GroupPolicyBlocked() - { - GroupPolicyHelper.EnableHashOverride.Disable(); - - AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; - resourceData.Settings[InstallerHashOverride] = true; - - var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - - private static void ResetAllSettings() - { - Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings reset", "--all").ExitCode); - } - - private static void AssertTestOfBoolSetting(ref TestCommon.RunCommandResult result, string settingName, bool expectedState, bool testState) - { - AssertSuccessfulResourceRun(ref result); - - bool inDesiredState = expectedState == testState; - - (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.AreEqual(inDesiredState, output.InDesiredState); - Assert.IsNotNull(output.Settings); - Assert.IsTrue(output.Settings.ContainsKey(settingName)); - Assert.AreEqual(expectedState ? JsonValueKind.True : JsonValueKind.False, output.Settings[settingName].AsValue().GetValueKind()); - - AssertDiffState(diff, inDesiredState ? [] : [ SettingsPropertyName ]); - } - - private static void AssertSetOfBoolSetting(ref TestCommon.RunCommandResult result, string settingName, bool previousState, bool desiredState) - { - AssertSuccessfulResourceRun(ref result); - - bool inDesiredState = previousState == desiredState; - - (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.IsNotNull(output.Settings); - Assert.IsTrue(output.Settings.ContainsKey(settingName)); - Assert.AreEqual(desiredState ? JsonValueKind.True : JsonValueKind.False, output.Settings[settingName].AsValue().GetValueKind()); - - AssertDiffState(diff, inDesiredState ? [] : [ SettingsPropertyName ]); - } - - private static void AssertTestOfStringSetting(ref TestCommon.RunCommandResult result, string settingName, string expectedState, string testState) - { - AssertSuccessfulResourceRun(ref result); - - bool inDesiredState = expectedState == testState; - - (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.AreEqual(inDesiredState, output.InDesiredState); - Assert.IsNotNull(output.Settings); - if (expectedState != null) - { - Assert.IsTrue(output.Settings.ContainsKey(settingName)); - Assert.AreEqual(expectedState, output.Settings[settingName].AsValue().GetValue()); - } - else - { - Assert.IsFalse(output.Settings.ContainsKey(settingName)); - } - - AssertDiffState(diff, inDesiredState ? [] : [SettingsPropertyName]); - } - - private static void AssertSetOfStringSetting(ref TestCommon.RunCommandResult result, string settingName, string previousState, string desiredState) - { - AssertSuccessfulResourceRun(ref result); - - bool inDesiredState = previousState == desiredState; - - (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.IsNotNull(output.Settings); - if (desiredState != null) - { - Assert.IsTrue(output.Settings.ContainsKey(settingName)); - Assert.AreEqual(desiredState, output.Settings[settingName].AsValue().GetValue()); - } - else - { - Assert.IsFalse(output.Settings.ContainsKey(settingName)); - } - - AssertDiffState(diff, inDesiredState ? [] : [SettingsPropertyName]); - } - - private class AdminSettingsResourceData - { - [JsonPropertyName(InDesiredStatePropertyName)] - public bool? InDesiredState { get; set; } - - public JsonObject Settings { get; set; } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.Collections.Generic; + using System.Text.Json; + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure` command tests. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public class DSCv3AdminSettingsResourceCommand : DSCv3ResourceTestBase + { + private const string AdminSettingsResource = "admin-settings"; + private const string SettingsPropertyName = "settings"; + + // Bool settings + private const string BypassCertificatePinningForMicrosoftStore = "BypassCertificatePinningForMicrosoftStore"; + private const string InstallerHashOverride = "InstallerHashOverride"; + private const string LocalArchiveMalwareScanOverride = "LocalArchiveMalwareScanOverride"; + private const string LocalManifestFiles = "LocalManifestFiles"; + private const string ProxyCommandLineOptions = "ProxyCommandLineOptions"; + + // String settings + private const string DefaultProxy = "DefaultProxy"; + + // Not a setting + private const string NotAnAdminSettingName = "NotAnAdminSetting"; + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + ResetAllSettings(); + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + ResetAllSettings(); + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Calls `get` on the `admin-settings` resource with the value not present. + /// + /// The resource function to invoke. + [TestCase(GetFunction)] + [TestCase(ExportFunction)] + public void AdminSettings_Get(string function) + { + var result = RunDSCv3Command(AdminSettingsResource, function, null); + AssertSuccessfulResourceRun(ref result); + + AdminSettingsResourceData output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + Assert.IsTrue(output.Settings.ContainsKey(LocalManifestFiles)); + Assert.IsFalse(output.Settings.ContainsKey(DefaultProxy)); + Assert.IsFalse(output.Settings.ContainsKey(NotAnAdminSettingName)); + } + + /// + /// Calls `test` on the `admin-settings` resource with a bool setting. + /// + /// The setting to test. + [TestCase(BypassCertificatePinningForMicrosoftStore)] + [TestCase(InstallerHashOverride)] + [TestCase(LocalArchiveMalwareScanOverride)] + [TestCase(LocalManifestFiles)] + [TestCase(ProxyCommandLineOptions)] + public void AdminSettings_Test_BoolSetting(string settingName) + { + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = true; + var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, false, true); + + resourceData.Settings[settingName] = false; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, false, false); + + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {settingName}").ExitCode); + + resourceData.Settings[settingName] = false; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, true, false); + + resourceData.Settings[settingName] = true; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfBoolSetting(ref result, settingName, true, true); + } + + /// + /// Calls `test` on the `admin-settings` resource with a string setting. + /// + /// The setting to test. + [TestCase(DefaultProxy)] + public void AdminSettings_Test_StringSetting(string settingName) + { + const string testValue = "A string to test"; + const string differentTestValue = "A different value"; + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = null; + var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, null, null); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, null, testValue); + + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings set", $"{settingName} \"{testValue}\"").ExitCode); + + resourceData.Settings[settingName] = null; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, testValue, null); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, testValue, testValue); + + resourceData.Settings[settingName] = differentTestValue; + result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertTestOfStringSetting(ref result, settingName, testValue, differentTestValue); + } + + /// + /// Calls `test` on the `admin-settings` resource with a complex input. + /// + [Test] + public void AdminSettings_Test_Complex() + { + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {LocalArchiveMalwareScanOverride}").ExitCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings", $"--enable {LocalManifestFiles}").ExitCode); + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + resourceData.Settings[LocalArchiveMalwareScanOverride] = true; + resourceData.Settings[BypassCertificatePinningForMicrosoftStore] = false; + resourceData.Settings[DefaultProxy] = null; + + var result = RunDSCv3Command(AdminSettingsResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsTrue(output.InDesiredState); + Assert.IsNotNull(output.Settings); + + AssertDiffState(diff, []); + } + + /// + /// Calls `set` on the `admin-settings` resource with a bool setting. + /// + /// The setting to test. + [TestCase(BypassCertificatePinningForMicrosoftStore)] + [TestCase(InstallerHashOverride)] + [TestCase(LocalArchiveMalwareScanOverride)] + [TestCase(LocalManifestFiles)] + [TestCase(ProxyCommandLineOptions)] + public void AdminSettings_Set_BoolSetting(string settingName) + { + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = true; + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, false, true); + + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, true, true); + + resourceData.Settings[settingName] = false; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, true, false); + + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfBoolSetting(ref result, settingName, false, false); + } + + /// + /// Calls `set` on the `admin-settings` resource with a string setting. + /// + /// The setting to test. + [TestCase(DefaultProxy)] + public void AdminSettings_Set_StringSetting(string settingName) + { + const string testValue = "A string to test"; + const string differentTestValue = "A different value"; + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + + resourceData.Settings[settingName] = null; + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, null, null); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, null, testValue); + + resourceData.Settings[settingName] = testValue; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, testValue, testValue); + + resourceData.Settings[settingName] = differentTestValue; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, testValue, differentTestValue); + + resourceData.Settings[settingName] = null; + result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSetOfStringSetting(ref result, settingName, differentTestValue, null); + } + + /// + /// Calls `set` on the `admin-settings` resource with a complex input. + /// + [Test] + public void AdminSettings_Set_Complex() + { + const string testValue = "A string to test"; + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + resourceData.Settings[LocalArchiveMalwareScanOverride] = true; + resourceData.Settings[BypassCertificatePinningForMicrosoftStore] = false; + resourceData.Settings[DefaultProxy] = testValue; + + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + Assert.AreEqual(JsonValueKind.True, output.Settings[LocalArchiveMalwareScanOverride].AsValue().GetValueKind()); + Assert.AreEqual(JsonValueKind.False, output.Settings[BypassCertificatePinningForMicrosoftStore].AsValue().GetValueKind()); + Assert.AreEqual(testValue, output.Settings[DefaultProxy].AsValue().GetValue()); + + AssertDiffState(diff, [ SettingsPropertyName ]); + } + + /// + /// Calls `set` on the `admin-settings` resource attempting to change a setting with group policy enabled. + /// + [Test] + public void AdminSettings_Set_GroupPolicyBlocked() + { + GroupPolicyHelper.EnableHashOverride.Disable(); + + AdminSettingsResourceData resourceData = new AdminSettingsResourceData() { Settings = new JsonObject() }; + resourceData.Settings[InstallerHashOverride] = true; + + var result = RunDSCv3Command(AdminSettingsResource, SetFunction, resourceData); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + private static void ResetAllSettings() + { + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("settings reset", "--all").ExitCode); + } + + private static void AssertTestOfBoolSetting(ref TestCommon.RunCommandResult result, string settingName, bool expectedState, bool testState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = expectedState == testState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.AreEqual(inDesiredState, output.InDesiredState); + Assert.IsNotNull(output.Settings); + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(expectedState ? JsonValueKind.True : JsonValueKind.False, output.Settings[settingName].AsValue().GetValueKind()); + + AssertDiffState(diff, inDesiredState ? [] : [ SettingsPropertyName ]); + } + + private static void AssertSetOfBoolSetting(ref TestCommon.RunCommandResult result, string settingName, bool previousState, bool desiredState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = previousState == desiredState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(desiredState ? JsonValueKind.True : JsonValueKind.False, output.Settings[settingName].AsValue().GetValueKind()); + + AssertDiffState(diff, inDesiredState ? [] : [ SettingsPropertyName ]); + } + + private static void AssertTestOfStringSetting(ref TestCommon.RunCommandResult result, string settingName, string expectedState, string testState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = expectedState == testState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.AreEqual(inDesiredState, output.InDesiredState); + Assert.IsNotNull(output.Settings); + if (expectedState != null) + { + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(expectedState, output.Settings[settingName].AsValue().GetValue()); + } + else + { + Assert.IsFalse(output.Settings.ContainsKey(settingName)); + } + + AssertDiffState(diff, inDesiredState ? [] : [SettingsPropertyName]); + } + + private static void AssertSetOfStringSetting(ref TestCommon.RunCommandResult result, string settingName, string previousState, string desiredState) + { + AssertSuccessfulResourceRun(ref result); + + bool inDesiredState = previousState == desiredState; + + (AdminSettingsResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.IsNotNull(output.Settings); + if (desiredState != null) + { + Assert.IsTrue(output.Settings.ContainsKey(settingName)); + Assert.AreEqual(desiredState, output.Settings[settingName].AsValue().GetValue()); + } + else + { + Assert.IsFalse(output.Settings.ContainsKey(settingName)); + } + + AssertDiffState(diff, inDesiredState ? [] : [SettingsPropertyName]); + } + + private class AdminSettingsResourceData + { + [JsonPropertyName(InDesiredStatePropertyName)] + public bool? InDesiredState { get; set; } + + public JsonObject Settings { get; set; } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs b/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs index d371defb5a..4fbf1e789d 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3PackageResourceCommand.cs @@ -1,623 +1,623 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.Collections.Generic; - using System.IO; - using System.Text.Json.Serialization; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// `Configure` command tests. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public class DSCv3PackageResourceCommand : DSCv3ResourceTestBase - { - private const string DefaultPackageIdentifier = Constants.ExeInstallerPackageId; - private const string DefaultPackageLowVersion = "1.0.0.0"; - private const string DefaultPackageMidVersion = "1.1.0.0"; - private const string DefaultPackageHighVersion = "2.0.0.0"; - private const string DefaultPackageInstallLocationEnvironmentVariableName = "WINGET_TEST_EXE_INSTALL_LOCATION"; - private const string PackageResource = "package"; - private const string VersionPropertyName = "version"; - private const string UseLatestPropertyName = "useLatest"; - - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - TestCommon.SetupTestSource(); - WinGetSettingsHelper.ConfigureLoggingLevel("verbose"); - EnsureTestResourcePresence(); - } - - /// - /// Teardown done once after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTeardown() - { - RemoveTestPackage(); - WinGetSettingsHelper.ConfigureLoggingLevel(null); - TestCommon.TearDownTestSource(); - } - - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - // Try clean up TestExeInstaller for failure cases where cleanup is not successful - RemoveTestPackage(); - } - - /// - /// Calls `get` on the `package` resource with the value not present. - /// - [Test] - public void Package_Get_NotPresent() - { - PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; - - var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - PackageResourceData output = GetSingleOutputLineAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(packageResourceData.Identifier, output.Identifier); - } - - /// - /// Calls `get` on the `package` resource with the package not existing. - /// - [Test] - public void Package_Get_UnknownIdentifier() - { - PackageResourceData packageResourceData = new PackageResourceData() { Identifier = "Not.An.Existing.Identifier.123456789.ABCDEFG" }; - - var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - PackageResourceData output = GetSingleOutputLineAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(packageResourceData.Identifier, output.Identifier); - } - - /// - /// Calls `get` on the `package` resource with the value present. - /// - [Test] - public void Package_Get_Present() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; - - var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - PackageResourceData output = GetSingleOutputLineAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageLowVersion); - } - - /// - /// Calls `get` on the `package` resource with the value present and supplying most inputs. - /// - [Test] - public void Package_Get_MuchInput() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Source = Constants.TestSourceName, - MatchOption = "equals", - }; - - var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - PackageResourceData output = GetSingleOutputLineAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageLowVersion); - } - - /// - /// Calls `test` on the `package` resource with the value not present. - /// - [Test] - public void Package_Test_NotPresent() - { - PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; - - var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(packageResourceData.Identifier, output.Identifier); - Assert.False(output.InDesiredState); - - AssertDiffState(diff, [ ExistPropertyName ]); - } - - /// - /// Calls `test` on the `package` resource with the value present. - /// - [Test] - public void Package_Test_SimplePresent() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; - - var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageLowVersion); - Assert.True(output.InDesiredState); - - AssertDiffState(diff, []); - } - - /// - /// Calls `test` on the `package` resource with a version that matches. - /// - [Test] - public void Package_Test_VersionMatch() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Version = DefaultPackageLowVersion, - }; - - var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageLowVersion); - Assert.True(output.InDesiredState); - - AssertDiffState(diff, []); - } - - /// - /// Calls `test` on the `package` resource with a version that does not match. - /// - [Test] - public void Package_Test_VersionMismatch() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Version = DefaultPackageMidVersion, - }; - - var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageLowVersion); - Assert.False(output.InDesiredState); - - AssertDiffState(diff, [ VersionPropertyName ]); - } - - /// - /// Calls `test` on the `package` resource with a version that is the latest. - /// - [Test] - public void Package_Test_Latest() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - UseLatest = true, - }; - - var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion); - Assert.True(output.InDesiredState); - - AssertDiffState(diff, []); - } - - /// - /// Calls `test` on the `package` resource with a version that is not the latest. - /// - [Test] - public void Package_Test_NotLatest() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageMidVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - UseLatest = true, - }; - - var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageMidVersion); - Assert.False(output.InDesiredState); - - AssertDiffState(diff, [ UseLatestPropertyName ]); - } - - /// - /// Calls `set` on the `package` resource when the package is not present, and again afterward. - /// - [Test] - public void Package_Set_SimpleRepeated() - { - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); - - AssertDiffState(diff, [ ExistPropertyName ]); - - // Set again should be a no-op - result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (output, diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion); - - AssertDiffState(diff, []); - } - - /// - /// Calls `set` on the `package` resource with `installMode` set to `silent`. - /// - [Test] - public void Package_Set_SilentInstallMode_UsesSilentAndCustomSwitches() - { - string installDir = Path.GetTempPath(); - var environmentVariables = new Dictionary(); - environmentVariables[DefaultPackageInstallLocationEnvironmentVariableName] = installDir; - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - InstallMode = "silent", - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData, environmentVariables: environmentVariables); - AssertSuccessfulResourceRun(ref result); - - Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/execustom")); - Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/exesilent")); - TestCommon.BestEffortTestExeCleanup(installDir); - } - - /// - /// Calls `set` on the `package` resource with `installMode` set to `interactive`. - /// - [Test] - public void Package_Set_InteractiveInstallMode_UsesInteractiveAndCustomSwitches() - { - string installDir = Path.GetTempPath(); - var environmentVariables = new Dictionary(); - environmentVariables[DefaultPackageInstallLocationEnvironmentVariableName] = installDir; - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - InstallMode = "interactive", - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData, environmentVariables: environmentVariables); - AssertSuccessfulResourceRun(ref result); - - Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/execustom")); - Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/exeinteractive")); - TestCommon.BestEffortTestExeCleanup(installDir); - } - - /// - /// Calls `set` on the `package` resource to ensure that it is not present. - /// - [Test] - public void Package_Set_Remove() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Exist = false, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(packageResourceData.Identifier, output.Identifier); - - AssertDiffState(diff, [ ExistPropertyName ]); - - // Call `get` to ensure the result - PackageResourceData packageResourceDataForGet = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - }; - - result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); - AssertSuccessfulResourceRun(ref result); - - output = GetSingleOutputLineAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(packageResourceDataForGet.Identifier, output.Identifier); - } - - /// - /// Calls `set` on the `package` resource to request the latest version when a lower version is installed. - /// - [Test] - public void Package_Set_Latest() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageMidVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - UseLatest = true, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); - - AssertDiffState(diff, [ UseLatestPropertyName ]); - - // Call `get` to ensure the result - PackageResourceData packageResourceDataForGet = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - }; - - result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); - AssertSuccessfulResourceRun(ref result); - - output = GetSingleOutputLineAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageHighVersion); - } - - /// - /// Calls `set` on the `package` resource to request a specific version when a lower version is installed. - /// - [Test] - public void Package_Set_SpecificVersionUpgrade() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Version = DefaultPackageMidVersion, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); - - AssertDiffState(diff, [ VersionPropertyName ]); - - // Call `get` to ensure the result - PackageResourceData packageResourceDataForGet = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - }; - - result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); - AssertSuccessfulResourceRun(ref result); - - output = GetSingleOutputLineAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageMidVersion); - } - - /// - /// Calls `set` on the `package` resource to request a specific version when a higher version is installed. - /// - [Test] - public void Package_Set_SpecificVersionDowngrade() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Version = DefaultPackageMidVersion, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - - (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); - - AssertDiffState(diff, [ VersionPropertyName ]); - - // Call `get` to ensure the result - PackageResourceData packageResourceDataForGet = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - }; - - result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); - AssertSuccessfulResourceRun(ref result); - - output = GetSingleOutputLineAs(result.StdOut); - AssertExistingPackageResourceData(output, DefaultPackageMidVersion); - } - - /// - /// Calls `export` on the `package` resource without providing any input. - /// - [Test] - public void Package_Export_NoInput() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - var result = RunDSCv3Command(PackageResource, ExportFunction, " "); - AssertSuccessfulResourceRun(ref result); - - List output = GetOutputLinesAs(result.StdOut); - - bool foundDefaultPackage = false; - foreach (PackageResourceData item in output) - { - if (item.Identifier == DefaultPackageIdentifier) - { - foundDefaultPackage = true; - Assert.IsNull(item.Version); - break; - } - } - - Assert.IsTrue(foundDefaultPackage); - } - - /// - /// Calls `export` on the `package` resource providing input to request that versions be included. - /// - [Test] - public void Package_Export_RequestVersions() - { - var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); - Assert.AreEqual(0, setupInstall.ExitCode); - - PackageResourceData packageResourceData = new PackageResourceData() - { - UseLatest = false, - }; - - var result = RunDSCv3Command(PackageResource, ExportFunction, packageResourceData, 300000); - AssertSuccessfulResourceRun(ref result); - - List output = GetOutputLinesAs(result.StdOut); - - bool foundDefaultPackage = false; - foreach (PackageResourceData item in output) - { - if (item.Identifier == DefaultPackageIdentifier) - { - foundDefaultPackage = true; - Assert.AreEqual(DefaultPackageLowVersion, item.Version); - } - else - { - Assert.IsNotNull(item.Version); - Assert.IsNotEmpty(item.Version); - } - } - - Assert.IsTrue(foundDefaultPackage); - } - - private static void RemoveTestPackage() - { - PackageResourceData packageResourceData = new PackageResourceData() - { - Identifier = DefaultPackageIdentifier, - Exist = false, - }; - - var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); - AssertSuccessfulResourceRun(ref result); - } - - private static void AssertExistingPackageResourceData(PackageResourceData output, string version, bool ignoreLatest = false) - { - Assert.IsNotNull(output); - Assert.True(output.Exist); - Assert.AreEqual(DefaultPackageIdentifier, output.Identifier); - Assert.AreEqual(version, output.Version); - - if (!ignoreLatest) - { - if (version == DefaultPackageHighVersion) - { - Assert.True(output.UseLatest); - } - else - { - Assert.False(output.UseLatest); - } - } - } - - private class PackageResourceData - { - [JsonPropertyName(ExistPropertyName)] - public bool? Exist { get; set; } - - [JsonPropertyName(InDesiredStatePropertyName)] - public bool? InDesiredState { get; set; } - - [JsonPropertyName("id")] - public string Identifier { get; set; } - - public string Source { get; set; } - - public string Version { get; set; } - - public string MatchOption { get; set; } - - public bool? UseLatest { get; set; } - - public string InstallMode { get; set; } - - public bool? AcceptAgreements { get; set; } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.Collections.Generic; + using System.IO; + using System.Text.Json.Serialization; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure` command tests. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public class DSCv3PackageResourceCommand : DSCv3ResourceTestBase + { + private const string DefaultPackageIdentifier = Constants.ExeInstallerPackageId; + private const string DefaultPackageLowVersion = "1.0.0.0"; + private const string DefaultPackageMidVersion = "1.1.0.0"; + private const string DefaultPackageHighVersion = "2.0.0.0"; + private const string DefaultPackageInstallLocationEnvironmentVariableName = "WINGET_TEST_EXE_INSTALL_LOCATION"; + private const string PackageResource = "package"; + private const string VersionPropertyName = "version"; + private const string UseLatestPropertyName = "useLatest"; + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + TestCommon.SetupTestSource(); + WinGetSettingsHelper.ConfigureLoggingLevel("verbose"); + EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + RemoveTestPackage(); + WinGetSettingsHelper.ConfigureLoggingLevel(null); + TestCommon.TearDownTestSource(); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + // Try clean up TestExeInstaller for failure cases where cleanup is not successful + RemoveTestPackage(); + } + + /// + /// Calls `get` on the `package` resource with the value not present. + /// + [Test] + public void Package_Get_NotPresent() + { + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + } + + /// + /// Calls `get` on the `package` resource with the package not existing. + /// + [Test] + public void Package_Get_UnknownIdentifier() + { + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = "Not.An.Existing.Identifier.123456789.ABCDEFG" }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + } + + /// + /// Calls `get` on the `package` resource with the value present. + /// + [Test] + public void Package_Get_Present() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + } + + /// + /// Calls `get` on the `package` resource with the value present and supplying most inputs. + /// + [Test] + public void Package_Get_MuchInput() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Source = Constants.TestSourceName, + MatchOption = "equals", + }; + + var result = RunDSCv3Command(PackageResource, GetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + PackageResourceData output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + } + + /// + /// Calls `test` on the `package` resource with the value not present. + /// + [Test] + public void Package_Test_NotPresent() + { + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ ExistPropertyName ]); + } + + /// + /// Calls `test` on the `package` resource with the value present. + /// + [Test] + public void Package_Test_SimplePresent() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() { Identifier = DefaultPackageIdentifier }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `package` resource with a version that matches. + /// + [Test] + public void Package_Test_VersionMatch() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageLowVersion, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `package` resource with a version that does not match. + /// + [Test] + public void Package_Test_VersionMismatch() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageMidVersion, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageLowVersion); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ VersionPropertyName ]); + } + + /// + /// Calls `test` on the `package` resource with a version that is the latest. + /// + [Test] + public void Package_Test_Latest() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + UseLatest = true, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `package` resource with a version that is not the latest. + /// + [Test] + public void Package_Test_NotLatest() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageMidVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + UseLatest = true, + }; + + var result = RunDSCv3Command(PackageResource, TestFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ UseLatestPropertyName ]); + } + + /// + /// Calls `set` on the `package` resource when the package is not present, and again afterward. + /// + [Test] + public void Package_Set_SimpleRepeated() + { + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); + + AssertDiffState(diff, [ ExistPropertyName ]); + + // Set again should be a no-op + result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (output, diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + + AssertDiffState(diff, []); + } + + /// + /// Calls `set` on the `package` resource with `installMode` set to `silent`. + /// + [Test] + public void Package_Set_SilentInstallMode_UsesSilentAndCustomSwitches() + { + string installDir = Path.GetTempPath(); + var environmentVariables = new Dictionary(); + environmentVariables[DefaultPackageInstallLocationEnvironmentVariableName] = installDir; + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + InstallMode = "silent", + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData, environmentVariables: environmentVariables); + AssertSuccessfulResourceRun(ref result); + + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/execustom")); + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/exesilent")); + TestCommon.BestEffortTestExeCleanup(installDir); + } + + /// + /// Calls `set` on the `package` resource with `installMode` set to `interactive`. + /// + [Test] + public void Package_Set_InteractiveInstallMode_UsesInteractiveAndCustomSwitches() + { + string installDir = Path.GetTempPath(); + var environmentVariables = new Dictionary(); + environmentVariables[DefaultPackageInstallLocationEnvironmentVariableName] = installDir; + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + InstallMode = "interactive", + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData, environmentVariables: environmentVariables); + AssertSuccessfulResourceRun(ref result); + + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/execustom")); + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "/exeinteractive")); + TestCommon.BestEffortTestExeCleanup(installDir); + } + + /// + /// Calls `set` on the `package` resource to ensure that it is not present. + /// + [Test] + public void Package_Set_Remove() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Exist = false, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceData.Identifier, output.Identifier); + + AssertDiffState(diff, [ ExistPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(packageResourceDataForGet.Identifier, output.Identifier); + } + + /// + /// Calls `set` on the `package` resource to request the latest version when a lower version is installed. + /// + [Test] + public void Package_Set_Latest() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageMidVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + UseLatest = true, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion, ignoreLatest: true); + + AssertDiffState(diff, [ UseLatestPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageHighVersion); + } + + /// + /// Calls `set` on the `package` resource to request a specific version when a lower version is installed. + /// + [Test] + public void Package_Set_SpecificVersionUpgrade() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageMidVersion, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); + + AssertDiffState(diff, [ VersionPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + } + + /// + /// Calls `set` on the `package` resource to request a specific version when a higher version is installed. + /// + [Test] + public void Package_Set_SpecificVersionDowngrade() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Version = DefaultPackageMidVersion, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + + (PackageResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion, ignoreLatest: true); + + AssertDiffState(diff, [ VersionPropertyName ]); + + // Call `get` to ensure the result + PackageResourceData packageResourceDataForGet = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + }; + + result = RunDSCv3Command(PackageResource, GetFunction, packageResourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingPackageResourceData(output, DefaultPackageMidVersion); + } + + /// + /// Calls `export` on the `package` resource without providing any input. + /// + [Test] + public void Package_Export_NoInput() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + var result = RunDSCv3Command(PackageResource, ExportFunction, " "); + AssertSuccessfulResourceRun(ref result); + + List output = GetOutputLinesAs(result.StdOut); + + bool foundDefaultPackage = false; + foreach (PackageResourceData item in output) + { + if (item.Identifier == DefaultPackageIdentifier) + { + foundDefaultPackage = true; + Assert.IsNull(item.Version); + break; + } + } + + Assert.IsTrue(foundDefaultPackage); + } + + /// + /// Calls `export` on the `package` resource providing input to request that versions be included. + /// + [Test] + public void Package_Export_RequestVersions() + { + var setupInstall = TestCommon.RunAICLICommand("install", $"--id {DefaultPackageIdentifier} --version {DefaultPackageLowVersion}"); + Assert.AreEqual(0, setupInstall.ExitCode); + + PackageResourceData packageResourceData = new PackageResourceData() + { + UseLatest = false, + }; + + var result = RunDSCv3Command(PackageResource, ExportFunction, packageResourceData, 300000); + AssertSuccessfulResourceRun(ref result); + + List output = GetOutputLinesAs(result.StdOut); + + bool foundDefaultPackage = false; + foreach (PackageResourceData item in output) + { + if (item.Identifier == DefaultPackageIdentifier) + { + foundDefaultPackage = true; + Assert.AreEqual(DefaultPackageLowVersion, item.Version); + } + else + { + Assert.IsNotNull(item.Version); + Assert.IsNotEmpty(item.Version); + } + } + + Assert.IsTrue(foundDefaultPackage); + } + + private static void RemoveTestPackage() + { + PackageResourceData packageResourceData = new PackageResourceData() + { + Identifier = DefaultPackageIdentifier, + Exist = false, + }; + + var result = RunDSCv3Command(PackageResource, SetFunction, packageResourceData); + AssertSuccessfulResourceRun(ref result); + } + + private static void AssertExistingPackageResourceData(PackageResourceData output, string version, bool ignoreLatest = false) + { + Assert.IsNotNull(output); + Assert.True(output.Exist); + Assert.AreEqual(DefaultPackageIdentifier, output.Identifier); + Assert.AreEqual(version, output.Version); + + if (!ignoreLatest) + { + if (version == DefaultPackageHighVersion) + { + Assert.True(output.UseLatest); + } + else + { + Assert.False(output.UseLatest); + } + } + } + + private class PackageResourceData + { + [JsonPropertyName(ExistPropertyName)] + public bool? Exist { get; set; } + + [JsonPropertyName(InDesiredStatePropertyName)] + public bool? InDesiredState { get; set; } + + [JsonPropertyName("id")] + public string Identifier { get; set; } + + public string Source { get; set; } + + public string Version { get; set; } + + public string MatchOption { get; set; } + + public bool? UseLatest { get; set; } + + public string InstallMode { get; set; } + + public bool? AcceptAgreements { get; set; } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs b/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs index fe3b9e2ac4..24786e006c 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3ResourceTestBase.cs @@ -1,190 +1,190 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text.Json; - using System.Text.Json.Serialization; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Provides common functionality for DSC v3 resource tests. - /// - public class DSCv3ResourceTestBase - { - /// - /// The string for the `get` function. - /// - public const string GetFunction = "get"; - - /// - /// The string for the `test` function. - /// - public const string TestFunction = "test"; - - /// - /// The string for the `set` function. - /// - public const string SetFunction = "set"; - - /// - /// The string for the `export` function. - /// - public const string ExportFunction = "export"; - - /// - /// The string for the `_exist` property name. - /// - public const string ExistPropertyName = "_exist"; - - /// - /// The string for the `_inDesiredState` property name. - /// - public const string InDesiredStatePropertyName = "_inDesiredState"; - - /// - /// Write the resource manifests out to the WindowsApps alias directory. - /// - public static void EnsureTestResourcePresence() - { - string outputDirectory = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\WindowsApps"); - Assert.IsNotEmpty(outputDirectory); - - var result = TestCommon.RunAICLICommand($"dscv3", $"--manifest -o {outputDirectory}"); - Assert.AreEqual(0, result.ExitCode); - } - - /// - /// Runs a DSC v3 resource command. - /// - /// The resource to target. - /// The resource function to run. - /// Input for the function; supports null, direct string, or JSON serialization of complex objects. - /// The maximum time to wait in milliseconds. - /// Whether to throw on a timeout or simply return the incomplete result. - /// Environment variables to set. - /// A RunCommandResult containing the process exit code and output and error streams. - protected static TestCommon.RunCommandResult RunDSCv3Command( - string resource, - string function, - object input, - int timeOut = 60000, - bool throwOnTimeout = true, - Dictionary environmentVariables = null) - { - return TestCommon.RunAICLICommand($"dscv3 {resource}", $"--{function}", ConvertToJSON(input), timeOut, throwOnTimeout, environmentVariables); - } - - /// - /// Asserts that a RunCommandResult contains a success for a DSC v3 resource command run. - /// - /// The result of a DSC v3 resource command run. - protected static void AssertSuccessfulResourceRun(ref TestCommon.RunCommandResult result) - { - Assert.AreEqual(0, result.ExitCode); - Assert.IsNotEmpty(result.StdOut); - } - - /// - /// Gets the output as lines. - /// - /// The output stream from a DSC v3 resource command. - /// The lines of the output. - protected static string[] GetOutputLines(string output) - { - return output.TrimEnd().Split(Environment.NewLine); - } - - /// - /// Asserts that the output is a single line and deserializes that line as JSON. - /// - /// The type to deserialize from JSON. - /// The output stream from a DSC v3 resource command. - /// The object as deserialized. - protected static T GetSingleOutputLineAs(string output) - { - string[] lines = GetOutputLines(output); - Assert.AreEqual(1, lines.Length); - - return JsonSerializer.Deserialize(lines[0], GetDefaultJsonOptions()); - } - - /// - /// Asserts that the output is two lines and deserializes them as a JSON object and JSON string array. - /// - /// The type to deserialize from JSON. - /// The output stream from a DSC v3 resource command. - /// The object as deserialized and the contents of the string array. - protected static (T, List) GetSingleOutputLineAndDiffAs(string output) - { - string[] lines = GetOutputLines(output); - Assert.AreEqual(2, lines.Length); - - var options = GetDefaultJsonOptions(); - return (JsonSerializer.Deserialize(lines[0], options), JsonSerializer.Deserialize>(lines[1], options)); - } - - /// - /// Deserializes all lines as JSON objects. - /// - /// The type to deserialize from JSON. - /// The output stream from a DSC v3 resource command. - /// A List of objects as deserialized. - protected static List GetOutputLinesAs(string output) - { - List result = new List(); - string[] lines = GetOutputLines(output); - var options = GetDefaultJsonOptions(); - - foreach (string line in lines) - { - result.Add(JsonSerializer.Deserialize(line, options)); - } - - return result; - } - - /// - /// Requires that the diff from a resource command contain the same set of strings as expected. - /// - /// The diff from a resource command. - /// The expected strings. - protected static void AssertDiffState(List diff, IList expected) - { - Assert.IsNotNull(diff); - Assert.AreEqual(expected.Count, diff.Count); - - foreach (string item in expected) - { - Assert.Contains(item, diff); - } - } - - private static JsonSerializerOptions GetDefaultJsonOptions() - { - return new JsonSerializerOptions() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter(), - }, - }; - } - - private static string ConvertToJSON(object value) => value switch - { - string s => s, - null => null, - _ => JsonSerializer.Serialize(value, GetDefaultJsonOptions()), - }; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text.Json; + using System.Text.Json.Serialization; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Provides common functionality for DSC v3 resource tests. + /// + public class DSCv3ResourceTestBase + { + /// + /// The string for the `get` function. + /// + public const string GetFunction = "get"; + + /// + /// The string for the `test` function. + /// + public const string TestFunction = "test"; + + /// + /// The string for the `set` function. + /// + public const string SetFunction = "set"; + + /// + /// The string for the `export` function. + /// + public const string ExportFunction = "export"; + + /// + /// The string for the `_exist` property name. + /// + public const string ExistPropertyName = "_exist"; + + /// + /// The string for the `_inDesiredState` property name. + /// + public const string InDesiredStatePropertyName = "_inDesiredState"; + + /// + /// Write the resource manifests out to the WindowsApps alias directory. + /// + public static void EnsureTestResourcePresence() + { + string outputDirectory = Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Microsoft\\WindowsApps"); + Assert.IsNotEmpty(outputDirectory); + + var result = TestCommon.RunAICLICommand($"dscv3", $"--manifest -o {outputDirectory}"); + Assert.AreEqual(0, result.ExitCode); + } + + /// + /// Runs a DSC v3 resource command. + /// + /// The resource to target. + /// The resource function to run. + /// Input for the function; supports null, direct string, or JSON serialization of complex objects. + /// The maximum time to wait in milliseconds. + /// Whether to throw on a timeout or simply return the incomplete result. + /// Environment variables to set. + /// A RunCommandResult containing the process exit code and output and error streams. + protected static TestCommon.RunCommandResult RunDSCv3Command( + string resource, + string function, + object input, + int timeOut = 60000, + bool throwOnTimeout = true, + Dictionary environmentVariables = null) + { + return TestCommon.RunAICLICommand($"dscv3 {resource}", $"--{function}", ConvertToJSON(input), timeOut, throwOnTimeout, environmentVariables); + } + + /// + /// Asserts that a RunCommandResult contains a success for a DSC v3 resource command run. + /// + /// The result of a DSC v3 resource command run. + protected static void AssertSuccessfulResourceRun(ref TestCommon.RunCommandResult result) + { + Assert.AreEqual(0, result.ExitCode); + Assert.IsNotEmpty(result.StdOut); + } + + /// + /// Gets the output as lines. + /// + /// The output stream from a DSC v3 resource command. + /// The lines of the output. + protected static string[] GetOutputLines(string output) + { + return output.TrimEnd().Split(Environment.NewLine); + } + + /// + /// Asserts that the output is a single line and deserializes that line as JSON. + /// + /// The type to deserialize from JSON. + /// The output stream from a DSC v3 resource command. + /// The object as deserialized. + protected static T GetSingleOutputLineAs(string output) + { + string[] lines = GetOutputLines(output); + Assert.AreEqual(1, lines.Length); + + return JsonSerializer.Deserialize(lines[0], GetDefaultJsonOptions()); + } + + /// + /// Asserts that the output is two lines and deserializes them as a JSON object and JSON string array. + /// + /// The type to deserialize from JSON. + /// The output stream from a DSC v3 resource command. + /// The object as deserialized and the contents of the string array. + protected static (T, List) GetSingleOutputLineAndDiffAs(string output) + { + string[] lines = GetOutputLines(output); + Assert.AreEqual(2, lines.Length); + + var options = GetDefaultJsonOptions(); + return (JsonSerializer.Deserialize(lines[0], options), JsonSerializer.Deserialize>(lines[1], options)); + } + + /// + /// Deserializes all lines as JSON objects. + /// + /// The type to deserialize from JSON. + /// The output stream from a DSC v3 resource command. + /// A List of objects as deserialized. + protected static List GetOutputLinesAs(string output) + { + List result = new List(); + string[] lines = GetOutputLines(output); + var options = GetDefaultJsonOptions(); + + foreach (string line in lines) + { + result.Add(JsonSerializer.Deserialize(line, options)); + } + + return result; + } + + /// + /// Requires that the diff from a resource command contain the same set of strings as expected. + /// + /// The diff from a resource command. + /// The expected strings. + protected static void AssertDiffState(List diff, IList expected) + { + Assert.IsNotNull(diff); + Assert.AreEqual(expected.Count, diff.Count); + + foreach (string item in expected) + { + Assert.Contains(item, diff); + } + } + + private static JsonSerializerOptions GetDefaultJsonOptions() + { + return new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(), + }, + }; + } + + private static string ConvertToJSON(object value) => value switch + { + string s => s, + null => null, + _ => JsonSerializer.Serialize(value, GetDefaultJsonOptions()), + }; + } +} diff --git a/src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs b/src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs index dc5d9e7061..51222af39e 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3SourceResourceCommand.cs @@ -1,546 +1,546 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.Collections.Generic; - using System.Text.Json.Serialization; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// `Configure` command tests. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public class DSCv3SourceResourceCommand : DSCv3ResourceTestBase - { - private const string DefaultSourceName = "SourceResourceTestSource"; - private const string DefaultSourceType = "Microsoft.Test.Configurable"; - private const string DefaultTrustLevel = "none"; - private const string TrustedTrustLevel = "trusted"; - private const bool DefaultExplicitState = false; - private const int DefaultPriority = 0; - private const string SourceResource = "source"; - private const string ArgumentPropertyName = "argument"; - private const string TypePropertyName = "type"; - private const string TrustLevelPropertyName = "trustLevel"; - private const string ExplicitPropertyName = "explicit"; - private const string PriorityPropertyName = "priority"; - - private static string DefaultSourceArgForCmdLine - { - get { return CreateSourceArgument(true); } - } - - private static string NonDefaultSourceArgForCmdLine - { - get { return CreateSourceArgument(true, 1, 1); } - } - - private static string DefaultSourceArgDirect - { - get { return CreateSourceArgument(false); } - } - - private static string NonDefaultSourceArgDirect - { - get { return CreateSourceArgument(false, 1, 1); } - } - - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - TestCommon.SetupTestSource(); - EnsureTestResourcePresence(); - } - - /// - /// Teardown done once after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTeardown() - { - RemoveTestSource(); - TestCommon.TearDownTestSource(); - } - - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - RemoveTestSource(); - WinGetSettingsHelper.ConfigureFeature("sourcePriority", true); - } - - /// - /// Calls `get` on the `source` resource with the value not present. - /// - [Test] - public void Source_Get_NotPresent() - { - SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; - - var result = RunDSCv3Command(SourceResource, GetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - SourceResourceData output = GetSingleOutputLineAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(resourceData.Name, output.Name); - } - - /// - /// Calls `get` on the `source` resource with the value present. - /// - [Test] - public void Source_Get_Present() - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType} --explicit"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; - - var result = RunDSCv3Command(SourceResource, GetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - SourceResourceData output = GetSingleOutputLineAs(result.StdOut); - AssertExistingSourceResourceData(output, DefaultSourceArgDirect, DefaultTrustLevel, true); - } - - /// - /// Calls `test` on the `source` resource with the value not present. - /// - [Test] - public void Source_Test_NotPresent() - { - SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; - - var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(resourceData.Name, output.Name); - Assert.False(output.InDesiredState); - - AssertDiffState(diff, [ ExistPropertyName ]); - } - - /// - /// Calls `test` on the `source` resource with the value present. - /// - [Test] - public void Source_Test_SimplePresent() - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; - - var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, DefaultSourceArgDirect, DefaultTrustLevel, DefaultExplicitState); - Assert.True(output.InDesiredState); - - AssertDiffState(diff, []); - } - - /// - /// Calls `test` on the `source` resource with an argument that matches. - /// - /// The argument to use when adding the existing source. - /// The trust level to use when adding the existing source. - /// The explicit state to use when adding the existing source. - /// The priority to use when adding the existing source. - /// The property to target for the test. - [TestCase(false, DefaultTrustLevel, true, 42, ArgumentPropertyName)] - [TestCase(true, DefaultTrustLevel, false, 14, TypePropertyName)] - [TestCase(false, TrustedTrustLevel, false, 42, TrustLevelPropertyName)] - [TestCase(true, DefaultTrustLevel, true, 39, ExplicitPropertyName)] - [TestCase(true, DefaultTrustLevel, true, 1, PriorityPropertyName)] - public void Source_Test_PropertyMatch(bool useDefaultArgument, string trustLevel, bool isExplicit, int priority, string targetProperty) - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {(useDefaultArgument ? DefaultSourceArgForCmdLine : NonDefaultSourceArgForCmdLine)} --type {DefaultSourceType} --trust-level {trustLevel} {(isExplicit ? "--explicit" : string.Empty)} --priority {priority}"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; - - switch (targetProperty) - { - case ArgumentPropertyName: - resourceData.Argument = useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect; - break; - case TypePropertyName: - resourceData.Type = DefaultSourceType; - break; - case TrustLevelPropertyName: - resourceData.TrustLevel = trustLevel; - break; - case ExplicitPropertyName: - resourceData.Explicit = isExplicit; - break; - case PriorityPropertyName: - resourceData.Priority = priority; - break; - default: - Assert.Fail($"{targetProperty} is not a handled case."); - break; - } - - var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect, trustLevel, isExplicit); - Assert.True(output.InDesiredState); - - AssertDiffState(diff, []); - } - - /// - /// Calls `test` on the `source` resource with a argument that does not match. - /// - /// The argument to use when adding the existing source. - /// The trust level to use when adding the existing source. - /// The explicit state to use when adding the existing source. - /// The priority to use when adding the existing source. - /// The property to target for the test. - /// The value to test against. - [TestCase(false, DefaultTrustLevel, true, 2, ArgumentPropertyName, true)] - [TestCase(false, DefaultTrustLevel, false, 13, TrustLevelPropertyName, TrustedTrustLevel)] - [TestCase(true, DefaultTrustLevel, true, 42, ExplicitPropertyName, false)] - [TestCase(true, DefaultTrustLevel, true, 8, PriorityPropertyName, 76)] - public void Source_Test_PropertyMismatch(bool useDefaultArgument, string trustLevel, bool isExplicit, int priority, string targetProperty, object testValue) - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {(useDefaultArgument ? DefaultSourceArgForCmdLine : NonDefaultSourceArgForCmdLine)} --type {DefaultSourceType} --trust-level {trustLevel} {(isExplicit ? "--explicit" : string.Empty)} --priority {priority}"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; - - switch (targetProperty) - { - case ArgumentPropertyName: - resourceData.Argument = (bool)testValue ? DefaultSourceArgDirect : NonDefaultSourceArgDirect; - break; - case TrustLevelPropertyName: - resourceData.TrustLevel = (string)testValue; - break; - case ExplicitPropertyName: - resourceData.Explicit = (bool)testValue; - break; - case PriorityPropertyName: - resourceData.Priority = (int)testValue; - break; - default: - Assert.Fail($"{targetProperty} is not a handled case."); - break; - } - - var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect, trustLevel, isExplicit); - Assert.False(output.InDesiredState); - - AssertDiffState(diff, [ targetProperty ]); - } - - /// - /// Calls `test` on the `source` resource with all properties matching. - /// - [Test] - public void Source_Test_AllMatch() - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {NonDefaultSourceArgForCmdLine} --type {DefaultSourceType} --trust-level {TrustedTrustLevel} --explicit --priority 42"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() - { - Name = DefaultSourceName, - Argument = NonDefaultSourceArgDirect, - Type = DefaultSourceType, - TrustLevel = TrustedTrustLevel, - Explicit = true, - Priority = 42, - }; - - var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, resourceData); - Assert.True(output.InDesiredState); - - AssertDiffState(diff, []); - } - - /// - /// Calls `set` on the `source` resource when the source is not present, and again afterward. - /// - [Test] - public void Source_Set_SimpleRepeated() - { - SourceResourceData resourceData = new SourceResourceData() - { - Name = DefaultSourceName, - Argument = NonDefaultSourceArgDirect, - Type = DefaultSourceType, - }; - - var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, resourceData); - - AssertDiffState(diff, [ ExistPropertyName ]); - - // Set again should be a no-op - result = RunDSCv3Command(SourceResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (output, diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, resourceData); - - AssertDiffState(diff, []); - } - - /// - /// Calls `set` on the `source` resource to ensure that it is not present. - /// - [Test] - public void Source_Set_Remove() - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType} --explicit"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() - { - Name = DefaultSourceName, - Exist = false, - }; - - var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(resourceData.Name, output.Name); - - AssertDiffState(diff, [ ExistPropertyName ]); - - // Call `get` to ensure the result - SourceResourceData resourceDataForGet = new SourceResourceData() - { - Name = DefaultSourceName, - }; - - result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet); - AssertSuccessfulResourceRun(ref result); - - output = GetSingleOutputLineAs(result.StdOut); - Assert.IsNotNull(output); - Assert.False(output.Exist); - Assert.AreEqual(resourceDataForGet.Name, output.Name); - } - - /// - /// Calls `set` on the `source` resource with an existing item, replacing it. - /// - [Test] - public void Source_Set_Replace() - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() - { - Name = DefaultSourceName, - Argument = DefaultSourceArgDirect, - Type = DefaultSourceType, - TrustLevel = TrustedTrustLevel, - Explicit = true, - }; - - var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, resourceData); - - AssertDiffState(diff, [ TrustLevelPropertyName, ExplicitPropertyName ]); - - // Call `get` to ensure the result - SourceResourceData resourceDataForGet = new SourceResourceData() - { - Name = DefaultSourceName, - }; - - result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet); - AssertSuccessfulResourceRun(ref result); - - output = GetSingleOutputLineAs(result.StdOut); - AssertExistingSourceResourceData(output, resourceData); - } - - /// - /// Calls `set` on the `source` resource with an existing item, editing it due to only changing editable properties. - /// - [Test] - public void Source_Set_Replace_Edit() - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); - Assert.AreEqual(0, setup.ExitCode); - - SourceResourceData resourceData = new SourceResourceData() - { - Name = DefaultSourceName, - Explicit = true, - Priority = 42, - }; - - var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - - (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); - AssertExistingSourceResourceData(output, resourceData); - - AssertDiffState(diff, [ExplicitPropertyName, PriorityPropertyName]); - - // Call `get` to ensure the result - SourceResourceData resourceDataForGet = new SourceResourceData() - { - Name = DefaultSourceName, - }; - - result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet); - AssertSuccessfulResourceRun(ref result); - - output = GetSingleOutputLineAs(result.StdOut); - AssertExistingSourceResourceData(output, resourceData); - } - - /// - /// Calls `export` on the `source` resource without providing any input. - /// - [Test] - public void Source_Export_NoInput() - { - var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); - Assert.AreEqual(0, setup.ExitCode); - - var result = RunDSCv3Command(SourceResource, ExportFunction, " "); - AssertSuccessfulResourceRun(ref result); - - List output = GetOutputLinesAs(result.StdOut); - - bool foundDefaultSource = false; - foreach (SourceResourceData item in output) - { - if (item.Name == DefaultSourceName) - { - foundDefaultSource = true; - Assert.AreEqual(DefaultSourceName, item.Name); - Assert.AreEqual(DefaultSourceArgDirect, item.Argument); - Assert.AreEqual(DefaultSourceType, item.Type); - Assert.AreEqual(DefaultTrustLevel, item.TrustLevel); - Assert.AreEqual(DefaultExplicitState, item.Explicit); - Assert.AreEqual(DefaultPriority, item.Priority); - break; - } - } - - Assert.IsTrue(foundDefaultSource); - } - - private static void RemoveTestSource() - { - SourceResourceData resourceData = new SourceResourceData() - { - Name = DefaultSourceName, - Exist = false, - }; - - var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - } - - private static void AssertExistingSourceResourceData(SourceResourceData output, SourceResourceData input) - { - AssertExistingSourceResourceData(output, input.Argument, input.TrustLevel, input.Explicit, input.Priority); - } - - private static void AssertExistingSourceResourceData(SourceResourceData output, string argument, string trustLevel = null, bool? isExplicit = null, int? priority = null) - { - Assert.IsNotNull(output); - Assert.True(output.Exist); - Assert.AreEqual(DefaultSourceName, output.Name); - - if (argument != null) - { - Assert.AreEqual(argument, output.Argument); - } - - Assert.AreEqual(DefaultSourceType, output.Type); - - if (trustLevel != null) - { - Assert.AreEqual(trustLevel, output.TrustLevel); - } - - if (isExplicit != null) - { - Assert.AreEqual(isExplicit, output.Explicit); - } - - if (priority != null) - { - Assert.AreEqual(priority, output.Priority); - } - } - - private static string CreateSourceArgument(bool forCommandLine = false, int openHR = 0, int searchHR = 0) - { - const string CommandLineFormat = @"""{{""""OpenHR"""": {0}, """"SearchHR"""": {1} }}"""; - const string DirectFormat = @"{{""OpenHR"": {0}, ""SearchHR"": {1} }}"; - return string.Format(forCommandLine ? CommandLineFormat : DirectFormat, openHR, searchHR); - } - - private class SourceResourceData - { - [JsonPropertyName(ExistPropertyName)] - public bool? Exist { get; set; } - - [JsonPropertyName(InDesiredStatePropertyName)] - public bool? InDesiredState { get; set; } - - public string Name { get; set; } - - public string Argument { get; set; } - - public string Type { get; set; } - - public string TrustLevel { get; set; } - - public bool? Explicit { get; set; } - - public bool? AcceptAgreements { get; set; } - - public int? Priority { get; set; } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.Collections.Generic; + using System.Text.Json.Serialization; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// `Configure` command tests. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public class DSCv3SourceResourceCommand : DSCv3ResourceTestBase + { + private const string DefaultSourceName = "SourceResourceTestSource"; + private const string DefaultSourceType = "Microsoft.Test.Configurable"; + private const string DefaultTrustLevel = "none"; + private const string TrustedTrustLevel = "trusted"; + private const bool DefaultExplicitState = false; + private const int DefaultPriority = 0; + private const string SourceResource = "source"; + private const string ArgumentPropertyName = "argument"; + private const string TypePropertyName = "type"; + private const string TrustLevelPropertyName = "trustLevel"; + private const string ExplicitPropertyName = "explicit"; + private const string PriorityPropertyName = "priority"; + + private static string DefaultSourceArgForCmdLine + { + get { return CreateSourceArgument(true); } + } + + private static string NonDefaultSourceArgForCmdLine + { + get { return CreateSourceArgument(true, 1, 1); } + } + + private static string DefaultSourceArgDirect + { + get { return CreateSourceArgument(false); } + } + + private static string NonDefaultSourceArgDirect + { + get { return CreateSourceArgument(false, 1, 1); } + } + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + TestCommon.SetupTestSource(); + EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + RemoveTestSource(); + TestCommon.TearDownTestSource(); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + RemoveTestSource(); + WinGetSettingsHelper.ConfigureFeature("sourcePriority", true); + } + + /// + /// Calls `get` on the `source` resource with the value not present. + /// + [Test] + public void Source_Get_NotPresent() + { + SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; + + var result = RunDSCv3Command(SourceResource, GetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + SourceResourceData output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(resourceData.Name, output.Name); + } + + /// + /// Calls `get` on the `source` resource with the value present. + /// + [Test] + public void Source_Get_Present() + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType} --explicit"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; + + var result = RunDSCv3Command(SourceResource, GetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + SourceResourceData output = GetSingleOutputLineAs(result.StdOut); + AssertExistingSourceResourceData(output, DefaultSourceArgDirect, DefaultTrustLevel, true); + } + + /// + /// Calls `test` on the `source` resource with the value not present. + /// + [Test] + public void Source_Test_NotPresent() + { + SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; + + var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(resourceData.Name, output.Name); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ ExistPropertyName ]); + } + + /// + /// Calls `test` on the `source` resource with the value present. + /// + [Test] + public void Source_Test_SimplePresent() + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; + + var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, DefaultSourceArgDirect, DefaultTrustLevel, DefaultExplicitState); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `source` resource with an argument that matches. + /// + /// The argument to use when adding the existing source. + /// The trust level to use when adding the existing source. + /// The explicit state to use when adding the existing source. + /// The priority to use when adding the existing source. + /// The property to target for the test. + [TestCase(false, DefaultTrustLevel, true, 42, ArgumentPropertyName)] + [TestCase(true, DefaultTrustLevel, false, 14, TypePropertyName)] + [TestCase(false, TrustedTrustLevel, false, 42, TrustLevelPropertyName)] + [TestCase(true, DefaultTrustLevel, true, 39, ExplicitPropertyName)] + [TestCase(true, DefaultTrustLevel, true, 1, PriorityPropertyName)] + public void Source_Test_PropertyMatch(bool useDefaultArgument, string trustLevel, bool isExplicit, int priority, string targetProperty) + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {(useDefaultArgument ? DefaultSourceArgForCmdLine : NonDefaultSourceArgForCmdLine)} --type {DefaultSourceType} --trust-level {trustLevel} {(isExplicit ? "--explicit" : string.Empty)} --priority {priority}"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; + + switch (targetProperty) + { + case ArgumentPropertyName: + resourceData.Argument = useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect; + break; + case TypePropertyName: + resourceData.Type = DefaultSourceType; + break; + case TrustLevelPropertyName: + resourceData.TrustLevel = trustLevel; + break; + case ExplicitPropertyName: + resourceData.Explicit = isExplicit; + break; + case PriorityPropertyName: + resourceData.Priority = priority; + break; + default: + Assert.Fail($"{targetProperty} is not a handled case."); + break; + } + + var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect, trustLevel, isExplicit); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `test` on the `source` resource with a argument that does not match. + /// + /// The argument to use when adding the existing source. + /// The trust level to use when adding the existing source. + /// The explicit state to use when adding the existing source. + /// The priority to use when adding the existing source. + /// The property to target for the test. + /// The value to test against. + [TestCase(false, DefaultTrustLevel, true, 2, ArgumentPropertyName, true)] + [TestCase(false, DefaultTrustLevel, false, 13, TrustLevelPropertyName, TrustedTrustLevel)] + [TestCase(true, DefaultTrustLevel, true, 42, ExplicitPropertyName, false)] + [TestCase(true, DefaultTrustLevel, true, 8, PriorityPropertyName, 76)] + public void Source_Test_PropertyMismatch(bool useDefaultArgument, string trustLevel, bool isExplicit, int priority, string targetProperty, object testValue) + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {(useDefaultArgument ? DefaultSourceArgForCmdLine : NonDefaultSourceArgForCmdLine)} --type {DefaultSourceType} --trust-level {trustLevel} {(isExplicit ? "--explicit" : string.Empty)} --priority {priority}"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() { Name = DefaultSourceName }; + + switch (targetProperty) + { + case ArgumentPropertyName: + resourceData.Argument = (bool)testValue ? DefaultSourceArgDirect : NonDefaultSourceArgDirect; + break; + case TrustLevelPropertyName: + resourceData.TrustLevel = (string)testValue; + break; + case ExplicitPropertyName: + resourceData.Explicit = (bool)testValue; + break; + case PriorityPropertyName: + resourceData.Priority = (int)testValue; + break; + default: + Assert.Fail($"{targetProperty} is not a handled case."); + break; + } + + var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, useDefaultArgument ? DefaultSourceArgDirect : NonDefaultSourceArgDirect, trustLevel, isExplicit); + Assert.False(output.InDesiredState); + + AssertDiffState(diff, [ targetProperty ]); + } + + /// + /// Calls `test` on the `source` resource with all properties matching. + /// + [Test] + public void Source_Test_AllMatch() + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {NonDefaultSourceArgForCmdLine} --type {DefaultSourceType} --trust-level {TrustedTrustLevel} --explicit --priority 42"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() + { + Name = DefaultSourceName, + Argument = NonDefaultSourceArgDirect, + Type = DefaultSourceType, + TrustLevel = TrustedTrustLevel, + Explicit = true, + Priority = 42, + }; + + var result = RunDSCv3Command(SourceResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, resourceData); + Assert.True(output.InDesiredState); + + AssertDiffState(diff, []); + } + + /// + /// Calls `set` on the `source` resource when the source is not present, and again afterward. + /// + [Test] + public void Source_Set_SimpleRepeated() + { + SourceResourceData resourceData = new SourceResourceData() + { + Name = DefaultSourceName, + Argument = NonDefaultSourceArgDirect, + Type = DefaultSourceType, + }; + + var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, resourceData); + + AssertDiffState(diff, [ ExistPropertyName ]); + + // Set again should be a no-op + result = RunDSCv3Command(SourceResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (output, diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, resourceData); + + AssertDiffState(diff, []); + } + + /// + /// Calls `set` on the `source` resource to ensure that it is not present. + /// + [Test] + public void Source_Set_Remove() + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType} --explicit"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() + { + Name = DefaultSourceName, + Exist = false, + }; + + var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(resourceData.Name, output.Name); + + AssertDiffState(diff, [ ExistPropertyName ]); + + // Call `get` to ensure the result + SourceResourceData resourceDataForGet = new SourceResourceData() + { + Name = DefaultSourceName, + }; + + result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + Assert.IsNotNull(output); + Assert.False(output.Exist); + Assert.AreEqual(resourceDataForGet.Name, output.Name); + } + + /// + /// Calls `set` on the `source` resource with an existing item, replacing it. + /// + [Test] + public void Source_Set_Replace() + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() + { + Name = DefaultSourceName, + Argument = DefaultSourceArgDirect, + Type = DefaultSourceType, + TrustLevel = TrustedTrustLevel, + Explicit = true, + }; + + var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, resourceData); + + AssertDiffState(diff, [ TrustLevelPropertyName, ExplicitPropertyName ]); + + // Call `get` to ensure the result + SourceResourceData resourceDataForGet = new SourceResourceData() + { + Name = DefaultSourceName, + }; + + result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingSourceResourceData(output, resourceData); + } + + /// + /// Calls `set` on the `source` resource with an existing item, editing it due to only changing editable properties. + /// + [Test] + public void Source_Set_Replace_Edit() + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); + Assert.AreEqual(0, setup.ExitCode); + + SourceResourceData resourceData = new SourceResourceData() + { + Name = DefaultSourceName, + Explicit = true, + Priority = 42, + }; + + var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + + (SourceResourceData output, List diff) = GetSingleOutputLineAndDiffAs(result.StdOut); + AssertExistingSourceResourceData(output, resourceData); + + AssertDiffState(diff, [ExplicitPropertyName, PriorityPropertyName]); + + // Call `get` to ensure the result + SourceResourceData resourceDataForGet = new SourceResourceData() + { + Name = DefaultSourceName, + }; + + result = RunDSCv3Command(SourceResource, GetFunction, resourceDataForGet); + AssertSuccessfulResourceRun(ref result); + + output = GetSingleOutputLineAs(result.StdOut); + AssertExistingSourceResourceData(output, resourceData); + } + + /// + /// Calls `export` on the `source` resource without providing any input. + /// + [Test] + public void Source_Export_NoInput() + { + var setup = TestCommon.RunAICLICommand("source add", $"--name {DefaultSourceName} --arg {DefaultSourceArgForCmdLine} --type {DefaultSourceType}"); + Assert.AreEqual(0, setup.ExitCode); + + var result = RunDSCv3Command(SourceResource, ExportFunction, " "); + AssertSuccessfulResourceRun(ref result); + + List output = GetOutputLinesAs(result.StdOut); + + bool foundDefaultSource = false; + foreach (SourceResourceData item in output) + { + if (item.Name == DefaultSourceName) + { + foundDefaultSource = true; + Assert.AreEqual(DefaultSourceName, item.Name); + Assert.AreEqual(DefaultSourceArgDirect, item.Argument); + Assert.AreEqual(DefaultSourceType, item.Type); + Assert.AreEqual(DefaultTrustLevel, item.TrustLevel); + Assert.AreEqual(DefaultExplicitState, item.Explicit); + Assert.AreEqual(DefaultPriority, item.Priority); + break; + } + } + + Assert.IsTrue(foundDefaultSource); + } + + private static void RemoveTestSource() + { + SourceResourceData resourceData = new SourceResourceData() + { + Name = DefaultSourceName, + Exist = false, + }; + + var result = RunDSCv3Command(SourceResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + } + + private static void AssertExistingSourceResourceData(SourceResourceData output, SourceResourceData input) + { + AssertExistingSourceResourceData(output, input.Argument, input.TrustLevel, input.Explicit, input.Priority); + } + + private static void AssertExistingSourceResourceData(SourceResourceData output, string argument, string trustLevel = null, bool? isExplicit = null, int? priority = null) + { + Assert.IsNotNull(output); + Assert.True(output.Exist); + Assert.AreEqual(DefaultSourceName, output.Name); + + if (argument != null) + { + Assert.AreEqual(argument, output.Argument); + } + + Assert.AreEqual(DefaultSourceType, output.Type); + + if (trustLevel != null) + { + Assert.AreEqual(trustLevel, output.TrustLevel); + } + + if (isExplicit != null) + { + Assert.AreEqual(isExplicit, output.Explicit); + } + + if (priority != null) + { + Assert.AreEqual(priority, output.Priority); + } + } + + private static string CreateSourceArgument(bool forCommandLine = false, int openHR = 0, int searchHR = 0) + { + const string CommandLineFormat = @"""{{""""OpenHR"""": {0}, """"SearchHR"""": {1} }}"""; + const string DirectFormat = @"{{""OpenHR"": {0}, ""SearchHR"": {1} }}"; + return string.Format(forCommandLine ? CommandLineFormat : DirectFormat, openHR, searchHR); + } + + private class SourceResourceData + { + [JsonPropertyName(ExistPropertyName)] + public bool? Exist { get; set; } + + [JsonPropertyName(InDesiredStatePropertyName)] + public bool? InDesiredState { get; set; } + + public string Name { get; set; } + + public string Argument { get; set; } + + public string Type { get; set; } + + public string TrustLevel { get; set; } + + public bool? Explicit { get; set; } + + public bool? AcceptAgreements { get; set; } + + public int? Priority { get; set; } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/DSCv3UserSettingsFileResourceCommand.cs b/src/AppInstallerCLIE2ETests/DSCv3UserSettingsFileResourceCommand.cs index b99b4b1b0a..178a70aeed 100644 --- a/src/AppInstallerCLIE2ETests/DSCv3UserSettingsFileResourceCommand.cs +++ b/src/AppInstallerCLIE2ETests/DSCv3UserSettingsFileResourceCommand.cs @@ -1,362 +1,362 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests; - -using AppInstallerCLIE2ETests.Helpers; -using NUnit.Framework; -using System.Collections.Generic; -using System.IO; -using System.Text.Json.Nodes; -using System.Text.Json.Serialization; - -/// -/// `Configure` command tests. -/// -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] -[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] -public class DSCv3UserSettingsFileResourceCommand : DSCv3ResourceTestBase -{ - private const string UserSettingsFileResource = "user-settings-file"; - private const string SettingsPropertyName = "settings"; - private const string ActionPropertyValueFull = "Full"; - private const string ActionPropertyValuePartial = "Partial"; - private const string SettingsMock = "mock"; - private const string SettingsMockObject = "mockObject"; - private const string SettingsMockNested = "mockNested"; - - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - TestCommon.SetupTestSource(); - EnsureTestResourcePresence(); - } - - /// - /// Teardown done once after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTeardown() - { - WinGetSettingsHelper.InitializeWingetSettings(); - } - - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - // Reset the settings to default before each test. - WinGetSettingsHelper.InitializeWingetSettings(); - WinGetSettingsHelper.ConfigureFeature("dsc3", true); - } - - /// - /// Calls `get` on the `user-settings-file` resource. - /// - [Test] - public void UserSettingsFile_Get() - { - var expected = GetCurrentUserSettings(); - var getOutput = Get(new ()); - - Assert.IsNotNull(getOutput); - Assert.IsNull(getOutput.Action); - AssertSettingsAreEqual(expected, getOutput.Settings); - } - - /// - /// Calls `set` on the `user-settings-file` resource with no diff. - /// - /// The action value. - [Test] - [TestCase(ActionPropertyValueFull)] - [TestCase(ActionPropertyValuePartial)] - public void UserSettingsFile_Set_NoDiff(string action) - { - var setSettings = GetSettingsArg(action); - - (var setOutput, var setDiff) = Set(new () { Action = action, Settings = setSettings }); - - var expected = GetCurrentUserSettings(); - - Assert.IsNotNull(setOutput); - Assert.AreEqual(action, setOutput.Action); - AssertSettingsAreEqual(expected, setOutput.Settings); - AssertDiffState(setDiff, []); - } - - /// - /// Calls `set` on the `user-settings-file` resource to add fields. - /// - /// The action value. - [Test] - [TestCase(ActionPropertyValueFull)] - [TestCase(ActionPropertyValuePartial)] - public void UserSettingsFile_Set_AddFields(string action) - { - // Call `set` to add mock properties to the settings - var setSettings = GetSettingsArg(action); - AddOrModifyMockProperties(setSettings, "mock"); - - (var setOutput, var setDiff) = Set(new () { Action = action, Settings = setSettings }); - - var expected = GetCurrentUserSettings(); - - // Assert that the settings are added - Assert.IsNotNull(setOutput); - Assert.AreEqual(action, setOutput.Action); - AssertMockProperties(setOutput.Settings, "mock"); - AssertSettingsAreEqual(expected, setOutput.Settings); - AssertDiffState(setDiff, [ SettingsPropertyName ]); - } - - /// - /// Calls `set` on the `user-settings-file` resource to ensure action is partial by default. - /// - [Test] - public void UserSettingsFile_Set_ActionIsPartialByDefault() - { - // Call `set` to add mock properties to the settings - var setSettings = GetSettingsArg(ActionPropertyValuePartial); - AddOrModifyMockProperties(setSettings, "mock"); - - var expected = GetCurrentUserSettings(); - AddOrModifyMockProperties(expected, "mock"); - - (var setOutput, var setDiff) = Set(new () { Settings = setSettings }); - - // Assert that the settings are added - Assert.IsNotNull(setOutput); - Assert.AreEqual(setOutput.Action, ActionPropertyValuePartial); - AssertMockProperties(setOutput.Settings, "mock"); - AssertSettingsAreEqual(expected, setOutput.Settings); - AssertDiffState(setDiff, [ SettingsPropertyName ]); - } - - /// - /// Calls `set` on the `user-settings-file` resource to update fields. - /// - /// The action value. - [Test] - [TestCase(ActionPropertyValueFull)] - [TestCase(ActionPropertyValuePartial)] - public void UserSettingsFile_Set_UpdateFields(string action) - { - // Add mock properties to the settings - var set1Settings = new JsonObject(); - AddOrModifyMockProperties(set1Settings, "mock_old"); - Set(new () { Action = ActionPropertyValuePartial, Settings = set1Settings }); - - // Call `set` to update the settings - var set2Settings = GetSettingsArg(action); - AddOrModifyMockProperties(set2Settings, "mock_new"); - - (var setOutput, var setDiff) = Set(new () { Action = action, Settings = set2Settings }); - - var expected = GetCurrentUserSettings(); - - // Assert that the settings are updated - Assert.IsNotNull(setOutput); - Assert.AreEqual(action, setOutput.Action); - AssertMockProperties(setOutput.Settings, "mock_new"); - AssertSettingsAreEqual(expected, setOutput.Settings); - AssertDiffState(setDiff, [ SettingsPropertyName ]); - } - - /// - /// Calls `test` on the `user-settings-file` resource to check if the settings are in desired state. - /// - /// The action value. - [Test] - [TestCase(ActionPropertyValueFull)] - [TestCase(ActionPropertyValuePartial)] - public void UserSettingsFile_Test_InDesiredState(string action) - { - // Add mock properties to the settings - var setSettings = new JsonObject(); - AddOrModifyMockProperties(setSettings, "mock"); - Set(new () { Action = ActionPropertyValuePartial, Settings = setSettings }); - - // Call `test` to check the settings - var testSettings = GetSettingsArg(action); - AddOrModifyMockProperties(testSettings, "mock"); - - (var testOutput, var testDiff) = Test(new () { Action = action, Settings = testSettings }); - - var expected = GetCurrentUserSettings(); - - // Assert that the settings are in desired state - Assert.IsNotNull(testOutput); - Assert.AreEqual(action, testOutput.Action); - AssertMockProperties(testOutput.Settings, "mock"); - AssertSettingsAreEqual(expected, testOutput.Settings); - Assert.IsTrue(testOutput.InDesiredState); - AssertDiffState(testDiff, []); - } - - /// - /// Calls `test` on the `user-settings-file` resource to check if the settings are not in desired state. - /// - /// The action value. - [Test] - [TestCase(ActionPropertyValueFull)] - [TestCase(ActionPropertyValuePartial)] - public void UserSettingsFile_Test_NotInDesiredState(string action) - { - // Add mock properties to the settings - var setSettings = new JsonObject(); - AddOrModifyMockProperties(setSettings, "mock_set"); - Set(new () { Action = ActionPropertyValuePartial, Settings = setSettings }); - - // Call `test` to check the settings - var testSettings = GetSettingsArg(action); - AddOrModifyMockProperties(testSettings, "mock_test"); - - (var testOutput, var testDiff) = Test(new () { Action = action, Settings = testSettings }); - - var expected = GetCurrentUserSettings(); - - // Assert that the settings are not in desired state - Assert.IsNotNull(testOutput); - Assert.AreEqual(action, testOutput.Action); - AssertMockProperties(testOutput.Settings, "mock_set"); - AssertSettingsAreEqual(expected, testOutput.Settings); - Assert.IsFalse(testOutput.InDesiredState); - AssertDiffState(testDiff, [ SettingsPropertyName ]); - } - - /// - /// Calls `export` on the `user-settings-file` resource to export the settings. - /// - [Test] - public void UserSettingsFile_Export() - { - var expected = GetCurrentUserSettings(); - var exportOutput = Export(new ()); - - Assert.IsNotNull(exportOutput); - Assert.IsNull(exportOutput.Action); - AssertSettingsAreEqual(expected, exportOutput.Settings); - } - - /// - /// Calls `get` on the `user-settings-file` resource. - /// - /// The input resource data. - /// The output resource data. - private static UserSettingsFileResourceData Get(UserSettingsFileResourceData resourceData) - { - var result = RunDSCv3Command(UserSettingsFileResource, GetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - return GetSingleOutputLineAs(result.StdOut); - } - - /// - /// Calls `set` on the `user-settings-file` resource. - /// - /// The input resource data. - /// The output resource data and the diff. - private static (UserSettingsFileResourceData, List) Set(UserSettingsFileResourceData resourceData) - { - var result = RunDSCv3Command(UserSettingsFileResource, SetFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - return GetSingleOutputLineAndDiffAs(result.StdOut); - } - - /// - /// Calls `test` on the `user-settings-file` resource. - /// - /// The input resource data. - /// The output resource data and the diff. - private static (UserSettingsFileResourceData, List) Test(UserSettingsFileResourceData resourceData) - { - var result = RunDSCv3Command(UserSettingsFileResource, TestFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - return GetSingleOutputLineAndDiffAs(result.StdOut); - } - - /// - /// Calls `export` on the `user-settings-file` resource. - /// - /// The input resource data. - /// The output resource data. - private static UserSettingsFileResourceData Export(UserSettingsFileResourceData resourceData) - { - var result = RunDSCv3Command(UserSettingsFileResource, ExportFunction, resourceData); - AssertSuccessfulResourceRun(ref result); - return GetSingleOutputLineAs(result.StdOut); - } - - /// - /// Gets the current user settings from the settings file. - /// - /// The current user settings as a JsonObject. - private static JsonObject GetCurrentUserSettings() - { - var settingsContent = File.ReadAllText(WinGetSettingsHelper.GetUserSettingsPath()); - return JsonNode.Parse(settingsContent).AsObject(); - } - - /// - /// Adds or modifies mock properties in the settings. - /// - /// Target settings. - /// The mock value. - private static void AddOrModifyMockProperties(JsonObject settings, string value) - { - settings[SettingsMock] = value; - settings[SettingsMockObject] ??= new JsonObject(); - settings[SettingsMockObject][SettingsMockNested] = value; - } - - /// - /// Asserts that the settings contain the expected mock properties. - /// - /// Target settings. - /// The expected mock value. - private static void AssertMockProperties(JsonObject settings, string value) - { - Assert.IsNotNull(settings); - Assert.IsTrue(settings.ContainsKey(SettingsMock)); - Assert.AreEqual(settings[SettingsMock].ToString(), value); - Assert.IsTrue(settings.ContainsKey(SettingsMockObject)); - Assert.IsTrue(settings[SettingsMockObject].AsObject().ContainsKey(SettingsMockNested)); - Assert.AreEqual(settings[SettingsMockObject][SettingsMockNested].ToString(), value); - } - - /// - /// Asserts that the diff state is as expected. - /// - /// The expected settings. - /// The actual settings. - private static void AssertSettingsAreEqual(JsonObject expected, JsonObject actual) - { - Assert.IsTrue(JsonNode.DeepEquals(expected, actual)); - } - - /// - /// Gets the settings argument based on the action. - /// - /// The action value. - /// The settings argument as a JsonObject. - private static JsonObject GetSettingsArg(string action) => action == ActionPropertyValueFull ? GetCurrentUserSettings() : new (); - - private class UserSettingsFileResourceData - { - [JsonPropertyName(InDesiredStatePropertyName)] - public bool? InDesiredState { get; set; } - - [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] - public string Action { get; set; } - - public JsonObject Settings { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests; + +using AppInstallerCLIE2ETests.Helpers; +using NUnit.Framework; +using System.Collections.Generic; +using System.IO; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +/// +/// `Configure` command tests. +/// +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] +[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] +public class DSCv3UserSettingsFileResourceCommand : DSCv3ResourceTestBase +{ + private const string UserSettingsFileResource = "user-settings-file"; + private const string SettingsPropertyName = "settings"; + private const string ActionPropertyValueFull = "Full"; + private const string ActionPropertyValuePartial = "Partial"; + private const string SettingsMock = "mock"; + private const string SettingsMockObject = "mockObject"; + private const string SettingsMockNested = "mockNested"; + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + TestCommon.SetupTestSource(); + EnsureTestResourcePresence(); + } + + /// + /// Teardown done once after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTeardown() + { + WinGetSettingsHelper.InitializeWingetSettings(); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + // Reset the settings to default before each test. + WinGetSettingsHelper.InitializeWingetSettings(); + WinGetSettingsHelper.ConfigureFeature("dsc3", true); + } + + /// + /// Calls `get` on the `user-settings-file` resource. + /// + [Test] + public void UserSettingsFile_Get() + { + var expected = GetCurrentUserSettings(); + var getOutput = Get(new ()); + + Assert.IsNotNull(getOutput); + Assert.IsNull(getOutput.Action); + AssertSettingsAreEqual(expected, getOutput.Settings); + } + + /// + /// Calls `set` on the `user-settings-file` resource with no diff. + /// + /// The action value. + [Test] + [TestCase(ActionPropertyValueFull)] + [TestCase(ActionPropertyValuePartial)] + public void UserSettingsFile_Set_NoDiff(string action) + { + var setSettings = GetSettingsArg(action); + + (var setOutput, var setDiff) = Set(new () { Action = action, Settings = setSettings }); + + var expected = GetCurrentUserSettings(); + + Assert.IsNotNull(setOutput); + Assert.AreEqual(action, setOutput.Action); + AssertSettingsAreEqual(expected, setOutput.Settings); + AssertDiffState(setDiff, []); + } + + /// + /// Calls `set` on the `user-settings-file` resource to add fields. + /// + /// The action value. + [Test] + [TestCase(ActionPropertyValueFull)] + [TestCase(ActionPropertyValuePartial)] + public void UserSettingsFile_Set_AddFields(string action) + { + // Call `set` to add mock properties to the settings + var setSettings = GetSettingsArg(action); + AddOrModifyMockProperties(setSettings, "mock"); + + (var setOutput, var setDiff) = Set(new () { Action = action, Settings = setSettings }); + + var expected = GetCurrentUserSettings(); + + // Assert that the settings are added + Assert.IsNotNull(setOutput); + Assert.AreEqual(action, setOutput.Action); + AssertMockProperties(setOutput.Settings, "mock"); + AssertSettingsAreEqual(expected, setOutput.Settings); + AssertDiffState(setDiff, [ SettingsPropertyName ]); + } + + /// + /// Calls `set` on the `user-settings-file` resource to ensure action is partial by default. + /// + [Test] + public void UserSettingsFile_Set_ActionIsPartialByDefault() + { + // Call `set` to add mock properties to the settings + var setSettings = GetSettingsArg(ActionPropertyValuePartial); + AddOrModifyMockProperties(setSettings, "mock"); + + var expected = GetCurrentUserSettings(); + AddOrModifyMockProperties(expected, "mock"); + + (var setOutput, var setDiff) = Set(new () { Settings = setSettings }); + + // Assert that the settings are added + Assert.IsNotNull(setOutput); + Assert.AreEqual(setOutput.Action, ActionPropertyValuePartial); + AssertMockProperties(setOutput.Settings, "mock"); + AssertSettingsAreEqual(expected, setOutput.Settings); + AssertDiffState(setDiff, [ SettingsPropertyName ]); + } + + /// + /// Calls `set` on the `user-settings-file` resource to update fields. + /// + /// The action value. + [Test] + [TestCase(ActionPropertyValueFull)] + [TestCase(ActionPropertyValuePartial)] + public void UserSettingsFile_Set_UpdateFields(string action) + { + // Add mock properties to the settings + var set1Settings = new JsonObject(); + AddOrModifyMockProperties(set1Settings, "mock_old"); + Set(new () { Action = ActionPropertyValuePartial, Settings = set1Settings }); + + // Call `set` to update the settings + var set2Settings = GetSettingsArg(action); + AddOrModifyMockProperties(set2Settings, "mock_new"); + + (var setOutput, var setDiff) = Set(new () { Action = action, Settings = set2Settings }); + + var expected = GetCurrentUserSettings(); + + // Assert that the settings are updated + Assert.IsNotNull(setOutput); + Assert.AreEqual(action, setOutput.Action); + AssertMockProperties(setOutput.Settings, "mock_new"); + AssertSettingsAreEqual(expected, setOutput.Settings); + AssertDiffState(setDiff, [ SettingsPropertyName ]); + } + + /// + /// Calls `test` on the `user-settings-file` resource to check if the settings are in desired state. + /// + /// The action value. + [Test] + [TestCase(ActionPropertyValueFull)] + [TestCase(ActionPropertyValuePartial)] + public void UserSettingsFile_Test_InDesiredState(string action) + { + // Add mock properties to the settings + var setSettings = new JsonObject(); + AddOrModifyMockProperties(setSettings, "mock"); + Set(new () { Action = ActionPropertyValuePartial, Settings = setSettings }); + + // Call `test` to check the settings + var testSettings = GetSettingsArg(action); + AddOrModifyMockProperties(testSettings, "mock"); + + (var testOutput, var testDiff) = Test(new () { Action = action, Settings = testSettings }); + + var expected = GetCurrentUserSettings(); + + // Assert that the settings are in desired state + Assert.IsNotNull(testOutput); + Assert.AreEqual(action, testOutput.Action); + AssertMockProperties(testOutput.Settings, "mock"); + AssertSettingsAreEqual(expected, testOutput.Settings); + Assert.IsTrue(testOutput.InDesiredState); + AssertDiffState(testDiff, []); + } + + /// + /// Calls `test` on the `user-settings-file` resource to check if the settings are not in desired state. + /// + /// The action value. + [Test] + [TestCase(ActionPropertyValueFull)] + [TestCase(ActionPropertyValuePartial)] + public void UserSettingsFile_Test_NotInDesiredState(string action) + { + // Add mock properties to the settings + var setSettings = new JsonObject(); + AddOrModifyMockProperties(setSettings, "mock_set"); + Set(new () { Action = ActionPropertyValuePartial, Settings = setSettings }); + + // Call `test` to check the settings + var testSettings = GetSettingsArg(action); + AddOrModifyMockProperties(testSettings, "mock_test"); + + (var testOutput, var testDiff) = Test(new () { Action = action, Settings = testSettings }); + + var expected = GetCurrentUserSettings(); + + // Assert that the settings are not in desired state + Assert.IsNotNull(testOutput); + Assert.AreEqual(action, testOutput.Action); + AssertMockProperties(testOutput.Settings, "mock_set"); + AssertSettingsAreEqual(expected, testOutput.Settings); + Assert.IsFalse(testOutput.InDesiredState); + AssertDiffState(testDiff, [ SettingsPropertyName ]); + } + + /// + /// Calls `export` on the `user-settings-file` resource to export the settings. + /// + [Test] + public void UserSettingsFile_Export() + { + var expected = GetCurrentUserSettings(); + var exportOutput = Export(new ()); + + Assert.IsNotNull(exportOutput); + Assert.IsNull(exportOutput.Action); + AssertSettingsAreEqual(expected, exportOutput.Settings); + } + + /// + /// Calls `get` on the `user-settings-file` resource. + /// + /// The input resource data. + /// The output resource data. + private static UserSettingsFileResourceData Get(UserSettingsFileResourceData resourceData) + { + var result = RunDSCv3Command(UserSettingsFileResource, GetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + return GetSingleOutputLineAs(result.StdOut); + } + + /// + /// Calls `set` on the `user-settings-file` resource. + /// + /// The input resource data. + /// The output resource data and the diff. + private static (UserSettingsFileResourceData, List) Set(UserSettingsFileResourceData resourceData) + { + var result = RunDSCv3Command(UserSettingsFileResource, SetFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + return GetSingleOutputLineAndDiffAs(result.StdOut); + } + + /// + /// Calls `test` on the `user-settings-file` resource. + /// + /// The input resource data. + /// The output resource data and the diff. + private static (UserSettingsFileResourceData, List) Test(UserSettingsFileResourceData resourceData) + { + var result = RunDSCv3Command(UserSettingsFileResource, TestFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + return GetSingleOutputLineAndDiffAs(result.StdOut); + } + + /// + /// Calls `export` on the `user-settings-file` resource. + /// + /// The input resource data. + /// The output resource data. + private static UserSettingsFileResourceData Export(UserSettingsFileResourceData resourceData) + { + var result = RunDSCv3Command(UserSettingsFileResource, ExportFunction, resourceData); + AssertSuccessfulResourceRun(ref result); + return GetSingleOutputLineAs(result.StdOut); + } + + /// + /// Gets the current user settings from the settings file. + /// + /// The current user settings as a JsonObject. + private static JsonObject GetCurrentUserSettings() + { + var settingsContent = File.ReadAllText(WinGetSettingsHelper.GetUserSettingsPath()); + return JsonNode.Parse(settingsContent).AsObject(); + } + + /// + /// Adds or modifies mock properties in the settings. + /// + /// Target settings. + /// The mock value. + private static void AddOrModifyMockProperties(JsonObject settings, string value) + { + settings[SettingsMock] = value; + settings[SettingsMockObject] ??= new JsonObject(); + settings[SettingsMockObject][SettingsMockNested] = value; + } + + /// + /// Asserts that the settings contain the expected mock properties. + /// + /// Target settings. + /// The expected mock value. + private static void AssertMockProperties(JsonObject settings, string value) + { + Assert.IsNotNull(settings); + Assert.IsTrue(settings.ContainsKey(SettingsMock)); + Assert.AreEqual(settings[SettingsMock].ToString(), value); + Assert.IsTrue(settings.ContainsKey(SettingsMockObject)); + Assert.IsTrue(settings[SettingsMockObject].AsObject().ContainsKey(SettingsMockNested)); + Assert.AreEqual(settings[SettingsMockObject][SettingsMockNested].ToString(), value); + } + + /// + /// Asserts that the diff state is as expected. + /// + /// The expected settings. + /// The actual settings. + private static void AssertSettingsAreEqual(JsonObject expected, JsonObject actual) + { + Assert.IsTrue(JsonNode.DeepEquals(expected, actual)); + } + + /// + /// Gets the settings argument based on the action. + /// + /// The action value. + /// The settings argument as a JsonObject. + private static JsonObject GetSettingsArg(string action) => action == ActionPropertyValueFull ? GetCurrentUserSettings() : new (); + + private class UserSettingsFileResourceData + { + [JsonPropertyName(InDesiredStatePropertyName)] + public bool? InDesiredState { get; set; } + + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)] + public string Action { get; set; } + + public JsonObject Settings { get; set; } + } +} diff --git a/src/AppInstallerCLIE2ETests/DownloadCommand.cs b/src/AppInstallerCLIE2ETests/DownloadCommand.cs index ba40685cc3..f6c10373e0 100644 --- a/src/AppInstallerCLIE2ETests/DownloadCommand.cs +++ b/src/AppInstallerCLIE2ETests/DownloadCommand.cs @@ -69,7 +69,7 @@ public void DownloadToDirectory() Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); TestCommon.AssertInstallerDownload(downloadDir, "TestExeInstaller", "2.0.0.0", ProcessorArchitecture.X86, TestCommon.Scope.User, PackageInstallerType.Exe); } - + /// /// Downloads the test installer with Arm64. /// @@ -78,11 +78,11 @@ public void DownloadWithArm64() { var downloadDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("download", $"AppInstallerTest.TestMultipleInstallers --scope user --download-directory {downloadDir} --architecture Arm64"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); -#pragma warning disable CA1416 // Validate platform compatibility. Arm64 is not reachable. - TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.Arm64, TestCommon.Scope.User, PackageInstallerType.Nullsoft, "en-US"); -#pragma warning restore CA1416 - } + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); +#pragma warning disable CA1416 // Validate platform compatibility. Arm64 is not reachable. + TestCommon.AssertInstallerDownload(downloadDir, "TestMultipleInstallers", "1.0.0.0", ProcessorArchitecture.Arm64, TestCommon.Scope.User, PackageInstallerType.Nullsoft, "en-US"); +#pragma warning restore CA1416 + } /// /// Downloads the test installer using the user scope argument. diff --git a/src/AppInstallerCLIE2ETests/ErrorCommand.cs b/src/AppInstallerCLIE2ETests/ErrorCommand.cs index 10a257defe..a2504bbb63 100644 --- a/src/AppInstallerCLIE2ETests/ErrorCommand.cs +++ b/src/AppInstallerCLIE2ETests/ErrorCommand.cs @@ -1,139 +1,139 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test error command. - /// - public class ErrorCommand - { - /// - /// Reset settings file to avoid affecting output from error command. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - WinGetSettingsHelper.InitializeWingetSettings(); - } - - /// - /// Tests 0. - /// - [Test] - public void Success() - { - var result = TestCommon.RunAICLICommand("error", "0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x00000000")); - } - - /// - /// Tests 0x8a15c001. - /// - [Test] - public void HexError() - { - var result = TestCommon.RunAICLICommand("error", "0x8a15c001"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x8a15c001")); - Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); - } - - /// - /// Tests a number larger than an HRESULT. - /// - [Test] - public void HexErrorTooBig() - { - var result = TestCommon.RunAICLICommand("error", "0x8a15c0014"); - Assert.AreEqual(Constants.ErrorCode.E_INVALIDARG, result.ExitCode); - Assert.True(result.StdOut.Contains("The given number is too large to be an HRESULT.")); - } - - /// - /// Tests 2316681217. - /// - [Test] - public void DecimalError() - { - var result = TestCommon.RunAICLICommand("error", "2316681217"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x8a15c001")); - Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); - } - - /// - /// Tests -1978335191. - /// - [Test] - public void NegativeDecimalError() - { - var result = TestCommon.RunAICLICommand("error", "-- -1978335191"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x8a150029")); - Assert.True(result.StdOut.Contains("APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE")); - } - - /// - /// Tests 0x8a15c000. - /// - [Test] - public void HexErrorNotFound() - { - var result = TestCommon.RunAICLICommand("error", "0x8a15c000"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x8a15c000")); - Assert.True(result.StdOut.Contains("Unknown error code")); - } - - /// - /// Tests 0xA150202. - /// - [Test] - public void NonError() - { - var result = TestCommon.RunAICLICommand("error", "0xA150202"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x0a150202")); - Assert.True(result.StdOut.Contains("WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE")); - } - - /// - /// Tests WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE. - /// - [Test] - public void Symbol() - { - var result = TestCommon.RunAICLICommand("error", "WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x8a15c001")); - Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); - Assert.AreEqual(2, result.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries).Length); - } - - /// - /// Tests config. - /// - [Test] - public void String() - { - var result = TestCommon.RunAICLICommand("error", "config"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("0x8a15c001")); - Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); - Assert.True(result.StdOut.Contains("0x8a15c110")); - Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT")); - - // This contains config in it's message. - Assert.True(result.StdOut.Contains("0x8a150038")); - Assert.True(result.StdOut.Contains("APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test error command. + /// + public class ErrorCommand + { + /// + /// Reset settings file to avoid affecting output from error command. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.InitializeWingetSettings(); + } + + /// + /// Tests 0. + /// + [Test] + public void Success() + { + var result = TestCommon.RunAICLICommand("error", "0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x00000000")); + } + + /// + /// Tests 0x8a15c001. + /// + [Test] + public void HexError() + { + var result = TestCommon.RunAICLICommand("error", "0x8a15c001"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x8a15c001")); + Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); + } + + /// + /// Tests a number larger than an HRESULT. + /// + [Test] + public void HexErrorTooBig() + { + var result = TestCommon.RunAICLICommand("error", "0x8a15c0014"); + Assert.AreEqual(Constants.ErrorCode.E_INVALIDARG, result.ExitCode); + Assert.True(result.StdOut.Contains("The given number is too large to be an HRESULT.")); + } + + /// + /// Tests 2316681217. + /// + [Test] + public void DecimalError() + { + var result = TestCommon.RunAICLICommand("error", "2316681217"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x8a15c001")); + Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); + } + + /// + /// Tests -1978335191. + /// + [Test] + public void NegativeDecimalError() + { + var result = TestCommon.RunAICLICommand("error", "-- -1978335191"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x8a150029")); + Assert.True(result.StdOut.Contains("APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE")); + } + + /// + /// Tests 0x8a15c000. + /// + [Test] + public void HexErrorNotFound() + { + var result = TestCommon.RunAICLICommand("error", "0x8a15c000"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x8a15c000")); + Assert.True(result.StdOut.Contains("Unknown error code")); + } + + /// + /// Tests 0xA150202. + /// + [Test] + public void NonError() + { + var result = TestCommon.RunAICLICommand("error", "0xA150202"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x0a150202")); + Assert.True(result.StdOut.Contains("WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE")); + } + + /// + /// Tests WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE. + /// + [Test] + public void Symbol() + { + var result = TestCommon.RunAICLICommand("error", "WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x8a15c001")); + Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); + Assert.AreEqual(2, result.StdOut.Split('\n', System.StringSplitOptions.RemoveEmptyEntries).Length); + } + + /// + /// Tests config. + /// + [Test] + public void String() + { + var result = TestCommon.RunAICLICommand("error", "config"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("0x8a15c001")); + Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE")); + Assert.True(result.StdOut.Contains("0x8a15c110")); + Assert.True(result.StdOut.Contains("WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT")); + + // This contains config in it's message. + Assert.True(result.StdOut.Contains("0x8a150038")); + Assert.True(result.StdOut.Contains("APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/FeaturesCommand.cs b/src/AppInstallerCLIE2ETests/FeaturesCommand.cs index ebea4aea2b..f8cf49c344 100644 --- a/src/AppInstallerCLIE2ETests/FeaturesCommand.cs +++ b/src/AppInstallerCLIE2ETests/FeaturesCommand.cs @@ -1,61 +1,61 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Features command tests. - /// - public class FeaturesCommand : BaseCommand - { - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - WinGetSettingsHelper.InitializeAllFeatures(false); - } - - /// - /// Tear down. - /// - [TearDown] - public void TearDown() - { - WinGetSettingsHelper.InitializeAllFeatures(false); - } - - /// - /// Tests winget features. - /// - [Test] - public void DisplayFeatures() - { - var result = TestCommon.RunAICLICommand("features", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Direct MSI Installation")); - } - - /// - /// Tests enabled winget features. - /// - [Test] - public void EnableExperimentalFeatures() - { - WinGetSettingsHelper.ConfigureFeature("experimentalArg", true); - WinGetSettingsHelper.ConfigureFeature("experimentalCmd", true); - WinGetSettingsHelper.ConfigureFeature("directMSI", true); - WinGetSettingsHelper.ConfigureFeature("resume", true); - WinGetSettingsHelper.ConfigureFeature("fonts", true); - var result = TestCommon.RunAICLICommand("features", string.Empty); - Assert.True(result.StdOut.Contains("Enabled")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Features command tests. + /// + public class FeaturesCommand : BaseCommand + { + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + WinGetSettingsHelper.InitializeAllFeatures(false); + } + + /// + /// Tear down. + /// + [TearDown] + public void TearDown() + { + WinGetSettingsHelper.InitializeAllFeatures(false); + } + + /// + /// Tests winget features. + /// + [Test] + public void DisplayFeatures() + { + var result = TestCommon.RunAICLICommand("features", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Direct MSI Installation")); + } + + /// + /// Tests enabled winget features. + /// + [Test] + public void EnableExperimentalFeatures() + { + WinGetSettingsHelper.ConfigureFeature("experimentalArg", true); + WinGetSettingsHelper.ConfigureFeature("experimentalCmd", true); + WinGetSettingsHelper.ConfigureFeature("directMSI", true); + WinGetSettingsHelper.ConfigureFeature("resume", true); + WinGetSettingsHelper.ConfigureFeature("fonts", true); + var result = TestCommon.RunAICLICommand("features", string.Empty); + Assert.True(result.StdOut.Contains("Enabled")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/GroupPolicy.cs b/src/AppInstallerCLIE2ETests/GroupPolicy.cs index ac0625ba6c..1f2b874306 100644 --- a/src/AppInstallerCLIE2ETests/GroupPolicy.cs +++ b/src/AppInstallerCLIE2ETests/GroupPolicy.cs @@ -1,309 +1,309 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Tests for enforcement of Group Policy. - /// Behavior is better tested in the unit tests; these tests mostly ensure match between the code and the definition. - /// - public class GroupPolicy : BaseCommand - { - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - WinGetSettingsHelper.InitializeAllFeatures(false); - GroupPolicyHelper.DeleteExistingPolicies(); - } - - /// - /// Tear down. - /// - [TearDown] - public void TearDown() - { - WinGetSettingsHelper.InitializeAllFeatures(false); - GroupPolicyHelper.DeleteExistingPolicies(); - } - - /// - /// Test winget search is disabled by policy. - /// - [Test] - public void PolicyEnableWinget() - { - GroupPolicyHelper.EnableWinget.Disable(); - var result = TestCommon.RunAICLICommand("search", "foo"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - // Scenario if Policy WinGet is disabled but Policy EnableWindowsPackageManagerCommandLineInterfaces is Enabled. - GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Enable(); - result = TestCommon.RunAICLICommand("search", "foo"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - // Scenario if Policy WinGet is disabled but Policy EnableWindowsPackageManagerCommandLineInterfaces is Not-Configured. - GroupPolicyHelper.EnableWinGetCommandLineInterfaces.SetNotConfigured(); - result = TestCommon.RunAICLICommand("search", "foo"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - // Scenario if Policy WinGet is enabled but Policy EnableWindowsPackageManagerCommandLineInterfaces is disabled. - GroupPolicyHelper.EnableWinget.Enable(); - GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Disable(); - result = TestCommon.RunAICLICommand("search", "foo"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - // Scenario if Policy WinGet is Not-Configured but Policy EnableWindowsPackageManagerCommandLineInterfaces is disabled. - GroupPolicyHelper.EnableWinget.SetNotConfigured(); - GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Disable(); - result = TestCommon.RunAICLICommand("search", "foo"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - - /// - /// Test winget settings is disable by policy. - /// - [Test] - public void EnableSettings() - { - GroupPolicyHelper.EnableSettings.Disable(); - var result = TestCommon.RunAICLICommand("settings", string.Empty); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - - /// - /// Test experimental features policy. - /// - [Test] - public void EnableExperimentalFeatures() - { - WinGetSettingsHelper.ConfigureFeature("experimentalCmd", true); - var result = TestCommon.RunAICLICommand("experimental", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // An experimental feature disabled by Group Policy behaves the same as one that is not enabled. - // The expected result is a command line error as the argument validation rejects this. - GroupPolicyHelper.EnableExperimentalFeatures.Disable(); - result = TestCommon.RunAICLICommand("experimental", string.Empty); - Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); - } - - /// - /// Test install via manifest is disabled by policy. - /// - [Test] - public void EnableLocalManifests() - { - GroupPolicyHelper.EnableLocalManifests.Disable(); - var result = TestCommon.RunAICLICommand("install", $"-m {TestCommon.GetTestDataFile(@"Manifests\TestExeInstaller.yaml")}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - - /// - /// Test install without checking the hash is disabled by policy. - /// - [Test] - public void EnableHashOverride() - { - GroupPolicyHelper.EnableHashOverride.Disable(); - var result = TestCommon.RunAICLICommand("install", "AnyPackage --ignore-security-hash"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - - /// - /// Test install ignoring the malware scan is disabled by policy. - /// - [Test] - public void EnableIgnoreLocalArchiveMalwareScanOverride() - { - GroupPolicyHelper.EnableLocalArchiveMalwareScanOverride.Disable(); - var result = TestCommon.RunAICLICommand("install", "AnyPackage --ignore-local-archive-malware-scan"); - - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - - /// - /// Test winget source is enabled by policy. - /// - [Test] - public void EnableDefaultSource() - { - // Default sources are disabled during setup so they are missing. - var result = TestCommon.RunAICLICommand("source list", "winget"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - - GroupPolicyHelper.EnableDefaultSource.Enable(); - result = TestCommon.RunAICLICommand("source list", "winget"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - } - - /// - /// Test store source is enabled by policy. - /// - [Test] - public void EnableMicrosoftStoreSource() - { - // Default sources are disabled during setup so they are missing. - var result = TestCommon.RunAICLICommand("source list", "msstore"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - - GroupPolicyHelper.EnableMicrosoftStoreSource.Enable(); - result = TestCommon.RunAICLICommand("source list", "msstore"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - } - - /// - /// Test font source is enabled by policy. - /// - [Test] - public void EnableFontSource() - { - GroupPolicyHelper.EnableFontSource.Disable(); - var result = TestCommon.RunAICLICommand("source list", "winget-font"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - - GroupPolicyHelper.EnableFontSource.Enable(); - result = TestCommon.RunAICLICommand("source list", "winget-font"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - } - - /// - /// Test additional sources are enabled by policy. - /// - [Test] - public void EnableAdditionalSources() - { - // Remove the test source, then add it with policy. - TestCommon.RunAICLICommand("source remove", "TestSource"); - var result = TestCommon.RunAICLICommand("source list", "TestSource"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] - { - "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}", - }); - - result = TestCommon.RunAICLICommand("source list", "TestSource"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - } - - /// - /// Test additional sources with trust levels and explicit are enabled by policy. - /// - [Test] - public void EnableAdditionalSources_TrustLevel_Explicit() - { - // Remove the test source, then add it with policy. - TestCommon.RunAICLICommand("source remove", "TestSource"); - var result = TestCommon.RunAICLICommand("source list", "TestSource"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] - { - "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\",\"TrustLevel\":[\"Trusted\"],\"Explicit\":true}", - }); - - result = TestCommon.RunAICLICommand("source list", "TestSource"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Trust Level")); - Assert.True(result.StdOut.Contains("Trusted")); - - var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode); - Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'")); - } - - /// - /// Test enable allowed sources. - /// - [Test] - public void EnableAllowedSources() - { - // Try listing the test source. We should only see it if it is allowed. - // With allowed sources disabled: - GroupPolicyHelper.EnableAllowedSources.Disable(); - var result = TestCommon.RunAICLICommand("source list", "TestSource"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - - // With allowed sources enabled, but not listing the test source: - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] - { - "{\"Arg\":\"An argument\",\"Data\":\"Some data\",\"Identifier\":\"Test id\",\"Name\":\"NotTestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}", - }); - - result = TestCommon.RunAICLICommand("source list", "TestSource"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - - // With the test source allowed: - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] - { - "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}", - }); - - result = TestCommon.RunAICLICommand("source list", "TestSource"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - } - - /// - /// Tests source auto update policy. - /// - [Test] - public void SourceAutoUpdateInterval() - { - // Test this policy by inspecting the result of --info - GroupPolicyHelper.SourceAutoUpdateInterval.SetEnabledValue(123); - var result = TestCommon.RunAICLICommand(string.Empty, "--info"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("Source Auto Update Interval In Minutes 123")); - } - - /// - /// Test configuration is disabled by policy. - /// - [Test] - public void EnableConfiguration() - { - GroupPolicyHelper.EnableConfiguration.Disable(); - var result = TestCommon.RunAICLICommand("configure", TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - result = TestCommon.RunAICLICommand("configure show", TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - - /// - /// Test that using a custom configuration processor path is disabled by policy. - /// - [Test] - public void EnableConfigurationProcessorPath() - { - GroupPolicyHelper.EnableConfigurationProcessorPath.Disable(); - - // --processor-path is rejected by policy at argument validation time across all configure subcommands. - var result = TestCommon.RunAICLICommand("configure", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - result = TestCommon.RunAICLICommand("configure show", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - result = TestCommon.RunAICLICommand("configure test", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - result = TestCommon.RunAICLICommand("configure validate", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - - // When not configured, the argument is allowed by policy (admin setting governs). - GroupPolicyHelper.EnableConfigurationProcessorPath.SetNotConfigured(); - result = TestCommon.RunAICLICommand("configure show", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); - Assert.AreNotEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Tests for enforcement of Group Policy. + /// Behavior is better tested in the unit tests; these tests mostly ensure match between the code and the definition. + /// + public class GroupPolicy : BaseCommand + { + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + WinGetSettingsHelper.InitializeAllFeatures(false); + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Tear down. + /// + [TearDown] + public void TearDown() + { + WinGetSettingsHelper.InitializeAllFeatures(false); + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Test winget search is disabled by policy. + /// + [Test] + public void PolicyEnableWinget() + { + GroupPolicyHelper.EnableWinget.Disable(); + var result = TestCommon.RunAICLICommand("search", "foo"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + // Scenario if Policy WinGet is disabled but Policy EnableWindowsPackageManagerCommandLineInterfaces is Enabled. + GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Enable(); + result = TestCommon.RunAICLICommand("search", "foo"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + // Scenario if Policy WinGet is disabled but Policy EnableWindowsPackageManagerCommandLineInterfaces is Not-Configured. + GroupPolicyHelper.EnableWinGetCommandLineInterfaces.SetNotConfigured(); + result = TestCommon.RunAICLICommand("search", "foo"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + // Scenario if Policy WinGet is enabled but Policy EnableWindowsPackageManagerCommandLineInterfaces is disabled. + GroupPolicyHelper.EnableWinget.Enable(); + GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Disable(); + result = TestCommon.RunAICLICommand("search", "foo"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + // Scenario if Policy WinGet is Not-Configured but Policy EnableWindowsPackageManagerCommandLineInterfaces is disabled. + GroupPolicyHelper.EnableWinget.SetNotConfigured(); + GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Disable(); + result = TestCommon.RunAICLICommand("search", "foo"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + /// + /// Test winget settings is disable by policy. + /// + [Test] + public void EnableSettings() + { + GroupPolicyHelper.EnableSettings.Disable(); + var result = TestCommon.RunAICLICommand("settings", string.Empty); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + /// + /// Test experimental features policy. + /// + [Test] + public void EnableExperimentalFeatures() + { + WinGetSettingsHelper.ConfigureFeature("experimentalCmd", true); + var result = TestCommon.RunAICLICommand("experimental", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // An experimental feature disabled by Group Policy behaves the same as one that is not enabled. + // The expected result is a command line error as the argument validation rejects this. + GroupPolicyHelper.EnableExperimentalFeatures.Disable(); + result = TestCommon.RunAICLICommand("experimental", string.Empty); + Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); + } + + /// + /// Test install via manifest is disabled by policy. + /// + [Test] + public void EnableLocalManifests() + { + GroupPolicyHelper.EnableLocalManifests.Disable(); + var result = TestCommon.RunAICLICommand("install", $"-m {TestCommon.GetTestDataFile(@"Manifests\TestExeInstaller.yaml")}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + /// + /// Test install without checking the hash is disabled by policy. + /// + [Test] + public void EnableHashOverride() + { + GroupPolicyHelper.EnableHashOverride.Disable(); + var result = TestCommon.RunAICLICommand("install", "AnyPackage --ignore-security-hash"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + /// + /// Test install ignoring the malware scan is disabled by policy. + /// + [Test] + public void EnableIgnoreLocalArchiveMalwareScanOverride() + { + GroupPolicyHelper.EnableLocalArchiveMalwareScanOverride.Disable(); + var result = TestCommon.RunAICLICommand("install", "AnyPackage --ignore-local-archive-malware-scan"); + + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + /// + /// Test winget source is enabled by policy. + /// + [Test] + public void EnableDefaultSource() + { + // Default sources are disabled during setup so they are missing. + var result = TestCommon.RunAICLICommand("source list", "winget"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + + GroupPolicyHelper.EnableDefaultSource.Enable(); + result = TestCommon.RunAICLICommand("source list", "winget"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + } + + /// + /// Test store source is enabled by policy. + /// + [Test] + public void EnableMicrosoftStoreSource() + { + // Default sources are disabled during setup so they are missing. + var result = TestCommon.RunAICLICommand("source list", "msstore"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + + GroupPolicyHelper.EnableMicrosoftStoreSource.Enable(); + result = TestCommon.RunAICLICommand("source list", "msstore"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + } + + /// + /// Test font source is enabled by policy. + /// + [Test] + public void EnableFontSource() + { + GroupPolicyHelper.EnableFontSource.Disable(); + var result = TestCommon.RunAICLICommand("source list", "winget-font"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + + GroupPolicyHelper.EnableFontSource.Enable(); + result = TestCommon.RunAICLICommand("source list", "winget-font"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + } + + /// + /// Test additional sources are enabled by policy. + /// + [Test] + public void EnableAdditionalSources() + { + // Remove the test source, then add it with policy. + TestCommon.RunAICLICommand("source remove", "TestSource"); + var result = TestCommon.RunAICLICommand("source list", "TestSource"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] + { + "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}", + }); + + result = TestCommon.RunAICLICommand("source list", "TestSource"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + } + + /// + /// Test additional sources with trust levels and explicit are enabled by policy. + /// + [Test] + public void EnableAdditionalSources_TrustLevel_Explicit() + { + // Remove the test source, then add it with policy. + TestCommon.RunAICLICommand("source remove", "TestSource"); + var result = TestCommon.RunAICLICommand("source list", "TestSource"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] + { + "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\",\"TrustLevel\":[\"Trusted\"],\"Explicit\":true}", + }); + + result = TestCommon.RunAICLICommand("source list", "TestSource"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Trust Level")); + Assert.True(result.StdOut.Contains("Trusted")); + + var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode); + Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'")); + } + + /// + /// Test enable allowed sources. + /// + [Test] + public void EnableAllowedSources() + { + // Try listing the test source. We should only see it if it is allowed. + // With allowed sources disabled: + GroupPolicyHelper.EnableAllowedSources.Disable(); + var result = TestCommon.RunAICLICommand("source list", "TestSource"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + + // With allowed sources enabled, but not listing the test source: + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] + { + "{\"Arg\":\"An argument\",\"Data\":\"Some data\",\"Identifier\":\"Test id\",\"Name\":\"NotTestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}", + }); + + result = TestCommon.RunAICLICommand("source list", "TestSource"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + + // With the test source allowed: + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new string[] + { + "{\"Arg\":\"https://localhost:5001/TestKit\",\"Data\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Identifier\":\"WingetE2E.Tests_8wekyb3d8bbwe\",\"Name\":\"TestSource\",\"Type\":\"Microsoft.PreIndexed.Package\"}", + }); + + result = TestCommon.RunAICLICommand("source list", "TestSource"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + } + + /// + /// Tests source auto update policy. + /// + [Test] + public void SourceAutoUpdateInterval() + { + // Test this policy by inspecting the result of --info + GroupPolicyHelper.SourceAutoUpdateInterval.SetEnabledValue(123); + var result = TestCommon.RunAICLICommand(string.Empty, "--info"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("Source Auto Update Interval In Minutes 123")); + } + + /// + /// Test configuration is disabled by policy. + /// + [Test] + public void EnableConfiguration() + { + GroupPolicyHelper.EnableConfiguration.Disable(); + var result = TestCommon.RunAICLICommand("configure", TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + result = TestCommon.RunAICLICommand("configure show", TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + + /// + /// Test that using a custom configuration processor path is disabled by policy. + /// + [Test] + public void EnableConfigurationProcessorPath() + { + GroupPolicyHelper.EnableConfigurationProcessorPath.Disable(); + + // --processor-path is rejected by policy at argument validation time across all configure subcommands. + var result = TestCommon.RunAICLICommand("configure", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + result = TestCommon.RunAICLICommand("configure show", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + result = TestCommon.RunAICLICommand("configure test", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + result = TestCommon.RunAICLICommand("configure validate", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + + // When not configured, the argument is allowed by policy (admin setting governs). + GroupPolicyHelper.EnableConfigurationProcessorPath.SetNotConfigured(); + result = TestCommon.RunAICLICommand("configure show", $"--processor-path C:\\dsc.exe {TestCommon.GetTestDataFile("Configuration\\ShowDetails_TestRepo.yml")}"); + Assert.AreNotEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, result.ExitCode); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs b/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs index 28bcdf2e7c..ef771dcd5f 100644 --- a/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs +++ b/src/AppInstallerCLIE2ETests/GroupPolicyHelper.cs @@ -1,511 +1,511 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Xml.Linq; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Win32; - using Newtonsoft.Json; - using NUnit.Framework; - - /// - /// Helper for setting Group Policy settings. - /// This helper reads the keys and values to use directly from the ADMX file to ensure that the names - /// used by the source code are correct. - /// - /// - /// This helper modifies the policies for winget configured in the machine. - /// - public class GroupPolicyHelper - { - private const string PoliciesDefinitionFileName = "DesktopAppInstaller.admx"; - private static Lazy policyDefinitions = new Lazy(() => - { - string filePath = TestCommon.GetTestDataFile(PoliciesDefinitionFileName); - string fileText = File.ReadAllText(filePath); - return XElement.Parse(fileText); - }); - - /// - /// Name of the policy. Used to identify it in the file. - /// - private string name; - - /// - /// ID of the value element of this policy (if it has one). - /// This assumes that each policy has a single value element. - /// - private string elementId; - - private GroupPolicyHelper(string name) - { - this.name = name; - } - - private GroupPolicyHelper(string name, string elementId) - { - this.name = name; - this.elementId = elementId; - } - - // Policies available. - - /// - /// Gets the Enable winget policy. - /// - public static GroupPolicyHelper EnableWinget { get; private set; } = new GroupPolicyHelper("EnableAppInstaller"); - - /// - /// Gets the Enable Windows Package Manager CommandLine Interfaces policy. - /// - public static GroupPolicyHelper EnableWinGetCommandLineInterfaces { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerCommandLineInterfaces"); - - /// - /// Gets the Enable settings policy. - /// - public static GroupPolicyHelper EnableSettings { get; private set; } = new GroupPolicyHelper("EnableSettings"); - - /// - /// Gets the Enable experimental features policy. - /// - public static GroupPolicyHelper EnableExperimentalFeatures { get; private set; } = new GroupPolicyHelper("EnableExperimentalFeatures"); - - /// - /// Gets the Enable local manifest policy. - /// - public static GroupPolicyHelper EnableLocalManifests { get; private set; } = new GroupPolicyHelper("EnableLocalManifestFiles"); - - /// - /// Gets the Enable hash override policy. - /// - public static GroupPolicyHelper EnableHashOverride { get; private set; } = new GroupPolicyHelper("EnableHashOverride"); - - /// - /// Gets the Enable ignore malware scan policy. - /// - public static GroupPolicyHelper EnableLocalArchiveMalwareScanOverride { get; private set; } = new GroupPolicyHelper("EnableLocalArchiveMalwareScanOverride"); - - /// - /// Gets the Enable default source policy. - /// - public static GroupPolicyHelper EnableDefaultSource { get; private set; } = new GroupPolicyHelper("EnableDefaultSource"); - - /// - /// Gets the Enable store source policy. - /// - public static GroupPolicyHelper EnableMicrosoftStoreSource { get; private set; } = new GroupPolicyHelper("EnableMicrosoftStoreSource"); - - /// - /// Gets the Enable font source policy. - /// - public static GroupPolicyHelper EnableFontSource { get; private set; } = new GroupPolicyHelper("EnableFontSource"); - - /// - /// Gets the Enable additional sources policy. - /// - public static GroupPolicyHelper EnableAdditionalSources { get; private set; } = new GroupPolicyHelper("EnableAdditionalSources", "AdditionalSources"); - - /// - /// Gets the Enable allowed sources policy. - /// - public static GroupPolicyHelper EnableAllowedSources { get; private set; } = new GroupPolicyHelper("EnableAllowedSources", "AllowedSources"); - - /// - /// Gets the Enable Windows Package Manager Configuration Interfaces policy. - /// - public static GroupPolicyHelper EnableConfiguration { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerConfiguration"); - - /// - /// Gets the Enable Windows Package Manager proxy command line options policy. - /// - public static GroupPolicyHelper EnableProxyCommandLineOptions { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerProxyCommandLineOptions"); - - /// - /// Gets the Enable Windows Package Manager Configuration processor path policy. - /// - public static GroupPolicyHelper EnableConfigurationProcessorPath { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerConfigurationProcessorPath"); - - /// - /// Gets the Bypass certificate pinning for Microsoft Store policy. - /// - public static GroupPolicyHelper BypassCertificatePinningForMicrosoftStore { get; private set; } = new GroupPolicyHelper("EnableBypassCertificatePinningForMicrosoftStore"); - - /// - /// Gets the Enable auto update interval policy. - /// - public static GroupPolicyHelper SourceAutoUpdateInterval { get; private set; } = new GroupPolicyHelper("SourceAutoUpdateInterval", "SourceAutoUpdateInterval"); - - private static GroupPolicyHelper[] AllPolicies { get; set; } = new GroupPolicyHelper[] - { - EnableWinget, - EnableSettings, - EnableExperimentalFeatures, - EnableLocalManifests, - EnableHashOverride, - EnableLocalArchiveMalwareScanOverride, - EnableDefaultSource, - EnableMicrosoftStoreSource, - EnableFontSource, - EnableAdditionalSources, - EnableAllowedSources, - SourceAutoUpdateInterval, - EnableWinGetCommandLineInterfaces, - EnableConfiguration, - EnableProxyCommandLineOptions, - EnableConfigurationProcessorPath, - }; - - /// - /// Gets the content of the ADMX file as an XML. - /// - private static XElement PolicyDefinitions => policyDefinitions.Value; - - /// - /// Gets the XML element that defines this policy. - /// - // The XML structure is like this: - // - // ... - // - // - // - // - private XElement PolicyElement => PolicyDefinitions - .Element(XmlNames.Policies) - .Elements(XmlNames.Policy) - .First(policy => policy.Attribute(XmlNames.Attributes.Name).Value == this.name); - - /// - /// Gets the path to the registry key that backs this policy. - /// - private string KeyPath => this.PolicyElement.Attribute(XmlNames.Attributes.Key).Value; - - /// - /// Gets the name of the registry value that backs this policy. - /// - private string ValueName => this.PolicyElement.Attribute(XmlNames.Attributes.ValueName)?.Value; - - /// - /// Gets the XElement that defines the single value element of this policy. - /// This only works if the policy has a single element for its value. - /// - // Looks for something like this: - // - // - // - // - // - // We use only list and decimal elements. - private XElement ValueElement => this.PolicyElement - .Element(XmlNames.Elements) - .Elements() - .First(element => element.Attribute(XmlNames.Attributes.Id).Value == this.elementId); - - /// - /// Deletes all of the existing policies from the registry. - /// - public static void DeleteExistingPolicies() - { - foreach (var policy in AllPolicies) - { - policy.SetNotConfigured(); - } - } - - /// - /// Sets the policy to the Enabled state. - /// This will fail if the policy's EnabledValue does not exist or is not exactly as expected. - /// - public void Enable() - { - // The expected format is like this: - // - // - // - // We expect the value to always be 1, but still parse it to catch errors in the ADMX. - int enabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.EnabledValue)); - using (RegistryKey key = this.GetKey()) - { - key.SetValue(this.ValueName, enabledValue); - } - - ReloadGroupPolicyIfAvailable(); - } - - /// - /// Sets the policy to the Disabled state. - /// This will fail if the policy's DisabledValue does not exist or is not exactly as expected. - /// - public void Disable() - { - // The expected format is like this: - // - // - // - // We expect the value to always be 0, but still parse it to catch errors in the ADMX. - int disabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.DisabledValue)); - using (RegistryKey key = this.GetKey()) - { - key.SetValue(this.ValueName, disabledValue); - } - - ReloadGroupPolicyIfAvailable(); - } - - /// - /// Sets the policy to the Not Configured state. - /// This deletes the value associated with the policy, including its list if it has one. - /// - public void SetNotConfigured() - { - // Delete the enabled/disabled value - if (this.ValueName != null) - { - using (RegistryKey key = this.GetKey()) - { - key.DeleteValue(this.ValueName, throwOnMissingValue: false); - } - } - - // Delete the value element - if (this.elementId != null) - { - if (this.ValueElement.Name == XmlNames.List) - { - // Lists are stored in separate keys. - Registry.LocalMachine.DeleteSubKeyTree(this.ValueElement.Attribute(XmlNames.Attributes.Key).Value, throwOnMissingSubKey: false); - } - else if (this.ValueElement.Name == XmlNames.Decimal) - { - // Decimals are stored in single values - using (RegistryKey key = this.GetKey()) - { - key.DeleteValue(this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value, throwOnMissingValue: false); - } - } - } - - ReloadGroupPolicyIfAvailable(); - } - - /// - /// Sets the value of the policy when enabled. - /// This uses only the "elements" of the policy, not the "enabledValue". - /// The type used in the registry is chosen automatically. - /// - /// Value of the policy. - public void SetEnabledValue(object value) - { - using (RegistryKey key = this.GetKey()) - { - key.SetValue( - this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value, - value); - } - } - - /// - /// Sets the list value of the policy when enabled. - /// This sets from the "elements" and also sets the enabled value as lists are also gated by a toggle. - /// This will fail if the value of the policy is not a list. - /// - /// Values to set in the list. - public void SetEnabledList(IEnumerable values) - { - this.Enable(); - - // Delete the existing list - string listKeyPath = this.ValueElement.Attribute(XmlNames.Attributes.Key).Value; - Registry.LocalMachine.DeleteSubKeyTree(listKeyPath, throwOnMissingSubKey: false); - - // Create and fill the key. - // This assumes that the values don't need to have special names or prefixes. - var listKey = Registry.LocalMachine.CreateSubKey(listKeyPath); - int index = 0; - foreach (string value in values) - { - TestContext.Out.WriteLine($"Setting {this.name} list value: {value}"); - listKey.SetValue(index++.ToString(), value); - } - - listKey.Close(); - } - - /// - /// Sets the list value of the policy when enabled. - /// This sets from the "elements" and also sets the enabled value as lists are also gated by a toggle. - /// This will fail if the value of the policy is not a list. - /// - /// Values to set in the list. - public void SetEnabledList(IEnumerable values) - { - this.SetEnabledList(values.Select(source => JsonConvert.SerializeObject(source))); - } - - [DllImport("WindowsPackageManager.dll", CallingConvention = CallingConvention.StdCall)] - private static extern int WindowsPackageManagerTestHook_ReloadGroupPolicy(); - - /// - /// Calls the in-process test hook to reload the GroupPolicy singleton from the current registry state. - /// Silently ignored if the DLL is not loaded in this process (e.g., out-of-process test scenarios). - /// - private static void ReloadGroupPolicyIfAvailable() - { - try - { - WindowsPackageManagerTestHook_ReloadGroupPolicy(); - } - catch (Exception) - { - // The DLL is not loaded in this process (out-of-process scenario); nothing to do. - } - } - - /// - /// Gets the value from a "decimal" child element. - /// - /// Element containing the decimal. - /// Value in the element. - private static int GetDecimalValue(XElement element) - { - // Reads a child that looks like this: - // - return int.Parse(element.Element(XmlNames.Decimal).Attribute(XmlNames.Attributes.Value).Value); - } - - /// - /// Gets the registry key backing this policy. - /// - /// - /// This assumes that all the policies are machine-wide. - /// If this changes, we will need to parse the class="machine|user" attribute. - /// - private RegistryKey GetKey() - { - return Registry.LocalMachine.CreateSubKey(this.KeyPath); - } - - /// - /// A group policy source object as used by AdditionalSources and AllowedSources. - /// - public class GroupPolicySource - { - /// - /// Gets or sets the source name. - /// - public string Name { get; set; } - - /// - /// Gets or sets the source arg. - /// - public string Arg { get; set; } - - /// - /// Gets or sets the source type. - /// - public string Type { get; set; } - - /// - /// Gets or sets the source data. - /// - public string Data { get; set; } - - /// - /// Gets or sets the source identifier. - /// - public string Identifier { get; set; } - - /// - /// Gets or sets certificate pinning. - /// - public GroupPolicyCertificatePinning CertificatePinning { get; set; } - - /// - /// Gets or sets the source trust levels. - /// - public string[] TrustLevel { get; set; } - - /// - /// Gets or sets a value indicating whether the source is explicit. - /// - public bool Explicit { get; set; } - } - - /// - /// Group policy certificate pinning. - /// - public class GroupPolicyCertificatePinning - { - /// - /// Gets or sets the cert pinning chains. - /// - public GroupPolicyCertificatePinningChain[] Chains { get; set; } - } - - /// - /// Group policy certificate pinning chain. - /// - public class GroupPolicyCertificatePinningChain - { - /// - /// Gets or sets the cert pinning details. - /// - public GroupPolicyCertificatePinningDetails[] Chain { get; set; } - } - - /// - /// Group policy certificate pinning details. - /// - public class GroupPolicyCertificatePinningDetails - { - /// - /// Gets or sets the validation. - /// - public string[] Validation { get; set; } - - /// - /// Gets or sets the embedded cert. - /// - public string EmbeddedCertificate { get; set; } - } - - /// - /// Names of the XML elements and attributes that make up the definition file. - /// - private static class XmlNames - { - // Root element - public static readonly XName PolicyDefinitions = XName.Get("policyDefinitions", Namespace); - - public static readonly XName Policies = XName.Get("policies", Namespace); - public static readonly XName Policy = XName.Get("policy", Namespace); - - public static readonly XName EnabledValue = XName.Get("enabledValue", Namespace); - public static readonly XName DisabledValue = XName.Get("disabledValue", Namespace); - public static readonly XName Elements = XName.Get("elements", Namespace); - - public static readonly XName Decimal = XName.Get("decimal", Namespace); - public static readonly XName List = XName.Get("list", Namespace); - - private const string Namespace = "http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions"; - - public static class Attributes - { - public const string Name = "name"; - public const string Value = "value"; - public const string Id = "id"; - public const string Key = "key"; - public const string ValueName = "valueName"; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Xml.Linq; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Win32; + using Newtonsoft.Json; + using NUnit.Framework; + + /// + /// Helper for setting Group Policy settings. + /// This helper reads the keys and values to use directly from the ADMX file to ensure that the names + /// used by the source code are correct. + /// + /// + /// This helper modifies the policies for winget configured in the machine. + /// + public class GroupPolicyHelper + { + private const string PoliciesDefinitionFileName = "DesktopAppInstaller.admx"; + private static Lazy policyDefinitions = new Lazy(() => + { + string filePath = TestCommon.GetTestDataFile(PoliciesDefinitionFileName); + string fileText = File.ReadAllText(filePath); + return XElement.Parse(fileText); + }); + + /// + /// Name of the policy. Used to identify it in the file. + /// + private string name; + + /// + /// ID of the value element of this policy (if it has one). + /// This assumes that each policy has a single value element. + /// + private string elementId; + + private GroupPolicyHelper(string name) + { + this.name = name; + } + + private GroupPolicyHelper(string name, string elementId) + { + this.name = name; + this.elementId = elementId; + } + + // Policies available. + + /// + /// Gets the Enable winget policy. + /// + public static GroupPolicyHelper EnableWinget { get; private set; } = new GroupPolicyHelper("EnableAppInstaller"); + + /// + /// Gets the Enable Windows Package Manager CommandLine Interfaces policy. + /// + public static GroupPolicyHelper EnableWinGetCommandLineInterfaces { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerCommandLineInterfaces"); + + /// + /// Gets the Enable settings policy. + /// + public static GroupPolicyHelper EnableSettings { get; private set; } = new GroupPolicyHelper("EnableSettings"); + + /// + /// Gets the Enable experimental features policy. + /// + public static GroupPolicyHelper EnableExperimentalFeatures { get; private set; } = new GroupPolicyHelper("EnableExperimentalFeatures"); + + /// + /// Gets the Enable local manifest policy. + /// + public static GroupPolicyHelper EnableLocalManifests { get; private set; } = new GroupPolicyHelper("EnableLocalManifestFiles"); + + /// + /// Gets the Enable hash override policy. + /// + public static GroupPolicyHelper EnableHashOverride { get; private set; } = new GroupPolicyHelper("EnableHashOverride"); + + /// + /// Gets the Enable ignore malware scan policy. + /// + public static GroupPolicyHelper EnableLocalArchiveMalwareScanOverride { get; private set; } = new GroupPolicyHelper("EnableLocalArchiveMalwareScanOverride"); + + /// + /// Gets the Enable default source policy. + /// + public static GroupPolicyHelper EnableDefaultSource { get; private set; } = new GroupPolicyHelper("EnableDefaultSource"); + + /// + /// Gets the Enable store source policy. + /// + public static GroupPolicyHelper EnableMicrosoftStoreSource { get; private set; } = new GroupPolicyHelper("EnableMicrosoftStoreSource"); + + /// + /// Gets the Enable font source policy. + /// + public static GroupPolicyHelper EnableFontSource { get; private set; } = new GroupPolicyHelper("EnableFontSource"); + + /// + /// Gets the Enable additional sources policy. + /// + public static GroupPolicyHelper EnableAdditionalSources { get; private set; } = new GroupPolicyHelper("EnableAdditionalSources", "AdditionalSources"); + + /// + /// Gets the Enable allowed sources policy. + /// + public static GroupPolicyHelper EnableAllowedSources { get; private set; } = new GroupPolicyHelper("EnableAllowedSources", "AllowedSources"); + + /// + /// Gets the Enable Windows Package Manager Configuration Interfaces policy. + /// + public static GroupPolicyHelper EnableConfiguration { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerConfiguration"); + + /// + /// Gets the Enable Windows Package Manager proxy command line options policy. + /// + public static GroupPolicyHelper EnableProxyCommandLineOptions { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerProxyCommandLineOptions"); + + /// + /// Gets the Enable Windows Package Manager Configuration processor path policy. + /// + public static GroupPolicyHelper EnableConfigurationProcessorPath { get; private set; } = new GroupPolicyHelper("EnableWindowsPackageManagerConfigurationProcessorPath"); + + /// + /// Gets the Bypass certificate pinning for Microsoft Store policy. + /// + public static GroupPolicyHelper BypassCertificatePinningForMicrosoftStore { get; private set; } = new GroupPolicyHelper("EnableBypassCertificatePinningForMicrosoftStore"); + + /// + /// Gets the Enable auto update interval policy. + /// + public static GroupPolicyHelper SourceAutoUpdateInterval { get; private set; } = new GroupPolicyHelper("SourceAutoUpdateInterval", "SourceAutoUpdateInterval"); + + private static GroupPolicyHelper[] AllPolicies { get; set; } = new GroupPolicyHelper[] + { + EnableWinget, + EnableSettings, + EnableExperimentalFeatures, + EnableLocalManifests, + EnableHashOverride, + EnableLocalArchiveMalwareScanOverride, + EnableDefaultSource, + EnableMicrosoftStoreSource, + EnableFontSource, + EnableAdditionalSources, + EnableAllowedSources, + SourceAutoUpdateInterval, + EnableWinGetCommandLineInterfaces, + EnableConfiguration, + EnableProxyCommandLineOptions, + EnableConfigurationProcessorPath, + }; + + /// + /// Gets the content of the ADMX file as an XML. + /// + private static XElement PolicyDefinitions => policyDefinitions.Value; + + /// + /// Gets the XML element that defines this policy. + /// + // The XML structure is like this: + // + // ... + // + // + // + // + private XElement PolicyElement => PolicyDefinitions + .Element(XmlNames.Policies) + .Elements(XmlNames.Policy) + .First(policy => policy.Attribute(XmlNames.Attributes.Name).Value == this.name); + + /// + /// Gets the path to the registry key that backs this policy. + /// + private string KeyPath => this.PolicyElement.Attribute(XmlNames.Attributes.Key).Value; + + /// + /// Gets the name of the registry value that backs this policy. + /// + private string ValueName => this.PolicyElement.Attribute(XmlNames.Attributes.ValueName)?.Value; + + /// + /// Gets the XElement that defines the single value element of this policy. + /// This only works if the policy has a single element for its value. + /// + // Looks for something like this: + // + // + // + // + // + // We use only list and decimal elements. + private XElement ValueElement => this.PolicyElement + .Element(XmlNames.Elements) + .Elements() + .First(element => element.Attribute(XmlNames.Attributes.Id).Value == this.elementId); + + /// + /// Deletes all of the existing policies from the registry. + /// + public static void DeleteExistingPolicies() + { + foreach (var policy in AllPolicies) + { + policy.SetNotConfigured(); + } + } + + /// + /// Sets the policy to the Enabled state. + /// This will fail if the policy's EnabledValue does not exist or is not exactly as expected. + /// + public void Enable() + { + // The expected format is like this: + // + // + // + // We expect the value to always be 1, but still parse it to catch errors in the ADMX. + int enabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.EnabledValue)); + using (RegistryKey key = this.GetKey()) + { + key.SetValue(this.ValueName, enabledValue); + } + + ReloadGroupPolicyIfAvailable(); + } + + /// + /// Sets the policy to the Disabled state. + /// This will fail if the policy's DisabledValue does not exist or is not exactly as expected. + /// + public void Disable() + { + // The expected format is like this: + // + // + // + // We expect the value to always be 0, but still parse it to catch errors in the ADMX. + int disabledValue = GetDecimalValue(this.PolicyElement.Element(XmlNames.DisabledValue)); + using (RegistryKey key = this.GetKey()) + { + key.SetValue(this.ValueName, disabledValue); + } + + ReloadGroupPolicyIfAvailable(); + } + + /// + /// Sets the policy to the Not Configured state. + /// This deletes the value associated with the policy, including its list if it has one. + /// + public void SetNotConfigured() + { + // Delete the enabled/disabled value + if (this.ValueName != null) + { + using (RegistryKey key = this.GetKey()) + { + key.DeleteValue(this.ValueName, throwOnMissingValue: false); + } + } + + // Delete the value element + if (this.elementId != null) + { + if (this.ValueElement.Name == XmlNames.List) + { + // Lists are stored in separate keys. + Registry.LocalMachine.DeleteSubKeyTree(this.ValueElement.Attribute(XmlNames.Attributes.Key).Value, throwOnMissingSubKey: false); + } + else if (this.ValueElement.Name == XmlNames.Decimal) + { + // Decimals are stored in single values + using (RegistryKey key = this.GetKey()) + { + key.DeleteValue(this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value, throwOnMissingValue: false); + } + } + } + + ReloadGroupPolicyIfAvailable(); + } + + /// + /// Sets the value of the policy when enabled. + /// This uses only the "elements" of the policy, not the "enabledValue". + /// The type used in the registry is chosen automatically. + /// + /// Value of the policy. + public void SetEnabledValue(object value) + { + using (RegistryKey key = this.GetKey()) + { + key.SetValue( + this.ValueElement.Attribute(XmlNames.Attributes.ValueName).Value, + value); + } + } + + /// + /// Sets the list value of the policy when enabled. + /// This sets from the "elements" and also sets the enabled value as lists are also gated by a toggle. + /// This will fail if the value of the policy is not a list. + /// + /// Values to set in the list. + public void SetEnabledList(IEnumerable values) + { + this.Enable(); + + // Delete the existing list + string listKeyPath = this.ValueElement.Attribute(XmlNames.Attributes.Key).Value; + Registry.LocalMachine.DeleteSubKeyTree(listKeyPath, throwOnMissingSubKey: false); + + // Create and fill the key. + // This assumes that the values don't need to have special names or prefixes. + var listKey = Registry.LocalMachine.CreateSubKey(listKeyPath); + int index = 0; + foreach (string value in values) + { + TestContext.Out.WriteLine($"Setting {this.name} list value: {value}"); + listKey.SetValue(index++.ToString(), value); + } + + listKey.Close(); + } + + /// + /// Sets the list value of the policy when enabled. + /// This sets from the "elements" and also sets the enabled value as lists are also gated by a toggle. + /// This will fail if the value of the policy is not a list. + /// + /// Values to set in the list. + public void SetEnabledList(IEnumerable values) + { + this.SetEnabledList(values.Select(source => JsonConvert.SerializeObject(source))); + } + + [DllImport("WindowsPackageManager.dll", CallingConvention = CallingConvention.StdCall)] + private static extern int WindowsPackageManagerTestHook_ReloadGroupPolicy(); + + /// + /// Calls the in-process test hook to reload the GroupPolicy singleton from the current registry state. + /// Silently ignored if the DLL is not loaded in this process (e.g., out-of-process test scenarios). + /// + private static void ReloadGroupPolicyIfAvailable() + { + try + { + WindowsPackageManagerTestHook_ReloadGroupPolicy(); + } + catch (Exception) + { + // The DLL is not loaded in this process (out-of-process scenario); nothing to do. + } + } + + /// + /// Gets the value from a "decimal" child element. + /// + /// Element containing the decimal. + /// Value in the element. + private static int GetDecimalValue(XElement element) + { + // Reads a child that looks like this: + // + return int.Parse(element.Element(XmlNames.Decimal).Attribute(XmlNames.Attributes.Value).Value); + } + + /// + /// Gets the registry key backing this policy. + /// + /// + /// This assumes that all the policies are machine-wide. + /// If this changes, we will need to parse the class="machine|user" attribute. + /// + private RegistryKey GetKey() + { + return Registry.LocalMachine.CreateSubKey(this.KeyPath); + } + + /// + /// A group policy source object as used by AdditionalSources and AllowedSources. + /// + public class GroupPolicySource + { + /// + /// Gets or sets the source name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the source arg. + /// + public string Arg { get; set; } + + /// + /// Gets or sets the source type. + /// + public string Type { get; set; } + + /// + /// Gets or sets the source data. + /// + public string Data { get; set; } + + /// + /// Gets or sets the source identifier. + /// + public string Identifier { get; set; } + + /// + /// Gets or sets certificate pinning. + /// + public GroupPolicyCertificatePinning CertificatePinning { get; set; } + + /// + /// Gets or sets the source trust levels. + /// + public string[] TrustLevel { get; set; } + + /// + /// Gets or sets a value indicating whether the source is explicit. + /// + public bool Explicit { get; set; } + } + + /// + /// Group policy certificate pinning. + /// + public class GroupPolicyCertificatePinning + { + /// + /// Gets or sets the cert pinning chains. + /// + public GroupPolicyCertificatePinningChain[] Chains { get; set; } + } + + /// + /// Group policy certificate pinning chain. + /// + public class GroupPolicyCertificatePinningChain + { + /// + /// Gets or sets the cert pinning details. + /// + public GroupPolicyCertificatePinningDetails[] Chain { get; set; } + } + + /// + /// Group policy certificate pinning details. + /// + public class GroupPolicyCertificatePinningDetails + { + /// + /// Gets or sets the validation. + /// + public string[] Validation { get; set; } + + /// + /// Gets or sets the embedded cert. + /// + public string EmbeddedCertificate { get; set; } + } + + /// + /// Names of the XML elements and attributes that make up the definition file. + /// + private static class XmlNames + { + // Root element + public static readonly XName PolicyDefinitions = XName.Get("policyDefinitions", Namespace); + + public static readonly XName Policies = XName.Get("policies", Namespace); + public static readonly XName Policy = XName.Get("policy", Namespace); + + public static readonly XName EnabledValue = XName.Get("enabledValue", Namespace); + public static readonly XName DisabledValue = XName.Get("disabledValue", Namespace); + public static readonly XName Elements = XName.Get("elements", Namespace); + + public static readonly XName Decimal = XName.Get("decimal", Namespace); + public static readonly XName List = XName.Get("list", Namespace); + + private const string Namespace = "http://schemas.microsoft.com/GroupPolicy/2006/07/PolicyDefinitions"; + + public static class Attributes + { + public const string Name = "name"; + public const string Value = "value"; + public const string Id = "id"; + public const string Key = "key"; + public const string ValueName = "valueName"; + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/HashCommand.cs b/src/AppInstallerCLIE2ETests/HashCommand.cs index 22e8f2f123..8d44add3a8 100644 --- a/src/AppInstallerCLIE2ETests/HashCommand.cs +++ b/src/AppInstallerCLIE2ETests/HashCommand.cs @@ -1,64 +1,64 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - using NUnit.Framework.Internal; - - /// - /// Test hash command. - /// - public class HashCommand : BaseCommand - { - /// - /// Test hash file. - /// - [Test] - public void HashFile() - { - var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTestMsiInstaller.msi")); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("21d90ee9b3569590c624836ef50bf39791c7184869c227eedc00585e1f39b4de")); - } - - /// - /// Test hash msix. - /// - [Test] - public void HashMSIX() - { - var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile(Constants.TestPackage) + " -m"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("08917b781939a7796746b5e2349e1f1d83b6c15599b60cd3f62816f15e565fc4")); - Assert.True(result.StdOut.Contains("223b318c4b1154a1fb72b1bc23422810faa5ce899a8e774ba2a02834b2058f00")); - } - - /// - /// Test hash invalid msix. - /// - [Test] - public void HashInvalidMSIX() - { - var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTestMsiInstaller.msi") + " -m"); - Assert.AreEqual(Constants.ErrorCode.OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY, result.ExitCode); - Assert.True(result.StdOut.Contains("21d90ee9b3569590c624836ef50bf39791c7184869c227eedc00585e1f39b4de")); - Assert.True(result.StdOut.Contains("Please verify that the input file is a valid, signed MSIX.")); - } - - /// - /// Test hash file not found. - /// - [Test] - public void HashFileNotFound() - { - var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("DoesNot.Exist")); - Assert.AreEqual(Constants.ErrorCode.ERROR_FILE_NOT_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("File does not exist")); - } - } +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + using NUnit.Framework.Internal; + + /// + /// Test hash command. + /// + public class HashCommand : BaseCommand + { + /// + /// Test hash file. + /// + [Test] + public void HashFile() + { + var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTestMsiInstaller.msi")); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("21d90ee9b3569590c624836ef50bf39791c7184869c227eedc00585e1f39b4de")); + } + + /// + /// Test hash msix. + /// + [Test] + public void HashMSIX() + { + var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile(Constants.TestPackage) + " -m"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("08917b781939a7796746b5e2349e1f1d83b6c15599b60cd3f62816f15e565fc4")); + Assert.True(result.StdOut.Contains("223b318c4b1154a1fb72b1bc23422810faa5ce899a8e774ba2a02834b2058f00")); + } + + /// + /// Test hash invalid msix. + /// + [Test] + public void HashInvalidMSIX() + { + var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("AppInstallerTestMsiInstaller.msi") + " -m"); + Assert.AreEqual(Constants.ErrorCode.OPC_E_ZIP_MISSING_END_OF_CENTRAL_DIRECTORY, result.ExitCode); + Assert.True(result.StdOut.Contains("21d90ee9b3569590c624836ef50bf39791c7184869c227eedc00585e1f39b4de")); + Assert.True(result.StdOut.Contains("Please verify that the input file is a valid, signed MSIX.")); + } + + /// + /// Test hash file not found. + /// + [Test] + public void HashFileNotFound() + { + var result = TestCommon.RunAICLICommand("hash", TestCommon.GetTestDataFile("DoesNot.Exist")); + Assert.AreEqual(Constants.ErrorCode.ERROR_FILE_NOT_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("File does not exist")); + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs index 6b3857fe39..b0a8f84801 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestCommon.cs @@ -1,1314 +1,1314 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Helpers -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics; - using System.IO; - using System.Linq; - using System.Management.Automation; - using System.Reflection; - using System.Security.Principal; - using System.Text; - using System.Threading; - using AppInstallerCLIE2ETests; - using AppInstallerCLIE2ETests.PowerShell; - using Microsoft.Management.Deployment; - using Microsoft.Win32; - using NUnit.Framework; - - /// - /// Test common. - /// - public static class TestCommon - { - /// - /// Scope. - /// - public enum Scope - { - /// - /// None. - /// - Unknown, - - /// - /// User. - /// - User, - - /// - /// Machine. - /// - Machine, - } - - /// - /// The type of location. - /// - public enum TestModuleLocation - { - /// - /// Current user. - /// - CurrentUser, - - /// - /// All users. - /// - AllUsers, - - /// - /// Winget module path. - /// - WinGetModulePath, - - /// - /// Custom. - /// - Custom, - - /// - /// Default winget configure. - /// - Default, - } - - /// - /// Gets a value indicating whether the current assembly is executing in an administrative context. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ExecutingAsAdministrator - { - get - { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new (identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - } - - /// - /// Gets a value indicating whether the test is running in the CI build. - /// - public static bool IsCIEnvironment - { - get - { - return Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") != null; - } - } - - /// - /// Run winget command. - /// - /// Command to run. - /// Parameters. - /// Optional std in. - /// Optional timeout. - /// Throw on timeout. - /// Environment variables to set. - /// The result of the command. - public static RunCommandResult RunAICLICommand( - string command, - string parameters, - string stdIn = null, - int timeOut = 60000, - bool throwOnTimeout = true, - Dictionary environmentVariables = null) - { - string correlationParameter = " --correlation " + Guid.NewGuid().ToString(); - - // Don't include correlation when the call has an option ending `--` value. - foreach (string part in parameters.Split(' ', StringSplitOptions.TrimEntries)) - { - if (part == "--") - { - correlationParameter = string.Empty; - } - } - - return RunAICLICommandViaDirectProcess(command, parameters + correlationParameter, stdIn, timeOut, throwOnTimeout, environmentVariables); - } - - /// - /// Run command. - /// - /// File name. - /// Args. - /// Time out. - /// If true, throw instead of returning false on a failure. - /// True if exit code is 0. - public static bool RunCommand(string fileName, string args = "", int timeOut = 60000, bool throwOnFailure = false) - { - RunCommandResult result = RunCommandWithResult(fileName, args, timeOut); - - if (result.ExitCode != 0) - { - TestContext.Out.WriteLine($"Command failed with: {result.ExitCode}"); - if (throwOnFailure) - { - throw new RunCommandException(fileName, args, result); - } - - return false; - } - else - { - return true; - } - } - - /// - /// Run command with result. - /// - /// File name. - /// Args. - /// Optional timeout. - /// Command result. - public static RunCommandResult RunCommandWithResult(string fileName, string args, int timeOut = 60000) - { - TestContext.Out.WriteLine($"Running command: {fileName} {args}"); - - Process p = new Process(); - p.StartInfo = new ProcessStartInfo(fileName, args); - p.StartInfo.RedirectStandardOutput = true; - p.StartInfo.RedirectStandardError = true; - p.Start(); - - RunCommandResult result = new (); - if (p.WaitForExit(timeOut)) - { - result.ExitCode = p.ExitCode; - result.StdOut = p.StandardOutput.ReadToEnd(); - result.StdErr = p.StandardError.ReadToEnd(); - - if (TestSetup.Parameters.VerboseLogging) - { - TestContext.Out.WriteLine($"Command run finished. {fileName} {args} {timeOut}. Output: {result.StdOut} Error: {result.StdErr}"); - } - } - else - { - throw new TimeoutException($"Command run timed out. {fileName} {args} {timeOut}"); - } - - return result; - } - - /// - /// Get test file path. - /// - /// Test file name. - /// Path of test file. - public static string GetTestFile(string fileName) - { - return Path.Combine(TestContext.CurrentContext.TestDirectory, fileName); - } - - /// - /// Get test data file path. - /// - /// File name. - /// Test file data path. - public static string GetTestDataFile(string fileName) - { - return GetTestFile(Path.Combine("TestData", fileName)); - } - - /// - /// Get test work directory. Creates if not exists. - /// - /// The work directory. - public static string GetTestWorkDir() - { - string workDir = Path.Combine(TestContext.CurrentContext.TestDirectory, "WorkDirectory"); - Directory.CreateDirectory(workDir); - return workDir; - } - - /// - /// Create random test directory. - /// - /// Path of new test directory. - public static string GetRandomTestDir() - { - string randDir = Path.Combine(GetTestWorkDir(), Path.GetRandomFileName()); - Directory.CreateDirectory(randDir); - return randDir; - } - - /// - /// Creates new random file name. File is not created. - /// - /// Extension of random file. - /// Path of random file. - public static string GetRandomTestFile(string extension) - { - return Path.Combine(GetTestWorkDir(), Path.GetRandomFileName() + extension); - } - - /// - /// Install msix package via PowerShell. - /// - /// Msix file. - /// True if installed. - public static bool InstallMsix(string file) - { - return RunCommand("powershell", $"Add-AppxPackage \"{file}\"", throwOnFailure: true); - } - - /// - /// Install and register msix package via appx manifest. - /// - /// Path to package. - /// Force shutdown. - /// Throw on failure. - /// True if installed correctly. - public static bool InstallMsixRegister(string packagePath, bool forceShutdown = false, bool throwOnFailure = true) - { - string manifestFile = Path.Combine(packagePath, "AppxManifest.xml"); - - var command = $"Add-AppxPackage -Register \"{manifestFile}\""; - if (forceShutdown) - { - command += " -ForceTargetApplicationShutdown"; - } - - return RunCommand("powershell", command, throwOnFailure: throwOnFailure); - } - - /// - /// Remove msix package. - /// - /// Package to remove. - /// Whether the package is provisioned. - /// True if removed correctly. - public static bool RemoveMsix(string name, bool isProvisioned = false) - { - if (isProvisioned) - { - return RunCommand("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{name}*\"}} | Remove-AppxProvisionedPackage -Online -AllUsers") && - RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage -AllUsers"); - } - else - { - return RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage"); - } - } - - /// - /// Gets the portable symlink directory. - /// - /// Scope. - /// The path of the symlinks. - public static string GetPortableSymlinkDirectory(Scope scope) - { - if (scope == Scope.User) - { - return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Links"); - } - else - { - return Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles"), "WinGet", "Links"); - } - } - - /// - /// Gets the portable package directory. - /// - /// The portable package directory. - public static string GetPortablePackagesDirectory() - { - return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); - } - - /// - /// Gets the default download directory for the download command. - /// - /// The default download directory. - public static string GetDefaultDownloadDirectory() - { - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"); - } - - /// - /// Gets the checkpoints directory based on whether the command is invoked in desktop package or not. - /// - /// The default checkpoints directory. - public static string GetCheckpointsDirectory() - { - if (TestSetup.Parameters.PackagedContext) - { - return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), Constants.CheckpointDirectoryPackaged); - } - else - { - return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), Constants.CheckpointDirectoryUnpackaged); - } - } - - /// - /// Gets the fonts directory based on scope. - /// - /// Scope. - /// The path of the fonts directory. - public static string GetFontsDirectory(Scope scope) - { - if (scope == Scope.Machine) - { - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts"); - } - else - { - return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "Windows", "Fonts"); - } - } - - /// - /// Verify font package. - /// - /// Name of the package. - /// Name of the package version. - /// Scope. - /// If package should exist. - public static void VerifyFontPackage( - string packageName, - string packageVersion, - Scope scope = Scope.User, - bool shouldExist = true) - { - RegistryKey baseKey = (scope == Scope.Machine) ? Registry.LocalMachine : Registry.CurrentUser; - - var fileList = new List(); - using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey, true)) - { - using var winGetRootKey = fontsRegistryKey.OpenSubKey("Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); - if (shouldExist) - { - Assert.IsNotNull(winGetRootKey); - } - else - { - return; - } - - using var packageNameSubkey = winGetRootKey.OpenSubKey(packageName); - if (shouldExist) - { - Assert.IsNotNull(packageNameSubkey); - } - - if (packageNameSubkey is not null) - { - using var versionSubkey = packageNameSubkey.OpenSubKey(packageVersion); - - if (shouldExist) - { - Assert.IsNotNull(versionSubkey); - } - else - { - Assert.IsNull(versionSubkey); - } - - if (versionSubkey is not null) - { - var valueNames = versionSubkey.GetValueNames(); - foreach (var valueName in valueNames) - { - fileList.Add(versionSubkey.GetValue(valueName).ToString()); - } - - Assert.AreEqual(valueNames.Length, fileList.Count); - } - } - } - - // Verify each package file we expect to exist actually exists. - foreach (var file in fileList) - { - Assert.IsTrue(File.Exists(file)); - } - } - - /// - /// Verify portable package. - /// - /// Install dir. - /// Command alias. - /// File name. - /// Product code. - /// Should exists. - /// Scope. - /// Install directory added to path instead of the symlink directory. - public static void VerifyPortablePackage( - string installDir, - string commandAlias, - string filename, - string productCode, - bool shouldExist, - Scope scope = Scope.User, - bool installDirectoryAddedToPath = false) - { - // When portables are installed, if the exe path is inside a directory it will not be aliased - // if the exe path is at the root level, it will be aliased. Therefore, if either exist, the exe exists - string exePath = Path.Combine(installDir, filename); - string exeAliasedPath = Path.Combine(installDir, commandAlias); - bool exeExists = File.Exists(exePath) || File.Exists(exeAliasedPath); - - string symlinkDirectory = GetPortableSymlinkDirectory(scope); - string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); - bool symlinkExists = File.Exists(symlinkPath); - - bool portableEntryExists; - RegistryKey baseKey = scope == Scope.User ? Registry.CurrentUser : Registry.LocalMachine; - string uninstallSubKey = Constants.UninstallSubKey; - using (RegistryKey uninstallRegistryKey = baseKey.OpenSubKey(uninstallSubKey, true)) - { - RegistryKey portableEntry = uninstallRegistryKey.OpenSubKey(productCode, true); - portableEntryExists = portableEntry != null; - } - - bool isAddedToPath; - string pathSubKey = scope == Scope.User ? Constants.PathSubKey_User : Constants.PathSubKey_Machine; - using (RegistryKey environmentRegistryKey = baseKey.OpenSubKey(pathSubKey, true)) - { - string pathName = "Path"; - var currentPathValue = (string)environmentRegistryKey.GetValue(pathName); - var portablePathValue = (installDirectoryAddedToPath ? installDir : symlinkDirectory) + ';'; - isAddedToPath = currentPathValue.Contains(portablePathValue); - } - - // Always clean up as best effort. - RunAICLICommand("uninstall", $"--product-code {productCode} --force"); - - Assert.AreEqual(shouldExist, exeExists, $"Expected portable exe path: {exePath}"); - Assert.AreEqual(shouldExist && !installDirectoryAddedToPath, symlinkExists, $"Expected portable symlink path: {symlinkPath}"); - Assert.AreEqual(shouldExist, portableEntryExists, $"Expected {productCode} subkey in path: {uninstallSubKey}"); - Assert.AreEqual(shouldExist, isAddedToPath, $"Expected path variable: {(installDirectoryAddedToPath ? installDir : symlinkDirectory)}"); - } - - /// - /// Copies log files to the path %TEMP%\E2ETestLogs. - /// - public static void PublishE2ETestLogs() - { - string tempPath = Path.GetTempPath(); - string localAppDataPath = Environment.GetEnvironmentVariable("LocalAppData"); - string testLogsPackagedSourcePath = Path.Combine(localAppDataPath, Constants.E2ETestLogsPathPackaged); - string testLogsUnpackagedSourcePath = Path.Combine(tempPath, Constants.E2ETestLogsPathUnpackaged); - string testLogsDestPath = Path.Combine(tempPath, "E2ETestLogs"); - string testLogsPackagedDestPath = Path.Combine(testLogsDestPath, "Packaged"); - string testLogsUnpackagedDestPath = Path.Combine(testLogsDestPath, "Unpackaged"); - - if (Directory.Exists(testLogsPackagedSourcePath)) - { - CopyDirectory(testLogsPackagedSourcePath, testLogsPackagedDestPath); - } - - if (Directory.Exists(testLogsUnpackagedSourcePath)) - { - CopyDirectory(testLogsUnpackagedSourcePath, testLogsUnpackagedDestPath); - } - } - - /// - /// Gets the server certificate as a hex string. - /// - /// Hex string. - public static string GetTestServerCertificateHexString() - { - if (string.IsNullOrEmpty(TestSetup.Parameters.LocalServerCertPath)) - { - throw new Exception($"{Constants.LocalServerCertPathParameter} not set."); - } - - if (!File.Exists(TestSetup.Parameters.LocalServerCertPath)) - { - throw new FileNotFoundException(TestSetup.Parameters.LocalServerCertPath); - } - - return Convert.ToHexString(File.ReadAllBytes(TestSetup.Parameters.LocalServerCertPath)); - } - - /// - /// Verify exe installer correctly. - /// - /// Install directory. - /// Optional expected content. - /// True if success. - public static bool VerifyTestExeInstalled(string installDir, string expectedContent = null) - { - bool verifyInstallSuccess = true; - - if (!File.Exists(Path.Combine(installDir, Constants.TestExeInstalledFileName))) - { - TestContext.Out.WriteLine($"TestExeInstalled.exe not found at {installDir}"); - verifyInstallSuccess = false; - } - - if (verifyInstallSuccess && !string.IsNullOrEmpty(expectedContent)) - { - string content = File.ReadAllText(Path.Combine(installDir, Constants.TestExeInstalledFileName)); - TestContext.Out.WriteLine($"TestExeInstalled.exe content: {content}"); - verifyInstallSuccess = content.Contains(expectedContent); - } - - return verifyInstallSuccess; - } - - /// - /// Verifies if the repair of the test executable was successful. - /// - /// The directory where the test executable is installed. - /// The expected content in the test executable file. This is optional. - /// Returns true if the repair was successful, false otherwise. - public static bool VerifyTestExeRepairSuccessful(string installDir, string expectedContent = null) - { - bool verifyRepairSuccess = true; - - if (!File.Exists(Path.Combine(installDir, Constants.TestExeRepairCompletedFileName))) - { - TestContext.Out.WriteLine($"{Constants.TestExeRepairCompletedFileName} not found at {installDir}"); - verifyRepairSuccess = false; - } - - if (verifyRepairSuccess && !string.IsNullOrEmpty(expectedContent)) - { - string content = File.ReadAllText(Path.Combine(installDir, Constants.TestExeRepairCompletedFileName)); - TestContext.Out.WriteLine($"TestExeRepairCompleted.txt content: {content}"); - verifyRepairSuccess = content.Contains(expectedContent); - } - - return verifyRepairSuccess; - } - - /// - /// Assert installer and manifest downloaded correctly and cleanup. - /// - /// Download directory. - /// Package name. - /// Package version. - /// Installer architecture. - /// Installer scope. - /// Installer type. - /// Installer locale. - /// Boolean value indicating whether the installer is an archive. - /// Boolean value indicating whether to remove the installer file and directory. - public static void AssertInstallerDownload( - string downloadDir, - string name, - string version, - Windows.System.ProcessorArchitecture arch, - Scope scope, - PackageInstallerType installerType, - string locale = null, - bool isArchive = false, - bool cleanup = true) - { - string expectedFileName = $"{name}_{version}"; - - if (scope != Scope.Unknown) - { - expectedFileName += $"_{scope}"; - } - - expectedFileName += $"_{arch}_{installerType}"; - - if (!string.IsNullOrEmpty(locale)) - { - expectedFileName += $"_{locale}"; - } - - string installerExtension; - if (isArchive) - { - installerExtension = ".zip"; - } - else - { - installerExtension = installerType switch - { - PackageInstallerType.Msi => ".msi", - PackageInstallerType.Msix => ".msix", - _ => ".exe" - }; - } - - string installerDownloadPath = Path.Combine(downloadDir, expectedFileName + installerExtension); - string manifestDownloadPath = Path.Combine(downloadDir, expectedFileName + ".yaml"); - - Assert.IsTrue(Directory.Exists(downloadDir), $"Download directory does not exist: {downloadDir}"); - Assert.IsTrue(File.Exists(installerDownloadPath), $"Installer file does not exist: {installerDownloadPath}"); - Assert.IsTrue(File.Exists(manifestDownloadPath), $"Manifest file does not exist: {manifestDownloadPath}"); - - if (cleanup) - { - Directory.Delete(downloadDir, true); - } - } - - /// - /// Best effort test exe cleanup. - /// - /// Install directory. - public static void BestEffortTestExeCleanup(string installDir) - { - var uninstallerPath = Path.Combine(installDir, Constants.TestExeUninstallerFileName); - if (File.Exists(uninstallerPath)) - { - RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); - } - } - - /// - /// Best effort test exe cleanup and install directory cleanup. - /// - /// Install directory. - public static void CleanupTestExeAndDirectory(string installDir) - { - // Always try clean up and ignore clean up failure - BestEffortTestExeCleanup(installDir); - - // Delete the install directory to reclaim disk space - if (Directory.Exists(installDir)) - { - Directory.Delete(installDir, true); - } - } - - /// - /// Verify exe installer correctly and then uninstall it. - /// - /// Install directory. - /// Optional expected content. - /// True if success. - public static bool VerifyTestExeInstalledAndCleanup(string installDir, string expectedContent = null) - { - bool verifyInstallSuccess = VerifyTestExeInstalled(installDir, expectedContent); - - // Always try clean up and ignore clean up failure - BestEffortTestExeCleanup(installDir); - - return verifyInstallSuccess; - } - - /// - /// Verify exe repair completed and cleanup. - /// - /// Install directory. - /// Optional expected context. - /// True if success. - public static bool VerifyTestExeRepairCompletedAndCleanup(string installDir, string expectedContent = null) - { - bool verifyRepairSuccess = VerifyTestExeRepairSuccessful(installDir, expectedContent); - CleanupTestExeAndDirectory(installDir); - - return verifyRepairSuccess; - } - - /// - /// Verify msi installed correctly. - /// - /// Installed directory. - /// True if success. - public static bool VerifyTestMsiInstalledAndCleanup(string installDir) - { - string pathToCheck = Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe); - if (!File.Exists(pathToCheck)) - { - TestContext.Out.WriteLine($"File not found: {pathToCheck}"); - return false; - } - - return RunCommand("msiexec.exe", $"/qn /x {Constants.MsiInstallerProductCode}"); - } - - /// - /// Verify msix installed correctly. - /// - /// Whether the package is provisioned. - /// True if success. - public static bool VerifyTestMsixInstalledAndCleanup(bool isProvisioned = false) - { - var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); - - if (!result.StdOut.Contains(Constants.MsixInstallerName)) - { - return false; - } - - if (isProvisioned) - { - result = RunCommandWithResult("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{Constants.MsixInstallerName}*\"}}"); - if (!result.StdOut.Contains(Constants.MsixInstallerName)) - { - return false; - } - } - - return RemoveMsix(Constants.MsixInstallerName, isProvisioned); - } - - /// - /// Verify test exe uninstalled. - /// - /// Installed directory. - /// True if success. - public static bool VerifyTestExeUninstalled(string installDir) - { - return File.Exists(Path.Combine(installDir, Constants.TestExeUninstalledFileName)); - } - - /// - /// Verify msi uninstalled. - /// - /// Install directory. - /// True if success. - public static bool VerifyTestMsiUninstalled(string installDir) - { - return !File.Exists(Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe)); - } - - /// - /// Verify msix uninstalled. - /// - /// Whether the package is provisioned. - /// True if success. - public static bool VerifyTestMsixUninstalled(bool isProvisioned = false) - { - bool isUninstalled = false; - var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); - isUninstalled = string.IsNullOrWhiteSpace(result.StdOut); - - if (isProvisioned) - { - result = RunCommandWithResult("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{Constants.MsixInstallerName}*\"}}"); - isUninstalled = isUninstalled && string.IsNullOrWhiteSpace(result.StdOut); - } - - return isUninstalled; - } - - /// - /// Modify uninstalled registry key. - /// - /// Product code. - /// Name. - /// Value. - public static void ModifyPortableARPEntryValue(string productCode, string name, string value) - { - using (RegistryKey uninstallRegistryKey = Registry.CurrentUser.OpenSubKey(Constants.UninstallSubKey, true)) - { - RegistryKey entry = uninstallRegistryKey.OpenSubKey(productCode, true); - entry.SetValue(name, value); - } - } - - /// - /// Set up test source. - /// - /// Use group policy. - public static void SetupTestSource(bool useGroupPolicyForTestSource = false) - { - // Remove the test source so that its package is also removed. - RunAICLICommand("source remove", Constants.TestSourceName); - - RunAICLICommand("source reset", "--force"); - RunAICLICommand("source remove", Constants.DefaultWingetSourceName); - RunAICLICommand("source remove", Constants.DefaultMSStoreSourceName); - - // TODO: If/when cert pinning is implemented on the packaged index source, useGroupPolicyForTestSource should be set to default true - // to enable testing it by default. Until then, leaving this here... - if (useGroupPolicyForTestSource) - { - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicyHelper.GroupPolicySource[] - { - new GroupPolicyHelper.GroupPolicySource - { - Name = Constants.TestSourceName, - Arg = Constants.TestSourceUrl, - Type = Constants.TestSourceType, - Data = Constants.TestSourceIdentifier, - Identifier = Constants.TestSourceIdentifier, - CertificatePinning = new GroupPolicyHelper.GroupPolicyCertificatePinning - { - Chains = new GroupPolicyHelper.GroupPolicyCertificatePinningChain[] - { - new GroupPolicyHelper.GroupPolicyCertificatePinningChain - { - Chain = new GroupPolicyHelper.GroupPolicyCertificatePinningDetails[] - { - new GroupPolicyHelper.GroupPolicyCertificatePinningDetails - { - Validation = new string[] { "publickey" }, - EmbeddedCertificate = GetTestServerCertificateHexString(), - }, - }, - }, - }, - }, - TrustLevel = new string[] { "None" }, - Explicit = false, - }, - }); - } - else - { - GroupPolicyHelper.EnableAdditionalSources.SetNotConfigured(); - RunAICLICommand("source add", $"{Constants.TestSourceName} {Constants.TestSourceUrl} --trust-level trusted"); - } - - Thread.Sleep(2000); - } - - /// - /// Tear down test source. - /// - public static void TearDownTestSource() - { - RunAICLICommand("source remove", Constants.TestSourceName); - RunAICLICommand("source reset", "--force"); - } - - /// - /// Ensures that a module is in the desired state. - /// - /// The module. - /// Whether the module is present or not. - /// The repository to get the module from if needed. - /// The location to install the module. - public static void EnsureModuleState(string moduleName, bool present, string repository = null, TestCommon.TestModuleLocation location = TestModuleLocation.CurrentUser) - { - string wingetModulePath = TestCommon.GetExpectedModulePath(TestModuleLocation.WinGetModulePath); - string customPath = TestCommon.GetExpectedModulePath(TestModuleLocation.Custom); - - ICollection e2eModule; - bool isPresent = false; - { - using var pwsh = new PowerShellHost(); - pwsh.AddModulePath($"{wingetModulePath};{customPath}"); - - e2eModule = pwsh.PowerShell.AddCommand("Get-Module").AddParameter("Name", moduleName).AddParameter("ListAvailable").Invoke(); - isPresent = e2eModule.Any(); - } - - TestContext.Out.WriteLine($"EnsureModuleState: {moduleName}[present:{present}] => isPresent:{isPresent}"); - - if (isPresent) - { - // If the module was saved in a different location we can't Uninstall-Module. - foreach (var module in e2eModule) - { - var moduleBase = module.Path; - while (Path.GetFileName(moduleBase) != moduleName) - { - moduleBase = Path.GetDirectoryName(moduleBase); - } - - if (!present) - { - TestContext.Out.WriteLine($"EnsureModuleState: Removing {moduleName} to match present=false"); - Directory.Delete(moduleBase, true); - } - else - { - // Must be present in the right location. - var expectedLocation = TestCommon.GetExpectedModulePath(location); - if (!moduleBase.StartsWith(expectedLocation)) - { - TestContext.Out.WriteLine($"EnsureModuleState: Removing {moduleName} as it is not in the correct location"); - Directory.Delete(moduleBase, true); - isPresent = false; - } - } - } - } - - if (!isPresent && present) - { - if (location == TestModuleLocation.CurrentUser || - location == TestModuleLocation.AllUsers) - { - using var pwsh = new PowerShellHost(); - pwsh.AddModulePath($"{wingetModulePath};{customPath}"); - pwsh.PowerShell.AddCommand("Install-Module").AddParameter("Name", moduleName).AddParameter("Force"); - - if (!string.IsNullOrEmpty(repository)) - { - pwsh.PowerShell.AddParameter("Repository", repository); - } - - if (location == TestModuleLocation.CurrentUser) - { - pwsh.PowerShell.AddParameter("Scope", "CurrentUser"); - } - else if (location == TestModuleLocation.AllUsers) - { - pwsh.PowerShell.AddParameter("Scope", "AllUsers"); - } - - TestContext.Out.WriteLine($"EnsureModuleState: Installing module {moduleName} to {location}"); - _ = pwsh.PowerShell.Invoke(); - } - else - { - string path = customPath; - if (location == TestModuleLocation.WinGetModulePath || - location == TestModuleLocation.Default) - { - path = wingetModulePath; - } - - using var pwsh = new PowerShellHost(); - pwsh.AddModulePath($"{wingetModulePath};{customPath}"); - pwsh.PowerShell.AddCommand("Save-Module").AddParameter("Name", moduleName).AddParameter("Path", path).AddParameter("Force"); - - if (!string.IsNullOrEmpty(repository)) - { - pwsh.PowerShell.AddParameter("Repository", repository); - } - - TestContext.Out.WriteLine($"EnsureModuleState: Saving module {moduleName} to {path}"); - _ = pwsh.PowerShell.Invoke(); - } - } - } - - /// - /// Creates an ARP entry from the given values. - /// - /// Product code of the entry. - /// The properties to set in the entry. - /// Scope of the entry. - public static void CreateARPEntry( - string productCode, - object properties, - Scope scope = Scope.User) - { - RegistryKey baseKey = scope == Scope.User ? Registry.CurrentUser : Registry.LocalMachine; - using (RegistryKey uninstallRegistryKey = baseKey.OpenSubKey(Constants.UninstallSubKey, true)) - { - RegistryKey entry = uninstallRegistryKey.CreateSubKey(productCode, true); - - foreach (PropertyInfo property in properties.GetType().GetProperties()) - { - entry.SetValue(property.Name, property.GetValue(properties)); - } - } - } - - /// - /// Removes an ARP entry. - /// - /// Product code of the entry. - /// Scope of the entry. - public static void RemoveARPEntry( - string productCode, - Scope scope = Scope.User) - { - RegistryKey baseKey = scope == Scope.User ? Registry.CurrentUser : Registry.LocalMachine; - using (RegistryKey uninstallRegistryKey = baseKey.OpenSubKey(Constants.UninstallSubKey, true)) - { - uninstallRegistryKey.DeleteSubKey(productCode); - } - } - - /// - /// Copies the contents of a given directory from a source path to a destination path. - /// - /// Source directory name. - /// Destination directory name. - public static void CopyDirectory(string sourceDirName, string destDirName) - { - DirectoryInfo dir = new DirectoryInfo(sourceDirName); - DirectoryInfo[] dirs = dir.GetDirectories(); - - if (!Directory.Exists(destDirName)) - { - Directory.CreateDirectory(destDirName); - } - - FileInfo[] files = dir.GetFiles(); - foreach (FileInfo file in files) - { - string temppath = Path.Combine(destDirName, file.Name); - file.CopyTo(temppath, false); - } - - foreach (DirectoryInfo subdir in dirs) - { - string temppath = Path.Combine(destDirName, subdir.Name); - CopyDirectory(subdir.FullName, temppath); - } - } - - /// - /// Gets the expected module path. - /// - /// Location. - /// The expected path of the module. - public static string GetExpectedModulePath(TestModuleLocation location) - { - switch (location) - { - case TestModuleLocation.CurrentUser: - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"PowerShell\Modules"); - case TestModuleLocation.AllUsers: - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"PowerShell\Modules"); - case TestModuleLocation.WinGetModulePath: - case TestModuleLocation.Default: - return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WinGet\Configuration\Modules"); - case TestModuleLocation.Custom: - return Path.Combine(Path.GetTempPath(), "E2ECustomModules"); - default: - throw new ArgumentException(location.ToString()); - } - } - - /// - /// Gets the instance identifier of the first configuration history item with name in its output line. - /// - /// The string to search for. - /// The instance identifier of a configuration that matched the search, or an empty string if none did. - public static string GetConfigurationInstanceIdentifierFor(string name) - { - var result = TestCommon.RunAICLICommand("configure list", string.Empty); - Assert.AreEqual(0, result.ExitCode); - - string[] lines = result.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries); - - foreach (string line in lines) - { - if (line.Contains(name)) - { - // Find the first GUID in the output - int left = line.IndexOf('{'); - int right = line.IndexOfAny(new char[] { '}', '…' }); - Assert.AreNotEqual(-1, left); - Assert.AreNotEqual(-1, right); - Assert.LessOrEqual(right - left, 38); - - return line.Substring(left, right - left); - } - } - - return string.Empty; - } - - /// - /// Copy the installer file to the ARP InstallSource directory. - /// - /// Test installer to be copied. - /// Installer Product. - /// is WoW6432Node to use. - /// Returns the installer source directory if the file operation is successful, otherwise returns an empty string. - public static string CopyInstallerFileToARPInstallSourceDirectory(string installerFilePath, string productCode, bool useWoW6432Node = false) - { - if (string.IsNullOrEmpty(installerFilePath)) - { - new ArgumentNullException(nameof(installerFilePath)); - } - - if (!File.Exists(installerFilePath)) - { - new FileNotFoundException(installerFilePath); - } - - string outputDirectory = string.Empty; - - // Define the registry paths for both x64 and x86 - string registryPath = useWoW6432Node - ? $@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}" - : $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}"; - - // Open the registry key where the uninstall information is stored - using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath)) - { - if (key != null) - { - // Read the InstallSource value - string arpInstallSourceDirectory = key.GetValue("InstallSource") as string; - - if (!string.IsNullOrEmpty(arpInstallSourceDirectory)) - { - // Copy the MSI installer to the InstallSource directory - string installerFileName = Path.GetFileName(installerFilePath); - string installerDestinationPath = Path.Combine(arpInstallSourceDirectory, installerFileName); - - if (!Directory.Exists(arpInstallSourceDirectory)) - { - Directory.CreateDirectory(arpInstallSourceDirectory); - } - - File.Copy(installerFilePath, installerDestinationPath, true); - - outputDirectory = arpInstallSourceDirectory; - } - } - } - - return outputDirectory; - } - - /// - /// Run winget command via direct process. - /// - /// The executable to run. - /// Command to run. - /// Parameters. - /// Optional std in. - /// Optional timeout. - /// Throw on timeout. - /// Environment variables to set. - /// The result of the command. - public static RunCommandResult RunProcess( - string executablePath, - string command, - string parameters, - string stdIn, - int timeOut, - bool throwOnTimeout, - Dictionary environmentVariables) - { - string inputMsg = - "Exe path: " + executablePath + - " Command: " + command + - " Parameters: " + parameters + - (string.IsNullOrEmpty(stdIn) ? string.Empty : " StdIn: " + stdIn) + - " Timeout: " + timeOut + - (environmentVariables == null ? string.Empty : - " Env: " + string.Join(", ", environmentVariables.Select(item => $"{item.Key}={item.Value}"))); - - TestContext.Out.WriteLine($"Starting command run. {inputMsg}"); - - RunCommandResult result = new (); - Process p = new Process(); - p.StartInfo = new ProcessStartInfo(executablePath, command + ' ' + parameters); - p.StartInfo.UseShellExecute = false; - - p.StartInfo.StandardOutputEncoding = Encoding.UTF8; - p.StartInfo.RedirectStandardOutput = true; - StringBuilder outputData = new (); - p.OutputDataReceived += (sender, args) => - { - if (args.Data != null) - { - outputData.AppendLine(args.Data); - } - }; - - p.StartInfo.StandardErrorEncoding = Encoding.UTF8; - p.StartInfo.RedirectStandardError = true; - StringBuilder errorData = new (); - p.ErrorDataReceived += (sender, args) => - { - if (args.Data != null) - { - errorData.AppendLine(args.Data); - } - }; - - if (!string.IsNullOrEmpty(stdIn)) - { - p.StartInfo.RedirectStandardInput = true; - } - - if (environmentVariables != null) - { - foreach (var item in environmentVariables) - { - p.StartInfo.EnvironmentVariables[item.Key] = item.Value; - } - } - - p.Start(); - p.BeginOutputReadLine(); - p.BeginErrorReadLine(); - - if (!string.IsNullOrEmpty(stdIn)) - { - p.StandardInput.Write(stdIn); - p.StandardInput.Close(); - } - - if (p.WaitForExit(timeOut)) - { - // According to documentation, this extra call will ensure that the redirected streams - // have finished reading all of the data. - p.WaitForExit(); - - result.ExitCode = p.ExitCode; - result.StdOut = outputData.ToString(); - result.StdErr = errorData.ToString(); - - TestContext.Out.WriteLine("Command run completed with exit code: " + result.ExitCode); - - if (!string.IsNullOrEmpty(result.StdErr)) - { - TestContext.Error.WriteLine("Command run error. Error: " + result.StdErr); - } - - if (TestSetup.Parameters.VerboseLogging) - { - TestContext.Out.WriteLine("Command run output. Output:\n" + result.StdOut ?? ""); - } - } - else if (throwOnTimeout) - { - throw new TimeoutException($"Direct command run timed out: {command} {parameters}"); - } - - return result; - } - - /// - /// Run winget command via direct process. - /// - /// Command to run. - /// Parameters. - /// Optional std in. - /// Optional timeout. - /// Throw on timeout. - /// Environment variables to set. - /// The result of the command. - private static RunCommandResult RunAICLICommandViaDirectProcess( - string command, - string parameters, - string stdIn, - int timeOut, - bool throwOnTimeout, - Dictionary environmentVariables) - { - return RunProcess(TestSetup.Parameters.AICLIPath, command, parameters, stdIn, timeOut, throwOnTimeout, environmentVariables); - } - - /// - /// Run command result. - /// - public struct RunCommandResult - { - /// - /// Exit code. - /// - public int ExitCode; - - /// - /// StdOut. - /// - public string StdOut; - - /// - /// StdErr. - /// - public string StdErr; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Helpers +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Reflection; + using System.Security.Principal; + using System.Text; + using System.Threading; + using AppInstallerCLIE2ETests; + using AppInstallerCLIE2ETests.PowerShell; + using Microsoft.Management.Deployment; + using Microsoft.Win32; + using NUnit.Framework; + + /// + /// Test common. + /// + public static class TestCommon + { + /// + /// Scope. + /// + public enum Scope + { + /// + /// None. + /// + Unknown, + + /// + /// User. + /// + User, + + /// + /// Machine. + /// + Machine, + } + + /// + /// The type of location. + /// + public enum TestModuleLocation + { + /// + /// Current user. + /// + CurrentUser, + + /// + /// All users. + /// + AllUsers, + + /// + /// Winget module path. + /// + WinGetModulePath, + + /// + /// Custom. + /// + Custom, + + /// + /// Default winget configure. + /// + Default, + } + + /// + /// Gets a value indicating whether the current assembly is executing in an administrative context. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ExecutingAsAdministrator + { + get + { + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new (identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + + /// + /// Gets a value indicating whether the test is running in the CI build. + /// + public static bool IsCIEnvironment + { + get + { + return Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") != null; + } + } + + /// + /// Run winget command. + /// + /// Command to run. + /// Parameters. + /// Optional std in. + /// Optional timeout. + /// Throw on timeout. + /// Environment variables to set. + /// The result of the command. + public static RunCommandResult RunAICLICommand( + string command, + string parameters, + string stdIn = null, + int timeOut = 60000, + bool throwOnTimeout = true, + Dictionary environmentVariables = null) + { + string correlationParameter = " --correlation " + Guid.NewGuid().ToString(); + + // Don't include correlation when the call has an option ending `--` value. + foreach (string part in parameters.Split(' ', StringSplitOptions.TrimEntries)) + { + if (part == "--") + { + correlationParameter = string.Empty; + } + } + + return RunAICLICommandViaDirectProcess(command, parameters + correlationParameter, stdIn, timeOut, throwOnTimeout, environmentVariables); + } + + /// + /// Run command. + /// + /// File name. + /// Args. + /// Time out. + /// If true, throw instead of returning false on a failure. + /// True if exit code is 0. + public static bool RunCommand(string fileName, string args = "", int timeOut = 60000, bool throwOnFailure = false) + { + RunCommandResult result = RunCommandWithResult(fileName, args, timeOut); + + if (result.ExitCode != 0) + { + TestContext.Out.WriteLine($"Command failed with: {result.ExitCode}"); + if (throwOnFailure) + { + throw new RunCommandException(fileName, args, result); + } + + return false; + } + else + { + return true; + } + } + + /// + /// Run command with result. + /// + /// File name. + /// Args. + /// Optional timeout. + /// Command result. + public static RunCommandResult RunCommandWithResult(string fileName, string args, int timeOut = 60000) + { + TestContext.Out.WriteLine($"Running command: {fileName} {args}"); + + Process p = new Process(); + p.StartInfo = new ProcessStartInfo(fileName, args); + p.StartInfo.RedirectStandardOutput = true; + p.StartInfo.RedirectStandardError = true; + p.Start(); + + RunCommandResult result = new (); + if (p.WaitForExit(timeOut)) + { + result.ExitCode = p.ExitCode; + result.StdOut = p.StandardOutput.ReadToEnd(); + result.StdErr = p.StandardError.ReadToEnd(); + + if (TestSetup.Parameters.VerboseLogging) + { + TestContext.Out.WriteLine($"Command run finished. {fileName} {args} {timeOut}. Output: {result.StdOut} Error: {result.StdErr}"); + } + } + else + { + throw new TimeoutException($"Command run timed out. {fileName} {args} {timeOut}"); + } + + return result; + } + + /// + /// Get test file path. + /// + /// Test file name. + /// Path of test file. + public static string GetTestFile(string fileName) + { + return Path.Combine(TestContext.CurrentContext.TestDirectory, fileName); + } + + /// + /// Get test data file path. + /// + /// File name. + /// Test file data path. + public static string GetTestDataFile(string fileName) + { + return GetTestFile(Path.Combine("TestData", fileName)); + } + + /// + /// Get test work directory. Creates if not exists. + /// + /// The work directory. + public static string GetTestWorkDir() + { + string workDir = Path.Combine(TestContext.CurrentContext.TestDirectory, "WorkDirectory"); + Directory.CreateDirectory(workDir); + return workDir; + } + + /// + /// Create random test directory. + /// + /// Path of new test directory. + public static string GetRandomTestDir() + { + string randDir = Path.Combine(GetTestWorkDir(), Path.GetRandomFileName()); + Directory.CreateDirectory(randDir); + return randDir; + } + + /// + /// Creates new random file name. File is not created. + /// + /// Extension of random file. + /// Path of random file. + public static string GetRandomTestFile(string extension) + { + return Path.Combine(GetTestWorkDir(), Path.GetRandomFileName() + extension); + } + + /// + /// Install msix package via PowerShell. + /// + /// Msix file. + /// True if installed. + public static bool InstallMsix(string file) + { + return RunCommand("powershell", $"Add-AppxPackage \"{file}\"", throwOnFailure: true); + } + + /// + /// Install and register msix package via appx manifest. + /// + /// Path to package. + /// Force shutdown. + /// Throw on failure. + /// True if installed correctly. + public static bool InstallMsixRegister(string packagePath, bool forceShutdown = false, bool throwOnFailure = true) + { + string manifestFile = Path.Combine(packagePath, "AppxManifest.xml"); + + var command = $"Add-AppxPackage -Register \"{manifestFile}\""; + if (forceShutdown) + { + command += " -ForceTargetApplicationShutdown"; + } + + return RunCommand("powershell", command, throwOnFailure: throwOnFailure); + } + + /// + /// Remove msix package. + /// + /// Package to remove. + /// Whether the package is provisioned. + /// True if removed correctly. + public static bool RemoveMsix(string name, bool isProvisioned = false) + { + if (isProvisioned) + { + return RunCommand("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{name}*\"}} | Remove-AppxProvisionedPackage -Online -AllUsers") && + RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage -AllUsers"); + } + else + { + return RunCommand("powershell", $"Get-AppxPackage \"{name}\" | Remove-AppxPackage"); + } + } + + /// + /// Gets the portable symlink directory. + /// + /// Scope. + /// The path of the symlinks. + public static string GetPortableSymlinkDirectory(Scope scope) + { + if (scope == Scope.User) + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Links"); + } + else + { + return Path.Combine(Environment.GetEnvironmentVariable("ProgramFiles"), "WinGet", "Links"); + } + } + + /// + /// Gets the portable package directory. + /// + /// The portable package directory. + public static string GetPortablePackagesDirectory() + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); + } + + /// + /// Gets the default download directory for the download command. + /// + /// The default download directory. + public static string GetDefaultDownloadDirectory() + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads"); + } + + /// + /// Gets the checkpoints directory based on whether the command is invoked in desktop package or not. + /// + /// The default checkpoints directory. + public static string GetCheckpointsDirectory() + { + if (TestSetup.Parameters.PackagedContext) + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), Constants.CheckpointDirectoryPackaged); + } + else + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), Constants.CheckpointDirectoryUnpackaged); + } + } + + /// + /// Gets the fonts directory based on scope. + /// + /// Scope. + /// The path of the fonts directory. + public static string GetFontsDirectory(Scope scope) + { + if (scope == Scope.Machine) + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "Fonts"); + } + else + { + return Path.Combine(Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "Windows", "Fonts"); + } + } + + /// + /// Verify font package. + /// + /// Name of the package. + /// Name of the package version. + /// Scope. + /// If package should exist. + public static void VerifyFontPackage( + string packageName, + string packageVersion, + Scope scope = Scope.User, + bool shouldExist = true) + { + RegistryKey baseKey = (scope == Scope.Machine) ? Registry.LocalMachine : Registry.CurrentUser; + + var fileList = new List(); + using (RegistryKey fontsRegistryKey = baseKey.OpenSubKey(Constants.FontsSubKey, true)) + { + using var winGetRootKey = fontsRegistryKey.OpenSubKey("Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + if (shouldExist) + { + Assert.IsNotNull(winGetRootKey); + } + else + { + return; + } + + using var packageNameSubkey = winGetRootKey.OpenSubKey(packageName); + if (shouldExist) + { + Assert.IsNotNull(packageNameSubkey); + } + + if (packageNameSubkey is not null) + { + using var versionSubkey = packageNameSubkey.OpenSubKey(packageVersion); + + if (shouldExist) + { + Assert.IsNotNull(versionSubkey); + } + else + { + Assert.IsNull(versionSubkey); + } + + if (versionSubkey is not null) + { + var valueNames = versionSubkey.GetValueNames(); + foreach (var valueName in valueNames) + { + fileList.Add(versionSubkey.GetValue(valueName).ToString()); + } + + Assert.AreEqual(valueNames.Length, fileList.Count); + } + } + } + + // Verify each package file we expect to exist actually exists. + foreach (var file in fileList) + { + Assert.IsTrue(File.Exists(file)); + } + } + + /// + /// Verify portable package. + /// + /// Install dir. + /// Command alias. + /// File name. + /// Product code. + /// Should exists. + /// Scope. + /// Install directory added to path instead of the symlink directory. + public static void VerifyPortablePackage( + string installDir, + string commandAlias, + string filename, + string productCode, + bool shouldExist, + Scope scope = Scope.User, + bool installDirectoryAddedToPath = false) + { + // When portables are installed, if the exe path is inside a directory it will not be aliased + // if the exe path is at the root level, it will be aliased. Therefore, if either exist, the exe exists + string exePath = Path.Combine(installDir, filename); + string exeAliasedPath = Path.Combine(installDir, commandAlias); + bool exeExists = File.Exists(exePath) || File.Exists(exeAliasedPath); + + string symlinkDirectory = GetPortableSymlinkDirectory(scope); + string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); + bool symlinkExists = File.Exists(symlinkPath); + + bool portableEntryExists; + RegistryKey baseKey = scope == Scope.User ? Registry.CurrentUser : Registry.LocalMachine; + string uninstallSubKey = Constants.UninstallSubKey; + using (RegistryKey uninstallRegistryKey = baseKey.OpenSubKey(uninstallSubKey, true)) + { + RegistryKey portableEntry = uninstallRegistryKey.OpenSubKey(productCode, true); + portableEntryExists = portableEntry != null; + } + + bool isAddedToPath; + string pathSubKey = scope == Scope.User ? Constants.PathSubKey_User : Constants.PathSubKey_Machine; + using (RegistryKey environmentRegistryKey = baseKey.OpenSubKey(pathSubKey, true)) + { + string pathName = "Path"; + var currentPathValue = (string)environmentRegistryKey.GetValue(pathName); + var portablePathValue = (installDirectoryAddedToPath ? installDir : symlinkDirectory) + ';'; + isAddedToPath = currentPathValue.Contains(portablePathValue); + } + + // Always clean up as best effort. + RunAICLICommand("uninstall", $"--product-code {productCode} --force"); + + Assert.AreEqual(shouldExist, exeExists, $"Expected portable exe path: {exePath}"); + Assert.AreEqual(shouldExist && !installDirectoryAddedToPath, symlinkExists, $"Expected portable symlink path: {symlinkPath}"); + Assert.AreEqual(shouldExist, portableEntryExists, $"Expected {productCode} subkey in path: {uninstallSubKey}"); + Assert.AreEqual(shouldExist, isAddedToPath, $"Expected path variable: {(installDirectoryAddedToPath ? installDir : symlinkDirectory)}"); + } + + /// + /// Copies log files to the path %TEMP%\E2ETestLogs. + /// + public static void PublishE2ETestLogs() + { + string tempPath = Path.GetTempPath(); + string localAppDataPath = Environment.GetEnvironmentVariable("LocalAppData"); + string testLogsPackagedSourcePath = Path.Combine(localAppDataPath, Constants.E2ETestLogsPathPackaged); + string testLogsUnpackagedSourcePath = Path.Combine(tempPath, Constants.E2ETestLogsPathUnpackaged); + string testLogsDestPath = Path.Combine(tempPath, "E2ETestLogs"); + string testLogsPackagedDestPath = Path.Combine(testLogsDestPath, "Packaged"); + string testLogsUnpackagedDestPath = Path.Combine(testLogsDestPath, "Unpackaged"); + + if (Directory.Exists(testLogsPackagedSourcePath)) + { + CopyDirectory(testLogsPackagedSourcePath, testLogsPackagedDestPath); + } + + if (Directory.Exists(testLogsUnpackagedSourcePath)) + { + CopyDirectory(testLogsUnpackagedSourcePath, testLogsUnpackagedDestPath); + } + } + + /// + /// Gets the server certificate as a hex string. + /// + /// Hex string. + public static string GetTestServerCertificateHexString() + { + if (string.IsNullOrEmpty(TestSetup.Parameters.LocalServerCertPath)) + { + throw new Exception($"{Constants.LocalServerCertPathParameter} not set."); + } + + if (!File.Exists(TestSetup.Parameters.LocalServerCertPath)) + { + throw new FileNotFoundException(TestSetup.Parameters.LocalServerCertPath); + } + + return Convert.ToHexString(File.ReadAllBytes(TestSetup.Parameters.LocalServerCertPath)); + } + + /// + /// Verify exe installer correctly. + /// + /// Install directory. + /// Optional expected content. + /// True if success. + public static bool VerifyTestExeInstalled(string installDir, string expectedContent = null) + { + bool verifyInstallSuccess = true; + + if (!File.Exists(Path.Combine(installDir, Constants.TestExeInstalledFileName))) + { + TestContext.Out.WriteLine($"TestExeInstalled.exe not found at {installDir}"); + verifyInstallSuccess = false; + } + + if (verifyInstallSuccess && !string.IsNullOrEmpty(expectedContent)) + { + string content = File.ReadAllText(Path.Combine(installDir, Constants.TestExeInstalledFileName)); + TestContext.Out.WriteLine($"TestExeInstalled.exe content: {content}"); + verifyInstallSuccess = content.Contains(expectedContent); + } + + return verifyInstallSuccess; + } + + /// + /// Verifies if the repair of the test executable was successful. + /// + /// The directory where the test executable is installed. + /// The expected content in the test executable file. This is optional. + /// Returns true if the repair was successful, false otherwise. + public static bool VerifyTestExeRepairSuccessful(string installDir, string expectedContent = null) + { + bool verifyRepairSuccess = true; + + if (!File.Exists(Path.Combine(installDir, Constants.TestExeRepairCompletedFileName))) + { + TestContext.Out.WriteLine($"{Constants.TestExeRepairCompletedFileName} not found at {installDir}"); + verifyRepairSuccess = false; + } + + if (verifyRepairSuccess && !string.IsNullOrEmpty(expectedContent)) + { + string content = File.ReadAllText(Path.Combine(installDir, Constants.TestExeRepairCompletedFileName)); + TestContext.Out.WriteLine($"TestExeRepairCompleted.txt content: {content}"); + verifyRepairSuccess = content.Contains(expectedContent); + } + + return verifyRepairSuccess; + } + + /// + /// Assert installer and manifest downloaded correctly and cleanup. + /// + /// Download directory. + /// Package name. + /// Package version. + /// Installer architecture. + /// Installer scope. + /// Installer type. + /// Installer locale. + /// Boolean value indicating whether the installer is an archive. + /// Boolean value indicating whether to remove the installer file and directory. + public static void AssertInstallerDownload( + string downloadDir, + string name, + string version, + Windows.System.ProcessorArchitecture arch, + Scope scope, + PackageInstallerType installerType, + string locale = null, + bool isArchive = false, + bool cleanup = true) + { + string expectedFileName = $"{name}_{version}"; + + if (scope != Scope.Unknown) + { + expectedFileName += $"_{scope}"; + } + + expectedFileName += $"_{arch}_{installerType}"; + + if (!string.IsNullOrEmpty(locale)) + { + expectedFileName += $"_{locale}"; + } + + string installerExtension; + if (isArchive) + { + installerExtension = ".zip"; + } + else + { + installerExtension = installerType switch + { + PackageInstallerType.Msi => ".msi", + PackageInstallerType.Msix => ".msix", + _ => ".exe" + }; + } + + string installerDownloadPath = Path.Combine(downloadDir, expectedFileName + installerExtension); + string manifestDownloadPath = Path.Combine(downloadDir, expectedFileName + ".yaml"); + + Assert.IsTrue(Directory.Exists(downloadDir), $"Download directory does not exist: {downloadDir}"); + Assert.IsTrue(File.Exists(installerDownloadPath), $"Installer file does not exist: {installerDownloadPath}"); + Assert.IsTrue(File.Exists(manifestDownloadPath), $"Manifest file does not exist: {manifestDownloadPath}"); + + if (cleanup) + { + Directory.Delete(downloadDir, true); + } + } + + /// + /// Best effort test exe cleanup. + /// + /// Install directory. + public static void BestEffortTestExeCleanup(string installDir) + { + var uninstallerPath = Path.Combine(installDir, Constants.TestExeUninstallerFileName); + if (File.Exists(uninstallerPath)) + { + RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + } + + /// + /// Best effort test exe cleanup and install directory cleanup. + /// + /// Install directory. + public static void CleanupTestExeAndDirectory(string installDir) + { + // Always try clean up and ignore clean up failure + BestEffortTestExeCleanup(installDir); + + // Delete the install directory to reclaim disk space + if (Directory.Exists(installDir)) + { + Directory.Delete(installDir, true); + } + } + + /// + /// Verify exe installer correctly and then uninstall it. + /// + /// Install directory. + /// Optional expected content. + /// True if success. + public static bool VerifyTestExeInstalledAndCleanup(string installDir, string expectedContent = null) + { + bool verifyInstallSuccess = VerifyTestExeInstalled(installDir, expectedContent); + + // Always try clean up and ignore clean up failure + BestEffortTestExeCleanup(installDir); + + return verifyInstallSuccess; + } + + /// + /// Verify exe repair completed and cleanup. + /// + /// Install directory. + /// Optional expected context. + /// True if success. + public static bool VerifyTestExeRepairCompletedAndCleanup(string installDir, string expectedContent = null) + { + bool verifyRepairSuccess = VerifyTestExeRepairSuccessful(installDir, expectedContent); + CleanupTestExeAndDirectory(installDir); + + return verifyRepairSuccess; + } + + /// + /// Verify msi installed correctly. + /// + /// Installed directory. + /// True if success. + public static bool VerifyTestMsiInstalledAndCleanup(string installDir) + { + string pathToCheck = Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe); + if (!File.Exists(pathToCheck)) + { + TestContext.Out.WriteLine($"File not found: {pathToCheck}"); + return false; + } + + return RunCommand("msiexec.exe", $"/qn /x {Constants.MsiInstallerProductCode}"); + } + + /// + /// Verify msix installed correctly. + /// + /// Whether the package is provisioned. + /// True if success. + public static bool VerifyTestMsixInstalledAndCleanup(bool isProvisioned = false) + { + var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); + + if (!result.StdOut.Contains(Constants.MsixInstallerName)) + { + return false; + } + + if (isProvisioned) + { + result = RunCommandWithResult("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{Constants.MsixInstallerName}*\"}}"); + if (!result.StdOut.Contains(Constants.MsixInstallerName)) + { + return false; + } + } + + return RemoveMsix(Constants.MsixInstallerName, isProvisioned); + } + + /// + /// Verify test exe uninstalled. + /// + /// Installed directory. + /// True if success. + public static bool VerifyTestExeUninstalled(string installDir) + { + return File.Exists(Path.Combine(installDir, Constants.TestExeUninstalledFileName)); + } + + /// + /// Verify msi uninstalled. + /// + /// Install directory. + /// True if success. + public static bool VerifyTestMsiUninstalled(string installDir) + { + return !File.Exists(Path.Combine(installDir, Constants.AppInstallerTestExeInstallerExe)); + } + + /// + /// Verify msix uninstalled. + /// + /// Whether the package is provisioned. + /// True if success. + public static bool VerifyTestMsixUninstalled(bool isProvisioned = false) + { + bool isUninstalled = false; + var result = RunCommandWithResult("powershell", $"Get-AppxPackage {Constants.MsixInstallerName}"); + isUninstalled = string.IsNullOrWhiteSpace(result.StdOut); + + if (isProvisioned) + { + result = RunCommandWithResult("powershell", $"Get-AppxProvisionedPackage -Online | Where-Object {{$_.PackageName -like \"*{Constants.MsixInstallerName}*\"}}"); + isUninstalled = isUninstalled && string.IsNullOrWhiteSpace(result.StdOut); + } + + return isUninstalled; + } + + /// + /// Modify uninstalled registry key. + /// + /// Product code. + /// Name. + /// Value. + public static void ModifyPortableARPEntryValue(string productCode, string name, string value) + { + using (RegistryKey uninstallRegistryKey = Registry.CurrentUser.OpenSubKey(Constants.UninstallSubKey, true)) + { + RegistryKey entry = uninstallRegistryKey.OpenSubKey(productCode, true); + entry.SetValue(name, value); + } + } + + /// + /// Set up test source. + /// + /// Use group policy. + public static void SetupTestSource(bool useGroupPolicyForTestSource = false) + { + // Remove the test source so that its package is also removed. + RunAICLICommand("source remove", Constants.TestSourceName); + + RunAICLICommand("source reset", "--force"); + RunAICLICommand("source remove", Constants.DefaultWingetSourceName); + RunAICLICommand("source remove", Constants.DefaultMSStoreSourceName); + + // TODO: If/when cert pinning is implemented on the packaged index source, useGroupPolicyForTestSource should be set to default true + // to enable testing it by default. Until then, leaving this here... + if (useGroupPolicyForTestSource) + { + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicyHelper.GroupPolicySource[] + { + new GroupPolicyHelper.GroupPolicySource + { + Name = Constants.TestSourceName, + Arg = Constants.TestSourceUrl, + Type = Constants.TestSourceType, + Data = Constants.TestSourceIdentifier, + Identifier = Constants.TestSourceIdentifier, + CertificatePinning = new GroupPolicyHelper.GroupPolicyCertificatePinning + { + Chains = new GroupPolicyHelper.GroupPolicyCertificatePinningChain[] + { + new GroupPolicyHelper.GroupPolicyCertificatePinningChain + { + Chain = new GroupPolicyHelper.GroupPolicyCertificatePinningDetails[] + { + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails + { + Validation = new string[] { "publickey" }, + EmbeddedCertificate = GetTestServerCertificateHexString(), + }, + }, + }, + }, + }, + TrustLevel = new string[] { "None" }, + Explicit = false, + }, + }); + } + else + { + GroupPolicyHelper.EnableAdditionalSources.SetNotConfigured(); + RunAICLICommand("source add", $"{Constants.TestSourceName} {Constants.TestSourceUrl} --trust-level trusted"); + } + + Thread.Sleep(2000); + } + + /// + /// Tear down test source. + /// + public static void TearDownTestSource() + { + RunAICLICommand("source remove", Constants.TestSourceName); + RunAICLICommand("source reset", "--force"); + } + + /// + /// Ensures that a module is in the desired state. + /// + /// The module. + /// Whether the module is present or not. + /// The repository to get the module from if needed. + /// The location to install the module. + public static void EnsureModuleState(string moduleName, bool present, string repository = null, TestCommon.TestModuleLocation location = TestModuleLocation.CurrentUser) + { + string wingetModulePath = TestCommon.GetExpectedModulePath(TestModuleLocation.WinGetModulePath); + string customPath = TestCommon.GetExpectedModulePath(TestModuleLocation.Custom); + + ICollection e2eModule; + bool isPresent = false; + { + using var pwsh = new PowerShellHost(); + pwsh.AddModulePath($"{wingetModulePath};{customPath}"); + + e2eModule = pwsh.PowerShell.AddCommand("Get-Module").AddParameter("Name", moduleName).AddParameter("ListAvailable").Invoke(); + isPresent = e2eModule.Any(); + } + + TestContext.Out.WriteLine($"EnsureModuleState: {moduleName}[present:{present}] => isPresent:{isPresent}"); + + if (isPresent) + { + // If the module was saved in a different location we can't Uninstall-Module. + foreach (var module in e2eModule) + { + var moduleBase = module.Path; + while (Path.GetFileName(moduleBase) != moduleName) + { + moduleBase = Path.GetDirectoryName(moduleBase); + } + + if (!present) + { + TestContext.Out.WriteLine($"EnsureModuleState: Removing {moduleName} to match present=false"); + Directory.Delete(moduleBase, true); + } + else + { + // Must be present in the right location. + var expectedLocation = TestCommon.GetExpectedModulePath(location); + if (!moduleBase.StartsWith(expectedLocation)) + { + TestContext.Out.WriteLine($"EnsureModuleState: Removing {moduleName} as it is not in the correct location"); + Directory.Delete(moduleBase, true); + isPresent = false; + } + } + } + } + + if (!isPresent && present) + { + if (location == TestModuleLocation.CurrentUser || + location == TestModuleLocation.AllUsers) + { + using var pwsh = new PowerShellHost(); + pwsh.AddModulePath($"{wingetModulePath};{customPath}"); + pwsh.PowerShell.AddCommand("Install-Module").AddParameter("Name", moduleName).AddParameter("Force"); + + if (!string.IsNullOrEmpty(repository)) + { + pwsh.PowerShell.AddParameter("Repository", repository); + } + + if (location == TestModuleLocation.CurrentUser) + { + pwsh.PowerShell.AddParameter("Scope", "CurrentUser"); + } + else if (location == TestModuleLocation.AllUsers) + { + pwsh.PowerShell.AddParameter("Scope", "AllUsers"); + } + + TestContext.Out.WriteLine($"EnsureModuleState: Installing module {moduleName} to {location}"); + _ = pwsh.PowerShell.Invoke(); + } + else + { + string path = customPath; + if (location == TestModuleLocation.WinGetModulePath || + location == TestModuleLocation.Default) + { + path = wingetModulePath; + } + + using var pwsh = new PowerShellHost(); + pwsh.AddModulePath($"{wingetModulePath};{customPath}"); + pwsh.PowerShell.AddCommand("Save-Module").AddParameter("Name", moduleName).AddParameter("Path", path).AddParameter("Force"); + + if (!string.IsNullOrEmpty(repository)) + { + pwsh.PowerShell.AddParameter("Repository", repository); + } + + TestContext.Out.WriteLine($"EnsureModuleState: Saving module {moduleName} to {path}"); + _ = pwsh.PowerShell.Invoke(); + } + } + } + + /// + /// Creates an ARP entry from the given values. + /// + /// Product code of the entry. + /// The properties to set in the entry. + /// Scope of the entry. + public static void CreateARPEntry( + string productCode, + object properties, + Scope scope = Scope.User) + { + RegistryKey baseKey = scope == Scope.User ? Registry.CurrentUser : Registry.LocalMachine; + using (RegistryKey uninstallRegistryKey = baseKey.OpenSubKey(Constants.UninstallSubKey, true)) + { + RegistryKey entry = uninstallRegistryKey.CreateSubKey(productCode, true); + + foreach (PropertyInfo property in properties.GetType().GetProperties()) + { + entry.SetValue(property.Name, property.GetValue(properties)); + } + } + } + + /// + /// Removes an ARP entry. + /// + /// Product code of the entry. + /// Scope of the entry. + public static void RemoveARPEntry( + string productCode, + Scope scope = Scope.User) + { + RegistryKey baseKey = scope == Scope.User ? Registry.CurrentUser : Registry.LocalMachine; + using (RegistryKey uninstallRegistryKey = baseKey.OpenSubKey(Constants.UninstallSubKey, true)) + { + uninstallRegistryKey.DeleteSubKey(productCode); + } + } + + /// + /// Copies the contents of a given directory from a source path to a destination path. + /// + /// Source directory name. + /// Destination directory name. + public static void CopyDirectory(string sourceDirName, string destDirName) + { + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + DirectoryInfo[] dirs = dir.GetDirectories(); + + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, false); + } + + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(destDirName, subdir.Name); + CopyDirectory(subdir.FullName, temppath); + } + } + + /// + /// Gets the expected module path. + /// + /// Location. + /// The expected path of the module. + public static string GetExpectedModulePath(TestModuleLocation location) + { + switch (location) + { + case TestModuleLocation.CurrentUser: + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), @"PowerShell\Modules"); + case TestModuleLocation.AllUsers: + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles), @"PowerShell\Modules"); + case TestModuleLocation.WinGetModulePath: + case TestModuleLocation.Default: + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), @"Microsoft\WinGet\Configuration\Modules"); + case TestModuleLocation.Custom: + return Path.Combine(Path.GetTempPath(), "E2ECustomModules"); + default: + throw new ArgumentException(location.ToString()); + } + } + + /// + /// Gets the instance identifier of the first configuration history item with name in its output line. + /// + /// The string to search for. + /// The instance identifier of a configuration that matched the search, or an empty string if none did. + public static string GetConfigurationInstanceIdentifierFor(string name) + { + var result = TestCommon.RunAICLICommand("configure list", string.Empty); + Assert.AreEqual(0, result.ExitCode); + + string[] lines = result.StdOut.Split('\n', StringSplitOptions.RemoveEmptyEntries); + + foreach (string line in lines) + { + if (line.Contains(name)) + { + // Find the first GUID in the output + int left = line.IndexOf('{'); + int right = line.IndexOfAny(new char[] { '}', '…' }); + Assert.AreNotEqual(-1, left); + Assert.AreNotEqual(-1, right); + Assert.LessOrEqual(right - left, 38); + + return line.Substring(left, right - left); + } + } + + return string.Empty; + } + + /// + /// Copy the installer file to the ARP InstallSource directory. + /// + /// Test installer to be copied. + /// Installer Product. + /// is WoW6432Node to use. + /// Returns the installer source directory if the file operation is successful, otherwise returns an empty string. + public static string CopyInstallerFileToARPInstallSourceDirectory(string installerFilePath, string productCode, bool useWoW6432Node = false) + { + if (string.IsNullOrEmpty(installerFilePath)) + { + new ArgumentNullException(nameof(installerFilePath)); + } + + if (!File.Exists(installerFilePath)) + { + new FileNotFoundException(installerFilePath); + } + + string outputDirectory = string.Empty; + + // Define the registry paths for both x64 and x86 + string registryPath = useWoW6432Node + ? $@"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}" + : $@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{productCode}"; + + // Open the registry key where the uninstall information is stored + using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath)) + { + if (key != null) + { + // Read the InstallSource value + string arpInstallSourceDirectory = key.GetValue("InstallSource") as string; + + if (!string.IsNullOrEmpty(arpInstallSourceDirectory)) + { + // Copy the MSI installer to the InstallSource directory + string installerFileName = Path.GetFileName(installerFilePath); + string installerDestinationPath = Path.Combine(arpInstallSourceDirectory, installerFileName); + + if (!Directory.Exists(arpInstallSourceDirectory)) + { + Directory.CreateDirectory(arpInstallSourceDirectory); + } + + File.Copy(installerFilePath, installerDestinationPath, true); + + outputDirectory = arpInstallSourceDirectory; + } + } + } + + return outputDirectory; + } + + /// + /// Run winget command via direct process. + /// + /// The executable to run. + /// Command to run. + /// Parameters. + /// Optional std in. + /// Optional timeout. + /// Throw on timeout. + /// Environment variables to set. + /// The result of the command. + public static RunCommandResult RunProcess( + string executablePath, + string command, + string parameters, + string stdIn, + int timeOut, + bool throwOnTimeout, + Dictionary environmentVariables) + { + string inputMsg = + "Exe path: " + executablePath + + " Command: " + command + + " Parameters: " + parameters + + (string.IsNullOrEmpty(stdIn) ? string.Empty : " StdIn: " + stdIn) + + " Timeout: " + timeOut + + (environmentVariables == null ? string.Empty : + " Env: " + string.Join(", ", environmentVariables.Select(item => $"{item.Key}={item.Value}"))); + + TestContext.Out.WriteLine($"Starting command run. {inputMsg}"); + + RunCommandResult result = new (); + Process p = new Process(); + p.StartInfo = new ProcessStartInfo(executablePath, command + ' ' + parameters); + p.StartInfo.UseShellExecute = false; + + p.StartInfo.StandardOutputEncoding = Encoding.UTF8; + p.StartInfo.RedirectStandardOutput = true; + StringBuilder outputData = new (); + p.OutputDataReceived += (sender, args) => + { + if (args.Data != null) + { + outputData.AppendLine(args.Data); + } + }; + + p.StartInfo.StandardErrorEncoding = Encoding.UTF8; + p.StartInfo.RedirectStandardError = true; + StringBuilder errorData = new (); + p.ErrorDataReceived += (sender, args) => + { + if (args.Data != null) + { + errorData.AppendLine(args.Data); + } + }; + + if (!string.IsNullOrEmpty(stdIn)) + { + p.StartInfo.RedirectStandardInput = true; + } + + if (environmentVariables != null) + { + foreach (var item in environmentVariables) + { + p.StartInfo.EnvironmentVariables[item.Key] = item.Value; + } + } + + p.Start(); + p.BeginOutputReadLine(); + p.BeginErrorReadLine(); + + if (!string.IsNullOrEmpty(stdIn)) + { + p.StandardInput.Write(stdIn); + p.StandardInput.Close(); + } + + if (p.WaitForExit(timeOut)) + { + // According to documentation, this extra call will ensure that the redirected streams + // have finished reading all of the data. + p.WaitForExit(); + + result.ExitCode = p.ExitCode; + result.StdOut = outputData.ToString(); + result.StdErr = errorData.ToString(); + + TestContext.Out.WriteLine("Command run completed with exit code: " + result.ExitCode); + + if (!string.IsNullOrEmpty(result.StdErr)) + { + TestContext.Error.WriteLine("Command run error. Error: " + result.StdErr); + } + + if (TestSetup.Parameters.VerboseLogging) + { + TestContext.Out.WriteLine("Command run output. Output:\n" + result.StdOut ?? ""); + } + } + else if (throwOnTimeout) + { + throw new TimeoutException($"Direct command run timed out: {command} {parameters}"); + } + + return result; + } + + /// + /// Run winget command via direct process. + /// + /// Command to run. + /// Parameters. + /// Optional std in. + /// Optional timeout. + /// Throw on timeout. + /// Environment variables to set. + /// The result of the command. + private static RunCommandResult RunAICLICommandViaDirectProcess( + string command, + string parameters, + string stdIn, + int timeOut, + bool throwOnTimeout, + Dictionary environmentVariables) + { + return RunProcess(TestSetup.Parameters.AICLIPath, command, parameters, stdIn, timeOut, throwOnTimeout, environmentVariables); + } + + /// + /// Run command result. + /// + public struct RunCommandResult + { + /// + /// Exit code. + /// + public int ExitCode; + + /// + /// StdOut. + /// + public string StdOut; + + /// + /// StdErr. + /// + public string StdErr; + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs b/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs index d79d57aa90..b8022e6021 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestIndex.cs @@ -1,199 +1,199 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Helpers -{ - using System; - using System.IO; - using System.Text.Json; - using Microsoft.WinGetSourceCreator; - using WinGetSourceCreator.Model; - - /// - /// Test index setup. - /// - public static class TestIndex - { - static TestIndex() - { - // Expected path for the installers. - TestIndex.ExeInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ExeInstaller, Constants.ExeInstallerFileName); - TestIndex.MsiInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerFileName); - TestIndex.MsiInstallerV2 = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerV2FileName); - TestIndex.MsixInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsixInstaller, Constants.MsixInstallerFileName); - TestIndex.ZipInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ZipInstaller, Constants.ZipInstallerFileName); - TestIndex.Font = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.FontFileName, Constants.FontFileName); - } - - /// - /// Gets the signed exe installer path used by the manifests in the E2E test. - /// - public static string ExeInstaller { get; private set; } - - /// - /// Gets the signed msi installer path used by the manifests in the E2E test. - /// - public static string MsiInstaller { get; private set; } - - /// - /// Gets the signed msi installerV2 path used by the manifests in the E2E test. - /// - public static string MsiInstallerV2 { get; private set; } - - /// - /// Gets the signed msix installer path used by the manifests in the E2E test. - /// - public static string MsixInstaller { get; private set; } - - /// - /// Gets the zip installer path used by the manifests in the E2E test. - /// - public static string ZipInstaller { get; private set; } - - /// - /// Gets the font file path used by the manifests in the E2E test. - /// - public static string Font { get; private set; } - - /// - /// Generate test source. - /// - public static void GenerateE2ESource() - { - var testParams = TestSetup.Parameters; - - if (string.IsNullOrEmpty(testParams.ExeInstallerPath)) - { - throw new ArgumentNullException($"{Constants.ExeInstallerPathParameter} is required"); - } - - if (!File.Exists(testParams.ExeInstallerPath)) - { - throw new FileNotFoundException(testParams.ExeInstallerPath); - } - - if (string.IsNullOrEmpty(testParams.MsiInstallerPath)) - { - throw new ArgumentNullException($"{Constants.MsiInstallerPathParameter} is required"); - } - - if (!File.Exists(testParams.MsiInstallerPath)) - { - throw new FileNotFoundException(testParams.MsiInstallerPath); - } - - if (string.IsNullOrEmpty(testParams.MsiInstallerV2Path)) - { - throw new ArgumentNullException($"{Constants.MsiInstallerV2PathParameter} is required"); - } - - if (!File.Exists(testParams.MsiInstallerV2Path)) - { - throw new FileNotFoundException(testParams.MsiInstallerV2Path); - } - - if (string.IsNullOrEmpty(testParams.MsixInstallerPath)) - { - throw new ArgumentNullException($"{Constants.MsixInstallerPathParameter} is required"); - } - - if (!File.Exists(testParams.MsixInstallerPath)) - { - throw new FileNotFoundException(testParams.MsixInstallerPath); - } - - if (string.IsNullOrEmpty(testParams.FontPath)) - { - throw new ArgumentNullException($"{Constants.FontPathParameter} is required"); - } - - if (!File.Exists(testParams.FontPath)) - { - throw new FileNotFoundException(testParams.FontPath); - } - - if (string.IsNullOrEmpty(testParams.PackageCertificatePath)) - { - throw new ArgumentNullException($"{Constants.PackageCertificatePathParameter} is required"); - } - - if (!File.Exists(testParams.PackageCertificatePath)) - { - throw new FileNotFoundException(testParams.PackageCertificatePath); - } - - LocalSource e2eSource = new () - { - AppxManifest = TestCommon.GetTestDataFile(Path.Combine("Package", "AppxManifest.xml")), - WorkingDirectory = testParams.StaticFileRootPath, - LocalManifests = new () - { - TestCommon.GetTestDataFile("Manifests"), - }, - LocalInstallers = new () - { - new LocalInstaller - { - Type = InstallerType.Exe, - Name = Path.Combine(Constants.ExeInstaller, Constants.ExeInstallerFileName), - Input = testParams.ExeInstallerPath, - HashToken = "", - }, - new LocalInstaller - { - Type = InstallerType.Msi, - Name = Path.Combine(Constants.MsiInstaller, Constants.MsiInstallerFileName), - Input = testParams.MsiInstallerPath, - HashToken = "", - }, - new LocalInstaller - { - Type = InstallerType.Msi, - Name = Path.Combine(Constants.MsiInstaller, Constants.MsiInstallerV2FileName), - Input = testParams.MsiInstallerPath, - HashToken = "", - }, - new LocalInstaller - { - Type = InstallerType.Msix, - Name = Path.Combine(Constants.MsixInstaller, Constants.MsixInstallerFileName), - Input = testParams.MsixInstallerPath, - HashToken = "", - SignatureToken = "", - }, - new LocalInstaller - { - Type = InstallerType.Font, - Name = Path.Combine(Constants.FontFileName, Constants.FontFileName), - Input = testParams.FontPath, - HashToken = "", - }, - }, - DynamicInstallers = new () - { - new DynamicInstaller - { - Type = InstallerType.Zip, - Name = Path.Combine(Constants.ZipInstaller, Constants.ZipInstallerFileName), - Input = new () - { - ExeInstaller, - MsiInstaller, - MsixInstaller, - }, - HashToken = "", - }, - }, - Signature = new () - { - CertFile = testParams.PackageCertificatePath, - }, - }; - - WinGetLocalSource.CreateLocalSource(e2eSource); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Helpers +{ + using System; + using System.IO; + using System.Text.Json; + using Microsoft.WinGetSourceCreator; + using WinGetSourceCreator.Model; + + /// + /// Test index setup. + /// + public static class TestIndex + { + static TestIndex() + { + // Expected path for the installers. + TestIndex.ExeInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ExeInstaller, Constants.ExeInstallerFileName); + TestIndex.MsiInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerFileName); + TestIndex.MsiInstallerV2 = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsiInstaller, Constants.MsiInstallerV2FileName); + TestIndex.MsixInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.MsixInstaller, Constants.MsixInstallerFileName); + TestIndex.ZipInstaller = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.ZipInstaller, Constants.ZipInstallerFileName); + TestIndex.Font = Path.Combine(TestSetup.Parameters.StaticFileRootPath, Constants.FontFileName, Constants.FontFileName); + } + + /// + /// Gets the signed exe installer path used by the manifests in the E2E test. + /// + public static string ExeInstaller { get; private set; } + + /// + /// Gets the signed msi installer path used by the manifests in the E2E test. + /// + public static string MsiInstaller { get; private set; } + + /// + /// Gets the signed msi installerV2 path used by the manifests in the E2E test. + /// + public static string MsiInstallerV2 { get; private set; } + + /// + /// Gets the signed msix installer path used by the manifests in the E2E test. + /// + public static string MsixInstaller { get; private set; } + + /// + /// Gets the zip installer path used by the manifests in the E2E test. + /// + public static string ZipInstaller { get; private set; } + + /// + /// Gets the font file path used by the manifests in the E2E test. + /// + public static string Font { get; private set; } + + /// + /// Generate test source. + /// + public static void GenerateE2ESource() + { + var testParams = TestSetup.Parameters; + + if (string.IsNullOrEmpty(testParams.ExeInstallerPath)) + { + throw new ArgumentNullException($"{Constants.ExeInstallerPathParameter} is required"); + } + + if (!File.Exists(testParams.ExeInstallerPath)) + { + throw new FileNotFoundException(testParams.ExeInstallerPath); + } + + if (string.IsNullOrEmpty(testParams.MsiInstallerPath)) + { + throw new ArgumentNullException($"{Constants.MsiInstallerPathParameter} is required"); + } + + if (!File.Exists(testParams.MsiInstallerPath)) + { + throw new FileNotFoundException(testParams.MsiInstallerPath); + } + + if (string.IsNullOrEmpty(testParams.MsiInstallerV2Path)) + { + throw new ArgumentNullException($"{Constants.MsiInstallerV2PathParameter} is required"); + } + + if (!File.Exists(testParams.MsiInstallerV2Path)) + { + throw new FileNotFoundException(testParams.MsiInstallerV2Path); + } + + if (string.IsNullOrEmpty(testParams.MsixInstallerPath)) + { + throw new ArgumentNullException($"{Constants.MsixInstallerPathParameter} is required"); + } + + if (!File.Exists(testParams.MsixInstallerPath)) + { + throw new FileNotFoundException(testParams.MsixInstallerPath); + } + + if (string.IsNullOrEmpty(testParams.FontPath)) + { + throw new ArgumentNullException($"{Constants.FontPathParameter} is required"); + } + + if (!File.Exists(testParams.FontPath)) + { + throw new FileNotFoundException(testParams.FontPath); + } + + if (string.IsNullOrEmpty(testParams.PackageCertificatePath)) + { + throw new ArgumentNullException($"{Constants.PackageCertificatePathParameter} is required"); + } + + if (!File.Exists(testParams.PackageCertificatePath)) + { + throw new FileNotFoundException(testParams.PackageCertificatePath); + } + + LocalSource e2eSource = new () + { + AppxManifest = TestCommon.GetTestDataFile(Path.Combine("Package", "AppxManifest.xml")), + WorkingDirectory = testParams.StaticFileRootPath, + LocalManifests = new () + { + TestCommon.GetTestDataFile("Manifests"), + }, + LocalInstallers = new () + { + new LocalInstaller + { + Type = InstallerType.Exe, + Name = Path.Combine(Constants.ExeInstaller, Constants.ExeInstallerFileName), + Input = testParams.ExeInstallerPath, + HashToken = "", + }, + new LocalInstaller + { + Type = InstallerType.Msi, + Name = Path.Combine(Constants.MsiInstaller, Constants.MsiInstallerFileName), + Input = testParams.MsiInstallerPath, + HashToken = "", + }, + new LocalInstaller + { + Type = InstallerType.Msi, + Name = Path.Combine(Constants.MsiInstaller, Constants.MsiInstallerV2FileName), + Input = testParams.MsiInstallerPath, + HashToken = "", + }, + new LocalInstaller + { + Type = InstallerType.Msix, + Name = Path.Combine(Constants.MsixInstaller, Constants.MsixInstallerFileName), + Input = testParams.MsixInstallerPath, + HashToken = "", + SignatureToken = "", + }, + new LocalInstaller + { + Type = InstallerType.Font, + Name = Path.Combine(Constants.FontFileName, Constants.FontFileName), + Input = testParams.FontPath, + HashToken = "", + }, + }, + DynamicInstallers = new () + { + new DynamicInstaller + { + Type = InstallerType.Zip, + Name = Path.Combine(Constants.ZipInstaller, Constants.ZipInstallerFileName), + Input = new () + { + ExeInstaller, + MsiInstaller, + MsixInstaller, + }, + HashToken = "", + }, + }, + Signature = new () + { + CertFile = testParams.PackageCertificatePath, + }, + }; + + WinGetLocalSource.CreateLocalSource(e2eSource); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs index 7cf71fa7ba..479d8d438e 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/TestSetup.cs @@ -1,240 +1,240 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Helpers -{ - using System; - using System.IO; - using NUnit.Framework; - - /// - /// Singleton class with test parameters. - /// - internal class TestSetup - { - private static readonly Lazy Lazy = new (() => new TestSetup()); - - private string settingFilePath = null; - - private TestSetup() - { - if (TestContext.Parameters.Count == 0) - { - this.IsDefault = true; - } - - // Read TestParameters and set runtime variables - this.PackagedContext = this.InitializeBoolParam(Constants.PackagedContextParameter, true); - this.VerboseLogging = this.InitializeBoolParam(Constants.VerboseLoggingParameter, true); - this.LooseFileRegistration = this.InitializeBoolParam(Constants.LooseFileRegistrationParameter); - this.SkipTestSource = this.InitializeBoolParam(Constants.SkipTestSourceParameter, this.IsDefault); - this.InprocTestbedUseTestPackage = this.InitializeBoolParam(Constants.InprocTestbedUseTestPackageParameter); - - // For packaged context, default to AppExecutionAlias - this.AICLIPath = this.InitializeStringParam(Constants.AICLIPathParameter, this.PackagedContext ? "WinGetDev.exe" : TestCommon.GetTestFile("winget.exe")); - this.AICLIPackagePath = this.InitializeStringParam(Constants.AICLIPackagePathParameter, TestCommon.GetTestFile("AppInstallerCLIPackage.appxbundle")); - - this.StaticFileRootPath = this.InitializeDirectoryParam(Constants.StaticFileRootPathParameter, Path.GetTempPath()); - - this.LocalServerCertPath = this.InitializeFileParam(Constants.LocalServerCertPathParameter); - this.PackageCertificatePath = this.InitializeFileParam(Constants.PackageCertificatePathParameter); - this.ExeInstallerPath = this.InitializeFileParam(Constants.ExeInstallerPathParameter); - this.MsiInstallerPath = this.InitializeFileParam(Constants.MsiInstallerPathParameter); - this.MsixInstallerPath = this.InitializeFileParam(Constants.MsixInstallerPathParameter); - this.MsiInstallerV2Path = this.InitializeFileParam(Constants.MsiInstallerV2PathParameter); - this.FontPath = this.InitializeFileParam(Constants.FontPathParameter); - this.InprocTestbedPath = this.InitializeFileParam(Constants.InprocTestbedPathParameter); - - this.ForcedExperimentalFeatures = this.InitializeStringArrayParam(Constants.ForcedExperimentalFeaturesParameter); - } - - /// - /// Gets the instance object. - /// - public static TestSetup Parameters - { - get - { - return Lazy.Value; - } - } - - /// - /// Gets the cli path. - /// - public string AICLIPath { get; } - - /// - /// Gets the package path. - /// - public string AICLIPackagePath { get; } - - /// - /// Gets a value indicating whether the test runs in package context. - /// - public bool PackagedContext { get; } - - /// - /// Gets a value indicating whether the test uses verbose logging. - /// - public bool VerboseLogging { get; } - - /// - /// Gets a value indicating whether to use loose file registration. - /// - public bool LooseFileRegistration { get; } - - /// - /// Gets the static file root path. - /// - public string StaticFileRootPath { get; } - - /// - /// Gets the local server cert path. - /// - public string LocalServerCertPath { get; } - - /// - /// Gets the exe installer path. - /// - public string ExeInstallerPath { get; } - - /// - /// Gets the msi installer path. - /// - public string MsiInstallerPath { get; } - - /// - /// Gets the msi installer V2 path. - /// - public string MsiInstallerV2Path { get; } - - /// - /// Gets the msix installer path. - /// - public string MsixInstallerPath { get; } - - /// - /// Gets the zip installer path. - /// - public string ZipInstallerPath { get; } - - /// - /// Gets the font path. - /// - public string FontPath { get; } - - /// - /// Gets the package cert path. - /// - public string PackageCertificatePath { get; } - - /// - /// Gets the inproc testbed executable path. - /// - public string InprocTestbedPath { get; } - - /// - /// Gets a value indicating whether to use the test package or not. - /// - public bool InprocTestbedUseTestPackage { get; } - - /// - /// Gets a value indicating whether to skip creating test source. - /// - public bool SkipTestSource { get; } - - /// - /// Gets the settings json path. - /// - public string SettingsJsonFilePath - { - get - { - if (this.settingFilePath == null) - { - this.settingFilePath = WinGetSettingsHelper.GetUserSettingsPath(); - } - - return this.settingFilePath; - } - } - - /// - /// Gets the experimental features that should be forcibly enabled. - /// - public string[] ForcedExperimentalFeatures { get; } - - /// - /// Gets a value indicating whether is the default parameters. - /// - public bool IsDefault { get; } - - private bool InitializeBoolParam(string paramName, bool defaultValue = false) - { - if (this.IsDefault || !TestContext.Parameters.Exists(paramName)) - { - return defaultValue; - } - - return TestContext.Parameters.Get(paramName).Equals("true", StringComparison.OrdinalIgnoreCase); - } - - private string InitializeStringParam(string paramName, string defaultValue = null) - { - if (this.IsDefault || !TestContext.Parameters.Exists(paramName)) - { - return defaultValue; - } - - return TestContext.Parameters.Get(paramName); - } - - private string[] InitializeStringArrayParam(string paramName, string[] defaultValue = null) - { - if (this.IsDefault || !TestContext.Parameters.Exists(paramName)) - { - return defaultValue; - } - - return TestContext.Parameters.Get(paramName).Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); - } - - private string InitializeFileParam(string paramName, string defaultValue = null) - { - if (!TestContext.Parameters.Exists(paramName)) - { - return defaultValue; - } - - var value = TestContext.Parameters.Get(paramName); - - if (!File.Exists(value)) - { - throw new FileNotFoundException($"{paramName}: {value}"); - } - - return value; - } - - private string InitializeDirectoryParam(string paramName, string defaultValue = null) - { - if (!TestContext.Parameters.Exists(paramName)) - { - return defaultValue; - } - - var value = TestContext.Parameters.Get(paramName); - - if (!Directory.Exists(value)) - { - throw new DirectoryNotFoundException($"{paramName}: {value}"); - } - - return value; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Helpers +{ + using System; + using System.IO; + using NUnit.Framework; + + /// + /// Singleton class with test parameters. + /// + internal class TestSetup + { + private static readonly Lazy Lazy = new (() => new TestSetup()); + + private string settingFilePath = null; + + private TestSetup() + { + if (TestContext.Parameters.Count == 0) + { + this.IsDefault = true; + } + + // Read TestParameters and set runtime variables + this.PackagedContext = this.InitializeBoolParam(Constants.PackagedContextParameter, true); + this.VerboseLogging = this.InitializeBoolParam(Constants.VerboseLoggingParameter, true); + this.LooseFileRegistration = this.InitializeBoolParam(Constants.LooseFileRegistrationParameter); + this.SkipTestSource = this.InitializeBoolParam(Constants.SkipTestSourceParameter, this.IsDefault); + this.InprocTestbedUseTestPackage = this.InitializeBoolParam(Constants.InprocTestbedUseTestPackageParameter); + + // For packaged context, default to AppExecutionAlias + this.AICLIPath = this.InitializeStringParam(Constants.AICLIPathParameter, this.PackagedContext ? "WinGetDev.exe" : TestCommon.GetTestFile("winget.exe")); + this.AICLIPackagePath = this.InitializeStringParam(Constants.AICLIPackagePathParameter, TestCommon.GetTestFile("AppInstallerCLIPackage.appxbundle")); + + this.StaticFileRootPath = this.InitializeDirectoryParam(Constants.StaticFileRootPathParameter, Path.GetTempPath()); + + this.LocalServerCertPath = this.InitializeFileParam(Constants.LocalServerCertPathParameter); + this.PackageCertificatePath = this.InitializeFileParam(Constants.PackageCertificatePathParameter); + this.ExeInstallerPath = this.InitializeFileParam(Constants.ExeInstallerPathParameter); + this.MsiInstallerPath = this.InitializeFileParam(Constants.MsiInstallerPathParameter); + this.MsixInstallerPath = this.InitializeFileParam(Constants.MsixInstallerPathParameter); + this.MsiInstallerV2Path = this.InitializeFileParam(Constants.MsiInstallerV2PathParameter); + this.FontPath = this.InitializeFileParam(Constants.FontPathParameter); + this.InprocTestbedPath = this.InitializeFileParam(Constants.InprocTestbedPathParameter); + + this.ForcedExperimentalFeatures = this.InitializeStringArrayParam(Constants.ForcedExperimentalFeaturesParameter); + } + + /// + /// Gets the instance object. + /// + public static TestSetup Parameters + { + get + { + return Lazy.Value; + } + } + + /// + /// Gets the cli path. + /// + public string AICLIPath { get; } + + /// + /// Gets the package path. + /// + public string AICLIPackagePath { get; } + + /// + /// Gets a value indicating whether the test runs in package context. + /// + public bool PackagedContext { get; } + + /// + /// Gets a value indicating whether the test uses verbose logging. + /// + public bool VerboseLogging { get; } + + /// + /// Gets a value indicating whether to use loose file registration. + /// + public bool LooseFileRegistration { get; } + + /// + /// Gets the static file root path. + /// + public string StaticFileRootPath { get; } + + /// + /// Gets the local server cert path. + /// + public string LocalServerCertPath { get; } + + /// + /// Gets the exe installer path. + /// + public string ExeInstallerPath { get; } + + /// + /// Gets the msi installer path. + /// + public string MsiInstallerPath { get; } + + /// + /// Gets the msi installer V2 path. + /// + public string MsiInstallerV2Path { get; } + + /// + /// Gets the msix installer path. + /// + public string MsixInstallerPath { get; } + + /// + /// Gets the zip installer path. + /// + public string ZipInstallerPath { get; } + + /// + /// Gets the font path. + /// + public string FontPath { get; } + + /// + /// Gets the package cert path. + /// + public string PackageCertificatePath { get; } + + /// + /// Gets the inproc testbed executable path. + /// + public string InprocTestbedPath { get; } + + /// + /// Gets a value indicating whether to use the test package or not. + /// + public bool InprocTestbedUseTestPackage { get; } + + /// + /// Gets a value indicating whether to skip creating test source. + /// + public bool SkipTestSource { get; } + + /// + /// Gets the settings json path. + /// + public string SettingsJsonFilePath + { + get + { + if (this.settingFilePath == null) + { + this.settingFilePath = WinGetSettingsHelper.GetUserSettingsPath(); + } + + return this.settingFilePath; + } + } + + /// + /// Gets the experimental features that should be forcibly enabled. + /// + public string[] ForcedExperimentalFeatures { get; } + + /// + /// Gets a value indicating whether is the default parameters. + /// + public bool IsDefault { get; } + + private bool InitializeBoolParam(string paramName, bool defaultValue = false) + { + if (this.IsDefault || !TestContext.Parameters.Exists(paramName)) + { + return defaultValue; + } + + return TestContext.Parameters.Get(paramName).Equals("true", StringComparison.OrdinalIgnoreCase); + } + + private string InitializeStringParam(string paramName, string defaultValue = null) + { + if (this.IsDefault || !TestContext.Parameters.Exists(paramName)) + { + return defaultValue; + } + + return TestContext.Parameters.Get(paramName); + } + + private string[] InitializeStringArrayParam(string paramName, string[] defaultValue = null) + { + if (this.IsDefault || !TestContext.Parameters.Exists(paramName)) + { + return defaultValue; + } + + return TestContext.Parameters.Get(paramName).Split('|', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + } + + private string InitializeFileParam(string paramName, string defaultValue = null) + { + if (!TestContext.Parameters.Exists(paramName)) + { + return defaultValue; + } + + var value = TestContext.Parameters.Get(paramName); + + if (!File.Exists(value)) + { + throw new FileNotFoundException($"{paramName}: {value}"); + } + + return value; + } + + private string InitializeDirectoryParam(string paramName, string defaultValue = null) + { + if (!TestContext.Parameters.Exists(paramName)) + { + return defaultValue; + } + + var value = TestContext.Parameters.Get(paramName); + + if (!Directory.Exists(value)) + { + throw new DirectoryNotFoundException($"{paramName}: {value}"); + } + + return value; + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs index 95bf7f99b7..3f99bde3ce 100644 --- a/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs +++ b/src/AppInstallerCLIE2ETests/Helpers/WinGetSettingsHelper.cs @@ -1,324 +1,324 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Helpers -{ - using System.Collections; - using System.IO; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - - /// - /// Helper class to set winget settings. - /// - internal static class WinGetSettingsHelper - { - /// - /// Gets or sets the experimental features that should be forcibly enabled. - /// - public static string[] ForcedExperimentalFeatures { get; set; } - - /// - /// Gets the user settings path by calling winget settings export. - /// - /// Expanded path for user settings. - public static string GetUserSettingsPath() - { - var result = TestCommon.RunAICLICommand("settings", "export"); - var output = result.StdOut; - var serialized = JObject.Parse(output); - return (string)serialized.GetValue("userSettingsFile"); - } - - /// - /// Initialize settings. - /// - public static void InitializeWingetSettings() - { - Hashtable experimentalFeatures = new Hashtable(); - - var forcedExperimentalFeatures = ForcedExperimentalFeatures; - if (forcedExperimentalFeatures != null) - { - foreach (var feature in forcedExperimentalFeatures) - { - experimentalFeatures[feature] = true; - } - } - - var settingsJson = new Hashtable() - { - { - "$schema", - "https://aka.ms/winget-settings.schema.json" - }, - { - "experimentalFeatures", - experimentalFeatures - }, - { - "debugging", - new Hashtable() - { - { "enableSelfInitiatedMinidump", true }, - { "keepAllLogFiles", true }, - } - }, - { - "installBehavior", - new Hashtable() - { - } - }, - { - "configureBehavior", - new Hashtable() - { - } - }, - }; - - // Run winget one time to initialize settings directory - // when running in unpackaged context - TestCommon.RunAICLICommand(string.Empty, "-v"); - - SetWingetSettings(JsonConvert.SerializeObject(settingsJson, Formatting.Indented)); - } - - /// - /// Enables an admin setting. - /// - /// The admin setting name. - public static void EnableAdminSetting(string settingName) - { - TestCommon.RunAICLICommand("settings", $"--enable {settingName}"); - } - - /// - /// Disables an admin setting. - /// - /// The admin setting name. - public static void DisableAdminSetting(string settingName) - { - TestCommon.RunAICLICommand("settings", $"--disable {settingName}"); - } - - /// - /// Configure experimental features. - /// - /// Feature name. - /// Status. - public static void ConfigureFeature(string featureName, bool status) - { - JObject settingsJson = GetJsonSettingsObject("experimentalFeatures"); - ConfigureFeature(settingsJson, featureName, status); - SetWingetSettings(settingsJson); - } - - /// - /// Configure the install behavior. - /// - /// Setting name. - /// Setting value. - public static void ConfigureInstallBehavior(string settingName, string value) - { - JObject settingsJson = GetJsonSettingsObject("installBehavior"); - var installBehavior = settingsJson["installBehavior"]; - installBehavior[settingName] = value; - - SetWingetSettings(settingsJson); - } - - /// - /// Configure the configuration behavior. - /// - /// Setting name. - /// Setting value. - public static void ConfigureConfigureBehavior(string settingName, string value) - { - JObject settingsJson = GetJsonSettingsObject("configureBehavior"); - var configureBehavior = settingsJson["configureBehavior"]; - configureBehavior[settingName] = value; - - SetWingetSettings(settingsJson); - } - - /// - /// Configure the install behavior preferences. - /// - /// Setting name. - /// Setting value. - public static void ConfigureInstallBehaviorPreferences(string settingName, string value) - { - JObject settingsJson = GetJsonSettingsObject("installBehavior"); - var installBehavior = settingsJson["installBehavior"]; - - if (installBehavior["preferences"] == null) - { - installBehavior["preferences"] = new JObject(); - } - - var preferences = installBehavior["preferences"]; - preferences[settingName] = value; - - SetWingetSettings(settingsJson); - } - - /// - /// Configure the install behavior preferences. - /// - /// Setting name. - /// Setting value array. - public static void ConfigureInstallBehaviorPreferences(string settingName, string[] value) - { - JObject settingsJson = GetJsonSettingsObject("installBehavior"); - var installBehavior = settingsJson["installBehavior"]; - - if (installBehavior["preferences"] == null) - { - installBehavior["preferences"] = new JObject(); - } - - var preferences = installBehavior["preferences"]; - preferences[settingName] = new JArray(value); - - SetWingetSettings(settingsJson); - } - - /// - /// Configure the install behavior requirements. - /// - /// Setting name. - /// Setting value. - public static void ConfigureInstallBehaviorRequirements(string settingName, string value) - { - JObject settingsJson = GetJsonSettingsObject("installBehavior"); - var installBehavior = settingsJson["installBehavior"]; - - if (installBehavior["requirements"] == null) - { - installBehavior["requirements"] = new JObject(); - } - - var requirements = installBehavior["requirements"]; - requirements[settingName] = value; - - SetWingetSettings(settingsJson); - } - - /// - /// Configure the install behavior requirements. - /// - /// Setting name. - /// Setting value array. - public static void ConfigureInstallBehaviorRequirements(string settingName, string[] value) - { - JObject settingsJson = GetJsonSettingsObject("installBehavior"); - var installBehavior = settingsJson["installBehavior"]; - - if (installBehavior["requirements"] == null) - { - installBehavior["requirements"] = new JObject(); - } - - var requirements = installBehavior["requirements"]; - requirements[settingName] = new JArray(value); - - SetWingetSettings(settingsJson); - } - - /// - /// Initialize all features. - /// - /// Initialized feature value. - public static void InitializeAllFeatures(bool status) - { - JObject settingsJson = GetJsonSettingsObject("experimentalFeatures"); - - ConfigureFeature(settingsJson, "experimentalArg", status); - ConfigureFeature(settingsJson, "experimentalCmd", status); - ConfigureFeature(settingsJson, "directMSI", status); - ConfigureFeature(settingsJson, "windowsFeature", status); - ConfigureFeature(settingsJson, "resume", status); - ConfigureFeature(settingsJson, "reboot", status); - ConfigureFeature(settingsJson, "fonts", status); - ConfigureFeature(settingsJson, "sourcePriority", status); - - SetWingetSettings(settingsJson); - } - - /// - /// Configure the logging level for the settings. - /// - /// The logging level to set; null removes the value. - public static void ConfigureLoggingLevel(string level) - { - JObject settingsJson = GetJsonSettingsObject("logging"); - - if (level == null) - { - settingsJson["logging"]["level"]?.Parent?.Remove(); - } - else - { - settingsJson["logging"]["level"] = new JValue(level); - } - - SetWingetSettings(settingsJson); - } - - /// - /// Configure experimental features. - /// - /// The settings JSON object to modify. - /// Feature name. - /// Status. - private static void ConfigureFeature(JObject settingsJson, string featureName, bool status) - { - var experimentalFeatures = settingsJson["experimentalFeatures"]; - experimentalFeatures[featureName] = status; - } - - private static JObject GetJsonSettingsObject(string objectName) - { - JObject settingsJson = JObject.Parse(File.ReadAllText(TestSetup.Parameters.SettingsJsonFilePath)); - - if (!settingsJson.ContainsKey(objectName)) - { - settingsJson[objectName] = new JObject(); - } - - return settingsJson; - } - - /// - /// Converts a JObject to a string and writes to the settings file. - /// - /// Settings to set. - private static void SetWingetSettings(JObject settingsJson) - { - var forcedExperimentalFeatures = ForcedExperimentalFeatures; - if (forcedExperimentalFeatures != null) - { - foreach (var feature in forcedExperimentalFeatures) - { - ConfigureFeature(settingsJson, feature, true); - } - } - - SetWingetSettings(settingsJson.ToString()); - } - - /// - /// Writes string to settings file. - /// - /// Settings as string. - private static void SetWingetSettings(string settings) - { - File.WriteAllText(TestSetup.Parameters.SettingsJsonFilePath, settings); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Helpers +{ + using System.Collections; + using System.IO; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// + /// Helper class to set winget settings. + /// + internal static class WinGetSettingsHelper + { + /// + /// Gets or sets the experimental features that should be forcibly enabled. + /// + public static string[] ForcedExperimentalFeatures { get; set; } + + /// + /// Gets the user settings path by calling winget settings export. + /// + /// Expanded path for user settings. + public static string GetUserSettingsPath() + { + var result = TestCommon.RunAICLICommand("settings", "export"); + var output = result.StdOut; + var serialized = JObject.Parse(output); + return (string)serialized.GetValue("userSettingsFile"); + } + + /// + /// Initialize settings. + /// + public static void InitializeWingetSettings() + { + Hashtable experimentalFeatures = new Hashtable(); + + var forcedExperimentalFeatures = ForcedExperimentalFeatures; + if (forcedExperimentalFeatures != null) + { + foreach (var feature in forcedExperimentalFeatures) + { + experimentalFeatures[feature] = true; + } + } + + var settingsJson = new Hashtable() + { + { + "$schema", + "https://aka.ms/winget-settings.schema.json" + }, + { + "experimentalFeatures", + experimentalFeatures + }, + { + "debugging", + new Hashtable() + { + { "enableSelfInitiatedMinidump", true }, + { "keepAllLogFiles", true }, + } + }, + { + "installBehavior", + new Hashtable() + { + } + }, + { + "configureBehavior", + new Hashtable() + { + } + }, + }; + + // Run winget one time to initialize settings directory + // when running in unpackaged context + TestCommon.RunAICLICommand(string.Empty, "-v"); + + SetWingetSettings(JsonConvert.SerializeObject(settingsJson, Formatting.Indented)); + } + + /// + /// Enables an admin setting. + /// + /// The admin setting name. + public static void EnableAdminSetting(string settingName) + { + TestCommon.RunAICLICommand("settings", $"--enable {settingName}"); + } + + /// + /// Disables an admin setting. + /// + /// The admin setting name. + public static void DisableAdminSetting(string settingName) + { + TestCommon.RunAICLICommand("settings", $"--disable {settingName}"); + } + + /// + /// Configure experimental features. + /// + /// Feature name. + /// Status. + public static void ConfigureFeature(string featureName, bool status) + { + JObject settingsJson = GetJsonSettingsObject("experimentalFeatures"); + ConfigureFeature(settingsJson, featureName, status); + SetWingetSettings(settingsJson); + } + + /// + /// Configure the install behavior. + /// + /// Setting name. + /// Setting value. + public static void ConfigureInstallBehavior(string settingName, string value) + { + JObject settingsJson = GetJsonSettingsObject("installBehavior"); + var installBehavior = settingsJson["installBehavior"]; + installBehavior[settingName] = value; + + SetWingetSettings(settingsJson); + } + + /// + /// Configure the configuration behavior. + /// + /// Setting name. + /// Setting value. + public static void ConfigureConfigureBehavior(string settingName, string value) + { + JObject settingsJson = GetJsonSettingsObject("configureBehavior"); + var configureBehavior = settingsJson["configureBehavior"]; + configureBehavior[settingName] = value; + + SetWingetSettings(settingsJson); + } + + /// + /// Configure the install behavior preferences. + /// + /// Setting name. + /// Setting value. + public static void ConfigureInstallBehaviorPreferences(string settingName, string value) + { + JObject settingsJson = GetJsonSettingsObject("installBehavior"); + var installBehavior = settingsJson["installBehavior"]; + + if (installBehavior["preferences"] == null) + { + installBehavior["preferences"] = new JObject(); + } + + var preferences = installBehavior["preferences"]; + preferences[settingName] = value; + + SetWingetSettings(settingsJson); + } + + /// + /// Configure the install behavior preferences. + /// + /// Setting name. + /// Setting value array. + public static void ConfigureInstallBehaviorPreferences(string settingName, string[] value) + { + JObject settingsJson = GetJsonSettingsObject("installBehavior"); + var installBehavior = settingsJson["installBehavior"]; + + if (installBehavior["preferences"] == null) + { + installBehavior["preferences"] = new JObject(); + } + + var preferences = installBehavior["preferences"]; + preferences[settingName] = new JArray(value); + + SetWingetSettings(settingsJson); + } + + /// + /// Configure the install behavior requirements. + /// + /// Setting name. + /// Setting value. + public static void ConfigureInstallBehaviorRequirements(string settingName, string value) + { + JObject settingsJson = GetJsonSettingsObject("installBehavior"); + var installBehavior = settingsJson["installBehavior"]; + + if (installBehavior["requirements"] == null) + { + installBehavior["requirements"] = new JObject(); + } + + var requirements = installBehavior["requirements"]; + requirements[settingName] = value; + + SetWingetSettings(settingsJson); + } + + /// + /// Configure the install behavior requirements. + /// + /// Setting name. + /// Setting value array. + public static void ConfigureInstallBehaviorRequirements(string settingName, string[] value) + { + JObject settingsJson = GetJsonSettingsObject("installBehavior"); + var installBehavior = settingsJson["installBehavior"]; + + if (installBehavior["requirements"] == null) + { + installBehavior["requirements"] = new JObject(); + } + + var requirements = installBehavior["requirements"]; + requirements[settingName] = new JArray(value); + + SetWingetSettings(settingsJson); + } + + /// + /// Initialize all features. + /// + /// Initialized feature value. + public static void InitializeAllFeatures(bool status) + { + JObject settingsJson = GetJsonSettingsObject("experimentalFeatures"); + + ConfigureFeature(settingsJson, "experimentalArg", status); + ConfigureFeature(settingsJson, "experimentalCmd", status); + ConfigureFeature(settingsJson, "directMSI", status); + ConfigureFeature(settingsJson, "windowsFeature", status); + ConfigureFeature(settingsJson, "resume", status); + ConfigureFeature(settingsJson, "reboot", status); + ConfigureFeature(settingsJson, "fonts", status); + ConfigureFeature(settingsJson, "sourcePriority", status); + + SetWingetSettings(settingsJson); + } + + /// + /// Configure the logging level for the settings. + /// + /// The logging level to set; null removes the value. + public static void ConfigureLoggingLevel(string level) + { + JObject settingsJson = GetJsonSettingsObject("logging"); + + if (level == null) + { + settingsJson["logging"]["level"]?.Parent?.Remove(); + } + else + { + settingsJson["logging"]["level"] = new JValue(level); + } + + SetWingetSettings(settingsJson); + } + + /// + /// Configure experimental features. + /// + /// The settings JSON object to modify. + /// Feature name. + /// Status. + private static void ConfigureFeature(JObject settingsJson, string featureName, bool status) + { + var experimentalFeatures = settingsJson["experimentalFeatures"]; + experimentalFeatures[featureName] = status; + } + + private static JObject GetJsonSettingsObject(string objectName) + { + JObject settingsJson = JObject.Parse(File.ReadAllText(TestSetup.Parameters.SettingsJsonFilePath)); + + if (!settingsJson.ContainsKey(objectName)) + { + settingsJson[objectName] = new JObject(); + } + + return settingsJson; + } + + /// + /// Converts a JObject to a string and writes to the settings file. + /// + /// Settings to set. + private static void SetWingetSettings(JObject settingsJson) + { + var forcedExperimentalFeatures = ForcedExperimentalFeatures; + if (forcedExperimentalFeatures != null) + { + foreach (var feature in forcedExperimentalFeatures) + { + ConfigureFeature(settingsJson, feature, true); + } + } + + SetWingetSettings(settingsJson.ToString()); + } + + /// + /// Writes string to settings file. + /// + /// Settings as string. + private static void SetWingetSettings(string settings) + { + File.WriteAllText(TestSetup.Parameters.SettingsJsonFilePath, settings); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ImportCommand.cs b/src/AppInstallerCLIE2ETests/ImportCommand.cs index b14777892c..261f6a7c5e 100644 --- a/src/AppInstallerCLIE2ETests/ImportCommand.cs +++ b/src/AppInstallerCLIE2ETests/ImportCommand.cs @@ -1,22 +1,22 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests { - using System.IO; - using AppInstallerCLIE2ETests.Helpers; + using System.IO; + using AppInstallerCLIE2ETests.Helpers; using NUnit.Framework; - /// - /// Import command tests. + /// + /// Import command tests. /// public class ImportCommand : BaseCommand { - /// - /// Set up. + /// + /// Set up. /// [SetUp] public void Setup() @@ -24,8 +24,8 @@ public void Setup() this.CleanupTestExe(); } - /// - /// Test import v1. + /// + /// Test import v1. /// [Test] public void ImportSuccessful_1_0() @@ -36,8 +36,8 @@ public void ImportSuccessful_1_0() this.UninstallTestExe(); } - /// - /// Test import v2. + /// + /// Test import v2. /// [Test] public void ImportSuccessful_2_0() @@ -48,8 +48,8 @@ public void ImportSuccessful_2_0() this.UninstallTestExe(); } - /// - /// Test import invalid file. + /// + /// Test import invalid file. /// [Test] public void ImportInvalidFile() @@ -60,8 +60,8 @@ public void ImportInvalidFile() Assert.True(result.StdOut.Contains("JSON file is not valid")); } - /// - /// Test import from an unknown source. + /// + /// Test import from an unknown source. /// [Test] public void ImportUnknownSource() @@ -72,8 +72,8 @@ public void ImportUnknownSource() Assert.True(result.StdOut.Contains("Source required for import is not installed")); } - /// - /// Test import for an unknown package. + /// + /// Test import for an unknown package. /// [Test] public void ImportUnavailablePackage() @@ -84,8 +84,8 @@ public void ImportUnavailablePackage() Assert.True(result.StdOut.Contains("Package not found: MissingPackage")); } - /// - /// Test import when the package version is not present. + /// + /// Test import when the package version is not present. /// [Test] public void ImportUnavailableVersion() @@ -96,8 +96,8 @@ public void ImportUnavailableVersion() Assert.True(result.StdOut.Contains("Search failed for: AppInstallerTest.TestExeInstaller")); } - /// - /// Test import when the package is already installed. + /// + /// Test import when the package is already installed. /// [Test] public void ImportAlreadyInstalled() @@ -112,8 +112,8 @@ public void ImportAlreadyInstalled() this.UninstallTestExe(); } - /// - /// Test Import with an exported file. + /// + /// Test Import with an exported file. /// [Test] public void ImportExportedFile() diff --git a/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs index 229b6c2d37..8e77c659d9 100644 --- a/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs +++ b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs @@ -1,241 +1,241 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using System.Reflection; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Tests that run the inproc testbed targeting COM lifetime. - /// - public class InprocTestbedTests - { - /// - /// The activation type to use when creating objects. - /// - public enum ActivationType - { - /// - /// Use the WinRT type name for activation via C++/WinRT object construction. - /// - ClassName, - - /// - /// Use the CLSID for activation via C++/WinRT `create_instance`. - /// - CoCreateInstance, - } - - /// - /// Control when the module will allow signal that it can be unloaded if all objects are released. - /// This does not affect the loader by taking additional references to the module. - /// - public enum UnloadBehavior - { - /// - /// Allows the unload check function to proceed with object count checks and unload when possible. - /// - Allow, - - /// - /// Prevents the unload check until just before COM is uninitialized. - /// - AtUninitialize, - - /// - /// Prevents the unload check at all times. - /// - Never, - } - - /// - /// Gets or sets the path to the inproc testbed executable. - /// - private string InprocTestbedPath { get; set; } - - /// - /// Gets or sets the string that contains the package identity to use for the tests. - /// - private string TargetPackageInformation { get; set; } - - /// - /// Setup done once before all the tests here. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - this.InprocTestbedPath = TestSetup.Parameters.InprocTestbedPath; - - if (string.IsNullOrWhiteSpace(this.InprocTestbedPath)) - { - string assemblyLocation = Assembly.GetExecutingAssembly().Location; - this.InprocTestbedPath = Path.Join(Path.GetDirectoryName(assemblyLocation), "..\\ComInprocTestbed\\ComInprocTestbed.exe"); - } - - if (TestSetup.Parameters.InprocTestbedUseTestPackage) - { - this.TargetPackageInformation = $"-pkg {Constants.ExeInstallerPackageId} -src {Constants.TestSourceName} -url {Constants.TestSourceUrl}"; - } - } - - /// - /// Executes the testbed as simply as possible to ensure integrations. - /// - [Test] - public void DefaultTest() - { - this.RunInprocTestbed(new TestbedParameters() { WorkTestSleepInterval = 1000 }); - } - - /// - /// Tests using the CLSID with CoCreateInstance. - /// - /// Control whether COM should be uninitialized at the end of the process. - /// Set the unload behavior for the test. - /// Sets the number of milliseconds to sleep between each work/test iteration. - [Test] - [TestCase(false, UnloadBehavior.AtUninitialize)] - [TestCase(false, UnloadBehavior.Never)] - [TestCase(true, UnloadBehavior.Allow, 1000)] - [TestCase(true, UnloadBehavior.Never)] - public void CLSID_Tests(bool leakCOM, UnloadBehavior unloadBehavior, int? workTestSleep = null) - { - this.RunInprocTestbed(new TestbedParameters() - { - ActivationType = ActivationType.CoCreateInstance, - LeakCOM = leakCOM, - UnloadBehavior = unloadBehavior, - Iterations = 10, - WorkTestSleepInterval = workTestSleep, - }); - } - - /// - /// Tests using C++/WinRT object activation through the type name. - /// - /// Control whether the C++/WinRT factory cache will be cleared between iterations. - /// Control whether COM should be uninitialized at the end of the process. - /// Set the unload behavior for the test. - /// Sets the number of milliseconds to sleep between each work/test iteration. - [Test] - [TestCase(false, false, UnloadBehavior.AtUninitialize)] - [TestCase(false, false, UnloadBehavior.Never)] - [TestCase(false, true, UnloadBehavior.Allow)] - [TestCase(false, true, UnloadBehavior.Never)] - [TestCase(true, false, UnloadBehavior.AtUninitialize)] - [TestCase(true, false, UnloadBehavior.Never)] - [TestCase(true, true, UnloadBehavior.Allow, 1000)] - [TestCase(true, true, UnloadBehavior.Never)] - public void TypeName_Tests(bool freeCachedFactories, bool leakCOM, UnloadBehavior unloadBehavior, int? workTestSleep = null) - { - this.RunInprocTestbed(new TestbedParameters() - { - ActivationType = ActivationType.ClassName, - ClearFactories = freeCachedFactories, - LeakCOM = leakCOM, - UnloadBehavior = unloadBehavior, - Iterations = 10, - WorkTestSleepInterval = workTestSleep, - }); - } - - /// - /// Tests that disable the termination signal handling. - /// - /// Control whether the module should listen to termination signals. - /// Set the unload behavior for the test. - /// Sets the number of milliseconds to sleep between each work/test iteration. - [Test] - [TestCase(true, UnloadBehavior.Allow, 1000)] - [TestCase(true, UnloadBehavior.Never)] - [TestCase(false, UnloadBehavior.Allow, 1000)] - [TestCase(false, UnloadBehavior.Never)] - public void TerminationSignal_Tests(bool disableTerminationSignals, UnloadBehavior unloadBehavior, int? workTestSleep = null) - { - this.RunInprocTestbed(new TestbedParameters() - { - ActivationType = ActivationType.CoCreateInstance, - DisableTerminationSignals = disableTerminationSignals, - UnloadBehavior = unloadBehavior, - Iterations = 10, - WorkTestSleepInterval = workTestSleep, - }); - } - - private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000) - { - string builtParameters = string.Empty; - - if (parameters.ActivationType != null) - { - builtParameters += $"-activation {parameters.ActivationType} "; - } - - if (!parameters.ClearFactories) - { - builtParameters += "-keep-factories "; - } - - if (parameters.LeakCOM) - { - builtParameters += "-leak-com "; - } - - if (parameters.UnloadBehavior != null) - { - builtParameters += $"-unload {parameters.UnloadBehavior} "; - } - - if (parameters.Test != null) - { - builtParameters += $"-test {parameters.Test} "; - } - - if (parameters.Iterations != null) - { - builtParameters += $"-itr {parameters.Iterations} "; - } - - if (parameters.WorkTestSleepInterval != null) - { - builtParameters += $"-work-test-sleep {parameters.WorkTestSleepInterval} "; - } - - if (parameters.DisableTerminationSignals) - { - builtParameters += $"-no-term "; - } - - var result = TestCommon.RunProcess(this.InprocTestbedPath, this.TargetPackageInformation, builtParameters, null, timeout, true, null); - Assert.AreEqual(0, result.ExitCode); - } - - /// - /// The parameters to provide for running tests. - /// - private class TestbedParameters - { - internal ActivationType? ActivationType { get; init; } = null; - - internal bool ClearFactories { get; init; } = true; - - internal bool LeakCOM { get; init; } = false; - - internal UnloadBehavior? UnloadBehavior { get; init; } = null; - - internal string Test { get; init; } = "unload_check"; - - internal int? Iterations { get; init; } = null; - - internal int? WorkTestSleepInterval { get; init; } = null; - - internal bool DisableTerminationSignals { get; init; } = false; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using System.Reflection; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Tests that run the inproc testbed targeting COM lifetime. + /// + public class InprocTestbedTests + { + /// + /// The activation type to use when creating objects. + /// + public enum ActivationType + { + /// + /// Use the WinRT type name for activation via C++/WinRT object construction. + /// + ClassName, + + /// + /// Use the CLSID for activation via C++/WinRT `create_instance`. + /// + CoCreateInstance, + } + + /// + /// Control when the module will allow signal that it can be unloaded if all objects are released. + /// This does not affect the loader by taking additional references to the module. + /// + public enum UnloadBehavior + { + /// + /// Allows the unload check function to proceed with object count checks and unload when possible. + /// + Allow, + + /// + /// Prevents the unload check until just before COM is uninitialized. + /// + AtUninitialize, + + /// + /// Prevents the unload check at all times. + /// + Never, + } + + /// + /// Gets or sets the path to the inproc testbed executable. + /// + private string InprocTestbedPath { get; set; } + + /// + /// Gets or sets the string that contains the package identity to use for the tests. + /// + private string TargetPackageInformation { get; set; } + + /// + /// Setup done once before all the tests here. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + this.InprocTestbedPath = TestSetup.Parameters.InprocTestbedPath; + + if (string.IsNullOrWhiteSpace(this.InprocTestbedPath)) + { + string assemblyLocation = Assembly.GetExecutingAssembly().Location; + this.InprocTestbedPath = Path.Join(Path.GetDirectoryName(assemblyLocation), "..\\ComInprocTestbed\\ComInprocTestbed.exe"); + } + + if (TestSetup.Parameters.InprocTestbedUseTestPackage) + { + this.TargetPackageInformation = $"-pkg {Constants.ExeInstallerPackageId} -src {Constants.TestSourceName} -url {Constants.TestSourceUrl}"; + } + } + + /// + /// Executes the testbed as simply as possible to ensure integrations. + /// + [Test] + public void DefaultTest() + { + this.RunInprocTestbed(new TestbedParameters() { WorkTestSleepInterval = 1000 }); + } + + /// + /// Tests using the CLSID with CoCreateInstance. + /// + /// Control whether COM should be uninitialized at the end of the process. + /// Set the unload behavior for the test. + /// Sets the number of milliseconds to sleep between each work/test iteration. + [Test] + [TestCase(false, UnloadBehavior.AtUninitialize)] + [TestCase(false, UnloadBehavior.Never)] + [TestCase(true, UnloadBehavior.Allow, 1000)] + [TestCase(true, UnloadBehavior.Never)] + public void CLSID_Tests(bool leakCOM, UnloadBehavior unloadBehavior, int? workTestSleep = null) + { + this.RunInprocTestbed(new TestbedParameters() + { + ActivationType = ActivationType.CoCreateInstance, + LeakCOM = leakCOM, + UnloadBehavior = unloadBehavior, + Iterations = 10, + WorkTestSleepInterval = workTestSleep, + }); + } + + /// + /// Tests using C++/WinRT object activation through the type name. + /// + /// Control whether the C++/WinRT factory cache will be cleared between iterations. + /// Control whether COM should be uninitialized at the end of the process. + /// Set the unload behavior for the test. + /// Sets the number of milliseconds to sleep between each work/test iteration. + [Test] + [TestCase(false, false, UnloadBehavior.AtUninitialize)] + [TestCase(false, false, UnloadBehavior.Never)] + [TestCase(false, true, UnloadBehavior.Allow)] + [TestCase(false, true, UnloadBehavior.Never)] + [TestCase(true, false, UnloadBehavior.AtUninitialize)] + [TestCase(true, false, UnloadBehavior.Never)] + [TestCase(true, true, UnloadBehavior.Allow, 1000)] + [TestCase(true, true, UnloadBehavior.Never)] + public void TypeName_Tests(bool freeCachedFactories, bool leakCOM, UnloadBehavior unloadBehavior, int? workTestSleep = null) + { + this.RunInprocTestbed(new TestbedParameters() + { + ActivationType = ActivationType.ClassName, + ClearFactories = freeCachedFactories, + LeakCOM = leakCOM, + UnloadBehavior = unloadBehavior, + Iterations = 10, + WorkTestSleepInterval = workTestSleep, + }); + } + + /// + /// Tests that disable the termination signal handling. + /// + /// Control whether the module should listen to termination signals. + /// Set the unload behavior for the test. + /// Sets the number of milliseconds to sleep between each work/test iteration. + [Test] + [TestCase(true, UnloadBehavior.Allow, 1000)] + [TestCase(true, UnloadBehavior.Never)] + [TestCase(false, UnloadBehavior.Allow, 1000)] + [TestCase(false, UnloadBehavior.Never)] + public void TerminationSignal_Tests(bool disableTerminationSignals, UnloadBehavior unloadBehavior, int? workTestSleep = null) + { + this.RunInprocTestbed(new TestbedParameters() + { + ActivationType = ActivationType.CoCreateInstance, + DisableTerminationSignals = disableTerminationSignals, + UnloadBehavior = unloadBehavior, + Iterations = 10, + WorkTestSleepInterval = workTestSleep, + }); + } + + private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000) + { + string builtParameters = string.Empty; + + if (parameters.ActivationType != null) + { + builtParameters += $"-activation {parameters.ActivationType} "; + } + + if (!parameters.ClearFactories) + { + builtParameters += "-keep-factories "; + } + + if (parameters.LeakCOM) + { + builtParameters += "-leak-com "; + } + + if (parameters.UnloadBehavior != null) + { + builtParameters += $"-unload {parameters.UnloadBehavior} "; + } + + if (parameters.Test != null) + { + builtParameters += $"-test {parameters.Test} "; + } + + if (parameters.Iterations != null) + { + builtParameters += $"-itr {parameters.Iterations} "; + } + + if (parameters.WorkTestSleepInterval != null) + { + builtParameters += $"-work-test-sleep {parameters.WorkTestSleepInterval} "; + } + + if (parameters.DisableTerminationSignals) + { + builtParameters += $"-no-term "; + } + + var result = TestCommon.RunProcess(this.InprocTestbedPath, this.TargetPackageInformation, builtParameters, null, timeout, true, null); + Assert.AreEqual(0, result.ExitCode); + } + + /// + /// The parameters to provide for running tests. + /// + private class TestbedParameters + { + internal ActivationType? ActivationType { get; init; } = null; + + internal bool ClearFactories { get; init; } = true; + + internal bool LeakCOM { get; init; } = false; + + internal UnloadBehavior? UnloadBehavior { get; init; } = null; + + internal string Test { get; init; } = "unload_check"; + + internal int? Iterations { get; init; } = null; + + internal int? WorkTestSleepInterval { get; init; } = null; + + internal bool DisableTerminationSignals { get; init; } = false; + } + } +} diff --git a/src/AppInstallerCLIE2ETests/InstallCommand.cs b/src/AppInstallerCLIE2ETests/InstallCommand.cs index 5c56cb56b8..cdbc171082 100644 --- a/src/AppInstallerCLIE2ETests/InstallCommand.cs +++ b/src/AppInstallerCLIE2ETests/InstallCommand.cs @@ -1,866 +1,866 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System; - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test install command. - /// - public class InstallCommand : BaseCommand - { - /// - /// One time set up. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - WinGetSettingsHelper.ConfigureFeature("sourcePriority", true); - } - - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - // Try clean up TestExeInstaller for failure cases where cleanup is not successful - TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); - } - - /// - /// Test package doesn't exist. - /// - [Test] - public void InstallAppDoesNotExist() - { - var result = TestCommon.RunAICLICommand("install", "DoesNotExist"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No package found matching input criteria.")); - } - - /// - /// Test multiple matches found. - /// - [Test] - public void InstallWithMultipleAppsMatchingQuery() - { - var result = TestCommon.RunAICLICommand("install", "TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_MULTIPLE_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("Multiple packages found matching input criteria. Please refine the input.")); - } - - /// - /// Test install exe. - /// - [Test] - public void InstallExe() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); - } - - /// - /// Test inapplicable os version. - /// - [Test] - public void InstallExeWithInsufficientMinOsVersion() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); - - // MinOSVersion is moved to installer level, the check is performed during installer selection - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); - } - - /// - /// Test install exe hash mismatch. - /// - [Test] - public void InstallExeWithHashMismatch() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"TestExeSha256Mismatch --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); - Assert.True(result.StdOut.Contains("Installer hash does not match")); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); - } - - /// - /// Test install inno. - /// - [Test] - public void InstallWithInno() - { - // Install test inno, manifest does not provide silent switch, we should be populating the default - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"TestInnoInstaller --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/VERYSILENT")); - } - - /// - /// Test install burn. - /// - [Test] - public void InstallBurn() - { - // Install test burn, manifest does not provide silent switch, we should be populating the default - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"TestBurnInstaller --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/quiet")); - } - - /// - /// Test install nullsoft. - /// - [Test] - public void InstallNullSoft() - { - // Install test Nullsoft, manifest does not provide silent switch, we should be populating the default - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"TestNullsoftInstaller --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/S")); - } - - /// - /// Test install msi. - /// - [Test] - public void InstallMSI() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"TestMsiInstaller --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); - } - - /// - /// Test install msix. - /// - [Test] - public void InstallMSIX() - { - var result = TestCommon.RunAICLICommand("install", $"TestMsixInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test install msix machine scope. - /// - [Test] - public void InstallMSIXMachineScope() - { - // TODO: Provision and Deprovision api not supported in build server. - Assert.Ignore(); - - var result = TestCommon.RunAICLICommand("install", $"TestMsixInstaller --scope machine"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); - } - - /// - /// Test install msix with signature hash. - /// - [Test] - public void InstallMSIXWithSignature() - { - var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test install msix with signature hash machine scope. - /// - [Test] - public void InstallMSIXWithSignatureMachineScope() - { - // TODO: Provision and Deprovision api not supported in build server. - Assert.Ignore(); - - var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash --scope machine"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); - } - - /// - /// Test msix hash mismatch. - /// - [Test] - public void InstallMSIXWithSignatureHashMismatch() - { - var result = TestCommon.RunAICLICommand("install", $"TestMsixSignatureHashMismatch"); - Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); - Assert.True(result.StdOut.Contains("Installer hash does not match")); - Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test install with alternate source failure. - /// - [Test] - public void InstallExeWithAlternateSourceFailure() - { - TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); - - try - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); - Assert.AreEqual(unchecked((int)0x80070002), result.ExitCode); - Assert.True(result.StdOut.Contains("Failed when searching source: failSearch")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); - Assert.False(result.StdOut.Contains("Successfully installed")); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); - } - finally - { - this.ResetTestSource(); - } - } - - /// - /// Test install portable package. - /// - [Test] - public void InstallPortableExe() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test install portable package with command. - /// - [Test] - public void InstallPortableExeWithCommand() - { - var installDir = TestCommon.GetRandomTestDir(); - string packageId, commandAlias, fileName, productCode; - packageId = "AppInstallerTest.TestPortableExeWithCommand"; - productCode = packageId + "_" + Constants.TestSourceIdentifier; - fileName = "AppInstallerTestExeInstaller.exe"; - commandAlias = "testCommand.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(installDir, commandAlias, fileName, productCode, true); - } - - /// - /// Test install portable package with rename. - /// - [Test] - public void InstallPortableExeWithRename() - { - var installDir = TestCommon.GetRandomTestDir(); - string packageId, productCode, renameArgValue; - packageId = "AppInstallerTest.TestPortableExeWithCommand"; - productCode = packageId + "_" + Constants.TestSourceIdentifier; - renameArgValue = "testRename.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir} --rename {renameArgValue}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(installDir, renameArgValue, renameArgValue, productCode, true); - } - - /// - /// Test install portable package invalid rename. - /// - [Test] - public void InstallPortableInvalidRename() - { - var installDir = TestCommon.GetRandomTestDir(); - string packageId, renameArgValue; - packageId = "AppInstallerTest.TestPortableExeWithCommand"; - renameArgValue = "test?"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir} --rename {renameArgValue}"); - Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("The specified filename is not a valid filename")); - } - - /// - /// Test install portable package with reserve names. - /// - [Test] - public void InstallPortableReservedNames() - { - var installDir = TestCommon.GetRandomTestDir(); - string packageId, renameArgValue; - packageId = "AppInstallerTest.TestPortableExeWithCommand"; - renameArgValue = "CON"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir} --rename {renameArgValue}"); - Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("The specified filename is not a valid filename")); - } - - /// - /// Test install portable package to an existing directory. - /// - [Test] - public void InstallPortableToExistingDirectory() - { - var installDir = TestCommon.GetRandomTestDir(); - var existingDir = Path.Combine(installDir, "testDirectory"); - Directory.CreateDirectory(existingDir); - - string packageId, commandAlias, fileName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {existingDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); - } - - /// - /// Test install portable package. Symlink is a directory. - /// - [Test] - public void InstallPortableFailsWithCleanup() - { - string packageId, commandAlias; - packageId = "AppInstallerTest.TestPortableExe"; - commandAlias = "AppInstallerTestExeInstaller.exe"; - - // Create a directory with the same name as the symlink in order to cause install to fail. - string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); - string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); - - Directory.CreateDirectory(conflictDirectory); - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - - // Remove directory prior to assertions as this will impact other tests if assertions fail. - Directory.Delete(conflictDirectory, true); - - Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Unable to create symlink")); - } - - /// - /// Test reinstalling portable package. - /// - [Test] - public void ReinstallPortable() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); - string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); - - // Clean first install should not display file overwrite message. - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.False(result.StdOut.Contains($"Overwriting existing file: {symlinkPath}")); - - // Perform second install and verify that file overwrite message is displayed. - var result2 = TestCommon.RunAICLICommand("install", $"{packageId} --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); - Assert.True(result2.StdOut.Contains("Successfully installed")); - - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test installing portable package user scope. - /// - [Test] - public void InstallPortable_UserScope() - { - string installDir = TestCommon.GetRandomTestDir(); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, installDir); - - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} --scope user"); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test install portable package machine scope. - /// - [Test] - public void InstallPortable_MachineScope() - { - string installDir = TestCommon.GetRandomTestDir(); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); - - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} --scope machine"); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); - } - - /// - /// Test install portable package with settings set to user install scope. - /// - [Test] - public void InstallPortable_InstallScopePreference_User() - { - string installDir = TestCommon.GetRandomTestDir(); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, installDir); - WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallBehaviorScope, "user"); - - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test install portable package with settings set to machine install scope. - /// - [Test] - public void InstallPortable_InstallScopePreference_Machine() - { - string installDir = TestCommon.GetRandomTestDir(); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); - WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallBehaviorScope, "machine"); - - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); - } - - /// - /// Test install zip exe. - /// - [Test] - public void InstallZip_Exe() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithExe --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); - } - - /// - /// Test install zip portable. - /// - [Test] - public void InstallZip_Portable() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestZipInstallerWithPortable"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = "TestPortable.exe"; - fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.User); - } - - /// - /// Test install zip portable with binaries that depend on PATH variable. - /// - [Test] - public void InstallZip_ArchivePortableWithBinariesDependentOnPath() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.ArchivePortableWithBinariesDependentOnPath"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = "TestPortable.exe"; - fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.User, true); - } - - /// - /// Test install zip with invalid relative file path. - /// - [Test] - public void InstallZipWithInvalidRelativeFilePath() - { - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInvalidRelativePath"); - Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Invalid relative file path to the nested installer; path points to a location outside of the install directory")); - } - - /// - /// Test install zip msi. - /// - [Test] - public void InstallZipWithMsi() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithMsi --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); - } - - /// - /// Test install zip msix. - /// - [Test] - public void InstallZipWithMsix() - { - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithMsix"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test install zip exe by extracting with tar. - /// - [Test] - public void InstallZip_ExtractWithTar() - { - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.ArchiveExtractionMethod, "tar"); - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithExe --silent -l {installDir}"); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.ArchiveExtractionMethod, string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); - } - - /// - /// Test install an installed package and convert to upgrade. - /// - [Test] - public void InstallExeFoundExistingConvertToUpgrade() - { - var baseDir = TestCommon.GetRandomTestDir(); - var baseResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 1.0.0.0 --silent -l {baseDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, baseResult.ExitCode); - Assert.True(baseResult.StdOut.Contains("Successfully installed")); - - // Install will convert to upgrade - var upgradeDir = TestCommon.GetRandomTestDir(); - var upgradeResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {upgradeDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); - Assert.True(upgradeResult.StdOut.Contains("Trying to upgrade the installed package...")); - Assert.True(upgradeResult.StdOut.Contains("Successfully installed")); - - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(baseDir)); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(upgradeDir, "/Version 2.0.0.0")); - } - - /// - /// Test install an installed package without an available upgrade. - /// - [Test] - public void InstallExeFoundExistingConvertToUpgradeNoAvailableUpgrade() - { - var baseDir = TestCommon.GetRandomTestDir(); - var baseResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 2.0.0.0 --silent -l {baseDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, baseResult.ExitCode); - Assert.True(baseResult.StdOut.Contains("Successfully installed")); - - // Install will convert to upgrade - var upgradeDir = TestCommon.GetRandomTestDir(); - var upgradeResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {upgradeDir}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_UPDATE_NOT_APPLICABLE, upgradeResult.ExitCode); - Assert.True(upgradeResult.StdOut.Contains("Trying to upgrade the installed package...")); - Assert.True(upgradeResult.StdOut.Contains("No available upgrade")); - - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(baseDir)); - } - - /// - /// Test force installing a package. - /// - [Test] - public void InstallExeWithLatestInstalledWithForce() - { - var baseDir = TestCommon.GetRandomTestDir(); - var baseResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 2.0.0.0 --silent -l {baseDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, baseResult.ExitCode); - Assert.True(baseResult.StdOut.Contains("Successfully installed")); - - // Install will not convert to upgrade - var installDir = TestCommon.GetRandomTestDir(); - var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 1.0.0.0 --silent -l {installDir} --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); - Assert.True(installResult.StdOut.Contains("Successfully installed")); - - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(baseDir)); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); - } - - /// - /// Test install a package with an invalid Windows Feature dependency. - /// - [Test] - public void InstallWithWindowsFeatureDependency_FeatureNotFound() - { - var testDir = TestCommon.GetRandomTestDir(); - var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.WindowsFeature -l {testDir}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_DEPENDENCIES, installResult.ExitCode); - Assert.True(installResult.StdOut.Contains("The feature [invalidFeature] was not found.")); - Assert.True(installResult.StdOut.Contains("Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'.")); - } - - /// - /// Test install a package with a Windows Feature dependency using the force argument. - /// - [Test] - public void InstallWithWindowsFeatureDependency_Force() - { - var testDir = TestCommon.GetRandomTestDir(); - var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.WindowsFeature --silent --force -l {testDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); - Assert.True(installResult.StdOut.Contains("Failed to enable Windows Feature dependencies; proceeding due to --force")); - Assert.True(installResult.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(testDir)); - } - - /// - /// Test install a package with a package dependency that requires the PATH environment variable to be refreshed between dependency installs. - /// - [Test] - public void InstallWithPackageDependency_RefreshPathVariable() - { - var testDir = TestCommon.GetRandomTestDir(); - string installDir = TestCommon.GetPortablePackagesDirectory(); - var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.PackageDependencyRequiresPathRefresh -l {testDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); - Assert.True(installResult.StdOut.Contains("Successfully installed")); - - // Portable package is used as a dependency. Ensure that it is installed and cleaned up successfully. - string portablePackageId, commandAlias, fileName, packageDirName, productCode; - portablePackageId = "AppInstallerTest.TestPortableExeWithCommand"; - packageDirName = productCode = portablePackageId + "_" + Constants.TestSourceIdentifier; - fileName = "AppInstallerTestExeInstaller.exe"; - commandAlias = "testCommand.exe"; - - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(testDir)); - } - - /// - /// Test install a package with a package dependency and specify dependencies only. - /// - [Test] - public void InstallWithPackageDependency_DependenciesOnly() - { - var testDir = TestCommon.GetRandomTestDir(); - string installDir = TestCommon.GetPortablePackagesDirectory(); - var installResult = TestCommon.RunAICLICommand("install", $"-q AppInstallerTest.PackageDependencyRequiresPathRefresh -l {testDir} --dependencies-only"); - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); - Assert.True(installResult.StdOut.Contains("Installing dependencies only. The package itself will not be installed.")); - Assert.True(installResult.StdOut.Contains("Successfully installed")); - - // Portable package is used as a dependency. Ensure that it is installed and cleaned up successfully. - string portablePackageId, commandAlias, fileName, packageDirName, productCode; - portablePackageId = "AppInstallerTest.TestPortableExeWithCommand"; - packageDirName = productCode = portablePackageId + "_" + Constants.TestSourceIdentifier; - fileName = "AppInstallerTestExeInstaller.exe"; - commandAlias = "testCommand.exe"; - - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(testDir)); - } - - /// - /// Test install a package using a specific installer type. - /// - [Test] - public void InstallWithInstallerTypeArgument() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMultipleInstallers --silent -l {installDir} --installer-type exe"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); - } - - /// - /// Test install package with installer type preference settings. - /// - [Test] - public void InstallWithInstallerTypePreference() - { - string[] installerTypePreference = { "nullsoft" }; - WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallerTypes, installerTypePreference); - - string installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMultipleInstallers --silent -l {installDir}"); - - // Reset installer type preferences. - WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallerTypes, Array.Empty()); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir), "/S"); - } - - /// - /// Test install package with installer type requirement settings. - /// - [Test] - public void InstallWithInstallerTypeRequirement() - { - string[] installerTypeRequirement = { "inno" }; - WinGetSettingsHelper.ConfigureInstallBehaviorRequirements(Constants.InstallerTypes, installerTypeRequirement); - - string installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMultipleInstallers --silent -l {installDir}"); - - // Reset installer type requirements. - WinGetSettingsHelper.ConfigureInstallBehaviorRequirements(Constants.InstallerTypes, Array.Empty()); - - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); - Assert.True(result.StdOut.Contains("No applicable installer found; see logs for more details.")); - } - - /// - /// This test flow is intended to test an EXE that actually installs an MSIX internally, and whose name+publisher - /// information resembles an existing installation. Given this, the goal is to get correlation to stick to the - /// MSIX rather than the ARP entry that we would match with in the absence of the package family name being present. - /// - [Test] - public void InstallExeThatInstallsMSIX() - { - string targetPackageIdentifier = "AppInstallerTest.TestExeInstallerInstallsMSIX"; - string fakeProductCode = "e35f5799-cce3-41fd-886c-c36fcb7104fe"; - - // Insert fake ARP entry as if a non-MSIX version of the package is already installed. - // The name here must not match the normalized name of the package, but be close enough to meet - // the confidence requirements for correlation after an install operation (so we drop one word here). - TestCommon.CreateARPEntry(fakeProductCode, new - { - DisplayName = "EXE Installer that Installs MSIX", - Publisher = "AppInstallerTest", - DisplayVersion = "1.0.0", - }); - - // We should not find it before installing because the normalized name doesn't match - var result = TestCommon.RunAICLICommand("list", targetPackageIdentifier); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - - // Add the MSIX to simulate an installer doing it - TestCommon.InstallMsix(TestIndex.MsixInstaller); - - // Install our exe that "installs" the MSIX - result = TestCommon.RunAICLICommand("install", $"{targetPackageIdentifier} --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // We should find the package now, and it should be correlated to the MSIX (although we don't actually know that from this probe) - result = TestCommon.RunAICLICommand("list", targetPackageIdentifier); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // Remove the MSIX outside of winget's knowledge to keep the tracking data. - TestCommon.RemoveMsix(Constants.MsixInstallerName); - - // We should not find the package now that the MSIX is gone, confirming that it was correlated - result = TestCommon.RunAICLICommand("list", targetPackageIdentifier); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - - TestCommon.RemoveARPEntry(fakeProductCode); - } - - /// - /// Test install source priority. - /// - [Test] - public void InstallExeWithSourcePriority() - { - // This test source always returns a single package from search - TestCommon.RunAICLICommand("source add", "dummyPackage \"{ \"\"ContainsPackage\"\": true }\" Microsoft.Test.Configurable --header \"{}\""); - - try - { - // Attempt install with equal (default) priorities - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_MULTIPLE_APPLICATIONS_FOUND, result.ExitCode); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); - - // Change the priority of the primary test source to be higher - TestCommon.RunAICLICommand("source edit", $"{Constants.TestSourceName} --priority 1"); - - result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); - Assert.True(result.StdOut.Contains("Successfully installed")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); - } - finally - { - this.ResetTestSource(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test install command. + /// + public class InstallCommand : BaseCommand + { + /// + /// One time set up. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("sourcePriority", true); + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + // Try clean up TestExeInstaller for failure cases where cleanup is not successful + TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); + } + + /// + /// Test package doesn't exist. + /// + [Test] + public void InstallAppDoesNotExist() + { + var result = TestCommon.RunAICLICommand("install", "DoesNotExist"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No package found matching input criteria.")); + } + + /// + /// Test multiple matches found. + /// + [Test] + public void InstallWithMultipleAppsMatchingQuery() + { + var result = TestCommon.RunAICLICommand("install", "TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_MULTIPLE_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("Multiple packages found matching input criteria. Please refine the input.")); + } + + /// + /// Test install exe. + /// + [Test] + public void InstallExe() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); + } + + /// + /// Test inapplicable os version. + /// + [Test] + public void InstallExeWithInsufficientMinOsVersion() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"InapplicableOsVersion --silent -l {installDir}"); + + // MinOSVersion is moved to installer level, the check is performed during installer selection + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + /// + /// Test install exe hash mismatch. + /// + [Test] + public void InstallExeWithHashMismatch() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"TestExeSha256Mismatch --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); + Assert.True(result.StdOut.Contains("Installer hash does not match")); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + /// + /// Test install inno. + /// + [Test] + public void InstallWithInno() + { + // Install test inno, manifest does not provide silent switch, we should be populating the default + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"TestInnoInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/VERYSILENT")); + } + + /// + /// Test install burn. + /// + [Test] + public void InstallBurn() + { + // Install test burn, manifest does not provide silent switch, we should be populating the default + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"TestBurnInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/quiet")); + } + + /// + /// Test install nullsoft. + /// + [Test] + public void InstallNullSoft() + { + // Install test Nullsoft, manifest does not provide silent switch, we should be populating the default + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"TestNullsoftInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/S")); + } + + /// + /// Test install msi. + /// + [Test] + public void InstallMSI() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"TestMsiInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); + } + + /// + /// Test install msix. + /// + [Test] + public void InstallMSIX() + { + var result = TestCommon.RunAICLICommand("install", $"TestMsixInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test install msix machine scope. + /// + [Test] + public void InstallMSIXMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + var result = TestCommon.RunAICLICommand("install", $"TestMsixInstaller --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + + /// + /// Test install msix with signature hash. + /// + [Test] + public void InstallMSIXWithSignature() + { + var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test install msix with signature hash machine scope. + /// + [Test] + public void InstallMSIXWithSignatureMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + var result = TestCommon.RunAICLICommand("install", $"TestMsixWithSignatureHash --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + + /// + /// Test msix hash mismatch. + /// + [Test] + public void InstallMSIXWithSignatureHashMismatch() + { + var result = TestCommon.RunAICLICommand("install", $"TestMsixSignatureHashMismatch"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALLER_HASH_MISMATCH, result.ExitCode); + Assert.True(result.StdOut.Contains("Installer hash does not match")); + Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test install with alternate source failure. + /// + [Test] + public void InstallExeWithAlternateSourceFailure() + { + TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); + + try + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); + Assert.AreEqual(unchecked((int)0x80070002), result.ExitCode); + Assert.True(result.StdOut.Contains("Failed when searching source: failSearch")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); + Assert.False(result.StdOut.Contains("Successfully installed")); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + finally + { + this.ResetTestSource(); + } + } + + /// + /// Test install portable package. + /// + [Test] + public void InstallPortableExe() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test install portable package with command. + /// + [Test] + public void InstallPortableExeWithCommand() + { + var installDir = TestCommon.GetRandomTestDir(); + string packageId, commandAlias, fileName, productCode; + packageId = "AppInstallerTest.TestPortableExeWithCommand"; + productCode = packageId + "_" + Constants.TestSourceIdentifier; + fileName = "AppInstallerTestExeInstaller.exe"; + commandAlias = "testCommand.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(installDir, commandAlias, fileName, productCode, true); + } + + /// + /// Test install portable package with rename. + /// + [Test] + public void InstallPortableExeWithRename() + { + var installDir = TestCommon.GetRandomTestDir(); + string packageId, productCode, renameArgValue; + packageId = "AppInstallerTest.TestPortableExeWithCommand"; + productCode = packageId + "_" + Constants.TestSourceIdentifier; + renameArgValue = "testRename.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir} --rename {renameArgValue}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(installDir, renameArgValue, renameArgValue, productCode, true); + } + + /// + /// Test install portable package invalid rename. + /// + [Test] + public void InstallPortableInvalidRename() + { + var installDir = TestCommon.GetRandomTestDir(); + string packageId, renameArgValue; + packageId = "AppInstallerTest.TestPortableExeWithCommand"; + renameArgValue = "test?"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir} --rename {renameArgValue}"); + Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("The specified filename is not a valid filename")); + } + + /// + /// Test install portable package with reserve names. + /// + [Test] + public void InstallPortableReservedNames() + { + var installDir = TestCommon.GetRandomTestDir(); + string packageId, renameArgValue; + packageId = "AppInstallerTest.TestPortableExeWithCommand"; + renameArgValue = "CON"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {installDir} --rename {renameArgValue}"); + Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("The specified filename is not a valid filename")); + } + + /// + /// Test install portable package to an existing directory. + /// + [Test] + public void InstallPortableToExistingDirectory() + { + var installDir = TestCommon.GetRandomTestDir(); + var existingDir = Path.Combine(installDir, "testDirectory"); + Directory.CreateDirectory(existingDir); + + string packageId, commandAlias, fileName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -l {existingDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); + } + + /// + /// Test install portable package. Symlink is a directory. + /// + [Test] + public void InstallPortableFailsWithCleanup() + { + string packageId, commandAlias; + packageId = "AppInstallerTest.TestPortableExe"; + commandAlias = "AppInstallerTestExeInstaller.exe"; + + // Create a directory with the same name as the symlink in order to cause install to fail. + string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); + string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); + + Directory.CreateDirectory(conflictDirectory); + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + + // Remove directory prior to assertions as this will impact other tests if assertions fail. + Directory.Delete(conflictDirectory, true); + + Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Unable to create symlink")); + } + + /// + /// Test reinstalling portable package. + /// + [Test] + public void ReinstallPortable() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); + string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); + + // Clean first install should not display file overwrite message. + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.False(result.StdOut.Contains($"Overwriting existing file: {symlinkPath}")); + + // Perform second install and verify that file overwrite message is displayed. + var result2 = TestCommon.RunAICLICommand("install", $"{packageId} --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Successfully installed")); + + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test installing portable package user scope. + /// + [Test] + public void InstallPortable_UserScope() + { + string installDir = TestCommon.GetRandomTestDir(); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, installDir); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} --scope user"); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test install portable package machine scope. + /// + [Test] + public void InstallPortable_MachineScope() + { + string installDir = TestCommon.GetRandomTestDir(); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} --scope machine"); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); + } + + /// + /// Test install portable package with settings set to user install scope. + /// + [Test] + public void InstallPortable_InstallScopePreference_User() + { + string installDir = TestCommon.GetRandomTestDir(); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, installDir); + WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallBehaviorScope, "user"); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageUserRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test install portable package with settings set to machine install scope. + /// + [Test] + public void InstallPortable_InstallScopePreference_Machine() + { + string installDir = TestCommon.GetRandomTestDir(); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); + WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallBehaviorScope, "machine"); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); + } + + /// + /// Test install zip exe. + /// + [Test] + public void InstallZip_Exe() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithExe --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); + } + + /// + /// Test install zip portable. + /// + [Test] + public void InstallZip_Portable() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestZipInstallerWithPortable"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = "TestPortable.exe"; + fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.User); + } + + /// + /// Test install zip portable with binaries that depend on PATH variable. + /// + [Test] + public void InstallZip_ArchivePortableWithBinariesDependentOnPath() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.ArchivePortableWithBinariesDependentOnPath"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = "TestPortable.exe"; + fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.User, true); + } + + /// + /// Test install zip with invalid relative file path. + /// + [Test] + public void InstallZipWithInvalidRelativeFilePath() + { + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInvalidRelativePath"); + Assert.AreNotEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Invalid relative file path to the nested installer; path points to a location outside of the install directory")); + } + + /// + /// Test install zip msi. + /// + [Test] + public void InstallZipWithMsi() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithMsi --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); + } + + /// + /// Test install zip msix. + /// + [Test] + public void InstallZipWithMsix() + { + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithMsix"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test install zip exe by extracting with tar. + /// + [Test] + public void InstallZip_ExtractWithTar() + { + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.ArchiveExtractionMethod, "tar"); + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithExe --silent -l {installDir}"); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.ArchiveExtractionMethod, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); + } + + /// + /// Test install an installed package and convert to upgrade. + /// + [Test] + public void InstallExeFoundExistingConvertToUpgrade() + { + var baseDir = TestCommon.GetRandomTestDir(); + var baseResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 1.0.0.0 --silent -l {baseDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, baseResult.ExitCode); + Assert.True(baseResult.StdOut.Contains("Successfully installed")); + + // Install will convert to upgrade + var upgradeDir = TestCommon.GetRandomTestDir(); + var upgradeResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {upgradeDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); + Assert.True(upgradeResult.StdOut.Contains("Trying to upgrade the installed package...")); + Assert.True(upgradeResult.StdOut.Contains("Successfully installed")); + + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(baseDir)); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(upgradeDir, "/Version 2.0.0.0")); + } + + /// + /// Test install an installed package without an available upgrade. + /// + [Test] + public void InstallExeFoundExistingConvertToUpgradeNoAvailableUpgrade() + { + var baseDir = TestCommon.GetRandomTestDir(); + var baseResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 2.0.0.0 --silent -l {baseDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, baseResult.ExitCode); + Assert.True(baseResult.StdOut.Contains("Successfully installed")); + + // Install will convert to upgrade + var upgradeDir = TestCommon.GetRandomTestDir(); + var upgradeResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {upgradeDir}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_UPDATE_NOT_APPLICABLE, upgradeResult.ExitCode); + Assert.True(upgradeResult.StdOut.Contains("Trying to upgrade the installed package...")); + Assert.True(upgradeResult.StdOut.Contains("No available upgrade")); + + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(baseDir)); + } + + /// + /// Test force installing a package. + /// + [Test] + public void InstallExeWithLatestInstalledWithForce() + { + var baseDir = TestCommon.GetRandomTestDir(); + var baseResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 2.0.0.0 --silent -l {baseDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, baseResult.ExitCode); + Assert.True(baseResult.StdOut.Contains("Successfully installed")); + + // Install will not convert to upgrade + var installDir = TestCommon.GetRandomTestDir(); + var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller -v 1.0.0.0 --silent -l {installDir} --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("Successfully installed")); + + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(baseDir)); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); + } + + /// + /// Test install a package with an invalid Windows Feature dependency. + /// + [Test] + public void InstallWithWindowsFeatureDependency_FeatureNotFound() + { + var testDir = TestCommon.GetRandomTestDir(); + var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.WindowsFeature -l {testDir}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_DEPENDENCIES, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("The feature [invalidFeature] was not found.")); + Assert.True(installResult.StdOut.Contains("Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'.")); + } + + /// + /// Test install a package with a Windows Feature dependency using the force argument. + /// + [Test] + public void InstallWithWindowsFeatureDependency_Force() + { + var testDir = TestCommon.GetRandomTestDir(); + var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.WindowsFeature --silent --force -l {testDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("Failed to enable Windows Feature dependencies; proceeding due to --force")); + Assert.True(installResult.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(testDir)); + } + + /// + /// Test install a package with a package dependency that requires the PATH environment variable to be refreshed between dependency installs. + /// + [Test] + public void InstallWithPackageDependency_RefreshPathVariable() + { + var testDir = TestCommon.GetRandomTestDir(); + string installDir = TestCommon.GetPortablePackagesDirectory(); + var installResult = TestCommon.RunAICLICommand("install", $"AppInstallerTest.PackageDependencyRequiresPathRefresh -l {testDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("Successfully installed")); + + // Portable package is used as a dependency. Ensure that it is installed and cleaned up successfully. + string portablePackageId, commandAlias, fileName, packageDirName, productCode; + portablePackageId = "AppInstallerTest.TestPortableExeWithCommand"; + packageDirName = productCode = portablePackageId + "_" + Constants.TestSourceIdentifier; + fileName = "AppInstallerTestExeInstaller.exe"; + commandAlias = "testCommand.exe"; + + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(testDir)); + } + + /// + /// Test install a package with a package dependency and specify dependencies only. + /// + [Test] + public void InstallWithPackageDependency_DependenciesOnly() + { + var testDir = TestCommon.GetRandomTestDir(); + string installDir = TestCommon.GetPortablePackagesDirectory(); + var installResult = TestCommon.RunAICLICommand("install", $"-q AppInstallerTest.PackageDependencyRequiresPathRefresh -l {testDir} --dependencies-only"); + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("Installing dependencies only. The package itself will not be installed.")); + Assert.True(installResult.StdOut.Contains("Successfully installed")); + + // Portable package is used as a dependency. Ensure that it is installed and cleaned up successfully. + string portablePackageId, commandAlias, fileName, packageDirName, productCode; + portablePackageId = "AppInstallerTest.TestPortableExeWithCommand"; + packageDirName = productCode = portablePackageId + "_" + Constants.TestSourceIdentifier; + fileName = "AppInstallerTestExeInstaller.exe"; + commandAlias = "testCommand.exe"; + + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(testDir)); + } + + /// + /// Test install a package using a specific installer type. + /// + [Test] + public void InstallWithInstallerTypeArgument() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMultipleInstallers --silent -l {installDir} --installer-type exe"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/execustom")); + } + + /// + /// Test install package with installer type preference settings. + /// + [Test] + public void InstallWithInstallerTypePreference() + { + string[] installerTypePreference = { "nullsoft" }; + WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallerTypes, installerTypePreference); + + string installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMultipleInstallers --silent -l {installDir}"); + + // Reset installer type preferences. + WinGetSettingsHelper.ConfigureInstallBehaviorPreferences(Constants.InstallerTypes, Array.Empty()); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir), "/S"); + } + + /// + /// Test install package with installer type requirement settings. + /// + [Test] + public void InstallWithInstallerTypeRequirement() + { + string[] installerTypeRequirement = { "inno" }; + WinGetSettingsHelper.ConfigureInstallBehaviorRequirements(Constants.InstallerTypes, installerTypeRequirement); + + string installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMultipleInstallers --silent -l {installDir}"); + + // Reset installer type requirements. + WinGetSettingsHelper.ConfigureInstallBehaviorRequirements(Constants.InstallerTypes, Array.Empty()); + + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICABLE_INSTALLER, result.ExitCode); + Assert.True(result.StdOut.Contains("No applicable installer found; see logs for more details.")); + } + + /// + /// This test flow is intended to test an EXE that actually installs an MSIX internally, and whose name+publisher + /// information resembles an existing installation. Given this, the goal is to get correlation to stick to the + /// MSIX rather than the ARP entry that we would match with in the absence of the package family name being present. + /// + [Test] + public void InstallExeThatInstallsMSIX() + { + string targetPackageIdentifier = "AppInstallerTest.TestExeInstallerInstallsMSIX"; + string fakeProductCode = "e35f5799-cce3-41fd-886c-c36fcb7104fe"; + + // Insert fake ARP entry as if a non-MSIX version of the package is already installed. + // The name here must not match the normalized name of the package, but be close enough to meet + // the confidence requirements for correlation after an install operation (so we drop one word here). + TestCommon.CreateARPEntry(fakeProductCode, new + { + DisplayName = "EXE Installer that Installs MSIX", + Publisher = "AppInstallerTest", + DisplayVersion = "1.0.0", + }); + + // We should not find it before installing because the normalized name doesn't match + var result = TestCommon.RunAICLICommand("list", targetPackageIdentifier); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + + // Add the MSIX to simulate an installer doing it + TestCommon.InstallMsix(TestIndex.MsixInstaller); + + // Install our exe that "installs" the MSIX + result = TestCommon.RunAICLICommand("install", $"{targetPackageIdentifier} --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // We should find the package now, and it should be correlated to the MSIX (although we don't actually know that from this probe) + result = TestCommon.RunAICLICommand("list", targetPackageIdentifier); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // Remove the MSIX outside of winget's knowledge to keep the tracking data. + TestCommon.RemoveMsix(Constants.MsixInstallerName); + + // We should not find the package now that the MSIX is gone, confirming that it was correlated + result = TestCommon.RunAICLICommand("list", targetPackageIdentifier); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + + TestCommon.RemoveARPEntry(fakeProductCode); + } + + /// + /// Test install source priority. + /// + [Test] + public void InstallExeWithSourcePriority() + { + // This test source always returns a single package from search + TestCommon.RunAICLICommand("source add", "dummyPackage \"{ \"\"ContainsPackage\"\": true }\" Microsoft.Test.Configurable --header \"{}\""); + + try + { + // Attempt install with equal (default) priorities + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_MULTIPLE_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + + // Change the priority of the primary test source to be higher + TestCommon.RunAICLICommand("source edit", $"{Constants.TestSourceName} --priority 1"); + + result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --silent -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); + Assert.True(result.StdOut.Contains("Successfully installed")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + finally + { + this.ResetTestSource(); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs b/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs index b3633a37d4..f336f935a5 100644 --- a/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/BaseInterop.cs @@ -1,9 +1,9 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { using System.Collections.Generic; @@ -12,25 +12,25 @@ namespace AppInstallerCLIE2ETests.Interop using Microsoft.Management.Deployment.Projection; using NUnit.Framework; - /// - /// Base interop class. + /// + /// Base interop class. /// public abstract class BaseInterop { - /// - /// Initializes a new instance of the class. - /// - /// Initializer. + /// + /// Initializes a new instance of the class. + /// + /// Initializer. public BaseInterop(IInstanceInitializer initializer) { this.TestFactory = new (initializer); - } - - /// - /// Gets the test factory. + } + + /// + /// Gets the test factory. /// - public WinGetProjectionFactory TestFactory { get; } - + public WinGetProjectionFactory TestFactory { get; } + /// /// Find one filtered package from a provided package catalog reference. /// diff --git a/src/AppInstallerCLIE2ETests/Interop/CheckInstalledStatusInterop.cs b/src/AppInstallerCLIE2ETests/Interop/CheckInstalledStatusInterop.cs index 44223eac3b..aaf23d2f53 100644 --- a/src/AppInstallerCLIE2ETests/Interop/CheckInstalledStatusInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/CheckInstalledStatusInterop.cs @@ -8,8 +8,8 @@ namespace AppInstallerCLIE2ETests.Interop { using System; using System.IO; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; using NUnit.Framework; diff --git a/src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs b/src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs index 814be7190a..0abe283e64 100644 --- a/src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/ConnectionValidationInterop.cs @@ -6,8 +6,8 @@ namespace AppInstallerCLIE2ETests.Interop { using System; - using System.IO; - using System.Runtime.InteropServices; + using System.IO; + using System.Runtime.InteropServices; using System.Threading.Tasks; using AppInstallerCLIE2ETests.Helpers; using Microsoft.Management.Deployment; @@ -60,7 +60,7 @@ public void TearDown() /// Verifies that the connection validation callback is invoked with a non-null server certificate, /// that returning Ok allows the connection to succeed, and that the received certificate matches /// the known test server certificate. - /// + /// /// The task. [Test] public async Task ConnectionValidationCallback_AllowsConnection_ConnectSucceeds() @@ -153,8 +153,8 @@ public void ConnectionValidationHandler_MicrosoftStore_PolicyDisabled_ThrowsBloc catalogRef.ConnectionValidationHandler = (args) => PackageCatalogConnectionValidationResult.Ok; }); - Assert.AreEqual( - Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, + Assert.AreEqual( + Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, ex.HResult, "Setting ConnectionValidationHandler on MicrosoftStore with policy disabled should return ERROR_BLOCKED_BY_POLICY."); } diff --git a/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs b/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs index 2283c4c520..a0313727cd 100644 --- a/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/FindPackagesInterop.cs @@ -1,37 +1,37 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop -{ - using System; +{ + using System; using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; - - /// - /// Test find package interop. + using NUnit.Framework; + + /// + /// Test find package interop. /// [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class FindPackagesInterop : BaseInterop { private PackageManager packageManager; - private PackageCatalogReference testSource; - - /// - /// Initializes a new instance of the class. - /// - /// Initializer. + private PackageCatalogReference testSource; + + /// + /// Initializes a new instance of the class. + /// + /// Initializer. public FindPackagesInterop(IInstanceInitializer initializer) : base(initializer) { } - /// - /// Set up. + /// + /// Set up. /// [SetUp] public void SetUp() @@ -40,8 +40,8 @@ public void SetUp() this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); } - /// - /// Test find package. no package. + /// + /// Test find package. no package. /// [Test] public void FindPackageDoesNotExist() @@ -53,8 +53,8 @@ public void FindPackageDoesNotExist() Assert.AreEqual(0, searchResult.Count); } - /// - /// Test find package with multiple match. + /// + /// Test find package with multiple match. /// [Test] public void FindPackagesMultipleMatchingQuery() @@ -66,13 +66,13 @@ public void FindPackagesMultipleMatchingQuery() Assert.AreEqual(2, searchResult.Count); } - /// - /// Test to find a package and verify the CatalogPackageMetadata COM output. + /// + /// Test to find a package and verify the CatalogPackageMetadata COM output. /// [Test] - public void FindPackagesVerifyDefaultLocaleFields() - { - var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); + public void FindPackagesVerifyDefaultLocaleFields() + { + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); Assert.AreEqual(1, searchResult.Count); @@ -110,102 +110,102 @@ public void FindPackagesVerifyDefaultLocaleFields() var agreement = packageAgreements[0]; Assert.AreEqual("testAgreementLabel", agreement.Label); Assert.AreEqual("testAgreementText", agreement.Text); - Assert.AreEqual("https://testAgreementUrl.net", agreement.Url); - - var documentations = catalogPackageMetadata.Documentations; - Assert.AreEqual(1, documentations.Count); - - var documentation = documentations[0]; - Assert.AreEqual("testDocumentLabel", documentation.DocumentLabel); - Assert.AreEqual("https://testDocumentUrl.com", documentation.DocumentUrl); - - var icons = catalogPackageMetadata.Icons; - Assert.AreEqual(1, icons.Count); - - var icon = icons[0]; - Assert.AreEqual("https://testIcon", icon.Url); - Assert.AreEqual(IconFileType.Ico, icon.FileType); - Assert.AreEqual(IconTheme.Default, icon.Theme); - Assert.AreEqual(IconResolution.Custom, icon.Resolution); - Assert.AreEqual(Convert.FromHexString("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123"), icon.Sha256); + Assert.AreEqual("https://testAgreementUrl.net", agreement.Url); + + var documentations = catalogPackageMetadata.Documentations; + Assert.AreEqual(1, documentations.Count); + + var documentation = documentations[0]; + Assert.AreEqual("testDocumentLabel", documentation.DocumentLabel); + Assert.AreEqual("https://testDocumentUrl.com", documentation.DocumentUrl); + + var icons = catalogPackageMetadata.Icons; + Assert.AreEqual(1, icons.Count); + + var icon = icons[0]; + Assert.AreEqual("https://testIcon", icon.Url); + Assert.AreEqual(IconFileType.Ico, icon.FileType); + Assert.AreEqual(IconTheme.Default, icon.Theme); + Assert.AreEqual(IconResolution.Custom, icon.Resolution); + Assert.AreEqual(Convert.FromHexString("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123"), icon.Sha256); } - /// - /// Verifies that an exception is thrown if the provided locale string is invalid. + /// + /// Verifies that an exception is thrown if the provided locale string is invalid. /// - [Test] - public void FindPackagesInvalidLocale() - { - var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); - var catalogPackage = searchResult[0].CatalogPackage; - var packageVersionId = catalogPackage.AvailableVersions[0]; + [Test] + public void FindPackagesInvalidLocale() + { + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); + var catalogPackage = searchResult[0].CatalogPackage; + var packageVersionId = catalogPackage.AvailableVersions[0]; var packageVersionInfo = catalogPackage.GetPackageVersionInfo(packageVersionId); - Assert.Throws(() => packageVersionInfo.GetCatalogPackageMetadata("badLocale")); + Assert.Throws(() => packageVersionInfo.GetCatalogPackageMetadata("badLocale")); } - /// - /// Verifies that the correct CatalogPackageMetadata is exposed when specifying a locale. + /// + /// Verifies that the correct CatalogPackageMetadata is exposed when specifying a locale. /// [Test] - public void FindPackagesGetCatalogPackageMetadataLocale() - { - var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.MultipleLocale"); + public void FindPackagesGetCatalogPackageMetadataLocale() + { + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.MultipleLocale"); Assert.AreEqual(1, searchResult.Count); - - var catalogPackage = searchResult[0].CatalogPackage; + + var catalogPackage = searchResult[0].CatalogPackage; var packageVersionId = catalogPackage.AvailableVersions[0]; var packageVersionInfo = catalogPackage.GetPackageVersionInfo(packageVersionId); - var catalogPackageMetadata = packageVersionInfo.GetCatalogPackageMetadata("zh-CN"); - - Assert.AreEqual("zh-CN", catalogPackageMetadata.Locale); - Assert.AreEqual("localeLicense", catalogPackageMetadata.License); - Assert.AreEqual("localePackageName", catalogPackageMetadata.PackageName); - Assert.AreEqual("localePublisher", catalogPackageMetadata.Publisher); - Assert.AreEqual("localeReleaseNotes", catalogPackageMetadata.ReleaseNotes); - Assert.AreEqual("https://localeReleaseNotesUrl.com", catalogPackageMetadata.ReleaseNotesUrl); - Assert.AreEqual("https://localePurchaseUrl.com", catalogPackageMetadata.PurchaseUrl); - + var catalogPackageMetadata = packageVersionInfo.GetCatalogPackageMetadata("zh-CN"); + + Assert.AreEqual("zh-CN", catalogPackageMetadata.Locale); + Assert.AreEqual("localeLicense", catalogPackageMetadata.License); + Assert.AreEqual("localePackageName", catalogPackageMetadata.PackageName); + Assert.AreEqual("localePublisher", catalogPackageMetadata.Publisher); + Assert.AreEqual("localeReleaseNotes", catalogPackageMetadata.ReleaseNotes); + Assert.AreEqual("https://localeReleaseNotesUrl.com", catalogPackageMetadata.ReleaseNotesUrl); + Assert.AreEqual("https://localePurchaseUrl.com", catalogPackageMetadata.PurchaseUrl); + var tags = catalogPackageMetadata.Tags; Assert.AreEqual(2, tags.Count); Assert.AreEqual("tag1", tags[0]); - Assert.AreEqual("tag2", tags[1]); - + Assert.AreEqual("tag2", tags[1]); + var packageAgreements = catalogPackageMetadata.Agreements; Assert.AreEqual(1, packageAgreements.Count); var agreement = packageAgreements[0]; Assert.AreEqual("localeAgreementLabel", agreement.Label); Assert.AreEqual("localeAgreement", agreement.Text); - Assert.AreEqual("https://localeAgreementUrl.net", agreement.Url); - - var documentations = catalogPackageMetadata.Documentations; - Assert.AreEqual(1, documentations.Count); - - var documentation = documentations[0]; - Assert.AreEqual("localeDocumentLabel", documentation.DocumentLabel); - Assert.AreEqual("https://localeDocumentUrl.com", documentation.DocumentUrl); - - var icons = catalogPackageMetadata.Icons; - Assert.AreEqual(1, icons.Count); - - var icon = icons[0]; - Assert.AreEqual("https://localeTestIcon", icon.Url); - Assert.AreEqual(IconFileType.Png, icon.FileType); - Assert.AreEqual(IconTheme.Light, icon.Theme); - Assert.AreEqual(IconResolution.Square32, icon.Resolution); - Assert.AreEqual(Convert.FromHexString("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321"), icon.Sha256); + Assert.AreEqual("https://localeAgreementUrl.net", agreement.Url); + + var documentations = catalogPackageMetadata.Documentations; + Assert.AreEqual(1, documentations.Count); + + var documentation = documentations[0]; + Assert.AreEqual("localeDocumentLabel", documentation.DocumentLabel); + Assert.AreEqual("https://localeDocumentUrl.com", documentation.DocumentUrl); + + var icons = catalogPackageMetadata.Icons; + Assert.AreEqual(1, icons.Count); + + var icon = icons[0]; + Assert.AreEqual("https://localeTestIcon", icon.Url); + Assert.AreEqual(IconFileType.Png, icon.FileType); + Assert.AreEqual(IconTheme.Light, icon.Theme); + Assert.AreEqual(IconResolution.Square32, icon.Resolution); + Assert.AreEqual(Convert.FromHexString("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321"), icon.Sha256); } - /// - /// Verifies that GetCatalogPackageMetadata returns the correct metadata based on the specified locale. + /// + /// Verifies that GetCatalogPackageMetadata returns the correct metadata based on the specified locale. /// [Test] - public void FindPackagesGetAllCatalogPackageMetadata() - { - var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.MultipleLocale"); + public void FindPackagesGetAllCatalogPackageMetadata() + { + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.MultipleLocale"); Assert.AreEqual(1, searchResult.Count); - - var catalogPackage = searchResult[0].CatalogPackage; + + var catalogPackage = searchResult[0].CatalogPackage; var packageVersionId = catalogPackage.AvailableVersions[0]; var packageVersionInfo = catalogPackage.GetPackageVersionInfo(packageVersionId); @@ -213,25 +213,25 @@ public void FindPackagesGetAllCatalogPackageMetadata() Assert.AreEqual("zh-CN", catalogPackageMetadata1.Locale); var catalogPackageMetadata2 = packageVersionInfo.GetCatalogPackageMetadata("en-GB"); - Assert.AreEqual("en-GB", catalogPackageMetadata2.Locale); - Assert.AreEqual("packageNameUK", catalogPackageMetadata2.PackageName); + Assert.AreEqual("en-GB", catalogPackageMetadata2.Locale); + Assert.AreEqual("packageNameUK", catalogPackageMetadata2.PackageName); } - /// - /// Verifies that GetCatalogPackageMetadata returns the correct metadata based on the specified locale. + /// + /// Verifies that GetCatalogPackageMetadata returns the correct metadata based on the specified locale. /// [Test] - public void FindPackagesGetVersionMetadata() - { - var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.MultipleLocale"); + public void FindPackagesGetVersionMetadata() + { + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.MultipleLocale"); Assert.AreEqual(1, searchResult.Count); - - var catalogPackage = searchResult[0].CatalogPackage; + + var catalogPackage = searchResult[0].CatalogPackage; var packageVersionId = catalogPackage.AvailableVersions[0]; var packageVersionInfo = catalogPackage.GetPackageVersionInfo(packageVersionId); - string metadata = packageVersionInfo.GetMetadata(PackageVersionMetadataField.SilentUninstallCommand); - Assert.IsEmpty(metadata); + string metadata = packageVersionInfo.GetMetadata(PackageVersionMetadataField.SilentUninstallCommand); + Assert.IsEmpty(metadata); } } -} +} diff --git a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs index f5e86f9434..b807a7a083 100644 --- a/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/GroupPolicyForInterop.cs @@ -1,246 +1,246 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Interop -{ - using System; - using System.IO; - using System.Text; - using System.Threading; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using Microsoft.WinGet.SharedLib.Exceptions; - using NUnit.Framework; - using Windows.System; - - /// - /// Group Policy Tests for COM/WinRT Interop classes. - /// - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] - public class GroupPolicyForInterop : BaseInterop - { - /// - /// Initializes a new instance of the class. - /// - /// Initializer. - public GroupPolicyForInterop(IInstanceInitializer initializer) - : base(initializer) - { - } - - /// - /// Test setup. - /// - [SetUp] - public void SetUp() - { - GroupPolicyHelper.DeleteExistingPolicies(); - } - - /// - /// Clean up. - /// - [TearDown] - public void CleanUp() - { - GroupPolicyHelper.DeleteExistingPolicies(); - } - - /// - /// Test class Tear down. - /// - [OneTimeTearDown] - public void TestClassTearDown() - { - // Restore the tests source if it was removed as the affects subsequent tests. - TestCommon.SetupTestSource(); - } - - /// - /// Validates disabling WinGetPolicy should block COM/WinRT Objects creation (InProcess and OutOfProcess). - /// - [Test] - public void DisableWinGetPolicy() - { - GroupPolicyHelper.EnableWinget.Disable(); - - GroupPolicyException groupPolicyException = Assert.Catch(() => { PackageManager packageManager = this.TestFactory.CreatePackageManager(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { FindPackagesOptions findPackagesOptions = this.TestFactory.CreateFindPackagesOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { InstallOptions installOptions = this.TestFactory.CreateInstallOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { UninstallOptions uninstallOptions = this.TestFactory.CreateUninstallOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { DownloadOptions downloadOptions = this.TestFactory.CreateDownloadOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { PackageMatchFilter packageMatchFilter = this.TestFactory.CreatePackageMatchFilter(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { RepairOptions repairOptions = this.TestFactory.CreateRepairOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { AddPackageCatalogOptions packageManagerSettings = this.TestFactory.CreateAddPackageCatalogOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { RemovePackageCatalogOptions packageManagerSettings = this.TestFactory.CreateRemovePackageCatalogOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - groupPolicyException = Assert.Catch(() => { EditPackageCatalogOptions packageManagerSettings = this.TestFactory.CreateEditPackageCatalogOptions(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - - // PackageManagerSettings is not implemented in context OutOfProcDev - if (this.TestFactory.Context == ClsidContext.InProc) - { - groupPolicyException = Assert.Catch(() => { PackageManagerSettings packageManagerSettings = this.TestFactory.CreatePackageManagerSettings(); }); - Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); - Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); - } - } - - /// - /// Validates disabling the EnableWindowsPackageManagerCommandLineInterfaces policy should still allow COM calls. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task DisableWinGetCommandLineInterfacesPolicy() - { - GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Disable(); - - PackageManager packageManager = this.TestFactory.CreatePackageManager(); - string installDir = TestCommon.GetRandomTestDir(); - - // Create composite package catalog source - var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); - var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); - Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); - options.Catalogs.Add(testSource); - options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - PackageCatalogReference compositeSource = packageManager.CreateCompositePackageCatalog(options); - - // Find package - var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ModifyRepairInstaller); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.AcceptPackageAgreements = true; - installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair /UseHKLM"; - - // Install - var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - - // Find package again, but this time it should detect the installed version - searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ModifyRepairInstaller); - Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); - - // Repair - var repairOptions = this.TestFactory.CreateRepairOptions(); - repairOptions.PackageRepairMode = PackageRepairMode.Silent; - var repairResult = await packageManager.RepairPackageAsync(searchResult.CatalogPackage, repairOptions); - Assert.AreEqual(RepairResultStatus.Ok, repairResult.Status); - - // Uninstall - var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); - Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); - Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); - - // Clean up. - if (Directory.Exists(installDir)) - { - Directory.Delete(installDir, true); - } - - // Download - var downloadResult = await packageManager.DownloadPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateDownloadOptions()); - Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); - var packageVersion = "2.0.0.0"; - string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ModifyRepairInstaller}_{packageVersion}"); - TestCommon.AssertInstallerDownload(downloadDir, "TestModifyRepair", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Burn, "en-US"); - - // Add, update and remove package catalog - await this.AddUpdateRemovePackageCatalog(); - } - - private async Task AddUpdateRemovePackageCatalog() - { - // Remove the tests source if it exists. - await this.RemovePackageCatalog(); - - PackageManager packageManager = this.TestFactory.CreatePackageManager(); - - // Add package catalog - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - var addCatalogResult = await packageManager.AddPackageCatalogAsync(options); - Assert.IsNotNull(addCatalogResult); - Assert.AreEqual(AddPackageCatalogStatus.Ok, addCatalogResult.Status); - - // Get package catalog - var packageCatalog = packageManager.GetPackageCatalogByName(options.Name); - - Assert.IsNotNull(packageCatalog); - Assert.AreEqual(options.Name, packageCatalog.Info.Name); - Assert.AreEqual(options.SourceUri, packageCatalog.Info.Argument); - var lastUpdatedTime = packageCatalog.Info.LastUpdateTime; - - // Update package catalog - // Sleep for 30 seconds to make sure the last updated time is different after the refresh. - Thread.Sleep(TimeSpan.FromSeconds(30)); - - var updateResult = await packageCatalog.RefreshPackageCatalogAsync(); - Assert.IsNotNull(updateResult); - Assert.AreEqual(RefreshPackageCatalogStatus.Ok, updateResult.Status); - - packageCatalog = packageManager.GetPackageCatalogByName(options.Name); - Assert.IsTrue(packageCatalog.Info.LastUpdateTime > lastUpdatedTime); - - // Remove package catalog - await this.RemovePackageCatalog(); - } - - private async Task RemovePackageCatalog() - { - PackageManager packageManager = this.TestFactory.CreatePackageManager(); - - // Remove the tests source if it exists. - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - - var removeCatalogResult = await packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); - Assert.IsNotNull(removeCatalogResult); - Assert.AreEqual(RemovePackageCatalogStatus.Ok, removeCatalogResult.Status); - - var packageCatalog = packageManager.GetPackageCatalogByName(removePackageCatalogOptions.Name); - Assert.IsNull(packageCatalog); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.IO; + using System.Text; + using System.Threading; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using Microsoft.WinGet.SharedLib.Exceptions; + using NUnit.Framework; + using Windows.System; + + /// + /// Group Policy Tests for COM/WinRT Interop classes. + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class GroupPolicyForInterop : BaseInterop + { + /// + /// Initializes a new instance of the class. + /// + /// Initializer. + public GroupPolicyForInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Test setup. + /// + [SetUp] + public void SetUp() + { + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Clean up. + /// + [TearDown] + public void CleanUp() + { + GroupPolicyHelper.DeleteExistingPolicies(); + } + + /// + /// Test class Tear down. + /// + [OneTimeTearDown] + public void TestClassTearDown() + { + // Restore the tests source if it was removed as the affects subsequent tests. + TestCommon.SetupTestSource(); + } + + /// + /// Validates disabling WinGetPolicy should block COM/WinRT Objects creation (InProcess and OutOfProcess). + /// + [Test] + public void DisableWinGetPolicy() + { + GroupPolicyHelper.EnableWinget.Disable(); + + GroupPolicyException groupPolicyException = Assert.Catch(() => { PackageManager packageManager = this.TestFactory.CreatePackageManager(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { FindPackagesOptions findPackagesOptions = this.TestFactory.CreateFindPackagesOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { InstallOptions installOptions = this.TestFactory.CreateInstallOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { UninstallOptions uninstallOptions = this.TestFactory.CreateUninstallOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { DownloadOptions downloadOptions = this.TestFactory.CreateDownloadOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { PackageMatchFilter packageMatchFilter = this.TestFactory.CreatePackageMatchFilter(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { RepairOptions repairOptions = this.TestFactory.CreateRepairOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { AddPackageCatalogOptions packageManagerSettings = this.TestFactory.CreateAddPackageCatalogOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { RemovePackageCatalogOptions packageManagerSettings = this.TestFactory.CreateRemovePackageCatalogOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + groupPolicyException = Assert.Catch(() => { EditPackageCatalogOptions packageManagerSettings = this.TestFactory.CreateEditPackageCatalogOptions(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + + // PackageManagerSettings is not implemented in context OutOfProcDev + if (this.TestFactory.Context == ClsidContext.InProc) + { + groupPolicyException = Assert.Catch(() => { PackageManagerSettings packageManagerSettings = this.TestFactory.CreatePackageManagerSettings(); }); + Assert.AreEqual(Constants.BlockByWinGetPolicyErrorMessage, groupPolicyException.Message); + Assert.AreEqual(Constants.ErrorCode.ERROR_BLOCKED_BY_POLICY, groupPolicyException.HResult); + } + } + + /// + /// Validates disabling the EnableWindowsPackageManagerCommandLineInterfaces policy should still allow COM calls. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task DisableWinGetCommandLineInterfacesPolicy() + { + GroupPolicyHelper.EnableWinGetCommandLineInterfaces.Disable(); + + PackageManager packageManager = this.TestFactory.CreatePackageManager(); + string installDir = TestCommon.GetRandomTestDir(); + + // Create composite package catalog source + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); + options.Catalogs.Add(testSource); + options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; + PackageCatalogReference compositeSource = packageManager.CreateCompositePackageCatalog(options); + + // Find package + var searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ModifyRepairInstaller); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.AcceptPackageAgreements = true; + installOptions.ReplacementInstallerArguments = $"/InstallDir {installDir} /Version 2.0.0.0 /DisplayName TestModifyRepair /UseHKLM"; + + // Install + var installResult = await packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version + searchResult = this.FindOnePackage(compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.ModifyRepairInstaller); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Repair + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + var repairResult = await packageManager.RepairPackageAsync(searchResult.CatalogPackage, repairOptions); + Assert.AreEqual(RepairResultStatus.Ok, repairResult.Status); + + // Uninstall + var uninstallResult = await packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); + Assert.AreEqual(UninstallResultStatus.Ok, uninstallResult.Status); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + + // Clean up. + if (Directory.Exists(installDir)) + { + Directory.Delete(installDir, true); + } + + // Download + var downloadResult = await packageManager.DownloadPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateDownloadOptions()); + Assert.AreEqual(DownloadResultStatus.Ok, downloadResult.Status); + var packageVersion = "2.0.0.0"; + string downloadDir = Path.Combine(TestCommon.GetDefaultDownloadDirectory(), $"{Constants.ModifyRepairInstaller}_{packageVersion}"); + TestCommon.AssertInstallerDownload(downloadDir, "TestModifyRepair", packageVersion, ProcessorArchitecture.X86, TestCommon.Scope.Unknown, PackageInstallerType.Burn, "en-US"); + + // Add, update and remove package catalog + await this.AddUpdateRemovePackageCatalog(); + } + + private async Task AddUpdateRemovePackageCatalog() + { + // Remove the tests source if it exists. + await this.RemovePackageCatalog(); + + PackageManager packageManager = this.TestFactory.CreatePackageManager(); + + // Add package catalog + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + var addCatalogResult = await packageManager.AddPackageCatalogAsync(options); + Assert.IsNotNull(addCatalogResult); + Assert.AreEqual(AddPackageCatalogStatus.Ok, addCatalogResult.Status); + + // Get package catalog + var packageCatalog = packageManager.GetPackageCatalogByName(options.Name); + + Assert.IsNotNull(packageCatalog); + Assert.AreEqual(options.Name, packageCatalog.Info.Name); + Assert.AreEqual(options.SourceUri, packageCatalog.Info.Argument); + var lastUpdatedTime = packageCatalog.Info.LastUpdateTime; + + // Update package catalog + // Sleep for 30 seconds to make sure the last updated time is different after the refresh. + Thread.Sleep(TimeSpan.FromSeconds(30)); + + var updateResult = await packageCatalog.RefreshPackageCatalogAsync(); + Assert.IsNotNull(updateResult); + Assert.AreEqual(RefreshPackageCatalogStatus.Ok, updateResult.Status); + + packageCatalog = packageManager.GetPackageCatalogByName(options.Name); + Assert.IsTrue(packageCatalog.Info.LastUpdateTime > lastUpdatedTime); + + // Remove package catalog + await this.RemovePackageCatalog(); + } + + private async Task RemovePackageCatalog() + { + PackageManager packageManager = this.TestFactory.CreatePackageManager(); + + // Remove the tests source if it exists. + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + + var removeCatalogResult = await packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); + Assert.IsNotNull(removeCatalogResult); + Assert.AreEqual(RemovePackageCatalogStatus.Ok, removeCatalogResult.Status); + + var packageCatalog = packageManager.GetPackageCatalogByName(removePackageCatalogOptions.Name); + Assert.IsNull(packageCatalog); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs index 03c350fb0a..dc613c7700 100644 --- a/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/InstallInterop.cs @@ -1,728 +1,728 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Interop -{ - using System; - using System.IO; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; - - /// - /// Install interop. - /// - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] - public class InstallInterop : BaseInterop - { - private string installDir; - private PackageManager packageManager; - private PackageCatalogReference testSource; - - /// - /// Initializes a new instance of the class. - /// - /// Initializer. - public InstallInterop(IInstanceInitializer initializer) - : base(initializer) - { - } - - /// - /// Set up. - /// - [SetUp] - public void SetUp() - { - this.packageManager = this.TestFactory.CreatePackageManager(); - this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); - this.installDir = TestCommon.GetRandomTestDir(); - } - - /// - /// Install exe. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallExe() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.AcceptPackageAgreements = true; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - - /// - /// Test install with inapplicable os version. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallExeWithInsufficientMinOsVersion() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - - /// - /// Test install with hash mismatch. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallExeWithHashMismatch() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeSha256Mismatch"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - - /// - /// Test installing inno installer. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallWithInno() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestInnoInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - - /// - /// Test installing burn installer. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallBurn() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestBurnInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - - /// - /// Test installing nullsoft installer. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallNullSoft() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestNullsoftInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - - /// - /// Test installing msi. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSI() - { - if (string.IsNullOrEmpty(TestIndex.MsiInstaller)) - { - Assert.Ignore("MSI installer not available"); - } - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsiInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); - } - - /// - /// Test installing an msix. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIX() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test installing msix with machine scope. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIXMachineScope() - { - // TODO: Provision and Deprovision api not supported in build server. - Assert.Ignore(); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallScope = PackageInstallScope.System; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); - } - - /// - /// Test installing msix with signature. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIXWithSignature() - { - // Task to investigate installation error - // TODO: https://task.ms/40489822 - Assert.Ignore(); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test installing msix with signature machine scope. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIXWithSignatureMachineScope() - { - // TODO: Provision and Deprovision api not supported in build server. - Assert.Ignore(); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallScope = PackageInstallScope.System; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); - } - - /// - /// Test installing msix with signature hash mismatch. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIXWithSignatureHashMismatch() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixSignatureHashMismatch"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); - Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test installing exe. - /// - [Test] - public void InstallExeWithAlternateSourceFailure() - { - // Add mock source - TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); - - // Get mock source - var failSearchSource = this.packageManager.GetPackageCatalogByName("failSearch"); - - // Find package - var searchResult = this.FindAllPackages(failSearchSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - - // Assert - Assert.NotNull(failSearchSource); - Assert.AreEqual(0, searchResult.Count); - - // Remove mock source - TestCommon.RunAICLICommand("source remove", "failSearch"); - } - - /// - /// Test installing portable exe. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableExe() - { - string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); - string productCode = Constants.PortableExePackageDirName; - string commandAlias = $"{Constants.ExeInstaller}.exe"; - string fileName = $"{Constants.ExeInstaller}.exe"; - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test installing portable exe with command. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableExeWithCommand() - { - string productCode = Constants.PortableExeWithCommandPackageDirName; - string fileName = Constants.AppInstallerTestExeInstallerExe; - string commandAlias = Constants.TestCommandExe; - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExeWithCommandPackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = this.installDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - TestCommon.VerifyPortablePackage(this.installDir, commandAlias, fileName, productCode, true); - } - - /// - /// Test installing portable package to existing directory. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableToExistingDirectory() - { - var existingDir = Path.Combine(this.installDir, "testDirectory"); - Directory.CreateDirectory(existingDir); - - string productCode = Constants.PortableExePackageDirName; - string commandAlias = Constants.AppInstallerTestExeInstallerExe; - string fileName = Constants.AppInstallerTestExeInstallerExe; - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = existingDir; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); - } - - /// - /// Test installing portable package where it fails on clean up. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallPortableFailsWithCleanup() - { - if (this.TestFactory.Context == ClsidContext.InProc) - { - // Task to investigate validation error when running in-process - // TODO: https://task.ms/40489822 - Assert.Ignore(); - } - - string winGetDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet"); - string installDir = Path.Combine(winGetDir, "Packages"); - string symlinkDirectory = Path.Combine(winGetDir, "Links"); - string packageDirName = Constants.PortableExePackageDirName; - string productCode = Constants.PortableExePackageDirName; - string commandAlias = Constants.AppInstallerTestExeInstallerExe; - string fileName = Constants.AppInstallerTestExeInstallerExe; - string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); - - // Create a directory with the same name as the symlink in order to cause install to fail. - Directory.CreateDirectory(conflictDirectory); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.InstallError, installResult.Status); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - Directory.Delete(conflictDirectory, true); - } - - /// - /// Test installing a package with user scope. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallRequireUserScope() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstallerNoScope"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.PackageInstallScope = PackageInstallScope.User; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); - } - - /// - /// Test installing package with user scope or unknown. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallRequireUserScopeAndUnknown() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstallerNoScope"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.PackageInstallScope = PackageInstallScope.UserOrUnknown; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - } - - /// - /// Test installing package with agreements and accepting those agreements. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallWithAgreementsAccepted() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.AcceptPackageAgreements = true; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - } - - /// - /// Test installing package with agreements and not accepting those agreements. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallWithAgreementsNotAccepted() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.AcceptPackageAgreements = false; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.PackageAgreementsNotAccepted, installResult.Status); - } - - /// - /// Test installing a package with a package dependency and passing in the 'skip-dependencies' install option. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallWithSkipDependencies() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.PackageDependency"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.AcceptPackageAgreements = true; - installOptions.SkipDependencies = true; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert that only the exe installer is installed and not the portable package dependency. - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - - string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); - string productCode = Constants.PortableExePackageDirName; - string commandAlias = $"{Constants.ExeInstaller}.exe"; - string fileName = $"{Constants.ExeInstaller}.exe"; - TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, false); - } - - /// - /// Test installing a package with a specific installer type install option. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallWithInstallerType() - { - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestMultipleInstallers"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.InstallerType = PackageInstallerType.Msi; - installOptions.AcceptPackageAgreements = true; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); - } - - /// - /// Test to verify the GetApplicableInstaller() COM call returns the correct manifest installer metadata. - /// - [Test] - public void GetApplicableInstaller() - { - // Find package - var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.PackageInstallerInfo"); - Assert.AreEqual(1, searchResult.Count); - - // Configure installation - var catalogPackage = searchResult[0].CatalogPackage; - var packageVersionId = catalogPackage.AvailableVersions[0]; - var packageVersionInfo = catalogPackage.GetPackageVersionInfo(packageVersionId); - - // Use install options with no applicable match. - var badInstallOptions = this.TestFactory.CreateInstallOptions(); - badInstallOptions.PackageInstallScope = PackageInstallScope.System; - - Assert.IsNull(packageVersionInfo.GetApplicableInstaller(badInstallOptions)); - - // Use install options with valid applicable match. - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallScope = PackageInstallScope.User; - var packageInstallerInfo = packageVersionInfo.GetApplicableInstaller(installOptions); - - // Assert - Assert.IsNotNull(packageInstallerInfo); - Assert.AreEqual(ElevationRequirement.ElevationRequired, packageInstallerInfo.ElevationRequirement); - Assert.AreEqual(Windows.System.ProcessorArchitecture.X64, packageInstallerInfo.Architecture); - Assert.AreEqual(PackageInstallerType.Zip, packageInstallerInfo.InstallerType); - Assert.AreEqual(PackageInstallerType.Exe, packageInstallerInfo.NestedInstallerType); - Assert.AreEqual(PackageInstallerScope.User, packageInstallerInfo.Scope); - Assert.AreEqual("en-US", packageInstallerInfo.Locale); - } - - /// - /// Install exe and verify that we can find it as installed after. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallExe_VerifyInstalledCatalog() - { - var installedCatalogReference = this.packageManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); - - // Ensure package is not installed - var installedResult = this.FindAllPackages(installedCatalogReference, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, Constants.ExeInstalledDefaultProductCode); - Assert.IsNotNull(installedResult); - Assert.AreEqual(0, installedResult.Count); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = this.installDir; - installOptions.AcceptPackageAgreements = true; - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - - // Check installed catalog after - this.FindOnePackage(installedCatalogReference, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, Constants.ExeInstalledDefaultProductCode); - - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); - } - - /// - /// Install msix and verify that we can find it as installed after. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task InstallMSIX_VerifyInstalledCatalog() - { - var installedCatalogReference = this.packageManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); - - // Ensure package is not installed - var installedResult = this.FindAllPackages(installedCatalogReference, PackageMatchField.PackageFamilyName, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageFamilyName); - Assert.IsNotNull(installedResult); - Assert.AreEqual(0, installedResult.Count); - - // Find package - var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - - // Install - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // Assert - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - - // Check installed catalog after - this.FindOnePackage(installedCatalogReference, PackageMatchField.PackageFamilyName, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageFamilyName); - - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.IO; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + + /// + /// Install interop. + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class InstallInterop : BaseInterop + { + private string installDir; + private PackageManager packageManager; + private PackageCatalogReference testSource; + + /// + /// Initializes a new instance of the class. + /// + /// Initializer. + public InstallInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Set up. + /// + [SetUp] + public void SetUp() + { + this.packageManager = this.TestFactory.CreatePackageManager(); + this.testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + this.installDir = TestCommon.GetRandomTestDir(); + } + + /// + /// Install exe. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallExe() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.AcceptPackageAgreements = true; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + + /// + /// Test install with inapplicable os version. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallExeWithInsufficientMinOsVersion() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + + /// + /// Test install with hash mismatch. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallExeWithHashMismatch() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestExeSha256Mismatch"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + + /// + /// Test installing inno installer. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallWithInno() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestInnoInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + + /// + /// Test installing burn installer. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallBurn() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestBurnInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + + /// + /// Test installing nullsoft installer. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallNullSoft() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestNullsoftInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + + /// + /// Test installing msi. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSI() + { + if (string.IsNullOrEmpty(TestIndex.MsiInstaller)) + { + Assert.Ignore("MSI installer not available"); + } + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsiInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); + } + + /// + /// Test installing an msix. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIX() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test installing msix with machine scope. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallScope = PackageInstallScope.System; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + + /// + /// Test installing msix with signature. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXWithSignature() + { + // Task to investigate installation error + // TODO: https://task.ms/40489822 + Assert.Ignore(); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test installing msix with signature machine scope. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXWithSignatureMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixWithSignatureHash"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallScope = PackageInstallScope.System; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup(true)); + } + + /// + /// Test installing msix with signature hash mismatch. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIXWithSignatureHashMismatch() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixSignatureHashMismatch"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.DownloadError, installResult.Status); + Assert.False(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test installing exe. + /// + [Test] + public void InstallExeWithAlternateSourceFailure() + { + // Add mock source + TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"SearchHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); + + // Get mock source + var failSearchSource = this.packageManager.GetPackageCatalogByName("failSearch"); + + // Find package + var searchResult = this.FindAllPackages(failSearchSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Assert + Assert.NotNull(failSearchSource); + Assert.AreEqual(0, searchResult.Count); + + // Remove mock source + TestCommon.RunAICLICommand("source remove", "failSearch"); + } + + /// + /// Test installing portable exe. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableExe() + { + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string productCode = Constants.PortableExePackageDirName; + string commandAlias = $"{Constants.ExeInstaller}.exe"; + string fileName = $"{Constants.ExeInstaller}.exe"; + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test installing portable exe with command. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableExeWithCommand() + { + string productCode = Constants.PortableExeWithCommandPackageDirName; + string fileName = Constants.AppInstallerTestExeInstallerExe; + string commandAlias = Constants.TestCommandExe; + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExeWithCommandPackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = this.installDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(this.installDir, commandAlias, fileName, productCode, true); + } + + /// + /// Test installing portable package to existing directory. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableToExistingDirectory() + { + var existingDir = Path.Combine(this.installDir, "testDirectory"); + Directory.CreateDirectory(existingDir); + + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PreferredInstallLocation = existingDir; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + TestCommon.VerifyPortablePackage(existingDir, commandAlias, fileName, productCode, true); + } + + /// + /// Test installing portable package where it fails on clean up. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallPortableFailsWithCleanup() + { + if (this.TestFactory.Context == ClsidContext.InProc) + { + // Task to investigate validation error when running in-process + // TODO: https://task.ms/40489822 + Assert.Ignore(); + } + + string winGetDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet"); + string installDir = Path.Combine(winGetDir, "Packages"); + string symlinkDirectory = Path.Combine(winGetDir, "Links"); + string packageDirName = Constants.PortableExePackageDirName; + string productCode = Constants.PortableExePackageDirName; + string commandAlias = Constants.AppInstallerTestExeInstallerExe; + string fileName = Constants.AppInstallerTestExeInstallerExe; + string conflictDirectory = Path.Combine(symlinkDirectory, commandAlias); + + // Create a directory with the same name as the symlink in order to cause install to fail. + Directory.CreateDirectory(conflictDirectory); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, Constants.PortableExePackageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.InstallError, installResult.Status); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + Directory.Delete(conflictDirectory, true); + } + + /// + /// Test installing a package with user scope. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallRequireUserScope() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstallerNoScope"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.PackageInstallScope = PackageInstallScope.User; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.NoApplicableInstallers, installResult.Status); + } + + /// + /// Test installing package with user scope or unknown. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallRequireUserScopeAndUnknown() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstallerNoScope"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.PackageInstallScope = PackageInstallScope.UserOrUnknown; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + } + + /// + /// Test installing package with agreements and accepting those agreements. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallWithAgreementsAccepted() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.AcceptPackageAgreements = true; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + } + + /// + /// Test installing package with agreements and not accepting those agreements. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallWithAgreementsNotAccepted() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.CatalogPackageMetadata"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.AcceptPackageAgreements = false; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.PackageAgreementsNotAccepted, installResult.Status); + } + + /// + /// Test installing a package with a package dependency and passing in the 'skip-dependencies' install option. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallWithSkipDependencies() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.PackageDependency"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.AcceptPackageAgreements = true; + installOptions.SkipDependencies = true; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert that only the exe installer is installed and not the portable package dependency. + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + + string installDir = Path.Combine(Environment.GetEnvironmentVariable(Constants.LocalAppData), "Microsoft", "WinGet", "Packages"); + string productCode = Constants.PortableExePackageDirName; + string commandAlias = $"{Constants.ExeInstaller}.exe"; + string fileName = $"{Constants.ExeInstaller}.exe"; + TestCommon.VerifyPortablePackage(Path.Combine(installDir, Constants.PortableExePackageDirName), commandAlias, fileName, productCode, false); + } + + /// + /// Test installing a package with a specific installer type install option. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallWithInstallerType() + { + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestMultipleInstallers"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.InstallerType = PackageInstallerType.Msi; + installOptions.AcceptPackageAgreements = true; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); + } + + /// + /// Test to verify the GetApplicableInstaller() COM call returns the correct manifest installer metadata. + /// + [Test] + public void GetApplicableInstaller() + { + // Find package + var searchResult = this.FindAllPackages(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.PackageInstallerInfo"); + Assert.AreEqual(1, searchResult.Count); + + // Configure installation + var catalogPackage = searchResult[0].CatalogPackage; + var packageVersionId = catalogPackage.AvailableVersions[0]; + var packageVersionInfo = catalogPackage.GetPackageVersionInfo(packageVersionId); + + // Use install options with no applicable match. + var badInstallOptions = this.TestFactory.CreateInstallOptions(); + badInstallOptions.PackageInstallScope = PackageInstallScope.System; + + Assert.IsNull(packageVersionInfo.GetApplicableInstaller(badInstallOptions)); + + // Use install options with valid applicable match. + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallScope = PackageInstallScope.User; + var packageInstallerInfo = packageVersionInfo.GetApplicableInstaller(installOptions); + + // Assert + Assert.IsNotNull(packageInstallerInfo); + Assert.AreEqual(ElevationRequirement.ElevationRequired, packageInstallerInfo.ElevationRequirement); + Assert.AreEqual(Windows.System.ProcessorArchitecture.X64, packageInstallerInfo.Architecture); + Assert.AreEqual(PackageInstallerType.Zip, packageInstallerInfo.InstallerType); + Assert.AreEqual(PackageInstallerType.Exe, packageInstallerInfo.NestedInstallerType); + Assert.AreEqual(PackageInstallerScope.User, packageInstallerInfo.Scope); + Assert.AreEqual("en-US", packageInstallerInfo.Locale); + } + + /// + /// Install exe and verify that we can find it as installed after. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallExe_VerifyInstalledCatalog() + { + var installedCatalogReference = this.packageManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); + + // Ensure package is not installed + var installedResult = this.FindAllPackages(installedCatalogReference, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, Constants.ExeInstalledDefaultProductCode); + Assert.IsNotNull(installedResult); + Assert.AreEqual(0, installedResult.Count); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = this.installDir; + installOptions.AcceptPackageAgreements = true; + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Check installed catalog after + this.FindOnePackage(installedCatalogReference, PackageMatchField.ProductCode, PackageFieldMatchOption.Equals, Constants.ExeInstalledDefaultProductCode); + + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(this.installDir)); + } + + /// + /// Install msix and verify that we can find it as installed after. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task InstallMSIX_VerifyInstalledCatalog() + { + var installedCatalogReference = this.packageManager.GetLocalPackageCatalog(LocalPackageCatalog.InstalledPackages); + + // Ensure package is not installed + var installedResult = this.FindAllPackages(installedCatalogReference, PackageMatchField.PackageFamilyName, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageFamilyName); + Assert.IsNotNull(installedResult); + Assert.AreEqual(0, installedResult.Count); + + // Find package + var searchResult = this.FindOnePackage(this.testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "TestMsixInstaller"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + + // Install + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // Assert + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Check installed catalog after + this.FindOnePackage(installedCatalogReference, PackageMatchField.PackageFamilyName, PackageFieldMatchOption.Equals, Constants.MsixInstallerPackageFamilyName); + + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs b/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs index b5d69e00f8..d97db0cba4 100644 --- a/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs +++ b/src/AppInstallerCLIE2ETests/Interop/InstanceInitializersSource.cs @@ -1,9 +1,9 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { using Microsoft.Management.Deployment.Projection; diff --git a/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs b/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs index 655d4a4d05..ab01a8c520 100644 --- a/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs +++ b/src/AppInstallerCLIE2ETests/Interop/InteropSetUpFixture.cs @@ -1,32 +1,32 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { - using System; - using AppInstallerCLIE2ETests.Helpers; + using System; + using AppInstallerCLIE2ETests.Helpers; using NUnit.Framework; - /// - /// Interop set up fixture. + /// + /// Interop set up fixture. /// [SetUpFixture] public class InteropSetUpFixture { - /// - /// One time set up. + /// + /// One time set up. /// [OneTimeSetUp] public void Setup() { - TestCommon.SetupTestSource(); + TestCommon.SetupTestSource(); } - /// - /// Tear down. + /// + /// Tear down. /// [OneTimeTearDown] public void TearDown() diff --git a/src/AppInstallerCLIE2ETests/Interop/PackageCatalogInterop.cs b/src/AppInstallerCLIE2ETests/Interop/PackageCatalogInterop.cs index b312e4840d..72918b2fcb 100644 --- a/src/AppInstallerCLIE2ETests/Interop/PackageCatalogInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/PackageCatalogInterop.cs @@ -1,415 +1,415 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- -namespace AppInstallerCLIE2ETests.Interop -{ - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.CodeAnalysis; - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; - using Windows.System; - - /// - /// Package catalog interop class. - /// - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] - public class PackageCatalogInterop : BaseInterop - { - private PackageManager packageManager; - - /// - /// Initializes a new instance of the class. - /// - /// initializer. - public PackageCatalogInterop(IInstanceInitializer initializer) - : base(initializer) - { - } - - /// - /// Set up. - /// - [SetUp] - public void Setup() - { - // Remove the tests source if it exists. - TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); - - this.packageManager = this.TestFactory.CreatePackageManager(); - } - - /// - /// Add and remove package catalog. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddRemovePackageCatalog() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); - - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - var removeCatalogResult = await this.packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); - Assert.IsNotNull(removeCatalogResult); - Assert.AreEqual(RemovePackageCatalogStatus.Ok, removeCatalogResult.Status); - - var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); - Assert.IsNull(testSource); - } - - /// - /// Add package catalog with invalid options. - /// - [Test] - public void AddPackageCatalogWithInvalidOptions() - { - // Add package catalog with null options. - Assert.ThrowsAsync(async () => await this.packageManager.AddPackageCatalogAsync(null)); - - // Add package catalog with empty options. - Assert.ThrowsAsync(async () => await this.packageManager.AddPackageCatalogAsync(this.TestFactory.CreateAddPackageCatalogOptions())); - } - - /// - /// Add package catalog with a duplicate name and verify it returns SourceNameExists. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddPackageCatalog_DuplicateName_ReturnsSourceNameExists() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); - - // Add the same package catalog again. - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NAME_ALREADY_EXISTS); - - // Remove the tests source if it exists. - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); - } - - /// - /// Add package catalog with a duplicate source uri and verify it returns SourceArg AlreadyExists. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddPackageCatalog_DuplicateSourceUri_ReturnSourceArgAlreadyExists() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); - - // Add the same package catalog again. - options.Name = "TestSource2"; - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_ARG_ALREADY_EXISTS); - - // Remove the tests source if it exists. - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); - } - - /// - /// Add package catalog with invalid source uri. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddPackageCatalogWithInvalidSourceUri() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = "InvalidUri"; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InternalError); - } - - /// - /// Add package catalog with insecure source uri. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddPackageCatalogWithHttpSourceUri() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = "http://microsoft.com"; - options.Name = "Insecure"; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NOT_SECURE); - } - - /// - /// Add package catalog with invalid type. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddPackageCatalogWithInvalidType() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.Type = "InvalidType"; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_INVALID_SOURCE_TYPE); - } - - /// - /// Add, update and remove package catalog. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddUpdateRemovePackageCatalog() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - await this.AddCatalogAndVerifyStatusAsync(options, AddPackageCatalogStatus.Ok); - - var packageCatalog = this.GetAndValidatePackageCatalog(options); - var lastUpdatedTime = packageCatalog.Info.LastUpdateTime; - - // Sleep for 30 seconds to make sure the last updated time is different after the refresh. - Thread.Sleep(TimeSpan.FromSeconds(30)); - - var updateResult = await packageCatalog.RefreshPackageCatalogAsync(); - Assert.IsNotNull(updateResult); - Assert.AreEqual(RefreshPackageCatalogStatus.Ok, updateResult.Status); - - packageCatalog = this.GetAndValidatePackageCatalog(options); - Assert.IsTrue(packageCatalog.Info.LastUpdateTime > lastUpdatedTime); - - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); - } - - /// - /// Add, remove package catalog with PreserveData filed set.. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddRemovePackageCatalogWithPreserveDataFiledSet() - { - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); - - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - removePackageCatalogOptions.PreserveData = true; - - await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); - } - - /// - /// Remove package catalog with invalid options. - /// - [Test] - public void RemovePackageCatalogWithInvalidOptions() - { - // Remove package catalog with null options. - Assert.ThrowsAsync(async () => await this.packageManager.RemovePackageCatalogAsync(null)); - - // Remove package catalog with empty options. - Assert.ThrowsAsync(async () => await this.packageManager.RemovePackageCatalogAsync(this.TestFactory.CreateRemovePackageCatalogOptions())); - } - - /// - /// Remove a package catalog that is not present. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RemoveNonExistingPackageCatalog() - { - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - - await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST); - } - - /// - /// Edit package catalog with invalid options. - /// - [Test] - public void EditPackageCatalogWithInvalidOptions() - { - // Edit package catalog with null options. - Assert.Throws(() => this.packageManager.EditPackageCatalog(null)); - - // Edit package catalog with empty options. - Assert.Throws(() => this.packageManager.EditPackageCatalog(this.TestFactory.CreateEditPackageCatalogOptions())); - } - - /// - /// Edit a package catalog that is not present. - /// - [Test] - public void EditNonExistingPackageCatalog() - { - EditPackageCatalogOptions editPackageCatalogOptions = this.TestFactory.CreateEditPackageCatalogOptions(); - editPackageCatalogOptions.Name = Constants.TestSourceName; - - this.EditAndValidatePackageCatalog(editPackageCatalogOptions, EditPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST); - } - - /// - /// Edit a package catalog that is not present. - /// - /// representing the asynchronous unit test. - [Test] - public async Task AddEditRemovePackageCatalog() - { - // Add - AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); - options.SourceUri = Constants.TestSourceUrl; - options.Name = Constants.TestSourceName; - options.TrustLevel = PackageCatalogTrustLevel.Trusted; - options.Explicit = true; - options.Priority = 12; - - await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); - - // Edit - EditPackageCatalogOptions editOptions = this.TestFactory.CreateEditPackageCatalogOptions(); - editOptions.Name = Constants.TestSourceName; - editOptions.Explicit = false; - editOptions.Priority = 42; - this.EditAndValidatePackageCatalog(editOptions, EditPackageCatalogStatus.Ok); - - // Remove - RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); - removePackageCatalogOptions.Name = Constants.TestSourceName; - var removeCatalogResult = await this.packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); - Assert.IsNotNull(removeCatalogResult); - Assert.AreEqual(RemovePackageCatalogStatus.Ok, removeCatalogResult.Status); - - var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); - Assert.IsNull(testSource); - } - - /// - /// Test class Tear down. - /// - [OneTimeTearDown] - public void TestClassTearDown() - { - // Restore the tests source if it was removed as the affects subsequent tests. - TestCommon.SetupTestSource(); - } - - private async Task AddCatalogAndVerifyStatusAsync(AddPackageCatalogOptions addPackageCatalogOptions, AddPackageCatalogStatus expectedStatus, int expectedErrorCode = 0) - { - var addCatalogResult = await this.packageManager.AddPackageCatalogAsync(addPackageCatalogOptions); - Assert.IsNotNull(addCatalogResult); - Assert.AreEqual(expectedStatus, addCatalogResult.Status); - - if (expectedStatus != AddPackageCatalogStatus.Ok && expectedErrorCode != 0) - { - Assert.AreEqual(expectedErrorCode, addCatalogResult.ExtendedErrorCode.HResult); - } - } - - private PackageCatalogReference GetAndValidatePackageCatalog(AddPackageCatalogOptions addPackageCatalogOptions) - { - var packageCatalog = this.packageManager.GetPackageCatalogByName(addPackageCatalogOptions.Name); - - Assert.IsNotNull(packageCatalog); - Assert.AreEqual(addPackageCatalogOptions.Name, packageCatalog.Info.Name); - Assert.AreEqual(addPackageCatalogOptions.SourceUri, packageCatalog.Info.Argument); - Assert.AreEqual(addPackageCatalogOptions.Explicit, packageCatalog.Info.Explicit); - Assert.AreEqual(addPackageCatalogOptions.Priority, packageCatalog.Info.Priority); - - return packageCatalog; - } - - private async Task AddAndValidatePackageCatalogAsync(AddPackageCatalogOptions addPackageCatalogOptions, AddPackageCatalogStatus expectedStatus, int expectedErrorCode = 0) - { - // Add the package catalog and verify the status - var addCatalogResult = await this.packageManager.AddPackageCatalogAsync(addPackageCatalogOptions); - Assert.IsNotNull(addCatalogResult); - Assert.AreEqual(expectedStatus, addCatalogResult.Status); - - if (expectedStatus != AddPackageCatalogStatus.Ok) - { - // Only validate the error code if the status is not Ok and the expected error code is not 0 - if (expectedErrorCode != 0) - { - Assert.AreEqual(expectedErrorCode, addCatalogResult.ExtendedErrorCode.HResult); - } - - return; - } - - // Validate the added package catalog if the status is Ok - this.GetAndValidatePackageCatalog(addPackageCatalogOptions); - } - - private async Task RemoveAndValidatePackageCatalogAsync(RemovePackageCatalogOptions removePackageCatalogOptions, RemovePackageCatalogStatus expectedStatus, int expectedErrorCode = 0) - { - var removeCatalogResult = await this.packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); - Assert.IsNotNull(removeCatalogResult); - Assert.AreEqual(expectedStatus, removeCatalogResult.Status); - - if (expectedStatus != RemovePackageCatalogStatus.Ok && expectedErrorCode != 0) - { - Assert.AreEqual(expectedErrorCode, removeCatalogResult.ExtendedErrorCode.HResult); - return; - } - - var packageCatalog = this.packageManager.GetPackageCatalogByName(removePackageCatalogOptions.Name); - Assert.IsNull(packageCatalog); - } - - private void EditAndValidatePackageCatalog(EditPackageCatalogOptions editPackageCatalogOptions, EditPackageCatalogStatus expectedStatus, int expectedErrorCode = 0) - { - var editCatalogResult = this.packageManager.EditPackageCatalog(editPackageCatalogOptions); - Assert.IsNotNull(editCatalogResult); - Assert.AreEqual(expectedStatus, editCatalogResult.Status); - - if (expectedStatus != EditPackageCatalogStatus.Ok && expectedErrorCode != 0) - { - Assert.AreEqual(expectedErrorCode, editCatalogResult.ExtendedErrorCode.HResult); - return; - } - - // Verify edits are correct. - var packageCatalog = this.packageManager.GetPackageCatalogByName(editPackageCatalogOptions.Name); - if (editPackageCatalogOptions.Explicit != null) - { - Assert.AreEqual(packageCatalog.Info.Explicit, editPackageCatalogOptions.Explicit); - } - - if (editPackageCatalogOptions.Priority != null) - { - Assert.AreEqual(packageCatalog.Info.Priority, editPackageCatalogOptions.Priority); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.IO; + using System.Threading; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.CodeAnalysis; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + using Windows.System; + + /// + /// Package catalog interop class. + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class PackageCatalogInterop : BaseInterop + { + private PackageManager packageManager; + + /// + /// Initializes a new instance of the class. + /// + /// initializer. + public PackageCatalogInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Set up. + /// + [SetUp] + public void Setup() + { + // Remove the tests source if it exists. + TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); + + this.packageManager = this.TestFactory.CreatePackageManager(); + } + + /// + /// Add and remove package catalog. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddRemovePackageCatalog() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); + + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + var removeCatalogResult = await this.packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); + Assert.IsNotNull(removeCatalogResult); + Assert.AreEqual(RemovePackageCatalogStatus.Ok, removeCatalogResult.Status); + + var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + Assert.IsNull(testSource); + } + + /// + /// Add package catalog with invalid options. + /// + [Test] + public void AddPackageCatalogWithInvalidOptions() + { + // Add package catalog with null options. + Assert.ThrowsAsync(async () => await this.packageManager.AddPackageCatalogAsync(null)); + + // Add package catalog with empty options. + Assert.ThrowsAsync(async () => await this.packageManager.AddPackageCatalogAsync(this.TestFactory.CreateAddPackageCatalogOptions())); + } + + /// + /// Add package catalog with a duplicate name and verify it returns SourceNameExists. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddPackageCatalog_DuplicateName_ReturnsSourceNameExists() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); + + // Add the same package catalog again. + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NAME_ALREADY_EXISTS); + + // Remove the tests source if it exists. + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); + } + + /// + /// Add package catalog with a duplicate source uri and verify it returns SourceArg AlreadyExists. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddPackageCatalog_DuplicateSourceUri_ReturnSourceArgAlreadyExists() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); + + // Add the same package catalog again. + options.Name = "TestSource2"; + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_ARG_ALREADY_EXISTS); + + // Remove the tests source if it exists. + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); + } + + /// + /// Add package catalog with invalid source uri. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddPackageCatalogWithInvalidSourceUri() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = "InvalidUri"; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InternalError); + } + + /// + /// Add package catalog with insecure source uri. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddPackageCatalogWithHttpSourceUri() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = "http://microsoft.com"; + options.Name = "Insecure"; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NOT_SECURE); + } + + /// + /// Add package catalog with invalid type. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddPackageCatalogWithInvalidType() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.Type = "InvalidType"; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_INVALID_SOURCE_TYPE); + } + + /// + /// Add, update and remove package catalog. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddUpdateRemovePackageCatalog() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + await this.AddCatalogAndVerifyStatusAsync(options, AddPackageCatalogStatus.Ok); + + var packageCatalog = this.GetAndValidatePackageCatalog(options); + var lastUpdatedTime = packageCatalog.Info.LastUpdateTime; + + // Sleep for 30 seconds to make sure the last updated time is different after the refresh. + Thread.Sleep(TimeSpan.FromSeconds(30)); + + var updateResult = await packageCatalog.RefreshPackageCatalogAsync(); + Assert.IsNotNull(updateResult); + Assert.AreEqual(RefreshPackageCatalogStatus.Ok, updateResult.Status); + + packageCatalog = this.GetAndValidatePackageCatalog(options); + Assert.IsTrue(packageCatalog.Info.LastUpdateTime > lastUpdatedTime); + + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); + } + + /// + /// Add, remove package catalog with PreserveData filed set.. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddRemovePackageCatalogWithPreserveDataFiledSet() + { + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); + + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + removePackageCatalogOptions.PreserveData = true; + + await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.Ok); + } + + /// + /// Remove package catalog with invalid options. + /// + [Test] + public void RemovePackageCatalogWithInvalidOptions() + { + // Remove package catalog with null options. + Assert.ThrowsAsync(async () => await this.packageManager.RemovePackageCatalogAsync(null)); + + // Remove package catalog with empty options. + Assert.ThrowsAsync(async () => await this.packageManager.RemovePackageCatalogAsync(this.TestFactory.CreateRemovePackageCatalogOptions())); + } + + /// + /// Remove a package catalog that is not present. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RemoveNonExistingPackageCatalog() + { + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + + await this.RemoveAndValidatePackageCatalogAsync(removePackageCatalogOptions, RemovePackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST); + } + + /// + /// Edit package catalog with invalid options. + /// + [Test] + public void EditPackageCatalogWithInvalidOptions() + { + // Edit package catalog with null options. + Assert.Throws(() => this.packageManager.EditPackageCatalog(null)); + + // Edit package catalog with empty options. + Assert.Throws(() => this.packageManager.EditPackageCatalog(this.TestFactory.CreateEditPackageCatalogOptions())); + } + + /// + /// Edit a package catalog that is not present. + /// + [Test] + public void EditNonExistingPackageCatalog() + { + EditPackageCatalogOptions editPackageCatalogOptions = this.TestFactory.CreateEditPackageCatalogOptions(); + editPackageCatalogOptions.Name = Constants.TestSourceName; + + this.EditAndValidatePackageCatalog(editPackageCatalogOptions, EditPackageCatalogStatus.InvalidOptions, Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST); + } + + /// + /// Edit a package catalog that is not present. + /// + /// representing the asynchronous unit test. + [Test] + public async Task AddEditRemovePackageCatalog() + { + // Add + AddPackageCatalogOptions options = this.TestFactory.CreateAddPackageCatalogOptions(); + options.SourceUri = Constants.TestSourceUrl; + options.Name = Constants.TestSourceName; + options.TrustLevel = PackageCatalogTrustLevel.Trusted; + options.Explicit = true; + options.Priority = 12; + + await this.AddAndValidatePackageCatalogAsync(options, AddPackageCatalogStatus.Ok); + + // Edit + EditPackageCatalogOptions editOptions = this.TestFactory.CreateEditPackageCatalogOptions(); + editOptions.Name = Constants.TestSourceName; + editOptions.Explicit = false; + editOptions.Priority = 42; + this.EditAndValidatePackageCatalog(editOptions, EditPackageCatalogStatus.Ok); + + // Remove + RemovePackageCatalogOptions removePackageCatalogOptions = this.TestFactory.CreateRemovePackageCatalogOptions(); + removePackageCatalogOptions.Name = Constants.TestSourceName; + var removeCatalogResult = await this.packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); + Assert.IsNotNull(removeCatalogResult); + Assert.AreEqual(RemovePackageCatalogStatus.Ok, removeCatalogResult.Status); + + var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + Assert.IsNull(testSource); + } + + /// + /// Test class Tear down. + /// + [OneTimeTearDown] + public void TestClassTearDown() + { + // Restore the tests source if it was removed as the affects subsequent tests. + TestCommon.SetupTestSource(); + } + + private async Task AddCatalogAndVerifyStatusAsync(AddPackageCatalogOptions addPackageCatalogOptions, AddPackageCatalogStatus expectedStatus, int expectedErrorCode = 0) + { + var addCatalogResult = await this.packageManager.AddPackageCatalogAsync(addPackageCatalogOptions); + Assert.IsNotNull(addCatalogResult); + Assert.AreEqual(expectedStatus, addCatalogResult.Status); + + if (expectedStatus != AddPackageCatalogStatus.Ok && expectedErrorCode != 0) + { + Assert.AreEqual(expectedErrorCode, addCatalogResult.ExtendedErrorCode.HResult); + } + } + + private PackageCatalogReference GetAndValidatePackageCatalog(AddPackageCatalogOptions addPackageCatalogOptions) + { + var packageCatalog = this.packageManager.GetPackageCatalogByName(addPackageCatalogOptions.Name); + + Assert.IsNotNull(packageCatalog); + Assert.AreEqual(addPackageCatalogOptions.Name, packageCatalog.Info.Name); + Assert.AreEqual(addPackageCatalogOptions.SourceUri, packageCatalog.Info.Argument); + Assert.AreEqual(addPackageCatalogOptions.Explicit, packageCatalog.Info.Explicit); + Assert.AreEqual(addPackageCatalogOptions.Priority, packageCatalog.Info.Priority); + + return packageCatalog; + } + + private async Task AddAndValidatePackageCatalogAsync(AddPackageCatalogOptions addPackageCatalogOptions, AddPackageCatalogStatus expectedStatus, int expectedErrorCode = 0) + { + // Add the package catalog and verify the status + var addCatalogResult = await this.packageManager.AddPackageCatalogAsync(addPackageCatalogOptions); + Assert.IsNotNull(addCatalogResult); + Assert.AreEqual(expectedStatus, addCatalogResult.Status); + + if (expectedStatus != AddPackageCatalogStatus.Ok) + { + // Only validate the error code if the status is not Ok and the expected error code is not 0 + if (expectedErrorCode != 0) + { + Assert.AreEqual(expectedErrorCode, addCatalogResult.ExtendedErrorCode.HResult); + } + + return; + } + + // Validate the added package catalog if the status is Ok + this.GetAndValidatePackageCatalog(addPackageCatalogOptions); + } + + private async Task RemoveAndValidatePackageCatalogAsync(RemovePackageCatalogOptions removePackageCatalogOptions, RemovePackageCatalogStatus expectedStatus, int expectedErrorCode = 0) + { + var removeCatalogResult = await this.packageManager.RemovePackageCatalogAsync(removePackageCatalogOptions); + Assert.IsNotNull(removeCatalogResult); + Assert.AreEqual(expectedStatus, removeCatalogResult.Status); + + if (expectedStatus != RemovePackageCatalogStatus.Ok && expectedErrorCode != 0) + { + Assert.AreEqual(expectedErrorCode, removeCatalogResult.ExtendedErrorCode.HResult); + return; + } + + var packageCatalog = this.packageManager.GetPackageCatalogByName(removePackageCatalogOptions.Name); + Assert.IsNull(packageCatalog); + } + + private void EditAndValidatePackageCatalog(EditPackageCatalogOptions editPackageCatalogOptions, EditPackageCatalogStatus expectedStatus, int expectedErrorCode = 0) + { + var editCatalogResult = this.packageManager.EditPackageCatalog(editPackageCatalogOptions); + Assert.IsNotNull(editCatalogResult); + Assert.AreEqual(expectedStatus, editCatalogResult.Status); + + if (expectedStatus != EditPackageCatalogStatus.Ok && expectedErrorCode != 0) + { + Assert.AreEqual(expectedErrorCode, editCatalogResult.ExtendedErrorCode.HResult); + return; + } + + // Verify edits are correct. + var packageCatalog = this.packageManager.GetPackageCatalogByName(editPackageCatalogOptions.Name); + if (editPackageCatalogOptions.Explicit != null) + { + Assert.AreEqual(packageCatalog.Info.Explicit, editPackageCatalogOptions.Explicit); + } + + if (editPackageCatalogOptions.Priority != null) + { + Assert.AreEqual(packageCatalog.Info.Priority, editPackageCatalogOptions.Priority); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs index 7fc265dc18..b563e3cb33 100644 --- a/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/RepairInterop.cs @@ -1,356 +1,356 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Interop -{ - using System; - using System.IO; - using System.Linq; - using System.Text; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; - - /// - /// Repair Interop Tests for COM/WinRT Interop classes. - /// - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] - public class RepairInterop : BaseInterop - { - private string installDir; - private PackageManager packageManager; - private PackageCatalogReference compositeSource; - - /// - /// Initializes a new instance of the class. - /// - /// Initializer. - public RepairInterop(IInstanceInitializer initializer) - : base(initializer) - { - } - - /// - /// Test setup. - /// - [SetUp] - public void Init() - { - this.packageManager = this.TestFactory.CreatePackageManager(); - this.installDir = TestCommon.GetRandomTestDir(); - - // Create a composite source - var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); - var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); - Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); - options.Catalogs.Add(testSource); - options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; - this.compositeSource = this.packageManager.CreateCompositePackageCatalog(options); - } - - /// - /// Test Repair MSI Installer. - /// - /// A representing the asynchronous unit test. - [Test] - public async Task RepairMSIInstaller() - { - string msiInstallerPackageId = "AppInstallerTest.TestMsiRepair"; - var searchResult = await this.FindAndInstallPackage(msiInstallerPackageId, this.installDir, null); - - // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs). - // In our test scenario, the MSI installer file is initially placed in a temporary location and then deleted, which can cause the repair operation to fail. - // To work around this, we copy the installer file to the ARP source directory before running the repair command. - // A more permanent solution would be to modify the MSI installer to cache the installer file in a known location and register that location as the installer source. - // This would allow the 'msiexec repair' command to function as expected. - string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true); - - // Repair the package - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); - - // Cleanup - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); - - if (installerSourceDir != null && Directory.Exists(installerSourceDir)) - { - Directory.Delete(installerSourceDir, true); - } - } - - /// - /// Test Repair MSIX Installer. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairNonStoreMSIXPackage() - { - string msixPackageId = "AppInstallerTest.TestMsixInstaller"; - var searchResult = await this.FindAndInstallPackage(msixPackageId, this.installDir, null); - - // Repair the package - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); - - // Cleanup - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test MSIX non-store package repair with machine scope. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairNonStoreMsixPackageWithMachineScope() - { - var findPackages = this.FindAllPackages(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.ContainsCaseInsensitive, "Microsoft.Paint"); - - if (findPackages == null || findPackages.Count == 0) - { - Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe can't be found."); - } - - var searchResult = findPackages.First(); - - if (searchResult == null || searchResult.CatalogPackage == null || searchResult.CatalogPackage.InstalledVersion == null) - { - Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed."); - } - - // Repair the package - var repairOptions = this.TestFactory.CreateRepairOptions(); - repairOptions.PackageRepairScope = PackageRepairScope.System; - - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); - } - - /// - /// Test repair of a Burn installer that has a "modify" repair behavior specified in the manifest. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairBurnInstallerWithModifyBehavior() - { - var replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepair", useHKLM: true); - var searchResult = await this.FindAndInstallPackage(Constants.ModifyRepairInstaller, this.installDir, replaceInstallerArguments.ToString()); - - // Repair the package - var repairOptions = this.TestFactory.CreateRepairOptions(); - repairOptions.PackageRepairMode = PackageRepairMode.Silent; - - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.Ok); - - // Cleanup - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Modify Repair operation")); - } - - /// - /// Tests the repair operation of a Burn installer that was installed in user scope but is being repaired in an admin context. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairBurnInstallerInAdminContextWithUserScopeInstall() - { - if (this.TestFactory.Context != ClsidContext.InProc) - { - Assert.Ignore("Test is only applicable for InProc context."); - } - - string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestUserScopeInstallRepairInAdminContext"); - var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestUserScopeInstallRepairInAdminContext", this.installDir, replaceInstallerArguments); - - // Repair the package - var repairOptions = this.TestFactory.CreateRepairOptions(); - repairOptions.PackageRepairMode = PackageRepairMode.Silent; - - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); - - // Cleanup - TestCommon.CleanupTestExeAndDirectory(this.installDir); - } - - /// - /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoModify ARP flag set. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairBurnInstallerWithModifyBehaviorAndNoModifyFlag() - { - string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepairWithNoModify", useHKLM: true, noModify: true); - var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestModifyRepairWithNoModify", this.installDir, replaceInstallerArguments); - - // Repair the package - var repairOptions = this.TestFactory.CreateRepairOptions(); - repairOptions.PackageRepairMode = PackageRepairMode.Silent; - - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); - - // Cleanup - TestCommon.CleanupTestExeAndDirectory(this.installDir); - } - - /// - /// Tests the scenario where the repair operation is not supported for Portable Installer type. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairOperationNotSupportedForPortableInstaller() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var searchResult = await this.FindAndInstallPackage(packageId, null, null); - - // Repair the package - var repairOptions = this.TestFactory.CreateRepairOptions(); - repairOptions.PackageRepairMode = PackageRepairMode.Silent; - - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); - - // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairExeInstallerWithUninstallerBehavior() - { - string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepair", useHKLM: true); - var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepair", this.installDir, replaceInstallerArguments); - - // Repair the package - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); - - // Cleanup - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation")); - } - - /// - /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoRepair ARP flag set. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag() - { - string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepairWithNoRepair", useHKLM: true, noRepair: true); - var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepairWithNoRepair", this.installDir, replaceInstallerArguments); - - // Repair the package - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); - - // Cleanup - TestCommon.CleanupTestExeAndDirectory(this.installDir); - } - - /// - /// Test repair of a Nullsoft installer that has a "uninstaller" repair behavior specified in the manifest. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairNullsoftInstallerWithUninstallerBehavior() - { - string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "NullsoftUninstallerRepair", useHKLM: true); - var searchResult = await this.FindAndInstallPackage("AppInstallerTest.NullsoftUninstallerRepair", this.installDir, replaceInstallerArguments.ToString()); - - // Repair the package - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); - - // Cleanup - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation")); - } - - /// - /// Test repair of a Inno installer that has a "installer" repair behavior specified in the manifest. - /// - /// representing the asynchronous unit test. - [Test] - public async Task RepairInnoInstallerWithInstallerRepairBehavior() - { - string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestInstallerRepair", useHKLM: true); - var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestInstallerRepair", this.installDir, replaceInstallerArguments); - - // Repair the package - await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); - - // Cleanup - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Installer Repair operation")); - } - - private async Task FindAndInstallPackage(string packageId, string installDir, string replacementInstallerArguments) - { - // Find a package - var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - - if (!string.IsNullOrEmpty(installDir)) - { - installOptions.PreferredInstallLocation = installDir; - } - - if (!string.IsNullOrEmpty(replacementInstallerArguments)) - { - installOptions.ReplacementInstallerArguments = replacementInstallerArguments; - } - - // Install the package - var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - - // Find package again, but this time it should be detected as installed - searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); - Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); - - // Return the search result - return searchResult; - } - - private async Task RepairPackageAndValidateStatus(CatalogPackage package, RepairOptions repairOptions, RepairResultStatus expectedStatus, int expectedErrorCode = 0) - { - var repairResult = await this.packageManager.RepairPackageAsync(package, repairOptions); - Assert.AreEqual(expectedStatus, repairResult.Status); - - if (expectedStatus != RepairResultStatus.Ok) - { - Assert.AreEqual(expectedErrorCode, repairResult.ExtendedErrorCode.HResult); - } - } - - private string GetReplacementArguments(string installDir, string version, string displayName, bool useHKLM = false, bool noModify = false, bool noRepair = false) - { - var replacementArguments = new StringBuilder($"/InstallDir {installDir} /Version {version} /DisplayName {displayName}"); - - // Machine scope install. - if (useHKLM) - { - replacementArguments.Append($" /UseHKLM"); - } - - // Instructs test installer to set NoModify ARP flag. - if (noModify) - { - replacementArguments.Append($" /NoModify"); - } - - // Instructs test installer to set NoRepair ARP flag. - if (noRepair) - { - replacementArguments.Append($" /NoRepair"); - } - - return replacementArguments.ToString(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.IO; + using System.Linq; + using System.Text; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + + /// + /// Repair Interop Tests for COM/WinRT Interop classes. + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class RepairInterop : BaseInterop + { + private string installDir; + private PackageManager packageManager; + private PackageCatalogReference compositeSource; + + /// + /// Initializes a new instance of the class. + /// + /// Initializer. + public RepairInterop(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Test setup. + /// + [SetUp] + public void Init() + { + this.packageManager = this.TestFactory.CreatePackageManager(); + this.installDir = TestCommon.GetRandomTestDir(); + + // Create a composite source + var options = this.TestFactory.CreateCreateCompositePackageCatalogOptions(); + var testSource = this.packageManager.GetPackageCatalogByName(Constants.TestSourceName); + Assert.NotNull(testSource, $"{Constants.TestSourceName} cannot be null"); + options.Catalogs.Add(testSource); + options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; + this.compositeSource = this.packageManager.CreateCompositePackageCatalog(options); + } + + /// + /// Test Repair MSI Installer. + /// + /// A representing the asynchronous unit test. + [Test] + public async Task RepairMSIInstaller() + { + string msiInstallerPackageId = "AppInstallerTest.TestMsiRepair"; + var searchResult = await this.FindAndInstallPackage(msiInstallerPackageId, this.installDir, null); + + // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs). + // In our test scenario, the MSI installer file is initially placed in a temporary location and then deleted, which can cause the repair operation to fail. + // To work around this, we copy the installer file to the ARP source directory before running the repair command. + // A more permanent solution would be to modify the MSI installer to cache the installer file in a known location and register that location as the installer source. + // This would allow the 'msiexec repair' command to function as expected. + string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(this.installDir)); + + if (installerSourceDir != null && Directory.Exists(installerSourceDir)) + { + Directory.Delete(installerSourceDir, true); + } + } + + /// + /// Test Repair MSIX Installer. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairNonStoreMSIXPackage() + { + string msixPackageId = "AppInstallerTest.TestMsixInstaller"; + var searchResult = await this.FindAndInstallPackage(msixPackageId, this.installDir, null); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test MSIX non-store package repair with machine scope. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairNonStoreMsixPackageWithMachineScope() + { + var findPackages = this.FindAllPackages(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.ContainsCaseInsensitive, "Microsoft.Paint"); + + if (findPackages == null || findPackages.Count == 0) + { + Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe can't be found."); + } + + var searchResult = findPackages.First(); + + if (searchResult == null || searchResult.CatalogPackage == null || searchResult.CatalogPackage.InstalledVersion == null) + { + Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed."); + } + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairScope = PackageRepairScope.System; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_INSTALL_SYSTEM_NOT_SUPPORTED); + } + + /// + /// Test repair of a Burn installer that has a "modify" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairBurnInstallerWithModifyBehavior() + { + var replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage(Constants.ModifyRepairInstaller, this.installDir, replaceInstallerArguments.ToString()); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Modify Repair operation")); + } + + /// + /// Tests the repair operation of a Burn installer that was installed in user scope but is being repaired in an admin context. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairBurnInstallerInAdminContextWithUserScopeInstall() + { + if (this.TestFactory.Context != ClsidContext.InProc) + { + Assert.Ignore("Test is only applicable for InProc context."); + } + + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestUserScopeInstallRepairInAdminContext"); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestUserScopeInstallRepairInAdminContext", this.installDir, replaceInstallerArguments); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED); + + // Cleanup + TestCommon.CleanupTestExeAndDirectory(this.installDir); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoModify ARP flag set. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairBurnInstallerWithModifyBehaviorAndNoModifyFlag() + { + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestModifyRepairWithNoModify", useHKLM: true, noModify: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestModifyRepairWithNoModify", this.installDir, replaceInstallerArguments); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); + + // Cleanup + TestCommon.CleanupTestExeAndDirectory(this.installDir); + } + + /// + /// Tests the scenario where the repair operation is not supported for Portable Installer type. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairOperationNotSupportedForPortableInstaller() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var searchResult = await this.FindAndInstallPackage(packageId, null, null); + + // Repair the package + var repairOptions = this.TestFactory.CreateRepairOptions(); + repairOptions.PackageRepairMode = PackageRepairMode.Silent; + + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, repairOptions, RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); + + // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairExeInstallerWithUninstallerBehavior() + { + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepair", this.installDir, replaceInstallerArguments); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation")); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoRepair ARP flag set. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag() + { + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "UninstallerRepairWithNoRepair", useHKLM: true, noRepair: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.UninstallerRepairWithNoRepair", this.installDir, replaceInstallerArguments); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.RepairError, Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED); + + // Cleanup + TestCommon.CleanupTestExeAndDirectory(this.installDir); + } + + /// + /// Test repair of a Nullsoft installer that has a "uninstaller" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairNullsoftInstallerWithUninstallerBehavior() + { + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "NullsoftUninstallerRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.NullsoftUninstallerRepair", this.installDir, replaceInstallerArguments.ToString()); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Uninstaller Repair operation")); + } + + /// + /// Test repair of a Inno installer that has a "installer" repair behavior specified in the manifest. + /// + /// representing the asynchronous unit test. + [Test] + public async Task RepairInnoInstallerWithInstallerRepairBehavior() + { + string replaceInstallerArguments = this.GetReplacementArguments(this.installDir, "2.0.0.0", "TestInstallerRepair", useHKLM: true); + var searchResult = await this.FindAndInstallPackage("AppInstallerTest.TestInstallerRepair", this.installDir, replaceInstallerArguments); + + // Repair the package + await this.RepairPackageAndValidateStatus(searchResult.CatalogPackage, this.TestFactory.CreateRepairOptions(), RepairResultStatus.Ok); + + // Cleanup + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(this.installDir, "Installer Repair operation")); + } + + private async Task FindAndInstallPackage(string packageId, string installDir, string replacementInstallerArguments) + { + // Find a package + var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + + if (!string.IsNullOrEmpty(installDir)) + { + installOptions.PreferredInstallLocation = installDir; + } + + if (!string.IsNullOrEmpty(replacementInstallerArguments)) + { + installOptions.ReplacementInstallerArguments = replacementInstallerArguments; + } + + // Install the package + var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should be detected as installed + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); + Assert.NotNull(searchResult.CatalogPackage.InstalledVersion); + + // Return the search result + return searchResult; + } + + private async Task RepairPackageAndValidateStatus(CatalogPackage package, RepairOptions repairOptions, RepairResultStatus expectedStatus, int expectedErrorCode = 0) + { + var repairResult = await this.packageManager.RepairPackageAsync(package, repairOptions); + Assert.AreEqual(expectedStatus, repairResult.Status); + + if (expectedStatus != RepairResultStatus.Ok) + { + Assert.AreEqual(expectedErrorCode, repairResult.ExtendedErrorCode.HResult); + } + } + + private string GetReplacementArguments(string installDir, string version, string displayName, bool useHKLM = false, bool noModify = false, bool noRepair = false) + { + var replacementArguments = new StringBuilder($"/InstallDir {installDir} /Version {version} /DisplayName {displayName}"); + + // Machine scope install. + if (useHKLM) + { + replacementArguments.Append($" /UseHKLM"); + } + + // Instructs test installer to set NoModify ARP flag. + if (noModify) + { + replacementArguments.Append($" /NoModify"); + } + + // Instructs test installer to set NoRepair ARP flag. + if (noRepair) + { + replacementArguments.Append($" /NoRepair"); + } + + return replacementArguments.ToString(); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/Shutdown.cs b/src/AppInstallerCLIE2ETests/Interop/Shutdown.cs index be7eef3612..6a443e2b27 100644 --- a/src/AppInstallerCLIE2ETests/Interop/Shutdown.cs +++ b/src/AppInstallerCLIE2ETests/Interop/Shutdown.cs @@ -1,134 +1,134 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.Interop -{ - using System; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Management.Deployment; - using Microsoft.Management.Deployment.Projection; - using NUnit.Framework; - using WinGetTestCommon; - - /// - /// Shutdown testing. - /// - [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] - public class Shutdown : BaseInterop - { - /// - /// Initializes a new instance of the class. - /// - /// Initializer. - public Shutdown(IInstanceInitializer initializer) - : base(initializer) - { - } - - /// - /// Checks that shutdown will proceed even though an object is active. - /// - [Test] - public void NoActiveOperations() - { - var packageManager = this.TestFactory.CreatePackageManager(); - - var servers = WinGetServerInstance.GetInstances(); - Assert.AreEqual(1, servers.Count); - - var server = servers[0]; - Assert.IsTrue(server.HasWindow); - - // This is the call pattern from Windows - this.SendMessageAndLog(server, WindowMessage.QueryEndSession); - this.SendMessageAndLog(server, WindowMessage.EndSession); - this.SendMessageAndLog(server, WindowMessage.Close); - - Assert.IsTrue(server.Process.WaitForExit(5000)); - } - - /// - /// Checks that shutdown will proceed even though an operation is active. - /// - /// The task. - [Test] - public async Task ActiveInstallOperation() - { - var packageManager = this.TestFactory.CreatePackageManager(); - var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); - var installDir = TestCommon.GetRandomTestDir(); - - var servers = WinGetServerInstance.GetInstances(); - Assert.AreEqual(1, servers.Count); - - var server = servers[0]; - Assert.IsTrue(server.HasWindow); - - // Find package - var searchResult = this.FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); - - // Configure installation - var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PackageInstallMode = PackageInstallMode.Silent; - installOptions.PreferredInstallLocation = installDir; - - // Install - var installOperation = packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - - // This is the call pattern from Windows - this.SendMessageAndLog(server, WindowMessage.QueryEndSession); - this.SendMessageAndLog(server, WindowMessage.EndSession); - this.SendMessageAndLog(server, WindowMessage.Close); - - Assert.IsTrue(server.Process.WaitForExit(5000)); - - InstallResult installResult = null; - Exception exception = null; - - try - { - installResult = await installOperation; - } - catch (Exception ex) - { - exception = ex; - } - - // We just expect some kind of signal to indicate the failed attempt. - if (installResult != null) - { - Assert.AreNotEqual(InstallResultStatus.Ok, installResult.Status); - } - else - { - Assert.NotNull(exception); - } - - Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); - } - - private void SendMessageAndLog(WinGetServerInstance server, WindowMessage message) - { - TestContext.Out.WriteLine($"Sending message {message} to process {server.Process.Id}..."); - try - { - if (server.SendMessage(message)) - { - TestContext.Out.WriteLine("... succeeded."); - } - else - { - TestContext.Out.WriteLine("... failed."); - } - } - catch (Exception e) - { - TestContext.Out.WriteLine($"... had exception: {e.Message}"); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.Interop +{ + using System; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Management.Deployment; + using Microsoft.Management.Deployment.Projection; + using NUnit.Framework; + using WinGetTestCommon; + + /// + /// Shutdown testing. + /// + [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] + public class Shutdown : BaseInterop + { + /// + /// Initializes a new instance of the class. + /// + /// Initializer. + public Shutdown(IInstanceInitializer initializer) + : base(initializer) + { + } + + /// + /// Checks that shutdown will proceed even though an object is active. + /// + [Test] + public void NoActiveOperations() + { + var packageManager = this.TestFactory.CreatePackageManager(); + + var servers = WinGetServerInstance.GetInstances(); + Assert.AreEqual(1, servers.Count); + + var server = servers[0]; + Assert.IsTrue(server.HasWindow); + + // This is the call pattern from Windows + this.SendMessageAndLog(server, WindowMessage.QueryEndSession); + this.SendMessageAndLog(server, WindowMessage.EndSession); + this.SendMessageAndLog(server, WindowMessage.Close); + + Assert.IsTrue(server.Process.WaitForExit(5000)); + } + + /// + /// Checks that shutdown will proceed even though an operation is active. + /// + /// The task. + [Test] + public async Task ActiveInstallOperation() + { + var packageManager = this.TestFactory.CreatePackageManager(); + var testSource = packageManager.GetPackageCatalogByName(Constants.TestSourceName); + var installDir = TestCommon.GetRandomTestDir(); + + var servers = WinGetServerInstance.GetInstances(); + Assert.AreEqual(1, servers.Count); + + var server = servers[0]; + Assert.IsTrue(server.HasWindow); + + // Find package + var searchResult = this.FindOnePackage(testSource, PackageMatchField.Name, PackageFieldMatchOption.Equals, "InapplicableOsVersion"); + + // Configure installation + var installOptions = this.TestFactory.CreateInstallOptions(); + installOptions.PackageInstallMode = PackageInstallMode.Silent; + installOptions.PreferredInstallLocation = installDir; + + // Install + var installOperation = packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); + + // This is the call pattern from Windows + this.SendMessageAndLog(server, WindowMessage.QueryEndSession); + this.SendMessageAndLog(server, WindowMessage.EndSession); + this.SendMessageAndLog(server, WindowMessage.Close); + + Assert.IsTrue(server.Process.WaitForExit(5000)); + + InstallResult installResult = null; + Exception exception = null; + + try + { + installResult = await installOperation; + } + catch (Exception ex) + { + exception = ex; + } + + // We just expect some kind of signal to indicate the failed attempt. + if (installResult != null) + { + Assert.AreNotEqual(InstallResultStatus.Ok, installResult.Status); + } + else + { + Assert.NotNull(exception); + } + + Assert.False(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + } + + private void SendMessageAndLog(WinGetServerInstance server, WindowMessage message) + { + TestContext.Out.WriteLine($"Sending message {message} to process {server.Process.Id}..."); + try + { + if (server.SendMessage(message)) + { + TestContext.Out.WriteLine("... succeeded."); + } + else + { + TestContext.Out.WriteLine("... failed."); + } + } + catch (Exception e) + { + TestContext.Out.WriteLine($"... had exception: {e.Message}"); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs b/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs index d723416c7d..65a9d2a393 100644 --- a/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/UninstallInterop.cs @@ -277,8 +277,8 @@ public async Task UninstallPortableModifiedSymlink() Assert.True(modifiedSymlinkInfo.Exists, "Modified symlink should still exist"); // Remove modified symlink as to not interfere with other tests - modifiedSymlinkInfo.Delete(); - + modifiedSymlinkInfo.Delete(); + // Uninstall again to clean up. await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, this.TestFactory.CreateUninstallOptions()); } diff --git a/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs b/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs index 51402f6817..5b528506f2 100644 --- a/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs +++ b/src/AppInstallerCLIE2ETests/Interop/UpgradeInterop.cs @@ -1,42 +1,42 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.Interop { using System; using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading.Tasks; - using AppInstallerCLIE2ETests.Helpers; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + using AppInstallerCLIE2ETests.Helpers; using Microsoft.Management.Deployment; using Microsoft.Management.Deployment.Projection; using NUnit.Framework; - /// - /// Test upgrade interop. + /// + /// Test upgrade interop. /// [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.InProcess), Category = nameof(InstanceInitializersSource.InProcess))] [TestFixtureSource(typeof(InstanceInitializersSource), nameof(InstanceInitializersSource.OutOfProcess), Category = nameof(InstanceInitializersSource.OutOfProcess))] public class UpgradeInterop : BaseInterop { private PackageManager packageManager; - private PackageCatalogReference compositeSource; - - /// - /// Initializes a new instance of the class. - /// - /// Initializer. + private PackageCatalogReference compositeSource; + + /// + /// Initializes a new instance of the class. + /// + /// Initializer. public UpgradeInterop(IInstanceInitializer initializer) : base(initializer) { } - /// - /// Set up. + /// + /// Set up. /// [SetUp] public void Init() @@ -50,11 +50,11 @@ public void Init() options.Catalogs.Add(testSource); options.CompositeSearchBehavior = CompositeSearchBehavior.AllCatalogs; this.compositeSource = this.packageManager.CreateCompositePackageCatalog(options); - } - - /// - /// Tests upgrade portable package. - /// + } + + /// + /// Tests upgrade portable package. + /// /// A representing the asynchronous unit test. [Test] public async Task UpgradePortable() @@ -95,9 +95,9 @@ public async Task UpgradePortable() TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } - /// - /// Test upgrade portable package with arp mismatch. - /// + /// + /// Test upgrade portable package with arp mismatch. + /// /// A representing the asynchronous unit test. [Test] public async Task UpgradePortableARPMismatch() @@ -142,9 +142,9 @@ public async Task UpgradePortableARPMismatch() TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } - /// - /// Test upgrade portable package force. - /// + /// + /// Test upgrade portable package force. + /// /// A representing the asynchronous unit test. [Test] public async Task UpgradePortableForcedOverride() @@ -190,9 +190,9 @@ public async Task UpgradePortableForcedOverride() TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); } - /// - /// Test upgrade portable package uninstall previous. - /// + /// + /// Test upgrade portable package uninstall previous. + /// /// A representing the asynchronous unit test. [Test] public async Task UpgradePortableUninstallPrevious() @@ -231,68 +231,68 @@ public async Task UpgradePortableUninstallPrevious() searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, packageId); Assert.AreEqual("3.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version); TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Tests IsUpdateAvailable. - /// + } + + /// + /// Tests IsUpdateAvailable. + /// /// A representing the asynchronous unit test. [Test] public async Task TestIsUpdateAvailable_ApplicableTrue() - { - // Find and install the test package. Install the version 1.0.0.0. + { + // Find and install the test package. Install the version 1.0.0.0. var installDir = TestCommon.GetRandomTestDir(); var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = installDir; installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0"); var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - - // Find package again, but this time it should detect the installed version. - searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); - + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version. + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestExeInstaller"); + // The installed version is 1.0.0.0. - Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version); - - // IsUpdateAvailable is true. - Assert.True(searchResult.CatalogPackage.IsUpdateAvailable); - - // Uninstall to clean up. - var uninstallOptions = this.TestFactory.CreateUninstallOptions(); + Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version); + + // IsUpdateAvailable is true. + Assert.True(searchResult.CatalogPackage.IsUpdateAvailable); + + // Uninstall to clean up. + var uninstallOptions = this.TestFactory.CreateUninstallOptions(); var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, uninstallOptions); - } - - /// - /// Tests applicability check is performed for IsUpdateAvailable api. - /// + } + + /// + /// Tests applicability check is performed for IsUpdateAvailable api. + /// /// A representing the asynchronous unit test. [Test] public async Task TestIsUpdateAvailable_ApplicableFalse() - { - // Find and install the test package. Install the version 1.0.0.0. + { + // Find and install the test package. Install the version 1.0.0.0. var installDir = TestCommon.GetRandomTestDir(); var searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestUpgradeApplicability"); var installOptions = this.TestFactory.CreateInstallOptions(); - installOptions.PreferredInstallLocation = installDir; + installOptions.PreferredInstallLocation = installDir; installOptions.PackageVersionId = First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "1.0.0.0"); var installResult = await this.packageManager.InstallPackageAsync(searchResult.CatalogPackage, installOptions); - Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); - - // Find package again, but this time it should detect the installed version. - searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestUpgradeApplicability"); - + Assert.AreEqual(InstallResultStatus.Ok, installResult.Status); + + // Find package again, but this time it should detect the installed version. + searchResult = this.FindOnePackage(this.compositeSource, PackageMatchField.Id, PackageFieldMatchOption.Equals, "AppInstallerTest.TestUpgradeApplicability"); + // The installed version is 1.0.0.0. - Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version); - - // There is version 2.0.0.0 in the package available versions. - Assert.NotNull(First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "2.0.0.0")); - - // IsUpdateAvailable is false due to applicability check. Only arm64 in version 2.0.0.0. - Assert.False(searchResult.CatalogPackage.IsUpdateAvailable); - - // Uninstall to clean up. - var uninstallOptions = this.TestFactory.CreateUninstallOptions(); + Assert.AreEqual("1.0.0.0", searchResult.CatalogPackage.InstalledVersion.Version); + + // There is version 2.0.0.0 in the package available versions. + Assert.NotNull(First(searchResult.CatalogPackage.AvailableVersions, i => i.Version == "2.0.0.0")); + + // IsUpdateAvailable is false due to applicability check. Only arm64 in version 2.0.0.0. + Assert.False(searchResult.CatalogPackage.IsUpdateAvailable); + + // Uninstall to clean up. + var uninstallOptions = this.TestFactory.CreateUninstallOptions(); var uninstallResult = await this.packageManager.UninstallPackageAsync(searchResult.CatalogPackage, uninstallOptions); } diff --git a/src/AppInstallerCLIE2ETests/ListCommand.cs b/src/AppInstallerCLIE2ETests/ListCommand.cs index b0fe1ba68a..35441b6741 100644 --- a/src/AppInstallerCLIE2ETests/ListCommand.cs +++ b/src/AppInstallerCLIE2ETests/ListCommand.cs @@ -1,259 +1,259 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// List command tests. - /// - public class ListCommand : BaseCommand - { - /// - /// Test list winget. - /// - [Test] - public void ListSelf() - { - var result = TestCommon.RunAICLICommand("list", Constants.AICLIPackageFamilyName); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.AICLIPackageName)); - Assert.True(result.StdOut.Contains(Constants.AICLIPackagePublisherHash)); - } - - /// - /// Test list after installing a package. - /// - [Test] - public void ListAfterInstall() - { - System.Guid guid = System.Guid.NewGuid(); - string productCode = guid.ToString(); - var installDir = TestCommon.GetRandomTestDir(); - - // DisplayName must be set to avoid conflicts with other packages that use the same exe installer. - string displayName = "TestExeInstaller"; - string localAppDataPath = System.Environment.GetEnvironmentVariable(Constants.LocalAppData); - string logFilePath = System.IO.Path.Combine(localAppDataPath, Constants.E2ETestLogsPathPackaged); - logFilePath = System.IO.Path.Combine(logFilePath, "ListAfterInstall-" + System.IO.Path.GetRandomFileName() + ".log"); - - var result = TestCommon.RunAICLICommand("list", productCode); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - - result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --override \"/InstallDir {installDir} /ProductID {productCode} /LogFile {logFilePath} /DisplayName {displayName}\""); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - result = TestCommon.RunAICLICommand("list", productCode); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); - Assert.True(result.StdOut.Contains("1.0.0.0")); - Assert.True(result.StdOut.Contains("2.0.0.0")); - } - - /// - /// Test expected entries after list. - /// - [Test] - public void ListWithArpVersionMapping() - { - // No mapping performed - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameVersion", "TestArpVersionSameVersion", "0.5", "0.5", "< 1.0"); - - // Partial mapping performed(i.e. only if version falls within arp version range) - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "10.1", "1.0", "10.1"); - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "9.9", "9.9", "> 2.0"); - - // Full mapping performed - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "7.0", "< 1.0", "7.0"); - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.1", "1.0", "10.1"); - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.7", "< 2.0", "10.7"); - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "11.1", "2.0", "11.1"); - this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "12.0", "> 2.0", "12.0"); - } - - /// - /// Test list with upgrade code. - /// - [Test] - public void ListWithUpgradeCode() - { - // Installs the MSI installer using the TestMsiInstaller package. - // Then tries listing the TestMsiInstallerUpgradeCode package, which should - // be correlated to it by the UpgradeCode. - if (string.IsNullOrEmpty(TestIndex.MsiInstaller)) - { - Assert.Ignore("MSI installer not available"); - } - - var installDir = TestCommon.GetRandomTestDir(); - Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("install", $"TestMsiInstaller --silent -l {installDir}").ExitCode); - - var result = TestCommon.RunAICLICommand("list", "TestMsiInstallerUpgradeCode"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestMsiInstallerUpgradeCode")); - } - - /// - /// Test list with exe installed with machine scope. - /// - [Test] - public void ListWithScopeExeInstalledAsMachine() - { - System.Guid guid = System.Guid.NewGuid(); - string identifier = "AppInstallerTest.TestExeInstaller"; - string productCode = guid.ToString(); - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"{identifier} --override \"/InstallDir {installDir} /ProductID {productCode} /UseHKLM\""); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // List with user scope will not find the package - result = TestCommon.RunAICLICommand("list", $"{productCode} --scope user"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.False(result.StdOut.Contains(identifier)); - - // List with machine scope will find the package - result = TestCommon.RunAICLICommand("list", $"{productCode} --scope machine"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(identifier)); - - TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); - } - - /// - /// Test list with exe installed with user scope. - /// - [Test] - public void ListWithScopeExeInstalledAsUser() - { - System.Guid guid = System.Guid.NewGuid(); - string identifier = "AppInstallerTest.TestExeInstaller"; - string productCode = guid.ToString(); - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"{identifier} --override \"/InstallDir {installDir} /ProductID {productCode}\""); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // List with user scope will find the package - result = TestCommon.RunAICLICommand("list", $"{productCode} --scope user"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(identifier)); - - // List with machine scope will not find the package - result = TestCommon.RunAICLICommand("list", $"{productCode} --scope machine"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.False(result.StdOut.Contains(identifier)); - - TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); - } - - /// - /// Test list with msix installed with machine scope. - /// - [Test] - public void ListWithScopeMsixInstalledAsMachine() - { - // TODO: Provision and Deprovision api not supported in build server. - Assert.Ignore(); - - var result = TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope machine"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // List with user scope will also find the package because msix is provisioned for all users - result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope user"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); - - // List with machine scope will find the package - result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope machine"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); - - TestCommon.RemoveMsix(Constants.MsixInstallerName, true); - } - - /// - /// Test list with msix installed with user scope. - /// - [Test] - public void ListWithScopeMsixInstalledAsUser() - { - var result = TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope user"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // List with user scope will find the package - result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope user"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); - - // List with machine scope will not find the package - result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope machine"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.False(result.StdOut.Contains(Constants.MsixInstallerPackageId)); - - TestCommon.RemoveMsix(Constants.MsixInstallerName); - } - - /// - /// Test package correlation with same package name but different architecture is correct. - /// - [Test] - public void ListWithMappingWithArchitecture() - { - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMappingWithArchitectureX86 -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - // List with AppInstallerTest.TestMappingWithArchitectureX64 (from available to installed scenario) will not find the package. - result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestMappingWithArchitectureX64"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.False(result.StdOut.Contains("AppInstallerTest.TestMappingWithArchitectureX64")); - - // List with AppInstallerTest.TestMappingWithArchitectureX86 (from available to installed scenario) will find the package. - result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestMappingWithArchitectureX86"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestMappingWithArchitectureX86")); - - // List with product code (from installed to available scenario) will find the AppInstallerTest.TestMappingWithArchitectureX86 package. - result = TestCommon.RunAICLICommand("list", "{0e426f01-b682-4e67-a357-52f9ecb4590d}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestMappingWithArchitectureX86")); - - // best effort clean up - TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); - } - - private void ArpVersionMappingTest(string packageIdentifier, string displayNameOverride, string displayVersionOverride, string expectedListVersion, string notExpectedListVersion = "") - { - System.Guid guid = System.Guid.NewGuid(); - string productCode = guid.ToString(); - var installDir = TestCommon.GetRandomTestDir(); - - var result = TestCommon.RunAICLICommand("list", productCode); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - - result = TestCommon.RunAICLICommand("install", $"{packageIdentifier} --override \"/InstallDir {installDir} /ProductID {productCode} /DisplayName {displayNameOverride} /Version {displayVersionOverride}\""); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - result = TestCommon.RunAICLICommand("list", productCode); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - Assert.True(result.StdOut.Contains(packageIdentifier)); - Assert.True(result.StdOut.Contains(expectedListVersion)); - if (!string.IsNullOrEmpty(notExpectedListVersion)) - { - Assert.False(result.StdOut.Contains(notExpectedListVersion)); - } - - // Try clean up - if (File.Exists(Path.Combine(installDir, Constants.TestExeInstalledFileName))) - { - TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// List command tests. + /// + public class ListCommand : BaseCommand + { + /// + /// Test list winget. + /// + [Test] + public void ListSelf() + { + var result = TestCommon.RunAICLICommand("list", Constants.AICLIPackageFamilyName); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.AICLIPackageName)); + Assert.True(result.StdOut.Contains(Constants.AICLIPackagePublisherHash)); + } + + /// + /// Test list after installing a package. + /// + [Test] + public void ListAfterInstall() + { + System.Guid guid = System.Guid.NewGuid(); + string productCode = guid.ToString(); + var installDir = TestCommon.GetRandomTestDir(); + + // DisplayName must be set to avoid conflicts with other packages that use the same exe installer. + string displayName = "TestExeInstaller"; + string localAppDataPath = System.Environment.GetEnvironmentVariable(Constants.LocalAppData); + string logFilePath = System.IO.Path.Combine(localAppDataPath, Constants.E2ETestLogsPathPackaged); + logFilePath = System.IO.Path.Combine(logFilePath, "ListAfterInstall-" + System.IO.Path.GetRandomFileName() + ".log"); + + var result = TestCommon.RunAICLICommand("list", productCode); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + + result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestExeInstaller --override \"/InstallDir {installDir} /ProductID {productCode} /LogFile {logFilePath} /DisplayName {displayName}\""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("list", productCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); + Assert.True(result.StdOut.Contains("1.0.0.0")); + Assert.True(result.StdOut.Contains("2.0.0.0")); + } + + /// + /// Test expected entries after list. + /// + [Test] + public void ListWithArpVersionMapping() + { + // No mapping performed + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameVersion", "TestArpVersionSameVersion", "0.5", "0.5", "< 1.0"); + + // Partial mapping performed(i.e. only if version falls within arp version range) + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "10.1", "1.0", "10.1"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionOppositeOrder", "TestArpVersionOppositeOrder", "9.9", "9.9", "> 2.0"); + + // Full mapping performed + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "7.0", "< 1.0", "7.0"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.1", "1.0", "10.1"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "10.7", "< 2.0", "10.7"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "11.1", "2.0", "11.1"); + this.ArpVersionMappingTest("AppInstallerTest.TestArpVersionSameOrder", "TestArpVersionSameOrder", "12.0", "> 2.0", "12.0"); + } + + /// + /// Test list with upgrade code. + /// + [Test] + public void ListWithUpgradeCode() + { + // Installs the MSI installer using the TestMsiInstaller package. + // Then tries listing the TestMsiInstallerUpgradeCode package, which should + // be correlated to it by the UpgradeCode. + if (string.IsNullOrEmpty(TestIndex.MsiInstaller)) + { + Assert.Ignore("MSI installer not available"); + } + + var installDir = TestCommon.GetRandomTestDir(); + Assert.AreEqual(Constants.ErrorCode.S_OK, TestCommon.RunAICLICommand("install", $"TestMsiInstaller --silent -l {installDir}").ExitCode); + + var result = TestCommon.RunAICLICommand("list", "TestMsiInstallerUpgradeCode"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestMsiInstallerUpgradeCode")); + } + + /// + /// Test list with exe installed with machine scope. + /// + [Test] + public void ListWithScopeExeInstalledAsMachine() + { + System.Guid guid = System.Guid.NewGuid(); + string identifier = "AppInstallerTest.TestExeInstaller"; + string productCode = guid.ToString(); + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"{identifier} --override \"/InstallDir {installDir} /ProductID {productCode} /UseHKLM\""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will not find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope user"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(result.StdOut.Contains(identifier)); + + // List with machine scope will find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(identifier)); + + TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + + /// + /// Test list with exe installed with user scope. + /// + [Test] + public void ListWithScopeExeInstalledAsUser() + { + System.Guid guid = System.Guid.NewGuid(); + string identifier = "AppInstallerTest.TestExeInstaller"; + string productCode = guid.ToString(); + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"{identifier} --override \"/InstallDir {installDir} /ProductID {productCode}\""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(identifier)); + + // List with machine scope will not find the package + result = TestCommon.RunAICLICommand("list", $"{productCode} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(result.StdOut.Contains(identifier)); + + TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + + /// + /// Test list with msix installed with machine scope. + /// + [Test] + public void ListWithScopeMsixInstalledAsMachine() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + var result = TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will also find the package because msix is provisioned for all users + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + // List with machine scope will find the package + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + TestCommon.RemoveMsix(Constants.MsixInstallerName, true); + } + + /// + /// Test list with msix installed with user scope. + /// + [Test] + public void ListWithScopeMsixInstalledAsUser() + { + var result = TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with user scope will find the package + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope user"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + // List with machine scope will not find the package + result = TestCommon.RunAICLICommand("list", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(result.StdOut.Contains(Constants.MsixInstallerPackageId)); + + TestCommon.RemoveMsix(Constants.MsixInstallerName); + } + + /// + /// Test package correlation with same package name but different architecture is correct. + /// + [Test] + public void ListWithMappingWithArchitecture() + { + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMappingWithArchitectureX86 -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + // List with AppInstallerTest.TestMappingWithArchitectureX64 (from available to installed scenario) will not find the package. + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestMappingWithArchitectureX64"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.False(result.StdOut.Contains("AppInstallerTest.TestMappingWithArchitectureX64")); + + // List with AppInstallerTest.TestMappingWithArchitectureX86 (from available to installed scenario) will find the package. + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestMappingWithArchitectureX86"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestMappingWithArchitectureX86")); + + // List with product code (from installed to available scenario) will find the AppInstallerTest.TestMappingWithArchitectureX86 package. + result = TestCommon.RunAICLICommand("list", "{0e426f01-b682-4e67-a357-52f9ecb4590d}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestMappingWithArchitectureX86")); + + // best effort clean up + TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + + private void ArpVersionMappingTest(string packageIdentifier, string displayNameOverride, string displayVersionOverride, string expectedListVersion, string notExpectedListVersion = "") + { + System.Guid guid = System.Guid.NewGuid(); + string productCode = guid.ToString(); + var installDir = TestCommon.GetRandomTestDir(); + + var result = TestCommon.RunAICLICommand("list", productCode); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + + result = TestCommon.RunAICLICommand("install", $"{packageIdentifier} --override \"/InstallDir {installDir} /ProductID {productCode} /DisplayName {displayNameOverride} /Version {displayVersionOverride}\""); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("list", productCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + Assert.True(result.StdOut.Contains(packageIdentifier)); + Assert.True(result.StdOut.Contains(expectedListVersion)); + if (!string.IsNullOrEmpty(notExpectedListVersion)) + { + Assert.False(result.StdOut.Contains(notExpectedListVersion)); + } + + // Try clean up + if (File.Exists(Path.Combine(installDir, Constants.TestExeInstalledFileName))) + { + TestCommon.RunCommand(Path.Combine(installDir, Constants.TestExeUninstallerFileName)); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Pinning.cs b/src/AppInstallerCLIE2ETests/Pinning.cs index e2964a8653..b95e3a696b 100644 --- a/src/AppInstallerCLIE2ETests/Pinning.cs +++ b/src/AppInstallerCLIE2ETests/Pinning.cs @@ -1,238 +1,238 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - using static AppInstallerCLIE2ETests.Helpers.TestCommon; - - /// - /// Test upgrading pinned packages. - /// - public class Pinning : BaseCommand - { - /// - /// Set up for all tests. - /// - [SetUp] - public void Setup() - { - // All tests use TestExeInstaller; try to clean it up for failure cases, - // then install the base version for pinning - TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); - TestCommon.RunAICLICommand("install", "AppInstallerTest.TestExeInstaller -v 1.0.0.0"); - TestCommon.RunAICLICommand("pin remove", "AppInstallerTest.TestExeInstaller"); - } - - /// - /// Clean up done after all the tests here. - /// - [OneTimeTearDown] - public void OneTimeTearDown() - { - TestCommon.RunAICLICommand("pin remove", "AppInstallerTest.TestExeInstaller"); - TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); - } - - // All tests do roughly the same with different types of pins: - // * Check that the available version shown by list is the latest - // * Check that the available version shown by upgrade is appropriate for the pin, - // including checks with flags to include pinned. - // * Check that an upgrade installs the right version - - /// - /// Tests upgrading a package when there are no pins on it. - /// - [Test] - public void UpgradeWithNoPins() - { - RunCommandResult result; - - result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); - - result = TestCommon.RunAICLICommand("upgrade", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "The latest upgrade-able version is the same if there are no pins"); - } - - /// - /// Tests upgrading a package when it has a pinning pin. - /// - [Test] - public void UpgradeWithPinningPin() - { - RunCommandResult result; - string installDir = Path.GetTempPath(); - - // The base version of this app does not log /Version, but it still includes the version number in the log file name. - Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "1.0.0.0"), "Base version installed"); - - result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); - - result = TestCommon.RunAICLICommand("upgrade", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); - Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); - - result = TestCommon.RunAICLICommand("upgrade", "--all"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds with nothing to upgrade"); - - Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "1.0.0.0"), "No newer version installed"); - - result = TestCommon.RunAICLICommand("upgrade", "--include-pinned"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "Argument makes available version show up"); - - result = TestCommon.RunAICLICommand("upgrade", "--all --include-pinned"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); - } - - /// - /// Tests upgrading a package when it has a gating pin that allows updating to another version. - /// - [Test] - public void UpgradeWithGatingPin() - { - RunCommandResult result; - string installDir = Path.GetTempPath(); - - var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.*"); - Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); - - result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); - - result = TestCommon.RunAICLICommand("upgrade", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); - Assert.IsTrue(result.StdOut.Contains("1.0.1.0"), "Version matching pin gated version shows up"); - - result = TestCommon.RunAICLICommand("upgrade", "--all"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 1.0.1.0")); - } - - /// - /// Tests upgrading a package when it has a gating pin that blocks all other versions. - /// - [Test] - public void UpgradeWithGatingPinToCurrent() - { - RunCommandResult result; - - result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.0.*"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); - - result = TestCommon.RunAICLICommand("upgrade", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); - Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); - - result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_PACKAGE_IS_PINNED, result.ExitCode, "No upgrades available due to pin"); - } - - /// - /// Tests upgrading a package when it has a blocking pin. - /// - [Test] - public void UpgradeWithBlockingPin() - { - RunCommandResult result; - - var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --blocking"); - Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); - - result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); - - result = TestCommon.RunAICLICommand("upgrade", string.Empty); - Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); - Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); - - result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_PACKAGE_IS_PINNED, result.ExitCode, "No upgrades available due to pin"); - } - - /// - /// Tests upgrading a package when it has a pinning pin and the --force flag is used. - /// - [Test] - public void ForceUpgradeWithPinningPin() - { - RunCommandResult result; - string installDir = Path.GetTempPath(); - - result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - result = TestCommon.RunAICLICommand("upgrade", "--force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); - - result = TestCommon.RunAICLICommand("upgrade", "--all --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0"), "--force argument installs last version despite pin"); - } - - /// - /// Tests upgrading a package when it has a gating pin and the --force flag is used. - /// - [Test] - public void ForceUpgradeWithGatingPin() - { - RunCommandResult result; - string installDir = Path.GetTempPath(); - - var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.*"); - Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); - - result = TestCommon.RunAICLICommand("upgrade", "--force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); - - result = TestCommon.RunAICLICommand("upgrade", "--all --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); - } - - /// - /// Tests upgrading a package when it has a blocking pin and the --force flag is used. - /// - [Test] - public void ForceUpgradeWithBlockingPin() - { - RunCommandResult result; - string installDir = Path.GetTempPath(); - - var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --blocking"); - Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); - - result = TestCommon.RunAICLICommand("upgrade", "--force"); - Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); - - result = TestCommon.RunAICLICommand("upgrade", "--all --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + using static AppInstallerCLIE2ETests.Helpers.TestCommon; + + /// + /// Test upgrading pinned packages. + /// + public class Pinning : BaseCommand + { + /// + /// Set up for all tests. + /// + [SetUp] + public void Setup() + { + // All tests use TestExeInstaller; try to clean it up for failure cases, + // then install the base version for pinning + TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); + TestCommon.RunAICLICommand("install", "AppInstallerTest.TestExeInstaller -v 1.0.0.0"); + TestCommon.RunAICLICommand("pin remove", "AppInstallerTest.TestExeInstaller"); + } + + /// + /// Clean up done after all the tests here. + /// + [OneTimeTearDown] + public void OneTimeTearDown() + { + TestCommon.RunAICLICommand("pin remove", "AppInstallerTest.TestExeInstaller"); + TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestExeInstaller"); + } + + // All tests do roughly the same with different types of pins: + // * Check that the available version shown by list is the latest + // * Check that the available version shown by upgrade is appropriate for the pin, + // including checks with flags to include pinned. + // * Check that an upgrade installs the right version + + /// + /// Tests upgrading a package when there are no pins on it. + /// + [Test] + public void UpgradeWithNoPins() + { + RunCommandResult result; + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "The latest upgrade-able version is the same if there are no pins"); + } + + /// + /// Tests upgrading a package when it has a pinning pin. + /// + [Test] + public void UpgradeWithPinningPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + // The base version of this app does not log /Version, but it still includes the version number in the log file name. + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "1.0.0.0"), "Base version installed"); + + result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); + + result = TestCommon.RunAICLICommand("upgrade", "--all"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds with nothing to upgrade"); + + Assert.True(TestCommon.VerifyTestExeInstalled(installDir, "1.0.0.0"), "No newer version installed"); + + result = TestCommon.RunAICLICommand("upgrade", "--include-pinned"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "Argument makes available version show up"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --include-pinned"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); + } + + /// + /// Tests upgrading a package when it has a gating pin that allows updating to another version. + /// + [Test] + public void UpgradeWithGatingPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.*"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("1.0.1.0"), "Version matching pin gated version shows up"); + + result = TestCommon.RunAICLICommand("upgrade", "--all"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 1.0.1.0")); + } + + /// + /// Tests upgrading a package when it has a gating pin that blocks all other versions. + /// + [Test] + public void UpgradeWithGatingPinToCurrent() + { + RunCommandResult result; + + result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.0.*"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); + + result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_PACKAGE_IS_PINNED, result.ExitCode, "No upgrades available due to pin"); + } + + /// + /// Tests upgrading a package when it has a blocking pin. + /// + [Test] + public void UpgradeWithBlockingPin() + { + RunCommandResult result; + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --blocking"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("list", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "List shows the latest available version"); + + result = TestCommon.RunAICLICommand("upgrade", string.Empty); + Assert.IsFalse(result.StdOut.Contains("2.0.0.0"), "Pin hides latest available version"); + Assert.IsTrue(result.StdOut.Contains("package(s) have pins that prevent upgrade")); + + result = TestCommon.RunAICLICommand("upgrade", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_PACKAGE_IS_PINNED, result.ExitCode, "No upgrades available due to pin"); + } + + /// + /// Tests upgrading a package when it has a pinning pin and the --force flag is used. + /// + [Test] + public void ForceUpgradeWithPinningPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + result = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", "--force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0"), "--force argument installs last version despite pin"); + } + + /// + /// Tests upgrading a package when it has a gating pin and the --force flag is used. + /// + [Test] + public void ForceUpgradeWithGatingPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --version 1.0.*"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", "--force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); + } + + /// + /// Tests upgrading a package when it has a blocking pin and the --force flag is used. + /// + [Test] + public void ForceUpgradeWithBlockingPin() + { + RunCommandResult result; + string installDir = Path.GetTempPath(); + + var pinResult = TestCommon.RunAICLICommand("pin add", "AppInstallerTest.TestExeInstaller --blocking"); + Assert.AreEqual(Constants.ErrorCode.S_OK, pinResult.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", "--force"); + Assert.IsTrue(result.StdOut.Contains("2.0.0.0"), "--force argument shows latest version"); + + result = TestCommon.RunAICLICommand("upgrade", "--all --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode, "Upgrade succeeds"); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir, "/Version 2.0.0.0")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/PowerShell/PowerShellHost.cs b/src/AppInstallerCLIE2ETests/PowerShell/PowerShellHost.cs index d0f1d22970..fd84a6fa5d 100644 --- a/src/AppInstallerCLIE2ETests/PowerShell/PowerShellHost.cs +++ b/src/AppInstallerCLIE2ETests/PowerShell/PowerShellHost.cs @@ -1,107 +1,107 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests.PowerShell -{ - using System; - using System.Collections; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using Microsoft.PowerShell; - using NUnit.Framework; - - /// - /// Helper class to run powershell commands. - /// - internal class PowerShellHost : IDisposable - { - private readonly Runspace runspace = null; - - private bool disposed = false; - - /// - /// Initializes a new instance of the class. - /// - public PowerShellHost() - { - InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); - initialSessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted; - - this.runspace = RunspaceFactory.CreateRunspace(initialSessionState); - this.runspace.Open(); - this.VerifyErrorState(); - - this.PowerShell = PowerShell.Create(this.runspace); - } - - /// - /// Finalizes an instance of the class. - /// - ~PowerShellHost() => this.Dispose(false); - - /// - /// Gets PowerShell. - /// - public PowerShell PowerShell { get; private set; } = null; - - /// - /// Dispose. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Add module path. - /// - /// Path. - public void AddModulePath(string path) - { - var newModulePath = this.PowerShell.Runspace.SessionStateProxy.PSVariable.GetValue("env:PSModulePath") + $";{path}"; - this.PowerShell.Runspace.SessionStateProxy.PSVariable.Set("env:PSModulePath", newModulePath); - } - - /// - /// Protected implementation of dispose pattern. - /// - /// Dispose. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - this.PowerShell.Dispose(); - this.runspace.Dispose(); - } - - this.disposed = true; - } - } - - /// - /// The most common error is that the module was not found. - /// - private void VerifyErrorState() - { - var errors = (ArrayList)this.runspace.SessionStateProxy.PSVariable.GetValue("Error"); - - if (errors.Count > 0) - { - string errorMessage = "PSVariable Error:"; - foreach (var error in errors) - { - errorMessage += Environment.NewLine + ((ErrorRecord)error).Exception.Message; - } - - TestContext.Error.WriteLine(errorMessage); - throw new Exception(errorMessage); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests.PowerShell +{ + using System; + using System.Collections; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using Microsoft.PowerShell; + using NUnit.Framework; + + /// + /// Helper class to run powershell commands. + /// + internal class PowerShellHost : IDisposable + { + private readonly Runspace runspace = null; + + private bool disposed = false; + + /// + /// Initializes a new instance of the class. + /// + public PowerShellHost() + { + InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); + initialSessionState.ExecutionPolicy = ExecutionPolicy.Unrestricted; + + this.runspace = RunspaceFactory.CreateRunspace(initialSessionState); + this.runspace.Open(); + this.VerifyErrorState(); + + this.PowerShell = PowerShell.Create(this.runspace); + } + + /// + /// Finalizes an instance of the class. + /// + ~PowerShellHost() => this.Dispose(false); + + /// + /// Gets PowerShell. + /// + public PowerShell PowerShell { get; private set; } = null; + + /// + /// Dispose. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Add module path. + /// + /// Path. + public void AddModulePath(string path) + { + var newModulePath = this.PowerShell.Runspace.SessionStateProxy.PSVariable.GetValue("env:PSModulePath") + $";{path}"; + this.PowerShell.Runspace.SessionStateProxy.PSVariable.Set("env:PSModulePath", newModulePath); + } + + /// + /// Protected implementation of dispose pattern. + /// + /// Dispose. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + this.PowerShell.Dispose(); + this.runspace.Dispose(); + } + + this.disposed = true; + } + } + + /// + /// The most common error is that the module was not found. + /// + private void VerifyErrorState() + { + var errors = (ArrayList)this.runspace.SessionStateProxy.PSVariable.GetValue("Error"); + + if (errors.Count > 0) + { + string errorMessage = "PSVariable Error:"; + foreach (var error in errors) + { + errorMessage += Environment.NewLine + ((ErrorRecord)error).Exception.Message; + } + + TestContext.Error.WriteLine(errorMessage); + throw new Exception(errorMessage); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Properties/AssemblyInfo.cs b/src/AppInstallerCLIE2ETests/Properties/AssemblyInfo.cs index a0df35f458..d3d8d74842 100644 --- a/src/AppInstallerCLIE2ETests/Properties/AssemblyInfo.cs +++ b/src/AppInstallerCLIE2ETests/Properties/AssemblyInfo.cs @@ -1,7 +1,7 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.17763")] +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +[assembly: System.Runtime.Versioning.SupportedOSPlatform("windows10.0.17763")] diff --git a/src/AppInstallerCLIE2ETests/README.md b/src/AppInstallerCLIE2ETests/README.md index 5eda72bfce..a4d5467440 100644 --- a/src/AppInstallerCLIE2ETests/README.md +++ b/src/AppInstallerCLIE2ETests/README.md @@ -67,7 +67,7 @@ The localhost web server needs to be running for the duration of the tests. The | **CertPassword** | Mandatory | HTTPS Developer Certificate Password | | **Port** | Optional | Port number [Default Port Number: 5001] | | **OutCertFile** | Optional | The exported certificate used | -| **LocalSourceJson** | Optional | The local source definition. If set generates the source. | +| **LocalSourceJson** | Optional | The local source definition. If set generates the source. | | **ForcedExperimentalFeatures** | Optional | Experimental features that should be forcibly enabled always. | ### How to create and trust an ASP.NET Core HTTPS Development Certificate diff --git a/src/AppInstallerCLIE2ETests/RepairCommand.cs b/src/AppInstallerCLIE2ETests/RepairCommand.cs index 11af2bd588..f7166d8fff 100644 --- a/src/AppInstallerCLIE2ETests/RepairCommand.cs +++ b/src/AppInstallerCLIE2ETests/RepairCommand.cs @@ -1,280 +1,280 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test Repair command. - /// - public class RepairCommand : BaseCommand - { - /// - /// One time setup. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - // Try clean up AppInstallerTest.TestMsiInstaller for failure cases where cleanup is not successful - TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestMsiInstaller"); - } - - /// - /// Test MSI installer repair. - /// - [Test] - public void RepairMSIInstaller() - { - if (string.IsNullOrEmpty(TestIndex.MsiInstallerV2)) - { - Assert.Ignore("MSI installer not available"); - } - - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMsiRepair --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs). - // In our test scenario, the MSI installer file is initially placed in a temporary location and then deleted, which can cause the repair operation to fail. - // To work around this, we copy the installer file to the ARP source directory before running the repair command. - // A more permanent solution would be to modify the MSI installer to cache the installer file in a known location and register that location as the installer source. - // This would allow the 'msiexec repair' command to function as expected. - string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestMsiRepair"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Repair operation completed successfully")); - Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); - - if (installerSourceDir != null && Directory.Exists(installerSourceDir)) - { - Directory.Delete(installerSourceDir, true); - } - } - - /// - /// Test MSIX non-store package repair. - /// - [Test] - public void RepairNonStoreMSIXPackage() - { - // install a test msix package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMsixInstaller --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestMsixInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Repair operation completed successfully")); - Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); - } - - /// - /// Test MSIX non-store package repair with machine scope. - /// - [Test] - public void RepairNonStoreMsixPackageWithMachineScope() - { - // Selecting Microsoft.Paint_8wekyb3d8bbwe because it's a system package suitable for this scenario. - // First, we need to ensure this package is installed, otherwise, we skip the test. - var result = TestCommon.RunAICLICommand("list", "Microsoft.Paint_8wekyb3d8bbwe"); - - if (result.ExitCode != Constants.ErrorCode.S_OK) - { - Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed."); - } - - Assert.True(result.StdOut.Contains("Microsoft.Paint")); - - result = TestCommon.RunAICLICommand("repair", "Microsoft.Paint_8wekyb3d8bbwe --scope machine"); - Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, result.ExitCode); - Assert.True(result.StdOut.Contains("The current system configuration does not support the repair of this package.")); - } - - /// - /// Test repair of a Burn installer that has a "modify" repair behavior specified in the manifest. - /// - [Test] - public void RepairBurnInstallerWithModifyBehavior() - { - // install a test burn package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestModifyRepair -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestModifyRepair"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Repair operation completed successfully")); - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Modify Repair operation")); - } - - /// - /// Tests the repair operation of a Burn installer that was installed in user scope but is being repaired in an admin context. - /// - [Test] - public void RepairBurnInstallerInAdminContextWithUserScopeInstall() - { - // install a test burn package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestUserScopeInstallRepairInAdminContext -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestUserScopeInstallRepairInAdminContext"); - Assert.AreEqual(Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, result.ExitCode); - Assert.True(result.StdOut.Contains("The package installed for user scope cannot be repaired when running with administrator privileges.")); - TestCommon.CleanupTestExeAndDirectory(installDir); - } - - /// - /// Tests the repair operation of a Burn installer that lacks a repair behavior. - /// - [Test] - public void RepairBurnInstallerMissingRepairBehavior() - { - // install a test burn package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMissingRepairBehavior -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestMissingRepairBehavior"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_REPAIR_INFO_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("The repair command for this package cannot be found. Please reach out to the package publisher for support.")); - TestCommon.CleanupTestExeAndDirectory(installDir); - } - - /// - /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoModify ARP flag set. - /// - [Test] - public void RepairBurnInstallerWithWithModifyBehaviorAndNoModifyFlag() - { - // install a test Exe package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestModifyRepairWithNoModify -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestModifyRepairWithNoModify"); - Assert.AreEqual(Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED, result.ExitCode); - Assert.True(result.StdOut.Contains("The installer technology in use does not support repair.")); - TestCommon.CleanupTestExeAndDirectory(installDir); - } - - /// - /// Tests the scenario where the repair operation is not supported for Portable Installer type. - /// - [Test] - public void RepairOperationNotSupportedForPortableInstaller() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestPortableExe"); - Assert.AreEqual(Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED, result.ExitCode); - Assert.True(result.StdOut.Contains("The installer technology in use does not support repair.")); - - // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest. - /// - [Test] - public void RepairExeInstallerWithUninstallerBehavior() - { - // install a test Exe package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.UninstallerRepair -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.UninstallerRepair"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Repair operation completed successfully")); - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Uninstaller Repair operation")); - } - - /// - /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoRepair ARP flag set. - /// - [Test] - public void RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag() - { - // install a test Exe package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.UninstallerRepairWithNoRepair -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.UninstallerRepairWithNoRepair"); - Assert.AreEqual(Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED, result.ExitCode); - Assert.True(result.StdOut.Contains("The installer technology in use does not support repair.")); - TestCommon.CleanupTestExeAndDirectory(installDir); - } - - /// - /// Test repair of a Nullsoft installer that has a "uninstaller" repair behavior specified in the manifest. - /// - [Test] - public void RepairNullsoftInstallerWithUninstallerBehavior() - { - // install a test Nullsoft package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.UninstallerRepair -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.UninstallerRepair"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Repair operation completed successfully")); - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Uninstaller Repair operation")); - } - - /// - /// Test repair of a Inno installer that has a "installer" repair behavior specified in the manifest. - /// - [Test] - public void RepairInnoInstallerWithInstallerBehavior() - { - // install a test Inno package from TestSource and then repair it. - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestInstallerRepair -v 2.0.0.0 --silent -l {installDir}"); - - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestInstallerRepair"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Repair operation completed successfully")); - Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Installer Repair operation")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test Repair command. + /// + public class RepairCommand : BaseCommand + { + /// + /// One time setup. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + // Try clean up AppInstallerTest.TestMsiInstaller for failure cases where cleanup is not successful + TestCommon.RunAICLICommand("uninstall", "AppInstallerTest.TestMsiInstaller"); + } + + /// + /// Test MSI installer repair. + /// + [Test] + public void RepairMSIInstaller() + { + if (string.IsNullOrEmpty(TestIndex.MsiInstallerV2)) + { + Assert.Ignore("MSI installer not available"); + } + + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMsiRepair --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + // Note: The 'msiexec repair' command requires the original installer file to be present at the location registered in the ARP (Add/Remove Programs). + // In our test scenario, the MSI installer file is initially placed in a temporary location and then deleted, which can cause the repair operation to fail. + // To work around this, we copy the installer file to the ARP source directory before running the repair command. + // A more permanent solution would be to modify the MSI installer to cache the installer file in a known location and register that location as the installer source. + // This would allow the 'msiexec repair' command to function as expected. + string installerSourceDir = TestCommon.CopyInstallerFileToARPInstallSourceDirectory(TestCommon.GetTestDataFile("AppInstallerTestMsiInstallerV2.msi"), Constants.MsiInstallerProductCode, true); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestMsiRepair"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Repair operation completed successfully")); + Assert.True(TestCommon.VerifyTestMsiInstalledAndCleanup(installDir)); + + if (installerSourceDir != null && Directory.Exists(installerSourceDir)) + { + Directory.Delete(installerSourceDir, true); + } + } + + /// + /// Test MSIX non-store package repair. + /// + [Test] + public void RepairNonStoreMSIXPackage() + { + // install a test msix package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMsixInstaller --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestMsixInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Repair operation completed successfully")); + Assert.True(TestCommon.VerifyTestMsixInstalledAndCleanup()); + } + + /// + /// Test MSIX non-store package repair with machine scope. + /// + [Test] + public void RepairNonStoreMsixPackageWithMachineScope() + { + // Selecting Microsoft.Paint_8wekyb3d8bbwe because it's a system package suitable for this scenario. + // First, we need to ensure this package is installed, otherwise, we skip the test. + var result = TestCommon.RunAICLICommand("list", "Microsoft.Paint_8wekyb3d8bbwe"); + + if (result.ExitCode != Constants.ErrorCode.S_OK) + { + Assert.Ignore("Test skipped as Microsoft.Paint_8wekyb3d8bbwe is not installed."); + } + + Assert.True(result.StdOut.Contains("Microsoft.Paint")); + + result = TestCommon.RunAICLICommand("repair", "Microsoft.Paint_8wekyb3d8bbwe --scope machine"); + Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, result.ExitCode); + Assert.True(result.StdOut.Contains("The current system configuration does not support the repair of this package.")); + } + + /// + /// Test repair of a Burn installer that has a "modify" repair behavior specified in the manifest. + /// + [Test] + public void RepairBurnInstallerWithModifyBehavior() + { + // install a test burn package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestModifyRepair -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestModifyRepair"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Repair operation completed successfully")); + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Modify Repair operation")); + } + + /// + /// Tests the repair operation of a Burn installer that was installed in user scope but is being repaired in an admin context. + /// + [Test] + public void RepairBurnInstallerInAdminContextWithUserScopeInstall() + { + // install a test burn package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestUserScopeInstallRepairInAdminContext -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestUserScopeInstallRepairInAdminContext"); + Assert.AreEqual(Constants.ErrorCode.ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, result.ExitCode); + Assert.True(result.StdOut.Contains("The package installed for user scope cannot be repaired when running with administrator privileges.")); + TestCommon.CleanupTestExeAndDirectory(installDir); + } + + /// + /// Tests the repair operation of a Burn installer that lacks a repair behavior. + /// + [Test] + public void RepairBurnInstallerMissingRepairBehavior() + { + // install a test burn package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestMissingRepairBehavior -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestMissingRepairBehavior"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_REPAIR_INFO_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("The repair command for this package cannot be found. Please reach out to the package publisher for support.")); + TestCommon.CleanupTestExeAndDirectory(installDir); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoModify ARP flag set. + /// + [Test] + public void RepairBurnInstallerWithWithModifyBehaviorAndNoModifyFlag() + { + // install a test Exe package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestModifyRepairWithNoModify -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestModifyRepairWithNoModify"); + Assert.AreEqual(Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED, result.ExitCode); + Assert.True(result.StdOut.Contains("The installer technology in use does not support repair.")); + TestCommon.CleanupTestExeAndDirectory(installDir); + } + + /// + /// Tests the scenario where the repair operation is not supported for Portable Installer type. + /// + [Test] + public void RepairOperationNotSupportedForPortableInstaller() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestPortableExe"); + Assert.AreEqual(Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED, result.ExitCode); + Assert.True(result.StdOut.Contains("The installer technology in use does not support repair.")); + + // If no location specified, default behavior is to create a package directory with the name "{packageId}_{sourceId}" + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest. + /// + [Test] + public void RepairExeInstallerWithUninstallerBehavior() + { + // install a test Exe package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.UninstallerRepair -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.UninstallerRepair"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Repair operation completed successfully")); + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Uninstaller Repair operation")); + } + + /// + /// Test repair of a Exe installer that has a "uninstaller" repair behavior specified in the manifest and NoRepair ARP flag set. + /// + [Test] + public void RepairExeInstallerWithUninstallerBehaviorAndNoRepairFlag() + { + // install a test Exe package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.UninstallerRepairWithNoRepair -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.UninstallerRepairWithNoRepair"); + Assert.AreEqual(Constants.ErrorCode.ERROR_REPAIR_NOT_SUPPORTED, result.ExitCode); + Assert.True(result.StdOut.Contains("The installer technology in use does not support repair.")); + TestCommon.CleanupTestExeAndDirectory(installDir); + } + + /// + /// Test repair of a Nullsoft installer that has a "uninstaller" repair behavior specified in the manifest. + /// + [Test] + public void RepairNullsoftInstallerWithUninstallerBehavior() + { + // install a test Nullsoft package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.UninstallerRepair -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.UninstallerRepair"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Repair operation completed successfully")); + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Uninstaller Repair operation")); + } + + /// + /// Test repair of a Inno installer that has a "installer" repair behavior specified in the manifest. + /// + [Test] + public void RepairInnoInstallerWithInstallerBehavior() + { + // install a test Inno package from TestSource and then repair it. + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestInstallerRepair -v 2.0.0.0 --silent -l {installDir}"); + + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + result = TestCommon.RunAICLICommand("repair", "AppInstallerTest.TestInstallerRepair"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Repair operation completed successfully")); + Assert.True(TestCommon.VerifyTestExeRepairCompletedAndCleanup(installDir, "Installer Repair operation")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ResumeCommand.cs b/src/AppInstallerCLIE2ETests/ResumeCommand.cs index 4d75b02a68..6f39c78908 100644 --- a/src/AppInstallerCLIE2ETests/ResumeCommand.cs +++ b/src/AppInstallerCLIE2ETests/ResumeCommand.cs @@ -67,44 +67,44 @@ public void ResumeIdNotFound() Assert.AreEqual(Constants.ErrorCode.ERROR_RESUME_ID_NOT_FOUND, resumeResult.ExitCode); } - /// - /// Test install a package that returns REBOOT_REQUIRED_TO_FINISH and verify that the checkpoint database is properly cleaned up. - /// - [Test] - public void InstallRequiresRebootToFinish() - { - var checkpointsDir = TestCommon.GetCheckpointsDirectory(); - int initialCheckpointsCount = Directory.Exists(checkpointsDir) ? Directory.GetDirectories(checkpointsDir).Length : 0; - - var installDir = TestCommon.GetRandomTestDir(); - var result = TestCommon.RunAICLICommand("install", $"TestRebootRequired --custom \"/ExitCode 9\" -l {installDir}"); - - // REBOOT_REQUIRED_TO_FINISH is treated as a success. - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Restart your PC to finish installation.")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); - - int actualCheckpointsCount = Directory.GetDirectories(checkpointsDir).Length; - - // Checkpoint database should be cleaned up since resume is not needed to complete installation. - Assert.AreEqual(initialCheckpointsCount, actualCheckpointsCount); - } - - /// - /// Test install a package that returns REBOOT_REQUIRED_FOR_INSTALL and verify that resume command can be called successfully. + /// + /// Test install a package that returns REBOOT_REQUIRED_TO_FINISH and verify that the checkpoint database is properly cleaned up. /// [Test] - public void InstallRequiresRebootForInstall() - { + public void InstallRequiresRebootToFinish() + { + var checkpointsDir = TestCommon.GetCheckpointsDirectory(); + int initialCheckpointsCount = Directory.Exists(checkpointsDir) ? Directory.GetDirectories(checkpointsDir).Length : 0; + + var installDir = TestCommon.GetRandomTestDir(); + var result = TestCommon.RunAICLICommand("install", $"TestRebootRequired --custom \"/ExitCode 9\" -l {installDir}"); + + // REBOOT_REQUIRED_TO_FINISH is treated as a success. + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Restart your PC to finish installation.")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + + int actualCheckpointsCount = Directory.GetDirectories(checkpointsDir).Length; + + // Checkpoint database should be cleaned up since resume is not needed to complete installation. + Assert.AreEqual(initialCheckpointsCount, actualCheckpointsCount); + } + + /// + /// Test install a package that returns REBOOT_REQUIRED_FOR_INSTALL and verify that resume command can be called successfully. + /// + [Test] + public void InstallRequiresRebootForInstall() + { var checkpointsDir = TestCommon.GetCheckpointsDirectory(); int initialCheckpointsCount = Directory.Exists(checkpointsDir) ? Directory.GetDirectories(checkpointsDir).Length : 0; - var installDir = TestCommon.GetRandomTestDir(); + var installDir = TestCommon.GetRandomTestDir(); var result = TestCommon.RunAICLICommand("install", $"TestRebootRequired --custom \"/ExitCode 10\" -l {installDir}"); Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, result.ExitCode); Assert.True(result.StdOut.Contains("Your PC will restart to finish installation.")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); int actualCheckpointsCount = Directory.GetDirectories(checkpointsDir).Length; Assert.AreEqual(initialCheckpointsCount + 1, actualCheckpointsCount); @@ -118,8 +118,8 @@ public void InstallRequiresRebootForInstall() // Resume output should be the same as the install result. var resumeResult = TestCommon.RunAICLICommand("resume", $"-g {checkpoint.Name}"); Assert.AreEqual(Constants.ErrorCode.ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, resumeResult.ExitCode); - Assert.True(resumeResult.StdOut.Contains("Your PC will restart to finish installation.")); - Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); + Assert.True(resumeResult.StdOut.Contains("Your PC will restart to finish installation.")); + Assert.True(TestCommon.VerifyTestExeInstalledAndCleanup(installDir)); } } } \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/RunCommandException.cs b/src/AppInstallerCLIE2ETests/RunCommandException.cs index cff2477ed0..780e418e00 100644 --- a/src/AppInstallerCLIE2ETests/RunCommandException.cs +++ b/src/AppInstallerCLIE2ETests/RunCommandException.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System; - using static AppInstallerCLIE2ETests.Helpers.TestCommon; - - /// - /// An exception that occurred when running a command. - /// - internal class RunCommandException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// The file name of the command. - /// The arguments for the command. - /// The `RunCommand` result. - public RunCommandException(string fileName, string args, RunCommandResult result) - : base($"Command `{fileName} {args}` failed with: {result.ExitCode}\nOut: {result.StdOut}\nErr: {result.StdErr}") - { - this.FileName = fileName; - this.Arguments = args; - this.Result = result; - } - - /// - /// Gets or initializes the file name. - /// - public string FileName { get; private init; } - - /// - /// Gets or initializes the arguments. - /// - public string Arguments { get; private init; } - - /// - /// Gets or initializes the result object. - /// - public RunCommandResult Result { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System; + using static AppInstallerCLIE2ETests.Helpers.TestCommon; + + /// + /// An exception that occurred when running a command. + /// + internal class RunCommandException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// The file name of the command. + /// The arguments for the command. + /// The `RunCommand` result. + public RunCommandException(string fileName, string args, RunCommandResult result) + : base($"Command `{fileName} {args}` failed with: {result.ExitCode}\nOut: {result.StdOut}\nErr: {result.StdErr}") + { + this.FileName = fileName; + this.Arguments = args; + this.Result = result; + } + + /// + /// Gets or initializes the file name. + /// + public string FileName { get; private init; } + + /// + /// Gets or initializes the arguments. + /// + public string Arguments { get; private init; } + + /// + /// Gets or initializes the result object. + /// + public RunCommandResult Result { get; private init; } + } +} diff --git a/src/AppInstallerCLIE2ETests/SearchCommand.cs b/src/AppInstallerCLIE2ETests/SearchCommand.cs index 7e68a266b3..4a64ec26fd 100644 --- a/src/AppInstallerCLIE2ETests/SearchCommand.cs +++ b/src/AppInstallerCLIE2ETests/SearchCommand.cs @@ -1,216 +1,216 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test search command. - /// - public class SearchCommand : BaseCommand - { - /// - /// Test search without args. - /// - [Test] - public void SearchWithoutArgs() - { - var result = TestCommon.RunAICLICommand("search", string.Empty); - Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); - } - - /// - /// Test search with query. - /// - [Test] - public void SearchQuery() - { - var result = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test search with alias. - /// - public void SearchUsingAlias() - { - var result = TestCommon.RunAICLICommand("find", "TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test search with name. - /// - [Test] - public void SearchWithName() - { - var result = TestCommon.RunAICLICommand("search", "--name testexampleinstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test search with Id. - /// - [Test] - public void SearchWithID() - { - var result = TestCommon.RunAICLICommand("search", "--id appinstallertest.testexampleinstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test search with invalid name. - /// - [Test] - public void SearchWithInvalidName() - { - var result = TestCommon.RunAICLICommand("search", "--name InvalidName"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No package found matching input criteria.")); - } - - /// - /// Test search where it returns multiple results. - /// - [Test] - public void SearchReturnsMultiple() - { - // Search Microsoft should return multiple - var result = TestCommon.RunAICLICommand("search", "AppInstallerTest"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestBurnInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test search with exact name. - /// - [Test] - public void SearchWithExactName() - { - var result = TestCommon.RunAICLICommand("search", "--exact TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test search with exact ID. - /// - [Test] - public void SearchWithExactID() - { - var result = TestCommon.RunAICLICommand("search", "--exact AppInstallerTest.TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test search with exact case-sensitive. - /// - [Test] - public void SearchWithExactArgCaseSensitivity() - { - var result = TestCommon.RunAICLICommand("search", "--exact testexampleinstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No package found matching input criteria.")); - } - - /// - /// Test search with a failed source. - /// - [Test] - public void SearchWithSingleSourceFailure() - { - TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"OpenHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); - - try - { - var result = TestCommon.RunAICLICommand("search", "--exact AppInstallerTest.TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Failed when searching source; results will not be included: failSearch")); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - finally - { - this.ResetTestSource(); - } - } - - /// - /// Test search with bad pin. - /// - [Test] - public void SearchStoreWithBadPin() - { - // Configure as close as possible to the real chain but use the test cert for everything - // This will at least force the public key to be checked rather than simply failing based on chain length - GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicyHelper.GroupPolicySource[] - { - new GroupPolicyHelper.GroupPolicySource - { - Name = Constants.TestAlternateSourceName, - Arg = Constants.DefaultMSStoreSourceUrl, - Type = Constants.DefaultMSStoreSourceType, - Data = string.Empty, - Identifier = Constants.DefaultMSStoreSourceIdentifier, - CertificatePinning = new GroupPolicyHelper.GroupPolicyCertificatePinning - { - Chains = new GroupPolicyHelper.GroupPolicyCertificatePinningChain[] - { - new GroupPolicyHelper.GroupPolicyCertificatePinningChain - { - Chain = new GroupPolicyHelper.GroupPolicyCertificatePinningDetails[] - { - new GroupPolicyHelper.GroupPolicyCertificatePinningDetails - { - Validation = new string[] { "publickey" }, - EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), - }, - new GroupPolicyHelper.GroupPolicyCertificatePinningDetails - { - Validation = new string[] { "subject", "issuer" }, - EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), - }, - new GroupPolicyHelper.GroupPolicyCertificatePinningDetails - { - Validation = new string[] { "subject", "issuer" }, - EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), - }, - }, - }, - }, - }, - TrustLevel = new string[] { "None" }, - Explicit = false, - }, - }); - - try - { - var result = TestCommon.RunAICLICommand("search", $"-s {Constants.TestAlternateSourceName} foo --verbose-logs"); - Assert.AreEqual(Constants.ErrorCode.ERROR_PINNED_CERTIFICATE_MISMATCH, result.ExitCode); - } - finally - { - this.ResetTestSource(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test search command. + /// + public class SearchCommand : BaseCommand + { + /// + /// Test search without args. + /// + [Test] + public void SearchWithoutArgs() + { + var result = TestCommon.RunAICLICommand("search", string.Empty); + Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); + } + + /// + /// Test search with query. + /// + [Test] + public void SearchQuery() + { + var result = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test search with alias. + /// + public void SearchUsingAlias() + { + var result = TestCommon.RunAICLICommand("find", "TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test search with name. + /// + [Test] + public void SearchWithName() + { + var result = TestCommon.RunAICLICommand("search", "--name testexampleinstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test search with Id. + /// + [Test] + public void SearchWithID() + { + var result = TestCommon.RunAICLICommand("search", "--id appinstallertest.testexampleinstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test search with invalid name. + /// + [Test] + public void SearchWithInvalidName() + { + var result = TestCommon.RunAICLICommand("search", "--name InvalidName"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No package found matching input criteria.")); + } + + /// + /// Test search where it returns multiple results. + /// + [Test] + public void SearchReturnsMultiple() + { + // Search Microsoft should return multiple + var result = TestCommon.RunAICLICommand("search", "AppInstallerTest"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExeInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestBurnInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test search with exact name. + /// + [Test] + public void SearchWithExactName() + { + var result = TestCommon.RunAICLICommand("search", "--exact TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test search with exact ID. + /// + [Test] + public void SearchWithExactID() + { + var result = TestCommon.RunAICLICommand("search", "--exact AppInstallerTest.TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test search with exact case-sensitive. + /// + [Test] + public void SearchWithExactArgCaseSensitivity() + { + var result = TestCommon.RunAICLICommand("search", "--exact testexampleinstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No package found matching input criteria.")); + } + + /// + /// Test search with a failed source. + /// + [Test] + public void SearchWithSingleSourceFailure() + { + TestCommon.RunAICLICommand("source add", "failSearch \"{ \"\"OpenHR\"\": \"\"0x80070002\"\" }\" Microsoft.Test.Configurable --header \"{}\""); + + try + { + var result = TestCommon.RunAICLICommand("search", "--exact AppInstallerTest.TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Failed when searching source; results will not be included: failSearch")); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + finally + { + this.ResetTestSource(); + } + } + + /// + /// Test search with bad pin. + /// + [Test] + public void SearchStoreWithBadPin() + { + // Configure as close as possible to the real chain but use the test cert for everything + // This will at least force the public key to be checked rather than simply failing based on chain length + GroupPolicyHelper.EnableAdditionalSources.SetEnabledList(new GroupPolicyHelper.GroupPolicySource[] + { + new GroupPolicyHelper.GroupPolicySource + { + Name = Constants.TestAlternateSourceName, + Arg = Constants.DefaultMSStoreSourceUrl, + Type = Constants.DefaultMSStoreSourceType, + Data = string.Empty, + Identifier = Constants.DefaultMSStoreSourceIdentifier, + CertificatePinning = new GroupPolicyHelper.GroupPolicyCertificatePinning + { + Chains = new GroupPolicyHelper.GroupPolicyCertificatePinningChain[] + { + new GroupPolicyHelper.GroupPolicyCertificatePinningChain + { + Chain = new GroupPolicyHelper.GroupPolicyCertificatePinningDetails[] + { + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails + { + Validation = new string[] { "publickey" }, + EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), + }, + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails + { + Validation = new string[] { "subject", "issuer" }, + EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), + }, + new GroupPolicyHelper.GroupPolicyCertificatePinningDetails + { + Validation = new string[] { "subject", "issuer" }, + EmbeddedCertificate = TestCommon.GetTestServerCertificateHexString(), + }, + }, + }, + }, + }, + TrustLevel = new string[] { "None" }, + Explicit = false, + }, + }); + + try + { + var result = TestCommon.RunAICLICommand("search", $"-s {Constants.TestAlternateSourceName} foo --verbose-logs"); + Assert.AreEqual(Constants.ErrorCode.ERROR_PINNED_CERTIFICATE_MISMATCH, result.ExitCode); + } + finally + { + this.ResetTestSource(); + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/SetUpFixture.cs b/src/AppInstallerCLIE2ETests/SetUpFixture.cs index 422eb1bc1f..ae52b7da35 100644 --- a/src/AppInstallerCLIE2ETests/SetUpFixture.cs +++ b/src/AppInstallerCLIE2ETests/SetUpFixture.cs @@ -1,147 +1,147 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using Microsoft.Win32; - using NUnit.Framework; - - /// - /// Set up fixture. - /// - [SetUpFixture] - public class SetUpFixture - { - private static bool shouldDisableDevModeOnExit = true; - private static bool shouldRevertDefaultFileTypeRiskOnExit = true; - private static bool shouldDoAnyTeardown = true; - private static string defaultFileTypes = string.Empty; - - /// - /// Set up. - /// - [OneTimeSetUp] - public void Setup() - { - var testParams = TestSetup.Parameters; - - if (testParams.IsDefault) - { - // If no parameters are provided, use defaults that work locally. - // This allows the user to assume responsibility for setup. - shouldDoAnyTeardown = false; - } - else - { - shouldDisableDevModeOnExit = this.EnableDevMode(true); - - shouldRevertDefaultFileTypeRiskOnExit = this.DecreaseFileTypeRisk(".exe;.msi", false); - - if (testParams.PackagedContext) - { - if (testParams.LooseFileRegistration) - { - Assert.True(TestCommon.InstallMsixRegister(testParams.AICLIPackagePath), $"InstallMsixRegister : {testParams.AICLIPackagePath}"); - } - else - { - Assert.True(TestCommon.InstallMsix(testParams.AICLIPackagePath), $"InstallMsix : {testParams.AICLIPackagePath}"); - } - } - } - - if (!testParams.SkipTestSource) - { - TestIndex.GenerateE2ESource(); - } - - WinGetSettingsHelper.ForcedExperimentalFeatures = testParams.ForcedExperimentalFeatures; - WinGetSettingsHelper.InitializeWingetSettings(); - } - - /// - /// Tear down. - /// - [OneTimeTearDown] - public void TearDown() - { - if (shouldDoAnyTeardown) - { - if (shouldDisableDevModeOnExit) - { - this.EnableDevMode(false); - } - - if (shouldRevertDefaultFileTypeRiskOnExit) - { - this.DecreaseFileTypeRisk(defaultFileTypes, true); - } - - TestCommon.PublishE2ETestLogs(); - - if (TestSetup.Parameters.PackagedContext) - { - TestCommon.RemoveMsix(Constants.AICLIPackageName); - } - } - } - - // Returns whether there's a change to the dev mode state after execution - private bool EnableDevMode(bool enable) - { - var appModelUnlockKey = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"); - - if (enable) - { - var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); - if (value == null || (int)value == 0) - { - appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 1, RegistryValueKind.DWord); - return true; - } - } - else - { - var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); - if (value != null && ((int)value) != 0) - { - appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 0, RegistryValueKind.DWord); - return true; - } - } - - return false; - } - - private bool DecreaseFileTypeRisk(string fileTypes, bool revert) - { - var defaultFileTypeRiskKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Associations"); - string value = (string)defaultFileTypeRiskKey.GetValue("DefaultFileTypeRisk"); - - if (revert) - { - defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", fileTypes); - return false; - } - else - { - if (string.IsNullOrEmpty(value)) - { - defaultFileTypes = string.Empty; - defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", fileTypes); - } - else - { - defaultFileTypes = value; - defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", string.Concat(value, fileTypes)); - } - - return true; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using Microsoft.Win32; + using NUnit.Framework; + + /// + /// Set up fixture. + /// + [SetUpFixture] + public class SetUpFixture + { + private static bool shouldDisableDevModeOnExit = true; + private static bool shouldRevertDefaultFileTypeRiskOnExit = true; + private static bool shouldDoAnyTeardown = true; + private static string defaultFileTypes = string.Empty; + + /// + /// Set up. + /// + [OneTimeSetUp] + public void Setup() + { + var testParams = TestSetup.Parameters; + + if (testParams.IsDefault) + { + // If no parameters are provided, use defaults that work locally. + // This allows the user to assume responsibility for setup. + shouldDoAnyTeardown = false; + } + else + { + shouldDisableDevModeOnExit = this.EnableDevMode(true); + + shouldRevertDefaultFileTypeRiskOnExit = this.DecreaseFileTypeRisk(".exe;.msi", false); + + if (testParams.PackagedContext) + { + if (testParams.LooseFileRegistration) + { + Assert.True(TestCommon.InstallMsixRegister(testParams.AICLIPackagePath), $"InstallMsixRegister : {testParams.AICLIPackagePath}"); + } + else + { + Assert.True(TestCommon.InstallMsix(testParams.AICLIPackagePath), $"InstallMsix : {testParams.AICLIPackagePath}"); + } + } + } + + if (!testParams.SkipTestSource) + { + TestIndex.GenerateE2ESource(); + } + + WinGetSettingsHelper.ForcedExperimentalFeatures = testParams.ForcedExperimentalFeatures; + WinGetSettingsHelper.InitializeWingetSettings(); + } + + /// + /// Tear down. + /// + [OneTimeTearDown] + public void TearDown() + { + if (shouldDoAnyTeardown) + { + if (shouldDisableDevModeOnExit) + { + this.EnableDevMode(false); + } + + if (shouldRevertDefaultFileTypeRiskOnExit) + { + this.DecreaseFileTypeRisk(defaultFileTypes, true); + } + + TestCommon.PublishE2ETestLogs(); + + if (TestSetup.Parameters.PackagedContext) + { + TestCommon.RemoveMsix(Constants.AICLIPackageName); + } + } + } + + // Returns whether there's a change to the dev mode state after execution + private bool EnableDevMode(bool enable) + { + var appModelUnlockKey = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock"); + + if (enable) + { + var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); + if (value == null || (int)value == 0) + { + appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 1, RegistryValueKind.DWord); + return true; + } + } + else + { + var value = appModelUnlockKey.GetValue("AllowDevelopmentWithoutDevLicense"); + if (value != null && ((int)value) != 0) + { + appModelUnlockKey.SetValue("AllowDevelopmentWithoutDevLicense", 0, RegistryValueKind.DWord); + return true; + } + } + + return false; + } + + private bool DecreaseFileTypeRisk(string fileTypes, bool revert) + { + var defaultFileTypeRiskKey = Registry.CurrentUser.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Associations"); + string value = (string)defaultFileTypeRiskKey.GetValue("DefaultFileTypeRisk"); + + if (revert) + { + defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", fileTypes); + return false; + } + else + { + if (string.IsNullOrEmpty(value)) + { + defaultFileTypes = string.Empty; + defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", fileTypes); + } + else + { + defaultFileTypes = value; + defaultFileTypeRiskKey.SetValue("LowRiskFileTypes", string.Concat(value, fileTypes)); + } + + return true; + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ShowCommand.cs b/src/AppInstallerCLIE2ETests/ShowCommand.cs index d6004b4ae3..1452e6aa1f 100644 --- a/src/AppInstallerCLIE2ETests/ShowCommand.cs +++ b/src/AppInstallerCLIE2ETests/ShowCommand.cs @@ -1,152 +1,152 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test show command. - /// - public class ShowCommand : BaseCommand - { - /// - /// Test show with no args. - /// - [Test] - public void ShowWithNoArgs() - { - var result = TestCommon.RunAICLICommand("show", string.Empty); - Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); - } - - /// - /// Test show no match. - /// - [Test] - public void ShowWithNoMatches() - { - // Show with 0 search match shows a "please refine input" - var result = TestCommon.RunAICLICommand("show", $"DoesNotExist"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No package found matching input criteria.")); - } - - /// - /// Test show with substring match. - /// - [Test] - public void ShowWithSubstringMatch() - { - // Show with a substring match still returns 0 results - var result = TestCommon.RunAICLICommand("show", $"AppInstallerTest"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No package found matching input criteria.")); - } - - /// - /// Test show with name match. - /// - [Test] - public void ShowWithNameMatch() - { - var result = TestCommon.RunAICLICommand("show", $"--name testexampleinstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test show with id match. - /// - [Test] - public void ShowWithIDMatch() - { - var result = TestCommon.RunAICLICommand("show", $"--id appinstallertest.testexampleinstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test show versions. - /// - [Test] - public void ShowWithVersions() - { - // Show with --versions list the versions - var result = TestCommon.RunAICLICommand("show", $"TestExampleInstaller --versions"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - Assert.True(result.StdOut.Contains("1.2.3.4")); - } - - /// - /// Test show with exact match name. - /// - [Test] - public void ShowWithExactName() - { - var result = TestCommon.RunAICLICommand("show", $"--exact TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test show with exact id. - /// - [Test] - public void ShowWithExactID() - { - var result = TestCommon.RunAICLICommand("show", $"--exact AppInstallerTest.TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); - Assert.True(result.StdOut.Contains("TestExampleInstaller")); - Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - } - - /// - /// Test show with exact args. - /// - [Test] - public void ShowWithExactArgCaseSensitivity() - { - var result = TestCommon.RunAICLICommand("show", $"--exact testexampleinstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No package found matching input criteria.")); - } - - /// - /// Test show with installer type. - /// - [Test] - public void ShowWithInstallerTypeArg() - { - var result = TestCommon.RunAICLICommand("show", $"--id AppInstallerTest.TestMultipleInstallers --installer-type msi"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Found TestMultipleInstallers [AppInstallerTest.TestMultipleInstallers]")); - Assert.True(result.StdOut.Contains("Installer Type: msi")); - } - - /// - /// Test show with an archive installer type. - /// - [Test] - public void ShowWithZipInstallerTypeArg() - { - var result = TestCommon.RunAICLICommand("show", $"--id AppInstallerTest.TestMultipleInstallers --installer-type zip"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Found TestMultipleInstallers [AppInstallerTest.TestMultipleInstallers]")); - Assert.True(result.StdOut.Contains("Installer Type: exe (zip)")); - } - } +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test show command. + /// + public class ShowCommand : BaseCommand + { + /// + /// Test show with no args. + /// + [Test] + public void ShowWithNoArgs() + { + var result = TestCommon.RunAICLICommand("show", string.Empty); + Assert.AreEqual(Constants.ErrorCode.ERROR_INVALID_CL_ARGUMENTS, result.ExitCode); + } + + /// + /// Test show no match. + /// + [Test] + public void ShowWithNoMatches() + { + // Show with 0 search match shows a "please refine input" + var result = TestCommon.RunAICLICommand("show", $"DoesNotExist"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No package found matching input criteria.")); + } + + /// + /// Test show with substring match. + /// + [Test] + public void ShowWithSubstringMatch() + { + // Show with a substring match still returns 0 results + var result = TestCommon.RunAICLICommand("show", $"AppInstallerTest"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No package found matching input criteria.")); + } + + /// + /// Test show with name match. + /// + [Test] + public void ShowWithNameMatch() + { + var result = TestCommon.RunAICLICommand("show", $"--name testexampleinstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test show with id match. + /// + [Test] + public void ShowWithIDMatch() + { + var result = TestCommon.RunAICLICommand("show", $"--id appinstallertest.testexampleinstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test show versions. + /// + [Test] + public void ShowWithVersions() + { + // Show with --versions list the versions + var result = TestCommon.RunAICLICommand("show", $"TestExampleInstaller --versions"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + Assert.True(result.StdOut.Contains("1.2.3.4")); + } + + /// + /// Test show with exact match name. + /// + [Test] + public void ShowWithExactName() + { + var result = TestCommon.RunAICLICommand("show", $"--exact TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test show with exact id. + /// + [Test] + public void ShowWithExactID() + { + var result = TestCommon.RunAICLICommand("show", $"--exact AppInstallerTest.TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Found TestExampleInstaller [AppInstallerTest.TestExampleInstaller]")); + Assert.True(result.StdOut.Contains("TestExampleInstaller")); + Assert.True(result.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + } + + /// + /// Test show with exact args. + /// + [Test] + public void ShowWithExactArgCaseSensitivity() + { + var result = TestCommon.RunAICLICommand("show", $"--exact testexampleinstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No package found matching input criteria.")); + } + + /// + /// Test show with installer type. + /// + [Test] + public void ShowWithInstallerTypeArg() + { + var result = TestCommon.RunAICLICommand("show", $"--id AppInstallerTest.TestMultipleInstallers --installer-type msi"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Found TestMultipleInstallers [AppInstallerTest.TestMultipleInstallers]")); + Assert.True(result.StdOut.Contains("Installer Type: msi")); + } + + /// + /// Test show with an archive installer type. + /// + [Test] + public void ShowWithZipInstallerTypeArg() + { + var result = TestCommon.RunAICLICommand("show", $"--id AppInstallerTest.TestMultipleInstallers --installer-type zip"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Found TestMultipleInstallers [AppInstallerTest.TestMultipleInstallers]")); + Assert.True(result.StdOut.Contains("Installer Type: exe (zip)")); + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/SourceCommand.cs b/src/AppInstallerCLIE2ETests/SourceCommand.cs index ee4207e366..6f7a424d2e 100644 --- a/src/AppInstallerCLIE2ETests/SourceCommand.cs +++ b/src/AppInstallerCLIE2ETests/SourceCommand.cs @@ -1,395 +1,395 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test source command. - /// - public class SourceCommand : BaseCommand - { - /// - /// One time set up. - /// - [OneTimeSetUp] - public void OneTimeSetup() - { - WinGetSettingsHelper.ConfigureFeature("sourcePriority", true); - } - - /// - /// Test set up. - /// - [SetUp] - public void Setup() - { - this.ResetTestSource(false); - } - - /// - /// Test source add. - /// - [Test] - public void SourceAdd() - { - // TODO: Our test source package is being rejected by SmartScreen on the build server. - // Reenable when SmartScreen issue is solved or removed. - Assert.Ignore(); - - var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Done")); - TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); - } - - /// - /// Test source add with trust level. - /// - [Test] - public void SourceAddWithTrustLevel() - { - // Remove the test source. - TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); - - var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Done")); - - var listResult = TestCommon.RunAICLICommand("source list", $"-n SourceTest"); - Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); - Assert.True(listResult.StdOut.Contains("Trust Level")); - Assert.True(listResult.StdOut.Contains("Trusted")); - TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); - } - - /// - /// Test source add with store origin trust level. - /// - [Test] - public void SourceAddWithStoreOriginTrustLevel() - { - // Remove the test source. - TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); - - var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level storeOrigin"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_DATA_INTEGRITY_FAILURE, result.ExitCode); - Assert.True(result.StdOut.Contains("The source data is corrupted or tampered")); - } - - /// - /// Test source add with explicit flag. Packages should only appear if the source is explicitly declared. - /// - [Test] - public void SourceAddWithExplicit() - { - // Remove the test source. - TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); - - var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted --explicit"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Done")); - - var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode); - Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'")); - - var searchResult2 = TestCommon.RunAICLICommand("search", "TestExampleInstaller --source SourceTest"); - Assert.AreEqual(Constants.ErrorCode.S_OK, searchResult2.ExitCode); - Assert.True(searchResult2.StdOut.Contains("TestExampleInstaller")); - Assert.True(searchResult2.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); - } - - /// - /// Test source add with a priority value. - /// - [Test] - public void SourceAddWithPriority() - { - // Remove the test source. - TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); - - var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --priority 42"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Done")); - - var listResult = TestCommon.RunAICLICommand("source list", "SourceTest"); - Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); - Assert.True(listResult.StdOut.Contains("42")); - - var exportResult = TestCommon.RunAICLICommand("source export", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); - Assert.True(exportResult.StdOut.Contains("42")); - } - - /// - /// Test source add with duplicate name. - /// - [Test] - public void SourceAddWithDuplicateName() - { - // Add source with duplicate name should fail - var result = TestCommon.RunAICLICommand("source add", $"{Constants.TestSourceName} https://microsoft.com"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_ALREADY_EXISTS, result.ExitCode); - Assert.True(result.StdOut.Contains("A source with the given name already exists and refers to a different location")); - } - - /// - /// Test source add with duplicate source url. - /// - [Test] - public void SourceAddWithDuplicateSourceUrl() - { - // Add source with duplicate url should fail - var result = TestCommon.RunAICLICommand("source add", $"TestSource2 {Constants.TestSourceUrl}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_ARG_ALREADY_EXISTS, result.ExitCode); - Assert.True(result.StdOut.Contains("A source with a different name already refers to this location")); - } - - /// - /// Test source add with invalid url. - /// - [Test] - public void SourceAddWithInvalidURL() - { - // Add source with invalid url should fail - var result = TestCommon.RunAICLICommand("source add", $"AnotherSource {Constants.TestSourceUrl}/Invalid/Directory/Dont/Add/Me"); - Assert.AreEqual(Constants.ErrorCode.HTTP_E_STATUS_NOT_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("An unexpected error occurred while executing the command")); - } - - /// - /// Test source add with http url. - /// - [Test] - public void SourceAddWithHttpURL() - { - // Add source with an HTTP url should fail - var result = TestCommon.RunAICLICommand("source add", "Insecure http://microsoft.com"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NOT_SECURE, result.ExitCode); - Assert.True(result.StdOut.Contains("error occurred while executing the command")); - } - - /// - /// Test source list with no args. - /// - [Test] - public void SourceListWithNoArgs() - { - // List with no args should list all available sources - var result = TestCommon.RunAICLICommand("source list", string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); - } - - /// - /// Test source list with name. - /// - [Test] - public void SourceListWithName() - { - var result = TestCommon.RunAICLICommand("source list", $"-n {Constants.TestSourceName}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains(Constants.TestSourceName)); - Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); - Assert.True(result.StdOut.Contains("Microsoft.PreIndexed.Package")); - Assert.True(result.StdOut.Contains("Trust Level")); - Assert.True(result.StdOut.Contains("Updated")); - } - - /// - /// Test source list name mismatch. - /// - [Test] - public void SourceListNameMismatch() - { - var result = TestCommon.RunAICLICommand("source list", "-n UnknownName"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - Assert.True(result.StdOut.Contains("Did not find a source named")); - } - - /// - /// Test source update. - /// - [Test] - public void SourceUpdate() - { - var result = TestCommon.RunAICLICommand("source update", $"-n {Constants.TestSourceName}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Done")); - } - - /// - /// Test source update with invalid name. - /// - [Test] - public void SourceUpdateWithInvalidName() - { - var result = TestCommon.RunAICLICommand("source update", "-n UnknownName"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); - } - - /// - /// Test source remove by name. - /// - [Test] - public void SourceRemoveValidName() - { - var result = TestCommon.RunAICLICommand("source remove", $"-n {Constants.TestSourceName}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Done")); - this.ResetTestSource(false); - } - - /// - /// Test source remove with invalid name. - /// - [Test] - public void SourceRemoveInvalidName() - { - var result = TestCommon.RunAICLICommand("source remove", "-n UnknownName"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); - Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); - } - - /// - /// Test source reset. - /// - [Test] - public void SourceReset() - { - var result = TestCommon.RunAICLICommand("source reset", string.Empty); - Assert.True(result.StdOut.Contains("The following sources will be reset if the --force option is given:")); - Assert.True(result.StdOut.Contains(Constants.TestSourceName)); - Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); - } - - /// - /// Test source reset force. - /// - [Test] - public void SourceForceReset() - { - // Force Reset Sources - var result = TestCommon.RunAICLICommand("source reset", "--force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Resetting all sources...Done")); - - // Verify sources have been reset - result = TestCommon.RunAICLICommand("source list", string.Empty); - Assert.True(result.StdOut.Contains("winget")); - Assert.True(result.StdOut.Contains("https://cdn.winget.microsoft.com/cache")); - Assert.False(result.StdOut.Contains(Constants.TestSourceName)); - Assert.False(result.StdOut.Contains(Constants.TestSourceUrl)); - } - - /// - /// Test source edit with explicit flag, edit the source to not be explicit. - /// - [Test] - public void SourceEdit() - { - // Remove the test source. - TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); - - // Add source as explicit and verify it is explicit. - var addResult = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted --explicit"); - Assert.AreEqual(Constants.ErrorCode.S_OK, addResult.ExitCode); - Assert.True(addResult.StdOut.Contains("Done")); - - var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode); - Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'")); - - // Run the edit, this should be S_OK with "Done" as it changed the state to not-explicit. - var editResult = TestCommon.RunAICLICommand("source edit", $"SourceTest --explicit false"); - Assert.AreEqual(Constants.ErrorCode.S_OK, editResult.ExitCode); - Assert.True(editResult.StdOut.Contains("Explicit")); - - // Run it again, this should result in S_OK with no changes and a message that the source is already in that state. - var editResult2 = TestCommon.RunAICLICommand("source edit", $"SourceTest --explicit false"); - Assert.AreEqual(Constants.ErrorCode.S_OK, editResult2.ExitCode); - Assert.True(editResult2.StdOut.Contains("The source named 'SourceTest' is already in the desired state.")); - - // Now verify it is no longer explicit by running the search again without adding the source parameter. - var searchResult2 = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); - Assert.AreEqual(Constants.ErrorCode.S_OK, searchResult2.ExitCode); - Assert.True(searchResult2.StdOut.Contains("TestExampleInstaller")); - Assert.True(searchResult2.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); - TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); - } - - /// - /// Test source edit with priority. - /// - [Test] - public void SourceEdit_Priority() - { - // Remove the test source. - TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); - - var addResult = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, addResult.ExitCode); - Assert.True(addResult.StdOut.Contains("Done")); - - // Run the edit, this should be S_OK with "Done" as it changed the state - var editResult = TestCommon.RunAICLICommand("source edit", $"SourceTest --priority 14"); - Assert.AreEqual(Constants.ErrorCode.S_OK, editResult.ExitCode); - Assert.True(editResult.StdOut.Contains("14")); - - // Run it again, this should result in S_OK with no changes and a message that the source is already in that state. - var editResult2 = TestCommon.RunAICLICommand("source edit", $"SourceTest --priority 14"); - Assert.AreEqual(Constants.ErrorCode.S_OK, editResult2.ExitCode); - Assert.True(editResult2.StdOut.Contains("The source named 'SourceTest' is already in the desired state.")); - } - - /// - /// Test override of a default source via edit command. - /// - [Test] - public void SourceEditOverrideDefault() - { - // Force Reset Sources - var resetResult = TestCommon.RunAICLICommand("source reset", "--force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, resetResult.ExitCode); - - // Verify it is explicit true. Explicit is the only boolean value in the output. - var listResult = TestCommon.RunAICLICommand("source list", "winget-font"); - Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); - Assert.True(listResult.StdOut.Contains("true")); - - var editResult = TestCommon.RunAICLICommand("source edit", "winget-font -e false"); - Assert.AreEqual(Constants.ErrorCode.S_OK, editResult.ExitCode); - Assert.True(editResult.StdOut.Contains("Explicit")); - - // Verify that after edit it is now explicit false. - var listResult2 = TestCommon.RunAICLICommand("source list", "winget-font"); - Assert.AreEqual(Constants.ErrorCode.S_OK, listResult2.ExitCode); - Assert.True(listResult2.StdOut.Contains("false")); - - // Remove the source. This should correctly tombstone it, even though it is overridden. - var removeResult = TestCommon.RunAICLICommand("source remove", "winget-font"); - Assert.AreEqual(Constants.ErrorCode.S_OK, removeResult.ExitCode); - Assert.True(removeResult.StdOut.Contains("Done")); - - var listResult3 = TestCommon.RunAICLICommand("source list", "winget-font"); - Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, listResult3.ExitCode); - - // Force Reset Sources - var resetResult2 = TestCommon.RunAICLICommand("source reset", "--force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, resetResult2.ExitCode); - - // Verify it is back to being explicit true. - var listResult4 = TestCommon.RunAICLICommand("source list", "winget-font"); - Assert.AreEqual(Constants.ErrorCode.S_OK, listResult4.ExitCode); - Assert.True(listResult4.StdOut.Contains("true")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test source command. + /// + public class SourceCommand : BaseCommand + { + /// + /// One time set up. + /// + [OneTimeSetUp] + public void OneTimeSetup() + { + WinGetSettingsHelper.ConfigureFeature("sourcePriority", true); + } + + /// + /// Test set up. + /// + [SetUp] + public void Setup() + { + this.ResetTestSource(false); + } + + /// + /// Test source add. + /// + [Test] + public void SourceAdd() + { + // TODO: Our test source package is being rejected by SmartScreen on the build server. + // Reenable when SmartScreen issue is solved or removed. + Assert.Ignore(); + + var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); + } + + /// + /// Test source add with trust level. + /// + [Test] + public void SourceAddWithTrustLevel() + { + // Remove the test source. + TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); + + var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + + var listResult = TestCommon.RunAICLICommand("source list", $"-n SourceTest"); + Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); + Assert.True(listResult.StdOut.Contains("Trust Level")); + Assert.True(listResult.StdOut.Contains("Trusted")); + TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); + } + + /// + /// Test source add with store origin trust level. + /// + [Test] + public void SourceAddWithStoreOriginTrustLevel() + { + // Remove the test source. + TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); + + var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level storeOrigin"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_DATA_INTEGRITY_FAILURE, result.ExitCode); + Assert.True(result.StdOut.Contains("The source data is corrupted or tampered")); + } + + /// + /// Test source add with explicit flag. Packages should only appear if the source is explicitly declared. + /// + [Test] + public void SourceAddWithExplicit() + { + // Remove the test source. + TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); + + var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted --explicit"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + + var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode); + Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'")); + + var searchResult2 = TestCommon.RunAICLICommand("search", "TestExampleInstaller --source SourceTest"); + Assert.AreEqual(Constants.ErrorCode.S_OK, searchResult2.ExitCode); + Assert.True(searchResult2.StdOut.Contains("TestExampleInstaller")); + Assert.True(searchResult2.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); + } + + /// + /// Test source add with a priority value. + /// + [Test] + public void SourceAddWithPriority() + { + // Remove the test source. + TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); + + var result = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --priority 42"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + + var listResult = TestCommon.RunAICLICommand("source list", "SourceTest"); + Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); + Assert.True(listResult.StdOut.Contains("42")); + + var exportResult = TestCommon.RunAICLICommand("source export", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); + Assert.True(exportResult.StdOut.Contains("42")); + } + + /// + /// Test source add with duplicate name. + /// + [Test] + public void SourceAddWithDuplicateName() + { + // Add source with duplicate name should fail + var result = TestCommon.RunAICLICommand("source add", $"{Constants.TestSourceName} https://microsoft.com"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_ALREADY_EXISTS, result.ExitCode); + Assert.True(result.StdOut.Contains("A source with the given name already exists and refers to a different location")); + } + + /// + /// Test source add with duplicate source url. + /// + [Test] + public void SourceAddWithDuplicateSourceUrl() + { + // Add source with duplicate url should fail + var result = TestCommon.RunAICLICommand("source add", $"TestSource2 {Constants.TestSourceUrl}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_ARG_ALREADY_EXISTS, result.ExitCode); + Assert.True(result.StdOut.Contains("A source with a different name already refers to this location")); + } + + /// + /// Test source add with invalid url. + /// + [Test] + public void SourceAddWithInvalidURL() + { + // Add source with invalid url should fail + var result = TestCommon.RunAICLICommand("source add", $"AnotherSource {Constants.TestSourceUrl}/Invalid/Directory/Dont/Add/Me"); + Assert.AreEqual(Constants.ErrorCode.HTTP_E_STATUS_NOT_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("An unexpected error occurred while executing the command")); + } + + /// + /// Test source add with http url. + /// + [Test] + public void SourceAddWithHttpURL() + { + // Add source with an HTTP url should fail + var result = TestCommon.RunAICLICommand("source add", "Insecure http://microsoft.com"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NOT_SECURE, result.ExitCode); + Assert.True(result.StdOut.Contains("error occurred while executing the command")); + } + + /// + /// Test source list with no args. + /// + [Test] + public void SourceListWithNoArgs() + { + // List with no args should list all available sources + var result = TestCommon.RunAICLICommand("source list", string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); + } + + /// + /// Test source list with name. + /// + [Test] + public void SourceListWithName() + { + var result = TestCommon.RunAICLICommand("source list", $"-n {Constants.TestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains(Constants.TestSourceName)); + Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); + Assert.True(result.StdOut.Contains("Microsoft.PreIndexed.Package")); + Assert.True(result.StdOut.Contains("Trust Level")); + Assert.True(result.StdOut.Contains("Updated")); + } + + /// + /// Test source list name mismatch. + /// + [Test] + public void SourceListNameMismatch() + { + var result = TestCommon.RunAICLICommand("source list", "-n UnknownName"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + Assert.True(result.StdOut.Contains("Did not find a source named")); + } + + /// + /// Test source update. + /// + [Test] + public void SourceUpdate() + { + var result = TestCommon.RunAICLICommand("source update", $"-n {Constants.TestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + } + + /// + /// Test source update with invalid name. + /// + [Test] + public void SourceUpdateWithInvalidName() + { + var result = TestCommon.RunAICLICommand("source update", "-n UnknownName"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); + } + + /// + /// Test source remove by name. + /// + [Test] + public void SourceRemoveValidName() + { + var result = TestCommon.RunAICLICommand("source remove", $"-n {Constants.TestSourceName}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Done")); + this.ResetTestSource(false); + } + + /// + /// Test source remove with invalid name. + /// + [Test] + public void SourceRemoveInvalidName() + { + var result = TestCommon.RunAICLICommand("source remove", "-n UnknownName"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, result.ExitCode); + Assert.True(result.StdOut.Contains("Did not find a source named: UnknownName")); + } + + /// + /// Test source reset. + /// + [Test] + public void SourceReset() + { + var result = TestCommon.RunAICLICommand("source reset", string.Empty); + Assert.True(result.StdOut.Contains("The following sources will be reset if the --force option is given:")); + Assert.True(result.StdOut.Contains(Constants.TestSourceName)); + Assert.True(result.StdOut.Contains(Constants.TestSourceUrl)); + } + + /// + /// Test source reset force. + /// + [Test] + public void SourceForceReset() + { + // Force Reset Sources + var result = TestCommon.RunAICLICommand("source reset", "--force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Resetting all sources...Done")); + + // Verify sources have been reset + result = TestCommon.RunAICLICommand("source list", string.Empty); + Assert.True(result.StdOut.Contains("winget")); + Assert.True(result.StdOut.Contains("https://cdn.winget.microsoft.com/cache")); + Assert.False(result.StdOut.Contains(Constants.TestSourceName)); + Assert.False(result.StdOut.Contains(Constants.TestSourceUrl)); + } + + /// + /// Test source edit with explicit flag, edit the source to not be explicit. + /// + [Test] + public void SourceEdit() + { + // Remove the test source. + TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); + + // Add source as explicit and verify it is explicit. + var addResult = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl} --trust-level trusted --explicit"); + Assert.AreEqual(Constants.ErrorCode.S_OK, addResult.ExitCode); + Assert.True(addResult.StdOut.Contains("Done")); + + var searchResult = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_SOURCES_DEFINED, searchResult.ExitCode); + Assert.True(searchResult.StdOut.Contains("No sources defined; add one with 'source add' or reset to defaults with 'source reset'")); + + // Run the edit, this should be S_OK with "Done" as it changed the state to not-explicit. + var editResult = TestCommon.RunAICLICommand("source edit", $"SourceTest --explicit false"); + Assert.AreEqual(Constants.ErrorCode.S_OK, editResult.ExitCode); + Assert.True(editResult.StdOut.Contains("Explicit")); + + // Run it again, this should result in S_OK with no changes and a message that the source is already in that state. + var editResult2 = TestCommon.RunAICLICommand("source edit", $"SourceTest --explicit false"); + Assert.AreEqual(Constants.ErrorCode.S_OK, editResult2.ExitCode); + Assert.True(editResult2.StdOut.Contains("The source named 'SourceTest' is already in the desired state.")); + + // Now verify it is no longer explicit by running the search again without adding the source parameter. + var searchResult2 = TestCommon.RunAICLICommand("search", "TestExampleInstaller"); + Assert.AreEqual(Constants.ErrorCode.S_OK, searchResult2.ExitCode); + Assert.True(searchResult2.StdOut.Contains("TestExampleInstaller")); + Assert.True(searchResult2.StdOut.Contains("AppInstallerTest.TestExampleInstaller")); + TestCommon.RunAICLICommand("source remove", $"-n SourceTest"); + } + + /// + /// Test source edit with priority. + /// + [Test] + public void SourceEdit_Priority() + { + // Remove the test source. + TestCommon.RunAICLICommand("source remove", Constants.TestSourceName); + + var addResult = TestCommon.RunAICLICommand("source add", $"SourceTest {Constants.TestSourceUrl}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, addResult.ExitCode); + Assert.True(addResult.StdOut.Contains("Done")); + + // Run the edit, this should be S_OK with "Done" as it changed the state + var editResult = TestCommon.RunAICLICommand("source edit", $"SourceTest --priority 14"); + Assert.AreEqual(Constants.ErrorCode.S_OK, editResult.ExitCode); + Assert.True(editResult.StdOut.Contains("14")); + + // Run it again, this should result in S_OK with no changes and a message that the source is already in that state. + var editResult2 = TestCommon.RunAICLICommand("source edit", $"SourceTest --priority 14"); + Assert.AreEqual(Constants.ErrorCode.S_OK, editResult2.ExitCode); + Assert.True(editResult2.StdOut.Contains("The source named 'SourceTest' is already in the desired state.")); + } + + /// + /// Test override of a default source via edit command. + /// + [Test] + public void SourceEditOverrideDefault() + { + // Force Reset Sources + var resetResult = TestCommon.RunAICLICommand("source reset", "--force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, resetResult.ExitCode); + + // Verify it is explicit true. Explicit is the only boolean value in the output. + var listResult = TestCommon.RunAICLICommand("source list", "winget-font"); + Assert.AreEqual(Constants.ErrorCode.S_OK, listResult.ExitCode); + Assert.True(listResult.StdOut.Contains("true")); + + var editResult = TestCommon.RunAICLICommand("source edit", "winget-font -e false"); + Assert.AreEqual(Constants.ErrorCode.S_OK, editResult.ExitCode); + Assert.True(editResult.StdOut.Contains("Explicit")); + + // Verify that after edit it is now explicit false. + var listResult2 = TestCommon.RunAICLICommand("source list", "winget-font"); + Assert.AreEqual(Constants.ErrorCode.S_OK, listResult2.ExitCode); + Assert.True(listResult2.StdOut.Contains("false")); + + // Remove the source. This should correctly tombstone it, even though it is overridden. + var removeResult = TestCommon.RunAICLICommand("source remove", "winget-font"); + Assert.AreEqual(Constants.ErrorCode.S_OK, removeResult.ExitCode); + Assert.True(removeResult.StdOut.Contains("Done")); + + var listResult3 = TestCommon.RunAICLICommand("source list", "winget-font"); + Assert.AreEqual(Constants.ErrorCode.ERROR_SOURCE_NAME_DOES_NOT_EXIST, listResult3.ExitCode); + + // Force Reset Sources + var resetResult2 = TestCommon.RunAICLICommand("source reset", "--force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, resetResult2.ExitCode); + + // Verify it is back to being explicit true. + var listResult4 = TestCommon.RunAICLICommand("source list", "winget-font"); + Assert.AreEqual(Constants.ErrorCode.S_OK, listResult4.ExitCode); + Assert.True(listResult4.StdOut.Contains("true")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/Test.runsettings b/src/AppInstallerCLIE2ETests/Test.runsettings index 6b5ac0d4ff..bbdc7f7163 100644 --- a/src/AppInstallerCLIE2ETests/Test.runsettings +++ b/src/AppInstallerCLIE2ETests/Test.runsettings @@ -1,43 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ConfigServerUnexpectedExit.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ConfigServerUnexpectedExit.yml index 9a3db50a08..b74e4dba7f 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ConfigServerUnexpectedExit.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ConfigServerUnexpectedExit.yml @@ -1,17 +1,17 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2ETestResourceCrash - id: first - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - key: Foo - - resource: xE2ETestResource/E2EFileResource - dependsOn: - - first - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\ConfigServerUnexpectedExit.txt - Content: Contents! +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceCrash + id: first + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + dependsOn: + - first + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\ConfigServerUnexpectedExit.txt + Content: Contents! diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo.yml index 58038098b3..45eecbe312 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo.yml @@ -1,9 +1,9 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2EFileResource - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\Configure_TestRepo.txt - Content: Contents! +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2EFileResource + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\Configure_TestRepo.txt + Content: Contents! diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_DSCv3.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_DSCv3.yml index 3a88835562..900be467a3 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_DSCv3.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_DSCv3.yml @@ -1,10 +1,10 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - processor: dscv3 -resources: - - name: Test File - type: xE2ETestResource/E2EFileResource - properties: - Path: ${WinGetConfigRoot}\Configure_TestRepo.txt - Content: Contents! +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Test File + type: xE2ETestResource/E2EFileResource + properties: + Path: ${WinGetConfigRoot}\Configure_TestRepo.txt + Content: Contents! diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_Location.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_Location.yml index df92d80ac8..88675d6096 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_Location.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Configure_TestRepo_Location.yml @@ -1,8 +1,8 @@ -properties: - configurationVersion: 0.2 - assertions: - - resource: xE2ETestResource/E2ETestResource - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: +properties: + configurationVersion: 0.2 + assertions: + - resource: xE2ETestResource/E2ETestResource + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: SecretCode: 4815162342 \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/DependencyCycle.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/DependencyCycle.yml index 1bea30876e..bad5c901cd 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/DependencyCycle.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/DependencyCycle.yml @@ -1,23 +1,23 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2ETestResourceThrows - id: A - dependsOn: - - B - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - key: Foo - - resource: xE2ETestResource/E2EFileResource - id: B - dependsOn: - - A - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt - Content: Contents! - - resource: NotMentioned - settings: - A: B +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + id: A + dependsOn: + - B + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + id: B + dependsOn: + - A + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt + Content: Contents! + - resource: NotMentioned + settings: + A: B diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/DependentResources_Failure.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/DependentResources_Failure.yml index 4ab9a829b0..2d97952623 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/DependentResources_Failure.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/DependentResources_Failure.yml @@ -1,17 +1,17 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2ETestResourceThrows - id: first - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - key: Foo - - resource: xE2ETestResource/E2EFileResource - dependsOn: - - first - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\DependentResources_Failure.txt - Content: Contents! +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + id: first + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + dependsOn: + - first + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\DependentResources_Failure.txt + Content: Contents! diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/DuplicateIdentifiers.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/DuplicateIdentifiers.yml index 0454401684..be1b320cfa 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/DuplicateIdentifiers.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/DuplicateIdentifiers.yml @@ -1,19 +1,19 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2ETestResourceThrows - id: same - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - key: Foo - - resource: xE2ETestResource/E2EFileResource - id: same - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt - Content: Contents! - - resource: NotMentioned - settings: - A: B +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + id: same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + id: same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt + Content: Contents! + - resource: NotMentioned + settings: + A: B diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/GetPSModulePath.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/GetPSModulePath.yml index ea6692f981..2e70a5202f 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/GetPSModulePath.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/GetPSModulePath.yml @@ -1,11 +1,11 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - 1e62d683-2999-44e7-81f7-6f8f35e8d731: true -resources: - - name: Name1 - type: xE2ETestResource/E2ETestResourcePSModulePath - metadata: - repository: AppInstallerCLIE2ETestsRepo - securityContext: elevated - properties: - outputPath: ${WinGetConfigRoot}\PSModulePath.txt +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + 1e62d683-2999-44e7-81f7-6f8f35e8d731: true +resources: + - name: Name1 + type: xE2ETestResource/E2ETestResourcePSModulePath + metadata: + repository: AppInstallerCLIE2ETestsRepo + securityContext: elevated + properties: + outputPath: ${WinGetConfigRoot}\PSModulePath.txt diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/IndependentResources_OneFailure.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/IndependentResources_OneFailure.yml index b702eeb4ba..a39ad612de 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/IndependentResources_OneFailure.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/IndependentResources_OneFailure.yml @@ -1,14 +1,14 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2ETestResourceThrows - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - key: Foo - - resource: xE2ETestResource/E2EFileResource - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt - Content: Contents! +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt + Content: Contents! diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Init-TestRepository.ps1 b/src/AppInstallerCLIE2ETests/TestData/Configuration/Init-TestRepository.ps1 index 0bd04e3ff3..808f249e0a 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Init-TestRepository.ps1 +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Init-TestRepository.ps1 @@ -1,65 +1,65 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[CmdletBinding()] -param( - [string]$ModulesPath, - - [string]$RepositoryPath, - - [string]$RepositoryName, - - [switch]$Force -) - -if ([System.String]::IsNullOrEmpty($ModulesPath)) -{ - $ModulesPath = Join-Path $PSScriptRoot "Modules" -} - -if ([System.String]::IsNullOrEmpty($RepositoryPath)) -{ - $RepositoryPath = Join-Path ([System.IO.Path]::GetTempPath()) (New-Guid) -} - -if ([System.String]::IsNullOrEmpty($RepositoryName)) -{ - $RepositoryName = "AppInstallerCLIE2ETestsRepo" -} - -if ($Force) { - $null = New-Item -Path $RepositoryPath -ItemType Directory -Force -} else { - $null = New-Item -Path $RepositoryPath -ItemType Directory -ErrorAction Inquire -} - -$Local:existingRepository = Get-PSRepository -Name $RepositoryName -ErrorAction Ignore -if ($Local:existingRepository) -{ - if ($Force) - { - Unregister-PSRepository -Name $RepositoryName - } - else - { - throw "Repository named $RepositoryName is already registered. Use -Force to overwrite it." - } -} - -$null = Register-PSRepository -Name $RepositoryName -SourceLocation $RepositoryPath -ScriptSourceLocation $RepositoryPath - -$Local:allItems = Get-ChildItem $ModulesPath - -$Local:progressActivity = "Publishing modules to $RepositoryPath" -Write-Progress -Activity $Local:progressActivity - -[Int32]$Local:modulesPublished = 0 - -$Local:allItems | ForEach-Object -Process { - $Local:modulePath = $_.FullName - Write-Verbose "Publishing $Local:modulePath" - Publish-Module -Path $Local:modulePath -Repository $RepositoryName -Force - $Local:modulesPublished += 1 - Write-Progress -Activity $Local:progressActivity -PercentComplete (($Local:modulesPublished * 100) / $Local:allItems.Count) -} - -Write-Progress -Activity $Local:progressActivity -Completed +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + [string]$ModulesPath, + + [string]$RepositoryPath, + + [string]$RepositoryName, + + [switch]$Force +) + +if ([System.String]::IsNullOrEmpty($ModulesPath)) +{ + $ModulesPath = Join-Path $PSScriptRoot "Modules" +} + +if ([System.String]::IsNullOrEmpty($RepositoryPath)) +{ + $RepositoryPath = Join-Path ([System.IO.Path]::GetTempPath()) (New-Guid) +} + +if ([System.String]::IsNullOrEmpty($RepositoryName)) +{ + $RepositoryName = "AppInstallerCLIE2ETestsRepo" +} + +if ($Force) { + $null = New-Item -Path $RepositoryPath -ItemType Directory -Force +} else { + $null = New-Item -Path $RepositoryPath -ItemType Directory -ErrorAction Inquire +} + +$Local:existingRepository = Get-PSRepository -Name $RepositoryName -ErrorAction Ignore +if ($Local:existingRepository) +{ + if ($Force) + { + Unregister-PSRepository -Name $RepositoryName + } + else + { + throw "Repository named $RepositoryName is already registered. Use -Force to overwrite it." + } +} + +$null = Register-PSRepository -Name $RepositoryName -SourceLocation $RepositoryPath -ScriptSourceLocation $RepositoryPath + +$Local:allItems = Get-ChildItem $ModulesPath + +$Local:progressActivity = "Publishing modules to $RepositoryPath" +Write-Progress -Activity $Local:progressActivity + +[Int32]$Local:modulesPublished = 0 + +$Local:allItems | ForEach-Object -Process { + $Local:modulePath = $_.FullName + Write-Verbose "Publishing $Local:modulePath" + Publish-Module -Path $Local:modulePath -Repository $RepositoryName -Force + $Local:modulesPublished += 1 + Write-Progress -Activity $Local:progressActivity -PercentComplete (($Local:modulesPublished * 100) / $Local:allItems.Count) +} + +Write-Progress -Activity $Local:progressActivity -Completed diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/LargeContentStrings.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/LargeContentStrings.yml index a5971d708d..7a953ff76c 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/LargeContentStrings.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/LargeContentStrings.yml @@ -1,22 +1,22 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2EMalicious/E2EMalicious - id: firstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirst - directives: - repository: AppInstallerCLIE2ETestsRepo - description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" - settings: - key: Foo - description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" - - resource: Unknown - dependsOn: - - firstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirst - directives: - module: UnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknown - repository: AppInstallerCLIE2ETestsRepo - description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" - settings: - Path: ${WinGetConfigRoot}\ConfigServerUnexpectedExit.txt - Content: Contents! - description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" +properties: + configurationVersion: 0.2 + resources: + - resource: xE2EMalicious/E2EMalicious + id: firstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirst + directives: + repository: AppInstallerCLIE2ETestsRepo + description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" + settings: + key: Foo + description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" + - resource: Unknown + dependsOn: + - firstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirstfirst + directives: + module: UnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknownUnknown + repository: AppInstallerCLIE2ETestsRepo + description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" + settings: + Path: ${WinGetConfigRoot}\ConfigServerUnexpectedExit.txt + Content: Contents! + description: "Line1\nLine2\nLine3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3Line3\nLine4\nLine5\nLine6" diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/MissingDependency.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/MissingDependency.yml index e3553aa493..5d2a13e76a 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/MissingDependency.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/MissingDependency.yml @@ -1,22 +1,22 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2ETestResourceThrows - id: same - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - key: Foo - - resource: xE2ETestResource/E2EFileResource - dependsOn: - - same - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt - Content: Contents! - - resource: MissingDependency - dependsOn: - - missing - settings: - A: B +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2ETestResourceThrows + id: same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo + - resource: xE2ETestResource/E2EFileResource + dependsOn: + - same + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\IndependentResources_OneFailure.txt + Content: Contents! + - resource: MissingDependency + dependsOn: + - missing + settings: + A: B diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ModuleMismatch.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ModuleMismatch.yml index 991a5be477..9407b8c346 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ModuleMismatch.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ModuleMismatch.yml @@ -1,9 +1,9 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: Module/Resource - id: Identifier - directives: - module: DifferentModule - settings: +properties: + configurationVersion: 0.2 + resources: + - resource: Module/Resource + id: Identifier + directives: + module: DifferentModule + settings: SettingInt: 1 \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psd1 b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psd1 index fcf009be10..76280ec04d 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psd1 +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psd1 @@ -1,33 +1,33 @@ -# -# Module manifest for module 'xE2ETestResource' -# - -@{ - -RootModule = 'xE2EMalicious.psm1' -ModuleVersion = '0.0.0.1' -GUID = 'a0be43e8-ac22-4244-8efc-7263dfa50b92' -CompatiblePSEditions = 'Core' -Author = "WinGet Dev Team" -CompanyName = 'Microsoft Corporation' -Copyright = '(c) Microsoft Corporation. All rights reserved.' -Description = "PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests" -PowerShellVersion = '7.2' -FunctionsToExport = @() -CmdletsToExport = @() -DscResourcesToExport = @( - 'E2EMalicious' -) -HelpInfoURI = 'https://www.contoso.com/help' - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - ProjectUri = 'https://github.com/microsoft/winget-cli' - IconUri = 'https://www.contoso.com/icons/icon.png' - } - -} - -} +# +# Module manifest for module 'xE2ETestResource' +# + +@{ + +RootModule = 'xE2EMalicious.psm1' +ModuleVersion = '0.0.0.1' +GUID = 'a0be43e8-ac22-4244-8efc-7263dfa50b92' +CompatiblePSEditions = 'Core' +Author = "WinGet Dev Team" +CompanyName = 'Microsoft Corporation' +Copyright = '(c) Microsoft Corporation. All rights reserved.' +Description = "PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests | PowerShell module with DSC resources for unit tests" +PowerShellVersion = '7.2' +FunctionsToExport = @() +CmdletsToExport = @() +DscResourcesToExport = @( + 'E2EMalicious' +) +HelpInfoURI = 'https://www.contoso.com/help' + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + ProjectUri = 'https://github.com/microsoft/winget-cli' + IconUri = 'https://www.contoso.com/icons/icon.png' + } + +} + +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psm1 b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psm1 index 603827fb55..1fb3d7ae72 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psm1 +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2EMalicious/xE2EMalicious.psm1 @@ -1,86 +1,86 @@ -# E2E module with resources. - -enum Ensure -{ - Absent - Present -} - -# This resource just checks if a file is there or not with and if its with the specified content. -[DscResource()] -class E2EMalicious -{ - [DscProperty(Key)] - [string] $Path - - [DscProperty()] - [Ensure] $Ensure = [Ensure]::Present - - [DscProperty()] - [string] $Content = $null - - [E2EFileResource] Get() - { - if ([string]::IsNullOrEmpty($this.Path)) - { - throw - } - - $fileContent = $null - if (Test-Path -Path $this.Path -PathType Leaf) - { - $fileContent = Get-Content $this.Path -Raw - } - - $result = @{ - Path = $this.Path - Content = $fileContent - } - - return $result - } - - [bool] Test() - { - $get = $this.Get() - - if (Test-Path -Path $this.Path -PathType Leaf) - { - if ($this.Ensure -eq [Ensure]::Present) - { - return $this.Content -eq $get.Content - } - } - elseif ($this.Ensure -eq [Ensure]::Absent) - { - return $true - } - - return $false - } - - [void] Set() - { - if (-not $this.Test()) - { - if (Test-Path -Path $this.Path -PathType Leaf) - { - if ($this.Ensure -eq [Ensure]::Present) - { - Set-Content $this.Path $this.Content -NoNewline - } - else - { - Remove-Item $this.Path - } - } - else - { - if ($this.Ensure -eq [Ensure]::Present) - { - Set-Content $this.Path $this.Content -NoNewline - } - } - } - } -} +# E2E module with resources. + +enum Ensure +{ + Absent + Present +} + +# This resource just checks if a file is there or not with and if its with the specified content. +[DscResource()] +class E2EMalicious +{ + [DscProperty(Key)] + [string] $Path + + [DscProperty()] + [Ensure] $Ensure = [Ensure]::Present + + [DscProperty()] + [string] $Content = $null + + [E2EFileResource] Get() + { + if ([string]::IsNullOrEmpty($this.Path)) + { + throw + } + + $fileContent = $null + if (Test-Path -Path $this.Path -PathType Leaf) + { + $fileContent = Get-Content $this.Path -Raw + } + + $result = @{ + Path = $this.Path + Content = $fileContent + } + + return $result + } + + [bool] Test() + { + $get = $this.Get() + + if (Test-Path -Path $this.Path -PathType Leaf) + { + if ($this.Ensure -eq [Ensure]::Present) + { + return $this.Content -eq $get.Content + } + } + elseif ($this.Ensure -eq [Ensure]::Absent) + { + return $true + } + + return $false + } + + [void] Set() + { + if (-not $this.Test()) + { + if (Test-Path -Path $this.Path -PathType Leaf) + { + if ($this.Ensure -eq [Ensure]::Present) + { + Set-Content $this.Path $this.Content -NoNewline + } + else + { + Remove-Item $this.Path + } + } + else + { + if ($this.Ensure -eq [Ensure]::Present) + { + Set-Content $this.Path $this.Content -NoNewline + } + } + } + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psd1 b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psd1 index 65507e16fc..2f9f0989d7 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psd1 +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psd1 @@ -1,40 +1,40 @@ -# -# Module manifest for module 'xE2ETestResource' -# - -@{ - -RootModule = 'xE2ETestResource.psm1' -ModuleVersion = '0.0.0.1' -GUID = 'a0be43e8-ac22-4244-8efc-7263dfa50b8c' -CompatiblePSEditions = 'Core' -Author = 'WinGet Dev Team' -CompanyName = 'Microsoft Corporation' -Copyright = '(c) Microsoft Corporation. All rights reserved.' -Description = 'PowerShell module with DSC resources for unit tests' -PowerShellVersion = '7.2' -FunctionsToExport = @() -CmdletsToExport = @() -DscResourcesToExport = @( - 'E2EFileResource' - 'E2ETestResource' - 'E2ETestResourceThrows' - 'E2ETestResourceError' - 'E2ETestResourceTypes' - 'E2ETestResourceCrash' - 'E2ETestResourcePID' - 'E2ETestResourcePSModulePath' -) -HelpInfoURI = 'https://www.contoso.com/help' - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - ProjectUri = 'https://github.com/microsoft/winget-cli' - IconUri = 'https://www.contoso.com/icons/icon.png' - } - -} - -} +# +# Module manifest for module 'xE2ETestResource' +# + +@{ + +RootModule = 'xE2ETestResource.psm1' +ModuleVersion = '0.0.0.1' +GUID = 'a0be43e8-ac22-4244-8efc-7263dfa50b8c' +CompatiblePSEditions = 'Core' +Author = 'WinGet Dev Team' +CompanyName = 'Microsoft Corporation' +Copyright = '(c) Microsoft Corporation. All rights reserved.' +Description = 'PowerShell module with DSC resources for unit tests' +PowerShellVersion = '7.2' +FunctionsToExport = @() +CmdletsToExport = @() +DscResourcesToExport = @( + 'E2EFileResource' + 'E2ETestResource' + 'E2ETestResourceThrows' + 'E2ETestResourceError' + 'E2ETestResourceTypes' + 'E2ETestResourceCrash' + 'E2ETestResourcePID' + 'E2ETestResourcePSModulePath' +) +HelpInfoURI = 'https://www.contoso.com/help' + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + ProjectUri = 'https://github.com/microsoft/winget-cli' + IconUri = 'https://www.contoso.com/icons/icon.png' + } + +} + +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psm1 b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psm1 index 5c5e0da43a..5da368c279 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psm1 +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Modules/xE2ETestResource/xE2ETestResource.psm1 @@ -1,357 +1,357 @@ -# E2E module with resources. - -enum Ensure -{ - Absent - Present -} - -# This resource just checks if a file is there or not with and if its with the specified content. -[DscResource()] -class E2EFileResource -{ - [DscProperty(Key)] - [string] $Path - - [DscProperty()] - [Ensure] $Ensure = [Ensure]::Present - - [DscProperty()] - [string] $Content = $null - - [E2EFileResource] Get() - { - if ([string]::IsNullOrEmpty($this.Path)) - { - throw - } - - $fileContent = $null - if (Test-Path -Path $this.Path -PathType Leaf) - { - $fileContent = Get-Content $this.Path -Raw - } - - $result = @{ - Path = $this.Path - Content = $fileContent - } - - return $result - } - - [bool] Test() - { - $get = $this.Get() - - if (Test-Path -Path $this.Path -PathType Leaf) - { - if ($this.Ensure -eq [Ensure]::Present) - { - return $this.Content -eq $get.Content - } - } - elseif ($this.Ensure -eq [Ensure]::Absent) - { - return $true - } - - return $false - } - - [void] Set() - { - if (-not $this.Test()) - { - if (Test-Path -Path $this.Path -PathType Leaf) - { - if ($this.Ensure -eq [Ensure]::Present) - { - Set-Content $this.Path $this.Content -NoNewline - } - else - { - Remove-Item $this.Path - } - } - else - { - if ($this.Ensure -eq [Ensure]::Present) - { - Set-Content $this.Path $this.Content -NoNewline - } - } - } - } -} - -[DscResource()] -class E2ETestResource -{ - [DscProperty(Key)] - [string] $key - - [DscProperty(Mandatory)] - [string] $secretCode - - [E2ETestResource] Get() - { - $result = @{ - key = "E2ETestResourceKey" - } - return $result - } - - [bool] Test() - { - return $this.secretCode -eq "4815162342" - } - - [void] Set() - { - if (-not $this.Test()) - { - $global:DSCMachineStatus = 1 - } - } -} - -[DscResource()] -class E2ETestResourceThrows -{ - [DscProperty(Key)] - [string] $key - - [E2ETestResourceThrows] Get() - { - $result = @{ - key = "E2ETestResourceThrowsKey" - } - throw "throws in Get" - return $result - } - - [bool] Test() - { - throw "throws in Test" - return $false - } - - [void] Set() - { - throw "throws in Set" - } -} - -[DscResource()] -class E2ETestResourceError -{ - [DscProperty(Key)] - [string] $key - - [E2ETestResourceError] Get() - { - $result = @{ - key = "E2ETestResourceErrorKey" - } - Write-Error "Error in Get" - return $result - } - - [bool] Test() - { - Write-Error "Error in Test" - return $true - } - - [void] Set() - { - Write-Error "Error in Set" - } -} - -[DscResource()] -class E2ETestResourceTypes -{ - [DscProperty(Key)] - [string] $key - - [DscProperty()] - [boolean] $boolProperty - - [DscProperty()] - [int] $intProperty; - - [DscProperty()] - [double] $doubleProperty; - - [DscProperty()] - [char] $charProperty; - - [DscProperty()] - [Hashtable] $hashtableProperty; - - [E2ETestResourceTypes] Get() - { - $result = @{ - key = "E2ETestResourceTypesKey" - boolProperty = $false - intProperty = 0 - doubleProperty = 0.0 - charProperty = 'z' - hashtableProperty = @{} - } - return $result - } - - [bool] Test() - { - # Because we can't get the error stream from a class based resource, I throw so is easier to know if - # there's something wrong. - if ($this.boolProperty -ne $true) - { - throw "Failed boolProperty" - } - - if ($this.intProperty -ne 3) - { - throw "Failed intProperty. Got $($this.intProperty)" - } - - if ($this.doubleProperty -ne -9.876) - { - throw "Failed doubleProperty Got $($this.doubleProperty)" - } - - if ($this.charProperty -ne 'f') - { - throw "Failed charProperty Got $($this.charProperty)" - } - - if ($this.hashtableProperty.ContainsKey("secretStringKey")) - { - if ($this.hashtableProperty["secretStringKey"] -ne "secretCode") - { - throw "Failed comparing value of `$hashtableProperty.secretStringKey Got $($this.hashtableProperty["secretStringKey"])" - } - } - else - { - throw "Failed finding secretStringKey in hashtableProperty" - } - - if ($this.hashtableProperty.ContainsKey("secretIntKey")) - { - if ($this.hashtableProperty["secretIntKey"] -ne 123456) - { - throw "Failed comparing value of `$hashtableProperty.secretIntKey Got $($this.hashtableProperty["secretIntKey"])" - } - } - else - { - throw "Failed finding secretIntKey in hashtableProperty" - } - - return $true - } - - [void] Set() - { - # no-op - } -} - -# This resource "crashes" the containing process (really it just exits) -[DscResource()] -class E2ETestResourceCrash -{ - [DscProperty(Key)] - [string] $key - - [E2ETestResourceCrash] Get() - { - $result = @{ - key = "E2ETestResourceCrashKey" - } - [System.Environment]::Exit(0) - return $result - } - - [bool] Test() - { - [System.Environment]::Exit(0) - return $true - } - - [void] Set() - { - [System.Environment]::Exit(0) - } -} - -# This resource writes the current PID to the provided file path. -[DscResource()] -class E2ETestResourcePID -{ - [DscProperty(Key)] - [string] $key - - [DscProperty(Mandatory)] - [string] $directoryPath - - [E2ETestResourcePID] Get() - { - $result = @{ - key = "E2ETestResourcePID" - directoryPath = $this.directoryPath - } - - return $result - } - - [bool] Test() - { - return $false - } - - [void] Set() - { - if (Test-Path -Path $this.directoryPath) - { - $processId = [System.Diagnostics.Process]::GetCurrentProcess().Id - $filePath = Join-Path -Path $this.directoryPath -ChildPath "$processId.txt" - New-Item -Path $filePath -ItemType File -Force - } - } -} - -# This resource writes the current PSModulePath to the provided file path. -[DscResource()] -class E2ETestResourcePSModulePath -{ - [DscProperty(Key)] - [string] $key - - [DscProperty(Mandatory)] - [string] $outputPath - - [E2ETestResourcePSModulePath] Get() - { - $result = @{ - key = "E2ETestResourcePSModulePath" - outputPath = $this.outputPath - } - - return $result - } - - [bool] Test() - { - return $false - } - - [void] Set() - { - Set-Content -Path $this.outputPath -Value $env:PSModulePath -Force - } -} +# E2E module with resources. + +enum Ensure +{ + Absent + Present +} + +# This resource just checks if a file is there or not with and if its with the specified content. +[DscResource()] +class E2EFileResource +{ + [DscProperty(Key)] + [string] $Path + + [DscProperty()] + [Ensure] $Ensure = [Ensure]::Present + + [DscProperty()] + [string] $Content = $null + + [E2EFileResource] Get() + { + if ([string]::IsNullOrEmpty($this.Path)) + { + throw + } + + $fileContent = $null + if (Test-Path -Path $this.Path -PathType Leaf) + { + $fileContent = Get-Content $this.Path -Raw + } + + $result = @{ + Path = $this.Path + Content = $fileContent + } + + return $result + } + + [bool] Test() + { + $get = $this.Get() + + if (Test-Path -Path $this.Path -PathType Leaf) + { + if ($this.Ensure -eq [Ensure]::Present) + { + return $this.Content -eq $get.Content + } + } + elseif ($this.Ensure -eq [Ensure]::Absent) + { + return $true + } + + return $false + } + + [void] Set() + { + if (-not $this.Test()) + { + if (Test-Path -Path $this.Path -PathType Leaf) + { + if ($this.Ensure -eq [Ensure]::Present) + { + Set-Content $this.Path $this.Content -NoNewline + } + else + { + Remove-Item $this.Path + } + } + else + { + if ($this.Ensure -eq [Ensure]::Present) + { + Set-Content $this.Path $this.Content -NoNewline + } + } + } + } +} + +[DscResource()] +class E2ETestResource +{ + [DscProperty(Key)] + [string] $key + + [DscProperty(Mandatory)] + [string] $secretCode + + [E2ETestResource] Get() + { + $result = @{ + key = "E2ETestResourceKey" + } + return $result + } + + [bool] Test() + { + return $this.secretCode -eq "4815162342" + } + + [void] Set() + { + if (-not $this.Test()) + { + $global:DSCMachineStatus = 1 + } + } +} + +[DscResource()] +class E2ETestResourceThrows +{ + [DscProperty(Key)] + [string] $key + + [E2ETestResourceThrows] Get() + { + $result = @{ + key = "E2ETestResourceThrowsKey" + } + throw "throws in Get" + return $result + } + + [bool] Test() + { + throw "throws in Test" + return $false + } + + [void] Set() + { + throw "throws in Set" + } +} + +[DscResource()] +class E2ETestResourceError +{ + [DscProperty(Key)] + [string] $key + + [E2ETestResourceError] Get() + { + $result = @{ + key = "E2ETestResourceErrorKey" + } + Write-Error "Error in Get" + return $result + } + + [bool] Test() + { + Write-Error "Error in Test" + return $true + } + + [void] Set() + { + Write-Error "Error in Set" + } +} + +[DscResource()] +class E2ETestResourceTypes +{ + [DscProperty(Key)] + [string] $key + + [DscProperty()] + [boolean] $boolProperty + + [DscProperty()] + [int] $intProperty; + + [DscProperty()] + [double] $doubleProperty; + + [DscProperty()] + [char] $charProperty; + + [DscProperty()] + [Hashtable] $hashtableProperty; + + [E2ETestResourceTypes] Get() + { + $result = @{ + key = "E2ETestResourceTypesKey" + boolProperty = $false + intProperty = 0 + doubleProperty = 0.0 + charProperty = 'z' + hashtableProperty = @{} + } + return $result + } + + [bool] Test() + { + # Because we can't get the error stream from a class based resource, I throw so is easier to know if + # there's something wrong. + if ($this.boolProperty -ne $true) + { + throw "Failed boolProperty" + } + + if ($this.intProperty -ne 3) + { + throw "Failed intProperty. Got $($this.intProperty)" + } + + if ($this.doubleProperty -ne -9.876) + { + throw "Failed doubleProperty Got $($this.doubleProperty)" + } + + if ($this.charProperty -ne 'f') + { + throw "Failed charProperty Got $($this.charProperty)" + } + + if ($this.hashtableProperty.ContainsKey("secretStringKey")) + { + if ($this.hashtableProperty["secretStringKey"] -ne "secretCode") + { + throw "Failed comparing value of `$hashtableProperty.secretStringKey Got $($this.hashtableProperty["secretStringKey"])" + } + } + else + { + throw "Failed finding secretStringKey in hashtableProperty" + } + + if ($this.hashtableProperty.ContainsKey("secretIntKey")) + { + if ($this.hashtableProperty["secretIntKey"] -ne 123456) + { + throw "Failed comparing value of `$hashtableProperty.secretIntKey Got $($this.hashtableProperty["secretIntKey"])" + } + } + else + { + throw "Failed finding secretIntKey in hashtableProperty" + } + + return $true + } + + [void] Set() + { + # no-op + } +} + +# This resource "crashes" the containing process (really it just exits) +[DscResource()] +class E2ETestResourceCrash +{ + [DscProperty(Key)] + [string] $key + + [E2ETestResourceCrash] Get() + { + $result = @{ + key = "E2ETestResourceCrashKey" + } + [System.Environment]::Exit(0) + return $result + } + + [bool] Test() + { + [System.Environment]::Exit(0) + return $true + } + + [void] Set() + { + [System.Environment]::Exit(0) + } +} + +# This resource writes the current PID to the provided file path. +[DscResource()] +class E2ETestResourcePID +{ + [DscProperty(Key)] + [string] $key + + [DscProperty(Mandatory)] + [string] $directoryPath + + [E2ETestResourcePID] Get() + { + $result = @{ + key = "E2ETestResourcePID" + directoryPath = $this.directoryPath + } + + return $result + } + + [bool] Test() + { + return $false + } + + [void] Set() + { + if (Test-Path -Path $this.directoryPath) + { + $processId = [System.Diagnostics.Process]::GetCurrentProcess().Id + $filePath = Join-Path -Path $this.directoryPath -ChildPath "$processId.txt" + New-Item -Path $filePath -ItemType File -Force + } + } +} + +# This resource writes the current PSModulePath to the provided file path. +[DscResource()] +class E2ETestResourcePSModulePath +{ + [DscProperty(Key)] + [string] $key + + [DscProperty(Mandatory)] + [string] $outputPath + + [E2ETestResourcePSModulePath] Get() + { + $result = @{ + key = "E2ETestResourcePSModulePath" + outputPath = $this.outputPath + } + + return $result + } + + [bool] Test() + { + return $false + } + + [void] Set() + { + Set-Content -Path $this.outputPath -Value $env:PSModulePath -Force + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/NoResourceName.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/NoResourceName.yml index 7d422056c4..4f36bb1b21 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/NoResourceName.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/NoResourceName.yml @@ -1,7 +1,7 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: Module/ - id: Identifier - settings: +properties: + configurationVersion: 0.2 + resources: + - resource: Module/ + id: Identifier + settings: SettingInt: 1 \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/NoVersion.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/NoVersion.yml index fb46011d7d..3846ae8138 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/NoVersion.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/NoVersion.yml @@ -1,2 +1,2 @@ -properties: +properties: value: 1 \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoModule_NoSettings.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoModule_NoSettings.yml index b3ff5f1402..2a947c578f 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoModule_NoSettings.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoModule_NoSettings.yml @@ -1,6 +1,6 @@ -properties: - configurationVersion: 0.1 - resources: - - resource: XmlFileContentResource - directives: - description: Set XML file contents +properties: + configurationVersion: 0.1 + resources: + - resource: XmlFileContentResource + directives: + description: Set XML file contents diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoSettings.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoSettings.yml index a596a7d1f3..77934b2064 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoSettings.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/PSGallery_NoSettings.yml @@ -1,6 +1,6 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: XmlContentDsc/XmlFileContentResource - directives: - description: Set XML file contents +properties: + configurationVersion: 0.2 + resources: + - resource: XmlContentDsc/XmlFileContentResource + directives: + description: Set XML file contents diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceCaseInsensitive.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceCaseInsensitive.yml index 32cec1bade..fc1c034fbd 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceCaseInsensitive.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceCaseInsensitive.yml @@ -1,9 +1,9 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/e2efileresource - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - Path: ${WinGetConfigRoot}\ResourceCaseInsensitive.txt - Content: Contents! +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/e2efileresource + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + Path: ${WinGetConfigRoot}\ResourceCaseInsensitive.txt + Content: Contents! diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceNotFound.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceNotFound.yml index 96036f4d1d..c95683909e 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceNotFound.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourceNotFound.yml @@ -1,8 +1,8 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: moduleThatDoesNotExist/resourceThatDoesNotExist - directives: - repository: AppInstallerCLIE2ETestsRepo - settings: - key: Foo +properties: + configurationVersion: 0.2 + resources: + - resource: moduleThatDoesNotExist/resourceThatDoesNotExist + directives: + repository: AppInstallerCLIE2ETestsRepo + settings: + key: Foo diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourcesNotASequence.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourcesNotASequence.yml index 648f40ea4b..5217503037 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourcesNotASequence.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ResourcesNotASequence.yml @@ -1,3 +1,3 @@ -properties: - configurationVersion: 0.1 +properties: + configurationVersion: 0.1 resources: 1 \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/RunCommandOnSet.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/RunCommandOnSet.yml index 3cb86874ef..c2574c1063 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/RunCommandOnSet.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/RunCommandOnSet.yml @@ -1,15 +1,15 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - processor: dscv3 -resources: - - name: Test RunCommandOnSet - type: Microsoft.DSC.Transitional/RunCommandOnSet - properties: - executable: pwsh - arguments: - - -NoProfile - - -NoLogo - - -Command - - | - Set-Content -Path \TestFile.txt -Value 'TestContent' +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Test RunCommandOnSet + type: Microsoft.DSC.Transitional/RunCommandOnSet + properties: + executable: pwsh + arguments: + - -NoProfile + - -NoLogo + - -Command + - | + Set-Content -Path \TestFile.txt -Value 'TestContent' diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_DSCv3.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_DSCv3.yml index 76e36d4360..5e9f12556f 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_DSCv3.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_DSCv3.yml @@ -1,12 +1,12 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - processor: dscv3 -resources: - - name: Test File - type: Microsoft.WinGet.Dev/TestFile - metadata: - description: Description 1. - properties: - path: ${WinGetConfigRoot}\ShowDetails_DSCv3.txt - content: DSCv3 Contents! +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: dscv3 +resources: + - name: Test File + type: Microsoft.WinGet.Dev/TestFile + metadata: + description: Description 1. + properties: + path: ${WinGetConfigRoot}\ShowDetails_DSCv3.txt + content: DSCv3 Contents! diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo.yml index 6e0c18a471..3e81ad27c0 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo.yml @@ -1,6 +1,6 @@ -properties: - configurationVersion: 0.2 - resources: - - resource: xE2ETestResource/E2EFileResource - directives: - repository: AppInstallerCLIE2ETestsRepo +properties: + configurationVersion: 0.2 + resources: + - resource: xE2ETestResource/E2EFileResource + directives: + repository: AppInstallerCLIE2ETestsRepo diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo_0_3.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo_0_3.yml index d52dc62041..ce40761b5e 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo_0_3.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/ShowDetails_TestRepo_0_3.yml @@ -1,9 +1,9 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -resources: - - name: Name1 - type: xE2ETestResource/E2EFileResource - metadata: - repository: AppInstallerCLIE2ETestsRepo - properties: - prop1: 3 - prop2: '4' +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: Name1 + type: xE2ETestResource/E2EFileResource + metadata: + repository: AppInstallerCLIE2ETestsRepo + properties: + prop1: 3 + prop2: '4' diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/UnitNotAMap.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/UnitNotAMap.yml index 6fbc55103c..cb4ab5ab99 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/UnitNotAMap.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/UnitNotAMap.yml @@ -1,4 +1,4 @@ -properties: - configurationVersion: 0.1 - resources: +properties: + configurationVersion: 0.1 + resources: - string \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/UnknownVersion.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/UnknownVersion.yml index 05016bc732..5173f72588 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/UnknownVersion.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/UnknownVersion.yml @@ -1,2 +1,2 @@ -properties: +properties: configurationVersion: 99999999 \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/Unknown_Processor.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/Unknown_Processor.yml index 6f22adfd11..224525e5b6 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/Unknown_Processor.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/Unknown_Processor.yml @@ -1,12 +1,12 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - processor: unknown -resources: - - name: Name1 - type: xE2ETestResource/E2EFileResource - metadata: - repository: AppInstallerCLIE2ETestsRepo - properties: - prop1: 3 - prop2: '4' +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + processor: unknown +resources: + - name: Name1 + type: xE2ETestResource/E2EFileResource + metadata: + repository: AppInstallerCLIE2ETestsRepo + properties: + prop1: 3 + prop2: '4' diff --git a/src/AppInstallerCLIE2ETests/TestData/Configuration/WithParameters_0_3.yml b/src/AppInstallerCLIE2ETests/TestData/Configuration/WithParameters_0_3.yml index 8be30425a3..e4dae10c22 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Configuration/WithParameters_0_3.yml +++ b/src/AppInstallerCLIE2ETests/TestData/Configuration/WithParameters_0_3.yml @@ -1,13 +1,13 @@ -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -parameters: - param1: - type: string - defaultValue: value -resources: - - name: Name1 - type: xE2ETestResource/E2EFileResource - metadata: - repository: AppInstallerCLIE2ETestsRepo - properties: - prop1: 3 - prop2: '4' +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +parameters: + param1: + type: string + defaultValue: value +resources: + - name: Name1 + type: xE2ETestResource/E2EFileResource + metadata: + repository: AppInstallerCLIE2ETestsRepo + properties: + prop1: 3 + prop2: '4' diff --git a/src/AppInstallerCLIE2ETests/TestData/ImportFiles/ImportFile-Bad-UnknownPackage.json b/src/AppInstallerCLIE2ETests/TestData/ImportFiles/ImportFile-Bad-UnknownPackage.json index 0acbe429b0..5b1f1e4915 100644 --- a/src/AppInstallerCLIE2ETests/TestData/ImportFiles/ImportFile-Bad-UnknownPackage.json +++ b/src/AppInstallerCLIE2ETests/TestData/ImportFiles/ImportFile-Bad-UnknownPackage.json @@ -18,4 +18,4 @@ } ], "WinGetVersion": "1.0.0" -} +} diff --git a/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml b/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml index a1bf8c5b4b..f6b2379294 100644 --- a/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml +++ b/src/AppInstallerCLIE2ETests/TestData/IndexPackageManifest.xml @@ -1,41 +1,41 @@ - - - - - - - Microsoft WinGet Source - Microsoft Corporation - Assets\AppPackageStoreLogo.png - - - - - - - - - - - - - - - - - - - - - - + + + + + + + Microsoft WinGet Source + Microsoft Corporation + Assets\AppPackageStoreLogo.png + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_1.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_1.0.yaml index 644d83fcc8..30f5512c52 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_1.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_1.0.yaml @@ -1,19 +1,19 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestArpVersionOppositeOrder -PackageVersion: '1.0' -PackageName: TestArpVersionOppositeOrder -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for arp version test. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - AppsAndFeaturesEntries: - - DisplayVersion: "10.0" - - DisplayVersion: "10.5" -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestArpVersionOppositeOrder +PackageVersion: '1.0' +PackageName: TestArpVersionOppositeOrder +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for arp version test. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + AppsAndFeaturesEntries: + - DisplayVersion: "10.0" + - DisplayVersion: "10.5" +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_2.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_2.0.yaml index 36829ca23e..4b3e4be125 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_2.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_OppositeOrder_2.0.yaml @@ -1,19 +1,19 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestArpVersionOppositeOrder -PackageVersion: '2.0' -PackageName: TestArpVersionOppositeOrder -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for arp version test. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - AppsAndFeaturesEntries: - - DisplayVersion: "9.0" - - DisplayVersion: "9.5" -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestArpVersionOppositeOrder +PackageVersion: '2.0' +PackageName: TestArpVersionOppositeOrder +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for arp version test. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + AppsAndFeaturesEntries: + - DisplayVersion: "9.0" + - DisplayVersion: "9.5" +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameAsPackageVersion.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameAsPackageVersion.yaml index c8894aa4d4..ec8ed4e923 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameAsPackageVersion.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameAsPackageVersion.yaml @@ -1,18 +1,18 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestArpVersionSameVersion -PackageVersion: '1.0' -PackageName: TestArpVersionSameVersion -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for arp version test. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - AppsAndFeaturesEntries: - - DisplayVersion: "1.0" -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestArpVersionSameVersion +PackageVersion: '1.0' +PackageName: TestArpVersionSameVersion +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for arp version test. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + AppsAndFeaturesEntries: + - DisplayVersion: "1.0" +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_1.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_1.0.yaml index f85e171675..f93d4d56d3 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_1.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_1.0.yaml @@ -1,19 +1,19 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestArpVersionSameOrder -PackageVersion: '1.0' -PackageName: TestArpVersionSameOrder -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for arp version test. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - AppsAndFeaturesEntries: - - DisplayVersion: "10.0" - - DisplayVersion: "10.5" -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestArpVersionSameOrder +PackageVersion: '1.0' +PackageName: TestArpVersionSameOrder +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for arp version test. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + AppsAndFeaturesEntries: + - DisplayVersion: "10.0" + - DisplayVersion: "10.5" +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_2.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_2.0.yaml index 62d14f6f54..b0513eb1de 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_2.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestArpVersionMapping_SameOrder_2.0.yaml @@ -1,19 +1,19 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestArpVersionSameOrder -PackageVersion: '2.0' -PackageName: TestArpVersionSameOrder -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for arp version test. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - AppsAndFeaturesEntries: - - DisplayVersion: "11.0" - - DisplayVersion: "11.5" -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestArpVersionSameOrder +PackageVersion: '2.0' +PackageName: TestArpVersionSameOrder +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for arp version test. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + AppsAndFeaturesEntries: + - DisplayVersion: "11.0" + - DisplayVersion: "11.5" +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml index 7fa9e6d6a0..dd8cead584 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.MissingRepairBehavior.yaml @@ -1,19 +1,19 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.TestMissingRepairBehavior -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestMissingRepairBehavior -Publisher: AppInstallerTest -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: burn - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName TestMissingRepairBehavior /UseHKLM - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.TestMissingRepairBehavior +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestMissingRepairBehavior +Publisher: AppInstallerTest +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: burn + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName TestMissingRepairBehavior /UseHKLM + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml index 72a03ca866..7b9887d032 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepair.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.TestModifyRepair -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestModifyRepair -Publisher: AppInstallerTest -RepairBehavior: modify -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: burn - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName TestModifyRepair /UseHKLM - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.TestModifyRepair +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestModifyRepair +Publisher: AppInstallerTest +RepairBehavior: modify +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: burn + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName TestModifyRepair /UseHKLM + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml index 7a5e562bcf..dbb283b6ea 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.ModifyRepairWithNoModify.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.TestModifyRepairWithNoModify -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestModifyRepairWithNoModify -Publisher: AppInstallerTest -RepairBehavior: modify -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: burn - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName TestModifyRepairWithNoModify /UseHKLM /NoModify - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.TestModifyRepairWithNoModify +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestModifyRepairWithNoModify +Publisher: AppInstallerTest +RepairBehavior: modify +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: burn + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName TestModifyRepairWithNoModify /UseHKLM /NoModify + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.UserScopeInstallRepairInAdminContext.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.UserScopeInstallRepairInAdminContext.yaml index 13f568f377..3b407993e3 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.UserScopeInstallRepairInAdminContext.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.UserScopeInstallRepairInAdminContext.yaml @@ -1,21 +1,21 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.TestUserScopeInstallRepairInAdminContext -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestUserScopeInstallRepairInAdminContext -Publisher: AppInstallerTest -RepairBehavior: modify -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: burn - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - ElevationRequirement: elevationRequired - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName TestUserScopeInstallRepairInAdminContext - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.TestUserScopeInstallRepairInAdminContext +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestUserScopeInstallRepairInAdminContext +Publisher: AppInstallerTest +RepairBehavior: modify +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: burn + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + ElevationRequirement: elevationRequired + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName TestUserScopeInstallRepairInAdminContext + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.yaml index 42a54b7378..2906a6bc14 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestBurnInstaller.yaml @@ -1,14 +1,14 @@ -Id: AppInstallerTest.TestBurnInstaller -Name: TestBurnInstaller -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - - Sha256: - InstallerType: burn - Switches: - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestBurnInstaller +Name: TestBurnInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + + Sha256: + InstallerType: burn + Switches: + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExampleInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExampleInstaller.yaml index f24d855d92..1671dc7aeb 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExampleInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExampleInstaller.yaml @@ -1,19 +1,19 @@ -Id: AppInstallerTest.TestExampleInstaller -Name: TestExampleInstaller -Version: 1.2.3.4 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExampleInstaller +Name: TestExampleInstaller +Version: 1.2.3.4 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml index 0e5dd4c9e0..0371ebc6ae 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.0.1.0.yaml @@ -1,35 +1,35 @@ -Id: AppInstallerTest.TestExeInstaller -Name: TestExeInstaller -Version: 1.0.1.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Scope: user - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom /Version 1.0.1.0 - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir - - Arch: x86 - Scope: machine - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom /Version 1.0.1.0 /UseHKLM - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExeInstaller +Name: TestExeInstaller +Version: 1.0.1.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Scope: user + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.0.1.0 + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.0.1.0 /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml index 2e52222f20..60e35c9389 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.1.1.0.0.yaml @@ -1,35 +1,35 @@ -Id: AppInstallerTest.TestExeInstaller -Name: TestExeInstaller -Version: 1.1.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Scope: user - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom /Version 1.1.0.0 - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir - - Arch: x86 - Scope: machine - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom /Version 1.1.0.0 /UseHKLM - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExeInstaller +Name: TestExeInstaller +Version: 1.1.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Scope: user + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.1.0.0 + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 1.1.0.0 /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml index 7e44697901..a22dc1550f 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.2.0.0.0.yaml @@ -1,35 +1,35 @@ -Id: AppInstallerTest.TestExeInstaller -Name: TestExeInstaller -Version: 2.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Scope: user - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom /Version 2.0.0.0 - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir - - Arch: x86 - Scope: machine - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom /Version 2.0.0.0 /UseHKLM - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExeInstaller +Name: TestExeInstaller +Version: 2.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Scope: user + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 2.0.0.0 + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /Version 2.0.0.0 /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml index 33050bf341..87c5463dcb 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepair.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.UninstallerRepair -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: UninstallerRepair -Publisher: AppInstallerTest -RepairBehavior: uninstaller -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName UninstallerRepair /UseHKLM - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.UninstallerRepair +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: UninstallerRepair +Publisher: AppInstallerTest +RepairBehavior: uninstaller +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName UninstallerRepair /UseHKLM + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml index bf15d21bfb..d4eaf4cbc3 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.UninstallerRepairWithNoRepair.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.UninstallerRepairWithNoRepair -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: UninstallerRepairWithNoRepair -Publisher: AppInstallerTest -RepairBehavior: uninstaller -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName UninstallerRepairWithNoRepair /UseHKLM /NoRepair - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.UninstallerRepairWithNoRepair +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: UninstallerRepairWithNoRepair +Publisher: AppInstallerTest +RepairBehavior: uninstaller +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName UninstallerRepairWithNoRepair /UseHKLM /NoRepair + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.WindowsFeature.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.WindowsFeature.yaml index 5c0d44ec52..8ff0a055a5 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.WindowsFeature.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.WindowsFeature.yaml @@ -1,27 +1,27 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.WindowsFeature -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: TestWindowsFeature -ShortDescription: Installs exe installer with valid Windows Features dependencies -Publisher: Microsoft Corporation -License: Test -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - InstallerSwitches: - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir - Dependencies: - WindowsFeatures: - - netfx3 - - invalidFeature -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.WindowsFeature +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: TestWindowsFeature +ShortDescription: Installs exe installer with valid Windows Features dependencies +Publisher: Microsoft Corporation +License: Test +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + InstallerSwitches: + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir + Dependencies: + WindowsFeatures: + - netfx3 + - invalidFeature +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml index 9e29ba0c4e..65b0fb7a27 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller.yaml @@ -1,35 +1,35 @@ -Id: AppInstallerTest.TestExeInstaller -Name: TestExeInstaller -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Scope: user - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir - - Arch: x86 - Scope: machine - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom /UseHKLM - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExeInstaller +Name: TestExeInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Scope: user + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir + - Arch: x86 + Scope: machine + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom /UseHKLM + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstallerForExport.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstallerForExport.yaml index cdce0b462e..903bb41406 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstallerForExport.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstallerForExport.yaml @@ -1,20 +1,20 @@ -Id: AppInstallerTest.TestPackageExport -Name: TestPackageExport -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{92e3d4e5-6e3d-4ae4-b9f0-b7e0a5f25b91}' - Switches: - Custom: '/ProductID {92e3d4e5-6e3d-4ae4-b9f0-b7e0a5f25b91} /DisplayName TestPackageExport /GenerateDscResourceFiles' - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestPackageExport +Name: TestPackageExport +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{92e3d4e5-6e3d-4ae4-b9f0-b7e0a5f25b91}' + Switches: + Custom: '/ProductID {92e3d4e5-6e3d-4ae4-b9f0-b7e0a5f25b91} /DisplayName TestPackageExport /GenerateDscResourceFiles' + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_CatalogPackageMetadata.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_CatalogPackageMetadata.yaml index d7d0fcd61d..354634470b 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_CatalogPackageMetadata.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_CatalogPackageMetadata.yaml @@ -1,55 +1,55 @@ -# Manifest for verifying the output of the CatalogPackageMetadata COM object. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.5.0.schema.json - -PackageIdentifier: AppInstallerTest.CatalogPackageMetadata -PackageVersion: 1.0.0.0 -PackageName: TestCatalogPackageMetadata -PackageLocale: en-US -Publisher: AppInstallerTest -License: testLicense -ShortDescription: testShortDescription -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - InstallerSwitches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -Agreements: - - AgreementLabel: testAgreementLabel - Agreement: testAgreementText - AgreementUrl: https://testAgreementUrl.net -PublisherUrl: https://testPublisherUrl.com -PublisherSupportUrl: https://testPublisherSupportUrl.com -PrivacyUrl: https://testPrivacyUrl.com -Author: testAuthor -PackageUrl: https://testPackageUrl.com -LicenseUrl: https://testLicenseUrl.com -Copyright: testCopyright -CopyrightUrl: https://testCopyrightUrl.com -Description: testDescription -Moniker: testMoniker -PurchaseUrl: https://testPurchaseUrl.com -Tags: - - "tag1" - - "tag2" -ReleaseNotes: testReleaseNotes -ReleaseNotesUrl: https://testReleaseNotes.net -InstallationNotes: testInstallationNotes -Documentations: - - DocumentLabel: testDocumentLabel - DocumentUrl: https://testDocumentUrl.com -Icons: - - IconUrl: https://testIcon - IconFileType: ico - IconResolution: custom - IconTheme: default - IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 -ManifestType: singleton -ManifestVersion: 1.5.0 +# Manifest for verifying the output of the CatalogPackageMetadata COM object. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.5.0.schema.json + +PackageIdentifier: AppInstallerTest.CatalogPackageMetadata +PackageVersion: 1.0.0.0 +PackageName: TestCatalogPackageMetadata +PackageLocale: en-US +Publisher: AppInstallerTest +License: testLicense +ShortDescription: testShortDescription +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + InstallerSwitches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +Agreements: + - AgreementLabel: testAgreementLabel + Agreement: testAgreementText + AgreementUrl: https://testAgreementUrl.net +PublisherUrl: https://testPublisherUrl.com +PublisherSupportUrl: https://testPublisherSupportUrl.com +PrivacyUrl: https://testPrivacyUrl.com +Author: testAuthor +PackageUrl: https://testPackageUrl.com +LicenseUrl: https://testLicenseUrl.com +Copyright: testCopyright +CopyrightUrl: https://testCopyrightUrl.com +Description: testDescription +Moniker: testMoniker +PurchaseUrl: https://testPurchaseUrl.com +Tags: + - "tag1" + - "tag2" +ReleaseNotes: testReleaseNotes +ReleaseNotesUrl: https://testReleaseNotes.net +InstallationNotes: testInstallationNotes +Documentations: + - DocumentLabel: testDocumentLabel + DocumentUrl: https://testDocumentUrl.com +Icons: + - IconUrl: https://testIcon + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +ManifestType: singleton +ManifestVersion: 1.5.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InapplicableOsVersion.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InapplicableOsVersion.yaml index 23ef264613..a23765126d 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InapplicableOsVersion.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InapplicableOsVersion.yaml @@ -1,20 +1,20 @@ -Id: AppInstallerTest.InapplicableOsVersion -Name: InapplicableOsVersion -Version: 1.0.0.0 -Publisher: AppInstallerTest -MinOSVersion: 11.0.0.0 -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.InapplicableOsVersion +Name: InapplicableOsVersion +Version: 1.0.0.0 +Publisher: AppInstallerTest +MinOSVersion: 11.0.0.0 +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InstallMSIX.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InstallMSIX.yaml index c73d69200e..48513bfabc 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InstallMSIX.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_InstallMSIX.yaml @@ -1,20 +1,20 @@ -Id: AppInstallerTest.TestExeInstallerInstallsMSIX -Name: EXE Installer that Installs MSIX - extra -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - PackageFamilyName: 6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e_8wekyb3d8bbwe - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExeInstallerInstallsMSIX +Name: EXE Installer that Installs MSIX - extra +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + PackageFamilyName: 6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e_8wekyb3d8bbwe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml index 92ada3f407..ccb44391f6 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_NoScope.yaml @@ -1,20 +1,20 @@ -Id: AppInstallerTest.TestExeInstallerNoScope -Name: TestExeInstallerNoScope -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExeInstallerNoScope +Name: TestExeInstallerNoScope +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_Sha256Mismatch.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_Sha256Mismatch.yaml index 2372f2d532..5cb2c3b12c 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_Sha256Mismatch.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestExeInstaller_Sha256Mismatch.yaml @@ -1,19 +1,19 @@ -Id: AppInstallerTest.TestExeSha256Mismatch -Name: TestExeSha256Mismatch -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: 0000000000000000000000000000000000000000000000000000000000000000 - InstallerType: exe - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestExeSha256Mismatch +Name: TestExeSha256Mismatch +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: 0000000000000000000000000000000000000000000000000000000000000000 + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestGoodManifestV1_10-SchemaHeader.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestGoodManifestV1_10-SchemaHeader.yaml index 47c385853c..28eaed004e 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestGoodManifestV1_10-SchemaHeader.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestGoodManifestV1_10-SchemaHeader.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeader -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest with schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeader +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest with schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml index 92b5aab7a7..a837c6ff6d 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.InstallerRepair.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.TestInstallerRepair -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestInstallerRepair -Publisher: AppInstallerTest -RepairBehavior: installer -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: inno - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName TestInstallerRepair /UseHKLM - Repair: /repair /UseHKLM -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.TestInstallerRepair +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestInstallerRepair +Publisher: AppInstallerTest +RepairBehavior: installer +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: inno + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName TestInstallerRepair /UseHKLM + Repair: /repair /UseHKLM +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.yaml index b2d07ccd75..6b171cd01b 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInnoInstaller.yaml @@ -1,13 +1,13 @@ -Id: AppInstallerTest.TestInnoInstaller -Name: TestInnoInstaller -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: inno - Switches: - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestInnoInstaller +Name: TestInnoInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: inno + Switches: + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInstalledStatus.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInstalledStatus.yaml index 106a533aeb..c559bd1389 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInstalledStatus.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInstalledStatus.yaml @@ -1,26 +1,26 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestCheckInstalledStatus -PackageVersion: '1.0' -PackageName: TestCheckInstalledStatus -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for check installed status test. -Installers: - - Architecture: neutral - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - ProductCode: CheckInstalledStatusProductId - InstallationMetadata: - DefaultInstallLocation: "%TEMP%\\TestInstalledStatus" - Files: - - RelativeFilePath: "data.txt" - # Hash value is for a txt file with "Test" as content in utf8 - FileSha256: 532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25 - FileType: other - - RelativeFilePath: "TestExeInstalled.txt" - FileType: other -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestCheckInstalledStatus +PackageVersion: '1.0' +PackageName: TestCheckInstalledStatus +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for check installed status test. +Installers: + - Architecture: neutral + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + ProductCode: CheckInstalledStatusProductId + InstallationMetadata: + DefaultInstallLocation: "%TEMP%\\TestInstalledStatus" + Files: + - RelativeFilePath: "data.txt" + # Hash value is for a txt file with "Test" as content in utf8 + FileSha256: 532eaabd9574880dbf76b9b8cc00832c20a6ec113d682299550d7a6e0f345e25 + FileType: other + - RelativeFilePath: "TestExeInstalled.txt" + FileType: other +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidManifest.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidManifest.yaml index 8b02e9356e..46c32b50bd 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidManifest.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestInvalidManifest.yaml @@ -1,19 +1,19 @@ -Id: TestInvalidManifest TestInvalidManifest -Name: TestInvalidManifest -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: TestInvalidManifest TestInvalidManifest +Name: TestInvalidManifest +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX64.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX64.yaml index 8371ee1a48..264545659c 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX64.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX64.yaml @@ -1,21 +1,21 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestMappingWithArchitectureX64 -PackageVersion: '1.0' -PackageName: TestMappingWithArchitecture -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for mapping with architecture. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - AppsAndFeaturesEntries: - - DisplayName: "TestMappingWithArchitecture(X64)" - InstallerSwitches: - Custom: '/DisplayName TestMappingWithArchitecture(X64) /ProductID {0e426f01-b682-4e67-a357-52f9ecb4590d}' - InstallLocation: /InstallDir -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestMappingWithArchitectureX64 +PackageVersion: '1.0' +PackageName: TestMappingWithArchitecture +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for mapping with architecture. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + AppsAndFeaturesEntries: + - DisplayName: "TestMappingWithArchitecture(X64)" + InstallerSwitches: + Custom: '/DisplayName TestMappingWithArchitecture(X64) /ProductID {0e426f01-b682-4e67-a357-52f9ecb4590d}' + InstallLocation: /InstallDir +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX86.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX86.yaml index ff7e336d6f..2577d5e00a 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX86.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMappingWithArchitectureX86.yaml @@ -1,21 +1,21 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestMappingWithArchitectureX86 -PackageVersion: 1.0 -PackageName: TestMappingWithArchitecture -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for mapping with architecture. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - AppsAndFeaturesEntries: - - DisplayName: "TestMappingWithArchitecture(X86)" - InstallerSwitches: - Custom: '/DisplayName TestMappingWithArchitecture(X86) /ProductID {0e426f01-b682-4e67-a357-52f9ecb4590d}' - InstallLocation: /InstallDir -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestMappingWithArchitectureX86 +PackageVersion: 1.0 +PackageName: TestMappingWithArchitecture +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for mapping with architecture. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + AppsAndFeaturesEntries: + - DisplayName: "TestMappingWithArchitecture(X86)" + InstallerSwitches: + Custom: '/DisplayName TestMappingWithArchitecture(X86) /ProductID {0e426f01-b682-4e67-a357-52f9ecb4590d}' + InstallLocation: /InstallDir +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.Repair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.Repair.yaml index 1db84ba3e8..ad44f7609f 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.Repair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.Repair.yaml @@ -1,16 +1,16 @@ -# Uses the MSI installer; -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.TestMsiRepair -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestMsiInstallerV2 -Publisher: AppInstallerTest -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstallerV2.msi - InstallerType: msi - InstallerSha256: - ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' -ManifestType: singleton -ManifestVersion: 1.7.0 +# Uses the MSI installer; +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.TestMsiRepair +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestMsiInstallerV2 +Publisher: AppInstallerTest +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstallerV2.msi + InstallerType: msi + InstallerSha256: + ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.yaml index 6133191be3..d9b3f07f12 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller.yaml @@ -1,12 +1,12 @@ -Id: AppInstallerTest.TestMsiInstaller -Name: TestMsiInstaller -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi - Sha256: - InstallerType: msi - ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestMsiInstaller +Name: TestMsiInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi + Sha256: + InstallerType: msi + ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller_UpgradeCode.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller_UpgradeCode.yaml index 84f62c4c51..ef37368906 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller_UpgradeCode.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsiInstaller_UpgradeCode.yaml @@ -1,19 +1,19 @@ -# Uses the MSI installer; doesn't list the ProductCode, only the UpgradeCode -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerTest.TestMsiInstallerUpgradeCode -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: TestMsiInstallerUpgradeCode -Publisher: AppInstallerTest -License: Test -ShortDescription: Test Installer -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi - InstallerType: msi - InstallerSha256: - AppsAndFeaturesEntries: - - UpgradeCode: '{B9CF9DD5-D46F-4CE0-BFC9-633BF9D3A6F4}' -ManifestType: singleton -ManifestVersion: 1.1.0 +# Uses the MSI installer; doesn't list the ProductCode, only the UpgradeCode +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerTest.TestMsiInstallerUpgradeCode +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: TestMsiInstallerUpgradeCode +Publisher: AppInstallerTest +License: Test +ShortDescription: Test Installer +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi + InstallerType: msi + InstallerSha256: + AppsAndFeaturesEntries: + - UpgradeCode: '{B9CF9DD5-D46F-4CE0-BFC9-633BF9D3A6F4}' +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller.yaml index 932a2a5de9..e3ac951a46 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller.yaml @@ -1,12 +1,12 @@ -Id: AppInstallerTest.TestMsixInstaller -Name: TestMsixInstaller -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix - Sha256: - InstallerType: msix - PackageFamilyName: 6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e_8wekyb3d8bbwe -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestMsixInstaller +Name: TestMsixInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix + Sha256: + InstallerType: msix + PackageFamilyName: 6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e_8wekyb3d8bbwe +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_SignatureHashMismatch.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_SignatureHashMismatch.yaml index ebf77c9433..58a9f68c79 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_SignatureHashMismatch.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_SignatureHashMismatch.yaml @@ -1,12 +1,12 @@ -Id: AppInstallerTest.TestMsixSignatureHashMismatch -Name: TestMsixSignatureHashMismatch -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix - Sha256: - SignatureSha256: 0000000000000000000000000000000000000000000000000000000000000000 - InstallerType: msix -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestMsixSignatureHashMismatch +Name: TestMsixSignatureHashMismatch +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix + Sha256: + SignatureSha256: 0000000000000000000000000000000000000000000000000000000000000000 + InstallerType: msix +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_WithSignatureHash.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_WithSignatureHash.yaml index 44def38461..fcbf7daef4 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_WithSignatureHash.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMsixInstaller_WithSignatureHash.yaml @@ -1,12 +1,12 @@ -Id: AppInstallerTest.TestMsixWithSignatureHash -Name: TestMsixWithSignatureHash -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix - Sha256: - SignatureSha256: - InstallerType: msix -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestMsixWithSignatureHash +Name: TestMsixWithSignatureHash +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix + Sha256: + SignatureSha256: + InstallerType: msix +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMultipleInstallers.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMultipleInstallers.yaml index 92d48f5b28..283a1c0525 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMultipleInstallers.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestMultipleInstallers.yaml @@ -1,49 +1,49 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestMultipleInstallers -PackageVersion: 1.0.0.0 -PackageName: TestMultipleInstallers -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test manifest with multiple installers -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: nullsoft - InstallerSha256: - Scope: user - InstallerSwitches: - InstallLocation: /InstallDir - - Architecture: arm64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: nullsoft - InstallerSha256: - Scope: user - InstallerSwitches: - InstallLocation: /InstallDir - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi - InstallerSha256: - InstallerType: msi - ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' - Scope: machine - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - InstallerLocale: zh-CN - ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' - InstallerSha256: - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: AppInstallerTestExeInstaller.exe - InstallerSwitches: - Custom: /execustom /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestMultipleInstallers +PackageVersion: 1.0.0.0 +PackageName: TestMultipleInstallers +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test manifest with multiple installers +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: nullsoft + InstallerSha256: + Scope: user + InstallerSwitches: + InstallLocation: /InstallDir + - Architecture: arm64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: nullsoft + InstallerSha256: + Scope: user + InstallerSwitches: + InstallLocation: /InstallDir + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi + InstallerSha256: + InstallerType: msi + ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' + Scope: machine + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + InstallerLocale: zh-CN + ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' + InstallerSha256: + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: AppInstallerTestExeInstaller.exe + InstallerSwitches: + Custom: /execustom /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml index 429aec00cb..3001c93e56 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.UninstallerRepair.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: AppInstallerTest.NullsoftUninstallerRepair -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: NullsoftUninstallerRepair -Publisher: AppInstallerTest -RepairBehavior: uninstaller -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' - InstallerSwitches: - InstallLocation: /InstallDir - Custom: /Version 2.0.0.0 /DisplayName NullsoftUninstallerRepair /UseHKLM - Repair: /repair -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: AppInstallerTest.NullsoftUninstallerRepair +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: NullsoftUninstallerRepair +Publisher: AppInstallerTest +RepairBehavior: uninstaller +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + ProductCode: '{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}' + InstallerSwitches: + InstallLocation: /InstallDir + Custom: /Version 2.0.0.0 /DisplayName NullsoftUninstallerRepair /UseHKLM + Repair: /repair +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.yaml index 6ddf92f6e2..2e233c56fb 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestNullsoftInstaller.yaml @@ -1,13 +1,13 @@ -Id: AppInstallerTest.TestNullsoftInstaller -Name: TestNullsoftInstaller -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: nullsoft - Switches: - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestNullsoftInstaller +Name: TestNullsoftInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: nullsoft + Switches: + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.2.0.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.2.0.0.0.yaml index bd96f00695..ea2fe3562a 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.2.0.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.2.0.0.0.yaml @@ -1,16 +1,16 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestPortableExe -PackageVersion: 2.0.0.0 -PackageName: TestPortableExe -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for portable install. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: portable - InstallerSha256: -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestPortableExe +PackageVersion: 2.0.0.0 +PackageName: TestPortableExe +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for portable install. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: portable + InstallerSha256: +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.3.0.0.0_UninstallPrevious.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.3.0.0.0_UninstallPrevious.yaml index 7d52b6e871..11948c8e63 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.3.0.0.0_UninstallPrevious.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.3.0.0.0_UninstallPrevious.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestPortableExe -PackageVersion: 3.0.0.0 -PackageName: TestPortableExe -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for portable install. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: portable - InstallerSha256: - UpgradeBehavior: uninstallPrevious -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestPortableExe +PackageVersion: 3.0.0.0 +PackageName: TestPortableExe +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for portable install. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: portable + InstallerSha256: + UpgradeBehavior: uninstallPrevious +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.yaml index 805bfe755d..b45f6f2c09 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller.yaml @@ -1,16 +1,16 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestPortableExe -PackageVersion: 1.0.0.0 -PackageName: TestPortableExe -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for portable install. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: portable - InstallerSha256: -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestPortableExe +PackageVersion: 1.0.0.0 +PackageName: TestPortableExe +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for portable install. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: portable + InstallerSha256: +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstallerUninstallPrevious.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstallerUninstallPrevious.yaml index 54955d33ba..9258cebf83 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstallerUninstallPrevious.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstallerUninstallPrevious.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestPortableExe -PackageVersion: 1.1.0.0 -PackageName: TestPortableExe -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for portable install. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: portable - InstallerSha256: - UpgradeBehavior: uninstallPrevious -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestPortableExe +PackageVersion: 1.1.0.0 +PackageName: TestPortableExe +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for portable install. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: portable + InstallerSha256: + UpgradeBehavior: uninstallPrevious +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller_WithCommand.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller_WithCommand.yaml index 2ea4d2cebb..2ddb8c5184 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller_WithCommand.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestPortableInstaller_WithCommand.yaml @@ -1,18 +1,18 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerTest.TestPortableExeWithCommand -PackageVersion: 1.0.0.0 -PackageName: TestPortableExeWithCommand -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for portable install with command value. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: portable - InstallerSha256: - Commands: - - testCommand -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerTest.TestPortableExeWithCommand +PackageVersion: 1.0.0.0 +PackageName: TestPortableExeWithCommand +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for portable install with command value. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: portable + InstallerSha256: + Commands: + - testCommand +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.1.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.1.0.yaml index cd820d3b2e..c33f361ec9 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.1.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.1.0.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependency -PackageVersion: '1.0' -PackageName: TestUpgradeAddsDependency -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for adding a new package dependency during an upgrade. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - ProductCode: '{fda6c0e2-0977-482f-bda1-9bd025457b81}' - InstallerSwitches: - Custom: '/ProductID {fda6c0e2-0977-482f-bda1-9bd025457b81}' - InstallLocation: /InstallDir -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependency +PackageVersion: '1.0' +PackageName: TestUpgradeAddsDependency +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for adding a new package dependency during an upgrade. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + ProductCode: '{fda6c0e2-0977-482f-bda1-9bd025457b81}' + InstallerSwitches: + Custom: '/ProductID {fda6c0e2-0977-482f-bda1-9bd025457b81}' + InstallLocation: /InstallDir +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.2.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.2.0.yaml index 05f1e2ef16..45b3b239bf 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.2.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependency.2.0.yaml @@ -1,23 +1,23 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependency -PackageVersion: '2.0' -PackageName: TestUpgradeAddsDependency -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for adding a new package dependency during an upgrade. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - ProductCode: '{fda6c0e2-0977-482f-bda1-9bd025457b81}' - InstallerSwitches: - Custom: '/ProductID {fda6c0e2-0977-482f-bda1-9bd025457b81}' - InstallLocation: /InstallDir - Dependencies: - PackageDependencies: - - PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependencyDependent -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependency +PackageVersion: '2.0' +PackageName: TestUpgradeAddsDependency +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for adding a new package dependency during an upgrade. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + ProductCode: '{fda6c0e2-0977-482f-bda1-9bd025457b81}' + InstallerSwitches: + Custom: '/ProductID {fda6c0e2-0977-482f-bda1-9bd025457b81}' + InstallLocation: /InstallDir + Dependencies: + PackageDependencies: + - PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependencyDependent +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependencyDependent.1.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependencyDependent.1.0.yaml index 70f92f6f99..e1ced7abfc 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependencyDependent.1.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAddsDependencyDependent.1.0.yaml @@ -1,21 +1,21 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependencyDependent -PackageVersion: '1.0' -PackageName: TestUpgradeAddsDependencyDependent -PackageLocale: en-US -Publisher: Microsoft -License: Test -ShortDescription: E2E test for adding a new package dependency during an upgrade. -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerType: exe - InstallerSha256: - UpgradeBehavior: uninstallPrevious - ProductCode: '{648274a3-17aa-4731-8bf1-5854ca61e475}' - InstallerSwitches: - Custom: '/ProductID {648274a3-17aa-4731-8bf1-5854ca61e475}' - InstallLocation: /InstallDir -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestUpgradeAddsDependencyDependent +PackageVersion: '1.0' +PackageName: TestUpgradeAddsDependencyDependent +PackageLocale: en-US +Publisher: Microsoft +License: Test +ShortDescription: E2E test for adding a new package dependency during an upgrade. +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerType: exe + InstallerSha256: + UpgradeBehavior: uninstallPrevious + ProductCode: '{648274a3-17aa-4731-8bf1-5854ca61e475}' + InstallerSwitches: + Custom: '/ProductID {648274a3-17aa-4731-8bf1-5854ca61e475}' + InstallLocation: /InstallDir +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.1.0.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.1.0.0.0.yaml index 881e4dab5e..31774a7063 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.1.0.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.1.0.0.0.yaml @@ -1,20 +1,20 @@ -Id: AppInstallerTest.TestUpgradeApplicability -Name: TestUpgradeApplicability -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{bfb0f666-99d5-433d-8a2e-32f31d4f8e48}' - Switches: - Custom: '/ProductID {bfb0f666-99d5-433d-8a2e-32f31d4f8e48} /DisplayName TestUpgradeApplicability' - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestUpgradeApplicability +Name: TestUpgradeApplicability +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{bfb0f666-99d5-433d-8a2e-32f31d4f8e48}' + Switches: + Custom: '/ProductID {bfb0f666-99d5-433d-8a2e-32f31d4f8e48} /DisplayName TestUpgradeApplicability' + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.2.0.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.2.0.0.0.yaml index 0a58e3ae1e..5508c68c58 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.2.0.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestUpgradeAvailableApi.2.0.0.0.yaml @@ -1,20 +1,20 @@ -Id: AppInstallerTest.TestUpgradeApplicability -Name: TestUpgradeApplicability -Version: 2.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: arm64 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: - InstallerType: exe - ProductCode: '{bfb0f666-99d5-433d-8a2e-32f31d4f8e48}' - Switches: - Custom: '/ProductID {bfb0f666-99d5-433d-8a2e-32f31d4f8e48} /DisplayName TestUpgradeApplicability' - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestUpgradeApplicability +Name: TestUpgradeApplicability +Version: 2.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: arm64 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: + InstallerType: exe + ProductCode: '{bfb0f666-99d5-433d-8a2e-32f31d4f8e48}' + Switches: + Custom: '/ProductID {bfb0f666-99d5-433d-8a2e-32f31d4f8e48} /DisplayName TestUpgradeApplicability' + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestValidManifest.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestValidManifest.yaml index 608679660e..6d679f9dd1 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestValidManifest.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestValidManifest.yaml @@ -1,19 +1,19 @@ -Id: AppInstallerTest.TestValidManifest -Name: TestValidManifest -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: 0000000000000000000000000000000000000000000000000000000000000000 - InstallerType: exe - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TestValidManifest +Name: TestValidManifest +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: 0000000000000000000000000000000000000000000000000000000000000000 + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifest.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifest.yaml index 4ec5724950..4fa54ebc2d 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifest.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifest.yaml @@ -1,16 +1,16 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json - -PackageIdentifier: TestWarning.Manifest -PackageName: TestWarningManifest -PackageVersion: 1.0.0.0 -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - InstallerSha256: 0000000000000000000000000000000000000000000000000000000000000000 - InstallerType: exe -ShortDescription: This manifest should have only warnings, no errors -ManifestType: singleton -ManifestVersion: 1.6.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json + +PackageIdentifier: TestWarning.Manifest +PackageName: TestWarningManifest +PackageVersion: 1.0.0.0 +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + InstallerSha256: 0000000000000000000000000000000000000000000000000000000000000000 + InstallerType: exe +ShortDescription: This manifest should have only warnings, no errors +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderInvalid.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderInvalid.yaml index 17a1e1ec4e..e0c68e7442 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderInvalid.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderInvalid.yaml @@ -1,17 +1,17 @@ -# yaml-language-server= $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeaderInvalid -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header Invalid -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has an invalid schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server= $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeaderInvalid +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header Invalid +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has an invalid schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml index 4f2a1a149c..245203f554 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeaderManifestTypeMismatch -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header ManifestType Mismatch -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a mismatched ManisfestType in the schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeaderManifestTypeMismatch +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header ManifestType Mismatch +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a mismatched ManisfestType in the schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderNotFound.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderNotFound.yaml index 2e42d9a7b7..6a9e7f4a84 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderNotFound.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderNotFound.yaml @@ -1,15 +1,15 @@ -PackageIdentifier: AppInstallerCliTest.SchemaHeaderNotFound -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header Not Found -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a missing schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +PackageIdentifier: AppInstallerCliTest.SchemaHeaderNotFound +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header Not Found +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a missing schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml index 2c7b723156..f9a6016323 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest-invalid.singleton.1.10.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeaderURLPatternMismatch -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header URL Pattern Mismatch -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a mismatched schema header URL pattern - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest-invalid.singleton.1.10.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeaderURLPatternMismatch +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header URL Pattern Mismatch +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a mismatched schema header URL pattern + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml index 1950651794..fb2b5c30c3 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.9.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeaderVersionMismatch -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header ManifestVersion Mismatch -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a mismatched ManisfestVersion in the schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.9.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeaderVersionMismatch +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header ManifestVersion Mismatch +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a mismatched ManisfestVersion in the schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml index a06953a282..f153793dbc 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe.yaml @@ -1,28 +1,28 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestZipInstallerWithExe -PackageVersion: 1.0.0.0 -PackageName: TestZipInstallerWithExe -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for installing a zip with exe. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' - InstallerSha256: - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: AppInstallerTestExeInstaller.exe - InstallerSwitches: - Custom: /execustom /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /LogFile - InstallLocation: /InstallDir -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestZipInstallerWithExe +PackageVersion: 1.0.0.0 +PackageName: TestZipInstallerWithExe +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for installing a zip with exe. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' + InstallerSha256: + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: AppInstallerTestExeInstaller.exe + InstallerSwitches: + Custom: /execustom /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /LogFile + InstallLocation: /InstallDir +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml index 085c03e899..c25f11e35a 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Exe_InvalidRelativeFilePath.yaml @@ -1,22 +1,22 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestZipInvalidRelativePath -PackageVersion: 1.0.0.0 -PackageName: TestZipInvalidRelativePath -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for installing a zip with a bad relative file path. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' - InstallerSha256: - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: ../../AppInstallerTestExeInstaller.exe - InstallerSwitches: - Custom: /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestZipInvalidRelativePath +PackageVersion: 1.0.0.0 +PackageName: TestZipInvalidRelativePath +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for installing a zip with a bad relative file path. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + ProductCode: '{E1880465-8CC2-4033-90AE-DE4E7FDBA26E}' + InstallerSha256: + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: ../../AppInstallerTestExeInstaller.exe + InstallerSwitches: + Custom: /productID {E1880465-8CC2-4033-90AE-DE4E7FDBA26E} +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msi.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msi.yaml index f52ab790e5..7002056f53 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msi.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msi.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestZipInstallerWithMsi -PackageVersion: 1.0.0.0 -PackageName: TestZipInstallerWithMsi -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for installing a zip with msi. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' - InstallerSha256: - NestedInstallerType: msi - NestedInstallerFiles: - - RelativeFilePath: AppInstallerTestMsiInstaller.msi -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestZipInstallerWithMsi +PackageVersion: 1.0.0.0 +PackageName: TestZipInstallerWithMsi +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for installing a zip with msi. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' + InstallerSha256: + NestedInstallerType: msi + NestedInstallerFiles: + - RelativeFilePath: AppInstallerTestMsiInstaller.msi +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msix.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msix.yaml index 862a164dad..7553368011 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msix.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Msix.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestZipInstallerWithMsix -PackageVersion: 1.0.0.0 -PackageName: TestZipInstallerWithMsix -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for installing a zip with msix. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' - InstallerSha256: - NestedInstallerType: msix - NestedInstallerFiles: - - RelativeFilePath: AppInstallerTestMsixInstaller.msix -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestZipInstallerWithMsix +PackageVersion: 1.0.0.0 +PackageName: TestZipInstallerWithMsix +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for installing a zip with msix. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' + InstallerSha256: + NestedInstallerType: msix + NestedInstallerFiles: + - RelativeFilePath: AppInstallerTestMsixInstaller.msix +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_PackageInstallerInfo.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_PackageInstallerInfo.yaml index 6d5e704146..da7ddc96eb 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_PackageInstallerInfo.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_PackageInstallerInfo.yaml @@ -1,23 +1,23 @@ -# Test manifest for verifying the behavior of retrieving a PackageInstallerInfo COM object. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.PackageInstallerInfo -PackageVersion: 1.0.0.0 -PackageName: TestPackageInstallerInfo -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for PackageInstallerInfo COM object. -Installers: - - Architecture: x64 - Scope: user - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - InstallerSha256: - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: AppInstallerTestExeInstaller.exe - ElevationRequirement: elevationRequired - InstallerLocale: en-US -ManifestType: singleton -ManifestVersion: 1.4.0 +# Test manifest for verifying the behavior of retrieving a PackageInstallerInfo COM object. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.PackageInstallerInfo +PackageVersion: 1.0.0.0 +PackageName: TestPackageInstallerInfo +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for PackageInstallerInfo COM object. +Installers: + - Architecture: x64 + Scope: user + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + InstallerSha256: + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: AppInstallerTestExeInstaller.exe + ElevationRequirement: elevationRequired + InstallerLocale: en-US +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.2.0.0.0.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.2.0.0.0.yaml index f8c2526cf7..99c40693ad 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.2.0.0.0.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.2.0.0.0.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestZipInstallerWithPortable -PackageVersion: 2.0.0.0 -PackageName: TestZipInstallerWithPortable -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for installing a zip with portable. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - InstallerSha256: - NestedInstallerType: portable - NestedInstallerFiles: - - RelativeFilePath: AppInstallerTestExeInstaller.exe - PortableCommandAlias: TestPortable -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestZipInstallerWithPortable +PackageVersion: 2.0.0.0 +PackageName: TestZipInstallerWithPortable +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for installing a zip with portable. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + InstallerSha256: + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: AppInstallerTestExeInstaller.exe + PortableCommandAlias: TestPortable +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.yaml index 17a0bfa647..5d314e8252 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/TestZipInstaller_Portable.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerTest.TestZipInstallerWithPortable -PackageVersion: 1.0.0.0 -PackageName: TestZipInstallerWithPortable -PackageLocale: en-US -Publisher: AppInstallerTest -License: Test -ShortDescription: E2E test for installing a zip with portable. -Installers: - - Architecture: x64 - InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip - InstallerType: zip - InstallerSha256: - NestedInstallerType: portable - NestedInstallerFiles: - - RelativeFilePath: AppInstallerTestExeInstaller.exe - PortableCommandAlias: TestPortable -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerTest.TestZipInstallerWithPortable +PackageVersion: 1.0.0.0 +PackageName: TestZipInstallerWithPortable +PackageLocale: en-US +Publisher: AppInstallerTest +License: Test +ShortDescription: E2E test for installing a zip with portable. +Installers: + - Architecture: x64 + InstallerUrl: https://localhost:5001/TestKit/AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip + InstallerType: zip + InstallerSha256: + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: AppInstallerTestExeInstaller.exe + PortableCommandAlias: TestPortable +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git "a/src/AppInstallerCLIE2ETests/TestData/Manifests/T\303\253stExeInstaller.yaml" "b/src/AppInstallerCLIE2ETests/TestData/Manifests/T\303\253stExeInstaller.yaml" index 2482e08da3..d93bd3e835 100644 --- "a/src/AppInstallerCLIE2ETests/TestData/Manifests/T\303\253stExeInstaller.yaml" +++ "b/src/AppInstallerCLIE2ETests/TestData/Manifests/T\303\253stExeInstaller.yaml" @@ -1,19 +1,19 @@ -Id: AppInstallerTest.TëstExeInstaller -Name: TestExeInstaller -Version: 1.0.0.0 -Publisher: AppInstallerTest -License: Test -Installers: - - Arch: x86 - Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe - Sha256: 0000000000000000000000000000000000000000000000000000000000000000 - InstallerType: exe - Switches: - Custom: /execustom - SilentWithProgress: /exeswp - Silent: /exesilent - Interactive: /exeinteractive - Language: /exeenus - Log: /exelog - InstallLocation: /InstallDir -ManifestVersion: 0.1.0 +Id: AppInstallerTest.TëstExeInstaller +Name: TestExeInstaller +Version: 1.0.0.0 +Publisher: AppInstallerTest +License: Test +Installers: + - Arch: x86 + Url: https://localhost:5001/TestKit/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe + Sha256: 0000000000000000000000000000000000000000000000000000000000000000 + InstallerType: exe + Switches: + Custom: /execustom + SilentWithProgress: /exeswp + Silent: /exesilent + Interactive: /exeinteractive + Language: /exeenus + Log: /exelog + InstallLocation: /InstallDir +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_CorrectHash.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_CorrectHash.yaml index bf21cbb6b7..4c4407271a 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_CorrectHash.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_CorrectHash.yaml @@ -1,16 +1,16 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json - -PackageIdentifier: ZeroByteFile.CorrectHash -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: ZeroByteFile-CorrectHash -ShortDescription: Points to a zero byte installer file using the correct hash -Publisher: Microsoft Corporation -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/TestData/empty - InstallerType: exe - InstallerSha256: E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 -ManifestType: singleton -ManifestVersion: 1.6.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json + +PackageIdentifier: ZeroByteFile.CorrectHash +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: ZeroByteFile-CorrectHash +ShortDescription: Points to a zero byte installer file using the correct hash +Publisher: Microsoft Corporation +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/TestData/empty + InstallerType: exe + InstallerSha256: E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855 +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_IncorrectHash.yaml b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_IncorrectHash.yaml index adcf5c896b..1b7c138de5 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_IncorrectHash.yaml +++ b/src/AppInstallerCLIE2ETests/TestData/Manifests/ZeroByteFile_IncorrectHash.yaml @@ -1,16 +1,16 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json - -PackageIdentifier: ZeroByteFile.IncorrectHash -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: ZeroByteFile-IncorrectHash -ShortDescription: Points to a zero byte installer file using the incorrect hash -Publisher: Microsoft Corporation -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://localhost:5001/TestKit/TestData/empty - InstallerType: exe - InstallerSha256: BAD0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852BBAD -ManifestType: singleton -ManifestVersion: 1.6.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json + +PackageIdentifier: ZeroByteFile.IncorrectHash +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: ZeroByteFile-IncorrectHash +ShortDescription: Points to a zero byte installer file using the incorrect hash +Publisher: Microsoft Corporation +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://localhost:5001/TestKit/TestData/empty + InstallerType: exe + InstallerSha256: BAD0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852BBAD +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLIE2ETests/TestData/Package/AppxManifest.xml b/src/AppInstallerCLIE2ETests/TestData/Package/AppxManifest.xml index 42852f1281..145afd060d 100644 --- a/src/AppInstallerCLIE2ETests/TestData/Package/AppxManifest.xml +++ b/src/AppInstallerCLIE2ETests/TestData/Package/AppxManifest.xml @@ -1,47 +1,47 @@ - - - - - - - WinGet E2E Source - Microsoft Corporation - Assets\AppPackageStoreLogo.png - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + WinGet E2E Source + Microsoft Corporation + Assets\AppPackageStoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AppInstallerCLIE2ETests/TestData/README.md b/src/AppInstallerCLIE2ETests/TestData/README.md index 3e8bb74e64..5a6564d1a4 100644 --- a/src/AppInstallerCLIE2ETests/TestData/README.md +++ b/src/AppInstallerCLIE2ETests/TestData/README.md @@ -1,9 +1,9 @@ -## AppInstallerTestMsiInstaller.msi -Due to MSI projects requiring a special extension, we have simply checked in a built version of the test MSI. To create a new one, which may be needed if the test EXE inside it needs to be updated: - -1. Ensure that the extension is installed locally - - a. [Microsoft Visual Studio Installer Projects 2022](https://marketplace.visualstudio.com/items?itemName=VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects) -2. Set configuration to "Release|x86" -3. Build AppInstallerTestMsiInstaller project +## AppInstallerTestMsiInstaller.msi +Due to MSI projects requiring a special extension, we have simply checked in a built version of the test MSI. To create a new one, which may be needed if the test EXE inside it needs to be updated: + +1. Ensure that the extension is installed locally + + a. [Microsoft Visual Studio Installer Projects 2022](https://marketplace.visualstudio.com/items?itemName=VisualStudioClient.MicrosoftVisualStudio2022InstallerProjects) +2. Set configuration to "Release|x86" +3. Build AppInstallerTestMsiInstaller project 4. Check in new MSI over the one in TestData \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/TestData/TestRestSource/information b/src/AppInstallerCLIE2ETests/TestData/TestRestSource/information index b51107fca9..7b1bc36fdb 100644 --- a/src/AppInstallerCLIE2ETests/TestData/TestRestSource/information +++ b/src/AppInstallerCLIE2ETests/TestData/TestRestSource/information @@ -1,6 +1,6 @@ -{ - "Data": { - "SourceIdentifier": "TestRestSource", - "ServerSupportedVersions": ["1.0.0"] - } -} +{ + "Data": { + "SourceIdentifier": "TestRestSource", + "ServerSupportedVersions": ["1.0.0"] + } +} diff --git a/src/AppInstallerCLIE2ETests/TestData/localsource.json b/src/AppInstallerCLIE2ETests/TestData/localsource.json index 129e335ae7..a7dbdf0bb0 100644 --- a/src/AppInstallerCLIE2ETests/TestData/localsource.json +++ b/src/AppInstallerCLIE2ETests/TestData/localsource.json @@ -1,56 +1,56 @@ -{ - "AppxManifest": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/Package/AppxManifest.xml", - "WorkingDirectory": "%AGENT_TEMPDIRECTORY%/TestLocalIndex", - "LocalManifests": [ - "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/Manifests" - ], - "LocalInstallers": [ - { - "Type": "exe", - "Name": "AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe", - "Input": "%BUILDOUTDIR%/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe", - "HashToken": "" - }, - { - "Type": "msi", - "Name": "AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi", - "Input": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsiInstaller.msi", - "HashToken": "" - }, - { - "Type": "msi", - "Name": "AppInstallerTestMsiInstaller/AppInstallerTestMsiInstallerV2.msi", - "Input": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsiInstallerV2.msi", - "HashToken": "" - }, - { - "Type": "msix", - "Name": "AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix", - "Input": "%BUILD_ARTIFACTSTAGINGDIRECTORY%/AppInstallerTestMsixInstaller.msix", - "HashToken": "", - "SignatureToken": "" - }, - { - "Type": "font", - "Name": "AppInstallerTestFont/AppInstallerTestFont.ttf", - "Input": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestFont.ttf", - "HashToken": "" - } - ], - "DynamicInstallers": [ - { - "Type": "zip", - "Name": "AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip", - "Input": [ - "%AGENT_TEMPDIRECTORY%/TestLocalIndex/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe", - "%AGENT_TEMPDIRECTORY%/TestLocalIndex/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi", - "%AGENT_TEMPDIRECTORY%/TestLocalIndex/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix" - ], - "HashToken": "" - } - ], - "Signature": { - "CertFile": "%TestSigningCert_PfxPath%", - "Password": "%TestSigningCert_Password%" - } -} +{ + "AppxManifest": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/Package/AppxManifest.xml", + "WorkingDirectory": "%AGENT_TEMPDIRECTORY%/TestLocalIndex", + "LocalManifests": [ + "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/Manifests" + ], + "LocalInstallers": [ + { + "Type": "exe", + "Name": "AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe", + "Input": "%BUILDOUTDIR%/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe", + "HashToken": "" + }, + { + "Type": "msi", + "Name": "AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi", + "Input": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsiInstaller.msi", + "HashToken": "" + }, + { + "Type": "msi", + "Name": "AppInstallerTestMsiInstaller/AppInstallerTestMsiInstallerV2.msi", + "Input": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestMsiInstallerV2.msi", + "HashToken": "" + }, + { + "Type": "msix", + "Name": "AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix", + "Input": "%BUILD_ARTIFACTSTAGINGDIRECTORY%/AppInstallerTestMsixInstaller.msix", + "HashToken": "", + "SignatureToken": "" + }, + { + "Type": "font", + "Name": "AppInstallerTestFont/AppInstallerTestFont.ttf", + "Input": "%BUILD_SOURCESDIRECTORY%/src/AppInstallerCLIE2ETests/TestData/AppInstallerTestFont.ttf", + "HashToken": "" + } + ], + "DynamicInstallers": [ + { + "Type": "zip", + "Name": "AppInstallerTestZipInstaller/AppInstallerTestZipInstaller.zip", + "Input": [ + "%AGENT_TEMPDIRECTORY%/TestLocalIndex/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.exe", + "%AGENT_TEMPDIRECTORY%/TestLocalIndex/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.msi", + "%AGENT_TEMPDIRECTORY%/TestLocalIndex/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.msix" + ], + "HashToken": "" + } + ], + "Signature": { + "CertFile": "%TestSigningCert_PfxPath%", + "Password": "%TestSigningCert_Password%" + } +} diff --git a/src/AppInstallerCLIE2ETests/UninstallCommand.cs b/src/AppInstallerCLIE2ETests/UninstallCommand.cs index 55d09056bd..4a8f70134d 100644 --- a/src/AppInstallerCLIE2ETests/UninstallCommand.cs +++ b/src/AppInstallerCLIE2ETests/UninstallCommand.cs @@ -1,220 +1,220 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test uninstall command. - /// - public class UninstallCommand : BaseCommand - { - // Custom product code for overriding the default in the test exe - private const string CustomProductCode = "{f08fc03c-0b7e-4fca-9b3c-3a384d18a9f3}"; - - // File written when uninstalling the test exe - private const string UninstallTestExeUninstalledFile = "TestExeUninstalled.txt"; - - // Name of a file installed by the MSI that will be removed during uninstall - private const string UninstallTestMsiInstalledFile = "AppInstallerTestExeInstaller.exe"; - - // Package name of the test MSIX package - private const string UninstallTestMsixName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; - - /// - /// Test uninstall exe. - /// - [Test] - public void UninstallTestExe() - { - // Uninstall an Exe - var installDir = TestCommon.GetRandomTestDir(); - TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --silent -l {installDir}"); - var result = TestCommon.RunAICLICommand("uninstall", Constants.ExeInstallerPackageId); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); - } - - /// - /// Test uninstall msi. - /// - [Test] - public void UninstallTestMsi() - { - if (string.IsNullOrEmpty(TestIndex.MsiInstaller)) - { - Assert.Ignore("MSI installer not available"); - } - - // Uninstall an MSI - var installDir = TestCommon.GetRandomTestDir(); - TestCommon.RunAICLICommand("install", $"{Constants.MsiInstallerPackageId} -l {installDir}"); - var result = TestCommon.RunAICLICommand("uninstall", Constants.MsiInstallerPackageId); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestMsiUninstalled(installDir)); - } - - /// - /// Test uninstall msix. - /// - [Test] - public void UninstallTestMsix() - { - // Uninstall an MSIX - TestCommon.RunAICLICommand("install", Constants.MsixInstallerPackageId); - var result = TestCommon.RunAICLICommand("uninstall", Constants.MsixInstallerPackageId); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestMsixUninstalled()); - } - - /// - /// Test uninstall msix package with machine scope. - /// - [Test] - public void UninstallTestMsixMachineScope() - { - // TODO: Provision and Deprovision api not supported in build server. - Assert.Ignore(); - - // Uninstall an MSIX - TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope machine"); - var result = TestCommon.RunAICLICommand("uninstall", $"{Constants.MsixInstallerPackageId} --scope machine"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestMsixUninstalled(true)); - } - - /// - /// Test uninstall portable package. - /// - [Test] - public void UninstallPortable() - { - // Uninstall a Portable - string installDir = Path.Combine(System.Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - TestCommon.RunAICLICommand("install", $"{packageId}"); - var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - } - - /// - /// Test uninstall portable package with product code. - /// - [Test] - public void UninstallPortableWithProductCode() - { - // Uninstall a Portable with ProductCode - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - TestCommon.RunAICLICommand("install", $"{packageId}"); - var result = TestCommon.RunAICLICommand("uninstall", $"--product-code {productCode}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - } - - /// - /// Test uninstall portable package with modified symlink. - /// - [Test] - public void UninstallPortableModifiedSymlink() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - TestCommon.RunAICLICommand("install", $"{packageId}"); - - string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); - string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); - - // Replace symlink with modified symlink - File.Delete(symlinkPath); - FileSystemInfo modifiedSymlinkInfo = File.CreateSymbolicLink(symlinkPath, "fakeTargetExe"); - - var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.ERROR_PORTABLE_UNINSTALL_FAILED, result.ExitCode); - Assert.True(result.StdOut.Contains("Unable to remove Portable package as it has been modified; to override this check use --force")); - Assert.True(modifiedSymlinkInfo.Exists, "Modified symlink should still exist"); - - // Try again with --force - var result2 = TestCommon.RunAICLICommand("uninstall", $"{packageId} --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); - Assert.True(result2.StdOut.Contains("Portable package has been modified; proceeding due to --force")); - Assert.True(result2.StdOut.Contains("Successfully uninstalled")); - - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - } - - /// - /// Test uninstall zip portable package. - /// - [Test] - public void UninstallZip_Portable() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestZipInstallerWithPortable"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = "TestPortable.exe"; - fileName = "AppInstallerTestExeInstaller.exe"; - - var testResult = TestCommon.RunAICLICommand("install", $"{packageId}"); - var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); - } - - /// - /// Test uninstall not indexed. - /// - [Test] - public void UninstallNotIndexed() - { - // Uninstalls a package found with ARP not matching any known manifest. - // Install the test EXE providing a custom Product Code so that it cannot be mapped - // back to its manifest, then uninstall it using its Product Code - var installDir = TestCommon.GetRandomTestDir(); - TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --override \"/ProductID {CustomProductCode} /InstallDir {installDir}"); - var result = TestCommon.RunAICLICommand("uninstall", CustomProductCode); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully uninstalled")); - Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); - } - - /// - /// Test uninstalled app not found. - /// - [Test] - public void UninstallAppNotInstalled() - { - // Verify failure when trying to uninstall an app that is not installed. - var result = TestCommon.RunAICLICommand("uninstall", $"TestMsixInstaller"); - Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("No installed package found matching input criteria.")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test uninstall command. + /// + public class UninstallCommand : BaseCommand + { + // Custom product code for overriding the default in the test exe + private const string CustomProductCode = "{f08fc03c-0b7e-4fca-9b3c-3a384d18a9f3}"; + + // File written when uninstalling the test exe + private const string UninstallTestExeUninstalledFile = "TestExeUninstalled.txt"; + + // Name of a file installed by the MSI that will be removed during uninstall + private const string UninstallTestMsiInstalledFile = "AppInstallerTestExeInstaller.exe"; + + // Package name of the test MSIX package + private const string UninstallTestMsixName = "6c6338fe-41b7-46ca-8ba6-b5ad5312bb0e"; + + /// + /// Test uninstall exe. + /// + [Test] + public void UninstallTestExe() + { + // Uninstall an Exe + var installDir = TestCommon.GetRandomTestDir(); + TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --silent -l {installDir}"); + var result = TestCommon.RunAICLICommand("uninstall", Constants.ExeInstallerPackageId); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + } + + /// + /// Test uninstall msi. + /// + [Test] + public void UninstallTestMsi() + { + if (string.IsNullOrEmpty(TestIndex.MsiInstaller)) + { + Assert.Ignore("MSI installer not available"); + } + + // Uninstall an MSI + var installDir = TestCommon.GetRandomTestDir(); + TestCommon.RunAICLICommand("install", $"{Constants.MsiInstallerPackageId} -l {installDir}"); + var result = TestCommon.RunAICLICommand("uninstall", Constants.MsiInstallerPackageId); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestMsiUninstalled(installDir)); + } + + /// + /// Test uninstall msix. + /// + [Test] + public void UninstallTestMsix() + { + // Uninstall an MSIX + TestCommon.RunAICLICommand("install", Constants.MsixInstallerPackageId); + var result = TestCommon.RunAICLICommand("uninstall", Constants.MsixInstallerPackageId); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestMsixUninstalled()); + } + + /// + /// Test uninstall msix package with machine scope. + /// + [Test] + public void UninstallTestMsixMachineScope() + { + // TODO: Provision and Deprovision api not supported in build server. + Assert.Ignore(); + + // Uninstall an MSIX + TestCommon.RunAICLICommand("install", $"{Constants.MsixInstallerPackageId} --scope machine"); + var result = TestCommon.RunAICLICommand("uninstall", $"{Constants.MsixInstallerPackageId} --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestMsixUninstalled(true)); + } + + /// + /// Test uninstall portable package. + /// + [Test] + public void UninstallPortable() + { + // Uninstall a Portable + string installDir = Path.Combine(System.Environment.GetEnvironmentVariable("LocalAppData"), "Microsoft", "WinGet", "Packages"); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + TestCommon.RunAICLICommand("install", $"{packageId}"); + var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + } + + /// + /// Test uninstall portable package with product code. + /// + [Test] + public void UninstallPortableWithProductCode() + { + // Uninstall a Portable with ProductCode + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + TestCommon.RunAICLICommand("install", $"{packageId}"); + var result = TestCommon.RunAICLICommand("uninstall", $"--product-code {productCode}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + } + + /// + /// Test uninstall portable package with modified symlink. + /// + [Test] + public void UninstallPortableModifiedSymlink() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + TestCommon.RunAICLICommand("install", $"{packageId}"); + + string symlinkDirectory = TestCommon.GetPortableSymlinkDirectory(TestCommon.Scope.User); + string symlinkPath = Path.Combine(symlinkDirectory, commandAlias); + + // Replace symlink with modified symlink + File.Delete(symlinkPath); + FileSystemInfo modifiedSymlinkInfo = File.CreateSymbolicLink(symlinkPath, "fakeTargetExe"); + + var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.ERROR_PORTABLE_UNINSTALL_FAILED, result.ExitCode); + Assert.True(result.StdOut.Contains("Unable to remove Portable package as it has been modified; to override this check use --force")); + Assert.True(modifiedSymlinkInfo.Exists, "Modified symlink should still exist"); + + // Try again with --force + var result2 = TestCommon.RunAICLICommand("uninstall", $"{packageId} --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Portable package has been modified; proceeding due to --force")); + Assert.True(result2.StdOut.Contains("Successfully uninstalled")); + + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + } + + /// + /// Test uninstall zip portable package. + /// + [Test] + public void UninstallZip_Portable() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestZipInstallerWithPortable"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = "TestPortable.exe"; + fileName = "AppInstallerTestExeInstaller.exe"; + + var testResult = TestCommon.RunAICLICommand("install", $"{packageId}"); + var result = TestCommon.RunAICLICommand("uninstall", $"{packageId}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, false); + } + + /// + /// Test uninstall not indexed. + /// + [Test] + public void UninstallNotIndexed() + { + // Uninstalls a package found with ARP not matching any known manifest. + // Install the test EXE providing a custom Product Code so that it cannot be mapped + // back to its manifest, then uninstall it using its Product Code + var installDir = TestCommon.GetRandomTestDir(); + TestCommon.RunAICLICommand("install", $"{Constants.ExeInstallerPackageId} --override \"/ProductID {CustomProductCode} /InstallDir {installDir}"); + var result = TestCommon.RunAICLICommand("uninstall", CustomProductCode); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully uninstalled")); + Assert.True(TestCommon.VerifyTestExeUninstalled(installDir)); + } + + /// + /// Test uninstalled app not found. + /// + [Test] + public void UninstallAppNotInstalled() + { + // Verify failure when trying to uninstall an app that is not installed. + var result = TestCommon.RunAICLICommand("uninstall", $"TestMsixInstaller"); + Assert.AreEqual(Constants.ErrorCode.ERROR_NO_APPLICATIONS_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("No installed package found matching input criteria.")); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/UpgradeCommand.cs b/src/AppInstallerCLIE2ETests/UpgradeCommand.cs index 821c1bd406..4a62e0b7e2 100644 --- a/src/AppInstallerCLIE2ETests/UpgradeCommand.cs +++ b/src/AppInstallerCLIE2ETests/UpgradeCommand.cs @@ -1,226 +1,226 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using System.IO; - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test upgrade command. - /// - public class UpgradeCommand : BaseCommand - { - private static readonly string DenyUpgradePackage = "AppInstallerTest.TestUpgradeDeny"; - - /// - /// Tear down. - /// - [TearDown] - public void TearDown() - { - // Due to its properties, this being present is problematic. - TestCommon.RunAICLICommand("uninstall", DenyUpgradePackage); - } - - /// - /// Test upgrade portable package. - /// - [Test] - public void UpgradePortable() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe -v 1.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); - Assert.True(result2.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test upgrade portable package. - /// - [Test] - public void UpgradePortableNonAsciiPath() - { - string installDir = Path.Combine(TestCommon.GetRandomTestDir(), "Tést"); - string packageId, commandAlias, fileName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestPortableExe -v 1.0.0.0 -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0 -l {installDir}"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); - Assert.True(result2.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(installDir, commandAlias, fileName, productCode, true); - } - - /// - /// Test upgrade portable package with arp mismatch. - /// - [Test] - public void UpgradePortableARPMismatch() - { - string packageId = "AppInstallerTest.TestPortableExe"; - string productCode = packageId + "_" + Constants.TestSourceIdentifier; - - var installResult = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe -v 1.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); - Assert.True(installResult.StdOut.Contains("Successfully installed")); - - // Modify packageId to cause mismatch. - TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); - - var upgradeResult = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); - - // Reset and perform uninstall cleanup - TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, packageId); - TestCommon.RunAICLICommand("uninstall", $"--product-code {productCode}"); - - Assert.AreNotEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); - Assert.True(upgradeResult.StdOut.Contains("Portable package from a different source already exists")); - } - - /// - /// Test upgrade portable package force override. - /// - [Test] - public void UpgradePortableForcedOverride() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var installResult = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe -v 1.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); - Assert.True(installResult.StdOut.Contains("Successfully installed")); - - // Modify packageId and sourceId to cause mismatch. - TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); - TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetSourceIdentifier, "testSourceId"); - - var upgradeResult = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0 --force"); - Assert.AreEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); - Assert.True(upgradeResult.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test upgrade portable package uninstall previous version. - /// - [Test] - public void UpgradePortableUninstallPrevious() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -v 1.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 3.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); - Assert.True(result2.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); - } - - /// - /// Test upgrade with deny behavior. - /// - [Test] - public void UpgradeBehaviorDeny() - { - string packageId = DenyUpgradePackage; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -v 1.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED, result2.ExitCode); - Assert.True(result2.StdOut.Contains("package cannot be upgraded using WinGet")); - } - - /// - /// Test upgrade portable package machine scope. - /// - [Test] - public void UpgradePortableMachineScope() - { - string installDir = TestCommon.GetRandomTestDir(); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); - - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestPortableExe"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"{packageId} -v 1.0.0.0 --scope machine"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); - WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); - Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); - Assert.True(result2.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); - } - - /// - /// Test upgrade zip portable package. - /// - [Test] - public void UpgradeZip_Portable() - { - string installDir = TestCommon.GetPortablePackagesDirectory(); - string packageId, commandAlias, fileName, packageDirName, productCode; - packageId = "AppInstallerTest.TestZipInstallerWithPortable"; - packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; - commandAlias = "TestPortable.exe"; - fileName = "AppInstallerTestExeInstaller.exe"; - - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithPortable -v 1.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Successfully installed")); - - var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); - Assert.True(result2.StdOut.Contains("Successfully installed")); - TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.User); - } - - /// - /// Test upgrade when a new dependency is added that is not installed. - /// - [Test] - public void UpgradeAddsDependency() - { - var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestUpgradeAddsDependency -v 1.0 --verbose"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - - result = TestCommon.RunAICLICommand("upgrade", $"AppInstallerTest.TestUpgradeAddsDependency"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using System.IO; + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test upgrade command. + /// + public class UpgradeCommand : BaseCommand + { + private static readonly string DenyUpgradePackage = "AppInstallerTest.TestUpgradeDeny"; + + /// + /// Tear down. + /// + [TearDown] + public void TearDown() + { + // Due to its properties, this being present is problematic. + TestCommon.RunAICLICommand("uninstall", DenyUpgradePackage); + } + + /// + /// Test upgrade portable package. + /// + [Test] + public void UpgradePortable() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe -v 1.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test upgrade portable package. + /// + [Test] + public void UpgradePortableNonAsciiPath() + { + string installDir = Path.Combine(TestCommon.GetRandomTestDir(), "Tést"); + string packageId, commandAlias, fileName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestPortableExe -v 1.0.0.0 -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0 -l {installDir}"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(installDir, commandAlias, fileName, productCode, true); + } + + /// + /// Test upgrade portable package with arp mismatch. + /// + [Test] + public void UpgradePortableARPMismatch() + { + string packageId = "AppInstallerTest.TestPortableExe"; + string productCode = packageId + "_" + Constants.TestSourceIdentifier; + + var installResult = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe -v 1.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("Successfully installed")); + + // Modify packageId to cause mismatch. + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); + + var upgradeResult = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); + + // Reset and perform uninstall cleanup + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, packageId); + TestCommon.RunAICLICommand("uninstall", $"--product-code {productCode}"); + + Assert.AreNotEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); + Assert.True(upgradeResult.StdOut.Contains("Portable package from a different source already exists")); + } + + /// + /// Test upgrade portable package force override. + /// + [Test] + public void UpgradePortableForcedOverride() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var installResult = TestCommon.RunAICLICommand("install", "AppInstallerTest.TestPortableExe -v 1.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, installResult.ExitCode); + Assert.True(installResult.StdOut.Contains("Successfully installed")); + + // Modify packageId and sourceId to cause mismatch. + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetPackageIdentifier, "testPackageId"); + TestCommon.ModifyPortableARPEntryValue(productCode, Constants.WinGetSourceIdentifier, "testSourceId"); + + var upgradeResult = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0 --force"); + Assert.AreEqual(Constants.ErrorCode.S_OK, upgradeResult.ExitCode); + Assert.True(upgradeResult.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test upgrade portable package uninstall previous version. + /// + [Test] + public void UpgradePortableUninstallPrevious() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -v 1.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 3.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true); + } + + /// + /// Test upgrade with deny behavior. + /// + [Test] + public void UpgradeBehaviorDeny() + { + string packageId = DenyUpgradePackage; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -v 1.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED, result2.ExitCode); + Assert.True(result2.StdOut.Contains("package cannot be upgraded using WinGet")); + } + + /// + /// Test upgrade portable package machine scope. + /// + [Test] + public void UpgradePortableMachineScope() + { + string installDir = TestCommon.GetRandomTestDir(); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, installDir); + + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestPortableExe"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"{packageId} -v 1.0.0.0 --scope machine"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); + WinGetSettingsHelper.ConfigureInstallBehavior(Constants.PortablePackageMachineRoot, string.Empty); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.Machine); + } + + /// + /// Test upgrade zip portable package. + /// + [Test] + public void UpgradeZip_Portable() + { + string installDir = TestCommon.GetPortablePackagesDirectory(); + string packageId, commandAlias, fileName, packageDirName, productCode; + packageId = "AppInstallerTest.TestZipInstallerWithPortable"; + packageDirName = productCode = packageId + "_" + Constants.TestSourceIdentifier; + commandAlias = "TestPortable.exe"; + fileName = "AppInstallerTestExeInstaller.exe"; + + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestZipInstallerWithPortable -v 1.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Successfully installed")); + + var result2 = TestCommon.RunAICLICommand("upgrade", $"{packageId} -v 2.0.0.0"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result2.ExitCode); + Assert.True(result2.StdOut.Contains("Successfully installed")); + TestCommon.VerifyPortablePackage(Path.Combine(installDir, packageDirName), commandAlias, fileName, productCode, true, TestCommon.Scope.User); + } + + /// + /// Test upgrade when a new dependency is added that is not installed. + /// + [Test] + public void UpgradeAddsDependency() + { + var result = TestCommon.RunAICLICommand("install", $"AppInstallerTest.TestUpgradeAddsDependency -v 1.0 --verbose"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + + result = TestCommon.RunAICLICommand("upgrade", $"AppInstallerTest.TestUpgradeAddsDependency"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + } + } +} diff --git a/src/AppInstallerCLIE2ETests/ValidateCommand.cs b/src/AppInstallerCLIE2ETests/ValidateCommand.cs index 4893568dbc..725da15a41 100644 --- a/src/AppInstallerCLIE2ETests/ValidateCommand.cs +++ b/src/AppInstallerCLIE2ETests/ValidateCommand.cs @@ -1,126 +1,126 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace AppInstallerCLIE2ETests -{ - using AppInstallerCLIE2ETests.Helpers; - using NUnit.Framework; - - /// - /// Test validate command. - /// - public class ValidateCommand : BaseCommand - { - /// - /// Test validate manifest. - /// - [Test] - public void ValidateManifest() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestValidManifest.yaml")); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); - } - - /// - /// Test validate manifest with extended characters. - /// - [Test] - public void ValidateManifestWithExtendedCharacter() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TstExeInstaller.yaml")); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); - } - - /// - /// Test validate invalid manifest. - /// - [Test] - public void ValidateInvalidManifest() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestInvalidManifest.yaml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_FAILURE, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation failed.")); - } - - /// - /// Test validate manifest with warnings. - /// - [Test] - public void ValidateManifestWithWarnings() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifest.yaml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); - } - - /// - /// Test validate manifest with warnings suppressed. - /// - [Test] - public void ValidateManifestSuppressWarnings() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifest.yaml") + " --ignore-warnings"); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - Assert.False(result.StdOut.Contains("Manifest validation succeeded with warnings.")); - Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); - } - - /// - /// Test validate manifest that doesn't exist. - /// - [Test] - public void ValidateManifestDoesNotExist() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\DoesNotExist")); - Assert.AreEqual(Constants.ErrorCode.ERROR_PATH_NOT_FOUND, result.ExitCode); - Assert.True(result.StdOut.Contains("Path does not exist")); - } - - /// - /// Test validate manifest with invalid schema and expect warnings. - /// - [Test] - public void ValidateManifestV1_10_SchemaHeaderExpectWarnings() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderNotFound.yaml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); - Assert.True(result.StdOut.Contains("Manifest Warning: Schema header not found.")); - - result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderInvalid.yaml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); - Assert.True(result.StdOut.Contains("Manifest Warning: The schema header is invalid. Please verify that the schema header is present and formatted correctly.")); - - result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); - Assert.True(result.StdOut.Contains("Manifest Warning: The schema header URL does not match the expected pattern")); - - result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); - Assert.True(result.StdOut.Contains("Manifest Warning: The manifest type in the schema header does not match the ManifestType property value in the manifest.")); - - result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml")); - Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); - Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); - Assert.True(result.StdOut.Contains("Manifest Warning: The manifest version in the schema header does not match the ManifestVersion property value in the manifest.")); - } - - /// - /// Test validate manifest with valid schema header. - /// - [Test] - public void ValidateManifestV1_10_SchemaHeaderExpectNoWarning() - { - var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestGoodManifestV1_10-SchemaHeader.yaml")); - Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); - } - } +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace AppInstallerCLIE2ETests +{ + using AppInstallerCLIE2ETests.Helpers; + using NUnit.Framework; + + /// + /// Test validate command. + /// + public class ValidateCommand : BaseCommand + { + /// + /// Test validate manifest. + /// + [Test] + public void ValidateManifest() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestValidManifest.yaml")); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); + } + + /// + /// Test validate manifest with extended characters. + /// + [Test] + public void ValidateManifestWithExtendedCharacter() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TstExeInstaller.yaml")); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); + } + + /// + /// Test validate invalid manifest. + /// + [Test] + public void ValidateInvalidManifest() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestInvalidManifest.yaml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_FAILURE, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation failed.")); + } + + /// + /// Test validate manifest with warnings. + /// + [Test] + public void ValidateManifestWithWarnings() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifest.yaml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); + } + + /// + /// Test validate manifest with warnings suppressed. + /// + [Test] + public void ValidateManifestSuppressWarnings() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifest.yaml") + " --ignore-warnings"); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + Assert.False(result.StdOut.Contains("Manifest validation succeeded with warnings.")); + Assert.True(result.StdOut.Contains("Manifest validation succeeded.")); + } + + /// + /// Test validate manifest that doesn't exist. + /// + [Test] + public void ValidateManifestDoesNotExist() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\DoesNotExist")); + Assert.AreEqual(Constants.ErrorCode.ERROR_PATH_NOT_FOUND, result.ExitCode); + Assert.True(result.StdOut.Contains("Path does not exist")); + } + + /// + /// Test validate manifest with invalid schema and expect warnings. + /// + [Test] + public void ValidateManifestV1_10_SchemaHeaderExpectWarnings() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderNotFound.yaml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); + Assert.True(result.StdOut.Contains("Manifest Warning: Schema header not found.")); + + result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderInvalid.yaml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); + Assert.True(result.StdOut.Contains("Manifest Warning: The schema header is invalid. Please verify that the schema header is present and formatted correctly.")); + + result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); + Assert.True(result.StdOut.Contains("Manifest Warning: The schema header URL does not match the expected pattern")); + + result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); + Assert.True(result.StdOut.Contains("Manifest Warning: The manifest type in the schema header does not match the ManifestType property value in the manifest.")); + + result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml")); + Assert.AreEqual(Constants.ErrorCode.ERROR_MANIFEST_VALIDATION_WARNING, result.ExitCode); + Assert.True(result.StdOut.Contains("Manifest validation succeeded with warnings.")); + Assert.True(result.StdOut.Contains("Manifest Warning: The manifest version in the schema header does not match the ManifestVersion property value in the manifest.")); + } + + /// + /// Test validate manifest with valid schema header. + /// + [Test] + public void ValidateManifestV1_10_SchemaHeaderExpectNoWarning() + { + var result = TestCommon.RunAICLICommand("validate", TestCommon.GetTestDataFile("Manifests\\TestGoodManifestV1_10-SchemaHeader.yaml")); + Assert.AreEqual(Constants.ErrorCode.S_OK, result.ExitCode); + } + } } \ No newline at end of file diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilCompareVersions.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilCompareVersions.cs index 89f538bac2..85a51c36ab 100644 --- a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilCompareVersions.cs +++ b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilCompareVersions.cs @@ -1,23 +1,23 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using NUnit.Framework; - /// - /// Test winget util compare versions. + /// + /// Test winget util compare versions. /// public class WinGetUtilCompareVersions { - /// - /// Test compare versions. - /// - /// Version 1. - /// Version 2. + /// + /// Test compare versions. + /// + /// Version 1. + /// Version 2. /// Expected result. [Test] //// V1 = V2 diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilDownload.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilDownload.cs index fcec745af1..ea09e2af10 100644 --- a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilDownload.cs +++ b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilDownload.cs @@ -7,8 +7,8 @@ namespace AppInstallerCLIE2ETests.WinGetUtil { using System.IO; - using System.Linq; - using AppInstallerCLIE2ETests.Helpers; + using System.Linq; + using AppInstallerCLIE2ETests.Helpers; using NUnit.Framework; /// diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilInstallerMetadataCollection.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilInstallerMetadataCollection.cs index fcafd33cbc..2542644a7f 100644 --- a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilInstallerMetadataCollection.cs +++ b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilInstallerMetadataCollection.cs @@ -1,25 +1,25 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using System; using System.IO; - using System.Runtime.InteropServices; - using AppInstallerCLIE2ETests.Helpers; + using System.Runtime.InteropServices; + using AppInstallerCLIE2ETests.Helpers; using Newtonsoft.Json; using NUnit.Framework; - /// - /// Test winget util installer metadata. + /// + /// Test winget util installer metadata. /// public class WinGetUtilInstallerMetadataCollection { - /// - /// Test begin complete installer metadata. + /// + /// Test begin complete installer metadata. /// [Test] public void WinGetUtil_BeginCompleteInstallerMetadataCollection() @@ -46,8 +46,8 @@ public void WinGetUtil_BeginCompleteInstallerMetadataCollection() Assert.IsNotEmpty(JsonConvert.DeserializeObject(outputJson).ToString()); } - /// - /// Test merge installer metadata. + /// + /// Test merge installer metadata. /// [Test] public void WinGetUtil_MergeInstallerMetadata_Success() @@ -67,8 +67,8 @@ public void WinGetUtil_MergeInstallerMetadata_Success() Assert.IsNotEmpty(JsonConvert.DeserializeObject(outputJson).ToString()); } - /// - /// Test merge installer metadata failed. + /// + /// Test merge installer metadata failed. /// [Test] public void WinGetUtil_MergeInstallerMetadata_Fail_SubmissionMismatch() diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilLog.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilLog.cs index e99448c06e..1e932f19ed 100644 --- a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilLog.cs +++ b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilLog.cs @@ -1,22 +1,22 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { - using System.IO; - using AppInstallerCLIE2ETests.Helpers; + using System.IO; + using AppInstallerCLIE2ETests.Helpers; using NUnit.Framework; - /// - /// Test winget util log. + /// + /// Test winget util log. /// public class WinGetUtilLog { - /// - /// Test logging functions. + /// + /// Test logging functions. /// [Test] public void WinGetUtil_Logging() diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilManifest.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilManifest.cs index ab0d76a223..16cdd8c5a7 100644 --- a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilManifest.cs +++ b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilManifest.cs @@ -1,25 +1,25 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using System; - using System.IO; - using AppInstallerCLIE2ETests.Helpers; + using System.IO; + using AppInstallerCLIE2ETests.Helpers; using NUnit.Framework; - /// - /// Test winget util manifest. + /// + /// Test winget util manifest. /// public class WinGetUtilManifest { private IntPtr indexHandle; - /// - /// Set up. + /// + /// Set up. /// [SetUp] public void SetUp() @@ -31,8 +31,8 @@ public void SetUp() WinGetUtilWrapper.WinGetSQLiteIndexCreate(sqliteFile, majorVersion, minorVersion, out this.indexHandle); } - /// - /// Tear down. + /// + /// Tear down. /// [TearDown] public void TearDown() @@ -40,9 +40,9 @@ public void TearDown() WinGetUtilWrapper.WinGetSQLiteIndexClose(this.indexHandle); } - /// - /// Test validate manifest. - /// + /// + /// Test validate manifest. + /// /// Create manifest options. [Test] [TestCase(WinGetUtilWrapper.CreateManifestOption.NoValidation)] @@ -80,85 +80,85 @@ public void WinGetUtil_ValidateManifest_Success(WinGetUtilWrapper.CreateManifest // Close manifest WinGetUtilWrapper.WinGetCloseManifest(manifestHandle); - } - - /// - /// Test validate manifest with schema header. - /// - /// Create manifest options. - [Test] + } + + /// + /// Test validate manifest with schema header. + /// + /// Create manifest options. + [Test] [TestCase(WinGetUtilWrapper.CreateManifestOption.NoValidation)] - [TestCase(WinGetUtilWrapper.CreateManifestOption.SchemaAndSemanticValidation)] - public void WinGetUtil_ValidateManifest_V1_10_WithSchemaHeader_Success(WinGetUtilWrapper.CreateManifestOption createManifestOption) - { - string manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestGoodManifestV1_10-SchemaHeader.yaml"); - - // Create manifest - WinGetUtilWrapper.WinGetCreateManifest( - manifestsFilePath, - out bool succeeded, - out IntPtr manifestHandle, - out string createFailureMessage, - string.Empty, - createManifestOption); - - Assert.True(succeeded); - Assert.AreNotEqual(IntPtr.Zero, manifestHandle); - Assert.IsNull(createFailureMessage); - - // Close manifest - WinGetUtilWrapper.WinGetCloseManifest(manifestHandle); - } - - /// - /// Test validate manifest with schema header for failure scenarios. - /// - /// Create manifest options. - [Test] - [TestCase(WinGetUtilWrapper.CreateManifestOption.SchemaAndSemanticValidation)] - public void WinGetUtil_ValidateManifest_V1_10_WithSchemaHeader_Failure(WinGetUtilWrapper.CreateManifestOption createManifestOption) - { - // Schema header not found - string manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderNotFound.yaml"); - string expectedError = "Manifest Error: Schema header not found."; - ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); - - // Schema header invalid - manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderInvalid.yaml"); - expectedError = "Manifest Error: The schema header is invalid. Please verify that the schema header is present and formatted correctly."; - ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); - - // Schema header URL pattern mismatch - manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml"); - expectedError = "Manifest Error: The schema header URL does not match the expected pattern."; - ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); - - // Schema header manifest type mismatch - manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml"); - expectedError = "Manifest Error: The manifest type in the schema header does not match the ManifestType property value in the manifest."; - ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); - - // Schema header version mismatch - manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml"); - expectedError = "Manifest Error: The manifest version in the schema header does not match the ManifestVersion property value in the manifest."; - ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); - } - - private static void ValidateSchemaHeaderFailure(string manifestsFilePath, WinGetUtilWrapper.CreateManifestOption createManifestOption, string expectedError) - { - // Create manifest - WinGetUtilWrapper.WinGetCreateManifest( - manifestsFilePath, - out bool succeeded, - out IntPtr manifestHandle, - out string createFailureMessage, - string.Empty, - createManifestOption); - - Assert.False(succeeded); - Assert.AreEqual(IntPtr.Zero, manifestHandle); - Assert.IsNotNull(createFailureMessage); - Assert.IsTrue(createFailureMessage.Contains(expectedError)); - } - } + [TestCase(WinGetUtilWrapper.CreateManifestOption.SchemaAndSemanticValidation)] + public void WinGetUtil_ValidateManifest_V1_10_WithSchemaHeader_Success(WinGetUtilWrapper.CreateManifestOption createManifestOption) + { + string manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestGoodManifestV1_10-SchemaHeader.yaml"); + + // Create manifest + WinGetUtilWrapper.WinGetCreateManifest( + manifestsFilePath, + out bool succeeded, + out IntPtr manifestHandle, + out string createFailureMessage, + string.Empty, + createManifestOption); + + Assert.True(succeeded); + Assert.AreNotEqual(IntPtr.Zero, manifestHandle); + Assert.IsNull(createFailureMessage); + + // Close manifest + WinGetUtilWrapper.WinGetCloseManifest(manifestHandle); + } + + /// + /// Test validate manifest with schema header for failure scenarios. + /// + /// Create manifest options. + [Test] + [TestCase(WinGetUtilWrapper.CreateManifestOption.SchemaAndSemanticValidation)] + public void WinGetUtil_ValidateManifest_V1_10_WithSchemaHeader_Failure(WinGetUtilWrapper.CreateManifestOption createManifestOption) + { + // Schema header not found + string manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderNotFound.yaml"); + string expectedError = "Manifest Error: Schema header not found."; + ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); + + // Schema header invalid + manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderInvalid.yaml"); + expectedError = "Manifest Error: The schema header is invalid. Please verify that the schema header is present and formatted correctly."; + ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); + + // Schema header URL pattern mismatch + manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderURLPatternMismatch.yaml"); + expectedError = "Manifest Error: The schema header URL does not match the expected pattern."; + ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); + + // Schema header manifest type mismatch + manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderManifestTypeMismatch.yaml"); + expectedError = "Manifest Error: The manifest type in the schema header does not match the ManifestType property value in the manifest."; + ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); + + // Schema header version mismatch + manifestsFilePath = TestCommon.GetTestDataFile(@"Manifests\TestWarningManifestV1_10-SchemaHeaderVersionMismatch.yaml"); + expectedError = "Manifest Error: The manifest version in the schema header does not match the ManifestVersion property value in the manifest."; + ValidateSchemaHeaderFailure(manifestsFilePath, createManifestOption, expectedError); + } + + private static void ValidateSchemaHeaderFailure(string manifestsFilePath, WinGetUtilWrapper.CreateManifestOption createManifestOption, string expectedError) + { + // Create manifest + WinGetUtilWrapper.WinGetCreateManifest( + manifestsFilePath, + out bool succeeded, + out IntPtr manifestHandle, + out string createFailureMessage, + string.Empty, + createManifestOption); + + Assert.False(succeeded); + Assert.AreEqual(IntPtr.Zero, manifestHandle); + Assert.IsNotNull(createFailureMessage); + Assert.IsTrue(createFailureMessage.Contains(expectedError)); + } + } } diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilSQLiteIndex.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilSQLiteIndex.cs index ae3986e867..9f1e4fd4cd 100644 --- a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilSQLiteIndex.cs +++ b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilSQLiteIndex.cs @@ -8,8 +8,8 @@ namespace AppInstallerCLIE2ETests.WinGetUtil { using System; using System.IO; - using System.Runtime.InteropServices; - using AppInstallerCLIE2ETests.Helpers; + using System.Runtime.InteropServices; + using AppInstallerCLIE2ETests.Helpers; using NUnit.Framework; /// diff --git a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilWrapper.cs b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilWrapper.cs index 5922a4c85c..91666148b3 100644 --- a/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilWrapper.cs +++ b/src/AppInstallerCLIE2ETests/WinGetUtil/WinGetUtilWrapper.cs @@ -1,9 +1,9 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + namespace AppInstallerCLIE2ETests.WinGetUtil { using System; @@ -17,227 +17,227 @@ public class WinGetUtilWrapper { private const string DllName = @"WinGetUtil.dll"; - /// - /// Create manifest flags. + /// + /// Create manifest flags. /// [Flags] public enum CreateManifestOption { - /// - /// No validation. + /// + /// No validation. /// NoValidation = 0, - /// - /// Schema validation. + /// + /// Schema validation. /// SchemaValidation = 0x1, - /// - /// Schema and semantic validation. + /// + /// Schema and semantic validation. /// SchemaAndSemanticValidation = 0x2, - /// - /// Return error on verified publisher. + /// + /// Return error on verified publisher. /// ReturnErrorOnVerifiedPublisherFields = 0x1000, } - /// - /// Validate manifests results. + /// + /// Validate manifests results. /// [Flags] public enum ValidateManifestResultCode { - /// - /// Success. + /// + /// Success. /// Success = 0, - /// - /// Dependencies validation failure. + /// + /// Dependencies validation failure. /// DependenciesValidationFailure = 0x1, - /// - /// Arp version validation failure. + /// + /// Arp version validation failure. /// ArpVersionValidationFailure = 0x2, - /// - /// Installer validation failure. + /// + /// Installer validation failure. /// InstallerValidationFailure = 0x4, - /// - /// Single manifest package has dependencies. + /// + /// Single manifest package has dependencies. /// SingleManifestPackageHasDependencies = 0x10000, - /// - /// Multi manifest package has dependencies. + /// + /// Multi manifest package has dependencies. /// MultiManifestPackageHasDependencies = 0x20000, - /// - /// Missing manifest dependencies. + /// + /// Missing manifest dependencies. /// MissingManifestDependenciesNode = 0x40000, - /// - /// No suitable min version dependencies. + /// + /// No suitable min version dependencies. /// NoSuitableMinVersionDependency = 0x80000, - /// - /// Found dependency loop. + /// + /// Found dependency loop. /// FoundDependencyLoop = 0x100000, - /// - /// Internal error. + /// + /// Internal error. /// InternalError = 0x1000, } - /// - /// ValidateManifestOptionV2 flags. + /// + /// ValidateManifestOptionV2 flags. /// [Flags] public enum ValidateManifestOptionV2 { - /// - /// None. + /// + /// None. /// None = 0, - /// - /// Dependencies validation. + /// + /// Dependencies validation. /// DependenciesValidation = 0x1, - /// - /// Arp version validation. + /// + /// Arp version validation. /// ArpVersionValidation = 0x2, - /// - /// Installer validation. + /// + /// Installer validation. /// InstallerValidation = 0x4, } - /// - /// Validate manifest operation type. + /// + /// Validate manifest operation type. /// public enum ValidateManifestOperationType { - /// - /// Add. + /// + /// Add. /// Add = 0, - /// - /// Update. + /// + /// Update. /// Update = 1, - /// - /// Delete. + /// + /// Delete. /// Delete = 2, } - /// - /// Begin installer metadata collection options. + /// + /// Begin installer metadata collection options. /// public enum WinGetBeginInstallerMetadataCollectionOptions { - /// - /// None. + /// + /// None. /// WinGetBeginInstallerMetadataCollectionOption_None = 0, - /// - /// Input is file path. + /// + /// Input is file path. /// WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath = 0x1, - /// - /// Input is URI. + /// + /// Input is URI. /// WinGetBeginInstallerMetadataCollectionOption_InputIsURI = 0x2, } - /// - /// Complete installer metadata collection. + /// + /// Complete installer metadata collection. /// public enum WinGetCompleteInstallerMetadataCollectionOptions { - /// - /// None. + /// + /// None. /// WinGetCompleteInstallerMetadataCollectionOption_None = 0, - /// - /// Abandon. + /// + /// Abandon. /// WinGetCompleteInstallerMetadataCollectionOption_Abandon = 0x1, } - /// - /// Merge installer metadata. + /// + /// Merge installer metadata. /// public enum WinGetMergeInstallerMetadataOptions { - /// - /// None. + /// + /// None. /// WinGetMergeInstallerMetadataOptions_None = 0, } - /// - /// WinGetCompareVersion from wingetutil.dll . - /// - /// Version. - /// Other version. + /// + /// WinGetCompareVersion from wingetutil.dll . + /// + /// Version. + /// Other version. /// Result of comparison. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCompareVersions(string version1, string version2, [MarshalAs(UnmanagedType.U4)] out int comparisonResult); - /// - /// WinGetDownload from wingetutil.dll . - /// - /// Url. - /// File path where to download. - /// SHA256 hash. + /// + /// WinGetDownload from wingetutil.dll . + /// + /// Url. + /// File path where to download. + /// SHA256 hash. /// SHA256 hash length. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetDownload(string url, string filePath, [MarshalAs(UnmanagedType.LPArray)] byte[] sha256Hash, uint sha256HashLength); - /// - /// WinGetLoggingInit from wingetutil.dll . - /// + /// + /// WinGetLoggingInit from wingetutil.dll . + /// /// Log path. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetLoggingInit(string logPath); - /// - /// WinGetLoggingTerm from wingetutil.dll . - /// + /// + /// WinGetLoggingTerm from wingetutil.dll . + /// /// Log path. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetLoggingTerm(string logPath); - /// - /// WinGetCreateManifest from wingetutil.dll . - /// - /// Input path. - /// Succeeded. - /// Manifest handle. - /// Failure message. - /// Merge manifest path. + /// + /// WinGetCreateManifest from wingetutil.dll . + /// + /// Input path. + /// Succeeded. + /// Manifest handle. + /// Failure message. + /// Merge manifest path. /// Option. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCreateManifest( @@ -248,21 +248,21 @@ public static extern void WinGetCreateManifest( string mergedManifestPath, CreateManifestOption option); - /// - /// WinGetCloseManifest from wingetutil.dll . - /// + /// + /// WinGetCloseManifest from wingetutil.dll . + /// /// Manifest. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCloseManifest(IntPtr manifest); - /// - /// WinGetValidateManifestV3 from wingetutil.dll . - /// - /// Manifest handle. - /// Index handle. - /// Result. - /// Failure message. - /// Option. + /// + /// WinGetValidateManifestV3 from wingetutil.dll . + /// + /// Manifest handle. + /// Index handle. + /// Result. + /// Failure message. + /// Option. /// Operation type. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetValidateManifestV3( @@ -273,46 +273,46 @@ public static extern void WinGetValidateManifestV3( ValidateManifestOptionV2 option, ValidateManifestOperationType operationType); - /// - /// WinGetSQLiteIndexCreate from wingetutil.dll . - /// - /// File path. - /// Major version. - /// Minor version. + /// + /// WinGetSQLiteIndexCreate from wingetutil.dll . + /// + /// File path. + /// Major version. + /// Minor version. /// Index. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexCreate(string filePath, uint majorVersion, uint minorVersion, out IntPtr index); - /// - /// WinGetSQLiteIndexOpen from wingetutil.dll . - /// - /// File path. + /// + /// WinGetSQLiteIndexOpen from wingetutil.dll . + /// + /// File path. /// Index. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexOpen(string filePath, out IntPtr index); - /// - /// WinGetSQLiteIndexClose from wingetutil.dll . - /// + /// + /// WinGetSQLiteIndexClose from wingetutil.dll . + /// /// Index. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexClose(IntPtr index); - /// - /// WinGetSQLiteIndexAddManifest from wingetutil.dll . - /// - /// Index. - /// Manifest path. + /// + /// WinGetSQLiteIndexAddManifest from wingetutil.dll . + /// + /// Index. + /// Manifest path. /// Relative path. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexAddManifest(IntPtr index, string manifestPath, string relativePath); - /// - /// WinGetSQLiteIndexUpdateManifest from wingetutil.dll . - /// - /// Index. - /// Manifest path. - /// Relative path. + /// + /// WinGetSQLiteIndexUpdateManifest from wingetutil.dll . + /// + /// Index. + /// Manifest path. + /// Relative path. /// Index modified. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexUpdateManifest( @@ -321,36 +321,36 @@ public static extern void WinGetSQLiteIndexUpdateManifest( string relativePath, [MarshalAs(UnmanagedType.U1)] out bool indexModified); - /// - /// WinGetSQLiteIndexRemoveManifest from wingetutil.dll . - /// - /// Index. - /// Manifest path. + /// + /// WinGetSQLiteIndexRemoveManifest from wingetutil.dll . + /// + /// Index. + /// Manifest path. /// Relative path. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexRemoveManifest(IntPtr index, string manifestPath, string relativePath); - /// - /// WinGetSQLiteIndexPrepareForPackaging from wingetutil.dll . - /// + /// + /// WinGetSQLiteIndexPrepareForPackaging from wingetutil.dll . + /// /// Index. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexPrepareForPackaging(IntPtr index); - /// - /// WinGetSQLiteIndexCheckConsistency from wingetutil.dll . - /// - /// Index. + /// + /// WinGetSQLiteIndexCheckConsistency from wingetutil.dll . + /// + /// Index. /// Succeeded. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetSQLiteIndexCheckConsistency(IntPtr index, [MarshalAs(UnmanagedType.U1)] out bool succeeded); - /// - /// WinGetBeginInstallerMetadataCollection from wingetutil.dll . - /// - /// Input json. - /// Log file path. - /// Options. + /// + /// WinGetBeginInstallerMetadataCollection from wingetutil.dll . + /// + /// Input json. + /// Log file path. + /// Options. /// Collection handle. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetBeginInstallerMetadataCollection( @@ -359,11 +359,11 @@ public static extern void WinGetBeginInstallerMetadataCollection( WinGetBeginInstallerMetadataCollectionOptions options, out IntPtr collectionHandle); - /// - /// WinGetCompleteInstallerMetadataCollection from wingetutil.dll . - /// - /// Collection handle. - /// Output file path. + /// + /// WinGetCompleteInstallerMetadataCollection from wingetutil.dll . + /// + /// Collection handle. + /// Output file path. /// Options. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetCompleteInstallerMetadataCollection( @@ -371,13 +371,13 @@ public static extern void WinGetCompleteInstallerMetadataCollection( string outputFilePath, WinGetCompleteInstallerMetadataCollectionOptions options); - /// - /// WinGetMergeInstallerMetadata from wingetutil.dll . - /// - /// Input json. - /// Output json. - /// Maximum output size in bytes. - /// Log file path. + /// + /// WinGetMergeInstallerMetadata from wingetutil.dll . + /// + /// Input json. + /// Output json. + /// Maximum output size in bytes. + /// Log file path. /// Options. [DllImport(DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] public static extern void WinGetMergeInstallerMetadata( diff --git a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj index be5c39977e..738507f0b0 100644 --- a/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj +++ b/src/AppInstallerCLIPackage/AppInstallerCLIPackage.wapproj @@ -1,278 +1,278 @@ - - - - 15.0 - - - - Debug - x86 - - - Release - x86 - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - Debug - AnyCPU - - - Release - AnyCPU - - - - $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ - - - - 6aa3791a-0713-4548-a357-87a323e7ac3a - 10.0.26100.0 - 10.0.17763.0 - en-US - false - ..\AppInstallerCLI\AppInstallerCLI.vcxproj - - - - Designer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - - - - - - - - - - - - - - - - - - - copy "$(TargetDir)\resources.pri" "$(ProjectDir)\..\$(Platform)\$(Configuration)\AppInstallerCLI\resources.pri" - copy "$(TargetDir)\resources.pri" "$(TargetDir)\AppInstallerCLI\resources.pri" - - - - win-arm64 - - - win-x64 - - - win-x86 - - - - - - - - - - $(SolutionDir) - DotNet - - - - - WindowsPackageManager.dll - - - Microsoft.Management.Deployment.winmd - - - Microsoft.Management.Configuration.dll - - - Microsoft.Management.Configuration.winmd - - - $(WinGetDotNetDirectoryName) - true - - - $(WinGetDotNetDirectoryName)\Microsoft.Management.Deployment.dll - - - ExternalModules - true - - - . - true - - - - - - - - %(WinGetAdditionalPackageFile.PackagePath) - - - - - - - %(WinGetAdditionalPackageFile.PackagePath)\%(WinGetAdditionalPackageFile.RecursiveDir)%(WinGetAdditionalPackageFile.Filename)%(WinGetAdditionalPackageFile.Extension) - - - + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + AnyCPU + + + Release + AnyCPU + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + 6aa3791a-0713-4548-a357-87a323e7ac3a + 10.0.26100.0 + 10.0.17763.0 + en-US + false + ..\AppInstallerCLI\AppInstallerCLI.vcxproj + + + + Designer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + + + + + + + + + + + + + + + + + + + copy "$(TargetDir)\resources.pri" "$(ProjectDir)\..\$(Platform)\$(Configuration)\AppInstallerCLI\resources.pri" + copy "$(TargetDir)\resources.pri" "$(TargetDir)\AppInstallerCLI\resources.pri" + + + + win-arm64 + + + win-x64 + + + win-x86 + + + + + + + + + + $(SolutionDir) + DotNet + + + + + WindowsPackageManager.dll + + + Microsoft.Management.Deployment.winmd + + + Microsoft.Management.Configuration.dll + + + Microsoft.Management.Configuration.winmd + + + $(WinGetDotNetDirectoryName) + true + + + $(WinGetDotNetDirectoryName)\Microsoft.Management.Deployment.dll + + + ExternalModules + true + + + . + true + + + + + + + + %(WinGetAdditionalPackageFile.PackagePath) + + + + + + + %(WinGetAdditionalPackageFile.PackagePath)\%(WinGetAdditionalPackageFile.RecursiveDir)%(WinGetAdditionalPackageFile.Filename)%(WinGetAdditionalPackageFile.Extension) + + + \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Execute-AppxRecipe.ps1 b/src/AppInstallerCLIPackage/Execute-AppxRecipe.ps1 index 4bad569048..3656af1886 100644 --- a/src/AppInstallerCLIPackage/Execute-AppxRecipe.ps1 +++ b/src/AppInstallerCLIPackage/Execute-AppxRecipe.ps1 @@ -1,50 +1,50 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[CmdletBinding()] -param( - [Parameter(Mandatory=$true,Position=0)] - [string]$AppxRecipePath, - - [string]$LayoutPath, - - [switch]$Force -) - -[xml]$Local:recipe = Get-Content $AppxRecipePath - -if ([System.String]::IsNullOrEmpty($LayoutPath)) -{ - $LayoutPath = $Local:recipe.Project.PropertyGroup.LayoutDir -} - -$Local:namespace = @{ - ns = "http://schemas.microsoft.com/developer/msbuild/2003" -} - -$Local:manifestElement = Select-Xml -Xml $Local:recipe -Namespace $Local:namespace -XPath "//ns:AppXManifest" | Select-Object -ExpandProperty Node -$Local:packageFileElements = Select-Xml -Xml $Local:recipe -Namespace $Local:namespace -XPath "//ns:AppxPackagedFile" | Select-Object -ExpandProperty Node - -$Local:allItems = $Local:packageFileElements + $Local:manifestElement - -$Local:progressActivity = "Copying files to $LayoutPath" -Write-Progress -Activity $Local:progressActivity - -if ($Force) { - $null = New-Item -Path $LayoutPath -ItemType Directory -Force -} else { - New-Item -Path $LayoutPath -ItemType Directory -ErrorAction Inquire -} - -[Int32]$Local:filesCopied = 0 - -$Local:allItems | ForEach-Object -Process { - $Local:sourcePath = $_.Include - $Local:destinationPath = Join-Path $LayoutPath $_.PackagePath - Write-Verbose "$Local:sourcePath => $Local:destinationPath" - $null = New-Item -Path ([System.IO.Path]::GetDirectoryName($Local:destinationPath)) -ItemType Directory -Force - Copy-Item -Path $Local:sourcePath -Destination $Local:destinationPath - $Local:filesCopied += 1 - Write-Progress -Activity $Local:progressActivity -PercentComplete (($Local:filesCopied * 100) / $Local:allItems.Count) -} - -Write-Progress -Activity $Local:progressActivity -Completed +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + [Parameter(Mandatory=$true,Position=0)] + [string]$AppxRecipePath, + + [string]$LayoutPath, + + [switch]$Force +) + +[xml]$Local:recipe = Get-Content $AppxRecipePath + +if ([System.String]::IsNullOrEmpty($LayoutPath)) +{ + $LayoutPath = $Local:recipe.Project.PropertyGroup.LayoutDir +} + +$Local:namespace = @{ + ns = "http://schemas.microsoft.com/developer/msbuild/2003" +} + +$Local:manifestElement = Select-Xml -Xml $Local:recipe -Namespace $Local:namespace -XPath "//ns:AppXManifest" | Select-Object -ExpandProperty Node +$Local:packageFileElements = Select-Xml -Xml $Local:recipe -Namespace $Local:namespace -XPath "//ns:AppxPackagedFile" | Select-Object -ExpandProperty Node + +$Local:allItems = $Local:packageFileElements + $Local:manifestElement + +$Local:progressActivity = "Copying files to $LayoutPath" +Write-Progress -Activity $Local:progressActivity + +if ($Force) { + $null = New-Item -Path $LayoutPath -ItemType Directory -Force +} else { + New-Item -Path $LayoutPath -ItemType Directory -ErrorAction Inquire +} + +[Int32]$Local:filesCopied = 0 + +$Local:allItems | ForEach-Object -Process { + $Local:sourcePath = $_.Include + $Local:destinationPath = Join-Path $LayoutPath $_.PackagePath + Write-Verbose "$Local:sourcePath => $Local:destinationPath" + $null = New-Item -Path ([System.IO.Path]::GetDirectoryName($Local:destinationPath)) -ItemType Directory -Force + Copy-Item -Path $Local:sourcePath -Destination $Local:destinationPath + $Local:filesCopied += 1 + Write-Progress -Activity $Local:progressActivity -PercentComplete (($Local:filesCopied * 100) / $Local:allItems.Count) +} + +Write-Progress -Activity $Local:progressActivity -Completed diff --git a/src/AppInstallerCLIPackage/Package.appxmanifest b/src/AppInstallerCLIPackage/Package.appxmanifest index 8559da43ae..aa37c3b184 100644 --- a/src/AppInstallerCLIPackage/Package.appxmanifest +++ b/src/AppInstallerCLIPackage/Package.appxmanifest @@ -1,158 +1,158 @@ - - - - - WinGet Dev CLI - Microsoft Corporation - Images\StoreLogo.png - disabled - disabled - - - - - - - - - - - - - - - - - com.microsoft.winget.source - - - - - - - - - - - .wingetdev - - WinGetDev configuration file - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Microsoft.Management.Configuration.winmd - - - - - - - - - - - - - - - - - - - - - - - - - - Microsoft.Management.Configuration.dll - - - - - - - - - - - - - - + + + + + WinGet Dev CLI + Microsoft Corporation + Images\StoreLogo.png + disabled + disabled + + + + + + + + + + + + + + + + + com.microsoft.winget.source + + + + + + + + + + + .wingetdev + + WinGetDev configuration file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft.Management.Configuration.winmd + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft.Management.Configuration.dll + + + + + + + + + + + + + + diff --git a/src/AppInstallerCLIPackage/Register-WingetdevAutoComplete.ps1 b/src/AppInstallerCLIPackage/Register-WingetdevAutoComplete.ps1 index 61162b6ac4..3586c415f4 100644 --- a/src/AppInstallerCLIPackage/Register-WingetdevAutoComplete.ps1 +++ b/src/AppInstallerCLIPackage/Register-WingetdevAutoComplete.ps1 @@ -1,9 +1,9 @@ -Register-ArgumentCompleter -Native -CommandName wingetdev -ScriptBlock { - param($wordToComplete, $commandAst, $cursorPosition) - [Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new() - $Local:word = $wordToComplete.Replace('"', '""') - $Local:ast = $commandAst.ToString().Replace('"', '""') - wingetdev complete --word="$Local:word" --commandline "$Local:ast" --position $cursorPosition | ForEach-Object { - [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) - } +Register-ArgumentCompleter -Native -CommandName wingetdev -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + [Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new() + $Local:word = $wordToComplete.Replace('"', '""') + $Local:ast = $commandAst.ToString().Replace('"', '""') + wingetdev complete --word="$Local:word" --commandline "$Local:ast" --position $cursorPosition | ForEach-Object { + [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) + } } \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/Resources.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/Resources.resw index 6fa1b87984..61208b95bd 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/Resources.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/Resources.resw @@ -1,124 +1,124 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Not Localized - {Locked} This file is required to establish the basic expected subresource in the resource map. It is not used to facilitate localization with other projects. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Not Localized + {Locked} This file is required to establish the basic expected subresource in the resource map. It is not used to facilitate localization with other projects. + \ No newline at end of file diff --git a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw index 8754372ac8..b562e3022a 100644 --- a/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw +++ b/src/AppInstallerCLIPackage/Shared/Strings/en-us/winget.resw @@ -1,3652 +1,3652 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - Adjoined alias is not a flag: '{0}' - {Locked="{0}"} Error message displayed when the user provides an adjoined alias that is not a flag argument. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). - - - Adjoined flag alias not found: '{0}' - {Locked="{0}"} Error message displayed when the user provides an adjoined flag alias argument that was not found. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). - - - The following arguments are available: - Message displayed to inform the user about the available command line arguments. - - - The following command aliases are available: - Message displayed to inform the user about the available command line alias arguments. - - - The following commands are available: - Title displayed to inform the user about the available commands. - - - Available - As in "a new version is available to upgrade to". - - - The following options are available: - Message displayed to inform the user about the available options. - - - The following sub-commands are available: - Message displayed to inform the user about the available nested commands that run in context of the selected command. - - - {0} upgrades available. - {Locked="{0}"} Message displayed to inform the user about available package upgrades. {0} is a placeholder replaced by the number of package upgrades. - - - Use the specified channel; default is general audience - - - command - Label displayed for a command to give the software. - - - Filter results by command - Description message displayed to inform the user about filtering the search results by a package command. - - - The full command line for completion - - - This command requires administrator privileges to execute. - - - This command can be used to request context sensitive command line completion. The command line, cursor position, and word to be completed are passed in. The output is a set of potential values based on the inputs, with one possible value per line. - - - Enables context sensitive command line completion - - - Show no more than specified number of results (between 1 and 1000) - - - Done - Label displayed when an operation completes or is done executing. - - - Find package using exact match - Description message displayed to inform the user about finding an application package using an exact matching criteria. - - - Experimental argument for demonstration purposes - - - This command is an example on how to implement an experimental feature. To turn on go to 'winget settings' and enable experimentalCmd or experimentalArg features. - {Locked="winget settings"} - - - Experimental feature example - - - Found a positional argument when none was expected: '{0}' - {Locked="{0}"} Error message displayed when the user provides an extra positional argument when none was expected. {0} is a placeholder replaced by the user's extra argument input. - - - This feature is a work in progress, and may be changed dramatically or removed altogether in the future. To enable it, edit your settings ('winget settings') to include the experimental feature: '{0}' - {Locked="winget settings","{0}"}. Error message displayed when the user uses an experimental feature that is disabled. {0} is a placeholder replaced by the experimental feature name. - - - Shows the status of experimental features. Experimental features can be turned on via 'winget settings'. - {Locked="winget settings"} - - - Shows the status of experimental features - - - Disabled - Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. - - - Enabled - Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. - - - Feature - Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. - - - Link - Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. - - - The following experimental features are in progress. -They can be configured through the settings file 'winget settings'. - {Locked="winget settings"} - - - Property - Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. - - - Status - Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. - - - File to be hashed - - - Flag argument cannot contain adjoined value: '{0}' - {Locked="{0}"} Error message displayed when the user provides a flag argument containing an unexpected adjoined value. {0} is a placeholder replaced by the user input. - - - Computes the hash of a local file, appropriate for entry into a manifest. It can also compute the hash of the signature file of an MSIX package to enable streaming installations. - - - Helper to hash installer files - - - Shows help about the selected command - - - For more details on a specific command, pass it the help argument. - - - More help can be found at: {0} - {Locked="{0}"} Message displayed to inform the user about a link where they can learn more about the subject context. {0} is a placeholder replaced by a website address. - - - Filter results by id - - - Suppresses warning outputs - - - This application is licensed to you by its owner. - - - Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. - - - This package is provided through Microsoft Store. WinGet may need to acquire the package from Microsoft Store on behalf of the current user. - {Locked="WinGet"} - - - Installs the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, install command will check package installed status and try to perform an upgrade if applicable. Override with --force to perform a direct install. - {Locked="--force"}; id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Installs the given package - - - Installer hash does not match; this cannot be overridden when running as admin - - - Installer hash does not match; proceeding due to --ignore-security-hash - {Locked="--ignore-security-hash"} - - - Installer hash does not match; to override this check use --ignore-security-hash - {Locked="--ignore-security-hash"} - - - Successfully verified installer hash - - - Successfully installed - - - Starting package install... - - - Ignore the installer hash check failure - - - Ignore the malware scan performed as part of installing an archive type package from local manifest - - - Request interactive installation; user input may be needed - - - Argument alias was not recognized for the current command: '{0}' - {Locked="{0}"} Error message displayed when the user provides a command line argument alias that was not recognized for a selected command. {0} is a placeholder replaced by the user's argument alias input (e.g. '-a'). - - - Invalid argument specifier: '{0}' - {Locked="{0}"} Error message displayed when the user provides an invalid argument specifier. {0} is a placeholder replaced by an argument specifier (e.g. '-'). - - - Argument name was not recognized for the current command: '{0}' - {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). - - - WinGet Directories - {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs - - - Locale to use (BCP47 format) - {Locked="BCP47"} - - - License Agreement - - - Links - Links to different webpages - - - The list command displays the packages installed on the system, as well as whether an upgrade is available. Additional options can be provided to filter the output, much like the search command. - {Locked="list","search"} - - - Display installed packages - - - Location to install to (if supported) - - - Log location (if supported) - - - © 2026 Microsoft. All rights reserved. - - - Homepage - The primary webpage for the software - - - The path to the manifest of the package - - - Manifest validation failed. - - - Manifest validation succeeded. - - - Manifest validation succeeded with warnings. - - - Argument value required, but none found: '{0}' - {Locked="{0}"} Error message displayed when the user does not provide a required command line argument value. {0} is a placeholder replaced by the argument name. - - - Filter results by moniker - - - Input file will be treated as msix; signature hash will be provided if signed - - - Failed to calculate MSIX signature hash. - - - Failed to install or upgrade Microsoft Store package because the specific app is blocked by policy - - - Failed to install or upgrade Microsoft Store package. Error code: {0} - {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to install or upgrade. {0} is a placeholder replaced by an error code. - - - Failed to install or upgrade Microsoft Store package because Microsoft Store client is blocked by policy - - - Verifying/Requesting package acquisition... - - - Multiple installed packages found matching input criteria. Please refine the input. - - - Multiple packages found matching input criteria. Please refine the input. - - - Filter results by name - - - No applicable installer found; see logs for more details. - - - There are currently no experimental features available. - - - No installed package found matching input criteria. - - - No package found matching input criteria. - - - Disables VirtualTerminal display - {Locked="VirtualTerminal"} - - - Open the default logs location - - - options - Options to change how a command works - - - Override arguments to be passed on to the installer - - - Package: {0} - {Locked="{0}"} Label displayed for a software package. {0} is a placeholder replaced by the software package name. - - - Oops, we forgot to do this... - - - The position of the cursor within the command line - - - Privacy Statement - - - The query used to search for a package - - - Progress display a rainbow of colors - - - Required argument not provided: '{0}' - {Locked="{0}"} Error message displayed when the user does not provide a required command line argument. {0} is a placeholder replaced by an argument name. - - - Progress display as the default color - - - Searches for packages from configured sources. - - - Find and show basic info of packages - - - Id - Abbreviation of Identifier. - - - Match - Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. - - - Name - - - Source - Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. - - - additional entries truncated due to result limit - - - Version - - - The following failures were found validating the settings: - - - Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings This command can also be used to set administrator settings by providing the --enable or --disable arguments - {Locked="--enable"} {Locked="--disable"} - - - Open settings or set administrator settings - - - Unexpected error while loading settings. Please verify your settings by running the 'settings' command. - {Locked="'settings'"} - - - Channel - Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. - - - Shows information on a specific package. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Shows information about a package - - - Version - - - Request silent installation - - - Only the single character alias can occur after a single -: '{0}' - {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. - - - Sort results by a property (can be repeated) - Description for the --sort argument used to sort output by field name. - - - Sort results in ascending order - Description for the --ascending (--asc) flag that sets ascending sort direction for output. - - - Sort results in descending order - Description for the --descending (--desc) flag that sets descending sort direction for output. - - - A source with the given name already exists and refers to a different location: - - - A source with a different name already refers to this location: - - - A source with the given name already exists and refers to the same location: - - - Adding source: - - - Add a new source. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. - - - Add a new source - - - Argument given to the source - - - Find package using the specified source - - - Manage sources with the sub-commands. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. - - - Manage sources of packages - - - Edit properties of an existing source. A source provides the data for you to discover and install packages. - - - Edit properties of a source - - - Editing source: {0} - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. - - - The source named '{0}' is already in the desired state. - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. - - - Argument - Value given to source. - - - List all current sources, or full details of a specific source. - - - List current sources - - - Data - Data stored by the source. - - - Field - The name of a piece of information about a source. - - - Name - The name of the source. - - - Identifier - The source's unique identifier. - - - Did not find a source named: {0} - Error message displayed when the user provides a repository source name that was not found. {0} is a placeholder replaced by the user input. - - - There are no sources configured. - - - Type - The kind of source. - - - Updated - The last time the source was updated. - - - never - The source has never been updated. - - - Value - The value of information about a source. - - - Name of the source - - - Failed when opening source(s); try the 'source reset' command if the problem persists. - {Locked="source reset"} - - - Failed to open the predefined source; please report to WinGet maintainers. - {Locked="WinGet"} - - - Removing all sources... - - - Remove a specific source. - - - Remove current sources - - - Removing source: {0}... - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being removed. {0} is a placeholder replaced by the repository source name. - - - Resetting all sources... - - - This command drops existing sources, potentially leaving any local data behind. Without any argument, it will drop all sources and add the defaults. If a named source is provided, only that source will be dropped. - - - Reset sources - - - Forces the reset of the sources - - - The following sources will be reset if the --force option is given: - {Locked="--force"} - - - Resetting source: {0}... - {Locked="{0}"} Message displayed to inform the user about a repository source that is currently being reset. {0} is a placeholder replaced by the repository source name. - - - Type of the source - - - Updating all sources... - - - Update all sources, or only a specific source. - - - Update current sources - - - Updating source: {0}... - {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being updated. {0} is a placeholder replaced by the repository source name. - - - Filter results by tag - - - Thank you for using WinGet - {Locked="WinGet"} - - - Third Party Notices - - - The winget command line utility enables installing applications and other packages from the command line. - - - Display general info of the tool - - - Display the version of the tool - - - Argument provided more times than allowed: '{0}' - {Locked="{0}"} Error message displayed when the user provides a command line argument more times than it is allowed. {0} is a placeholder replaced by the user's argument name input. - - - More than one execution behavior argument provided: '{0}' - {Locked="{0}"} Error message displayed when the user provides more than one execution behavior argument when installing an application package. {0} is a placeholder replaced by the user specified execution behaviors (e.g. 'silent|interactive'). - - - An unexpected error occurred while executing the command: - - - Uninstall the previous version of the package during upgrade - - - Unrecognized command: '{0}' - {Locked="{0}"} Error message displayed when the user provides an unrecognized command. {0} is a placeholder replaced by the user input. - - - Upgrade all installed packages to latest if available - - - No applicable upgrade found. - - - A newer package version is available in a configured source, but it does not apply to your system or requirements. - - - No available upgrade found. - - - No newer package versions are available from the configured sources. - - - Upgrades the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. When no arguments are given, shows the packages with upgrades available - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Shows and performs available upgrades - - - usage: {0} {1} - {Locked="{0} {1}"} Message displayed to provide the user with instructions on how to use a command. {0} is a placeholder replaced by the program name (e.g. 'winget'). {1} is a placeholder replaced by the pattern for using the selected command. - - - Validates a manifest using a strict set of guidelines. This is intended to enable you to check your manifest before submitting to a repo. - - - Validates a manifest file - - - The path to the manifest to be validated - - - Enables verbose logging for winget - - - Please verify that the input file is a valid, signed MSIX. - - - Use the specified version; default is the latest version - - - Show available versions of the package - - - The value provided before completion is requested - - - No version found matching: {0} - {Locked="{0}"} Error message displayed when the user attempts to upgrade an application package to a version that was not found. {0} is a placeholder replaced by the user's provided upgrade package version. - - - No sources match the given value: {0} - {Locked="{0}"} Error message displayed when the user attempts to install or upgrade an application package from a repository source that was not found. {0} is a placeholder replaced by the user's repository source name input. - - - The configured sources are: - - - No sources defined; add one with 'source add' or reset to defaults with 'source reset' - {Locked="source add","source reset"} - - - Found - Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. - - - Path is a directory: {0} - {Locked="{0}"} Error message displayed when the user provides a system path that is a directory. {0} is a placeholder replaced by the provided directory path. - - - File does not exist: {0} - {Locked="{0}"} Error message displayed when the user provides a system file that does not exist. {0} is a placeholder replaced by the provided file path. - - - Both local manifest and search query arguments are provided - - - Logs - Label displayed for diagnostic files containing information about the application use. - - - The installer is blocked by policy - - - The installer failed security check - - - An anti-virus product reports an infection in the installer - - - Failed in attempting to update the source: {0} - {Locked="{0}"} Error message displayed when an attempt to update the repository source fails. {0} is a placeholder replaced by the repository source name. - - - Uninstalls the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Uninstalls the given package - - - Starting package uninstall... - - - Successfully uninstalled - - - WinGet cannot locate the uninstall command for this package. Please reach out to the package publisher for support. - {Locked="WinGet"} - - - Uninstallation abandoned - - - Uninstall failed with exit code: {0} - {Locked="{0}"} Error message displayed when an attempt to uninstall an application package fails. {0} is a placeholder replaced by an error code. - - - Exports a list of the installed packages - - - Installs all the packages listed in a file. - - - Installs all the packages in a file - - - File where the result is to be written - - - File describing the packages to install - - - Export packages from the specified source - - - Writes a list of the installed packages to a file. The packages can then be installed with the import command. - {Locked="import"} - - - One or more imported packages failed to install - - - Source required for import is not installed: {0} - {Locked="{0}"} Error message displayed when the user attempts to import application package(s) from a repository source that is not installed. {0} is a placeholder replaced by the repository source name. - - - Installed package is not available from any source: {0} - {Locked="{0}"} Warning message displayed when the user attempts to export an installed application package that is not available from any repository source. {0} is a placeholder replaced by the installed package name. - - - Installed version of package is not available from any source: {0} {1} {2} - {Locked="{0} {1} {2}"} Warning message displayed when the user attempts to export an installed application package with a version that is not available from any repository source. {0} is a placeholder replaced by the installed package identifier. {1} is a placeholder replaced by the installed package version. {2} is a placeholder replaced by the installed package channel. - - - No packages found in import file - - - JSON file is not valid - - - Package is already installed: {0} - {Locked="{0}"} Message displayed to inform the user that an application package is already installed. {0} is a placeholder replaced by the package identifier or search query. - - - Ignore unavailable packages - - - Include package versions in export file - - - Ignore package versions from import file - - - Path does not exist: {0} - {Locked="{0}"} Error message displayed when the user provides a system path argument value that does not exist. {0} is a placeholder replaced by the user's provided path. - - - The JSON file does not specify a recognized schema. - - - Select install scope (user or machine) - {Locked="user","machine"} This argument allows the user to select between installing for just the user or for the entire machine. - - - The value provided for the `{0}` argument is invalid; valid values are: {1} - {Locked="{0}","{1}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. {1} is a placeholder replaced by a list of valid options. - - - This operation is disabled by Group Policy: {0} - {Locked="{0}"} Error message displayed when the user performs a command operation that is disabled by a group policy. {0} is a placeholder replaced by a group policy description. - - - Enable Additional Windows App Installer Sources - - - Enable Windows App Installer Allowed Sources - - - Enable Windows App Installer Default Source - - - Enable Windows App Installer Experimental Features - - - Enable Windows App Installer Microsoft Store Source - - - Enable Windows App Installer Font Source - - - Enable Windows Package Manager Settings - - - Enable Windows Package Manager - - - Enable Windows Package Manager command line interfaces - - - Set Windows Package Manager Source Auto Update Interval In Minutes - - - Group Policy - Header for a table listing active Group Policies - - - Enable Windows App Installer Local Manifest Files - - - Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass - - - Enable Windows App Installer Local Archive Malware Scan Override - - - Field: {0} - {Locked="{0}"} Warning message displayed when a user setting field has invalid syntax or semantics. {0} is a placeholder replaced by the setting field path. - - - Invalid field format. - - - Invalid field value. - - - Invalid setting from Group Policy. - - - Loaded settings from backup file. - - - Error parsing file: - - - Value: {0} - {Locked="{0}"} Warning message displayed when a user setting value has invalid syntax or semantics. {0} is a placeholder replaced by the setting data value. - - - The following experimental features are in progress. -Configuration is disabled due to Group Policy. - - - Installer hash does not match. - - - Enable Windows App Installer Hash Override - - - Export current sources as JSON for Group Policy. - - - Export current sources - - - Additional source - An additional source required by policy. - - - Allowed source - A source that the user is allowed to add. - - - The value provided for the `{0}` argument is invalid - {Locked="{0}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. - - - Cancelled - - - External - Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". - - - The packages found in this import have the following dependencies: - Import command sentence showed before reporting dependencies - - - This package requires the following dependencies: - Message shown before reporting dependencies - - - Installing dependencies: - - - Dependency source not found - - - Package search yield more than one result: {0} - {Locked="{0}"} Error message displayed when application packages search yield more than one result. {0} is a placeholder replaced by the dependency package identifier. - - - Latest version not found for package: {0} - {Locked="{0}"} Error message displayed when no suitable version found for the specific application package. {0} is a placeholder replaced by the package identifier. - - - No installers found: {0} - {Locked="{0}"} Error message displayed when no installer found for a manifest. {0} is a placeholder replaced by the manifest identifier. - - - Minimum required version not available for package: {0} - {Locked="{0}"} Error message displayed when the minimum required version is not available for an application package. {0} is a placeholder replaced by the package identifier. - - - No matches - When package search yields no matches - - - Has loop - Dependency graph has loop - - - No suitable installer found for manifest: {0} version {1} - {Locked="{0}","{1}"} Error message displayed when an attempt to get a preferred installer for a manifest fails. {0} is a placeholder replaced by the manifest identifier. {1} is a placeholder replaced by the manifest version. - - - Error processing package dependencies. Do you wish to continue installation? - Prompt message shown when dependencies processing yields errors. - - - Error processing package dependencies. Exiting... - - - Packages - - - This package had dependencies that may not be needed anymore: - Uninstall command sentence showed before reporting dependencies - - - Manifest has the following dependencies that were not validated; ensure that they are valid: - Validate command sentence showed before reporting dependencies - - - Windows Features - - - Windows Libraries - - - Find package dependencies using the specified source - For getting package type dependencies when installing from a local manifest - - - Windows Store Terms - - - Accept all license agreements for packages - - - Exported package requires license agreement to install: {0} - {Locked="{0}"} Warning message displayed when an exported application package requires license agreement to install. {0} is a placeholder replaced by the package name. - - - The publisher requires that you view the above information and accept the agreements before installing. -Do you agree to the terms? - - - Package agreements were not agreed to. Operation cancelled. - - - Agreements: - - - Author: - - - Description: - - - Installer: - - - Installer Locale: - - - Store Product Id: - - - Installer SHA256: - - - Installer Type: - - - Installer Url: - - - License: - - - License Url: - - - Moniker: - - - Homepage: - - - Publisher: - - - Tags: - - - Version: - - - Dependencies: - - - Windows Features: - - - Windows Libraries: - - - Package Dependencies: - - - External Dependencies: - - - No - - - Yes - - - Failed to open the added source. - - - Accept all source agreements during source operations - - - The `{0}` source requires that you view the following agreements before using. - {Locked="{0}"} Message displayed to inform the user that a repository source requires viewing agreements before using. {0} is a placeholder replaced by the repository source name. - - - Do you agree to all the source agreements terms? - - - One or more of the source agreements were not agreed to. Operation cancelled. Please accept the source agreements or remove the corresponding sources. - - - The source requires the current machine's 2-letter geographic region to be sent to the backend service to function properly (ex. "US"). - - - Successfully installed. Restart the application to complete the upgrade. - - - Optional Windows-Package-Manager REST source HTTP header - - - Ignoring the optional header as it is not applicable for this source. - - - The optional header is not applicable without specifying a source: '{0}' - {Locked="{0}"} Error message displayed when the user performs an operation (e.g install) and provides the HTTP 'header' argument without specifying the repository source. {0} is a placeholder replaced by the header argument name. - - - Release Date: - - - Offline Distribution Supported: - - - Publisher Url: - - - Purchase Url: - - - Publisher Support Url: - - - Privacy Url: - - - Copyright: - - - Copyright Url: - - - Release Notes: - - - Release Notes Url: - - - Failed when searching source; results will not be included: {0} - {Locked="{0}"} Warning message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. - - - Failed when searching source: {0} - {Locked="{0}"} Error message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. - - - This feature needs to be enabled by administrators. To enable it, run 'winget settings --enable {0}' as administrator. - {Locked="winget settings --enable", "{0}"}. Error message displayed when the user uses a feature that needs to be enabled by administrators. {0} is a placeholder replaced by the admin setting. - - - Enables the specific administrator setting - - - Disables the specific administrator setting - - - Enabled admin setting '{0}'. - {Locked="{0}"} Message displayed when the user enables an admin setting. {0} is a placeholder replaced by the setting name. - - - Disabled admin setting '{0}'. - {Locked="{0}"} Message displayed when the user disables an admin setting. {0} is a placeholder replaced by the setting name. - - - Admin Setting - Header for a table displaying admin settings. - - - Application is currently running. Exit the application then try again. - - - Files modified by the installer are currently in use by a different application. Exit the applications then try again. - - - Another installation is already in progress. Try again later. - - - One or more files are being used. Exit the application then try again. - - - This package has a dependency missing from your system. - - - There's no more space on your PC. Make space, then try again. - - - There's not enough memory available to install. Close other applications then try again. - - - One of the installation parameters is invalid. The package installation log may have additional details. - - - This application requires internet connectivity. Connect to a network then try again. - - - This application encountered an error during installation. Contact support. - - - Restart your PC to finish installation. - - - Your PC will restart to finish installation. - - - Installation failed. Restart your PC then try again. - - - You cancelled the installation. - - - Another version of this application is already installed. - - - A higher version of this application is already installed. - - - Organization policies are preventing installation. Contact your admin. - - - The current system configuration does not support the installation of this package. - - - Installation failed with a custom installer error. Contact package support. - - - Installation abandoned - - - Installer failed with exit code: {0} - {Locked="{0}"} Error message displayed when the application installer fails. {0} is a placeholder replaced by an error code. - - - Installer log is available at: {0} - {Locked="{0}"} Message displayed to inform the user about the system path of a diagnostic files containing information about the installer. {0} is a placeholder replaced by the diagnostic file system path. - - - The following packages were found among the working sources. -Please specify one of them using the --source option to proceed. - {Locked="--source"} "working sources" as in "sources that are working correctly" - - - No packages were found among the working sources. - "working sources" as in "sources that are working correctly" - - - This is a stable release of the Windows Package Manager. If you would like to try experimental features, please install a pre-release build. Instructions are available on GitHub at https://github.com/microsoft/winget-cli. - {Locked="https://github.com/microsoft/winget-cli"} - - - Windows Package Manager v{0} - {Locked="{0}"} Label displaying the product name and version. {0} is a placeholder replaced by the product version. - - - Ignore package versions in import file - - - The requested number of results must be between 1 and 1000. - - - Arguments to be passed on to the installer in addition to the defaults - - - A newer version was found, but the install technology is different from the current version installed. Please uninstall the package and install the newer version. - - - {0} package(s) have upgrades blocked because newer versions use a different install technology than the current installation. Uninstall each package, then install the newer version. - {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. - - - The install technology of the newer version specified is different from the current version installed. Please uninstall the package and install the newer version. - - - Windows Package Manager (Preview) v{0} - {Locked="{0}"} Label displaying the preview product name and pre-release version. {0} is a placeholder replaced by the product version. - - - Select the architecture - - - Upgrade packages even if their current version cannot be determined - - - List packages even if their current version cannot be determined. Can only be used with the --upgrade-available argument - {Locked="--upgrade-available"} - - - This package's version number cannot be determined. To upgrade it anyway, add the argument --include-unknown to your previous command. - {Locked="--include-unknown"} - - - {0} package(s) have version numbers that cannot be determined. Use --include-unknown to see all results. - {Locked="{0}","--include-unknown"} {0} is a placeholder that is replaced by an integer number of packages that do not have notated versions. - - - {0} package(s) are pinned and need to be explicitly upgraded. - {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages that require explicit upgrades. - - - The arguments provided can only be used with a query. - - - System Architecture: {0} - {Locked="{0}"} Label displayed for the system architecture. {0} is a placeholder replaced by the value of the system architecture (e.g. X64). - - - Retains all files and directories created by the package (portable) - - - Deletes all files and directories in the package directory (portable) - - - Press Enter to continue . . . - - - The value to rename the executable file (portable) - - - Prompts the user to press any key before exiting - - - The installer will request to run as administrator. Expect a prompt. - - - The installer cannot be run from an administrator context. - - - Path environment variable modified; restart your shell to use the new value. - - - Filters using the product code - - - A portable package with the same name but from a different source already exists; proceeding due to --force - {Locked="--force"} - - - The volume does not support reparse points - - - The specified filename is not a valid filename - - - Overwriting existing file: {0} - {Locked="{0}"} Warning message displayed to inform the user that an existing file is being overwritten. {0} is a placeholder replaced by the file system path. - - - No package selection argument was provided; see the help for details about finding a package. - - - Command line alias added: - - - Portable Links Directory (Machine) - - - Portable Links Directory (User) - - - Portable Package Root (User) - - - Portable Package Root - - - Portable Package Root (x86) - - - Portable install failed; Cleaning up... - - - Portable package has been modified; proceeding due to --force - {Locked="--force"} - - - Unable to remove Portable package as it has been modified; to override this check use --force - {Locked="--force"} - - - Files remain in install directory: {0} - {Locked="{0}"} Warning message displayed when files remain in install directory. {0} is a placeholder replaced by the directory path. - - - Purging install directory... - - - Cannot purge install directory as it was not created by WinGet - {Locked="WinGet"} - - - Related Link - - - Documentation: - - - Notes: {0} - {Locked="{0}"} Label displayed for installation notes. {0} is a placeholder replaced by installation notes. - - - Installation Notes: - - - A provided argument is not supported for this package - - - Failed to extract the contents of the archive - - - Nested installer file does not exist. Ensure the specified relative path of the nested installer matches: {0} - {Locked="{0}"} Error message displayed when nested installer file does not exist. {0} is a placeholder replaced by the nested installer file path. - - - Invalid relative file path to the nested installer; path points to a location outside of the install directory - - - No nested installers specified for this package - - - Only one nested installer can be specified for an archive installer unless it is a portable or font nested installer - - - Incompatible command line arguments provided - - - Lists only packages which have an upgrade available - - - The following packages have an upgrade available, but require explicit targeting for upgrade: - "require explicit targeting for upgrade" means that the package will not be upgraded with all others unless an extra flag is added, or the package is mentioned explicitly - - - Downloading - Label displayed while downloading an application installer. - - - The nested installer type is not supported - - - This installer is known to restart the terminal or shell - - - This package requires an install location - - - The following installers are known to restart the terminal or shell: - - - The following installers require an install location: - - - Specify the install root: - - - Do you want to proceed? - - - Agreements for - This will be followed by a package name, and then a list of license agreements - - - Install location is required by the package but it was not provided - - - Disable interactive prompts - Description for a command line argument, shown next to it in the help - - - Found an existing package already installed. Trying to upgrade the installed package... - - - Direct run the command and continue with non security related issues - Description for a command line argument, shown next to it in the help - - - Portable package from a different source already exists - - - Successfully extracted archive - - - Extracting archive... - - - Archive scan detected malware. To override this check use --ignore-local-archive-malware-scan - {Locked="--ignore-local-archive-malware-scan"} - - - Archive scan detected malware. Proceeding due to --ignore-local-archive-malware-scan - {Locked="--ignore-local-archive-malware-scan"} - - - Skips upgrade if an installed version already exists - - - A package version is already installed. Installation cancelled. - - - Cannot enable {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Cannot disable {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Add a new pin. A pin can limit the Windows Package Manager from upgrade a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. By default, a pinned package can be upgraded by mentioning it explicitly in the 'upgrade' command or by adding the '--include-pinned' flag to 'winget upgrade --all'. - {Locked{"'upgrade'"} Locked{"--include-pinned"} Locked{"winget upgrade --all"} - - - Add a new pin - - - Manage package pins with the sub-commands. A pin can limit the Windows Package Manager from upgrading a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. - - - Manage package pins - - - List all current pins, or full details of a specific pin. - - - List current pins - - - Remove a specific package pin. - - - Remove a package pin - - - Reset all existing pins. - - - Reset pins - - - Version to which to pin the package. The wildcard '*' can be used as the last version part - - - Block from upgrading until the pin is removed, preventing override arguments - - - Pin a specific installed version - - - Installed - Value used in a table to indicate that a package comes from the list of packages installed in the machine - - - Export settings as JSON - - - Export settings - - - User Settings - Label displayed for the file containing the user settings. - - - Settings file couldn't load. Using default values. - - - Select installed package scope filter (user or machine) - {Locked="user","machine"} This argument allows the user to select installed packages for just the user or for the entire machine. - - - Pin added successfully - - - There is already a pin for package {0} - {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to add a pin for a package that is already pinned. - - - A pin already exists for package {0}. Overwriting due to the --force argument. - {Locked="--force"}{Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. - - - A pin already exists for package {0}. Use the --force argument to overwrite it. - {Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. - - - Resetting all current pins. - - - Use the --force argument to reset all pins. The following pins would be removed: - {Locked="--force"} - - - Pin type - - - There is no pin for package {0} - {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to delete a pin for a package that is not pinned. - - - There are no pins configured. - Shown when listing or modifying existing pins if there are none. - - - Pins reset successfully - Shown after resetting (deleting) all the pins - - - Unable to open pin database. - Error message for when we cannot open the database containing package pins. - - - An argument was provided that can only be used for single package - - - Multiple mutually exclusive arguments provided: {0} - {Locked="{0}"} Error message shown when mutually incompatible command line arguments are used. {0} is a placeholder replaced by the arguments that cannot be specified together - - - Argument {0} can only be used with {1} - {Locked="{0}","{1}"} Error message shown when having an argument needs another to be present, but that is missing. {0} and {1} are replaced by the arguments. For example "Argument --include-unknown can only be used with --upgrade" - - - Disabled - As in enabled/disabled - - - Enabled - As in enabled/disabled - - - State - Header for a table listing the state (enabled/disabled) of Group Policies and Settings - - - The query used to search for a package - - - Package not found: {0} - {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns no results. {0} is a placeholder replaced by the package name or query. - - - Multiple packages found for: {0} - {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns more than one result. {0} is a placeholder replaced by the package name or query. - - - Search failed for: {0} - {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches fails. This message is for generic failures, we have more specific messages for when the search returns no results, or when it returns more than one result. {0} is a placeholder replaced by the package name or query. - - - {0} package(s) have a pin that needs to be removed before upgrade - {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade - - - The package cannot be upgraded using WinGet. Please use the method provided by the publisher for upgrading this package. - {Locked="WinGet"} - - - Upgrade packages even if they have a non-blocking pin - - - List packages even if they have a pin that prevents upgrade. Can only be used with the --upgrade-available argument - {Locked="--upgrade-available"} - - - Pin removed successfully - - - A newer version was found, but the package has a pin that prevents from upgrading it. - - - The package is pinned and cannot be upgraded. Use the 'winget pin' command to view and edit pins. Some pin types can be bypassed with the --include-pinned argument. - {Locked="winget pin","--include-pinned"} Error shown when we block an upgrade due to the package being pinned - - - {0} package(s) have pins that prevent upgrade. Use the 'winget pin' command to view and edit pins. Using the '--include-pinned' argument may show more results. - {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages - - - Ensures that the system matches the desired state as described by the provided configuration. May download/execute processors in order to achieve the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before applying them. - - - Configures the system into a desired state - - - Shows details of the provided configuration. By default, will not modify the system, but some options will cause files to be downloaded and/or loaded. - - - Shows details of a configuration - - - Checks that the system matches the desired state as described by the provided configuration. May download/execute processors in order to test the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before executing them. - - - Checks the system against a desired state - - - Validates a configuration file for correctness. - - - Validates a configuration file - - - The field '{0}' in the configuration file is the wrong type. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. - - - The path to the configuration file - - - The configuration file is invalid. - - - Configuration file version {0} is not known. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the version of the configuration file. - - - Accepts the configuration warning, preventing an interactive prompt - - - Apply - Indicates that this item is used to write state - - - Assert - Indicates that this item is used to check/assert the state rather than write to it - - - Dependencies:{0} - {Locked="{0}"} Label displaying a list of dependencies. {0} is replaced with a space separated list of identifiers referencing other items. - - - Some of the configuration was not applied successfully. - - - Failed to get detailed information about the configuration. - - - Inform - Indicates that this item is used to retrieve values for future use rather than writing them - - - Local - Used to indicate that the item is present on the device. - - - Module: {0} - {Locked="{0}"} Label displaying a module name. {0} is replaced with the name of the module from the user input file. - - - Module: {0} by {1} [{2}] - {Locked="{0}","{1}","{2}"} Label displaying module information. {0} is replaced by the module name. {1} is replaced by the module author. {2} is replaced by a string indicating the source of the module. - - - Settings: - Label for the values that are used as inputs for this item when applying state - - - Configuration successfully applied. - - - Unit successfully applied. - - - Another configuration is being applied to the system. This configuration will continue as soon as is possible... - - - You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services. - Legal approved. Do not change without approval. - - - Have you reviewed the configuration and would you like to proceed applying it to the system? - PM approved. - - - The configuration is empty. - - - The feature [{0}] was not found. - {Locked="{0}"} Error message displayed when a Windows feature was not found on the system. - - - Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'. - {Locked="--force"} - - - Reboot required to fully enable the Windows Feature(s); to override this check use '--force'. - "Windows Feature(s)" is the name of the options Windows features setting. - - - Failed to enable Windows Feature dependencies; proceeding due to --force - "Windows Feature(s)" is the name of the options Windows features setting. -{Locked="--force"} - - - Enabling [{0}]... - {Locked="{0}"} Message displayed to the user regarding which Windows Feature is being enabled. - - - Failed to enable [{0}] feature: {1} - {Locked="{0}","{1}"} An error when enabling a Windows Feature. {0} is a placeholder for the name of the Windows Feature. -{1} is a placeholder for the unrecognized error code. - - - Reboot required to fully enable the Windows Feature(s); proceeding due to --force - "Windows Feature(s)" is the name of the options Windows features setting. - - - Waiting for another install/uninstall to complete... - - - Pinned version - Table header for the version to which a package is pinned; meaning it should not update from that version. - - - <See the log file for additional details> - The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable. - - - Retrieving configuration details - - - Initializing configuration system - - - Reading configuration file - - - The system is not in the desired state asserted by the configuration. - - - This configuration unit failed for an unknown reason: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to the configuration: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed while attempting to get the current system state. - - - The configuration unit failed while attempting to apply the desired state. - - - The configuration unit failed while attempting to test the current system state. - - - The configuration unit failed due to an internal error: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to a precondition not being valid: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to the system state: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed while attempting to run: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration contains the identifier `{0}` multiple times. - {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. - - - The dependency `{0}` was not found within the configuration. - {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. - - - This configuration unit was manually skipped. - - - The module for the configuration unit is available in multiple locations with the same version. - - - Loading the module for the configuration unit failed. - - - Multiple matches were found for the configuration unit; specify the module to select the correct one. - - - The configuration unit could not be found. - - - The configuration unit was not in the module as expected. - - - This configuration unit was not run because a dependency failed or was not run. - - - This configuration unit was not run because an assert failed or was false. - - - The configuration unit returned an unexpected result during execution. - - - This configuration unit was not run for an unknown reason: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The field '{0}' has an invalid value: {1} - {Locked="{0}","{1}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. {1} is a placeholder for the invalid value. - - - The field '{0}' is missing or empty. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the expected field name from the file. - - - See line {0}, column {1} in the file. - {Locked="{0}","{1}"} Indicates the file location of the error, {0} and {1} are placeholders for numbers of the line and column, respectively. - - - Cancelling operation - - - Install the stub package for AppInstaller - - - Install the full package for AppInstaller - - - Enable extended features. Requires store access. - - - Option '--enable' and '--disable' cannot be used with other arguments. - {Locked="--enable", "--disable"} - - - Enabling extended features. Requires store access. - - - Extended features are not enabled. Run `winget configure --enable` to enable them. - {Locked="winget configure --enable"} - - - Extended features are enabled. - - - Disable extended features. Requires store access. - - - Disabling extended features. Requires store access. - - - Extended features are disabled. - - - Skips processing package dependencies and Windows features - - - Installs only the dependencies of the package - - - Installing dependencies only. The package itself will not be installed. - - - Dependencies skipped. - - - Failed to refresh PATH variable for process. Subsequent installs that depend on changes to the PATH variable may fail. - {Locked="PATH"} - - - Some of the configuration units failed while testing their state. - - - System is in the described configuration state. - - - Configuration state was not tested. - - - System is not in the described configuration state. - - - Unexpected test result: {0} - {Locked="{0}"} Error message. {0} will be replaced with the unexpected value (a number). - - - Have you reviewed the configuration and would you like to proceed verifying it against the system? - - - The configuration file is not a valid YAML file. - {Locked="YAML"} YAML is a file format name. - - - This configuration unit is part of a dependency cycle. - - - Validation found no issues. - - - The configuration unit is only available as a prerelease, but it is not marked that way in the configuration. Add `allowPrerelease: true` to the `directives`. - {Locked="allowPrerelease: true","directives"} These are values in the configuration file that are not localized. - - - The configuration unit was found locally, but could not be found in any configured catalog. Ensure that it is present on any system before applying the configuration. - - - The configuration unit is not available publicly; ensure that anyone who will use this configuration has access to it. - - - The module was not provided. Specifying the module improves performance and prevents future name collisions. - - - Downloads the installer from the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, download command will download the appropriate installer to the user's Downloads folder. - - - Downloads the installer from a given package - - - Directory where the installers are downloaded to - - - Downloading dependencies: - - - Installer downloaded: {0} - {Locked="{0}"} Full path of the downloaded installer. - - - Select the installer type - - - Installer Downloads - - - Installer is prohibited from being downloaded for later offline installation. - - - `--module-path allusers` requires administrator privileges to execute. - {Locked="--module-path allusers"} - - - Specifies the location on the local computer to store modules. Default %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules - {Locked="%LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules"} - - - `--module-path` value must be `currentuser`, `allusers`, `default`, or an absolute path. - {Locked="{--module-path}, {currentuser}, {allusers}, {default}} - - - Enable Windows Package Manager Configuration - - - Retrieve information about errors. Given a number, the output will contain details about the error, including the symbol name if it is a WinGet specific error. Given a string, the WinGet specific errors are searched for this value. - {Locked="WinGet"} - - - Get information on errors - - - A value to search within the error information - - - Generate error code Markdown file - Description of an argument that causes a Markdown file to be generated for all winget error codes. - - - The --output argument cannot be combined with an input value. - {Locked="--output"} - - - An input value or --output must be provided. - {Locked="--output"} - - - The given number is too large to be an HRESULT. - - - Unknown error code - - - Internal Error - - - Invalid command line arguments - - - Executing command failed - - - Opening manifest failed - - - Cancellation signal received - - - Running ShellExecute failed - - - Cannot process manifest. The manifest version is higher than supported. Please update the client. - - - Downloading installer failed - - - Cannot write to index; it is a higher schema version - - - The index is corrupt - - - The configured source information is corrupt - - - The source name is already configured - - - The source type is invalid - - - The MSIX file is a bundle, not a package - - - Data required by the source is missing - - - None of the installers are applicable for the current system - - - The installer file's hash does not match the manifest - - - The source name does not exist - - - The source location is already configured under another name - - - No packages found - - - No sources are configured - - - Multiple packages found matching the criteria - - - No manifest found matching the criteria - - - Failed to get Public folder from source package - - - Command requires administrator privileges to run - - - The source location is not secure - - - The Microsoft Store client is blocked by policy - - - The Microsoft Store app is blocked by policy - - - The feature is currently under development. It can be enabled using `winget settings`. - {Locked="winget settings"} - - - Failed to install the Microsoft Store app - - - Failed to perform auto complete - - - Failed to initialize YAML parser - - - Encountered an invalid YAML key - - - Encountered a duplicate YAML key - - - Invalid YAML operation - - - Failed to build YAML doc - - - Invalid YAML emitter state - - - Invalid YAML data - - - LibYAML error - - - Manifest validation succeeded with warning - - - Manifest validation failed - - - Manifest is invalid - - - No applicable update found - - - An upgrade is available but uses a different install technology than the current installation - - - winget upgrade --all completed with failures - - - Installer failed security check - - - Download size does not match expected content length - - - Uninstall command not found - - - Running uninstall command failed - - - ICU break iterator error - - - ICU casemap error - - - ICU regex error - - - Failed to install one or more imported packages - - - Could not find one or more requested packages - - - Json file is invalid - - - The source location is not remote - - - The configured rest source is not supported - - - Invalid data returned by rest source - - - Operation is blocked by Group Policy - - - Rest API internal error - - - Invalid rest source url - - - Unsupported MIME type returned by rest API - - - Invalid rest source contract version - - - The source data is corrupted or tampered - - - Error reading from the stream - - - Package agreements were not agreed to - - - Error reading input in prompt - - - The search request is not supported by one or more sources - - - The rest API endpoint is not found. - - - Failed to open the source. - - - Source agreements were not agreed to - - - Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. - - - Missing resource file - - - Running MSI install failed - - - Arguments for msiexec are invalid - - - Failed to open one or more sources - - - Failed to validate dependencies - - - One or more package is missing - - - Invalid table column - - - The upgrade version is not newer than the installed version - - - Upgrade version is unknown and override is not specified - - - ICU conversion error - - - Failed to install portable package - - - Volume does not support reparse points - - - Portable package from a different source already exists. - - - Unable to create symlink. Path points to a directory. - - - The installer cannot be run from an administrator context. - - - Failed to uninstall portable package - - - Failed to validate DisplayVersion values against index. - - - One or more arguments are not supported. - - - Embedded null characters are disallowed for SQLite - - - Failed to find the nested installer in the archive. - - - Failed to extract archive. - - - Invalid relative file path to nested installer provided. - - - The server certificate did not match any of the expected values. - - - Install location must be provided. - - - Archive malware scan failed. - - - Found at least one version of the package installed. - - - A pin already exists for the package. - - - There is no pin for the package. - - - Unable to open the pin database. - - - One or more applications failed to install - - - One or more applications failed to uninstall - - - One or more queries did not return exactly one match - - - The package has a pin that prevents upgrade. - - - The package currently installed is the stub package - - - Application shutdown signal received - - - Failed to download package dependencies. - - - Failed to download package. Download for offline installation is prohibited. - - - A required service is busy or unavailable. Try again later. - - - The guid provided does not correspond to a valid resume state. - - - The current client version did not match the client version of the saved state. - - - The resume state data is invalid. - - - Unable to open the checkpoint database. - - - Exceeded max resume limit. - - - Invalid authentication info. - - - Authentication method not supported. - - - Authentication failed. - - - Authentication failed. Interactive authentication required. - - - Authentication failed. User cancelled. - - - Authentication failed. Authenticated account is not the desired account. - - - Application is currently running. Exit the application then try again. - - - Another installation is already in progress. Try again later. - - - One or more file is being used. Exit the application then try again. - - - This package has a dependency missing from your system. - - - There's no more space on your PC. Make space, then try again. - - - There's not enough memory available to install. Close other applications then try again. - - - This application requires internet connectivity. Connect to a network then try again. - - - This application encountered an error during installation. Contact support. - - - Restart your PC to finish installation. - - - Installation failed. Restart your PC then try again. - - - Your PC will restart to finish installation. - - - You cancelled the installation. - - - Another version of this application is already installed. - - - A higher version of this application is already installed. - - - Organization policies are preventing installation. Contact your admin. - - - Failed to install package dependencies. - - - Application is currently in use by another application. - - - Invalid parameter. - - - Package not supported by the system. - - - The installer does not support upgrading an existing package. - - - Installation failed with a custom installer error. - - - The Apps and Features Entry for the package could not be found. - - - The install location is not applicable. - - - The install location could not be found. - - - The hash of the existing file did not match. - - - File not found. - - - The file was found but the hash was not checked. - - - The file could not be accessed. - - - The configuration file is invalid. - - - The YAML syntax is invalid. - - - A configuration field has an invalid type. - - - The configuration has an unknown version. - - - An error occurred while applying the configuration. - - - The configuration contains a duplicate identifier. - - - The configuration is missing a dependency. - - - The configuration has an unsatisfied dependency. - - - An assertion for the configuration unit failed. - - - The configuration was manually skipped. - - - The user declined to continue execution. - - - The dependency graph contains a cycle which cannot be resolved. - - - The configuration has an invalid field value. - - - The configuration is missing a field. - - - Some of the configuration units failed while testing their state. - - - Configuration state was not tested. - - - The configuration unit was not installed. - - - The configuration unit could not be found. - - - Multiple matches were found for the configuration unit; specify the module to select the correct one. - - - The configuration unit failed while attempting to get the current system state. - - - The configuration unit failed while attempting to test the current system state. - - - The configuration unit failed while attempting to apply the desired state. - - - The module for the configuration unit is available in multiple locations with the same version. - - - Loading the module for the configuration unit failed. - - - The configuration unit returned an unexpected result during execution. - - - A unit contains a setting that requires the config root. - - - Operation is not supported by the configuration processor. - - - Unavailable - - - Successfully enabled Windows Features dependencies - - - Loading the module for the configuration unit failed because it requires administrator privileges to run. - - - A unit contains a setting that requires the config root. - - - Loading the module for the configuration unit failed because it requires administrator privileges to run. - - - Resumes execution of a previously saved command by passing in the unique identifier of the saved command. This is used to resume an executed command that may have been terminated due to a reboot. - - - Resumes execution of a previously saved command. - - - The unique identifier of the saved state to resume - - - Resuming the state from a different client version is not supported: {0} - {Locked= "{0}"} Message displayed to inform the user that the client version of the resume state does not match the current client version. {0} is a placeholder for the client version that created the resume state. - - - The resume state does not exist: {0} - {Locked="{0}"} Error message displayed when the user provides a guid that does not correspond to a valid saved state. {0} is a placeholder replaced by the provided guid string. - - - No data found in the resume state. - - - This command does not support resuming. - - - Allows a reboot if applicable - - - Initiating reboot to complete operation... - - - Failed to initiate a reboot. - - - Resume operation exceeds the allowable limit of {0} resume(s). To manually resume, run the command `{1}`. - {Locked="{0}", "{1}"} {0} is a placeholder that is replaced by an integer number of the number of allowed resumes. {1} is a placeholder for the command to run to perform a manual resume. - - - Ignore the limit on resuming a saved state - - - Uri scheme not supported: {0} - {Locked="{0}"} Error message displayed when the provided uri is not supported. {0} is a placeholder replaced by the provided uri. - - - Uri not well formed: {0} - {Locked="{0}"} Error message displayed when the provided uri is not well formed. {0} is a placeholder replaced by the provided uri. - - - Failed to parse {0} configuration unit settings content or settings content is empty. - {Locked="{0}"} {0} is a placeholder replaced by the input winget configure resource unit type. - - - {0} configuration unit is missing required argument: {1} - {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. - - - {0} configuration unit is missing recommended argument: {1} - {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. - - - WinGetSource configuration unit conflicts with a known source: {0} - {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. - - - WinGetSource configuration unit asserts on a third-party source: {0} - {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. - - - WinGetPackage declares both UseLatest and Version. Package Id: {0} - {Locked="WinGetPackage,UseLatest,Version,{0}"} - - - WinGetPackage configuration unit asserts on a package from third-party source. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package depends on a third-party source not previously configured. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package depends on a 3rd party source. It is recommended to declare the dependency in uni dependsOn section. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,dependsOn,{0},{1}"} - - - WinGetPackage configuration unit package cannot be validated. Source open failed. Package Id: {0}; Source: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package cannot be validated. Package not found. Package Id: {0} - {Locked="WinGetPackage,{0}"} - - - WinGetPackage configuration unit package cannot be validated. More than one package found. Package Id: {0} - {Locked="WinGetPackage,{0}"} - - - WinGetPackage configuration unit package cannot be validated. Package version not found. Package Id: {0}; Version {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package specified with a specific version while only one package version is available. Package Id: {0}; Version: {1} - {Locked="WinGetPackage,{0},{1}"} - - - WinGetPackage configuration unit package cannot be validated. Package Id: {0} - {Locked="WinGetPackage,{0}"} - - - Specify authentication window preference (silent, silentPreferred, or interactive) - {Locked="silent","silentPreferred","interactive"} This argument allows the user to select authentication window popup behavior. - - - Specify the account to be used for authentication - - - Failed to add source. This WinGet version does not support the source's authentication method. Try upgrading to latest WinGet version. - {Locked="WinGet"} - - - The {0} source requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the source for access authorization. - {Locked="{0}"} - - - Repairs the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. - id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. - - - Repairs the selected package - - - The repair command for this package cannot be found. Please reach out to the package publisher for support. - - - The installer technology in use does not match the version currently installed. - - - Repair command not found. - - - The installer technology in use doesn't support repair. - - - Repair operation completed successfully. - - - Repair abandoned - - - Starting package repair... - - - Failed to repair Microsoft Store package. Error code: {0} - {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to repair. {0} is a placeholder replaced by an error code. - - - Repair operation failed. - - - Repair operation is not applicable. - - - No matching package versions are available from the configured sources. - - - The current system configuration does not support the repair of this package. - - - The installer technology in use does not support repair. - - - The package installed for user scope cannot be repaired when running with administrator privileges. - - - Repair operations involving administrator privileges are not permitted on packages installed within the user scope. - - - Repair failed with exit code: {0} - {Locked="{0}"} Error message displayed when an attempt to repair an application package fails. {0} is a placeholder replaced by an error code. - - - The SQLite connection was terminated to prevent corruption. - - - Uninstall all versions - - - The version to act upon - - - Multiple versions of this package are installed. Either refine the search, pass the `--version` argument to select one, or pass the `--all-versions` flag to uninstall all of them. - {Locked="--version,--all-versions"} - - - Enable Windows Package Manager proxy command line options - Describes a Group Policy that can enable the use of the --proxy option to set a proxy - - - Enable Windows Package Manager Configuration processor path - Describes a Group Policy that can enable the use of the --processor-path option in configuration commands - - - Set a proxy to use for this execution - - - Disable the use of proxy for this execution - - - Disables the progress bar and spinner - - - Cannot reset {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Reset admin setting '{0}'. - {Locked="{0}"} Message displayed after the user resets an admin setting to its default value. Reset is used as verb in past tense. {0} is a placeholder replaced by the setting name. - - - Cannot set {0}. This setting is controlled by policy. For more information contact your system administrator. - {Locked="{0}"} The value will be replaced with the feature name - - - Set admin setting '{0}' to '{1}'. - {Locked="{0}"} Message displayed after the user sets the value of an admin setting. Set is used as a verb in past tense. {0} is a placeholder replaced by the setting name. {1} is a placeholder replaced - - - Name of the setting to modify - - - Value to set for the setting. - - - Resets an admin setting to its default value. - - - Resets an admin setting to its default value. - - - Sets the value of an admin setting. - - - Sets the value of an admin setting. - - - Excludes a source from discovery unless specified - - - Excludes a source from discovery (true or false) - - - Explicit - Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. - - - Trust level of the source (none or trusted) - - - Trust Level - - - Failed to get Microsoft Store package catalog. - - - No applicable Microsoft Store package found from Microsoft Store package catalog. - - - Failed to get Microsoft Store package download information. - - - No applicable Microsoft Store package found for download. - - - Failed to retrieve Microsoft Store package license. - - - No applicable Microsoft Store package found. - - - Successfully verified Microsoft Store package hash - - - Microsoft Store package hash mismatch - - - Microsoft Store package downloaded: {0} - {Locked="{0}"} Full path of the downloaded package. - - - Microsoft Store package download failed: {0} - {Locked="{0}"} Package name. - - - Microsoft Store package download completed - - - Downloading main packages from Microsoft Store... - - - Downloading dependency packages from Microsoft Store... - - - Retrieving Microsoft Store package download information - - - Failed to retrieve Microsoft Store package download information - - - Retrieving Microsoft Store package license - - - Microsoft Store package license saved: {0} - {Locked="{0}"} License file full path. - - - Failed to retrieve Microsoft Store package license - - - Microsoft Store package download does not support --rename argument. Microsoft Store package will use names provided by Microsoft Store catalog. - {Locked="--rename"} - - - Microsoft Store package download requires Microsoft Entra Id authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with Microsoft services for access authorization. For Microsoft Store package licensing, the Microsoft Entra Id account needs to be a member of Global Administrator, User Administrator, or License Administrator. - {Locked="Global Administrator,User Administrator,License Administrator"} - - - Skips retrieving Microsoft Store package offline license - - - Select the target platform - - - Adding configuration file: {0} - {Locked="{0}"} - - - Successfully exported - - - Getting configuration settings... - - - At least --packageId and/or --module with --resource must be provided. Or use --all to export all package configurations. - {Locked="--packageId,--module, --resource, --all"} - - - Arguments --packageId, --module and --resource cannot be used with --all. - {Locked="--packageId,--module, --resource, --all"} - - - Exports configuration resources to a configuration file. When used with --all, exports all package configurations. When used with --packageId, exports a WinGetPackage resource of the given package id. When used with --module and --resource, gets the settings of the resource and exports it to the configuration file. If the output configuration file already exists, appends the exported configuration resources. - {Locked="WinGetPackage,--packageId,--module, --resource"} - - - Exports configuration resources to a configuration file. - - - The module of the resource to export. - - - The package identifier to export. - - - The configuration resource to export. - - - Exports all package configurations. - - - The configuration unit failed getting its properties. - - - Failed exporting configuration. - - - Configure {0} - {Locked="{0}"} - - - Install {0} - {Locked="{0}"} - - - Some of the data present in the configuration file was truncated for this output; inspect the file contents for the complete content. - - - <this value has been truncated; inspect the file contents for the complete text> - Keep some form of separator like the "<>" around the text so that it stands out from the preceding text. - - - Shows the high level details for configurations that have been applied to the system. This data can then be used with `configure` commands to get more details. - {Locked="`configure`"} - - - Shows configuration history - - - There are no configurations in the history. - - - Select items from history - - - No single configuration could be found that matched the provided data. Provide either the full name or part of the identifier that unambiguously matches the desired configuration. - - - Remove the item from history - - - First Applied - Column header for date values indicating when a configuration was first applied to the system. - - - Identifier - - - Name - - - Origin - - - Path - - - The specified configuration could not be found. - - - Completed - The state of processing an item. - - - In progress - The state of processing an item. - - - Pending - The state of processing an item. - - - Unknown - The state of processing an item. - - - Monitor configuration status. - As in "to monitor the status of a configuration being applied". - - - Completed - The state of processing an item. - - - In progress - The state of processing an item. - - - Pending - The state of processing an item. - - - Skipped - The state of processing an item. - - - Unknown - The state of processing an item. - - - Apply Started - When the configuration application started. - - - Apply Ended - When the configuration application ended. - - - Result - - - Details - - - State - The state of processing an item. - - - Unit - - - The package is not compatible with the current Windows version or platform. - - - Suppress showing initial configuration details when possible - - - Multiple Microsoft Store packages may be downloaded targeting different platforms and architectures. --platform, --architecture can be used to filter the packages. - {Locked="--platform--architecture"} - - - Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account is not a member of Global Administrator, User Administrator, or License Administrator. Use --skip-license to skip retrieving Microsoft Store package license. - {Locked="Global Administrator,User Administrator,License Administrator,--skip-license"} - - - The Microsoft Store package does not support download. - - - The Microsoft Store package does not support download. - - - Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege. - - - Parameter cannot be passed across integrity boundary. - - - Downloaded zero byte installer; ensure that your network connection is working properly. - - - Downloaded zero byte installer; ensure that your network connection is working properly. - - - Manage fonts - - - Manage fonts with sub-commands. Fonts can be installed, upgraded, or uninstalled similar to packages. - - - Family - Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. - - - Faces - Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. - - - Filter results by family name - 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. - - - Face - A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. - - - Paths - - - No installed font found matching input criteria. - - - List installed fonts - - - List all installed fonts, all font files, or full details of a specific font. - - - 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. - - - Specify the path to the configuration processor - - - Custom processor path: - Displayed as a warning header when a custom --processor-path argument is provided. - - - Hash: {0} - {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. - - - Type: App execution alias - Indicates the processor path is an app execution alias (MSIX package link). - - - Path: {0} - {Locked="{0}"} {0} is the file system path provided by the user. - - - Signed By: {0} - {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. - - - Signed By: (unsigned) - Indicates the custom processor executable does not have an Authenticode signature. - - - The integrity check failed for the custom DSC processor path. The path may have been modified. - Error when the server-side hash of the processor path does not match the client-computed hash. - - - DSC v3 resource commands - DSC stands for "Desired State Configuration". It should already have a locked translation. - - - The sub-commands here implement Desired State Configuration (DSC) v3 resources for configuring WinGet and packages. - {Locked="WinGet"} - - - Get the resource state - - - Set the resource state - - - Describe required state changes - - - Test the resource state - - - Delete the resource state - - - Get all state instances - - - Validate group contents - - - Resolve external state - - - Run the adapter - - - Get the resource schema - - - Get the resource manifest - - - Manage package state - - - Manage packages through WinGet. - {Locked="WinGet"} - - - Indicates whether an instance should or does exist. - - - Indicates whether an instance is in the desired state. - - - The identifier of the package. - - - The source of the package. - - - The version of the package. - - - The method for matching the identifier with a package. - - - Indicate that the latest available version of the package should be installed. - - - The install mode to use if needed. - - - The target scope of the package. - - - Indicates whether to accept agreements for sources and packages. - - - Manage user settings file - - - Manage the user settings of WinGet. - {Locked="WinGet"} - - - The settings json file content. - - - The action used to apply the settings. - - - Value is logged for correlation - - - Manage source configuration - - - Manage the sources of WinGet. - {Locked="WinGet"} - - - The name to use for the source. - - - The argument for the source. - - - The type of the source. - - - The trust level of the source. - - - Whether the source is included when calls don't specify a source. - - - Applying configuration unit... - - - Exporting configuration unit... - - - Getting configuration unit processors... - - - Ensure required module for export [{0}] - {Locked="{0}"} - - - Failed to test or acquire required module. Related settings will not be exported. - - - Export [{0}] - {Locked="{0}"} - - - Failed to export the resource. - - - Failed to get unit processors. Individual package settings will not be exported. - - - Directory where the results are to be written - - - Desired State Configuration package not found on the system. Installing the package... - - - Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through --processor-path argument. - {Locked="dsc.exe","--processor-path"} - - - An object containing the administrator settings and their values. - - - Manage administrator settings - - - Manage the administrator settings of WinGet. - {Locked="WinGet"} - - - Resets all admin settings - - - All admin settings reset. - - - MCP information - MCP stands for Model Context Protocol and should probably remain as-is - - - MCP (Model Context Protocol) information for the Windows Package Manager. - - - To manually configure the Windows Package Manager MCP server with your MCP client, use the following JSON fragment in the `servers` object: - {Locked="`servers`"} -MCP stands for Model Context Protocol and should probably remain as-is. -An unlocalized JSON fragment will follow on another line. - - - Enable Windows Package Manager MCP Server - - - Target OS version - - - Failed installing one or more fonts. - - - Font file is not supported and cannot be installed. - - - One or more fonts in the font package is not supported and cannot be installed. - - - Font install failed; cleaning up. - - - Font already installed. - - - Package Id - - - WinGet Supported - - - Title - - - Font file not found. - - - Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. - - - Font validation failed. - - - Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. - - - Show font file detailed information. - - - Validation of the font package failed. - - - Rollback attempt for failed font install was unsuccessful. A restart may be required to successfully uninstall the font. - - - Font package uninstall failed. This is often due to the fonts being in use by the system or an application. Uninstall may be successful after restarting your computer. - - - OK - "OK" means the font is in a good healthy state - - - Corrupt - "Corrupt" refers to an install that is in a corrupted or bad state, and needs repair. - - - Status - - - Unknown - - - Font package is already installed. - - - Show detailed information about packages - Providing this argument causes the CLI to output additional details about installed application packages. - - - Channel: - Precedes a string value that names the delivery channel for the software package (ex. stable, beta). - - - Local Identifier: - Precedes a value that is the unique identifier for the installed package on the local system. - - - Package Family Name: - Precedes a value that is the APPX/MSIX package family name of the installed package. - - - Product Code: - Precedes a value that is the Add/Remove Programs identifier in the registry. This is also the Product Code value as defined in MSI installers. - - - Upgrade Code: - Precedes a value that is the MSI Upgrade Code for the installed package. - - - Installed Scope: - Precedes a value that is the scope of the installation of the package (ex. user, machine). - - - Installed Architecture: - Precedes a value that is the installed architecture of the package (ex. x86, x64, ARM64). - - - Installed Locale: - Precedes a value that is the locale of the installed package. - - - Installed Location: - Precedes a value that is the directory path to the installed package. - - - Origin Source: - Precedes a value that names the package source where the installed package originated from. - - - Available Upgrades: - Precedes a list of package upgrades available for the installed package. - - - Installer Category: - Precedes a value that indicates the category of the installer for the installed package (ex. exe, msi, msix). - - - Old Value - Column title for listing edit changes. - - - New Value - Column title for listing the new value. - - - Priority; higher numbers first - This argument sets the numerical priority of the source. Higher values will be first in priority. - - - Priority - Label for the priority of the source with respect to other sources. Higher values come first in the order. - - - The priority of the source. Higher values are sorted first in the order. - - - Results have been filtered to the highest matched source priority. - A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. - - - The DSC processor hash provided does not match hash of the target file. - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Adjoined alias is not a flag: '{0}' + {Locked="{0}"} Error message displayed when the user provides an adjoined alias that is not a flag argument. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). + + + Adjoined flag alias not found: '{0}' + {Locked="{0}"} Error message displayed when the user provides an adjoined flag alias argument that was not found. {0} is a placeholder replaced by the user input argument (e.g. '-ab'). + + + The following arguments are available: + Message displayed to inform the user about the available command line arguments. + + + The following command aliases are available: + Message displayed to inform the user about the available command line alias arguments. + + + The following commands are available: + Title displayed to inform the user about the available commands. + + + Available + As in "a new version is available to upgrade to". + + + The following options are available: + Message displayed to inform the user about the available options. + + + The following sub-commands are available: + Message displayed to inform the user about the available nested commands that run in context of the selected command. + + + {0} upgrades available. + {Locked="{0}"} Message displayed to inform the user about available package upgrades. {0} is a placeholder replaced by the number of package upgrades. + + + Use the specified channel; default is general audience + + + command + Label displayed for a command to give the software. + + + Filter results by command + Description message displayed to inform the user about filtering the search results by a package command. + + + The full command line for completion + + + This command requires administrator privileges to execute. + + + This command can be used to request context sensitive command line completion. The command line, cursor position, and word to be completed are passed in. The output is a set of potential values based on the inputs, with one possible value per line. + + + Enables context sensitive command line completion + + + Show no more than specified number of results (between 1 and 1000) + + + Done + Label displayed when an operation completes or is done executing. + + + Find package using exact match + Description message displayed to inform the user about finding an application package using an exact matching criteria. + + + Experimental argument for demonstration purposes + + + This command is an example on how to implement an experimental feature. To turn on go to 'winget settings' and enable experimentalCmd or experimentalArg features. + {Locked="winget settings"} + + + Experimental feature example + + + Found a positional argument when none was expected: '{0}' + {Locked="{0}"} Error message displayed when the user provides an extra positional argument when none was expected. {0} is a placeholder replaced by the user's extra argument input. + + + This feature is a work in progress, and may be changed dramatically or removed altogether in the future. To enable it, edit your settings ('winget settings') to include the experimental feature: '{0}' + {Locked="winget settings","{0}"}. Error message displayed when the user uses an experimental feature that is disabled. {0} is a placeholder replaced by the experimental feature name. + + + Shows the status of experimental features. Experimental features can be turned on via 'winget settings'. + {Locked="winget settings"} + + + Shows the status of experimental features + + + Disabled + Status value in the 'winget features' table. Indicates an experimental feature is turned off. Paired with 'Enabled'. + + + Enabled + Status value in the 'winget features' table. Indicates an experimental feature is active/turned on. Paired with 'Disabled'. + + + Feature + Column header in the 'winget features' output table. 'Feature' is noun - an experimental software capability. Do NOT translate as a verb. + + + Link + Column header in the 'winget features' output table. 'Link' is a noun — a URL pointing to documentation for the feature. Do NOT translate as a verb. + + + The following experimental features are in progress. +They can be configured through the settings file 'winget settings'. + {Locked="winget settings"} + + + Property + Column header in the 'winget features' output table. 'Property' refers to a named attribute/setting of the feature. + + + Status + Column header in the 'winget features' output table. Shows whether an experimental feature is Enabled or Disabled. + + + File to be hashed + + + Flag argument cannot contain adjoined value: '{0}' + {Locked="{0}"} Error message displayed when the user provides a flag argument containing an unexpected adjoined value. {0} is a placeholder replaced by the user input. + + + Computes the hash of a local file, appropriate for entry into a manifest. It can also compute the hash of the signature file of an MSIX package to enable streaming installations. + + + Helper to hash installer files + + + Shows help about the selected command + + + For more details on a specific command, pass it the help argument. + + + More help can be found at: {0} + {Locked="{0}"} Message displayed to inform the user about a link where they can learn more about the subject context. {0} is a placeholder replaced by a website address. + + + Filter results by id + + + Suppresses warning outputs + + + This application is licensed to you by its owner. + + + Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. + + + This package is provided through Microsoft Store. WinGet may need to acquire the package from Microsoft Store on behalf of the current user. + {Locked="WinGet"} + + + Installs the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, install command will check package installed status and try to perform an upgrade if applicable. Override with --force to perform a direct install. + {Locked="--force"}; id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Installs the given package + + + Installer hash does not match; this cannot be overridden when running as admin + + + Installer hash does not match; proceeding due to --ignore-security-hash + {Locked="--ignore-security-hash"} + + + Installer hash does not match; to override this check use --ignore-security-hash + {Locked="--ignore-security-hash"} + + + Successfully verified installer hash + + + Successfully installed + + + Starting package install... + + + Ignore the installer hash check failure + + + Ignore the malware scan performed as part of installing an archive type package from local manifest + + + Request interactive installation; user input may be needed + + + Argument alias was not recognized for the current command: '{0}' + {Locked="{0}"} Error message displayed when the user provides a command line argument alias that was not recognized for a selected command. {0} is a placeholder replaced by the user's argument alias input (e.g. '-a'). + + + Invalid argument specifier: '{0}' + {Locked="{0}"} Error message displayed when the user provides an invalid argument specifier. {0} is a placeholder replaced by an argument specifier (e.g. '-'). + + + Argument name was not recognized for the current command: '{0}' + {Locked="{0}"} Error message displayed when the user provides an unrecognized command line argument name for the selected command. {0} is a placeholder replaced by the user's argument name input (e.g. '--example'). + + + WinGet Directories + {Locked="WinGet"} Header for a table detailing the directories WinGet uses for key operations like logging and portable installs + + + Locale to use (BCP47 format) + {Locked="BCP47"} + + + License Agreement + + + Links + Links to different webpages + + + The list command displays the packages installed on the system, as well as whether an upgrade is available. Additional options can be provided to filter the output, much like the search command. + {Locked="list","search"} + + + Display installed packages + + + Location to install to (if supported) + + + Log location (if supported) + + + © 2026 Microsoft. All rights reserved. + + + Homepage + The primary webpage for the software + + + The path to the manifest of the package + + + Manifest validation failed. + + + Manifest validation succeeded. + + + Manifest validation succeeded with warnings. + + + Argument value required, but none found: '{0}' + {Locked="{0}"} Error message displayed when the user does not provide a required command line argument value. {0} is a placeholder replaced by the argument name. + + + Filter results by moniker + + + Input file will be treated as msix; signature hash will be provided if signed + + + Failed to calculate MSIX signature hash. + + + Failed to install or upgrade Microsoft Store package because the specific app is blocked by policy + + + Failed to install or upgrade Microsoft Store package. Error code: {0} + {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to install or upgrade. {0} is a placeholder replaced by an error code. + + + Failed to install or upgrade Microsoft Store package because Microsoft Store client is blocked by policy + + + Verifying/Requesting package acquisition... + + + Multiple installed packages found matching input criteria. Please refine the input. + + + Multiple packages found matching input criteria. Please refine the input. + + + Filter results by name + + + No applicable installer found; see logs for more details. + + + There are currently no experimental features available. + + + No installed package found matching input criteria. + + + No package found matching input criteria. + + + Disables VirtualTerminal display + {Locked="VirtualTerminal"} + + + Open the default logs location + + + options + Options to change how a command works + + + Override arguments to be passed on to the installer + + + Package: {0} + {Locked="{0}"} Label displayed for a software package. {0} is a placeholder replaced by the software package name. + + + Oops, we forgot to do this... + + + The position of the cursor within the command line + + + Privacy Statement + + + The query used to search for a package + + + Progress display a rainbow of colors + + + Required argument not provided: '{0}' + {Locked="{0}"} Error message displayed when the user does not provide a required command line argument. {0} is a placeholder replaced by an argument name. + + + Progress display as the default color + + + Searches for packages from configured sources. + + + Find and show basic info of packages + + + Id + Abbreviation of Identifier. + + + Match + Column header in the 'winget search' output table. 'Match' is a noun describing how the result matched the search query (e.g., Exact, Substring). Do NOT translate as a verb. + + + Name + + + Source + Column header in the 'winget search' output table. 'Source' refers to the WinGet package repository the result came from (e.g., 'winget', 'msstore'). Not 'source code'. + + + additional entries truncated due to result limit + + + Version + + + The following failures were found validating the settings: + + + Open settings in the default json text editor. If no editor is configured, opens settings in notepad. For available settings see https://aka.ms/winget-settings This command can also be used to set administrator settings by providing the --enable or --disable arguments + {Locked="--enable"} {Locked="--disable"} + + + Open settings or set administrator settings + + + Unexpected error while loading settings. Please verify your settings by running the 'settings' command. + {Locked="'settings'"} + + + Channel + Label in the 'winget show' output. 'Channel' refers to a software release channel (e.g., stable, preview, beta). Not a TV or media channel. + + + Shows information on a specific package. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Shows information about a package + + + Version + + + Request silent installation + + + Only the single character alias can occur after a single -: '{0}' + {Locked="{0}"} Error message displayed when the user provides more than a single character command line alias argument after an alias argument specifier '-'. {0} is a placeholder replaced by the user's argument input. + + + Sort results by a property (can be repeated) + Description for the --sort argument used to sort output by field name. + + + Sort results in ascending order + Description for the --ascending (--asc) flag that sets ascending sort direction for output. + + + Sort results in descending order + Description for the --descending (--desc) flag that sets descending sort direction for output. + + + A source with the given name already exists and refers to a different location: + + + A source with a different name already refers to this location: + + + A source with the given name already exists and refers to the same location: + + + Adding source: + + + Add a new source. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. + + + Add a new source + + + Argument given to the source + + + Find package using the specified source + + + Manage sources with the sub-commands. A source provides the data for you to discover and install packages. Only add a new source if you trust it as a secure location. + + + Manage sources of packages + + + Edit properties of an existing source. A source provides the data for you to discover and install packages. + + + Edit properties of a source + + + Editing source: {0} + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. + + + The source named '{0}' is already in the desired state. + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being edited. {0} is a placeholder replaced by the repository source name. + + + Argument + Value given to source. + + + List all current sources, or full details of a specific source. + + + List current sources + + + Data + Data stored by the source. + + + Field + The name of a piece of information about a source. + + + Name + The name of the source. + + + Identifier + The source's unique identifier. + + + Did not find a source named: {0} + Error message displayed when the user provides a repository source name that was not found. {0} is a placeholder replaced by the user input. + + + There are no sources configured. + + + Type + The kind of source. + + + Updated + The last time the source was updated. + + + never + The source has never been updated. + + + Value + The value of information about a source. + + + Name of the source + + + Failed when opening source(s); try the 'source reset' command if the problem persists. + {Locked="source reset"} + + + Failed to open the predefined source; please report to WinGet maintainers. + {Locked="WinGet"} + + + Removing all sources... + + + Remove a specific source. + + + Remove current sources + + + Removing source: {0}... + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being removed. {0} is a placeholder replaced by the repository source name. + + + Resetting all sources... + + + This command drops existing sources, potentially leaving any local data behind. Without any argument, it will drop all sources and add the defaults. If a named source is provided, only that source will be dropped. + + + Reset sources + + + Forces the reset of the sources + + + The following sources will be reset if the --force option is given: + {Locked="--force"} + + + Resetting source: {0}... + {Locked="{0}"} Message displayed to inform the user about a repository source that is currently being reset. {0} is a placeholder replaced by the repository source name. + + + Type of the source + + + Updating all sources... + + + Update all sources, or only a specific source. + + + Update current sources + + + Updating source: {0}... + {Locked="{0}"} Message displayed to inform the user about a registered repository source that is currently being updated. {0} is a placeholder replaced by the repository source name. + + + Filter results by tag + + + Thank you for using WinGet + {Locked="WinGet"} + + + Third Party Notices + + + The winget command line utility enables installing applications and other packages from the command line. + + + Display general info of the tool + + + Display the version of the tool + + + Argument provided more times than allowed: '{0}' + {Locked="{0}"} Error message displayed when the user provides a command line argument more times than it is allowed. {0} is a placeholder replaced by the user's argument name input. + + + More than one execution behavior argument provided: '{0}' + {Locked="{0}"} Error message displayed when the user provides more than one execution behavior argument when installing an application package. {0} is a placeholder replaced by the user specified execution behaviors (e.g. 'silent|interactive'). + + + An unexpected error occurred while executing the command: + + + Uninstall the previous version of the package during upgrade + + + Unrecognized command: '{0}' + {Locked="{0}"} Error message displayed when the user provides an unrecognized command. {0} is a placeholder replaced by the user input. + + + Upgrade all installed packages to latest if available + + + No applicable upgrade found. + + + A newer package version is available in a configured source, but it does not apply to your system or requirements. + + + No available upgrade found. + + + No newer package versions are available from the configured sources. + + + Upgrades the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. When no arguments are given, shows the packages with upgrades available + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Shows and performs available upgrades + + + usage: {0} {1} + {Locked="{0} {1}"} Message displayed to provide the user with instructions on how to use a command. {0} is a placeholder replaced by the program name (e.g. 'winget'). {1} is a placeholder replaced by the pattern for using the selected command. + + + Validates a manifest using a strict set of guidelines. This is intended to enable you to check your manifest before submitting to a repo. + + + Validates a manifest file + + + The path to the manifest to be validated + + + Enables verbose logging for winget + + + Please verify that the input file is a valid, signed MSIX. + + + Use the specified version; default is the latest version + + + Show available versions of the package + + + The value provided before completion is requested + + + No version found matching: {0} + {Locked="{0}"} Error message displayed when the user attempts to upgrade an application package to a version that was not found. {0} is a placeholder replaced by the user's provided upgrade package version. + + + No sources match the given value: {0} + {Locked="{0}"} Error message displayed when the user attempts to install or upgrade an application package from a repository source that was not found. {0} is a placeholder replaced by the user's repository source name input. + + + The configured sources are: + + + No sources defined; add one with 'source add' or reset to defaults with 'source reset' + {Locked="source add","source reset"} + + + Found + Header label printed before showing details about a located package, e.g. "Found [package name]". This is the past tense of "to find" — the package was successfully found/located. + + + Path is a directory: {0} + {Locked="{0}"} Error message displayed when the user provides a system path that is a directory. {0} is a placeholder replaced by the provided directory path. + + + File does not exist: {0} + {Locked="{0}"} Error message displayed when the user provides a system file that does not exist. {0} is a placeholder replaced by the provided file path. + + + Both local manifest and search query arguments are provided + + + Logs + Label displayed for diagnostic files containing information about the application use. + + + The installer is blocked by policy + + + The installer failed security check + + + An anti-virus product reports an infection in the installer + + + Failed in attempting to update the source: {0} + {Locked="{0}"} Error message displayed when an attempt to update the repository source fails. {0} is a placeholder replaced by the repository source name. + + + Uninstalls the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Uninstalls the given package + + + Starting package uninstall... + + + Successfully uninstalled + + + WinGet cannot locate the uninstall command for this package. Please reach out to the package publisher for support. + {Locked="WinGet"} + + + Uninstallation abandoned + + + Uninstall failed with exit code: {0} + {Locked="{0}"} Error message displayed when an attempt to uninstall an application package fails. {0} is a placeholder replaced by an error code. + + + Exports a list of the installed packages + + + Installs all the packages listed in a file. + + + Installs all the packages in a file + + + File where the result is to be written + + + File describing the packages to install + + + Export packages from the specified source + + + Writes a list of the installed packages to a file. The packages can then be installed with the import command. + {Locked="import"} + + + One or more imported packages failed to install + + + Source required for import is not installed: {0} + {Locked="{0}"} Error message displayed when the user attempts to import application package(s) from a repository source that is not installed. {0} is a placeholder replaced by the repository source name. + + + Installed package is not available from any source: {0} + {Locked="{0}"} Warning message displayed when the user attempts to export an installed application package that is not available from any repository source. {0} is a placeholder replaced by the installed package name. + + + Installed version of package is not available from any source: {0} {1} {2} + {Locked="{0} {1} {2}"} Warning message displayed when the user attempts to export an installed application package with a version that is not available from any repository source. {0} is a placeholder replaced by the installed package identifier. {1} is a placeholder replaced by the installed package version. {2} is a placeholder replaced by the installed package channel. + + + No packages found in import file + + + JSON file is not valid + + + Package is already installed: {0} + {Locked="{0}"} Message displayed to inform the user that an application package is already installed. {0} is a placeholder replaced by the package identifier or search query. + + + Ignore unavailable packages + + + Include package versions in export file + + + Ignore package versions from import file + + + Path does not exist: {0} + {Locked="{0}"} Error message displayed when the user provides a system path argument value that does not exist. {0} is a placeholder replaced by the user's provided path. + + + The JSON file does not specify a recognized schema. + + + Select install scope (user or machine) + {Locked="user","machine"} This argument allows the user to select between installing for just the user or for the entire machine. + + + The value provided for the `{0}` argument is invalid; valid values are: {1} + {Locked="{0}","{1}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. {1} is a placeholder replaced by a list of valid options. + + + This operation is disabled by Group Policy: {0} + {Locked="{0}"} Error message displayed when the user performs a command operation that is disabled by a group policy. {0} is a placeholder replaced by a group policy description. + + + Enable Additional Windows App Installer Sources + + + Enable Windows App Installer Allowed Sources + + + Enable Windows App Installer Default Source + + + Enable Windows App Installer Experimental Features + + + Enable Windows App Installer Microsoft Store Source + + + Enable Windows App Installer Font Source + + + Enable Windows Package Manager Settings + + + Enable Windows Package Manager + + + Enable Windows Package Manager command line interfaces + + + Set Windows Package Manager Source Auto Update Interval In Minutes + + + Group Policy + Header for a table listing active Group Policies + + + Enable Windows App Installer Local Manifest Files + + + Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass + + + Enable Windows App Installer Local Archive Malware Scan Override + + + Field: {0} + {Locked="{0}"} Warning message displayed when a user setting field has invalid syntax or semantics. {0} is a placeholder replaced by the setting field path. + + + Invalid field format. + + + Invalid field value. + + + Invalid setting from Group Policy. + + + Loaded settings from backup file. + + + Error parsing file: + + + Value: {0} + {Locked="{0}"} Warning message displayed when a user setting value has invalid syntax or semantics. {0} is a placeholder replaced by the setting data value. + + + The following experimental features are in progress. +Configuration is disabled due to Group Policy. + + + Installer hash does not match. + + + Enable Windows App Installer Hash Override + + + Export current sources as JSON for Group Policy. + + + Export current sources + + + Additional source + An additional source required by policy. + + + Allowed source + A source that the user is allowed to add. + + + The value provided for the `{0}` argument is invalid + {Locked="{0}"} Error message displayed when the user provides an invalid command line argument value. {0} is a placeholder replaced by the argument name. + + + Cancelled + + + External + Category label for external package dependencies — packages that this package depends on but are distributed separately. Contrasts with built-in/internal dependencies. This is an adjective modifying the implied noun "dependencies". + + + The packages found in this import have the following dependencies: + Import command sentence showed before reporting dependencies + + + This package requires the following dependencies: + Message shown before reporting dependencies + + + Installing dependencies: + + + Dependency source not found + + + Package search yield more than one result: {0} + {Locked="{0}"} Error message displayed when application packages search yield more than one result. {0} is a placeholder replaced by the dependency package identifier. + + + Latest version not found for package: {0} + {Locked="{0}"} Error message displayed when no suitable version found for the specific application package. {0} is a placeholder replaced by the package identifier. + + + No installers found: {0} + {Locked="{0}"} Error message displayed when no installer found for a manifest. {0} is a placeholder replaced by the manifest identifier. + + + Minimum required version not available for package: {0} + {Locked="{0}"} Error message displayed when the minimum required version is not available for an application package. {0} is a placeholder replaced by the package identifier. + + + No matches + When package search yields no matches + + + Has loop + Dependency graph has loop + + + No suitable installer found for manifest: {0} version {1} + {Locked="{0}","{1}"} Error message displayed when an attempt to get a preferred installer for a manifest fails. {0} is a placeholder replaced by the manifest identifier. {1} is a placeholder replaced by the manifest version. + + + Error processing package dependencies. Do you wish to continue installation? + Prompt message shown when dependencies processing yields errors. + + + Error processing package dependencies. Exiting... + + + Packages + + + This package had dependencies that may not be needed anymore: + Uninstall command sentence showed before reporting dependencies + + + Manifest has the following dependencies that were not validated; ensure that they are valid: + Validate command sentence showed before reporting dependencies + + + Windows Features + + + Windows Libraries + + + Find package dependencies using the specified source + For getting package type dependencies when installing from a local manifest + + + Windows Store Terms + + + Accept all license agreements for packages + + + Exported package requires license agreement to install: {0} + {Locked="{0}"} Warning message displayed when an exported application package requires license agreement to install. {0} is a placeholder replaced by the package name. + + + The publisher requires that you view the above information and accept the agreements before installing. +Do you agree to the terms? + + + Package agreements were not agreed to. Operation cancelled. + + + Agreements: + + + Author: + + + Description: + + + Installer: + + + Installer Locale: + + + Store Product Id: + + + Installer SHA256: + + + Installer Type: + + + Installer Url: + + + License: + + + License Url: + + + Moniker: + + + Homepage: + + + Publisher: + + + Tags: + + + Version: + + + Dependencies: + + + Windows Features: + + + Windows Libraries: + + + Package Dependencies: + + + External Dependencies: + + + No + + + Yes + + + Failed to open the added source. + + + Accept all source agreements during source operations + + + The `{0}` source requires that you view the following agreements before using. + {Locked="{0}"} Message displayed to inform the user that a repository source requires viewing agreements before using. {0} is a placeholder replaced by the repository source name. + + + Do you agree to all the source agreements terms? + + + One or more of the source agreements were not agreed to. Operation cancelled. Please accept the source agreements or remove the corresponding sources. + + + The source requires the current machine's 2-letter geographic region to be sent to the backend service to function properly (ex. "US"). + + + Successfully installed. Restart the application to complete the upgrade. + + + Optional Windows-Package-Manager REST source HTTP header + + + Ignoring the optional header as it is not applicable for this source. + + + The optional header is not applicable without specifying a source: '{0}' + {Locked="{0}"} Error message displayed when the user performs an operation (e.g install) and provides the HTTP 'header' argument without specifying the repository source. {0} is a placeholder replaced by the header argument name. + + + Release Date: + + + Offline Distribution Supported: + + + Publisher Url: + + + Purchase Url: + + + Publisher Support Url: + + + Privacy Url: + + + Copyright: + + + Copyright Url: + + + Release Notes: + + + Release Notes Url: + + + Failed when searching source; results will not be included: {0} + {Locked="{0}"} Warning message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. + + + Failed when searching source: {0} + {Locked="{0}"} Error message displayed when searching a repository source fails. {0} is a placeholder replaced by the repository source name. + + + This feature needs to be enabled by administrators. To enable it, run 'winget settings --enable {0}' as administrator. + {Locked="winget settings --enable", "{0}"}. Error message displayed when the user uses a feature that needs to be enabled by administrators. {0} is a placeholder replaced by the admin setting. + + + Enables the specific administrator setting + + + Disables the specific administrator setting + + + Enabled admin setting '{0}'. + {Locked="{0}"} Message displayed when the user enables an admin setting. {0} is a placeholder replaced by the setting name. + + + Disabled admin setting '{0}'. + {Locked="{0}"} Message displayed when the user disables an admin setting. {0} is a placeholder replaced by the setting name. + + + Admin Setting + Header for a table displaying admin settings. + + + Application is currently running. Exit the application then try again. + + + Files modified by the installer are currently in use by a different application. Exit the applications then try again. + + + Another installation is already in progress. Try again later. + + + One or more files are being used. Exit the application then try again. + + + This package has a dependency missing from your system. + + + There's no more space on your PC. Make space, then try again. + + + There's not enough memory available to install. Close other applications then try again. + + + One of the installation parameters is invalid. The package installation log may have additional details. + + + This application requires internet connectivity. Connect to a network then try again. + + + This application encountered an error during installation. Contact support. + + + Restart your PC to finish installation. + + + Your PC will restart to finish installation. + + + Installation failed. Restart your PC then try again. + + + You cancelled the installation. + + + Another version of this application is already installed. + + + A higher version of this application is already installed. + + + Organization policies are preventing installation. Contact your admin. + + + The current system configuration does not support the installation of this package. + + + Installation failed with a custom installer error. Contact package support. + + + Installation abandoned + + + Installer failed with exit code: {0} + {Locked="{0}"} Error message displayed when the application installer fails. {0} is a placeholder replaced by an error code. + + + Installer log is available at: {0} + {Locked="{0}"} Message displayed to inform the user about the system path of a diagnostic files containing information about the installer. {0} is a placeholder replaced by the diagnostic file system path. + + + The following packages were found among the working sources. +Please specify one of them using the --source option to proceed. + {Locked="--source"} "working sources" as in "sources that are working correctly" + + + No packages were found among the working sources. + "working sources" as in "sources that are working correctly" + + + This is a stable release of the Windows Package Manager. If you would like to try experimental features, please install a pre-release build. Instructions are available on GitHub at https://github.com/microsoft/winget-cli. + {Locked="https://github.com/microsoft/winget-cli"} + + + Windows Package Manager v{0} + {Locked="{0}"} Label displaying the product name and version. {0} is a placeholder replaced by the product version. + + + Ignore package versions in import file + + + The requested number of results must be between 1 and 1000. + + + Arguments to be passed on to the installer in addition to the defaults + + + A newer version was found, but the install technology is different from the current version installed. Please uninstall the package and install the newer version. + + + {0} package(s) have upgrades blocked because newer versions use a different install technology than the current installation. Uninstall each package, then install the newer version. + {Locked="{0}"} {0} is the number of packages skipped for this reason during winget upgrade --all. + + + The install technology of the newer version specified is different from the current version installed. Please uninstall the package and install the newer version. + + + Windows Package Manager (Preview) v{0} + {Locked="{0}"} Label displaying the preview product name and pre-release version. {0} is a placeholder replaced by the product version. + + + Select the architecture + + + Upgrade packages even if their current version cannot be determined + + + List packages even if their current version cannot be determined. Can only be used with the --upgrade-available argument + {Locked="--upgrade-available"} + + + This package's version number cannot be determined. To upgrade it anyway, add the argument --include-unknown to your previous command. + {Locked="--include-unknown"} + + + {0} package(s) have version numbers that cannot be determined. Use --include-unknown to see all results. + {Locked="{0}","--include-unknown"} {0} is a placeholder that is replaced by an integer number of packages that do not have notated versions. + + + {0} package(s) are pinned and need to be explicitly upgraded. + {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages that require explicit upgrades. + + + The arguments provided can only be used with a query. + + + System Architecture: {0} + {Locked="{0}"} Label displayed for the system architecture. {0} is a placeholder replaced by the value of the system architecture (e.g. X64). + + + Retains all files and directories created by the package (portable) + + + Deletes all files and directories in the package directory (portable) + + + Press Enter to continue . . . + + + The value to rename the executable file (portable) + + + Prompts the user to press any key before exiting + + + The installer will request to run as administrator. Expect a prompt. + + + The installer cannot be run from an administrator context. + + + Path environment variable modified; restart your shell to use the new value. + + + Filters using the product code + + + A portable package with the same name but from a different source already exists; proceeding due to --force + {Locked="--force"} + + + The volume does not support reparse points + + + The specified filename is not a valid filename + + + Overwriting existing file: {0} + {Locked="{0}"} Warning message displayed to inform the user that an existing file is being overwritten. {0} is a placeholder replaced by the file system path. + + + No package selection argument was provided; see the help for details about finding a package. + + + Command line alias added: + + + Portable Links Directory (Machine) + + + Portable Links Directory (User) + + + Portable Package Root (User) + + + Portable Package Root + + + Portable Package Root (x86) + + + Portable install failed; Cleaning up... + + + Portable package has been modified; proceeding due to --force + {Locked="--force"} + + + Unable to remove Portable package as it has been modified; to override this check use --force + {Locked="--force"} + + + Files remain in install directory: {0} + {Locked="{0}"} Warning message displayed when files remain in install directory. {0} is a placeholder replaced by the directory path. + + + Purging install directory... + + + Cannot purge install directory as it was not created by WinGet + {Locked="WinGet"} + + + Related Link + + + Documentation: + + + Notes: {0} + {Locked="{0}"} Label displayed for installation notes. {0} is a placeholder replaced by installation notes. + + + Installation Notes: + + + A provided argument is not supported for this package + + + Failed to extract the contents of the archive + + + Nested installer file does not exist. Ensure the specified relative path of the nested installer matches: {0} + {Locked="{0}"} Error message displayed when nested installer file does not exist. {0} is a placeholder replaced by the nested installer file path. + + + Invalid relative file path to the nested installer; path points to a location outside of the install directory + + + No nested installers specified for this package + + + Only one nested installer can be specified for an archive installer unless it is a portable or font nested installer + + + Incompatible command line arguments provided + + + Lists only packages which have an upgrade available + + + The following packages have an upgrade available, but require explicit targeting for upgrade: + "require explicit targeting for upgrade" means that the package will not be upgraded with all others unless an extra flag is added, or the package is mentioned explicitly + + + Downloading + Label displayed while downloading an application installer. + + + The nested installer type is not supported + + + This installer is known to restart the terminal or shell + + + This package requires an install location + + + The following installers are known to restart the terminal or shell: + + + The following installers require an install location: + + + Specify the install root: + + + Do you want to proceed? + + + Agreements for + This will be followed by a package name, and then a list of license agreements + + + Install location is required by the package but it was not provided + + + Disable interactive prompts + Description for a command line argument, shown next to it in the help + + + Found an existing package already installed. Trying to upgrade the installed package... + + + Direct run the command and continue with non security related issues + Description for a command line argument, shown next to it in the help + + + Portable package from a different source already exists + + + Successfully extracted archive + + + Extracting archive... + + + Archive scan detected malware. To override this check use --ignore-local-archive-malware-scan + {Locked="--ignore-local-archive-malware-scan"} + + + Archive scan detected malware. Proceeding due to --ignore-local-archive-malware-scan + {Locked="--ignore-local-archive-malware-scan"} + + + Skips upgrade if an installed version already exists + + + A package version is already installed. Installation cancelled. + + + Cannot enable {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Cannot disable {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Add a new pin. A pin can limit the Windows Package Manager from upgrade a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. By default, a pinned package can be upgraded by mentioning it explicitly in the 'upgrade' command or by adding the '--include-pinned' flag to 'winget upgrade --all'. + {Locked{"'upgrade'"} Locked{"--include-pinned"} Locked{"winget upgrade --all"} + + + Add a new pin + + + Manage package pins with the sub-commands. A pin can limit the Windows Package Manager from upgrading a package to specific ranges of versions, or it can prevent it from upgrading the package altogether. A pinned package may still upgrade on its own and be upgraded from outside the Windows Package Manager. + + + Manage package pins + + + List all current pins, or full details of a specific pin. + + + List current pins + + + Remove a specific package pin. + + + Remove a package pin + + + Reset all existing pins. + + + Reset pins + + + Version to which to pin the package. The wildcard '*' can be used as the last version part + + + Block from upgrading until the pin is removed, preventing override arguments + + + Pin a specific installed version + + + Installed + Value used in a table to indicate that a package comes from the list of packages installed in the machine + + + Export settings as JSON + + + Export settings + + + User Settings + Label displayed for the file containing the user settings. + + + Settings file couldn't load. Using default values. + + + Select installed package scope filter (user or machine) + {Locked="user","machine"} This argument allows the user to select installed packages for just the user or for the entire machine. + + + Pin added successfully + + + There is already a pin for package {0} + {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to add a pin for a package that is already pinned. + + + A pin already exists for package {0}. Overwriting due to the --force argument. + {Locked="--force"}{Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. + + + A pin already exists for package {0}. Use the --force argument to overwrite it. + {Locked="--force","{0}"} {0} is a placeholder that will be replaced by a package name. + + + Resetting all current pins. + + + Use the --force argument to reset all pins. The following pins would be removed: + {Locked="--force"} + + + Pin type + + + There is no pin for package {0} + {Locked="{0}"} {0} is a placeholder that will be replaced by a package name. The message is shown when attempting to delete a pin for a package that is not pinned. + + + There are no pins configured. + Shown when listing or modifying existing pins if there are none. + + + Pins reset successfully + Shown after resetting (deleting) all the pins + + + Unable to open pin database. + Error message for when we cannot open the database containing package pins. + + + An argument was provided that can only be used for single package + + + Multiple mutually exclusive arguments provided: {0} + {Locked="{0}"} Error message shown when mutually incompatible command line arguments are used. {0} is a placeholder replaced by the arguments that cannot be specified together + + + Argument {0} can only be used with {1} + {Locked="{0}","{1}"} Error message shown when having an argument needs another to be present, but that is missing. {0} and {1} are replaced by the arguments. For example "Argument --include-unknown can only be used with --upgrade" + + + Disabled + As in enabled/disabled + + + Enabled + As in enabled/disabled + + + State + Header for a table listing the state (enabled/disabled) of Group Policies and Settings + + + The query used to search for a package + + + Package not found: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns no results. {0} is a placeholder replaced by the package name or query. + + + Multiple packages found for: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches returns more than one result. {0} is a placeholder replaced by the package name or query. + + + Search failed for: {0} + {Locked="{0}"} Error message displayed when the user attempts to search for multiple application packages and one of the searches fails. This message is for generic failures, we have more specific messages for when the search returns no results, or when it returns more than one result. {0} is a placeholder replaced by the package name or query. + + + {0} package(s) have a pin that needs to be removed before upgrade + {Locked="{0}"} {0} is a placeholder that is replaced by an integer number of packages with pins that prevent upgrade + + + The package cannot be upgraded using WinGet. Please use the method provided by the publisher for upgrading this package. + {Locked="WinGet"} + + + Upgrade packages even if they have a non-blocking pin + + + List packages even if they have a pin that prevents upgrade. Can only be used with the --upgrade-available argument + {Locked="--upgrade-available"} + + + Pin removed successfully + + + A newer version was found, but the package has a pin that prevents from upgrading it. + + + The package is pinned and cannot be upgraded. Use the 'winget pin' command to view and edit pins. Some pin types can be bypassed with the --include-pinned argument. + {Locked="winget pin","--include-pinned"} Error shown when we block an upgrade due to the package being pinned + + + {0} package(s) have pins that prevent upgrade. Use the 'winget pin' command to view and edit pins. Using the '--include-pinned' argument may show more results. + {Locked="winget pin","--include-pinned"} {0} is a placeholder replaced by an integer number of packages + + + Ensures that the system matches the desired state as described by the provided configuration. May download/execute processors in order to achieve the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before applying them. + + + Configures the system into a desired state + + + Shows details of the provided configuration. By default, will not modify the system, but some options will cause files to be downloaded and/or loaded. + + + Shows details of a configuration + + + Checks that the system matches the desired state as described by the provided configuration. May download/execute processors in order to test the desired state. The configuration and the processors should be checked to ensure that they are trustworthy before executing them. + + + Checks the system against a desired state + + + Validates a configuration file for correctness. + + + Validates a configuration file + + + The field '{0}' in the configuration file is the wrong type. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. + + + The path to the configuration file + + + The configuration file is invalid. + + + Configuration file version {0} is not known. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the version of the configuration file. + + + Accepts the configuration warning, preventing an interactive prompt + + + Apply + Indicates that this item is used to write state + + + Assert + Indicates that this item is used to check/assert the state rather than write to it + + + Dependencies:{0} + {Locked="{0}"} Label displaying a list of dependencies. {0} is replaced with a space separated list of identifiers referencing other items. + + + Some of the configuration was not applied successfully. + + + Failed to get detailed information about the configuration. + + + Inform + Indicates that this item is used to retrieve values for future use rather than writing them + + + Local + Used to indicate that the item is present on the device. + + + Module: {0} + {Locked="{0}"} Label displaying a module name. {0} is replaced with the name of the module from the user input file. + + + Module: {0} by {1} [{2}] + {Locked="{0}","{1}","{2}"} Label displaying module information. {0} is replaced by the module name. {1} is replaced by the module author. {2} is replaced by a string indicating the source of the module. + + + Settings: + Label for the values that are used as inputs for this item when applying state + + + Configuration successfully applied. + + + Unit successfully applied. + + + Another configuration is being applied to the system. This configuration will continue as soon as is possible... + + + You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services. + Legal approved. Do not change without approval. + + + Have you reviewed the configuration and would you like to proceed applying it to the system? + PM approved. + + + The configuration is empty. + + + The feature [{0}] was not found. + {Locked="{0}"} Error message displayed when a Windows feature was not found on the system. + + + Failed to enable Windows Feature dependencies. To proceed with installation, use '--force'. + {Locked="--force"} + + + Reboot required to fully enable the Windows Feature(s); to override this check use '--force'. + "Windows Feature(s)" is the name of the options Windows features setting. + + + Failed to enable Windows Feature dependencies; proceeding due to --force + "Windows Feature(s)" is the name of the options Windows features setting. +{Locked="--force"} + + + Enabling [{0}]... + {Locked="{0}"} Message displayed to the user regarding which Windows Feature is being enabled. + + + Failed to enable [{0}] feature: {1} + {Locked="{0}","{1}"} An error when enabling a Windows Feature. {0} is a placeholder for the name of the Windows Feature. +{1} is a placeholder for the unrecognized error code. + + + Reboot required to fully enable the Windows Feature(s); proceeding due to --force + "Windows Feature(s)" is the name of the options Windows features setting. + + + Waiting for another install/uninstall to complete... + + + Pinned version + Table header for the version to which a package is pinned; meaning it should not update from that version. + + + <See the log file for additional details> + The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable. + + + Retrieving configuration details + + + Initializing configuration system + + + Reading configuration file + + + The system is not in the desired state asserted by the configuration. + + + This configuration unit failed for an unknown reason: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to the configuration: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed while attempting to get the current system state. + + + The configuration unit failed while attempting to apply the desired state. + + + The configuration unit failed while attempting to test the current system state. + + + The configuration unit failed due to an internal error: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to a precondition not being valid: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to the system state: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed while attempting to run: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration contains the identifier `{0}` multiple times. + {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. + + + The dependency `{0}` was not found within the configuration. + {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. + + + This configuration unit was manually skipped. + + + The module for the configuration unit is available in multiple locations with the same version. + + + Loading the module for the configuration unit failed. + + + Multiple matches were found for the configuration unit; specify the module to select the correct one. + + + The configuration unit could not be found. + + + The configuration unit was not in the module as expected. + + + This configuration unit was not run because a dependency failed or was not run. + + + This configuration unit was not run because an assert failed or was false. + + + The configuration unit returned an unexpected result during execution. + + + This configuration unit was not run for an unknown reason: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The field '{0}' has an invalid value: {1} + {Locked="{0}","{1}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. {1} is a placeholder for the invalid value. + + + The field '{0}' is missing or empty. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the expected field name from the file. + + + See line {0}, column {1} in the file. + {Locked="{0}","{1}"} Indicates the file location of the error, {0} and {1} are placeholders for numbers of the line and column, respectively. + + + Cancelling operation + + + Install the stub package for AppInstaller + + + Install the full package for AppInstaller + + + Enable extended features. Requires store access. + + + Option '--enable' and '--disable' cannot be used with other arguments. + {Locked="--enable", "--disable"} + + + Enabling extended features. Requires store access. + + + Extended features are not enabled. Run `winget configure --enable` to enable them. + {Locked="winget configure --enable"} + + + Extended features are enabled. + + + Disable extended features. Requires store access. + + + Disabling extended features. Requires store access. + + + Extended features are disabled. + + + Skips processing package dependencies and Windows features + + + Installs only the dependencies of the package + + + Installing dependencies only. The package itself will not be installed. + + + Dependencies skipped. + + + Failed to refresh PATH variable for process. Subsequent installs that depend on changes to the PATH variable may fail. + {Locked="PATH"} + + + Some of the configuration units failed while testing their state. + + + System is in the described configuration state. + + + Configuration state was not tested. + + + System is not in the described configuration state. + + + Unexpected test result: {0} + {Locked="{0}"} Error message. {0} will be replaced with the unexpected value (a number). + + + Have you reviewed the configuration and would you like to proceed verifying it against the system? + + + The configuration file is not a valid YAML file. + {Locked="YAML"} YAML is a file format name. + + + This configuration unit is part of a dependency cycle. + + + Validation found no issues. + + + The configuration unit is only available as a prerelease, but it is not marked that way in the configuration. Add `allowPrerelease: true` to the `directives`. + {Locked="allowPrerelease: true","directives"} These are values in the configuration file that are not localized. + + + The configuration unit was found locally, but could not be found in any configured catalog. Ensure that it is present on any system before applying the configuration. + + + The configuration unit is not available publicly; ensure that anyone who will use this configuration has access to it. + + + The module was not provided. Specifying the module improves performance and prevents future name collisions. + + + Downloads the installer from the selected package, either found by searching a configured source or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. By default, download command will download the appropriate installer to the user's Downloads folder. + + + Downloads the installer from a given package + + + Directory where the installers are downloaded to + + + Downloading dependencies: + + + Installer downloaded: {0} + {Locked="{0}"} Full path of the downloaded installer. + + + Select the installer type + + + Installer Downloads + + + Installer is prohibited from being downloaded for later offline installation. + + + `--module-path allusers` requires administrator privileges to execute. + {Locked="--module-path allusers"} + + + Specifies the location on the local computer to store modules. Default %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules + {Locked="%LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules"} + + + `--module-path` value must be `currentuser`, `allusers`, `default`, or an absolute path. + {Locked="{--module-path}, {currentuser}, {allusers}, {default}} + + + Enable Windows Package Manager Configuration + + + Retrieve information about errors. Given a number, the output will contain details about the error, including the symbol name if it is a WinGet specific error. Given a string, the WinGet specific errors are searched for this value. + {Locked="WinGet"} + + + Get information on errors + + + A value to search within the error information + + + Generate error code Markdown file + Description of an argument that causes a Markdown file to be generated for all winget error codes. + + + The --output argument cannot be combined with an input value. + {Locked="--output"} + + + An input value or --output must be provided. + {Locked="--output"} + + + The given number is too large to be an HRESULT. + + + Unknown error code + + + Internal Error + + + Invalid command line arguments + + + Executing command failed + + + Opening manifest failed + + + Cancellation signal received + + + Running ShellExecute failed + + + Cannot process manifest. The manifest version is higher than supported. Please update the client. + + + Downloading installer failed + + + Cannot write to index; it is a higher schema version + + + The index is corrupt + + + The configured source information is corrupt + + + The source name is already configured + + + The source type is invalid + + + The MSIX file is a bundle, not a package + + + Data required by the source is missing + + + None of the installers are applicable for the current system + + + The installer file's hash does not match the manifest + + + The source name does not exist + + + The source location is already configured under another name + + + No packages found + + + No sources are configured + + + Multiple packages found matching the criteria + + + No manifest found matching the criteria + + + Failed to get Public folder from source package + + + Command requires administrator privileges to run + + + The source location is not secure + + + The Microsoft Store client is blocked by policy + + + The Microsoft Store app is blocked by policy + + + The feature is currently under development. It can be enabled using `winget settings`. + {Locked="winget settings"} + + + Failed to install the Microsoft Store app + + + Failed to perform auto complete + + + Failed to initialize YAML parser + + + Encountered an invalid YAML key + + + Encountered a duplicate YAML key + + + Invalid YAML operation + + + Failed to build YAML doc + + + Invalid YAML emitter state + + + Invalid YAML data + + + LibYAML error + + + Manifest validation succeeded with warning + + + Manifest validation failed + + + Manifest is invalid + + + No applicable update found + + + An upgrade is available but uses a different install technology than the current installation + + + winget upgrade --all completed with failures + + + Installer failed security check + + + Download size does not match expected content length + + + Uninstall command not found + + + Running uninstall command failed + + + ICU break iterator error + + + ICU casemap error + + + ICU regex error + + + Failed to install one or more imported packages + + + Could not find one or more requested packages + + + Json file is invalid + + + The source location is not remote + + + The configured rest source is not supported + + + Invalid data returned by rest source + + + Operation is blocked by Group Policy + + + Rest API internal error + + + Invalid rest source url + + + Unsupported MIME type returned by rest API + + + Invalid rest source contract version + + + The source data is corrupted or tampered + + + Error reading from the stream + + + Package agreements were not agreed to + + + Error reading input in prompt + + + The search request is not supported by one or more sources + + + The rest API endpoint is not found. + + + Failed to open the source. + + + Source agreements were not agreed to + + + Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again. + + + Missing resource file + + + Running MSI install failed + + + Arguments for msiexec are invalid + + + Failed to open one or more sources + + + Failed to validate dependencies + + + One or more package is missing + + + Invalid table column + + + The upgrade version is not newer than the installed version + + + Upgrade version is unknown and override is not specified + + + ICU conversion error + + + Failed to install portable package + + + Volume does not support reparse points + + + Portable package from a different source already exists. + + + Unable to create symlink. Path points to a directory. + + + The installer cannot be run from an administrator context. + + + Failed to uninstall portable package + + + Failed to validate DisplayVersion values against index. + + + One or more arguments are not supported. + + + Embedded null characters are disallowed for SQLite + + + Failed to find the nested installer in the archive. + + + Failed to extract archive. + + + Invalid relative file path to nested installer provided. + + + The server certificate did not match any of the expected values. + + + Install location must be provided. + + + Archive malware scan failed. + + + Found at least one version of the package installed. + + + A pin already exists for the package. + + + There is no pin for the package. + + + Unable to open the pin database. + + + One or more applications failed to install + + + One or more applications failed to uninstall + + + One or more queries did not return exactly one match + + + The package has a pin that prevents upgrade. + + + The package currently installed is the stub package + + + Application shutdown signal received + + + Failed to download package dependencies. + + + Failed to download package. Download for offline installation is prohibited. + + + A required service is busy or unavailable. Try again later. + + + The guid provided does not correspond to a valid resume state. + + + The current client version did not match the client version of the saved state. + + + The resume state data is invalid. + + + Unable to open the checkpoint database. + + + Exceeded max resume limit. + + + Invalid authentication info. + + + Authentication method not supported. + + + Authentication failed. + + + Authentication failed. Interactive authentication required. + + + Authentication failed. User cancelled. + + + Authentication failed. Authenticated account is not the desired account. + + + Application is currently running. Exit the application then try again. + + + Another installation is already in progress. Try again later. + + + One or more file is being used. Exit the application then try again. + + + This package has a dependency missing from your system. + + + There's no more space on your PC. Make space, then try again. + + + There's not enough memory available to install. Close other applications then try again. + + + This application requires internet connectivity. Connect to a network then try again. + + + This application encountered an error during installation. Contact support. + + + Restart your PC to finish installation. + + + Installation failed. Restart your PC then try again. + + + Your PC will restart to finish installation. + + + You cancelled the installation. + + + Another version of this application is already installed. + + + A higher version of this application is already installed. + + + Organization policies are preventing installation. Contact your admin. + + + Failed to install package dependencies. + + + Application is currently in use by another application. + + + Invalid parameter. + + + Package not supported by the system. + + + The installer does not support upgrading an existing package. + + + Installation failed with a custom installer error. + + + The Apps and Features Entry for the package could not be found. + + + The install location is not applicable. + + + The install location could not be found. + + + The hash of the existing file did not match. + + + File not found. + + + The file was found but the hash was not checked. + + + The file could not be accessed. + + + The configuration file is invalid. + + + The YAML syntax is invalid. + + + A configuration field has an invalid type. + + + The configuration has an unknown version. + + + An error occurred while applying the configuration. + + + The configuration contains a duplicate identifier. + + + The configuration is missing a dependency. + + + The configuration has an unsatisfied dependency. + + + An assertion for the configuration unit failed. + + + The configuration was manually skipped. + + + The user declined to continue execution. + + + The dependency graph contains a cycle which cannot be resolved. + + + The configuration has an invalid field value. + + + The configuration is missing a field. + + + Some of the configuration units failed while testing their state. + + + Configuration state was not tested. + + + The configuration unit was not installed. + + + The configuration unit could not be found. + + + Multiple matches were found for the configuration unit; specify the module to select the correct one. + + + The configuration unit failed while attempting to get the current system state. + + + The configuration unit failed while attempting to test the current system state. + + + The configuration unit failed while attempting to apply the desired state. + + + The module for the configuration unit is available in multiple locations with the same version. + + + Loading the module for the configuration unit failed. + + + The configuration unit returned an unexpected result during execution. + + + A unit contains a setting that requires the config root. + + + Operation is not supported by the configuration processor. + + + Unavailable + + + Successfully enabled Windows Features dependencies + + + Loading the module for the configuration unit failed because it requires administrator privileges to run. + + + A unit contains a setting that requires the config root. + + + Loading the module for the configuration unit failed because it requires administrator privileges to run. + + + Resumes execution of a previously saved command by passing in the unique identifier of the saved command. This is used to resume an executed command that may have been terminated due to a reboot. + + + Resumes execution of a previously saved command. + + + The unique identifier of the saved state to resume + + + Resuming the state from a different client version is not supported: {0} + {Locked= "{0}"} Message displayed to inform the user that the client version of the resume state does not match the current client version. {0} is a placeholder for the client version that created the resume state. + + + The resume state does not exist: {0} + {Locked="{0}"} Error message displayed when the user provides a guid that does not correspond to a valid saved state. {0} is a placeholder replaced by the provided guid string. + + + No data found in the resume state. + + + This command does not support resuming. + + + Allows a reboot if applicable + + + Initiating reboot to complete operation... + + + Failed to initiate a reboot. + + + Resume operation exceeds the allowable limit of {0} resume(s). To manually resume, run the command `{1}`. + {Locked="{0}", "{1}"} {0} is a placeholder that is replaced by an integer number of the number of allowed resumes. {1} is a placeholder for the command to run to perform a manual resume. + + + Ignore the limit on resuming a saved state + + + Uri scheme not supported: {0} + {Locked="{0}"} Error message displayed when the provided uri is not supported. {0} is a placeholder replaced by the provided uri. + + + Uri not well formed: {0} + {Locked="{0}"} Error message displayed when the provided uri is not well formed. {0} is a placeholder replaced by the provided uri. + + + Failed to parse {0} configuration unit settings content or settings content is empty. + {Locked="{0}"} {0} is a placeholder replaced by the input winget configure resource unit type. + + + {0} configuration unit is missing required argument: {1} + {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. + + + {0} configuration unit is missing recommended argument: {1} + {Locked="{0},{1}"} {0} is a placeholder for the input winget configure resource unit type. {1} is placeholder for the missing arg. + + + WinGetSource configuration unit conflicts with a known source: {0} + {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. + + + WinGetSource configuration unit asserts on a third-party source: {0} + {Locked="WinGetSource,{0}"} {0} is a placeholder for the input winget source in the configuration unit settings. + + + WinGetPackage declares both UseLatest and Version. Package Id: {0} + {Locked="WinGetPackage,UseLatest,Version,{0}"} + + + WinGetPackage configuration unit asserts on a package from third-party source. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package depends on a third-party source not previously configured. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package depends on a 3rd party source. It is recommended to declare the dependency in uni dependsOn section. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,dependsOn,{0},{1}"} + + + WinGetPackage configuration unit package cannot be validated. Source open failed. Package Id: {0}; Source: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package cannot be validated. Package not found. Package Id: {0} + {Locked="WinGetPackage,{0}"} + + + WinGetPackage configuration unit package cannot be validated. More than one package found. Package Id: {0} + {Locked="WinGetPackage,{0}"} + + + WinGetPackage configuration unit package cannot be validated. Package version not found. Package Id: {0}; Version {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package specified with a specific version while only one package version is available. Package Id: {0}; Version: {1} + {Locked="WinGetPackage,{0},{1}"} + + + WinGetPackage configuration unit package cannot be validated. Package Id: {0} + {Locked="WinGetPackage,{0}"} + + + Specify authentication window preference (silent, silentPreferred, or interactive) + {Locked="silent","silentPreferred","interactive"} This argument allows the user to select authentication window popup behavior. + + + Specify the account to be used for authentication + + + Failed to add source. This WinGet version does not support the source's authentication method. Try upgrading to latest WinGet version. + {Locked="WinGet"} + + + The {0} source requires authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with the source for access authorization. + {Locked="{0}"} + + + Repairs the selected package, either found by searching the installed packages list or directly from a manifest. By default, the query must case-insensitively match the id, name, or moniker of the package. Other fields can be used by passing their appropriate option. + id, name, and moniker are all named values in our context, and may benefit from not being translated. The match must be for any of them, with comparison ignoring case. + + + Repairs the selected package + + + The repair command for this package cannot be found. Please reach out to the package publisher for support. + + + The installer technology in use does not match the version currently installed. + + + Repair command not found. + + + The installer technology in use doesn't support repair. + + + Repair operation completed successfully. + + + Repair abandoned + + + Starting package repair... + + + Failed to repair Microsoft Store package. Error code: {0} + {Locked="{0}"} Error message displayed when a Microsoft Store application package fails to repair. {0} is a placeholder replaced by an error code. + + + Repair operation failed. + + + Repair operation is not applicable. + + + No matching package versions are available from the configured sources. + + + The current system configuration does not support the repair of this package. + + + The installer technology in use does not support repair. + + + The package installed for user scope cannot be repaired when running with administrator privileges. + + + Repair operations involving administrator privileges are not permitted on packages installed within the user scope. + + + Repair failed with exit code: {0} + {Locked="{0}"} Error message displayed when an attempt to repair an application package fails. {0} is a placeholder replaced by an error code. + + + The SQLite connection was terminated to prevent corruption. + + + Uninstall all versions + + + The version to act upon + + + Multiple versions of this package are installed. Either refine the search, pass the `--version` argument to select one, or pass the `--all-versions` flag to uninstall all of them. + {Locked="--version,--all-versions"} + + + Enable Windows Package Manager proxy command line options + Describes a Group Policy that can enable the use of the --proxy option to set a proxy + + + Enable Windows Package Manager Configuration processor path + Describes a Group Policy that can enable the use of the --processor-path option in configuration commands + + + Set a proxy to use for this execution + + + Disable the use of proxy for this execution + + + Disables the progress bar and spinner + + + Cannot reset {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Reset admin setting '{0}'. + {Locked="{0}"} Message displayed after the user resets an admin setting to its default value. Reset is used as verb in past tense. {0} is a placeholder replaced by the setting name. + + + Cannot set {0}. This setting is controlled by policy. For more information contact your system administrator. + {Locked="{0}"} The value will be replaced with the feature name + + + Set admin setting '{0}' to '{1}'. + {Locked="{0}"} Message displayed after the user sets the value of an admin setting. Set is used as a verb in past tense. {0} is a placeholder replaced by the setting name. {1} is a placeholder replaced + + + Name of the setting to modify + + + Value to set for the setting. + + + Resets an admin setting to its default value. + + + Resets an admin setting to its default value. + + + Sets the value of an admin setting. + + + Sets the value of an admin setting. + + + Excludes a source from discovery unless specified + + + Excludes a source from discovery (true or false) + + + Explicit + Column header in 'winget source list' output. 'Explicit' here is a technical term meaning the source must be directly named/specified to be used—it is NOT automatically included in package discovery. Translate as the equivalent of "requires explicit specification" or "must be directly specified". Do NOT translate as "explicit content", "adult content", "mature content", or any phrase suggesting inappropriate material. + + + Trust level of the source (none or trusted) + + + Trust Level + + + Failed to get Microsoft Store package catalog. + + + No applicable Microsoft Store package found from Microsoft Store package catalog. + + + Failed to get Microsoft Store package download information. + + + No applicable Microsoft Store package found for download. + + + Failed to retrieve Microsoft Store package license. + + + No applicable Microsoft Store package found. + + + Successfully verified Microsoft Store package hash + + + Microsoft Store package hash mismatch + + + Microsoft Store package downloaded: {0} + {Locked="{0}"} Full path of the downloaded package. + + + Microsoft Store package download failed: {0} + {Locked="{0}"} Package name. + + + Microsoft Store package download completed + + + Downloading main packages from Microsoft Store... + + + Downloading dependency packages from Microsoft Store... + + + Retrieving Microsoft Store package download information + + + Failed to retrieve Microsoft Store package download information + + + Retrieving Microsoft Store package license + + + Microsoft Store package license saved: {0} + {Locked="{0}"} License file full path. + + + Failed to retrieve Microsoft Store package license + + + Microsoft Store package download does not support --rename argument. Microsoft Store package will use names provided by Microsoft Store catalog. + {Locked="--rename"} + + + Microsoft Store package download requires Microsoft Entra Id authentication. Authentication prompt may appear when necessary. Authenticated information will be shared with Microsoft services for access authorization. For Microsoft Store package licensing, the Microsoft Entra Id account needs to be a member of Global Administrator, User Administrator, or License Administrator. + {Locked="Global Administrator,User Administrator,License Administrator"} + + + Skips retrieving Microsoft Store package offline license + + + Select the target platform + + + Adding configuration file: {0} + {Locked="{0}"} + + + Successfully exported + + + Getting configuration settings... + + + At least --packageId and/or --module with --resource must be provided. Or use --all to export all package configurations. + {Locked="--packageId,--module, --resource, --all"} + + + Arguments --packageId, --module and --resource cannot be used with --all. + {Locked="--packageId,--module, --resource, --all"} + + + Exports configuration resources to a configuration file. When used with --all, exports all package configurations. When used with --packageId, exports a WinGetPackage resource of the given package id. When used with --module and --resource, gets the settings of the resource and exports it to the configuration file. If the output configuration file already exists, appends the exported configuration resources. + {Locked="WinGetPackage,--packageId,--module, --resource"} + + + Exports configuration resources to a configuration file. + + + The module of the resource to export. + + + The package identifier to export. + + + The configuration resource to export. + + + Exports all package configurations. + + + The configuration unit failed getting its properties. + + + Failed exporting configuration. + + + Configure {0} + {Locked="{0}"} + + + Install {0} + {Locked="{0}"} + + + Some of the data present in the configuration file was truncated for this output; inspect the file contents for the complete content. + + + <this value has been truncated; inspect the file contents for the complete text> + Keep some form of separator like the "<>" around the text so that it stands out from the preceding text. + + + Shows the high level details for configurations that have been applied to the system. This data can then be used with `configure` commands to get more details. + {Locked="`configure`"} + + + Shows configuration history + + + There are no configurations in the history. + + + Select items from history + + + No single configuration could be found that matched the provided data. Provide either the full name or part of the identifier that unambiguously matches the desired configuration. + + + Remove the item from history + + + First Applied + Column header for date values indicating when a configuration was first applied to the system. + + + Identifier + + + Name + + + Origin + + + Path + + + The specified configuration could not be found. + + + Completed + The state of processing an item. + + + In progress + The state of processing an item. + + + Pending + The state of processing an item. + + + Unknown + The state of processing an item. + + + Monitor configuration status. + As in "to monitor the status of a configuration being applied". + + + Completed + The state of processing an item. + + + In progress + The state of processing an item. + + + Pending + The state of processing an item. + + + Skipped + The state of processing an item. + + + Unknown + The state of processing an item. + + + Apply Started + When the configuration application started. + + + Apply Ended + When the configuration application ended. + + + Result + + + Details + + + State + The state of processing an item. + + + Unit + + + The package is not compatible with the current Windows version or platform. + + + Suppress showing initial configuration details when possible + + + Multiple Microsoft Store packages may be downloaded targeting different platforms and architectures. --platform, --architecture can be used to filter the packages. + {Locked="--platform--architecture"} + + + Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account is not a member of Global Administrator, User Administrator, or License Administrator. Use --skip-license to skip retrieving Microsoft Store package license. + {Locked="Global Administrator,User Administrator,License Administrator,--skip-license"} + + + The Microsoft Store package does not support download. + + + The Microsoft Store package does not support download. + + + Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have required privilege. + + + Parameter cannot be passed across integrity boundary. + + + Downloaded zero byte installer; ensure that your network connection is working properly. + + + Downloaded zero byte installer; ensure that your network connection is working properly. + + + Manage fonts + + + Manage fonts with sub-commands. Fonts can be installed, upgraded, or uninstalled similar to packages. + + + Family + Column header for the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate as a family of people or a surname. + + + Faces + Column header listing the typefaces within a font family, e.g. 'Bold', 'Italic', 'Bold Italic'. Do NOT translate as human/anatomical faces. + + + Filter results by family name + 'Family name' refers to the font family name, e.g. 'Arial' or 'Times New Roman'. Do NOT translate 'family' as a family of people or 'family name' as a surname. + + + Face + A single typeface within a font family, e.g. 'Bold' or 'Italic'. Do NOT translate as a human/anatomical face. + + + Paths + + + No installed font found matching input criteria. + + + List installed fonts + + + List all installed fonts, all font files, or full details of a specific font. + + + 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. + + + Specify the path to the configuration processor + + + Custom processor path: + Displayed as a warning header when a custom --processor-path argument is provided. + + + Hash: {0} + {Locked="{0}"} {0} is the SHA256 hash hex string of the processor executable or its reparse data. + + + Type: App execution alias + Indicates the processor path is an app execution alias (MSIX package link). + + + Path: {0} + {Locked="{0}"} {0} is the file system path provided by the user. + + + Signed By: {0} + {Locked="{0}"} {0} is the Authenticode signing subject name of the executable. + + + Signed By: (unsigned) + Indicates the custom processor executable does not have an Authenticode signature. + + + The integrity check failed for the custom DSC processor path. The path may have been modified. + Error when the server-side hash of the processor path does not match the client-computed hash. + + + DSC v3 resource commands + DSC stands for "Desired State Configuration". It should already have a locked translation. + + + The sub-commands here implement Desired State Configuration (DSC) v3 resources for configuring WinGet and packages. + {Locked="WinGet"} + + + Get the resource state + + + Set the resource state + + + Describe required state changes + + + Test the resource state + + + Delete the resource state + + + Get all state instances + + + Validate group contents + + + Resolve external state + + + Run the adapter + + + Get the resource schema + + + Get the resource manifest + + + Manage package state + + + Manage packages through WinGet. + {Locked="WinGet"} + + + Indicates whether an instance should or does exist. + + + Indicates whether an instance is in the desired state. + + + The identifier of the package. + + + The source of the package. + + + The version of the package. + + + The method for matching the identifier with a package. + + + Indicate that the latest available version of the package should be installed. + + + The install mode to use if needed. + + + The target scope of the package. + + + Indicates whether to accept agreements for sources and packages. + + + Manage user settings file + + + Manage the user settings of WinGet. + {Locked="WinGet"} + + + The settings json file content. + + + The action used to apply the settings. + + + Value is logged for correlation + + + Manage source configuration + + + Manage the sources of WinGet. + {Locked="WinGet"} + + + The name to use for the source. + + + The argument for the source. + + + The type of the source. + + + The trust level of the source. + + + Whether the source is included when calls don't specify a source. + + + Applying configuration unit... + + + Exporting configuration unit... + + + Getting configuration unit processors... + + + Ensure required module for export [{0}] + {Locked="{0}"} + + + Failed to test or acquire required module. Related settings will not be exported. + + + Export [{0}] + {Locked="{0}"} + + + Failed to export the resource. + + + Failed to get unit processors. Individual package settings will not be exported. + + + Directory where the results are to be written + + + Desired State Configuration package not found on the system. Installing the package... + + + Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through --processor-path argument. + {Locked="dsc.exe","--processor-path"} + + + An object containing the administrator settings and their values. + + + Manage administrator settings + + + Manage the administrator settings of WinGet. + {Locked="WinGet"} + + + Resets all admin settings + + + All admin settings reset. + + + MCP information + MCP stands for Model Context Protocol and should probably remain as-is + + + MCP (Model Context Protocol) information for the Windows Package Manager. + + + To manually configure the Windows Package Manager MCP server with your MCP client, use the following JSON fragment in the `servers` object: + {Locked="`servers`"} +MCP stands for Model Context Protocol and should probably remain as-is. +An unlocalized JSON fragment will follow on another line. + + + Enable Windows Package Manager MCP Server + + + Target OS version + + + Failed installing one or more fonts. + + + Font file is not supported and cannot be installed. + + + One or more fonts in the font package is not supported and cannot be installed. + + + Font install failed; cleaning up. + + + Font already installed. + + + Package Id + + + WinGet Supported + + + Title + + + Font file not found. + + + Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart. + + + Font validation failed. + + + Font rollback failed. The font may not be in a good state. Try uninstalling after a restart. + + + Show font file detailed information. + + + Validation of the font package failed. + + + Rollback attempt for failed font install was unsuccessful. A restart may be required to successfully uninstall the font. + + + Font package uninstall failed. This is often due to the fonts being in use by the system or an application. Uninstall may be successful after restarting your computer. + + + OK + "OK" means the font is in a good healthy state + + + Corrupt + "Corrupt" refers to an install that is in a corrupted or bad state, and needs repair. + + + Status + + + Unknown + + + Font package is already installed. + + + Show detailed information about packages + Providing this argument causes the CLI to output additional details about installed application packages. + + + Channel: + Precedes a string value that names the delivery channel for the software package (ex. stable, beta). + + + Local Identifier: + Precedes a value that is the unique identifier for the installed package on the local system. + + + Package Family Name: + Precedes a value that is the APPX/MSIX package family name of the installed package. + + + Product Code: + Precedes a value that is the Add/Remove Programs identifier in the registry. This is also the Product Code value as defined in MSI installers. + + + Upgrade Code: + Precedes a value that is the MSI Upgrade Code for the installed package. + + + Installed Scope: + Precedes a value that is the scope of the installation of the package (ex. user, machine). + + + Installed Architecture: + Precedes a value that is the installed architecture of the package (ex. x86, x64, ARM64). + + + Installed Locale: + Precedes a value that is the locale of the installed package. + + + Installed Location: + Precedes a value that is the directory path to the installed package. + + + Origin Source: + Precedes a value that names the package source where the installed package originated from. + + + Available Upgrades: + Precedes a list of package upgrades available for the installed package. + + + Installer Category: + Precedes a value that indicates the category of the installer for the installed package (ex. exe, msi, msix). + + + Old Value + Column title for listing edit changes. + + + New Value + Column title for listing the new value. + + + Priority; higher numbers first + This argument sets the numerical priority of the source. Higher values will be first in priority. + + + Priority + Label for the priority of the source with respect to other sources. Higher values come first in the order. + + + The priority of the source. Higher values are sorted first in the order. + + + Results have been filtered to the highest matched source priority. + A warning message to indicate to the user that the results of a search have been filtered by choosing only those matching the highest source priority amongst the results. + + + The DSC processor hash provided does not match hash of the target file. + \ No newline at end of file diff --git a/src/AppInstallerCLITests/ARPChanges.cpp b/src/AppInstallerCLITests/ARPChanges.cpp index 04728aa9d3..66b4481c05 100644 --- a/src/AppInstallerCLITests/ARPChanges.cpp +++ b/src/AppInstallerCLITests/ARPChanges.cpp @@ -1,459 +1,459 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSource.h" -#include "TestHooks.h" -#include -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Logging; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Correlation; - -struct TestTelemetry : public TelemetryTraceLogger -{ - void LogSuccessfulInstallARPChange( - std::string_view sourceIdentifier, - std::string_view packageIdentifier, - std::string_view packageVersion, - std::string_view packageChannel, - size_t changesToARP, - size_t matchesInARP, - size_t countOfIntersectionOfChangesAndMatches, - std::string_view arpName, - std::string_view arpVersion, - std::string_view arpPublisher, - std::string_view arpLanguage) const noexcept override - { - WasLogSuccessfulInstallARPChangeCalled = true; - if (OnLogSuccessfulInstallARPChange) - { - OnLogSuccessfulInstallARPChange( - sourceIdentifier, packageIdentifier, packageVersion, packageChannel, - changesToARP, matchesInARP, countOfIntersectionOfChangesAndMatches, - arpName, arpVersion, arpPublisher, arpLanguage); - } - } - - std::function OnLogSuccessfulInstallARPChange; - - mutable bool WasLogSuccessfulInstallARPChangeCalled = false; -}; - -struct ARPTestContext : public Context -{ - ARPTestContext(Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Exe) : - Context(OStream, IStream), SourceFactory([this](const SourceDetails&) { return Source; }) - { - // Put installer in to control whether arp change code cares to run - Manifest::ManifestInstaller installer; - installer.BaseInstallerType = installerType; - Add(std::move(installer)); - - // Put in an empty manifest by default - Manifest::Manifest manifest; - manifest.Id = "Installing.Id"; - manifest.Version = "Installing.Version"; - manifest.Channel = "Installing.Channel"; - manifest.DefaultLocalization.Add("Installing.Name"); - Add(std::move(manifest)); - - // Set up logger to intercept event - Logger = std::make_shared(); - TestHook_SetTelemetryOverride(Logger); - - Logger->OnLogSuccessfulInstallARPChange = [this]( - std::string_view sourceIdentifier, - std::string_view packageIdentifier, - std::string_view packageVersion, - std::string_view packageChannel, - size_t changesToARP, - size_t matchesInARP, - size_t countOfIntersectionOfChangesAndMatches, - std::string_view arpName, - std::string_view arpVersion, - std::string_view arpPublisher, - std::string_view arpLanguage) - { - SourceIdentifier = sourceIdentifier; - PackageIdentifier = packageIdentifier; - PackageVersion = packageVersion; - PackageChannel = packageChannel; - ChangesToARP = changesToARP; - MatchesInARP = matchesInARP; - CountOfIntersectionOfChangesAndMatches = countOfIntersectionOfChangesAndMatches; - ARPName = arpName; - ARPVersion = arpVersion; - ARPPublisher = arpPublisher; - ARPLanguage = arpLanguage; - }; - - // Inject our source - TestHook_SetSourceFactoryOverride(std::string{ Repository::Microsoft::PredefinedInstalledSourceFactory::Type() }, SourceFactory); - - Source = std::make_shared(); - Source->SearchFunction = [&](const SearchRequest& request) - { - return request.IsForEverything() ? EverythingResult : MatchResult; - }; - - // The package version is used to get the source identifier - Add(TestPackageVersion::Make(Get(), Source)); - - // Populate everything result with a few items - AddEverythingResult("Id1", "Name1", "Publisher1", "1.0"); - AddEverythingResult("Id2", "Name2", "Publisher2", "2.0"); - } - - ~ARPTestContext() - { - TestHook_ClearSourceFactoryOverrides(); - TestHook_SetTelemetryOverride({}); - } - - void AddEverythingResult(std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) - { - AddResult(EverythingResult, id, name, publisher, version); - } - - void AddMatchResult(std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) - { - AddResult(MatchResult, id, name, publisher, version); - } - - void ExpectEvent(size_t arpChanges, size_t matches, size_t overlap, const std::shared_ptr& arpEntry = nullptr) - { - REQUIRE(Logger->WasLogSuccessfulInstallARPChangeCalled); - - const auto& manifest = Get(); - - REQUIRE(Source->GetIdentifier() == SourceIdentifier); - REQUIRE(manifest.Id == PackageIdentifier); - REQUIRE(manifest.Version == PackageVersion); - REQUIRE(manifest.Channel == PackageChannel); - REQUIRE(arpChanges == ChangesToARP); - REQUIRE(matches == MatchesInARP); - REQUIRE(overlap == CountOfIntersectionOfChangesAndMatches); - - if (arpEntry) - { - auto version = GetInstalledVersion(arpEntry); - REQUIRE(version->GetProperty(PackageVersionProperty::Name) == ARPName); - REQUIRE(version->GetProperty(PackageVersionProperty::Version) == ARPVersion); - - auto metadata = version->GetMetadata(); - REQUIRE(metadata[PackageVersionMetadata::Publisher] == ARPPublisher); - REQUIRE(metadata[PackageVersionMetadata::InstalledLocale] == ARPLanguage); - } - else - { - REQUIRE(ARPName.empty()); - REQUIRE(ARPVersion.empty()); - REQUIRE(ARPPublisher.empty()); - REQUIRE(ARPLanguage.empty()); - } - } - - std::ostringstream OStream; - std::istringstream IStream; - std::shared_ptr Logger; - TestSourceFactory SourceFactory; - std::shared_ptr Source; - SearchResult EverythingResult; - SearchResult MatchResult; - - // EventData - std::string SourceIdentifier; - std::string PackageIdentifier; - std::string PackageVersion; - std::string PackageChannel; - size_t ChangesToARP; - size_t MatchesInARP; - size_t CountOfIntersectionOfChangesAndMatches; - std::string ARPName; - std::string ARPVersion; - std::string ARPPublisher; - std::string ARPLanguage; - - private: - void AddResult(SearchResult& result, std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) - { - PackageMatchFilter defaultFilter{ PackageMatchField::Id, MatchType::Exact }; - Manifest::Manifest manifest; - - manifest.Id = id; - manifest.DefaultLocalization.Add(name); - manifest.DefaultLocalization.Add(publisher); - manifest.Version = version; - manifest.Installers.push_back({}); - - TestPackage::MetadataMap metadata; - metadata[PackageVersionMetadata::Publisher] = publisher; - - result.Matches.emplace_back(TestCompositePackage::Make(manifest, std::move(metadata), std::vector{}, Source), defaultFilter); - } -}; - -// Override the correlation heuristic by an empty one to ensure that these tests -// consider only the exact matching. -struct TestHeuristicOverride -{ - TestHeuristicOverride() - { - IARPMatchConfidenceAlgorithm::OverrideInstance(&m_algorithm); - } - - ~TestHeuristicOverride() - { - IARPMatchConfidenceAlgorithm::ResetInstance(); - } - -private: - EmptyMatchConfidenceAlgorithm m_algorithm; -}; - -TEST_CASE("ARPChanges_MSIX_Ignored", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context(Manifest::InstallerTypeEnum::Msix); - - context << SnapshotARPEntries; - - REQUIRE(!context.Contains(Data::ARPCorrelationData)); - - context << ReportARPChanges; - - REQUIRE(!context.Logger->WasLogSuccessfulInstallARPChangeCalled); -} - -TEST_CASE("ARPChanges_CheckSnapshot", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - auto snapshot = context.Get().GetPreInstallSnapshot(); - - REQUIRE(context.EverythingResult.Matches.size() == snapshot.size()); - - // Destructively match - for (const auto& match : context.EverythingResult.Matches) - { - bool found = false; - for (auto itr = snapshot.begin(); itr != snapshot.end(); ++itr) - { - if (match.Package->GetProperty(PackageProperty::Id) == std::get<0>(*itr)) - { - REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Version) == std::get<1>(*itr)); - REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Channel) == std::get<2>(*itr)); - - snapshot.erase(itr); - found = true; - break; - } - } - REQUIRE(found); - } - - REQUIRE(snapshot.empty()); -} - -TEST_CASE("ARPChanges_NoChange_NoMatch", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context << ReportARPChanges; - context.ExpectEvent(0, 0, 0); -} - -TEST_CASE("ARPChanges_NoChange_SingleMatch", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); - - context << ReportARPChanges; - context.ExpectEvent(0, 1, 0, context.MatchResult.Matches[0].Package); -} - -TEST_CASE("ARPChanges_NoChange_MultiMatch", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); - context.AddMatchResult("MatchId2", "MatchName2", "MatchPublisher2", "MatchVersion2"); - - context << ReportARPChanges; - context.ExpectEvent(0, 2, 0); -} - -TEST_CASE("ARPChanges_SingleChange_NoMatch", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - - context << ReportARPChanges; - context.ExpectEvent(1, 0, 0); -} - -TEST_CASE("ARPChanges_SingleChange_SingleMatch", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); - - context << ReportARPChanges; - context.ExpectEvent(1, 1, 0, context.MatchResult.Matches.back().Package); -} - -TEST_CASE("ARPChanges_SingleChange_MultiMatch", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); - context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); - - context << ReportARPChanges; - context.ExpectEvent(1, 2, 1, context.EverythingResult.Matches.back().Package); -} - -TEST_CASE("ARPChanges_MultiChange_NoMatch", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); - - context << ReportARPChanges; - context.ExpectEvent(2, 0, 0); -} - -TEST_CASE("ARPChanges_MultiChange_SingleMatch_NoOverlap", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); - context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); - - context << ReportARPChanges; - context.ExpectEvent(2, 1, 0, context.MatchResult.Matches.back().Package); -} - -TEST_CASE("ARPChanges_MultiChange_SingleMatch_Overlap", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); - context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); - - context << ReportARPChanges; - context.ExpectEvent(2, 1, 1, context.MatchResult.Matches.back().Package); -} - -TEST_CASE("ARPChanges_MultiChange_MultiMatch_NoOverlap", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); - context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); - context.AddMatchResult("MatchId2", "MatchName2", "MatchPublisher2", "MatchVersion2"); - - context << ReportARPChanges; - context.ExpectEvent(2, 2, 0); -} - -TEST_CASE("ARPChanges_MultiChange_MultiMatch_SingleOverlap", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); - context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); - context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); - - context << ReportARPChanges; - context.ExpectEvent(2, 2, 1, context.MatchResult.Matches.back().Package); -} - -TEST_CASE("ARPChanges_MultiChange_MultiMatch_MultiOverlap", "[ARPChanges][workflow]") -{ - TestHeuristicOverride heuristicOverride; - ARPTestContext context; - - context << SnapshotARPEntries; - REQUIRE(context.Contains(Data::ARPCorrelationData)); - - context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); - context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); - context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); - context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); - - context << ReportARPChanges; - context.ExpectEvent(2, 2, 2); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSource.h" +#include "TestHooks.h" +#include +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Logging; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Correlation; + +struct TestTelemetry : public TelemetryTraceLogger +{ + void LogSuccessfulInstallARPChange( + std::string_view sourceIdentifier, + std::string_view packageIdentifier, + std::string_view packageVersion, + std::string_view packageChannel, + size_t changesToARP, + size_t matchesInARP, + size_t countOfIntersectionOfChangesAndMatches, + std::string_view arpName, + std::string_view arpVersion, + std::string_view arpPublisher, + std::string_view arpLanguage) const noexcept override + { + WasLogSuccessfulInstallARPChangeCalled = true; + if (OnLogSuccessfulInstallARPChange) + { + OnLogSuccessfulInstallARPChange( + sourceIdentifier, packageIdentifier, packageVersion, packageChannel, + changesToARP, matchesInARP, countOfIntersectionOfChangesAndMatches, + arpName, arpVersion, arpPublisher, arpLanguage); + } + } + + std::function OnLogSuccessfulInstallARPChange; + + mutable bool WasLogSuccessfulInstallARPChangeCalled = false; +}; + +struct ARPTestContext : public Context +{ + ARPTestContext(Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Exe) : + Context(OStream, IStream), SourceFactory([this](const SourceDetails&) { return Source; }) + { + // Put installer in to control whether arp change code cares to run + Manifest::ManifestInstaller installer; + installer.BaseInstallerType = installerType; + Add(std::move(installer)); + + // Put in an empty manifest by default + Manifest::Manifest manifest; + manifest.Id = "Installing.Id"; + manifest.Version = "Installing.Version"; + manifest.Channel = "Installing.Channel"; + manifest.DefaultLocalization.Add("Installing.Name"); + Add(std::move(manifest)); + + // Set up logger to intercept event + Logger = std::make_shared(); + TestHook_SetTelemetryOverride(Logger); + + Logger->OnLogSuccessfulInstallARPChange = [this]( + std::string_view sourceIdentifier, + std::string_view packageIdentifier, + std::string_view packageVersion, + std::string_view packageChannel, + size_t changesToARP, + size_t matchesInARP, + size_t countOfIntersectionOfChangesAndMatches, + std::string_view arpName, + std::string_view arpVersion, + std::string_view arpPublisher, + std::string_view arpLanguage) + { + SourceIdentifier = sourceIdentifier; + PackageIdentifier = packageIdentifier; + PackageVersion = packageVersion; + PackageChannel = packageChannel; + ChangesToARP = changesToARP; + MatchesInARP = matchesInARP; + CountOfIntersectionOfChangesAndMatches = countOfIntersectionOfChangesAndMatches; + ARPName = arpName; + ARPVersion = arpVersion; + ARPPublisher = arpPublisher; + ARPLanguage = arpLanguage; + }; + + // Inject our source + TestHook_SetSourceFactoryOverride(std::string{ Repository::Microsoft::PredefinedInstalledSourceFactory::Type() }, SourceFactory); + + Source = std::make_shared(); + Source->SearchFunction = [&](const SearchRequest& request) + { + return request.IsForEverything() ? EverythingResult : MatchResult; + }; + + // The package version is used to get the source identifier + Add(TestPackageVersion::Make(Get(), Source)); + + // Populate everything result with a few items + AddEverythingResult("Id1", "Name1", "Publisher1", "1.0"); + AddEverythingResult("Id2", "Name2", "Publisher2", "2.0"); + } + + ~ARPTestContext() + { + TestHook_ClearSourceFactoryOverrides(); + TestHook_SetTelemetryOverride({}); + } + + void AddEverythingResult(std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) + { + AddResult(EverythingResult, id, name, publisher, version); + } + + void AddMatchResult(std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) + { + AddResult(MatchResult, id, name, publisher, version); + } + + void ExpectEvent(size_t arpChanges, size_t matches, size_t overlap, const std::shared_ptr& arpEntry = nullptr) + { + REQUIRE(Logger->WasLogSuccessfulInstallARPChangeCalled); + + const auto& manifest = Get(); + + REQUIRE(Source->GetIdentifier() == SourceIdentifier); + REQUIRE(manifest.Id == PackageIdentifier); + REQUIRE(manifest.Version == PackageVersion); + REQUIRE(manifest.Channel == PackageChannel); + REQUIRE(arpChanges == ChangesToARP); + REQUIRE(matches == MatchesInARP); + REQUIRE(overlap == CountOfIntersectionOfChangesAndMatches); + + if (arpEntry) + { + auto version = GetInstalledVersion(arpEntry); + REQUIRE(version->GetProperty(PackageVersionProperty::Name) == ARPName); + REQUIRE(version->GetProperty(PackageVersionProperty::Version) == ARPVersion); + + auto metadata = version->GetMetadata(); + REQUIRE(metadata[PackageVersionMetadata::Publisher] == ARPPublisher); + REQUIRE(metadata[PackageVersionMetadata::InstalledLocale] == ARPLanguage); + } + else + { + REQUIRE(ARPName.empty()); + REQUIRE(ARPVersion.empty()); + REQUIRE(ARPPublisher.empty()); + REQUIRE(ARPLanguage.empty()); + } + } + + std::ostringstream OStream; + std::istringstream IStream; + std::shared_ptr Logger; + TestSourceFactory SourceFactory; + std::shared_ptr Source; + SearchResult EverythingResult; + SearchResult MatchResult; + + // EventData + std::string SourceIdentifier; + std::string PackageIdentifier; + std::string PackageVersion; + std::string PackageChannel; + size_t ChangesToARP; + size_t MatchesInARP; + size_t CountOfIntersectionOfChangesAndMatches; + std::string ARPName; + std::string ARPVersion; + std::string ARPPublisher; + std::string ARPLanguage; + + private: + void AddResult(SearchResult& result, std::string_view id, std::string_view name, std::string_view publisher, std::string_view version) + { + PackageMatchFilter defaultFilter{ PackageMatchField::Id, MatchType::Exact }; + Manifest::Manifest manifest; + + manifest.Id = id; + manifest.DefaultLocalization.Add(name); + manifest.DefaultLocalization.Add(publisher); + manifest.Version = version; + manifest.Installers.push_back({}); + + TestPackage::MetadataMap metadata; + metadata[PackageVersionMetadata::Publisher] = publisher; + + result.Matches.emplace_back(TestCompositePackage::Make(manifest, std::move(metadata), std::vector{}, Source), defaultFilter); + } +}; + +// Override the correlation heuristic by an empty one to ensure that these tests +// consider only the exact matching. +struct TestHeuristicOverride +{ + TestHeuristicOverride() + { + IARPMatchConfidenceAlgorithm::OverrideInstance(&m_algorithm); + } + + ~TestHeuristicOverride() + { + IARPMatchConfidenceAlgorithm::ResetInstance(); + } + +private: + EmptyMatchConfidenceAlgorithm m_algorithm; +}; + +TEST_CASE("ARPChanges_MSIX_Ignored", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context(Manifest::InstallerTypeEnum::Msix); + + context << SnapshotARPEntries; + + REQUIRE(!context.Contains(Data::ARPCorrelationData)); + + context << ReportARPChanges; + + REQUIRE(!context.Logger->WasLogSuccessfulInstallARPChangeCalled); +} + +TEST_CASE("ARPChanges_CheckSnapshot", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + auto snapshot = context.Get().GetPreInstallSnapshot(); + + REQUIRE(context.EverythingResult.Matches.size() == snapshot.size()); + + // Destructively match + for (const auto& match : context.EverythingResult.Matches) + { + bool found = false; + for (auto itr = snapshot.begin(); itr != snapshot.end(); ++itr) + { + if (match.Package->GetProperty(PackageProperty::Id) == std::get<0>(*itr)) + { + REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Version) == std::get<1>(*itr)); + REQUIRE(GetInstalledVersion(match.Package)->GetProperty(PackageVersionProperty::Channel) == std::get<2>(*itr)); + + snapshot.erase(itr); + found = true; + break; + } + } + REQUIRE(found); + } + + REQUIRE(snapshot.empty()); +} + +TEST_CASE("ARPChanges_NoChange_NoMatch", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context << ReportARPChanges; + context.ExpectEvent(0, 0, 0); +} + +TEST_CASE("ARPChanges_NoChange_SingleMatch", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); + + context << ReportARPChanges; + context.ExpectEvent(0, 1, 0, context.MatchResult.Matches[0].Package); +} + +TEST_CASE("ARPChanges_NoChange_MultiMatch", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); + context.AddMatchResult("MatchId2", "MatchName2", "MatchPublisher2", "MatchVersion2"); + + context << ReportARPChanges; + context.ExpectEvent(0, 2, 0); +} + +TEST_CASE("ARPChanges_SingleChange_NoMatch", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + + context << ReportARPChanges; + context.ExpectEvent(1, 0, 0); +} + +TEST_CASE("ARPChanges_SingleChange_SingleMatch", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); + + context << ReportARPChanges; + context.ExpectEvent(1, 1, 0, context.MatchResult.Matches.back().Package); +} + +TEST_CASE("ARPChanges_SingleChange_MultiMatch", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); + context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); + + context << ReportARPChanges; + context.ExpectEvent(1, 2, 1, context.EverythingResult.Matches.back().Package); +} + +TEST_CASE("ARPChanges_MultiChange_NoMatch", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); + + context << ReportARPChanges; + context.ExpectEvent(2, 0, 0); +} + +TEST_CASE("ARPChanges_MultiChange_SingleMatch_NoOverlap", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); + context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); + + context << ReportARPChanges; + context.ExpectEvent(2, 1, 0, context.MatchResult.Matches.back().Package); +} + +TEST_CASE("ARPChanges_MultiChange_SingleMatch_Overlap", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); + context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); + + context << ReportARPChanges; + context.ExpectEvent(2, 1, 1, context.MatchResult.Matches.back().Package); +} + +TEST_CASE("ARPChanges_MultiChange_MultiMatch_NoOverlap", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); + context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); + context.AddMatchResult("MatchId2", "MatchName2", "MatchPublisher2", "MatchVersion2"); + + context << ReportARPChanges; + context.ExpectEvent(2, 2, 0); +} + +TEST_CASE("ARPChanges_MultiChange_MultiMatch_SingleOverlap", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); + context.AddMatchResult("MatchId1", "MatchName1", "MatchPublisher1", "MatchVersion1"); + context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); + + context << ReportARPChanges; + context.ExpectEvent(2, 2, 1, context.MatchResult.Matches.back().Package); +} + +TEST_CASE("ARPChanges_MultiChange_MultiMatch_MultiOverlap", "[ARPChanges][workflow]") +{ + TestHeuristicOverride heuristicOverride; + ARPTestContext context; + + context << SnapshotARPEntries; + REQUIRE(context.Contains(Data::ARPCorrelationData)); + + context.AddEverythingResult("EverythingId1", "EverythingName1", "EverythingPublisher1", "EverythingVersion1"); + context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); + context.AddEverythingResult("EverythingId2", "EverythingName2", "EverythingPublisher2", "EverythingVersion2"); + context.MatchResult.Matches.emplace_back(context.EverythingResult.Matches.back()); + + context << ReportARPChanges; + context.ExpectEvent(2, 2, 2); +} diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj index ad2925f603..1cdebf305d 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj @@ -1,1133 +1,1133 @@ - - - - - true - true - true - true - 15.0 - {89b1aab4-2bbc-4b65-9ed7-a01d5cf88230} - Win32Proj - AppInstallerCLITests - 10.0.26100.0 - 10.0.17763.0 - AppInstallerCLITests - - - - - - - Debug - ARM64 - - - Debug - Win32 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - Application - - - true - true - - - false - true - false - - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - Level4 - 5321;%(DisableSpecificWarnings) - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - true - true - false - false - - - Console - false - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - gdi32.dll - gdi32.dll - - - $(ProjectDir)..\manifest\shared.manifest - $(ProjectDir)..\manifest\shared.manifest - - - copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" - copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" - - - - - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - true - false - - - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - gdi32.dll - - - $(ProjectDir)..\manifest\shared.manifest - - - copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) - true - true - true - false - false - false - - - Console - true - true - false - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - gdi32.dll - gdi32.dll - gdi32.dll - - - $(ProjectDir)..\manifest\shared.manifest - $(ProjectDir)..\manifest\shared.manifest - - - $(ProjectDir)..\manifest\shared.manifest - - - copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" - - - copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" - copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - Create - - - - - - - - - - - - - - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - true - - - - - {1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d} - - - {5890d6ed-7c3b-40f3-b436-b54f640d9e65} - - - {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} - - - {ca460806-5e41-4e97-9a3d-1d74b433b663} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + true + true + true + true + 15.0 + {89b1aab4-2bbc-4b65-9ed7-a01d5cf88230} + Win32Proj + AppInstallerCLITests + 10.0.26100.0 + 10.0.17763.0 + AppInstallerCLITests + + + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + Application + + + true + true + + + false + true + false + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + Level4 + 5321;%(DisableSpecificWarnings) + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + true + true + false + false + + + Console + false + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + gdi32.dll + gdi32.dll + + + $(ProjectDir)..\manifest\shared.manifest + $(ProjectDir)..\manifest\shared.manifest + + + copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" + copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" + + + + + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + true + false + + + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + gdi32.dll + + + $(ProjectDir)..\manifest\shared.manifest + + + copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + $(MSBuildThisFileDirectory)..\AppInstallerCommonCore;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerRepositoryCore;$(MSBuildThisFileDirectory)..\AppInstallerCommonCore\Public;$(MSBuildThisFileDirectory)..\AppInstallerSharedLib\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore\Public;$(MSBuildThisFileDirectory)..\AppInstallerCLICore;%(AdditionalIncludeDirectories) + true + true + true + false + false + false + + + Console + true + true + false + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + gdi32.dll + gdi32.dll + gdi32.dll + + + $(ProjectDir)..\manifest\shared.manifest + $(ProjectDir)..\manifest\shared.manifest + + + $(ProjectDir)..\manifest\shared.manifest + + + copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" + + + copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" + copy "$(ProjectDir)..\AppInstallerCLIPackage\bin\$(PlatformTarget)\$(configuration)\resources.pri" "$(TargetDir)\resources.pri" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + Create + + + + + + + + + + + + + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + true + + + + + {1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d} + + + {5890d6ed-7c3b-40f3-b436-b54f640d9e65} + + + {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + + {ca460806-5e41-4e97-9a3d-1d74b433b663} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + diff --git a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters index 348f9cdfda..bf64a51757 100644 --- a/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters +++ b/src/AppInstallerCLITests/AppInstallerCLITests.vcxproj.filters @@ -1,1183 +1,1183 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {d5cac203-3846-4b39-a1cd-8de9303757b4} - - - {69fcd25c-e737-4d28-a6d1-39ce491bf293} - - - {81fadc81-4327-4b9e-b588-97155b770aa3} - - - {0215df7f-7a73-4cc0-9589-adf401329e59} - - - {52b26063-ccff-40ae-a420-243327dcca25} - - - {488e9b7a-d72e-470c-b73a-5abcb83ca2c6} - - - {82dd5390-16ce-4a4c-a61a-148013890cc4} - - - {3e7d4dff-5513-46dd-95d2-f616c867ebe4} - - - {3499d391-99c1-4fa3-ba8d-0ce846c2497b} - - - {69069174-d4b1-4070-80c0-37d416b07128} - - - {d6d7e50d-394d-4486-8f93-4a5312ca4bf6} - - - {b35e1a8b-1961-46d5-98e5-adc8e52c54a9} - - - {d055e02a-59c9-4ae4-9405-579dac6ff59b} - - - {13d4d227-0f04-4e57-a663-c3c535438ab3} - - - {6f8102b7-ea5f-4379-bb59-067ced63d970} - - - {6bd68492-3f96-4023-af35-63249cd18158} - - - {0e5f9ff4-6f22-46eb-8538-1c560a008f06} - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files\Repository - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Common - - - Source Files\CLI - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\Common - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files - - - Source Files\Common - - - Source Files\Common - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Repository - - - Source Files\Common - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\CLI - - - Source Files\CLI - - - Source Files - - - Source Files\CLI - - - Source Files\Common - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\CLI - - - Source Files\Common - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Common - - - Source Files\Common - - - Source Files\Repository - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Repository - - - Source Files\Common - - - Source Files\CLI - - - Source Files\Repository - - - Source Files\Common - - - Source Files\Repository - - - - - - - - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData\MultiFileManifestV1 - - - TestData\MultiFileManifestV1 - - - TestData\MultiFileManifestV1 - - - TestData\MultiFileManifestV1 - - - TestData\MultiFileManifestV1_1 - - - TestData\MultiFileManifestV1_1 - - - TestData\MultiFileManifestV1_1 - - - TestData\MultiFileManifestV1_1 - - - TestData\MultiFileManifestV1_2 - - - TestData\MultiFileManifestV1_2 - - - TestData\MultiFileManifestV1_2 - - - TestData\MultiFileManifestV1_2 - - - TestData\MultiFileManifestV1_4 - - - TestData\MultiFileManifestV1_4 - - - TestData\MultiFileManifestV1_4 - - - TestData\MultiFileManifestV1_4 - - - TestData\MultiFileManifestV1_5 - - - TestData\MultiFileManifestV1_5 - - - TestData\MultiFileManifestV1_5 - - - TestData\MultiFileManifestV1_5 - - - TestData\MultiFileManifestV1_6 - - - TestData\MultiFileManifestV1_6 - - - TestData\MultiFileManifestV1_6 - - - TestData\MultiFileManifestV1_6 - - - TestData\MultiFileManifestV1_7 - - - TestData\MultiFileManifestV1_7 - - - TestData\MultiFileManifestV1_7 - - - TestData\MultiFileManifestV1_7 - - - TestData\MultiFileManifestV1_9 - - - TestData\MultiFileManifestV1_9 - - - TestData\MultiFileManifestV1_9 - - - TestData\MultiFileManifestV1_9 - - - TestData\MultiFileManifestV1_10 - - - TestData\MultiFileManifestV1_10 - - - TestData\MultiFileManifestV1_10 - - - TestData\MultiFileManifestV1_10 - - - TestData\MultiFileManifestV1_12 - - - TestData\MultiFileManifestV1_12 - - - TestData\MultiFileManifestV1_12 - - - TestData\MultiFileManifestV1_12 - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData\Shadow\V1_5 - - - TestData\Shadow\V1_5 - - - TestData\Shadow\V1_5 - - - TestData\Shadow\V1_5 - - - TestData\Shadow\V1_5 - - - TestData\Shadow\V1_5 - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData\MultiFileManifestV1_28 - - - TestData\MultiFileManifestV1_28 - - - TestData\MultiFileManifestV1_28 - - - TestData\MultiFileManifestV1_28 - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - - TestData - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {d5cac203-3846-4b39-a1cd-8de9303757b4} + + + {69fcd25c-e737-4d28-a6d1-39ce491bf293} + + + {81fadc81-4327-4b9e-b588-97155b770aa3} + + + {0215df7f-7a73-4cc0-9589-adf401329e59} + + + {52b26063-ccff-40ae-a420-243327dcca25} + + + {488e9b7a-d72e-470c-b73a-5abcb83ca2c6} + + + {82dd5390-16ce-4a4c-a61a-148013890cc4} + + + {3e7d4dff-5513-46dd-95d2-f616c867ebe4} + + + {3499d391-99c1-4fa3-ba8d-0ce846c2497b} + + + {69069174-d4b1-4070-80c0-37d416b07128} + + + {d6d7e50d-394d-4486-8f93-4a5312ca4bf6} + + + {b35e1a8b-1961-46d5-98e5-adc8e52c54a9} + + + {d055e02a-59c9-4ae4-9405-579dac6ff59b} + + + {13d4d227-0f04-4e57-a663-c3c535438ab3} + + + {6f8102b7-ea5f-4379-bb59-067ced63d970} + + + {6bd68492-3f96-4023-af35-63249cd18158} + + + {0e5f9ff4-6f22-46eb-8538-1c560a008f06} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files\Repository + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Common + + + Source Files\CLI + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\Common + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files + + + Source Files\Common + + + Source Files\Common + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Repository + + + Source Files\Common + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\CLI + + + Source Files\CLI + + + Source Files + + + Source Files\CLI + + + Source Files\Common + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\CLI + + + Source Files\Common + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Common + + + Source Files\Common + + + Source Files\Repository + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Repository + + + Source Files\Common + + + Source Files\CLI + + + Source Files\Repository + + + Source Files\Common + + + Source Files\Repository + + + + + + + + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData\MultiFileManifestV1 + + + TestData\MultiFileManifestV1 + + + TestData\MultiFileManifestV1 + + + TestData\MultiFileManifestV1 + + + TestData\MultiFileManifestV1_1 + + + TestData\MultiFileManifestV1_1 + + + TestData\MultiFileManifestV1_1 + + + TestData\MultiFileManifestV1_1 + + + TestData\MultiFileManifestV1_2 + + + TestData\MultiFileManifestV1_2 + + + TestData\MultiFileManifestV1_2 + + + TestData\MultiFileManifestV1_2 + + + TestData\MultiFileManifestV1_4 + + + TestData\MultiFileManifestV1_4 + + + TestData\MultiFileManifestV1_4 + + + TestData\MultiFileManifestV1_4 + + + TestData\MultiFileManifestV1_5 + + + TestData\MultiFileManifestV1_5 + + + TestData\MultiFileManifestV1_5 + + + TestData\MultiFileManifestV1_5 + + + TestData\MultiFileManifestV1_6 + + + TestData\MultiFileManifestV1_6 + + + TestData\MultiFileManifestV1_6 + + + TestData\MultiFileManifestV1_6 + + + TestData\MultiFileManifestV1_7 + + + TestData\MultiFileManifestV1_7 + + + TestData\MultiFileManifestV1_7 + + + TestData\MultiFileManifestV1_7 + + + TestData\MultiFileManifestV1_9 + + + TestData\MultiFileManifestV1_9 + + + TestData\MultiFileManifestV1_9 + + + TestData\MultiFileManifestV1_9 + + + TestData\MultiFileManifestV1_10 + + + TestData\MultiFileManifestV1_10 + + + TestData\MultiFileManifestV1_10 + + + TestData\MultiFileManifestV1_10 + + + TestData\MultiFileManifestV1_12 + + + TestData\MultiFileManifestV1_12 + + + TestData\MultiFileManifestV1_12 + + + TestData\MultiFileManifestV1_12 + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData\Shadow\V1_5 + + + TestData\Shadow\V1_5 + + + TestData\Shadow\V1_5 + + + TestData\Shadow\V1_5 + + + TestData\Shadow\V1_5 + + + TestData\Shadow\V1_5 + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData\MultiFileManifestV1_28 + + + TestData\MultiFileManifestV1_28 + + + TestData\MultiFileManifestV1_28 + + + TestData\MultiFileManifestV1_28 + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + + TestData + + \ No newline at end of file diff --git a/src/AppInstallerCLITests/Archive.cpp b/src/AppInstallerCLITests/Archive.cpp index 4d415761f1..2aafb57e59 100644 --- a/src/AppInstallerCLITests/Archive.cpp +++ b/src/AppInstallerCLITests/Archive.cpp @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include - -using namespace AppInstaller::Archive; -using namespace TestCommon; - -constexpr std::string_view s_ZipFile = "TestZip.zip"; - -TEST_CASE("Extract_ZipArchive", "[archive]") -{ - TestCommon::TempDirectory tempDirectory("TempDirectory"); - TestDataFile testZip(s_ZipFile); - - const auto& testZipPath = testZip.GetPath(); - const auto& tempDirectoryPath = tempDirectory.GetPath(); - - HRESULT hr = TryExtractArchive(testZipPath, tempDirectoryPath); - - std::filesystem::path expectedPath = tempDirectoryPath / "test.txt"; - REQUIRE(SUCCEEDED(hr)); - REQUIRE(std::filesystem::exists(expectedPath)); -} - -TEST_CASE("Scan_ZipArchive", "[archive]") -{ - TestDataFile testZip(s_ZipFile); - - const auto& testZipPath = testZip.GetPath(); - bool result = ScanZipFile(testZipPath); - REQUIRE(result); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace AppInstaller::Archive; +using namespace TestCommon; + +constexpr std::string_view s_ZipFile = "TestZip.zip"; + +TEST_CASE("Extract_ZipArchive", "[archive]") +{ + TestCommon::TempDirectory tempDirectory("TempDirectory"); + TestDataFile testZip(s_ZipFile); + + const auto& testZipPath = testZip.GetPath(); + const auto& tempDirectoryPath = tempDirectory.GetPath(); + + HRESULT hr = TryExtractArchive(testZipPath, tempDirectoryPath); + + std::filesystem::path expectedPath = tempDirectoryPath / "test.txt"; + REQUIRE(SUCCEEDED(hr)); + REQUIRE(std::filesystem::exists(expectedPath)); +} + +TEST_CASE("Scan_ZipArchive", "[archive]") +{ + TestDataFile testZip(s_ZipFile); + + const auto& testZipPath = testZip.GetPath(); + bool result = ScanZipFile(testZipPath); + REQUIRE(result); +} diff --git a/src/AppInstallerCLITests/Argument.cpp b/src/AppInstallerCLITests/Argument.cpp index 77911b5323..daecb6dfd4 100644 --- a/src/AppInstallerCLITests/Argument.cpp +++ b/src/AppInstallerCLITests/Argument.cpp @@ -1,16 +1,16 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include - -using namespace AppInstaller::CLI; - -TEST_CASE("EnsureAllArgumentsDefined", "[argument]") -{ - using Arg_t = std::underlying_type_t; - for (Arg_t i = static_cast(0); i < static_cast(Execution::Args::Type::Max); ++i) - { - REQUIRE_NOTHROW(ArgumentCommon::ForType(static_cast(i))); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace AppInstaller::CLI; + +TEST_CASE("EnsureAllArgumentsDefined", "[argument]") +{ + using Arg_t = std::underlying_type_t; + for (Arg_t i = static_cast(0); i < static_cast(Execution::Args::Type::Max); ++i) + { + REQUIRE_NOTHROW(ArgumentCommon::ForType(static_cast(i))); + } } \ No newline at end of file diff --git a/src/AppInstallerCLITests/ArpHelper.cpp b/src/AppInstallerCLITests/ArpHelper.cpp index fdbfd2cb4c..07dd74d0fe 100644 --- a/src/AppInstallerCLITests/ArpHelper.cpp +++ b/src/AppInstallerCLITests/ArpHelper.cpp @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestHooks.h" -#include "Microsoft/ARPHelper.h" -#include "AppInstallerStrings.h" - -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Registry; - - -TEST_CASE("ARPHelper_Watcher", "[ARPHelper]") -{ - ARPHelper helper; - - wil::unique_event callbackEvent; - callbackEvent.create(); - - ScopeEnum scopeCallback = ScopeEnum::Unknown; - Architecture architectureCallback = Architecture::Unknown; - - ScopeEnum scopeTarget = ScopeEnum::Machine; - Architecture architectureTarget = Architecture::X86; - - auto fakeRoot = TestCommon::RegCreateVolatileTestRoot(); - TestHook::SetGetARPKey_Override arpOverride([&](ScopeEnum scope, Architecture arch) - { - if (scope == scopeTarget && arch == architectureTarget) - { - return Key(fakeRoot.get(), L""); - } - else - { - return Key{}; - } - }); - - auto watchers = helper.CreateRegistryWatchers(scopeTarget, [&](ScopeEnum scope, Architecture arch, wil::RegistryChangeKind) - { - scopeCallback = scope; - architectureCallback = arch; - callbackEvent.SetEvent(); - }); - - auto arpKey = helper.GetARPKey(scopeTarget, architectureTarget); - REQUIRE(!!arpKey); - - GUID guid; - std::ignore = CoCreateGuid(&guid); - std::ostringstream stream; - stream << guid; - - auto testKey = TestCommon::RegCreateVolatileSubKey(arpKey, ConvertToUTF16(stream.str())); - - REQUIRE(callbackEvent.wait(1000)); - REQUIRE(scopeTarget == scopeCallback); - REQUIRE(architectureTarget == architectureCallback); - - // Reset for changing a value - scopeCallback = ScopeEnum::Unknown; - architectureCallback = Architecture::Unknown; - - TestCommon::SetRegistryValue(testKey.get(), L"testValue", L"valueValue"); - - REQUIRE(callbackEvent.wait(1000)); - REQUIRE(scopeTarget == scopeCallback); - REQUIRE(architectureTarget == architectureCallback); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestHooks.h" +#include "Microsoft/ARPHelper.h" +#include "AppInstallerStrings.h" + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Registry; + + +TEST_CASE("ARPHelper_Watcher", "[ARPHelper]") +{ + ARPHelper helper; + + wil::unique_event callbackEvent; + callbackEvent.create(); + + ScopeEnum scopeCallback = ScopeEnum::Unknown; + Architecture architectureCallback = Architecture::Unknown; + + ScopeEnum scopeTarget = ScopeEnum::Machine; + Architecture architectureTarget = Architecture::X86; + + auto fakeRoot = TestCommon::RegCreateVolatileTestRoot(); + TestHook::SetGetARPKey_Override arpOverride([&](ScopeEnum scope, Architecture arch) + { + if (scope == scopeTarget && arch == architectureTarget) + { + return Key(fakeRoot.get(), L""); + } + else + { + return Key{}; + } + }); + + auto watchers = helper.CreateRegistryWatchers(scopeTarget, [&](ScopeEnum scope, Architecture arch, wil::RegistryChangeKind) + { + scopeCallback = scope; + architectureCallback = arch; + callbackEvent.SetEvent(); + }); + + auto arpKey = helper.GetARPKey(scopeTarget, architectureTarget); + REQUIRE(!!arpKey); + + GUID guid; + std::ignore = CoCreateGuid(&guid); + std::ostringstream stream; + stream << guid; + + auto testKey = TestCommon::RegCreateVolatileSubKey(arpKey, ConvertToUTF16(stream.str())); + + REQUIRE(callbackEvent.wait(1000)); + REQUIRE(scopeTarget == scopeCallback); + REQUIRE(architectureTarget == architectureCallback); + + // Reset for changing a value + scopeCallback = ScopeEnum::Unknown; + architectureCallback = Architecture::Unknown; + + TestCommon::SetRegistryValue(testKey.get(), L"testValue", L"valueValue"); + + REQUIRE(callbackEvent.wait(1000)); + REQUIRE(scopeTarget == scopeCallback); + REQUIRE(architectureTarget == architectureCallback); +} diff --git a/src/AppInstallerCLITests/Certificates.cpp b/src/AppInstallerCLITests/Certificates.cpp index 0a71d0731a..469d3e501e 100644 --- a/src/AppInstallerCLITests/Certificates.cpp +++ b/src/AppInstallerCLITests/Certificates.cpp @@ -1,345 +1,345 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::Certificates; - - -TEST_CASE("Certificates_NoPinningSucceeds", "[certificates]") -{ - PinningDetails expected; - expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::None); - - PinningDetails actual; - actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); -} - -TEST_CASE("Certificates_PublicKeyMismatch", "[certificates]") -{ - PinningDetails expected; - expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - - PinningDetails actual; - actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); -} - -TEST_CASE("Certificates_PublicKeyMatch", "[certificates]") -{ - PinningDetails expected; - expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - - PinningDetails actual; - actual.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Root)); -} - -TEST_CASE("Certificates_SubjectMismatch", "[certificates]") -{ - PinningDetails expected; - expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject); - - PinningDetails actual; - actual.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Intermediate)); -} - -TEST_CASE("Certificates_SubjectMatch", "[certificates]") -{ - PinningDetails expected; - expected.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject); - - PinningDetails actual; - actual.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Intermediate)); -} - -TEST_CASE("Certificates_IssuerMismatch", "[certificates]") -{ - PinningDetails expected; - expected.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Issuer); - - PinningDetails actual; - actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); -} - -TEST_CASE("Certificates_IssuerMatch", "[certificates]") -{ - PinningDetails expected; - expected.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Issuer); - - PinningDetails actual; - actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); -} - -TEST_CASE("Certificates_ChainLengthDiffers", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(!config.Validate(details.GetCertificate())); -} - -TEST_CASE("Certificates_ChainLengthDiffers_Partial", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chain.PartialChain(); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); -} - -TEST_CASE("CertificateChain_AnyIssuer_Intermediate", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); - chain.PartialChain(); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); -} - -TEST_CASE("CertificateChain_AnyIssuer_IntermediateDiffers", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); - chain.PartialChain(); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(!config.Validate(details.GetCertificate())); -} - -TEST_CASE("CertificateChain_AnyIssuer_IntermediateAndLeaf", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chain.PartialChain(); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); -} - -TEST_CASE("CertificateChain_AnyIssuer_Leaf", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer); - chain.PartialChain(); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); -} - -TEST_CASE("CertificateChain_AnyIssuer_LeafDiffers", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer); - chain.PartialChain(); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(!config.Validate(details.GetCertificate())); -} - -TEST_CASE("CertificateChain_AnyIssuer_SameLeaf_RequireNonLeaf", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); - chain.PartialChain(); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(!config.Validate(details.GetCertificate())); -} - -TEST_CASE("Certificates_EmptyChainRejects", "[certificates]") -{ - PinningChain chain; - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(!config.Validate(details.GetCertificate())); -} - -TEST_CASE("Certificates_ChainOrderDiffers", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(!config.Validate(details.GetCertificate())); -} - -TEST_CASE("Certificates_StoreChain_BuiltInTest", "[certificates]") -{ - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); -} - -TEST_CASE("Certificates_MultipleChains_Success", "[certificates]") -{ - PinningChain chainOutOfOrder; - auto chainElement = chainOutOfOrder.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - PinningConfiguration config; - config.AddChain(chainOutOfOrder); - - PinningChain chain; - chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); -} - -TEST_CASE("CertificateChain_Position", "[certificates]") -{ - CertificateChainPosition positions[3]{ CertificateChainPosition::Unknown, CertificateChainPosition::Unknown, CertificateChainPosition::Unknown }; - - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; }); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[1] = position; return true; }); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[2] = position; return true; }); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); - - REQUIRE(CertificateChainPosition::Root == positions[0]); - REQUIRE(CertificateChainPosition::Intermediate == positions[1]); - REQUIRE(CertificateChainPosition::Leaf == positions[2]); -} - -TEST_CASE("CertificateChain_Position_SelfSigned", "[certificates]") -{ - CertificateChainPosition positions[1]{ CertificateChainPosition::Unknown }; - - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; }); - - PinningConfiguration config; - config.AddChain(chain); - - PinningDetails details; - details.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE); - - REQUIRE(config.Validate(details.GetCertificate())); - - REQUIRE((CertificateChainPosition::Root | CertificateChainPosition::Leaf) == positions[0]); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Certificates; + + +TEST_CASE("Certificates_NoPinningSucceeds", "[certificates]") +{ + PinningDetails expected; + expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::None); + + PinningDetails actual; + actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); +} + +TEST_CASE("Certificates_PublicKeyMismatch", "[certificates]") +{ + PinningDetails expected; + expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + + PinningDetails actual; + actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); +} + +TEST_CASE("Certificates_PublicKeyMatch", "[certificates]") +{ + PinningDetails expected; + expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + + PinningDetails actual; + actual.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Root)); +} + +TEST_CASE("Certificates_SubjectMismatch", "[certificates]") +{ + PinningDetails expected; + expected.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject); + + PinningDetails actual; + actual.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Intermediate)); +} + +TEST_CASE("Certificates_SubjectMatch", "[certificates]") +{ + PinningDetails expected; + expected.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject); + + PinningDetails actual; + actual.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Intermediate)); +} + +TEST_CASE("Certificates_IssuerMismatch", "[certificates]") +{ + PinningDetails expected; + expected.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Issuer); + + PinningDetails actual; + actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(CertificatePinningValidationResult::Rejected == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); +} + +TEST_CASE("Certificates_IssuerMatch", "[certificates]") +{ + PinningDetails expected; + expected.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Issuer); + + PinningDetails actual; + actual.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(CertificatePinningValidationResult::Accepted == expected.Validate(actual.GetCertificate(), CertificateChainPosition::Leaf)); +} + +TEST_CASE("Certificates_ChainLengthDiffers", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(!config.Validate(details.GetCertificate())); +} + +TEST_CASE("Certificates_ChainLengthDiffers_Partial", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chain.PartialChain(); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); +} + +TEST_CASE("CertificateChain_AnyIssuer_Intermediate", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); + chain.PartialChain(); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); +} + +TEST_CASE("CertificateChain_AnyIssuer_IntermediateDiffers", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); + chain.PartialChain(); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(!config.Validate(details.GetCertificate())); +} + +TEST_CASE("CertificateChain_AnyIssuer_IntermediateAndLeaf", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chain.PartialChain(); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); +} + +TEST_CASE("CertificateChain_AnyIssuer_Leaf", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer); + chain.PartialChain(); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); +} + +TEST_CASE("CertificateChain_AnyIssuer_LeafDiffers", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer); + chain.PartialChain(); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(!config.Validate(details.GetCertificate())); +} + +TEST_CASE("CertificateChain_AnyIssuer_SameLeaf_RequireNonLeaf", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); + chain.PartialChain(); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(!config.Validate(details.GetCertificate())); +} + +TEST_CASE("Certificates_EmptyChainRejects", "[certificates]") +{ + PinningChain chain; + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(!config.Validate(details.GetCertificate())); +} + +TEST_CASE("Certificates_ChainOrderDiffers", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(!config.Validate(details.GetCertificate())); +} + +TEST_CASE("Certificates_StoreChain_BuiltInTest", "[certificates]") +{ + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); +} + +TEST_CASE("Certificates_MultipleChains_Success", "[certificates]") +{ + PinningChain chainOutOfOrder; + auto chainElement = chainOutOfOrder.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + PinningConfiguration config; + config.AddChain(chainOutOfOrder); + + PinningChain chain; + chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); +} + +TEST_CASE("CertificateChain_Position", "[certificates]") +{ + CertificateChainPosition positions[3]{ CertificateChainPosition::Unknown, CertificateChainPosition::Unknown, CertificateChainPosition::Unknown }; + + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; }); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[1] = position; return true; }); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[2] = position; return true; }); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); + + REQUIRE(CertificateChainPosition::Root == positions[0]); + REQUIRE(CertificateChainPosition::Intermediate == positions[1]); + REQUIRE(CertificateChainPosition::Leaf == positions[2]); +} + +TEST_CASE("CertificateChain_Position_SelfSigned", "[certificates]") +{ + CertificateChainPosition positions[1]{ CertificateChainPosition::Unknown }; + + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetCustomValidationFunction([&](const PinningDetails&, PCCERT_CONTEXT, CertificateChainPosition position) { positions[0] = position; return true; }); + + PinningConfiguration config; + config.AddChain(chain); + + PinningDetails details; + details.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE); + + REQUIRE(config.Validate(details.GetCertificate())); + + REQUIRE((CertificateChainPosition::Root | CertificateChainPosition::Leaf) == positions[0]); +} diff --git a/src/AppInstallerCLITests/Command.cpp b/src/AppInstallerCLITests/Command.cpp index 943981889e..e29fc8a209 100644 --- a/src/AppInstallerCLITests/Command.cpp +++ b/src/AppInstallerCLITests/Command.cpp @@ -1,690 +1,690 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; - -std::string GetCommandName(const std::unique_ptr& command) -{ - return std::string{ command->FullName() }; -} - -std::vector GetCommandAliases(const std::unique_ptr& command) -{ - return std::vector { command->Aliases() }; -} - -std::string GetArgumentName(const Argument& arg) -{ - return std::string{ arg.Name() }; -} - -std::string GetArgumentAlternateName(const Argument& arg) -{ - if (arg.AlternateName() == Argument::NoAlternateName) - { - return {}; - } - else - { - return std::string{ arg.AlternateName() }; - } -} - -std::string GetArgumentAlias(const Argument& arg) -{ - if (arg.Alias() == ArgumentCommon::NoAlias) - { - return {}; - } - else - { - return std::string(1, arg.Alias()); - } -} - -bool StringIsLowercase(const std::string& s) -{ - return Utility::ToLower(s) == s; -} - -template -void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set& values, bool requireLower = true) -{ - INFO(info); - - for (const auto& val : e) - { - std::string valString = op(val); - if (valString.empty()) - { - continue; - } - INFO(valString); - - if (requireLower) - { - REQUIRE(StringIsLowercase(valString)); - } - - REQUIRE(values.find(valString) == values.end()); - - values.emplace(std::move(valString)); - } -} - -template -void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, bool requireLower = true) -{ - std::unordered_set values; - EnsureStringsAreLowercaseAndNoCollisions(info, e, op, values, requireLower); -} - -template -void EnsureVectorStringViewsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set& values, bool requireLower = true) -{ - INFO(info); - - for (const auto& val : e) - { - std::vector aliasVector = op(val); - std::vector valVector(aliasVector.begin(), aliasVector.end()); - if (valVector.empty()) - { - continue; - } - // When op returns a vector, we need to ensure every value in the vector does not cause a collision - for (auto& valString : valVector) - { - INFO(valString); - - if (requireLower) - { - REQUIRE(StringIsLowercase(valString)); - } - - REQUIRE(values.find(valString) == values.end()); - values.emplace(std::move(valString)); - } - } -} - -void EnsureCommandConsistency(const Command& command) -{ - // Command names and aliases exist in the same space, so both need to be checked as a set - // However, collisions do not occur between levels, so the full name must be used to check for collision - std::unordered_set allCommandAliasNames; - EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " commands", command.GetCommands(), GetCommandName, allCommandAliasNames); - EnsureVectorStringViewsAreLowercaseAndNoCollisions(command.FullName() + " aliases", command.GetCommands(), GetCommandAliases, allCommandAliasNames); - - auto args = command.GetArguments(); - Argument::GetCommon(args); - - // Argument names and alternate names exist in the same space, so both need to be checked as a set - std::unordered_set allArgumentNames; - EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument names", args, GetArgumentName, allArgumentNames); - EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument alternate name", args, GetArgumentAlternateName, allArgumentNames); - // Argument aliases use a different space than the names and can be checked separately - EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument alias", args, GetArgumentAlias, false); - - // No : allowed in commands - for (const auto& comm : command.GetCommands()) - { - INFO(command.FullName()); - INFO(comm->Name()); - - REQUIRE(comm->Name().find_first_of(Command::ParentSplitChar) == std::string_view::npos); - } - - // No = allowed in arguments - // All positional args should be listed first - bool foundNonPositional = false; - bool queryArgPresent = false; - bool multiQueryArgPresent = false; - for (const auto& arg : command.GetArguments()) - { - INFO(command.FullName()); - INFO(arg.Name()); - - REQUIRE(arg.Name().find_first_of(APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR) == std::string_view::npos); - - if (arg.Type() == ArgumentType::Positional) - { - REQUIRE(!foundNonPositional); - } - else - { - foundNonPositional = true; - } - - if (arg.ExecArgType() == Execution::Args::Type::Query) - { - queryArgPresent = true; - } - - if (arg.ExecArgType() == Execution::Args::Type::MultiQuery) - { - multiQueryArgPresent = true; - } - } - - REQUIRE((!queryArgPresent || !multiQueryArgPresent)); - - // Recurse for all subcommands - for (const auto& sub : command.GetCommands()) - { - EnsureCommandConsistency(*sub.get()); - } -} - -// This test ensure that the command tree we expose does not have any inconsistencies. -// 1. No command name collisions -// 2. All command names are lower cased -// 3. No argument name collisions -// 4. All arguments are lower cased -// 5. No argument alias collisions -// 6. All argument alias are lower cased -// 7. No argument names contain '=' -// 8. All positional arguments are first in the list -// 9. No command includes both Query and MultiQuery arguments -TEST_CASE("EnsureCommandTreeConsistency", "[command]") -{ - RootCommand root; - EnsureCommandConsistency(root); -} - -struct TestCommand : public Command -{ - TestCommand(std::vector args) : Command("test", ""), m_args(std::move(args)) {} - - std::vector GetArguments() const override - { - return m_args; - } - - CLI::Resource::LocString ShortDescription() const override { return {}; } - CLI::Resource::LocString LongDescription() const override { return {}; } - - std::vector m_args; -}; - -// Matcher that lets us verify CommandExceptions. -struct CommandExceptionMatcher : public Catch::Matchers::MatcherBase -{ - CommandExceptionMatcher(CLI::Resource::LocString message) : m_expectedMessage(std::move(message)) {} - - bool match(const CommandException& ce) const override - { - return ce.Message() == m_expectedMessage; - } - - std::string describe() const override - { - std::ostringstream result; - result << "has message == " << m_expectedMessage; - return result.str(); - } - -private: - CLI::Resource::LocString m_expectedMessage; -}; - -namespace Catch { - template<> - struct StringMaker { - static std::string convert(CommandException const& ce) { - return Utility::Format("CommandException{ '{0}' }", ce.Message().get()); - } - }; -} - -#define REQUIRE_COMMAND_EXCEPTION(_expr_, _arg_) REQUIRE_THROWS_MATCHES(_expr_, CommandException, CommandExceptionMatcher(_arg_)) - -void RequireValueParsedToArg(const std::string& value, const Argument& arg, const Args& args) -{ - REQUIRE(args.Contains(arg.ExecArgType())); - REQUIRE(value == args.GetArg(arg.ExecArgType())); -} - -void RequireValuesParsedToArg(const std::vector& values, Args::Type execArgType, const Args& args) -{ - REQUIRE(args.Contains(execArgType)); - REQUIRE(args.GetCount(execArgType) == values.size()); - - auto argValues = args.GetArgs(execArgType); - for (size_t i = 0; i < values.size(); ++i) - { - REQUIRE(argValues->at(i) == values[i]); - } -} - -void RequireValuesParsedToArg(const std::vector& values, const Argument& arg, const Args& args) -{ - RequireValuesParsedToArg(values, arg.ExecArgType(), args); -} - -// Description used for tests; doesn't need to be anything in particular. -static constexpr CLI::Resource::StringId DefaultDesc{ L""sv }; - -TEST_CASE("ParseArguments_MultiplePositional", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "val1", "val2" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[0], command.m_args[0], args); - RequireValueParsedToArg(values[1], command.m_args[2], args); -} - -TEST_CASE("ParseArguments_ForcePositional", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "val1", "--", "-std1" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[0], command.m_args[0], args); - RequireValueParsedToArg(values[2], command.m_args[2], args); -} - -TEST_CASE("ParseArguments_TooManyPositional", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - }); - - std::vector values{ "val1", "--", "-std1" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::ExtraPositionalError(Utility::LocIndView{ values[2] })); -} - -TEST_CASE("ParseArguments_InvalidChar", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "val1", "-", "-std1" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidArgumentSpecifierError(Utility::LocIndView{ values[1] })); -} - -TEST_CASE("ParseArguments_InvalidAlias", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "val1", "-b", "-std1" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidAliasError(Utility::LocIndView{ values[1] })); -} - -TEST_CASE("ParseArguments_MultiFlag", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "val1", "-st", "val2" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - REQUIRE(args.Contains(command.m_args[1].ExecArgType())); - REQUIRE(args.Contains(command.m_args[3].ExecArgType())); -} - -TEST_CASE("ParseArguments_FlagThenUnknown", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "val1", "-sr", "val2" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFoundError(Utility::LocIndView{ values[1] })); -} - -TEST_CASE("ParseArguments_FlagThenNonFlag", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "val1", "-sp", "val2" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFlagError(Utility::LocIndView{ values[1] })); -} - -TEST_CASE("ParseArguments_NameUsingAliasSpecifier", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "another", "-flag1" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFoundError(Utility::LocIndView{ values[1] })); -} - -TEST_CASE("ParseArguments_AliasWithAdjoinedValue", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "-s=Val1" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[0].substr(3), command.m_args[1], args); -} - -TEST_CASE("ParseArguments_AliasWithSeparatedValue", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "-s", "Val1" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[1], command.m_args[1], args); -} - -TEST_CASE("ParseArguments_AliasWithSeparatedValueMissing", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "-s" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::MissingArgumentError(Utility::LocIndView{ values[0] })); -} - -TEST_CASE("ParseArguments_NameWithAdjoinedValue", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "--pos1=Val1" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[0].substr(7), command.m_args[0], args); -} - -TEST_CASE("ParseArguments_AlternateNameWithAdjoinedValue", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', "p1", Args::Type::Channel, DefaultDesc, ArgumentType::Positional}, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - }); - - std::vector values{ "--p1=Val1" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[0].substr(5), command.m_args[0], args); -} - -TEST_CASE("ParseArguments_NameFlag", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "--flag1", "arbitrary" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[1], command.m_args[0], args); - REQUIRE(args.Contains(command.m_args[3].ExecArgType())); -} - -TEST_CASE("ParseArguments_NameFlagWithAdjoinedValue", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "another", "--flag1=arbitrary" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::FlagContainAdjoinedError(Utility::LocIndView{ values[1] })); -} - -TEST_CASE("ParseArguments_NameWithSeparatedValue", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "--pos2", "arbitrary" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - - RequireValueParsedToArg(values[1], command.m_args[2], args); -} - -TEST_CASE("ParseArguments_NameWithSeparatedValueMissing", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "--pos2" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::MissingArgumentError(Utility::LocIndView{ values[0] })); -} - -TEST_CASE("ParseArguments_UnknownName", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, - Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, - Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, - Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "another", "--nope" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidNameError(Utility::LocIndView{ values[1] })); -} - -TEST_CASE("ParseArguments_PositionalWithMultipleValues", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), - }); - - std::vector values{ "value1" "value2", "value3" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - RequireValuesParsedToArg(values, command.m_args[0], args); -} - -TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndOtherArgs", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "pos1", 'p', Args::Type::Source, DefaultDesc, ArgumentType::Positional }, - Argument{ "pos2", 'q', Args::Type::All, DefaultDesc, ArgumentType::Positional }, - Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), - Argument{ "flag", 'f', Args::Type::BlockingPin, DefaultDesc, ArgumentType::Flag }, - }); - - std::vector values{ "positional", "-q", "anotherPos", "multiValue1", "multiValue2", "-f" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - RequireValueParsedToArg(values[0], command.m_args[0], args); - RequireValueParsedToArg(values[2], command.m_args[1], args); - RequireValuesParsedToArg({ values[3], values[4] }, command.m_args[2], args); - REQUIRE(args.Contains(command.m_args[3].ExecArgType())); -} - -TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndName", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), - }); - - std::vector values{ "--multi", "one", "two", "three" }; - Invocation inv{ std::vector(values) }; - - command.ParseArguments(inv, args); - RequireValuesParsedToArg({ values[1], values[2], values[3] }, command.m_args[0], args); -} - -TEST_CASE("ParseArguments_MultiQueryConvertedToSingleQuery", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), - }); - - std::vector values{ "singleValue" }; - Invocation inv{ std::vector(values) }; - - // ParseArguments converts MultiQuery args with a single value into Query args - command.ParseArguments(inv, args); - RequireValuesParsedToArg({ values[0] }, Args::Type::Query, args); -} - -TEST_CASE("ParseArguments_PositionalWithTooManyValues", "[command]") -{ - Args args; - TestCommand command({ - Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), - }); - - std::vector values{ "1", "2", "3", "4", "5", "tooMany" }; - Invocation inv{ std::vector(values) }; - - REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::ExtraPositionalError(Utility::LocIndView{ values.back() })); -} - -TEST_CASE("EnsureListSortFieldCountMatchesLimit", "[command]") -{ - ListCommand listCommand({}); - const auto& args = listCommand.GetArguments(); - - size_t sortLimit = 0; - for (const auto& arg : args) - { - if (arg.ExecArgType() == Args::Type::Sort) - { - sortLimit = arg.Limit(); - break; - } - } - - // The product's configured limit must match Max: adding a new field - // without bumping Max (or changing limit) will fail this check. - REQUIRE((1u << sortLimit) == static_cast(Settings::SortField::Max)); -} - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; + +std::string GetCommandName(const std::unique_ptr& command) +{ + return std::string{ command->FullName() }; +} + +std::vector GetCommandAliases(const std::unique_ptr& command) +{ + return std::vector { command->Aliases() }; +} + +std::string GetArgumentName(const Argument& arg) +{ + return std::string{ arg.Name() }; +} + +std::string GetArgumentAlternateName(const Argument& arg) +{ + if (arg.AlternateName() == Argument::NoAlternateName) + { + return {}; + } + else + { + return std::string{ arg.AlternateName() }; + } +} + +std::string GetArgumentAlias(const Argument& arg) +{ + if (arg.Alias() == ArgumentCommon::NoAlias) + { + return {}; + } + else + { + return std::string(1, arg.Alias()); + } +} + +bool StringIsLowercase(const std::string& s) +{ + return Utility::ToLower(s) == s; +} + +template +void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set& values, bool requireLower = true) +{ + INFO(info); + + for (const auto& val : e) + { + std::string valString = op(val); + if (valString.empty()) + { + continue; + } + INFO(valString); + + if (requireLower) + { + REQUIRE(StringIsLowercase(valString)); + } + + REQUIRE(values.find(valString) == values.end()); + + values.emplace(std::move(valString)); + } +} + +template +void EnsureStringsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, bool requireLower = true) +{ + std::unordered_set values; + EnsureStringsAreLowercaseAndNoCollisions(info, e, op, values, requireLower); +} + +template +void EnsureVectorStringViewsAreLowercaseAndNoCollisions(const std::string& info, const Enumerable& e, Op& op, std::unordered_set& values, bool requireLower = true) +{ + INFO(info); + + for (const auto& val : e) + { + std::vector aliasVector = op(val); + std::vector valVector(aliasVector.begin(), aliasVector.end()); + if (valVector.empty()) + { + continue; + } + // When op returns a vector, we need to ensure every value in the vector does not cause a collision + for (auto& valString : valVector) + { + INFO(valString); + + if (requireLower) + { + REQUIRE(StringIsLowercase(valString)); + } + + REQUIRE(values.find(valString) == values.end()); + values.emplace(std::move(valString)); + } + } +} + +void EnsureCommandConsistency(const Command& command) +{ + // Command names and aliases exist in the same space, so both need to be checked as a set + // However, collisions do not occur between levels, so the full name must be used to check for collision + std::unordered_set allCommandAliasNames; + EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " commands", command.GetCommands(), GetCommandName, allCommandAliasNames); + EnsureVectorStringViewsAreLowercaseAndNoCollisions(command.FullName() + " aliases", command.GetCommands(), GetCommandAliases, allCommandAliasNames); + + auto args = command.GetArguments(); + Argument::GetCommon(args); + + // Argument names and alternate names exist in the same space, so both need to be checked as a set + std::unordered_set allArgumentNames; + EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument names", args, GetArgumentName, allArgumentNames); + EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument alternate name", args, GetArgumentAlternateName, allArgumentNames); + // Argument aliases use a different space than the names and can be checked separately + EnsureStringsAreLowercaseAndNoCollisions(command.FullName() + " argument alias", args, GetArgumentAlias, false); + + // No : allowed in commands + for (const auto& comm : command.GetCommands()) + { + INFO(command.FullName()); + INFO(comm->Name()); + + REQUIRE(comm->Name().find_first_of(Command::ParentSplitChar) == std::string_view::npos); + } + + // No = allowed in arguments + // All positional args should be listed first + bool foundNonPositional = false; + bool queryArgPresent = false; + bool multiQueryArgPresent = false; + for (const auto& arg : command.GetArguments()) + { + INFO(command.FullName()); + INFO(arg.Name()); + + REQUIRE(arg.Name().find_first_of(APPINSTALLER_CLI_ARGUMENT_SPLIT_CHAR) == std::string_view::npos); + + if (arg.Type() == ArgumentType::Positional) + { + REQUIRE(!foundNonPositional); + } + else + { + foundNonPositional = true; + } + + if (arg.ExecArgType() == Execution::Args::Type::Query) + { + queryArgPresent = true; + } + + if (arg.ExecArgType() == Execution::Args::Type::MultiQuery) + { + multiQueryArgPresent = true; + } + } + + REQUIRE((!queryArgPresent || !multiQueryArgPresent)); + + // Recurse for all subcommands + for (const auto& sub : command.GetCommands()) + { + EnsureCommandConsistency(*sub.get()); + } +} + +// This test ensure that the command tree we expose does not have any inconsistencies. +// 1. No command name collisions +// 2. All command names are lower cased +// 3. No argument name collisions +// 4. All arguments are lower cased +// 5. No argument alias collisions +// 6. All argument alias are lower cased +// 7. No argument names contain '=' +// 8. All positional arguments are first in the list +// 9. No command includes both Query and MultiQuery arguments +TEST_CASE("EnsureCommandTreeConsistency", "[command]") +{ + RootCommand root; + EnsureCommandConsistency(root); +} + +struct TestCommand : public Command +{ + TestCommand(std::vector args) : Command("test", ""), m_args(std::move(args)) {} + + std::vector GetArguments() const override + { + return m_args; + } + + CLI::Resource::LocString ShortDescription() const override { return {}; } + CLI::Resource::LocString LongDescription() const override { return {}; } + + std::vector m_args; +}; + +// Matcher that lets us verify CommandExceptions. +struct CommandExceptionMatcher : public Catch::Matchers::MatcherBase +{ + CommandExceptionMatcher(CLI::Resource::LocString message) : m_expectedMessage(std::move(message)) {} + + bool match(const CommandException& ce) const override + { + return ce.Message() == m_expectedMessage; + } + + std::string describe() const override + { + std::ostringstream result; + result << "has message == " << m_expectedMessage; + return result.str(); + } + +private: + CLI::Resource::LocString m_expectedMessage; +}; + +namespace Catch { + template<> + struct StringMaker { + static std::string convert(CommandException const& ce) { + return Utility::Format("CommandException{ '{0}' }", ce.Message().get()); + } + }; +} + +#define REQUIRE_COMMAND_EXCEPTION(_expr_, _arg_) REQUIRE_THROWS_MATCHES(_expr_, CommandException, CommandExceptionMatcher(_arg_)) + +void RequireValueParsedToArg(const std::string& value, const Argument& arg, const Args& args) +{ + REQUIRE(args.Contains(arg.ExecArgType())); + REQUIRE(value == args.GetArg(arg.ExecArgType())); +} + +void RequireValuesParsedToArg(const std::vector& values, Args::Type execArgType, const Args& args) +{ + REQUIRE(args.Contains(execArgType)); + REQUIRE(args.GetCount(execArgType) == values.size()); + + auto argValues = args.GetArgs(execArgType); + for (size_t i = 0; i < values.size(); ++i) + { + REQUIRE(argValues->at(i) == values[i]); + } +} + +void RequireValuesParsedToArg(const std::vector& values, const Argument& arg, const Args& args) +{ + RequireValuesParsedToArg(values, arg.ExecArgType(), args); +} + +// Description used for tests; doesn't need to be anything in particular. +static constexpr CLI::Resource::StringId DefaultDesc{ L""sv }; + +TEST_CASE("ParseArguments_MultiplePositional", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "val1", "val2" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[0], command.m_args[0], args); + RequireValueParsedToArg(values[1], command.m_args[2], args); +} + +TEST_CASE("ParseArguments_ForcePositional", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "val1", "--", "-std1" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[0], command.m_args[0], args); + RequireValueParsedToArg(values[2], command.m_args[2], args); +} + +TEST_CASE("ParseArguments_TooManyPositional", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + }); + + std::vector values{ "val1", "--", "-std1" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::ExtraPositionalError(Utility::LocIndView{ values[2] })); +} + +TEST_CASE("ParseArguments_InvalidChar", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "val1", "-", "-std1" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidArgumentSpecifierError(Utility::LocIndView{ values[1] })); +} + +TEST_CASE("ParseArguments_InvalidAlias", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "val1", "-b", "-std1" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidAliasError(Utility::LocIndView{ values[1] })); +} + +TEST_CASE("ParseArguments_MultiFlag", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "val1", "-st", "val2" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + REQUIRE(args.Contains(command.m_args[1].ExecArgType())); + REQUIRE(args.Contains(command.m_args[3].ExecArgType())); +} + +TEST_CASE("ParseArguments_FlagThenUnknown", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "val1", "-sr", "val2" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFoundError(Utility::LocIndView{ values[1] })); +} + +TEST_CASE("ParseArguments_FlagThenNonFlag", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Flag }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag2", 't', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "val1", "-sp", "val2" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFlagError(Utility::LocIndView{ values[1] })); +} + +TEST_CASE("ParseArguments_NameUsingAliasSpecifier", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "another", "-flag1" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::AdjoinedNotFoundError(Utility::LocIndView{ values[1] })); +} + +TEST_CASE("ParseArguments_AliasWithAdjoinedValue", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "-s=Val1" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[0].substr(3), command.m_args[1], args); +} + +TEST_CASE("ParseArguments_AliasWithSeparatedValue", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "-s", "Val1" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[1], command.m_args[1], args); +} + +TEST_CASE("ParseArguments_AliasWithSeparatedValueMissing", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "-s" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::MissingArgumentError(Utility::LocIndView{ values[0] })); +} + +TEST_CASE("ParseArguments_NameWithAdjoinedValue", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "--pos1=Val1" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[0].substr(7), command.m_args[0], args); +} + +TEST_CASE("ParseArguments_AlternateNameWithAdjoinedValue", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', "p1", Args::Type::Channel, DefaultDesc, ArgumentType::Positional}, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + }); + + std::vector values{ "--p1=Val1" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[0].substr(5), command.m_args[0], args); +} + +TEST_CASE("ParseArguments_NameFlag", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "--flag1", "arbitrary" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[1], command.m_args[0], args); + REQUIRE(args.Contains(command.m_args[3].ExecArgType())); +} + +TEST_CASE("ParseArguments_NameFlagWithAdjoinedValue", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "another", "--flag1=arbitrary" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::FlagContainAdjoinedError(Utility::LocIndView{ values[1] })); +} + +TEST_CASE("ParseArguments_NameWithSeparatedValue", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "--pos2", "arbitrary" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + + RequireValueParsedToArg(values[1], command.m_args[2], args); +} + +TEST_CASE("ParseArguments_NameWithSeparatedValueMissing", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "--pos2" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::MissingArgumentError(Utility::LocIndView{ values[0] })); +} + +TEST_CASE("ParseArguments_UnknownName", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Channel, DefaultDesc, ArgumentType::Positional }, + Argument{ "std1", 's', Args::Type::Command, DefaultDesc, ArgumentType::Standard }, + Argument{ "pos2", 'q', Args::Type::Count, DefaultDesc, ArgumentType::Positional }, + Argument{ "flag1", 'f', Args::Type::Exact, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "another", "--nope" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::InvalidNameError(Utility::LocIndView{ values[1] })); +} + +TEST_CASE("ParseArguments_PositionalWithMultipleValues", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "value1" "value2", "value3" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + RequireValuesParsedToArg(values, command.m_args[0], args); +} + +TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndOtherArgs", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "pos1", 'p', Args::Type::Source, DefaultDesc, ArgumentType::Positional }, + Argument{ "pos2", 'q', Args::Type::All, DefaultDesc, ArgumentType::Positional }, + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + Argument{ "flag", 'f', Args::Type::BlockingPin, DefaultDesc, ArgumentType::Flag }, + }); + + std::vector values{ "positional", "-q", "anotherPos", "multiValue1", "multiValue2", "-f" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + RequireValueParsedToArg(values[0], command.m_args[0], args); + RequireValueParsedToArg(values[2], command.m_args[1], args); + RequireValuesParsedToArg({ values[3], values[4] }, command.m_args[2], args); + REQUIRE(args.Contains(command.m_args[3].ExecArgType())); +} + +TEST_CASE("ParseArguments_PositionalWithMultipleValuesAndName", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "--multi", "one", "two", "three" }; + Invocation inv{ std::vector(values) }; + + command.ParseArguments(inv, args); + RequireValuesParsedToArg({ values[1], values[2], values[3] }, command.m_args[0], args); +} + +TEST_CASE("ParseArguments_MultiQueryConvertedToSingleQuery", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "singleValue" }; + Invocation inv{ std::vector(values) }; + + // ParseArguments converts MultiQuery args with a single value into Query args + command.ParseArguments(inv, args); + RequireValuesParsedToArg({ values[0] }, Args::Type::Query, args); +} + +TEST_CASE("ParseArguments_PositionalWithTooManyValues", "[command]") +{ + Args args; + TestCommand command({ + Argument{ "multi", 'm', Args::Type::MultiQuery, DefaultDesc, ArgumentType::Positional }.SetCountLimit(5), + }); + + std::vector values{ "1", "2", "3", "4", "5", "tooMany" }; + Invocation inv{ std::vector(values) }; + + REQUIRE_COMMAND_EXCEPTION(command.ParseArguments(inv, args), CLI::Resource::String::ExtraPositionalError(Utility::LocIndView{ values.back() })); +} + +TEST_CASE("EnsureListSortFieldCountMatchesLimit", "[command]") +{ + ListCommand listCommand({}); + const auto& args = listCommand.GetArguments(); + + size_t sortLimit = 0; + for (const auto& arg : args) + { + if (arg.ExecArgType() == Args::Type::Sort) + { + sortLimit = arg.Limit(); + break; + } + } + + // The product's configured limit must match Max: adding a new field + // without bumping Max (or changing limit) will fail this check. + REQUIRE((1u << sortLimit) == static_cast(Settings::SortField::Max)); +} + diff --git a/src/AppInstallerCLITests/Completion.cpp b/src/AppInstallerCLITests/Completion.cpp index 17075af86d..af7b69164a 100644 --- a/src/AppInstallerCLITests/Completion.cpp +++ b/src/AppInstallerCLITests/Completion.cpp @@ -1,552 +1,552 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; - - -TEST_CASE("CompletionData_EmptyWord_PositionAtEnd", "[complete]") -{ - CompletionData cd{ "", "winget ", "7" }; - REQUIRE(cd.Word() == ""); - REQUIRE(cd.BeforeWord().size() == 0); - REQUIRE(cd.AfterWord().size() == 0); -} - -TEST_CASE("CompletionData_EmptyWord_PositionPastEnd", "[complete]") -{ - CompletionData cd{ "", "winget ", "8" }; - REQUIRE(cd.Word() == ""); - REQUIRE(cd.BeforeWord().size() == 0); - REQUIRE(cd.AfterWord().size() == 0); -} - -TEST_CASE("CompletionData_EmptyWord_PositionCorrect", "[complete]") -{ - CompletionData cd{ "", "winget install", "7" }; - REQUIRE(cd.Word() == ""); - REQUIRE(cd.BeforeWord().size() == 0); - REQUIRE(cd.AfterWord().size() == 1); -} - - -TEST_CASE("CompletionData_EmptyWord_PositionOffset", "[complete]") -{ - CompletionData cd{ "", "winget install PowerToys --version", "17" }; - REQUIRE(cd.Word() == ""); - REQUIRE(cd.BeforeWord().size() == 1); - REQUIRE(cd.AfterWord().size() == 2); -} - -TEST_CASE("CompletionData_Word_NoMatch", "[complete]") -{ - auto lambda = []() { CompletionData cd{ "foo", "winget install PowerToys --version", "17" }; }; - REQUIRE_THROWS_HR(lambda(), APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD); -} - -TEST_CASE("CompletionData_Word_SingleMatch", "[complete]") -{ - CompletionData cd{ "power", "winget install power --version", "17" }; - REQUIRE(cd.Word() == "power"); - REQUIRE(cd.BeforeWord().size() == 1); - REQUIRE(cd.AfterWord().size() == 1); -} - -TEST_CASE("CompletionData_Word_MultiMatch_PositionCorrect", "[complete]") -{ - CompletionData cd{ "power", "winget install power --id power", "27" }; - REQUIRE(cd.Word() == "power"); - REQUIRE(cd.BeforeWord().size() == 3); - REQUIRE(cd.AfterWord().size() == 0); -} - -TEST_CASE("CompletionData_Word_MultiMatch_PositionOffset", "[complete]") -{ - CompletionData cd{ "power", "winget install power --id power", "21" }; - REQUIRE(cd.Word() == "power"); - REQUIRE(cd.BeforeWord().size() == 1); - REQUIRE(cd.AfterWord().size() == 2); -} - -TEST_CASE("CompletionData_UTF8_EmptyWord_End", "[complete]") -{ - CompletionData cd{ "", u8"winget install \x175\x12b\x14b\x1e5\x229\x288 --version ", "32" }; - REQUIRE(cd.Word() == ""); - REQUIRE(cd.BeforeWord().size() == 3); - REQUIRE(cd.AfterWord().size() == 0); -} - -TEST_CASE("CompletionData_UTF8_EmptyWord_Middle", "[complete]") -{ - CompletionData cd{ "", u8"winget install \x175\x12b\x14b\x1e5\x229\x288 --version ", "22" }; - REQUIRE(cd.Word() == ""); - REQUIRE(cd.BeforeWord().size() == 2); - REQUIRE(cd.AfterWord().size() == 1); -} - -TEST_CASE("CompletionData_UTF8_UTF8Word", "[complete]") -{ - CompletionData cd{ u8"\x175\x12b\x14b\x1e5\x229\x288", u8"winget install \x175\x12b\x14b\x1e5\x229\x288 --version ", "18" }; - REQUIRE(cd.Word() == u8"\x175\x12b\x14b\x1e5\x229\x288"); - REQUIRE(cd.BeforeWord().size() == 1); - REQUIRE(cd.AfterWord().size() == 1); -} - -void OutputAllSubCommands(Command& command, std::ostream& out, std::string_view filter = {}) -{ - for (const auto& c : command.GetCommands()) - { - if (Utility::CaseInsensitiveStartsWith(c->Name(), filter)) - { - out << c->Name() << std::endl; - } - } -} - -void OutputAllArgumentNames(Command& command, std::ostream& out, std::string_view filter = {}, bool includeCommon = true) -{ - auto args = command.GetArguments(); - if (includeCommon) - { - Argument::GetCommon(args); - } - - for (const auto& a : args) - { - if (Utility::CaseInsensitiveStartsWith(a.Name(), filter)) - { - out << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << a.Name() << std::endl; - } - } -} - -void OutputAllArgumentAliases(Command& command, std::ostream& out, bool includeCommon = true) -{ - auto args = command.GetArguments(); - if (includeCommon) - { - Argument::GetCommon(args); - } - - for (const auto& a : args) - { - if (a.Alias() != ArgumentCommon::NoAlias) - { - out << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << a.Alias() << std::endl; - } - } -} - -TEST_CASE("CompleteCommand_FindRoot", "[complete]") -{ - std::stringstream out, in; - Context context{ out, in }; - - CompleteCommand command{ "test" }; - context.Args.AddArg(Args::Type::Word, ""sv); - context.Args.AddArg(Args::Type::CommandLine, "winget "sv); - context.Args.AddArg(Args::Type::Position, "7"sv); - command.Execute(context); - - // Create expected values - RootCommand expectedCommand; - std::stringstream expected; - OutputAllSubCommands(expectedCommand, expected); - OutputAllArgumentNames(expectedCommand, expected); - - REQUIRE(out.str() == expected.str()); -} - -TEST_CASE("CompleteCommand_FindSource", "[complete]") -{ - std::stringstream out, in; - Context context{ out, in }; - - CompleteCommand command{ "test" }; - context.Args.AddArg(Args::Type::Word, ""sv); - context.Args.AddArg(Args::Type::CommandLine, "winget source "sv); - context.Args.AddArg(Args::Type::Position, "14"sv); - command.Execute(context); - - // Create expected values - SourceCommand expectedCommand{ "test" }; - std::stringstream expected; - OutputAllSubCommands(expectedCommand, expected); - OutputAllArgumentNames(expectedCommand, expected); - - REQUIRE(out.str() == expected.str()); -} - -TEST_CASE("CompleteCommand_FindSourceAdd", "[complete]") -{ - std::stringstream out, in; - Context context{ out, in }; - - CompleteCommand command{ "test" }; - context.Args.AddArg(Args::Type::Word, ""sv); - context.Args.AddArg(Args::Type::CommandLine, "winget source add "sv); - context.Args.AddArg(Args::Type::Position, "18"sv); - command.Execute(context); - - // Create expected values - SourceAddCommand expectedCommand{ "test" }; - std::stringstream expected; - OutputAllSubCommands(expectedCommand, expected); - OutputAllArgumentNames(expectedCommand, expected); - - REQUIRE(out.str() == expected.str()); -} - -struct CompletionTestCommand : public Command -{ - CompletionTestCommand() : Command("test", "") {} - CompletionTestCommand(std::string_view name) : Command(name, "") {} - - std::vector> GetCommands() const override - { - std::vector> result; - - for (const auto& sc : SubCommandNames) - { - result.emplace_back(std::make_unique(sc)); - } - - return result; - } - - std::vector GetArguments() const override - { - return Arguments; - } - - CLI::Resource::LocString ShortDescription() const override { return {}; } - CLI::Resource::LocString LongDescription() const override { return {}; } - - using Command::Complete; - - void Complete(Execution::Context& context, Execution::Args::Type valueType) const override - { - if (ArgumentValueCallback) - { - ArgumentValueCallback(context, valueType); - } - } - - std::vector SubCommandNames; - std::vector Arguments; - std::function ArgumentValueCallback; -}; - -struct CompletionTestContext -{ - CompletionTestContext(std::string_view word, std::string_view commandLine, std::string_view position) : - context(out, in) - { - context.Reporter.SetChannel(Execution::Reporter::Channel::Completion); - context.Add(CompletionData{ word, commandLine, position }); - } - - std::stringstream out; - std::stringstream in; - Context context; -}; - -TEST_CASE("CommandComplete_Simple", "[complete]") -{ - CompletionTestContext ctc{ "", "winget ", "7" }; - - CompletionTestCommand command; - command.SubCommandNames = { "test1", "test2" }; - command.Arguments = { Argument{ "arg1", 'a', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard } }; - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllSubCommands(command, expected); - OutputAllArgumentNames(command, expected); - - REQUIRE(ctc.out.str() == expected.str()); -} - -TEST_CASE("CommandComplete_PartialCommandMatch", "[complete]") -{ - CompletionTestContext ctc{ "cart", "winget cart", "11" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllSubCommands(command, expected, "cart"); - OutputAllArgumentNames(command, expected, "cart"); - - REQUIRE(ctc.out.str() == expected.str()); -} - -TEST_CASE("CommandComplete_CommandsNotAllowed", "[complete]") -{ - CompletionTestContext ctc{ "", "winget foobar ", "14" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllArgumentNames(command, expected); - - REQUIRE(ctc.out.str() == expected.str()); -} - -TEST_CASE("CommandComplete_Routing1", "[complete]") -{ - CompletionTestContext ctc{ "", "winget --arg1 ", "14" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, - }; - Args::Type argType = static_cast(-1); - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(argType == command.Arguments[0].ExecArgType()); -} - -TEST_CASE("CommandComplete_Routing2", "[complete]") -{ - CompletionTestContext ctc{ "", "winget --arg2 ", "14" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, - }; - Args::Type argType = static_cast(-1); - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(argType == command.Arguments[1].ExecArgType()); -} - -TEST_CASE("CommandComplete_PositionalRouting", "[complete]") -{ - CompletionTestContext ctc{ "", "winget ", "7" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, - }; - Args::Type argType = static_cast(-1); - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllSubCommands(command, expected); - OutputAllArgumentNames(command, expected); - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(argType == command.Arguments[1].ExecArgType()); -} - -TEST_CASE("CommandComplete_PositionalRoutingAfterArgs", "[complete]") -{ - CompletionTestContext ctc{ "", "winget --arg1 value ", "20" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, - }; - Args::Type argType = static_cast(-1); - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllArgumentNames(command, expected); - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(argType == command.Arguments[1].ExecArgType()); -} - -TEST_CASE("CommandComplete_PositionalRoutingAfterDoubleDash", "[complete]") -{ - CompletionTestContext ctc{ "", "winget -- ", "10" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, - }; - Args::Type argType = static_cast(-1); - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(argType == command.Arguments[1].ExecArgType()); -} - -TEST_CASE("CommandComplete_ArgNamesAfterDash", "[complete]") -{ - CompletionTestContext ctc{ "-", "winget -", "8" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, - }; - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllArgumentNames(command, expected); - - REQUIRE(ctc.out.str() == expected.str()); -} - -TEST_CASE("CommandComplete_AliasNames", "[complete]") -{ - CompletionTestContext ctc{ "-a", "winget -a", "9" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, - }; - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllArgumentAliases(command, expected); - - REQUIRE(ctc.out.str() == expected.str()); -} - -TEST_CASE("CommandComplete_ArgNamesFilter", "[complete]") -{ - CompletionTestContext ctc{ "--a", "winget --a", "10" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, - Argument{ "foo1", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, - }; - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllArgumentNames(command, expected, "a"); - - REQUIRE(ctc.out.str() == expected.str()); -} - -TEST_CASE("CommandComplete_IgnoreBadArgs", "[complete]") -{ - CompletionTestContext ctc{ "", "winget foo bar --arg1 ", "22" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, - }; - Args::Type argType = static_cast(-1); - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(argType == command.Arguments[0].ExecArgType()); -} - -TEST_CASE("CommandComplete_OtherArgsParsed", "[complete]") -{ - CompletionTestContext ctc{ "", "winget --arg1 value1 --arg2 value2", "21" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, - }; - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - OutputAllArgumentNames(command, expected); - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(ctc.context.Args.Contains(command.Arguments[0].ExecArgType())); - REQUIRE(ctc.context.Args.GetArg(command.Arguments[0].ExecArgType()) == "value1"); - REQUIRE(ctc.context.Args.Contains(command.Arguments[1].ExecArgType())); - REQUIRE(ctc.context.Args.GetArg(command.Arguments[1].ExecArgType()) == "value2"); -} - -TEST_CASE("CommandComplete_Complex", "[complete]") -{ - CompletionTestContext ctc{ "", "winget foo --arg1 value1 bar junk --arg2 ", "41" }; - - CompletionTestCommand command; - command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; - command.Arguments = { - Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, - Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, - }; - Args::Type argType = static_cast(-1); - command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; - command.Complete(ctc.context); - - // Create expected values - std::stringstream expected; - - REQUIRE(ctc.out.str() == expected.str()); - REQUIRE(argType == command.Arguments[1].ExecArgType()); - REQUIRE(ctc.context.Args.Contains(command.Arguments[0].ExecArgType())); - REQUIRE(ctc.context.Args.GetArg(command.Arguments[0].ExecArgType()) == "value1"); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; + + +TEST_CASE("CompletionData_EmptyWord_PositionAtEnd", "[complete]") +{ + CompletionData cd{ "", "winget ", "7" }; + REQUIRE(cd.Word() == ""); + REQUIRE(cd.BeforeWord().size() == 0); + REQUIRE(cd.AfterWord().size() == 0); +} + +TEST_CASE("CompletionData_EmptyWord_PositionPastEnd", "[complete]") +{ + CompletionData cd{ "", "winget ", "8" }; + REQUIRE(cd.Word() == ""); + REQUIRE(cd.BeforeWord().size() == 0); + REQUIRE(cd.AfterWord().size() == 0); +} + +TEST_CASE("CompletionData_EmptyWord_PositionCorrect", "[complete]") +{ + CompletionData cd{ "", "winget install", "7" }; + REQUIRE(cd.Word() == ""); + REQUIRE(cd.BeforeWord().size() == 0); + REQUIRE(cd.AfterWord().size() == 1); +} + + +TEST_CASE("CompletionData_EmptyWord_PositionOffset", "[complete]") +{ + CompletionData cd{ "", "winget install PowerToys --version", "17" }; + REQUIRE(cd.Word() == ""); + REQUIRE(cd.BeforeWord().size() == 1); + REQUIRE(cd.AfterWord().size() == 2); +} + +TEST_CASE("CompletionData_Word_NoMatch", "[complete]") +{ + auto lambda = []() { CompletionData cd{ "foo", "winget install PowerToys --version", "17" }; }; + REQUIRE_THROWS_HR(lambda(), APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD); +} + +TEST_CASE("CompletionData_Word_SingleMatch", "[complete]") +{ + CompletionData cd{ "power", "winget install power --version", "17" }; + REQUIRE(cd.Word() == "power"); + REQUIRE(cd.BeforeWord().size() == 1); + REQUIRE(cd.AfterWord().size() == 1); +} + +TEST_CASE("CompletionData_Word_MultiMatch_PositionCorrect", "[complete]") +{ + CompletionData cd{ "power", "winget install power --id power", "27" }; + REQUIRE(cd.Word() == "power"); + REQUIRE(cd.BeforeWord().size() == 3); + REQUIRE(cd.AfterWord().size() == 0); +} + +TEST_CASE("CompletionData_Word_MultiMatch_PositionOffset", "[complete]") +{ + CompletionData cd{ "power", "winget install power --id power", "21" }; + REQUIRE(cd.Word() == "power"); + REQUIRE(cd.BeforeWord().size() == 1); + REQUIRE(cd.AfterWord().size() == 2); +} + +TEST_CASE("CompletionData_UTF8_EmptyWord_End", "[complete]") +{ + CompletionData cd{ "", u8"winget install \x175\x12b\x14b\x1e5\x229\x288 --version ", "32" }; + REQUIRE(cd.Word() == ""); + REQUIRE(cd.BeforeWord().size() == 3); + REQUIRE(cd.AfterWord().size() == 0); +} + +TEST_CASE("CompletionData_UTF8_EmptyWord_Middle", "[complete]") +{ + CompletionData cd{ "", u8"winget install \x175\x12b\x14b\x1e5\x229\x288 --version ", "22" }; + REQUIRE(cd.Word() == ""); + REQUIRE(cd.BeforeWord().size() == 2); + REQUIRE(cd.AfterWord().size() == 1); +} + +TEST_CASE("CompletionData_UTF8_UTF8Word", "[complete]") +{ + CompletionData cd{ u8"\x175\x12b\x14b\x1e5\x229\x288", u8"winget install \x175\x12b\x14b\x1e5\x229\x288 --version ", "18" }; + REQUIRE(cd.Word() == u8"\x175\x12b\x14b\x1e5\x229\x288"); + REQUIRE(cd.BeforeWord().size() == 1); + REQUIRE(cd.AfterWord().size() == 1); +} + +void OutputAllSubCommands(Command& command, std::ostream& out, std::string_view filter = {}) +{ + for (const auto& c : command.GetCommands()) + { + if (Utility::CaseInsensitiveStartsWith(c->Name(), filter)) + { + out << c->Name() << std::endl; + } + } +} + +void OutputAllArgumentNames(Command& command, std::ostream& out, std::string_view filter = {}, bool includeCommon = true) +{ + auto args = command.GetArguments(); + if (includeCommon) + { + Argument::GetCommon(args); + } + + for (const auto& a : args) + { + if (Utility::CaseInsensitiveStartsWith(a.Name(), filter)) + { + out << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << a.Name() << std::endl; + } + } +} + +void OutputAllArgumentAliases(Command& command, std::ostream& out, bool includeCommon = true) +{ + auto args = command.GetArguments(); + if (includeCommon) + { + Argument::GetCommon(args); + } + + for (const auto& a : args) + { + if (a.Alias() != ArgumentCommon::NoAlias) + { + out << APPINSTALLER_CLI_ARGUMENT_IDENTIFIER_CHAR << a.Alias() << std::endl; + } + } +} + +TEST_CASE("CompleteCommand_FindRoot", "[complete]") +{ + std::stringstream out, in; + Context context{ out, in }; + + CompleteCommand command{ "test" }; + context.Args.AddArg(Args::Type::Word, ""sv); + context.Args.AddArg(Args::Type::CommandLine, "winget "sv); + context.Args.AddArg(Args::Type::Position, "7"sv); + command.Execute(context); + + // Create expected values + RootCommand expectedCommand; + std::stringstream expected; + OutputAllSubCommands(expectedCommand, expected); + OutputAllArgumentNames(expectedCommand, expected); + + REQUIRE(out.str() == expected.str()); +} + +TEST_CASE("CompleteCommand_FindSource", "[complete]") +{ + std::stringstream out, in; + Context context{ out, in }; + + CompleteCommand command{ "test" }; + context.Args.AddArg(Args::Type::Word, ""sv); + context.Args.AddArg(Args::Type::CommandLine, "winget source "sv); + context.Args.AddArg(Args::Type::Position, "14"sv); + command.Execute(context); + + // Create expected values + SourceCommand expectedCommand{ "test" }; + std::stringstream expected; + OutputAllSubCommands(expectedCommand, expected); + OutputAllArgumentNames(expectedCommand, expected); + + REQUIRE(out.str() == expected.str()); +} + +TEST_CASE("CompleteCommand_FindSourceAdd", "[complete]") +{ + std::stringstream out, in; + Context context{ out, in }; + + CompleteCommand command{ "test" }; + context.Args.AddArg(Args::Type::Word, ""sv); + context.Args.AddArg(Args::Type::CommandLine, "winget source add "sv); + context.Args.AddArg(Args::Type::Position, "18"sv); + command.Execute(context); + + // Create expected values + SourceAddCommand expectedCommand{ "test" }; + std::stringstream expected; + OutputAllSubCommands(expectedCommand, expected); + OutputAllArgumentNames(expectedCommand, expected); + + REQUIRE(out.str() == expected.str()); +} + +struct CompletionTestCommand : public Command +{ + CompletionTestCommand() : Command("test", "") {} + CompletionTestCommand(std::string_view name) : Command(name, "") {} + + std::vector> GetCommands() const override + { + std::vector> result; + + for (const auto& sc : SubCommandNames) + { + result.emplace_back(std::make_unique(sc)); + } + + return result; + } + + std::vector GetArguments() const override + { + return Arguments; + } + + CLI::Resource::LocString ShortDescription() const override { return {}; } + CLI::Resource::LocString LongDescription() const override { return {}; } + + using Command::Complete; + + void Complete(Execution::Context& context, Execution::Args::Type valueType) const override + { + if (ArgumentValueCallback) + { + ArgumentValueCallback(context, valueType); + } + } + + std::vector SubCommandNames; + std::vector Arguments; + std::function ArgumentValueCallback; +}; + +struct CompletionTestContext +{ + CompletionTestContext(std::string_view word, std::string_view commandLine, std::string_view position) : + context(out, in) + { + context.Reporter.SetChannel(Execution::Reporter::Channel::Completion); + context.Add(CompletionData{ word, commandLine, position }); + } + + std::stringstream out; + std::stringstream in; + Context context; +}; + +TEST_CASE("CommandComplete_Simple", "[complete]") +{ + CompletionTestContext ctc{ "", "winget ", "7" }; + + CompletionTestCommand command; + command.SubCommandNames = { "test1", "test2" }; + command.Arguments = { Argument{ "arg1", 'a', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard } }; + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllSubCommands(command, expected); + OutputAllArgumentNames(command, expected); + + REQUIRE(ctc.out.str() == expected.str()); +} + +TEST_CASE("CommandComplete_PartialCommandMatch", "[complete]") +{ + CompletionTestContext ctc{ "cart", "winget cart", "11" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllSubCommands(command, expected, "cart"); + OutputAllArgumentNames(command, expected, "cart"); + + REQUIRE(ctc.out.str() == expected.str()); +} + +TEST_CASE("CommandComplete_CommandsNotAllowed", "[complete]") +{ + CompletionTestContext ctc{ "", "winget foobar ", "14" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllArgumentNames(command, expected); + + REQUIRE(ctc.out.str() == expected.str()); +} + +TEST_CASE("CommandComplete_Routing1", "[complete]") +{ + CompletionTestContext ctc{ "", "winget --arg1 ", "14" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, + }; + Args::Type argType = static_cast(-1); + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(argType == command.Arguments[0].ExecArgType()); +} + +TEST_CASE("CommandComplete_Routing2", "[complete]") +{ + CompletionTestContext ctc{ "", "winget --arg2 ", "14" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, + }; + Args::Type argType = static_cast(-1); + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(argType == command.Arguments[1].ExecArgType()); +} + +TEST_CASE("CommandComplete_PositionalRouting", "[complete]") +{ + CompletionTestContext ctc{ "", "winget ", "7" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, + }; + Args::Type argType = static_cast(-1); + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllSubCommands(command, expected); + OutputAllArgumentNames(command, expected); + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(argType == command.Arguments[1].ExecArgType()); +} + +TEST_CASE("CommandComplete_PositionalRoutingAfterArgs", "[complete]") +{ + CompletionTestContext ctc{ "", "winget --arg1 value ", "20" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, + }; + Args::Type argType = static_cast(-1); + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllArgumentNames(command, expected); + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(argType == command.Arguments[1].ExecArgType()); +} + +TEST_CASE("CommandComplete_PositionalRoutingAfterDoubleDash", "[complete]") +{ + CompletionTestContext ctc{ "", "winget -- ", "10" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, + }; + Args::Type argType = static_cast(-1); + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(argType == command.Arguments[1].ExecArgType()); +} + +TEST_CASE("CommandComplete_ArgNamesAfterDash", "[complete]") +{ + CompletionTestContext ctc{ "-", "winget -", "8" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, + }; + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllArgumentNames(command, expected); + + REQUIRE(ctc.out.str() == expected.str()); +} + +TEST_CASE("CommandComplete_AliasNames", "[complete]") +{ + CompletionTestContext ctc{ "-a", "winget -a", "9" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, + }; + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllArgumentAliases(command, expected); + + REQUIRE(ctc.out.str() == expected.str()); +} + +TEST_CASE("CommandComplete_ArgNamesFilter", "[complete]") +{ + CompletionTestContext ctc{ "--a", "winget --a", "10" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, + Argument{ "foo1", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Positional }, + }; + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllArgumentNames(command, expected, "a"); + + REQUIRE(ctc.out.str() == expected.str()); +} + +TEST_CASE("CommandComplete_IgnoreBadArgs", "[complete]") +{ + CompletionTestContext ctc{ "", "winget foo bar --arg1 ", "22" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, + }; + Args::Type argType = static_cast(-1); + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(argType == command.Arguments[0].ExecArgType()); +} + +TEST_CASE("CommandComplete_OtherArgsParsed", "[complete]") +{ + CompletionTestContext ctc{ "", "winget --arg1 value1 --arg2 value2", "21" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, + }; + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type) { FAIL("No argument value should be requested"); }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + OutputAllArgumentNames(command, expected); + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(ctc.context.Args.Contains(command.Arguments[0].ExecArgType())); + REQUIRE(ctc.context.Args.GetArg(command.Arguments[0].ExecArgType()) == "value1"); + REQUIRE(ctc.context.Args.Contains(command.Arguments[1].ExecArgType())); + REQUIRE(ctc.context.Args.GetArg(command.Arguments[1].ExecArgType()) == "value2"); +} + +TEST_CASE("CommandComplete_Complex", "[complete]") +{ + CompletionTestContext ctc{ "", "winget foo --arg1 value1 bar junk --arg2 ", "41" }; + + CompletionTestCommand command; + command.SubCommandNames = { "car", "cart", "cartesian", "carpet" }; + command.Arguments = { + Argument{ "arg1", '1', Args::Type::Query, CLI::Resource::String::Done, ArgumentType::Standard }, + Argument{ "arg2", '2', Args::Type::Channel, CLI::Resource::String::Done, ArgumentType::Standard }, + }; + Args::Type argType = static_cast(-1); + command.ArgumentValueCallback = [&](Context&, Execution::Args::Type type) { argType = type; }; + command.Complete(ctc.context); + + // Create expected values + std::stringstream expected; + + REQUIRE(ctc.out.str() == expected.str()); + REQUIRE(argType == command.Arguments[1].ExecArgType()); + REQUIRE(ctc.context.Args.Contains(command.Arguments[0].ExecArgType())); + REQUIRE(ctc.context.Args.GetArg(command.Arguments[0].ExecArgType()) == "value1"); +} diff --git a/src/AppInstallerCLITests/CompositeSource.cpp b/src/AppInstallerCLITests/CompositeSource.cpp index b45993ac1a..8160c8df5e 100644 --- a/src/AppInstallerCLITests/CompositeSource.cpp +++ b/src/AppInstallerCLITests/CompositeSource.cpp @@ -1,2039 +1,2039 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSource.h" -#include "TestHooks.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::Pinning; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::Utility; - -constexpr std::string_view s_Everything_Query = "everything"sv; - -// A test source that has two modes: -// 1. A request that IsForEverything returns the stored result. This models the -// incoming search request to a CompositeSource. -// 2. A request that is not for everything invokes TestSource::SearchFunction to -// enable verification of expectations. -struct ComponentTestSource : public TestSource -{ - ComponentTestSource() = default; - - ComponentTestSource(std::string_view identifier, SourceOrigin origin = SourceOrigin::Default) - { - Details.Identifier = identifier; - Details.Origin = origin; - } - - SearchResult Search(const SearchRequest& request) const override - { - if (request.Query && request.Query.value().Value == s_Everything_Query) - { - return Everything; - } - else - { - return TestSource::Search(request); - } - } - - SearchResult Everything; -}; - -// A helper to make matches. -struct Criteria : public PackageMatchFilter -{ - Criteria() : PackageMatchFilter(PackageMatchField::Id, MatchType::Wildcard, ""sv) {} - Criteria(PackageMatchField field) : PackageMatchFilter(field, MatchType::Wildcard, ""sv) {} -}; - -Manifest::Manifest MakeDefaultManifest(std::string_view version = "1.0"sv) -{ - Manifest::Manifest result; - - result.Id = "Id"; - result.DefaultLocalization.Add("Name"); - result.DefaultLocalization.Add("Publisher"); - result.Version = version; - result.Installers.push_back({}); - - return result; -} - -struct TestManifestHelper -{ - TestManifestHelper() : m_manifest(MakeDefaultManifest()) {} - - TestManifestHelper& WithId(const std::string& id) - { - m_manifest.Id = id; - return *this; - } - - TestManifestHelper& WithVersion(std::string_view version) - { - m_manifest.Version = version; - return *this; - } - - TestManifestHelper& WithChannel(const std::string& channel) - { - m_manifest.Channel = channel; - return *this; - } - - TestManifestHelper& WithDefaultName(std::string_view name) - { - m_manifest.DefaultLocalization.Add(std::string{ name }); - return *this; - } - - TestManifestHelper& WithPFN(const std::string& pfn) - { - m_manifest.Installers[0].PackageFamilyName = pfn; - return *this; - } - - TestManifestHelper& WithPC(const std::string& pc) - { - m_manifest.Installers[0].ProductCode = pc; - return *this; - } - - TestManifestHelper& WithType(Manifest::InstallerTypeEnum type) - { - m_manifest.Installers[0].BaseInstallerType = type; - return *this; - } - - TestManifestHelper& WithDisplayVersion(std::string_view version) - { - if (m_manifest.Installers[0].AppsAndFeaturesEntries.empty()) - { - m_manifest.Installers[0].AppsAndFeaturesEntries.emplace_back(); - } - m_manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = version; - return *this; - } - - operator const Manifest::Manifest& () const - { - return m_manifest; - } - -private: - Manifest::Manifest m_manifest; -}; - -struct TestPackageHelper -{ - TestPackageHelper(bool isInstalled, std::shared_ptr source = {}) : - m_isInstalled(isInstalled), m_source(source) - { - m_manifestHelpers.emplace_back(); - } - - TestPackageHelper& HideSRS(bool value = true) - { - m_hideSystemReferenceStrings = value; - return *this; - } - - std::shared_ptr ToPackage() - { - if (!m_package) - { - if (m_isInstalled) - { - m_package = TestCompositePackage::Make(this->operator const Manifest::Manifest&(), m_metadata, std::vector(), m_source); - } - else - { - std::vector manifests; - - for (const auto& helper : m_manifestHelpers) - { - manifests.emplace_back(helper.operator const AppInstaller::Manifest::Manifest &()); - } - - m_package = TestCompositePackage::Make(manifests, m_source, m_hideSystemReferenceStrings); - } - } - - return m_package; - } - - operator std::shared_ptr() - { - return ToPackage(); - } - - TestPackageHelper& WithId(const std::string& id) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithId(id); - return *this; - } - - TestPackageHelper& WithVersion(std::string_view version) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithVersion(version); - return *this; - } - - TestPackageHelper& WithChannel(const std::string& channel) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithChannel(channel); - return *this; - } - - TestPackageHelper& WithDefaultName(std::string_view name) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithDefaultName(name); - return *this; - } - - TestPackageHelper& WithPFN(const std::string& pfn) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithPFN(pfn); - return *this; - } - - TestPackageHelper& WithPC(const std::string& pc) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithPC(pc); - return *this; - } - - TestPackageHelper& WithType(Manifest::InstallerTypeEnum type) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithType(type); - return *this; - } - - TestPackageHelper& WithDisplayVersion(std::string_view version) - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - m_manifestHelpers[0].WithDisplayVersion(version); - return *this; - } - - TestPackageHelper& WithMetadata(PackageVersionMetadata metadata, const std::string& value) - { - THROW_HR_IF(E_UNEXPECTED, !m_isInstalled); - m_metadata[metadata] = value; - return *this; - } - - operator const Manifest::Manifest& () const - { - THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); - return m_manifestHelpers[0]; - } - - TestManifestHelper& MakeManifest() - { - THROW_HR_IF(E_UNEXPECTED, m_isInstalled); - m_manifestHelpers.emplace_back(); - return m_manifestHelpers.back(); - } - -private: - bool m_isInstalled; - std::vector m_manifestHelpers; - std::shared_ptr m_source; - std::shared_ptr m_package; - bool m_hideSystemReferenceStrings = false; - TestCompositePackage::MetadataMap m_metadata; -}; - -// A helper to create the sources used by the majority of tests in this file. -struct CompositeTestSetup -{ - CompositeTestSetup(CompositeSearchBehavior behavior = CompositeSearchBehavior::Installed) : Composite("*Tests") - { - Installed = std::make_shared("InstalledTestSource1", SourceOrigin::Predefined); - Available = std::make_shared("AvailableTestSource1"); - Composite.SetInstalledSource(Source{ Installed }, behavior); - Composite.AddAvailableSource(Source{ Available }); - } - - SearchResult Search(bool disableDataChecks = false) - { - size_t initialCountOfCallsRequiringVersionData = Available->CountOfCallsRequiringVersionData; - size_t initialCountOfCallsRequiringManifestData = Available->CountOfCallsRequiringManifestData; - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, s_Everything_Query); - auto result = Composite.Search(request); - - // We want to prevent calls to these functions for Search as they can require network or I/O activity. - size_t countOfCallsRequiringVersionData = Available->CountOfCallsRequiringVersionData - initialCountOfCallsRequiringVersionData; - size_t countOfCallsRequiringManifestData = Available->CountOfCallsRequiringManifestData - initialCountOfCallsRequiringManifestData; - if (!disableDataChecks && (countOfCallsRequiringVersionData || countOfCallsRequiringManifestData)) - { - std::ostringstream stream; - stream << "Version data calls [" << countOfCallsRequiringVersionData << "] : Manifest data calls [" << countOfCallsRequiringManifestData << "]"; - FAIL(stream.str()); - } - - return result; - } - - TestPackageHelper MakeInstalled(std::shared_ptr source) - { - return { /* isInstalled */ true, std::move(source)}; - } - - TestPackageHelper MakeInstalled() - { - return MakeInstalled(Installed); - } - - TestPackageHelper MakeAvailable(std::shared_ptr source) - { - return { /* isInstalled */ false, std::move(source) }; - } - - TestPackageHelper MakeAvailable() - { - return MakeAvailable(Available); - } - - std::shared_ptr Installed; - std::shared_ptr Available; - CompositeSource Composite; -}; - -// A helper to create the sources used by the majority of tests in this file. -struct CompositeWithTrackingTestSetup : public CompositeTestSetup -{ - CompositeWithTrackingTestSetup() : TrackingFactory([&](const SourceDetails&) { return Tracking; }) - { - Tracking = std::make_shared(SourceDetails{}, SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET)); - TestHook_SetSourceFactoryOverride(std::string{ PackageTrackingCatalogSourceFactory::Type() }, TrackingFactory); - } - - ~CompositeWithTrackingTestSetup() - { - TestHook_ClearSourceFactoryOverrides(); - } - - TestSourceFactory TrackingFactory; - std::shared_ptr Tracking; -}; - -bool SearchRequestIncludes(const std::vector& filters, PackageMatchField field, MatchType type, std::optional value = {}) -{ - bool found = false; - - for (const PackageMatchFilter& filter : filters) - { - if (filter.Field == field && filter.Type == type && - (!value || filter.Value == value.value())) - { - found = true; - } - } - - return found; -} - -void RequireSearchRequestIncludes(const std::vector& filters, PackageMatchField field, MatchType type, std::optional value = {}) -{ - REQUIRE(SearchRequestIncludes(filters, field, type, value)); -} - -TEST_CASE("CompositeSource_PackageFamilyName_NotAvailable", "[CompositeSource]") -{ - // Pre-folded for easier == - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().empty()); -} - -TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); -} - -TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") -{ - std::string pc = "thiscouldbeapc"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPC(pc), Criteria()); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().empty()); -} - -TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") -{ - std::string pc = "thiscouldbeapc"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPC(pc), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, pc); - - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithPC(pc), Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); -} - -TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") -{ - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled(), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact); - - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable(), Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); -} - -TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") -{ - std::string name = "MatchingName"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); - result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(name), Criteria(PackageMatchField::PackageFamilyName)); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); - auto version = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); - REQUIRE(version->GetProperty(PackageVersionProperty::Name).get() == name); - REQUIRE(!Version(version->GetProperty(PackageVersionProperty::Version)).IsUnknown()); -} - -TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource]") -{ - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); - result.Matches.emplace_back(setup.MakeAvailable().WithId("Another diff ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().empty()); -} - -TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn); - - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria()); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); -} - -TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria(PackageMatchField::PackageFamilyName)); - return result; - }; - - std::shared_ptr availablePackage = setup.MakeAvailable().WithPFN(pfn).ToPackage(); - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria(PackageMatchField::PackageFamilyName)); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); -} - -TEST_CASE("CompositeSource_FoundByAvailableRootSearch_NotInstalled", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.empty()); -} - -TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - MatchType originalType = MatchType::Wildcard; - MatchType type = MatchType::Exact; - - CompositeTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn); - - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); - REQUIRE(result.Matches[0].MatchCriteria.Type == originalType); - - // Now make the source root search find it with a better criteria - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria()); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(availablePackage, PackageMatchFilter(PackageMatchField::Id, type, ""sv)); - - result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); - REQUIRE(result.Matches[0].MatchCriteria.Type == type); -} - -TEST_CASE("CompositePackage_PropertyFromInstalled", "[CompositeSource]") -{ - std::string id = "Special test ID"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(id), Criteria()); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetProperty(PackageProperty::Id) == id); -} - -TEST_CASE("CompositePackage_PropertyFromAvailable", "[CompositeSource]") -{ - std::string id = "Special test ID"; - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithId(id), Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetProperty(PackageProperty::Id) == id); -} - -TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - std::string channel = "Channel"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest&) - { - Manifest::Manifest noChannel = MakeDefaultManifest(); - noChannel.Version = "1.0"; - - Manifest::Manifest hasChannel = MakeDefaultManifest(); - hasChannel.Channel = channel; - hasChannel.Version = "2.0"; - - SearchResult result; - result.Matches.emplace_back(TestCompositePackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2); - return result; - }; - - // Disable data checks as we call one of the data methods as validation in the search - SearchResult result = setup.Search(true); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - auto package = result.Matches[0].Package->GetAvailable()[0]; - - auto versionKeys = package->GetVersionKeys(); - REQUIRE(versionKeys.size() == 2); - - auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package); - auto availableVersionKeys = availableVersions->GetVersionKeys(); - REQUIRE(availableVersionKeys.size() == 1); - REQUIRE(availableVersionKeys[0].Channel.empty()); - - auto latestVersion = availableVersions->GetLatestVersion(); - REQUIRE(latestVersion); - REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get().empty()); -} - -TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - std::string channel = "Channel"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn).WithChannel(channel), Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest&) - { - Manifest::Manifest noChannel = MakeDefaultManifest(); - noChannel.Version = "1.0"; - - Manifest::Manifest hasChannel = MakeDefaultManifest(); - hasChannel.Channel = channel; - hasChannel.Version = "2.0"; - - SearchResult result; - result.Matches.emplace_back(TestCompositePackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2); - return result; - }; - - // Disable data checks as we call one of the data methods as validation in the search - SearchResult result = setup.Search(true); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - auto package = result.Matches[0].Package->GetAvailable()[0]; - - auto versionKeys = package->GetVersionKeys(); - REQUIRE(versionKeys.size() == 2); - - auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package); - auto availableVersionKeys = availableVersions->GetVersionKeys(); - REQUIRE(availableVersionKeys.size() == 1); - REQUIRE(availableVersionKeys[0].Channel == channel); - - auto latestVersion = availableVersions->GetLatestVersion(); - REQUIRE(latestVersion); - REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get() == channel); -} - -TEST_CASE("CompositeSource_MultipleAvailableSources_MatchAll", "[CompositeSource]") -{ - TestCommon::TestUserSettings testSettings; - - std::string pfn = "sortof_apfn"; - std::string firstName = "Name1"; - std::string secondName = "Name2"; - - CompositeTestSetup setup; - std::shared_ptr secondAvailable = std::make_shared(); - setup.Composite.AddAvailableSource(Source{ secondAvailable }); - - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(firstName), Criteria()); - return result; - }; - - secondAvailable->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable(secondAvailable).WithDefaultName(secondName), Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 2); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == firstName); - REQUIRE(result.Matches[0].Package->GetAvailable()[1]->GetProperty(PackageProperty::Name).get() == secondName); -} - -TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - std::string firstName = "Name1"; - std::string secondName = "Name2"; - - CompositeTestSetup setup; - std::shared_ptr secondAvailable = std::make_shared(); - setup.Composite.AddAvailableSource(Source{ secondAvailable }); - - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); - - secondAvailable->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(secondName), Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == secondName); -} - -TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[CompositeSource]") -{ - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - - std::shared_ptr secondAvailable = std::make_shared(); - setup.Composite.AddAvailableSource(Source{ secondAvailable }); - - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria(PackageMatchField::PackageFamilyName)); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); - secondAvailable->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 2); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[1]->GetVersionKeys().size() == 1); -} - -TEST_CASE("CompositeSource_IsSame", "[CompositeSource]") -{ - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria()); - - SearchResult result1 = setup.Search(); - REQUIRE(result1.Matches.size() == 1); - - SearchResult result2 = setup.Search(); - REQUIRE(result2.Matches.size() == 1); - - REQUIRE(result1.Matches[0].Package->GetInstalled()); - REQUIRE(result1.Matches[0].Package->GetInstalled()->IsSame(result1.Matches[0].Package->GetInstalled().get())); -} - -TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]") -{ - HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_FOUND; - std::string pfn = "sortof_apfn"; - - std::shared_ptr AvailableSucceeds = std::make_shared(); - AvailableSucceeds->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(TestPackageHelper{ /* isInstalled */ false }.WithPFN(pfn), Criteria()); - return result; - }; - - std::shared_ptr AvailableFails = std::make_shared(); - AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); }; - AvailableFails->Details.Name = "The one that fails"; - - CompositeSource Composite("*CompositeSource_AvailableSearchFailure"); - Composite.AddAvailableSource(Source{ AvailableSucceeds }); - Composite.AddAvailableSource(Source{ AvailableFails }); - - SearchResult result = Composite.Search({}); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - - auto pfns = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); - REQUIRE(pfns.size() == 1); - REQUIRE(pfns[0] == pfn); - - REQUIRE(result.Failures.size() == 1); - REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); - - HRESULT searchFailure = S_OK; - try - { - std::rethrow_exception(result.Failures[0].Exception); - } - catch (const wil::ResultException& re) - { - searchFailure = re.GetErrorCode(); - } - catch (...) {} - - REQUIRE(searchFailure == expectedHR); -} - -TEST_CASE("CompositeSource_InstalledToAvailableCorrelationSearchFailure", "[CompositeSource]") -{ - HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_LONG; - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); - setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); - - std::shared_ptr AvailableFails = std::make_shared(); - AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); }; - AvailableFails->Details.Name = "The one that fails"; - - setup.Composite.AddAvailableSource(Source{ AvailableFails }); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - - REQUIRE(result.Failures.size() == 1); - REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); - - HRESULT searchFailure = S_OK; - try - { - std::rethrow_exception(result.Failures[0].Exception); - } - catch (const wil::ResultException& re) - { - searchFailure = re.GetErrorCode(); - } - catch (...) {} - - REQUIRE(searchFailure == expectedHR); -} - -TEST_CASE("CompositeSource_InstalledAvailableSearchFailure", "[CompositeSource]") -{ - HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_LONG; - std::string pfn = "sortof_apfn"; - - CompositeTestSetup setup; - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); - return result; - }; - - std::shared_ptr AvailableFails = std::make_shared(); - AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); }; - AvailableFails->Details.Name = "The one that fails"; - - setup.Composite.AddAvailableSource(Source{ AvailableFails }); - - setup.Composite.SetInstalledSource(Source{ setup.Installed }, CompositeSearchBehavior::AvailablePackages); - - SearchRequest request; - request.Query = RequestMatch{ MatchType::Exact, "whatever" }; - SearchResult result = setup.Composite.Search(request); - - REQUIRE(result.Matches.size() == 1); - - REQUIRE(result.Failures.size() == 1); - REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); - - HRESULT searchFailure = S_OK; - try - { - std::rethrow_exception(result.Failures[0].Exception); - } - catch (const wil::ResultException& re) - { - searchFailure = re.GetErrorCode(); - } - catch (...) {} - - REQUIRE(searchFailure == expectedHR); -} - -TEST_CASE("CompositeSource_TrackingPackageFound", "[CompositeSource]") -{ - std::string availableID = "Available.ID"; - std::string pfn = "sortof_apfn"; - - CompositeWithTrackingTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria()); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - if (request.Filters.empty()) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - } - else - { - REQUIRE(request.Filters.size() == 1); - RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); - } - - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - setup.Tracking->GetIndex().AddManifest(availablePackage); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()); -} - -TEST_CASE("CompositeSource_TrackingPackageFound_MetadataPopulatedFromTracking", "[CompositeSource]") -{ - std::string availableID = "Available.ID"; - std::string pfn = "sortof_apfn"; - - CompositeWithTrackingTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria()); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - if (request.Filters.empty()) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - } - else - { - REQUIRE(request.Filters.size() == 1); - RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); - } - - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - auto manifestId = setup.Tracking->GetIndex().AddManifest(availablePackage); - - // Add test PackageVersionMetadata to be populated to InstalledVersion metadata - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::TrackingWriteTime, "100"); - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::UserIntentArchitecture, "X86"); - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::UserIntentLocale, "en-US"); - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InstalledArchitecture, "X86"); - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InstalledLocale, "en-US"); - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::PinnedState, "PinnedByManifest"); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - - auto metadata = GetInstalledVersion(result.Matches[0].Package)->GetMetadata(); - REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentArchitecture] == "X86"); - REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentLocale] == "en-US"); - REQUIRE(metadata[Repository::PackageVersionMetadata::InstalledArchitecture] == "X86"); - REQUIRE(metadata[Repository::PackageVersionMetadata::InstalledLocale] == "en-US"); - REQUIRE(metadata[Repository::PackageVersionMetadata::PinnedState] == "PinnedByManifest"); -} - -TEST_CASE("CompositeSource_TrackingPackageFound_UserInstallerArgsPopulatedFromTracking", "[CompositeSource]") -{ - std::string availableID = "Available.ID"; - std::string pfn = "sortof_apfn"; - - CompositeWithTrackingTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria()); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - if (request.Filters.empty()) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - } - else - { - REQUIRE(request.Filters.size() == 1); - RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); - } - - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - auto manifestId = setup.Tracking->GetIndex().AddManifest(availablePackage); - - // InitialOverrideArguments and InitialCustomSwitches are only stored in the tracking catalog, - // so they must be merged from there into the composite installed version's metadata. - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InitialOverrideArguments, "/silent /norestart"); - setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InitialCustomSwitches, "--no-telemetry"); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - - auto metadata = GetInstalledVersion(result.Matches[0].Package)->GetMetadata(); - REQUIRE(metadata[Repository::PackageVersionMetadata::InitialOverrideArguments] == "/silent /norestart"); - REQUIRE(metadata[Repository::PackageVersionMetadata::InitialCustomSwitches] == "--no-telemetry"); -} - -TEST_CASE("CompositeSource_TrackingFound_AvailableNot", "[CompositeSource]") -{ - std::string availableID = "Available.ID"; - std::string pfn = "sortof_apfn"; - - CompositeWithTrackingTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria()); - return result; - }; - - setup.Tracking->GetIndex().AddManifest(availablePackage); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(result.Matches[0].Package->GetAvailable().empty()); -} - -TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]") -{ - CompositeWithTrackingTestSetup setup; - - std::string availableID = "Available.ID"; - std::string pfn = "sortof_apfn"; - - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); - - SearchResult result; - result.Matches.emplace_back(installedPackage, Criteria(PackageMatchField::PackageFamilyName)); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - REQUIRE(request.Filters.size() == 1); - RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); - - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - setup.Tracking->GetIndex().AddManifest(availablePackage); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Package); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)); - REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); - REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()); -} - -TEST_CASE("CompositeSource_TrackingFound_NotInstalled", "[CompositeSource]") -{ - std::string availableID = "Available.ID"; - std::string pfn = "sortof_apfn"; - - CompositeWithTrackingTestSetup setup; - auto installedPackage = setup.MakeInstalled().WithPFN(pfn); - auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); - - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - - setup.Tracking->GetIndex().AddManifest(availablePackage); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.empty()); -} - -TEST_CASE("CompositeSource_NullInstalledVersion", "[CompositeSource]") -{ - CompositeTestSetup setup; - setup.Installed->Everything.Matches.emplace_back(setup.MakeAvailable(), Criteria()); - - // We are mostly testing to see if a null installed version causes an AV or not - SearchResult result = setup.Search(); - REQUIRE(result.Matches.size() == 0); -} - -TEST_CASE("CompositeSource_NullAvailableVersion", "[CompositeSource]") -{ - CompositeTestSetup setup{ CompositeSearchBehavior::AvailablePackages }; - setup.Available->Everything.Matches.emplace_back(setup.MakeInstalled(), Criteria()); - - // We are mostly testing to see if a null available version causes an AV or not - REQUIRE_THROWS_HR(setup.Search(), E_UNEXPECTED); -} - -struct ExpectedResultForPinBehavior -{ - ExpectedResultForPinBehavior(bool isUpdateAvailable, std::optional latestAvailableVersion) - : IsUpdateAvailable(isUpdateAvailable), LatestAvailableVersion(latestAvailableVersion) {} - ExpectedResultForPinBehavior() {} - - bool IsUpdateAvailable = false; - std::optional LatestAvailableVersion; -}; - -struct ExpectedPackageVersionKey : public PackageVersionKey -{ - ExpectedPackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel, PinType pinType) : - PackageVersionKey(sourceId, version, channel), PinnedState(pinType) {} - - PinType PinnedState; -}; - -struct ExpectedResultsForPinning -{ - std::map ResultsForPinBehavior; - std::vector AvailableVersions; -}; - -void RequireExpectedResultsWithPin(std::shared_ptr package, const ExpectedResultsForPinning& expectedResult, std::shared_ptr packageVersion = {}) -{ - PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto availableVersions = GetAvailableVersionsForInstalledVersion(package); - - if (!packageVersion) - { - packageVersion = GetInstalledVersion(package); - } - - for (const auto& entry : expectedResult.ResultsForPinBehavior) - { - auto pinBehavior = entry.first; - const auto& result = entry.second; - - auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, packageVersion); - auto latestAvailable = evaluator.GetLatestAvailableVersionForPins(availableVersions); - - REQUIRE(evaluator.IsUpdate(latestAvailable) == result.IsUpdateAvailable); - - if (result.LatestAvailableVersion.has_value()) - { - REQUIRE(latestAvailable); - REQUIRE(latestAvailable->GetManifest().Version == result.LatestAvailableVersion.value()); - } - else - { - REQUIRE(!latestAvailable); - } - } - - auto availableVersionKeys = availableVersions->GetVersionKeys(); - REQUIRE(availableVersionKeys.size() == expectedResult.AvailableVersions.size()); - for (size_t i = 0; i < availableVersionKeys.size(); ++i) - { - auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, packageVersion); - - auto availableVersion = availableVersions->GetVersion(expectedResult.AvailableVersions[i]); - REQUIRE(availableVersion); - REQUIRE(availableVersionKeys[i].SourceId == expectedResult.AvailableVersions[i].SourceId); - REQUIRE(availableVersionKeys[i].Version == expectedResult.AvailableVersions[i].Version); - REQUIRE(evaluator.EvaluatePinType(availableVersion) == expectedResult.AvailableVersions[i].PinnedState); - } -} - -TEST_CASE("CompositeSource_Pinning_AvailableVersionPinned", "[CompositeSource][PinFlow]") -{ - // We use an installed package that has 3 available versions: v1.0.0, v1.0.1 and v1.1.0. - // Installed is v1.0.1 - // We then test the 4 possible pin states (unpinned, Pinned, Blocked, Gated) - // with the 3 possible pin search behaviors (ignore, consider, include pinned) - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - TestUserSettings userSettings; - CompositeTestSetup setup; - - auto installedPackage = setup.MakeInstalled().WithVersion("1.0.1"sv); - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest&) - { - auto manifest1 = MakeDefaultManifest("1.0.0"sv); - auto manifest2 = MakeDefaultManifest("1.0.1"sv); - auto manifest3 = MakeDefaultManifest("1.1.0"sv); - auto package = TestCompositePackage::Make( - std::vector{ manifest3, manifest2, manifest1 }, - setup.Available); - - SearchResult result; - result.Matches.emplace_back(package, Criteria()); - return result; - }; - - ExpectedResultsForPinning expectedResult; - // The result when ignoring pins is always the same - expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" }; - - PinKey pinKey("Id", setup.Available->Details.Identifier); - auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - REQUIRE(pinningIndex); - - SECTION("Unpinned") - { - // If there are no pins, the result should not change if we consider them - expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; - expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; - expectedResult.AvailableVersions = { - { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown }, - { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, - { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, - }; - } - SECTION("Pinned") - { - pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); - - // Pinning pins are ignored with --include-pinned - expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; - - expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; - expectedResult.AvailableVersions = { - { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Pinning }, - { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Pinning }, - { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Pinning }, - }; - } - SECTION("Blocked") - { - pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); - expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; - - // Blocking pins are not affected by --include-pinned - expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; - - expectedResult.AvailableVersions = { - { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Blocking }, - { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Blocking }, - { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Blocking }, - }; - } - SECTION("Gated to 1.*") - { - pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); - expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" }; - - // Gating pins are not affected by --include-pinned - expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; - - expectedResult.AvailableVersions = { - { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown }, - { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, - { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, - }; - } - SECTION("Gated to 1.0.*") - { - pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.0.*"sv })); - expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ "1.0.1" }; - - // Gating pins are not affected by --include-pinned - expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; - - expectedResult.AvailableVersions = { - { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Gating }, - { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, - { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, - }; - } - - SearchResult result = setup.Search(); - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - - RequireExpectedResultsWithPin(package, expectedResult); -} - -TEST_CASE("CompositeSource_Pinning_OneSourcePinned", "[CompositeSource][PinFlow]") -{ - // We use an installed package that has 2 available sources. - // If one of them is pinned, we should still get the updates from the other one. - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - TestUserSettings userSettings; - CompositeTestSetup setup; - - auto installedPackage = setup.MakeInstalled().WithVersion("1.0"sv); - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest&) - { - auto package = TestCompositePackage::Make(std::vector{ MakeDefaultManifest("2.0"sv) }, setup.Available); - - SearchResult result; - result.Matches.emplace_back(package, Criteria()); - return result; - }; - - std::shared_ptr secondAvailable = std::make_shared("SecondTestSource"); - setup.Composite.AddAvailableSource(Source{ secondAvailable }); - secondAvailable->SearchFunction = [&](const SearchRequest&) - { - auto package = TestCompositePackage::Make(std::vector{ MakeDefaultManifest("1.1"sv) }, secondAvailable); - - SearchResult result; - result.Matches.emplace_back(package, Criteria()); - return result; - }; - - { - PinKey pinKey("Id", setup.Available->Details.Identifier); - auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - REQUIRE(pinningIndex); - pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); - } - - ExpectedResultsForPinning expectedResult; - expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; - expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1" }; - expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; - expectedResult.AvailableVersions = { - { "AvailableTestSource1", "2.0", "", Pinning::PinType::Pinning }, - { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown }, - }; - - SearchResult result = setup.Search(); - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - RequireExpectedResultsWithPin(package, expectedResult); -} - -TEST_CASE("CompositeSource_Pinning_OneSourceGated", "[CompositeSource][PinFlow]") -{ - // We use an installed package that has 2 available sources. - // If one of them has a gating pin, we should still get the updates from it - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - TestUserSettings userSettings; - CompositeTestSetup setup; - - auto installedPackage = setup.MakeInstalled().WithVersion("1.0.1"sv); - setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest&) - { - auto package = TestCompositePackage::Make( - std::vector{ - MakeDefaultManifest("2.0"sv), - MakeDefaultManifest("1.2"sv), - }, - setup.Available); - - SearchResult result; - result.Matches.emplace_back(package, Criteria()); - return result; - }; - - std::shared_ptr secondAvailable = std::make_shared("SecondTestSource"); - setup.Composite.AddAvailableSource(Source{ secondAvailable }); - secondAvailable->SearchFunction = [&](const SearchRequest&) - { - auto package = TestCompositePackage::Make(std::vector{ MakeDefaultManifest("1.1"sv) }, secondAvailable); - - SearchResult result; - result.Matches.emplace_back(package, Criteria()); - return result; - }; - - { - PinKey pinKey("Id", setup.Available->Details.Identifier); - auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - REQUIRE(pinningIndex); - pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); - } - - ExpectedResultsForPinning expectedResult; - expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; - expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; - expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; - expectedResult.AvailableVersions = { - { "AvailableTestSource1", "2.0", "", Pinning::PinType::Gating }, - { "AvailableTestSource1", "1.2", "", Pinning::PinType::Unknown }, - { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown }, - }; - - SearchResult result = setup.Search(); - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - RequireExpectedResultsWithPin(package, expectedResult); -} - -TEST_CASE("CompositeSource_Pinning_MultipleInstalled", "[CompositeSource][PinFlow]") -{ - // Tests the case where multiple installed packages match to a single available package. - // If one of the two installed packages is pinned, when searching we should get - // two Composite packages, with only one of them pinned. - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - TestUserSettings userSettings; - - std::string packageId = "packageId"; - std::string productCode1 = "product-code1"; - std::string productCode2 = "product-code2"; - - CompositeTestSetup setup; - - // Installed packages differ in product code and version - auto installedPackage1 = setup.MakeInstalled().WithId(productCode1).WithPC(productCode1).WithVersion("1.1"sv); - auto installedPackage2 = setup.MakeInstalled().WithId(productCode2).WithPC(productCode2).WithVersion("1.2"sv); - - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - bool isSearchById = SearchRequestIncludes(request.Inclusions, PackageMatchField::Id, MatchType::Exact, packageId); - - SearchResult result; - if (isSearchById || SearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, productCode1)) - { - result.Matches.emplace_back(installedPackage1, Criteria(request.Inclusions[0].Field)); - } - - if (isSearchById || SearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, productCode2)) - { - result.Matches.emplace_back(installedPackage2, Criteria(request.Inclusions[0].Field)); - } - - return result; - }; - - // Available package has the same ID, no product code, and different version from both the installed packages; - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithId(packageId).WithVersion("2.0"sv), Criteria()); - return result; - }; - - // We will pin the first package only - PinKey pinKey = PinKey::GetPinKeyForInstalled(productCode1); - auto pinningIndex = PinningIndex::OpenOrCreateDefault(); - REQUIRE(pinningIndex); - - // We will check the pinning status for both installed packages - ExpectedResultsForPinning expectedResult1; - ExpectedResultsForPinning expectedResult2; - - expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins] - = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; - - // The second package is never pinned, so its result is always the same - expectedResult2.ResultsForPinBehavior[PinBehavior::IgnorePins] - = expectedResult2.ResultsForPinBehavior[PinBehavior::ConsiderPins] - = expectedResult2.ResultsForPinBehavior[PinBehavior::IncludePinned] - = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; - expectedResult2.AvailableVersions = { - { "AvailableTestSource1", "2.0", "", Pinning::PinType::Unknown }, - }; - - SECTION("Unpinned") - { - // If there are no pins, the result should not change if we consider them - expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] - = expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] - = expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins]; - expectedResult1.AvailableVersions = { - { "AvailableTestSource1", "2.0", "", Pinning::PinType::Unknown }, - }; - } - SECTION("Pinned") - { - pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); - - // Pinning pins are ignored with --include-pinned - expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins]; - - expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; - expectedResult1.AvailableVersions = { - { "AvailableTestSource1", "2.0", "", Pinning::PinType::Pinning }, - }; - } - SECTION("Blocked") - { - pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); - expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; - - // Blocking pins are not affected by --include-pinned - expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins]; - - expectedResult1.AvailableVersions = { - { "AvailableTestSource1", "2.0", "", Pinning::PinType::Blocking }, - }; - } - - SearchRequest searchRequest; - searchRequest.Inclusions.emplace_back(PackageMatchField::Id, MatchType::Exact, packageId); - SearchResult result = setup.Composite.Search(searchRequest); - - REQUIRE(result.Matches.size() == 1); - auto installedPackage = result.Matches[0].Package->GetInstalled(); - REQUIRE(installedPackage); - auto installedVersions = installedPackage->GetVersionKeys(); - REQUIRE(installedVersions.size() == 2); - - // Here we assume that the order we return the packages in the installed source - // search is preserved. We'll need to change it if that stops being the case. - auto packageVersion1 = installedPackage->GetVersion(installedVersions[1]); - REQUIRE(packageVersion1); - - auto packageVersion2 = installedPackage->GetVersion(installedVersions[0]); - REQUIRE(packageVersion2); - - RequireExpectedResultsWithPin(result.Matches[0].Package, expectedResult1, packageVersion1); - RequireExpectedResultsWithPin(result.Matches[0].Package, expectedResult2, packageVersion2); -} - -TEST_CASE("CompositeSource_CorrelateToInstalledContainsManifestData", "[CompositeSource]") -{ - CompositeTestSetup setup; - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - if (request.Purpose == SearchPurpose::CorrelationToInstalled) - { - bool expectedSearchFound = false; - for (const auto& inclusion : request.Inclusions) - { - if (inclusion.Field == PackageMatchField::ProductCode && inclusion.Value == "hello") - { - expectedSearchFound = true; - break; - } - } - - REQUIRE(expectedSearchFound); - } - - SearchResult result; - return result; - }; - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithPC("hello"), Criteria()); - return result; - }; - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, "NotForEverything"); - SearchResult result = setup.Composite.Search(request); -} - -TEST_CASE("CompositeSource_Respects_FeatureFlag_ManifestMayContainAdditionalSystemReferenceStrings", "[CompositeSource]") -{ - std::string id = "Special test ID"; - std::string productCode1 = "product-code1"; - - CompositeTestSetup setup; - bool productCodeSearched = false; - setup.Installed->SearchFunction = [&](const SearchRequest& request) - { - for (const auto& inclusion : request.Inclusions) - { - if (inclusion.Field == PackageMatchField::ProductCode) - { - productCodeSearched = true; - } - } - - return SearchResult{}; - }; - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeAvailable().WithId(id).WithPC(productCode1).HideSRS(), Criteria()); - return result; - }; - - SECTION("Feature false") - { - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, "NotForEverything"); - SearchResult result = setup.Composite.Search(request); - - REQUIRE(!productCodeSearched); - } - SECTION("Feature true") - { - setup.Available->QueryFeatureFlagFunction = [](SourceFeatureFlag flag) - { - return (flag == SourceFeatureFlag::ManifestMayContainAdditionalSystemReferenceStrings); - }; - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, "NotForEverything"); - SearchResult result = setup.Composite.Search(request); - - REQUIRE(productCodeSearched); - } -} - -TEST_CASE("CompositeSource_SxS_TwoVersions_NoAvailable", "[CompositeSource][SideBySide]") -{ - std::string productCode1 = "PC1"; - std::string productCode2 = "PC2"; - - CompositeTestSetup setup; - auto availablePackage = setup.MakeAvailable(); - - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("1.0").WithPC(productCode1), Criteria()); - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("2.0").WithPC(productCode2), Criteria()); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 2); -} - -TEST_CASE("CompositeSource_SxS_TwoVersions_DifferentAvailable", "[CompositeSource][SideBySide]") -{ - std::string productCode1 = "PC1"; - std::string productCode2 = "PC2"; - - CompositeTestSetup setup; - auto availablePackage1 = setup.MakeAvailable().ToPackage(); - auto availablePackage2 = setup.MakeAvailable().ToPackage(); - - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("1.0").WithPC(productCode1), Criteria()); - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("2.0").WithPC(productCode2), Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest& request) - { - SearchResult result; - - std::string productCode; - for (const auto& item : request.Inclusions) - { - if (item.Field == PackageMatchField::ProductCode) - { - productCode = item.Value; - break; - } - } - - if (productCode == productCode1) - { - result.Matches.emplace_back(availablePackage1, Criteria()); - } - else if (productCode == productCode1) - { - result.Matches.emplace_back(availablePackage2, Criteria()); - } - - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 2); -} - -TEST_CASE("CompositeSource_SxS_TwoVersions_SameAvailable", "[CompositeSource][SideBySide]") -{ - std::string version1 = "1.0"; - std::string version2 = "2.0"; - std::string productCode1 = "PC1"; - std::string productCode2 = "PC2"; - - CompositeTestSetup setup; - auto availablePackage = setup.MakeAvailable().ToPackage(); - - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria()); - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - auto installedPackage = package->GetInstalled(); - REQUIRE(installedPackage); - auto installedVersions = installedPackage->GetVersionKeys(); - REQUIRE(installedVersions.size() == 2); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); - auto availablePackages = package->GetAvailable(); - REQUIRE(availablePackages.size() == 1); - REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); -} - -TEST_CASE("CompositeSource_SxS_ThreeVersions_SameAvailable", "[CompositeSource][SideBySide]") -{ - std::string version1 = "1.0"; - std::string version2 = "2.0"; - std::string version3 = "3.0"; - std::string productCode1 = "PC1"; - std::string productCode2 = "PC2"; - std::string productCode3 = "PC3"; - - CompositeTestSetup setup; - auto availablePackage = setup.MakeAvailable().ToPackage(); - - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria()); - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version3).WithPC(productCode3), Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - auto installedPackage = package->GetInstalled(); - REQUIRE(installedPackage); - auto installedVersions = installedPackage->GetVersionKeys(); - REQUIRE(installedVersions.size() == 3); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version3; })); - auto availablePackages = package->GetAvailable(); - REQUIRE(availablePackages.size() == 1); - REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); -} - -TEST_CASE("CompositeSource_SxS_TwoVersions_SameAvailable_Tracking", "[CompositeSource][SideBySide]") -{ - std::string version1 = "1.0"; - std::string version2 = "2.0"; - std::string productCode1 = "PC1"; - std::string productCode2 = "PC2"; - - CompositeWithTrackingTestSetup setup; - auto installedPackage1 = setup.MakeInstalled().WithVersion(version1).WithPC(productCode1); - auto availablePackage = setup.MakeAvailable().ToPackage(); - - setup.Installed->Everything.Matches.emplace_back(installedPackage1, Criteria()); - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); - setup.Tracking->GetIndex().AddManifest(installedPackage1); - - setup.Available->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(availablePackage, Criteria()); - return result; - }; - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - auto installedPackage = package->GetInstalled(); - REQUIRE(installedPackage); - auto installedVersions = installedPackage->GetVersionKeys(); - REQUIRE(installedVersions.size() == 2); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); - auto availablePackages = package->GetAvailable(); - REQUIRE(availablePackages.size() == 1); - REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); -} - -TEST_CASE("CompositeSource_SxS_Available_TwoVersions_SameAvailable", "[CompositeSource][SideBySide]") -{ - std::string version1 = "1.0"; - std::string version2 = "2.0"; - std::string productCode1 = "PC1"; - std::string productCode2 = "PC2"; - - CompositeTestSetup setup; - auto availablePackage = setup.MakeAvailable().ToPackage(); - - setup.Installed->SearchFunction = [&](const SearchRequest&) - { - SearchResult result; - result.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria()); - result.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); - return result; - }; - - setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); - - SearchResult result = setup.Search(); - - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - auto installedPackage = package->GetInstalled(); - REQUIRE(installedPackage); - auto installedVersions = installedPackage->GetVersionKeys(); - REQUIRE(installedVersions.size() == 2); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); - REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); - auto availablePackages = package->GetAvailable(); - REQUIRE(availablePackages.size() == 1); - REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); -} - -TEST_CASE("CompositeSource_MappedVersions_ProperSorting", "[CompositeSource]") -{ - std::string installedID = "Installed.Id"; - std::string availableID = "Available.Id"; - auto type = Manifest::InstallerTypeEnum::Exe; - std::string pfn = "MY_PFN"; - std::string version1 = "1000.0"; - std::string version2 = "2000.0"; - std::string versionMapped1 = "1.0"; - std::string versionMapped2 = "2.0"; - - CompositeTestSetup setup; - - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(installedID).WithPFN(pfn).WithVersion(version1).WithMetadata(PackageVersionMetadata::InstalledType, "exe"), Criteria()); - setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(installedID).WithPFN(pfn).WithVersion(version2).WithMetadata(PackageVersionMetadata::InstalledType, "exe"), Criteria()); - - setup.Available->SearchFunction = [&](const SearchRequest&) - { - auto package = setup.MakeAvailable(); - package.WithId(availableID).WithType(type).WithPFN(pfn).WithVersion(versionMapped1).WithDisplayVersion(version1); - package.MakeManifest().WithId(availableID).WithType(type).WithPFN(pfn).WithVersion(versionMapped2).WithDisplayVersion(version2); - - SearchResult result; - result.Matches.emplace_back(package, Criteria()); - return result; - }; - - SearchResult result = setup.Search(true); - - REQUIRE(result.Matches.size() == 1); - auto package = result.Matches[0].Package; - REQUIRE(package); - auto installedPackage = package->GetInstalled(); - REQUIRE(installedPackage); - auto installedVersions = installedPackage->GetVersionKeys(); - REQUIRE(installedVersions.size() == 2); - REQUIRE(installedVersions[0].Version == versionMapped2); - REQUIRE(installedVersions[1].Version == versionMapped1); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSource.h" +#include "TestHooks.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Utility; + +constexpr std::string_view s_Everything_Query = "everything"sv; + +// A test source that has two modes: +// 1. A request that IsForEverything returns the stored result. This models the +// incoming search request to a CompositeSource. +// 2. A request that is not for everything invokes TestSource::SearchFunction to +// enable verification of expectations. +struct ComponentTestSource : public TestSource +{ + ComponentTestSource() = default; + + ComponentTestSource(std::string_view identifier, SourceOrigin origin = SourceOrigin::Default) + { + Details.Identifier = identifier; + Details.Origin = origin; + } + + SearchResult Search(const SearchRequest& request) const override + { + if (request.Query && request.Query.value().Value == s_Everything_Query) + { + return Everything; + } + else + { + return TestSource::Search(request); + } + } + + SearchResult Everything; +}; + +// A helper to make matches. +struct Criteria : public PackageMatchFilter +{ + Criteria() : PackageMatchFilter(PackageMatchField::Id, MatchType::Wildcard, ""sv) {} + Criteria(PackageMatchField field) : PackageMatchFilter(field, MatchType::Wildcard, ""sv) {} +}; + +Manifest::Manifest MakeDefaultManifest(std::string_view version = "1.0"sv) +{ + Manifest::Manifest result; + + result.Id = "Id"; + result.DefaultLocalization.Add("Name"); + result.DefaultLocalization.Add("Publisher"); + result.Version = version; + result.Installers.push_back({}); + + return result; +} + +struct TestManifestHelper +{ + TestManifestHelper() : m_manifest(MakeDefaultManifest()) {} + + TestManifestHelper& WithId(const std::string& id) + { + m_manifest.Id = id; + return *this; + } + + TestManifestHelper& WithVersion(std::string_view version) + { + m_manifest.Version = version; + return *this; + } + + TestManifestHelper& WithChannel(const std::string& channel) + { + m_manifest.Channel = channel; + return *this; + } + + TestManifestHelper& WithDefaultName(std::string_view name) + { + m_manifest.DefaultLocalization.Add(std::string{ name }); + return *this; + } + + TestManifestHelper& WithPFN(const std::string& pfn) + { + m_manifest.Installers[0].PackageFamilyName = pfn; + return *this; + } + + TestManifestHelper& WithPC(const std::string& pc) + { + m_manifest.Installers[0].ProductCode = pc; + return *this; + } + + TestManifestHelper& WithType(Manifest::InstallerTypeEnum type) + { + m_manifest.Installers[0].BaseInstallerType = type; + return *this; + } + + TestManifestHelper& WithDisplayVersion(std::string_view version) + { + if (m_manifest.Installers[0].AppsAndFeaturesEntries.empty()) + { + m_manifest.Installers[0].AppsAndFeaturesEntries.emplace_back(); + } + m_manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = version; + return *this; + } + + operator const Manifest::Manifest& () const + { + return m_manifest; + } + +private: + Manifest::Manifest m_manifest; +}; + +struct TestPackageHelper +{ + TestPackageHelper(bool isInstalled, std::shared_ptr source = {}) : + m_isInstalled(isInstalled), m_source(source) + { + m_manifestHelpers.emplace_back(); + } + + TestPackageHelper& HideSRS(bool value = true) + { + m_hideSystemReferenceStrings = value; + return *this; + } + + std::shared_ptr ToPackage() + { + if (!m_package) + { + if (m_isInstalled) + { + m_package = TestCompositePackage::Make(this->operator const Manifest::Manifest&(), m_metadata, std::vector(), m_source); + } + else + { + std::vector manifests; + + for (const auto& helper : m_manifestHelpers) + { + manifests.emplace_back(helper.operator const AppInstaller::Manifest::Manifest &()); + } + + m_package = TestCompositePackage::Make(manifests, m_source, m_hideSystemReferenceStrings); + } + } + + return m_package; + } + + operator std::shared_ptr() + { + return ToPackage(); + } + + TestPackageHelper& WithId(const std::string& id) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithId(id); + return *this; + } + + TestPackageHelper& WithVersion(std::string_view version) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithVersion(version); + return *this; + } + + TestPackageHelper& WithChannel(const std::string& channel) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithChannel(channel); + return *this; + } + + TestPackageHelper& WithDefaultName(std::string_view name) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithDefaultName(name); + return *this; + } + + TestPackageHelper& WithPFN(const std::string& pfn) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithPFN(pfn); + return *this; + } + + TestPackageHelper& WithPC(const std::string& pc) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithPC(pc); + return *this; + } + + TestPackageHelper& WithType(Manifest::InstallerTypeEnum type) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithType(type); + return *this; + } + + TestPackageHelper& WithDisplayVersion(std::string_view version) + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + m_manifestHelpers[0].WithDisplayVersion(version); + return *this; + } + + TestPackageHelper& WithMetadata(PackageVersionMetadata metadata, const std::string& value) + { + THROW_HR_IF(E_UNEXPECTED, !m_isInstalled); + m_metadata[metadata] = value; + return *this; + } + + operator const Manifest::Manifest& () const + { + THROW_HR_IF(E_UNEXPECTED, m_manifestHelpers.size() != 1); + return m_manifestHelpers[0]; + } + + TestManifestHelper& MakeManifest() + { + THROW_HR_IF(E_UNEXPECTED, m_isInstalled); + m_manifestHelpers.emplace_back(); + return m_manifestHelpers.back(); + } + +private: + bool m_isInstalled; + std::vector m_manifestHelpers; + std::shared_ptr m_source; + std::shared_ptr m_package; + bool m_hideSystemReferenceStrings = false; + TestCompositePackage::MetadataMap m_metadata; +}; + +// A helper to create the sources used by the majority of tests in this file. +struct CompositeTestSetup +{ + CompositeTestSetup(CompositeSearchBehavior behavior = CompositeSearchBehavior::Installed) : Composite("*Tests") + { + Installed = std::make_shared("InstalledTestSource1", SourceOrigin::Predefined); + Available = std::make_shared("AvailableTestSource1"); + Composite.SetInstalledSource(Source{ Installed }, behavior); + Composite.AddAvailableSource(Source{ Available }); + } + + SearchResult Search(bool disableDataChecks = false) + { + size_t initialCountOfCallsRequiringVersionData = Available->CountOfCallsRequiringVersionData; + size_t initialCountOfCallsRequiringManifestData = Available->CountOfCallsRequiringManifestData; + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, s_Everything_Query); + auto result = Composite.Search(request); + + // We want to prevent calls to these functions for Search as they can require network or I/O activity. + size_t countOfCallsRequiringVersionData = Available->CountOfCallsRequiringVersionData - initialCountOfCallsRequiringVersionData; + size_t countOfCallsRequiringManifestData = Available->CountOfCallsRequiringManifestData - initialCountOfCallsRequiringManifestData; + if (!disableDataChecks && (countOfCallsRequiringVersionData || countOfCallsRequiringManifestData)) + { + std::ostringstream stream; + stream << "Version data calls [" << countOfCallsRequiringVersionData << "] : Manifest data calls [" << countOfCallsRequiringManifestData << "]"; + FAIL(stream.str()); + } + + return result; + } + + TestPackageHelper MakeInstalled(std::shared_ptr source) + { + return { /* isInstalled */ true, std::move(source)}; + } + + TestPackageHelper MakeInstalled() + { + return MakeInstalled(Installed); + } + + TestPackageHelper MakeAvailable(std::shared_ptr source) + { + return { /* isInstalled */ false, std::move(source) }; + } + + TestPackageHelper MakeAvailable() + { + return MakeAvailable(Available); + } + + std::shared_ptr Installed; + std::shared_ptr Available; + CompositeSource Composite; +}; + +// A helper to create the sources used by the majority of tests in this file. +struct CompositeWithTrackingTestSetup : public CompositeTestSetup +{ + CompositeWithTrackingTestSetup() : TrackingFactory([&](const SourceDetails&) { return Tracking; }) + { + Tracking = std::make_shared(SourceDetails{}, SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET)); + TestHook_SetSourceFactoryOverride(std::string{ PackageTrackingCatalogSourceFactory::Type() }, TrackingFactory); + } + + ~CompositeWithTrackingTestSetup() + { + TestHook_ClearSourceFactoryOverrides(); + } + + TestSourceFactory TrackingFactory; + std::shared_ptr Tracking; +}; + +bool SearchRequestIncludes(const std::vector& filters, PackageMatchField field, MatchType type, std::optional value = {}) +{ + bool found = false; + + for (const PackageMatchFilter& filter : filters) + { + if (filter.Field == field && filter.Type == type && + (!value || filter.Value == value.value())) + { + found = true; + } + } + + return found; +} + +void RequireSearchRequestIncludes(const std::vector& filters, PackageMatchField field, MatchType type, std::optional value = {}) +{ + REQUIRE(SearchRequestIncludes(filters, field, type, value)); +} + +TEST_CASE("CompositeSource_PackageFamilyName_NotAvailable", "[CompositeSource]") +{ + // Pre-folded for easier == + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); +} + +TEST_CASE("CompositeSource_PackageFamilyName_Available", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); +} + +TEST_CASE("CompositeSource_ProductCode_NotAvailable", "[CompositeSource]") +{ + std::string pc = "thiscouldbeapc"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPC(pc), Criteria()); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); +} + +TEST_CASE("CompositeSource_ProductCode_Available", "[CompositeSource]") +{ + std::string pc = "thiscouldbeapc"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPC(pc), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, pc); + + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithPC(pc), Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); +} + +TEST_CASE("CompositeSource_NameAndPublisher_Match", "[CompositeSource]") +{ + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled(), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact); + + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable(), Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); +} + +TEST_CASE("CompositeSource_MultiMatch_FindsStrongMatch", "[CompositeSource]") +{ + std::string name = "MatchingName"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); + result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(name), Criteria(PackageMatchField::PackageFamilyName)); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); + auto version = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); + REQUIRE(version->GetProperty(PackageVersionProperty::Name).get() == name); + REQUIRE(!Version(version->GetProperty(PackageVersionProperty::Version)).IsUnknown()); +} + +TEST_CASE("CompositeSource_MultiMatch_DoesNotFindStrongMatch", "[CompositeSource]") +{ + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithId("A different ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); + result.Matches.emplace_back(setup.MakeAvailable().WithId("Another diff ID"), Criteria(PackageMatchField::NormalizedNameAndPublisher)); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); +} + +TEST_CASE("CompositeSource_FoundByBothRootSearches", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn); + + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria()); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); +} + +TEST_CASE("CompositeSource_OnlyAvailableFoundByRootSearch", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria(PackageMatchField::PackageFamilyName)); + return result; + }; + + std::shared_ptr availablePackage = setup.MakeAvailable().WithPFN(pfn).ToPackage(); + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria(PackageMatchField::PackageFamilyName)); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); +} + +TEST_CASE("CompositeSource_FoundByAvailableRootSearch_NotInstalled", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.empty()); +} + +TEST_CASE("CompositeSource_UpdateWithBetterMatchCriteria", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + MatchType originalType = MatchType::Wildcard; + MatchType type = MatchType::Exact; + + CompositeTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn); + + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); + REQUIRE(result.Matches[0].MatchCriteria.Type == originalType); + + // Now make the source root search find it with a better criteria + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria()); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(availablePackage, PackageMatchFilter(PackageMatchField::Id, type, ""sv)); + + result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); + REQUIRE(result.Matches[0].MatchCriteria.Type == type); +} + +TEST_CASE("CompositePackage_PropertyFromInstalled", "[CompositeSource]") +{ + std::string id = "Special test ID"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(id), Criteria()); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package->GetProperty(PackageProperty::Id) == id); +} + +TEST_CASE("CompositePackage_PropertyFromAvailable", "[CompositeSource]") +{ + std::string id = "Special test ID"; + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithId(id), Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package->GetProperty(PackageProperty::Id) == id); +} + +TEST_CASE("CompositePackage_AvailableVersions_ChannelFilteredOut", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + std::string channel = "Channel"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest&) + { + Manifest::Manifest noChannel = MakeDefaultManifest(); + noChannel.Version = "1.0"; + + Manifest::Manifest hasChannel = MakeDefaultManifest(); + hasChannel.Channel = channel; + hasChannel.Version = "2.0"; + + SearchResult result; + result.Matches.emplace_back(TestCompositePackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2); + return result; + }; + + // Disable data checks as we call one of the data methods as validation in the search + SearchResult result = setup.Search(true); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + auto package = result.Matches[0].Package->GetAvailable()[0]; + + auto versionKeys = package->GetVersionKeys(); + REQUIRE(versionKeys.size() == 2); + + auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package); + auto availableVersionKeys = availableVersions->GetVersionKeys(); + REQUIRE(availableVersionKeys.size() == 1); + REQUIRE(availableVersionKeys[0].Channel.empty()); + + auto latestVersion = availableVersions->GetLatestVersion(); + REQUIRE(latestVersion); + REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get().empty()); +} + +TEST_CASE("CompositePackage_AvailableVersions_NoChannelFilteredOut", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + std::string channel = "Channel"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn).WithChannel(channel), Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest&) + { + Manifest::Manifest noChannel = MakeDefaultManifest(); + noChannel.Version = "1.0"; + + Manifest::Manifest hasChannel = MakeDefaultManifest(); + hasChannel.Channel = channel; + hasChannel.Version = "2.0"; + + SearchResult result; + result.Matches.emplace_back(TestCompositePackage::Make(std::vector{ noChannel, hasChannel }, setup.Available), Criteria()); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 2); + return result; + }; + + // Disable data checks as we call one of the data methods as validation in the search + SearchResult result = setup.Search(true); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + auto package = result.Matches[0].Package->GetAvailable()[0]; + + auto versionKeys = package->GetVersionKeys(); + REQUIRE(versionKeys.size() == 2); + + auto availableVersions = GetAvailableVersionsForInstalledVersion(result.Matches[0].Package); + auto availableVersionKeys = availableVersions->GetVersionKeys(); + REQUIRE(availableVersionKeys.size() == 1); + REQUIRE(availableVersionKeys[0].Channel == channel); + + auto latestVersion = availableVersions->GetLatestVersion(); + REQUIRE(latestVersion); + REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Channel).get() == channel); +} + +TEST_CASE("CompositeSource_MultipleAvailableSources_MatchAll", "[CompositeSource]") +{ + TestCommon::TestUserSettings testSettings; + + std::string pfn = "sortof_apfn"; + std::string firstName = "Name1"; + std::string secondName = "Name2"; + + CompositeTestSetup setup; + std::shared_ptr secondAvailable = std::make_shared(); + setup.Composite.AddAvailableSource(Source{ secondAvailable }); + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(firstName), Criteria()); + return result; + }; + + secondAvailable->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable(secondAvailable).WithDefaultName(secondName), Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 2); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == firstName); + REQUIRE(result.Matches[0].Package->GetAvailable()[1]->GetProperty(PackageProperty::Name).get() == secondName); +} + +TEST_CASE("CompositeSource_MultipleAvailableSources_MatchSecond", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + std::string firstName = "Name1"; + std::string secondName = "Name2"; + + CompositeTestSetup setup; + std::shared_ptr secondAvailable = std::make_shared(); + setup.Composite.AddAvailableSource(Source{ secondAvailable }); + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); + + secondAvailable->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithDefaultName(secondName), Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetProperty(PackageProperty::Name).get() == secondName); +} + +TEST_CASE("CompositeSource_MultipleAvailableSources_ReverseMatchBoth", "[CompositeSource]") +{ + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + + std::shared_ptr secondAvailable = std::make_shared(); + setup.Composite.AddAvailableSource(Source{ secondAvailable }); + + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria(PackageMatchField::PackageFamilyName)); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); + secondAvailable->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 2); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetVersionKeys().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[1]->GetVersionKeys().size() == 1); +} + +TEST_CASE("CompositeSource_IsSame", "[CompositeSource]") +{ + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN("sortof_apfn"), Criteria()); + + SearchResult result1 = setup.Search(); + REQUIRE(result1.Matches.size() == 1); + + SearchResult result2 = setup.Search(); + REQUIRE(result2.Matches.size() == 1); + + REQUIRE(result1.Matches[0].Package->GetInstalled()); + REQUIRE(result1.Matches[0].Package->GetInstalled()->IsSame(result1.Matches[0].Package->GetInstalled().get())); +} + +TEST_CASE("CompositeSource_AvailableSearchFailure", "[CompositeSource]") +{ + HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_FOUND; + std::string pfn = "sortof_apfn"; + + std::shared_ptr AvailableSucceeds = std::make_shared(); + AvailableSucceeds->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(TestPackageHelper{ /* isInstalled */ false }.WithPFN(pfn), Criteria()); + return result; + }; + + std::shared_ptr AvailableFails = std::make_shared(); + AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); }; + AvailableFails->Details.Name = "The one that fails"; + + CompositeSource Composite("*CompositeSource_AvailableSearchFailure"); + Composite.AddAvailableSource(Source{ AvailableSucceeds }); + Composite.AddAvailableSource(Source{ AvailableFails }); + + SearchResult result = Composite.Search({}); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + + auto pfns = result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetMultiProperty(PackageVersionMultiProperty::PackageFamilyName); + REQUIRE(pfns.size() == 1); + REQUIRE(pfns[0] == pfn); + + REQUIRE(result.Failures.size() == 1); + REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); + + HRESULT searchFailure = S_OK; + try + { + std::rethrow_exception(result.Failures[0].Exception); + } + catch (const wil::ResultException& re) + { + searchFailure = re.GetErrorCode(); + } + catch (...) {} + + REQUIRE(searchFailure == expectedHR); +} + +TEST_CASE("CompositeSource_InstalledToAvailableCorrelationSearchFailure", "[CompositeSource]") +{ + HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_LONG; + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithPFN(pfn), Criteria()); + setup.Available->Everything.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); + + std::shared_ptr AvailableFails = std::make_shared(); + AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); }; + AvailableFails->Details.Name = "The one that fails"; + + setup.Composite.AddAvailableSource(Source{ AvailableFails }); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + + REQUIRE(result.Failures.size() == 1); + REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); + + HRESULT searchFailure = S_OK; + try + { + std::rethrow_exception(result.Failures[0].Exception); + } + catch (const wil::ResultException& re) + { + searchFailure = re.GetErrorCode(); + } + catch (...) {} + + REQUIRE(searchFailure == expectedHR); +} + +TEST_CASE("CompositeSource_InstalledAvailableSearchFailure", "[CompositeSource]") +{ + HRESULT expectedHR = E_BLUETOOTH_ATT_ATTRIBUTE_NOT_LONG; + std::string pfn = "sortof_apfn"; + + CompositeTestSetup setup; + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithPFN(pfn), Criteria()); + return result; + }; + + std::shared_ptr AvailableFails = std::make_shared(); + AvailableFails->SearchFunction = [&](const SearchRequest&) -> SearchResult { THROW_HR(expectedHR); }; + AvailableFails->Details.Name = "The one that fails"; + + setup.Composite.AddAvailableSource(Source{ AvailableFails }); + + setup.Composite.SetInstalledSource(Source{ setup.Installed }, CompositeSearchBehavior::AvailablePackages); + + SearchRequest request; + request.Query = RequestMatch{ MatchType::Exact, "whatever" }; + SearchResult result = setup.Composite.Search(request); + + REQUIRE(result.Matches.size() == 1); + + REQUIRE(result.Failures.size() == 1); + REQUIRE(result.Failures[0].SourceName == AvailableFails->Details.Name); + + HRESULT searchFailure = S_OK; + try + { + std::rethrow_exception(result.Failures[0].Exception); + } + catch (const wil::ResultException& re) + { + searchFailure = re.GetErrorCode(); + } + catch (...) {} + + REQUIRE(searchFailure == expectedHR); +} + +TEST_CASE("CompositeSource_TrackingPackageFound", "[CompositeSource]") +{ + std::string availableID = "Available.ID"; + std::string pfn = "sortof_apfn"; + + CompositeWithTrackingTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria()); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + if (request.Filters.empty()) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + } + else + { + REQUIRE(request.Filters.size() == 1); + RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); + } + + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + setup.Tracking->GetIndex().AddManifest(availablePackage); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()); +} + +TEST_CASE("CompositeSource_TrackingPackageFound_MetadataPopulatedFromTracking", "[CompositeSource]") +{ + std::string availableID = "Available.ID"; + std::string pfn = "sortof_apfn"; + + CompositeWithTrackingTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria()); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + if (request.Filters.empty()) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + } + else + { + REQUIRE(request.Filters.size() == 1); + RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); + } + + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + auto manifestId = setup.Tracking->GetIndex().AddManifest(availablePackage); + + // Add test PackageVersionMetadata to be populated to InstalledVersion metadata + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::TrackingWriteTime, "100"); + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::UserIntentArchitecture, "X86"); + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::UserIntentLocale, "en-US"); + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InstalledArchitecture, "X86"); + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InstalledLocale, "en-US"); + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::PinnedState, "PinnedByManifest"); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + + auto metadata = GetInstalledVersion(result.Matches[0].Package)->GetMetadata(); + REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentArchitecture] == "X86"); + REQUIRE(metadata[Repository::PackageVersionMetadata::UserIntentLocale] == "en-US"); + REQUIRE(metadata[Repository::PackageVersionMetadata::InstalledArchitecture] == "X86"); + REQUIRE(metadata[Repository::PackageVersionMetadata::InstalledLocale] == "en-US"); + REQUIRE(metadata[Repository::PackageVersionMetadata::PinnedState] == "PinnedByManifest"); +} + +TEST_CASE("CompositeSource_TrackingPackageFound_UserInstallerArgsPopulatedFromTracking", "[CompositeSource]") +{ + std::string availableID = "Available.ID"; + std::string pfn = "sortof_apfn"; + + CompositeWithTrackingTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria()); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + if (request.Filters.empty()) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + } + else + { + REQUIRE(request.Filters.size() == 1); + RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); + } + + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + auto manifestId = setup.Tracking->GetIndex().AddManifest(availablePackage); + + // InitialOverrideArguments and InitialCustomSwitches are only stored in the tracking catalog, + // so they must be merged from there into the composite installed version's metadata. + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InitialOverrideArguments, "/silent /norestart"); + setup.Tracking->GetIndex().SetMetadataByManifestId(manifestId, Repository::PackageVersionMetadata::InitialCustomSwitches, "--no-telemetry"); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + + auto metadata = GetInstalledVersion(result.Matches[0].Package)->GetMetadata(); + REQUIRE(metadata[Repository::PackageVersionMetadata::InitialOverrideArguments] == "/silent /norestart"); + REQUIRE(metadata[Repository::PackageVersionMetadata::InitialCustomSwitches] == "--no-telemetry"); +} + +TEST_CASE("CompositeSource_TrackingFound_AvailableNot", "[CompositeSource]") +{ + std::string availableID = "Available.ID"; + std::string pfn = "sortof_apfn"; + + CompositeWithTrackingTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria()); + return result; + }; + + setup.Tracking->GetIndex().AddManifest(availablePackage); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(result.Matches[0].Package->GetAvailable().empty()); +} + +TEST_CASE("CompositeSource_TrackingFound_AvailablePath", "[CompositeSource]") +{ + CompositeWithTrackingTestSetup setup; + + std::string availableID = "Available.ID"; + std::string pfn = "sortof_apfn"; + + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + RequireSearchRequestIncludes(request.Inclusions, PackageMatchField::PackageFamilyName, MatchType::Exact, pfn); + + SearchResult result; + result.Matches.emplace_back(installedPackage, Criteria(PackageMatchField::PackageFamilyName)); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + REQUIRE(request.Filters.size() == 1); + RequireSearchRequestIncludes(request.Filters, PackageMatchField::Id, MatchType::CaseInsensitive, availableID); + + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + setup.Tracking->GetIndex().AddManifest(availablePackage); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Package); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)); + REQUIRE(result.Matches[0].Package->GetInstalled()->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(GetInstalledVersion(result.Matches[0].Package)->GetSource().GetIdentifier() == setup.Available->Details.Identifier); + REQUIRE(result.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(result.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()); +} + +TEST_CASE("CompositeSource_TrackingFound_NotInstalled", "[CompositeSource]") +{ + std::string availableID = "Available.ID"; + std::string pfn = "sortof_apfn"; + + CompositeWithTrackingTestSetup setup; + auto installedPackage = setup.MakeInstalled().WithPFN(pfn); + auto availablePackage = setup.MakeAvailable().WithPFN(pfn).WithId(availableID).WithDefaultName(s_Everything_Query); + + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + + setup.Tracking->GetIndex().AddManifest(availablePackage); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.empty()); +} + +TEST_CASE("CompositeSource_NullInstalledVersion", "[CompositeSource]") +{ + CompositeTestSetup setup; + setup.Installed->Everything.Matches.emplace_back(setup.MakeAvailable(), Criteria()); + + // We are mostly testing to see if a null installed version causes an AV or not + SearchResult result = setup.Search(); + REQUIRE(result.Matches.size() == 0); +} + +TEST_CASE("CompositeSource_NullAvailableVersion", "[CompositeSource]") +{ + CompositeTestSetup setup{ CompositeSearchBehavior::AvailablePackages }; + setup.Available->Everything.Matches.emplace_back(setup.MakeInstalled(), Criteria()); + + // We are mostly testing to see if a null available version causes an AV or not + REQUIRE_THROWS_HR(setup.Search(), E_UNEXPECTED); +} + +struct ExpectedResultForPinBehavior +{ + ExpectedResultForPinBehavior(bool isUpdateAvailable, std::optional latestAvailableVersion) + : IsUpdateAvailable(isUpdateAvailable), LatestAvailableVersion(latestAvailableVersion) {} + ExpectedResultForPinBehavior() {} + + bool IsUpdateAvailable = false; + std::optional LatestAvailableVersion; +}; + +struct ExpectedPackageVersionKey : public PackageVersionKey +{ + ExpectedPackageVersionKey(Utility::NormalizedString sourceId, Utility::NormalizedString version, Utility::NormalizedString channel, PinType pinType) : + PackageVersionKey(sourceId, version, channel), PinnedState(pinType) {} + + PinType PinnedState; +}; + +struct ExpectedResultsForPinning +{ + std::map ResultsForPinBehavior; + std::vector AvailableVersions; +}; + +void RequireExpectedResultsWithPin(std::shared_ptr package, const ExpectedResultsForPinning& expectedResult, std::shared_ptr packageVersion = {}) +{ + PinningData pinningData{ PinningData::Disposition::ReadOnly }; + auto availableVersions = GetAvailableVersionsForInstalledVersion(package); + + if (!packageVersion) + { + packageVersion = GetInstalledVersion(package); + } + + for (const auto& entry : expectedResult.ResultsForPinBehavior) + { + auto pinBehavior = entry.first; + const auto& result = entry.second; + + auto evaluator = pinningData.CreatePinStateEvaluator(pinBehavior, packageVersion); + auto latestAvailable = evaluator.GetLatestAvailableVersionForPins(availableVersions); + + REQUIRE(evaluator.IsUpdate(latestAvailable) == result.IsUpdateAvailable); + + if (result.LatestAvailableVersion.has_value()) + { + REQUIRE(latestAvailable); + REQUIRE(latestAvailable->GetManifest().Version == result.LatestAvailableVersion.value()); + } + else + { + REQUIRE(!latestAvailable); + } + } + + auto availableVersionKeys = availableVersions->GetVersionKeys(); + REQUIRE(availableVersionKeys.size() == expectedResult.AvailableVersions.size()); + for (size_t i = 0; i < availableVersionKeys.size(); ++i) + { + auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, packageVersion); + + auto availableVersion = availableVersions->GetVersion(expectedResult.AvailableVersions[i]); + REQUIRE(availableVersion); + REQUIRE(availableVersionKeys[i].SourceId == expectedResult.AvailableVersions[i].SourceId); + REQUIRE(availableVersionKeys[i].Version == expectedResult.AvailableVersions[i].Version); + REQUIRE(evaluator.EvaluatePinType(availableVersion) == expectedResult.AvailableVersions[i].PinnedState); + } +} + +TEST_CASE("CompositeSource_Pinning_AvailableVersionPinned", "[CompositeSource][PinFlow]") +{ + // We use an installed package that has 3 available versions: v1.0.0, v1.0.1 and v1.1.0. + // Installed is v1.0.1 + // We then test the 4 possible pin states (unpinned, Pinned, Blocked, Gated) + // with the 3 possible pin search behaviors (ignore, consider, include pinned) + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + TestUserSettings userSettings; + CompositeTestSetup setup; + + auto installedPackage = setup.MakeInstalled().WithVersion("1.0.1"sv); + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto manifest1 = MakeDefaultManifest("1.0.0"sv); + auto manifest2 = MakeDefaultManifest("1.0.1"sv); + auto manifest3 = MakeDefaultManifest("1.1.0"sv); + auto package = TestCompositePackage::Make( + std::vector{ manifest3, manifest2, manifest1 }, + setup.Available); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + ExpectedResultsForPinning expectedResult; + // The result when ignoring pins is always the same + expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" }; + + PinKey pinKey("Id", setup.Available->Details.Identifier); + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + REQUIRE(pinningIndex); + + SECTION("Unpinned") + { + // If there are no pins, the result should not change if we consider them + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, + }; + } + SECTION("Pinned") + { + pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); + + // Pinning pins are ignored with --include-pinned + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins]; + + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Pinning }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Pinning }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Pinning }, + }; + } + SECTION("Blocked") + { + pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; + + // Blocking pins are not affected by --include-pinned + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; + + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Blocking }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Blocking }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Blocking }, + }; + } + SECTION("Gated to 1.*") + { + pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1.0" }; + + // Gating pins are not affected by --include-pinned + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; + + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, + }; + } + SECTION("Gated to 1.0.*") + { + pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.0.*"sv })); + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ "1.0.1" }; + + // Gating pins are not affected by --include-pinned + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins]; + + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "1.1.0", "", Pinning::PinType::Gating }, + { "AvailableTestSource1", "1.0.1", "", Pinning::PinType::Unknown }, + { "AvailableTestSource1", "1.0.0", "", Pinning::PinType::Unknown }, + }; + } + + SearchResult result = setup.Search(); + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + + RequireExpectedResultsWithPin(package, expectedResult); +} + +TEST_CASE("CompositeSource_Pinning_OneSourcePinned", "[CompositeSource][PinFlow]") +{ + // We use an installed package that has 2 available sources. + // If one of them is pinned, we should still get the updates from the other one. + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + TestUserSettings userSettings; + CompositeTestSetup setup; + + auto installedPackage = setup.MakeInstalled().WithVersion("1.0"sv); + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto package = TestCompositePackage::Make(std::vector{ MakeDefaultManifest("2.0"sv) }, setup.Available); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + std::shared_ptr secondAvailable = std::make_shared("SecondTestSource"); + setup.Composite.AddAvailableSource(Source{ secondAvailable }); + secondAvailable->SearchFunction = [&](const SearchRequest&) + { + auto package = TestCompositePackage::Make(std::vector{ MakeDefaultManifest("1.1"sv) }, secondAvailable); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + { + PinKey pinKey("Id", setup.Available->Details.Identifier); + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + REQUIRE(pinningIndex); + pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); + } + + ExpectedResultsForPinning expectedResult; + expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.1" }; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Pinning }, + { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown }, + }; + + SearchResult result = setup.Search(); + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + RequireExpectedResultsWithPin(package, expectedResult); +} + +TEST_CASE("CompositeSource_Pinning_OneSourceGated", "[CompositeSource][PinFlow]") +{ + // We use an installed package that has 2 available sources. + // If one of them has a gating pin, we should still get the updates from it + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + TestUserSettings userSettings; + CompositeTestSetup setup; + + auto installedPackage = setup.MakeInstalled().WithVersion("1.0.1"sv); + setup.Installed->Everything.Matches.emplace_back(installedPackage, Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto package = TestCompositePackage::Make( + std::vector{ + MakeDefaultManifest("2.0"sv), + MakeDefaultManifest("1.2"sv), + }, + setup.Available); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + std::shared_ptr secondAvailable = std::make_shared("SecondTestSource"); + setup.Composite.AddAvailableSource(Source{ secondAvailable }); + secondAvailable->SearchFunction = [&](const SearchRequest&) + { + auto package = TestCompositePackage::Make(std::vector{ MakeDefaultManifest("1.1"sv) }, secondAvailable); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + { + PinKey pinKey("Id", setup.Available->Details.Identifier); + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + REQUIRE(pinningIndex); + pinningIndex->AddPin(Pin::CreateGatingPin(PinKey{ pinKey }, GatedVersion{ "1.*"sv })); + } + + ExpectedResultsForPinning expectedResult; + expectedResult.ResultsForPinBehavior[PinBehavior::IgnorePins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + expectedResult.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; + expectedResult.ResultsForPinBehavior[PinBehavior::IncludePinned] = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "1.2" }; + expectedResult.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Gating }, + { "AvailableTestSource1", "1.2", "", Pinning::PinType::Unknown }, + { "SecondTestSource", "1.1", "", Pinning::PinType::Unknown }, + }; + + SearchResult result = setup.Search(); + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + RequireExpectedResultsWithPin(package, expectedResult); +} + +TEST_CASE("CompositeSource_Pinning_MultipleInstalled", "[CompositeSource][PinFlow]") +{ + // Tests the case where multiple installed packages match to a single available package. + // If one of the two installed packages is pinned, when searching we should get + // two Composite packages, with only one of them pinned. + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + TestUserSettings userSettings; + + std::string packageId = "packageId"; + std::string productCode1 = "product-code1"; + std::string productCode2 = "product-code2"; + + CompositeTestSetup setup; + + // Installed packages differ in product code and version + auto installedPackage1 = setup.MakeInstalled().WithId(productCode1).WithPC(productCode1).WithVersion("1.1"sv); + auto installedPackage2 = setup.MakeInstalled().WithId(productCode2).WithPC(productCode2).WithVersion("1.2"sv); + + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + bool isSearchById = SearchRequestIncludes(request.Inclusions, PackageMatchField::Id, MatchType::Exact, packageId); + + SearchResult result; + if (isSearchById || SearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, productCode1)) + { + result.Matches.emplace_back(installedPackage1, Criteria(request.Inclusions[0].Field)); + } + + if (isSearchById || SearchRequestIncludes(request.Inclusions, PackageMatchField::ProductCode, MatchType::Exact, productCode2)) + { + result.Matches.emplace_back(installedPackage2, Criteria(request.Inclusions[0].Field)); + } + + return result; + }; + + // Available package has the same ID, no product code, and different version from both the installed packages; + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithId(packageId).WithVersion("2.0"sv), Criteria()); + return result; + }; + + // We will pin the first package only + PinKey pinKey = PinKey::GetPinKeyForInstalled(productCode1); + auto pinningIndex = PinningIndex::OpenOrCreateDefault(); + REQUIRE(pinningIndex); + + // We will check the pinning status for both installed packages + ExpectedResultsForPinning expectedResult1; + ExpectedResultsForPinning expectedResult2; + + expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins] + = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + + // The second package is never pinned, so its result is always the same + expectedResult2.ResultsForPinBehavior[PinBehavior::IgnorePins] + = expectedResult2.ResultsForPinBehavior[PinBehavior::ConsiderPins] + = expectedResult2.ResultsForPinBehavior[PinBehavior::IncludePinned] + = { /* IsUpdateAvailable */ true, /* LatestAvailableVersion */ "2.0" }; + expectedResult2.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Unknown }, + }; + + SECTION("Unpinned") + { + // If there are no pins, the result should not change if we consider them + expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] + = expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] + = expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins]; + expectedResult1.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Unknown }, + }; + } + SECTION("Pinned") + { + pinningIndex->AddPin(Pin::CreatePinningPin(PinKey{ pinKey })); + + // Pinning pins are ignored with --include-pinned + expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult1.ResultsForPinBehavior[PinBehavior::IgnorePins]; + + expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; + expectedResult1.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Pinning }, + }; + } + SECTION("Blocked") + { + pinningIndex->AddPin(Pin::CreateBlockingPin(PinKey{ pinKey })); + expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins] = { /* IsUpdateAvailable */ false, /* LatestAvailableVersion */ {} }; + + // Blocking pins are not affected by --include-pinned + expectedResult1.ResultsForPinBehavior[PinBehavior::IncludePinned] = expectedResult1.ResultsForPinBehavior[PinBehavior::ConsiderPins]; + + expectedResult1.AvailableVersions = { + { "AvailableTestSource1", "2.0", "", Pinning::PinType::Blocking }, + }; + } + + SearchRequest searchRequest; + searchRequest.Inclusions.emplace_back(PackageMatchField::Id, MatchType::Exact, packageId); + SearchResult result = setup.Composite.Search(searchRequest); + + REQUIRE(result.Matches.size() == 1); + auto installedPackage = result.Matches[0].Package->GetInstalled(); + REQUIRE(installedPackage); + auto installedVersions = installedPackage->GetVersionKeys(); + REQUIRE(installedVersions.size() == 2); + + // Here we assume that the order we return the packages in the installed source + // search is preserved. We'll need to change it if that stops being the case. + auto packageVersion1 = installedPackage->GetVersion(installedVersions[1]); + REQUIRE(packageVersion1); + + auto packageVersion2 = installedPackage->GetVersion(installedVersions[0]); + REQUIRE(packageVersion2); + + RequireExpectedResultsWithPin(result.Matches[0].Package, expectedResult1, packageVersion1); + RequireExpectedResultsWithPin(result.Matches[0].Package, expectedResult2, packageVersion2); +} + +TEST_CASE("CompositeSource_CorrelateToInstalledContainsManifestData", "[CompositeSource]") +{ + CompositeTestSetup setup; + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + if (request.Purpose == SearchPurpose::CorrelationToInstalled) + { + bool expectedSearchFound = false; + for (const auto& inclusion : request.Inclusions) + { + if (inclusion.Field == PackageMatchField::ProductCode && inclusion.Value == "hello") + { + expectedSearchFound = true; + break; + } + } + + REQUIRE(expectedSearchFound); + } + + SearchResult result; + return result; + }; + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithPC("hello"), Criteria()); + return result; + }; + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, "NotForEverything"); + SearchResult result = setup.Composite.Search(request); +} + +TEST_CASE("CompositeSource_Respects_FeatureFlag_ManifestMayContainAdditionalSystemReferenceStrings", "[CompositeSource]") +{ + std::string id = "Special test ID"; + std::string productCode1 = "product-code1"; + + CompositeTestSetup setup; + bool productCodeSearched = false; + setup.Installed->SearchFunction = [&](const SearchRequest& request) + { + for (const auto& inclusion : request.Inclusions) + { + if (inclusion.Field == PackageMatchField::ProductCode) + { + productCodeSearched = true; + } + } + + return SearchResult{}; + }; + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeAvailable().WithId(id).WithPC(productCode1).HideSRS(), Criteria()); + return result; + }; + + SECTION("Feature false") + { + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, "NotForEverything"); + SearchResult result = setup.Composite.Search(request); + + REQUIRE(!productCodeSearched); + } + SECTION("Feature true") + { + setup.Available->QueryFeatureFlagFunction = [](SourceFeatureFlag flag) + { + return (flag == SourceFeatureFlag::ManifestMayContainAdditionalSystemReferenceStrings); + }; + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, "NotForEverything"); + SearchResult result = setup.Composite.Search(request); + + REQUIRE(productCodeSearched); + } +} + +TEST_CASE("CompositeSource_SxS_TwoVersions_NoAvailable", "[CompositeSource][SideBySide]") +{ + std::string productCode1 = "PC1"; + std::string productCode2 = "PC2"; + + CompositeTestSetup setup; + auto availablePackage = setup.MakeAvailable(); + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("1.0").WithPC(productCode1), Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("2.0").WithPC(productCode2), Criteria()); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 2); +} + +TEST_CASE("CompositeSource_SxS_TwoVersions_DifferentAvailable", "[CompositeSource][SideBySide]") +{ + std::string productCode1 = "PC1"; + std::string productCode2 = "PC2"; + + CompositeTestSetup setup; + auto availablePackage1 = setup.MakeAvailable().ToPackage(); + auto availablePackage2 = setup.MakeAvailable().ToPackage(); + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("1.0").WithPC(productCode1), Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion("2.0").WithPC(productCode2), Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest& request) + { + SearchResult result; + + std::string productCode; + for (const auto& item : request.Inclusions) + { + if (item.Field == PackageMatchField::ProductCode) + { + productCode = item.Value; + break; + } + } + + if (productCode == productCode1) + { + result.Matches.emplace_back(availablePackage1, Criteria()); + } + else if (productCode == productCode1) + { + result.Matches.emplace_back(availablePackage2, Criteria()); + } + + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 2); +} + +TEST_CASE("CompositeSource_SxS_TwoVersions_SameAvailable", "[CompositeSource][SideBySide]") +{ + std::string version1 = "1.0"; + std::string version2 = "2.0"; + std::string productCode1 = "PC1"; + std::string productCode2 = "PC2"; + + CompositeTestSetup setup; + auto availablePackage = setup.MakeAvailable().ToPackage(); + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + auto installedPackage = package->GetInstalled(); + REQUIRE(installedPackage); + auto installedVersions = installedPackage->GetVersionKeys(); + REQUIRE(installedVersions.size() == 2); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); + auto availablePackages = package->GetAvailable(); + REQUIRE(availablePackages.size() == 1); + REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); +} + +TEST_CASE("CompositeSource_SxS_ThreeVersions_SameAvailable", "[CompositeSource][SideBySide]") +{ + std::string version1 = "1.0"; + std::string version2 = "2.0"; + std::string version3 = "3.0"; + std::string productCode1 = "PC1"; + std::string productCode2 = "PC2"; + std::string productCode3 = "PC3"; + + CompositeTestSetup setup; + auto availablePackage = setup.MakeAvailable().ToPackage(); + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version3).WithPC(productCode3), Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + auto installedPackage = package->GetInstalled(); + REQUIRE(installedPackage); + auto installedVersions = installedPackage->GetVersionKeys(); + REQUIRE(installedVersions.size() == 3); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version3; })); + auto availablePackages = package->GetAvailable(); + REQUIRE(availablePackages.size() == 1); + REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); +} + +TEST_CASE("CompositeSource_SxS_TwoVersions_SameAvailable_Tracking", "[CompositeSource][SideBySide]") +{ + std::string version1 = "1.0"; + std::string version2 = "2.0"; + std::string productCode1 = "PC1"; + std::string productCode2 = "PC2"; + + CompositeWithTrackingTestSetup setup; + auto installedPackage1 = setup.MakeInstalled().WithVersion(version1).WithPC(productCode1); + auto availablePackage = setup.MakeAvailable().ToPackage(); + + setup.Installed->Everything.Matches.emplace_back(installedPackage1, Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); + setup.Tracking->GetIndex().AddManifest(installedPackage1); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(availablePackage, Criteria()); + return result; + }; + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + auto installedPackage = package->GetInstalled(); + REQUIRE(installedPackage); + auto installedVersions = installedPackage->GetVersionKeys(); + REQUIRE(installedVersions.size() == 2); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); + auto availablePackages = package->GetAvailable(); + REQUIRE(availablePackages.size() == 1); + REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); +} + +TEST_CASE("CompositeSource_SxS_Available_TwoVersions_SameAvailable", "[CompositeSource][SideBySide]") +{ + std::string version1 = "1.0"; + std::string version2 = "2.0"; + std::string productCode1 = "PC1"; + std::string productCode2 = "PC2"; + + CompositeTestSetup setup; + auto availablePackage = setup.MakeAvailable().ToPackage(); + + setup.Installed->SearchFunction = [&](const SearchRequest&) + { + SearchResult result; + result.Matches.emplace_back(setup.MakeInstalled().WithVersion(version1).WithPC(productCode1), Criteria()); + result.Matches.emplace_back(setup.MakeInstalled().WithVersion(version2).WithPC(productCode2), Criteria()); + return result; + }; + + setup.Available->Everything.Matches.emplace_back(availablePackage, Criteria()); + + SearchResult result = setup.Search(); + + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + auto installedPackage = package->GetInstalled(); + REQUIRE(installedPackage); + auto installedVersions = installedPackage->GetVersionKeys(); + REQUIRE(installedVersions.size() == 2); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version1; })); + REQUIRE(std::any_of(installedVersions.begin(), installedVersions.end(), [&](const PackageVersionKey& key) { return key.Version == version2; })); + auto availablePackages = package->GetAvailable(); + REQUIRE(availablePackages.size() == 1); + REQUIRE(availablePackages[0]->IsSame(availablePackage->Available[0].get())); +} + +TEST_CASE("CompositeSource_MappedVersions_ProperSorting", "[CompositeSource]") +{ + std::string installedID = "Installed.Id"; + std::string availableID = "Available.Id"; + auto type = Manifest::InstallerTypeEnum::Exe; + std::string pfn = "MY_PFN"; + std::string version1 = "1000.0"; + std::string version2 = "2000.0"; + std::string versionMapped1 = "1.0"; + std::string versionMapped2 = "2.0"; + + CompositeTestSetup setup; + + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(installedID).WithPFN(pfn).WithVersion(version1).WithMetadata(PackageVersionMetadata::InstalledType, "exe"), Criteria()); + setup.Installed->Everything.Matches.emplace_back(setup.MakeInstalled().WithId(installedID).WithPFN(pfn).WithVersion(version2).WithMetadata(PackageVersionMetadata::InstalledType, "exe"), Criteria()); + + setup.Available->SearchFunction = [&](const SearchRequest&) + { + auto package = setup.MakeAvailable(); + package.WithId(availableID).WithType(type).WithPFN(pfn).WithVersion(versionMapped1).WithDisplayVersion(version1); + package.MakeManifest().WithId(availableID).WithType(type).WithPFN(pfn).WithVersion(versionMapped2).WithDisplayVersion(version2); + + SearchResult result; + result.Matches.emplace_back(package, Criteria()); + return result; + }; + + SearchResult result = setup.Search(true); + + REQUIRE(result.Matches.size() == 1); + auto package = result.Matches[0].Package; + REQUIRE(package); + auto installedPackage = package->GetInstalled(); + REQUIRE(installedPackage); + auto installedVersions = installedPackage->GetVersionKeys(); + REQUIRE(installedVersions.size() == 2); + REQUIRE(installedVersions[0].Version == versionMapped2); + REQUIRE(installedVersions[1].Version == versionMapped1); +} diff --git a/src/AppInstallerCLITests/ContextOrchestrator.cpp b/src/AppInstallerCLITests/ContextOrchestrator.cpp index 8b0ba68346..4686c1210a 100644 --- a/src/AppInstallerCLITests/ContextOrchestrator.cpp +++ b/src/AppInstallerCLITests/ContextOrchestrator.cpp @@ -9,8 +9,8 @@ using namespace TestCommon; using namespace AppInstaller::CLI; using namespace AppInstaller::CLI::Execution; using namespace AppInstaller::Manifest; - -static constexpr DWORD c_DefaultWaitInMs = 5000; + +static constexpr DWORD c_DefaultWaitInMs = 5000; struct TestCOMContext : public COMContext { @@ -79,18 +79,18 @@ TestQueueItem CreateTestItem(std::optional packageName = std::nullo // Forcibly initialize the thread globals objects context->GetThreadGlobals().SetForCurrentThread(); - TestDataFile testManifest("Manifest-Good.yaml"); - auto manifest = YamlParser::CreateFromPath(testManifest); - - if (packageName) - { - manifest.Id = packageName.value(); - } + TestDataFile testManifest("Manifest-Good.yaml"); + auto manifest = YamlParser::CreateFromPath(testManifest); + + if (packageName) + { + manifest.Id = packageName.value(); + } context->Add(std::move(manifest)); result.Context = context.get(); - + // Marking it an uninstall removes the extra work adding the items to the installing index result.QueueItem = std::make_shared(OrchestratorQueueItemId(AppInstaller::Utility::ConvertToUTF16(packageName.value_or("package")), L"source"), std::move(context), PackageOperationType::Uninstall); @@ -121,8 +121,8 @@ TEST_CASE("ContextOrchestrator_Disabled_NewEnqueue", "[context_orchestrator]") { ContextOrchestrator orchestrator; auto testItem = CreateTestItem(); - - auto reason = AppInstaller::CancelReason::AppShutdown; + + auto reason = AppInstaller::CancelReason::AppShutdown; orchestrator.Disable(reason); REQUIRE_THROWS_HR(orchestrator.EnqueueAndRunItem(testItem.QueueItem), AppInstaller::ToHRESULT(reason)); } @@ -136,74 +136,74 @@ TEST_CASE("ContextOrchestrator_Disabled_QueueTransition", "[context_orchestrator wil::slim_event_manual_reset downloadEvent; testItem.Context->DownloadCallback = [&]() { downloadEvent.wait(); }; - orchestrator.EnqueueAndRunItem(testItem.QueueItem); - - auto reason = AppInstaller::CancelReason::AppShutdown; - orchestrator.Disable(reason); - + orchestrator.EnqueueAndRunItem(testItem.QueueItem); + + auto reason = AppInstaller::CancelReason::AppShutdown; + orchestrator.Disable(reason); + downloadEvent.SetEvent(); - REQUIRE(testItem.QueueItem->GetCompletedEvent().wait(c_DefaultWaitInMs)); - REQUIRE(testItem.Context->IsTerminated()); + REQUIRE(testItem.QueueItem->GetCompletedEvent().wait(c_DefaultWaitInMs)); + REQUIRE(testItem.Context->IsTerminated()); // Context translates our shutdown HRs to E_ABORT REQUIRE(E_ABORT == testItem.Context->GetTerminationHR()); } // While item in { Queued, Running } in both queues, cancel everything TEST_CASE("ContextOrchestrator_CancelAllItems", "[context_orchestrator]") -{ +{ // Limit to one thread for downloads so we can get a queued item - ContextOrchestrator orchestrator{ 1 }; + ContextOrchestrator orchestrator{ 1 }; auto downloadQueued = CreateTestItem("downloadQueued"); auto downloadRunning = CreateTestItem("downloadRunning"); wil::slim_event_manual_reset downloadBegunEvent; wil::slim_event_manual_reset downloadWaitingEvent; - downloadRunning.Context->DownloadCallback = [&]() - { - downloadBegunEvent.SetEvent(); - downloadWaitingEvent.wait(); - }; + downloadRunning.Context->DownloadCallback = [&]() + { + downloadBegunEvent.SetEvent(); + downloadWaitingEvent.wait(); + }; auto operationQueued = CreateTestItem("operationQueued"); auto operationRunning = CreateTestItem("operationRunning"); wil::slim_event_manual_reset operationBegunEvent; wil::slim_event_manual_reset operationWaitingEvent; - operationRunning.Context->OperationCallback = [&]() - { - operationBegunEvent.SetEvent(); - operationWaitingEvent.wait(); - }; + operationRunning.Context->OperationCallback = [&]() + { + operationBegunEvent.SetEvent(); + operationWaitingEvent.wait(); + }; orchestrator.EnqueueAndRunItem(operationRunning.QueueItem); orchestrator.EnqueueAndRunItem(operationQueued.QueueItem); orchestrator.EnqueueAndRunItem(downloadRunning.QueueItem); - orchestrator.EnqueueAndRunItem(downloadQueued.QueueItem); - - operationBegunEvent.wait(c_DefaultWaitInMs); - downloadBegunEvent.wait(c_DefaultWaitInMs); - - INFO("Pre-shutdown state: \n" << orchestrator.GetStatusString()); - - auto reason = AppInstaller::CancelReason::AppShutdown; - orchestrator.Disable(reason); - orchestrator.CancelQueuedItems(reason); - - operationWaitingEvent.SetEvent(); - downloadWaitingEvent.SetEvent(); - - if (!orchestrator.WaitForRunningItems(c_DefaultWaitInMs)) - { - INFO("Post-wait state: \n" << orchestrator.GetStatusString()); - FAIL("Timed out waiting for orchestrator to empty"); - } - - auto checkQueueItem = [](TestQueueItem& item) + orchestrator.EnqueueAndRunItem(downloadQueued.QueueItem); + + operationBegunEvent.wait(c_DefaultWaitInMs); + downloadBegunEvent.wait(c_DefaultWaitInMs); + + INFO("Pre-shutdown state: \n" << orchestrator.GetStatusString()); + + auto reason = AppInstaller::CancelReason::AppShutdown; + orchestrator.Disable(reason); + orchestrator.CancelQueuedItems(reason); + + operationWaitingEvent.SetEvent(); + downloadWaitingEvent.SetEvent(); + + if (!orchestrator.WaitForRunningItems(c_DefaultWaitInMs)) + { + INFO("Post-wait state: \n" << orchestrator.GetStatusString()); + FAIL("Timed out waiting for orchestrator to empty"); + } + + auto checkQueueItem = [](TestQueueItem& item) { - REQUIRE(item.QueueItem->GetCompletedEvent().wait(0)); + REQUIRE(item.QueueItem->GetCompletedEvent().wait(0)); REQUIRE(item.Context->IsTerminated()); - REQUIRE(E_ABORT == item.Context->GetTerminationHR()); - }; + REQUIRE(E_ABORT == item.Context->GetTerminationHR()); + }; checkQueueItem(downloadQueued); checkQueueItem(downloadRunning); diff --git a/src/AppInstallerCLITests/Correlation.cpp b/src/AppInstallerCLITests/Correlation.cpp index 1b364ad06d..87a2a359fc 100644 --- a/src/AppInstallerCLITests/Correlation.cpp +++ b/src/AppInstallerCLITests/Correlation.cpp @@ -1,306 +1,306 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSource.h" - -#include -#include -#include -#include - -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Correlation; -using namespace AppInstaller::Utility; - -using namespace TestCommon; - -// Data for defining a test case -struct TestCase -{ - // Actual app data - std::string AppName; - std::string AppPublisher; - - // Data in ARP - std::string ARPName; - std::string ARPPublisher; - - bool IsMatch; -}; - -// Definition of a collection of test cases that we evaluate -// together to get a single aggregate result -struct DataSet -{ - // Details about the apps we are trying to correlate - std::vector TestCases; - - // Additional ARP entries to use as "noise" for the correlation - std::vector ARPNoise; - - // Thresholds for considering a run of an heuristic against - // this data set "good". - // Values are ratios to the total number of test cases - double RequiredTrueMatchRatio; - double RequiredTrueMismatchRatio; - double RequiredFalseMatchRatio; - double RequiredFalseMismatchRatio; -}; - -// Aggregate result of running an heuristic against a data set. -struct ResultSummary -{ - size_t TrueMatches; - size_t TrueMismatches; - size_t FalseMatches; - size_t FalseMismatches; - std::chrono::milliseconds TotalTime; - - size_t TotalCases() const - { - return TrueMatches + TrueMismatches + FalseMatches + FalseMismatches; - } - - auto AverageMatchingTime() const - { - return TotalTime / TotalCases(); - } -}; - -Manifest GetManifestFromTestCase(const TestCase& testCase) -{ - Manifest manifest; - manifest.DefaultLocalization.Add(testCase.AppName); - manifest.DefaultLocalization.Add(testCase.AppPublisher); - manifest.Localizations.push_back(manifest.DefaultLocalization); - return manifest; -} - -ARPEntry GetARPEntryFromTestCase(const TestCase& testCase, bool isNew) -{ - Manifest arpManifest; - arpManifest.DefaultLocalization.Add(testCase.ARPName); - arpManifest.DefaultLocalization.Add(testCase.ARPPublisher); - arpManifest.Localizations.push_back(arpManifest.DefaultLocalization); - return ARPEntry{ TestPackage::Make(arpManifest, TestPackage::MetadataMap{}), isNew }; -} - -ARPEntry GetExistingARPEntryFromTestCase(const TestCase& testCase) -{ - return GetARPEntryFromTestCase(testCase, /* isNew */ false); -} - -void ReportMatch(std::string_view label, std::string_view appName, std::string_view appPublisher, std::string_view arpName, std::string_view arpPublisher) -{ - WARN(label << '\n' << - "\tApp name = " << appName << '\n' << - "\tApp publisher = " << appPublisher << '\n' << - "\tARP name = " << arpName << '\n' << - "\tARP publisher = " << arpPublisher); -} - -ResultSummary EvaluateDataSetWithHeuristic(const DataSet& dataSet, IARPMatchConfidenceAlgorithm& correlationAlgorithm, bool reportErrors = false) -{ - ResultSummary result{}; - auto startTime = std::chrono::steady_clock::now(); - - // Each entry under test will be pushed at the end of this - // and removed at the end. - auto arpEntries = dataSet.ARPNoise; - - for (const auto& testCase : dataSet.TestCases) - { - arpEntries.push_back(GetARPEntryFromTestCase(testCase, /* isNew */ true)); - ARPHeuristicsCorrelationResult correlationResult = FindARPEntryForNewlyInstalledPackageWithHeuristics(GetManifestFromTestCase(testCase), arpEntries, correlationAlgorithm); - auto match = correlationResult.Package; - arpEntries.pop_back(); - - if (match) - { - auto matchName = match->GetProperty(PackageVersionProperty::Name); - auto matchPublisher = match->GetProperty(PackageVersionProperty::Publisher); - - // The strings get normalized when added to the manifest, so we have - // to normalize for the comparison. - if (matchName == NormalizedString(testCase.ARPName) && matchPublisher == NormalizedString(testCase.ARPPublisher)) - { - ++result.TrueMatches; - } - else - { - ++result.FalseMatches; - - if (reportErrors) - { - ReportMatch("False match", testCase.AppName, testCase.AppPublisher, matchName, matchPublisher); - } - } - } - else - { - if (testCase.IsMatch) - { - ++result.FalseMismatches; - - if (reportErrors) - { - ReportMatch("False mismatch", testCase.AppName, testCase.AppPublisher, testCase.ARPName, testCase.ARPPublisher); - } - } - else - { - ++result.TrueMismatches; - } - } - } - - auto endTime = std::chrono::steady_clock::now(); - result.TotalTime = std::chrono::duration_cast(endTime - startTime); - - return result; -} - -void ReportResults(ResultSummary results) -{ - // This uses WARN to report as that is always shown regardless of the test result. - // We may want to re-consider reporting in some other way - WARN("Total cases: " << results.TotalCases() << '\n' << - "True matches: " << results.TrueMatches << '\n' << - "False matches: " << results.FalseMatches << '\n' << - "True mismatches: " << results.TrueMismatches << '\n' << - "False mismatches: " << results.FalseMismatches << '\n' << - "Total matching time: " << results.TotalTime.count() << "ms\n" << - "Average matching time: " << results.AverageMatchingTime().count() << "ms"); -} - -void ReportAndEvaluateResults(ResultSummary results, const DataSet& dataSet) -{ - ReportResults(results); - - // Required True ratio is a lower limit. The more results we get right, the better. - // Required False ratio is an upper limit. The fewer results we get wrong, the better. - REQUIRE(results.TrueMatches >= results.TotalCases() * dataSet.RequiredTrueMatchRatio); - REQUIRE(results.TrueMismatches >= results.TotalCases() * dataSet.RequiredTrueMismatchRatio); - REQUIRE(results.FalseMatches <= results.TotalCases() * dataSet.RequiredFalseMatchRatio); - REQUIRE(results.FalseMismatches <= results.TotalCases()* dataSet.RequiredFalseMismatchRatio); -} - -// TODO: Define multiple data sets -// - Data set with many apps. -// - Data set with popular apps. The match requirements should be higher -// - Data set(s) in other languages. -// - Data set where not everything has a match - -std::vector LoadTestData() -{ - // Creates test cases from the test data file. - // The format of the file is one case per line, each with pipe (|) separated values. - // Each row contains: AppId, AppName, AppPublisher, ARPDisplayName, ARPDisplayVersion, ARPPublisherName, ARPProductCode - // TODO: Add more test cases; particularly for non-matches - std::ifstream testDataStream(TestCommon::TestDataFile("InputARPData.txt").GetPath()); - REQUIRE(testDataStream); - - std::vector testCases; - - std::string line; - while (std::getline(testDataStream, line)) - { - std::stringstream ss{ line }; - - TestCase testCase; - std::string appId; - std::string arpDisplayVersion; - std::string arpProductCode; - std::getline(ss, appId, '|'); - std::getline(ss, testCase.AppName, '|'); - std::getline(ss, testCase.AppPublisher, '|'); - std::getline(ss, testCase.ARPName, '|'); - std::getline(ss, arpDisplayVersion, '|'); - std::getline(ss, testCase.ARPPublisher, '|'); - std::getline(ss, arpProductCode, '|'); - - testCase.IsMatch = true; - - testCases.push_back(std::move(testCase)); - } - - return testCases; -} - -DataSet GetDataSet_NoNoise() -{ - DataSet dataSet; - dataSet.TestCases = LoadTestData(); - - // Arbitrary values. We should refine them as the algorithm gets better. - dataSet.RequiredTrueMatchRatio = 0.81; - dataSet.RequiredFalseMatchRatio = 0; - dataSet.RequiredTrueMismatchRatio = 0; // There are no expected mismatches in this data set - dataSet.RequiredFalseMismatchRatio = 0.25; - - return dataSet; -} - -DataSet GetDataSet_WithNoise() -{ - DataSet dataSet; - auto baseTestCases = LoadTestData(); - - std::transform(baseTestCases.begin(), baseTestCases.end(), std::back_inserter(dataSet.ARPNoise), GetExistingARPEntryFromTestCase); - dataSet.TestCases = std::move(baseTestCases); - - // Arbitrary values. We should refine them as the algorithm gets better. - dataSet.RequiredTrueMatchRatio = 0.81; - dataSet.RequiredFalseMatchRatio = 0; // This should always stay at 0 - dataSet.RequiredTrueMismatchRatio = 0; // There are no expected mismatches in this data set - dataSet.RequiredFalseMismatchRatio = 0.25; - - return dataSet; -} - -// Hide this test as it takes too long to run. -// It is useful for comparing multiple algorithms, but for -// regular testing we need only check that the chosen algorithm -// performs well. -TEMPLATE_TEST_CASE("Correlation_MeasureAlgorithmPerformance", "[correlation][.]", - EmptyMatchConfidenceAlgorithm, - WordsEditDistanceMatchConfidenceAlgorithm) -{ - // Each section loads a different data set, - // and then they are all handled the same - DataSet dataSet; - SECTION("No ARP noise") - { - dataSet = GetDataSet_NoNoise(); - } - SECTION("With ARP noise") - { - dataSet = GetDataSet_WithNoise(); - } - - TestType measure; - auto results = EvaluateDataSetWithHeuristic(dataSet, measure); - ReportResults(results); -} - -TEST_CASE("Correlation_ChosenHeuristicIsGood", "[correlation]") -{ - // Each section loads a different data set, - // and then they are all handled the same - DataSet dataSet; - SECTION("No ARP noise") - { - dataSet = GetDataSet_NoNoise(); - } - SECTION("With ARP noise") - { - dataSet = GetDataSet_WithNoise(); - } - - // Use only the measure we ultimately pick - auto& algorithm = IARPMatchConfidenceAlgorithm::Instance(); - auto results = EvaluateDataSetWithHeuristic(dataSet, algorithm, /* reportErrors */ true); - ReportAndEvaluateResults(results, dataSet); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSource.h" + +#include +#include +#include +#include + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Correlation; +using namespace AppInstaller::Utility; + +using namespace TestCommon; + +// Data for defining a test case +struct TestCase +{ + // Actual app data + std::string AppName; + std::string AppPublisher; + + // Data in ARP + std::string ARPName; + std::string ARPPublisher; + + bool IsMatch; +}; + +// Definition of a collection of test cases that we evaluate +// together to get a single aggregate result +struct DataSet +{ + // Details about the apps we are trying to correlate + std::vector TestCases; + + // Additional ARP entries to use as "noise" for the correlation + std::vector ARPNoise; + + // Thresholds for considering a run of an heuristic against + // this data set "good". + // Values are ratios to the total number of test cases + double RequiredTrueMatchRatio; + double RequiredTrueMismatchRatio; + double RequiredFalseMatchRatio; + double RequiredFalseMismatchRatio; +}; + +// Aggregate result of running an heuristic against a data set. +struct ResultSummary +{ + size_t TrueMatches; + size_t TrueMismatches; + size_t FalseMatches; + size_t FalseMismatches; + std::chrono::milliseconds TotalTime; + + size_t TotalCases() const + { + return TrueMatches + TrueMismatches + FalseMatches + FalseMismatches; + } + + auto AverageMatchingTime() const + { + return TotalTime / TotalCases(); + } +}; + +Manifest GetManifestFromTestCase(const TestCase& testCase) +{ + Manifest manifest; + manifest.DefaultLocalization.Add(testCase.AppName); + manifest.DefaultLocalization.Add(testCase.AppPublisher); + manifest.Localizations.push_back(manifest.DefaultLocalization); + return manifest; +} + +ARPEntry GetARPEntryFromTestCase(const TestCase& testCase, bool isNew) +{ + Manifest arpManifest; + arpManifest.DefaultLocalization.Add(testCase.ARPName); + arpManifest.DefaultLocalization.Add(testCase.ARPPublisher); + arpManifest.Localizations.push_back(arpManifest.DefaultLocalization); + return ARPEntry{ TestPackage::Make(arpManifest, TestPackage::MetadataMap{}), isNew }; +} + +ARPEntry GetExistingARPEntryFromTestCase(const TestCase& testCase) +{ + return GetARPEntryFromTestCase(testCase, /* isNew */ false); +} + +void ReportMatch(std::string_view label, std::string_view appName, std::string_view appPublisher, std::string_view arpName, std::string_view arpPublisher) +{ + WARN(label << '\n' << + "\tApp name = " << appName << '\n' << + "\tApp publisher = " << appPublisher << '\n' << + "\tARP name = " << arpName << '\n' << + "\tARP publisher = " << arpPublisher); +} + +ResultSummary EvaluateDataSetWithHeuristic(const DataSet& dataSet, IARPMatchConfidenceAlgorithm& correlationAlgorithm, bool reportErrors = false) +{ + ResultSummary result{}; + auto startTime = std::chrono::steady_clock::now(); + + // Each entry under test will be pushed at the end of this + // and removed at the end. + auto arpEntries = dataSet.ARPNoise; + + for (const auto& testCase : dataSet.TestCases) + { + arpEntries.push_back(GetARPEntryFromTestCase(testCase, /* isNew */ true)); + ARPHeuristicsCorrelationResult correlationResult = FindARPEntryForNewlyInstalledPackageWithHeuristics(GetManifestFromTestCase(testCase), arpEntries, correlationAlgorithm); + auto match = correlationResult.Package; + arpEntries.pop_back(); + + if (match) + { + auto matchName = match->GetProperty(PackageVersionProperty::Name); + auto matchPublisher = match->GetProperty(PackageVersionProperty::Publisher); + + // The strings get normalized when added to the manifest, so we have + // to normalize for the comparison. + if (matchName == NormalizedString(testCase.ARPName) && matchPublisher == NormalizedString(testCase.ARPPublisher)) + { + ++result.TrueMatches; + } + else + { + ++result.FalseMatches; + + if (reportErrors) + { + ReportMatch("False match", testCase.AppName, testCase.AppPublisher, matchName, matchPublisher); + } + } + } + else + { + if (testCase.IsMatch) + { + ++result.FalseMismatches; + + if (reportErrors) + { + ReportMatch("False mismatch", testCase.AppName, testCase.AppPublisher, testCase.ARPName, testCase.ARPPublisher); + } + } + else + { + ++result.TrueMismatches; + } + } + } + + auto endTime = std::chrono::steady_clock::now(); + result.TotalTime = std::chrono::duration_cast(endTime - startTime); + + return result; +} + +void ReportResults(ResultSummary results) +{ + // This uses WARN to report as that is always shown regardless of the test result. + // We may want to re-consider reporting in some other way + WARN("Total cases: " << results.TotalCases() << '\n' << + "True matches: " << results.TrueMatches << '\n' << + "False matches: " << results.FalseMatches << '\n' << + "True mismatches: " << results.TrueMismatches << '\n' << + "False mismatches: " << results.FalseMismatches << '\n' << + "Total matching time: " << results.TotalTime.count() << "ms\n" << + "Average matching time: " << results.AverageMatchingTime().count() << "ms"); +} + +void ReportAndEvaluateResults(ResultSummary results, const DataSet& dataSet) +{ + ReportResults(results); + + // Required True ratio is a lower limit. The more results we get right, the better. + // Required False ratio is an upper limit. The fewer results we get wrong, the better. + REQUIRE(results.TrueMatches >= results.TotalCases() * dataSet.RequiredTrueMatchRatio); + REQUIRE(results.TrueMismatches >= results.TotalCases() * dataSet.RequiredTrueMismatchRatio); + REQUIRE(results.FalseMatches <= results.TotalCases() * dataSet.RequiredFalseMatchRatio); + REQUIRE(results.FalseMismatches <= results.TotalCases()* dataSet.RequiredFalseMismatchRatio); +} + +// TODO: Define multiple data sets +// - Data set with many apps. +// - Data set with popular apps. The match requirements should be higher +// - Data set(s) in other languages. +// - Data set where not everything has a match + +std::vector LoadTestData() +{ + // Creates test cases from the test data file. + // The format of the file is one case per line, each with pipe (|) separated values. + // Each row contains: AppId, AppName, AppPublisher, ARPDisplayName, ARPDisplayVersion, ARPPublisherName, ARPProductCode + // TODO: Add more test cases; particularly for non-matches + std::ifstream testDataStream(TestCommon::TestDataFile("InputARPData.txt").GetPath()); + REQUIRE(testDataStream); + + std::vector testCases; + + std::string line; + while (std::getline(testDataStream, line)) + { + std::stringstream ss{ line }; + + TestCase testCase; + std::string appId; + std::string arpDisplayVersion; + std::string arpProductCode; + std::getline(ss, appId, '|'); + std::getline(ss, testCase.AppName, '|'); + std::getline(ss, testCase.AppPublisher, '|'); + std::getline(ss, testCase.ARPName, '|'); + std::getline(ss, arpDisplayVersion, '|'); + std::getline(ss, testCase.ARPPublisher, '|'); + std::getline(ss, arpProductCode, '|'); + + testCase.IsMatch = true; + + testCases.push_back(std::move(testCase)); + } + + return testCases; +} + +DataSet GetDataSet_NoNoise() +{ + DataSet dataSet; + dataSet.TestCases = LoadTestData(); + + // Arbitrary values. We should refine them as the algorithm gets better. + dataSet.RequiredTrueMatchRatio = 0.81; + dataSet.RequiredFalseMatchRatio = 0; + dataSet.RequiredTrueMismatchRatio = 0; // There are no expected mismatches in this data set + dataSet.RequiredFalseMismatchRatio = 0.25; + + return dataSet; +} + +DataSet GetDataSet_WithNoise() +{ + DataSet dataSet; + auto baseTestCases = LoadTestData(); + + std::transform(baseTestCases.begin(), baseTestCases.end(), std::back_inserter(dataSet.ARPNoise), GetExistingARPEntryFromTestCase); + dataSet.TestCases = std::move(baseTestCases); + + // Arbitrary values. We should refine them as the algorithm gets better. + dataSet.RequiredTrueMatchRatio = 0.81; + dataSet.RequiredFalseMatchRatio = 0; // This should always stay at 0 + dataSet.RequiredTrueMismatchRatio = 0; // There are no expected mismatches in this data set + dataSet.RequiredFalseMismatchRatio = 0.25; + + return dataSet; +} + +// Hide this test as it takes too long to run. +// It is useful for comparing multiple algorithms, but for +// regular testing we need only check that the chosen algorithm +// performs well. +TEMPLATE_TEST_CASE("Correlation_MeasureAlgorithmPerformance", "[correlation][.]", + EmptyMatchConfidenceAlgorithm, + WordsEditDistanceMatchConfidenceAlgorithm) +{ + // Each section loads a different data set, + // and then they are all handled the same + DataSet dataSet; + SECTION("No ARP noise") + { + dataSet = GetDataSet_NoNoise(); + } + SECTION("With ARP noise") + { + dataSet = GetDataSet_WithNoise(); + } + + TestType measure; + auto results = EvaluateDataSetWithHeuristic(dataSet, measure); + ReportResults(results); +} + +TEST_CASE("Correlation_ChosenHeuristicIsGood", "[correlation]") +{ + // Each section loads a different data set, + // and then they are all handled the same + DataSet dataSet; + SECTION("No ARP noise") + { + dataSet = GetDataSet_NoNoise(); + } + SECTION("With ARP noise") + { + dataSet = GetDataSet_WithNoise(); + } + + // Use only the measure we ultimately pick + auto& algorithm = IARPMatchConfidenceAlgorithm::Instance(); + auto results = EvaluateDataSetWithHeuristic(dataSet, algorithm, /* reportErrors */ true); + ReportAndEvaluateResults(results, dataSet); +} diff --git a/src/AppInstallerCLITests/CustomHeader.cpp b/src/AppInstallerCLITests/CustomHeader.cpp index e299523251..5b04f0693a 100644 --- a/src/AppInstallerCLITests/CustomHeader.cpp +++ b/src/AppInstallerCLITests/CustomHeader.cpp @@ -1,152 +1,152 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestHooks.h" -#include "TestSettings.h" -#include "TestSource.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::Http; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_0; - -namespace -{ - utility::string_t CustomHeaderName = L"Windows-Package-Manager"; - - constexpr std::string_view s_EmptySources = R"( - Sources: - )"sv; - - utility::string_t sampleSearchResponse = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [ - { "PackageVersion": "1.0.0" }] - }] - })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( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.0.0", - "2.0.0"] - }})delimiter"); - - 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 = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper); - REQUIRE(client.GetSourceIdentifier() == "Source123"); -} - -TEST_CASE("RestSourceSearch_CustomHeader", "[RestSource][CustomHeader]") -{ - utility::string_t customHeader = L"Testing custom header"; - auto header = std::make_pair<>(CustomHeaderName, customHeader); - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; - std::unordered_map headers; - headers.emplace(CustomHeaderName, customHeader); - - V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper) , {}, headers}; - Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); - REQUIRE(searchResponse.Matches.size() == 1); - Schema::IRestClient::Package package = searchResponse.Matches.at(0); -} - -TEST_CASE("RestSourceSearch_WhitespaceCustomHeader", "[RestSource][CustomHeader]") -{ - utility::string_t customHeader = L" "; - auto header = std::make_pair<>(CustomHeaderName, customHeader); - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; - std::unordered_map headers; - headers.emplace(CustomHeaderName, customHeader); - - V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper), {}, headers }; - Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); - REQUIRE(searchResponse.Matches.size() == 1); -} - -TEST_CASE("RestSourceSearch_NoCustomHeader", "[RestSource][CustomHeader]") -{ - utility::string_t customHeader = L" "; - auto header = std::make_pair<>(CustomHeaderName, customHeader); - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; - std::unordered_map headers; - headers.emplace(CustomHeaderName, customHeader); - - V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper), {}, {} }; - REQUIRE_THROWS_HR(v1_1.Search({}), APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR); -} - -TEST_CASE("RestSourceSearch_CustomHeaderExceedingSize", "[RestSource][CustomHeader]") -{ - std::string customHeader = "This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. "; - auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader)); - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; - - REQUIRE_THROWS_HR(CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper), - APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH); -} - -TEST_CASE("RestClient_CustomUserAgentHeader", "[RestSource][CustomHeader]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.0.0", - "2.0.0"] - }})delimiter"); - - 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 = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, helper); - REQUIRE(client.GetSourceIdentifier() == "Source123"); -} - -TEST_CASE("RestClient_DefaultUserAgentHeader", "[RestSource][CustomHeader]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.0.0", - "2.0.0"] - }})delimiter"); - - 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 = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, helper); - REQUIRE(client.GetSourceIdentifier() == "Source123"); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestHooks.h" +#include "TestSettings.h" +#include "TestSource.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Http; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_0; + +namespace +{ + utility::string_t CustomHeaderName = L"Windows-Package-Manager"; + + constexpr std::string_view s_EmptySources = R"( + Sources: + )"sv; + + utility::string_t sampleSearchResponse = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [ + { "PackageVersion": "1.0.0" }] + }] + })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( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.0.0", + "2.0.0"] + }})delimiter"); + + 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 = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper); + REQUIRE(client.GetSourceIdentifier() == "Source123"); +} + +TEST_CASE("RestSourceSearch_CustomHeader", "[RestSource][CustomHeader]") +{ + utility::string_t customHeader = L"Testing custom header"; + auto header = std::make_pair<>(CustomHeaderName, customHeader); + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + std::unordered_map headers; + headers.emplace(CustomHeaderName, customHeader); + + V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper) , {}, headers}; + Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); + REQUIRE(searchResponse.Matches.size() == 1); + Schema::IRestClient::Package package = searchResponse.Matches.at(0); +} + +TEST_CASE("RestSourceSearch_WhitespaceCustomHeader", "[RestSource][CustomHeader]") +{ + utility::string_t customHeader = L" "; + auto header = std::make_pair<>(CustomHeaderName, customHeader); + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + std::unordered_map headers; + headers.emplace(CustomHeaderName, customHeader); + + V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper), {}, headers }; + Schema::IRestClient::SearchResult searchResponse = v1_1.Search({}); + REQUIRE(searchResponse.Matches.size() == 1); +} + +TEST_CASE("RestSourceSearch_NoCustomHeader", "[RestSource][CustomHeader]") +{ + utility::string_t customHeader = L" "; + auto header = std::make_pair<>(CustomHeaderName, customHeader); + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + std::unordered_map headers; + headers.emplace(CustomHeaderName, customHeader); + + V1_1::Interface v1_1{ "https://restsource.com/api", std::move(helper), {}, {} }; + REQUIRE_THROWS_HR(v1_1.Search({}), APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR); +} + +TEST_CASE("RestSourceSearch_CustomHeaderExceedingSize", "[RestSource][CustomHeader]") +{ + std::string customHeader = "This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. This is a custom header that is longer than 1024 characters. "; + auto header = std::make_pair<>(CustomHeaderName, JSON::GetUtilityString(customHeader)); + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, sampleSearchResponse, header) }; + + REQUIRE_THROWS_HR(CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), customHeader, {}, helper), + APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH); +} + +TEST_CASE("RestClient_CustomUserAgentHeader", "[RestSource][CustomHeader]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.0.0", + "2.0.0"] + }})delimiter"); + + 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 = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, testCaller, helper); + REQUIRE(client.GetSourceIdentifier() == "Source123"); +} + +TEST_CASE("RestClient_DefaultUserAgentHeader", "[RestSource][CustomHeader]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.0.0", + "2.0.0"] + }})delimiter"); + + 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 = CreateRestClient(utility::conversions::to_utf8string("https://restsource.com/api"), {}, {}, helper); + REQUIRE(client.GetSourceIdentifier() == "Source123"); +} diff --git a/src/AppInstallerCLITests/DateTime.cpp b/src/AppInstallerCLITests/DateTime.cpp index 264894b8b2..258836f95b 100644 --- a/src/AppInstallerCLITests/DateTime.cpp +++ b/src/AppInstallerCLITests/DateTime.cpp @@ -1,93 +1,93 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include - -using namespace AppInstaller::Utility; -using namespace TestCommon; -using namespace std::chrono; - -namespace Catch -{ - template<> - struct StringMaker - { - static std::string convert(const std::chrono::system_clock::time_point& value) - { - std::ostringstream stream; - OutputTimePoint(stream, value); - return std::move(stream).str(); - } - }; -} - -void VerifyGetTimePointFromVersion(std::string_view version, int year, int month, int day, int hour, int minute) -{ - system_clock::time_point result = GetTimePointFromVersion(UInt64Version{ std::string{ version } }); - - tm time{}; - auto tt = system_clock::to_time_t(result); - _gmtime64_s(&time, &tt); - - REQUIRE(year == time.tm_year + 1900); - REQUIRE(month == time.tm_mon + 1); - REQUIRE(day == time.tm_mday); - REQUIRE(hour == time.tm_hour); - REQUIRE(minute == time.tm_min); -} - -std::string StringFromTimePoint(system_clock::time_point input) -{ - tm time{}; - auto tt = system_clock::to_time_t(input); - _gmtime64_s(&time, &tt); - - std::ostringstream stream; - stream << time.tm_year + 1900 << '.' << ((time.tm_mon + 1) * 100) + time.tm_mday << '.' << ((time.tm_hour + 1) * 100) + time.tm_min; - return std::move(stream).str(); -} - -TEST_CASE("GetTimePointFromVersion", "[datetime]") -{ - // Years out of range - REQUIRE(GetTimePointFromVersion(UInt64Version{ "1969.1231.2459.0" }) == system_clock::time_point::min()); - REQUIRE(GetTimePointFromVersion(UInt64Version{ "3001.101.100.0" }) == system_clock::time_point::min()); - - // Months out of range - REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.1.100.0" }) == system_clock::time_point::min()); - REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.1301.100.0" }) == system_clock::time_point::min()); - - // Days out of range - REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.100.100.0" }) == system_clock::time_point::min()); - REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.132.100.0" }) == system_clock::time_point::min()); - - // Hours out of range - REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.0.0" }) == system_clock::time_point::min()); - REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.2500.0" }) == system_clock::time_point::min()); - - // Minutes out of range - REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.160.0" }) == system_clock::time_point::min()); - - // In range baseline - VerifyGetTimePointFromVersion("2023.101.100.0", 2023, 1, 1, 0, 0); - - // Time for presents! - VerifyGetTimePointFromVersion("2023.1225.814.0", 2023, 12, 25, 7, 14); - - // Epoch time - REQUIRE(GetTimePointFromVersion(UInt64Version{ "1970.101.100.0" }) == system_clock::time_point{}); - - // Round trip now - system_clock::time_point now = system_clock::now(); - REQUIRE(GetTimePointFromVersion(UInt64Version{ StringFromTimePoint(now) }) == time_point_cast(now)); -} - -TEST_CASE("ShortFileTime", "[datetime]") -{ - auto shortTime = GetCurrentTimeForFilename(true); - auto longTime = GetCurrentTimeForFilename(false); - INFO(shortTime); - INFO(longTime); - REQUIRE(shortTime.length() < longTime.length()); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace AppInstaller::Utility; +using namespace TestCommon; +using namespace std::chrono; + +namespace Catch +{ + template<> + struct StringMaker + { + static std::string convert(const std::chrono::system_clock::time_point& value) + { + std::ostringstream stream; + OutputTimePoint(stream, value); + return std::move(stream).str(); + } + }; +} + +void VerifyGetTimePointFromVersion(std::string_view version, int year, int month, int day, int hour, int minute) +{ + system_clock::time_point result = GetTimePointFromVersion(UInt64Version{ std::string{ version } }); + + tm time{}; + auto tt = system_clock::to_time_t(result); + _gmtime64_s(&time, &tt); + + REQUIRE(year == time.tm_year + 1900); + REQUIRE(month == time.tm_mon + 1); + REQUIRE(day == time.tm_mday); + REQUIRE(hour == time.tm_hour); + REQUIRE(minute == time.tm_min); +} + +std::string StringFromTimePoint(system_clock::time_point input) +{ + tm time{}; + auto tt = system_clock::to_time_t(input); + _gmtime64_s(&time, &tt); + + std::ostringstream stream; + stream << time.tm_year + 1900 << '.' << ((time.tm_mon + 1) * 100) + time.tm_mday << '.' << ((time.tm_hour + 1) * 100) + time.tm_min; + return std::move(stream).str(); +} + +TEST_CASE("GetTimePointFromVersion", "[datetime]") +{ + // Years out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "1969.1231.2459.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "3001.101.100.0" }) == system_clock::time_point::min()); + + // Months out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.1.100.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.1301.100.0" }) == system_clock::time_point::min()); + + // Days out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.100.100.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.132.100.0" }) == system_clock::time_point::min()); + + // Hours out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.0.0" }) == system_clock::time_point::min()); + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.2500.0" }) == system_clock::time_point::min()); + + // Minutes out of range + REQUIRE(GetTimePointFromVersion(UInt64Version{ "2023.101.160.0" }) == system_clock::time_point::min()); + + // In range baseline + VerifyGetTimePointFromVersion("2023.101.100.0", 2023, 1, 1, 0, 0); + + // Time for presents! + VerifyGetTimePointFromVersion("2023.1225.814.0", 2023, 12, 25, 7, 14); + + // Epoch time + REQUIRE(GetTimePointFromVersion(UInt64Version{ "1970.101.100.0" }) == system_clock::time_point{}); + + // Round trip now + system_clock::time_point now = system_clock::now(); + REQUIRE(GetTimePointFromVersion(UInt64Version{ StringFromTimePoint(now) }) == time_point_cast(now)); +} + +TEST_CASE("ShortFileTime", "[datetime]") +{ + auto shortTime = GetCurrentTimeForFilename(true); + auto longTime = GetCurrentTimeForFilename(false); + INFO(shortTime); + INFO(longTime); + REQUIRE(shortTime.length() < longTime.length()); +} diff --git a/src/AppInstallerCLITests/DownloadFlow.cpp b/src/AppInstallerCLITests/DownloadFlow.cpp index c4e1a42f21..e2cac0c111 100644 --- a/src/AppInstallerCLITests/DownloadFlow.cpp +++ b/src/AppInstallerCLITests/DownloadFlow.cpp @@ -1,141 +1,141 @@ -// 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]") -{ - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_DownloadCommandProhibited.yaml").GetPath().u8string()); - - DownloadCommand download({}); - download.Execute(context); - INFO(downloadOutput.str()); - - // Verify AppInfo is printed - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED); - 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); -} +// 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]") +{ + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_DownloadCommandProhibited.yaml").GetPath().u8string()); + + DownloadCommand download({}); + download.Execute(context); + INFO(downloadOutput.str()); + + // Verify AppInfo is printed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED); + 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/Downloader.cpp b/src/AppInstallerCLITests/Downloader.cpp index 4c65c05bb1..4128d543b5 100644 --- a/src/AppInstallerCLITests/Downloader.cpp +++ b/src/AppInstallerCLITests/Downloader.cpp @@ -1,171 +1,171 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "AppInstallerDownloader.h" -#include "AppInstallerSHA256.h" -#include "HttpStream/HttpLocalCache.h" - -using namespace AppInstaller; -using namespace AppInstaller::Utility; -using namespace std::string_literals; - -TEST_CASE("DownloadValidFileAndVerifyHash", "[Downloader]") -{ - TestCommon::TempFile tempFile("downloader_test"s, ".test"s); - INFO("Using temporary file named: " << tempFile.GetPath()); - - // Todo: point to files from our repo when the repo goes public - ProgressCallback callback; - auto result = Download("https://raw.githubusercontent.com/microsoft/msix-packaging/master/LICENSE", tempFile.GetPath(), DownloadType::Manifest, callback); - - REQUIRE(!result.Sha256Hash.empty()); - auto resultHash = result.Sha256Hash; - - auto expectedHash = SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - REQUIRE(std::equal( - expectedHash.begin(), - expectedHash.end(), - resultHash.begin())); - - uint64_t expectedFileSize = 1119; - REQUIRE(result.SizeInBytes == expectedFileSize); - REQUIRE(std::filesystem::file_size(tempFile.GetPath()) == expectedFileSize); - - REQUIRE(result.ContentType); - REQUIRE(!result.ContentType.value().empty()); - - // Verify motw content - std::filesystem::path motwFile(tempFile); - motwFile += ":Zone.Identifier:$data"; - std::ifstream motwStream(motwFile); - std::stringstream motwContent; - motwContent << motwStream.rdbuf(); - std::string motwContentStr = motwContent.str(); - REQUIRE(motwContentStr.find("ZoneId=3") != std::string::npos); -} - -TEST_CASE("DownloadValidFileAndCancel", "[Downloader]") -{ - TestCommon::TempFile tempFile("downloader_test"s, ".test"s); - INFO("Using temporary file named: " << tempFile.GetPath()); - - ProgressCallback callback; - - DownloadResult waitResult; - std::thread waitThread([&] - { - waitResult = Download("https://aka.ms/win32-x64-user-stable", tempFile.GetPath(), DownloadType::Installer, callback); - }); - - callback.Cancel(); - - waitThread.join(); - - REQUIRE(waitResult.Sha256Hash.empty()); -} - -TEST_CASE("DownloadInvalidUrl", "[Downloader]") -{ - TestCommon::TempFile tempFile("downloader_test"s, ".test"s); - INFO("Using temporary file named: " << tempFile.GetPath()); - - ProgressCallback callback; - - REQUIRE_THROWS(Download("blargle-flargle-fluff", tempFile.GetPath(), DownloadType::Installer, callback)); -} - -TEST_CASE("HttpStream_ReadLastFullPage", "[HttpStream]") -{ - Microsoft::WRL::ComPtr stream; - STATSTG stat = { 0 }; - - for (size_t i = 0; i < 10; ++i) - { - stream = GetReadOnlyStreamFromURI("https://aka.ms/win32-x64-user-stable"); - - stat = { 0 }; - REQUIRE(stream->Stat(&stat, STATFLAG_NONAME) == S_OK); - - if (stat.cbSize.QuadPart > 0) - { - break; - } - - Sleep(500); - } - - { - INFO("https://aka.ms/win32-x64-user-stable gave back a 0 byte file"); - REQUIRE(stream); - } - - LARGE_INTEGER seek; - seek.QuadPart = (stat.cbSize.QuadPart / HttpStream::HttpLocalCache::PAGE_SIZE) * HttpStream::HttpLocalCache::PAGE_SIZE; - REQUIRE(stream->Seek(seek, STREAM_SEEK_SET, nullptr) == S_OK); - - std::unique_ptr buffer = std::make_unique(HttpStream::HttpLocalCache::PAGE_SIZE); - ULONG read = 0; - REQUIRE(stream->Read(buffer.get(), static_cast(HttpStream::HttpLocalCache::PAGE_SIZE), &read) >= S_OK); - REQUIRE(read == (stat.cbSize.QuadPart % HttpStream::HttpLocalCache::PAGE_SIZE)); -} - -TEST_CASE("CacheControl", "[Downloader]") -{ - SECTION("Empty") - { - CacheControlPolicy test{ L"" }; - REQUIRE(!test.Present); - } - SECTION("Space") - { - CacheControlPolicy test{ L" " }; - REQUIRE(!test.Present); - } - SECTION("Standard") - { - CacheControlPolicy test{ L"public, max-age=77287" }; - REQUIRE(test.Present); - REQUIRE(test.Public); - REQUIRE(!test.NoCache); - REQUIRE(!test.NoStore); - REQUIRE(test.MaxAge == 77287); - } - SECTION("All") - { - CacheControlPolicy test{ L"public, no-cache, no-store, max-age = 77" }; - REQUIRE(test.Present); - REQUIRE(test.Public); - REQUIRE(test.NoCache); - REQUIRE(test.NoStore); - REQUIRE(test.MaxAge == 77); - } - SECTION("Casing") - { - CacheControlPolicy test{ L"Public, Max-Age=42" }; - REQUIRE(test.Present); - REQUIRE(test.Public); - REQUIRE(!test.NoCache); - REQUIRE(!test.NoStore); - REQUIRE(test.MaxAge == 42); - } - SECTION("Unknown") - { - CacheControlPolicy test{ L"public, max-age=77287, not-real" }; - REQUIRE(test.Present); - REQUIRE(test.Public); - REQUIRE(!test.NoCache); - REQUIRE(!test.NoStore); - REQUIRE(test.MaxAge == 77287); - } - SECTION("MaxAge Negative") - { - CacheControlPolicy test{ L"max-age=-1" }; - REQUIRE(test.MaxAge == CacheControlPolicy::MaximumMaxAge); - } - SECTION("MaxAge not a number") - { - CacheControlPolicy test{ L"max-age=FOO" }; - REQUIRE(test.MaxAge == 0); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "AppInstallerDownloader.h" +#include "AppInstallerSHA256.h" +#include "HttpStream/HttpLocalCache.h" + +using namespace AppInstaller; +using namespace AppInstaller::Utility; +using namespace std::string_literals; + +TEST_CASE("DownloadValidFileAndVerifyHash", "[Downloader]") +{ + TestCommon::TempFile tempFile("downloader_test"s, ".test"s); + INFO("Using temporary file named: " << tempFile.GetPath()); + + // Todo: point to files from our repo when the repo goes public + ProgressCallback callback; + auto result = Download("https://raw.githubusercontent.com/microsoft/msix-packaging/master/LICENSE", tempFile.GetPath(), DownloadType::Manifest, callback); + + REQUIRE(!result.Sha256Hash.empty()); + auto resultHash = result.Sha256Hash; + + auto expectedHash = SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + REQUIRE(std::equal( + expectedHash.begin(), + expectedHash.end(), + resultHash.begin())); + + uint64_t expectedFileSize = 1119; + REQUIRE(result.SizeInBytes == expectedFileSize); + REQUIRE(std::filesystem::file_size(tempFile.GetPath()) == expectedFileSize); + + REQUIRE(result.ContentType); + REQUIRE(!result.ContentType.value().empty()); + + // Verify motw content + std::filesystem::path motwFile(tempFile); + motwFile += ":Zone.Identifier:$data"; + std::ifstream motwStream(motwFile); + std::stringstream motwContent; + motwContent << motwStream.rdbuf(); + std::string motwContentStr = motwContent.str(); + REQUIRE(motwContentStr.find("ZoneId=3") != std::string::npos); +} + +TEST_CASE("DownloadValidFileAndCancel", "[Downloader]") +{ + TestCommon::TempFile tempFile("downloader_test"s, ".test"s); + INFO("Using temporary file named: " << tempFile.GetPath()); + + ProgressCallback callback; + + DownloadResult waitResult; + std::thread waitThread([&] + { + waitResult = Download("https://aka.ms/win32-x64-user-stable", tempFile.GetPath(), DownloadType::Installer, callback); + }); + + callback.Cancel(); + + waitThread.join(); + + REQUIRE(waitResult.Sha256Hash.empty()); +} + +TEST_CASE("DownloadInvalidUrl", "[Downloader]") +{ + TestCommon::TempFile tempFile("downloader_test"s, ".test"s); + INFO("Using temporary file named: " << tempFile.GetPath()); + + ProgressCallback callback; + + REQUIRE_THROWS(Download("blargle-flargle-fluff", tempFile.GetPath(), DownloadType::Installer, callback)); +} + +TEST_CASE("HttpStream_ReadLastFullPage", "[HttpStream]") +{ + Microsoft::WRL::ComPtr stream; + STATSTG stat = { 0 }; + + for (size_t i = 0; i < 10; ++i) + { + stream = GetReadOnlyStreamFromURI("https://aka.ms/win32-x64-user-stable"); + + stat = { 0 }; + REQUIRE(stream->Stat(&stat, STATFLAG_NONAME) == S_OK); + + if (stat.cbSize.QuadPart > 0) + { + break; + } + + Sleep(500); + } + + { + INFO("https://aka.ms/win32-x64-user-stable gave back a 0 byte file"); + REQUIRE(stream); + } + + LARGE_INTEGER seek; + seek.QuadPart = (stat.cbSize.QuadPart / HttpStream::HttpLocalCache::PAGE_SIZE) * HttpStream::HttpLocalCache::PAGE_SIZE; + REQUIRE(stream->Seek(seek, STREAM_SEEK_SET, nullptr) == S_OK); + + std::unique_ptr buffer = std::make_unique(HttpStream::HttpLocalCache::PAGE_SIZE); + ULONG read = 0; + REQUIRE(stream->Read(buffer.get(), static_cast(HttpStream::HttpLocalCache::PAGE_SIZE), &read) >= S_OK); + REQUIRE(read == (stat.cbSize.QuadPart % HttpStream::HttpLocalCache::PAGE_SIZE)); +} + +TEST_CASE("CacheControl", "[Downloader]") +{ + SECTION("Empty") + { + CacheControlPolicy test{ L"" }; + REQUIRE(!test.Present); + } + SECTION("Space") + { + CacheControlPolicy test{ L" " }; + REQUIRE(!test.Present); + } + SECTION("Standard") + { + CacheControlPolicy test{ L"public, max-age=77287" }; + REQUIRE(test.Present); + REQUIRE(test.Public); + REQUIRE(!test.NoCache); + REQUIRE(!test.NoStore); + REQUIRE(test.MaxAge == 77287); + } + SECTION("All") + { + CacheControlPolicy test{ L"public, no-cache, no-store, max-age = 77" }; + REQUIRE(test.Present); + REQUIRE(test.Public); + REQUIRE(test.NoCache); + REQUIRE(test.NoStore); + REQUIRE(test.MaxAge == 77); + } + SECTION("Casing") + { + CacheControlPolicy test{ L"Public, Max-Age=42" }; + REQUIRE(test.Present); + REQUIRE(test.Public); + REQUIRE(!test.NoCache); + REQUIRE(!test.NoStore); + REQUIRE(test.MaxAge == 42); + } + SECTION("Unknown") + { + CacheControlPolicy test{ L"public, max-age=77287, not-real" }; + REQUIRE(test.Present); + REQUIRE(test.Public); + REQUIRE(!test.NoCache); + REQUIRE(!test.NoStore); + REQUIRE(test.MaxAge == 77287); + } + SECTION("MaxAge Negative") + { + CacheControlPolicy test{ L"max-age=-1" }; + REQUIRE(test.MaxAge == CacheControlPolicy::MaximumMaxAge); + } + SECTION("MaxAge not a number") + { + CacheControlPolicy test{ L"max-age=FOO" }; + REQUIRE(test.MaxAge == 0); + } +} diff --git a/src/AppInstallerCLITests/Errors.cpp b/src/AppInstallerCLITests/Errors.cpp index a1ca55e7ae..57a7494451 100644 --- a/src/AppInstallerCLITests/Errors.cpp +++ b/src/AppInstallerCLITests/Errors.cpp @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include - -using namespace AppInstaller; -using namespace AppInstaller::Utility; -using namespace std::string_literals; - -TEST_CASE("EnsureSortedErrorList", "[errors]") -{ - auto errors = Errors::GetWinGetErrors(); - for (size_t i = 1; i < errors.size(); ++i) - { - INFO(errors[i - 1]->Symbol() << " then " << errors[i]->Symbol()); - REQUIRE(errors[i]->Value() > errors[i - 1]->Value()); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace AppInstaller; +using namespace AppInstaller::Utility; +using namespace std::string_literals; + +TEST_CASE("EnsureSortedErrorList", "[errors]") +{ + auto errors = Errors::GetWinGetErrors(); + for (size_t i = 1; i < errors.size(); ++i) + { + INFO(errors[i - 1]->Symbol() << " then " << errors[i]->Symbol()); + REQUIRE(errors[i]->Value() > errors[i - 1]->Value()); + } +} diff --git a/src/AppInstallerCLITests/ExperimentalFeature.cpp b/src/AppInstallerCLITests/ExperimentalFeature.cpp index b9f9084896..d14bbbf5ea 100644 --- a/src/AppInstallerCLITests/ExperimentalFeature.cpp +++ b/src/AppInstallerCLITests/ExperimentalFeature.cpp @@ -32,7 +32,7 @@ TEST_CASE("ExperimentalFeature ExperimentalCmd", "[experimentalFeature]") SECTION("Feature off default") { - UserSettingsTest userSettingTest; + UserSettingsTest userSettingTest; REQUIRE_FALSE(ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::ExperimentalCmd, userSettingTest)); } @@ -40,7 +40,7 @@ TEST_CASE("ExperimentalFeature ExperimentalCmd", "[experimentalFeature]") { std::string_view json = R"({ "experimentalFeatures": { "experimentalCmd": true } })"; SetSetting(Stream::PrimaryUserSettings, json); - UserSettingsTest userSettingTest; + UserSettingsTest userSettingTest; REQUIRE(ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::ExperimentalCmd, userSettingTest)); } @@ -48,7 +48,7 @@ TEST_CASE("ExperimentalFeature ExperimentalCmd", "[experimentalFeature]") { std::string_view json = R"({ "experimentalFeatures": { "experimentalCmd": false } })"; SetSetting(Stream::PrimaryUserSettings, json); - UserSettingsTest userSettingTest; + UserSettingsTest userSettingTest; REQUIRE_FALSE(ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::ExperimentalCmd, userSettingTest)); } @@ -56,7 +56,7 @@ TEST_CASE("ExperimentalFeature ExperimentalCmd", "[experimentalFeature]") { std::string_view json = R"({ "experimentalFeatures": { "experimentalCmd": "string" } })"; SetSetting(Stream::PrimaryUserSettings, json); - UserSettingsTest userSettingTest; + UserSettingsTest userSettingTest; REQUIRE_FALSE(ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::ExperimentalCmd, userSettingTest)); } @@ -68,7 +68,7 @@ TEST_CASE("ExperimentalFeature ExperimentalCmd", "[experimentalFeature]") std::string_view json = R"({ "experimentalFeatures": { "experimentalCmd": true } })"; SetSetting(Stream::PrimaryUserSettings, json); - UserSettingsTest userSettingTest; + UserSettingsTest userSettingTest; REQUIRE_FALSE(ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::ExperimentalCmd, userSettingTest)); } diff --git a/src/AppInstallerCLITests/ExportFlow.cpp b/src/AppInstallerCLITests/ExportFlow.cpp index ec4e710971..6e5deb0eca 100644 --- a/src/AppInstallerCLITests/ExportFlow.cpp +++ b/src/AppInstallerCLITests/ExportFlow.cpp @@ -1,191 +1,191 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "TestSource.h" -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Manifest::YamlParser; - -TEST_CASE("ExportFlow_ExportAll", "[ExportFlow][workflow]") -{ - TestCommon::TempFile exportResultPath("TestExport.json"); - - std::ostringstream exportOutput; - TestContext context{ exportOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath); - - ExportCommand exportCommand({}); - exportCommand.Execute(context); - INFO(exportOutput.str()); - - // Verify contents of exported collection - const auto& exportedCollection = context.Get(); - REQUIRE(exportedCollection.Sources.size() == 1); - REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource"); - - const auto& exportedPackages = exportedCollection.Sources[0].Packages; - REQUIRE(exportedPackages.size() == 6); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestMsixInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestMSStoreInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestPortableInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString().empty(); - })); -} - -TEST_CASE("ExportFlow_ExportAll_WithVersions", "[ExportFlow][workflow]") -{ - TestCommon::TempFile exportResultPath("TestExport.json"); - - std::ostringstream exportOutput; - TestContext context{ exportOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath); - context.Args.AddArg(Execution::Args::Type::IncludeVersions); - - ExportCommand exportCommand({}); - exportCommand.Execute(context); - INFO(exportOutput.str()); - - // Verify contents of exported collection - const auto& exportedCollection = context.Get(); - REQUIRE(exportedCollection.Sources.size() == 1); - REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource"); - - const auto& exportedPackages = exportedCollection.Sources[0].Packages; - REQUIRE(exportedPackages.size() == 6); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestMsixInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestMSStoreInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestPortableInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; - })); - REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) - { - return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString() == "unknown"; - })); -} - -TEST_CASE("ExportFlow_ExportAll_WithUserInstallerArgs", "[ExportFlow][workflow]") -{ - TestCommon::TempFile exportResultPath("TestExport.json"); - - std::ostringstream exportOutput; - TestContext context{ exportOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - - // Create a test source with packages that have InitialOverrideArguments and InitialCustomSwitches set - auto testSource = CreateTestSource({}); - - TestSourceResult exeWithOverride( - "AppInstallerCliTest.TestExeInstaller"sv, - [](std::vector& matches, std::weak_ptr source) - { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); - auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); - - auto testPackage = TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Exe" }, - { PackageVersionMetadata::InitialOverrideArguments, "/silent /override" }, - { PackageVersionMetadata::InitialCustomSwitches, "--custom-flag" }, - }, - std::vector{ manifest3, manifest2, manifest }, - source); - for (auto& availablePackage : testPackage->Available) - { - availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; - } - matches.emplace_back( - ResultMatch( - testPackage, - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - testSource->AddResult(exeWithOverride); - - OverrideForCompositeInstalledSource(context, testSource); - context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath); - - ExportCommand exportCommand({}); - exportCommand.Execute(context); - INFO(exportOutput.str()); - - const auto& exportedCollection = context.Get(); - REQUIRE(exportedCollection.Sources.size() == 1); - - const auto& exportedPackages = exportedCollection.Sources[0].Packages; - REQUIRE(exportedPackages.size() == 1); - - const auto& pkg = exportedPackages[0]; - REQUIRE(pkg.Id == "AppInstallerCliTest.TestExeInstaller"); - REQUIRE(pkg.InitialOverrideArgs == "/silent /override"); - REQUIRE(pkg.InitialCustomSwitches == "--custom-flag"); - - // Verify the values are in the exported JSON file - std::ifstream exportFile(exportResultPath.GetPath()); - Json::Value exportedJson; - exportFile >> exportedJson; - - const auto& jsonPackage = exportedJson["Sources"][0]["Packages"][0]; - REQUIRE(jsonPackage["InitialOverrideArguments"].asString() == "/silent /override"); - REQUIRE(jsonPackage["InitialCustomSwitches"].asString() == "--custom-flag"); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestSource.h" +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Manifest::YamlParser; + +TEST_CASE("ExportFlow_ExportAll", "[ExportFlow][workflow]") +{ + TestCommon::TempFile exportResultPath("TestExport.json"); + + std::ostringstream exportOutput; + TestContext context{ exportOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath); + + ExportCommand exportCommand({}); + exportCommand.Execute(context); + INFO(exportOutput.str()); + + // Verify contents of exported collection + const auto& exportedCollection = context.Get(); + REQUIRE(exportedCollection.Sources.size() == 1); + REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource"); + + const auto& exportedPackages = exportedCollection.Sources[0].Packages; + REQUIRE(exportedPackages.size() == 6); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestMsixInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestMSStoreInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestPortableInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString().empty(); + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString().empty(); + })); +} + +TEST_CASE("ExportFlow_ExportAll_WithVersions", "[ExportFlow][workflow]") +{ + TestCommon::TempFile exportResultPath("TestExport.json"); + + std::ostringstream exportOutput; + TestContext context{ exportOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath); + context.Args.AddArg(Execution::Args::Type::IncludeVersions); + + ExportCommand exportCommand({}); + exportCommand.Execute(context); + INFO(exportOutput.str()); + + // Verify contents of exported collection + const auto& exportedCollection = context.Get(); + REQUIRE(exportedCollection.Sources.size() == 1); + REQUIRE(exportedCollection.Sources[0].Details.Identifier == "*TestSource"); + + const auto& exportedPackages = exportedCollection.Sources[0].Packages; + REQUIRE(exportedPackages.size() == 6); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestExeInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestMsixInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestMSStoreInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestPortableInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestZipInstaller" && p.VersionAndChannel.GetVersion().ToString() == "1.0.0.0"; + })); + REQUIRE(exportedPackages.end() != std::find_if(exportedPackages.begin(), exportedPackages.end(), [](const auto& p) + { + return p.Id == "AppInstallerCliTest.TestExeUnknownVersion" && p.VersionAndChannel.GetVersion().ToString() == "unknown"; + })); +} + +TEST_CASE("ExportFlow_ExportAll_WithUserInstallerArgs", "[ExportFlow][workflow]") +{ + TestCommon::TempFile exportResultPath("TestExport.json"); + + std::ostringstream exportOutput; + TestContext context{ exportOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + + // Create a test source with packages that have InitialOverrideArguments and InitialCustomSwitches set + auto testSource = CreateTestSource({}); + + TestSourceResult exeWithOverride( + "AppInstallerCliTest.TestExeInstaller"sv, + [](std::vector& matches, std::weak_ptr source) + { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); + auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); + + auto testPackage = TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::InitialOverrideArguments, "/silent /override" }, + { PackageVersionMetadata::InitialCustomSwitches, "--custom-flag" }, + }, + std::vector{ manifest3, manifest2, manifest }, + source); + for (auto& availablePackage : testPackage->Available) + { + availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + } + matches.emplace_back( + ResultMatch( + testPackage, + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + testSource->AddResult(exeWithOverride); + + OverrideForCompositeInstalledSource(context, testSource); + context.Args.AddArg(Execution::Args::Type::OutputFile, exportResultPath); + + ExportCommand exportCommand({}); + exportCommand.Execute(context); + INFO(exportOutput.str()); + + const auto& exportedCollection = context.Get(); + REQUIRE(exportedCollection.Sources.size() == 1); + + const auto& exportedPackages = exportedCollection.Sources[0].Packages; + REQUIRE(exportedPackages.size() == 1); + + const auto& pkg = exportedPackages[0]; + REQUIRE(pkg.Id == "AppInstallerCliTest.TestExeInstaller"); + REQUIRE(pkg.InitialOverrideArgs == "/silent /override"); + REQUIRE(pkg.InitialCustomSwitches == "--custom-flag"); + + // Verify the values are in the exported JSON file + std::ifstream exportFile(exportResultPath.GetPath()); + Json::Value exportedJson; + exportFile >> exportedJson; + + const auto& jsonPackage = exportedJson["Sources"][0]["Packages"][0]; + REQUIRE(jsonPackage["InitialOverrideArguments"].asString() == "/silent /override"); + REQUIRE(jsonPackage["InitialCustomSwitches"].asString() == "--custom-flag"); +} diff --git a/src/AppInstallerCLITests/FileCache.cpp b/src/AppInstallerCLITests/FileCache.cpp index c8f458e400..47a1ac7e80 100644 --- a/src/AppInstallerCLITests/FileCache.cpp +++ b/src/AppInstallerCLITests/FileCache.cpp @@ -1,240 +1,240 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace AppInstaller::Caching; -using namespace AppInstaller::Utility; -using namespace TestCommon; - -struct TestFileCache -{ - struct UpstreamFileInfo - { - TestDataFile OriginalFile; - std::filesystem::path Offset; - std::filesystem::path UpstreamPath; - std::vector Contents; - SHA256::HashBuffer ContentHash; - }; - - TestFileCache(std::string identifier = {}, size_t upstreamCount = 1) - { - if (identifier.empty()) - { - identifier = ConvertToUTF8(CreateNewGuidNameWString()); - } - - std::vector upstreamStrings; - - for (size_t i = 0; i < upstreamCount; ++i) - { - UpstreamSources.emplace_back("TestFileCache"); - upstreamStrings.emplace_back(UpstreamSources.back().GetPath().u8string()); - } - - CachePtr = std::make_unique(FileCache::Type::Tests, std::move(identifier), std::move(upstreamStrings)); - } - - FileCache& Cache() { return *CachePtr; } - FileCache* operator->() { return CachePtr.get(); } - - UpstreamFileInfo PrepareUpstreamFile(const std::filesystem::path& testDataFile, const std::filesystem::path& offset = {}, size_t index = 0) - { - UpstreamFileInfo result{ testDataFile }; - - auto dataFilePath = result.OriginalFile.GetPath(); - - result.Offset = offset.empty() ? dataFilePath.filename() : offset; - result.UpstreamPath = UpstreamSources[index].GetPath() / result.Offset; - - std::filesystem::copy_file(dataFilePath, result.UpstreamPath); - - std::ifstream fileStream{ dataFilePath, std::ios_base::in | std::ios_base::binary }; - result.Contents = ReadEntireStreamAsByteArray(fileStream); - - result.ContentHash = SHA256::ComputeHash(result.Contents); - - return result; - } - - std::filesystem::path GetCacheFilePath(const UpstreamFileInfo& upstreamFileInfo) - { - std::filesystem::path result = CachePtr->GetDetails().GetCachePath() / upstreamFileInfo.Offset; - std::filesystem::create_directories(result.parent_path()); - return result; - } - - std::unique_ptr GetFile(const UpstreamFileInfo& upstreamFileInfo) - { - return CachePtr->GetFile(upstreamFileInfo.Offset, upstreamFileInfo.ContentHash); - } - - void RequireCachedFile(const UpstreamFileInfo& upstreamFileInfo) - { - std::filesystem::path cachedFilePath = GetCacheFilePath(upstreamFileInfo); - REQUIRE(std::filesystem::is_regular_file(cachedFilePath)); - REQUIRE(SHA256::AreEqual(upstreamFileInfo.ContentHash, SHA256::ComputeHashFromFile(cachedFilePath))); - } - - std::unique_ptr CachePtr; - std::vector UpstreamSources; -}; - -TEST_CASE("FileCache_TypeLocationsDiffer", "[file_cache]") -{ - std::string identifier = "identifier"; - std::string identifier2 = "identifier2"; - - REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath()); - REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath()); - REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath()); - - REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV1_Manifest, identifier2, {}).GetDetails().GetCachePath()); - REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_Manifest, identifier2, {}).GetDetails().GetCachePath()); - REQUIRE(FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier2, {}).GetDetails().GetCachePath()); -} - -TEST_CASE("FileCache_TypeLocationsSame", "[file_cache]") -{ - std::string identifier = "identifier"; - std::string source = "source"; - - REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV1_Manifest, identifier, { source }).GetDetails().GetCachePath()); - REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV2_Manifest, identifier, { source }).GetDetails().GetCachePath()); - REQUIRE(FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, { source }).GetDetails().GetCachePath()); -} - -TEST_CASE("FileCache_NoCachedFile", "[file_cache]") -{ - TestFileCache testFileCache; - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-SystemReferenceComplex.yaml"); - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); - - testFileCache.RequireCachedFile(sourceFile); -} - -TEST_CASE("FileCache_CachedFileIsDirectory", "[file_cache]") -{ - TestFileCache testFileCache; - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good.yaml"); - std::filesystem::create_directories(testFileCache.GetCacheFilePath(sourceFile)); - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); - - testFileCache.RequireCachedFile(sourceFile); -} - -TEST_CASE("FileCache_CachedFileGoodHash", "[file_cache]") -{ - TestFileCache testFileCache; - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto sourceFile = testFileCache.PrepareUpstreamFile("InstallFlowTest_MSStore.yaml"); - std::filesystem::copy_file(sourceFile.OriginalFile, testFileCache.GetCacheFilePath(sourceFile)); - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); - - testFileCache.RequireCachedFile(sourceFile); -} - -TEST_CASE("FileCache_CachedFileBadHash", "[file_cache]") -{ - TestFileCache testFileCache; - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto sourceFile = testFileCache.PrepareUpstreamFile("ManifestV1-MultiFile-Version.yaml"); - std::filesystem::copy_file(TestDataFile("Manifest-Bad-ProductCodeOnMSIX.yaml"), testFileCache.GetCacheFilePath(sourceFile)); - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); - - testFileCache.RequireCachedFile(sourceFile); -} - -TEST_CASE("FileCache_CachedFileLockedExclusive", "[file_cache]") -{ - TestFileCache testFileCache; - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto sourceFile = testFileCache.PrepareUpstreamFile("ManifestV1-MultiFile-Installer.yaml"); - TestDataFile wrongFileOriginal = TestDataFile("Manifest-Bad-InvalidLocale.yaml"); - std::filesystem::path wrongFilePath = testFileCache.GetCacheFilePath(sourceFile); - std::filesystem::copy_file(wrongFileOriginal, wrongFilePath); - wil::unique_handle exclusiveFileHandle{ CreateFileW(wrongFilePath.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) }; - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); -} - -TEST_CASE("FileCache_FirstUpstreamDoesNotHaveFile", "[file_cache]") -{ - TestFileCache testFileCache({}, 2); - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-MultiLocale.yaml", {}, 1); - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); - - testFileCache.RequireCachedFile(sourceFile); -} - -TEST_CASE("FileCache_FirstUpstreamHasBadHash", "[file_cache]") -{ - TestFileCache testFileCache({}, 2); - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto badFile = testFileCache.PrepareUpstreamFile("Manifest-Bad-VersionMissing.yaml", {}, 0); - auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-MultiLocale.yaml", {}, 1); - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); - - testFileCache.RequireCachedFile(sourceFile); -} - -TEST_CASE("FileCache_NoUpstreamSources", "[file_cache]") -{ - TestFileCache testFileCache("", 0); - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - REQUIRE_THROWS_HR(testFileCache->GetFile("any_file", SHA256::ComputeHash("garbage")), E_NOT_SET); -} - -TEST_CASE("FileCache_PathTooLong", "[file_cache]") -{ - TestFileCache testFileCache(std::string(260, 'a')); - INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); - - auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-SystemReferenceComplex.yaml"); - - auto cachedStream = testFileCache.GetFile(sourceFile); - - REQUIRE(cachedStream); - REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace AppInstaller::Caching; +using namespace AppInstaller::Utility; +using namespace TestCommon; + +struct TestFileCache +{ + struct UpstreamFileInfo + { + TestDataFile OriginalFile; + std::filesystem::path Offset; + std::filesystem::path UpstreamPath; + std::vector Contents; + SHA256::HashBuffer ContentHash; + }; + + TestFileCache(std::string identifier = {}, size_t upstreamCount = 1) + { + if (identifier.empty()) + { + identifier = ConvertToUTF8(CreateNewGuidNameWString()); + } + + std::vector upstreamStrings; + + for (size_t i = 0; i < upstreamCount; ++i) + { + UpstreamSources.emplace_back("TestFileCache"); + upstreamStrings.emplace_back(UpstreamSources.back().GetPath().u8string()); + } + + CachePtr = std::make_unique(FileCache::Type::Tests, std::move(identifier), std::move(upstreamStrings)); + } + + FileCache& Cache() { return *CachePtr; } + FileCache* operator->() { return CachePtr.get(); } + + UpstreamFileInfo PrepareUpstreamFile(const std::filesystem::path& testDataFile, const std::filesystem::path& offset = {}, size_t index = 0) + { + UpstreamFileInfo result{ testDataFile }; + + auto dataFilePath = result.OriginalFile.GetPath(); + + result.Offset = offset.empty() ? dataFilePath.filename() : offset; + result.UpstreamPath = UpstreamSources[index].GetPath() / result.Offset; + + std::filesystem::copy_file(dataFilePath, result.UpstreamPath); + + std::ifstream fileStream{ dataFilePath, std::ios_base::in | std::ios_base::binary }; + result.Contents = ReadEntireStreamAsByteArray(fileStream); + + result.ContentHash = SHA256::ComputeHash(result.Contents); + + return result; + } + + std::filesystem::path GetCacheFilePath(const UpstreamFileInfo& upstreamFileInfo) + { + std::filesystem::path result = CachePtr->GetDetails().GetCachePath() / upstreamFileInfo.Offset; + std::filesystem::create_directories(result.parent_path()); + return result; + } + + std::unique_ptr GetFile(const UpstreamFileInfo& upstreamFileInfo) + { + return CachePtr->GetFile(upstreamFileInfo.Offset, upstreamFileInfo.ContentHash); + } + + void RequireCachedFile(const UpstreamFileInfo& upstreamFileInfo) + { + std::filesystem::path cachedFilePath = GetCacheFilePath(upstreamFileInfo); + REQUIRE(std::filesystem::is_regular_file(cachedFilePath)); + REQUIRE(SHA256::AreEqual(upstreamFileInfo.ContentHash, SHA256::ComputeHashFromFile(cachedFilePath))); + } + + std::unique_ptr CachePtr; + std::vector UpstreamSources; +}; + +TEST_CASE("FileCache_TypeLocationsDiffer", "[file_cache]") +{ + std::string identifier = "identifier"; + std::string identifier2 = "identifier2"; + + REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath()); + REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath()); + REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath()); + + REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV1_Manifest, identifier2, {}).GetDetails().GetCachePath()); + REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_Manifest, identifier2, {}).GetDetails().GetCachePath()); + REQUIRE(FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath() != FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier2, {}).GetDetails().GetCachePath()); +} + +TEST_CASE("FileCache_TypeLocationsSame", "[file_cache]") +{ + std::string identifier = "identifier"; + std::string source = "source"; + + REQUIRE(FileCache(FileCache::Type::IndexV1_Manifest, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV1_Manifest, identifier, { source }).GetDetails().GetCachePath()); + REQUIRE(FileCache(FileCache::Type::IndexV2_Manifest, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV2_Manifest, identifier, { source }).GetDetails().GetCachePath()); + REQUIRE(FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, {}).GetDetails().GetCachePath() == FileCache(FileCache::Type::IndexV2_PackageVersionData, identifier, { source }).GetDetails().GetCachePath()); +} + +TEST_CASE("FileCache_NoCachedFile", "[file_cache]") +{ + TestFileCache testFileCache; + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-SystemReferenceComplex.yaml"); + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); + + testFileCache.RequireCachedFile(sourceFile); +} + +TEST_CASE("FileCache_CachedFileIsDirectory", "[file_cache]") +{ + TestFileCache testFileCache; + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good.yaml"); + std::filesystem::create_directories(testFileCache.GetCacheFilePath(sourceFile)); + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); + + testFileCache.RequireCachedFile(sourceFile); +} + +TEST_CASE("FileCache_CachedFileGoodHash", "[file_cache]") +{ + TestFileCache testFileCache; + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto sourceFile = testFileCache.PrepareUpstreamFile("InstallFlowTest_MSStore.yaml"); + std::filesystem::copy_file(sourceFile.OriginalFile, testFileCache.GetCacheFilePath(sourceFile)); + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); + + testFileCache.RequireCachedFile(sourceFile); +} + +TEST_CASE("FileCache_CachedFileBadHash", "[file_cache]") +{ + TestFileCache testFileCache; + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto sourceFile = testFileCache.PrepareUpstreamFile("ManifestV1-MultiFile-Version.yaml"); + std::filesystem::copy_file(TestDataFile("Manifest-Bad-ProductCodeOnMSIX.yaml"), testFileCache.GetCacheFilePath(sourceFile)); + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); + + testFileCache.RequireCachedFile(sourceFile); +} + +TEST_CASE("FileCache_CachedFileLockedExclusive", "[file_cache]") +{ + TestFileCache testFileCache; + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto sourceFile = testFileCache.PrepareUpstreamFile("ManifestV1-MultiFile-Installer.yaml"); + TestDataFile wrongFileOriginal = TestDataFile("Manifest-Bad-InvalidLocale.yaml"); + std::filesystem::path wrongFilePath = testFileCache.GetCacheFilePath(sourceFile); + std::filesystem::copy_file(wrongFileOriginal, wrongFilePath); + wil::unique_handle exclusiveFileHandle{ CreateFileW(wrongFilePath.c_str(), GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL) }; + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); +} + +TEST_CASE("FileCache_FirstUpstreamDoesNotHaveFile", "[file_cache]") +{ + TestFileCache testFileCache({}, 2); + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-MultiLocale.yaml", {}, 1); + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); + + testFileCache.RequireCachedFile(sourceFile); +} + +TEST_CASE("FileCache_FirstUpstreamHasBadHash", "[file_cache]") +{ + TestFileCache testFileCache({}, 2); + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto badFile = testFileCache.PrepareUpstreamFile("Manifest-Bad-VersionMissing.yaml", {}, 0); + auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-MultiLocale.yaml", {}, 1); + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); + + testFileCache.RequireCachedFile(sourceFile); +} + +TEST_CASE("FileCache_NoUpstreamSources", "[file_cache]") +{ + TestFileCache testFileCache("", 0); + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + REQUIRE_THROWS_HR(testFileCache->GetFile("any_file", SHA256::ComputeHash("garbage")), E_NOT_SET); +} + +TEST_CASE("FileCache_PathTooLong", "[file_cache]") +{ + TestFileCache testFileCache(std::string(260, 'a')); + INFO("Cache location: " << testFileCache->GetDetails().GetCachePath().u8string()); + + auto sourceFile = testFileCache.PrepareUpstreamFile("Manifest-Good-SystemReferenceComplex.yaml"); + + auto cachedStream = testFileCache.GetFile(sourceFile); + + REQUIRE(cachedStream); + REQUIRE(SHA256::AreEqual(sourceFile.ContentHash, SHA256::ComputeHash(ReadEntireStreamAsByteArray(*cachedStream)))); +} diff --git a/src/AppInstallerCLITests/FileLogger.cpp b/src/AppInstallerCLITests/FileLogger.cpp index f2ff9c7ab8..8d82b15093 100644 --- a/src/AppInstallerCLITests/FileLogger.cpp +++ b/src/AppInstallerCLITests/FileLogger.cpp @@ -1,245 +1,245 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace AppInstaller::Logging; -using namespace AppInstaller::Utility; -using namespace TestCommon; - - -std::string GetHeaderString() -{ - return "TIME [CHAN] Header Message"; -} - -std::string GetLargeString() -{ - return "[===|Clearly defined start to large string|===]\r\n" - "While this string does not need to be particularly unique, it is still good if it is not easily duplicated by any other random set of data.\r\n" - "It should also end in a character that is not used in any other way within these tests, so please don't include that character when writing tests.\r\n" - "That character is &"; -} - -namespace -{ -#define WINGET_DEFINE_STRING_ENUM(_enum_,_value_) constexpr std::string_view _enum_##_##_value_ = #_value_##sv - - WINGET_DEFINE_STRING_ENUM(TagState, Unset); - WINGET_DEFINE_STRING_ENUM(TagState, SetAtStart); - WINGET_DEFINE_STRING_ENUM(TagState, SetAfterLogging); - - WINGET_DEFINE_STRING_ENUM(MaximumSizeState, Zero); - WINGET_DEFINE_STRING_ENUM(MaximumSizeState, SmallerThanLargeString); - WINGET_DEFINE_STRING_ENUM(MaximumSizeState, EqualToLargeString); - WINGET_DEFINE_STRING_ENUM(MaximumSizeState, SlightlyLargerThanLargeString); - WINGET_DEFINE_STRING_ENUM(MaximumSizeState, MuchLargerThanLargeString); - - constexpr std::string_view WrapIndicator = "--- log file has wrapped ---"sv; - // The amount of extra size that is allowed (total indicator size + newline + newlines from test strings) - constexpr size_t ExtraAllowedSize = 72; - - constexpr size_t NewLineCharacterCount = 2; - constexpr size_t SmallDifferenceSize = 10; - constexpr AppInstaller::Logging::Channel DefaultChannel = AppInstaller::Logging::Channel::Core; - constexpr AppInstaller::Logging::Level DefaultLevel = AppInstaller::Logging::Level::Info; - - void ValidateFileContents(const std::filesystem::path& file, const std::vector& expectedContents, size_t maximumSize) - { - std::ifstream fileStream{ file, std::ios::binary }; - auto fileContents = ReadEntireStream(fileStream); - std::string_view fileContentsView = fileContents; - - std::string fileContentsCopy = fileContents; - FindAndReplace(fileContentsCopy, "\r", "\\r"); - FindAndReplace(fileContentsCopy, "\n", "\\n"); - INFO("File contents:\n" << fileContentsCopy); - - if (maximumSize) - { - REQUIRE(maximumSize + ExtraAllowedSize >= fileContents.size()); - } - - size_t currentPosition = 0; - for (std::string_view expectedContent : expectedContents) - { - REQUIRE(currentPosition < fileContents.size()); - - if (expectedContent == WrapIndicator) - { - auto endLinePosition = fileContentsView.find('\n', currentPosition); - REQUIRE(endLinePosition != -1); - REQUIRE(endLinePosition >= expectedContent.size() + NewLineCharacterCount); - auto actualContent = fileContentsView.substr(endLinePosition + 1 - expectedContent.size() - NewLineCharacterCount, expectedContent.size()); - REQUIRE(expectedContent == actualContent); - currentPosition = endLinePosition + 1; - } - else - { - auto actualContent = fileContentsView.substr(currentPosition, expectedContent.size()); - REQUIRE(expectedContent == actualContent); - currentPosition += expectedContent.size() + NewLineCharacterCount; - } - } - } - - void FileLogger_MaximumSize_Test(std::string_view tagState, std::string_view sizeState) - { - auto headerString = GetHeaderString(); - auto largeString = GetLargeString(); - - // Determine maximum size - size_t maximumSize = 0; - - if (sizeState == MaximumSizeState_SmallerThanLargeString) - { - maximumSize = largeString.size() - SmallDifferenceSize; - } - else if (sizeState == MaximumSizeState_EqualToLargeString) - { - maximumSize = largeString.size(); - } - else if (sizeState == MaximumSizeState_SlightlyLargerThanLargeString) - { - maximumSize = largeString.size() + SmallDifferenceSize; - } - else if (sizeState == MaximumSizeState_MuchLargerThanLargeString) - { - maximumSize = largeString.size() * 2; - } - - INFO("Tag State: " << tagState << ", Size State: " << sizeState << "[" << maximumSize << "]"); - - TempFile tempFile{ "FileLogger_MaximumSize", ".log" }; - FileLogger logger{ tempFile }; - - INFO("File: " << tempFile.GetPath().u8string()); - - logger.SetMaximumSize(wil::safe_cast(maximumSize)); - - // Set tag and log strings - size_t tagPosition = 0; - if (tagState == TagState_SetAtStart) - { - logger.SetTag(Tag::HeadersComplete); - } - - logger.WriteDirect(DefaultChannel, DefaultLevel, headerString); - - if (tagState == TagState_SetAfterLogging) - { - logger.SetTag(Tag::HeadersComplete); - tagPosition = headerString.size() + NewLineCharacterCount; - } - - // Due to text output in the logger, log with \n only - std::string largeStringWithoutCarriageReturn = largeString; - FindAndReplace(largeStringWithoutCarriageReturn, "\r\n", "\n"); - - logger.WriteDirect(DefaultChannel, DefaultLevel, largeStringWithoutCarriageReturn); - - // Calculate current state - size_t maximumAvailableSpace = std::numeric_limits::max(); - size_t currentAvailableSpace = std::numeric_limits::max(); - if (maximumSize) - { - maximumAvailableSpace = maximumSize - tagPosition; - currentAvailableSpace = maximumSize - headerString.size() - NewLineCharacterCount; - } - - bool shouldWrap = largeString.size() > currentAvailableSpace; - - INFO("Maximum Available: " << maximumAvailableSpace << ", Current Available: " << currentAvailableSpace << ", ShouldWrap: " << shouldWrap); - - std::vector expectedFileContents; - - if (tagPosition || !shouldWrap) - { - expectedFileContents.push_back(headerString); - } - - if (shouldWrap) - { - expectedFileContents.push_back(WrapIndicator); - } - - std::string_view largeStringView = largeString; - expectedFileContents.push_back(largeStringView.substr(0, std::min(largeString.size(), maximumAvailableSpace))); - - ValidateFileContents(tempFile, expectedFileContents, maximumSize); - - // Log again - INFO("Second time logging large string"); - logger.WriteDirect(DefaultChannel, DefaultLevel, largeStringWithoutCarriageReturn); - - // The maximum size is twice the large log, so anything with a limit will wrap - shouldWrap = maximumSize != 0; - - expectedFileContents.clear(); - - if (tagPosition || !shouldWrap) - { - expectedFileContents.push_back(headerString); - } - - if (shouldWrap) - { - expectedFileContents.push_back(WrapIndicator); - } - else - { - expectedFileContents.push_back(largeStringView); - } - - expectedFileContents.push_back(largeStringView.substr(0, std::min(largeString.size(), maximumAvailableSpace))); - - ValidateFileContents(tempFile, expectedFileContents, maximumSize); - } -} - -TEST_CASE("FileLogger_MaximumSize", "[logging]") -{ - auto tagState = GENERATE(TagState_Unset, TagState_SetAtStart, TagState_SetAfterLogging); - auto sizeState = GENERATE(MaximumSizeState_Zero, MaximumSizeState_SmallerThanLargeString, MaximumSizeState_EqualToLargeString, MaximumSizeState_SlightlyLargerThanLargeString, MaximumSizeState_MuchLargerThanLargeString); - FileLogger_MaximumSize_Test(tagState, sizeState); -} - -TEST_CASE("FileLogger_MaximumSize_ManyWraps", "[logging]") -{ - TempFile tempFile{ "FileLogger_ManyWraps", ".log" }; - FileLogger logger{ tempFile }; - - INFO("File: " << tempFile.GetPath().u8string()); - - size_t maximumSize = 1000; - logger.SetMaximumSize(static_cast(maximumSize)); - - std::string header = GetHeaderString(); - header += " !Now with more header!"; - std::string largeString = "[*=INIT=*]Now we just need another few dozen characters, which shouldn't be that hard to get. Wow, made it already."; - std::string_view largeStringView = largeString; - size_t initSize = 10; - - logger.WriteDirect(DefaultChannel, DefaultLevel, header); - logger.SetTag(Tag::HeadersComplete); - - // Use the default seed value as we want arbitrary but reproducible results - std::default_random_engine randomEngine; - std::uniform_int_distribution<> sizeDistribution(static_cast(initSize), 100); - - // We should expect ~500 wraps on average - for (size_t i = 0; i < 9999; ++i) - { - logger.WriteDirect(DefaultChannel, DefaultLevel, largeStringView.substr(0, sizeDistribution(randomEngine))); - } - - // We want the header to be preserved, followed by the wrap indicator, and at a minimum we should see the first few characters in the log string - std::vector expectedFileContents; - expectedFileContents.push_back(header); - expectedFileContents.push_back(WrapIndicator); - expectedFileContents.push_back(largeStringView.substr(0, initSize)); - - ValidateFileContents(tempFile, expectedFileContents, maximumSize); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace AppInstaller::Logging; +using namespace AppInstaller::Utility; +using namespace TestCommon; + + +std::string GetHeaderString() +{ + return "TIME [CHAN] Header Message"; +} + +std::string GetLargeString() +{ + return "[===|Clearly defined start to large string|===]\r\n" + "While this string does not need to be particularly unique, it is still good if it is not easily duplicated by any other random set of data.\r\n" + "It should also end in a character that is not used in any other way within these tests, so please don't include that character when writing tests.\r\n" + "That character is &"; +} + +namespace +{ +#define WINGET_DEFINE_STRING_ENUM(_enum_,_value_) constexpr std::string_view _enum_##_##_value_ = #_value_##sv + + WINGET_DEFINE_STRING_ENUM(TagState, Unset); + WINGET_DEFINE_STRING_ENUM(TagState, SetAtStart); + WINGET_DEFINE_STRING_ENUM(TagState, SetAfterLogging); + + WINGET_DEFINE_STRING_ENUM(MaximumSizeState, Zero); + WINGET_DEFINE_STRING_ENUM(MaximumSizeState, SmallerThanLargeString); + WINGET_DEFINE_STRING_ENUM(MaximumSizeState, EqualToLargeString); + WINGET_DEFINE_STRING_ENUM(MaximumSizeState, SlightlyLargerThanLargeString); + WINGET_DEFINE_STRING_ENUM(MaximumSizeState, MuchLargerThanLargeString); + + constexpr std::string_view WrapIndicator = "--- log file has wrapped ---"sv; + // The amount of extra size that is allowed (total indicator size + newline + newlines from test strings) + constexpr size_t ExtraAllowedSize = 72; + + constexpr size_t NewLineCharacterCount = 2; + constexpr size_t SmallDifferenceSize = 10; + constexpr AppInstaller::Logging::Channel DefaultChannel = AppInstaller::Logging::Channel::Core; + constexpr AppInstaller::Logging::Level DefaultLevel = AppInstaller::Logging::Level::Info; + + void ValidateFileContents(const std::filesystem::path& file, const std::vector& expectedContents, size_t maximumSize) + { + std::ifstream fileStream{ file, std::ios::binary }; + auto fileContents = ReadEntireStream(fileStream); + std::string_view fileContentsView = fileContents; + + std::string fileContentsCopy = fileContents; + FindAndReplace(fileContentsCopy, "\r", "\\r"); + FindAndReplace(fileContentsCopy, "\n", "\\n"); + INFO("File contents:\n" << fileContentsCopy); + + if (maximumSize) + { + REQUIRE(maximumSize + ExtraAllowedSize >= fileContents.size()); + } + + size_t currentPosition = 0; + for (std::string_view expectedContent : expectedContents) + { + REQUIRE(currentPosition < fileContents.size()); + + if (expectedContent == WrapIndicator) + { + auto endLinePosition = fileContentsView.find('\n', currentPosition); + REQUIRE(endLinePosition != -1); + REQUIRE(endLinePosition >= expectedContent.size() + NewLineCharacterCount); + auto actualContent = fileContentsView.substr(endLinePosition + 1 - expectedContent.size() - NewLineCharacterCount, expectedContent.size()); + REQUIRE(expectedContent == actualContent); + currentPosition = endLinePosition + 1; + } + else + { + auto actualContent = fileContentsView.substr(currentPosition, expectedContent.size()); + REQUIRE(expectedContent == actualContent); + currentPosition += expectedContent.size() + NewLineCharacterCount; + } + } + } + + void FileLogger_MaximumSize_Test(std::string_view tagState, std::string_view sizeState) + { + auto headerString = GetHeaderString(); + auto largeString = GetLargeString(); + + // Determine maximum size + size_t maximumSize = 0; + + if (sizeState == MaximumSizeState_SmallerThanLargeString) + { + maximumSize = largeString.size() - SmallDifferenceSize; + } + else if (sizeState == MaximumSizeState_EqualToLargeString) + { + maximumSize = largeString.size(); + } + else if (sizeState == MaximumSizeState_SlightlyLargerThanLargeString) + { + maximumSize = largeString.size() + SmallDifferenceSize; + } + else if (sizeState == MaximumSizeState_MuchLargerThanLargeString) + { + maximumSize = largeString.size() * 2; + } + + INFO("Tag State: " << tagState << ", Size State: " << sizeState << "[" << maximumSize << "]"); + + TempFile tempFile{ "FileLogger_MaximumSize", ".log" }; + FileLogger logger{ tempFile }; + + INFO("File: " << tempFile.GetPath().u8string()); + + logger.SetMaximumSize(wil::safe_cast(maximumSize)); + + // Set tag and log strings + size_t tagPosition = 0; + if (tagState == TagState_SetAtStart) + { + logger.SetTag(Tag::HeadersComplete); + } + + logger.WriteDirect(DefaultChannel, DefaultLevel, headerString); + + if (tagState == TagState_SetAfterLogging) + { + logger.SetTag(Tag::HeadersComplete); + tagPosition = headerString.size() + NewLineCharacterCount; + } + + // Due to text output in the logger, log with \n only + std::string largeStringWithoutCarriageReturn = largeString; + FindAndReplace(largeStringWithoutCarriageReturn, "\r\n", "\n"); + + logger.WriteDirect(DefaultChannel, DefaultLevel, largeStringWithoutCarriageReturn); + + // Calculate current state + size_t maximumAvailableSpace = std::numeric_limits::max(); + size_t currentAvailableSpace = std::numeric_limits::max(); + if (maximumSize) + { + maximumAvailableSpace = maximumSize - tagPosition; + currentAvailableSpace = maximumSize - headerString.size() - NewLineCharacterCount; + } + + bool shouldWrap = largeString.size() > currentAvailableSpace; + + INFO("Maximum Available: " << maximumAvailableSpace << ", Current Available: " << currentAvailableSpace << ", ShouldWrap: " << shouldWrap); + + std::vector expectedFileContents; + + if (tagPosition || !shouldWrap) + { + expectedFileContents.push_back(headerString); + } + + if (shouldWrap) + { + expectedFileContents.push_back(WrapIndicator); + } + + std::string_view largeStringView = largeString; + expectedFileContents.push_back(largeStringView.substr(0, std::min(largeString.size(), maximumAvailableSpace))); + + ValidateFileContents(tempFile, expectedFileContents, maximumSize); + + // Log again + INFO("Second time logging large string"); + logger.WriteDirect(DefaultChannel, DefaultLevel, largeStringWithoutCarriageReturn); + + // The maximum size is twice the large log, so anything with a limit will wrap + shouldWrap = maximumSize != 0; + + expectedFileContents.clear(); + + if (tagPosition || !shouldWrap) + { + expectedFileContents.push_back(headerString); + } + + if (shouldWrap) + { + expectedFileContents.push_back(WrapIndicator); + } + else + { + expectedFileContents.push_back(largeStringView); + } + + expectedFileContents.push_back(largeStringView.substr(0, std::min(largeString.size(), maximumAvailableSpace))); + + ValidateFileContents(tempFile, expectedFileContents, maximumSize); + } +} + +TEST_CASE("FileLogger_MaximumSize", "[logging]") +{ + auto tagState = GENERATE(TagState_Unset, TagState_SetAtStart, TagState_SetAfterLogging); + auto sizeState = GENERATE(MaximumSizeState_Zero, MaximumSizeState_SmallerThanLargeString, MaximumSizeState_EqualToLargeString, MaximumSizeState_SlightlyLargerThanLargeString, MaximumSizeState_MuchLargerThanLargeString); + FileLogger_MaximumSize_Test(tagState, sizeState); +} + +TEST_CASE("FileLogger_MaximumSize_ManyWraps", "[logging]") +{ + TempFile tempFile{ "FileLogger_ManyWraps", ".log" }; + FileLogger logger{ tempFile }; + + INFO("File: " << tempFile.GetPath().u8string()); + + size_t maximumSize = 1000; + logger.SetMaximumSize(static_cast(maximumSize)); + + std::string header = GetHeaderString(); + header += " !Now with more header!"; + std::string largeString = "[*=INIT=*]Now we just need another few dozen characters, which shouldn't be that hard to get. Wow, made it already."; + std::string_view largeStringView = largeString; + size_t initSize = 10; + + logger.WriteDirect(DefaultChannel, DefaultLevel, header); + logger.SetTag(Tag::HeadersComplete); + + // Use the default seed value as we want arbitrary but reproducible results + std::default_random_engine randomEngine; + std::uniform_int_distribution<> sizeDistribution(static_cast(initSize), 100); + + // We should expect ~500 wraps on average + for (size_t i = 0; i < 9999; ++i) + { + logger.WriteDirect(DefaultChannel, DefaultLevel, largeStringView.substr(0, sizeDistribution(randomEngine))); + } + + // We want the header to be preserved, followed by the wrap indicator, and at a minimum we should see the first few characters in the log string + std::vector expectedFileContents; + expectedFileContents.push_back(header); + expectedFileContents.push_back(WrapIndicator); + expectedFileContents.push_back(largeStringView.substr(0, initSize)); + + ValidateFileContents(tempFile, expectedFileContents, maximumSize); +} diff --git a/src/AppInstallerCLITests/Filesystem.cpp b/src/AppInstallerCLITests/Filesystem.cpp index 59cf02b840..12684f19d5 100644 --- a/src/AppInstallerCLITests/Filesystem.cpp +++ b/src/AppInstallerCLITests/Filesystem.cpp @@ -1,494 +1,494 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include - -using namespace AppInstaller::Utility; -using namespace AppInstaller::Filesystem; -using namespace TestCommon; - -TEST_CASE("PathEscapesDirectory", "[filesystem]") -{ - std::string badRelativePath = "../../target.exe"; - std::string badRelativePath2 = "test/../../target.exe"; - std::string goodRelativePath = "target.exe"; - std::string goodRelativePath2 = "test/../test1/target.exe"; - - REQUIRE(PathEscapesBaseDirectory(badRelativePath)); - REQUIRE(PathEscapesBaseDirectory(badRelativePath2)); - REQUIRE_FALSE(PathEscapesBaseDirectory(goodRelativePath)); - REQUIRE_FALSE(PathEscapesBaseDirectory(goodRelativePath2)); -} - -TEST_CASE("VerifySymlink", "[filesystem]") -{ - TestCommon::TempDirectory tempDirectory("TempDirectory"); - const std::filesystem::path& basePath = tempDirectory.GetPath(); - - std::filesystem::path testFilePath = basePath / "testFile.txt"; - std::filesystem::path symlinkPath = basePath / "symlink.exe"; - - TestCommon::TempFile testFile(testFilePath); - std::ofstream file2(testFile, std::ofstream::out); - file2.close(); - - std::filesystem::create_symlink(testFile.GetPath(), symlinkPath); - - REQUIRE(SymlinkExists(symlinkPath)); - REQUIRE(VerifySymlink(symlinkPath, testFilePath)); - REQUIRE_FALSE(VerifySymlink(symlinkPath, "badPath")); - - // Verify case-insensitive comparison works (Windows paths are case-insensitive). - std::filesystem::path testFilePathUpperCase = basePath / "TESTFILE.TXT"; - REQUIRE(VerifySymlink(symlinkPath, testFilePathUpperCase)); - - std::filesystem::remove(testFilePath); - - // Ensure that symlink existence does not check the target - REQUIRE(SymlinkExists(symlinkPath)); - - std::filesystem::remove(symlinkPath); - - REQUIRE_FALSE(SymlinkExists(symlinkPath)); -} - -TEST_CASE("VerifyIsSameVolume", "[filesystem]") -{ - // Note: Pipeline build machine uses 'D:\' as the volume. - std::filesystem::path path1 = L"C:\\Program Files\\WinGet\\Packages"; - std::filesystem::path path2 = L"c:\\Users\\testUser\\AppData\\Local\\Microsoft\\WinGet\\Packages"; - std::filesystem::path path3 = L"localPath\\test\\folder"; - std::filesystem::path path4 = L"test\\folder"; - std::filesystem::path path5 = L"D:\\test\\folder"; - // path 6 dynamically generated below to be a nonexistent drive letter - std::filesystem::path path7 = L"d:\\randomFolder"; - // path 8 dynamically generated below to be a nonexistent drive letter with different case than path 6 - std::filesystem::path path9 = L"a"; - std::filesystem::path path10 = L"b"; - - // Dynamically find a drive letter that is not mounted on this machine so that - // GetVolumePathNameW will fail for it, making IsSameVolume return false. - // This avoids environment-dependent failures when drive letters like F:\ are mounted. - wchar_t nonExistentDriveLetter = L'\0'; - const DWORD driveMask = GetLogicalDrives(); - for (wchar_t c = L'Z'; c >= L'A'; c--) - { - if (!(driveMask & (1 << (c - L'A')))) - { - nonExistentDriveLetter = c; - break; - } - } - - REQUIRE(nonExistentDriveLetter != L'\0'); - - std::filesystem::path path6 = std::wstring(1, nonExistentDriveLetter) + L":\\test\\folder"; - std::filesystem::path path8 = std::wstring(1, towlower(nonExistentDriveLetter)) + L":\\randomFolder"; - - REQUIRE(IsSameVolume(path1, path2)); - if (IsSameVolume(path5, path5)) - { - REQUIRE(IsSameVolume(path5, path7)); - } - REQUIRE(IsSameVolume(path3, path4)); - REQUIRE(IsSameVolume(path9, path10)); - - REQUIRE_FALSE(IsSameVolume(path1, path5)); - REQUIRE_FALSE(IsSameVolume(path2, path5)); - REQUIRE_FALSE(IsSameVolume(path1, path6)); - REQUIRE_FALSE(IsSameVolume(path2, path6)); - REQUIRE_FALSE(IsSameVolume(path3, path6)); - REQUIRE_FALSE(IsSameVolume(path4, path6)); - REQUIRE_FALSE(IsSameVolume(path5, path6)); - REQUIRE_FALSE(IsSameVolume(path6, path8)); -} - -TEST_CASE("ReplaceCommonPathPrefix", "[filesystem]") -{ - std::filesystem::path prefix = "C:\\test1\\test2"; - std::string replacement = "%TEST%"; - - std::filesystem::path shouldReplace = "C:\\test1\\test2\\subdir1\\subdir2"; - REQUIRE(ReplaceCommonPathPrefix(shouldReplace, prefix, replacement)); - REQUIRE(shouldReplace.u8string() == (replacement + "\\subdir1\\subdir2")); - - std::filesystem::path shouldNotReplace = "C:\\test1\\test3\\subdir1\\subdir2"; - REQUIRE(!ReplaceCommonPathPrefix(shouldNotReplace, prefix, replacement)); - REQUIRE(shouldNotReplace.u8string() == "C:\\test1\\test3\\subdir1\\subdir2"); -} - -TEST_CASE("GetExecutablePathForProcess", "[filesystem]") -{ - std::filesystem::path thisExecutable = GetExecutablePathForProcess(GetCurrentProcess()); - REQUIRE(!thisExecutable.empty()); - REQUIRE(thisExecutable.is_absolute()); - REQUIRE(thisExecutable.has_filename()); - REQUIRE(thisExecutable.has_extension()); - REQUIRE(thisExecutable.filename() == L"AppInstallerCLITests.exe"); -} - -TEST_CASE("PathTree_InsertAndFind", "[filesystem][pathtree]") -{ - PathTree pathTree; - - std::filesystem::path path1 = L"C:\\test"; - std::filesystem::path path1sub = L"C:\\test\\sub"; - std::filesystem::path path2 = L"C:\\diff"; - std::filesystem::path path3 = L"D:\\test"; - - REQUIRE(nullptr == pathTree.Find(path1)); - pathTree.FindOrInsert(path1) = true; - - REQUIRE(nullptr != pathTree.Find(path1)); - REQUIRE(*pathTree.Find(path1)); - - REQUIRE(nullptr == pathTree.Find(path1sub)); - REQUIRE(nullptr == pathTree.Find(path2)); - REQUIRE(nullptr == pathTree.Find(path3)); -} - -TEST_CASE("PathTree_InsertAndFind_Negative", "[filesystem][pathtree]") -{ - PathTree pathTree; - pathTree.FindOrInsert(L"C:\\a\\aa\\aaa"); - - REQUIRE(nullptr == pathTree.Find({})); - REQUIRE_THROWS_HR(pathTree.FindOrInsert({}), E_INVALIDARG); -} - -size_t CountVisited(const PathTree& pathTree, const std::filesystem::path& path, std::function predicate) -{ - size_t result = 0; - pathTree.VisitIf(path, [&](const bool&) { ++result; }, predicate); - return result; -} - -TEST_CASE("PathTree_VisitIf_Count", "[filesystem][pathtree]") -{ - PathTree pathTree; - - pathTree.FindOrInsert(L"C:\\a\\aa\\aaa") = true; - pathTree.FindOrInsert(L"C:\\a\\aa\\bbb") = true; - pathTree.FindOrInsert(L"C:\\a\\aa\\ccc") = false; - pathTree.FindOrInsert(L"C:\\a\\aa") = true; - - pathTree.FindOrInsert(L"C:\\a\\bb\\aaa") = false; - pathTree.FindOrInsert(L"C:\\a\\bb\\bbb") = true; - pathTree.FindOrInsert(L"C:\\a\\bb\\ccc") = false; - pathTree.FindOrInsert(L"C:\\a\\bb") = true; - - pathTree.FindOrInsert(L"C:\\a\\cc\\aaa") = true; - pathTree.FindOrInsert(L"C:\\a\\cc\\bbb") = false; - pathTree.FindOrInsert(L"C:\\a\\cc\\ccc") = false; - pathTree.FindOrInsert(L"C:\\a\\cc") = false; - - pathTree.FindOrInsert(L"C:\\a") = true; - pathTree.FindOrInsert(L"C:\\b") = false; - pathTree.FindOrInsert(L"D:\\a") = false; - - auto always = [](const bool&) { return true; }; - auto never = [](const bool&) { return false; }; - auto if_input = [](const bool& b) { return b; }; - - REQUIRE(0 == CountVisited(pathTree, {}, always)); - - REQUIRE(15 == CountVisited(pathTree, L"C:\\", always)); - REQUIRE(2 == CountVisited(pathTree, L"D:\\", always)); - REQUIRE(0 == CountVisited(pathTree, L"E:\\", always)); - - REQUIRE(1 == CountVisited(pathTree, L"C:\\", never)); - REQUIRE(1 == CountVisited(pathTree, L"D:\\", never)); - REQUIRE(0 == CountVisited(pathTree, L"E:\\", never)); - - REQUIRE(7 == CountVisited(pathTree, L"C:\\", if_input)); - REQUIRE(6 == CountVisited(pathTree, L"C:\\a", if_input)); - REQUIRE(2 == CountVisited(pathTree, L"C:\\a\\cc", if_input)); - REQUIRE(1 == CountVisited(pathTree, L"D:\\", if_input)); - REQUIRE(0 == CountVisited(pathTree, L"E:\\", if_input)); -} - -TEST_CASE("PathTree_VisitIf_Correct", "[filesystem][pathtree]") -{ - PathTree> pathTree; - - pathTree.FindOrInsert(L"C:\\a\\aa\\aaa") = { true, true }; - pathTree.FindOrInsert(L"C:\\a\\aa\\bbb") = { true, true }; - pathTree.FindOrInsert(L"C:\\a\\aa\\ccc") = { false, false }; - pathTree.FindOrInsert(L"C:\\a\\aa") = { true, true }; - - pathTree.FindOrInsert(L"C:\\a\\bb\\aaa") = { false, false }; - pathTree.FindOrInsert(L"C:\\a\\bb\\bbb") = { true, true }; - pathTree.FindOrInsert(L"C:\\a\\bb\\ccc") = { false, false }; - pathTree.FindOrInsert(L"C:\\a\\bb") = { true, true }; - - pathTree.FindOrInsert(L"C:\\a\\cc\\aaa") = { true, true }; - pathTree.FindOrInsert(L"C:\\a\\cc\\bbb") = { false, false }; - pathTree.FindOrInsert(L"C:\\a\\cc\\ccc") = { false, false }; - pathTree.FindOrInsert(L"C:\\a\\cc") = { false, false }; - - pathTree.FindOrInsert(L"C:\\a") = { true, true }; - pathTree.FindOrInsert(L"C:\\b") = { false, false }; - pathTree.FindOrInsert(L"C:") = { true, false }; - - auto check_input = [](const std::pair& p) { REQUIRE(p.first); }; - auto if_input = [](const std::pair& p) { return p.second; }; - - pathTree.VisitIf(L"C:", check_input, if_input); -} - -TEST_CASE("WriteStringToFile", "[filesystem]") -{ - SECTION("Basic content") - { - TestCommon::TempDirectory tempDirectory{ "WriteStringToFile" }; - auto tempFile = tempDirectory.CreateTempFile("output", ".txt"); - wil::unique_hfile fileHandle{ CreateFileW(tempFile.GetPath().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; - REQUIRE(fileHandle); - - std::string content = "Hello, WinGet!"; - REQUIRE_NOTHROW(WriteStringToFile(fileHandle.get(), content)); - fileHandle.reset(); - - std::ifstream readBack{ tempFile.GetPath() }; - std::string result{ std::istreambuf_iterator(readBack), std::istreambuf_iterator() }; - REQUIRE(result == content); - } - - SECTION("Empty content") - { - TestCommon::TempDirectory tempDirectory{ "WriteStringToFile" }; - auto tempFile = tempDirectory.CreateTempFile("empty", ".txt"); - wil::unique_hfile fileHandle{ CreateFileW(tempFile.GetPath().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; - REQUIRE(fileHandle); - - REQUIRE_NOTHROW(WriteStringToFile(fileHandle.get(), "")); - fileHandle.reset(); - - std::ifstream readBack{ tempFile.GetPath() }; - std::string result{ std::istreambuf_iterator(readBack), std::istreambuf_iterator() }; - REQUIRE(result.empty()); - } - - SECTION("Large content") - { - TestCommon::TempDirectory tempDirectory{ "WriteStringToFile" }; - auto tempFile = tempDirectory.CreateTempFile("large", ".txt"); - wil::unique_hfile fileHandle{ CreateFileW(tempFile.GetPath().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; - REQUIRE(fileHandle); - - std::string content(1 << 20, 'x'); // 1 MiB of 'x' - REQUIRE_NOTHROW(WriteStringToFile(fileHandle.get(), content)); - fileHandle.reset(); - - std::ifstream readBack{ tempFile.GetPath() }; - std::string result{ std::istreambuf_iterator(readBack), std::istreambuf_iterator() }; - REQUIRE(result == content); - } -} - -TEST_CASE("GetFileInfoFor", "[filesystem]") -{ - TestCommon::TempDirectory tempDirectory{ "GetFileInfoFor" }; - - auto now = std::filesystem::file_time_type::clock::now(); - - std::this_thread::sleep_for(1s); - auto file1 = tempDirectory.CreateTempFile("c.txt"); - std::string file1Content = "File 1 Content!"; - std::ofstream{ file1 } << file1Content; - std::this_thread::sleep_for(1s); - auto file2 = tempDirectory.CreateTempFile("b.txt"); - std::string file2Content = "More Content! Better Content!"; - std::ofstream{ file2 } << file2Content; - std::this_thread::sleep_for(1s); - auto file3 = tempDirectory.CreateTempFile("a.txt"); - std::string file3Content = "Maybe less is better?"; - std::ofstream{ file3 } << file3Content; - - auto fileInfo = GetFileInfoFor(tempDirectory); - REQUIRE(3 == fileInfo.size()); - - // Sort with oldest first - std::sort(fileInfo.begin(), fileInfo.end(), [](const FileInfo& a, const FileInfo& b) { return a.LastWriteTime < b.LastWriteTime; }); - - REQUIRE(fileInfo[0].Path == file1.GetPath()); - REQUIRE(fileInfo[0].LastWriteTime > now); - REQUIRE(fileInfo[0].Size == file1Content.size()); - - REQUIRE(fileInfo[1].Path == file2.GetPath()); - REQUIRE(fileInfo[1].LastWriteTime > fileInfo[0].LastWriteTime); - REQUIRE(fileInfo[1].Size == file2Content.size()); - - REQUIRE(fileInfo[2].Path == file3.GetPath()); - REQUIRE(fileInfo[2].LastWriteTime > fileInfo[1].LastWriteTime); - REQUIRE(fileInfo[2].Size == file3Content.size()); -} - -void RequireFilePaths(const std::vector& files, std::initializer_list paths) -{ - REQUIRE(paths.size() == files.size()); - size_t i = 0; - for (const char* val : paths) - { - REQUIRE(val == files[i++].Path.u8string()); - } -} - -TEST_CASE("FilterToFilesExceedingLimits", "[filesystem]") -{ - auto now = std::filesystem::file_time_type::clock::now(); - - std::vector files - { - { "a", now, 42 }, - { "b", now - 32min, 45321 }, - { "c", now - 84min, 24567 }, - { "d", now - 4h, 876312 }, - { "e", now - 18h, 2908534 }, - { "f", now - 47h, 312 }, - { "g", now - 132h, 74321 }, - { "h", now - 4567h, 6573423 }, - }; - - // Give the sort inside FilterToFilesExceedingLimits something to do - std::shuffle(files.begin(), files.end(), std::mt19937{}); - FileLimits limits{}; - - SECTION("No limits") - { - FilterToFilesExceedingLimits(files, limits); - // Without limits, nothing exceeds them - REQUIRE(files.empty()); - } - SECTION("Age - 1h") - { - limits.Age = 1h; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e", "d", "c" }); - } - SECTION("Age - 2h") - { - limits.Age = 2h; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e", "d" }); - } - SECTION("Age - 24h") - { - limits.Age = 24h; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f" }); - } - SECTION("Age - 7d") - { - limits.Age = 7 * 24h; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h" }); - } - SECTION("Age - 365d") - { - limits.Age = 365 * 24h; - FilterToFilesExceedingLimits(files, limits); - REQUIRE(files.empty()); - } - SECTION("Size - 1MB") - { - limits.TotalSizeInMB = 1; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e" }); - } - SECTION("Size - 2MB") - { - limits.TotalSizeInMB = 2; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e" }); - } - SECTION("Size - 3MB") - { - limits.TotalSizeInMB = 3; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e" }); - } - SECTION("Size - 4MB") - { - limits.TotalSizeInMB = 4; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h" }); - } - SECTION("Size - 100MB") - { - limits.TotalSizeInMB = 100; - FilterToFilesExceedingLimits(files, limits); - REQUIRE(files.empty()); - } - SECTION("Count - 1") - { - limits.Count = 1; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e", "d", "c", "b" }); - } - SECTION("Count - 2") - { - limits.Count = 2; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e", "d", "c" }); - } - SECTION("Count - 4") - { - limits.Count = 4; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e" }); - } - SECTION("Count - 7") - { - limits.Count = 7; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h" }); - } - SECTION("Count - 8") - { - limits.Count = 8; - FilterToFilesExceedingLimits(files, limits); - REQUIRE(files.empty()); - } - SECTION("Count - 100") - { - limits.Count = 100; - FilterToFilesExceedingLimits(files, limits); - REQUIRE(files.empty()); - } - SECTION("Mix - 24h - 2MB - 4") - { - limits.Age = 24h; - limits.TotalSizeInMB = 2; - limits.Count = 4; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e" }); - } - SECTION("Mix - 2h - 2MB - 4") - { - limits.Age = 2h; - limits.TotalSizeInMB = 2; - limits.Count = 4; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e", "d" }); - } - SECTION("Mix - 24h - 1MB - 4") - { - limits.Age = 24h; - limits.TotalSizeInMB = 1; - limits.Count = 4; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e" }); - } - SECTION("Mix - 24h - 2MB - 2") - { - limits.Age = 24h; - limits.TotalSizeInMB = 2; - limits.Count = 2; - FilterToFilesExceedingLimits(files, limits); - RequireFilePaths(files, { "h", "g", "f", "e", "d", "c" }); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +using namespace AppInstaller::Utility; +using namespace AppInstaller::Filesystem; +using namespace TestCommon; + +TEST_CASE("PathEscapesDirectory", "[filesystem]") +{ + std::string badRelativePath = "../../target.exe"; + std::string badRelativePath2 = "test/../../target.exe"; + std::string goodRelativePath = "target.exe"; + std::string goodRelativePath2 = "test/../test1/target.exe"; + + REQUIRE(PathEscapesBaseDirectory(badRelativePath)); + REQUIRE(PathEscapesBaseDirectory(badRelativePath2)); + REQUIRE_FALSE(PathEscapesBaseDirectory(goodRelativePath)); + REQUIRE_FALSE(PathEscapesBaseDirectory(goodRelativePath2)); +} + +TEST_CASE("VerifySymlink", "[filesystem]") +{ + TestCommon::TempDirectory tempDirectory("TempDirectory"); + const std::filesystem::path& basePath = tempDirectory.GetPath(); + + std::filesystem::path testFilePath = basePath / "testFile.txt"; + std::filesystem::path symlinkPath = basePath / "symlink.exe"; + + TestCommon::TempFile testFile(testFilePath); + std::ofstream file2(testFile, std::ofstream::out); + file2.close(); + + std::filesystem::create_symlink(testFile.GetPath(), symlinkPath); + + REQUIRE(SymlinkExists(symlinkPath)); + REQUIRE(VerifySymlink(symlinkPath, testFilePath)); + REQUIRE_FALSE(VerifySymlink(symlinkPath, "badPath")); + + // Verify case-insensitive comparison works (Windows paths are case-insensitive). + std::filesystem::path testFilePathUpperCase = basePath / "TESTFILE.TXT"; + REQUIRE(VerifySymlink(symlinkPath, testFilePathUpperCase)); + + std::filesystem::remove(testFilePath); + + // Ensure that symlink existence does not check the target + REQUIRE(SymlinkExists(symlinkPath)); + + std::filesystem::remove(symlinkPath); + + REQUIRE_FALSE(SymlinkExists(symlinkPath)); +} + +TEST_CASE("VerifyIsSameVolume", "[filesystem]") +{ + // Note: Pipeline build machine uses 'D:\' as the volume. + std::filesystem::path path1 = L"C:\\Program Files\\WinGet\\Packages"; + std::filesystem::path path2 = L"c:\\Users\\testUser\\AppData\\Local\\Microsoft\\WinGet\\Packages"; + std::filesystem::path path3 = L"localPath\\test\\folder"; + std::filesystem::path path4 = L"test\\folder"; + std::filesystem::path path5 = L"D:\\test\\folder"; + // path 6 dynamically generated below to be a nonexistent drive letter + std::filesystem::path path7 = L"d:\\randomFolder"; + // path 8 dynamically generated below to be a nonexistent drive letter with different case than path 6 + std::filesystem::path path9 = L"a"; + std::filesystem::path path10 = L"b"; + + // Dynamically find a drive letter that is not mounted on this machine so that + // GetVolumePathNameW will fail for it, making IsSameVolume return false. + // This avoids environment-dependent failures when drive letters like F:\ are mounted. + wchar_t nonExistentDriveLetter = L'\0'; + const DWORD driveMask = GetLogicalDrives(); + for (wchar_t c = L'Z'; c >= L'A'; c--) + { + if (!(driveMask & (1 << (c - L'A')))) + { + nonExistentDriveLetter = c; + break; + } + } + + REQUIRE(nonExistentDriveLetter != L'\0'); + + std::filesystem::path path6 = std::wstring(1, nonExistentDriveLetter) + L":\\test\\folder"; + std::filesystem::path path8 = std::wstring(1, towlower(nonExistentDriveLetter)) + L":\\randomFolder"; + + REQUIRE(IsSameVolume(path1, path2)); + if (IsSameVolume(path5, path5)) + { + REQUIRE(IsSameVolume(path5, path7)); + } + REQUIRE(IsSameVolume(path3, path4)); + REQUIRE(IsSameVolume(path9, path10)); + + REQUIRE_FALSE(IsSameVolume(path1, path5)); + REQUIRE_FALSE(IsSameVolume(path2, path5)); + REQUIRE_FALSE(IsSameVolume(path1, path6)); + REQUIRE_FALSE(IsSameVolume(path2, path6)); + REQUIRE_FALSE(IsSameVolume(path3, path6)); + REQUIRE_FALSE(IsSameVolume(path4, path6)); + REQUIRE_FALSE(IsSameVolume(path5, path6)); + REQUIRE_FALSE(IsSameVolume(path6, path8)); +} + +TEST_CASE("ReplaceCommonPathPrefix", "[filesystem]") +{ + std::filesystem::path prefix = "C:\\test1\\test2"; + std::string replacement = "%TEST%"; + + std::filesystem::path shouldReplace = "C:\\test1\\test2\\subdir1\\subdir2"; + REQUIRE(ReplaceCommonPathPrefix(shouldReplace, prefix, replacement)); + REQUIRE(shouldReplace.u8string() == (replacement + "\\subdir1\\subdir2")); + + std::filesystem::path shouldNotReplace = "C:\\test1\\test3\\subdir1\\subdir2"; + REQUIRE(!ReplaceCommonPathPrefix(shouldNotReplace, prefix, replacement)); + REQUIRE(shouldNotReplace.u8string() == "C:\\test1\\test3\\subdir1\\subdir2"); +} + +TEST_CASE("GetExecutablePathForProcess", "[filesystem]") +{ + std::filesystem::path thisExecutable = GetExecutablePathForProcess(GetCurrentProcess()); + REQUIRE(!thisExecutable.empty()); + REQUIRE(thisExecutable.is_absolute()); + REQUIRE(thisExecutable.has_filename()); + REQUIRE(thisExecutable.has_extension()); + REQUIRE(thisExecutable.filename() == L"AppInstallerCLITests.exe"); +} + +TEST_CASE("PathTree_InsertAndFind", "[filesystem][pathtree]") +{ + PathTree pathTree; + + std::filesystem::path path1 = L"C:\\test"; + std::filesystem::path path1sub = L"C:\\test\\sub"; + std::filesystem::path path2 = L"C:\\diff"; + std::filesystem::path path3 = L"D:\\test"; + + REQUIRE(nullptr == pathTree.Find(path1)); + pathTree.FindOrInsert(path1) = true; + + REQUIRE(nullptr != pathTree.Find(path1)); + REQUIRE(*pathTree.Find(path1)); + + REQUIRE(nullptr == pathTree.Find(path1sub)); + REQUIRE(nullptr == pathTree.Find(path2)); + REQUIRE(nullptr == pathTree.Find(path3)); +} + +TEST_CASE("PathTree_InsertAndFind_Negative", "[filesystem][pathtree]") +{ + PathTree pathTree; + pathTree.FindOrInsert(L"C:\\a\\aa\\aaa"); + + REQUIRE(nullptr == pathTree.Find({})); + REQUIRE_THROWS_HR(pathTree.FindOrInsert({}), E_INVALIDARG); +} + +size_t CountVisited(const PathTree& pathTree, const std::filesystem::path& path, std::function predicate) +{ + size_t result = 0; + pathTree.VisitIf(path, [&](const bool&) { ++result; }, predicate); + return result; +} + +TEST_CASE("PathTree_VisitIf_Count", "[filesystem][pathtree]") +{ + PathTree pathTree; + + pathTree.FindOrInsert(L"C:\\a\\aa\\aaa") = true; + pathTree.FindOrInsert(L"C:\\a\\aa\\bbb") = true; + pathTree.FindOrInsert(L"C:\\a\\aa\\ccc") = false; + pathTree.FindOrInsert(L"C:\\a\\aa") = true; + + pathTree.FindOrInsert(L"C:\\a\\bb\\aaa") = false; + pathTree.FindOrInsert(L"C:\\a\\bb\\bbb") = true; + pathTree.FindOrInsert(L"C:\\a\\bb\\ccc") = false; + pathTree.FindOrInsert(L"C:\\a\\bb") = true; + + pathTree.FindOrInsert(L"C:\\a\\cc\\aaa") = true; + pathTree.FindOrInsert(L"C:\\a\\cc\\bbb") = false; + pathTree.FindOrInsert(L"C:\\a\\cc\\ccc") = false; + pathTree.FindOrInsert(L"C:\\a\\cc") = false; + + pathTree.FindOrInsert(L"C:\\a") = true; + pathTree.FindOrInsert(L"C:\\b") = false; + pathTree.FindOrInsert(L"D:\\a") = false; + + auto always = [](const bool&) { return true; }; + auto never = [](const bool&) { return false; }; + auto if_input = [](const bool& b) { return b; }; + + REQUIRE(0 == CountVisited(pathTree, {}, always)); + + REQUIRE(15 == CountVisited(pathTree, L"C:\\", always)); + REQUIRE(2 == CountVisited(pathTree, L"D:\\", always)); + REQUIRE(0 == CountVisited(pathTree, L"E:\\", always)); + + REQUIRE(1 == CountVisited(pathTree, L"C:\\", never)); + REQUIRE(1 == CountVisited(pathTree, L"D:\\", never)); + REQUIRE(0 == CountVisited(pathTree, L"E:\\", never)); + + REQUIRE(7 == CountVisited(pathTree, L"C:\\", if_input)); + REQUIRE(6 == CountVisited(pathTree, L"C:\\a", if_input)); + REQUIRE(2 == CountVisited(pathTree, L"C:\\a\\cc", if_input)); + REQUIRE(1 == CountVisited(pathTree, L"D:\\", if_input)); + REQUIRE(0 == CountVisited(pathTree, L"E:\\", if_input)); +} + +TEST_CASE("PathTree_VisitIf_Correct", "[filesystem][pathtree]") +{ + PathTree> pathTree; + + pathTree.FindOrInsert(L"C:\\a\\aa\\aaa") = { true, true }; + pathTree.FindOrInsert(L"C:\\a\\aa\\bbb") = { true, true }; + pathTree.FindOrInsert(L"C:\\a\\aa\\ccc") = { false, false }; + pathTree.FindOrInsert(L"C:\\a\\aa") = { true, true }; + + pathTree.FindOrInsert(L"C:\\a\\bb\\aaa") = { false, false }; + pathTree.FindOrInsert(L"C:\\a\\bb\\bbb") = { true, true }; + pathTree.FindOrInsert(L"C:\\a\\bb\\ccc") = { false, false }; + pathTree.FindOrInsert(L"C:\\a\\bb") = { true, true }; + + pathTree.FindOrInsert(L"C:\\a\\cc\\aaa") = { true, true }; + pathTree.FindOrInsert(L"C:\\a\\cc\\bbb") = { false, false }; + pathTree.FindOrInsert(L"C:\\a\\cc\\ccc") = { false, false }; + pathTree.FindOrInsert(L"C:\\a\\cc") = { false, false }; + + pathTree.FindOrInsert(L"C:\\a") = { true, true }; + pathTree.FindOrInsert(L"C:\\b") = { false, false }; + pathTree.FindOrInsert(L"C:") = { true, false }; + + auto check_input = [](const std::pair& p) { REQUIRE(p.first); }; + auto if_input = [](const std::pair& p) { return p.second; }; + + pathTree.VisitIf(L"C:", check_input, if_input); +} + +TEST_CASE("WriteStringToFile", "[filesystem]") +{ + SECTION("Basic content") + { + TestCommon::TempDirectory tempDirectory{ "WriteStringToFile" }; + auto tempFile = tempDirectory.CreateTempFile("output", ".txt"); + wil::unique_hfile fileHandle{ CreateFileW(tempFile.GetPath().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; + REQUIRE(fileHandle); + + std::string content = "Hello, WinGet!"; + REQUIRE_NOTHROW(WriteStringToFile(fileHandle.get(), content)); + fileHandle.reset(); + + std::ifstream readBack{ tempFile.GetPath() }; + std::string result{ std::istreambuf_iterator(readBack), std::istreambuf_iterator() }; + REQUIRE(result == content); + } + + SECTION("Empty content") + { + TestCommon::TempDirectory tempDirectory{ "WriteStringToFile" }; + auto tempFile = tempDirectory.CreateTempFile("empty", ".txt"); + wil::unique_hfile fileHandle{ CreateFileW(tempFile.GetPath().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; + REQUIRE(fileHandle); + + REQUIRE_NOTHROW(WriteStringToFile(fileHandle.get(), "")); + fileHandle.reset(); + + std::ifstream readBack{ tempFile.GetPath() }; + std::string result{ std::istreambuf_iterator(readBack), std::istreambuf_iterator() }; + REQUIRE(result.empty()); + } + + SECTION("Large content") + { + TestCommon::TempDirectory tempDirectory{ "WriteStringToFile" }; + auto tempFile = tempDirectory.CreateTempFile("large", ".txt"); + wil::unique_hfile fileHandle{ CreateFileW(tempFile.GetPath().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr) }; + REQUIRE(fileHandle); + + std::string content(1 << 20, 'x'); // 1 MiB of 'x' + REQUIRE_NOTHROW(WriteStringToFile(fileHandle.get(), content)); + fileHandle.reset(); + + std::ifstream readBack{ tempFile.GetPath() }; + std::string result{ std::istreambuf_iterator(readBack), std::istreambuf_iterator() }; + REQUIRE(result == content); + } +} + +TEST_CASE("GetFileInfoFor", "[filesystem]") +{ + TestCommon::TempDirectory tempDirectory{ "GetFileInfoFor" }; + + auto now = std::filesystem::file_time_type::clock::now(); + + std::this_thread::sleep_for(1s); + auto file1 = tempDirectory.CreateTempFile("c.txt"); + std::string file1Content = "File 1 Content!"; + std::ofstream{ file1 } << file1Content; + std::this_thread::sleep_for(1s); + auto file2 = tempDirectory.CreateTempFile("b.txt"); + std::string file2Content = "More Content! Better Content!"; + std::ofstream{ file2 } << file2Content; + std::this_thread::sleep_for(1s); + auto file3 = tempDirectory.CreateTempFile("a.txt"); + std::string file3Content = "Maybe less is better?"; + std::ofstream{ file3 } << file3Content; + + auto fileInfo = GetFileInfoFor(tempDirectory); + REQUIRE(3 == fileInfo.size()); + + // Sort with oldest first + std::sort(fileInfo.begin(), fileInfo.end(), [](const FileInfo& a, const FileInfo& b) { return a.LastWriteTime < b.LastWriteTime; }); + + REQUIRE(fileInfo[0].Path == file1.GetPath()); + REQUIRE(fileInfo[0].LastWriteTime > now); + REQUIRE(fileInfo[0].Size == file1Content.size()); + + REQUIRE(fileInfo[1].Path == file2.GetPath()); + REQUIRE(fileInfo[1].LastWriteTime > fileInfo[0].LastWriteTime); + REQUIRE(fileInfo[1].Size == file2Content.size()); + + REQUIRE(fileInfo[2].Path == file3.GetPath()); + REQUIRE(fileInfo[2].LastWriteTime > fileInfo[1].LastWriteTime); + REQUIRE(fileInfo[2].Size == file3Content.size()); +} + +void RequireFilePaths(const std::vector& files, std::initializer_list paths) +{ + REQUIRE(paths.size() == files.size()); + size_t i = 0; + for (const char* val : paths) + { + REQUIRE(val == files[i++].Path.u8string()); + } +} + +TEST_CASE("FilterToFilesExceedingLimits", "[filesystem]") +{ + auto now = std::filesystem::file_time_type::clock::now(); + + std::vector files + { + { "a", now, 42 }, + { "b", now - 32min, 45321 }, + { "c", now - 84min, 24567 }, + { "d", now - 4h, 876312 }, + { "e", now - 18h, 2908534 }, + { "f", now - 47h, 312 }, + { "g", now - 132h, 74321 }, + { "h", now - 4567h, 6573423 }, + }; + + // Give the sort inside FilterToFilesExceedingLimits something to do + std::shuffle(files.begin(), files.end(), std::mt19937{}); + FileLimits limits{}; + + SECTION("No limits") + { + FilterToFilesExceedingLimits(files, limits); + // Without limits, nothing exceeds them + REQUIRE(files.empty()); + } + SECTION("Age - 1h") + { + limits.Age = 1h; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e", "d", "c" }); + } + SECTION("Age - 2h") + { + limits.Age = 2h; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e", "d" }); + } + SECTION("Age - 24h") + { + limits.Age = 24h; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f" }); + } + SECTION("Age - 7d") + { + limits.Age = 7 * 24h; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h" }); + } + SECTION("Age - 365d") + { + limits.Age = 365 * 24h; + FilterToFilesExceedingLimits(files, limits); + REQUIRE(files.empty()); + } + SECTION("Size - 1MB") + { + limits.TotalSizeInMB = 1; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e" }); + } + SECTION("Size - 2MB") + { + limits.TotalSizeInMB = 2; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e" }); + } + SECTION("Size - 3MB") + { + limits.TotalSizeInMB = 3; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e" }); + } + SECTION("Size - 4MB") + { + limits.TotalSizeInMB = 4; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h" }); + } + SECTION("Size - 100MB") + { + limits.TotalSizeInMB = 100; + FilterToFilesExceedingLimits(files, limits); + REQUIRE(files.empty()); + } + SECTION("Count - 1") + { + limits.Count = 1; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e", "d", "c", "b" }); + } + SECTION("Count - 2") + { + limits.Count = 2; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e", "d", "c" }); + } + SECTION("Count - 4") + { + limits.Count = 4; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e" }); + } + SECTION("Count - 7") + { + limits.Count = 7; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h" }); + } + SECTION("Count - 8") + { + limits.Count = 8; + FilterToFilesExceedingLimits(files, limits); + REQUIRE(files.empty()); + } + SECTION("Count - 100") + { + limits.Count = 100; + FilterToFilesExceedingLimits(files, limits); + REQUIRE(files.empty()); + } + SECTION("Mix - 24h - 2MB - 4") + { + limits.Age = 24h; + limits.TotalSizeInMB = 2; + limits.Count = 4; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e" }); + } + SECTION("Mix - 2h - 2MB - 4") + { + limits.Age = 2h; + limits.TotalSizeInMB = 2; + limits.Count = 4; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e", "d" }); + } + SECTION("Mix - 24h - 1MB - 4") + { + limits.Age = 24h; + limits.TotalSizeInMB = 1; + limits.Count = 4; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e" }); + } + SECTION("Mix - 24h - 2MB - 2") + { + limits.Age = 24h; + limits.TotalSizeInMB = 2; + limits.Count = 2; + FilterToFilesExceedingLimits(files, limits); + RequireFilePaths(files, { "h", "g", "f", "e", "d", "c" }); + } +} diff --git a/src/AppInstallerCLITests/GroupPolicy.cpp b/src/AppInstallerCLITests/GroupPolicy.cpp index 28d1fdc961..9fba8a0d66 100644 --- a/src/AppInstallerCLITests/GroupPolicy.cpp +++ b/src/AppInstallerCLITests/GroupPolicy.cpp @@ -1,414 +1,414 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSettings.h" -#include "winget/GroupPolicy.h" -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Settings; -using namespace std::string_view_literals; - -namespace -{ - std::wstring GetSourceJson(std::wstring_view name, std::wstring_view arg, std::wstring_view type, std::wstring_view data, std::wstring_view identifier, std::wstring_view trustLevel, std::wstring_view isExplicit, std::wstring_view pinningConfig = {}) - { - std::wstringstream json; - json << L"{ \"Name\":\"" << name << L"\", \"Arg\":\"" << arg << L"\", \"Type\":\"" << type << L"\", \"Data\":\"" << data << L"\", \"Identifier\":\"" << identifier << L"\", \"TrustLevel\":" << trustLevel << L", \"Explicit\":" << isExplicit; - if (!pinningConfig.empty()) - { - json << L", \"CertificatePinning\":" << pinningConfig; - } - json << " }"; - return json.str(); - } -} - -TEST_CASE("GroupPolicy_NoPolicies", "[groupPolicy]") -{ - auto policiesKey = RegCreateVolatileTestRoot(); - GroupPolicy groupPolicy{ policiesKey.get() }; - - // Policies setting a value should be empty - REQUIRE(!groupPolicy.GetValue().has_value()); - REQUIRE(!groupPolicy.GetValue().has_value()); - REQUIRE(!groupPolicy.GetValue().has_value()); - - // Everything should be not configured - for (const auto& policy : TogglePolicy::GetAllPolicies()) - { - REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::NotConfigured); - } -} - -TEST_CASE("GroupPolicy_UpdateInterval", "[groupPolicy]") -{ - auto policiesKey = RegCreateVolatileTestRoot(); - - SECTION("Good value") - { - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 5); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(*policy == 5); - } - - SECTION("Wrong type") - { - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong"); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(!policy.has_value()); - } -} - - -TEST_CASE("GroupPolicy_UpdateInterval_OldName", "[groupPolicy]") -{ - auto policiesKey = RegCreateVolatileTestRoot(); - - SECTION("New name shadows old") - { - SECTION("When old is valid") - { - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 3); - } - SECTION("When old is invalid") - { - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, L"Invalid type"); - } - - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 1); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(*policy == 1); - } - - SECTION("Fallback to old name") - { - SECTION("When new name has invalid data") - { - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong type"); - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20); - GroupPolicy groupPolicy{ policiesKey.get() }; - - // We should not fall back on this case - auto policy = groupPolicy.GetValue(); - REQUIRE(!policy.has_value()); - } - SECTION("When new name is missing") - { - // Don't add the registry value with the new name - SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(*policy == 20); - } - } -} - -TEST_CASE("GroupPolicy_Sources", "[groupPolicy]") -{ - auto policiesKey = RegCreateVolatileTestRoot(); - - // Note that the following tests mix using Additional/Allowed sources policy. - SECTION("Single source") - { - // We can read single source correctly - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); - SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true"), REG_SZ); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->size() == 1); - REQUIRE(policy.value()[0].Name == "source-name"); - REQUIRE(policy.value()[0].Arg == "source-arg"); - REQUIRE(policy.value()[0].Type == "source-type"); - REQUIRE(policy.value()[0].Data == "source-data"); - REQUIRE(policy.value()[0].Identifier == "source-identifier"); - REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted"); - REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin"); - REQUIRE(policy.value()[0].Explicit == true); - } - SECTION("Missing field") - { - // A single missing field causes the source to not be read. - // "Type" is missing here. - std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":\"source_data\", \"Identifier\":\"source_identifier\" }"; - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); - SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->empty()); - } - SECTION("Invalid field") - { - // A single invalid field causes the source to not be read. - // "Data" is invalid as it is an object, not a string. - std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":{}, \"Type\":\"source_type\", \"Identifier\":\"source_identifier\" }"; - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); - SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->empty()); - } - SECTION("Invalid source JSON") - { - // An invalid source JSON causes the source to not be read. - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); - SetRegistryValue(additionalSourcesKey.get(), L"0", L"not a JSON", REG_SZ); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->empty()); - } - SECTION("Missing key") - { - // If the key does not exist we should not get anything. - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE_FALSE(policy.has_value()); - } - SECTION("Empty key") - { - // If the key is empty we should get an empty list. - // Note that the policy editor doesn't actually create empty keys. - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->empty()); - } - SECTION("Valid list") - { - // We should be able to read multiple values. - // No specific order is required, but it will likely be the same. - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); - SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"None\"]", L"true"), REG_SZ); - SetRegistryValue(additionalSourcesKey.get(), L"1", GetSourceJson(L"s1-name", L"s1-arg", L"s1-type", L"s1-data", L"s1-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ); - SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->size() == 3); - - REQUIRE(policy.value()[0].Name == "s0-name"); - REQUIRE(policy.value()[0].Arg == "s0-arg"); - REQUIRE(policy.value()[0].Type == "s0-type"); - REQUIRE(policy.value()[0].Data == "s0-data"); - REQUIRE(policy.value()[0].Identifier == "s0-identifier"); - REQUIRE(policy.value()[0].TrustLevel[0] == "None"); - REQUIRE(policy.value()[0].Explicit == true); - - REQUIRE(policy.value()[1].Name == "s1-name"); - REQUIRE(policy.value()[1].Arg == "s1-arg"); - REQUIRE(policy.value()[1].Type == "s1-type"); - REQUIRE(policy.value()[1].Data == "s1-data"); - REQUIRE(policy.value()[1].Identifier == "s1-identifier"); - REQUIRE(policy.value()[1].TrustLevel[0] == "Trusted"); - REQUIRE(policy.value()[1].TrustLevel[1] == "StoreOrigin"); - REQUIRE(policy.value()[1].Explicit == false); - - REQUIRE(policy.value()[2].Name == "s2-name"); - REQUIRE(policy.value()[2].Arg == "s2-arg"); - REQUIRE(policy.value()[2].Type == "s2-type"); - REQUIRE(policy.value()[2].Data == "s2-data"); - REQUIRE(policy.value()[2].Identifier == "s2-identifier"); - REQUIRE(policy.value()[2].TrustLevel[0] == "StoreOrigin"); - REQUIRE(policy.value()[2].TrustLevel[1] == "Trusted"); - REQUIRE(policy.value()[2].Explicit == true); - } - SECTION("Invalid source in list") - { - // If a single source is invalid we should still get all others - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); - SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ); - SetRegistryValue(additionalSourcesKey.get(), L"1", L"not a source", REG_SZ); - SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->size() == 2); - - REQUIRE(policy.value()[0].Name == "s0-name"); - REQUIRE(policy.value()[0].Arg == "s0-arg"); - REQUIRE(policy.value()[0].Type == "s0-type"); - REQUIRE(policy.value()[0].Data == "s0-data"); - REQUIRE(policy.value()[0].Identifier == "s0-identifier"); - REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted"); - REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin"); - REQUIRE(policy.value()[0].Explicit == false); - - REQUIRE(policy.value()[1].Name == "s2-name"); - REQUIRE(policy.value()[1].Arg == "s2-arg"); - REQUIRE(policy.value()[1].Type == "s2-type"); - REQUIRE(policy.value()[1].Data == "s2-data"); - REQUIRE(policy.value()[1].Identifier == "s2-identifier"); - REQUIRE(policy.value()[1].TrustLevel[0] == "StoreOrigin"); - REQUIRE(policy.value()[1].TrustLevel[1] == "Trusted"); - REQUIRE(policy.value()[1].Explicit == true); - } - SECTION("Exported JSON") - { - // Policy should be able to use an exported JSON strings - SourceFromPolicy source; - source.Name = "json-name"; - source.Type = "json-type"; - source.Arg = "json-arg"; - source.Data = "json-data"; - source.Identifier = "json-id"; - source.TrustLevel = {"Trusted", "StoreOrigin"}; - source.Explicit = false; - - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); - SetRegistryValue(additionalSourcesKey.get(), L"0", AppInstaller::Utility::ConvertToUTF16(source.ToJsonString())); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->size() == 1); - REQUIRE(policy.value()[0].Name == source.Name); - REQUIRE(policy.value()[0].Arg == source.Arg); - REQUIRE(policy.value()[0].Type == source.Type); - REQUIRE(policy.value()[0].Data == source.Data); - REQUIRE(policy.value()[0].Identifier == source.Identifier); - REQUIRE(policy.value()[0].TrustLevel[0] == source.TrustLevel[0]); // Trusted - REQUIRE(policy.value()[0].TrustLevel[1] == source.TrustLevel[1]); // StoreOrigin - REQUIRE(policy.value()[0].Explicit == source.Explicit); - } - SECTION("Source with PinningConfiguration") - { - using namespace AppInstaller::Certificates; - - auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); - - PinningDetails rootCert; - rootCert.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE); - PinningDetails intermediateCert; - intermediateCert.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE); - PinningDetails leafCert; - leafCert.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); - - auto getBytesString = [](const PinningDetails& details) - { - std::vector bytes; - bytes.assign(details.GetCertificate()->pbCertEncoded, details.GetCertificate()->pbCertEncoded + details.GetCertificate()->cbCertEncoded); - return AppInstaller::Utility::ConvertToUTF16(AppInstaller::Utility::ConvertToHexString(bytes)); - }; - - std::wostringstream pinningConfig; - pinningConfig << -LR"({ - "Chains": [{ - "Chain":[ - { "Validation": ["publickey"], "EmbeddedCertificate": ")" << getBytesString(rootCert) << LR"(" }, - { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(intermediateCert) << LR"(" }, - { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(leafCert) << LR"(" } - ] - }] -})"; - - SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true", pinningConfig.str()), REG_SZ); - GroupPolicy groupPolicy{ policiesKey.get() }; - - auto policy = groupPolicy.GetValue(); - REQUIRE(policy.has_value()); - REQUIRE(policy->size() == 1); - const auto& sourceInfo = policy.value()[0]; - REQUIRE(sourceInfo.Name == "source-name"); - REQUIRE(sourceInfo.Arg == "source-arg"); - REQUIRE(sourceInfo.Type == "source-type"); - REQUIRE(sourceInfo.Data == "source-data"); - REQUIRE(sourceInfo.Identifier == "source-identifier"); - REQUIRE(sourceInfo.TrustLevel[0] == "Trusted"); - REQUIRE(sourceInfo.TrustLevel[1] == "StoreOrigin"); - REQUIRE(sourceInfo.Explicit == true); - - // Use loaded pinning config and validate against leaf certificate - REQUIRE(!sourceInfo.PinningConfiguration.IsEmpty()); - REQUIRE(sourceInfo.PinningConfiguration.Validate(leafCert.GetCertificate())); - } -} - -TEST_CASE("GroupPolicy_Toggle", "[groupPolicy]") -{ - auto policiesKey = RegCreateVolatileTestRoot(); - - SECTION("'None' is not configured") - { - GroupPolicy groupPolicy{ policiesKey.get() }; - REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::None) == PolicyState::NotConfigured); - REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::None)); - } - - SECTION("Enabled") - { - SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1); - GroupPolicy groupPolicy{ policiesKey.get() }; - REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::WinGet) == PolicyState::Enabled); - REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::WinGet)); - } - - SECTION("Disabled") - { - SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 0); - GroupPolicy groupPolicy{ policiesKey.get() }; - REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::LocalManifestFiles) == PolicyState::Disabled); - REQUIRE_FALSE(groupPolicy.IsEnabled(TogglePolicy::Policy::LocalManifestFiles)); - } - - SECTION("Wrong type") - { - SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, L"Wrong"); - GroupPolicy groupPolicy{ policiesKey.get() }; - REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::DefaultSource) == PolicyState::NotConfigured); - REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::DefaultSource)); - } -} - -TEST_CASE("GroupPolicy_AllEnabled", "[groupPolicy]") -{ - auto policiesKey = RegCreateVolatileTestRoot(); - SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), WinGetSettingsPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), EnableHashOverridePolicyValueName, 1); - SetRegistryValue(policiesKey.get(), EnableLocalArchiveMalwareScanOverridePolicyValueName, 1); - SetRegistryValue(policiesKey.get(), DefaultSourcePolicyValueName, 1); - SetRegistryValue(policiesKey.get(), MSStoreSourcePolicyValueName, 1); - SetRegistryValue(policiesKey.get(), FontSourcePolicyValueName, 1); - SetRegistryValue(policiesKey.get(), AdditionalSourcesPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), AllowedSourcesPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), BypassCertificatePinningForMicrosoftStoreValueName, 1); - SetRegistryValue(policiesKey.get(), EnableWindowsPackageManagerCommandLineInterfaces, 1); - SetRegistryValue(policiesKey.get(), ConfigurationPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), ProxyCommandLineOptionsPolicyValueName, 1); - SetRegistryValue(policiesKey.get(), McpServerValueName, 1); - SetRegistryValue(policiesKey.get(), ConfigurationProcessorPathValueName, 1); - - GroupPolicy groupPolicy{ policiesKey.get() }; - for (const auto& policy : TogglePolicy::GetAllPolicies()) - { - REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::Enabled); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSettings.h" +#include "winget/GroupPolicy.h" +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Settings; +using namespace std::string_view_literals; + +namespace +{ + std::wstring GetSourceJson(std::wstring_view name, std::wstring_view arg, std::wstring_view type, std::wstring_view data, std::wstring_view identifier, std::wstring_view trustLevel, std::wstring_view isExplicit, std::wstring_view pinningConfig = {}) + { + std::wstringstream json; + json << L"{ \"Name\":\"" << name << L"\", \"Arg\":\"" << arg << L"\", \"Type\":\"" << type << L"\", \"Data\":\"" << data << L"\", \"Identifier\":\"" << identifier << L"\", \"TrustLevel\":" << trustLevel << L", \"Explicit\":" << isExplicit; + if (!pinningConfig.empty()) + { + json << L", \"CertificatePinning\":" << pinningConfig; + } + json << " }"; + return json.str(); + } +} + +TEST_CASE("GroupPolicy_NoPolicies", "[groupPolicy]") +{ + auto policiesKey = RegCreateVolatileTestRoot(); + GroupPolicy groupPolicy{ policiesKey.get() }; + + // Policies setting a value should be empty + REQUIRE(!groupPolicy.GetValue().has_value()); + REQUIRE(!groupPolicy.GetValue().has_value()); + REQUIRE(!groupPolicy.GetValue().has_value()); + + // Everything should be not configured + for (const auto& policy : TogglePolicy::GetAllPolicies()) + { + REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::NotConfigured); + } +} + +TEST_CASE("GroupPolicy_UpdateInterval", "[groupPolicy]") +{ + auto policiesKey = RegCreateVolatileTestRoot(); + + SECTION("Good value") + { + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 5); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(*policy == 5); + } + + SECTION("Wrong type") + { + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong"); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(!policy.has_value()); + } +} + + +TEST_CASE("GroupPolicy_UpdateInterval_OldName", "[groupPolicy]") +{ + auto policiesKey = RegCreateVolatileTestRoot(); + + SECTION("New name shadows old") + { + SECTION("When old is valid") + { + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 3); + } + SECTION("When old is invalid") + { + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, L"Invalid type"); + } + + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, 1); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(*policy == 1); + } + + SECTION("Fallback to old name") + { + SECTION("When new name has invalid data") + { + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyValueName, L"Wrong type"); + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20); + GroupPolicy groupPolicy{ policiesKey.get() }; + + // We should not fall back on this case + auto policy = groupPolicy.GetValue(); + REQUIRE(!policy.has_value()); + } + SECTION("When new name is missing") + { + // Don't add the registry value with the new name + SetRegistryValue(policiesKey.get(), SourceUpdateIntervalPolicyOldValueName, 20); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(*policy == 20); + } + } +} + +TEST_CASE("GroupPolicy_Sources", "[groupPolicy]") +{ + auto policiesKey = RegCreateVolatileTestRoot(); + + // Note that the following tests mix using Additional/Allowed sources policy. + SECTION("Single source") + { + // We can read single source correctly + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); + SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true"), REG_SZ); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->size() == 1); + REQUIRE(policy.value()[0].Name == "source-name"); + REQUIRE(policy.value()[0].Arg == "source-arg"); + REQUIRE(policy.value()[0].Type == "source-type"); + REQUIRE(policy.value()[0].Data == "source-data"); + REQUIRE(policy.value()[0].Identifier == "source-identifier"); + REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted"); + REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin"); + REQUIRE(policy.value()[0].Explicit == true); + } + SECTION("Missing field") + { + // A single missing field causes the source to not be read. + // "Type" is missing here. + std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":\"source_data\", \"Identifier\":\"source_identifier\" }"; + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); + SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->empty()); + } + SECTION("Invalid field") + { + // A single invalid field causes the source to not be read. + // "Data" is invalid as it is an object, not a string. + std::wstring sourceJson = L"{ \"Name\":\"source_name\", \"Arg\":\"source_arg\", \"Data\":{}, \"Type\":\"source_type\", \"Identifier\":\"source_identifier\" }"; + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); + SetRegistryValue(additionalSourcesKey.get(), L"0", sourceJson, REG_SZ); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->empty()); + } + SECTION("Invalid source JSON") + { + // An invalid source JSON causes the source to not be read. + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); + SetRegistryValue(additionalSourcesKey.get(), L"0", L"not a JSON", REG_SZ); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->empty()); + } + SECTION("Missing key") + { + // If the key does not exist we should not get anything. + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE_FALSE(policy.has_value()); + } + SECTION("Empty key") + { + // If the key is empty we should get an empty list. + // Note that the policy editor doesn't actually create empty keys. + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->empty()); + } + SECTION("Valid list") + { + // We should be able to read multiple values. + // No specific order is required, but it will likely be the same. + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); + SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"None\"]", L"true"), REG_SZ); + SetRegistryValue(additionalSourcesKey.get(), L"1", GetSourceJson(L"s1-name", L"s1-arg", L"s1-type", L"s1-data", L"s1-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ); + SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->size() == 3); + + REQUIRE(policy.value()[0].Name == "s0-name"); + REQUIRE(policy.value()[0].Arg == "s0-arg"); + REQUIRE(policy.value()[0].Type == "s0-type"); + REQUIRE(policy.value()[0].Data == "s0-data"); + REQUIRE(policy.value()[0].Identifier == "s0-identifier"); + REQUIRE(policy.value()[0].TrustLevel[0] == "None"); + REQUIRE(policy.value()[0].Explicit == true); + + REQUIRE(policy.value()[1].Name == "s1-name"); + REQUIRE(policy.value()[1].Arg == "s1-arg"); + REQUIRE(policy.value()[1].Type == "s1-type"); + REQUIRE(policy.value()[1].Data == "s1-data"); + REQUIRE(policy.value()[1].Identifier == "s1-identifier"); + REQUIRE(policy.value()[1].TrustLevel[0] == "Trusted"); + REQUIRE(policy.value()[1].TrustLevel[1] == "StoreOrigin"); + REQUIRE(policy.value()[1].Explicit == false); + + REQUIRE(policy.value()[2].Name == "s2-name"); + REQUIRE(policy.value()[2].Arg == "s2-arg"); + REQUIRE(policy.value()[2].Type == "s2-type"); + REQUIRE(policy.value()[2].Data == "s2-data"); + REQUIRE(policy.value()[2].Identifier == "s2-identifier"); + REQUIRE(policy.value()[2].TrustLevel[0] == "StoreOrigin"); + REQUIRE(policy.value()[2].TrustLevel[1] == "Trusted"); + REQUIRE(policy.value()[2].Explicit == true); + } + SECTION("Invalid source in list") + { + // If a single source is invalid we should still get all others + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); + SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"s0-name", L"s0-arg", L"s0-type", L"s0-data", L"s0-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"false"), REG_SZ); + SetRegistryValue(additionalSourcesKey.get(), L"1", L"not a source", REG_SZ); + SetRegistryValue(additionalSourcesKey.get(), L"2", GetSourceJson(L"s2-name", L"s2-arg", L"s2-type", L"s2-data", L"s2-identifier", L"[\"StoreOrigin\", \"Trusted\"]", L"true"), REG_SZ); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->size() == 2); + + REQUIRE(policy.value()[0].Name == "s0-name"); + REQUIRE(policy.value()[0].Arg == "s0-arg"); + REQUIRE(policy.value()[0].Type == "s0-type"); + REQUIRE(policy.value()[0].Data == "s0-data"); + REQUIRE(policy.value()[0].Identifier == "s0-identifier"); + REQUIRE(policy.value()[0].TrustLevel[0] == "Trusted"); + REQUIRE(policy.value()[0].TrustLevel[1] == "StoreOrigin"); + REQUIRE(policy.value()[0].Explicit == false); + + REQUIRE(policy.value()[1].Name == "s2-name"); + REQUIRE(policy.value()[1].Arg == "s2-arg"); + REQUIRE(policy.value()[1].Type == "s2-type"); + REQUIRE(policy.value()[1].Data == "s2-data"); + REQUIRE(policy.value()[1].Identifier == "s2-identifier"); + REQUIRE(policy.value()[1].TrustLevel[0] == "StoreOrigin"); + REQUIRE(policy.value()[1].TrustLevel[1] == "Trusted"); + REQUIRE(policy.value()[1].Explicit == true); + } + SECTION("Exported JSON") + { + // Policy should be able to use an exported JSON strings + SourceFromPolicy source; + source.Name = "json-name"; + source.Type = "json-type"; + source.Arg = "json-arg"; + source.Data = "json-data"; + source.Identifier = "json-id"; + source.TrustLevel = {"Trusted", "StoreOrigin"}; + source.Explicit = false; + + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AllowedSourcesPolicyKeyName); + SetRegistryValue(additionalSourcesKey.get(), L"0", AppInstaller::Utility::ConvertToUTF16(source.ToJsonString())); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->size() == 1); + REQUIRE(policy.value()[0].Name == source.Name); + REQUIRE(policy.value()[0].Arg == source.Arg); + REQUIRE(policy.value()[0].Type == source.Type); + REQUIRE(policy.value()[0].Data == source.Data); + REQUIRE(policy.value()[0].Identifier == source.Identifier); + REQUIRE(policy.value()[0].TrustLevel[0] == source.TrustLevel[0]); // Trusted + REQUIRE(policy.value()[0].TrustLevel[1] == source.TrustLevel[1]); // StoreOrigin + REQUIRE(policy.value()[0].Explicit == source.Explicit); + } + SECTION("Source with PinningConfiguration") + { + using namespace AppInstaller::Certificates; + + auto additionalSourcesKey = RegCreateVolatileSubKey(policiesKey.get(), AdditionalSourcesPolicyKeyName); + + PinningDetails rootCert; + rootCert.LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE); + PinningDetails intermediateCert; + intermediateCert.LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE); + PinningDetails leafCert; + leafCert.LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE); + + auto getBytesString = [](const PinningDetails& details) + { + std::vector bytes; + bytes.assign(details.GetCertificate()->pbCertEncoded, details.GetCertificate()->pbCertEncoded + details.GetCertificate()->cbCertEncoded); + return AppInstaller::Utility::ConvertToUTF16(AppInstaller::Utility::ConvertToHexString(bytes)); + }; + + std::wostringstream pinningConfig; + pinningConfig << +LR"({ + "Chains": [{ + "Chain":[ + { "Validation": ["publickey"], "EmbeddedCertificate": ")" << getBytesString(rootCert) << LR"(" }, + { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(intermediateCert) << LR"(" }, + { "Validation": ["subject","issuer"], "EmbeddedCertificate": ")" << getBytesString(leafCert) << LR"(" } + ] + }] +})"; + + SetRegistryValue(additionalSourcesKey.get(), L"0", GetSourceJson(L"source-name", L"source-arg", L"source-type", L"source-data", L"source-identifier", L"[\"Trusted\", \"StoreOrigin\"]", L"true", pinningConfig.str()), REG_SZ); + GroupPolicy groupPolicy{ policiesKey.get() }; + + auto policy = groupPolicy.GetValue(); + REQUIRE(policy.has_value()); + REQUIRE(policy->size() == 1); + const auto& sourceInfo = policy.value()[0]; + REQUIRE(sourceInfo.Name == "source-name"); + REQUIRE(sourceInfo.Arg == "source-arg"); + REQUIRE(sourceInfo.Type == "source-type"); + REQUIRE(sourceInfo.Data == "source-data"); + REQUIRE(sourceInfo.Identifier == "source-identifier"); + REQUIRE(sourceInfo.TrustLevel[0] == "Trusted"); + REQUIRE(sourceInfo.TrustLevel[1] == "StoreOrigin"); + REQUIRE(sourceInfo.Explicit == true); + + // Use loaded pinning config and validate against leaf certificate + REQUIRE(!sourceInfo.PinningConfiguration.IsEmpty()); + REQUIRE(sourceInfo.PinningConfiguration.Validate(leafCert.GetCertificate())); + } +} + +TEST_CASE("GroupPolicy_Toggle", "[groupPolicy]") +{ + auto policiesKey = RegCreateVolatileTestRoot(); + + SECTION("'None' is not configured") + { + GroupPolicy groupPolicy{ policiesKey.get() }; + REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::None) == PolicyState::NotConfigured); + REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::None)); + } + + SECTION("Enabled") + { + SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1); + GroupPolicy groupPolicy{ policiesKey.get() }; + REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::WinGet) == PolicyState::Enabled); + REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::WinGet)); + } + + SECTION("Disabled") + { + SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 0); + GroupPolicy groupPolicy{ policiesKey.get() }; + REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::LocalManifestFiles) == PolicyState::Disabled); + REQUIRE_FALSE(groupPolicy.IsEnabled(TogglePolicy::Policy::LocalManifestFiles)); + } + + SECTION("Wrong type") + { + SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, L"Wrong"); + GroupPolicy groupPolicy{ policiesKey.get() }; + REQUIRE(groupPolicy.GetState(TogglePolicy::Policy::DefaultSource) == PolicyState::NotConfigured); + REQUIRE(groupPolicy.IsEnabled(TogglePolicy::Policy::DefaultSource)); + } +} + +TEST_CASE("GroupPolicy_AllEnabled", "[groupPolicy]") +{ + auto policiesKey = RegCreateVolatileTestRoot(); + SetRegistryValue(policiesKey.get(), WinGetPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), WinGetSettingsPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), ExperimentalFeaturesPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), LocalManifestsPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), EnableHashOverridePolicyValueName, 1); + SetRegistryValue(policiesKey.get(), EnableLocalArchiveMalwareScanOverridePolicyValueName, 1); + SetRegistryValue(policiesKey.get(), DefaultSourcePolicyValueName, 1); + SetRegistryValue(policiesKey.get(), MSStoreSourcePolicyValueName, 1); + SetRegistryValue(policiesKey.get(), FontSourcePolicyValueName, 1); + SetRegistryValue(policiesKey.get(), AdditionalSourcesPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), AllowedSourcesPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), BypassCertificatePinningForMicrosoftStoreValueName, 1); + SetRegistryValue(policiesKey.get(), EnableWindowsPackageManagerCommandLineInterfaces, 1); + SetRegistryValue(policiesKey.get(), ConfigurationPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), ProxyCommandLineOptionsPolicyValueName, 1); + SetRegistryValue(policiesKey.get(), McpServerValueName, 1); + SetRegistryValue(policiesKey.get(), ConfigurationProcessorPathValueName, 1); + + GroupPolicy groupPolicy{ policiesKey.get() }; + for (const auto& policy : TogglePolicy::GetAllPolicies()) + { + REQUIRE(groupPolicy.GetState(policy.GetPolicy()) == PolicyState::Enabled); + } +} diff --git a/src/AppInstallerCLITests/HashCommand.cpp b/src/AppInstallerCLITests/HashCommand.cpp index 40845e56b5..aab81e6d9b 100644 --- a/src/AppInstallerCLITests/HashCommand.cpp +++ b/src/AppInstallerCLITests/HashCommand.cpp @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "Commands/HashCommand.h" - -using namespace std::string_literals; -using namespace TestCommon; -using namespace AppInstaller::CLI; - -TEST_CASE("HashCommandWithTestMsix", "[Sha256Hash]") -{ - std::ostringstream hashOutput; - Execution::Context context{ hashOutput, std::cin }; - context.Args.AddArg(Execution::Args::Type::HashFile, TestDataFile("TestSignedApp.msix").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Msix); - HashCommand hashCommand({}); - - hashCommand.Execute(context); - - REQUIRE(hashOutput.str().find("Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea") != std::string::npos); - REQUIRE(hashOutput.str().find("SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07") != std::string::npos); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "Commands/HashCommand.h" + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::CLI; + +TEST_CASE("HashCommandWithTestMsix", "[Sha256Hash]") +{ + std::ostringstream hashOutput; + Execution::Context context{ hashOutput, std::cin }; + context.Args.AddArg(Execution::Args::Type::HashFile, TestDataFile("TestSignedApp.msix").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Msix); + HashCommand hashCommand({}); + + hashCommand.Execute(context); + + REQUIRE(hashOutput.str().find("Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea") != std::string::npos); + REQUIRE(hashOutput.str().find("SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07") != std::string::npos); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/HttpClientHelper.cpp b/src/AppInstallerCLITests/HttpClientHelper.cpp index 0705c815b2..3f013e823e 100644 --- a/src/AppInstallerCLITests/HttpClientHelper.cpp +++ b/src/AppInstallerCLITests/HttpClientHelper.cpp @@ -1,91 +1,91 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller::Http; -using namespace AppInstaller::Runtime; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Certificates; - -TEST_CASE("ExtractJsonResponse_UnsupportedMimeType", "[RestSource][RestSearch]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, L"", web::http::details::mime_types::text_plain) }; - REQUIRE_THROWS_HR(helper.HandleGet(L"https://testUri"), APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE); -} - -TEST_CASE("ValidateAndExtractResponse_ServiceUnavailable", "[RestSource]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::ServiceUnavailable) }; - REQUIRE_THROWS_HR(helper.HandleGet(L"https://testUri"), APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE); -} - -TEST_CASE("ValidateAndExtractResponse_NotFound", "[RestSource]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; - REQUIRE_THROWS_HR(helper.HandleGet(L"https://testUri"), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); -} - -TEST_CASE("EnsureDefaultUserAgent", "[RestSource]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler([](const web::http::http_request& request) - { - auto itr = request.headers().find(web::http::header_names::user_agent); - if (itr != request.headers().end() && - itr->second.find(ConvertToUTF16(GetClientVersion())) != utility::string_t::npos && - itr->second.find(ConvertToUTF16(GetPackageVersion())) != utility::string_t::npos) - { - return web::http::status_codes::OK; - } - else - { - return web::http::status_codes::BadRequest; - } - }) }; - - SECTION("GET") - { - REQUIRE_NOTHROW(helper.HandleGet(L"https://testUri")); - } - SECTION("POST") - { - REQUIRE_NOTHROW(helper.HandlePost(L"https://testUri", {})); - } -} - -TEST_CASE("HttpClientHelper_PinningConfiguration", "[RestSource]") -{ - // Create the Store chain config - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - PinningConfiguration config; - config.AddChain(chain); - - HttpClientHelper helper; - helper.SetPinningConfiguration(config); - - REQUIRE_THROWS_HR(helper.HandleGet(L"https://github.com"), APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH); -} - -TEST_CASE("HttpClientHelper_CallerCharacters", "[RestSource]") -{ - HttpClientHelper::HttpRequestHeaders headers; - headers.emplace(web::http::header_names::user_agent, AppInstaller::JSON::GetUtilityString(AppInstaller::Runtime::GetUserAgent("\xe6\xb5\x8b\xe8\xaf\x95"))); - - HttpClientHelper helper; - REQUIRE_THROWS_HR(helper.HandleGet(L"https://github.com", headers), APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller::Http; +using namespace AppInstaller::Runtime; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Certificates; + +TEST_CASE("ExtractJsonResponse_UnsupportedMimeType", "[RestSource][RestSearch]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, L"", web::http::details::mime_types::text_plain) }; + REQUIRE_THROWS_HR(helper.HandleGet(L"https://testUri"), APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE); +} + +TEST_CASE("ValidateAndExtractResponse_ServiceUnavailable", "[RestSource]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::ServiceUnavailable) }; + REQUIRE_THROWS_HR(helper.HandleGet(L"https://testUri"), APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE); +} + +TEST_CASE("ValidateAndExtractResponse_NotFound", "[RestSource]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; + REQUIRE_THROWS_HR(helper.HandleGet(L"https://testUri"), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); +} + +TEST_CASE("EnsureDefaultUserAgent", "[RestSource]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler([](const web::http::http_request& request) + { + auto itr = request.headers().find(web::http::header_names::user_agent); + if (itr != request.headers().end() && + itr->second.find(ConvertToUTF16(GetClientVersion())) != utility::string_t::npos && + itr->second.find(ConvertToUTF16(GetPackageVersion())) != utility::string_t::npos) + { + return web::http::status_codes::OK; + } + else + { + return web::http::status_codes::BadRequest; + } + }) }; + + SECTION("GET") + { + REQUIRE_NOTHROW(helper.HandleGet(L"https://testUri")); + } + SECTION("POST") + { + REQUIRE_NOTHROW(helper.HandlePost(L"https://testUri", {})); + } +} + +TEST_CASE("HttpClientHelper_PinningConfiguration", "[RestSource]") +{ + // Create the Store chain config + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + PinningConfiguration config; + config.AddChain(chain); + + HttpClientHelper helper; + helper.SetPinningConfiguration(config); + + REQUIRE_THROWS_HR(helper.HandleGet(L"https://github.com"), APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH); +} + +TEST_CASE("HttpClientHelper_CallerCharacters", "[RestSource]") +{ + HttpClientHelper::HttpRequestHeaders headers; + headers.emplace(web::http::header_names::user_agent, AppInstaller::JSON::GetUtilityString(AppInstaller::Runtime::GetUserAgent("\xe6\xb5\x8b\xe8\xaf\x95"))); + + HttpClientHelper helper; + REQUIRE_THROWS_HR(helper.HandleGet(L"https://github.com", headers), APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE); +} diff --git a/src/AppInstallerCLITests/IconExtraction.cpp b/src/AppInstallerCLITests/IconExtraction.cpp index a8e12cf9fb..1224f5b2bd 100644 --- a/src/AppInstallerCLITests/IconExtraction.cpp +++ b/src/AppInstallerCLITests/IconExtraction.cpp @@ -1,18 +1,18 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace AppInstaller::Repository; - -TEST_CASE("ExtractIconFromBinaryFile", "IconExtraction") -{ - auto extracted = ExtractIconFromBinaryFile(TestCommon::TestDataFile{ "notepad.exe" }.GetPath()); - - std::ifstream expectedIconFile{ TestCommon::TestDataFile{ "notepad.ico" }.GetPath(), std::ios::in | std::ios::binary}; - auto expected = AppInstaller::Utility::ReadEntireStreamAsByteArray(expectedIconFile); - - REQUIRE(expected == extracted); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace AppInstaller::Repository; + +TEST_CASE("ExtractIconFromBinaryFile", "IconExtraction") +{ + auto extracted = ExtractIconFromBinaryFile(TestCommon::TestDataFile{ "notepad.exe" }.GetPath()); + + std::ifstream expectedIconFile{ TestCommon::TestDataFile{ "notepad.ico" }.GetPath(), std::ios::in | std::ios::binary}; + auto expected = AppInstaller::Utility::ReadEntireStreamAsByteArray(expectedIconFile); + + REQUIRE(expected == extracted); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/ImportFlow.cpp b/src/AppInstallerCLITests/ImportFlow.cpp index 28096bdd64..583a16adab 100644 --- a/src/AppInstallerCLITests/ImportFlow.cpp +++ b/src/AppInstallerCLITests/ImportFlow.cpp @@ -1,352 +1,352 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestHooks.h" -#include "WorkflowCommon.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Utility::literals; - -void OverrideForImportSource(TestContext& context, bool useTestCompositeSource = false) -{ - auto testCompositeSource = CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_Dependencies, - TSR::TestInstaller_Exe_LicenseAgreement, - TSR::TestInstaller_Exe_NothingInstalled, - TSR::TestInstaller_Msix, - TSR::TestInstaller_Msix_WFDependency, - }); - - context.Override({ "OpenPredefinedSource", [=](TestContext& context) - { - auto installedSource = useTestCompositeSource ? testCompositeSource : std::make_shared(); - context.Add(Source{ installedSource }); - } }); - - context.Override({ Workflow::OpenSourcesForImport, [=](TestContext& context) - { - context.Add(std::vector{ Source{ testCompositeSource } }); - } }); -} - -TEST_CASE("ImportFlow_Successful", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForMSIX(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify all packages were installed - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(std::filesystem::exists(msixInstallResultPath.GetPath())); -} - -TEST_CASE("ImportFlow_PackageAlreadyInstalled", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context, true); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-AlreadyInstalled.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Exe should not have been installed again - REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageAlreadyInstalled("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); -} - -TEST_CASE("ImportFlow_IgnoreVersions", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-AlreadyInstalled.json").GetPath().string()); - context.Args.AddArg(Execution::Args::Type::IgnoreVersions); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Specified version is already installed. It should have been updated since we ignored the version. - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); -} - -TEST_CASE("ImportFlow_MissingSource", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownSource.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Installer should not be called - REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportSourceNotInstalled("TestSource"_liv)).get()) != std::string::npos); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); -} - -TEST_CASE("ImportFlow_MissingPackage", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackage.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Installer should not be called - REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); -} - -TEST_CASE("ImportFlow_IgnoreMissingPackage", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackage.json").GetPath().string()); - context.Args.AddArg(Execution::Args::Type::IgnoreUnavailable); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify installer was called for the package that was available. - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); -} - -TEST_CASE("ImportFlow_MissingVersion", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackageVersion.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Installer should not be called - REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQuerySearchFailed("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); -} - -TEST_CASE("ImportFlow_MalformedJsonFile", "[ImportFlow][workflow]") -{ - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Malformed.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Command should have failed - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); -} - -TEST_CASE("ImportFlow_InvalidJsonFile", "[ImportFlow][workflow]") -{ - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Invalid.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Command should have failed - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); -} - -TEST_CASE("ImportFlow_MachineScope", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-MachineScope.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify all packages were installed - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); - std::ifstream installResultFile(exeInstallResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/scope=machine") != std::string::npos); -} - -TEST_CASE("ImportFlow_WithOverrideArgs", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithOverrideArgs.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify package was installed with override args (override replaces all installer args) - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); - std::ifstream installResultFile(exeInstallResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/overrideArgs") != std::string::npos); - // Override replaces all args, so default silent switches should not be present - REQUIRE(installResultStr.find("/silentwithprogress") == std::string::npos); -} - -TEST_CASE("ImportFlow_WithCustomSwitches", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithCustomSwitches.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify package was installed with custom switches appended to the default args - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); - std::ifstream installResultFile(exeInstallResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/customSwitches") != std::string::npos); - // Custom switches are appended, so default silent switches should still be present - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("ImportFlow_Dependencies", "[ImportFlow][workflow][dependencies]") -{ - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForMSIX(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-Dependencies.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify dependencies for all packages are informed - REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportCommandReportDependencies).get()) != std::string::npos); - REQUIRE(importOutput.str().find("PreviewIIS") != std::string::npos); - REQUIRE(importOutput.str().find("Preview VC Runtime") != std::string::npos); - REQUIRE(importOutput.str().find("Hyper-V") != std::string::npos); -} - -TEST_CASE("ImportFlow_LicenseAgreement", "[ImportFlow][workflow]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - - std::ostringstream importOutput; - TestContext context{ importOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithLicenseAgreement.json").GetPath().string()); - context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify agreements are shown - REQUIRE(importOutput.str().find("Agreement for EXE") != std::string::npos); - REQUIRE(importOutput.str().find("This is the agreement for the EXE") != std::string::npos); - - // Verify all packages were installed - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); -} - -TEST_CASE("ImportFlow_LicenseAgreement_NotAccepted", "[ImportFlow][workflow]") -{ - // Say "No" at the agreements prompt - std::istringstream importInput{ "n" }; - - std::ostringstream importOutput; - TestContext context{ importOutput, importInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForImportSource(context); - context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithLicenseAgreement.json").GetPath().string()); - - ImportCommand importCommand({}); - importCommand.Execute(context); - INFO(importOutput.str()); - - // Verify agreements are shown - REQUIRE(importOutput.str().find("Agreement for EXE") != std::string::npos); - REQUIRE(importOutput.str().find("This is the agreement for the EXE") != std::string::npos); - - // Command should have failed - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestHooks.h" +#include "WorkflowCommon.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility::literals; + +void OverrideForImportSource(TestContext& context, bool useTestCompositeSource = false) +{ + auto testCompositeSource = CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_Dependencies, + TSR::TestInstaller_Exe_LicenseAgreement, + TSR::TestInstaller_Exe_NothingInstalled, + TSR::TestInstaller_Msix, + TSR::TestInstaller_Msix_WFDependency, + }); + + context.Override({ "OpenPredefinedSource", [=](TestContext& context) + { + auto installedSource = useTestCompositeSource ? testCompositeSource : std::make_shared(); + context.Add(Source{ installedSource }); + } }); + + context.Override({ Workflow::OpenSourcesForImport, [=](TestContext& context) + { + context.Add(std::vector{ Source{ testCompositeSource } }); + } }); +} + +TEST_CASE("ImportFlow_Successful", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForMSIX(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify all packages were installed + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(std::filesystem::exists(msixInstallResultPath.GetPath())); +} + +TEST_CASE("ImportFlow_PackageAlreadyInstalled", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context, true); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-AlreadyInstalled.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Exe should not have been installed again + REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageAlreadyInstalled("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); +} + +TEST_CASE("ImportFlow_IgnoreVersions", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-AlreadyInstalled.json").GetPath().string()); + context.Args.AddArg(Execution::Args::Type::IgnoreVersions); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Specified version is already installed. It should have been updated since we ignored the version. + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); +} + +TEST_CASE("ImportFlow_MissingSource", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownSource.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Installer should not be called + REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportSourceNotInstalled("TestSource"_liv)).get()) != std::string::npos); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST); +} + +TEST_CASE("ImportFlow_MissingPackage", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackage.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Installer should not be called + REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); +} + +TEST_CASE("ImportFlow_IgnoreMissingPackage", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackage.json").GetPath().string()); + context.Args.AddArg(Execution::Args::Type::IgnoreUnavailable); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify installer was called for the package that was available. + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQueryPackageNotFound("MissingPackage"_liv)).get()) != std::string::npos); +} + +TEST_CASE("ImportFlow_MissingVersion", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-UnknownPackageVersion.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Installer should not be called + REQUIRE(!std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::MultiQuerySearchFailed("AppInstallerCliTest.TestExeInstaller"_liv)).get()) != std::string::npos); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); +} + +TEST_CASE("ImportFlow_MalformedJsonFile", "[ImportFlow][workflow]") +{ + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Malformed.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Command should have failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); +} + +TEST_CASE("ImportFlow_InvalidJsonFile", "[ImportFlow][workflow]") +{ + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Bad-Invalid.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Command should have failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); +} + +TEST_CASE("ImportFlow_MachineScope", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-MachineScope.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify all packages were installed + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); + std::ifstream installResultFile(exeInstallResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/scope=machine") != std::string::npos); +} + +TEST_CASE("ImportFlow_WithOverrideArgs", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithOverrideArgs.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify package was installed with override args (override replaces all installer args) + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); + std::ifstream installResultFile(exeInstallResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/overrideArgs") != std::string::npos); + // Override replaces all args, so default silent switches should not be present + REQUIRE(installResultStr.find("/silentwithprogress") == std::string::npos); +} + +TEST_CASE("ImportFlow_WithCustomSwitches", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithCustomSwitches.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify package was installed with custom switches appended to the default args + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); + std::ifstream installResultFile(exeInstallResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/customSwitches") != std::string::npos); + // Custom switches are appended, so default silent switches should still be present + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("ImportFlow_Dependencies", "[ImportFlow][workflow][dependencies]") +{ + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForMSIX(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-Dependencies.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify dependencies for all packages are informed + REQUIRE(importOutput.str().find(Resource::LocString(Resource::String::ImportCommandReportDependencies).get()) != std::string::npos); + REQUIRE(importOutput.str().find("PreviewIIS") != std::string::npos); + REQUIRE(importOutput.str().find("Preview VC Runtime") != std::string::npos); + REQUIRE(importOutput.str().find("Hyper-V") != std::string::npos); +} + +TEST_CASE("ImportFlow_LicenseAgreement", "[ImportFlow][workflow]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + + std::ostringstream importOutput; + TestContext context{ importOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithLicenseAgreement.json").GetPath().string()); + context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify agreements are shown + REQUIRE(importOutput.str().find("Agreement for EXE") != std::string::npos); + REQUIRE(importOutput.str().find("This is the agreement for the EXE") != std::string::npos); + + // Verify all packages were installed + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); +} + +TEST_CASE("ImportFlow_LicenseAgreement_NotAccepted", "[ImportFlow][workflow]") +{ + // Say "No" at the agreements prompt + std::istringstream importInput{ "n" }; + + std::ostringstream importOutput; + TestContext context{ importOutput, importInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForImportSource(context); + context.Args.AddArg(Execution::Args::Type::ImportFile, TestDataFile("ImportFile-Good-WithLicenseAgreement.json").GetPath().string()); + + ImportCommand importCommand({}); + importCommand.Execute(context); + INFO(importOutput.str()); + + // Verify agreements are shown + REQUIRE(importOutput.str().find("Agreement for EXE") != std::string::npos); + REQUIRE(importOutput.str().find("This is the agreement for the EXE") != std::string::npos); + + // Command should have failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); +} diff --git a/src/AppInstallerCLITests/InstallDependenciesFlow.cpp b/src/AppInstallerCLITests/InstallDependenciesFlow.cpp index 73ff6dc9c2..f7afd9b7af 100644 --- a/src/AppInstallerCLITests/InstallDependenciesFlow.cpp +++ b/src/AppInstallerCLITests/InstallDependenciesFlow.cpp @@ -1,371 +1,371 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "DependenciesTestSource.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Repository; - -void OverrideOpenSourceForDependencies(TestContext& context) -{ - context.Override({ "OpenSource", [](TestContext& context) - { - context.Add(Source{ std::make_shared() }); - } }); - - context.Override({ Workflow::OpenDependencySource, [](TestContext& context) - { - context.Add(Source{ std::make_shared() }); - } }); -} - -void OverrideForProcessMultiplePackages(TestContext& context) -{ - context.Override({ ProcessMultiplePackages( - Resource::String::PackageRequiresDependencies, - APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, - ProcessMultiplePackages::Flags::SkipPackageAgreements | ProcessMultiplePackages::Flags::IgnoreDependencies), [](TestContext&) - { - - } }); -} - -void OverrideShellExecute(TestContext& context, std::vector& installationOrder) -{ - context.Override({ ShellExecuteInstallImpl, [&installationOrder](TestContext& c) - { - installationOrder.push_back(c.Get().Id); - c.Add(0); - } }); -} - -TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGraph][dependencies]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - - Manifest manifest = CreateFakeManifestWithDependencies("DependenciesInstalled"); - OverrideOpenDependencySource(context); - - context.Add(Source{ std::make_shared() }); - context.Add(manifest); - context.Add(manifest.Installers[0]); - - context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies); - - auto& dependencyPackages = context.Get(); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); - REQUIRE(dependencyPackages.size() == 0); -} - -TEST_CASE("DependencyGraph_validMinVersions", "[InstallFlow][workflow][dependencyGraph][dependencies]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - Manifest manifest = CreateFakeManifestWithDependencies("DependenciesValidMinVersions"); - OverrideOpenDependencySource(context); - - context.Add(Source{ std::make_shared() }); - context.Add(manifest); - context.Add(manifest.Installers[0]); - - context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies); - - auto& dependencyPackages = context.Get(); - - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); - REQUIRE(dependencyPackages.size() == 1); - REQUIRE(dependencyPackages.at(0)->Get().Id == "minVersion"); - // minVersion 1.5 is available but this requires 1.0 so that version is installed - REQUIRE(dependencyPackages.at(0)->Get().Version == "1.0"); -} - -TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph][dependencies]", ) -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - Manifest manifest = CreateFakeManifestWithDependencies("PathBetweenBranchesButNoLoop"); - OverrideOpenDependencySource(context); - - context.Add(Source{ std::make_shared() }); - context.Add(manifest); - context.Add(manifest.Installers[0]); - - context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies); - - auto& dependencyPackages = context.Get(); - - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); - - // Verify installers are called in order - REQUIRE(dependencyPackages.size() == 4); - REQUIRE(dependencyPackages.at(0)->Get().Id == "B"); - REQUIRE(dependencyPackages.at(1)->Get().Id == "C"); - REQUIRE(dependencyPackages.at(2)->Get().Id == "G"); - REQUIRE(dependencyPackages.at(3)->Get().Id == "H"); -} - -TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyGraph][dependencies]") -{ - std::vector installationOrder; - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideOpenSourceForDependencies(context); - OverrideForShellExecute(context, installationOrder); - - context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); - - // Verify installers are called in order - REQUIRE(installationOrder.size() == 3); - REQUIRE(installationOrder.at(0).Id() == "B"); - REQUIRE(installationOrder.at(1).Id() == "C"); - REQUIRE(installationOrder.at(2).Id() == "StackOrderIsOk"); -} - -TEST_CASE("DependencyGraph_MultipleDependenciesFromManifest", "[InstallFlow][workflow][dependencyGraph][dependencies]") -{ - std::vector installationOrder; - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideOpenSourceForDependencies(context); - OverrideForShellExecute(context, installationOrder); - OverrideEnableWindowsFeaturesDependencies(context); - - context.Args.AddArg(Execution::Args::Type::Query, "MultipleDependenciesFromManifest"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); - - // Verify installers are called in order - REQUIRE(installationOrder.size() == 3); - REQUIRE(installationOrder.at(0).Id() == "Dependency1"); - REQUIRE(installationOrder.at(1).Id() == "Dependency2"); - REQUIRE(installationOrder.at(2).Id() == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies"); -} - -TEST_CASE("InstallerWithoutDependencies_RootDependenciesAreUsed", "[dependencies]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideOpenDependencySource(context); - OverrideEnableWindowsFeaturesDependencies(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesOnRoot.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify root dependencies are shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); - REQUIRE(installOutput.str().find("PreviewIISOnRoot") != std::string::npos); -} - -TEST_CASE("InstallerWithDependencies_SkipDependencies", "[dependencies]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::SkipDependencies); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesSkippedMessage).get()) != std::string::npos); - REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); - REQUIRE_FALSE(installOutput.str().find("PreviewIIS") != std::string::npos); -} - -TEST_CASE("InstallerWithDependencies_IgnoreDependenciesSetting", "[dependencies]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); - - TestUserSettings settings; - settings.Set({ true }); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesSkippedMessage).get()) != std::string::npos); - REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); - REQUIRE_FALSE(installOutput.str().find("PreviewIIS") != std::string::npos); -} - -TEST_CASE("InstallerWithDependencies_DependenciesOnly", "[dependencies]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideOpenDependencySource(context); - OverrideEnableWindowsFeaturesDependencies(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DependenciesOnly); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Dependencies should be reported and installed - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); - REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); - // DependenciesOnly message should be shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesOnlyMessage).get()) != std::string::npos); -} - -TEST_CASE("DependenciesMultideclaration_InstallerDependenciesPreference", "[dependencies]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideOpenDependencySource(context); - OverrideEnableWindowsFeaturesDependencies(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesMultideclaration.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify installer dependencies are shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); - REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); - // and root dependencies are not - REQUIRE(installOutput.str().find("PreviewIISOnRoot") == std::string::npos); -} - -TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideOpenDependencySource(context); - OverrideEnableWindowsFeaturesDependencies(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify all types of dependencies are printed - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); - REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); -} - -TEST_CASE("InstallFlow_Dependencies_COM", "[InstallFlow][workflow][dependencies]") -{ - std::vector installationOrder; - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideShellExecute(context, installationOrder); - OverrideOpenDependencySource(context); - OverrideEnableWindowsFeaturesDependencies(context); - context.Override({ ReverifyInstallerHash, [](TestContext&) {} }); - - context.Add(YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_MultipleDependencies.yaml"))); - - COMDownloadCommand download({}); - download.Execute(context); - - REQUIRE(installationOrder.size() == 0); - - COMInstallCommand install({}); - REQUIRE_NOTHROW(install.Execute(context)); - - REQUIRE(context.GetTerminationHR() == S_OK); - - // Verify installers are called in order - REQUIRE(installationOrder.size() == 3); - REQUIRE(installationOrder.at(0) == "Dependency1"); - REQUIRE(installationOrder.at(1) == "Dependency2"); - REQUIRE(installationOrder.at(2) == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies"); -} - -void InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(std::string_view featureName) -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - - context << ShellExecuteEnableWindowsFeature(featureName); - - INFO(installOutput.str()); - - REQUIRE(context.Contains(Execution::Data::OperationReturnCode)); - REQUIRE(context.Get() == E_INVALIDARG); -} - -TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Extras", "[InstallFlow][workflow][dependencies][111981]") -{ - TempFile potentialLogFile("dism-log", ".log"); - std::string featureName = "MediaPlayback /LogPath:"; - featureName.append(potentialLogFile.GetPath().u8string()); - - InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName); - - REQUIRE(!std::filesystem::exists(potentialLogFile)); -} - -TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Quoted", "[InstallFlow][workflow][dependencies][111981]") -{ - TempFile potentialLogFile("dism-log", ".log"); - std::string featureName = "\"MediaPlayback /LogPath:"; - featureName.append(potentialLogFile.GetPath().u8string()); - featureName.append("\""); - - InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName); - - REQUIRE(!std::filesystem::exists(potentialLogFile)); -} - -// TODO: -// add dependencies for installer tests to DependenciesTestSource (or a new one) -// add tests for min version dependency solving -// add tests that check for correct installation of dependencies (not only the order) +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "DependenciesTestSource.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Repository; + +void OverrideOpenSourceForDependencies(TestContext& context) +{ + context.Override({ "OpenSource", [](TestContext& context) + { + context.Add(Source{ std::make_shared() }); + } }); + + context.Override({ Workflow::OpenDependencySource, [](TestContext& context) + { + context.Add(Source{ std::make_shared() }); + } }); +} + +void OverrideForProcessMultiplePackages(TestContext& context) +{ + context.Override({ ProcessMultiplePackages( + Resource::String::PackageRequiresDependencies, + APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, + ProcessMultiplePackages::Flags::SkipPackageAgreements | ProcessMultiplePackages::Flags::IgnoreDependencies), [](TestContext&) + { + + } }); +} + +void OverrideShellExecute(TestContext& context, std::vector& installationOrder) +{ + context.Override({ ShellExecuteInstallImpl, [&installationOrder](TestContext& c) + { + installationOrder.push_back(c.Get().Id); + c.Add(0); + } }); +} + +TEST_CASE("DependencyGraph_SkipInstalled", "[InstallFlow][workflow][dependencyGraph][dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + + Manifest manifest = CreateFakeManifestWithDependencies("DependenciesInstalled"); + OverrideOpenDependencySource(context); + + context.Add(Source{ std::make_shared() }); + context.Add(manifest); + context.Add(manifest.Installers[0]); + + context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies); + + auto& dependencyPackages = context.Get(); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); + REQUIRE(dependencyPackages.size() == 0); +} + +TEST_CASE("DependencyGraph_validMinVersions", "[InstallFlow][workflow][dependencyGraph][dependencies]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + Manifest manifest = CreateFakeManifestWithDependencies("DependenciesValidMinVersions"); + OverrideOpenDependencySource(context); + + context.Add(Source{ std::make_shared() }); + context.Add(manifest); + context.Add(manifest.Installers[0]); + + context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies); + + auto& dependencyPackages = context.Get(); + + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); + REQUIRE(dependencyPackages.size() == 1); + REQUIRE(dependencyPackages.at(0)->Get().Id == "minVersion"); + // minVersion 1.5 is available but this requires 1.0 so that version is installed + REQUIRE(dependencyPackages.at(0)->Get().Version == "1.0"); +} + +TEST_CASE("DependencyGraph_PathNoLoop", "[InstallFlow][workflow][dependencyGraph][dependencies]", ) +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + Manifest manifest = CreateFakeManifestWithDependencies("PathBetweenBranchesButNoLoop"); + OverrideOpenDependencySource(context); + + context.Add(Source{ std::make_shared() }); + context.Add(manifest); + context.Add(manifest.Installers[0]); + + context << CreateDependencySubContexts(Resource::String::PackageRequiresDependencies); + + auto& dependencyPackages = context.Get(); + + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); + + // Verify installers are called in order + REQUIRE(dependencyPackages.size() == 4); + REQUIRE(dependencyPackages.at(0)->Get().Id == "B"); + REQUIRE(dependencyPackages.at(1)->Get().Id == "C"); + REQUIRE(dependencyPackages.at(2)->Get().Id == "G"); + REQUIRE(dependencyPackages.at(3)->Get().Id == "H"); +} + +TEST_CASE("DependencyGraph_StackOrderIsOk", "[InstallFlow][workflow][dependencyGraph][dependencies]") +{ + std::vector installationOrder; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideOpenSourceForDependencies(context); + OverrideForShellExecute(context, installationOrder); + + context.Args.AddArg(Execution::Args::Type::Query, "StackOrderIsOk"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); + + // Verify installers are called in order + REQUIRE(installationOrder.size() == 3); + REQUIRE(installationOrder.at(0).Id() == "B"); + REQUIRE(installationOrder.at(1).Id() == "C"); + REQUIRE(installationOrder.at(2).Id() == "StackOrderIsOk"); +} + +TEST_CASE("DependencyGraph_MultipleDependenciesFromManifest", "[InstallFlow][workflow][dependencyGraph][dependencies]") +{ + std::vector installationOrder; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideOpenSourceForDependencies(context); + OverrideForShellExecute(context, installationOrder); + OverrideEnableWindowsFeaturesDependencies(context); + + context.Args.AddArg(Execution::Args::Type::Query, "MultipleDependenciesFromManifest"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesFlowContainsLoop)) == std::string::npos); + + // Verify installers are called in order + REQUIRE(installationOrder.size() == 3); + REQUIRE(installationOrder.at(0).Id() == "Dependency1"); + REQUIRE(installationOrder.at(1).Id() == "Dependency2"); + REQUIRE(installationOrder.at(2).Id() == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies"); +} + +TEST_CASE("InstallerWithoutDependencies_RootDependenciesAreUsed", "[dependencies]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideOpenDependencySource(context); + OverrideEnableWindowsFeaturesDependencies(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesOnRoot.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify root dependencies are shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIISOnRoot") != std::string::npos); +} + +TEST_CASE("InstallerWithDependencies_SkipDependencies", "[dependencies]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::SkipDependencies); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesSkippedMessage).get()) != std::string::npos); + REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); + REQUIRE_FALSE(installOutput.str().find("PreviewIIS") != std::string::npos); +} + +TEST_CASE("InstallerWithDependencies_IgnoreDependenciesSetting", "[dependencies]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({ true }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesSkippedMessage).get()) != std::string::npos); + REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); + REQUIRE_FALSE(installOutput.str().find("PreviewIIS") != std::string::npos); +} + +TEST_CASE("InstallerWithDependencies_DependenciesOnly", "[dependencies]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideOpenDependencySource(context); + OverrideEnableWindowsFeaturesDependencies(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DependenciesOnly); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Dependencies should be reported and installed + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); + // DependenciesOnly message should be shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::DependenciesOnlyMessage).get()) != std::string::npos); +} + +TEST_CASE("DependenciesMultideclaration_InstallerDependenciesPreference", "[dependencies]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideOpenDependencySource(context); + OverrideEnableWindowsFeaturesDependencies(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_DependenciesMultideclaration.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify installer dependencies are shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); + // and root dependencies are not + REQUIRE(installOutput.str().find("PreviewIISOnRoot") == std::string::npos); +} + +TEST_CASE("InstallFlow_Dependencies", "[InstallFlow][workflow][dependencies]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideOpenDependencySource(context); + OverrideEnableWindowsFeaturesDependencies(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Installer_Exe_Dependencies.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify all types of dependencies are printed + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); + REQUIRE(installOutput.str().find("PreviewIIS") != std::string::npos); +} + +TEST_CASE("InstallFlow_Dependencies_COM", "[InstallFlow][workflow][dependencies]") +{ + std::vector installationOrder; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideShellExecute(context, installationOrder); + OverrideOpenDependencySource(context); + OverrideEnableWindowsFeaturesDependencies(context); + context.Override({ ReverifyInstallerHash, [](TestContext&) {} }); + + context.Add(YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_MultipleDependencies.yaml"))); + + COMDownloadCommand download({}); + download.Execute(context); + + REQUIRE(installationOrder.size() == 0); + + COMInstallCommand install({}); + REQUIRE_NOTHROW(install.Execute(context)); + + REQUIRE(context.GetTerminationHR() == S_OK); + + // Verify installers are called in order + REQUIRE(installationOrder.size() == 3); + REQUIRE(installationOrder.at(0) == "Dependency1"); + REQUIRE(installationOrder.at(1) == "Dependency2"); + REQUIRE(installationOrder.at(2) == "AppInstallerCliTest.TestExeInstaller.MultipleDependencies"); +} + +void InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(std::string_view featureName) +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + + context << ShellExecuteEnableWindowsFeature(featureName); + + INFO(installOutput.str()); + + REQUIRE(context.Contains(Execution::Data::OperationReturnCode)); + REQUIRE(context.Get() == E_INVALIDARG); +} + +TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Extras", "[InstallFlow][workflow][dependencies][111981]") +{ + TempFile potentialLogFile("dism-log", ".log"); + std::string featureName = "MediaPlayback /LogPath:"; + featureName.append(potentialLogFile.GetPath().u8string()); + + InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName); + + REQUIRE(!std::filesystem::exists(potentialLogFile)); +} + +TEST_CASE("InstallFlow_Dependencies_WindowsFeaturesArgument_Quoted", "[InstallFlow][workflow][dependencies][111981]") +{ + TempFile potentialLogFile("dism-log", ".log"); + std::string featureName = "\"MediaPlayback /LogPath:"; + featureName.append(potentialLogFile.GetPath().u8string()); + featureName.append("\""); + + InstallFlow_Dependencies_WindowsFeaturesArgument_Generic(featureName); + + REQUIRE(!std::filesystem::exists(potentialLogFile)); +} + +// TODO: +// add dependencies for installer tests to DependenciesTestSource (or a new one) +// add tests for min version dependency solving +// add tests that check for correct installation of dependencies (not only the order) diff --git a/src/AppInstallerCLITests/InstallFlow.cpp b/src/AppInstallerCLITests/InstallFlow.cpp index d6f0881b5d..64fb767bbc 100644 --- a/src/AppInstallerCLITests/InstallFlow.cpp +++ b/src/AppInstallerCLITests/InstallFlow.cpp @@ -1,1442 +1,1442 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "TestHooks.h" -#include "AppInstallerRuntime.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace winrt::Windows::Foundation; -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Logging; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Utility::literals; - -void OverrideForDirectMsi(TestContext& context) -{ - OverrideForCheckExistingInstaller(context); - - context.Override({ DownloadInstallerFile, [](TestContext& context) - { - context.Add({ {}, {} }); - // We don't have an msi installer for tests, but we won't execute it anyway - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - } }); - - context.Override({ RenameDownloadedInstaller, [](TestContext&) - { - } }); - - OverrideForUpdateInstallerMotw(context); - - context.Override({ DirectMSIInstallImpl, [](TestContext& context) - { - // Write out the install command - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestMsiInstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - file << context.Get(); - file.close(); - - context.Add(0); - } }); -} - -TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("InstallFlow_RenameFromEncodedUrl", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCheckExistingInstaller(context); - context.Override({ DownloadInstallerFile, [](TestContext& context) - { - context.Add({ {}, {} }); - auto installerPath = std::filesystem::temp_directory_path(); - installerPath /= "EncodedUrlTest.exe"; - std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); - context.Add(installerPath); - } }); - OverrideForUpdateInstallerMotw(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_EncodedUrl.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/encodedUrl") != std::string::npos); -} - -TEST_CASE("InstallFlow_RenameFromInvalidFileCharacterUrl", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCheckExistingInstaller(context); - context.Override({ DownloadInstallerFile, [](TestContext& context) - { - context.Add({ {}, {} }); - auto installerPath = std::filesystem::temp_directory_path(); - installerPath /= "InvalidFileCharacterUrlTest.exe"; - std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); - context.Add(installerPath); - } }); - OverrideForUpdateInstallerMotw(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InvalidFileCharacterUrl.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/invalidFileCharacterUrl") != std::string::npos); -} - -TEST_CASE("InstallFlowNonZeroExitCode", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NonZeroExitCode.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(context.GetTerminationHR() == S_OK); - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/ExitCode 0x80070005") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("InstallFlow_InstallationNotes", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallationNotes.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify installation notes are displayed - REQUIRE(context.GetTerminationHR() == S_OK); - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find("testInstallationNotes") != std::string::npos); -} - -TEST_CASE("InstallFlow_UnsupportedArguments_Warn", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TempDirectory tempDirectory("TempDirectory", false); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Log, tempDirectory); - - InstallCommand install({}); - context.SetExecutingCommand(&install); - install.Execute(context); - INFO(installOutput.str()); - - // Verify unsupported arguments warn message is shown - REQUIRE(context.GetTerminationHR() == S_OK); - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); - REQUIRE(installOutput.str().find("-o,--log") != std::string::npos); -} - -TEST_CASE("InstallFlow_UnsupportedArguments_Error", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TempDirectory tempDirectory("TempDirectory", false); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); - - InstallCommand install({}); - context.SetExecutingCommand(&install); - install.Execute(context); - INFO(installOutput.str()); - - // Verify unsupported arguments error message is shown - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); - REQUIRE(installOutput.str().find("-l,--location") != std::string::npos); -} - -TEST_CASE("InstallFlow_UnsupportedArguments_NotProvided") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); - - InstallCommand install({}); - context.SetExecutingCommand(&install); - install.Execute(context); - INFO(installOutput.str()); - - // Verify unsupported arguments error message is not shown when not provided - REQUIRE(context.GetTerminationHR() == S_OK); - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) == std::string::npos); - REQUIRE(installOutput.str().find("-o,--log") == std::string::npos); - REQUIRE(installOutput.str().find("-l,--location") == std::string::npos); -} - -TEST_CASE("InstallFlow_ExpectedReturnCodes", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Override, "/ExitCode 8"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify install failed with the right message - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT); - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallFlowReturnCodeContactSupport).get()) != std::string::npos); - REQUIRE(installOutput.str().find("https://TestReturnResponseUrl") != std::string::npos); -} - -TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); -} - -TEST_CASE("InstallFlow_Zip_Exe", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideForExtractInstallerFromArchive(context); - OverrideForVerifyAndSetNestedInstaller(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); - - TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("InstallFlow_Zip_BadRelativePath", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideForExtractInstallerFromArchive(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); - - TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - auto relativePath = context.Get().parent_path() / "extracted" / "relativeFilePath"; - auto expectedMessage = Resource::String::NestedInstallerNotFound(AppInstaller::Utility::LocIndString{ relativePath.u8string()}); - REQUIRE(installOutput.str().find(Resource::LocString(expectedMessage).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_Zip_MissingNestedInstaller", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_MissingNestedInstaller.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NestedInstallerNotSpecified).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_Zip_UnsupportedNestedInstaller", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_UnsupportedNestedInstaller.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, ERROR_NOT_SUPPORTED); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NestedInstallerNotSupported).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_Zip_MultipleNonPortableNestedInstallers", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_MultipleNonPortableNestedInstallers.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::MultipleUnsupportedNestedInstallersSpecified).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_Zip_ArchiveScanFailed", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); - - TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScan).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingDisabled", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); - - DisableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); - - TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScan).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingEnabled", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideForExtractInstallerFromArchive(context); - OverrideForVerifyAndSetNestedInstaller(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); - - EnableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); - - TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify override message is displayed to the user. - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScanOverridden).get()) != std::string::npos); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("ExtractInstallerFromArchive_InvalidZip", "[InstallFlow][workflow]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - - // Provide an invalid zip file which should be handled appropriately. - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - context << ExtractFilesFromArchive; - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveFailed).get()) != std::string::npos); -} - -TEST_CASE("ExtractInstallerFromArchiveWithTar", "[InstallFlow][workflow]") -{ - TestCommon::TestUserSettings testSettings; - testSettings.Set(AppInstaller::Archive::ExtractionMethod::Tar); - - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - - OverrideForShellExecute(context); - OverrideForVerifyAndSetNestedInstaller(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); - - TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); - TestHook::SetExtractArchiveWithTarResult_Override setExtractArchiveWithTarResultOverride(ERROR_SUCCESS); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveSucceeded).get()) != std::string::npos); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("ExtractInstallerFromArchiveWithTar_InvalidZip", "[InstallFlow][workflow]") -{ - TestCommon::TestUserSettings testSettings; - testSettings.Set(AppInstaller::Archive::ExtractionMethod::Tar); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - - // Provide an invalid zip file which should be handled appropriately. - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - context << ExtractFilesFromArchive; - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveFailed).get()) != std::string::npos); -} - -TEST_CASE("MSStoreInstallFlowWithTestManifest", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestMSStoreInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForMSStore(context, false); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("9WZDNCRFJ364") != std::string::npos); -} - -TEST_CASE("MSStoreInstallFlow_MachineScopeProvision", "[InstallFlow][MSStore]") -{ - if (!AppInstaller::Runtime::IsRunningAsAdmin() || AppInstaller::Runtime::IsRunningAsSystem()) - { - WARN("Test requires running as admin but not SYSTEM. Skipped."); - return; - } - - TestHook::SetForceProvisionAfterInstall_Override forceProvisionOverride(true); - - AppInstaller::ProgressCallback progress; - AppInstaller::MSStore::MSStoreOperation installOperation( - AppInstaller::MSStore::MSStoreOperationType::Install, - L"9NVTPZWRC6KQ", - AppInstaller::Manifest::ScopeEnum::User, - true, - false); - - HRESULT hr = installOperation.StartAndWaitForOperation(progress); - REQUIRE(SUCCEEDED(hr)); - - // Verify the package is now provisioned. - winrt::Windows::Management::Deployment::PackageManager packageManager; - bool isProvisioned = false; - for (auto const& pkg : packageManager.FindProvisionedPackages()) - { - if (pkg.Id().FamilyName() == L"Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe") - { - isProvisioned = true; - break; - } - } - REQUIRE(isProvisioned); -} - -TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForMSIX(context); - OverrideForUpdateInstallerMotw(context); - // Todo: point to files from our repo when the repo goes public - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and a local file is used as package Uri. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - Uri uri = Uri(ConvertToUTF16(installResultStr)); - REQUIRE(uri.SchemeName() == L"file"); -} - -TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForMSIX(context); - OverrideForCheckExistingInstaller(context); - // Todo: point to files from our repo when the repo goes public - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and a http address is used as package Uri. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - Uri uri = Uri(ConvertToUTF16(installResultStr)); - REQUIRE(uri.SchemeName() == L"https"); -} - -TEST_CASE("MsiInstallFlow_DirectMsi", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestMsiInstalled.txt"); - - TestCommon::TestUserSettings testSettings; - testSettings.Set(true); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForDirectMsi(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Silent); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/quiet") != std::string::npos); -} - -TEST_CASE("InstallFlow_Portable", "[InstallFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); - TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForPortableInstallFlow(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); -} - -TEST_CASE("InstallFlow_Portable_SymlinkCreationFail", "[InstallFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); - std::ostringstream installOutput; - TestContext installContext{ installOutput, std::cin }; - auto PreviousThreadGlobals = installContext.SetForCurrentThread(); - OverridePortableInstaller(installContext); - TestHook::SetCreateSymlinkResult_Override createSymlinkResultOverride(false); - const auto& targetDirectory = tempDirectory.GetPath(); - const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; - installContext.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); - installContext.Args.AddArg(Execution::Args::Type::InstallLocation, targetDirectory.u8string()); - installContext.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); - - InstallCommand install({}); - install.Execute(installContext); - - { - INFO(installOutput.str()); - - // Use CHECK to allow the uninstall to still occur - CHECK(std::filesystem::exists(portableTargetPath)); - CHECK(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); - } - - // Perform uninstall - std::ostringstream uninstallOutput; - TestContext uninstallContext{ uninstallOutput, std::cin }; - auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); - uninstallContext.Args.AddArg(Execution::Args::Type::Name, "AppInstaller Test Portable Exe"sv); - uninstallContext.Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); - - UninstallCommand uninstall({}); - uninstall.Execute(uninstallContext); - INFO(uninstallOutput.str()); - REQUIRE_FALSE(std::filesystem::exists(portableTargetPath)); -} - -TEST_CASE("PortableInstallFlow_UserScope", "[InstallFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); - TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForPortableInstallFlow(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); - context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); -} - -TEST_CASE("PortableInstallFlow_MachineScope", "[InstallFlow][workflow]") -{ - if (!AppInstaller::Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); - TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForPortableInstallFlow(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); - context.Args.AddArg(Execution::Args::Type::InstallScope, "machine"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); -} - -TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow][workflow]") -{ - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Default Msi type with no args passed in, no switches specified in manifest - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/passive") != std::string::npos); - REQUIRE(installerArgs.find(FileLogger::DefaultPrefix()) != std::string::npos); - REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); - REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml")); - context.Args.AddArg(Execution::Args::Type::Silent); - context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); - context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/quiet") != std::string::npos); - REQUIRE(installerArgs.find("/log \"MyLog.log\"") != std::string::npos); - REQUIRE(installerArgs.find("TARGETDIR=\"MyDir\"") != std::string::npos); - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yaml")); - context.Args.AddArg(Execution::Args::Type::Silent); - context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); - context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Default Inno type with no args passed in, no switches specified in manifest - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/SILENT") != std::string::npos); - REQUIRE(installerArgs.find(FileLogger::DefaultPrefix()) != std::string::npos); - REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); - REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml")); - context.Args.AddArg(Execution::Args::Type::Silent); - context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); - context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); - REQUIRE(installerArgs.find("/LOG=\"MyLog.log\"") != std::string::npos); - REQUIRE(installerArgs.find("/DIR=\"MyDir\"") != std::string::npos); - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Args.AddArg(Execution::Args::Type::Silent); - context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); - context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest and --custom argument used in cli - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Args.AddArg(Execution::Args::Type::Silent); - context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); - context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); - context.Args.AddArg(Execution::Args::Type::CustomSwitches, "/MyAppendedSwitch"sv); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("/MyAppendedSwitch") != std::string::npos); // Use declaration from argument - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest and whitespace-only --custom argument used in cli - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Args.AddArg(Execution::Args::Type::Silent); - context.Args.AddArg(Execution::Args::Type::CustomSwitches, "\t"sv); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest - REQUIRE(installerArgs.find("\t") == std::string::npos); // Whitespace only Custom switches should not be appended - } - - { - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - // Override switch specified. The whole arg passed to installer is overridden. - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Args.AddArg(Execution::Args::Type::Silent); - context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); - context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); - context.Args.AddArg(Execution::Args::Type::Override, "/OverrideEverything"sv); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - std::string installerArgs = context.Get(); - REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch - } -} - -TEST_CASE("ShellExecuteHandlerInstallerArgs_LogNamingStrategy", "[InstallFlow][workflow]") -{ - SECTION("Manifest") - { - TestUserSettings testSettings; - testSettings.Set(LogNameStrategy::Manifest); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); - REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); - - REQUIRE(context.Contains(Data::LogPath)); - auto logPath = context.Get(); - REQUIRE(logPath.filename().u8string().find(manifest.Id) != std::string::npos); - } - - SECTION("Timestamp") - { - TestUserSettings testSettings; - testSettings.Set(LogNameStrategy::Timestamp); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find(manifest.Id) == std::string::npos); - REQUIRE(installerArgs.find(manifest.Version) == std::string::npos); - - REQUIRE(context.Contains(Data::LogPath)); - auto logPath = context.Get(); - REQUIRE(logPath.extension().u8string() == std::string{ FileLogger::DefaultExt() }); - REQUIRE(logPath.stem().u8string().find(manifest.Id) == std::string::npos); - } - - SECTION("Guid") - { - TestUserSettings testSettings; - testSettings.Set(LogNameStrategy::Guid); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find(manifest.Id) == std::string::npos); - - REQUIRE(context.Contains(Data::LogPath)); - auto logPath = context.Get(); - REQUIRE(logPath.extension().u8string() == std::string{ FileLogger::DefaultExt() }); - // A GUID string is 36 characters: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx - REQUIRE(logPath.stem().u8string().size() == 36); - } - - SECTION("ShortGuid") - { - TestUserSettings testSettings; - testSettings.Set(LogNameStrategy::ShortGuid); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); - context.Add(manifest); - context.Add(manifest.Installers.at(0)); - context << GetInstallerArgs; - - std::string installerArgs = context.Get(); - REQUIRE(installerArgs.find(manifest.Id) == std::string::npos); - - REQUIRE(context.Contains(Data::LogPath)); - auto logPath = context.Get(); - REQUIRE(logPath.extension().u8string() == std::string{ FileLogger::DefaultExt() }); - // A short GUID is the first 8 characters of a full GUID - REQUIRE(logPath.stem().u8string().size() == 8); - } -} - -TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne }), true); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow][workflow]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForOpenSource(context, CreateTestSource({}), true); - context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnZero"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify proper message is printed - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NoPackageFound).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow][workflow]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnTwo }), true); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnTwo.Query); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify proper message is printed - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::MultiplePackagesFound).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_LicenseAgreement", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify agreements are shown - REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); - REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); - REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); - REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); - - // Verify Installer is called. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); -} - -TEST_CASE("InstallFlow_LicenseAgreement_Prompt", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - // Accept the agreements by saying "Yes" at the prompt - std::istringstream installInput{ "y" }; - - std::ostringstream installOutput; - TestContext context{ installOutput, installInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify prompt was shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsPrompt).get()) != std::string::npos); - - // Verify agreements are shown - REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); - REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); - REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); - REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); - - // Verify Installer is called. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); -} - -TEST_CASE("InstallFlow_LicenseAgreement_NotAccepted", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - // Say "No" at the agreements prompt - std::istringstream installInput{ "n" }; - - std::ostringstream installOutput; - TestContext context{ installOutput, installInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify agreements are shown - REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); - REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); - REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); - REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); - - // Verify installation failed - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); - REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); -} - -TEST_CASE("InstallFlowMultiLocale_RequirementNotSatisfied", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); - - // Verify Installer was not called - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); -} - -TEST_CASE("InstallFlowMultiLocale_RequirementSatisfied", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Locale, "fr-FR"sv); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/fr-FR") != std::string::npos); -} - -TEST_CASE("InstallFlowMultiLocale_PreferenceNoBetterLocale", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); - - TestUserSettings settings; - settings.Set({ "zh-CN" }); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/unknown") != std::string::npos); -} - -TEST_CASE("InstallFlowMultiLocale_PreferenceWithBetterLocale", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); - - TestUserSettings settings; - settings.Set({ "en-US" }); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/en-GB") != std::string::npos); -} - -TEST_CASE("InstallFlow_InstallMultiple", "[InstallFlow][workflow][MultiQuery]") -{ - TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); - TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForMSIX(context); - OverrideForShellExecute(context); - OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix }), true); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); - - InstallCommand installCommand({}); - installCommand.Execute(context); - INFO(installOutput.str()); - - // Verify all packages were installed - REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); - REQUIRE(std::filesystem::exists(msixInstallResultPath.GetPath())); -} - -TEST_CASE("InstallFlow_InstallMultiple_SearchFailed", "[InstallFlow][workflow][MultiQuery]") -{ - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe }), true); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); - - InstallCommand installCommand({}); - installCommand.Execute(context); - INFO(installOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); -} - -TEST_CASE("InstallFlow_InstallAcquiresLock", "[InstallFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne }), true); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); - - wil::unique_event enteredShellExecute; - enteredShellExecute.create(); - wil::unique_event canLeaveShellExecute; - canLeaveShellExecute.create(); - AppInstaller::ProgressCallback progress; - - context.Override({ ShellExecuteInstallImpl, [&](TestContext& context) - { - enteredShellExecute.SetEvent(); - canLeaveShellExecute.wait(500); - ShellExecuteInstallImpl(context); - }}); - - { - std::thread otherThread([&]() { - InstallCommand install({}); - install.Execute(context); - }); - - REQUIRE(enteredShellExecute.wait(5000)); - - AppInstaller::Synchronization::CrossProcessInstallLock mainThreadLock; - REQUIRE(!mainThreadLock.TryAcquireNoWait()); - - canLeaveShellExecute.SetEvent(); - otherThread.join(); - } - - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("InstallFlow_InstallWithReboot", "[InstallFlow][workflow][reboot]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TestUserSettings testSettings; - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::AllowReboot); - - context.Override({ ShellExecuteInstallImpl, [&](TestContext& context) - { - // APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_INSTALL (should be treated as an installer error) - context.Add(10); - } }); - - SECTION("Reboot success") - { - TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(true); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(context.IsTerminated()); - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); - REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); - } - SECTION("Reboot failed") - { - TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(false); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(context.IsTerminated()); - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestHooks.h" +#include "AppInstallerRuntime.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt::Windows::Foundation; +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Logging; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Utility::literals; + +void OverrideForDirectMsi(TestContext& context) +{ + OverrideForCheckExistingInstaller(context); + + context.Override({ DownloadInstallerFile, [](TestContext& context) + { + context.Add({ {}, {} }); + // We don't have an msi installer for tests, but we won't execute it anyway + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + } }); + + context.Override({ RenameDownloadedInstaller, [](TestContext&) + { + } }); + + OverrideForUpdateInstallerMotw(context); + + context.Override({ DirectMSIInstallImpl, [](TestContext& context) + { + // Write out the install command + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestMsiInstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + file << context.Get(); + file.close(); + + context.Add(0); + } }); +} + +TEST_CASE("ExeInstallFlowWithTestManifest", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlow_RenameFromEncodedUrl", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCheckExistingInstaller(context); + context.Override({ DownloadInstallerFile, [](TestContext& context) + { + context.Add({ {}, {} }); + auto installerPath = std::filesystem::temp_directory_path(); + installerPath /= "EncodedUrlTest.exe"; + std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); + context.Add(installerPath); + } }); + OverrideForUpdateInstallerMotw(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_EncodedUrl.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/encodedUrl") != std::string::npos); +} + +TEST_CASE("InstallFlow_RenameFromInvalidFileCharacterUrl", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCheckExistingInstaller(context); + context.Override({ DownloadInstallerFile, [](TestContext& context) + { + context.Add({ {}, {} }); + auto installerPath = std::filesystem::temp_directory_path(); + installerPath /= "InvalidFileCharacterUrlTest.exe"; + std::filesystem::copy(TestDataFile("AppInstallerTestExeInstaller.exe"), installerPath, std::filesystem::copy_options::overwrite_existing); + context.Add(installerPath); + } }); + OverrideForUpdateInstallerMotw(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InvalidFileCharacterUrl.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/invalidFileCharacterUrl") != std::string::npos); +} + +TEST_CASE("InstallFlowNonZeroExitCode", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NonZeroExitCode.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(context.GetTerminationHR() == S_OK); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/ExitCode 0x80070005") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlow_InstallationNotes", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallationNotes.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify installation notes are displayed + REQUIRE(context.GetTerminationHR() == S_OK); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find("testInstallationNotes") != std::string::npos); +} + +TEST_CASE("InstallFlow_UnsupportedArguments_Warn", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Log, tempDirectory); + + InstallCommand install({}); + context.SetExecutingCommand(&install); + install.Execute(context); + INFO(installOutput.str()); + + // Verify unsupported arguments warn message is shown + REQUIRE(context.GetTerminationHR() == S_OK); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); + REQUIRE(installOutput.str().find("-o,--log") != std::string::npos); +} + +TEST_CASE("InstallFlow_UnsupportedArguments_Error", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + + InstallCommand install({}); + context.SetExecutingCommand(&install); + install.Execute(context); + INFO(installOutput.str()); + + // Verify unsupported arguments error message is shown + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); + REQUIRE(installOutput.str().find("-l,--location") != std::string::npos); +} + +TEST_CASE("InstallFlow_UnsupportedArguments_NotProvided") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_UnsupportedArguments.yaml").GetPath().u8string()); + + InstallCommand install({}); + context.SetExecutingCommand(&install); + install.Execute(context); + INFO(installOutput.str()); + + // Verify unsupported arguments error message is not shown when not provided + REQUIRE(context.GetTerminationHR() == S_OK); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) == std::string::npos); + REQUIRE(installOutput.str().find("-o,--log") == std::string::npos); + REQUIRE(installOutput.str().find("-l,--location") == std::string::npos); +} + +TEST_CASE("InstallFlow_ExpectedReturnCodes", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Override, "/ExitCode 8"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify install failed with the right message + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallFlowReturnCodeContactSupport).get()) != std::string::npos); + REQUIRE(installOutput.str().find("https://TestReturnResponseUrl") != std::string::npos); +} + +TEST_CASE("InstallFlowWithNonApplicableArchitecture", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_NoApplicableArchitecture.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("InstallFlow_Zip_Exe", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideForExtractInstallerFromArchive(context); + OverrideForVerifyAndSetNestedInstaller(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); + + TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlow_Zip_BadRelativePath", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideForExtractInstallerFromArchive(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); + + TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + auto relativePath = context.Get().parent_path() / "extracted" / "relativeFilePath"; + auto expectedMessage = Resource::String::NestedInstallerNotFound(AppInstaller::Utility::LocIndString{ relativePath.u8string()}); + REQUIRE(installOutput.str().find(Resource::LocString(expectedMessage).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_Zip_MissingNestedInstaller", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_MissingNestedInstaller.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NestedInstallerNotSpecified).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_Zip_UnsupportedNestedInstaller", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_UnsupportedNestedInstaller.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, ERROR_NOT_SUPPORTED); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NestedInstallerNotSupported).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_Zip_MultipleNonPortableNestedInstallers", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_MultipleNonPortableNestedInstallers.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INVALID_MANIFEST); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::MultipleUnsupportedNestedInstallersSpecified).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_Zip_ArchiveScanFailed", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); + + TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScan).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingDisabled", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); + + DisableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); + + TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScan).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_Zip_ArchiveScanOverride_AdminSettingEnabled", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideForExtractInstallerFromArchive(context); + OverrideForVerifyAndSetNestedInstaller(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::IgnoreLocalArchiveMalwareScan); + + EnableAdminSetting(AppInstaller::Settings::BoolAdminSetting::LocalArchiveMalwareScanOverride); + + TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(false); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify override message is displayed to the user. + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ArchiveFailedMalwareScanOverridden).get()) != std::string::npos); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("ExtractInstallerFromArchive_InvalidZip", "[InstallFlow][workflow]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + + // Provide an invalid zip file which should be handled appropriately. + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + context << ExtractFilesFromArchive; + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveFailed).get()) != std::string::npos); +} + +TEST_CASE("ExtractInstallerFromArchiveWithTar", "[InstallFlow][workflow]") +{ + TestCommon::TestUserSettings testSettings; + testSettings.Set(AppInstaller::Archive::ExtractionMethod::Tar); + + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + + OverrideForShellExecute(context); + OverrideForVerifyAndSetNestedInstaller(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); + + TestHook::SetScanArchiveResult_Override scanArchiveResultOverride(true); + TestHook::SetExtractArchiveWithTarResult_Override setExtractArchiveWithTarResultOverride(ERROR_SUCCESS); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveSucceeded).get()) != std::string::npos); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("ExtractInstallerFromArchiveWithTar_InvalidZip", "[InstallFlow][workflow]") +{ + TestCommon::TestUserSettings testSettings; + testSettings.Set(AppInstaller::Archive::ExtractionMethod::Tar); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + + // Provide an invalid zip file which should be handled appropriately. + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + context << ExtractFilesFromArchive; + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::ExtractArchiveFailed).get()) != std::string::npos); +} + +TEST_CASE("MSStoreInstallFlowWithTestManifest", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestMSStoreInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForMSStore(context, false); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("9WZDNCRFJ364") != std::string::npos); +} + +TEST_CASE("MSStoreInstallFlow_MachineScopeProvision", "[InstallFlow][MSStore]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin() || AppInstaller::Runtime::IsRunningAsSystem()) + { + WARN("Test requires running as admin but not SYSTEM. Skipped."); + return; + } + + TestHook::SetForceProvisionAfterInstall_Override forceProvisionOverride(true); + + AppInstaller::ProgressCallback progress; + AppInstaller::MSStore::MSStoreOperation installOperation( + AppInstaller::MSStore::MSStoreOperationType::Install, + L"9NVTPZWRC6KQ", + AppInstaller::Manifest::ScopeEnum::User, + true, + false); + + HRESULT hr = installOperation.StartAndWaitForOperation(progress); + REQUIRE(SUCCEEDED(hr)); + + // Verify the package is now provisioned. + winrt::Windows::Management::Deployment::PackageManager packageManager; + bool isProvisioned = false; + for (auto const& pkg : packageManager.FindProvisionedPackages()) + { + if (pkg.Id().FamilyName() == L"Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe") + { + isProvisioned = true; + break; + } + } + REQUIRE(isProvisioned); +} + +TEST_CASE("MsixInstallFlow_DownloadFlow", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForMSIX(context); + OverrideForUpdateInstallerMotw(context); + // Todo: point to files from our repo when the repo goes public + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_DownloadFlow.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and a local file is used as package Uri. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + Uri uri = Uri(ConvertToUTF16(installResultStr)); + REQUIRE(uri.SchemeName() == L"file"); +} + +TEST_CASE("MsixInstallFlow_StreamingFlow", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestMsixInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForMSIX(context); + OverrideForCheckExistingInstaller(context); + // Todo: point to files from our repo when the repo goes public + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and a http address is used as package Uri. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + Uri uri = Uri(ConvertToUTF16(installResultStr)); + REQUIRE(uri.SchemeName() == L"https"); +} + +TEST_CASE("MsiInstallFlow_DirectMsi", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestMsiInstalled.txt"); + + TestCommon::TestUserSettings testSettings; + testSettings.Set(true); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForDirectMsi(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Silent); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/quiet") != std::string::npos); +} + +TEST_CASE("InstallFlow_Portable", "[InstallFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); + TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForPortableInstallFlow(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); +} + +TEST_CASE("InstallFlow_Portable_SymlinkCreationFail", "[InstallFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); + std::ostringstream installOutput; + TestContext installContext{ installOutput, std::cin }; + auto PreviousThreadGlobals = installContext.SetForCurrentThread(); + OverridePortableInstaller(installContext); + TestHook::SetCreateSymlinkResult_Override createSymlinkResultOverride(false); + const auto& targetDirectory = tempDirectory.GetPath(); + const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; + installContext.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); + installContext.Args.AddArg(Execution::Args::Type::InstallLocation, targetDirectory.u8string()); + installContext.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); + + InstallCommand install({}); + install.Execute(installContext); + + { + INFO(installOutput.str()); + + // Use CHECK to allow the uninstall to still occur + CHECK(std::filesystem::exists(portableTargetPath)); + CHECK(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); + } + + // Perform uninstall + std::ostringstream uninstallOutput; + TestContext uninstallContext{ uninstallOutput, std::cin }; + auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); + uninstallContext.Args.AddArg(Execution::Args::Type::Name, "AppInstaller Test Portable Exe"sv); + uninstallContext.Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); + + UninstallCommand uninstall({}); + uninstall.Execute(uninstallContext); + INFO(uninstallOutput.str()); + REQUIRE_FALSE(std::filesystem::exists(portableTargetPath)); +} + +TEST_CASE("PortableInstallFlow_UserScope", "[InstallFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); + TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForPortableInstallFlow(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); +} + +TEST_CASE("PortableInstallFlow_MachineScope", "[InstallFlow][workflow]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); + TestCommon::TempFile portableInstallResultPath("TestPortableInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForPortableInstallFlow(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Portable.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + context.Args.AddArg(Execution::Args::Type::InstallScope, "machine"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + REQUIRE(std::filesystem::exists(portableInstallResultPath.GetPath())); +} + +TEST_CASE("ShellExecuteHandlerInstallerArgs", "[InstallFlow][workflow]") +{ + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Default Msi type with no args passed in, no switches specified in manifest + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/passive") != std::string::npos); + REQUIRE(installerArgs.find(FileLogger::DefaultPrefix()) != std::string::npos); + REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); + REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Msi type with /silent and /log and /custom and /installlocation, no switches specified in manifest + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_NoSwitches.yaml")); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/quiet") != std::string::npos); + REQUIRE(installerArgs.find("/log \"MyLog.log\"") != std::string::npos); + REQUIRE(installerArgs.find("TARGETDIR=\"MyDir\"") != std::string::npos); + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Msi type with /silent and /log and /custom and /installlocation, switches specified in manifest + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Msi_WithSwitches.yaml")); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Default Inno type with no args passed in, no switches specified in manifest + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/SILENT") != std::string::npos); + REQUIRE(installerArgs.find(FileLogger::DefaultPrefix()) != std::string::npos); + REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); + REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Inno type with /silent and /log and /custom and /installlocation, no switches specified in manifest + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml")); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/VERYSILENT") != std::string::npos); + REQUIRE(installerArgs.find("/LOG=\"MyLog.log\"") != std::string::npos); + REQUIRE(installerArgs.find("/DIR=\"MyDir\"") != std::string::npos); + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest and --custom argument used in cli + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); + context.Args.AddArg(Execution::Args::Type::CustomSwitches, "/MyAppendedSwitch"sv); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mylog=\"MyLog.log\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/mycustom") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/myinstalldir=\"MyDir\"") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("/MyAppendedSwitch") != std::string::npos); // Use declaration from argument + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Inno type with /silent and /log and /custom and /installlocation, switches specified in manifest and whitespace-only --custom argument used in cli + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::CustomSwitches, "\t"sv); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find("/mysilent") != std::string::npos); // Use declaration in manifest + REQUIRE(installerArgs.find("\t") == std::string::npos); // Whitespace only Custom switches should not be appended + } + + { + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + // Override switch specified. The whole arg passed to installer is overridden. + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Args.AddArg(Execution::Args::Type::Silent); + context.Args.AddArg(Execution::Args::Type::Log, "MyLog.log"sv); + context.Args.AddArg(Execution::Args::Type::InstallLocation, "MyDir"sv); + context.Args.AddArg(Execution::Args::Type::Override, "/OverrideEverything"sv); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + std::string installerArgs = context.Get(); + REQUIRE(installerArgs == "/OverrideEverything"); // Use value specified in override switch + } +} + +TEST_CASE("ShellExecuteHandlerInstallerArgs_LogNamingStrategy", "[InstallFlow][workflow]") +{ + SECTION("Manifest") + { + TestUserSettings testSettings; + testSettings.Set(LogNameStrategy::Manifest); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find(manifest.Id) != std::string::npos); + REQUIRE(installerArgs.find(manifest.Version) != std::string::npos); + + REQUIRE(context.Contains(Data::LogPath)); + auto logPath = context.Get(); + REQUIRE(logPath.filename().u8string().find(manifest.Id) != std::string::npos); + } + + SECTION("Timestamp") + { + TestUserSettings testSettings; + testSettings.Set(LogNameStrategy::Timestamp); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find(manifest.Id) == std::string::npos); + REQUIRE(installerArgs.find(manifest.Version) == std::string::npos); + + REQUIRE(context.Contains(Data::LogPath)); + auto logPath = context.Get(); + REQUIRE(logPath.extension().u8string() == std::string{ FileLogger::DefaultExt() }); + REQUIRE(logPath.stem().u8string().find(manifest.Id) == std::string::npos); + } + + SECTION("Guid") + { + TestUserSettings testSettings; + testSettings.Set(LogNameStrategy::Guid); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find(manifest.Id) == std::string::npos); + + REQUIRE(context.Contains(Data::LogPath)); + auto logPath = context.Get(); + REQUIRE(logPath.extension().u8string() == std::string{ FileLogger::DefaultExt() }); + // A GUID string is 36 characters: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + REQUIRE(logPath.stem().u8string().size() == 36); + } + + SECTION("ShortGuid") + { + TestUserSettings testSettings; + testSettings.Set(LogNameStrategy::ShortGuid); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallerArgTest_Inno_WithSwitches.yaml")); + context.Add(manifest); + context.Add(manifest.Installers.at(0)); + context << GetInstallerArgs; + + std::string installerArgs = context.Get(); + REQUIRE(installerArgs.find(manifest.Id) == std::string::npos); + + REQUIRE(context.Contains(Data::LogPath)); + auto logPath = context.Get(); + REQUIRE(logPath.extension().u8string() == std::string{ FileLogger::DefaultExt() }); + // A short GUID is the first 8 characters of a full GUID + REQUIRE(logPath.stem().u8string().size() == 8); + } +} + +TEST_CASE("InstallFlow_SearchAndInstall", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne }), true); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlow_SearchFoundNoApp", "[InstallFlow][workflow]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({}), true); + context.Args.AddArg(Execution::Args::Type::Query, "TestQueryReturnZero"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify proper message is printed + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::NoPackageFound).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_SearchFoundMultipleApp", "[InstallFlow][workflow]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnTwo }), true); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnTwo.Query); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify proper message is printed + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::MultiplePackagesFound).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_LicenseAgreement", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify agreements are shown + REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); + REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); + REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); + REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); + + // Verify Installer is called. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("InstallFlow_LicenseAgreement_Prompt", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + // Accept the agreements by saying "Yes" at the prompt + std::istringstream installInput{ "y" }; + + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt was shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsPrompt).get()) != std::string::npos); + + // Verify agreements are shown + REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); + REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); + REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); + REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); + + // Verify Installer is called. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("InstallFlow_LicenseAgreement_NotAccepted", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + // Say "No" at the agreements prompt + std::istringstream installInput{ "n" }; + + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify agreements are shown + REQUIRE(installOutput.str().find("Agreement with text") != std::string::npos); + REQUIRE(installOutput.str().find("This is the text of the agreement.") != std::string::npos); + REQUIRE(installOutput.str().find("Agreement with URL") != std::string::npos); + REQUIRE(installOutput.str().find("https://TestAgreementUrl") != std::string::npos); + + // Verify installation failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); + REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); +} + +TEST_CASE("InstallFlowMultiLocale_RequirementNotSatisfied", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER); + + // Verify Installer was not called + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("InstallFlowMultiLocale_RequirementSatisfied", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Locale, "fr-FR"sv); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/fr-FR") != std::string::npos); +} + +TEST_CASE("InstallFlowMultiLocale_PreferenceNoBetterLocale", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({ "zh-CN" }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/unknown") != std::string::npos); +} + +TEST_CASE("InstallFlowMultiLocale_PreferenceWithBetterLocale", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-MultiLocale.yaml").GetPath().u8string()); + + TestUserSettings settings; + settings.Set({ "en-US" }); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/en-GB") != std::string::npos); +} + +TEST_CASE("InstallFlow_InstallMultiple", "[InstallFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeInstallResultPath("TestExeInstalled.txt"); + TestCommon::TempFile msixInstallResultPath("TestMsixInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForMSIX(context); + OverrideForShellExecute(context); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix }), true); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + InstallCommand installCommand({}); + installCommand.Execute(context); + INFO(installOutput.str()); + + // Verify all packages were installed + REQUIRE(std::filesystem::exists(exeInstallResultPath.GetPath())); + REQUIRE(std::filesystem::exists(msixInstallResultPath.GetPath())); +} + +TEST_CASE("InstallFlow_InstallMultiple_SearchFailed", "[InstallFlow][workflow][MultiQuery]") +{ + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestInstaller_Exe }), true); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + InstallCommand installCommand({}); + installCommand.Execute(context); + INFO(installOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); +} + +TEST_CASE("InstallFlow_InstallAcquiresLock", "[InstallFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne }), true); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); + + wil::unique_event enteredShellExecute; + enteredShellExecute.create(); + wil::unique_event canLeaveShellExecute; + canLeaveShellExecute.create(); + AppInstaller::ProgressCallback progress; + + context.Override({ ShellExecuteInstallImpl, [&](TestContext& context) + { + enteredShellExecute.SetEvent(); + canLeaveShellExecute.wait(500); + ShellExecuteInstallImpl(context); + }}); + + { + std::thread otherThread([&]() { + InstallCommand install({}); + install.Execute(context); + }); + + REQUIRE(enteredShellExecute.wait(5000)); + + AppInstaller::Synchronization::CrossProcessInstallLock mainThreadLock; + REQUIRE(!mainThreadLock.TryAcquireNoWait()); + + canLeaveShellExecute.SetEvent(); + otherThread.join(); + } + + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlow_InstallWithReboot", "[InstallFlow][workflow][reboot]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::AllowReboot); + + context.Override({ ShellExecuteInstallImpl, [&](TestContext& context) + { + // APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_INSTALL (should be treated as an installer error) + context.Add(10); + } }); + + SECTION("Reboot success") + { + TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(true); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(context.IsTerminated()); + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); + REQUIRE_FALSE(installOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); + } + SECTION("Reboot failed") + { + TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(false); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(context.IsTerminated()); + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); + } +} diff --git a/src/AppInstallerCLITests/InstallerMetadataCollectionContext.cpp b/src/AppInstallerCLITests/InstallerMetadataCollectionContext.cpp index 840162d2c6..073cad53f1 100644 --- a/src/AppInstallerCLITests/InstallerMetadataCollectionContext.cpp +++ b/src/AppInstallerCLITests/InstallerMetadataCollectionContext.cpp @@ -1,1269 +1,1269 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSource.h" -#include "TestHooks.h" - -#include - -using namespace AppInstaller; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Correlation; -using namespace AppInstaller::Repository::Metadata; -using namespace TestCommon; - -namespace -{ - // Indicates to set minimal defaults on TestInput - struct MinimalDefaults_t {} MinimalDefaults; - - struct TestInput - { - TestInput() = default; - - TestInput(MinimalDefaults_t) - { - Version = "1.0"; - SupportedMetadataVersion = "1.0"; - SubmissionIdentifier = "1"; - InstallerHash = "ABCD"; - PackageData = std::make_optional(); - PackageData->DefaultLocalization.Locale = "en-us"; - PackageData->DefaultLocalization.Add("Name"); - PackageData->DefaultLocalization.Add("Publisher"); - } - - TestInput(MinimalDefaults_t, const std::string& productVersion, const std::string& productCode, Manifest::InstallerTypeEnum installerType) : TestInput(MinimalDefaults) - { - CurrentMetadata = std::make_optional(); - CurrentMetadata->SchemaVersion.Assign("1.0"); - CurrentMetadata->ProductVersionMin.Assign(productVersion); - CurrentMetadata->ProductVersionMax.Assign(productVersion); - auto& installerMetadata = CurrentMetadata->InstallerMetadataMap[InstallerHash.value()]; - installerMetadata.SubmissionIdentifier = SubmissionIdentifier.value(); - installerMetadata.AppsAndFeaturesEntries.push_back({}); - auto& entry = installerMetadata.AppsAndFeaturesEntries.back(); - entry.DisplayName = PackageData->DefaultLocalization.Get(); - entry.Publisher = PackageData->DefaultLocalization.Get(); - entry.DisplayVersion = productVersion; - entry.ProductCode = productCode; - entry.InstallerType = installerType; - } - - std::optional Version; - std::optional SupportedMetadataVersion; - std::optional CurrentMetadata; - std::optional SubmissionData; - std::optional SubmissionIdentifier; - std::optional InstallerHash; - // Schema 1.0 only cares about DefaultLocale and Locales - std::optional PackageData; - - std::wstring ToJSON() - { - web::json::value json; - - if (Version) - { - json[L"version"] = JSON::GetStringValue(Version.value()); - } - - if (SupportedMetadataVersion) - { - json[L"supportedMetadataVersion"] = JSON::GetStringValue(SupportedMetadataVersion.value()); - } - - if (CurrentMetadata) - { - json[L"currentMetadata"] = CurrentMetadata->ToJson(CurrentMetadata->SchemaVersion, 0); - } - - if (SubmissionData) - { - json[L"submissionData"] = SubmissionData.value(); - } - else if (SubmissionIdentifier) - { - web::json::value submissionData; - submissionData[L"submissionIdentifier"] = JSON::GetStringValue(SubmissionIdentifier.value()); - json[L"submissionData"] = std::move(submissionData); - } - - if (InstallerHash || PackageData) - { - web::json::value packageData; - - if (InstallerHash) - { - packageData[L"installerHash"] = JSON::GetStringValue(InstallerHash.value()); - } - - if (PackageData) - { - packageData[L"DefaultLocale"] = LocaleToJSON(PackageData->DefaultLocalization); - - // TODO: Implement other locales - } - - json[L"packageData"] = std::move(packageData); - } - - return json.serialize(); - } - - private: - web::json::value LocaleToJSON(const Manifest::ManifestLocalization& localization) const - { - web::json::value locale; - - locale[L"PackageLocale"] = JSON::GetStringValue(localization.Locale); - - if (localization.Contains(Manifest::Localization::PackageName)) - { - locale[L"PackageName"] = JSON::GetStringValue(localization.Get()); - } - - if (localization.Contains(Manifest::Localization::Publisher)) - { - locale[L"Publisher"] = JSON::GetStringValue(localization.Get()); - } - - // TODO: Implement any other needed fields - - return locale; - } - }; - - struct TestOutput - { - TestOutput(const std::string& json) : OriginalJSON(json) - { - web::json::value input = web::json::value::parse(Utility::ConvertToUTF16(json)); - - auto versionString = JSON::GetRawStringValueFromJsonNode(input, L"version"); - if (versionString) - { - Version = std::move(versionString); - } - - auto submissionDataValue = JSON::GetJsonValueFromNode(input, L"submissionData"); - if (submissionDataValue) - { - SubmissionData = submissionDataValue.value(); - } - - auto installerHashString = JSON::GetRawStringValueFromJsonNode(input, L"installerHash"); - if (installerHashString) - { - InstallerHash = std::move(installerHashString); - } - - auto statusString = JSON::GetRawStringValueFromJsonNode(input, L"status"); - if (statusString) - { - Status = std::move(statusString); - } - - auto metadataValue = JSON::GetJsonValueFromNode(input, L"metadata"); - if (metadataValue && !metadataValue->get().is_null()) - { - Metadata = std::make_optional(); - Metadata->FromJson(metadataValue->get()); - } - - auto diagnosticsValue = JSON::GetJsonValueFromNode(input, L"diagnostics"); - if (diagnosticsValue) - { - auto errorHRNumber = JSON::GetRawIntValueFromJsonNode(diagnosticsValue.value(), L"errorHR"); - if (errorHRNumber) - { - ErrorHR = std::move(errorHRNumber); - } - - auto errorTextString = JSON::GetRawStringValueFromJsonNode(diagnosticsValue.value(), L"errorText"); - if (errorTextString) - { - ErrorText = std::move(errorTextString); - } - } - } - - std::string OriginalJSON; - - std::optional Version; - std::optional SubmissionData; - std::optional InstallerHash; - std::optional Status; - std::optional Metadata; - std::optional ErrorHR; - std::optional ErrorText; - - bool IsError() const - { - return Status && Status.value() == "Error"; - } - - bool IsSuccess() const - { - return Status && Status.value() == "Success"; - } - - bool IsLowConfidence() const - { - return Status && Status.value() == "LowConfidence"; - } - - void ValidateFieldPresence() const - { - REQUIRE(Version); - REQUIRE(SubmissionData); - REQUIRE(InstallerHash); - REQUIRE(Status); - - REQUIRE(IsSuccess() == Metadata.has_value()); - - REQUIRE(IsError() == ErrorHR.has_value()); - REQUIRE(IsError() == ErrorText.has_value()); - } - }; - - struct TestARPCorrelationData : public ARPCorrelationData - { - ARPCorrelationResult CorrelateForNewlyInstalled(const Manifest::Manifest&, const ARPCorrelationSettings&) override - { - return CorrelateForNewlyInstalledResult; - } - - ARPCorrelationResult CorrelateForNewlyInstalledResult; - }; - - struct TestInstalledFilesCorrelation : public InstalledFilesCorrelation - { - Correlation::InstallationMetadata CorrelateForNewlyInstalled(const Manifest::Manifest&, const std::string&) override - { - return InstallationMetadata; - } - - void StartFileWatcher() override {} - - void StopFileWatcher() override {} - - Correlation::InstallationMetadata InstallationMetadata; - }; - - InstallerMetadataCollectionContext CreateTestContext( - std::unique_ptr&& data, - std::unique_ptr&& installedFiles, - TestInput& input) - { - return { std::move(data), std::move(installedFiles), input.ToJSON() }; - } - - InstallerMetadataCollectionContext CreateTestContext(std::unique_ptr&& data, TestInput& input) - { - return { std::move(data), std::make_unique(), input.ToJSON()}; - } - - InstallerMetadataCollectionContext CreateTestContext(TestInput& input) - { - return CreateTestContext(std::make_unique(), input); - } - - TestOutput GetOutput(InstallerMetadataCollectionContext& context) - { - std::ostringstream strstr; - context.Complete(strstr); - - return { strstr.str() }; - } - - TestOutput GetOutput(TestInput& input) - { - InstallerMetadataCollectionContext context = CreateTestContext(input); - return GetOutput(context); - } - - void BadInputTest(TestInput& input) - { - TestOutput output = GetOutput(input); - REQUIRE(output.IsError()); - output.ValidateFieldPresence(); - } - - ProductMetadata MakeProductMetadata(std::string_view submissionIdentifier = "Submission 1", const std::string& installerHash = "ABCD") - { - ProductMetadata result; - result.SchemaVersion.Assign("1.0"); - result.ProductVersionMin.Assign("1.0"); - result.ProductVersionMax.Assign("1.0"); - auto& installerMetadata = result.InstallerMetadataMap[installerHash]; - installerMetadata.SubmissionIdentifier = submissionIdentifier; - installerMetadata.AppsAndFeaturesEntries.push_back({}); - auto& entry = installerMetadata.AppsAndFeaturesEntries.back(); - entry.DisplayName = "Name"; - entry.Publisher = "Publisher"; - entry.DisplayVersion = "1.0"; - entry.ProductCode = "{guid}"; - entry.InstallerType = Manifest::InstallerTypeEnum::Msi; - return result; - } - - struct TestMerge - { - TestMerge() = default; - - TestMerge(MinimalDefaults_t) - { - Version = "1.0"; - Metadatas = std::make_optional>(); - Metadatas->emplace_back(MakeProductMetadata()); - } - - std::optional Version; - std::optional> Metadatas; - - std::wstring ToJSON() - { - web::json::value json; - - if (Version) - { - json[L"version"] = JSON::GetStringValue(Version.value()); - } - - if (Metadatas) - { - web::json::value metadatasArray; - - if (Metadatas->empty()) - { - metadatasArray = web::json::value::array(); - } - else - { - size_t index = 0; - for (auto& value : Metadatas.value()) - { - metadatasArray[index++] = value.ToJson(value.SchemaVersion, 0); - } - } - - json[L"metadatas"] = std::move(metadatasArray); - } - - return json.serialize(); - } - }; -} - -TEST_CASE("MetadataCollection_MinimumInput", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - TestOutput output = GetOutput(input); - REQUIRE(!output.IsError()); - output.ValidateFieldPresence(); - - REQUIRE(output.Version.value() == input.Version.value()); - REQUIRE(output.InstallerHash.value() == input.InstallerHash.value()); -} - -TEST_CASE("MetadataCollection_SubmissionDataCopied", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - - web::json::value submissionData; - std::wstring testValueName = L"testValueName"; - std::string testValueValue = "Test value value"; - submissionData[L"submissionIdentifier"] = JSON::GetStringValue("Required identifier"); - submissionData[testValueName] = JSON::GetStringValue(testValueValue); - - input.SubmissionData = submissionData; - - TestOutput output = GetOutput(input); - REQUIRE(!output.IsError()); - output.ValidateFieldPresence(); - - REQUIRE(output.Version.value() == input.Version.value()); - REQUIRE(output.InstallerHash.value() == input.InstallerHash.value()); - auto outputValue = JSON::GetRawStringValueFromJsonNode(output.SubmissionData.value(), testValueName); - REQUIRE(outputValue); - REQUIRE(outputValue.value() == testValueValue); -} - -TEST_CASE("MetadataCollection_BadInput", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - -#define RESET_FIELD_SECTION(_field_) \ - SECTION("No " #_field_) \ - { \ - input._field_.reset(); \ - BadInputTest(input); \ - } - - RESET_FIELD_SECTION(Version); - RESET_FIELD_SECTION(SupportedMetadataVersion); - RESET_FIELD_SECTION(SubmissionIdentifier); - RESET_FIELD_SECTION(InstallerHash); - RESET_FIELD_SECTION(PackageData); - -#undef RESET_FIELD_SECTION -} - -TEST_CASE("MetadataCollection_LowConfidence", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - // The default test correlation object won't have a package correlation set - TestOutput output = GetOutput(input); - REQUIRE(output.IsLowConfidence()); - REQUIRE(!output.Metadata); - output.ValidateFieldPresence(); -} - -TEST_CASE("MetadataCollection_NewPackage", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Test Package Name"); - manifest.DefaultLocalization.Add("Test Publisher"); - manifest.Version = "1.2.3"; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = "{guid}"; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); - metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); - REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); - REQUIRE(entry.Scope.empty()); - REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); - REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); - REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); - REQUIRE(output.Metadata->HistoricalMetadataList.empty()); -} - -TEST_CASE("MetadataCollection_SameSubmission_SameInstaller", "[metadata_collection]") -{ - std::string version = "1.3.5"; - std::string productCode = "{guid}"; - Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; - - TestInput input(MinimalDefaults, version, productCode, installerType); - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Different Language Name"); - // Same publisher - manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); - manifest.Version = version; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = productCode; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); - REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); - REQUIRE(entry.AppsAndFeaturesEntries.size() == 2); - - // One should have all values, and the other should have just a different name - // Base which one is which off of whether Publisher is set - for (const auto& featureEntry : entry.AppsAndFeaturesEntries) - { - if (featureEntry.Publisher.empty()) - { - REQUIRE(featureEntry.DisplayName == manifest.DefaultLocalization.Get()); - REQUIRE(featureEntry.DisplayVersion.empty()); - REQUIRE(featureEntry.ProductCode.empty()); - REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Unknown); - } - else - { - REQUIRE(featureEntry.DisplayName == input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); - REQUIRE(featureEntry.Publisher == manifest.DefaultLocalization.Get()); - REQUIRE(featureEntry.DisplayVersion == manifest.Version); - REQUIRE(featureEntry.ProductCode == manifest.Installers[0].ProductCode); - REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); - } - } - REQUIRE(output.Metadata->HistoricalMetadataList.empty()); -} - -TEST_CASE("MetadataCollection_SameSubmission_NewInstaller", "[metadata_collection]") -{ - std::string versionPresent = "1.3.5"; - std::string versionIncoming = "1.3.5.1"; - std::string productCodePresent = "{guid}"; - std::string productCodeIncoming = "{guid_different}"; - Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; - - TestInput input(MinimalDefaults, versionPresent, productCodePresent, installerType); - // Change the incoming hash to be new - input.InstallerHash = input.InstallerHash.value() + "_DIFFERENT"; - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Name (but different architecture)"); - // Same publisher - manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); - manifest.Version = versionIncoming; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = productCodeIncoming; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->ProductVersionMin.ToString() == versionPresent); - REQUIRE(output.Metadata->ProductVersionMax.ToString() == versionIncoming); - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 2); - - for (const auto& installerMetadata : output.Metadata->InstallerMetadataMap) - { - const auto& entry = installerMetadata.second; - - REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); - REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); - - const auto& featureEntry = entry.AppsAndFeaturesEntries.front(); - REQUIRE(featureEntry.Publisher == manifest.DefaultLocalization.Get()); - - if (featureEntry.ProductCode == productCodePresent) - { - REQUIRE(featureEntry.DisplayName == input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); - REQUIRE(featureEntry.DisplayVersion == versionPresent); - REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); - } - else - { - REQUIRE(featureEntry.DisplayName == manifest.DefaultLocalization.Get()); - REQUIRE(featureEntry.DisplayVersion == versionIncoming); - REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); - REQUIRE(featureEntry.ProductCode == productCodeIncoming); - } - } - - REQUIRE(output.Metadata->HistoricalMetadataList.empty()); -} - -TEST_CASE("MetadataCollection_NewSubmission", "[metadata_collection]") -{ - std::string versionPresent = "1.3.5"; - std::string versionIncoming = "1.4.0"; - std::string productCodePresent = "{guid}"; - std::string productCodeIncoming = "{guid_different}"; - Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; - - TestInput input(MinimalDefaults, versionPresent, productCodePresent, installerType); - input.SubmissionIdentifier = input.SubmissionIdentifier.value() + "_NEW"; - input.InstallerHash = input.InstallerHash.value() + "_DIFFERENT"; - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); - manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); - manifest.Version = versionIncoming; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = productCodeIncoming; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); - REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); - REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); - REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); - REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); - - REQUIRE(output.Metadata->HistoricalMetadataList.size() == 1); - const auto& historicalEntry = output.Metadata->HistoricalMetadataList[0]; - REQUIRE(historicalEntry.ProductVersionMin.ToString() == input.CurrentMetadata->ProductVersionMin.ToString()); - REQUIRE(historicalEntry.ProductVersionMax.ToString() == input.CurrentMetadata->ProductVersionMax.ToString()); - const auto& appsAndFeaturesEntry = input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries.front(); - REQUIRE(historicalEntry.Names.size() == 1); - REQUIRE(*historicalEntry.Names.begin() == appsAndFeaturesEntry.DisplayName); - REQUIRE(historicalEntry.ProductCodes.size() == 1); - REQUIRE(*historicalEntry.ProductCodes.begin() == appsAndFeaturesEntry.ProductCode); - REQUIRE(historicalEntry.Publishers.size() == 1); - REQUIRE(*historicalEntry.Publishers.begin() == appsAndFeaturesEntry.Publisher); -} - -TEST_CASE("MetadataCollection_Merge_Empty", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->clear(); - - REQUIRE_THROWS_HR(InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}), E_NOT_SET); -} - -TEST_CASE("MetadataCollection_Merge_SubmissionMismatch", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->emplace_back(MakeProductMetadata("Submission 2")); - - REQUIRE_THROWS_HR(InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}), E_NOT_VALID_STATE); -} - -TEST_CASE("MetadataCollection_Merge_DifferentInstallers", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->emplace_back(MakeProductMetadata(mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.SubmissionIdentifier, "EFGH")); - - std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - ProductMetadata mergeMetadata; - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 2); - for (const auto& item : mergeMetadata.InstallerMetadataMap) - { - REQUIRE(item.second.AppsAndFeaturesEntries.size() == 1); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayName.empty()); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].Publisher.empty()); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayVersion.empty()); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].ProductCode.empty()); - } -} - -TEST_CASE("MetadataCollection_Merge_SameInstaller", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->emplace_back(MakeProductMetadata()); - - std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - ProductMetadata mergeMetadata; - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - for (const auto& item : mergeMetadata.InstallerMetadataMap) - { - REQUIRE(item.second.AppsAndFeaturesEntries.size() == 1); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayName.empty()); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].Publisher.empty()); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayVersion.empty()); - REQUIRE(!item.second.AppsAndFeaturesEntries[0].ProductCode.empty()); - } -} - -TEST_CASE("MetadataCollection_NewPackage_1_1", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - input.SupportedMetadataVersion = "1.1"; - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Test Package Name"); - manifest.DefaultLocalization.Add("Test Publisher"); - manifest.Version = "1.2.3"; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = "{guid}"; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); - metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); - REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); - REQUIRE(entry.Scope == metadata[PackageVersionMetadata::InstalledScope]); - REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); - REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); - REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); - REQUIRE(output.Metadata->HistoricalMetadataList.empty()); -} - -TEST_CASE("MetadataCollection_NewPackage_NoScope", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - input.SupportedMetadataVersion = "1.1"; - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Test Package Name"); - manifest.DefaultLocalization.Add("Test Publisher"); - manifest.Version = "1.2.3"; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = "{guid}"; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - REQUIRE(entry.Scope.empty()); -} - -TEST_CASE("MetadataCollection_SameSubmission_SameInstaller_Scopes", "[metadata_collection]") -{ - std::string version = "1.3.5"; - std::string productCode = "{guid}"; - Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; - std::string currentScope = GENERATE(std::string{}, - Manifest::ScopeToString(Manifest::ScopeEnum::Unknown), - Manifest::ScopeToString(Manifest::ScopeEnum::User), - Manifest::ScopeToString(Manifest::ScopeEnum::Machine)); - std::string newScope{ Manifest::ScopeToString(Manifest::ScopeEnum::User) }; - - INFO(currentScope); - - TestInput input(MinimalDefaults, version, productCode, installerType); - input.SupportedMetadataVersion = "1.1"; - input.CurrentMetadata->SchemaVersion = { "1.1" }; - input.CurrentMetadata->InstallerMetadataMap.begin()->second.Scope = currentScope; - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Different Language Name"); - // Same publisher - manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); - manifest.Version = version; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = productCode; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); - metadata[PackageVersionMetadata::InstalledScope] = newScope; - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - - if (currentScope.empty()) - { - REQUIRE(entry.Scope == newScope); - } - else if (currentScope != newScope) - { - // If Unknown, should stay Unknown - // If different, should become Unknown - REQUIRE(entry.Scope == Manifest::ScopeToString(Manifest::ScopeEnum::Unknown)); - } - else - { - // If same, should not change - REQUIRE(entry.Scope == currentScope); - } -} - -TEST_CASE("MetadataCollection_Merge_SameInstaller_Scopes", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->emplace_back(MakeProductMetadata()); - - std::string currentScope = GENERATE(std::string{}, - Manifest::ScopeToString(Manifest::ScopeEnum::Unknown), - Manifest::ScopeToString(Manifest::ScopeEnum::User), - Manifest::ScopeToString(Manifest::ScopeEnum::Machine)); - std::string newScope{ Manifest::ScopeToString(Manifest::ScopeEnum::Machine) }; - - INFO(currentScope); - - mergeData.Metadatas->at(0).SchemaVersion = { "1.1" }; - mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.Scope = currentScope; - mergeData.Metadatas->at(1).SchemaVersion = { "1.1" }; - mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.Scope = newScope; - - std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - ProductMetadata mergeMetadata; - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - for (const auto& item : mergeMetadata.InstallerMetadataMap) - { - if (currentScope.empty()) - { - REQUIRE(item.second.Scope == newScope); - } - else if (currentScope != newScope) - { - // If Unknown, should stay Unknown - // If different, should become Unknown - REQUIRE(item.second.Scope == Manifest::ScopeToString(Manifest::ScopeEnum::Unknown)); - } - else - { - // If same, should not change - REQUIRE(item.second.Scope == currentScope); - } - } -} - -TEST_CASE("MetadataCollection_NewPackage_1_2", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - input.SupportedMetadataVersion = "1.2"; - auto correlationData = std::make_unique(); - auto installedFilesData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Test Package Name"); - manifest.DefaultLocalization.Add("Test Publisher"); - manifest.Version = "1.2.3"; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = "{guid}"; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); - metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - Correlation::InstallationMetadata installedFiles; - installedFiles.InstalledFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; - Manifest::InstalledFile installedFile; - installedFile.RelativeFilePath = "test.exe"; - installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; - installedFile.InvocationParameter = "invocation"; - installedFile.DisplayName = "name"; - installedFiles.InstalledFiles.Files.emplace_back(std::move(installedFile)); - Correlation::InstalledStartupLinkFile startupLink; - startupLink.RelativeFilePath = "TestApp.lnk"; - startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; - installedFiles.StartupLinkFiles.emplace_back(std::move(startupLink)); - - installedFilesData->InstallationMetadata = std::move(installedFiles); - - std::vector testIcons; - AppInstaller::Repository::ExtractedIconInfo iconInfo; - iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); - iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; - iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; - iconInfo.IconTheme = Manifest::IconThemeEnum::Default; - testIcons.emplace_back(std::move(iconInfo)); - - TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ testIcons }; - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), std::move(installedFilesData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); - REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); - REQUIRE(entry.Scope == metadata[PackageVersionMetadata::InstalledScope]); - REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); - REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); - REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); - REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); - REQUIRE(entry.InstalledFiles.has_value()); - REQUIRE(entry.InstalledFiles->DefaultInstallLocation == "%TEMP%\\TestApp"); - REQUIRE(entry.InstalledFiles->Files.size() == 1); - REQUIRE(entry.InstalledFiles->Files[0].RelativeFilePath == "test.exe"); - REQUIRE(entry.InstalledFiles->Files[0].FileSha256 == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759")); - REQUIRE(entry.InstalledFiles->Files[0].FileType == Manifest::InstalledFileTypeEnum::Launch); - REQUIRE(entry.InstalledFiles->Files[0].InvocationParameter == "invocation"); - REQUIRE(entry.InstalledFiles->Files[0].DisplayName == "name"); - REQUIRE(entry.StartupLinkFiles.has_value()); - REQUIRE(entry.StartupLinkFiles->size() == 1); - REQUIRE(entry.StartupLinkFiles->at(0).RelativeFilePath == "TestApp.lnk"); - REQUIRE(entry.StartupLinkFiles->at(0).FileType == Manifest::InstalledFileTypeEnum::Launch); - REQUIRE(entry.Icons.size() == 1); - REQUIRE(entry.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700")); - REQUIRE(entry.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759")); - REQUIRE(entry.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Ico); - REQUIRE(entry.Icons[0].IconResolution == Manifest::IconResolutionEnum::Custom); - REQUIRE(entry.Icons[0].IconTheme == Manifest::IconThemeEnum::Default); - REQUIRE(output.Metadata->HistoricalMetadataList.empty()); -} - -TEST_CASE("MetadataCollection_NewPackage_NoInstallationMetadata_NoIcons", "[metadata_collection]") -{ - TestInput input(MinimalDefaults); - input.SupportedMetadataVersion = "1.2"; - auto correlationData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Test Package Name"); - manifest.DefaultLocalization.Add("Test Publisher"); - manifest.Version = "1.2.3"; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = "{guid}"; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - std::vector testIcons; - TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ testIcons }; - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - REQUIRE(entry.Scope.empty()); - REQUIRE_FALSE(entry.InstalledFiles.has_value()); - REQUIRE_FALSE(entry.StartupLinkFiles.has_value()); - REQUIRE(entry.Icons.size() == 0); -} - -TEST_CASE("MetadataCollection_SameSubmission_SameInstaller_InstallationMetadata_Icons", "[metadata_collection]") -{ - std::string version = "1.3.5"; - std::string productCode = "{guid}"; - Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; - - TestInput input(MinimalDefaults, version, productCode, installerType); - input.SupportedMetadataVersion = "1.2"; - input.CurrentMetadata->SchemaVersion = { "1.2" }; - - Manifest::InstallationMetadataInfo installedFiles; - installedFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; - Manifest::InstalledFile installedFile; - installedFile.RelativeFilePath = "test.exe"; - installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; - installedFile.InvocationParameter = "invocation"; - installedFile.DisplayName = "name"; - installedFiles.Files.emplace_back(std::move(installedFile)); - input.CurrentMetadata->InstallerMetadataMap.begin()->second.InstalledFiles = std::move(installedFiles); - - std::vector startupLinkFiles; - Correlation::InstalledStartupLinkFile startupLink; - startupLink.RelativeFilePath = "TestApp.lnk"; - startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; - startupLinkFiles.emplace_back(std::move(startupLink)); - input.CurrentMetadata->InstallerMetadataMap.begin()->second.StartupLinkFiles = std::move(startupLinkFiles); - - std::vector testIcons; - AppInstaller::Repository::ExtractedIconInfo iconInfo; - iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); - iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; - iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; - iconInfo.IconTheme = Manifest::IconThemeEnum::Default; - testIcons.emplace_back(std::move(iconInfo)); - input.CurrentMetadata->InstallerMetadataMap.begin()->second.Icons = std::move(testIcons); - - auto correlationData = std::make_unique(); - auto installedFilesData = std::make_unique(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add("Different Language Name"); - // Same publisher - manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); - manifest.Version = version; - manifest.Installers.push_back({}); - manifest.Installers[0].ProductCode = productCode; - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); - - correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); - - Correlation::InstallationMetadata newInstalledFiles; - newInstalledFiles.InstalledFiles.DefaultInstallLocation = "%TEMP%\\NewTestApp"; - Manifest::InstalledFile newInstalledFile; - newInstalledFile.RelativeFilePath = "test.exe"; - newInstalledFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - newInstalledFile.FileType = Manifest::InstalledFileTypeEnum::Launch; - newInstalledFile.InvocationParameter = "invocation"; - newInstalledFile.DisplayName = "name"; - newInstalledFiles.InstalledFiles.Files.emplace_back(std::move(newInstalledFile)); - Correlation::InstalledStartupLinkFile newStartupLink; - newStartupLink.RelativeFilePath = "NewTestApp.lnk"; - newStartupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; - newInstalledFiles.StartupLinkFiles.emplace_back(std::move(newStartupLink)); - - installedFilesData->InstallationMetadata = std::move(newInstalledFiles); - - std::vector newTestIcons; - AppInstaller::Repository::ExtractedIconInfo newIconInfo; - newIconInfo.IconContent = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"); - newIconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499"); - newIconInfo.IconFileType = Manifest::IconFileTypeEnum::Jpeg; - newIconInfo.IconResolution = Manifest::IconResolutionEnum::Square16; - newIconInfo.IconTheme = Manifest::IconThemeEnum::Light; - newTestIcons.emplace_back(std::move(newIconInfo)); - - TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ newTestIcons }; - - InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), std::move(installedFilesData), input); - TestOutput output = GetOutput(context); - - REQUIRE(output.IsSuccess()); - output.ValidateFieldPresence(); - - REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); - REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); - const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; - - // Conflicting installed files entries get removed. - REQUIRE(entry.InstalledFiles.has_value()); - REQUIRE_FALSE(entry.InstalledFiles->HasData()); - // Non duplicate startup links get added. - REQUIRE(entry.StartupLinkFiles.has_value()); - REQUIRE(entry.StartupLinkFiles->size() == 2); - // New detected icons always take over - REQUIRE(entry.Icons.size() == 1); - REQUIRE(entry.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(entry.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499")); - REQUIRE(entry.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Jpeg); - REQUIRE(entry.Icons[0].IconResolution == Manifest::IconResolutionEnum::Square16); - REQUIRE(entry.Icons[0].IconTheme == Manifest::IconThemeEnum::Light); -} - -TEST_CASE("MetadataCollection_Merge_SameInstaller_InstalledFiles", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->emplace_back(MakeProductMetadata()); - - Manifest::InstallationMetadataInfo installedFiles; - installedFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; - Manifest::InstalledFile installedFile; - installedFile.RelativeFilePath = "test.exe"; - installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; - installedFile.InvocationParameter = "invocation"; - installedFile.DisplayName = "name"; - installedFiles.Files.emplace_back(std::move(installedFile)); - - mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; - mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.InstalledFiles = installedFiles; - - // Different default install location clears whole data - Manifest::InstallationMetadataInfo newInstalledFiles = installedFiles; - newInstalledFiles.DefaultInstallLocation = "%TEMP%\\NewTestApp"; - mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; - - mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles; - std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - ProductMetadata mergeMetadata; - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); - REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->HasData()); - - // Different RelativeFilePath clears the file entry - Manifest::InstallationMetadataInfo newInstalledFiles2 = installedFiles; - newInstalledFiles2.Files[0].RelativeFilePath = "test2.exe"; - mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles2; - mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); - REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->DefaultInstallLocation.empty()); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files.empty()); - - // Different other fields clears the fields themselves - Manifest::InstallationMetadataInfo newInstalledFiles3 = installedFiles; - newInstalledFiles3.Files[0].DisplayName = "name2"; - newInstalledFiles3.Files[0].FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b756"); - newInstalledFiles3.Files[0].InvocationParameter = "invocation2"; - newInstalledFiles3.Files[0].FileType = Manifest::InstalledFileTypeEnum::Uninstall; - mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles3; - mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); - REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->DefaultInstallLocation.empty()); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].RelativeFilePath == "test.exe"); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].DisplayName == ""); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].InvocationParameter == ""); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].FileType == Manifest::InstalledFileTypeEnum::Unknown); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].FileSha256.empty()); -} - -TEST_CASE("MetadataCollection_Merge_SameInstaller_StartupLinkFiles", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->emplace_back(MakeProductMetadata()); - - std::vector startupLinkFiles; - Correlation::InstalledStartupLinkFile startupLink; - startupLink.RelativeFilePath = "TestApp.lnk"; - startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; - startupLinkFiles.emplace_back(std::move(startupLink)); - - mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; - mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.StartupLinkFiles = startupLinkFiles; - - // Different relative file path gets added - std::vector newStartupLinkFiles = startupLinkFiles; - newStartupLinkFiles[0].RelativeFilePath = "TestApp2.lnk"; - - mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; - mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.StartupLinkFiles = newStartupLinkFiles; - std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - ProductMetadata mergeMetadata; - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles.has_value()); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->size() == 2); - - // Different other fields clears the fields themselves - std::vector newStartupLinkFiles2 = startupLinkFiles; - newStartupLinkFiles2[0].FileType = Manifest::InstalledFileTypeEnum::Uninstall; - mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.StartupLinkFiles = newStartupLinkFiles2; - mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles.has_value()); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->at(0).RelativeFilePath == "TestApp.lnk"); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->at(0).FileType == Manifest::InstalledFileTypeEnum::Unknown); -} - -TEST_CASE("MetadataCollection_Merge_SameInstaller_Icons", "[metadata_collection]") -{ - TestMerge mergeData{ MinimalDefaults }; - mergeData.Metadatas->emplace_back(MakeProductMetadata()); - - std::vector testIcons; - AppInstaller::Repository::ExtractedIconInfo iconInfo; - iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); - iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); - iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; - iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; - iconInfo.IconTheme = Manifest::IconThemeEnum::Default; - testIcons.emplace_back(std::move(iconInfo)); - - mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; - mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.Icons = testIcons; - - // Different test icons - std::vector newTestIcons; - AppInstaller::Repository::ExtractedIconInfo newIconInfo; - newIconInfo.IconContent = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"); - newIconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499"); - newIconInfo.IconFileType = Manifest::IconFileTypeEnum::Jpeg; - newIconInfo.IconResolution = Manifest::IconResolutionEnum::Square16; - newIconInfo.IconTheme = Manifest::IconThemeEnum::Light; - newTestIcons.emplace_back(std::move(newIconInfo)); - - mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; - mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.Icons = newTestIcons; - std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); - REQUIRE(!mergeResult.empty()); - - ProductMetadata mergeMetadata; - mergeMetadata.FromJson(web::json::value::parse(mergeResult)); - - // New data always take over - REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons.size() == 1); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499")); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Jpeg); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconResolution == Manifest::IconResolutionEnum::Square16); - REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconTheme == Manifest::IconThemeEnum::Light); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSource.h" +#include "TestHooks.h" + +#include + +using namespace AppInstaller; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Correlation; +using namespace AppInstaller::Repository::Metadata; +using namespace TestCommon; + +namespace +{ + // Indicates to set minimal defaults on TestInput + struct MinimalDefaults_t {} MinimalDefaults; + + struct TestInput + { + TestInput() = default; + + TestInput(MinimalDefaults_t) + { + Version = "1.0"; + SupportedMetadataVersion = "1.0"; + SubmissionIdentifier = "1"; + InstallerHash = "ABCD"; + PackageData = std::make_optional(); + PackageData->DefaultLocalization.Locale = "en-us"; + PackageData->DefaultLocalization.Add("Name"); + PackageData->DefaultLocalization.Add("Publisher"); + } + + TestInput(MinimalDefaults_t, const std::string& productVersion, const std::string& productCode, Manifest::InstallerTypeEnum installerType) : TestInput(MinimalDefaults) + { + CurrentMetadata = std::make_optional(); + CurrentMetadata->SchemaVersion.Assign("1.0"); + CurrentMetadata->ProductVersionMin.Assign(productVersion); + CurrentMetadata->ProductVersionMax.Assign(productVersion); + auto& installerMetadata = CurrentMetadata->InstallerMetadataMap[InstallerHash.value()]; + installerMetadata.SubmissionIdentifier = SubmissionIdentifier.value(); + installerMetadata.AppsAndFeaturesEntries.push_back({}); + auto& entry = installerMetadata.AppsAndFeaturesEntries.back(); + entry.DisplayName = PackageData->DefaultLocalization.Get(); + entry.Publisher = PackageData->DefaultLocalization.Get(); + entry.DisplayVersion = productVersion; + entry.ProductCode = productCode; + entry.InstallerType = installerType; + } + + std::optional Version; + std::optional SupportedMetadataVersion; + std::optional CurrentMetadata; + std::optional SubmissionData; + std::optional SubmissionIdentifier; + std::optional InstallerHash; + // Schema 1.0 only cares about DefaultLocale and Locales + std::optional PackageData; + + std::wstring ToJSON() + { + web::json::value json; + + if (Version) + { + json[L"version"] = JSON::GetStringValue(Version.value()); + } + + if (SupportedMetadataVersion) + { + json[L"supportedMetadataVersion"] = JSON::GetStringValue(SupportedMetadataVersion.value()); + } + + if (CurrentMetadata) + { + json[L"currentMetadata"] = CurrentMetadata->ToJson(CurrentMetadata->SchemaVersion, 0); + } + + if (SubmissionData) + { + json[L"submissionData"] = SubmissionData.value(); + } + else if (SubmissionIdentifier) + { + web::json::value submissionData; + submissionData[L"submissionIdentifier"] = JSON::GetStringValue(SubmissionIdentifier.value()); + json[L"submissionData"] = std::move(submissionData); + } + + if (InstallerHash || PackageData) + { + web::json::value packageData; + + if (InstallerHash) + { + packageData[L"installerHash"] = JSON::GetStringValue(InstallerHash.value()); + } + + if (PackageData) + { + packageData[L"DefaultLocale"] = LocaleToJSON(PackageData->DefaultLocalization); + + // TODO: Implement other locales + } + + json[L"packageData"] = std::move(packageData); + } + + return json.serialize(); + } + + private: + web::json::value LocaleToJSON(const Manifest::ManifestLocalization& localization) const + { + web::json::value locale; + + locale[L"PackageLocale"] = JSON::GetStringValue(localization.Locale); + + if (localization.Contains(Manifest::Localization::PackageName)) + { + locale[L"PackageName"] = JSON::GetStringValue(localization.Get()); + } + + if (localization.Contains(Manifest::Localization::Publisher)) + { + locale[L"Publisher"] = JSON::GetStringValue(localization.Get()); + } + + // TODO: Implement any other needed fields + + return locale; + } + }; + + struct TestOutput + { + TestOutput(const std::string& json) : OriginalJSON(json) + { + web::json::value input = web::json::value::parse(Utility::ConvertToUTF16(json)); + + auto versionString = JSON::GetRawStringValueFromJsonNode(input, L"version"); + if (versionString) + { + Version = std::move(versionString); + } + + auto submissionDataValue = JSON::GetJsonValueFromNode(input, L"submissionData"); + if (submissionDataValue) + { + SubmissionData = submissionDataValue.value(); + } + + auto installerHashString = JSON::GetRawStringValueFromJsonNode(input, L"installerHash"); + if (installerHashString) + { + InstallerHash = std::move(installerHashString); + } + + auto statusString = JSON::GetRawStringValueFromJsonNode(input, L"status"); + if (statusString) + { + Status = std::move(statusString); + } + + auto metadataValue = JSON::GetJsonValueFromNode(input, L"metadata"); + if (metadataValue && !metadataValue->get().is_null()) + { + Metadata = std::make_optional(); + Metadata->FromJson(metadataValue->get()); + } + + auto diagnosticsValue = JSON::GetJsonValueFromNode(input, L"diagnostics"); + if (diagnosticsValue) + { + auto errorHRNumber = JSON::GetRawIntValueFromJsonNode(diagnosticsValue.value(), L"errorHR"); + if (errorHRNumber) + { + ErrorHR = std::move(errorHRNumber); + } + + auto errorTextString = JSON::GetRawStringValueFromJsonNode(diagnosticsValue.value(), L"errorText"); + if (errorTextString) + { + ErrorText = std::move(errorTextString); + } + } + } + + std::string OriginalJSON; + + std::optional Version; + std::optional SubmissionData; + std::optional InstallerHash; + std::optional Status; + std::optional Metadata; + std::optional ErrorHR; + std::optional ErrorText; + + bool IsError() const + { + return Status && Status.value() == "Error"; + } + + bool IsSuccess() const + { + return Status && Status.value() == "Success"; + } + + bool IsLowConfidence() const + { + return Status && Status.value() == "LowConfidence"; + } + + void ValidateFieldPresence() const + { + REQUIRE(Version); + REQUIRE(SubmissionData); + REQUIRE(InstallerHash); + REQUIRE(Status); + + REQUIRE(IsSuccess() == Metadata.has_value()); + + REQUIRE(IsError() == ErrorHR.has_value()); + REQUIRE(IsError() == ErrorText.has_value()); + } + }; + + struct TestARPCorrelationData : public ARPCorrelationData + { + ARPCorrelationResult CorrelateForNewlyInstalled(const Manifest::Manifest&, const ARPCorrelationSettings&) override + { + return CorrelateForNewlyInstalledResult; + } + + ARPCorrelationResult CorrelateForNewlyInstalledResult; + }; + + struct TestInstalledFilesCorrelation : public InstalledFilesCorrelation + { + Correlation::InstallationMetadata CorrelateForNewlyInstalled(const Manifest::Manifest&, const std::string&) override + { + return InstallationMetadata; + } + + void StartFileWatcher() override {} + + void StopFileWatcher() override {} + + Correlation::InstallationMetadata InstallationMetadata; + }; + + InstallerMetadataCollectionContext CreateTestContext( + std::unique_ptr&& data, + std::unique_ptr&& installedFiles, + TestInput& input) + { + return { std::move(data), std::move(installedFiles), input.ToJSON() }; + } + + InstallerMetadataCollectionContext CreateTestContext(std::unique_ptr&& data, TestInput& input) + { + return { std::move(data), std::make_unique(), input.ToJSON()}; + } + + InstallerMetadataCollectionContext CreateTestContext(TestInput& input) + { + return CreateTestContext(std::make_unique(), input); + } + + TestOutput GetOutput(InstallerMetadataCollectionContext& context) + { + std::ostringstream strstr; + context.Complete(strstr); + + return { strstr.str() }; + } + + TestOutput GetOutput(TestInput& input) + { + InstallerMetadataCollectionContext context = CreateTestContext(input); + return GetOutput(context); + } + + void BadInputTest(TestInput& input) + { + TestOutput output = GetOutput(input); + REQUIRE(output.IsError()); + output.ValidateFieldPresence(); + } + + ProductMetadata MakeProductMetadata(std::string_view submissionIdentifier = "Submission 1", const std::string& installerHash = "ABCD") + { + ProductMetadata result; + result.SchemaVersion.Assign("1.0"); + result.ProductVersionMin.Assign("1.0"); + result.ProductVersionMax.Assign("1.0"); + auto& installerMetadata = result.InstallerMetadataMap[installerHash]; + installerMetadata.SubmissionIdentifier = submissionIdentifier; + installerMetadata.AppsAndFeaturesEntries.push_back({}); + auto& entry = installerMetadata.AppsAndFeaturesEntries.back(); + entry.DisplayName = "Name"; + entry.Publisher = "Publisher"; + entry.DisplayVersion = "1.0"; + entry.ProductCode = "{guid}"; + entry.InstallerType = Manifest::InstallerTypeEnum::Msi; + return result; + } + + struct TestMerge + { + TestMerge() = default; + + TestMerge(MinimalDefaults_t) + { + Version = "1.0"; + Metadatas = std::make_optional>(); + Metadatas->emplace_back(MakeProductMetadata()); + } + + std::optional Version; + std::optional> Metadatas; + + std::wstring ToJSON() + { + web::json::value json; + + if (Version) + { + json[L"version"] = JSON::GetStringValue(Version.value()); + } + + if (Metadatas) + { + web::json::value metadatasArray; + + if (Metadatas->empty()) + { + metadatasArray = web::json::value::array(); + } + else + { + size_t index = 0; + for (auto& value : Metadatas.value()) + { + metadatasArray[index++] = value.ToJson(value.SchemaVersion, 0); + } + } + + json[L"metadatas"] = std::move(metadatasArray); + } + + return json.serialize(); + } + }; +} + +TEST_CASE("MetadataCollection_MinimumInput", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + TestOutput output = GetOutput(input); + REQUIRE(!output.IsError()); + output.ValidateFieldPresence(); + + REQUIRE(output.Version.value() == input.Version.value()); + REQUIRE(output.InstallerHash.value() == input.InstallerHash.value()); +} + +TEST_CASE("MetadataCollection_SubmissionDataCopied", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + + web::json::value submissionData; + std::wstring testValueName = L"testValueName"; + std::string testValueValue = "Test value value"; + submissionData[L"submissionIdentifier"] = JSON::GetStringValue("Required identifier"); + submissionData[testValueName] = JSON::GetStringValue(testValueValue); + + input.SubmissionData = submissionData; + + TestOutput output = GetOutput(input); + REQUIRE(!output.IsError()); + output.ValidateFieldPresence(); + + REQUIRE(output.Version.value() == input.Version.value()); + REQUIRE(output.InstallerHash.value() == input.InstallerHash.value()); + auto outputValue = JSON::GetRawStringValueFromJsonNode(output.SubmissionData.value(), testValueName); + REQUIRE(outputValue); + REQUIRE(outputValue.value() == testValueValue); +} + +TEST_CASE("MetadataCollection_BadInput", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + +#define RESET_FIELD_SECTION(_field_) \ + SECTION("No " #_field_) \ + { \ + input._field_.reset(); \ + BadInputTest(input); \ + } + + RESET_FIELD_SECTION(Version); + RESET_FIELD_SECTION(SupportedMetadataVersion); + RESET_FIELD_SECTION(SubmissionIdentifier); + RESET_FIELD_SECTION(InstallerHash); + RESET_FIELD_SECTION(PackageData); + +#undef RESET_FIELD_SECTION +} + +TEST_CASE("MetadataCollection_LowConfidence", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + // The default test correlation object won't have a package correlation set + TestOutput output = GetOutput(input); + REQUIRE(output.IsLowConfidence()); + REQUIRE(!output.Metadata); + output.ValidateFieldPresence(); +} + +TEST_CASE("MetadataCollection_NewPackage", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Test Package Name"); + manifest.DefaultLocalization.Add("Test Publisher"); + manifest.Version = "1.2.3"; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = "{guid}"; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); + metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); + REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); + REQUIRE(entry.Scope.empty()); + REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); + REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); + REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); + REQUIRE(output.Metadata->HistoricalMetadataList.empty()); +} + +TEST_CASE("MetadataCollection_SameSubmission_SameInstaller", "[metadata_collection]") +{ + std::string version = "1.3.5"; + std::string productCode = "{guid}"; + Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; + + TestInput input(MinimalDefaults, version, productCode, installerType); + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Different Language Name"); + // Same publisher + manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); + manifest.Version = version; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = productCode; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); + REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); + REQUIRE(entry.AppsAndFeaturesEntries.size() == 2); + + // One should have all values, and the other should have just a different name + // Base which one is which off of whether Publisher is set + for (const auto& featureEntry : entry.AppsAndFeaturesEntries) + { + if (featureEntry.Publisher.empty()) + { + REQUIRE(featureEntry.DisplayName == manifest.DefaultLocalization.Get()); + REQUIRE(featureEntry.DisplayVersion.empty()); + REQUIRE(featureEntry.ProductCode.empty()); + REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Unknown); + } + else + { + REQUIRE(featureEntry.DisplayName == input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); + REQUIRE(featureEntry.Publisher == manifest.DefaultLocalization.Get()); + REQUIRE(featureEntry.DisplayVersion == manifest.Version); + REQUIRE(featureEntry.ProductCode == manifest.Installers[0].ProductCode); + REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); + } + } + REQUIRE(output.Metadata->HistoricalMetadataList.empty()); +} + +TEST_CASE("MetadataCollection_SameSubmission_NewInstaller", "[metadata_collection]") +{ + std::string versionPresent = "1.3.5"; + std::string versionIncoming = "1.3.5.1"; + std::string productCodePresent = "{guid}"; + std::string productCodeIncoming = "{guid_different}"; + Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; + + TestInput input(MinimalDefaults, versionPresent, productCodePresent, installerType); + // Change the incoming hash to be new + input.InstallerHash = input.InstallerHash.value() + "_DIFFERENT"; + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Name (but different architecture)"); + // Same publisher + manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); + manifest.Version = versionIncoming; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = productCodeIncoming; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->ProductVersionMin.ToString() == versionPresent); + REQUIRE(output.Metadata->ProductVersionMax.ToString() == versionIncoming); + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 2); + + for (const auto& installerMetadata : output.Metadata->InstallerMetadataMap) + { + const auto& entry = installerMetadata.second; + + REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); + REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); + + const auto& featureEntry = entry.AppsAndFeaturesEntries.front(); + REQUIRE(featureEntry.Publisher == manifest.DefaultLocalization.Get()); + + if (featureEntry.ProductCode == productCodePresent) + { + REQUIRE(featureEntry.DisplayName == input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); + REQUIRE(featureEntry.DisplayVersion == versionPresent); + REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); + } + else + { + REQUIRE(featureEntry.DisplayName == manifest.DefaultLocalization.Get()); + REQUIRE(featureEntry.DisplayVersion == versionIncoming); + REQUIRE(featureEntry.InstallerType == Manifest::InstallerTypeEnum::Msi); + REQUIRE(featureEntry.ProductCode == productCodeIncoming); + } + } + + REQUIRE(output.Metadata->HistoricalMetadataList.empty()); +} + +TEST_CASE("MetadataCollection_NewSubmission", "[metadata_collection]") +{ + std::string versionPresent = "1.3.5"; + std::string versionIncoming = "1.4.0"; + std::string productCodePresent = "{guid}"; + std::string productCodeIncoming = "{guid_different}"; + Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; + + TestInput input(MinimalDefaults, versionPresent, productCodePresent, installerType); + input.SubmissionIdentifier = input.SubmissionIdentifier.value() + "_NEW"; + input.InstallerHash = input.InstallerHash.value() + "_DIFFERENT"; + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].DisplayName); + manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); + manifest.Version = versionIncoming; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = productCodeIncoming; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); + REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); + REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); + REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); + REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); + + REQUIRE(output.Metadata->HistoricalMetadataList.size() == 1); + const auto& historicalEntry = output.Metadata->HistoricalMetadataList[0]; + REQUIRE(historicalEntry.ProductVersionMin.ToString() == input.CurrentMetadata->ProductVersionMin.ToString()); + REQUIRE(historicalEntry.ProductVersionMax.ToString() == input.CurrentMetadata->ProductVersionMax.ToString()); + const auto& appsAndFeaturesEntry = input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries.front(); + REQUIRE(historicalEntry.Names.size() == 1); + REQUIRE(*historicalEntry.Names.begin() == appsAndFeaturesEntry.DisplayName); + REQUIRE(historicalEntry.ProductCodes.size() == 1); + REQUIRE(*historicalEntry.ProductCodes.begin() == appsAndFeaturesEntry.ProductCode); + REQUIRE(historicalEntry.Publishers.size() == 1); + REQUIRE(*historicalEntry.Publishers.begin() == appsAndFeaturesEntry.Publisher); +} + +TEST_CASE("MetadataCollection_Merge_Empty", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->clear(); + + REQUIRE_THROWS_HR(InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}), E_NOT_SET); +} + +TEST_CASE("MetadataCollection_Merge_SubmissionMismatch", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->emplace_back(MakeProductMetadata("Submission 2")); + + REQUIRE_THROWS_HR(InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}), E_NOT_VALID_STATE); +} + +TEST_CASE("MetadataCollection_Merge_DifferentInstallers", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->emplace_back(MakeProductMetadata(mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.SubmissionIdentifier, "EFGH")); + + std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + ProductMetadata mergeMetadata; + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 2); + for (const auto& item : mergeMetadata.InstallerMetadataMap) + { + REQUIRE(item.second.AppsAndFeaturesEntries.size() == 1); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayName.empty()); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].Publisher.empty()); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayVersion.empty()); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].ProductCode.empty()); + } +} + +TEST_CASE("MetadataCollection_Merge_SameInstaller", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->emplace_back(MakeProductMetadata()); + + std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + ProductMetadata mergeMetadata; + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + for (const auto& item : mergeMetadata.InstallerMetadataMap) + { + REQUIRE(item.second.AppsAndFeaturesEntries.size() == 1); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayName.empty()); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].Publisher.empty()); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].DisplayVersion.empty()); + REQUIRE(!item.second.AppsAndFeaturesEntries[0].ProductCode.empty()); + } +} + +TEST_CASE("MetadataCollection_NewPackage_1_1", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + input.SupportedMetadataVersion = "1.1"; + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Test Package Name"); + manifest.DefaultLocalization.Add("Test Publisher"); + manifest.Version = "1.2.3"; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = "{guid}"; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); + metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); + REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); + REQUIRE(entry.Scope == metadata[PackageVersionMetadata::InstalledScope]); + REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); + REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); + REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); + REQUIRE(output.Metadata->HistoricalMetadataList.empty()); +} + +TEST_CASE("MetadataCollection_NewPackage_NoScope", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + input.SupportedMetadataVersion = "1.1"; + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Test Package Name"); + manifest.DefaultLocalization.Add("Test Publisher"); + manifest.Version = "1.2.3"; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = "{guid}"; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + REQUIRE(entry.Scope.empty()); +} + +TEST_CASE("MetadataCollection_SameSubmission_SameInstaller_Scopes", "[metadata_collection]") +{ + std::string version = "1.3.5"; + std::string productCode = "{guid}"; + Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; + std::string currentScope = GENERATE(std::string{}, + Manifest::ScopeToString(Manifest::ScopeEnum::Unknown), + Manifest::ScopeToString(Manifest::ScopeEnum::User), + Manifest::ScopeToString(Manifest::ScopeEnum::Machine)); + std::string newScope{ Manifest::ScopeToString(Manifest::ScopeEnum::User) }; + + INFO(currentScope); + + TestInput input(MinimalDefaults, version, productCode, installerType); + input.SupportedMetadataVersion = "1.1"; + input.CurrentMetadata->SchemaVersion = { "1.1" }; + input.CurrentMetadata->InstallerMetadataMap.begin()->second.Scope = currentScope; + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Different Language Name"); + // Same publisher + manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); + manifest.Version = version; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = productCode; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); + metadata[PackageVersionMetadata::InstalledScope] = newScope; + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + + if (currentScope.empty()) + { + REQUIRE(entry.Scope == newScope); + } + else if (currentScope != newScope) + { + // If Unknown, should stay Unknown + // If different, should become Unknown + REQUIRE(entry.Scope == Manifest::ScopeToString(Manifest::ScopeEnum::Unknown)); + } + else + { + // If same, should not change + REQUIRE(entry.Scope == currentScope); + } +} + +TEST_CASE("MetadataCollection_Merge_SameInstaller_Scopes", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->emplace_back(MakeProductMetadata()); + + std::string currentScope = GENERATE(std::string{}, + Manifest::ScopeToString(Manifest::ScopeEnum::Unknown), + Manifest::ScopeToString(Manifest::ScopeEnum::User), + Manifest::ScopeToString(Manifest::ScopeEnum::Machine)); + std::string newScope{ Manifest::ScopeToString(Manifest::ScopeEnum::Machine) }; + + INFO(currentScope); + + mergeData.Metadatas->at(0).SchemaVersion = { "1.1" }; + mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.Scope = currentScope; + mergeData.Metadatas->at(1).SchemaVersion = { "1.1" }; + mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.Scope = newScope; + + std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + ProductMetadata mergeMetadata; + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + for (const auto& item : mergeMetadata.InstallerMetadataMap) + { + if (currentScope.empty()) + { + REQUIRE(item.second.Scope == newScope); + } + else if (currentScope != newScope) + { + // If Unknown, should stay Unknown + // If different, should become Unknown + REQUIRE(item.second.Scope == Manifest::ScopeToString(Manifest::ScopeEnum::Unknown)); + } + else + { + // If same, should not change + REQUIRE(item.second.Scope == currentScope); + } + } +} + +TEST_CASE("MetadataCollection_NewPackage_1_2", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + input.SupportedMetadataVersion = "1.2"; + auto correlationData = std::make_unique(); + auto installedFilesData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Test Package Name"); + manifest.DefaultLocalization.Add("Test Publisher"); + manifest.Version = "1.2.3"; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = "{guid}"; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); + metadata[PackageVersionMetadata::InstalledScope] = Manifest::ScopeToString(Manifest::ScopeEnum::User); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + Correlation::InstallationMetadata installedFiles; + installedFiles.InstalledFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; + Manifest::InstalledFile installedFile; + installedFile.RelativeFilePath = "test.exe"; + installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; + installedFile.InvocationParameter = "invocation"; + installedFile.DisplayName = "name"; + installedFiles.InstalledFiles.Files.emplace_back(std::move(installedFile)); + Correlation::InstalledStartupLinkFile startupLink; + startupLink.RelativeFilePath = "TestApp.lnk"; + startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; + installedFiles.StartupLinkFiles.emplace_back(std::move(startupLink)); + + installedFilesData->InstallationMetadata = std::move(installedFiles); + + std::vector testIcons; + AppInstaller::Repository::ExtractedIconInfo iconInfo; + iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); + iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; + iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; + iconInfo.IconTheme = Manifest::IconThemeEnum::Default; + testIcons.emplace_back(std::move(iconInfo)); + + TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ testIcons }; + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), std::move(installedFilesData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->ProductVersionMin.ToString() == output.Metadata->ProductVersionMax.ToString()); + REQUIRE(output.Metadata->ProductVersionMin.ToString() == manifest.Version); + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + REQUIRE(entry.SubmissionIdentifier == input.SubmissionIdentifier.value()); + REQUIRE(entry.Scope == metadata[PackageVersionMetadata::InstalledScope]); + REQUIRE(entry.AppsAndFeaturesEntries.size() == 1); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayName == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].Publisher == manifest.DefaultLocalization.Get()); + REQUIRE(entry.AppsAndFeaturesEntries[0].DisplayVersion == manifest.Version); + REQUIRE(entry.AppsAndFeaturesEntries[0].ProductCode == manifest.Installers[0].ProductCode); + REQUIRE(entry.AppsAndFeaturesEntries[0].InstallerType == Manifest::InstallerTypeEnum::Msi); + REQUIRE(entry.InstalledFiles.has_value()); + REQUIRE(entry.InstalledFiles->DefaultInstallLocation == "%TEMP%\\TestApp"); + REQUIRE(entry.InstalledFiles->Files.size() == 1); + REQUIRE(entry.InstalledFiles->Files[0].RelativeFilePath == "test.exe"); + REQUIRE(entry.InstalledFiles->Files[0].FileSha256 == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759")); + REQUIRE(entry.InstalledFiles->Files[0].FileType == Manifest::InstalledFileTypeEnum::Launch); + REQUIRE(entry.InstalledFiles->Files[0].InvocationParameter == "invocation"); + REQUIRE(entry.InstalledFiles->Files[0].DisplayName == "name"); + REQUIRE(entry.StartupLinkFiles.has_value()); + REQUIRE(entry.StartupLinkFiles->size() == 1); + REQUIRE(entry.StartupLinkFiles->at(0).RelativeFilePath == "TestApp.lnk"); + REQUIRE(entry.StartupLinkFiles->at(0).FileType == Manifest::InstalledFileTypeEnum::Launch); + REQUIRE(entry.Icons.size() == 1); + REQUIRE(entry.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700")); + REQUIRE(entry.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759")); + REQUIRE(entry.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Ico); + REQUIRE(entry.Icons[0].IconResolution == Manifest::IconResolutionEnum::Custom); + REQUIRE(entry.Icons[0].IconTheme == Manifest::IconThemeEnum::Default); + REQUIRE(output.Metadata->HistoricalMetadataList.empty()); +} + +TEST_CASE("MetadataCollection_NewPackage_NoInstallationMetadata_NoIcons", "[metadata_collection]") +{ + TestInput input(MinimalDefaults); + input.SupportedMetadataVersion = "1.2"; + auto correlationData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Test Package Name"); + manifest.DefaultLocalization.Add("Test Publisher"); + manifest.Version = "1.2.3"; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = "{guid}"; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msi); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + std::vector testIcons; + TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ testIcons }; + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + REQUIRE(entry.Scope.empty()); + REQUIRE_FALSE(entry.InstalledFiles.has_value()); + REQUIRE_FALSE(entry.StartupLinkFiles.has_value()); + REQUIRE(entry.Icons.size() == 0); +} + +TEST_CASE("MetadataCollection_SameSubmission_SameInstaller_InstallationMetadata_Icons", "[metadata_collection]") +{ + std::string version = "1.3.5"; + std::string productCode = "{guid}"; + Manifest::InstallerTypeEnum installerType = Manifest::InstallerTypeEnum::Msi; + + TestInput input(MinimalDefaults, version, productCode, installerType); + input.SupportedMetadataVersion = "1.2"; + input.CurrentMetadata->SchemaVersion = { "1.2" }; + + Manifest::InstallationMetadataInfo installedFiles; + installedFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; + Manifest::InstalledFile installedFile; + installedFile.RelativeFilePath = "test.exe"; + installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; + installedFile.InvocationParameter = "invocation"; + installedFile.DisplayName = "name"; + installedFiles.Files.emplace_back(std::move(installedFile)); + input.CurrentMetadata->InstallerMetadataMap.begin()->second.InstalledFiles = std::move(installedFiles); + + std::vector startupLinkFiles; + Correlation::InstalledStartupLinkFile startupLink; + startupLink.RelativeFilePath = "TestApp.lnk"; + startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; + startupLinkFiles.emplace_back(std::move(startupLink)); + input.CurrentMetadata->InstallerMetadataMap.begin()->second.StartupLinkFiles = std::move(startupLinkFiles); + + std::vector testIcons; + AppInstaller::Repository::ExtractedIconInfo iconInfo; + iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); + iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; + iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; + iconInfo.IconTheme = Manifest::IconThemeEnum::Default; + testIcons.emplace_back(std::move(iconInfo)); + input.CurrentMetadata->InstallerMetadataMap.begin()->second.Icons = std::move(testIcons); + + auto correlationData = std::make_unique(); + auto installedFilesData = std::make_unique(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add("Different Language Name"); + // Same publisher + manifest.DefaultLocalization.Add(input.CurrentMetadata->InstallerMetadataMap.begin()->second.AppsAndFeaturesEntries[0].Publisher); + manifest.Version = version; + manifest.Installers.push_back({}); + manifest.Installers[0].ProductCode = productCode; + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = Manifest::InstallerTypeToString(installerType); + + correlationData->CorrelateForNewlyInstalledResult.Package = std::make_shared(manifest, metadata); + + Correlation::InstallationMetadata newInstalledFiles; + newInstalledFiles.InstalledFiles.DefaultInstallLocation = "%TEMP%\\NewTestApp"; + Manifest::InstalledFile newInstalledFile; + newInstalledFile.RelativeFilePath = "test.exe"; + newInstalledFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + newInstalledFile.FileType = Manifest::InstalledFileTypeEnum::Launch; + newInstalledFile.InvocationParameter = "invocation"; + newInstalledFile.DisplayName = "name"; + newInstalledFiles.InstalledFiles.Files.emplace_back(std::move(newInstalledFile)); + Correlation::InstalledStartupLinkFile newStartupLink; + newStartupLink.RelativeFilePath = "NewTestApp.lnk"; + newStartupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; + newInstalledFiles.StartupLinkFiles.emplace_back(std::move(newStartupLink)); + + installedFilesData->InstallationMetadata = std::move(newInstalledFiles); + + std::vector newTestIcons; + AppInstaller::Repository::ExtractedIconInfo newIconInfo; + newIconInfo.IconContent = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"); + newIconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499"); + newIconInfo.IconFileType = Manifest::IconFileTypeEnum::Jpeg; + newIconInfo.IconResolution = Manifest::IconResolutionEnum::Square16; + newIconInfo.IconTheme = Manifest::IconThemeEnum::Light; + newTestIcons.emplace_back(std::move(newIconInfo)); + + TestHook::SetExtractIconFromArpEntryResult_Override iconsOverride{ newTestIcons }; + + InstallerMetadataCollectionContext context = CreateTestContext(std::move(correlationData), std::move(installedFilesData), input); + TestOutput output = GetOutput(context); + + REQUIRE(output.IsSuccess()); + output.ValidateFieldPresence(); + + REQUIRE(output.Metadata->InstallerMetadataMap.size() == 1); + REQUIRE(output.Metadata->InstallerMetadataMap.count(input.InstallerHash.value()) == 1); + const auto& entry = output.Metadata->InstallerMetadataMap[input.InstallerHash.value()]; + + // Conflicting installed files entries get removed. + REQUIRE(entry.InstalledFiles.has_value()); + REQUIRE_FALSE(entry.InstalledFiles->HasData()); + // Non duplicate startup links get added. + REQUIRE(entry.StartupLinkFiles.has_value()); + REQUIRE(entry.StartupLinkFiles->size() == 2); + // New detected icons always take over + REQUIRE(entry.Icons.size() == 1); + REQUIRE(entry.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(entry.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499")); + REQUIRE(entry.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Jpeg); + REQUIRE(entry.Icons[0].IconResolution == Manifest::IconResolutionEnum::Square16); + REQUIRE(entry.Icons[0].IconTheme == Manifest::IconThemeEnum::Light); +} + +TEST_CASE("MetadataCollection_Merge_SameInstaller_InstalledFiles", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->emplace_back(MakeProductMetadata()); + + Manifest::InstallationMetadataInfo installedFiles; + installedFiles.DefaultInstallLocation = "%TEMP%\\TestApp"; + Manifest::InstalledFile installedFile; + installedFile.RelativeFilePath = "test.exe"; + installedFile.FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + installedFile.FileType = Manifest::InstalledFileTypeEnum::Launch; + installedFile.InvocationParameter = "invocation"; + installedFile.DisplayName = "name"; + installedFiles.Files.emplace_back(std::move(installedFile)); + + mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; + mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.InstalledFiles = installedFiles; + + // Different default install location clears whole data + Manifest::InstallationMetadataInfo newInstalledFiles = installedFiles; + newInstalledFiles.DefaultInstallLocation = "%TEMP%\\NewTestApp"; + mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; + + mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles; + std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + ProductMetadata mergeMetadata; + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); + REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->HasData()); + + // Different RelativeFilePath clears the file entry + Manifest::InstallationMetadataInfo newInstalledFiles2 = installedFiles; + newInstalledFiles2.Files[0].RelativeFilePath = "test2.exe"; + mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles2; + mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); + REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->DefaultInstallLocation.empty()); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files.empty()); + + // Different other fields clears the fields themselves + Manifest::InstallationMetadataInfo newInstalledFiles3 = installedFiles; + newInstalledFiles3.Files[0].DisplayName = "name2"; + newInstalledFiles3.Files[0].FileSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b756"); + newInstalledFiles3.Files[0].InvocationParameter = "invocation2"; + newInstalledFiles3.Files[0].FileType = Manifest::InstalledFileTypeEnum::Uninstall; + mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.InstalledFiles = newInstalledFiles3; + mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles.has_value()); + REQUIRE_FALSE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->DefaultInstallLocation.empty()); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].RelativeFilePath == "test.exe"); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].DisplayName == ""); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].InvocationParameter == ""); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].FileType == Manifest::InstalledFileTypeEnum::Unknown); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.InstalledFiles->Files[0].FileSha256.empty()); +} + +TEST_CASE("MetadataCollection_Merge_SameInstaller_StartupLinkFiles", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->emplace_back(MakeProductMetadata()); + + std::vector startupLinkFiles; + Correlation::InstalledStartupLinkFile startupLink; + startupLink.RelativeFilePath = "TestApp.lnk"; + startupLink.FileType = Manifest::InstalledFileTypeEnum::Launch; + startupLinkFiles.emplace_back(std::move(startupLink)); + + mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; + mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.StartupLinkFiles = startupLinkFiles; + + // Different relative file path gets added + std::vector newStartupLinkFiles = startupLinkFiles; + newStartupLinkFiles[0].RelativeFilePath = "TestApp2.lnk"; + + mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; + mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.StartupLinkFiles = newStartupLinkFiles; + std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + ProductMetadata mergeMetadata; + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles.has_value()); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->size() == 2); + + // Different other fields clears the fields themselves + std::vector newStartupLinkFiles2 = startupLinkFiles; + newStartupLinkFiles2[0].FileType = Manifest::InstalledFileTypeEnum::Uninstall; + mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.StartupLinkFiles = newStartupLinkFiles2; + mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles.has_value()); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->at(0).RelativeFilePath == "TestApp.lnk"); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.StartupLinkFiles->at(0).FileType == Manifest::InstalledFileTypeEnum::Unknown); +} + +TEST_CASE("MetadataCollection_Merge_SameInstaller_Icons", "[metadata_collection]") +{ + TestMerge mergeData{ MinimalDefaults }; + mergeData.Metadatas->emplace_back(MakeProductMetadata()); + + std::vector testIcons; + AppInstaller::Repository::ExtractedIconInfo iconInfo; + iconInfo.IconContent = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b700"); + iconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("d2a45116709136462ee7a1c42f0e75f0efa258fe959b1504dc8ea4573451b759"); + iconInfo.IconFileType = Manifest::IconFileTypeEnum::Ico; + iconInfo.IconResolution = Manifest::IconResolutionEnum::Custom; + iconInfo.IconTheme = Manifest::IconThemeEnum::Default; + testIcons.emplace_back(std::move(iconInfo)); + + mergeData.Metadatas->at(0).SchemaVersion = { "1.2" }; + mergeData.Metadatas->at(0).InstallerMetadataMap.begin()->second.Icons = testIcons; + + // Different test icons + std::vector newTestIcons; + AppInstaller::Repository::ExtractedIconInfo newIconInfo; + newIconInfo.IconContent = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6"); + newIconInfo.IconSha256 = Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499"); + newIconInfo.IconFileType = Manifest::IconFileTypeEnum::Jpeg; + newIconInfo.IconResolution = Manifest::IconResolutionEnum::Square16; + newIconInfo.IconTheme = Manifest::IconThemeEnum::Light; + newTestIcons.emplace_back(std::move(newIconInfo)); + + mergeData.Metadatas->at(1).SchemaVersion = { "1.2" }; + mergeData.Metadatas->at(1).InstallerMetadataMap.begin()->second.Icons = newTestIcons; + std::wstring mergeResult = InstallerMetadataCollectionContext::Merge(mergeData.ToJSON(), 0, {}); + REQUIRE(!mergeResult.empty()); + + ProductMetadata mergeMetadata; + mergeMetadata.FromJson(web::json::value::parse(mergeResult)); + + // New data always take over + REQUIRE(mergeMetadata.InstallerMetadataMap.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons.size() == 1); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconContent == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconSha256 == Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df8499")); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconFileType == Manifest::IconFileTypeEnum::Jpeg); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconResolution == Manifest::IconResolutionEnum::Square16); + REQUIRE(mergeMetadata.InstallerMetadataMap.begin()->second.Icons[0].IconTheme == Manifest::IconThemeEnum::Light); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/JsonHelper.cpp b/src/AppInstallerCLITests/JsonHelper.cpp index ab5009205c..3bb643d11d 100644 --- a/src/AppInstallerCLITests/JsonHelper.cpp +++ b/src/AppInstallerCLITests/JsonHelper.cpp @@ -1,113 +1,113 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include "cpprest/json.h" - -using namespace AppInstaller; - -web::json::value GetTestJsonObject() -{ - web::json::value jsonObject = web::json::value::object(); - jsonObject[L"Key1"] = web::json::value::string(L"Value1"); - jsonObject[L"Key2"] = web::json::value::string(L"Value2"); - jsonObject[L"IntKey"] = 100; - - web::json::value arrayValue = web::json::value::array(); - arrayValue[0] = web::json::value::string(L"ArrayValue1"); - arrayValue[1] = web::json::value::string(L"ArrayValue2"); - arrayValue[2] = web::json::value::string(L"ArrayValue3"); - jsonObject[L"Array"] = arrayValue; - - return jsonObject; -} - -TEST_CASE("GetUtilityString", "[RestSource]") -{ - REQUIRE(JSON::GetUtilityString("cpprest") == L"cpprest"); - REQUIRE(JSON::GetUtilityString(" ") == L" "); -} - -TEST_CASE("GetJsonValueFromNode", "[RestSource]") -{ - web::json::value jsonObject = GetTestJsonObject(); - std::optional> actual = JSON::GetJsonValueFromNode(jsonObject, L"Key1"); - REQUIRE(actual); - REQUIRE(actual.value().get().as_string() == L"Value1"); - - std::optional> absentKey = JSON::GetJsonValueFromNode(jsonObject, L"Key3"); - REQUIRE(!absentKey); - - web::json::value emptyObject; - std::optional> empty = JSON::GetJsonValueFromNode(emptyObject, L"Key1"); - REQUIRE(!empty); -} - -TEST_CASE("GetRawStringValueFromJsonValue", "[RestSource]") -{ - std::optional stringTest = JSON::GetRawStringValueFromJsonValue(web::json::value::string(L"cpprest ")); - REQUIRE(stringTest); - REQUIRE(stringTest.value() == "cpprest "); - - std::optional emptyTest = JSON::GetRawStringValueFromJsonValue(web::json::value::string(L" ")); - REQUIRE(emptyTest); - REQUIRE(emptyTest.value() == " "); - - web::json::value obj; - std::optional nullTest = JSON::GetRawStringValueFromJsonValue(obj); - REQUIRE(!nullTest); - - web::json::value integer = 100; - std::optional mismatchFieldTest = JSON::GetRawStringValueFromJsonValue(integer); - REQUIRE(!mismatchFieldTest); -} - -TEST_CASE("GetRawStringValueFromJsonNode", "[RestSource]") -{ - web::json::value jsonObject = GetTestJsonObject(); - - std::optional stringTest = JSON::GetRawStringValueFromJsonNode(jsonObject, L"Key1"); - REQUIRE(stringTest); - REQUIRE(stringTest.value() == "Value1"); - - std::optional emptyTest = JSON::GetRawStringValueFromJsonNode(jsonObject, L"Key3"); - REQUIRE(!emptyTest); - - std::optional mismatchFieldTest = JSON::GetRawStringValueFromJsonNode(jsonObject, L"IntKey"); - REQUIRE(!mismatchFieldTest); -} - -TEST_CASE("GetRawIntValueFromJsonValue", "[RestSource]") -{ - web::json::value jsonObject = 100; - std::optional expected = JSON::GetRawIntValueFromJsonValue(jsonObject); - REQUIRE(expected); - REQUIRE(expected.value() == 100); - - std::optional mismatchFieldTest = JSON::GetRawIntValueFromJsonValue(web::json::value::string(L"cpprest")); - REQUIRE(!mismatchFieldTest); -} - -TEST_CASE("GetRawJsonArrayFromJsonNode", "[RestSource]") -{ - web::json::value jsonObject = GetTestJsonObject(); - std::optional> expected = JSON::GetRawJsonArrayFromJsonNode(jsonObject, L"Array"); - REQUIRE(expected); - REQUIRE(expected.value().get().size() == 3); - REQUIRE(expected.value().get().at(0).as_string() == L"ArrayValue1"); - - std::optional> mismatchFieldTest = JSON::GetRawJsonArrayFromJsonNode(jsonObject, L"Keyword"); - REQUIRE(!mismatchFieldTest); -} - -TEST_CASE("GetRawStringArrayFromJsonNode", "[RestSource]") -{ - web::json::value jsonObject = GetTestJsonObject(); - std::vector expected = JSON::GetRawStringArrayFromJsonNode(jsonObject, L"Array"); - REQUIRE(expected.size() == 3); - REQUIRE(expected[0] == "ArrayValue1"); - - std::vector mismatchFieldTest = JSON::GetRawStringArrayFromJsonNode(jsonObject, L"Keyword"); - REQUIRE(mismatchFieldTest.size() == 0); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include "cpprest/json.h" + +using namespace AppInstaller; + +web::json::value GetTestJsonObject() +{ + web::json::value jsonObject = web::json::value::object(); + jsonObject[L"Key1"] = web::json::value::string(L"Value1"); + jsonObject[L"Key2"] = web::json::value::string(L"Value2"); + jsonObject[L"IntKey"] = 100; + + web::json::value arrayValue = web::json::value::array(); + arrayValue[0] = web::json::value::string(L"ArrayValue1"); + arrayValue[1] = web::json::value::string(L"ArrayValue2"); + arrayValue[2] = web::json::value::string(L"ArrayValue3"); + jsonObject[L"Array"] = arrayValue; + + return jsonObject; +} + +TEST_CASE("GetUtilityString", "[RestSource]") +{ + REQUIRE(JSON::GetUtilityString("cpprest") == L"cpprest"); + REQUIRE(JSON::GetUtilityString(" ") == L" "); +} + +TEST_CASE("GetJsonValueFromNode", "[RestSource]") +{ + web::json::value jsonObject = GetTestJsonObject(); + std::optional> actual = JSON::GetJsonValueFromNode(jsonObject, L"Key1"); + REQUIRE(actual); + REQUIRE(actual.value().get().as_string() == L"Value1"); + + std::optional> absentKey = JSON::GetJsonValueFromNode(jsonObject, L"Key3"); + REQUIRE(!absentKey); + + web::json::value emptyObject; + std::optional> empty = JSON::GetJsonValueFromNode(emptyObject, L"Key1"); + REQUIRE(!empty); +} + +TEST_CASE("GetRawStringValueFromJsonValue", "[RestSource]") +{ + std::optional stringTest = JSON::GetRawStringValueFromJsonValue(web::json::value::string(L"cpprest ")); + REQUIRE(stringTest); + REQUIRE(stringTest.value() == "cpprest "); + + std::optional emptyTest = JSON::GetRawStringValueFromJsonValue(web::json::value::string(L" ")); + REQUIRE(emptyTest); + REQUIRE(emptyTest.value() == " "); + + web::json::value obj; + std::optional nullTest = JSON::GetRawStringValueFromJsonValue(obj); + REQUIRE(!nullTest); + + web::json::value integer = 100; + std::optional mismatchFieldTest = JSON::GetRawStringValueFromJsonValue(integer); + REQUIRE(!mismatchFieldTest); +} + +TEST_CASE("GetRawStringValueFromJsonNode", "[RestSource]") +{ + web::json::value jsonObject = GetTestJsonObject(); + + std::optional stringTest = JSON::GetRawStringValueFromJsonNode(jsonObject, L"Key1"); + REQUIRE(stringTest); + REQUIRE(stringTest.value() == "Value1"); + + std::optional emptyTest = JSON::GetRawStringValueFromJsonNode(jsonObject, L"Key3"); + REQUIRE(!emptyTest); + + std::optional mismatchFieldTest = JSON::GetRawStringValueFromJsonNode(jsonObject, L"IntKey"); + REQUIRE(!mismatchFieldTest); +} + +TEST_CASE("GetRawIntValueFromJsonValue", "[RestSource]") +{ + web::json::value jsonObject = 100; + std::optional expected = JSON::GetRawIntValueFromJsonValue(jsonObject); + REQUIRE(expected); + REQUIRE(expected.value() == 100); + + std::optional mismatchFieldTest = JSON::GetRawIntValueFromJsonValue(web::json::value::string(L"cpprest")); + REQUIRE(!mismatchFieldTest); +} + +TEST_CASE("GetRawJsonArrayFromJsonNode", "[RestSource]") +{ + web::json::value jsonObject = GetTestJsonObject(); + std::optional> expected = JSON::GetRawJsonArrayFromJsonNode(jsonObject, L"Array"); + REQUIRE(expected); + REQUIRE(expected.value().get().size() == 3); + REQUIRE(expected.value().get().at(0).as_string() == L"ArrayValue1"); + + std::optional> mismatchFieldTest = JSON::GetRawJsonArrayFromJsonNode(jsonObject, L"Keyword"); + REQUIRE(!mismatchFieldTest); +} + +TEST_CASE("GetRawStringArrayFromJsonNode", "[RestSource]") +{ + web::json::value jsonObject = GetTestJsonObject(); + std::vector expected = JSON::GetRawStringArrayFromJsonNode(jsonObject, L"Array"); + REQUIRE(expected.size() == 3); + REQUIRE(expected[0] == "ArrayValue1"); + + std::vector mismatchFieldTest = JSON::GetRawStringArrayFromJsonNode(jsonObject, L"Keyword"); + REQUIRE(mismatchFieldTest.size() == 0); +} diff --git a/src/AppInstallerCLITests/LanguageUtilities.cpp b/src/AppInstallerCLITests/LanguageUtilities.cpp index 8dde3b71b1..9dbe4cbbdd 100644 --- a/src/AppInstallerCLITests/LanguageUtilities.cpp +++ b/src/AppInstallerCLITests/LanguageUtilities.cpp @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include - -using namespace AppInstaller; - - -TEST_CASE("DestructionToken", "[langutil]") -{ - DestructionToken beginToken = true; - DestructionToken endToken = false; - - REQUIRE(beginToken); - REQUIRE(!endToken); - - endToken = std::move(beginToken); - - REQUIRE(!beginToken); - REQUIRE(endToken); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace AppInstaller; + + +TEST_CASE("DestructionToken", "[langutil]") +{ + DestructionToken beginToken = true; + DestructionToken endToken = false; + + REQUIRE(beginToken); + REQUIRE(!endToken); + + endToken = std::move(beginToken); + + REQUIRE(!beginToken); + REQUIRE(endToken); +} diff --git a/src/AppInstallerCLITests/MSStoreDownloadFlow.cpp b/src/AppInstallerCLITests/MSStoreDownloadFlow.cpp index ca0775b4f6..df42c25eaf 100644 --- a/src/AppInstallerCLITests/MSStoreDownloadFlow.cpp +++ b/src/AppInstallerCLITests/MSStoreDownloadFlow.cpp @@ -1,663 +1,663 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestHooks.h" -#include "TestRestRequestHandler.h" -#include "WorkflowCommon.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility::literals; - -utility::string_t TestDisplayCatalogResponse = _XPLATSTR( - R"delimiter( - { - "Product": { - "DisplaySkuAvailabilities": [ - { - "Sku": { - "SkuId": "0015", - "Properties": { - "Packages": [ - { - "PackageId": "PackageEnglish", - "Architectures": [ "x64", "arm" ], - "Languages": [ "en-US", "en-GB" ], - "PackageFormat": "Appx", - "ContentId": "LicenseContentId", - "FulfillmentData": { - "WuCategoryId": "TestCategoryIdEnglish" - } - }, - { - "PackageId": "PackageFrench", - "Architectures": [ "x64", "arm" ], - "Languages": [ "fr-FR" ], - "PackageFormat": "Appx", - "ContentId": "LicenseContentId", - "FulfillmentData": { - "WuCategoryId": "TestCategoryIdFrench" - } - } - ] - } - } - } - ] - } - })delimiter"); - -utility::string_t TestLicensingResponseRaw = _XPLATSTR( - R"delimiter( - { - "license": { - "keys": [ - { - "value": "" - } - ] - } - })delimiter"); - -std::string LicenseContent = "TestLicense"; - -utility::string_t TestLicensingResponse = AppInstaller::Utility::ReplaceWhileCopying( - TestLicensingResponseRaw, L"", - AppInstaller::Utility::ConvertToUTF16(AppInstaller::JSON::Base64Encode(std::vector{ LicenseContent.begin(), LicenseContent.end() }))); - -utility::string_t TestDisplayCatalogResponse_TargetSkuNotFound = _XPLATSTR( - R"delimiter( - { - "Product": { - "DisplaySkuAvailabilities": [ - { - "Sku": { - "SkuId": "0011", - "Properties": { - "Packages": [ - { - "PackageId": "PackageEnglish", - "Architectures": [ "x64", "arm" ], - "Languages": [ "en-US", "en-GB" ], - "PackageFormat": "Appx", - "ContentId": "LicenseContentId", - "FulfillmentData": { - "WuCategoryId": "TestCategoryIdEnglish" - } - } - ] - } - } - } - ] - } - })delimiter"); - -std::vector GetSfsAppContentsOverrideFunction(std::string_view wuCategoryId) -{ - std::string wuCategoryIdStr{ wuCategoryId }; - - std::vector result; - - std::unique_ptr contentId; - std::vector dependencies; - std::vector packages; - std::unique_ptr appContent; - - std::vector sha256Bytes = AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798223CD4FEBAB1D734A07C2E51E56A28E0DF8123"); - std::string base64EncodedSha256 = AppInstaller::JSON::Base64Encode(sha256Bytes); - - { - // Create dependencies content - std::unique_ptr dependencyContentId; - std::vector dependencyPackages; - std::unique_ptr dependencyContent; - - std::ignore = SFS::ContentId::Make("testDependency", "testDependency", "1.0.0.0", dependencyContentId); - - std::unique_ptr dependencyX64; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/dependency/x64", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Amd64 }, - { "Universal=10.0.0.0" }, - wuCategoryIdStr + ".Dependency_1.2.3.4_x64__8wekyb3d8bbwe", - dependencyX64); - dependencyPackages.emplace_back(std::move(*dependencyX64)); - - // Lower target OS dependency - std::unique_ptr dependencyX64_lower; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/dependency/x64", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Amd64 }, - { "Universal=9.0.0.0" }, - wuCategoryIdStr + ".Dependency_0.9.3.4_x64__8wekyb3d8bbwe", - dependencyX64_lower); - dependencyPackages.emplace_back(std::move(*dependencyX64_lower)); - - std::unique_ptr dependencyArm; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/dependency/arm", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Arm }, - { "Universal=10.0.0.0" }, - wuCategoryIdStr + ".Dependency_1.2.3.4_arm__8wekyb3d8bbwe", - dependencyArm); - dependencyPackages.emplace_back(std::move(*dependencyArm)); - - std::ignore = SFS::AppPrerequisiteContent::Make(std::move(dependencyContentId), std::move(dependencyPackages), dependencyContent); - - dependencies.emplace_back(std::move(*dependencyContent)); - } - - { - // Create main packages - - // Good candidate x64 - std::unique_ptr packageX64; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/x64", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Amd64 }, - { "Desktop=10.0.0.0" }, - wuCategoryIdStr + "_1.0.0.0_x64__8wekyb3d8bbwe", - packageX64); - packages.emplace_back(std::move(*packageX64)); - - // Good candidate x64, lower minimum OS version, lower package version - std::unique_ptr packageX64_lower; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/x64", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Amd64 }, - { "Desktop=9.0.0.0" }, - wuCategoryIdStr + "_0.9.0.0_x64__8wekyb3d8bbwe", - packageX64_lower); - packages.emplace_back(std::move(*packageX64_lower)); - - // Good candidate arm - std::unique_ptr packageArm; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/arm", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Arm }, - { "Desktop=10.0.0.0" }, - wuCategoryIdStr + "_1.0.0.0_arm__8wekyb3d8bbwe", - packageArm); - packages.emplace_back(std::move(*packageArm)); - - // Good candidate IoT - std::unique_ptr packageIoT; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/IoT/arm", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Arm }, - { "IoT=10.0.0.0" }, - wuCategoryIdStr + ".IoT_1.0.0.0_arm__8wekyb3d8bbwe", - packageIoT); - packages.emplace_back(std::move(*packageIoT)); - - // Good candidate IoT has newer version - std::unique_ptr packageIoT2; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/IoT/arm/2.0", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Arm }, - { "IoT=10.0.0.0" }, - wuCategoryIdStr + ".IoT_2.0.0.0_arm__8wekyb3d8bbwe", - packageIoT2); - packages.emplace_back(std::move(*packageIoT2)); - - // Candidate unsupported platform - std::unique_ptr packageXbox; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".appx", - "https://NotUsed/" + wuCategoryIdStr + "/Xbox/arm", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Arm }, - { "Xbox=10.0.0.0" }, - wuCategoryIdStr + ".Xbox_1.0.0.0_arm__8wekyb3d8bbwe", - packageXbox); - packages.emplace_back(std::move(*packageXbox)); - - // Candidate unsupported filetype - std::unique_ptr packageData; - std::ignore = SFS::AppFile::Make( - wuCategoryIdStr + ".cab", - "https://NotUsed/" + wuCategoryIdStr + "/cab", - 100, - { { SFS::HashType::Sha256, base64EncodedSha256 } }, - { SFS::Architecture::Arm }, - { "Desktop=10.0.0.0" }, - wuCategoryIdStr + ".Data_1.0.0.0_arm__8wekyb3d8bbwe", - packageData); - packages.emplace_back(std::move(*packageData)); - } - - std::ignore = SFS::ContentId::Make("test", "test", "1.0.0.0", contentId); - std::ignore = SFS::AppContent::Make(std::move(contentId), "updateId", std::move(dependencies), std::move(packages), appContent); - - result.emplace_back(std::move(*appContent)); - - return result; -} - -TEST_CASE("MSStoreDownloadFlow_Success", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE(context.GetTerminationHR() == S_OK); - INFO(downloadOutput.str()); - - // Verify downloaded files - REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); - - // Verify license - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); - std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); - REQUIRE(licenseFile.is_open()); - std::string licenseFileStr; - std::getline(licenseFile, licenseFileStr); - REQUIRE(licenseFileStr == LicenseContent); - - // Verify unsupported packages filtered out - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_1.0.0.0_IoT_Arm.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.Xbox_1.0.0.0_Xbox_Arm.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.Data_1.0.0.0_Desktop_Arm.cab")); -} - -TEST_CASE("MSStoreDownloadFlow_Success_SkipDependencies", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - context.Args.AddArg(Execution::Args::Type::SkipDependencies); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE(context.GetTerminationHR() == S_OK); - INFO(downloadOutput.str()); - - // Verify downloaded files - REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); - - // Verify license - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); - std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); - REQUIRE(licenseFile.is_open()); - std::string licenseFileStr; - std::getline(licenseFile, licenseFileStr); - REQUIRE(licenseFileStr == LicenseContent); -} - -TEST_CASE("MSStoreDownloadFlow_Success_SkipLicense", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - context.Args.AddArg(Execution::Args::Type::SkipMicrosoftStorePackageLicense); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE(context.GetTerminationHR() == S_OK); - INFO(downloadOutput.str()); - - // Verify downloaded files - REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); - - // Verify license - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); -} - -TEST_CASE("MSStoreDownloadFlow_Success_SpecificLocale", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "fr-FR"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE(context.GetTerminationHR() == S_OK); - INFO(downloadOutput.str()); - - // Verify downloaded files - REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdFrench.Dependency_1.2.3.4_Universal_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdFrench.Dependency_1.2.3.4_Universal_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdFrench_1.0.0.0_Desktop_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdFrench_1.0.0.0_Desktop_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdFrench.IoT_2.0.0.0_IoT_Arm.appx")); - - // Verify license - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); - std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); - REQUIRE(licenseFile.is_open()); - std::string licenseFileStr; - std::getline(licenseFile, licenseFileStr); - REQUIRE(licenseFileStr == LicenseContent); -} - -TEST_CASE("MSStoreDownloadFlow_Success_SpecificArchitecture", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - context.Args.AddArg(Execution::Args::Type::InstallerArchitecture, "x64"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE(context.GetTerminationHR() == S_OK); - INFO(downloadOutput.str()); - - // Verify downloaded files - REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_x64.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); - - // Verify license - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); - std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); - REQUIRE(licenseFile.is_open()); - std::string licenseFileStr; - std::getline(licenseFile, licenseFileStr); - REQUIRE(licenseFileStr == LicenseContent); -} - -TEST_CASE("MSStoreDownloadFlow_Success_SpecificPlatform", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - context.Args.AddArg(Execution::Args::Type::Platform, "Windows.IoT"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE(context.GetTerminationHR() == S_OK); - INFO(downloadOutput.str()); - - // Verify downloaded files - REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); - - // Verify license - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); - std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); - REQUIRE(licenseFile.is_open()); - std::string licenseFileStr; - std::getline(licenseFile, licenseFileStr); - REQUIRE(licenseFileStr == LicenseContent); -} - -TEST_CASE("MSStoreDownloadFlow_Fail_TargetSkuNotFound", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse_TargetSkuNotFound)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); - INFO(downloadOutput.str()); -} - -TEST_CASE("MSStoreDownloadFlow_Fail_LocaleNotApplicable", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "ja-JP"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); - INFO(downloadOutput.str()); -} - -TEST_CASE("MSStoreDownloadFlow_Fail_ArchitectureNotApplicable", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - context.Args.AddArg(Execution::Args::Type::InstallerArchitecture, "arm64"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); - INFO(downloadOutput.str()); -} - -TEST_CASE("MSStoreDownloadFlow_Fail_PlatformNotApplicable", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - context.Args.AddArg(Execution::Args::Type::Platform, "Windows.Holographic"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE); - INFO(downloadOutput.str()); -} - -TEST_CASE("MSStoreDownloadFlow_Fail_Licensing", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::InternalError)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE_TERMINATED_WITH(context, MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, web::http::status_codes::InternalError)); - INFO(downloadOutput.str()); -} - -TEST_CASE("MSStoreDownloadFlow_Fail_Licensing_Forbidden", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::Forbidden)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN); - INFO(downloadOutput.str()); -} - -TEST_CASE("MSStoreDownloadFlow_Success_TargetOSVersion", "[MSStoreDownloadFlow][workflow]") -{ - TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); - - std::ostringstream downloadOutput; - TestContext context{ downloadOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideDownloadInstallerFileForMSStoreDownload(context); - TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); - TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); - TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); - context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); - context.Args.AddArg(Execution::Args::Type::Platform, "Windows.Desktop"sv); - context.Args.AddArg(Execution::Args::Type::OSVersion, "9.0.0.0"sv); - - DownloadCommand download({}); - download.Execute(context); - REQUIRE(context.GetTerminationHR() == S_OK); - INFO(downloadOutput.str()); - - // Verify downloaded files - REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_0.9.3.4_Universal_X64.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_0.9.0.0_Desktop_X64.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); - REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); - - // Verify license - REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); - std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); - REQUIRE(licenseFile.is_open()); - std::string licenseFileStr; - std::getline(licenseFile, licenseFileStr); - REQUIRE(licenseFileStr == LicenseContent); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestHooks.h" +#include "TestRestRequestHandler.h" +#include "WorkflowCommon.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility::literals; + +utility::string_t TestDisplayCatalogResponse = _XPLATSTR( + R"delimiter( + { + "Product": { + "DisplaySkuAvailabilities": [ + { + "Sku": { + "SkuId": "0015", + "Properties": { + "Packages": [ + { + "PackageId": "PackageEnglish", + "Architectures": [ "x64", "arm" ], + "Languages": [ "en-US", "en-GB" ], + "PackageFormat": "Appx", + "ContentId": "LicenseContentId", + "FulfillmentData": { + "WuCategoryId": "TestCategoryIdEnglish" + } + }, + { + "PackageId": "PackageFrench", + "Architectures": [ "x64", "arm" ], + "Languages": [ "fr-FR" ], + "PackageFormat": "Appx", + "ContentId": "LicenseContentId", + "FulfillmentData": { + "WuCategoryId": "TestCategoryIdFrench" + } + } + ] + } + } + } + ] + } + })delimiter"); + +utility::string_t TestLicensingResponseRaw = _XPLATSTR( + R"delimiter( + { + "license": { + "keys": [ + { + "value": "" + } + ] + } + })delimiter"); + +std::string LicenseContent = "TestLicense"; + +utility::string_t TestLicensingResponse = AppInstaller::Utility::ReplaceWhileCopying( + TestLicensingResponseRaw, L"", + AppInstaller::Utility::ConvertToUTF16(AppInstaller::JSON::Base64Encode(std::vector{ LicenseContent.begin(), LicenseContent.end() }))); + +utility::string_t TestDisplayCatalogResponse_TargetSkuNotFound = _XPLATSTR( + R"delimiter( + { + "Product": { + "DisplaySkuAvailabilities": [ + { + "Sku": { + "SkuId": "0011", + "Properties": { + "Packages": [ + { + "PackageId": "PackageEnglish", + "Architectures": [ "x64", "arm" ], + "Languages": [ "en-US", "en-GB" ], + "PackageFormat": "Appx", + "ContentId": "LicenseContentId", + "FulfillmentData": { + "WuCategoryId": "TestCategoryIdEnglish" + } + } + ] + } + } + } + ] + } + })delimiter"); + +std::vector GetSfsAppContentsOverrideFunction(std::string_view wuCategoryId) +{ + std::string wuCategoryIdStr{ wuCategoryId }; + + std::vector result; + + std::unique_ptr contentId; + std::vector dependencies; + std::vector packages; + std::unique_ptr appContent; + + std::vector sha256Bytes = AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798223CD4FEBAB1D734A07C2E51E56A28E0DF8123"); + std::string base64EncodedSha256 = AppInstaller::JSON::Base64Encode(sha256Bytes); + + { + // Create dependencies content + std::unique_ptr dependencyContentId; + std::vector dependencyPackages; + std::unique_ptr dependencyContent; + + std::ignore = SFS::ContentId::Make("testDependency", "testDependency", "1.0.0.0", dependencyContentId); + + std::unique_ptr dependencyX64; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/dependency/x64", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Amd64 }, + { "Universal=10.0.0.0" }, + wuCategoryIdStr + ".Dependency_1.2.3.4_x64__8wekyb3d8bbwe", + dependencyX64); + dependencyPackages.emplace_back(std::move(*dependencyX64)); + + // Lower target OS dependency + std::unique_ptr dependencyX64_lower; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/dependency/x64", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Amd64 }, + { "Universal=9.0.0.0" }, + wuCategoryIdStr + ".Dependency_0.9.3.4_x64__8wekyb3d8bbwe", + dependencyX64_lower); + dependencyPackages.emplace_back(std::move(*dependencyX64_lower)); + + std::unique_ptr dependencyArm; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/dependency/arm", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Arm }, + { "Universal=10.0.0.0" }, + wuCategoryIdStr + ".Dependency_1.2.3.4_arm__8wekyb3d8bbwe", + dependencyArm); + dependencyPackages.emplace_back(std::move(*dependencyArm)); + + std::ignore = SFS::AppPrerequisiteContent::Make(std::move(dependencyContentId), std::move(dependencyPackages), dependencyContent); + + dependencies.emplace_back(std::move(*dependencyContent)); + } + + { + // Create main packages + + // Good candidate x64 + std::unique_ptr packageX64; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/x64", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Amd64 }, + { "Desktop=10.0.0.0" }, + wuCategoryIdStr + "_1.0.0.0_x64__8wekyb3d8bbwe", + packageX64); + packages.emplace_back(std::move(*packageX64)); + + // Good candidate x64, lower minimum OS version, lower package version + std::unique_ptr packageX64_lower; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/x64", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Amd64 }, + { "Desktop=9.0.0.0" }, + wuCategoryIdStr + "_0.9.0.0_x64__8wekyb3d8bbwe", + packageX64_lower); + packages.emplace_back(std::move(*packageX64_lower)); + + // Good candidate arm + std::unique_ptr packageArm; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/arm", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Arm }, + { "Desktop=10.0.0.0" }, + wuCategoryIdStr + "_1.0.0.0_arm__8wekyb3d8bbwe", + packageArm); + packages.emplace_back(std::move(*packageArm)); + + // Good candidate IoT + std::unique_ptr packageIoT; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/IoT/arm", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Arm }, + { "IoT=10.0.0.0" }, + wuCategoryIdStr + ".IoT_1.0.0.0_arm__8wekyb3d8bbwe", + packageIoT); + packages.emplace_back(std::move(*packageIoT)); + + // Good candidate IoT has newer version + std::unique_ptr packageIoT2; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/IoT/arm/2.0", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Arm }, + { "IoT=10.0.0.0" }, + wuCategoryIdStr + ".IoT_2.0.0.0_arm__8wekyb3d8bbwe", + packageIoT2); + packages.emplace_back(std::move(*packageIoT2)); + + // Candidate unsupported platform + std::unique_ptr packageXbox; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".appx", + "https://NotUsed/" + wuCategoryIdStr + "/Xbox/arm", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Arm }, + { "Xbox=10.0.0.0" }, + wuCategoryIdStr + ".Xbox_1.0.0.0_arm__8wekyb3d8bbwe", + packageXbox); + packages.emplace_back(std::move(*packageXbox)); + + // Candidate unsupported filetype + std::unique_ptr packageData; + std::ignore = SFS::AppFile::Make( + wuCategoryIdStr + ".cab", + "https://NotUsed/" + wuCategoryIdStr + "/cab", + 100, + { { SFS::HashType::Sha256, base64EncodedSha256 } }, + { SFS::Architecture::Arm }, + { "Desktop=10.0.0.0" }, + wuCategoryIdStr + ".Data_1.0.0.0_arm__8wekyb3d8bbwe", + packageData); + packages.emplace_back(std::move(*packageData)); + } + + std::ignore = SFS::ContentId::Make("test", "test", "1.0.0.0", contentId); + std::ignore = SFS::AppContent::Make(std::move(contentId), "updateId", std::move(dependencies), std::move(packages), appContent); + + result.emplace_back(std::move(*appContent)); + + return result; +} + +TEST_CASE("MSStoreDownloadFlow_Success", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE(context.GetTerminationHR() == S_OK); + INFO(downloadOutput.str()); + + // Verify downloaded files + REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); + + // Verify license + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); + std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); + REQUIRE(licenseFile.is_open()); + std::string licenseFileStr; + std::getline(licenseFile, licenseFileStr); + REQUIRE(licenseFileStr == LicenseContent); + + // Verify unsupported packages filtered out + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_1.0.0.0_IoT_Arm.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.Xbox_1.0.0.0_Xbox_Arm.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.Data_1.0.0.0_Desktop_Arm.cab")); +} + +TEST_CASE("MSStoreDownloadFlow_Success_SkipDependencies", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + context.Args.AddArg(Execution::Args::Type::SkipDependencies); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE(context.GetTerminationHR() == S_OK); + INFO(downloadOutput.str()); + + // Verify downloaded files + REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); + + // Verify license + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); + std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); + REQUIRE(licenseFile.is_open()); + std::string licenseFileStr; + std::getline(licenseFile, licenseFileStr); + REQUIRE(licenseFileStr == LicenseContent); +} + +TEST_CASE("MSStoreDownloadFlow_Success_SkipLicense", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + context.Args.AddArg(Execution::Args::Type::SkipMicrosoftStorePackageLicense); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE(context.GetTerminationHR() == S_OK); + INFO(downloadOutput.str()); + + // Verify downloaded files + REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); + + // Verify license + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); +} + +TEST_CASE("MSStoreDownloadFlow_Success_SpecificLocale", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "fr-FR"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE(context.GetTerminationHR() == S_OK); + INFO(downloadOutput.str()); + + // Verify downloaded files + REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdFrench.Dependency_1.2.3.4_Universal_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdFrench.Dependency_1.2.3.4_Universal_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdFrench_1.0.0.0_Desktop_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdFrench_1.0.0.0_Desktop_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdFrench.IoT_2.0.0.0_IoT_Arm.appx")); + + // Verify license + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); + std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); + REQUIRE(licenseFile.is_open()); + std::string licenseFileStr; + std::getline(licenseFile, licenseFileStr); + REQUIRE(licenseFileStr == LicenseContent); +} + +TEST_CASE("MSStoreDownloadFlow_Success_SpecificArchitecture", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + context.Args.AddArg(Execution::Args::Type::InstallerArchitecture, "x64"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE(context.GetTerminationHR() == S_OK); + INFO(downloadOutput.str()); + + // Verify downloaded files + REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_x64.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); + + // Verify license + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); + std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); + REQUIRE(licenseFile.is_open()); + std::string licenseFileStr; + std::getline(licenseFile, licenseFileStr); + REQUIRE(licenseFileStr == LicenseContent); +} + +TEST_CASE("MSStoreDownloadFlow_Success_SpecificPlatform", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + context.Args.AddArg(Execution::Args::Type::Platform, "Windows.IoT"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE(context.GetTerminationHR() == S_OK); + INFO(downloadOutput.str()); + + // Verify downloaded files + REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_X64.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_X64.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); + + // Verify license + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); + std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); + REQUIRE(licenseFile.is_open()); + std::string licenseFileStr; + std::getline(licenseFile, licenseFileStr); + REQUIRE(licenseFileStr == LicenseContent); +} + +TEST_CASE("MSStoreDownloadFlow_Fail_TargetSkuNotFound", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse_TargetSkuNotFound)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); + INFO(downloadOutput.str()); +} + +TEST_CASE("MSStoreDownloadFlow_Fail_LocaleNotApplicable", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "ja-JP"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); + INFO(downloadOutput.str()); +} + +TEST_CASE("MSStoreDownloadFlow_Fail_ArchitectureNotApplicable", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + context.Args.AddArg(Execution::Args::Type::InstallerArchitecture, "arm64"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); + INFO(downloadOutput.str()); +} + +TEST_CASE("MSStoreDownloadFlow_Fail_PlatformNotApplicable", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + context.Args.AddArg(Execution::Args::Type::Platform, "Windows.Holographic"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE); + INFO(downloadOutput.str()); +} + +TEST_CASE("MSStoreDownloadFlow_Fail_Licensing", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::InternalError)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE_TERMINATED_WITH(context, MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, web::http::status_codes::InternalError)); + INFO(downloadOutput.str()); +} + +TEST_CASE("MSStoreDownloadFlow_Fail_Licensing_Forbidden", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::Forbidden)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN); + INFO(downloadOutput.str()); +} + +TEST_CASE("MSStoreDownloadFlow_Success_TargetOSVersion", "[MSStoreDownloadFlow][workflow]") +{ + TestCommon::TempDirectory tempDirectory("TestDownloadDirectory", false); + + std::ostringstream downloadOutput; + TestContext context{ downloadOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideDownloadInstallerFileForMSStoreDownload(context); + TestHook::SetDisplayCatalogHttpPipelineStage_Override displayCatalogOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestDisplayCatalogResponse)); + TestHook::SetSfsClientAppContents_Override sfsClientOverride({ &GetSfsAppContentsOverrideFunction }); + TestHook::SetLicensingHttpPipelineStage_Override licensingOverride(GetTestRestRequestHandler(web::http::status_codes::OK, TestLicensingResponse)); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("DownloadFlowTest_MSStore.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::DownloadDirectory, tempDirectory); + context.Args.AddArg(Execution::Args::Type::Locale, "en-US"sv); + context.Args.AddArg(Execution::Args::Type::Platform, "Windows.Desktop"sv); + context.Args.AddArg(Execution::Args::Type::OSVersion, "9.0.0.0"sv); + + DownloadCommand download({}); + download.Execute(context); + REQUIRE(context.GetTerminationHR() == S_OK); + INFO(downloadOutput.str()); + + // Verify downloaded files + REQUIRE(std::filesystem::exists(tempDirectory.GetPath())); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_0.9.3.4_Universal_X64.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"Dependencies" / L"TestCategoryIdEnglish.Dependency_1.2.3.4_Universal_Arm.appx")); + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_0.9.0.0_Desktop_X64.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish_1.0.0.0_Desktop_Arm.appx")); + REQUIRE_FALSE(std::filesystem::exists(tempDirectory.GetPath() / L"TestCategoryIdEnglish.IoT_2.0.0.0_IoT_Arm.appx")); + + // Verify license + REQUIRE(std::filesystem::exists(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml")); + std::ifstream licenseFile(tempDirectory.GetPath() / L"9WZDNCRFJ364_License.xml"); + REQUIRE(licenseFile.is_open()); + std::string licenseFileStr; + std::getline(licenseFile, licenseFileStr); + REQUIRE(licenseFileStr == LicenseContent); +} diff --git a/src/AppInstallerCLITests/ManifestComparator.cpp b/src/AppInstallerCLITests/ManifestComparator.cpp index 5555c52eec..986af6ac48 100644 --- a/src/AppInstallerCLITests/ManifestComparator.cpp +++ b/src/AppInstallerCLITests/ManifestComparator.cpp @@ -1,952 +1,952 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; - -using Manifest = ::AppInstaller::Manifest::Manifest; - -struct ManifestComparatorTestContext : public NullStream, Context -{ - ManifestComparatorTestContext() : NullStream(), Context(*m_nullOut, *m_nullIn) {} -}; - -const ManifestInstaller& AddInstaller( - Manifest& manifest, - Architecture architecture, - InstallerTypeEnum installerType, - ScopeEnum scope = ScopeEnum::Unknown, - std::string minOSVersion = {}, - std::string locale = {}, - std::vector unsupportedOSArchitectures = {}, - MarketsInfo markets = {}) -{ - ManifestInstaller toAdd; - toAdd.Arch = architecture; - toAdd.BaseInstallerType = installerType; - toAdd.Scope = scope; - toAdd.MinOSVersion = minOSVersion; - toAdd.Locale = locale; - toAdd.UnsupportedOSArchitectures = unsupportedOSArchitectures; - toAdd.Markets = markets; - - manifest.Installers.emplace_back(std::move(toAdd)); - - return manifest.Installers.back(); -} - -template -void RequireVectorsEqual(const std::vector& actual, const std::vector& expected) -{ - REQUIRE(actual.size() == expected.size()); - - for (std::size_t i = 0; i < actual.size(); ++i) - { - REQUIRE(actual[i] == expected[i]); - } -} - -void RequireInstaller(const std::optional& actual, const ManifestInstaller& expected) -{ - REQUIRE(actual); - REQUIRE(actual->Arch == expected.Arch); - REQUIRE(actual->EffectiveInstallerType() == expected.EffectiveInstallerType()); - REQUIRE(actual->Scope == expected.Scope); - REQUIRE(actual->MinOSVersion == expected.MinOSVersion); - REQUIRE(actual->Locale == expected.Locale); - - RequireVectorsEqual(actual->Markets.AllowedMarkets, expected.Markets.AllowedMarkets); - RequireVectorsEqual(actual->Markets.ExcludedMarkets, expected.Markets.ExcludedMarkets); -} - -void RequireInapplicabilities(const std::vector& inapplicabilities, const std::vector& expected) -{ - RequireVectorsEqual(inapplicabilities, expected); -} - -void RequireInapplicabilityType(const std::vector& inapplicabilities, InapplicabilityFlags expected) -{ - REQUIRE(!inapplicabilities.empty()); - for (std::size_t i = 0; i < inapplicabilities.size(); ++i) - { - REQUIRE(inapplicabilities[i] == expected); - } -} - -TEST_CASE("ManifestComparator_OSFilter_Low", "[manifest_comparator]") -{ - Manifest manifest; - AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.99999.0"); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::OSVersion }); -} - -TEST_CASE("ManifestComparator_OSFilter_High", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller expected = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.0.0"); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, expected); - REQUIRE(inapplicabilities.size() == 0); -} - -TEST_CASE("ManifestComparator_InstalledScopeFilter_Unknown", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller unknown = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Unknown); - - SECTION("Nothing Installed") - { - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // Only because it is first - RequireInstaller(result, unknown); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("User Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, unknown); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Machine Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, unknown); - REQUIRE(inapplicabilities.size() == 0); - } -} - -TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); - ManifestInstaller machine = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Machine); - - SECTION("Nothing Installed") - { - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // Only because it is first - RequireInstaller(result, user); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("User Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, user); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledScope }); - } - SECTION("Machine Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, machine); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledScope }); - } -} - -TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller msi = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi); - ManifestInstaller msix = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msix); - - SECTION("Nothing Installed") - { - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // Msix is preferred over Msi by the default installer type precedence order - RequireInstaller(result, msix); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("MSI Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msi); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msi); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledType }); - } - SECTION("MSIX Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msix); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledType }); - } -} - -TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller burn = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Burn); - ManifestInstaller exe = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe); - - SECTION("Nothing Installed") - { - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // Burn is preferred over Exe by the default installer type precedence order - RequireInstaller(result, burn); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Exe Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Exe); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, burn); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Inno Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Inno); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, burn); - REQUIRE(inapplicabilities.size() == 0); - } -} - -TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); - ManifestInstaller machine = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Machine); - - SECTION("Nothing Required") - { - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // Only because it is first - RequireInstaller(result, user); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("User Required") - { - ManifestComparatorTestContext context; - context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::User)); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, user); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); - } - SECTION("Machine Required") - { - ManifestComparatorTestContext context; - context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::Machine)); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, machine); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); - } -} - -TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller machine = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Machine); - ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); - - SECTION("No Preference") - { - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // The default preference is user - RequireInstaller(result, user); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("User Preference") - { - TestUserSettings settings; - settings.Set(ScopeEnum::User); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, user); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Machine Preference") - { - TestUserSettings settings; - settings.Set(ScopeEnum::Machine); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, machine); - REQUIRE(inapplicabilities.size() == 0); - } -} - -TEST_CASE("ManifestComparator_LocaleComparator_Installed_WithUnknown", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller unknown = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - ManifestInstaller enGB = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "en-GB"); - - SECTION("Nothing Installed en-US preference") - { - TestUserSettings settings; - settings.Set({ "en-US" }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // Only because it is first - RequireInstaller(result, enGB); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("en-US Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, enGB); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("zh-CN Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, unknown); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); - } -} - -TEST_CASE("ManifestComparator_LocaleComparator_Installed", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller frFR = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "fr-FR"); - ManifestInstaller enGB = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "en-GB"); - - SECTION("Nothing Installed en-US preference") - { - TestUserSettings settings; - settings.Set({ "en-US" }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - // Only because it is first - RequireInstaller(result, enGB); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("en-US Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, enGB); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); - } - SECTION("zh-CN Installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale, InapplicabilityFlags::InstalledLocale }); - } - SECTION("en-US installed but fr-fr as user intent") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; - metadata[PackageVersionMetadata::UserIntentLocale] = "fr-FR"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, frFR); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); // en-US inapplicable - } - SECTION("en-US installed but zh-CN as user intent") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; - metadata[PackageVersionMetadata::UserIntentLocale] = "zh-CN"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale, InapplicabilityFlags::InstalledLocale }); - } -} - -TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller unknown = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - ManifestInstaller frFR = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "fr-FR"); - ManifestInstaller enGB = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "en-GB"); - - SECTION("en-GB Required") - { - ManifestComparatorTestContext context; - context.Args.AddArg(Args::Type::Locale, "en-GB"s); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, enGB); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Locale , InapplicabilityFlags::Locale }); - } - SECTION("zh-CN Required") - { - ManifestComparatorTestContext context; - context.Args.AddArg(Args::Type::Locale, "zh-CN"s); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Locale , InapplicabilityFlags::Locale, InapplicabilityFlags::Locale }); - } - SECTION("en-US Preference") - { - TestUserSettings settings; - settings.Set({ "en-US" }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, enGB); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("zh-CN Preference") - { - TestUserSettings settings; - settings.Set({ "zh-CN" }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, unknown); - REQUIRE(inapplicabilities.size() == 0); - } -} - -TEST_CASE("ManifestComparator_AllowedArchitecture_x64_only", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller x86 = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - - ManifestComparatorTestContext context; - context.Add({ Architecture::X64 }); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); -} - -TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller x86 = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - ManifestInstaller x64 = AddInstaller(manifest, Architecture::X64, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - ManifestInstaller arm = AddInstaller(manifest, Architecture::Arm, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - ManifestInstaller arm64 = AddInstaller(manifest, Architecture::Arm64, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - - SECTION("x86 Preference") - { - ManifestComparatorTestContext context; - context.Add({ Architecture::X86, Architecture::X64 }); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, x86); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture, InapplicabilityFlags::MachineArchitecture }); - } - SECTION("Unknown") - { - ManifestComparatorTestContext context; - context.Add({ Architecture::Unknown }); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(result); - REQUIRE(result->Arch == GetApplicableArchitectures()[0]); - RequireInapplicabilityType(inapplicabilities, InapplicabilityFlags::MachineArchitecture); - } - SECTION("x86 and Unknown") - { - ManifestComparatorTestContext context; - context.Add({ Architecture::X86, Architecture::Unknown }); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, x86); - RequireInapplicabilityType(inapplicabilities, InapplicabilityFlags::MachineArchitecture); - } -} - -TEST_CASE("ManifestComparator_Architectures_WithUserIntent", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller x86 = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - ManifestInstaller x64 = AddInstaller(manifest, Architecture::X64, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - - SECTION("x86 installed") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, x86); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("x86 installed but x64 as user intent") - { - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; - metadata[PackageVersionMetadata::UserIntentArchitecture] = "x64"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, x64); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); - } - SECTION("x86 installed but x64 as user intent") - { - Manifest x86OnlyManifest; - AddInstaller(x86OnlyManifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; - metadata[PackageVersionMetadata::UserIntentArchitecture] = "x64"; - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(x86OnlyManifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); - } -} - -TEST_CASE("ManifestComparator_UnsupportedOSArchitecture", "[manifest_comparator]") -{ - auto systemArchitecture = GetSystemArchitecture(); - auto applicableArchitectures = GetApplicableArchitectures(); - - // Try to find an applicable architecture that is not the system architecture - auto itr = std::find_if(applicableArchitectures.begin(), applicableArchitectures.end(), [&](const auto& arch) { return arch != systemArchitecture; }); - if (itr == applicableArchitectures.end()) - { - // This test requires having an applicable architecture different from the system one. - // TODO: Does this actually happen in any arch we use? - return; - } - - auto applicableArchitecture = *itr; - - Manifest manifest; - - SECTION("System is unsupported") - { - ManifestInstaller installer = AddInstaller(manifest, applicableArchitecture, InstallerTypeEnum::Msi, {}, {}, {}, { systemArchitecture }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); - } - SECTION("Other is unsupported") - { - ManifestInstaller installer = AddInstaller(manifest, applicableArchitecture, InstallerTypeEnum::Msi, {}, {}, {}, { applicableArchitecture }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, installer); - REQUIRE(inapplicabilities.empty()); - } -} - -TEST_CASE("ManifestComparator_Inapplicabilities", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller installer = AddInstaller(manifest, Architecture::Arm64, InstallerTypeEnum::Exe, ScopeEnum::User, "10.0.99999.0", "es-MX"); - - ManifestComparatorTestContext context; - context.Add({ Architecture::X86 }); - context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::Machine)); - context.Args.AddArg(Args::Type::Locale, "en-GB"s); - - IPackageVersion::Metadata metadata; - metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); - - ManifestComparator mc(GetManifestComparatorOptions(context, metadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities( - inapplicabilities, - { InapplicabilityFlags::OSVersion | InapplicabilityFlags::InstalledType | InapplicabilityFlags::Locale | InapplicabilityFlags::Scope | InapplicabilityFlags::MachineArchitecture }); -} - -TEST_CASE("ManifestComparator_MarketFilter", "[manifest_comparator]") -{ - Manifest manifest; - - // Get current market. - winrt::Windows::Globalization::GeographicRegion region; - Manifest::string_t currentMarket{ region.CodeTwoLetter() }; - - SECTION("Applicable") - { - MarketsInfo markets; - SECTION("Allowed") - { - markets.AllowedMarkets = { currentMarket }; - } - SECTION("Not excluded") - { - markets.ExcludedMarkets = { "XX" }; - } - - ManifestInstaller installer = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Exe, {}, {}, {}, {}, markets); - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, installer); - REQUIRE(inapplicabilities.empty()); - } - - SECTION("Inapplicable") - { - MarketsInfo markets; - SECTION("Excluded") - { - markets.ExcludedMarkets = { currentMarket }; - } - SECTION("Not allowed") - { - markets.AllowedMarkets = { "XX" }; - } - - ManifestInstaller installer = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Exe, {}, {}, {}, {}, markets); - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Market}); - } -} - -TEST_CASE("ManifestComparator_Scope_AllowUnknown", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller expected = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown); - - ManifestComparatorTestContext testContext; - testContext.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::User)); - - SECTION("Default") - { - ManifestComparator mc(GetManifestComparatorOptions(testContext, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); - } - SECTION("Allow Unknown") - { - testContext.Add(true); - - ManifestComparator mc(GetManifestComparatorOptions(testContext, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, expected); - REQUIRE(inapplicabilities.size() == 0); - } -} - -TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller msi = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); - ManifestInstaller exe = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::User); - ManifestInstaller msix = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msix, ScopeEnum::User); - - SECTION("Msi arg requirement") - { - ManifestComparatorTestContext context; - context.Args.AddArg(Args::Type::InstallerType, "msi"s); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msi); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); - } - SECTION("Msix arg requirement") - { - ManifestComparatorTestContext context; - context.Args.AddArg(Args::Type::InstallerType, "msix"s); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msix); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); - } - SECTION("Portable arg requirement") - { - ManifestComparatorTestContext context; - context.Args.AddArg(Args::Type::InstallerType, "portable"s); - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); - } - SECTION("Exe preference") - { - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Exe }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, exe); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Preference does not exist") - { - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Portable }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msi); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Multiple preferences") - { - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Exe, InstallerTypeEnum::Msix }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, exe); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Multiple preferences alternate order") - { - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Msix, InstallerTypeEnum::Exe }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msix); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("Exe requirement") - { - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Exe }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, exe); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); - } - SECTION("Multiple requirements - first requirement wins") - { - // Requirements act as both a filter and an ordering guide; Exe is listed first so it wins. - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Exe, InstallerTypeEnum::Msix }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, exe); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType }); - } - SECTION("Multiple requirements alternate order") - { - // Same requirements as above but Msix is listed first — Msix should win. - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Msix, InstallerTypeEnum::Exe }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msix); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType }); - } - SECTION("Requirements and preferences coexist - preference ordering wins") - { - // When both are set, preference ordering takes precedence over requirement ordering. - // Preferences say Msix first; requirements say Exe first — Msix should win. - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Exe, InstallerTypeEnum::Msix }); - settings.Set({ InstallerTypeEnum::Msix, InstallerTypeEnum::Exe }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msix); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType }); - } - SECTION("Inno requirement") - { - TestUserSettings settings; - settings.Set({ InstallerTypeEnum::Inno }); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - REQUIRE(!result); - RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); - } - SECTION("No user preference - default order applies") - { - // No TestUserSettings means no user-configured preferences; the default order should be used. - // Default order: MSStore > Msix > Msi > Wix > Burn > Nullsoft > Inno > Exe > Portable - // Manifest has Msi, Exe, Msix — Msix should win. - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, msix); - REQUIRE(inapplicabilities.size() == 0); - } - SECTION("No user preference - type not in default list does not win over default-ordered types") - { - // Zip is not in the default preference list; it should not be preferred over Msix/Msi/Exe. - Manifest localManifest; - ManifestInstaller zip = AddInstaller(localManifest, Architecture::Neutral, InstallerTypeEnum::Zip, ScopeEnum::User); - ManifestInstaller localExe = AddInstaller(localManifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::User); - - ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(localManifest); - - // Exe is in the default list; Zip is not — Exe should be preferred. - RequireInstaller(result, localExe); - REQUIRE(inapplicabilities.size() == 0); - } -} - -TEST_CASE("ManifestComparator_MachineArchitecture_Strong_Scope_Weak", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller system = AddInstaller(manifest, GetSystemArchitecture(), InstallerTypeEnum::Msi, ScopeEnum::Unknown, "", ""); - ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); - - ManifestComparatorTestContext context; - - ManifestComparator mc(GetManifestComparatorOptions(context, {})); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, system); -} - -TEST_CASE("ManifestComparator_InstallerCompatibilitySet_Weaker_Than_Architecture", "[manifest_comparator]") -{ - Manifest manifest; - ManifestInstaller target = AddInstaller(manifest, GetSystemArchitecture(), InstallerTypeEnum::Wix, ScopeEnum::Unknown, "", ""); - ManifestInstaller foil = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Unknown, "", ""); - - ManifestComparatorTestContext context; - - IPackageVersion::Metadata installationMetadata; - installationMetadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(foil.EffectiveInstallerType()); - - ManifestComparator mc(GetManifestComparatorOptions(context, installationMetadata)); - auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); - - RequireInstaller(result, target); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; + +using Manifest = ::AppInstaller::Manifest::Manifest; + +struct ManifestComparatorTestContext : public NullStream, Context +{ + ManifestComparatorTestContext() : NullStream(), Context(*m_nullOut, *m_nullIn) {} +}; + +const ManifestInstaller& AddInstaller( + Manifest& manifest, + Architecture architecture, + InstallerTypeEnum installerType, + ScopeEnum scope = ScopeEnum::Unknown, + std::string minOSVersion = {}, + std::string locale = {}, + std::vector unsupportedOSArchitectures = {}, + MarketsInfo markets = {}) +{ + ManifestInstaller toAdd; + toAdd.Arch = architecture; + toAdd.BaseInstallerType = installerType; + toAdd.Scope = scope; + toAdd.MinOSVersion = minOSVersion; + toAdd.Locale = locale; + toAdd.UnsupportedOSArchitectures = unsupportedOSArchitectures; + toAdd.Markets = markets; + + manifest.Installers.emplace_back(std::move(toAdd)); + + return manifest.Installers.back(); +} + +template +void RequireVectorsEqual(const std::vector& actual, const std::vector& expected) +{ + REQUIRE(actual.size() == expected.size()); + + for (std::size_t i = 0; i < actual.size(); ++i) + { + REQUIRE(actual[i] == expected[i]); + } +} + +void RequireInstaller(const std::optional& actual, const ManifestInstaller& expected) +{ + REQUIRE(actual); + REQUIRE(actual->Arch == expected.Arch); + REQUIRE(actual->EffectiveInstallerType() == expected.EffectiveInstallerType()); + REQUIRE(actual->Scope == expected.Scope); + REQUIRE(actual->MinOSVersion == expected.MinOSVersion); + REQUIRE(actual->Locale == expected.Locale); + + RequireVectorsEqual(actual->Markets.AllowedMarkets, expected.Markets.AllowedMarkets); + RequireVectorsEqual(actual->Markets.ExcludedMarkets, expected.Markets.ExcludedMarkets); +} + +void RequireInapplicabilities(const std::vector& inapplicabilities, const std::vector& expected) +{ + RequireVectorsEqual(inapplicabilities, expected); +} + +void RequireInapplicabilityType(const std::vector& inapplicabilities, InapplicabilityFlags expected) +{ + REQUIRE(!inapplicabilities.empty()); + for (std::size_t i = 0; i < inapplicabilities.size(); ++i) + { + REQUIRE(inapplicabilities[i] == expected); + } +} + +TEST_CASE("ManifestComparator_OSFilter_Low", "[manifest_comparator]") +{ + Manifest manifest; + AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.99999.0"); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::OSVersion }); +} + +TEST_CASE("ManifestComparator_OSFilter_High", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller expected = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown, "10.0.0.0"); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, expected); + REQUIRE(inapplicabilities.size() == 0); +} + +TEST_CASE("ManifestComparator_InstalledScopeFilter_Unknown", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller unknown = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Unknown); + + SECTION("Nothing Installed") + { + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Only because it is first + RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("User Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Machine Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); + } +} + +TEST_CASE("ManifestComparator_InstalledScopeFilter", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); + ManifestInstaller machine = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Machine); + + SECTION("Nothing Installed") + { + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Only because it is first + RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("User Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::User); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, user); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledScope }); + } + SECTION("Machine Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledScope] = ScopeToString(ScopeEnum::Machine); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, machine); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledScope }); + } +} + +TEST_CASE("ManifestComparator_InstalledTypeFilter", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller msi = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi); + ManifestInstaller msix = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msix); + + SECTION("Nothing Installed") + { + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Msix is preferred over Msi by the default installer type precedence order + RequireInstaller(result, msix); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("MSI Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msi); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msi); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledType }); + } + SECTION("MSIX Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msix); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledType }); + } +} + +TEST_CASE("ManifestComparator_InstalledTypeCompare", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller burn = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Burn); + ManifestInstaller exe = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe); + + SECTION("Nothing Installed") + { + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Burn is preferred over Exe by the default installer type precedence order + RequireInstaller(result, burn); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Exe Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Exe); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, burn); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Inno Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Inno); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, burn); + REQUIRE(inapplicabilities.size() == 0); + } +} + +TEST_CASE("ManifestComparator_ScopeFilter", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); + ManifestInstaller machine = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Machine); + + SECTION("Nothing Required") + { + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Only because it is first + RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("User Required") + { + ManifestComparatorTestContext context; + context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::User)); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, user); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); + } + SECTION("Machine Required") + { + ManifestComparatorTestContext context; + context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::Machine)); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, machine); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); + } +} + +TEST_CASE("ManifestComparator_ScopeCompare", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller machine = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Machine); + ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); + + SECTION("No Preference") + { + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // The default preference is user + RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("User Preference") + { + TestUserSettings settings; + settings.Set(ScopeEnum::User); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, user); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Machine Preference") + { + TestUserSettings settings; + settings.Set(ScopeEnum::Machine); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, machine); + REQUIRE(inapplicabilities.size() == 0); + } +} + +TEST_CASE("ManifestComparator_LocaleComparator_Installed_WithUnknown", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller unknown = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + ManifestInstaller enGB = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "en-GB"); + + SECTION("Nothing Installed en-US preference") + { + TestUserSettings settings; + settings.Set({ "en-US" }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Only because it is first + RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("en-US Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("zh-CN Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, unknown); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); + } +} + +TEST_CASE("ManifestComparator_LocaleComparator_Installed", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller frFR = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "fr-FR"); + ManifestInstaller enGB = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "en-GB"); + + SECTION("Nothing Installed en-US preference") + { + TestUserSettings settings; + settings.Set({ "en-US" }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + // Only because it is first + RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("en-US Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, enGB); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); + } + SECTION("zh-CN Installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledLocale] = "zh-CN"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale, InapplicabilityFlags::InstalledLocale }); + } + SECTION("en-US installed but fr-fr as user intent") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; + metadata[PackageVersionMetadata::UserIntentLocale] = "fr-FR"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, frFR); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale }); // en-US inapplicable + } + SECTION("en-US installed but zh-CN as user intent") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledLocale] = "en-US"; + metadata[PackageVersionMetadata::UserIntentLocale] = "zh-CN"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstalledLocale, InapplicabilityFlags::InstalledLocale }); + } +} + +TEST_CASE("ManifestComparator_LocaleComparator", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller unknown = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + ManifestInstaller frFR = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "fr-FR"); + ManifestInstaller enGB = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", "en-GB"); + + SECTION("en-GB Required") + { + ManifestComparatorTestContext context; + context.Args.AddArg(Args::Type::Locale, "en-GB"s); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, enGB); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Locale , InapplicabilityFlags::Locale }); + } + SECTION("zh-CN Required") + { + ManifestComparatorTestContext context; + context.Args.AddArg(Args::Type::Locale, "zh-CN"s); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Locale , InapplicabilityFlags::Locale, InapplicabilityFlags::Locale }); + } + SECTION("en-US Preference") + { + TestUserSettings settings; + settings.Set({ "en-US" }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, enGB); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("zh-CN Preference") + { + TestUserSettings settings; + settings.Set({ "zh-CN" }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, unknown); + REQUIRE(inapplicabilities.size() == 0); + } +} + +TEST_CASE("ManifestComparator_AllowedArchitecture_x64_only", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller x86 = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + + ManifestComparatorTestContext context; + context.Add({ Architecture::X64 }); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); +} + +TEST_CASE("ManifestComparator_AllowedArchitecture", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller x86 = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + ManifestInstaller x64 = AddInstaller(manifest, Architecture::X64, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + ManifestInstaller arm = AddInstaller(manifest, Architecture::Arm, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + ManifestInstaller arm64 = AddInstaller(manifest, Architecture::Arm64, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + + SECTION("x86 Preference") + { + ManifestComparatorTestContext context; + context.Add({ Architecture::X86, Architecture::X64 }); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, x86); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture, InapplicabilityFlags::MachineArchitecture }); + } + SECTION("Unknown") + { + ManifestComparatorTestContext context; + context.Add({ Architecture::Unknown }); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(result); + REQUIRE(result->Arch == GetApplicableArchitectures()[0]); + RequireInapplicabilityType(inapplicabilities, InapplicabilityFlags::MachineArchitecture); + } + SECTION("x86 and Unknown") + { + ManifestComparatorTestContext context; + context.Add({ Architecture::X86, Architecture::Unknown }); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, x86); + RequireInapplicabilityType(inapplicabilities, InapplicabilityFlags::MachineArchitecture); + } +} + +TEST_CASE("ManifestComparator_Architectures_WithUserIntent", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller x86 = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + ManifestInstaller x64 = AddInstaller(manifest, Architecture::X64, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + + SECTION("x86 installed") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, x86); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("x86 installed but x64 as user intent") + { + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; + metadata[PackageVersionMetadata::UserIntentArchitecture] = "x64"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, x64); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); + } + SECTION("x86 installed but x64 as user intent") + { + Manifest x86OnlyManifest; + AddInstaller(x86OnlyManifest, Architecture::X86, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledArchitecture] = "x86"; + metadata[PackageVersionMetadata::UserIntentArchitecture] = "x64"; + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(x86OnlyManifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); + } +} + +TEST_CASE("ManifestComparator_UnsupportedOSArchitecture", "[manifest_comparator]") +{ + auto systemArchitecture = GetSystemArchitecture(); + auto applicableArchitectures = GetApplicableArchitectures(); + + // Try to find an applicable architecture that is not the system architecture + auto itr = std::find_if(applicableArchitectures.begin(), applicableArchitectures.end(), [&](const auto& arch) { return arch != systemArchitecture; }); + if (itr == applicableArchitectures.end()) + { + // This test requires having an applicable architecture different from the system one. + // TODO: Does this actually happen in any arch we use? + return; + } + + auto applicableArchitecture = *itr; + + Manifest manifest; + + SECTION("System is unsupported") + { + ManifestInstaller installer = AddInstaller(manifest, applicableArchitecture, InstallerTypeEnum::Msi, {}, {}, {}, { systemArchitecture }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::MachineArchitecture }); + } + SECTION("Other is unsupported") + { + ManifestInstaller installer = AddInstaller(manifest, applicableArchitecture, InstallerTypeEnum::Msi, {}, {}, {}, { applicableArchitecture }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, installer); + REQUIRE(inapplicabilities.empty()); + } +} + +TEST_CASE("ManifestComparator_Inapplicabilities", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller installer = AddInstaller(manifest, Architecture::Arm64, InstallerTypeEnum::Exe, ScopeEnum::User, "10.0.99999.0", "es-MX"); + + ManifestComparatorTestContext context; + context.Add({ Architecture::X86 }); + context.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::Machine)); + context.Args.AddArg(Args::Type::Locale, "en-GB"s); + + IPackageVersion::Metadata metadata; + metadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(InstallerTypeEnum::Msix); + + ManifestComparator mc(GetManifestComparatorOptions(context, metadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities( + inapplicabilities, + { InapplicabilityFlags::OSVersion | InapplicabilityFlags::InstalledType | InapplicabilityFlags::Locale | InapplicabilityFlags::Scope | InapplicabilityFlags::MachineArchitecture }); +} + +TEST_CASE("ManifestComparator_MarketFilter", "[manifest_comparator]") +{ + Manifest manifest; + + // Get current market. + winrt::Windows::Globalization::GeographicRegion region; + Manifest::string_t currentMarket{ region.CodeTwoLetter() }; + + SECTION("Applicable") + { + MarketsInfo markets; + SECTION("Allowed") + { + markets.AllowedMarkets = { currentMarket }; + } + SECTION("Not excluded") + { + markets.ExcludedMarkets = { "XX" }; + } + + ManifestInstaller installer = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Exe, {}, {}, {}, {}, markets); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, installer); + REQUIRE(inapplicabilities.empty()); + } + + SECTION("Inapplicable") + { + MarketsInfo markets; + SECTION("Excluded") + { + markets.ExcludedMarkets = { currentMarket }; + } + SECTION("Not allowed") + { + markets.AllowedMarkets = { "XX" }; + } + + ManifestInstaller installer = AddInstaller(manifest, Architecture::X86, InstallerTypeEnum::Exe, {}, {}, {}, {}, markets); + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Market}); + } +} + +TEST_CASE("ManifestComparator_Scope_AllowUnknown", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller expected = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::Unknown); + + ManifestComparatorTestContext testContext; + testContext.Args.AddArg(Args::Type::InstallScope, ScopeToString(ScopeEnum::User)); + + SECTION("Default") + { + ManifestComparator mc(GetManifestComparatorOptions(testContext, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::Scope }); + } + SECTION("Allow Unknown") + { + testContext.Add(true); + + ManifestComparator mc(GetManifestComparatorOptions(testContext, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, expected); + REQUIRE(inapplicabilities.size() == 0); + } +} + +TEST_CASE("ManifestComparator_InstallerType", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller msi = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User); + ManifestInstaller exe = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::User); + ManifestInstaller msix = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msix, ScopeEnum::User); + + SECTION("Msi arg requirement") + { + ManifestComparatorTestContext context; + context.Args.AddArg(Args::Type::InstallerType, "msi"s); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msi); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); + } + SECTION("Msix arg requirement") + { + ManifestComparatorTestContext context; + context.Args.AddArg(Args::Type::InstallerType, "msix"s); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msix); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); + } + SECTION("Portable arg requirement") + { + ManifestComparatorTestContext context; + context.Args.AddArg(Args::Type::InstallerType, "portable"s); + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); + } + SECTION("Exe preference") + { + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Exe }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, exe); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Preference does not exist") + { + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Portable }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msi); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Multiple preferences") + { + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Exe, InstallerTypeEnum::Msix }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, exe); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Multiple preferences alternate order") + { + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Msix, InstallerTypeEnum::Exe }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msix); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("Exe requirement") + { + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Exe }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, exe); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); + } + SECTION("Multiple requirements - first requirement wins") + { + // Requirements act as both a filter and an ordering guide; Exe is listed first so it wins. + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Exe, InstallerTypeEnum::Msix }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, exe); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType }); + } + SECTION("Multiple requirements alternate order") + { + // Same requirements as above but Msix is listed first — Msix should win. + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Msix, InstallerTypeEnum::Exe }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msix); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType }); + } + SECTION("Requirements and preferences coexist - preference ordering wins") + { + // When both are set, preference ordering takes precedence over requirement ordering. + // Preferences say Msix first; requirements say Exe first — Msix should win. + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Exe, InstallerTypeEnum::Msix }); + settings.Set({ InstallerTypeEnum::Msix, InstallerTypeEnum::Exe }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msix); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType }); + } + SECTION("Inno requirement") + { + TestUserSettings settings; + settings.Set({ InstallerTypeEnum::Inno }); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + REQUIRE(!result); + RequireInapplicabilities(inapplicabilities, { InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType, InapplicabilityFlags::InstallerType }); + } + SECTION("No user preference - default order applies") + { + // No TestUserSettings means no user-configured preferences; the default order should be used. + // Default order: MSStore > Msix > Msi > Wix > Burn > Nullsoft > Inno > Exe > Portable + // Manifest has Msi, Exe, Msix — Msix should win. + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, msix); + REQUIRE(inapplicabilities.size() == 0); + } + SECTION("No user preference - type not in default list does not win over default-ordered types") + { + // Zip is not in the default preference list; it should not be preferred over Msix/Msi/Exe. + Manifest localManifest; + ManifestInstaller zip = AddInstaller(localManifest, Architecture::Neutral, InstallerTypeEnum::Zip, ScopeEnum::User); + ManifestInstaller localExe = AddInstaller(localManifest, Architecture::Neutral, InstallerTypeEnum::Exe, ScopeEnum::User); + + ManifestComparator mc(GetManifestComparatorOptions(ManifestComparatorTestContext{}, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(localManifest); + + // Exe is in the default list; Zip is not — Exe should be preferred. + RequireInstaller(result, localExe); + REQUIRE(inapplicabilities.size() == 0); + } +} + +TEST_CASE("ManifestComparator_MachineArchitecture_Strong_Scope_Weak", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller system = AddInstaller(manifest, GetSystemArchitecture(), InstallerTypeEnum::Msi, ScopeEnum::Unknown, "", ""); + ManifestInstaller user = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::User, "", ""); + + ManifestComparatorTestContext context; + + ManifestComparator mc(GetManifestComparatorOptions(context, {})); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, system); +} + +TEST_CASE("ManifestComparator_InstallerCompatibilitySet_Weaker_Than_Architecture", "[manifest_comparator]") +{ + Manifest manifest; + ManifestInstaller target = AddInstaller(manifest, GetSystemArchitecture(), InstallerTypeEnum::Wix, ScopeEnum::Unknown, "", ""); + ManifestInstaller foil = AddInstaller(manifest, Architecture::Neutral, InstallerTypeEnum::Msi, ScopeEnum::Unknown, "", ""); + + ManifestComparatorTestContext context; + + IPackageVersion::Metadata installationMetadata; + installationMetadata[PackageVersionMetadata::InstalledType] = InstallerTypeToString(foil.EffectiveInstallerType()); + + ManifestComparator mc(GetManifestComparatorOptions(context, installationMetadata)); + auto [result, inapplicabilities] = mc.GetPreferredInstaller(manifest); + + RequireInstaller(result, target); +} diff --git a/src/AppInstallerCLITests/MatchCriteriaResolver.cpp b/src/AppInstallerCLITests/MatchCriteriaResolver.cpp index 998884fd75..2854eb71b2 100644 --- a/src/AppInstallerCLITests/MatchCriteriaResolver.cpp +++ b/src/AppInstallerCLITests/MatchCriteriaResolver.cpp @@ -11,9 +11,9 @@ using namespace AppInstaller; using namespace AppInstaller::Repository; using namespace AppInstaller::Utility; using namespace TestCommon; - - -void RequireMatchCriteria(const PackageMatchFilter& expected, const PackageMatchFilter& actual) + + +void RequireMatchCriteria(const PackageMatchFilter& expected, const PackageMatchFilter& actual) { REQUIRE(expected.Field == actual.Field); REQUIRE(expected.Type == actual.Type); @@ -21,119 +21,119 @@ void RequireMatchCriteria(const PackageMatchFilter& expected, const PackageMatch } TEST_CASE("MatchCriteriaResolver_MatchType", "[MatchCriteriaResolver]") -{ +{ Manifest::Manifest manifest; - PackageMatchFilter expected{ PackageMatchField::Id, MatchType::Wildcard, "Not set by test" }; - std::string searchString = "Search"; - - SECTION("Exact") + PackageMatchFilter expected{ PackageMatchField::Id, MatchType::Wildcard, "Not set by test" }; + std::string searchString = "Search"; + + SECTION("Exact") { - manifest.Id = searchString; + manifest.Id = searchString; expected.Type = MatchType::Exact; - } - SECTION("Case Insensitive") + } + SECTION("Case Insensitive") { - manifest.Id = "search"; + manifest.Id = "search"; expected.Type = MatchType::CaseInsensitive; - } - SECTION("Starts With") + } + SECTION("Starts With") { - manifest.Id = "Search Result"; + manifest.Id = "Search Result"; expected.Type = MatchType::StartsWith; - } - SECTION("Substring") + } + SECTION("Substring") { - manifest.Id = "Contains searches within"; + manifest.Id = "Contains searches within"; expected.Type = MatchType::Substring; - } - SECTION("None") - { + } + SECTION("None") + { expected.Field = PackageMatchField::Unknown; - } - - expected.Value = manifest.Id; - - SearchRequest request; - request.Query = RequestMatch{ MatchType::Substring, searchString }; - - TestPackageVersion packageVersion(manifest); - - PackageMatchFilter actual = FindBestMatchCriteria(request, &packageVersion); + } + + expected.Value = manifest.Id; + + SearchRequest request; + request.Query = RequestMatch{ MatchType::Substring, searchString }; + + TestPackageVersion packageVersion(manifest); + + PackageMatchFilter actual = FindBestMatchCriteria(request, &packageVersion); RequireMatchCriteria(expected, actual); } TEST_CASE("MatchCriteriaResolver_MatchField", "[MatchCriteriaResolver]") -{ - Manifest::Manifest manifest; +{ + Manifest::Manifest manifest; Utility::NormalizedString searchString = "Search"; auto foldedSearchString = Utility::FoldCase(searchString); - PackageMatchFilter expected{ PackageMatchField::Unknown, MatchType::Exact, searchString }; - - SECTION("Identifier") + PackageMatchFilter expected{ PackageMatchField::Unknown, MatchType::Exact, searchString }; + + SECTION("Identifier") { - manifest.Id = searchString; + manifest.Id = searchString; expected.Field = PackageMatchField::Id; - } - SECTION("Name") + } + SECTION("Name") { manifest.DefaultLocalization.Add(searchString); expected.Field = PackageMatchField::Name; - } - SECTION("Moniker") + } + SECTION("Moniker") { manifest.Moniker = searchString; expected.Field = PackageMatchField::Moniker; - } - SECTION("Command") + } + SECTION("Command") { manifest.Installers.emplace_back().Commands.emplace_back(searchString); expected.Field = PackageMatchField::Command; - } - SECTION("Tag") + } + SECTION("Tag") { manifest.DefaultLocalization.Add({ searchString }); expected.Field = PackageMatchField::Tag; - } - SECTION("Package Family Name") + } + SECTION("Package Family Name") { manifest.Installers.emplace_back().PackageFamilyName = searchString; - expected.Field = PackageMatchField::PackageFamilyName; - // Folded by test package version - expected.Type = MatchType::CaseInsensitive; + expected.Field = PackageMatchField::PackageFamilyName; + // Folded by test package version + expected.Type = MatchType::CaseInsensitive; expected.Value = foldedSearchString; - } - SECTION("Product Code") + } + SECTION("Product Code") { manifest.Installers.emplace_back().ProductCode = searchString; - expected.Field = PackageMatchField::ProductCode; - // Folded by test package version + expected.Field = PackageMatchField::ProductCode; + // Folded by test package version expected.Type = MatchType::CaseInsensitive; expected.Value = foldedSearchString; - } - SECTION("Upgrade Code") + } + SECTION("Upgrade Code") { manifest.Installers.emplace_back().AppsAndFeaturesEntries.emplace_back().UpgradeCode = searchString; - expected.Field = PackageMatchField::UpgradeCode; - // Folded by test package version + expected.Field = PackageMatchField::UpgradeCode; + // Folded by test package version expected.Type = MatchType::CaseInsensitive; expected.Value = foldedSearchString; - } - - SearchRequest request; - request.Query = RequestMatch{ MatchType::Substring, searchString }; - - TestPackageVersion packageVersion(manifest); - - PackageMatchFilter actual = FindBestMatchCriteria(request, &packageVersion); + } + + SearchRequest request; + request.Query = RequestMatch{ MatchType::Substring, searchString }; + + TestPackageVersion packageVersion(manifest); + + PackageMatchFilter actual = FindBestMatchCriteria(request, &packageVersion); RequireMatchCriteria(expected, actual); } TEST_CASE("MatchCriteriaResolver_Complex", "[MatchCriteriaResolver]") -{ - Manifest::Manifest manifest; +{ + Manifest::Manifest manifest; Utility::NormalizedString searchString = "Search"; auto foldedSearchString = Utility::FoldCase(searchString); - PackageMatchFilter expected{ PackageMatchField::Tag, MatchType::Exact, searchString }; + PackageMatchFilter expected{ PackageMatchField::Tag, MatchType::Exact, searchString }; manifest.Id = "Identifer search substring"; manifest.DefaultLocalization.Add("Search name starts"); @@ -142,13 +142,13 @@ TEST_CASE("MatchCriteriaResolver_Complex", "[MatchCriteriaResolver]") manifest.DefaultLocalization.Add({ searchString }); manifest.Installers.emplace_back().PackageFamilyName = searchString; manifest.Installers.emplace_back().ProductCode = searchString; - manifest.Installers.emplace_back().AppsAndFeaturesEntries.emplace_back().UpgradeCode = searchString; - - SearchRequest request; - request.Query = RequestMatch{ MatchType::Substring, searchString }; - - TestPackageVersion packageVersion(manifest); - - PackageMatchFilter actual = FindBestMatchCriteria(request, &packageVersion); + manifest.Installers.emplace_back().AppsAndFeaturesEntries.emplace_back().UpgradeCode = searchString; + + SearchRequest request; + request.Query = RequestMatch{ MatchType::Substring, searchString }; + + TestPackageVersion packageVersion(manifest); + + PackageMatchFilter actual = FindBestMatchCriteria(request, &packageVersion); RequireMatchCriteria(expected, actual); } diff --git a/src/AppInstallerCLITests/MsiExecArguments.cpp b/src/AppInstallerCLITests/MsiExecArguments.cpp index 6508563122..d8b2038e29 100644 --- a/src/AppInstallerCLITests/MsiExecArguments.cpp +++ b/src/AppInstallerCLITests/MsiExecArguments.cpp @@ -1,214 +1,214 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace std::string_view_literals; -using namespace AppInstaller; - -TEST_CASE("MsiExecArgs_ParseEmpty", "[msiexec]") -{ - std::vector emptyArguments = { ""sv, " ", "\t" }; - - for (const auto argString : emptyArguments) - { - auto args = Msi::ParseMSIArguments(argString); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } -} - -TEST_CASE("MsiExecArgs_ParseUILevel", "[msiexec]") -{ - { - auto args = Msi::ParseMSIArguments("/qn"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == (INSTALLUILEVEL_NONE | INSTALLUILEVEL_UACONLY)); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/qb+"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == (INSTALLUILEVEL_BASIC | INSTALLUILEVEL_ENDDIALOG)); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/q"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == (INSTALLUILEVEL_NONE | INSTALLUILEVEL_UACONLY)); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/qr"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == (INSTALLUILEVEL_REDUCED)); - REQUIRE(args.Properties.empty()); - } - - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/qr-"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/q arg"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); -} - -TEST_CASE("MsiExecArgs_ParseLogMode", "[msiexec]") -{ - { - auto args = Msi::ParseMSIArguments("/l file.txt"sv); - REQUIRE(args.LogMode == Msi::DefaultLogMode); - REQUIRE(args.LogAttributes == 0); - REQUIRE(args.LogFile == L"file.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/le errors.txt"sv); - REQUIRE(args.LogMode == INSTALLLOGMODE_ERROR); - REQUIRE(args.LogAttributes == 0); - REQUIRE(args.LogFile == L"errors.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/l! flush.txt"sv); - REQUIRE(args.LogMode == Msi::DefaultLogMode); - REQUIRE(args.LogAttributes == INSTALLLOGATTRIBUTES_FLUSHEACHLINE); - REQUIRE(args.LogFile == L"flush.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/l\"i\" \"quoted path.txt\""sv); - REQUIRE(args.LogMode == INSTALLLOGMODE_INFO); - REQUIRE(args.LogAttributes == 0); - REQUIRE(args.LogFile == L"quoted path.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/liwpx+! log.txt"sv); - REQUIRE(args.LogMode == (INSTALLLOGMODE_INFO | INSTALLLOGMODE_WARNING | INSTALLLOGMODE_PROPERTYDUMP | INSTALLLOGMODE_EXTRADEBUG)); - REQUIRE(args.LogAttributes == (INSTALLLOGATTRIBUTES_FLUSHEACHLINE | INSTALLLOGATTRIBUTES_APPEND)); - REQUIRE(args.LogFile == L"log.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/l* all.txt"sv); - REQUIRE(args.LogMode == Msi::AllLogMode); - REQUIRE(args.LogAttributes == 0); - REQUIRE(args.LogFile == L"all.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/l* \"without closing quote.txt"sv); - REQUIRE(args.LogMode == Msi::AllLogMode); - REQUIRE(args.LogAttributes == 0); - REQUIRE(args.LogFile == L"without closing quote.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/l"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/lz log.txt"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); -} - -TEST_CASE("MsiExecArgs_ParseProperties", "[msiexec]") -{ - { - auto args = Msi::ParseMSIArguments("PROPERTY=value"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties == L" PROPERTY=value"sv); - } - - { - auto args = Msi::ParseMSIArguments("EMPTY="sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties == L" EMPTY="sv); - } - - { - auto args = Msi::ParseMSIArguments("PROPERTY=\"quoted value\""sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties == L" PROPERTY=\"quoted value\""sv); - } - - { - auto args = Msi::ParseMSIArguments("PROPERTY=\"escaped \"\" quotes\""sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties == L" PROPERTY=\"escaped \"\" quotes\""sv); - } - - { - auto args = Msi::ParseMSIArguments("PROPERTY1=value1 PROPERTY2=value2"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties == L" PROPERTY1=value1 PROPERTY2=value2"sv); - } - - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("NOSEPARATOR"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("$NOTAPROPERTY=value"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("PROPERTY=not quoted"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("PROPERTY=\"bad \"internal\" quotes\""sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - REQUIRE_THROWS_HR(Msi::ParseMSIArguments("PROPERTY=\"mismatched quote"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); -} - -TEST_CASE("MsiExecArgs_ParseMultipleOptions", "[msiexec]") -{ - { - auto args = Msi::ParseMSIArguments("/li first.txt /le second.txt"sv); - REQUIRE(args.LogMode == INSTALLLOGMODE_ERROR); - REQUIRE(args.LogAttributes == 0); - REQUIRE(args.LogFile == L"second.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("PROPERTY1=value1 /qb PROPERTY2= /lw file.txt"sv); - REQUIRE(args.LogMode == INSTALLLOGMODE_WARNING); - REQUIRE(args.LogAttributes == 0); - REQUIRE(args.LogFile == L"file.txt"sv); - REQUIRE(args.UILevel == INSTALLUILEVEL_BASIC); - REQUIRE(args.Properties == L" PROPERTY1=value1 PROPERTY2="); - } -} - -TEST_CASE("MsiExecArgs_ParseLongOptions", "[msiexec]") -{ - { - auto args = Msi::ParseMSIArguments("/quiet"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == (INSTALLUILEVEL_NONE | INSTALLUILEVEL_UACONLY)); - REQUIRE(args.Properties.empty()); - } - - { - auto args = Msi::ParseMSIArguments("/passive"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == (INSTALLUILEVEL_BASIC | INSTALLUILEVEL_PROGRESSONLY | INSTALLUILEVEL_HIDECANCEL)); - REQUIRE(args.Properties == L" REBOOTPROMPT=S"sv); - } - - { - auto args = Msi::ParseMSIArguments("/NoRestart"sv); - REQUIRE(!args.LogFile.has_value()); - REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); - REQUIRE(args.Properties == L" REBOOT=ReallySuppress"sv); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace std::string_view_literals; +using namespace AppInstaller; + +TEST_CASE("MsiExecArgs_ParseEmpty", "[msiexec]") +{ + std::vector emptyArguments = { ""sv, " ", "\t" }; + + for (const auto argString : emptyArguments) + { + auto args = Msi::ParseMSIArguments(argString); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } +} + +TEST_CASE("MsiExecArgs_ParseUILevel", "[msiexec]") +{ + { + auto args = Msi::ParseMSIArguments("/qn"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == (INSTALLUILEVEL_NONE | INSTALLUILEVEL_UACONLY)); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/qb+"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == (INSTALLUILEVEL_BASIC | INSTALLUILEVEL_ENDDIALOG)); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/q"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == (INSTALLUILEVEL_NONE | INSTALLUILEVEL_UACONLY)); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/qr"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == (INSTALLUILEVEL_REDUCED)); + REQUIRE(args.Properties.empty()); + } + + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/qr-"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/q arg"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); +} + +TEST_CASE("MsiExecArgs_ParseLogMode", "[msiexec]") +{ + { + auto args = Msi::ParseMSIArguments("/l file.txt"sv); + REQUIRE(args.LogMode == Msi::DefaultLogMode); + REQUIRE(args.LogAttributes == 0); + REQUIRE(args.LogFile == L"file.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/le errors.txt"sv); + REQUIRE(args.LogMode == INSTALLLOGMODE_ERROR); + REQUIRE(args.LogAttributes == 0); + REQUIRE(args.LogFile == L"errors.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/l! flush.txt"sv); + REQUIRE(args.LogMode == Msi::DefaultLogMode); + REQUIRE(args.LogAttributes == INSTALLLOGATTRIBUTES_FLUSHEACHLINE); + REQUIRE(args.LogFile == L"flush.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/l\"i\" \"quoted path.txt\""sv); + REQUIRE(args.LogMode == INSTALLLOGMODE_INFO); + REQUIRE(args.LogAttributes == 0); + REQUIRE(args.LogFile == L"quoted path.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/liwpx+! log.txt"sv); + REQUIRE(args.LogMode == (INSTALLLOGMODE_INFO | INSTALLLOGMODE_WARNING | INSTALLLOGMODE_PROPERTYDUMP | INSTALLLOGMODE_EXTRADEBUG)); + REQUIRE(args.LogAttributes == (INSTALLLOGATTRIBUTES_FLUSHEACHLINE | INSTALLLOGATTRIBUTES_APPEND)); + REQUIRE(args.LogFile == L"log.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/l* all.txt"sv); + REQUIRE(args.LogMode == Msi::AllLogMode); + REQUIRE(args.LogAttributes == 0); + REQUIRE(args.LogFile == L"all.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/l* \"without closing quote.txt"sv); + REQUIRE(args.LogMode == Msi::AllLogMode); + REQUIRE(args.LogAttributes == 0); + REQUIRE(args.LogFile == L"without closing quote.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/l"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("/lz log.txt"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); +} + +TEST_CASE("MsiExecArgs_ParseProperties", "[msiexec]") +{ + { + auto args = Msi::ParseMSIArguments("PROPERTY=value"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties == L" PROPERTY=value"sv); + } + + { + auto args = Msi::ParseMSIArguments("EMPTY="sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties == L" EMPTY="sv); + } + + { + auto args = Msi::ParseMSIArguments("PROPERTY=\"quoted value\""sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties == L" PROPERTY=\"quoted value\""sv); + } + + { + auto args = Msi::ParseMSIArguments("PROPERTY=\"escaped \"\" quotes\""sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties == L" PROPERTY=\"escaped \"\" quotes\""sv); + } + + { + auto args = Msi::ParseMSIArguments("PROPERTY1=value1 PROPERTY2=value2"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties == L" PROPERTY1=value1 PROPERTY2=value2"sv); + } + + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("NOSEPARATOR"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("$NOTAPROPERTY=value"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("PROPERTY=not quoted"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("PROPERTY=\"bad \"internal\" quotes\""sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + REQUIRE_THROWS_HR(Msi::ParseMSIArguments("PROPERTY=\"mismatched quote"sv), APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); +} + +TEST_CASE("MsiExecArgs_ParseMultipleOptions", "[msiexec]") +{ + { + auto args = Msi::ParseMSIArguments("/li first.txt /le second.txt"sv); + REQUIRE(args.LogMode == INSTALLLOGMODE_ERROR); + REQUIRE(args.LogAttributes == 0); + REQUIRE(args.LogFile == L"second.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("PROPERTY1=value1 /qb PROPERTY2= /lw file.txt"sv); + REQUIRE(args.LogMode == INSTALLLOGMODE_WARNING); + REQUIRE(args.LogAttributes == 0); + REQUIRE(args.LogFile == L"file.txt"sv); + REQUIRE(args.UILevel == INSTALLUILEVEL_BASIC); + REQUIRE(args.Properties == L" PROPERTY1=value1 PROPERTY2="); + } +} + +TEST_CASE("MsiExecArgs_ParseLongOptions", "[msiexec]") +{ + { + auto args = Msi::ParseMSIArguments("/quiet"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == (INSTALLUILEVEL_NONE | INSTALLUILEVEL_UACONLY)); + REQUIRE(args.Properties.empty()); + } + + { + auto args = Msi::ParseMSIArguments("/passive"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == (INSTALLUILEVEL_BASIC | INSTALLUILEVEL_PROGRESSONLY | INSTALLUILEVEL_HIDECANCEL)); + REQUIRE(args.Properties == L" REBOOTPROMPT=S"sv); + } + + { + auto args = Msi::ParseMSIArguments("/NoRestart"sv); + REQUIRE(!args.LogFile.has_value()); + REQUIRE(args.UILevel == INSTALLUILEVEL_DEFAULT); + REQUIRE(args.Properties == L" REBOOT=ReallySuppress"sv); + } } \ No newline at end of file diff --git a/src/AppInstallerCLITests/MsixInfo.cpp b/src/AppInstallerCLITests/MsixInfo.cpp index caf48b14b8..2c5b28a701 100644 --- a/src/AppInstallerCLITests/MsixInfo.cpp +++ b/src/AppInstallerCLITests/MsixInfo.cpp @@ -1,107 +1,107 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller; - -constexpr std::string_view s_MsixFile_1 = "index.1.0.0.0.msix"; -constexpr std::string_view s_MsixFile_2 = "index.2.0.0.0.msix"; -constexpr std::string_view s_MsixFileSigned_1 = "index.1.0.0.0.signed.msix"; - -TEST_CASE("MsixInfo_GetPackageFullName", "[msixinfo]") -{ - TestDataFile index(s_MsixFile_1); - Msix::MsixInfo msix(index.GetPath()); - - std::string expectedFullName = "AppInstallerCLITestsFakeIndex_1.0.0.0_neutral__125rzkzqaqjwj"; - std::string actualFullName = msix.GetPackageFullName(); - - REQUIRE(expectedFullName == actualFullName); -} - -TEST_CASE("MsixInfo_CompareToSelf", "[msixinfo]") -{ - TestDataFile index(s_MsixFile_1); - Msix::MsixInfo msix(index.GetPath()); - - REQUIRE(!msix.IsNewerThan(index.GetPath().u8string())); -} - -TEST_CASE("MsixInfo_CompareToOlder", "[msixinfo]") -{ - TestDataFile index1(s_MsixFile_1); - TestDataFile index2(s_MsixFile_2); - Msix::MsixInfo msix2(index2.GetPath()); - - REQUIRE(msix2.IsNewerThan(index1)); -} - -TEST_CASE("MsixInfo_WriteFile", "[msixinfo]") -{ - TestDataFile index(s_MsixFile_1); - Msix::MsixInfo msix(index.GetPath()); - - TempFile file{ "msixtest_file"s, ".bin"s }; - ProgressCallback callback; - - msix.WriteToFile("Public\\index.db", file, callback); - - REQUIRE(1 == std::filesystem::file_size(file)); -} - -TEST_CASE("MsixInfo_ValidateMsixTrustInfo", "[msixinfo]") -{ - if (!Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - TestDataFile notSigned{ s_MsixFile_1 }; - Msix::WriteLockedMsixFile notSignedWriteLocked{ notSigned }; - REQUIRE_FALSE(notSignedWriteLocked.ValidateTrustInfo(false)); - - TestDataFile testSigned{ s_MsixFileSigned_1 }; - Msix::WriteLockedMsixFile testSignedWriteLocked{ testSigned }; - - // Remove the cert if already trusted - bool certExistsBeforeTest = UninstallCertFromSignedPackage(testSigned); - - REQUIRE_FALSE(testSignedWriteLocked.ValidateTrustInfo(false)); - - // Add the cert to trusted - InstallCertFromSignedPackage(testSigned); - - REQUIRE(testSignedWriteLocked.ValidateTrustInfo(false)); - REQUIRE_FALSE(testSignedWriteLocked.ValidateTrustInfo(true)); - - TestCommon::TempFile microsoftSigned{ "testIndex"s, ".msix"s }; - ProgressCallback callback; - Utility::Download("https://cdn.winget.microsoft.com/cache/source2.msix", microsoftSigned.GetPath(), Utility::DownloadType::Index, callback); - - Msix::WriteLockedMsixFile microsoftSignedWriteLocked{ microsoftSigned }; - REQUIRE(microsoftSignedWriteLocked.ValidateTrustInfo(true)); - - if (!certExistsBeforeTest) - { - UninstallCertFromSignedPackage(testSigned); - } -} - -TEST_CASE("MsixInfo_GetPackageIdInfoFromFullName", "[msixinfo]") -{ - auto testPackageIdInfo = Msix::GetPackageIdInfoFromFullName("Microsoft.NET.Native.Framework.2.2_2.2.29512.0_arm64__8wekyb3d8bbwe"); - REQUIRE(testPackageIdInfo.Name == "Microsoft.NET.Native.Framework.2.2"); - REQUIRE(testPackageIdInfo.Version == Utility::UInt64Version{ "2.2.29512.0" }); - - auto testPackageIdInfo2 = Msix::GetPackageIdInfoFromFullName("Microsoft.DoesNotExist_1.2.3.4_neutral_~_8wekyb3d8bbwe"); - REQUIRE(testPackageIdInfo2.Name == "Microsoft.DoesNotExist"); - REQUIRE(testPackageIdInfo2.Version == Utility::UInt64Version{ "1.2.3.4" }); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller; + +constexpr std::string_view s_MsixFile_1 = "index.1.0.0.0.msix"; +constexpr std::string_view s_MsixFile_2 = "index.2.0.0.0.msix"; +constexpr std::string_view s_MsixFileSigned_1 = "index.1.0.0.0.signed.msix"; + +TEST_CASE("MsixInfo_GetPackageFullName", "[msixinfo]") +{ + TestDataFile index(s_MsixFile_1); + Msix::MsixInfo msix(index.GetPath()); + + std::string expectedFullName = "AppInstallerCLITestsFakeIndex_1.0.0.0_neutral__125rzkzqaqjwj"; + std::string actualFullName = msix.GetPackageFullName(); + + REQUIRE(expectedFullName == actualFullName); +} + +TEST_CASE("MsixInfo_CompareToSelf", "[msixinfo]") +{ + TestDataFile index(s_MsixFile_1); + Msix::MsixInfo msix(index.GetPath()); + + REQUIRE(!msix.IsNewerThan(index.GetPath().u8string())); +} + +TEST_CASE("MsixInfo_CompareToOlder", "[msixinfo]") +{ + TestDataFile index1(s_MsixFile_1); + TestDataFile index2(s_MsixFile_2); + Msix::MsixInfo msix2(index2.GetPath()); + + REQUIRE(msix2.IsNewerThan(index1)); +} + +TEST_CASE("MsixInfo_WriteFile", "[msixinfo]") +{ + TestDataFile index(s_MsixFile_1); + Msix::MsixInfo msix(index.GetPath()); + + TempFile file{ "msixtest_file"s, ".bin"s }; + ProgressCallback callback; + + msix.WriteToFile("Public\\index.db", file, callback); + + REQUIRE(1 == std::filesystem::file_size(file)); +} + +TEST_CASE("MsixInfo_ValidateMsixTrustInfo", "[msixinfo]") +{ + if (!Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestDataFile notSigned{ s_MsixFile_1 }; + Msix::WriteLockedMsixFile notSignedWriteLocked{ notSigned }; + REQUIRE_FALSE(notSignedWriteLocked.ValidateTrustInfo(false)); + + TestDataFile testSigned{ s_MsixFileSigned_1 }; + Msix::WriteLockedMsixFile testSignedWriteLocked{ testSigned }; + + // Remove the cert if already trusted + bool certExistsBeforeTest = UninstallCertFromSignedPackage(testSigned); + + REQUIRE_FALSE(testSignedWriteLocked.ValidateTrustInfo(false)); + + // Add the cert to trusted + InstallCertFromSignedPackage(testSigned); + + REQUIRE(testSignedWriteLocked.ValidateTrustInfo(false)); + REQUIRE_FALSE(testSignedWriteLocked.ValidateTrustInfo(true)); + + TestCommon::TempFile microsoftSigned{ "testIndex"s, ".msix"s }; + ProgressCallback callback; + Utility::Download("https://cdn.winget.microsoft.com/cache/source2.msix", microsoftSigned.GetPath(), Utility::DownloadType::Index, callback); + + Msix::WriteLockedMsixFile microsoftSignedWriteLocked{ microsoftSigned }; + REQUIRE(microsoftSignedWriteLocked.ValidateTrustInfo(true)); + + if (!certExistsBeforeTest) + { + UninstallCertFromSignedPackage(testSigned); + } +} + +TEST_CASE("MsixInfo_GetPackageIdInfoFromFullName", "[msixinfo]") +{ + auto testPackageIdInfo = Msix::GetPackageIdInfoFromFullName("Microsoft.NET.Native.Framework.2.2_2.2.29512.0_arm64__8wekyb3d8bbwe"); + REQUIRE(testPackageIdInfo.Name == "Microsoft.NET.Native.Framework.2.2"); + REQUIRE(testPackageIdInfo.Version == Utility::UInt64Version{ "2.2.29512.0" }); + + auto testPackageIdInfo2 = Msix::GetPackageIdInfoFromFullName("Microsoft.DoesNotExist_1.2.3.4_neutral_~_8wekyb3d8bbwe"); + REQUIRE(testPackageIdInfo2.Name == "Microsoft.DoesNotExist"); + REQUIRE(testPackageIdInfo2.Version == Utility::UInt64Version{ "1.2.3.4" }); +} diff --git a/src/AppInstallerCLITests/NameNormalization.cpp b/src/AppInstallerCLITests/NameNormalization.cpp index cfb145f90b..c4ab35e3fd 100644 --- a/src/AppInstallerCLITests/NameNormalization.cpp +++ b/src/AppInstallerCLITests/NameNormalization.cpp @@ -1,156 +1,156 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include - -using namespace std::string_view_literals; -using namespace AppInstaller::Utility; - - -// This skipped test case can be used to update the test file. -// It writes back to the output content location, so you must manually -// copy the file(s) back to the git managed location to update. -TEST_CASE("NameNorm_Update_Database_Initial", "[.]") -{ - std::ifstream namesStream(TestCommon::TestDataFile("InputNames.txt").GetPath()); - REQUIRE(namesStream); - std::ifstream publishersStream(TestCommon::TestDataFile("InputPublishers.txt").GetPath()); - REQUIRE(publishersStream); - std::ofstream resultsStream(TestCommon::TestDataFile("NormalizationInitialIdsUpdate.txt").GetPath(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); - REQUIRE(resultsStream); - - // Far larger than any one value; hopefully - char name[4096]{}; - char publisher[4096]{}; - - NameNormalizer normer(NormalizationVersion::Initial); - - for (;;) - { - namesStream.getline(name, ARRAYSIZE(name)); - publishersStream.getline(publisher, ARRAYSIZE(publisher)); - - if (namesStream || publishersStream) - { - REQUIRE(namesStream); - REQUIRE(publishersStream); - - INFO("Name[" << name << "], Publisher[" << publisher << "]"); - - auto normalized = normer.Normalize(name, publisher); - - std::string normalizedId = normalized.Publisher(); - normalizedId += '.'; - normalizedId += normalized.Name(); - - resultsStream << normalizedId << std::endl; - REQUIRE(resultsStream); - } - else - { - break; - } - } -} - -// If this test is failing, either changes to winget code or the ICU binaries have caused it. -// This will impact the functionality of the PreIndexedPackageSource, as it is the primary -// mechanism used to cross reference packages installed outside of winget with those in the -// source. -TEST_CASE("NameNorm_Database_Initial", "[name_norm]") -{ - std::ifstream namesStream(TestCommon::TestDataFile("InputNames.txt").GetPath()); - REQUIRE(namesStream); - std::ifstream publishersStream(TestCommon::TestDataFile("InputPublishers.txt").GetPath()); - REQUIRE(publishersStream); - std::ifstream resultsStream(TestCommon::TestDataFile("NormalizationInitialIds.txt").GetPath()); - REQUIRE(resultsStream); - - // Far larger than any one value; hopefully - char name[4096]{}; - char publisher[4096]{}; - char expectedId[4096]{}; - - NameNormalizer normer(NormalizationVersion::Initial); - - for (;;) - { - namesStream.getline(name, ARRAYSIZE(name)); - publishersStream.getline(publisher, ARRAYSIZE(publisher)); - resultsStream.getline(expectedId, ARRAYSIZE(expectedId)); - - if (namesStream || publishersStream || resultsStream) - { - REQUIRE(namesStream); - REQUIRE(publishersStream); - REQUIRE(resultsStream); - - INFO("Name[" << name << "], Publisher[" << publisher << "]"); - - auto normalized = normer.Normalize(name, publisher); - - std::string normalizedId = normalized.Publisher(); - normalizedId += '.'; - normalizedId += normalized.Name(); - - REQUIRE(expectedId == normalizedId); - } - else - { - break; - } - } -} - -TEST_CASE("NameNorm_Architecture", "[name_norm]") -{ - NameNormalizer normer(NormalizationVersion::Initial); - - REQUIRE(normer.Normalize("Name", {}).Architecture() == Architecture::Unknown); - REQUIRE(normer.Normalize("Name x86", {}).Architecture() == Architecture::X86); - REQUIRE(normer.Normalize("Name x86_64", {}).Architecture() == Architecture::X64); - REQUIRE(normer.Normalize("Name (64 bit)", {}).Architecture() == Architecture::X64); - REQUIRE(normer.Normalize("Name 32/64 bit", {}).Architecture() == Architecture::Unknown); - REQUIRE(normer.Normalize("Fox86", {}).Architecture() == Architecture::Unknown); -} - -TEST_CASE("NameNorm_Locale", "[name_norm]") -{ - NameNormalizer normer(NormalizationVersion::Initial); - - REQUIRE(normer.Normalize("Name", {}).Locale() == ""); - REQUIRE(normer.Normalize("Name en-US", {}).Locale() == "en-us"); - REQUIRE(normer.Normalize("Name (es-mx)", {}).Locale() == "es-mx"); - REQUIRE(normer.Normalize("Names-mx", {}).Locale() == ""); -} - -TEST_CASE("NameNorm_KBNumbers", "[name_norm]") -{ - NameNormalizer normer(NormalizationVersion::Initial); - - REQUIRE(normer.Normalize("Fix for (KB42)", {}).Name() == "FixforKB42"); -} - -TEST_CASE("NameNorm_Initial_PreserveWhitespace", "[name_norm]") -{ - NameNormalizer normer(NormalizationVersion::InitialPreserveWhiteSpace); - - REQUIRE(normer.NormalizeName("Some Name").Name() == "Some Name"); - REQUIRE(normer.NormalizePublisher("Some Publisher Corp") == "Some Publisher"); -} - -TEST_CASE("NameNorm_GetNormalizedName_GetNormalizedFields", "[name_norm]") -{ - NameNormalizer normer(NormalizationVersion::Initial); - - auto normalizedName = normer.NormalizeName("Name(X64)"); - REQUIRE(normalizedName.GetNormalizedName(NormalizationField::None) == "Name"); - REQUIRE(normalizedName.GetNormalizedName(NormalizationField::Architecture) == "Name(X64)"); - REQUIRE(normalizedName.GetNormalizedFields() == NormalizationField::Architecture); - - auto normalizedName2 = normer.NormalizeName("Name"); - REQUIRE(normalizedName2.GetNormalizedName(NormalizationField::None) == "Name"); - REQUIRE(normalizedName2.GetNormalizedName(NormalizationField::Architecture) == "Name"); - REQUIRE(normalizedName2.GetNormalizedFields() == NormalizationField::None); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace std::string_view_literals; +using namespace AppInstaller::Utility; + + +// This skipped test case can be used to update the test file. +// It writes back to the output content location, so you must manually +// copy the file(s) back to the git managed location to update. +TEST_CASE("NameNorm_Update_Database_Initial", "[.]") +{ + std::ifstream namesStream(TestCommon::TestDataFile("InputNames.txt").GetPath()); + REQUIRE(namesStream); + std::ifstream publishersStream(TestCommon::TestDataFile("InputPublishers.txt").GetPath()); + REQUIRE(publishersStream); + std::ofstream resultsStream(TestCommon::TestDataFile("NormalizationInitialIdsUpdate.txt").GetPath(), std::ofstream::out | std::ofstream::trunc | std::ofstream::binary); + REQUIRE(resultsStream); + + // Far larger than any one value; hopefully + char name[4096]{}; + char publisher[4096]{}; + + NameNormalizer normer(NormalizationVersion::Initial); + + for (;;) + { + namesStream.getline(name, ARRAYSIZE(name)); + publishersStream.getline(publisher, ARRAYSIZE(publisher)); + + if (namesStream || publishersStream) + { + REQUIRE(namesStream); + REQUIRE(publishersStream); + + INFO("Name[" << name << "], Publisher[" << publisher << "]"); + + auto normalized = normer.Normalize(name, publisher); + + std::string normalizedId = normalized.Publisher(); + normalizedId += '.'; + normalizedId += normalized.Name(); + + resultsStream << normalizedId << std::endl; + REQUIRE(resultsStream); + } + else + { + break; + } + } +} + +// If this test is failing, either changes to winget code or the ICU binaries have caused it. +// This will impact the functionality of the PreIndexedPackageSource, as it is the primary +// mechanism used to cross reference packages installed outside of winget with those in the +// source. +TEST_CASE("NameNorm_Database_Initial", "[name_norm]") +{ + std::ifstream namesStream(TestCommon::TestDataFile("InputNames.txt").GetPath()); + REQUIRE(namesStream); + std::ifstream publishersStream(TestCommon::TestDataFile("InputPublishers.txt").GetPath()); + REQUIRE(publishersStream); + std::ifstream resultsStream(TestCommon::TestDataFile("NormalizationInitialIds.txt").GetPath()); + REQUIRE(resultsStream); + + // Far larger than any one value; hopefully + char name[4096]{}; + char publisher[4096]{}; + char expectedId[4096]{}; + + NameNormalizer normer(NormalizationVersion::Initial); + + for (;;) + { + namesStream.getline(name, ARRAYSIZE(name)); + publishersStream.getline(publisher, ARRAYSIZE(publisher)); + resultsStream.getline(expectedId, ARRAYSIZE(expectedId)); + + if (namesStream || publishersStream || resultsStream) + { + REQUIRE(namesStream); + REQUIRE(publishersStream); + REQUIRE(resultsStream); + + INFO("Name[" << name << "], Publisher[" << publisher << "]"); + + auto normalized = normer.Normalize(name, publisher); + + std::string normalizedId = normalized.Publisher(); + normalizedId += '.'; + normalizedId += normalized.Name(); + + REQUIRE(expectedId == normalizedId); + } + else + { + break; + } + } +} + +TEST_CASE("NameNorm_Architecture", "[name_norm]") +{ + NameNormalizer normer(NormalizationVersion::Initial); + + REQUIRE(normer.Normalize("Name", {}).Architecture() == Architecture::Unknown); + REQUIRE(normer.Normalize("Name x86", {}).Architecture() == Architecture::X86); + REQUIRE(normer.Normalize("Name x86_64", {}).Architecture() == Architecture::X64); + REQUIRE(normer.Normalize("Name (64 bit)", {}).Architecture() == Architecture::X64); + REQUIRE(normer.Normalize("Name 32/64 bit", {}).Architecture() == Architecture::Unknown); + REQUIRE(normer.Normalize("Fox86", {}).Architecture() == Architecture::Unknown); +} + +TEST_CASE("NameNorm_Locale", "[name_norm]") +{ + NameNormalizer normer(NormalizationVersion::Initial); + + REQUIRE(normer.Normalize("Name", {}).Locale() == ""); + REQUIRE(normer.Normalize("Name en-US", {}).Locale() == "en-us"); + REQUIRE(normer.Normalize("Name (es-mx)", {}).Locale() == "es-mx"); + REQUIRE(normer.Normalize("Names-mx", {}).Locale() == ""); +} + +TEST_CASE("NameNorm_KBNumbers", "[name_norm]") +{ + NameNormalizer normer(NormalizationVersion::Initial); + + REQUIRE(normer.Normalize("Fix for (KB42)", {}).Name() == "FixforKB42"); +} + +TEST_CASE("NameNorm_Initial_PreserveWhitespace", "[name_norm]") +{ + NameNormalizer normer(NormalizationVersion::InitialPreserveWhiteSpace); + + REQUIRE(normer.NormalizeName("Some Name").Name() == "Some Name"); + REQUIRE(normer.NormalizePublisher("Some Publisher Corp") == "Some Publisher"); +} + +TEST_CASE("NameNorm_GetNormalizedName_GetNormalizedFields", "[name_norm]") +{ + NameNormalizer normer(NormalizationVersion::Initial); + + auto normalizedName = normer.NormalizeName("Name(X64)"); + REQUIRE(normalizedName.GetNormalizedName(NormalizationField::None) == "Name"); + REQUIRE(normalizedName.GetNormalizedName(NormalizationField::Architecture) == "Name(X64)"); + REQUIRE(normalizedName.GetNormalizedFields() == NormalizationField::Architecture); + + auto normalizedName2 = normer.NormalizeName("Name"); + REQUIRE(normalizedName2.GetNormalizedName(NormalizationField::None) == "Name"); + REQUIRE(normalizedName2.GetNormalizedName(NormalizationField::Architecture) == "Name"); + REQUIRE(normalizedName2.GetNormalizedFields() == NormalizationField::None); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/PackageCollection.cpp b/src/AppInstallerCLITests/PackageCollection.cpp index 921bc63507..7a76c82578 100644 --- a/src/AppInstallerCLITests/PackageCollection.cpp +++ b/src/AppInstallerCLITests/PackageCollection.cpp @@ -51,10 +51,10 @@ namespace } REQUIRE(node.isMember(propertyName)); - REQUIRE(node[propertyName].isString()); - if (!expectedValue.empty()) - { - REQUIRE(node[propertyName].asString() == expectedValue); + REQUIRE(node[propertyName].isString()); + if (!expectedValue.empty()) + { + REQUIRE(node[propertyName].asString() == expectedValue); } } @@ -773,6 +773,6 @@ TEST_CASE("PackageCollection_Read_BadTimeStamp", "[PackageCollection]") auto parseResult = PackagesJson::TryParseJson(json); INFO(parseResult.Errors); - REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed); + REQUIRE(parseResult.Result == PackagesJson::ParseResult::Type::SchemaValidationFailed); REQUIRE_FALSE(parseResult.Errors.empty()); -} +} diff --git a/src/AppInstallerCLITests/PackageTableSortHelper.cpp b/src/AppInstallerCLITests/PackageTableSortHelper.cpp index 3497804ddd..302ea8eaed 100644 --- a/src/AppInstallerCLITests/PackageTableSortHelper.cpp +++ b/src/AppInstallerCLITests/PackageTableSortHelper.cpp @@ -1,396 +1,396 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "Workflows/PackageTableSortHelper.h" - -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Settings; - -namespace -{ - // Use all-fields mask for tests so every field is precomputed. - constexpr SortField AllFieldsMask = - SortField::Name | SortField::Id | - SortField::Version | SortField::Source | - SortField::Available; - - SortablePackageEntry MakeEntry(std::string name, std::string id, std::string version, std::string available = {}, std::string source = {}) - { - return SortablePackageEntry{ 0, name, id, version, available, source, AllFieldsMask }; - } - - // Validates that all precomputed fields in actual match expected, entry by entry. - void ValidateSortResult( - const std::vector& actual, - const std::vector& expected) - { - REQUIRE(actual.size() == expected.size()); - for (size_t i = 0; i < actual.size(); ++i) - { - INFO("Entry index: " << i); - REQUIRE(actual[i].FoldedName == expected[i].FoldedName); - REQUIRE(actual[i].FoldedId == expected[i].FoldedId); - REQUIRE(actual[i].FoldedSource == expected[i].FoldedSource); - REQUIRE(actual[i].ParsedInstalledVersion == expected[i].ParsedInstalledVersion); - REQUIRE(actual[i].ParsedAvailableVersion == expected[i].ParsedAvailableVersion); - } - } - - // Creates a SortParameters matching production constructor logic: - // ShouldSort is true only when fields contain non-relevance values. - SortParameters MakeSortParams(std::vector fields, SortDirection direction = SortDirection::Ascending) - { - SortParameters params; - params.Fields = std::move(fields); - params.Direction = direction; - params.ShouldSort = !params.Fields.empty() && - !(params.Fields.size() == 1 && params.Fields[0] == SortField::Relevance); - return params; - } - -} - -TEST_CASE("ListSort_CompareByField_Name", "[listsort]") -{ - auto a = MakeEntry("Alpha", "a.id", "1.0"); - auto b = MakeEntry("Beta", "b.id", "1.0"); - - SECTION("Less than") - { - REQUIRE(CompareByField(a, b, SortField::Name) < 0); - } - SECTION("Greater than") - { - REQUIRE(CompareByField(b, a, SortField::Name) > 0); - } - SECTION("Equal") - { - REQUIRE(CompareByField(a, a, SortField::Name) == 0); - } - SECTION("Case-insensitive") - { - auto upper = MakeEntry("ALPHA", "a.id", "1.0"); - REQUIRE(CompareByField(a, upper, SortField::Name) == 0); - } -} - -TEST_CASE("ListSort_CompareByField_Id", "[listsort]") -{ - auto a = MakeEntry("Name", "com.alpha", "1.0"); - auto b = MakeEntry("Name", "com.beta", "1.0"); - - REQUIRE(CompareByField(a, b, SortField::Id) < 0); - REQUIRE(CompareByField(b, a, SortField::Id) > 0); - - SECTION("Case-insensitive") - { - auto upper = MakeEntry("Name", "COM.ALPHA", "1.0"); - REQUIRE(CompareByField(a, upper, SortField::Id) == 0); - } -} - -TEST_CASE("ListSort_CompareByField_Version", "[listsort]") -{ - auto v1 = MakeEntry("App", "app", "1.0.0"); - auto v2 = MakeEntry("App", "app", "2.0.0"); - auto v10 = MakeEntry("App", "app", "10.0.0"); - - SECTION("Semantic ordering") - { - REQUIRE(CompareByField(v1, v2, SortField::Version) < 0); - REQUIRE(CompareByField(v2, v1, SortField::Version) > 0); - } - SECTION("Numeric not lexicographic - 10.0 > 2.0") - { - REQUIRE(CompareByField(v10, v2, SortField::Version) > 0); - } - SECTION("Equal versions") - { - REQUIRE(CompareByField(v1, v1, SortField::Version) == 0); - } -} - -TEST_CASE("ListSort_CompareByField_Source", "[listsort]") -{ - auto a = MakeEntry("App", "app", "1.0", "", "msstore"); - auto b = MakeEntry("App", "app", "1.0", "", "winget"); - - REQUIRE(CompareByField(a, b, SortField::Source) < 0); - REQUIRE(CompareByField(b, a, SortField::Source) > 0); -} - -TEST_CASE("ListSort_CompareByField_Available", "[listsort]") -{ - auto withUpdate = MakeEntry("App", "app", "1.0", "2.0"); - auto noUpdate = MakeEntry("App", "app", "1.0", ""); - auto higherUpdate = MakeEntry("App", "app", "1.0", "3.0"); - - SECTION("Has-update before no-update in ascending") - { - REQUIRE(CompareByField(withUpdate, noUpdate, SortField::Available) < 0); - REQUIRE(CompareByField(noUpdate, withUpdate, SortField::Available) > 0); - } - SECTION("Both have updates - compare versions") - { - REQUIRE(CompareByField(withUpdate, higherUpdate, SortField::Available) < 0); - REQUIRE(CompareByField(higherUpdate, withUpdate, SortField::Available) > 0); - } - SECTION("Both empty - equal") - { - REQUIRE(CompareByField(noUpdate, noUpdate, SortField::Available) == 0); - } -} - -TEST_CASE("ListSort_SortEntries_ByName_Ascending", "[listsort]") -{ - std::vector entries = { - MakeEntry("Charlie", "c", "1.0"), - MakeEntry("Alpha", "a", "1.0"), - MakeEntry("Beta", "b", "1.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Name })); - - std::vector expected = { - MakeEntry("Alpha", "a", "1.0"), - MakeEntry("Beta", "b", "1.0"), - MakeEntry("Charlie", "c", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_ByName_Descending", "[listsort]") -{ - std::vector entries = { - MakeEntry("Alpha", "a", "1.0"), - MakeEntry("Charlie", "c", "1.0"), - MakeEntry("Beta", "b", "1.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Name }, SortDirection::Descending)); - - std::vector expected = { - MakeEntry("Charlie", "c", "1.0"), - MakeEntry("Beta", "b", "1.0"), - MakeEntry("Alpha", "a", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_ByName_CaseInsensitive", "[listsort]") -{ - std::vector entries = { - MakeEntry("charlie", "c", "1.0"), - MakeEntry("ALPHA", "a", "1.0"), - MakeEntry("Beta", "b", "1.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Name })); - - // Expected uses same casing as input — ValidateSortResult compares folded values - std::vector expected = { - MakeEntry("ALPHA", "a", "1.0"), - MakeEntry("Beta", "b", "1.0"), - MakeEntry("charlie", "c", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_ById", "[listsort]") -{ - std::vector entries = { - MakeEntry("Z App", "com.zeta", "1.0"), - MakeEntry("A App", "com.alpha", "1.0"), - MakeEntry("M App", "com.mu", "1.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Id })); - - std::vector expected = { - MakeEntry("A App", "com.alpha", "1.0"), - MakeEntry("M App", "com.mu", "1.0"), - MakeEntry("Z App", "com.zeta", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_ByVersion", "[listsort]") -{ - std::vector entries = { - MakeEntry("App C", "c", "10.0.0"), - MakeEntry("App A", "a", "2.0.0"), - MakeEntry("App B", "b", "1.0.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Version })); - - std::vector expected = { - MakeEntry("App B", "b", "1.0.0"), - MakeEntry("App A", "a", "2.0.0"), - MakeEntry("App C", "c", "10.0.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_MultiField", "[listsort]") -{ - std::vector entries = { - MakeEntry("Beta", "b.2", "2.0"), - MakeEntry("Alpha", "a.1", "1.0"), - MakeEntry("Beta", "b.1", "1.0"), - MakeEntry("Alpha", "a.2", "2.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Name, SortField::Id })); - - std::vector expected = { - MakeEntry("Alpha", "a.1", "1.0"), - MakeEntry("Alpha", "a.2", "2.0"), - MakeEntry("Beta", "b.1", "1.0"), - MakeEntry("Beta", "b.2", "2.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_Available_GroupsByPresence", "[listsort]") -{ - std::vector entries = { - MakeEntry("NoUpdate1", "n1", "1.0", ""), - MakeEntry("HasUpdate", "h1", "1.0", "2.0"), - MakeEntry("NoUpdate2", "n2", "1.0", ""), - }; - - SortEntries(entries, MakeSortParams({ SortField::Available })); - - std::vector expected = { - MakeEntry("HasUpdate", "h1", "1.0", "2.0"), - MakeEntry("NoUpdate1", "n1", "1.0", ""), - MakeEntry("NoUpdate2", "n2", "1.0", ""), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_Relevance_NoOp", "[listsort]") -{ - std::vector entries = { - MakeEntry("Charlie", "c", "1.0"), - MakeEntry("Alpha", "a", "1.0"), - MakeEntry("Beta", "b", "1.0"), - }; - - // Relevance means preserve original order — exercises CompareByField(Relevance) returning 0. - SortEntries(entries, MakeSortParams({ SortField::Relevance })); - - std::vector expected = { - MakeEntry("Charlie", "c", "1.0"), - MakeEntry("Alpha", "a", "1.0"), - MakeEntry("Beta", "b", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_EmptyFields_NoOp", "[listsort]") -{ - std::vector entries = { - MakeEntry("Charlie", "c", "1.0"), - MakeEntry("Alpha", "a", "1.0"), - }; - - // Empty sort fields means no sorting - SortEntries(entries, SortParameters{}); - - std::vector expected = { - MakeEntry("Charlie", "c", "1.0"), - MakeEntry("Alpha", "a", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_SingleElement", "[listsort]") -{ - std::vector entries = { - MakeEntry("Only", "only", "1.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Name })); - - std::vector expected = { - MakeEntry("Only", "only", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -TEST_CASE("ListSort_SortEntries_StableSort", "[listsort]") -{ - // Two entries with same Name — stable sort preserves original order - std::vector entries = { - MakeEntry("Same", "first", "1.0"), - MakeEntry("Same", "second", "1.0"), - }; - - SortEntries(entries, MakeSortParams({ SortField::Name })); - - std::vector expected = { - MakeEntry("Same", "first", "1.0"), - MakeEntry("Same", "second", "1.0"), - }; - ValidateSortResult(entries, expected); -} - -// Tests for SortBy template — validates the production sort pipeline -// that converts arbitrary types to SortablePackageEntry and reorders in place. -TEST_CASE("ListSort_SortBy_ReordersSourceItems", "[listsort]") -{ - struct Row { std::string name; std::string id; std::string ver; int extra; }; - - std::vector rows = { - { "Charlie", "c", "1.0", 100 }, - { "Alpha", "a", "1.0", 200 }, - { "Beta", "b", "1.0", 300 }, - }; - - SortBy(rows, - [](const Row& r, size_t i) { - return SortablePackageEntry(i, r.name, r.id, r.ver, "", "", AllFieldsMask); - }, - MakeSortParams({ SortField::Name })); - - // Verify all fields of each row after sort - REQUIRE(rows.size() == 3); - REQUIRE(rows[0].name == "Alpha"); - REQUIRE(rows[0].id == "a"); - REQUIRE(rows[0].ver == "1.0"); - REQUIRE(rows[0].extra == 200); - REQUIRE(rows[1].name == "Beta"); - REQUIRE(rows[1].id == "b"); - REQUIRE(rows[1].ver == "1.0"); - REQUIRE(rows[1].extra == 300); - REQUIRE(rows[2].name == "Charlie"); - REQUIRE(rows[2].id == "c"); - REQUIRE(rows[2].ver == "1.0"); - REQUIRE(rows[2].extra == 100); -} - -TEST_CASE("ListSort_SortBy_PreservesExtraFields", "[listsort]") -{ - struct Row { std::string name; std::string payload; }; - - std::vector rows = { - { "Zeta", "payload-z" }, - { "Alpha", "payload-a" }, - }; - - SortBy(rows, - [](const Row& r, size_t i) { - return SortablePackageEntry(i, r.name, "", "", "", "", AllFieldsMask); - }, - MakeSortParams({ SortField::Name })); - - // Verify all fields preserved after sort - REQUIRE(rows.size() == 2); - REQUIRE(rows[0].name == "Alpha"); - REQUIRE(rows[0].payload == "payload-a"); - REQUIRE(rows[1].name == "Zeta"); - REQUIRE(rows[1].payload == "payload-z"); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "Workflows/PackageTableSortHelper.h" + +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Settings; + +namespace +{ + // Use all-fields mask for tests so every field is precomputed. + constexpr SortField AllFieldsMask = + SortField::Name | SortField::Id | + SortField::Version | SortField::Source | + SortField::Available; + + SortablePackageEntry MakeEntry(std::string name, std::string id, std::string version, std::string available = {}, std::string source = {}) + { + return SortablePackageEntry{ 0, name, id, version, available, source, AllFieldsMask }; + } + + // Validates that all precomputed fields in actual match expected, entry by entry. + void ValidateSortResult( + const std::vector& actual, + const std::vector& expected) + { + REQUIRE(actual.size() == expected.size()); + for (size_t i = 0; i < actual.size(); ++i) + { + INFO("Entry index: " << i); + REQUIRE(actual[i].FoldedName == expected[i].FoldedName); + REQUIRE(actual[i].FoldedId == expected[i].FoldedId); + REQUIRE(actual[i].FoldedSource == expected[i].FoldedSource); + REQUIRE(actual[i].ParsedInstalledVersion == expected[i].ParsedInstalledVersion); + REQUIRE(actual[i].ParsedAvailableVersion == expected[i].ParsedAvailableVersion); + } + } + + // Creates a SortParameters matching production constructor logic: + // ShouldSort is true only when fields contain non-relevance values. + SortParameters MakeSortParams(std::vector fields, SortDirection direction = SortDirection::Ascending) + { + SortParameters params; + params.Fields = std::move(fields); + params.Direction = direction; + params.ShouldSort = !params.Fields.empty() && + !(params.Fields.size() == 1 && params.Fields[0] == SortField::Relevance); + return params; + } + +} + +TEST_CASE("ListSort_CompareByField_Name", "[listsort]") +{ + auto a = MakeEntry("Alpha", "a.id", "1.0"); + auto b = MakeEntry("Beta", "b.id", "1.0"); + + SECTION("Less than") + { + REQUIRE(CompareByField(a, b, SortField::Name) < 0); + } + SECTION("Greater than") + { + REQUIRE(CompareByField(b, a, SortField::Name) > 0); + } + SECTION("Equal") + { + REQUIRE(CompareByField(a, a, SortField::Name) == 0); + } + SECTION("Case-insensitive") + { + auto upper = MakeEntry("ALPHA", "a.id", "1.0"); + REQUIRE(CompareByField(a, upper, SortField::Name) == 0); + } +} + +TEST_CASE("ListSort_CompareByField_Id", "[listsort]") +{ + auto a = MakeEntry("Name", "com.alpha", "1.0"); + auto b = MakeEntry("Name", "com.beta", "1.0"); + + REQUIRE(CompareByField(a, b, SortField::Id) < 0); + REQUIRE(CompareByField(b, a, SortField::Id) > 0); + + SECTION("Case-insensitive") + { + auto upper = MakeEntry("Name", "COM.ALPHA", "1.0"); + REQUIRE(CompareByField(a, upper, SortField::Id) == 0); + } +} + +TEST_CASE("ListSort_CompareByField_Version", "[listsort]") +{ + auto v1 = MakeEntry("App", "app", "1.0.0"); + auto v2 = MakeEntry("App", "app", "2.0.0"); + auto v10 = MakeEntry("App", "app", "10.0.0"); + + SECTION("Semantic ordering") + { + REQUIRE(CompareByField(v1, v2, SortField::Version) < 0); + REQUIRE(CompareByField(v2, v1, SortField::Version) > 0); + } + SECTION("Numeric not lexicographic - 10.0 > 2.0") + { + REQUIRE(CompareByField(v10, v2, SortField::Version) > 0); + } + SECTION("Equal versions") + { + REQUIRE(CompareByField(v1, v1, SortField::Version) == 0); + } +} + +TEST_CASE("ListSort_CompareByField_Source", "[listsort]") +{ + auto a = MakeEntry("App", "app", "1.0", "", "msstore"); + auto b = MakeEntry("App", "app", "1.0", "", "winget"); + + REQUIRE(CompareByField(a, b, SortField::Source) < 0); + REQUIRE(CompareByField(b, a, SortField::Source) > 0); +} + +TEST_CASE("ListSort_CompareByField_Available", "[listsort]") +{ + auto withUpdate = MakeEntry("App", "app", "1.0", "2.0"); + auto noUpdate = MakeEntry("App", "app", "1.0", ""); + auto higherUpdate = MakeEntry("App", "app", "1.0", "3.0"); + + SECTION("Has-update before no-update in ascending") + { + REQUIRE(CompareByField(withUpdate, noUpdate, SortField::Available) < 0); + REQUIRE(CompareByField(noUpdate, withUpdate, SortField::Available) > 0); + } + SECTION("Both have updates - compare versions") + { + REQUIRE(CompareByField(withUpdate, higherUpdate, SortField::Available) < 0); + REQUIRE(CompareByField(higherUpdate, withUpdate, SortField::Available) > 0); + } + SECTION("Both empty - equal") + { + REQUIRE(CompareByField(noUpdate, noUpdate, SortField::Available) == 0); + } +} + +TEST_CASE("ListSort_SortEntries_ByName_Ascending", "[listsort]") +{ + std::vector entries = { + MakeEntry("Charlie", "c", "1.0"), + MakeEntry("Alpha", "a", "1.0"), + MakeEntry("Beta", "b", "1.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Name })); + + std::vector expected = { + MakeEntry("Alpha", "a", "1.0"), + MakeEntry("Beta", "b", "1.0"), + MakeEntry("Charlie", "c", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_ByName_Descending", "[listsort]") +{ + std::vector entries = { + MakeEntry("Alpha", "a", "1.0"), + MakeEntry("Charlie", "c", "1.0"), + MakeEntry("Beta", "b", "1.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Name }, SortDirection::Descending)); + + std::vector expected = { + MakeEntry("Charlie", "c", "1.0"), + MakeEntry("Beta", "b", "1.0"), + MakeEntry("Alpha", "a", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_ByName_CaseInsensitive", "[listsort]") +{ + std::vector entries = { + MakeEntry("charlie", "c", "1.0"), + MakeEntry("ALPHA", "a", "1.0"), + MakeEntry("Beta", "b", "1.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Name })); + + // Expected uses same casing as input — ValidateSortResult compares folded values + std::vector expected = { + MakeEntry("ALPHA", "a", "1.0"), + MakeEntry("Beta", "b", "1.0"), + MakeEntry("charlie", "c", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_ById", "[listsort]") +{ + std::vector entries = { + MakeEntry("Z App", "com.zeta", "1.0"), + MakeEntry("A App", "com.alpha", "1.0"), + MakeEntry("M App", "com.mu", "1.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Id })); + + std::vector expected = { + MakeEntry("A App", "com.alpha", "1.0"), + MakeEntry("M App", "com.mu", "1.0"), + MakeEntry("Z App", "com.zeta", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_ByVersion", "[listsort]") +{ + std::vector entries = { + MakeEntry("App C", "c", "10.0.0"), + MakeEntry("App A", "a", "2.0.0"), + MakeEntry("App B", "b", "1.0.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Version })); + + std::vector expected = { + MakeEntry("App B", "b", "1.0.0"), + MakeEntry("App A", "a", "2.0.0"), + MakeEntry("App C", "c", "10.0.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_MultiField", "[listsort]") +{ + std::vector entries = { + MakeEntry("Beta", "b.2", "2.0"), + MakeEntry("Alpha", "a.1", "1.0"), + MakeEntry("Beta", "b.1", "1.0"), + MakeEntry("Alpha", "a.2", "2.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Name, SortField::Id })); + + std::vector expected = { + MakeEntry("Alpha", "a.1", "1.0"), + MakeEntry("Alpha", "a.2", "2.0"), + MakeEntry("Beta", "b.1", "1.0"), + MakeEntry("Beta", "b.2", "2.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_Available_GroupsByPresence", "[listsort]") +{ + std::vector entries = { + MakeEntry("NoUpdate1", "n1", "1.0", ""), + MakeEntry("HasUpdate", "h1", "1.0", "2.0"), + MakeEntry("NoUpdate2", "n2", "1.0", ""), + }; + + SortEntries(entries, MakeSortParams({ SortField::Available })); + + std::vector expected = { + MakeEntry("HasUpdate", "h1", "1.0", "2.0"), + MakeEntry("NoUpdate1", "n1", "1.0", ""), + MakeEntry("NoUpdate2", "n2", "1.0", ""), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_Relevance_NoOp", "[listsort]") +{ + std::vector entries = { + MakeEntry("Charlie", "c", "1.0"), + MakeEntry("Alpha", "a", "1.0"), + MakeEntry("Beta", "b", "1.0"), + }; + + // Relevance means preserve original order — exercises CompareByField(Relevance) returning 0. + SortEntries(entries, MakeSortParams({ SortField::Relevance })); + + std::vector expected = { + MakeEntry("Charlie", "c", "1.0"), + MakeEntry("Alpha", "a", "1.0"), + MakeEntry("Beta", "b", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_EmptyFields_NoOp", "[listsort]") +{ + std::vector entries = { + MakeEntry("Charlie", "c", "1.0"), + MakeEntry("Alpha", "a", "1.0"), + }; + + // Empty sort fields means no sorting + SortEntries(entries, SortParameters{}); + + std::vector expected = { + MakeEntry("Charlie", "c", "1.0"), + MakeEntry("Alpha", "a", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_SingleElement", "[listsort]") +{ + std::vector entries = { + MakeEntry("Only", "only", "1.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Name })); + + std::vector expected = { + MakeEntry("Only", "only", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +TEST_CASE("ListSort_SortEntries_StableSort", "[listsort]") +{ + // Two entries with same Name — stable sort preserves original order + std::vector entries = { + MakeEntry("Same", "first", "1.0"), + MakeEntry("Same", "second", "1.0"), + }; + + SortEntries(entries, MakeSortParams({ SortField::Name })); + + std::vector expected = { + MakeEntry("Same", "first", "1.0"), + MakeEntry("Same", "second", "1.0"), + }; + ValidateSortResult(entries, expected); +} + +// Tests for SortBy template — validates the production sort pipeline +// that converts arbitrary types to SortablePackageEntry and reorders in place. +TEST_CASE("ListSort_SortBy_ReordersSourceItems", "[listsort]") +{ + struct Row { std::string name; std::string id; std::string ver; int extra; }; + + std::vector rows = { + { "Charlie", "c", "1.0", 100 }, + { "Alpha", "a", "1.0", 200 }, + { "Beta", "b", "1.0", 300 }, + }; + + SortBy(rows, + [](const Row& r, size_t i) { + return SortablePackageEntry(i, r.name, r.id, r.ver, "", "", AllFieldsMask); + }, + MakeSortParams({ SortField::Name })); + + // Verify all fields of each row after sort + REQUIRE(rows.size() == 3); + REQUIRE(rows[0].name == "Alpha"); + REQUIRE(rows[0].id == "a"); + REQUIRE(rows[0].ver == "1.0"); + REQUIRE(rows[0].extra == 200); + REQUIRE(rows[1].name == "Beta"); + REQUIRE(rows[1].id == "b"); + REQUIRE(rows[1].ver == "1.0"); + REQUIRE(rows[1].extra == 300); + REQUIRE(rows[2].name == "Charlie"); + REQUIRE(rows[2].id == "c"); + REQUIRE(rows[2].ver == "1.0"); + REQUIRE(rows[2].extra == 100); +} + +TEST_CASE("ListSort_SortBy_PreservesExtraFields", "[listsort]") +{ + struct Row { std::string name; std::string payload; }; + + std::vector rows = { + { "Zeta", "payload-z" }, + { "Alpha", "payload-a" }, + }; + + SortBy(rows, + [](const Row& r, size_t i) { + return SortablePackageEntry(i, r.name, "", "", "", "", AllFieldsMask); + }, + MakeSortParams({ SortField::Name })); + + // Verify all fields preserved after sort + REQUIRE(rows.size() == 2); + REQUIRE(rows[0].name == "Alpha"); + REQUIRE(rows[0].payload == "payload-a"); + REQUIRE(rows[1].name == "Zeta"); + REQUIRE(rows[1].payload == "payload-z"); +} diff --git a/src/AppInstallerCLITests/PackageTrackingCatalog.cpp b/src/AppInstallerCLITests/PackageTrackingCatalog.cpp index 112c9c84a7..d0a6354a1a 100644 --- a/src/AppInstallerCLITests/PackageTrackingCatalog.cpp +++ b/src/AppInstallerCLITests/PackageTrackingCatalog.cpp @@ -1,281 +1,281 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include - -using namespace std::string_literals; -using namespace TestCommon; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Utility; - -namespace -{ - static Source SimpleTestSetup(const std::string& filePath, SourceDetails& details, Manifest& manifest, std::string& relativePath) - { - SQLiteIndex index = SQLiteIndex::CreateNew(filePath, AppInstaller::SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport); - - TestDataFile testManifest("Manifest-Good.yaml"); - manifest = YamlParser::CreateFromPath(testManifest); - - relativePath = testManifest.GetPath().filename().u8string(); - - index.AddManifest(manifest, relativePath); - - details.Identifier = "*SimpleTestSetup"; - details.Name = "TestName"; - details.Type = "TestType"; - details.Arg = testManifest.GetPath().parent_path().u8string(); - details.Data = ""; - - auto result = std::make_shared(details, std::move(index)); - - PackageTrackingCatalog::RemoveForSource(result->GetIdentifier()); - - return { result }; - } - - struct TestCatalog : public PackageTrackingCatalog - { - using PackageTrackingCatalog::CreateForSource; - }; - - PackageTrackingCatalog CreatePackageTrackingCatalogForSource(const Source& source) - { - return TestCatalog::CreateForSource(source); - } -} - -TEST_CASE("TrackingCatalog_Create", "[tracking_catalog]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); -} - -TEST_CASE("TrackingCatalog_Install", "[tracking_catalog]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); - - SearchResult resultBefore = catalog.Search(request); - REQUIRE(resultBefore.Matches.size() == 0); - - catalog.RecordInstall(manifest, manifest.Installers[0], false); - - SearchResult resultAfter = catalog.Search(request); - REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); - - auto trackingVersion = resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); - REQUIRE(trackingVersion); - - auto metadata = trackingVersion->GetMetadata(); - REQUIRE(metadata.find(PackageVersionMetadata::TrackingWriteTime) != metadata.end()); -} - -TEST_CASE("TrackingCatalog_Reinstall", "[tracking_catalog]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); - - catalog.RecordInstall(manifest, manifest.Installers[0], false); - - SearchResult resultBefore = catalog.Search(request); - REQUIRE(resultBefore.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Name) == - manifest.DefaultLocalization.Get()); - - // Change name - std::string newName = "New Package Name"; - manifest.DefaultLocalization.Add(newName); - - catalog.RecordInstall(manifest, manifest.Installers[0], false); - - SearchResult resultAfter = catalog.Search(request); - REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Name) == - newName); -} - -TEST_CASE("TrackingCatalog_Upgrade", "[tracking_catalog]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); - - catalog.RecordInstall(manifest, manifest.Installers[0], false); - - SearchResult resultBefore = catalog.Search(request); - REQUIRE(resultBefore.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == - manifest.Version); - - // Change version - manifest.Version = "99.1.2.3"; - - catalog.RecordInstall(manifest, manifest.Installers[0], true); - - SearchResult resultAfter = catalog.Search(request); - REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == - manifest.Version); -} - -TEST_CASE("TrackingCatalog_Uninstall", "[tracking_catalog]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); - - catalog.RecordInstall(manifest, manifest.Installers[0], false); - - SearchResult resultBefore = catalog.Search(request); - REQUIRE(resultBefore.Matches.size() == 1); - - catalog.RecordUninstall(LocIndString{ manifest.Id }); - - SearchResult resultAfter = catalog.Search(request); - REQUIRE(resultAfter.Matches.size() == 0); -} - -TEST_CASE("TrackingCatalog_Overlapping_ARP_Range", "[tracking_catalog]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - REQUIRE(manifest.Installers.size() >= 2); - AppsAndFeaturesEntry appEntry{}; - appEntry.DisplayVersion = "1.23"; - manifest.Installers[0].AppsAndFeaturesEntries.emplace_back(appEntry); - appEntry.DisplayVersion = "1.24"; - manifest.Installers[1].AppsAndFeaturesEntries.emplace_back(appEntry); - - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); - - catalog.RecordInstall(manifest, manifest.Installers[0], false); - - SearchResult resultBefore = catalog.Search(request); - REQUIRE(resultBefore.Matches.size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == - manifest.Version); - - // Change version - manifest.Version = "99.1.2.3"; - - catalog.RecordInstall(manifest, manifest.Installers[0], true); - - SearchResult resultAfter = catalog.Search(request); - REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == - manifest.Version); -} - -TEST_CASE("TrackingCatalog_Corrupt", "[tracking_catalog]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); - - std::filesystem::path catalogFile; - - { - // Add data to initial database - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); - - SearchResult resultBefore = catalog.Search(request); - REQUIRE(resultBefore.Matches.size() == 0); - - catalog.RecordInstall(manifest, manifest.Installers[0], false); - - SearchResult resultAfter = catalog.Search(request); - REQUIRE(resultAfter.Matches.size() == 1); - REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); - - catalogFile = catalog.GetFilePath(); - } - - { - std::ofstream file{ catalogFile, std::ios_base::trunc }; - file << "Corrupted!"; - } - - { - // Open database again after "corruption" - PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); - - // Should not find anything in new database - SearchResult resultBefore = catalog.Search(request); - REQUIRE(resultBefore.Matches.size() == 0); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; + +namespace +{ + static Source SimpleTestSetup(const std::string& filePath, SourceDetails& details, Manifest& manifest, std::string& relativePath) + { + SQLiteIndex index = SQLiteIndex::CreateNew(filePath, AppInstaller::SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport); + + TestDataFile testManifest("Manifest-Good.yaml"); + manifest = YamlParser::CreateFromPath(testManifest); + + relativePath = testManifest.GetPath().filename().u8string(); + + index.AddManifest(manifest, relativePath); + + details.Identifier = "*SimpleTestSetup"; + details.Name = "TestName"; + details.Type = "TestType"; + details.Arg = testManifest.GetPath().parent_path().u8string(); + details.Data = ""; + + auto result = std::make_shared(details, std::move(index)); + + PackageTrackingCatalog::RemoveForSource(result->GetIdentifier()); + + return { result }; + } + + struct TestCatalog : public PackageTrackingCatalog + { + using PackageTrackingCatalog::CreateForSource; + }; + + PackageTrackingCatalog CreatePackageTrackingCatalogForSource(const Source& source) + { + return TestCatalog::CreateForSource(source); + } +} + +TEST_CASE("TrackingCatalog_Create", "[tracking_catalog]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); +} + +TEST_CASE("TrackingCatalog_Install", "[tracking_catalog]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); + + SearchResult resultBefore = catalog.Search(request); + REQUIRE(resultBefore.Matches.size() == 0); + + catalog.RecordInstall(manifest, manifest.Installers[0], false); + + SearchResult resultAfter = catalog.Search(request); + REQUIRE(resultAfter.Matches.size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); + + auto trackingVersion = resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); + REQUIRE(trackingVersion); + + auto metadata = trackingVersion->GetMetadata(); + REQUIRE(metadata.find(PackageVersionMetadata::TrackingWriteTime) != metadata.end()); +} + +TEST_CASE("TrackingCatalog_Reinstall", "[tracking_catalog]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); + + catalog.RecordInstall(manifest, manifest.Installers[0], false); + + SearchResult resultBefore = catalog.Search(request); + REQUIRE(resultBefore.Matches.size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Name) == + manifest.DefaultLocalization.Get()); + + // Change name + std::string newName = "New Package Name"; + manifest.DefaultLocalization.Add(newName); + + catalog.RecordInstall(manifest, manifest.Installers[0], false); + + SearchResult resultAfter = catalog.Search(request); + REQUIRE(resultAfter.Matches.size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Name) == + newName); +} + +TEST_CASE("TrackingCatalog_Upgrade", "[tracking_catalog]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); + + catalog.RecordInstall(manifest, manifest.Installers[0], false); + + SearchResult resultBefore = catalog.Search(request); + REQUIRE(resultBefore.Matches.size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == + manifest.Version); + + // Change version + manifest.Version = "99.1.2.3"; + + catalog.RecordInstall(manifest, manifest.Installers[0], true); + + SearchResult resultAfter = catalog.Search(request); + REQUIRE(resultAfter.Matches.size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == + manifest.Version); +} + +TEST_CASE("TrackingCatalog_Uninstall", "[tracking_catalog]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); + + catalog.RecordInstall(manifest, manifest.Installers[0], false); + + SearchResult resultBefore = catalog.Search(request); + REQUIRE(resultBefore.Matches.size() == 1); + + catalog.RecordUninstall(LocIndString{ manifest.Id }); + + SearchResult resultAfter = catalog.Search(request); + REQUIRE(resultAfter.Matches.size() == 0); +} + +TEST_CASE("TrackingCatalog_Overlapping_ARP_Range", "[tracking_catalog]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + REQUIRE(manifest.Installers.size() >= 2); + AppsAndFeaturesEntry appEntry{}; + appEntry.DisplayVersion = "1.23"; + manifest.Installers[0].AppsAndFeaturesEntries.emplace_back(appEntry); + appEntry.DisplayVersion = "1.24"; + manifest.Installers[1].AppsAndFeaturesEntries.emplace_back(appEntry); + + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); + + catalog.RecordInstall(manifest, manifest.Installers[0], false); + + SearchResult resultBefore = catalog.Search(request); + REQUIRE(resultBefore.Matches.size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultBefore.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == + manifest.Version); + + // Change version + manifest.Version = "99.1.2.3"; + + catalog.RecordInstall(manifest, manifest.Installers[0], true); + + SearchResult resultAfter = catalog.Search(request); + REQUIRE(resultAfter.Matches.size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable()[0]->GetLatestVersion()->GetProperty(PackageVersionProperty::Version) == + manifest.Version); +} + +TEST_CASE("TrackingCatalog_Corrupt", "[tracking_catalog]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + auto source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, manifest.Id); + + std::filesystem::path catalogFile; + + { + // Add data to initial database + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); + + SearchResult resultBefore = catalog.Search(request); + REQUIRE(resultBefore.Matches.size() == 0); + + catalog.RecordInstall(manifest, manifest.Installers[0], false); + + SearchResult resultAfter = catalog.Search(request); + REQUIRE(resultAfter.Matches.size() == 1); + REQUIRE(resultAfter.Matches[0].Package->GetAvailable().size() == 1); + + catalogFile = catalog.GetFilePath(); + } + + { + std::ofstream file{ catalogFile, std::ios_base::trunc }; + file << "Corrupted!"; + } + + { + // Open database again after "corruption" + PackageTrackingCatalog catalog = CreatePackageTrackingCatalogForSource(source); + + // Should not find anything in new database + SearchResult resultBefore = catalog.Search(request); + REQUIRE(resultBefore.Matches.size() == 0); + } +} diff --git a/src/AppInstallerCLITests/PackageVersionDataManifest.cpp b/src/AppInstallerCLITests/PackageVersionDataManifest.cpp index 27076045c7..15e6fd6d73 100644 --- a/src/AppInstallerCLITests/PackageVersionDataManifest.cpp +++ b/src/AppInstallerCLITests/PackageVersionDataManifest.cpp @@ -8,96 +8,96 @@ using namespace TestCommon; using namespace AppInstaller; using namespace AppInstaller::Manifest; using namespace AppInstaller::Utility; - -void RequireVersionDataEqual(const PackageVersionDataManifest::VersionData& first, const PackageVersionDataManifest::VersionData& second) -{ - REQUIRE(first.Version == second.Version); - REQUIRE(first.ArpMinVersion == second.ArpMinVersion); - REQUIRE(first.ArpMaxVersion == second.ArpMaxVersion); - REQUIRE(first.ManifestRelativePath == second.ManifestRelativePath); - REQUIRE(first.ManifestHash == second.ManifestHash); -} + +void RequireVersionDataEqual(const PackageVersionDataManifest::VersionData& first, const PackageVersionDataManifest::VersionData& second) +{ + REQUIRE(first.Version == second.Version); + REQUIRE(first.ArpMinVersion == second.ArpMinVersion); + REQUIRE(first.ArpMaxVersion == second.ArpMaxVersion); + REQUIRE(first.ManifestRelativePath == second.ManifestRelativePath); + REQUIRE(first.ManifestHash == second.ManifestHash); +} TEST_CASE("PackageVersionDataManifest_Empty", "[PackageVersionDataManifest]") { - PackageVersionDataManifest original; - - PackageVersionDataManifest copy; - copy.Deserialize(original.Serialize()); - + PackageVersionDataManifest original; + + PackageVersionDataManifest copy; + copy.Deserialize(original.Serialize()); + REQUIRE(original.Versions().empty()); REQUIRE(copy.Versions().empty()); } TEST_CASE("PackageVersionDataManifest_Single_Simple", "[PackageVersionDataManifest]") { - PackageVersionDataManifest original; - original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, {}, {}, "path", "hash" }); - - PackageVersionDataManifest copy; - copy.Deserialize(original.Serialize()); - + PackageVersionDataManifest original; + original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, {}, {}, "path", "hash" }); + + PackageVersionDataManifest copy; + copy.Deserialize(original.Serialize()); + REQUIRE(original.Versions().size() == 1); - REQUIRE(copy.Versions().size() == 1); - + REQUIRE(copy.Versions().size() == 1); + RequireVersionDataEqual(copy.Versions()[0], original.Versions()[0]); } TEST_CASE("PackageVersionDataManifest_Single_Complete", "[PackageVersionDataManifest]") { - PackageVersionDataManifest original; - original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, ".99", "1.01", "path", "hash"}); - - PackageVersionDataManifest copy; - copy.Deserialize(original.Serialize()); - + PackageVersionDataManifest original; + original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, ".99", "1.01", "path", "hash"}); + + PackageVersionDataManifest copy; + copy.Deserialize(original.Serialize()); + REQUIRE(original.Versions().size() == 1); - REQUIRE(copy.Versions().size() == 1); - + REQUIRE(copy.Versions().size() == 1); + RequireVersionDataEqual(copy.Versions()[0], original.Versions()[0]); } TEST_CASE("PackageVersionDataManifest_Multiple", "[PackageVersionDataManifest]") { - PackageVersionDataManifest original; - original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, ".99", "1.01", "path", "hash" }); - original.AddVersion({ VersionAndChannel{ Version{ "1.1" }, Channel{} }, "1.99", "2.01", "path2", "hash2" }); - original.AddVersion({ VersionAndChannel{ Version{ "1.2" }, Channel{} }, {}, {}, "path2", "hash2" }); - original.AddVersion({ VersionAndChannel{ Version{ "2.0" }, Channel{} }, "3.99", "15.01", "path4", "hash4" }); - - PackageVersionDataManifest copy; - copy.Deserialize(original.Serialize()); - - REQUIRE(original.Versions().size() == copy.Versions().size()); - - for (size_t i = 0; i < original.Versions().size(); ++i) - { - INFO(i); - RequireVersionDataEqual(copy.Versions()[i], original.Versions()[i]); + PackageVersionDataManifest original; + original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, ".99", "1.01", "path", "hash" }); + original.AddVersion({ VersionAndChannel{ Version{ "1.1" }, Channel{} }, "1.99", "2.01", "path2", "hash2" }); + original.AddVersion({ VersionAndChannel{ Version{ "1.2" }, Channel{} }, {}, {}, "path2", "hash2" }); + original.AddVersion({ VersionAndChannel{ Version{ "2.0" }, Channel{} }, "3.99", "15.01", "path4", "hash4" }); + + PackageVersionDataManifest copy; + copy.Deserialize(original.Serialize()); + + REQUIRE(original.Versions().size() == copy.Versions().size()); + + for (size_t i = 0; i < original.Versions().size(); ++i) + { + INFO(i); + RequireVersionDataEqual(copy.Versions()[i], original.Versions()[i]); } } TEST_CASE("PackageVersionDataManifest_CompressionRoundTrip", "[PackageVersionDataManifest]") { - PackageVersionDataManifest original; - original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, ".99", "1.01", "path", "hash" }); - original.AddVersion({ VersionAndChannel{ Version{ "1.1" }, Channel{} }, "1.99", "2.01", "path2", "hash2" }); - original.AddVersion({ VersionAndChannel{ Version{ "1.2" }, Channel{} }, {}, {}, "path2", "hash2" }); - original.AddVersion({ VersionAndChannel{ Version{ "2.0" }, Channel{} }, "3.99", "15.01", "path4", "hash4" }); - - std::string serialized = original.Serialize(); - auto compressed = PackageVersionDataManifest::CreateCompressor().Compress(serialized); - - auto decompressed = PackageVersionDataManifest::CreateDecompressor().Decompress(compressed); - - PackageVersionDataManifest copy; - copy.Deserialize(decompressed); - - REQUIRE(original.Versions().size() == copy.Versions().size()); - - for (size_t i = 0; i < original.Versions().size(); ++i) - { - INFO(i); - RequireVersionDataEqual(copy.Versions()[i], original.Versions()[i]); + PackageVersionDataManifest original; + original.AddVersion({ VersionAndChannel{ Version{ "1.0" }, Channel{} }, ".99", "1.01", "path", "hash" }); + original.AddVersion({ VersionAndChannel{ Version{ "1.1" }, Channel{} }, "1.99", "2.01", "path2", "hash2" }); + original.AddVersion({ VersionAndChannel{ Version{ "1.2" }, Channel{} }, {}, {}, "path2", "hash2" }); + original.AddVersion({ VersionAndChannel{ Version{ "2.0" }, Channel{} }, "3.99", "15.01", "path4", "hash4" }); + + std::string serialized = original.Serialize(); + auto compressed = PackageVersionDataManifest::CreateCompressor().Compress(serialized); + + auto decompressed = PackageVersionDataManifest::CreateDecompressor().Decompress(compressed); + + PackageVersionDataManifest copy; + copy.Deserialize(decompressed); + + REQUIRE(original.Versions().size() == copy.Versions().size()); + + for (size_t i = 0; i < original.Versions().size(); ++i) + { + INFO(i); + RequireVersionDataEqual(copy.Versions()[i], original.Versions()[i]); } -} +} diff --git a/src/AppInstallerCLITests/PinFlow.cpp b/src/AppInstallerCLITests/PinFlow.cpp index ade8c5b252..ecab34392d 100644 --- a/src/AppInstallerCLITests/PinFlow.cpp +++ b/src/AppInstallerCLITests/PinFlow.cpp @@ -1,201 +1,201 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "TestHooks.h" -#include -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Pinning; -using namespace AppInstaller::SQLite; - -TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); - addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - addContext.Args.AddArg(Execution::Args::Type::BlockingPin); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - SECTION("Pin is saved") - { - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Blocking); - REQUIRE(pins[0].GetGatedVersion().ToString() == ""); - REQUIRE(pins[0].GetKey().PackageId == "AppInstallerCliTest.TestExeInstaller"); - REQUIRE(pins[0].GetKey().SourceId == "*TestSource"); - - std::ostringstream pinListOutput; - TestContext listContext{ pinListOutput, std::cin }; - OverrideForCompositeInstalledSource(listContext, CreateTestSource({ TSR::TestInstaller_Exe })); - listContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - PinListCommand pinList({}); - pinList.Execute(listContext); - - INFO(pinListOutput.str()); - REQUIRE(pinListOutput.str().find("AppInstallerCliTest.TestExeInstaller")); - REQUIRE(pinListOutput.str().find("Blocking")); - } - SECTION("Remove pin") - { - std::ostringstream pinRemoveOutput; - TestContext removeContext{ pinRemoveOutput, std::cin }; - OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); - removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - PinRemoveCommand pinRemove({}); - pinRemove.Execute(removeContext); - INFO(pinRemoveOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.empty()); - } - SECTION("Reset pins") - { - std::ostringstream pinResetOutput; - TestContext resetContext{ pinResetOutput, std::cin }; - - SECTION("Without --force") - { - OverrideForCompositeInstalledSource(resetContext, CreateTestSource({ TSR::TestInstaller_Exe })); - PinResetCommand pinReset({}); - pinReset.Execute(resetContext); - INFO(pinResetOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - } - SECTION("With --force") - { - resetContext.Args.AddArg(Execution::Args::Type::Force); - - PinResetCommand pinReset({}); - pinReset.Execute(resetContext); - INFO(pinResetOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.empty()); - } - } - SECTION("Update pin") - { - std::ostringstream pinUpdateOutput; - TestContext updateContext{ pinUpdateOutput, std::cin }; - OverrideForCompositeInstalledSource(updateContext, CreateTestSource({ TSR::TestInstaller_Exe })); - updateContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - SECTION("Without --force") - { - PinAddCommand pinUpdate({}); - pinUpdate.Execute(updateContext); - INFO(pinUpdateOutput.str()); - REQUIRE_TERMINATED_WITH(updateContext, APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Blocking); - } - SECTION("With --force") - { - updateContext.Args.AddArg(Execution::Args::Type::Force); - - PinAddCommand pinUpdate({}); - pinUpdate.Execute(updateContext); - INFO(pinUpdateOutput.str()); - - auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); - auto pins = index.GetAllPins(); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0].GetType() == PinType::Pinning); - } - } -} - -TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinAddOutput; - TestContext addContext{ pinAddOutput, std::cin }; - OverrideForCompositeInstalledSource(addContext, CreateTestSource({})); - addContext.Args.AddArg(Execution::Args::Type::Query, "This package doesn't exist"sv); - - PinAddCommand pinAdd({}); - pinAdd.Execute(addContext); - INFO(pinAddOutput.str()); - - REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); -} - -TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinListOutput; - TestContext listContext{ pinListOutput, std::cin }; - OverrideForCompositeInstalledSource(listContext, CreateTestSource({})); - - PinListCommand pinList({}); - pinList.Execute(listContext); - INFO(pinListOutput.str()); - - REQUIRE(pinListOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); -} - -TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinRemoveOutput; - TestContext removeContext{ pinRemoveOutput, std::cin }; - OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); - removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - - PinRemoveCommand pinRemove({}); - pinRemove.Execute(removeContext); - INFO(pinRemoveOutput.str()); - - REQUIRE_TERMINATED_WITH(removeContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); -} - -TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") -{ - TempFile indexFile("pinningIndex", ".db"); - TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); - - std::ostringstream pinResetOutput; - TestContext resetContext{ pinResetOutput, std::cin }; - resetContext.Args.AddArg(Execution::Args::Type::Force); - - PinResetCommand pinReset({}); - pinReset.Execute(resetContext); - INFO(pinResetOutput.str()); - - REQUIRE(pinResetOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestHooks.h" +#include +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::SQLite; + +TEST_CASE("PinFlow_Add", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({ TSR::TestInstaller_Exe })); + addContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + addContext.Args.AddArg(Execution::Args::Type::BlockingPin); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + SECTION("Pin is saved") + { + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Blocking); + REQUIRE(pins[0].GetGatedVersion().ToString() == ""); + REQUIRE(pins[0].GetKey().PackageId == "AppInstallerCliTest.TestExeInstaller"); + REQUIRE(pins[0].GetKey().SourceId == "*TestSource"); + + std::ostringstream pinListOutput; + TestContext listContext{ pinListOutput, std::cin }; + OverrideForCompositeInstalledSource(listContext, CreateTestSource({ TSR::TestInstaller_Exe })); + listContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + PinListCommand pinList({}); + pinList.Execute(listContext); + + INFO(pinListOutput.str()); + REQUIRE(pinListOutput.str().find("AppInstallerCliTest.TestExeInstaller")); + REQUIRE(pinListOutput.str().find("Blocking")); + } + SECTION("Remove pin") + { + std::ostringstream pinRemoveOutput; + TestContext removeContext{ pinRemoveOutput, std::cin }; + OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); + removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + PinRemoveCommand pinRemove({}); + pinRemove.Execute(removeContext); + INFO(pinRemoveOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.empty()); + } + SECTION("Reset pins") + { + std::ostringstream pinResetOutput; + TestContext resetContext{ pinResetOutput, std::cin }; + + SECTION("Without --force") + { + OverrideForCompositeInstalledSource(resetContext, CreateTestSource({ TSR::TestInstaller_Exe })); + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + } + SECTION("With --force") + { + resetContext.Args.AddArg(Execution::Args::Type::Force); + + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.empty()); + } + } + SECTION("Update pin") + { + std::ostringstream pinUpdateOutput; + TestContext updateContext{ pinUpdateOutput, std::cin }; + OverrideForCompositeInstalledSource(updateContext, CreateTestSource({ TSR::TestInstaller_Exe })); + updateContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + SECTION("Without --force") + { + PinAddCommand pinUpdate({}); + pinUpdate.Execute(updateContext); + INFO(pinUpdateOutput.str()); + REQUIRE_TERMINATED_WITH(updateContext, APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Blocking); + } + SECTION("With --force") + { + updateContext.Args.AddArg(Execution::Args::Type::Force); + + PinAddCommand pinUpdate({}); + pinUpdate.Execute(updateContext); + INFO(pinUpdateOutput.str()); + + auto index = PinningIndex::Open(indexFile.GetPath().u8string(), SQLiteStorageBase::OpenDisposition::Read); + auto pins = index.GetAllPins(); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0].GetType() == PinType::Pinning); + } + } +} + +TEST_CASE("PinFlow_Add_NotFound", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinAddOutput; + TestContext addContext{ pinAddOutput, std::cin }; + OverrideForCompositeInstalledSource(addContext, CreateTestSource({})); + addContext.Args.AddArg(Execution::Args::Type::Query, "This package doesn't exist"sv); + + PinAddCommand pinAdd({}); + pinAdd.Execute(addContext); + INFO(pinAddOutput.str()); + + REQUIRE_TERMINATED_WITH(addContext, APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); +} + +TEST_CASE("PinFlow_ListEmpty", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinListOutput; + TestContext listContext{ pinListOutput, std::cin }; + OverrideForCompositeInstalledSource(listContext, CreateTestSource({})); + + PinListCommand pinList({}); + pinList.Execute(listContext); + INFO(pinListOutput.str()); + + REQUIRE(pinListOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); +} + +TEST_CASE("PinFlow_RemoveNonExisting", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinRemoveOutput; + TestContext removeContext{ pinRemoveOutput, std::cin }; + OverrideForCompositeInstalledSource(removeContext, CreateTestSource({ TSR::TestInstaller_Exe })); + removeContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + + PinRemoveCommand pinRemove({}); + pinRemove.Execute(removeContext); + INFO(pinRemoveOutput.str()); + + REQUIRE_TERMINATED_WITH(removeContext, APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST); +} + +TEST_CASE("PinFlow_ResetEmpty", "[PinFlow][workflow]") +{ + TempFile indexFile("pinningIndex", ".db"); + TestHook::SetPinningIndex_Override pinningIndexOverride(indexFile.GetPath()); + + std::ostringstream pinResetOutput; + TestContext resetContext{ pinResetOutput, std::cin }; + resetContext.Args.AddArg(Execution::Args::Type::Force); + + PinResetCommand pinReset({}); + pinReset.Execute(resetContext); + INFO(pinResetOutput.str()); + + REQUIRE(pinResetOutput.str().find(Resource::LocString(Resource::String::PinNoPinsExist)) != std::string::npos); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/PinningIndex.cpp b/src/AppInstallerCLITests/PinningIndex.cpp index 0682dc31f6..a2930cb453 100644 --- a/src/AppInstallerCLITests/PinningIndex.cpp +++ b/src/AppInstallerCLITests/PinningIndex.cpp @@ -1,166 +1,166 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace TestCommon; -using namespace AppInstaller::Pinning; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Repository::Microsoft::Schema; - -TEST_CASE("PinningIndexCreateLatestAndReopen", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Version versionCreated; - - // Create the index - { - PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); - versionCreated = index.GetVersion(); - } - - // Reopen the index for read only - { - INFO("Trying with Read"); - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for read/write - { - INFO("Trying with ReadWrite"); - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for immutable read - { - INFO("Trying with Immutable"); - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } -} - -TEST_CASE("PinningIndexAddEntryToTable", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); - - { - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - auto pins = Pinning_V1_0::PinTable::GetAllPins(connection); - REQUIRE(pins.size() == 1); - REQUIRE(pins[0] == pin); - - auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); - REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex.value() == pin); - - REQUIRE(pinFromIndex->GetType() == pin.GetType()); - REQUIRE(pinFromIndex->GetKey() == pin.GetKey()); - } - - { - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.RemovePin(pin.GetKey()); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); - REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); - } -} - -TEST_CASE("PinningIndex_AddUpdateRemove", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); - Pin updatedPin = Pin::CreatePinningPin({ "pkgId", "srcId" }); - - { - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin); - REQUIRE(index.UpdatePin(updatedPin)); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); - REQUIRE(pinFromIndex.has_value()); - REQUIRE(pinFromIndex.value() == updatedPin); - } - - { - PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.RemovePin(updatedPin.GetKey()); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); - REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); - } -} - -TEST_CASE("PinningIndex_ResetAll", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); - Pin pin2 = Pin::CreatePinningPin({ "pkg2", "src2" }); - - // Add two pins to the index, then check that they show up when queried - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin1); - index.AddPin(pin2); - - REQUIRE(index.GetAllPins().size() == 2); - REQUIRE(index.GetPin(pin1.GetKey()).has_value()); - REQUIRE(index.GetPin(pin2.GetKey()).has_value()); - REQUIRE(!index.GetPin({ "pkg", "src" }).has_value()); - - // Reset the index, then check that there are no pins - index.ResetAllPins(); - REQUIRE(index.GetAllPins().empty()); - REQUIRE(!index.GetPin(pin1.GetKey()).has_value()); - REQUIRE(!index.GetPin(pin2.GetKey()).has_value()); -} - -TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.*"sv }); - - PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPin(pin); - - REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::Pinning; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Repository::Microsoft::Schema; + +TEST_CASE("PinningIndexCreateLatestAndReopen", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Version versionCreated; + + // Create the index + { + PinningIndex index = PinningIndex::CreateNew(tempFile, Version::Latest()); + versionCreated = index.GetVersion(); + } + + // Reopen the index for read only + { + INFO("Trying with Read"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for read/write + { + INFO("Trying with ReadWrite"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for immutable read + { + INFO("Trying with Immutable"); + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } +} + +TEST_CASE("PinningIndexAddEntryToTable", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateBlockingPin({ "pkgId", "sourceId" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + auto pins = Pinning_V1_0::PinTable::GetAllPins(connection); + REQUIRE(pins.size() == 1); + REQUIRE(pins[0] == pin); + + auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex.value() == pin); + + REQUIRE(pinFromIndex->GetType() == pin.GetType()); + REQUIRE(pinFromIndex->GetKey() == pin.GetKey()); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(pin.GetKey()); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_AddUpdateRemove", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateGatingPin({ "pkgId", "srcId" }, { "1.0.*"sv }); + Pin updatedPin = Pin::CreatePinningPin({ "pkgId", "srcId" }); + + { + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + REQUIRE(index.UpdatePin(updatedPin)); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + auto pinFromIndex = Pinning_V1_0::PinTable::GetPinById(connection, 1); + REQUIRE(pinFromIndex.has_value()); + REQUIRE(pinFromIndex.value() == updatedPin); + } + + { + PinningIndex index = PinningIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePin(updatedPin.GetKey()); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Pinning_V1_0::PinTable::GetAllPins(connection).empty()); + REQUIRE(!Pinning_V1_0::PinTable::GetPinById(connection, 1)); + } +} + +TEST_CASE("PinningIndex_ResetAll", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin1 = Pin::CreateBlockingPin({ "pkg1", "src1" }); + Pin pin2 = Pin::CreatePinningPin({ "pkg2", "src2" }); + + // Add two pins to the index, then check that they show up when queried + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin1); + index.AddPin(pin2); + + REQUIRE(index.GetAllPins().size() == 2); + REQUIRE(index.GetPin(pin1.GetKey()).has_value()); + REQUIRE(index.GetPin(pin2.GetKey()).has_value()); + REQUIRE(!index.GetPin({ "pkg", "src" }).has_value()); + + // Reset the index, then check that there are no pins + index.ResetAllPins(); + REQUIRE(index.GetAllPins().empty()); + REQUIRE(!index.GetPin(pin1.GetKey()).has_value()); + REQUIRE(!index.GetPin(pin2.GetKey()).has_value()); +} + +TEST_CASE("PinningIndex_AddDuplicatePin", "[pinningIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Pin pin = Pin::CreateGatingPin({ "pkg", "src" }, { "1.*"sv }); + + PinningIndex index = PinningIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPin(pin); + + REQUIRE_THROWS(index.AddPin(pin), ERROR_ALREADY_EXISTS); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/PortableIndex.cpp b/src/AppInstallerCLITests/PortableIndex.cpp index e12e71ea02..41ea8c02bb 100644 --- a/src/AppInstallerCLITests/PortableIndex.cpp +++ b/src/AppInstallerCLITests/PortableIndex.cpp @@ -1,202 +1,202 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace TestCommon; -using namespace AppInstaller::Portable; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Repository::Microsoft::Schema; - -void CreateFakePortableFile(PortableFileEntry& file) -{ - file.SetFilePath("testPortableFile.exe"); - file.FileType = PortableFileType::File; - file.SHA256 = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; - file.SymlinkTarget = "testSymlinkTarget.exe"; -} - -TEST_CASE("PortableIndexCreateLatestAndReopen", "[portableIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Version versionCreated; - - // Create the index - { - PortableIndex index = PortableIndex::CreateNew(tempFile, Version::Latest()); - versionCreated = index.GetVersion(); - } - - // Reopen the index for read only - { - INFO("Trying with Read"); - PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for read/write - { - INFO("Trying with ReadWrite"); - PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for immutable read - { - INFO("Trying with Immutable"); - PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); - Version versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } -} - -TEST_CASE("PortableIndexAddEntryToTable", "[portableIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - PortableFileEntry portableFile; - CreateFakePortableFile(portableFile); - - { - PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPortableFile(portableFile); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(!Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); - } - - { - PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.RemovePortableFile(portableFile); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); - } -} - -TEST_CASE("PortableIndex_AddUpdateRemove", "[portableIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - PortableFileEntry portableFile; - CreateFakePortableFile(portableFile); - - PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPortableFile(portableFile); - - // Apply changes to portable file - std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b"; - portableFile.FileType = PortableFileType::Symlink; - portableFile.SHA256 = updatedHash; - portableFile.SymlinkTarget = "fakeSymlinkTarget.exe"; - - REQUIRE(index.UpdatePortableFile(portableFile)); - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1); - REQUIRE(fileFromIndex.has_value()); - REQUIRE(fileFromIndex->GetFilePath() == portableFile.GetFilePath()); - REQUIRE(fileFromIndex->FileType == PortableFileType::Symlink); - REQUIRE(fileFromIndex->SHA256 == updatedHash); - REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe"); - } - - { - PortableIndex index2 = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index2.RemovePortableFile(portableFile); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); - } -} - -TEST_CASE("PortableIndex_UpdateFile_CaseInsensitive", "[portableIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - PortableFileEntry portableFile; - CreateFakePortableFile(portableFile); - - PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPortableFile(portableFile); - - // By default, portable file path is set to "testPortableFile.exe" - // Change file path to all upper case should still successfully update. - portableFile.SetFilePath("TESTPORTABLEFILE.exe"); - std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b"; - portableFile.FileType = PortableFileType::Symlink; - portableFile.SHA256 = updatedHash; - portableFile.SymlinkTarget = "fakeSymlinkTarget.exe"; - - REQUIRE(index.UpdatePortableFile(portableFile)); - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1); - REQUIRE(fileFromIndex.has_value()); - REQUIRE(fileFromIndex->GetFilePath() == portableFile.GetFilePath()); - REQUIRE(fileFromIndex->FileType == PortableFileType::Symlink); - REQUIRE(fileFromIndex->SHA256 == updatedHash); - REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe"); - } -} - -TEST_CASE("PortableIndex_AddDuplicateFile", "[portableIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - PortableFileEntry portableFile; - CreateFakePortableFile(portableFile); - - PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPortableFile(portableFile); - - // Change file path to all upper case. Adding duplicate file should fail. - portableFile.SetFilePath("TESTPORTABLEFILE.exe"); - REQUIRE_THROWS(index.AddPortableFile(portableFile), ERROR_ALREADY_EXISTS); -} - -TEST_CASE("PortableIndex_RemoveWithId", "[portableIndex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - PortableFileEntry portableFile; - CreateFakePortableFile(portableFile); - - PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); - index.AddPortableFile(portableFile); - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - REQUIRE(Portable_V1_0::PortableTable::ExistsById(connection, 1)); - Portable_V1_0::PortableTable::DeleteById(connection, 1); - REQUIRE_FALSE(Portable_V1_0::PortableTable::ExistsById(connection, 1)); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::Portable; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Repository::Microsoft::Schema; + +void CreateFakePortableFile(PortableFileEntry& file) +{ + file.SetFilePath("testPortableFile.exe"); + file.FileType = PortableFileType::File; + file.SHA256 = "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"; + file.SymlinkTarget = "testSymlinkTarget.exe"; +} + +TEST_CASE("PortableIndexCreateLatestAndReopen", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Version versionCreated; + + // Create the index + { + PortableIndex index = PortableIndex::CreateNew(tempFile, Version::Latest()); + versionCreated = index.GetVersion(); + } + + // Reopen the index for read only + { + INFO("Trying with Read"); + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for read/write + { + INFO("Trying with ReadWrite"); + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for immutable read + { + INFO("Trying with Immutable"); + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); + Version versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } +} + +TEST_CASE("PortableIndexAddEntryToTable", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + PortableFileEntry portableFile; + CreateFakePortableFile(portableFile); + + { + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(!Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); + } + + { + PortableIndex index = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.RemovePortableFile(portableFile); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); + } +} + +TEST_CASE("PortableIndex_AddUpdateRemove", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + PortableFileEntry portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + // Apply changes to portable file + std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b"; + portableFile.FileType = PortableFileType::Symlink; + portableFile.SHA256 = updatedHash; + portableFile.SymlinkTarget = "fakeSymlinkTarget.exe"; + + REQUIRE(index.UpdatePortableFile(portableFile)); + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1); + REQUIRE(fileFromIndex.has_value()); + REQUIRE(fileFromIndex->GetFilePath() == portableFile.GetFilePath()); + REQUIRE(fileFromIndex->FileType == PortableFileType::Symlink); + REQUIRE(fileFromIndex->SHA256 == updatedHash); + REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe"); + } + + { + PortableIndex index2 = PortableIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index2.RemovePortableFile(portableFile); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Schema::Portable_V1_0::PortableTable::IsEmpty(connection)); + } +} + +TEST_CASE("PortableIndex_UpdateFile_CaseInsensitive", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + PortableFileEntry portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + // By default, portable file path is set to "testPortableFile.exe" + // Change file path to all upper case should still successfully update. + portableFile.SetFilePath("TESTPORTABLEFILE.exe"); + std::string updatedHash = "2db8ae7657c6622b04700137740002c51c36588e566651c9f67b4b096c8ad18b"; + portableFile.FileType = PortableFileType::Symlink; + portableFile.SHA256 = updatedHash; + portableFile.SymlinkTarget = "fakeSymlinkTarget.exe"; + + REQUIRE(index.UpdatePortableFile(portableFile)); + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + auto fileFromIndex = Schema::Portable_V1_0::PortableTable::GetPortableFileById(connection, 1); + REQUIRE(fileFromIndex.has_value()); + REQUIRE(fileFromIndex->GetFilePath() == portableFile.GetFilePath()); + REQUIRE(fileFromIndex->FileType == PortableFileType::Symlink); + REQUIRE(fileFromIndex->SHA256 == updatedHash); + REQUIRE(fileFromIndex->SymlinkTarget == "fakeSymlinkTarget.exe"); + } +} + +TEST_CASE("PortableIndex_AddDuplicateFile", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + PortableFileEntry portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + // Change file path to all upper case. Adding duplicate file should fail. + portableFile.SetFilePath("TESTPORTABLEFILE.exe"); + REQUIRE_THROWS(index.AddPortableFile(portableFile), ERROR_ALREADY_EXISTS); +} + +TEST_CASE("PortableIndex_RemoveWithId", "[portableIndex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + PortableFileEntry portableFile; + CreateFakePortableFile(portableFile); + + PortableIndex index = PortableIndex::CreateNew(tempFile, { 1, 0 }); + index.AddPortableFile(portableFile); + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + REQUIRE(Portable_V1_0::PortableTable::ExistsById(connection, 1)); + Portable_V1_0::PortableTable::DeleteById(connection, 1); + REQUIRE_FALSE(Portable_V1_0::PortableTable::ExistsById(connection, 1)); + } } \ No newline at end of file diff --git a/src/AppInstallerCLITests/PortableInstaller.cpp b/src/AppInstallerCLITests/PortableInstaller.cpp index c3674c3e7d..e5259e7651 100644 --- a/src/AppInstallerCLITests/PortableInstaller.cpp +++ b/src/AppInstallerCLITests/PortableInstaller.cpp @@ -1,215 +1,215 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace AppInstaller::CLI::Portable; -using namespace AppInstaller::Filesystem; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Registry::Environment; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Repository::Microsoft::Schema; -using namespace AppInstaller::Utility; -using namespace AppInstaller::CLI::Workflow; -using namespace TestCommon; - -TEST_CASE("PortableInstaller_InstallToRegistry", "[PortableInstaller]") -{ - TempDirectory tempDirectory = TestCommon::TempDirectory("TempDirectory", false); - - std::vector desiredTestState; - - TestCommon::TempFile testPortable("testPortable.txt"); - std::ofstream file(testPortable, std::ofstream::out); - file.close(); - - std::filesystem::path targetPath = tempDirectory.GetPath() / "testPortable.txt"; - std::filesystem::path symlinkPath = tempDirectory.GetPath() / "testSymlink.exe"; - - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); - - PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - portableInstaller.TargetInstallLocation = tempDirectory.GetPath(); - portableInstaller.SetDesiredState(desiredTestState); - REQUIRE(portableInstaller.VerifyExpectedState()); - - portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); - - auto entry = portableInstaller.GetAppsAndFeaturesEntry(); - REQUIRE(entry.ProductCode == portableInstaller.GetProductCode()); - - PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - REQUIRE(portableInstaller2.ARPEntryExists()); - REQUIRE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); - REQUIRE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); - - portableInstaller2.Uninstall(); - REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); - REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); - REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.InstallLocation)); -} - -TEST_CASE("PortableInstaller_InstallToIndex_CreateInstallRoot", "[PortableInstaller]") -{ - TempDirectory installRootDirectory = TestCommon::TempDirectory("PortableInstallRoot", false); - - std::vector desiredTestState; - - TestCommon::TempFile testPortable("testPortable.txt"); - std::ofstream file1(testPortable, std::ofstream::out); - file1.close(); - - TestCommon::TempFile testPortable2("testPortable2.txt"); - std::ofstream file2(testPortable2, std::ofstream::out); - file2.close(); - - TestCommon::TempDirectory testDirectoryFolder("testDirectory", true); - - std::filesystem::path installRootPath = installRootDirectory.GetPath(); - std::filesystem::path targetPath = installRootPath / "testPortable.txt"; - std::filesystem::path targetPath2 = installRootPath / "testPortable2.txt"; - std::filesystem::path symlinkPath = installRootPath / "testSymlink.exe"; - std::filesystem::path symlinkPath2 = installRootPath / "testSymlink2.exe"; - std::filesystem::path directoryPath = installRootPath / "testDirectory"; - - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable2.GetPath(), targetPath2, {}))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath2, targetPath2))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateDirectoryEntry(testDirectoryFolder.GetPath(), directoryPath))); - - PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - portableInstaller.TargetInstallLocation = installRootDirectory.GetPath(); - portableInstaller.RecordToIndex = true; - portableInstaller.SetDesiredState(desiredTestState); - REQUIRE(portableInstaller.VerifyExpectedState()); - - portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); - - REQUIRE(std::filesystem::exists(installRootPath / portableInstaller.GetPortableIndexFileName())); - REQUIRE(std::filesystem::exists(targetPath)); - REQUIRE(std::filesystem::exists(targetPath2)); - REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); - REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); - REQUIRE(std::filesystem::exists(directoryPath)); - - PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - REQUIRE(portableInstaller2.ARPEntryExists()); - - portableInstaller2.Uninstall(); - - // Install root directory should be removed since it was created. - REQUIRE_FALSE(std::filesystem::exists(installRootPath)); - REQUIRE_FALSE(std::filesystem::exists(targetPath)); - REQUIRE_FALSE(std::filesystem::exists(targetPath2)); - REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); - REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); - REQUIRE_FALSE(std::filesystem::exists(directoryPath)); -} - -TEST_CASE("PortableInstaller_InstallToIndex_ExistingInstallRoot", "[PortableInstaller]") -{ - TempDirectory installRootDirectory = TestCommon::TempDirectory("PortableInstallRoot", true); - - std::vector desiredTestState; - - TestCommon::TempFile testPortable("testPortable.txt"); - std::ofstream file1(testPortable, std::ofstream::out); - file1.close(); - - TestCommon::TempFile testPortable2("testPortable2.txt"); - std::ofstream file2(testPortable2, std::ofstream::out); - file2.close(); - - TestCommon::TempDirectory testDirectoryFolder("testDirectory", true); - - std::filesystem::path installRootPath = installRootDirectory.GetPath(); - std::filesystem::path targetPath = installRootPath / "testPortable.txt"; - std::filesystem::path targetPath2 = installRootPath / "testPortable2.txt"; - std::filesystem::path symlinkPath = installRootPath / "testSymlink.exe"; - std::filesystem::path symlinkPath2 = installRootPath / "testSymlink2.exe"; - std::filesystem::path directoryPath = installRootPath / "testDirectory"; - - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable2.GetPath(), targetPath2, {}))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath2, targetPath2))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateDirectoryEntry(testDirectoryFolder.GetPath(), directoryPath))); - - PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - portableInstaller.TargetInstallLocation = installRootDirectory.GetPath(); - portableInstaller.RecordToIndex = true; - portableInstaller.SetDesiredState(desiredTestState); - REQUIRE(portableInstaller.VerifyExpectedState()); - - portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); - - REQUIRE(std::filesystem::exists(installRootPath / portableInstaller.GetPortableIndexFileName())); - REQUIRE(std::filesystem::exists(targetPath)); - REQUIRE(std::filesystem::exists(targetPath2)); - REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); - REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); - REQUIRE(std::filesystem::exists(directoryPath)); - - PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - REQUIRE(portableInstaller2.ARPEntryExists()); - - portableInstaller2.Uninstall(); - - // Install root directory should still exist since it was created previously. - REQUIRE(std::filesystem::exists(installRootPath)); - REQUIRE_FALSE(std::filesystem::exists(targetPath)); - REQUIRE_FALSE(std::filesystem::exists(targetPath2)); - REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); - REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); - REQUIRE_FALSE(std::filesystem::exists(directoryPath)); -} - -TEST_CASE("PortableInstaller_UnicodeSymlinkPath", "[PortableInstaller]") -{ - TempDirectory tempDirectory = TestCommon::TempDirectory("TempDirectory", false); - - // Modify install location path to include unicode characters. - std::filesystem::path testInstallLocation = tempDirectory.GetPath() / std::filesystem::path{ ConvertToUTF16("романтический") }; - - std::vector desiredTestState; - - TestCommon::TempFile testPortable("testPortable.txt"); - std::ofstream file(testPortable, std::ofstream::out); - file.close(); - - std::filesystem::path targetPath = testInstallLocation / "testPortable.txt"; - std::filesystem::path symlinkPath = tempDirectory.GetPath() / "testSymlink.exe"; - - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); - desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); - - PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - portableInstaller.TargetInstallLocation = testInstallLocation; - portableInstaller.SetDesiredState(desiredTestState); - REQUIRE(portableInstaller.VerifyExpectedState()); - - portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); - - PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); - REQUIRE(portableInstaller2.ARPEntryExists()); - REQUIRE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); - REQUIRE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); - - portableInstaller2.Uninstall(); - REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); - REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); - REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.InstallLocation)); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace AppInstaller::CLI::Portable; +using namespace AppInstaller::Filesystem; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Registry::Environment; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Repository::Microsoft::Schema; +using namespace AppInstaller::Utility; +using namespace AppInstaller::CLI::Workflow; +using namespace TestCommon; + +TEST_CASE("PortableInstaller_InstallToRegistry", "[PortableInstaller]") +{ + TempDirectory tempDirectory = TestCommon::TempDirectory("TempDirectory", false); + + std::vector desiredTestState; + + TestCommon::TempFile testPortable("testPortable.txt"); + std::ofstream file(testPortable, std::ofstream::out); + file.close(); + + std::filesystem::path targetPath = tempDirectory.GetPath() / "testPortable.txt"; + std::filesystem::path symlinkPath = tempDirectory.GetPath() / "testSymlink.exe"; + + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); + + PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + portableInstaller.TargetInstallLocation = tempDirectory.GetPath(); + portableInstaller.SetDesiredState(desiredTestState); + REQUIRE(portableInstaller.VerifyExpectedState()); + + portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); + + auto entry = portableInstaller.GetAppsAndFeaturesEntry(); + REQUIRE(entry.ProductCode == portableInstaller.GetProductCode()); + + PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + REQUIRE(portableInstaller2.ARPEntryExists()); + REQUIRE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); + REQUIRE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); + + portableInstaller2.Uninstall(); + REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); + REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); + REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.InstallLocation)); +} + +TEST_CASE("PortableInstaller_InstallToIndex_CreateInstallRoot", "[PortableInstaller]") +{ + TempDirectory installRootDirectory = TestCommon::TempDirectory("PortableInstallRoot", false); + + std::vector desiredTestState; + + TestCommon::TempFile testPortable("testPortable.txt"); + std::ofstream file1(testPortable, std::ofstream::out); + file1.close(); + + TestCommon::TempFile testPortable2("testPortable2.txt"); + std::ofstream file2(testPortable2, std::ofstream::out); + file2.close(); + + TestCommon::TempDirectory testDirectoryFolder("testDirectory", true); + + std::filesystem::path installRootPath = installRootDirectory.GetPath(); + std::filesystem::path targetPath = installRootPath / "testPortable.txt"; + std::filesystem::path targetPath2 = installRootPath / "testPortable2.txt"; + std::filesystem::path symlinkPath = installRootPath / "testSymlink.exe"; + std::filesystem::path symlinkPath2 = installRootPath / "testSymlink2.exe"; + std::filesystem::path directoryPath = installRootPath / "testDirectory"; + + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable2.GetPath(), targetPath2, {}))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath2, targetPath2))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateDirectoryEntry(testDirectoryFolder.GetPath(), directoryPath))); + + PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + portableInstaller.TargetInstallLocation = installRootDirectory.GetPath(); + portableInstaller.RecordToIndex = true; + portableInstaller.SetDesiredState(desiredTestState); + REQUIRE(portableInstaller.VerifyExpectedState()); + + portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); + + REQUIRE(std::filesystem::exists(installRootPath / portableInstaller.GetPortableIndexFileName())); + REQUIRE(std::filesystem::exists(targetPath)); + REQUIRE(std::filesystem::exists(targetPath2)); + REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); + REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); + REQUIRE(std::filesystem::exists(directoryPath)); + + PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + REQUIRE(portableInstaller2.ARPEntryExists()); + + portableInstaller2.Uninstall(); + + // Install root directory should be removed since it was created. + REQUIRE_FALSE(std::filesystem::exists(installRootPath)); + REQUIRE_FALSE(std::filesystem::exists(targetPath)); + REQUIRE_FALSE(std::filesystem::exists(targetPath2)); + REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); + REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); + REQUIRE_FALSE(std::filesystem::exists(directoryPath)); +} + +TEST_CASE("PortableInstaller_InstallToIndex_ExistingInstallRoot", "[PortableInstaller]") +{ + TempDirectory installRootDirectory = TestCommon::TempDirectory("PortableInstallRoot", true); + + std::vector desiredTestState; + + TestCommon::TempFile testPortable("testPortable.txt"); + std::ofstream file1(testPortable, std::ofstream::out); + file1.close(); + + TestCommon::TempFile testPortable2("testPortable2.txt"); + std::ofstream file2(testPortable2, std::ofstream::out); + file2.close(); + + TestCommon::TempDirectory testDirectoryFolder("testDirectory", true); + + std::filesystem::path installRootPath = installRootDirectory.GetPath(); + std::filesystem::path targetPath = installRootPath / "testPortable.txt"; + std::filesystem::path targetPath2 = installRootPath / "testPortable2.txt"; + std::filesystem::path symlinkPath = installRootPath / "testSymlink.exe"; + std::filesystem::path symlinkPath2 = installRootPath / "testSymlink2.exe"; + std::filesystem::path directoryPath = installRootPath / "testDirectory"; + + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable2.GetPath(), targetPath2, {}))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath2, targetPath2))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateDirectoryEntry(testDirectoryFolder.GetPath(), directoryPath))); + + PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + portableInstaller.TargetInstallLocation = installRootDirectory.GetPath(); + portableInstaller.RecordToIndex = true; + portableInstaller.SetDesiredState(desiredTestState); + REQUIRE(portableInstaller.VerifyExpectedState()); + + portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); + + REQUIRE(std::filesystem::exists(installRootPath / portableInstaller.GetPortableIndexFileName())); + REQUIRE(std::filesystem::exists(targetPath)); + REQUIRE(std::filesystem::exists(targetPath2)); + REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); + REQUIRE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); + REQUIRE(std::filesystem::exists(directoryPath)); + + PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + REQUIRE(portableInstaller2.ARPEntryExists()); + + portableInstaller2.Uninstall(); + + // Install root directory should still exist since it was created previously. + REQUIRE(std::filesystem::exists(installRootPath)); + REQUIRE_FALSE(std::filesystem::exists(targetPath)); + REQUIRE_FALSE(std::filesystem::exists(targetPath2)); + REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath)); + REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(symlinkPath2)); + REQUIRE_FALSE(std::filesystem::exists(directoryPath)); +} + +TEST_CASE("PortableInstaller_UnicodeSymlinkPath", "[PortableInstaller]") +{ + TempDirectory tempDirectory = TestCommon::TempDirectory("TempDirectory", false); + + // Modify install location path to include unicode characters. + std::filesystem::path testInstallLocation = tempDirectory.GetPath() / std::filesystem::path{ ConvertToUTF16("романтический") }; + + std::vector desiredTestState; + + TestCommon::TempFile testPortable("testPortable.txt"); + std::ofstream file(testPortable, std::ofstream::out); + file.close(); + + std::filesystem::path targetPath = testInstallLocation / "testPortable.txt"; + std::filesystem::path symlinkPath = tempDirectory.GetPath() / "testSymlink.exe"; + + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateFileEntry(testPortable.GetPath(), targetPath, {}))); + desiredTestState.emplace_back(std::move(PortableFileEntry::CreateSymlinkEntry(symlinkPath, targetPath))); + + PortableInstaller portableInstaller = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + portableInstaller.TargetInstallLocation = testInstallLocation; + portableInstaller.SetDesiredState(desiredTestState); + REQUIRE(portableInstaller.VerifyExpectedState()); + + portableInstaller.Install(AppInstaller::CLI::Workflow::OperationType::Install); + + PortableInstaller portableInstaller2 = PortableInstaller(ScopeEnum::User, Architecture::X64, "testProductCode"); + REQUIRE(portableInstaller2.ARPEntryExists()); + REQUIRE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); + REQUIRE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); + + portableInstaller2.Uninstall(); + REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.PortableTargetFullPath)); + REQUIRE_FALSE(AppInstaller::Filesystem::SymlinkExists(portableInstaller2.PortableSymlinkFullPath)); + REQUIRE_FALSE(std::filesystem::exists(portableInstaller2.InstallLocation)); +} diff --git a/src/AppInstallerCLITests/PreIndexedPackageSource.cpp b/src/AppInstallerCLITests/PreIndexedPackageSource.cpp index cdffb2945e..fe36dd1b31 100644 --- a/src/AppInstallerCLITests/PreIndexedPackageSource.cpp +++ b/src/AppInstallerCLITests/PreIndexedPackageSource.cpp @@ -1,228 +1,228 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestSource.h" -#include "TestCommon.h" -#include "TestSettings.h" -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Runtime; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; - -namespace fs = std::filesystem; - -constexpr std::string_view s_RepositorySettings_UserSources = "usersources"sv; - -constexpr std::string_view s_MsixFile_1 = "index.1.0.0.0.signed.msix"; -constexpr std::string_view s_MsixFile_2 = "index.2.0.0.0.signed.msix"; -constexpr std::string_view s_Msix_FamilyName = "AppInstallerCLITestsFakeIndex_8wekyb3d8bbwe"; -constexpr std::string_view s_IndexMsixName = "source.msix"sv; - -void CopyIndexFileToDirectory(const fs::path& from, const fs::path& to) -{ - fs::path toFile = to; - toFile /= s_IndexMsixName; - if (fs::exists(toFile)) - { - fs::remove(toFile); - } - fs::copy_file(from, toFile); -} - -fs::path GetPathToFileDir() -{ - fs::path result = GetPathTo(Runtime::PathName::LocalState); - result /= AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); - result /= s_Msix_FamilyName; - return result; -} - -std::string GetContents(const fs::path& file) -{ - REQUIRE(fs::exists(file)); - std::ifstream stream(file); - return ReadEntireStream(stream); -} - -void CleanSources() -{ - RemoveSetting(Stream::UserSources); - RemoveSetting(Stream::SourcesMetadata); - fs::remove_all(GetPathToFileDir()); -} - -TEST_CASE("PIPS_Add", "[pips]") -{ - if (!Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - CleanSources(); - - TempDirectory dir("pipssource"); - TestDataFile index(s_MsixFile_1); - CopyIndexFileToDirectory(index, dir); - - bool shouldCleanCert = InstallCertFromSignedPackage(index); - - SourceDetails details; - details.Name = "TestName"; - details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); - details.Arg = dir; - ProgressCallback callback; - - AddSource(details, callback); - - fs::path state = GetPathToFileDir(); - REQUIRE(fs::exists(state)); - - fs::path indexMsix = state; - indexMsix /= s_IndexMsixName; - REQUIRE(fs::exists(indexMsix)); - REQUIRE(fs::file_size(indexMsix) > 0); - - if (shouldCleanCert) - { - UninstallCertFromSignedPackage(index); - } -} - -TEST_CASE("PIPS_UpdateSameVersion", "[pips]") -{ - if (!Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - CleanSources(); - - TempDirectory dir("pipssource"); - TestDataFile index(s_MsixFile_1); - CopyIndexFileToDirectory(index, dir); - - bool shouldCleanCert = InstallCertFromSignedPackage(index); - - SourceDetails details; - details.Name = "TestName"; - details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); - details.Arg = dir; - TestProgress callback; - - AddSource(details, callback); - - fs::path state = GetPathToFileDir(); - REQUIRE(fs::exists(state)); - - bool progressCalled = false; - callback.m_OnProgress = [&](uint64_t, uint64_t, ProgressType) { progressCalled = true; }; - - UpdateSource(details.Name, callback); - REQUIRE(!progressCalled); - - if (shouldCleanCert) - { - UninstallCertFromSignedPackage(index); - } -} - -TEST_CASE("PIPS_UpdateNewVersion", "[pips]") -{ - if (!Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - CleanSources(); - - TempDirectory dir("pipssource"); - TestDataFile indexMsix1(s_MsixFile_1); - CopyIndexFileToDirectory(indexMsix1, dir); - - bool shouldCleanCert = InstallCertFromSignedPackage(indexMsix1); - - SourceDetails details; - details.Name = "TestName"; - details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); - details.Arg = dir; - TestProgress callback; - - AddSource(details, callback); - - fs::path state = GetPathToFileDir(); - REQUIRE(fs::exists(state)); - - fs::path indexMsix = state; - indexMsix /= s_IndexMsixName; - std::string indexContents1 = GetContents(indexMsix); - - TestDataFile indexMsix2(s_MsixFile_2); - CopyIndexFileToDirectory(indexMsix2, dir); - - bool progressCalled = false; - callback.m_OnProgress = [&](uint64_t, uint64_t, ProgressType) { progressCalled = true; }; - - UpdateSource(details.Name, callback); - REQUIRE(progressCalled); - - std::string indexContents2 = GetContents(indexMsix); - REQUIRE(indexContents1 != indexContents2); - - if (shouldCleanCert) - { - UninstallCertFromSignedPackage(indexMsix1); - } -} - -TEST_CASE("PIPS_Remove", "[pips]") -{ - if (!Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - CleanSources(); - - TempDirectory dir("pipssource"); - TestDataFile index(s_MsixFile_1); - CopyIndexFileToDirectory(index, dir); - - bool shouldCleanCert = InstallCertFromSignedPackage(index); - - SourceDetails details; - details.Name = "TestName"; - details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); - details.Arg = dir; - ProgressCallback callback; - - AddSource(details, callback); - - fs::path state = GetPathToFileDir(); - REQUIRE(fs::exists(state)); - - fs::path indexMsix = state; - indexMsix /= s_IndexMsixName; - REQUIRE(fs::exists(indexMsix)); - - RemoveSource(details.Name, callback); - REQUIRE(!fs::exists(state)); - - if (shouldCleanCert) - { - UninstallCertFromSignedPackage(index); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestSource.h" +#include "TestCommon.h" +#include "TestSettings.h" +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Runtime; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; + +namespace fs = std::filesystem; + +constexpr std::string_view s_RepositorySettings_UserSources = "usersources"sv; + +constexpr std::string_view s_MsixFile_1 = "index.1.0.0.0.signed.msix"; +constexpr std::string_view s_MsixFile_2 = "index.2.0.0.0.signed.msix"; +constexpr std::string_view s_Msix_FamilyName = "AppInstallerCLITestsFakeIndex_8wekyb3d8bbwe"; +constexpr std::string_view s_IndexMsixName = "source.msix"sv; + +void CopyIndexFileToDirectory(const fs::path& from, const fs::path& to) +{ + fs::path toFile = to; + toFile /= s_IndexMsixName; + if (fs::exists(toFile)) + { + fs::remove(toFile); + } + fs::copy_file(from, toFile); +} + +fs::path GetPathToFileDir() +{ + fs::path result = GetPathTo(Runtime::PathName::LocalState); + result /= AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); + result /= s_Msix_FamilyName; + return result; +} + +std::string GetContents(const fs::path& file) +{ + REQUIRE(fs::exists(file)); + std::ifstream stream(file); + return ReadEntireStream(stream); +} + +void CleanSources() +{ + RemoveSetting(Stream::UserSources); + RemoveSetting(Stream::SourcesMetadata); + fs::remove_all(GetPathToFileDir()); +} + +TEST_CASE("PIPS_Add", "[pips]") +{ + if (!Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + CleanSources(); + + TempDirectory dir("pipssource"); + TestDataFile index(s_MsixFile_1); + CopyIndexFileToDirectory(index, dir); + + bool shouldCleanCert = InstallCertFromSignedPackage(index); + + SourceDetails details; + details.Name = "TestName"; + details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); + details.Arg = dir; + ProgressCallback callback; + + AddSource(details, callback); + + fs::path state = GetPathToFileDir(); + REQUIRE(fs::exists(state)); + + fs::path indexMsix = state; + indexMsix /= s_IndexMsixName; + REQUIRE(fs::exists(indexMsix)); + REQUIRE(fs::file_size(indexMsix) > 0); + + if (shouldCleanCert) + { + UninstallCertFromSignedPackage(index); + } +} + +TEST_CASE("PIPS_UpdateSameVersion", "[pips]") +{ + if (!Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + CleanSources(); + + TempDirectory dir("pipssource"); + TestDataFile index(s_MsixFile_1); + CopyIndexFileToDirectory(index, dir); + + bool shouldCleanCert = InstallCertFromSignedPackage(index); + + SourceDetails details; + details.Name = "TestName"; + details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); + details.Arg = dir; + TestProgress callback; + + AddSource(details, callback); + + fs::path state = GetPathToFileDir(); + REQUIRE(fs::exists(state)); + + bool progressCalled = false; + callback.m_OnProgress = [&](uint64_t, uint64_t, ProgressType) { progressCalled = true; }; + + UpdateSource(details.Name, callback); + REQUIRE(!progressCalled); + + if (shouldCleanCert) + { + UninstallCertFromSignedPackage(index); + } +} + +TEST_CASE("PIPS_UpdateNewVersion", "[pips]") +{ + if (!Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + CleanSources(); + + TempDirectory dir("pipssource"); + TestDataFile indexMsix1(s_MsixFile_1); + CopyIndexFileToDirectory(indexMsix1, dir); + + bool shouldCleanCert = InstallCertFromSignedPackage(indexMsix1); + + SourceDetails details; + details.Name = "TestName"; + details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); + details.Arg = dir; + TestProgress callback; + + AddSource(details, callback); + + fs::path state = GetPathToFileDir(); + REQUIRE(fs::exists(state)); + + fs::path indexMsix = state; + indexMsix /= s_IndexMsixName; + std::string indexContents1 = GetContents(indexMsix); + + TestDataFile indexMsix2(s_MsixFile_2); + CopyIndexFileToDirectory(indexMsix2, dir); + + bool progressCalled = false; + callback.m_OnProgress = [&](uint64_t, uint64_t, ProgressType) { progressCalled = true; }; + + UpdateSource(details.Name, callback); + REQUIRE(progressCalled); + + std::string indexContents2 = GetContents(indexMsix); + REQUIRE(indexContents1 != indexContents2); + + if (shouldCleanCert) + { + UninstallCertFromSignedPackage(indexMsix1); + } +} + +TEST_CASE("PIPS_Remove", "[pips]") +{ + if (!Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + CleanSources(); + + TempDirectory dir("pipssource"); + TestDataFile index(s_MsixFile_1); + CopyIndexFileToDirectory(index, dir); + + bool shouldCleanCert = InstallCertFromSignedPackage(index); + + SourceDetails details; + details.Name = "TestName"; + details.Type = AppInstaller::Repository::Microsoft::PreIndexedPackageSourceFactory::Type(); + details.Arg = dir; + ProgressCallback callback; + + AddSource(details, callback); + + fs::path state = GetPathToFileDir(); + REQUIRE(fs::exists(state)); + + fs::path indexMsix = state; + indexMsix /= s_IndexMsixName; + REQUIRE(fs::exists(indexMsix)); + + RemoveSource(details.Name, callback); + REQUIRE(!fs::exists(state)); + + if (shouldCleanCert) + { + UninstallCertFromSignedPackage(index); + } +} diff --git a/src/AppInstallerCLITests/PredefinedInstalledSource.cpp b/src/AppInstallerCLITests/PredefinedInstalledSource.cpp index d4bec5c90c..4939f99cda 100644 --- a/src/AppInstallerCLITests/PredefinedInstalledSource.cpp +++ b/src/AppInstallerCLITests/PredefinedInstalledSource.cpp @@ -1,488 +1,488 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Runtime; -using namespace AppInstaller::Utility; - -using SQLiteIndex = AppInstaller::Repository::Microsoft::SQLiteIndex; -using SQLiteIndexSource = AppInstaller::Repository::Microsoft::SQLiteIndexSource; -using Factory = AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory; -using ARPHelper = AppInstaller::Repository::Microsoft::ARPHelper; - -constexpr std::string_view s_TestScope = "TestScope"sv; - -struct ARPEntry -{ - ARPEntry(std::string entryName) : EntryName(std::move(entryName)) {} - ARPEntry(std::string entryName, std::optional displayName, std::optional displayVersion, bool systemComponent = false) : - EntryName(std::move(entryName)), DisplayName(std::move(displayName)), DisplayVersion(std::move(displayVersion)), SystemComponent(systemComponent) {} - - std::string EntryName; - std::optional DisplayName; - std::optional DisplayVersion; - std::optional Publisher; - std::optional InstallLocation; - std::optional UninstallString; - std::optional QuietUninstallString; - std::optional WindowsInstaller; - std::optional SystemComponent; -}; - -void AddARPValueToKey(HKEY key, const std::wstring& name, const std::optional& value) -{ - if (value) - { - SetRegistryValue(key, name, ConvertToUTF16(value.value())); - } -} - -void AddARPValueToKey(HKEY key, const std::wstring& name, const std::optional& value) -{ - if (value) - { - SetRegistryValue(key, name, (value.value() ? 1 : 0)); - } -} - -void AddARPEntryToKey(HKEY key, const ARPHelper& helper, const ARPEntry& entry) -{ - auto subkey = RegCreateVolatileSubKey(key, ConvertToUTF16(entry.EntryName)); - -#define ADD_ARP_VALUE(_name_) AddARPValueToKey(subkey.get(), helper._name_, entry._name_) - ADD_ARP_VALUE(DisplayName); - ADD_ARP_VALUE(DisplayVersion); - ADD_ARP_VALUE(Publisher); - ADD_ARP_VALUE(InstallLocation); - ADD_ARP_VALUE(UninstallString); - ADD_ARP_VALUE(QuietUninstallString); - ADD_ARP_VALUE(WindowsInstaller); - ADD_ARP_VALUE(SystemComponent); -#undef ADD_ARP_VALUE -} - -void AddARPEntriesToKey(HKEY key, const ARPHelper& helper, const std::vector& entries) -{ - for (const auto& entry : entries) - { - AddARPEntryToKey(key, helper, entry); - } -} - -SQLiteIndex::MetadataResult::const_iterator Find(const SQLiteIndex::MetadataResult& metadata, PackageVersionMetadata value) -{ - return std::find_if(metadata.begin(), metadata.end(), [value](const auto& m) { return m.first == value; }); -} - -void VerifyInstalledType(const SQLiteIndex::MetadataResult& metadata, InstallerTypeEnum type) -{ - auto itr = Find(metadata, PackageVersionMetadata::InstalledType); - REQUIRE(itr != metadata.end()); - REQUIRE(ConvertToInstallerTypeEnum(itr->second) == type); -} - -void VerifyTestScope(const SQLiteIndex::MetadataResult& metadata) -{ - auto itr = Find(metadata, PackageVersionMetadata::InstalledScope); - REQUIRE(itr != metadata.end()); - REQUIRE(itr->second == s_TestScope); -} - -void VerifyMetadataString(const SQLiteIndex::MetadataResult& metadata, PackageVersionMetadata pvm, const std::optional& value) -{ - auto itr = Find(metadata, pvm); - if (value) - { - REQUIRE(itr != metadata.end()); - REQUIRE(itr->second == value.value()); - } - else - { - REQUIRE(itr == metadata.end()); - } -} - -void VerifyEntryAgainstIndex(const SQLiteIndex& index, SQLiteIndex::IdType manifestId, const ARPEntry& entry) -{ - REQUIRE(index.GetPropertyByPrimaryId(manifestId, PackageVersionProperty::Name) == entry.DisplayName); - REQUIRE(index.GetPropertyByPrimaryId(manifestId, PackageVersionProperty::Version) == entry.DisplayVersion); - - REQUIRE(index.GetMultiPropertyByPrimaryId(manifestId, PackageVersionMultiProperty::PackageFamilyName).empty()); - auto productCodes = index.GetMultiPropertyByPrimaryId(manifestId, PackageVersionMultiProperty::ProductCode); - REQUIRE(productCodes.size() == 1); - REQUIRE(productCodes[0] == FoldCase(static_cast(entry.EntryName))); - - auto metadata = index.GetMetadataByManifestId(manifestId); - - VerifyInstalledType(metadata, entry.WindowsInstaller.value_or(false) ? InstallerTypeEnum::Msi : InstallerTypeEnum::Exe); - VerifyTestScope(metadata); - VerifyMetadataString(metadata, PackageVersionMetadata::Publisher, entry.Publisher); - VerifyMetadataString(metadata, PackageVersionMetadata::InstalledLocation, entry.InstallLocation); - VerifyMetadataString(metadata, PackageVersionMetadata::StandardUninstallCommand, entry.UninstallString); - VerifyMetadataString(metadata, PackageVersionMetadata::SilentUninstallCommand, entry.QuietUninstallString); -} - -std::shared_ptr CreatePredefinedInstalledSource(Factory::Filter filter = Factory::Filter::None) -{ - SourceDetails details; - details.Type = Factory::Type(); - details.Arg = Factory::FilterToString(filter); - - TestProgress progress; - - auto factory = Factory::Create(); - return factory->Create(details)->Open(progress); -} - -SQLiteIndex CreateMemoryIndex() -{ - return SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless); -} - -TEST_CASE("ARPHelper_GetARPForArchitecture", "[arphelper][list]") -{ - auto systemArch = GetSystemArchitecture(); - - ARPHelper helper; - - auto nativeMachineKey = helper.GetARPKey(ScopeEnum::Machine, systemArch); - REQUIRE(nativeMachineKey); -} - -TEST_CASE("ARPHelper_GetBoolValue_DoesNotExist", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - std::wstring valueName = L"TestValueName"; - - ARPHelper helper; - - REQUIRE_FALSE(helper.GetBoolValue(key, valueName)); -} - -TEST_CASE("ARPHelper_GetBoolValue_NotDword", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - std::wstring valueName = L"TestValueName"; - - SetRegistryValue(root.get(), valueName, L"True"); - - ARPHelper helper; - - REQUIRE_FALSE(helper.GetBoolValue(key, valueName)); -} - -TEST_CASE("ARPHelper_GetBoolValue_Zero", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - std::wstring valueName = L"TestValueName"; - - SetRegistryValue(root.get(), valueName, 0); - - ARPHelper helper; - - REQUIRE_FALSE(helper.GetBoolValue(key, valueName)); -} - -TEST_CASE("ARPHelper_GetBoolValue_One", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - std::wstring valueName = L"TestValueName"; - - SetRegistryValue(root.get(), valueName, 1); - - ARPHelper helper; - - REQUIRE(helper.GetBoolValue(key, valueName)); -} - -TEST_CASE("ARPHelper_GetBoolValue_FortyTwo", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - std::wstring valueName = L"TestValueName"; - - SetRegistryValue(root.get(), valueName, 42); - - ARPHelper helper; - - REQUIRE(helper.GetBoolValue(key, valueName)); -} - -TEST_CASE("ARPHelper_DetermineVersion_DisplayVersion", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - - ARPHelper helper; - - SetRegistryValue(root.get(), helper.DisplayVersion, L"1.0"); - SetRegistryValue(root.get(), helper.Version, 0x0207002A); - SetRegistryValue(root.get(), helper.VersionMajor, 3); - SetRegistryValue(root.get(), helper.VersionMinor, 14); - - auto result = helper.DetermineVersion(key); - REQUIRE(result == "1.0"); -} - -TEST_CASE("ARPHelper_DetermineVersion_Version", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - - ARPHelper helper; - - SetRegistryValue(root.get(), helper.Version, 0x0207002A); - SetRegistryValue(root.get(), helper.VersionMajor, 3); - SetRegistryValue(root.get(), helper.VersionMinor, 14); - - auto result = helper.DetermineVersion(key); - REQUIRE(result == "3.14"); -} - -TEST_CASE("ARPHelper_DetermineVersion_VersionMajorMinor", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - - ARPHelper helper; - - SetRegistryValue(root.get(), helper.VersionMajor, 3); - SetRegistryValue(root.get(), helper.VersionMinor, 14); - - auto result = helper.DetermineVersion(key); - REQUIRE(result == "3.14"); -} - -TEST_CASE("ARPHelper_DetermineVersion_Unknown", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - - ARPHelper helper; - - auto result = helper.DetermineVersion(key); - REQUIRE(result == Version::CreateUnknown().ToString()); -} - -TEST_CASE("ARPHelper_PopulateIndexFromKey_Single", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - - ARPHelper helper; - - // Create a single ARP entry under the root - ARPEntry entry("SingleEntry"); - - entry.DisplayName = "Test Name"; - entry.DisplayVersion = "1.2"; - entry.Publisher = "Test Publisher"; - entry.InstallLocation = "TestLocation"; - entry.UninstallString = "Test Uninstall"; - entry.QuietUninstallString = "Test Quiet Uninstall"; - entry.WindowsInstaller = true; - - AddARPEntryToKey(root.get(), helper, entry); - - auto index = CreateMemoryIndex(); - helper.PopulateIndexFromKey(index, key, s_TestScope, "TestArchitecture"); - - auto result = index.Search({}); - - REQUIRE(result.Matches.size() == 1); - VerifyEntryAgainstIndex(index, result.Matches[0].first, entry); -} - -TEST_CASE("ARPHelper_PopulateIndexFromKey_SingleValid", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - - ARPHelper helper; - - // Create a single ARP entry under the root - ARPEntry entry("SingleEntry"); - - entry.DisplayName = "Test Name"; - entry.DisplayVersion = "1.2"; - entry.Publisher = "Test Publisher"; - entry.InstallLocation = "TestLocation"; - entry.UninstallString = "Test Uninstall"; - entry.QuietUninstallString = "Test Quiet Uninstall"; - entry.WindowsInstaller = false; - - AddARPEntryToKey(root.get(), helper, entry); - - // Name and version must exist, as well as not being a system component. - AddARPEntriesToKey(root.get(), helper, { - { "ValidButIsSystemComponent", "A", "0.1", true }, - { "NoName", {}, "0.2" }, - { "Nothing" }, - }); - - auto index = CreateMemoryIndex(); - helper.PopulateIndexFromKey(index, key, s_TestScope, "TestArchitecture"); - - auto result = index.Search({}); - - REQUIRE(result.Matches.size() == 1); - VerifyEntryAgainstIndex(index, result.Matches[0].first, entry); -} - -TEST_CASE("ARPHelper_PopulateIndexFromKey_Two", "[arphelper][list]") -{ - auto root = RegCreateVolatileTestRoot(); - Registry::Key key(root.get()); - - ARPHelper helper; - - ARPEntry entry1("FirstEntry"); - entry1.DisplayName = "Test Name"; - entry1.DisplayVersion = "1.2"; - entry1.Publisher = "Test Publisher"; - entry1.InstallLocation = "TestLocation"; - entry1.UninstallString = "Test Uninstall"; - entry1.QuietUninstallString = "Test Quiet Uninstall"; - entry1.WindowsInstaller = true; - - ARPEntry entry2("SecondEntry"); - entry2.DisplayName = "Different Test Name"; - entry2.DisplayVersion = "31.4"; - entry2.Publisher = "Different Test Publisher"; - entry2.InstallLocation = "DifferentTestLocation"; - entry2.UninstallString = "Different Test Uninstall"; - entry2.QuietUninstallString = "Different Test Quiet Uninstall"; - - AddARPEntryToKey(root.get(), helper, entry1); - AddARPEntryToKey(root.get(), helper, entry2); - - auto index = CreateMemoryIndex(); - helper.PopulateIndexFromKey(index, key, s_TestScope, "TestArchitecture"); - - REQUIRE(index.Search({}).Matches.size() == 2); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, entry1.EntryName); - auto result = index.Search(request); - - REQUIRE(result.Matches.size() == 1); - VerifyEntryAgainstIndex(index, result.Matches[0].first, entry1); - - request.Query = RequestMatch(MatchType::Exact, entry2.EntryName); - result = index.Search(request); - - REQUIRE(result.Matches.size() == 1); - VerifyEntryAgainstIndex(index, result.Matches[0].first, entry2); -} - -TEST_CASE("PredefinedInstalledSource_Create", "[installed][list]") -{ - auto source = CreatePredefinedInstalledSource(); -} - -TEST_CASE("PredefinedInstalledSource_Search", "[installed][list]") -{ - auto source = CreatePredefinedInstalledSource(); - - SearchRequest request; - - auto results = source->Search(request); - - REQUIRE_FALSE(results.Matches.empty()); -} - -std::string GetDatabaseIdentifier(const std::shared_ptr& source) -{ - return reinterpret_cast(source->CastTo(ISourceType::SQLiteIndexSource))->GetIndex().GetDatabaseIdentifier(); -} - -void RequirePackagesHaveSameNames(std::shared_ptr& source1, std::shared_ptr& source2) -{ - auto result1 = source1->Search({}); - REQUIRE(!result1.Matches.empty()); - - // Ensure that all packages have the same name values - for (const auto& match : result1.Matches) - { - std::string packageId = match.Package->GetProperty(PackageProperty::Id).get(); - INFO(packageId); - - SearchRequest id2; - id2.Inclusions.emplace_back(PackageMatchFilter{ PackageMatchField::Id, MatchType::CaseInsensitive, packageId }); - auto result2 = source2->Search(id2); - REQUIRE(result2.Matches.size() == 1); - REQUIRE(match.Package->GetProperty(PackageProperty::Name) == result2.Matches[0].Package->GetProperty(PackageProperty::Name)); - } -} - -TEST_CASE("PredefinedInstalledSource_Create_Cached", "[installed][list][installed-cache]") -{ - auto source1 = CreatePredefinedInstalledSource(); - auto source2 = CreatePredefinedInstalledSource(); - - // Ensure the same identifier (which should mean the cache was not updated) - REQUIRE( - GetDatabaseIdentifier(source1) - == - GetDatabaseIdentifier(source2) - ); - - RequirePackagesHaveSameNames(source1, source2); - RequirePackagesHaveSameNames(source2, source1); -} - -TEST_CASE("PredefinedInstalledSource_Create_ForceCacheUpdate", "[installed][list][installed-cache]") -{ - auto source1 = CreatePredefinedInstalledSource(); - auto source2 = CreatePredefinedInstalledSource(Factory::Filter::NoneWithForcedCacheUpdate); - - // Ensure different identifier (which should mean the cache was updated) - REQUIRE( - GetDatabaseIdentifier(source1) - != - GetDatabaseIdentifier(source2) - ); - - RequirePackagesHaveSameNames(source1, source2); - RequirePackagesHaveSameNames(source2, source1); -} - -TEST_CASE("PredefinedInstalledSource_Create_ForceCacheUpdate_StillCached", "[installed][list][installed-cache]") -{ - auto source1 = CreatePredefinedInstalledSource(); - auto source2 = CreatePredefinedInstalledSource(Factory::Filter::NoneWithForcedCacheUpdate); - auto source3 = CreatePredefinedInstalledSource(); - - CAPTURE(GetDatabaseIdentifier(source1), GetDatabaseIdentifier(source2), GetDatabaseIdentifier(source3)); - - // Ensure different identifier (which should mean the cache was updated) - REQUIRE( - GetDatabaseIdentifier(source1) - != - GetDatabaseIdentifier(source2) - ); - - // Ensure the same identifier (which should mean the cache was not updated) - REQUIRE( - GetDatabaseIdentifier(source2) - == - GetDatabaseIdentifier(source3) - ); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Runtime; +using namespace AppInstaller::Utility; + +using SQLiteIndex = AppInstaller::Repository::Microsoft::SQLiteIndex; +using SQLiteIndexSource = AppInstaller::Repository::Microsoft::SQLiteIndexSource; +using Factory = AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory; +using ARPHelper = AppInstaller::Repository::Microsoft::ARPHelper; + +constexpr std::string_view s_TestScope = "TestScope"sv; + +struct ARPEntry +{ + ARPEntry(std::string entryName) : EntryName(std::move(entryName)) {} + ARPEntry(std::string entryName, std::optional displayName, std::optional displayVersion, bool systemComponent = false) : + EntryName(std::move(entryName)), DisplayName(std::move(displayName)), DisplayVersion(std::move(displayVersion)), SystemComponent(systemComponent) {} + + std::string EntryName; + std::optional DisplayName; + std::optional DisplayVersion; + std::optional Publisher; + std::optional InstallLocation; + std::optional UninstallString; + std::optional QuietUninstallString; + std::optional WindowsInstaller; + std::optional SystemComponent; +}; + +void AddARPValueToKey(HKEY key, const std::wstring& name, const std::optional& value) +{ + if (value) + { + SetRegistryValue(key, name, ConvertToUTF16(value.value())); + } +} + +void AddARPValueToKey(HKEY key, const std::wstring& name, const std::optional& value) +{ + if (value) + { + SetRegistryValue(key, name, (value.value() ? 1 : 0)); + } +} + +void AddARPEntryToKey(HKEY key, const ARPHelper& helper, const ARPEntry& entry) +{ + auto subkey = RegCreateVolatileSubKey(key, ConvertToUTF16(entry.EntryName)); + +#define ADD_ARP_VALUE(_name_) AddARPValueToKey(subkey.get(), helper._name_, entry._name_) + ADD_ARP_VALUE(DisplayName); + ADD_ARP_VALUE(DisplayVersion); + ADD_ARP_VALUE(Publisher); + ADD_ARP_VALUE(InstallLocation); + ADD_ARP_VALUE(UninstallString); + ADD_ARP_VALUE(QuietUninstallString); + ADD_ARP_VALUE(WindowsInstaller); + ADD_ARP_VALUE(SystemComponent); +#undef ADD_ARP_VALUE +} + +void AddARPEntriesToKey(HKEY key, const ARPHelper& helper, const std::vector& entries) +{ + for (const auto& entry : entries) + { + AddARPEntryToKey(key, helper, entry); + } +} + +SQLiteIndex::MetadataResult::const_iterator Find(const SQLiteIndex::MetadataResult& metadata, PackageVersionMetadata value) +{ + return std::find_if(metadata.begin(), metadata.end(), [value](const auto& m) { return m.first == value; }); +} + +void VerifyInstalledType(const SQLiteIndex::MetadataResult& metadata, InstallerTypeEnum type) +{ + auto itr = Find(metadata, PackageVersionMetadata::InstalledType); + REQUIRE(itr != metadata.end()); + REQUIRE(ConvertToInstallerTypeEnum(itr->second) == type); +} + +void VerifyTestScope(const SQLiteIndex::MetadataResult& metadata) +{ + auto itr = Find(metadata, PackageVersionMetadata::InstalledScope); + REQUIRE(itr != metadata.end()); + REQUIRE(itr->second == s_TestScope); +} + +void VerifyMetadataString(const SQLiteIndex::MetadataResult& metadata, PackageVersionMetadata pvm, const std::optional& value) +{ + auto itr = Find(metadata, pvm); + if (value) + { + REQUIRE(itr != metadata.end()); + REQUIRE(itr->second == value.value()); + } + else + { + REQUIRE(itr == metadata.end()); + } +} + +void VerifyEntryAgainstIndex(const SQLiteIndex& index, SQLiteIndex::IdType manifestId, const ARPEntry& entry) +{ + REQUIRE(index.GetPropertyByPrimaryId(manifestId, PackageVersionProperty::Name) == entry.DisplayName); + REQUIRE(index.GetPropertyByPrimaryId(manifestId, PackageVersionProperty::Version) == entry.DisplayVersion); + + REQUIRE(index.GetMultiPropertyByPrimaryId(manifestId, PackageVersionMultiProperty::PackageFamilyName).empty()); + auto productCodes = index.GetMultiPropertyByPrimaryId(manifestId, PackageVersionMultiProperty::ProductCode); + REQUIRE(productCodes.size() == 1); + REQUIRE(productCodes[0] == FoldCase(static_cast(entry.EntryName))); + + auto metadata = index.GetMetadataByManifestId(manifestId); + + VerifyInstalledType(metadata, entry.WindowsInstaller.value_or(false) ? InstallerTypeEnum::Msi : InstallerTypeEnum::Exe); + VerifyTestScope(metadata); + VerifyMetadataString(metadata, PackageVersionMetadata::Publisher, entry.Publisher); + VerifyMetadataString(metadata, PackageVersionMetadata::InstalledLocation, entry.InstallLocation); + VerifyMetadataString(metadata, PackageVersionMetadata::StandardUninstallCommand, entry.UninstallString); + VerifyMetadataString(metadata, PackageVersionMetadata::SilentUninstallCommand, entry.QuietUninstallString); +} + +std::shared_ptr CreatePredefinedInstalledSource(Factory::Filter filter = Factory::Filter::None) +{ + SourceDetails details; + details.Type = Factory::Type(); + details.Arg = Factory::FilterToString(filter); + + TestProgress progress; + + auto factory = Factory::Create(); + return factory->Create(details)->Open(progress); +} + +SQLiteIndex CreateMemoryIndex() +{ + return SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless); +} + +TEST_CASE("ARPHelper_GetARPForArchitecture", "[arphelper][list]") +{ + auto systemArch = GetSystemArchitecture(); + + ARPHelper helper; + + auto nativeMachineKey = helper.GetARPKey(ScopeEnum::Machine, systemArch); + REQUIRE(nativeMachineKey); +} + +TEST_CASE("ARPHelper_GetBoolValue_DoesNotExist", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + std::wstring valueName = L"TestValueName"; + + ARPHelper helper; + + REQUIRE_FALSE(helper.GetBoolValue(key, valueName)); +} + +TEST_CASE("ARPHelper_GetBoolValue_NotDword", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + std::wstring valueName = L"TestValueName"; + + SetRegistryValue(root.get(), valueName, L"True"); + + ARPHelper helper; + + REQUIRE_FALSE(helper.GetBoolValue(key, valueName)); +} + +TEST_CASE("ARPHelper_GetBoolValue_Zero", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + std::wstring valueName = L"TestValueName"; + + SetRegistryValue(root.get(), valueName, 0); + + ARPHelper helper; + + REQUIRE_FALSE(helper.GetBoolValue(key, valueName)); +} + +TEST_CASE("ARPHelper_GetBoolValue_One", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + std::wstring valueName = L"TestValueName"; + + SetRegistryValue(root.get(), valueName, 1); + + ARPHelper helper; + + REQUIRE(helper.GetBoolValue(key, valueName)); +} + +TEST_CASE("ARPHelper_GetBoolValue_FortyTwo", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + std::wstring valueName = L"TestValueName"; + + SetRegistryValue(root.get(), valueName, 42); + + ARPHelper helper; + + REQUIRE(helper.GetBoolValue(key, valueName)); +} + +TEST_CASE("ARPHelper_DetermineVersion_DisplayVersion", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + + ARPHelper helper; + + SetRegistryValue(root.get(), helper.DisplayVersion, L"1.0"); + SetRegistryValue(root.get(), helper.Version, 0x0207002A); + SetRegistryValue(root.get(), helper.VersionMajor, 3); + SetRegistryValue(root.get(), helper.VersionMinor, 14); + + auto result = helper.DetermineVersion(key); + REQUIRE(result == "1.0"); +} + +TEST_CASE("ARPHelper_DetermineVersion_Version", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + + ARPHelper helper; + + SetRegistryValue(root.get(), helper.Version, 0x0207002A); + SetRegistryValue(root.get(), helper.VersionMajor, 3); + SetRegistryValue(root.get(), helper.VersionMinor, 14); + + auto result = helper.DetermineVersion(key); + REQUIRE(result == "3.14"); +} + +TEST_CASE("ARPHelper_DetermineVersion_VersionMajorMinor", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + + ARPHelper helper; + + SetRegistryValue(root.get(), helper.VersionMajor, 3); + SetRegistryValue(root.get(), helper.VersionMinor, 14); + + auto result = helper.DetermineVersion(key); + REQUIRE(result == "3.14"); +} + +TEST_CASE("ARPHelper_DetermineVersion_Unknown", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + + ARPHelper helper; + + auto result = helper.DetermineVersion(key); + REQUIRE(result == Version::CreateUnknown().ToString()); +} + +TEST_CASE("ARPHelper_PopulateIndexFromKey_Single", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + + ARPHelper helper; + + // Create a single ARP entry under the root + ARPEntry entry("SingleEntry"); + + entry.DisplayName = "Test Name"; + entry.DisplayVersion = "1.2"; + entry.Publisher = "Test Publisher"; + entry.InstallLocation = "TestLocation"; + entry.UninstallString = "Test Uninstall"; + entry.QuietUninstallString = "Test Quiet Uninstall"; + entry.WindowsInstaller = true; + + AddARPEntryToKey(root.get(), helper, entry); + + auto index = CreateMemoryIndex(); + helper.PopulateIndexFromKey(index, key, s_TestScope, "TestArchitecture"); + + auto result = index.Search({}); + + REQUIRE(result.Matches.size() == 1); + VerifyEntryAgainstIndex(index, result.Matches[0].first, entry); +} + +TEST_CASE("ARPHelper_PopulateIndexFromKey_SingleValid", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + + ARPHelper helper; + + // Create a single ARP entry under the root + ARPEntry entry("SingleEntry"); + + entry.DisplayName = "Test Name"; + entry.DisplayVersion = "1.2"; + entry.Publisher = "Test Publisher"; + entry.InstallLocation = "TestLocation"; + entry.UninstallString = "Test Uninstall"; + entry.QuietUninstallString = "Test Quiet Uninstall"; + entry.WindowsInstaller = false; + + AddARPEntryToKey(root.get(), helper, entry); + + // Name and version must exist, as well as not being a system component. + AddARPEntriesToKey(root.get(), helper, { + { "ValidButIsSystemComponent", "A", "0.1", true }, + { "NoName", {}, "0.2" }, + { "Nothing" }, + }); + + auto index = CreateMemoryIndex(); + helper.PopulateIndexFromKey(index, key, s_TestScope, "TestArchitecture"); + + auto result = index.Search({}); + + REQUIRE(result.Matches.size() == 1); + VerifyEntryAgainstIndex(index, result.Matches[0].first, entry); +} + +TEST_CASE("ARPHelper_PopulateIndexFromKey_Two", "[arphelper][list]") +{ + auto root = RegCreateVolatileTestRoot(); + Registry::Key key(root.get()); + + ARPHelper helper; + + ARPEntry entry1("FirstEntry"); + entry1.DisplayName = "Test Name"; + entry1.DisplayVersion = "1.2"; + entry1.Publisher = "Test Publisher"; + entry1.InstallLocation = "TestLocation"; + entry1.UninstallString = "Test Uninstall"; + entry1.QuietUninstallString = "Test Quiet Uninstall"; + entry1.WindowsInstaller = true; + + ARPEntry entry2("SecondEntry"); + entry2.DisplayName = "Different Test Name"; + entry2.DisplayVersion = "31.4"; + entry2.Publisher = "Different Test Publisher"; + entry2.InstallLocation = "DifferentTestLocation"; + entry2.UninstallString = "Different Test Uninstall"; + entry2.QuietUninstallString = "Different Test Quiet Uninstall"; + + AddARPEntryToKey(root.get(), helper, entry1); + AddARPEntryToKey(root.get(), helper, entry2); + + auto index = CreateMemoryIndex(); + helper.PopulateIndexFromKey(index, key, s_TestScope, "TestArchitecture"); + + REQUIRE(index.Search({}).Matches.size() == 2); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, entry1.EntryName); + auto result = index.Search(request); + + REQUIRE(result.Matches.size() == 1); + VerifyEntryAgainstIndex(index, result.Matches[0].first, entry1); + + request.Query = RequestMatch(MatchType::Exact, entry2.EntryName); + result = index.Search(request); + + REQUIRE(result.Matches.size() == 1); + VerifyEntryAgainstIndex(index, result.Matches[0].first, entry2); +} + +TEST_CASE("PredefinedInstalledSource_Create", "[installed][list]") +{ + auto source = CreatePredefinedInstalledSource(); +} + +TEST_CASE("PredefinedInstalledSource_Search", "[installed][list]") +{ + auto source = CreatePredefinedInstalledSource(); + + SearchRequest request; + + auto results = source->Search(request); + + REQUIRE_FALSE(results.Matches.empty()); +} + +std::string GetDatabaseIdentifier(const std::shared_ptr& source) +{ + return reinterpret_cast(source->CastTo(ISourceType::SQLiteIndexSource))->GetIndex().GetDatabaseIdentifier(); +} + +void RequirePackagesHaveSameNames(std::shared_ptr& source1, std::shared_ptr& source2) +{ + auto result1 = source1->Search({}); + REQUIRE(!result1.Matches.empty()); + + // Ensure that all packages have the same name values + for (const auto& match : result1.Matches) + { + std::string packageId = match.Package->GetProperty(PackageProperty::Id).get(); + INFO(packageId); + + SearchRequest id2; + id2.Inclusions.emplace_back(PackageMatchFilter{ PackageMatchField::Id, MatchType::CaseInsensitive, packageId }); + auto result2 = source2->Search(id2); + REQUIRE(result2.Matches.size() == 1); + REQUIRE(match.Package->GetProperty(PackageProperty::Name) == result2.Matches[0].Package->GetProperty(PackageProperty::Name)); + } +} + +TEST_CASE("PredefinedInstalledSource_Create_Cached", "[installed][list][installed-cache]") +{ + auto source1 = CreatePredefinedInstalledSource(); + auto source2 = CreatePredefinedInstalledSource(); + + // Ensure the same identifier (which should mean the cache was not updated) + REQUIRE( + GetDatabaseIdentifier(source1) + == + GetDatabaseIdentifier(source2) + ); + + RequirePackagesHaveSameNames(source1, source2); + RequirePackagesHaveSameNames(source2, source1); +} + +TEST_CASE("PredefinedInstalledSource_Create_ForceCacheUpdate", "[installed][list][installed-cache]") +{ + auto source1 = CreatePredefinedInstalledSource(); + auto source2 = CreatePredefinedInstalledSource(Factory::Filter::NoneWithForcedCacheUpdate); + + // Ensure different identifier (which should mean the cache was updated) + REQUIRE( + GetDatabaseIdentifier(source1) + != + GetDatabaseIdentifier(source2) + ); + + RequirePackagesHaveSameNames(source1, source2); + RequirePackagesHaveSameNames(source2, source1); +} + +TEST_CASE("PredefinedInstalledSource_Create_ForceCacheUpdate_StillCached", "[installed][list][installed-cache]") +{ + auto source1 = CreatePredefinedInstalledSource(); + auto source2 = CreatePredefinedInstalledSource(Factory::Filter::NoneWithForcedCacheUpdate); + auto source3 = CreatePredefinedInstalledSource(); + + CAPTURE(GetDatabaseIdentifier(source1), GetDatabaseIdentifier(source2), GetDatabaseIdentifier(source3)); + + // Ensure different identifier (which should mean the cache was updated) + REQUIRE( + GetDatabaseIdentifier(source1) + != + GetDatabaseIdentifier(source2) + ); + + // Ensure the same identifier (which should mean the cache was not updated) + REQUIRE( + GetDatabaseIdentifier(source2) + == + GetDatabaseIdentifier(source3) + ); +} diff --git a/src/AppInstallerCLITests/PromptFlow.cpp b/src/AppInstallerCLITests/PromptFlow.cpp index c948cb95d2..f07e22c45d 100644 --- a/src/AppInstallerCLITests/PromptFlow.cpp +++ b/src/AppInstallerCLITests/PromptFlow.cpp @@ -1,163 +1,163 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::Settings; - -TEST_CASE("PromptFlow_InteractivityDisabled", "[PromptFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TestUserSettings testSettings; - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); - - SECTION("Disabled by setting") - { - testSettings.Set(true); - } - SECTION("Disabled by arg") - { - context.Args.AddArg(Execution::Args::Type::DisableInteractivity); - } - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify prompt is not shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsPrompt).get()) == std::string::npos); - - // Verify installation failed - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); - REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); -} - -TEST_CASE("PromptFlow_InstallerAbortsTerminal_Proceed", "[PromptFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - // Accept that the installer may abort the terminal by saying "Yes" at the prompt - std::istringstream installInput{ "y" }; - - std::ostringstream installOutput; - TestContext context{ installOutput, installInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_AbortsTerminal.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify prompt is shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerAbortsTerminal).get()) != std::string::npos); - - // Verify Installer is called. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); -} - -TEST_CASE("PromptFlow_InstallerAbortsTerminal_Cancel", "[PromptFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - // Cancel the installation by saying "No" at the prompt that the installer may abort the terminal - std::istringstream installInput{ "n" }; - - std::ostringstream installOutput; - TestContext context{ installOutput, installInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_AbortsTerminal.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify prompt is shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerAbortsTerminal).get()) != std::string::npos); - - // Verify installation failed - REQUIRE_TERMINATED_WITH(context, E_ABORT); - REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); -} - -TEST_CASE("PromptFlow_InstallLocationRequired", "[PromptFlow][workflow]") -{ - TestCommon::TempDirectory installLocation("TempDirectory"); - TestCommon::TestUserSettings testSettings; - - std::istringstream installInput; - std::ostringstream installOutput; - TestContext context{ installOutput, installInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallLocationRequired.yaml").GetPath().u8string()); - - bool shouldShowPrompt = false; - std::filesystem::path installResultPath = installLocation.GetPath() / "TestExeInstalled.txt"; - SECTION("From argument") - { - context.Args.AddArg(Execution::Args::Type::InstallLocation, installLocation.GetPath().string()); - } - SECTION("From settings") - { - testSettings.Set(installLocation.GetPath().string()); - - // When using the default location from settings, the Package ID is appended to the root - auto installLocationWithPackageId = installLocation.GetPath() / "AppInstallerCliTest.TestInstaller"; - std::filesystem::create_directory(installLocationWithPackageId); - installResultPath = installLocationWithPackageId / "TestExeInstalled.txt"; - } - SECTION("From prompt") - { - installInput.str(installLocation.GetPath().string()); - shouldShowPrompt = true; - } - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - bool promptShown = installOutput.str().find(Resource::LocString(Resource::String::InstallerRequiresInstallLocation).get()) != std::string::npos; - REQUIRE(shouldShowPrompt == promptShown); - - // Verify Installer is called with the right parameters - REQUIRE(std::filesystem::exists(installResultPath)); - std::ifstream installResultFile(installResultPath); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - const auto installDirArgument = "/InstallDir " + installLocation.GetPath().string(); - REQUIRE(installResultStr.find(installDirArgument) != std::string::npos); -} - -TEST_CASE("PromptFlow_InstallLocationRequired_Missing", "[PromptFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallLocationRequired.yaml").GetPath().u8string()); - // Disable interactivity so that there is not prompt and we cannot get the required location - context.Args.AddArg(Execution::Args::Type::DisableInteractivity); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify prompt is shown - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerRequiresInstallLocation).get()) != std::string::npos); - - // Verify installation failed - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED); - REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::Settings; + +TEST_CASE("PromptFlow_InteractivityDisabled", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_LicenseAgreement.yaml").GetPath().u8string()); + + SECTION("Disabled by setting") + { + testSettings.Set(true); + } + SECTION("Disabled by arg") + { + context.Args.AddArg(Execution::Args::Type::DisableInteractivity); + } + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is not shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsPrompt).get()) == std::string::npos); + + // Verify installation failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); + REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); +} + +TEST_CASE("PromptFlow_InstallerAbortsTerminal_Proceed", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + // Accept that the installer may abort the terminal by saying "Yes" at the prompt + std::istringstream installInput{ "y" }; + + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_AbortsTerminal.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerAbortsTerminal).get()) != std::string::npos); + + // Verify Installer is called. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("PromptFlow_InstallerAbortsTerminal_Cancel", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + // Cancel the installation by saying "No" at the prompt that the installer may abort the terminal + std::istringstream installInput{ "n" }; + + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_AbortsTerminal.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerAbortsTerminal).get()) != std::string::npos); + + // Verify installation failed + REQUIRE_TERMINATED_WITH(context, E_ABORT); + REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); +} + +TEST_CASE("PromptFlow_InstallLocationRequired", "[PromptFlow][workflow]") +{ + TestCommon::TempDirectory installLocation("TempDirectory"); + TestCommon::TestUserSettings testSettings; + + std::istringstream installInput; + std::ostringstream installOutput; + TestContext context{ installOutput, installInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallLocationRequired.yaml").GetPath().u8string()); + + bool shouldShowPrompt = false; + std::filesystem::path installResultPath = installLocation.GetPath() / "TestExeInstalled.txt"; + SECTION("From argument") + { + context.Args.AddArg(Execution::Args::Type::InstallLocation, installLocation.GetPath().string()); + } + SECTION("From settings") + { + testSettings.Set(installLocation.GetPath().string()); + + // When using the default location from settings, the Package ID is appended to the root + auto installLocationWithPackageId = installLocation.GetPath() / "AppInstallerCliTest.TestInstaller"; + std::filesystem::create_directory(installLocationWithPackageId); + installResultPath = installLocationWithPackageId / "TestExeInstalled.txt"; + } + SECTION("From prompt") + { + installInput.str(installLocation.GetPath().string()); + shouldShowPrompt = true; + } + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + bool promptShown = installOutput.str().find(Resource::LocString(Resource::String::InstallerRequiresInstallLocation).get()) != std::string::npos; + REQUIRE(shouldShowPrompt == promptShown); + + // Verify Installer is called with the right parameters + REQUIRE(std::filesystem::exists(installResultPath)); + std::ifstream installResultFile(installResultPath); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + const auto installDirArgument = "/InstallDir " + installLocation.GetPath().string(); + REQUIRE(installResultStr.find(installDirArgument) != std::string::npos); +} + +TEST_CASE("PromptFlow_InstallLocationRequired_Missing", "[PromptFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_InstallLocationRequired.yaml").GetPath().u8string()); + // Disable interactivity so that there is not prompt and we cannot get the required location + context.Args.AddArg(Execution::Args::Type::DisableInteractivity); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify prompt is shown + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::InstallerRequiresInstallLocation).get()) != std::string::npos); + + // Verify installation failed + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED); + REQUIRE_FALSE(std::filesystem::exists(installResultPath.GetPath())); +} diff --git a/src/AppInstallerCLITests/Regex.cpp b/src/AppInstallerCLITests/Regex.cpp index 974c800682..1fb140cb39 100644 --- a/src/AppInstallerCLITests/Regex.cpp +++ b/src/AppInstallerCLITests/Regex.cpp @@ -1,86 +1,86 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace std::string_view_literals; -using namespace AppInstaller::Regex; - - -TEST_CASE("Regex_Construction", "[regex]") -{ - Expression empty; - REQUIRE(!empty); - - Expression lowerVowels("(a|e|i|o|u)"); - REQUIRE(lowerVowels); - - // Ensure functionality to later verify against copy - REQUIRE(lowerVowels.IsMatch(L"a")); - REQUIRE(!lowerVowels.IsMatch(L"b")); - - Expression copy = lowerVowels; - REQUIRE(copy); - - // Ensure that the copy can also work - REQUIRE(lowerVowels.IsMatch(L"a")); - REQUIRE(copy.IsMatch(L"a")); - - REQUIRE(!lowerVowels.IsMatch(L"b")); - REQUIRE(!copy.IsMatch(L"b")); - - Expression moved = std::move(copy); - REQUIRE(moved); - - REQUIRE(moved.IsMatch(L"a")); - REQUIRE(!moved.IsMatch(L"b")); -} - -TEST_CASE("Regex_IsMatch", "[regex]") -{ - Expression ArchitectureX32{ R"((X32|X86)(?=\P{Nd}|$)(?:\sEDITION)?)", Options::CaseInsensitive }; - - REQUIRE(ArchitectureX32.IsMatch(L"X32")); - REQUIRE(ArchitectureX32.IsMatch(L"X86 edition")); - - REQUIRE(!ArchitectureX32.IsMatch(L"Not a match")); - REQUIRE(!ArchitectureX32.IsMatch(L"X86 editions")); -} - -TEST_CASE("Regex_Replace", "[regex]") -{ - Expression test{ R"((b|d\s|vy))" }; - REQUIRE(test.Replace(L"The bright and swervy", {}) == std::wstring{ L"The right answer" }); - - Expression vowels{ "(a|e|i|o|u)", Options::CaseInsensitive }; - REQUIRE(vowels.Replace(L"The QUICK brown fox jumped over the lazy dog.", L"[$0]") == std::wstring{ L"Th[e] Q[U][I]CK br[o]wn f[o]x j[u]mp[e]d [o]v[e]r th[e] l[a]zy d[o]g." }); -} - -TEST_CASE("Regex_ForEach", "[regex]") -{ - std::wstring input = L"The words in the Sentence but no more"; - std::vector expected = { L"The", L"words", L"in", L"the", L"Sentence" }; - - Expression test{ R"(\S+)" }; - - size_t i = 0; - test.ForEach(input, - [&](bool isMatch, std::wstring_view text) - { - if (!isMatch) - { - REQUIRE(text == L" "); - return true; - } - else - { - REQUIRE(i < expected.size()); - REQUIRE(text == expected[i]); - ++i; - - return i < expected.size(); - } - }); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace std::string_view_literals; +using namespace AppInstaller::Regex; + + +TEST_CASE("Regex_Construction", "[regex]") +{ + Expression empty; + REQUIRE(!empty); + + Expression lowerVowels("(a|e|i|o|u)"); + REQUIRE(lowerVowels); + + // Ensure functionality to later verify against copy + REQUIRE(lowerVowels.IsMatch(L"a")); + REQUIRE(!lowerVowels.IsMatch(L"b")); + + Expression copy = lowerVowels; + REQUIRE(copy); + + // Ensure that the copy can also work + REQUIRE(lowerVowels.IsMatch(L"a")); + REQUIRE(copy.IsMatch(L"a")); + + REQUIRE(!lowerVowels.IsMatch(L"b")); + REQUIRE(!copy.IsMatch(L"b")); + + Expression moved = std::move(copy); + REQUIRE(moved); + + REQUIRE(moved.IsMatch(L"a")); + REQUIRE(!moved.IsMatch(L"b")); +} + +TEST_CASE("Regex_IsMatch", "[regex]") +{ + Expression ArchitectureX32{ R"((X32|X86)(?=\P{Nd}|$)(?:\sEDITION)?)", Options::CaseInsensitive }; + + REQUIRE(ArchitectureX32.IsMatch(L"X32")); + REQUIRE(ArchitectureX32.IsMatch(L"X86 edition")); + + REQUIRE(!ArchitectureX32.IsMatch(L"Not a match")); + REQUIRE(!ArchitectureX32.IsMatch(L"X86 editions")); +} + +TEST_CASE("Regex_Replace", "[regex]") +{ + Expression test{ R"((b|d\s|vy))" }; + REQUIRE(test.Replace(L"The bright and swervy", {}) == std::wstring{ L"The right answer" }); + + Expression vowels{ "(a|e|i|o|u)", Options::CaseInsensitive }; + REQUIRE(vowels.Replace(L"The QUICK brown fox jumped over the lazy dog.", L"[$0]") == std::wstring{ L"Th[e] Q[U][I]CK br[o]wn f[o]x j[u]mp[e]d [o]v[e]r th[e] l[a]zy d[o]g." }); +} + +TEST_CASE("Regex_ForEach", "[regex]") +{ + std::wstring input = L"The words in the Sentence but no more"; + std::vector expected = { L"The", L"words", L"in", L"the", L"Sentence" }; + + Expression test{ R"(\S+)" }; + + size_t i = 0; + test.ForEach(input, + [&](bool isMatch, std::wstring_view text) + { + if (!isMatch) + { + REQUIRE(text == L" "); + return true; + } + else + { + REQUIRE(i < expected.size()); + REQUIRE(text == expected[i]); + ++i; + + return i < expected.size(); + } + }); +} diff --git a/src/AppInstallerCLITests/Registry.cpp b/src/AppInstallerCLITests/Registry.cpp index c908d99f9c..f859ec6923 100644 --- a/src/AppInstallerCLITests/Registry.cpp +++ b/src/AppInstallerCLITests/Registry.cpp @@ -1,215 +1,215 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace AppInstaller::Registry; -using namespace AppInstaller::Utility; -using namespace TestCommon; - -TEST_CASE("EmptyKey", "[registry]") -{ - Key key; - REQUIRE(!key); -} - -TEST_CASE("Constructor_NotFound", "[registry]") -{ - Key key; - REQUIRE_THROWS_HR(key = Key(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Foo\\Bar\\Does\\Not\\Exist"), HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); -} - -TEST_CASE("OpenIfExists_NotFound", "[registry]") -{ - Key key = Key::OpenIfExists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Foo\\Bar\\Does\\Not\\Exist"); - REQUIRE(!key); -} - -TEST_CASE("CreateKeyAndDelete", "[registry]") -{ - std::wstring subkey = L"Foo\\Bar"; - wil::unique_hkey root = RegCreateVolatileTestRoot(); - Key key = Key::Create(root.get(), subkey, REG_OPTION_VOLATILE); - REQUIRE(key); - Key::Delete(root.get(), subkey, KEY_WOW64_64KEY); - Key secondKey = Key::OpenIfExists(root.get(), subkey); - REQUIRE(!secondKey); -} - -TEST_CASE("SetKeyValue", "[registry]") -{ - std::wstring valueName = L"TestValueName"; - std::wstring valueValue = L"TestValueValue"; - std::wstring subkey = L"FooBar"; - - wil::unique_hkey root = RegCreateVolatileTestRoot(); - Key key = Key::Create(root.get(), subkey, REG_OPTION_VOLATILE); - key.SetValue(valueName, valueValue, REG_SZ); - auto value = key[valueName]; - REQUIRE(value); - REQUIRE(value->GetType() == Value::Type::String); - REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); -} - -TEST_CASE("DeleteKeyValue", "[registry]") -{ - std::wstring valueName = L"TestValueName"; - std::wstring valueValue = L"TestValueValue"; - std::wstring subkey = L"FooBar"; - - wil::unique_hkey root = RegCreateVolatileTestRoot(); - Key key = Key::Create(root.get(), subkey, REG_OPTION_VOLATILE); - - key.SetValue(valueName, valueValue, REG_SZ); - auto value = key[valueName]; - REQUIRE(value); - - key.DeleteValue(valueName); - value = key[valueName]; - REQUIRE(!value); -} - -TEST_CASE("EnumerateKeys", "[registry]") -{ - wil::unique_hkey root = RegCreateVolatileTestRoot(); - - std::vector subKeyNames = { L"A", L"BEE", L"SEE", L"deigh" }; - for (const auto& name : subKeyNames) - { - RegCreateVolatileSubKey(root.get(), name); - } - - Key key{ root.get(), L"" }; - - for (const auto& subkey : key) - { - INFO(subkey.Name()); - - std::wstring nameUtf16 = ConvertToUTF16(subkey.Name()); - - auto itr = std::find(subKeyNames.begin(), subKeyNames.end(), nameUtf16); - if (itr == subKeyNames.end()) - { - FAIL(); - } - else - { - subKeyNames.erase(itr); - } - - Key sk = subkey.Open(); - REQUIRE(sk); - } - - REQUIRE(subKeyNames.empty()); -} - -TEST_CASE("Values_String", "[registry]") -{ - std::wstring valueName = L"TestValueName"; - std::wstring valueValue = L"TestValueValue"; - - wil::unique_hkey root = RegCreateVolatileTestRoot(); - SetRegistryValue(root.get(), valueName, valueValue); - - Key key{ root.get(), L"" }; - - auto value = key[valueName]; - REQUIRE(value); - REQUIRE(value->GetType() == Value::Type::String); - REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); -} - -TEST_CASE("Values_WideStringWithNarrowNull", "[registry]") -{ - std::wstring valueName = L"TestValueName"; - std::wstring valueValue = L"TestValueValue"; - - wil::unique_hkey root = RegCreateVolatileTestRoot(); - - // Copy the bytes from the string value into a byte vector - std::vector valueBytes; - valueBytes.resize((valueValue.length() + 1) * sizeof(wchar_t)); - memcpy_s(valueBytes.data(), valueBytes.size(), valueValue.c_str(), (valueValue.length() + 1) * sizeof(wchar_t)); - // Remove the last byte to make a narrow null - valueBytes.resize(valueBytes.size() - 1); - - SetRegistryValue(root.get(), valueName, valueBytes, REG_SZ); - - Key key{ root.get(), L"" }; - - auto value = key[valueName]; - REQUIRE(value); - REQUIRE(value->GetType() == Value::Type::String); - REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); -} - -TEST_CASE("Values_ExpandString", "[registry]") -{ - std::wstring valueName = L"TestValueName"; - std::wstring valueValue = L"%TEMP%"; - - wil::unique_hkey root = RegCreateVolatileTestRoot(); - SetRegistryValue(root.get(), valueName, valueValue, REG_EXPAND_SZ); - - Key key{ root.get(), L"" }; - - auto value = key[valueName]; - REQUIRE(value); - REQUIRE(value->GetType() == Value::Type::ExpandString); - REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); - - wchar_t buffer[MAX_PATH]; - GetTempPathW(ARRAYSIZE(buffer), buffer); - - std::string tempPath = ConvertToUTF8(buffer); - if (!tempPath.empty() && tempPath.back() == '\\') - { - tempPath.resize(tempPath.size() - 1); - } - - REQUIRE(value->GetValue() == tempPath); -} - -TEST_CASE("Values_Binary", "[registry]") -{ - std::wstring valueName = L"TestValueName"; - std::vector valueValue = { 2, 7, 3, 14, 42 }; - - wil::unique_hkey root = RegCreateVolatileTestRoot(); - SetRegistryValue(root.get(), valueName, valueValue); - - Key key{ root.get(), L"" }; - - auto value = key[valueName]; - REQUIRE(value); - REQUIRE(value->GetType() == Value::Type::Binary); - - auto result = value->GetValue(); - REQUIRE(result.size() == valueValue.size()); - for (size_t i = 0; i < result.size(); ++i) - { - INFO(i); - REQUIRE(result[i] == valueValue[i]); - } -} - -TEST_CASE("Values_DWORD", "[registry]") -{ - std::wstring valueName = L"TestValueName"; - DWORD valueValue = 42; - - wil::unique_hkey root = RegCreateVolatileTestRoot(); - SetRegistryValue(root.get(), valueName, valueValue); - - Key key{ root.get(), L"" }; - - auto value = key[valueName]; - REQUIRE(value); - REQUIRE(value->GetType() == Value::Type::DWord); - REQUIRE(value->GetValue() == valueValue); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace AppInstaller::Registry; +using namespace AppInstaller::Utility; +using namespace TestCommon; + +TEST_CASE("EmptyKey", "[registry]") +{ + Key key; + REQUIRE(!key); +} + +TEST_CASE("Constructor_NotFound", "[registry]") +{ + Key key; + REQUIRE_THROWS_HR(key = Key(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Foo\\Bar\\Does\\Not\\Exist"), HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND)); +} + +TEST_CASE("OpenIfExists_NotFound", "[registry]") +{ + Key key = Key::OpenIfExists(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Foo\\Bar\\Does\\Not\\Exist"); + REQUIRE(!key); +} + +TEST_CASE("CreateKeyAndDelete", "[registry]") +{ + std::wstring subkey = L"Foo\\Bar"; + wil::unique_hkey root = RegCreateVolatileTestRoot(); + Key key = Key::Create(root.get(), subkey, REG_OPTION_VOLATILE); + REQUIRE(key); + Key::Delete(root.get(), subkey, KEY_WOW64_64KEY); + Key secondKey = Key::OpenIfExists(root.get(), subkey); + REQUIRE(!secondKey); +} + +TEST_CASE("SetKeyValue", "[registry]") +{ + std::wstring valueName = L"TestValueName"; + std::wstring valueValue = L"TestValueValue"; + std::wstring subkey = L"FooBar"; + + wil::unique_hkey root = RegCreateVolatileTestRoot(); + Key key = Key::Create(root.get(), subkey, REG_OPTION_VOLATILE); + key.SetValue(valueName, valueValue, REG_SZ); + auto value = key[valueName]; + REQUIRE(value); + REQUIRE(value->GetType() == Value::Type::String); + REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); +} + +TEST_CASE("DeleteKeyValue", "[registry]") +{ + std::wstring valueName = L"TestValueName"; + std::wstring valueValue = L"TestValueValue"; + std::wstring subkey = L"FooBar"; + + wil::unique_hkey root = RegCreateVolatileTestRoot(); + Key key = Key::Create(root.get(), subkey, REG_OPTION_VOLATILE); + + key.SetValue(valueName, valueValue, REG_SZ); + auto value = key[valueName]; + REQUIRE(value); + + key.DeleteValue(valueName); + value = key[valueName]; + REQUIRE(!value); +} + +TEST_CASE("EnumerateKeys", "[registry]") +{ + wil::unique_hkey root = RegCreateVolatileTestRoot(); + + std::vector subKeyNames = { L"A", L"BEE", L"SEE", L"deigh" }; + for (const auto& name : subKeyNames) + { + RegCreateVolatileSubKey(root.get(), name); + } + + Key key{ root.get(), L"" }; + + for (const auto& subkey : key) + { + INFO(subkey.Name()); + + std::wstring nameUtf16 = ConvertToUTF16(subkey.Name()); + + auto itr = std::find(subKeyNames.begin(), subKeyNames.end(), nameUtf16); + if (itr == subKeyNames.end()) + { + FAIL(); + } + else + { + subKeyNames.erase(itr); + } + + Key sk = subkey.Open(); + REQUIRE(sk); + } + + REQUIRE(subKeyNames.empty()); +} + +TEST_CASE("Values_String", "[registry]") +{ + std::wstring valueName = L"TestValueName"; + std::wstring valueValue = L"TestValueValue"; + + wil::unique_hkey root = RegCreateVolatileTestRoot(); + SetRegistryValue(root.get(), valueName, valueValue); + + Key key{ root.get(), L"" }; + + auto value = key[valueName]; + REQUIRE(value); + REQUIRE(value->GetType() == Value::Type::String); + REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); +} + +TEST_CASE("Values_WideStringWithNarrowNull", "[registry]") +{ + std::wstring valueName = L"TestValueName"; + std::wstring valueValue = L"TestValueValue"; + + wil::unique_hkey root = RegCreateVolatileTestRoot(); + + // Copy the bytes from the string value into a byte vector + std::vector valueBytes; + valueBytes.resize((valueValue.length() + 1) * sizeof(wchar_t)); + memcpy_s(valueBytes.data(), valueBytes.size(), valueValue.c_str(), (valueValue.length() + 1) * sizeof(wchar_t)); + // Remove the last byte to make a narrow null + valueBytes.resize(valueBytes.size() - 1); + + SetRegistryValue(root.get(), valueName, valueBytes, REG_SZ); + + Key key{ root.get(), L"" }; + + auto value = key[valueName]; + REQUIRE(value); + REQUIRE(value->GetType() == Value::Type::String); + REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); +} + +TEST_CASE("Values_ExpandString", "[registry]") +{ + std::wstring valueName = L"TestValueName"; + std::wstring valueValue = L"%TEMP%"; + + wil::unique_hkey root = RegCreateVolatileTestRoot(); + SetRegistryValue(root.get(), valueName, valueValue, REG_EXPAND_SZ); + + Key key{ root.get(), L"" }; + + auto value = key[valueName]; + REQUIRE(value); + REQUIRE(value->GetType() == Value::Type::ExpandString); + REQUIRE(value->GetValue() == ConvertToUTF8(valueValue)); + + wchar_t buffer[MAX_PATH]; + GetTempPathW(ARRAYSIZE(buffer), buffer); + + std::string tempPath = ConvertToUTF8(buffer); + if (!tempPath.empty() && tempPath.back() == '\\') + { + tempPath.resize(tempPath.size() - 1); + } + + REQUIRE(value->GetValue() == tempPath); +} + +TEST_CASE("Values_Binary", "[registry]") +{ + std::wstring valueName = L"TestValueName"; + std::vector valueValue = { 2, 7, 3, 14, 42 }; + + wil::unique_hkey root = RegCreateVolatileTestRoot(); + SetRegistryValue(root.get(), valueName, valueValue); + + Key key{ root.get(), L"" }; + + auto value = key[valueName]; + REQUIRE(value); + REQUIRE(value->GetType() == Value::Type::Binary); + + auto result = value->GetValue(); + REQUIRE(result.size() == valueValue.size()); + for (size_t i = 0; i < result.size(); ++i) + { + INFO(i); + REQUIRE(result[i] == valueValue[i]); + } +} + +TEST_CASE("Values_DWORD", "[registry]") +{ + std::wstring valueName = L"TestValueName"; + DWORD valueValue = 42; + + wil::unique_hkey root = RegCreateVolatileTestRoot(); + SetRegistryValue(root.get(), valueName, valueValue); + + Key key{ root.get(), L"" }; + + auto value = key[valueName]; + REQUIRE(value); + REQUIRE(value->GetType() == Value::Type::DWord); + REQUIRE(value->GetValue() == valueValue); +} diff --git a/src/AppInstallerCLITests/Rest.cpp b/src/AppInstallerCLITests/Rest.cpp index 8bb5a83d61..02db7d8fc2 100644 --- a/src/AppInstallerCLITests/Rest.cpp +++ b/src/AppInstallerCLITests/Rest.cpp @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "cpprest/json.h" -#include - -using namespace AppInstaller::Rest; - -TEST_CASE("ValidateAndGetRestAPIBaseUri", "[RestSource]") -{ - REQUIRE(GetRestAPIBaseUri("https://restsource.azurewebsites.net/api/ ") == L"https://restsource.azurewebsites.net/api"); - REQUIRE(GetRestAPIBaseUri("http://rest_sourc e.azurewebsites.net/api") == L"http://rest_sourc%20e.azurewebsites.net/api"); - REQUIRE(GetRestAPIBaseUri("http://restsource.azurewebsites.net/v1.0/%v1") == L"http://restsource.azurewebsites.net/v1.0/%25v1"); -} - -TEST_CASE("IsValidUri", "[RestSource]") -{ - REQUIRE(IsValidUri(L"http://rest%20source.azurewebsites.net/api")); - REQUIRE(!IsValidUri(L"http://rest source.azurewebsites.net/api")); -} - -TEST_CASE("AppendPathToUri", "[RestSource]") -{ - REQUIRE(AppendPathToUri(L"http://restsource.azurewebsites.net/api/", L"/path") == L"http://restsource.azurewebsites.net/api/path"); - REQUIRE(AppendPathToUri(L"http://restsource.azurewebsites.net/api/", L"/pat h") == L"http://restsource.azurewebsites.net/api/pat%20%20h"); - REQUIRE(AppendPathToUri(L"http://restsource.azurewebsites.net/api/", L"/path+version") == L"http://restsource.azurewebsites.net/api/path%2Bversion"); -} - -TEST_CASE("AppendQueryParamsToUri", "[RestSource]") -{ - utility::string_t url = L"http://restsource.azurewebsites.net/api"; - std::map queryParams; - queryParams.emplace("Version", "1.0 .0"); - queryParams.emplace("Channel", "beta+"); - - REQUIRE(AppendQueryParamsToUri(url, queryParams) == L"http://restsource.azurewebsites.net/api?Channel=beta%2B&Version=1.0%20.0"); -} - -TEST_CASE("GetUniqueItems", "[RestSource]") -{ - std::vector list; - REQUIRE(GetUniqueItems(list).size() == 0); - - std::vector listWithDuplicates; - listWithDuplicates.emplace_back("object1"); - listWithDuplicates.emplace_back("object1"); - listWithDuplicates.emplace_back("object2"); - listWithDuplicates.emplace_back("object2"); - listWithDuplicates.emplace_back("object3"); - std::vector result = GetUniqueItems(listWithDuplicates); - REQUIRE(result.size() == 3); - REQUIRE(result.at(0) == "object1"); - REQUIRE(result.at(1) == "object2"); - REQUIRE(result.at(2) == "object3"); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "cpprest/json.h" +#include + +using namespace AppInstaller::Rest; + +TEST_CASE("ValidateAndGetRestAPIBaseUri", "[RestSource]") +{ + REQUIRE(GetRestAPIBaseUri("https://restsource.azurewebsites.net/api/ ") == L"https://restsource.azurewebsites.net/api"); + REQUIRE(GetRestAPIBaseUri("http://rest_sourc e.azurewebsites.net/api") == L"http://rest_sourc%20e.azurewebsites.net/api"); + REQUIRE(GetRestAPIBaseUri("http://restsource.azurewebsites.net/v1.0/%v1") == L"http://restsource.azurewebsites.net/v1.0/%25v1"); +} + +TEST_CASE("IsValidUri", "[RestSource]") +{ + REQUIRE(IsValidUri(L"http://rest%20source.azurewebsites.net/api")); + REQUIRE(!IsValidUri(L"http://rest source.azurewebsites.net/api")); +} + +TEST_CASE("AppendPathToUri", "[RestSource]") +{ + REQUIRE(AppendPathToUri(L"http://restsource.azurewebsites.net/api/", L"/path") == L"http://restsource.azurewebsites.net/api/path"); + REQUIRE(AppendPathToUri(L"http://restsource.azurewebsites.net/api/", L"/pat h") == L"http://restsource.azurewebsites.net/api/pat%20%20h"); + REQUIRE(AppendPathToUri(L"http://restsource.azurewebsites.net/api/", L"/path+version") == L"http://restsource.azurewebsites.net/api/path%2Bversion"); +} + +TEST_CASE("AppendQueryParamsToUri", "[RestSource]") +{ + utility::string_t url = L"http://restsource.azurewebsites.net/api"; + std::map queryParams; + queryParams.emplace("Version", "1.0 .0"); + queryParams.emplace("Channel", "beta+"); + + REQUIRE(AppendQueryParamsToUri(url, queryParams) == L"http://restsource.azurewebsites.net/api?Channel=beta%2B&Version=1.0%20.0"); +} + +TEST_CASE("GetUniqueItems", "[RestSource]") +{ + std::vector list; + REQUIRE(GetUniqueItems(list).size() == 0); + + std::vector listWithDuplicates; + listWithDuplicates.emplace_back("object1"); + listWithDuplicates.emplace_back("object1"); + listWithDuplicates.emplace_back("object2"); + listWithDuplicates.emplace_back("object2"); + listWithDuplicates.emplace_back("object3"); + std::vector result = GetUniqueItems(listWithDuplicates); + REQUIRE(result.size() == 3); + REQUIRE(result.at(0) == "object1"); + REQUIRE(result.at(1) == "object2"); + REQUIRE(result.at(2) == "object3"); +} diff --git a/src/AppInstallerCLITests/RestClient.cpp b/src/AppInstallerCLITests/RestClient.cpp index 46de28a7df..cb40b602a1 100644 --- a/src/AppInstallerCLITests/RestClient.cpp +++ b/src/AppInstallerCLITests/RestClient.cpp @@ -1,671 +1,671 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Repository::Rest; -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"} }; - std::vector versions{ "1.0.0", "2.0.0", "1.2.0" }; - std::optional actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); - REQUIRE(actual); - REQUIRE(actual.value().ToString() == "1.2.0"); -} - -TEST_CASE("GetLatestCommonVersion_OnlyMajorMinorVersionMatched", "[RestSource]") -{ - std::set wingetSupportedContracts = { Version {"1.0.0"}, Version {"1.2.0"} }; - std::vector versions{ "1.0.0", "2.0.0", "1.2.1" }; - std::optional actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); - REQUIRE(actual); - REQUIRE(actual.value().ToString() == "1.2.0"); -} - -TEST_CASE("GetLatestCommonVersion_UnsupportedVersion", "[RestSource]") -{ - std::set wingetSupportedContracts = { Version {"3.0.0"}, Version {"4.2.0"} }; - std::vector versions{ "1.0.0", "2.0.0" }; - std::optional actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); - REQUIRE(!actual); -} - -TEST_CASE("GetSupportedInterface", "[RestSource]") -{ - IRestClient::Information info{ "TestId", { "1.0.0" } }; - - Version version{ "1.0.0" }; - REQUIRE(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, version, {})->GetVersion() == version); - - // Update this test to next version so that we don't forget to add to supported versions before rest e2e tests are available. - Version invalid{ "1.29.0" }; - REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, invalid, {}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); - - Authentication::AuthenticationArguments authArgs; - authArgs.Mode = Authentication::AuthenticationMode::Silent; - Version version_1_7{ "1.7.0" }; - - // GetSupportedInterface throws on unknown authentication type. - IRestClient::Information infoWithUnknownAuthenticationType{ "TestId", { "1.7.0" } }; - infoWithUnknownAuthenticationType.Authentication.Type = Authentication::AuthenticationType::Unknown; - REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithUnknownAuthenticationType, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); - - // GetSupportedInterface throws on invalid authentication info. - IRestClient::Information infoWithInvalidAuthenticationInfo{ "TestId", { "1.7.0" } }; - infoWithInvalidAuthenticationInfo.Authentication.Type = Authentication::AuthenticationType::MicrosoftEntraId; - REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithInvalidAuthenticationInfo, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO); -} - -TEST_CASE("GetInformation_Success", "[RestSource]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.0.0", - "1.1.0" - ], - "SourceAgreements": { - "AgreementsIdentifier": "agreementV1", - "Agreements": [{ - "AgreementLabel": "EULA", - "Agreement": "this is store agreement", - "AgreementUrl": "https://store.agreement" - } - ] - }, - "RequiredQueryParameters": [ - "Market" - ], - "RequiredPackageMatchFields": [ - "Market" - ], - "UnsupportedQueryParameters": [ - "Moniker" - ], - "UnsupportedPackageMatchFields": [ - "Moniker" - ] - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); - REQUIRE(information.SourceIdentifier == "Source123"); - REQUIRE(information.ServerSupportedVersions.size() == 2); - REQUIRE(information.ServerSupportedVersions.at(0) == "1.0.0"); - REQUIRE(information.ServerSupportedVersions.at(1) == "1.1.0"); - REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); - REQUIRE(information.SourceAgreements.size() == 1); - REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); - REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); - REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); - REQUIRE(information.RequiredQueryParameters.size() == 1); - REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); - REQUIRE(information.RequiredPackageMatchFields.size() == 1); - REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); - REQUIRE(information.UnsupportedQueryParameters.size() == 1); - REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); - REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); - REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); - REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::None); - REQUIRE_FALSE(information.Authentication.MicrosoftEntraIdInfo.has_value()); -} - -TEST_CASE("GetInformation_WithAuthenticationInfo_Success", "[RestSource]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.7.0" - ], - "SourceAgreements": { - "AgreementsIdentifier": "agreementV1", - "Agreements": [{ - "AgreementLabel": "EULA", - "Agreement": "this is store agreement", - "AgreementUrl": "https://store.agreement" - } - ] - }, - "RequiredQueryParameters": [ - "Market" - ], - "RequiredPackageMatchFields": [ - "Market" - ], - "UnsupportedQueryParameters": [ - "Moniker" - ], - "UnsupportedPackageMatchFields": [ - "Moniker" - ], - "Authentication": { - "AuthenticationType": "microsoftEntraId", - "MicrosoftEntraIdAuthenticationInfo" : { - "Resource": "GUID", - "Scope" : "test" - } - } - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); - REQUIRE(information.SourceIdentifier == "Source123"); - REQUIRE(information.ServerSupportedVersions.size() == 1); - REQUIRE(information.ServerSupportedVersions.at(0) == "1.7.0"); - REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); - REQUIRE(information.SourceAgreements.size() == 1); - REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); - REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); - REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); - REQUIRE(information.RequiredQueryParameters.size() == 1); - REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); - REQUIRE(information.RequiredPackageMatchFields.size() == 1); - REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); - REQUIRE(information.UnsupportedQueryParameters.size() == 1); - REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); - REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); - REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); - REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId); - REQUIRE(information.Authentication.MicrosoftEntraIdInfo.has_value()); - REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Resource == "GUID"); - REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Scope == "test"); -} - -TEST_CASE("GetInformation_Fail_AgreementsWithoutIdentifier", "[RestSource]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.0.0", - "1.1.0"], - "SourceAgreements": { - "Agreements": [{ - "AgreementLabel": "EULA", - "Agreement": "this is store agreement", - "AgreementUrl": "https://store.agreement" - } - ] - } - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); -} - -TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") -{ - utility::string_t sample1 = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.7.0" - ], - "Authentication": { - "AuthenticationType": "microsoftEntraId" - } - }})delimiter"); - - HttpClientHelper helper1{ GetTestRestRequestHandler(web::http::status_codes::OK, sample1) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper1), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); - - utility::string_t sample2 = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.7.0" - ], - "Authentication": { - "AuthenticationType": "microsoftEntraId", - "MicrosoftEntraIdAuthenticationInfo" : { - "Resource": "", - "Scope" : "test" - } - } - }})delimiter"); - - HttpClientHelper helper2{ GetTestRestRequestHandler(web::http::status_codes::OK, sample2) }; - REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper2), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); - - utility::string_t sample3 = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.7.0" - ], - "Authentication": { - "AuthenticationType": "microsoftEntraId", - "MicrosoftEntraIdAuthenticationInfo" : { - "Scope" : "test" - } - } - }})delimiter"); - - HttpClientHelper helper3{ GetTestRestRequestHandler(web::http::status_codes::OK, sample3) }; - 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]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.2.0", - "2.0.0"] - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); -} - -TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.7.0" - ], - "Authentication": { - "AuthenticationType": "unknown" - } - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - Authentication::AuthenticationArguments authArgs; - authArgs.Mode = Authentication::AuthenticationMode::Silent; - REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); -} - -TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.7.0" - ], - "Authentication": { - "AuthenticationType": "microsoftEntraId", - "MicrosoftEntraIdAuthenticationInfo" : { - "Resource" : "test" - } - } - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - Authentication::AuthenticationArguments authArgs; - authArgs.Mode = Authentication::AuthenticationMode::Unknown; - REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), E_UNEXPECTED); -} - -TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.0.0", - "2.0.0"] - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); - REQUIRE(client.GetSourceIdentifier() == "Source123"); -} - -TEST_CASE("RestClientCreate_1.1_Success", "[RestSource]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.0.0", - "1.1.0"], - "SourceAgreements": { - "AgreementsIdentifier": "agreementV1", - "Agreements": [{ - "AgreementLabel": "EULA", - "Agreement": "this is store agreement", - "AgreementUrl": "https://store.agreement" - } - ] - }, - "RequiredQueryParameters": [ - "Market" - ], - "RequiredPackageMatchFields": [ - "Market" - ], - "UnsupportedQueryParameters": [ - "Moniker" - ], - "UnsupportedPackageMatchFields": [ - "Moniker" - ] - }})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); - REQUIRE(client.GetSourceIdentifier() == "Source123"); - auto information = client.GetSourceInformation(); - REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); - REQUIRE(information.SourceAgreements.size() == 1); - REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); - REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); - REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); - REQUIRE(information.RequiredQueryParameters.size() == 1); - REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); - REQUIRE(information.RequiredPackageMatchFields.size() == 1); - REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); - REQUIRE(information.UnsupportedQueryParameters.size() == 1); - REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); - REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); - REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); -} - -TEST_CASE("RestClientCreate_1.7_Success", "[RestSource]") -{ - if (Runtime::IsRunningAsSystem()) - { - WARN("Test does not support running as system. Skipped."); - return; - } - - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : { - "SourceIdentifier": "Source123", - "ServerSupportedVersions": [ - "1.7.0" - ], - "SourceAgreements": { - "AgreementsIdentifier": "agreementV1", - "Agreements": [{ - "AgreementLabel": "EULA", - "Agreement": "this is store agreement", - "AgreementUrl": "https://store.agreement" - } - ] - }, - "RequiredQueryParameters": [ - "Market" - ], - "RequiredPackageMatchFields": [ - "Market" - ], - "UnsupportedQueryParameters": [ - "Moniker" - ], - "UnsupportedPackageMatchFields": [ - "Moniker" - ], - "Authentication": { - "AuthenticationType": "microsoftEntraId", - "MicrosoftEntraIdAuthenticationInfo" : { - "Resource": "GUID", - "Scope" : "test" - } - } - }})delimiter"); - - Authentication::AuthenticationArguments authArgs; - authArgs.Mode = Authentication::AuthenticationMode::Silent; - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; - RestClient client = CreateRestClient(TestRestUri, {}, {}, helper, authArgs); - REQUIRE(client.GetSourceIdentifier() == "Source123"); - auto information = client.GetSourceInformation(); - REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); - REQUIRE(information.SourceAgreements.size() == 1); - REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); - REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); - REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); - REQUIRE(information.RequiredQueryParameters.size() == 1); - REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); - REQUIRE(information.RequiredPackageMatchFields.size() == 1); - REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); - REQUIRE(information.UnsupportedQueryParameters.size() == 1); - REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); - REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); - REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); - REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId); - REQUIRE(information.Authentication.MicrosoftEntraIdInfo.has_value()); - REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Resource == "GUID"); - REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Scope == "test"); -} - -// Simulate the msstore cache round trip using real world data. -TEST_CASE("RestInformationCache_RoundTrip", "[RestInformationCache]") -{ - Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); - - std::wstring endpoint = L"https://test-url-com/information"; - CacheControlPolicy cacheControl{ L"public, max-age=77287" }; - auto response = web::json::value::parse( -R"delimiter({ - "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageMetadata.PackageMetadataResponse, StoreEdgeFD", - "Data": { - "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageMetadata.PackageMetadataData, StoreEdgeFD", - "SourceIdentifier": "StoreEdgeFD", - "SourceAgreements": { - "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageMetadata.SourceAgreements, StoreEdgeFD", - "AgreementsIdentifier": "StoreEdgeFD", - "Agreements": [ - { - "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageManifest.AgreementDetail, StoreEdgeFD", - "AgreementLabel": "Terms of Transaction", - "AgreementUrl": "https://aka.ms/microsoft-store-terms-of-transaction" - } - ] - }, - "ServerSupportedVersions": [ "1.0.0", "1.1.0", "1.6.0" ], - "RequiredQueryParameters": [ "market" ], - "RequiredPackageMatchFields": [ "market" ] - } -})delimiter"); - - RestInformationCache cache; - cache.Cache(endpoint, {}, {}, cacheControl, response); - auto cachedValue = cache.Get(endpoint, {}, {}); - - REQUIRE(cachedValue.has_value()); - - InformationResponseDeserializer deserializer; - const auto expected = deserializer.Deserialize(response); - const auto& actual = cachedValue.value(); - - REQUIRE(expected.SourceIdentifier == actual.SourceIdentifier); - REQUIRE(expected.SourceAgreementsIdentifier == actual.SourceAgreementsIdentifier); - REQUIRE(expected.SourceAgreements.size() == actual.SourceAgreements.size()); - REQUIRE(1 == actual.SourceAgreements.size()); - REQUIRE(expected.SourceAgreements[0].Label == actual.SourceAgreements[0].Label); - REQUIRE(expected.SourceAgreements[0].Text == actual.SourceAgreements[0].Text); - REQUIRE(expected.SourceAgreements[0].Url == actual.SourceAgreements[0].Url); - - REQUIRE(expected.ServerSupportedVersions.size() == actual.ServerSupportedVersions.size()); - for (const auto& expectedVersion : expected.ServerSupportedVersions) - { - REQUIRE(std::find(actual.ServerSupportedVersions.begin(), actual.ServerSupportedVersions.end(), expectedVersion) != actual.ServerSupportedVersions.end()); - } - - REQUIRE(expected.RequiredQueryParameters.size() == actual.RequiredQueryParameters.size()); - REQUIRE(1 == actual.RequiredQueryParameters.size()); - REQUIRE(expected.RequiredQueryParameters[0] == actual.RequiredQueryParameters[0]); - - REQUIRE(expected.RequiredPackageMatchFields.size() == actual.RequiredPackageMatchFields.size()); - REQUIRE(1 == actual.RequiredPackageMatchFields.size()); - REQUIRE(expected.RequiredPackageMatchFields[0] == actual.RequiredPackageMatchFields[0]); -} - -web::json::value CreateInformationResponse(std::string_view identifier) -{ - std::ostringstream stream; - stream << R"({ "Data": { "SourceIdentifier": ")" << identifier << R"(", "ServerSupportedVersions": [ "1.0.0" ] } })"; - - return web::json::value::parse(stream.str()); -} - -TEST_CASE("RestInformationCache_Get", "[RestInformationCache]") -{ - Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); - - std::wstring endpoint1 = L"https://test-url1-com/information"; - std::wstring endpoint2 = L"https://test-url2-com/information"; - std::wstring endpointNotPresent = L"https://test-url-not-present-com/information"; - std::string header = "Header"; - std::string caller = "Caller"; - std::string publicEndpoint1Identifier = "Identifier1"; - std::string privateEndpoint1Identifier = "Identifier2"; - std::string privateEndpoint2Identifier = "Identifier3"; - auto publicEndpoint1Response = CreateInformationResponse(publicEndpoint1Identifier); - auto privateEndpoint1Response = CreateInformationResponse(privateEndpoint1Identifier); - auto privateEndpoint2Response = CreateInformationResponse(privateEndpoint2Identifier); - - RestInformationCache cache; - - // Cache: - // 1. public and private for same endpoint - cache.Cache(endpoint1, header, caller, { L"public" }, publicEndpoint1Response); - cache.Cache(endpoint1, header, caller, {}, privateEndpoint1Response); - // 2. another endpoint with private data (same headers) - cache.Cache(endpoint2, header, caller, {}, privateEndpoint2Response); - - SECTION("Same headers prefers private") - { - auto cachedValue = cache.Get(endpoint1, header, caller); - REQUIRE(cachedValue.has_value()); - REQUIRE(privateEndpoint1Identifier == cachedValue->SourceIdentifier); - } - SECTION("Different headers falls back to public") - { - auto cachedValue = cache.Get(endpoint1, "Different", "Different"); - REQUIRE(cachedValue.has_value()); - REQUIRE(publicEndpoint1Identifier == cachedValue->SourceIdentifier); - } - SECTION("Second endpoint") - { - auto cachedValue = cache.Get(endpoint2, header, caller); - REQUIRE(cachedValue.has_value()); - REQUIRE(privateEndpoint2Identifier == cachedValue->SourceIdentifier); - } - SECTION("Second endpoint different headers") - { - auto cachedValue = cache.Get(endpoint2, {}, {}); - REQUIRE(!cachedValue.has_value()); - } - SECTION("Missing endpoint") - { - auto cachedValue = cache.Get(endpointNotPresent, header, caller); - REQUIRE(!cachedValue.has_value()); - } -} - -TEST_CASE("RestInformationCache_Cache_NoStore", "[RestInformationCache]") -{ - Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); - - std::wstring endpoint = L"https://test-url-com/information"; - - RestInformationCache cache; - cache.Cache(endpoint, {}, {}, { L"no-store" }, CreateInformationResponse("Identifier")); - - auto cachedValue = cache.Get(endpoint, {}, {}); - REQUIRE(!cachedValue.has_value()); -} - -TEST_CASE("RestInformationCache_Cache_Expiration", "[RestInformationCache]") -{ - Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); - - std::wstring endpoint = L"https://test-url-com/information"; - - RestInformationCache cache; - cache.Cache(endpoint, {}, {}, { L"max-age=2" }, CreateInformationResponse("Identifier")); - - auto cachedValue = cache.Get(endpoint, {}, {}); - REQUIRE(cachedValue.has_value()); - - std::this_thread::sleep_for(5s); - - cachedValue = cache.Get(endpoint, {}, {}); - REQUIRE(!cachedValue.has_value()); -} - -TEST_CASE("RestInformationCache_Cache_Overwrite", "[RestInformationCache]") -{ - Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); - - std::wstring endpoint = L"https://test-url-com/information"; - std::string identifier1 = "Identifier1"; - std::string identifier2 = "Identifier2"; - - RestInformationCache cache; - cache.Cache(endpoint, {}, {}, {}, CreateInformationResponse(identifier1)); - - auto cachedValue = cache.Get(endpoint, {}, {}); - REQUIRE(cachedValue.has_value()); - REQUIRE(identifier1 == cachedValue->SourceIdentifier); - - cache.Cache(endpoint, {}, {}, {}, CreateInformationResponse(identifier2)); - - cachedValue = cache.Get(endpoint, {}, {}); - REQUIRE(cachedValue.has_value()); - REQUIRE(identifier2 == cachedValue->SourceIdentifier); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Repository::Rest; +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"} }; + std::vector versions{ "1.0.0", "2.0.0", "1.2.0" }; + std::optional actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); + REQUIRE(actual); + REQUIRE(actual.value().ToString() == "1.2.0"); +} + +TEST_CASE("GetLatestCommonVersion_OnlyMajorMinorVersionMatched", "[RestSource]") +{ + std::set wingetSupportedContracts = { Version {"1.0.0"}, Version {"1.2.0"} }; + std::vector versions{ "1.0.0", "2.0.0", "1.2.1" }; + std::optional actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); + REQUIRE(actual); + REQUIRE(actual.value().ToString() == "1.2.0"); +} + +TEST_CASE("GetLatestCommonVersion_UnsupportedVersion", "[RestSource]") +{ + std::set wingetSupportedContracts = { Version {"3.0.0"}, Version {"4.2.0"} }; + std::vector versions{ "1.0.0", "2.0.0" }; + std::optional actual = RestClient::GetLatestCommonVersion(versions, wingetSupportedContracts); + REQUIRE(!actual); +} + +TEST_CASE("GetSupportedInterface", "[RestSource]") +{ + IRestClient::Information info{ "TestId", { "1.0.0" } }; + + Version version{ "1.0.0" }; + REQUIRE(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, version, {})->GetVersion() == version); + + // Update this test to next version so that we don't forget to add to supported versions before rest e2e tests are available. + Version invalid{ "1.29.0" }; + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, info, {}, invalid, {}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); + + Authentication::AuthenticationArguments authArgs; + authArgs.Mode = Authentication::AuthenticationMode::Silent; + Version version_1_7{ "1.7.0" }; + + // GetSupportedInterface throws on unknown authentication type. + IRestClient::Information infoWithUnknownAuthenticationType{ "TestId", { "1.7.0" } }; + infoWithUnknownAuthenticationType.Authentication.Type = Authentication::AuthenticationType::Unknown; + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithUnknownAuthenticationType, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + + // GetSupportedInterface throws on invalid authentication info. + IRestClient::Information infoWithInvalidAuthenticationInfo{ "TestId", { "1.7.0" } }; + infoWithInvalidAuthenticationInfo.Authentication.Type = Authentication::AuthenticationType::MicrosoftEntraId; + REQUIRE_THROWS_HR(RestClient::GetSupportedInterface(TestRestUri, {}, infoWithInvalidAuthenticationInfo, authArgs, version_1_7, {}), APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO); +} + +TEST_CASE("GetInformation_Success", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.0.0", + "1.1.0" + ], + "SourceAgreements": { + "AgreementsIdentifier": "agreementV1", + "Agreements": [{ + "AgreementLabel": "EULA", + "Agreement": "this is store agreement", + "AgreementUrl": "https://store.agreement" + } + ] + }, + "RequiredQueryParameters": [ + "Market" + ], + "RequiredPackageMatchFields": [ + "Market" + ], + "UnsupportedQueryParameters": [ + "Moniker" + ], + "UnsupportedPackageMatchFields": [ + "Moniker" + ] + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); + REQUIRE(information.SourceIdentifier == "Source123"); + REQUIRE(information.ServerSupportedVersions.size() == 2); + REQUIRE(information.ServerSupportedVersions.at(0) == "1.0.0"); + REQUIRE(information.ServerSupportedVersions.at(1) == "1.1.0"); + REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); + REQUIRE(information.SourceAgreements.size() == 1); + REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); + REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); + REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); + REQUIRE(information.RequiredQueryParameters.size() == 1); + REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); + REQUIRE(information.RequiredPackageMatchFields.size() == 1); + REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); + REQUIRE(information.UnsupportedQueryParameters.size() == 1); + REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); + REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); + REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); + REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::None); + REQUIRE_FALSE(information.Authentication.MicrosoftEntraIdInfo.has_value()); +} + +TEST_CASE("GetInformation_WithAuthenticationInfo_Success", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "SourceAgreements": { + "AgreementsIdentifier": "agreementV1", + "Agreements": [{ + "AgreementLabel": "EULA", + "Agreement": "this is store agreement", + "AgreementUrl": "https://store.agreement" + } + ] + }, + "RequiredQueryParameters": [ + "Market" + ], + "RequiredPackageMatchFields": [ + "Market" + ], + "UnsupportedQueryParameters": [ + "Moniker" + ], + "UnsupportedPackageMatchFields": [ + "Moniker" + ], + "Authentication": { + "AuthenticationType": "microsoftEntraId", + "MicrosoftEntraIdAuthenticationInfo" : { + "Resource": "GUID", + "Scope" : "test" + } + } + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + IRestClient::Information information = RestClient::GetInformation(TestRestUri, {}, {}, helper); + REQUIRE(information.SourceIdentifier == "Source123"); + REQUIRE(information.ServerSupportedVersions.size() == 1); + REQUIRE(information.ServerSupportedVersions.at(0) == "1.7.0"); + REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); + REQUIRE(information.SourceAgreements.size() == 1); + REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); + REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); + REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); + REQUIRE(information.RequiredQueryParameters.size() == 1); + REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); + REQUIRE(information.RequiredPackageMatchFields.size() == 1); + REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); + REQUIRE(information.UnsupportedQueryParameters.size() == 1); + REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); + REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); + REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); + REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId); + REQUIRE(information.Authentication.MicrosoftEntraIdInfo.has_value()); + REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Resource == "GUID"); + REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Scope == "test"); +} + +TEST_CASE("GetInformation_Fail_AgreementsWithoutIdentifier", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.0.0", + "1.1.0"], + "SourceAgreements": { + "Agreements": [{ + "AgreementLabel": "EULA", + "Agreement": "this is store agreement", + "AgreementUrl": "https://store.agreement" + } + ] + } + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); +} + +TEST_CASE("GetInformation_Fail_InvalidMicrosoftEntraIdInfo", "[RestSource]") +{ + utility::string_t sample1 = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "Authentication": { + "AuthenticationType": "microsoftEntraId" + } + }})delimiter"); + + HttpClientHelper helper1{ GetTestRestRequestHandler(web::http::status_codes::OK, sample1) }; + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper1), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + + utility::string_t sample2 = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "Authentication": { + "AuthenticationType": "microsoftEntraId", + "MicrosoftEntraIdAuthenticationInfo" : { + "Resource": "", + "Scope" : "test" + } + } + }})delimiter"); + + HttpClientHelper helper2{ GetTestRestRequestHandler(web::http::status_codes::OK, sample2) }; + REQUIRE_THROWS_HR(RestClient::GetInformation(TestRestUri, {}, {}, helper2), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); + + utility::string_t sample3 = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "Authentication": { + "AuthenticationType": "microsoftEntraId", + "MicrosoftEntraIdAuthenticationInfo" : { + "Scope" : "test" + } + } + }})delimiter"); + + HttpClientHelper helper3{ GetTestRestRequestHandler(web::http::status_codes::OK, sample3) }; + 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]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.2.0", + "2.0.0"] + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper), APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE); +} + +TEST_CASE("RestClientCreate_UnsupportedAuthenticationMethod", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "Authentication": { + "AuthenticationType": "unknown" + } + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + Authentication::AuthenticationArguments authArgs; + authArgs.Mode = Authentication::AuthenticationMode::Silent; + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); +} + +TEST_CASE("RestClientCreate_InvalidAuthenticationArguments", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "Authentication": { + "AuthenticationType": "microsoftEntraId", + "MicrosoftEntraIdAuthenticationInfo" : { + "Resource" : "test" + } + } + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + Authentication::AuthenticationArguments authArgs; + authArgs.Mode = Authentication::AuthenticationMode::Unknown; + REQUIRE_THROWS_HR(CreateRestClient("https://restsource.com/api", {}, {}, helper, authArgs), E_UNEXPECTED); +} + +TEST_CASE("RestClientCreate_1.0_Success", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.0.0", + "2.0.0"] + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); + REQUIRE(client.GetSourceIdentifier() == "Source123"); +} + +TEST_CASE("RestClientCreate_1.1_Success", "[RestSource]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.0.0", + "1.1.0"], + "SourceAgreements": { + "AgreementsIdentifier": "agreementV1", + "Agreements": [{ + "AgreementLabel": "EULA", + "Agreement": "this is store agreement", + "AgreementUrl": "https://store.agreement" + } + ] + }, + "RequiredQueryParameters": [ + "Market" + ], + "RequiredPackageMatchFields": [ + "Market" + ], + "UnsupportedQueryParameters": [ + "Moniker" + ], + "UnsupportedPackageMatchFields": [ + "Moniker" + ] + }})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper); + REQUIRE(client.GetSourceIdentifier() == "Source123"); + auto information = client.GetSourceInformation(); + REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); + REQUIRE(information.SourceAgreements.size() == 1); + REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); + REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); + REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); + REQUIRE(information.RequiredQueryParameters.size() == 1); + REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); + REQUIRE(information.RequiredPackageMatchFields.size() == 1); + REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); + REQUIRE(information.UnsupportedQueryParameters.size() == 1); + REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); + REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); + REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); +} + +TEST_CASE("RestClientCreate_1.7_Success", "[RestSource]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : { + "SourceIdentifier": "Source123", + "ServerSupportedVersions": [ + "1.7.0" + ], + "SourceAgreements": { + "AgreementsIdentifier": "agreementV1", + "Agreements": [{ + "AgreementLabel": "EULA", + "Agreement": "this is store agreement", + "AgreementUrl": "https://store.agreement" + } + ] + }, + "RequiredQueryParameters": [ + "Market" + ], + "RequiredPackageMatchFields": [ + "Market" + ], + "UnsupportedQueryParameters": [ + "Moniker" + ], + "UnsupportedPackageMatchFields": [ + "Moniker" + ], + "Authentication": { + "AuthenticationType": "microsoftEntraId", + "MicrosoftEntraIdAuthenticationInfo" : { + "Resource": "GUID", + "Scope" : "test" + } + } + }})delimiter"); + + Authentication::AuthenticationArguments authArgs; + authArgs.Mode = Authentication::AuthenticationMode::Silent; + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, sample) }; + RestClient client = CreateRestClient(TestRestUri, {}, {}, helper, authArgs); + REQUIRE(client.GetSourceIdentifier() == "Source123"); + auto information = client.GetSourceInformation(); + REQUIRE(information.SourceAgreementsIdentifier == "agreementV1"); + REQUIRE(information.SourceAgreements.size() == 1); + REQUIRE(information.SourceAgreements.at(0).Label == "EULA"); + REQUIRE(information.SourceAgreements.at(0).Text == "this is store agreement"); + REQUIRE(information.SourceAgreements.at(0).Url == "https://store.agreement"); + REQUIRE(information.RequiredQueryParameters.size() == 1); + REQUIRE(information.RequiredQueryParameters.at(0) == "Market"); + REQUIRE(information.RequiredPackageMatchFields.size() == 1); + REQUIRE(information.RequiredPackageMatchFields.at(0) == "Market"); + REQUIRE(information.UnsupportedQueryParameters.size() == 1); + REQUIRE(information.UnsupportedQueryParameters.at(0) == "Moniker"); + REQUIRE(information.UnsupportedPackageMatchFields.size() == 1); + REQUIRE(information.UnsupportedPackageMatchFields.at(0) == "Moniker"); + REQUIRE(information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId); + REQUIRE(information.Authentication.MicrosoftEntraIdInfo.has_value()); + REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Resource == "GUID"); + REQUIRE(information.Authentication.MicrosoftEntraIdInfo->Scope == "test"); +} + +// Simulate the msstore cache round trip using real world data. +TEST_CASE("RestInformationCache_RoundTrip", "[RestInformationCache]") +{ + Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); + + std::wstring endpoint = L"https://test-url-com/information"; + CacheControlPolicy cacheControl{ L"public, max-age=77287" }; + auto response = web::json::value::parse( +R"delimiter({ + "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageMetadata.PackageMetadataResponse, StoreEdgeFD", + "Data": { + "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageMetadata.PackageMetadataData, StoreEdgeFD", + "SourceIdentifier": "StoreEdgeFD", + "SourceAgreements": { + "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageMetadata.SourceAgreements, StoreEdgeFD", + "AgreementsIdentifier": "StoreEdgeFD", + "Agreements": [ + { + "$type": "Microsoft.Marketplace.Storefront.StoreEdgeFD.BusinessLogic.Response.PackageManifest.AgreementDetail, StoreEdgeFD", + "AgreementLabel": "Terms of Transaction", + "AgreementUrl": "https://aka.ms/microsoft-store-terms-of-transaction" + } + ] + }, + "ServerSupportedVersions": [ "1.0.0", "1.1.0", "1.6.0" ], + "RequiredQueryParameters": [ "market" ], + "RequiredPackageMatchFields": [ "market" ] + } +})delimiter"); + + RestInformationCache cache; + cache.Cache(endpoint, {}, {}, cacheControl, response); + auto cachedValue = cache.Get(endpoint, {}, {}); + + REQUIRE(cachedValue.has_value()); + + InformationResponseDeserializer deserializer; + const auto expected = deserializer.Deserialize(response); + const auto& actual = cachedValue.value(); + + REQUIRE(expected.SourceIdentifier == actual.SourceIdentifier); + REQUIRE(expected.SourceAgreementsIdentifier == actual.SourceAgreementsIdentifier); + REQUIRE(expected.SourceAgreements.size() == actual.SourceAgreements.size()); + REQUIRE(1 == actual.SourceAgreements.size()); + REQUIRE(expected.SourceAgreements[0].Label == actual.SourceAgreements[0].Label); + REQUIRE(expected.SourceAgreements[0].Text == actual.SourceAgreements[0].Text); + REQUIRE(expected.SourceAgreements[0].Url == actual.SourceAgreements[0].Url); + + REQUIRE(expected.ServerSupportedVersions.size() == actual.ServerSupportedVersions.size()); + for (const auto& expectedVersion : expected.ServerSupportedVersions) + { + REQUIRE(std::find(actual.ServerSupportedVersions.begin(), actual.ServerSupportedVersions.end(), expectedVersion) != actual.ServerSupportedVersions.end()); + } + + REQUIRE(expected.RequiredQueryParameters.size() == actual.RequiredQueryParameters.size()); + REQUIRE(1 == actual.RequiredQueryParameters.size()); + REQUIRE(expected.RequiredQueryParameters[0] == actual.RequiredQueryParameters[0]); + + REQUIRE(expected.RequiredPackageMatchFields.size() == actual.RequiredPackageMatchFields.size()); + REQUIRE(1 == actual.RequiredPackageMatchFields.size()); + REQUIRE(expected.RequiredPackageMatchFields[0] == actual.RequiredPackageMatchFields[0]); +} + +web::json::value CreateInformationResponse(std::string_view identifier) +{ + std::ostringstream stream; + stream << R"({ "Data": { "SourceIdentifier": ")" << identifier << R"(", "ServerSupportedVersions": [ "1.0.0" ] } })"; + + return web::json::value::parse(stream.str()); +} + +TEST_CASE("RestInformationCache_Get", "[RestInformationCache]") +{ + Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); + + std::wstring endpoint1 = L"https://test-url1-com/information"; + std::wstring endpoint2 = L"https://test-url2-com/information"; + std::wstring endpointNotPresent = L"https://test-url-not-present-com/information"; + std::string header = "Header"; + std::string caller = "Caller"; + std::string publicEndpoint1Identifier = "Identifier1"; + std::string privateEndpoint1Identifier = "Identifier2"; + std::string privateEndpoint2Identifier = "Identifier3"; + auto publicEndpoint1Response = CreateInformationResponse(publicEndpoint1Identifier); + auto privateEndpoint1Response = CreateInformationResponse(privateEndpoint1Identifier); + auto privateEndpoint2Response = CreateInformationResponse(privateEndpoint2Identifier); + + RestInformationCache cache; + + // Cache: + // 1. public and private for same endpoint + cache.Cache(endpoint1, header, caller, { L"public" }, publicEndpoint1Response); + cache.Cache(endpoint1, header, caller, {}, privateEndpoint1Response); + // 2. another endpoint with private data (same headers) + cache.Cache(endpoint2, header, caller, {}, privateEndpoint2Response); + + SECTION("Same headers prefers private") + { + auto cachedValue = cache.Get(endpoint1, header, caller); + REQUIRE(cachedValue.has_value()); + REQUIRE(privateEndpoint1Identifier == cachedValue->SourceIdentifier); + } + SECTION("Different headers falls back to public") + { + auto cachedValue = cache.Get(endpoint1, "Different", "Different"); + REQUIRE(cachedValue.has_value()); + REQUIRE(publicEndpoint1Identifier == cachedValue->SourceIdentifier); + } + SECTION("Second endpoint") + { + auto cachedValue = cache.Get(endpoint2, header, caller); + REQUIRE(cachedValue.has_value()); + REQUIRE(privateEndpoint2Identifier == cachedValue->SourceIdentifier); + } + SECTION("Second endpoint different headers") + { + auto cachedValue = cache.Get(endpoint2, {}, {}); + REQUIRE(!cachedValue.has_value()); + } + SECTION("Missing endpoint") + { + auto cachedValue = cache.Get(endpointNotPresent, header, caller); + REQUIRE(!cachedValue.has_value()); + } +} + +TEST_CASE("RestInformationCache_Cache_NoStore", "[RestInformationCache]") +{ + Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); + + std::wstring endpoint = L"https://test-url-com/information"; + + RestInformationCache cache; + cache.Cache(endpoint, {}, {}, { L"no-store" }, CreateInformationResponse("Identifier")); + + auto cachedValue = cache.Get(endpoint, {}, {}); + REQUIRE(!cachedValue.has_value()); +} + +TEST_CASE("RestInformationCache_Cache_Expiration", "[RestInformationCache]") +{ + Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); + + std::wstring endpoint = L"https://test-url-com/information"; + + RestInformationCache cache; + cache.Cache(endpoint, {}, {}, { L"max-age=2" }, CreateInformationResponse("Identifier")); + + auto cachedValue = cache.Get(endpoint, {}, {}); + REQUIRE(cachedValue.has_value()); + + std::this_thread::sleep_for(5s); + + cachedValue = cache.Get(endpoint, {}, {}); + REQUIRE(!cachedValue.has_value()); +} + +TEST_CASE("RestInformationCache_Cache_Overwrite", "[RestInformationCache]") +{ + Settings::Stream{ Settings::Stream::RestInformationCache }.Remove(); + + std::wstring endpoint = L"https://test-url-com/information"; + std::string identifier1 = "Identifier1"; + std::string identifier2 = "Identifier2"; + + RestInformationCache cache; + cache.Cache(endpoint, {}, {}, {}, CreateInformationResponse(identifier1)); + + auto cachedValue = cache.Get(endpoint, {}, {}); + REQUIRE(cachedValue.has_value()); + REQUIRE(identifier1 == cachedValue->SourceIdentifier); + + cache.Cache(endpoint, {}, {}, {}, CreateInformationResponse(identifier2)); + + cachedValue = cache.Get(endpoint, {}, {}); + REQUIRE(cachedValue.has_value()); + REQUIRE(identifier2 == cachedValue->SourceIdentifier); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_0.cpp b/src/AppInstallerCLITests/RestInterface_1_0.cpp index 9c060e1c54..bb8ef2526a 100644 --- a/src/AppInstallerCLITests/RestInterface_1_0.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_0.cpp @@ -1,645 +1,645 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_0; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - utility::string_t GetGoodManifest_RequiredFields() - { - return _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerType": "exe", - "InstallerUrl": "https://installer.example.com/foobar.exe" - } - ] - } - ] - } - })delimiter"); - } - - utility::string_t GetManifestsResponse_MultipleVersions() - { - return _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerType": "exe", - "InstallerUrl": "https://installer.example.com/foobar.exe" - } - ] - }, - { - "PackageVersion": "6.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerType": "exe", - "InstallerUrl": "https://installer.example.com/foobar.exe" - } - ] - } - ] - } - })delimiter"); - } - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker" - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ] - } - ], - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.exe", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "msix", - "Scope": "user", - "SignatureSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "install", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "PackageFamilyName": "FooBar.PackageFamilyName", - "ProductCode": "", - "Capabilities": [ - "Bluetooth" - ], - "RestrictedCapabilities": [ - "restrictedCapability" - ] - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - } - - void VerifyInstallers_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.exe"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Msix); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.SignatureSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 7); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == "FooBar.PackageFamilyName"); - REQUIRE(actualInstaller.ProductCode == ""); - REQUIRE(actualInstaller.Capabilities.at(0) == "Bluetooth"); - REQUIRE(actualInstaller.RestrictedCapabilities.at(0) == "restrictedCapability"); - } - }; -} - -TEST_CASE("Search_GoodResponse", "[RestSource][Interface_1_0]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [ - { "PackageVersion": "1.0.0" }, - { "PackageVersion": "2.0.0" }] - }] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - Schema::IRestClient::SearchResult searchResponse = v1.Search({}); - REQUIRE(searchResponse.Matches.size() == 1); - Schema::IRestClient::Package package = searchResponse.Matches.at(0); - REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); - REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); - REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); - REQUIRE(package.Versions.size() == 2); - REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); - REQUIRE(package.Versions.at(1).VersionAndChannel.GetVersion().ToString().compare("2.0.0") == 0); -} - -TEST_CASE("Search_GoodResponse_AllFields", "[RestSource][Interface_1_0]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [ - { - "PackageVersion": "1.0.0", - "PackageFamilyNames" : [ - "pfn1", - "pfn2", - "pfn2" - ], - "ProductCodes" : [ - "pc1", - "pc2" - ] - }] - }] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - Schema::IRestClient::SearchResult searchResponse = v1.Search({}); - REQUIRE(searchResponse.Matches.size() == 1); - Schema::IRestClient::Package package = searchResponse.Matches.at(0); - REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); - REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); - REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); - REQUIRE(package.Versions.size() == 1); - REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); - REQUIRE(package.Versions.at(0).PackageFamilyNames.size() == 2); - REQUIRE(package.Versions.at(0).PackageFamilyNames.at(0) == "pfn1"); - REQUIRE(package.Versions.at(0).PackageFamilyNames.at(1) == "pfn2"); - REQUIRE(package.Versions.at(0).ProductCodes.at(0) == "pc1"); - REQUIRE(package.Versions.at(0).ProductCodes.at(1) == "pc2"); -} - -TEST_CASE("Search_GoodResponse_404AsEmpty", "[RestSource][Interface_1_0]") -{ - utility::string_t notFoundResponse = _XPLATSTR( - R"delimiter({"code":"DataNotFound","data":[],"details":[],"innererror":{"code":"DataNotFound","data":[],"details":[],"message":"Product is not present","source":"StoreEdgeFD"},"message":"Product is not present","source":"StoreEdgeFD"})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound, std::move(notFoundResponse)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - Schema::IRestClient::SearchResult searchResponse = v1.Search({}); - REQUIRE(searchResponse.Matches.size() == 0); -} - -TEST_CASE("Search_ContinuationToken", "[RestSource][Interface_1_0]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [ - { "PackageVersion": "1.0.0" }] - }, - { - "PackageIdentifier": "foo.package", - "PackageName": "package", - "Publisher": "foo", - "Versions": [ - { "PackageVersion": "1.0.0" }] - }], - "ContinuationToken" : "abcd-ct=" - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - SearchRequest request{}; - request.MaximumResults = 9; - Schema::IRestClient::SearchResult results = v1.Search(request); - REQUIRE(results.Matches.size() == request.MaximumResults); - - SearchRequest requestWithSize1{}; - requestWithSize1.MaximumResults = 1; - Schema::IRestClient::SearchResult resultsWithSize1 = v1.Search(requestWithSize1); - REQUIRE(resultsWithSize1.Matches.size() == requestWithSize1.MaximumResults); -} - -TEST_CASE("Search_BadResponse_NoVersions", "[RestSource][Interface_1_0]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": null }] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - REQUIRE_THROWS_HR(v1.Search({}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); -} - -TEST_CASE("Search_BadResponse_NotFoundCode", "[RestSource][Interface_1_0]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; - Interface v1{ TestRestUriString, std::move(helper) }; - REQUIRE_THROWS_HR(v1.Search({}), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); -} - -TEST_CASE("Search_Optimized_ManifestResponse", "[RestSource][Interface_1_0]") -{ - utility::string_t sample = GetGoodManifest_RequiredFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - AppInstaller::Repository::SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo" }; - request.Filters.emplace_back(std::move(filter)); - Interface v1{ TestRestUriString, std::move(helper) }; - Schema::IRestClient::SearchResult result = v1.Search(request); - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Versions.size() == 1); - REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetVersion().ToString() == "5.0.0"); - REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetChannel().ToString() == ""); - REQUIRE(result.Matches[0].Versions[0].Manifest); - - // Verify manifest is populated - Manifest manifest = result.Matches[0].Versions[0].Manifest.value(); - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "5.0.0"); - REQUIRE(manifest.DefaultLocalization.Locale == "en-us"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar license"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar description"); - REQUIRE(manifest.Installers.size() == 1); - REQUIRE(manifest.Installers[0].Arch == Architecture::X64); - REQUIRE(manifest.Installers[0].Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(manifest.Installers[0].BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(manifest.Installers[0].Url == "https://installer.example.com/foobar.exe"); -} - -TEST_CASE("Search_Optimized_ManifestResponse_MultipleVersions", "[RestSource][Interface_1_0]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; - AppInstaller::Repository::SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo.Bar" }; - request.Filters.emplace_back(std::move(filter)); - Interface v1{ TestRestUriString, std::move(helper) }; - Schema::IRestClient::SearchResult result = v1.Search(request); - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].Versions.size() == 2); - REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetVersion().ToString() == "5.0.0"); - REQUIRE(result.Matches[0].Versions[0].Manifest); - REQUIRE(result.Matches[0].Versions[1].VersionAndChannel.GetVersion().ToString() == "6.0.0"); - REQUIRE(result.Matches[0].Versions[1].Manifest); -} - -TEST_CASE("Search_Optimized_NoResponse_NotFoundCode", "[RestSource][Interface_1_0]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; - AppInstaller::Repository::SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo" }; - request.Filters.emplace_back(std::move(filter)); - Interface v1{ TestRestUriString, std::move(helper) }; - REQUIRE_THROWS_HR(v1.Search(request), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); -} - -TEST_CASE("GetManifests_GoodResponse", "[RestSource][Interface_1_0]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - std::vector manifests = v1.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - Manifest manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.0.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} - -TEST_CASE("GetManifests_GoodResponse_404AsEmpty", "[RestSource][Interface_1_0]") -{ - utility::string_t notFoundResponse = _XPLATSTR( - R"delimiter({"code":"DataNotFound","data":[],"details":[],"innererror":{"code":"DataNotFound","data":[],"details":[],"message":"Product is not present","source":"StoreEdgeFD"},"message":"Product is not present","source":"StoreEdgeFD"})delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound, std::move(notFoundResponse)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - std::vector manifests = v1.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 0); -} - -TEST_CASE("GetManifests_GoodResponse_MultipleVersions", "[RestSource][Interface_1_0]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; - Interface v1{ TestRestUriString, std::move(helper) }; - - // GetManifests - std::vector manifests = v1.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 2); - REQUIRE(manifests[0].Version == "5.0.0"); - REQUIRE(manifests[1].Version == "6.0.0"); -} - -TEST_CASE("GetManifests_BadResponse_SuccessCode", "[RestSource][Interface_1_0]") -{ - utility::string_t badManifest = _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0" - } - ] - } - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(badManifest)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - REQUIRE_THROWS_HR(v1.GetManifests("Foo.Bar"), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); -} - -TEST_CASE("GetManifests_NotFoundCode", "[RestSource][Interface_1_0]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; - Interface v1{ TestRestUriString, std::move(helper) }; - REQUIRE_THROWS_HR(v1.GetManifests("Foo.Bar"), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); -} - -TEST_CASE("GetManifests_GoodResponse_UnknownInstaller", "[RestSource][Interface_1_0]") -{ - utility::string_t msstoreInstallerResponse = _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerType": "msstore", - "MSStoreProductIdentifier": "9nblggh4nns1" - } - ] - } - ] - } - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(msstoreInstallerResponse)) }; - Interface v1{ TestRestUriString, std::move(helper) }; - std::vector manifests = v1.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated and manifest validation passed - Manifest& manifest = manifests[0]; - REQUIRE(manifest.Installers.size() == 1); - REQUIRE(manifest.Installers.at(0).BaseInstallerType == InstallerTypeEnum::Unknown); - REQUIRE(manifest.Installers.at(0).ProductId.empty()); -} - -TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionFound", "[RestSource][Interface_1_0]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; - Interface v1{ TestRestUriString, std::move(helper) }; - - // GetManifests - std::optional manifest = v1.GetManifestByVersion("Foo.Bar", "5.0.0", ""); - REQUIRE(manifest.has_value()); - REQUIRE(manifest->Version == "5.0.0"); -} - -TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionNotFound", "[RestSource][Interface_1_0]") -{ - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; - Interface v1{ TestRestUriString, std::move(helper) }; - - // GetManifests - std::optional manifest = v1.GetManifestByVersion("Foo.Bar", "7.0.0", ""); - REQUIRE_FALSE(manifest.has_value()); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_0; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + utility::string_t GetGoodManifest_RequiredFields() + { + return _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerType": "exe", + "InstallerUrl": "https://installer.example.com/foobar.exe" + } + ] + } + ] + } + })delimiter"); + } + + utility::string_t GetManifestsResponse_MultipleVersions() + { + return _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerType": "exe", + "InstallerUrl": "https://installer.example.com/foobar.exe" + } + ] + }, + { + "PackageVersion": "6.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerType": "exe", + "InstallerUrl": "https://installer.example.com/foobar.exe" + } + ] + } + ] + } + })delimiter"); + } + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker" + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ] + } + ], + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.exe", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "msix", + "Scope": "user", + "SignatureSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "install", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "PackageFamilyName": "FooBar.PackageFamilyName", + "ProductCode": "", + "Capabilities": [ + "Bluetooth" + ], + "RestrictedCapabilities": [ + "restrictedCapability" + ] + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + } + + void VerifyInstallers_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.exe"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Msix); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.SignatureSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 7); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == "FooBar.PackageFamilyName"); + REQUIRE(actualInstaller.ProductCode == ""); + REQUIRE(actualInstaller.Capabilities.at(0) == "Bluetooth"); + REQUIRE(actualInstaller.RestrictedCapabilities.at(0) == "restrictedCapability"); + } + }; +} + +TEST_CASE("Search_GoodResponse", "[RestSource][Interface_1_0]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [ + { "PackageVersion": "1.0.0" }, + { "PackageVersion": "2.0.0" }] + }] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + Schema::IRestClient::SearchResult searchResponse = v1.Search({}); + REQUIRE(searchResponse.Matches.size() == 1); + Schema::IRestClient::Package package = searchResponse.Matches.at(0); + REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); + REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); + REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); + REQUIRE(package.Versions.size() == 2); + REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); + REQUIRE(package.Versions.at(1).VersionAndChannel.GetVersion().ToString().compare("2.0.0") == 0); +} + +TEST_CASE("Search_GoodResponse_AllFields", "[RestSource][Interface_1_0]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [ + { + "PackageVersion": "1.0.0", + "PackageFamilyNames" : [ + "pfn1", + "pfn2", + "pfn2" + ], + "ProductCodes" : [ + "pc1", + "pc2" + ] + }] + }] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + Schema::IRestClient::SearchResult searchResponse = v1.Search({}); + REQUIRE(searchResponse.Matches.size() == 1); + Schema::IRestClient::Package package = searchResponse.Matches.at(0); + REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); + REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); + REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); + REQUIRE(package.Versions.size() == 1); + REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); + REQUIRE(package.Versions.at(0).PackageFamilyNames.size() == 2); + REQUIRE(package.Versions.at(0).PackageFamilyNames.at(0) == "pfn1"); + REQUIRE(package.Versions.at(0).PackageFamilyNames.at(1) == "pfn2"); + REQUIRE(package.Versions.at(0).ProductCodes.at(0) == "pc1"); + REQUIRE(package.Versions.at(0).ProductCodes.at(1) == "pc2"); +} + +TEST_CASE("Search_GoodResponse_404AsEmpty", "[RestSource][Interface_1_0]") +{ + utility::string_t notFoundResponse = _XPLATSTR( + R"delimiter({"code":"DataNotFound","data":[],"details":[],"innererror":{"code":"DataNotFound","data":[],"details":[],"message":"Product is not present","source":"StoreEdgeFD"},"message":"Product is not present","source":"StoreEdgeFD"})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound, std::move(notFoundResponse)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + Schema::IRestClient::SearchResult searchResponse = v1.Search({}); + REQUIRE(searchResponse.Matches.size() == 0); +} + +TEST_CASE("Search_ContinuationToken", "[RestSource][Interface_1_0]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [ + { "PackageVersion": "1.0.0" }] + }, + { + "PackageIdentifier": "foo.package", + "PackageName": "package", + "Publisher": "foo", + "Versions": [ + { "PackageVersion": "1.0.0" }] + }], + "ContinuationToken" : "abcd-ct=" + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + SearchRequest request{}; + request.MaximumResults = 9; + Schema::IRestClient::SearchResult results = v1.Search(request); + REQUIRE(results.Matches.size() == request.MaximumResults); + + SearchRequest requestWithSize1{}; + requestWithSize1.MaximumResults = 1; + Schema::IRestClient::SearchResult resultsWithSize1 = v1.Search(requestWithSize1); + REQUIRE(resultsWithSize1.Matches.size() == requestWithSize1.MaximumResults); +} + +TEST_CASE("Search_BadResponse_NoVersions", "[RestSource][Interface_1_0]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": null }] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + REQUIRE_THROWS_HR(v1.Search({}), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); +} + +TEST_CASE("Search_BadResponse_NotFoundCode", "[RestSource][Interface_1_0]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; + Interface v1{ TestRestUriString, std::move(helper) }; + REQUIRE_THROWS_HR(v1.Search({}), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); +} + +TEST_CASE("Search_Optimized_ManifestResponse", "[RestSource][Interface_1_0]") +{ + utility::string_t sample = GetGoodManifest_RequiredFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + AppInstaller::Repository::SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo" }; + request.Filters.emplace_back(std::move(filter)); + Interface v1{ TestRestUriString, std::move(helper) }; + Schema::IRestClient::SearchResult result = v1.Search(request); + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Versions.size() == 1); + REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetVersion().ToString() == "5.0.0"); + REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetChannel().ToString() == ""); + REQUIRE(result.Matches[0].Versions[0].Manifest); + + // Verify manifest is populated + Manifest manifest = result.Matches[0].Versions[0].Manifest.value(); + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "5.0.0"); + REQUIRE(manifest.DefaultLocalization.Locale == "en-us"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar license"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar description"); + REQUIRE(manifest.Installers.size() == 1); + REQUIRE(manifest.Installers[0].Arch == Architecture::X64); + REQUIRE(manifest.Installers[0].Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(manifest.Installers[0].BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(manifest.Installers[0].Url == "https://installer.example.com/foobar.exe"); +} + +TEST_CASE("Search_Optimized_ManifestResponse_MultipleVersions", "[RestSource][Interface_1_0]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; + AppInstaller::Repository::SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo.Bar" }; + request.Filters.emplace_back(std::move(filter)); + Interface v1{ TestRestUriString, std::move(helper) }; + Schema::IRestClient::SearchResult result = v1.Search(request); + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].Versions.size() == 2); + REQUIRE(result.Matches[0].Versions[0].VersionAndChannel.GetVersion().ToString() == "5.0.0"); + REQUIRE(result.Matches[0].Versions[0].Manifest); + REQUIRE(result.Matches[0].Versions[1].VersionAndChannel.GetVersion().ToString() == "6.0.0"); + REQUIRE(result.Matches[0].Versions[1].Manifest); +} + +TEST_CASE("Search_Optimized_NoResponse_NotFoundCode", "[RestSource][Interface_1_0]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; + AppInstaller::Repository::SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Id, MatchType::Exact, "Foo" }; + request.Filters.emplace_back(std::move(filter)); + Interface v1{ TestRestUriString, std::move(helper) }; + REQUIRE_THROWS_HR(v1.Search(request), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); +} + +TEST_CASE("GetManifests_GoodResponse", "[RestSource][Interface_1_0]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + std::vector manifests = v1.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.0.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} + +TEST_CASE("GetManifests_GoodResponse_404AsEmpty", "[RestSource][Interface_1_0]") +{ + utility::string_t notFoundResponse = _XPLATSTR( + R"delimiter({"code":"DataNotFound","data":[],"details":[],"innererror":{"code":"DataNotFound","data":[],"details":[],"message":"Product is not present","source":"StoreEdgeFD"},"message":"Product is not present","source":"StoreEdgeFD"})delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound, std::move(notFoundResponse)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + std::vector manifests = v1.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 0); +} + +TEST_CASE("GetManifests_GoodResponse_MultipleVersions", "[RestSource][Interface_1_0]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; + Interface v1{ TestRestUriString, std::move(helper) }; + + // GetManifests + std::vector manifests = v1.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 2); + REQUIRE(manifests[0].Version == "5.0.0"); + REQUIRE(manifests[1].Version == "6.0.0"); +} + +TEST_CASE("GetManifests_BadResponse_SuccessCode", "[RestSource][Interface_1_0]") +{ + utility::string_t badManifest = _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0" + } + ] + } + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(badManifest)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + REQUIRE_THROWS_HR(v1.GetManifests("Foo.Bar"), APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); +} + +TEST_CASE("GetManifests_NotFoundCode", "[RestSource][Interface_1_0]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::NotFound) }; + Interface v1{ TestRestUriString, std::move(helper) }; + REQUIRE_THROWS_HR(v1.GetManifests("Foo.Bar"), APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); +} + +TEST_CASE("GetManifests_GoodResponse_UnknownInstaller", "[RestSource][Interface_1_0]") +{ + utility::string_t msstoreInstallerResponse = _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerType": "msstore", + "MSStoreProductIdentifier": "9nblggh4nns1" + } + ] + } + ] + } + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(msstoreInstallerResponse)) }; + Interface v1{ TestRestUriString, std::move(helper) }; + std::vector manifests = v1.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated and manifest validation passed + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Installers.size() == 1); + REQUIRE(manifest.Installers.at(0).BaseInstallerType == InstallerTypeEnum::Unknown); + REQUIRE(manifest.Installers.at(0).ProductId.empty()); +} + +TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionFound", "[RestSource][Interface_1_0]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; + Interface v1{ TestRestUriString, std::move(helper) }; + + // GetManifests + std::optional manifest = v1.GetManifestByVersion("Foo.Bar", "5.0.0", ""); + REQUIRE(manifest.has_value()); + REQUIRE(manifest->Version == "5.0.0"); +} + +TEST_CASE("GetManifestByVersion_GoodResponse_MultipleVersions_VersionNotFound", "[RestSource][Interface_1_0]") +{ + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, GetManifestsResponse_MultipleVersions()) }; + Interface v1{ TestRestUriString, std::move(helper) }; + + // GetManifests + std::optional manifest = v1.GetManifestByVersion("Foo.Bar", "7.0.0", ""); + REQUIRE_FALSE(manifest.has_value()); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_1.cpp b/src/AppInstallerCLITests/RestInterface_1_1.cpp index c0b1aa4b73..eaefeda053 100644 --- a/src/AppInstallerCLITests/RestInterface_1_1.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_1.cpp @@ -1,559 +1,559 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_1; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - IRestClient::Information GetTestSourceInformation() - { - IRestClient::Information result; - - result.RequiredPackageMatchFields.emplace_back("Market"); - result.RequiredQueryParameters.emplace_back("Market"); - result.UnsupportedPackageMatchFields.emplace_back("Moniker"); - result.UnsupportedQueryParameters.emplace_back("Channel"); - - return result; - } - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker", - "ReleaseNotes": "Default release notes", - "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "DefaultLabel", - "Agreement": "DefaultText", - "AgreementUrl": "https://DefaultAgreementUrl.net" - }] - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ], - "ReleaseNotes": "Release notes", - "ReleaseNotesUrl": "https://ReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "Label", - "Agreement": "Text", - "AgreementUrl": "https://AgreementUrl.net" - }] - } - ], - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.exe", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "msi", - "Scope": "user", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "install", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", - "ReleaseDate": "2021-01-01", - "InstallerAbortsTerminal": true, - "InstallLocationRequired": true, - "RequireExplicitUpgrade": true, - "UnsupportedOSArchitectures": [ "arm" ], - "ElevationRequirement": "elevatesSelf", - "AppsAndFeaturesEntries": [{ - "DisplayName": "DisplayName", - "DisplayVersion": "DisplayVersion", - "Publisher": "Publisher", - "ProductCode": "ProductCode", - "UpgradeCode": "UpgradeCode", - "InstallerType": "exe" - }], - "Markets" : { - "AllowedMarkets": [ "US" ] - }, - "ExpectedReturnCodes": [{ - "InstallerReturnCode": 3, - "ReturnResponse": "InstallInProgress" - }] - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - REQUIRE(frenchLocalization.Get() == "Release notes"); - REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); - REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); - REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - } - - void VerifyInstallers_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.exe"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Msi); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 7); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == ""); - REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); - REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); - REQUIRE(actualInstaller.InstallerAbortsTerminal); - REQUIRE(actualInstaller.InstallLocationRequired); - REQUIRE(actualInstaller.RequireExplicitUpgrade); - REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); - REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::InstallInProgress); - } - }; -} - -TEST_CASE("Search_BadResponse_UnsupportedPackageMatchFields", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [], - "UnsupportedPackageMatchFields" : [ "Moniker" ] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - AppInstaller::Repository::SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; - request.Filters.emplace_back(std::move(filter)); - REQUIRE_THROWS_HR(v1_1.Search(request), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); -} - -TEST_CASE("Search_BadResponse_RequiredPackageMatchFields", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [], - "RequiredPackageMatchFields" : [ "Moniker" ] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - AppInstaller::Repository::SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; - request.Filters.emplace_back(std::move(filter)); - REQUIRE_THROWS_HR(v1_1.Search(request), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); -} - -TEST_CASE("GetManifests_BadResponse_UnsupportedQueryParameters", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : null, - "UnsupportedQueryParameters" : [ "Channel" ] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); -} - -TEST_CASE("GetManifests_BadResponse_RequiredQueryParameters", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : null, - "RequiredQueryParameters" : [ "Version" ] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); -} - -TEST_CASE("Search_BadRequest_UnsupportedPackageMatchFields", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [ - { "PackageVersion": "1.0.0" }, - { "PackageVersion": "2.0.0" }] - }] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - AppInstaller::Repository::SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Moniker, MatchType::Exact, "Foo" }; - request.Filters.emplace_back(std::move(filter)); - REQUIRE_THROWS_HR(v1_1.Search(request), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); -} - -TEST_CASE("Search_GoodRequest_OnlyMarketRequired", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [ - { "PackageVersion": "1.0.0" }, - { "PackageVersion": "2.0.0" }] - }] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - AppInstaller::Repository::SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; - request.Filters.emplace_back(std::move(filter)); - Schema::IRestClient::SearchResult searchResponse = v1_1.Search(request); - REQUIRE(searchResponse.Matches.size() == 1); - Schema::IRestClient::Package package = searchResponse.Matches.at(0); - REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); - REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); - REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); - REQUIRE(package.Versions.size() == 2); - REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); - REQUIRE(package.Versions.at(1).VersionAndChannel.GetVersion().ToString().compare("2.0.0") == 0); -} - -TEST_CASE("GetManifests_BadRequest_UnsupportedQueryParameters", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerType": "exe", - "InstallerUrl": "https://installer.example.com/foobar.exe" - } - ] - } - ] - } - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - REQUIRE_THROWS_HR(v1_1.GetManifestByVersion("Foo", "1.0", "beta"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); -} - -TEST_CASE("GetManifests_GoodRequest_OnlyMarketRequired", "[RestSource][Interface_1_1]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerType": "exe", - "InstallerUrl": "https://installer.example.com/foobar.exe" - } - ] - } - ] - } - })delimiter"); - - IRestClient::Information info = GetTestSourceInformation(); - info.UnsupportedQueryParameters.clear(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), info, {} }; - auto manifestResult = v1_1.GetManifestByVersion("Foo", "5.0.0", ""); - REQUIRE(manifestResult.has_value()); - const Manifest& manifest = manifestResult.value(); - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "5.0.0"); - REQUIRE(manifest.DefaultLocalization.Locale == "en-us"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar license"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar description"); - REQUIRE(manifest.Installers.size() == 1); - REQUIRE(manifest.Installers[0].Arch == Architecture::X64); - REQUIRE(manifest.Installers[0].Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(manifest.Installers[0].BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(manifest.Installers[0].Url == "https://installer.example.com/foobar.exe"); -} - -TEST_CASE("GetManifests_GoodResponse_MSStoreType", "[RestSource][Interface_1_1]") -{ - utility::string_t msstoreInstallerResponse = _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerType": "msstore", - "MSStoreProductIdentifier": "9nblggh4nns1" - } - ] - } - ] - } - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(msstoreInstallerResponse)) }; - Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; - std::vector manifests = v1_1.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated and manifest validation passed - Manifest manifest = manifests[0]; - REQUIRE(manifest.Installers.size() == 1); - REQUIRE(manifest.Installers.at(0).BaseInstallerType == InstallerTypeEnum::MSStore); - REQUIRE(manifest.Installers.at(0).ProductId == "9nblggh4nns1"); -} - -TEST_CASE("GetManifests_GoodResponse_V1_1", "[RestSource][Interface_1_1]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_1{ TestRestUriString, std::move(helper), {} }; - std::vector manifests = v1_1.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - Manifest& manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.1.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_1; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + IRestClient::Information GetTestSourceInformation() + { + IRestClient::Information result; + + result.RequiredPackageMatchFields.emplace_back("Market"); + result.RequiredQueryParameters.emplace_back("Market"); + result.UnsupportedPackageMatchFields.emplace_back("Moniker"); + result.UnsupportedQueryParameters.emplace_back("Channel"); + + return result; + } + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }] + } + ], + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.exe", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "msi", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "install", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "InstallInProgress" + }] + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + } + + void VerifyInstallers_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.exe"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Msi); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 7); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::InstallInProgress); + } + }; +} + +TEST_CASE("Search_BadResponse_UnsupportedPackageMatchFields", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [], + "UnsupportedPackageMatchFields" : [ "Moniker" ] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + AppInstaller::Repository::SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; + request.Filters.emplace_back(std::move(filter)); + REQUIRE_THROWS_HR(v1_1.Search(request), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); +} + +TEST_CASE("Search_BadResponse_RequiredPackageMatchFields", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [], + "RequiredPackageMatchFields" : [ "Moniker" ] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + AppInstaller::Repository::SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; + request.Filters.emplace_back(std::move(filter)); + REQUIRE_THROWS_HR(v1_1.Search(request), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); +} + +TEST_CASE("GetManifests_BadResponse_UnsupportedQueryParameters", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : null, + "UnsupportedQueryParameters" : [ "Channel" ] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); +} + +TEST_CASE("GetManifests_BadResponse_RequiredQueryParameters", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : null, + "RequiredQueryParameters" : [ "Version" ] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + REQUIRE_THROWS_HR(v1_1.GetManifests("Foo"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); +} + +TEST_CASE("Search_BadRequest_UnsupportedPackageMatchFields", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [ + { "PackageVersion": "1.0.0" }, + { "PackageVersion": "2.0.0" }] + }] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + AppInstaller::Repository::SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Moniker, MatchType::Exact, "Foo" }; + request.Filters.emplace_back(std::move(filter)); + REQUIRE_THROWS_HR(v1_1.Search(request), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); +} + +TEST_CASE("Search_GoodRequest_OnlyMarketRequired", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [ + { "PackageVersion": "1.0.0" }, + { "PackageVersion": "2.0.0" }] + }] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + AppInstaller::Repository::SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "Foo" }; + request.Filters.emplace_back(std::move(filter)); + Schema::IRestClient::SearchResult searchResponse = v1_1.Search(request); + REQUIRE(searchResponse.Matches.size() == 1); + Schema::IRestClient::Package package = searchResponse.Matches.at(0); + REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); + REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); + REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); + REQUIRE(package.Versions.size() == 2); + REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); + REQUIRE(package.Versions.at(1).VersionAndChannel.GetVersion().ToString().compare("2.0.0") == 0); +} + +TEST_CASE("GetManifests_BadRequest_UnsupportedQueryParameters", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerType": "exe", + "InstallerUrl": "https://installer.example.com/foobar.exe" + } + ] + } + ] + } + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + REQUIRE_THROWS_HR(v1_1.GetManifestByVersion("Foo", "1.0", "beta"), APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST); +} + +TEST_CASE("GetManifests_GoodRequest_OnlyMarketRequired", "[RestSource][Interface_1_1]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerType": "exe", + "InstallerUrl": "https://installer.example.com/foobar.exe" + } + ] + } + ] + } + })delimiter"); + + IRestClient::Information info = GetTestSourceInformation(); + info.UnsupportedQueryParameters.clear(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), info, {} }; + auto manifestResult = v1_1.GetManifestByVersion("Foo", "5.0.0", ""); + REQUIRE(manifestResult.has_value()); + const Manifest& manifest = manifestResult.value(); + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "5.0.0"); + REQUIRE(manifest.DefaultLocalization.Locale == "en-us"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar license"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar description"); + REQUIRE(manifest.Installers.size() == 1); + REQUIRE(manifest.Installers[0].Arch == Architecture::X64); + REQUIRE(manifest.Installers[0].Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(manifest.Installers[0].BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(manifest.Installers[0].Url == "https://installer.example.com/foobar.exe"); +} + +TEST_CASE("GetManifests_GoodResponse_MSStoreType", "[RestSource][Interface_1_1]") +{ + utility::string_t msstoreInstallerResponse = _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerType": "msstore", + "MSStoreProductIdentifier": "9nblggh4nns1" + } + ] + } + ] + } + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(msstoreInstallerResponse)) }; + Interface v1_1{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {} }; + std::vector manifests = v1_1.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated and manifest validation passed + Manifest manifest = manifests[0]; + REQUIRE(manifest.Installers.size() == 1); + REQUIRE(manifest.Installers.at(0).BaseInstallerType == InstallerTypeEnum::MSStore); + REQUIRE(manifest.Installers.at(0).ProductId == "9nblggh4nns1"); +} + +TEST_CASE("GetManifests_GoodResponse_V1_1", "[RestSource][Interface_1_1]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_1{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_1.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.1.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_10.cpp b/src/AppInstallerCLITests/RestInterface_1_10.cpp index 87130fd366..8c002dcc31 100644 --- a/src/AppInstallerCLITests/RestInterface_1_10.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_10.cpp @@ -1,411 +1,411 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_10; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker", - "ReleaseNotes": "Default release notes", - "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "DefaultLabel", - "Agreement": "DefaultText", - "AgreementUrl": "https://DefaultAgreementUrl.net" - }], - "PurchaseUrl": "http://DefaultPurchaseUrl.net", - "InstallationNotes": "Default Installation Notes", - "Documentations": [{ - "DocumentLabel": "Default Document Label", - "DocumentUrl": "http://DefaultDocumentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://DefaultTestIcon", - "IconFileType": "ico", - "IconResolution": "custom", - "IconTheme": "default", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" - }] - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ], - "ReleaseNotes": "Release notes", - "ReleaseNotesUrl": "https://ReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "Label", - "Agreement": "Text", - "AgreementUrl": "https://AgreementUrl.net" - }], - "PurchaseUrl": "http://purchaseUrl.net", - "InstallationNotes": "Installation Notes", - "Documentations": [{ - "DocumentLabel": "Document Label", - "DocumentUrl": "http://documentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://testIcon", - "IconFileType": "png", - "IconResolution": "32x32", - "IconTheme": "light", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" - }] - } - ],)delimiter") _XPLATSTR(R"delimiter( - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.zip", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "zip", - "Scope": "user", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom", - "Repair": "/repair" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "deny", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", - "ReleaseDate": "2021-01-01", - "InstallerAbortsTerminal": true, - "InstallLocationRequired": true, - "RequireExplicitUpgrade": true, - "UnsupportedOSArchitectures": [ "arm" ], - "ElevationRequirement": "elevatesSelf", - "AppsAndFeaturesEntries": [{ - "DisplayName": "DisplayName", - "DisplayVersion": "DisplayVersion", - "Publisher": "Publisher", - "ProductCode": "ProductCode", - "UpgradeCode": "UpgradeCode", - "InstallerType": "exe" - }], - "Markets" : { - "AllowedMarkets": [ "US" ] - }, - "ExpectedReturnCodes": [{ - "InstallerReturnCode": 3, - "ReturnResponse": "custom", - "ReturnResponseUrl": "http://returnResponseUrl.net" - }], - "NestedInstallerType": "portable", - "DisplayInstallWarnings": true, - "UnsupportedArguments": [ "log" ], - "NestedInstallerFiles": [{ - "RelativeFilePath": "test\\app.exe", - "PortableCommandAlias": "test.exe" - }], - "InstallationMetadata": { - "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", - "Files": [{ - "RelativeFilePath": "test\\app.exe", - "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "FileType": "launch", - "InvocationParameter": "/parameter", - "DisplayName": "test" - }] - }, - "DownloadCommandProhibited": true, - "RepairBehavior": "uninstaller", - "ArchiveBinariesDependOnPath": true, - "Authentication": { - "AuthenticationType": "microsoftEntraId", - "MicrosoftEntraIdAuthenticationInfo" : { - "Resource": "TestResource", - "Scope" : "TestScope" - } - } - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - REQUIRE(frenchLocalization.Get() == "Release notes"); - REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); - REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); - REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); - REQUIRE(frenchLocalization.Get() == "Installation Notes"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); - REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); - REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); - } - - void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.zip"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 8); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == ""); - REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); - REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); - REQUIRE(actualInstaller.InstallerAbortsTerminal); - REQUIRE(actualInstaller.InstallLocationRequired); - REQUIRE(actualInstaller.RequireExplicitUpgrade); - REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); - REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); - REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(actualInstaller.DisplayInstallWarnings); - REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); - REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); - REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); - REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); - REQUIRE(actualInstaller.DownloadCommandProhibited); - REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); - REQUIRE(actualInstaller.ArchiveBinariesDependOnPath); - REQUIRE(actualInstaller.AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId); - REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo.has_value()); - REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo->Resource == "TestResource"); - REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo->Scope == "TestScope"); - } - }; -} - -TEST_CASE("GetManifests_GoodResponse_V1_10", "[RestSource][Interface_1_10]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_10{ TestRestUriString, std::move(helper), {} }; - std::vector manifests = v1_10.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - Manifest& manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.10.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_10; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom", + "Repair": "/repair" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "deny", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + }, + "DownloadCommandProhibited": true, + "RepairBehavior": "uninstaller", + "ArchiveBinariesDependOnPath": true, + "Authentication": { + "AuthenticationType": "microsoftEntraId", + "MicrosoftEntraIdAuthenticationInfo" : { + "Resource": "TestResource", + "Scope" : "TestScope" + } + } + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get() == "Installation Notes"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 8); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + REQUIRE(actualInstaller.DownloadCommandProhibited); + REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); + REQUIRE(actualInstaller.ArchiveBinariesDependOnPath); + REQUIRE(actualInstaller.AuthInfo.Type == AppInstaller::Authentication::AuthenticationType::MicrosoftEntraId); + REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo.has_value()); + REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo->Resource == "TestResource"); + REQUIRE(actualInstaller.AuthInfo.MicrosoftEntraIdInfo->Scope == "TestScope"); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_10", "[RestSource][Interface_1_10]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_10{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_10.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.10.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_4.cpp b/src/AppInstallerCLITests/RestInterface_1_4.cpp index 6787fbd047..fe5f2ecca0 100644 --- a/src/AppInstallerCLITests/RestInterface_1_4.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_4.cpp @@ -1,426 +1,426 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_4; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - IRestClient::Information GetTestSourceInformation() - { - IRestClient::Information result; - - result.RequiredPackageMatchFields.emplace_back("Market"); - result.RequiredQueryParameters.emplace_back("Market"); - result.UnsupportedPackageMatchFields.emplace_back("Moniker"); - result.UnsupportedQueryParameters.emplace_back("Channel"); - - return result; - } - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker", - "ReleaseNotes": "Default release notes", - "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "DefaultLabel", - "Agreement": "DefaultText", - "AgreementUrl": "https://DefaultAgreementUrl.net" - }], - "PurchaseUrl": "http://DefaultPurchaseUrl.net", - "InstallationNotes": "Default Installation Notes", - "Documentations": [{ - "DocumentLabel": "Default Document Label", - "DocumentUrl": "http://DefaultDocumentUrl.net" - }] - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ], - "ReleaseNotes": "Release notes", - "ReleaseNotesUrl": "https://ReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "Label", - "Agreement": "Text", - "AgreementUrl": "https://AgreementUrl.net" - }], - "PurchaseUrl": "http://purchaseUrl.net", - "InstallationNotes": "Installation Notes", - "Documentations": [{ - "DocumentLabel": "Document Label", - "DocumentUrl": "http://documentUrl.net" - }] - } - ], - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.zip", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "zip", - "Scope": "user", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "install", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", - "ReleaseDate": "2021-01-01", - "InstallerAbortsTerminal": true, - "InstallLocationRequired": true, - "RequireExplicitUpgrade": true, - "UnsupportedOSArchitectures": [ "arm" ], - "ElevationRequirement": "elevatesSelf", - "AppsAndFeaturesEntries": [{ - "DisplayName": "DisplayName", - "DisplayVersion": "DisplayVersion", - "Publisher": "Publisher", - "ProductCode": "ProductCode", - "UpgradeCode": "UpgradeCode", - "InstallerType": "exe" - }], - "Markets" : { - "AllowedMarkets": [ "US" ] - }, - "ExpectedReturnCodes": [{ - "InstallerReturnCode": 3, - "ReturnResponse": "custom", - "ReturnResponseUrl": "http://returnResponseUrl.net" - }], - "NestedInstallerType": "portable", - "DisplayInstallWarnings": true, - "UnsupportedArguments": [ "log" ], - "NestedInstallerFiles": [{ - "RelativeFilePath": "test\\app.exe", - "PortableCommandAlias": "test.exe" - }], - "InstallationMetadata": { - "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", - "Files": [{ - "RelativeFilePath": "test\\app.exe", - "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "FileType": "launch", - "InvocationParameter": "/parameter", - "DisplayName": "test" - }] - } - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - REQUIRE(frenchLocalization.Get() == "Release notes"); - REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); - REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); - REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); - REQUIRE(frenchLocalization.Get() == "Installation Notes"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); - REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); - } - - void VerifyInstallers_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.zip"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 7); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == ""); - REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); - REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); - REQUIRE(actualInstaller.InstallerAbortsTerminal); - REQUIRE(actualInstaller.InstallLocationRequired); - REQUIRE(actualInstaller.RequireExplicitUpgrade); - REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); - REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); - REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(actualInstaller.DisplayInstallWarnings); - REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); - REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); - REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); - REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); - } - }; -} - -TEST_CASE("GetManifests_GoodResponse_V1_4", "[RestSource][Interface_1_4]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_4{ TestRestUriString, std::move(helper), {} }; - std::vector manifests = v1_4.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - Manifest& manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.4.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} - -TEST_CASE("Search_GoodResponse_V1_4", "[RestSource][Interface_1_4]") -{ - utility::string_t sample = _XPLATSTR( - R"delimiter({ - "Data" : [{ - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [{ - "PackageVersion": "1.0.0", - "PackageFamilyNames": [ - "pfn1" - ], - "ProductCodes": [ - "pc1" - ], - "UpgradeCodes": [ - "upgradeCode" - ], - "AppsAndFeaturesEntryVersions": [ - "2.0", - "1.0" - ] - }] - }] - })delimiter"); - - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_4{ TestRestUriString, std::move(helper), {} }; - Schema::IRestClient::SearchResult searchResponse = v1_4.Search({}); - REQUIRE(searchResponse.Matches.size() == 1); - Schema::IRestClient::Package package = searchResponse.Matches.at(0); - REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); - REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); - REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); - REQUIRE(package.Versions.size() == 1); - REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); - REQUIRE(package.Versions.at(0).PackageFamilyNames.size() == 1); - REQUIRE(package.Versions.at(0).PackageFamilyNames.at(0) == "pfn1"); - REQUIRE(package.Versions.at(0).ProductCodes.size() == 1); - REQUIRE(package.Versions.at(0).ProductCodes.at(0) == "pc1"); - REQUIRE(package.Versions.at(0).UpgradeCodes.size() == 1); - REQUIRE(package.Versions.at(0).UpgradeCodes.at(0) == "upgradeCode"); - REQUIRE(package.Versions.at(0).ArpVersions.size() == 2); - REQUIRE(package.Versions.at(0).ArpVersions.at(0).ToString() == "1.0"); - REQUIRE(package.Versions.at(0).ArpVersions.at(1).ToString() == "2.0"); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_4; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + IRestClient::Information GetTestSourceInformation() + { + IRestClient::Information result; + + result.RequiredPackageMatchFields.emplace_back("Market"); + result.RequiredQueryParameters.emplace_back("Market"); + result.UnsupportedPackageMatchFields.emplace_back("Moniker"); + result.UnsupportedQueryParameters.emplace_back("Channel"); + + return result; + } + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }] + } + ], + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "install", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + } + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get() == "Installation Notes"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); + } + + void VerifyInstallers_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 7); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_4", "[RestSource][Interface_1_4]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_4{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_4.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.4.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} + +TEST_CASE("Search_GoodResponse_V1_4", "[RestSource][Interface_1_4]") +{ + utility::string_t sample = _XPLATSTR( + R"delimiter({ + "Data" : [{ + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [{ + "PackageVersion": "1.0.0", + "PackageFamilyNames": [ + "pfn1" + ], + "ProductCodes": [ + "pc1" + ], + "UpgradeCodes": [ + "upgradeCode" + ], + "AppsAndFeaturesEntryVersions": [ + "2.0", + "1.0" + ] + }] + }] + })delimiter"); + + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_4{ TestRestUriString, std::move(helper), {} }; + Schema::IRestClient::SearchResult searchResponse = v1_4.Search({}); + REQUIRE(searchResponse.Matches.size() == 1); + Schema::IRestClient::Package package = searchResponse.Matches.at(0); + REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); + REQUIRE(package.PackageInformation.Publisher.compare("git") == 0); + REQUIRE(package.PackageInformation.PackageName.compare("package") == 0); + REQUIRE(package.Versions.size() == 1); + REQUIRE(package.Versions.at(0).VersionAndChannel.GetVersion().ToString().compare("1.0.0") == 0); + REQUIRE(package.Versions.at(0).PackageFamilyNames.size() == 1); + REQUIRE(package.Versions.at(0).PackageFamilyNames.at(0) == "pfn1"); + REQUIRE(package.Versions.at(0).ProductCodes.size() == 1); + REQUIRE(package.Versions.at(0).ProductCodes.at(0) == "pc1"); + REQUIRE(package.Versions.at(0).UpgradeCodes.size() == 1); + REQUIRE(package.Versions.at(0).UpgradeCodes.at(0) == "upgradeCode"); + REQUIRE(package.Versions.at(0).ArpVersions.size() == 2); + REQUIRE(package.Versions.at(0).ArpVersions.at(0).ToString() == "1.0"); + REQUIRE(package.Versions.at(0).ArpVersions.at(1).ToString() == "2.0"); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_5.cpp b/src/AppInstallerCLITests/RestInterface_1_5.cpp index 8213f67414..f8207dd20f 100644 --- a/src/AppInstallerCLITests/RestInterface_1_5.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_5.cpp @@ -1,404 +1,404 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_5; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - IRestClient::Information GetTestSourceInformation() - { - IRestClient::Information result; - - result.RequiredPackageMatchFields.emplace_back("Market"); - result.RequiredQueryParameters.emplace_back("Market"); - result.UnsupportedPackageMatchFields.emplace_back("Moniker"); - result.UnsupportedQueryParameters.emplace_back("Channel"); - - return result; - } - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker", - "ReleaseNotes": "Default release notes", - "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "DefaultLabel", - "Agreement": "DefaultText", - "AgreementUrl": "https://DefaultAgreementUrl.net" - }], - "PurchaseUrl": "http://DefaultPurchaseUrl.net", - "InstallationNotes": "Default Installation Notes", - "Documentations": [{ - "DocumentLabel": "Default Document Label", - "DocumentUrl": "http://DefaultDocumentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://DefaultTestIcon", - "IconFileType": "ico", - "IconResolution": "custom", - "IconTheme": "default", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" - }] - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ], - "ReleaseNotes": "Release notes", - "ReleaseNotesUrl": "https://ReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "Label", - "Agreement": "Text", - "AgreementUrl": "https://AgreementUrl.net" - }], - "PurchaseUrl": "http://purchaseUrl.net", - "InstallationNotes": "Installation Notes", - "Documentations": [{ - "DocumentLabel": "Document Label", - "DocumentUrl": "http://documentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://testIcon", - "IconFileType": "png", - "IconResolution": "32x32", - "IconTheme": "light", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" - }] - } - ],)delimiter") _XPLATSTR(R"delimiter( - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.zip", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "zip", - "Scope": "user", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "install", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", - "ReleaseDate": "2021-01-01", - "InstallerAbortsTerminal": true, - "InstallLocationRequired": true, - "RequireExplicitUpgrade": true, - "UnsupportedOSArchitectures": [ "arm" ], - "ElevationRequirement": "elevatesSelf", - "AppsAndFeaturesEntries": [{ - "DisplayName": "DisplayName", - "DisplayVersion": "DisplayVersion", - "Publisher": "Publisher", - "ProductCode": "ProductCode", - "UpgradeCode": "UpgradeCode", - "InstallerType": "exe" - }], - "Markets" : { - "AllowedMarkets": [ "US" ] - }, - "ExpectedReturnCodes": [{ - "InstallerReturnCode": 3, - "ReturnResponse": "custom", - "ReturnResponseUrl": "http://returnResponseUrl.net" - }], - "NestedInstallerType": "portable", - "DisplayInstallWarnings": true, - "UnsupportedArguments": [ "log" ], - "NestedInstallerFiles": [{ - "RelativeFilePath": "test\\app.exe", - "PortableCommandAlias": "test.exe" - }], - "InstallationMetadata": { - "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", - "Files": [{ - "RelativeFilePath": "test\\app.exe", - "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "FileType": "launch", - "InvocationParameter": "/parameter", - "DisplayName": "test" - }] - } - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - REQUIRE(frenchLocalization.Get() == "Release notes"); - REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); - REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); - REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); - REQUIRE(frenchLocalization.Get() == "Installation Notes"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); - REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); - REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); - } - - void VerifyInstallers_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.zip"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 7); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == ""); - REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); - REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); - REQUIRE(actualInstaller.InstallerAbortsTerminal); - REQUIRE(actualInstaller.InstallLocationRequired); - REQUIRE(actualInstaller.RequireExplicitUpgrade); - REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); - REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); - REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(actualInstaller.DisplayInstallWarnings); - REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); - REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); - REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); - REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); - } - }; -} - -TEST_CASE("GetManifests_GoodResponse_V1_5", "[RestSource][Interface_1_5]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_5{ TestRestUriString, std::move(helper), {} }; - std::vector manifests = v1_5.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - Manifest& manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.5.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_5; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + IRestClient::Information GetTestSourceInformation() + { + IRestClient::Information result; + + result.RequiredPackageMatchFields.emplace_back("Market"); + result.RequiredQueryParameters.emplace_back("Market"); + result.UnsupportedPackageMatchFields.emplace_back("Moniker"); + result.UnsupportedQueryParameters.emplace_back("Channel"); + + return result; + } + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "install", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + } + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get() == "Installation Notes"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 7); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_5", "[RestSource][Interface_1_5]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_5{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_5.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.5.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_6.cpp b/src/AppInstallerCLITests/RestInterface_1_6.cpp index 8b80b9fc1d..17540a91e0 100644 --- a/src/AppInstallerCLITests/RestInterface_1_6.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_6.cpp @@ -1,406 +1,406 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_6; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - IRestClient::Information GetTestSourceInformation() - { - IRestClient::Information result; - - result.RequiredPackageMatchFields.emplace_back("Market"); - result.RequiredQueryParameters.emplace_back("Market"); - result.UnsupportedPackageMatchFields.emplace_back("Moniker"); - result.UnsupportedQueryParameters.emplace_back("Channel"); - - return result; - } - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker", - "ReleaseNotes": "Default release notes", - "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "DefaultLabel", - "Agreement": "DefaultText", - "AgreementUrl": "https://DefaultAgreementUrl.net" - }], - "PurchaseUrl": "http://DefaultPurchaseUrl.net", - "InstallationNotes": "Default Installation Notes", - "Documentations": [{ - "DocumentLabel": "Default Document Label", - "DocumentUrl": "http://DefaultDocumentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://DefaultTestIcon", - "IconFileType": "ico", - "IconResolution": "custom", - "IconTheme": "default", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" - }] - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ], - "ReleaseNotes": "Release notes", - "ReleaseNotesUrl": "https://ReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "Label", - "Agreement": "Text", - "AgreementUrl": "https://AgreementUrl.net" - }], - "PurchaseUrl": "http://purchaseUrl.net", - "InstallationNotes": "Installation Notes", - "Documentations": [{ - "DocumentLabel": "Document Label", - "DocumentUrl": "http://documentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://testIcon", - "IconFileType": "png", - "IconResolution": "32x32", - "IconTheme": "light", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" - }] - } - ],)delimiter") _XPLATSTR(R"delimiter( - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.zip", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "zip", - "Scope": "user", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "deny", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", - "ReleaseDate": "2021-01-01", - "InstallerAbortsTerminal": true, - "InstallLocationRequired": true, - "RequireExplicitUpgrade": true, - "UnsupportedOSArchitectures": [ "arm" ], - "ElevationRequirement": "elevatesSelf", - "AppsAndFeaturesEntries": [{ - "DisplayName": "DisplayName", - "DisplayVersion": "DisplayVersion", - "Publisher": "Publisher", - "ProductCode": "ProductCode", - "UpgradeCode": "UpgradeCode", - "InstallerType": "exe" - }], - "Markets" : { - "AllowedMarkets": [ "US" ] - }, - "ExpectedReturnCodes": [{ - "InstallerReturnCode": 3, - "ReturnResponse": "custom", - "ReturnResponseUrl": "http://returnResponseUrl.net" - }], - "NestedInstallerType": "portable", - "DisplayInstallWarnings": true, - "UnsupportedArguments": [ "log" ], - "NestedInstallerFiles": [{ - "RelativeFilePath": "test\\app.exe", - "PortableCommandAlias": "test.exe" - }], - "InstallationMetadata": { - "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", - "Files": [{ - "RelativeFilePath": "test\\app.exe", - "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "FileType": "launch", - "InvocationParameter": "/parameter", - "DisplayName": "test" - }] - }, - "DownloadCommandProhibited": true - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - REQUIRE(frenchLocalization.Get() == "Release notes"); - REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); - REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); - REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); - REQUIRE(frenchLocalization.Get() == "Installation Notes"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); - REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); - REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); - } - - void VerifyInstallers_AllFields(const Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.zip"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 7); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == ""); - REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); - REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); - REQUIRE(actualInstaller.InstallerAbortsTerminal); - REQUIRE(actualInstaller.InstallLocationRequired); - REQUIRE(actualInstaller.RequireExplicitUpgrade); - REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); - REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); - REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(actualInstaller.DisplayInstallWarnings); - REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); - REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); - REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); - REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); - REQUIRE(actualInstaller.DownloadCommandProhibited); - } - }; -} - -TEST_CASE("GetManifests_GoodResponse_V1_6", "[RestSource][Interface_1_6]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_6{ TestRestUriString, std::move(helper), {} }; - std::vector manifests = v1_6.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - Manifest& manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.6.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_6; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + IRestClient::Information GetTestSourceInformation() + { + IRestClient::Information result; + + result.RequiredPackageMatchFields.emplace_back("Market"); + result.RequiredQueryParameters.emplace_back("Market"); + result.UnsupportedPackageMatchFields.emplace_back("Moniker"); + result.UnsupportedQueryParameters.emplace_back("Channel"); + + return result; + } + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "deny", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + }, + "DownloadCommandProhibited": true + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get() == "Installation Notes"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 7); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + REQUIRE(actualInstaller.DownloadCommandProhibited); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_6", "[RestSource][Interface_1_6]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_6{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_6.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.6.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_7.cpp b/src/AppInstallerCLITests/RestInterface_1_7.cpp index 8aaad82135..1d9da5af9a 100644 --- a/src/AppInstallerCLITests/RestInterface_1_7.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_7.cpp @@ -1,607 +1,607 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestHooks.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::Authentication; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_7; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - IRestClient::Information GetTestSourceInformation() - { - IRestClient::Information result; - - result.Authentication.Type = AuthenticationType::MicrosoftEntraId; - MicrosoftEntraIdAuthenticationInfo microsoftEntraIdInfo; - microsoftEntraIdInfo.Resource = "GUID"; - result.Authentication.MicrosoftEntraIdInfo = std::move(microsoftEntraIdInfo); - - return result; - } - - AuthenticationArguments GetTestAuthenticationArguments() - { - AuthenticationArguments result; - result.Mode = AuthenticationMode::Silent; - return result; - } - - utility::string_t SampleSearchResponse = _XPLATSTR( - R"delimiter({ - "Data" : [ - { - "PackageIdentifier": "git.package", - "PackageName": "package", - "Publisher": "git", - "Versions": [ - { "PackageVersion": "1.0.0" }] - }] - })delimiter"); - - utility::string_t SampleGetManifestResponse = _XPLATSTR( - R"delimiter({ - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "5.0.0", - "DefaultLocale": { - "PackageLocale": "en-us", - "Publisher": "Foo", - "PackageName": "Bar", - "License": "Foo bar license", - "ShortDescription": "Foo bar description" - }, - "Installers": [ - { - "Architecture": "x64", - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerType": "exe", - "InstallerUrl": "https://installer.example.com/foobar.exe" - } - ] - } - ] - } - })delimiter"); - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker", - "ReleaseNotes": "Default release notes", - "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "DefaultLabel", - "Agreement": "DefaultText", - "AgreementUrl": "https://DefaultAgreementUrl.net" - }], - "PurchaseUrl": "http://DefaultPurchaseUrl.net", - "InstallationNotes": "Default Installation Notes", - "Documentations": [{ - "DocumentLabel": "Default Document Label", - "DocumentUrl": "http://DefaultDocumentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://DefaultTestIcon", - "IconFileType": "ico", - "IconResolution": "custom", - "IconTheme": "default", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" - }] - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ], - "ReleaseNotes": "Release notes", - "ReleaseNotesUrl": "https://ReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "Label", - "Agreement": "Text", - "AgreementUrl": "https://AgreementUrl.net" - }], - "PurchaseUrl": "http://purchaseUrl.net", - "InstallationNotes": "Installation Notes", - "Documentations": [{ - "DocumentLabel": "Document Label", - "DocumentUrl": "http://documentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://testIcon", - "IconFileType": "png", - "IconResolution": "32x32", - "IconTheme": "light", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" - }] - } - ],)delimiter") _XPLATSTR(R"delimiter( - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.zip", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "zip", - "Scope": "user", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom", - "Repair": "/repair" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "deny", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", - "ReleaseDate": "2021-01-01", - "InstallerAbortsTerminal": true, - "InstallLocationRequired": true, - "RequireExplicitUpgrade": true, - "UnsupportedOSArchitectures": [ "arm" ], - "ElevationRequirement": "elevatesSelf", - "AppsAndFeaturesEntries": [{ - "DisplayName": "DisplayName", - "DisplayVersion": "DisplayVersion", - "Publisher": "Publisher", - "ProductCode": "ProductCode", - "UpgradeCode": "UpgradeCode", - "InstallerType": "exe" - }], - "Markets" : { - "AllowedMarkets": [ "US" ] - }, - "ExpectedReturnCodes": [{ - "InstallerReturnCode": 3, - "ReturnResponse": "custom", - "ReturnResponseUrl": "http://returnResponseUrl.net" - }], - "NestedInstallerType": "portable", - "DisplayInstallWarnings": true, - "UnsupportedArguments": [ "log" ], - "NestedInstallerFiles": [{ - "RelativeFilePath": "test\\app.exe", - "PortableCommandAlias": "test.exe" - }], - "InstallationMetadata": { - "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", - "Files": [{ - "RelativeFilePath": "test\\app.exe", - "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "FileType": "launch", - "InvocationParameter": "/parameter", - "DisplayName": "test" - }] - }, - "DownloadCommandProhibited": true, - "RepairBehavior": "uninstaller" - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - REQUIRE(frenchLocalization.Get() == "Release notes"); - REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); - REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); - REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); - REQUIRE(frenchLocalization.Get() == "Installation Notes"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); - REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); - REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); - } - - void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.zip"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 8); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == ""); - REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); - REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); - REQUIRE(actualInstaller.InstallerAbortsTerminal); - REQUIRE(actualInstaller.InstallLocationRequired); - REQUIRE(actualInstaller.RequireExplicitUpgrade); - REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); - REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); - REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(actualInstaller.DisplayInstallWarnings); - REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); - REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); - REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); - REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); - REQUIRE(actualInstaller.DownloadCommandProhibited); - REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); - } - }; -} - -TEST_CASE("GetManifests_GoodRequest_Authentication", "[RestSource][Interface_1_7]") -{ - if (Runtime::IsRunningAsSystem()) - { - WARN("Test does not support running as system. Skipped."); - return; - } - - std::string expectedToken = "TestToken"; - - // Set good authentication result - AuthenticationResult authResultOverride; - authResultOverride.Status = S_OK; - authResultOverride.Token = expectedToken; - TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); - - // GetManifest should succeed with expected value. - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; - auto manifestResult = v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""); - REQUIRE(manifestResult.has_value()); - const auto& manifest = manifestResult.value(); - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "5.0.0"); -} - -TEST_CASE("GetManifests_BadRequest_AuthenticationFailed", "[RestSource][Interface_1_7]") -{ - if (Runtime::IsRunningAsSystem()) - { - WARN("Test does not support running as system. Skipped."); - return; - } - - std::string expectedToken = "TestToken"; - - // Set authentication failed result - AuthenticationResult authResultOverride; - authResultOverride.Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED; - TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); - - // GetManifest should fail with authentication failure - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; - REQUIRE_THROWS_HR(v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""), APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED); -} - -TEST_CASE("GetManifests_BadRequest_InvalidAuthenticationToken", "[RestSource][Interface_1_7]") -{ - if (Runtime::IsRunningAsSystem()) - { - WARN("Test does not support running as system. Skipped."); - return; - } - - std::string expectedToken = "TestToken"; - - // Set authentication result with incorrect token - AuthenticationResult authResultOverride; - authResultOverride.Status = S_OK; - authResultOverride.Token = "OtherToken"; - TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); - - // GetManifest should fail with access denied - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; - REQUIRE_THROWS_HR(v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""), HTTP_E_STATUS_DENIED); -} - -TEST_CASE("Search_GoodRequest_Authentication", "[RestSource][Interface_1_7]") -{ - if (Runtime::IsRunningAsSystem()) - { - WARN("Test does not support running as system. Skipped."); - return; - } - - std::string expectedToken = "TestToken"; - - // Set good authentication result - AuthenticationResult authResultOverride; - authResultOverride.Status = S_OK; - authResultOverride.Token = expectedToken; - TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); - - // Search should succeed with expected value. - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; - SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; - request.Filters.emplace_back(std::move(filter)); - IRestClient::SearchResult searchResponse = v1_7.Search(request); - REQUIRE(searchResponse.Matches.size() == 1); - IRestClient::Package package = searchResponse.Matches.at(0); - REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); -} - -TEST_CASE("Search_BadRequest_AuthenticationFailed", "[RestSource][Interface_1_7]") -{ - if (Runtime::IsRunningAsSystem()) - { - WARN("Test does not support running as system. Skipped."); - return; - } - - std::string expectedToken = "TestToken"; - - // Set authentication failed result - AuthenticationResult authResultOverride; - authResultOverride.Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED; - TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); - - // Search should fail with authentication failure - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; - SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; - request.Filters.emplace_back(std::move(filter)); - REQUIRE_THROWS_HR(v1_7.Search(request), APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED); -} - -TEST_CASE("Search_BadRequest_InvalidAuthenticationToken", "[RestSource][Interface_1_7]") -{ - if (Runtime::IsRunningAsSystem()) - { - WARN("Test does not support running as system. Skipped."); - return; - } - - std::string expectedToken = "TestToken"; - - // Set authentication result with incorrect token - AuthenticationResult authResultOverride; - authResultOverride.Status = S_OK; - authResultOverride.Token = "OtherToken"; - TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); - - // Search should fail with access denied - HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; - Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; - SearchRequest request; - PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; - request.Filters.emplace_back(std::move(filter)); - REQUIRE_THROWS_HR(v1_7.Search(request), HTTP_E_STATUS_DENIED); -} - -TEST_CASE("GetManifests_GoodResponse_V1_7", "[RestSource][Interface_1_7]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_7{ TestRestUriString, std::move(helper), {} }; - std::vector manifests = v1_7.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - AppInstaller::Manifest::Manifest& manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.7.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestHooks.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Authentication; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_7; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + IRestClient::Information GetTestSourceInformation() + { + IRestClient::Information result; + + result.Authentication.Type = AuthenticationType::MicrosoftEntraId; + MicrosoftEntraIdAuthenticationInfo microsoftEntraIdInfo; + microsoftEntraIdInfo.Resource = "GUID"; + result.Authentication.MicrosoftEntraIdInfo = std::move(microsoftEntraIdInfo); + + return result; + } + + AuthenticationArguments GetTestAuthenticationArguments() + { + AuthenticationArguments result; + result.Mode = AuthenticationMode::Silent; + return result; + } + + utility::string_t SampleSearchResponse = _XPLATSTR( + R"delimiter({ + "Data" : [ + { + "PackageIdentifier": "git.package", + "PackageName": "package", + "Publisher": "git", + "Versions": [ + { "PackageVersion": "1.0.0" }] + }] + })delimiter"); + + utility::string_t SampleGetManifestResponse = _XPLATSTR( + R"delimiter({ + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "5.0.0", + "DefaultLocale": { + "PackageLocale": "en-us", + "Publisher": "Foo", + "PackageName": "Bar", + "License": "Foo bar license", + "ShortDescription": "Foo bar description" + }, + "Installers": [ + { + "Architecture": "x64", + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerType": "exe", + "InstallerUrl": "https://installer.example.com/foobar.exe" + } + ] + } + ] + } + })delimiter"); + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom", + "Repair": "/repair" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "deny", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + }, + "DownloadCommandProhibited": true, + "RepairBehavior": "uninstaller" + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get() == "Installation Notes"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 8); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + REQUIRE(actualInstaller.DownloadCommandProhibited); + REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); + } + }; +} + +TEST_CASE("GetManifests_GoodRequest_Authentication", "[RestSource][Interface_1_7]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + std::string expectedToken = "TestToken"; + + // Set good authentication result + AuthenticationResult authResultOverride; + authResultOverride.Status = S_OK; + authResultOverride.Token = expectedToken; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + // GetManifest should succeed with expected value. + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; + auto manifestResult = v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""); + REQUIRE(manifestResult.has_value()); + const auto& manifest = manifestResult.value(); + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "5.0.0"); +} + +TEST_CASE("GetManifests_BadRequest_AuthenticationFailed", "[RestSource][Interface_1_7]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + std::string expectedToken = "TestToken"; + + // Set authentication failed result + AuthenticationResult authResultOverride; + authResultOverride.Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + // GetManifest should fail with authentication failure + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; + REQUIRE_THROWS_HR(v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""), APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED); +} + +TEST_CASE("GetManifests_BadRequest_InvalidAuthenticationToken", "[RestSource][Interface_1_7]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + std::string expectedToken = "TestToken"; + + // Set authentication result with incorrect token + AuthenticationResult authResultOverride; + authResultOverride.Status = S_OK; + authResultOverride.Token = "OtherToken"; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + // GetManifest should fail with access denied + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleGetManifestResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; + REQUIRE_THROWS_HR(v1_7.GetManifestByVersion("Foo.Bar", "5.0.0", ""), HTTP_E_STATUS_DENIED); +} + +TEST_CASE("Search_GoodRequest_Authentication", "[RestSource][Interface_1_7]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + std::string expectedToken = "TestToken"; + + // Set good authentication result + AuthenticationResult authResultOverride; + authResultOverride.Status = S_OK; + authResultOverride.Token = expectedToken; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + // Search should succeed with expected value. + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; + SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; + request.Filters.emplace_back(std::move(filter)); + IRestClient::SearchResult searchResponse = v1_7.Search(request); + REQUIRE(searchResponse.Matches.size() == 1); + IRestClient::Package package = searchResponse.Matches.at(0); + REQUIRE(package.PackageInformation.PackageIdentifier.compare("git.package") == 0); +} + +TEST_CASE("Search_BadRequest_AuthenticationFailed", "[RestSource][Interface_1_7]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + std::string expectedToken = "TestToken"; + + // Set authentication failed result + AuthenticationResult authResultOverride; + authResultOverride.Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + // Search should fail with authentication failure + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; + SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; + request.Filters.emplace_back(std::move(filter)); + REQUIRE_THROWS_HR(v1_7.Search(request), APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED); +} + +TEST_CASE("Search_BadRequest_InvalidAuthenticationToken", "[RestSource][Interface_1_7]") +{ + if (Runtime::IsRunningAsSystem()) + { + WARN("Test does not support running as system. Skipped."); + return; + } + + std::string expectedToken = "TestToken"; + + // Set authentication result with incorrect token + AuthenticationResult authResultOverride; + authResultOverride.Status = S_OK; + authResultOverride.Token = "OtherToken"; + TestHook::SetAuthenticationResult_Override setAuthenticationResultOverride(authResultOverride); + + // Search should fail with access denied + HttpClientHelper helper{ GetHeaderVerificationHandler(web::http::status_codes::OK, SampleSearchResponse, { web::http::header_names::authorization, JSON::GetUtilityString(CreateBearerToken(expectedToken)) }, web::http::status_codes::Unauthorized) }; + Interface v1_7{ TestRestUriString, std::move(helper), GetTestSourceInformation(), {}, GetTestAuthenticationArguments() }; + SearchRequest request; + PackageMatchFilter filter{ PackageMatchField::Name, MatchType::Exact, "package" }; + request.Filters.emplace_back(std::move(filter)); + REQUIRE_THROWS_HR(v1_7.Search(request), HTTP_E_STATUS_DENIED); +} + +TEST_CASE("GetManifests_GoodResponse_V1_7", "[RestSource][Interface_1_7]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_7{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_7.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + AppInstaller::Manifest::Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.7.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/RestInterface_1_9.cpp b/src/AppInstallerCLITests/RestInterface_1_9.cpp index e33480dd19..e9763ae2ba 100644 --- a/src/AppInstallerCLITests/RestInterface_1_9.cpp +++ b/src/AppInstallerCLITests/RestInterface_1_9.cpp @@ -1,400 +1,400 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Http; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest; -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_9; - -namespace -{ - const std::string TestRestUriString = "http://restsource.com/api"; - - struct GoodManifest_AllFields - { - utility::string_t GetSampleManifest_AllFields() - { - return _XPLATSTR( - R"delimiter( - { - "Data": { - "PackageIdentifier": "Foo.Bar", - "Versions": [ - { - "PackageVersion": "3.0.0abc", - "DefaultLocale": { - "PackageLocale": "en-US", - "Publisher": "Foo", - "PublisherUrl": "http://publisher.net", - "PublisherSupportUrl": "http://publisherSupport.net", - "PrivacyUrl": "http://packagePrivacyUrl.net", - "Author": "FooBar", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl.net", - "ShortDescription": "Foo bar is a foo bar.", - "Description": "Foo bar is a placeholder.", - "Tags": [ - "FooBar", - "Foo", - "Bar" - ], - "Moniker": "FooBarMoniker", - "ReleaseNotes": "Default release notes", - "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "DefaultLabel", - "Agreement": "DefaultText", - "AgreementUrl": "https://DefaultAgreementUrl.net" - }], - "PurchaseUrl": "http://DefaultPurchaseUrl.net", - "InstallationNotes": "Default Installation Notes", - "Documentations": [{ - "DocumentLabel": "Default Document Label", - "DocumentUrl": "http://DefaultDocumentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://DefaultTestIcon", - "IconFileType": "ico", - "IconResolution": "custom", - "IconTheme": "default", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" - }] - }, - "Channel": "", - "Locales": [ - { - "PackageLocale": "fr-Fr", - "Publisher": "Foo French", - "PublisherUrl": "http://publisher-fr.net", - "PublisherSupportUrl": "http://publisherSupport-fr.net", - "PrivacyUrl": "http://packagePrivacyUrl-fr.net", - "Author": "FooBar French", - "PackageName": "Bar", - "PackageUrl": "http://packageUrl-fr.net", - "License": "Foo Bar License", - "LicenseUrl": "http://licenseUrl-fr.net", - "Copyright": "Foo Bar Copyright", - "CopyrightUrl": "http://copyrightUrl-fr.net", - "ShortDescription": "Foo bar is a foo bar French.", - "Description": "Foo bar is a placeholder French.", - "Tags": [ - "FooBarFr", - "FooFr", - "BarFr" - ], - "ReleaseNotes": "Release notes", - "ReleaseNotesUrl": "https://ReleaseNotes.net", - "Agreements": [{ - "AgreementLabel": "Label", - "Agreement": "Text", - "AgreementUrl": "https://AgreementUrl.net" - }], - "PurchaseUrl": "http://purchaseUrl.net", - "InstallationNotes": "Installation Notes", - "Documentations": [{ - "DocumentLabel": "Document Label", - "DocumentUrl": "http://documentUrl.net" - }], - "Icons": [{ - "IconUrl": "https://testIcon", - "IconFileType": "png", - "IconResolution": "32x32", - "IconTheme": "light", - "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" - }] - } - ],)delimiter") _XPLATSTR(R"delimiter( - "Installers": [ - { - "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "InstallerUrl": "http://foobar.zip", - "Architecture": "x86", - "InstallerLocale": "en-US", - "Platform": [ - "Windows.Desktop" - ], - "MinimumOSVersion": "1078", - "InstallerType": "zip", - "Scope": "user", - "InstallModes": [ - "interactive" - ], - "InstallerSwitches": { - "Silent": "/s", - "SilentWithProgress": "/s", - "Interactive": "/i", - "InstallLocation": "C:\\Users\\User1", - "Log": "/l", - "Upgrade": "/u", - "Custom": "/custom", - "Repair": "/repair" - }, - "InstallerSuccessCodes": [ - 0 - ], - "UpgradeBehavior": "deny", - "Commands": [ - "command1" - ], - "Protocols": [ - "protocol1" - ], - "FileExtensions": [ - ".file-extension" - ], - "Dependencies": { - "WindowsFeatures": [ - "feature1" - ], - "WindowsLibraries": [ - "library1" - ], - "PackageDependencies": [ - { - "PackageIdentifier": "Foo.Baz", - "MinimumVersion": "2.0.0" - } - ], - "ExternalDependencies": [ - "FooBarBaz" - ] - }, - "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", - "ReleaseDate": "2021-01-01", - "InstallerAbortsTerminal": true, - "InstallLocationRequired": true, - "RequireExplicitUpgrade": true, - "UnsupportedOSArchitectures": [ "arm" ], - "ElevationRequirement": "elevatesSelf", - "AppsAndFeaturesEntries": [{ - "DisplayName": "DisplayName", - "DisplayVersion": "DisplayVersion", - "Publisher": "Publisher", - "ProductCode": "ProductCode", - "UpgradeCode": "UpgradeCode", - "InstallerType": "exe" - }], - "Markets" : { - "AllowedMarkets": [ "US" ] - }, - "ExpectedReturnCodes": [{ - "InstallerReturnCode": 3, - "ReturnResponse": "custom", - "ReturnResponseUrl": "http://returnResponseUrl.net" - }], - "NestedInstallerType": "portable", - "DisplayInstallWarnings": true, - "UnsupportedArguments": [ "log" ], - "NestedInstallerFiles": [{ - "RelativeFilePath": "test\\app.exe", - "PortableCommandAlias": "test.exe" - }], - "InstallationMetadata": { - "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", - "Files": [{ - "RelativeFilePath": "test\\app.exe", - "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", - "FileType": "launch", - "InvocationParameter": "/parameter", - "DisplayName": "test" - }] - }, - "DownloadCommandProhibited": true, - "RepairBehavior": "uninstaller", - "ArchiveBinariesDependOnPath": true - } - ] - } - ] - }, - "ContinuationToken": "abcd" - })delimiter"); - } - - void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest) - { - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); - REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); - REQUIRE(manifest.DefaultLocalization.Get().size() == 3); - REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); - REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); - REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); - - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization frenchLocalization = manifest.Localizations.at(0); - REQUIRE(frenchLocalization.Locale == "fr-Fr"); - REQUIRE(frenchLocalization.Get() == "Foo French"); - REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); - REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "FooBar French"); - REQUIRE(frenchLocalization.Get() == "Bar"); - REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar License"); - REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); - REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); - REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); - REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); - REQUIRE(frenchLocalization.Get().size() == 3); - REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); - REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); - REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); - REQUIRE(frenchLocalization.Get() == "Release notes"); - REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); - REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); - REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); - REQUIRE(frenchLocalization.Get() == "Installation Notes"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); - REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); - REQUIRE(frenchLocalization.Get().size() == 1); - REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); - REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); - } - - void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest) - { - REQUIRE(manifest.Installers.size() == 1); - - ManifestInstaller actualInstaller = manifest.Installers.at(0); - REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.Url == "http://foobar.zip"); - REQUIRE(actualInstaller.Arch == Architecture::X86); - REQUIRE(actualInstaller.Locale == "en-US"); - REQUIRE(actualInstaller.Platform.size() == 1); - REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); - REQUIRE(actualInstaller.MinOSVersion == "1078"); - REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(actualInstaller.Scope == ScopeEnum::User); - REQUIRE(actualInstaller.InstallModes.size() == 1); - REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); - REQUIRE(actualInstaller.Switches.size() == 8); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair"); - REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); - REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); - REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); - REQUIRE(actualInstaller.Commands.at(0) == "command1"); - REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); - REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); - REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); - REQUIRE(actualInstaller.PackageFamilyName == ""); - REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); - REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); - REQUIRE(actualInstaller.InstallerAbortsTerminal); - REQUIRE(actualInstaller.InstallLocationRequired); - REQUIRE(actualInstaller.RequireExplicitUpgrade); - REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); - REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); - REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); - REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(actualInstaller.DisplayInstallWarnings); - REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); - REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); - REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); - REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); - REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); - REQUIRE(actualInstaller.DownloadCommandProhibited); - REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); - REQUIRE(actualInstaller.ArchiveBinariesDependOnPath); - } - }; -} - -TEST_CASE("GetManifests_GoodResponse_V1_9", "[RestSource][Interface_1_9]") -{ - GoodManifest_AllFields sampleManifest; - utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); - HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; - Interface v1_9{ TestRestUriString, std::move(helper), {} }; - std::vector manifests = v1_9.GetManifests("Foo.Bar"); - REQUIRE(manifests.size() == 1); - - // Verify manifest is populated - Manifest& manifest = manifests[0]; - REQUIRE(manifest.Id == "Foo.Bar"); - REQUIRE(manifest.Version == "3.0.0abc"); - REQUIRE(manifest.Moniker == "FooBarMoniker"); - REQUIRE(manifest.Channel == ""); - REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.9.0" }); - sampleManifest.VerifyLocalizations_AllFields(manifest); - sampleManifest.VerifyInstallers_AllFields(manifest); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Http; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest; +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_9; + +namespace +{ + const std::string TestRestUriString = "http://restsource.com/api"; + + struct GoodManifest_AllFields + { + utility::string_t GetSampleManifest_AllFields() + { + return _XPLATSTR( + R"delimiter( + { + "Data": { + "PackageIdentifier": "Foo.Bar", + "Versions": [ + { + "PackageVersion": "3.0.0abc", + "DefaultLocale": { + "PackageLocale": "en-US", + "Publisher": "Foo", + "PublisherUrl": "http://publisher.net", + "PublisherSupportUrl": "http://publisherSupport.net", + "PrivacyUrl": "http://packagePrivacyUrl.net", + "Author": "FooBar", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl.net", + "ShortDescription": "Foo bar is a foo bar.", + "Description": "Foo bar is a placeholder.", + "Tags": [ + "FooBar", + "Foo", + "Bar" + ], + "Moniker": "FooBarMoniker", + "ReleaseNotes": "Default release notes", + "ReleaseNotesUrl": "https://DefaultReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "DefaultLabel", + "Agreement": "DefaultText", + "AgreementUrl": "https://DefaultAgreementUrl.net" + }], + "PurchaseUrl": "http://DefaultPurchaseUrl.net", + "InstallationNotes": "Default Installation Notes", + "Documentations": [{ + "DocumentLabel": "Default Document Label", + "DocumentUrl": "http://DefaultDocumentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://DefaultTestIcon", + "IconFileType": "ico", + "IconResolution": "custom", + "IconTheme": "default", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123" + }] + }, + "Channel": "", + "Locales": [ + { + "PackageLocale": "fr-Fr", + "Publisher": "Foo French", + "PublisherUrl": "http://publisher-fr.net", + "PublisherSupportUrl": "http://publisherSupport-fr.net", + "PrivacyUrl": "http://packagePrivacyUrl-fr.net", + "Author": "FooBar French", + "PackageName": "Bar", + "PackageUrl": "http://packageUrl-fr.net", + "License": "Foo Bar License", + "LicenseUrl": "http://licenseUrl-fr.net", + "Copyright": "Foo Bar Copyright", + "CopyrightUrl": "http://copyrightUrl-fr.net", + "ShortDescription": "Foo bar is a foo bar French.", + "Description": "Foo bar is a placeholder French.", + "Tags": [ + "FooBarFr", + "FooFr", + "BarFr" + ], + "ReleaseNotes": "Release notes", + "ReleaseNotesUrl": "https://ReleaseNotes.net", + "Agreements": [{ + "AgreementLabel": "Label", + "Agreement": "Text", + "AgreementUrl": "https://AgreementUrl.net" + }], + "PurchaseUrl": "http://purchaseUrl.net", + "InstallationNotes": "Installation Notes", + "Documentations": [{ + "DocumentLabel": "Document Label", + "DocumentUrl": "http://documentUrl.net" + }], + "Icons": [{ + "IconUrl": "https://testIcon", + "IconFileType": "png", + "IconResolution": "32x32", + "IconTheme": "light", + "IconSha256": "69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321" + }] + } + ],)delimiter") _XPLATSTR(R"delimiter( + "Installers": [ + { + "InstallerSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "InstallerUrl": "http://foobar.zip", + "Architecture": "x86", + "InstallerLocale": "en-US", + "Platform": [ + "Windows.Desktop" + ], + "MinimumOSVersion": "1078", + "InstallerType": "zip", + "Scope": "user", + "InstallModes": [ + "interactive" + ], + "InstallerSwitches": { + "Silent": "/s", + "SilentWithProgress": "/s", + "Interactive": "/i", + "InstallLocation": "C:\\Users\\User1", + "Log": "/l", + "Upgrade": "/u", + "Custom": "/custom", + "Repair": "/repair" + }, + "InstallerSuccessCodes": [ + 0 + ], + "UpgradeBehavior": "deny", + "Commands": [ + "command1" + ], + "Protocols": [ + "protocol1" + ], + "FileExtensions": [ + ".file-extension" + ], + "Dependencies": { + "WindowsFeatures": [ + "feature1" + ], + "WindowsLibraries": [ + "library1" + ], + "PackageDependencies": [ + { + "PackageIdentifier": "Foo.Baz", + "MinimumVersion": "2.0.0" + } + ], + "ExternalDependencies": [ + "FooBarBaz" + ] + }, + "ProductCode": "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d", + "ReleaseDate": "2021-01-01", + "InstallerAbortsTerminal": true, + "InstallLocationRequired": true, + "RequireExplicitUpgrade": true, + "UnsupportedOSArchitectures": [ "arm" ], + "ElevationRequirement": "elevatesSelf", + "AppsAndFeaturesEntries": [{ + "DisplayName": "DisplayName", + "DisplayVersion": "DisplayVersion", + "Publisher": "Publisher", + "ProductCode": "ProductCode", + "UpgradeCode": "UpgradeCode", + "InstallerType": "exe" + }], + "Markets" : { + "AllowedMarkets": [ "US" ] + }, + "ExpectedReturnCodes": [{ + "InstallerReturnCode": 3, + "ReturnResponse": "custom", + "ReturnResponseUrl": "http://returnResponseUrl.net" + }], + "NestedInstallerType": "portable", + "DisplayInstallWarnings": true, + "UnsupportedArguments": [ "log" ], + "NestedInstallerFiles": [{ + "RelativeFilePath": "test\\app.exe", + "PortableCommandAlias": "test.exe" + }], + "InstallationMetadata": { + "DefaultInstallLocation": "%TEMP%\\DefaultInstallLocation", + "Files": [{ + "RelativeFilePath": "test\\app.exe", + "FileSha256": "011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6", + "FileType": "launch", + "InvocationParameter": "/parameter", + "DisplayName": "test" + }] + }, + "DownloadCommandProhibited": true, + "RepairBehavior": "uninstaller", + "ArchiveBinariesDependOnPath": true + } + ] + } + ] + }, + "ContinuationToken": "abcd" + })delimiter"); + } + + void VerifyLocalizations_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisher.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://publisherSupport.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packagePrivacyUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://packageUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar License"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://licenseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://copyrightUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a foo bar."); + REQUIRE(manifest.DefaultLocalization.Get() == "Foo bar is a placeholder."); + REQUIRE(manifest.DefaultLocalization.Get().size() == 3); + REQUIRE(manifest.DefaultLocalization.Get().at(0) == "FooBar"); + REQUIRE(manifest.DefaultLocalization.Get().at(1) == "Foo"); + REQUIRE(manifest.DefaultLocalization.Get().at(2) == "Bar"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "http://DefaultPurchaseUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default Installation Notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default Document Label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "http://DefaultDocumentUrl.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://DefaultTestIcon"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization frenchLocalization = manifest.Localizations.at(0); + REQUIRE(frenchLocalization.Locale == "fr-Fr"); + REQUIRE(frenchLocalization.Get() == "Foo French"); + REQUIRE(frenchLocalization.Get() == "http://publisher-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://publisherSupport-fr.net"); + REQUIRE(frenchLocalization.Get() == "http://packagePrivacyUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "FooBar French"); + REQUIRE(frenchLocalization.Get() == "Bar"); + REQUIRE(frenchLocalization.Get() == "http://packageUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar License"); + REQUIRE(frenchLocalization.Get() == "http://licenseUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo Bar Copyright"); + REQUIRE(frenchLocalization.Get() == "http://copyrightUrl-fr.net"); + REQUIRE(frenchLocalization.Get() == "Foo bar is a foo bar French."); + REQUIRE(frenchLocalization.Get() == "Foo bar is a placeholder French."); + REQUIRE(frenchLocalization.Get().size() == 3); + REQUIRE(frenchLocalization.Get().at(0) == "FooBarFr"); + REQUIRE(frenchLocalization.Get().at(1) == "FooFr"); + REQUIRE(frenchLocalization.Get().at(2) == "BarFr"); + REQUIRE(frenchLocalization.Get() == "Release notes"); + REQUIRE(frenchLocalization.Get() == "https://ReleaseNotes.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Label == "Label"); + REQUIRE(frenchLocalization.Get().at(0).AgreementText == "Text"); + REQUIRE(frenchLocalization.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + REQUIRE(frenchLocalization.Get() == "http://purchaseUrl.net"); + REQUIRE(frenchLocalization.Get() == "Installation Notes"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).DocumentLabel == "Document Label"); + REQUIRE(frenchLocalization.Get().at(0).DocumentUrl == "http://documentUrl.net"); + REQUIRE(frenchLocalization.Get().size() == 1); + REQUIRE(frenchLocalization.Get().at(0).Url == "https://testIcon"); + REQUIRE(frenchLocalization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(frenchLocalization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(frenchLocalization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(frenchLocalization.Get().at(0).Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + void VerifyInstallers_AllFields(const AppInstaller::Manifest::Manifest& manifest) + { + REQUIRE(manifest.Installers.size() == 1); + + ManifestInstaller actualInstaller = manifest.Installers.at(0); + REQUIRE(actualInstaller.Sha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.Url == "http://foobar.zip"); + REQUIRE(actualInstaller.Arch == Architecture::X86); + REQUIRE(actualInstaller.Locale == "en-US"); + REQUIRE(actualInstaller.Platform.size() == 1); + REQUIRE(actualInstaller.Platform[0] == PlatformEnum::Desktop); + REQUIRE(actualInstaller.MinOSVersion == "1078"); + REQUIRE(actualInstaller.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(actualInstaller.Scope == ScopeEnum::User); + REQUIRE(actualInstaller.InstallModes.size() == 1); + REQUIRE(actualInstaller.InstallModes.at(0) == InstallModeEnum::Interactive); + REQUIRE(actualInstaller.Switches.size() == 8); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::SilentWithProgress) == "/s"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::InstallLocation) == "C:\\Users\\User1"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Log) == "/l"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Update) == "/u"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(actualInstaller.Switches.at(InstallerSwitchType::Repair) == "/repair"); + REQUIRE(actualInstaller.InstallerSuccessCodes.size() == 1); + REQUIRE(actualInstaller.InstallerSuccessCodes.at(0) == 0); + REQUIRE(actualInstaller.UpdateBehavior == UpdateBehaviorEnum::Deny); + REQUIRE(actualInstaller.Commands.at(0) == "command1"); + REQUIRE(actualInstaller.Protocols.at(0) == "protocol1"); + REQUIRE(actualInstaller.FileExtensions.at(0) == ".file-extension"); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsFeature, "feature1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "library1")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::Package, "Foo.Baz", "2.0.0")); + REQUIRE(actualInstaller.Dependencies.HasExactDependency(DependencyType::External, "FooBarBaz")); + REQUIRE(actualInstaller.PackageFamilyName == ""); + REQUIRE(actualInstaller.ProductCode == "5b6e0f8a-3bbf-4a17-aefd-024c2b3e075d"); + REQUIRE(actualInstaller.ReleaseDate == "2021-01-01"); + REQUIRE(actualInstaller.InstallerAbortsTerminal); + REQUIRE(actualInstaller.InstallLocationRequired); + REQUIRE(actualInstaller.RequireExplicitUpgrade); + REQUIRE(actualInstaller.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.size() == 1); + REQUIRE(actualInstaller.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.size() == 1); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(actualInstaller.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(actualInstaller.Markets.AllowedMarkets.size() == 1); + REQUIRE(actualInstaller.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(actualInstaller.ExpectedReturnCodes.at(3).ReturnResponseUrl == "http://returnResponseUrl.net"); + REQUIRE(actualInstaller.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(actualInstaller.DisplayInstallWarnings); + REQUIRE(actualInstaller.UnsupportedArguments.size() == 1); + REQUIRE(actualInstaller.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + REQUIRE(actualInstaller.NestedInstallerFiles.size() == 1); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.NestedInstallerFiles.at(0).PortableCommandAlias == "test.exe"); + REQUIRE(actualInstaller.InstallationMetadata.DefaultInstallLocation == "%TEMP%\\DefaultInstallLocation"); + REQUIRE(actualInstaller.InstallationMetadata.Files.size() == 1); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).RelativeFilePath == "test\\app.exe"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).FileSha256 == AppInstaller::Utility::SHA256::ConvertToBytes("011048877dfaef109801b3f3ab2b60afc74f3fc4f7b3430e0c897f5da1df84b6")); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).InvocationParameter == "/parameter"); + REQUIRE(actualInstaller.InstallationMetadata.Files.at(0).DisplayName == "test"); + REQUIRE(actualInstaller.DownloadCommandProhibited); + REQUIRE(actualInstaller.RepairBehavior == RepairBehaviorEnum::Uninstaller); + REQUIRE(actualInstaller.ArchiveBinariesDependOnPath); + } + }; +} + +TEST_CASE("GetManifests_GoodResponse_V1_9", "[RestSource][Interface_1_9]") +{ + GoodManifest_AllFields sampleManifest; + utility::string_t sample = sampleManifest.GetSampleManifest_AllFields(); + HttpClientHelper helper{ GetTestRestRequestHandler(web::http::status_codes::OK, std::move(sample)) }; + Interface v1_9{ TestRestUriString, std::move(helper), {} }; + std::vector manifests = v1_9.GetManifests("Foo.Bar"); + REQUIRE(manifests.size() == 1); + + // Verify manifest is populated + Manifest& manifest = manifests[0]; + REQUIRE(manifest.Id == "Foo.Bar"); + REQUIRE(manifest.Version == "3.0.0abc"); + REQUIRE(manifest.Moniker == "FooBarMoniker"); + REQUIRE(manifest.Channel == ""); + REQUIRE(manifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ "1.9.0" }); + sampleManifest.VerifyLocalizations_AllFields(manifest); + sampleManifest.VerifyInstallers_AllFields(manifest); +} diff --git a/src/AppInstallerCLITests/Run-TestsInPackage.ps1 b/src/AppInstallerCLITests/Run-TestsInPackage.ps1 index d471a9859b..7ef840ca1a 100644 --- a/src/AppInstallerCLITests/Run-TestsInPackage.ps1 +++ b/src/AppInstallerCLITests/Run-TestsInPackage.ps1 @@ -1,184 +1,184 @@ -<# -.SYNOPSIS - Runs the AppInstallerCLI tests within the packaged context. -.DESCRIPTION - Registers the loose files generated by the AppInstallerCLIPackage project, then runs the - existing tests from "within" this context. -.PARAMETER BuildRoot - The root of the build output directory. If not provided, assumed to be the local default - location relative to this script. -.PARAMETER PackageRoot - The root of the package build output directory. If not provided, assumed to be the local default - location relative to this script. -.PARAMETER LogTarget - The file path to log to. -.PARAMETER MdmpTarget - The path to write a minidump to if the tests crash. -.PARAMETER TestResultsTarget - The file path to place the test result file in. -.PARAMETER Args - Additional args to pass to the tests. -.PARAMETER Wait - Have the test process wait for user input before exiting. -.PARAMETER ScriptWait - Have the script wait for the output files to be freed before exiting. -#> -param( - [Parameter(Mandatory=$false)] - [string]$BuildRoot, - - [Parameter(Mandatory=$false)] - [string]$PackageRoot, - - [Parameter(Mandatory=$false)] - [string]$LogTarget, - - [Parameter(Mandatory=$false)] - [string]$MdmpTarget, - - [Parameter(Mandatory=$false)] - [string]$TestResultsTarget, - - [Parameter(Mandatory=$false)] - [string]$Args, - - [switch]$Wait, - - [switch]$ScriptWait -) - -function Wait-ForFileClose([string]$Path) -{ - $Local:FileInfo = [System.IO.FileInfo]::new($Path) - $Local:SleepCount = 0 - $Local:SleepCountMax = 600 - - while ($Local:SleepCount -lt $Local:SleepCountMax) - { - try - { - [System.IO.FileStream] $Local:Stream = $Local:FileInfo.OpenWrite() - $Local:Stream.Dispose() - break - } - catch - { - Start-Sleep 1 - $Local:SleepCount = $Local:SleepCount + 1 - } - } - - if ($Local:SleepCount -ge $Local:SleepCountMax) - { - throw "Timed out waiting for file close: $Path" - } -} - -if ([String]::IsNullOrEmpty($BuildRoot)) -{ - $BuildRoot = Split-Path -Parent $PSCommandPath; - $BuildRoot = Join-Path $BuildRoot "..\x64\Debug"; -} -$BuildRoot = Resolve-Path $BuildRoot -Write-Host "Using BuildRoot = $BuildRoot" - -if ([String]::IsNullOrEmpty($PackageRoot)) -{ - $PackageRoot = Split-Path -Parent $PSCommandPath; - $PackageRoot = Join-Path $PackageRoot "..\AppInstallerCLIPackage\bin\x64\Debug"; -} -$PackageRoot = Resolve-Path $PackageRoot -Write-Host "Using PackageRoot = $PackageRoot" - -if (![String]::IsNullOrEmpty($LogTarget)) -{ - $Local:temp = Split-Path -Parent $LogTarget - $Local:temp = Resolve-Path $Local:temp - $LogTarget = Join-Path $Local:temp (Split-Path -Leaf $LogTarget) - Write-Host "Using LogTarget = $LogTarget" -} - -if (![String]::IsNullOrEmpty($MdmpTarget)) -{ - $Local:temp = Split-Path -Parent $MdmpTarget - $Local:temp = Resolve-Path $Local:temp - $MdmpTarget = Join-Path $Local:temp (Split-Path -Leaf $MdmpTarget) - Write-Host "Using MdmpTarget = $MdmpTarget" -} - -if (![String]::IsNullOrEmpty($TestResultsTarget)) -{ - $Local:temp = Split-Path -Parent $TestResultsTarget - $Local:temp = Resolve-Path $Local:temp - $TestResultsTarget = Join-Path $Local:temp (Split-Path -Leaf $TestResultsTarget) - Write-Host "Using TestResultsTarget = $TestResultsTarget" -} - -# Register the package; this requires the local package to have been deployed at least once or it won't be built. -$Local:ManifestPath = Join-Path $PackageRoot "AppxManifest.xml" -if (-not (Test-Path $Local:ManifestPath)) -{ - $Local:ManifestPath = Join-Path $PackageRoot "AppX\AppxManifest.xml" -} -Write-Host "Registering manifest at path: $Local:ManifestPath" -Add-AppxPackage -Register $Local:ManifestPath - -# Execute the tests from within the package's runtime. -$Local:TestExePath = Join-Path $BuildRoot "AppInstallerCLITests\AppInstallerCLITests.exe" -$Local:TestArgs = $Args - -if ([String]::IsNullOrEmpty($LogTarget)) -{ - $Local:TestArgs = $Local:TestArgs + " -log" -} -else -{ - $Local:TestArgs = $Local:TestArgs + " -logto ""$LogTarget""" -} - -if ([String]::IsNullOrEmpty($MdmpTarget)) -{ - $Local:TestArgs = $Local:TestArgs + " -mdmp" -} -else -{ - $Local:TestArgs = $Local:TestArgs + " -mdmpto ""$MdmpTarget""" -} - -if (![String]::IsNullOrEmpty($TestResultsTarget)) -{ - $Local:TestArgs = $Local:TestArgs + " -s -r junit -o ""$TestResultsTarget""" -} - -if ($Wait) -{ - $Local:TestArgs = $Local:TestArgs + " -wait" -} - -Write-Host "Executing tests at path: $Local:TestExePath" -Write-Host "Executing tests with args: $Local:TestArgs" -Invoke-CommandInDesktopPackage -PackageFamilyName WinGetDevCLI_8wekyb3d8bbwe -AppId WinGetDev -Command $Local:TestExePath -Args $Local:TestArgs - -if ($ScriptWait) -{ - try - { - Write-Host "Waiting for output files to be closed..." - Start-Sleep 5 - if (![String]::IsNullOrEmpty($LogTarget)) - { - Wait-ForFileClose $LogTarget - } - - if (![String]::IsNullOrEmpty($TestResultsTarget)) - { - Wait-ForFileClose $TestResultsTarget - } - Write-Host "Done" - } - finally - { - Write-Host "Remove registered package" - Get-AppxPackage WinGetDevCLI | Remove-AppxPackage - } -} +<# +.SYNOPSIS + Runs the AppInstallerCLI tests within the packaged context. +.DESCRIPTION + Registers the loose files generated by the AppInstallerCLIPackage project, then runs the + existing tests from "within" this context. +.PARAMETER BuildRoot + The root of the build output directory. If not provided, assumed to be the local default + location relative to this script. +.PARAMETER PackageRoot + The root of the package build output directory. If not provided, assumed to be the local default + location relative to this script. +.PARAMETER LogTarget + The file path to log to. +.PARAMETER MdmpTarget + The path to write a minidump to if the tests crash. +.PARAMETER TestResultsTarget + The file path to place the test result file in. +.PARAMETER Args + Additional args to pass to the tests. +.PARAMETER Wait + Have the test process wait for user input before exiting. +.PARAMETER ScriptWait + Have the script wait for the output files to be freed before exiting. +#> +param( + [Parameter(Mandatory=$false)] + [string]$BuildRoot, + + [Parameter(Mandatory=$false)] + [string]$PackageRoot, + + [Parameter(Mandatory=$false)] + [string]$LogTarget, + + [Parameter(Mandatory=$false)] + [string]$MdmpTarget, + + [Parameter(Mandatory=$false)] + [string]$TestResultsTarget, + + [Parameter(Mandatory=$false)] + [string]$Args, + + [switch]$Wait, + + [switch]$ScriptWait +) + +function Wait-ForFileClose([string]$Path) +{ + $Local:FileInfo = [System.IO.FileInfo]::new($Path) + $Local:SleepCount = 0 + $Local:SleepCountMax = 600 + + while ($Local:SleepCount -lt $Local:SleepCountMax) + { + try + { + [System.IO.FileStream] $Local:Stream = $Local:FileInfo.OpenWrite() + $Local:Stream.Dispose() + break + } + catch + { + Start-Sleep 1 + $Local:SleepCount = $Local:SleepCount + 1 + } + } + + if ($Local:SleepCount -ge $Local:SleepCountMax) + { + throw "Timed out waiting for file close: $Path" + } +} + +if ([String]::IsNullOrEmpty($BuildRoot)) +{ + $BuildRoot = Split-Path -Parent $PSCommandPath; + $BuildRoot = Join-Path $BuildRoot "..\x64\Debug"; +} +$BuildRoot = Resolve-Path $BuildRoot +Write-Host "Using BuildRoot = $BuildRoot" + +if ([String]::IsNullOrEmpty($PackageRoot)) +{ + $PackageRoot = Split-Path -Parent $PSCommandPath; + $PackageRoot = Join-Path $PackageRoot "..\AppInstallerCLIPackage\bin\x64\Debug"; +} +$PackageRoot = Resolve-Path $PackageRoot +Write-Host "Using PackageRoot = $PackageRoot" + +if (![String]::IsNullOrEmpty($LogTarget)) +{ + $Local:temp = Split-Path -Parent $LogTarget + $Local:temp = Resolve-Path $Local:temp + $LogTarget = Join-Path $Local:temp (Split-Path -Leaf $LogTarget) + Write-Host "Using LogTarget = $LogTarget" +} + +if (![String]::IsNullOrEmpty($MdmpTarget)) +{ + $Local:temp = Split-Path -Parent $MdmpTarget + $Local:temp = Resolve-Path $Local:temp + $MdmpTarget = Join-Path $Local:temp (Split-Path -Leaf $MdmpTarget) + Write-Host "Using MdmpTarget = $MdmpTarget" +} + +if (![String]::IsNullOrEmpty($TestResultsTarget)) +{ + $Local:temp = Split-Path -Parent $TestResultsTarget + $Local:temp = Resolve-Path $Local:temp + $TestResultsTarget = Join-Path $Local:temp (Split-Path -Leaf $TestResultsTarget) + Write-Host "Using TestResultsTarget = $TestResultsTarget" +} + +# Register the package; this requires the local package to have been deployed at least once or it won't be built. +$Local:ManifestPath = Join-Path $PackageRoot "AppxManifest.xml" +if (-not (Test-Path $Local:ManifestPath)) +{ + $Local:ManifestPath = Join-Path $PackageRoot "AppX\AppxManifest.xml" +} +Write-Host "Registering manifest at path: $Local:ManifestPath" +Add-AppxPackage -Register $Local:ManifestPath + +# Execute the tests from within the package's runtime. +$Local:TestExePath = Join-Path $BuildRoot "AppInstallerCLITests\AppInstallerCLITests.exe" +$Local:TestArgs = $Args + +if ([String]::IsNullOrEmpty($LogTarget)) +{ + $Local:TestArgs = $Local:TestArgs + " -log" +} +else +{ + $Local:TestArgs = $Local:TestArgs + " -logto ""$LogTarget""" +} + +if ([String]::IsNullOrEmpty($MdmpTarget)) +{ + $Local:TestArgs = $Local:TestArgs + " -mdmp" +} +else +{ + $Local:TestArgs = $Local:TestArgs + " -mdmpto ""$MdmpTarget""" +} + +if (![String]::IsNullOrEmpty($TestResultsTarget)) +{ + $Local:TestArgs = $Local:TestArgs + " -s -r junit -o ""$TestResultsTarget""" +} + +if ($Wait) +{ + $Local:TestArgs = $Local:TestArgs + " -wait" +} + +Write-Host "Executing tests at path: $Local:TestExePath" +Write-Host "Executing tests with args: $Local:TestArgs" +Invoke-CommandInDesktopPackage -PackageFamilyName WinGetDevCLI_8wekyb3d8bbwe -AppId WinGetDev -Command $Local:TestExePath -Args $Local:TestArgs + +if ($ScriptWait) +{ + try + { + Write-Host "Waiting for output files to be closed..." + Start-Sleep 5 + if (![String]::IsNullOrEmpty($LogTarget)) + { + Wait-ForFileClose $LogTarget + } + + if (![String]::IsNullOrEmpty($TestResultsTarget)) + { + Wait-ForFileClose $TestResultsTarget + } + Write-Host "Done" + } + finally + { + Write-Host "Remove registered package" + Get-AppxPackage WinGetDevCLI | Remove-AppxPackage + } +} diff --git a/src/AppInstallerCLITests/Runtime.cpp b/src/AppInstallerCLITests/Runtime.cpp index e986122b32..59bb50c22f 100644 --- a/src/AppInstallerCLITests/Runtime.cpp +++ b/src/AppInstallerCLITests/Runtime.cpp @@ -1,430 +1,430 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestHooks.h" -#include -#include - -using namespace AppInstaller; -using namespace AppInstaller::Filesystem; -using namespace AppInstaller::Runtime; -using namespace TestCommon; - -bool CanWriteToPath(const std::filesystem::path& directory, const std::filesystem::path& file = "test.txt") -{ - std::ofstream out{ directory / file }; - out << "Test"; - return out.good(); -} - -void RequireAdminOwner(const std::filesystem::path& directory) -{ - wil::unique_hlocal_security_descriptor securityDescriptor; - PSID ownerSID = nullptr; - THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(directory.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &ownerSID, nullptr, nullptr, nullptr, &securityDescriptor)); - - auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); - REQUIRE(EqualSid(adminSID.get(), ownerSID)); -} - -DWORD AccessMaskFrom(ACEPermissions permissions) -{ - DWORD result = 0; - - if (permissions == ACEPermissions::All) - { - result |= GENERIC_ALL; - } - else - { - if (WI_IsFlagSet(permissions, ACEPermissions::Read)) - { - result |= GENERIC_READ; - } - - if (WI_IsFlagSet(permissions, ACEPermissions::Write)) - { - result |= GENERIC_WRITE | FILE_DELETE_CHILD; - } - - if (WI_IsFlagSet(permissions, ACEPermissions::Execute)) - { - result |= GENERIC_EXECUTE; - } - } - - return result; -} - -DWORD NormalizedAccessMaskFrom(ACEPermissions permissions) -{ - DWORD result = AccessMaskFrom(permissions); - GENERIC_MAPPING genericMapping - { - FILE_GENERIC_READ, - FILE_GENERIC_WRITE, - FILE_GENERIC_EXECUTE, - FILE_ALL_ACCESS, - }; - - MapGenericMask(&result, &genericMapping); - return result; -} - -void ApplyAclForTest(const std::filesystem::path& directory, const std::optional& owner, const std::map& acl, bool protectDacl = true) -{ - auto userToken = wil::get_token_information(); - auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); - auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); - PSID ownerSID = nullptr; - - struct ACEDetails - { - ACEPrincipal Principal; - PSID SID; - TRUSTEE_TYPE TrusteeType; - }; - - ACEDetails aceDetails[] = - { - { ACEPrincipal::CurrentUser, userToken->User.Sid, TRUSTEE_IS_USER }, - { ACEPrincipal::Admins, adminSID.get(), TRUSTEE_IS_WELL_KNOWN_GROUP }, - { ACEPrincipal::System, systemSID.get(), TRUSTEE_IS_USER }, - }; - - ULONG entriesCount = 0; - std::array explicitAccess; - - for (const auto& ace : aceDetails) - { - if (owner && owner.value() == ace.Principal) - { - ownerSID = ace.SID; - } - - auto itr = acl.find(ace.Principal); - if (itr != acl.end()) - { - EXPLICIT_ACCESS_W& entry = explicitAccess[entriesCount++]; - entry = {}; - entry.grfAccessPermissions = AccessMaskFrom(itr->second); - entry.grfAccessMode = SET_ACCESS; - entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; - entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; - entry.Trustee.TrusteeType = ace.TrusteeType; - entry.Trustee.ptstrName = reinterpret_cast(ace.SID); - } - } - - wil::unique_any appliedAcl; - THROW_IF_WIN32_ERROR(SetEntriesInAclW(entriesCount, explicitAccess.data(), nullptr, &appliedAcl)); - - SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION; - if (protectDacl) - { - securityInformation |= PROTECTED_DACL_SECURITY_INFORMATION; - } - - if (ownerSID) - { - securityInformation |= OWNER_SECURITY_INFORMATION; - } - - std::wstring path = directory.wstring(); - THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation, ownerSID, nullptr, appliedAcl.get(), nullptr)); -} - -void AddUnexpectedUsersAce(const std::filesystem::path& directory) -{ - wil::unique_hlocal_security_descriptor securityDescriptor; - PACL existingDacl = nullptr; - THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(directory.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &existingDacl, nullptr, &securityDescriptor)); - - auto usersSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS); - EXPLICIT_ACCESS_W entry = {}; - entry.grfAccessPermissions = GENERIC_READ; - entry.grfAccessMode = GRANT_ACCESS; - entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; - entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; - entry.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; - entry.Trustee.ptstrName = reinterpret_cast(usersSID.get()); - - wil::unique_any updatedDacl; - THROW_IF_WIN32_ERROR(SetEntriesInAclW(1, &entry, existingDacl, &updatedDacl)); - - std::wstring path = directory.wstring(); - THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, updatedDacl.get(), nullptr)); -} - -void ApplyCombinedAceAclForTest(const std::filesystem::path& directory, const std::optional& owner, const std::map& acl) -{ - auto userToken = wil::get_token_information(); - auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); - auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); - PSID ownerSID = nullptr; - - struct ACEDetails - { - ACEPrincipal Principal; - PSID SID; - }; - - ACEDetails aceDetails[] = - { - { ACEPrincipal::CurrentUser, userToken->User.Sid }, - { ACEPrincipal::Admins, adminSID.get() }, - { ACEPrincipal::System, systemSID.get() }, - }; - - DWORD aclSize = sizeof(ACL); - for (const auto& ace : aceDetails) - { - if (owner && owner.value() == ace.Principal) - { - ownerSID = ace.SID; - } - - if (acl.count(ace.Principal) != 0) - { - aclSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(ace.SID); - } - } - - std::vector aclBuffer(aclSize); - PACL appliedAcl = reinterpret_cast(aclBuffer.data()); - THROW_IF_WIN32_BOOL_FALSE(InitializeAcl(appliedAcl, aclSize, ACL_REVISION)); - - for (const auto& ace : aceDetails) - { - auto itr = acl.find(ace.Principal); - if (itr != acl.end()) - { - THROW_IF_WIN32_BOOL_FALSE(AddAccessAllowedAceEx( - appliedAcl, - ACL_REVISION, - CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, - NormalizedAccessMaskFrom(itr->second), - ace.SID)); - } - } - - SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION; - if (ownerSID) - { - securityInformation |= OWNER_SECURITY_INFORMATION; - } - - std::wstring path = directory.wstring(); - THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW( - &path[0], - SE_FILE_OBJECT, - securityInformation, - ownerSID, - nullptr, - appliedAcl, - nullptr)); -} - -TEST_CASE("ApplyACL_CurrentUserOwner", "[runtime]") -{ - TempDirectory directory("CurrentUserOwner"); - PathDetails details; - details.Path = directory; - details.SetOwner(ACEPrincipal::CurrentUser); - - details.ApplyACL(); - - REQUIRE(CanWriteToPath(directory)); -} - -TEST_CASE("ApplyACL_RemoveWriteForUser", "[runtime]") -{ - TempDirectory directory("CurrentUserCantWrite"); - PathDetails details; - details.Path = directory; - details.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; - - details.ApplyACL(); - - REQUIRE(!CanWriteToPath(directory)); -} - -TEST_CASE("ApplyACL_AdminOwner", "[runtime]") -{ - TempDirectory directory("AdminOwner"); - PathDetails details; - details.Path = directory; - details.SetOwner(ACEPrincipal::Admins); - - if (IsRunningAsAdmin()) - { - details.ApplyACL(); - RequireAdminOwner(directory); - REQUIRE(CanWriteToPath(directory)); - } - else - { - // A non-admin token cannot set the owner to be the Admins group - REQUIRE_THROWS_HR(details.ApplyACL(), HRESULT_FROM_WIN32(ERROR_INVALID_OWNER)); - } -} - -TEST_CASE("ApplyACL_BothOwners", "[runtime]") -{ - TempDirectory directory("AdminOwner"); - PathDetails details; - details.Path = directory; - details.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; - details.ACL[ACEPrincipal::System] = ACEPermissions::All; - - if (IsRunningAsSystem()) - { - // Both cannot be owners - REQUIRE_THROWS_HR(details.ApplyACL(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - else - { - REQUIRE_NOTHROW(details.ApplyACL()); - } -} - -TEST_CASE("ApplyACL_CurrentUserOwner_SystemAll", "[runtime]") -{ - TempDirectory directory("UserAndSystem"); - PathDetails details; - details.Path = directory; - details.SetOwner(ACEPrincipal::CurrentUser); - details.ACL[ACEPrincipal::System] = ACEPermissions::All; - - details.ApplyACL(); - - REQUIRE(CanWriteToPath(directory)); -} - -TEST_CASE("ShouldApplyACL_FalseWhenSecurityAlreadyMatches", "[runtime]") -{ - TempDirectory directory("ShouldApplyACLExactMatch"); - PathDetails details; - details.Path = directory; - details.SetOwner(ACEPrincipal::CurrentUser); - details.ACL[ACEPrincipal::System] = ACEPermissions::All; - details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - - details.ApplyACL(); - - REQUIRE_FALSE(details.ShouldApplyACL()); -} - -TEST_CASE("ShouldApplyACL_FalseWhenSecurityIsSemanticallyEquivalent", "[runtime]") -{ - TempDirectory directory("ShouldApplyACLSemanticMatch"); - PathDetails details; - details.Path = directory; - details.SetOwner(ACEPrincipal::CurrentUser); - details.ACL[ACEPrincipal::System] = ACEPermissions::All; - details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - - ApplyCombinedAceAclForTest(directory, details.Owner, details.ACL); - - REQUIRE_FALSE(details.ShouldApplyACL()); -} - -TEST_CASE("ShouldApplyACL_TrueWhenOwnerMismatched", "[runtime]") -{ - TempDirectory directory("ShouldApplyACLMismatchedOwner"); - PathDetails actualDetails; - actualDetails.Path = directory; - actualDetails.SetOwner(ACEPrincipal::CurrentUser); - actualDetails.ACL[ACEPrincipal::System] = ACEPermissions::All; - actualDetails.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - actualDetails.ApplyACL(); - - PathDetails expectedDetails = actualDetails; - expectedDetails.Owner = ACEPrincipal::Admins; - - REQUIRE(expectedDetails.ShouldApplyACL()); -} - -TEST_CASE("ShouldApplyACL_TrueWhenPermissionsMismatched", "[runtime]") -{ - TempDirectory directory("ShouldApplyACLMismatchedPermissions"); - PathDetails actualDetails; - actualDetails.Path = directory; - actualDetails.SetOwner(ACEPrincipal::CurrentUser); - actualDetails.ACL[ACEPrincipal::System] = ACEPermissions::All; - actualDetails.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - actualDetails.ApplyACL(); - - PathDetails expectedDetails = actualDetails; - expectedDetails.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; - - REQUIRE(expectedDetails.ShouldApplyACL()); -} - -TEST_CASE("ShouldApplyACL_TrueWhenDaclIsNotProtected", "[runtime]") -{ - TempDirectory directory("ShouldApplyACLUnprotectedDacl"); - PathDetails details; - details.Path = directory; - details.SetOwner(ACEPrincipal::CurrentUser); - details.ACL[ACEPrincipal::System] = ACEPermissions::All; - details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - - ApplyAclForTest(directory, details.Owner, details.ACL, false); - - REQUIRE(details.ShouldApplyACL()); -} - -TEST_CASE("ShouldApplyACL_TrueWhenUnexpectedAceExists", "[runtime]") -{ - TempDirectory directory("ShouldApplyACLUnexpectedAce"); - PathDetails details; - details.Path = directory; - details.SetOwner(ACEPrincipal::CurrentUser); - details.ACL[ACEPrincipal::System] = ACEPermissions::All; - details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - details.ApplyACL(); - AddUnexpectedUsersAce(directory); - - REQUIRE(details.ShouldApplyACL()); -} - -TEST_CASE("VerifyDevModeEnabledCheck", "[runtime]") -{ - if (!Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - bool initialState = IsDevModeEnabled(); - - EnableDevMode(!initialState); - bool modifiedState = IsDevModeEnabled(); - - // Revert to original state. - EnableDevMode(initialState); - bool revertedState = IsDevModeEnabled(); - - REQUIRE(modifiedState != initialState); - REQUIRE(revertedState == initialState); -} - -TEST_CASE("EnsureUserProfileNotPresentInDisplayPaths", "[runtime]") -{ - // Clear the overrides that we use when testing as they don't consider display purposes - Runtime::TestHook_ClearPathOverrides(); - auto restorePaths = wil::scope_exit([]() { TestCommon::SetTestPathOverrides(); }); - - std::filesystem::path userProfilePath = Filesystem::GetKnownFolderPath(FOLDERID_Profile); - std::string userProfileString = userProfilePath.u8string(); - - for (auto i = ToIntegral(ToEnum(0)); i < ToIntegral(Runtime::PathName::Max); ++i) - { - std::filesystem::path displayPath = GetPathTo(ToEnum(i), true); - std::string displayPathString = displayPath.u8string(); - INFO(i << " = " << displayPathString); - REQUIRE(displayPathString.find(userProfileString) == std::string::npos); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestHooks.h" +#include +#include + +using namespace AppInstaller; +using namespace AppInstaller::Filesystem; +using namespace AppInstaller::Runtime; +using namespace TestCommon; + +bool CanWriteToPath(const std::filesystem::path& directory, const std::filesystem::path& file = "test.txt") +{ + std::ofstream out{ directory / file }; + out << "Test"; + return out.good(); +} + +void RequireAdminOwner(const std::filesystem::path& directory) +{ + wil::unique_hlocal_security_descriptor securityDescriptor; + PSID ownerSID = nullptr; + THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(directory.c_str(), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &ownerSID, nullptr, nullptr, nullptr, &securityDescriptor)); + + auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + REQUIRE(EqualSid(adminSID.get(), ownerSID)); +} + +DWORD AccessMaskFrom(ACEPermissions permissions) +{ + DWORD result = 0; + + if (permissions == ACEPermissions::All) + { + result |= GENERIC_ALL; + } + else + { + if (WI_IsFlagSet(permissions, ACEPermissions::Read)) + { + result |= GENERIC_READ; + } + + if (WI_IsFlagSet(permissions, ACEPermissions::Write)) + { + result |= GENERIC_WRITE | FILE_DELETE_CHILD; + } + + if (WI_IsFlagSet(permissions, ACEPermissions::Execute)) + { + result |= GENERIC_EXECUTE; + } + } + + return result; +} + +DWORD NormalizedAccessMaskFrom(ACEPermissions permissions) +{ + DWORD result = AccessMaskFrom(permissions); + GENERIC_MAPPING genericMapping + { + FILE_GENERIC_READ, + FILE_GENERIC_WRITE, + FILE_GENERIC_EXECUTE, + FILE_ALL_ACCESS, + }; + + MapGenericMask(&result, &genericMapping); + return result; +} + +void ApplyAclForTest(const std::filesystem::path& directory, const std::optional& owner, const std::map& acl, bool protectDacl = true) +{ + auto userToken = wil::get_token_information(); + auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); + PSID ownerSID = nullptr; + + struct ACEDetails + { + ACEPrincipal Principal; + PSID SID; + TRUSTEE_TYPE TrusteeType; + }; + + ACEDetails aceDetails[] = + { + { ACEPrincipal::CurrentUser, userToken->User.Sid, TRUSTEE_IS_USER }, + { ACEPrincipal::Admins, adminSID.get(), TRUSTEE_IS_WELL_KNOWN_GROUP }, + { ACEPrincipal::System, systemSID.get(), TRUSTEE_IS_USER }, + }; + + ULONG entriesCount = 0; + std::array explicitAccess; + + for (const auto& ace : aceDetails) + { + if (owner && owner.value() == ace.Principal) + { + ownerSID = ace.SID; + } + + auto itr = acl.find(ace.Principal); + if (itr != acl.end()) + { + EXPLICIT_ACCESS_W& entry = explicitAccess[entriesCount++]; + entry = {}; + entry.grfAccessPermissions = AccessMaskFrom(itr->second); + entry.grfAccessMode = SET_ACCESS; + entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; + entry.Trustee.TrusteeType = ace.TrusteeType; + entry.Trustee.ptstrName = reinterpret_cast(ace.SID); + } + } + + wil::unique_any appliedAcl; + THROW_IF_WIN32_ERROR(SetEntriesInAclW(entriesCount, explicitAccess.data(), nullptr, &appliedAcl)); + + SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION; + if (protectDacl) + { + securityInformation |= PROTECTED_DACL_SECURITY_INFORMATION; + } + + if (ownerSID) + { + securityInformation |= OWNER_SECURITY_INFORMATION; + } + + std::wstring path = directory.wstring(); + THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation, ownerSID, nullptr, appliedAcl.get(), nullptr)); +} + +void AddUnexpectedUsersAce(const std::filesystem::path& directory) +{ + wil::unique_hlocal_security_descriptor securityDescriptor; + PACL existingDacl = nullptr; + THROW_IF_WIN32_ERROR(GetNamedSecurityInfoW(directory.c_str(), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, nullptr, nullptr, &existingDacl, nullptr, &securityDescriptor)); + + auto usersSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_USERS); + EXPLICIT_ACCESS_W entry = {}; + entry.grfAccessPermissions = GENERIC_READ; + entry.grfAccessMode = GRANT_ACCESS; + entry.grfInheritance = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; + entry.Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP; + entry.Trustee.ptstrName = reinterpret_cast(usersSID.get()); + + wil::unique_any updatedDacl; + THROW_IF_WIN32_ERROR(SetEntriesInAclW(1, &entry, existingDacl, &updatedDacl)); + + std::wstring path = directory.wstring(); + THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, nullptr, nullptr, updatedDacl.get(), nullptr)); +} + +void ApplyCombinedAceAclForTest(const std::filesystem::path& directory, const std::optional& owner, const std::map& acl) +{ + auto userToken = wil::get_token_information(); + auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); + PSID ownerSID = nullptr; + + struct ACEDetails + { + ACEPrincipal Principal; + PSID SID; + }; + + ACEDetails aceDetails[] = + { + { ACEPrincipal::CurrentUser, userToken->User.Sid }, + { ACEPrincipal::Admins, adminSID.get() }, + { ACEPrincipal::System, systemSID.get() }, + }; + + DWORD aclSize = sizeof(ACL); + for (const auto& ace : aceDetails) + { + if (owner && owner.value() == ace.Principal) + { + ownerSID = ace.SID; + } + + if (acl.count(ace.Principal) != 0) + { + aclSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD) + GetLengthSid(ace.SID); + } + } + + std::vector aclBuffer(aclSize); + PACL appliedAcl = reinterpret_cast(aclBuffer.data()); + THROW_IF_WIN32_BOOL_FALSE(InitializeAcl(appliedAcl, aclSize, ACL_REVISION)); + + for (const auto& ace : aceDetails) + { + auto itr = acl.find(ace.Principal); + if (itr != acl.end()) + { + THROW_IF_WIN32_BOOL_FALSE(AddAccessAllowedAceEx( + appliedAcl, + ACL_REVISION, + CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, + NormalizedAccessMaskFrom(itr->second), + ace.SID)); + } + } + + SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION; + if (ownerSID) + { + securityInformation |= OWNER_SECURITY_INFORMATION; + } + + std::wstring path = directory.wstring(); + THROW_IF_WIN32_ERROR(SetNamedSecurityInfoW( + &path[0], + SE_FILE_OBJECT, + securityInformation, + ownerSID, + nullptr, + appliedAcl, + nullptr)); +} + +TEST_CASE("ApplyACL_CurrentUserOwner", "[runtime]") +{ + TempDirectory directory("CurrentUserOwner"); + PathDetails details; + details.Path = directory; + details.SetOwner(ACEPrincipal::CurrentUser); + + details.ApplyACL(); + + REQUIRE(CanWriteToPath(directory)); +} + +TEST_CASE("ApplyACL_RemoveWriteForUser", "[runtime]") +{ + TempDirectory directory("CurrentUserCantWrite"); + PathDetails details; + details.Path = directory; + details.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; + + details.ApplyACL(); + + REQUIRE(!CanWriteToPath(directory)); +} + +TEST_CASE("ApplyACL_AdminOwner", "[runtime]") +{ + TempDirectory directory("AdminOwner"); + PathDetails details; + details.Path = directory; + details.SetOwner(ACEPrincipal::Admins); + + if (IsRunningAsAdmin()) + { + details.ApplyACL(); + RequireAdminOwner(directory); + REQUIRE(CanWriteToPath(directory)); + } + else + { + // A non-admin token cannot set the owner to be the Admins group + REQUIRE_THROWS_HR(details.ApplyACL(), HRESULT_FROM_WIN32(ERROR_INVALID_OWNER)); + } +} + +TEST_CASE("ApplyACL_BothOwners", "[runtime]") +{ + TempDirectory directory("AdminOwner"); + PathDetails details; + details.Path = directory; + details.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; + details.ACL[ACEPrincipal::System] = ACEPermissions::All; + + if (IsRunningAsSystem()) + { + // Both cannot be owners + REQUIRE_THROWS_HR(details.ApplyACL(), HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + else + { + REQUIRE_NOTHROW(details.ApplyACL()); + } +} + +TEST_CASE("ApplyACL_CurrentUserOwner_SystemAll", "[runtime]") +{ + TempDirectory directory("UserAndSystem"); + PathDetails details; + details.Path = directory; + details.SetOwner(ACEPrincipal::CurrentUser); + details.ACL[ACEPrincipal::System] = ACEPermissions::All; + + details.ApplyACL(); + + REQUIRE(CanWriteToPath(directory)); +} + +TEST_CASE("ShouldApplyACL_FalseWhenSecurityAlreadyMatches", "[runtime]") +{ + TempDirectory directory("ShouldApplyACLExactMatch"); + PathDetails details; + details.Path = directory; + details.SetOwner(ACEPrincipal::CurrentUser); + details.ACL[ACEPrincipal::System] = ACEPermissions::All; + details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + + details.ApplyACL(); + + REQUIRE_FALSE(details.ShouldApplyACL()); +} + +TEST_CASE("ShouldApplyACL_FalseWhenSecurityIsSemanticallyEquivalent", "[runtime]") +{ + TempDirectory directory("ShouldApplyACLSemanticMatch"); + PathDetails details; + details.Path = directory; + details.SetOwner(ACEPrincipal::CurrentUser); + details.ACL[ACEPrincipal::System] = ACEPermissions::All; + details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + + ApplyCombinedAceAclForTest(directory, details.Owner, details.ACL); + + REQUIRE_FALSE(details.ShouldApplyACL()); +} + +TEST_CASE("ShouldApplyACL_TrueWhenOwnerMismatched", "[runtime]") +{ + TempDirectory directory("ShouldApplyACLMismatchedOwner"); + PathDetails actualDetails; + actualDetails.Path = directory; + actualDetails.SetOwner(ACEPrincipal::CurrentUser); + actualDetails.ACL[ACEPrincipal::System] = ACEPermissions::All; + actualDetails.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + actualDetails.ApplyACL(); + + PathDetails expectedDetails = actualDetails; + expectedDetails.Owner = ACEPrincipal::Admins; + + REQUIRE(expectedDetails.ShouldApplyACL()); +} + +TEST_CASE("ShouldApplyACL_TrueWhenPermissionsMismatched", "[runtime]") +{ + TempDirectory directory("ShouldApplyACLMismatchedPermissions"); + PathDetails actualDetails; + actualDetails.Path = directory; + actualDetails.SetOwner(ACEPrincipal::CurrentUser); + actualDetails.ACL[ACEPrincipal::System] = ACEPermissions::All; + actualDetails.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + actualDetails.ApplyACL(); + + PathDetails expectedDetails = actualDetails; + expectedDetails.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; + + REQUIRE(expectedDetails.ShouldApplyACL()); +} + +TEST_CASE("ShouldApplyACL_TrueWhenDaclIsNotProtected", "[runtime]") +{ + TempDirectory directory("ShouldApplyACLUnprotectedDacl"); + PathDetails details; + details.Path = directory; + details.SetOwner(ACEPrincipal::CurrentUser); + details.ACL[ACEPrincipal::System] = ACEPermissions::All; + details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + + ApplyAclForTest(directory, details.Owner, details.ACL, false); + + REQUIRE(details.ShouldApplyACL()); +} + +TEST_CASE("ShouldApplyACL_TrueWhenUnexpectedAceExists", "[runtime]") +{ + TempDirectory directory("ShouldApplyACLUnexpectedAce"); + PathDetails details; + details.Path = directory; + details.SetOwner(ACEPrincipal::CurrentUser); + details.ACL[ACEPrincipal::System] = ACEPermissions::All; + details.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + details.ApplyACL(); + AddUnexpectedUsersAce(directory); + + REQUIRE(details.ShouldApplyACL()); +} + +TEST_CASE("VerifyDevModeEnabledCheck", "[runtime]") +{ + if (!Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + bool initialState = IsDevModeEnabled(); + + EnableDevMode(!initialState); + bool modifiedState = IsDevModeEnabled(); + + // Revert to original state. + EnableDevMode(initialState); + bool revertedState = IsDevModeEnabled(); + + REQUIRE(modifiedState != initialState); + REQUIRE(revertedState == initialState); +} + +TEST_CASE("EnsureUserProfileNotPresentInDisplayPaths", "[runtime]") +{ + // Clear the overrides that we use when testing as they don't consider display purposes + Runtime::TestHook_ClearPathOverrides(); + auto restorePaths = wil::scope_exit([]() { TestCommon::SetTestPathOverrides(); }); + + std::filesystem::path userProfilePath = Filesystem::GetKnownFolderPath(FOLDERID_Profile); + std::string userProfileString = userProfilePath.u8string(); + + for (auto i = ToIntegral(ToEnum(0)); i < ToIntegral(Runtime::PathName::Max); ++i) + { + std::filesystem::path displayPath = GetPathTo(ToEnum(i), true); + std::string displayPathString = displayPath.u8string(); + INFO(i << " = " << displayPathString); + REQUIRE(displayPathString.find(userProfileString) == std::string::npos); + } +} diff --git a/src/AppInstallerCLITests/SQLiteDynamicStorage.cpp b/src/AppInstallerCLITests/SQLiteDynamicStorage.cpp index 03ed583994..3f5d6106d3 100644 --- a/src/AppInstallerCLITests/SQLiteDynamicStorage.cpp +++ b/src/AppInstallerCLITests/SQLiteDynamicStorage.cpp @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include - -using namespace AppInstaller::SQLite; -using namespace std::string_literals; - -TEST_CASE("SQLiteDynamicStorage_UpgradeDetection", "[sqlite_dynamic]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - // Create a database with version 1.0 - SQLiteDynamicStorage storage{ tempFile.GetPath(), Version{ 1, 0 } }; - - { - auto transactionLock = storage.TryBeginTransaction("test", false); - REQUIRE(transactionLock); - } - - // Update the database to version 2.0 - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - Version version{ 2, 0 }; - version.SetSchemaVersion(connection); - } - - REQUIRE(storage.GetVersion() == Version{ 1, 0 }); - - auto transactionLock = storage.TryBeginTransaction("test", false); - REQUIRE(!transactionLock); - - REQUIRE(storage.GetVersion() == Version{ 2, 0 }); - - transactionLock = storage.TryBeginTransaction("test", false); - REQUIRE(transactionLock); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include + +using namespace AppInstaller::SQLite; +using namespace std::string_literals; + +TEST_CASE("SQLiteDynamicStorage_UpgradeDetection", "[sqlite_dynamic]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + // Create a database with version 1.0 + SQLiteDynamicStorage storage{ tempFile.GetPath(), Version{ 1, 0 } }; + + { + auto transactionLock = storage.TryBeginTransaction("test", false); + REQUIRE(transactionLock); + } + + // Update the database to version 2.0 + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + Version version{ 2, 0 }; + version.SetSchemaVersion(connection); + } + + REQUIRE(storage.GetVersion() == Version{ 1, 0 }); + + auto transactionLock = storage.TryBeginTransaction("test", false); + REQUIRE(!transactionLock); + + REQUIRE(storage.GetVersion() == Version{ 2, 0 }); + + transactionLock = storage.TryBeginTransaction("test", false); + REQUIRE(transactionLock); +} diff --git a/src/AppInstallerCLITests/SQLiteIndex.cpp b/src/AppInstallerCLITests/SQLiteIndex.cpp index e15e72e50c..99b6dd8786 100644 --- a/src/AppInstallerCLITests/SQLiteIndex.cpp +++ b/src/AppInstallerCLITests/SQLiteIndex.cpp @@ -1,3965 +1,3965 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace TestCommon; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Utility; - -using UtilityVersion = AppInstaller::Utility::Version; -using SQLiteVersion = AppInstaller::SQLite::Version; - -SQLiteIndex CreateTestIndex(const std::string& filePath, std::optional version = {}) -{ - // If no specific version requested, then use generator to run against the last 3 versions. - if (!version) - { - SQLiteVersion latestVersion{ 2, 0 }; - SQLiteVersion versionMinus1 = SQLiteVersion{ 1, 7 }; - SQLiteVersion versionMinus2 = SQLiteVersion{ 1, 6 }; - - version = GENERATE_COPY(SQLiteVersion{ versionMinus2 }, SQLiteVersion{ versionMinus1 }, SQLiteVersion{ latestVersion }); - } - - return SQLiteIndex::CreateNew(filePath, version.value()); -} - -SQLiteVersion TestPrepareForRead(SQLiteIndex& index) -{ - SQLiteVersion latestVersion{ 2, 0 }; - SQLiteVersion versionMinus1 = SQLiteVersion{ 1, 7 }; - SQLiteVersion versionMinus2 = SQLiteVersion{ 1, 6 }; - - index.PrepareForPackaging(); - - if (index.GetVersion() == versionMinus2) - { - // Degenerate case where we don't need to do anything - } - else if (index.GetVersion() == versionMinus1) - { - SQLiteVersion version = GENERATE_COPY(SQLiteVersion{ versionMinus2 }, SQLiteVersion{ versionMinus1 }); - - if (version != versionMinus1) - { - index.ForceVersion(version); - return version; - } - } - else if (index.GetVersion() == latestVersion) - { - // This crosses major versions, so leave it at 2.0 always - } - - return index.GetVersion(); -} - -std::string GetPathFromManifest(Manifest& manifest) -{ - auto publisher = manifest.Id; - AppInstaller::Utility::FindAndReplace(publisher, ".", "/"); - - return AppInstaller::Utility::ToLower(publisher).append("/").append(manifest.Version); -} - -void CreateFakeManifest(Manifest& manifest, string_t publisher, string_t version = "1.0.0") -{ - manifest.Installers.push_back({}); - manifest.Id = publisher.append(".").append("Id"); - manifest.DefaultLocalization.Add(publisher.append(" Name")); - manifest.Moniker = "testmoniker"; - manifest.Version = version; - manifest.Channel = "test"; - manifest.DefaultLocalization.Add({ "t1", "t2" }); - manifest.Installers[0].Commands = { "test1", "test2" }; -} - -SQLiteIndex SimpleTestSetup(const std::string& filePath, Manifest& manifest, std::optional version = {}) -{ - SQLiteIndex index = CreateTestIndex(filePath, version); - - string_t publisher = "Test"; - CreateFakeManifest(manifest, publisher); - - auto relativePath = GetPathFromManifest(manifest); - - index.AddManifest(manifest, relativePath); - - return index; -} - -struct IndexFields -{ - IndexFields( - std::string id, - std::string name, - std::string moniker, - std::string version, - std::string channel, - std::vector tags, - std::vector commands, - std::string path - ) : - Id(std::move(id)), - Name(std::move(name)), - Moniker(std::move(moniker)), - Version(std::move(version)), - Channel(std::move(channel)), - Tags(std::move(tags)), - Commands(std::move(commands)), - Path(std::move(path)) - {} - - IndexFields( - std::string id, - std::string name, - std::string moniker, - std::string version, - std::string channel, - std::vector tags, - std::vector commands, - std::string path, - std::vector packageFamilyNames, - std::vector productCodes - ) : - Id(std::move(id)), - Name(std::move(name)), - Moniker(std::move(moniker)), - Version(std::move(version)), - Channel(std::move(channel)), - Tags(std::move(tags)), - Commands(std::move(commands)), - Path(std::move(path)), - PackageFamilyNames(std::move(packageFamilyNames)), - ProductCodes(std::move(productCodes)) - {} - - IndexFields( - std::string id, - std::string name, - std::string publisher, - std::string moniker, - std::string version, - std::string channel, - std::vector tags, - std::vector commands, - std::string path, - std::vector packageFamilyNames, - std::vector productCodes - ) : - Id(std::move(id)), - Name(std::move(name)), - Publisher(std::move(publisher)), - Moniker(std::move(moniker)), - Version(std::move(version)), - Channel(std::move(channel)), - Tags(std::move(tags)), - Commands(std::move(commands)), - Path(std::move(path)), - PackageFamilyNames(std::move(packageFamilyNames)), - ProductCodes(std::move(productCodes)) - {} - - IndexFields( - std::string id, - std::string name, - std::string publisher, - std::string moniker, - std::string version, - std::string channel, - std::vector tags, - std::vector commands, - std::string path, - std::vector packageFamilyNames, - std::vector productCodes, - std::string arpName, - std::string arpPublisher - ) : - Id(std::move(id)), - Name(std::move(name)), - Publisher(std::move(publisher)), - Moniker(std::move(moniker)), - Version(std::move(version)), - Channel(std::move(channel)), - Tags(std::move(tags)), - Commands(std::move(commands)), - Path(std::move(path)), - PackageFamilyNames(std::move(packageFamilyNames)), - ProductCodes(std::move(productCodes)), - ArpName(std::move(arpName)), - ArpPublisher(std::move(arpPublisher)) - {} - - std::string Id; - std::string Name; - std::string Publisher; - std::string Moniker; - std::string Version; - std::string Channel; - std::vector Tags; - std::vector Commands; - std::string Path; - std::vector PackageFamilyNames; - std::vector ProductCodes; - std::string ArpName; - std::string ArpPublisher; -}; - -SQLiteIndex SearchTestSetup(const std::string& filePath, std::initializer_list data = {}, std::optional version = {}) -{ - SQLiteIndex index = CreateTestIndex(filePath, version); - - Manifest manifest; - - auto addFunc = [&](const IndexFields& d) - { - manifest.Id = d.Id; - manifest.DefaultLocalization.Add(d.Name); - manifest.DefaultLocalization.Add(d.Publisher); - manifest.Moniker = d.Moniker; - manifest.Version = d.Version; - manifest.DefaultLocalization.Add(d.Tags); - - manifest.Installers.resize(std::max(d.PackageFamilyNames.size(), d.ProductCodes.size())); - - if (manifest.Installers.size() == 0) - { - manifest.Installers.push_back({}); - } - - manifest.Channel = d.Channel; - manifest.Installers[0].Commands = d.Commands; - - for (size_t i = 0; i < d.PackageFamilyNames.size(); ++i) - { - manifest.Installers[i].PackageFamilyName = d.PackageFamilyNames[i]; - } - - for (size_t i = 0; i < d.ProductCodes.size(); ++i) - { - manifest.Installers[i].ProductCode = d.ProductCodes[i]; - } - - if (!d.ArpName.empty() || !d.ArpPublisher.empty()) - { - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayName = d.ArpName; - manifest.Installers[0].AppsAndFeaturesEntries[0].Publisher = d.ArpPublisher; - } - - index.AddManifest(manifest, d.Path); - }; - - for (const auto& d : data) - { - addFunc(d); - } - - return index; -} - -bool ArePackageFamilyNameAndProductCodeSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) -{ - UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); - return (index.GetVersion() >= SQLiteVersion{ 1, 1 } && testVersion >= SQLiteVersion{ 1, 1 }); -} - -bool AreNormalizedNameAndPublisherSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) -{ - UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); - return (index.GetVersion() >= SQLiteVersion{ 1, 2 } && testVersion >= SQLiteVersion{ 1, 2 }); -} - -bool IsManifestMetadataSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) -{ - UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); - return (index.GetVersion() >= SQLiteVersion{ 1, 1 } && testVersion >= SQLiteVersion{ 1, 1 }); -} - -bool AreManifestHashesSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) -{ - UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); - return (index.GetVersion() >= SQLiteVersion{ 1, 3 } && testVersion >= SQLiteVersion{ 1, 3 } && index.GetVersion() < SQLiteVersion{ 2, 0 }); -} - -bool AreArpVersionsSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) -{ - UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); - return (index.GetVersion() >= SQLiteVersion{ 1, 5 } && testVersion >= SQLiteVersion{ 1, 5 }); -} - -bool AreArpVersionsNullable(const SQLiteIndex& index) -{ - UNSCOPED_INFO("Index " << index.GetVersion()); - return (index.GetVersion() >= SQLiteVersion{ 2, 0 }); -} - -bool IsMapDataFoldingSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) -{ - UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); - return (index.GetVersion() >= SQLiteVersion{ 1, 7 } && testVersion >= SQLiteVersion{ 1, 7 }); -} - -bool IsMapDataFolded(const SQLiteIndex& index) -{ - UNSCOPED_INFO("Index " << index.GetVersion()); - return (index.GetVersion() >= SQLiteVersion{ 1, 7 }); -} - -bool AreVersionKeysSupported(const SQLiteIndex& index) -{ - UNSCOPED_INFO("Index " << index.GetVersion()); - return (index.GetVersion() < SQLiteVersion{ 2, 0 }); -} - -bool AreChannelsSupported(const SQLiteIndex& index) -{ - UNSCOPED_INFO("Index " << index.GetVersion()); - return (index.GetVersion() < SQLiteVersion{ 2, 0 }); -} - -bool AreManifestPathsSupported(const SQLiteIndex& index) -{ - UNSCOPED_INFO("Index " << index.GetVersion()); - return (index.GetVersion() < SQLiteVersion{ 2, 0 }); -} - -std::string GetPropertyStringByKey(const SQLiteIndex& index, rowid_t primaryId, PackageVersionProperty property) -{ - auto result = index.GetPropertyByPrimaryId(primaryId, property); - REQUIRE(result); - return result.value(); -} - -std::string GetPropertyStringByKey(const SQLiteIndex& index, rowid_t id, PackageVersionProperty property, std::string_view version, std::string_view channel) -{ - if (AreVersionKeysSupported(index)) - { - auto manifestId = index.GetManifestIdByKey(id, version, channel); - REQUIRE(manifestId); - auto result = index.GetPropertyByPrimaryId(manifestId.value(), property); - REQUIRE(result); - return result.value(); - } - else - { - return GetPropertyStringByKey(index, id, property); - } -} - -std::string GetPropertyStringById(const SQLiteIndex& index, rowid_t id, PackageVersionProperty property) -{ - if (AreVersionKeysSupported(index)) - { - auto versions = index.GetVersionKeysById(id); - REQUIRE(!versions.empty()); - return GetPropertyStringByKey(index, versions[0].ManifestId, property); - } - else - { - return GetPropertyStringByKey(index, id, property); - } -} - -std::string GetIdStringById(const SQLiteIndex& index, rowid_t id) -{ - return GetPropertyStringById(index, id, PackageVersionProperty::Id); -} - -std::string GetNameStringById(const SQLiteIndex& index, rowid_t id) -{ - return GetPropertyStringById(index, id, PackageVersionProperty::Name); -} - -std::string GetPathStringByKey(const SQLiteIndex& index, rowid_t id, std::string_view version, std::string_view channel) -{ - return GetPropertyStringByKey(index, id, PackageVersionProperty::RelativePath, version, channel); -} - -TEST_CASE("SQLiteIndexCreateLatestAndReopen", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteVersion versionCreated; - - // Create the index - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, SQLiteVersion::Latest()); - versionCreated = index.GetVersion(); - } - - // Reopen the index for read only - { - INFO("Trying with Read"); - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); - SQLiteVersion versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for read/write - { - INFO("Trying with ReadWrite"); - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - SQLiteVersion versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } - - // Reopen the index for immutable read - { - INFO("Trying with Immutable"); - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); - SQLiteVersion versionRead = index.GetVersion(); - REQUIRE(versionRead == versionCreated); - } -} - -TEST_CASE("SQLiteIndexCreateAndAddManifest", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - std::string relativePath = "test/id/1.0.0.yaml"; - - SQLiteIndex index = SimpleTestSetup(tempFile, manifest, SQLiteVersion::Latest()); -} - -TEST_CASE("SQLiteIndexCreateAndAddManifestFile", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile); - - TestDataFile manifestFile{ "Manifest-Good.yaml" }; - std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; - - index.AddManifest(manifestFile, manifestPath); -} - -TEST_CASE("SQLiteIndexCreateAndAddManifestDuplicate", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - auto relativePath = GetPathFromManifest(manifest); - - // Attempting to add the same manifest at a different path should fail. - REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); - - // Attempting to add the same manifest with a differently cased Id at a different path should fail. - manifest.Id = ToLower(manifest.Id); - REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); - - // Attempting to add a different manifest at the same path should fail. - manifest.Id += "-new"; - REQUIRE_THROWS_HR(index.AddManifest(manifest, relativePath), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); -} - -TEST_CASE("SQLiteIndex_VersionReferencedByDependenciesClearsUnusedVersionAndKeepUsedVersion", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest, isolatedManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - std::string dependencyOnlyVersion = "0.0.5"; - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, dependencyOnlyVersion)); - - index.AddManifest(manifest, GetPathFromManifest(manifest)); - - // Create a new manifest that depends on v0.0.5 - auto& publisher4 = "Test4"; - CreateFakeManifest(isolatedManifest, publisher4); - isolatedManifest.Version = dependencyOnlyVersion; - index.AddManifest(isolatedManifest); - - index.RemoveManifest(isolatedManifest); - // After deletion that the version(v0.0.5) must be present because it still referenced via dependencies table. - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, dependencyOnlyVersion).has_value()); - } - - index.RemoveManifest(manifest); - // Now, that we've deleted the manifest depending on version(v0.0.5), it should be absent. - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - REQUIRE(!Schema::V1_0::VersionTable::SelectIdByValue(connection, dependencyOnlyVersion).has_value()); - } - index.RemoveManifest(dependencyManifest1); - index.RemoveManifest(dependencyManifest2); - - // Final sanity check, nothing should be in the version table. - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - } -} - -TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); - - index.AddManifest(manifest, GetPathFromManifest(manifest)); - index.UpdateManifest(manifest, GetPathFromManifest(manifest)); - index.RemoveManifest(manifest); -} - -TEST_CASE("SQLiteIndex_AddManifestWithDependencies_MissingPackage", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - // Publisher2 is not present - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); - - REQUIRE_THROWS_HR(index.AddManifest(manifest, GetPathFromManifest(manifest)), APPINSTALLER_CLI_ERROR_MISSING_PACKAGE); -} - -TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies_MissingVersion", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "0.0.1")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "0.0.2")); - - index.AddManifest(manifest, GetPathFromManifest(manifest)); - index.UpdateManifest(manifest, GetPathFromManifest(manifest)); - index.RemoveManifest(manifest); -} - -TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies_EmptyManifestVersion", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id)); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id)); - - index.AddManifest(manifest, GetPathFromManifest(manifest)); - index.UpdateManifest(manifest, GetPathFromManifest(manifest)); - index.RemoveManifest(manifest); -} - -TEST_CASE("SQLiteIndex_DependenciesTable_CheckConsistency", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - { - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); - } - - { - // Open it directly to modify the table - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - rowid_t nonExistentRowId = 40; - rowid_t nonExistentManifest = 41; - rowid_t nonExistentVersion = 42; - rowid_t nonExistentPackageId = 43; - - Builder::StatementBuilder builder; - builder.InsertInto(Schema::V1_4::DependenciesTable::TableName()) - .Values(nonExistentRowId, nonExistentManifest, nonExistentVersion, nonExistentPackageId); - builder.Execute(connection); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - REQUIRE(!index.CheckConsistency(true)); - } - - TempFile tempFile2{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile2.GetPath()); - - { - SQLiteIndex index = CreateTestIndex(tempFile2, SQLiteVersion::Latest()); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "10.0"; - - index.AddManifest(manifest, "path"); - - REQUIRE(index.CheckConsistency(true)); - - // Add dependency that does not require min version - Manifest manifestWithDependency1; - manifestWithDependency1.Id = "Bar1"; - manifestWithDependency1.Version = "10.0"; - manifestWithDependency1.Installers.push_back({}); - manifestWithDependency1.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id)); - - index.AddManifest(manifestWithDependency1, "path1"); - - REQUIRE(index.CheckConsistency(true)); - - // Add dependency with min version satisfied - Manifest manifestWithDependency2; - manifestWithDependency2.Id = "Bar2"; - manifestWithDependency2.Version = "10.0"; - manifestWithDependency2.Installers.push_back({}); - manifestWithDependency2.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id, "1.0")); - - index.AddManifest(manifestWithDependency2, "path2"); - - REQUIRE(index.CheckConsistency(true)); - - // Add dependency with min version not satisfied - Manifest manifestWithDependency3; - manifestWithDependency3.Id = "Bar3"; - manifestWithDependency3.Version = "10.0"; - manifestWithDependency3.Installers.push_back({}); - manifestWithDependency3.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id, "11.0")); - - index.AddManifest(manifestWithDependency3, "path3"); - - REQUIRE_FALSE(index.CheckConsistency(true)); - } -} - -TEST_CASE("SQLiteIndex_RemoveManifestFile_NotPresent", "[sqliteindex]") -{ - SQLiteIndex index = CreateTestIndex(SQLITE_MEMORY_DB_CONNECTION_TARGET); - - TestDataFile manifestFile{ "Manifest-Good.yaml" }; - std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; - - REQUIRE_THROWS_HR(index.RemoveManifest(manifestFile, manifestPath), E_NOT_SET); -} - -TEST_CASE("SQLiteIndex_RemoveManifest", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; - Manifest manifest1; - manifest1.Installers.push_back({}); - manifest1.Id = "test.id"; - manifest1.DefaultLocalization.Add("Test Name"); - manifest1.Moniker = "testmoniker"; - manifest1.Version = "1.0.0"; - manifest1.Channel = "test"; - manifest1.DefaultLocalization.Add({ "t1", "t2" }); - manifest1.Installers[0].Commands = { "test1", "test2" }; - - std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; - Manifest manifest2; - manifest2.Installers.push_back({}); - manifest2.Id = "test.woah"; - manifest2.DefaultLocalization.Add("Test Name WOAH"); - manifest2.Moniker = "testmoniker"; - manifest2.Version = "1.0.0"; - manifest2.Channel = "test"; - manifest2.DefaultLocalization.Add({}); - manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); - - index.AddManifest(manifest1, manifest1Path); - index.AddManifest(manifest2, manifest2Path); - - // Now remove manifest1 - index.RemoveManifest(manifest1, manifest1Path); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); - // Because manifest2 had no tags - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Now remove manifest2 - index.RemoveManifest(manifest2, manifest2Path); - } - - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); -} - -TEST_CASE("SQLiteIndex_RemoveManifestWithDependencies", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); - - index.AddManifest(manifest, GetPathFromManifest(manifest)); - - index.RemoveManifest(manifest, GetPathFromManifest(manifest)); -} - -TEST_CASE("SQLiteIndex_ValidateManifestWithDependencies", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); - REQUIRE(PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest)); -} - -TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesHasLoops", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); - index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); - - levelThreeManifest.Installers.push_back(ManifestInstaller{}); - levelThreeManifest.Installers[1].Dependencies.Add(Dependency(DependencyType::Package, topLevelManifest.Id, "1.0.0")); - REQUIRE_THROWS_HR( - PackageDependenciesValidation::ValidateManifestDependencies(&index, levelThreeManifest), - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); -} - -TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesMissingNode", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - // This node is missing, because it's not in the index. - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); - REQUIRE_THROWS_HR( - PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest), - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); -} - -TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesNoSuitableMinVersion", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "2.0.0")); - - REQUIRE_THROWS_HR( - PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest), - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); -} - -TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureBroken", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); - index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); - - REQUIRE_THROWS_HR( - PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifest), - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); -} - -TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureNotBroken", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest, levelThreeManifestV2; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); - index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); - - constexpr std::string_view levelThreeManifestV2Publisher = "Test"; - CreateFakeManifest(levelThreeManifestV2, levelThreeManifestV2Publisher, "2.0.0"); - index.AddManifest(levelThreeManifestV2, GetPathFromManifest(levelThreeManifestV2)); - - REQUIRE(PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifest)); -} - -TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureBroken_NoSuitableOldManifest", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest, levelThreeManifestV2; - SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); - - constexpr std::string_view levelThreeManifestV2Publisher = "Test"; - CreateFakeManifest(levelThreeManifestV2, levelThreeManifestV2Publisher, "2.0.0"); - index.AddManifest(levelThreeManifestV2, GetPathFromManifest(levelThreeManifestV2)); - - constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; - CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); - - levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "2.0.0")); - index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); - - constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; - CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); - levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); - index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); - - constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; - CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); - topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); - index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); - - REQUIRE_THROWS( - PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifestV2), - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); -} - -TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex][V1_7]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; - Manifest manifest1; - manifest1.Installers.push_back({}); - manifest1.Id = "test.id"; - manifest1.DefaultLocalization.Add("Test Name"); - manifest1.Moniker = "testmoniker"; - manifest1.Version = "1.0.0"; - manifest1.Channel = "test"; - manifest1.DefaultLocalization.Add({ "t1", "t2" }); - manifest1.Installers[0].Commands = { "test1", "test2" }; - - std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; - Manifest manifest2; - manifest2.Installers.push_back({}); - manifest2.Id = "test.woah"; - manifest2.DefaultLocalization.Add("Test Name WOAH"); - manifest2.Moniker = "testmoniker"; - manifest2.Version = "1.0.0"; - manifest2.Channel = "test"; - manifest2.DefaultLocalization.Add({}); - manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; - - SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion{ 1, 7 }); - - index.AddManifest(manifest1, manifest1Path); - index.AddManifest(manifest2, manifest2Path); - - // Get the second manifest's id for validating consistency - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, manifest2.Id)); - auto result = index.Search(request); - - REQUIRE(result.Matches.size() == 1); - auto manifest2IdRowId = result.Matches[0].first; - - auto rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {}); - REQUIRE(rowId); - auto manifest2RowId = rowId.value(); - - // Now remove manifest1 and prepare - index.RemoveManifest(manifest1, manifest1Path); - index.PrepareForPackaging(); - - // Checking consistency will also uncover issues, but not potentially the same ones as below. - REQUIRE(index.CheckConsistency(true)); - - // Repeat search to ensure consistent ids - result = index.Search(request); - REQUIRE(result.Matches.size() == 1); - REQUIRE(result.Matches[0].first == manifest2IdRowId); - - rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {}); - REQUIRE(rowId); - REQUIRE(rowId.value() == manifest2RowId); - - REQUIRE(manifest2.Id == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Id)); - REQUIRE(manifest2.DefaultLocalization.Get() == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Name)); - REQUIRE(manifest2.Moniker == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Moniker)); - REQUIRE(manifest2.Version == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Version)); - REQUIRE(manifest2.Channel == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Channel)); - REQUIRE(manifest2Path == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::RelativePath)); -} - -TEST_CASE("SQLiteIndex_RemoveManifestFile", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); - - TestDataFile manifestFile{ "Manifest-Good.yaml" }; - std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; - - index.AddManifest(manifestFile, manifestPath); - - // Now remove that manifest - index.RemoveManifest(manifestFile, manifestPath); - } - - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); -} - -TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifestPath = "test/id/test.id-1.0.0.yaml"; - Manifest manifest; - manifest.Installers.push_back({}); - manifest.Id = "test.id"; - manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); - manifest.Moniker = "testmoniker"; - manifest.Version = "1.0.0"; - manifest.Channel = "test"; - manifest.DefaultLocalization.Add({ "t1", "t2" }); - manifest.Installers[0].Commands = { "test1", "test2" }; - - - { - auto version = GENERATE(SQLiteVersion{ 1, 0 }, SQLiteVersion::Latest()); - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, version); - - index.AddManifest(manifest, manifestPath); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Update with no updates should return false - REQUIRE(!index.UpdateManifest(manifest, manifestPath)); - - manifest.DefaultLocalization.Add("description2"); - - // Update with no indexed updates should return false - REQUIRE(!index.UpdateManifest(manifest, manifestPath)); - - // Update with indexed changes - manifest.DefaultLocalization.Add("Test Name2"); - manifest.Moniker = "testmoniker2"; - manifest.DefaultLocalization.Add({ "t1", "t2", "t3" }); - manifest.Installers[0].Commands = {}; - - REQUIRE(index.UpdateManifest(manifest, manifestPath)); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); - // The update removed all commands - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Now remove manifest2 - index.RemoveManifest(manifest, manifestPath); - } - - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); -} - -TEST_CASE("SQLiteIndex_UpdateManifestWithDependencies", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest, updateManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - const std::string dependencyPath3 = GetPathFromManifest(manifest); - - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); - - index.AddManifest(manifest, dependencyPath3); - - auto& publisher4 = "Test4"; - CreateFakeManifest(updateManifest, publisher4); - index.AddManifest(updateManifest, GetPathFromManifest(updateManifest)); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, updateManifest.Id, "1.0.0")); - - REQUIRE(index.UpdateManifest(manifest, dependencyPath3)); -} - -TEST_CASE("SQLiteIndex_UpdateManifestWithDependenciesDeleteAndAdd", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest, updateManifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - const std::string dependencyPath3 = GetPathFromManifest(manifest); - - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); - - index.AddManifest(manifest, dependencyPath3); - - manifest.Installers[0].Dependencies.Clear(); - - auto& publisher4 = "Test4"; - CreateFakeManifest(updateManifest, publisher4); - index.AddManifest(updateManifest, GetPathFromManifest(updateManifest)); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, updateManifest.Id, "1.0.0")); - - REQUIRE(index.UpdateManifest(manifest, dependencyPath3)); -} - -TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifestPath = "test/id/test.id-1.0.0.yaml"; - Manifest manifest; - manifest.Installers.push_back({}); - manifest.Id = "test.id"; - manifest.DefaultLocalization.Add("Test Name"); - manifest.Moniker = "testmoniker"; - manifest.Version = "1.0.0"; - manifest.Channel = "test"; - manifest.DefaultLocalization.Add({ "t1", "t2" }); - manifest.Installers[0].Commands = { "test1", "test2" }; - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); - - index.AddManifest(manifest, manifestPath); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - manifestPath = "test/newid/test.newid-1.0.0.yaml"; - - // Update with path update should indicate change - REQUIRE(index.UpdateManifest(manifest, manifestPath)); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Now remove manifest, with unknown path - index.RemoveManifest(manifest, ""); - } - - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); -} - -TEST_CASE("SQLiteIndex_UpdateManifest_Pathless", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - manifest.Installers.push_back({}); - manifest.Id = "test.id"; - manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); - manifest.Moniker = "testmoniker"; - manifest.Version = "1.0.0"; - manifest.Channel = "test"; - manifest.DefaultLocalization.Add({ "t1", "t2" }); - manifest.Installers[0].Commands = { "test1", "test2" }; - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); - - index.AddManifest(manifest); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Update with no updates should return false - REQUIRE(!index.UpdateManifest(manifest)); - - manifest.DefaultLocalization.Add("description2"); - - // Update with no indexed updates should return false - REQUIRE(!index.UpdateManifest(manifest)); - - // Update with indexed changes - manifest.DefaultLocalization.Add("Test Name2"); - manifest.Moniker = "testmoniker2"; - manifest.DefaultLocalization.Add({ "t1", "t2", "t3" }); - manifest.Installers[0].Commands = {}; - - REQUIRE(index.UpdateManifest(manifest)); - } - - { - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); - // The update removed all commands - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Now remove manifest2 - index.RemoveManifest(manifest); - } - - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); -} - -TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifestPath = "test/id/test.id-1.0.0.yaml"; - Manifest manifest; - manifest.Installers.push_back({}); - manifest.Id = "test.id"; - manifest.DefaultLocalization.Add("Test Name"); - manifest.Moniker = "testmoniker"; - manifest.Version = "1.0.0-test"; - manifest.Channel = "test"; - manifest.DefaultLocalization.Add({ "t1", "t2" }); - manifest.Installers[0].Commands = { "test1", "test2" }; - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); - - index.AddManifest(manifest, manifestPath); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - manifest.Id = "Test.Id"; - - // Update with path update should indicate change - REQUIRE(index.UpdateManifest(manifest, manifestPath)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - manifest.Version = "1.0.0-Test"; - - // Update with path update should indicate change - REQUIRE(index.UpdateManifest(manifest, manifestPath)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - manifest.Channel = "Test"; - - // Update with path update should indicate change - REQUIRE(index.UpdateManifest(manifest, manifestPath)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - manifest.DefaultLocalization.Add("test name"); - - // Update with path update should indicate change - REQUIRE(index.UpdateManifest(manifest, manifestPath)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Now remove manifest, with unknown path - index.RemoveManifest(manifest, ""); - } - - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); -} - -TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; - Manifest manifest1; - manifest1.Installers.push_back({}); - manifest1.Id = "test.id"; - manifest1.DefaultLocalization.Add("Test Name"); - manifest1.Moniker = "testmoniker"; - manifest1.Version = "1.0.0"; - manifest1.DefaultLocalization.Add({ "t1", "t2" }); - manifest1.Installers[0].Commands = { "test1", "test2" }; - - std::string manifest2Path = "test/id/test.id-2.0.0.yaml"; - Manifest manifest2 = manifest1; - manifest2.Id = "Test.Id"; - manifest1.Version = "2.0.0"; - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); - - index.AddManifest(manifest1, manifest1Path); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - index.AddManifest(manifest2, manifest2Path); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - REQUIRE(manifest2.Id == GetIdStringById(index, results.Matches[0].first)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - manifest1.Id = "TEST.ID"; - - REQUIRE(index.UpdateManifest(manifest1, manifest1Path)); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - index.RemoveManifest(manifest1, manifest1Path); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - index.RemoveManifest(manifest2, manifest2Path); - - auto results = index.Search({}); - REQUIRE(results.Matches.empty()); - } - - // Open it directly to directly test table state - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); - REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); -} - -TEST_CASE("PathPartTable_EnsurePathExists_Negative_Paths", "[sqliteindex][V1_0]") -{ - // Open it directly to directly test pathpart table - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"()", false), E_INVALIDARG); - REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(\)", false), E_INVALIDARG); - REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(/)", false), E_INVALIDARG); - REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:)", false), E_INVALIDARG); - REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:\\)", false), E_INVALIDARG); - REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:\temp\path\file.txt)", false), E_INVALIDARG); - REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(\temp\path\file.txt)", false), E_INVALIDARG); -} - -TEST_CASE("PathPartTable_EnsurePathExists", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - // Create the index - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); - SQLiteVersion versionCreated = index.GetVersion(); - REQUIRE(versionCreated == SQLiteVersion{ 1, 0 }); - } - - // Open it directly to directly test pathpart table - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - // attempt to find path that doesn't exist - auto result0 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", false); - REQUIRE(!std::get<0>(result0)); - - // add path - auto result1 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", true); - REQUIRE(std::get<0>(result1)); - - // Second time trying to create should return false and same id - auto result2 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", true); - REQUIRE(!std::get<0>(result2)); - REQUIRE(std::get<1>(result1) == std::get<1>(result2)); - - // Trying to find but not create should return true because it exists - auto result3 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", false); - REQUIRE(std::get<0>(result3)); - REQUIRE(std::get<1>(result1) == std::get<1>(result3)); - - // attempt to find a different file - auto result4 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d.txt)", false); - REQUIRE(!std::get<0>(result4)); - - // add a different file - auto result5 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d.txt)", true); - REQUIRE(std::get<0>(result5)); - REQUIRE(std::get<1>(result1) != std::get<1>(result5)); - - // add the same file but deeper - auto result6 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d\c.txt)", true); - REQUIRE(std::get<0>(result6)); - REQUIRE(std::get<1>(result1) != std::get<1>(result6)); - - // get the deeper file with extra separators - auto result7 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\\b\d\\c.txt)", true); - REQUIRE(!std::get<0>(result7)); - REQUIRE(std::get<1>(result6) == std::get<1>(result7)); -} - -TEST_CASE("SQLiteIndex_PrepareForPackaging", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile); - - TestDataFile manifestFile{ "Manifest-Good.yaml" }; - std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; - - index.AddManifest(manifestFile, manifestPath); - - index.PrepareForPackaging(); -} - -TEST_CASE("SQLiteIndex_Search_IdExactMatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - std::string relativePath = "test/id/1.0.0.yaml"; - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].second.Field == PackageMatchField::Id); - REQUIRE(results.Matches[0].second.Type == MatchType::Exact); - REQUIRE(results.Matches[0].second.Value == manifest.Id); -} - -TEST_CASE("SQLiteIndex_Search_MultipleMatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - std::string relativePath = "test/id/1.0.0.yaml"; - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - - manifest.Version = "2.0.0"; - index.AddManifest(manifest, relativePath + "2"); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - if (AreVersionKeysSupported(index)) - { - auto result = index.GetVersionKeysById(results.Matches[0].first); - REQUIRE(result.size() == 2); - } - else - { - REQUIRE_THROWS_HR(index.GetVersionKeysById(results.Matches[0].first), E_NOT_VALID_STATE); - } -} - -TEST_CASE("SQLiteIndex_Search_NoMatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, "THIS DOES NOT MATCH ANYTHING!"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 0); -} - -TEST_CASE("SQLiteIndex_IdString", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = GetIdStringById(index, results.Matches[0].first); - REQUIRE(result == manifest.Id); -} - -TEST_CASE("SQLiteIndex_NameString", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = GetNameStringById(index, results.Matches[0].first); - REQUIRE(result == manifest.DefaultLocalization.Get()); -} - -TEST_CASE("SQLiteIndex_PathString", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - auto relativePath = GetPathFromManifest(manifest); - - TestPrepareForRead(index); - - if (!AreManifestPathsSupported(index)) - { - return; - } - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel); - REQUIRE(specificResult == relativePath); - - auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel); - REQUIRE(latestResult == relativePath); -} - -TEST_CASE("SQLiteIndex_PathlessString", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - std::string relativePath; - - SQLiteIndex index = CreateTestIndex(tempFile); - CreateFakeManifest(manifest, "Test"); - index.AddManifest(manifest); - - TestPrepareForRead(index); - - if (!AreManifestPathsSupported(index)) - { - return; - } - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel); - REQUIRE(specificResult == relativePath); - - auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel); - REQUIRE(latestResult == relativePath); -} - -TEST_CASE("SQLiteIndex_Versions", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - std::string relativePath = "test/id/1.0.0.yaml"; - SQLiteIndex index = SimpleTestSetup(tempFile, manifest); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - if (AreVersionKeysSupported(index)) - { - auto result = index.GetVersionKeysById(results.Matches[0].first); - REQUIRE(result.size() == 1); - REQUIRE(result[0].VersionAndChannel.GetVersion().ToString() == manifest.Version); - REQUIRE(result[0].VersionAndChannel.GetChannel().ToString() == manifest.Channel); - } - else - { - REQUIRE_THROWS_HR(index.GetVersionKeysById(results.Matches[0].first), E_NOT_VALID_STATE); - } -} - -TEST_CASE("SQLiteIndex_Search_VersionSorting", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::vector sortedList = - { - { UtilityVersion("15.0.0"), Channel("") }, - { UtilityVersion("14.0.0"), Channel("") }, - { UtilityVersion("13.2.0"), Channel("") }, - { UtilityVersion("13.2.0-bugfix"), Channel("") }, - { UtilityVersion("13.0.0"), Channel("") }, - { UtilityVersion("16.0.0"), Channel("alpha") }, - { UtilityVersion("15.8.0"), Channel("alpha") }, - { UtilityVersion("15.1.0"), Channel("beta") }, - }; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" }, - { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" }, - { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" }, - { "Id", "Name", "Moniker", "13.2.0", "", {}, { "Command" }, "Path4" }, - { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" }, - { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" }, - { "Id", "Name", "Moniker", "13.2.0-bugfix", "", { "foo" }, { "com3" }, "Path7" }, - { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" }, - }); - - TestPrepareForRead(index); - - if (!AreChannelsSupported(index)) - { - return; - } - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = index.GetVersionKeysById(results.Matches[0].first); - REQUIRE(result.size() == sortedList.size()); - - for (size_t i = 0; i < result.size(); ++i) - { - const VersionAndChannel& sortedVAC = sortedList[i]; - const VersionAndChannel& resultVAC = result[i].VersionAndChannel; - - INFO(i); - REQUIRE(sortedVAC.GetVersion().ToString() == resultVAC.GetVersion().ToString()); - REQUIRE(sortedVAC.GetChannel().ToString() == resultVAC.GetChannel().ToString()); - } -} - -TEST_CASE("SQLiteIndex_PathString_VersionSorting", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::vector sortedList = - { - { UtilityVersion("15.0.0"), Channel("") }, - { UtilityVersion("14.0.0"), Channel("") }, - { UtilityVersion("13.2.0"), Channel("") }, - { UtilityVersion("13.2.0-bugfix"), Channel("") }, - { UtilityVersion("13.0.0"), Channel("") }, - { UtilityVersion("16.0.0"), Channel("alpha") }, - { UtilityVersion("15.8.0"), Channel("alpha") }, - { UtilityVersion("15.1.0"), Channel("beta") }, - }; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" }, - { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" }, - { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" }, - { "Id", "Name", "Moniker", "13.2.0", "", {}, { "Command" }, "Path4" }, - { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" }, - { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" }, - { "Id", "Name", "Moniker", "13.2.0-bugfix", "", { "foo" }, { "com3" }, "Path7" }, - { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" }, - }); - - TestPrepareForRead(index); - - if (!AreChannelsSupported(index)) - { - return; - } - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = GetPathStringByKey(index, results.Matches[0].first, "", ""); - REQUIRE(result == "Path3"); - - result = GetPathStringByKey(index, results.Matches[0].first, "", "alpha"); - REQUIRE(result == "Path2"); - - result = GetPathStringByKey(index, results.Matches[0].first, "", "beta"); - REQUIRE(result == "Path5"); - - auto nonResult = index.GetManifestIdByKey(results.Matches[0].first, "", "gamma"); - REQUIRE(!nonResult.has_value()); -} - -TEST_CASE("SQLiteIndex_PathString_CaseInsensitive", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" }, - { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" }, - { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" }, - { "Id", "Name", "Moniker", "13.2.0-BUGFIX", "", {}, { "Command" }, "Path4" }, - { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" }, - { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" }, - { "Id", "Name", "Moniker", "13.2.0-bugfix", "beta", { "foo" }, { "com3" }, "Path7" }, - { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" }, - }); - - TestPrepareForRead(index); - - if (!AreChannelsSupported(index)) - { - return; - } - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = index.GetManifestIdByKey(results.Matches[0].first, "", "Alpha"); - REQUIRE(result.has_value()); - - result = index.GetManifestIdByKey(results.Matches[0].first, "13.2.0-BugFix", ""); - REQUIRE(result.has_value()); - - result = index.GetManifestIdByKey(results.Matches[0].first, "13.2.0-BugFix", "BETA"); - REQUIRE(result.has_value()); -} - -TEST_CASE("SQLiteIndex_SearchResultsTableSearches", "[sqliteindex][V1_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - { - (void)SimpleTestSetup(tempFile, manifest, SQLiteVersion{ 1, 0 }); - } - - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - Schema::V1_0::SearchResultsTable search(connection); - - std::string value = "test"; - - // Perform every type of field and match search - PackageMatchFilter filter(PackageMatchField::Id, MatchType::Exact, value); - - for (auto field : { PackageMatchField::Id, PackageMatchField::Name, PackageMatchField::Moniker, PackageMatchField::Tag, PackageMatchField::Command }) - { - filter.Field = field; - - for (auto match : { MatchType::Exact, MatchType::Fuzzy, MatchType::FuzzySubstring, MatchType::Substring, MatchType::Wildcard }) - { - filter.Type = match; - search.SearchOnField(filter); - } - } -} - -TEST_CASE("SQLiteIndex_Search_EmptySearch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile,{ - { "Id1", "Name", "Moniker", "Version1", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id1", "Name", "Moniker", "Version2", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - { "Id3", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path4" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 3); -} - -TEST_CASE("SQLiteIndex_Search_Exact", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, "Id"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); -} - -TEST_CASE("SQLiteIndex_Search_Substring", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "Id"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); -} - -TEST_CASE("SQLiteIndex_Search_ExactBeforeSubstring", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "Id"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); - - REQUIRE(GetIdStringById(index, results.Matches[0].first) == "Id"); - REQUIRE(GetIdStringById(index, results.Matches[1].first) == "Id2"); -} - -TEST_CASE("SQLiteIndex_Search_SingleFilter", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Name, MatchType::Substring, "a"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); - - request.Filters[0].Value = "e"; - - results = index.Search(request); - REQUIRE(results.Matches.size() == 1); -} - -TEST_CASE("SQLiteIndex_Search_Multimatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id1", "Name1", "Moniker", "Version1", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id2", "Name", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path3" }, - { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path4" }, - { "Id3", "Name", "Moniker", "Version1", "", { "Tag" }, { "Command" }, "Path5" }, - { "Id3", "Name", "Moniker", "Version2", "", { "Tag" }, { "Command" }, "Path6" }, - { "Id3", "Name", "Moniker", "Version3", "", { "Tag" }, { "Command" }, "Path7" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - // An empty string should match all substrings - request.Query = RequestMatch(MatchType::Substring, ""); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 3); -} - -TEST_CASE("SQLiteIndex_Search_QueryAndFilter", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "Id"); - request.Filters.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = GetIdStringById(index, results.Matches[0].first); - REQUIRE(result == "Id2"); -} - -TEST_CASE("SQLiteIndex_Search_QueryAndMultipleFilters", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path1" }, - { "Id1", "Name1", "Moniker", "Version1", "Channel", { "floor" }, { "com3" }, "Path2" }, - { "Id2", "Name", "Moniker", "Version", "", {}, { "Command" }, "Path3" }, - { "Id2", "Name", "Moniker", "Version", "Channel", {}, { "Command" }, "Path4" }, - { "Id3", "Tagit", "Moniker", "Version1", "", { "foo" }, { "com3" }, "Path5" }, - { "Id3", "Tagit", "Moniker", "Version2", "", { "foo" }, { "com3" }, "Path6" }, - { "Id3", "Tagit", "new", "Version3", "", { "foo" }, { "com3" }, "Path7" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "tag"); - request.Filters.emplace_back(PackageMatchField::Command, MatchType::Exact, "com3"); - request.Filters.emplace_back(PackageMatchField::Tag, MatchType::Substring, "foo"); - request.Filters.emplace_back(PackageMatchField::Moniker, MatchType::Substring, "new"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = GetIdStringById(index, results.Matches[0].first); - REQUIRE(result == "Id3"); -} - -TEST_CASE("SQLiteIndex_Search_SimpleICULike", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - // Insert decomposed character: [upper] A + umlaut - SQLiteIndex index = SearchTestSetup(tempFile, { - { u8"\x41\x308wesomeApp", "HasUmlaut", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path1" }, - { u8"AwesomeApp", "Nope", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path2" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - // Search for anything containing: [lower] a + umlaut - request.Filters.emplace_back(PackageMatchField::Id, MatchType::Substring, u8"\xE4"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = GetNameStringById(index, results.Matches[0].first); - REQUIRE(result == "HasUmlaut"); -} - -TEST_CASE("SQLiteIndex_Search_MaximumResults_Equal", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.MaximumResults = 3; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 3); - REQUIRE(!results.Truncated); -} - -TEST_CASE("SQLiteIndex_Search_MaximumResults_Less", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.MaximumResults = 2; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); - REQUIRE(results.Truncated); -} - -TEST_CASE("SQLiteIndex_Search_MaximumResults_Greater", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.MaximumResults = 4; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 3); - REQUIRE(!results.Truncated); -} - -TEST_CASE("SQLiteIndex_Search_QueryAndInclusion", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::CaseInsensitive, "id3"); - request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 3); -} - -TEST_CASE("SQLiteIndex_Search_InclusionOnly", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); -} - -TEST_CASE("SQLiteIndex_Search_InclusionAndFilter", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); - request.Filters.emplace_back(PackageMatchField::Name, MatchType::CaseInsensitive, "name"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto result = GetIdStringById(index, results.Matches[0].first); - REQUIRE(result == "Nope"); -} - -TEST_CASE("SQLiteIndex_Search_QueryInclusionAndFilter", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "monicka", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, - { "Id3", "No", "moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "id3"); - request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "na"); - request.Filters.emplace_back(PackageMatchField::Moniker, MatchType::CaseInsensitive, "MONIKER"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); -} - -TEST_CASE("SQLiteIndex_Search_CaseInsensitive", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Nope", "id3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::CaseInsensitive, "id3"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 3); -} - -TEST_CASE("SQLiteIndex_Search_StartsWith", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "NopeId", "id3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, - { "Id2", "Na", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2" }, - { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, - }); - - TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.push_back(PackageMatchFilter(PackageMatchField::Id, MatchType::StartsWith, "id")); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); -} - -TEST_CASE("SQLiteIndex_Search_Query_PackageFamilyNameSubstring", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "PFN"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 0); -} - -TEST_CASE("SQLiteIndex_Search_Query_ProductCodeSubstring", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "PC"); - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 0); -} - -TEST_CASE("SQLiteIndex_Search_Query_PackageFamilyNameMatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "pfn1"); - - auto results = index.Search(request); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.size() == 0); - } -} - -TEST_CASE("SQLiteIndex_Search_Query_ProductCodeMatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Substring, "pc2"); - - auto results = index.Search(request); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.size() == 0); - } -} - -TEST_CASE("SQLiteIndex_Search_PackageFamilyNameSubstring", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::PackageFamilyName, MatchType::Substring, "PFN"); - - auto results = index.Search(request); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 3); - } - else - { - REQUIRE(results.Matches.size() == 0); - } -} - -TEST_CASE("SQLiteIndex_Search_ProductCodeSubstring", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::ProductCode, MatchType::Substring, "PC"); - - auto results = index.Search(request); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 3); - } - else - { - REQUIRE(results.Matches.size() == 0); - } -} - -TEST_CASE("SQLiteIndex_Search_PackageFamilyNameMatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::PackageFamilyName, MatchType::Exact, "pfn1"); - - auto results = index.Search(request); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.size() == 0); - } -} - -TEST_CASE("SQLiteIndex_Search_ProductCodeMatch", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::ProductCode, MatchType::Exact, "pc2"); - - auto results = index.Search(request); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.size() == 0); - } -} - -TEST_CASE("SQLiteIndex_CheckConsistency_Failure", "[sqliteindex][V1_1]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; - Manifest manifest1; - manifest1.Installers.push_back({}); - manifest1.Id = "test.id"; - manifest1.DefaultLocalization.Add("Test Name"); - manifest1.Moniker = "testmoniker"; - manifest1.Version = "1.0.0"; - manifest1.Channel = "test"; - manifest1.DefaultLocalization.Add({ "t1", "t2" }); - manifest1.Installers[0].Commands = { "test1", "test2" }; - - std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; - Manifest manifest2; - manifest2.Installers.push_back({}); - manifest2.Id = "test.woah"; - manifest2.DefaultLocalization.Add("Test Name WOAH"); - manifest2.Moniker = "testmoniker"; - manifest2.Version = "1.0.0"; - manifest2.Channel = "test"; - manifest2.DefaultLocalization.Add({}); - manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; - - rowid_t manifestRowId = 0; - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 1 }); - - index.AddManifest(manifest1, manifest1Path); - index.AddManifest(manifest2, manifest2Path); - - // Get the first manifest's id for removal - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, manifest1.Id)); - auto result = index.Search(request); - - REQUIRE(result.Matches.size() == 1); - manifestRowId = result.Matches[0].first; - } - - { - // Open it directly to modify the table - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - Builder::StatementBuilder builder; - builder.DeleteFrom(Schema::V1_0::IdTable::TableName()).Where(RowIDName).Equals(manifestRowId); - builder.Execute(connection); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - REQUIRE(!index.CheckConsistency(true)); - } -} - -TEST_CASE("SQLiteIndex_GetMultiProperty_PackageFamilyName", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1", "PFN2" }, {} }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::PackageFamilyName); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(props.size() == 2); - REQUIRE(std::find(props.begin(), props.end(), FoldCase("PFN1"sv)) != props.end()); - REQUIRE(std::find(props.begin(), props.end(), FoldCase("PFN2"sv)) != props.end()); - } - else - { - REQUIRE(props.empty()); - } -} - -TEST_CASE("SQLiteIndex_GetMultiProperty_ProductCode", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::ProductCode); - - if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) - { - REQUIRE(props.size() == 2); - REQUIRE(std::find(props.begin(), props.end(), FoldCase("PC1"sv)) != props.end()); - REQUIRE(std::find(props.begin(), props.end(), FoldCase("PC2"sv)) != props.end()); - } - else - { - REQUIRE(props.empty()); - } -} - -TEST_CASE("SQLiteIndex_GetMultiProperty_Tag", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag1", "Tag2" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::Tag); - - REQUIRE(props.size() == 2); - REQUIRE(std::find(props.begin(), props.end(), "Tag1") != props.end()); - REQUIRE(std::find(props.begin(), props.end(), "Tag2") != props.end()); -} - -TEST_CASE("SQLiteIndex_GetMultiProperty_Command", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag1", "Tag2" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 1); - - auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::Command); - - REQUIRE(props.size() == 1); - REQUIRE(props[0] == "Command"); -} - -TEST_CASE("SQLiteIndex_ManifestMetadata", "[sqliteindex][V1_7]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, - { "Id2", "Name2", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2", { "PFN1", "PFN2" }, {} }, - }, SQLiteVersion{ 1, 7 }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - - auto results = index.Search(request); - REQUIRE(results.Matches.size() == 2); - - for (const auto [id, match] : results.Matches) - { - REQUIRE(index.GetMetadataByManifestId(id).empty()); - } - - auto manifestId1 = results.Matches[0].first; - auto manifestId2 = results.Matches[1].first; - - std::string metadataValue = "data about data"; - - index.SetMetadataByManifestId(manifestId1, PackageVersionMetadata::InstalledType, metadataValue); - - if (IsManifestMetadataSupported(index, testVersion)) - { - auto metadataResult = index.GetMetadataByManifestId(manifestId1); - REQUIRE(metadataResult.size() == 1); - REQUIRE(metadataResult[0].first == PackageVersionMetadata::InstalledType); - REQUIRE(metadataResult[0].second == metadataValue); - } - else - { - REQUIRE(index.GetMetadataByManifestId(manifestId1).empty()); - } - - REQUIRE(index.GetMetadataByManifestId(manifestId2).empty()); -} - -TEST_CASE("SQLiteIndex_NormNameAndPublisher_Exact", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string testName = "Name"; - std::string testPublisher = "Publisher"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName, testPublisher)); - - auto results = index.Search(request); - - if (AreNormalizedNameAndPublisherSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.empty()); - } -} - -TEST_CASE("SQLiteIndex_NormNameAndPublisher_Simple", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string testName = "Name"; - std::string testPublisher = "Publisher"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName + " 1.0", testPublisher + " Corporation")); - - auto results = index.Search(request); - - if (AreNormalizedNameAndPublisherSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.empty()); - } -} - -TEST_CASE("SQLiteIndex_NormNameAndPublisher_Complex", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string testName = "Name"; - std::string testPublisher = "Publisher"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, - { "Id2", testName, "Different Publisher", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2", {}, { "PC1", "PC2" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName + " 1.0", testPublisher)); - - auto results = index.Search(request); - - if (AreNormalizedNameAndPublisherSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.empty()); - } -} - -TEST_CASE("SQLiteIndex_NormNameAndPublisher_AppsAndFeatures", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string testName = "Name"; - std::string testPublisher = "Publisher"; - std::string arpTestName = "Other Thing"; - std::string arpTestPublisher = "Big Company Name"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" }, arpTestName, arpTestPublisher }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, arpTestName, arpTestPublisher)); - - auto results = index.Search(request); - - if (AreNormalizedNameAndPublisherSupported(index, testVersion)) - { - REQUIRE(results.Matches.size() == 1); - } - else - { - REQUIRE(results.Matches.empty()); - } -} - -TEST_CASE("SQLiteIndex_ManifestHash_Present", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - uint8_t data[4] = { 1, 2, 3, 4 }; - SHA256::HashBuffer hash = SHA256::ComputeHash(data, sizeof(data)); - - SQLiteIndex index = CreateTestIndex(tempFile); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "Bar"; - manifest.StreamSha256 = hash; - index.AddManifest(manifest, "path"); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - - auto hashResult = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ManifestSHA256Hash); - - // Regardless of what hash, it should still be a SHA256 hash - REQUIRE(hashResult); - auto hashResultBytes = SHA256::ConvertToBytes(hashResult.value()); - REQUIRE(hash.size() == hashResultBytes.size()); - - if (AreManifestHashesSupported(index, testVersion)) - { - REQUIRE(std::equal(hash.begin(), hash.end(), hashResultBytes.begin())); - } -} - -TEST_CASE("SQLiteIndex_ManifestHash_Missing", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "Bar"; - index.AddManifest(manifest, "path"); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - - auto hashResult = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ManifestSHA256Hash); - - if (AreManifestHashesSupported(index, testVersion)) - { - REQUIRE(!hashResult); - } -} - -TEST_CASE("SQLiteIndex_ManifestArpVersion_Present_Add", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "Bar"; - manifest.Installers.push_back({}); - manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; - - index.AddManifest(manifest, "path"); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - - auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion); - auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion); - - if (AreArpVersionsSupported(index, testVersion)) - { - REQUIRE(arpMin); - REQUIRE(UtilityVersion(arpMin.value()) == UtilityVersion(manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion)); - REQUIRE(arpMax); - REQUIRE(UtilityVersion(arpMax.value()) == UtilityVersion(manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion)); - } - else - { - REQUIRE_FALSE(arpMin); - REQUIRE_FALSE(arpMax); - } -} - -TEST_CASE("SQLiteIndex_ManifestArpVersion_Present_AddThenUpdate", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "Bar"; - manifest.Installers.push_back({}); - manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; - - index.AddManifest(manifest, "path"); - - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.1"; - - index.UpdateManifest(manifest, "path"); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - - auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion); - auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion); - - if (AreArpVersionsSupported(index, testVersion)) - { - REQUIRE(arpMin); - REQUIRE(arpMin.value() == "1.1"); - REQUIRE(arpMax); - REQUIRE(arpMax.value() == "1.1"); - } - else - { - REQUIRE_FALSE(arpMin); - REQUIRE_FALSE(arpMax); - } -} - -TEST_CASE("SQLiteIndex_ManifestArpVersion_Empty", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "Bar"; - index.AddManifest(manifest, "path"); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - - auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion); - auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion); - - if (AreArpVersionsSupported(index, testVersion) && !AreArpVersionsNullable(index)) - { - REQUIRE(arpMin); - REQUIRE(arpMin.value() == ""); - REQUIRE(arpMax); - REQUIRE(arpMax.value() == ""); - } - else - { - REQUIRE_FALSE(arpMin); - REQUIRE_FALSE(arpMax); - } -} - -TEST_CASE("SQLiteIndex_RemoveManifestArpVersionKeepUsedDeleteUnused", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "10.0"; - manifest.Installers.push_back({}); - manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; - - index.AddManifest(manifest, "path"); - - Manifest manifest2; - manifest2.Id = "Foo2"; - manifest2.Version = "1.0"; - manifest2.Installers.push_back({}); - manifest2.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest2.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest2.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "10.0"; - - index.AddManifest(manifest2, "path2"); - - // Before removing, "10.0", "1.0" and "1.1" should all exist. - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "10.0").has_value()); - REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.0").has_value()); - REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.1").has_value()); - } - - index.RemoveManifest(manifest); - - // After removing the first manifest, "10.0" and "1.0" should still stay, "1.1" should be removed. - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); - REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "10.0").has_value()); - REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.0").has_value()); - REQUIRE_FALSE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.1").has_value()); - } -} - -TEST_CASE("SQLiteIndex_ManifestArpVersionConflict_AddThrows", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "10.0"; - manifest.DefaultLocalization.Add("ArpVersionCheckConsistencyTest"); - manifest.Moniker = "testmoniker"; - manifest.Installers.push_back({}); - manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; - - index.AddManifest(manifest, "path"); - - REQUIRE(index.CheckConsistency(true)); - - // Add a conflicting one - manifest.Version = "10.1"; - - REQUIRE_THROWS_HR(index.AddManifest(manifest, "path2"), APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); -} - -TEST_CASE("SQLiteIndex_ManifestArpVersionConflict_UpdateThrows", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "10.0"; - manifest.DefaultLocalization.Add("ArpVersionCheckConsistencyTest"); - manifest.Moniker = "testmoniker"; - manifest.Installers.push_back({}); - manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; - - index.AddManifest(manifest, "path"); - REQUIRE(index.CheckConsistency(true)); - - // Add another version - manifest.Version = "10.1"; - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "2.0"; - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "2.1"; - - index.AddManifest(manifest, "path2"); - REQUIRE(index.CheckConsistency(true)); - - // Update to a conflict - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "2.1"; - - REQUIRE_THROWS_HR(index.UpdateManifest(manifest, "path2"), APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); -} - -TEST_CASE("SQLiteIndex_ManifestArpVersion_ValidateManifestAgainstIndex", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "10.0"; - manifest.Installers.push_back({}); - manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; - - index.AddManifest(manifest, "path"); - - // Updating same version should not result in failure. - REQUIRE_NOTHROW(ValidateManifestArpVersion(&index, manifest)); - - // Add different version should result in failure. - manifest.Version = "10.1"; - REQUIRE_THROWS(ValidateManifestArpVersion(&index, manifest)); -} - -TEST_CASE("SQLiteIndex_CheckConsistency_FindEmbeddedNull", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); - - Manifest manifest; - manifest.Id = "Foo"; - manifest.Version = "10.0"; - manifest.Installers.push_back({}); - manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; - manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; - - index.AddManifest(manifest, "path"); - - // Inject a null character using SQL without binding since we block it - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - Statement update = Statement::Create(connection, "Update versions set version = '10.0'||char(0)||'After Null' where version = '10.0'"); - update.Execute(); - - REQUIRE(!index.CheckConsistency(true)); -} - -TEST_CASE("SQLiteIndex_MapDataFolding_Tags", "[sqliteindex][mapdatafolding]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string tag1 = "Tag1"; - std::string tag2 = "Tag2"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Publisher", "Moniker", "Version1", "", { tag1 }, { "Command" }, "Path1", {}, { "PC1" } }, - { "Id", "Name", "Publisher", "Moniker", "Version2", "", { tag2 }, { "Command" }, "Path2", {}, { "PC2" } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - SearchRequest request1; - request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Tag, MatchType::Exact, tag1)); - auto results1 = index.Search(request1); - - SearchRequest request2; - request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Tag, MatchType::Exact, tag2)); - auto results2 = index.Search(request2); - - REQUIRE(results1.Matches.size() == 1); - REQUIRE(results2.Matches.size() == 1); - REQUIRE(results1.Matches[0].first == results2.Matches[0].first); -} - -TEST_CASE("SQLiteIndex_MapDataFolding_PFNs", "[sqliteindex][mapdatafolding]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string pfn1 = "PFN1"; - std::string pfn2 = "PFN2"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Publisher", "Moniker", "Version1", "", { }, { "Command" }, "Path1", { pfn1 }, { } }, - { "Id", "Name", "Publisher", "Moniker", "Version2", "", { }, { "Command" }, "Path2", { pfn2 }, { } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - if (!AreChannelsSupported(index)) - { - return; - } - - SearchRequest request1; - request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pfn1)); - auto results1 = index.Search(request1); - - SearchRequest request2; - request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pfn2)); - auto results2 = index.Search(request2); - - REQUIRE(results1.Matches.size() == 1); - REQUIRE(results2.Matches.size() == 1); - REQUIRE(results1.Matches[0].first == results2.Matches[0].first); - - auto versionKeys = index.GetVersionKeysById(results1.Matches[0].first); - REQUIRE(versionKeys.size() == 2); - - auto manifestId1 = versionKeys[0].ManifestId; - auto manifestId2 = versionKeys[1].ManifestId; - - auto pfnValues1 = index.GetMultiPropertyByPrimaryId(manifestId1, PackageVersionMultiProperty::PackageFamilyName); - auto pfnValues2 = index.GetMultiPropertyByPrimaryId(manifestId2, PackageVersionMultiProperty::PackageFamilyName); - - if (IsMapDataFoldingSupported(index, testVersion)) - { - REQUIRE(pfnValues1.size() == 2); - REQUIRE(pfnValues2.size() == 2); - REQUIRE(pfnValues1[0] != pfnValues1[1]); - } - else if (IsMapDataFolded(index)) - { - if (manifestId1 > manifestId2) - { - REQUIRE(pfnValues1.size() == 2); - REQUIRE(pfnValues2.size() == 0); - REQUIRE(pfnValues1[0] != pfnValues1[1]); - } - else - { - REQUIRE(pfnValues1.size() == 0); - REQUIRE(pfnValues2.size() == 2); - REQUIRE(pfnValues2[0] != pfnValues2[1]); - } - } - else - { - REQUIRE(pfnValues1.size() == 1); - REQUIRE(pfnValues2.size() == 1); - REQUIRE(pfnValues1[0] != pfnValues2[0]); - } -} - -TEST_CASE("SQLiteIndex_MapDataFolding_ProductCodes", "[sqliteindex][mapdatafolding]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string pc1 = "PC1"; - std::string pc2 = "PC2"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { "Id", "Name", "Publisher", "Moniker", "Version1", "", { }, { "Command" }, "Path1", { }, { pc1 } }, - { "Id", "Name", "Publisher", "Moniker", "Version2", "", { }, { "Command" }, "Path2", { }, { pc2 } }, - }); - - SQLiteVersion testVersion = TestPrepareForRead(index); - - if (!AreChannelsSupported(index)) - { - return; - } - - SearchRequest request1; - request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pc1)); - auto results1 = index.Search(request1); - - SearchRequest request2; - request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pc2)); - auto results2 = index.Search(request2); - - REQUIRE(results1.Matches.size() == 1); - REQUIRE(results2.Matches.size() == 1); - REQUIRE(results1.Matches[0].first == results2.Matches[0].first); - - auto versionKeys = index.GetVersionKeysById(results1.Matches[0].first); - REQUIRE(versionKeys.size() == 2); - - auto manifestId1 = versionKeys[0].ManifestId; - auto manifestId2 = versionKeys[1].ManifestId; - - auto pcValues1 = index.GetMultiPropertyByPrimaryId(manifestId1, PackageVersionMultiProperty::ProductCode); - auto pcValues2 = index.GetMultiPropertyByPrimaryId(manifestId2, PackageVersionMultiProperty::ProductCode); - - REQUIRE(pcValues1.size() == 1); - REQUIRE(pcValues2.size() == 1); - REQUIRE(pcValues1[0] != pcValues2[0]); -} - -TEST_CASE("SQLiteIndex_FilePath_Memory", "[sqliteindex]") -{ - SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET); - auto contextData = index.GetContextData(); - REQUIRE(!contextData.Contains(Schema::Property::DatabaseFilePath)); -} - -TEST_CASE("SQLiteIndex_FilePath_Create", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile); - auto contextData = index.GetContextData(); - REQUIRE(contextData.Contains(Schema::Property::DatabaseFilePath)); - REQUIRE(contextData.Get() == tempFile.GetPath()); -} - -TEST_CASE("SQLiteIndex_FilePath_Open", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile); - } - - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); - auto contextData = index.GetContextData(); - REQUIRE(contextData.Contains(Schema::Property::DatabaseFilePath)); - REQUIRE(contextData.Get() == tempFile.GetPath()); -} - -TEST_CASE("SQLiteIndex_MigrateTo_Unsupported", "[sqliteindex][V1_7]") -{ - SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLiteVersion{ 1, 6 }); - REQUIRE(!index.MigrateTo(SQLiteVersion{ 1, 7 })); -} - -TEST_CASE("SQLiteIndex_MigrateTo_Empty", "[sqliteindex][V2_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - { - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, SQLiteVersion{ 1, 7 }); - REQUIRE(index.MigrateTo(SQLiteVersion{ 2, 0 })); - REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 }); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); - REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 }); - } -} - -TEST_CASE("SQLiteIndex_MigrateTo_Data", "[sqliteindex][V2_0]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string packageId1 = "Id1"; - std::string packageId2 = "Id2"; - std::string packageId3 = "Id3"; - - SQLiteIndex index = SearchTestSetup(tempFile, { - { packageId1, "Name1", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, - { packageId2, "Name2", "Moniker", "Version", "", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, - { packageId3, "Name3", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, - }); - - auto preMigrationVersion = index.GetVersion(); - - if (preMigrationVersion == SQLiteVersion{ 1, 7 }) - { - REQUIRE(index.MigrateTo(SQLiteVersion{ 2, 0 })); - REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 }); - - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - auto updateData = Schema::V2_0::PackageUpdateTrackingTable::GetUpdatesSince(connection, 0); - - REQUIRE(updateData.size() == 3); - REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId1; }) == 1); - REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId2; }) == 1); - REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId3; }) == 1); - } - else - { - REQUIRE(!index.MigrateTo(SQLiteVersion{ 2, 0 })); - REQUIRE(index.GetVersion() == preMigrationVersion); - } -} - -TEST_CASE("SQLiteIndex_Property_IntermediateFilePath", "[sqliteindex]") -{ - SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET); - std::filesystem::path intermediateFilePath = "A:\\Path"; - index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediateFilePath.u8string()); - - auto contextData = index.GetContextData(); - REQUIRE(contextData.Contains(Schema::Property::IntermediateFileOutputPath)); - REQUIRE(contextData.Get() == intermediateFilePath); -} - -struct ManifestAndPath -{ - Manifest Manifest; - std::string Path; -}; - -void CreateFakeManifestAndPath( - ManifestAndPath& manifestAndPath, - const string_t& publisher, - std::string_view version = "1.0.0", - std::optional arpMinVersion = {}, - std::optional arpMaxVersion = {}) -{ - CreateFakeManifest(manifestAndPath.Manifest, publisher, version); - manifestAndPath.Path = ConvertToUTF8(CreateNewGuidNameWString()); - manifestAndPath.Manifest.StreamSha256 = SHA256::ComputeHash(manifestAndPath.Path); - - if (arpMinVersion) - { - manifestAndPath.Manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.back().DisplayVersion = arpMinVersion.value(); - } - - if (arpMaxVersion) - { - manifestAndPath.Manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; - manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); - manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.back().DisplayVersion = arpMaxVersion.value(); - } -} - -std::filesystem::path GetOnlyChild(const std::filesystem::path& parent) -{ - auto parentDirectoryIterator = std::filesystem::directory_iterator{ parent }; - std::filesystem::path result = parentDirectoryIterator->path(); - REQUIRE(++parentDirectoryIterator == std::filesystem::directory_iterator{}); - return result; -} - -void CheckIntermediates(const std::filesystem::path& baseDirectory, const std::vector>& expectedIntermediatesData, std::chrono::seconds sleep = 1s) -{ - std::filesystem::path intermediatesDirectory = baseDirectory / "packages"; - - size_t intermediatePackageCount = std::count_if(std::filesystem::directory_iterator{ intermediatesDirectory }, std::filesystem::directory_iterator{}, [](const auto&){ return true; }); - REQUIRE(intermediatePackageCount == expectedIntermediatesData.size()); - - for (const auto& versions : expectedIntermediatesData) - { - REQUIRE(!versions.empty()); - INFO(versions[0].Manifest.Id); - std::filesystem::path packageDirectory = intermediatesDirectory / ConvertToUTF16(versions[0].Manifest.Id); - - REQUIRE(std::filesystem::exists(packageDirectory)); - std::filesystem::path hashDirectory = GetOnlyChild(packageDirectory); - - std::filesystem::path versionDataFile = GetOnlyChild(hashDirectory); - std::ifstream versionDataStream{ versionDataFile, std::ios_base::in | std::ios_base::binary }; - auto versionDataBytes = ReadEntireStreamAsByteArray(versionDataStream); - - PackageVersionDataManifest versionDataManifest; - versionDataManifest.Deserialize(PackageVersionDataManifest::CreateDecompressor().Decompress(versionDataBytes)); - - const auto& versionDataVersions = versionDataManifest.Versions(); - REQUIRE(versionDataVersions.size() == versions.size()); - - for (const auto& manifestAndPath : versions) - { - const auto& versionDataItr = std::find_if(versionDataVersions.begin(), versionDataVersions.end(), [&](const auto& v) { return v.Version == manifestAndPath.Manifest.Version; }); - REQUIRE(versionDataItr != versionDataVersions.end()); - const auto& versionData = *versionDataItr; - - REQUIRE(manifestAndPath.Path == versionData.ManifestRelativePath); - REQUIRE(SHA256::ConvertToString(manifestAndPath.Manifest.StreamSha256) == versionData.ManifestHash); - - auto versionRange = manifestAndPath.Manifest.GetArpVersionRange(); - if (!versionRange.IsEmpty()) - { - REQUIRE(versionData.ArpMinVersion); - REQUIRE(versionRange.GetMinVersion() == versionData.ArpMinVersion.value()); - REQUIRE(versionData.ArpMaxVersion); - REQUIRE(versionRange.GetMaxVersion() == versionData.ArpMaxVersion.value()); - } - } - } - - // This is needed to force the timestamp to roll over to a new value for the next call to this function. - // An alternate solution would be to hook the timestamp function and control the values it returns - // so that we can advance/halt time arbitrarily. - std::this_thread::sleep_for(sleep); -} - -void PrepareAndCheckIntermediates(const std::filesystem::path& baseFile, const std::filesystem::path& preparedFile, const std::vector>& expectedIntermediatesData, std::chrono::seconds sleep = 1s) -{ - TempDirectory intermediatesDirectory{ "v2_0_intermediates" }; - INFO("Intermediates directory: " << intermediatesDirectory.GetPath()); - - std::filesystem::copy_file(baseFile, preparedFile, std::filesystem::copy_options::overwrite_existing); - - SQLiteIndex index = SQLiteIndex::Open(preparedFile.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); - index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory); - index.PrepareForPackaging(); - - CheckIntermediates(intermediatesDirectory, expectedIntermediatesData, sleep); -} - -TEST_CASE("SQLiteIndex_V2_0_UsageFlow_Simple", "[sqliteindex][V2_0]") -{ - TempFile baseFile{ "v2_0_index_tempdb"s, ".db"s }; - TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s }; - INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]"); - - // Create empty index - std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 2, 0 }); - - std::string publisher = "Publisher"; - ManifestAndPath manifest1; - CreateFakeManifestAndPath(manifest1, publisher, "1.0"); - - { - // Open existing file to add a manifest - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); - index.AddManifest(manifest1.Manifest, manifest1.Path); - } - - PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } }, 0s); -} - -TEST_CASE("SQLiteIndex_V2_0_UsageFlow_Complex", "[sqliteindex][V2_0]") -{ - TempFile baseFile{ "v2_0_index_tempdb"s, ".db"s }; - TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s }; - INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]"); - - // Create empty index - std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 2, 0 }); - - // Open existing file to add a new package - std::string Publisher1 = "Publisher1"; - ManifestAndPath manifest1; - CreateFakeManifestAndPath(manifest1, Publisher1, "1.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); - index.AddManifest(manifest1.Manifest, manifest1.Path); - } - - PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } }); - - // Open existing file to add another new package - ManifestAndPath manifest2; - CreateFakeManifestAndPath(manifest2, "Publisher2", "1.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); - index.AddManifest(manifest2.Manifest, manifest2.Path); - } - - PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 } }); - - // Open existing file to add a new version of existing package - ManifestAndPath manifest3; - CreateFakeManifestAndPath(manifest3, Publisher1, "2.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); - index.AddManifest(manifest3.Manifest, manifest3.Path); - } - - PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1, manifest3 } }); - - // Open existing file to add a new version of existing package and update an existing version - manifest2.Manifest.StreamSha256 = SHA256::ComputeHash(manifest2.Manifest.Id); - - ManifestAndPath manifest4; - CreateFakeManifestAndPath(manifest4, Publisher1, "3.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); - index.UpdateManifest(manifest2.Manifest, manifest2.Path); - index.AddManifest(manifest4.Manifest, manifest4.Path); - } - - PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3, manifest4 } }, 0s); -} - -void MigratePrepareAndCheckIntermediates(const std::filesystem::path& baseFile, const std::filesystem::path& preparedFile, const std::vector>& expectedIntermediatesData) -{ - TempDirectory intermediatesDirectory{ "v2_0_intermediates" }; - INFO("Intermediates directory: " << intermediatesDirectory.GetPath()); - - std::filesystem::copy_file(baseFile, preparedFile, std::filesystem::copy_options::overwrite_existing); - - SQLiteIndex index = SQLiteIndex::Open(preparedFile.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); - index.MigrateTo({ 2, 0 }); - index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory); - index.PrepareForPackaging(); - - CheckIntermediates(intermediatesDirectory, expectedIntermediatesData, 0s); -} - -TEST_CASE("SQLiteIndex_V2_0_UsageFlow_ComplexMigration", "[sqliteindex][V2_0]") -{ - TempFile baseFile{ "v1_7_index_tempdb"s, ".db"s }; - TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s }; - INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]"); - - // Create empty index - std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 1, 7 }); - - // Open existing file to add a new package - std::string Publisher1 = "Publisher1"; - ManifestAndPath manifest1; - CreateFakeManifestAndPath(manifest1, Publisher1, "1.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.AddManifest(manifest1.Manifest, manifest1.Path); - } - - MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } }); - - // Open existing file to add another new package - ManifestAndPath manifest2; - CreateFakeManifestAndPath(manifest2, "Publisher2", "1.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.AddManifest(manifest2.Manifest, manifest2.Path); - } - - MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1 } }); - - // Open existing file to add a new version of existing package - ManifestAndPath manifest3; - CreateFakeManifestAndPath(manifest3, Publisher1, "2.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.AddManifest(manifest3.Manifest, manifest3.Path); - } - - MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3 } }); - - // Open existing file to add a new version of existing package and update an existing version - manifest2.Manifest.StreamSha256 = SHA256::ComputeHash(manifest2.Manifest.Id); - - ManifestAndPath manifest4; - CreateFakeManifestAndPath(manifest4, Publisher1, "3.0"); - - { - SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - index.UpdateManifest(manifest2.Manifest, manifest2.Path); - index.AddManifest(manifest4.Manifest, manifest4.Path); - } - - MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3, manifest4 } }); -} - -TEST_CASE("SQLiteIndex_DependencyWithCaseMismatch", "[sqliteindex][V1_4]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest dependencyManifest1, dependencyManifest2, manifest; - SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); - - // Must contain some upper case - auto& publisher2 = "Test2"; - CreateFakeManifest(dependencyManifest2, publisher2); - index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); - - auto& publisher3 = "Test3"; - CreateFakeManifest(manifest, publisher3); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); - manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, ToLower(dependencyManifest2.Id), "1.0.0")); - - index.AddManifest(manifest, GetPathFromManifest(manifest)); -} - -TEST_CASE("SQLiteIndex_AddOrUpdateManifest", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - std::string manifestPath = "test/id/test.id-1.0.0.yaml"; - Manifest manifest; - manifest.Installers.push_back({}); - manifest.Id = "test.id"; - manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); - manifest.Moniker = "testmoniker"; - manifest.Version = "1.0.0"; - manifest.Channel = "test"; - manifest.DefaultLocalization.Add({ "t1", "t2" }); - manifest.Installers[0].Commands = { "test1", "test2" }; - - { - auto version = GENERATE(SQLiteVersion{ 1, 0 }, SQLiteVersion::Latest()); - SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, version); - - REQUIRE(index.AddOrUpdateManifest(manifest, manifestPath)); - } - - { - SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - - // Update should return false - REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath)); - - manifest.DefaultLocalization.Add("description2"); - - // Update should return false - REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath)); - - // Update with indexed changes should still return false - manifest.DefaultLocalization.Add("Test Name2"); - manifest.Moniker = "testmoniker2"; - manifest.DefaultLocalization.Add({ "t1", "t2", "t3" }); - manifest.Installers[0].Commands = {}; - - REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath)); - } -} - -TEST_CASE("SQLiteIndex_VersionStringPreserved", "[sqliteindex]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Manifest manifest; - SQLiteIndex index = CreateTestIndex(tempFile); - - string_t publisher = "Test"; - std::string version = GENERATE("1.0", "1.10"); - - CreateFakeManifest(manifest, publisher, version); - index.AddManifest(manifest, GetPathFromManifest(manifest)); - - TempDirectory intermediatesDirectory{ "v2_0_intermediates" }; - INFO("Intermediates directory: " << intermediatesDirectory.GetPath()); - - index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory); - index.PrepareForPackaging(); - - auto results = index.Search({}); - REQUIRE(results.Matches.size() == 1); - - std::string extractedVersion = GetPropertyStringById(index, results.Matches[0].first, PackageVersionProperty::Version); - - REQUIRE(extractedVersion == version); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace TestCommon; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; + +using UtilityVersion = AppInstaller::Utility::Version; +using SQLiteVersion = AppInstaller::SQLite::Version; + +SQLiteIndex CreateTestIndex(const std::string& filePath, std::optional version = {}) +{ + // If no specific version requested, then use generator to run against the last 3 versions. + if (!version) + { + SQLiteVersion latestVersion{ 2, 0 }; + SQLiteVersion versionMinus1 = SQLiteVersion{ 1, 7 }; + SQLiteVersion versionMinus2 = SQLiteVersion{ 1, 6 }; + + version = GENERATE_COPY(SQLiteVersion{ versionMinus2 }, SQLiteVersion{ versionMinus1 }, SQLiteVersion{ latestVersion }); + } + + return SQLiteIndex::CreateNew(filePath, version.value()); +} + +SQLiteVersion TestPrepareForRead(SQLiteIndex& index) +{ + SQLiteVersion latestVersion{ 2, 0 }; + SQLiteVersion versionMinus1 = SQLiteVersion{ 1, 7 }; + SQLiteVersion versionMinus2 = SQLiteVersion{ 1, 6 }; + + index.PrepareForPackaging(); + + if (index.GetVersion() == versionMinus2) + { + // Degenerate case where we don't need to do anything + } + else if (index.GetVersion() == versionMinus1) + { + SQLiteVersion version = GENERATE_COPY(SQLiteVersion{ versionMinus2 }, SQLiteVersion{ versionMinus1 }); + + if (version != versionMinus1) + { + index.ForceVersion(version); + return version; + } + } + else if (index.GetVersion() == latestVersion) + { + // This crosses major versions, so leave it at 2.0 always + } + + return index.GetVersion(); +} + +std::string GetPathFromManifest(Manifest& manifest) +{ + auto publisher = manifest.Id; + AppInstaller::Utility::FindAndReplace(publisher, ".", "/"); + + return AppInstaller::Utility::ToLower(publisher).append("/").append(manifest.Version); +} + +void CreateFakeManifest(Manifest& manifest, string_t publisher, string_t version = "1.0.0") +{ + manifest.Installers.push_back({}); + manifest.Id = publisher.append(".").append("Id"); + manifest.DefaultLocalization.Add(publisher.append(" Name")); + manifest.Moniker = "testmoniker"; + manifest.Version = version; + manifest.Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; +} + +SQLiteIndex SimpleTestSetup(const std::string& filePath, Manifest& manifest, std::optional version = {}) +{ + SQLiteIndex index = CreateTestIndex(filePath, version); + + string_t publisher = "Test"; + CreateFakeManifest(manifest, publisher); + + auto relativePath = GetPathFromManifest(manifest); + + index.AddManifest(manifest, relativePath); + + return index; +} + +struct IndexFields +{ + IndexFields( + std::string id, + std::string name, + std::string moniker, + std::string version, + std::string channel, + std::vector tags, + std::vector commands, + std::string path + ) : + Id(std::move(id)), + Name(std::move(name)), + Moniker(std::move(moniker)), + Version(std::move(version)), + Channel(std::move(channel)), + Tags(std::move(tags)), + Commands(std::move(commands)), + Path(std::move(path)) + {} + + IndexFields( + std::string id, + std::string name, + std::string moniker, + std::string version, + std::string channel, + std::vector tags, + std::vector commands, + std::string path, + std::vector packageFamilyNames, + std::vector productCodes + ) : + Id(std::move(id)), + Name(std::move(name)), + Moniker(std::move(moniker)), + Version(std::move(version)), + Channel(std::move(channel)), + Tags(std::move(tags)), + Commands(std::move(commands)), + Path(std::move(path)), + PackageFamilyNames(std::move(packageFamilyNames)), + ProductCodes(std::move(productCodes)) + {} + + IndexFields( + std::string id, + std::string name, + std::string publisher, + std::string moniker, + std::string version, + std::string channel, + std::vector tags, + std::vector commands, + std::string path, + std::vector packageFamilyNames, + std::vector productCodes + ) : + Id(std::move(id)), + Name(std::move(name)), + Publisher(std::move(publisher)), + Moniker(std::move(moniker)), + Version(std::move(version)), + Channel(std::move(channel)), + Tags(std::move(tags)), + Commands(std::move(commands)), + Path(std::move(path)), + PackageFamilyNames(std::move(packageFamilyNames)), + ProductCodes(std::move(productCodes)) + {} + + IndexFields( + std::string id, + std::string name, + std::string publisher, + std::string moniker, + std::string version, + std::string channel, + std::vector tags, + std::vector commands, + std::string path, + std::vector packageFamilyNames, + std::vector productCodes, + std::string arpName, + std::string arpPublisher + ) : + Id(std::move(id)), + Name(std::move(name)), + Publisher(std::move(publisher)), + Moniker(std::move(moniker)), + Version(std::move(version)), + Channel(std::move(channel)), + Tags(std::move(tags)), + Commands(std::move(commands)), + Path(std::move(path)), + PackageFamilyNames(std::move(packageFamilyNames)), + ProductCodes(std::move(productCodes)), + ArpName(std::move(arpName)), + ArpPublisher(std::move(arpPublisher)) + {} + + std::string Id; + std::string Name; + std::string Publisher; + std::string Moniker; + std::string Version; + std::string Channel; + std::vector Tags; + std::vector Commands; + std::string Path; + std::vector PackageFamilyNames; + std::vector ProductCodes; + std::string ArpName; + std::string ArpPublisher; +}; + +SQLiteIndex SearchTestSetup(const std::string& filePath, std::initializer_list data = {}, std::optional version = {}) +{ + SQLiteIndex index = CreateTestIndex(filePath, version); + + Manifest manifest; + + auto addFunc = [&](const IndexFields& d) + { + manifest.Id = d.Id; + manifest.DefaultLocalization.Add(d.Name); + manifest.DefaultLocalization.Add(d.Publisher); + manifest.Moniker = d.Moniker; + manifest.Version = d.Version; + manifest.DefaultLocalization.Add(d.Tags); + + manifest.Installers.resize(std::max(d.PackageFamilyNames.size(), d.ProductCodes.size())); + + if (manifest.Installers.size() == 0) + { + manifest.Installers.push_back({}); + } + + manifest.Channel = d.Channel; + manifest.Installers[0].Commands = d.Commands; + + for (size_t i = 0; i < d.PackageFamilyNames.size(); ++i) + { + manifest.Installers[i].PackageFamilyName = d.PackageFamilyNames[i]; + } + + for (size_t i = 0; i < d.ProductCodes.size(); ++i) + { + manifest.Installers[i].ProductCode = d.ProductCodes[i]; + } + + if (!d.ArpName.empty() || !d.ArpPublisher.empty()) + { + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayName = d.ArpName; + manifest.Installers[0].AppsAndFeaturesEntries[0].Publisher = d.ArpPublisher; + } + + index.AddManifest(manifest, d.Path); + }; + + for (const auto& d : data) + { + addFunc(d); + } + + return index; +} + +bool ArePackageFamilyNameAndProductCodeSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) +{ + UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); + return (index.GetVersion() >= SQLiteVersion{ 1, 1 } && testVersion >= SQLiteVersion{ 1, 1 }); +} + +bool AreNormalizedNameAndPublisherSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) +{ + UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); + return (index.GetVersion() >= SQLiteVersion{ 1, 2 } && testVersion >= SQLiteVersion{ 1, 2 }); +} + +bool IsManifestMetadataSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) +{ + UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); + return (index.GetVersion() >= SQLiteVersion{ 1, 1 } && testVersion >= SQLiteVersion{ 1, 1 }); +} + +bool AreManifestHashesSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) +{ + UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); + return (index.GetVersion() >= SQLiteVersion{ 1, 3 } && testVersion >= SQLiteVersion{ 1, 3 } && index.GetVersion() < SQLiteVersion{ 2, 0 }); +} + +bool AreArpVersionsSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) +{ + UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); + return (index.GetVersion() >= SQLiteVersion{ 1, 5 } && testVersion >= SQLiteVersion{ 1, 5 }); +} + +bool AreArpVersionsNullable(const SQLiteIndex& index) +{ + UNSCOPED_INFO("Index " << index.GetVersion()); + return (index.GetVersion() >= SQLiteVersion{ 2, 0 }); +} + +bool IsMapDataFoldingSupported(const SQLiteIndex& index, const SQLiteVersion& testVersion) +{ + UNSCOPED_INFO("Index " << index.GetVersion() << " | Test " << testVersion); + return (index.GetVersion() >= SQLiteVersion{ 1, 7 } && testVersion >= SQLiteVersion{ 1, 7 }); +} + +bool IsMapDataFolded(const SQLiteIndex& index) +{ + UNSCOPED_INFO("Index " << index.GetVersion()); + return (index.GetVersion() >= SQLiteVersion{ 1, 7 }); +} + +bool AreVersionKeysSupported(const SQLiteIndex& index) +{ + UNSCOPED_INFO("Index " << index.GetVersion()); + return (index.GetVersion() < SQLiteVersion{ 2, 0 }); +} + +bool AreChannelsSupported(const SQLiteIndex& index) +{ + UNSCOPED_INFO("Index " << index.GetVersion()); + return (index.GetVersion() < SQLiteVersion{ 2, 0 }); +} + +bool AreManifestPathsSupported(const SQLiteIndex& index) +{ + UNSCOPED_INFO("Index " << index.GetVersion()); + return (index.GetVersion() < SQLiteVersion{ 2, 0 }); +} + +std::string GetPropertyStringByKey(const SQLiteIndex& index, rowid_t primaryId, PackageVersionProperty property) +{ + auto result = index.GetPropertyByPrimaryId(primaryId, property); + REQUIRE(result); + return result.value(); +} + +std::string GetPropertyStringByKey(const SQLiteIndex& index, rowid_t id, PackageVersionProperty property, std::string_view version, std::string_view channel) +{ + if (AreVersionKeysSupported(index)) + { + auto manifestId = index.GetManifestIdByKey(id, version, channel); + REQUIRE(manifestId); + auto result = index.GetPropertyByPrimaryId(manifestId.value(), property); + REQUIRE(result); + return result.value(); + } + else + { + return GetPropertyStringByKey(index, id, property); + } +} + +std::string GetPropertyStringById(const SQLiteIndex& index, rowid_t id, PackageVersionProperty property) +{ + if (AreVersionKeysSupported(index)) + { + auto versions = index.GetVersionKeysById(id); + REQUIRE(!versions.empty()); + return GetPropertyStringByKey(index, versions[0].ManifestId, property); + } + else + { + return GetPropertyStringByKey(index, id, property); + } +} + +std::string GetIdStringById(const SQLiteIndex& index, rowid_t id) +{ + return GetPropertyStringById(index, id, PackageVersionProperty::Id); +} + +std::string GetNameStringById(const SQLiteIndex& index, rowid_t id) +{ + return GetPropertyStringById(index, id, PackageVersionProperty::Name); +} + +std::string GetPathStringByKey(const SQLiteIndex& index, rowid_t id, std::string_view version, std::string_view channel) +{ + return GetPropertyStringByKey(index, id, PackageVersionProperty::RelativePath, version, channel); +} + +TEST_CASE("SQLiteIndexCreateLatestAndReopen", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteVersion versionCreated; + + // Create the index + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, SQLiteVersion::Latest()); + versionCreated = index.GetVersion(); + } + + // Reopen the index for read only + { + INFO("Trying with Read"); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + SQLiteVersion versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for read/write + { + INFO("Trying with ReadWrite"); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + SQLiteVersion versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } + + // Reopen the index for immutable read + { + INFO("Trying with Immutable"); + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Immutable); + SQLiteVersion versionRead = index.GetVersion(); + REQUIRE(versionRead == versionCreated); + } +} + +TEST_CASE("SQLiteIndexCreateAndAddManifest", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + std::string relativePath = "test/id/1.0.0.yaml"; + + SQLiteIndex index = SimpleTestSetup(tempFile, manifest, SQLiteVersion::Latest()); +} + +TEST_CASE("SQLiteIndexCreateAndAddManifestFile", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile); + + TestDataFile manifestFile{ "Manifest-Good.yaml" }; + std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; + + index.AddManifest(manifestFile, manifestPath); +} + +TEST_CASE("SQLiteIndexCreateAndAddManifestDuplicate", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + auto relativePath = GetPathFromManifest(manifest); + + // Attempting to add the same manifest at a different path should fail. + REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); + + // Attempting to add the same manifest with a differently cased Id at a different path should fail. + manifest.Id = ToLower(manifest.Id); + REQUIRE_THROWS_HR(index.AddManifest(manifest, "differentpath.yaml"), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); + + // Attempting to add a different manifest at the same path should fail. + manifest.Id += "-new"; + REQUIRE_THROWS_HR(index.AddManifest(manifest, relativePath), HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS)); +} + +TEST_CASE("SQLiteIndex_VersionReferencedByDependenciesClearsUnusedVersionAndKeepUsedVersion", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest, isolatedManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + std::string dependencyOnlyVersion = "0.0.5"; + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, dependencyOnlyVersion)); + + index.AddManifest(manifest, GetPathFromManifest(manifest)); + + // Create a new manifest that depends on v0.0.5 + auto& publisher4 = "Test4"; + CreateFakeManifest(isolatedManifest, publisher4); + isolatedManifest.Version = dependencyOnlyVersion; + index.AddManifest(isolatedManifest); + + index.RemoveManifest(isolatedManifest); + // After deletion that the version(v0.0.5) must be present because it still referenced via dependencies table. + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, dependencyOnlyVersion).has_value()); + } + + index.RemoveManifest(manifest); + // Now, that we've deleted the manifest depending on version(v0.0.5), it should be absent. + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + REQUIRE(!Schema::V1_0::VersionTable::SelectIdByValue(connection, dependencyOnlyVersion).has_value()); + } + index.RemoveManifest(dependencyManifest1); + index.RemoveManifest(dependencyManifest2); + + // Final sanity check, nothing should be in the version table. + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + } +} + +TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); + + index.AddManifest(manifest, GetPathFromManifest(manifest)); + index.UpdateManifest(manifest, GetPathFromManifest(manifest)); + index.RemoveManifest(manifest); +} + +TEST_CASE("SQLiteIndex_AddManifestWithDependencies_MissingPackage", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + // Publisher2 is not present + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); + + REQUIRE_THROWS_HR(index.AddManifest(manifest, GetPathFromManifest(manifest)), APPINSTALLER_CLI_ERROR_MISSING_PACKAGE); +} + +TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies_MissingVersion", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "0.0.1")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "0.0.2")); + + index.AddManifest(manifest, GetPathFromManifest(manifest)); + index.UpdateManifest(manifest, GetPathFromManifest(manifest)); + index.RemoveManifest(manifest); +} + +TEST_CASE("SQLiteIndex_AddUpdateRemoveManifestWithDependencies_EmptyManifestVersion", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id)); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id)); + + index.AddManifest(manifest, GetPathFromManifest(manifest)); + index.UpdateManifest(manifest, GetPathFromManifest(manifest)); + index.RemoveManifest(manifest); +} + +TEST_CASE("SQLiteIndex_DependenciesTable_CheckConsistency", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + { + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); + } + + { + // Open it directly to modify the table + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + rowid_t nonExistentRowId = 40; + rowid_t nonExistentManifest = 41; + rowid_t nonExistentVersion = 42; + rowid_t nonExistentPackageId = 43; + + Builder::StatementBuilder builder; + builder.InsertInto(Schema::V1_4::DependenciesTable::TableName()) + .Values(nonExistentRowId, nonExistentManifest, nonExistentVersion, nonExistentPackageId); + builder.Execute(connection); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + REQUIRE(!index.CheckConsistency(true)); + } + + TempFile tempFile2{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile2.GetPath()); + + { + SQLiteIndex index = CreateTestIndex(tempFile2, SQLiteVersion::Latest()); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "10.0"; + + index.AddManifest(manifest, "path"); + + REQUIRE(index.CheckConsistency(true)); + + // Add dependency that does not require min version + Manifest manifestWithDependency1; + manifestWithDependency1.Id = "Bar1"; + manifestWithDependency1.Version = "10.0"; + manifestWithDependency1.Installers.push_back({}); + manifestWithDependency1.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id)); + + index.AddManifest(manifestWithDependency1, "path1"); + + REQUIRE(index.CheckConsistency(true)); + + // Add dependency with min version satisfied + Manifest manifestWithDependency2; + manifestWithDependency2.Id = "Bar2"; + manifestWithDependency2.Version = "10.0"; + manifestWithDependency2.Installers.push_back({}); + manifestWithDependency2.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id, "1.0")); + + index.AddManifest(manifestWithDependency2, "path2"); + + REQUIRE(index.CheckConsistency(true)); + + // Add dependency with min version not satisfied + Manifest manifestWithDependency3; + manifestWithDependency3.Id = "Bar3"; + manifestWithDependency3.Version = "10.0"; + manifestWithDependency3.Installers.push_back({}); + manifestWithDependency3.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, manifest.Id, "11.0")); + + index.AddManifest(manifestWithDependency3, "path3"); + + REQUIRE_FALSE(index.CheckConsistency(true)); + } +} + +TEST_CASE("SQLiteIndex_RemoveManifestFile_NotPresent", "[sqliteindex]") +{ + SQLiteIndex index = CreateTestIndex(SQLITE_MEMORY_DB_CONNECTION_TARGET); + + TestDataFile manifestFile{ "Manifest-Good.yaml" }; + std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; + + REQUIRE_THROWS_HR(index.RemoveManifest(manifestFile, manifestPath), E_NOT_SET); +} + +TEST_CASE("SQLiteIndex_RemoveManifest", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; + Manifest manifest1; + manifest1.Installers.push_back({}); + manifest1.Id = "test.id"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; + manifest1.Version = "1.0.0"; + manifest1.Channel = "test"; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; + + std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; + Manifest manifest2; + manifest2.Installers.push_back({}); + manifest2.Id = "test.woah"; + manifest2.DefaultLocalization.Add("Test Name WOAH"); + manifest2.Moniker = "testmoniker"; + manifest2.Version = "1.0.0"; + manifest2.Channel = "test"; + manifest2.DefaultLocalization.Add({}); + manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + + index.AddManifest(manifest1, manifest1Path); + index.AddManifest(manifest2, manifest2Path); + + // Now remove manifest1 + index.RemoveManifest(manifest1, manifest1Path); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); + // Because manifest2 had no tags + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Now remove manifest2 + index.RemoveManifest(manifest2, manifest2Path); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + +TEST_CASE("SQLiteIndex_RemoveManifestWithDependencies", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); + + index.AddManifest(manifest, GetPathFromManifest(manifest)); + + index.RemoveManifest(manifest, GetPathFromManifest(manifest)); +} + +TEST_CASE("SQLiteIndex_ValidateManifestWithDependencies", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); + REQUIRE(PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest)); +} + +TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesHasLoops", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); + index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); + + levelThreeManifest.Installers.push_back(ManifestInstaller{}); + levelThreeManifest.Installers[1].Dependencies.Add(Dependency(DependencyType::Package, topLevelManifest.Id, "1.0.0")); + REQUIRE_THROWS_HR( + PackageDependenciesValidation::ValidateManifestDependencies(&index, levelThreeManifest), + APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); +} + +TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesMissingNode", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + // This node is missing, because it's not in the index. + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); + REQUIRE_THROWS_HR( + PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest), + APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); +} + +TEST_CASE("SQLiteIndex_ValidateManifestWithDependenciesNoSuitableMinVersion", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "2.0.0")); + + REQUIRE_THROWS_HR( + PackageDependenciesValidation::ValidateManifestDependencies(&index, topLevelManifest), + APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); +} + +TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureBroken", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); + index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); + + REQUIRE_THROWS_HR( + PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifest), + APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); +} + +TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureNotBroken", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest, levelThreeManifestV2; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "1.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); + index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); + + constexpr std::string_view levelThreeManifestV2Publisher = "Test"; + CreateFakeManifest(levelThreeManifestV2, levelThreeManifestV2Publisher, "2.0.0"); + index.AddManifest(levelThreeManifestV2, GetPathFromManifest(levelThreeManifestV2)); + + REQUIRE(PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifest)); +} + +TEST_CASE("SQLiteIndex_ValidateManifestWhenManifestIsDependency_StructureBroken_NoSuitableOldManifest", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest levelOneManifest, levelTwoManifest, levelThreeManifest, topLevelManifest, levelThreeManifestV2; + SQLiteIndex index = SimpleTestSetup(tempFile, levelThreeManifest, SQLiteVersion::Latest()); + + constexpr std::string_view levelThreeManifestV2Publisher = "Test"; + CreateFakeManifest(levelThreeManifestV2, levelThreeManifestV2Publisher, "2.0.0"); + index.AddManifest(levelThreeManifestV2, GetPathFromManifest(levelThreeManifestV2)); + + constexpr std::string_view levelTwoManifestPublisher = "LevelTwoManifest"; + CreateFakeManifest(levelTwoManifest, levelTwoManifestPublisher); + + levelTwoManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelThreeManifest.Id, "2.0.0")); + index.AddManifest(levelTwoManifest, GetPathFromManifest(levelTwoManifest)); + + constexpr std::string_view levelOneManifestPublisher = "LevelOneManifest"; + CreateFakeManifest(levelOneManifest, levelOneManifestPublisher); + levelOneManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelTwoManifest.Id, "1.0.0")); + index.AddManifest(levelOneManifest, GetPathFromManifest(levelOneManifest)); + + constexpr std::string_view topLevelManifestPublisher = "TopLevelManifest"; + CreateFakeManifest(topLevelManifest, topLevelManifestPublisher); + topLevelManifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, levelOneManifest.Id, "1.0.0")); + index.AddManifest(topLevelManifest, GetPathFromManifest(topLevelManifest)); + + REQUIRE_THROWS( + PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(&index, levelThreeManifestV2), + APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED); +} + +TEST_CASE("SQLiteIndex_RemoveManifest_EnsureConsistentRowId", "[sqliteindex][V1_7]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; + Manifest manifest1; + manifest1.Installers.push_back({}); + manifest1.Id = "test.id"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; + manifest1.Version = "1.0.0"; + manifest1.Channel = "test"; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; + + std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; + Manifest manifest2; + manifest2.Installers.push_back({}); + manifest2.Id = "test.woah"; + manifest2.DefaultLocalization.Add("Test Name WOAH"); + manifest2.Moniker = "testmoniker"; + manifest2.Version = "1.0.0"; + manifest2.Channel = "test"; + manifest2.DefaultLocalization.Add({}); + manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; + + SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion{ 1, 7 }); + + index.AddManifest(manifest1, manifest1Path); + index.AddManifest(manifest2, manifest2Path); + + // Get the second manifest's id for validating consistency + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, manifest2.Id)); + auto result = index.Search(request); + + REQUIRE(result.Matches.size() == 1); + auto manifest2IdRowId = result.Matches[0].first; + + auto rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {}); + REQUIRE(rowId); + auto manifest2RowId = rowId.value(); + + // Now remove manifest1 and prepare + index.RemoveManifest(manifest1, manifest1Path); + index.PrepareForPackaging(); + + // Checking consistency will also uncover issues, but not potentially the same ones as below. + REQUIRE(index.CheckConsistency(true)); + + // Repeat search to ensure consistent ids + result = index.Search(request); + REQUIRE(result.Matches.size() == 1); + REQUIRE(result.Matches[0].first == manifest2IdRowId); + + rowId = index.GetManifestIdByKey(manifest2IdRowId, {}, {}); + REQUIRE(rowId); + REQUIRE(rowId.value() == manifest2RowId); + + REQUIRE(manifest2.Id == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Id)); + REQUIRE(manifest2.DefaultLocalization.Get() == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Name)); + REQUIRE(manifest2.Moniker == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Moniker)); + REQUIRE(manifest2.Version == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Version)); + REQUIRE(manifest2.Channel == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::Channel)); + REQUIRE(manifest2Path == index.GetPropertyByPrimaryId(manifest2RowId, PackageVersionProperty::RelativePath)); +} + +TEST_CASE("SQLiteIndex_RemoveManifestFile", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + + TestDataFile manifestFile{ "Manifest-Good.yaml" }; + std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; + + index.AddManifest(manifestFile, manifestPath); + + // Now remove that manifest + index.RemoveManifest(manifestFile, manifestPath); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + +TEST_CASE("SQLiteIndex_UpdateManifest", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifestPath = "test/id/test.id-1.0.0.yaml"; + Manifest manifest; + manifest.Installers.push_back({}); + manifest.Id = "test.id"; + manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); + manifest.Moniker = "testmoniker"; + manifest.Version = "1.0.0"; + manifest.Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; + + + { + auto version = GENERATE(SQLiteVersion{ 1, 0 }, SQLiteVersion::Latest()); + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, version); + + index.AddManifest(manifest, manifestPath); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Update with no updates should return false + REQUIRE(!index.UpdateManifest(manifest, manifestPath)); + + manifest.DefaultLocalization.Add("description2"); + + // Update with no indexed updates should return false + REQUIRE(!index.UpdateManifest(manifest, manifestPath)); + + // Update with indexed changes + manifest.DefaultLocalization.Add("Test Name2"); + manifest.Moniker = "testmoniker2"; + manifest.DefaultLocalization.Add({ "t1", "t2", "t3" }); + manifest.Installers[0].Commands = {}; + + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); + // The update removed all commands + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Now remove manifest2 + index.RemoveManifest(manifest, manifestPath); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + +TEST_CASE("SQLiteIndex_UpdateManifestWithDependencies", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest, updateManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + const std::string dependencyPath3 = GetPathFromManifest(manifest); + + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); + + index.AddManifest(manifest, dependencyPath3); + + auto& publisher4 = "Test4"; + CreateFakeManifest(updateManifest, publisher4); + index.AddManifest(updateManifest, GetPathFromManifest(updateManifest)); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, updateManifest.Id, "1.0.0")); + + REQUIRE(index.UpdateManifest(manifest, dependencyPath3)); +} + +TEST_CASE("SQLiteIndex_UpdateManifestWithDependenciesDeleteAndAdd", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest, updateManifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + const std::string dependencyPath3 = GetPathFromManifest(manifest); + + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest2.Id, "1.0.0")); + + index.AddManifest(manifest, dependencyPath3); + + manifest.Installers[0].Dependencies.Clear(); + + auto& publisher4 = "Test4"; + CreateFakeManifest(updateManifest, publisher4); + index.AddManifest(updateManifest, GetPathFromManifest(updateManifest)); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, updateManifest.Id, "1.0.0")); + + REQUIRE(index.UpdateManifest(manifest, dependencyPath3)); +} + +TEST_CASE("SQLiteIndex_UpdateManifestChangePath", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifestPath = "test/id/test.id-1.0.0.yaml"; + Manifest manifest; + manifest.Installers.push_back({}); + manifest.Id = "test.id"; + manifest.DefaultLocalization.Add("Test Name"); + manifest.Moniker = "testmoniker"; + manifest.Version = "1.0.0"; + manifest.Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + + index.AddManifest(manifest, manifestPath); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + manifestPath = "test/newid/test.newid-1.0.0.yaml"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Now remove manifest, with unknown path + index.RemoveManifest(manifest, ""); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + +TEST_CASE("SQLiteIndex_UpdateManifest_Pathless", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + manifest.Installers.push_back({}); + manifest.Id = "test.id"; + manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); + manifest.Moniker = "testmoniker"; + manifest.Version = "1.0.0"; + manifest.Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + + index.AddManifest(manifest); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::CommandsTable::IsEmpty(connection)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Update with no updates should return false + REQUIRE(!index.UpdateManifest(manifest)); + + manifest.DefaultLocalization.Add("description2"); + + // Update with no indexed updates should return false + REQUIRE(!index.UpdateManifest(manifest)); + + // Update with indexed changes + manifest.DefaultLocalization.Add("Test Name2"); + manifest.Moniker = "testmoniker2"; + manifest.DefaultLocalization.Add({ "t1", "t2", "t3" }); + manifest.Installers[0].Commands = {}; + + REQUIRE(index.UpdateManifest(manifest)); + } + + { + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(!Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(!Schema::V1_0::TagsTable::IsEmpty(connection)); + // The update removed all commands + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Now remove manifest2 + index.RemoveManifest(manifest); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + +TEST_CASE("SQLiteIndex_UpdateManifestChangeCase", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifestPath = "test/id/test.id-1.0.0.yaml"; + Manifest manifest; + manifest.Installers.push_back({}); + manifest.Id = "test.id"; + manifest.DefaultLocalization.Add("Test Name"); + manifest.Moniker = "testmoniker"; + manifest.Version = "1.0.0-test"; + manifest.Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + + index.AddManifest(manifest, manifestPath); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + manifest.Id = "Test.Id"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + manifest.Version = "1.0.0-Test"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + manifest.Channel = "Test"; + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + manifest.DefaultLocalization.Add("test name"); + + // Update with path update should indicate change + REQUIRE(index.UpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Now remove manifest, with unknown path + index.RemoveManifest(manifest, ""); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + +TEST_CASE("SQLiteIndex_IdCaseInsensitivity", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; + Manifest manifest1; + manifest1.Installers.push_back({}); + manifest1.Id = "test.id"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; + manifest1.Version = "1.0.0"; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; + + std::string manifest2Path = "test/id/test.id-2.0.0.yaml"; + Manifest manifest2 = manifest1; + manifest2.Id = "Test.Id"; + manifest1.Version = "2.0.0"; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + + index.AddManifest(manifest1, manifest1Path); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + index.AddManifest(manifest2, manifest2Path); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + REQUIRE(manifest2.Id == GetIdStringById(index, results.Matches[0].first)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + manifest1.Id = "TEST.ID"; + + REQUIRE(index.UpdateManifest(manifest1, manifest1Path)); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + index.RemoveManifest(manifest1, manifest1Path); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + REQUIRE(manifest1.Id == GetIdStringById(index, results.Matches[0].first)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + index.RemoveManifest(manifest2, manifest2Path); + + auto results = index.Search({}); + REQUIRE(results.Matches.empty()); + } + + // Open it directly to directly test table state + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + REQUIRE(Schema::V1_0::ManifestTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::IdTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::NameTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::MonikerTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::VersionTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::ChannelTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::PathPartTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::TagsTable::IsEmpty(connection)); + REQUIRE(Schema::V1_0::CommandsTable::IsEmpty(connection)); +} + +TEST_CASE("PathPartTable_EnsurePathExists_Negative_Paths", "[sqliteindex][V1_0]") +{ + // Open it directly to directly test pathpart table + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"()", false), E_INVALIDARG); + REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(\)", false), E_INVALIDARG); + REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(/)", false), E_INVALIDARG); + REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:)", false), E_INVALIDARG); + REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:\\)", false), E_INVALIDARG); + REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(C:\temp\path\file.txt)", false), E_INVALIDARG); + REQUIRE_THROWS_HR(Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(\temp\path\file.txt)", false), E_INVALIDARG); +} + +TEST_CASE("PathPartTable_EnsurePathExists", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + // Create the index + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 0 }); + SQLiteVersion versionCreated = index.GetVersion(); + REQUIRE(versionCreated == SQLiteVersion{ 1, 0 }); + } + + // Open it directly to directly test pathpart table + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + // attempt to find path that doesn't exist + auto result0 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", false); + REQUIRE(!std::get<0>(result0)); + + // add path + auto result1 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", true); + REQUIRE(std::get<0>(result1)); + + // Second time trying to create should return false and same id + auto result2 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", true); + REQUIRE(!std::get<0>(result2)); + REQUIRE(std::get<1>(result1) == std::get<1>(result2)); + + // Trying to find but not create should return true because it exists + auto result3 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\c.txt)", false); + REQUIRE(std::get<0>(result3)); + REQUIRE(std::get<1>(result1) == std::get<1>(result3)); + + // attempt to find a different file + auto result4 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d.txt)", false); + REQUIRE(!std::get<0>(result4)); + + // add a different file + auto result5 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d.txt)", true); + REQUIRE(std::get<0>(result5)); + REQUIRE(std::get<1>(result1) != std::get<1>(result5)); + + // add the same file but deeper + auto result6 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\b\d\c.txt)", true); + REQUIRE(std::get<0>(result6)); + REQUIRE(std::get<1>(result1) != std::get<1>(result6)); + + // get the deeper file with extra separators + auto result7 = Schema::V1_0::PathPartTable::EnsurePathExists(connection, R"(a\\b\d\\c.txt)", true); + REQUIRE(!std::get<0>(result7)); + REQUIRE(std::get<1>(result6) == std::get<1>(result7)); +} + +TEST_CASE("SQLiteIndex_PrepareForPackaging", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile); + + TestDataFile manifestFile{ "Manifest-Good.yaml" }; + std::filesystem::path manifestPath{ "microsoft/msixsdk/microsoft.msixsdk-1.7.32.yaml" }; + + index.AddManifest(manifestFile, manifestPath); + + index.PrepareForPackaging(); +} + +TEST_CASE("SQLiteIndex_Search_IdExactMatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + std::string relativePath = "test/id/1.0.0.yaml"; + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].second.Field == PackageMatchField::Id); + REQUIRE(results.Matches[0].second.Type == MatchType::Exact); + REQUIRE(results.Matches[0].second.Value == manifest.Id); +} + +TEST_CASE("SQLiteIndex_Search_MultipleMatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + std::string relativePath = "test/id/1.0.0.yaml"; + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + + manifest.Version = "2.0.0"; + index.AddManifest(manifest, relativePath + "2"); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + if (AreVersionKeysSupported(index)) + { + auto result = index.GetVersionKeysById(results.Matches[0].first); + REQUIRE(result.size() == 2); + } + else + { + REQUIRE_THROWS_HR(index.GetVersionKeysById(results.Matches[0].first), E_NOT_VALID_STATE); + } +} + +TEST_CASE("SQLiteIndex_Search_NoMatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, "THIS DOES NOT MATCH ANYTHING!"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 0); +} + +TEST_CASE("SQLiteIndex_IdString", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = GetIdStringById(index, results.Matches[0].first); + REQUIRE(result == manifest.Id); +} + +TEST_CASE("SQLiteIndex_NameString", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = GetNameStringById(index, results.Matches[0].first); + REQUIRE(result == manifest.DefaultLocalization.Get()); +} + +TEST_CASE("SQLiteIndex_PathString", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + auto relativePath = GetPathFromManifest(manifest); + + TestPrepareForRead(index); + + if (!AreManifestPathsSupported(index)) + { + return; + } + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel); + REQUIRE(specificResult == relativePath); + + auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel); + REQUIRE(latestResult == relativePath); +} + +TEST_CASE("SQLiteIndex_PathlessString", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + std::string relativePath; + + SQLiteIndex index = CreateTestIndex(tempFile); + CreateFakeManifest(manifest, "Test"); + index.AddManifest(manifest); + + TestPrepareForRead(index); + + if (!AreManifestPathsSupported(index)) + { + return; + } + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto specificResult = GetPathStringByKey(index, results.Matches[0].first, manifest.Version, manifest.Channel); + REQUIRE(specificResult == relativePath); + + auto latestResult = GetPathStringByKey(index, results.Matches[0].first, "", manifest.Channel); + REQUIRE(latestResult == relativePath); +} + +TEST_CASE("SQLiteIndex_Versions", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + std::string relativePath = "test/id/1.0.0.yaml"; + SQLiteIndex index = SimpleTestSetup(tempFile, manifest); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + if (AreVersionKeysSupported(index)) + { + auto result = index.GetVersionKeysById(results.Matches[0].first); + REQUIRE(result.size() == 1); + REQUIRE(result[0].VersionAndChannel.GetVersion().ToString() == manifest.Version); + REQUIRE(result[0].VersionAndChannel.GetChannel().ToString() == manifest.Channel); + } + else + { + REQUIRE_THROWS_HR(index.GetVersionKeysById(results.Matches[0].first), E_NOT_VALID_STATE); + } +} + +TEST_CASE("SQLiteIndex_Search_VersionSorting", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::vector sortedList = + { + { UtilityVersion("15.0.0"), Channel("") }, + { UtilityVersion("14.0.0"), Channel("") }, + { UtilityVersion("13.2.0"), Channel("") }, + { UtilityVersion("13.2.0-bugfix"), Channel("") }, + { UtilityVersion("13.0.0"), Channel("") }, + { UtilityVersion("16.0.0"), Channel("alpha") }, + { UtilityVersion("15.8.0"), Channel("alpha") }, + { UtilityVersion("15.1.0"), Channel("beta") }, + }; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" }, + { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" }, + { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" }, + { "Id", "Name", "Moniker", "13.2.0", "", {}, { "Command" }, "Path4" }, + { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" }, + { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" }, + { "Id", "Name", "Moniker", "13.2.0-bugfix", "", { "foo" }, { "com3" }, "Path7" }, + { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" }, + }); + + TestPrepareForRead(index); + + if (!AreChannelsSupported(index)) + { + return; + } + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = index.GetVersionKeysById(results.Matches[0].first); + REQUIRE(result.size() == sortedList.size()); + + for (size_t i = 0; i < result.size(); ++i) + { + const VersionAndChannel& sortedVAC = sortedList[i]; + const VersionAndChannel& resultVAC = result[i].VersionAndChannel; + + INFO(i); + REQUIRE(sortedVAC.GetVersion().ToString() == resultVAC.GetVersion().ToString()); + REQUIRE(sortedVAC.GetChannel().ToString() == resultVAC.GetChannel().ToString()); + } +} + +TEST_CASE("SQLiteIndex_PathString_VersionSorting", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::vector sortedList = + { + { UtilityVersion("15.0.0"), Channel("") }, + { UtilityVersion("14.0.0"), Channel("") }, + { UtilityVersion("13.2.0"), Channel("") }, + { UtilityVersion("13.2.0-bugfix"), Channel("") }, + { UtilityVersion("13.0.0"), Channel("") }, + { UtilityVersion("16.0.0"), Channel("alpha") }, + { UtilityVersion("15.8.0"), Channel("alpha") }, + { UtilityVersion("15.1.0"), Channel("beta") }, + }; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" }, + { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" }, + { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" }, + { "Id", "Name", "Moniker", "13.2.0", "", {}, { "Command" }, "Path4" }, + { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" }, + { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" }, + { "Id", "Name", "Moniker", "13.2.0-bugfix", "", { "foo" }, { "com3" }, "Path7" }, + { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" }, + }); + + TestPrepareForRead(index); + + if (!AreChannelsSupported(index)) + { + return; + } + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = GetPathStringByKey(index, results.Matches[0].first, "", ""); + REQUIRE(result == "Path3"); + + result = GetPathStringByKey(index, results.Matches[0].first, "", "alpha"); + REQUIRE(result == "Path2"); + + result = GetPathStringByKey(index, results.Matches[0].first, "", "beta"); + REQUIRE(result == "Path5"); + + auto nonResult = index.GetManifestIdByKey(results.Matches[0].first, "", "gamma"); + REQUIRE(!nonResult.has_value()); +} + +TEST_CASE("SQLiteIndex_PathString_CaseInsensitive", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Moniker", "14.0.0", "", { "foot" }, { "com34" }, "Path1" }, + { "Id", "Name", "Moniker", "16.0.0", "alpha", { "floor" }, { "com3" }, "Path2" }, + { "Id", "Name", "Moniker", "15.0.0", "", {}, { "Command" }, "Path3" }, + { "Id", "Name", "Moniker", "13.2.0-BUGFIX", "", {}, { "Command" }, "Path4" }, + { "Id", "Name", "Moniker", "15.1.0", "beta", { "foo" }, { "com3" }, "Path5" }, + { "Id", "Name", "Moniker", "15.8.0", "alpha", { "foo" }, { "com3" }, "Path6" }, + { "Id", "Name", "Moniker", "13.2.0-bugfix", "beta", { "foo" }, { "com3" }, "Path7" }, + { "Id", "Name", "Moniker", "13.0.0", "", { "foo" }, { "com3" }, "Path8" }, + }); + + TestPrepareForRead(index); + + if (!AreChannelsSupported(index)) + { + return; + } + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Exact, "Id"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = index.GetManifestIdByKey(results.Matches[0].first, "", "Alpha"); + REQUIRE(result.has_value()); + + result = index.GetManifestIdByKey(results.Matches[0].first, "13.2.0-BugFix", ""); + REQUIRE(result.has_value()); + + result = index.GetManifestIdByKey(results.Matches[0].first, "13.2.0-BugFix", "BETA"); + REQUIRE(result.has_value()); +} + +TEST_CASE("SQLiteIndex_SearchResultsTableSearches", "[sqliteindex][V1_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + { + (void)SimpleTestSetup(tempFile, manifest, SQLiteVersion{ 1, 0 }); + } + + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + Schema::V1_0::SearchResultsTable search(connection); + + std::string value = "test"; + + // Perform every type of field and match search + PackageMatchFilter filter(PackageMatchField::Id, MatchType::Exact, value); + + for (auto field : { PackageMatchField::Id, PackageMatchField::Name, PackageMatchField::Moniker, PackageMatchField::Tag, PackageMatchField::Command }) + { + filter.Field = field; + + for (auto match : { MatchType::Exact, MatchType::Fuzzy, MatchType::FuzzySubstring, MatchType::Substring, MatchType::Wildcard }) + { + filter.Type = match; + search.SearchOnField(filter); + } + } +} + +TEST_CASE("SQLiteIndex_Search_EmptySearch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile,{ + { "Id1", "Name", "Moniker", "Version1", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id1", "Name", "Moniker", "Version2", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + { "Id3", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path4" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 3); +} + +TEST_CASE("SQLiteIndex_Search_Exact", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, "Id"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); +} + +TEST_CASE("SQLiteIndex_Search_Substring", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "Id"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); +} + +TEST_CASE("SQLiteIndex_Search_ExactBeforeSubstring", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "Id"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); + + REQUIRE(GetIdStringById(index, results.Matches[0].first) == "Id"); + REQUIRE(GetIdStringById(index, results.Matches[1].first) == "Id2"); +} + +TEST_CASE("SQLiteIndex_Search_SingleFilter", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Name, MatchType::Substring, "a"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); + + request.Filters[0].Value = "e"; + + results = index.Search(request); + REQUIRE(results.Matches.size() == 1); +} + +TEST_CASE("SQLiteIndex_Search_Multimatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id1", "Name1", "Moniker", "Version1", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id2", "Name", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path3" }, + { "Id2", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path4" }, + { "Id3", "Name", "Moniker", "Version1", "", { "Tag" }, { "Command" }, "Path5" }, + { "Id3", "Name", "Moniker", "Version2", "", { "Tag" }, { "Command" }, "Path6" }, + { "Id3", "Name", "Moniker", "Version3", "", { "Tag" }, { "Command" }, "Path7" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + // An empty string should match all substrings + request.Query = RequestMatch(MatchType::Substring, ""); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 3); +} + +TEST_CASE("SQLiteIndex_Search_QueryAndFilter", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "Id"); + request.Filters.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = GetIdStringById(index, results.Matches[0].first); + REQUIRE(result == "Id2"); +} + +TEST_CASE("SQLiteIndex_Search_QueryAndMultipleFilters", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path1" }, + { "Id1", "Name1", "Moniker", "Version1", "Channel", { "floor" }, { "com3" }, "Path2" }, + { "Id2", "Name", "Moniker", "Version", "", {}, { "Command" }, "Path3" }, + { "Id2", "Name", "Moniker", "Version", "Channel", {}, { "Command" }, "Path4" }, + { "Id3", "Tagit", "Moniker", "Version1", "", { "foo" }, { "com3" }, "Path5" }, + { "Id3", "Tagit", "Moniker", "Version2", "", { "foo" }, { "com3" }, "Path6" }, + { "Id3", "Tagit", "new", "Version3", "", { "foo" }, { "com3" }, "Path7" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "tag"); + request.Filters.emplace_back(PackageMatchField::Command, MatchType::Exact, "com3"); + request.Filters.emplace_back(PackageMatchField::Tag, MatchType::Substring, "foo"); + request.Filters.emplace_back(PackageMatchField::Moniker, MatchType::Substring, "new"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = GetIdStringById(index, results.Matches[0].first); + REQUIRE(result == "Id3"); +} + +TEST_CASE("SQLiteIndex_Search_SimpleICULike", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + // Insert decomposed character: [upper] A + umlaut + SQLiteIndex index = SearchTestSetup(tempFile, { + { u8"\x41\x308wesomeApp", "HasUmlaut", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path1" }, + { u8"AwesomeApp", "Nope", "Moniker", "Version", "Channel", { "foot" }, { "com34" }, "Path2" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + // Search for anything containing: [lower] a + umlaut + request.Filters.emplace_back(PackageMatchField::Id, MatchType::Substring, u8"\xE4"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = GetNameStringById(index, results.Matches[0].first); + REQUIRE(result == "HasUmlaut"); +} + +TEST_CASE("SQLiteIndex_Search_MaximumResults_Equal", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.MaximumResults = 3; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 3); + REQUIRE(!results.Truncated); +} + +TEST_CASE("SQLiteIndex_Search_MaximumResults_Less", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.MaximumResults = 2; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); + REQUIRE(results.Truncated); +} + +TEST_CASE("SQLiteIndex_Search_MaximumResults_Greater", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.MaximumResults = 4; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 3); + REQUIRE(!results.Truncated); +} + +TEST_CASE("SQLiteIndex_Search_QueryAndInclusion", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::CaseInsensitive, "id3"); + request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 3); +} + +TEST_CASE("SQLiteIndex_Search_InclusionOnly", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); +} + +TEST_CASE("SQLiteIndex_Search_InclusionAndFilter", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "Na"); + request.Filters.emplace_back(PackageMatchField::Name, MatchType::CaseInsensitive, "name"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto result = GetIdStringById(index, results.Matches[0].first); + REQUIRE(result == "Nope"); +} + +TEST_CASE("SQLiteIndex_Search_QueryInclusionAndFilter", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "Name", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "monicka", "Version", "Channel", { "Tag" }, { "Command" }, "Path2" }, + { "Id3", "No", "moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "id3"); + request.Inclusions.emplace_back(PackageMatchField::Name, MatchType::Substring, "na"); + request.Filters.emplace_back(PackageMatchField::Moniker, MatchType::CaseInsensitive, "MONIKER"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); +} + +TEST_CASE("SQLiteIndex_Search_CaseInsensitive", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Nope", "id3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::CaseInsensitive, "id3"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 3); +} + +TEST_CASE("SQLiteIndex_Search_StartsWith", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "NopeId", "id3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1" }, + { "Id2", "Na", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2" }, + { "Id3", "No", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3" }, + }); + + TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.push_back(PackageMatchFilter(PackageMatchField::Id, MatchType::StartsWith, "id")); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); +} + +TEST_CASE("SQLiteIndex_Search_Query_PackageFamilyNameSubstring", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "PFN"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 0); +} + +TEST_CASE("SQLiteIndex_Search_Query_ProductCodeSubstring", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "PC"); + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 0); +} + +TEST_CASE("SQLiteIndex_Search_Query_PackageFamilyNameMatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "pfn1"); + + auto results = index.Search(request); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.size() == 0); + } +} + +TEST_CASE("SQLiteIndex_Search_Query_ProductCodeMatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Substring, "pc2"); + + auto results = index.Search(request); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.size() == 0); + } +} + +TEST_CASE("SQLiteIndex_Search_PackageFamilyNameSubstring", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::PackageFamilyName, MatchType::Substring, "PFN"); + + auto results = index.Search(request); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 3); + } + else + { + REQUIRE(results.Matches.size() == 0); + } +} + +TEST_CASE("SQLiteIndex_Search_ProductCodeSubstring", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::ProductCode, MatchType::Substring, "PC"); + + auto results = index.Search(request); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 3); + } + else + { + REQUIRE(results.Matches.size() == 0); + } +} + +TEST_CASE("SQLiteIndex_Search_PackageFamilyNameMatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::PackageFamilyName, MatchType::Exact, "pfn1"); + + auto results = index.Search(request); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.size() == 0); + } +} + +TEST_CASE("SQLiteIndex_Search_ProductCodeMatch", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { "Id3", "Name3", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::ProductCode, MatchType::Exact, "pc2"); + + auto results = index.Search(request); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.size() == 0); + } +} + +TEST_CASE("SQLiteIndex_CheckConsistency_Failure", "[sqliteindex][V1_1]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifest1Path = "test/id/test.id-1.0.0.yaml"; + Manifest manifest1; + manifest1.Installers.push_back({}); + manifest1.Id = "test.id"; + manifest1.DefaultLocalization.Add("Test Name"); + manifest1.Moniker = "testmoniker"; + manifest1.Version = "1.0.0"; + manifest1.Channel = "test"; + manifest1.DefaultLocalization.Add({ "t1", "t2" }); + manifest1.Installers[0].Commands = { "test1", "test2" }; + + std::string manifest2Path = "test/woah/test.id-1.0.0.yaml"; + Manifest manifest2; + manifest2.Installers.push_back({}); + manifest2.Id = "test.woah"; + manifest2.DefaultLocalization.Add("Test Name WOAH"); + manifest2.Moniker = "testmoniker"; + manifest2.Version = "1.0.0"; + manifest2.Channel = "test"; + manifest2.DefaultLocalization.Add({}); + manifest2.Installers[0].Commands = { "test1", "test2", "test3" }; + + rowid_t manifestRowId = 0; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, { 1, 1 }); + + index.AddManifest(manifest1, manifest1Path); + index.AddManifest(manifest2, manifest2Path); + + // Get the first manifest's id for removal + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, manifest1.Id)); + auto result = index.Search(request); + + REQUIRE(result.Matches.size() == 1); + manifestRowId = result.Matches[0].first; + } + + { + // Open it directly to modify the table + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + Builder::StatementBuilder builder; + builder.DeleteFrom(Schema::V1_0::IdTable::TableName()).Where(RowIDName).Equals(manifestRowId); + builder.Execute(connection); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + REQUIRE(!index.CheckConsistency(true)); + } +} + +TEST_CASE("SQLiteIndex_GetMultiProperty_PackageFamilyName", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", { "PFN1", "PFN2" }, {} }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::PackageFamilyName); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(props.size() == 2); + REQUIRE(std::find(props.begin(), props.end(), FoldCase("PFN1"sv)) != props.end()); + REQUIRE(std::find(props.begin(), props.end(), FoldCase("PFN2"sv)) != props.end()); + } + else + { + REQUIRE(props.empty()); + } +} + +TEST_CASE("SQLiteIndex_GetMultiProperty_ProductCode", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::ProductCode); + + if (ArePackageFamilyNameAndProductCodeSupported(index, testVersion)) + { + REQUIRE(props.size() == 2); + REQUIRE(std::find(props.begin(), props.end(), FoldCase("PC1"sv)) != props.end()); + REQUIRE(std::find(props.begin(), props.end(), FoldCase("PC2"sv)) != props.end()); + } + else + { + REQUIRE(props.empty()); + } +} + +TEST_CASE("SQLiteIndex_GetMultiProperty_Tag", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag1", "Tag2" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::Tag); + + REQUIRE(props.size() == 2); + REQUIRE(std::find(props.begin(), props.end(), "Tag1") != props.end()); + REQUIRE(std::find(props.begin(), props.end(), "Tag2") != props.end()); +} + +TEST_CASE("SQLiteIndex_GetMultiProperty_Command", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag1", "Tag2" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 1); + + auto props = index.GetMultiPropertyByPrimaryId(results.Matches[0].first, PackageVersionMultiProperty::Command); + + REQUIRE(props.size() == 1); + REQUIRE(props[0] == "Command"); +} + +TEST_CASE("SQLiteIndex_ManifestMetadata", "[sqliteindex][V1_7]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", "Name1", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + { "Id2", "Name2", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2", { "PFN1", "PFN2" }, {} }, + }, SQLiteVersion{ 1, 7 }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + + auto results = index.Search(request); + REQUIRE(results.Matches.size() == 2); + + for (const auto [id, match] : results.Matches) + { + REQUIRE(index.GetMetadataByManifestId(id).empty()); + } + + auto manifestId1 = results.Matches[0].first; + auto manifestId2 = results.Matches[1].first; + + std::string metadataValue = "data about data"; + + index.SetMetadataByManifestId(manifestId1, PackageVersionMetadata::InstalledType, metadataValue); + + if (IsManifestMetadataSupported(index, testVersion)) + { + auto metadataResult = index.GetMetadataByManifestId(manifestId1); + REQUIRE(metadataResult.size() == 1); + REQUIRE(metadataResult[0].first == PackageVersionMetadata::InstalledType); + REQUIRE(metadataResult[0].second == metadataValue); + } + else + { + REQUIRE(index.GetMetadataByManifestId(manifestId1).empty()); + } + + REQUIRE(index.GetMetadataByManifestId(manifestId2).empty()); +} + +TEST_CASE("SQLiteIndex_NormNameAndPublisher_Exact", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string testName = "Name"; + std::string testPublisher = "Publisher"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName, testPublisher)); + + auto results = index.Search(request); + + if (AreNormalizedNameAndPublisherSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.empty()); + } +} + +TEST_CASE("SQLiteIndex_NormNameAndPublisher_Simple", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string testName = "Name"; + std::string testPublisher = "Publisher"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName + " 1.0", testPublisher + " Corporation")); + + auto results = index.Search(request); + + if (AreNormalizedNameAndPublisherSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.empty()); + } +} + +TEST_CASE("SQLiteIndex_NormNameAndPublisher_Complex", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string testName = "Name"; + std::string testPublisher = "Publisher"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" } }, + { "Id2", testName, "Different Publisher", "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path2", {}, { "PC1", "PC2" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, testName + " 1.0", testPublisher)); + + auto results = index.Search(request); + + if (AreNormalizedNameAndPublisherSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.empty()); + } +} + +TEST_CASE("SQLiteIndex_NormNameAndPublisher_AppsAndFeatures", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string testName = "Name"; + std::string testPublisher = "Publisher"; + std::string arpTestName = "Other Thing"; + std::string arpTestPublisher = "Big Company Name"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id1", testName, testPublisher, "Moniker", "Version", "Channel", { "Tag" }, { "Command" }, "Path1", {}, { "PC1", "PC2" }, arpTestName, arpTestPublisher }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, arpTestName, arpTestPublisher)); + + auto results = index.Search(request); + + if (AreNormalizedNameAndPublisherSupported(index, testVersion)) + { + REQUIRE(results.Matches.size() == 1); + } + else + { + REQUIRE(results.Matches.empty()); + } +} + +TEST_CASE("SQLiteIndex_ManifestHash_Present", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + uint8_t data[4] = { 1, 2, 3, 4 }; + SHA256::HashBuffer hash = SHA256::ComputeHash(data, sizeof(data)); + + SQLiteIndex index = CreateTestIndex(tempFile); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "Bar"; + manifest.StreamSha256 = hash; + index.AddManifest(manifest, "path"); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + + auto hashResult = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ManifestSHA256Hash); + + // Regardless of what hash, it should still be a SHA256 hash + REQUIRE(hashResult); + auto hashResultBytes = SHA256::ConvertToBytes(hashResult.value()); + REQUIRE(hash.size() == hashResultBytes.size()); + + if (AreManifestHashesSupported(index, testVersion)) + { + REQUIRE(std::equal(hash.begin(), hash.end(), hashResultBytes.begin())); + } +} + +TEST_CASE("SQLiteIndex_ManifestHash_Missing", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "Bar"; + index.AddManifest(manifest, "path"); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + + auto hashResult = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ManifestSHA256Hash); + + if (AreManifestHashesSupported(index, testVersion)) + { + REQUIRE(!hashResult); + } +} + +TEST_CASE("SQLiteIndex_ManifestArpVersion_Present_Add", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "Bar"; + manifest.Installers.push_back({}); + manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; + + index.AddManifest(manifest, "path"); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + + auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion); + auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion); + + if (AreArpVersionsSupported(index, testVersion)) + { + REQUIRE(arpMin); + REQUIRE(UtilityVersion(arpMin.value()) == UtilityVersion(manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion)); + REQUIRE(arpMax); + REQUIRE(UtilityVersion(arpMax.value()) == UtilityVersion(manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion)); + } + else + { + REQUIRE_FALSE(arpMin); + REQUIRE_FALSE(arpMax); + } +} + +TEST_CASE("SQLiteIndex_ManifestArpVersion_Present_AddThenUpdate", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "Bar"; + manifest.Installers.push_back({}); + manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; + + index.AddManifest(manifest, "path"); + + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.1"; + + index.UpdateManifest(manifest, "path"); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + + auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion); + auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion); + + if (AreArpVersionsSupported(index, testVersion)) + { + REQUIRE(arpMin); + REQUIRE(arpMin.value() == "1.1"); + REQUIRE(arpMax); + REQUIRE(arpMax.value() == "1.1"); + } + else + { + REQUIRE_FALSE(arpMin); + REQUIRE_FALSE(arpMax); + } +} + +TEST_CASE("SQLiteIndex_ManifestArpVersion_Empty", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "Bar"; + index.AddManifest(manifest, "path"); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + + auto arpMin = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMinVersion); + auto arpMax = index.GetPropertyByPrimaryId(results.Matches[0].first, PackageVersionProperty::ArpMaxVersion); + + if (AreArpVersionsSupported(index, testVersion) && !AreArpVersionsNullable(index)) + { + REQUIRE(arpMin); + REQUIRE(arpMin.value() == ""); + REQUIRE(arpMax); + REQUIRE(arpMax.value() == ""); + } + else + { + REQUIRE_FALSE(arpMin); + REQUIRE_FALSE(arpMax); + } +} + +TEST_CASE("SQLiteIndex_RemoveManifestArpVersionKeepUsedDeleteUnused", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "10.0"; + manifest.Installers.push_back({}); + manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; + + index.AddManifest(manifest, "path"); + + Manifest manifest2; + manifest2.Id = "Foo2"; + manifest2.Version = "1.0"; + manifest2.Installers.push_back({}); + manifest2.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest2.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest2.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "10.0"; + + index.AddManifest(manifest2, "path2"); + + // Before removing, "10.0", "1.0" and "1.1" should all exist. + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "10.0").has_value()); + REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.0").has_value()); + REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.1").has_value()); + } + + index.RemoveManifest(manifest); + + // After removing the first manifest, "10.0" and "1.0" should still stay, "1.1" should be removed. + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadOnly); + REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "10.0").has_value()); + REQUIRE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.0").has_value()); + REQUIRE_FALSE(Schema::V1_0::VersionTable::SelectIdByValue(connection, "1.1").has_value()); + } +} + +TEST_CASE("SQLiteIndex_ManifestArpVersionConflict_AddThrows", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "10.0"; + manifest.DefaultLocalization.Add("ArpVersionCheckConsistencyTest"); + manifest.Moniker = "testmoniker"; + manifest.Installers.push_back({}); + manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; + + index.AddManifest(manifest, "path"); + + REQUIRE(index.CheckConsistency(true)); + + // Add a conflicting one + manifest.Version = "10.1"; + + REQUIRE_THROWS_HR(index.AddManifest(manifest, "path2"), APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); +} + +TEST_CASE("SQLiteIndex_ManifestArpVersionConflict_UpdateThrows", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "10.0"; + manifest.DefaultLocalization.Add("ArpVersionCheckConsistencyTest"); + manifest.Moniker = "testmoniker"; + manifest.Installers.push_back({}); + manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; + + index.AddManifest(manifest, "path"); + REQUIRE(index.CheckConsistency(true)); + + // Add another version + manifest.Version = "10.1"; + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "2.0"; + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "2.1"; + + index.AddManifest(manifest, "path2"); + REQUIRE(index.CheckConsistency(true)); + + // Update to a conflict + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "2.1"; + + REQUIRE_THROWS_HR(index.UpdateManifest(manifest, "path2"), APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); +} + +TEST_CASE("SQLiteIndex_ManifestArpVersion_ValidateManifestAgainstIndex", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "10.0"; + manifest.Installers.push_back({}); + manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; + + index.AddManifest(manifest, "path"); + + // Updating same version should not result in failure. + REQUIRE_NOTHROW(ValidateManifestArpVersion(&index, manifest)); + + // Add different version should result in failure. + manifest.Version = "10.1"; + REQUIRE_THROWS(ValidateManifestArpVersion(&index, manifest)); +} + +TEST_CASE("SQLiteIndex_CheckConsistency_FindEmbeddedNull", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SQLiteIndex index = CreateTestIndex(tempFile, SQLiteVersion::Latest()); + + Manifest manifest; + manifest.Id = "Foo"; + manifest.Version = "10.0"; + manifest.Installers.push_back({}); + manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayVersion = "1.0"; + manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifest.Installers[0].AppsAndFeaturesEntries[1].DisplayVersion = "1.1"; + + index.AddManifest(manifest, "path"); + + // Inject a null character using SQL without binding since we block it + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + Statement update = Statement::Create(connection, "Update versions set version = '10.0'||char(0)||'After Null' where version = '10.0'"); + update.Execute(); + + REQUIRE(!index.CheckConsistency(true)); +} + +TEST_CASE("SQLiteIndex_MapDataFolding_Tags", "[sqliteindex][mapdatafolding]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string tag1 = "Tag1"; + std::string tag2 = "Tag2"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Publisher", "Moniker", "Version1", "", { tag1 }, { "Command" }, "Path1", {}, { "PC1" } }, + { "Id", "Name", "Publisher", "Moniker", "Version2", "", { tag2 }, { "Command" }, "Path2", {}, { "PC2" } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + SearchRequest request1; + request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Tag, MatchType::Exact, tag1)); + auto results1 = index.Search(request1); + + SearchRequest request2; + request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Tag, MatchType::Exact, tag2)); + auto results2 = index.Search(request2); + + REQUIRE(results1.Matches.size() == 1); + REQUIRE(results2.Matches.size() == 1); + REQUIRE(results1.Matches[0].first == results2.Matches[0].first); +} + +TEST_CASE("SQLiteIndex_MapDataFolding_PFNs", "[sqliteindex][mapdatafolding]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string pfn1 = "PFN1"; + std::string pfn2 = "PFN2"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Publisher", "Moniker", "Version1", "", { }, { "Command" }, "Path1", { pfn1 }, { } }, + { "Id", "Name", "Publisher", "Moniker", "Version2", "", { }, { "Command" }, "Path2", { pfn2 }, { } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + if (!AreChannelsSupported(index)) + { + return; + } + + SearchRequest request1; + request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pfn1)); + auto results1 = index.Search(request1); + + SearchRequest request2; + request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::PackageFamilyName, MatchType::Exact, pfn2)); + auto results2 = index.Search(request2); + + REQUIRE(results1.Matches.size() == 1); + REQUIRE(results2.Matches.size() == 1); + REQUIRE(results1.Matches[0].first == results2.Matches[0].first); + + auto versionKeys = index.GetVersionKeysById(results1.Matches[0].first); + REQUIRE(versionKeys.size() == 2); + + auto manifestId1 = versionKeys[0].ManifestId; + auto manifestId2 = versionKeys[1].ManifestId; + + auto pfnValues1 = index.GetMultiPropertyByPrimaryId(manifestId1, PackageVersionMultiProperty::PackageFamilyName); + auto pfnValues2 = index.GetMultiPropertyByPrimaryId(manifestId2, PackageVersionMultiProperty::PackageFamilyName); + + if (IsMapDataFoldingSupported(index, testVersion)) + { + REQUIRE(pfnValues1.size() == 2); + REQUIRE(pfnValues2.size() == 2); + REQUIRE(pfnValues1[0] != pfnValues1[1]); + } + else if (IsMapDataFolded(index)) + { + if (manifestId1 > manifestId2) + { + REQUIRE(pfnValues1.size() == 2); + REQUIRE(pfnValues2.size() == 0); + REQUIRE(pfnValues1[0] != pfnValues1[1]); + } + else + { + REQUIRE(pfnValues1.size() == 0); + REQUIRE(pfnValues2.size() == 2); + REQUIRE(pfnValues2[0] != pfnValues2[1]); + } + } + else + { + REQUIRE(pfnValues1.size() == 1); + REQUIRE(pfnValues2.size() == 1); + REQUIRE(pfnValues1[0] != pfnValues2[0]); + } +} + +TEST_CASE("SQLiteIndex_MapDataFolding_ProductCodes", "[sqliteindex][mapdatafolding]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string pc1 = "PC1"; + std::string pc2 = "PC2"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { "Id", "Name", "Publisher", "Moniker", "Version1", "", { }, { "Command" }, "Path1", { }, { pc1 } }, + { "Id", "Name", "Publisher", "Moniker", "Version2", "", { }, { "Command" }, "Path2", { }, { pc2 } }, + }); + + SQLiteVersion testVersion = TestPrepareForRead(index); + + if (!AreChannelsSupported(index)) + { + return; + } + + SearchRequest request1; + request1.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pc1)); + auto results1 = index.Search(request1); + + SearchRequest request2; + request2.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, pc2)); + auto results2 = index.Search(request2); + + REQUIRE(results1.Matches.size() == 1); + REQUIRE(results2.Matches.size() == 1); + REQUIRE(results1.Matches[0].first == results2.Matches[0].first); + + auto versionKeys = index.GetVersionKeysById(results1.Matches[0].first); + REQUIRE(versionKeys.size() == 2); + + auto manifestId1 = versionKeys[0].ManifestId; + auto manifestId2 = versionKeys[1].ManifestId; + + auto pcValues1 = index.GetMultiPropertyByPrimaryId(manifestId1, PackageVersionMultiProperty::ProductCode); + auto pcValues2 = index.GetMultiPropertyByPrimaryId(manifestId2, PackageVersionMultiProperty::ProductCode); + + REQUIRE(pcValues1.size() == 1); + REQUIRE(pcValues2.size() == 1); + REQUIRE(pcValues1[0] != pcValues2[0]); +} + +TEST_CASE("SQLiteIndex_FilePath_Memory", "[sqliteindex]") +{ + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET); + auto contextData = index.GetContextData(); + REQUIRE(!contextData.Contains(Schema::Property::DatabaseFilePath)); +} + +TEST_CASE("SQLiteIndex_FilePath_Create", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile); + auto contextData = index.GetContextData(); + REQUIRE(contextData.Contains(Schema::Property::DatabaseFilePath)); + REQUIRE(contextData.Get() == tempFile.GetPath()); +} + +TEST_CASE("SQLiteIndex_FilePath_Open", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile); + } + + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + auto contextData = index.GetContextData(); + REQUIRE(contextData.Contains(Schema::Property::DatabaseFilePath)); + REQUIRE(contextData.Get() == tempFile.GetPath()); +} + +TEST_CASE("SQLiteIndex_MigrateTo_Unsupported", "[sqliteindex][V1_7]") +{ + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLiteVersion{ 1, 6 }); + REQUIRE(!index.MigrateTo(SQLiteVersion{ 1, 7 })); +} + +TEST_CASE("SQLiteIndex_MigrateTo_Empty", "[sqliteindex][V2_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + { + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, SQLiteVersion{ 1, 7 }); + REQUIRE(index.MigrateTo(SQLiteVersion{ 2, 0 })); + REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 }); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::Read); + REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 }); + } +} + +TEST_CASE("SQLiteIndex_MigrateTo_Data", "[sqliteindex][V2_0]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string packageId1 = "Id1"; + std::string packageId2 = "Id2"; + std::string packageId3 = "Id3"; + + SQLiteIndex index = SearchTestSetup(tempFile, { + { packageId1, "Name1", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path1", { "PFN1" }, { "PC1" } }, + { packageId2, "Name2", "Moniker", "Version", "", { "ID3" }, { "Command" }, "Path2", { "PFN2" }, { "PC2" } }, + { packageId3, "Name3", "Moniker", "Version", "", { "Tag" }, { "Command" }, "Path3", { "PFN3" }, { "PC3" } }, + }); + + auto preMigrationVersion = index.GetVersion(); + + if (preMigrationVersion == SQLiteVersion{ 1, 7 }) + { + REQUIRE(index.MigrateTo(SQLiteVersion{ 2, 0 })); + REQUIRE(index.GetVersion() == SQLiteVersion{ 2, 0 }); + + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + auto updateData = Schema::V2_0::PackageUpdateTrackingTable::GetUpdatesSince(connection, 0); + + REQUIRE(updateData.size() == 3); + REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId1; }) == 1); + REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId2; }) == 1); + REQUIRE(std::count_if(updateData.begin(), updateData.end(), [&](const auto& x) { return x.PackageIdentifier == packageId3; }) == 1); + } + else + { + REQUIRE(!index.MigrateTo(SQLiteVersion{ 2, 0 })); + REQUIRE(index.GetVersion() == preMigrationVersion); + } +} + +TEST_CASE("SQLiteIndex_Property_IntermediateFilePath", "[sqliteindex]") +{ + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET); + std::filesystem::path intermediateFilePath = "A:\\Path"; + index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediateFilePath.u8string()); + + auto contextData = index.GetContextData(); + REQUIRE(contextData.Contains(Schema::Property::IntermediateFileOutputPath)); + REQUIRE(contextData.Get() == intermediateFilePath); +} + +struct ManifestAndPath +{ + Manifest Manifest; + std::string Path; +}; + +void CreateFakeManifestAndPath( + ManifestAndPath& manifestAndPath, + const string_t& publisher, + std::string_view version = "1.0.0", + std::optional arpMinVersion = {}, + std::optional arpMaxVersion = {}) +{ + CreateFakeManifest(manifestAndPath.Manifest, publisher, version); + manifestAndPath.Path = ConvertToUTF8(CreateNewGuidNameWString()); + manifestAndPath.Manifest.StreamSha256 = SHA256::ComputeHash(manifestAndPath.Path); + + if (arpMinVersion) + { + manifestAndPath.Manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.back().DisplayVersion = arpMinVersion.value(); + } + + if (arpMaxVersion) + { + manifestAndPath.Manifest.Installers[0].BaseInstallerType = InstallerTypeEnum::Exe; + manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.push_back({}); + manifestAndPath.Manifest.Installers[0].AppsAndFeaturesEntries.back().DisplayVersion = arpMaxVersion.value(); + } +} + +std::filesystem::path GetOnlyChild(const std::filesystem::path& parent) +{ + auto parentDirectoryIterator = std::filesystem::directory_iterator{ parent }; + std::filesystem::path result = parentDirectoryIterator->path(); + REQUIRE(++parentDirectoryIterator == std::filesystem::directory_iterator{}); + return result; +} + +void CheckIntermediates(const std::filesystem::path& baseDirectory, const std::vector>& expectedIntermediatesData, std::chrono::seconds sleep = 1s) +{ + std::filesystem::path intermediatesDirectory = baseDirectory / "packages"; + + size_t intermediatePackageCount = std::count_if(std::filesystem::directory_iterator{ intermediatesDirectory }, std::filesystem::directory_iterator{}, [](const auto&){ return true; }); + REQUIRE(intermediatePackageCount == expectedIntermediatesData.size()); + + for (const auto& versions : expectedIntermediatesData) + { + REQUIRE(!versions.empty()); + INFO(versions[0].Manifest.Id); + std::filesystem::path packageDirectory = intermediatesDirectory / ConvertToUTF16(versions[0].Manifest.Id); + + REQUIRE(std::filesystem::exists(packageDirectory)); + std::filesystem::path hashDirectory = GetOnlyChild(packageDirectory); + + std::filesystem::path versionDataFile = GetOnlyChild(hashDirectory); + std::ifstream versionDataStream{ versionDataFile, std::ios_base::in | std::ios_base::binary }; + auto versionDataBytes = ReadEntireStreamAsByteArray(versionDataStream); + + PackageVersionDataManifest versionDataManifest; + versionDataManifest.Deserialize(PackageVersionDataManifest::CreateDecompressor().Decompress(versionDataBytes)); + + const auto& versionDataVersions = versionDataManifest.Versions(); + REQUIRE(versionDataVersions.size() == versions.size()); + + for (const auto& manifestAndPath : versions) + { + const auto& versionDataItr = std::find_if(versionDataVersions.begin(), versionDataVersions.end(), [&](const auto& v) { return v.Version == manifestAndPath.Manifest.Version; }); + REQUIRE(versionDataItr != versionDataVersions.end()); + const auto& versionData = *versionDataItr; + + REQUIRE(manifestAndPath.Path == versionData.ManifestRelativePath); + REQUIRE(SHA256::ConvertToString(manifestAndPath.Manifest.StreamSha256) == versionData.ManifestHash); + + auto versionRange = manifestAndPath.Manifest.GetArpVersionRange(); + if (!versionRange.IsEmpty()) + { + REQUIRE(versionData.ArpMinVersion); + REQUIRE(versionRange.GetMinVersion() == versionData.ArpMinVersion.value()); + REQUIRE(versionData.ArpMaxVersion); + REQUIRE(versionRange.GetMaxVersion() == versionData.ArpMaxVersion.value()); + } + } + } + + // This is needed to force the timestamp to roll over to a new value for the next call to this function. + // An alternate solution would be to hook the timestamp function and control the values it returns + // so that we can advance/halt time arbitrarily. + std::this_thread::sleep_for(sleep); +} + +void PrepareAndCheckIntermediates(const std::filesystem::path& baseFile, const std::filesystem::path& preparedFile, const std::vector>& expectedIntermediatesData, std::chrono::seconds sleep = 1s) +{ + TempDirectory intermediatesDirectory{ "v2_0_intermediates" }; + INFO("Intermediates directory: " << intermediatesDirectory.GetPath()); + + std::filesystem::copy_file(baseFile, preparedFile, std::filesystem::copy_options::overwrite_existing); + + SQLiteIndex index = SQLiteIndex::Open(preparedFile.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); + index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory); + index.PrepareForPackaging(); + + CheckIntermediates(intermediatesDirectory, expectedIntermediatesData, sleep); +} + +TEST_CASE("SQLiteIndex_V2_0_UsageFlow_Simple", "[sqliteindex][V2_0]") +{ + TempFile baseFile{ "v2_0_index_tempdb"s, ".db"s }; + TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s }; + INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]"); + + // Create empty index + std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 2, 0 }); + + std::string publisher = "Publisher"; + ManifestAndPath manifest1; + CreateFakeManifestAndPath(manifest1, publisher, "1.0"); + + { + // Open existing file to add a manifest + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); + index.AddManifest(manifest1.Manifest, manifest1.Path); + } + + PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } }, 0s); +} + +TEST_CASE("SQLiteIndex_V2_0_UsageFlow_Complex", "[sqliteindex][V2_0]") +{ + TempFile baseFile{ "v2_0_index_tempdb"s, ".db"s }; + TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s }; + INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]"); + + // Create empty index + std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 2, 0 }); + + // Open existing file to add a new package + std::string Publisher1 = "Publisher1"; + ManifestAndPath manifest1; + CreateFakeManifestAndPath(manifest1, Publisher1, "1.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); + index.AddManifest(manifest1.Manifest, manifest1.Path); + } + + PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } }); + + // Open existing file to add another new package + ManifestAndPath manifest2; + CreateFakeManifestAndPath(manifest2, "Publisher2", "1.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); + index.AddManifest(manifest2.Manifest, manifest2.Path); + } + + PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 } }); + + // Open existing file to add a new version of existing package + ManifestAndPath manifest3; + CreateFakeManifestAndPath(manifest3, Publisher1, "2.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); + index.AddManifest(manifest3.Manifest, manifest3.Path); + } + + PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1, manifest3 } }); + + // Open existing file to add a new version of existing package and update an existing version + manifest2.Manifest.StreamSha256 = SHA256::ComputeHash(manifest2.Manifest.Id); + + ManifestAndPath manifest4; + CreateFakeManifestAndPath(manifest4, Publisher1, "3.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.SetProperty(SQLiteIndex::Property::PackageUpdateTrackingBaseTime, ""); + index.UpdateManifest(manifest2.Manifest, manifest2.Path); + index.AddManifest(manifest4.Manifest, manifest4.Path); + } + + PrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3, manifest4 } }, 0s); +} + +void MigratePrepareAndCheckIntermediates(const std::filesystem::path& baseFile, const std::filesystem::path& preparedFile, const std::vector>& expectedIntermediatesData) +{ + TempDirectory intermediatesDirectory{ "v2_0_intermediates" }; + INFO("Intermediates directory: " << intermediatesDirectory.GetPath()); + + std::filesystem::copy_file(baseFile, preparedFile, std::filesystem::copy_options::overwrite_existing); + + SQLiteIndex index = SQLiteIndex::Open(preparedFile.u8string(), SQLiteStorageBase::OpenDisposition::ReadWrite); + index.MigrateTo({ 2, 0 }); + index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory); + index.PrepareForPackaging(); + + CheckIntermediates(intermediatesDirectory, expectedIntermediatesData, 0s); +} + +TEST_CASE("SQLiteIndex_V2_0_UsageFlow_ComplexMigration", "[sqliteindex][V2_0]") +{ + TempFile baseFile{ "v1_7_index_tempdb"s, ".db"s }; + TempFile preparedFile{ "v2_0_index_prepared_tempdb"s, ".db"s }; + INFO("Using files named: [" << baseFile.GetPath() << "] and [" << preparedFile.GetPath() << "]"); + + // Create empty index + std::ignore = SQLiteIndex::CreateNew(baseFile, SQLiteVersion{ 1, 7 }); + + // Open existing file to add a new package + std::string Publisher1 = "Publisher1"; + ManifestAndPath manifest1; + CreateFakeManifestAndPath(manifest1, Publisher1, "1.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.AddManifest(manifest1.Manifest, manifest1.Path); + } + + MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest1 } }); + + // Open existing file to add another new package + ManifestAndPath manifest2; + CreateFakeManifestAndPath(manifest2, "Publisher2", "1.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.AddManifest(manifest2.Manifest, manifest2.Path); + } + + MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1 } }); + + // Open existing file to add a new version of existing package + ManifestAndPath manifest3; + CreateFakeManifestAndPath(manifest3, Publisher1, "2.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.AddManifest(manifest3.Manifest, manifest3.Path); + } + + MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3 } }); + + // Open existing file to add a new version of existing package and update an existing version + manifest2.Manifest.StreamSha256 = SHA256::ComputeHash(manifest2.Manifest.Id); + + ManifestAndPath manifest4; + CreateFakeManifestAndPath(manifest4, Publisher1, "3.0"); + + { + SQLiteIndex index = SQLiteIndex::Open(baseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + index.UpdateManifest(manifest2.Manifest, manifest2.Path); + index.AddManifest(manifest4.Manifest, manifest4.Path); + } + + MigratePrepareAndCheckIntermediates(baseFile, preparedFile, { { manifest2 }, { manifest1, manifest3, manifest4 } }); +} + +TEST_CASE("SQLiteIndex_DependencyWithCaseMismatch", "[sqliteindex][V1_4]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest dependencyManifest1, dependencyManifest2, manifest; + SQLiteIndex index = SimpleTestSetup(tempFile, dependencyManifest1, SQLiteVersion::Latest()); + + // Must contain some upper case + auto& publisher2 = "Test2"; + CreateFakeManifest(dependencyManifest2, publisher2); + index.AddManifest(dependencyManifest2, GetPathFromManifest(dependencyManifest2)); + + auto& publisher3 = "Test3"; + CreateFakeManifest(manifest, publisher3); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, dependencyManifest1.Id, "1.0.0")); + manifest.Installers[0].Dependencies.Add(Dependency(DependencyType::Package, ToLower(dependencyManifest2.Id), "1.0.0")); + + index.AddManifest(manifest, GetPathFromManifest(manifest)); +} + +TEST_CASE("SQLiteIndex_AddOrUpdateManifest", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + std::string manifestPath = "test/id/test.id-1.0.0.yaml"; + Manifest manifest; + manifest.Installers.push_back({}); + manifest.Id = "test.id"; + manifest.DefaultLocalization.Add < Localization::PackageName>("Test Name"); + manifest.Moniker = "testmoniker"; + manifest.Version = "1.0.0"; + manifest.Channel = "test"; + manifest.DefaultLocalization.Add({ "t1", "t2" }); + manifest.Installers[0].Commands = { "test1", "test2" }; + + { + auto version = GENERATE(SQLiteVersion{ 1, 0 }, SQLiteVersion::Latest()); + SQLiteIndex index = SQLiteIndex::CreateNew(tempFile, version); + + REQUIRE(index.AddOrUpdateManifest(manifest, manifestPath)); + } + + { + SQLiteIndex index = SQLiteIndex::Open(tempFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + + // Update should return false + REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath)); + + manifest.DefaultLocalization.Add("description2"); + + // Update should return false + REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath)); + + // Update with indexed changes should still return false + manifest.DefaultLocalization.Add("Test Name2"); + manifest.Moniker = "testmoniker2"; + manifest.DefaultLocalization.Add({ "t1", "t2", "t3" }); + manifest.Installers[0].Commands = {}; + + REQUIRE(!index.AddOrUpdateManifest(manifest, manifestPath)); + } +} + +TEST_CASE("SQLiteIndex_VersionStringPreserved", "[sqliteindex]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Manifest manifest; + SQLiteIndex index = CreateTestIndex(tempFile); + + string_t publisher = "Test"; + std::string version = GENERATE("1.0", "1.10"); + + CreateFakeManifest(manifest, publisher, version); + index.AddManifest(manifest, GetPathFromManifest(manifest)); + + TempDirectory intermediatesDirectory{ "v2_0_intermediates" }; + INFO("Intermediates directory: " << intermediatesDirectory.GetPath()); + + index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, intermediatesDirectory); + index.PrepareForPackaging(); + + auto results = index.Search({}); + REQUIRE(results.Matches.size() == 1); + + std::string extractedVersion = GetPropertyStringById(index, results.Matches[0].first, PackageVersionProperty::Version); + + REQUIRE(extractedVersion == version); +} diff --git a/src/AppInstallerCLITests/SQLiteIndexSource.cpp b/src/AppInstallerCLITests/SQLiteIndexSource.cpp index 911c0cac29..296d0f2c0b 100644 --- a/src/AppInstallerCLITests/SQLiteIndexSource.cpp +++ b/src/AppInstallerCLITests/SQLiteIndexSource.cpp @@ -1,285 +1,285 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace std::string_literals; -using namespace TestCommon; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Microsoft; -using namespace AppInstaller::SQLite; - -using SQLiteVersion = AppInstaller::SQLite::Version; - -static std::shared_ptr SimpleTestSetup(const std::string& filePath, SourceDetails& details, Manifest& manifest, std::string& relativePath, const std::filesystem::path& manifestFile = "Manifest-Good.yaml") -{ - SQLiteVersion latest1 = Version::LatestForMajor(1); - SQLiteVersion latest2 = Version::LatestForMajor(2); - - const SQLiteVersion versionToUse = GENERATE_COPY(SQLiteVersion{ latest1 }, SQLiteVersion{ latest2 }); - - SQLiteIndex index = SQLiteIndex::CreateNew(filePath, versionToUse); - - TestDataFile testManifest(manifestFile); - manifest = YamlParser::CreateFromPath(testManifest); - - std::filesystem::path testManifestPath = testManifest.GetPath(); - relativePath = testManifestPath.filename().u8string(); - - TempDirectory sourceFilesDirectory{ "SQLiteIndexSource" }; - std::filesystem::path sourceFilesDirectoryPath = sourceFilesDirectory.GetPath(); - std::filesystem::create_directories(sourceFilesDirectoryPath); - std::filesystem::copy_file(testManifestPath, sourceFilesDirectoryPath / relativePath); - sourceFilesDirectory.Release(); - - index.AddManifest(manifest, relativePath); - - details.Name = "TestName"; - details.Type = "TestType"; - details.Arg = sourceFilesDirectoryPath.u8string(); - details.Data = ""; - details.Identifier = "SimpleTestSetup"; - - if (versionToUse.MajorVersion == 2) - { - index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, sourceFilesDirectoryPath.u8string()); - } - - index.PrepareForPackaging(); - - return std::make_shared(details, std::move(index)); -} - -static bool SupportsChannel(const std::shared_ptr& source) -{ - return source->GetIndex().GetVersion().MajorVersion == 1; -} - -TEST_CASE("SQLiteIndexSource_Search_IdExactMatch", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].Package); - REQUIRE(results.Matches[0].MatchCriteria.Field == PackageMatchField::Id); - REQUIRE(results.Matches[0].MatchCriteria.Type == MatchType::Exact); - REQUIRE(results.Matches[0].MatchCriteria.Value == manifest.Id); -} - -TEST_CASE("SQLiteIndexSource_Search_NoMatch", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, "THIS DOES NOT MATCH ANYTHING!"); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 0); -} - -TEST_CASE("SQLiteIndexSource_Id", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].Package); - auto latestVersion = results.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); - - REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Id).get() == manifest.Id); -} - -TEST_CASE("SQLiteIndexSource_Name", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].Package); - auto latestVersion = results.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); - - REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Name).get() == manifest.DefaultLocalization.Get()); -} - -TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].Package); - REQUIRE(results.Matches[0].Package->GetAvailable().size() == 1); - - auto result = results.Matches[0].Package->GetAvailable()[0]->GetVersionKeys(); - REQUIRE(result.size() == 1); - REQUIRE(result[0].Version == manifest.Version); - if (SupportsChannel(source)) - { - REQUIRE(result[0].Channel == manifest.Channel); - } -} - -TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].Package); - REQUIRE(results.Matches[0].Package->GetAvailable().size() == 1); - auto package = results.Matches[0].Package->GetAvailable()[0]; - - auto specificResultVersion = package->GetVersion(PackageVersionKey("", manifest.Version, SupportsChannel(source) ? manifest.Channel : "")); - REQUIRE(specificResultVersion); - auto specificResult = specificResultVersion->GetManifest(); - REQUIRE(specificResult.Id == manifest.Id); - REQUIRE(specificResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); - REQUIRE(specificResult.Version == manifest.Version); - if (SupportsChannel(source)) - { - REQUIRE(specificResult.Channel == manifest.Channel); - } - - if (SupportsChannel(source)) - { - auto latestResultVersion = package->GetVersion(PackageVersionKey("", "", manifest.Channel)); - REQUIRE(latestResultVersion); - auto latestResult = latestResultVersion->GetManifest(); - REQUIRE(latestResult.Id == manifest.Id); - REQUIRE(latestResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); - REQUIRE(latestResult.Version == manifest.Version); - REQUIRE(latestResult.Channel == manifest.Channel); - } - - auto noResultVersion = package->GetVersion(PackageVersionKey("", "blargle", "flargle")); - REQUIRE(!noResultVersion); -} - -TEST_CASE("SQLiteIndexSource_IsSame", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto result1 = source->Search(request); - REQUIRE(result1.Matches.size() == 1); - REQUIRE(result1.Matches[0].Package->GetAvailable().size() == 1); - - auto result2 = source->Search(request); - REQUIRE(result2.Matches.size() == 1); - REQUIRE(result2.Matches[0].Package->GetAvailable().size() == 1); - - REQUIRE(result1.Matches[0].Package->GetAvailable()[0]->IsSame(result2.Matches[0].Package->GetAvailable()[0].get())); -} - -TEST_CASE("SQLiteIndexSource_Package_ProductCodes", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].Package); - - auto package = results.Matches[0].Package->GetAvailable()[0]; - - auto manifestPCs = manifest.GetProductCodes(); - auto propertyPCs = package->GetMultiProperty(PackageMultiProperty::ProductCode); - REQUIRE(manifestPCs.size() == 1); - REQUIRE(propertyPCs.size() == 1); - REQUIRE(manifestPCs[0] == propertyPCs[0].get()); -} - -TEST_CASE("SQLiteIndexSource_VersionSelection", "[sqliteindexsource]") -{ - TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - SourceDetails details; - Manifest manifest; - std::string relativePath; - std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath, "InstallFlowTest_Exe.yaml"); - - SearchRequest request; - request.Query = RequestMatch(MatchType::Exact, manifest.Id); - - auto results = source->Search(request); - REQUIRE(results.Matches.size() == 1); - REQUIRE(results.Matches[0].Package); - - auto package = results.Matches[0].Package->GetAvailable()[0]; - - PackageVersionKey key{ {}, "1", {} }; - auto version = package->GetVersion(key); - REQUIRE(version); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace std::string_literals; +using namespace TestCommon; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; +using namespace AppInstaller::SQLite; + +using SQLiteVersion = AppInstaller::SQLite::Version; + +static std::shared_ptr SimpleTestSetup(const std::string& filePath, SourceDetails& details, Manifest& manifest, std::string& relativePath, const std::filesystem::path& manifestFile = "Manifest-Good.yaml") +{ + SQLiteVersion latest1 = Version::LatestForMajor(1); + SQLiteVersion latest2 = Version::LatestForMajor(2); + + const SQLiteVersion versionToUse = GENERATE_COPY(SQLiteVersion{ latest1 }, SQLiteVersion{ latest2 }); + + SQLiteIndex index = SQLiteIndex::CreateNew(filePath, versionToUse); + + TestDataFile testManifest(manifestFile); + manifest = YamlParser::CreateFromPath(testManifest); + + std::filesystem::path testManifestPath = testManifest.GetPath(); + relativePath = testManifestPath.filename().u8string(); + + TempDirectory sourceFilesDirectory{ "SQLiteIndexSource" }; + std::filesystem::path sourceFilesDirectoryPath = sourceFilesDirectory.GetPath(); + std::filesystem::create_directories(sourceFilesDirectoryPath); + std::filesystem::copy_file(testManifestPath, sourceFilesDirectoryPath / relativePath); + sourceFilesDirectory.Release(); + + index.AddManifest(manifest, relativePath); + + details.Name = "TestName"; + details.Type = "TestType"; + details.Arg = sourceFilesDirectoryPath.u8string(); + details.Data = ""; + details.Identifier = "SimpleTestSetup"; + + if (versionToUse.MajorVersion == 2) + { + index.SetProperty(SQLiteIndex::Property::IntermediateFileOutputPath, sourceFilesDirectoryPath.u8string()); + } + + index.PrepareForPackaging(); + + return std::make_shared(details, std::move(index)); +} + +static bool SupportsChannel(const std::shared_ptr& source) +{ + return source->GetIndex().GetVersion().MajorVersion == 1; +} + +TEST_CASE("SQLiteIndexSource_Search_IdExactMatch", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].Package); + REQUIRE(results.Matches[0].MatchCriteria.Field == PackageMatchField::Id); + REQUIRE(results.Matches[0].MatchCriteria.Type == MatchType::Exact); + REQUIRE(results.Matches[0].MatchCriteria.Value == manifest.Id); +} + +TEST_CASE("SQLiteIndexSource_Search_NoMatch", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, "THIS DOES NOT MATCH ANYTHING!"); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 0); +} + +TEST_CASE("SQLiteIndexSource_Id", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].Package); + auto latestVersion = results.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); + + REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Id).get() == manifest.Id); +} + +TEST_CASE("SQLiteIndexSource_Name", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].Package); + auto latestVersion = results.Matches[0].Package->GetAvailable()[0]->GetLatestVersion(); + + REQUIRE(latestVersion->GetProperty(PackageVersionProperty::Name).get() == manifest.DefaultLocalization.Get()); +} + +TEST_CASE("SQLiteIndexSource_Versions", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].Package); + REQUIRE(results.Matches[0].Package->GetAvailable().size() == 1); + + auto result = results.Matches[0].Package->GetAvailable()[0]->GetVersionKeys(); + REQUIRE(result.size() == 1); + REQUIRE(result[0].Version == manifest.Version); + if (SupportsChannel(source)) + { + REQUIRE(result[0].Channel == manifest.Channel); + } +} + +TEST_CASE("SQLiteIndexSource_GetManifest", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].Package); + REQUIRE(results.Matches[0].Package->GetAvailable().size() == 1); + auto package = results.Matches[0].Package->GetAvailable()[0]; + + auto specificResultVersion = package->GetVersion(PackageVersionKey("", manifest.Version, SupportsChannel(source) ? manifest.Channel : "")); + REQUIRE(specificResultVersion); + auto specificResult = specificResultVersion->GetManifest(); + REQUIRE(specificResult.Id == manifest.Id); + REQUIRE(specificResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); + REQUIRE(specificResult.Version == manifest.Version); + if (SupportsChannel(source)) + { + REQUIRE(specificResult.Channel == manifest.Channel); + } + + if (SupportsChannel(source)) + { + auto latestResultVersion = package->GetVersion(PackageVersionKey("", "", manifest.Channel)); + REQUIRE(latestResultVersion); + auto latestResult = latestResultVersion->GetManifest(); + REQUIRE(latestResult.Id == manifest.Id); + REQUIRE(latestResult.DefaultLocalization.Get() == manifest.DefaultLocalization.Get()); + REQUIRE(latestResult.Version == manifest.Version); + REQUIRE(latestResult.Channel == manifest.Channel); + } + + auto noResultVersion = package->GetVersion(PackageVersionKey("", "blargle", "flargle")); + REQUIRE(!noResultVersion); +} + +TEST_CASE("SQLiteIndexSource_IsSame", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto result1 = source->Search(request); + REQUIRE(result1.Matches.size() == 1); + REQUIRE(result1.Matches[0].Package->GetAvailable().size() == 1); + + auto result2 = source->Search(request); + REQUIRE(result2.Matches.size() == 1); + REQUIRE(result2.Matches[0].Package->GetAvailable().size() == 1); + + REQUIRE(result1.Matches[0].Package->GetAvailable()[0]->IsSame(result2.Matches[0].Package->GetAvailable()[0].get())); +} + +TEST_CASE("SQLiteIndexSource_Package_ProductCodes", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].Package); + + auto package = results.Matches[0].Package->GetAvailable()[0]; + + auto manifestPCs = manifest.GetProductCodes(); + auto propertyPCs = package->GetMultiProperty(PackageMultiProperty::ProductCode); + REQUIRE(manifestPCs.size() == 1); + REQUIRE(propertyPCs.size() == 1); + REQUIRE(manifestPCs[0] == propertyPCs[0].get()); +} + +TEST_CASE("SQLiteIndexSource_VersionSelection", "[sqliteindexsource]") +{ + TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + SourceDetails details; + Manifest manifest; + std::string relativePath; + std::shared_ptr source = SimpleTestSetup(tempFile, details, manifest, relativePath, "InstallFlowTest_Exe.yaml"); + + SearchRequest request; + request.Query = RequestMatch(MatchType::Exact, manifest.Id); + + auto results = source->Search(request); + REQUIRE(results.Matches.size() == 1); + REQUIRE(results.Matches[0].Package); + + auto package = results.Matches[0].Package->GetAvailable()[0]; + + PackageVersionKey key{ {}, "1", {} }; + auto version = package->GetVersion(key); + REQUIRE(version); +} diff --git a/src/AppInstallerCLITests/SQLiteWrapper.cpp b/src/AppInstallerCLITests/SQLiteWrapper.cpp index 7fa70aa955..10fd960372 100644 --- a/src/AppInstallerCLITests/SQLiteWrapper.cpp +++ b/src/AppInstallerCLITests/SQLiteWrapper.cpp @@ -1,856 +1,856 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include - -using namespace AppInstaller::SQLite; -using namespace std::string_literals; - -static const char* s_firstColumn = "first"; -static const char* s_secondColumn = "second"; -static const char* s_tableName = "simpletest"; -static const char* s_savepoint = "simplesave"; - -static const char* s_CreateSimpleTestTableSQL = R"( -CREATE TABLE [main].[simpletest]( - [first] INT, - [second] TEXT); -)"; - -static const char* s_insertToSimpleTestTableSQL = R"( -insert into simpletest (first, second) values (?, ?) -)"; - -static const char* s_selectFromSimpleTestTableSQL = R"( -select first, second from simpletest -)"; - -void CreateSimpleTestTable(Connection& connection) -{ - Builder::StatementBuilder builder; - builder.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), - Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), - }); - - Statement createTable = builder.Prepare(connection); - REQUIRE_FALSE(createTable.Step()); - REQUIRE(createTable.GetState() == Statement::State::Completed); -} - -void InsertIntoSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) -{ - Builder::StatementBuilder builder; - builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, secondVal); - Statement insert = builder.Prepare(connection); - - REQUIRE_FALSE(insert.Step()); - REQUIRE(insert.GetState() == Statement::State::Completed); -} - -void UpdateSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) -{ - Builder::StatementBuilder update; - update.Update(s_tableName).Set().Column(s_firstColumn).Equals(firstVal).Column(s_secondColumn).Equals(secondVal); - update.Execute(connection); -} - -void InsertIntoSimpleTestTableWithNull(Connection& connection, int firstVal) -{ - Builder::StatementBuilder builder; - builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, nullptr); - Statement insert = builder.Prepare(connection); - - REQUIRE_FALSE(insert.Step()); - REQUIRE(insert.GetState() == Statement::State::Completed); -} - -void SelectFromSimpleTestTableOnlyOneRow(Connection& connection, int firstVal, const std::string& secondVal) -{ - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName); - Statement select = builder.Prepare(connection); - - REQUIRE(select.Step()); - REQUIRE(select.GetState() == Statement::State::HasRow); - - int firstRead = select.GetColumn(0); - std::string secondRead = select.GetColumn(1); - - REQUIRE(firstVal == firstRead); - REQUIRE(secondVal == secondRead); - - auto tuple = select.GetRow(); - - REQUIRE(firstVal == std::get<0>(tuple)); - REQUIRE(secondVal == std::get<1>(tuple)); - - REQUIRE_FALSE(select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); - - select.Reset(); - REQUIRE(select.GetState() == Statement::State::Prepared); - - REQUIRE(select.Step()); - REQUIRE(select.GetState() == Statement::State::HasRow); -} - -TEST_CASE("SQLiteWrapperMemoryCreate", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - int firstVal = 1; - std::string secondVal = "test"; - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperFileCreateAndReopen", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - int firstVal = 1; - std::string secondVal = "test"; - - // Create the DB and some data - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - // Reopen the DB and read data - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - } -} - -TEST_CASE("SQLiteWrapperSavepointRollback", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Rollback(); - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperSavepointRollbackOnDestruct", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperSavepointCommit", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Commit(); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperSavepointReuse", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - int firstVal = 1; - std::string secondVal = "test"; - - // Create the DB and some data - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - // Reopen the DB and update with a single savepoint - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - Savepoint savepoint = Savepoint::Create(connection, s_savepoint); - - firstVal = 2; - secondVal = "test2"; - UpdateSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Commit(); - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - } - - // Reopen the DB and update with a multiple savepoint - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - - { - Savepoint savepoint = Savepoint::Create(connection, s_savepoint); - - firstVal = 3; - secondVal = "test3"; - UpdateSimpleTestTable(connection, firstVal, secondVal); - } - - { - Savepoint savepoint = Savepoint::Create(connection, s_savepoint); - - firstVal = 4; - secondVal = "test4"; - UpdateSimpleTestTable(connection, firstVal, secondVal); - - savepoint.Commit(); - } - } - - { - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - } -} - -TEST_CASE("SQLiteWrapper_EscapeStringForLike", "[sqlitewrapper]") -{ - std::string escape(EscapeCharForLike); - - std::string input = "test"; - std::string output = EscapeStringForLike(input); - REQUIRE(input == output); - - input = EscapeCharForLike; - output = EscapeStringForLike(input); - REQUIRE((input + input) == output); - - input = "%"; - output = EscapeStringForLike(input); - REQUIRE((escape + input) == output); - - input = "_"; - output = EscapeStringForLike(input); - REQUIRE((escape + input) == output); - - input = "%_A_%"; - std::string expected = escape + "%" + escape + "_A" + escape + "_" + escape + "%"; - output = EscapeStringForLike(input); - REQUIRE(expected == output); -} - -TEST_CASE("SQLiteWrapper_BindWithEmbeddedNull", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - int firstVal = 1; - std::string secondVal = "test"; - secondVal[1] = '\0'; - - REQUIRE_THROWS_HR(InsertIntoSimpleTestTable(connection, firstVal, secondVal), APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL); -} - -TEST_CASE("SQLiteWrapper_PrepareFailure", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(std::string{ s_tableName } + "2").Where(s_firstColumn).Equals(2); - - REQUIRE_THROWS_HR(builder.Prepare(connection), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_ERROR)); -} - -TEST_CASE("SQLiteWrapper_BusyTimeout_None", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - wil::unique_event busy, done; - busy.create(); - done.create(); - - std::thread busyThread([&]() - { - Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - Statement threadStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); - threadStatement.Execute(); - busy.SetEvent(); - done.wait(500); - }); - busyThread.detach(); - - busy.wait(500); - - Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - testConnection.SetBusyTimeout(0ms); - Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); - REQUIRE_THROWS_HR(testStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_BUSY)); - - done.SetEvent(); -} - -TEST_CASE("SQLiteWrapper_BusyTimeout_Some", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - wil::unique_event busy, ready, done; - busy.create(); - ready.create(); - done.create(); - - std::thread busyThread([&]() - { - Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - Statement threadBeginStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); - Statement threadCommitStatement = Statement::Create(threadConnection, "COMMIT"); - threadBeginStatement.Execute(); - busy.SetEvent(); - ready.wait(500); - done.wait(100); - threadCommitStatement.Execute(); - }); - busyThread.detach(); - - busy.wait(500); - - Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - testConnection.SetBusyTimeout(500ms); - Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); - ready.SetEvent(); - testStatement.Execute(); - - done.SetEvent(); -} - -TEST_CASE("SQLiteWrapper_CloseConnectionOnError", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - Builder::StatementBuilder builder; - builder.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), - Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), - }); - - Statement createTable = builder.Prepare(connection); - REQUIRE_FALSE(createTable.Step()); - REQUIRE(createTable.GetState() == Statement::State::Completed); - - createTable.Reset(); - REQUIRE_THROWS(createTable.Step(true)); - - // Do anything that needs the connection - REQUIRE_THROWS_HR(connection.GetLastInsertRowID(), APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED); -} - -TEST_CASE("SQLBuilder_SimpleSelectBind", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTable(connection, 3, "3"); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(2); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 2); - REQUIRE(statement.GetColumn(0) == "2"); - - REQUIRE(!statement.Step()); - - Builder::StatementBuilder buildCount; - buildCount.Select(Builder::RowCount).From(s_tableName); - - auto rows = buildCount.Prepare(connection); - - REQUIRE(rows.Step()); - REQUIRE(rows.GetColumn(0) == 3); - - REQUIRE(!rows.Step()); -} - -TEST_CASE("SQLBuilder_SimpleSelectUnbound", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTable(connection, 3, "3"); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(Builder::Unbound); - - auto statement = builder.Prepare(connection); - - statement.Bind(1, 2); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 2); - REQUIRE(statement.GetColumn(0) == "2"); - - REQUIRE(!statement.Step()); -} - -TEST_CASE("SQLBuilder_SimpleSelectNull", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTableWithNull(connection, 3); - - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).IsNull(); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 3); - REQUIRE(statement.GetColumnIsNull(1)); - - REQUIRE(!statement.Step()); -} - -TEST_CASE("SQLBuilder_SimpleSelectOptional", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - InsertIntoSimpleTestTable(connection, 1, "1"); - InsertIntoSimpleTestTable(connection, 2, "2"); - InsertIntoSimpleTestTableWithNull(connection, 3); - - std::optional secondValue; - - { - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 3); - REQUIRE(statement.GetColumnIsNull(1)); - - REQUIRE(!statement.Step()); - } - - { - secondValue = "2"; - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); - - auto statement = builder.Prepare(connection); - - REQUIRE(statement.Step()); - REQUIRE(statement.GetColumn(0) == 2); - REQUIRE(statement.GetColumn(1) == "2"); - - REQUIRE(!statement.Step()); - } -} - -TEST_CASE("SQLBuilder_Update", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - CreateSimpleTestTable(connection); - - int firstVal = 1; - std::string secondVal = "test"; - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); - - firstVal = 2; - secondVal = "testing"; - - UpdateSimpleTestTable(connection, firstVal, secondVal); - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLBuilder_CaseInsensitive", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - Builder::StatementBuilder createTable; - createTable.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Text).CollateNoCase() - }); - - createTable.Execute(connection); - - std::string upperCaseVal = "TEST"; - std::string lowerCaseVal = "test"; - - { - INFO("Insert initial value"); - Builder::StatementBuilder builder; - builder.InsertInto(s_tableName) - .Columns({ s_firstColumn }) - .Values(upperCaseVal); - - builder.Execute(connection); - } - - { - INFO("Retrieve using case-insensitive value"); - Builder::StatementBuilder builder; - builder.Select({ s_firstColumn }).From(s_tableName).Where(s_firstColumn).Equals(lowerCaseVal); - - auto statement = builder.Prepare(connection); - REQUIRE(statement.Step()); - } -} - -TEST_CASE("SQLBuilder_CreateTable", "[sqlbuilder]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int testRun = GENERATE(0, 1, 2, 3, 4, 5, 6, 7); - - bool notNull = ((testRun & 1) != 0); - bool unique = ((testRun & 2) != 0); - bool pk = ((testRun & 4) != 0); - CAPTURE(notNull, unique, pk); - - Builder::StatementBuilder createTable; - createTable.CreateTable(s_tableName).Columns({ - Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int).NotNull(notNull).Unique(unique).PrimaryKey(pk) - }); - - createTable.Execute(connection); - - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_tableName).Columns(s_firstColumn).Values(Builder::Unbound); - - Statement insertStatement = insertBuilder.Prepare(connection); - - { - INFO("Insert NULL"); - insertStatement.Bind(1, nullptr); - - if (notNull) - { - REQUIRE_THROWS_HR(insertStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_NOTNULL)); - } - else - { - insertStatement.Execute(); - } - } - - { - INFO("Insert unique values"); - insertStatement.Reset(); - insertStatement.Bind(1, 1); - insertStatement.Execute(); - - insertStatement.Reset(); - insertStatement.Bind(1, 2); - insertStatement.Execute(); - } - - { - INFO("Insert duplicate values"); - insertStatement.Reset(); - insertStatement.Bind(1, 1); - - if (unique || pk) - { - HRESULT expectedHR = S_OK; - if (pk) - { - expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_PRIMARYKEY); - } - else - { - expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_UNIQUE); - } - REQUIRE_THROWS_HR(insertStatement.Execute(), expectedHR); - } - else - { - insertStatement.Execute(); - } - } -} - -TEST_CASE("SQLBuilder_InsertValueBinding", "[sqlbuilder]") -{ - char const* const columns[] = { "a", "b", "c", "d", "e", "f" }; - - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - - { - INFO("Create table"); - Builder::StatementBuilder createTable; - createTable.CreateTable(s_tableName).BeginColumns(); - for (const auto c : columns) - { - createTable.Column(Builder::ColumnBuilder(c, Builder::Type::Int)); - } - createTable.EndColumns(); - createTable.Execute(connection); - } - - { - INFO("Insert values"); - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_tableName).BeginColumns(); - for (const auto c : columns) - { - insertBuilder.Column(c); - } - insertBuilder.EndColumns().Values(0, 1, 2, 3, 4, 5); - insertBuilder.Execute(connection); - } - - { - INFO("Insert values"); - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_tableName).BeginColumns(); - for (const auto c : columns) - { - insertBuilder.Column(c); - } - insertBuilder.EndColumns().BeginValues(); - insertBuilder.Value(5); - insertBuilder.Value(nullptr); - insertBuilder.Value(3); - insertBuilder.Value(std::optional{}); - insertBuilder.Value(std::optional{ 1 }); - insertBuilder.Value(Builder::Unbound); - insertBuilder.EndValues(); - insertBuilder.Execute(connection); - } - - { - INFO("Select values"); - Builder::StatementBuilder selectBuilder; - selectBuilder.Select(); - for (const auto c : columns) - { - selectBuilder.Column(c); - } - selectBuilder.From(s_tableName); - - Statement select = selectBuilder.Prepare(connection); - REQUIRE(select.Step()); - - for (int i = 0; i < ARRAYSIZE(columns); ++i) - { - REQUIRE(i == select.GetColumn(i)); - } - - REQUIRE(select.Step()); - - for (int i = 0; i < ARRAYSIZE(columns); ++i) - { - if (i & 1) - { - REQUIRE(select.GetColumnIsNull(i)); - } - else - { - REQUIRE((5 - i) == select.GetColumn(i)); - } - } - - REQUIRE(!select.Step()); - } -} - -TEST_CASE("SQLiteWrapperTransactionRollback", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - Transaction transaction = Transaction::Create(connection, "test_transaction", false); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - transaction.Rollback(); - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperTransactionRollbackOnDestruct", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", false); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - } - - Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); - REQUIRE(!select.Step()); - REQUIRE(select.GetState() == Statement::State::Completed); -} - -TEST_CASE("SQLiteWrapperTransactionCommit", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", false); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - transaction.Commit(); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperTransactionImmediate", "[sqlitewrapper]") -{ - Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", true); - - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - transaction.Commit(); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} - -TEST_CASE("SQLiteWrapperTransactionWriteConflict", "[sqlitewrapper]") -{ - TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; - INFO("Using temporary file named: " << tempFile.GetPath()); - - Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); - connection.SetJournalMode("WAL"); - - int firstVal = 1; - std::string secondVal = "test"; - - CreateSimpleTestTable(connection); - - Connection connection2 = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); - std::chrono::milliseconds busyWait = 250ms; - connection2.SetBusyTimeout(busyWait); - - { - Transaction transaction = Transaction::Create(connection, "test_transaction", true); - InsertIntoSimpleTestTable(connection, firstVal, secondVal); - - // Start second transaction - std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); - std::chrono::system_clock::time_point end = start; - try - { - Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); - } - catch (...) - { - end = std::chrono::system_clock::now(); - } - - std::chrono::milliseconds duration = std::chrono::duration_cast(end - start); - REQUIRE(duration >= busyWait); - - transaction.Commit(); - - Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); - InsertIntoSimpleTestTable(connection2, firstVal, secondVal); - } - - SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +using namespace AppInstaller::SQLite; +using namespace std::string_literals; + +static const char* s_firstColumn = "first"; +static const char* s_secondColumn = "second"; +static const char* s_tableName = "simpletest"; +static const char* s_savepoint = "simplesave"; + +static const char* s_CreateSimpleTestTableSQL = R"( +CREATE TABLE [main].[simpletest]( + [first] INT, + [second] TEXT); +)"; + +static const char* s_insertToSimpleTestTableSQL = R"( +insert into simpletest (first, second) values (?, ?) +)"; + +static const char* s_selectFromSimpleTestTableSQL = R"( +select first, second from simpletest +)"; + +void CreateSimpleTestTable(Connection& connection) +{ + Builder::StatementBuilder builder; + builder.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), + Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), + }); + + Statement createTable = builder.Prepare(connection); + REQUIRE_FALSE(createTable.Step()); + REQUIRE(createTable.GetState() == Statement::State::Completed); +} + +void InsertIntoSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) +{ + Builder::StatementBuilder builder; + builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, secondVal); + Statement insert = builder.Prepare(connection); + + REQUIRE_FALSE(insert.Step()); + REQUIRE(insert.GetState() == Statement::State::Completed); +} + +void UpdateSimpleTestTable(Connection& connection, int firstVal, const std::string& secondVal) +{ + Builder::StatementBuilder update; + update.Update(s_tableName).Set().Column(s_firstColumn).Equals(firstVal).Column(s_secondColumn).Equals(secondVal); + update.Execute(connection); +} + +void InsertIntoSimpleTestTableWithNull(Connection& connection, int firstVal) +{ + Builder::StatementBuilder builder; + builder.InsertInto(s_tableName).Columns({ s_firstColumn, s_secondColumn }).Values(firstVal, nullptr); + Statement insert = builder.Prepare(connection); + + REQUIRE_FALSE(insert.Step()); + REQUIRE(insert.GetState() == Statement::State::Completed); +} + +void SelectFromSimpleTestTableOnlyOneRow(Connection& connection, int firstVal, const std::string& secondVal) +{ + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName); + Statement select = builder.Prepare(connection); + + REQUIRE(select.Step()); + REQUIRE(select.GetState() == Statement::State::HasRow); + + int firstRead = select.GetColumn(0); + std::string secondRead = select.GetColumn(1); + + REQUIRE(firstVal == firstRead); + REQUIRE(secondVal == secondRead); + + auto tuple = select.GetRow(); + + REQUIRE(firstVal == std::get<0>(tuple)); + REQUIRE(secondVal == std::get<1>(tuple)); + + REQUIRE_FALSE(select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); + + select.Reset(); + REQUIRE(select.GetState() == Statement::State::Prepared); + + REQUIRE(select.Step()); + REQUIRE(select.GetState() == Statement::State::HasRow); +} + +TEST_CASE("SQLiteWrapperMemoryCreate", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperFileCreateAndReopen", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + int firstVal = 1; + std::string secondVal = "test"; + + // Create the DB and some data + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + // Reopen the DB and read data + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + } +} + +TEST_CASE("SQLiteWrapperSavepointRollback", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Rollback(); + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperSavepointRollbackOnDestruct", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperSavepointCommit", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Savepoint savepoint = Savepoint::Create(connection, "test_savepoint"); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperSavepointReuse", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + int firstVal = 1; + std::string secondVal = "test"; + + // Create the DB and some data + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + // Reopen the DB and update with a single savepoint + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + Savepoint savepoint = Savepoint::Create(connection, s_savepoint); + + firstVal = 2; + secondVal = "test2"; + UpdateSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Commit(); + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + } + + // Reopen the DB and update with a multiple savepoint + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + + { + Savepoint savepoint = Savepoint::Create(connection, s_savepoint); + + firstVal = 3; + secondVal = "test3"; + UpdateSimpleTestTable(connection, firstVal, secondVal); + } + + { + Savepoint savepoint = Savepoint::Create(connection, s_savepoint); + + firstVal = 4; + secondVal = "test4"; + UpdateSimpleTestTable(connection, firstVal, secondVal); + + savepoint.Commit(); + } + } + + { + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + } +} + +TEST_CASE("SQLiteWrapper_EscapeStringForLike", "[sqlitewrapper]") +{ + std::string escape(EscapeCharForLike); + + std::string input = "test"; + std::string output = EscapeStringForLike(input); + REQUIRE(input == output); + + input = EscapeCharForLike; + output = EscapeStringForLike(input); + REQUIRE((input + input) == output); + + input = "%"; + output = EscapeStringForLike(input); + REQUIRE((escape + input) == output); + + input = "_"; + output = EscapeStringForLike(input); + REQUIRE((escape + input) == output); + + input = "%_A_%"; + std::string expected = escape + "%" + escape + "_A" + escape + "_" + escape + "%"; + output = EscapeStringForLike(input); + REQUIRE(expected == output); +} + +TEST_CASE("SQLiteWrapper_BindWithEmbeddedNull", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + secondVal[1] = '\0'; + + REQUIRE_THROWS_HR(InsertIntoSimpleTestTable(connection, firstVal, secondVal), APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL); +} + +TEST_CASE("SQLiteWrapper_PrepareFailure", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(std::string{ s_tableName } + "2").Where(s_firstColumn).Equals(2); + + REQUIRE_THROWS_HR(builder.Prepare(connection), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_ERROR)); +} + +TEST_CASE("SQLiteWrapper_BusyTimeout_None", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + wil::unique_event busy, done; + busy.create(); + done.create(); + + std::thread busyThread([&]() + { + Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + Statement threadStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); + threadStatement.Execute(); + busy.SetEvent(); + done.wait(500); + }); + busyThread.detach(); + + busy.wait(500); + + Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + testConnection.SetBusyTimeout(0ms); + Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); + REQUIRE_THROWS_HR(testStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_BUSY)); + + done.SetEvent(); +} + +TEST_CASE("SQLiteWrapper_BusyTimeout_Some", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + wil::unique_event busy, ready, done; + busy.create(); + ready.create(); + done.create(); + + std::thread busyThread([&]() + { + Connection threadConnection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + Statement threadBeginStatement = Statement::Create(threadConnection, "BEGIN EXCLUSIVE TRANSACTION"); + Statement threadCommitStatement = Statement::Create(threadConnection, "COMMIT"); + threadBeginStatement.Execute(); + busy.SetEvent(); + ready.wait(500); + done.wait(100); + threadCommitStatement.Execute(); + }); + busyThread.detach(); + + busy.wait(500); + + Connection testConnection = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + testConnection.SetBusyTimeout(500ms); + Statement testStatement = Statement::Create(testConnection, "BEGIN EXCLUSIVE TRANSACTION"); + ready.SetEvent(); + testStatement.Execute(); + + done.SetEvent(); +} + +TEST_CASE("SQLiteWrapper_CloseConnectionOnError", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + Builder::StatementBuilder builder; + builder.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int), + Builder::ColumnBuilder(s_secondColumn, Builder::Type::Text), + }); + + Statement createTable = builder.Prepare(connection); + REQUIRE_FALSE(createTable.Step()); + REQUIRE(createTable.GetState() == Statement::State::Completed); + + createTable.Reset(); + REQUIRE_THROWS(createTable.Step(true)); + + // Do anything that needs the connection + REQUIRE_THROWS_HR(connection.GetLastInsertRowID(), APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED); +} + +TEST_CASE("SQLBuilder_SimpleSelectBind", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTable(connection, 3, "3"); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(2); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 2); + REQUIRE(statement.GetColumn(0) == "2"); + + REQUIRE(!statement.Step()); + + Builder::StatementBuilder buildCount; + buildCount.Select(Builder::RowCount).From(s_tableName); + + auto rows = buildCount.Prepare(connection); + + REQUIRE(rows.Step()); + REQUIRE(rows.GetColumn(0) == 3); + + REQUIRE(!rows.Step()); +} + +TEST_CASE("SQLBuilder_SimpleSelectUnbound", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTable(connection, 3, "3"); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_firstColumn).Equals(Builder::Unbound); + + auto statement = builder.Prepare(connection); + + statement.Bind(1, 2); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 2); + REQUIRE(statement.GetColumn(0) == "2"); + + REQUIRE(!statement.Step()); +} + +TEST_CASE("SQLBuilder_SimpleSelectNull", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTableWithNull(connection, 3); + + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).IsNull(); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 3); + REQUIRE(statement.GetColumnIsNull(1)); + + REQUIRE(!statement.Step()); +} + +TEST_CASE("SQLBuilder_SimpleSelectOptional", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + InsertIntoSimpleTestTable(connection, 1, "1"); + InsertIntoSimpleTestTable(connection, 2, "2"); + InsertIntoSimpleTestTableWithNull(connection, 3); + + std::optional secondValue; + + { + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 3); + REQUIRE(statement.GetColumnIsNull(1)); + + REQUIRE(!statement.Step()); + } + + { + secondValue = "2"; + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn, s_secondColumn }).From(s_tableName).Where(s_secondColumn).Equals(secondValue); + + auto statement = builder.Prepare(connection); + + REQUIRE(statement.Step()); + REQUIRE(statement.GetColumn(0) == 2); + REQUIRE(statement.GetColumn(1) == "2"); + + REQUIRE(!statement.Step()); + } +} + +TEST_CASE("SQLBuilder_Update", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + CreateSimpleTestTable(connection); + + int firstVal = 1; + std::string secondVal = "test"; + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); + + firstVal = 2; + secondVal = "testing"; + + UpdateSimpleTestTable(connection, firstVal, secondVal); + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLBuilder_CaseInsensitive", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + Builder::StatementBuilder createTable; + createTable.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Text).CollateNoCase() + }); + + createTable.Execute(connection); + + std::string upperCaseVal = "TEST"; + std::string lowerCaseVal = "test"; + + { + INFO("Insert initial value"); + Builder::StatementBuilder builder; + builder.InsertInto(s_tableName) + .Columns({ s_firstColumn }) + .Values(upperCaseVal); + + builder.Execute(connection); + } + + { + INFO("Retrieve using case-insensitive value"); + Builder::StatementBuilder builder; + builder.Select({ s_firstColumn }).From(s_tableName).Where(s_firstColumn).Equals(lowerCaseVal); + + auto statement = builder.Prepare(connection); + REQUIRE(statement.Step()); + } +} + +TEST_CASE("SQLBuilder_CreateTable", "[sqlbuilder]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int testRun = GENERATE(0, 1, 2, 3, 4, 5, 6, 7); + + bool notNull = ((testRun & 1) != 0); + bool unique = ((testRun & 2) != 0); + bool pk = ((testRun & 4) != 0); + CAPTURE(notNull, unique, pk); + + Builder::StatementBuilder createTable; + createTable.CreateTable(s_tableName).Columns({ + Builder::ColumnBuilder(s_firstColumn, Builder::Type::Int).NotNull(notNull).Unique(unique).PrimaryKey(pk) + }); + + createTable.Execute(connection); + + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_tableName).Columns(s_firstColumn).Values(Builder::Unbound); + + Statement insertStatement = insertBuilder.Prepare(connection); + + { + INFO("Insert NULL"); + insertStatement.Bind(1, nullptr); + + if (notNull) + { + REQUIRE_THROWS_HR(insertStatement.Execute(), MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_NOTNULL)); + } + else + { + insertStatement.Execute(); + } + } + + { + INFO("Insert unique values"); + insertStatement.Reset(); + insertStatement.Bind(1, 1); + insertStatement.Execute(); + + insertStatement.Reset(); + insertStatement.Bind(1, 2); + insertStatement.Execute(); + } + + { + INFO("Insert duplicate values"); + insertStatement.Reset(); + insertStatement.Bind(1, 1); + + if (unique || pk) + { + HRESULT expectedHR = S_OK; + if (pk) + { + expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_PRIMARYKEY); + } + else + { + expectedHR = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, SQLITE_CONSTRAINT_UNIQUE); + } + REQUIRE_THROWS_HR(insertStatement.Execute(), expectedHR); + } + else + { + insertStatement.Execute(); + } + } +} + +TEST_CASE("SQLBuilder_InsertValueBinding", "[sqlbuilder]") +{ + char const* const columns[] = { "a", "b", "c", "d", "e", "f" }; + + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + + { + INFO("Create table"); + Builder::StatementBuilder createTable; + createTable.CreateTable(s_tableName).BeginColumns(); + for (const auto c : columns) + { + createTable.Column(Builder::ColumnBuilder(c, Builder::Type::Int)); + } + createTable.EndColumns(); + createTable.Execute(connection); + } + + { + INFO("Insert values"); + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_tableName).BeginColumns(); + for (const auto c : columns) + { + insertBuilder.Column(c); + } + insertBuilder.EndColumns().Values(0, 1, 2, 3, 4, 5); + insertBuilder.Execute(connection); + } + + { + INFO("Insert values"); + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_tableName).BeginColumns(); + for (const auto c : columns) + { + insertBuilder.Column(c); + } + insertBuilder.EndColumns().BeginValues(); + insertBuilder.Value(5); + insertBuilder.Value(nullptr); + insertBuilder.Value(3); + insertBuilder.Value(std::optional{}); + insertBuilder.Value(std::optional{ 1 }); + insertBuilder.Value(Builder::Unbound); + insertBuilder.EndValues(); + insertBuilder.Execute(connection); + } + + { + INFO("Select values"); + Builder::StatementBuilder selectBuilder; + selectBuilder.Select(); + for (const auto c : columns) + { + selectBuilder.Column(c); + } + selectBuilder.From(s_tableName); + + Statement select = selectBuilder.Prepare(connection); + REQUIRE(select.Step()); + + for (int i = 0; i < ARRAYSIZE(columns); ++i) + { + REQUIRE(i == select.GetColumn(i)); + } + + REQUIRE(select.Step()); + + for (int i = 0; i < ARRAYSIZE(columns); ++i) + { + if (i & 1) + { + REQUIRE(select.GetColumnIsNull(i)); + } + else + { + REQUIRE((5 - i) == select.GetColumn(i)); + } + } + + REQUIRE(!select.Step()); + } +} + +TEST_CASE("SQLiteWrapperTransactionRollback", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Rollback(); + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperTransactionRollbackOnDestruct", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + } + + Statement select = Statement::Create(connection, s_selectFromSimpleTestTableSQL); + REQUIRE(!select.Step()); + REQUIRE(select.GetState() == Statement::State::Completed); +} + +TEST_CASE("SQLiteWrapperTransactionCommit", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", false); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperTransactionImmediate", "[sqlitewrapper]") +{ + Connection connection = Connection::Create(SQLITE_MEMORY_DB_CONNECTION_TARGET, Connection::OpenDisposition::Create); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", true); + + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + transaction.Commit(); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} + +TEST_CASE("SQLiteWrapperTransactionWriteConflict", "[sqlitewrapper]") +{ + TestCommon::TempFile tempFile{ "repolibtest_tempdb"s, ".db"s }; + INFO("Using temporary file named: " << tempFile.GetPath()); + + Connection connection = Connection::Create(tempFile, Connection::OpenDisposition::Create); + connection.SetJournalMode("WAL"); + + int firstVal = 1; + std::string secondVal = "test"; + + CreateSimpleTestTable(connection); + + Connection connection2 = Connection::Create(tempFile, Connection::OpenDisposition::ReadWrite); + std::chrono::milliseconds busyWait = 250ms; + connection2.SetBusyTimeout(busyWait); + + { + Transaction transaction = Transaction::Create(connection, "test_transaction", true); + InsertIntoSimpleTestTable(connection, firstVal, secondVal); + + // Start second transaction + std::chrono::system_clock::time_point start = std::chrono::system_clock::now(); + std::chrono::system_clock::time_point end = start; + try + { + Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); + } + catch (...) + { + end = std::chrono::system_clock::now(); + } + + std::chrono::milliseconds duration = std::chrono::duration_cast(end - start); + REQUIRE(duration >= busyWait); + + transaction.Commit(); + + Transaction transaction2 = Transaction::Create(connection2, "test_transaction2", true); + InsertIntoSimpleTestTable(connection2, firstVal, secondVal); + } + + SelectFromSimpleTestTableOnlyOneRow(connection, firstVal, secondVal); +} diff --git a/src/AppInstallerCLITests/SearchRequestSerializer.cpp b/src/AppInstallerCLITests/SearchRequestSerializer.cpp index 77641ac9f1..910539c4a1 100644 --- a/src/AppInstallerCLITests/SearchRequestSerializer.cpp +++ b/src/AppInstallerCLITests/SearchRequestSerializer.cpp @@ -1,88 +1,88 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Rest::Schema; - -TEST_CASE("SearchRequestSerializer_InclusionsFilters", "[RestSource]") -{ - SearchRequest searchRequest; - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Substring, "Foo.Bar")); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::Substring, "Foo")); - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, MatchType::Exact, "FooBar")); - searchRequest.MaximumResults = 10; - - V1_0::Json::SearchRequestSerializer serializer; - web::json::value actual = serializer.Serialize(searchRequest); - - REQUIRE(!actual.is_null()); - REQUIRE(!actual.has_field(L"FetchAllManifests")); - REQUIRE(actual.at(L"MaximumResults").as_integer() == static_cast(searchRequest.MaximumResults)); - - // Inclusions - web::json::array inclusions = actual.at(L"Inclusions").as_array(); - REQUIRE(inclusions.size() == 2); - REQUIRE(inclusions.at(0).at(L"PackageMatchField").as_string() == L"PackageIdentifier"); - REQUIRE(inclusions.at(1).at(L"PackageMatchField").as_string() == L"PackageName"); - web::json::value requestMatch = inclusions.at(0).at(L"RequestMatch"); - REQUIRE(!requestMatch.is_null()); - REQUIRE(requestMatch.at(L"KeyWord").as_string() == L"Foo.Bar"); - REQUIRE(requestMatch.at(L"MatchType").as_string() == L"Substring"); - - // Filters - web::json::array filters = actual.at(L"Filters").as_array(); - REQUIRE(filters.size() == 1); - REQUIRE(filters.at(0).at(L"PackageMatchField").as_string() == L"Moniker"); - web::json::value requestMatchFilter = filters.at(0).at(L"RequestMatch"); - REQUIRE(!requestMatchFilter.is_null()); - REQUIRE(requestMatchFilter.at(L"KeyWord").as_string() == L"FooBar"); - REQUIRE(requestMatchFilter.at(L"MatchType").as_string() == L"Exact"); -} - -TEST_CASE("SearchRequestSerializer_Query", "[RestSource]") -{ - SearchRequest searchRequest; - searchRequest.Query = RequestMatch(MatchType::Substring, "Foo.Bar"); - - V1_0::Json::SearchRequestSerializer serializer; - web::json::value actual = serializer.Serialize(std::move(searchRequest)); - - REQUIRE(!actual.is_null()); - web::json::value query = actual.at(L"Query"); - REQUIRE(query.at(L"KeyWord").as_string() == L"Foo.Bar"); - REQUIRE(query.at(L"MatchType").as_string() == L"Substring"); -} - -TEST_CASE("SearchRequestSerializer_FetchAllManifests", "[RestSource]") -{ - V1_0::Json::SearchRequestSerializer serializer; - web::json::value actual = serializer.Serialize({}); - - REQUIRE(!actual.is_null()); - REQUIRE(actual.at(L"FetchAllManifests").as_bool()); -} - -TEST_CASE("SearchRequestSerializer_NewFields", "[RestSource]") -{ - SearchRequest searchRequest; - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Substring, "Foo.Bar")); - searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::Substring, "Foo")); - searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Market, MatchType::Exact, "FooBar")); - - V1_0::Json::SearchRequestSerializer serializerV1_0; - web::json::value actual_1_0 = serializerV1_0.Serialize(searchRequest); - REQUIRE(!actual_1_0.is_null()); - REQUIRE(actual_1_0.at(L"Filters").as_array().size() == 0); - - V1_1::Json::SearchRequestSerializer serializerV1_1; - web::json::value actual_1_1 = serializerV1_1.Serialize(searchRequest); - REQUIRE(!actual_1_1.is_null()); - REQUIRE(actual_1_1.at(L"Filters").as_array().size() == 1); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Rest::Schema; + +TEST_CASE("SearchRequestSerializer_InclusionsFilters", "[RestSource]") +{ + SearchRequest searchRequest; + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Substring, "Foo.Bar")); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::Substring, "Foo")); + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Moniker, MatchType::Exact, "FooBar")); + searchRequest.MaximumResults = 10; + + V1_0::Json::SearchRequestSerializer serializer; + web::json::value actual = serializer.Serialize(searchRequest); + + REQUIRE(!actual.is_null()); + REQUIRE(!actual.has_field(L"FetchAllManifests")); + REQUIRE(actual.at(L"MaximumResults").as_integer() == static_cast(searchRequest.MaximumResults)); + + // Inclusions + web::json::array inclusions = actual.at(L"Inclusions").as_array(); + REQUIRE(inclusions.size() == 2); + REQUIRE(inclusions.at(0).at(L"PackageMatchField").as_string() == L"PackageIdentifier"); + REQUIRE(inclusions.at(1).at(L"PackageMatchField").as_string() == L"PackageName"); + web::json::value requestMatch = inclusions.at(0).at(L"RequestMatch"); + REQUIRE(!requestMatch.is_null()); + REQUIRE(requestMatch.at(L"KeyWord").as_string() == L"Foo.Bar"); + REQUIRE(requestMatch.at(L"MatchType").as_string() == L"Substring"); + + // Filters + web::json::array filters = actual.at(L"Filters").as_array(); + REQUIRE(filters.size() == 1); + REQUIRE(filters.at(0).at(L"PackageMatchField").as_string() == L"Moniker"); + web::json::value requestMatchFilter = filters.at(0).at(L"RequestMatch"); + REQUIRE(!requestMatchFilter.is_null()); + REQUIRE(requestMatchFilter.at(L"KeyWord").as_string() == L"FooBar"); + REQUIRE(requestMatchFilter.at(L"MatchType").as_string() == L"Exact"); +} + +TEST_CASE("SearchRequestSerializer_Query", "[RestSource]") +{ + SearchRequest searchRequest; + searchRequest.Query = RequestMatch(MatchType::Substring, "Foo.Bar"); + + V1_0::Json::SearchRequestSerializer serializer; + web::json::value actual = serializer.Serialize(std::move(searchRequest)); + + REQUIRE(!actual.is_null()); + web::json::value query = actual.at(L"Query"); + REQUIRE(query.at(L"KeyWord").as_string() == L"Foo.Bar"); + REQUIRE(query.at(L"MatchType").as_string() == L"Substring"); +} + +TEST_CASE("SearchRequestSerializer_FetchAllManifests", "[RestSource]") +{ + V1_0::Json::SearchRequestSerializer serializer; + web::json::value actual = serializer.Serialize({}); + + REQUIRE(!actual.is_null()); + REQUIRE(actual.at(L"FetchAllManifests").as_bool()); +} + +TEST_CASE("SearchRequestSerializer_NewFields", "[RestSource]") +{ + SearchRequest searchRequest; + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Id, MatchType::Substring, "Foo.Bar")); + searchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::Name, MatchType::Substring, "Foo")); + searchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Market, MatchType::Exact, "FooBar")); + + V1_0::Json::SearchRequestSerializer serializerV1_0; + web::json::value actual_1_0 = serializerV1_0.Serialize(searchRequest); + REQUIRE(!actual_1_0.is_null()); + REQUIRE(actual_1_0.at(L"Filters").as_array().size() == 0); + + V1_1::Json::SearchRequestSerializer serializerV1_1; + web::json::value actual_1_1 = serializerV1_1.Serialize(searchRequest); + REQUIRE(!actual_1_1.is_null()); + REQUIRE(actual_1_1.at(L"Filters").as_array().size() == 1); +} diff --git a/src/AppInstallerCLITests/Settings.cpp b/src/AppInstallerCLITests/Settings.cpp index 9bedce468c..80e28cf025 100644 --- a/src/AppInstallerCLITests/Settings.cpp +++ b/src/AppInstallerCLITests/Settings.cpp @@ -1,309 +1,309 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" - -#include -#include -#include - -using namespace AppInstaller::Runtime; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; - -TEST_CASE("ReadEmptySetting", "[settings]") -{ - StreamDefinition name{ Type::Standard, "nonexistentsetting" }; - - auto result = Stream{ name }.Get(); - REQUIRE(!result); -} - -TEST_CASE("SetAndReadSetting", "[settings]") -{ - StreamDefinition name{ Type::Standard, "testsettingname" }; - std::string value = "This is the test setting value"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); -} - -TEST_CASE("SetAndReadSettingInContainer", "[settings]") -{ - StreamDefinition name{ Type::Standard, "testcontainer/testsettingname" }; - std::string value = "This is the test setting value from inside a container"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); -} - -TEST_CASE("RemoveSetting", "[settings]") -{ - StreamDefinition name{ Type::Standard, "testsettingname" }; - std::string value = "This is the test setting value to be removed"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - { - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); - } - - stream.Remove(); - - auto result = stream.Get(); - REQUIRE(!static_cast(result)); -} - -TEST_CASE("SetAndReadUserFileSetting", "[settings]") -{ - StreamDefinition name{ Type::UserFile, "userfilesetting" }; - std::string value = "This is the test setting value for a user file"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); -} - -TEST_CASE("ReadEmptySecureSetting", "[settings]") -{ - StreamDefinition name{ Type::Secure, "secure_nonexistentsetting" }; - - auto result = Stream{ name }.Get(); - REQUIRE(!result); -} - -TEST_CASE("SetAndReadSecureSetting", "[settings]") -{ - StreamDefinition name{ Type::Secure, "secure_testsettingname" }; - std::string value = "This is the test setting value"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); -} - -TEST_CASE("SetAndReadSecureSettingInContainer", "[settings]") -{ - StreamDefinition name{ Type::Secure, "testcontainer/secure_testsettingname" }; - std::string value = "This is the test setting value from inside a container"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); -} - -TEST_CASE("RemoveSecureSetting", "[settings]") -{ - StreamDefinition name{ Type::Secure, "secure_testsettingname" }; - std::string value = "This is the test setting value to be removed"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - { - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); - } - - stream.Remove(); - - auto result = stream.Get(); - REQUIRE(!static_cast(result)); -} - -TEST_CASE("SetAndReadSecureSetting_SecureDataRemoved", "[settings]") -{ - StreamDefinition name{ Type::Secure, "secure_testsettingname" }; - std::string value = "This is the test setting value"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); - - std::filesystem::remove(GetPathTo(PathName::SecureSettingsForRead) / name.Name); - - REQUIRE_THROWS_HR(stream.Get(), SPAPI_E_FILE_HASH_NOT_IN_CATALOG); -} - -TEST_CASE("SetAndReadSecureSetting_DataTampered", "[settings]") -{ - StreamDefinition name{ Type::Secure, "secure_testsettingname" }; - std::string value = "This is the test setting value"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); - - StreamDefinition insecureName = name; - insecureName.Type = Type::Standard; - - REQUIRE(Stream{ insecureName }.Set("Tampered data")); - - REQUIRE_THROWS_HR(stream.Get(), HRESULT_FROM_WIN32(ERROR_DATA_CHECKSUM_ERROR)); -} - -TEST_CASE("SetChangeAndReadSetting", "[settings]") -{ - StreamDefinition name{ Type::Standard, "testsettingname" }; - std::string value1 = "This is the test setting value1"; - std::string value2 = "This is the test setting value2, which is different"; - std::string value3 = "This is the test setting value3; also different"; - - name.Type = GENERATE(Type::Standard, Type::Secure); - INFO(ToString(name.Type)); - - // Set the value on stream 1 - Stream stream1{ name }; - REQUIRE(stream1.Set(value1)); - - // Read the value on stream 2 to verify - { - Stream stream2{ name }; - - auto result = stream2.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value1 == settingValue); - - // Set the value on stream 2 - REQUIRE(stream2.Set(value2)); - } - - // Attempt to set the value on stream 1 again - REQUIRE(!stream1.Set(value3)); - - // Attempting to set again should still not work - REQUIRE(!stream1.Set(value3)); - - // Ensure that the value remains value 2 - auto result = stream1.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value2 == settingValue); - - // Now that we have read it, we can update it - REQUIRE(stream1.Set(value3)); - - result = stream1.Get(); - REQUIRE(static_cast(result)); - - settingValue = ReadEntireStream(*result); - REQUIRE(value3 == settingValue); -} - -TEST_CASE("AttemptSetOnNewValue", "[settings]") -{ - StreamDefinition name{ Type::Standard, "testsettingname" }; - std::string value1 = "This is the test setting value1"; - std::string value2 = "This is the test setting value2, which is different"; - - name.Type = GENERATE(Type::Standard, Type::Secure); - INFO(ToString(name.Type)); - - // Remove the stream - Stream{ name }.Remove(); - - Stream stream1{ name }; - REQUIRE(!stream1.Get()); - - // Set the value on stream 2 - { - Stream stream2{ name }; - REQUIRE(stream2.Set(value1)); - } - - // Attempt to set the value on stream 1 again - REQUIRE(!stream1.Set(value2)); - - // Attempting to set again should still not work - REQUIRE(!stream1.Set(value2)); - - // Ensure that the value remains value 2 - auto result = stream1.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value1 == settingValue); - - // Now that we have read it, we can update it - REQUIRE(stream1.Set(value2)); - - result = stream1.Get(); - REQUIRE(static_cast(result)); - - settingValue = ReadEntireStream(*result); - REQUIRE(value2 == settingValue); -} - -TEST_CASE("SetAndReadSettingEncrypted", "[settings]") -{ - StreamDefinition name{ Type::Encrypted, "encrypted_test_setting" }; - std::string value = "This is the test setting value"; - - Stream stream{ name }; - REQUIRE(stream.Set(value)); - - auto result = stream.Get(); - REQUIRE(static_cast(result)); - - std::string settingValue = ReadEntireStream(*result); - REQUIRE(value == settingValue); - - // Ensure that the data is encrypted - name.Type = Type::StandardFile; - - Stream streamDirect{ name }; - - auto resultDirect = streamDirect.Get(); - REQUIRE(static_cast(resultDirect)); - - std::string settingValueDirect = ReadEntireStream(*resultDirect); - REQUIRE(value != settingValueDirect); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" + +#include +#include +#include + +using namespace AppInstaller::Runtime; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; + +TEST_CASE("ReadEmptySetting", "[settings]") +{ + StreamDefinition name{ Type::Standard, "nonexistentsetting" }; + + auto result = Stream{ name }.Get(); + REQUIRE(!result); +} + +TEST_CASE("SetAndReadSetting", "[settings]") +{ + StreamDefinition name{ Type::Standard, "testsettingname" }; + std::string value = "This is the test setting value"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); +} + +TEST_CASE("SetAndReadSettingInContainer", "[settings]") +{ + StreamDefinition name{ Type::Standard, "testcontainer/testsettingname" }; + std::string value = "This is the test setting value from inside a container"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); +} + +TEST_CASE("RemoveSetting", "[settings]") +{ + StreamDefinition name{ Type::Standard, "testsettingname" }; + std::string value = "This is the test setting value to be removed"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + { + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); + } + + stream.Remove(); + + auto result = stream.Get(); + REQUIRE(!static_cast(result)); +} + +TEST_CASE("SetAndReadUserFileSetting", "[settings]") +{ + StreamDefinition name{ Type::UserFile, "userfilesetting" }; + std::string value = "This is the test setting value for a user file"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); +} + +TEST_CASE("ReadEmptySecureSetting", "[settings]") +{ + StreamDefinition name{ Type::Secure, "secure_nonexistentsetting" }; + + auto result = Stream{ name }.Get(); + REQUIRE(!result); +} + +TEST_CASE("SetAndReadSecureSetting", "[settings]") +{ + StreamDefinition name{ Type::Secure, "secure_testsettingname" }; + std::string value = "This is the test setting value"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); +} + +TEST_CASE("SetAndReadSecureSettingInContainer", "[settings]") +{ + StreamDefinition name{ Type::Secure, "testcontainer/secure_testsettingname" }; + std::string value = "This is the test setting value from inside a container"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); +} + +TEST_CASE("RemoveSecureSetting", "[settings]") +{ + StreamDefinition name{ Type::Secure, "secure_testsettingname" }; + std::string value = "This is the test setting value to be removed"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + { + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); + } + + stream.Remove(); + + auto result = stream.Get(); + REQUIRE(!static_cast(result)); +} + +TEST_CASE("SetAndReadSecureSetting_SecureDataRemoved", "[settings]") +{ + StreamDefinition name{ Type::Secure, "secure_testsettingname" }; + std::string value = "This is the test setting value"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); + + std::filesystem::remove(GetPathTo(PathName::SecureSettingsForRead) / name.Name); + + REQUIRE_THROWS_HR(stream.Get(), SPAPI_E_FILE_HASH_NOT_IN_CATALOG); +} + +TEST_CASE("SetAndReadSecureSetting_DataTampered", "[settings]") +{ + StreamDefinition name{ Type::Secure, "secure_testsettingname" }; + std::string value = "This is the test setting value"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); + + StreamDefinition insecureName = name; + insecureName.Type = Type::Standard; + + REQUIRE(Stream{ insecureName }.Set("Tampered data")); + + REQUIRE_THROWS_HR(stream.Get(), HRESULT_FROM_WIN32(ERROR_DATA_CHECKSUM_ERROR)); +} + +TEST_CASE("SetChangeAndReadSetting", "[settings]") +{ + StreamDefinition name{ Type::Standard, "testsettingname" }; + std::string value1 = "This is the test setting value1"; + std::string value2 = "This is the test setting value2, which is different"; + std::string value3 = "This is the test setting value3; also different"; + + name.Type = GENERATE(Type::Standard, Type::Secure); + INFO(ToString(name.Type)); + + // Set the value on stream 1 + Stream stream1{ name }; + REQUIRE(stream1.Set(value1)); + + // Read the value on stream 2 to verify + { + Stream stream2{ name }; + + auto result = stream2.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value1 == settingValue); + + // Set the value on stream 2 + REQUIRE(stream2.Set(value2)); + } + + // Attempt to set the value on stream 1 again + REQUIRE(!stream1.Set(value3)); + + // Attempting to set again should still not work + REQUIRE(!stream1.Set(value3)); + + // Ensure that the value remains value 2 + auto result = stream1.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value2 == settingValue); + + // Now that we have read it, we can update it + REQUIRE(stream1.Set(value3)); + + result = stream1.Get(); + REQUIRE(static_cast(result)); + + settingValue = ReadEntireStream(*result); + REQUIRE(value3 == settingValue); +} + +TEST_CASE("AttemptSetOnNewValue", "[settings]") +{ + StreamDefinition name{ Type::Standard, "testsettingname" }; + std::string value1 = "This is the test setting value1"; + std::string value2 = "This is the test setting value2, which is different"; + + name.Type = GENERATE(Type::Standard, Type::Secure); + INFO(ToString(name.Type)); + + // Remove the stream + Stream{ name }.Remove(); + + Stream stream1{ name }; + REQUIRE(!stream1.Get()); + + // Set the value on stream 2 + { + Stream stream2{ name }; + REQUIRE(stream2.Set(value1)); + } + + // Attempt to set the value on stream 1 again + REQUIRE(!stream1.Set(value2)); + + // Attempting to set again should still not work + REQUIRE(!stream1.Set(value2)); + + // Ensure that the value remains value 2 + auto result = stream1.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value1 == settingValue); + + // Now that we have read it, we can update it + REQUIRE(stream1.Set(value2)); + + result = stream1.Get(); + REQUIRE(static_cast(result)); + + settingValue = ReadEntireStream(*result); + REQUIRE(value2 == settingValue); +} + +TEST_CASE("SetAndReadSettingEncrypted", "[settings]") +{ + StreamDefinition name{ Type::Encrypted, "encrypted_test_setting" }; + std::string value = "This is the test setting value"; + + Stream stream{ name }; + REQUIRE(stream.Set(value)); + + auto result = stream.Get(); + REQUIRE(static_cast(result)); + + std::string settingValue = ReadEntireStream(*result); + REQUIRE(value == settingValue); + + // Ensure that the data is encrypted + name.Type = Type::StandardFile; + + Stream streamDirect{ name }; + + auto resultDirect = streamDirect.Get(); + REQUIRE(static_cast(resultDirect)); + + std::string settingValueDirect = ReadEntireStream(*resultDirect); + REQUIRE(value != settingValueDirect); +} diff --git a/src/AppInstallerCLITests/ShowFlow.cpp b/src/AppInstallerCLITests/ShowFlow.cpp index 5a6325935b..f354fe2692 100644 --- a/src/AppInstallerCLITests/ShowFlow.cpp +++ b/src/AppInstallerCLITests/ShowFlow.cpp @@ -1,104 +1,104 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; - -TEST_CASE("ShowFlow_SearchAndShowAppInfo", "[ShowFlow][workflow]") -{ - std::ostringstream showOutput; - TestContext context{ showOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); - - ShowCommand show({}); - show.Execute(context); - INFO(showOutput.str()); - - // Verify AppInfo is printed - REQUIRE(showOutput.str().find("AppInstallerCliTest.TestExeInstaller") != std::string::npos); - REQUIRE(showOutput.str().find("AppInstaller Test Exe Installer") != std::string::npos); - REQUIRE(showOutput.str().find("1.0.0.0") != std::string::npos); - REQUIRE(showOutput.str().find("https://ThisIsNotUsed") != std::string::npos); -} - -TEST_CASE("ShowFlow_SearchAndShowAppVersion", "[ShowFlow][workflow]") -{ - std::ostringstream showOutput; - TestContext context{ showOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); - context.Args.AddArg(Execution::Args::Type::ListVersions); - - ShowCommand show({}); - show.Execute(context); - INFO(showOutput.str()); - - // Verify App version is printed - REQUIRE(showOutput.str().find("1.0.0.0") != std::string::npos); - // No manifest info is printed - REQUIRE(showOutput.str().find(" Download Url: https://ThisIsNotUsed") == std::string::npos); -} - -TEST_CASE("ShowFlow_Dependencies", "[ShowFlow][workflow][dependencies]") -{ - std::ostringstream showOutput; - TestContext context{ showOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-AllDependencyTypes.yaml").GetPath().u8string()); - - ShowCommand show({}); - show.Execute(context); - INFO(showOutput.str()); - - // Verify all types of dependencies are printed - REQUIRE(showOutput.str().find("Dependencies") != std::string::npos); - REQUIRE(showOutput.str().find("WindowsFeaturesDep") != std::string::npos); - REQUIRE(showOutput.str().find("WindowsLibrariesDep") != std::string::npos); - // PackageDep1 has minimum version (1.0), PackageDep2 doesn't (shouldn't show [>=...]) - REQUIRE(showOutput.str().find("Package.Dep1-x64 [>= 1.0]") != std::string::npos); - REQUIRE(showOutput.str().find("Package.Dep2-x64") != std::string::npos); - REQUIRE(showOutput.str().find("Package.Dep2-x64 [") == std::string::npos); - REQUIRE(showOutput.str().find("ExternalDep") != std::string::npos); -} - -TEST_CASE("ShowFlow_InstallerType", "[ShowFlow][workflow]") -{ - std::ostringstream showOutput; - TestContext context{ showOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); - - ShowCommand show({}); - show.Execute(context); - INFO(showOutput.str()); - - // Verify that just the base installed type is shown; - REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerType)) != std::string::npos); - REQUIRE(showOutput.str().find("exe") != std::string::npos); - - // If the base installer is incorrectly shown, an open parenthesis would appear after the effective installer type - REQUIRE(showOutput.str().find("exe (") == std::string::npos); -} - -TEST_CASE("ShowFlow_NestedInstallerType", "[ShowFlow][workflow]") -{ - std::ostringstream showOutput; - TestContext context{ showOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); - - ShowCommand show({}); - show.Execute(context); - INFO(showOutput.str()); - - // Verify that both the effective and base installer types are shown - REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerType)) != std::string::npos); - REQUIRE(showOutput.str().find("exe (zip)") != std::string::npos); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; + +TEST_CASE("ShowFlow_SearchAndShowAppInfo", "[ShowFlow][workflow]") +{ + std::ostringstream showOutput; + TestContext context{ showOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); + + ShowCommand show({}); + show.Execute(context); + INFO(showOutput.str()); + + // Verify AppInfo is printed + REQUIRE(showOutput.str().find("AppInstallerCliTest.TestExeInstaller") != std::string::npos); + REQUIRE(showOutput.str().find("AppInstaller Test Exe Installer") != std::string::npos); + REQUIRE(showOutput.str().find("1.0.0.0") != std::string::npos); + REQUIRE(showOutput.str().find("https://ThisIsNotUsed") != std::string::npos); +} + +TEST_CASE("ShowFlow_SearchAndShowAppVersion", "[ShowFlow][workflow]") +{ + std::ostringstream showOutput; + TestContext context{ showOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForOpenSource(context, CreateTestSource({ TSR::TestQuery_ReturnOne })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestQuery_ReturnOne.Query); + context.Args.AddArg(Execution::Args::Type::ListVersions); + + ShowCommand show({}); + show.Execute(context); + INFO(showOutput.str()); + + // Verify App version is printed + REQUIRE(showOutput.str().find("1.0.0.0") != std::string::npos); + // No manifest info is printed + REQUIRE(showOutput.str().find(" Download Url: https://ThisIsNotUsed") == std::string::npos); +} + +TEST_CASE("ShowFlow_Dependencies", "[ShowFlow][workflow][dependencies]") +{ + std::ostringstream showOutput; + TestContext context{ showOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("Manifest-Good-AllDependencyTypes.yaml").GetPath().u8string()); + + ShowCommand show({}); + show.Execute(context); + INFO(showOutput.str()); + + // Verify all types of dependencies are printed + REQUIRE(showOutput.str().find("Dependencies") != std::string::npos); + REQUIRE(showOutput.str().find("WindowsFeaturesDep") != std::string::npos); + REQUIRE(showOutput.str().find("WindowsLibrariesDep") != std::string::npos); + // PackageDep1 has minimum version (1.0), PackageDep2 doesn't (shouldn't show [>=...]) + REQUIRE(showOutput.str().find("Package.Dep1-x64 [>= 1.0]") != std::string::npos); + REQUIRE(showOutput.str().find("Package.Dep2-x64") != std::string::npos); + REQUIRE(showOutput.str().find("Package.Dep2-x64 [") == std::string::npos); + REQUIRE(showOutput.str().find("ExternalDep") != std::string::npos); +} + +TEST_CASE("ShowFlow_InstallerType", "[ShowFlow][workflow]") +{ + std::ostringstream showOutput; + TestContext context{ showOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + + ShowCommand show({}); + show.Execute(context); + INFO(showOutput.str()); + + // Verify that just the base installed type is shown; + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerType)) != std::string::npos); + REQUIRE(showOutput.str().find("exe") != std::string::npos); + + // If the base installer is incorrectly shown, an open parenthesis would appear after the effective installer type + REQUIRE(showOutput.str().find("exe (") == std::string::npos); +} + +TEST_CASE("ShowFlow_NestedInstallerType", "[ShowFlow][workflow]") +{ + std::ostringstream showOutput; + TestContext context{ showOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Zip_Exe.yaml").GetPath().u8string()); + + ShowCommand show({}); + show.Execute(context); + INFO(showOutput.str()); + + // Verify that both the effective and base installer types are shown + REQUIRE(showOutput.str().find(Resource::LocString(Resource::String::ShowLabelInstallerType)) != std::string::npos); + REQUIRE(showOutput.str().find("exe (zip)") != std::string::npos); +} diff --git a/src/AppInstallerCLITests/Sixel.cpp b/src/AppInstallerCLITests/Sixel.cpp index b3ae9875d5..973c93e21e 100644 --- a/src/AppInstallerCLITests/Sixel.cpp +++ b/src/AppInstallerCLITests/Sixel.cpp @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include - -using namespace AppInstaller::CLI::VirtualTerminal::Sixel; - -void ValidateGetPixel(std::string_view info, UINT_PTR offset, UINT byteCount, UINT_PTR expected) -{ - INFO(info); - REQUIRE(offset < byteCount); - REQUIRE(offset == expected); -} - -TEST_CASE("ImageView_GetPixel", "[sixel]") -{ - UINT width = 20; - UINT height = 20; - UINT stride = 32; - UINT byteCount = height * stride; - BYTE* byteBase = reinterpret_cast(100); - - ImageView view{ width, height, stride, byteCount, byteBase }; - - ValidateGetPixel("No translation", view.GetPixel(3, 17) - byteBase, byteCount, 17 * stride + 3); - - view.Translate(14, 8, true); - ValidateGetPixel("Positive translation (tile)", view.GetPixel(3, 17) - byteBase, byteCount, 9 * stride + 9); - - view.Translate(-14, 8, true); - ValidateGetPixel("Negative translation (tile)", view.GetPixel(3, 17) - byteBase, byteCount, 9 * stride + 17); - - view.Translate(14, -8, false); - REQUIRE(view.GetPixel(3, 17) == nullptr); - ValidateGetPixel("Negative translation (no tile)", view.GetPixel(15, 1) - byteBase, byteCount, 9 * stride + 1); -} - -TEST_CASE("Image_Render", "[sixel]") -{ - Image image{ TestCommon::TestDataFile("notepad.ico") }; - REQUIRE(!image.Render().Get().empty()); - - image.AspectRatio(AspectRatio::ThreeToOne); - image.ColorCount(64); - image.RenderSizeInCells(2, 1); - image.UseRepeatSequence(true); - REQUIRE(!image.Render().Get().empty()); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include + +using namespace AppInstaller::CLI::VirtualTerminal::Sixel; + +void ValidateGetPixel(std::string_view info, UINT_PTR offset, UINT byteCount, UINT_PTR expected) +{ + INFO(info); + REQUIRE(offset < byteCount); + REQUIRE(offset == expected); +} + +TEST_CASE("ImageView_GetPixel", "[sixel]") +{ + UINT width = 20; + UINT height = 20; + UINT stride = 32; + UINT byteCount = height * stride; + BYTE* byteBase = reinterpret_cast(100); + + ImageView view{ width, height, stride, byteCount, byteBase }; + + ValidateGetPixel("No translation", view.GetPixel(3, 17) - byteBase, byteCount, 17 * stride + 3); + + view.Translate(14, 8, true); + ValidateGetPixel("Positive translation (tile)", view.GetPixel(3, 17) - byteBase, byteCount, 9 * stride + 9); + + view.Translate(-14, 8, true); + ValidateGetPixel("Negative translation (tile)", view.GetPixel(3, 17) - byteBase, byteCount, 9 * stride + 17); + + view.Translate(14, -8, false); + REQUIRE(view.GetPixel(3, 17) == nullptr); + ValidateGetPixel("Negative translation (no tile)", view.GetPixel(15, 1) - byteBase, byteCount, 9 * stride + 1); +} + +TEST_CASE("Image_Render", "[sixel]") +{ + Image image{ TestCommon::TestDataFile("notepad.ico") }; + REQUIRE(!image.Render().Get().empty()); + + image.AspectRatio(AspectRatio::ThreeToOne); + image.ColorCount(64); + image.RenderSizeInCells(2, 1); + image.UseRepeatSequence(true); + REQUIRE(!image.Render().Get().empty()); +} diff --git a/src/AppInstallerCLITests/SortParametersResolution.cpp b/src/AppInstallerCLITests/SortParametersResolution.cpp index 0980c960a2..da62504a84 100644 --- a/src/AppInstallerCLITests/SortParametersResolution.cpp +++ b/src/AppInstallerCLITests/SortParametersResolution.cpp @@ -1,213 +1,213 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -// -// Integration tests for SortParameters(Context&) constructor. -// These exercise the full resolution chain: CLI args → user settings → query-aware default. -// Pure sort algorithm tests live in PackageTableSortHelper.cpp. -#include "pch.h" -#include "TestCommon.h" -#include "ExecutionContext.h" -#include "Workflows/PackageTableSortHelper.h" - -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::Settings; -using namespace TestCommon; - -TEST_CASE("ListSort_SortParameters_NoArgs_NoQuery_DefaultsByName", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - - SortParameters params(context); - - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Name); - REQUIRE(params.Direction == SortDirection::Ascending); -} - -TEST_CASE("ListSort_SortParameters_WithQuery_PreservesRelevance", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::Query, "firefox"sv); - - SortParameters params(context); - - REQUIRE(params.Fields.empty()); - REQUIRE_FALSE(params.ShouldSort); -} - -TEST_CASE("ListSort_SortParameters_WithMultiQuery_PreservesRelevance", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::MultiQuery, "fire"sv); - context.Args.AddArg(Args::Type::MultiQuery, "fox"sv); - - SortParameters params(context); - - REQUIRE(params.Fields.empty()); - REQUIRE_FALSE(params.ShouldSort); -} - -TEST_CASE("ListSort_SortParameters_ExplicitSort_OverridesDefault", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::Sort, "id"sv); - - SortParameters params(context); - - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Id); - REQUIRE(params.Direction == SortDirection::Ascending); -} - -TEST_CASE("ListSort_SortParameters_ExplicitSort_MultiField", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::Sort, "source"sv); - context.Args.AddArg(Args::Type::Sort, "name"sv); - - SortParameters params(context); - - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 2); - REQUIRE(params.Fields[0] == SortField::Source); - REQUIRE(params.Fields[1] == SortField::Name); - REQUIRE(params.Direction == SortDirection::Ascending); -} - -TEST_CASE("ListSort_SortParameters_ExplicitSort_Descending", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::Sort, "name"sv); - context.Args.AddArg(Args::Type::SortDescending); - - SortParameters params(context); - - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Name); - REQUIRE(params.Direction == SortDirection::Descending); -} - -TEST_CASE("ListSort_SortParameters_ExplicitRelevance_NoSort", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::Sort, "relevance"sv); - - SortParameters params(context); - - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Relevance); - REQUIRE_FALSE(params.ShouldSort); -} - -TEST_CASE("ListSort_SortParameters_ExplicitSort_OverridesQuery", "[listsort]") -{ - TestUserSettings testSettings; - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::Query, "firefox"sv); - context.Args.AddArg(Args::Type::Sort, "name"sv); - - SortParameters params(context); - - // Explicit --sort overrides query-based relevance preservation - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Name); - REQUIRE(params.Direction == SortDirection::Ascending); -} - -TEST_CASE("ListSort_SortParameters_Settings_SortOrder", "[listsort]") -{ - TestUserSettings testSettings; - testSettings.Set(std::vector{ SortField::Version }); - - std::ostringstream output; - Context context{ output, std::cin }; - - SortParameters params(context); - - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Version); - REQUIRE(params.Direction == SortDirection::Ascending); -} - -TEST_CASE("ListSort_SortParameters_Settings_DescendingDirection", "[listsort]") -{ - TestUserSettings testSettings; - testSettings.Set(std::vector{ SortField::Name }); - testSettings.Set(SortDirection::Descending); - - std::ostringstream output; - Context context{ output, std::cin }; - - SortParameters params(context); - - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Name); - REQUIRE(params.Direction == SortDirection::Descending); -} - -TEST_CASE("ListSort_SortParameters_CLIArgs_OverrideSettings", "[listsort]") -{ - // Settings say sort by version - TestUserSettings testSettings; - testSettings.Set(std::vector{ SortField::Version }); - - std::ostringstream output; - Context context{ output, std::cin }; - // CLI says sort by id - context.Args.AddArg(Args::Type::Sort, "id"sv); - - SortParameters params(context); - - // CLI args win - REQUIRE(params.ShouldSort); - REQUIRE(params.Fields.size() == 1); - REQUIRE(params.Fields[0] == SortField::Id); - REQUIRE(params.Direction == SortDirection::Ascending); -} - -TEST_CASE("ListSort_SortParameters_CLIDirection_OverridesSettings", "[listsort]") -{ - // Settings say descending - TestUserSettings testSettings; - testSettings.Set(SortDirection::Descending); - - std::ostringstream output; - Context context{ output, std::cin }; - context.Args.AddArg(Args::Type::Sort, "name"sv); - context.Args.AddArg(Args::Type::SortAscending); - - SortParameters params(context); - - // CLI --ascending overrides settings descending - REQUIRE(params.Direction == SortDirection::Ascending); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +// +// Integration tests for SortParameters(Context&) constructor. +// These exercise the full resolution chain: CLI args → user settings → query-aware default. +// Pure sort algorithm tests live in PackageTableSortHelper.cpp. +#include "pch.h" +#include "TestCommon.h" +#include "ExecutionContext.h" +#include "Workflows/PackageTableSortHelper.h" + +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Settings; +using namespace TestCommon; + +TEST_CASE("ListSort_SortParameters_NoArgs_NoQuery_DefaultsByName", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + + SortParameters params(context); + + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Name); + REQUIRE(params.Direction == SortDirection::Ascending); +} + +TEST_CASE("ListSort_SortParameters_WithQuery_PreservesRelevance", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::Query, "firefox"sv); + + SortParameters params(context); + + REQUIRE(params.Fields.empty()); + REQUIRE_FALSE(params.ShouldSort); +} + +TEST_CASE("ListSort_SortParameters_WithMultiQuery_PreservesRelevance", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::MultiQuery, "fire"sv); + context.Args.AddArg(Args::Type::MultiQuery, "fox"sv); + + SortParameters params(context); + + REQUIRE(params.Fields.empty()); + REQUIRE_FALSE(params.ShouldSort); +} + +TEST_CASE("ListSort_SortParameters_ExplicitSort_OverridesDefault", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::Sort, "id"sv); + + SortParameters params(context); + + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Id); + REQUIRE(params.Direction == SortDirection::Ascending); +} + +TEST_CASE("ListSort_SortParameters_ExplicitSort_MultiField", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::Sort, "source"sv); + context.Args.AddArg(Args::Type::Sort, "name"sv); + + SortParameters params(context); + + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 2); + REQUIRE(params.Fields[0] == SortField::Source); + REQUIRE(params.Fields[1] == SortField::Name); + REQUIRE(params.Direction == SortDirection::Ascending); +} + +TEST_CASE("ListSort_SortParameters_ExplicitSort_Descending", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::Sort, "name"sv); + context.Args.AddArg(Args::Type::SortDescending); + + SortParameters params(context); + + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Name); + REQUIRE(params.Direction == SortDirection::Descending); +} + +TEST_CASE("ListSort_SortParameters_ExplicitRelevance_NoSort", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::Sort, "relevance"sv); + + SortParameters params(context); + + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Relevance); + REQUIRE_FALSE(params.ShouldSort); +} + +TEST_CASE("ListSort_SortParameters_ExplicitSort_OverridesQuery", "[listsort]") +{ + TestUserSettings testSettings; + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::Query, "firefox"sv); + context.Args.AddArg(Args::Type::Sort, "name"sv); + + SortParameters params(context); + + // Explicit --sort overrides query-based relevance preservation + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Name); + REQUIRE(params.Direction == SortDirection::Ascending); +} + +TEST_CASE("ListSort_SortParameters_Settings_SortOrder", "[listsort]") +{ + TestUserSettings testSettings; + testSettings.Set(std::vector{ SortField::Version }); + + std::ostringstream output; + Context context{ output, std::cin }; + + SortParameters params(context); + + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Version); + REQUIRE(params.Direction == SortDirection::Ascending); +} + +TEST_CASE("ListSort_SortParameters_Settings_DescendingDirection", "[listsort]") +{ + TestUserSettings testSettings; + testSettings.Set(std::vector{ SortField::Name }); + testSettings.Set(SortDirection::Descending); + + std::ostringstream output; + Context context{ output, std::cin }; + + SortParameters params(context); + + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Name); + REQUIRE(params.Direction == SortDirection::Descending); +} + +TEST_CASE("ListSort_SortParameters_CLIArgs_OverrideSettings", "[listsort]") +{ + // Settings say sort by version + TestUserSettings testSettings; + testSettings.Set(std::vector{ SortField::Version }); + + std::ostringstream output; + Context context{ output, std::cin }; + // CLI says sort by id + context.Args.AddArg(Args::Type::Sort, "id"sv); + + SortParameters params(context); + + // CLI args win + REQUIRE(params.ShouldSort); + REQUIRE(params.Fields.size() == 1); + REQUIRE(params.Fields[0] == SortField::Id); + REQUIRE(params.Direction == SortDirection::Ascending); +} + +TEST_CASE("ListSort_SortParameters_CLIDirection_OverridesSettings", "[listsort]") +{ + // Settings say descending + TestUserSettings testSettings; + testSettings.Set(SortDirection::Descending); + + std::ostringstream output; + Context context{ output, std::cin }; + context.Args.AddArg(Args::Type::Sort, "name"sv); + context.Args.AddArg(Args::Type::SortAscending); + + SortParameters params(context); + + // CLI --ascending overrides settings descending + REQUIRE(params.Direction == SortDirection::Ascending); +} diff --git a/src/AppInstallerCLITests/SourceFlow.cpp b/src/AppInstallerCLITests/SourceFlow.cpp index 013d53f652..1bea3bbc92 100644 --- a/src/AppInstallerCLITests/SourceFlow.cpp +++ b/src/AppInstallerCLITests/SourceFlow.cpp @@ -1,184 +1,184 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "TestHooks.h" -#include "TestSettings.h" -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Settings; - -void OverrideForSourceAddWithAgreements(TestContext& context, bool isAddExpected = true) -{ - context.Override({ EnsureRunningAsAdmin, [](TestContext&) - { - } }); - - if (isAddExpected) - { - context.Override({ AddSource, [](TestContext&) - { - } }); - } - - context.Override({ CreateSourceForSourceAdd, [](TestContext& context) - { - auto testSource = std::make_shared(); - testSource->Information.SourceAgreementsIdentifier = "AgreementsIdentifier"; - testSource->Information.SourceAgreements.emplace_back("Agreement Label", "Agreement Text", "https://test"); - testSource->Information.RequiredPackageMatchFields.emplace_back("Market"); - testSource->Information.RequiredQueryParameters.emplace_back("Market"); - context << Workflow::HandleSourceAgreements(Source{ testSource }); - } }); -} - -TEST_CASE("SourceAddFlow_Agreement", "[SourceAddFlow][workflow]") -{ - std::ostringstream sourceAddOutput; - TestContext context{ sourceAddOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForSourceAddWithAgreements(context); - context.Args.AddArg(Execution::Args::Type::SourceName, "TestSource"sv); - context.Args.AddArg(Execution::Args::Type::SourceType, "Microsoft.Test"sv); - context.Args.AddArg(Execution::Args::Type::SourceArg, "TestArg"sv); - context.Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); - - SourceAddCommand sourceAdd({}); - sourceAdd.Execute(context); - INFO(sourceAddOutput.str()); - - // Verify agreements are shown - REQUIRE(sourceAddOutput.str().find("Agreement Label") != std::string::npos); - REQUIRE(sourceAddOutput.str().find("Agreement Text") != std::string::npos); - REQUIRE(sourceAddOutput.str().find("https://test") != std::string::npos); - REQUIRE(sourceAddOutput.str().find(Resource::LocString(Resource::String::SourceAgreementsMarketMessage).get()) != std::string::npos); - - // Verify Installer is called. - REQUIRE(context.GetTerminationHR() == S_OK); -} - -TEST_CASE("SourceAddFlow_Agreement_Prompt_Yes", "[SourceAddFlow][workflow]") -{ - // Accept the agreements by saying "Yes" at the prompt - std::istringstream sourceAddInput{ "y" }; - std::ostringstream sourceAddOutput; - TestContext context{ sourceAddOutput, sourceAddInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForSourceAddWithAgreements(context); - context.Args.AddArg(Execution::Args::Type::SourceName, "TestSource"sv); - context.Args.AddArg(Execution::Args::Type::SourceType, "Microsoft.Test"sv); - context.Args.AddArg(Execution::Args::Type::SourceArg, "TestArg"sv); - - SourceAddCommand sourceAdd({}); - sourceAdd.Execute(context); - INFO(sourceAddOutput.str()); - - // Verify agreements are shown - REQUIRE(sourceAddOutput.str().find("Agreement Label") != std::string::npos); - REQUIRE(sourceAddOutput.str().find("Agreement Text") != std::string::npos); - REQUIRE(sourceAddOutput.str().find("https://test") != std::string::npos); - REQUIRE(sourceAddOutput.str().find(Resource::LocString(Resource::String::SourceAgreementsMarketMessage).get()) != std::string::npos); - - // Verify Installer is called. - REQUIRE(context.GetTerminationHR() == S_OK); -} - -TEST_CASE("SourceAddFlow_Agreement_Prompt_No", "[SourceAddFlow][workflow]") -{ - // Accept the agreements by saying "No" at the prompt - std::istringstream sourceAddInput{ "n" }; - std::ostringstream sourceAddOutput; - TestContext context{ sourceAddOutput, sourceAddInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForSourceAddWithAgreements(context, false); - context.Args.AddArg(Execution::Args::Type::SourceName, "TestSource"sv); - context.Args.AddArg(Execution::Args::Type::SourceType, "Microsoft.Test"sv); - context.Args.AddArg(Execution::Args::Type::SourceArg, "TestArg"sv); - - SourceAddCommand sourceAdd({}); - sourceAdd.Execute(context); - INFO(sourceAddOutput.str()); - - // Verify agreements are shown - REQUIRE(sourceAddOutput.str().find("Agreement Label") != std::string::npos); - REQUIRE(sourceAddOutput.str().find("Agreement Text") != std::string::npos); - REQUIRE(sourceAddOutput.str().find("https://test") != std::string::npos); - REQUIRE(sourceAddOutput.str().find(Resource::LocString(Resource::String::SourceAgreementsMarketMessage).get()) != std::string::npos); - - // Verify Installer is called. - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED); -} - -TEST_CASE("OpenSource_WithCustomHeader", "[OpenSource][CustomHeader]") -{ - SetSetting(Stream::UserSources, R"(Sources:)"sv); - TestHook_ClearSourceFactoryOverrides(); - - SourceDetails details; - details.Name = "restsource"; - details.Type = "Microsoft.Rest"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - - std::string customHeader = "Test custom header in Open source Flow"; - - bool receivedCustomHeader = false; - TestSourceFactory factory{ - [&](const SourceDetails& sd, std::optional header) - { - receivedCustomHeader = header.value() == customHeader; - return std::shared_ptr(new TestSource(sd)); - } }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - TestProgress progress; - AddSource(details, progress); - - std::ostringstream output; - TestContext context{ output, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::Query, "TestQuery"sv); - context.Args.AddArg(Execution::Args::Type::CustomHeader, customHeader); - context.Args.AddArg(Execution::Args::Type::Source, details.Name); - - AppInstaller::CLI::Workflow::OpenSource()(context); - REQUIRE(receivedCustomHeader); -} - -TEST_CASE("SourceResetFlow_ByNameResetsTombstonedDefaultSource", "[SourceResetFlow][workflow]") -{ - SetSetting(Stream::UserSources, R"( -Sources: - - Name: winget - Type: "" - Arg: "" - Data: "" - IsTombstone: true -)"sv); - - std::ostringstream output; - TestContext context{ output, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); - context.Args.AddArg(Execution::Args::Type::SourceName, "winget"sv); - - SourceResetCommand sourceReset({}); - sourceReset.Execute(context); - - INFO(output.str()); - REQUIRE(context.GetTerminationHR() == S_OK); - - auto sources = Source::GetCurrentSources(); - auto winget = std::find_if( - sources.begin(), - sources.end(), - [](const SourceDetails& sd) { return sd.Name == "winget"; }); - REQUIRE(winget != sources.end()); - REQUIRE(winget->Origin == SourceOrigin::Default); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestHooks.h" +#include "TestSettings.h" +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Settings; + +void OverrideForSourceAddWithAgreements(TestContext& context, bool isAddExpected = true) +{ + context.Override({ EnsureRunningAsAdmin, [](TestContext&) + { + } }); + + if (isAddExpected) + { + context.Override({ AddSource, [](TestContext&) + { + } }); + } + + context.Override({ CreateSourceForSourceAdd, [](TestContext& context) + { + auto testSource = std::make_shared(); + testSource->Information.SourceAgreementsIdentifier = "AgreementsIdentifier"; + testSource->Information.SourceAgreements.emplace_back("Agreement Label", "Agreement Text", "https://test"); + testSource->Information.RequiredPackageMatchFields.emplace_back("Market"); + testSource->Information.RequiredQueryParameters.emplace_back("Market"); + context << Workflow::HandleSourceAgreements(Source{ testSource }); + } }); +} + +TEST_CASE("SourceAddFlow_Agreement", "[SourceAddFlow][workflow]") +{ + std::ostringstream sourceAddOutput; + TestContext context{ sourceAddOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForSourceAddWithAgreements(context); + context.Args.AddArg(Execution::Args::Type::SourceName, "TestSource"sv); + context.Args.AddArg(Execution::Args::Type::SourceType, "Microsoft.Test"sv); + context.Args.AddArg(Execution::Args::Type::SourceArg, "TestArg"sv); + context.Args.AddArg(Execution::Args::Type::AcceptSourceAgreements); + + SourceAddCommand sourceAdd({}); + sourceAdd.Execute(context); + INFO(sourceAddOutput.str()); + + // Verify agreements are shown + REQUIRE(sourceAddOutput.str().find("Agreement Label") != std::string::npos); + REQUIRE(sourceAddOutput.str().find("Agreement Text") != std::string::npos); + REQUIRE(sourceAddOutput.str().find("https://test") != std::string::npos); + REQUIRE(sourceAddOutput.str().find(Resource::LocString(Resource::String::SourceAgreementsMarketMessage).get()) != std::string::npos); + + // Verify Installer is called. + REQUIRE(context.GetTerminationHR() == S_OK); +} + +TEST_CASE("SourceAddFlow_Agreement_Prompt_Yes", "[SourceAddFlow][workflow]") +{ + // Accept the agreements by saying "Yes" at the prompt + std::istringstream sourceAddInput{ "y" }; + std::ostringstream sourceAddOutput; + TestContext context{ sourceAddOutput, sourceAddInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForSourceAddWithAgreements(context); + context.Args.AddArg(Execution::Args::Type::SourceName, "TestSource"sv); + context.Args.AddArg(Execution::Args::Type::SourceType, "Microsoft.Test"sv); + context.Args.AddArg(Execution::Args::Type::SourceArg, "TestArg"sv); + + SourceAddCommand sourceAdd({}); + sourceAdd.Execute(context); + INFO(sourceAddOutput.str()); + + // Verify agreements are shown + REQUIRE(sourceAddOutput.str().find("Agreement Label") != std::string::npos); + REQUIRE(sourceAddOutput.str().find("Agreement Text") != std::string::npos); + REQUIRE(sourceAddOutput.str().find("https://test") != std::string::npos); + REQUIRE(sourceAddOutput.str().find(Resource::LocString(Resource::String::SourceAgreementsMarketMessage).get()) != std::string::npos); + + // Verify Installer is called. + REQUIRE(context.GetTerminationHR() == S_OK); +} + +TEST_CASE("SourceAddFlow_Agreement_Prompt_No", "[SourceAddFlow][workflow]") +{ + // Accept the agreements by saying "No" at the prompt + std::istringstream sourceAddInput{ "n" }; + std::ostringstream sourceAddOutput; + TestContext context{ sourceAddOutput, sourceAddInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForSourceAddWithAgreements(context, false); + context.Args.AddArg(Execution::Args::Type::SourceName, "TestSource"sv); + context.Args.AddArg(Execution::Args::Type::SourceType, "Microsoft.Test"sv); + context.Args.AddArg(Execution::Args::Type::SourceArg, "TestArg"sv); + + SourceAddCommand sourceAdd({}); + sourceAdd.Execute(context); + INFO(sourceAddOutput.str()); + + // Verify agreements are shown + REQUIRE(sourceAddOutput.str().find("Agreement Label") != std::string::npos); + REQUIRE(sourceAddOutput.str().find("Agreement Text") != std::string::npos); + REQUIRE(sourceAddOutput.str().find("https://test") != std::string::npos); + REQUIRE(sourceAddOutput.str().find(Resource::LocString(Resource::String::SourceAgreementsMarketMessage).get()) != std::string::npos); + + // Verify Installer is called. + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED); +} + +TEST_CASE("OpenSource_WithCustomHeader", "[OpenSource][CustomHeader]") +{ + SetSetting(Stream::UserSources, R"(Sources:)"sv); + TestHook_ClearSourceFactoryOverrides(); + + SourceDetails details; + details.Name = "restsource"; + details.Type = "Microsoft.Rest"; + details.Arg = "thisIsTheArg"; + details.Data = "thisIsTheData"; + + std::string customHeader = "Test custom header in Open source Flow"; + + bool receivedCustomHeader = false; + TestSourceFactory factory{ + [&](const SourceDetails& sd, std::optional header) + { + receivedCustomHeader = header.value() == customHeader; + return std::shared_ptr(new TestSource(sd)); + } }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + TestProgress progress; + AddSource(details, progress); + + std::ostringstream output; + TestContext context{ output, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::Query, "TestQuery"sv); + context.Args.AddArg(Execution::Args::Type::CustomHeader, customHeader); + context.Args.AddArg(Execution::Args::Type::Source, details.Name); + + AppInstaller::CLI::Workflow::OpenSource()(context); + REQUIRE(receivedCustomHeader); +} + +TEST_CASE("SourceResetFlow_ByNameResetsTombstonedDefaultSource", "[SourceResetFlow][workflow]") +{ + SetSetting(Stream::UserSources, R"( +Sources: + - Name: winget + Type: "" + Arg: "" + Data: "" + IsTombstone: true +)"sv); + + std::ostringstream output; + TestContext context{ output, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); + context.Args.AddArg(Execution::Args::Type::SourceName, "winget"sv); + + SourceResetCommand sourceReset({}); + sourceReset.Execute(context); + + INFO(output.str()); + REQUIRE(context.GetTerminationHR() == S_OK); + + auto sources = Source::GetCurrentSources(); + auto winget = std::find_if( + sources.begin(), + sources.end(), + [](const SourceDetails& sd) { return sd.Name == "winget"; }); + REQUIRE(winget != sources.end()); + REQUIRE(winget->Origin == SourceOrigin::Default); +} diff --git a/src/AppInstallerCLITests/Sources.cpp b/src/AppInstallerCLITests/Sources.cpp index 7d0dfc4c80..730dfa02f2 100644 --- a/src/AppInstallerCLITests/Sources.cpp +++ b/src/AppInstallerCLITests/Sources.cpp @@ -1,1539 +1,1539 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestHooks.h" -#include "TestSettings.h" -#include "TestSource.h" - -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller; -using namespace AppInstaller::Runtime; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; - -// Duplicating here because a change to these values in the product *REALLY* needs to be thought through. -using namespace std::string_literals; -using namespace std::string_view_literals; - -constexpr size_t c_DefaultSourceCount = 3; - -constexpr std::string_view s_SourcesYaml_Sources = "Sources"sv; -constexpr std::string_view s_SourcesYaml_Source_Name = "Name"sv; -constexpr std::string_view s_SourcesYaml_Source_Type = "Type"sv; -constexpr std::string_view s_SourcesYaml_Source_Arg = "Arg"sv; -constexpr std::string_view s_SourcesYaml_Source_Data = "Data"sv; -constexpr std::string_view s_SourcesYaml_Source_TrustLevel = "TrustLevel"sv; -constexpr std::string_view s_SourcesYaml_Source_Explicit = "Explicit"sv; -constexpr std::string_view s_SourcesYaml_Source_LastUpdate = "LastUpdate"sv; - -constexpr std::string_view s_EmptySources = R"( -Sources: -)"sv; - -constexpr std::string_view s_DefaultSourcesTombstoned = R"( -Sources: - - Name: winget - Type: "" - Arg: "" - Data: "" - IsTombstone: true - - Name: msstore - Type: "" - Arg: "" - Data: "" - IsTombstone: true - - Name: winget-font - Type: "" - Arg: "" - Data: "" - IsTombstone: true -)"sv; - -constexpr std::string_view s_SingleSource = R"( -Sources: - - Name: testName - Type: testType - Arg: testArg - Data: testData - IsTombstone: false -)"sv; - -constexpr std::string_view s_SingleSourceOverride = R"( -Sources: - - Name: winget-font - Type: "" - Arg: "" - Data: "" - IsTombstone: false - IsOverride: true - Explicit: false - Priority: 12 -)"sv; - -constexpr std::string_view s_WingetTombstoned = R"( -Sources: - - Name: winget - Type: "" - Arg: "" - Data: "" - IsTombstone: true -)"sv; - -constexpr std::string_view s_WingetDefaultMetadata = R"( -Sources: - - Name: winget - LastUpdate: 100 -)"sv; - -constexpr std::string_view s_WingetFontOverrideMetadata = R"( -Sources: - - Name: winget-font - LastUpdate: 100 -)"sv; - -constexpr std::string_view s_SingleSourceMetadata = R"( -Sources: - - Name: testName - LastUpdate: 100 -)"sv; - -constexpr std::string_view s_SingleSourceMetadataUpdate = R"( -Sources: - - Name: testName - LastUpdate: 101 -)"sv; - -constexpr std::string_view s_DoubleSource = R"( -Sources: - - Name: testName - Type: testType - Arg: testArg - Data: testData - IsTombstone: false - - Name: testName2 - Type: testType - Arg: testArg2 - Data: testData2 - IsTombstone: false -)"sv; - -constexpr std::string_view s_DoubleSourceMetadata = R"( -Sources: - - Name: testName - LastUpdate: 100 - - Name: testName2 - LastUpdate: 200 -)"sv; - -constexpr std::string_view s_ThreeSources = R"( -Sources: - - Name: testName - Type: testType - Arg: testArg - Data: testData - IsTombstone: false - Priority: 1 - - Name: testName2 - Type: testType2 - Arg: testArg2 - Data: testData2 - IsTombstone: false - Priority: 5 - - Name: testName3 - Type: testType3 - Arg: testArg3 - Data: testData3 - IsTombstone: false - Priority: 3 - - Name: winget - Type: "" - Arg: "" - Data: "" - IsTombstone: true - - Name: msstore - Type: "" - Arg: "" - Data: "" - IsTombstone: true - - Name: winget-font - Type: "" - Arg: "" - Data: "" - IsTombstone: true -)"sv; - -constexpr std::string_view s_ThreeSourcesMetadata = R"( -Sources: - - Name: testName - LastUpdate: 0 - - Name: testName2 - LastUpdate: 1 - - Name: testName3 - LastUpdate: 2 -)"sv; - -constexpr std::string_view s_SingleSource_MissingArg = R"( -Sources: - - Name: testName - Type: testType - Data: testData - IsTombstone: false -)"sv; - -constexpr std::string_view s_TwoSource_AggregateSourceTest = R"( -Sources: - - Name: winget - Type: testType - Arg: testArg - Data: testData - IsTombstone: false - - Name: msstore - Type: testType - Arg: testArg - Data: testData - IsTombstone: false -)"sv; - -constexpr std::string_view s_DefaultSourceAsUserSource = R"( -Sources: - - Name: not-winget - Type: Microsoft.PreIndexed.Package - Arg: https://cdn.winget.microsoft.com/cache - Data: Microsoft.Winget.Source_8wekyb3d8bbwe - IsTombstone: false -)"sv; - -constexpr std::string_view s_UserSourceNamedLikeDefault = R"( -Sources: - - Name: winget - Type: testType - Arg: testArg - Data: testData - IsTombstone: false -)"sv; - -constexpr std::string_view s_SingleSource_AllProperties= R"( -Sources: - - Name: testName - Type: testType - Arg: testArg - Data: testData - IsTombstone: false - TrustLevel: 3 - Explicit: true - Priority: 1 -)"sv; - -namespace -{ - // Helper to create a simple source. - struct SourcesTestSource : public TestSource - { - SourcesTestSource() = default; - SourcesTestSource(const SourceDetails& details) - { - Details = details; - } - - static std::shared_ptr Create(const SourceDetails& details) - { - // using return std::make_shared(details); will crash the x86 test during destruction. - return std::shared_ptr(new SourcesTestSource(details)); - } - - SearchResult Search(const SearchRequest&) const override - { - SearchResult result; - PackageMatchFilter testMatchFilter1{ PackageMatchField::Id, MatchType::Exact, "test" }; - PackageMatchFilter testMatchFilter2{ PackageMatchField::Name, MatchType::Exact, "test" }; - PackageMatchFilter testMatchFilter3{ PackageMatchField::Id, MatchType::CaseInsensitive, "test" }; - result.Matches.emplace_back(nullptr, testMatchFilter1); - result.Matches.emplace_back(nullptr, testMatchFilter2); - result.Matches.emplace_back(nullptr, testMatchFilter3); - return result; - } - }; - - // Failing source for use with s_TwoSource_AggregateSourceTest - struct FailingSourcesTestSource : public TestSource - { - static constexpr HRESULT FailingHR = 0xBADDAD0D; - - FailingSourcesTestSource() = default; - FailingSourcesTestSource(const SourceDetails& details) - { - Details = details; - } - - static std::shared_ptr CreateFailWinget(const SourceDetails& details) - { - if (details.Name == "winget") - { - THROW_HR(FailingHR); - } - - return std::shared_ptr(new FailingSourcesTestSource(details)); - } - - static std::shared_ptr CreateFailAll(const SourceDetails&) - { - THROW_HR(FailingHR); - } - }; - - void RequireDefaultSourcesAt(const std::vector& sources, size_t index) - { - REQUIRE(sources.size() >= index + c_DefaultSourceCount); - - for (size_t i = index; i < sources.size(); ++i) - { - INFO(i); - REQUIRE(sources[i].Origin == SourceOrigin::Default); - } - } -} - - -TEST_CASE("RepoSources_UserSettingDoesNotExist", "[sources]") -{ - RemoveSetting(Stream::UserSources); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - RequireDefaultSourcesAt(sources, 0); -} - -TEST_CASE("RepoSources_EmptySourcesList", "[sources]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - RequireDefaultSourcesAt(sources, 0); -} - -TEST_CASE("RepoSources_DefaultSourcesTombstoned", "[sources]") -{ - SetSetting(Stream::UserSources, s_DefaultSourcesTombstoned); - - std::vector sources = GetSources(); - REQUIRE(sources.empty()); -} - - -TEST_CASE("RepoSources_DefaultSourceOverride", "[sources]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - - // Default font has explicit to true. - // Font is at index 2 as it is the third one added. - auto beforeOverride = GetSources(); - REQUIRE(beforeOverride.size() == c_DefaultSourceCount); - REQUIRE(beforeOverride[2].Name == "winget-font"); - REQUIRE(beforeOverride[2].Arg == "https://cdn.winget.microsoft.com/fonts"); - REQUIRE(beforeOverride[2].Data == "Microsoft.Winget.Fonts.Source_8wekyb3d8bbwe"); - REQUIRE(beforeOverride[2].Type == "Microsoft.PreIndexed.Package"); - REQUIRE(beforeOverride[2].Origin == SourceOrigin::Default); - REQUIRE(beforeOverride[2].Explicit == true); - REQUIRE(beforeOverride[2].Priority == 0); - - SetSetting(Stream::UserSources, s_SingleSourceOverride); - auto afterOverride = GetSources(); - - // The override will change the index value as the Default will be replaced by the override. - // User sources have higher priority so the override will be at index 0. - // We expect the same count, and the Name, Arg, Data, and Type properties to all be identical. - // Only the name is defined in the override setting so all others should be properly populated. - REQUIRE(afterOverride.size() == c_DefaultSourceCount); - REQUIRE(afterOverride[0].Name == beforeOverride[2].Name); - REQUIRE(afterOverride[0].Arg == beforeOverride[2].Arg); - REQUIRE(afterOverride[0].Data == beforeOverride[2].Data); - REQUIRE(afterOverride[0].Type == beforeOverride[2].Type); - - // The only properties we expect to be different are the Origin, which is now User, and Explicit. - REQUIRE(afterOverride[0].Origin == SourceOrigin::User); - REQUIRE(afterOverride[0].Explicit == false); - REQUIRE(afterOverride[0].Priority == 12); -} - -TEST_CASE("RepoSources_SingleSource", "[sources]") -{ - SetSetting(Stream::UserSources, s_SingleSource); - RemoveSetting(Stream::SourcesMetadata); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - REQUIRE(sources[0].Name == "testName"); - REQUIRE(sources[0].Type == "testType"); - REQUIRE(sources[0].Arg == "testArg"); - REQUIRE(sources[0].Data == "testData"); - REQUIRE(sources[0].Origin == SourceOrigin::User); - REQUIRE(sources[0].LastUpdateTime == ConvertUnixEpochToSystemClock(0)); - - RequireDefaultSourcesAt(sources, 1); -} - -TEST_CASE("RepoSources_SingleSource_AllProperties", "[sources]") -{ - SetSetting(Stream::UserSources, s_SingleSource_AllProperties); - RemoveSetting(Stream::SourcesMetadata); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - REQUIRE(sources[0].Name == "testName"); - REQUIRE(sources[0].Type == "testType"); - REQUIRE(sources[0].Arg == "testArg"); - REQUIRE(sources[0].Data == "testData"); - REQUIRE(sources[0].Origin == SourceOrigin::User); - REQUIRE(sources[0].Explicit == true); - REQUIRE(sources[0].Priority == 1); - REQUIRE(WI_IsFlagSet(sources[0].TrustLevel, SourceTrustLevel::Trusted)); - REQUIRE(WI_IsFlagSet(sources[0].TrustLevel, SourceTrustLevel::StoreOrigin)); - REQUIRE(sources[0].LastUpdateTime == ConvertUnixEpochToSystemClock(0)); - - RequireDefaultSourcesAt(sources, 1); -} - -TEST_CASE("RepoSources_ThreeSources", "[sources]") -{ - SetSetting(Stream::UserSources, s_ThreeSources); - SetSetting(Stream::SourcesMetadata, s_ThreeSourcesMetadata); - - const char* suffixStrings[3] = { "", "2", "3" }; - size_t suffixUnsorted[3] = { 0, 1, 2 }; - size_t suffixPrioritySorted[3] = { 1, 2, 0 }; - size_t* suffix = nullptr; - std::unique_ptr override; - - SECTION("Unsorted") - { - suffix = suffixUnsorted; - } - SECTION("Priority Sorted") - { - override = std::make_unique(ExperimentalFeature::Feature::SourcePriority); - suffix = suffixPrioritySorted; - } - - std::vector sources = GetSources(); - REQUIRE(sources.size() == 3); - - for (size_t index = 0; index < 3; ++index) - { - size_t i = suffix[index]; - - INFO("Source #" << index << " [" << i << "]"); - REQUIRE(sources[index].Name == "testName"s + suffixStrings[i]); - REQUIRE(sources[index].Type == "testType"s + suffixStrings[i]); - REQUIRE(sources[index].Arg == "testArg"s + suffixStrings[i]); - REQUIRE(sources[index].Data == "testData"s + suffixStrings[i]); - REQUIRE(sources[index].LastUpdateTime == ConvertUnixEpochToSystemClock(i)); - REQUIRE(sources[index].Origin == SourceOrigin::User); - } -} - -TEST_CASE("RepoSources_InvalidYAML", "[sources]") -{ - SetSetting(Stream::UserSources, "Name: Value : BAD"); - - REQUIRE_NOTHROW(GetSources()); -} - -TEST_CASE("RepoSources_MissingField", "[sources]") -{ - SetSetting(Stream::UserSources, s_SingleSource_MissingArg); - - REQUIRE_NOTHROW(GetSources()); -} - -TEST_CASE("RepoSources_AddSource", "[sources]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - SourceDetails details; - details.Name = "thisIsTheName"; - details.Type = "thisIsTheType"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - details.TrustLevel = Repository::SourceTrustLevel::None; - details.Explicit = false; - details.Priority = 42; - - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - ProgressCallback progress; - AddSource(details, progress); - - REQUIRE(addCalledOnFactory); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - REQUIRE(sources[0].Name == details.Name); - REQUIRE(sources[0].Type == details.Type); - REQUIRE(sources[0].Arg == details.Arg); - REQUIRE(sources[0].Data == details.Data); - REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); - REQUIRE(sources[0].Origin == SourceOrigin::User); - REQUIRE(sources[0].TrustLevel == details.TrustLevel); - REQUIRE(sources[0].Explicit == details.Explicit); - REQUIRE(sources[0].Priority == details.Priority); - - RequireDefaultSourcesAt(sources, 1); -} - -TEST_CASE("RepoSources_AddMultipleSources", "[sources]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - - SourceDetails details; - details.Name = "thisIsTheName"; - details.Type = "thisIsTheType"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - - const char* suffix[2] = { "", "2" }; - - TestSourceFactory factory1{ SourcesTestSource::Create }; - factory1.OnAdd = [&](SourceDetails& sd) { sd.Data = details.Data; }; - TestHook_SetSourceFactoryOverride(details.Type, factory1); - - ProgressCallback progress; - AddSource(details, progress); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - REQUIRE(sources[0].Name == details.Name); - REQUIRE(sources[0].Type == details.Type); - REQUIRE(sources[0].Arg == details.Arg); - REQUIRE(sources[0].Data == details.Data); - REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); - REQUIRE(sources[0].Origin == SourceOrigin::User); - - RequireDefaultSourcesAt(sources, 1); - - SourceDetails details2; - details2.Name = details.Name + suffix[1]; - details2.Type = details.Type + suffix[1]; - details2.Arg = details.Arg + suffix[1]; - details2.Data = details.Data + suffix[1]; - TestSourceFactory factory2{ SourcesTestSource::Create }; - factory2.OnAdd = [&](SourceDetails& sd) { sd.Data = details2.Data; }; - TestHook_SetSourceFactoryOverride(details2.Type, factory2); - - AddSource(details2, progress); - - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 2); - - for (size_t i = 0; i < 2; ++i) - { - INFO("Source #" << i); - REQUIRE(sources[i].Name == details.Name + suffix[i]); - REQUIRE(sources[i].Type == details.Type + suffix[i]); - REQUIRE(sources[i].Arg == details.Arg + suffix[i]); - REQUIRE(sources[i].Data == details.Data + suffix[i]); - REQUIRE(sources[i].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); - REQUIRE(sources[i].Origin == SourceOrigin::User); - } - - RequireDefaultSourcesAt(sources, 2); -} - -TEST_CASE("RepoSources_UpdateSource", "[sources]") -{ - using namespace std::chrono_literals; - - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - SourceDetails details; - details.Name = "thisIsTheName"; - details.Type = "thisIsTheType"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - ProgressCallback progress; - AddSource(details, progress); - - REQUIRE(addCalledOnFactory); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - REQUIRE(sources[0].Name == details.Name); - REQUIRE(sources[0].Type == details.Type); - REQUIRE(sources[0].Arg == details.Arg); - REQUIRE(sources[0].Data == details.Data); - REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); - REQUIRE(sources[0].Origin == SourceOrigin::User); - - RequireDefaultSourcesAt(sources, 1); - - // Reset for a call to update - bool updateCalledOnFactory = false; - auto now = std::chrono::system_clock::now(); - factory.OnUpdate = [&](const SourceDetails&) { updateCalledOnFactory = true; }; - - UpdateSource(details.Name, progress); - - REQUIRE(updateCalledOnFactory); - - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - REQUIRE(sources[0].Name == details.Name); - REQUIRE(sources[0].Type == details.Type); - REQUIRE(sources[0].Arg == details.Arg); - REQUIRE(sources[0].Data == details.Data); - REQUIRE((now - sources[0].LastUpdateTime) < 1s); -} - -TEST_CASE("RepoSources_UpdateSourceRetries", "[sources]") -{ - using namespace std::chrono_literals; - - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - SourceDetails details; - details.Name = "thisIsTheName"; - details.Type = "thisIsTheType"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails& sd) { sd.Data = details.Data; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - ProgressCallback progress; - AddSource(details, progress); - - // Reset for a call to update - bool updateShouldThrow = false; - bool updateCalledOnFactoryAgain = false; - factory.OnUpdate = [&](const SourceDetails&) - { - if (updateShouldThrow) - { - updateShouldThrow = false; - THROW_HR(E_ACCESSDENIED); - } - updateCalledOnFactoryAgain = true; - }; - - UpdateSource(details.Name, progress); - - REQUIRE(updateCalledOnFactoryAgain); -} - -TEST_CASE("RepoSources_RemoveSource", "[sources]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - SourceDetails details; - details.Name = "thisIsTheName"; - details.Type = "thisIsTheType"; - details.Arg = "thisIsTheArg"; - details.Data = "thisIsTheData"; - - bool removeCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - ProgressCallback progress; - AddSource(details, progress); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - RemoveSource(details.Name, progress); - - REQUIRE(removeCalledOnFactory); - - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); -} - -TEST_CASE("RepoSources_RemoveDefaultSource", "[sources]") -{ - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - REQUIRE(sources[0].Origin == SourceOrigin::Default); - - bool removeCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; }; - TestHook_SetSourceFactoryOverride(sources[0].Type, factory); - - ProgressCallback progress; - - RemoveSource(sources[0].Name, progress); - - REQUIRE(removeCalledOnFactory); - - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount - 1); -} - -TEST_CASE("RepoSources_UpdateOnOpen", "[sources]") -{ - using namespace std::chrono_literals; - - TestHook_ClearSourceFactoryOverrides(); - - std::string name = "testName"; - std::string type = "testType"; - std::string arg = "testArg"; - std::string data = "testData"; - - bool updateCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnUpdate = [&](const SourceDetails&) { updateCalledOnFactory = true; }; - factory.ShouldUpdateBeforeOpenResult = true; - TestHook_SetSourceFactoryOverride(type, factory); - - SetSetting(Stream::UserSources, s_SingleSource); - - ProgressCallback progress; - auto source = OpenSource(name, progress); - - REQUIRE(updateCalledOnFactory); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - - REQUIRE(sources[0].Name == name); - REQUIRE(sources[0].Type == type); - REQUIRE(sources[0].Arg == arg); - REQUIRE(sources[0].Data == data); - REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); -} - -TEST_CASE("RepoSources_DropSourceByName", "[sources]") -{ - SetSetting(Stream::UserSources, s_ThreeSources); - SetSetting(Stream::SourcesMetadata, s_ThreeSourcesMetadata); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == 3); - - DropSource("testName"); - - sources = GetSources(); - REQUIRE(sources.size() == 2); - - const char* suffix[2] = { "2", "3" }; - - for (size_t i = 0; i < 2; ++i) - { - INFO("Source #" << i); - REQUIRE(sources[i].Name == "testName"s + suffix[i]); - REQUIRE(sources[i].Type == "testType"s + suffix[i]); - REQUIRE(sources[i].Arg == "testArg"s + suffix[i]); - REQUIRE(sources[i].Data == "testData"s + suffix[i]); - REQUIRE(sources[i].LastUpdateTime == ConvertUnixEpochToSystemClock(i + 1)); - REQUIRE(sources[i].Origin == SourceOrigin::User); - } -} - -TEST_CASE("RepoSources_DropAllSources", "[sources]") -{ - SetSetting(Stream::UserSources, s_ThreeSources); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == 3); - - DropSource({}); - - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - REQUIRE(sources[0].Origin == SourceOrigin::Default); -} - - -TEST_CASE("RepoSources_DropDefaultSourceByName", "[sources]") -{ - RemoveSetting(Stream::UserSources); - SetSetting(Stream::SourcesMetadata, s_WingetDefaultMetadata); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - // Verify the winget source has non-zero metadata before reset - auto wingetBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); - REQUIRE(wingetBefore != sources.end()); - REQUIRE(wingetBefore->LastUpdateTime == ConvertUnixEpochToSystemClock(100)); - - DropSource("winget"); - - // Source should still be present as a Default source - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - auto wingetAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); - REQUIRE(wingetAfter != sources.end()); - REQUIRE(wingetAfter->Origin == SourceOrigin::Default); - // Metadata should be cleared - REQUIRE(wingetAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); -} - -TEST_CASE("RepoSources_ResetTombstonedDefaultSourceByName", "[sources]") -{ - SetSetting(Stream::UserSources, s_WingetTombstoned); - SetSetting(Stream::SourcesMetadata, s_WingetDefaultMetadata); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount - 1); - - auto wingetBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); - REQUIRE(wingetBefore == sources.end()); - - DropSource("winget"); - - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - auto wingetAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); - REQUIRE(wingetAfter != sources.end()); - REQUIRE(wingetAfter->Origin == SourceOrigin::Default); - REQUIRE(wingetAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); -} - -TEST_CASE("RepoSources_DropDefaultSourceOverrideByName", "[sources]") -{ - SetSetting(Stream::UserSources, s_SingleSourceOverride); - SetSetting(Stream::SourcesMetadata, s_WingetFontOverrideMetadata); - - std::vector sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - // winget-font should be present as a User (override) source with metadata - auto fontBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget-font"; }); - REQUIRE(fontBefore != sources.end()); - REQUIRE(fontBefore->Origin == SourceOrigin::User); - REQUIRE(fontBefore->LastUpdateTime == ConvertUnixEpochToSystemClock(100)); - - DropSource("winget-font"); - - // winget-font should be restored to Default with cleared metadata - sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - auto fontAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget-font"; }); - REQUIRE(fontAfter != sources.end()); - REQUIRE(fontAfter->Origin == SourceOrigin::Default); - REQUIRE(fontAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); -} - -TEST_CASE("RepoSources_SearchAcrossMultipleSources", "[sources]") -{ - TestHook_ClearSourceFactoryOverrides(); - TestSourceFactory factory{ SourcesTestSource::Create }; - TestHook_SetSourceFactoryOverride("testType", factory); - - SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest); - - ProgressCallback progress; - auto source = OpenSource("", progress); - - SearchRequest request; - auto result = source.Search(request); - REQUIRE(result.Matches.size() == 6); - REQUIRE_FALSE(result.Truncated); - // matches are sorted in expected order - REQUIRE((result.Matches[0].MatchCriteria.Type == MatchType::Exact && result.Matches[0].MatchCriteria.Field == PackageMatchField::Id)); - REQUIRE((result.Matches[1].MatchCriteria.Type == MatchType::Exact && result.Matches[1].MatchCriteria.Field == PackageMatchField::Id)); - REQUIRE((result.Matches[2].MatchCriteria.Type == MatchType::Exact && result.Matches[2].MatchCriteria.Field == PackageMatchField::Name)); - REQUIRE((result.Matches[3].MatchCriteria.Type == MatchType::Exact && result.Matches[3].MatchCriteria.Field == PackageMatchField::Name)); - REQUIRE((result.Matches[4].MatchCriteria.Type == MatchType::CaseInsensitive && result.Matches[4].MatchCriteria.Field == PackageMatchField::Id)); - REQUIRE((result.Matches[5].MatchCriteria.Type == MatchType::CaseInsensitive && result.Matches[5].MatchCriteria.Field == PackageMatchField::Id)); - - // when truncate required - request.MaximumResults = 3; - result = source.Search(request); - REQUIRE(result.Matches.size() == 3); - REQUIRE(result.Truncated); - // matches are sorted in expected order - REQUIRE((result.Matches[0].MatchCriteria.Type == MatchType::Exact && result.Matches[0].MatchCriteria.Field == PackageMatchField::Id)); - REQUIRE((result.Matches[1].MatchCriteria.Type == MatchType::Exact && result.Matches[1].MatchCriteria.Field == PackageMatchField::Id)); - REQUIRE((result.Matches[2].MatchCriteria.Type == MatchType::Exact && result.Matches[2].MatchCriteria.Field == PackageMatchField::Name)); -} - -TEST_CASE("RepoSources_GroupPolicy_DefaultSource", "[sources][groupPolicy]") -{ - WHEN("Default source is disabled") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::DefaultSource, PolicyState::Disabled); - - SECTION("Get source") - { - // Listing the sources should not return the default. - SetSetting(Stream::UserSources, s_EmptySources); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount - 1); - } - SECTION("Add default source") - { - // We should not be able to add the default source manually. - SetSetting(Stream::UserSources, s_EmptySources); - - ProgressCallback progress; - SourceDetails details; - details.Name = "winget"; - details.Type = "Microsoft.PreIndexed.Package"; - details.Arg = "https://cdn.winget.microsoft.com/cache"; - REQUIRE_POLICY_EXCEPTION( - AddSource(details, progress), - TogglePolicy::Policy::DefaultSource); - } - SECTION("Ignore default source from user") - { - // We should ignore any existing user source that is the same as the default. - SetSetting(Stream::UserSources, s_DefaultSourceAsUserSource); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount - 1); - } - SECTION("Add same-name source from user") - { - // We should allow adding sources with the same name as the default but - // pointing somewhere else. - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - SourceDetails details; - details.Name = "winget"; - details.Type = "someType"; - details.Arg = "notWingetRealArg"; - details.Data = "someData"; - - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - ProgressCallback progress; - AddSource(details, progress); - - REQUIRE(addCalledOnFactory); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - REQUIRE(sources[0].Name == details.Name); - REQUIRE(sources[0].Type == details.Type); - REQUIRE(sources[0].Arg == details.Arg); - REQUIRE(sources[0].Data == details.Data); - REQUIRE(sources[0].Origin == SourceOrigin::User); - } - SECTION("Allow same name source from user") - { - // We should respect existing user sources with the same name. - // We should allow adding sources with the same name as the default but - // pointing somewhere else. - SetSetting(Stream::UserSources, s_UserSourceNamedLikeDefault); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - REQUIRE(sources[0].Name == "winget"); - REQUIRE(sources[0].Type == "testType"); - REQUIRE(sources[0].Arg == "testArg"); - REQUIRE(sources[0].Data == "testData"); - REQUIRE(sources[0].Origin == SourceOrigin::User); - } - } - - WHEN("Default source is enabled") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::DefaultSource, PolicyState::Enabled); - - SECTION("Remove source is blocked") - { - // We should not be able to remove the default source. - SetSetting(Stream::UserSources, s_EmptySources); - - ProgressCallback progress; - REQUIRE_POLICY_EXCEPTION( - RemoveSource("winget", progress), - TogglePolicy::Policy::DefaultSource); - } - SECTION("Tombstone is overridden") - { - // We should ignore if the default source was already deleted. - SetSetting(Stream::UserSources, s_DefaultSourcesTombstoned); - - auto sources = GetSources(); - REQUIRE(sources.size() == 1); - REQUIRE(sources[0].Name == "winget"); - REQUIRE(sources[0].Origin == SourceOrigin::Default); - } - SECTION("Same name source is overridden") - { - // We should ignore existing user sources with the same name as the default. - SetSetting(Stream::UserSources, s_UserSourceNamedLikeDefault); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - - REQUIRE(sources[1].Name == "winget"); - REQUIRE(sources[1].Arg == "https://cdn.winget.microsoft.com/cache"); - REQUIRE(sources[1].Origin == SourceOrigin::Default); - } - } -} - -TEST_CASE("RepoSources_GroupPolicy_AdditionalSources", "[sources][groupPolicy]") -{ - WHEN("Additional sources are enabled") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::AdditionalSources, PolicyState::Enabled); - - SECTION("Additional sources are listed") - { - // Getting the current sources should list the additional sources. - std::vector policySources; - const std::string suffix[3] = { "", "2", "3" }; - for (size_t i = 0; i < 3; ++i) - { - SourceFromPolicy source; - source.Name = "name" + suffix[i]; - source.Type = "type" + suffix[i]; - source.Arg = "arg" + suffix[i]; - source.Data = "data" + suffix[i]; - source.Identifier = "id" + suffix[i]; - policySources.emplace_back(std::move(source)); - } - - policies.SetValue(policySources); - SetSetting(Stream::UserSources, s_EmptySources); - - auto sources = GetSources(); - - // The source list includes the default source - REQUIRE(sources.size() == policySources.size() + c_DefaultSourceCount); - REQUIRE(sources.back().Origin == SourceOrigin::Default); - - for (size_t i = 0; i < policySources.size(); ++i) - { - REQUIRE(sources[i].Name == policySources[i].Name); - REQUIRE(sources[i].Type == policySources[i].Type); - REQUIRE(sources[i].Arg == policySources[i].Arg); - REQUIRE(sources[i].Data == policySources[i].Data); - REQUIRE(sources[i].Identifier == policySources[i].Identifier); - REQUIRE(sources[i].Origin == SourceOrigin::GroupPolicy); - } - } - SECTION("Same-name user source is overridden") - { - // User sources with the same name as an additional source are ignored. - SourceFromPolicy policySource; - policySource.Name = "testName"; - policySource.Type = "notTestType"; - policySource.Arg = "notTestArg"; - policySource.Data = "notTestData"; - policySource.Identifier = "notTestId"; - - policies.SetValue({ policySource }); - SetSetting(Stream::UserSources, s_SingleSource); - - auto sources = GetSources(); - - // The source list includes the default source - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - REQUIRE(sources[1].Origin == SourceOrigin::Default); - - REQUIRE(sources[0].Name == policySource.Name); - REQUIRE(sources[0].Type == policySource.Type); - REQUIRE(sources[0].Arg == policySource.Arg); - REQUIRE(sources[0].Data == policySource.Data); - REQUIRE(sources[0].Identifier == policySource.Identifier); - REQUIRE(sources[0].Origin == SourceOrigin::GroupPolicy); - } - SECTION("Cannot remove additional source") - { - // An additional source cannot be removed. - SourceFromPolicy policySource; - policySource.Name = "name"; - policySource.Type = "type"; - policySource.Arg = "arg"; - policySource.Data = "data"; - policySource.Identifier = "id"; - - bool removeCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; }; - TestHook_SetSourceFactoryOverride(policySource.Type, factory); - - policies.SetValue({ policySource }); - SetSetting(Stream::UserSources, s_EmptySources); - - ProgressCallback progress; - REQUIRE_POLICY_EXCEPTION( - RemoveSource(policySource.Name, progress), - TogglePolicy::Policy::AdditionalSources); - REQUIRE_FALSE(removeCalledOnFactory); - } - SECTION("Additional source overrides default") - { - // An additional source with the same name as a default overrides it. - SourceFromPolicy policySource; - policySource.Name = "winget"; - policySource.Type = "notDefaultType"; - policySource.Arg = "notDefaultArg"; - policySource.Data = "notDefaultData"; - policySource.Identifier = "notDefaultId"; - - policies.SetValue({ policySource }); - SetSetting(Stream::UserSources, s_EmptySources); - - auto sources = GetSources(); - - REQUIRE(sources.size() == c_DefaultSourceCount); - REQUIRE(sources[0].Name == policySource.Name); - REQUIRE(sources[0].Type == policySource.Type); - REQUIRE(sources[0].Arg == policySource.Arg); - REQUIRE(sources[0].Data == policySource.Data); - REQUIRE(sources[0].Identifier == policySource.Identifier); - REQUIRE(sources[0].Origin == SourceOrigin::GroupPolicy); - } - } -} - -TEST_CASE("RepoSources_GroupPolicy_AllowedSources", "[sources][groupPolicy]") -{ - WHEN("Allowed sources are enabled") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::AllowedSources, PolicyState::Enabled); - - SECTION("Add allowed source") - { - // We should be able to add sources in the allow list. - SourceFromPolicy policySource; - policySource.Name = "testName"; - policySource.Type = "testType"; - policySource.Arg = "testArg"; - policySource.Data = "testData"; - policySource.Identifier = "testId"; - - policies.SetValue({ policySource }); - SetSetting(Stream::UserSources, s_EmptySources); - TestHook_ClearSourceFactoryOverrides(); - - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails& sd) - { - addCalledOnFactory = true; - sd.Data = policySource.Data; - sd.Identifier = policySource.Identifier; - }; - TestHook_SetSourceFactoryOverride(policySource.Type, factory); - - ProgressCallback progress; - SourceDetails details; - details.Name = policySource.Name; - details.Type = policySource.Type; - details.Arg = policySource.Arg; - AddSource(details, progress); - - REQUIRE(addCalledOnFactory); - - // The source list includes the default source - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount + 1); - REQUIRE(sources[1].Origin == SourceOrigin::Default); - - REQUIRE(sources[0].Name == policySource.Name); - REQUIRE(sources[0].Type == policySource.Type); - REQUIRE(sources[0].Arg == policySource.Arg); - REQUIRE(sources[0].Data == policySource.Data); - REQUIRE(sources[0].Identifier == policySource.Identifier); - REQUIRE(sources[0].Origin == SourceOrigin::User); - } - SECTION("Cannot add non-allowed source") - { - // We should not be allowed to add anything not matching the allow list. - SourceFromPolicy policySource; - policySource.Name = "testName"; - policySource.Type = "testType"; - policySource.Arg = "testArg"; - policySource.Data = "testData"; - policySource.Identifier = "testId"; - - policies.SetValue({ policySource }); - SetSetting(Stream::UserSources, s_EmptySources); - - ProgressCallback progress; - SourceDetails details; - details.Name = "notAllowed"; - details.Type = "type"; - details.Arg = "arg"; - - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - REQUIRE_POLICY_EXCEPTION( - AddSource(details, progress), - TogglePolicy::Policy::AllowedSources); - REQUIRE_FALSE(addCalledOnFactory); - } - } - - WHEN("Allowed sources are disabled") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::AllowedSources, PolicyState::Disabled); - - SECTION("Cannot add any source") - { - SetSetting(Stream::UserSources, s_EmptySources); - - ProgressCallback progress; - SourceDetails details; - details.Name = "name"; - details.Type = "type"; - details.Arg = "arg"; - - bool addCalledOnFactory = false; - TestSourceFactory factory{ SourcesTestSource::Create }; - factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - REQUIRE_POLICY_EXCEPTION( - AddSource(details, progress), - TogglePolicy::Policy::AllowedSources); - REQUIRE_FALSE(addCalledOnFactory); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - REQUIRE(sources[0].Origin == SourceOrigin::Default); - } - SECTION("Existing sources are ignored") - { - SetSetting(Stream::UserSources, s_SingleSource); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - REQUIRE(sources[0].Origin == SourceOrigin::Default); - } - } -} - -TEST_CASE("RepoSources_OpenMultipleWithSingleFailure", "[sources]") -{ - TestHook_ClearSourceFactoryOverrides(); - TestSourceFactory factory{ FailingSourcesTestSource::CreateFailWinget }; - TestHook_SetSourceFactoryOverride("testType", factory); - - SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest); - - ProgressCallback progress; - auto result = OpenSource("", progress); - - REQUIRE(result); - - SearchResult searchResult = result.Search({}); - - REQUIRE(searchResult.Failures.size() == 1); - - HRESULT openFailure = S_OK; - try - { - std::rethrow_exception(searchResult.Failures[0].Exception); - } - catch (const wil::ResultException& re) - { - openFailure = re.GetErrorCode(); - } - catch (...) {} - - REQUIRE(openFailure == FailingSourcesTestSource::FailingHR); -} - -TEST_CASE("RepoSources_OpenMultipleWithTotalFailure", "[sources]") -{ - TestHook_ClearSourceFactoryOverrides(); - TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll }; - TestHook_SetSourceFactoryOverride("testType", factory); - - SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest); - - ProgressCallback progress; - REQUIRE_THROWS_HR(OpenSource("", progress), APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES); -} - -TEST_CASE("RepoSources_UpdateSettingsDuringAction_SourcesUpdate", "[sources]") -{ - SetSetting(Stream::UserSources, s_SingleSource); - SetSetting(Stream::SourcesMetadata, s_SingleSourceMetadata); - - std::string userSourcesUpdate{ s_DoubleSource }; - std::string sourcesMetadataUpdate{ s_DoubleSourceMetadata }; - - std::string singleSourceName = "testName"; - std::string doubleSourceName = "testName2"; - - std::string unusedSourceName = "unusedName"; - std::string unusedSourceArg = "unusedArg"; - std::string testSourceType = "testType"; - - TestHook_ClearSourceFactoryOverrides(); - TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll }; - auto settingsUpdate = [&](const AppInstaller::Repository::SourceDetails&) - { - SetSetting(Stream::UserSources, userSourcesUpdate); - SetSetting(Stream::SourcesMetadata, sourcesMetadataUpdate); - }; - factory.OnAdd = settingsUpdate; - factory.OnUpdate = settingsUpdate; - factory.OnRemove = settingsUpdate; - TestHook_SetSourceFactoryOverride(testSourceType, factory); - - ProgressCallback progress; - - SECTION("Add") - { - SourceDetails addedSource; - addedSource.Name = unusedSourceName; - addedSource.Type = testSourceType; - addedSource.Arg = unusedSourceArg; - AddSource(addedSource, progress); - - auto sources = GetSources(); - REQUIRE(sources.size() == 3 + c_DefaultSourceCount); - - REQUIRE(sources[0].Name == singleSourceName); - REQUIRE(sources[1].Name == doubleSourceName); - REQUIRE(sources[2].Name == addedSource.Name); - } - SECTION("Add conflicting") - { - SourceDetails addedSource; - addedSource.Name = doubleSourceName; - addedSource.Type = testSourceType; - addedSource.Arg = unusedSourceArg; - REQUIRE_THROWS_HR(AddSource(addedSource, progress), APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); - } - SECTION("Update") - { - UpdateSource(singleSourceName, progress); - - auto sources = GetSources(); - REQUIRE(sources.size() == 2 + c_DefaultSourceCount); - - REQUIRE(sources[0].Name == singleSourceName); - REQUIRE(sources[1].Name == doubleSourceName); - } - SECTION("Remove") - { - RemoveSource(singleSourceName, progress); - - auto sources = GetSources(); - REQUIRE(sources.size() == 1 + c_DefaultSourceCount); - - REQUIRE(sources[0].Name == doubleSourceName); - } - SECTION("Remove already removed") - { - userSourcesUpdate = s_EmptySources; - sourcesMetadataUpdate = s_EmptySources; - - RemoveSource(singleSourceName, progress); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - } -} - -TEST_CASE("RepoSources_UpdateSettingsDuringAction_MetadataUpdate", "[sources]") -{ - SetSetting(Stream::UserSources, s_SingleSource); - SetSetting(Stream::SourcesMetadata, s_SingleSourceMetadata); - - std::string sourcesMetadataUpdate{ s_SingleSourceMetadataUpdate }; - int64_t updateTime = 101; - - std::string singleSourceName = "testName"; - std::string doubleSourceName = "testName2"; - - std::string unusedSourceName = "unusedName"; - std::string unusedSourceArg = "unusedArg"; - std::string testSourceType = "testType"; - - TestHook_ClearSourceFactoryOverrides(); - TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll }; - auto settingsUpdate = [&](const AppInstaller::Repository::SourceDetails&) - { - SetSetting(Stream::SourcesMetadata, sourcesMetadataUpdate); - }; - factory.OnAdd = settingsUpdate; - factory.OnUpdate = settingsUpdate; - factory.OnRemove = settingsUpdate; - TestHook_SetSourceFactoryOverride(testSourceType, factory); - - ProgressCallback progress; - - SECTION("Add") - { - SourceDetails addedSource; - addedSource.Name = unusedSourceName; - addedSource.Type = testSourceType; - addedSource.Arg = unusedSourceArg; - AddSource(addedSource, progress); - - auto sources = GetSources(); - REQUIRE(sources.size() == 2 + c_DefaultSourceCount); - - REQUIRE(sources[0].Name == singleSourceName); - REQUIRE(ConvertSystemClockToUnixEpoch(sources[0].LastUpdateTime) == updateTime); - REQUIRE(sources[1].Name == addedSource.Name); - } - SECTION("Update") - { - UpdateSource(singleSourceName, progress); - - auto sources = GetSources(); - REQUIRE(sources.size() == 1 + c_DefaultSourceCount); - - REQUIRE(sources[0].Name == singleSourceName); - REQUIRE(ConvertSystemClockToUnixEpoch(sources[0].LastUpdateTime) > updateTime); - } - SECTION("Remove") - { - RemoveSource(singleSourceName, progress); - - auto sources = GetSources(); - REQUIRE(sources.size() == c_DefaultSourceCount); - } -} - -TEST_CASE("RepoSources_RestoringWellKnownSource", "[sources]") -{ - TestHook_ClearSourceFactoryOverrides(); - RemoveSetting(Stream::UserSources); - - Source storeSource{ WellKnownSource::MicrosoftStore }; - SourceDetails details = storeSource.GetDetails(); - REQUIRE(!details.CertificatePinningConfiguration.IsEmpty()); - - TestSourceFactory factory{ SourcesTestSource::Create }; - TestHook_SetSourceFactoryOverride(details.Type, factory); - - ProgressCallback progress; - - REQUIRE(storeSource.Remove(progress)); - - Source storeAfterRemove{ details.Name }; - REQUIRE(!storeAfterRemove); - - SECTION("with well known name") - { - Source addStoreBack{ details.Name, details.Arg, details.Type, Repository::SourceTrustLevel::None, {} }; - REQUIRE(addStoreBack.Add(progress)); - - Source storeAfterAdd{ details.Name }; - REQUIRE(storeAfterAdd); - REQUIRE(!storeAfterAdd.GetDetails().CertificatePinningConfiguration.IsEmpty()); - } - - SECTION("with different name") - { - std::string newName = details.Name + "_new"; - Source addStoreBack{ newName, details.Arg, details.Type, Repository::SourceTrustLevel::None, {} }; - REQUIRE(addStoreBack.Add(progress)); - - Source storeAfterAdd{ newName }; - REQUIRE(storeAfterAdd); - REQUIRE(storeAfterAdd.GetDetails().CertificatePinningConfiguration.IsEmpty()); - } -} - -TEST_CASE("RepoSources_GroupPolicy_BypassCertificatePinningForMicrosoftStore", "[sources][groupPolicy]") -{ - TestHook_ClearSourceFactoryOverrides(); - - SECTION("Not configured") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::NotConfigured); - Source source(WellKnownSource::MicrosoftStore); - REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); - } - - SECTION("Enabled") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Enabled); - Source source(WellKnownSource::MicrosoftStore); - REQUIRE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); - } - - SECTION("Disabled") - { - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Disabled); - Source source(WellKnownSource::MicrosoftStore); - REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); - } -} - -TEST_CASE("RepoSources_BuiltInDesktopFrameworkSourceAlwaysCreatable", "[sources]") -{ - Source source(WellKnownSource::DesktopFrameworks); - REQUIRE(source); -} - -TEST_CASE("RepoSources_MicrosoftStore_CertificatePinningLifetimeCheck", "[sources]") -{ - TestHook_ClearSourceFactoryOverrides(); - - GroupPolicyTestOverride policies; - policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Disabled); - Source source(WellKnownSource::MicrosoftStore); - REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); - - // The configuration's remaining lifetime is the *maximum* of the remaining lifetimes of the individual chains. - // A chain's remaining lifetime is the *minimum* of the remaining lifetimes of the individual certificates. - // A certificate's remaining lifetime is a value between 0.0 and 1.0 that is the ratio of remaining valid time to total valid time. - - // The goal of this test is to warn when the pinning configuration may be in danger of expiration; either via certificate validity or - // more likely by renewals causing the pinning to reject the new, correct certificates. It operates in percentage lifetime to normalize - // the values across the chain. - INFO("If this test has failed, the pinning certificates may be nearing expiration and should be investigated."); - double lifetimePercentage = source.GetDetails().CertificatePinningConfiguration.GetRemainingLifetimePercentage(); - REQUIRE(lifetimePercentage > 0.25); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestHooks.h" +#include "TestSettings.h" +#include "TestSource.h" + +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller; +using namespace AppInstaller::Runtime; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; + +// Duplicating here because a change to these values in the product *REALLY* needs to be thought through. +using namespace std::string_literals; +using namespace std::string_view_literals; + +constexpr size_t c_DefaultSourceCount = 3; + +constexpr std::string_view s_SourcesYaml_Sources = "Sources"sv; +constexpr std::string_view s_SourcesYaml_Source_Name = "Name"sv; +constexpr std::string_view s_SourcesYaml_Source_Type = "Type"sv; +constexpr std::string_view s_SourcesYaml_Source_Arg = "Arg"sv; +constexpr std::string_view s_SourcesYaml_Source_Data = "Data"sv; +constexpr std::string_view s_SourcesYaml_Source_TrustLevel = "TrustLevel"sv; +constexpr std::string_view s_SourcesYaml_Source_Explicit = "Explicit"sv; +constexpr std::string_view s_SourcesYaml_Source_LastUpdate = "LastUpdate"sv; + +constexpr std::string_view s_EmptySources = R"( +Sources: +)"sv; + +constexpr std::string_view s_DefaultSourcesTombstoned = R"( +Sources: + - Name: winget + Type: "" + Arg: "" + Data: "" + IsTombstone: true + - Name: msstore + Type: "" + Arg: "" + Data: "" + IsTombstone: true + - Name: winget-font + Type: "" + Arg: "" + Data: "" + IsTombstone: true +)"sv; + +constexpr std::string_view s_SingleSource = R"( +Sources: + - Name: testName + Type: testType + Arg: testArg + Data: testData + IsTombstone: false +)"sv; + +constexpr std::string_view s_SingleSourceOverride = R"( +Sources: + - Name: winget-font + Type: "" + Arg: "" + Data: "" + IsTombstone: false + IsOverride: true + Explicit: false + Priority: 12 +)"sv; + +constexpr std::string_view s_WingetTombstoned = R"( +Sources: + - Name: winget + Type: "" + Arg: "" + Data: "" + IsTombstone: true +)"sv; + +constexpr std::string_view s_WingetDefaultMetadata = R"( +Sources: + - Name: winget + LastUpdate: 100 +)"sv; + +constexpr std::string_view s_WingetFontOverrideMetadata = R"( +Sources: + - Name: winget-font + LastUpdate: 100 +)"sv; + +constexpr std::string_view s_SingleSourceMetadata = R"( +Sources: + - Name: testName + LastUpdate: 100 +)"sv; + +constexpr std::string_view s_SingleSourceMetadataUpdate = R"( +Sources: + - Name: testName + LastUpdate: 101 +)"sv; + +constexpr std::string_view s_DoubleSource = R"( +Sources: + - Name: testName + Type: testType + Arg: testArg + Data: testData + IsTombstone: false + - Name: testName2 + Type: testType + Arg: testArg2 + Data: testData2 + IsTombstone: false +)"sv; + +constexpr std::string_view s_DoubleSourceMetadata = R"( +Sources: + - Name: testName + LastUpdate: 100 + - Name: testName2 + LastUpdate: 200 +)"sv; + +constexpr std::string_view s_ThreeSources = R"( +Sources: + - Name: testName + Type: testType + Arg: testArg + Data: testData + IsTombstone: false + Priority: 1 + - Name: testName2 + Type: testType2 + Arg: testArg2 + Data: testData2 + IsTombstone: false + Priority: 5 + - Name: testName3 + Type: testType3 + Arg: testArg3 + Data: testData3 + IsTombstone: false + Priority: 3 + - Name: winget + Type: "" + Arg: "" + Data: "" + IsTombstone: true + - Name: msstore + Type: "" + Arg: "" + Data: "" + IsTombstone: true + - Name: winget-font + Type: "" + Arg: "" + Data: "" + IsTombstone: true +)"sv; + +constexpr std::string_view s_ThreeSourcesMetadata = R"( +Sources: + - Name: testName + LastUpdate: 0 + - Name: testName2 + LastUpdate: 1 + - Name: testName3 + LastUpdate: 2 +)"sv; + +constexpr std::string_view s_SingleSource_MissingArg = R"( +Sources: + - Name: testName + Type: testType + Data: testData + IsTombstone: false +)"sv; + +constexpr std::string_view s_TwoSource_AggregateSourceTest = R"( +Sources: + - Name: winget + Type: testType + Arg: testArg + Data: testData + IsTombstone: false + - Name: msstore + Type: testType + Arg: testArg + Data: testData + IsTombstone: false +)"sv; + +constexpr std::string_view s_DefaultSourceAsUserSource = R"( +Sources: + - Name: not-winget + Type: Microsoft.PreIndexed.Package + Arg: https://cdn.winget.microsoft.com/cache + Data: Microsoft.Winget.Source_8wekyb3d8bbwe + IsTombstone: false +)"sv; + +constexpr std::string_view s_UserSourceNamedLikeDefault = R"( +Sources: + - Name: winget + Type: testType + Arg: testArg + Data: testData + IsTombstone: false +)"sv; + +constexpr std::string_view s_SingleSource_AllProperties= R"( +Sources: + - Name: testName + Type: testType + Arg: testArg + Data: testData + IsTombstone: false + TrustLevel: 3 + Explicit: true + Priority: 1 +)"sv; + +namespace +{ + // Helper to create a simple source. + struct SourcesTestSource : public TestSource + { + SourcesTestSource() = default; + SourcesTestSource(const SourceDetails& details) + { + Details = details; + } + + static std::shared_ptr Create(const SourceDetails& details) + { + // using return std::make_shared(details); will crash the x86 test during destruction. + return std::shared_ptr(new SourcesTestSource(details)); + } + + SearchResult Search(const SearchRequest&) const override + { + SearchResult result; + PackageMatchFilter testMatchFilter1{ PackageMatchField::Id, MatchType::Exact, "test" }; + PackageMatchFilter testMatchFilter2{ PackageMatchField::Name, MatchType::Exact, "test" }; + PackageMatchFilter testMatchFilter3{ PackageMatchField::Id, MatchType::CaseInsensitive, "test" }; + result.Matches.emplace_back(nullptr, testMatchFilter1); + result.Matches.emplace_back(nullptr, testMatchFilter2); + result.Matches.emplace_back(nullptr, testMatchFilter3); + return result; + } + }; + + // Failing source for use with s_TwoSource_AggregateSourceTest + struct FailingSourcesTestSource : public TestSource + { + static constexpr HRESULT FailingHR = 0xBADDAD0D; + + FailingSourcesTestSource() = default; + FailingSourcesTestSource(const SourceDetails& details) + { + Details = details; + } + + static std::shared_ptr CreateFailWinget(const SourceDetails& details) + { + if (details.Name == "winget") + { + THROW_HR(FailingHR); + } + + return std::shared_ptr(new FailingSourcesTestSource(details)); + } + + static std::shared_ptr CreateFailAll(const SourceDetails&) + { + THROW_HR(FailingHR); + } + }; + + void RequireDefaultSourcesAt(const std::vector& sources, size_t index) + { + REQUIRE(sources.size() >= index + c_DefaultSourceCount); + + for (size_t i = index; i < sources.size(); ++i) + { + INFO(i); + REQUIRE(sources[i].Origin == SourceOrigin::Default); + } + } +} + + +TEST_CASE("RepoSources_UserSettingDoesNotExist", "[sources]") +{ + RemoveSetting(Stream::UserSources); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + RequireDefaultSourcesAt(sources, 0); +} + +TEST_CASE("RepoSources_EmptySourcesList", "[sources]") +{ + SetSetting(Stream::UserSources, s_EmptySources); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + RequireDefaultSourcesAt(sources, 0); +} + +TEST_CASE("RepoSources_DefaultSourcesTombstoned", "[sources]") +{ + SetSetting(Stream::UserSources, s_DefaultSourcesTombstoned); + + std::vector sources = GetSources(); + REQUIRE(sources.empty()); +} + + +TEST_CASE("RepoSources_DefaultSourceOverride", "[sources]") +{ + SetSetting(Stream::UserSources, s_EmptySources); + + // Default font has explicit to true. + // Font is at index 2 as it is the third one added. + auto beforeOverride = GetSources(); + REQUIRE(beforeOverride.size() == c_DefaultSourceCount); + REQUIRE(beforeOverride[2].Name == "winget-font"); + REQUIRE(beforeOverride[2].Arg == "https://cdn.winget.microsoft.com/fonts"); + REQUIRE(beforeOverride[2].Data == "Microsoft.Winget.Fonts.Source_8wekyb3d8bbwe"); + REQUIRE(beforeOverride[2].Type == "Microsoft.PreIndexed.Package"); + REQUIRE(beforeOverride[2].Origin == SourceOrigin::Default); + REQUIRE(beforeOverride[2].Explicit == true); + REQUIRE(beforeOverride[2].Priority == 0); + + SetSetting(Stream::UserSources, s_SingleSourceOverride); + auto afterOverride = GetSources(); + + // The override will change the index value as the Default will be replaced by the override. + // User sources have higher priority so the override will be at index 0. + // We expect the same count, and the Name, Arg, Data, and Type properties to all be identical. + // Only the name is defined in the override setting so all others should be properly populated. + REQUIRE(afterOverride.size() == c_DefaultSourceCount); + REQUIRE(afterOverride[0].Name == beforeOverride[2].Name); + REQUIRE(afterOverride[0].Arg == beforeOverride[2].Arg); + REQUIRE(afterOverride[0].Data == beforeOverride[2].Data); + REQUIRE(afterOverride[0].Type == beforeOverride[2].Type); + + // The only properties we expect to be different are the Origin, which is now User, and Explicit. + REQUIRE(afterOverride[0].Origin == SourceOrigin::User); + REQUIRE(afterOverride[0].Explicit == false); + REQUIRE(afterOverride[0].Priority == 12); +} + +TEST_CASE("RepoSources_SingleSource", "[sources]") +{ + SetSetting(Stream::UserSources, s_SingleSource); + RemoveSetting(Stream::SourcesMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + REQUIRE(sources[0].Name == "testName"); + REQUIRE(sources[0].Type == "testType"); + REQUIRE(sources[0].Arg == "testArg"); + REQUIRE(sources[0].Data == "testData"); + REQUIRE(sources[0].Origin == SourceOrigin::User); + REQUIRE(sources[0].LastUpdateTime == ConvertUnixEpochToSystemClock(0)); + + RequireDefaultSourcesAt(sources, 1); +} + +TEST_CASE("RepoSources_SingleSource_AllProperties", "[sources]") +{ + SetSetting(Stream::UserSources, s_SingleSource_AllProperties); + RemoveSetting(Stream::SourcesMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + REQUIRE(sources[0].Name == "testName"); + REQUIRE(sources[0].Type == "testType"); + REQUIRE(sources[0].Arg == "testArg"); + REQUIRE(sources[0].Data == "testData"); + REQUIRE(sources[0].Origin == SourceOrigin::User); + REQUIRE(sources[0].Explicit == true); + REQUIRE(sources[0].Priority == 1); + REQUIRE(WI_IsFlagSet(sources[0].TrustLevel, SourceTrustLevel::Trusted)); + REQUIRE(WI_IsFlagSet(sources[0].TrustLevel, SourceTrustLevel::StoreOrigin)); + REQUIRE(sources[0].LastUpdateTime == ConvertUnixEpochToSystemClock(0)); + + RequireDefaultSourcesAt(sources, 1); +} + +TEST_CASE("RepoSources_ThreeSources", "[sources]") +{ + SetSetting(Stream::UserSources, s_ThreeSources); + SetSetting(Stream::SourcesMetadata, s_ThreeSourcesMetadata); + + const char* suffixStrings[3] = { "", "2", "3" }; + size_t suffixUnsorted[3] = { 0, 1, 2 }; + size_t suffixPrioritySorted[3] = { 1, 2, 0 }; + size_t* suffix = nullptr; + std::unique_ptr override; + + SECTION("Unsorted") + { + suffix = suffixUnsorted; + } + SECTION("Priority Sorted") + { + override = std::make_unique(ExperimentalFeature::Feature::SourcePriority); + suffix = suffixPrioritySorted; + } + + std::vector sources = GetSources(); + REQUIRE(sources.size() == 3); + + for (size_t index = 0; index < 3; ++index) + { + size_t i = suffix[index]; + + INFO("Source #" << index << " [" << i << "]"); + REQUIRE(sources[index].Name == "testName"s + suffixStrings[i]); + REQUIRE(sources[index].Type == "testType"s + suffixStrings[i]); + REQUIRE(sources[index].Arg == "testArg"s + suffixStrings[i]); + REQUIRE(sources[index].Data == "testData"s + suffixStrings[i]); + REQUIRE(sources[index].LastUpdateTime == ConvertUnixEpochToSystemClock(i)); + REQUIRE(sources[index].Origin == SourceOrigin::User); + } +} + +TEST_CASE("RepoSources_InvalidYAML", "[sources]") +{ + SetSetting(Stream::UserSources, "Name: Value : BAD"); + + REQUIRE_NOTHROW(GetSources()); +} + +TEST_CASE("RepoSources_MissingField", "[sources]") +{ + SetSetting(Stream::UserSources, s_SingleSource_MissingArg); + + REQUIRE_NOTHROW(GetSources()); +} + +TEST_CASE("RepoSources_AddSource", "[sources]") +{ + SetSetting(Stream::UserSources, s_EmptySources); + TestHook_ClearSourceFactoryOverrides(); + + SourceDetails details; + details.Name = "thisIsTheName"; + details.Type = "thisIsTheType"; + details.Arg = "thisIsTheArg"; + details.Data = "thisIsTheData"; + details.TrustLevel = Repository::SourceTrustLevel::None; + details.Explicit = false; + details.Priority = 42; + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + ProgressCallback progress; + AddSource(details, progress); + + REQUIRE(addCalledOnFactory); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + REQUIRE(sources[0].Name == details.Name); + REQUIRE(sources[0].Type == details.Type); + REQUIRE(sources[0].Arg == details.Arg); + REQUIRE(sources[0].Data == details.Data); + REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); + REQUIRE(sources[0].Origin == SourceOrigin::User); + REQUIRE(sources[0].TrustLevel == details.TrustLevel); + REQUIRE(sources[0].Explicit == details.Explicit); + REQUIRE(sources[0].Priority == details.Priority); + + RequireDefaultSourcesAt(sources, 1); +} + +TEST_CASE("RepoSources_AddMultipleSources", "[sources]") +{ + SetSetting(Stream::UserSources, s_EmptySources); + + SourceDetails details; + details.Name = "thisIsTheName"; + details.Type = "thisIsTheType"; + details.Arg = "thisIsTheArg"; + details.Data = "thisIsTheData"; + + const char* suffix[2] = { "", "2" }; + + TestSourceFactory factory1{ SourcesTestSource::Create }; + factory1.OnAdd = [&](SourceDetails& sd) { sd.Data = details.Data; }; + TestHook_SetSourceFactoryOverride(details.Type, factory1); + + ProgressCallback progress; + AddSource(details, progress); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + REQUIRE(sources[0].Name == details.Name); + REQUIRE(sources[0].Type == details.Type); + REQUIRE(sources[0].Arg == details.Arg); + REQUIRE(sources[0].Data == details.Data); + REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); + REQUIRE(sources[0].Origin == SourceOrigin::User); + + RequireDefaultSourcesAt(sources, 1); + + SourceDetails details2; + details2.Name = details.Name + suffix[1]; + details2.Type = details.Type + suffix[1]; + details2.Arg = details.Arg + suffix[1]; + details2.Data = details.Data + suffix[1]; + TestSourceFactory factory2{ SourcesTestSource::Create }; + factory2.OnAdd = [&](SourceDetails& sd) { sd.Data = details2.Data; }; + TestHook_SetSourceFactoryOverride(details2.Type, factory2); + + AddSource(details2, progress); + + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 2); + + for (size_t i = 0; i < 2; ++i) + { + INFO("Source #" << i); + REQUIRE(sources[i].Name == details.Name + suffix[i]); + REQUIRE(sources[i].Type == details.Type + suffix[i]); + REQUIRE(sources[i].Arg == details.Arg + suffix[i]); + REQUIRE(sources[i].Data == details.Data + suffix[i]); + REQUIRE(sources[i].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); + REQUIRE(sources[i].Origin == SourceOrigin::User); + } + + RequireDefaultSourcesAt(sources, 2); +} + +TEST_CASE("RepoSources_UpdateSource", "[sources]") +{ + using namespace std::chrono_literals; + + SetSetting(Stream::UserSources, s_EmptySources); + TestHook_ClearSourceFactoryOverrides(); + + SourceDetails details; + details.Name = "thisIsTheName"; + details.Type = "thisIsTheType"; + details.Arg = "thisIsTheArg"; + details.Data = "thisIsTheData"; + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + ProgressCallback progress; + AddSource(details, progress); + + REQUIRE(addCalledOnFactory); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + REQUIRE(sources[0].Name == details.Name); + REQUIRE(sources[0].Type == details.Type); + REQUIRE(sources[0].Arg == details.Arg); + REQUIRE(sources[0].Data == details.Data); + REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); + REQUIRE(sources[0].Origin == SourceOrigin::User); + + RequireDefaultSourcesAt(sources, 1); + + // Reset for a call to update + bool updateCalledOnFactory = false; + auto now = std::chrono::system_clock::now(); + factory.OnUpdate = [&](const SourceDetails&) { updateCalledOnFactory = true; }; + + UpdateSource(details.Name, progress); + + REQUIRE(updateCalledOnFactory); + + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + REQUIRE(sources[0].Name == details.Name); + REQUIRE(sources[0].Type == details.Type); + REQUIRE(sources[0].Arg == details.Arg); + REQUIRE(sources[0].Data == details.Data); + REQUIRE((now - sources[0].LastUpdateTime) < 1s); +} + +TEST_CASE("RepoSources_UpdateSourceRetries", "[sources]") +{ + using namespace std::chrono_literals; + + SetSetting(Stream::UserSources, s_EmptySources); + TestHook_ClearSourceFactoryOverrides(); + + SourceDetails details; + details.Name = "thisIsTheName"; + details.Type = "thisIsTheType"; + details.Arg = "thisIsTheArg"; + details.Data = "thisIsTheData"; + + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails& sd) { sd.Data = details.Data; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + ProgressCallback progress; + AddSource(details, progress); + + // Reset for a call to update + bool updateShouldThrow = false; + bool updateCalledOnFactoryAgain = false; + factory.OnUpdate = [&](const SourceDetails&) + { + if (updateShouldThrow) + { + updateShouldThrow = false; + THROW_HR(E_ACCESSDENIED); + } + updateCalledOnFactoryAgain = true; + }; + + UpdateSource(details.Name, progress); + + REQUIRE(updateCalledOnFactoryAgain); +} + +TEST_CASE("RepoSources_RemoveSource", "[sources]") +{ + SetSetting(Stream::UserSources, s_EmptySources); + TestHook_ClearSourceFactoryOverrides(); + + SourceDetails details; + details.Name = "thisIsTheName"; + details.Type = "thisIsTheType"; + details.Arg = "thisIsTheArg"; + details.Data = "thisIsTheData"; + + bool removeCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + ProgressCallback progress; + AddSource(details, progress); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + RemoveSource(details.Name, progress); + + REQUIRE(removeCalledOnFactory); + + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); +} + +TEST_CASE("RepoSources_RemoveDefaultSource", "[sources]") +{ + SetSetting(Stream::UserSources, s_EmptySources); + TestHook_ClearSourceFactoryOverrides(); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + REQUIRE(sources[0].Origin == SourceOrigin::Default); + + bool removeCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(sources[0].Type, factory); + + ProgressCallback progress; + + RemoveSource(sources[0].Name, progress); + + REQUIRE(removeCalledOnFactory); + + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount - 1); +} + +TEST_CASE("RepoSources_UpdateOnOpen", "[sources]") +{ + using namespace std::chrono_literals; + + TestHook_ClearSourceFactoryOverrides(); + + std::string name = "testName"; + std::string type = "testType"; + std::string arg = "testArg"; + std::string data = "testData"; + + bool updateCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnUpdate = [&](const SourceDetails&) { updateCalledOnFactory = true; }; + factory.ShouldUpdateBeforeOpenResult = true; + TestHook_SetSourceFactoryOverride(type, factory); + + SetSetting(Stream::UserSources, s_SingleSource); + + ProgressCallback progress; + auto source = OpenSource(name, progress); + + REQUIRE(updateCalledOnFactory); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + + REQUIRE(sources[0].Name == name); + REQUIRE(sources[0].Type == type); + REQUIRE(sources[0].Arg == arg); + REQUIRE(sources[0].Data == data); + REQUIRE(sources[0].LastUpdateTime != ConvertUnixEpochToSystemClock(0)); +} + +TEST_CASE("RepoSources_DropSourceByName", "[sources]") +{ + SetSetting(Stream::UserSources, s_ThreeSources); + SetSetting(Stream::SourcesMetadata, s_ThreeSourcesMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == 3); + + DropSource("testName"); + + sources = GetSources(); + REQUIRE(sources.size() == 2); + + const char* suffix[2] = { "2", "3" }; + + for (size_t i = 0; i < 2; ++i) + { + INFO("Source #" << i); + REQUIRE(sources[i].Name == "testName"s + suffix[i]); + REQUIRE(sources[i].Type == "testType"s + suffix[i]); + REQUIRE(sources[i].Arg == "testArg"s + suffix[i]); + REQUIRE(sources[i].Data == "testData"s + suffix[i]); + REQUIRE(sources[i].LastUpdateTime == ConvertUnixEpochToSystemClock(i + 1)); + REQUIRE(sources[i].Origin == SourceOrigin::User); + } +} + +TEST_CASE("RepoSources_DropAllSources", "[sources]") +{ + SetSetting(Stream::UserSources, s_ThreeSources); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == 3); + + DropSource({}); + + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + REQUIRE(sources[0].Origin == SourceOrigin::Default); +} + + +TEST_CASE("RepoSources_DropDefaultSourceByName", "[sources]") +{ + RemoveSetting(Stream::UserSources); + SetSetting(Stream::SourcesMetadata, s_WingetDefaultMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + // Verify the winget source has non-zero metadata before reset + auto wingetBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); + REQUIRE(wingetBefore != sources.end()); + REQUIRE(wingetBefore->LastUpdateTime == ConvertUnixEpochToSystemClock(100)); + + DropSource("winget"); + + // Source should still be present as a Default source + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + auto wingetAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); + REQUIRE(wingetAfter != sources.end()); + REQUIRE(wingetAfter->Origin == SourceOrigin::Default); + // Metadata should be cleared + REQUIRE(wingetAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); +} + +TEST_CASE("RepoSources_ResetTombstonedDefaultSourceByName", "[sources]") +{ + SetSetting(Stream::UserSources, s_WingetTombstoned); + SetSetting(Stream::SourcesMetadata, s_WingetDefaultMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount - 1); + + auto wingetBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); + REQUIRE(wingetBefore == sources.end()); + + DropSource("winget"); + + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + auto wingetAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); + REQUIRE(wingetAfter != sources.end()); + REQUIRE(wingetAfter->Origin == SourceOrigin::Default); + REQUIRE(wingetAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); +} + +TEST_CASE("RepoSources_DropDefaultSourceOverrideByName", "[sources]") +{ + SetSetting(Stream::UserSources, s_SingleSourceOverride); + SetSetting(Stream::SourcesMetadata, s_WingetFontOverrideMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + // winget-font should be present as a User (override) source with metadata + auto fontBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget-font"; }); + REQUIRE(fontBefore != sources.end()); + REQUIRE(fontBefore->Origin == SourceOrigin::User); + REQUIRE(fontBefore->LastUpdateTime == ConvertUnixEpochToSystemClock(100)); + + DropSource("winget-font"); + + // winget-font should be restored to Default with cleared metadata + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + auto fontAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget-font"; }); + REQUIRE(fontAfter != sources.end()); + REQUIRE(fontAfter->Origin == SourceOrigin::Default); + REQUIRE(fontAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); +} + +TEST_CASE("RepoSources_SearchAcrossMultipleSources", "[sources]") +{ + TestHook_ClearSourceFactoryOverrides(); + TestSourceFactory factory{ SourcesTestSource::Create }; + TestHook_SetSourceFactoryOverride("testType", factory); + + SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest); + + ProgressCallback progress; + auto source = OpenSource("", progress); + + SearchRequest request; + auto result = source.Search(request); + REQUIRE(result.Matches.size() == 6); + REQUIRE_FALSE(result.Truncated); + // matches are sorted in expected order + REQUIRE((result.Matches[0].MatchCriteria.Type == MatchType::Exact && result.Matches[0].MatchCriteria.Field == PackageMatchField::Id)); + REQUIRE((result.Matches[1].MatchCriteria.Type == MatchType::Exact && result.Matches[1].MatchCriteria.Field == PackageMatchField::Id)); + REQUIRE((result.Matches[2].MatchCriteria.Type == MatchType::Exact && result.Matches[2].MatchCriteria.Field == PackageMatchField::Name)); + REQUIRE((result.Matches[3].MatchCriteria.Type == MatchType::Exact && result.Matches[3].MatchCriteria.Field == PackageMatchField::Name)); + REQUIRE((result.Matches[4].MatchCriteria.Type == MatchType::CaseInsensitive && result.Matches[4].MatchCriteria.Field == PackageMatchField::Id)); + REQUIRE((result.Matches[5].MatchCriteria.Type == MatchType::CaseInsensitive && result.Matches[5].MatchCriteria.Field == PackageMatchField::Id)); + + // when truncate required + request.MaximumResults = 3; + result = source.Search(request); + REQUIRE(result.Matches.size() == 3); + REQUIRE(result.Truncated); + // matches are sorted in expected order + REQUIRE((result.Matches[0].MatchCriteria.Type == MatchType::Exact && result.Matches[0].MatchCriteria.Field == PackageMatchField::Id)); + REQUIRE((result.Matches[1].MatchCriteria.Type == MatchType::Exact && result.Matches[1].MatchCriteria.Field == PackageMatchField::Id)); + REQUIRE((result.Matches[2].MatchCriteria.Type == MatchType::Exact && result.Matches[2].MatchCriteria.Field == PackageMatchField::Name)); +} + +TEST_CASE("RepoSources_GroupPolicy_DefaultSource", "[sources][groupPolicy]") +{ + WHEN("Default source is disabled") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::DefaultSource, PolicyState::Disabled); + + SECTION("Get source") + { + // Listing the sources should not return the default. + SetSetting(Stream::UserSources, s_EmptySources); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount - 1); + } + SECTION("Add default source") + { + // We should not be able to add the default source manually. + SetSetting(Stream::UserSources, s_EmptySources); + + ProgressCallback progress; + SourceDetails details; + details.Name = "winget"; + details.Type = "Microsoft.PreIndexed.Package"; + details.Arg = "https://cdn.winget.microsoft.com/cache"; + REQUIRE_POLICY_EXCEPTION( + AddSource(details, progress), + TogglePolicy::Policy::DefaultSource); + } + SECTION("Ignore default source from user") + { + // We should ignore any existing user source that is the same as the default. + SetSetting(Stream::UserSources, s_DefaultSourceAsUserSource); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount - 1); + } + SECTION("Add same-name source from user") + { + // We should allow adding sources with the same name as the default but + // pointing somewhere else. + SetSetting(Stream::UserSources, s_EmptySources); + TestHook_ClearSourceFactoryOverrides(); + + SourceDetails details; + details.Name = "winget"; + details.Type = "someType"; + details.Arg = "notWingetRealArg"; + details.Data = "someData"; + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails& sd) { addCalledOnFactory = true; sd.Data = details.Data; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + ProgressCallback progress; + AddSource(details, progress); + + REQUIRE(addCalledOnFactory); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + REQUIRE(sources[0].Name == details.Name); + REQUIRE(sources[0].Type == details.Type); + REQUIRE(sources[0].Arg == details.Arg); + REQUIRE(sources[0].Data == details.Data); + REQUIRE(sources[0].Origin == SourceOrigin::User); + } + SECTION("Allow same name source from user") + { + // We should respect existing user sources with the same name. + // We should allow adding sources with the same name as the default but + // pointing somewhere else. + SetSetting(Stream::UserSources, s_UserSourceNamedLikeDefault); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + REQUIRE(sources[0].Name == "winget"); + REQUIRE(sources[0].Type == "testType"); + REQUIRE(sources[0].Arg == "testArg"); + REQUIRE(sources[0].Data == "testData"); + REQUIRE(sources[0].Origin == SourceOrigin::User); + } + } + + WHEN("Default source is enabled") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::DefaultSource, PolicyState::Enabled); + + SECTION("Remove source is blocked") + { + // We should not be able to remove the default source. + SetSetting(Stream::UserSources, s_EmptySources); + + ProgressCallback progress; + REQUIRE_POLICY_EXCEPTION( + RemoveSource("winget", progress), + TogglePolicy::Policy::DefaultSource); + } + SECTION("Tombstone is overridden") + { + // We should ignore if the default source was already deleted. + SetSetting(Stream::UserSources, s_DefaultSourcesTombstoned); + + auto sources = GetSources(); + REQUIRE(sources.size() == 1); + REQUIRE(sources[0].Name == "winget"); + REQUIRE(sources[0].Origin == SourceOrigin::Default); + } + SECTION("Same name source is overridden") + { + // We should ignore existing user sources with the same name as the default. + SetSetting(Stream::UserSources, s_UserSourceNamedLikeDefault); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + REQUIRE(sources[1].Name == "winget"); + REQUIRE(sources[1].Arg == "https://cdn.winget.microsoft.com/cache"); + REQUIRE(sources[1].Origin == SourceOrigin::Default); + } + } +} + +TEST_CASE("RepoSources_GroupPolicy_AdditionalSources", "[sources][groupPolicy]") +{ + WHEN("Additional sources are enabled") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::AdditionalSources, PolicyState::Enabled); + + SECTION("Additional sources are listed") + { + // Getting the current sources should list the additional sources. + std::vector policySources; + const std::string suffix[3] = { "", "2", "3" }; + for (size_t i = 0; i < 3; ++i) + { + SourceFromPolicy source; + source.Name = "name" + suffix[i]; + source.Type = "type" + suffix[i]; + source.Arg = "arg" + suffix[i]; + source.Data = "data" + suffix[i]; + source.Identifier = "id" + suffix[i]; + policySources.emplace_back(std::move(source)); + } + + policies.SetValue(policySources); + SetSetting(Stream::UserSources, s_EmptySources); + + auto sources = GetSources(); + + // The source list includes the default source + REQUIRE(sources.size() == policySources.size() + c_DefaultSourceCount); + REQUIRE(sources.back().Origin == SourceOrigin::Default); + + for (size_t i = 0; i < policySources.size(); ++i) + { + REQUIRE(sources[i].Name == policySources[i].Name); + REQUIRE(sources[i].Type == policySources[i].Type); + REQUIRE(sources[i].Arg == policySources[i].Arg); + REQUIRE(sources[i].Data == policySources[i].Data); + REQUIRE(sources[i].Identifier == policySources[i].Identifier); + REQUIRE(sources[i].Origin == SourceOrigin::GroupPolicy); + } + } + SECTION("Same-name user source is overridden") + { + // User sources with the same name as an additional source are ignored. + SourceFromPolicy policySource; + policySource.Name = "testName"; + policySource.Type = "notTestType"; + policySource.Arg = "notTestArg"; + policySource.Data = "notTestData"; + policySource.Identifier = "notTestId"; + + policies.SetValue({ policySource }); + SetSetting(Stream::UserSources, s_SingleSource); + + auto sources = GetSources(); + + // The source list includes the default source + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + REQUIRE(sources[1].Origin == SourceOrigin::Default); + + REQUIRE(sources[0].Name == policySource.Name); + REQUIRE(sources[0].Type == policySource.Type); + REQUIRE(sources[0].Arg == policySource.Arg); + REQUIRE(sources[0].Data == policySource.Data); + REQUIRE(sources[0].Identifier == policySource.Identifier); + REQUIRE(sources[0].Origin == SourceOrigin::GroupPolicy); + } + SECTION("Cannot remove additional source") + { + // An additional source cannot be removed. + SourceFromPolicy policySource; + policySource.Name = "name"; + policySource.Type = "type"; + policySource.Arg = "arg"; + policySource.Data = "data"; + policySource.Identifier = "id"; + + bool removeCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnRemove = [&](const SourceDetails&) { removeCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(policySource.Type, factory); + + policies.SetValue({ policySource }); + SetSetting(Stream::UserSources, s_EmptySources); + + ProgressCallback progress; + REQUIRE_POLICY_EXCEPTION( + RemoveSource(policySource.Name, progress), + TogglePolicy::Policy::AdditionalSources); + REQUIRE_FALSE(removeCalledOnFactory); + } + SECTION("Additional source overrides default") + { + // An additional source with the same name as a default overrides it. + SourceFromPolicy policySource; + policySource.Name = "winget"; + policySource.Type = "notDefaultType"; + policySource.Arg = "notDefaultArg"; + policySource.Data = "notDefaultData"; + policySource.Identifier = "notDefaultId"; + + policies.SetValue({ policySource }); + SetSetting(Stream::UserSources, s_EmptySources); + + auto sources = GetSources(); + + REQUIRE(sources.size() == c_DefaultSourceCount); + REQUIRE(sources[0].Name == policySource.Name); + REQUIRE(sources[0].Type == policySource.Type); + REQUIRE(sources[0].Arg == policySource.Arg); + REQUIRE(sources[0].Data == policySource.Data); + REQUIRE(sources[0].Identifier == policySource.Identifier); + REQUIRE(sources[0].Origin == SourceOrigin::GroupPolicy); + } + } +} + +TEST_CASE("RepoSources_GroupPolicy_AllowedSources", "[sources][groupPolicy]") +{ + WHEN("Allowed sources are enabled") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::AllowedSources, PolicyState::Enabled); + + SECTION("Add allowed source") + { + // We should be able to add sources in the allow list. + SourceFromPolicy policySource; + policySource.Name = "testName"; + policySource.Type = "testType"; + policySource.Arg = "testArg"; + policySource.Data = "testData"; + policySource.Identifier = "testId"; + + policies.SetValue({ policySource }); + SetSetting(Stream::UserSources, s_EmptySources); + TestHook_ClearSourceFactoryOverrides(); + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails& sd) + { + addCalledOnFactory = true; + sd.Data = policySource.Data; + sd.Identifier = policySource.Identifier; + }; + TestHook_SetSourceFactoryOverride(policySource.Type, factory); + + ProgressCallback progress; + SourceDetails details; + details.Name = policySource.Name; + details.Type = policySource.Type; + details.Arg = policySource.Arg; + AddSource(details, progress); + + REQUIRE(addCalledOnFactory); + + // The source list includes the default source + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount + 1); + REQUIRE(sources[1].Origin == SourceOrigin::Default); + + REQUIRE(sources[0].Name == policySource.Name); + REQUIRE(sources[0].Type == policySource.Type); + REQUIRE(sources[0].Arg == policySource.Arg); + REQUIRE(sources[0].Data == policySource.Data); + REQUIRE(sources[0].Identifier == policySource.Identifier); + REQUIRE(sources[0].Origin == SourceOrigin::User); + } + SECTION("Cannot add non-allowed source") + { + // We should not be allowed to add anything not matching the allow list. + SourceFromPolicy policySource; + policySource.Name = "testName"; + policySource.Type = "testType"; + policySource.Arg = "testArg"; + policySource.Data = "testData"; + policySource.Identifier = "testId"; + + policies.SetValue({ policySource }); + SetSetting(Stream::UserSources, s_EmptySources); + + ProgressCallback progress; + SourceDetails details; + details.Name = "notAllowed"; + details.Type = "type"; + details.Arg = "arg"; + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + REQUIRE_POLICY_EXCEPTION( + AddSource(details, progress), + TogglePolicy::Policy::AllowedSources); + REQUIRE_FALSE(addCalledOnFactory); + } + } + + WHEN("Allowed sources are disabled") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::AllowedSources, PolicyState::Disabled); + + SECTION("Cannot add any source") + { + SetSetting(Stream::UserSources, s_EmptySources); + + ProgressCallback progress; + SourceDetails details; + details.Name = "name"; + details.Type = "type"; + details.Arg = "arg"; + + bool addCalledOnFactory = false; + TestSourceFactory factory{ SourcesTestSource::Create }; + factory.OnAdd = [&](SourceDetails&) { addCalledOnFactory = true; }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + REQUIRE_POLICY_EXCEPTION( + AddSource(details, progress), + TogglePolicy::Policy::AllowedSources); + REQUIRE_FALSE(addCalledOnFactory); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + REQUIRE(sources[0].Origin == SourceOrigin::Default); + } + SECTION("Existing sources are ignored") + { + SetSetting(Stream::UserSources, s_SingleSource); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + REQUIRE(sources[0].Origin == SourceOrigin::Default); + } + } +} + +TEST_CASE("RepoSources_OpenMultipleWithSingleFailure", "[sources]") +{ + TestHook_ClearSourceFactoryOverrides(); + TestSourceFactory factory{ FailingSourcesTestSource::CreateFailWinget }; + TestHook_SetSourceFactoryOverride("testType", factory); + + SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest); + + ProgressCallback progress; + auto result = OpenSource("", progress); + + REQUIRE(result); + + SearchResult searchResult = result.Search({}); + + REQUIRE(searchResult.Failures.size() == 1); + + HRESULT openFailure = S_OK; + try + { + std::rethrow_exception(searchResult.Failures[0].Exception); + } + catch (const wil::ResultException& re) + { + openFailure = re.GetErrorCode(); + } + catch (...) {} + + REQUIRE(openFailure == FailingSourcesTestSource::FailingHR); +} + +TEST_CASE("RepoSources_OpenMultipleWithTotalFailure", "[sources]") +{ + TestHook_ClearSourceFactoryOverrides(); + TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll }; + TestHook_SetSourceFactoryOverride("testType", factory); + + SetSetting(Stream::UserSources, s_TwoSource_AggregateSourceTest); + + ProgressCallback progress; + REQUIRE_THROWS_HR(OpenSource("", progress), APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES); +} + +TEST_CASE("RepoSources_UpdateSettingsDuringAction_SourcesUpdate", "[sources]") +{ + SetSetting(Stream::UserSources, s_SingleSource); + SetSetting(Stream::SourcesMetadata, s_SingleSourceMetadata); + + std::string userSourcesUpdate{ s_DoubleSource }; + std::string sourcesMetadataUpdate{ s_DoubleSourceMetadata }; + + std::string singleSourceName = "testName"; + std::string doubleSourceName = "testName2"; + + std::string unusedSourceName = "unusedName"; + std::string unusedSourceArg = "unusedArg"; + std::string testSourceType = "testType"; + + TestHook_ClearSourceFactoryOverrides(); + TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll }; + auto settingsUpdate = [&](const AppInstaller::Repository::SourceDetails&) + { + SetSetting(Stream::UserSources, userSourcesUpdate); + SetSetting(Stream::SourcesMetadata, sourcesMetadataUpdate); + }; + factory.OnAdd = settingsUpdate; + factory.OnUpdate = settingsUpdate; + factory.OnRemove = settingsUpdate; + TestHook_SetSourceFactoryOverride(testSourceType, factory); + + ProgressCallback progress; + + SECTION("Add") + { + SourceDetails addedSource; + addedSource.Name = unusedSourceName; + addedSource.Type = testSourceType; + addedSource.Arg = unusedSourceArg; + AddSource(addedSource, progress); + + auto sources = GetSources(); + REQUIRE(sources.size() == 3 + c_DefaultSourceCount); + + REQUIRE(sources[0].Name == singleSourceName); + REQUIRE(sources[1].Name == doubleSourceName); + REQUIRE(sources[2].Name == addedSource.Name); + } + SECTION("Add conflicting") + { + SourceDetails addedSource; + addedSource.Name = doubleSourceName; + addedSource.Type = testSourceType; + addedSource.Arg = unusedSourceArg; + REQUIRE_THROWS_HR(AddSource(addedSource, progress), APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS); + } + SECTION("Update") + { + UpdateSource(singleSourceName, progress); + + auto sources = GetSources(); + REQUIRE(sources.size() == 2 + c_DefaultSourceCount); + + REQUIRE(sources[0].Name == singleSourceName); + REQUIRE(sources[1].Name == doubleSourceName); + } + SECTION("Remove") + { + RemoveSource(singleSourceName, progress); + + auto sources = GetSources(); + REQUIRE(sources.size() == 1 + c_DefaultSourceCount); + + REQUIRE(sources[0].Name == doubleSourceName); + } + SECTION("Remove already removed") + { + userSourcesUpdate = s_EmptySources; + sourcesMetadataUpdate = s_EmptySources; + + RemoveSource(singleSourceName, progress); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + } +} + +TEST_CASE("RepoSources_UpdateSettingsDuringAction_MetadataUpdate", "[sources]") +{ + SetSetting(Stream::UserSources, s_SingleSource); + SetSetting(Stream::SourcesMetadata, s_SingleSourceMetadata); + + std::string sourcesMetadataUpdate{ s_SingleSourceMetadataUpdate }; + int64_t updateTime = 101; + + std::string singleSourceName = "testName"; + std::string doubleSourceName = "testName2"; + + std::string unusedSourceName = "unusedName"; + std::string unusedSourceArg = "unusedArg"; + std::string testSourceType = "testType"; + + TestHook_ClearSourceFactoryOverrides(); + TestSourceFactory factory{ FailingSourcesTestSource::CreateFailAll }; + auto settingsUpdate = [&](const AppInstaller::Repository::SourceDetails&) + { + SetSetting(Stream::SourcesMetadata, sourcesMetadataUpdate); + }; + factory.OnAdd = settingsUpdate; + factory.OnUpdate = settingsUpdate; + factory.OnRemove = settingsUpdate; + TestHook_SetSourceFactoryOverride(testSourceType, factory); + + ProgressCallback progress; + + SECTION("Add") + { + SourceDetails addedSource; + addedSource.Name = unusedSourceName; + addedSource.Type = testSourceType; + addedSource.Arg = unusedSourceArg; + AddSource(addedSource, progress); + + auto sources = GetSources(); + REQUIRE(sources.size() == 2 + c_DefaultSourceCount); + + REQUIRE(sources[0].Name == singleSourceName); + REQUIRE(ConvertSystemClockToUnixEpoch(sources[0].LastUpdateTime) == updateTime); + REQUIRE(sources[1].Name == addedSource.Name); + } + SECTION("Update") + { + UpdateSource(singleSourceName, progress); + + auto sources = GetSources(); + REQUIRE(sources.size() == 1 + c_DefaultSourceCount); + + REQUIRE(sources[0].Name == singleSourceName); + REQUIRE(ConvertSystemClockToUnixEpoch(sources[0].LastUpdateTime) > updateTime); + } + SECTION("Remove") + { + RemoveSource(singleSourceName, progress); + + auto sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + } +} + +TEST_CASE("RepoSources_RestoringWellKnownSource", "[sources]") +{ + TestHook_ClearSourceFactoryOverrides(); + RemoveSetting(Stream::UserSources); + + Source storeSource{ WellKnownSource::MicrosoftStore }; + SourceDetails details = storeSource.GetDetails(); + REQUIRE(!details.CertificatePinningConfiguration.IsEmpty()); + + TestSourceFactory factory{ SourcesTestSource::Create }; + TestHook_SetSourceFactoryOverride(details.Type, factory); + + ProgressCallback progress; + + REQUIRE(storeSource.Remove(progress)); + + Source storeAfterRemove{ details.Name }; + REQUIRE(!storeAfterRemove); + + SECTION("with well known name") + { + Source addStoreBack{ details.Name, details.Arg, details.Type, Repository::SourceTrustLevel::None, {} }; + REQUIRE(addStoreBack.Add(progress)); + + Source storeAfterAdd{ details.Name }; + REQUIRE(storeAfterAdd); + REQUIRE(!storeAfterAdd.GetDetails().CertificatePinningConfiguration.IsEmpty()); + } + + SECTION("with different name") + { + std::string newName = details.Name + "_new"; + Source addStoreBack{ newName, details.Arg, details.Type, Repository::SourceTrustLevel::None, {} }; + REQUIRE(addStoreBack.Add(progress)); + + Source storeAfterAdd{ newName }; + REQUIRE(storeAfterAdd); + REQUIRE(storeAfterAdd.GetDetails().CertificatePinningConfiguration.IsEmpty()); + } +} + +TEST_CASE("RepoSources_GroupPolicy_BypassCertificatePinningForMicrosoftStore", "[sources][groupPolicy]") +{ + TestHook_ClearSourceFactoryOverrides(); + + SECTION("Not configured") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::NotConfigured); + Source source(WellKnownSource::MicrosoftStore); + REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); + } + + SECTION("Enabled") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Enabled); + Source source(WellKnownSource::MicrosoftStore); + REQUIRE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); + } + + SECTION("Disabled") + { + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Disabled); + Source source(WellKnownSource::MicrosoftStore); + REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); + } +} + +TEST_CASE("RepoSources_BuiltInDesktopFrameworkSourceAlwaysCreatable", "[sources]") +{ + Source source(WellKnownSource::DesktopFrameworks); + REQUIRE(source); +} + +TEST_CASE("RepoSources_MicrosoftStore_CertificatePinningLifetimeCheck", "[sources]") +{ + TestHook_ClearSourceFactoryOverrides(); + + GroupPolicyTestOverride policies; + policies.SetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore, PolicyState::Disabled); + Source source(WellKnownSource::MicrosoftStore); + REQUIRE_FALSE(source.GetDetails().CertificatePinningConfiguration.IsEmpty()); + + // The configuration's remaining lifetime is the *maximum* of the remaining lifetimes of the individual chains. + // A chain's remaining lifetime is the *minimum* of the remaining lifetimes of the individual certificates. + // A certificate's remaining lifetime is a value between 0.0 and 1.0 that is the ratio of remaining valid time to total valid time. + + // The goal of this test is to warn when the pinning configuration may be in danger of expiration; either via certificate validity or + // more likely by renewals causing the pinning to reject the new, correct certificates. It operates in percentage lifetime to normalize + // the values across the chain. + INFO("If this test has failed, the pinning certificates may be nearing expiration and should be investigated."); + double lifetimePercentage = source.GetDetails().CertificatePinningConfiguration.GetRemainingLifetimePercentage(); + REQUIRE(lifetimePercentage > 0.25); +} diff --git a/src/AppInstallerCLITests/Strings.cpp b/src/AppInstallerCLITests/Strings.cpp index 5a8a0d0fda..86f052cb31 100644 --- a/src/AppInstallerCLITests/Strings.cpp +++ b/src/AppInstallerCLITests/Strings.cpp @@ -1,381 +1,381 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Utility::literals; - -TEST_CASE("UTF8Length", "[strings]") -{ - REQUIRE(UTF8Length("") == 0); - REQUIRE(UTF8Length("a") == 1); - REQUIRE(UTF8Length(" a b c ") == 7); - REQUIRE(UTF8Length("K\xC3\xA4se") == 4); // "Käse" - REQUIRE(UTF8Length("bye\xE2\x80\xA6") == 4); // "bye…" - REQUIRE(UTF8Length("\xf0\x9f\xa6\x86") == 1); // [duck emoji] - REQUIRE(UTF8Length("\xf0\x9d\x85\xa0\xf0\x9d\x85\xa0") == 2); // [8th note][8th note] -} - -TEST_CASE("UTF8Substring", "[strings]") -{ - REQUIRE(UTF8Substring("", 0, 0) == ""); - REQUIRE(UTF8Substring("abcd", 0, 4) == "abcd"); - REQUIRE(UTF8Substring("abcd", 0, 5) == "abcd"); - REQUIRE(UTF8Substring("abcd", 0, 2) == "ab"); - REQUIRE(UTF8Substring("abcd", 1, 0) == ""); - REQUIRE(UTF8Substring("abcd", 1, 1) == "b"); - REQUIRE(UTF8Substring("abcd", 1, 3) == "bcd"); - REQUIRE(UTF8Substring("abcd", 4, 0) == ""); - - const char* s = "\xf0\x9f\xa6\x86s like \xf0\x9f\x8c\x8a"; // [duck emoji]s like [wave emoji] - REQUIRE(UTF8Substring(s, 0, 9) == "\xf0\x9f\xa6\x86s like \xf0\x9f\x8c\x8a"); - REQUIRE(UTF8Substring(s, 0, 1) == "\xf0\x9f\xa6\x86"); - REQUIRE(UTF8Substring(s, 0, 2) == "\xf0\x9f\xa6\x86s"); - REQUIRE(UTF8Substring(s, 1, 7) == "s like "); - REQUIRE(UTF8Substring(s, 1, 8) == "s like \xf0\x9f\x8c\x8a"); -} - -TEST_CASE("UTF8ColumnWidth", "[strings]") -{ - REQUIRE(UTF8ColumnWidth("") == 0); - REQUIRE(UTF8ColumnWidth("a") == 1); - REQUIRE(UTF8ColumnWidth(" a b c ") == 7); - REQUIRE(UTF8ColumnWidth("K\xC3\xA4se") == 4); // "Käse" - REQUIRE(UTF8ColumnWidth("bye\xE2\x80\xA6") == 4); // "bye…" - REQUIRE(UTF8ColumnWidth("fi\xEF\xAC\x81") == 3); // "fi[fi]" [fi] is not decoupled - REQUIRE(UTF8ColumnWidth("\xf0\x9f\xa6\x86") == 2); // [duck emoji] - REQUIRE(UTF8ColumnWidth("\xf0\x9d\x85\xa0\xf0\x9d\x85\xa0") == 2); // [8th note][8th note] - REQUIRE(UTF8ColumnWidth("\xe6\xb5\x8b\xe8\xaf\x95") == 4); // 测试 - REQUIRE(UTF8ColumnWidth("te\xe6\xb5\x8bs\xe8\xaf\x95t") == 8); // te测s试t -} - -TEST_CASE("UTF8TrimRightToColumnWidth", "[strings]") -{ - size_t actualWidth; - REQUIRE((UTF8TrimRightToColumnWidth("", 0, actualWidth) == "" && actualWidth == 0)); - REQUIRE((UTF8TrimRightToColumnWidth("abcd", 4, actualWidth) == "abcd" && actualWidth == 4)); - REQUIRE((UTF8TrimRightToColumnWidth("abcd", 5, actualWidth) == "abcd" && actualWidth == 4)); - REQUIRE((UTF8TrimRightToColumnWidth("abcd", 2, actualWidth) == "ab" && actualWidth == 2)); - - NormalizedString s{ "te\xe6\xb5\x8bs\xe8\xaf\x95t" }; // // te测s试t - REQUIRE((UTF8TrimRightToColumnWidth(s, 0, actualWidth) == "" && actualWidth == 0)); - REQUIRE((UTF8TrimRightToColumnWidth(s, 2, actualWidth) == "te" && actualWidth == 2)); - REQUIRE((UTF8TrimRightToColumnWidth(s, 3, actualWidth) == "te" && actualWidth == 2)); - REQUIRE((UTF8TrimRightToColumnWidth(s, 4, actualWidth) == "te\xe6\xb5\x8b" && actualWidth == 4)); - REQUIRE((UTF8TrimRightToColumnWidth(s, 8, actualWidth) == "te\xe6\xb5\x8bs\xe8\xaf\x95t" && actualWidth == 8)); - REQUIRE((UTF8TrimRightToColumnWidth(s, 10, actualWidth) == "te\xe6\xb5\x8bs\xe8\xaf\x95t" && actualWidth == 8)); -} - -TEST_CASE("Normalize", "[strings]") -{ - REQUIRE(Normalize("test") == "test"); - - // A + combining Dieresis => single A with umlaut char - REQUIRE(Normalize(L"\x41\x308") == L"\xC4"); - // This will stop working in C++20, sigh. - REQUIRE(Normalize(u8"\x41\x308") == u8"\xC4"); - - // Ligature fi => f + i - REQUIRE(Normalize(u8"\xFB01") == u8"fi"); -} - -TEST_CASE("NormalizedString", "[strings]") -{ - REQUIRE(NormalizedString("test") == "test"); - std::string input = "test"; - REQUIRE(NormalizedString(input) == input); - - // A + combining Dieresis => single A with umlaut char - REQUIRE(NormalizedString(std::wstring_view(L"\x41\x308")) == u8"\xC4"); - // This will stop working in C++20, sigh. - input = u8"\x41\x308"; - REQUIRE(NormalizedString(input) == u8"\xC4"); - - // Ligature fi => f + i - std::string_view input2 = u8"\xFB01"; - REQUIRE(NormalizedString(input2) == u8"fi"); - - // Embedded null - std::string_view input3{ "Test\0Case", 9 }; - REQUIRE(NormalizedString(input3) == "Test Case"); -} - -TEST_CASE("Trim", "[strings]") -{ - std::string str; - REQUIRE(Trim(str.assign("")) == ""); - REQUIRE(Trim(str.assign(" ")) == ""); - REQUIRE(Trim(str.assign(" \t ")) == ""); - REQUIRE(Trim(str.assign(" a")) == "a"); - REQUIRE(Trim(str.assign("bght ")) == "bght"); - REQUIRE(Trim(str.assign("\tStuff\f")) == "Stuff"); - REQUIRE(Trim(str.assign("Multiple words")) == "Multiple words"); - REQUIRE(Trim(str.assign(" Multiple words")) == "Multiple words"); - REQUIRE(Trim(str.assign("Much after is taken \f\n\r\t\v\v\t\r\n\f ")) == "Much after is taken"); - - REQUIRE(Trim(L" Test"sv) == L"Test"); - REQUIRE(Trim(L" "sv) == L""); -} - -TEST_CASE("CaseInsensitiveStartsWith", "[strings]") -{ - REQUIRE(CaseInsensitiveStartsWith("startswith", "starts")); - REQUIRE(CaseInsensitiveStartsWith("startswith", "STAR")); - REQUIRE(CaseInsensitiveStartsWith("startswith", "startSWITH")); - REQUIRE(CaseInsensitiveStartsWith("startswith", "")); - - REQUIRE(!CaseInsensitiveStartsWith("starts", "startswith")); - REQUIRE(!CaseInsensitiveStartsWith("", "nuffing")); - REQUIRE(!CaseInsensitiveStartsWith("withstarts", "starts")); - REQUIRE(!CaseInsensitiveStartsWith(" starts", "starts")); -} - -TEST_CASE("FoldCase", "[strings]") -{ - REQUIRE(FoldCase(""sv) == FoldCase(""sv)); - REQUIRE(FoldCase("foldcase"sv) == FoldCase("FOLDCASE"sv)); - REQUIRE(FoldCase(u8"f\xF6ldcase"sv) == FoldCase(u8"F\xD6LDCASE"sv)); - REQUIRE(FoldCase(u8"foldc\x430se"sv) == FoldCase(u8"FOLDC\x410SE"sv)); -} - -TEST_CASE("ExpandEnvironmentVariables", "[strings]") -{ - wchar_t buffer[MAX_PATH]; - GetTempPathW(ARRAYSIZE(buffer), buffer); - - std::wstring tempPath = buffer; - if (!tempPath.empty() && tempPath.back() == '\\') - { - tempPath.resize(tempPath.size() - 1); - } - - REQUIRE(ExpandEnvironmentVariables(L"%TEMP%") == tempPath); -} - -TEST_CASE("PathOutput", "[strings]") -{ - std::string original = "\xe6\xb5\x8b\xe8\xaf\x95"; - std::filesystem::path path = ConvertToUTF16(original); - AICLI_LOG(Test, Info, << path); - - std::istringstream in; - std::ostringstream out; - AppInstaller::CLI::Execution::Reporter reporter{ out, in }; - - reporter.Info() << path; - - std::string output = out.str(); - REQUIRE(output.substr(output.size() - original.size()) == original); -} - -TEST_CASE("ReplaceWhileCopying", "[strings]") -{ - REQUIRE(ReplaceWhileCopying(L"A red apple", L"red", L"green") == L"A green apple"); - REQUIRE(ReplaceWhileCopying(L"A red, red apple", L"red", L"green") == L"A green, green apple"); - REQUIRE(ReplaceWhileCopying(L"A red, red apple", L"ed", L"ad") == L"A rad, rad apple"); - REQUIRE(ReplaceWhileCopying(L"A red apple", L"p", L"f") == L"A red affle"); - REQUIRE(ReplaceWhileCopying(L"A red apple", L"", L"green") == L"A red apple"); -} - -TEST_CASE("MakeSuitablePathPart", "[strings]") -{ - REQUIRE(MakeSuitablePathPart("A\\B") == "A_B"); - REQUIRE(MakeSuitablePathPart("A\\B/") == "A_B_"); - REQUIRE(MakeSuitablePathPart("*AB") == "_AB"); - REQUIRE(MakeSuitablePathPart(u8"f*\xF6*ldcase") == u8"f_\xF6_ldcase"); - REQUIRE(MakeSuitablePathPart(".") == "_"); - REQUIRE(MakeSuitablePathPart("..") == "._"); - REQUIRE(MakeSuitablePathPart(std::string(300, ' ')) == SHA256::ConvertToString(SHA256::ComputeHash(std::string(300, ' ')))); - REQUIRE_THROWS_HR(MakeSuitablePathPart("COM1"), E_INVALIDARG); - REQUIRE_THROWS_HR(MakeSuitablePathPart("NUL.txt"), E_INVALIDARG); -} - -TEST_CASE("GetFileNameFromURI", "[strings]") -{ - REQUIRE(GetFileNameFromURI("https://github.com/microsoft/winget-cli/pull/1722").u8string() == "1722"); - REQUIRE(GetFileNameFromURI("https://github.com/microsoft/winget-cli/README.md").u8string() == "README.md"); - REQUIRE(GetFileNameFromURI("https://microsoft.com/").u8string() == ""); -} - -void ValidateSplitFileName(std::string_view uri, std::string_view base, std::string_view fileName) -{ - auto split = SplitFileNameFromURI(uri); - REQUIRE(split.first == base); - REQUIRE(split.second.u8string() == fileName); -} - -TEST_CASE("SplitFileNameFromURI", "[strings]") -{ - ValidateSplitFileName("https://github.com/microsoft/winget-cli/pull/1722", "https://github.com/microsoft/winget-cli/pull/", "1722"); - ValidateSplitFileName("https://github.com/microsoft/winget-cli/README.md", "https://github.com/microsoft/winget-cli/", "README.md"); - ValidateSplitFileName("https://microsoft.com/", "https://microsoft.com/", ""); -} - -TEST_CASE("SplitIntoWords", "[strings]") -{ - REQUIRE(SplitIntoWords("A B") == std::vector{ "A", "B" }); - REQUIRE(SplitIntoWords("Some-Thing") == std::vector{ "Some", "Thing" }); - - // 私のテスト = "My test" according to an online translator - // Split as "私" "の" "テスト" - REQUIRE(SplitIntoWords("\xe7\xa7\x81\xe3\x81\xae\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88") == std::vector{ "\xe7\xa7\x81", "\xe3\x81\xae", "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" }); -} - -TEST_CASE("ReplaceEmbeddedNullCharacters", "[strings]") -{ - std::string test = "Test Parts"; - test[4] = '\0'; - ReplaceEmbeddedNullCharacters(test); - REQUIRE(test == "Test Parts"); -} - -TEST_CASE("HexStrings", "[strings]") -{ - std::vector buffer{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; - std::string value = "000102030405060708090a0b0c0d0e0f"; - - REQUIRE(value == ConvertToHexString(buffer)); - REQUIRE(std::equal(buffer.begin(), buffer.end(), ParseFromHexString(value).begin())); -} - -TEST_CASE("Join", "[strings]") -{ - std::vector list_0{ }; - std::vector list_1{ "A"_lis }; - std::vector list_2{ "A"_lis, "B"_lis }; - - REQUIRE(""_lis == Join(", "_liv, list_0)); - REQUIRE("A"_lis == Join(", "_liv, list_1)); - REQUIRE("A, B"_lis == Join(", "_liv, list_2)); - REQUIRE("AB"_lis == Join(""_liv, list_2)); -} - -TEST_CASE("Format", "[strings]") -{ - REQUIRE("First Second" == Format("{0} {1}", "First", "Second")); - REQUIRE("First Second" == Format("{1} {0}", "Second", "First")); - REQUIRE("First Second" == Format("{0} {1}", "First", "Second", "(Extra", "Input", "Ignored)")); - REQUIRE("First Second First Second" == Format("{0} {1} {0} {1}", "First", "Second")); - - // Note: C++20 std::format will throw an exception for this test case - REQUIRE("First {1}" == Format("{0} {1}", "First")); -} - -TEST_CASE("SplitIntoLines", "[strings]") -{ - REQUIRE(SplitIntoLines("Boring test") == std::vector{ "Boring test" }); - REQUIRE(SplitIntoLines( - "I'm Luffy! The Man Who Will Become the Pirate King!\r-Monkey D. Luffy") == std::vector{ "I'm Luffy! The Man Who Will Become the Pirate King!", "-Monkey D. Luffy" }); - REQUIRE(SplitIntoLines( - "I want live!\n-Nico Robin") == std::vector{ "I want live!", "-Nico Robin" }); - REQUIRE(SplitIntoLines( - "You want my treasure?\rYou can have it!\nI left everything I gathered in one place!\r\nYou just have to find it!") - == std::vector{ "You want my treasure?", "You can have it!", "I left everything I gathered in one place!", "You just have to find it!" }); -} - -TEST_CASE("SplitWithSeparator", "[strings]") -{ - std::vector test1 = Split("first;second;third"s, ';'); - REQUIRE(test1.size() == 3); - REQUIRE(test1[0] == "first"); - REQUIRE(test1[1] == "second"); - REQUIRE(test1[2] == "third"); - - std::vector test2 = Split("two spaces"s, ' '); - REQUIRE(test2.size() == 3); - REQUIRE(test2[0] == "two"); - REQUIRE(test2[1] == ""); - REQUIRE(test2[2] == "spaces"); - - std::vector test3 = Split("test"s, '.'); - REQUIRE(test3.size() == 1); - REQUIRE(test3[0] == "test"); - - std::vector test4 = Split(" trim | spaces "s, '|', true); - REQUIRE(test4.size() == 2); - REQUIRE(test4[0] == "trim"); - REQUIRE(test4[1] == "spaces"); - - std::vector test5 = Split(L" trim /spaces / "sv, '/', true); - REQUIRE(test5.size() == 3); - REQUIRE(test5[0] == L"trim"); - REQUIRE(test5[1] == L"spaces"); - REQUIRE(test5[2] == L""); - - std::vector test6 = Split(L" "sv, '/', true); - REQUIRE(test6.size() == 1); - REQUIRE(test6[0] == L""); - - std::vector test7 = Split(""s, ';'); - REQUIRE(test7.size() == 1); - REQUIRE(test7[0] == ""); -} - -TEST_CASE("ConvertGuid", "[strings]") -{ - std::string validGuidString = "{4d1e55b2-f16f-11cf-88cb-001111000030}"; - GUID guid = { 0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; - - REQUIRE(CaseInsensitiveEquals(ConvertGuidToString(guid), validGuidString)); -} - -TEST_CASE("FindControlCodeToConvert", "[strings]") -{ - REQUIRE(FindControlCodeToConvert("No codes") == std::string::npos); - REQUIRE(FindControlCodeToConvert("Allowed codes: \t\r\n") == std::string::npos); - REQUIRE(FindControlCodeToConvert("\x1bSkipped code", 1) == std::string::npos); - - REQUIRE(FindControlCodeToConvert("\x1bUnskipped code") == 0); - REQUIRE(FindControlCodeToConvert("Escape code: \x1b") == 13); - - std::string_view allCodes{ "\x0\x1\x2\x3\x4\x5\x6\x7\x8\xb\xc\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"sv }; - for (size_t i = 0; i < allCodes.length(); ++i) - { - REQUIRE(FindControlCodeToConvert(allCodes, i) == i); - } -} - -TEST_CASE("ConvertControlCodesToPictures", "[strings]") -{ - REQUIRE(ConvertControlCodesToPictures("No codes") == "No codes"); - REQUIRE(ConvertControlCodesToPictures("Allowed codes: \t\r\n") == "Allowed codes: \t\r\n"); - - REQUIRE(ConvertControlCodesToPictures("\x1b Code First") == ConvertToUTF8(L"\x241b Code First")); - REQUIRE(ConvertControlCodesToPictures("Escape code: \x1b") == ConvertToUTF8(L"Escape code: \x241b")); - - std::string_view allCodes{ "\x0\x1\x2\x3\x4\x5\x6\x7\x8\xb\xc\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"sv }; - std::wstring_view allPictures{ L"\x2400\x2401\x2402\x2403\x2404\x2405\x2406\x2407\x2408\x240b\x240c\x240e\x240f\x2410\x2411\x2412\x2413\x2414\x2415\x2416\x2417\x2418\x2419\x241a\x241b\x241c\x241d\x241e\x241f\x2421"sv }; - - REQUIRE(ConvertControlCodesToPictures(allCodes) == ConvertToUTF8(allPictures)); -} - -TEST_CASE("IsValidWindowsFeaturePattern_AllFound_True", "[strings][111981]") -{ - for (const auto& name : { - "IIS-ODBCLogging", - "NetFx3", - "SMB1Protocol", - }) - { - INFO(name); - REQUIRE(IsValidWindowsFeaturePattern(name)); - } -} - -TEST_CASE("IsValidWindowsFeaturePattern_Bad_False", "[strings][111981]") -{ - for (const auto& name : { - "MediaPlayback /LogPath:C:\\file.txt", - "\"MediaPlayback /LogPath:C:\\file.txt\"", - }) - { - INFO(name); - REQUIRE(!IsValidWindowsFeaturePattern(name)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Utility::literals; + +TEST_CASE("UTF8Length", "[strings]") +{ + REQUIRE(UTF8Length("") == 0); + REQUIRE(UTF8Length("a") == 1); + REQUIRE(UTF8Length(" a b c ") == 7); + REQUIRE(UTF8Length("K\xC3\xA4se") == 4); // "Käse" + REQUIRE(UTF8Length("bye\xE2\x80\xA6") == 4); // "bye…" + REQUIRE(UTF8Length("\xf0\x9f\xa6\x86") == 1); // [duck emoji] + REQUIRE(UTF8Length("\xf0\x9d\x85\xa0\xf0\x9d\x85\xa0") == 2); // [8th note][8th note] +} + +TEST_CASE("UTF8Substring", "[strings]") +{ + REQUIRE(UTF8Substring("", 0, 0) == ""); + REQUIRE(UTF8Substring("abcd", 0, 4) == "abcd"); + REQUIRE(UTF8Substring("abcd", 0, 5) == "abcd"); + REQUIRE(UTF8Substring("abcd", 0, 2) == "ab"); + REQUIRE(UTF8Substring("abcd", 1, 0) == ""); + REQUIRE(UTF8Substring("abcd", 1, 1) == "b"); + REQUIRE(UTF8Substring("abcd", 1, 3) == "bcd"); + REQUIRE(UTF8Substring("abcd", 4, 0) == ""); + + const char* s = "\xf0\x9f\xa6\x86s like \xf0\x9f\x8c\x8a"; // [duck emoji]s like [wave emoji] + REQUIRE(UTF8Substring(s, 0, 9) == "\xf0\x9f\xa6\x86s like \xf0\x9f\x8c\x8a"); + REQUIRE(UTF8Substring(s, 0, 1) == "\xf0\x9f\xa6\x86"); + REQUIRE(UTF8Substring(s, 0, 2) == "\xf0\x9f\xa6\x86s"); + REQUIRE(UTF8Substring(s, 1, 7) == "s like "); + REQUIRE(UTF8Substring(s, 1, 8) == "s like \xf0\x9f\x8c\x8a"); +} + +TEST_CASE("UTF8ColumnWidth", "[strings]") +{ + REQUIRE(UTF8ColumnWidth("") == 0); + REQUIRE(UTF8ColumnWidth("a") == 1); + REQUIRE(UTF8ColumnWidth(" a b c ") == 7); + REQUIRE(UTF8ColumnWidth("K\xC3\xA4se") == 4); // "Käse" + REQUIRE(UTF8ColumnWidth("bye\xE2\x80\xA6") == 4); // "bye…" + REQUIRE(UTF8ColumnWidth("fi\xEF\xAC\x81") == 3); // "fi[fi]" [fi] is not decoupled + REQUIRE(UTF8ColumnWidth("\xf0\x9f\xa6\x86") == 2); // [duck emoji] + REQUIRE(UTF8ColumnWidth("\xf0\x9d\x85\xa0\xf0\x9d\x85\xa0") == 2); // [8th note][8th note] + REQUIRE(UTF8ColumnWidth("\xe6\xb5\x8b\xe8\xaf\x95") == 4); // 测试 + REQUIRE(UTF8ColumnWidth("te\xe6\xb5\x8bs\xe8\xaf\x95t") == 8); // te测s试t +} + +TEST_CASE("UTF8TrimRightToColumnWidth", "[strings]") +{ + size_t actualWidth; + REQUIRE((UTF8TrimRightToColumnWidth("", 0, actualWidth) == "" && actualWidth == 0)); + REQUIRE((UTF8TrimRightToColumnWidth("abcd", 4, actualWidth) == "abcd" && actualWidth == 4)); + REQUIRE((UTF8TrimRightToColumnWidth("abcd", 5, actualWidth) == "abcd" && actualWidth == 4)); + REQUIRE((UTF8TrimRightToColumnWidth("abcd", 2, actualWidth) == "ab" && actualWidth == 2)); + + NormalizedString s{ "te\xe6\xb5\x8bs\xe8\xaf\x95t" }; // // te测s试t + REQUIRE((UTF8TrimRightToColumnWidth(s, 0, actualWidth) == "" && actualWidth == 0)); + REQUIRE((UTF8TrimRightToColumnWidth(s, 2, actualWidth) == "te" && actualWidth == 2)); + REQUIRE((UTF8TrimRightToColumnWidth(s, 3, actualWidth) == "te" && actualWidth == 2)); + REQUIRE((UTF8TrimRightToColumnWidth(s, 4, actualWidth) == "te\xe6\xb5\x8b" && actualWidth == 4)); + REQUIRE((UTF8TrimRightToColumnWidth(s, 8, actualWidth) == "te\xe6\xb5\x8bs\xe8\xaf\x95t" && actualWidth == 8)); + REQUIRE((UTF8TrimRightToColumnWidth(s, 10, actualWidth) == "te\xe6\xb5\x8bs\xe8\xaf\x95t" && actualWidth == 8)); +} + +TEST_CASE("Normalize", "[strings]") +{ + REQUIRE(Normalize("test") == "test"); + + // A + combining Dieresis => single A with umlaut char + REQUIRE(Normalize(L"\x41\x308") == L"\xC4"); + // This will stop working in C++20, sigh. + REQUIRE(Normalize(u8"\x41\x308") == u8"\xC4"); + + // Ligature fi => f + i + REQUIRE(Normalize(u8"\xFB01") == u8"fi"); +} + +TEST_CASE("NormalizedString", "[strings]") +{ + REQUIRE(NormalizedString("test") == "test"); + std::string input = "test"; + REQUIRE(NormalizedString(input) == input); + + // A + combining Dieresis => single A with umlaut char + REQUIRE(NormalizedString(std::wstring_view(L"\x41\x308")) == u8"\xC4"); + // This will stop working in C++20, sigh. + input = u8"\x41\x308"; + REQUIRE(NormalizedString(input) == u8"\xC4"); + + // Ligature fi => f + i + std::string_view input2 = u8"\xFB01"; + REQUIRE(NormalizedString(input2) == u8"fi"); + + // Embedded null + std::string_view input3{ "Test\0Case", 9 }; + REQUIRE(NormalizedString(input3) == "Test Case"); +} + +TEST_CASE("Trim", "[strings]") +{ + std::string str; + REQUIRE(Trim(str.assign("")) == ""); + REQUIRE(Trim(str.assign(" ")) == ""); + REQUIRE(Trim(str.assign(" \t ")) == ""); + REQUIRE(Trim(str.assign(" a")) == "a"); + REQUIRE(Trim(str.assign("bght ")) == "bght"); + REQUIRE(Trim(str.assign("\tStuff\f")) == "Stuff"); + REQUIRE(Trim(str.assign("Multiple words")) == "Multiple words"); + REQUIRE(Trim(str.assign(" Multiple words")) == "Multiple words"); + REQUIRE(Trim(str.assign("Much after is taken \f\n\r\t\v\v\t\r\n\f ")) == "Much after is taken"); + + REQUIRE(Trim(L" Test"sv) == L"Test"); + REQUIRE(Trim(L" "sv) == L""); +} + +TEST_CASE("CaseInsensitiveStartsWith", "[strings]") +{ + REQUIRE(CaseInsensitiveStartsWith("startswith", "starts")); + REQUIRE(CaseInsensitiveStartsWith("startswith", "STAR")); + REQUIRE(CaseInsensitiveStartsWith("startswith", "startSWITH")); + REQUIRE(CaseInsensitiveStartsWith("startswith", "")); + + REQUIRE(!CaseInsensitiveStartsWith("starts", "startswith")); + REQUIRE(!CaseInsensitiveStartsWith("", "nuffing")); + REQUIRE(!CaseInsensitiveStartsWith("withstarts", "starts")); + REQUIRE(!CaseInsensitiveStartsWith(" starts", "starts")); +} + +TEST_CASE("FoldCase", "[strings]") +{ + REQUIRE(FoldCase(""sv) == FoldCase(""sv)); + REQUIRE(FoldCase("foldcase"sv) == FoldCase("FOLDCASE"sv)); + REQUIRE(FoldCase(u8"f\xF6ldcase"sv) == FoldCase(u8"F\xD6LDCASE"sv)); + REQUIRE(FoldCase(u8"foldc\x430se"sv) == FoldCase(u8"FOLDC\x410SE"sv)); +} + +TEST_CASE("ExpandEnvironmentVariables", "[strings]") +{ + wchar_t buffer[MAX_PATH]; + GetTempPathW(ARRAYSIZE(buffer), buffer); + + std::wstring tempPath = buffer; + if (!tempPath.empty() && tempPath.back() == '\\') + { + tempPath.resize(tempPath.size() - 1); + } + + REQUIRE(ExpandEnvironmentVariables(L"%TEMP%") == tempPath); +} + +TEST_CASE("PathOutput", "[strings]") +{ + std::string original = "\xe6\xb5\x8b\xe8\xaf\x95"; + std::filesystem::path path = ConvertToUTF16(original); + AICLI_LOG(Test, Info, << path); + + std::istringstream in; + std::ostringstream out; + AppInstaller::CLI::Execution::Reporter reporter{ out, in }; + + reporter.Info() << path; + + std::string output = out.str(); + REQUIRE(output.substr(output.size() - original.size()) == original); +} + +TEST_CASE("ReplaceWhileCopying", "[strings]") +{ + REQUIRE(ReplaceWhileCopying(L"A red apple", L"red", L"green") == L"A green apple"); + REQUIRE(ReplaceWhileCopying(L"A red, red apple", L"red", L"green") == L"A green, green apple"); + REQUIRE(ReplaceWhileCopying(L"A red, red apple", L"ed", L"ad") == L"A rad, rad apple"); + REQUIRE(ReplaceWhileCopying(L"A red apple", L"p", L"f") == L"A red affle"); + REQUIRE(ReplaceWhileCopying(L"A red apple", L"", L"green") == L"A red apple"); +} + +TEST_CASE("MakeSuitablePathPart", "[strings]") +{ + REQUIRE(MakeSuitablePathPart("A\\B") == "A_B"); + REQUIRE(MakeSuitablePathPart("A\\B/") == "A_B_"); + REQUIRE(MakeSuitablePathPart("*AB") == "_AB"); + REQUIRE(MakeSuitablePathPart(u8"f*\xF6*ldcase") == u8"f_\xF6_ldcase"); + REQUIRE(MakeSuitablePathPart(".") == "_"); + REQUIRE(MakeSuitablePathPart("..") == "._"); + REQUIRE(MakeSuitablePathPart(std::string(300, ' ')) == SHA256::ConvertToString(SHA256::ComputeHash(std::string(300, ' ')))); + REQUIRE_THROWS_HR(MakeSuitablePathPart("COM1"), E_INVALIDARG); + REQUIRE_THROWS_HR(MakeSuitablePathPart("NUL.txt"), E_INVALIDARG); +} + +TEST_CASE("GetFileNameFromURI", "[strings]") +{ + REQUIRE(GetFileNameFromURI("https://github.com/microsoft/winget-cli/pull/1722").u8string() == "1722"); + REQUIRE(GetFileNameFromURI("https://github.com/microsoft/winget-cli/README.md").u8string() == "README.md"); + REQUIRE(GetFileNameFromURI("https://microsoft.com/").u8string() == ""); +} + +void ValidateSplitFileName(std::string_view uri, std::string_view base, std::string_view fileName) +{ + auto split = SplitFileNameFromURI(uri); + REQUIRE(split.first == base); + REQUIRE(split.second.u8string() == fileName); +} + +TEST_CASE("SplitFileNameFromURI", "[strings]") +{ + ValidateSplitFileName("https://github.com/microsoft/winget-cli/pull/1722", "https://github.com/microsoft/winget-cli/pull/", "1722"); + ValidateSplitFileName("https://github.com/microsoft/winget-cli/README.md", "https://github.com/microsoft/winget-cli/", "README.md"); + ValidateSplitFileName("https://microsoft.com/", "https://microsoft.com/", ""); +} + +TEST_CASE("SplitIntoWords", "[strings]") +{ + REQUIRE(SplitIntoWords("A B") == std::vector{ "A", "B" }); + REQUIRE(SplitIntoWords("Some-Thing") == std::vector{ "Some", "Thing" }); + + // 私のテスト = "My test" according to an online translator + // Split as "私" "の" "テスト" + REQUIRE(SplitIntoWords("\xe7\xa7\x81\xe3\x81\xae\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88") == std::vector{ "\xe7\xa7\x81", "\xe3\x81\xae", "\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88" }); +} + +TEST_CASE("ReplaceEmbeddedNullCharacters", "[strings]") +{ + std::string test = "Test Parts"; + test[4] = '\0'; + ReplaceEmbeddedNullCharacters(test); + REQUIRE(test == "Test Parts"); +} + +TEST_CASE("HexStrings", "[strings]") +{ + std::vector buffer{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 }; + std::string value = "000102030405060708090a0b0c0d0e0f"; + + REQUIRE(value == ConvertToHexString(buffer)); + REQUIRE(std::equal(buffer.begin(), buffer.end(), ParseFromHexString(value).begin())); +} + +TEST_CASE("Join", "[strings]") +{ + std::vector list_0{ }; + std::vector list_1{ "A"_lis }; + std::vector list_2{ "A"_lis, "B"_lis }; + + REQUIRE(""_lis == Join(", "_liv, list_0)); + REQUIRE("A"_lis == Join(", "_liv, list_1)); + REQUIRE("A, B"_lis == Join(", "_liv, list_2)); + REQUIRE("AB"_lis == Join(""_liv, list_2)); +} + +TEST_CASE("Format", "[strings]") +{ + REQUIRE("First Second" == Format("{0} {1}", "First", "Second")); + REQUIRE("First Second" == Format("{1} {0}", "Second", "First")); + REQUIRE("First Second" == Format("{0} {1}", "First", "Second", "(Extra", "Input", "Ignored)")); + REQUIRE("First Second First Second" == Format("{0} {1} {0} {1}", "First", "Second")); + + // Note: C++20 std::format will throw an exception for this test case + REQUIRE("First {1}" == Format("{0} {1}", "First")); +} + +TEST_CASE("SplitIntoLines", "[strings]") +{ + REQUIRE(SplitIntoLines("Boring test") == std::vector{ "Boring test" }); + REQUIRE(SplitIntoLines( + "I'm Luffy! The Man Who Will Become the Pirate King!\r-Monkey D. Luffy") == std::vector{ "I'm Luffy! The Man Who Will Become the Pirate King!", "-Monkey D. Luffy" }); + REQUIRE(SplitIntoLines( + "I want live!\n-Nico Robin") == std::vector{ "I want live!", "-Nico Robin" }); + REQUIRE(SplitIntoLines( + "You want my treasure?\rYou can have it!\nI left everything I gathered in one place!\r\nYou just have to find it!") + == std::vector{ "You want my treasure?", "You can have it!", "I left everything I gathered in one place!", "You just have to find it!" }); +} + +TEST_CASE("SplitWithSeparator", "[strings]") +{ + std::vector test1 = Split("first;second;third"s, ';'); + REQUIRE(test1.size() == 3); + REQUIRE(test1[0] == "first"); + REQUIRE(test1[1] == "second"); + REQUIRE(test1[2] == "third"); + + std::vector test2 = Split("two spaces"s, ' '); + REQUIRE(test2.size() == 3); + REQUIRE(test2[0] == "two"); + REQUIRE(test2[1] == ""); + REQUIRE(test2[2] == "spaces"); + + std::vector test3 = Split("test"s, '.'); + REQUIRE(test3.size() == 1); + REQUIRE(test3[0] == "test"); + + std::vector test4 = Split(" trim | spaces "s, '|', true); + REQUIRE(test4.size() == 2); + REQUIRE(test4[0] == "trim"); + REQUIRE(test4[1] == "spaces"); + + std::vector test5 = Split(L" trim /spaces / "sv, '/', true); + REQUIRE(test5.size() == 3); + REQUIRE(test5[0] == L"trim"); + REQUIRE(test5[1] == L"spaces"); + REQUIRE(test5[2] == L""); + + std::vector test6 = Split(L" "sv, '/', true); + REQUIRE(test6.size() == 1); + REQUIRE(test6[0] == L""); + + std::vector test7 = Split(""s, ';'); + REQUIRE(test7.size() == 1); + REQUIRE(test7[0] == ""); +} + +TEST_CASE("ConvertGuid", "[strings]") +{ + std::string validGuidString = "{4d1e55b2-f16f-11cf-88cb-001111000030}"; + GUID guid = { 0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 }; + + REQUIRE(CaseInsensitiveEquals(ConvertGuidToString(guid), validGuidString)); +} + +TEST_CASE("FindControlCodeToConvert", "[strings]") +{ + REQUIRE(FindControlCodeToConvert("No codes") == std::string::npos); + REQUIRE(FindControlCodeToConvert("Allowed codes: \t\r\n") == std::string::npos); + REQUIRE(FindControlCodeToConvert("\x1bSkipped code", 1) == std::string::npos); + + REQUIRE(FindControlCodeToConvert("\x1bUnskipped code") == 0); + REQUIRE(FindControlCodeToConvert("Escape code: \x1b") == 13); + + std::string_view allCodes{ "\x0\x1\x2\x3\x4\x5\x6\x7\x8\xb\xc\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"sv }; + for (size_t i = 0; i < allCodes.length(); ++i) + { + REQUIRE(FindControlCodeToConvert(allCodes, i) == i); + } +} + +TEST_CASE("ConvertControlCodesToPictures", "[strings]") +{ + REQUIRE(ConvertControlCodesToPictures("No codes") == "No codes"); + REQUIRE(ConvertControlCodesToPictures("Allowed codes: \t\r\n") == "Allowed codes: \t\r\n"); + + REQUIRE(ConvertControlCodesToPictures("\x1b Code First") == ConvertToUTF8(L"\x241b Code First")); + REQUIRE(ConvertControlCodesToPictures("Escape code: \x1b") == ConvertToUTF8(L"Escape code: \x241b")); + + std::string_view allCodes{ "\x0\x1\x2\x3\x4\x5\x6\x7\x8\xb\xc\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x7f"sv }; + std::wstring_view allPictures{ L"\x2400\x2401\x2402\x2403\x2404\x2405\x2406\x2407\x2408\x240b\x240c\x240e\x240f\x2410\x2411\x2412\x2413\x2414\x2415\x2416\x2417\x2418\x2419\x241a\x241b\x241c\x241d\x241e\x241f\x2421"sv }; + + REQUIRE(ConvertControlCodesToPictures(allCodes) == ConvertToUTF8(allPictures)); +} + +TEST_CASE("IsValidWindowsFeaturePattern_AllFound_True", "[strings][111981]") +{ + for (const auto& name : { + "IIS-ODBCLogging", + "NetFx3", + "SMB1Protocol", + }) + { + INFO(name); + REQUIRE(IsValidWindowsFeaturePattern(name)); + } +} + +TEST_CASE("IsValidWindowsFeaturePattern_Bad_False", "[strings][111981]") +{ + for (const auto& name : { + "MediaPlayback /LogPath:C:\\file.txt", + "\"MediaPlayback /LogPath:C:\\file.txt\"", + }) + { + INFO(name); + REQUIRE(!IsValidWindowsFeaturePattern(name)); + } +} diff --git a/src/AppInstallerCLITests/Synchronization.cpp b/src/AppInstallerCLITests/Synchronization.cpp index 6017babd61..299e3a19cc 100644 --- a/src/AppInstallerCLITests/Synchronization.cpp +++ b/src/AppInstallerCLITests/Synchronization.cpp @@ -1,63 +1,63 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" - -#include - -using namespace AppInstaller::Synchronization; - -TEST_CASE("CPIL_BlocksOthers", "[CrossProcessInstallLock]") -{ - wil::unique_event signal; - signal.create(); - AppInstaller::ProgressCallback progress; - - { - CrossProcessInstallLock mainThreadLock; - mainThreadLock.Acquire(progress); - - std::thread otherThread([&signal, &progress]() { - CrossProcessInstallLock otherThreadLock; - otherThreadLock.Acquire(progress); - signal.SetEvent(); - }); - // In the event of bugs, we don't want to block the test waiting forever - otherThread.detach(); - - REQUIRE(!signal.wait(500)); - } - - // Upon release of the writer, the other thread should signal - REQUIRE(signal.wait(500)); -} - -TEST_CASE("CPIL_CancelEndsWait", "[CrossProcessInstallLock]") -{ - wil::unique_event signal; - signal.create(); - AppInstaller::ProgressCallback progress; - - CrossProcessInstallLock mainThreadLock; - mainThreadLock.Acquire(progress); - - std::optional otherThreadAcquireResult = std::nullopt; - - std::thread otherThread([&]() { - CrossProcessInstallLock otherThreadLock; - otherThreadAcquireResult = otherThreadLock.Acquire(progress); - signal.SetEvent(); - }); - // In the event of bugs, we don't want to block the test waiting forever - otherThread.detach(); - - REQUIRE(!signal.wait(500)); - - progress.Cancel(); - - // Upon release of the writer, the other thread should signal - REQUIRE(signal.wait(500)); - - REQUIRE(otherThreadAcquireResult.has_value()); - REQUIRE(!otherThreadAcquireResult.value()); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" + +#include + +using namespace AppInstaller::Synchronization; + +TEST_CASE("CPIL_BlocksOthers", "[CrossProcessInstallLock]") +{ + wil::unique_event signal; + signal.create(); + AppInstaller::ProgressCallback progress; + + { + CrossProcessInstallLock mainThreadLock; + mainThreadLock.Acquire(progress); + + std::thread otherThread([&signal, &progress]() { + CrossProcessInstallLock otherThreadLock; + otherThreadLock.Acquire(progress); + signal.SetEvent(); + }); + // In the event of bugs, we don't want to block the test waiting forever + otherThread.detach(); + + REQUIRE(!signal.wait(500)); + } + + // Upon release of the writer, the other thread should signal + REQUIRE(signal.wait(500)); +} + +TEST_CASE("CPIL_CancelEndsWait", "[CrossProcessInstallLock]") +{ + wil::unique_event signal; + signal.create(); + AppInstaller::ProgressCallback progress; + + CrossProcessInstallLock mainThreadLock; + mainThreadLock.Acquire(progress); + + std::optional otherThreadAcquireResult = std::nullopt; + + std::thread otherThread([&]() { + CrossProcessInstallLock otherThreadLock; + otherThreadAcquireResult = otherThreadLock.Acquire(progress); + signal.SetEvent(); + }); + // In the event of bugs, we don't want to block the test waiting forever + otherThread.detach(); + + REQUIRE(!signal.wait(500)); + + progress.Cancel(); + + // Upon release of the writer, the other thread should signal + REQUIRE(signal.wait(500)); + + REQUIRE(otherThreadAcquireResult.has_value()); + REQUIRE(!otherThreadAcquireResult.value()); +} diff --git a/src/AppInstallerCLITests/TestCommon.cpp b/src/AppInstallerCLITests/TestCommon.cpp index 1b83319edd..3a527aca85 100644 --- a/src/AppInstallerCLITests/TestCommon.cpp +++ b/src/AppInstallerCLITests/TestCommon.cpp @@ -1,414 +1,414 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestHooks.h" -#include -#include -#include -#include - -using namespace AppInstaller; - -namespace TestCommon -{ - namespace - { - int initRand() - { - srand(static_cast(time(NULL))); - return rand(); - }; - - inline int getRand() - { - static int randStart = initRand(); - return randStart++; - } - - inline std::filesystem::path GetFilePath(std::filesystem::path path, const std::string& baseName, const std::string& baseExt) - { - path /= baseName + std::to_string(getRand()) + baseExt; - return path; - } - - inline std::filesystem::path GetTempFilePath(const std::string& baseName, const std::string& baseExt) - { - std::filesystem::path tempFilePath = std::filesystem::temp_directory_path(); - return GetFilePath(tempFilePath, baseName, baseExt); - } - - static TempFileDestructionBehavior s_TempFileDestructorBehavior = TempFileDestructionBehavior::Delete; - static std::vector s_TempFilesOnFile; - - static std::filesystem::path s_TestDataFileBasePath{}; - - bool CleanVolatileTestRoot(HKEY root) - { - THROW_IF_WIN32_ERROR(RegDeleteTreeW(root, nullptr)); - return true; - } - } - - TempFile::TempFile(const std::string& baseName, const std::string& baseExt, std::optional keepTempFile) - { - _filepath = GetTempFilePath(baseName, baseExt); - if (!keepTempFile) - { - std::filesystem::remove(_filepath); - } - } - - TempFile::TempFile(const std::filesystem::path& parent, const std::string& baseName, const std::string& baseExt, std::optional keepTempFile) - { - _filepath = GetFilePath(parent, baseName, baseExt); - if (!keepTempFile) - { - std::filesystem::remove(_filepath); - } - } - - TempFile::TempFile(const std::filesystem::path& filePath, std::optional keepTempFile) - { - if (filePath.is_relative()) - { - _filepath = std::filesystem::temp_directory_path(); - _filepath /= filePath; - } - else - { - _filepath = filePath; - } - if (!keepTempFile) - { - std::filesystem::remove(_filepath); - } - } - - TempFile::~TempFile() try - { - if (m_destructionToken) - { - switch (s_TempFileDestructorBehavior) - { - case TempFileDestructionBehavior::Delete: - std::filesystem::remove_all(_filepath); - break; - case TempFileDestructionBehavior::Keep: - break; - case TempFileDestructionBehavior::ShellExecuteOnFailure: - s_TempFilesOnFile.emplace_back(std::move(_filepath)); - break; - } - } - } - CATCH_LOG_RETURN() - - void TempFile::Rename(const std::filesystem::path& newFilePath) - { - std::filesystem::rename(GetPath(), newFilePath); - _filepath = newFilePath; - } - - void TempFile::Release() - { - m_destructionToken = false; - } - - void TempFile::SetDestructorBehavior(TempFileDestructionBehavior behavior) - { - s_TempFileDestructorBehavior = behavior; - } - - void TempFile::SetTestFailed(bool failed) - { - if (failed) - { - for (const auto& path : s_TempFilesOnFile) - { - SHELLEXECUTEINFOW seinfo{}; - seinfo.cbSize = sizeof(seinfo); - seinfo.lpVerb = L"open"; - seinfo.lpFile = path.c_str(); - - ShellExecuteExW(&seinfo); - } - } - else - { - s_TempFilesOnFile.clear(); - } - } - - TempDirectory::TempDirectory(const std::string& baseName, bool create) - { - _filepath = GetTempFilePath(baseName, ""); - if (create) - { - if (std::filesystem::exists(_filepath)) - { - std::filesystem::remove_all(_filepath); - } - std::filesystem::create_directories(_filepath); - } - } - - TempFile TempDirectory::CreateTempFile(const std::string& baseName, const std::string& baseExt) - { - return { _filepath, baseName, baseExt }; - } - - TempFile TempDirectory::CreateTempFile(const std::string& name) - { - return { _filepath / name }; - } - - std::filesystem::path TestDataFile::GetPath() const - { - std::filesystem::path result = s_TestDataFileBasePath; - result /= m_path; - return result; - } - - void TestDataFile::SetTestDataBasePath(const std::filesystem::path& path) - { - s_TestDataFileBasePath = path; - } - - void TestProgress::OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) - { - if (m_OnProgress) - { - m_OnProgress(current, maximum, type); - } - } - - void TestProgress::SetProgressMessage(std::string_view) - { - } - - void TestProgress::BeginProgress() - { - } - - void TestProgress::EndProgress(bool) - { - } - - bool TestProgress::IsCancelledBy(AppInstaller::CancelReason) - { - return false; - } - - AppInstaller::IProgressCallback::CancelFunctionRemoval TestProgress::SetCancellationFunction(std::function&&) - { - return {}; - } - - wil::unique_hkey RegCreateVolatileTestRoot() - { - // First create/open the real test root - wil::unique_hkey root; - THROW_IF_WIN32_ERROR(RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\WinGet\\TestRoot", 0, nullptr, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, nullptr, &root, nullptr)); - - static bool s_ignored = CleanVolatileTestRoot(root.get()); - - // Create a random name - GUID name{}; - (void)CoCreateGuid(&name); - - wchar_t nameBuffer[256]; - (void)StringFromGUID2(name, nameBuffer, ARRAYSIZE(nameBuffer)); - - return RegCreateVolatileSubKey(root.get(), nameBuffer); - } - - wil::unique_hkey RegCreateVolatileSubKey(HKEY parent, const std::wstring& name) - { - wil::unique_hkey result; - THROW_IF_WIN32_ERROR(RegCreateKeyExW(parent, name.c_str(), 0, nullptr, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, nullptr, &result, nullptr)); - return result; - } - - void SetRegistryValue(HKEY key, const std::wstring& name, const std::wstring& value, DWORD type) - { - THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, type, reinterpret_cast(value.c_str()), static_cast(sizeof(wchar_t) * (value.size() + 1)))); - } - - void SetRegistryValue(HKEY key, const std::wstring& name, const std::vector& value, DWORD type) - { - THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, type, reinterpret_cast(value.data()), static_cast(value.size()))); - } - - void SetRegistryValue(HKEY key, const std::wstring& name, DWORD value) - { - THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, REG_DWORD, reinterpret_cast(&value), sizeof(DWORD))); - } - - void EnableDevMode(bool enable) - { - wil::unique_hkey result; - THROW_IF_WIN32_ERROR(RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, KEY_ALL_ACCESS|KEY_WOW64_64KEY, &result)); - SetRegistryValue(result.get(), L"AllowDevelopmentWithoutDevLicense", (enable ? 1 : 0)); - } - - TestUserSettings::TestUserSettings(bool keepFileSettings) - { - if (!keepFileSettings) - { - m_settings.clear(); - } - - AppInstaller::Settings::SetUserSettingsOverride(this); - } - - TestUserSettings::~TestUserSettings() - { - AppInstaller::Settings::SetUserSettingsOverride(nullptr); - } - - std::unique_ptr TestUserSettings::EnableExperimentalFeature(Settings::ExperimentalFeature::Feature feature, bool keepFileSettings) - { - std::unique_ptr result = std::make_unique(keepFileSettings); - - // Due to the template usage, this needs to be updated for any features that want to use it. - // Currently no feature is used. Uncomment below when a feature needs to be used. - // switch (feature) - // { - // default: - // THROW_HR(E_NOTIMPL); - // } - UNREFERENCED_PARAMETER(feature); - - return result; - } - - bool InstallCertFromSignedPackage(const std::filesystem::path& package) - { - auto [certContext, certStore] = AppInstaller::Msix::GetCertContextFromMsix(package); - - wil::unique_hcertstore trustedPeopleStore; - trustedPeopleStore.reset(CertOpenStore( - CERT_STORE_PROV_SYSTEM_W, - PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, - NULL, - CERT_SYSTEM_STORE_LOCAL_MACHINE, - L"TrustedPeople")); - THROW_LAST_ERROR_IF(!trustedPeopleStore.get()); - - wil::unique_cert_context existingCert; - existingCert.reset(CertFindCertificateInStore( - trustedPeopleStore.get(), - PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, - 0, - CERT_FIND_EXISTING, - certContext.get(), - nullptr)); - - // Add if it does not already exist in the store - if (!existingCert.get()) - { - THROW_LAST_ERROR_IF(!CertAddCertificateContextToStore( - trustedPeopleStore.get(), - certContext.get(), - CERT_STORE_ADD_NEW, - nullptr)); - - return true; - } - - return false; - } - - bool UninstallCertFromSignedPackage(const std::filesystem::path& package) - { - auto [certContext, certStore] = AppInstaller::Msix::GetCertContextFromMsix(package); - - wil::unique_hcertstore trustedPeopleStore; - trustedPeopleStore.reset(CertOpenStore( - CERT_STORE_PROV_SYSTEM_W, - PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, - NULL, - CERT_SYSTEM_STORE_LOCAL_MACHINE, - L"TrustedPeople")); - THROW_LAST_ERROR_IF(!trustedPeopleStore.get()); - - wil::unique_cert_context existingCert; - existingCert.reset(CertFindCertificateInStore( - trustedPeopleStore.get(), - PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, - 0, - CERT_FIND_EXISTING, - certContext.get(), - nullptr)); - - // Remove if it exists in the store - if (existingCert.get()) - { - THROW_LAST_ERROR_IF(!CertDeleteCertificateFromStore(existingCert.get())); - - return true; - } - - return false; - } - - bool GetMsixPackageManifestReader(std::string_view testFileName, IAppxManifestReader** manifestReader) - { - // Locate test file - TestDataFile testFile(testFileName); - auto path = testFile.GetPath().u8string(); - - // Get the stream for the test file - auto stream = AppInstaller::Utility::GetReadOnlyStreamFromURI(path); - - // Get manifest from package reader - Microsoft::WRL::ComPtr packageReader; - return AppInstaller::Msix::GetPackageReader(stream.Get(), &packageReader) - && SUCCEEDED(packageReader->GetManifest(manifestReader)); - } - - std::string RemoveConsoleFormat(const std::string& str) - { - // We are looking something that starts with "\x1b[0m" - if (!str.empty() && str[0] == '\x1b') - { - // Find first m - auto pos = str.find("m"); - if (pos != std::string::npos) - { - return str.substr(pos + 1); - } - } - - return str; - } - - Json::Value ConvertToJson(const std::string& content) - { - auto contentClean = RemoveConsoleFormat(content); - - Json::Value root; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); - std::string error; - - if (!reader->parse(contentClean.c_str(), contentClean.c_str() + contentClean.size(), &root, &error)) - { - throw error; - } - - return root; - } - - void SetTestPathOverrides() - { - // Force all tests to run against settings inside this container. - // This prevents test runs from trashing the users actual settings. - Runtime::TestHook_SetPathOverride(Runtime::PathName::LocalState, Runtime::GetPathTo(Runtime::PathName::LocalState) / "Tests"); - Runtime::TestHook_SetPathOverride(Runtime::PathName::UserFileSettings, Runtime::GetPathTo(Runtime::PathName::UserFileSettings) / "Tests"); - Runtime::TestHook_SetPathOverride(Runtime::PathName::StandardSettings, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "Tests"); - Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForRead, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "WinGet_SecureSettings_Tests"); - Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForWrite, Runtime::GetPathDetailsFor(Runtime::PathName::SecureSettingsForRead)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestHooks.h" +#include +#include +#include +#include + +using namespace AppInstaller; + +namespace TestCommon +{ + namespace + { + int initRand() + { + srand(static_cast(time(NULL))); + return rand(); + }; + + inline int getRand() + { + static int randStart = initRand(); + return randStart++; + } + + inline std::filesystem::path GetFilePath(std::filesystem::path path, const std::string& baseName, const std::string& baseExt) + { + path /= baseName + std::to_string(getRand()) + baseExt; + return path; + } + + inline std::filesystem::path GetTempFilePath(const std::string& baseName, const std::string& baseExt) + { + std::filesystem::path tempFilePath = std::filesystem::temp_directory_path(); + return GetFilePath(tempFilePath, baseName, baseExt); + } + + static TempFileDestructionBehavior s_TempFileDestructorBehavior = TempFileDestructionBehavior::Delete; + static std::vector s_TempFilesOnFile; + + static std::filesystem::path s_TestDataFileBasePath{}; + + bool CleanVolatileTestRoot(HKEY root) + { + THROW_IF_WIN32_ERROR(RegDeleteTreeW(root, nullptr)); + return true; + } + } + + TempFile::TempFile(const std::string& baseName, const std::string& baseExt, std::optional keepTempFile) + { + _filepath = GetTempFilePath(baseName, baseExt); + if (!keepTempFile) + { + std::filesystem::remove(_filepath); + } + } + + TempFile::TempFile(const std::filesystem::path& parent, const std::string& baseName, const std::string& baseExt, std::optional keepTempFile) + { + _filepath = GetFilePath(parent, baseName, baseExt); + if (!keepTempFile) + { + std::filesystem::remove(_filepath); + } + } + + TempFile::TempFile(const std::filesystem::path& filePath, std::optional keepTempFile) + { + if (filePath.is_relative()) + { + _filepath = std::filesystem::temp_directory_path(); + _filepath /= filePath; + } + else + { + _filepath = filePath; + } + if (!keepTempFile) + { + std::filesystem::remove(_filepath); + } + } + + TempFile::~TempFile() try + { + if (m_destructionToken) + { + switch (s_TempFileDestructorBehavior) + { + case TempFileDestructionBehavior::Delete: + std::filesystem::remove_all(_filepath); + break; + case TempFileDestructionBehavior::Keep: + break; + case TempFileDestructionBehavior::ShellExecuteOnFailure: + s_TempFilesOnFile.emplace_back(std::move(_filepath)); + break; + } + } + } + CATCH_LOG_RETURN() + + void TempFile::Rename(const std::filesystem::path& newFilePath) + { + std::filesystem::rename(GetPath(), newFilePath); + _filepath = newFilePath; + } + + void TempFile::Release() + { + m_destructionToken = false; + } + + void TempFile::SetDestructorBehavior(TempFileDestructionBehavior behavior) + { + s_TempFileDestructorBehavior = behavior; + } + + void TempFile::SetTestFailed(bool failed) + { + if (failed) + { + for (const auto& path : s_TempFilesOnFile) + { + SHELLEXECUTEINFOW seinfo{}; + seinfo.cbSize = sizeof(seinfo); + seinfo.lpVerb = L"open"; + seinfo.lpFile = path.c_str(); + + ShellExecuteExW(&seinfo); + } + } + else + { + s_TempFilesOnFile.clear(); + } + } + + TempDirectory::TempDirectory(const std::string& baseName, bool create) + { + _filepath = GetTempFilePath(baseName, ""); + if (create) + { + if (std::filesystem::exists(_filepath)) + { + std::filesystem::remove_all(_filepath); + } + std::filesystem::create_directories(_filepath); + } + } + + TempFile TempDirectory::CreateTempFile(const std::string& baseName, const std::string& baseExt) + { + return { _filepath, baseName, baseExt }; + } + + TempFile TempDirectory::CreateTempFile(const std::string& name) + { + return { _filepath / name }; + } + + std::filesystem::path TestDataFile::GetPath() const + { + std::filesystem::path result = s_TestDataFileBasePath; + result /= m_path; + return result; + } + + void TestDataFile::SetTestDataBasePath(const std::filesystem::path& path) + { + s_TestDataFileBasePath = path; + } + + void TestProgress::OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) + { + if (m_OnProgress) + { + m_OnProgress(current, maximum, type); + } + } + + void TestProgress::SetProgressMessage(std::string_view) + { + } + + void TestProgress::BeginProgress() + { + } + + void TestProgress::EndProgress(bool) + { + } + + bool TestProgress::IsCancelledBy(AppInstaller::CancelReason) + { + return false; + } + + AppInstaller::IProgressCallback::CancelFunctionRemoval TestProgress::SetCancellationFunction(std::function&&) + { + return {}; + } + + wil::unique_hkey RegCreateVolatileTestRoot() + { + // First create/open the real test root + wil::unique_hkey root; + THROW_IF_WIN32_ERROR(RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\\Microsoft\\WinGet\\TestRoot", 0, nullptr, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, nullptr, &root, nullptr)); + + static bool s_ignored = CleanVolatileTestRoot(root.get()); + + // Create a random name + GUID name{}; + (void)CoCreateGuid(&name); + + wchar_t nameBuffer[256]; + (void)StringFromGUID2(name, nameBuffer, ARRAYSIZE(nameBuffer)); + + return RegCreateVolatileSubKey(root.get(), nameBuffer); + } + + wil::unique_hkey RegCreateVolatileSubKey(HKEY parent, const std::wstring& name) + { + wil::unique_hkey result; + THROW_IF_WIN32_ERROR(RegCreateKeyExW(parent, name.c_str(), 0, nullptr, REG_OPTION_VOLATILE, KEY_ALL_ACCESS, nullptr, &result, nullptr)); + return result; + } + + void SetRegistryValue(HKEY key, const std::wstring& name, const std::wstring& value, DWORD type) + { + THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, type, reinterpret_cast(value.c_str()), static_cast(sizeof(wchar_t) * (value.size() + 1)))); + } + + void SetRegistryValue(HKEY key, const std::wstring& name, const std::vector& value, DWORD type) + { + THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, type, reinterpret_cast(value.data()), static_cast(value.size()))); + } + + void SetRegistryValue(HKEY key, const std::wstring& name, DWORD value) + { + THROW_IF_WIN32_ERROR(RegSetValueExW(key, name.c_str(), 0, REG_DWORD, reinterpret_cast(&value), sizeof(DWORD))); + } + + void EnableDevMode(bool enable) + { + wil::unique_hkey result; + THROW_IF_WIN32_ERROR(RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock", 0, KEY_ALL_ACCESS|KEY_WOW64_64KEY, &result)); + SetRegistryValue(result.get(), L"AllowDevelopmentWithoutDevLicense", (enable ? 1 : 0)); + } + + TestUserSettings::TestUserSettings(bool keepFileSettings) + { + if (!keepFileSettings) + { + m_settings.clear(); + } + + AppInstaller::Settings::SetUserSettingsOverride(this); + } + + TestUserSettings::~TestUserSettings() + { + AppInstaller::Settings::SetUserSettingsOverride(nullptr); + } + + std::unique_ptr TestUserSettings::EnableExperimentalFeature(Settings::ExperimentalFeature::Feature feature, bool keepFileSettings) + { + std::unique_ptr result = std::make_unique(keepFileSettings); + + // Due to the template usage, this needs to be updated for any features that want to use it. + // Currently no feature is used. Uncomment below when a feature needs to be used. + // switch (feature) + // { + // default: + // THROW_HR(E_NOTIMPL); + // } + UNREFERENCED_PARAMETER(feature); + + return result; + } + + bool InstallCertFromSignedPackage(const std::filesystem::path& package) + { + auto [certContext, certStore] = AppInstaller::Msix::GetCertContextFromMsix(package); + + wil::unique_hcertstore trustedPeopleStore; + trustedPeopleStore.reset(CertOpenStore( + CERT_STORE_PROV_SYSTEM_W, + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL, + CERT_SYSTEM_STORE_LOCAL_MACHINE, + L"TrustedPeople")); + THROW_LAST_ERROR_IF(!trustedPeopleStore.get()); + + wil::unique_cert_context existingCert; + existingCert.reset(CertFindCertificateInStore( + trustedPeopleStore.get(), + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + 0, + CERT_FIND_EXISTING, + certContext.get(), + nullptr)); + + // Add if it does not already exist in the store + if (!existingCert.get()) + { + THROW_LAST_ERROR_IF(!CertAddCertificateContextToStore( + trustedPeopleStore.get(), + certContext.get(), + CERT_STORE_ADD_NEW, + nullptr)); + + return true; + } + + return false; + } + + bool UninstallCertFromSignedPackage(const std::filesystem::path& package) + { + auto [certContext, certStore] = AppInstaller::Msix::GetCertContextFromMsix(package); + + wil::unique_hcertstore trustedPeopleStore; + trustedPeopleStore.reset(CertOpenStore( + CERT_STORE_PROV_SYSTEM_W, + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + NULL, + CERT_SYSTEM_STORE_LOCAL_MACHINE, + L"TrustedPeople")); + THROW_LAST_ERROR_IF(!trustedPeopleStore.get()); + + wil::unique_cert_context existingCert; + existingCert.reset(CertFindCertificateInStore( + trustedPeopleStore.get(), + PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, + 0, + CERT_FIND_EXISTING, + certContext.get(), + nullptr)); + + // Remove if it exists in the store + if (existingCert.get()) + { + THROW_LAST_ERROR_IF(!CertDeleteCertificateFromStore(existingCert.get())); + + return true; + } + + return false; + } + + bool GetMsixPackageManifestReader(std::string_view testFileName, IAppxManifestReader** manifestReader) + { + // Locate test file + TestDataFile testFile(testFileName); + auto path = testFile.GetPath().u8string(); + + // Get the stream for the test file + auto stream = AppInstaller::Utility::GetReadOnlyStreamFromURI(path); + + // Get manifest from package reader + Microsoft::WRL::ComPtr packageReader; + return AppInstaller::Msix::GetPackageReader(stream.Get(), &packageReader) + && SUCCEEDED(packageReader->GetManifest(manifestReader)); + } + + std::string RemoveConsoleFormat(const std::string& str) + { + // We are looking something that starts with "\x1b[0m" + if (!str.empty() && str[0] == '\x1b') + { + // Find first m + auto pos = str.find("m"); + if (pos != std::string::npos) + { + return str.substr(pos + 1); + } + } + + return str; + } + + Json::Value ConvertToJson(const std::string& content) + { + auto contentClean = RemoveConsoleFormat(content); + + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + std::string error; + + if (!reader->parse(contentClean.c_str(), contentClean.c_str() + contentClean.size(), &root, &error)) + { + throw error; + } + + return root; + } + + void SetTestPathOverrides() + { + // Force all tests to run against settings inside this container. + // This prevents test runs from trashing the users actual settings. + Runtime::TestHook_SetPathOverride(Runtime::PathName::LocalState, Runtime::GetPathTo(Runtime::PathName::LocalState) / "Tests"); + Runtime::TestHook_SetPathOverride(Runtime::PathName::UserFileSettings, Runtime::GetPathTo(Runtime::PathName::UserFileSettings) / "Tests"); + Runtime::TestHook_SetPathOverride(Runtime::PathName::StandardSettings, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "Tests"); + Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForRead, Runtime::GetPathTo(Runtime::PathName::StandardSettings) / "WinGet_SecureSettings_Tests"); + Runtime::TestHook_SetPathOverride(Runtime::PathName::SecureSettingsForWrite, Runtime::GetPathDetailsFor(Runtime::PathName::SecureSettingsForRead)); + } +} diff --git a/src/AppInstallerCLITests/TestCommon.h b/src/AppInstallerCLITests/TestCommon.h index 9b85a30aa9..9f029ece31 100644 --- a/src/AppInstallerCLITests/TestCommon.h +++ b/src/AppInstallerCLITests/TestCommon.h @@ -1,173 +1,173 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#define REQUIRE_THROWS_HR(_expr_, _hr_) REQUIRE_THROWS_MATCHES(_expr_, wil::ResultException, ::TestCommon::ResultExceptionHRMatcher(_hr_)) - -namespace TestCommon -{ - enum class TempFileDestructionBehavior - { - Delete, - Keep, - ShellExecuteOnFailure, - }; - - struct KeepTempFile {}; - - // Use this to create a temporary file for testing. - struct TempFile - { - TempFile(const std::string& baseName, const std::string& baseExt, std::optional keepTempFile = {}); - TempFile(const std::filesystem::path& parent, const std::string& baseName, const std::string& baseExt, std::optional keepTempFile = {}); - TempFile(const std::filesystem::path& filePath, std::optional keepTempFile = {}); - - TempFile(const TempFile&) = delete; - TempFile& operator=(const TempFile&) = delete; - - TempFile(TempFile&&) = default; - TempFile& operator=(TempFile&&) = default; - - ~TempFile(); - - const std::filesystem::path& GetPath() const { return _filepath; } - operator const std::filesystem::path& () const { return _filepath; } - operator const std::string() const { return _filepath.u8string(); } - - void Rename(const std::filesystem::path& newFilePath); - - void Release(); - - static void SetDestructorBehavior(TempFileDestructionBehavior behavior); - - static void SetTestFailed(bool failed); - - protected: - TempFile() = default; - std::filesystem::path _filepath; - AppInstaller::DestructionToken m_destructionToken{ true }; - }; - - // Use to create a temporary directory for testing. - struct TempDirectory : public TempFile - { - TempDirectory(const std::string& baseName, bool create = true); - - TempFile CreateTempFile(const std::string& baseName, const std::string& baseExt); - TempFile CreateTempFile(const std::string& name); - }; - - // Use this to find a test data file when testing. - struct TestDataFile - { - TestDataFile(const std::filesystem::path& path) : m_path(path) {} - - std::filesystem::path GetPath() const; - operator std::filesystem::path () const { return GetPath(); } - - static void SetTestDataBasePath(const std::filesystem::path& path); - - private: - std::filesystem::path m_path; - }; - - // Matcher that lets us verify wil::ResultExceptions have a specific HR. - struct ResultExceptionHRMatcher : public Catch::Matchers::MatcherBase - { - ResultExceptionHRMatcher(HRESULT hr) : m_expectedHR(hr) {} - - bool match(const wil::ResultException& re) const override - { - return re.GetErrorCode() == m_expectedHR; - } - - std::string describe() const override - { - std::ostringstream result; - result << "has HR == 0x" << AppInstaller::Logging::SetHRFormat << m_expectedHR; - return result.str(); - } - - private: - HRESULT m_expectedHR = S_OK; - }; - - // An IProgressCallback that is easily hooked. - struct TestProgress : public AppInstaller::IProgressCallback - { - // Inherited via IProgressCallback - void BeginProgress() override; - - void OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) override; - - void SetProgressMessage(std::string_view message) override; - - void EndProgress(bool) override; - - bool IsCancelledBy(AppInstaller::CancelReason) override; - - CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; - - std::function m_OnProgress; - }; - - // Creates a volatile key for testing. - wil::unique_hkey RegCreateVolatileTestRoot(); - - // Creates a volatile subkey for testing. - wil::unique_hkey RegCreateVolatileSubKey(HKEY parent, const std::wstring& name); - - // Set registry values. - void SetRegistryValue(HKEY key, const std::wstring& name, const std::wstring& value, DWORD type = REG_SZ); - void SetRegistryValue(HKEY key, const std::wstring& name, const std::vector& value, DWORD type = REG_BINARY); - void SetRegistryValue(HKEY key, const std::wstring& name, DWORD value); - - // Enable or disable developer mode. - void EnableDevMode(bool enable); - - // Override UserSettings using this class. - // Automatically overrides the user settings for the lifetime of this object. - // DOES NOT SUPPORT NESTED USE - struct TestUserSettings : public AppInstaller::Settings::UserSettings - { - TestUserSettings(bool keepFileSettings = false); - ~TestUserSettings(); - - template - void Set(typename AppInstaller::Settings::details::SettingMapping::value_t&& value) - { - m_settings[S].emplace(std::move(value)); - } - - static std::unique_ptr EnableExperimentalFeature(AppInstaller::Settings::ExperimentalFeature::Feature feature, bool keepFileSettings = false); - }; - - // Below cert installation/uninstallation methods require admin privilege, - // tests calling these functions should skip when not running with admin. - bool InstallCertFromSignedPackage(const std::filesystem::path& package); - bool UninstallCertFromSignedPackage(const std::filesystem::path& package); - - // Get manifest reader from a msix file path - bool GetMsixPackageManifestReader(std::string_view testFileName, IAppxManifestReader** manifestReader); - - // Removes console format - std::string RemoveConsoleFormat(const std::string& str); - - // Convert to Json::Value - Json::Value ConvertToJson(const std::string& content); - - // Sets up the test path overrides. - void SetTestPathOverrides(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define REQUIRE_THROWS_HR(_expr_, _hr_) REQUIRE_THROWS_MATCHES(_expr_, wil::ResultException, ::TestCommon::ResultExceptionHRMatcher(_hr_)) + +namespace TestCommon +{ + enum class TempFileDestructionBehavior + { + Delete, + Keep, + ShellExecuteOnFailure, + }; + + struct KeepTempFile {}; + + // Use this to create a temporary file for testing. + struct TempFile + { + TempFile(const std::string& baseName, const std::string& baseExt, std::optional keepTempFile = {}); + TempFile(const std::filesystem::path& parent, const std::string& baseName, const std::string& baseExt, std::optional keepTempFile = {}); + TempFile(const std::filesystem::path& filePath, std::optional keepTempFile = {}); + + TempFile(const TempFile&) = delete; + TempFile& operator=(const TempFile&) = delete; + + TempFile(TempFile&&) = default; + TempFile& operator=(TempFile&&) = default; + + ~TempFile(); + + const std::filesystem::path& GetPath() const { return _filepath; } + operator const std::filesystem::path& () const { return _filepath; } + operator const std::string() const { return _filepath.u8string(); } + + void Rename(const std::filesystem::path& newFilePath); + + void Release(); + + static void SetDestructorBehavior(TempFileDestructionBehavior behavior); + + static void SetTestFailed(bool failed); + + protected: + TempFile() = default; + std::filesystem::path _filepath; + AppInstaller::DestructionToken m_destructionToken{ true }; + }; + + // Use to create a temporary directory for testing. + struct TempDirectory : public TempFile + { + TempDirectory(const std::string& baseName, bool create = true); + + TempFile CreateTempFile(const std::string& baseName, const std::string& baseExt); + TempFile CreateTempFile(const std::string& name); + }; + + // Use this to find a test data file when testing. + struct TestDataFile + { + TestDataFile(const std::filesystem::path& path) : m_path(path) {} + + std::filesystem::path GetPath() const; + operator std::filesystem::path () const { return GetPath(); } + + static void SetTestDataBasePath(const std::filesystem::path& path); + + private: + std::filesystem::path m_path; + }; + + // Matcher that lets us verify wil::ResultExceptions have a specific HR. + struct ResultExceptionHRMatcher : public Catch::Matchers::MatcherBase + { + ResultExceptionHRMatcher(HRESULT hr) : m_expectedHR(hr) {} + + bool match(const wil::ResultException& re) const override + { + return re.GetErrorCode() == m_expectedHR; + } + + std::string describe() const override + { + std::ostringstream result; + result << "has HR == 0x" << AppInstaller::Logging::SetHRFormat << m_expectedHR; + return result.str(); + } + + private: + HRESULT m_expectedHR = S_OK; + }; + + // An IProgressCallback that is easily hooked. + struct TestProgress : public AppInstaller::IProgressCallback + { + // Inherited via IProgressCallback + void BeginProgress() override; + + void OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) override; + + void SetProgressMessage(std::string_view message) override; + + void EndProgress(bool) override; + + bool IsCancelledBy(AppInstaller::CancelReason) override; + + CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; + + std::function m_OnProgress; + }; + + // Creates a volatile key for testing. + wil::unique_hkey RegCreateVolatileTestRoot(); + + // Creates a volatile subkey for testing. + wil::unique_hkey RegCreateVolatileSubKey(HKEY parent, const std::wstring& name); + + // Set registry values. + void SetRegistryValue(HKEY key, const std::wstring& name, const std::wstring& value, DWORD type = REG_SZ); + void SetRegistryValue(HKEY key, const std::wstring& name, const std::vector& value, DWORD type = REG_BINARY); + void SetRegistryValue(HKEY key, const std::wstring& name, DWORD value); + + // Enable or disable developer mode. + void EnableDevMode(bool enable); + + // Override UserSettings using this class. + // Automatically overrides the user settings for the lifetime of this object. + // DOES NOT SUPPORT NESTED USE + struct TestUserSettings : public AppInstaller::Settings::UserSettings + { + TestUserSettings(bool keepFileSettings = false); + ~TestUserSettings(); + + template + void Set(typename AppInstaller::Settings::details::SettingMapping::value_t&& value) + { + m_settings[S].emplace(std::move(value)); + } + + static std::unique_ptr EnableExperimentalFeature(AppInstaller::Settings::ExperimentalFeature::Feature feature, bool keepFileSettings = false); + }; + + // Below cert installation/uninstallation methods require admin privilege, + // tests calling these functions should skip when not running with admin. + bool InstallCertFromSignedPackage(const std::filesystem::path& package); + bool UninstallCertFromSignedPackage(const std::filesystem::path& package); + + // Get manifest reader from a msix file path + bool GetMsixPackageManifestReader(std::string_view testFileName, IAppxManifestReader** manifestReader); + + // Removes console format + std::string RemoveConsoleFormat(const std::string& str); + + // Convert to Json::Value + Json::Value ConvertToJson(const std::string& content); + + // Sets up the test path overrides. + void SetTestPathOverrides(); +} diff --git a/src/AppInstallerCLITests/TestConfiguration.cpp b/src/AppInstallerCLITests/TestConfiguration.cpp index a7a0d74c6d..a2805d6bff 100644 --- a/src/AppInstallerCLITests/TestConfiguration.cpp +++ b/src/AppInstallerCLITests/TestConfiguration.cpp @@ -1,110 +1,110 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestConfiguration.h" - -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Foundation::Collections; -using namespace winrt::Microsoft::Management::Configuration; - -namespace TestCommon -{ - IConfigurationSetProcessor TestConfigurationSetProcessorFactory::CreateSetProcessor(const ConfigurationSet& configurationSet) - { - if (CreateSetProcessorFunc) - { - return CreateSetProcessorFunc(configurationSet); - } - else - { - return winrt::make(); - } - } - - winrt::event_token TestConfigurationSetProcessorFactory::Diagnostics(const EventHandler& handler) - { - return m_diagnostics.add(handler); - } - - void TestConfigurationSetProcessorFactory::Diagnostics(const winrt::event_token& token) noexcept - { - m_diagnostics.remove(token); - } - - DiagnosticLevel TestConfigurationSetProcessorFactory::MinimumLevel() - { - return DiagnosticLevel::Informational; - } - - void TestConfigurationSetProcessorFactory::MinimumLevel(DiagnosticLevel) - { - } - - IConfigurationUnitProcessorDetails TestConfigurationSetProcessor::GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) - { - if (GetUnitProcessorDetailsFunc) - { - return GetUnitProcessorDetailsFunc(unit, detailFlags); - } - else - { - return winrt::make(unit); - } - } - - IConfigurationUnitProcessor TestConfigurationSetProcessor::CreateUnitProcessor(const ConfigurationUnit& unit) - { - if (CreateUnitProcessorFunc) - { - return CreateUnitProcessorFunc(unit); - } - else - { - return winrt::make(unit); - } - } - - TestConfigurationUnitProcessorDetails::TestConfigurationUnitProcessorDetails(const ConfigurationUnit& unit) : - UnitTypeValue(unit.Type()) - {} - - TestConfigurationUnitProcessor::TestConfigurationUnitProcessor(const ConfigurationUnit& unit) : - UnitValue(unit) - {} - - ITestSettingsResult TestConfigurationUnitProcessor::TestSettings() - { - if (TestSettingsFunc) - { - return TestSettingsFunc(); - } - else - { - return winrt::make(UnitValue); - } - } - - IGetSettingsResult TestConfigurationUnitProcessor::GetSettings() - { - if (GetSettingsFunc) - { - return GetSettingsFunc(); - } - else - { - return winrt::make(UnitValue); - } - } - - IApplySettingsResult TestConfigurationUnitProcessor::ApplySettings() - { - if (ApplySettingsFunc) - { - return ApplySettingsFunc(); - } - else - { - return winrt::make(UnitValue); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestConfiguration.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Foundation::Collections; +using namespace winrt::Microsoft::Management::Configuration; + +namespace TestCommon +{ + IConfigurationSetProcessor TestConfigurationSetProcessorFactory::CreateSetProcessor(const ConfigurationSet& configurationSet) + { + if (CreateSetProcessorFunc) + { + return CreateSetProcessorFunc(configurationSet); + } + else + { + return winrt::make(); + } + } + + winrt::event_token TestConfigurationSetProcessorFactory::Diagnostics(const EventHandler& handler) + { + return m_diagnostics.add(handler); + } + + void TestConfigurationSetProcessorFactory::Diagnostics(const winrt::event_token& token) noexcept + { + m_diagnostics.remove(token); + } + + DiagnosticLevel TestConfigurationSetProcessorFactory::MinimumLevel() + { + return DiagnosticLevel::Informational; + } + + void TestConfigurationSetProcessorFactory::MinimumLevel(DiagnosticLevel) + { + } + + IConfigurationUnitProcessorDetails TestConfigurationSetProcessor::GetUnitProcessorDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) + { + if (GetUnitProcessorDetailsFunc) + { + return GetUnitProcessorDetailsFunc(unit, detailFlags); + } + else + { + return winrt::make(unit); + } + } + + IConfigurationUnitProcessor TestConfigurationSetProcessor::CreateUnitProcessor(const ConfigurationUnit& unit) + { + if (CreateUnitProcessorFunc) + { + return CreateUnitProcessorFunc(unit); + } + else + { + return winrt::make(unit); + } + } + + TestConfigurationUnitProcessorDetails::TestConfigurationUnitProcessorDetails(const ConfigurationUnit& unit) : + UnitTypeValue(unit.Type()) + {} + + TestConfigurationUnitProcessor::TestConfigurationUnitProcessor(const ConfigurationUnit& unit) : + UnitValue(unit) + {} + + ITestSettingsResult TestConfigurationUnitProcessor::TestSettings() + { + if (TestSettingsFunc) + { + return TestSettingsFunc(); + } + else + { + return winrt::make(UnitValue); + } + } + + IGetSettingsResult TestConfigurationUnitProcessor::GetSettings() + { + if (GetSettingsFunc) + { + return GetSettingsFunc(); + } + else + { + return winrt::make(UnitValue); + } + } + + IApplySettingsResult TestConfigurationUnitProcessor::ApplySettings() + { + if (ApplySettingsFunc) + { + return ApplySettingsFunc(); + } + else + { + return winrt::make(UnitValue); + } + } +} diff --git a/src/AppInstallerCLITests/TestConfiguration.h b/src/AppInstallerCLITests/TestConfiguration.h index 73a76b3cad..2a36d289e1 100644 --- a/src/AppInstallerCLITests/TestConfiguration.h +++ b/src/AppInstallerCLITests/TestConfiguration.h @@ -1,178 +1,178 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - -namespace TestCommon -{ - struct TestConfigurationSetProcessorFactory : winrt::implements - { - winrt::Microsoft::Management::Configuration::IConfigurationSetProcessor CreateSetProcessor(const winrt::Microsoft::Management::Configuration::ConfigurationSet& configurationSet); - - winrt::event_token Diagnostics(const winrt::Windows::Foundation::EventHandler& handler); - void Diagnostics(const winrt::event_token& token) noexcept; - - winrt::Microsoft::Management::Configuration::DiagnosticLevel MinimumLevel(); - void MinimumLevel(winrt::Microsoft::Management::Configuration::DiagnosticLevel value); - - std::function CreateSetProcessorFunc; - - private: - winrt::event> m_diagnostics; - }; - - struct TestConfigurationSetProcessor : winrt::implements - { - winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails GetUnitProcessorDetails( - const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit, - winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailFlags detailFlags); - - std::function GetUnitProcessorDetailsFunc; - - winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessor CreateUnitProcessor( - const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); - - std::function CreateUnitProcessorFunc; - }; - - struct TestConfigurationUnitProcessorDetails : winrt::implements - { - TestConfigurationUnitProcessorDetails(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); - - winrt::hstring UnitTypeValue; - winrt::hstring UnitType() const { return UnitTypeValue; } - - winrt::hstring UnitDescriptionValue; - winrt::hstring UnitDescription() const { return UnitDescriptionValue; } - - winrt::Windows::Foundation::Uri UnitDocumentationUriValue = nullptr; - winrt::Windows::Foundation::Uri UnitDocumentationUri() const { return UnitDocumentationUriValue; } - - winrt::Windows::Foundation::Uri UnitIconUriValue = nullptr; - winrt::Windows::Foundation::Uri UnitIconUri() const { return UnitIconUriValue; } - - winrt::hstring ModuleNameValue; - winrt::hstring ModuleName() const { return ModuleNameValue; } - - winrt::hstring ModuleTypeValue; - winrt::hstring ModuleType() const { return ModuleTypeValue; } - - winrt::hstring ModuleSourceValue; - winrt::hstring ModuleSource() const { return ModuleSourceValue; } - - winrt::hstring ModuleDescriptionValue; - winrt::hstring ModuleDescription() const { return ModuleDescriptionValue; } - - winrt::Windows::Foundation::Uri ModuleDocumentationUriValue = nullptr; - winrt::Windows::Foundation::Uri ModuleDocumentationUri() const { return ModuleDocumentationUriValue; } - - winrt::Windows::Foundation::Uri PublishedModuleUriValue = nullptr; - winrt::Windows::Foundation::Uri PublishedModuleUri() const { return PublishedModuleUriValue; } - - winrt::hstring VersionValue; - winrt::hstring Version() const { return VersionValue; } - - winrt::Windows::Foundation::DateTime PublishedDateValue; - winrt::Windows::Foundation::DateTime PublishedDate() const { return PublishedDateValue; } - - bool IsLocalValue = false; - bool IsLocal() const { return IsLocalValue; } - - winrt::hstring AuthorValue; - winrt::hstring Author() const { return AuthorValue; } - - winrt::hstring PublisherValue; - winrt::hstring Publisher() const { return PublisherValue; } - - winrt::Windows::Foundation::Collections::IVector SigningInformationValue = nullptr; - winrt::Windows::Foundation::Collections::IVectorView SigningInformation() const { return SigningInformationValue.GetView(); } - - winrt::Windows::Foundation::Collections::IVector SettingsValue; - winrt::Windows::Foundation::Collections::IVectorView Settings() const { return (SettingsValue ? SettingsValue.GetView() : nullptr); } - - bool IsPublicValue = false; - bool IsPublic() const { return IsPublicValue; } - }; - - struct TestConfigurationUnitProcessor : winrt::implements - { - TestConfigurationUnitProcessor( - const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); - - winrt::Microsoft::Management::Configuration::ConfigurationUnit UnitValue; - winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return UnitValue; } - - winrt::Microsoft::Management::Configuration::ITestSettingsResult TestSettings(); - - std::function TestSettingsFunc; - - winrt::Microsoft::Management::Configuration::IGetSettingsResult GetSettings(); - - std::function GetSettingsFunc; - - winrt::Microsoft::Management::Configuration::IApplySettingsResult ApplySettings(); - - std::function ApplySettingsFunc; - }; - - struct TestSettingsResultInstance : winrt::implements - { - TestSettingsResultInstance(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit) : m_unit(unit) {} - - winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return m_unit; } - - winrt::Microsoft::Management::Configuration::ConfigurationTestResult TestResult() { return m_testResult; } - void TestResult(winrt::Microsoft::Management::Configuration::ConfigurationTestResult value) { m_testResult = value; } - - winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation ResultInformation() { return m_resultInformation; } - void ResultInformation(winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation value) { m_resultInformation = value; } - - private: - winrt::Microsoft::Management::Configuration::ConfigurationUnit m_unit; - winrt::Microsoft::Management::Configuration::ConfigurationTestResult m_testResult = winrt::Microsoft::Management::Configuration::ConfigurationTestResult::Unknown; - winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation m_resultInformation; - }; - - struct ApplySettingsResultInstance : winrt::implements - { - ApplySettingsResultInstance(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit) : m_unit(unit) {} - - winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return m_unit; } - - bool RebootRequired() { return m_rebootRequired; } - void RebootRequired(bool value) { m_rebootRequired = value; } - - winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation ResultInformation() { return m_resultInformation; } - void ResultInformation(winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation value) { m_resultInformation = value; } - - private: - winrt::Microsoft::Management::Configuration::ConfigurationUnit m_unit; - bool m_rebootRequired = false; - winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation m_resultInformation; - }; - - struct GetSettingsResultInstance : winrt::implements - { - GetSettingsResultInstance(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit) : m_unit(unit) {} - - winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return m_unit; } - - winrt::Windows::Foundation::Collections::ValueSet Settings() { return m_settings; } - void Settings(winrt::Windows::Foundation::Collections::ValueSet value) { m_settings = value; } - - winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation ResultInformation() { return m_resultInformation; } - void ResultInformation(winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation value) { m_resultInformation = value; } - - private: - winrt::Microsoft::Management::Configuration::ConfigurationUnit m_unit; - winrt::Windows::Foundation::Collections::ValueSet m_settings; - winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation m_resultInformation; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + +namespace TestCommon +{ + struct TestConfigurationSetProcessorFactory : winrt::implements + { + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessor CreateSetProcessor(const winrt::Microsoft::Management::Configuration::ConfigurationSet& configurationSet); + + winrt::event_token Diagnostics(const winrt::Windows::Foundation::EventHandler& handler); + void Diagnostics(const winrt::event_token& token) noexcept; + + winrt::Microsoft::Management::Configuration::DiagnosticLevel MinimumLevel(); + void MinimumLevel(winrt::Microsoft::Management::Configuration::DiagnosticLevel value); + + std::function CreateSetProcessorFunc; + + private: + winrt::event> m_diagnostics; + }; + + struct TestConfigurationSetProcessor : winrt::implements + { + winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessorDetails GetUnitProcessorDetails( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit, + winrt::Microsoft::Management::Configuration::ConfigurationUnitDetailFlags detailFlags); + + std::function GetUnitProcessorDetailsFunc; + + winrt::Microsoft::Management::Configuration::IConfigurationUnitProcessor CreateUnitProcessor( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); + + std::function CreateUnitProcessorFunc; + }; + + struct TestConfigurationUnitProcessorDetails : winrt::implements + { + TestConfigurationUnitProcessorDetails(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); + + winrt::hstring UnitTypeValue; + winrt::hstring UnitType() const { return UnitTypeValue; } + + winrt::hstring UnitDescriptionValue; + winrt::hstring UnitDescription() const { return UnitDescriptionValue; } + + winrt::Windows::Foundation::Uri UnitDocumentationUriValue = nullptr; + winrt::Windows::Foundation::Uri UnitDocumentationUri() const { return UnitDocumentationUriValue; } + + winrt::Windows::Foundation::Uri UnitIconUriValue = nullptr; + winrt::Windows::Foundation::Uri UnitIconUri() const { return UnitIconUriValue; } + + winrt::hstring ModuleNameValue; + winrt::hstring ModuleName() const { return ModuleNameValue; } + + winrt::hstring ModuleTypeValue; + winrt::hstring ModuleType() const { return ModuleTypeValue; } + + winrt::hstring ModuleSourceValue; + winrt::hstring ModuleSource() const { return ModuleSourceValue; } + + winrt::hstring ModuleDescriptionValue; + winrt::hstring ModuleDescription() const { return ModuleDescriptionValue; } + + winrt::Windows::Foundation::Uri ModuleDocumentationUriValue = nullptr; + winrt::Windows::Foundation::Uri ModuleDocumentationUri() const { return ModuleDocumentationUriValue; } + + winrt::Windows::Foundation::Uri PublishedModuleUriValue = nullptr; + winrt::Windows::Foundation::Uri PublishedModuleUri() const { return PublishedModuleUriValue; } + + winrt::hstring VersionValue; + winrt::hstring Version() const { return VersionValue; } + + winrt::Windows::Foundation::DateTime PublishedDateValue; + winrt::Windows::Foundation::DateTime PublishedDate() const { return PublishedDateValue; } + + bool IsLocalValue = false; + bool IsLocal() const { return IsLocalValue; } + + winrt::hstring AuthorValue; + winrt::hstring Author() const { return AuthorValue; } + + winrt::hstring PublisherValue; + winrt::hstring Publisher() const { return PublisherValue; } + + winrt::Windows::Foundation::Collections::IVector SigningInformationValue = nullptr; + winrt::Windows::Foundation::Collections::IVectorView SigningInformation() const { return SigningInformationValue.GetView(); } + + winrt::Windows::Foundation::Collections::IVector SettingsValue; + winrt::Windows::Foundation::Collections::IVectorView Settings() const { return (SettingsValue ? SettingsValue.GetView() : nullptr); } + + bool IsPublicValue = false; + bool IsPublic() const { return IsPublicValue; } + }; + + struct TestConfigurationUnitProcessor : winrt::implements + { + TestConfigurationUnitProcessor( + const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit); + + winrt::Microsoft::Management::Configuration::ConfigurationUnit UnitValue; + winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return UnitValue; } + + winrt::Microsoft::Management::Configuration::ITestSettingsResult TestSettings(); + + std::function TestSettingsFunc; + + winrt::Microsoft::Management::Configuration::IGetSettingsResult GetSettings(); + + std::function GetSettingsFunc; + + winrt::Microsoft::Management::Configuration::IApplySettingsResult ApplySettings(); + + std::function ApplySettingsFunc; + }; + + struct TestSettingsResultInstance : winrt::implements + { + TestSettingsResultInstance(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit) : m_unit(unit) {} + + winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return m_unit; } + + winrt::Microsoft::Management::Configuration::ConfigurationTestResult TestResult() { return m_testResult; } + void TestResult(winrt::Microsoft::Management::Configuration::ConfigurationTestResult value) { m_testResult = value; } + + winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation ResultInformation() { return m_resultInformation; } + void ResultInformation(winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation value) { m_resultInformation = value; } + + private: + winrt::Microsoft::Management::Configuration::ConfigurationUnit m_unit; + winrt::Microsoft::Management::Configuration::ConfigurationTestResult m_testResult = winrt::Microsoft::Management::Configuration::ConfigurationTestResult::Unknown; + winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation m_resultInformation; + }; + + struct ApplySettingsResultInstance : winrt::implements + { + ApplySettingsResultInstance(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit) : m_unit(unit) {} + + winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return m_unit; } + + bool RebootRequired() { return m_rebootRequired; } + void RebootRequired(bool value) { m_rebootRequired = value; } + + winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation ResultInformation() { return m_resultInformation; } + void ResultInformation(winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation value) { m_resultInformation = value; } + + private: + winrt::Microsoft::Management::Configuration::ConfigurationUnit m_unit; + bool m_rebootRequired = false; + winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation m_resultInformation; + }; + + struct GetSettingsResultInstance : winrt::implements + { + GetSettingsResultInstance(const winrt::Microsoft::Management::Configuration::ConfigurationUnit& unit) : m_unit(unit) {} + + winrt::Microsoft::Management::Configuration::ConfigurationUnit Unit() { return m_unit; } + + winrt::Windows::Foundation::Collections::ValueSet Settings() { return m_settings; } + void Settings(winrt::Windows::Foundation::Collections::ValueSet value) { m_settings = value; } + + winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation ResultInformation() { return m_resultInformation; } + void ResultInformation(winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation value) { m_resultInformation = value; } + + private: + winrt::Microsoft::Management::Configuration::ConfigurationUnit m_unit; + winrt::Windows::Foundation::Collections::ValueSet m_settings; + winrt::Microsoft::Management::Configuration::IConfigurationUnitResultInformation m_resultInformation; + }; +} diff --git a/src/AppInstallerCLITests/TestData/ContainsEscapeControlCode.yaml b/src/AppInstallerCLITests/TestData/ContainsEscapeControlCode.yaml index 7dee1859d0..42fb744b66 100644 --- a/src/AppInstallerCLITests/TestData/ContainsEscapeControlCode.yaml +++ b/src/AppInstallerCLITests/TestData/ContainsEscapeControlCode.yaml @@ -1 +1 @@ -key: "This is the ESCAPE control code: \x1b" +key: "This is the ESCAPE control code: \x1b" diff --git a/src/AppInstallerCLITests/TestData/ContainsTooManyNestedLayers.yaml b/src/AppInstallerCLITests/TestData/ContainsTooManyNestedLayers.yaml index aac73e111d..8c6639071c 100644 --- a/src/AppInstallerCLITests/TestData/ContainsTooManyNestedLayers.yaml +++ b/src/AppInstallerCLITests/TestData/ContainsTooManyNestedLayers.yaml @@ -1,101 +1,101 @@ -- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - value +- + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - value diff --git a/src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml b/src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml index caca1cc444..424ca387f1 100644 --- a/src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml +++ b/src/AppInstallerCLITests/TestData/DownloadFlowTest_DownloadCommandProhibited.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json - -PackageIdentifier: AppInstallerCliTest.DownloadCommandProhibited -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test DownloadCommandProhibited -Publisher: Microsoft Corporation -AppMoniker: AICLITestDownloadCommandProhibited -License: Test -DownloadCommandProhibited: true -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.6.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json + +PackageIdentifier: AppInstallerCliTest.DownloadCommandProhibited +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test DownloadCommandProhibited +Publisher: Microsoft Corporation +AppMoniker: AICLITestDownloadCommandProhibited +License: Test +DownloadCommandProhibited: true +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLITests/TestData/DownloadFlowTest_MSStore.yaml b/src/AppInstallerCLITests/TestData/DownloadFlowTest_MSStore.yaml index 43fb4b9a57..b11f766b76 100644 --- a/src/AppInstallerCLITests/TestData/DownloadFlowTest_MSStore.yaml +++ b/src/AppInstallerCLITests/TestData/DownloadFlowTest_MSStore.yaml @@ -1,38 +1,38 @@ -Id: AppInstallerCliTest.TestMSStoreDownload -Version: Latest -Name: AppInstaller Test MSStore Download -Publisher: Microsoft Corporation -AppMoniker: AICLITestMSStore -License: Test -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9WZDNCRFJ364 - Language: en-US - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9WZDNCRFJ364 - Language: fr-FR - - Arch: arm - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9WZDNCRFJ364 - Language: en-US - - Arch: arm - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9WZDNCRFJ364 - Language: fr-FR - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9WZDNCRFJ364 - Language: ja-JP - - Arch: arm64 - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9WZDNCRFJ364 - Language: en-US -ManifestVersion: 0.2.0-msstore +Id: AppInstallerCliTest.TestMSStoreDownload +Version: Latest +Name: AppInstaller Test MSStore Download +Publisher: Microsoft Corporation +AppMoniker: AICLITestMSStore +License: Test +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9WZDNCRFJ364 + Language: en-US + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9WZDNCRFJ364 + Language: fr-FR + - Arch: arm + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9WZDNCRFJ364 + Language: en-US + - Arch: arm + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9WZDNCRFJ364 + Language: fr-FR + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9WZDNCRFJ364 + Language: ja-JP + - Arch: arm64 + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9WZDNCRFJ364 + Language: en-US +ManifestVersion: 0.2.0-msstore diff --git a/src/AppInstallerCLITests/TestData/ImportFile-Good-MachineScope.json b/src/AppInstallerCLITests/TestData/ImportFile-Good-MachineScope.json index 4057140d03..a80b455e47 100644 --- a/src/AppInstallerCLITests/TestData/ImportFile-Good-MachineScope.json +++ b/src/AppInstallerCLITests/TestData/ImportFile-Good-MachineScope.json @@ -1,22 +1,22 @@ -{ - "$schema": "https://aka.ms/winget-packages.schema.1.0.json", - "CreationDate": "2021-01-01T12:00:00.000-00:00", - "Sources": [ - { - "Packages": [ - { - "Id": "TestExeInstallerWithNothingInstalled", - "Version": "1.0.0.0", - "Scope": "machine" - } - ], - "SourceDetails": { - "Argument": "//arg", - "Identifier": "*TestSource", - "Name": "TestSource", - "Type": "Microsoft.TestSource" - } - } - ], - "WinGetVersion": "1.0.0" -} +{ + "$schema": "https://aka.ms/winget-packages.schema.1.0.json", + "CreationDate": "2021-01-01T12:00:00.000-00:00", + "Sources": [ + { + "Packages": [ + { + "Id": "TestExeInstallerWithNothingInstalled", + "Version": "1.0.0.0", + "Scope": "machine" + } + ], + "SourceDetails": { + "Argument": "//arg", + "Identifier": "*TestSource", + "Name": "TestSource", + "Type": "Microsoft.TestSource" + } + } + ], + "WinGetVersion": "1.0.0" +} diff --git a/src/AppInstallerCLITests/TestData/ImportFile-Good-WithLicenseAgreement.json b/src/AppInstallerCLITests/TestData/ImportFile-Good-WithLicenseAgreement.json index 01c7ab02f2..e035111554 100644 --- a/src/AppInstallerCLITests/TestData/ImportFile-Good-WithLicenseAgreement.json +++ b/src/AppInstallerCLITests/TestData/ImportFile-Good-WithLicenseAgreement.json @@ -18,4 +18,4 @@ } ], "WinGetVersion": "1.0.0" -} +} diff --git a/src/AppInstallerCLITests/TestData/InputARPData.txt b/src/AppInstallerCLITests/TestData/InputARPData.txt index e28cb36a99..764405184c 100644 --- a/src/AppInstallerCLITests/TestData/InputARPData.txt +++ b/src/AppInstallerCLITests/TestData/InputARPData.txt @@ -1,112 +1,112 @@ -XP890ZFCZZR294|Studio Fisioterapico Pro|Esposito Software di M. G. Caputo|Studio Fisioterapico Pro Demo||Copyright Esposito Software|Studio Fisioterapico Pro Demo_is1 -XP89DCGQ3K6VLD|Microsoft PowerToys|Microsoft Corporation|PowerToys (Preview) x64|0.57.0|Microsoft Corporation|{582f7a19-045d-43d4-89bf-7f8e9479311c} -XP89HZ8SVWTT0M|ElevenClock|Martí Climent|ElevenClock version 3.3.2|3.3.2|SomePythonThings|{D62480B8-71F1-48CE-BEEC-9D3E172C87B5}_is1 -XP89HZKG342W76|POWER-KI GUI Client|XPLAB - Research in Automation|POWER-KI GUI|33.11|XPLAB - Research in Automation - Brescia - Italy|{0760E097-F794-4836-9941-8846EA43BE06} -XP89J5462CMGJD|Apache OpenOffice|The Apache Software Foundation|OpenOffice 4.1.11|4.111.9808|Apache Software Foundation|{D2F124FC-5373-4A4A-8C5A-61052A3D34CA} -XP8BTFNM0T53BJ|PolypopLive|Simmetri, Inc.|PolyPop 0.98.222.0|0.98.222.0|Simmetri, Inc.|{75454996-E72B-480E-BB8C-CD743A54C362}_is1 -XP8BX12N1KK2QJ|MyLifeOrganized - To-Do List|Andriy Tkachuk|MyLifeOrganized v. 5.1.3|5.1.3|MyLifeOrganized.net|MyLife Organized -XP8CD7JST163BL|BPM Counter|Abyssmedia.com|BPM Counter 3.8.0.0|3.8.0.0|AbyssMedia.com|BPM Counter_is1 -XP8CDF4CV9XP5Q|Archivio Esami Clinici|Esposito Software di M. G. Caputo|Archivio Esami Clinici 3.0 Demo||Copyright Esposito Software|Archivio Esami Clinici 3.0 Demo_is1 -XP8CF6SB8MX31V|Ashampoo Photo Optimizer 8|Ashampoo|Ashampoo Photo Optimizer 8|8.2.3|Ashampoo GmbH & Co. KG|{91B33C97-5FC6-8971-3444-C57BBE022215}_is1 -XP8JJ8VX6VL0Q5|Cleaner One Pro - Free PC Cleaner|Trend Micro Inc.|Cleaner One Pro 6.6.0|6.6.0|Trend Micro, Inc.|99388cc2-2782-5495-bbd2-525df2487901 -XP8JJRV6TV79LG|DiskZIP|ZIPmagic Software|DiskZIP|2022.3.1415.932|DiskZIP Computing, Inc.|DiskZIP -XP8JJVZXG23JLN|WorldClock.Classic.ScreenSaver|Fulvio Castelli|WorldClock Screen Saver (Trial)|7.0.12.0|Fulvio Castelli|{EF3BC641-89A9-4703-9DED-19CEE72CEF07}_is1 -XP8JK4HZBVF435|Auto Dark Mode|Armin Osaj|Auto Dark Mode|10.1.0.10|Armin Osaj & Samuel Schiegg|{470BC918-3740-4A97-9797-8570A7961130}_is1 -XP8JMKMC3GVX23|Wondershare EdrawMax|WONDERSHARE GLOBAL LIMITED|Wondershare EdrawMax(Build 11.1.2.870)|11.1.2.870|EdrawSoft Co.,Ltd.|{037BAB81-3DF7-4381-A72C-A26B57C03548}_is1 -XP8JNNTH0LT9F1|ApowerEdit|网旭科技|ApowerEdit V1.7.7.22|1.7.7.22|Apowersoft LIMITED|{3089CCCD-BC5F-4309-A3C1-45B5ACA7A5E7}_is1 -XP8K17KD2T7W8V|Ashampoo WinOptimizer 19|Ashampoo|Ashampoo WinOptimizer 19|19.00.23|Ashampoo GmbH & Co. KG|{4209F371-A9E3-7DD2-C1E5-04BB2B081219}_is1 -XP8K1F4KDP9DSJ|Autonoleggio N.S.C.|Esposito Software di M. G. Caputo|Autonoleggio NSC 3.0 Demo||Copyright Esposito Software|Autonoleggio NSC 3.0 Demo_is1 -XP8K43JX54F7FL|Cute Cursors|Cute Cursors|CuteCursors|1.0.0|Apollo One|{6683BBFB-B899-4755-B260-DF0387D9F872} -XP8K513CFB5K58|Archivio Dipendenti con Foto|Esposito Software di Maria Grazia Caputo|Archivio Dipendenti con Foto Demo||Copyright Esposito Software|Archivio Dipendenti con Foto Demo_is1 -XP8LFCZM790F6B|Visual Studio Code - Insiders|Microsoft Corporation|Microsoft Visual Studio Code Insiders (User)|1.67.0|Microsoft Corporation|{217B4C08-948D-4276-BFBB-BEE930AE5A2C}_is1 -XP8LFD92C0T8P0|Stampa Tessere Associazioni|Esposito Software di Maria Grazia Caputo|Stampa Tessere Associazioni 5.0 Demo||Copyright Esposito Software|Stampa Tessere Associazioni 5.0 Demo_is1 -XP8LG1VTM0XW03|Gestione Protocollo e Pratiche|Esposito Software di Maria Grazia Caputo|Gestione Protocollo e Pratiche Demo||Copyright Esposito Software|Gestione Protocollo e Pratiche Demo_is1 -XP8LG2X182JTJ9|Wondershare Dr.Fone - Mobile Device Management|WONDERSHARE GLOBAL LIMITED|Wondershare Dr.Fone (Version 10.9.6)|10.9.6.398|Wondershare Technology Co.,Ltd.|{E8F86DA8-B8E4-42C7-AFD4-EBB692AC43FD}_is1 -XP8LG65GV4C7C8|GitMind Mind Map|网旭科技|GitMind 1.0.8|1.0.8|Apowersoft|a0e10d84-6512-552f-a0ec-5dd2e61ffe64 -XP8LKPZT4X0Z0P|GOM Player|Gom and Company|GOM Player|2.3.67.5331|GOM & Company|GOM Player -XP8LKWQ22DX3TF|JYL Visitor Windows|JYL Software|JYL Visitor 1.94|1.94|JYL Software|{02ADFF54-7D56-42F1-B517-FDA35F55D2CC} -XP99J3KP4XZ4VV|ZOOM Cloud Meetings|Zoom Video Communications, Inc.|Zoom|5.10.0 (4306)|Zoom Video Communications, Inc.|ZoomUMX -XP99J7FXZD0JDM|Emjysoft eSanté|Emjysoft|Suivi des soins et des remboursements de Santé|3.11|Emjysoft|{6CC28634-D98C-4DE1-9EE7-E121277996F6}_is1 -XP99JXDBM4XKFP|Parallels Toolbox|Corel Corporation|Parallels Toolbox|5.1.0.3170|Parallels International GmbH|{5145E2CF-E9FC-48E6-A2B4-E409FC84D059} -XP99K41V2P36RQ|MSIX Editor|InstallAware Software Corporation|InstallAware Msix Editor 1.0|1.0.0.2703|InstallAware Software|InstallAware Msix Editor 1.0 -XP99VR1BPSBQJ2|Epic Games Store|Epic Games Inc.|Epic Games Launcher|1.3.23.0|Epic Games, Inc.|{FAC47927-1A6A-4C6E-AD7D-E9756794A4BC} -XP99WSCKQSH7SW|Emjysoft Sauvegarde Facile|Emjysoft|Easy Backup|VersionApplication|Emjysoft|{37215B1A-1990-4F55-936E-C9BA1634EF75}}_is1 -XP99WT9NMGB1PN|蜜蜂剪辑|网旭科技|BeeCut V1.7.7.22|1.7.7.22|Apowersoft LIMITED|{CA76BFA8-1862-49D7-B2C7-AE3D6CF40E53}_is1 -XP9B0HTG55KTCH|Free Hex Editor Neo|HHD Software Ltd.|HHD Software Free Hex Editor Neo 6.54|6.54.02.6790|HHD Software, Ltd.|{8EB85C0E-DE7D-4A53-BD66-708B8F2C80B0} -XP9B16C2TFN8P1|GOM Mix Pro|Gom and Company|GOM Mix Pro|2.0.4.8|GOM & Company|GOMMixPro -XP9CFZ9PKV0DWS|Automation Workshop|Febooti, SIA|Febooti Automation Workshop|5.1.1.0|Febooti Software|{6114DD12-2516-4465-9275-FB9A8E1A583C} -XP9CRZD7D219NK|FolderSizes|Key Metric Software|FolderSizes 9|9.3.362|Key Metric Software|{587D3069-EFE1-4FC2-B917-01496D5ABF8A} -XP9CRZQDCJ0CC6|LetsView|网旭科技|LetsView V1.1.2.5|1.1.2.5|LetsView LIMITED|{6AA74BE4-9506-4D81-A07C-A40F883C2EA7}_is1 -XP9CSP03RV8BX9|Audials One 2022|Audials AG|Audials 2022|22.0.177.0|Audials AG|{3F273072-3D14-479E-B4CD-AC8B1F436DA1} -XP9K4SR87H227Q|VisualNEO Win|SinLios Soluciones Digitales|VisualNEO Win|21.9.9|SinLios|{57147D4D-2492-41EC-A552-FB37C1C7FF3E}_is1 -XP9K5VRXFHVP75|Database Creator|Esposito Software di Maria Grazia Caputo|Database Creator Demo||Copyright Esposito Software|Database Creator Demo_is1 -XP9K5XN9BRN466|Housecall Free Virus - Malware Scanner|Trend Micro Inc.|HouseCall|1.62|Trend Micro Inc.|{A114E34B-AA5C-4DD8-98A9-3130ACA19491} -XP9KHKZS1M19ZP|x-studio|Simdsoft Limited|x-studio 2022|2022.1.4|Simdsoft Limited|{2F7387D3-EB5F-4CA5-8C42-04C59F922740} -XP9KHM4BK9FZ7Q|Visual Studio Code|Microsoft Corporation|Microsoft Visual Studio Code (User)|1.66.0|Microsoft Corporation|{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1 -XP9KHPQ5C9MSN2|ZIPmagic|ZIPmagic Software|ZIPmagic|19.19.21|Simon King|ZIPmagic -XP9KHPXMW6RQLL|Gestione Studio Tecnico|Esposito Software di M. G. Caputo|Gestione Studio Tecnico Demo||Copyright Esposito Software|Gestione Studio Tecnico Demo_is1 -XP9KHQZV691PF9|PTZ Link|AVer Information|AVer PTZ Link|1.1.1013.0|AVer Information Inc|{AC08D179-14D5-4B93-9684-20DBE0848637} -XP9KM2X7H10448|PCmover Reconfigurator|Laplink Software Inc|Laplink Reconfigurator|1.0.0.1|Laplink Software, Inc.|{BBB86720-65BA-452A-A14D-B152CB506DD8} -XP9M20CZB2C5W8|Powder - Gaming Recorder|Unique Entertainment Experience SAS|Powder 2.5.0|2.5.0|powder-team|2b39bc52-9c37-5fcd-ab25-906727f7c690 -XP9MFNDJM19N0G|Gestione Affitti Pro|Esposito Software di M. G. Caputo|Gestione Affitti Pro 4.0 Demo||Copyright Esposito Software|Gestione Affitti Pro 4.0 Demo_is1 -XPDBZ0BW87BCTV|POWER-KI Executor|XPLAB - Research in Automation|POWER-KI Executor|33.11|XPLAB - Research in Automation - Brescia - Italy|{B2B40FB5-0B60-4B47-A1F1-F0254CD0BE04} -XPDBZ4MPRKNN30|Opera GX|Opera Norway AS|Opera GX Stable 82.0.4227.44|82.0.4227.44|Opera Software|Opera GX 82.0.4227.44 -XPDC1LX9VNW7Z7|VirtualDJ|Atomix International, S.A.|VirtualDJ 2021|8.5.6747.0|Atomix Productions|{97CFEA35-98EF-4EBC-8AF1-4F161CFCAE86} -XPDC2KHD93HVJW|Stampa Ricevute Generiche|Esposito Software di Maria Grazia Caputo|Stampa Ricevute Generiche Demo||Copyright Esposito Software|Stampa Ricevute Generiche Demo_is1 -XPDCFJD1GFFDXD|WorldClock.Classic|Fulvio Castelli|WorldClock (Trial)|7.0.12.0|Fulvio Castelli|{E32193B9-8870-40be-B88A-B302251B8AA7}_is1 -XPDCJ80KGNRVSS|TeamSpeak|TeamSpeak Systems GmbH|TeamSpeak|5.0.0|TeamSpeak|{C9D97E1E-B188-4500-A87D-902530E0D1E0} -XPDCK0XGHVWNBK|Trend Micro Antivirus Plus Security|Trend Micro Inc.|Trend Micro Antivirus+|17.7|Trend Micro Inc.|{ABBD4BA8-6703-40D2-AB1E-5BB1F7DB49A4} -XPDDZ434WT2M5Z|SOLARWATT Pro experience|SOLARWATT GmbH|SOLARWATT Experience|2.1.0.4|SOLARWATT|{40CF234F-1D35-4ED8-AAFC-E07EA2FD8B3B} -XPDF9J69VVFMX3|Apowersoft Background Eraser|网旭科技|Apowersoft background eraser V2.3.13|2.3.13|Apowersoft LIMITED|{98EC0F66-C563-40FA-A77A-F2FC558F5DAA}_is1 -XPDFF6P40P0M5Q|星愿浏览器|Twinkstar|Twinkstar Browser|7.12.1000.2112|Twinkstar Limited|Twinkstar -XPDLNG5248Q7NC|HttpMaster Express|Borvid, Informacijske storitve, Janez Čas s.p.|HttpMaster Express Edition 5.4.1|5.4.1|Borvid|{B61241AA-F5FC-42C9-A1F9-F6D72D654349} -XPDM19SX6D8V40|JYL Orders Suppliers Windows|JYL Software|JYL Order Suppliers 1.70|1.70|JYL Software|{57DF6E60-F6E4-498F-9637-18D6C0FA08B9} -XPDM4ZR5KJ9JN9|PowerDirector 365 Free - Video Editor, Movie Maker|CyberLink Corp.|CyberLink PowerDirector 365|20.1.2519.0|CyberLink Corp.|{278A8296-12A6-4CD0-8A8E-6947948477C5} -XPDM5Q9J9SFCX9|Stampa Ricevute Pagamento|Esposito Software di M. G. Caputo|Stampa Ricevute Pagamento Demo||Copyright Esposito Software|Stampa Ricevute Pagamento Demo_is1 -XPDNG54ZDC79K0|JYL Time Clock Windows|JYL Software|JYL Time Clock 2.22|2.22|JYL Software|{839FD23A-EFE9-4252-AF1A-B8B56ED925F4} -XPDNH1FMW7NB40|火绒安全软件|Beijing Huorong Network Technology Co., Ltd.|Huorong Internet Security|5.0|Beijing Huorong Network Technology Co., Ltd.|HuorongSysdiag -XPDNLQK867NNXF|Ashampoo ZIP Pro 4|Ashampoo|Ashampoo ZIP Pro 4|4.10.22|Ashampoo GmbH & Co. KG|{0A11EA01-1F01-7AF6-20A2-E6F8131AD29C}_is1 -XPDNXDPXBRSVXT|WinZip 26|WinZip Computing|WinZip 26.0|26.0.15033|Corel Corporation|{CD95F661-A5C4-44F5-A6AA-ECDD91C2413F} -XPDNXG5333CSVK|Hard Disk Sentinel Professional|Janos Mathe|Hard Disk Sentinel PRO|6.01|Janos Mathe|Hard Disk Sentinel_is1 -XPDNZ9TPLKW6TB|Fy Slideshow|Guutara's Notebook|Fy Slideshow|5.6.0|Guutara|{5A4DEC47-8784-4591-983F-A3A6C3C89A46} -XPDNZJFNCR1B07|Avast Free Antivirus|AVAST Software|Avast Free Antivirus|22.2.6003|Avast Software|Avast Antivirus -XPDP1XPZR8NL28|Studio Medico Pro|Esposito Software di M. G. Caputo|Studio Medico Pro 3.0 Demo||Copyright Esposito Software|Studio Medico Pro 3.0 Demo_is1 -XPDP255TRF9WP8|Logspire|Anfibia Software|Logspire 1.0.0.51|1.0.0.51|Anfibia|Logspire_is1 -XPDP2X1MMZ4KR8|Ashampoo Burning Studio 23|Ashampoo|Ashampoo Burning Studio 23|23.0.5|Ashampoo GmbH & Co. KG|{91B33C97-2A56-F111-077E-E591CE9D7DE7}_is1 -XPFCFBB4FB3D6D|Emjysoft Cleaner|Emjysoft|Emjysoft Cleaner 2022 v4.1|4.1|Emjysoft|{167B1302-A739-42DE-BBD2-4C2F13D1FF51}_is1 -XPFCFKCNNTXGQD|Yandex Browser|Yandex|Yandex|21.9.1.686|ООО «ЯНДЕКС»|YandexBrowser -XPFCFL5ZTNFGD7|Wondershare Anireel|WONDERSHARE GLOBAL LIMITED|Wondershare Anireel(Build 1.6.2)||Wondershare Software|Wondershare Anireel_is1 -XPFCG86X2PGLDJ|Christmas Elf by Pothos|Pothos|Christmas Elf|||ChristmasElf -XPFCGHHXNH4WBW|Biblioteca e Prestiti Librari|Esposito Software di M. G. Caputo|Gestione Biblioteca e Prestiti Librari 3.0 Demo||Copyright Esposito Software|Gestione Biblioteca e Prestiti Librari 3.0 Demo_is1 -XPFCWP0SQWXM3V|CCleaner|Piriform Software Ltd|CCleaner|5.89|Piriform|CCleaner -XPFCXFRDJ8VGPT|Домашняя Бухгалтерия|Keepsoft|Äîìàøíÿÿ áóõãàëòåðèÿ Lite|7.2|Keepsoft|Äîìàøíÿÿ áóõãàëòåðèÿ Lite -XPFCXPF18WNKP6|Total Defense Essential Anti-Virus|Total Defense, Inc.|Total Defense|13.0.0.572|Total Defense, Inc.|TotalDefense -XPFCXS0QVTHDC9|Active@ Disk Editor|LSoft Technologies Inc.|Active@ Disk Editor 7|7|LSoft Technologies Inc|{F40165C8-BD5B-4E42-A40D-396BB707E5B7}_is1 -XPFD27PCFQJQ68|TextSeek|Xiamen Zesite Company|TextSeek|2.12.3060|Zesite Company|TextSeek -XPFD28MTCS0GXJ|VisualNEO Web|SinLios Soluciones Digitales|VisualNeoWeb||SinLios|{EEF9B1C5-7E35-4972-A79A-44B2B2C72D3D}_is1 -XPFFBRXVQ2L6JN|Coolnew PDF|CoolNewPDF|CoolNew PDF|3.0.0.1|CoolNew Software Corporation|coolnewpdf -XPFFC9N4PVM9N8|Prenotazione Tavoli OK|Esposito Software di Maria Grazia Caputo|Prenotazione Tavoli OK Demo||Copyright Esposito Software|Prenotazione Tavoli OK Demo_is1 -XPFFCCM235X204|Fy Memo|Guutara's Notebook|Fy Memo|6.5.0|Guutara|{4BDAE26E-3414-4516-89F9-B6C277029CA5} -XPFFCM599XXT5P|傲软录屏|网旭科技|ApowerREC V1.5.5.18|1.5.5.18|Apowersoft LIMITED|{6F2998B2-21F7-4CEF-94B2-C3919D939CF9}_is1 -XPFFH5S3C4Q1CB|傲软抠图|网旭科技|Apowersoft background eraser V2.3.13|2.3.13|Apowersoft LIMITED|{98EC0F66-C563-40FA-A77A-F2FC558F5DAA}_is1 -XPFFSV0VCDKTM5|PolicyApplicator Conversion Kit|Hauke Hasselberg|PolicyApplicator Conversion Kit|1.0.11.0|Hauke Götze|{C918DB43-6B86-4364-BEAC-1184D3EE3C07} -XPFFT29L5QQ7RL|SRPG Studio|SapphireSoft|SRPG Studio Trial version 1.251|1.251|SapphireSoft|{FBC98908-FD84-4C92-A539-5DA61EDD7F9F}_is1 -XPFFT3RD5FMWX2|Emjysoft Comptabilité Personnelle|Emjysoft|Personal Finance|20.5|Emjysoft|{2369DC9E-11A7-4BAE-A43E-7A4CB477574F}_is1 -XPFFTPNN0NNHVQ|Auto Print Order|NAMTUK|AutoPrintOrder 1.10.1215|1.10.1215|Namtuk|{B26EF0DD-2375-4E88-9991-4652AC89FE3F} -XPFM2BJ3RPZ9XB|轻闪PDF编辑|网旭科技|LightPDF Editor V1.2.6.0|1.2.6.0|Apowersoft LIMITED|{161C8BF4-DB06-49A7-B6AC-7CAB7DAF136F}_is1 -XPFM306TS4PHH5|Ashampoo Burning Studio FREE|Ashampoo|Ashampoo Burning Studio FREE|1.21.5|Ashampoo GmbH & Co. KG|{91B33C97-91F8-FFB3-581B-BC952C901685}_is1 -XPFM5W1J84KQZX|ndCurveMaster|SigmaLab Tomasz Cepowski|ndCurveMaster Trial x64 version 8.2.0.1|8.2.0.1|SigmaLab|{5FB2948C-B95A-49CD-A2ED-62D0A38D7B1C}_is1 -XPFMJGWHHCNL5P|傲软投屏—手机/电脑/电视高清投屏神器|网旭科技|ApowerMirror V1.6.5.1|1.6.5.1|APOWERSOFT LIMITED|{a9482532-9c34-478c-80c3-85bdccbb981f}_is1 -XPFMKKKLHMMK6Q|Videoteca OK|Esposito Software di Maria Grazia Caputo|Videoteca OK 5.0 Demo||Copyright Esposito Software|Videoteca OK 5.0 Demo_is1 -XPFNZJKG6100L4|ASM Visual|gri-software|ASM Visual version 1.1.7|1.1.7|gri-software|{7416EF27-89A5-4819-9996-36C16F49BAEC}_is1 -XPFNZKDRP1SXM6|视频转换王|网旭科技|Apowersoft Video Converter Studio V4.8.6.7|4.8.6.7|APOWERSOFT LIMITED|{195E8D7F-292B-4B04-A6E7-E96CAF04C767}_is1 -XPFP0G0V147H6D|Wondershare PDFelement|WONDERSHARE GLOBAL LIMITED|Wondershare PDFelement ( Version 8.3.0 )|8.3.0|Wondershare|{343A530C-4726-4091-87E0-F9CC41792CE2}_is1 -XPFP2VCXM8D2DB|傲软PDF编辑——一键编辑&转化&压缩&签名PDF文件|网旭科技|ApowerPDF V5.4.2.3|5.4.2.3|Apowersoft LIMITED|{8691C793-7B2C-46C5-9AB2-AB80D129A5EC}_is1 -XPFP30KL61D4SC|Wondershare UniConverter|WONDERSHARE GLOBAL LIMITED|Wondershare UniConverter 13(Build 13.5.1.116)|13.5.1.116|Wondershare Software|UniConverter 13_is1 -XPFP42D8L456SK|X-VPN - Best VPN Proxy and Wifi Security|Free Connected Limited.|X-VPN|71.0|Free Connected Limited|X-VPN -XPFP42J061BPC1|Documenti Lavori Cantiere|Esposito Software di M. G. Caputo|Documenti Lavori Cantiere Demo||Copyright Esposito Software|Documenti Lavori Cantiere Demo_is1 -XPFPFN4LT21PZJ|Studio Dentistico Pro|Esposito Software di M. G. Caputo|Studio Dentistico Pro Demo||Copyright Esposito Software|Studio Dentistico Pro Demo_is1 -XPFPFWMVTR0WHP|Ashampoo UnInstaller 11|Ashampoo|Ashampoo UnInstaller 11|11.00.12|Ashampoo GmbH & Co. KG|{4209F371-B84B-F321-6BD3-1D91E2505732}_is1 -XPFPFWV5JD80K2|BeeCut|网旭科技|BeeCut V1.7.7.22|1.7.7.22|Apowersoft LIMITED|{CA76BFA8-1862-49D7-B2C7-AE3D6CF40E53}_is1 -XPFPLCB36G8V8J|HttpMaster Professional|Borvid, Informacijske storitve, Janez Čas s.p.|HttpMaster Professional Edition 5.4.1|5.4.1|Borvid|{B61241AA-F5FC-42C9-A1F9-F6D72D654349} +XP890ZFCZZR294|Studio Fisioterapico Pro|Esposito Software di M. G. Caputo|Studio Fisioterapico Pro Demo||Copyright Esposito Software|Studio Fisioterapico Pro Demo_is1 +XP89DCGQ3K6VLD|Microsoft PowerToys|Microsoft Corporation|PowerToys (Preview) x64|0.57.0|Microsoft Corporation|{582f7a19-045d-43d4-89bf-7f8e9479311c} +XP89HZ8SVWTT0M|ElevenClock|Martí Climent|ElevenClock version 3.3.2|3.3.2|SomePythonThings|{D62480B8-71F1-48CE-BEEC-9D3E172C87B5}_is1 +XP89HZKG342W76|POWER-KI GUI Client|XPLAB - Research in Automation|POWER-KI GUI|33.11|XPLAB - Research in Automation - Brescia - Italy|{0760E097-F794-4836-9941-8846EA43BE06} +XP89J5462CMGJD|Apache OpenOffice|The Apache Software Foundation|OpenOffice 4.1.11|4.111.9808|Apache Software Foundation|{D2F124FC-5373-4A4A-8C5A-61052A3D34CA} +XP8BTFNM0T53BJ|PolypopLive|Simmetri, Inc.|PolyPop 0.98.222.0|0.98.222.0|Simmetri, Inc.|{75454996-E72B-480E-BB8C-CD743A54C362}_is1 +XP8BX12N1KK2QJ|MyLifeOrganized - To-Do List|Andriy Tkachuk|MyLifeOrganized v. 5.1.3|5.1.3|MyLifeOrganized.net|MyLife Organized +XP8CD7JST163BL|BPM Counter|Abyssmedia.com|BPM Counter 3.8.0.0|3.8.0.0|AbyssMedia.com|BPM Counter_is1 +XP8CDF4CV9XP5Q|Archivio Esami Clinici|Esposito Software di M. G. Caputo|Archivio Esami Clinici 3.0 Demo||Copyright Esposito Software|Archivio Esami Clinici 3.0 Demo_is1 +XP8CF6SB8MX31V|Ashampoo Photo Optimizer 8|Ashampoo|Ashampoo Photo Optimizer 8|8.2.3|Ashampoo GmbH & Co. KG|{91B33C97-5FC6-8971-3444-C57BBE022215}_is1 +XP8JJ8VX6VL0Q5|Cleaner One Pro - Free PC Cleaner|Trend Micro Inc.|Cleaner One Pro 6.6.0|6.6.0|Trend Micro, Inc.|99388cc2-2782-5495-bbd2-525df2487901 +XP8JJRV6TV79LG|DiskZIP|ZIPmagic Software|DiskZIP|2022.3.1415.932|DiskZIP Computing, Inc.|DiskZIP +XP8JJVZXG23JLN|WorldClock.Classic.ScreenSaver|Fulvio Castelli|WorldClock Screen Saver (Trial)|7.0.12.0|Fulvio Castelli|{EF3BC641-89A9-4703-9DED-19CEE72CEF07}_is1 +XP8JK4HZBVF435|Auto Dark Mode|Armin Osaj|Auto Dark Mode|10.1.0.10|Armin Osaj & Samuel Schiegg|{470BC918-3740-4A97-9797-8570A7961130}_is1 +XP8JMKMC3GVX23|Wondershare EdrawMax|WONDERSHARE GLOBAL LIMITED|Wondershare EdrawMax(Build 11.1.2.870)|11.1.2.870|EdrawSoft Co.,Ltd.|{037BAB81-3DF7-4381-A72C-A26B57C03548}_is1 +XP8JNNTH0LT9F1|ApowerEdit|网旭科技|ApowerEdit V1.7.7.22|1.7.7.22|Apowersoft LIMITED|{3089CCCD-BC5F-4309-A3C1-45B5ACA7A5E7}_is1 +XP8K17KD2T7W8V|Ashampoo WinOptimizer 19|Ashampoo|Ashampoo WinOptimizer 19|19.00.23|Ashampoo GmbH & Co. KG|{4209F371-A9E3-7DD2-C1E5-04BB2B081219}_is1 +XP8K1F4KDP9DSJ|Autonoleggio N.S.C.|Esposito Software di M. G. Caputo|Autonoleggio NSC 3.0 Demo||Copyright Esposito Software|Autonoleggio NSC 3.0 Demo_is1 +XP8K43JX54F7FL|Cute Cursors|Cute Cursors|CuteCursors|1.0.0|Apollo One|{6683BBFB-B899-4755-B260-DF0387D9F872} +XP8K513CFB5K58|Archivio Dipendenti con Foto|Esposito Software di Maria Grazia Caputo|Archivio Dipendenti con Foto Demo||Copyright Esposito Software|Archivio Dipendenti con Foto Demo_is1 +XP8LFCZM790F6B|Visual Studio Code - Insiders|Microsoft Corporation|Microsoft Visual Studio Code Insiders (User)|1.67.0|Microsoft Corporation|{217B4C08-948D-4276-BFBB-BEE930AE5A2C}_is1 +XP8LFD92C0T8P0|Stampa Tessere Associazioni|Esposito Software di Maria Grazia Caputo|Stampa Tessere Associazioni 5.0 Demo||Copyright Esposito Software|Stampa Tessere Associazioni 5.0 Demo_is1 +XP8LG1VTM0XW03|Gestione Protocollo e Pratiche|Esposito Software di Maria Grazia Caputo|Gestione Protocollo e Pratiche Demo||Copyright Esposito Software|Gestione Protocollo e Pratiche Demo_is1 +XP8LG2X182JTJ9|Wondershare Dr.Fone - Mobile Device Management|WONDERSHARE GLOBAL LIMITED|Wondershare Dr.Fone (Version 10.9.6)|10.9.6.398|Wondershare Technology Co.,Ltd.|{E8F86DA8-B8E4-42C7-AFD4-EBB692AC43FD}_is1 +XP8LG65GV4C7C8|GitMind Mind Map|网旭科技|GitMind 1.0.8|1.0.8|Apowersoft|a0e10d84-6512-552f-a0ec-5dd2e61ffe64 +XP8LKPZT4X0Z0P|GOM Player|Gom and Company|GOM Player|2.3.67.5331|GOM & Company|GOM Player +XP8LKWQ22DX3TF|JYL Visitor Windows|JYL Software|JYL Visitor 1.94|1.94|JYL Software|{02ADFF54-7D56-42F1-B517-FDA35F55D2CC} +XP99J3KP4XZ4VV|ZOOM Cloud Meetings|Zoom Video Communications, Inc.|Zoom|5.10.0 (4306)|Zoom Video Communications, Inc.|ZoomUMX +XP99J7FXZD0JDM|Emjysoft eSanté|Emjysoft|Suivi des soins et des remboursements de Santé|3.11|Emjysoft|{6CC28634-D98C-4DE1-9EE7-E121277996F6}_is1 +XP99JXDBM4XKFP|Parallels Toolbox|Corel Corporation|Parallels Toolbox|5.1.0.3170|Parallels International GmbH|{5145E2CF-E9FC-48E6-A2B4-E409FC84D059} +XP99K41V2P36RQ|MSIX Editor|InstallAware Software Corporation|InstallAware Msix Editor 1.0|1.0.0.2703|InstallAware Software|InstallAware Msix Editor 1.0 +XP99VR1BPSBQJ2|Epic Games Store|Epic Games Inc.|Epic Games Launcher|1.3.23.0|Epic Games, Inc.|{FAC47927-1A6A-4C6E-AD7D-E9756794A4BC} +XP99WSCKQSH7SW|Emjysoft Sauvegarde Facile|Emjysoft|Easy Backup|VersionApplication|Emjysoft|{37215B1A-1990-4F55-936E-C9BA1634EF75}}_is1 +XP99WT9NMGB1PN|蜜蜂剪辑|网旭科技|BeeCut V1.7.7.22|1.7.7.22|Apowersoft LIMITED|{CA76BFA8-1862-49D7-B2C7-AE3D6CF40E53}_is1 +XP9B0HTG55KTCH|Free Hex Editor Neo|HHD Software Ltd.|HHD Software Free Hex Editor Neo 6.54|6.54.02.6790|HHD Software, Ltd.|{8EB85C0E-DE7D-4A53-BD66-708B8F2C80B0} +XP9B16C2TFN8P1|GOM Mix Pro|Gom and Company|GOM Mix Pro|2.0.4.8|GOM & Company|GOMMixPro +XP9CFZ9PKV0DWS|Automation Workshop|Febooti, SIA|Febooti Automation Workshop|5.1.1.0|Febooti Software|{6114DD12-2516-4465-9275-FB9A8E1A583C} +XP9CRZD7D219NK|FolderSizes|Key Metric Software|FolderSizes 9|9.3.362|Key Metric Software|{587D3069-EFE1-4FC2-B917-01496D5ABF8A} +XP9CRZQDCJ0CC6|LetsView|网旭科技|LetsView V1.1.2.5|1.1.2.5|LetsView LIMITED|{6AA74BE4-9506-4D81-A07C-A40F883C2EA7}_is1 +XP9CSP03RV8BX9|Audials One 2022|Audials AG|Audials 2022|22.0.177.0|Audials AG|{3F273072-3D14-479E-B4CD-AC8B1F436DA1} +XP9K4SR87H227Q|VisualNEO Win|SinLios Soluciones Digitales|VisualNEO Win|21.9.9|SinLios|{57147D4D-2492-41EC-A552-FB37C1C7FF3E}_is1 +XP9K5VRXFHVP75|Database Creator|Esposito Software di Maria Grazia Caputo|Database Creator Demo||Copyright Esposito Software|Database Creator Demo_is1 +XP9K5XN9BRN466|Housecall Free Virus - Malware Scanner|Trend Micro Inc.|HouseCall|1.62|Trend Micro Inc.|{A114E34B-AA5C-4DD8-98A9-3130ACA19491} +XP9KHKZS1M19ZP|x-studio|Simdsoft Limited|x-studio 2022|2022.1.4|Simdsoft Limited|{2F7387D3-EB5F-4CA5-8C42-04C59F922740} +XP9KHM4BK9FZ7Q|Visual Studio Code|Microsoft Corporation|Microsoft Visual Studio Code (User)|1.66.0|Microsoft Corporation|{771FD6B0-FA20-440A-A002-3B3BAC16DC50}_is1 +XP9KHPQ5C9MSN2|ZIPmagic|ZIPmagic Software|ZIPmagic|19.19.21|Simon King|ZIPmagic +XP9KHPXMW6RQLL|Gestione Studio Tecnico|Esposito Software di M. G. Caputo|Gestione Studio Tecnico Demo||Copyright Esposito Software|Gestione Studio Tecnico Demo_is1 +XP9KHQZV691PF9|PTZ Link|AVer Information|AVer PTZ Link|1.1.1013.0|AVer Information Inc|{AC08D179-14D5-4B93-9684-20DBE0848637} +XP9KM2X7H10448|PCmover Reconfigurator|Laplink Software Inc|Laplink Reconfigurator|1.0.0.1|Laplink Software, Inc.|{BBB86720-65BA-452A-A14D-B152CB506DD8} +XP9M20CZB2C5W8|Powder - Gaming Recorder|Unique Entertainment Experience SAS|Powder 2.5.0|2.5.0|powder-team|2b39bc52-9c37-5fcd-ab25-906727f7c690 +XP9MFNDJM19N0G|Gestione Affitti Pro|Esposito Software di M. G. Caputo|Gestione Affitti Pro 4.0 Demo||Copyright Esposito Software|Gestione Affitti Pro 4.0 Demo_is1 +XPDBZ0BW87BCTV|POWER-KI Executor|XPLAB - Research in Automation|POWER-KI Executor|33.11|XPLAB - Research in Automation - Brescia - Italy|{B2B40FB5-0B60-4B47-A1F1-F0254CD0BE04} +XPDBZ4MPRKNN30|Opera GX|Opera Norway AS|Opera GX Stable 82.0.4227.44|82.0.4227.44|Opera Software|Opera GX 82.0.4227.44 +XPDC1LX9VNW7Z7|VirtualDJ|Atomix International, S.A.|VirtualDJ 2021|8.5.6747.0|Atomix Productions|{97CFEA35-98EF-4EBC-8AF1-4F161CFCAE86} +XPDC2KHD93HVJW|Stampa Ricevute Generiche|Esposito Software di Maria Grazia Caputo|Stampa Ricevute Generiche Demo||Copyright Esposito Software|Stampa Ricevute Generiche Demo_is1 +XPDCFJD1GFFDXD|WorldClock.Classic|Fulvio Castelli|WorldClock (Trial)|7.0.12.0|Fulvio Castelli|{E32193B9-8870-40be-B88A-B302251B8AA7}_is1 +XPDCJ80KGNRVSS|TeamSpeak|TeamSpeak Systems GmbH|TeamSpeak|5.0.0|TeamSpeak|{C9D97E1E-B188-4500-A87D-902530E0D1E0} +XPDCK0XGHVWNBK|Trend Micro Antivirus Plus Security|Trend Micro Inc.|Trend Micro Antivirus+|17.7|Trend Micro Inc.|{ABBD4BA8-6703-40D2-AB1E-5BB1F7DB49A4} +XPDDZ434WT2M5Z|SOLARWATT Pro experience|SOLARWATT GmbH|SOLARWATT Experience|2.1.0.4|SOLARWATT|{40CF234F-1D35-4ED8-AAFC-E07EA2FD8B3B} +XPDF9J69VVFMX3|Apowersoft Background Eraser|网旭科技|Apowersoft background eraser V2.3.13|2.3.13|Apowersoft LIMITED|{98EC0F66-C563-40FA-A77A-F2FC558F5DAA}_is1 +XPDFF6P40P0M5Q|星愿浏览器|Twinkstar|Twinkstar Browser|7.12.1000.2112|Twinkstar Limited|Twinkstar +XPDLNG5248Q7NC|HttpMaster Express|Borvid, Informacijske storitve, Janez Čas s.p.|HttpMaster Express Edition 5.4.1|5.4.1|Borvid|{B61241AA-F5FC-42C9-A1F9-F6D72D654349} +XPDM19SX6D8V40|JYL Orders Suppliers Windows|JYL Software|JYL Order Suppliers 1.70|1.70|JYL Software|{57DF6E60-F6E4-498F-9637-18D6C0FA08B9} +XPDM4ZR5KJ9JN9|PowerDirector 365 Free - Video Editor, Movie Maker|CyberLink Corp.|CyberLink PowerDirector 365|20.1.2519.0|CyberLink Corp.|{278A8296-12A6-4CD0-8A8E-6947948477C5} +XPDM5Q9J9SFCX9|Stampa Ricevute Pagamento|Esposito Software di M. G. Caputo|Stampa Ricevute Pagamento Demo||Copyright Esposito Software|Stampa Ricevute Pagamento Demo_is1 +XPDNG54ZDC79K0|JYL Time Clock Windows|JYL Software|JYL Time Clock 2.22|2.22|JYL Software|{839FD23A-EFE9-4252-AF1A-B8B56ED925F4} +XPDNH1FMW7NB40|火绒安全软件|Beijing Huorong Network Technology Co., Ltd.|Huorong Internet Security|5.0|Beijing Huorong Network Technology Co., Ltd.|HuorongSysdiag +XPDNLQK867NNXF|Ashampoo ZIP Pro 4|Ashampoo|Ashampoo ZIP Pro 4|4.10.22|Ashampoo GmbH & Co. KG|{0A11EA01-1F01-7AF6-20A2-E6F8131AD29C}_is1 +XPDNXDPXBRSVXT|WinZip 26|WinZip Computing|WinZip 26.0|26.0.15033|Corel Corporation|{CD95F661-A5C4-44F5-A6AA-ECDD91C2413F} +XPDNXG5333CSVK|Hard Disk Sentinel Professional|Janos Mathe|Hard Disk Sentinel PRO|6.01|Janos Mathe|Hard Disk Sentinel_is1 +XPDNZ9TPLKW6TB|Fy Slideshow|Guutara's Notebook|Fy Slideshow|5.6.0|Guutara|{5A4DEC47-8784-4591-983F-A3A6C3C89A46} +XPDNZJFNCR1B07|Avast Free Antivirus|AVAST Software|Avast Free Antivirus|22.2.6003|Avast Software|Avast Antivirus +XPDP1XPZR8NL28|Studio Medico Pro|Esposito Software di M. G. Caputo|Studio Medico Pro 3.0 Demo||Copyright Esposito Software|Studio Medico Pro 3.0 Demo_is1 +XPDP255TRF9WP8|Logspire|Anfibia Software|Logspire 1.0.0.51|1.0.0.51|Anfibia|Logspire_is1 +XPDP2X1MMZ4KR8|Ashampoo Burning Studio 23|Ashampoo|Ashampoo Burning Studio 23|23.0.5|Ashampoo GmbH & Co. KG|{91B33C97-2A56-F111-077E-E591CE9D7DE7}_is1 +XPFCFBB4FB3D6D|Emjysoft Cleaner|Emjysoft|Emjysoft Cleaner 2022 v4.1|4.1|Emjysoft|{167B1302-A739-42DE-BBD2-4C2F13D1FF51}_is1 +XPFCFKCNNTXGQD|Yandex Browser|Yandex|Yandex|21.9.1.686|ООО «ЯНДЕКС»|YandexBrowser +XPFCFL5ZTNFGD7|Wondershare Anireel|WONDERSHARE GLOBAL LIMITED|Wondershare Anireel(Build 1.6.2)||Wondershare Software|Wondershare Anireel_is1 +XPFCG86X2PGLDJ|Christmas Elf by Pothos|Pothos|Christmas Elf|||ChristmasElf +XPFCGHHXNH4WBW|Biblioteca e Prestiti Librari|Esposito Software di M. G. Caputo|Gestione Biblioteca e Prestiti Librari 3.0 Demo||Copyright Esposito Software|Gestione Biblioteca e Prestiti Librari 3.0 Demo_is1 +XPFCWP0SQWXM3V|CCleaner|Piriform Software Ltd|CCleaner|5.89|Piriform|CCleaner +XPFCXFRDJ8VGPT|Домашняя Бухгалтерия|Keepsoft|Äîìàøíÿÿ áóõãàëòåðèÿ Lite|7.2|Keepsoft|Äîìàøíÿÿ áóõãàëòåðèÿ Lite +XPFCXPF18WNKP6|Total Defense Essential Anti-Virus|Total Defense, Inc.|Total Defense|13.0.0.572|Total Defense, Inc.|TotalDefense +XPFCXS0QVTHDC9|Active@ Disk Editor|LSoft Technologies Inc.|Active@ Disk Editor 7|7|LSoft Technologies Inc|{F40165C8-BD5B-4E42-A40D-396BB707E5B7}_is1 +XPFD27PCFQJQ68|TextSeek|Xiamen Zesite Company|TextSeek|2.12.3060|Zesite Company|TextSeek +XPFD28MTCS0GXJ|VisualNEO Web|SinLios Soluciones Digitales|VisualNeoWeb||SinLios|{EEF9B1C5-7E35-4972-A79A-44B2B2C72D3D}_is1 +XPFFBRXVQ2L6JN|Coolnew PDF|CoolNewPDF|CoolNew PDF|3.0.0.1|CoolNew Software Corporation|coolnewpdf +XPFFC9N4PVM9N8|Prenotazione Tavoli OK|Esposito Software di Maria Grazia Caputo|Prenotazione Tavoli OK Demo||Copyright Esposito Software|Prenotazione Tavoli OK Demo_is1 +XPFFCCM235X204|Fy Memo|Guutara's Notebook|Fy Memo|6.5.0|Guutara|{4BDAE26E-3414-4516-89F9-B6C277029CA5} +XPFFCM599XXT5P|傲软录屏|网旭科技|ApowerREC V1.5.5.18|1.5.5.18|Apowersoft LIMITED|{6F2998B2-21F7-4CEF-94B2-C3919D939CF9}_is1 +XPFFH5S3C4Q1CB|傲软抠图|网旭科技|Apowersoft background eraser V2.3.13|2.3.13|Apowersoft LIMITED|{98EC0F66-C563-40FA-A77A-F2FC558F5DAA}_is1 +XPFFSV0VCDKTM5|PolicyApplicator Conversion Kit|Hauke Hasselberg|PolicyApplicator Conversion Kit|1.0.11.0|Hauke Götze|{C918DB43-6B86-4364-BEAC-1184D3EE3C07} +XPFFT29L5QQ7RL|SRPG Studio|SapphireSoft|SRPG Studio Trial version 1.251|1.251|SapphireSoft|{FBC98908-FD84-4C92-A539-5DA61EDD7F9F}_is1 +XPFFT3RD5FMWX2|Emjysoft Comptabilité Personnelle|Emjysoft|Personal Finance|20.5|Emjysoft|{2369DC9E-11A7-4BAE-A43E-7A4CB477574F}_is1 +XPFFTPNN0NNHVQ|Auto Print Order|NAMTUK|AutoPrintOrder 1.10.1215|1.10.1215|Namtuk|{B26EF0DD-2375-4E88-9991-4652AC89FE3F} +XPFM2BJ3RPZ9XB|轻闪PDF编辑|网旭科技|LightPDF Editor V1.2.6.0|1.2.6.0|Apowersoft LIMITED|{161C8BF4-DB06-49A7-B6AC-7CAB7DAF136F}_is1 +XPFM306TS4PHH5|Ashampoo Burning Studio FREE|Ashampoo|Ashampoo Burning Studio FREE|1.21.5|Ashampoo GmbH & Co. KG|{91B33C97-91F8-FFB3-581B-BC952C901685}_is1 +XPFM5W1J84KQZX|ndCurveMaster|SigmaLab Tomasz Cepowski|ndCurveMaster Trial x64 version 8.2.0.1|8.2.0.1|SigmaLab|{5FB2948C-B95A-49CD-A2ED-62D0A38D7B1C}_is1 +XPFMJGWHHCNL5P|傲软投屏—手机/电脑/电视高清投屏神器|网旭科技|ApowerMirror V1.6.5.1|1.6.5.1|APOWERSOFT LIMITED|{a9482532-9c34-478c-80c3-85bdccbb981f}_is1 +XPFMKKKLHMMK6Q|Videoteca OK|Esposito Software di Maria Grazia Caputo|Videoteca OK 5.0 Demo||Copyright Esposito Software|Videoteca OK 5.0 Demo_is1 +XPFNZJKG6100L4|ASM Visual|gri-software|ASM Visual version 1.1.7|1.1.7|gri-software|{7416EF27-89A5-4819-9996-36C16F49BAEC}_is1 +XPFNZKDRP1SXM6|视频转换王|网旭科技|Apowersoft Video Converter Studio V4.8.6.7|4.8.6.7|APOWERSOFT LIMITED|{195E8D7F-292B-4B04-A6E7-E96CAF04C767}_is1 +XPFP0G0V147H6D|Wondershare PDFelement|WONDERSHARE GLOBAL LIMITED|Wondershare PDFelement ( Version 8.3.0 )|8.3.0|Wondershare|{343A530C-4726-4091-87E0-F9CC41792CE2}_is1 +XPFP2VCXM8D2DB|傲软PDF编辑——一键编辑&转化&压缩&签名PDF文件|网旭科技|ApowerPDF V5.4.2.3|5.4.2.3|Apowersoft LIMITED|{8691C793-7B2C-46C5-9AB2-AB80D129A5EC}_is1 +XPFP30KL61D4SC|Wondershare UniConverter|WONDERSHARE GLOBAL LIMITED|Wondershare UniConverter 13(Build 13.5.1.116)|13.5.1.116|Wondershare Software|UniConverter 13_is1 +XPFP42D8L456SK|X-VPN - Best VPN Proxy and Wifi Security|Free Connected Limited.|X-VPN|71.0|Free Connected Limited|X-VPN +XPFP42J061BPC1|Documenti Lavori Cantiere|Esposito Software di M. G. Caputo|Documenti Lavori Cantiere Demo||Copyright Esposito Software|Documenti Lavori Cantiere Demo_is1 +XPFPFN4LT21PZJ|Studio Dentistico Pro|Esposito Software di M. G. Caputo|Studio Dentistico Pro Demo||Copyright Esposito Software|Studio Dentistico Pro Demo_is1 +XPFPFWMVTR0WHP|Ashampoo UnInstaller 11|Ashampoo|Ashampoo UnInstaller 11|11.00.12|Ashampoo GmbH & Co. KG|{4209F371-B84B-F321-6BD3-1D91E2505732}_is1 +XPFPFWV5JD80K2|BeeCut|网旭科技|BeeCut V1.7.7.22|1.7.7.22|Apowersoft LIMITED|{CA76BFA8-1862-49D7-B2C7-AE3D6CF40E53}_is1 +XPFPLCB36G8V8J|HttpMaster Professional|Borvid, Informacijske storitve, Janez Čas s.p.|HttpMaster Professional Edition 5.4.1|5.4.1|Borvid|{B61241AA-F5FC-42C9-A1F9-F6D72D654349} XP8CDJNZKFM06W|Visual Studio Community 2019|Microsoft Corporation|Microsoft Visual Studio Installer|3.1.2196.8931|Microsoft Corporation|{6F320B93-EE3C-4826-85E0-ADF79F8D4C61} \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml index 501ea22312..73770c5537 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_AbortsTerminal.yaml @@ -1,18 +1,18 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -InstallerAbortsTerminal: true -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.1.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +InstallerAbortsTerminal: true +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_EncodedUrl.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_EncodedUrl.yaml index 509da3e4eb..1760c0b582 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_EncodedUrl.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_EncodedUrl.yaml @@ -1,18 +1,18 @@ -Id: AppInstallerCliTest.UrlEncodeTest -Version: 1.0.0.0 -Name: AppInstaller Test Exe Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -ProductCode: AppInstallerCliTest.TestExeInstaller -Installers: - - Arch: x64 - Url: https://EncodedUrlTest/%E6%B5%8B%E8%AF%95.exe - InstallerType: exe - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Switches: - Custom: /encodedUrl - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.UrlEncodeTest +Version: 1.0.0.0 +Name: AppInstaller Test Exe Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +ProductCode: AppInstallerCliTest.TestExeInstaller +Installers: + - Arch: x64 + Url: https://EncodedUrlTest/%E6%B5%8B%E8%AF%95.exe + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Switches: + Custom: /encodedUrl + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yaml index e392dcbff5..1d0d8df2fe 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Exe.yaml @@ -1,29 +1,29 @@ -Id: AppInstallerCliTest.TestExeInstaller -Version: 1.0.0.0 -Name: AppInstaller Test Exe Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -ProductCode: AppInstallerCliTest.TestExeInstaller -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: exe - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Scope: user - Switches: - Custom: /custom /scope=user - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: exe - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Scope: machine - Switches: - Custom: /custom /scope=machine - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestExeInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Exe Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +ProductCode: AppInstallerCliTest.TestExeInstaller +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Scope: user + Switches: + Custom: /custom /scope=user + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Scope: machine + Switches: + Custom: /custom /scope=machine + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_ExpectedReturnCodes.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_ExpectedReturnCodes.yaml index 45fe80534b..1223cbbbee 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_ExpectedReturnCodes.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_ExpectedReturnCodes.yaml @@ -1,49 +1,49 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.ExpectedReturnCodes -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: TestExeInstallerWithExpectedReturnCodes -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - ExpectedReturnCodes: - - InstallerReturnCode: 1 - ReturnResponse: packageInUse - - InstallerReturnCode: 2 - ReturnResponse: installInProgress - - InstallerReturnCode: 3 - ReturnResponse: fileInUse - - InstallerReturnCode: 4 - ReturnResponse: missingDependency - - InstallerReturnCode: 5 - ReturnResponse: diskFull - - InstallerReturnCode: 6 - ReturnResponse: insufficientMemory - - InstallerReturnCode: 7 - ReturnResponse: noNetwork - - InstallerReturnCode: 8 - ReturnResponse: contactSupport - ReturnResponseUrl: https://TestReturnResponseUrl - - InstallerReturnCode: 9 - ReturnResponse: rebootRequiredToFinish - - InstallerReturnCode: 10 - ReturnResponse: rebootRequiredForInstall - - InstallerReturnCode: 11 - ReturnResponse: rebootInitiated - - InstallerReturnCode: 12 - ReturnResponse: cancelledByUser - - InstallerReturnCode: 13 - ReturnResponse: alreadyInstalled - - InstallerReturnCode: 14 - ReturnResponse: downgrade - - InstallerReturnCode: 15 - ReturnResponse: blockedByPolicy -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.ExpectedReturnCodes +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: TestExeInstallerWithExpectedReturnCodes +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + ExpectedReturnCodes: + - InstallerReturnCode: 1 + ReturnResponse: packageInUse + - InstallerReturnCode: 2 + ReturnResponse: installInProgress + - InstallerReturnCode: 3 + ReturnResponse: fileInUse + - InstallerReturnCode: 4 + ReturnResponse: missingDependency + - InstallerReturnCode: 5 + ReturnResponse: diskFull + - InstallerReturnCode: 6 + ReturnResponse: insufficientMemory + - InstallerReturnCode: 7 + ReturnResponse: noNetwork + - InstallerReturnCode: 8 + ReturnResponse: contactSupport + ReturnResponseUrl: https://TestReturnResponseUrl + - InstallerReturnCode: 9 + ReturnResponse: rebootRequiredToFinish + - InstallerReturnCode: 10 + ReturnResponse: rebootRequiredForInstall + - InstallerReturnCode: 11 + ReturnResponse: rebootInitiated + - InstallerReturnCode: 12 + ReturnResponse: cancelledByUser + - InstallerReturnCode: 13 + ReturnResponse: alreadyInstalled + - InstallerReturnCode: 14 + ReturnResponse: downgrade + - InstallerReturnCode: 15 + ReturnResponse: blockedByPolicy +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml index c45e48ef02..7e3669b352 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallLocationRequired.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -InstallLocationRequired: true -InstallerSwitches: - InstallLocation: /InstallDir -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.1.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +InstallLocationRequired: true +InstallerSwitches: + InstallLocation: /InstallDir +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallationNotes.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallationNotes.yaml index 9f9e89b939..4bd05586fa 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallationNotes.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_InstallationNotes.yaml @@ -1,18 +1,18 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -InstallationNotes: testInstallationNotes -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +InstallationNotes: testInstallationNotes +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_InvalidFileCharacterUrl.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_InvalidFileCharacterUrl.yaml index 028e4c9784..af13601689 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_InvalidFileCharacterUrl.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_InvalidFileCharacterUrl.yaml @@ -1,18 +1,18 @@ -Id: AppInstallerCliTest.InvalidFileCharacterUrlTest -Version: 1.0.0.0 -Name: AppInstaller Test Exe Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -ProductCode: AppInstallerCliTest.TestExeInstaller -Installers: - - Arch: x64 - Url: https://EncodedUrlTest/te*st.exe - InstallerType: exe - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Switches: - Custom: /invalidFileCharacterUrl - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.InvalidFileCharacterUrlTest +Version: 1.0.0.0 +Name: AppInstaller Test Exe Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +ProductCode: AppInstallerCliTest.TestExeInstaller +Installers: + - Arch: x64 + Url: https://EncodedUrlTest/te*st.exe + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Switches: + Custom: /invalidFileCharacterUrl + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_LicenseAgreement.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_LicenseAgreement.yaml index 3264fe035f..7806757ccb 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_LicenseAgreement.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_LicenseAgreement.yaml @@ -1,25 +1,25 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: TestInstaller.WithLicenseAgreement -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: TestInstallerWithLicenseAgreement -Agreements: - - AgreementLabel: Agreement with text - Agreement: This is the text of the agreement. - - AgreementLabel: Agreement with URL - AgreementUrl: https://TestAgreementUrl -InstallerSwitches: - SilentWithProgress: /silentwithprogress - Silent: /silence -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.1.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: TestInstaller.WithLicenseAgreement +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: TestInstallerWithLicenseAgreement +Agreements: + - AgreementLabel: Agreement with text + Agreement: This is the text of the agreement. + - AgreementLabel: Agreement with URL + AgreementUrl: https://TestAgreementUrl +InstallerSwitches: + SilentWithProgress: /silentwithprogress + Silent: /silence +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore.yaml index 3c958cac01..87cfa088e2 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_MSStore.yaml @@ -1,13 +1,13 @@ -Id: AppInstallerCliTest.TestMSStoreInstaller -Version: Latest -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestMSStore -License: Test -Installers: - - Arch: neutral - Url: https://ThisIsNotUsed - InstallerType: msstore - ProductId: 9WZDNCRFJ364 - PackageFamilyName: Microsoft.SkypeApp_kzf8qxf38zg5c -ManifestVersion: 0.2.0-msstore +Id: AppInstallerCliTest.TestMSStoreInstaller +Version: Latest +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestMSStore +License: Test +Installers: + - Arch: neutral + Url: https://ThisIsNotUsed + InstallerType: msstore + ProductId: 9WZDNCRFJ364 + PackageFamilyName: Microsoft.SkypeApp_kzf8qxf38zg5c +ManifestVersion: 0.2.0-msstore diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_DownloadFlow.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_DownloadFlow.yaml index 23a9996ebf..3601c7be5d 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_DownloadFlow.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_DownloadFlow.yaml @@ -1,13 +1,13 @@ -Id: AppInstallerCliTest.TestMsixInstaller -Version: 1.0.0.0 -Name: AppInstaller Test MSIX Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestMsix -License: Test -Installers: - - Arch: x64 - Url: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true - InstallerType: msix - Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea - PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestMsixInstaller +Version: 1.0.0.0 +Name: AppInstaller Test MSIX Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestMsix +License: Test +Installers: + - Arch: x64 + Url: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true + InstallerType: msix + Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea + PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_StreamingFlow.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_StreamingFlow.yaml index b9ada47afd..221471c88d 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_StreamingFlow.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Msix_StreamingFlow.yaml @@ -1,14 +1,14 @@ -Id: AppInstallerCliTest.TestMsixInstaller -Version: 1.0.0.0 -Name: AppInstaller Test MSIX Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestMsix -License: Test -Installers: - - Arch: x64 - Url: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true - InstallerType: msix - Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea - SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07 - PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestMsixInstaller +Version: 1.0.0.0 +Name: AppInstaller Test MSIX Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestMsix +License: Test +Installers: + - Arch: x64 + Url: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true + InstallerType: msix + Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea + SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07 + PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_MultipleDependencies.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_MultipleDependencies.yaml index fa0a9ebed1..e33e4ec0d0 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_MultipleDependencies.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_MultipleDependencies.yaml @@ -1,26 +1,26 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.3.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller.MultipleDependencies -PackageVersion: 1.0.0.0 -PackageName: AppInstaller Test Installer -PackageLocale: en-US -Publisher: Microsoft Corporation -ShortDescription: Installs exe installer with multiple dependencies -Moniker: AICLITestExe -License: Test -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Upgrade: /upgrade -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Dependencies: - PackageDependencies: - - PackageIdentifier: Dependency1 - - PackageIdentifier: Dependency2 -ManifestType: singleton -ManifestVersion: 1.3.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.3.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.MultipleDependencies +PackageVersion: 1.0.0.0 +PackageName: AppInstaller Test Installer +PackageLocale: en-US +Publisher: Microsoft Corporation +ShortDescription: Installs exe installer with multiple dependencies +Moniker: AICLITestExe +License: Test +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Upgrade: /upgrade +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Dependencies: + PackageDependencies: + - PackageIdentifier: Dependency1 + - PackageIdentifier: Dependency2 +ManifestType: singleton +ManifestVersion: 1.3.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yaml index d2162be10c..3b00fdc3f8 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_NoApplicableArchitecture.yaml @@ -1,16 +1,16 @@ -Id: AppInstallerCliTest.TestInstaller -Version: 1.0.0.0 -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -Switches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence -Installers: - - Arch: unknown - Url: https://ThisIsNotUsed - InstallerType: exe - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +Switches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence +Installers: + - Arch: unknown + Url: https://ThisIsNotUsed + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_NonZeroExitCode.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_NonZeroExitCode.yaml index 636caed189..0af66259d1 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_NonZeroExitCode.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_NonZeroExitCode.yaml @@ -1,23 +1,23 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: AppInstaller Test Installer -InstallerSwitches: - Custom: /ExitCode 0x80070005 - SilentWithProgress: /silentwithprogress - Silent: /silence -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - InstallerSuccessCodes: - - -2147024891 -ManifestType: singleton -ManifestVersion: 1.0.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: AppInstaller Test Installer +InstallerSwitches: + Custom: /ExitCode 0x80070005 + SilentWithProgress: /silentwithprogress + Silent: /silence +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSuccessCodes: + - -2147024891 +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable.yaml index a80f30093d..498660e00f 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable.yaml @@ -1,18 +1,18 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestPortableInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Portable Exe -Publisher: Microsoft Corporation -AppMoniker: AICLITestPortable -License: Test -ProductCode: AppInstallerCliTest.TestPortableInstaller__TestSource -ShortDescription: AppInstaller Test Portable Exe -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestPortableInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Portable Exe +Publisher: Microsoft Corporation +AppMoniker: AICLITestPortable +License: Test +ProductCode: AppInstallerCliTest.TestPortableInstaller__TestSource +ShortDescription: AppInstaller Test Portable Exe +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable_WithCommand.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable_WithCommand.yaml index 94045d0f58..80a3826863 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable_WithCommand.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Portable_WithCommand.yaml @@ -1,19 +1,19 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestPortableInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Portable Exe -Publisher: Microsoft Corporation -AppMoniker: AICLITestPortable -License: Test -ShortDescription: AppInstaller Test Portable Exe -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Commands: - - portableCommand -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestPortableInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Portable Exe +Publisher: Microsoft Corporation +AppMoniker: AICLITestPortable +License: Test +ShortDescription: AppInstaller Test Portable Exe +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Commands: + - portableCommand +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_UnknownVersion.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_UnknownVersion.yaml index d64a58ff28..826d5156c8 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_UnknownVersion.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_UnknownVersion.yaml @@ -1,21 +1,21 @@ -# Test manifest for upgrading a package with an unknown version. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.3.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeUnknownVersion -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Exe Unknown Version -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -Switches: - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.3.0 +# Test manifest for upgrading a package with an unknown version. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.3.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeUnknownVersion +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Exe Unknown Version +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +Switches: + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.3.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_UnsupportedArguments.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_UnsupportedArguments.yaml index 6ca7ab601e..665e7c1b55 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_UnsupportedArguments.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_UnsupportedArguments.yaml @@ -1,20 +1,20 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -UnsupportedArguments: - - log - - location -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +UnsupportedArguments: + - log + - location +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_WindowsFeatures.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_WindowsFeatures.yaml index 281ff2d803..b09a4df85e 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_WindowsFeatures.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_WindowsFeatures.yaml @@ -1,25 +1,25 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerCliTest.WindowsFeatures -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -ShortDescription: Installs exe installer with valid Windows Features dependencies -Publisher: Microsoft Corporation -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - InstallerSwitches: - Custom: /custom /scope=machine - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update - Dependencies: - WindowsFeatures: - - testFeature1 - - testFeature2 -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerCliTest.WindowsFeatures +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: Installs exe installer with valid Windows Features dependencies +Publisher: Microsoft Corporation +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSwitches: + Custom: /custom /scope=machine + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update + Dependencies: + WindowsFeatures: + - testFeature1 + - testFeature2 +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_Exe.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_Exe.yaml index 0707cc98a4..bc8fc83c24 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_Exe.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_Exe.yaml @@ -1,25 +1,25 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestZipInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Zip Installer -ShortDescription: AppInstaller Test Zip Installer with exe -Publisher: Microsoft Corporation -Moniker: AICLITestZip -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: relativeFilePath - InstallerSwitches: - Custom: /custom /scope=machine - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestZipInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Zip Installer +ShortDescription: AppInstaller Test Zip Installer with exe +Publisher: Microsoft Corporation +Moniker: AICLITestZip +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath + InstallerSwitches: + Custom: /custom /scope=machine + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MissingNestedInstaller.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MissingNestedInstaller.yaml index a3a7b95769..a299e91a99 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MissingNestedInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MissingNestedInstaller.yaml @@ -1,23 +1,23 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestZipInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Zip Installer -ShortDescription: AppInstaller Test Zip Installer with exe -Publisher: Microsoft Corporation -Moniker: AICLITestZip -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: exe - InstallerSwitches: - Custom: /custom /scope=machine - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestZipInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Zip Installer +ShortDescription: AppInstaller Test Zip Installer with exe +Publisher: Microsoft Corporation +Moniker: AICLITestZip +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe + InstallerSwitches: + Custom: /custom /scope=machine + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MultipleNonPortableNestedInstallers.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MultipleNonPortableNestedInstallers.yaml index 06007a0213..e79a82d93a 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MultipleNonPortableNestedInstallers.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_MultipleNonPortableNestedInstallers.yaml @@ -1,21 +1,21 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestZipInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Zip Installer -ShortDescription: AppInstaller Test Zip Installer with exe -Publisher: Microsoft Corporation -Moniker: AICLITestZip -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: installerOne.exe - - RelativeFilePath: installerTwo.exe -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestZipInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Zip Installer +ShortDescription: AppInstaller Test Zip Installer with exe +Publisher: Microsoft Corporation +Moniker: AICLITestZip +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: installerOne.exe + - RelativeFilePath: installerTwo.exe +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_UnsupportedNestedInstaller.yaml b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_UnsupportedNestedInstaller.yaml index 45aee36a6c..c7871815fd 100644 --- a/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_UnsupportedNestedInstaller.yaml +++ b/src/AppInstallerCLITests/TestData/InstallFlowTest_Zip_UnsupportedNestedInstaller.yaml @@ -1,23 +1,23 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestZipInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Zip Installer -ShortDescription: AppInstaller Test Zip Installer with exe -Publisher: Microsoft Corporation -Moniker: AICLITestZip -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: msstore - InstallerSwitches: - Custom: /custom /scope=machine - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestZipInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Zip Installer +ShortDescription: AppInstaller Test Zip Installer with exe +Publisher: Microsoft Corporation +Moniker: AICLITestZip +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: msstore + InstallerSwitches: + Custom: /custom /scope=machine + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_NoSwitches.yaml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_NoSwitches.yaml index ae8e6b259f..8bc53ac76a 100644 --- a/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_NoSwitches.yaml +++ b/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_NoSwitches.yaml @@ -1,12 +1,12 @@ -Id: AppInstallerCliTest.TestInstaller -Version: 1.0.0.0 -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITest -License: Test -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: inno - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +License: Test +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: inno + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_WithSwitches.yaml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_WithSwitches.yaml index 426f3ac7ca..980d2b15ea 100644 --- a/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_WithSwitches.yaml +++ b/src/AppInstallerCLITests/TestData/InstallerArgTest_Inno_WithSwitches.yaml @@ -1,18 +1,18 @@ -Id: AppInstallerCliTest.TestInstaller -Version: 1.0.0.0 -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITest -License: Test -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: inno - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Switches: - Custom: /mycustom - SilentWithProgress: /mysilentwithprogress - Silent: /mysilent - Log: /mylog="" - InstallLocation: /myinstalldir="" -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +License: Test +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: inno + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Switches: + Custom: /mycustom + SilentWithProgress: /mysilentwithprogress + Silent: /mysilent + Log: /mylog="" + InstallLocation: /myinstalldir="" +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_NoSwitches.yaml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_NoSwitches.yaml index ca0bc77c93..ceb532f810 100644 --- a/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_NoSwitches.yaml +++ b/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_NoSwitches.yaml @@ -1,13 +1,13 @@ -Id: AppInstallerCliTest.TestInstaller -Version: 1.0.0.0 -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITest -License: Test -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: msi - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +License: Test +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: msi + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_WithSwitches.yaml b/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_WithSwitches.yaml index a7e4630b39..5daad8f684 100644 --- a/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_WithSwitches.yaml +++ b/src/AppInstallerCLITests/TestData/InstallerArgTest_Msi_WithSwitches.yaml @@ -1,19 +1,19 @@ -Id: AppInstallerCliTest.TestInstaller -Version: 1.0.0.0 -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITest -License: Test -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: msi - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' - Switches: - Custom: /mycustom - SilentWithProgress: /mysilentwithprogress - Silent: /mysilent - Log: /mylog="" - InstallLocation: /myinstalldir="" -ManifestVersion: 0.1.0 +Id: AppInstallerCliTest.TestInstaller +Version: 1.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITest +License: Test +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: msi + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + ProductCode: '{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}' + Switches: + Custom: /mycustom + SilentWithProgress: /mysilentwithprogress + Silent: /mysilent + Log: /mylog="" + InstallLocation: /myinstalldir="" +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml b/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml index 41150892dc..1d3b149249 100644 --- a/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml +++ b/src/AppInstallerCLITests/TestData/Installer_Exe_Dependencies.yaml @@ -1,23 +1,23 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies -PackageVersion: '1.0' -PackageLocale: en-US -PackageName: AppInstaller Test Exe Installer With Package Dep -Publisher: Microsoft Corporation -Moniker: AICLITestExePackageDep -License: Test -ShortDescription: AppInstaller Test Exe Installer With Package Dep -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - InstallerSwitches: - SilentWithProgress: /silentwithprogress - Silent: /silence - Dependencies: - WindowsFeatures: - - PreviewIIS -ManifestType: singleton -ManifestVersion: 1.0.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: '1.0' +PackageLocale: en-US +PackageName: AppInstaller Test Exe Installer With Package Dep +Publisher: Microsoft Corporation +Moniker: AICLITestExePackageDep +License: Test +ShortDescription: AppInstaller Test Exe Installer With Package Dep +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSwitches: + SilentWithProgress: /silentwithprogress + Silent: /silence + Dependencies: + WindowsFeatures: + - PreviewIIS +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml index 255bae030d..e36db6e8df 100644 --- a/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml +++ b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesMultideclaration.yaml @@ -1,26 +1,26 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Exe Installer With Package Dep -Publisher: Microsoft Corporation -Moniker: AICLITestExePackageDep -License: Test -ShortDescription: AppInstaller Test Exe Installer With Package Dep -Dependencies: - WindowsFeatures: - - PreviewIISOnRoot -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - InstallerSwitches: - SilentWithProgress: /silentwithprogress - Silent: /silence - Dependencies: - WindowsFeatures: - - PreviewIIS -ManifestType: singleton -ManifestVersion: 1.0.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Exe Installer With Package Dep +Publisher: Microsoft Corporation +Moniker: AICLITestExePackageDep +License: Test +ShortDescription: AppInstaller Test Exe Installer With Package Dep +Dependencies: + WindowsFeatures: + - PreviewIISOnRoot +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSwitches: + SilentWithProgress: /silentwithprogress + Silent: /silence + Dependencies: + WindowsFeatures: + - PreviewIIS +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml index cde144dfa0..226007f45f 100644 --- a/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml +++ b/src/AppInstallerCLITests/TestData/Installer_Exe_DependenciesOnRoot.yaml @@ -1,23 +1,23 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Exe Installer With Package Dep -Publisher: Microsoft Corporation -Moniker: AICLITestExePackageDep -License: Test -ShortDescription: AppInstaller Test Exe Installer With Package Dep -Dependencies: - WindowsFeatures: - - PreviewIISOnRoot -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - InstallerSwitches: - SilentWithProgress: /silentwithprogress - Silent: /silence -ManifestType: singleton -ManifestVersion: 1.0.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Exe Installer With Package Dep +Publisher: Microsoft Corporation +Moniker: AICLITestExePackageDep +License: Test +ShortDescription: AppInstaller Test Exe Installer With Package Dep +Dependencies: + WindowsFeatures: + - PreviewIISOnRoot +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSwitches: + SilentWithProgress: /silentwithprogress + Silent: /silence +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/Installer_Msix_WFDependency.yaml b/src/AppInstallerCLITests/TestData/Installer_Msix_WFDependency.yaml index 7232c4cb7e..490289ea2b 100644 --- a/src/AppInstallerCLITests/TestData/Installer_Msix_WFDependency.yaml +++ b/src/AppInstallerCLITests/TestData/Installer_Msix_WFDependency.yaml @@ -1,21 +1,21 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestMsixInstaller.WFDep -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test MSIX Installer With Windows Feature Dep -Publisher: Microsoft Corporation -Moniker: AICLITestMsixWindowsFeatureDep -License: Test -ShortDescription: AppInstaller Test MSIX Installer With Windows Feature Dep -Installers: - - Architecture: x64 - InstallerUrl: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true - InstallerType: msix - InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea - PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe - Dependencies: - WindowsFeatures: - - Hyper-V -ManifestType: singleton -ManifestVersion: 1.0.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestMsixInstaller.WFDep +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test MSIX Installer With Windows Feature Dep +Publisher: Microsoft Corporation +Moniker: AICLITestMsixWindowsFeatureDep +License: Test +ShortDescription: AppInstaller Test MSIX Installer With Windows Feature Dep +Installers: + - Architecture: x64 + InstallerUrl: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true + InstallerType: msix + InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea + PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe + Dependencies: + WindowsFeatures: + - Hyper-V +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml index 5d2748c5a1..d13e4cb12f 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-Channel-NotSupported.yaml @@ -1,12 +1,12 @@ -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -Channel: release -InstallerType: Msi -License: Test -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +Channel: release +InstallerType: Msi +License: Test +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-UPPER.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-UPPER.yaml index 1db62610e3..549320f8ad 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-UPPER.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-UPPER.yaml @@ -1,12 +1,12 @@ -# Bad manifest. InstallerType is UPPER -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -INSTALLERTYPE: Msi -License: Test -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +# Bad manifest. InstallerType is UPPER +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +INSTALLERTYPE: Msi +License: Test +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-lower.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-lower.yaml index d4a98be628..5de95a4389 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-lower.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-DifferentCase-lower.yaml @@ -1,12 +1,12 @@ -# Bad manifest. InstallerType is lower case -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -installertype: Msi -License: Test -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +# Bad manifest. InstallerType is lower case +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +installertype: Msi +License: Test +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml index d686c4fdf7..a93b993ce8 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml @@ -1,13 +1,13 @@ -# Bad manifest. Duplicated key with different casing convention. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -InstallerType: Msi -installertype: Msi -License: Test -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +# Bad manifest. Duplicated key with different casing convention. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +InstallerType: Msi +installertype: Msi +License: Test +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml index 209f7ebe84..2c2e7de64b 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml @@ -1,23 +1,23 @@ -# Bad manifest. Expected return codes repeat the same return code -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - ExpectedReturnCodes: - - InstallerReturnCode: 1 - ReturnResponse: packageInUse - - InstallerReturnCode: 1 - ReturnResponse: installInProgress -ManifestType: singleton -ManifestVersion: 1.1.0 +# Bad manifest. Expected return codes repeat the same return code +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + ExpectedReturnCodes: + - InstallerReturnCode: 1 + ReturnResponse: packageInUse + - InstallerReturnCode: 1 + ReturnResponse: installInProgress +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-SuccessCodes.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-SuccessCodes.yaml index f1f8cc9f21..88ba95bee3 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-SuccessCodes.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-DuplicateReturnCode-SuccessCodes.yaml @@ -1,23 +1,23 @@ -# Bad manifest. Expected return codes repeat the same return code -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestInstaller -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - InstallerSuccessCodes: - - 1 - ExpectedReturnCodes: - - InstallerReturnCode: 1 - ReturnResponse: packageInUse -ManifestType: singleton -ManifestVersion: 1.1.0 +# Bad manifest. Expected return codes repeat the same return code +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestInstaller +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + InstallerSuccessCodes: + - 1 + ExpectedReturnCodes: + - InstallerReturnCode: 1 + ReturnResponse: packageInUse +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilent.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilent.yaml index c00b11c0ce..a8b04e4ee3 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilent.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilent.yaml @@ -1,14 +1,14 @@ -# Bad manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -License: Test -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - InstallerType: Exe - Switches: - Interactive: /i -ManifestVersion: 0.1.0 +# Bad manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +License: Test +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD + InstallerType: Exe + Switches: + Interactive: /i +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml index 19649eed42..bff9239c11 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml @@ -1,14 +1,14 @@ -# Bad manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -License: Test -Switches: - Interactive: /i -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - InstallerType: Exe -ManifestVersion: 0.1.0 +# Bad manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +License: Test +Switches: + Interactive: /i +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD + InstallerType: Exe +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml index e65c0cc252..8ed0e7f068 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml @@ -1,14 +1,14 @@ -# Bad manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -InstallerType: Exe -License: Test -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - Switches: - Interactive: /i -ManifestVersion: 0.1.0 +# Bad manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +InstallerType: Exe +License: Test +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD + Switches: + Interactive: /i +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml index 2008b97788..60cdcbc99b 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml @@ -1,14 +1,14 @@ -# Bad manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -InstallerType: Exe -License: Test -Switches: - Interactive: /i -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +# Bad manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +InstallerType: Exe +License: Test +Switches: + Interactive: /i +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml index 237e91e790..8abdcbfed3 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml @@ -1,44 +1,44 @@ -# Bad manifest. Installer Type portable can only have zero or one AppsAndFeatureEntry defined. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: TestInstaller.WithLicenseAgreement -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: TestInstallerWithLicenseAgreement -AppsAndFeaturesEntries: - - DisplayName: DisplayName1 - DisplayVersion: DisplayVersion1 - Publisher: Publisher1 - ProductCode: ProductCode1 - UpgradeCode: UpgradeCode1 - - DisplayName: DisplayName2 - DisplayVersion: DisplayVersion2 - Publisher: Publisher2 - ProductCode: ProductCode2 - UpgradeCode: UpgradeCode2 -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed2 - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - AppsAndFeaturesEntries: - - DisplayName: DisplayName1 - DisplayVersion: DisplayVersion1 - Publisher: Publisher1 - ProductCode: ProductCode1 - UpgradeCode: UpgradeCode1 - - DisplayName: DisplayName2 - DisplayVersion: DisplayVersion2 - Publisher: Publisher2 - ProductCode: ProductCode2 - UpgradeCode: UpgradeCode2 -ManifestType: singleton -ManifestVersion: 1.2.0 +# Bad manifest. Installer Type portable can only have zero or one AppsAndFeatureEntry defined. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: TestInstaller.WithLicenseAgreement +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: TestInstallerWithLicenseAgreement +AppsAndFeaturesEntries: + - DisplayName: DisplayName1 + DisplayVersion: DisplayVersion1 + Publisher: Publisher1 + ProductCode: ProductCode1 + UpgradeCode: UpgradeCode1 + - DisplayName: DisplayName2 + DisplayVersion: DisplayVersion2 + Publisher: Publisher2 + ProductCode: ProductCode2 + UpgradeCode: UpgradeCode2 +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed2 + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + AppsAndFeaturesEntries: + - DisplayName: DisplayName1 + DisplayVersion: DisplayVersion1 + Publisher: Publisher1 + ProductCode: ProductCode1 + UpgradeCode: UpgradeCode1 + - DisplayName: DisplayName2 + DisplayVersion: DisplayVersion2 + Publisher: Publisher2 + ProductCode: ProductCode2 + UpgradeCode: UpgradeCode2 +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml index dd34d8afcb..ba00e0b16e 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml @@ -1,28 +1,28 @@ -# Bad manifest. Installer Type portable can only have one or zero commands defined. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: TestInstaller.WithLicenseAgreement -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: TestInstallerWithLicenseAgreement -Commands: - - Command1 - - Command2 -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed2 - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Commands: - - Command1 - - Command2 -ManifestType: singleton -ManifestVersion: 1.2.0 +# Bad manifest. Installer Type portable can only have one or zero commands defined. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: TestInstaller.WithLicenseAgreement +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: TestInstallerWithLicenseAgreement +Commands: + - Command1 + - Command2 +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed2 + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Commands: + - Command1 + - Command2 +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidScope.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidScope.yaml index fe2e247251..f7ac8cee71 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidScope.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypePortable-InvalidScope.yaml @@ -1,24 +1,24 @@ -# Bad manifest. Installer Type portable does not support scope and should show a warning. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: TestInstaller.WithLicenseAgreement -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: TestInstallerWithLicenseAgreement -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Scope: Machine - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed2 - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.2.0 +# Bad manifest. Installer Type portable does not support scope and should show a warning. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: TestInstaller.WithLicenseAgreement +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: TestInstallerWithLicenseAgreement +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Scope: Machine + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed2 + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateCommandAlias.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateCommandAlias.yaml index 6c9278dcbc..339d642b1c 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateCommandAlias.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateCommandAlias.yaml @@ -1,24 +1,24 @@ -# Bad manifest. Installer type zip should not have any duplicate PortableCommandAlias values. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: Test installer for zip without nestedInstallers specified -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerFiles: - - RelativeFilePath: relativeFilePath1 - PortableCommandAlias: DUPLICATEALIAS - - RelativeFilePath: relativeFilePath2 - PortableCommandAlias: duplicateAlias -ManifestType: singleton -ManifestVersion: 1.4.0 +# Bad manifest. Installer type zip should not have any duplicate PortableCommandAlias values. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip without nestedInstallers specified +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1 + PortableCommandAlias: DUPLICATEALIAS + - RelativeFilePath: relativeFilePath2 + PortableCommandAlias: duplicateAlias +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateRelativeFilePath.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateRelativeFilePath.yaml index 56dea325ef..cc92c10566 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateRelativeFilePath.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-DuplicateRelativeFilePath.yaml @@ -1,24 +1,24 @@ -# Bad manifest. Installer type zip should not have any duplicate PortableCommandAlias values. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: Test installer for zip without nestedInstallers specified -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerFiles: - - RelativeFilePath: RELATIVEFILEPATH - PortableCommandAlias: alias1 - - RelativeFilePath: relativefilepath - PortableCommandAlias: alias2 -ManifestType: singleton -ManifestVersion: 1.4.0 +# Bad manifest. Installer type zip should not have any duplicate PortableCommandAlias values. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip without nestedInstallers specified +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerFiles: + - RelativeFilePath: RELATIVEFILEPATH + PortableCommandAlias: alias1 + - RelativeFilePath: relativefilepath + PortableCommandAlias: alias2 +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml index 3726d0aad0..0a6458ac3b 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml @@ -1,22 +1,22 @@ -# Bad manifest. A nested installer file must have a valid RelativeFilePath. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: Test installer for zip with an invalid RelativeFilePath -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: ../../relativeFilePath -ManifestType: singleton -ManifestVersion: 1.4.0 +# Bad manifest. A nested installer file must have a valid RelativeFilePath. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip with an invalid RelativeFilePath +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: ../../relativeFilePath +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml index b94cacf99d..36d08cd829 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml @@ -1,22 +1,22 @@ -# Bad manifest. A nested installer file must have a RelativeFilePath specified. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: Test installer for zip without RelativeFilePath specified -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: exe - NestedInstallerFiles: - - PortableCommandAlias: portableCommandAlias -ManifestType: singleton -ManifestVersion: 1.4.0 +# Bad manifest. A nested installer file must have a RelativeFilePath specified. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip without RelativeFilePath specified +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe + NestedInstallerFiles: + - PortableCommandAlias: portableCommandAlias +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml index bd90431fff..ccc614f3d2 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml @@ -1,25 +1,25 @@ -# Bad manifest. Installer type zip should have exactly one NestedInstaller specified. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: Test installer for zip with too many nestedInstallers specified. -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: msi - NestedInstallerFiles: - - RelativeFilePath: relativeFilePath1 - PortableCommandAlias: portableCommandAlias1 - - RelativeFilePath: relativeFilePath2 - PortableCommandAlias: portableCommandAlias2 -ManifestType: singleton -ManifestVersion: 1.4.0 +# Bad manifest. Installer type zip should have exactly one NestedInstaller specified. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip with too many nestedInstallers specified. +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: msi + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1 + PortableCommandAlias: portableCommandAlias1 + - RelativeFilePath: relativeFilePath2 + PortableCommandAlias: portableCommandAlias2 +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml index 753dca631e..97197179e9 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml @@ -1,20 +1,20 @@ -# Bad manifest. Installer type zip should have exactly one NestedInstaller specified. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: Test installer for zip without nestedInstallers specified -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: exe -ManifestType: singleton -ManifestVersion: 1.4.0 +# Bad manifest. Installer type zip should have exactly one NestedInstaller specified. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip without nestedInstallers specified +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml index e1b5b10dff..f13ad7c78e 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml @@ -1,22 +1,22 @@ -# Bad manifest. Installer type zip should have exactly one NestedInstaller specified. -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: Test installer for zip without nestedInstallers specified -Scope: user -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerFiles: - - RelativeFilePath: relativeFilePath - PortableCommandAlias: portableCommandAlias -ManifestType: singleton -ManifestVersion: 1.4.0 +# Bad manifest. Installer type zip should have exactly one NestedInstaller specified. +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: Test installer for zip without nestedInstallers specified +Scope: user +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath + PortableCommandAlias: portableCommandAlias +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-PublisherMissing.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-PublisherMissing.yaml index 4b8fe3de26..8a4c8ca3d5 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-PublisherMissing.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-PublisherMissing.yaml @@ -1,11 +1,11 @@ -# Bad manifest. Publisher missing -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -InstallerType: Zip -License: Test -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +# Bad manifest. Publisher missing +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +InstallerType: Zip +License: Test +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Bad-SwitchInvalid.yaml b/src/AppInstallerCLITests/TestData/Manifest-Bad-SwitchInvalid.yaml index ff9b8f3bff..8ccbd8e563 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Bad-SwitchInvalid.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Bad-SwitchInvalid.yaml @@ -1,14 +1,14 @@ -# Bad manifest. Incorrect switch key. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -InstallerType: Exe -License: Test -Switches: - NotASwitch: /fakeswitch -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +# Bad manifest. Incorrect switch key. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +InstallerType: Exe +License: Test +Switches: + NotASwitch: /fakeswitch +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-Silent.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-Silent.yaml index 6d54fa9c5f..32035ec473 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-Silent.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-Silent.yaml @@ -1,15 +1,15 @@ -# Good manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -License: Test -Installers: - - Arch: x86 - Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - InstallerType: Exe - Switches: - Silent: /s - SilentWithProgress: /s -ManifestVersion: 0.1.0 +# Good manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +License: Test +Installers: + - Arch: x86 + Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD + InstallerType: Exe + Switches: + Silent: /s + SilentWithProgress: /s +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-SilentRoot.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-SilentRoot.yaml index fb6ab43c14..f192e22951 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-SilentRoot.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExe-SilentRoot.yaml @@ -1,15 +1,15 @@ -# Good manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -License: Test -Switches: - Silent: /s - SilentWithProgress: /s -Installers: - - Arch: x86 - Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - InstallerType: Exe -ManifestVersion: 0.1.0 +# Good manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +License: Test +Switches: + Silent: /s + SilentWithProgress: /s +Installers: + - Arch: x86 + Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD + InstallerType: Exe +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-Silent.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-Silent.yaml index 51d9a8f5e3..b0719c805d 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-Silent.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-Silent.yaml @@ -1,15 +1,15 @@ -# Good manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -InstallerType: Exe -License: Test -Installers: - - Arch: x86 - Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - Switches: - Silent: /s - SilentWithProgress: /s -ManifestVersion: 0.1.0 +# Good manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +InstallerType: Exe +License: Test +Installers: + - Arch: x86 + Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD + Switches: + Silent: /s + SilentWithProgress: /s +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml index 4ae56b326e..349ba1defc 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml @@ -1,15 +1,15 @@ -# Good manifest. Installer Type exe requires Silent switch. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -InstallerType: Exe -License: Test -Switches: - Silent: /s - SilentWithProgress: /s -Installers: - - Arch: x86 - Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD -ManifestVersion: 0.1.0 +# Good manifest. Installer Type exe requires Silent switch. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +InstallerType: Exe +License: Test +Switches: + Silent: /s + SilentWithProgress: /s +Installers: + - Arch: x86 + Url: https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdk-x86.zip + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-Good-Switches.yaml b/src/AppInstallerCLITests/TestData/Manifest-Good-Switches.yaml index e5ff96435d..20756330ff 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-Good-Switches.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-Good-Switches.yaml @@ -1,16 +1,16 @@ -# Good manifest with Switches. -Id: microsoft.msixsdk -Name: MSIX SDK -Version: 1.7.32 -Publisher: Microsoft -InstallerType: Msi -License: Test -Switches: - SilentWithProgress: /passive -Installers: - - Arch: x86 - Url: https://ThisIsNotUsed - Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD - Switches: - Silent: /quiet -ManifestVersion: 0.1.0 +# Good manifest with Switches. +Id: microsoft.msixsdk +Name: MSIX SDK +Version: 1.7.32 +Publisher: Microsoft +InstallerType: Msi +License: Test +Switches: + SilentWithProgress: /passive +Installers: + - Arch: x86 + Url: https://ThisIsNotUsed + Sha256: 98B67758CEAFFCBB3FE47838FD0A8D7BD581C2650842D6B2B0E0D49A23270CCD + Switches: + Silent: /quiet +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-AppsAndFeatures.yaml b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-AppsAndFeatures.yaml index 4a0503d09f..d68bf41f8e 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-AppsAndFeatures.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-AppsAndFeatures.yaml @@ -36,7 +36,7 @@ Installers: - Architecture: x86 InstallerLocale: en-GB InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 ManifestType: singleton ManifestVersion: 1.12.0 diff --git a/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-Archive.yaml b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-Archive.yaml index 02f5072321..3cf8de07fc 100644 --- a/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-Archive.yaml +++ b/src/AppInstallerCLITests/TestData/Manifest-MSIX-in-Archive.yaml @@ -17,7 +17,7 @@ CopyrightUrl: https://www.microsoft.com/msixsdk/copyright ShortDescription: Archive with nested MSIX Description: A manifest containing an archive with a nested MSIX installer InstallerLocale: en-US -InstallerType: zip +InstallerType: zip NestedInstallerType: msix PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe NestedInstallerFiles: @@ -28,7 +28,7 @@ Installers: - Architecture: x86 InstallerLocale: en-GB InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 ManifestType: singleton ManifestVersion: 1.12.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderInvalid.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderInvalid.yaml index 17a1e1ec4e..e0c68e7442 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderInvalid.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderInvalid.yaml @@ -1,17 +1,17 @@ -# yaml-language-server= $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeaderInvalid -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header Invalid -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has an invalid schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server= $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeaderInvalid +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header Invalid +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has an invalid schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestTypeMismatch.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestTypeMismatch.yaml index ce370d7dd3..ae10e31cf1 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestTypeMismatch.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestTypeMismatch.yaml @@ -1,16 +1,16 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json -PackageIdentifier: AppInstallerCliTest.SchemaHeaderManifestTypeMismatch -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header ManifestType Mismatch -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a mismatched ManisfestType in the schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json +PackageIdentifier: AppInstallerCliTest.SchemaHeaderManifestTypeMismatch +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header ManifestType Mismatch +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a mismatched ManisfestType in the schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestVersionMismatch.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestVersionMismatch.yaml index 1950651794..fb2b5c30c3 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestVersionMismatch.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderManifestVersionMismatch.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.9.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeaderVersionMismatch -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header ManifestVersion Mismatch -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a mismatched ManisfestVersion in the schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.9.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeaderVersionMismatch +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header ManifestVersion Mismatch +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a mismatched ManisfestVersion in the schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderNotFound.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderNotFound.yaml index 2e42d9a7b7..6a9e7f4a84 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderNotFound.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderNotFound.yaml @@ -1,15 +1,15 @@ -PackageIdentifier: AppInstallerCliTest.SchemaHeaderNotFound -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header Not Found -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a missing schema header - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +PackageIdentifier: AppInstallerCliTest.SchemaHeaderNotFound +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header Not Found +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a missing schema header + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderURLPatternMismatch.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderURLPatternMismatch.yaml index 2c7b723156..f9a6016323 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderURLPatternMismatch.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-Bad-SchemaHeaderURLPatternMismatch.yaml @@ -1,17 +1,17 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest-invalid.singleton.1.10.0.schema.json - -PackageIdentifier: AppInstallerCliTest.SchemaHeaderURLPatternMismatch -PackageVersion: 1.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Schema Header URL Pattern Mismatch -Publisher: Microsoft Corporation -License: Test -ShortDescription: This manifest has a mismatched schema header URL pattern - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest-invalid.singleton.1.10.0.schema.json + +PackageIdentifier: AppInstallerCliTest.SchemaHeaderURLPatternMismatch +PackageVersion: 1.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Schema Header URL Pattern Mismatch +Publisher: Microsoft Corporation +License: Test +ShortDescription: This manifest has a mismatched schema header URL pattern + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-InstallerAuthentication.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-InstallerAuthentication.yaml index 883259d84d..ee5881dad5 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_10-InstallerAuthentication.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-InstallerAuthentication.yaml @@ -1,25 +1,25 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json - -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 -ShortDescription: Test installer for authentication - -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: msi - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Authentication: - AuthenticationType: microsoftEntraIdForAzureBlobStorage -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json + +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 +ShortDescription: Test installer for authentication + +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: msi + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Authentication: + AuthenticationType: microsoftEntraIdForAzureBlobStorage +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_10-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_10-Singleton.yaml index 62c6fdd5c7..a72d743272 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_10-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_10-Singleton.yaml @@ -1,202 +1,202 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-US -Publisher: Microsoft -PublisherUrl: https://www.microsoft.com -PublisherSupportUrl: https://www.microsoft.com/support -PrivacyUrl: https://www.microsoft.com/privacy -Author: Microsoft -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -ShortDescription: This is MSIX SDK -Description: The MSIX SDK project is an effort to enable developers -Moniker: msixsdk -Tags: - - "appxsdk" - - "msixsdk" -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: Default installation notes -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Icons: - - IconUrl: https://testIcon-en-US - IconFileType: ico - IconResolution: custom - IconTheme: default - IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 -Agreements: - - AgreementLabel: DefaultLabel - Agreement: DefaultText - AgreementUrl: https://DefaultAgreementUrl.net -InstallerLocale: en-US -Platform: - - Windows.Desktop - - Windows.Universal -MinimumOSVersion: 10.0.0.0 -InstallerType: exe -Scope: machine -InstallModes: - - interactive - - silent - - silentWithProgress -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Interactive: /interactive - Log: /log= - InstallLocation: /dir= - Upgrade: /upgrade - Repair: /repair -InstallerSuccessCodes: - - 1 - - 0x80070005 -UpgradeBehavior: uninstallPrevious -RepairBehavior: modify -Commands: - - makemsix - - makeappx -Protocols: - - protocol1 - - protocol2 -FileExtensions: - - appx - - msix - - appxbundle - - msixbundle -Dependencies: - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDep - MinimumVersion: 1.0.0 - ExternalDependencies: - - Outside dependencies -Capabilities: - - internetClient -RestrictedCapabilities: - - runFullTrust -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ProductCode: "{Foo}" -ReleaseDate: 2021-01-01 -InstallerAbortsTerminal: true -InstallLocationRequired: true -RequireExplicitUpgrade: true -DisplayInstallWarnings: true -ElevationRequirement: elevatesSelf -UnsupportedOSArchitectures: - - arm -AppsAndFeaturesEntries: - - DisplayName: DisplayName - DisplayVersion: DisplayVersion - Publisher: Publisher - ProductCode: ProductCode - UpgradeCode: UpgradeCode - InstallerType: exe -Markets: - AllowedMarkets: - - US -ExpectedReturnCodes: - - InstallerReturnCode: 10 - ReturnResponse: packageInUse - ReturnResponseUrl: https://DefaultReturnResponseUrl.com -UnsupportedArguments: - - log -NestedInstallerType: msi -NestedInstallerFiles: - - RelativeFilePath: RelativeFilePath - PortableCommandAlias: PortableCommandAlias -InstallationMetadata: - DefaultInstallLocation: "%ProgramFiles%\\TestApp" - Files: - - RelativeFilePath: "main.exe" - FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - FileType: launch - InvocationParameter: "/arg" - DisplayName: "DisplayName" -DownloadCommandProhibited: true -ArchiveBinariesDependOnPath: true - -Installers: - - Architecture: x86 - InstallerLocale: en-GB - Platform: - - Windows.Desktop - MinimumOSVersion: 10.0.1.0 - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Scope: user - InstallModes: - - interactive - InstallerSwitches: - Custom: /c - SilentWithProgress: /sp - Silent: /s - Interactive: /i - Log: /l= - InstallLocation: /d= - Upgrade: /u - Repair: /r - UpgradeBehavior: install - Commands: - - makemsixPreview - - makeappxPreview - Protocols: - - protocol1preview - - protocol2preview - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - MinimumVersion: 1.0.0 - ExternalDependencies: - - Preview Outside dependencies - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Capabilities: - - internetClientPreview - RestrictedCapabilities: - - runFullTrustPreview - ReleaseDate: 2021-02-02 - InstallerAbortsTerminal: false - InstallLocationRequired: false - RequireExplicitUpgrade: false - DisplayInstallWarnings: false - ElevationRequirement: elevationRequired - UnsupportedArguments: - - location - UnsupportedOSArchitectures: - - arm64 - Markets: - ExcludedMarkets: - - "US" - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - - InstallerReturnCode: 3 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com - DownloadCommandProhibited: false - ArchiveBinariesDependOnPath: false -ManifestType: singleton -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.10.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DownloadCommandProhibited: false + ArchiveBinariesDependOnPath: false +ManifestType: singleton +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml index c42b7feb46..6afb69e834 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_2-Singleton.yaml @@ -1,177 +1,177 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-US -Publisher: Microsoft -PublisherUrl: https://www.microsoft.com -PublisherSupportUrl: https://www.microsoft.com/support -PrivacyUrl: https://www.microsoft.com/privacy -Author: Microsoft -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -ShortDescription: This is MSIX SDK -Description: The MSIX SDK project is an effort to enable developers -Moniker: msixsdk -Tags: - - "appxsdk" - - "msixsdk" -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: Default installation notes -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Agreements: - - AgreementLabel: DefaultLabel - Agreement: DefaultText - AgreementUrl: https://DefaultAgreementUrl.net -InstallerLocale: en-US -Platform: - - Windows.Desktop - - Windows.Universal -MinimumOSVersion: 10.0.0.0 -InstallerType: exe -Scope: machine -InstallModes: - - interactive - - silent - - silentWithProgress -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Interactive: /interactive - Log: /log= - InstallLocation: /dir= - Upgrade: /upgrade -InstallerSuccessCodes: - - 1 - - 0x80070005 -UpgradeBehavior: uninstallPrevious -Commands: - - makemsix - - makeappx -Protocols: - - protocol1 - - protocol2 -FileExtensions: - - appx - - msix - - appxbundle - - msixbundle -Dependencies: - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDep - MinimumVersion: 1.0.0 - ExternalDependencies: - - Outside dependencies -Capabilities: - - internetClient -RestrictedCapabilities: - - runFullTrust -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ProductCode: "{Foo}" -ReleaseDate: 2021-01-01 -InstallerAbortsTerminal: true -InstallLocationRequired: true -RequireExplicitUpgrade: true -DisplayInstallWarnings: true -ElevationRequirement: elevatesSelf -UnsupportedOSArchitectures: - - arm -AppsAndFeaturesEntries: - - DisplayName: DisplayName - DisplayVersion: DisplayVersion - Publisher: Publisher - ProductCode: ProductCode - UpgradeCode: UpgradeCode - InstallerType: exe -Markets: - AllowedMarkets: - - US -ExpectedReturnCodes: - - InstallerReturnCode: 10 - ReturnResponse: packageInUse - ReturnResponseUrl: https://DefaultReturnResponseUrl.com -UnsupportedArguments: - - log - -Installers: - - Architecture: x86 - InstallerLocale: en-GB - Platform: - - Windows.Desktop - MinimumOSVersion: 10.0.1.0 - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Scope: user - InstallModes: - - interactive - InstallerSwitches: - Custom: /c - SilentWithProgress: /sp - Silent: /s - Interactive: /i - Log: /l= - InstallLocation: /d= - Upgrade: /u - UpgradeBehavior: install - Commands: - - makemsixPreview - - makeappxPreview - Protocols: - - protocol1preview - - protocol2preview - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - MinimumVersion: 1.0.0 - ExternalDependencies: - - Preview Outside dependencies - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Capabilities: - - internetClientPreview - RestrictedCapabilities: - - runFullTrustPreview - ReleaseDate: 2021-02-02 - InstallerAbortsTerminal: false - InstallLocationRequired: false - RequireExplicitUpgrade: false - DisplayInstallWarnings: false - ElevationRequirement: elevationRequired - UnsupportedArguments: - - location - UnsupportedOSArchitectures: - - arm64 - Markets: - ExcludedMarkets: - - "US" - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - - InstallerReturnCode: 3 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com -ManifestType: singleton -ManifestVersion: 1.2.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml index a9630d5bf2..b27e955014 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_28-PowerShellDSC.yaml @@ -126,27 +126,27 @@ InstallationMetadata: InvocationParameter: "/arg" DisplayName: "DisplayName" DownloadCommandProhibited: true -ArchiveBinariesDependOnPath: true -DesiredStateConfiguration: - PowerShell: - - RepositoryUrl: https://www.powershellgallery.com/api/v2 - ModuleName: Microsoft.WinGet.DSC - Resources: - - Name: WinGetUserSettings - - Name: WinGetAdminSettings - - Name: WinGetSource - - Name: WinGetPackageManager - - Name: WinGetPackage - - RepositoryUrl: https://mcr.microsoft.com/ - ModuleName: Microsoft.WinGet.DSC - Resources: - - Name: WinGetUserSettings - - Name: WinGetAdminSettings - - Name: WinGetSource - - Name: WinGetPackageManager - - Name: WinGetPackage - DSCv3: - Resources: +ArchiveBinariesDependOnPath: true +DesiredStateConfiguration: + PowerShell: + - RepositoryUrl: https://www.powershellgallery.com/api/v2 + ModuleName: Microsoft.WinGet.DSC + Resources: + - Name: WinGetUserSettings + - Name: WinGetAdminSettings + - Name: WinGetSource + - Name: WinGetPackageManager + - Name: WinGetPackage + - RepositoryUrl: https://mcr.microsoft.com/ + ModuleName: Microsoft.WinGet.DSC + Resources: + - Name: WinGetUserSettings + - Name: WinGetAdminSettings + - Name: WinGetSource + - Name: WinGetPackageManager + - Name: WinGetPackage + DSCv3: + Resources: - Type: Microsoft.WinGet/AdminSettings - Type: Microsoft.WinGet/Package - Type: Microsoft.WinGet/Source diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml index 1de1249422..1cb053d005 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_28-Singleton.yaml @@ -126,10 +126,10 @@ InstallationMetadata: InvocationParameter: "/arg" DisplayName: "DisplayName" DownloadCommandProhibited: true -ArchiveBinariesDependOnPath: true -DesiredStateConfiguration: - DSCv3: - Resources: +ArchiveBinariesDependOnPath: true +DesiredStateConfiguration: + DSCv3: + Resources: - Type: Microsoft.WinGet/AdminSettings - Type: Microsoft.WinGet/Package - Type: Microsoft.WinGet/Source diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_4-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_4-Singleton.yaml index 36c61543fa..080f9c9491 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_4-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_4-Singleton.yaml @@ -1,189 +1,189 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-US -Publisher: Microsoft -PublisherUrl: https://www.microsoft.com -PublisherSupportUrl: https://www.microsoft.com/support -PrivacyUrl: https://www.microsoft.com/privacy -Author: Microsoft -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -ShortDescription: This is MSIX SDK -Description: The MSIX SDK project is an effort to enable developers -Moniker: msixsdk -Tags: - - "appxsdk" - - "msixsdk" -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: Default installation notes -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Agreements: - - AgreementLabel: DefaultLabel - Agreement: DefaultText - AgreementUrl: https://DefaultAgreementUrl.net -InstallerLocale: en-US -Platform: - - Windows.Desktop - - Windows.Universal -MinimumOSVersion: 10.0.0.0 -InstallerType: exe -Scope: machine -InstallModes: - - interactive - - silent - - silentWithProgress -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Interactive: /interactive - Log: /log= - InstallLocation: /dir= - Upgrade: /upgrade -InstallerSuccessCodes: - - 1 - - 0x80070005 -UpgradeBehavior: uninstallPrevious -Commands: - - makemsix - - makeappx -Protocols: - - protocol1 - - protocol2 -FileExtensions: - - appx - - msix - - appxbundle - - msixbundle -Dependencies: - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDep - MinimumVersion: 1.0.0 - ExternalDependencies: - - Outside dependencies -Capabilities: - - internetClient -RestrictedCapabilities: - - runFullTrust -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ProductCode: "{Foo}" -ReleaseDate: 2021-01-01 -InstallerAbortsTerminal: true -InstallLocationRequired: true -RequireExplicitUpgrade: true -DisplayInstallWarnings: true -ElevationRequirement: elevatesSelf -UnsupportedOSArchitectures: - - arm -AppsAndFeaturesEntries: - - DisplayName: DisplayName - DisplayVersion: DisplayVersion - Publisher: Publisher - ProductCode: ProductCode - UpgradeCode: UpgradeCode - InstallerType: exe -Markets: - AllowedMarkets: - - US -ExpectedReturnCodes: - - InstallerReturnCode: 10 - ReturnResponse: packageInUse - ReturnResponseUrl: https://DefaultReturnResponseUrl.com -UnsupportedArguments: - - log -NestedInstallerType: msi -NestedInstallerFiles: - - RelativeFilePath: RelativeFilePath - PortableCommandAlias: PortableCommandAlias -InstallationMetadata: - DefaultInstallLocation: "%ProgramFiles%\\TestApp" - Files: - - RelativeFilePath: "main.exe" - FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - FileType: launch - InvocationParameter: "/arg" - DisplayName: "DisplayName" - -Installers: - - Architecture: x86 - InstallerLocale: en-GB - Platform: - - Windows.Desktop - MinimumOSVersion: 10.0.1.0 - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Scope: user - InstallModes: - - interactive - InstallerSwitches: - Custom: /c - SilentWithProgress: /sp - Silent: /s - Interactive: /i - Log: /l= - InstallLocation: /d= - Upgrade: /u - UpgradeBehavior: install - Commands: - - makemsixPreview - - makeappxPreview - Protocols: - - protocol1preview - - protocol2preview - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - MinimumVersion: 1.0.0 - ExternalDependencies: - - Preview Outside dependencies - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Capabilities: - - internetClientPreview - RestrictedCapabilities: - - runFullTrustPreview - ReleaseDate: 2021-02-02 - InstallerAbortsTerminal: false - InstallLocationRequired: false - RequireExplicitUpgrade: false - DisplayInstallWarnings: false - ElevationRequirement: elevationRequired - UnsupportedArguments: - - location - UnsupportedOSArchitectures: - - arm64 - Markets: - ExcludedMarkets: - - "US" - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - - InstallerReturnCode: 3 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com -ManifestType: singleton -ManifestVersion: 1.4.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml index c52ff2d99b..e27e625026 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_5-Singleton.yaml @@ -1,195 +1,195 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.5.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-US -Publisher: Microsoft -PublisherUrl: https://www.microsoft.com -PublisherSupportUrl: https://www.microsoft.com/support -PrivacyUrl: https://www.microsoft.com/privacy -Author: Microsoft -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -ShortDescription: This is MSIX SDK -Description: The MSIX SDK project is an effort to enable developers -Moniker: msixsdk -Tags: - - "appxsdk" - - "msixsdk" -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: Default installation notes -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Icons: - - IconUrl: https://testIcon-en-US - IconFileType: ico - IconResolution: custom - IconTheme: default - IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 -Agreements: - - AgreementLabel: DefaultLabel - Agreement: DefaultText - AgreementUrl: https://DefaultAgreementUrl.net -InstallerLocale: en-US -Platform: - - Windows.Desktop - - Windows.Universal -MinimumOSVersion: 10.0.0.0 -InstallerType: exe -Scope: machine -InstallModes: - - interactive - - silent - - silentWithProgress -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Interactive: /interactive - Log: /log= - InstallLocation: /dir= - Upgrade: /upgrade -InstallerSuccessCodes: - - 1 - - 0x80070005 -UpgradeBehavior: uninstallPrevious -Commands: - - makemsix - - makeappx -Protocols: - - protocol1 - - protocol2 -FileExtensions: - - appx - - msix - - appxbundle - - msixbundle -Dependencies: - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDep - MinimumVersion: 1.0.0 - ExternalDependencies: - - Outside dependencies -Capabilities: - - internetClient -RestrictedCapabilities: - - runFullTrust -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ProductCode: "{Foo}" -ReleaseDate: 2021-01-01 -InstallerAbortsTerminal: true -InstallLocationRequired: true -RequireExplicitUpgrade: true -DisplayInstallWarnings: true -ElevationRequirement: elevatesSelf -UnsupportedOSArchitectures: - - arm -AppsAndFeaturesEntries: - - DisplayName: DisplayName - DisplayVersion: DisplayVersion - Publisher: Publisher - ProductCode: ProductCode - UpgradeCode: UpgradeCode - InstallerType: exe -Markets: - AllowedMarkets: - - US -ExpectedReturnCodes: - - InstallerReturnCode: 10 - ReturnResponse: packageInUse - ReturnResponseUrl: https://DefaultReturnResponseUrl.com -UnsupportedArguments: - - log -NestedInstallerType: msi -NestedInstallerFiles: - - RelativeFilePath: RelativeFilePath - PortableCommandAlias: PortableCommandAlias -InstallationMetadata: - DefaultInstallLocation: "%ProgramFiles%\\TestApp" - Files: - - RelativeFilePath: "main.exe" - FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - FileType: launch - InvocationParameter: "/arg" - DisplayName: "DisplayName" - -Installers: - - Architecture: x86 - InstallerLocale: en-GB - Platform: - - Windows.Desktop - MinimumOSVersion: 10.0.1.0 - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Scope: user - InstallModes: - - interactive - InstallerSwitches: - Custom: /c - SilentWithProgress: /sp - Silent: /s - Interactive: /i - Log: /l= - InstallLocation: /d= - Upgrade: /u - UpgradeBehavior: install - Commands: - - makemsixPreview - - makeappxPreview - Protocols: - - protocol1preview - - protocol2preview - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - MinimumVersion: 1.0.0 - ExternalDependencies: - - Preview Outside dependencies - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Capabilities: - - internetClientPreview - RestrictedCapabilities: - - runFullTrustPreview - ReleaseDate: 2021-02-02 - InstallerAbortsTerminal: false - InstallLocationRequired: false - RequireExplicitUpgrade: false - DisplayInstallWarnings: false - ElevationRequirement: elevationRequired - UnsupportedArguments: - - location - UnsupportedOSArchitectures: - - arm64 - Markets: - ExcludedMarkets: - - "US" - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - - InstallerReturnCode: 3 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com -ManifestType: singleton -ManifestVersion: 1.5.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.5.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com +ManifestType: singleton +ManifestVersion: 1.5.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml index ddf947ffe0..c1b86fdb9b 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_6-Singleton.yaml @@ -1,197 +1,197 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-US -Publisher: Microsoft -PublisherUrl: https://www.microsoft.com -PublisherSupportUrl: https://www.microsoft.com/support -PrivacyUrl: https://www.microsoft.com/privacy -Author: Microsoft -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -ShortDescription: This is MSIX SDK -Description: The MSIX SDK project is an effort to enable developers -Moniker: msixsdk -Tags: - - "appxsdk" - - "msixsdk" -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: Default installation notes -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Icons: - - IconUrl: https://testIcon-en-US - IconFileType: ico - IconResolution: custom - IconTheme: default - IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 -Agreements: - - AgreementLabel: DefaultLabel - Agreement: DefaultText - AgreementUrl: https://DefaultAgreementUrl.net -InstallerLocale: en-US -Platform: - - Windows.Desktop - - Windows.Universal -MinimumOSVersion: 10.0.0.0 -InstallerType: exe -Scope: machine -InstallModes: - - interactive - - silent - - silentWithProgress -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Interactive: /interactive - Log: /log= - InstallLocation: /dir= - Upgrade: /upgrade -InstallerSuccessCodes: - - 1 - - 0x80070005 -UpgradeBehavior: uninstallPrevious -Commands: - - makemsix - - makeappx -Protocols: - - protocol1 - - protocol2 -FileExtensions: - - appx - - msix - - appxbundle - - msixbundle -Dependencies: - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDep - MinimumVersion: 1.0.0 - ExternalDependencies: - - Outside dependencies -Capabilities: - - internetClient -RestrictedCapabilities: - - runFullTrust -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ProductCode: "{Foo}" -ReleaseDate: 2021-01-01 -InstallerAbortsTerminal: true -InstallLocationRequired: true -RequireExplicitUpgrade: true -DisplayInstallWarnings: true -ElevationRequirement: elevatesSelf -UnsupportedOSArchitectures: - - arm -AppsAndFeaturesEntries: - - DisplayName: DisplayName - DisplayVersion: DisplayVersion - Publisher: Publisher - ProductCode: ProductCode - UpgradeCode: UpgradeCode - InstallerType: exe -Markets: - AllowedMarkets: - - US -ExpectedReturnCodes: - - InstallerReturnCode: 10 - ReturnResponse: packageInUse - ReturnResponseUrl: https://DefaultReturnResponseUrl.com -UnsupportedArguments: - - log -NestedInstallerType: msi -NestedInstallerFiles: - - RelativeFilePath: RelativeFilePath - PortableCommandAlias: PortableCommandAlias -InstallationMetadata: - DefaultInstallLocation: "%ProgramFiles%\\TestApp" - Files: - - RelativeFilePath: "main.exe" - FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - FileType: launch - InvocationParameter: "/arg" - DisplayName: "DisplayName" -DownloadCommandProhibited: true - -Installers: - - Architecture: x86 - InstallerLocale: en-GB - Platform: - - Windows.Desktop - MinimumOSVersion: 10.0.1.0 - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Scope: user - InstallModes: - - interactive - InstallerSwitches: - Custom: /c - SilentWithProgress: /sp - Silent: /s - Interactive: /i - Log: /l= - InstallLocation: /d= - Upgrade: /u - UpgradeBehavior: install - Commands: - - makemsixPreview - - makeappxPreview - Protocols: - - protocol1preview - - protocol2preview - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - MinimumVersion: 1.0.0 - ExternalDependencies: - - Preview Outside dependencies - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Capabilities: - - internetClientPreview - RestrictedCapabilities: - - runFullTrustPreview - ReleaseDate: 2021-02-02 - InstallerAbortsTerminal: false - InstallLocationRequired: false - RequireExplicitUpgrade: false - DisplayInstallWarnings: false - ElevationRequirement: elevationRequired - UnsupportedArguments: - - location - UnsupportedOSArchitectures: - - arm64 - Markets: - ExcludedMarkets: - - "US" - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - - InstallerReturnCode: 3 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com - DownloadCommandProhibited: false -ManifestType: singleton -ManifestVersion: 1.6.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.6.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DownloadCommandProhibited: false +ManifestType: singleton +ManifestVersion: 1.6.0 diff --git a/src/AppInstallerCLITests/TestData/ManifestV1_7-Singleton.yaml b/src/AppInstallerCLITests/TestData/ManifestV1_7-Singleton.yaml index 377071c5fb..f51b7218c5 100644 --- a/src/AppInstallerCLITests/TestData/ManifestV1_7-Singleton.yaml +++ b/src/AppInstallerCLITests/TestData/ManifestV1_7-Singleton.yaml @@ -1,200 +1,200 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-US -Publisher: Microsoft -PublisherUrl: https://www.microsoft.com -PublisherSupportUrl: https://www.microsoft.com/support -PrivacyUrl: https://www.microsoft.com/privacy -Author: Microsoft -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -ShortDescription: This is MSIX SDK -Description: The MSIX SDK project is an effort to enable developers -Moniker: msixsdk -Tags: - - "appxsdk" - - "msixsdk" -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: Default installation notes -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Icons: - - IconUrl: https://testIcon-en-US - IconFileType: ico - IconResolution: custom - IconTheme: default - IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 -Agreements: - - AgreementLabel: DefaultLabel - Agreement: DefaultText - AgreementUrl: https://DefaultAgreementUrl.net -InstallerLocale: en-US -Platform: - - Windows.Desktop - - Windows.Universal -MinimumOSVersion: 10.0.0.0 -InstallerType: exe -Scope: machine -InstallModes: - - interactive - - silent - - silentWithProgress -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Interactive: /interactive - Log: /log= - InstallLocation: /dir= - Upgrade: /upgrade - Repair: /repair -InstallerSuccessCodes: - - 1 - - 0x80070005 -UpgradeBehavior: uninstallPrevious -RepairBehavior: modify -Commands: - - makemsix - - makeappx -Protocols: - - protocol1 - - protocol2 -FileExtensions: - - appx - - msix - - appxbundle - - msixbundle -Dependencies: - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDep - MinimumVersion: 1.0.0 - ExternalDependencies: - - Outside dependencies -Capabilities: - - internetClient -RestrictedCapabilities: - - runFullTrust -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ProductCode: "{Foo}" -ReleaseDate: 2021-01-01 -InstallerAbortsTerminal: true -InstallLocationRequired: true -RequireExplicitUpgrade: true -DisplayInstallWarnings: true -ElevationRequirement: elevatesSelf -UnsupportedOSArchitectures: - - arm -AppsAndFeaturesEntries: - - DisplayName: DisplayName - DisplayVersion: DisplayVersion - Publisher: Publisher - ProductCode: ProductCode - UpgradeCode: UpgradeCode - InstallerType: exe -Markets: - AllowedMarkets: - - US -ExpectedReturnCodes: - - InstallerReturnCode: 10 - ReturnResponse: packageInUse - ReturnResponseUrl: https://DefaultReturnResponseUrl.com -UnsupportedArguments: - - log -NestedInstallerType: msi -NestedInstallerFiles: - - RelativeFilePath: RelativeFilePath - PortableCommandAlias: PortableCommandAlias -InstallationMetadata: - DefaultInstallLocation: "%ProgramFiles%\\TestApp" - Files: - - RelativeFilePath: "main.exe" - FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - FileType: launch - InvocationParameter: "/arg" - DisplayName: "DisplayName" -DownloadCommandProhibited: true - -Installers: - - Architecture: x86 - InstallerLocale: en-GB - Platform: - - Windows.Desktop - MinimumOSVersion: 10.0.1.0 - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Scope: user - InstallModes: - - interactive - InstallerSwitches: - Custom: /c - SilentWithProgress: /sp - Silent: /s - Interactive: /i - Log: /l= - InstallLocation: /d= - Upgrade: /u - Repair: /r - UpgradeBehavior: install - Commands: - - makemsixPreview - - makeappxPreview - Protocols: - - protocol1preview - - protocol2preview - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - MinimumVersion: 1.0.0 - ExternalDependencies: - - Preview Outside dependencies - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Capabilities: - - internetClientPreview - RestrictedCapabilities: - - runFullTrustPreview - ReleaseDate: 2021-02-02 - InstallerAbortsTerminal: false - InstallLocationRequired: false - RequireExplicitUpgrade: false - DisplayInstallWarnings: false - ElevationRequirement: elevationRequired - UnsupportedArguments: - - location - UnsupportedOSArchitectures: - - arm64 - Markets: - ExcludedMarkets: - - "US" - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - - InstallerReturnCode: 3 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com - DownloadCommandProhibited: false -ManifestType: singleton -ManifestVersion: 1.7.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.7.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - US +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedArguments: + - location + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DownloadCommandProhibited: false +ManifestType: singleton +ManifestVersion: 1.7.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-DefaultLocale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-DefaultLocale.yaml index bfb1c35620..c3ba98aafa 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-DefaultLocale.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-DefaultLocale.yaml @@ -1,41 +1,41 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.10.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-US -Publisher: Microsoft -PublisherUrl: https://www.microsoft.com -PublisherSupportUrl: https://www.microsoft.com/support -PrivacyUrl: https://www.microsoft.com/privacy -Author: Microsoft -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: "Default installation notes" -ShortDescription: This is MSIX SDK -Description: The MSIX SDK project is an effort to enable developers -Moniker: msixsdk -Tags: - - "appxsdk" - - "msixsdk" -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Icons: - - IconUrl: https://testIcon-en-US - IconFileType: ico - IconResolution: custom - IconTheme: default - IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 -Agreements: - - AgreementLabel: DefaultLabel - Agreement: DefaultText - AgreementUrl: https://DefaultAgreementUrl.net -ManifestType: defaultLocale -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.10.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-US +Publisher: Microsoft +PublisherUrl: https://www.microsoft.com +PublisherSupportUrl: https://www.microsoft.com/support +PrivacyUrl: https://www.microsoft.com/privacy +Author: Microsoft +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: "Default installation notes" +ShortDescription: This is MSIX SDK +Description: The MSIX SDK project is an effort to enable developers +Moniker: msixsdk +Tags: + - "appxsdk" + - "msixsdk" +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://testIcon-en-US + IconFileType: ico + IconResolution: custom + IconTheme: default + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123 +Agreements: + - AgreementLabel: DefaultLabel + Agreement: DefaultText + AgreementUrl: https://DefaultAgreementUrl.net +ManifestType: defaultLocale +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Installer.yaml index 66d0afd941..f7b19d3094 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Installer.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Installer.yaml @@ -1,213 +1,213 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -InstallerLocale: en-US -Platform: - - Windows.Desktop - - Windows.Universal -MinimumOSVersion: 10.0.0.0 -InstallerType: exe -Scope: machine -InstallModes: - - interactive - - silent - - silentWithProgress -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Interactive: /interactive - Log: /log= - InstallLocation: /dir= - Upgrade: /upgrade - Repair: /repair -InstallerSuccessCodes: - - 1 - - 0x80070005 -UpgradeBehavior: uninstallPrevious -RepairBehavior: modify -Commands: - - makemsix - - makeappx -Protocols: - - protocol1 - - protocol2 -FileExtensions: - - appx - - msix - - appxbundle - - msixbundle -Dependencies: - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDep - MinimumVersion: 1.0.0 - ExternalDependencies: - - Outside dependencies -Capabilities: - - internetClient -RestrictedCapabilities: - - runFullTrust -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -ProductCode: "{Foo}" -ReleaseDate: 2021-01-01 -InstallerAbortsTerminal: true -InstallLocationRequired: true -RequireExplicitUpgrade: true -DisplayInstallWarnings: true -ElevationRequirement: elevatesSelf -UnsupportedOSArchitectures: - - arm -AppsAndFeaturesEntries: - - DisplayName: DisplayName - DisplayVersion: DisplayVersion - Publisher: Publisher - ProductCode: ProductCode - UpgradeCode: UpgradeCode - InstallerType: exe -Markets: - AllowedMarkets: - - "US" -ExpectedReturnCodes: - - InstallerReturnCode: 10 - ReturnResponse: packageInUse - ReturnResponseUrl: https://DefaultReturnResponseUrl.com -UnsupportedArguments: - - log -NestedInstallerType: msi -NestedInstallerFiles: - - RelativeFilePath: RelativeFilePath - PortableCommandAlias: PortableCommandAlias -InstallationMetadata: - DefaultInstallLocation: "%ProgramFiles%\\TestApp" - Files: - - RelativeFilePath: "main.exe" - FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - FileType: launch - InvocationParameter: "/arg" - DisplayName: "DisplayName" -DownloadCommandProhibited: true -ArchiveBinariesDependOnPath: true - -Installers: - - Architecture: x86 - InstallerLocale: en-GB - Platform: - - Windows.Desktop - MinimumOSVersion: 10.0.1.0 - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Scope: user - InstallModes: - - interactive - InstallerSwitches: - Custom: /c - SilentWithProgress: /sp - Silent: /s - Interactive: /i - Log: /l= - InstallLocation: /d= - Upgrade: /u - Repair: /r - UpgradeBehavior: install - Commands: - - makemsixPreview - - makeappxPreview - Protocols: - - protocol1preview - - protocol2preview - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - MinimumVersion: 1.0.0 - ExternalDependencies: - - Preview Outside dependencies - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Capabilities: - - internetClientPreview - RestrictedCapabilities: - - runFullTrustPreview - ReleaseDate: 2021-02-02 - InstallerAbortsTerminal: false - InstallLocationRequired: false - RequireExplicitUpgrade: false - DisplayInstallWarnings: false - ElevationRequirement: elevationRequired - UnsupportedOSArchitectures: - - arm64 - Markets: - ExcludedMarkets: - - "US" - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - - InstallerReturnCode: 3 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com - UnsupportedArguments: - - location - DownloadCommandProhibited: false - ArchiveBinariesDependOnPath: false - - Architecture: x64 - InstallerType: exe - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - ProductCode: "{Bar}" - InstallerSwitches: - Repair: /r - UpgradeBehavior: deny - RepairBehavior: uninstaller - - Architecture: x86 - InstallerType: portable - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.exe - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - DisplayInstallWarnings: false - Commands: - - standalone - ExpectedReturnCodes: - - InstallerReturnCode: 11 - ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com - - Architecture: x64 - InstallerType: zip - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - NestedInstallerType: portable - NestedInstallerFiles: - - RelativeFilePath: relativeFilePath1 - PortableCommandAlias: portableAlias1 - - RelativeFilePath: relativeFilePath2 - PortableCommandAlias: portableAlias2 - InstallationMetadata: - DefaultInstallLocation: "%ProgramFiles%\\TestApp2" - Files: - - RelativeFilePath: "main2.exe" - FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - FileType: other - InvocationParameter: "/arg2" - DisplayName: "DisplayName2" - ArchiveBinariesDependOnPath: true - - Architecture: x64 - InstallerType: burn - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - ProductCode: "{Bar}" - UpgradeBehavior: deny - RepairBehavior: modify -ManifestType: installer -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.10.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +InstallerLocale: en-US +Platform: + - Windows.Desktop + - Windows.Universal +MinimumOSVersion: 10.0.0.0 +InstallerType: exe +Scope: machine +InstallModes: + - interactive + - silent + - silentWithProgress +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Interactive: /interactive + Log: /log= + InstallLocation: /dir= + Upgrade: /upgrade + Repair: /repair +InstallerSuccessCodes: + - 1 + - 0x80070005 +UpgradeBehavior: uninstallPrevious +RepairBehavior: modify +Commands: + - makemsix + - makeappx +Protocols: + - protocol1 + - protocol2 +FileExtensions: + - appx + - msix + - appxbundle + - msixbundle +Dependencies: + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDep + MinimumVersion: 1.0.0 + ExternalDependencies: + - Outside dependencies +Capabilities: + - internetClient +RestrictedCapabilities: + - runFullTrust +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +ProductCode: "{Foo}" +ReleaseDate: 2021-01-01 +InstallerAbortsTerminal: true +InstallLocationRequired: true +RequireExplicitUpgrade: true +DisplayInstallWarnings: true +ElevationRequirement: elevatesSelf +UnsupportedOSArchitectures: + - arm +AppsAndFeaturesEntries: + - DisplayName: DisplayName + DisplayVersion: DisplayVersion + Publisher: Publisher + ProductCode: ProductCode + UpgradeCode: UpgradeCode + InstallerType: exe +Markets: + AllowedMarkets: + - "US" +ExpectedReturnCodes: + - InstallerReturnCode: 10 + ReturnResponse: packageInUse + ReturnResponseUrl: https://DefaultReturnResponseUrl.com +UnsupportedArguments: + - log +NestedInstallerType: msi +NestedInstallerFiles: + - RelativeFilePath: RelativeFilePath + PortableCommandAlias: PortableCommandAlias +InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp" + Files: + - RelativeFilePath: "main.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: launch + InvocationParameter: "/arg" + DisplayName: "DisplayName" +DownloadCommandProhibited: true +ArchiveBinariesDependOnPath: true + +Installers: + - Architecture: x86 + InstallerLocale: en-GB + Platform: + - Windows.Desktop + MinimumOSVersion: 10.0.1.0 + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Scope: user + InstallModes: + - interactive + InstallerSwitches: + Custom: /c + SilentWithProgress: /sp + Silent: /s + Interactive: /i + Log: /l= + InstallLocation: /d= + Upgrade: /u + Repair: /r + UpgradeBehavior: install + Commands: + - makemsixPreview + - makeappxPreview + Protocols: + - protocol1preview + - protocol2preview + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + MinimumVersion: 1.0.0 + ExternalDependencies: + - Preview Outside dependencies + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Capabilities: + - internetClientPreview + RestrictedCapabilities: + - runFullTrustPreview + ReleaseDate: 2021-02-02 + InstallerAbortsTerminal: false + InstallLocationRequired: false + RequireExplicitUpgrade: false + DisplayInstallWarnings: false + ElevationRequirement: elevationRequired + UnsupportedOSArchitectures: + - arm64 + Markets: + ExcludedMarkets: + - "US" + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + - InstallerReturnCode: 3 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + UnsupportedArguments: + - location + DownloadCommandProhibited: false + ArchiveBinariesDependOnPath: false + - Architecture: x64 + InstallerType: exe + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: "{Bar}" + InstallerSwitches: + Repair: /r + UpgradeBehavior: deny + RepairBehavior: uninstaller + - Architecture: x86 + InstallerType: portable + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + DisplayInstallWarnings: false + Commands: + - standalone + ExpectedReturnCodes: + - InstallerReturnCode: 11 + ReturnResponse: custom + ReturnResponseUrl: https://defaultReturnResponseUrl.com + - Architecture: x64 + InstallerType: zip + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + NestedInstallerType: portable + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath1 + PortableCommandAlias: portableAlias1 + - RelativeFilePath: relativeFilePath2 + PortableCommandAlias: portableAlias2 + InstallationMetadata: + DefaultInstallLocation: "%ProgramFiles%\\TestApp2" + Files: + - RelativeFilePath: "main2.exe" + FileSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + FileType: other + InvocationParameter: "/arg2" + DisplayName: "DisplayName2" + ArchiveBinariesDependOnPath: true + - Architecture: x64 + InstallerType: burn + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + ProductCode: "{Bar}" + UpgradeBehavior: deny + RepairBehavior: modify +ManifestType: installer +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Locale.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Locale.yaml index 58d60f3978..2814a2783a 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Locale.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Locale.yaml @@ -1,40 +1,40 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.locale.1.10.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -PackageLocale: en-GB -Publisher: Microsoft UK -PublisherUrl: https://www.microsoft.com/UK -PublisherSupportUrl: https://www.microsoft.com/support/UK -PrivacyUrl: https://www.microsoft.com/privacy/UK -Author: Microsoft UK -PackageName: MSIX SDK UK -PackageUrl: https://www.microsoft.com/msixsdk/home/UK -License: MIT License UK -LicenseUrl: https://www.microsoft.com/msixsdk/license/UK -Copyright: Copyright Microsoft Corporation UK -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK -ShortDescription: This is MSIX SDK UK -Description: The MSIX SDK project is an effort to enable developers UK -Tags: - - "appxsdkUK" - - "msixsdkUK" -ReleaseNotes: Release notes -ReleaseNotesUrl: https://ReleaseNotes.net -PurchaseUrl: https://DefaultPurchaseUrl.com -InstallationNotes: Default installation notes -Agreements: - - AgreementLabel: Label - Agreement: Text - AgreementUrl: https://AgreementUrl.net -Documentations: - - DocumentLabel: Default document label - DocumentUrl: https://DefaultDocumentUrl.com -Icons: - - IconUrl: https://localeTestIcon-en-GB - IconFileType: png - IconResolution: 32x32 - IconTheme: light - IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 -ManifestType: locale -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.locale.1.10.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +PackageLocale: en-GB +Publisher: Microsoft UK +PublisherUrl: https://www.microsoft.com/UK +PublisherSupportUrl: https://www.microsoft.com/support/UK +PrivacyUrl: https://www.microsoft.com/privacy/UK +Author: Microsoft UK +PackageName: MSIX SDK UK +PackageUrl: https://www.microsoft.com/msixsdk/home/UK +License: MIT License UK +LicenseUrl: https://www.microsoft.com/msixsdk/license/UK +Copyright: Copyright Microsoft Corporation UK +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK +ShortDescription: This is MSIX SDK UK +Description: The MSIX SDK project is an effort to enable developers UK +Tags: + - "appxsdkUK" + - "msixsdkUK" +ReleaseNotes: Release notes +ReleaseNotesUrl: https://ReleaseNotes.net +PurchaseUrl: https://DefaultPurchaseUrl.com +InstallationNotes: Default installation notes +Agreements: + - AgreementLabel: Label + Agreement: Text + AgreementUrl: https://AgreementUrl.net +Documentations: + - DocumentLabel: Default document label + DocumentUrl: https://DefaultDocumentUrl.com +Icons: + - IconUrl: https://localeTestIcon-en-GB + IconFileType: png + IconResolution: 32x32 + IconTheme: light + IconSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321 +ManifestType: locale +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Version.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Version.yaml index ec903cdf29..d33d550727 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Version.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_10/ManifestV1_10-MultiFile-Version.yaml @@ -1,7 +1,7 @@ -# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.10.0.schema.json - -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -DefaultLocale: en-US -ManifestType: version -ManifestVersion: 1.10.0 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.10.0.schema.json + +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.10.0 diff --git a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml index 353d04cc90..06d6fa07db 100644 --- a/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml +++ b/src/AppInstallerCLITests/TestData/MultiFileManifestV1_28/ManifestV1_28-MultiFile-Installer.yaml @@ -98,7 +98,7 @@ DesiredStateConfiguration: - Type: Microsoft.WinGet/AdminSettings - Type: Microsoft.WinGet/Package - Type: Microsoft.WinGet/Source - - Type: Microsoft.WinGet/UserSettingsFile + - Type: Microsoft.WinGet/UserSettingsFile Installers: - Architecture: x86 @@ -193,8 +193,8 @@ Installers: ExpectedReturnCodes: - InstallerReturnCode: 11 ReturnResponse: custom - ReturnResponseUrl: https://defaultReturnResponseUrl.com - DesiredStateConfiguration: + ReturnResponseUrl: https://defaultReturnResponseUrl.com + DesiredStateConfiguration: DSCv3: - Architecture: x64 InstallerType: zip diff --git a/src/AppInstallerCLITests/TestData/Node-Merge.yaml b/src/AppInstallerCLITests/TestData/Node-Merge.yaml index 64fbffb0d7..1313aeae9d 100644 --- a/src/AppInstallerCLITests/TestData/Node-Merge.yaml +++ b/src/AppInstallerCLITests/TestData/Node-Merge.yaml @@ -1,8 +1,8 @@ -StrawHats: - - Name: Monkey D Luffy - Bounty: 3,000,000,000 - - Name: Roronoa Zoro - Bounty: 1,111,000,000 - - Name: Nami - Bounty: 366,000,000 - +StrawHats: + - Name: Monkey D Luffy + Bounty: 3,000,000,000 + - Name: Roronoa Zoro + Bounty: 1,111,000,000 + - Name: Nami + Bounty: 366,000,000 + diff --git a/src/AppInstallerCLITests/TestData/Node-Merge2.yaml b/src/AppInstallerCLITests/TestData/Node-Merge2.yaml index 0fc4f6d165..ea5c1f1f67 100644 --- a/src/AppInstallerCLITests/TestData/Node-Merge2.yaml +++ b/src/AppInstallerCLITests/TestData/Node-Merge2.yaml @@ -1,7 +1,7 @@ -StrawHats: - - name: Monkey d Luffy - Bounty: 150,000,000 - Fruit: Gomu Gomu no Mi - - Name: Nico Robin - Bounty: 930,000,000 - Fruit: Hana Hana no Mi +StrawHats: + - name: Monkey d Luffy + Bounty: 150,000,000 + Fruit: Gomu Gomu no Mi + - Name: Nico Robin + Bounty: 930,000,000 + Fruit: Hana Hana no Mi diff --git a/src/AppInstallerCLITests/TestData/Node-Types.yaml b/src/AppInstallerCLITests/TestData/Node-Types.yaml index e62ea39c35..fad13dbb62 100644 --- a/src/AppInstallerCLITests/TestData/Node-Types.yaml +++ b/src/AppInstallerCLITests/TestData/Node-Types.yaml @@ -1,11 +1,11 @@ -IntegerUnquoted: 12345 -IntegerSingleQuoted: '12345' -IntegerDoubleQuoted: "12345" - -BooleanTrue: true -StringTrue: 'true' - -BooleanFalse: false -StringFalse: 'false' - +IntegerUnquoted: 12345 +IntegerSingleQuoted: '12345' +IntegerDoubleQuoted: "12345" + +BooleanTrue: true +StringTrue: 'true' + +BooleanFalse: false +StringFalse: 'false' + LocalTag: !myTag value \ No newline at end of file diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe.yaml index ebaf93ecf0..04a85bf182 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe.yaml @@ -1,18 +1,18 @@ -# Same content with InstallFlowTest_Exe.yaml but with higher version -Id: AppInstallerCliTest.TestExeInstaller -Version: 2.0.0.0 -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -Switches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: exe - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestVersion: 0.1.0 +# Same content with InstallFlowTest_Exe.yaml but with higher version +Id: AppInstallerCliTest.TestExeInstaller +Version: 2.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +Switches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml index 67166896df..11a1f3a6a1 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExeDependencies.yaml @@ -1,28 +1,28 @@ -# Same content with Installer_Exe_Dependencies but with higher version -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies -PackageVersion: 2.0.0.0 -PackageName: AppInstaller Test Installer -PackageLocale: en-US -Publisher: Microsoft Corporation -ShortDescription: Upgrade exe installer with dependencies -Moniker: AICLITestExe -License: Test -InstallerSwitches: - Custom: /custom - SilentWithProgress: /silentwithprogress - Silent: /silence - Upgrade: /upgrade -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - Dependencies: - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime -ManifestType: singleton -ManifestVersion: 1.0.0 +# Same content with Installer_Exe_Dependencies but with higher version +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.0.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller.Dependencies +PackageVersion: 2.0.0.0 +PackageName: AppInstaller Test Installer +PackageLocale: en-US +Publisher: Microsoft Corporation +ShortDescription: Upgrade exe installer with dependencies +Moniker: AICLITestExe +License: Test +InstallerSwitches: + Custom: /custom + SilentWithProgress: /silentwithprogress + Silent: /silence + Upgrade: /upgrade +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + Dependencies: + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2.yaml index 8674ba6f04..a55b5f8c6b 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2.yaml @@ -1,18 +1,18 @@ -# Same content with UpdateFlowTest_Exe.yaml but with higher version -Id: AppInstallerCliTest.TestExeInstaller -Version: 3.0.0.0 -Name: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -Switches: - Custom: /custom /ver3.0.0.0 - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -Installers: - - Arch: x64 - Url: https://ThisIsNotUsed - InstallerType: exe - Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestVersion: 0.1.0 +# Same content with UpdateFlowTest_Exe.yaml but with higher version +Id: AppInstallerCliTest.TestExeInstaller +Version: 3.0.0.0 +Name: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +Switches: + Custom: /custom /ver3.0.0.0 + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +Installers: + - Arch: x64 + Url: https://ThisIsNotUsed + InstallerType: exe + Sha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2_LicenseAgreement.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2_LicenseAgreement.yaml index bb81fec3f2..7bd99ad765 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2_LicenseAgreement.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_2_LicenseAgreement.yaml @@ -1,26 +1,26 @@ -# Similar content to UpdateFlowTest_Exe_2.yaml but with Agreements -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller -PackageVersion: 3.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: AppInstaller Test Installer -Agreements: - - AgreementLabel: Agreement for EXE - Agreement: This is the agreement for the EXE installer. -InstallerSwitches: - Custom: /custom /ver3.0.0.0 - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.1.0 +# Similar content to UpdateFlowTest_Exe_2.yaml but with Agreements +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller +PackageVersion: 3.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: AppInstaller Test Installer +Agreements: + - AgreementLabel: Agreement for EXE + Agreement: This is the agreement for the EXE installer. +InstallerSwitches: + Custom: /custom /ver3.0.0.0 + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_ARPInstallerType.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_ARPInstallerType.yaml index d1a1d956e5..aad3b7e202 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_ARPInstallerType.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_ARPInstallerType.yaml @@ -1,25 +1,25 @@ -# Similar content to UpdateFlowTest_Exe.yaml, but with an AppsAndFeaturesEntry specifying installer type -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -ShortDescription: AppInstaller Test Installer -AppsAndFeaturesEntries: - - InstallerType: msix -InstallerSwitches: - Custom: /custom /ver2.0.0.0 - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.1.0 +# Similar content to UpdateFlowTest_Exe.yaml, but with an AppsAndFeaturesEntry specifying installer type +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +ShortDescription: AppInstaller Test Installer +AppsAndFeaturesEntries: + - InstallerType: msix +InstallerSwitches: + Custom: /custom /ver2.0.0.0 + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_UnsupportedArgs.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_UnsupportedArgs.yaml index f7cd8ebe2c..30ac75d8cb 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_UnsupportedArgs.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Exe_UnsupportedArgs.yaml @@ -1,26 +1,26 @@ -# Same content with UpdateFlowTest_Exe.yaml but with higher version and UnsupportedArguments -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestExeInstaller -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestExe -License: Test -Switches: - Custom: /custom /ver3.0.0.0 - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -UnsupportedArguments: - - log - - location -ShortDescription: AppInstaller Test Exe Installer -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.2.0 +# Same content with UpdateFlowTest_Exe.yaml but with higher version and UnsupportedArguments +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestExeInstaller +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestExe +License: Test +Switches: + Custom: /custom /ver3.0.0.0 + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +UnsupportedArguments: + - log + - location +ShortDescription: AppInstaller Test Exe Installer +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExpectedReturnCodes.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExpectedReturnCodes.yaml index 9aaae1a774..a493952dbf 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExpectedReturnCodes.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_ExpectedReturnCodes.yaml @@ -1,50 +1,50 @@ -# Same content with InstallFlowTest_ExpectedReturnCodes.yaml but with higher version -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.ExpectedReturnCodes -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: TestExeInstallerWithExpectedReturnCodes -ShortDescription: AppInstaller Test Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: exe - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - ExpectedReturnCodes: - - InstallerReturnCode: 1 - ReturnResponse: packageInUse - - InstallerReturnCode: 2 - ReturnResponse: installInProgress - - InstallerReturnCode: 3 - ReturnResponse: fileInUse - - InstallerReturnCode: 4 - ReturnResponse: missingDependency - - InstallerReturnCode: 5 - ReturnResponse: diskFull - - InstallerReturnCode: 6 - ReturnResponse: insufficientMemory - - InstallerReturnCode: 7 - ReturnResponse: noNetwork - - InstallerReturnCode: 8 - ReturnResponse: contactSupport - ReturnResponseUrl: https://TestReturnResponseUrl - - InstallerReturnCode: 9 - ReturnResponse: rebootRequiredToFinish - - InstallerReturnCode: 10 - ReturnResponse: rebootRequiredForInstall - - InstallerReturnCode: 11 - ReturnResponse: rebootInitiated - - InstallerReturnCode: 12 - ReturnResponse: cancelledByUser - - InstallerReturnCode: 13 - ReturnResponse: alreadyInstalled - - InstallerReturnCode: 14 - ReturnResponse: downgrade - - InstallerReturnCode: 15 - ReturnResponse: blockedByPolicy -ManifestType: singleton -ManifestVersion: 1.2.0 +# Same content with InstallFlowTest_ExpectedReturnCodes.yaml but with higher version +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.ExpectedReturnCodes +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: TestExeInstallerWithExpectedReturnCodes +ShortDescription: AppInstaller Test Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: exe + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + ExpectedReturnCodes: + - InstallerReturnCode: 1 + ReturnResponse: packageInUse + - InstallerReturnCode: 2 + ReturnResponse: installInProgress + - InstallerReturnCode: 3 + ReturnResponse: fileInUse + - InstallerReturnCode: 4 + ReturnResponse: missingDependency + - InstallerReturnCode: 5 + ReturnResponse: diskFull + - InstallerReturnCode: 6 + ReturnResponse: insufficientMemory + - InstallerReturnCode: 7 + ReturnResponse: noNetwork + - InstallerReturnCode: 8 + ReturnResponse: contactSupport + ReturnResponseUrl: https://TestReturnResponseUrl + - InstallerReturnCode: 9 + ReturnResponse: rebootRequiredToFinish + - InstallerReturnCode: 10 + ReturnResponse: rebootRequiredForInstall + - InstallerReturnCode: 11 + ReturnResponse: rebootInitiated + - InstallerReturnCode: 12 + ReturnResponse: cancelledByUser + - InstallerReturnCode: 13 + ReturnResponse: alreadyInstalled + - InstallerReturnCode: 14 + ReturnResponse: downgrade + - InstallerReturnCode: 15 + ReturnResponse: blockedByPolicy +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix.yaml index 8fa39c20b3..f8ba162aa0 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix.yaml @@ -1,15 +1,15 @@ -# Same content with InstallFlowTest_Msix_StreamingFlow.yaml but with higher version -Id: AppInstallerCliTest.TestMsixInstaller -Version: 2.0.0.0 -Name: AppInstaller Test MSIX Installer -Publisher: Microsoft Corporation -AppMoniker: AICLITestMsix -License: Test -Installers: - - Arch: x64 - Url: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true - InstallerType: msix - Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea - SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07 - PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe -ManifestVersion: 0.1.0 +# Same content with InstallFlowTest_Msix_StreamingFlow.yaml but with higher version +Id: AppInstallerCliTest.TestMsixInstaller +Version: 2.0.0.0 +Name: AppInstaller Test MSIX Installer +Publisher: Microsoft Corporation +AppMoniker: AICLITestMsix +License: Test +Installers: + - Arch: x64 + Url: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true + InstallerType: msix + Sha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea + SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07 + PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe +ManifestVersion: 0.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix_LicenseAgreement.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix_LicenseAgreement.yaml index 6cab26795f..2d172cfbd7 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix_LicenseAgreement.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Msix_LicenseAgreement.yaml @@ -1,23 +1,23 @@ -# Similar content to UpdateFlowTest_Msix.yaml but with Agreements -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestMsixInstaller -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test MSIX Installer -Publisher: Microsoft Corporation -Moniker: AICLITestExe -License: Test -Agreements: - - AgreementLabel: Agreement for MSIX - Agreement: This is the agreement for the MSIX installer. -ShortDescription: AppInstaller Test MSIX Installer -Installers: - - Architecture: x64 - InstallerUrl: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true - InstallerType: msix - InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea - SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07 - PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe -ManifestType: singleton -ManifestVersion: 1.1.0 +# Similar content to UpdateFlowTest_Msix.yaml but with Agreements +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.1.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestMsixInstaller +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test MSIX Installer +Publisher: Microsoft Corporation +Moniker: AICLITestExe +License: Test +Agreements: + - AgreementLabel: Agreement for MSIX + Agreement: This is the agreement for the MSIX installer. +ShortDescription: AppInstaller Test MSIX Installer +Installers: + - Architecture: x64 + InstallerUrl: https://github.com/microsoft/msix-packaging/blob/master/src/test/testData/unpack/TestAppxPackage_x64.appx?raw=true + InstallerType: msix + InstallerSha256: 6a2d3683fa19bf00e58e07d1313d20a5f5735ebbd6a999d33381d28740ee07ea + SignatureSha256: 138781c3e6f635240353f3d14d1d57bdcb89413e49be63b375e6a5d7b93b0d07 + PackageFamilyName: 20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe +ManifestType: singleton +ManifestVersion: 1.1.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Portable.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Portable.yaml index b207d39598..fdea94c4c8 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Portable.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Portable.yaml @@ -1,19 +1,19 @@ -# Same content with InstallFlowTest_Portable.yaml but with higher version -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestPortableInstaller -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Portable Exe -Publisher: Microsoft Corporation -AppMoniker: AICLITestPortable -ProductCode: AppInstallerCliTest.TestPortableInstaller__TestSource -License: Test -ShortDescription: AppInstaller Test Portable Exe -Installers: - - Architecture: x64 - InstallerUrl: https://ThisIsNotUsed - InstallerType: portable - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B -ManifestType: singleton -ManifestVersion: 1.2.0 +# Same content with InstallFlowTest_Portable.yaml but with higher version +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.2.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestPortableInstaller +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Portable Exe +Publisher: Microsoft Corporation +AppMoniker: AICLITestPortable +ProductCode: AppInstallerCliTest.TestPortableInstaller__TestSource +License: Test +ShortDescription: AppInstaller Test Portable Exe +Installers: + - Architecture: x64 + InstallerUrl: https://ThisIsNotUsed + InstallerType: portable + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B +ManifestType: singleton +ManifestVersion: 1.2.0 diff --git a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Zip_Exe.yaml b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Zip_Exe.yaml index 6a1ee07e4e..9e621c8fe7 100644 --- a/src/AppInstallerCLITests/TestData/UpdateFlowTest_Zip_Exe.yaml +++ b/src/AppInstallerCLITests/TestData/UpdateFlowTest_Zip_Exe.yaml @@ -1,26 +1,26 @@ -# Same content with InstallFlowTest_ZipWithExe.yaml but with higher version -# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json - -PackageIdentifier: AppInstallerCliTest.TestZipInstaller -PackageVersion: 2.0.0.0 -PackageLocale: en-US -PackageName: AppInstaller Test Zip Installer -ShortDescription: AppInstaller Test Zip Installer with exe -Publisher: Microsoft Corporation -Moniker: AICLITestZip -License: Test -Installers: - - Architecture: x86 - InstallerUrl: https://ThisIsNotUsed - InstallerType: zip - InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B - NestedInstallerType: exe - NestedInstallerFiles: - - RelativeFilePath: relativeFilePath - InstallerSwitches: - Custom: /custom /ver2.0.0.0 - SilentWithProgress: /silentwithprogress - Silent: /silence - Update: /update -ManifestType: singleton -ManifestVersion: 1.4.0 +# Same content with InstallFlowTest_ZipWithExe.yaml but with higher version +# yaml-language-server: $schema=https://aka.ms/winget-manifest.singleton.1.4.0.schema.json + +PackageIdentifier: AppInstallerCliTest.TestZipInstaller +PackageVersion: 2.0.0.0 +PackageLocale: en-US +PackageName: AppInstaller Test Zip Installer +ShortDescription: AppInstaller Test Zip Installer with exe +Publisher: Microsoft Corporation +Moniker: AICLITestZip +License: Test +Installers: + - Architecture: x86 + InstallerUrl: https://ThisIsNotUsed + InstallerType: zip + InstallerSha256: 65DB2F2AC2686C7F2FD69D4A4C6683B888DC55BFA20A0E32CA9F838B51689A3B + NestedInstallerType: exe + NestedInstallerFiles: + - RelativeFilePath: relativeFilePath + InstallerSwitches: + Custom: /custom /ver2.0.0.0 + SilentWithProgress: /silentwithprogress + Silent: /silence + Update: /update +ManifestType: singleton +ManifestVersion: 1.4.0 diff --git a/src/AppInstallerCLITests/TestHooks.h b/src/AppInstallerCLITests/TestHooks.h index 37ac5b1034..8c976a1381 100644 --- a/src/AppInstallerCLITests/TestHooks.h +++ b/src/AppInstallerCLITests/TestHooks.h @@ -1,434 +1,434 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "TestSettings.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef AICLI_DISABLE_TEST_HOOKS -static_assert(false, "Test hooks have been disabled"); -#endif - -namespace AppInstaller -{ - // Don't forget to clear the overrides after use! - // A good way is to create a helper struct that cleans when destroyed - - namespace Runtime - { - void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path); - void TestHook_SetPathOverride(PathName target, const Filesystem::PathDetails& details); - void TestHook_ClearPathOverrides(); - } - - namespace Repository - { - void TestHook_SetSourceFactoryOverride(const std::string& type, std::function()>&& factory); - void TestHook_ClearSourceFactoryOverrides(); - void TestHook_SetExtractIconFromArpEntryResult_Override(std::vector* result); - } - - namespace Repository::Microsoft - { - void TestHook_SetPinningIndex_Override(std::optional&& indexPath); - - using GetARPKeyFunc = std::function; - void SetGetARPKeyOverride(GetARPKeyFunc value); - - using GetFontRegistryRootFunc = std::function; - void TestHook_SetGetFontRegistryRootFunc(GetFontRegistryRootFunc value); - } - - namespace Logging - { - void TestHook_SetTelemetryOverride(std::shared_ptr ttl); - } - - namespace Settings - { - void SetUserSettingsOverride(UserSettings* value); - void SetExperimentalFeatureOverride(const std::map* override); - } - - namespace Filesystem - { - void TestHook_SetCreateSymlinkResult_Override(bool* status); - } - - namespace Archive - { - void TestHook_SetScanArchiveResult_Override(bool* status); - } - - namespace CLI::Execution - { - void TestHook_SetConsoleWidth_Override(std::optional* value); - } - - namespace CLI::Workflow - { - void TestHook_SetEnableWindowsFeatureResult_Override(std::optional&& result); - void TestHook_SetDoesWindowsFeatureExistResult_Override(std::optional&& result); - void TestHook_SetExtractArchiveWithTarResult_Override(std::optional&& result); - } - - namespace Reboot - { - void TestHook_SetInitiateRebootResult_Override(bool* status); - void TestHook_SetRegisterForRestartResult_Override(bool* status); - } - - namespace Authentication - { - void TestHook_SetAuthenticationResult_Override(Authentication::AuthenticationResult* authResult); - } - - namespace MSStore::TestHooks - { - void SetDisplayCatalogHttpPipelineStage_Override(std::shared_ptr value); - - void SetSfsClientAppContents_Override(std::function(std::string_view)>* value); - - void SetLicensingHttpPipelineStage_Override(std::shared_ptr value); - - void TestHook_SetProvisionAfterInstall(bool* value); - } - - namespace Utility::TestHooks - { - void SetDownloadResult_Function_Override(std::function info)>* value); - } -} - -namespace TestHook -{ - struct SetCreateSymlinkResult_Override - { - SetCreateSymlinkResult_Override(bool status) : m_status(status) - { - AppInstaller::Filesystem::TestHook_SetCreateSymlinkResult_Override(&m_status); - } - - ~SetCreateSymlinkResult_Override() - { - AppInstaller::Filesystem::TestHook_SetCreateSymlinkResult_Override(nullptr); - } - - private: - bool m_status; - }; - - struct SetScanArchiveResult_Override - { - SetScanArchiveResult_Override(bool status) : m_status(status) - { - AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(&m_status); - } - - ~SetScanArchiveResult_Override() - { - AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(nullptr); - } - - private: - bool m_status; - }; - - struct SetPinningIndex_Override - { - SetPinningIndex_Override(const std::filesystem::path& indexPath) - { - AppInstaller::Repository::Microsoft::TestHook_SetPinningIndex_Override(indexPath); - } - - ~SetPinningIndex_Override() - { - AppInstaller::Repository::Microsoft::TestHook_SetPinningIndex_Override({}); - } - }; - - struct SetExtractIconFromArpEntryResult_Override - { - SetExtractIconFromArpEntryResult_Override(std::vector extractedIcons) : m_extractedIcons(std::move(extractedIcons)) - { - AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(&m_extractedIcons); - } - - ~SetExtractIconFromArpEntryResult_Override() - { - AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(nullptr); - } - - private: - std::vector m_extractedIcons; - }; - - struct SetEnableWindowsFeatureResult_Override - { - SetEnableWindowsFeatureResult_Override(DWORD result) - { - AppInstaller::CLI::Workflow::TestHook_SetEnableWindowsFeatureResult_Override(result); - } - - ~SetEnableWindowsFeatureResult_Override() - { - AppInstaller::CLI::Workflow::TestHook_SetEnableWindowsFeatureResult_Override({}); - } - }; - - struct SetDoesWindowsFeatureExistResult_Override - { - SetDoesWindowsFeatureExistResult_Override(DWORD result) - { - AppInstaller::CLI::Workflow::TestHook_SetDoesWindowsFeatureExistResult_Override(result); - } - - ~SetDoesWindowsFeatureExistResult_Override() - { - AppInstaller::CLI::Workflow::TestHook_SetDoesWindowsFeatureExistResult_Override({}); - } - }; - - struct SetExtractArchiveWithTarResult_Override - { - SetExtractArchiveWithTarResult_Override(DWORD result) - { - AppInstaller::CLI::Workflow::TestHook_SetExtractArchiveWithTarResult_Override(result); - } - - ~SetExtractArchiveWithTarResult_Override() - { - AppInstaller::CLI::Workflow::TestHook_SetExtractArchiveWithTarResult_Override({}); - } - }; - - struct SetInitiateRebootResult_Override - { - SetInitiateRebootResult_Override(bool status) : m_status(status) - { - AppInstaller::Reboot::TestHook_SetInitiateRebootResult_Override(&m_status); - } - - ~SetInitiateRebootResult_Override() - { - AppInstaller::Reboot::TestHook_SetInitiateRebootResult_Override(nullptr); - } - - private: - bool m_status; - }; - - struct SetGetARPKey_Override - { - SetGetARPKey_Override(std::function function) - { - AppInstaller::Repository::Microsoft::SetGetARPKeyOverride(function); - } - - ~SetGetARPKey_Override() - { - AppInstaller::Repository::Microsoft::SetGetARPKeyOverride({}); - } - - private: - }; - - struct SetRegisterForRestartResult_Override - { - SetRegisterForRestartResult_Override(bool status) : m_status(status) - { - AppInstaller::Reboot::TestHook_SetRegisterForRestartResult_Override(&m_status); - } - - ~SetRegisterForRestartResult_Override() - { - AppInstaller::Reboot::TestHook_SetRegisterForRestartResult_Override(nullptr); - } - - private: - bool m_status; - }; - - struct SetAuthenticationResult_Override - { - SetAuthenticationResult_Override(AppInstaller::Authentication::AuthenticationResult authResult) : m_authResult(authResult) - { - AppInstaller::Authentication::TestHook_SetAuthenticationResult_Override(&m_authResult); - } - - ~SetAuthenticationResult_Override() - { - AppInstaller::Authentication::TestHook_SetAuthenticationResult_Override(nullptr); - } - - private: - AppInstaller::Authentication::AuthenticationResult m_authResult; - }; - - struct SetDisplayCatalogHttpPipelineStage_Override - { - SetDisplayCatalogHttpPipelineStage_Override(std::shared_ptr value) - { - AppInstaller::MSStore::TestHooks::SetDisplayCatalogHttpPipelineStage_Override(value); - } - - ~SetDisplayCatalogHttpPipelineStage_Override() - { - AppInstaller::MSStore::TestHooks::SetDisplayCatalogHttpPipelineStage_Override(nullptr); - } - }; - - struct SetSfsClientAppContents_Override - { - SetSfsClientAppContents_Override(std::function(std::string_view)> value) : m_appContentsFunction(std::move(value)) - { - AppInstaller::MSStore::TestHooks::SetSfsClientAppContents_Override(&m_appContentsFunction); - } - - ~SetSfsClientAppContents_Override() - { - AppInstaller::MSStore::TestHooks::SetSfsClientAppContents_Override(nullptr); - } - - private: - std::function(std::string_view)> m_appContentsFunction; - }; - - struct SetLicensingHttpPipelineStage_Override - { - SetLicensingHttpPipelineStage_Override(std::shared_ptr value) - { - AppInstaller::MSStore::TestHooks::SetLicensingHttpPipelineStage_Override(value); - } - - ~SetLicensingHttpPipelineStage_Override() - { - AppInstaller::MSStore::TestHooks::SetLicensingHttpPipelineStage_Override(nullptr); - } - }; - - struct SetForceProvisionAfterInstall_Override - { - SetForceProvisionAfterInstall_Override(bool value) : m_value(value) - { - AppInstaller::MSStore::TestHooks::TestHook_SetProvisionAfterInstall(&m_value); - } - - ~SetForceProvisionAfterInstall_Override() - { - AppInstaller::MSStore::TestHooks::TestHook_SetProvisionAfterInstall(nullptr); - } - - private: - bool m_value; - }; - - 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; - }; - - struct SetConsoleWidth_Override - { - // Pass std::nullopt to simulate no console (redirected output); - // pass a size_t value to simulate a console of that width. - SetConsoleWidth_Override(std::optional width) : m_width(width) - { - AppInstaller::CLI::Execution::TestHook_SetConsoleWidth_Override(&m_width); - } - - ~SetConsoleWidth_Override() - { - AppInstaller::CLI::Execution::TestHook_SetConsoleWidth_Override(nullptr); - } - - private: - std::optional m_width; - }; - - struct SetGetFontRegistryRoot_Override - { - SetGetFontRegistryRoot_Override(std::function function) - { - AppInstaller::Repository::Microsoft::TestHook_SetGetFontRegistryRootFunc(function); - } - - ~SetGetFontRegistryRoot_Override() - { - AppInstaller::Repository::Microsoft::TestHook_SetGetFontRegistryRootFunc({}); - } - - private: - }; - - struct SetUserSettings_Override - { - SetUserSettings_Override(AppInstaller::Settings::UserSettings& settings) - { - AppInstaller::Settings::SetUserSettingsOverride(&settings); - } - - ~SetUserSettings_Override() - { - AppInstaller::Settings::SetUserSettingsOverride(nullptr); - } - }; - - struct SetSingleExperimentalFeature_Override - { - SetSingleExperimentalFeature_Override(AppInstaller::Settings::ExperimentalFeature::Feature feature) - { - m_overrides[feature] = true; - AppInstaller::Settings::SetExperimentalFeatureOverride(&m_overrides); - } - - ~SetSingleExperimentalFeature_Override() - { - AppInstaller::Settings::SetExperimentalFeatureOverride(nullptr); - } - - private: - std::map m_overrides; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "TestSettings.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef AICLI_DISABLE_TEST_HOOKS +static_assert(false, "Test hooks have been disabled"); +#endif + +namespace AppInstaller +{ + // Don't forget to clear the overrides after use! + // A good way is to create a helper struct that cleans when destroyed + + namespace Runtime + { + void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path); + void TestHook_SetPathOverride(PathName target, const Filesystem::PathDetails& details); + void TestHook_ClearPathOverrides(); + } + + namespace Repository + { + void TestHook_SetSourceFactoryOverride(const std::string& type, std::function()>&& factory); + void TestHook_ClearSourceFactoryOverrides(); + void TestHook_SetExtractIconFromArpEntryResult_Override(std::vector* result); + } + + namespace Repository::Microsoft + { + void TestHook_SetPinningIndex_Override(std::optional&& indexPath); + + using GetARPKeyFunc = std::function; + void SetGetARPKeyOverride(GetARPKeyFunc value); + + using GetFontRegistryRootFunc = std::function; + void TestHook_SetGetFontRegistryRootFunc(GetFontRegistryRootFunc value); + } + + namespace Logging + { + void TestHook_SetTelemetryOverride(std::shared_ptr ttl); + } + + namespace Settings + { + void SetUserSettingsOverride(UserSettings* value); + void SetExperimentalFeatureOverride(const std::map* override); + } + + namespace Filesystem + { + void TestHook_SetCreateSymlinkResult_Override(bool* status); + } + + namespace Archive + { + void TestHook_SetScanArchiveResult_Override(bool* status); + } + + namespace CLI::Execution + { + void TestHook_SetConsoleWidth_Override(std::optional* value); + } + + namespace CLI::Workflow + { + void TestHook_SetEnableWindowsFeatureResult_Override(std::optional&& result); + void TestHook_SetDoesWindowsFeatureExistResult_Override(std::optional&& result); + void TestHook_SetExtractArchiveWithTarResult_Override(std::optional&& result); + } + + namespace Reboot + { + void TestHook_SetInitiateRebootResult_Override(bool* status); + void TestHook_SetRegisterForRestartResult_Override(bool* status); + } + + namespace Authentication + { + void TestHook_SetAuthenticationResult_Override(Authentication::AuthenticationResult* authResult); + } + + namespace MSStore::TestHooks + { + void SetDisplayCatalogHttpPipelineStage_Override(std::shared_ptr value); + + void SetSfsClientAppContents_Override(std::function(std::string_view)>* value); + + void SetLicensingHttpPipelineStage_Override(std::shared_ptr value); + + void TestHook_SetProvisionAfterInstall(bool* value); + } + + namespace Utility::TestHooks + { + void SetDownloadResult_Function_Override(std::function info)>* value); + } +} + +namespace TestHook +{ + struct SetCreateSymlinkResult_Override + { + SetCreateSymlinkResult_Override(bool status) : m_status(status) + { + AppInstaller::Filesystem::TestHook_SetCreateSymlinkResult_Override(&m_status); + } + + ~SetCreateSymlinkResult_Override() + { + AppInstaller::Filesystem::TestHook_SetCreateSymlinkResult_Override(nullptr); + } + + private: + bool m_status; + }; + + struct SetScanArchiveResult_Override + { + SetScanArchiveResult_Override(bool status) : m_status(status) + { + AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(&m_status); + } + + ~SetScanArchiveResult_Override() + { + AppInstaller::Archive::TestHook_SetScanArchiveResult_Override(nullptr); + } + + private: + bool m_status; + }; + + struct SetPinningIndex_Override + { + SetPinningIndex_Override(const std::filesystem::path& indexPath) + { + AppInstaller::Repository::Microsoft::TestHook_SetPinningIndex_Override(indexPath); + } + + ~SetPinningIndex_Override() + { + AppInstaller::Repository::Microsoft::TestHook_SetPinningIndex_Override({}); + } + }; + + struct SetExtractIconFromArpEntryResult_Override + { + SetExtractIconFromArpEntryResult_Override(std::vector extractedIcons) : m_extractedIcons(std::move(extractedIcons)) + { + AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(&m_extractedIcons); + } + + ~SetExtractIconFromArpEntryResult_Override() + { + AppInstaller::Repository::TestHook_SetExtractIconFromArpEntryResult_Override(nullptr); + } + + private: + std::vector m_extractedIcons; + }; + + struct SetEnableWindowsFeatureResult_Override + { + SetEnableWindowsFeatureResult_Override(DWORD result) + { + AppInstaller::CLI::Workflow::TestHook_SetEnableWindowsFeatureResult_Override(result); + } + + ~SetEnableWindowsFeatureResult_Override() + { + AppInstaller::CLI::Workflow::TestHook_SetEnableWindowsFeatureResult_Override({}); + } + }; + + struct SetDoesWindowsFeatureExistResult_Override + { + SetDoesWindowsFeatureExistResult_Override(DWORD result) + { + AppInstaller::CLI::Workflow::TestHook_SetDoesWindowsFeatureExistResult_Override(result); + } + + ~SetDoesWindowsFeatureExistResult_Override() + { + AppInstaller::CLI::Workflow::TestHook_SetDoesWindowsFeatureExistResult_Override({}); + } + }; + + struct SetExtractArchiveWithTarResult_Override + { + SetExtractArchiveWithTarResult_Override(DWORD result) + { + AppInstaller::CLI::Workflow::TestHook_SetExtractArchiveWithTarResult_Override(result); + } + + ~SetExtractArchiveWithTarResult_Override() + { + AppInstaller::CLI::Workflow::TestHook_SetExtractArchiveWithTarResult_Override({}); + } + }; + + struct SetInitiateRebootResult_Override + { + SetInitiateRebootResult_Override(bool status) : m_status(status) + { + AppInstaller::Reboot::TestHook_SetInitiateRebootResult_Override(&m_status); + } + + ~SetInitiateRebootResult_Override() + { + AppInstaller::Reboot::TestHook_SetInitiateRebootResult_Override(nullptr); + } + + private: + bool m_status; + }; + + struct SetGetARPKey_Override + { + SetGetARPKey_Override(std::function function) + { + AppInstaller::Repository::Microsoft::SetGetARPKeyOverride(function); + } + + ~SetGetARPKey_Override() + { + AppInstaller::Repository::Microsoft::SetGetARPKeyOverride({}); + } + + private: + }; + + struct SetRegisterForRestartResult_Override + { + SetRegisterForRestartResult_Override(bool status) : m_status(status) + { + AppInstaller::Reboot::TestHook_SetRegisterForRestartResult_Override(&m_status); + } + + ~SetRegisterForRestartResult_Override() + { + AppInstaller::Reboot::TestHook_SetRegisterForRestartResult_Override(nullptr); + } + + private: + bool m_status; + }; + + struct SetAuthenticationResult_Override + { + SetAuthenticationResult_Override(AppInstaller::Authentication::AuthenticationResult authResult) : m_authResult(authResult) + { + AppInstaller::Authentication::TestHook_SetAuthenticationResult_Override(&m_authResult); + } + + ~SetAuthenticationResult_Override() + { + AppInstaller::Authentication::TestHook_SetAuthenticationResult_Override(nullptr); + } + + private: + AppInstaller::Authentication::AuthenticationResult m_authResult; + }; + + struct SetDisplayCatalogHttpPipelineStage_Override + { + SetDisplayCatalogHttpPipelineStage_Override(std::shared_ptr value) + { + AppInstaller::MSStore::TestHooks::SetDisplayCatalogHttpPipelineStage_Override(value); + } + + ~SetDisplayCatalogHttpPipelineStage_Override() + { + AppInstaller::MSStore::TestHooks::SetDisplayCatalogHttpPipelineStage_Override(nullptr); + } + }; + + struct SetSfsClientAppContents_Override + { + SetSfsClientAppContents_Override(std::function(std::string_view)> value) : m_appContentsFunction(std::move(value)) + { + AppInstaller::MSStore::TestHooks::SetSfsClientAppContents_Override(&m_appContentsFunction); + } + + ~SetSfsClientAppContents_Override() + { + AppInstaller::MSStore::TestHooks::SetSfsClientAppContents_Override(nullptr); + } + + private: + std::function(std::string_view)> m_appContentsFunction; + }; + + struct SetLicensingHttpPipelineStage_Override + { + SetLicensingHttpPipelineStage_Override(std::shared_ptr value) + { + AppInstaller::MSStore::TestHooks::SetLicensingHttpPipelineStage_Override(value); + } + + ~SetLicensingHttpPipelineStage_Override() + { + AppInstaller::MSStore::TestHooks::SetLicensingHttpPipelineStage_Override(nullptr); + } + }; + + struct SetForceProvisionAfterInstall_Override + { + SetForceProvisionAfterInstall_Override(bool value) : m_value(value) + { + AppInstaller::MSStore::TestHooks::TestHook_SetProvisionAfterInstall(&m_value); + } + + ~SetForceProvisionAfterInstall_Override() + { + AppInstaller::MSStore::TestHooks::TestHook_SetProvisionAfterInstall(nullptr); + } + + private: + bool m_value; + }; + + 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; + }; + + struct SetConsoleWidth_Override + { + // Pass std::nullopt to simulate no console (redirected output); + // pass a size_t value to simulate a console of that width. + SetConsoleWidth_Override(std::optional width) : m_width(width) + { + AppInstaller::CLI::Execution::TestHook_SetConsoleWidth_Override(&m_width); + } + + ~SetConsoleWidth_Override() + { + AppInstaller::CLI::Execution::TestHook_SetConsoleWidth_Override(nullptr); + } + + private: + std::optional m_width; + }; + + struct SetGetFontRegistryRoot_Override + { + SetGetFontRegistryRoot_Override(std::function function) + { + AppInstaller::Repository::Microsoft::TestHook_SetGetFontRegistryRootFunc(function); + } + + ~SetGetFontRegistryRoot_Override() + { + AppInstaller::Repository::Microsoft::TestHook_SetGetFontRegistryRootFunc({}); + } + + private: + }; + + struct SetUserSettings_Override + { + SetUserSettings_Override(AppInstaller::Settings::UserSettings& settings) + { + AppInstaller::Settings::SetUserSettingsOverride(&settings); + } + + ~SetUserSettings_Override() + { + AppInstaller::Settings::SetUserSettingsOverride(nullptr); + } + }; + + struct SetSingleExperimentalFeature_Override + { + SetSingleExperimentalFeature_Override(AppInstaller::Settings::ExperimentalFeature::Feature feature) + { + m_overrides[feature] = true; + AppInstaller::Settings::SetExperimentalFeatureOverride(&m_overrides); + } + + ~SetSingleExperimentalFeature_Override() + { + AppInstaller::Settings::SetExperimentalFeatureOverride(nullptr); + } + + private: + std::map m_overrides; + }; +} diff --git a/src/AppInstallerCLITests/TestRestRequestHandler.cpp b/src/AppInstallerCLITests/TestRestRequestHandler.cpp index 4bad4e5da5..04645a773d 100644 --- a/src/AppInstallerCLITests/TestRestRequestHandler.cpp +++ b/src/AppInstallerCLITests/TestRestRequestHandler.cpp @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestRestRequestHandler.h" -#include -#include - -std::shared_ptr GetTestRestRequestHandler( - const web::http::status_code statusCode, const utility::string_t& sampleResponseString, const utility::string_t& mimeType) -{ - return std::make_shared([statusCode, sampleResponseString, mimeType](web::http::http_request) -> - pplx::task - { - web::http::http_response response; - if (sampleResponseString.empty()) - { - response.set_body(utf16string{}); - } - else - { - response.set_body(web::json::value::parse(sampleResponseString)); - } - - response.headers().set_content_type(mimeType); - response.headers().set_cache_control(L"no-store"); - response.set_status_code(statusCode); - return pplx::task_from_result(response); - }); -} - -std::shared_ptr GetTestRestRequestHandler( - std::function handler) -{ - return std::make_shared([handler = std::move(handler)](web::http::http_request request) -> - pplx::task - { - web::http::http_response response; - response.set_body(utf16string{}); - - response.headers().set_content_type(web::http::details::mime_types::application_json); - response.headers().set_cache_control(L"no-store"); - response.set_status_code(handler(request)); - return pplx::task_from_result(response); - }); -} - -std::shared_ptr GetHeaderVerificationHandler( - const web::http::status_code statusCode, const utility::string_t& sampleResponseString, const std::pair& header, web::http::status_code statusCodeOnFailure) -{ - return std::make_shared([statusCode, sampleResponseString, header, statusCodeOnFailure](web::http::http_request request) -> - pplx::task - { - web::http::http_response response; - auto& headers = request.headers(); - if (!headers.has(header.first) || - (utility::conversions::to_utf8string(header.second).compare(utility::conversions::to_utf8string(headers[header.first]))) != 0) - { - response.set_body(utf16string{ L"Expected header not found" }); - response.set_status_code(statusCodeOnFailure); - return pplx::task_from_result(response); - } - - if (!sampleResponseString.empty()) - { - response.set_body(web::json::value::parse(sampleResponseString)); - } - - response.headers().set_content_type(web::http::details::mime_types::application_json); - response.headers().set_cache_control(L"no-store"); - response.set_status_code(statusCode); - return pplx::task_from_result(response); - }); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestRestRequestHandler.h" +#include +#include + +std::shared_ptr GetTestRestRequestHandler( + const web::http::status_code statusCode, const utility::string_t& sampleResponseString, const utility::string_t& mimeType) +{ + return std::make_shared([statusCode, sampleResponseString, mimeType](web::http::http_request) -> + pplx::task + { + web::http::http_response response; + if (sampleResponseString.empty()) + { + response.set_body(utf16string{}); + } + else + { + response.set_body(web::json::value::parse(sampleResponseString)); + } + + response.headers().set_content_type(mimeType); + response.headers().set_cache_control(L"no-store"); + response.set_status_code(statusCode); + return pplx::task_from_result(response); + }); +} + +std::shared_ptr GetTestRestRequestHandler( + std::function handler) +{ + return std::make_shared([handler = std::move(handler)](web::http::http_request request) -> + pplx::task + { + web::http::http_response response; + response.set_body(utf16string{}); + + response.headers().set_content_type(web::http::details::mime_types::application_json); + response.headers().set_cache_control(L"no-store"); + response.set_status_code(handler(request)); + return pplx::task_from_result(response); + }); +} + +std::shared_ptr GetHeaderVerificationHandler( + const web::http::status_code statusCode, const utility::string_t& sampleResponseString, const std::pair& header, web::http::status_code statusCodeOnFailure) +{ + return std::make_shared([statusCode, sampleResponseString, header, statusCodeOnFailure](web::http::http_request request) -> + pplx::task + { + web::http::http_response response; + auto& headers = request.headers(); + if (!headers.has(header.first) || + (utility::conversions::to_utf8string(header.second).compare(utility::conversions::to_utf8string(headers[header.first]))) != 0) + { + response.set_body(utf16string{ L"Expected header not found" }); + response.set_status_code(statusCodeOnFailure); + return pplx::task_from_result(response); + } + + if (!sampleResponseString.empty()) + { + response.set_body(web::json::value::parse(sampleResponseString)); + } + + response.headers().set_content_type(web::http::details::mime_types::application_json); + response.headers().set_cache_control(L"no-store"); + response.set_status_code(statusCode); + return pplx::task_from_result(response); + }); +} diff --git a/src/AppInstallerCLITests/TestRestRequestHandler.h b/src/AppInstallerCLITests/TestRestRequestHandler.h index fa8f7d10ef..3990934705 100644 --- a/src/AppInstallerCLITests/TestRestRequestHandler.h +++ b/src/AppInstallerCLITests/TestRestRequestHandler.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -class TestRestRequestHandler : public web::http::http_pipeline_stage -{ -public: - TestRestRequestHandler(const std::function(web::http::http_request request)>& handler) : m_handler(handler) {} - - virtual pplx::task propagate(web::http::http_request request) - { - return m_handler(request); - } - -private: - std::function(web::http::http_request request)> m_handler; -}; - -std::shared_ptr GetTestRestRequestHandler( - const web::http::status_code statusCode, const utility::string_t& sampleResponseString = {}, const utility::string_t& mimeType = web::http::details::mime_types::application_json); - -std::shared_ptr GetTestRestRequestHandler( - std::function handler); - -std::shared_ptr GetHeaderVerificationHandler( - const web::http::status_code statusCode, const utility::string_t& sampleResponseString, const std::pair& header, web::http::status_code statusCodeOnFailure = web::http::status_codes::BadRequest); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +class TestRestRequestHandler : public web::http::http_pipeline_stage +{ +public: + TestRestRequestHandler(const std::function(web::http::http_request request)>& handler) : m_handler(handler) {} + + virtual pplx::task propagate(web::http::http_request request) + { + return m_handler(request); + } + +private: + std::function(web::http::http_request request)> m_handler; +}; + +std::shared_ptr GetTestRestRequestHandler( + const web::http::status_code statusCode, const utility::string_t& sampleResponseString = {}, const utility::string_t& mimeType = web::http::details::mime_types::application_json); + +std::shared_ptr GetTestRestRequestHandler( + std::function handler); + +std::shared_ptr GetHeaderVerificationHandler( + const web::http::status_code statusCode, const utility::string_t& sampleResponseString, const std::pair& header, web::http::status_code statusCodeOnFailure = web::http::status_codes::BadRequest); diff --git a/src/AppInstallerCLITests/TestSettings.cpp b/src/AppInstallerCLITests/TestSettings.cpp index 2ee0b0dc1a..f7a96efa12 100644 --- a/src/AppInstallerCLITests/TestSettings.cpp +++ b/src/AppInstallerCLITests/TestSettings.cpp @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSettings.h" -#include - -using namespace AppInstaller::Settings; - -namespace TestCommon -{ - namespace - { - void DeleteUserSettingsFilesInternal() - { - auto settingsPath = UserSettings::SettingsFilePath(); - if (std::filesystem::exists(settingsPath)) - { - std::filesystem::remove(settingsPath); - } - - auto settingsBackupPath = GetPathTo(Stream::BackupUserSettings); - if (std::filesystem::exists(settingsBackupPath)) - { - std::filesystem::remove(settingsBackupPath); - } - } - } - - void SetSetting(const AppInstaller::Settings::StreamDefinition& stream, std::string_view value) - { - REQUIRE(Stream{ stream }.Set(value)); - } - - void RemoveSetting(const AppInstaller::Settings::StreamDefinition& stream) - { - Stream{ stream }.Remove(); - } - - std::filesystem::path GetPathTo(const AppInstaller::Settings::StreamDefinition& stream) - { - return Stream{ stream }.GetPath(); - } - - UserSettingsFileGuard::UserSettingsFileGuard() - { - DeleteUserSettingsFilesInternal(); - } - - UserSettingsFileGuard::~UserSettingsFileGuard() - { - DeleteUserSettingsFilesInternal(); - } - - [[nodiscard]] UserSettingsFileGuard DeleteUserSettingsFiles() - { - return {}; - } - - GroupPolicyTestOverride::GroupPolicyTestOverride(const AppInstaller::Registry::Key& key) : GroupPolicy(key) - { - GroupPolicy::OverrideInstance(this); - } - - GroupPolicyTestOverride::~GroupPolicyTestOverride() - { - GroupPolicy::ResetInstance(); - } - - void GroupPolicyTestOverride::SetState(TogglePolicy::Policy policy, PolicyState state) - { - m_toggles[policy] = state; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSettings.h" +#include + +using namespace AppInstaller::Settings; + +namespace TestCommon +{ + namespace + { + void DeleteUserSettingsFilesInternal() + { + auto settingsPath = UserSettings::SettingsFilePath(); + if (std::filesystem::exists(settingsPath)) + { + std::filesystem::remove(settingsPath); + } + + auto settingsBackupPath = GetPathTo(Stream::BackupUserSettings); + if (std::filesystem::exists(settingsBackupPath)) + { + std::filesystem::remove(settingsBackupPath); + } + } + } + + void SetSetting(const AppInstaller::Settings::StreamDefinition& stream, std::string_view value) + { + REQUIRE(Stream{ stream }.Set(value)); + } + + void RemoveSetting(const AppInstaller::Settings::StreamDefinition& stream) + { + Stream{ stream }.Remove(); + } + + std::filesystem::path GetPathTo(const AppInstaller::Settings::StreamDefinition& stream) + { + return Stream{ stream }.GetPath(); + } + + UserSettingsFileGuard::UserSettingsFileGuard() + { + DeleteUserSettingsFilesInternal(); + } + + UserSettingsFileGuard::~UserSettingsFileGuard() + { + DeleteUserSettingsFilesInternal(); + } + + [[nodiscard]] UserSettingsFileGuard DeleteUserSettingsFiles() + { + return {}; + } + + GroupPolicyTestOverride::GroupPolicyTestOverride(const AppInstaller::Registry::Key& key) : GroupPolicy(key) + { + GroupPolicy::OverrideInstance(this); + } + + GroupPolicyTestOverride::~GroupPolicyTestOverride() + { + GroupPolicy::ResetInstance(); + } + + void GroupPolicyTestOverride::SetState(TogglePolicy::Policy policy, PolicyState state) + { + m_toggles[policy] = state; + } +} diff --git a/src/AppInstallerCLITests/TestSettings.h b/src/AppInstallerCLITests/TestSettings.h index cc862073a9..3ac150db05 100644 --- a/src/AppInstallerCLITests/TestSettings.h +++ b/src/AppInstallerCLITests/TestSettings.h @@ -1,97 +1,97 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "TestCommon.h" -#include -#include -#include -#include - -namespace TestCommon -{ - // Repeat the policy values here so we can catch unintended changes in the source. - const std::wstring WinGetPolicyValueName = L"EnableAppInstaller"; - const std::wstring WinGetSettingsPolicyValueName = L"EnableSettings"; - const std::wstring ExperimentalFeaturesPolicyValueName = L"EnableExperimentalFeatures"; - const std::wstring LocalManifestsPolicyValueName = L"EnableLocalManifestFiles"; - const std::wstring EnableHashOverridePolicyValueName = L"EnableHashOverride"; - const std::wstring EnableLocalArchiveMalwareScanOverridePolicyValueName = L"EnableLocalArchiveMalwareScanOverride"; - const std::wstring DefaultSourcePolicyValueName = L"EnableDefaultSource"; - const std::wstring MSStoreSourcePolicyValueName = L"EnableMicrosoftStoreSource"; - const std::wstring FontSourcePolicyValueName = L"EnableFontSource"; - const std::wstring AdditionalSourcesPolicyValueName = L"EnableAdditionalSources"; - const std::wstring AllowedSourcesPolicyValueName = L"EnableAllowedSources"; - const std::wstring BypassCertificatePinningForMicrosoftStoreValueName = L"EnableBypassCertificatePinningForMicrosoftStore"; - const std::wstring EnableWindowsPackageManagerCommandLineInterfaces = L"EnableWindowsPackageManagerCommandLineInterfaces"; - const std::wstring ConfigurationPolicyValueName = L"EnableWindowsPackageManagerConfiguration"; - const std::wstring ProxyCommandLineOptionsPolicyValueName = L"EnableWindowsPackageManagerProxyCommandLineOptions"; - const std::wstring McpServerValueName = L"EnableWindowsPackageManagerMcpServer"; - const std::wstring ConfigurationProcessorPathValueName = L"EnableWindowsPackageManagerConfigurationProcessorPath"; - - const std::wstring SourceUpdateIntervalPolicyValueName = L"SourceAutoUpdateInterval"; - const std::wstring SourceUpdateIntervalPolicyOldValueName = L"SourceAutoUpdateIntervalInMinutes"; - - const std::wstring AdditionalSourcesPolicyKeyName = L"AdditionalSources"; - const std::wstring AllowedSourcesPolicyKeyName = L"AllowedSources"; - - void SetSetting(const AppInstaller::Settings::StreamDefinition& stream, std::string_view value); - void RemoveSetting(const AppInstaller::Settings::StreamDefinition& stream); - std::filesystem::path GetPathTo(const AppInstaller::Settings::StreamDefinition& stream); - - // This type removes the settings file on creation and destruction to ensure that a test that modifies them can do so cleanly. - struct UserSettingsFileGuard - { - UserSettingsFileGuard(); - ~UserSettingsFileGuard(); - }; - - [[nodiscard]] UserSettingsFileGuard DeleteUserSettingsFiles(); - - struct UserSettingsTest : AppInstaller::Settings::UserSettings - { - }; - - struct GroupPolicyTestOverride : AppInstaller::Settings::GroupPolicy - { - GroupPolicyTestOverride() : GroupPolicyTestOverride(RegCreateVolatileTestRoot().get()) {} - GroupPolicyTestOverride(const AppInstaller::Registry::Key& key); - ~GroupPolicyTestOverride(); - - template - void SetValue(const ValueType

& value) - { - m_values.Add

(value); - } - - template - void SetValue(ValueType

&&value) - { - m_values.Add

(std::move(value)); - } - - void SetState(AppInstaller::Settings::TogglePolicy::Policy policy, AppInstaller::Settings::PolicyState state); - }; - - // Matcher that lets us verify GroupPolicyExceptions. - struct GroupPolicyExceptionMatcher : public Catch::Matchers::MatcherBase - { - GroupPolicyExceptionMatcher(AppInstaller::Settings::TogglePolicy::Policy policy) : m_expectedPolicy(policy) {} - - bool match(const AppInstaller::Settings::GroupPolicyException& e) const override - { - return e.Policy() == m_expectedPolicy; - } - - std::string describe() const override - { - std::ostringstream result; - result << "has policy == " << m_expectedPolicy; - return result.str(); - } - - private: - AppInstaller::Settings::TogglePolicy::Policy m_expectedPolicy; - }; - -#define REQUIRE_POLICY_EXCEPTION(_expr_, _policy_) REQUIRE_THROWS_MATCHES(_expr_, AppInstaller::Settings::GroupPolicyException, TestCommon::GroupPolicyExceptionMatcher(_policy_)) -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "TestCommon.h" +#include +#include +#include +#include + +namespace TestCommon +{ + // Repeat the policy values here so we can catch unintended changes in the source. + const std::wstring WinGetPolicyValueName = L"EnableAppInstaller"; + const std::wstring WinGetSettingsPolicyValueName = L"EnableSettings"; + const std::wstring ExperimentalFeaturesPolicyValueName = L"EnableExperimentalFeatures"; + const std::wstring LocalManifestsPolicyValueName = L"EnableLocalManifestFiles"; + const std::wstring EnableHashOverridePolicyValueName = L"EnableHashOverride"; + const std::wstring EnableLocalArchiveMalwareScanOverridePolicyValueName = L"EnableLocalArchiveMalwareScanOverride"; + const std::wstring DefaultSourcePolicyValueName = L"EnableDefaultSource"; + const std::wstring MSStoreSourcePolicyValueName = L"EnableMicrosoftStoreSource"; + const std::wstring FontSourcePolicyValueName = L"EnableFontSource"; + const std::wstring AdditionalSourcesPolicyValueName = L"EnableAdditionalSources"; + const std::wstring AllowedSourcesPolicyValueName = L"EnableAllowedSources"; + const std::wstring BypassCertificatePinningForMicrosoftStoreValueName = L"EnableBypassCertificatePinningForMicrosoftStore"; + const std::wstring EnableWindowsPackageManagerCommandLineInterfaces = L"EnableWindowsPackageManagerCommandLineInterfaces"; + const std::wstring ConfigurationPolicyValueName = L"EnableWindowsPackageManagerConfiguration"; + const std::wstring ProxyCommandLineOptionsPolicyValueName = L"EnableWindowsPackageManagerProxyCommandLineOptions"; + const std::wstring McpServerValueName = L"EnableWindowsPackageManagerMcpServer"; + const std::wstring ConfigurationProcessorPathValueName = L"EnableWindowsPackageManagerConfigurationProcessorPath"; + + const std::wstring SourceUpdateIntervalPolicyValueName = L"SourceAutoUpdateInterval"; + const std::wstring SourceUpdateIntervalPolicyOldValueName = L"SourceAutoUpdateIntervalInMinutes"; + + const std::wstring AdditionalSourcesPolicyKeyName = L"AdditionalSources"; + const std::wstring AllowedSourcesPolicyKeyName = L"AllowedSources"; + + void SetSetting(const AppInstaller::Settings::StreamDefinition& stream, std::string_view value); + void RemoveSetting(const AppInstaller::Settings::StreamDefinition& stream); + std::filesystem::path GetPathTo(const AppInstaller::Settings::StreamDefinition& stream); + + // This type removes the settings file on creation and destruction to ensure that a test that modifies them can do so cleanly. + struct UserSettingsFileGuard + { + UserSettingsFileGuard(); + ~UserSettingsFileGuard(); + }; + + [[nodiscard]] UserSettingsFileGuard DeleteUserSettingsFiles(); + + struct UserSettingsTest : AppInstaller::Settings::UserSettings + { + }; + + struct GroupPolicyTestOverride : AppInstaller::Settings::GroupPolicy + { + GroupPolicyTestOverride() : GroupPolicyTestOverride(RegCreateVolatileTestRoot().get()) {} + GroupPolicyTestOverride(const AppInstaller::Registry::Key& key); + ~GroupPolicyTestOverride(); + + template + void SetValue(const ValueType

& value) + { + m_values.Add

(value); + } + + template + void SetValue(ValueType

&&value) + { + m_values.Add

(std::move(value)); + } + + void SetState(AppInstaller::Settings::TogglePolicy::Policy policy, AppInstaller::Settings::PolicyState state); + }; + + // Matcher that lets us verify GroupPolicyExceptions. + struct GroupPolicyExceptionMatcher : public Catch::Matchers::MatcherBase + { + GroupPolicyExceptionMatcher(AppInstaller::Settings::TogglePolicy::Policy policy) : m_expectedPolicy(policy) {} + + bool match(const AppInstaller::Settings::GroupPolicyException& e) const override + { + return e.Policy() == m_expectedPolicy; + } + + std::string describe() const override + { + std::ostringstream result; + result << "has policy == " << m_expectedPolicy; + return result.str(); + } + + private: + AppInstaller::Settings::TogglePolicy::Policy m_expectedPolicy; + }; + +#define REQUIRE_POLICY_EXCEPTION(_expr_, _policy_) REQUIRE_THROWS_MATCHES(_expr_, AppInstaller::Settings::GroupPolicyException, TestCommon::GroupPolicyExceptionMatcher(_policy_)) +} diff --git a/src/AppInstallerCLITests/TestSource.cpp b/src/AppInstallerCLITests/TestSource.cpp index e900dc556a..f864b95a98 100644 --- a/src/AppInstallerCLITests/TestSource.cpp +++ b/src/AppInstallerCLITests/TestSource.cpp @@ -1,527 +1,527 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSource.h" - -using namespace AppInstaller; -using namespace AppInstaller::Repository; - -namespace TestCommon -{ - namespace - { - size_t GetNextTestPackageId() - { - static std::atomic_size_t packageId(0); - return ++packageId; - } - - TestSource* GetTestSourceFromWeakPtr(const std::weak_ptr& weakSource) - { - if (auto source = weakSource.lock()) - { - if (auto testSource = const_cast(source.get())->CastTo(TestSource::SourceType)) - { - return reinterpret_cast(testSource); - } - } - - return nullptr; - } - } - - TestPackageVersion::TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source) : - VersionManifest(manifest), Metadata(std::move(installationMetadata)), Source(source) {} - - TestPackageVersion::TestPackageVersion(const Manifest& manifest, std::weak_ptr source, bool hideSystemReferenceStrings) : - VersionManifest(manifest), Source(source), HideSystemReferenceStrings(hideSystemReferenceStrings) {} - - TestPackageVersion::LocIndString TestPackageVersion::GetProperty(PackageVersionProperty property) const - { - switch (property) - { - case PackageVersionProperty::Id: - return LocIndString{ VersionManifest.Id }; - case PackageVersionProperty::Name: - return LocIndString{ VersionManifest.DefaultLocalization.Get() }; - case PackageVersionProperty::Version: - return LocIndString{ VersionManifest.Version }; - case PackageVersionProperty::Channel: - return LocIndString{ VersionManifest.Channel }; - case PackageVersionProperty::SourceIdentifier: - return LocIndString{ Source.lock()->GetIdentifier() }; - case PackageVersionProperty::Publisher: - return LocIndString{ VersionManifest.DefaultLocalization.Get() }; - case PackageVersionProperty::ArpMinVersion: - return LocIndString{ VersionManifest.GetArpVersionRange().IsEmpty() ? "" : VersionManifest.GetArpVersionRange().GetMinVersion().ToString() }; - case PackageVersionProperty::ArpMaxVersion: - return LocIndString{ VersionManifest.GetArpVersionRange().IsEmpty() ? "" : VersionManifest.GetArpVersionRange().GetMaxVersion().ToString() }; - case PackageVersionProperty::Moniker: - return LocIndString{ VersionManifest.Moniker }; - default: - return {}; - } - } - - std::vector TestPackageVersion::GetMultiProperty(PackageVersionMultiProperty property) const - { - std::vector result; - - switch (property) - { - case PackageVersionMultiProperty::PackageFamilyName: - if (!HideSystemReferenceStrings) - { - for (const auto& installer : VersionManifest.Installers) - { - AddIfHasValueAndNotPresent(installer.PackageFamilyName, result, true); - } - } - break; - case PackageVersionMultiProperty::ProductCode: - if (!HideSystemReferenceStrings) - { - for (const auto& installer : VersionManifest.Installers) - { - bool shouldFoldCaseForNonPortable = installer.EffectiveInstallerType() != AppInstaller::Manifest::InstallerTypeEnum::Portable; - AddIfHasValueAndNotPresent(installer.ProductCode, result, shouldFoldCaseForNonPortable); - } - } - break; - case PackageVersionMultiProperty::Name: - for (auto name : VersionManifest.GetPackageNames()) - { - result.emplace_back(std::move(name)); - } - break; - case PackageVersionMultiProperty::Publisher: - for (auto publisher : VersionManifest.GetPublishers()) - { - result.emplace_back(std::move(publisher)); - } - break; - case PackageVersionMultiProperty::Locale: - result.emplace_back(VersionManifest.DefaultLocalization.Locale); - for (const auto& loc : VersionManifest.Localizations) - { - result.emplace_back(loc.Locale); - } - break; - case PackageVersionMultiProperty::Command: - for (auto value : VersionManifest.GetAggregatedCommands()) - { - result.emplace_back(std::move(value)); - } - break; - case PackageVersionMultiProperty::Tag: - for (auto value : VersionManifest.GetAggregatedTags()) - { - result.emplace_back(std::move(value)); - } - break; - case PackageVersionMultiProperty::UpgradeCode: - if (!HideSystemReferenceStrings) - { - for (const auto& installer : VersionManifest.Installers) - { - bool shouldFoldCaseForNonPortable = installer.EffectiveInstallerType() != AppInstaller::Manifest::InstallerTypeEnum::Portable; - for (const auto& entry : installer.AppsAndFeaturesEntries) - { - AddIfHasValueAndNotPresent(entry.UpgradeCode, result, shouldFoldCaseForNonPortable); - } - } - } - break; - } - - return result; - } - - TestPackageVersion::Manifest TestPackageVersion::GetManifest() - { - if (auto source = GetTestSource()) - { - source->IncrementCountOfCallsRequiringManifestData(); - } - - return VersionManifest; - } - - Repository::Source TestPackageVersion::GetSource() const - { - return std::const_pointer_cast(Source.lock()); - } - - TestPackageVersion::MetadataMap TestPackageVersion::GetMetadata() const - { - return Metadata; - } - - void TestPackageVersion::AddIfHasValueAndNotPresent(const Utility::NormalizedString& value, std::vector& target, bool folded) - { - if (!value.empty()) - { - std::string valueString = folded ? FoldCase(value) : value; - auto itr = std::find(target.begin(), target.end(), valueString); - if (itr == target.end()) - { - target.emplace_back(std::move(valueString)); - } - } - } - - TestSource* TestPackageVersion::GetTestSource() const - { - return GetTestSourceFromWeakPtr(Source); - } - - TestPackage::TestPackage(const std::vector& available, std::weak_ptr source, bool hideSystemReferenceStringsOnVersion) : - Source(source) - { - DefaultIsSameIdentity = GetNextTestPackageId(); - for (const auto& manifest : available) - { - Versions.emplace_back(TestPackageVersion::Make(manifest, source, hideSystemReferenceStringsOnVersion)); - } - } - - TestPackage::TestPackage(const Manifest& installed, MetadataMap installationMetadata, std::weak_ptr source) : - Source(source) - { - DefaultIsSameIdentity = GetNextTestPackageId(); - Versions.emplace_back(TestPackageVersion::Make(installed, std::move(installationMetadata), source)); - } - - TestPackage::LocIndString TestPackage::GetProperty(PackageProperty property) const - { - std::shared_ptr truth; - - if (!Versions.empty()) - { - truth = Versions[0]; - } - - if (!truth) - { - THROW_HR(E_NOT_VALID_STATE); - } - - switch (property) - { - case PackageProperty::Id: - return truth->GetProperty(PackageVersionProperty::Id); - case PackageProperty::Name: - return truth->GetProperty(PackageVersionProperty::Name); - default: - return {}; - } - } - - std::vector TestPackage::GetMultiProperty(PackageMultiProperty property) const - { - std::vector result; - PackageVersionMultiProperty mappedProperty = PackageMultiPropertyToPackageVersionMultiProperty(property); - - for (const auto& version : Versions) - { - for (auto&& string : version->GetMultiProperty(mappedProperty)) - { - auto itr = std::lower_bound(result.begin(), result.end(), string); - - if (itr == result.end() || *itr != string) - { - result.emplace(itr, std::move(string)); - } - } - } - - return result; - } - - std::vector TestPackage::GetVersionKeys() const - { - if (auto source = GetTestSource()) - { - source->IncrementCountOfCallsRequiringVersionData(); - } - - std::vector result; - for (const auto& version : Versions) - { - result.emplace_back(PackageVersionKey(version->GetSource().GetIdentifier(), version->GetProperty(PackageVersionProperty::Version).get(), version->GetProperty(PackageVersionProperty::Channel).get())); - } - return result; - } - - std::shared_ptr TestPackage::GetLatestVersion() const - { - if (Versions.empty()) - { - return {}; - } - - return Versions[0]; - } - - std::shared_ptr TestPackage::GetVersion(const PackageVersionKey& versionKey) const - { - if (!versionKey.IsDefaultLatest()) - { - if (auto source = GetTestSource()) - { - source->IncrementCountOfCallsRequiringVersionData(); - } - } - - for (const auto& version : Versions) - { - if ((versionKey.Version.empty() || versionKey.Version == version->GetProperty(PackageVersionProperty::Version).get()) && - (versionKey.Channel.empty() || versionKey.Channel == version->GetProperty(PackageVersionProperty::Channel).get())) - { - return version; - } - } - - return {}; - } - - Repository::Source TestPackage::GetSource() const - { - return std::const_pointer_cast(Source.lock()); - } - - bool TestPackage::IsSame(const IPackage* other) const - { - if (IsSameOverride) - { - return IsSameOverride(this, other); - } - - const TestPackage* otherPackage = PackageCast(other); - - if (otherPackage && DefaultIsSameIdentity == otherPackage->DefaultIsSameIdentity) - { - return true; - } - - return false; - } - - const void* TestPackage::CastTo(IPackageType type) const - { - if (type == PackageType) - { - return this; - } - - return nullptr; - } - - TestSource* TestPackage::GetTestSource() const - { - return GetTestSourceFromWeakPtr(Source); - } - - TestCompositePackage::TestCompositePackage(const std::vector& available, std::weak_ptr source, bool hideSystemReferenceStringsOnVersion) - { - if (!available.empty()) - { - Available.emplace_back(TestPackage::Make(available, source, hideSystemReferenceStringsOnVersion)); - } - } - - TestCompositePackage::TestCompositePackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available, std::weak_ptr source) : - Installed(TestPackage::Make(installed, std::move(installationMetadata), source)) - { - if (!available.empty()) - { - Available.emplace_back(TestPackage::Make(available, source)); - } - } - - TestCompositePackage::LocIndString TestCompositePackage::GetProperty(PackageProperty property) const - { - std::shared_ptr truth; - - if (!Available.empty()) - { - truth = Available[0]; - } - else - { - truth = Installed; - } - - if (!truth) - { - THROW_HR(E_NOT_VALID_STATE); - } - - switch (property) - { - case PackageProperty::Id: - return truth->GetProperty(PackageProperty::Id); - case PackageProperty::Name: - return truth->GetProperty(PackageProperty::Name); - default: - return {}; - } - } - - std::shared_ptr TestCompositePackage::GetInstalled() - { - return Installed; - } - - std::vector> TestCompositePackage::GetAvailable() - { - return { Available.begin(), Available.end() }; - } - - const SourceDetails& TestSource::GetDetails() const - { - return Details; - } - - const std::string& TestSource::GetIdentifier() const - { - return Details.Identifier; - } - - SourceInformation TestSource::GetInformation() const - { - return Information; - } - - bool TestSource::QueryFeatureFlag(SourceFeatureFlag flag) const - { - return (QueryFeatureFlagFunction ? QueryFeatureFlagFunction(flag) : false); - } - - SearchResult TestSource::Search(const SearchRequest& request) const - { - if (SearchFunction) - { - return SearchFunction(request); - } - else - { - return {}; - } - } - - void* TestSource::CastTo(AppInstaller::Repository::ISourceType type) - { - if (type == SourceType) - { - return this; - } - - return nullptr; - } - - void TestSource::IncrementCountOfCallsRequiringVersionData() - { - ++CountOfCallsRequiringVersionData; - } - - void TestSource::IncrementCountOfCallsRequiringManifestData() - { - ++CountOfCallsRequiringManifestData; - } - - std::string_view TestSourceFactory::TypeName() const - { - return "*TestSource"sv; - } - - std::shared_ptr TestSourceFactory::Create(const SourceDetails& details) - { - std::shared_ptr result; - - if (OnOpenWithCustomHeader) - { - result = std::make_shared(details, OnOpenWithCustomHeader); - } - else - { - result = std::make_shared(details, OnOpen); - } - - result->ShouldUpdateBeforeOpenResult = ShouldUpdateBeforeOpenResult; - - return result; - } - - bool TestSourceFactory::Add(SourceDetails& details, IProgressCallback&) - { - if (OnAdd) - { - OnAdd(details); - } - return true; - } - - bool TestSourceFactory::Update(const SourceDetails& details, IProgressCallback&) - { - if (OnUpdate) - { - OnUpdate(details); - } - return true; - } - - bool TestSourceFactory::Remove(const SourceDetails& details, IProgressCallback&) - { - if (OnRemove) - { - OnRemove(details); - } - return true; - } - - // Make copies of self when requested. - TestSourceFactory::operator std::function()>() - { - return [this]() { return std::make_unique(*this); }; - } - - bool AddSource(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback& progress) - { - Repository::SourceEdit additionalProperties; - additionalProperties.Explicit = details.Explicit; - additionalProperties.Priority = details.Priority; - Repository::Source source{ details.Name, details.Arg, details.Type, Repository::SourceTrustLevel::None, additionalProperties }; - return source.Add(progress); - } - - bool UpdateSource(std::string_view name, AppInstaller::IProgressCallback& progress) - { - Repository::Source source{ name }; - return source.Update(progress).empty(); - } - - bool RemoveSource(std::string_view name, AppInstaller::IProgressCallback& progress) - { - Repository::Source source{ name }; - return source.Remove(progress); - } - - std::vector GetSources() - { - return Repository::Source::GetCurrentSources(); - } - - AppInstaller::Repository::Source OpenSource(std::string_view name, AppInstaller::IProgressCallback& progress) - { - Repository::Source source{ name }; - source.Open(progress); - return source; - } - - void DropSource(std::string_view name) - { - Source::DropSource(name); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSource.h" + +using namespace AppInstaller; +using namespace AppInstaller::Repository; + +namespace TestCommon +{ + namespace + { + size_t GetNextTestPackageId() + { + static std::atomic_size_t packageId(0); + return ++packageId; + } + + TestSource* GetTestSourceFromWeakPtr(const std::weak_ptr& weakSource) + { + if (auto source = weakSource.lock()) + { + if (auto testSource = const_cast(source.get())->CastTo(TestSource::SourceType)) + { + return reinterpret_cast(testSource); + } + } + + return nullptr; + } + } + + TestPackageVersion::TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source) : + VersionManifest(manifest), Metadata(std::move(installationMetadata)), Source(source) {} + + TestPackageVersion::TestPackageVersion(const Manifest& manifest, std::weak_ptr source, bool hideSystemReferenceStrings) : + VersionManifest(manifest), Source(source), HideSystemReferenceStrings(hideSystemReferenceStrings) {} + + TestPackageVersion::LocIndString TestPackageVersion::GetProperty(PackageVersionProperty property) const + { + switch (property) + { + case PackageVersionProperty::Id: + return LocIndString{ VersionManifest.Id }; + case PackageVersionProperty::Name: + return LocIndString{ VersionManifest.DefaultLocalization.Get() }; + case PackageVersionProperty::Version: + return LocIndString{ VersionManifest.Version }; + case PackageVersionProperty::Channel: + return LocIndString{ VersionManifest.Channel }; + case PackageVersionProperty::SourceIdentifier: + return LocIndString{ Source.lock()->GetIdentifier() }; + case PackageVersionProperty::Publisher: + return LocIndString{ VersionManifest.DefaultLocalization.Get() }; + case PackageVersionProperty::ArpMinVersion: + return LocIndString{ VersionManifest.GetArpVersionRange().IsEmpty() ? "" : VersionManifest.GetArpVersionRange().GetMinVersion().ToString() }; + case PackageVersionProperty::ArpMaxVersion: + return LocIndString{ VersionManifest.GetArpVersionRange().IsEmpty() ? "" : VersionManifest.GetArpVersionRange().GetMaxVersion().ToString() }; + case PackageVersionProperty::Moniker: + return LocIndString{ VersionManifest.Moniker }; + default: + return {}; + } + } + + std::vector TestPackageVersion::GetMultiProperty(PackageVersionMultiProperty property) const + { + std::vector result; + + switch (property) + { + case PackageVersionMultiProperty::PackageFamilyName: + if (!HideSystemReferenceStrings) + { + for (const auto& installer : VersionManifest.Installers) + { + AddIfHasValueAndNotPresent(installer.PackageFamilyName, result, true); + } + } + break; + case PackageVersionMultiProperty::ProductCode: + if (!HideSystemReferenceStrings) + { + for (const auto& installer : VersionManifest.Installers) + { + bool shouldFoldCaseForNonPortable = installer.EffectiveInstallerType() != AppInstaller::Manifest::InstallerTypeEnum::Portable; + AddIfHasValueAndNotPresent(installer.ProductCode, result, shouldFoldCaseForNonPortable); + } + } + break; + case PackageVersionMultiProperty::Name: + for (auto name : VersionManifest.GetPackageNames()) + { + result.emplace_back(std::move(name)); + } + break; + case PackageVersionMultiProperty::Publisher: + for (auto publisher : VersionManifest.GetPublishers()) + { + result.emplace_back(std::move(publisher)); + } + break; + case PackageVersionMultiProperty::Locale: + result.emplace_back(VersionManifest.DefaultLocalization.Locale); + for (const auto& loc : VersionManifest.Localizations) + { + result.emplace_back(loc.Locale); + } + break; + case PackageVersionMultiProperty::Command: + for (auto value : VersionManifest.GetAggregatedCommands()) + { + result.emplace_back(std::move(value)); + } + break; + case PackageVersionMultiProperty::Tag: + for (auto value : VersionManifest.GetAggregatedTags()) + { + result.emplace_back(std::move(value)); + } + break; + case PackageVersionMultiProperty::UpgradeCode: + if (!HideSystemReferenceStrings) + { + for (const auto& installer : VersionManifest.Installers) + { + bool shouldFoldCaseForNonPortable = installer.EffectiveInstallerType() != AppInstaller::Manifest::InstallerTypeEnum::Portable; + for (const auto& entry : installer.AppsAndFeaturesEntries) + { + AddIfHasValueAndNotPresent(entry.UpgradeCode, result, shouldFoldCaseForNonPortable); + } + } + } + break; + } + + return result; + } + + TestPackageVersion::Manifest TestPackageVersion::GetManifest() + { + if (auto source = GetTestSource()) + { + source->IncrementCountOfCallsRequiringManifestData(); + } + + return VersionManifest; + } + + Repository::Source TestPackageVersion::GetSource() const + { + return std::const_pointer_cast(Source.lock()); + } + + TestPackageVersion::MetadataMap TestPackageVersion::GetMetadata() const + { + return Metadata; + } + + void TestPackageVersion::AddIfHasValueAndNotPresent(const Utility::NormalizedString& value, std::vector& target, bool folded) + { + if (!value.empty()) + { + std::string valueString = folded ? FoldCase(value) : value; + auto itr = std::find(target.begin(), target.end(), valueString); + if (itr == target.end()) + { + target.emplace_back(std::move(valueString)); + } + } + } + + TestSource* TestPackageVersion::GetTestSource() const + { + return GetTestSourceFromWeakPtr(Source); + } + + TestPackage::TestPackage(const std::vector& available, std::weak_ptr source, bool hideSystemReferenceStringsOnVersion) : + Source(source) + { + DefaultIsSameIdentity = GetNextTestPackageId(); + for (const auto& manifest : available) + { + Versions.emplace_back(TestPackageVersion::Make(manifest, source, hideSystemReferenceStringsOnVersion)); + } + } + + TestPackage::TestPackage(const Manifest& installed, MetadataMap installationMetadata, std::weak_ptr source) : + Source(source) + { + DefaultIsSameIdentity = GetNextTestPackageId(); + Versions.emplace_back(TestPackageVersion::Make(installed, std::move(installationMetadata), source)); + } + + TestPackage::LocIndString TestPackage::GetProperty(PackageProperty property) const + { + std::shared_ptr truth; + + if (!Versions.empty()) + { + truth = Versions[0]; + } + + if (!truth) + { + THROW_HR(E_NOT_VALID_STATE); + } + + switch (property) + { + case PackageProperty::Id: + return truth->GetProperty(PackageVersionProperty::Id); + case PackageProperty::Name: + return truth->GetProperty(PackageVersionProperty::Name); + default: + return {}; + } + } + + std::vector TestPackage::GetMultiProperty(PackageMultiProperty property) const + { + std::vector result; + PackageVersionMultiProperty mappedProperty = PackageMultiPropertyToPackageVersionMultiProperty(property); + + for (const auto& version : Versions) + { + for (auto&& string : version->GetMultiProperty(mappedProperty)) + { + auto itr = std::lower_bound(result.begin(), result.end(), string); + + if (itr == result.end() || *itr != string) + { + result.emplace(itr, std::move(string)); + } + } + } + + return result; + } + + std::vector TestPackage::GetVersionKeys() const + { + if (auto source = GetTestSource()) + { + source->IncrementCountOfCallsRequiringVersionData(); + } + + std::vector result; + for (const auto& version : Versions) + { + result.emplace_back(PackageVersionKey(version->GetSource().GetIdentifier(), version->GetProperty(PackageVersionProperty::Version).get(), version->GetProperty(PackageVersionProperty::Channel).get())); + } + return result; + } + + std::shared_ptr TestPackage::GetLatestVersion() const + { + if (Versions.empty()) + { + return {}; + } + + return Versions[0]; + } + + std::shared_ptr TestPackage::GetVersion(const PackageVersionKey& versionKey) const + { + if (!versionKey.IsDefaultLatest()) + { + if (auto source = GetTestSource()) + { + source->IncrementCountOfCallsRequiringVersionData(); + } + } + + for (const auto& version : Versions) + { + if ((versionKey.Version.empty() || versionKey.Version == version->GetProperty(PackageVersionProperty::Version).get()) && + (versionKey.Channel.empty() || versionKey.Channel == version->GetProperty(PackageVersionProperty::Channel).get())) + { + return version; + } + } + + return {}; + } + + Repository::Source TestPackage::GetSource() const + { + return std::const_pointer_cast(Source.lock()); + } + + bool TestPackage::IsSame(const IPackage* other) const + { + if (IsSameOverride) + { + return IsSameOverride(this, other); + } + + const TestPackage* otherPackage = PackageCast(other); + + if (otherPackage && DefaultIsSameIdentity == otherPackage->DefaultIsSameIdentity) + { + return true; + } + + return false; + } + + const void* TestPackage::CastTo(IPackageType type) const + { + if (type == PackageType) + { + return this; + } + + return nullptr; + } + + TestSource* TestPackage::GetTestSource() const + { + return GetTestSourceFromWeakPtr(Source); + } + + TestCompositePackage::TestCompositePackage(const std::vector& available, std::weak_ptr source, bool hideSystemReferenceStringsOnVersion) + { + if (!available.empty()) + { + Available.emplace_back(TestPackage::Make(available, source, hideSystemReferenceStringsOnVersion)); + } + } + + TestCompositePackage::TestCompositePackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available, std::weak_ptr source) : + Installed(TestPackage::Make(installed, std::move(installationMetadata), source)) + { + if (!available.empty()) + { + Available.emplace_back(TestPackage::Make(available, source)); + } + } + + TestCompositePackage::LocIndString TestCompositePackage::GetProperty(PackageProperty property) const + { + std::shared_ptr truth; + + if (!Available.empty()) + { + truth = Available[0]; + } + else + { + truth = Installed; + } + + if (!truth) + { + THROW_HR(E_NOT_VALID_STATE); + } + + switch (property) + { + case PackageProperty::Id: + return truth->GetProperty(PackageProperty::Id); + case PackageProperty::Name: + return truth->GetProperty(PackageProperty::Name); + default: + return {}; + } + } + + std::shared_ptr TestCompositePackage::GetInstalled() + { + return Installed; + } + + std::vector> TestCompositePackage::GetAvailable() + { + return { Available.begin(), Available.end() }; + } + + const SourceDetails& TestSource::GetDetails() const + { + return Details; + } + + const std::string& TestSource::GetIdentifier() const + { + return Details.Identifier; + } + + SourceInformation TestSource::GetInformation() const + { + return Information; + } + + bool TestSource::QueryFeatureFlag(SourceFeatureFlag flag) const + { + return (QueryFeatureFlagFunction ? QueryFeatureFlagFunction(flag) : false); + } + + SearchResult TestSource::Search(const SearchRequest& request) const + { + if (SearchFunction) + { + return SearchFunction(request); + } + else + { + return {}; + } + } + + void* TestSource::CastTo(AppInstaller::Repository::ISourceType type) + { + if (type == SourceType) + { + return this; + } + + return nullptr; + } + + void TestSource::IncrementCountOfCallsRequiringVersionData() + { + ++CountOfCallsRequiringVersionData; + } + + void TestSource::IncrementCountOfCallsRequiringManifestData() + { + ++CountOfCallsRequiringManifestData; + } + + std::string_view TestSourceFactory::TypeName() const + { + return "*TestSource"sv; + } + + std::shared_ptr TestSourceFactory::Create(const SourceDetails& details) + { + std::shared_ptr result; + + if (OnOpenWithCustomHeader) + { + result = std::make_shared(details, OnOpenWithCustomHeader); + } + else + { + result = std::make_shared(details, OnOpen); + } + + result->ShouldUpdateBeforeOpenResult = ShouldUpdateBeforeOpenResult; + + return result; + } + + bool TestSourceFactory::Add(SourceDetails& details, IProgressCallback&) + { + if (OnAdd) + { + OnAdd(details); + } + return true; + } + + bool TestSourceFactory::Update(const SourceDetails& details, IProgressCallback&) + { + if (OnUpdate) + { + OnUpdate(details); + } + return true; + } + + bool TestSourceFactory::Remove(const SourceDetails& details, IProgressCallback&) + { + if (OnRemove) + { + OnRemove(details); + } + return true; + } + + // Make copies of self when requested. + TestSourceFactory::operator std::function()>() + { + return [this]() { return std::make_unique(*this); }; + } + + bool AddSource(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback& progress) + { + Repository::SourceEdit additionalProperties; + additionalProperties.Explicit = details.Explicit; + additionalProperties.Priority = details.Priority; + Repository::Source source{ details.Name, details.Arg, details.Type, Repository::SourceTrustLevel::None, additionalProperties }; + return source.Add(progress); + } + + bool UpdateSource(std::string_view name, AppInstaller::IProgressCallback& progress) + { + Repository::Source source{ name }; + return source.Update(progress).empty(); + } + + bool RemoveSource(std::string_view name, AppInstaller::IProgressCallback& progress) + { + Repository::Source source{ name }; + return source.Remove(progress); + } + + std::vector GetSources() + { + return Repository::Source::GetCurrentSources(); + } + + AppInstaller::Repository::Source OpenSource(std::string_view name, AppInstaller::IProgressCallback& progress) + { + Repository::Source source{ name }; + source.Open(progress); + return source; + } + + void DropSource(std::string_view name) + { + Source::DropSource(name); + } +} diff --git a/src/AppInstallerCLITests/TestSource.h b/src/AppInstallerCLITests/TestSource.h index bc5aca74f7..c6ced92427 100644 --- a/src/AppInstallerCLITests/TestSource.h +++ b/src/AppInstallerCLITests/TestSource.h @@ -1,218 +1,218 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include - -namespace TestCommon -{ - struct TestSource; - - // IPackageVersion for TestSource - struct TestPackageVersion : public AppInstaller::Repository::IPackageVersion - { - using Manifest = AppInstaller::Manifest::Manifest; - using ISource = AppInstaller::Repository::ISource; - using LocIndString = AppInstaller::Utility::LocIndString; - using MetadataMap = AppInstaller::Repository::IPackageVersion::Metadata; - - TestPackageVersion(const Manifest& manifest, std::weak_ptr source = {}, bool hideSystemReferenceStrings = false); - TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source = {}); - - template - static std::shared_ptr Make(Args&&... args) - { - return std::make_shared(std::forward(args)...); - } - - LocIndString GetProperty(AppInstaller::Repository::PackageVersionProperty property) const override; - std::vector GetMultiProperty(AppInstaller::Repository::PackageVersionMultiProperty property) const override; - Manifest GetManifest() override; - AppInstaller::Repository::Source GetSource() const override; - MetadataMap GetMetadata() const override; - - Manifest VersionManifest; - MetadataMap Metadata; - std::weak_ptr Source; - bool HideSystemReferenceStrings = false; - - protected: - static void AddIfHasValueAndNotPresent(const AppInstaller::Utility::NormalizedString& value, std::vector& target, bool folded = false); - TestSource* GetTestSource() const; - }; - - // IPackage for TestSource - struct TestPackage : public AppInstaller::Repository::IPackage - { - static constexpr AppInstaller::Repository::IPackageType PackageType = AppInstaller::Repository::IPackageType::TestPackage; - - using Manifest = AppInstaller::Manifest::Manifest; - using ISource = AppInstaller::Repository::ISource; - using LocIndString = AppInstaller::Utility::LocIndString; - using MetadataMap = TestPackageVersion::MetadataMap; - - // Create a package with only available versions using these manifests. - TestPackage(const std::vector& available, std::weak_ptr source = {}, bool hideSystemReferenceStringsOnVersion = false); - - // Create a package with an installed version, metadata, and optionally available versions. - TestPackage(const Manifest& installed, MetadataMap installationMetadata, std::weak_ptr source = {}); - - template - static std::shared_ptr Make(Args&&... args) - { - return std::make_shared(std::forward(args)...); - } - - AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; - std::vector GetMultiProperty(AppInstaller::Repository::PackageMultiProperty property) const override; - std::vector GetVersionKeys() const override; - std::shared_ptr GetLatestVersion() const override; - std::shared_ptr GetVersion(const AppInstaller::Repository::PackageVersionKey& versionKey) const override; - AppInstaller::Repository::Source GetSource() const override; - bool IsSame(const IPackage* other) const override; - const void* CastTo(AppInstaller::Repository::IPackageType type) const override; - - std::vector> Versions; - std::weak_ptr Source; - size_t DefaultIsSameIdentity = 0; - std::function IsSameOverride; - - protected: - TestSource* GetTestSource() const; - }; - - // ICompositePackage for TestSource - struct TestCompositePackage : public AppInstaller::Repository::ICompositePackage - { - using Manifest = AppInstaller::Manifest::Manifest; - using ISource = AppInstaller::Repository::ISource; - using LocIndString = AppInstaller::Utility::LocIndString; - using MetadataMap = TestPackageVersion::MetadataMap; - - // Create a package with only available versions using these manifests. - TestCompositePackage(const std::vector& available, std::weak_ptr source = {}, bool hideSystemReferenceStringsOnVersion = false); - - // Create a package with an installed version, metadata, and optionally available versions. - TestCompositePackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available = {}, std::weak_ptr source = {}); - - template - static std::shared_ptr Make(Args&&... args) - { - return std::make_shared(std::forward(args)...); - } - - AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; - std::shared_ptr GetInstalled() override; - std::vector> GetAvailable() override; - - std::shared_ptr Installed; - std::vector> Available; - }; - - // An ISource implementation for use across the test code. - struct TestSource : public AppInstaller::Repository::ISource, public std::enable_shared_from_this - { - static constexpr AppInstaller::Repository::ISourceType SourceType = AppInstaller::Repository::ISourceType::TestSource; - - const AppInstaller::Repository::SourceDetails& GetDetails() const override; - const std::string& GetIdentifier() const override; - AppInstaller::Repository::SourceInformation GetInformation() const override; - - bool QueryFeatureFlag(AppInstaller::Repository::SourceFeatureFlag flag) const override; - std::function QueryFeatureFlagFunction; - - AppInstaller::Repository::SearchResult Search(const AppInstaller::Repository::SearchRequest& request) const override; - void* CastTo(AppInstaller::Repository::ISourceType type) override; - - AppInstaller::Repository::SourceDetails Details = { "TestSource", "Microsoft.TestSource", "//arg", "", "*TestSource" }; - AppInstaller::Repository::SourceInformation Information; - std::function SearchFunction; - - TestSource() = default; - TestSource(const AppInstaller::Repository::SourceDetails& details) : Details(details) {} - - // Tracking for potential network impacts - void IncrementCountOfCallsRequiringVersionData(); - size_t CountOfCallsRequiringVersionData = 0; - - void IncrementCountOfCallsRequiringManifestData(); - size_t CountOfCallsRequiringManifestData = 0; - }; - - struct TestSourceReference : public AppInstaller::Repository::ISourceReference - { - using OpenFunctor = std::function(const AppInstaller::Repository::SourceDetails&)>; - using OpenFunctorWithCustomHeader = std::function(const AppInstaller::Repository::SourceDetails&, std::optional)>; - - TestSourceReference(const AppInstaller::Repository::SourceDetails& details, OpenFunctor open) : m_details(details), m_onOpen(open) {} - TestSourceReference(const AppInstaller::Repository::SourceDetails& details, OpenFunctorWithCustomHeader open) : m_details(details), m_onOpenWithCustomHeader(open) {} - - std::string GetIdentifier() override { return m_details.Identifier; } - - AppInstaller::Repository::SourceDetails& GetDetails() override { return m_details; }; - - bool SetCustomHeader(std::optional header) override { m_header = header; return true; } - - bool ShouldUpdateBeforeOpenResult = false; - bool ShouldUpdateBeforeOpen(const std::optional&) override { return ShouldUpdateBeforeOpenResult; } - - std::shared_ptr Open(AppInstaller::IProgressCallback&) override - { - if (m_onOpenWithCustomHeader) - { - return m_onOpenWithCustomHeader(m_details, m_header); - } - else - { - return m_onOpen(m_details); - } - } - - private: - AppInstaller::Repository::SourceDetails m_details; - OpenFunctor m_onOpen; - OpenFunctorWithCustomHeader m_onOpenWithCustomHeader; - std::optional m_header; - }; - - // An ISourceFactory implementation for use across the test code. - struct TestSourceFactory : public AppInstaller::Repository::ISourceFactory - { - using OpenFunctor = std::function(const AppInstaller::Repository::SourceDetails&)>; - using OpenFunctorWithCustomHeader = std::function(const AppInstaller::Repository::SourceDetails&, std::optional)>; - using AddFunctor = std::function; - using UpdateFunctor = std::function; - using RemoveFunctor = std::function; - - TestSourceFactory(OpenFunctor open) : OnOpen(std::move(open)) {} - TestSourceFactory(OpenFunctorWithCustomHeader open) : OnOpenWithCustomHeader(std::move(open)) {} - - // ISourceFactory - std::string_view TypeName() const override; - std::shared_ptr Create(const AppInstaller::Repository::SourceDetails& details) override; - bool Add(AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; - bool Update(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; - bool Remove(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; - - // Make copies of self when requested. - operator std::function()>(); - - bool ShouldUpdateBeforeOpenResult = false; - OpenFunctor OnOpen; - OpenFunctorWithCustomHeader OnOpenWithCustomHeader; - AddFunctor OnAdd; - UpdateFunctor OnUpdate; - RemoveFunctor OnRemove; - }; - - bool AddSource(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback& progress); - bool UpdateSource(std::string_view name, AppInstaller::IProgressCallback& progress); - bool RemoveSource(std::string_view name, AppInstaller::IProgressCallback& progress); - AppInstaller::Repository::Source OpenSource(std::string_view name, AppInstaller::IProgressCallback& progress); - void DropSource(std::string_view name); - std::vector GetSources(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include + +namespace TestCommon +{ + struct TestSource; + + // IPackageVersion for TestSource + struct TestPackageVersion : public AppInstaller::Repository::IPackageVersion + { + using Manifest = AppInstaller::Manifest::Manifest; + using ISource = AppInstaller::Repository::ISource; + using LocIndString = AppInstaller::Utility::LocIndString; + using MetadataMap = AppInstaller::Repository::IPackageVersion::Metadata; + + TestPackageVersion(const Manifest& manifest, std::weak_ptr source = {}, bool hideSystemReferenceStrings = false); + TestPackageVersion(const Manifest& manifest, MetadataMap installationMetadata, std::weak_ptr source = {}); + + template + static std::shared_ptr Make(Args&&... args) + { + return std::make_shared(std::forward(args)...); + } + + LocIndString GetProperty(AppInstaller::Repository::PackageVersionProperty property) const override; + std::vector GetMultiProperty(AppInstaller::Repository::PackageVersionMultiProperty property) const override; + Manifest GetManifest() override; + AppInstaller::Repository::Source GetSource() const override; + MetadataMap GetMetadata() const override; + + Manifest VersionManifest; + MetadataMap Metadata; + std::weak_ptr Source; + bool HideSystemReferenceStrings = false; + + protected: + static void AddIfHasValueAndNotPresent(const AppInstaller::Utility::NormalizedString& value, std::vector& target, bool folded = false); + TestSource* GetTestSource() const; + }; + + // IPackage for TestSource + struct TestPackage : public AppInstaller::Repository::IPackage + { + static constexpr AppInstaller::Repository::IPackageType PackageType = AppInstaller::Repository::IPackageType::TestPackage; + + using Manifest = AppInstaller::Manifest::Manifest; + using ISource = AppInstaller::Repository::ISource; + using LocIndString = AppInstaller::Utility::LocIndString; + using MetadataMap = TestPackageVersion::MetadataMap; + + // Create a package with only available versions using these manifests. + TestPackage(const std::vector& available, std::weak_ptr source = {}, bool hideSystemReferenceStringsOnVersion = false); + + // Create a package with an installed version, metadata, and optionally available versions. + TestPackage(const Manifest& installed, MetadataMap installationMetadata, std::weak_ptr source = {}); + + template + static std::shared_ptr Make(Args&&... args) + { + return std::make_shared(std::forward(args)...); + } + + AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; + std::vector GetMultiProperty(AppInstaller::Repository::PackageMultiProperty property) const override; + std::vector GetVersionKeys() const override; + std::shared_ptr GetLatestVersion() const override; + std::shared_ptr GetVersion(const AppInstaller::Repository::PackageVersionKey& versionKey) const override; + AppInstaller::Repository::Source GetSource() const override; + bool IsSame(const IPackage* other) const override; + const void* CastTo(AppInstaller::Repository::IPackageType type) const override; + + std::vector> Versions; + std::weak_ptr Source; + size_t DefaultIsSameIdentity = 0; + std::function IsSameOverride; + + protected: + TestSource* GetTestSource() const; + }; + + // ICompositePackage for TestSource + struct TestCompositePackage : public AppInstaller::Repository::ICompositePackage + { + using Manifest = AppInstaller::Manifest::Manifest; + using ISource = AppInstaller::Repository::ISource; + using LocIndString = AppInstaller::Utility::LocIndString; + using MetadataMap = TestPackageVersion::MetadataMap; + + // Create a package with only available versions using these manifests. + TestCompositePackage(const std::vector& available, std::weak_ptr source = {}, bool hideSystemReferenceStringsOnVersion = false); + + // Create a package with an installed version, metadata, and optionally available versions. + TestCompositePackage(const Manifest& installed, MetadataMap installationMetadata, const std::vector& available = {}, std::weak_ptr source = {}); + + template + static std::shared_ptr Make(Args&&... args) + { + return std::make_shared(std::forward(args)...); + } + + AppInstaller::Utility::LocIndString GetProperty(AppInstaller::Repository::PackageProperty property) const override; + std::shared_ptr GetInstalled() override; + std::vector> GetAvailable() override; + + std::shared_ptr Installed; + std::vector> Available; + }; + + // An ISource implementation for use across the test code. + struct TestSource : public AppInstaller::Repository::ISource, public std::enable_shared_from_this + { + static constexpr AppInstaller::Repository::ISourceType SourceType = AppInstaller::Repository::ISourceType::TestSource; + + const AppInstaller::Repository::SourceDetails& GetDetails() const override; + const std::string& GetIdentifier() const override; + AppInstaller::Repository::SourceInformation GetInformation() const override; + + bool QueryFeatureFlag(AppInstaller::Repository::SourceFeatureFlag flag) const override; + std::function QueryFeatureFlagFunction; + + AppInstaller::Repository::SearchResult Search(const AppInstaller::Repository::SearchRequest& request) const override; + void* CastTo(AppInstaller::Repository::ISourceType type) override; + + AppInstaller::Repository::SourceDetails Details = { "TestSource", "Microsoft.TestSource", "//arg", "", "*TestSource" }; + AppInstaller::Repository::SourceInformation Information; + std::function SearchFunction; + + TestSource() = default; + TestSource(const AppInstaller::Repository::SourceDetails& details) : Details(details) {} + + // Tracking for potential network impacts + void IncrementCountOfCallsRequiringVersionData(); + size_t CountOfCallsRequiringVersionData = 0; + + void IncrementCountOfCallsRequiringManifestData(); + size_t CountOfCallsRequiringManifestData = 0; + }; + + struct TestSourceReference : public AppInstaller::Repository::ISourceReference + { + using OpenFunctor = std::function(const AppInstaller::Repository::SourceDetails&)>; + using OpenFunctorWithCustomHeader = std::function(const AppInstaller::Repository::SourceDetails&, std::optional)>; + + TestSourceReference(const AppInstaller::Repository::SourceDetails& details, OpenFunctor open) : m_details(details), m_onOpen(open) {} + TestSourceReference(const AppInstaller::Repository::SourceDetails& details, OpenFunctorWithCustomHeader open) : m_details(details), m_onOpenWithCustomHeader(open) {} + + std::string GetIdentifier() override { return m_details.Identifier; } + + AppInstaller::Repository::SourceDetails& GetDetails() override { return m_details; }; + + bool SetCustomHeader(std::optional header) override { m_header = header; return true; } + + bool ShouldUpdateBeforeOpenResult = false; + bool ShouldUpdateBeforeOpen(const std::optional&) override { return ShouldUpdateBeforeOpenResult; } + + std::shared_ptr Open(AppInstaller::IProgressCallback&) override + { + if (m_onOpenWithCustomHeader) + { + return m_onOpenWithCustomHeader(m_details, m_header); + } + else + { + return m_onOpen(m_details); + } + } + + private: + AppInstaller::Repository::SourceDetails m_details; + OpenFunctor m_onOpen; + OpenFunctorWithCustomHeader m_onOpenWithCustomHeader; + std::optional m_header; + }; + + // An ISourceFactory implementation for use across the test code. + struct TestSourceFactory : public AppInstaller::Repository::ISourceFactory + { + using OpenFunctor = std::function(const AppInstaller::Repository::SourceDetails&)>; + using OpenFunctorWithCustomHeader = std::function(const AppInstaller::Repository::SourceDetails&, std::optional)>; + using AddFunctor = std::function; + using UpdateFunctor = std::function; + using RemoveFunctor = std::function; + + TestSourceFactory(OpenFunctor open) : OnOpen(std::move(open)) {} + TestSourceFactory(OpenFunctorWithCustomHeader open) : OnOpenWithCustomHeader(std::move(open)) {} + + // ISourceFactory + std::string_view TypeName() const override; + std::shared_ptr Create(const AppInstaller::Repository::SourceDetails& details) override; + bool Add(AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; + bool Update(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; + bool Remove(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback&) override; + + // Make copies of self when requested. + operator std::function()>(); + + bool ShouldUpdateBeforeOpenResult = false; + OpenFunctor OnOpen; + OpenFunctorWithCustomHeader OnOpenWithCustomHeader; + AddFunctor OnAdd; + UpdateFunctor OnUpdate; + RemoveFunctor OnRemove; + }; + + bool AddSource(const AppInstaller::Repository::SourceDetails& details, AppInstaller::IProgressCallback& progress); + bool UpdateSource(std::string_view name, AppInstaller::IProgressCallback& progress); + bool RemoveSource(std::string_view name, AppInstaller::IProgressCallback& progress); + AppInstaller::Repository::Source OpenSource(std::string_view name, AppInstaller::IProgressCallback& progress); + void DropSource(std::string_view name); + std::vector GetSources(); +} diff --git a/src/AppInstallerCLITests/UninstallFlow.cpp b/src/AppInstallerCLITests/UninstallFlow.cpp index 480c830e41..67ca2b52ce 100644 --- a/src/AppInstallerCLITests/UninstallFlow.cpp +++ b/src/AppInstallerCLITests/UninstallFlow.cpp @@ -1,212 +1,212 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Workflow; - -void OverrideForPortableUninstall(TestContext& context) -{ - context.Override({ GetUninstallInfo, [](TestContext&) - { - } }); - - context.Override({ PortableUninstallImpl, [](TestContext& context) - { - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestPortableUninstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - file.close(); - - context.Add(0); - } }); -} - -void OverrideForExeUninstall(TestContext& context) -{ - context.Override({ ShellExecuteUninstallImpl, [](TestContext& context) - { - // Write out the uninstall command - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestExeUninstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - file << context.Get(); - file.close(); - - context.Add(0); - } }); -} - -void OverrideForMSIXUninstall(TestContext& context) -{ - context.Override({ MsixUninstall, [](TestContext& context) - { - // Write out the package full name - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestMsixUninstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - for (const auto& packageFamilyName : context.Get()) - { - file << packageFamilyName << std::endl; - } - - file.close(); - } }); -} - -TEST_CASE("UninstallFlow_UninstallPortable", "[UninstallFlow][workflow]") -{ - TestCommon::TempFile uninstallResultPath("TestPortableUninstalled.txt"); - - std::ostringstream uninstallOutput; - TestContext context{ uninstallOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); - OverrideForPortableUninstall(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); - - UninstallCommand uninstall({}); - uninstall.Execute(context); - INFO(uninstallOutput.str()); - REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); -} - -TEST_CASE("UninstallFlow_UninstallExe", "[UninstallFlow][workflow]") -{ - TestCommon::TempFile uninstallResultPath("TestExeUninstalled.txt"); - - std::ostringstream uninstallOutput; - TestContext context{ uninstallOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - OverrideForExeUninstall(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::Silent); - - UninstallCommand uninstall({}); - uninstall.Execute(context); - INFO(uninstallOutput.str()); - - // Verify Uninstaller is called and parameters are passed in. - REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); - std::ifstream uninstallResultFile(uninstallResultPath.GetPath()); - REQUIRE(uninstallResultFile.is_open()); - std::string uninstallResultStr; - std::getline(uninstallResultFile, uninstallResultStr); - REQUIRE(uninstallResultStr.find("uninstall.exe") != std::string::npos); - REQUIRE(uninstallResultStr.find("/silence") != std::string::npos); -} - -TEST_CASE("UninstallFlow_UninstallMsix", "[UninstallFlow][workflow]") -{ - TestCommon::TempFile uninstallResultPath("TestMsixUninstalled.txt"); - - std::ostringstream uninstallOutput; - TestContext context{ uninstallOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Msix })); - OverrideForMSIXUninstall(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); - - UninstallCommand uninstall({}); - uninstall.Execute(context); - INFO(uninstallOutput.str()); - - // Verify Uninstaller is called with the package full name. - REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); - std::ifstream uninstallResultFile(uninstallResultPath.GetPath()); - REQUIRE(uninstallResultFile.is_open()); - std::string uninstallResultStr; - std::getline(uninstallResultFile, uninstallResultStr); - REQUIRE(uninstallResultStr.find("20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe") != std::string::npos); -} - -TEST_CASE("UninstallFlow_UninstallMSStore", "[UninstallFlow][workflow]") -{ - TestCommon::TempFile uninstallResultPath("TestMsixUninstalled.txt"); - - std::ostringstream uninstallOutput; - TestContext context{ uninstallOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); - OverrideForMSIXUninstall(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_MSStore.Query); - - UninstallCommand uninstall({}); - uninstall.Execute(context); - INFO(uninstallOutput.str()); - - // Verify Uninstaller is called with the package full name - REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); - std::ifstream uninstallResultFile(uninstallResultPath.GetPath()); - REQUIRE(uninstallResultFile.is_open()); - std::string uninstallResultStr; - std::getline(uninstallResultFile, uninstallResultStr); - REQUIRE(uninstallResultStr.find("microsoft.skypeapp_kzf8qxf38zg5c") != std::string::npos); -} - -TEST_CASE("UninstallFlow_UninstallExeNotFound", "[UninstallFlow][workflow]") -{ - TestCommon::TempFile uninstallResultPath("TestExeUninstalled.txt"); - - std::ostringstream uninstallOutput; - TestContext context{ uninstallOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({})); - context.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.MissingApp"sv); - context.Args.AddArg(Execution::Args::Type::Silent); - - UninstallCommand uninstall({}); - uninstall.Execute(context); - INFO(uninstallOutput.str()); - - // Verify Uninstaller is not called. - REQUIRE(!std::filesystem::exists(uninstallResultPath.GetPath())); - REQUIRE(uninstallOutput.str().find(Resource::LocString(Resource::String::NoInstalledPackageFound).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); -} - -TEST_CASE("UninstallFlow_UninstallMultiple", "[UninstallFlow][workflow][MultiQuery]") -{ - TestCommon::TempFile exeUninstallResultPath("TestExeUninstalled.txt"); - TestCommon::TempFile msixUninstallResultPath("TestMsixUninstalled.txt"); - - std::ostringstream uninstallOutput; - TestContext context{ uninstallOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix })); - OverrideForExeUninstall(context); - OverrideForMSIXUninstall(context); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); - - UninstallCommand uninstall({}); - uninstall.Execute(context); - INFO(uninstallOutput.str()); - - // Verify Uninstallers are called - REQUIRE(std::filesystem::exists(exeUninstallResultPath.GetPath())); - REQUIRE(std::filesystem::exists(msixUninstallResultPath.GetPath())); -} - -TEST_CASE("UninstallFlow_UninstallMultiple_NotAllInstalled", "[UninstallFlow][workflow][MultiQuery]") -{ - std::ostringstream uninstallOutput; - TestContext context{ uninstallOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); - - UninstallCommand uninstall({}); - uninstall.Execute(context); - INFO(uninstallOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Workflow; + +void OverrideForPortableUninstall(TestContext& context) +{ + context.Override({ GetUninstallInfo, [](TestContext&) + { + } }); + + context.Override({ PortableUninstallImpl, [](TestContext& context) + { + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestPortableUninstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + file.close(); + + context.Add(0); + } }); +} + +void OverrideForExeUninstall(TestContext& context) +{ + context.Override({ ShellExecuteUninstallImpl, [](TestContext& context) + { + // Write out the uninstall command + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestExeUninstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + file << context.Get(); + file.close(); + + context.Add(0); + } }); +} + +void OverrideForMSIXUninstall(TestContext& context) +{ + context.Override({ MsixUninstall, [](TestContext& context) + { + // Write out the package full name + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestMsixUninstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + for (const auto& packageFamilyName : context.Get()) + { + file << packageFamilyName << std::endl; + } + + file.close(); + } }); +} + +TEST_CASE("UninstallFlow_UninstallPortable", "[UninstallFlow][workflow]") +{ + TestCommon::TempFile uninstallResultPath("TestPortableUninstalled.txt"); + + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); + OverrideForPortableUninstall(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); +} + +TEST_CASE("UninstallFlow_UninstallExe", "[UninstallFlow][workflow]") +{ + TestCommon::TempFile uninstallResultPath("TestExeUninstalled.txt"); + + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + OverrideForExeUninstall(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::Silent); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + // Verify Uninstaller is called and parameters are passed in. + REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); + std::ifstream uninstallResultFile(uninstallResultPath.GetPath()); + REQUIRE(uninstallResultFile.is_open()); + std::string uninstallResultStr; + std::getline(uninstallResultFile, uninstallResultStr); + REQUIRE(uninstallResultStr.find("uninstall.exe") != std::string::npos); + REQUIRE(uninstallResultStr.find("/silence") != std::string::npos); +} + +TEST_CASE("UninstallFlow_UninstallMsix", "[UninstallFlow][workflow]") +{ + TestCommon::TempFile uninstallResultPath("TestMsixUninstalled.txt"); + + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Msix })); + OverrideForMSIXUninstall(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + // Verify Uninstaller is called with the package full name. + REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); + std::ifstream uninstallResultFile(uninstallResultPath.GetPath()); + REQUIRE(uninstallResultFile.is_open()); + std::string uninstallResultStr; + std::getline(uninstallResultFile, uninstallResultStr); + REQUIRE(uninstallResultStr.find("20477fca-282d-49fb-b03e-371dca074f0f_8wekyb3d8bbwe") != std::string::npos); +} + +TEST_CASE("UninstallFlow_UninstallMSStore", "[UninstallFlow][workflow]") +{ + TestCommon::TempFile uninstallResultPath("TestMsixUninstalled.txt"); + + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); + OverrideForMSIXUninstall(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_MSStore.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + // Verify Uninstaller is called with the package full name + REQUIRE(std::filesystem::exists(uninstallResultPath.GetPath())); + std::ifstream uninstallResultFile(uninstallResultPath.GetPath()); + REQUIRE(uninstallResultFile.is_open()); + std::string uninstallResultStr; + std::getline(uninstallResultFile, uninstallResultStr); + REQUIRE(uninstallResultStr.find("microsoft.skypeapp_kzf8qxf38zg5c") != std::string::npos); +} + +TEST_CASE("UninstallFlow_UninstallExeNotFound", "[UninstallFlow][workflow]") +{ + TestCommon::TempFile uninstallResultPath("TestExeUninstalled.txt"); + + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({})); + context.Args.AddArg(Execution::Args::Type::Query, "AppInstallerCliTest.MissingApp"sv); + context.Args.AddArg(Execution::Args::Type::Silent); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + // Verify Uninstaller is not called. + REQUIRE(!std::filesystem::exists(uninstallResultPath.GetPath())); + REQUIRE(uninstallOutput.str().find(Resource::LocString(Resource::String::NoInstalledPackageFound).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); +} + +TEST_CASE("UninstallFlow_UninstallMultiple", "[UninstallFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeUninstallResultPath("TestExeUninstalled.txt"); + TestCommon::TempFile msixUninstallResultPath("TestMsixUninstalled.txt"); + + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix })); + OverrideForExeUninstall(context); + OverrideForMSIXUninstall(context); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + // Verify Uninstallers are called + REQUIRE(std::filesystem::exists(exeUninstallResultPath.GetPath())); + REQUIRE(std::filesystem::exists(msixUninstallResultPath.GetPath())); +} + +TEST_CASE("UninstallFlow_UninstallMultiple_NotAllInstalled", "[UninstallFlow][workflow][MultiQuery]") +{ + std::ostringstream uninstallOutput; + TestContext context{ uninstallOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(context); + INFO(uninstallOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); +} diff --git a/src/AppInstallerCLITests/UpdateFlow.cpp b/src/AppInstallerCLITests/UpdateFlow.cpp index 6e38204bb2..9d70283c78 100644 --- a/src/AppInstallerCLITests/UpdateFlow.cpp +++ b/src/AppInstallerCLITests/UpdateFlow.cpp @@ -1,1057 +1,1057 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "TestHooks.h" -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility::literals; - -TEST_CASE("UpdateFlow_UpdateWithManifest", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("UpdateFlowTest_Exe.yaml").GetPath().u8string()); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); - std::ifstream updateResultFile(updateResultPath.GetPath()); - REQUIRE(updateResultFile.is_open()); - std::string updateResultStr; - std::getline(updateResultFile, updateResultStr); - REQUIRE(updateResultStr.find("/update") != std::string::npos); - REQUIRE(updateResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("UpdateFlow_UpdateWithManifestMSStore", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestMSStoreUpdated.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); - OverrideForMSStore(context, true); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore.yaml").GetPath().u8string()); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); - std::ifstream updateResultFile(updateResultPath.GetPath()); - REQUIRE(updateResultFile.is_open()); - std::string updateResultStr; - std::getline(updateResultFile, updateResultStr); - REQUIRE(updateResultStr.find("9WZDNCRFJ364") != std::string::npos); -} - -TEST_CASE("UpdateFlow_UpdateWithManifestAppNotInstalled", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({})); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml").GetPath().u8string()); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::NoInstalledPackageFound).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); -} - -TEST_CASE("UpdateFlow_UpdateWithManifestVersionAlreadyInstalled", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); -} - -TEST_CASE("UpdateFlow_UpdateExe", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::Silent); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); - std::ifstream updateResultFile(updateResultPath.GetPath()); - REQUIRE(updateResultFile.is_open()); - std::string updateResultStr; - std::getline(updateResultFile, updateResultStr); - REQUIRE(updateResultStr.find("/update") != std::string::npos); - REQUIRE(updateResultStr.find("/silence") != std::string::npos); - REQUIRE(updateResultStr.find("/ver3.0.0.0") != std::string::npos); -} - -TEST_CASE("UpdateFlow_UpdateZip_Exe", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Zip })); - OverrideForShellExecute(context); - OverrideForExtractInstallerFromArchive(context); - OverrideForVerifyAndSetNestedInstaller(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Zip.Query); - context.Args.AddArg(Execution::Args::Type::Silent); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); - std::ifstream updateResultFile(updateResultPath.GetPath()); - REQUIRE(updateResultFile.is_open()); - std::string updateResultStr; - std::getline(updateResultFile, updateResultStr); - REQUIRE(updateResultStr.find("/custom") != std::string::npos); - REQUIRE(updateResultStr.find("/silence") != std::string::npos); - REQUIRE(updateResultStr.find("/ver2.0.0.0") != std::string::npos); -} - -TEST_CASE("UpdateFlow_UpdatePortable", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestPortableInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); - OverrideForPortableInstallFlow(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_Portable_SymlinkCreationFail", "[UpdateFlow][workflow]") -{ - // Update portable with symlink creation failure verify that it succeeds. - TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto PreviousThreadGlobals = context.SetForCurrentThread(); - bool overrideCreateSymlinkStatus = false; - AppInstaller::Filesystem::TestHook_SetCreateSymlinkResult_Override(&overrideCreateSymlinkStatus); - OverridePortableInstaller(context); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); - const auto& targetDirectory = tempDirectory.GetPath(); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); - context.Args.AddArg(Execution::Args::Type::InstallLocation, targetDirectory.u8string()); - context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; - REQUIRE(std::filesystem::exists(portableTargetPath)); - REQUIRE(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); - - // Perform uninstall - std::ostringstream uninstallOutput; - TestContext uninstallContext{ uninstallOutput, std::cin }; - auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); - OverrideForCompositeInstalledSource(uninstallContext, CreateTestSource({ TSR::TestInstaller_Portable })); - uninstallContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); - - UninstallCommand uninstall({}); - uninstall.Execute(uninstallContext); - INFO(uninstallOutput.str()); - - REQUIRE_FALSE(std::filesystem::exists(portableTargetPath)); -} - -TEST_CASE("UpdateFlow_UpdateExeWithUnsupportedArgs", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - TestCommon::TempDirectory tempDirectory("TempDirectory", false); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnsupportedArguments })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnsupportedArguments.Query); - context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); - - UpgradeCommand update({}); - context.SetExecutingCommand(&update); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify unsupported arguments error message is shown - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); - REQUIRE(updateOutput.str().find("-l,--location") != std::string::npos); -} - -TEST_CASE("UpdateFlow_UnknownVersion", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - TestCommon::TempDirectory tempDirectory("TempDirectory", false); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnknownVersion })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnknownVersion.Query); - context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); - - UpgradeCommand update({}); - context.SetExecutingCommand(&update); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify help message is shown the user to use --include-unknown - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionExplanation).get()) != std::string::npos); -} - -TEST_CASE("UpdateFlow_UnknownVersion_IncludeUnknownArg", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - TestCommon::TempDirectory tempDirectory("TempDirectory", false); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnknownVersion })); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnknownVersion.Query); - context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); - context.Args.AddArg(Execution::Args::Type::IncludeUnknown); - - UpgradeCommand update({}); - context.SetExecutingCommand(&update); - update.Execute(context); - INFO(updateOutput.str()); - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_NoArgs_UnknownVersion", "[UpdateFlow][workflow]") -{ - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - - UpgradeCommand update({}); - context.SetExecutingCommand(&update); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify --include-unknown help text is displayed if update is executed with no args and an unknown version package is available for upgrade. - REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); -} - -TEST_CASE("UpdateFlow_IncludeUnknown", "[UpdateFlow][workflow]") -{ - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - context.Args.AddArg(Execution::Args::Type::IncludeUnknown); - - UpgradeCommand update({}); - context.SetExecutingCommand(&update); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify unknown version package is displayed available for upgrade. - REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) == std::string::npos); - REQUIRE(updateOutput.str().find("unknown") != std::string::npos); -} - -TEST_CASE("UpdateFlow_UpdatePortableWithManifest", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestPortableInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); - OverrideForPortableInstallFlow(context); - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("UpdateFlowTest_Portable.yaml").GetPath().u8string()); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_UpdateMsix", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestMsixInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Msix })); - OverrideForMSIX(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is called. - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_UpdateMSStore", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestMSStoreUpdated.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); - OverrideForMSStore(context, true); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_MSStore.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is called. - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); - std::ifstream updateResultFile(updateResultPath.GetPath()); - REQUIRE(updateResultFile.is_open()); - std::string updateResultStr; - std::getline(updateResultFile, updateResultStr); - REQUIRE(updateResultStr.find("9WZDNCRFJ364") != std::string::npos); -} - -TEST_CASE("UpdateFlow_UpdateExeLatestAlreadyInstalled", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LatestInstalled })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LatestInstalled.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); -} - -TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicable", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH); -} - -TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicableSpecificVersion", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); - context.Args.AddArg(Execution::Args::Type::Version, "2.0.0.0"sv); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnology).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); -} - -TEST_CASE("UpdateFlow_UpdateExeWithDifferentInstalledType", "[UpdateFlow][workflow]") -{ - // Tests installer applicability when installed type is different but listed in the manifest - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_DifferentInstallerType })); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_DifferentInstallerType.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is called. - REQUIRE(context.GetTerminationHR() == S_OK); - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_UpdateExeSpecificVersionNotFound", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::Version, "1.2.3.4"sv); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::GetManifestResultVersionNotFound("1.2.3.4"_liv)).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); -} - -TEST_CASE("UpdateFlow_UpdateExeSpecificVersionNotApplicable", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); - // This must be 2.0.0.0 since the version would not be an upgrade otherwise - context.Args.AddArg(Execution::Args::Type::Version, "2.0.0.0"sv); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnology).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); -} - -TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); - TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); - TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); - TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - OverrideForShellExecute(context); - OverrideForMSIX(context); - OverrideForMSStore(context, true); - OverrideForPortableInstall(context); - context.Args.AddArg(Execution::Args::Type::All); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify that --include-unknown help message is displayed. - REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); - REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") == std::string::npos); - - // Verify installers are called. - REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_UpdateAll_IncludeUnknown", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); - TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); - TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); - TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - OverrideForShellExecute(context); - OverrideForMSIX(context); - OverrideForMSStore(context, true); - OverrideForPortableInstall(context); - context.Args.AddArg(Execution::Args::Type::All); - context.Args.AddArg(Execution::Args::Type::IncludeUnknown); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify that --include-unknown help message is NOT displayed and unknown version package is upgraded. - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) == std::string::npos); - REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") != std::string::npos); - - // Verify installers are called. - REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_UpgradeWithDuplicateUpgradeItemsFound", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems })); - // Installer should only be run once since the 2 upgrade items are same. - OverrideForShellExecute(context, 1); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems.Query); - context.Args.AddArg(Execution::Args::Type::All); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify installers are called. - REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_Dependencies", "[UpdateFlow][workflow][dependencies]") -{ - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_Dependencies })); - OverrideForShellExecute(context); - OverrideEnableWindowsFeaturesDependencies(context); - - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_Dependencies.Query);; - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - std::string updateResultStr = updateOutput.str(); - - // Verify dependencies are informed - REQUIRE(updateResultStr.find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); - REQUIRE(updateResultStr.find("PreviewIIS") != std::string::npos); - REQUIRE(updateResultStr.find("Preview VC Runtime") != std::string::npos); -} - -TEST_CASE("UpdateFlow_LicenseAgreement", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LicenseAgreement })); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LicenseAgreement.Query); - context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify agreements are shown - REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); - REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); - - // Verify Installer is called. - REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_LicenseAgreement_NotAccepted", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); - - // Say "No" at the agreements prompt - std::istringstream updateInput{ "n" }; - - std::ostringstream updateOutput; - TestContext context{ updateOutput, updateInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LicenseAgreement })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LicenseAgreement.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify agreements are shown - REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); - REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); - - // Verify Installer is not called. - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); - REQUIRE_FALSE(std::filesystem::exists(updateResultPath.GetPath())); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); -} - -TEST_CASE("UpdateFlow_All_LicenseAgreement", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); - TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); - TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); - TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe_UpgradeUsesAgreements, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix_UpgradeUsesAgreements, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - OverrideForShellExecute(context); - OverrideForMSIX(context); - OverrideForMSStore(context, true); - OverrideForPortableInstall(context); - context.Args.AddArg(Execution::Args::Type::All); - context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify agreements are shown - REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); - REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); - REQUIRE(updateOutput.str().find("Agreement for MSIX") != std::string::npos); - REQUIRE(updateOutput.str().find("This is the agreement for the MSIX") != std::string::npos); - - // Verify installers are called. - REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_All_LicenseAgreement_NotAccepted", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); - TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); - TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); - - // Say "No" at the agreements prompt - std::istringstream updateInput{ "n" }; - - std::ostringstream updateOutput; - TestContext context{ updateOutput, updateInput }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe_UpgradeUsesAgreements, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix_UpgradeUsesAgreements, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - context.Args.AddArg(Execution::Args::Type::All); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify agreements are shown - REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); - REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); - REQUIRE(updateOutput.str().find("Agreement for MSIX") != std::string::npos); - REQUIRE(updateOutput.str().find("This is the agreement for the MSIX") != std::string::npos); - - // Verify installers are not called. - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); - REQUIRE_FALSE(std::filesystem::exists(updateExeResultPath.GetPath())); - REQUIRE_FALSE(std::filesystem::exists(updateMsixResultPath.GetPath())); - REQUIRE_FALSE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_RequireExplicit", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); - TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - - // Msix package has an update that requires explicit upgrade. - // Exe, Portable, MSStore, Zip are also listed with an available upgrade. - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Exe_UnknownVersion, - TSR::TestInstaller_Msix_UpgradeRequiresExplicit, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - TSR::TestInstaller_Zip, - })); - - SECTION("List available upgrades") - { - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // The package that requires explicit upgrade is listed below the header for pinned packages - REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeInstaller") != std::string::npos); - - auto pinnedPackagesHeaderPosition = updateOutput.str().find(Resource::LocString(Resource::String::UpgradeAvailableForPinned)); - auto pinnedPackageLinePosition = updateOutput.str().find("AppInstallerCliTest.TestMsixInstaller"); - REQUIRE(pinnedPackagesHeaderPosition != std::string::npos); - REQUIRE(pinnedPackageLinePosition != std::string::npos); - REQUIRE(pinnedPackagesHeaderPosition < pinnedPackageLinePosition); - REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) == std::string::npos); - } - - SECTION("Upgrade all except pinned") - { - context.Args.AddArg(Args::Type::All); - OverrideForMSStore(context, true); - OverrideForPortableInstall(context); - OverrideForShellExecute(context); - OverrideForExtractInstallerFromArchive(context); - OverrideForVerifyAndSetNestedInstaller(context); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - auto s = updateOutput.str(); - - // Verify message is printed for skipped package - REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) != std::string::npos); - - // Verify package is not installed, but all others are - REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); - REQUIRE(!std::filesystem::exists(updateMsixResultPath.GetPath())); - } - - SECTION("Upgrade explicitly") - { - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); - OverrideForMSIX(context); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); - } - - // Command should always succeed - REQUIRE(context.GetTerminationHR() == S_OK); -} - -TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::Silent); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/update") != std::string::npos); - REQUIRE(installResultStr.find("/ver3.0.0.0") != std::string::npos); -} - -TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable_WithNoUpgrade", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::NoUpgrade); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAlreadyInstalled).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED); -} - -TEST_CASE("InstallFlow_FoundInstalledAndUpgradeNotAvailable", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LatestInstalled })); - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LatestInstalled.Query); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is not called. - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); -} - -TEST_CASE("UpdateFlow_UpdateAll_ForwardArgs", "[UpdateFlow][workflow]") -{ - TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); - TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); - TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); - TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ - TSR::TestInstaller_Exe, - TSR::TestInstaller_Msix, - TSR::TestInstaller_MSStore, - TSR::TestInstaller_Portable, - })); - OverrideForShellExecute(context); - OverrideForMSIX(context); - OverrideForMSStore(context, true); - OverrideForPortableInstall(context); - context.Args.AddArg(Execution::Args::Type::All); - context.Args.AddArg(Execution::Args::Type::Silent); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify installers are called with the silent flags - REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); - std::ifstream updateExeResultFile(updateExeResultPath.GetPath()); - std::string updateExeResultStr; - std::getline(updateExeResultFile, updateExeResultStr); - REQUIRE(updateExeResultStr.find("/silence") != std::string::npos); - - REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); - REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_UpdateMultiple", "[UpdateFlow][workflow][MultiQuery]") -{ - TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); - TestCommon::TempFile msixUpdateResultPath("TestMsixInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix })); - OverrideForShellExecute(context); - OverrideForMSIX(context); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - // Verify Installers are called called. - REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); - REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); -} - -TEST_CASE("UpdateFlow_UpdateMultiple_NotAllFound", "[UpdateFlow][workflow][MultiQuery]") -{ - TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); - context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); - - SECTION("Ignore unavailable") - { - OverrideForShellExecute(context); - context.Args.AddArg(Execution::Args::Type::IgnoreUnavailable); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - REQUIRE(!context.IsTerminated()); - REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); - } - SECTION("Don't ignore unavailable") - { - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); - } -} - -TEST_CASE("UpdateFlow_UpdateWithReboot", "[UpdateFlow][workflow][reboot]") -{ - TestCommon::TestUserSettings testSettings; - - std::ostringstream updateOutput; - TestContext context{ updateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_ExpectedReturnCodes })); - - context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_ExpectedReturnCodes.Query); - context.Args.AddArg(Execution::Args::Type::AllowReboot); - - context.Override({ AppInstaller::CLI::Workflow::ShellExecuteInstallImpl, [&](TestContext& context) - { - // APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH (not treated as an installer error) - context.Add(9); - } }); - - SECTION("Reboot success") - { - TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(true); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - REQUIRE_FALSE(context.IsTerminated()); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); - REQUIRE_FALSE(updateOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); - } - SECTION("Reboot failed") - { - TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(false); - - UpgradeCommand update({}); - update.Execute(context); - INFO(updateOutput.str()); - - REQUIRE_FALSE(context.IsTerminated()); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); - REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestHooks.h" +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility::literals; + +TEST_CASE("UpdateFlow_UpdateWithManifest", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("UpdateFlowTest_Exe.yaml").GetPath().u8string()); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); + std::ifstream updateResultFile(updateResultPath.GetPath()); + REQUIRE(updateResultFile.is_open()); + std::string updateResultStr; + std::getline(updateResultFile, updateResultStr); + REQUIRE(updateResultStr.find("/update") != std::string::npos); + REQUIRE(updateResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("UpdateFlow_UpdateWithManifestMSStore", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestMSStoreUpdated.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); + OverrideForMSStore(context, true); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_MSStore.yaml").GetPath().u8string()); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); + std::ifstream updateResultFile(updateResultPath.GetPath()); + REQUIRE(updateResultFile.is_open()); + std::string updateResultStr; + std::getline(updateResultFile, updateResultStr); + REQUIRE(updateResultStr.find("9WZDNCRFJ364") != std::string::npos); +} + +TEST_CASE("UpdateFlow_UpdateWithManifestAppNotInstalled", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({})); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallerArgTest_Inno_NoSwitches.yaml").GetPath().u8string()); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::NoInstalledPackageFound).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND); +} + +TEST_CASE("UpdateFlow_UpdateWithManifestVersionAlreadyInstalled", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); +} + +TEST_CASE("UpdateFlow_UpdateExe", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::Silent); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); + std::ifstream updateResultFile(updateResultPath.GetPath()); + REQUIRE(updateResultFile.is_open()); + std::string updateResultStr; + std::getline(updateResultFile, updateResultStr); + REQUIRE(updateResultStr.find("/update") != std::string::npos); + REQUIRE(updateResultStr.find("/silence") != std::string::npos); + REQUIRE(updateResultStr.find("/ver3.0.0.0") != std::string::npos); +} + +TEST_CASE("UpdateFlow_UpdateZip_Exe", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Zip })); + OverrideForShellExecute(context); + OverrideForExtractInstallerFromArchive(context); + OverrideForVerifyAndSetNestedInstaller(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Zip.Query); + context.Args.AddArg(Execution::Args::Type::Silent); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); + std::ifstream updateResultFile(updateResultPath.GetPath()); + REQUIRE(updateResultFile.is_open()); + std::string updateResultStr; + std::getline(updateResultFile, updateResultStr); + REQUIRE(updateResultStr.find("/custom") != std::string::npos); + REQUIRE(updateResultStr.find("/silence") != std::string::npos); + REQUIRE(updateResultStr.find("/ver2.0.0.0") != std::string::npos); +} + +TEST_CASE("UpdateFlow_UpdatePortable", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestPortableInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); + OverrideForPortableInstallFlow(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_Portable_SymlinkCreationFail", "[UpdateFlow][workflow]") +{ + // Update portable with symlink creation failure verify that it succeeds. + TestCommon::TempDirectory tempDirectory("TestPortableInstallRoot", false); + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto PreviousThreadGlobals = context.SetForCurrentThread(); + bool overrideCreateSymlinkStatus = false; + AppInstaller::Filesystem::TestHook_SetCreateSymlinkResult_Override(&overrideCreateSymlinkStatus); + OverridePortableInstaller(context); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); + const auto& targetDirectory = tempDirectory.GetPath(); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); + context.Args.AddArg(Execution::Args::Type::InstallLocation, targetDirectory.u8string()); + context.Args.AddArg(Execution::Args::Type::InstallScope, "user"sv); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + const auto& portableTargetPath = targetDirectory / "AppInstallerTestExeInstaller.exe"; + REQUIRE(std::filesystem::exists(portableTargetPath)); + REQUIRE(AppInstaller::Registry::Environment::PathVariable(AppInstaller::Manifest::ScopeEnum::User).Contains(targetDirectory)); + + // Perform uninstall + std::ostringstream uninstallOutput; + TestContext uninstallContext{ uninstallOutput, std::cin }; + auto previousThreadGlobals = uninstallContext.SetForCurrentThread(); + OverrideForCompositeInstalledSource(uninstallContext, CreateTestSource({ TSR::TestInstaller_Portable })); + uninstallContext.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Portable.Query); + + UninstallCommand uninstall({}); + uninstall.Execute(uninstallContext); + INFO(uninstallOutput.str()); + + REQUIRE_FALSE(std::filesystem::exists(portableTargetPath)); +} + +TEST_CASE("UpdateFlow_UpdateExeWithUnsupportedArgs", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnsupportedArguments })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnsupportedArguments.Query); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + + UpgradeCommand update({}); + context.SetExecutingCommand(&update); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify unsupported arguments error message is shown + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT); + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UnsupportedArgument).get()) != std::string::npos); + REQUIRE(updateOutput.str().find("-l,--location") != std::string::npos); +} + +TEST_CASE("UpdateFlow_UnknownVersion", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnknownVersion })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnknownVersion.Query); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + + UpgradeCommand update({}); + context.SetExecutingCommand(&update); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify help message is shown the user to use --include-unknown + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionExplanation).get()) != std::string::npos); +} + +TEST_CASE("UpdateFlow_UnknownVersion_IncludeUnknownArg", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + TestCommon::TempDirectory tempDirectory("TempDirectory", false); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UnknownVersion })); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UnknownVersion.Query); + context.Args.AddArg(Execution::Args::Type::InstallLocation, tempDirectory); + context.Args.AddArg(Execution::Args::Type::IncludeUnknown); + + UpgradeCommand update({}); + context.SetExecutingCommand(&update); + update.Execute(context); + INFO(updateOutput.str()); + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_NoArgs_UnknownVersion", "[UpdateFlow][workflow]") +{ + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + + UpgradeCommand update({}); + context.SetExecutingCommand(&update); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify --include-unknown help text is displayed if update is executed with no args and an unknown version package is available for upgrade. + REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); +} + +TEST_CASE("UpdateFlow_IncludeUnknown", "[UpdateFlow][workflow]") +{ + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + context.Args.AddArg(Execution::Args::Type::IncludeUnknown); + + UpgradeCommand update({}); + context.SetExecutingCommand(&update); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify unknown version package is displayed available for upgrade. + REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) == std::string::npos); + REQUIRE(updateOutput.str().find("unknown") != std::string::npos); +} + +TEST_CASE("UpdateFlow_UpdatePortableWithManifest", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestPortableInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Portable })); + OverrideForPortableInstallFlow(context); + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("UpdateFlowTest_Portable.yaml").GetPath().u8string()); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateMsix", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestMsixInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Msix })); + OverrideForMSIX(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is called. + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateMSStore", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestMSStoreUpdated.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_MSStore })); + OverrideForMSStore(context, true); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_MSStore.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is called. + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); + std::ifstream updateResultFile(updateResultPath.GetPath()); + REQUIRE(updateResultFile.is_open()); + std::string updateResultStr; + std::getline(updateResultFile, updateResultStr); + REQUIRE(updateResultStr.find("9WZDNCRFJ364") != std::string::npos); +} + +TEST_CASE("UpdateFlow_UpdateExeLatestAlreadyInstalled", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LatestInstalled })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LatestInstalled.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); +} + +TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicable", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnologyInNewerVersions).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH); +} + +TEST_CASE("UpdateFlow_UpdateExeInstallerTypeNotApplicableSpecificVersion", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); + context.Args.AddArg(Execution::Args::Type::Version, "2.0.0.0"sv); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnology).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); +} + +TEST_CASE("UpdateFlow_UpdateExeWithDifferentInstalledType", "[UpdateFlow][workflow]") +{ + // Tests installer applicability when installed type is different but listed in the manifest + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_DifferentInstallerType })); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_DifferentInstallerType.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is called. + REQUIRE(context.GetTerminationHR() == S_OK); + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateExeSpecificVersionNotFound", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::Version, "1.2.3.4"sv); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::GetManifestResultVersionNotFound("1.2.3.4"_liv)).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND); +} + +TEST_CASE("UpdateFlow_UpdateExeSpecificVersionNotApplicable", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_IncompatibleInstallerType })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_IncompatibleInstallerType.Query); + // This must be 2.0.0.0 since the version would not be an upgrade otherwise + context.Args.AddArg(Execution::Args::Type::Version, "2.0.0.0"sv); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeDifferentInstallTechnology).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); +} + +TEST_CASE("UpdateFlow_UpdateAllApplicable", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); + TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); + TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); + TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + OverrideForShellExecute(context); + OverrideForMSIX(context); + OverrideForMSStore(context, true); + OverrideForPortableInstall(context); + context.Args.AddArg(Execution::Args::Type::All); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify that --include-unknown help message is displayed. + REQUIRE(updateOutput.str().find(Resource::String::UpgradeUnknownVersionCount(1)) != std::string::npos); + REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") == std::string::npos); + + // Verify installers are called. + REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateAll_IncludeUnknown", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); + TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); + TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); + TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + OverrideForShellExecute(context); + OverrideForMSIX(context); + OverrideForMSStore(context, true); + OverrideForPortableInstall(context); + context.Args.AddArg(Execution::Args::Type::All); + context.Args.AddArg(Execution::Args::Type::IncludeUnknown); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify that --include-unknown help message is NOT displayed and unknown version package is upgraded. + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::UpgradeUnknownVersionCount).get()) == std::string::npos); + REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeUnknownVersion") != std::string::npos); + + // Verify installers are called. + REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpgradeWithDuplicateUpgradeItemsFound", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems })); + // Installer should only be run once since the 2 upgrade items are same. + OverrideForShellExecute(context, 1); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems.Query); + context.Args.AddArg(Execution::Args::Type::All); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify installers are called. + REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_Dependencies", "[UpdateFlow][workflow][dependencies]") +{ + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_Dependencies })); + OverrideForShellExecute(context); + OverrideEnableWindowsFeaturesDependencies(context); + + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_Dependencies.Query);; + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + std::string updateResultStr = updateOutput.str(); + + // Verify dependencies are informed + REQUIRE(updateResultStr.find(Resource::LocString(Resource::String::PackageRequiresDependencies).get()) != std::string::npos); + REQUIRE(updateResultStr.find("PreviewIIS") != std::string::npos); + REQUIRE(updateResultStr.find("Preview VC Runtime") != std::string::npos); +} + +TEST_CASE("UpdateFlow_LicenseAgreement", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LicenseAgreement })); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LicenseAgreement.Query); + context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify agreements are shown + REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); + REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); + + // Verify Installer is called. + REQUIRE(std::filesystem::exists(updateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_LicenseAgreement_NotAccepted", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateResultPath("TestExeInstalled.txt"); + + // Say "No" at the agreements prompt + std::istringstream updateInput{ "n" }; + + std::ostringstream updateOutput; + TestContext context{ updateOutput, updateInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LicenseAgreement })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LicenseAgreement.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify agreements are shown + REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); + REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); + + // Verify Installer is not called. + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); + REQUIRE_FALSE(std::filesystem::exists(updateResultPath.GetPath())); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::PackageAgreementsNotAgreedTo).get()) != std::string::npos); +} + +TEST_CASE("UpdateFlow_All_LicenseAgreement", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); + TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); + TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); + TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe_UpgradeUsesAgreements, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix_UpgradeUsesAgreements, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + OverrideForShellExecute(context); + OverrideForMSIX(context); + OverrideForMSStore(context, true); + OverrideForPortableInstall(context); + context.Args.AddArg(Execution::Args::Type::All); + context.Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify agreements are shown + REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); + REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); + REQUIRE(updateOutput.str().find("Agreement for MSIX") != std::string::npos); + REQUIRE(updateOutput.str().find("This is the agreement for the MSIX") != std::string::npos); + + // Verify installers are called. + REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_All_LicenseAgreement_NotAccepted", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); + TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); + TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); + + // Say "No" at the agreements prompt + std::istringstream updateInput{ "n" }; + + std::ostringstream updateOutput; + TestContext context{ updateOutput, updateInput }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe_UpgradeUsesAgreements, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix_UpgradeUsesAgreements, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + context.Args.AddArg(Execution::Args::Type::All); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify agreements are shown + REQUIRE(updateOutput.str().find("Agreement for EXE") != std::string::npos); + REQUIRE(updateOutput.str().find("This is the agreement for the EXE") != std::string::npos); + REQUIRE(updateOutput.str().find("Agreement for MSIX") != std::string::npos); + REQUIRE(updateOutput.str().find("This is the agreement for the MSIX") != std::string::npos); + + // Verify installers are not called. + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED); + REQUIRE_FALSE(std::filesystem::exists(updateExeResultPath.GetPath())); + REQUIRE_FALSE(std::filesystem::exists(updateMsixResultPath.GetPath())); + REQUIRE_FALSE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_RequireExplicit", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); + TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + + // Msix package has an update that requires explicit upgrade. + // Exe, Portable, MSStore, Zip are also listed with an available upgrade. + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Exe_UnknownVersion, + TSR::TestInstaller_Msix_UpgradeRequiresExplicit, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + TSR::TestInstaller_Zip, + })); + + SECTION("List available upgrades") + { + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // The package that requires explicit upgrade is listed below the header for pinned packages + REQUIRE(updateOutput.str().find("AppInstallerCliTest.TestExeInstaller") != std::string::npos); + + auto pinnedPackagesHeaderPosition = updateOutput.str().find(Resource::LocString(Resource::String::UpgradeAvailableForPinned)); + auto pinnedPackageLinePosition = updateOutput.str().find("AppInstallerCliTest.TestMsixInstaller"); + REQUIRE(pinnedPackagesHeaderPosition != std::string::npos); + REQUIRE(pinnedPackageLinePosition != std::string::npos); + REQUIRE(pinnedPackagesHeaderPosition < pinnedPackageLinePosition); + REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) == std::string::npos); + } + + SECTION("Upgrade all except pinned") + { + context.Args.AddArg(Args::Type::All); + OverrideForMSStore(context, true); + OverrideForPortableInstall(context); + OverrideForShellExecute(context); + OverrideForExtractInstallerFromArchive(context); + OverrideForVerifyAndSetNestedInstaller(context); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + auto s = updateOutput.str(); + + // Verify message is printed for skipped package + REQUIRE(updateOutput.str().find(Resource::String::UpgradeRequireExplicitCount(1)) != std::string::npos); + + // Verify package is not installed, but all others are + REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); + REQUIRE(!std::filesystem::exists(updateMsixResultPath.GetPath())); + } + + SECTION("Upgrade explicitly") + { + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Msix.Query); + OverrideForMSIX(context); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); + } + + // Command should always succeed + REQUIRE(context.GetTerminationHR() == S_OK); +} + +TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::Silent); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/update") != std::string::npos); + REQUIRE(installResultStr.find("/ver3.0.0.0") != std::string::npos); +} + +TEST_CASE("InstallFlow_FoundInstalledAndUpgradeAvailable_WithNoUpgrade", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::NoUpgrade); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::PackageAlreadyInstalled).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED); +} + +TEST_CASE("InstallFlow_FoundInstalledAndUpgradeNotAvailable", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_LatestInstalled })); + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_LatestInstalled.Query); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is not called. + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFound).get()) != std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::UpdateNoPackagesFoundReason).get()) != std::string::npos); + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE); +} + +TEST_CASE("UpdateFlow_UpdateAll_ForwardArgs", "[UpdateFlow][workflow]") +{ + TestCommon::TempFile updateExeResultPath("TestExeInstalled.txt"); + TestCommon::TempFile updateMsixResultPath("TestMsixInstalled.txt"); + TestCommon::TempFile updateMSStoreResultPath("TestMSStoreUpdated.txt"); + TestCommon::TempFile updatePortableResultPath("TestPortableInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ + TSR::TestInstaller_Exe, + TSR::TestInstaller_Msix, + TSR::TestInstaller_MSStore, + TSR::TestInstaller_Portable, + })); + OverrideForShellExecute(context); + OverrideForMSIX(context); + OverrideForMSStore(context, true); + OverrideForPortableInstall(context); + context.Args.AddArg(Execution::Args::Type::All); + context.Args.AddArg(Execution::Args::Type::Silent); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify installers are called with the silent flags + REQUIRE(std::filesystem::exists(updateExeResultPath.GetPath())); + std::ifstream updateExeResultFile(updateExeResultPath.GetPath()); + std::string updateExeResultStr; + std::getline(updateExeResultFile, updateExeResultStr); + REQUIRE(updateExeResultStr.find("/silence") != std::string::npos); + + REQUIRE(std::filesystem::exists(updateMsixResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updateMSStoreResultPath.GetPath())); + REQUIRE(std::filesystem::exists(updatePortableResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateMultiple", "[UpdateFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); + TestCommon::TempFile msixUpdateResultPath("TestMsixInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe, TSR::TestInstaller_Msix })); + OverrideForShellExecute(context); + OverrideForMSIX(context); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + // Verify Installers are called called. + REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); + REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); +} + +TEST_CASE("UpdateFlow_UpdateMultiple_NotAllFound", "[UpdateFlow][workflow][MultiQuery]") +{ + TestCommon::TempFile exeUpdateResultPath("TestExeInstalled.txt"); + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe })); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Exe.Query); + context.Args.AddArg(Execution::Args::Type::MultiQuery, TSR::TestInstaller_Msix.Query); + + SECTION("Ignore unavailable") + { + OverrideForShellExecute(context); + context.Args.AddArg(Execution::Args::Type::IgnoreUnavailable); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE(!context.IsTerminated()); + REQUIRE(std::filesystem::exists(exeUpdateResultPath.GetPath())); + } + SECTION("Don't ignore unavailable") + { + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE_TERMINATED_WITH(context, APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE); + } +} + +TEST_CASE("UpdateFlow_UpdateWithReboot", "[UpdateFlow][workflow][reboot]") +{ + TestCommon::TestUserSettings testSettings; + + std::ostringstream updateOutput; + TestContext context{ updateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideForCompositeInstalledSource(context, CreateTestSource({ TSR::TestInstaller_Exe_ExpectedReturnCodes })); + + context.Args.AddArg(Execution::Args::Type::Query, TSR::TestInstaller_Exe_ExpectedReturnCodes.Query); + context.Args.AddArg(Execution::Args::Type::AllowReboot); + + context.Override({ AppInstaller::CLI::Workflow::ShellExecuteInstallImpl, [&](TestContext& context) + { + // APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH (not treated as an installer error) + context.Add(9); + } }); + + SECTION("Reboot success") + { + TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(true); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE_FALSE(context.IsTerminated()); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); + REQUIRE_FALSE(updateOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); + } + SECTION("Reboot failed") + { + TestHook::SetInitiateRebootResult_Override initiateRebootResultOverride(false); + + UpgradeCommand update({}); + update.Execute(context); + INFO(updateOutput.str()); + + REQUIRE_FALSE(context.IsTerminated()); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::InitiatingReboot).get()) != std::string::npos); + REQUIRE(updateOutput.str().find(Resource::LocString(Resource::String::FailedToInitiateReboot).get()) != std::string::npos); + } +} diff --git a/src/AppInstallerCLITests/UserSettings.cpp b/src/AppInstallerCLITests/UserSettings.cpp index b1b28519b3..c4d63e0905 100644 --- a/src/AppInstallerCLITests/UserSettings.cpp +++ b/src/AppInstallerCLITests/UserSettings.cpp @@ -318,8 +318,8 @@ TEST_CASE("SettingLoggingLevelPreference", "[settings]") REQUIRE(userSettingTest.Get() == Level::Info); REQUIRE(userSettingTest.GetWarnings().size() == 1); } -} - +} + TEST_CASE("SettingLoggingFileNameStrategy", "[settings]") { auto again = DeleteUserSettingsFiles(); @@ -392,14 +392,14 @@ TEST_CASE("SettingAutoUpdateIntervalInMinutes", "[settings]") constexpr static auto cinq = 5min; constexpr static auto cero = 0min; - constexpr static auto threehundred = 300min; - - std::chrono::minutes defaultAutoUpdateTime{}; - + constexpr static auto threehundred = 300min; + + std::chrono::minutes defaultAutoUpdateTime{}; + { SetSetting(Stream::PrimaryUserSettings, ""); UserSettingsTest userSettingTest; - defaultAutoUpdateTime = userSettingTest.Get(); + defaultAutoUpdateTime = userSettingTest.Get(); } SECTION("Valid value 0") @@ -590,10 +590,10 @@ TEST_CASE("SettingsDownloadDefaultDirectory", "[settings]") REQUIRE(userSettingTest.Get() == "C:/Foo/Bar"); REQUIRE(userSettingTest.GetWarnings().size() == 0); } -} - -TEST_CASE("SettingsConfigureDefaultModuleRoot", "[settings]") -{ +} + +TEST_CASE("SettingsConfigureDefaultModuleRoot", "[settings]") +{ auto again = DeleteUserSettingsFiles(); SECTION("Valid path") @@ -604,22 +604,22 @@ TEST_CASE("SettingsConfigureDefaultModuleRoot", "[settings]") REQUIRE(userSettingTest.Get() == "C:/Foo/Bar"); REQUIRE(userSettingTest.GetWarnings().size() == 0); - } -} - -TEST_CASE("SettingsArchiveExtractionMethod", "[settings]") + } +} + +TEST_CASE("SettingsArchiveExtractionMethod", "[settings]") { - auto again = DeleteUserSettingsFiles(); - - SECTION("Shell api") + auto again = DeleteUserSettingsFiles(); + + SECTION("Shell api") { std::string_view json = R"({ "installBehavior": { "archiveExtractionMethod": "shellApi" } })"; SetSetting(Stream::PrimaryUserSettings, json); UserSettingsTest userSettingTest; REQUIRE(userSettingTest.Get() == AppInstaller::Archive::ExtractionMethod::ShellApi); - } - SECTION("Shell api") + } + SECTION("Shell api") { std::string_view json = R"({ "installBehavior": { "archiveExtractionMethod": "tar" } })"; SetSetting(Stream::PrimaryUserSettings, json); diff --git a/src/AppInstallerCLITests/Versions.cpp b/src/AppInstallerCLITests/Versions.cpp index ec91271e60..48fa3549b9 100644 --- a/src/AppInstallerCLITests/Versions.cpp +++ b/src/AppInstallerCLITests/Versions.cpp @@ -1,472 +1,472 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace AppInstaller; -using namespace AppInstaller::Utility; - - -TEST_CASE("VersionParse", "[versions]") -{ - Version version("1.2.3.4-alpha"); - const auto& parts = version.GetParts(); - REQUIRE(parts.size() == 4); - for (size_t i = 0; i < parts.size(); ++i) - { - INFO(i); - REQUIRE(parts[i].Integer == static_cast(i + 1)); - if (i != 3) - { - REQUIRE(parts[i].Other == ""); - } - else - { - REQUIRE(parts[i].Other == "-alpha"); - } - } -} - -TEST_CASE("VersionParsePlusDash", "[versions]") -{ - Version version("1.2.3.4-alpha", ".-"); - const auto& parts = version.GetParts(); - REQUIRE(parts.size() == 5); - for (size_t i = 0; i < 4; ++i) - { - INFO(i); - REQUIRE(parts[i].Integer == static_cast(i + 1)); - REQUIRE(parts[i].Other == ""); - } - REQUIRE(parts[4].Other == "alpha"); -} - -TEST_CASE("VersionParseWithWhitespace", "[versions]") -{ - Version version("1. 2.3 . 4 "); - const auto& parts = version.GetParts(); - REQUIRE(parts.size() == 4); - for (size_t i = 0; i < parts.size(); ++i) - { - INFO(i); - REQUIRE(parts[i].Integer == static_cast(i + 1)); - REQUIRE(parts[i].Other == ""); - } -} - -TEST_CASE("VersionParseWithPreamble", "[versions]") -{ - Version version("v1.2.3.4"); - const auto& parts = version.GetParts(); - REQUIRE(parts.size() == 4); - for (size_t i = 0; i < parts.size(); ++i) - { - INFO(i); - REQUIRE(parts[i].Integer == static_cast(i + 1)); - REQUIRE(parts[i].Other == ""); - } -} - -TEST_CASE("VersionParseCorner", "[versions]") -{ - Version version1(""); - auto parts = version1.GetParts(); - REQUIRE(parts.size() == 0); - - Version version2("."); - parts = version2.GetParts(); - REQUIRE(parts.size() == 0); - - Version version3(".0"); - parts = version3.GetParts(); - REQUIRE(parts.size() == 0); - - Version version4(".1"); - parts = version4.GetParts(); - REQUIRE(parts.size() == 2); - REQUIRE(parts[0].Integer == 0); - REQUIRE(parts[0].Other == ""); - REQUIRE(parts[1].Integer == 1); - REQUIRE(parts[1].Other == ""); - - Version version5("version"); - parts = version5.GetParts(); - REQUIRE(parts.size() == 1); - REQUIRE(parts[0].Integer == 0); - REQUIRE(parts[0].Other == "version"); - - Version version6(". 1 "); - parts = version6.GetParts(); - REQUIRE(parts.size() == 2); - REQUIRE(parts[0].Integer == 0); - REQUIRE(parts[0].Other == ""); - REQUIRE(parts[1].Integer == 1); - REQUIRE(parts[1].Other == ""); - - Version version7("v1.2a"); - parts = version7.GetParts(); - REQUIRE(parts.size() == 2); - REQUIRE(parts[0].Integer == 1); - REQUIRE(parts[0].Other == ""); - REQUIRE(parts[1].Integer == 2); - REQUIRE(parts[1].Other == "a"); -} - -void RequireLessThan(std::string_view a, std::string_view b) -{ - Version vA{ std::string(a) }; - Version vB{ std::string(b) }; - - REQUIRE(vA < vB); - REQUIRE_FALSE(vB < vA); - REQUIRE(vA <= vB); - REQUIRE_FALSE(vB <= vA); - REQUIRE(vB > vA); - REQUIRE_FALSE(vA > vB); - REQUIRE(vB >= vA); - REQUIRE_FALSE(vA >= vB); - REQUIRE_FALSE(vA == vB); - REQUIRE(vA != vB); -} - -void RequireEqual(std::string_view a, std::string_view b) -{ - Version vA{ std::string(a) }; - Version vB{ std::string(b) }; - - REQUIRE(vA == vB); - REQUIRE_FALSE(vA != vB); - REQUIRE(vA <= vB); - REQUIRE(vA >= vB); - REQUIRE_FALSE(vA < vB); - REQUIRE_FALSE(vA > vB); -} - -TEST_CASE("VersionCompare", "[versions]") -{ - RequireLessThan("1", "2"); - RequireLessThan("1.0.0", "2.0.0"); - RequireLessThan("0.0.1", "0.0.2"); - RequireLessThan("0.0.1-alpha", "0.0.2-alpha"); - RequireLessThan("0.0.1-beta", "0.0.2-alpha"); - RequireLessThan("0.0.1-beta", "0.0.2-alpha"); - RequireLessThan("13.9.8", "14.1"); - - // Ensure that versions with non-digit characters in their parts are sorted correctly - RequireLessThan("1-rc", "1"); - RequireLessThan("1.2-rc", "1.2"); - RequireLessThan("1.0-rc", "1.0"); - RequireLessThan("1.0.0-rc", "1"); - RequireLessThan("22.0.0-rc.1", "22.0.0"); - RequireLessThan("22.0.0-rc.1", "22.0.0.1"); - RequireLessThan("22.0.0-rc.1", "22.0.0.1-rc"); - - // Ensure that Sub-RC versions are sorted correctly - RequireLessThan("22.0.0-rc.1", "22.0.0-rc.1.1"); - RequireLessThan("22.0.0-rc.1.1", "22.0.0-rc.1.2"); - RequireLessThan("22.0.0-rc.1.2", "22.0.0-rc.2"); - - RequireEqual("1.0", "1.0.0"); - - // Ensure that integers are parsed correctly when there is a leading zero - RequireEqual("1.2.00.3", "1.2.0.3"); - RequireEqual("1.2.003.4", "1.2.3.4"); - RequireEqual("01.02.03.04", "1.2.3.4"); - RequireEqual("1.2.03-beta", "1.2.3-beta"); - - // Ensure whitespace doesn't affect equality - RequireEqual("1.0", "1.0 "); - RequireEqual("1.0", "1. 0"); - RequireEqual("1.0", "1.0."); - - // Ensure versions with preambles are sorted correctly - RequireEqual("1.0", "Version 1.0"); - RequireEqual("foo1", "bar1"); - RequireLessThan("v0.0.1", "0.0.2"); - RequireLessThan("v0.0.1", "v0.0.2"); - RequireLessThan("1.a2", "1.b1"); - RequireLessThan("alpha", "beta"); -} - -TEST_CASE("VersionAndChannelSort", "[versions]") -{ - std::vector sortedList = - { - { Version("15.0.0"), Channel("") }, - { Version("14.0.0"), Channel("") }, - { Version("13.2.1-bugfix"), Channel("") }, - { Version("13.2.0"), Channel("") }, - { Version("13.2.0-rc"), Channel("") }, - { Version("13.0.0"), Channel("") }, - { Version("16.0.0"), Channel("alpha") }, - { Version("15.8.0"), Channel("alpha") }, - { Version("15.1.0"), Channel("beta") }, - }; - - std::vector reorderList = { 4, 2, 1, 7, 6, 3, 8, 5, 0 }; - REQUIRE(sortedList.size() == reorderList.size()); - - std::vector jumbledList; - for (auto i : reorderList) - { - jumbledList.emplace_back(sortedList[i]); - } - - std::sort(jumbledList.begin(), jumbledList.end()); - - for (size_t i = 0; i < jumbledList.size(); ++i) - { - const VersionAndChannel& sortedVAC = sortedList[i]; - const VersionAndChannel& jumbledVAC = jumbledList[i]; - - INFO(i); - REQUIRE(sortedVAC.GetVersion().ToString() == jumbledVAC.GetVersion().ToString()); - REQUIRE(sortedVAC.GetChannel().ToString() == jumbledVAC.GetChannel().ToString()); - } -} - -TEST_CASE("MinOsVersion_Check", "[versions]") -{ - // Just verify that we are greater than Win 7 and less than far future Win 10. - // Unfortunately, an unmanifested process will also pass these validations, - // but an unmanifested process also can't use Windows APIs to determine the actual version. - REQUIRE(Runtime::IsCurrentOSVersionGreaterThanOrEqual(Version("6.1"))); - REQUIRE(!Runtime::IsCurrentOSVersionGreaterThanOrEqual(Version("10.0.65535"))); -} - -TEST_CASE("VersionLatest", "[versions]") -{ - REQUIRE(Version::CreateLatest().IsLatest()); - REQUIRE(Version("latest").IsLatest()); - REQUIRE(Version("LATEST").IsLatest()); - REQUIRE(!Version("1.0").IsLatest()); - - RequireLessThan("1.0", "latest"); - RequireLessThan("100", "latest"); - RequireLessThan("943849587389754876.1", "latest"); - - RequireEqual("latest", "LATEST"); -} - -TEST_CASE("VersionUnknown", "[versions]") -{ - REQUIRE(Version::CreateUnknown().IsUnknown()); - REQUIRE(Version("unknown").IsUnknown()); - REQUIRE(Version("UNKNOWN").IsUnknown()); - REQUIRE(!Version("1.0").IsUnknown()); - - RequireLessThan("unknown", "1.0"); - RequireLessThan("unknown", "1.fork"); - - RequireEqual("unknown", "UNKNOWN"); -} - -TEST_CASE("VersionUnknownLessThanLatest", "[versions]") -{ - REQUIRE(Version::CreateUnknown() < Version::CreateLatest()); -} - -TEST_CASE("VersionIsEmpty", "[versions]") -{ - REQUIRE(Version{}.IsEmpty()); - REQUIRE(Version{""}.IsEmpty()); - REQUIRE(!Version{"1"}.IsEmpty()); - REQUIRE(!Version{"0"}.IsEmpty()); - - Version v{ "1" }; - REQUIRE(!v.IsEmpty()); - v.Assign(""); - REQUIRE(v.IsEmpty()); -} - -TEST_CASE("VersionPartAt", "[versions]") -{ - REQUIRE(Version{}.PartAt(0).Integer == 0); - REQUIRE(Version{"1"}.PartAt(0).Integer == 1); - REQUIRE(Version{"1"}.PartAt(1).Integer == 0); - REQUIRE(Version{"1"}.PartAt(9999).Integer == 0); -} - -TEST_CASE("UInt64Version_Success_FourParts", "[versions]") -{ - Version expectedVersion("1.2.3.4"); - UInt64Version versionNumberFromNumber(0x0001000200030004); - UInt64Version versionNumberFromString("1.2.3.4"); - REQUIRE(expectedVersion == versionNumberFromNumber); - REQUIRE(expectedVersion == versionNumberFromString); - REQUIRE(expectedVersion.ToString() == versionNumberFromNumber.ToString()); - REQUIRE(expectedVersion.ToString() == versionNumberFromString.ToString()); -} - -TEST_CASE("UInt64Version_Success_LessThanFourParts", "[versions]") -{ - UInt64Version versionNumberFromNumber(0x0001000200030000); - UInt64Version versionNumberFromString("1.2.3"); - REQUIRE(versionNumberFromNumber == versionNumberFromString); -} - -TEST_CASE("UInt64Version_Success_NoOverflow", "[versions]") -{ - REQUIRE_NOTHROW(UInt64Version("65535.65535.65535.65535")); // 65535 => 0xffff - REQUIRE_NOTHROW(UInt64Version(0xffffffffffffffff)); -} - -TEST_CASE("UInt64Version_Fail_Overflow", "[versions]") -{ - REQUIRE_THROWS(UInt64Version("1.0.0.65536")); // 65536 => 0x10000 -} - -TEST_CASE("UInt64Version_Fail_MoreThanFourParts", "[versions]") -{ - REQUIRE_THROWS(UInt64Version("1.0.0.0.1")); -} - -TEST_CASE("UInt64Version_Fail_NonNumeric", "[versions]") -{ - REQUIRE_THROWS(UInt64Version("1.0.0.a")); -} - -TEST_CASE("ApproximateVersionParse", "[versions]") -{ - Version v1_0{ "1.0" }; - Version v1_0_LessThan{ v1_0, Version::ApproximateComparator::LessThan }; - Version v1_0_GreaterThan{ v1_0, Version::ApproximateComparator::GreaterThan }; - - Version v1_0_LessThanFromString = Version{ "< 1.0" }; - Version v1_0_GreaterThanFromString = Version{ "> 1.0" }; - - REQUIRE_FALSE(v1_0.IsApproximate()); - REQUIRE(v1_0_LessThanFromString.IsApproximate()); - REQUIRE(v1_0_GreaterThanFromString.IsApproximate()); - - REQUIRE(v1_0_LessThan == v1_0_LessThanFromString); - REQUIRE(v1_0_GreaterThan == v1_0_GreaterThanFromString); - - REQUIRE_THROWS(Version{ "< Unknown" }); - REQUIRE_THROWS(Version{ v1_0_LessThan, Version::ApproximateComparator::LessThan }); - REQUIRE_THROWS(Version{ Version::CreateUnknown(), Version::ApproximateComparator::LessThan }); -} - -TEST_CASE("ApproximateVersionCompare", "[versions]") -{ - RequireEqual("< 1.0", "< 1.0"); - RequireEqual("< 1.0", "< 1.0.0"); - RequireEqual("> 1.0", "> 1.0"); - RequireEqual("> 1.0", "> 1.0.0"); - - RequireLessThan("< 1.0", "1.0"); - RequireLessThan("< 1.0", "> 1.0"); - RequireLessThan("1.0", "> 1.0"); - RequireLessThan("0.9", "< 1.0"); - RequireLessThan("> 1.0", "1.1"); - - // With latest - RequireLessThan("< latest", "latest"); - RequireLessThan("latest", "> latest"); - RequireLessThan("9999", "< latest"); -} - -TEST_CASE("VersionRange", "[versions]") -{ - // Create - REQUIRE_NOTHROW(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }); - REQUIRE_NOTHROW(VersionRange{ Version{ "1.0" }, Version{ "1.0" } }); - REQUIRE_NOTHROW(VersionRange{ Version{ "2.0" }, Version{ "1.0" } }); - - // Overlaps - REQUIRE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "2.0" }, Version{ "3.0" } })); - REQUIRE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "1.0" }, Version{ "1.0" } })); - REQUIRE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "0.5" }, Version{ "1.5" } })); - REQUIRE_FALSE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "2.1" }, Version{ "3.0" } })); - REQUIRE_FALSE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{})); - - // Empty - REQUIRE(VersionRange{}.IsEmpty()); - REQUIRE_THROWS(VersionRange{}.GetMinVersion()); - REQUIRE_THROWS(VersionRange{}.GetMaxVersion()); - - // Less than compare - REQUIRE_THROWS(VersionRange{ Version{ "0.5" }, Version{ "1.0" } } < VersionRange{ Version{ "1.0" }, Version{ "2.0" } }); - REQUIRE_THROWS(VersionRange{} < VersionRange{ Version{ "1.0" }, Version{ "2.0" } }); - REQUIRE(VersionRange{ Version{ "0.5" }, Version{ "1.0" } } < VersionRange{ Version{ "1.5" }, Version{ "2.0" } }); - REQUIRE_FALSE(VersionRange{ Version{ "1.5" }, Version{ "2.0" } } < VersionRange{ Version{ "0.5" }, Version{ "1.0" } }); -} - -TEST_CASE("GatedVersion", "[versions]") -{ - REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.1" })); - REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0" })); - REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1" })); - REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.alpha" })); - REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.1.2.3" })); - REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.*" })); - REQUIRE_FALSE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.1.1" })); - - REQUIRE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.*.1" })); - REQUIRE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.*.*" })); - REQUIRE_FALSE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.1.1" })); - - REQUIRE(GatedVersion("1.0.1"sv).IsValidVersion({ "1.0.1" })); - REQUIRE_FALSE(GatedVersion("1.0.1"sv).IsValidVersion({ "1.1.1" })); -} - -TEST_CASE("SemanticVersion", "[versions]") -{ - REQUIRE_THROWS_HR(SemanticVersion("1.2.3.4"), E_INVALIDARG); - REQUIRE_THROWS_HR(SemanticVersion("1.2abc.3"), E_INVALIDARG); - - SemanticVersion version = SemanticVersion("1.2.3-alpha"); - REQUIRE(version.IsPrerelease()); - REQUIRE(version.PrereleaseVersion() == Version("alpha")); - REQUIRE(!version.HasBuildMetadata()); - REQUIRE(version.PartAt(2).Other == "-alpha"); - - version = SemanticVersion("1.2.3-4.5.6"); - REQUIRE(version.IsPrerelease()); - REQUIRE(version.PrereleaseVersion() == Version("4.5.6")); - REQUIRE(!version.HasBuildMetadata()); - REQUIRE(version.PartAt(2).Other == "-4.5.6"); - - // Really shouldn't be allowed, but we are loose here - version = SemanticVersion("1.2+build"); - REQUIRE(!version.IsPrerelease()); - REQUIRE(version.HasBuildMetadata()); - REQUIRE(version.BuildMetadata() == Version("build")); - REQUIRE(version.PartAt(2).Other == "+build"); - - version = SemanticVersion("1.2.3-beta+4.5.6"); - REQUIRE(version.IsPrerelease()); - REQUIRE(version.PrereleaseVersion() == Version("beta")); - REQUIRE(version.HasBuildMetadata()); - REQUIRE(version.BuildMetadata() == Version("4.5.6")); - REQUIRE(version.PartAt(2).Other == "-beta+4.5.6"); -} - -TEST_CASE("OpenTypeFontVersion", "[versions]") -{ - // Valid font version. - OpenTypeFontVersion version = OpenTypeFontVersion("Version 1.234"); - REQUIRE(version.ToString() == "1.234"); - REQUIRE(version.GetParts().size() == 2); - REQUIRE(version.PartAt(0).Integer == 1); - REQUIRE(version.PartAt(1).Integer == 234); - - // Font version with additional metadata. - version = OpenTypeFontVersion("Version 9.876.54 ;2024"); - REQUIRE(version.ToString() == "9.876"); - REQUIRE(version.GetParts().size() == 2); - REQUIRE(version.PartAt(0).Integer == 9); - REQUIRE(version.PartAt(1).Integer == 876); - - // Invalid version. Font version must have at least 2 parts. - REQUIRE_NOTHROW(version = OpenTypeFontVersion("1234567")); - REQUIRE(version.IsUnknown()); - REQUIRE(version.ToString() == "Unknown"); - - // Major and minor parts must have digits. - REQUIRE_NOTHROW(version = OpenTypeFontVersion(" abc.def ")); - REQUIRE(version.IsUnknown()); - REQUIRE(version.ToString() == "Unknown"); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace AppInstaller; +using namespace AppInstaller::Utility; + + +TEST_CASE("VersionParse", "[versions]") +{ + Version version("1.2.3.4-alpha"); + const auto& parts = version.GetParts(); + REQUIRE(parts.size() == 4); + for (size_t i = 0; i < parts.size(); ++i) + { + INFO(i); + REQUIRE(parts[i].Integer == static_cast(i + 1)); + if (i != 3) + { + REQUIRE(parts[i].Other == ""); + } + else + { + REQUIRE(parts[i].Other == "-alpha"); + } + } +} + +TEST_CASE("VersionParsePlusDash", "[versions]") +{ + Version version("1.2.3.4-alpha", ".-"); + const auto& parts = version.GetParts(); + REQUIRE(parts.size() == 5); + for (size_t i = 0; i < 4; ++i) + { + INFO(i); + REQUIRE(parts[i].Integer == static_cast(i + 1)); + REQUIRE(parts[i].Other == ""); + } + REQUIRE(parts[4].Other == "alpha"); +} + +TEST_CASE("VersionParseWithWhitespace", "[versions]") +{ + Version version("1. 2.3 . 4 "); + const auto& parts = version.GetParts(); + REQUIRE(parts.size() == 4); + for (size_t i = 0; i < parts.size(); ++i) + { + INFO(i); + REQUIRE(parts[i].Integer == static_cast(i + 1)); + REQUIRE(parts[i].Other == ""); + } +} + +TEST_CASE("VersionParseWithPreamble", "[versions]") +{ + Version version("v1.2.3.4"); + const auto& parts = version.GetParts(); + REQUIRE(parts.size() == 4); + for (size_t i = 0; i < parts.size(); ++i) + { + INFO(i); + REQUIRE(parts[i].Integer == static_cast(i + 1)); + REQUIRE(parts[i].Other == ""); + } +} + +TEST_CASE("VersionParseCorner", "[versions]") +{ + Version version1(""); + auto parts = version1.GetParts(); + REQUIRE(parts.size() == 0); + + Version version2("."); + parts = version2.GetParts(); + REQUIRE(parts.size() == 0); + + Version version3(".0"); + parts = version3.GetParts(); + REQUIRE(parts.size() == 0); + + Version version4(".1"); + parts = version4.GetParts(); + REQUIRE(parts.size() == 2); + REQUIRE(parts[0].Integer == 0); + REQUIRE(parts[0].Other == ""); + REQUIRE(parts[1].Integer == 1); + REQUIRE(parts[1].Other == ""); + + Version version5("version"); + parts = version5.GetParts(); + REQUIRE(parts.size() == 1); + REQUIRE(parts[0].Integer == 0); + REQUIRE(parts[0].Other == "version"); + + Version version6(". 1 "); + parts = version6.GetParts(); + REQUIRE(parts.size() == 2); + REQUIRE(parts[0].Integer == 0); + REQUIRE(parts[0].Other == ""); + REQUIRE(parts[1].Integer == 1); + REQUIRE(parts[1].Other == ""); + + Version version7("v1.2a"); + parts = version7.GetParts(); + REQUIRE(parts.size() == 2); + REQUIRE(parts[0].Integer == 1); + REQUIRE(parts[0].Other == ""); + REQUIRE(parts[1].Integer == 2); + REQUIRE(parts[1].Other == "a"); +} + +void RequireLessThan(std::string_view a, std::string_view b) +{ + Version vA{ std::string(a) }; + Version vB{ std::string(b) }; + + REQUIRE(vA < vB); + REQUIRE_FALSE(vB < vA); + REQUIRE(vA <= vB); + REQUIRE_FALSE(vB <= vA); + REQUIRE(vB > vA); + REQUIRE_FALSE(vA > vB); + REQUIRE(vB >= vA); + REQUIRE_FALSE(vA >= vB); + REQUIRE_FALSE(vA == vB); + REQUIRE(vA != vB); +} + +void RequireEqual(std::string_view a, std::string_view b) +{ + Version vA{ std::string(a) }; + Version vB{ std::string(b) }; + + REQUIRE(vA == vB); + REQUIRE_FALSE(vA != vB); + REQUIRE(vA <= vB); + REQUIRE(vA >= vB); + REQUIRE_FALSE(vA < vB); + REQUIRE_FALSE(vA > vB); +} + +TEST_CASE("VersionCompare", "[versions]") +{ + RequireLessThan("1", "2"); + RequireLessThan("1.0.0", "2.0.0"); + RequireLessThan("0.0.1", "0.0.2"); + RequireLessThan("0.0.1-alpha", "0.0.2-alpha"); + RequireLessThan("0.0.1-beta", "0.0.2-alpha"); + RequireLessThan("0.0.1-beta", "0.0.2-alpha"); + RequireLessThan("13.9.8", "14.1"); + + // Ensure that versions with non-digit characters in their parts are sorted correctly + RequireLessThan("1-rc", "1"); + RequireLessThan("1.2-rc", "1.2"); + RequireLessThan("1.0-rc", "1.0"); + RequireLessThan("1.0.0-rc", "1"); + RequireLessThan("22.0.0-rc.1", "22.0.0"); + RequireLessThan("22.0.0-rc.1", "22.0.0.1"); + RequireLessThan("22.0.0-rc.1", "22.0.0.1-rc"); + + // Ensure that Sub-RC versions are sorted correctly + RequireLessThan("22.0.0-rc.1", "22.0.0-rc.1.1"); + RequireLessThan("22.0.0-rc.1.1", "22.0.0-rc.1.2"); + RequireLessThan("22.0.0-rc.1.2", "22.0.0-rc.2"); + + RequireEqual("1.0", "1.0.0"); + + // Ensure that integers are parsed correctly when there is a leading zero + RequireEqual("1.2.00.3", "1.2.0.3"); + RequireEqual("1.2.003.4", "1.2.3.4"); + RequireEqual("01.02.03.04", "1.2.3.4"); + RequireEqual("1.2.03-beta", "1.2.3-beta"); + + // Ensure whitespace doesn't affect equality + RequireEqual("1.0", "1.0 "); + RequireEqual("1.0", "1. 0"); + RequireEqual("1.0", "1.0."); + + // Ensure versions with preambles are sorted correctly + RequireEqual("1.0", "Version 1.0"); + RequireEqual("foo1", "bar1"); + RequireLessThan("v0.0.1", "0.0.2"); + RequireLessThan("v0.0.1", "v0.0.2"); + RequireLessThan("1.a2", "1.b1"); + RequireLessThan("alpha", "beta"); +} + +TEST_CASE("VersionAndChannelSort", "[versions]") +{ + std::vector sortedList = + { + { Version("15.0.0"), Channel("") }, + { Version("14.0.0"), Channel("") }, + { Version("13.2.1-bugfix"), Channel("") }, + { Version("13.2.0"), Channel("") }, + { Version("13.2.0-rc"), Channel("") }, + { Version("13.0.0"), Channel("") }, + { Version("16.0.0"), Channel("alpha") }, + { Version("15.8.0"), Channel("alpha") }, + { Version("15.1.0"), Channel("beta") }, + }; + + std::vector reorderList = { 4, 2, 1, 7, 6, 3, 8, 5, 0 }; + REQUIRE(sortedList.size() == reorderList.size()); + + std::vector jumbledList; + for (auto i : reorderList) + { + jumbledList.emplace_back(sortedList[i]); + } + + std::sort(jumbledList.begin(), jumbledList.end()); + + for (size_t i = 0; i < jumbledList.size(); ++i) + { + const VersionAndChannel& sortedVAC = sortedList[i]; + const VersionAndChannel& jumbledVAC = jumbledList[i]; + + INFO(i); + REQUIRE(sortedVAC.GetVersion().ToString() == jumbledVAC.GetVersion().ToString()); + REQUIRE(sortedVAC.GetChannel().ToString() == jumbledVAC.GetChannel().ToString()); + } +} + +TEST_CASE("MinOsVersion_Check", "[versions]") +{ + // Just verify that we are greater than Win 7 and less than far future Win 10. + // Unfortunately, an unmanifested process will also pass these validations, + // but an unmanifested process also can't use Windows APIs to determine the actual version. + REQUIRE(Runtime::IsCurrentOSVersionGreaterThanOrEqual(Version("6.1"))); + REQUIRE(!Runtime::IsCurrentOSVersionGreaterThanOrEqual(Version("10.0.65535"))); +} + +TEST_CASE("VersionLatest", "[versions]") +{ + REQUIRE(Version::CreateLatest().IsLatest()); + REQUIRE(Version("latest").IsLatest()); + REQUIRE(Version("LATEST").IsLatest()); + REQUIRE(!Version("1.0").IsLatest()); + + RequireLessThan("1.0", "latest"); + RequireLessThan("100", "latest"); + RequireLessThan("943849587389754876.1", "latest"); + + RequireEqual("latest", "LATEST"); +} + +TEST_CASE("VersionUnknown", "[versions]") +{ + REQUIRE(Version::CreateUnknown().IsUnknown()); + REQUIRE(Version("unknown").IsUnknown()); + REQUIRE(Version("UNKNOWN").IsUnknown()); + REQUIRE(!Version("1.0").IsUnknown()); + + RequireLessThan("unknown", "1.0"); + RequireLessThan("unknown", "1.fork"); + + RequireEqual("unknown", "UNKNOWN"); +} + +TEST_CASE("VersionUnknownLessThanLatest", "[versions]") +{ + REQUIRE(Version::CreateUnknown() < Version::CreateLatest()); +} + +TEST_CASE("VersionIsEmpty", "[versions]") +{ + REQUIRE(Version{}.IsEmpty()); + REQUIRE(Version{""}.IsEmpty()); + REQUIRE(!Version{"1"}.IsEmpty()); + REQUIRE(!Version{"0"}.IsEmpty()); + + Version v{ "1" }; + REQUIRE(!v.IsEmpty()); + v.Assign(""); + REQUIRE(v.IsEmpty()); +} + +TEST_CASE("VersionPartAt", "[versions]") +{ + REQUIRE(Version{}.PartAt(0).Integer == 0); + REQUIRE(Version{"1"}.PartAt(0).Integer == 1); + REQUIRE(Version{"1"}.PartAt(1).Integer == 0); + REQUIRE(Version{"1"}.PartAt(9999).Integer == 0); +} + +TEST_CASE("UInt64Version_Success_FourParts", "[versions]") +{ + Version expectedVersion("1.2.3.4"); + UInt64Version versionNumberFromNumber(0x0001000200030004); + UInt64Version versionNumberFromString("1.2.3.4"); + REQUIRE(expectedVersion == versionNumberFromNumber); + REQUIRE(expectedVersion == versionNumberFromString); + REQUIRE(expectedVersion.ToString() == versionNumberFromNumber.ToString()); + REQUIRE(expectedVersion.ToString() == versionNumberFromString.ToString()); +} + +TEST_CASE("UInt64Version_Success_LessThanFourParts", "[versions]") +{ + UInt64Version versionNumberFromNumber(0x0001000200030000); + UInt64Version versionNumberFromString("1.2.3"); + REQUIRE(versionNumberFromNumber == versionNumberFromString); +} + +TEST_CASE("UInt64Version_Success_NoOverflow", "[versions]") +{ + REQUIRE_NOTHROW(UInt64Version("65535.65535.65535.65535")); // 65535 => 0xffff + REQUIRE_NOTHROW(UInt64Version(0xffffffffffffffff)); +} + +TEST_CASE("UInt64Version_Fail_Overflow", "[versions]") +{ + REQUIRE_THROWS(UInt64Version("1.0.0.65536")); // 65536 => 0x10000 +} + +TEST_CASE("UInt64Version_Fail_MoreThanFourParts", "[versions]") +{ + REQUIRE_THROWS(UInt64Version("1.0.0.0.1")); +} + +TEST_CASE("UInt64Version_Fail_NonNumeric", "[versions]") +{ + REQUIRE_THROWS(UInt64Version("1.0.0.a")); +} + +TEST_CASE("ApproximateVersionParse", "[versions]") +{ + Version v1_0{ "1.0" }; + Version v1_0_LessThan{ v1_0, Version::ApproximateComparator::LessThan }; + Version v1_0_GreaterThan{ v1_0, Version::ApproximateComparator::GreaterThan }; + + Version v1_0_LessThanFromString = Version{ "< 1.0" }; + Version v1_0_GreaterThanFromString = Version{ "> 1.0" }; + + REQUIRE_FALSE(v1_0.IsApproximate()); + REQUIRE(v1_0_LessThanFromString.IsApproximate()); + REQUIRE(v1_0_GreaterThanFromString.IsApproximate()); + + REQUIRE(v1_0_LessThan == v1_0_LessThanFromString); + REQUIRE(v1_0_GreaterThan == v1_0_GreaterThanFromString); + + REQUIRE_THROWS(Version{ "< Unknown" }); + REQUIRE_THROWS(Version{ v1_0_LessThan, Version::ApproximateComparator::LessThan }); + REQUIRE_THROWS(Version{ Version::CreateUnknown(), Version::ApproximateComparator::LessThan }); +} + +TEST_CASE("ApproximateVersionCompare", "[versions]") +{ + RequireEqual("< 1.0", "< 1.0"); + RequireEqual("< 1.0", "< 1.0.0"); + RequireEqual("> 1.0", "> 1.0"); + RequireEqual("> 1.0", "> 1.0.0"); + + RequireLessThan("< 1.0", "1.0"); + RequireLessThan("< 1.0", "> 1.0"); + RequireLessThan("1.0", "> 1.0"); + RequireLessThan("0.9", "< 1.0"); + RequireLessThan("> 1.0", "1.1"); + + // With latest + RequireLessThan("< latest", "latest"); + RequireLessThan("latest", "> latest"); + RequireLessThan("9999", "< latest"); +} + +TEST_CASE("VersionRange", "[versions]") +{ + // Create + REQUIRE_NOTHROW(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }); + REQUIRE_NOTHROW(VersionRange{ Version{ "1.0" }, Version{ "1.0" } }); + REQUIRE_NOTHROW(VersionRange{ Version{ "2.0" }, Version{ "1.0" } }); + + // Overlaps + REQUIRE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "2.0" }, Version{ "3.0" } })); + REQUIRE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "1.0" }, Version{ "1.0" } })); + REQUIRE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "0.5" }, Version{ "1.5" } })); + REQUIRE_FALSE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{ Version{ "2.1" }, Version{ "3.0" } })); + REQUIRE_FALSE(VersionRange{ Version{ "1.0" }, Version{ "2.0" } }.Overlaps(VersionRange{})); + + // Empty + REQUIRE(VersionRange{}.IsEmpty()); + REQUIRE_THROWS(VersionRange{}.GetMinVersion()); + REQUIRE_THROWS(VersionRange{}.GetMaxVersion()); + + // Less than compare + REQUIRE_THROWS(VersionRange{ Version{ "0.5" }, Version{ "1.0" } } < VersionRange{ Version{ "1.0" }, Version{ "2.0" } }); + REQUIRE_THROWS(VersionRange{} < VersionRange{ Version{ "1.0" }, Version{ "2.0" } }); + REQUIRE(VersionRange{ Version{ "0.5" }, Version{ "1.0" } } < VersionRange{ Version{ "1.5" }, Version{ "2.0" } }); + REQUIRE_FALSE(VersionRange{ Version{ "1.5" }, Version{ "2.0" } } < VersionRange{ Version{ "0.5" }, Version{ "1.0" } }); +} + +TEST_CASE("GatedVersion", "[versions]") +{ + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.1" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.alpha" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.1.2.3" })); + REQUIRE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.0.*" })); + REQUIRE_FALSE(GatedVersion("1.0.*"sv).IsValidVersion({ "1.1.1" })); + + REQUIRE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.*.1" })); + REQUIRE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.*.*" })); + REQUIRE_FALSE(GatedVersion("1.*.*"sv).IsValidVersion({ "1.1.1" })); + + REQUIRE(GatedVersion("1.0.1"sv).IsValidVersion({ "1.0.1" })); + REQUIRE_FALSE(GatedVersion("1.0.1"sv).IsValidVersion({ "1.1.1" })); +} + +TEST_CASE("SemanticVersion", "[versions]") +{ + REQUIRE_THROWS_HR(SemanticVersion("1.2.3.4"), E_INVALIDARG); + REQUIRE_THROWS_HR(SemanticVersion("1.2abc.3"), E_INVALIDARG); + + SemanticVersion version = SemanticVersion("1.2.3-alpha"); + REQUIRE(version.IsPrerelease()); + REQUIRE(version.PrereleaseVersion() == Version("alpha")); + REQUIRE(!version.HasBuildMetadata()); + REQUIRE(version.PartAt(2).Other == "-alpha"); + + version = SemanticVersion("1.2.3-4.5.6"); + REQUIRE(version.IsPrerelease()); + REQUIRE(version.PrereleaseVersion() == Version("4.5.6")); + REQUIRE(!version.HasBuildMetadata()); + REQUIRE(version.PartAt(2).Other == "-4.5.6"); + + // Really shouldn't be allowed, but we are loose here + version = SemanticVersion("1.2+build"); + REQUIRE(!version.IsPrerelease()); + REQUIRE(version.HasBuildMetadata()); + REQUIRE(version.BuildMetadata() == Version("build")); + REQUIRE(version.PartAt(2).Other == "+build"); + + version = SemanticVersion("1.2.3-beta+4.5.6"); + REQUIRE(version.IsPrerelease()); + REQUIRE(version.PrereleaseVersion() == Version("beta")); + REQUIRE(version.HasBuildMetadata()); + REQUIRE(version.BuildMetadata() == Version("4.5.6")); + REQUIRE(version.PartAt(2).Other == "-beta+4.5.6"); +} + +TEST_CASE("OpenTypeFontVersion", "[versions]") +{ + // Valid font version. + OpenTypeFontVersion version = OpenTypeFontVersion("Version 1.234"); + REQUIRE(version.ToString() == "1.234"); + REQUIRE(version.GetParts().size() == 2); + REQUIRE(version.PartAt(0).Integer == 1); + REQUIRE(version.PartAt(1).Integer == 234); + + // Font version with additional metadata. + version = OpenTypeFontVersion("Version 9.876.54 ;2024"); + REQUIRE(version.ToString() == "9.876"); + REQUIRE(version.GetParts().size() == 2); + REQUIRE(version.PartAt(0).Integer == 9); + REQUIRE(version.PartAt(1).Integer == 876); + + // Invalid version. Font version must have at least 2 parts. + REQUIRE_NOTHROW(version = OpenTypeFontVersion("1234567")); + REQUIRE(version.IsUnknown()); + REQUIRE(version.ToString() == "Unknown"); + + // Major and minor parts must have digits. + REQUIRE_NOTHROW(version = OpenTypeFontVersion(" abc.def ")); + REQUIRE(version.IsUnknown()); + REQUIRE(version.ToString() == "Unknown"); +} diff --git a/src/AppInstallerCLITests/WindowsFeature.cpp b/src/AppInstallerCLITests/WindowsFeature.cpp index b2d570daee..46e023702c 100644 --- a/src/AppInstallerCLITests/WindowsFeature.cpp +++ b/src/AppInstallerCLITests/WindowsFeature.cpp @@ -1,187 +1,187 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "WorkflowCommon.h" -#include -#include -#include "TestHooks.h" - -using namespace AppInstaller::CLI; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; -using namespace TestCommon; - -TEST_CASE("InstallFlow_WindowsFeatureDoesNotExist", "[windowsFeature]") -{ - if (!AppInstaller::Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TestUserSettings testSettings; - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideOpenDependencySource(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::WindowsFeatureNotFound(LocIndView{ "testFeature1" })).get()) != std::string::npos); - - // "badFeature" should not be displayed as the flow should terminate after failing to find the first feature. - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::WindowsFeatureNotFound(LocIndView{ "testFeature2" })).get()) == std::string::npos); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverrideRequired).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_FailedToEnableWindowsFeature", "[windowsFeature]") -{ - if (!AppInstaller::Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TestUserSettings testSettings; - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideOpenDependencySource(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); - - auto setDoesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); - auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(0xc0040001); // DISMAPI_E_DISMAPI_NOT_INITIALIZED - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverrideRequired).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_FailedToEnableWindowsFeature_Force", "[windowsFeature]") -{ - if (!AppInstaller::Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TestUserSettings testSettings; - - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); - auto expectedErrorCode = 0xc0040001; // DISMAPI_E_DISMAPI_NOT_INITIALIZED - auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(expectedErrorCode); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideOpenDependencySource(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Force); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(context.GetTerminationHR() == ERROR_SUCCESS); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature1" }, expectedErrorCode)).get()) != std::string::npos); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature2" }, expectedErrorCode)).get()) != std::string::npos); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverridden).get()) != std::string::npos); - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); -} - -TEST_CASE("InstallFlow_RebootRequired", "[windowsFeature]") -{ - if (!AppInstaller::Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TestUserSettings testSettings; - - // Override with reboot required HRESULT. - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); - auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(ERROR_SUCCESS_REBOOT_REQUIRED); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideOpenDependencySource(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL); - REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::RebootRequiredToEnableWindowsFeatureOverrideRequired).get()) != std::string::npos); -} - -TEST_CASE("InstallFlow_RebootRequired_Force", "[windowsFeature]") -{ - if (!AppInstaller::Runtime::IsRunningAsAdmin()) - { - WARN("Test requires admin privilege. Skipped."); - return; - } - - TestCommon::TempFile installResultPath("TestExeInstalled.txt"); - TestCommon::TestUserSettings testSettings; - - // Override with reboot required HRESULT. - auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); - auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(ERROR_SUCCESS_REBOOT_REQUIRED); - - std::ostringstream installOutput; - TestContext context{ installOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - OverrideForShellExecute(context); - OverrideOpenDependencySource(context); - - context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); - context.Args.AddArg(Execution::Args::Type::Force); - - InstallCommand install({}); - install.Execute(context); - INFO(installOutput.str()); - - // Verify Installer is called and parameters are passed in. - REQUIRE(context.GetTerminationHR() == ERROR_SUCCESS); - REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::RebootRequiredToEnableWindowsFeatureOverridden).get()) != std::string::npos); - REQUIRE(std::filesystem::exists(installResultPath.GetPath())); - std::ifstream installResultFile(installResultPath.GetPath()); - REQUIRE(installResultFile.is_open()); - std::string installResultStr; - std::getline(installResultFile, installResultStr); - REQUIRE(installResultStr.find("/custom") != std::string::npos); - REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "WorkflowCommon.h" +#include +#include +#include "TestHooks.h" + +using namespace AppInstaller::CLI; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; +using namespace TestCommon; + +TEST_CASE("InstallFlow_WindowsFeatureDoesNotExist", "[windowsFeature]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideOpenDependencySource(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::WindowsFeatureNotFound(LocIndView{ "testFeature1" })).get()) != std::string::npos); + + // "badFeature" should not be displayed as the flow should terminate after failing to find the first feature. + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::WindowsFeatureNotFound(LocIndView{ "testFeature2" })).get()) == std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverrideRequired).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_FailedToEnableWindowsFeature", "[windowsFeature]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideOpenDependencySource(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); + + auto setDoesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); + auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(0xc0040001); // DISMAPI_E_DISMAPI_NOT_INITIALIZED + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES); + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverrideRequired).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_FailedToEnableWindowsFeature_Force", "[windowsFeature]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); + auto expectedErrorCode = 0xc0040001; // DISMAPI_E_DISMAPI_NOT_INITIALIZED + auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(expectedErrorCode); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideOpenDependencySource(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Force); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(context.GetTerminationHR() == ERROR_SUCCESS); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature1" }, expectedErrorCode)).get()) != std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeature(LocIndView{ "testFeature2" }, expectedErrorCode)).get()) != std::string::npos); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::FailedToEnableWindowsFeatureOverridden).get()) != std::string::npos); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); +} + +TEST_CASE("InstallFlow_RebootRequired", "[windowsFeature]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + // Override with reboot required HRESULT. + auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); + auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(ERROR_SUCCESS_REBOOT_REQUIRED); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideOpenDependencySource(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + REQUIRE(context.GetTerminationHR() == APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL); + REQUIRE(!std::filesystem::exists(installResultPath.GetPath())); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::RebootRequiredToEnableWindowsFeatureOverrideRequired).get()) != std::string::npos); +} + +TEST_CASE("InstallFlow_RebootRequired_Force", "[windowsFeature]") +{ + if (!AppInstaller::Runtime::IsRunningAsAdmin()) + { + WARN("Test requires admin privilege. Skipped."); + return; + } + + TestCommon::TempFile installResultPath("TestExeInstalled.txt"); + TestCommon::TestUserSettings testSettings; + + // Override with reboot required HRESULT. + auto doesFeatureExistOverride = TestHook::SetDoesWindowsFeatureExistResult_Override(ERROR_SUCCESS); + auto setEnableFeatureOverride = TestHook::SetEnableWindowsFeatureResult_Override(ERROR_SUCCESS_REBOOT_REQUIRED); + + std::ostringstream installOutput; + TestContext context{ installOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + OverrideForShellExecute(context); + OverrideOpenDependencySource(context); + + context.Args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_WindowsFeatures.yaml").GetPath().u8string()); + context.Args.AddArg(Execution::Args::Type::Force); + + InstallCommand install({}); + install.Execute(context); + INFO(installOutput.str()); + + // Verify Installer is called and parameters are passed in. + REQUIRE(context.GetTerminationHR() == ERROR_SUCCESS); + REQUIRE(installOutput.str().find(Resource::LocString(Resource::String::RebootRequiredToEnableWindowsFeatureOverridden).get()) != std::string::npos); + REQUIRE(std::filesystem::exists(installResultPath.GetPath())); + std::ifstream installResultFile(installResultPath.GetPath()); + REQUIRE(installResultFile.is_open()); + std::string installResultStr; + std::getline(installResultFile, installResultStr); + REQUIRE(installResultStr.find("/custom") != std::string::npos); + REQUIRE(installResultStr.find("/silentwithprogress") != std::string::npos); } \ No newline at end of file diff --git a/src/AppInstallerCLITests/WorkFlow.cpp b/src/AppInstallerCLITests/WorkFlow.cpp index ce69a4d12d..0a6795cb23 100644 --- a/src/AppInstallerCLITests/WorkFlow.cpp +++ b/src/AppInstallerCLITests/WorkFlow.cpp @@ -1,189 +1,189 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WorkflowCommon.h" -#include "TestSettings.h" -#include -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Utility; - -void VerifyMotw(const std::filesystem::path& testFile, DWORD zone) -{ - std::filesystem::path motwFile(testFile); - motwFile += ":Zone.Identifier:$data"; - std::ifstream motwStream(motwFile); - std::stringstream motwContent; - motwContent << motwStream.rdbuf(); - std::string motwContentStr = motwContent.str(); - motwStream.close(); - REQUIRE(motwContentStr.find("ZoneId=" + std::to_string(zone)) != std::string::npos); -} - -TEST_CASE("VerifyInstallerTrustLevelAndUpdateInstallerFileMotw", "[DownloadInstaller][workflow]") -{ - TestCommon::TempFile testInstallerPath("TestInstaller.txt"); - - std::ofstream ofile(testInstallerPath, std::ofstream::out); - ofile << "test"; - ofile.close(); - - ApplyMotwIfApplicable(testInstallerPath, URLZONE_INTERNET); - VerifyMotw(testInstallerPath, 3); - - std::ostringstream updateMotwOutput; - TestContext context{ updateMotwOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Add({ {}, {} }); - context.Add(testInstallerPath); - auto packageVersion = std::make_shared(Manifest{}); - auto testSource = std::make_shared(); - testSource->Details.TrustLevel = SourceTrustLevel::Trusted; - packageVersion->Source = testSource; - context.Add(packageVersion); - ManifestInstaller installer; - installer.Url = "http://NotTrusted.com"; - context.Add(std::move(installer)); - - context << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; - REQUIRE(WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerTrusted)); - VerifyMotw(testInstallerPath, 2); - - testSource->Details.TrustLevel = SourceTrustLevel::None; - context.ClearFlags(ContextFlag::InstallerTrusted); - context << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; - REQUIRE_FALSE(WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerTrusted)); - VerifyMotw(testInstallerPath, 3); - - INFO(updateMotwOutput.str()); -} - -TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") -{ - std::ostringstream validateOutput; - TestContext context{ validateOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Args::Type::ValidateManifest, TestDataFile("Manifest-Good-AllDependencyTypes.yaml").GetPath().u8string()); - - ValidateCommand validate({}); - validate.Execute(context); - INFO(validateOutput.str()); - - // Verify all types of dependencies are printed - REQUIRE(validateOutput.str().find(Resource::LocString(Resource::String::ValidateCommandReportDependencies).get()) != std::string::npos); - REQUIRE(validateOutput.str().find("WindowsFeaturesDep") != std::string::npos); - REQUIRE(validateOutput.str().find("WindowsLibrariesDep") != std::string::npos); - // PackageDep1 has minimum version (1.0), PackageDep2 doesn't (shouldn't show [>=...]) - REQUIRE(validateOutput.str().find("Package.Dep1-x64 [>= 1.0]") != std::string::npos); - REQUIRE(validateOutput.str().find("Package.Dep2-x64") != std::string::npos); - REQUIRE(validateOutput.str().find("Package.Dep2-x64 [") == std::string::npos); - REQUIRE(validateOutput.str().find("ExternalDep") != std::string::npos); -} - -TEST_CASE("AdminSetting_LocalManifestFiles", "[LocalManifests][workflow]") -{ - RemoveSetting(Stream::AdminSettings); - - { - // If there's no admin setting, using local manifest should fail. - Execution::Args args; - args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); - InstallCommand installCommand({}); - REQUIRE_THROWS(installCommand.ValidateArguments(args)); - } - - { - // Using settings command to enable local manifests - std::ostringstream settingsOutput; - TestContext context{ settingsOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::AdminSettingEnable, "LocalManifestFiles"sv); - context.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); - SettingsCommand settings({}); - settings.Execute(context); - INFO(settingsOutput.str()); - } - - { - // Now using local manifests should succeed - Execution::Args args2; - args2.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); - InstallCommand installCommand2({}); - REQUIRE_NOTHROW(installCommand2.ValidateArguments(args2)); - } - - { - // Using settings command to disable local manifests - std::ostringstream settingsOutput2; - TestContext context2{ settingsOutput2, std::cin }; - auto previousThreadGlobals = context2.SetForCurrentThread(); - context2.Args.AddArg(Execution::Args::Type::AdminSettingDisable, "LocalManifestFiles"sv); - context2.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); - SettingsCommand settings2({}); - settings2.Execute(context2); - INFO(settingsOutput2.str()); - } - - { - // Now using local manifests should fail - Execution::Args args3; - args3.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); - InstallCommand installCommand3({}); - REQUIRE_THROWS(installCommand3.ValidateArguments(args3)); - } -} - -TEST_CASE("Export_Settings", "[Settings][workflow]") -{ - RemoveSetting(Stream::AdminSettings); - - { - // No admin settings, local manifest should be false. - std::ostringstream exportOutput; - TestContext context{ exportOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - SettingsExportCommand settingsExportCommand({}); - settingsExportCommand.Execute(context); - - auto json = ConvertToJson(exportOutput.str()); - REQUIRE(!json.isNull()); - REQUIRE_FALSE(json["adminSettings"]["LocalManifestFiles"].asBool()); - - auto userSettingsFileValue = std::string(json["userSettingsFile"].asCString()); - REQUIRE(userSettingsFileValue.find("settings.json") != std::string::npos); - } - - { - // Enable local manifest and verify export works. - std::ostringstream settingsOutput; - TestContext context{ settingsOutput, std::cin }; - auto previousThreadGlobals = context.SetForCurrentThread(); - context.Args.AddArg(Execution::Args::Type::AdminSettingEnable, "LocalManifestFiles"sv); - context.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); - SettingsCommand settings({}); - settings.Execute(context); - - std::ostringstream exportOutput; - TestContext context2{ exportOutput, std::cin }; - auto previousThreadGlobals2 = context2.SetForCurrentThread(); - SettingsExportCommand settingsExportCommand({}); - settingsExportCommand.Execute(context2); - auto json = ConvertToJson(exportOutput.str()); - REQUIRE(!json.isNull()); - REQUIRE(json["adminSettings"]["LocalManifestFiles"].asBool()); - - auto userSettingsFileValue = std::string(json["userSettingsFile"].asCString()); - REQUIRE(userSettingsFileValue.find("settings.json") != std::string::npos); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WorkflowCommon.h" +#include "TestSettings.h" +#include +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Utility; + +void VerifyMotw(const std::filesystem::path& testFile, DWORD zone) +{ + std::filesystem::path motwFile(testFile); + motwFile += ":Zone.Identifier:$data"; + std::ifstream motwStream(motwFile); + std::stringstream motwContent; + motwContent << motwStream.rdbuf(); + std::string motwContentStr = motwContent.str(); + motwStream.close(); + REQUIRE(motwContentStr.find("ZoneId=" + std::to_string(zone)) != std::string::npos); +} + +TEST_CASE("VerifyInstallerTrustLevelAndUpdateInstallerFileMotw", "[DownloadInstaller][workflow]") +{ + TestCommon::TempFile testInstallerPath("TestInstaller.txt"); + + std::ofstream ofile(testInstallerPath, std::ofstream::out); + ofile << "test"; + ofile.close(); + + ApplyMotwIfApplicable(testInstallerPath, URLZONE_INTERNET); + VerifyMotw(testInstallerPath, 3); + + std::ostringstream updateMotwOutput; + TestContext context{ updateMotwOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Add({ {}, {} }); + context.Add(testInstallerPath); + auto packageVersion = std::make_shared(Manifest{}); + auto testSource = std::make_shared(); + testSource->Details.TrustLevel = SourceTrustLevel::Trusted; + packageVersion->Source = testSource; + context.Add(packageVersion); + ManifestInstaller installer; + installer.Url = "http://NotTrusted.com"; + context.Add(std::move(installer)); + + context << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; + REQUIRE(WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerTrusted)); + VerifyMotw(testInstallerPath, 2); + + testSource->Details.TrustLevel = SourceTrustLevel::None; + context.ClearFlags(ContextFlag::InstallerTrusted); + context << VerifyInstallerHash << UpdateInstallerFileMotwIfApplicable; + REQUIRE_FALSE(WI_IsFlagSet(context.GetFlags(), ContextFlag::InstallerTrusted)); + VerifyMotw(testInstallerPath, 3); + + INFO(updateMotwOutput.str()); +} + +TEST_CASE("ValidateCommand_Dependencies", "[workflow][dependencies]") +{ + std::ostringstream validateOutput; + TestContext context{ validateOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Args::Type::ValidateManifest, TestDataFile("Manifest-Good-AllDependencyTypes.yaml").GetPath().u8string()); + + ValidateCommand validate({}); + validate.Execute(context); + INFO(validateOutput.str()); + + // Verify all types of dependencies are printed + REQUIRE(validateOutput.str().find(Resource::LocString(Resource::String::ValidateCommandReportDependencies).get()) != std::string::npos); + REQUIRE(validateOutput.str().find("WindowsFeaturesDep") != std::string::npos); + REQUIRE(validateOutput.str().find("WindowsLibrariesDep") != std::string::npos); + // PackageDep1 has minimum version (1.0), PackageDep2 doesn't (shouldn't show [>=...]) + REQUIRE(validateOutput.str().find("Package.Dep1-x64 [>= 1.0]") != std::string::npos); + REQUIRE(validateOutput.str().find("Package.Dep2-x64") != std::string::npos); + REQUIRE(validateOutput.str().find("Package.Dep2-x64 [") == std::string::npos); + REQUIRE(validateOutput.str().find("ExternalDep") != std::string::npos); +} + +TEST_CASE("AdminSetting_LocalManifestFiles", "[LocalManifests][workflow]") +{ + RemoveSetting(Stream::AdminSettings); + + { + // If there's no admin setting, using local manifest should fail. + Execution::Args args; + args.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + InstallCommand installCommand({}); + REQUIRE_THROWS(installCommand.ValidateArguments(args)); + } + + { + // Using settings command to enable local manifests + std::ostringstream settingsOutput; + TestContext context{ settingsOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::AdminSettingEnable, "LocalManifestFiles"sv); + context.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); + SettingsCommand settings({}); + settings.Execute(context); + INFO(settingsOutput.str()); + } + + { + // Now using local manifests should succeed + Execution::Args args2; + args2.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + InstallCommand installCommand2({}); + REQUIRE_NOTHROW(installCommand2.ValidateArguments(args2)); + } + + { + // Using settings command to disable local manifests + std::ostringstream settingsOutput2; + TestContext context2{ settingsOutput2, std::cin }; + auto previousThreadGlobals = context2.SetForCurrentThread(); + context2.Args.AddArg(Execution::Args::Type::AdminSettingDisable, "LocalManifestFiles"sv); + context2.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); + SettingsCommand settings2({}); + settings2.Execute(context2); + INFO(settingsOutput2.str()); + } + + { + // Now using local manifests should fail + Execution::Args args3; + args3.AddArg(Execution::Args::Type::Manifest, TestDataFile("InstallFlowTest_Exe.yaml").GetPath().u8string()); + InstallCommand installCommand3({}); + REQUIRE_THROWS(installCommand3.ValidateArguments(args3)); + } +} + +TEST_CASE("Export_Settings", "[Settings][workflow]") +{ + RemoveSetting(Stream::AdminSettings); + + { + // No admin settings, local manifest should be false. + std::ostringstream exportOutput; + TestContext context{ exportOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + SettingsExportCommand settingsExportCommand({}); + settingsExportCommand.Execute(context); + + auto json = ConvertToJson(exportOutput.str()); + REQUIRE(!json.isNull()); + REQUIRE_FALSE(json["adminSettings"]["LocalManifestFiles"].asBool()); + + auto userSettingsFileValue = std::string(json["userSettingsFile"].asCString()); + REQUIRE(userSettingsFileValue.find("settings.json") != std::string::npos); + } + + { + // Enable local manifest and verify export works. + std::ostringstream settingsOutput; + TestContext context{ settingsOutput, std::cin }; + auto previousThreadGlobals = context.SetForCurrentThread(); + context.Args.AddArg(Execution::Args::Type::AdminSettingEnable, "LocalManifestFiles"sv); + context.Override({ EnsureRunningAsAdmin, [](TestContext&) {} }); + SettingsCommand settings({}); + settings.Execute(context); + + std::ostringstream exportOutput; + TestContext context2{ exportOutput, std::cin }; + auto previousThreadGlobals2 = context2.SetForCurrentThread(); + SettingsExportCommand settingsExportCommand({}); + settingsExportCommand.Execute(context2); + auto json = ConvertToJson(exportOutput.str()); + REQUIRE(!json.isNull()); + REQUIRE(json["adminSettings"]["LocalManifestFiles"].asBool()); + + auto userSettingsFileValue = std::string(json["userSettingsFile"].asCString()); + REQUIRE(userSettingsFileValue.find("settings.json") != std::string::npos); + } +} diff --git a/src/AppInstallerCLITests/WorkflowCommon.cpp b/src/AppInstallerCLITests/WorkflowCommon.cpp index 51d004c583..93a42282a4 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.cpp +++ b/src/AppInstallerCLITests/WorkflowCommon.cpp @@ -1,738 +1,738 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DependenciesTestSource.h" -#include "WorkflowCommon.h" -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller::CLI; -using namespace AppInstaller::CLI::Execution; -using namespace AppInstaller::CLI::Workflow; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Utility; - -namespace TestCommon -{ - namespace TSR - { - const TestSourceResult TestQuery_ReturnOne( - "TestQueryReturnOne"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make(std::vector{ manifest }, source), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnOne"))); - }); - - const TestSourceResult TestQuery_ReturnTwo( - "TestQueryReturnTwo"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make(std::vector{ manifest }, source), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); - - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("Manifest-Good.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make(std::vector{ manifest2 }, source), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); - }); - - const TestSourceResult TestInstaller_Exe( - "AppInstallerCliTest.TestExeInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); - auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); - - auto testPackage = - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Exe" }, - { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, - { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, - }, - std::vector{ manifest3, manifest2, manifest }, - source - ); - for (auto& availablePackage : testPackage->Available) - { - availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; - } - matches.emplace_back( - ResultMatch( - testPackage, - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - const TestSourceResult TestInstaller_Exe_UpgradeUsesAgreements( - "AppInstallerCliTest.TestExeInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); - auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2_LicenseAgreement.yaml")); - - auto testPackage = - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Exe" }, - { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, - { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, - }, - std::vector{ manifest3, manifest2, manifest }, - source - ); - for (auto& availablePackage : testPackage->Available) - { - availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; - } - matches.emplace_back( - ResultMatch( - testPackage, - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - const TestSourceResult TestInstaller_Portable( - "AppInstallerCliTest.TestPortableInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Portable.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Portable.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Portable" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestPortableInstaller"))); - }); - - const TestSourceResult TestInstaller_Msix( - "AppInstallerCliTest.TestMsixInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Msix.yaml")); - - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); - }); - - const TestSourceResult TestInstaller_Msix_UpgradeUsesAgreements( - "AppInstallerCliTest.TestMsixInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Msix_LicenseAgreement.yaml")); - - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); - }); - - const TestSourceResult TestInstaller_Msix_UpgradeRequiresExplicit( - "AppInstallerCliTest.TestMsixInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Msix.yaml")); - - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Msix" }, - { PackageVersionMetadata::PinnedState, "PinnedByManifest" }, - }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); - }); - - const TestSourceResult TestInstaller_Zip( - "AppInstallerCliTest.TestZipInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Zip_Exe.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestZipInstaller"))); - }); - - const TestSourceResult TestInstaller_MSStore( - "AppInstallerCliTest.TestMSStoreInstaller"sv, - [](std::vector& matches, std::weak_ptr source) { - auto installed = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_MSStore.yaml")); - auto available = installed; - // Override the installed version to not be Latest - installed.Version = "1.0.0.0"; - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - installed, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "MSStore" } }, - std::vector{ available }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMSStoreInstaller"))); - }); - - const TestSourceResult TestInstaller_Exe_ExpectedReturnCodes( - "AppInstallerCliTest.ExpectedReturnCodes"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_ExpectedReturnCodes.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.ExpectedReturnCodes"))); - }); - - const TestSourceResult TestInstaller_Exe_UnknownVersion( - "TestExeInstallerWithUnknownVersion"sv, - [](std::vector& matches, std::weak_ptr source) { - auto installed = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_UnknownVersion.yaml")); - auto available = installed; - // Override the installed version to be unknown. - installed.Version = "unknown"; - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - installed, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, - std::vector{ available }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeUnknownVersion"))); - }); - - const TestSourceResult TestInstaller_Exe_LatestInstalled( - "TestExeInstallerWithLatestInstalled"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest2, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - const TestSourceResult TestInstaller_Exe_IncompatibleInstallerType( - "TestExeInstallerWithIncompatibleInstallerType"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - const TestSourceResult TestInstaller_Exe_DifferentInstallerType( - "TestExeInstallerWithDifferentInstalledType"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_ARPInstallerType.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - const TestSourceResult TestInstaller_Exe_UnsupportedArguments( - "TestExeInstallerWithUnsupportedArguments"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_UnsupportedArgs.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - const TestSourceResult TestInstaller_Exe_NothingInstalled( - "TestExeInstallerWithNothingInstalled"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - std::vector{ manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - - const TestSourceResult TestInstaller_Exe_Dependencies( - "AppInstallerCliTest.TestExeInstaller.Dependencies"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Exe_Dependencies.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_ExeDependencies.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Exe" }, - { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, - { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, - }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller.Dependencies"))); - }); - - const TestSourceResult TestInstaller_Msix_WFDependency( - "AppInstallerCliTest.TestMsixInstaller.WFDep"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Msix_WFDependency.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - std::vector{ manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller.WFDep"))); - }); - - const TestSourceResult TestInstaller_Exe_LicenseAgreement( - "TestInstallerWithLicenseAgreement"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_LicenseAgreement.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2_LicenseAgreement.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, - std::vector{ manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestInstallerWithLicenseAgreement"))); - }); - - const TestSourceResult TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems( - "TestUpgradeAllWithDuplicateUpgradeItems"sv, - [](std::vector& matches, std::weak_ptr source) { - auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); - auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); - auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest, - TestCompositePackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Exe" }, - { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, - { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, - }, - std::vector{ manifest3, manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - matches.emplace_back( - ResultMatch( - TestCompositePackage::Make( - manifest2, - TestCompositePackage::MetadataMap - { - { PackageVersionMetadata::InstalledType, "Exe" }, - { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, - { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, - }, - std::vector{ manifest3, manifest2, manifest }, - source - ), - PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); - }); - } - - SearchResult WorkflowTestSource::Search(const SearchRequest& request) const - { - std::string input; - if (request.Query) - { - input = request.Query->Value; - } - else if (!request.Inclusions.empty()) - { - input = request.Inclusions[0].Value; - } - else if (!request.Filters.empty()) - { - input = request.Filters[0].Value; - } - - SearchResult result; - for (const auto& testSourceResult : m_testSourceResults) - { - if (input.empty() || CaseInsensitiveEquals(input, testSourceResult.Query)) - { - testSourceResult.AddResults(result.Matches, shared_from_this()); - } - } - - return result; - } - - void WorkflowTestSource::AddResult(const TestSourceResult& testSourceResult) - { - m_testSourceResults.push_back(testSourceResult); - } - - std::shared_ptr CreateTestSource(std::vector&& testSourceResults) - { - return std::make_shared(std::move(testSourceResults)); - } - - TestContext::TestContext(std::ostream& out, std::istream& in) : TestContext(out, in, false, std::make_shared>()) - { - WorkflowTaskOverride wto - { RemoveInstaller, [](TestContext&) - { - // Do nothing; we never want to remove the test files. - } }; - - // Mark this one as used so that it doesn't anger the destructor. - wto.UseCount++; - - Override(wto); - } - - TestContext::TestContext(std::ostream& out, std::istream& in, bool isClone, std::shared_ptr> overrides) : - m_out(out), m_in(in), m_overrides(overrides), m_isClone(isClone), Context(out, in) - { - m_shouldExecuteWorkflowTask = [this](const Workflow::WorkflowTask& task) - { - auto itr = std::find_if(m_overrides->begin(), m_overrides->end(), [&](const WorkflowTaskOverride& wto) { return wto.Target == task; }); - - if (itr == m_overrides->end()) - { - return true; - } - else - { - itr->UseCount++; - itr->Override(*this); - return false; - } - }; - } - - TestContext::~TestContext() - { - if (!m_isClone) - { - for (const auto& wto : *m_overrides) - { - if (wto.UseCount == 0) - { - FAIL_CHECK("Unused override " + wto.Target.GetName()); - } - else if (wto.ExpectedUseCount != -1 && wto.ExpectedUseCount != wto.UseCount) - { - FAIL_CHECK("Used override count does not match expected " + wto.Target.GetName()); - } - } - } - } - - void TestContext::Override(const WorkflowTaskOverride& wto) - { - m_overrides->emplace_back(wto); - } - - std::unique_ptr TestContext::CreateSubContext() - { - auto clone = std::make_unique(m_out, m_in, true, m_overrides); - clone->SetFlags(this->GetFlags()); - CopyArgsToSubContext(clone.get()); - return clone; - } - - void OverrideForOpenSource(TestContext& context, std::shared_ptr testSource, bool overrideOpenCompositeSource) - { - context.Override({ "OpenSource", [=](TestContext& context) - { - context.Add(Source{ testSource }); - } }); - - if (overrideOpenCompositeSource) - { - context.Override({ "OpenCompositeSource", [](TestContext&) - { - } }); - } - } - - void OverrideForCompositeInstalledSource(TestContext& context, std::shared_ptr testSource) - { - context.Override({ "OpenSource", [](TestContext&) - { - } }); - - context.Override({ "OpenCompositeSource", [=](TestContext& context) - { - context.Add(Source{ testSource }); - } }); - } - - void OverrideForUpdateInstallerMotw(TestContext& context) - { - context.Override({ UpdateInstallerFileMotwIfApplicable, [](TestContext&) - { - } }); - } - - void OverrideForCheckExistingInstaller(TestContext& context) - { - context.Override({ CheckForExistingInstaller, [](TestContext&) - { - } }); - } - - void OverrideForShellExecute(TestContext& context, int expectedUseCount) - { - OverrideForCheckExistingInstaller(context); - - context.Override({ DownloadInstallerFile, [](TestContext& context) - { - context.Add({ {}, {} }); - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - }, expectedUseCount }); - - context.Override({ RenameDownloadedInstaller, [](TestContext&) - { - }, expectedUseCount }); - - OverrideForUpdateInstallerMotw(context); - } - - void OverrideForShellExecute(TestContext& context, std::vector& installationLog) - { - context.Override({ DownloadInstallerFile, [&installationLog](TestContext& context) - { - context.Add({ {}, {} }); - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - - auto dependency = Dependency(DependencyType::Package, context.Get().Id, context.Get().Version); - installationLog.push_back(dependency); - } }); - - context.Override({ RenameDownloadedInstaller, [](TestContext&) - { - } }); - - OverrideForUpdateInstallerMotw(context); - } - - void OverrideForPortableInstall(TestContext& context) - { - context.Override({ Workflow::details::PortableInstall, [](TestContext&) - { - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestPortableInstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - file.close(); - } }); - } - - void OverrideForPortableInstallFlow(TestContext& context) - { - context.Override({ DownloadInstallerFile, [](TestContext& context) - { - context.Add({ {}, {} }); - context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); - } }); - - context.Override({ RenameDownloadedInstaller, [](TestContext&) - { - } }); - - OverrideForUpdateInstallerMotw(context); - OverrideForPortableInstall(context); - } - - void OverridePortableInstaller(TestContext& context) - { - context.Override({ DownloadInstallerFile, [](TestContext& context) - { - std::filesystem::path tempDirectory = std::filesystem::temp_directory_path(); - const auto& installerPath = TestDataFile("AppInstallerTestExeInstaller.exe").GetPath(); - const auto& tempInstallerPath = tempDirectory / "AppInstallerTestExeInstaller.exe"; - std::filesystem::copy(installerPath, tempInstallerPath, std::filesystem::copy_options::overwrite_existing); - context.Add(tempInstallerPath); - - std::ifstream inStream{ tempInstallerPath, std::ifstream::binary }; - SHA256::HashBuffer fileHash = SHA256::ComputeHash(inStream); - context.Add({ fileHash, DownloadResult{ fileHash } }); - } }); - - context.Override({ RenameDownloadedInstaller, [](TestContext&) - { - } }); - - OverrideForUpdateInstallerMotw(context); - } - - void OverrideForExtractInstallerFromArchive(TestContext& context) - { - context.Override({ ExtractFilesFromArchive, [](TestContext&) - { - } }); - } - - void OverrideForVerifyAndSetNestedInstaller(TestContext& context) - { - context.Override({ VerifyAndSetNestedInstaller, [](TestContext&) - { - } }); - } - - void OverrideForMSIX(TestContext& context) - { - context.Override({ Workflow::details::MsixInstall, [](TestContext& context) - { - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestMsixInstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - - if (context.Contains(Execution::Data::InstallerPath)) - { - file << context.Get().u8string(); - } - else - { - file << context.Get()->Url; - } - - file.close(); - } }); - } - - void OverrideForMSStore(TestContext& context, bool isUpdate) - { - if (isUpdate) - { - context.Override({ MSStoreUpdate, [](TestContext& context) - { - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestMSStoreUpdated.txt"; - std::ofstream file(temp, std::ofstream::out); - file << context.Get()->ProductId; - file.close(); - } }); - } - else - { - context.Override({ MSStoreInstall, [](TestContext& context) - { - std::filesystem::path temp = std::filesystem::temp_directory_path(); - temp /= "TestMSStoreInstalled.txt"; - std::ofstream file(temp, std::ofstream::out); - file << context.Get()->ProductId; - file.close(); - } }); - } - - context.Override({ Workflow::EnsureStorePolicySatisfied, [](TestContext&) - { - } }); - } - - void OverrideOpenDependencySource(TestContext& context) - { - context.Override({ Workflow::OpenDependencySource, [](TestContext& context) - { - context.Add(Source{ std::make_shared() }); - } }); - } - - void OverrideEnableWindowsFeaturesDependencies(TestContext& context) - { - context.Override({ Workflow::EnableWindowsFeaturesDependencies, [](TestContext&) - { - } }); - } - - void OverrideRegisterStartupAfterReboot(TestContext& context) - { - context.Override({ "RegisterStartupAfterReboot", [](TestContext&) - { - } }); - } - - void OverrideDownloadInstallerFileForMSStoreDownload(TestContext& context) - { - context.Override({ DownloadInstallerFile, [](TestContext& context) - { - const auto& installer = context.Get().value(); - const auto& installerPath = context.Get(); - std::ofstream file(installerPath, std::ofstream::out | std::ofstream::trunc); - file << installer.Url; - file.close(); - context.Add({ {}, {} }); - } }); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DependenciesTestSource.h" +#include "WorkflowCommon.h" +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller::CLI; +using namespace AppInstaller::CLI::Execution; +using namespace AppInstaller::CLI::Workflow; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility; + +namespace TestCommon +{ + namespace TSR + { + const TestSourceResult TestQuery_ReturnOne( + "TestQueryReturnOne"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make(std::vector{ manifest }, source), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnOne"))); + }); + + const TestSourceResult TestQuery_ReturnTwo( + "TestQueryReturnTwo"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make(std::vector{ manifest }, source), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); + + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("Manifest-Good.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make(std::vector{ manifest2 }, source), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestQueryReturnTwo"))); + }); + + const TestSourceResult TestInstaller_Exe( + "AppInstallerCliTest.TestExeInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); + auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); + + auto testPackage = + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ manifest3, manifest2, manifest }, + source + ); + for (auto& availablePackage : testPackage->Available) + { + availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + } + matches.emplace_back( + ResultMatch( + testPackage, + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + const TestSourceResult TestInstaller_Exe_UpgradeUsesAgreements( + "AppInstallerCliTest.TestExeInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); + auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2_LicenseAgreement.yaml")); + + auto testPackage = + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ manifest3, manifest2, manifest }, + source + ); + for (auto& availablePackage : testPackage->Available) + { + availablePackage->IsSameOverride = [](const IPackage*, const IPackage*) { return true; }; + } + matches.emplace_back( + ResultMatch( + testPackage, + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + const TestSourceResult TestInstaller_Portable( + "AppInstallerCliTest.TestPortableInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Portable.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Portable.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Portable" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestPortableInstaller"))); + }); + + const TestSourceResult TestInstaller_Msix( + "AppInstallerCliTest.TestMsixInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Msix.yaml")); + + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); + }); + + const TestSourceResult TestInstaller_Msix_UpgradeUsesAgreements( + "AppInstallerCliTest.TestMsixInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Msix_LicenseAgreement.yaml")); + + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); + }); + + const TestSourceResult TestInstaller_Msix_UpgradeRequiresExplicit( + "AppInstallerCliTest.TestMsixInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Msix_StreamingFlow.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Msix.yaml")); + + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Msix" }, + { PackageVersionMetadata::PinnedState, "PinnedByManifest" }, + }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller"))); + }); + + const TestSourceResult TestInstaller_Zip( + "AppInstallerCliTest.TestZipInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Zip_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Zip_Exe.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestZipInstaller"))); + }); + + const TestSourceResult TestInstaller_MSStore( + "AppInstallerCliTest.TestMSStoreInstaller"sv, + [](std::vector& matches, std::weak_ptr source) { + auto installed = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_MSStore.yaml")); + auto available = installed; + // Override the installed version to not be Latest + installed.Version = "1.0.0.0"; + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + installed, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "MSStore" } }, + std::vector{ available }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMSStoreInstaller"))); + }); + + const TestSourceResult TestInstaller_Exe_ExpectedReturnCodes( + "AppInstallerCliTest.ExpectedReturnCodes"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_ExpectedReturnCodes.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_ExpectedReturnCodes.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.ExpectedReturnCodes"))); + }); + + const TestSourceResult TestInstaller_Exe_UnknownVersion( + "TestExeInstallerWithUnknownVersion"sv, + [](std::vector& matches, std::weak_ptr source) { + auto installed = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_UnknownVersion.yaml")); + auto available = installed; + // Override the installed version to be unknown. + installed.Version = "unknown"; + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + installed, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ available }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeUnknownVersion"))); + }); + + const TestSourceResult TestInstaller_Exe_LatestInstalled( + "TestExeInstallerWithLatestInstalled"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest2, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + const TestSourceResult TestInstaller_Exe_IncompatibleInstallerType( + "TestExeInstallerWithIncompatibleInstallerType"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + const TestSourceResult TestInstaller_Exe_DifferentInstallerType( + "TestExeInstallerWithDifferentInstalledType"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_ARPInstallerType.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Msix" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + const TestSourceResult TestInstaller_Exe_UnsupportedArguments( + "TestExeInstallerWithUnsupportedArguments"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_UnsupportedArgs.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + const TestSourceResult TestInstaller_Exe_NothingInstalled( + "TestExeInstallerWithNothingInstalled"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + std::vector{ manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + + const TestSourceResult TestInstaller_Exe_Dependencies( + "AppInstallerCliTest.TestExeInstaller.Dependencies"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Exe_Dependencies.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_ExeDependencies.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller.Dependencies"))); + }); + + const TestSourceResult TestInstaller_Msix_WFDependency( + "AppInstallerCliTest.TestMsixInstaller.WFDep"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("Installer_Msix_WFDependency.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + std::vector{ manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestMsixInstaller.WFDep"))); + }); + + const TestSourceResult TestInstaller_Exe_LicenseAgreement( + "TestInstallerWithLicenseAgreement"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_LicenseAgreement.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2_LicenseAgreement.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap{ { PackageVersionMetadata::InstalledType, "Exe" } }, + std::vector{ manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "TestInstallerWithLicenseAgreement"))); + }); + + const TestSourceResult TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems( + "TestUpgradeAllWithDuplicateUpgradeItems"sv, + [](std::vector& matches, std::weak_ptr source) { + auto manifest = YamlParser::CreateFromPath(TestDataFile("InstallFlowTest_Exe.yaml")); + auto manifest2 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe.yaml")); + auto manifest3 = YamlParser::CreateFromPath(TestDataFile("UpdateFlowTest_Exe_2.yaml")); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ manifest3, manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + matches.emplace_back( + ResultMatch( + TestCompositePackage::Make( + manifest2, + TestCompositePackage::MetadataMap + { + { PackageVersionMetadata::InstalledType, "Exe" }, + { PackageVersionMetadata::StandardUninstallCommand, "C:\\uninstall.exe" }, + { PackageVersionMetadata::SilentUninstallCommand, "C:\\uninstall.exe /silence" }, + }, + std::vector{ manifest3, manifest2, manifest }, + source + ), + PackageMatchFilter(PackageMatchField::Id, MatchType::Exact, "AppInstallerCliTest.TestExeInstaller"))); + }); + } + + SearchResult WorkflowTestSource::Search(const SearchRequest& request) const + { + std::string input; + if (request.Query) + { + input = request.Query->Value; + } + else if (!request.Inclusions.empty()) + { + input = request.Inclusions[0].Value; + } + else if (!request.Filters.empty()) + { + input = request.Filters[0].Value; + } + + SearchResult result; + for (const auto& testSourceResult : m_testSourceResults) + { + if (input.empty() || CaseInsensitiveEquals(input, testSourceResult.Query)) + { + testSourceResult.AddResults(result.Matches, shared_from_this()); + } + } + + return result; + } + + void WorkflowTestSource::AddResult(const TestSourceResult& testSourceResult) + { + m_testSourceResults.push_back(testSourceResult); + } + + std::shared_ptr CreateTestSource(std::vector&& testSourceResults) + { + return std::make_shared(std::move(testSourceResults)); + } + + TestContext::TestContext(std::ostream& out, std::istream& in) : TestContext(out, in, false, std::make_shared>()) + { + WorkflowTaskOverride wto + { RemoveInstaller, [](TestContext&) + { + // Do nothing; we never want to remove the test files. + } }; + + // Mark this one as used so that it doesn't anger the destructor. + wto.UseCount++; + + Override(wto); + } + + TestContext::TestContext(std::ostream& out, std::istream& in, bool isClone, std::shared_ptr> overrides) : + m_out(out), m_in(in), m_overrides(overrides), m_isClone(isClone), Context(out, in) + { + m_shouldExecuteWorkflowTask = [this](const Workflow::WorkflowTask& task) + { + auto itr = std::find_if(m_overrides->begin(), m_overrides->end(), [&](const WorkflowTaskOverride& wto) { return wto.Target == task; }); + + if (itr == m_overrides->end()) + { + return true; + } + else + { + itr->UseCount++; + itr->Override(*this); + return false; + } + }; + } + + TestContext::~TestContext() + { + if (!m_isClone) + { + for (const auto& wto : *m_overrides) + { + if (wto.UseCount == 0) + { + FAIL_CHECK("Unused override " + wto.Target.GetName()); + } + else if (wto.ExpectedUseCount != -1 && wto.ExpectedUseCount != wto.UseCount) + { + FAIL_CHECK("Used override count does not match expected " + wto.Target.GetName()); + } + } + } + } + + void TestContext::Override(const WorkflowTaskOverride& wto) + { + m_overrides->emplace_back(wto); + } + + std::unique_ptr TestContext::CreateSubContext() + { + auto clone = std::make_unique(m_out, m_in, true, m_overrides); + clone->SetFlags(this->GetFlags()); + CopyArgsToSubContext(clone.get()); + return clone; + } + + void OverrideForOpenSource(TestContext& context, std::shared_ptr testSource, bool overrideOpenCompositeSource) + { + context.Override({ "OpenSource", [=](TestContext& context) + { + context.Add(Source{ testSource }); + } }); + + if (overrideOpenCompositeSource) + { + context.Override({ "OpenCompositeSource", [](TestContext&) + { + } }); + } + } + + void OverrideForCompositeInstalledSource(TestContext& context, std::shared_ptr testSource) + { + context.Override({ "OpenSource", [](TestContext&) + { + } }); + + context.Override({ "OpenCompositeSource", [=](TestContext& context) + { + context.Add(Source{ testSource }); + } }); + } + + void OverrideForUpdateInstallerMotw(TestContext& context) + { + context.Override({ UpdateInstallerFileMotwIfApplicable, [](TestContext&) + { + } }); + } + + void OverrideForCheckExistingInstaller(TestContext& context) + { + context.Override({ CheckForExistingInstaller, [](TestContext&) + { + } }); + } + + void OverrideForShellExecute(TestContext& context, int expectedUseCount) + { + OverrideForCheckExistingInstaller(context); + + context.Override({ DownloadInstallerFile, [](TestContext& context) + { + context.Add({ {}, {} }); + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + }, expectedUseCount }); + + context.Override({ RenameDownloadedInstaller, [](TestContext&) + { + }, expectedUseCount }); + + OverrideForUpdateInstallerMotw(context); + } + + void OverrideForShellExecute(TestContext& context, std::vector& installationLog) + { + context.Override({ DownloadInstallerFile, [&installationLog](TestContext& context) + { + context.Add({ {}, {} }); + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + + auto dependency = Dependency(DependencyType::Package, context.Get().Id, context.Get().Version); + installationLog.push_back(dependency); + } }); + + context.Override({ RenameDownloadedInstaller, [](TestContext&) + { + } }); + + OverrideForUpdateInstallerMotw(context); + } + + void OverrideForPortableInstall(TestContext& context) + { + context.Override({ Workflow::details::PortableInstall, [](TestContext&) + { + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestPortableInstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + file.close(); + } }); + } + + void OverrideForPortableInstallFlow(TestContext& context) + { + context.Override({ DownloadInstallerFile, [](TestContext& context) + { + context.Add({ {}, {} }); + context.Add(TestDataFile("AppInstallerTestExeInstaller.exe")); + } }); + + context.Override({ RenameDownloadedInstaller, [](TestContext&) + { + } }); + + OverrideForUpdateInstallerMotw(context); + OverrideForPortableInstall(context); + } + + void OverridePortableInstaller(TestContext& context) + { + context.Override({ DownloadInstallerFile, [](TestContext& context) + { + std::filesystem::path tempDirectory = std::filesystem::temp_directory_path(); + const auto& installerPath = TestDataFile("AppInstallerTestExeInstaller.exe").GetPath(); + const auto& tempInstallerPath = tempDirectory / "AppInstallerTestExeInstaller.exe"; + std::filesystem::copy(installerPath, tempInstallerPath, std::filesystem::copy_options::overwrite_existing); + context.Add(tempInstallerPath); + + std::ifstream inStream{ tempInstallerPath, std::ifstream::binary }; + SHA256::HashBuffer fileHash = SHA256::ComputeHash(inStream); + context.Add({ fileHash, DownloadResult{ fileHash } }); + } }); + + context.Override({ RenameDownloadedInstaller, [](TestContext&) + { + } }); + + OverrideForUpdateInstallerMotw(context); + } + + void OverrideForExtractInstallerFromArchive(TestContext& context) + { + context.Override({ ExtractFilesFromArchive, [](TestContext&) + { + } }); + } + + void OverrideForVerifyAndSetNestedInstaller(TestContext& context) + { + context.Override({ VerifyAndSetNestedInstaller, [](TestContext&) + { + } }); + } + + void OverrideForMSIX(TestContext& context) + { + context.Override({ Workflow::details::MsixInstall, [](TestContext& context) + { + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestMsixInstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + + if (context.Contains(Execution::Data::InstallerPath)) + { + file << context.Get().u8string(); + } + else + { + file << context.Get()->Url; + } + + file.close(); + } }); + } + + void OverrideForMSStore(TestContext& context, bool isUpdate) + { + if (isUpdate) + { + context.Override({ MSStoreUpdate, [](TestContext& context) + { + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestMSStoreUpdated.txt"; + std::ofstream file(temp, std::ofstream::out); + file << context.Get()->ProductId; + file.close(); + } }); + } + else + { + context.Override({ MSStoreInstall, [](TestContext& context) + { + std::filesystem::path temp = std::filesystem::temp_directory_path(); + temp /= "TestMSStoreInstalled.txt"; + std::ofstream file(temp, std::ofstream::out); + file << context.Get()->ProductId; + file.close(); + } }); + } + + context.Override({ Workflow::EnsureStorePolicySatisfied, [](TestContext&) + { + } }); + } + + void OverrideOpenDependencySource(TestContext& context) + { + context.Override({ Workflow::OpenDependencySource, [](TestContext& context) + { + context.Add(Source{ std::make_shared() }); + } }); + } + + void OverrideEnableWindowsFeaturesDependencies(TestContext& context) + { + context.Override({ Workflow::EnableWindowsFeaturesDependencies, [](TestContext&) + { + } }); + } + + void OverrideRegisterStartupAfterReboot(TestContext& context) + { + context.Override({ "RegisterStartupAfterReboot", [](TestContext&) + { + } }); + } + + void OverrideDownloadInstallerFileForMSStoreDownload(TestContext& context) + { + context.Override({ DownloadInstallerFile, [](TestContext& context) + { + const auto& installer = context.Get().value(); + const auto& installerPath = context.Get(); + std::ofstream file(installerPath, std::ofstream::out | std::ofstream::trunc); + file << installer.Url; + file.close(); + context.Add({ {}, {} }); + } }); + } +} diff --git a/src/AppInstallerCLITests/WorkflowCommon.h b/src/AppInstallerCLITests/WorkflowCommon.h index 8bd5348d2d..e4f6333780 100644 --- a/src/AppInstallerCLITests/WorkflowCommon.h +++ b/src/AppInstallerCLITests/WorkflowCommon.h @@ -1,144 +1,144 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSource.h" -#include -#include -#include -#include -#include - -#define REQUIRE_TERMINATED_WITH(_context_,_hr_) \ - REQUIRE(_context_.IsTerminated()); \ - REQUIRE(_hr_ == _context_.GetTerminationHR()) - -namespace TestCommon -{ - using namespace std::string_view_literals; - - // Possible results returned when searching the WorkflowTestSource. - // If the search query matches with this object or is empty, it adds to the search results. - struct TestSourceResult - { - using AddResultsFunction = std::function&, std::weak_ptr)>; - TestSourceResult(std::string_view query, AddResultsFunction addResults) : Query(query), AddResults(addResults) {} - - std::string Query; - AddResultsFunction AddResults; - }; - - namespace TSR - { - const extern TestSourceResult TestQuery_ReturnOne; - const extern TestSourceResult TestQuery_ReturnTwo; - const extern TestSourceResult TestInstaller_Exe; - const extern TestSourceResult TestInstaller_Exe_Dependencies; - const extern TestSourceResult TestInstaller_Exe_DifferentInstallerType; - const extern TestSourceResult TestInstaller_Exe_ExpectedReturnCodes; - const extern TestSourceResult TestInstaller_Exe_IncompatibleInstallerType; - const extern TestSourceResult TestInstaller_Exe_LatestInstalled; - const extern TestSourceResult TestInstaller_Exe_LicenseAgreement; - const extern TestSourceResult TestInstaller_Exe_NothingInstalled; - const extern TestSourceResult TestInstaller_Exe_UnknownVersion; - const extern TestSourceResult TestInstaller_Exe_UnsupportedArguments; - const extern TestSourceResult TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems; - const extern TestSourceResult TestInstaller_Exe_UpgradeUsesAgreements; - const extern TestSourceResult TestInstaller_Msix; - const extern TestSourceResult TestInstaller_Msix_UpgradeRequiresExplicit; - const extern TestSourceResult TestInstaller_Msix_UpgradeUsesAgreements; - const extern TestSourceResult TestInstaller_Msix_WFDependency; - const extern TestSourceResult TestInstaller_MSStore; - const extern TestSourceResult TestInstaller_Zip; - const extern TestSourceResult TestInstaller_Portable; - } - - struct WorkflowTestSource : public TestSource - { - WorkflowTestSource() {} - WorkflowTestSource(std::vector&& testSourceResults) : m_testSourceResults(std::move(testSourceResults)) {} - - AppInstaller::Repository::SearchResult Search(const AppInstaller::Repository::SearchRequest& request) const override; - - void AddResult(const TestSourceResult& testSourceResult); - - private: - std::vector m_testSourceResults; - }; - - std::shared_ptr CreateTestSource(std::vector&& testSourceResults); - - struct TestContext; - - struct WorkflowTaskOverride - { - WorkflowTaskOverride(AppInstaller::CLI::Workflow::WorkflowTask::Func f, const std::function& o, int expectedUseCount = -1) : - Target(f), Override(o), ExpectedUseCount(expectedUseCount) {} - - WorkflowTaskOverride(std::string_view n, const std::function& o, int expectedUseCount = -1) : - Target(n), Override(o), ExpectedUseCount(expectedUseCount) {} - - WorkflowTaskOverride(const AppInstaller::CLI::Workflow::WorkflowTask& t, const std::function& o, int expectedUseCount = -1) : - Target(t), Override(o), ExpectedUseCount(expectedUseCount) {} - - // -1 means no check on actual use count, as long as it's used. - int ExpectedUseCount = -1; - int UseCount = 0; - AppInstaller::CLI::Workflow::WorkflowTask Target; - std::function Override; - }; - - // Enables overriding the behavior of specific workflow tasks. - struct TestContext : public AppInstaller::CLI::Execution::Context - { - TestContext(std::ostream& out, std::istream& in); - - TestContext(std::ostream& out, std::istream& in, bool isClone, std::shared_ptr> overrides); - - ~TestContext(); - - void Override(const WorkflowTaskOverride& wto); - - std::unique_ptr CreateSubContext() override; - - private: - std::shared_ptr> m_overrides; - std::ostream& m_out; - std::istream& m_in; - bool m_isClone = false; - }; - - void OverrideForOpenSource(TestContext& context, std::shared_ptr testSource, bool overrideOpenCompositeSource = false); - - void OverrideForCompositeInstalledSource(TestContext& context, std::shared_ptr testSource); - - void OverrideForUpdateInstallerMotw(TestContext& context); - - void OverrideForCheckExistingInstaller(TestContext& context); - - void OverrideForShellExecute(TestContext& context, int expectedUseCount = -1); - - void OverrideForShellExecute(TestContext& context, std::vector& installationLog); - - void OverrideForPortableInstall(TestContext& context); - - void OverrideForPortableInstallFlow(TestContext& context); - - void OverridePortableInstaller(TestContext& context); - - void OverrideForExtractInstallerFromArchive(TestContext& context); - - void OverrideForVerifyAndSetNestedInstaller(TestContext& context); - - void OverrideForMSIX(TestContext& context); - - void OverrideForMSStore(TestContext& context, bool isUpdate); - - void OverrideOpenDependencySource(TestContext& context); - - void OverrideEnableWindowsFeaturesDependencies(TestContext& context); - - void OverrideRegisterStartupAfterReboot(TestContext& context); - - void OverrideDownloadInstallerFileForMSStoreDownload(TestContext& context); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSource.h" +#include +#include +#include +#include +#include + +#define REQUIRE_TERMINATED_WITH(_context_,_hr_) \ + REQUIRE(_context_.IsTerminated()); \ + REQUIRE(_hr_ == _context_.GetTerminationHR()) + +namespace TestCommon +{ + using namespace std::string_view_literals; + + // Possible results returned when searching the WorkflowTestSource. + // If the search query matches with this object or is empty, it adds to the search results. + struct TestSourceResult + { + using AddResultsFunction = std::function&, std::weak_ptr)>; + TestSourceResult(std::string_view query, AddResultsFunction addResults) : Query(query), AddResults(addResults) {} + + std::string Query; + AddResultsFunction AddResults; + }; + + namespace TSR + { + const extern TestSourceResult TestQuery_ReturnOne; + const extern TestSourceResult TestQuery_ReturnTwo; + const extern TestSourceResult TestInstaller_Exe; + const extern TestSourceResult TestInstaller_Exe_Dependencies; + const extern TestSourceResult TestInstaller_Exe_DifferentInstallerType; + const extern TestSourceResult TestInstaller_Exe_ExpectedReturnCodes; + const extern TestSourceResult TestInstaller_Exe_IncompatibleInstallerType; + const extern TestSourceResult TestInstaller_Exe_LatestInstalled; + const extern TestSourceResult TestInstaller_Exe_LicenseAgreement; + const extern TestSourceResult TestInstaller_Exe_NothingInstalled; + const extern TestSourceResult TestInstaller_Exe_UnknownVersion; + const extern TestSourceResult TestInstaller_Exe_UnsupportedArguments; + const extern TestSourceResult TestInstaller_Exe_UpgradeAllWithDuplicateUpgradeItems; + const extern TestSourceResult TestInstaller_Exe_UpgradeUsesAgreements; + const extern TestSourceResult TestInstaller_Msix; + const extern TestSourceResult TestInstaller_Msix_UpgradeRequiresExplicit; + const extern TestSourceResult TestInstaller_Msix_UpgradeUsesAgreements; + const extern TestSourceResult TestInstaller_Msix_WFDependency; + const extern TestSourceResult TestInstaller_MSStore; + const extern TestSourceResult TestInstaller_Zip; + const extern TestSourceResult TestInstaller_Portable; + } + + struct WorkflowTestSource : public TestSource + { + WorkflowTestSource() {} + WorkflowTestSource(std::vector&& testSourceResults) : m_testSourceResults(std::move(testSourceResults)) {} + + AppInstaller::Repository::SearchResult Search(const AppInstaller::Repository::SearchRequest& request) const override; + + void AddResult(const TestSourceResult& testSourceResult); + + private: + std::vector m_testSourceResults; + }; + + std::shared_ptr CreateTestSource(std::vector&& testSourceResults); + + struct TestContext; + + struct WorkflowTaskOverride + { + WorkflowTaskOverride(AppInstaller::CLI::Workflow::WorkflowTask::Func f, const std::function& o, int expectedUseCount = -1) : + Target(f), Override(o), ExpectedUseCount(expectedUseCount) {} + + WorkflowTaskOverride(std::string_view n, const std::function& o, int expectedUseCount = -1) : + Target(n), Override(o), ExpectedUseCount(expectedUseCount) {} + + WorkflowTaskOverride(const AppInstaller::CLI::Workflow::WorkflowTask& t, const std::function& o, int expectedUseCount = -1) : + Target(t), Override(o), ExpectedUseCount(expectedUseCount) {} + + // -1 means no check on actual use count, as long as it's used. + int ExpectedUseCount = -1; + int UseCount = 0; + AppInstaller::CLI::Workflow::WorkflowTask Target; + std::function Override; + }; + + // Enables overriding the behavior of specific workflow tasks. + struct TestContext : public AppInstaller::CLI::Execution::Context + { + TestContext(std::ostream& out, std::istream& in); + + TestContext(std::ostream& out, std::istream& in, bool isClone, std::shared_ptr> overrides); + + ~TestContext(); + + void Override(const WorkflowTaskOverride& wto); + + std::unique_ptr CreateSubContext() override; + + private: + std::shared_ptr> m_overrides; + std::ostream& m_out; + std::istream& m_in; + bool m_isClone = false; + }; + + void OverrideForOpenSource(TestContext& context, std::shared_ptr testSource, bool overrideOpenCompositeSource = false); + + void OverrideForCompositeInstalledSource(TestContext& context, std::shared_ptr testSource); + + void OverrideForUpdateInstallerMotw(TestContext& context); + + void OverrideForCheckExistingInstaller(TestContext& context); + + void OverrideForShellExecute(TestContext& context, int expectedUseCount = -1); + + void OverrideForShellExecute(TestContext& context, std::vector& installationLog); + + void OverrideForPortableInstall(TestContext& context); + + void OverrideForPortableInstallFlow(TestContext& context); + + void OverridePortableInstaller(TestContext& context); + + void OverrideForExtractInstallerFromArchive(TestContext& context); + + void OverrideForVerifyAndSetNestedInstaller(TestContext& context); + + void OverrideForMSIX(TestContext& context); + + void OverrideForMSStore(TestContext& context, bool isUpdate); + + void OverrideOpenDependencySource(TestContext& context); + + void OverrideEnableWindowsFeaturesDependencies(TestContext& context); + + void OverrideRegisterStartupAfterReboot(TestContext& context); + + void OverrideDownloadInstallerFileForMSStoreDownload(TestContext& context); +} diff --git a/src/AppInstallerCLITests/Yaml.cpp b/src/AppInstallerCLITests/Yaml.cpp index d21d14e63d..3fadf731ba 100644 --- a/src/AppInstallerCLITests/Yaml.cpp +++ b/src/AppInstallerCLITests/Yaml.cpp @@ -1,180 +1,180 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Utility; -using namespace AppInstaller::YAML; - - -TEST_CASE("YamlParserTypes", "[YAML]") -{ - auto document = AppInstaller::YAML::Load(TestDataFile("Node-Types.yaml")); - - auto intUnquoted = document["IntegerUnquoted"]; - CHECK(intUnquoted.GetTagType() == Node::TagType::Int); - - auto intSingleQuoted = document["IntegerSingleQuoted"]; - CHECK(intSingleQuoted.GetTagType() == Node::TagType::Str); - - auto intDoubleQuoted = document["IntegerDoubleQuoted"]; - CHECK(intDoubleQuoted.GetTagType() == Node::TagType::Str); - - auto boolTrue = document["BooleanTrue"]; - CHECK(boolTrue.GetTagType() == Node::TagType::Bool); - - auto strTrue = document["StringTrue"]; - CHECK(strTrue.GetTagType() == Node::TagType::Str); - - auto boolFalse = document["BooleanFalse"]; - CHECK(boolFalse.GetTagType() == Node::TagType::Bool); - - auto strFalse = document["StringFalse"]; - CHECK(strFalse.GetTagType() == Node::TagType::Str); - - auto localTag = document["LocalTag"]; - CHECK(localTag.GetTagType() == Node::TagType::Unknown); -} - -TEST_CASE("YamlMergeMappingNode", "[YAML]") -{ - auto document = Load(TestDataFile("Node-Mapping.yaml")); - - auto mergeNode = document["MergeNode"]; - auto mergeNode2 = document["MergeNode2"]; - - REQUIRE(3 == mergeNode.size()); - REQUIRE(2 == mergeNode2.size()); - - mergeNode.MergeMappingNode(mergeNode2); - - REQUIRE(5 == mergeNode.size()); -} - -TEST_CASE("YamlMergeMappingNode_CaseInsensitive", "[YAML]") -{ - auto document = Load(TestDataFile("Node-Mapping.yaml")); - - auto mergeNode = document["MergeNode"]; - auto mergeNode2 = document["MergeNode2"]; - - REQUIRE(3 == mergeNode.size()); - REQUIRE(2 == mergeNode2.size()); - - mergeNode.MergeMappingNode(mergeNode2, true); - - REQUIRE(4 == mergeNode.size()); -} - -TEST_CASE("YamlMergeSequenceNode", "[YAML]") -{ - auto document = Load(TestDataFile("Node-Merge.yaml")); - auto document2 = Load(TestDataFile("Node-Merge2.yaml")); - - REQUIRE(3 == document["StrawHats"].size()); - REQUIRE(2 == document2["StrawHats"].size()); - - // Internally will call MergeMappingNode. - document["StrawHats"].MergeSequenceNode(document2["StrawHats"], "Bounty"); - REQUIRE(5 == document["StrawHats"].size()); -} - -TEST_CASE("YamlMergeSequenceNode_CaseInsensitive", "[YAML]") -{ - auto document = Load(TestDataFile("Node-Merge.yaml")); - auto document2 = Load(TestDataFile("Node-Merge2.yaml")); - - REQUIRE(3 == document["StrawHats"].size()); - REQUIRE(2 == document2["StrawHats"].size()); - - // Internally will call MergeMappingNode. - document["StrawHats"].MergeSequenceNode(document2["StrawHats"], "Name", true); - REQUIRE(4 == document["StrawHats"].size()); - - auto luffy = std::find_if( - document["StrawHats"].Sequence().begin(), - document["StrawHats"].Sequence().end(), - [](Node const& n) { return n["Name"].as() == "Monkey D Luffy"; }); - REQUIRE(luffy != document["StrawHats"].Sequence().end()); - - // From original node - REQUIRE((*luffy)["Bounty"].as() == "3,000,000,000"); - - // From merged node - REQUIRE((*luffy)["Fruit"].as() == "Gomu Gomu no Mi"); -} - -TEST_CASE("YamlMergeNode_MergeSequenceNoKey", "[YAML]") -{ - auto document = Load(TestDataFile("Node-Merge.yaml")); - auto document2 = Load(TestDataFile("Node-Merge2.yaml")); - - REQUIRE_THROWS_HR(document["StrawHats"].MergeSequenceNode(document2["StrawHats"], "Power"), APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA); -} - -TEST_CASE("YamlMappingNode", "[YAML]") -{ - auto document = Load(TestDataFile("Node-Mapping.yaml")); - - auto node = document["key"]; - REQUIRE(node.as() == "value"); - - auto node2 = document.GetChildNode("KEY"); - REQUIRE(node2.as() == "value"); - - auto node3 = document.GetChildNode("key"); - REQUIRE(node3.as() == "value"); - - auto node4 = document.GetChildNode("kEy"); - REQUIRE(node4.as() == "value"); - - auto node5 = document.GetChildNode("fake"); - REQUIRE(node5.IsNull()); - - auto node6 = document["repeatedkey"]; - REQUIRE(node6.as() == "repeated value"); - REQUIRE_THROWS_HR(document.GetChildNode("repeatedkey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); - - REQUIRE_THROWS_HR(document.GetChildNode("RepeatedKey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); - REQUIRE_THROWS_HR(document["RepeatedKey"], APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); -} - -TEST_CASE("YamlMappingNode_const", "[YAML]") -{ - const auto document = Load(TestDataFile("Node-Mapping.yaml")); - - auto node = document["key"]; - REQUIRE(node.as() == "value"); - - auto node2 = document.GetChildNode("KEY"); - REQUIRE(node2.as() == "value"); - - auto node3 = document.GetChildNode("key"); - REQUIRE(node3.as() == "value"); - - auto node4 = document.GetChildNode("kEy"); - REQUIRE(node4.as() == "value"); - - auto node5 = document.GetChildNode("fake"); - REQUIRE(node5.IsNull()); - - auto node6 = document["repeatedkey"]; - REQUIRE(node6.as() == "repeated value"); - REQUIRE_THROWS_HR(document.GetChildNode("repeatedkey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); - - REQUIRE_THROWS_HR(document.GetChildNode("RepeatedKey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); - REQUIRE_THROWS_HR(document["RepeatedKey"], APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); -} - -TEST_CASE("YamlContainsEscapeControlCode", "[YAML]") -{ - REQUIRE_THROWS_HR(Load(TestDataFile("ContainsEscapeControlCode.yaml")), APPINSTALLER_CLI_ERROR_LIBYAML_ERROR); -} - -TEST_CASE("YamlContainsTooManyNestedLayers", "[YAML]") -{ - REQUIRE_THROWS_HR(Load(TestDataFile("ContainsTooManyNestedLayers.yaml")), APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Utility; +using namespace AppInstaller::YAML; + + +TEST_CASE("YamlParserTypes", "[YAML]") +{ + auto document = AppInstaller::YAML::Load(TestDataFile("Node-Types.yaml")); + + auto intUnquoted = document["IntegerUnquoted"]; + CHECK(intUnquoted.GetTagType() == Node::TagType::Int); + + auto intSingleQuoted = document["IntegerSingleQuoted"]; + CHECK(intSingleQuoted.GetTagType() == Node::TagType::Str); + + auto intDoubleQuoted = document["IntegerDoubleQuoted"]; + CHECK(intDoubleQuoted.GetTagType() == Node::TagType::Str); + + auto boolTrue = document["BooleanTrue"]; + CHECK(boolTrue.GetTagType() == Node::TagType::Bool); + + auto strTrue = document["StringTrue"]; + CHECK(strTrue.GetTagType() == Node::TagType::Str); + + auto boolFalse = document["BooleanFalse"]; + CHECK(boolFalse.GetTagType() == Node::TagType::Bool); + + auto strFalse = document["StringFalse"]; + CHECK(strFalse.GetTagType() == Node::TagType::Str); + + auto localTag = document["LocalTag"]; + CHECK(localTag.GetTagType() == Node::TagType::Unknown); +} + +TEST_CASE("YamlMergeMappingNode", "[YAML]") +{ + auto document = Load(TestDataFile("Node-Mapping.yaml")); + + auto mergeNode = document["MergeNode"]; + auto mergeNode2 = document["MergeNode2"]; + + REQUIRE(3 == mergeNode.size()); + REQUIRE(2 == mergeNode2.size()); + + mergeNode.MergeMappingNode(mergeNode2); + + REQUIRE(5 == mergeNode.size()); +} + +TEST_CASE("YamlMergeMappingNode_CaseInsensitive", "[YAML]") +{ + auto document = Load(TestDataFile("Node-Mapping.yaml")); + + auto mergeNode = document["MergeNode"]; + auto mergeNode2 = document["MergeNode2"]; + + REQUIRE(3 == mergeNode.size()); + REQUIRE(2 == mergeNode2.size()); + + mergeNode.MergeMappingNode(mergeNode2, true); + + REQUIRE(4 == mergeNode.size()); +} + +TEST_CASE("YamlMergeSequenceNode", "[YAML]") +{ + auto document = Load(TestDataFile("Node-Merge.yaml")); + auto document2 = Load(TestDataFile("Node-Merge2.yaml")); + + REQUIRE(3 == document["StrawHats"].size()); + REQUIRE(2 == document2["StrawHats"].size()); + + // Internally will call MergeMappingNode. + document["StrawHats"].MergeSequenceNode(document2["StrawHats"], "Bounty"); + REQUIRE(5 == document["StrawHats"].size()); +} + +TEST_CASE("YamlMergeSequenceNode_CaseInsensitive", "[YAML]") +{ + auto document = Load(TestDataFile("Node-Merge.yaml")); + auto document2 = Load(TestDataFile("Node-Merge2.yaml")); + + REQUIRE(3 == document["StrawHats"].size()); + REQUIRE(2 == document2["StrawHats"].size()); + + // Internally will call MergeMappingNode. + document["StrawHats"].MergeSequenceNode(document2["StrawHats"], "Name", true); + REQUIRE(4 == document["StrawHats"].size()); + + auto luffy = std::find_if( + document["StrawHats"].Sequence().begin(), + document["StrawHats"].Sequence().end(), + [](Node const& n) { return n["Name"].as() == "Monkey D Luffy"; }); + REQUIRE(luffy != document["StrawHats"].Sequence().end()); + + // From original node + REQUIRE((*luffy)["Bounty"].as() == "3,000,000,000"); + + // From merged node + REQUIRE((*luffy)["Fruit"].as() == "Gomu Gomu no Mi"); +} + +TEST_CASE("YamlMergeNode_MergeSequenceNoKey", "[YAML]") +{ + auto document = Load(TestDataFile("Node-Merge.yaml")); + auto document2 = Load(TestDataFile("Node-Merge2.yaml")); + + REQUIRE_THROWS_HR(document["StrawHats"].MergeSequenceNode(document2["StrawHats"], "Power"), APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA); +} + +TEST_CASE("YamlMappingNode", "[YAML]") +{ + auto document = Load(TestDataFile("Node-Mapping.yaml")); + + auto node = document["key"]; + REQUIRE(node.as() == "value"); + + auto node2 = document.GetChildNode("KEY"); + REQUIRE(node2.as() == "value"); + + auto node3 = document.GetChildNode("key"); + REQUIRE(node3.as() == "value"); + + auto node4 = document.GetChildNode("kEy"); + REQUIRE(node4.as() == "value"); + + auto node5 = document.GetChildNode("fake"); + REQUIRE(node5.IsNull()); + + auto node6 = document["repeatedkey"]; + REQUIRE(node6.as() == "repeated value"); + REQUIRE_THROWS_HR(document.GetChildNode("repeatedkey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); + + REQUIRE_THROWS_HR(document.GetChildNode("RepeatedKey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); + REQUIRE_THROWS_HR(document["RepeatedKey"], APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); +} + +TEST_CASE("YamlMappingNode_const", "[YAML]") +{ + const auto document = Load(TestDataFile("Node-Mapping.yaml")); + + auto node = document["key"]; + REQUIRE(node.as() == "value"); + + auto node2 = document.GetChildNode("KEY"); + REQUIRE(node2.as() == "value"); + + auto node3 = document.GetChildNode("key"); + REQUIRE(node3.as() == "value"); + + auto node4 = document.GetChildNode("kEy"); + REQUIRE(node4.as() == "value"); + + auto node5 = document.GetChildNode("fake"); + REQUIRE(node5.IsNull()); + + auto node6 = document["repeatedkey"]; + REQUIRE(node6.as() == "repeated value"); + REQUIRE_THROWS_HR(document.GetChildNode("repeatedkey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); + + REQUIRE_THROWS_HR(document.GetChildNode("RepeatedKey"), APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); + REQUIRE_THROWS_HR(document["RepeatedKey"], APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY); +} + +TEST_CASE("YamlContainsEscapeControlCode", "[YAML]") +{ + REQUIRE_THROWS_HR(Load(TestDataFile("ContainsEscapeControlCode.yaml")), APPINSTALLER_CLI_ERROR_LIBYAML_ERROR); +} + +TEST_CASE("YamlContainsTooManyNestedLayers", "[YAML]") +{ + REQUIRE_THROWS_HR(Load(TestDataFile("ContainsTooManyNestedLayers.yaml")), APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); +} diff --git a/src/AppInstallerCLITests/YamlManifest.cpp b/src/AppInstallerCLITests/YamlManifest.cpp index df758fe857..7574007329 100644 --- a/src/AppInstallerCLITests/YamlManifest.cpp +++ b/src/AppInstallerCLITests/YamlManifest.cpp @@ -1,1845 +1,1845 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestCommon.h" -#include "TestSettings.h" -#include -#include -#include -#include -#include - -using namespace TestCommon; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Manifest::YamlParser; -using namespace AppInstaller::Manifest::YamlWriter; -using namespace AppInstaller::Utility; -using namespace AppInstaller::YAML; - -namespace -{ - using MultiValue = std::vector; - bool operator==(const MultiValue& a, const MultiValue& b) - { - if (a.size() != b.size()) - { - return false; - } - - for (size_t i = 0; i < a.size(); ++i) - { - if (a[i] != b[i]) - { - return false; - } - } - - return true; - } - - void ValidateError( - const ValidationError& error, - ValidationError::Level level, - AppInstaller::StringResource::StringId message, - std::string field, - std::string value) - { - REQUIRE(level == error.ErrorLevel); - REQUIRE(message == error.Message); - REQUIRE(field == error.Context); - REQUIRE(value == error.Value); - } - - void ValidateError(const ValidationError& error, ValidationError::Level level, AppInstaller::StringResource::StringId message) - { - ValidateError(error, level, message, std::string(), std::string()); - } - - std::vector ValidateManifest(const Manifest& manifest, bool fullValidation) - { - return ValidateManifest(manifest, ManifestValidateOption{ fullValidation }); - } - - struct ManifestExceptionMatcher : public Catch::Matchers::MatcherBase - { - ManifestExceptionMatcher(std::string expectedMessage, bool expectedWarningOnly = false) : - m_expectedMessage(expectedMessage), m_expectedWarningOnly(expectedWarningOnly) {} - - // Performs the test for this matcher - bool match(ManifestException const& e) const override - { - return e.GetManifestErrorMessage().find(m_expectedMessage) != std::string::npos && - e.IsWarningOnly() == m_expectedWarningOnly; - } - - virtual std::string describe() const override { - std::ostringstream ss; - ss << std::boolalpha << "Expected exception message: " << m_expectedMessage << " Expected IsWarningOnly: " << m_expectedWarningOnly; - return ss.str(); - } - - private: - std::string m_expectedMessage; - bool m_expectedWarningOnly; - }; - - ManifestValidateOption GetTestManifestValidateOption( - bool schemaValidationOnly = false, - bool errorOnVerifiedPublisher = false) - { - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.ThrowOnWarning = true; - validateOption.SchemaValidationOnly = schemaValidationOnly; - validateOption.ErrorOnVerifiedPublisherFields = errorOnVerifiedPublisher; - return validateOption; - } - - void TestManifest( - const std::filesystem::path& manifestPath, - const std::string& expectedMessage = {}, - bool expectedWarningOnly = false, - ManifestValidateOption validateOption = GetTestManifestValidateOption()) - { - INFO(manifestPath.u8string()); - - if (expectedMessage.empty()) - { - CHECK_NOTHROW(YamlParser::CreateFromPath(TestDataFile(manifestPath), validateOption)); - } - else - { - CHECK_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile(manifestPath), validateOption), ManifestException, ManifestExceptionMatcher(expectedMessage, expectedWarningOnly)); - } - } - - struct ManifestTestCase - { - std::string TestFile; - std::string ExpectedMessage = {}; - bool IsWarningOnly = false; - ManifestValidateOption ValidateOption = GetTestManifestValidateOption(); - }; - - void CopyTestDataFilesToFolder(const std::vector& testDataFiles, const std::filesystem::path& dest) - { - for (const auto& fileName : testDataFiles) - { - std::filesystem::copy(TestDataFile(fileName), dest); - } - } - - void RequireContainerInfoPresent(const std::vector& containers, const DesiredStateConfigurationContainerInfo& info) - { - INFO("Looking for container info: " << AppInstaller::ToIntegral(info.Type) << " - " << info.RepositoryURL << " - " << info.ModuleName); - - for (const auto& container : containers) - { - if (container.Type == info.Type && container.RepositoryURL == info.RepositoryURL && container.ModuleName == info.ModuleName) - { - REQUIRE(container.Resources.size() == info.Resources.size()); - for (const auto& resource : info.Resources) - { - INFO("Looking for resource: " << resource.Name); - bool foundResource = std::any_of(container.Resources.begin(), container.Resources.end(), [&](const auto& a) { return a.Name == resource.Name; }); - REQUIRE(foundResource); - } - - return; - } - } - - FAIL("Did not find a matching container."); - } - - void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, ManifestVer manifestVer = { s_ManifestVersionV1 }, bool isExported = false) - { - REQUIRE(manifest.Id == "microsoft.msixsdk"); - REQUIRE(manifest.Version == "1.7.32"); - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/support"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/privacy"); - REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); - REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/home"); - REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/license"); - REQUIRE(manifest.DefaultLocalization.Get() == "Copyright Microsoft Corporation"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/copyright"); - REQUIRE(manifest.DefaultLocalization.Get() == "This is MSIX SDK"); - REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); - REQUIRE(manifest.Moniker == "msixsdk"); - REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "appxsdk", "msixsdk" }); - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) - { - REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) - { - REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultPurchaseUrl.com"); - REQUIRE(manifest.DefaultLocalization.Get() == "Default installation notes"); - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default document label"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) - { - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://testIcon-en-US"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); - } - - if (!isExported) - { - REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); - REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector{ PlatformEnum::Desktop, PlatformEnum::Universal }); - REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); - REQUIRE(manifest.DefaultInstallerInfo.BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(manifest.DefaultInstallerInfo.Scope == ScopeEnum::Machine); - REQUIRE(manifest.DefaultInstallerInfo.InstallModes == std::vector{ InstallModeEnum::Interactive, InstallModeEnum::Silent, InstallModeEnum::SilentWithProgress }); - - const auto& defaultSwitches = manifest.DefaultInstallerInfo.Switches; - REQUIRE(defaultSwitches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Silent) == "/silence"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Interactive) == "/interactive"); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Log) == "/log="); - REQUIRE(defaultSwitches.at(InstallerSwitchType::InstallLocation) == "/dir="); - REQUIRE(defaultSwitches.at(InstallerSwitchType::Update) == "/upgrade"); - - REQUIRE(manifest.DefaultInstallerInfo.InstallerSuccessCodes == std::vector{ 1, static_cast(0x80070005) }); - REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); - REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); - REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); - REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "msix", "appxbundle", "msixbundle" }); - - const auto& dependencies = manifest.DefaultInstallerInfo.Dependencies; - REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsFeature, "IIS")); - REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsLibrary, "VC Runtime")); - REQUIRE(dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDep", "1.0.0")); - REQUIRE(dependencies.HasExactDependency(DependencyType::External, "Outside dependencies")); - REQUIRE(dependencies.Size() == 4); - - REQUIRE(manifest.DefaultInstallerInfo.Capabilities == MultiValue{ "internetClient" }); - REQUIRE(manifest.DefaultInstallerInfo.RestrictedCapabilities == MultiValue{ "runFullTrust" }); - REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); - REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) - { - REQUIRE(manifest.DefaultInstallerInfo.ReleaseDate == "2021-01-01"); - REQUIRE(manifest.DefaultInstallerInfo.InstallerAbortsTerminal); - REQUIRE(manifest.DefaultInstallerInfo.InstallLocationRequired); - REQUIRE(manifest.DefaultInstallerInfo.RequireExplicitUpgrade); - REQUIRE(manifest.DefaultInstallerInfo.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) - { - REQUIRE(manifest.DefaultInstallerInfo.DisplayInstallWarnings); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) - { - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerType == InstallerTypeEnum::Msi); - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).RelativeFilePath == "RelativeFilePath"); - REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).PortableCommandAlias == "PortableCommandAlias"); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp"); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.size() == 1); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).RelativeFilePath == "main.exe"); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).InvocationParameter == "/arg"); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) - { - REQUIRE(manifest.DefaultInstallerInfo.DownloadCommandProhibited); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) - { - REQUIRE(defaultSwitches.at(InstallerSwitchType::Repair) == "/repair"); - REQUIRE(manifest.DefaultInstallerInfo.RepairBehavior == RepairBehaviorEnum::Modify); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 }) - { - REQUIRE(manifest.DefaultInstallerInfo.ArchiveBinariesDependOnPath); - } - } - - if (isSingleton || isExported) - { - REQUIRE(manifest.Installers.size() == 1); - } - else - { - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_12 }) - { - REQUIRE(manifest.Installers.size() == 7); - } - else if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) - { - REQUIRE(manifest.Installers.size() == 5); - } - else if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) - { - REQUIRE(manifest.Installers.size() == 4); - } - else if (manifestVer == ManifestVer{ s_ManifestVersionV1_2 }) - { - REQUIRE(manifest.Installers.size() == 3); - } - else - { - REQUIRE(manifest.Installers.size() == 2); - } - } - - const ManifestInstaller& installer1 = manifest.Installers.at(0); - REQUIRE(installer1.Arch == Architecture::X86); - REQUIRE(installer1.Locale == "en-GB"); - REQUIRE(installer1.Platform == std::vector{ PlatformEnum::Desktop }); - REQUIRE(installer1.MinOSVersion == "10.0.1.0"); - REQUIRE(installer1.BaseInstallerType == InstallerTypeEnum::Msix); - REQUIRE(installer1.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.msix"); - REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer1.SignatureSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer1.Scope == ScopeEnum::User); - REQUIRE(installer1.InstallModes == std::vector{ InstallModeEnum::Interactive }); - - const auto& installer1Switches = installer1.Switches; - REQUIRE(installer1Switches.at(InstallerSwitchType::Custom) == "/c"); - REQUIRE(installer1Switches.at(InstallerSwitchType::SilentWithProgress) == "/sp"); - REQUIRE(installer1Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(installer1Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(installer1Switches.at(InstallerSwitchType::Log) == "/l="); - REQUIRE(installer1Switches.at(InstallerSwitchType::InstallLocation) == "/d="); - REQUIRE(installer1Switches.at(InstallerSwitchType::Update) == "/u"); - - REQUIRE(installer1.UpdateBehavior == UpdateBehaviorEnum::Install); - REQUIRE(installer1.Commands == MultiValue{ "makemsixPreview", "makeappxPreview" }); - REQUIRE(installer1.Protocols == MultiValue{ "protocol1preview", "protocol2preview" }); - REQUIRE(installer1.FileExtensions == MultiValue{ "appxbundle", "msixbundle", "appx", "msix" }); - - const auto& installer1Dependencies = installer1.Dependencies; - REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsFeature, "PreviewIIS")); - REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "Preview VC Runtime")); - REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDepPreview", "1.0.0")); - REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::External, "Preview Outside dependencies")); - REQUIRE(installer1Dependencies.Size() == 4); - - REQUIRE(installer1.Capabilities == MultiValue{ "internetClientPreview" }); - REQUIRE(installer1.RestrictedCapabilities == MultiValue{ "runFullTrustPreview" }); - REQUIRE(installer1.PackageFamilyName == "Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe"); - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) - { - REQUIRE(installer1.ReleaseDate == "2021-02-02"); - REQUIRE_FALSE(installer1.InstallerAbortsTerminal); - REQUIRE_FALSE(installer1.InstallLocationRequired); - REQUIRE_FALSE(installer1.RequireExplicitUpgrade); - REQUIRE(installer1.ElevationRequirement == ElevationRequirementEnum::ElevationRequired); - REQUIRE(installer1.UnsupportedOSArchitectures.size() == 1); - REQUIRE(installer1.UnsupportedOSArchitectures.at(0) == Architecture::Arm64); - REQUIRE(installer1.AppsAndFeaturesEntries.size() == 0); - REQUIRE(installer1.Markets.AllowedMarkets.size() == 0); - REQUIRE(installer1.Markets.ExcludedMarkets.size() == 1); - REQUIRE(installer1.Markets.ExcludedMarkets.at(0) == "US"); - REQUIRE(installer1.ExpectedReturnCodes.at(2).ReturnResponseEnum == ExpectedReturnCodeEnum::ContactSupport); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) - { - REQUIRE_FALSE(installer1.DisplayInstallWarnings); - REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); - REQUIRE(installer1.UnsupportedArguments.size() == 1); - REQUIRE(installer1.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Location); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) - { - // NestedInstaller metadata should not be populated unless the InstallerType is zip. - REQUIRE(installer1.NestedInstallerType == InstallerTypeEnum::Unknown); - REQUIRE(installer1.NestedInstallerFiles.size() == 0); - - REQUIRE(installer1.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp"); - REQUIRE(installer1.InstallationMetadata.Files.size() == 1); - REQUIRE(installer1.InstallationMetadata.Files.at(0).RelativeFilePath == "main.exe"); - REQUIRE(installer1.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); - REQUIRE(installer1.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer1.InstallationMetadata.Files.at(0).InvocationParameter == "/arg"); - REQUIRE(installer1.InstallationMetadata.Files.at(0).DisplayName == "DisplayName"); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) - { - REQUIRE_FALSE(installer1.DownloadCommandProhibited); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) - { - REQUIRE(installer1.Switches.at(InstallerSwitchType::Repair) == "/r"); - REQUIRE(installer1.RepairBehavior == RepairBehaviorEnum::Modify); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 }) - { - REQUIRE_FALSE(installer1.ArchiveBinariesDependOnPath); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) - { - auto containers = &manifest.DefaultInstallerInfo.DesiredStateConfiguration; - if (isExported) - { - containers = &manifest.Installers[0].DesiredStateConfiguration; - } - - RequireContainerInfoPresent(*containers, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); - } - - if (!isSingleton) - { - if (!isExported) - { - const ManifestInstaller& installer2 = manifest.Installers.at(1); - REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(installer2.Arch == Architecture::X64); - REQUIRE(installer2.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); - REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer2.ProductCode == "{Bar}"); - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) - { - REQUIRE(installer2.ReleaseDate == "2021-01-01"); - REQUIRE(installer2.InstallerAbortsTerminal); - REQUIRE(installer2.InstallLocationRequired); - REQUIRE(installer2.RequireExplicitUpgrade); - REQUIRE(installer2.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); - REQUIRE(installer2.UnsupportedOSArchitectures.size() == 1); - REQUIRE(installer2.UnsupportedOSArchitectures.at(0) == Architecture::Arm); - REQUIRE(installer2.AppsAndFeaturesEntries.size() == 1); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); - REQUIRE(installer2.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); - REQUIRE(installer2.Markets.AllowedMarkets.size() == 1); - REQUIRE(installer2.Markets.AllowedMarkets.at(0) == "US"); - REQUIRE(installer2.ExpectedReturnCodes.size() == 1); - REQUIRE(installer2.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) - { - const ManifestInstaller& installer3 = manifest.Installers.at(2); - REQUIRE(installer3.BaseInstallerType == InstallerTypeEnum::Portable); - REQUIRE(installer3.Arch == Architecture::X86); - REQUIRE(installer3.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.exe"); - REQUIRE(installer3.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer3.Commands == MultiValue{ "standalone" }); - REQUIRE(installer3.ExpectedReturnCodes.size() == 1); - REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); - REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); - REQUIRE_FALSE(installer3.DisplayInstallWarnings); - REQUIRE(installer3.UnsupportedArguments.size() == 1); - REQUIRE(installer3.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) - { - const ManifestInstaller& installer4 = manifest.Installers.at(3); - REQUIRE(installer4.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(installer4.Arch == Architecture::X64); - REQUIRE(installer4.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); - REQUIRE(installer4.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer4.ProductCode == "{Foo}"); - REQUIRE(installer4.NestedInstallerType == InstallerTypeEnum::Portable); - REQUIRE(installer4.NestedInstallerFiles.size() == 2); - REQUIRE(installer4.NestedInstallerFiles.at(0).RelativeFilePath == "relativeFilePath1"); - REQUIRE(installer4.NestedInstallerFiles.at(0).PortableCommandAlias == "portableAlias1"); - REQUIRE(installer4.NestedInstallerFiles.at(1).RelativeFilePath == "relativeFilePath2"); - REQUIRE(installer4.NestedInstallerFiles.at(1).PortableCommandAlias == "portableAlias2"); - REQUIRE(installer4.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp2"); - REQUIRE(installer4.InstallationMetadata.Files.size() == 1); - REQUIRE(installer4.InstallationMetadata.Files.at(0).RelativeFilePath == "main2.exe"); - REQUIRE(installer4.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Other); - REQUIRE(installer4.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer4.InstallationMetadata.Files.at(0).InvocationParameter == "/arg2"); - REQUIRE(installer4.InstallationMetadata.Files.at(0).DisplayName == "DisplayName2"); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) - { - REQUIRE(installer2.DownloadCommandProhibited); - REQUIRE(installer2.UpdateBehavior == UpdateBehaviorEnum::Deny); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) - { - REQUIRE(installer2.RepairBehavior == RepairBehaviorEnum::Uninstaller); - REQUIRE(installer2.Switches.at(InstallerSwitchType::Repair) == "/r"); - - const ManifestInstaller& installer5 = manifest.Installers.at(4); - REQUIRE(installer5.BaseInstallerType == InstallerTypeEnum::Burn); - REQUIRE(installer5.Arch == Architecture::X64); - REQUIRE(installer5.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); - REQUIRE(installer5.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer5.ProductCode == "{Bar}"); - REQUIRE(installer5.Switches.at(InstallerSwitchType::Repair) == "/repair"); - REQUIRE(installer5.RepairBehavior == RepairBehaviorEnum::Modify); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 }) - { - const ManifestInstaller& installer4 = manifest.Installers.at(3); - REQUIRE(installer4.ArchiveBinariesDependOnPath); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_12 }) - { - const ManifestInstaller& installer6 = manifest.Installers.at(5); - REQUIRE(installer6.BaseInstallerType == InstallerTypeEnum::Zip); - REQUIRE(installer6.Arch == Architecture::Neutral); - REQUIRE(installer6.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); - REQUIRE(installer6.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer6.NestedInstallerType == InstallerTypeEnum::Font); - REQUIRE(installer6.NestedInstallerFiles.size() == 5); - REQUIRE(installer6.NestedInstallerFiles.at(0).RelativeFilePath == "relativeFilePath1.otf"); - REQUIRE(installer6.NestedInstallerFiles.at(1).RelativeFilePath == "relativeFilePath2.ttf"); - REQUIRE(installer6.NestedInstallerFiles.at(2).RelativeFilePath == "relativeFilePath3.fnt"); - REQUIRE(installer6.NestedInstallerFiles.at(3).RelativeFilePath == "relativeFilePath4.ttc"); - REQUIRE(installer6.NestedInstallerFiles.at(4).RelativeFilePath == "relativeFilePath5.otc"); - - const ManifestInstaller& installer7 = manifest.Installers.at(6); - REQUIRE(installer7.BaseInstallerType == InstallerTypeEnum::Font); - REQUIRE(installer7.Arch == Architecture::Neutral); - REQUIRE(installer7.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); - REQUIRE(installer7.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) - { - RequireContainerInfoPresent(manifest.Installers[1].DesiredStateConfiguration, { { { "None/None" } } }); - REQUIRE(manifest.Installers[2].DesiredStateConfiguration.size() == 0); - } - } - - // Localization - REQUIRE(manifest.Localizations.size() == 1); - const ManifestLocalization& localization1 = manifest.Localizations.at(0); - REQUIRE(localization1.Locale == "en-GB"); - REQUIRE(localization1.Get() == "Microsoft UK"); - REQUIRE(localization1.Get() == "https://www.microsoft.com/UK"); - REQUIRE(localization1.Get() == "https://www.microsoft.com/support/UK"); - REQUIRE(localization1.Get() == "https://www.microsoft.com/privacy/UK"); - REQUIRE(localization1.Get() == "Microsoft UK"); - REQUIRE(localization1.Get() == "MSIX SDK UK"); - REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/home/UK"); - REQUIRE(localization1.Get() == "MIT License UK"); - REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/license/UK"); - REQUIRE(localization1.Get() == "Copyright Microsoft Corporation UK"); - REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/copyright/UK"); - REQUIRE(localization1.Get() == "This is MSIX SDK UK"); - REQUIRE(localization1.Get() == "The MSIX SDK project is an effort to enable developers UK"); - REQUIRE(localization1.Get() == MultiValue{ "appxsdkUK", "msixsdkUK" }); - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) - { - REQUIRE(localization1.Get() == "Release notes"); - REQUIRE(localization1.Get() == "https://ReleaseNotes.net"); - REQUIRE(localization1.Get().size() == 1); - REQUIRE(localization1.Get().at(0).Label == "Label"); - REQUIRE(localization1.Get().at(0).AgreementText == "Text"); - REQUIRE(localization1.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) - { - REQUIRE(localization1.Get() == "https://DefaultPurchaseUrl.com"); - REQUIRE(localization1.Get() == "Default installation notes"); - REQUIRE(localization1.Get().size() == 1); - REQUIRE(localization1.Get().at(0).DocumentLabel == "Default document label"); - REQUIRE(localization1.Get().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); - } - - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) - { - REQUIRE(localization1.Get().size() == 1); - REQUIRE(localization1.Get().at(0).Url == "https://localeTestIcon-en-GB"); - REQUIRE(localization1.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(localization1.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(localization1.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(localization1.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); - } - } - } - - struct ManifestShadowTestInfo - { - bool shadowDefaultLocale; - bool shadowEnGbLocale; - }; - - void VerifyV1ManifestContentCreatedWithShadow(const Manifest& manifest, ManifestShadowTestInfo shadowInfo, ManifestVer manifestVer = { s_ManifestVersionV1_5 }) - { - REQUIRE(manifest.Id == "microsoft.msixsdk"); - REQUIRE(manifest.Version == "1.7.32"); - REQUIRE(manifest.Installers.size() == 1); - - // Default localization - REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); - REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); - REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); - REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); - REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); - REQUIRE(manifest.DefaultLocalization.Get() == "This is MSIX SDK"); - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) - { - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - - if (shadowInfo.shadowDefaultLocale) - { - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://shadowIcon-default"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == SHA256::ConvertToBytes("1111111111111111111111111111111111111111111111111111111111111111")); - } - else - { - REQUIRE(manifest.DefaultLocalization.Get().size() == 1); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://testIcon-en-US"); - REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); - REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); - } - } - - // Localization - if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) - { - REQUIRE(manifest.Localizations.size() == 3); - - bool foundEnGbLocale = false; - bool foundfrFrLocale = false; - for (auto const& localization : manifest.Localizations) - { - if (localization.Locale == "en-GB") - { - REQUIRE(localization.Get() == "The MSIX SDK project is an effort to enable developers UK"); - if (shadowInfo.shadowEnGbLocale) - { - REQUIRE(localization.Get().size() == 1); - REQUIRE(localization.Get().at(0).Url == "https://shadowIcon-en-GB"); - REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("2222222222222222222222222222222222222222222222222222222222222222")); - } - else - { - REQUIRE(localization.Get().size() == 1); - REQUIRE(localization.Get().at(0).Url == "https://localeTestIcon-en-GB"); - REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); - } - - foundEnGbLocale = true; - } - else if (localization.Locale == "fr-FR") - { - REQUIRE(localization.Get().size() == 1); - REQUIRE(localization.Get().at(0).Url == "https://shadowIcon-fr-FR"); - REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Jpeg); - REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square20); - REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Dark); - REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("3333333333333333333333333333333333333333333333333333333333333333")); - foundfrFrLocale = true; - } - else - { - REQUIRE(localization.Locale == "es-MX"); - REQUIRE(localization.Get() == "The MSIX SDK project is an effort to enable developers MX"); - REQUIRE(localization.Get().size() == 1); - REQUIRE(localization.Get().at(0).Url == "https://localeTestIcon-es-MX"); - REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Png); - REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square32); - REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Light); - REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("4444444444444444444444444444444444444444444444444444444444444444")); - } - } - - REQUIRE(foundEnGbLocale); - REQUIRE(foundfrFrLocale); - } - } -} - -TEST_CASE("ReadPreviewGoodManifestAndVerifyContents", "[ManifestValidation]") -{ - auto manifestFile = TestDataFile("Manifest-Good.yaml"); - Manifest manifest = YamlParser::CreateFromPath(manifestFile); - - REQUIRE(manifest.Id == "microsoft.msixsdk"); - REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); - REQUIRE(manifest.Moniker == "msixsdk"); - REQUIRE(manifest.Version == "1.7.32"); - REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); - REQUIRE(manifest.Channel == "release"); - REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); - REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE"); - REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "0.0.0.0"); - REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); - REQUIRE(manifest.DefaultLocalization.Get() == "https://github.com/microsoft/msix-packaging"); - REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "msix", "appx" }); - REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); - REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); - REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); - REQUIRE(manifest.DefaultInstallerInfo.BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); - REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); - REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); - - // default switches - auto switches = manifest.DefaultInstallerInfo.Switches; - REQUIRE(switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(switches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); - REQUIRE(switches.at(InstallerSwitchType::Silent) == "/silence"); - REQUIRE(switches.at(InstallerSwitchType::Interactive) == "/interactive"); - REQUIRE(switches.at(InstallerSwitchType::Language) == "/en-us"); - REQUIRE(switches.at(InstallerSwitchType::Log) == "/log="); - REQUIRE(switches.at(InstallerSwitchType::InstallLocation) == "/dir="); - REQUIRE(switches.at(InstallerSwitchType::Update) == "/update"); - - // installers - REQUIRE(manifest.Installers.size() == 2); - ManifestInstaller installer1 = manifest.Installers.at(0); - REQUIRE(installer1.Arch == Architecture::X86); - REQUIRE(installer1.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx86.zip"); - REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); - REQUIRE(installer1.Locale == "en-US"); - REQUIRE(installer1.BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(installer1.Scope == ScopeEnum::User); - REQUIRE(installer1.PackageFamilyName == ""); - REQUIRE(installer1.ProductCode == "{Foo}"); - REQUIRE(installer1.UpdateBehavior == UpdateBehaviorEnum::Install); - - auto installer1Switches = installer1.Switches; - REQUIRE(installer1Switches.at(InstallerSwitchType::Custom) == "/c"); - REQUIRE(installer1Switches.at(InstallerSwitchType::SilentWithProgress) == "/sp"); - REQUIRE(installer1Switches.at(InstallerSwitchType::Silent) == "/s"); - REQUIRE(installer1Switches.at(InstallerSwitchType::Interactive) == "/i"); - REQUIRE(installer1Switches.at(InstallerSwitchType::Language) == "/en"); - REQUIRE(installer1Switches.at(InstallerSwitchType::Log) == "/l="); - REQUIRE(installer1Switches.at(InstallerSwitchType::InstallLocation) == "/d="); - REQUIRE(installer1Switches.at(InstallerSwitchType::Update) == "/u"); - - ManifestInstaller installer2 = manifest.Installers.at(1); - REQUIRE(installer2.Arch == Architecture::X64); - REQUIRE(installer2.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx64.zip"); - REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF0000")); - REQUIRE(installer2.Locale == "en-US"); - REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Exe); - REQUIRE(installer2.Scope == ScopeEnum::User); - REQUIRE(installer2.PackageFamilyName == ""); - REQUIRE(installer2.ProductCode == "{Foo}"); - REQUIRE(installer2.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); - - // Installer2 does not declare switches, it inherits switches from package default. - auto installer2Switches = installer2.Switches; - REQUIRE(installer2Switches.at(InstallerSwitchType::Custom) == "/custom"); - REQUIRE(installer2Switches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); - REQUIRE(installer2Switches.at(InstallerSwitchType::Silent) == "/silence"); - REQUIRE(installer2Switches.at(InstallerSwitchType::Interactive) == "/interactive"); - REQUIRE(installer2Switches.at(InstallerSwitchType::Language) == "/en-us"); - REQUIRE(installer2Switches.at(InstallerSwitchType::Log) == "/log="); - REQUIRE(installer2Switches.at(InstallerSwitchType::InstallLocation) == "/dir="); - REQUIRE(installer2Switches.at(InstallerSwitchType::Update) == "/update"); - - // Localization - REQUIRE(manifest.Localizations.size() == 1); - ManifestLocalization localization1 = manifest.Localizations.at(0); - REQUIRE(localization1.Locale == "es-MX"); - REQUIRE(localization1.Get() == "El proyecto MSIX SDK es habilita desarrolladores de diferentes"); - REQUIRE(localization1.Get() == "https://github.com/microsoft/msix-packaging/es-MX"); - REQUIRE(localization1.Get() == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE-es-MX"); - - // Stream hash - std::ifstream stream(manifestFile.GetPath(), std::ios_base::in | std::ios_base::binary); - REQUIRE(!stream.fail()); - auto manifestHash = SHA256::ComputeHash(stream); - REQUIRE(manifestHash.size() == manifest.StreamSha256.size()); - REQUIRE(std::equal(manifestHash.begin(), manifestHash.end(), manifest.StreamSha256.begin())); -} - -TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]") -{ - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-Spaces.yaml")); - - REQUIRE(manifest.Id == "microsoft.msixsdk"); - REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); - REQUIRE(manifest.Moniker == "msixsdk"); - REQUIRE(manifest.Version == "1.7.32"); - REQUIRE(manifest.Channel == "release"); - REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "0.0.0.0"); - REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "msix", "appx" }); - REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); - REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); - REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); -} - -TEST_CASE("ReadGoodManifests", "[ManifestValidation]") -{ - ManifestTestCase TestCases[] = - { - { "Manifest-Good-InstallerTypeExeRoot-Silent.yaml" }, - { "Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml" }, - { "Manifest-Good-InstallerTypeExe-Silent.yaml" }, - { "Manifest-Good-InstallerTypeExe-SilentRoot.yaml" }, - { "Manifest-Good-InstallerUniqueness-DefaultLang.yaml" }, - { "Manifest-Good-InstallerUniqueness-DiffLangs.yaml" }, - { "Manifest-Good-InstallerUniqueness-DiffScope.yaml" }, - { "Manifest-Good-Minimum.yaml" }, - { "Manifest-Good-Minimum-InstallerType.yaml" }, - { "Manifest-Good-Switches.yaml" }, - { "Manifest-Good-DefaultExpectedReturnCodeInInstallerSuccessCodes.yaml" }, - { "Manifest-Good-InstallerTypeZip-PortableExe.yaml" }, - { "Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml" }, - }; - - for (auto const& testCase : TestCases) - { - TestManifest(testCase.TestFile); - } -} - -TEST_CASE("ReadBadManifests", "[ManifestValidation]") -{ - ManifestTestCase TestCases[] = - { - { "Manifest-Bad-ArchInvalid.yaml", "Invalid field value. [Architecture]" }, - { "Manifest-Bad-ArchMissing.yaml", "Missing required property 'Arch'" }, - { "Manifest-Bad-Channel-NotSupported.yaml", "Field is not supported. [Channel]" }, - { "Manifest-Bad-DifferentCase-camelCase.yaml", "All field names should be PascalCased. [installerType]" }, - { "Manifest-Bad-DifferentCase-lower.yaml", "All field names should be PascalCased. [installertype]" }, - { "Manifest-Bad-DifferentCase-UPPER.yaml", "All field names should be PascalCased. [INSTALLERTYPE]" }, - { "Manifest-Bad-DuplicateKey.yaml", "Duplicate field found in the manifest." }, - { "Manifest-Bad-DuplicateKey-DifferentCase.yaml", "Duplicate field found in the manifest." }, - { "Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml", "Duplicate field found in the manifest." }, - { "Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml", "Duplicate installer return code found." }, - { "Manifest-Bad-DuplicateReturnCode-SuccessCodes.yaml", "Duplicate installer return code found." }, - { "Manifest-Bad-DuplicateSha256.yaml", "Multiple Installer URLs found with the same InstallerSha256. Please ensure the accuracy of the URLs.", true }, - { "Manifest-Bad-IdInvalid.yaml", "Failed to validate against schema associated with property name 'Id'" }, - { "Manifest-Bad-IdMissing.yaml", "Missing required property 'Id'" }, - { "Manifest-Bad-InconsistentSha256.yaml", "The values of InstallerSha256 do not match for all instances of the same InstallerUrl." }, - { "Manifest-Bad-InstallersMissing.yaml", "Missing required property 'Installers'" }, - { "Manifest-Bad-InstallerTypeExe-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, - { "Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, - { "Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, - { "Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, - { "Manifest-Bad-InstallerTypeInvalid.yaml", "Invalid field value. [InstallerType]" }, - { "Manifest-Bad-InstallerTypeMissing.yaml", "Invalid field value. [InstallerType]" }, - { "Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml", "Only zero or one entry for Apps and Features may be specified for InstallerType portable." }, - { "Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml", "Only zero or one value for Commands may be specified for InstallerType portable." }, - { "Manifest-Bad-InstallerTypePortable-InvalidScope.yaml", "Scope is not supported for InstallerType portable." }, - { "Manifest-Bad-InstallerTypeZip-DuplicateCommandAlias.yaml", "Duplicate portable command alias found." }, - { "Manifest-Bad-InstallerTypeZip-DuplicateRelativeFilePath.yaml", "Duplicate relative file path found." }, - { "Manifest-Bad-InstallerTypeZip-InvalidPortableCommandAlias.yaml", "Portable command alias must not point to a location outside of base directory." }, - { "Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml", "Relative file path must not point to a location outside of archive directory" }, - { "Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml", "Required field missing. [RelativeFilePath]" }, - { "Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml", "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes." }, - { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml", "Required field missing. [NestedInstallerFiles]" }, - { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml", "Required field missing. [NestedInstallerType]" }, - { "Manifest-Bad-InstallerUniqueness.yaml", "Duplicate installer entry found." }, - { "Manifest-Bad-InstallerUniqueness-DefaultScope.yaml", "Duplicate installer entry found." }, - { "Manifest-Bad-InstallerUniqueness-DefaultValues.yaml", "Duplicate installer entry found." }, - { "Manifest-Bad-InstallerUniqueness-SameLang.yaml", "Duplicate installer entry found." }, - { "Manifest-Bad-LicenseMissing.yaml", "Missing required property 'License'" }, - { "Manifest-Bad-NameMissing.yaml", "Missing required property 'Name'" }, - { "Manifest-Bad-PublisherMissing.yaml", "Missing required property 'Publisher'" }, - { "Manifest-Bad-Sha256Invalid.yaml", "Failed to validate against schema associated with property name 'Sha256'" }, - { "Manifest-Bad-Sha256Missing.yaml", "Required field missing. [InstallerSha256]" }, - { "Manifest-Bad-SwitchInvalid.yaml", "Unknown field. [NotASwitch]", true }, - { "Manifest-Bad-UnknownProperty.yaml", "Unknown field. [Fake]", true }, - { "Manifest-Bad-UnsupportedVersion.yaml", "Unsupported ManifestVersion" }, - { "Manifest-Bad-UrlInvalid.yaml", "Invalid field value. [InstallerUrl]" }, - { "Manifest-Bad-UrlMissing.yaml", "Required field missing. [InstallerUrl]" }, - { "Manifest-Bad-VersionInvalid.yaml", "Failed to validate against schema associated with property name 'Version'" }, - { "Manifest-Bad-VersionMissing.yaml", "Missing required property 'Version'" }, - { "Manifest-Bad-InvalidManifestVersionValue.yaml", "Failed to validate against schema associated with property name 'ManifestVersion'" }, - { "InstallFlowTest_MSStore.yaml", "Field value is not supported. [InstallerType] Value: msstore" }, - { "Manifest-Bad-PackageFamilyNameOnMSI.yaml", "The specified installer type does not support PackageFamilyName. [InstallerType] Value: msi", true }, - { "Manifest-Bad-ProductCodeOnMSIX.yaml", "The specified installer type does not support ProductCode. [InstallerType] Value: msix" }, - { "Manifest-Bad-InvalidUpdateBehavior.yaml", "Invalid field value. [UpgradeBehavior]" }, - { "Manifest-Bad-InvalidLocale.yaml", "The locale value is not a well formed bcp47 language tag." }, - { "Manifest-Bad-AppsAndFeaturesEntriesOnMSIX.yaml", "The specified installer type does not write to Apps and Features entry." }, - { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers. [Agreement]", true }, - { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers. [Agreement]", false, GetTestManifestValidateOption(false, true) }, - { "Manifest-Bad-ApproximateVersionInPackageVersion.yaml", "Approximate version not allowed. [PackageVersion]" }, - { "Manifest-Bad-ApproximateVersionInArpVersion.yaml", "Approximate version not allowed. [DisplayVersion]" }, - { "Manifest-Bad-InstallerTypeZip-PortableNotExe.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, - { "Manifest-Bad-InstallerTypeZip-PortableNotExe_Root.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, - }; - - for (auto const& testCase : TestCases) - { - TestManifest(testCase.TestFile, testCase.ExpectedMessage, testCase.IsWarningOnly, testCase.ValidateOption); - } -} - -TEST_CASE("ManifestEncoding", "[ManifestValidation]") -{ - ManifestTestCase TestCases[] = - { - { "Manifest-Encoding-ANSI.yaml" }, - { "Manifest-Encoding-UTF8.yaml" }, - { "Manifest-Encoding-UTF8-BOM.yaml" }, - { "Manifest-Encoding-UTF16BE.yaml" }, - { "Manifest-Encoding-UTF16BE-BOM.yaml" }, - { "Manifest-Encoding-UTF16LE.yaml" }, - { "Manifest-Encoding-UTF16LE-BOM.yaml" }, - }; - - for (auto const& testCase : TestCases) - { - INFO(testCase.TestFile); - Manifest manifest = YamlParser::CreateFromPath(TestDataFile(testCase.TestFile), GetTestManifestValidateOption()); - REQUIRE(manifest.DefaultLocalization.Get() == u8"MSIX SDK\xA9"); - } -} - -TEST_CASE("ComplexSystemReference", "[ManifestValidation]") -{ - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-SystemReferenceComplex.yaml")); - - REQUIRE(manifest.Installers.size() == 4); - - // MSIX installer does inherit - auto& installer = manifest.Installers[0]; - REQUIRE(installer.BaseInstallerType == InstallerTypeEnum::Msix); - REQUIRE(installer.Arch == Architecture::X86); - REQUIRE(installer.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); - REQUIRE(installer.ProductCode == ""); - - // MSI installer does inherit - auto& installer1 = manifest.Installers[1]; - REQUIRE(installer1.BaseInstallerType == InstallerTypeEnum::Msi); - REQUIRE(installer1.Arch == Architecture::X86); - REQUIRE(installer1.PackageFamilyName == ""); - REQUIRE(installer1.ProductCode == "{Foo}"); - - // MSIX installer with override - auto& installer2 = manifest.Installers[2]; - REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Msix); - REQUIRE(installer2.Arch == Architecture::X64); - REQUIRE(installer2.PackageFamilyName == "Override_8wekyb3d8bbwe"); - REQUIRE(installer2.ProductCode == ""); - - // MSI installer with override - auto& installer3 = manifest.Installers[3]; - REQUIRE(installer3.BaseInstallerType == InstallerTypeEnum::Msi); - REQUIRE(installer3.Arch == Architecture::X64); - REQUIRE(installer3.PackageFamilyName == ""); - REQUIRE(installer3.ProductCode == "Override"); -} - -TEST_CASE("ManifestVersionExtensions", "[ManifestValidation]") -{ - REQUIRE(!ManifestVer("1.0.0"sv).HasExtension("msstore")); - REQUIRE(!ManifestVer("1.0.0-other"sv).HasExtension("msstore")); - REQUIRE(!ManifestVer("1.0.0-other-other2"sv).HasExtension("msstore")); - - REQUIRE(ManifestVer("1.0.0-msstore"sv).HasExtension("msstore")); - REQUIRE(ManifestVer("1.0.0-msstore.2"sv).HasExtension("msstore")); - REQUIRE(ManifestVer("1.0.0-other-msstore.2"sv).HasExtension("msstore")); - REQUIRE(ManifestVer("1.0.0-msstore.2-other"sv).HasExtension("msstore")); -} - -void ValidateGoodManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder(singleton, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); - VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ version }); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder(multiFiles, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ version }); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ version }); -} - -#define WINGET_VALIDATE_GOOD_MANIFEST_VERSION(_version_) TEST_CASE("ValidateGoodManifestAndVerifyContents_" #_version_ , "[ManifestValidation][ManifestVersionValidation]") \ -{ \ - ValidateGoodManifestAndVerifyContents( \ - { "ManifestV" #_version_ "-Singleton.yaml" }, \ - { \ - "ManifestV" #_version_ "-MultiFile-Version.yaml", \ - "ManifestV" #_version_ "-MultiFile-Installer.yaml", \ - "ManifestV" #_version_ "-MultiFile-DefaultLocale.yaml", \ - "ManifestV" #_version_ "-MultiFile-Locale.yaml" \ - }, \ - s_ManifestVersionV ## _version_); \ -} - -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_1) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_2) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_4) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_5) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_6) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_7) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_9) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_10) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_12) -WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_28) - -void WriteSingletonManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) -{ - TempDirectory singletonDirectory{ "SingletonManifest" }; - CopyTestDataFilesToFolder(singleton, singletonDirectory); - Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); - - TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; - std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; - YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); - - REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); - Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); - VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ version }, true); - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder(multiFiles, multiFileDirectory); - - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); - TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; - std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; - YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); - - REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); - Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); - VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ version }, true); -} - -#define WINGET_WRITE_VERIFY_MANIFEST_VERSION(_version_) TEST_CASE("WriteSingletonManifestAndVerifyContents_" #_version_ , "[ManifestCreation][ManifestVersionCreation]") \ -{ \ - WriteSingletonManifestAndVerifyContents( \ - { "ManifestV" #_version_ "-Singleton.yaml" }, \ - { \ - "ManifestV" #_version_ "-MultiFile-Version.yaml", \ - "ManifestV" #_version_ "-MultiFile-Installer.yaml", \ - "ManifestV" #_version_ "-MultiFile-DefaultLocale.yaml", \ - "ManifestV" #_version_ "-MultiFile-Locale.yaml" \ - }, \ - s_ManifestVersionV ## _version_); \ -} - -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_1) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_2) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_4) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_5) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_6) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_7) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_9) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_10) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_12) -WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_28) - -// 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", "[ManifestCreation][ManifestVersionCreation]") -{ - // Read manifest - TempDirectory testDirectory{ "TestManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_10-InstallerAuthentication.yaml" }, testDirectory); - Manifest testManifest = YamlParser::CreateFromPath(testDirectory); - - // Validate schema - ManifestValidateOption validateOption; - validateOption.SchemaValidationOnly = true; - validateOption.ThrowOnWarning = true; - YamlParser::CreateFromPath(testDirectory, validateOption); - - // 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(exportedManifest.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()); -} - -// PowerShell DSC is not supported in the community repo and will cause manifest validation failure. -TEST_CASE("ReadWriteValidateV1_28ManifestWithPowerShellDSC", "[ManifestCreation][ManifestVersionCreation]") -{ - // Read manifest - TempDirectory testDirectory{ "TestManifest" }; - CopyTestDataFilesToFolder({ "ManifestV1_28-PowerShellDSC.yaml" }, testDirectory); - Manifest testManifest = YamlParser::CreateFromPath(testDirectory); - - // Validate schema - ManifestValidateOption validateOption; - validateOption.SchemaValidationOnly = true; - validateOption.ThrowOnWarning = true; - YamlParser::CreateFromPath(testDirectory, validateOption); - - // TODO: Update ValidateManifest - // TODO: Update this test with similar validations - - // Verify content - REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_28 }); - REQUIRE(testManifest.Installers.size() == 1); - REQUIRE(testManifest.Installers[0].DesiredStateConfiguration.size() == 3); - - RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { "https://www.powershellgallery.com/api/v2", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); - RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { "https://mcr.microsoft.com/", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); - RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); - - // Manifest Validation. Only error is "PowerShell not supported". - auto errors = ValidateManifest(testManifest, true); - REQUIRE(errors.size() == 1); - REQUIRE(errors[0].GetErrorMessage() == "Field is not supported."); - REQUIRE(errors[0].Context == "DesiredStateConfiguration.PowerShell"); - - // 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(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_28 }); - REQUIRE(exportedManifest.Installers.size() == 1); - REQUIRE(exportedManifest.Installers[0].DesiredStateConfiguration.size() == 3); - - RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { "https://www.powershellgallery.com/api/v2", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); - RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { "https://mcr.microsoft.com/", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); - RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); -} - -TEST_CASE("WriteManifestWithMultipleLocale", "[ManifestCreation]") -{ - Manifest multiLocaleManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); - TempDirectory exportedDirectory{ "exported" }; - std::filesystem::path generatedManifestPath = exportedDirectory.GetPath() / "testManifestWithMultipleLocale.yaml"; - YamlWriter::OutputYamlFile(multiLocaleManifest, multiLocaleManifest.Installers[0], generatedManifestPath); - - REQUIRE(std::filesystem::exists(generatedManifestPath)); - Manifest generatedManifest = YamlParser::CreateFromPath(generatedManifestPath); - REQUIRE(generatedManifest.Localizations.size() == 2); -} - -TEST_CASE("WriteManifestWithMSStoreInstaller", "[ManifestCreation]") -{ - Manifest msstoreManifest = YamlParser::CreateFromPath(TestDataFile("DownloadFlowTest_MSStore.yaml")); - TempDirectory exportedDirectory{ "exported" }; - std::filesystem::path generatedManifestPath = exportedDirectory.GetPath() / "testManifestWithMultipleLocale.yaml"; - msstoreManifest.ManifestVersion = ManifestVer{ "1.1.0" }; - YamlWriter::OutputYamlFile(msstoreManifest, msstoreManifest.Installers[0], generatedManifestPath); - - REQUIRE(std::filesystem::exists(generatedManifestPath)); - Manifest generatedManifest = YamlParser::CreateFromPath(generatedManifestPath); - REQUIRE(generatedManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::MSStore); - REQUIRE(!generatedManifest.Installers[0].ProductId.empty()); -} - -YamlManifestInfo CreateYamlManifestInfo(std::string testDataFile) -{ - YamlManifestInfo result; - result.Root = AppInstaller::YAML::Load(TestDataFile(testDataFile)); - result.FileName = testDataFile; - return result; -} - -TEST_CASE("MultifileManifestInputValidation", "[ManifestValidation]") -{ - auto previewManifest = CreateYamlManifestInfo("Manifest-Good.yaml"); - auto v1SingletonManifest = CreateYamlManifestInfo("ManifestV1-Singleton.yaml"); - auto v1VersionManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Version.yaml"); - auto v1InstallerManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Installer.yaml"); - auto v1DefaultLocaleManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-DefaultLocale.yaml"); - auto v1LocaleManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Locale.yaml"); - - { - // Preview and multi file manifest together - std::vector input = { previewManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("Preview manifest does not support multi file manifest format")); - } - - { - // Singleton and multi file manifest together - std::vector input = { v1SingletonManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should not contain file with the particular ManifestType. [ManifestType] Value: singleton")); - } - - { - // More than 1 version manifest - std::vector input = { v1VersionManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: version")); - } - - { - // More than 1 installer manifest - std::vector input = { v1VersionManifest, v1InstallerManifest, v1InstallerManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: installer")); - } - - { - // More than 1 default locale manifest - std::vector input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: defaultLocale")); - } - - { - // Duplicate locales - std::vector input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1LocaleManifest, v1LocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest contains duplicate PackageLocale. [PackageLocale] Value: en-GB")); - } - - { - // default locale not match - auto defaultLocaleManifestCopy = v1DefaultLocaleManifest; - defaultLocaleManifestCopy.Root["PackageLocale"].SetScalar("fr-fr"); - std::vector input = { v1VersionManifest, v1InstallerManifest, defaultLocaleManifestCopy, v1LocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest")); - } - - { - // Package Id does not match - auto installerManifestCopy = v1InstallerManifest; - installerManifestCopy.Root["PackageIdentifier"].SetScalar("Another.Identifier"); - std::vector input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. [PackageIdentifier] Value: Another.Identifier")); - } - - { - // Package Version does not match - auto installerManifestCopy = v1InstallerManifest; - installerManifestCopy.Root["PackageVersion"].SetScalar("Another.Version"); - std::vector input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. [PackageVersion] Value: Another.Version")); - } - - { - // Incomplete multi file manifest, missing installer - std::vector input = { v1VersionManifest, v1DefaultLocaleManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest is incomplete")); - } - - { - // Incomplete multi file manifest, missing default locale - std::vector input = { v1VersionManifest, v1InstallerManifest }; - REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest is incomplete")); - } -} - -TEST_CASE("ManifestApplyLocale", "[ManifestValidation]") -{ - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); - - // No better alternative locale, default is used. - manifest.ApplyLocale("zh-CN"); - REQUIRE(manifest.CurrentLocalization.Locale == "es-MX"); - REQUIRE(manifest.CurrentLocalization.Get() == "es-MX package name"); - REQUIRE(manifest.CurrentLocalization.Get() == "es-MX publisher"); - - // en-US results in en-GB, which is better than default. - manifest.ApplyLocale("en-US"); - REQUIRE(manifest.CurrentLocalization.Locale == "en-GB"); - REQUIRE(manifest.CurrentLocalization.Get() == "en-GB package name"); - REQUIRE(manifest.CurrentLocalization.Get() == "en-GB publisher"); - - // fr-FR results in fr-FR, but only package name is localized. - manifest.ApplyLocale("fr-FR"); - REQUIRE(manifest.CurrentLocalization.Locale == "fr-FR"); - REQUIRE(manifest.CurrentLocalization.Get() == "fr-FR package name"); - REQUIRE(manifest.CurrentLocalization.Get() == "es-MX publisher"); -} - -TEST_CASE("ManifestLocalizationValidation", "[ManifestValidation]") -{ - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); - - // Set 1 locale to bad value - manifest.Localizations.at(0).Locale = "Invalid"; - - // Full validation should detect as error - auto errors = ValidateManifest(manifest, true); - REQUIRE(errors.size() == 1); - REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Error); - - // Not full validation should detect as warning - errors = ValidateManifest(manifest, false); - REQUIRE(errors.size() == 1); - REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Warning); -} - -TEST_CASE("PortableFileTypeValidation", "[ManifestValidation]") -{ - Manifest installerManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InstallerTypeZip-PortableNotExe.yaml")); - Manifest rootManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InstallerTypeZip-PortableNotExe_Root.yaml")); - Manifest uppercaseManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml")); - - // Regular validation should detect as error - auto errors = ValidateManifest(installerManifest, true); - REQUIRE(errors.size() == 1); - REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Error); - - errors = ValidateManifest(rootManifest, true); - REQUIRE(errors.size() == 1); - REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Error); - - // Should not error when full validation is set to false - errors = ValidateManifest(installerManifest, false); - REQUIRE(errors.size() == 0); - - errors = ValidateManifest(rootManifest, false); - REQUIRE(errors.size() == 0); - - // Uppercase file extension should be accepted (case-insensitive comparison) - errors = ValidateManifest(uppercaseManifest, true); - REQUIRE(errors.size() == 0); -} - -TEST_CASE("WindowsFeatureNameValidation", "[ManifestValidation][111981]") -{ - // An invalid Windows Feature name should produce an error regardless of the fullValidation flag - Manifest invalidManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InvalidWindowsFeatureName.yaml")); - - auto errors = ValidateManifest(invalidManifest, true); - REQUIRE(errors.size() == 1); - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InvalidWindowsFeatureName, "Invalid@Feature", ""); - - errors = ValidateManifest(invalidManifest, false); - REQUIRE(errors.size() == 1); - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InvalidWindowsFeatureName, "Invalid@Feature", ""); -} - -TEST_CASE("NetworkAddressInSwitchesValidation", "[ManifestValidation][111981]") -{ - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-NetworkAddressInSwitches.yaml")); - - auto errors = ValidateManifest(manifest, true); - REQUIRE(errors.size() == 1); - ValidateError(errors[0], ValidationError::Level::Warning, ManifestError::ContainsNetworkAddress, "http://evil.example.com", ""); - - ManifestValidateOption options{ true }; - options.ErrorOnNetworkAddressInSwitches = true; - errors = ValidateManifest(manifest, options); - REQUIRE(errors.size() == 1); - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::ContainsNetworkAddress, "http://evil.example.com", ""); - - errors = ValidateManifest(manifest, false); - REQUIRE(errors.size() == 0); -} - -TEST_CASE("BlockedMsiPropertyValidation", "[ManifestValidation][111981]") -{ - SECTION("Blocked property is detected under full validation") - { - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-BlockedMsiProperty.yaml")); - - auto errors = ValidateManifest(manifest, true); - REQUIRE(errors.size() == 1); - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::BlockedMsiProperty, "TRANSFORMS", ""); - - // Not checked when fullValidation is false - errors = ValidateManifest(manifest, false); - REQUIRE(errors.size() == 0); - } - - SECTION("Invalid MSI switches are detected under full validation") - { - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InvalidMsiSwitches.yaml")); - - auto errors = ValidateManifest(manifest, true); - REQUIRE(errors.size() == 1); - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InvalidMsiSwitches); - - // Not checked when fullValidation is false - errors = ValidateManifest(manifest, false); - REQUIRE(errors.size() == 0); - } -} - -TEST_CASE("ReadManifestAndValidateMsixInstallers_Success", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Good-MsixInstaller.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(0 == errors.size()); -} - -TEST_CASE("ReadManifestAndValidateMsixInstallers_InconsistentFields", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Bad-InconsistentMsixInstallerFields.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(4 == errors.size()); - - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::MsixSignatureHashFailed); - ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); - ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); - ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.0.0"); -} - -TEST_CASE("ReadManifestAndValidateMsixInstallers_NoSupportedPlatforms", "[ManifestValidation]") -{ - auto testFileName = "Manifest-Bad-NoSupportedPlatforms.yaml"; - TestDataFile testFile(testFileName); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(1 == errors.size()); - - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::NoSupportedPlatforms, "InstallerUrl", manifest.Installers.front().Url); -} - -TEST_CASE("ReadManifestAndValidateMsixInstallers_PackageVersionNotUINT64", "[ManifestValidation]") -{ - Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-MsixInstaller-PackageVersion.yaml")); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(1 == errors.size()); - - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); -} - -TEST_CASE("ReadManifestAndValidateMsixInstallers_MissingFields", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Bad-MissingMsixInstallerFields.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - for (bool treatErrorAsWarning : { false, true }) - { - auto errors = ValidateManifestInstallers(manifest, treatErrorAsWarning); - auto expectedLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error; - REQUIRE(2 == errors.size()); - - ValidateError(errors[0], expectedLevel, ManifestError::OptionalFieldMissing, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); - ValidateError(errors[1], expectedLevel, ManifestError::OptionalFieldMissing, "MinimumOSVersion", "10.0.0.0"); - } -} - -TEST_CASE("ReadManifestAndValidateMsixInstallers_Signed_Success", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Good-SignedMsixInstaller.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(0 == errors.size()); -} - -TEST_CASE("ReadManifestAndValidateMsixInstallers_Signed_InconsistentFields", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Bad-InconsistentSignedMsixInstallerFields.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(4 == errors.size()); - - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "SignatureSha256", "50562001202c8dad456474d3f20903138d0a15c44ee497c3d4f82e85edbf2f97"); - ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); - ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); - ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.0.0"); -} - -TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_Success", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Good-MsixBundleInstaller.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(0 == errors.size()); -} - -TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_WithStub_Success", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Good-MsixBundleInstaller-WithStub.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(0 == errors.size()); -} - -TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_InconsistentFields", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Bad-InconsistentMsixBundleInstallerFields.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(7 == errors.size()); - - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::MsixSignatureHashFailed); - - // Validate errors for the first msix package in the msix bundle - ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); - ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); - ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); - - // Validate errors for the second msix package in the msix bundle - ValidateError(errors[4], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); - ValidateError(errors[5], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); - ValidateError(errors[6], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); -} - -TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_Signed_Success", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Good-SignedMsixBundleInstaller.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(0 == errors.size()); -} - -TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_Signed_InconsistentFields", "[ManifestValidation]") -{ - TestDataFile testFile("Manifest-Bad-InconsistentSignedMsixBundleInstallerFields.yaml"); - Manifest manifest = YamlParser::CreateFromPath(testFile); - - // Update the installer path for testing - REQUIRE(1 == manifest.Installers.size()); - TestDataFile msixFile(manifest.Installers[0].Url.c_str()); - manifest.Installers[0].Url = msixFile.GetPath().u8string(); - - auto errors = ValidateManifestInstallers(manifest); - REQUIRE(7 == errors.size()); - - ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "SignatureSha256", "d70bd623f87b6ce4ddba4506c6000cf43ef3af4ab1207f5579ec43400de1623f"); - - // Validate errors for the first msix package in the msix bundle - ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); - ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); - ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); - - // Validate errors for the second msix package in the msix bundle - ValidateError(errors[4], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); - ValidateError(errors[5], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); - ValidateError(errors[6], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); -} - -TEST_CASE("ManifestArpVersionRange", "[ManifestValidation]") -{ - Manifest manifestNoArp = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-NoArpVersionDeclared.yaml")); - REQUIRE(manifestNoArp.GetArpVersionRange().IsEmpty()); - - Manifest manifestSingleArp = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-SingleArpVersionDeclared.yaml")); - auto arpRangeSingleArp = manifestSingleArp.GetArpVersionRange(); - REQUIRE(arpRangeSingleArp.GetMinVersion().ToString() == "11.0"); - REQUIRE(arpRangeSingleArp.GetMaxVersion().ToString() == "11.0"); - - Manifest manifestMultiArp = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultipleArpVersionDeclared.yaml")); - auto arpRangeMultiArp = manifestMultiArp.GetArpVersionRange(); - REQUIRE(arpRangeMultiArp.GetMinVersion().ToString() == "12.0"); - REQUIRE(arpRangeMultiArp.GetMaxVersion().ToString() == "13.0"); -} - -TEST_CASE("ManifestV1_10_SchemaHeaderValidations", "[ManifestValidation]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - - // Schema header not found - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderNotFound.yaml"),validateOption), ManifestException, ManifestExceptionMatcher("Schema header not found")); - - // Schema header not valid - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderInvalid.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The schema header is invalid. Please verify that the schema header is present and formatted correctly.")); - - // Schema header URL does not match the expected schema URL - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderURLPatternMismatch.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The schema header URL does not match the expected pattern.")); - - // Schema header ManifestType does not match the expected value - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderManifestTypeMismatch.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The manifest type in the schema header does not match the ManifestType property value in the manifest.")); - - // Schema header version does not match the expected version - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderManifestVersionMismatch.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The manifest version in the schema header does not match the ManifestVersion property value in the manifest.")); -} - -TEST_CASE("ShadowManifest", "[ShadowManifest]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.AllowShadowManifest = true; - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-Shadow-Installer.yaml", - "ManifestV1_5-Shadow-DefaultLocale.yaml", - "ManifestV1_5-Shadow-Locale.yaml", - "ManifestV1_5-Shadow-Locale2.yaml", - "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); - - auto shadowInfo = ManifestShadowTestInfo{}; - shadowInfo.shadowDefaultLocale = true; - shadowInfo.shadowEnGbLocale = true; - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContentCreatedWithShadow(multiFileManifest, shadowInfo); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContentCreatedWithShadow(mergedManifest, shadowInfo); -} - -TEST_CASE("ShadowManifest_SkipShadowDefaultLocale", "[ShadowManifest]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.AllowShadowManifest = true; - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-Shadow-Installer.yaml", - "ManifestV1_5-MultiFile-DefaultLocale.yaml", - "ManifestV1_5-Shadow-Locale.yaml", - "ManifestV1_5-Shadow-Locale2.yaml", - "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); - - auto shadowInfo = ManifestShadowTestInfo{}; - shadowInfo.shadowDefaultLocale = false; - shadowInfo.shadowEnGbLocale = true; - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContentCreatedWithShadow(multiFileManifest, shadowInfo); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContentCreatedWithShadow(mergedManifest, shadowInfo); -} - -TEST_CASE("ShadowManifest_SkipShadowLocalizationLocale", "[ShadowManifest]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.AllowShadowManifest = true; - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-Shadow-Installer.yaml", - "ManifestV1_5-Shadow-DefaultLocale.yaml", - "ManifestV1_5-MultiFile-Locale.yaml", - "ManifestV1_5-Shadow-Locale2.yaml", - "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); - - auto shadowInfo = ManifestShadowTestInfo{}; - shadowInfo.shadowDefaultLocale = true; - shadowInfo.shadowEnGbLocale = false; - - TempFile mergedManifestFile{ "merged.yaml" }; - Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); - VerifyV1ManifestContentCreatedWithShadow(multiFileManifest, shadowInfo); - - // Read from merged manifest should have the same content as multi file manifest - Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); - VerifyV1ManifestContentCreatedWithShadow(mergedManifest, shadowInfo); -} - -TEST_CASE("ShadowManifest_ShadowNotAllowed", "[ShadowManifest]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.AllowShadowManifest = false; - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-Shadow-Installer.yaml", - "ManifestV1_5-Shadow-DefaultLocale.yaml", - "ManifestV1_5-Shadow-Locale.yaml", - "ManifestV1_5-Shadow-Locale2.yaml", - "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile), ManifestException, ManifestExceptionMatcher("Shadow manifest is not allowed. [ManifestType] Value: shadow File: ManifestV1_5-Shadow-Shadow.yaml")); -} - -TEST_CASE("ShadowManifest_TwoShadowFiles", "[ShadowManifest]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.AllowShadowManifest = true; - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-Shadow-Installer.yaml", - "ManifestV1_5-Shadow-DefaultLocale.yaml", - "ManifestV1_5-Shadow-Shadow.yaml", - "ManifestV1_5-Shadow-Shadow2.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: shadow File: ManifestV1_5-Shadow-Shadow2.yaml")); -} - -TEST_CASE("ShadowManifest_NotVerifiedPublisher", "[ShadowManifest]") -{ - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.AllowShadowManifest = true; - validateOption.ErrorOnVerifiedPublisherFields = true; - - TempDirectory multiFileDirectory{ "MultiFileManifest" }; - CopyTestDataFilesToFolder({ - "ManifestV1_5-MultiFile-Version.yaml", - "ManifestV1_5-Shadow-Installer.yaml", - "ManifestV1_5-Shadow-DefaultLocale.yaml", - "ManifestV1_5-Shadow-Locale.yaml", - "ManifestV1_5-Shadow-Locale2.yaml", - "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); - - TempFile mergedManifestFile{ "merged.yaml" }; - REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile), ManifestException, ManifestExceptionMatcher("Field usage requires verified publishers. [Icons]")); -} - -TEST_CASE("Manifest_PackageFamilyNameInheritance", "[ManifestValidation]") -{ - std::filesystem::path testManifest; - - SECTION("MSIX inside Archive") - { - testManifest = "Manifest-MSIX-in-Archive.yaml"; - } - SECTION("MSIX in AppsAndFeatures") - { - testManifest = "Manifest-MSIX-in-AppsAndFeatures.yaml"; - } - - auto manifest = YamlParser::CreateFromPath(TestDataFile(testManifest), GetTestManifestValidateOption()); - - REQUIRE(!manifest.Installers.empty()); - REQUIRE(!manifest.Installers[0].PackageFamilyName.empty()); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestCommon.h" +#include "TestSettings.h" +#include +#include +#include +#include +#include + +using namespace TestCommon; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Manifest::YamlParser; +using namespace AppInstaller::Manifest::YamlWriter; +using namespace AppInstaller::Utility; +using namespace AppInstaller::YAML; + +namespace +{ + using MultiValue = std::vector; + bool operator==(const MultiValue& a, const MultiValue& b) + { + if (a.size() != b.size()) + { + return false; + } + + for (size_t i = 0; i < a.size(); ++i) + { + if (a[i] != b[i]) + { + return false; + } + } + + return true; + } + + void ValidateError( + const ValidationError& error, + ValidationError::Level level, + AppInstaller::StringResource::StringId message, + std::string field, + std::string value) + { + REQUIRE(level == error.ErrorLevel); + REQUIRE(message == error.Message); + REQUIRE(field == error.Context); + REQUIRE(value == error.Value); + } + + void ValidateError(const ValidationError& error, ValidationError::Level level, AppInstaller::StringResource::StringId message) + { + ValidateError(error, level, message, std::string(), std::string()); + } + + std::vector ValidateManifest(const Manifest& manifest, bool fullValidation) + { + return ValidateManifest(manifest, ManifestValidateOption{ fullValidation }); + } + + struct ManifestExceptionMatcher : public Catch::Matchers::MatcherBase + { + ManifestExceptionMatcher(std::string expectedMessage, bool expectedWarningOnly = false) : + m_expectedMessage(expectedMessage), m_expectedWarningOnly(expectedWarningOnly) {} + + // Performs the test for this matcher + bool match(ManifestException const& e) const override + { + return e.GetManifestErrorMessage().find(m_expectedMessage) != std::string::npos && + e.IsWarningOnly() == m_expectedWarningOnly; + } + + virtual std::string describe() const override { + std::ostringstream ss; + ss << std::boolalpha << "Expected exception message: " << m_expectedMessage << " Expected IsWarningOnly: " << m_expectedWarningOnly; + return ss.str(); + } + + private: + std::string m_expectedMessage; + bool m_expectedWarningOnly; + }; + + ManifestValidateOption GetTestManifestValidateOption( + bool schemaValidationOnly = false, + bool errorOnVerifiedPublisher = false) + { + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + validateOption.SchemaValidationOnly = schemaValidationOnly; + validateOption.ErrorOnVerifiedPublisherFields = errorOnVerifiedPublisher; + return validateOption; + } + + void TestManifest( + const std::filesystem::path& manifestPath, + const std::string& expectedMessage = {}, + bool expectedWarningOnly = false, + ManifestValidateOption validateOption = GetTestManifestValidateOption()) + { + INFO(manifestPath.u8string()); + + if (expectedMessage.empty()) + { + CHECK_NOTHROW(YamlParser::CreateFromPath(TestDataFile(manifestPath), validateOption)); + } + else + { + CHECK_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile(manifestPath), validateOption), ManifestException, ManifestExceptionMatcher(expectedMessage, expectedWarningOnly)); + } + } + + struct ManifestTestCase + { + std::string TestFile; + std::string ExpectedMessage = {}; + bool IsWarningOnly = false; + ManifestValidateOption ValidateOption = GetTestManifestValidateOption(); + }; + + void CopyTestDataFilesToFolder(const std::vector& testDataFiles, const std::filesystem::path& dest) + { + for (const auto& fileName : testDataFiles) + { + std::filesystem::copy(TestDataFile(fileName), dest); + } + } + + void RequireContainerInfoPresent(const std::vector& containers, const DesiredStateConfigurationContainerInfo& info) + { + INFO("Looking for container info: " << AppInstaller::ToIntegral(info.Type) << " - " << info.RepositoryURL << " - " << info.ModuleName); + + for (const auto& container : containers) + { + if (container.Type == info.Type && container.RepositoryURL == info.RepositoryURL && container.ModuleName == info.ModuleName) + { + REQUIRE(container.Resources.size() == info.Resources.size()); + for (const auto& resource : info.Resources) + { + INFO("Looking for resource: " << resource.Name); + bool foundResource = std::any_of(container.Resources.begin(), container.Resources.end(), [&](const auto& a) { return a.Name == resource.Name; }); + REQUIRE(foundResource); + } + + return; + } + } + + FAIL("Did not find a matching container."); + } + + void VerifyV1ManifestContent(const Manifest& manifest, bool isSingleton, ManifestVer manifestVer = { s_ManifestVersionV1 }, bool isExported = false) + { + REQUIRE(manifest.Id == "microsoft.msixsdk"); + REQUIRE(manifest.Version == "1.7.32"); + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/support"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/privacy"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/home"); + REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/license"); + REQUIRE(manifest.DefaultLocalization.Get() == "Copyright Microsoft Corporation"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://www.microsoft.com/msixsdk/copyright"); + REQUIRE(manifest.DefaultLocalization.Get() == "This is MSIX SDK"); + REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); + REQUIRE(manifest.Moniker == "msixsdk"); + REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "appxsdk", "msixsdk" }); + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(manifest.DefaultLocalization.Get() == "Default release notes"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultReleaseNotes.net"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Label == "DefaultLabel"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementText == "DefaultText"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).AgreementUrl == "https://DefaultAgreementUrl.net"); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(manifest.DefaultLocalization.Get() == "https://DefaultPurchaseUrl.com"); + REQUIRE(manifest.DefaultLocalization.Get() == "Default installation notes"); + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentLabel == "Default document label"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) + { + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://testIcon-en-US"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + } + + if (!isExported) + { + REQUIRE(manifest.DefaultInstallerInfo.Locale == "en-US"); + REQUIRE(manifest.DefaultInstallerInfo.Platform == std::vector{ PlatformEnum::Desktop, PlatformEnum::Universal }); + REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "10.0.0.0"); + REQUIRE(manifest.DefaultInstallerInfo.BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(manifest.DefaultInstallerInfo.Scope == ScopeEnum::Machine); + REQUIRE(manifest.DefaultInstallerInfo.InstallModes == std::vector{ InstallModeEnum::Interactive, InstallModeEnum::Silent, InstallModeEnum::SilentWithProgress }); + + const auto& defaultSwitches = manifest.DefaultInstallerInfo.Switches; + REQUIRE(defaultSwitches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Silent) == "/silence"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Log) == "/log="); + REQUIRE(defaultSwitches.at(InstallerSwitchType::InstallLocation) == "/dir="); + REQUIRE(defaultSwitches.at(InstallerSwitchType::Update) == "/upgrade"); + + REQUIRE(manifest.DefaultInstallerInfo.InstallerSuccessCodes == std::vector{ 1, static_cast(0x80070005) }); + REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); + REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); + REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); + REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "msix", "appxbundle", "msixbundle" }); + + const auto& dependencies = manifest.DefaultInstallerInfo.Dependencies; + REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsFeature, "IIS")); + REQUIRE(dependencies.HasExactDependency(DependencyType::WindowsLibrary, "VC Runtime")); + REQUIRE(dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDep", "1.0.0")); + REQUIRE(dependencies.HasExactDependency(DependencyType::External, "Outside dependencies")); + REQUIRE(dependencies.Size() == 4); + + REQUIRE(manifest.DefaultInstallerInfo.Capabilities == MultiValue{ "internetClient" }); + REQUIRE(manifest.DefaultInstallerInfo.RestrictedCapabilities == MultiValue{ "runFullTrust" }); + REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(manifest.DefaultInstallerInfo.ReleaseDate == "2021-01-01"); + REQUIRE(manifest.DefaultInstallerInfo.InstallerAbortsTerminal); + REQUIRE(manifest.DefaultInstallerInfo.InstallLocationRequired); + REQUIRE(manifest.DefaultInstallerInfo.RequireExplicitUpgrade); + REQUIRE(manifest.DefaultInstallerInfo.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(manifest.DefaultInstallerInfo.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(manifest.DefaultInstallerInfo.DisplayInstallWarnings); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) + { + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerType == InstallerTypeEnum::Msi); + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).RelativeFilePath == "RelativeFilePath"); + REQUIRE(manifest.DefaultInstallerInfo.NestedInstallerFiles.at(0).PortableCommandAlias == "PortableCommandAlias"); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp"); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.size() == 1); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).RelativeFilePath == "main.exe"); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(manifest.DefaultInstallerInfo.InstallationMetadata.Files.at(0).InvocationParameter == "/arg"); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) + { + REQUIRE(manifest.DefaultInstallerInfo.DownloadCommandProhibited); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) + { + REQUIRE(defaultSwitches.at(InstallerSwitchType::Repair) == "/repair"); + REQUIRE(manifest.DefaultInstallerInfo.RepairBehavior == RepairBehaviorEnum::Modify); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 }) + { + REQUIRE(manifest.DefaultInstallerInfo.ArchiveBinariesDependOnPath); + } + } + + if (isSingleton || isExported) + { + REQUIRE(manifest.Installers.size() == 1); + } + else + { + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_12 }) + { + REQUIRE(manifest.Installers.size() == 7); + } + else if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) + { + REQUIRE(manifest.Installers.size() == 5); + } + else if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) + { + REQUIRE(manifest.Installers.size() == 4); + } + else if (manifestVer == ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(manifest.Installers.size() == 3); + } + else + { + REQUIRE(manifest.Installers.size() == 2); + } + } + + const ManifestInstaller& installer1 = manifest.Installers.at(0); + REQUIRE(installer1.Arch == Architecture::X86); + REQUIRE(installer1.Locale == "en-GB"); + REQUIRE(installer1.Platform == std::vector{ PlatformEnum::Desktop }); + REQUIRE(installer1.MinOSVersion == "10.0.1.0"); + REQUIRE(installer1.BaseInstallerType == InstallerTypeEnum::Msix); + REQUIRE(installer1.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.msix"); + REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer1.SignatureSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer1.Scope == ScopeEnum::User); + REQUIRE(installer1.InstallModes == std::vector{ InstallModeEnum::Interactive }); + + const auto& installer1Switches = installer1.Switches; + REQUIRE(installer1Switches.at(InstallerSwitchType::Custom) == "/c"); + REQUIRE(installer1Switches.at(InstallerSwitchType::SilentWithProgress) == "/sp"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Log) == "/l="); + REQUIRE(installer1Switches.at(InstallerSwitchType::InstallLocation) == "/d="); + REQUIRE(installer1Switches.at(InstallerSwitchType::Update) == "/u"); + + REQUIRE(installer1.UpdateBehavior == UpdateBehaviorEnum::Install); + REQUIRE(installer1.Commands == MultiValue{ "makemsixPreview", "makeappxPreview" }); + REQUIRE(installer1.Protocols == MultiValue{ "protocol1preview", "protocol2preview" }); + REQUIRE(installer1.FileExtensions == MultiValue{ "appxbundle", "msixbundle", "appx", "msix" }); + + const auto& installer1Dependencies = installer1.Dependencies; + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsFeature, "PreviewIIS")); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::WindowsLibrary, "Preview VC Runtime")); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::Package, "Microsoft.MsixSdkDepPreview", "1.0.0")); + REQUIRE(installer1Dependencies.HasExactDependency(DependencyType::External, "Preview Outside dependencies")); + REQUIRE(installer1Dependencies.Size() == 4); + + REQUIRE(installer1.Capabilities == MultiValue{ "internetClientPreview" }); + REQUIRE(installer1.RestrictedCapabilities == MultiValue{ "runFullTrustPreview" }); + REQUIRE(installer1.PackageFamilyName == "Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe"); + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(installer1.ReleaseDate == "2021-02-02"); + REQUIRE_FALSE(installer1.InstallerAbortsTerminal); + REQUIRE_FALSE(installer1.InstallLocationRequired); + REQUIRE_FALSE(installer1.RequireExplicitUpgrade); + REQUIRE(installer1.ElevationRequirement == ElevationRequirementEnum::ElevationRequired); + REQUIRE(installer1.UnsupportedOSArchitectures.size() == 1); + REQUIRE(installer1.UnsupportedOSArchitectures.at(0) == Architecture::Arm64); + REQUIRE(installer1.AppsAndFeaturesEntries.size() == 0); + REQUIRE(installer1.Markets.AllowedMarkets.size() == 0); + REQUIRE(installer1.Markets.ExcludedMarkets.size() == 1); + REQUIRE(installer1.Markets.ExcludedMarkets.at(0) == "US"); + REQUIRE(installer1.ExpectedReturnCodes.at(2).ReturnResponseEnum == ExpectedReturnCodeEnum::ContactSupport); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE_FALSE(installer1.DisplayInstallWarnings); + REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(installer1.ExpectedReturnCodes.at(3).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); + REQUIRE(installer1.UnsupportedArguments.size() == 1); + REQUIRE(installer1.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Location); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) + { + // NestedInstaller metadata should not be populated unless the InstallerType is zip. + REQUIRE(installer1.NestedInstallerType == InstallerTypeEnum::Unknown); + REQUIRE(installer1.NestedInstallerFiles.size() == 0); + + REQUIRE(installer1.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp"); + REQUIRE(installer1.InstallationMetadata.Files.size() == 1); + REQUIRE(installer1.InstallationMetadata.Files.at(0).RelativeFilePath == "main.exe"); + REQUIRE(installer1.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Launch); + REQUIRE(installer1.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer1.InstallationMetadata.Files.at(0).InvocationParameter == "/arg"); + REQUIRE(installer1.InstallationMetadata.Files.at(0).DisplayName == "DisplayName"); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) + { + REQUIRE_FALSE(installer1.DownloadCommandProhibited); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) + { + REQUIRE(installer1.Switches.at(InstallerSwitchType::Repair) == "/r"); + REQUIRE(installer1.RepairBehavior == RepairBehaviorEnum::Modify); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 }) + { + REQUIRE_FALSE(installer1.ArchiveBinariesDependOnPath); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) + { + auto containers = &manifest.DefaultInstallerInfo.DesiredStateConfiguration; + if (isExported) + { + containers = &manifest.Installers[0].DesiredStateConfiguration; + } + + RequireContainerInfoPresent(*containers, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); + } + + if (!isSingleton) + { + if (!isExported) + { + const ManifestInstaller& installer2 = manifest.Installers.at(1); + REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(installer2.Arch == Architecture::X64); + REQUIRE(installer2.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer2.ProductCode == "{Bar}"); + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(installer2.ReleaseDate == "2021-01-01"); + REQUIRE(installer2.InstallerAbortsTerminal); + REQUIRE(installer2.InstallLocationRequired); + REQUIRE(installer2.RequireExplicitUpgrade); + REQUIRE(installer2.ElevationRequirement == ElevationRequirementEnum::ElevatesSelf); + REQUIRE(installer2.UnsupportedOSArchitectures.size() == 1); + REQUIRE(installer2.UnsupportedOSArchitectures.at(0) == Architecture::Arm); + REQUIRE(installer2.AppsAndFeaturesEntries.size() == 1); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayName == "DisplayName"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).DisplayVersion == "DisplayVersion"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).Publisher == "Publisher"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).ProductCode == "ProductCode"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).UpgradeCode == "UpgradeCode"); + REQUIRE(installer2.AppsAndFeaturesEntries.at(0).InstallerType == InstallerTypeEnum::Exe); + REQUIRE(installer2.Markets.AllowedMarkets.size() == 1); + REQUIRE(installer2.Markets.AllowedMarkets.at(0) == "US"); + REQUIRE(installer2.ExpectedReturnCodes.size() == 1); + REQUIRE(installer2.ExpectedReturnCodes.at(10).ReturnResponseEnum == ExpectedReturnCodeEnum::PackageInUse); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + const ManifestInstaller& installer3 = manifest.Installers.at(2); + REQUIRE(installer3.BaseInstallerType == InstallerTypeEnum::Portable); + REQUIRE(installer3.Arch == Architecture::X86); + REQUIRE(installer3.Url == "https://www.microsoft.com/msixsdk/msixsdkx86.exe"); + REQUIRE(installer3.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer3.Commands == MultiValue{ "standalone" }); + REQUIRE(installer3.ExpectedReturnCodes.size() == 1); + REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseEnum == ExpectedReturnCodeEnum::Custom); + REQUIRE(installer3.ExpectedReturnCodes.at(11).ReturnResponseUrl == "https://defaultReturnResponseUrl.com"); + REQUIRE_FALSE(installer3.DisplayInstallWarnings); + REQUIRE(installer3.UnsupportedArguments.size() == 1); + REQUIRE(installer3.UnsupportedArguments.at(0) == UnsupportedArgumentEnum::Log); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_4 }) + { + const ManifestInstaller& installer4 = manifest.Installers.at(3); + REQUIRE(installer4.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(installer4.Arch == Architecture::X64); + REQUIRE(installer4.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer4.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer4.ProductCode == "{Foo}"); + REQUIRE(installer4.NestedInstallerType == InstallerTypeEnum::Portable); + REQUIRE(installer4.NestedInstallerFiles.size() == 2); + REQUIRE(installer4.NestedInstallerFiles.at(0).RelativeFilePath == "relativeFilePath1"); + REQUIRE(installer4.NestedInstallerFiles.at(0).PortableCommandAlias == "portableAlias1"); + REQUIRE(installer4.NestedInstallerFiles.at(1).RelativeFilePath == "relativeFilePath2"); + REQUIRE(installer4.NestedInstallerFiles.at(1).PortableCommandAlias == "portableAlias2"); + REQUIRE(installer4.InstallationMetadata.DefaultInstallLocation == "%ProgramFiles%\\TestApp2"); + REQUIRE(installer4.InstallationMetadata.Files.size() == 1); + REQUIRE(installer4.InstallationMetadata.Files.at(0).RelativeFilePath == "main2.exe"); + REQUIRE(installer4.InstallationMetadata.Files.at(0).FileType == InstalledFileTypeEnum::Other); + REQUIRE(installer4.InstallationMetadata.Files.at(0).FileSha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer4.InstallationMetadata.Files.at(0).InvocationParameter == "/arg2"); + REQUIRE(installer4.InstallationMetadata.Files.at(0).DisplayName == "DisplayName2"); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_6 }) + { + REQUIRE(installer2.DownloadCommandProhibited); + REQUIRE(installer2.UpdateBehavior == UpdateBehaviorEnum::Deny); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_7 }) + { + REQUIRE(installer2.RepairBehavior == RepairBehaviorEnum::Uninstaller); + REQUIRE(installer2.Switches.at(InstallerSwitchType::Repair) == "/r"); + + const ManifestInstaller& installer5 = manifest.Installers.at(4); + REQUIRE(installer5.BaseInstallerType == InstallerTypeEnum::Burn); + REQUIRE(installer5.Arch == Architecture::X64); + REQUIRE(installer5.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer5.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer5.ProductCode == "{Bar}"); + REQUIRE(installer5.Switches.at(InstallerSwitchType::Repair) == "/repair"); + REQUIRE(installer5.RepairBehavior == RepairBehaviorEnum::Modify); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_9 }) + { + const ManifestInstaller& installer4 = manifest.Installers.at(3); + REQUIRE(installer4.ArchiveBinariesDependOnPath); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_12 }) + { + const ManifestInstaller& installer6 = manifest.Installers.at(5); + REQUIRE(installer6.BaseInstallerType == InstallerTypeEnum::Zip); + REQUIRE(installer6.Arch == Architecture::Neutral); + REQUIRE(installer6.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer6.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer6.NestedInstallerType == InstallerTypeEnum::Font); + REQUIRE(installer6.NestedInstallerFiles.size() == 5); + REQUIRE(installer6.NestedInstallerFiles.at(0).RelativeFilePath == "relativeFilePath1.otf"); + REQUIRE(installer6.NestedInstallerFiles.at(1).RelativeFilePath == "relativeFilePath2.ttf"); + REQUIRE(installer6.NestedInstallerFiles.at(2).RelativeFilePath == "relativeFilePath3.fnt"); + REQUIRE(installer6.NestedInstallerFiles.at(3).RelativeFilePath == "relativeFilePath4.ttc"); + REQUIRE(installer6.NestedInstallerFiles.at(4).RelativeFilePath == "relativeFilePath5.otc"); + + const ManifestInstaller& installer7 = manifest.Installers.at(6); + REQUIRE(installer7.BaseInstallerType == InstallerTypeEnum::Font); + REQUIRE(installer7.Arch == Architecture::Neutral); + REQUIRE(installer7.Url == "https://www.microsoft.com/msixsdk/msixsdkx64.exe"); + REQUIRE(installer7.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_28 }) + { + RequireContainerInfoPresent(manifest.Installers[1].DesiredStateConfiguration, { { { "None/None" } } }); + REQUIRE(manifest.Installers[2].DesiredStateConfiguration.size() == 0); + } + } + + // Localization + REQUIRE(manifest.Localizations.size() == 1); + const ManifestLocalization& localization1 = manifest.Localizations.at(0); + REQUIRE(localization1.Locale == "en-GB"); + REQUIRE(localization1.Get() == "Microsoft UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/support/UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/privacy/UK"); + REQUIRE(localization1.Get() == "Microsoft UK"); + REQUIRE(localization1.Get() == "MSIX SDK UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/home/UK"); + REQUIRE(localization1.Get() == "MIT License UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/license/UK"); + REQUIRE(localization1.Get() == "Copyright Microsoft Corporation UK"); + REQUIRE(localization1.Get() == "https://www.microsoft.com/msixsdk/copyright/UK"); + REQUIRE(localization1.Get() == "This is MSIX SDK UK"); + REQUIRE(localization1.Get() == "The MSIX SDK project is an effort to enable developers UK"); + REQUIRE(localization1.Get() == MultiValue{ "appxsdkUK", "msixsdkUK" }); + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_1 }) + { + REQUIRE(localization1.Get() == "Release notes"); + REQUIRE(localization1.Get() == "https://ReleaseNotes.net"); + REQUIRE(localization1.Get().size() == 1); + REQUIRE(localization1.Get().at(0).Label == "Label"); + REQUIRE(localization1.Get().at(0).AgreementText == "Text"); + REQUIRE(localization1.Get().at(0).AgreementUrl == "https://AgreementUrl.net"); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_2 }) + { + REQUIRE(localization1.Get() == "https://DefaultPurchaseUrl.com"); + REQUIRE(localization1.Get() == "Default installation notes"); + REQUIRE(localization1.Get().size() == 1); + REQUIRE(localization1.Get().at(0).DocumentLabel == "Default document label"); + REQUIRE(localization1.Get().at(0).DocumentUrl == "https://DefaultDocumentUrl.com"); + } + + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) + { + REQUIRE(localization1.Get().size() == 1); + REQUIRE(localization1.Get().at(0).Url == "https://localeTestIcon-en-GB"); + REQUIRE(localization1.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(localization1.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(localization1.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(localization1.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + } + } + + struct ManifestShadowTestInfo + { + bool shadowDefaultLocale; + bool shadowEnGbLocale; + }; + + void VerifyV1ManifestContentCreatedWithShadow(const Manifest& manifest, ManifestShadowTestInfo shadowInfo, ManifestVer manifestVer = { s_ManifestVersionV1_5 }) + { + REQUIRE(manifest.Id == "microsoft.msixsdk"); + REQUIRE(manifest.Version == "1.7.32"); + REQUIRE(manifest.Installers.size() == 1); + + // Default localization + REQUIRE(manifest.DefaultLocalization.Locale == "en-US"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); + REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); + REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); + REQUIRE(manifest.DefaultLocalization.Get() == "This is MSIX SDK"); + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) + { + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + + if (shadowInfo.shadowDefaultLocale) + { + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://shadowIcon-default"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == SHA256::ConvertToBytes("1111111111111111111111111111111111111111111111111111111111111111")); + } + else + { + REQUIRE(manifest.DefaultLocalization.Get().size() == 1); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Url == "https://testIcon-en-US"); + REQUIRE(manifest.DefaultLocalization.Get().at(0).FileType == IconFileTypeEnum::Ico); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Resolution == IconResolutionEnum::Custom); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Theme == IconThemeEnum::Default); + REQUIRE(manifest.DefaultLocalization.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123")); + } + } + + // Localization + if (manifestVer >= ManifestVer{ s_ManifestVersionV1_5 }) + { + REQUIRE(manifest.Localizations.size() == 3); + + bool foundEnGbLocale = false; + bool foundfrFrLocale = false; + for (auto const& localization : manifest.Localizations) + { + if (localization.Locale == "en-GB") + { + REQUIRE(localization.Get() == "The MSIX SDK project is an effort to enable developers UK"); + if (shadowInfo.shadowEnGbLocale) + { + REQUIRE(localization.Get().size() == 1); + REQUIRE(localization.Get().at(0).Url == "https://shadowIcon-en-GB"); + REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("2222222222222222222222222222222222222222222222222222222222222222")); + } + else + { + REQUIRE(localization.Get().size() == 1); + REQUIRE(localization.Get().at(0).Url == "https://localeTestIcon-en-GB"); + REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321")); + } + + foundEnGbLocale = true; + } + else if (localization.Locale == "fr-FR") + { + REQUIRE(localization.Get().size() == 1); + REQUIRE(localization.Get().at(0).Url == "https://shadowIcon-fr-FR"); + REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Jpeg); + REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square20); + REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Dark); + REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("3333333333333333333333333333333333333333333333333333333333333333")); + foundfrFrLocale = true; + } + else + { + REQUIRE(localization.Locale == "es-MX"); + REQUIRE(localization.Get() == "The MSIX SDK project is an effort to enable developers MX"); + REQUIRE(localization.Get().size() == 1); + REQUIRE(localization.Get().at(0).Url == "https://localeTestIcon-es-MX"); + REQUIRE(localization.Get().at(0).FileType == IconFileTypeEnum::Png); + REQUIRE(localization.Get().at(0).Resolution == IconResolutionEnum::Square32); + REQUIRE(localization.Get().at(0).Theme == IconThemeEnum::Light); + REQUIRE(localization.Get().at(0).Sha256 == SHA256::ConvertToBytes("4444444444444444444444444444444444444444444444444444444444444444")); + } + } + + REQUIRE(foundEnGbLocale); + REQUIRE(foundfrFrLocale); + } + } +} + +TEST_CASE("ReadPreviewGoodManifestAndVerifyContents", "[ManifestValidation]") +{ + auto manifestFile = TestDataFile("Manifest-Good.yaml"); + Manifest manifest = YamlParser::CreateFromPath(manifestFile); + + REQUIRE(manifest.Id == "microsoft.msixsdk"); + REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); + REQUIRE(manifest.Moniker == "msixsdk"); + REQUIRE(manifest.Version == "1.7.32"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.Channel == "release"); + REQUIRE(manifest.DefaultLocalization.Get() == "Microsoft"); + REQUIRE(manifest.DefaultLocalization.Get() == "MIT License"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE"); + REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "0.0.0.0"); + REQUIRE(manifest.DefaultLocalization.Get() == "The MSIX SDK project is an effort to enable developers"); + REQUIRE(manifest.DefaultLocalization.Get() == "https://github.com/microsoft/msix-packaging"); + REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "msix", "appx" }); + REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); + REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); + REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); + REQUIRE(manifest.DefaultInstallerInfo.BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(manifest.DefaultInstallerInfo.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + REQUIRE(manifest.DefaultInstallerInfo.ProductCode == "{Foo}"); + REQUIRE(manifest.DefaultInstallerInfo.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); + + // default switches + auto switches = manifest.DefaultInstallerInfo.Switches; + REQUIRE(switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(switches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(switches.at(InstallerSwitchType::Silent) == "/silence"); + REQUIRE(switches.at(InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(switches.at(InstallerSwitchType::Language) == "/en-us"); + REQUIRE(switches.at(InstallerSwitchType::Log) == "/log="); + REQUIRE(switches.at(InstallerSwitchType::InstallLocation) == "/dir="); + REQUIRE(switches.at(InstallerSwitchType::Update) == "/update"); + + // installers + REQUIRE(manifest.Installers.size() == 2); + ManifestInstaller installer1 = manifest.Installers.at(0); + REQUIRE(installer1.Arch == Architecture::X86); + REQUIRE(installer1.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx86.zip"); + REQUIRE(installer1.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82")); + REQUIRE(installer1.Locale == "en-US"); + REQUIRE(installer1.BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(installer1.Scope == ScopeEnum::User); + REQUIRE(installer1.PackageFamilyName == ""); + REQUIRE(installer1.ProductCode == "{Foo}"); + REQUIRE(installer1.UpdateBehavior == UpdateBehaviorEnum::Install); + + auto installer1Switches = installer1.Switches; + REQUIRE(installer1Switches.at(InstallerSwitchType::Custom) == "/c"); + REQUIRE(installer1Switches.at(InstallerSwitchType::SilentWithProgress) == "/sp"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Silent) == "/s"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Interactive) == "/i"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Language) == "/en"); + REQUIRE(installer1Switches.at(InstallerSwitchType::Log) == "/l="); + REQUIRE(installer1Switches.at(InstallerSwitchType::InstallLocation) == "/d="); + REQUIRE(installer1Switches.at(InstallerSwitchType::Update) == "/u"); + + ManifestInstaller installer2 = manifest.Installers.at(1); + REQUIRE(installer2.Arch == Architecture::X64); + REQUIRE(installer2.Url == "https://rubengustorage.blob.core.windows.net/publiccontainer/msixsdkx64.zip"); + REQUIRE(installer2.Sha256 == SHA256::ConvertToBytes("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF0000")); + REQUIRE(installer2.Locale == "en-US"); + REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Exe); + REQUIRE(installer2.Scope == ScopeEnum::User); + REQUIRE(installer2.PackageFamilyName == ""); + REQUIRE(installer2.ProductCode == "{Foo}"); + REQUIRE(installer2.UpdateBehavior == UpdateBehaviorEnum::UninstallPrevious); + + // Installer2 does not declare switches, it inherits switches from package default. + auto installer2Switches = installer2.Switches; + REQUIRE(installer2Switches.at(InstallerSwitchType::Custom) == "/custom"); + REQUIRE(installer2Switches.at(InstallerSwitchType::SilentWithProgress) == "/silentwithprogress"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Silent) == "/silence"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Interactive) == "/interactive"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Language) == "/en-us"); + REQUIRE(installer2Switches.at(InstallerSwitchType::Log) == "/log="); + REQUIRE(installer2Switches.at(InstallerSwitchType::InstallLocation) == "/dir="); + REQUIRE(installer2Switches.at(InstallerSwitchType::Update) == "/update"); + + // Localization + REQUIRE(manifest.Localizations.size() == 1); + ManifestLocalization localization1 = manifest.Localizations.at(0); + REQUIRE(localization1.Locale == "es-MX"); + REQUIRE(localization1.Get() == "El proyecto MSIX SDK es habilita desarrolladores de diferentes"); + REQUIRE(localization1.Get() == "https://github.com/microsoft/msix-packaging/es-MX"); + REQUIRE(localization1.Get() == "https://github.com/microsoft/msix-packaging/blob/master/LICENSE-es-MX"); + + // Stream hash + std::ifstream stream(manifestFile.GetPath(), std::ios_base::in | std::ios_base::binary); + REQUIRE(!stream.fail()); + auto manifestHash = SHA256::ComputeHash(stream); + REQUIRE(manifestHash.size() == manifest.StreamSha256.size()); + REQUIRE(std::equal(manifestHash.begin(), manifestHash.end(), manifest.StreamSha256.begin())); +} + +TEST_CASE("ReadGoodManifestWithSpaces", "[ManifestValidation]") +{ + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-Spaces.yaml")); + + REQUIRE(manifest.Id == "microsoft.msixsdk"); + REQUIRE(manifest.DefaultLocalization.Get() == "MSIX SDK"); + REQUIRE(manifest.Moniker == "msixsdk"); + REQUIRE(manifest.Version == "1.7.32"); + REQUIRE(manifest.Channel == "release"); + REQUIRE(manifest.DefaultInstallerInfo.MinOSVersion == "0.0.0.0"); + REQUIRE(manifest.DefaultLocalization.Get() == MultiValue{ "msix", "appx" }); + REQUIRE(manifest.DefaultInstallerInfo.Commands == MultiValue{ "makemsix", "makeappx" }); + REQUIRE(manifest.DefaultInstallerInfo.Protocols == MultiValue{ "protocol1", "protocol2" }); + REQUIRE(manifest.DefaultInstallerInfo.FileExtensions == MultiValue{ "appx", "appxbundle", "msix", "msixbundle" }); +} + +TEST_CASE("ReadGoodManifests", "[ManifestValidation]") +{ + ManifestTestCase TestCases[] = + { + { "Manifest-Good-InstallerTypeExeRoot-Silent.yaml" }, + { "Manifest-Good-InstallerTypeExeRoot-SilentRoot.yaml" }, + { "Manifest-Good-InstallerTypeExe-Silent.yaml" }, + { "Manifest-Good-InstallerTypeExe-SilentRoot.yaml" }, + { "Manifest-Good-InstallerUniqueness-DefaultLang.yaml" }, + { "Manifest-Good-InstallerUniqueness-DiffLangs.yaml" }, + { "Manifest-Good-InstallerUniqueness-DiffScope.yaml" }, + { "Manifest-Good-Minimum.yaml" }, + { "Manifest-Good-Minimum-InstallerType.yaml" }, + { "Manifest-Good-Switches.yaml" }, + { "Manifest-Good-DefaultExpectedReturnCodeInInstallerSuccessCodes.yaml" }, + { "Manifest-Good-InstallerTypeZip-PortableExe.yaml" }, + { "Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml" }, + }; + + for (auto const& testCase : TestCases) + { + TestManifest(testCase.TestFile); + } +} + +TEST_CASE("ReadBadManifests", "[ManifestValidation]") +{ + ManifestTestCase TestCases[] = + { + { "Manifest-Bad-ArchInvalid.yaml", "Invalid field value. [Architecture]" }, + { "Manifest-Bad-ArchMissing.yaml", "Missing required property 'Arch'" }, + { "Manifest-Bad-Channel-NotSupported.yaml", "Field is not supported. [Channel]" }, + { "Manifest-Bad-DifferentCase-camelCase.yaml", "All field names should be PascalCased. [installerType]" }, + { "Manifest-Bad-DifferentCase-lower.yaml", "All field names should be PascalCased. [installertype]" }, + { "Manifest-Bad-DifferentCase-UPPER.yaml", "All field names should be PascalCased. [INSTALLERTYPE]" }, + { "Manifest-Bad-DuplicateKey.yaml", "Duplicate field found in the manifest." }, + { "Manifest-Bad-DuplicateKey-DifferentCase.yaml", "Duplicate field found in the manifest." }, + { "Manifest-Bad-DuplicateKey-DifferentCase-lower.yaml", "Duplicate field found in the manifest." }, + { "Manifest-Bad-DuplicateReturnCode-ExpectedCodes.yaml", "Duplicate installer return code found." }, + { "Manifest-Bad-DuplicateReturnCode-SuccessCodes.yaml", "Duplicate installer return code found." }, + { "Manifest-Bad-DuplicateSha256.yaml", "Multiple Installer URLs found with the same InstallerSha256. Please ensure the accuracy of the URLs.", true }, + { "Manifest-Bad-IdInvalid.yaml", "Failed to validate against schema associated with property name 'Id'" }, + { "Manifest-Bad-IdMissing.yaml", "Missing required property 'Id'" }, + { "Manifest-Bad-InconsistentSha256.yaml", "The values of InstallerSha256 do not match for all instances of the same InstallerUrl." }, + { "Manifest-Bad-InstallersMissing.yaml", "Missing required property 'Installers'" }, + { "Manifest-Bad-InstallerTypeExe-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeExe-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeExeRoot-NoSilent.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeExeRoot-NoSilentRoot.yaml", "Silent and SilentWithProgress switches are not specified for InstallerType exe.", true }, + { "Manifest-Bad-InstallerTypeInvalid.yaml", "Invalid field value. [InstallerType]" }, + { "Manifest-Bad-InstallerTypeMissing.yaml", "Invalid field value. [InstallerType]" }, + { "Manifest-Bad-InstallerTypePortable-InvalidAppsAndFeatures.yaml", "Only zero or one entry for Apps and Features may be specified for InstallerType portable." }, + { "Manifest-Bad-InstallerTypePortable-InvalidCommands.yaml", "Only zero or one value for Commands may be specified for InstallerType portable." }, + { "Manifest-Bad-InstallerTypePortable-InvalidScope.yaml", "Scope is not supported for InstallerType portable." }, + { "Manifest-Bad-InstallerTypeZip-DuplicateCommandAlias.yaml", "Duplicate portable command alias found." }, + { "Manifest-Bad-InstallerTypeZip-DuplicateRelativeFilePath.yaml", "Duplicate relative file path found." }, + { "Manifest-Bad-InstallerTypeZip-InvalidPortableCommandAlias.yaml", "Portable command alias must not point to a location outside of base directory." }, + { "Manifest-Bad-InstallerTypeZip-InvalidRelativeFilePath.yaml", "Relative file path must not point to a location outside of archive directory" }, + { "Manifest-Bad-InstallerTypeZip-MissingRelativeFilePath.yaml", "Required field missing. [RelativeFilePath]" }, + { "Manifest-Bad-InstallerTypeZip-MultipleNestedInstallers.yaml", "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes." }, + { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerFile.yaml", "Required field missing. [NestedInstallerFiles]" }, + { "Manifest-Bad-InstallerTypeZip-NoNestedInstallerType.yaml", "Required field missing. [NestedInstallerType]" }, + { "Manifest-Bad-InstallerUniqueness.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-InstallerUniqueness-DefaultScope.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-InstallerUniqueness-DefaultValues.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-InstallerUniqueness-SameLang.yaml", "Duplicate installer entry found." }, + { "Manifest-Bad-LicenseMissing.yaml", "Missing required property 'License'" }, + { "Manifest-Bad-NameMissing.yaml", "Missing required property 'Name'" }, + { "Manifest-Bad-PublisherMissing.yaml", "Missing required property 'Publisher'" }, + { "Manifest-Bad-Sha256Invalid.yaml", "Failed to validate against schema associated with property name 'Sha256'" }, + { "Manifest-Bad-Sha256Missing.yaml", "Required field missing. [InstallerSha256]" }, + { "Manifest-Bad-SwitchInvalid.yaml", "Unknown field. [NotASwitch]", true }, + { "Manifest-Bad-UnknownProperty.yaml", "Unknown field. [Fake]", true }, + { "Manifest-Bad-UnsupportedVersion.yaml", "Unsupported ManifestVersion" }, + { "Manifest-Bad-UrlInvalid.yaml", "Invalid field value. [InstallerUrl]" }, + { "Manifest-Bad-UrlMissing.yaml", "Required field missing. [InstallerUrl]" }, + { "Manifest-Bad-VersionInvalid.yaml", "Failed to validate against schema associated with property name 'Version'" }, + { "Manifest-Bad-VersionMissing.yaml", "Missing required property 'Version'" }, + { "Manifest-Bad-InvalidManifestVersionValue.yaml", "Failed to validate against schema associated with property name 'ManifestVersion'" }, + { "InstallFlowTest_MSStore.yaml", "Field value is not supported. [InstallerType] Value: msstore" }, + { "Manifest-Bad-PackageFamilyNameOnMSI.yaml", "The specified installer type does not support PackageFamilyName. [InstallerType] Value: msi", true }, + { "Manifest-Bad-ProductCodeOnMSIX.yaml", "The specified installer type does not support ProductCode. [InstallerType] Value: msix" }, + { "Manifest-Bad-InvalidUpdateBehavior.yaml", "Invalid field value. [UpgradeBehavior]" }, + { "Manifest-Bad-InvalidLocale.yaml", "The locale value is not a well formed bcp47 language tag." }, + { "Manifest-Bad-AppsAndFeaturesEntriesOnMSIX.yaml", "The specified installer type does not write to Apps and Features entry." }, + { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers. [Agreement]", true }, + { "InstallFlowTest_LicenseAgreement.yaml", "Field usage requires verified publishers. [Agreement]", false, GetTestManifestValidateOption(false, true) }, + { "Manifest-Bad-ApproximateVersionInPackageVersion.yaml", "Approximate version not allowed. [PackageVersion]" }, + { "Manifest-Bad-ApproximateVersionInArpVersion.yaml", "Approximate version not allowed. [DisplayVersion]" }, + { "Manifest-Bad-InstallerTypeZip-PortableNotExe.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, + { "Manifest-Bad-InstallerTypeZip-PortableNotExe_Root.yaml", "The file type of the referenced file is not allowed. [RelativeFilePath] Value: ScriptedApplication.bat" }, + }; + + for (auto const& testCase : TestCases) + { + TestManifest(testCase.TestFile, testCase.ExpectedMessage, testCase.IsWarningOnly, testCase.ValidateOption); + } +} + +TEST_CASE("ManifestEncoding", "[ManifestValidation]") +{ + ManifestTestCase TestCases[] = + { + { "Manifest-Encoding-ANSI.yaml" }, + { "Manifest-Encoding-UTF8.yaml" }, + { "Manifest-Encoding-UTF8-BOM.yaml" }, + { "Manifest-Encoding-UTF16BE.yaml" }, + { "Manifest-Encoding-UTF16BE-BOM.yaml" }, + { "Manifest-Encoding-UTF16LE.yaml" }, + { "Manifest-Encoding-UTF16LE-BOM.yaml" }, + }; + + for (auto const& testCase : TestCases) + { + INFO(testCase.TestFile); + Manifest manifest = YamlParser::CreateFromPath(TestDataFile(testCase.TestFile), GetTestManifestValidateOption()); + REQUIRE(manifest.DefaultLocalization.Get() == u8"MSIX SDK\xA9"); + } +} + +TEST_CASE("ComplexSystemReference", "[ManifestValidation]") +{ + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-SystemReferenceComplex.yaml")); + + REQUIRE(manifest.Installers.size() == 4); + + // MSIX installer does inherit + auto& installer = manifest.Installers[0]; + REQUIRE(installer.BaseInstallerType == InstallerTypeEnum::Msix); + REQUIRE(installer.Arch == Architecture::X86); + REQUIRE(installer.PackageFamilyName == "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"); + REQUIRE(installer.ProductCode == ""); + + // MSI installer does inherit + auto& installer1 = manifest.Installers[1]; + REQUIRE(installer1.BaseInstallerType == InstallerTypeEnum::Msi); + REQUIRE(installer1.Arch == Architecture::X86); + REQUIRE(installer1.PackageFamilyName == ""); + REQUIRE(installer1.ProductCode == "{Foo}"); + + // MSIX installer with override + auto& installer2 = manifest.Installers[2]; + REQUIRE(installer2.BaseInstallerType == InstallerTypeEnum::Msix); + REQUIRE(installer2.Arch == Architecture::X64); + REQUIRE(installer2.PackageFamilyName == "Override_8wekyb3d8bbwe"); + REQUIRE(installer2.ProductCode == ""); + + // MSI installer with override + auto& installer3 = manifest.Installers[3]; + REQUIRE(installer3.BaseInstallerType == InstallerTypeEnum::Msi); + REQUIRE(installer3.Arch == Architecture::X64); + REQUIRE(installer3.PackageFamilyName == ""); + REQUIRE(installer3.ProductCode == "Override"); +} + +TEST_CASE("ManifestVersionExtensions", "[ManifestValidation]") +{ + REQUIRE(!ManifestVer("1.0.0"sv).HasExtension("msstore")); + REQUIRE(!ManifestVer("1.0.0-other"sv).HasExtension("msstore")); + REQUIRE(!ManifestVer("1.0.0-other-other2"sv).HasExtension("msstore")); + + REQUIRE(ManifestVer("1.0.0-msstore"sv).HasExtension("msstore")); + REQUIRE(ManifestVer("1.0.0-msstore.2"sv).HasExtension("msstore")); + REQUIRE(ManifestVer("1.0.0-other-msstore.2"sv).HasExtension("msstore")); + REQUIRE(ManifestVer("1.0.0-msstore.2-other"sv).HasExtension("msstore")); +} + +void ValidateGoodManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder(singleton, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory, validateOption); + VerifyV1ManifestContent(singletonManifest, true, ManifestVer{ version }); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder(multiFiles, multiFileDirectory); + + TempFile mergedManifestFile{ "merged.yaml" }; + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); + VerifyV1ManifestContent(multiFileManifest, false, ManifestVer{ version }); + + // Read from merged manifest should have the same content as multi file manifest + Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); + VerifyV1ManifestContent(mergedManifest, false, ManifestVer{ version }); +} + +#define WINGET_VALIDATE_GOOD_MANIFEST_VERSION(_version_) TEST_CASE("ValidateGoodManifestAndVerifyContents_" #_version_ , "[ManifestValidation][ManifestVersionValidation]") \ +{ \ + ValidateGoodManifestAndVerifyContents( \ + { "ManifestV" #_version_ "-Singleton.yaml" }, \ + { \ + "ManifestV" #_version_ "-MultiFile-Version.yaml", \ + "ManifestV" #_version_ "-MultiFile-Installer.yaml", \ + "ManifestV" #_version_ "-MultiFile-DefaultLocale.yaml", \ + "ManifestV" #_version_ "-MultiFile-Locale.yaml" \ + }, \ + s_ManifestVersionV ## _version_); \ +} + +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_1) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_2) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_4) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_5) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_6) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_7) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_9) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_10) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_12) +WINGET_VALIDATE_GOOD_MANIFEST_VERSION(1_28) + +void WriteSingletonManifestAndVerifyContents(const std::vector& singleton, const std::vector& multiFiles, std::string_view version) +{ + TempDirectory singletonDirectory{ "SingletonManifest" }; + CopyTestDataFilesToFolder(singleton, singletonDirectory); + Manifest singletonManifest = YamlParser::CreateFromPath(singletonDirectory); + + TempDirectory exportedSingletonDirectory{ "exportedSingleton" }; + std::filesystem::path generatedSingletonManifestPath = exportedSingletonDirectory.GetPath() / "testSingletonManifest.yaml"; + YamlWriter::OutputYamlFile(singletonManifest, singletonManifest.Installers[0], generatedSingletonManifestPath); + + REQUIRE(std::filesystem::exists(generatedSingletonManifestPath)); + Manifest generatedSingletonManifest = YamlParser::CreateFromPath(exportedSingletonDirectory); + VerifyV1ManifestContent(generatedSingletonManifest, true, ManifestVer{ version }, true); + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder(multiFiles, multiFileDirectory); + + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory); + TempDirectory exportedMultiFileDirectory{ "exportedMultiFile" }; + std::filesystem::path generatedMultiFileManifestPath = exportedMultiFileDirectory.GetPath() / "testMultiFileManifest.yaml"; + YamlWriter::OutputYamlFile(multiFileManifest, multiFileManifest.Installers[0], generatedMultiFileManifestPath); + + REQUIRE(std::filesystem::exists(generatedMultiFileManifestPath)); + Manifest generatedMultiFileManifest = YamlParser::CreateFromPath(exportedMultiFileDirectory); + VerifyV1ManifestContent(generatedMultiFileManifest, false, ManifestVer{ version }, true); +} + +#define WINGET_WRITE_VERIFY_MANIFEST_VERSION(_version_) TEST_CASE("WriteSingletonManifestAndVerifyContents_" #_version_ , "[ManifestCreation][ManifestVersionCreation]") \ +{ \ + WriteSingletonManifestAndVerifyContents( \ + { "ManifestV" #_version_ "-Singleton.yaml" }, \ + { \ + "ManifestV" #_version_ "-MultiFile-Version.yaml", \ + "ManifestV" #_version_ "-MultiFile-Installer.yaml", \ + "ManifestV" #_version_ "-MultiFile-DefaultLocale.yaml", \ + "ManifestV" #_version_ "-MultiFile-Locale.yaml" \ + }, \ + s_ManifestVersionV ## _version_); \ +} + +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_1) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_2) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_4) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_5) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_6) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_7) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_9) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_10) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_12) +WINGET_WRITE_VERIFY_MANIFEST_VERSION(1_28) + +// 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", "[ManifestCreation][ManifestVersionCreation]") +{ + // Read manifest + TempDirectory testDirectory{ "TestManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_10-InstallerAuthentication.yaml" }, testDirectory); + Manifest testManifest = YamlParser::CreateFromPath(testDirectory); + + // Validate schema + ManifestValidateOption validateOption; + validateOption.SchemaValidationOnly = true; + validateOption.ThrowOnWarning = true; + YamlParser::CreateFromPath(testDirectory, validateOption); + + // 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(exportedManifest.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()); +} + +// PowerShell DSC is not supported in the community repo and will cause manifest validation failure. +TEST_CASE("ReadWriteValidateV1_28ManifestWithPowerShellDSC", "[ManifestCreation][ManifestVersionCreation]") +{ + // Read manifest + TempDirectory testDirectory{ "TestManifest" }; + CopyTestDataFilesToFolder({ "ManifestV1_28-PowerShellDSC.yaml" }, testDirectory); + Manifest testManifest = YamlParser::CreateFromPath(testDirectory); + + // Validate schema + ManifestValidateOption validateOption; + validateOption.SchemaValidationOnly = true; + validateOption.ThrowOnWarning = true; + YamlParser::CreateFromPath(testDirectory, validateOption); + + // TODO: Update ValidateManifest + // TODO: Update this test with similar validations + + // Verify content + REQUIRE(testManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_28 }); + REQUIRE(testManifest.Installers.size() == 1); + REQUIRE(testManifest.Installers[0].DesiredStateConfiguration.size() == 3); + + RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { "https://www.powershellgallery.com/api/v2", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { "https://mcr.microsoft.com/", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(testManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); + + // Manifest Validation. Only error is "PowerShell not supported". + auto errors = ValidateManifest(testManifest, true); + REQUIRE(errors.size() == 1); + REQUIRE(errors[0].GetErrorMessage() == "Field is not supported."); + REQUIRE(errors[0].Context == "DesiredStateConfiguration.PowerShell"); + + // 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(exportedManifest.ManifestVersion == AppInstaller::Manifest::ManifestVer{ s_ManifestVersionV1_28 }); + REQUIRE(exportedManifest.Installers.size() == 1); + REQUIRE(exportedManifest.Installers[0].DesiredStateConfiguration.size() == 3); + + RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { "https://www.powershellgallery.com/api/v2", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { "https://mcr.microsoft.com/", "Microsoft.WinGet.DSC", { { "WinGetUserSettings" }, { "WinGetAdminSettings" }, { "WinGetSource" }, { "WinGetPackageManager" }, { "WinGetPackage" } } }); + RequireContainerInfoPresent(exportedManifest.Installers[0].DesiredStateConfiguration, { { { "Microsoft.WinGet/AdminSettings" }, { "Microsoft.WinGet/Package" }, { "Microsoft.WinGet/Source" }, { "Microsoft.WinGet/UserSettingsFile" } } }); +} + +TEST_CASE("WriteManifestWithMultipleLocale", "[ManifestCreation]") +{ + Manifest multiLocaleManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); + TempDirectory exportedDirectory{ "exported" }; + std::filesystem::path generatedManifestPath = exportedDirectory.GetPath() / "testManifestWithMultipleLocale.yaml"; + YamlWriter::OutputYamlFile(multiLocaleManifest, multiLocaleManifest.Installers[0], generatedManifestPath); + + REQUIRE(std::filesystem::exists(generatedManifestPath)); + Manifest generatedManifest = YamlParser::CreateFromPath(generatedManifestPath); + REQUIRE(generatedManifest.Localizations.size() == 2); +} + +TEST_CASE("WriteManifestWithMSStoreInstaller", "[ManifestCreation]") +{ + Manifest msstoreManifest = YamlParser::CreateFromPath(TestDataFile("DownloadFlowTest_MSStore.yaml")); + TempDirectory exportedDirectory{ "exported" }; + std::filesystem::path generatedManifestPath = exportedDirectory.GetPath() / "testManifestWithMultipleLocale.yaml"; + msstoreManifest.ManifestVersion = ManifestVer{ "1.1.0" }; + YamlWriter::OutputYamlFile(msstoreManifest, msstoreManifest.Installers[0], generatedManifestPath); + + REQUIRE(std::filesystem::exists(generatedManifestPath)); + Manifest generatedManifest = YamlParser::CreateFromPath(generatedManifestPath); + REQUIRE(generatedManifest.Installers[0].BaseInstallerType == InstallerTypeEnum::MSStore); + REQUIRE(!generatedManifest.Installers[0].ProductId.empty()); +} + +YamlManifestInfo CreateYamlManifestInfo(std::string testDataFile) +{ + YamlManifestInfo result; + result.Root = AppInstaller::YAML::Load(TestDataFile(testDataFile)); + result.FileName = testDataFile; + return result; +} + +TEST_CASE("MultifileManifestInputValidation", "[ManifestValidation]") +{ + auto previewManifest = CreateYamlManifestInfo("Manifest-Good.yaml"); + auto v1SingletonManifest = CreateYamlManifestInfo("ManifestV1-Singleton.yaml"); + auto v1VersionManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Version.yaml"); + auto v1InstallerManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Installer.yaml"); + auto v1DefaultLocaleManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-DefaultLocale.yaml"); + auto v1LocaleManifest = CreateYamlManifestInfo("ManifestV1-MultiFile-Locale.yaml"); + + { + // Preview and multi file manifest together + std::vector input = { previewManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("Preview manifest does not support multi file manifest format")); + } + + { + // Singleton and multi file manifest together + std::vector input = { v1SingletonManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should not contain file with the particular ManifestType. [ManifestType] Value: singleton")); + } + + { + // More than 1 version manifest + std::vector input = { v1VersionManifest, v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: version")); + } + + { + // More than 1 installer manifest + std::vector input = { v1VersionManifest, v1InstallerManifest, v1InstallerManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: installer")); + } + + { + // More than 1 default locale manifest + std::vector input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: defaultLocale")); + } + + { + // Duplicate locales + std::vector input = { v1VersionManifest, v1InstallerManifest, v1DefaultLocaleManifest, v1LocaleManifest, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest contains duplicate PackageLocale. [PackageLocale] Value: en-GB")); + } + + { + // default locale not match + auto defaultLocaleManifestCopy = v1DefaultLocaleManifest; + defaultLocaleManifestCopy.Root["PackageLocale"].SetScalar("fr-fr"); + std::vector input = { v1VersionManifest, v1InstallerManifest, defaultLocaleManifestCopy, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest")); + } + + { + // Package Id does not match + auto installerManifestCopy = v1InstallerManifest; + installerManifestCopy.Root["PackageIdentifier"].SetScalar("Another.Identifier"); + std::vector input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. [PackageIdentifier] Value: Another.Identifier")); + } + + { + // Package Version does not match + auto installerManifestCopy = v1InstallerManifest; + installerManifestCopy.Root["PackageVersion"].SetScalar("Another.Version"); + std::vector input = { v1VersionManifest, installerManifestCopy, v1DefaultLocaleManifest, v1LocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest has inconsistent field values. [PackageVersion] Value: Another.Version")); + } + + { + // Incomplete multi file manifest, missing installer + std::vector input = { v1VersionManifest, v1DefaultLocaleManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest is incomplete")); + } + + { + // Incomplete multi file manifest, missing default locale + std::vector input = { v1VersionManifest, v1InstallerManifest }; + REQUIRE_THROWS_MATCHES(YamlParser::ParseManifest(input), ManifestException, ManifestExceptionMatcher("The multi file manifest is incomplete")); + } +} + +TEST_CASE("ManifestApplyLocale", "[ManifestValidation]") +{ + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); + + // No better alternative locale, default is used. + manifest.ApplyLocale("zh-CN"); + REQUIRE(manifest.CurrentLocalization.Locale == "es-MX"); + REQUIRE(manifest.CurrentLocalization.Get() == "es-MX package name"); + REQUIRE(manifest.CurrentLocalization.Get() == "es-MX publisher"); + + // en-US results in en-GB, which is better than default. + manifest.ApplyLocale("en-US"); + REQUIRE(manifest.CurrentLocalization.Locale == "en-GB"); + REQUIRE(manifest.CurrentLocalization.Get() == "en-GB package name"); + REQUIRE(manifest.CurrentLocalization.Get() == "en-GB publisher"); + + // fr-FR results in fr-FR, but only package name is localized. + manifest.ApplyLocale("fr-FR"); + REQUIRE(manifest.CurrentLocalization.Locale == "fr-FR"); + REQUIRE(manifest.CurrentLocalization.Get() == "fr-FR package name"); + REQUIRE(manifest.CurrentLocalization.Get() == "es-MX publisher"); +} + +TEST_CASE("ManifestLocalizationValidation", "[ManifestValidation]") +{ + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultiLocale.yaml")); + + // Set 1 locale to bad value + manifest.Localizations.at(0).Locale = "Invalid"; + + // Full validation should detect as error + auto errors = ValidateManifest(manifest, true); + REQUIRE(errors.size() == 1); + REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Error); + + // Not full validation should detect as warning + errors = ValidateManifest(manifest, false); + REQUIRE(errors.size() == 1); + REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Warning); +} + +TEST_CASE("PortableFileTypeValidation", "[ManifestValidation]") +{ + Manifest installerManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InstallerTypeZip-PortableNotExe.yaml")); + Manifest rootManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InstallerTypeZip-PortableNotExe_Root.yaml")); + Manifest uppercaseManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-InstallerTypeZip-PortableExeUppercase.yaml")); + + // Regular validation should detect as error + auto errors = ValidateManifest(installerManifest, true); + REQUIRE(errors.size() == 1); + REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Error); + + errors = ValidateManifest(rootManifest, true); + REQUIRE(errors.size() == 1); + REQUIRE(errors.at(0).ErrorLevel == ValidationError::Level::Error); + + // Should not error when full validation is set to false + errors = ValidateManifest(installerManifest, false); + REQUIRE(errors.size() == 0); + + errors = ValidateManifest(rootManifest, false); + REQUIRE(errors.size() == 0); + + // Uppercase file extension should be accepted (case-insensitive comparison) + errors = ValidateManifest(uppercaseManifest, true); + REQUIRE(errors.size() == 0); +} + +TEST_CASE("WindowsFeatureNameValidation", "[ManifestValidation][111981]") +{ + // An invalid Windows Feature name should produce an error regardless of the fullValidation flag + Manifest invalidManifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InvalidWindowsFeatureName.yaml")); + + auto errors = ValidateManifest(invalidManifest, true); + REQUIRE(errors.size() == 1); + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InvalidWindowsFeatureName, "Invalid@Feature", ""); + + errors = ValidateManifest(invalidManifest, false); + REQUIRE(errors.size() == 1); + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InvalidWindowsFeatureName, "Invalid@Feature", ""); +} + +TEST_CASE("NetworkAddressInSwitchesValidation", "[ManifestValidation][111981]") +{ + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-NetworkAddressInSwitches.yaml")); + + auto errors = ValidateManifest(manifest, true); + REQUIRE(errors.size() == 1); + ValidateError(errors[0], ValidationError::Level::Warning, ManifestError::ContainsNetworkAddress, "http://evil.example.com", ""); + + ManifestValidateOption options{ true }; + options.ErrorOnNetworkAddressInSwitches = true; + errors = ValidateManifest(manifest, options); + REQUIRE(errors.size() == 1); + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::ContainsNetworkAddress, "http://evil.example.com", ""); + + errors = ValidateManifest(manifest, false); + REQUIRE(errors.size() == 0); +} + +TEST_CASE("BlockedMsiPropertyValidation", "[ManifestValidation][111981]") +{ + SECTION("Blocked property is detected under full validation") + { + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-BlockedMsiProperty.yaml")); + + auto errors = ValidateManifest(manifest, true); + REQUIRE(errors.size() == 1); + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::BlockedMsiProperty, "TRANSFORMS", ""); + + // Not checked when fullValidation is false + errors = ValidateManifest(manifest, false); + REQUIRE(errors.size() == 0); + } + + SECTION("Invalid MSI switches are detected under full validation") + { + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-InvalidMsiSwitches.yaml")); + + auto errors = ValidateManifest(manifest, true); + REQUIRE(errors.size() == 1); + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InvalidMsiSwitches); + + // Not checked when fullValidation is false + errors = ValidateManifest(manifest, false); + REQUIRE(errors.size() == 0); + } +} + +TEST_CASE("ReadManifestAndValidateMsixInstallers_Success", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Good-MsixInstaller.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(0 == errors.size()); +} + +TEST_CASE("ReadManifestAndValidateMsixInstallers_InconsistentFields", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Bad-InconsistentMsixInstallerFields.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(4 == errors.size()); + + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::MsixSignatureHashFailed); + ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); + ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); + ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.0.0"); +} + +TEST_CASE("ReadManifestAndValidateMsixInstallers_NoSupportedPlatforms", "[ManifestValidation]") +{ + auto testFileName = "Manifest-Bad-NoSupportedPlatforms.yaml"; + TestDataFile testFile(testFileName); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(1 == errors.size()); + + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::NoSupportedPlatforms, "InstallerUrl", manifest.Installers.front().Url); +} + +TEST_CASE("ReadManifestAndValidateMsixInstallers_PackageVersionNotUINT64", "[ManifestValidation]") +{ + Manifest manifest = YamlParser::CreateFromPath(TestDataFile("Manifest-Bad-MsixInstaller-PackageVersion.yaml")); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(1 == errors.size()); + + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); +} + +TEST_CASE("ReadManifestAndValidateMsixInstallers_MissingFields", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Bad-MissingMsixInstallerFields.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + for (bool treatErrorAsWarning : { false, true }) + { + auto errors = ValidateManifestInstallers(manifest, treatErrorAsWarning); + auto expectedLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error; + REQUIRE(2 == errors.size()); + + ValidateError(errors[0], expectedLevel, ManifestError::OptionalFieldMissing, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); + ValidateError(errors[1], expectedLevel, ManifestError::OptionalFieldMissing, "MinimumOSVersion", "10.0.0.0"); + } +} + +TEST_CASE("ReadManifestAndValidateMsixInstallers_Signed_Success", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Good-SignedMsixInstaller.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(0 == errors.size()); +} + +TEST_CASE("ReadManifestAndValidateMsixInstallers_Signed_InconsistentFields", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Bad-InconsistentSignedMsixInstallerFields.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(4 == errors.size()); + + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "SignatureSha256", "50562001202c8dad456474d3f20903138d0a15c44ee497c3d4f82e85edbf2f97"); + ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); + ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); + ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.0.0"); +} + +TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_Success", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Good-MsixBundleInstaller.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(0 == errors.size()); +} + +TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_WithStub_Success", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Good-MsixBundleInstaller-WithStub.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(0 == errors.size()); +} + +TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_InconsistentFields", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Bad-InconsistentMsixBundleInstallerFields.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(7 == errors.size()); + + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::MsixSignatureHashFailed); + + // Validate errors for the first msix package in the msix bundle + ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); + ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); + ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); + + // Validate errors for the second msix package in the msix bundle + ValidateError(errors[4], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); + ValidateError(errors[5], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); + ValidateError(errors[6], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); +} + +TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_Signed_Success", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Good-SignedMsixBundleInstaller.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(0 == errors.size()); +} + +TEST_CASE("ReadManifestAndValidateMsixBundleInstallers_Signed_InconsistentFields", "[ManifestValidation]") +{ + TestDataFile testFile("Manifest-Bad-InconsistentSignedMsixBundleInstallerFields.yaml"); + Manifest manifest = YamlParser::CreateFromPath(testFile); + + // Update the installer path for testing + REQUIRE(1 == manifest.Installers.size()); + TestDataFile msixFile(manifest.Installers[0].Url.c_str()); + manifest.Installers[0].Url = msixFile.GetPath().u8string(); + + auto errors = ValidateManifestInstallers(manifest); + REQUIRE(7 == errors.size()); + + ValidateError(errors[0], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "SignatureSha256", "d70bd623f87b6ce4ddba4506c6000cf43ef3af4ab1207f5579ec43400de1623f"); + + // Validate errors for the first msix package in the msix bundle + ValidateError(errors[1], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); + ValidateError(errors[2], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); + ValidateError(errors[3], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); + + // Validate errors for the second msix package in the msix bundle + ValidateError(errors[4], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageFamilyName", "FakeInstallerForTesting_125rzkzqaqjwj"); + ValidateError(errors[5], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "PackageVersion", "43690.48059.52428.56797"); + ValidateError(errors[6], ValidationError::Level::Error, ManifestError::InstallerMsixInconsistencies, "MinimumOSVersion", "10.0.16299.0"); +} + +TEST_CASE("ManifestArpVersionRange", "[ManifestValidation]") +{ + Manifest manifestNoArp = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-NoArpVersionDeclared.yaml")); + REQUIRE(manifestNoArp.GetArpVersionRange().IsEmpty()); + + Manifest manifestSingleArp = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-SingleArpVersionDeclared.yaml")); + auto arpRangeSingleArp = manifestSingleArp.GetArpVersionRange(); + REQUIRE(arpRangeSingleArp.GetMinVersion().ToString() == "11.0"); + REQUIRE(arpRangeSingleArp.GetMaxVersion().ToString() == "11.0"); + + Manifest manifestMultiArp = YamlParser::CreateFromPath(TestDataFile("Manifest-Good-MultipleArpVersionDeclared.yaml")); + auto arpRangeMultiArp = manifestMultiArp.GetArpVersionRange(); + REQUIRE(arpRangeMultiArp.GetMinVersion().ToString() == "12.0"); + REQUIRE(arpRangeMultiArp.GetMaxVersion().ToString() == "13.0"); +} + +TEST_CASE("ManifestV1_10_SchemaHeaderValidations", "[ManifestValidation]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + + // Schema header not found + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderNotFound.yaml"),validateOption), ManifestException, ManifestExceptionMatcher("Schema header not found")); + + // Schema header not valid + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderInvalid.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The schema header is invalid. Please verify that the schema header is present and formatted correctly.")); + + // Schema header URL does not match the expected schema URL + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderURLPatternMismatch.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The schema header URL does not match the expected pattern.")); + + // Schema header ManifestType does not match the expected value + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderManifestTypeMismatch.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The manifest type in the schema header does not match the ManifestType property value in the manifest.")); + + // Schema header version does not match the expected version + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(TestDataFile("ManifestV1_10-Bad-SchemaHeaderManifestVersionMismatch.yaml"), validateOption), ManifestException, ManifestExceptionMatcher("The manifest version in the schema header does not match the ManifestVersion property value in the manifest.")); +} + +TEST_CASE("ShadowManifest", "[ShadowManifest]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.AllowShadowManifest = true; + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_5-MultiFile-Version.yaml", + "ManifestV1_5-Shadow-Installer.yaml", + "ManifestV1_5-Shadow-DefaultLocale.yaml", + "ManifestV1_5-Shadow-Locale.yaml", + "ManifestV1_5-Shadow-Locale2.yaml", + "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); + + auto shadowInfo = ManifestShadowTestInfo{}; + shadowInfo.shadowDefaultLocale = true; + shadowInfo.shadowEnGbLocale = true; + + TempFile mergedManifestFile{ "merged.yaml" }; + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); + VerifyV1ManifestContentCreatedWithShadow(multiFileManifest, shadowInfo); + + // Read from merged manifest should have the same content as multi file manifest + Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); + VerifyV1ManifestContentCreatedWithShadow(mergedManifest, shadowInfo); +} + +TEST_CASE("ShadowManifest_SkipShadowDefaultLocale", "[ShadowManifest]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.AllowShadowManifest = true; + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_5-MultiFile-Version.yaml", + "ManifestV1_5-Shadow-Installer.yaml", + "ManifestV1_5-MultiFile-DefaultLocale.yaml", + "ManifestV1_5-Shadow-Locale.yaml", + "ManifestV1_5-Shadow-Locale2.yaml", + "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); + + auto shadowInfo = ManifestShadowTestInfo{}; + shadowInfo.shadowDefaultLocale = false; + shadowInfo.shadowEnGbLocale = true; + + TempFile mergedManifestFile{ "merged.yaml" }; + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); + VerifyV1ManifestContentCreatedWithShadow(multiFileManifest, shadowInfo); + + // Read from merged manifest should have the same content as multi file manifest + Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); + VerifyV1ManifestContentCreatedWithShadow(mergedManifest, shadowInfo); +} + +TEST_CASE("ShadowManifest_SkipShadowLocalizationLocale", "[ShadowManifest]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.AllowShadowManifest = true; + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_5-MultiFile-Version.yaml", + "ManifestV1_5-Shadow-Installer.yaml", + "ManifestV1_5-Shadow-DefaultLocale.yaml", + "ManifestV1_5-MultiFile-Locale.yaml", + "ManifestV1_5-Shadow-Locale2.yaml", + "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); + + auto shadowInfo = ManifestShadowTestInfo{}; + shadowInfo.shadowDefaultLocale = true; + shadowInfo.shadowEnGbLocale = false; + + TempFile mergedManifestFile{ "merged.yaml" }; + Manifest multiFileManifest = YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile); + VerifyV1ManifestContentCreatedWithShadow(multiFileManifest, shadowInfo); + + // Read from merged manifest should have the same content as multi file manifest + Manifest mergedManifest = YamlParser::CreateFromPath(mergedManifestFile); + VerifyV1ManifestContentCreatedWithShadow(mergedManifest, shadowInfo); +} + +TEST_CASE("ShadowManifest_ShadowNotAllowed", "[ShadowManifest]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.AllowShadowManifest = false; + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_5-MultiFile-Version.yaml", + "ManifestV1_5-Shadow-Installer.yaml", + "ManifestV1_5-Shadow-DefaultLocale.yaml", + "ManifestV1_5-Shadow-Locale.yaml", + "ManifestV1_5-Shadow-Locale2.yaml", + "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); + + TempFile mergedManifestFile{ "merged.yaml" }; + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile), ManifestException, ManifestExceptionMatcher("Shadow manifest is not allowed. [ManifestType] Value: shadow File: ManifestV1_5-Shadow-Shadow.yaml")); +} + +TEST_CASE("ShadowManifest_TwoShadowFiles", "[ShadowManifest]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.AllowShadowManifest = true; + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_5-MultiFile-Version.yaml", + "ManifestV1_5-Shadow-Installer.yaml", + "ManifestV1_5-Shadow-DefaultLocale.yaml", + "ManifestV1_5-Shadow-Shadow.yaml", + "ManifestV1_5-Shadow-Shadow2.yaml" }, multiFileDirectory); + + TempFile mergedManifestFile{ "merged.yaml" }; + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile), ManifestException, ManifestExceptionMatcher("The multi file manifest should contain only one file with the particular ManifestType. [ManifestType] Value: shadow File: ManifestV1_5-Shadow-Shadow2.yaml")); +} + +TEST_CASE("ShadowManifest_NotVerifiedPublisher", "[ShadowManifest]") +{ + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.AllowShadowManifest = true; + validateOption.ErrorOnVerifiedPublisherFields = true; + + TempDirectory multiFileDirectory{ "MultiFileManifest" }; + CopyTestDataFilesToFolder({ + "ManifestV1_5-MultiFile-Version.yaml", + "ManifestV1_5-Shadow-Installer.yaml", + "ManifestV1_5-Shadow-DefaultLocale.yaml", + "ManifestV1_5-Shadow-Locale.yaml", + "ManifestV1_5-Shadow-Locale2.yaml", + "ManifestV1_5-Shadow-Shadow.yaml" }, multiFileDirectory); + + TempFile mergedManifestFile{ "merged.yaml" }; + REQUIRE_THROWS_MATCHES(YamlParser::CreateFromPath(multiFileDirectory, validateOption, mergedManifestFile), ManifestException, ManifestExceptionMatcher("Field usage requires verified publishers. [Icons]")); +} + +TEST_CASE("Manifest_PackageFamilyNameInheritance", "[ManifestValidation]") +{ + std::filesystem::path testManifest; + + SECTION("MSIX inside Archive") + { + testManifest = "Manifest-MSIX-in-Archive.yaml"; + } + SECTION("MSIX in AppsAndFeatures") + { + testManifest = "Manifest-MSIX-in-AppsAndFeatures.yaml"; + } + + auto manifest = YamlParser::CreateFromPath(TestDataFile(testManifest), GetTestManifestValidateOption()); + + REQUIRE(!manifest.Installers.empty()); + REQUIRE(!manifest.Installers[0].PackageFamilyName.empty()); +} diff --git a/src/AppInstallerCLITests/packages.config b/src/AppInstallerCLITests/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/AppInstallerCLITests/packages.config +++ b/src/AppInstallerCLITests/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/AppInstallerCLITests/pch.h b/src/AppInstallerCLITests/pch.h index e6ca25b743..c7bb7c1f48 100644 --- a/src/AppInstallerCLITests/pch.h +++ b/src/AppInstallerCLITests/pch.h @@ -31,7 +31,7 @@ #include #include -#include +#include #include #include diff --git a/src/AppInstallerCommonCore/AdminSettings.cpp b/src/AppInstallerCommonCore/AdminSettings.cpp index 7ab9398171..d8b5cd83c7 100644 --- a/src/AppInstallerCommonCore/AdminSettings.cpp +++ b/src/AppInstallerCommonCore/AdminSettings.cpp @@ -1,478 +1,478 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerLogging.h" -#include "AppInstallerStrings.h" -#include "winget/Settings.h" -#include "winget/AdminSettings.h" -#include "winget/GroupPolicy.h" -#include "winget/Yaml.h" - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - using namespace Utility::literals; - - namespace - { - constexpr Utility::LocIndView s_AdminSettingsYaml_LocalManifestFiles = "LocalManifestFiles"_liv; - constexpr Utility::LocIndView s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore = "BypassCertificatePinningForMicrosoftStore"_liv; - constexpr Utility::LocIndView s_AdminSettingsYaml_InstallerHashOverride = "InstallerHashOverride"_liv; - constexpr Utility::LocIndView s_AdminSettingsYaml_LocalArchiveMalwareScanOverride = "LocalArchiveMalwareScanOverride"_liv; - constexpr Utility::LocIndView s_AdminSettingsYaml_ProxyCommandLineOptions = "ProxyCommandLineOptions"_liv; - constexpr Utility::LocIndView s_AdminSettingsYaml_ConfigurationProcessorPath = "ConfigurationProcessorPath"_liv; - - constexpr Utility::LocIndView s_AdminSettingsYaml_DefaultProxy = "DefaultProxy"_liv; - - // Attempts to read a single scalar value from the node. - template - bool TryReadScalar(const YAML::Node& rootNode, std::string_view name, Value& value) - { - YAML::Node valueNode = rootNode[std::string{ name }]; - - if (!valueNode || !valueNode.IsScalar()) - { - AICLI_LOG(Core, Verbose, << "Admin setting '" << name << "' was not found or did not contain the expected format"); - return false; - } - - value = valueNode.as(); - return true; - } - - struct AdminSettingValues - { - bool LocalManifestFiles = false; - bool BypassCertificatePinningForMicrosoftStore = false; - bool InstallerHashOverride = false; - bool LocalArchiveMalwareScanOverride = false; - bool ProxyCommandLineOptions = false; - bool ConfigurationProcessorPath = false; - - std::optional DefaultProxy; - }; - - struct AdminSettingsInternal - { - AdminSettingsInternal(); - - void SetAdminSetting(BoolAdminSetting setting, bool enabled); - void SetAdminSetting(StringAdminSetting setting, const std::optional& value); - - bool GetAdminSettingValue(BoolAdminSetting setting) const; - std::optional GetAdminSettingValue(StringAdminSetting setting) const; - - void Reset(); - - private: - void LoadAdminSettings(); - [[nodiscard]] bool SaveAdminSettings(); - - // Sets the value of an admin setting using the given function and then saves the changes. - // Encapsulates the retry and reload logic. - // Stops if the value cannot be set, as indicated by the return value of setValue() - void SetAdminSettingAndSave(std::function setValue); - - Stream m_settingStream; - AdminSettingValues m_settingValues; - }; - - AdminSettingsInternal::AdminSettingsInternal() : m_settingStream(Stream::AdminSettings) - { - LoadAdminSettings(); - } - - void AdminSettingsInternal::SetAdminSettingAndSave(std::function setValue) - { - for (size_t i = 0; i < 10; ++i) - { - if (!setValue()) - { - return; - } - - if (SaveAdminSettings()) - { - return; - } - - // We need to reload the settings as they have changed - LoadAdminSettings(); - } - - THROW_HR_MSG(E_UNEXPECTED, "Too many attempts at SaveAdminSettings"); - } - - void AdminSettingsInternal::SetAdminSetting(BoolAdminSetting setting, bool enabled) - { - SetAdminSettingAndSave([&]() - { - switch (setting) - { - case BoolAdminSetting::LocalManifestFiles: - m_settingValues.LocalManifestFiles = enabled; - return true; - case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: - m_settingValues.BypassCertificatePinningForMicrosoftStore = enabled; - return true; - case BoolAdminSetting::InstallerHashOverride: - m_settingValues.InstallerHashOverride = enabled; - return true; - case BoolAdminSetting::LocalArchiveMalwareScanOverride: - m_settingValues.LocalArchiveMalwareScanOverride = enabled; - return true; - case BoolAdminSetting::ProxyCommandLineOptions: - m_settingValues.ProxyCommandLineOptions = enabled; - return true; - case BoolAdminSetting::ConfigurationProcessorPath: - m_settingValues.ConfigurationProcessorPath = enabled; - return true; - default: - return false; - } - }); - } - - void AdminSettingsInternal::SetAdminSetting(StringAdminSetting setting, const std::optional& value) - { - SetAdminSettingAndSave([&]() - { - switch (setting) - { - case StringAdminSetting::DefaultProxy: - m_settingValues.DefaultProxy = value; - return true; - default: - return false; - } - }); - } - - bool AdminSettingsInternal::GetAdminSettingValue(BoolAdminSetting setting) const - { - switch (setting) - { - case BoolAdminSetting::LocalManifestFiles: - return m_settingValues.LocalManifestFiles; - case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: - return m_settingValues.BypassCertificatePinningForMicrosoftStore; - case BoolAdminSetting::InstallerHashOverride: - return m_settingValues.InstallerHashOverride; - case BoolAdminSetting::LocalArchiveMalwareScanOverride: - return m_settingValues.LocalArchiveMalwareScanOverride; - case BoolAdminSetting::ProxyCommandLineOptions: - return m_settingValues.ProxyCommandLineOptions; - case BoolAdminSetting::ConfigurationProcessorPath: - return m_settingValues.ConfigurationProcessorPath; - default: - return false; - } - } - - std::optional AdminSettingsInternal::GetAdminSettingValue(StringAdminSetting setting) const - { - switch (setting) - { - case StringAdminSetting::DefaultProxy: - return m_settingValues.DefaultProxy; - default: - return std::nullopt; - } - } - - void AdminSettingsInternal::Reset() - { - m_settingStream.Remove(); - } - - void AdminSettingsInternal::LoadAdminSettings() - { - std::unique_ptr stream; - try - { - stream = m_settingStream.Get(); - } - catch (const std::exception& e) - { - AICLI_LOG(Core, Error, << "Failed to read admin settings: " << e.what() << ". Falling back to default values."); - return; - } - catch (...) - { - AICLI_LOG(Core, Error, << "Failed to read admin settings due to unknown exception. Falling back to default values."); - return; - } - - if (!stream) - { - AICLI_LOG(Core, Verbose, << "Admin settings was not found"); - return; - } - - std::string adminSettingsYaml = Utility::ReadEntireStream(*stream); - - YAML::Node document; - try - { - document = YAML::Load(adminSettingsYaml); - } - catch (const std::exception& e) - { - AICLI_LOG(YAML, Error, << "Admin settings contained invalid YAML (" << e.what() << ")"); - return; - } - - if (document.IsNull()) - { - AICLI_LOG(Core, Info, << "Admin settings is empty"); - return; - } - - if (!document.IsMap()) - { - AICLI_LOG(Core, Error, << "Admin settings did not contain the expected format"); - return; - } - - TryReadScalar(document, s_AdminSettingsYaml_LocalManifestFiles, m_settingValues.LocalManifestFiles); - TryReadScalar(document, s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore, m_settingValues.BypassCertificatePinningForMicrosoftStore); - TryReadScalar(document, s_AdminSettingsYaml_InstallerHashOverride, m_settingValues.InstallerHashOverride); - TryReadScalar(document, s_AdminSettingsYaml_LocalArchiveMalwareScanOverride, m_settingValues.LocalArchiveMalwareScanOverride); - TryReadScalar(document, s_AdminSettingsYaml_ProxyCommandLineOptions, m_settingValues.ProxyCommandLineOptions); - TryReadScalar(document, s_AdminSettingsYaml_ConfigurationProcessorPath, m_settingValues.ConfigurationProcessorPath); - - std::string defaultProxy; - if (TryReadScalar(document, s_AdminSettingsYaml_DefaultProxy, defaultProxy)) - { - m_settingValues.DefaultProxy.emplace(std::move(defaultProxy)); - } - - AICLI_LOG(Core, Verbose, << "Admin settings loaded successfully"); - } - - bool AdminSettingsInternal::SaveAdminSettings() - { - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << s_AdminSettingsYaml_LocalManifestFiles << YAML::Value << m_settingValues.LocalManifestFiles; - out << YAML::Key << s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore << YAML::Value << m_settingValues.BypassCertificatePinningForMicrosoftStore; - out << YAML::Key << s_AdminSettingsYaml_InstallerHashOverride << YAML::Value << m_settingValues.InstallerHashOverride; - out << YAML::Key << s_AdminSettingsYaml_LocalArchiveMalwareScanOverride << YAML::Value << m_settingValues.LocalArchiveMalwareScanOverride; - out << YAML::Key << s_AdminSettingsYaml_ProxyCommandLineOptions << YAML::Value << m_settingValues.ProxyCommandLineOptions; - out << YAML::Key << s_AdminSettingsYaml_ConfigurationProcessorPath << YAML::Value << m_settingValues.ConfigurationProcessorPath; - - if (m_settingValues.DefaultProxy) - { - out << YAML::Key << s_AdminSettingsYaml_DefaultProxy << YAML::Value << m_settingValues.DefaultProxy.value(); - } - - out << YAML::EndMap; - - return m_settingStream.Set(out.str()); - } - - auto GetPolicyStateForSetting(BoolAdminSetting setting) - { - auto policy = GetAdminSettingPolicy(setting); - return GroupPolicies().GetState(policy); - } - - std::optional GetPolicyStateForSetting(StringAdminSetting setting) - { - switch (setting) - { - case AppInstaller::Settings::StringAdminSetting::DefaultProxy: - return GroupPolicies().GetValue(); - default: - return std::nullopt; - } - } - } - - BoolAdminSetting StringToBoolAdminSetting(std::string_view in) - { - BoolAdminSetting result = BoolAdminSetting::Unknown; - - if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_LocalManifestFiles, in)) - { - result = BoolAdminSetting::LocalManifestFiles; - } - else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore, in)) - { - result = BoolAdminSetting::BypassCertificatePinningForMicrosoftStore; - } - else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_InstallerHashOverride, in)) - { - result = BoolAdminSetting::InstallerHashOverride; - } - else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_LocalArchiveMalwareScanOverride, in)) - { - result = BoolAdminSetting::LocalArchiveMalwareScanOverride; - } - else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_ProxyCommandLineOptions, in)) - { - result = BoolAdminSetting::ProxyCommandLineOptions; - } - else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_ConfigurationProcessorPath, in)) - { - result = BoolAdminSetting::ConfigurationProcessorPath; - } - - return result; - } - - StringAdminSetting StringToStringAdminSetting(std::string_view in) - { - StringAdminSetting result = StringAdminSetting::Unknown; - - if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_DefaultProxy, in)) - { - result = StringAdminSetting::DefaultProxy; - } - - return result; - } - - Utility::LocIndView AdminSettingToString(BoolAdminSetting setting) - { - switch (setting) - { - case BoolAdminSetting::LocalManifestFiles: - return s_AdminSettingsYaml_LocalManifestFiles; - case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: - return s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore; - case BoolAdminSetting::InstallerHashOverride: - return s_AdminSettingsYaml_InstallerHashOverride; - case BoolAdminSetting::LocalArchiveMalwareScanOverride: - return s_AdminSettingsYaml_LocalArchiveMalwareScanOverride; - case BoolAdminSetting::ProxyCommandLineOptions: - return s_AdminSettingsYaml_ProxyCommandLineOptions; - case BoolAdminSetting::ConfigurationProcessorPath: - return s_AdminSettingsYaml_ConfigurationProcessorPath; - default: - return "Unknown"_liv; - } - } - - Utility::LocIndView AdminSettingToString(StringAdminSetting setting) - { - switch (setting) - { - case StringAdminSetting::DefaultProxy: - return s_AdminSettingsYaml_DefaultProxy; - default: - return "Unknown"_liv; - } - } - - TogglePolicy::Policy GetAdminSettingPolicy(BoolAdminSetting setting) - { - switch (setting) - { - case BoolAdminSetting::LocalManifestFiles: - return TogglePolicy::Policy::LocalManifestFiles; - case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: - return TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore; - case BoolAdminSetting::InstallerHashOverride: - return TogglePolicy::Policy::HashOverride; - case BoolAdminSetting::LocalArchiveMalwareScanOverride: - return TogglePolicy::Policy::LocalArchiveMalwareScanOverride; - case BoolAdminSetting::ProxyCommandLineOptions: - return TogglePolicy::Policy::ProxyCommandLineOptions; - case BoolAdminSetting::ConfigurationProcessorPath: - return TogglePolicy::Policy::ConfigurationProcessorPath; - default: - return TogglePolicy::Policy::None; - } - } - - bool EnableAdminSetting(BoolAdminSetting setting) - { - if (GetPolicyStateForSetting(setting) == PolicyState::Disabled) - { - return false; - } - - AdminSettingsInternal adminSettingsInternal; - adminSettingsInternal.SetAdminSetting(setting, true); - return true; - } - - bool DisableAdminSetting(BoolAdminSetting setting) - { - if (GetPolicyStateForSetting(setting) == PolicyState::Enabled) - { - return false; - } - - AdminSettingsInternal adminSettingsInternal; - adminSettingsInternal.SetAdminSetting(setting, false); - return true; - } - - bool IsAdminSettingEnabled(BoolAdminSetting setting) - { - // Check for a policy that overrides this setting. - auto policyState = GetPolicyStateForSetting(setting); - if (policyState != PolicyState::NotConfigured) - { - return policyState == PolicyState::Enabled; - } - - AdminSettingsInternal adminSettingsInternal; - return adminSettingsInternal.GetAdminSettingValue(setting); - } - - bool SetAdminSetting(StringAdminSetting setting, std::string_view value) - { - if (GetPolicyStateForSetting(setting)) - { - return false; - } - - AdminSettingsInternal adminSettingsInternal; - adminSettingsInternal.SetAdminSetting(setting, std::string{ value }); - return true; - } - - bool ResetAdminSetting(StringAdminSetting setting) - { - if (GetPolicyStateForSetting(setting)) - { - return false; - } - - AdminSettingsInternal adminSettingsInternal; - adminSettingsInternal.SetAdminSetting(setting, std::nullopt); - return true; - } - - void ResetAllAdminSettings() - { - AdminSettingsInternal{}.Reset(); - } - - std::optional GetAdminSetting(StringAdminSetting setting) - { - // Check for a policy that overrides this setting. - auto policyState = GetPolicyStateForSetting(setting); - if (policyState) - { - return policyState.value(); - } - - AdminSettingsInternal adminSettingsInternal; - return adminSettingsInternal.GetAdminSettingValue(setting); - } - - std::vector GetAllBoolAdminSettings() - { - return GetAllSequentialEnumValues(BoolAdminSetting::Unknown); - } - - std::vector GetAllStringAdminSettings() - { - return GetAllSequentialEnumValues(StringAdminSetting::Unknown); - } - -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerLogging.h" +#include "AppInstallerStrings.h" +#include "winget/Settings.h" +#include "winget/AdminSettings.h" +#include "winget/GroupPolicy.h" +#include "winget/Yaml.h" + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Utility::literals; + + namespace + { + constexpr Utility::LocIndView s_AdminSettingsYaml_LocalManifestFiles = "LocalManifestFiles"_liv; + constexpr Utility::LocIndView s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore = "BypassCertificatePinningForMicrosoftStore"_liv; + constexpr Utility::LocIndView s_AdminSettingsYaml_InstallerHashOverride = "InstallerHashOverride"_liv; + constexpr Utility::LocIndView s_AdminSettingsYaml_LocalArchiveMalwareScanOverride = "LocalArchiveMalwareScanOverride"_liv; + constexpr Utility::LocIndView s_AdminSettingsYaml_ProxyCommandLineOptions = "ProxyCommandLineOptions"_liv; + constexpr Utility::LocIndView s_AdminSettingsYaml_ConfigurationProcessorPath = "ConfigurationProcessorPath"_liv; + + constexpr Utility::LocIndView s_AdminSettingsYaml_DefaultProxy = "DefaultProxy"_liv; + + // Attempts to read a single scalar value from the node. + template + bool TryReadScalar(const YAML::Node& rootNode, std::string_view name, Value& value) + { + YAML::Node valueNode = rootNode[std::string{ name }]; + + if (!valueNode || !valueNode.IsScalar()) + { + AICLI_LOG(Core, Verbose, << "Admin setting '" << name << "' was not found or did not contain the expected format"); + return false; + } + + value = valueNode.as(); + return true; + } + + struct AdminSettingValues + { + bool LocalManifestFiles = false; + bool BypassCertificatePinningForMicrosoftStore = false; + bool InstallerHashOverride = false; + bool LocalArchiveMalwareScanOverride = false; + bool ProxyCommandLineOptions = false; + bool ConfigurationProcessorPath = false; + + std::optional DefaultProxy; + }; + + struct AdminSettingsInternal + { + AdminSettingsInternal(); + + void SetAdminSetting(BoolAdminSetting setting, bool enabled); + void SetAdminSetting(StringAdminSetting setting, const std::optional& value); + + bool GetAdminSettingValue(BoolAdminSetting setting) const; + std::optional GetAdminSettingValue(StringAdminSetting setting) const; + + void Reset(); + + private: + void LoadAdminSettings(); + [[nodiscard]] bool SaveAdminSettings(); + + // Sets the value of an admin setting using the given function and then saves the changes. + // Encapsulates the retry and reload logic. + // Stops if the value cannot be set, as indicated by the return value of setValue() + void SetAdminSettingAndSave(std::function setValue); + + Stream m_settingStream; + AdminSettingValues m_settingValues; + }; + + AdminSettingsInternal::AdminSettingsInternal() : m_settingStream(Stream::AdminSettings) + { + LoadAdminSettings(); + } + + void AdminSettingsInternal::SetAdminSettingAndSave(std::function setValue) + { + for (size_t i = 0; i < 10; ++i) + { + if (!setValue()) + { + return; + } + + if (SaveAdminSettings()) + { + return; + } + + // We need to reload the settings as they have changed + LoadAdminSettings(); + } + + THROW_HR_MSG(E_UNEXPECTED, "Too many attempts at SaveAdminSettings"); + } + + void AdminSettingsInternal::SetAdminSetting(BoolAdminSetting setting, bool enabled) + { + SetAdminSettingAndSave([&]() + { + switch (setting) + { + case BoolAdminSetting::LocalManifestFiles: + m_settingValues.LocalManifestFiles = enabled; + return true; + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: + m_settingValues.BypassCertificatePinningForMicrosoftStore = enabled; + return true; + case BoolAdminSetting::InstallerHashOverride: + m_settingValues.InstallerHashOverride = enabled; + return true; + case BoolAdminSetting::LocalArchiveMalwareScanOverride: + m_settingValues.LocalArchiveMalwareScanOverride = enabled; + return true; + case BoolAdminSetting::ProxyCommandLineOptions: + m_settingValues.ProxyCommandLineOptions = enabled; + return true; + case BoolAdminSetting::ConfigurationProcessorPath: + m_settingValues.ConfigurationProcessorPath = enabled; + return true; + default: + return false; + } + }); + } + + void AdminSettingsInternal::SetAdminSetting(StringAdminSetting setting, const std::optional& value) + { + SetAdminSettingAndSave([&]() + { + switch (setting) + { + case StringAdminSetting::DefaultProxy: + m_settingValues.DefaultProxy = value; + return true; + default: + return false; + } + }); + } + + bool AdminSettingsInternal::GetAdminSettingValue(BoolAdminSetting setting) const + { + switch (setting) + { + case BoolAdminSetting::LocalManifestFiles: + return m_settingValues.LocalManifestFiles; + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: + return m_settingValues.BypassCertificatePinningForMicrosoftStore; + case BoolAdminSetting::InstallerHashOverride: + return m_settingValues.InstallerHashOverride; + case BoolAdminSetting::LocalArchiveMalwareScanOverride: + return m_settingValues.LocalArchiveMalwareScanOverride; + case BoolAdminSetting::ProxyCommandLineOptions: + return m_settingValues.ProxyCommandLineOptions; + case BoolAdminSetting::ConfigurationProcessorPath: + return m_settingValues.ConfigurationProcessorPath; + default: + return false; + } + } + + std::optional AdminSettingsInternal::GetAdminSettingValue(StringAdminSetting setting) const + { + switch (setting) + { + case StringAdminSetting::DefaultProxy: + return m_settingValues.DefaultProxy; + default: + return std::nullopt; + } + } + + void AdminSettingsInternal::Reset() + { + m_settingStream.Remove(); + } + + void AdminSettingsInternal::LoadAdminSettings() + { + std::unique_ptr stream; + try + { + stream = m_settingStream.Get(); + } + catch (const std::exception& e) + { + AICLI_LOG(Core, Error, << "Failed to read admin settings: " << e.what() << ". Falling back to default values."); + return; + } + catch (...) + { + AICLI_LOG(Core, Error, << "Failed to read admin settings due to unknown exception. Falling back to default values."); + return; + } + + if (!stream) + { + AICLI_LOG(Core, Verbose, << "Admin settings was not found"); + return; + } + + std::string adminSettingsYaml = Utility::ReadEntireStream(*stream); + + YAML::Node document; + try + { + document = YAML::Load(adminSettingsYaml); + } + catch (const std::exception& e) + { + AICLI_LOG(YAML, Error, << "Admin settings contained invalid YAML (" << e.what() << ")"); + return; + } + + if (document.IsNull()) + { + AICLI_LOG(Core, Info, << "Admin settings is empty"); + return; + } + + if (!document.IsMap()) + { + AICLI_LOG(Core, Error, << "Admin settings did not contain the expected format"); + return; + } + + TryReadScalar(document, s_AdminSettingsYaml_LocalManifestFiles, m_settingValues.LocalManifestFiles); + TryReadScalar(document, s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore, m_settingValues.BypassCertificatePinningForMicrosoftStore); + TryReadScalar(document, s_AdminSettingsYaml_InstallerHashOverride, m_settingValues.InstallerHashOverride); + TryReadScalar(document, s_AdminSettingsYaml_LocalArchiveMalwareScanOverride, m_settingValues.LocalArchiveMalwareScanOverride); + TryReadScalar(document, s_AdminSettingsYaml_ProxyCommandLineOptions, m_settingValues.ProxyCommandLineOptions); + TryReadScalar(document, s_AdminSettingsYaml_ConfigurationProcessorPath, m_settingValues.ConfigurationProcessorPath); + + std::string defaultProxy; + if (TryReadScalar(document, s_AdminSettingsYaml_DefaultProxy, defaultProxy)) + { + m_settingValues.DefaultProxy.emplace(std::move(defaultProxy)); + } + + AICLI_LOG(Core, Verbose, << "Admin settings loaded successfully"); + } + + bool AdminSettingsInternal::SaveAdminSettings() + { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << s_AdminSettingsYaml_LocalManifestFiles << YAML::Value << m_settingValues.LocalManifestFiles; + out << YAML::Key << s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore << YAML::Value << m_settingValues.BypassCertificatePinningForMicrosoftStore; + out << YAML::Key << s_AdminSettingsYaml_InstallerHashOverride << YAML::Value << m_settingValues.InstallerHashOverride; + out << YAML::Key << s_AdminSettingsYaml_LocalArchiveMalwareScanOverride << YAML::Value << m_settingValues.LocalArchiveMalwareScanOverride; + out << YAML::Key << s_AdminSettingsYaml_ProxyCommandLineOptions << YAML::Value << m_settingValues.ProxyCommandLineOptions; + out << YAML::Key << s_AdminSettingsYaml_ConfigurationProcessorPath << YAML::Value << m_settingValues.ConfigurationProcessorPath; + + if (m_settingValues.DefaultProxy) + { + out << YAML::Key << s_AdminSettingsYaml_DefaultProxy << YAML::Value << m_settingValues.DefaultProxy.value(); + } + + out << YAML::EndMap; + + return m_settingStream.Set(out.str()); + } + + auto GetPolicyStateForSetting(BoolAdminSetting setting) + { + auto policy = GetAdminSettingPolicy(setting); + return GroupPolicies().GetState(policy); + } + + std::optional GetPolicyStateForSetting(StringAdminSetting setting) + { + switch (setting) + { + case AppInstaller::Settings::StringAdminSetting::DefaultProxy: + return GroupPolicies().GetValue(); + default: + return std::nullopt; + } + } + } + + BoolAdminSetting StringToBoolAdminSetting(std::string_view in) + { + BoolAdminSetting result = BoolAdminSetting::Unknown; + + if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_LocalManifestFiles, in)) + { + result = BoolAdminSetting::LocalManifestFiles; + } + else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore, in)) + { + result = BoolAdminSetting::BypassCertificatePinningForMicrosoftStore; + } + else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_InstallerHashOverride, in)) + { + result = BoolAdminSetting::InstallerHashOverride; + } + else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_LocalArchiveMalwareScanOverride, in)) + { + result = BoolAdminSetting::LocalArchiveMalwareScanOverride; + } + else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_ProxyCommandLineOptions, in)) + { + result = BoolAdminSetting::ProxyCommandLineOptions; + } + else if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_ConfigurationProcessorPath, in)) + { + result = BoolAdminSetting::ConfigurationProcessorPath; + } + + return result; + } + + StringAdminSetting StringToStringAdminSetting(std::string_view in) + { + StringAdminSetting result = StringAdminSetting::Unknown; + + if (Utility::CaseInsensitiveEquals(s_AdminSettingsYaml_DefaultProxy, in)) + { + result = StringAdminSetting::DefaultProxy; + } + + return result; + } + + Utility::LocIndView AdminSettingToString(BoolAdminSetting setting) + { + switch (setting) + { + case BoolAdminSetting::LocalManifestFiles: + return s_AdminSettingsYaml_LocalManifestFiles; + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: + return s_AdminSettingsYaml_BypassCertificatePinningForMicrosoftStore; + case BoolAdminSetting::InstallerHashOverride: + return s_AdminSettingsYaml_InstallerHashOverride; + case BoolAdminSetting::LocalArchiveMalwareScanOverride: + return s_AdminSettingsYaml_LocalArchiveMalwareScanOverride; + case BoolAdminSetting::ProxyCommandLineOptions: + return s_AdminSettingsYaml_ProxyCommandLineOptions; + case BoolAdminSetting::ConfigurationProcessorPath: + return s_AdminSettingsYaml_ConfigurationProcessorPath; + default: + return "Unknown"_liv; + } + } + + Utility::LocIndView AdminSettingToString(StringAdminSetting setting) + { + switch (setting) + { + case StringAdminSetting::DefaultProxy: + return s_AdminSettingsYaml_DefaultProxy; + default: + return "Unknown"_liv; + } + } + + TogglePolicy::Policy GetAdminSettingPolicy(BoolAdminSetting setting) + { + switch (setting) + { + case BoolAdminSetting::LocalManifestFiles: + return TogglePolicy::Policy::LocalManifestFiles; + case BoolAdminSetting::BypassCertificatePinningForMicrosoftStore: + return TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore; + case BoolAdminSetting::InstallerHashOverride: + return TogglePolicy::Policy::HashOverride; + case BoolAdminSetting::LocalArchiveMalwareScanOverride: + return TogglePolicy::Policy::LocalArchiveMalwareScanOverride; + case BoolAdminSetting::ProxyCommandLineOptions: + return TogglePolicy::Policy::ProxyCommandLineOptions; + case BoolAdminSetting::ConfigurationProcessorPath: + return TogglePolicy::Policy::ConfigurationProcessorPath; + default: + return TogglePolicy::Policy::None; + } + } + + bool EnableAdminSetting(BoolAdminSetting setting) + { + if (GetPolicyStateForSetting(setting) == PolicyState::Disabled) + { + return false; + } + + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, true); + return true; + } + + bool DisableAdminSetting(BoolAdminSetting setting) + { + if (GetPolicyStateForSetting(setting) == PolicyState::Enabled) + { + return false; + } + + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, false); + return true; + } + + bool IsAdminSettingEnabled(BoolAdminSetting setting) + { + // Check for a policy that overrides this setting. + auto policyState = GetPolicyStateForSetting(setting); + if (policyState != PolicyState::NotConfigured) + { + return policyState == PolicyState::Enabled; + } + + AdminSettingsInternal adminSettingsInternal; + return adminSettingsInternal.GetAdminSettingValue(setting); + } + + bool SetAdminSetting(StringAdminSetting setting, std::string_view value) + { + if (GetPolicyStateForSetting(setting)) + { + return false; + } + + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, std::string{ value }); + return true; + } + + bool ResetAdminSetting(StringAdminSetting setting) + { + if (GetPolicyStateForSetting(setting)) + { + return false; + } + + AdminSettingsInternal adminSettingsInternal; + adminSettingsInternal.SetAdminSetting(setting, std::nullopt); + return true; + } + + void ResetAllAdminSettings() + { + AdminSettingsInternal{}.Reset(); + } + + std::optional GetAdminSetting(StringAdminSetting setting) + { + // Check for a policy that overrides this setting. + auto policyState = GetPolicyStateForSetting(setting); + if (policyState) + { + return policyState.value(); + } + + AdminSettingsInternal adminSettingsInternal; + return adminSettingsInternal.GetAdminSettingValue(setting); + } + + std::vector GetAllBoolAdminSettings() + { + return GetAllSequentialEnumValues(BoolAdminSetting::Unknown); + } + + std::vector GetAllStringAdminSettings() + { + return GetAllSequentialEnumValues(StringAdminSetting::Unknown); + } + +} diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj index ff8ac9f05e..302805976d 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj @@ -1,479 +1,479 @@ - - - - - true - true - false - true - 15.0 - {5890d6ed-7c3b-40f3-b436-b54f640d9e65} - Win32Proj - AppInstallerLoggingCore - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - Fuzzing - x64 - - - Fuzzing - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - StaticLibrary - - - true - true - - - false - true - false - - - false - true - false - - - false - false - false - true - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - - - false - Windows - Windows - - - - - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - false - - - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD;WINGET_DISABLE_FOR_FUZZING;_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - stdcpp17 - MultiThreaded - %(AdditionalOptions) /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div - false - - - true - true - false - Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - - - - - - - - - - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - true - - - - - - - - - - - - - - - - - - - - true - - - - - - - - - - - - true - - - - Create - - - - - - - - - - - - - - - - - - - - - - {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + true + true + false + true + 15.0 + {5890d6ed-7c3b-40f3-b436-b54f640d9e65} + Win32Proj + AppInstallerLoggingCore + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + Fuzzing + x64 + + + Fuzzing + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + StaticLibrary + + + true + true + + + false + true + false + + + false + true + false + + + false + false + false + true + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + + + false + Windows + Windows + + + + + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + false + + + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD;WINGET_DISABLE_FOR_FUZZING;_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\AppInstallerSharedLib;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + stdcpp17 + MultiThreaded + %(AdditionalOptions) /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div + false + + + true + true + false + Windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + true + + + + + + + + + + + + true + + + + Create + + + + + + + + + + + + + + + + + + + + + + {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + diff --git a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters index 1bc3db76bd..9e1ebdf1ca 100644 --- a/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters +++ b/src/AppInstallerCommonCore/AppInstallerCommonCore.vcxproj.filters @@ -1,400 +1,400 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {5cdf3fa3-e657-4d84-81bb-f740aa476143} - - - {a9c14af9-ca74-4945-a19c-9e99df23a5ae} - - - {41035fd6-dc74-4464-b9b1-4ffe95d6789c} - - - {9b8e2682-3eb7-4530-bc9a-a57fafc44177} - - - {552a58eb-8d07-41b2-87b5-3e71b9fb3cfd} - - - {9dd9ab66-00f0-4b18-90ca-6c5da3dc01c4} - - - - - Header Files - - - Telemetry - - - Public - - - Public - - - Public - - - Public - - - HttpStream - - - HttpStream - - - HttpStream - - - Public - - - Public - - - Public - - - Public - - - Public - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Header Files - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Header Files - - - Public\winget - - - Public\winget - - - Header Files - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Authentication - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - - - Source Files - - - Telemetry - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - HttpStream - - - HttpStream - - - HttpStream - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Manifest - - - Manifest - - - Manifest - - - Source Files - - - Source Files - - - Manifest - - - Manifest - - - Manifest - - - Manifest - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Authentication - - - Authentication - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Manifest - - - Source Files - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {5cdf3fa3-e657-4d84-81bb-f740aa476143} + + + {a9c14af9-ca74-4945-a19c-9e99df23a5ae} + + + {41035fd6-dc74-4464-b9b1-4ffe95d6789c} + + + {9b8e2682-3eb7-4530-bc9a-a57fafc44177} + + + {552a58eb-8d07-41b2-87b5-3e71b9fb3cfd} + + + {9dd9ab66-00f0-4b18-90ca-6c5da3dc01c4} + + + + + Header Files + + + Telemetry + + + Public + + + Public + + + Public + + + Public + + + HttpStream + + + HttpStream + + + HttpStream + + + Public + + + Public + + + Public + + + Public + + + Public + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Header Files + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Header Files + + + Public\winget + + + Public\winget + + + Header Files + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Authentication + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + + + Source Files + + + Telemetry + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + HttpStream + + + HttpStream + + + HttpStream + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Manifest + + + Manifest + + + Manifest + + + Source Files + + + Source Files + + + Manifest + + + Manifest + + + Manifest + + + Manifest + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Authentication + + + Authentication + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Manifest + + + Source Files + + + + + + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp index a8db8bc02d..2fea3ee27f 100644 --- a/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp +++ b/src/AppInstallerCommonCore/AppInstallerTelemetry.cpp @@ -1,1016 +1,1016 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerTelemetry.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerSHA256.h" -#include "Public/AppInstallerStrings.h" -#include "Public/winget/ThreadGlobals.h" -#include "winget/Filesystem.h" -#include "winget/UserSettings.h" -#include - -#define AICLI_TraceLoggingStringView(_sv_,_name_) TraceLoggingCountedUtf8String(_sv_.data(), static_cast(_sv_.size()), _name_) -#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast(_sv_.size()), _name_) - -#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ -g_hTraceProvider,\ -_eventName_,\ -s_useGlobalTelemetryActivityId ? &s_globalTelemetryLoggerActivityId : GetActivityId(),\ -s_useGlobalTelemetryActivityId ? nullptr : GetParentActivityId(),\ -TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast(m_caller.size()), "Caller"),\ -TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"),\ -__VA_ARGS__) - -namespace AppInstaller::Logging -{ - using namespace Utility; - - namespace - { - // TODO: This and all usages should be removed after transition to summary event in back end. - static const uint32_t s_RootExecutionId = 0; - static std::atomic_uint32_t s_subExecutionId{ s_RootExecutionId }; - - // Data that is needed by AnonymizeString - constexpr std::wstring_view s_UserProfileReplacement = L"%USERPROFILE%"sv; - - // TODO: Temporary code to keep existing telemetry behavior - static bool s_useGlobalTelemetryActivityId = false; - static GUID s_globalTelemetryLoggerActivityId = GUID_NULL; - - void __stdcall wilResultLoggingCallback(const wil::FailureInfo& info) noexcept - { - Telemetry().LogFailure(info); - } - - FailureTypeEnum ConvertWilFailureTypeToFailureType(wil::FailureType failureType) - { - switch (failureType) - { - case wil::FailureType::Exception: - return FailureTypeEnum::ResultException; - case wil::FailureType::Return: - return FailureTypeEnum::ResultReturn; - case wil::FailureType::Log: - return FailureTypeEnum::ResultLog; - case wil::FailureType::FailFast: - return FailureTypeEnum::ResultFailFast; - default: - return FailureTypeEnum::Unknown; - } - } - - std::string_view LogExceptionTypeToString(FailureTypeEnum exceptionType) - { - switch (exceptionType) - { - case FailureTypeEnum::ResultException: - return "wil::ResultException"sv; - case FailureTypeEnum::WinrtHResultError: - return "winrt::hresult_error"sv; - case FailureTypeEnum::ResourceOpen: - return "ResourceOpenException"sv; - case FailureTypeEnum::StdException: - return "std::exception"sv; - case FailureTypeEnum::Unknown: - default: - return "unknown"sv; - } - } - } - - TelemetrySummary::TelemetrySummary(const TelemetrySummary& other) - { - this->IsCOMCall = other.IsCOMCall; - } - - TelemetryTraceLogger::TelemetryTraceLogger(bool useSummary) : m_useSummary(useSummary) - { - std::ignore = CoCreateGuid(&m_activityId); - m_subExecutionId = s_RootExecutionId; - } - - const GUID* TelemetryTraceLogger::GetActivityId() const - { - return &m_activityId; - } - - const GUID* TelemetryTraceLogger::GetParentActivityId() const - { - return &m_parentActivityId; - } - - bool TelemetryTraceLogger::DisableRuntime() - { - return m_isRuntimeEnabled.exchange(false); - } - - void TelemetryTraceLogger::EnableRuntime() - { - m_isRuntimeEnabled = true; - } - - void TelemetryTraceLogger::Initialize() - { - if (!m_isInitialized) - { - InitializeInternal(Settings::User()); - } - } - - bool TelemetryTraceLogger::TryInitialize() - { - if (!m_isInitialized) - { - // Only initialize if we already have the user settings, so that we can respect the telemetry setting. - // We may not yet have the user settings if we are trying to report an error while reading them. - auto userSettings = Settings::TryGetUser(); - if (userSettings) - { - InitializeInternal(*userSettings); - } - } - - return m_isInitialized; - } - - void TelemetryTraceLogger::SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept - { - // Check if passed in string is a valid Json formatted before returning the value - // If invalid, return empty Json - Json::CharReaderBuilder jsonBuilder; - std::unique_ptr jsonReader(jsonBuilder.newCharReader()); - std::unique_ptr pJsonValue = std::make_unique(); - std::string errors; - std::wstring jsonStrW{ jsonStr_view }; - std::string jsonStr = ConvertToUTF8(jsonStrW.c_str()); - - bool result = jsonReader->parse(jsonStr.c_str(), - jsonStr.c_str() + jsonStr.size(), - pJsonValue.get(), - &errors); - - if (result) - { - m_telemetryCorrelationJsonW = jsonStrW; - AICLI_LOG(Core, Info, << "Passed in Correlation Vector Json is valid: " << jsonStr); - } - else - { - AICLI_LOG(Core, Error, << "Passed in Correlation Vector Json is invalid: " << jsonStr << "; Error: " << errors); - } - } - - void TelemetryTraceLogger::SetCaller(const std::string& caller) - { - auto callerUTF16 = Utility::ConvertToUTF16(caller); - auto anonCaller = AnonymizeString(callerUTF16); - m_caller = Utility::ConvertToUTF8(anonCaller); - } - - void TelemetryTraceLogger::SetExecutionStage(uint32_t stage) noexcept - { - m_executionStage = stage; - } - - std::unique_ptr TelemetryTraceLogger::CreateSubTraceLogger() const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !this->m_isInitialized); - - auto subTraceLogger = std::make_unique(*this); - - std::ignore = CoCreateGuid(&subTraceLogger->m_activityId); - subTraceLogger->m_parentActivityId = this->m_activityId; - subTraceLogger->m_subExecutionId = s_subExecutionId++; - - return subTraceLogger; - } - - void TelemetryTraceLogger::LogFailure(const wil::FailureInfo& failure) const noexcept - { - if (IsTelemetryEnabled()) - { - auto anonMessage = AnonymizeString(failure.pszMessage); - - AICLI_TraceLoggingWriteActivity( - "FailureInfo", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingHResult(failure.hr, "HResult"), - AICLI_TraceLoggingWStringView(anonMessage, "Message"), - TraceLoggingString(failure.pszModule, "Module"), - TraceLoggingUInt32(failure.threadId, "ThreadId"), - TraceLoggingUInt32(static_cast(failure.type), "Type"), - TraceLoggingString(failure.pszFile, "File"), - TraceLoggingUInt32(failure.uLineNumber, "Line"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.FailureHResult = failure.hr; - m_summary.FailureMessage = anonMessage; - m_summary.FailureModule = StringOrEmptyIfNull(failure.pszModule); - m_summary.FailureThreadId = failure.threadId; - m_summary.FailureType = ConvertWilFailureTypeToFailureType(failure.type); - m_summary.FailureFile = StringOrEmptyIfNull(failure.pszFile); - m_summary.FailureLine = failure.uLineNumber; - } - } - - // Also send failure to the log - AICLI_LOG(Fail, Error, << [&]() { - wchar_t message[2048]; - GetFailureLogString(message, ARRAYSIZE(message), failure); - return Utility::ConvertToUTF8(message); - }()); - } - - void TelemetryTraceLogger::LogStartup(bool isCOMCall) const noexcept - { - LocIndString version = Runtime::GetClientVersion(); - LocIndString packageVersion; - if (Runtime::IsRunningInPackagedContext()) - { - packageVersion = Runtime::GetPackageVersion(); - } - - ExecutionLevel executionLevel = ExecutionLevel::User; - if (Runtime::IsRunningAsSystem()) - { - executionLevel = ExecutionLevel::System; - } - else if (Runtime::IsRunningAsAdmin()) - { - executionLevel = ExecutionLevel::Admin; - } - - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "ClientVersion", - TraceLoggingBool(isCOMCall, "IsCOMCall"), - TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "Version"), - TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "PackageVersion"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.IsCOMCall = isCOMCall; - m_summary.ExecutionLevel = executionLevel; - } - } - - AICLI_LOG(Core, Info, << "WinGet, version [" << version << "], activity [" << *GetActivityId() << ']'); - AICLI_LOG(Core, Info, << "Process: " << Filesystem::GetExecutablePathForProcess(GetCurrentProcess()).filename() << "[" << GetCurrentProcessId() << "], Level[" << executionLevel << "], Offset: " << &__ImageBase); - AICLI_LOG(Core, Info, << "OS: " << Runtime::GetOSVersion()); - AICLI_LOG(Core, Info, << "Command line Args: " << Utility::ConvertToUTF8(GetCommandLineW())); - if (Runtime::IsRunningInPackagedContext()) - { - AICLI_LOG(Core, Info, << "Package: " << packageVersion); - } - AICLI_LOG(Core, Info, << "IsCOMCall:" << isCOMCall << "; Caller: " << m_caller); - - Log().SetTag(Tag::HeadersComplete); - } - - void TelemetryTraceLogger::LogCommand(std::string_view commandName) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "CommandFound", - AICLI_TraceLoggingStringView(commandName, "Command"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.Command = commandName; - } - } - - AICLI_LOG(CLI, Info, << "Leaf command to execute: " << commandName); - } - - void TelemetryTraceLogger::LogCommandSuccess(std::string_view commandName) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "CommandSuccess", - AICLI_TraceLoggingStringView(commandName, "Command"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.CommandSuccess = true; - } - } - - AICLI_LOG(CLI, Info, << "Leaf command succeeded: " << commandName); - } - - void TelemetryTraceLogger::LogCommandTermination(HRESULT hr, std::string_view file, size_t line) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "CommandTermination", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingHResult(hr, "HResult"), - AICLI_TraceLoggingStringView(file, "File"), - TraceLoggingUInt64(static_cast(line), "Line"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.FailureHResult = hr; - m_summary.FailureType = FailureTypeEnum::CommandTermination; - m_summary.FailureFile = file; - m_summary.FailureLine = static_cast(line); - } - } - - AICLI_LOG(CLI, Error, << "Terminating context: 0x" << SetHRFormat << hr << " at " << file << ":" << line); - } - - void TelemetryTraceLogger::LogException(FailureTypeEnum type, std::string_view message) const noexcept - { - auto exceptionTypeString = LogExceptionTypeToString(type); - - if (IsTelemetryEnabled()) - { - auto anonMessage = AnonymizeString(Utility::ConvertToUTF16(message)); - - AICLI_TraceLoggingWriteActivity( - "Exception", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(exceptionTypeString, "Type"), - AICLI_TraceLoggingWStringView(anonMessage, "Message"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.FailureType = type; - m_summary.FailureMessage = anonMessage; - } - } - - AICLI_LOG(CLI, Error, << "Caught " << exceptionTypeString << ": " << message); - } - - void TelemetryTraceLogger::LogIsManifestLocal(bool isLocalManifest) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "GetManifest", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingBool(isLocalManifest, "IsManifestLocal"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.IsManifestLocal = isLocalManifest; - } - } - } - - void TelemetryTraceLogger::LogManifestFields(std::string_view id, std::string_view name, std::string_view version) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "ManifestFields", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(name, "Name"), - AICLI_TraceLoggingStringView(version, "Version"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageName = name; - m_summary.PackageVersion = version; - } - } - - AICLI_LOG(CLI, Info, << "Manifest fields: Name [" << name << "], Version [" << version << ']'); - } - - void TelemetryTraceLogger::LogNoAppMatch() const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "NoAppMatch", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - } - - AICLI_LOG(CLI, Info, << "No app found matching input criteria"); - } - - void TelemetryTraceLogger::LogMultiAppMatch() const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "MultiAppMatch", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - } - - AICLI_LOG(CLI, Info, << "Multiple apps found matching input criteria"); - } - - void TelemetryTraceLogger::LogAppFound(std::string_view name, std::string_view id) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "AppFound", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(name, "Name"), - AICLI_TraceLoggingStringView(id, "Id"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageName = name; - } - } - - AICLI_LOG(CLI, Info, << "Found one app. App id: " << id << " App name: " << name); - } - - void TelemetryTraceLogger::LogSelectedInstaller(int arch, std::string_view url, std::string_view installerType, std::string_view scope, std::string_view language) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "SelectedInstaller", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingInt32(arch, "Arch"), - AICLI_TraceLoggingStringView(url, "Url"), - AICLI_TraceLoggingStringView(installerType, "InstallerType"), - AICLI_TraceLoggingStringView(scope, "Scope"), - AICLI_TraceLoggingStringView(language, "Language"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.InstallerArchitecture = arch; - m_summary.InstallerUrl = url; - m_summary.InstallerType = installerType; - m_summary.InstallerScope = scope; - m_summary.InstallerLocale = language; - } - } - - AICLI_LOG(CLI, Verbose, << "Completed installer selection."); - AICLI_LOG(CLI, Verbose, << "Selected installer Architecture: " << arch); - AICLI_LOG(CLI, Verbose, << "Selected installer URL: " << url); - AICLI_LOG(CLI, Verbose, << "Selected installer InstallerType: " << installerType); - AICLI_LOG(CLI, Verbose, << "Selected installer Scope: " << scope); - AICLI_LOG(CLI, Verbose, << "Selected installer Language: " << language); - } - - void TelemetryTraceLogger::LogSearchRequest( - std::string_view type, - std::string_view query, - std::string_view id, - std::string_view name, - std::string_view moniker, - std::string_view tag, - std::string_view command, - size_t maximum, - std::string_view request) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "SearchRequest", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(type, "Type"), - AICLI_TraceLoggingStringView(query, "Query"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(name, "Name"), - AICLI_TraceLoggingStringView(moniker, "Moniker"), - AICLI_TraceLoggingStringView(tag, "Tag"), - AICLI_TraceLoggingStringView(command, "Command"), - TraceLoggingUInt64(static_cast(maximum), "Maximum"), - AICLI_TraceLoggingStringView(request, "Request"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.SearchType = type; - m_summary.SearchQuery = query; - m_summary.SearchId = id; - m_summary.SearchName = name; - m_summary.SearchMoniker = moniker; - m_summary.SearchTag = tag; - m_summary.SearchCommand = command; - m_summary.SearchMaximum = static_cast(maximum); - m_summary.SearchRequest = request; - } - } - } - - void TelemetryTraceLogger::LogSearchResultCount(uint64_t resultCount) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "SearchResultCount", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - TraceLoggingUInt64(resultCount, "ResultCount"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.SearchResultCount = resultCount; - } - } - - AICLI_LOG(CLI, Verbose, << "Search result size: " << resultCount); - } - - void TelemetryTraceLogger::LogInstallerHashMismatch( - std::string_view id, - std::string_view version, - std::string_view channel, - const std::vector& expected, - const std::vector& actual, - bool overrideHashMismatch, - uint64_t downloadSizeInBytes, - const std::optional& contentType) const noexcept - { - std::string actualContentType = contentType.value_or(std::string{}); - - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "HashMismatch", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(version, "Version"), - AICLI_TraceLoggingStringView(channel, "Channel"), - TraceLoggingBinary(expected.data(), static_cast(expected.size()), "Expected"), - TraceLoggingBinary(actual.data(), static_cast(actual.size()), "Actual"), - TraceLoggingBool(overrideHashMismatch, "Override"), - TraceLoggingUInt64(downloadSizeInBytes, "ActualSize"), - AICLI_TraceLoggingStringView(actualContentType, "ContentType"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageVersion = version; - m_summary.Channel = channel; - m_summary.HashMismatchExpected = expected; - m_summary.HashMismatchActual = actual; - m_summary.HashMismatchOverride = overrideHashMismatch; - m_summary.HashMismatchActualSize = downloadSizeInBytes; - m_summary.HashMismatchContentType = actualContentType; - } - } - - AICLI_LOG(CLI, Error, - << "Package hash verification failed. SHA256 in manifest [" - << Utility::SHA256::ConvertToString(expected) - << "] does not match download [" - << Utility::SHA256::ConvertToString(actual) - << "] with file size [" << downloadSizeInBytes << "] and content type [" << actualContentType << "]"); - } - - void TelemetryTraceLogger::LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "InstallerFailure", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(version, "Version"), - AICLI_TraceLoggingStringView(channel, "Channel"), - AICLI_TraceLoggingStringView(type, "Type"), - TraceLoggingUInt32(errorCode, "ErrorCode"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageVersion = version; - m_summary.Channel = channel; - m_summary.InstallerExecutionType = type; - m_summary.InstallerErrorCode = errorCode; - } - } - - AICLI_LOG(CLI, Error, << type << " installer failed: " << errorCode); - } - - void TelemetryTraceLogger::LogUninstallerFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "UninstallerFailure", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(version, "Version"), - AICLI_TraceLoggingStringView(type, "Type"), - TraceLoggingUInt32(errorCode, "ErrorCode"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageVersion = version; - m_summary.UninstallerExecutionType = type; - m_summary.UninstallerErrorCode = errorCode; - } - } - - AICLI_LOG(CLI, Error, << type << " uninstaller failed: " << errorCode); - } - - void TelemetryTraceLogger::LogSuccessfulInstallARPChange( - std::string_view sourceIdentifier, - std::string_view packageIdentifier, - std::string_view packageVersion, - std::string_view packageChannel, - size_t changesToARP, - size_t matchesInARP, - size_t countOfIntersectionOfChangesAndMatches, - std::string_view arpName, - std::string_view arpVersion, - std::string_view arpPublisher, - std::string_view arpLanguage) const noexcept - { - if (IsTelemetryEnabled()) - { - size_t languageNumber = 0xFFFF; - - try - { - std::istringstream languageConversion{ std::string{ arpLanguage } }; - languageConversion >> languageNumber; - } - catch (...) {} - - AICLI_TraceLoggingWriteActivity( - "InstallARPChange", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(sourceIdentifier, "SourceIdentifier"), - AICLI_TraceLoggingStringView(packageIdentifier, "PackageIdentifier"), - AICLI_TraceLoggingStringView(packageVersion, "PackageVersion"), - AICLI_TraceLoggingStringView(packageChannel, "PackageChannel"), - TraceLoggingUInt64(static_cast(changesToARP), "ChangesToARP"), - TraceLoggingUInt64(static_cast(matchesInARP), "MatchesInARP"), - TraceLoggingUInt64(static_cast(countOfIntersectionOfChangesAndMatches), "ChangesThatMatch"), - AICLI_TraceLoggingStringView(arpName, "ARPName"), - AICLI_TraceLoggingStringView(arpVersion, "ARPVersion"), - AICLI_TraceLoggingStringView(arpPublisher, "ARPPublisher"), - TraceLoggingUInt64(static_cast(languageNumber), "ARPLanguage"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - if (m_useSummary) - { - m_summary.SourceIdentifier = sourceIdentifier; - m_summary.PackageIdentifier = packageIdentifier; - m_summary.PackageVersion = packageVersion; - m_summary.Channel = packageChannel; - m_summary.ChangesToARP = static_cast(changesToARP); - m_summary.MatchesInARP = static_cast(matchesInARP); - m_summary.ChangesThatMatch = static_cast(countOfIntersectionOfChangesAndMatches); - m_summary.ARPName = arpName; - m_summary.ARPVersion = arpVersion; - m_summary.ARPPublisher = arpPublisher; - m_summary.ARPLanguage = static_cast(languageNumber); - } - } - - AICLI_LOG(CLI, Info, << "During package install, " << changesToARP << " changes to ARP were observed, " - << matchesInARP << " matches were found for the package, and " << countOfIntersectionOfChangesAndMatches << " packages were in both"); - - if (arpName.empty()) - { - AICLI_LOG(CLI, Info, << "No single entry was determined to be associated with the package"); - } - else - { - AICLI_LOG(CLI, Info, << "The entry determined to be associated with the package is '" << arpName << "', with publisher '" << arpPublisher << "'"); - } - } - - void TelemetryTraceLogger::LogNonFatalDOError(std::string_view url, HRESULT hr) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "NonFatalDOError", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(url, "Url"), - TraceLoggingHResult(hr, "HResult"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); - - if (m_useSummary) - { - m_summary.DOUrl = url; - m_summary.DOHResult = hr; - } - } - } - - void TelemetryTraceLogger::LogPreindexedPackageUpdate( - std::string_view sourceId, - std::optional previousIndexPublishedAt, - std::chrono::system_clock::time_point newIndexPublishedAt, - bool usedDeltaDownload, - std::optional previousBaselinePublishedAt, - std::optional newBaselinePublishedAt, - bool baselineUpdated, - uint64_t downloadedBytes, - bool isManualUpdate) const noexcept - { - // Converts a system_clock time_point to FILETIME. An empty optional maps to FILETIME{0,0} (null sentinel). - auto toFileTime = [](const std::optional& tp) -> FILETIME - { - return tp ? Utility::ConvertSystemClockToFileTime(*tp) : FILETIME{}; - }; - - bool isNewClient = !previousIndexPublishedAt.has_value(); - - double indexStalenessDays = -1.0; - if (previousIndexPublishedAt) - { - indexStalenessDays = std::chrono::duration>( - newIndexPublishedAt - *previousIndexPublishedAt).count(); - } - - double baselineStalenessDays = -1.0; - if (previousBaselinePublishedAt && newBaselinePublishedAt) - { - baselineStalenessDays = std::chrono::duration>( - *newBaselinePublishedAt - *previousBaselinePublishedAt).count(); - } - - FILETIME previousIndexFt = toFileTime(previousIndexPublishedAt); - FILETIME newIndexFt = toFileTime(newIndexPublishedAt); - FILETIME previousBaselineFt = toFileTime(previousBaselinePublishedAt); - FILETIME newBaselineFt = toFileTime(newBaselinePublishedAt); - - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "PreindexedPackageUpdate", - AICLI_TraceLoggingStringView(sourceId, "SourceId"), - TraceLoggingFileTime(previousIndexFt, "PreviousIndexPublishedAt"), - TraceLoggingFileTime(newIndexFt, "NewIndexPublishedAt"), - TraceLoggingFloat64(indexStalenessDays, "IndexStalenessDays"), - TraceLoggingBool(isNewClient, "IsNewClient"), - TraceLoggingBool(usedDeltaDownload, "UsedDeltaDownload"), - TraceLoggingFileTime(previousBaselineFt, "PreviousBaselinePublishedAt"), - TraceLoggingFileTime(newBaselineFt, "NewBaselinePublishedAt"), - TraceLoggingFloat64(baselineStalenessDays, "BaselineStalenessDays"), - TraceLoggingBool(baselineUpdated, "BaselineUpdated"), - TraceLoggingUInt64(downloadedBytes, "DownloadedBytes"), - TraceLoggingBool(isManualUpdate, "IsManualUpdate"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); - } - - AICLI_LOG(Repo, Verbose, << "PreindexedPackageUpdate: Source [" << sourceId - << "] IsNewClient [" << isNewClient - << "] IndexStalenessDays [" << indexStalenessDays - << "] UsedDeltaDownload [" << usedDeltaDownload - << "] BaselineStalenessDays [" << baselineStalenessDays - << "] BaselineUpdated [" << baselineUpdated - << "] DownloadedBytes [" << downloadedBytes - << "] IsManualUpdate [" << isManualUpdate << "]"); - } - - void TelemetryTraceLogger::LogRepairFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "RepairFailure", - TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), - AICLI_TraceLoggingStringView(id, "Id"), - AICLI_TraceLoggingStringView(version, "Version"), - AICLI_TraceLoggingStringView(type, "Type"), - TraceLoggingUInt32(errorCode, "ErrorCode"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); - - if (m_useSummary) - { - m_summary.PackageIdentifier = id; - m_summary.PackageVersion = version; - m_summary.RepairExecutionType = type; - m_summary.RepairErrorCode = errorCode; - - } - } - - AICLI_LOG(CLI, Error, << type << " repair failed: " << errorCode); - } - - TelemetryTraceLogger::~TelemetryTraceLogger() - { - if (IsTelemetryEnabled()) - { - LocIndString version = Runtime::GetClientVersion(); - LocIndString packageVersion; - if (Runtime::IsRunningInPackagedContext()) - { - packageVersion = Runtime::GetPackageVersion(); - } - - if (m_useSummary) - { - TraceLoggingWriteActivity( - g_hTraceProvider, - "SummaryV2", - GetActivityId(), - GetParentActivityId(), - // From member fields or program info. - AICLI_TraceLoggingStringView(m_caller, "Caller"), - TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"), - TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "ClientVersion"), - TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "ClientPackageVersion"), - TraceLoggingBool(Runtime::IsReleaseBuild(), "IsReleaseBuild"), - TraceLoggingUInt32(m_executionStage, "ExecutionStage"), - // From TelemetrySummary - TraceLoggingHResult(m_summary.FailureHResult, "FailureHResult"), - AICLI_TraceLoggingWStringView(m_summary.FailureMessage, "FailureMessage"), - AICLI_TraceLoggingStringView(m_summary.FailureModule, "FailureModule"), - TraceLoggingUInt32(m_summary.FailureThreadId, "FailureThreadId"), - TraceLoggingUInt32(static_cast(m_summary.FailureType), "FailureType"), - AICLI_TraceLoggingStringView(m_summary.FailureFile, "FailureFile"), - TraceLoggingUInt32(m_summary.FailureLine, "FailureLine"), - TraceLoggingBool(m_summary.IsCOMCall, "IsCOMCall"), - TraceLoggingUInt32(static_cast(m_summary.ExecutionLevel), "ExecutionLevel"), - AICLI_TraceLoggingStringView(m_summary.Command, "Command"), - TraceLoggingBool(m_summary.CommandSuccess, "CommandSuccess"), - TraceLoggingBool(m_summary.IsManifestLocal, "IsManifestLocal"), - AICLI_TraceLoggingStringView(m_summary.PackageIdentifier, "PackageIdentifier"), - AICLI_TraceLoggingStringView(m_summary.PackageName, "PackageName"), - AICLI_TraceLoggingStringView(m_summary.PackageVersion, "PackageVersion"), - AICLI_TraceLoggingStringView(m_summary.Channel, "Channel"), - AICLI_TraceLoggingStringView(m_summary.SourceIdentifier, "SourceIdentifier"), - TraceLoggingInt32(m_summary.InstallerArchitecture, "InstallerArchitecture"), - AICLI_TraceLoggingStringView(m_summary.InstallerUrl, "InstallerUrl"), - AICLI_TraceLoggingStringView(m_summary.InstallerType, "InstallerType"), - AICLI_TraceLoggingStringView(m_summary.InstallerScope, "InstallerScope"), - AICLI_TraceLoggingStringView(m_summary.InstallerLocale, "InstallerLocale"), - AICLI_TraceLoggingStringView(m_summary.SearchType, "SearchType"), - AICLI_TraceLoggingStringView(m_summary.SearchQuery, "SearchQuery"), - AICLI_TraceLoggingStringView(m_summary.SearchId, "SearchId"), - AICLI_TraceLoggingStringView(m_summary.SearchName, "SearchName"), - AICLI_TraceLoggingStringView(m_summary.SearchMoniker, "SearchMoniker"), - AICLI_TraceLoggingStringView(m_summary.SearchTag, "SearchTag"), - AICLI_TraceLoggingStringView(m_summary.SearchCommand, "SearchCommand"), - TraceLoggingUInt64(m_summary.SearchMaximum, "SearchMaximum"), - AICLI_TraceLoggingStringView(m_summary.SearchRequest, "SearchRequest"), - TraceLoggingUInt64(m_summary.SearchResultCount, "SearchResultCount"), - TraceLoggingBinary(m_summary.HashMismatchExpected.data(), static_cast(m_summary.HashMismatchExpected.size()), "HashMismatchExpected"), - TraceLoggingBinary(m_summary.HashMismatchActual.data(), static_cast(m_summary.HashMismatchActual.size()), "HashMismatchActual"), - TraceLoggingBool(m_summary.HashMismatchOverride, "HashMismatchOverride"), - TraceLoggingUInt64(m_summary.HashMismatchActualSize, "HashMismatchActualSize"), - AICLI_TraceLoggingStringView(m_summary.HashMismatchContentType, "HashMismatchContentType"), - AICLI_TraceLoggingStringView(m_summary.InstallerExecutionType, "InstallerExecutionType"), - TraceLoggingUInt32(m_summary.InstallerErrorCode, "InstallerErrorCode"), - AICLI_TraceLoggingStringView(m_summary.UninstallerExecutionType, "UninstallerExecutionType"), - TraceLoggingUInt32(m_summary.UninstallerErrorCode, "UninstallerErrorCode"), - TraceLoggingUInt64(m_summary.ChangesToARP, "ChangesToARP"), - TraceLoggingUInt64(m_summary.MatchesInARP, "MatchesInARP"), - TraceLoggingUInt64(m_summary.ChangesThatMatch, "ChangesThatMatch"), - TraceLoggingUInt64(m_summary.ARPLanguage, "ARPLanguage"), - AICLI_TraceLoggingStringView(m_summary.ARPName, "ARPName"), - AICLI_TraceLoggingStringView(m_summary.ARPVersion, "ARPVersion"), - AICLI_TraceLoggingStringView(m_summary.ARPPublisher, "ARPPublisher"), - AICLI_TraceLoggingStringView(m_summary.DOUrl, "DOUrl"), - TraceLoggingHResult(m_summary.DOHResult, "DOHResult"), - AICLI_TraceLoggingStringView(m_summary.RepairExecutionType, "RepairExecutionType"), - TraceLoggingUInt32(m_summary.RepairErrorCode, "RepairErrorCode"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); - } - } - } - - bool TelemetryTraceLogger::IsTelemetryEnabled() const noexcept - { - return g_IsTelemetryProviderEnabled && m_isInitialized && m_isSettingEnabled && m_isRuntimeEnabled; - } - - void TelemetryTraceLogger::InitializeInternal(const AppInstaller::Settings::UserSettings& userSettings) - { - m_isSettingEnabled = !userSettings.Get(); - m_isInitialized = true; - } - - std::wstring TelemetryTraceLogger::AnonymizeString(const wchar_t* input) const noexcept - { - return input ? AnonymizeString(std::wstring_view{ input }) : std::wstring{}; - } - - std::wstring TelemetryTraceLogger::AnonymizeString(std::wstring_view input) const noexcept try - { - // GetPathTo() may need to read the settings, so this function should only be called after settings are initialized. - // To ensure that, this function is only called when emitting an event, and we disable the telemetry until settings are ready. - static const std::wstring s_UserProfile = Runtime::GetPathTo(Runtime::PathName::UserProfile).wstring(); - - return Utility::ReplaceWhileCopying(input, s_UserProfile, s_UserProfileReplacement); - } - catch (...) { return std::wstring{ input }; } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static std::shared_ptr s_TelemetryTraceLogger_TestOverride; -#endif - - TelemetryTraceLogger& Telemetry() - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_TelemetryTraceLogger_TestOverride) - { - return *s_TelemetryTraceLogger_TestOverride.get(); - } -#endif - ThreadLocalStorage::ThreadGlobals* pThreadGlobals = ThreadLocalStorage::ThreadGlobals::GetForCurrentThread(); - if (pThreadGlobals) - { - return *reinterpret_cast(pThreadGlobals->GetTelemetryObject()); - } - else - { - // For the global telemetry object, we may not have yet read the settings file. - // In that case, we will not be able to initialize it, so we need to try it - // each time we get the object. - static TelemetryTraceLogger processGlobalTelemetry(/* useSummary */ false); - processGlobalTelemetry.TryInitialize(); - return processGlobalTelemetry; - } - } - - void EnableWilFailureTelemetry() - { - wil::SetResultLoggingCallback(wilResultLoggingCallback); - } - - void UseGlobalTelemetryLoggerActivityIdOnly() - { - s_useGlobalTelemetryActivityId = true; - std::ignore = CoCreateGuid(&s_globalTelemetryLoggerActivityId); - } - - DisableTelemetryScope::DisableTelemetryScope() - { - m_token = Telemetry().DisableRuntime(); - } - - DisableTelemetryScope::~DisableTelemetryScope() - { - if (m_token) - { - Telemetry().EnableRuntime(); - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Replace this test hook with context telemetry when it gets moved over - void TestHook_SetTelemetryOverride(std::shared_ptr ttl) - { - s_TelemetryTraceLogger_TestOverride = std::move(ttl); - } -#endif -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerTelemetry.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerSHA256.h" +#include "Public/AppInstallerStrings.h" +#include "Public/winget/ThreadGlobals.h" +#include "winget/Filesystem.h" +#include "winget/UserSettings.h" +#include + +#define AICLI_TraceLoggingStringView(_sv_,_name_) TraceLoggingCountedUtf8String(_sv_.data(), static_cast(_sv_.size()), _name_) +#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast(_sv_.size()), _name_) + +#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ +g_hTraceProvider,\ +_eventName_,\ +s_useGlobalTelemetryActivityId ? &s_globalTelemetryLoggerActivityId : GetActivityId(),\ +s_useGlobalTelemetryActivityId ? nullptr : GetParentActivityId(),\ +TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast(m_caller.size()), "Caller"),\ +TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"),\ +__VA_ARGS__) + +namespace AppInstaller::Logging +{ + using namespace Utility; + + namespace + { + // TODO: This and all usages should be removed after transition to summary event in back end. + static const uint32_t s_RootExecutionId = 0; + static std::atomic_uint32_t s_subExecutionId{ s_RootExecutionId }; + + // Data that is needed by AnonymizeString + constexpr std::wstring_view s_UserProfileReplacement = L"%USERPROFILE%"sv; + + // TODO: Temporary code to keep existing telemetry behavior + static bool s_useGlobalTelemetryActivityId = false; + static GUID s_globalTelemetryLoggerActivityId = GUID_NULL; + + void __stdcall wilResultLoggingCallback(const wil::FailureInfo& info) noexcept + { + Telemetry().LogFailure(info); + } + + FailureTypeEnum ConvertWilFailureTypeToFailureType(wil::FailureType failureType) + { + switch (failureType) + { + case wil::FailureType::Exception: + return FailureTypeEnum::ResultException; + case wil::FailureType::Return: + return FailureTypeEnum::ResultReturn; + case wil::FailureType::Log: + return FailureTypeEnum::ResultLog; + case wil::FailureType::FailFast: + return FailureTypeEnum::ResultFailFast; + default: + return FailureTypeEnum::Unknown; + } + } + + std::string_view LogExceptionTypeToString(FailureTypeEnum exceptionType) + { + switch (exceptionType) + { + case FailureTypeEnum::ResultException: + return "wil::ResultException"sv; + case FailureTypeEnum::WinrtHResultError: + return "winrt::hresult_error"sv; + case FailureTypeEnum::ResourceOpen: + return "ResourceOpenException"sv; + case FailureTypeEnum::StdException: + return "std::exception"sv; + case FailureTypeEnum::Unknown: + default: + return "unknown"sv; + } + } + } + + TelemetrySummary::TelemetrySummary(const TelemetrySummary& other) + { + this->IsCOMCall = other.IsCOMCall; + } + + TelemetryTraceLogger::TelemetryTraceLogger(bool useSummary) : m_useSummary(useSummary) + { + std::ignore = CoCreateGuid(&m_activityId); + m_subExecutionId = s_RootExecutionId; + } + + const GUID* TelemetryTraceLogger::GetActivityId() const + { + return &m_activityId; + } + + const GUID* TelemetryTraceLogger::GetParentActivityId() const + { + return &m_parentActivityId; + } + + bool TelemetryTraceLogger::DisableRuntime() + { + return m_isRuntimeEnabled.exchange(false); + } + + void TelemetryTraceLogger::EnableRuntime() + { + m_isRuntimeEnabled = true; + } + + void TelemetryTraceLogger::Initialize() + { + if (!m_isInitialized) + { + InitializeInternal(Settings::User()); + } + } + + bool TelemetryTraceLogger::TryInitialize() + { + if (!m_isInitialized) + { + // Only initialize if we already have the user settings, so that we can respect the telemetry setting. + // We may not yet have the user settings if we are trying to report an error while reading them. + auto userSettings = Settings::TryGetUser(); + if (userSettings) + { + InitializeInternal(*userSettings); + } + } + + return m_isInitialized; + } + + void TelemetryTraceLogger::SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept + { + // Check if passed in string is a valid Json formatted before returning the value + // If invalid, return empty Json + Json::CharReaderBuilder jsonBuilder; + std::unique_ptr jsonReader(jsonBuilder.newCharReader()); + std::unique_ptr pJsonValue = std::make_unique(); + std::string errors; + std::wstring jsonStrW{ jsonStr_view }; + std::string jsonStr = ConvertToUTF8(jsonStrW.c_str()); + + bool result = jsonReader->parse(jsonStr.c_str(), + jsonStr.c_str() + jsonStr.size(), + pJsonValue.get(), + &errors); + + if (result) + { + m_telemetryCorrelationJsonW = jsonStrW; + AICLI_LOG(Core, Info, << "Passed in Correlation Vector Json is valid: " << jsonStr); + } + else + { + AICLI_LOG(Core, Error, << "Passed in Correlation Vector Json is invalid: " << jsonStr << "; Error: " << errors); + } + } + + void TelemetryTraceLogger::SetCaller(const std::string& caller) + { + auto callerUTF16 = Utility::ConvertToUTF16(caller); + auto anonCaller = AnonymizeString(callerUTF16); + m_caller = Utility::ConvertToUTF8(anonCaller); + } + + void TelemetryTraceLogger::SetExecutionStage(uint32_t stage) noexcept + { + m_executionStage = stage; + } + + std::unique_ptr TelemetryTraceLogger::CreateSubTraceLogger() const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !this->m_isInitialized); + + auto subTraceLogger = std::make_unique(*this); + + std::ignore = CoCreateGuid(&subTraceLogger->m_activityId); + subTraceLogger->m_parentActivityId = this->m_activityId; + subTraceLogger->m_subExecutionId = s_subExecutionId++; + + return subTraceLogger; + } + + void TelemetryTraceLogger::LogFailure(const wil::FailureInfo& failure) const noexcept + { + if (IsTelemetryEnabled()) + { + auto anonMessage = AnonymizeString(failure.pszMessage); + + AICLI_TraceLoggingWriteActivity( + "FailureInfo", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingHResult(failure.hr, "HResult"), + AICLI_TraceLoggingWStringView(anonMessage, "Message"), + TraceLoggingString(failure.pszModule, "Module"), + TraceLoggingUInt32(failure.threadId, "ThreadId"), + TraceLoggingUInt32(static_cast(failure.type), "Type"), + TraceLoggingString(failure.pszFile, "File"), + TraceLoggingUInt32(failure.uLineNumber, "Line"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.FailureHResult = failure.hr; + m_summary.FailureMessage = anonMessage; + m_summary.FailureModule = StringOrEmptyIfNull(failure.pszModule); + m_summary.FailureThreadId = failure.threadId; + m_summary.FailureType = ConvertWilFailureTypeToFailureType(failure.type); + m_summary.FailureFile = StringOrEmptyIfNull(failure.pszFile); + m_summary.FailureLine = failure.uLineNumber; + } + } + + // Also send failure to the log + AICLI_LOG(Fail, Error, << [&]() { + wchar_t message[2048]; + GetFailureLogString(message, ARRAYSIZE(message), failure); + return Utility::ConvertToUTF8(message); + }()); + } + + void TelemetryTraceLogger::LogStartup(bool isCOMCall) const noexcept + { + LocIndString version = Runtime::GetClientVersion(); + LocIndString packageVersion; + if (Runtime::IsRunningInPackagedContext()) + { + packageVersion = Runtime::GetPackageVersion(); + } + + ExecutionLevel executionLevel = ExecutionLevel::User; + if (Runtime::IsRunningAsSystem()) + { + executionLevel = ExecutionLevel::System; + } + else if (Runtime::IsRunningAsAdmin()) + { + executionLevel = ExecutionLevel::Admin; + } + + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "ClientVersion", + TraceLoggingBool(isCOMCall, "IsCOMCall"), + TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "Version"), + TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "PackageVersion"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.IsCOMCall = isCOMCall; + m_summary.ExecutionLevel = executionLevel; + } + } + + AICLI_LOG(Core, Info, << "WinGet, version [" << version << "], activity [" << *GetActivityId() << ']'); + AICLI_LOG(Core, Info, << "Process: " << Filesystem::GetExecutablePathForProcess(GetCurrentProcess()).filename() << "[" << GetCurrentProcessId() << "], Level[" << executionLevel << "], Offset: " << &__ImageBase); + AICLI_LOG(Core, Info, << "OS: " << Runtime::GetOSVersion()); + AICLI_LOG(Core, Info, << "Command line Args: " << Utility::ConvertToUTF8(GetCommandLineW())); + if (Runtime::IsRunningInPackagedContext()) + { + AICLI_LOG(Core, Info, << "Package: " << packageVersion); + } + AICLI_LOG(Core, Info, << "IsCOMCall:" << isCOMCall << "; Caller: " << m_caller); + + Log().SetTag(Tag::HeadersComplete); + } + + void TelemetryTraceLogger::LogCommand(std::string_view commandName) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "CommandFound", + AICLI_TraceLoggingStringView(commandName, "Command"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.Command = commandName; + } + } + + AICLI_LOG(CLI, Info, << "Leaf command to execute: " << commandName); + } + + void TelemetryTraceLogger::LogCommandSuccess(std::string_view commandName) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "CommandSuccess", + AICLI_TraceLoggingStringView(commandName, "Command"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.CommandSuccess = true; + } + } + + AICLI_LOG(CLI, Info, << "Leaf command succeeded: " << commandName); + } + + void TelemetryTraceLogger::LogCommandTermination(HRESULT hr, std::string_view file, size_t line) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "CommandTermination", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingHResult(hr, "HResult"), + AICLI_TraceLoggingStringView(file, "File"), + TraceLoggingUInt64(static_cast(line), "Line"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.FailureHResult = hr; + m_summary.FailureType = FailureTypeEnum::CommandTermination; + m_summary.FailureFile = file; + m_summary.FailureLine = static_cast(line); + } + } + + AICLI_LOG(CLI, Error, << "Terminating context: 0x" << SetHRFormat << hr << " at " << file << ":" << line); + } + + void TelemetryTraceLogger::LogException(FailureTypeEnum type, std::string_view message) const noexcept + { + auto exceptionTypeString = LogExceptionTypeToString(type); + + if (IsTelemetryEnabled()) + { + auto anonMessage = AnonymizeString(Utility::ConvertToUTF16(message)); + + AICLI_TraceLoggingWriteActivity( + "Exception", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(exceptionTypeString, "Type"), + AICLI_TraceLoggingWStringView(anonMessage, "Message"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.FailureType = type; + m_summary.FailureMessage = anonMessage; + } + } + + AICLI_LOG(CLI, Error, << "Caught " << exceptionTypeString << ": " << message); + } + + void TelemetryTraceLogger::LogIsManifestLocal(bool isLocalManifest) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "GetManifest", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingBool(isLocalManifest, "IsManifestLocal"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.IsManifestLocal = isLocalManifest; + } + } + } + + void TelemetryTraceLogger::LogManifestFields(std::string_view id, std::string_view name, std::string_view version) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "ManifestFields", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(name, "Name"), + AICLI_TraceLoggingStringView(version, "Version"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageName = name; + m_summary.PackageVersion = version; + } + } + + AICLI_LOG(CLI, Info, << "Manifest fields: Name [" << name << "], Version [" << version << ']'); + } + + void TelemetryTraceLogger::LogNoAppMatch() const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "NoAppMatch", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + } + + AICLI_LOG(CLI, Info, << "No app found matching input criteria"); + } + + void TelemetryTraceLogger::LogMultiAppMatch() const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "MultiAppMatch", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + } + + AICLI_LOG(CLI, Info, << "Multiple apps found matching input criteria"); + } + + void TelemetryTraceLogger::LogAppFound(std::string_view name, std::string_view id) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "AppFound", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(name, "Name"), + AICLI_TraceLoggingStringView(id, "Id"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageName = name; + } + } + + AICLI_LOG(CLI, Info, << "Found one app. App id: " << id << " App name: " << name); + } + + void TelemetryTraceLogger::LogSelectedInstaller(int arch, std::string_view url, std::string_view installerType, std::string_view scope, std::string_view language) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "SelectedInstaller", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingInt32(arch, "Arch"), + AICLI_TraceLoggingStringView(url, "Url"), + AICLI_TraceLoggingStringView(installerType, "InstallerType"), + AICLI_TraceLoggingStringView(scope, "Scope"), + AICLI_TraceLoggingStringView(language, "Language"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.InstallerArchitecture = arch; + m_summary.InstallerUrl = url; + m_summary.InstallerType = installerType; + m_summary.InstallerScope = scope; + m_summary.InstallerLocale = language; + } + } + + AICLI_LOG(CLI, Verbose, << "Completed installer selection."); + AICLI_LOG(CLI, Verbose, << "Selected installer Architecture: " << arch); + AICLI_LOG(CLI, Verbose, << "Selected installer URL: " << url); + AICLI_LOG(CLI, Verbose, << "Selected installer InstallerType: " << installerType); + AICLI_LOG(CLI, Verbose, << "Selected installer Scope: " << scope); + AICLI_LOG(CLI, Verbose, << "Selected installer Language: " << language); + } + + void TelemetryTraceLogger::LogSearchRequest( + std::string_view type, + std::string_view query, + std::string_view id, + std::string_view name, + std::string_view moniker, + std::string_view tag, + std::string_view command, + size_t maximum, + std::string_view request) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "SearchRequest", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(type, "Type"), + AICLI_TraceLoggingStringView(query, "Query"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(name, "Name"), + AICLI_TraceLoggingStringView(moniker, "Moniker"), + AICLI_TraceLoggingStringView(tag, "Tag"), + AICLI_TraceLoggingStringView(command, "Command"), + TraceLoggingUInt64(static_cast(maximum), "Maximum"), + AICLI_TraceLoggingStringView(request, "Request"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.SearchType = type; + m_summary.SearchQuery = query; + m_summary.SearchId = id; + m_summary.SearchName = name; + m_summary.SearchMoniker = moniker; + m_summary.SearchTag = tag; + m_summary.SearchCommand = command; + m_summary.SearchMaximum = static_cast(maximum); + m_summary.SearchRequest = request; + } + } + } + + void TelemetryTraceLogger::LogSearchResultCount(uint64_t resultCount) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "SearchResultCount", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + TraceLoggingUInt64(resultCount, "ResultCount"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.SearchResultCount = resultCount; + } + } + + AICLI_LOG(CLI, Verbose, << "Search result size: " << resultCount); + } + + void TelemetryTraceLogger::LogInstallerHashMismatch( + std::string_view id, + std::string_view version, + std::string_view channel, + const std::vector& expected, + const std::vector& actual, + bool overrideHashMismatch, + uint64_t downloadSizeInBytes, + const std::optional& contentType) const noexcept + { + std::string actualContentType = contentType.value_or(std::string{}); + + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "HashMismatch", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(channel, "Channel"), + TraceLoggingBinary(expected.data(), static_cast(expected.size()), "Expected"), + TraceLoggingBinary(actual.data(), static_cast(actual.size()), "Actual"), + TraceLoggingBool(overrideHashMismatch, "Override"), + TraceLoggingUInt64(downloadSizeInBytes, "ActualSize"), + AICLI_TraceLoggingStringView(actualContentType, "ContentType"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.Channel = channel; + m_summary.HashMismatchExpected = expected; + m_summary.HashMismatchActual = actual; + m_summary.HashMismatchOverride = overrideHashMismatch; + m_summary.HashMismatchActualSize = downloadSizeInBytes; + m_summary.HashMismatchContentType = actualContentType; + } + } + + AICLI_LOG(CLI, Error, + << "Package hash verification failed. SHA256 in manifest [" + << Utility::SHA256::ConvertToString(expected) + << "] does not match download [" + << Utility::SHA256::ConvertToString(actual) + << "] with file size [" << downloadSizeInBytes << "] and content type [" << actualContentType << "]"); + } + + void TelemetryTraceLogger::LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "InstallerFailure", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(channel, "Channel"), + AICLI_TraceLoggingStringView(type, "Type"), + TraceLoggingUInt32(errorCode, "ErrorCode"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.Channel = channel; + m_summary.InstallerExecutionType = type; + m_summary.InstallerErrorCode = errorCode; + } + } + + AICLI_LOG(CLI, Error, << type << " installer failed: " << errorCode); + } + + void TelemetryTraceLogger::LogUninstallerFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "UninstallerFailure", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(type, "Type"), + TraceLoggingUInt32(errorCode, "ErrorCode"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.UninstallerExecutionType = type; + m_summary.UninstallerErrorCode = errorCode; + } + } + + AICLI_LOG(CLI, Error, << type << " uninstaller failed: " << errorCode); + } + + void TelemetryTraceLogger::LogSuccessfulInstallARPChange( + std::string_view sourceIdentifier, + std::string_view packageIdentifier, + std::string_view packageVersion, + std::string_view packageChannel, + size_t changesToARP, + size_t matchesInARP, + size_t countOfIntersectionOfChangesAndMatches, + std::string_view arpName, + std::string_view arpVersion, + std::string_view arpPublisher, + std::string_view arpLanguage) const noexcept + { + if (IsTelemetryEnabled()) + { + size_t languageNumber = 0xFFFF; + + try + { + std::istringstream languageConversion{ std::string{ arpLanguage } }; + languageConversion >> languageNumber; + } + catch (...) {} + + AICLI_TraceLoggingWriteActivity( + "InstallARPChange", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(sourceIdentifier, "SourceIdentifier"), + AICLI_TraceLoggingStringView(packageIdentifier, "PackageIdentifier"), + AICLI_TraceLoggingStringView(packageVersion, "PackageVersion"), + AICLI_TraceLoggingStringView(packageChannel, "PackageChannel"), + TraceLoggingUInt64(static_cast(changesToARP), "ChangesToARP"), + TraceLoggingUInt64(static_cast(matchesInARP), "MatchesInARP"), + TraceLoggingUInt64(static_cast(countOfIntersectionOfChangesAndMatches), "ChangesThatMatch"), + AICLI_TraceLoggingStringView(arpName, "ARPName"), + AICLI_TraceLoggingStringView(arpVersion, "ARPVersion"), + AICLI_TraceLoggingStringView(arpPublisher, "ARPPublisher"), + TraceLoggingUInt64(static_cast(languageNumber), "ARPLanguage"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + if (m_useSummary) + { + m_summary.SourceIdentifier = sourceIdentifier; + m_summary.PackageIdentifier = packageIdentifier; + m_summary.PackageVersion = packageVersion; + m_summary.Channel = packageChannel; + m_summary.ChangesToARP = static_cast(changesToARP); + m_summary.MatchesInARP = static_cast(matchesInARP); + m_summary.ChangesThatMatch = static_cast(countOfIntersectionOfChangesAndMatches); + m_summary.ARPName = arpName; + m_summary.ARPVersion = arpVersion; + m_summary.ARPPublisher = arpPublisher; + m_summary.ARPLanguage = static_cast(languageNumber); + } + } + + AICLI_LOG(CLI, Info, << "During package install, " << changesToARP << " changes to ARP were observed, " + << matchesInARP << " matches were found for the package, and " << countOfIntersectionOfChangesAndMatches << " packages were in both"); + + if (arpName.empty()) + { + AICLI_LOG(CLI, Info, << "No single entry was determined to be associated with the package"); + } + else + { + AICLI_LOG(CLI, Info, << "The entry determined to be associated with the package is '" << arpName << "', with publisher '" << arpPublisher << "'"); + } + } + + void TelemetryTraceLogger::LogNonFatalDOError(std::string_view url, HRESULT hr) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "NonFatalDOError", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(url, "Url"), + TraceLoggingHResult(hr, "HResult"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); + + if (m_useSummary) + { + m_summary.DOUrl = url; + m_summary.DOHResult = hr; + } + } + } + + void TelemetryTraceLogger::LogPreindexedPackageUpdate( + std::string_view sourceId, + std::optional previousIndexPublishedAt, + std::chrono::system_clock::time_point newIndexPublishedAt, + bool usedDeltaDownload, + std::optional previousBaselinePublishedAt, + std::optional newBaselinePublishedAt, + bool baselineUpdated, + uint64_t downloadedBytes, + bool isManualUpdate) const noexcept + { + // Converts a system_clock time_point to FILETIME. An empty optional maps to FILETIME{0,0} (null sentinel). + auto toFileTime = [](const std::optional& tp) -> FILETIME + { + return tp ? Utility::ConvertSystemClockToFileTime(*tp) : FILETIME{}; + }; + + bool isNewClient = !previousIndexPublishedAt.has_value(); + + double indexStalenessDays = -1.0; + if (previousIndexPublishedAt) + { + indexStalenessDays = std::chrono::duration>( + newIndexPublishedAt - *previousIndexPublishedAt).count(); + } + + double baselineStalenessDays = -1.0; + if (previousBaselinePublishedAt && newBaselinePublishedAt) + { + baselineStalenessDays = std::chrono::duration>( + *newBaselinePublishedAt - *previousBaselinePublishedAt).count(); + } + + FILETIME previousIndexFt = toFileTime(previousIndexPublishedAt); + FILETIME newIndexFt = toFileTime(newIndexPublishedAt); + FILETIME previousBaselineFt = toFileTime(previousBaselinePublishedAt); + FILETIME newBaselineFt = toFileTime(newBaselinePublishedAt); + + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "PreindexedPackageUpdate", + AICLI_TraceLoggingStringView(sourceId, "SourceId"), + TraceLoggingFileTime(previousIndexFt, "PreviousIndexPublishedAt"), + TraceLoggingFileTime(newIndexFt, "NewIndexPublishedAt"), + TraceLoggingFloat64(indexStalenessDays, "IndexStalenessDays"), + TraceLoggingBool(isNewClient, "IsNewClient"), + TraceLoggingBool(usedDeltaDownload, "UsedDeltaDownload"), + TraceLoggingFileTime(previousBaselineFt, "PreviousBaselinePublishedAt"), + TraceLoggingFileTime(newBaselineFt, "NewBaselinePublishedAt"), + TraceLoggingFloat64(baselineStalenessDays, "BaselineStalenessDays"), + TraceLoggingBool(baselineUpdated, "BaselineUpdated"), + TraceLoggingUInt64(downloadedBytes, "DownloadedBytes"), + TraceLoggingBool(isManualUpdate, "IsManualUpdate"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); + } + + AICLI_LOG(Repo, Verbose, << "PreindexedPackageUpdate: Source [" << sourceId + << "] IsNewClient [" << isNewClient + << "] IndexStalenessDays [" << indexStalenessDays + << "] UsedDeltaDownload [" << usedDeltaDownload + << "] BaselineStalenessDays [" << baselineStalenessDays + << "] BaselineUpdated [" << baselineUpdated + << "] DownloadedBytes [" << downloadedBytes + << "] IsManualUpdate [" << isManualUpdate << "]"); + } + + void TelemetryTraceLogger::LogRepairFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "RepairFailure", + TraceLoggingUInt32(m_subExecutionId, "SubExecutionId"), + AICLI_TraceLoggingStringView(id, "Id"), + AICLI_TraceLoggingStringView(version, "Version"), + AICLI_TraceLoggingStringView(type, "Type"), + TraceLoggingUInt32(errorCode, "ErrorCode"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); + + if (m_useSummary) + { + m_summary.PackageIdentifier = id; + m_summary.PackageVersion = version; + m_summary.RepairExecutionType = type; + m_summary.RepairErrorCode = errorCode; + + } + } + + AICLI_LOG(CLI, Error, << type << " repair failed: " << errorCode); + } + + TelemetryTraceLogger::~TelemetryTraceLogger() + { + if (IsTelemetryEnabled()) + { + LocIndString version = Runtime::GetClientVersion(); + LocIndString packageVersion; + if (Runtime::IsRunningInPackagedContext()) + { + packageVersion = Runtime::GetPackageVersion(); + } + + if (m_useSummary) + { + TraceLoggingWriteActivity( + g_hTraceProvider, + "SummaryV2", + GetActivityId(), + GetParentActivityId(), + // From member fields or program info. + AICLI_TraceLoggingStringView(m_caller, "Caller"), + TraceLoggingPackedFieldEx(m_telemetryCorrelationJsonW.c_str(), static_cast((m_telemetryCorrelationJsonW.size() + 1) * sizeof(wchar_t)), TlgInUNICODESTRING, TlgOutJSON, "CvJson"), + TraceLoggingCountedString(version->c_str(), static_cast(version->size()), "ClientVersion"), + TraceLoggingCountedString(packageVersion->c_str(), static_cast(packageVersion->size()), "ClientPackageVersion"), + TraceLoggingBool(Runtime::IsReleaseBuild(), "IsReleaseBuild"), + TraceLoggingUInt32(m_executionStage, "ExecutionStage"), + // From TelemetrySummary + TraceLoggingHResult(m_summary.FailureHResult, "FailureHResult"), + AICLI_TraceLoggingWStringView(m_summary.FailureMessage, "FailureMessage"), + AICLI_TraceLoggingStringView(m_summary.FailureModule, "FailureModule"), + TraceLoggingUInt32(m_summary.FailureThreadId, "FailureThreadId"), + TraceLoggingUInt32(static_cast(m_summary.FailureType), "FailureType"), + AICLI_TraceLoggingStringView(m_summary.FailureFile, "FailureFile"), + TraceLoggingUInt32(m_summary.FailureLine, "FailureLine"), + TraceLoggingBool(m_summary.IsCOMCall, "IsCOMCall"), + TraceLoggingUInt32(static_cast(m_summary.ExecutionLevel), "ExecutionLevel"), + AICLI_TraceLoggingStringView(m_summary.Command, "Command"), + TraceLoggingBool(m_summary.CommandSuccess, "CommandSuccess"), + TraceLoggingBool(m_summary.IsManifestLocal, "IsManifestLocal"), + AICLI_TraceLoggingStringView(m_summary.PackageIdentifier, "PackageIdentifier"), + AICLI_TraceLoggingStringView(m_summary.PackageName, "PackageName"), + AICLI_TraceLoggingStringView(m_summary.PackageVersion, "PackageVersion"), + AICLI_TraceLoggingStringView(m_summary.Channel, "Channel"), + AICLI_TraceLoggingStringView(m_summary.SourceIdentifier, "SourceIdentifier"), + TraceLoggingInt32(m_summary.InstallerArchitecture, "InstallerArchitecture"), + AICLI_TraceLoggingStringView(m_summary.InstallerUrl, "InstallerUrl"), + AICLI_TraceLoggingStringView(m_summary.InstallerType, "InstallerType"), + AICLI_TraceLoggingStringView(m_summary.InstallerScope, "InstallerScope"), + AICLI_TraceLoggingStringView(m_summary.InstallerLocale, "InstallerLocale"), + AICLI_TraceLoggingStringView(m_summary.SearchType, "SearchType"), + AICLI_TraceLoggingStringView(m_summary.SearchQuery, "SearchQuery"), + AICLI_TraceLoggingStringView(m_summary.SearchId, "SearchId"), + AICLI_TraceLoggingStringView(m_summary.SearchName, "SearchName"), + AICLI_TraceLoggingStringView(m_summary.SearchMoniker, "SearchMoniker"), + AICLI_TraceLoggingStringView(m_summary.SearchTag, "SearchTag"), + AICLI_TraceLoggingStringView(m_summary.SearchCommand, "SearchCommand"), + TraceLoggingUInt64(m_summary.SearchMaximum, "SearchMaximum"), + AICLI_TraceLoggingStringView(m_summary.SearchRequest, "SearchRequest"), + TraceLoggingUInt64(m_summary.SearchResultCount, "SearchResultCount"), + TraceLoggingBinary(m_summary.HashMismatchExpected.data(), static_cast(m_summary.HashMismatchExpected.size()), "HashMismatchExpected"), + TraceLoggingBinary(m_summary.HashMismatchActual.data(), static_cast(m_summary.HashMismatchActual.size()), "HashMismatchActual"), + TraceLoggingBool(m_summary.HashMismatchOverride, "HashMismatchOverride"), + TraceLoggingUInt64(m_summary.HashMismatchActualSize, "HashMismatchActualSize"), + AICLI_TraceLoggingStringView(m_summary.HashMismatchContentType, "HashMismatchContentType"), + AICLI_TraceLoggingStringView(m_summary.InstallerExecutionType, "InstallerExecutionType"), + TraceLoggingUInt32(m_summary.InstallerErrorCode, "InstallerErrorCode"), + AICLI_TraceLoggingStringView(m_summary.UninstallerExecutionType, "UninstallerExecutionType"), + TraceLoggingUInt32(m_summary.UninstallerErrorCode, "UninstallerErrorCode"), + TraceLoggingUInt64(m_summary.ChangesToARP, "ChangesToARP"), + TraceLoggingUInt64(m_summary.MatchesInARP, "MatchesInARP"), + TraceLoggingUInt64(m_summary.ChangesThatMatch, "ChangesThatMatch"), + TraceLoggingUInt64(m_summary.ARPLanguage, "ARPLanguage"), + AICLI_TraceLoggingStringView(m_summary.ARPName, "ARPName"), + AICLI_TraceLoggingStringView(m_summary.ARPVersion, "ARPVersion"), + AICLI_TraceLoggingStringView(m_summary.ARPPublisher, "ARPPublisher"), + AICLI_TraceLoggingStringView(m_summary.DOUrl, "DOUrl"), + TraceLoggingHResult(m_summary.DOHResult, "DOHResult"), + AICLI_TraceLoggingStringView(m_summary.RepairExecutionType, "RepairExecutionType"), + TraceLoggingUInt32(m_summary.RepairErrorCode, "RepairErrorCode"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance | PDT_ProductAndServiceUsage | PDT_SoftwareSetupAndInventory), + TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES)); + } + } + } + + bool TelemetryTraceLogger::IsTelemetryEnabled() const noexcept + { + return g_IsTelemetryProviderEnabled && m_isInitialized && m_isSettingEnabled && m_isRuntimeEnabled; + } + + void TelemetryTraceLogger::InitializeInternal(const AppInstaller::Settings::UserSettings& userSettings) + { + m_isSettingEnabled = !userSettings.Get(); + m_isInitialized = true; + } + + std::wstring TelemetryTraceLogger::AnonymizeString(const wchar_t* input) const noexcept + { + return input ? AnonymizeString(std::wstring_view{ input }) : std::wstring{}; + } + + std::wstring TelemetryTraceLogger::AnonymizeString(std::wstring_view input) const noexcept try + { + // GetPathTo() may need to read the settings, so this function should only be called after settings are initialized. + // To ensure that, this function is only called when emitting an event, and we disable the telemetry until settings are ready. + static const std::wstring s_UserProfile = Runtime::GetPathTo(Runtime::PathName::UserProfile).wstring(); + + return Utility::ReplaceWhileCopying(input, s_UserProfile, s_UserProfileReplacement); + } + catch (...) { return std::wstring{ input }; } + +#ifndef AICLI_DISABLE_TEST_HOOKS + static std::shared_ptr s_TelemetryTraceLogger_TestOverride; +#endif + + TelemetryTraceLogger& Telemetry() + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_TelemetryTraceLogger_TestOverride) + { + return *s_TelemetryTraceLogger_TestOverride.get(); + } +#endif + ThreadLocalStorage::ThreadGlobals* pThreadGlobals = ThreadLocalStorage::ThreadGlobals::GetForCurrentThread(); + if (pThreadGlobals) + { + return *reinterpret_cast(pThreadGlobals->GetTelemetryObject()); + } + else + { + // For the global telemetry object, we may not have yet read the settings file. + // In that case, we will not be able to initialize it, so we need to try it + // each time we get the object. + static TelemetryTraceLogger processGlobalTelemetry(/* useSummary */ false); + processGlobalTelemetry.TryInitialize(); + return processGlobalTelemetry; + } + } + + void EnableWilFailureTelemetry() + { + wil::SetResultLoggingCallback(wilResultLoggingCallback); + } + + void UseGlobalTelemetryLoggerActivityIdOnly() + { + s_useGlobalTelemetryActivityId = true; + std::ignore = CoCreateGuid(&s_globalTelemetryLoggerActivityId); + } + + DisableTelemetryScope::DisableTelemetryScope() + { + m_token = Telemetry().DisableRuntime(); + } + + DisableTelemetryScope::~DisableTelemetryScope() + { + if (m_token) + { + Telemetry().EnableRuntime(); + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Replace this test hook with context telemetry when it gets moved over + void TestHook_SetTelemetryOverride(std::shared_ptr ttl) + { + s_TelemetryTraceLogger_TestOverride = std::move(ttl); + } +#endif +} diff --git a/src/AppInstallerCommonCore/Architecture.cpp b/src/AppInstallerCommonCore/Architecture.cpp index 95839455a8..fae7dc0252 100644 --- a/src/AppInstallerCommonCore/Architecture.cpp +++ b/src/AppInstallerCommonCore/Architecture.cpp @@ -1,286 +1,286 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerArchitecture.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerStrings.h" - -namespace AppInstaller::Utility -{ - using namespace literals; - namespace - { - // IsWow64GuestMachineSupported() is available starting on Windows 10, version 1709 (RS3). - // We generally target a later version (version 1809, RS5), but the WinGetUtil is used in - // Azure Functions that run on version 1607 (RS1) where it is not available. So, we load and - // call this function only if available. - using IsWow64GuestMachineSupportedPtr = decltype(&IsWow64GuestMachineSupported); - - struct IsWow64GuestMachineSupportedHelper - { - IsWow64GuestMachineSupportedHelper() - { - m_module.reset(LoadLibraryEx(L"api-ms-win-core-wow64-l1-1-2.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); - if (!m_module) - { - AICLI_LOG(Core, Verbose, << "Could not load api-ms-win-core-wow64-l1-1-2.dll"); - return; - } - - m_isWow64GuestMachineSupported = - reinterpret_cast(GetProcAddress(m_module.get(), "IsWow64GuestMachineSupported")); - if (!m_isWow64GuestMachineSupported) - { - AICLI_LOG(Core, Verbose, << "Could not get proc address of IsWow64GuestMachineSupported"); - return; - } - } - - void AddArchitectureIfGuestMachineSupported(std::vector& target, Architecture architecture, USHORT guestMachine) - { - if (m_isWow64GuestMachineSupported) - { - BOOL supported = FALSE; - LOG_IF_FAILED(m_isWow64GuestMachineSupported(guestMachine, &supported)); - - if (supported) - { - target.push_back(architecture); - } - } - } - - private: - wil::unique_hmodule m_module; - IsWow64GuestMachineSupportedPtr m_isWow64GuestMachineSupported = nullptr; - }; - - void AddArchitectureIfGuestMachineSupported(std::vector& target, Architecture architecture, USHORT guestMachine) - { - IsWow64GuestMachineSupportedHelper helper; - helper.AddArchitectureIfGuestMachineSupported(target, architecture, guestMachine); - } - - // These types are defined in a future SDK and can be removed when we actually have them available. - // The exception is that None was added (and this is an enum class). - enum class MACHINE_ATTRIBUTES { - None = 0, - UserEnabled = 0x00000001, - KernelEnabled = 0x00000002, - Wow64Container = 0x00000004 - }; - - DEFINE_ENUM_FLAG_OPERATORS(MACHINE_ATTRIBUTES); - - using GetMachineTypeAttributesPtr = HRESULT(WINAPI*)(USHORT Machine, MACHINE_ATTRIBUTES* MachineTypeAttributes); - - // GetMachineTypeAttributes can apparently replace IsWow64GuestMachineSupported, but no reason to do so right now. - struct GetMachineTypeAttributesHelper - { - GetMachineTypeAttributesHelper() - { - m_module.reset(LoadLibraryEx(L"api-ms-win-core-processthreads-l1-1-7.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); - if (!m_module) - { - AICLI_LOG(Core, Verbose, << "Could not load api-ms-win-core-processthreads-l1-1-7.dll"); - return; - } - - m_getMachineTypeAttributes = - reinterpret_cast(GetProcAddress(m_module.get(), "GetMachineTypeAttributes")); - if (!m_getMachineTypeAttributes) - { - AICLI_LOG(Core, Verbose, << "Could not get proc address of GetMachineTypeAttributes"); - return; - } - } - - void AddArchitectureIfMachineTypeAttributesUserEnabled(std::vector& target, Architecture architecture, USHORT guestMachine) - { - if (m_getMachineTypeAttributes) - { - MACHINE_ATTRIBUTES attributes = MACHINE_ATTRIBUTES::None; - if (SUCCEEDED_LOG(m_getMachineTypeAttributes(guestMachine, &attributes))) - { - if (WI_IsFlagSet(attributes, MACHINE_ATTRIBUTES::UserEnabled)) - { - target.push_back(architecture); - } - } - } - } - - private: - wil::unique_hmodule m_module; - GetMachineTypeAttributesPtr m_getMachineTypeAttributes = nullptr; - }; - - void AddArchitectureIfMachineTypeAttributesUserEnabled(std::vector& target, Architecture architecture, USHORT guestMachine) - { - GetMachineTypeAttributesHelper helper; - helper.AddArchitectureIfMachineTypeAttributesUserEnabled(target, architecture, guestMachine); - } - - // Gets the applicable architectures for the current machine. - std::vector CreateApplicableArchitecturesVector() - { - std::vector applicableArchs; - - switch (GetSystemArchitecture()) - { - case Architecture::Arm64: - { - applicableArchs.push_back(Architecture::Arm64); - AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::Arm, IMAGE_FILE_MACHINE_ARMNT); - AddArchitectureIfMachineTypeAttributesUserEnabled(applicableArchs, Architecture::X64, IMAGE_FILE_MACHINE_AMD64); - AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::X86, IMAGE_FILE_MACHINE_I386); - applicableArchs.push_back(Architecture::Neutral); - } - break; - case Architecture::Arm: - applicableArchs.push_back(Architecture::Arm); - applicableArchs.push_back(Architecture::Neutral); - break; - case Architecture::X86: - applicableArchs.push_back(Architecture::X86); - applicableArchs.push_back(Architecture::Neutral); - break; - case Architecture::X64: - applicableArchs.push_back(Architecture::X64); - AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::X86, IMAGE_FILE_MACHINE_I386); - applicableArchs.push_back(Architecture::Neutral); - break; - default: - applicableArchs.push_back(Architecture::Neutral); - } - - return applicableArchs; - } - } - - Architecture ConvertToArchitectureEnum(std::string_view archStr) - { - std::string arch = ToLower(archStr); - if (arch == "x86") - { - return Architecture::X86; - } - else if (arch == "x64") - { - return Architecture::X64; - } - else if (arch == "arm") - { - return Architecture::Arm; - } - else if (arch == "arm64") - { - return Architecture::Arm64; - } - else if (arch == "neutral") - { - return Architecture::Neutral; - } - - AICLI_LOG(Core, Info, << "ConvertToArchitectureEnum: Unknown architecture: " << archStr); - return Architecture::Unknown; - } - - std::optional<::AppInstaller::Utility::Architecture> ConvertToArchitectureEnum(winrt::Windows::System::ProcessorArchitecture architecture) - { - switch (architecture) - { - case winrt::Windows::System::ProcessorArchitecture::X86: - return ::AppInstaller::Utility::Architecture::X86; - case winrt::Windows::System::ProcessorArchitecture::Arm: - return ::AppInstaller::Utility::Architecture::Arm; - case winrt::Windows::System::ProcessorArchitecture::X64: - return ::AppInstaller::Utility::Architecture::X64; - case winrt::Windows::System::ProcessorArchitecture::Neutral: - return ::AppInstaller::Utility::Architecture::Neutral; - case winrt::Windows::System::ProcessorArchitecture::Arm64: - return ::AppInstaller::Utility::Architecture::Arm64; - } - - return {}; - } - - LocIndView ToString(Architecture architecture) - { - switch (architecture) - { - case Architecture::Neutral: - return "Neutral"_liv; - case Architecture::X86: - return "X86"_liv; - case Architecture::X64: - return "X64"_liv; - case Architecture::Arm: - return "Arm"_liv; - case Architecture::Arm64: - return "Arm64"_liv; - } - - return "Unknown"_liv; - } - - Architecture GetSystemArchitecture() - { - Architecture systemArchitecture = Architecture::Unknown; - - 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 (machineArchitecture) - { - case IMAGE_FILE_MACHINE_AMD64: - systemArchitecture = Architecture::X64; - break; - case IMAGE_FILE_MACHINE_ARM: - systemArchitecture = Architecture::Arm; - break; - case IMAGE_FILE_MACHINE_ARM64: - systemArchitecture = Architecture::Arm64; - break; - case IMAGE_FILE_MACHINE_I386: - systemArchitecture = Architecture::X86; - break; - } - - return systemArchitecture; - } - - const std::vector& GetApplicableArchitectures() - { - static std::vector applicableArchs = CreateApplicableArchitecturesVector(); - return applicableArchs; - } - - const std::vector& GetAllArchitectures() - { - static std::vector allArchs = { Architecture::Neutral, Architecture::X86, Architecture::X64, Architecture::Arm, Architecture::Arm64 }; - return allArchs; - } - - int IsApplicableArchitecture(Architecture arch) - { - return IsApplicableArchitecture(arch, GetApplicableArchitectures()); - } - - int IsApplicableArchitecture(Architecture arch, const std::vector& allowedArchitectures) - { - auto it = std::find(allowedArchitectures.begin(), allowedArchitectures.end(), arch); - - if (it != allowedArchitectures.end()) - { - return static_cast(std::distance(it, allowedArchitectures.end())); - } - else - { - return InapplicableArchitecture; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerArchitecture.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerStrings.h" + +namespace AppInstaller::Utility +{ + using namespace literals; + namespace + { + // IsWow64GuestMachineSupported() is available starting on Windows 10, version 1709 (RS3). + // We generally target a later version (version 1809, RS5), but the WinGetUtil is used in + // Azure Functions that run on version 1607 (RS1) where it is not available. So, we load and + // call this function only if available. + using IsWow64GuestMachineSupportedPtr = decltype(&IsWow64GuestMachineSupported); + + struct IsWow64GuestMachineSupportedHelper + { + IsWow64GuestMachineSupportedHelper() + { + m_module.reset(LoadLibraryEx(L"api-ms-win-core-wow64-l1-1-2.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); + if (!m_module) + { + AICLI_LOG(Core, Verbose, << "Could not load api-ms-win-core-wow64-l1-1-2.dll"); + return; + } + + m_isWow64GuestMachineSupported = + reinterpret_cast(GetProcAddress(m_module.get(), "IsWow64GuestMachineSupported")); + if (!m_isWow64GuestMachineSupported) + { + AICLI_LOG(Core, Verbose, << "Could not get proc address of IsWow64GuestMachineSupported"); + return; + } + } + + void AddArchitectureIfGuestMachineSupported(std::vector& target, Architecture architecture, USHORT guestMachine) + { + if (m_isWow64GuestMachineSupported) + { + BOOL supported = FALSE; + LOG_IF_FAILED(m_isWow64GuestMachineSupported(guestMachine, &supported)); + + if (supported) + { + target.push_back(architecture); + } + } + } + + private: + wil::unique_hmodule m_module; + IsWow64GuestMachineSupportedPtr m_isWow64GuestMachineSupported = nullptr; + }; + + void AddArchitectureIfGuestMachineSupported(std::vector& target, Architecture architecture, USHORT guestMachine) + { + IsWow64GuestMachineSupportedHelper helper; + helper.AddArchitectureIfGuestMachineSupported(target, architecture, guestMachine); + } + + // These types are defined in a future SDK and can be removed when we actually have them available. + // The exception is that None was added (and this is an enum class). + enum class MACHINE_ATTRIBUTES { + None = 0, + UserEnabled = 0x00000001, + KernelEnabled = 0x00000002, + Wow64Container = 0x00000004 + }; + + DEFINE_ENUM_FLAG_OPERATORS(MACHINE_ATTRIBUTES); + + using GetMachineTypeAttributesPtr = HRESULT(WINAPI*)(USHORT Machine, MACHINE_ATTRIBUTES* MachineTypeAttributes); + + // GetMachineTypeAttributes can apparently replace IsWow64GuestMachineSupported, but no reason to do so right now. + struct GetMachineTypeAttributesHelper + { + GetMachineTypeAttributesHelper() + { + m_module.reset(LoadLibraryEx(L"api-ms-win-core-processthreads-l1-1-7.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); + if (!m_module) + { + AICLI_LOG(Core, Verbose, << "Could not load api-ms-win-core-processthreads-l1-1-7.dll"); + return; + } + + m_getMachineTypeAttributes = + reinterpret_cast(GetProcAddress(m_module.get(), "GetMachineTypeAttributes")); + if (!m_getMachineTypeAttributes) + { + AICLI_LOG(Core, Verbose, << "Could not get proc address of GetMachineTypeAttributes"); + return; + } + } + + void AddArchitectureIfMachineTypeAttributesUserEnabled(std::vector& target, Architecture architecture, USHORT guestMachine) + { + if (m_getMachineTypeAttributes) + { + MACHINE_ATTRIBUTES attributes = MACHINE_ATTRIBUTES::None; + if (SUCCEEDED_LOG(m_getMachineTypeAttributes(guestMachine, &attributes))) + { + if (WI_IsFlagSet(attributes, MACHINE_ATTRIBUTES::UserEnabled)) + { + target.push_back(architecture); + } + } + } + } + + private: + wil::unique_hmodule m_module; + GetMachineTypeAttributesPtr m_getMachineTypeAttributes = nullptr; + }; + + void AddArchitectureIfMachineTypeAttributesUserEnabled(std::vector& target, Architecture architecture, USHORT guestMachine) + { + GetMachineTypeAttributesHelper helper; + helper.AddArchitectureIfMachineTypeAttributesUserEnabled(target, architecture, guestMachine); + } + + // Gets the applicable architectures for the current machine. + std::vector CreateApplicableArchitecturesVector() + { + std::vector applicableArchs; + + switch (GetSystemArchitecture()) + { + case Architecture::Arm64: + { + applicableArchs.push_back(Architecture::Arm64); + AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::Arm, IMAGE_FILE_MACHINE_ARMNT); + AddArchitectureIfMachineTypeAttributesUserEnabled(applicableArchs, Architecture::X64, IMAGE_FILE_MACHINE_AMD64); + AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::X86, IMAGE_FILE_MACHINE_I386); + applicableArchs.push_back(Architecture::Neutral); + } + break; + case Architecture::Arm: + applicableArchs.push_back(Architecture::Arm); + applicableArchs.push_back(Architecture::Neutral); + break; + case Architecture::X86: + applicableArchs.push_back(Architecture::X86); + applicableArchs.push_back(Architecture::Neutral); + break; + case Architecture::X64: + applicableArchs.push_back(Architecture::X64); + AddArchitectureIfGuestMachineSupported(applicableArchs, Architecture::X86, IMAGE_FILE_MACHINE_I386); + applicableArchs.push_back(Architecture::Neutral); + break; + default: + applicableArchs.push_back(Architecture::Neutral); + } + + return applicableArchs; + } + } + + Architecture ConvertToArchitectureEnum(std::string_view archStr) + { + std::string arch = ToLower(archStr); + if (arch == "x86") + { + return Architecture::X86; + } + else if (arch == "x64") + { + return Architecture::X64; + } + else if (arch == "arm") + { + return Architecture::Arm; + } + else if (arch == "arm64") + { + return Architecture::Arm64; + } + else if (arch == "neutral") + { + return Architecture::Neutral; + } + + AICLI_LOG(Core, Info, << "ConvertToArchitectureEnum: Unknown architecture: " << archStr); + return Architecture::Unknown; + } + + std::optional<::AppInstaller::Utility::Architecture> ConvertToArchitectureEnum(winrt::Windows::System::ProcessorArchitecture architecture) + { + switch (architecture) + { + case winrt::Windows::System::ProcessorArchitecture::X86: + return ::AppInstaller::Utility::Architecture::X86; + case winrt::Windows::System::ProcessorArchitecture::Arm: + return ::AppInstaller::Utility::Architecture::Arm; + case winrt::Windows::System::ProcessorArchitecture::X64: + return ::AppInstaller::Utility::Architecture::X64; + case winrt::Windows::System::ProcessorArchitecture::Neutral: + return ::AppInstaller::Utility::Architecture::Neutral; + case winrt::Windows::System::ProcessorArchitecture::Arm64: + return ::AppInstaller::Utility::Architecture::Arm64; + } + + return {}; + } + + LocIndView ToString(Architecture architecture) + { + switch (architecture) + { + case Architecture::Neutral: + return "Neutral"_liv; + case Architecture::X86: + return "X86"_liv; + case Architecture::X64: + return "X64"_liv; + case Architecture::Arm: + return "Arm"_liv; + case Architecture::Arm64: + return "Arm64"_liv; + } + + return "Unknown"_liv; + } + + Architecture GetSystemArchitecture() + { + Architecture systemArchitecture = Architecture::Unknown; + + 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 (machineArchitecture) + { + case IMAGE_FILE_MACHINE_AMD64: + systemArchitecture = Architecture::X64; + break; + case IMAGE_FILE_MACHINE_ARM: + systemArchitecture = Architecture::Arm; + break; + case IMAGE_FILE_MACHINE_ARM64: + systemArchitecture = Architecture::Arm64; + break; + case IMAGE_FILE_MACHINE_I386: + systemArchitecture = Architecture::X86; + break; + } + + return systemArchitecture; + } + + const std::vector& GetApplicableArchitectures() + { + static std::vector applicableArchs = CreateApplicableArchitecturesVector(); + return applicableArchs; + } + + const std::vector& GetAllArchitectures() + { + static std::vector allArchs = { Architecture::Neutral, Architecture::X86, Architecture::X64, Architecture::Arm, Architecture::Arm64 }; + return allArchs; + } + + int IsApplicableArchitecture(Architecture arch) + { + return IsApplicableArchitecture(arch, GetApplicableArchitectures()); + } + + int IsApplicableArchitecture(Architecture arch, const std::vector& allowedArchitectures) + { + auto it = std::find(allowedArchitectures.begin(), allowedArchitectures.end(), arch); + + if (it != allowedArchitectures.end()) + { + return static_cast(std::distance(it, allowedArchitectures.end())); + } + else + { + return InapplicableArchitecture; + } + } +} diff --git a/src/AppInstallerCommonCore/Archive.cpp b/src/AppInstallerCommonCore/Archive.cpp index 95766519e9..7b5980e501 100644 --- a/src/AppInstallerCommonCore/Archive.cpp +++ b/src/AppInstallerCommonCore/Archive.cpp @@ -1,79 +1,79 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/Archive.h" - -// TODO: Move include statement to pch.h and resolve build errors -#pragma warning( push ) -#pragma warning ( disable : 4189 4244 26451 ) -#include -#pragma warning ( pop ) - -namespace AppInstaller::Archive -{ - using unique_pidlist_absolute = wil::unique_any; - using unique_lpitemidlist = wil::unique_any; - - HRESULT TryExtractArchive(const std::filesystem::path& archivePath, const std::filesystem::path& destPath) - { - wil::com_ptr pFileOperation; - RETURN_IF_FAILED(CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pFileOperation))); - RETURN_IF_FAILED(pFileOperation->SetOperationFlags(FOF_NO_UI)); - - wil::com_ptr pShellItemTo; - RETURN_IF_FAILED(SHCreateItemFromParsingName(destPath.c_str(), NULL, IID_PPV_ARGS(&pShellItemTo))); - - unique_pidlist_absolute pidlFull; - RETURN_IF_FAILED(SHParseDisplayName(archivePath.c_str(), NULL, &pidlFull, 0, NULL)); - - wil::com_ptr pArchiveShellFolder; - RETURN_IF_FAILED(SHBindToObject(NULL, pidlFull.get(), NULL, IID_PPV_ARGS(&pArchiveShellFolder))); - - wil::com_ptr pEnumIdList; - RETURN_IF_FAILED(pArchiveShellFolder->EnumObjects(nullptr, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnumIdList)); - - unique_lpitemidlist pidlChild; - ULONG nFetched; - while (pEnumIdList->Next(1, wil::out_param_ptr(pidlChild), &nFetched) == S_OK && nFetched == 1) - { - wil::com_ptr pShellItemFrom; - STRRET strFolderName; - WCHAR szFolderName[MAX_PATH]; - RETURN_IF_FAILED(pArchiveShellFolder->GetDisplayNameOf(pidlChild.get(), SHGDN_INFOLDER | SHGDN_FORPARSING, &strFolderName)); - RETURN_IF_FAILED(StrRetToBuf(&strFolderName, pidlChild.get(), szFolderName, MAX_PATH)); - RETURN_IF_FAILED(SHCreateItemWithParent(pidlFull.get(), pArchiveShellFolder.get(), pidlChild.get(), IID_PPV_ARGS(&pShellItemFrom))); - RETURN_IF_FAILED(pFileOperation->CopyItem(pShellItemFrom.get(), pShellItemTo.get(), NULL, NULL)); - } - - RETURN_IF_FAILED(pFileOperation->PerformOperations()); - return S_OK; - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static bool* s_ScanArchiveResult_TestHook_Override = nullptr; - - void TestHook_SetScanArchiveResult_Override(bool* status) - { - s_ScanArchiveResult_TestHook_Override = status; - } -#endif - - bool ScanZipFile(const std::filesystem::path& zipPath) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_ScanArchiveResult_TestHook_Override) - { - return *s_ScanArchiveResult_TestHook_Override; - } -#endif - - std::ifstream instream{ zipPath, std::ios::in | std::ios::binary }; - std::vector data{ { std::istreambuf_iterator{ instream } }, std::istreambuf_iterator{} }; - - uint8_t* buffer = &data[0]; - uint64_t flag = 0; - int scanResult = pure_zip(buffer, data.size(), flag); - - return scanResult == 0; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Archive.h" + +// TODO: Move include statement to pch.h and resolve build errors +#pragma warning( push ) +#pragma warning ( disable : 4189 4244 26451 ) +#include +#pragma warning ( pop ) + +namespace AppInstaller::Archive +{ + using unique_pidlist_absolute = wil::unique_any; + using unique_lpitemidlist = wil::unique_any; + + HRESULT TryExtractArchive(const std::filesystem::path& archivePath, const std::filesystem::path& destPath) + { + wil::com_ptr pFileOperation; + RETURN_IF_FAILED(CoCreateInstance(CLSID_FileOperation, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pFileOperation))); + RETURN_IF_FAILED(pFileOperation->SetOperationFlags(FOF_NO_UI)); + + wil::com_ptr pShellItemTo; + RETURN_IF_FAILED(SHCreateItemFromParsingName(destPath.c_str(), NULL, IID_PPV_ARGS(&pShellItemTo))); + + unique_pidlist_absolute pidlFull; + RETURN_IF_FAILED(SHParseDisplayName(archivePath.c_str(), NULL, &pidlFull, 0, NULL)); + + wil::com_ptr pArchiveShellFolder; + RETURN_IF_FAILED(SHBindToObject(NULL, pidlFull.get(), NULL, IID_PPV_ARGS(&pArchiveShellFolder))); + + wil::com_ptr pEnumIdList; + RETURN_IF_FAILED(pArchiveShellFolder->EnumObjects(nullptr, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &pEnumIdList)); + + unique_lpitemidlist pidlChild; + ULONG nFetched; + while (pEnumIdList->Next(1, wil::out_param_ptr(pidlChild), &nFetched) == S_OK && nFetched == 1) + { + wil::com_ptr pShellItemFrom; + STRRET strFolderName; + WCHAR szFolderName[MAX_PATH]; + RETURN_IF_FAILED(pArchiveShellFolder->GetDisplayNameOf(pidlChild.get(), SHGDN_INFOLDER | SHGDN_FORPARSING, &strFolderName)); + RETURN_IF_FAILED(StrRetToBuf(&strFolderName, pidlChild.get(), szFolderName, MAX_PATH)); + RETURN_IF_FAILED(SHCreateItemWithParent(pidlFull.get(), pArchiveShellFolder.get(), pidlChild.get(), IID_PPV_ARGS(&pShellItemFrom))); + RETURN_IF_FAILED(pFileOperation->CopyItem(pShellItemFrom.get(), pShellItemTo.get(), NULL, NULL)); + } + + RETURN_IF_FAILED(pFileOperation->PerformOperations()); + return S_OK; + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + static bool* s_ScanArchiveResult_TestHook_Override = nullptr; + + void TestHook_SetScanArchiveResult_Override(bool* status) + { + s_ScanArchiveResult_TestHook_Override = status; + } +#endif + + bool ScanZipFile(const std::filesystem::path& zipPath) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_ScanArchiveResult_TestHook_Override) + { + return *s_ScanArchiveResult_TestHook_Override; + } +#endif + + std::ifstream instream{ zipPath, std::ios::in | std::ios::binary }; + std::vector data{ { std::istreambuf_iterator{ instream } }, std::istreambuf_iterator{} }; + + uint8_t* buffer = &data[0]; + uint64_t flag = 0; + int scanResult = pure_zip(buffer, data.size(), flag); + + return scanResult == 0; + } +} diff --git a/src/AppInstallerCommonCore/Authentication/Authentication.cpp b/src/AppInstallerCommonCore/Authentication/Authentication.cpp index 3d2e8067a4..2c851fc6a3 100644 --- a/src/AppInstallerCommonCore/Authentication/Authentication.cpp +++ b/src/AppInstallerCommonCore/Authentication/Authentication.cpp @@ -12,8 +12,8 @@ namespace AppInstaller::Authentication { namespace { - 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_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; } @@ -56,39 +56,39 @@ 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 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 + // 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; + { + // 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; + } + else + { + MicrosoftEntraIdAuthenticationInfo authInfo; + authInfo.Resource = s_DefaultAzureBlobStorageResource; MicrosoftEntraIdInfo = std::move(authInfo); } } @@ -215,7 +215,7 @@ namespace AppInstaller::Authentication case AuthenticationType::None: return "none"sv; case AuthenticationType::MicrosoftEntraId: - return "microsoftEntraId"sv; + return "microsoftEntraId"sv; case AuthenticationType::MicrosoftEntraIdForAzureBlobStorage: return "microsoftEntraIdForAzureBlobStorage"sv; } @@ -235,7 +235,7 @@ namespace AppInstaller::Authentication else if (inStrLower == "microsoftentraid") { result = AuthenticationType::MicrosoftEntraId; - } + } else if (inStrLower == "microsoftentraidforazureblobstorage") { result = AuthenticationType::MicrosoftEntraIdForAzureBlobStorage; diff --git a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp index de6f7a15a3..845b4f4f89 100644 --- a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp +++ b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.cpp @@ -219,13 +219,13 @@ namespace AppInstaller::Authentication AICLI_LOG(Core, Error, << "CreateTokenRequest returned empty request"); return {}; } - - if (webAccount) - { + + if (webAccount) + { return HandleGetTokenResult(WebAuthenticationCoreManager::GetTokenSilentlyAsync(request, webAccount).get()); - } - else - { + } + else + { return HandleGetTokenResult(WebAuthenticationCoreManager::GetTokenSilentlyAsync(request).get()); } } @@ -290,7 +290,7 @@ namespace AppInstaller::Authentication AICLI_LOG(Core, Info, << "HandleGetTokenResult Result: " << result.Status); return result; - } + } bool WebAccountManagerAuthenticator::IsMicrosoftEntraIdAuthenticationType() { diff --git a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h index 76829b5e42..e2d0c2a1df 100644 --- a/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h +++ b/src/AppInstallerCommonCore/Authentication/WebAccountManagerAuthenticator.h @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Public/winget/Authentication.h" -#include -#include - -namespace AppInstaller::Authentication -{ - struct WebAccountManagerAuthenticator : public IAuthenticationProvider - { - WebAccountManagerAuthenticator(AuthenticationInfo info, AuthenticationArguments args); - - AuthenticationResult AuthenticateForToken(); - - private: - AuthenticationInfo m_authInfo; - AuthenticationArguments m_authArgs; - winrt::Windows::Security::Credentials::WebAccountProvider m_webAccountProvider = nullptr; - winrt::Windows::Security::Credentials::WebAccount m_authenticatedAccount = nullptr; - std::mutex m_authLock; - - winrt::Windows::Security::Credentials::WebAccount FindWebAccount(std::string_view accountName); - winrt::Windows::Security::Authentication::Web::Core::WebTokenRequest CreateTokenRequest(bool forceInteractive); - 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(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Public/winget/Authentication.h" +#include +#include + +namespace AppInstaller::Authentication +{ + struct WebAccountManagerAuthenticator : public IAuthenticationProvider + { + WebAccountManagerAuthenticator(AuthenticationInfo info, AuthenticationArguments args); + + AuthenticationResult AuthenticateForToken(); + + private: + AuthenticationInfo m_authInfo; + AuthenticationArguments m_authArgs; + winrt::Windows::Security::Credentials::WebAccountProvider m_webAccountProvider = nullptr; + winrt::Windows::Security::Credentials::WebAccount m_authenticatedAccount = nullptr; + std::mutex m_authLock; + + winrt::Windows::Security::Credentials::WebAccount FindWebAccount(std::string_view accountName); + winrt::Windows::Security::Authentication::Web::Core::WebTokenRequest CreateTokenRequest(bool forceInteractive); + 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(); + }; +} diff --git a/src/AppInstallerCommonCore/DODownloader.cpp b/src/AppInstallerCommonCore/DODownloader.cpp index 469edfcd3b..7113ede1c3 100644 --- a/src/AppInstallerCommonCore/DODownloader.cpp +++ b/src/AppInstallerCommonCore/DODownloader.cpp @@ -1,507 +1,507 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DODownloader.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerSHA256.h" -#include "Public/AppInstallerStrings.h" -#include "winget/UserSettings.h" - -// TODO: Get this from the Windows SDK when available -#define DODownloadProperty_HttpRedirectionTarget static_cast(DODownloadProperty_NonVolatile + 1) -#define DODownloadProperty_HttpResponseHeaders static_cast(DODownloadProperty_HttpRedirectionTarget + 1) -#define DODownloadProperty_HttpServerIPAddress static_cast(DODownloadProperty_HttpResponseHeaders + 1) -#define DODownloadProperty_HttpStatusCode static_cast(DODownloadProperty_HttpServerIPAddress + 1) - -namespace AppInstaller::Utility -{ - namespace - { - std::optional ExtractContentType(const std::optional& headers) - { - if (!headers) - { - return std::nullopt; - } - - static constexpr std::string_view s_ContentType = "content-type:"sv; - auto headerLines = Utility::SplitIntoLines(headers.value()); - - for (const auto& header : headerLines) - { - std::string_view headerView = header; - if (header.length() >= s_ContentType.length()) - { - std::string lowerFragment = ToLower(headerView.substr(0, s_ContentType.length())); - if (s_ContentType == lowerFragment) - { - return Trim(header.substr(s_ContentType.length())); - } - } - } - - return std::nullopt; - } - } - - namespace DeliveryOptimization - { - // Represents a download work item for Delivery Optimization. - struct Download - { - Download(IDOManager* manager) - { - THROW_IF_FAILED(manager->CreateDownload(&m_download)); - - // Cloaking - sets the authentication information that will be used to make calls on the DO interface proxy. - // This will make sure DO server impersonates the correct client identity. - THROW_IF_FAILED(CoSetProxyBlanket( - m_download.get(), - RPC_C_AUTHN_DEFAULT, - RPC_C_AUTHZ_DEFAULT, - COLE_DEFAULT_PRINCIPAL, - RPC_C_AUTHN_LEVEL_DEFAULT, - RPC_C_IMP_LEVEL_IMPERSONATE, - NULL, - EOAC_DEFAULT)); - } - - ~Download() - { - DO_DOWNLOAD_STATUS downloadStatus; - if (SUCCEEDED_LOG(m_download->GetStatus(&downloadStatus))) - { - if (downloadStatus.State == DODownloadState_Transferred) - { - // Calling IDODownload::Finalize() to inform DO that the DO job can be cleaned up. - // Otherwise, the resources associated with the job can be kept for a number of days - // until expiration set by DO. - (void)LOG_IF_FAILED(m_download->Finalize()); - } - else if (downloadStatus.State != DODownloadState_Finalized) - { - // For any other state, abort the download since it's no longer in use. - // This will allow DO to clean up the cache for the associated content ID. - (void)LOG_IF_FAILED(m_download->Abort()); - } - } - } - - void SetProperty(DODownloadProperty prop, const std::wstring& value) - { - wil::unique_variant var; - var.bstrVal = ::SysAllocString(value.c_str()); - THROW_IF_NULL_ALLOC(var.bstrVal); - var.vt = VT_BSTR; - THROW_IF_FAILED(m_download->SetProperty(prop, &var)); - } - - void SetProperty(DODownloadProperty prop, std::string_view value) - { - SetProperty(prop, Utility::ConvertToUTF16(value)); - } - - void SetProperty(DODownloadProperty prop, uint32_t value) - { - wil::unique_variant var; - var.ulVal = value; - var.vt = VT_UI4; - THROW_IF_FAILED(m_download->SetProperty(prop, &var)); - } - - void SetProperty(DODownloadProperty prop, bool value) - { - wil::unique_variant var; - var.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE; - var.vt = VT_BOOL; - THROW_IF_FAILED(m_download->SetProperty(prop, &var)); - } - - template - void SetUnknownProperty(DODownloadProperty prop, T&& value) - { - wil::unique_variant var; - var.punkVal = nullptr; - var.vt = VT_UNKNOWN; - if (value) - { - THROW_IF_FAILED(value->QueryInterface(IID_PPV_ARGS(&var.punkVal))); - } - THROW_IF_FAILED(m_download->SetProperty(prop, &var)); - } - - template - std::optional TryGetProperty(DODownloadProperty prop) - { - std::optional result; - wil::unique_variant var; - HRESULT hr = m_download->GetProperty(prop, &var); - if (SUCCEEDED(hr)) - { - T value; - if (ExtractFromVariant(var, value)) - { - result = std::move(value); - } - } - return result; - } - - void Uri(std::string_view uri) - { - SetProperty(DODownloadProperty_Uri, uri); - } - - void ContentId(std::string_view contentId) - { - SetProperty(DODownloadProperty_ContentId, contentId); - } - - void DisplayName(std::string_view displayName) - { - SetProperty(DODownloadProperty_DisplayName, displayName); - } - - void LocalPath(const std::filesystem::path& localPath) - { - SetProperty(DODownloadProperty_LocalPath, localPath.wstring()); - } - - void CorrelationVector(std::string_view correlationVector) - { - SetProperty(DODownloadProperty_CorrelationVector, correlationVector); - } - - void NoProgressTimeoutSeconds(uint32_t noProgressTimeoutSeconds) - { - SetProperty(DODownloadProperty_NoProgressTimeoutSeconds, noProgressTimeoutSeconds); - } - - void ForegroundPriority(bool foregroundPriority) - { - SetProperty(DODownloadProperty_ForegroundPriority, foregroundPriority); - } - - void BlockingMode(bool blockingMode) - { - SetProperty(DODownloadProperty_BlockingMode, blockingMode); - } - - void CallbackInterface(IDODownloadStatusCallback* callbackInterface) - { - SetUnknownProperty(DODownloadProperty_CallbackInterface, callbackInterface); - } - - void StreamInterface(IStream* streamInterface) - { - 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 - - void Start() - { - DO_DOWNLOAD_RANGES_INFO emptyRanges{}; - emptyRanges.RangeCount = 0; - THROW_IF_FAILED(m_download->Start(&emptyRanges)); - } - - // Returns true if Abort was successful; false if not. - bool Cancel() - { - return SUCCEEDED_LOG(m_download->Abort()); - } - - void Finalize() - { - THROW_IF_FAILED(m_download->Finalize()); - } - - DO_DOWNLOAD_STATUS Status() - { - DO_DOWNLOAD_STATUS result{}; - THROW_IF_FAILED(m_download->GetStatus(&result)); - return result; - } - - private: - bool ExtractFromVariant(const VARIANT& var, std::string& value) - { - if (var.vt == VT_BSTR && var.bstrVal != nullptr) - { - value = Utility::ConvertToUTF8(var.bstrVal); - return true; - } - else if (var.vt == (VT_BSTR | VT_BYREF) && var.pbstrVal != nullptr && *var.pbstrVal != nullptr) - { - value = Utility::ConvertToUTF8(*var.pbstrVal); - return true; - } - - return false; - } - - wil::com_ptr m_download; - }; - - // The top level Delivery Optimization manager object. - struct Manager - { - Manager() - { - THROW_IF_FAILED(CoCreateInstance( - __uuidof(::DeliveryOptimization), - nullptr, - CLSCTX_LOCAL_SERVER, - IID_PPV_ARGS(&m_manager))); - } - - Download CreateDownload() - { - return { m_manager.get() }; - } - - private: - wil::com_ptr m_manager; - }; - - // Status callback handler - class DODownloadStatusCallback : public Microsoft::WRL::RuntimeClass< - Microsoft::WRL::RuntimeClassFlags, - IDODownloadStatusCallback> - { - public: - DODownloadStatusCallback(IProgressCallback& progress) : - m_progress(progress) - { - } - - IFACEMETHOD(OnStatusChange)(IDODownload*, const DO_DOWNLOAD_STATUS* status) - { - { - std::lock_guard guard(m_statusMutex); - m_currentStatus = *status; - } - m_statusCV.notify_all(); - return S_OK; - } - - static HRESULT Create( - IProgressCallback& progress, - DODownloadStatusCallback** result) - { - Microsoft::WRL::ComPtr localResult = Microsoft::WRL::Make(progress); - RETURN_IF_NULL_ALLOC(localResult); - - *result = localResult.Detach(); - return S_OK; - } - - // Simply breaks the wait in Wait; the progress object must already be cancelled to force it out. - void Cancel() - { - m_statusCV.notify_all(); - } - - // Returns true on successful completion, false on cancellation, and throws on an error. - bool Wait() - { - std::unique_lock lock(m_statusMutex); - - // If there is no transfer status update for m_doNoProgressTimeout, we will fail. - auto timeoutTime = std::chrono::steady_clock::now() + Settings::User().Get(); - std::optional initialTransferAmount; - bool transferChange = false; - - while (!m_progress.IsCancelledBy(CancelReason::Any)) - { - if (!transferChange) - { - if (m_statusCV.wait_until(lock, timeoutTime) == std::cv_status::timeout) - { - THROW_HR(DO_E_DOWNLOAD_NO_PROGRESS); - } - } - else - { - m_statusCV.wait(lock); - } - - // Since we just finished a wait, check for cancellation before handling anything else - if (m_progress.IsCancelledBy(CancelReason::Any)) - { - return false; - } - - AICLI_LOG(Core, Verbose, << "DO State " << m_currentStatus.State << ", " << m_currentStatus.BytesTransferred << " / " << m_currentStatus.BytesTotal << - ", Error 0x" << Logging::SetHRFormat << m_currentStatus.Error << ", extended error 0x" << Logging::SetHRFormat << m_currentStatus.ExtendedError); - - // No matter the state, we are considering any error set to be a failure - if (FAILED(m_currentStatus.Error)) - { - AICLI_LOG(Core, Error, << "DeliveryOptimization error: 0x" << Logging::SetHRFormat << m_currentStatus.Error << - ", extended error: 0x" << Logging::SetHRFormat << m_currentStatus.ExtendedError); - THROW_HR(m_currentStatus.Error); - } - - switch (m_currentStatus.State) - { - // These states are ignored. - case DODownloadState_Created: - case DODownloadState_Paused: - break; - - case DODownloadState_Transferring: - if (m_currentStatus.BytesTransferred || m_currentStatus.BytesTotal) - { - m_progress.OnProgress(m_currentStatus.BytesTransferred, m_currentStatus.BytesTotal, ProgressType::Bytes); - } - - if (!initialTransferAmount) - { - initialTransferAmount = m_currentStatus.BytesTransferred; - } - else if (m_currentStatus.BytesTransferred != initialTransferAmount.value()) - { - transferChange = true; - } - break; - - // These are considered to be 'done' - case DODownloadState_Transferred: - case DODownloadState_Finalized: - if (m_currentStatus.BytesTransferred || m_currentStatus.BytesTotal) - { - m_progress.OnProgress(m_currentStatus.BytesTransferred, m_currentStatus.BytesTotal, ProgressType::Bytes); - } - return true; - - // This is the cancelled state - case DODownloadState_Aborted: - return false; - } - } - - return false; - } - - private: - IProgressCallback& m_progress; - std::mutex m_statusMutex; - std::condition_variable m_statusCV; - DO_DOWNLOAD_STATUS m_currentStatus = {}; - }; - } - - // Debugging tip: - // From an elevated PowerShell, run: - // > Get-DeliveryOptimizationLog | Set-Content doLogs.txt - DownloadResult DODownload( - const std::string& url, - const std::filesystem::path& dest, - IProgressCallback& progress, - std::optional info) - { - AICLI_LOG(Core, Info, << "DeliveryOptimization downloading from url: " << url); - - // Remove the target file since DO will not overwrite - std::filesystem::remove(dest); - - DeliveryOptimization::Manager manager; - DeliveryOptimization::Download download = manager.CreateDownload(); - - wil::com_ptr callback; - THROW_IF_FAILED(DeliveryOptimization::DODownloadStatusCallback::Create(progress, &callback)); - - download.Uri(url); - download.ForegroundPriority(true); - download.LocalPath(dest); - download.CallbackInterface(callback.get()); - - if (info) - { - if (!info->DisplayName.empty()) - { - download.DisplayName(info->DisplayName); - } - - if (!info->ContentId.empty()) - { - download.ContentId(info->ContentId); - } - - if (!info->RequestHeaders.empty()) - { - download.CustomHeaders(info->RequestHeaders); - } - } - - download.Start(); - - auto cancelLifetime = progress.SetCancellationFunction([&download, &callback]() - { - AICLI_LOG(Core, Info, << "Download cancelled."); - download.Cancel(); - callback->Cancel(); - }); - - // Check to handle cancellation between Start and SetCancellationFunction - if (progress.IsCancelledBy(CancelReason::Any)) - { - AICLI_LOG(Core, Info, << "Download cancelled."); - download.Cancel(); - return {}; - } - - // Wait returns true for success, false for cancellation, and throws on error. - if (callback->Wait()) - { - // Grab the headers so that we can use them later - std::optional responseHeaders = download.TryGetProperty(DODownloadProperty_HttpResponseHeaders); - - // Finalize is required to flush the data and change the file name. - download.Finalize(); - AICLI_LOG(Core, Info, << "Download completed."); - - std::ifstream inStream{ dest, std::ifstream::binary }; - auto hashDetails = SHA256::ComputeHashDetails(inStream); - - DownloadResult result; - result.Sha256Hash = std::move(hashDetails.Hash); - result.SizeInBytes = hashDetails.SizeInBytes; - result.ContentType = ExtractContentType(responseHeaders); - - return result; - } - - return {}; - } - - bool IsDOErrorFatal(HRESULT error) - { - // If this gets to be large, store in a sorted array and binary search on it. - // There will be more to update here, which we should be able to discover through telemetry. - return - error == DO_E_BLOCKED_BY_COST_TRANSFER_POLICY || - error == DO_E_BLOCKED_BY_CELLULAR_POLICY || - error == DO_E_BLOCKED_BY_POWER_STATE || - error == DO_E_BLOCKED_BY_NO_NETWORK; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DODownloader.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerSHA256.h" +#include "Public/AppInstallerStrings.h" +#include "winget/UserSettings.h" + +// TODO: Get this from the Windows SDK when available +#define DODownloadProperty_HttpRedirectionTarget static_cast(DODownloadProperty_NonVolatile + 1) +#define DODownloadProperty_HttpResponseHeaders static_cast(DODownloadProperty_HttpRedirectionTarget + 1) +#define DODownloadProperty_HttpServerIPAddress static_cast(DODownloadProperty_HttpResponseHeaders + 1) +#define DODownloadProperty_HttpStatusCode static_cast(DODownloadProperty_HttpServerIPAddress + 1) + +namespace AppInstaller::Utility +{ + namespace + { + std::optional ExtractContentType(const std::optional& headers) + { + if (!headers) + { + return std::nullopt; + } + + static constexpr std::string_view s_ContentType = "content-type:"sv; + auto headerLines = Utility::SplitIntoLines(headers.value()); + + for (const auto& header : headerLines) + { + std::string_view headerView = header; + if (header.length() >= s_ContentType.length()) + { + std::string lowerFragment = ToLower(headerView.substr(0, s_ContentType.length())); + if (s_ContentType == lowerFragment) + { + return Trim(header.substr(s_ContentType.length())); + } + } + } + + return std::nullopt; + } + } + + namespace DeliveryOptimization + { + // Represents a download work item for Delivery Optimization. + struct Download + { + Download(IDOManager* manager) + { + THROW_IF_FAILED(manager->CreateDownload(&m_download)); + + // Cloaking - sets the authentication information that will be used to make calls on the DO interface proxy. + // This will make sure DO server impersonates the correct client identity. + THROW_IF_FAILED(CoSetProxyBlanket( + m_download.get(), + RPC_C_AUTHN_DEFAULT, + RPC_C_AUTHZ_DEFAULT, + COLE_DEFAULT_PRINCIPAL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_DEFAULT)); + } + + ~Download() + { + DO_DOWNLOAD_STATUS downloadStatus; + if (SUCCEEDED_LOG(m_download->GetStatus(&downloadStatus))) + { + if (downloadStatus.State == DODownloadState_Transferred) + { + // Calling IDODownload::Finalize() to inform DO that the DO job can be cleaned up. + // Otherwise, the resources associated with the job can be kept for a number of days + // until expiration set by DO. + (void)LOG_IF_FAILED(m_download->Finalize()); + } + else if (downloadStatus.State != DODownloadState_Finalized) + { + // For any other state, abort the download since it's no longer in use. + // This will allow DO to clean up the cache for the associated content ID. + (void)LOG_IF_FAILED(m_download->Abort()); + } + } + } + + void SetProperty(DODownloadProperty prop, const std::wstring& value) + { + wil::unique_variant var; + var.bstrVal = ::SysAllocString(value.c_str()); + THROW_IF_NULL_ALLOC(var.bstrVal); + var.vt = VT_BSTR; + THROW_IF_FAILED(m_download->SetProperty(prop, &var)); + } + + void SetProperty(DODownloadProperty prop, std::string_view value) + { + SetProperty(prop, Utility::ConvertToUTF16(value)); + } + + void SetProperty(DODownloadProperty prop, uint32_t value) + { + wil::unique_variant var; + var.ulVal = value; + var.vt = VT_UI4; + THROW_IF_FAILED(m_download->SetProperty(prop, &var)); + } + + void SetProperty(DODownloadProperty prop, bool value) + { + wil::unique_variant var; + var.boolVal = value ? VARIANT_TRUE : VARIANT_FALSE; + var.vt = VT_BOOL; + THROW_IF_FAILED(m_download->SetProperty(prop, &var)); + } + + template + void SetUnknownProperty(DODownloadProperty prop, T&& value) + { + wil::unique_variant var; + var.punkVal = nullptr; + var.vt = VT_UNKNOWN; + if (value) + { + THROW_IF_FAILED(value->QueryInterface(IID_PPV_ARGS(&var.punkVal))); + } + THROW_IF_FAILED(m_download->SetProperty(prop, &var)); + } + + template + std::optional TryGetProperty(DODownloadProperty prop) + { + std::optional result; + wil::unique_variant var; + HRESULT hr = m_download->GetProperty(prop, &var); + if (SUCCEEDED(hr)) + { + T value; + if (ExtractFromVariant(var, value)) + { + result = std::move(value); + } + } + return result; + } + + void Uri(std::string_view uri) + { + SetProperty(DODownloadProperty_Uri, uri); + } + + void ContentId(std::string_view contentId) + { + SetProperty(DODownloadProperty_ContentId, contentId); + } + + void DisplayName(std::string_view displayName) + { + SetProperty(DODownloadProperty_DisplayName, displayName); + } + + void LocalPath(const std::filesystem::path& localPath) + { + SetProperty(DODownloadProperty_LocalPath, localPath.wstring()); + } + + void CorrelationVector(std::string_view correlationVector) + { + SetProperty(DODownloadProperty_CorrelationVector, correlationVector); + } + + void NoProgressTimeoutSeconds(uint32_t noProgressTimeoutSeconds) + { + SetProperty(DODownloadProperty_NoProgressTimeoutSeconds, noProgressTimeoutSeconds); + } + + void ForegroundPriority(bool foregroundPriority) + { + SetProperty(DODownloadProperty_ForegroundPriority, foregroundPriority); + } + + void BlockingMode(bool blockingMode) + { + SetProperty(DODownloadProperty_BlockingMode, blockingMode); + } + + void CallbackInterface(IDODownloadStatusCallback* callbackInterface) + { + SetUnknownProperty(DODownloadProperty_CallbackInterface, callbackInterface); + } + + void StreamInterface(IStream* streamInterface) + { + 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 + + void Start() + { + DO_DOWNLOAD_RANGES_INFO emptyRanges{}; + emptyRanges.RangeCount = 0; + THROW_IF_FAILED(m_download->Start(&emptyRanges)); + } + + // Returns true if Abort was successful; false if not. + bool Cancel() + { + return SUCCEEDED_LOG(m_download->Abort()); + } + + void Finalize() + { + THROW_IF_FAILED(m_download->Finalize()); + } + + DO_DOWNLOAD_STATUS Status() + { + DO_DOWNLOAD_STATUS result{}; + THROW_IF_FAILED(m_download->GetStatus(&result)); + return result; + } + + private: + bool ExtractFromVariant(const VARIANT& var, std::string& value) + { + if (var.vt == VT_BSTR && var.bstrVal != nullptr) + { + value = Utility::ConvertToUTF8(var.bstrVal); + return true; + } + else if (var.vt == (VT_BSTR | VT_BYREF) && var.pbstrVal != nullptr && *var.pbstrVal != nullptr) + { + value = Utility::ConvertToUTF8(*var.pbstrVal); + return true; + } + + return false; + } + + wil::com_ptr m_download; + }; + + // The top level Delivery Optimization manager object. + struct Manager + { + Manager() + { + THROW_IF_FAILED(CoCreateInstance( + __uuidof(::DeliveryOptimization), + nullptr, + CLSCTX_LOCAL_SERVER, + IID_PPV_ARGS(&m_manager))); + } + + Download CreateDownload() + { + return { m_manager.get() }; + } + + private: + wil::com_ptr m_manager; + }; + + // Status callback handler + class DODownloadStatusCallback : public Microsoft::WRL::RuntimeClass< + Microsoft::WRL::RuntimeClassFlags, + IDODownloadStatusCallback> + { + public: + DODownloadStatusCallback(IProgressCallback& progress) : + m_progress(progress) + { + } + + IFACEMETHOD(OnStatusChange)(IDODownload*, const DO_DOWNLOAD_STATUS* status) + { + { + std::lock_guard guard(m_statusMutex); + m_currentStatus = *status; + } + m_statusCV.notify_all(); + return S_OK; + } + + static HRESULT Create( + IProgressCallback& progress, + DODownloadStatusCallback** result) + { + Microsoft::WRL::ComPtr localResult = Microsoft::WRL::Make(progress); + RETURN_IF_NULL_ALLOC(localResult); + + *result = localResult.Detach(); + return S_OK; + } + + // Simply breaks the wait in Wait; the progress object must already be cancelled to force it out. + void Cancel() + { + m_statusCV.notify_all(); + } + + // Returns true on successful completion, false on cancellation, and throws on an error. + bool Wait() + { + std::unique_lock lock(m_statusMutex); + + // If there is no transfer status update for m_doNoProgressTimeout, we will fail. + auto timeoutTime = std::chrono::steady_clock::now() + Settings::User().Get(); + std::optional initialTransferAmount; + bool transferChange = false; + + while (!m_progress.IsCancelledBy(CancelReason::Any)) + { + if (!transferChange) + { + if (m_statusCV.wait_until(lock, timeoutTime) == std::cv_status::timeout) + { + THROW_HR(DO_E_DOWNLOAD_NO_PROGRESS); + } + } + else + { + m_statusCV.wait(lock); + } + + // Since we just finished a wait, check for cancellation before handling anything else + if (m_progress.IsCancelledBy(CancelReason::Any)) + { + return false; + } + + AICLI_LOG(Core, Verbose, << "DO State " << m_currentStatus.State << ", " << m_currentStatus.BytesTransferred << " / " << m_currentStatus.BytesTotal << + ", Error 0x" << Logging::SetHRFormat << m_currentStatus.Error << ", extended error 0x" << Logging::SetHRFormat << m_currentStatus.ExtendedError); + + // No matter the state, we are considering any error set to be a failure + if (FAILED(m_currentStatus.Error)) + { + AICLI_LOG(Core, Error, << "DeliveryOptimization error: 0x" << Logging::SetHRFormat << m_currentStatus.Error << + ", extended error: 0x" << Logging::SetHRFormat << m_currentStatus.ExtendedError); + THROW_HR(m_currentStatus.Error); + } + + switch (m_currentStatus.State) + { + // These states are ignored. + case DODownloadState_Created: + case DODownloadState_Paused: + break; + + case DODownloadState_Transferring: + if (m_currentStatus.BytesTransferred || m_currentStatus.BytesTotal) + { + m_progress.OnProgress(m_currentStatus.BytesTransferred, m_currentStatus.BytesTotal, ProgressType::Bytes); + } + + if (!initialTransferAmount) + { + initialTransferAmount = m_currentStatus.BytesTransferred; + } + else if (m_currentStatus.BytesTransferred != initialTransferAmount.value()) + { + transferChange = true; + } + break; + + // These are considered to be 'done' + case DODownloadState_Transferred: + case DODownloadState_Finalized: + if (m_currentStatus.BytesTransferred || m_currentStatus.BytesTotal) + { + m_progress.OnProgress(m_currentStatus.BytesTransferred, m_currentStatus.BytesTotal, ProgressType::Bytes); + } + return true; + + // This is the cancelled state + case DODownloadState_Aborted: + return false; + } + } + + return false; + } + + private: + IProgressCallback& m_progress; + std::mutex m_statusMutex; + std::condition_variable m_statusCV; + DO_DOWNLOAD_STATUS m_currentStatus = {}; + }; + } + + // Debugging tip: + // From an elevated PowerShell, run: + // > Get-DeliveryOptimizationLog | Set-Content doLogs.txt + DownloadResult DODownload( + const std::string& url, + const std::filesystem::path& dest, + IProgressCallback& progress, + std::optional info) + { + AICLI_LOG(Core, Info, << "DeliveryOptimization downloading from url: " << url); + + // Remove the target file since DO will not overwrite + std::filesystem::remove(dest); + + DeliveryOptimization::Manager manager; + DeliveryOptimization::Download download = manager.CreateDownload(); + + wil::com_ptr callback; + THROW_IF_FAILED(DeliveryOptimization::DODownloadStatusCallback::Create(progress, &callback)); + + download.Uri(url); + download.ForegroundPriority(true); + download.LocalPath(dest); + download.CallbackInterface(callback.get()); + + if (info) + { + if (!info->DisplayName.empty()) + { + download.DisplayName(info->DisplayName); + } + + if (!info->ContentId.empty()) + { + download.ContentId(info->ContentId); + } + + if (!info->RequestHeaders.empty()) + { + download.CustomHeaders(info->RequestHeaders); + } + } + + download.Start(); + + auto cancelLifetime = progress.SetCancellationFunction([&download, &callback]() + { + AICLI_LOG(Core, Info, << "Download cancelled."); + download.Cancel(); + callback->Cancel(); + }); + + // Check to handle cancellation between Start and SetCancellationFunction + if (progress.IsCancelledBy(CancelReason::Any)) + { + AICLI_LOG(Core, Info, << "Download cancelled."); + download.Cancel(); + return {}; + } + + // Wait returns true for success, false for cancellation, and throws on error. + if (callback->Wait()) + { + // Grab the headers so that we can use them later + std::optional responseHeaders = download.TryGetProperty(DODownloadProperty_HttpResponseHeaders); + + // Finalize is required to flush the data and change the file name. + download.Finalize(); + AICLI_LOG(Core, Info, << "Download completed."); + + std::ifstream inStream{ dest, std::ifstream::binary }; + auto hashDetails = SHA256::ComputeHashDetails(inStream); + + DownloadResult result; + result.Sha256Hash = std::move(hashDetails.Hash); + result.SizeInBytes = hashDetails.SizeInBytes; + result.ContentType = ExtractContentType(responseHeaders); + + return result; + } + + return {}; + } + + bool IsDOErrorFatal(HRESULT error) + { + // If this gets to be large, store in a sorted array and binary search on it. + // There will be more to update here, which we should be able to discover through telemetry. + return + error == DO_E_BLOCKED_BY_COST_TRANSFER_POLICY || + error == DO_E_BLOCKED_BY_CELLULAR_POLICY || + error == DO_E_BLOCKED_BY_POWER_STATE || + error == DO_E_BLOCKED_BY_NO_NETWORK; + } +} diff --git a/src/AppInstallerCommonCore/DODownloader.h b/src/AppInstallerCommonCore/DODownloader.h index a5186a4e55..95f2451c6a 100644 --- a/src/AppInstallerCommonCore/DODownloader.h +++ b/src/AppInstallerCommonCore/DODownloader.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include -#include -#include -#include - -namespace AppInstaller::Utility -{ - // Downloads a file from the given URL and places it in the given location. - // url: The url to be downloaded from. http->https redirection is allowed. - // dest: The stream to be downloaded to. - // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. - DownloadResult DODownload( - const std::string& url, - const std::filesystem::path& dest, - IProgressCallback& progress, - std::optional info); - - // Returns true if the error from DODownload should be treated as fatal; - // false if we should be able to fall back to other download methods. - bool IsDOErrorFatal(HRESULT error); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include +#include +#include +#include + +namespace AppInstaller::Utility +{ + // Downloads a file from the given URL and places it in the given location. + // url: The url to be downloaded from. http->https redirection is allowed. + // dest: The stream to be downloaded to. + // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. + DownloadResult DODownload( + const std::string& url, + const std::filesystem::path& dest, + IProgressCallback& progress, + std::optional info); + + // Returns true if the error from DODownload should be treated as fatal; + // false if we should be able to fall back to other download methods. + bool IsDOErrorFatal(HRESULT error); +} diff --git a/src/AppInstallerCommonCore/Debugging.cpp b/src/AppInstallerCommonCore/Debugging.cpp index fddac4b3d7..a058a2dc07 100644 --- a/src/AppInstallerCommonCore/Debugging.cpp +++ b/src/AppInstallerCommonCore/Debugging.cpp @@ -1,104 +1,104 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/Debugging.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerDateTime.h" - -namespace AppInstaller::Debugging -{ - namespace - { - constexpr std::string_view c_minidumpPrefix = "Minidump"; - constexpr std::string_view c_minidumpExtension = ".mdmp"; - - struct SelfInitiatedMinidumpHelper - { - SelfInitiatedMinidumpHelper() = default; - - ~SelfInitiatedMinidumpHelper() - { - if (!m_keepFile) - { - m_file.reset(); - DeleteFile(m_filePath.wstring().c_str()); - } - } - - static SelfInitiatedMinidumpHelper& Instance() - { - static SelfInitiatedMinidumpHelper instance; - return instance; - } - - static LONG WINAPI UnhandledExceptionCallback(EXCEPTION_POINTERS* ExceptionInfo) - { - MINIDUMP_EXCEPTION_INFORMATION exceptionInformation{}; - // The unhandled exception filter is executed in the context of the failing thread. - exceptionInformation.ThreadId = GetCurrentThreadId(); - exceptionInformation.ExceptionPointers = ExceptionInfo; - exceptionInformation.ClientPointers = FALSE; - - std::thread([&]() { - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), Instance().m_file.get(), MiniDumpNormal, &exceptionInformation, nullptr, nullptr); - Instance().m_keepFile = true; - }).join(); - - return EXCEPTION_CONTINUE_SEARCH; - } - - SelfInitiatedMinidumpHelper& Enable(const std::filesystem::path& filePath = {}) - { - std::call_once(m_enableFlag, [&]() - { - if (filePath.empty()) - { - m_filePath = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); - m_filePath /= c_minidumpPrefix.data() + ('-' + Utility::GetCurrentTimeForFilename() + c_minidumpExtension.data()); - } - else - { - m_filePath = filePath; - } - - m_file.reset(CreateFile(m_filePath.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); - THROW_LAST_ERROR_IF(!m_file); - - SetUnhandledExceptionFilter(UnhandledExceptionCallback); - }); - - return *this; - } - - void WriteMinidump() - { - std::thread([&]() { - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), Instance().m_file.get(), MiniDumpNormal, nullptr, nullptr, nullptr); - Instance().m_keepFile = true; - }).join(); - } - - private: - std::once_flag m_enableFlag; - std::filesystem::path m_filePath; - wil::unique_handle m_file; - std::atomic_bool m_keepFile{ false }; - }; - } - - void EnableSelfInitiatedMinidump() - { - SelfInitiatedMinidumpHelper::Instance().Enable(); - } - - void EnableSelfInitiatedMinidump(const std::filesystem::path& filePath) - { - SelfInitiatedMinidumpHelper::Instance().Enable(filePath); - } - - void WriteMinidump() - { - SelfInitiatedMinidumpHelper::Instance().Enable().WriteMinidump(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Debugging.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerDateTime.h" + +namespace AppInstaller::Debugging +{ + namespace + { + constexpr std::string_view c_minidumpPrefix = "Minidump"; + constexpr std::string_view c_minidumpExtension = ".mdmp"; + + struct SelfInitiatedMinidumpHelper + { + SelfInitiatedMinidumpHelper() = default; + + ~SelfInitiatedMinidumpHelper() + { + if (!m_keepFile) + { + m_file.reset(); + DeleteFile(m_filePath.wstring().c_str()); + } + } + + static SelfInitiatedMinidumpHelper& Instance() + { + static SelfInitiatedMinidumpHelper instance; + return instance; + } + + static LONG WINAPI UnhandledExceptionCallback(EXCEPTION_POINTERS* ExceptionInfo) + { + MINIDUMP_EXCEPTION_INFORMATION exceptionInformation{}; + // The unhandled exception filter is executed in the context of the failing thread. + exceptionInformation.ThreadId = GetCurrentThreadId(); + exceptionInformation.ExceptionPointers = ExceptionInfo; + exceptionInformation.ClientPointers = FALSE; + + std::thread([&]() { + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), Instance().m_file.get(), MiniDumpNormal, &exceptionInformation, nullptr, nullptr); + Instance().m_keepFile = true; + }).join(); + + return EXCEPTION_CONTINUE_SEARCH; + } + + SelfInitiatedMinidumpHelper& Enable(const std::filesystem::path& filePath = {}) + { + std::call_once(m_enableFlag, [&]() + { + if (filePath.empty()) + { + m_filePath = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); + m_filePath /= c_minidumpPrefix.data() + ('-' + Utility::GetCurrentTimeForFilename() + c_minidumpExtension.data()); + } + else + { + m_filePath = filePath; + } + + m_file.reset(CreateFile(m_filePath.wstring().c_str(), GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!m_file); + + SetUnhandledExceptionFilter(UnhandledExceptionCallback); + }); + + return *this; + } + + void WriteMinidump() + { + std::thread([&]() { + MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), Instance().m_file.get(), MiniDumpNormal, nullptr, nullptr, nullptr); + Instance().m_keepFile = true; + }).join(); + } + + private: + std::once_flag m_enableFlag; + std::filesystem::path m_filePath; + wil::unique_handle m_file; + std::atomic_bool m_keepFile{ false }; + }; + } + + void EnableSelfInitiatedMinidump() + { + SelfInitiatedMinidumpHelper::Instance().Enable(); + } + + void EnableSelfInitiatedMinidump(const std::filesystem::path& filePath) + { + SelfInitiatedMinidumpHelper::Instance().Enable(filePath); + } + + void WriteMinidump() + { + SelfInitiatedMinidumpHelper::Instance().Enable().WriteMinidump(); + } +} diff --git a/src/AppInstallerCommonCore/Deployment.cpp b/src/AppInstallerCommonCore/Deployment.cpp index e37480f208..4b054ff742 100644 --- a/src/AppInstallerCommonCore/Deployment.cpp +++ b/src/AppInstallerCommonCore/Deployment.cpp @@ -1,415 +1,415 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerDeployment.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerMsixInfo.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerStrings.h" - -namespace AppInstaller::Deployment -{ - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Management::Deployment; - - namespace - { - size_t GetDeploymentOperationId() - { - static std::atomic_size_t s_deploymentId = 0; - return s_deploymentId.fetch_add(1); - } - - HRESULT WaitForDeployment( - IAsyncOperationWithProgress& deployOperation, - size_t id, - IProgressCallback& callback, - bool throwOnError = true) - { - AICLI_LOG(Core, Info, << "Begin waiting for operation #" << id); - - AsyncOperationProgressHandler progressCallback( - [&callback](const IAsyncOperationWithProgress&, DeploymentProgress progress) - { - callback.OnProgress(progress.percentage, 100, ProgressType::Percent); - } - ); - - // Set progress callback. - deployOperation.Progress(progressCallback); - - auto removeCancel = callback.SetCancellationFunction([&]() { deployOperation.Cancel(); }); - - AICLI_LOG(Core, Info, << "Begin blocking for operation #" << id); - - auto deployResult = deployOperation.get(); - - if (!SUCCEEDED(deployResult.ExtendedErrorCode())) - { - AICLI_LOG(Core, Error, << "Deployment operation #" << id << ": " << Utility::ConvertToUTF8(deployResult.ErrorText())); - - // Note that while the format string is char*, it gets converted to wchar before being used. - if (throwOnError) - { - THROW_HR_MSG(deployResult.ExtendedErrorCode(), "Operation failed: %ws", deployResult.ErrorText().c_str()); - } - else - { - // Simple return because this path is generally used for recovery cases - return deployResult.ExtendedErrorCode(); - } - } - else - { - AICLI_LOG(Core, Info, << "Successfully completed #" << id); - } - - return S_OK; - } - - bool ShouldUseReputationCheck(const Options& options) - { - return options.ExpectedDigests.empty() && !options.SkipReputationCheck; - } - - IAsyncOperationWithProgress StartAddPackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options) - { - if (!options.ExpectedDigests.empty()) - { - // Must use API that supports digests - THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported()); - - AddPackageOptions addPackageOptions; - - for (const auto& digest : options.ExpectedDigests) - { - addPackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second); - } - - return packageManager.AddPackageByUriAsync(uri, addPackageOptions); - } - else if (options.SkipReputationCheck) - { - return packageManager.AddPackageAsync( - uri, - nullptr, /*dependencyPackageUris*/ - DeploymentOptions::None, - nullptr, /*targetVolume*/ - nullptr, /*optionalAndRelatedPackageFamilyNames*/ - nullptr, /*optionalPackageUris*/ - nullptr /*relatedPackageUris*/); - } - else - { - return packageManager.RequestAddPackageAsync( - uri, - nullptr, /*dependencyPackageUris*/ - DeploymentOptions::None, - nullptr, /*targetVolume*/ - nullptr, /*optionalAndRelatedPackageFamilyNames*/ - nullptr /*relatedPackageUris*/); - } - } - - IAsyncOperationWithProgress StartStagePackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options) - { - if (!options.ExpectedDigests.empty()) - { - // Must use API that supports digests - THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported()); - - StagePackageOptions stagePackageOptions; - - for (const auto& digest : options.ExpectedDigests) - { - stagePackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second); - } - - return packageManager.StagePackageByUriAsync(uri, stagePackageOptions); - } - else - { - return packageManager.StagePackageAsync( - uri, - nullptr /*dependencyPackageUris*/); - } - } - } - - std::ostream& operator<<(std::ostream& out, const Options& options) - { - out << " { SkipReputationCheck = " << options.SkipReputationCheck << ", ExpectedDigests = {"; - - for (const auto& digest : options.ExpectedDigests) - { - out << " { URI = " << digest.first << ", Digest = " << Utility::ConvertToUTF8(digest.second) << " } "; - } - - out << "} }"; - - return out; - } - - HRESULT WaitForDeployment( - IAsyncOperationWithProgress& deployOperation, - IProgressCallback& callback, - bool throwOnError) - { - return WaitForDeployment(deployOperation, GetDeploymentOperationId(), callback, throwOnError); - } - - void AddPackage( - const winrt::Windows::Foundation::Uri& uri, - const Options& options, - IProgressCallback& callback) - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting AddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str()) << " Options: " << options); - - PackageManager packageManager; - - IAsyncOperationWithProgress deployOperation = StartAddPackage(packageManager, uri, options); - - WaitForDeployment(deployOperation, id, callback); - } - - bool AddPackageWithDeferredFallback( - std::string_view uri, - const Options& options, - IProgressCallback& callback) - { - PackageManager packageManager; - - // In the event of a failure we want to ensure that the package is not left on the system. - // No need for proxy as Deployment won't use it anyways. - Msix::MsixInfo packageInfo{ uri }; - std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); - std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); - auto removePackage = wil::scope_exit([&]() { - try - { - ProgressCallback cb; - RemovePackage(packageFullName, RemovalOptions::None, cb); - } - CATCH_LOG(); - }); - - Uri uriObject(Utility::ConvertToUTF16(uri)); - - if (ShouldUseReputationCheck(options)) - { - // The only way to get SmartScreen is to use RequestAddPackageAsync, so we will have to start with that. - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting RequestAddPackageAsync operation #" << id << ": " << uri); - - DeploymentOptions deploymentOptions = DeploymentOptions::None; - // Optimization to keep files if the package is in use. Only available in a newer OS per: - // https://docs.microsoft.com/en-us/uwp/api/Windows.Management.Deployment.DeploymentOptions - if (Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.18362.0" })) - { - deploymentOptions = DeploymentOptions::RetainFilesOnFailure; - } - - IAsyncOperationWithProgress deployOperation = packageManager.RequestAddPackageAsync( - uriObject, - nullptr, /*dependencyPackageUris*/ - deploymentOptions, - nullptr, /*targetVolume*/ - nullptr, /*optionalAndRelatedPackageFamilyNames*/ - nullptr /*relatedPackageUris*/); - - HRESULT hr = WaitForDeployment(deployOperation, id, callback, false); - - if (SUCCEEDED(hr)) - { - removePackage.release(); - return false; - } - - THROW_HR_IF(hr, FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE)); - } - - // If we are skipping SmartScreen or the package was in use, stage then register the package. - PartialPercentProgressCallback progress{ callback, 100 }; - progress.SetRange(0, 95); - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options); - - IAsyncOperationWithProgress stageOperation = StartStagePackage(packageManager, uriObject, options); - WaitForDeployment(stageOperation, id, progress); - } - - bool registrationDeferred = false; - progress.SetRange(95, 100); - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName); - - IAsyncOperationWithProgress registerOperation = - packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None); - HRESULT hr = WaitForDeployment(registerOperation, id, progress, false); - - if (hr == HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE)) - { - registrationDeferred = true; - } - else - { - THROW_IF_FAILED(hr); - } - } - - removePackage.release(); - return registrationDeferred; - } - - void RemovePackage( - std::string_view packageFullName, - RemovalOptions options, - IProgressCallback& callback) - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting RemovePackage operation #" << id << ": " << packageFullName); - - PackageManager packageManager; - winrt::hstring fullName = Utility::ConvertToUTF16(packageFullName).c_str(); - auto deployOperation = packageManager.RemovePackageAsync(fullName, options); - - WaitForDeployment(deployOperation, id, callback); - } - - bool AddPackageMachineScope( - std::string_view uri, - const Options& options, - IProgressCallback& callback) - { - PackageManager packageManager; - - // In the event of a failure we want to ensure that the package is not left on the system. - // No need for proxy as Deployment won't use it anyways. - Msix::MsixInfo packageInfo{ uri }; - std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); - std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); - std::string packageFamilyName = Msix::GetPackageFamilyNameFromFullName(packageFullName); - auto removePackage = wil::scope_exit([&]() { - try - { - ProgressCallback cb; - RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, cb); - } - CATCH_LOG(); - }); - - Uri uriObject(Utility::ConvertToUTF16(uri)); - PartialPercentProgressCallback progress{ callback, 100 }; - - // First stage package contents - progress.SetRange(0, 90); - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options); - - IAsyncOperationWithProgress stageOperation = StartStagePackage(packageManager, uriObject, options); - WaitForDeployment(stageOperation, id, progress); - } - - // Provision for all users - progress.SetRange(90, 95); - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting ProvisionPackage operation #" << id << ": " << packageFamilyName); - - winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str(); - auto deployOperation = packageManager.ProvisionPackageForAllUsersAsync(familyName); - - WaitForDeployment(deployOperation, id, progress); - } - - // Try registration as best effort, operation is considered successful as long as provisioning is successful. - progress.SetRange(95, 100); - bool registrationDeferred = false; - if (Runtime::IsRunningAsSystem()) - { - // Packages cannot be registered under local system, just return registration deferred - registrationDeferred = true; - } - else - { - try - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName); - - IAsyncOperationWithProgress registerOperation = - packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None); - WaitForDeployment(registerOperation, id, progress); - } - catch (...) - { - registrationDeferred = true; - } - } - - progress.OnProgress(100, 100, ProgressType::Percent); - removePackage.release(); - return registrationDeferred; - } - - void RemovePackageMachineScope( - std::string_view packageFamilyName, - std::string_view packageFullName, - IProgressCallback& callback) - { - PartialPercentProgressCallback progress{ callback, 100 }; - - // Deprovision first - progress.SetRange(0, 5); - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting DeprovisionPackage operation #" << id << ": " << packageFamilyName); - - PackageManager packageManager; - winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str(); - auto deployOperation = packageManager.DeprovisionPackageForAllUsersAsync(familyName); - - WaitForDeployment(deployOperation, id, progress); - } - - // Remove for all users - progress.SetRange(5, 100); - { - RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, progress); - } - } - - bool IsRegistered(std::string_view packageFamilyName) - { - std::wstring wideFamilyName = Utility::ConvertToUTF16(packageFamilyName); - - PackageManager packageManager; - auto packages = packageManager.FindPackagesForUser({}, wideFamilyName); - - return packages.begin() != packages.end(); - } - - void RegisterPackage( - std::string_view packageFamilyName, - IProgressCallback& callback) - { - size_t id = GetDeploymentOperationId(); - AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFamilyName); - - PackageManager packageManager; - winrt::hstring packageFamilyNameWide = Utility::ConvertToUTF16(packageFamilyName).c_str(); - auto deployOperation = packageManager.RegisterPackageByFamilyNameAsync(packageFamilyNameWide, nullptr, DeploymentOptions::None, nullptr, nullptr); - - WaitForDeployment(deployOperation, id, callback); - } - - bool IsExpectedDigestsSupported() - { - static bool s_IsExpectedDigestsSupported = Metadata::ApiInformation::IsPropertyPresent(winrt::name_of(), L"ExpectedDigests"); - return s_IsExpectedDigestsSupported; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerDeployment.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerMsixInfo.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerStrings.h" + +namespace AppInstaller::Deployment +{ + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Management::Deployment; + + namespace + { + size_t GetDeploymentOperationId() + { + static std::atomic_size_t s_deploymentId = 0; + return s_deploymentId.fetch_add(1); + } + + HRESULT WaitForDeployment( + IAsyncOperationWithProgress& deployOperation, + size_t id, + IProgressCallback& callback, + bool throwOnError = true) + { + AICLI_LOG(Core, Info, << "Begin waiting for operation #" << id); + + AsyncOperationProgressHandler progressCallback( + [&callback](const IAsyncOperationWithProgress&, DeploymentProgress progress) + { + callback.OnProgress(progress.percentage, 100, ProgressType::Percent); + } + ); + + // Set progress callback. + deployOperation.Progress(progressCallback); + + auto removeCancel = callback.SetCancellationFunction([&]() { deployOperation.Cancel(); }); + + AICLI_LOG(Core, Info, << "Begin blocking for operation #" << id); + + auto deployResult = deployOperation.get(); + + if (!SUCCEEDED(deployResult.ExtendedErrorCode())) + { + AICLI_LOG(Core, Error, << "Deployment operation #" << id << ": " << Utility::ConvertToUTF8(deployResult.ErrorText())); + + // Note that while the format string is char*, it gets converted to wchar before being used. + if (throwOnError) + { + THROW_HR_MSG(deployResult.ExtendedErrorCode(), "Operation failed: %ws", deployResult.ErrorText().c_str()); + } + else + { + // Simple return because this path is generally used for recovery cases + return deployResult.ExtendedErrorCode(); + } + } + else + { + AICLI_LOG(Core, Info, << "Successfully completed #" << id); + } + + return S_OK; + } + + bool ShouldUseReputationCheck(const Options& options) + { + return options.ExpectedDigests.empty() && !options.SkipReputationCheck; + } + + IAsyncOperationWithProgress StartAddPackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options) + { + if (!options.ExpectedDigests.empty()) + { + // Must use API that supports digests + THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported()); + + AddPackageOptions addPackageOptions; + + for (const auto& digest : options.ExpectedDigests) + { + addPackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second); + } + + return packageManager.AddPackageByUriAsync(uri, addPackageOptions); + } + else if (options.SkipReputationCheck) + { + return packageManager.AddPackageAsync( + uri, + nullptr, /*dependencyPackageUris*/ + DeploymentOptions::None, + nullptr, /*targetVolume*/ + nullptr, /*optionalAndRelatedPackageFamilyNames*/ + nullptr, /*optionalPackageUris*/ + nullptr /*relatedPackageUris*/); + } + else + { + return packageManager.RequestAddPackageAsync( + uri, + nullptr, /*dependencyPackageUris*/ + DeploymentOptions::None, + nullptr, /*targetVolume*/ + nullptr, /*optionalAndRelatedPackageFamilyNames*/ + nullptr /*relatedPackageUris*/); + } + } + + IAsyncOperationWithProgress StartStagePackage(PackageManager& packageManager, const winrt::Windows::Foundation::Uri& uri, const Options& options) + { + if (!options.ExpectedDigests.empty()) + { + // Must use API that supports digests + THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !IsExpectedDigestsSupported()); + + StagePackageOptions stagePackageOptions; + + for (const auto& digest : options.ExpectedDigests) + { + stagePackageOptions.ExpectedDigests().Insert(Uri{ Utility::ConvertToUTF16(digest.first) }, digest.second); + } + + return packageManager.StagePackageByUriAsync(uri, stagePackageOptions); + } + else + { + return packageManager.StagePackageAsync( + uri, + nullptr /*dependencyPackageUris*/); + } + } + } + + std::ostream& operator<<(std::ostream& out, const Options& options) + { + out << " { SkipReputationCheck = " << options.SkipReputationCheck << ", ExpectedDigests = {"; + + for (const auto& digest : options.ExpectedDigests) + { + out << " { URI = " << digest.first << ", Digest = " << Utility::ConvertToUTF8(digest.second) << " } "; + } + + out << "} }"; + + return out; + } + + HRESULT WaitForDeployment( + IAsyncOperationWithProgress& deployOperation, + IProgressCallback& callback, + bool throwOnError) + { + return WaitForDeployment(deployOperation, GetDeploymentOperationId(), callback, throwOnError); + } + + void AddPackage( + const winrt::Windows::Foundation::Uri& uri, + const Options& options, + IProgressCallback& callback) + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting AddPackage operation #" << id << ": " << Utility::ConvertToUTF8(uri.AbsoluteUri().c_str()) << " Options: " << options); + + PackageManager packageManager; + + IAsyncOperationWithProgress deployOperation = StartAddPackage(packageManager, uri, options); + + WaitForDeployment(deployOperation, id, callback); + } + + bool AddPackageWithDeferredFallback( + std::string_view uri, + const Options& options, + IProgressCallback& callback) + { + PackageManager packageManager; + + // In the event of a failure we want to ensure that the package is not left on the system. + // No need for proxy as Deployment won't use it anyways. + Msix::MsixInfo packageInfo{ uri }; + std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); + std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); + auto removePackage = wil::scope_exit([&]() { + try + { + ProgressCallback cb; + RemovePackage(packageFullName, RemovalOptions::None, cb); + } + CATCH_LOG(); + }); + + Uri uriObject(Utility::ConvertToUTF16(uri)); + + if (ShouldUseReputationCheck(options)) + { + // The only way to get SmartScreen is to use RequestAddPackageAsync, so we will have to start with that. + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RequestAddPackageAsync operation #" << id << ": " << uri); + + DeploymentOptions deploymentOptions = DeploymentOptions::None; + // Optimization to keep files if the package is in use. Only available in a newer OS per: + // https://docs.microsoft.com/en-us/uwp/api/Windows.Management.Deployment.DeploymentOptions + if (Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.18362.0" })) + { + deploymentOptions = DeploymentOptions::RetainFilesOnFailure; + } + + IAsyncOperationWithProgress deployOperation = packageManager.RequestAddPackageAsync( + uriObject, + nullptr, /*dependencyPackageUris*/ + deploymentOptions, + nullptr, /*targetVolume*/ + nullptr, /*optionalAndRelatedPackageFamilyNames*/ + nullptr /*relatedPackageUris*/); + + HRESULT hr = WaitForDeployment(deployOperation, id, callback, false); + + if (SUCCEEDED(hr)) + { + removePackage.release(); + return false; + } + + THROW_HR_IF(hr, FAILED(hr) && hr != HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE)); + } + + // If we are skipping SmartScreen or the package was in use, stage then register the package. + PartialPercentProgressCallback progress{ callback, 100 }; + progress.SetRange(0, 95); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options); + + IAsyncOperationWithProgress stageOperation = StartStagePackage(packageManager, uriObject, options); + WaitForDeployment(stageOperation, id, progress); + } + + bool registrationDeferred = false; + progress.SetRange(95, 100); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName); + + IAsyncOperationWithProgress registerOperation = + packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None); + HRESULT hr = WaitForDeployment(registerOperation, id, progress, false); + + if (hr == HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE)) + { + registrationDeferred = true; + } + else + { + THROW_IF_FAILED(hr); + } + } + + removePackage.release(); + return registrationDeferred; + } + + void RemovePackage( + std::string_view packageFullName, + RemovalOptions options, + IProgressCallback& callback) + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RemovePackage operation #" << id << ": " << packageFullName); + + PackageManager packageManager; + winrt::hstring fullName = Utility::ConvertToUTF16(packageFullName).c_str(); + auto deployOperation = packageManager.RemovePackageAsync(fullName, options); + + WaitForDeployment(deployOperation, id, callback); + } + + bool AddPackageMachineScope( + std::string_view uri, + const Options& options, + IProgressCallback& callback) + { + PackageManager packageManager; + + // In the event of a failure we want to ensure that the package is not left on the system. + // No need for proxy as Deployment won't use it anyways. + Msix::MsixInfo packageInfo{ uri }; + std::wstring packageFullNameWide = packageInfo.GetPackageFullNameWide(); + std::string packageFullName = Utility::ConvertToUTF8(packageFullNameWide); + std::string packageFamilyName = Msix::GetPackageFamilyNameFromFullName(packageFullName); + auto removePackage = wil::scope_exit([&]() { + try + { + ProgressCallback cb; + RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, cb); + } + CATCH_LOG(); + }); + + Uri uriObject(Utility::ConvertToUTF16(uri)); + PartialPercentProgressCallback progress{ callback, 100 }; + + // First stage package contents + progress.SetRange(0, 90); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting StagePackageAsync operation #" << id << ": " << uri << " Options: " << options); + + IAsyncOperationWithProgress stageOperation = StartStagePackage(packageManager, uriObject, options); + WaitForDeployment(stageOperation, id, progress); + } + + // Provision for all users + progress.SetRange(90, 95); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting ProvisionPackage operation #" << id << ": " << packageFamilyName); + + winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str(); + auto deployOperation = packageManager.ProvisionPackageForAllUsersAsync(familyName); + + WaitForDeployment(deployOperation, id, progress); + } + + // Try registration as best effort, operation is considered successful as long as provisioning is successful. + progress.SetRange(95, 100); + bool registrationDeferred = false; + if (Runtime::IsRunningAsSystem()) + { + // Packages cannot be registered under local system, just return registration deferred + registrationDeferred = true; + } + else + { + try + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFullName); + + IAsyncOperationWithProgress registerOperation = + packageManager.RegisterPackageByFullNameAsync(packageFullNameWide, nullptr, DeploymentOptions::None); + WaitForDeployment(registerOperation, id, progress); + } + catch (...) + { + registrationDeferred = true; + } + } + + progress.OnProgress(100, 100, ProgressType::Percent); + removePackage.release(); + return registrationDeferred; + } + + void RemovePackageMachineScope( + std::string_view packageFamilyName, + std::string_view packageFullName, + IProgressCallback& callback) + { + PartialPercentProgressCallback progress{ callback, 100 }; + + // Deprovision first + progress.SetRange(0, 5); + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting DeprovisionPackage operation #" << id << ": " << packageFamilyName); + + PackageManager packageManager; + winrt::hstring familyName = Utility::ConvertToUTF16(packageFamilyName).c_str(); + auto deployOperation = packageManager.DeprovisionPackageForAllUsersAsync(familyName); + + WaitForDeployment(deployOperation, id, progress); + } + + // Remove for all users + progress.SetRange(5, 100); + { + RemovePackage(packageFullName, RemovalOptions::RemoveForAllUsers, progress); + } + } + + bool IsRegistered(std::string_view packageFamilyName) + { + std::wstring wideFamilyName = Utility::ConvertToUTF16(packageFamilyName); + + PackageManager packageManager; + auto packages = packageManager.FindPackagesForUser({}, wideFamilyName); + + return packages.begin() != packages.end(); + } + + void RegisterPackage( + std::string_view packageFamilyName, + IProgressCallback& callback) + { + size_t id = GetDeploymentOperationId(); + AICLI_LOG(Core, Info, << "Starting RegisterPackageByFullNameAsync operation #" << id << ": " << packageFamilyName); + + PackageManager packageManager; + winrt::hstring packageFamilyNameWide = Utility::ConvertToUTF16(packageFamilyName).c_str(); + auto deployOperation = packageManager.RegisterPackageByFamilyNameAsync(packageFamilyNameWide, nullptr, DeploymentOptions::None, nullptr, nullptr); + + WaitForDeployment(deployOperation, id, callback); + } + + bool IsExpectedDigestsSupported() + { + static bool s_IsExpectedDigestsSupported = Metadata::ApiInformation::IsPropertyPresent(winrt::name_of(), L"ExpectedDigests"); + return s_IsExpectedDigestsSupported; + } +} diff --git a/src/AppInstallerCommonCore/Downloader.cpp b/src/AppInstallerCommonCore/Downloader.cpp index 77c02927ca..49d55c4d2e 100644 --- a/src/AppInstallerCommonCore/Downloader.cpp +++ b/src/AppInstallerCommonCore/Downloader.cpp @@ -1,690 +1,690 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include "Public/AppInstallerErrors.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerDownloader.h" -#include "Public/AppInstallerSHA256.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerTelemetry.h" -#include "Public/winget/UserSettings.h" -#include "Public/winget/NetworkSettings.h" -#include "Public/winget/Filesystem.h" -#include "DODownloader.h" -#include "HttpStream/HttpRandomAccessStream.h" -#include "Public/winget/ThreadGlobals.h" - -using namespace AppInstaller::Runtime; -using namespace AppInstaller::Settings; -using namespace AppInstaller::Filesystem; -using namespace AppInstaller::Utility::HttpStream; -using namespace winrt::Windows::Web::Http; -using namespace winrt::Windows::Web::Http::Headers; -using namespace winrt::Windows::Web::Http::Filters; - -namespace AppInstaller::Utility -{ - namespace - { - std::wstring GetHttpQueryString(const wil::unique_hinternet& urlFile, DWORD queryProperty) - { - std::wstring result = {}; - DWORD length = 0; - if (!HttpQueryInfoW(urlFile.get(), - queryProperty, - &result[0], - &length, - nullptr)) - { - auto lastError = GetLastError(); - if (lastError == ERROR_INSUFFICIENT_BUFFER) - { - // lpdwBufferLength contains the size, in bytes, of a buffer large enough to receive the requested information - // without the nul char. not the exact buffer size. - auto size = static_cast(length) / sizeof(wchar_t); - result.resize(size + 1); - if (HttpQueryInfoW(urlFile.get(), - queryProperty, - &result[0], - &length, - nullptr)) - { - // because the buffer can be bigger remove possible null chars - result.erase(result.find(L'\0')); - } - else - { - AICLI_LOG(Core, Error, << "Error retrieving header value [" << queryProperty << "]: " << GetLastError()); - result.clear(); - } - } - else - { - AICLI_LOG(Core, Error, << "Error retrieving header [" << queryProperty << "]: " << GetLastError()); - } - } - - return result; - } - - // Gets the retry after value in terms of a delay in seconds - std::chrono::seconds GetRetryAfter(const HttpDateOrDeltaHeaderValue& retryAfter) - { - if (retryAfter) - { - auto delta = retryAfter.Delta(); - if (delta) - { - return std::chrono::duration_cast(delta.GetTimeSpan()); - } - - auto dateTimeRef = retryAfter.Date(); - if (dateTimeRef) - { - auto dateTime = dateTimeRef.GetDateTime(); - auto now = winrt::clock::now(); - - if (dateTime > now) - { - return std::chrono::duration_cast(dateTime - now); - } - } - } - - return 0s; - } - - std::chrono::seconds GetRetryAfter(const wil::unique_hinternet& urlFile) - { - std::wstring retryAfter = GetHttpQueryString(urlFile, HTTP_QUERY_RETRY_AFTER); - return retryAfter.empty() ? 0s : AppInstaller::Utility::GetRetryAfter(retryAfter); - } - } - -#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, - std::optional info) - { - // For AICLI_LOG usages with string literals. - #pragma warning(push) - #pragma warning(disable:26449) - - AICLI_LOG(Core, Info, << "WinINet downloading from url: " << url); - - auto agentWide = Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get()); - wil::unique_hinternet session; - - const auto& proxyUri = Network().GetProxyUri(); - if (proxyUri) - { - AICLI_LOG(Core, Info, << "Using proxy " << proxyUri.value()); - session.reset(InternetOpen( - agentWide.c_str(), - INTERNET_OPEN_TYPE_PROXY, - Utility::ConvertToUTF16(proxyUri.value()).c_str(), - NULL, - 0)); - } - else - { - session.reset(InternetOpen( - agentWide.c_str(), - INTERNET_OPEN_TYPE_PRECONFIG, - NULL, - NULL, - 0)); - } - - 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(), - 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."); - - // Check http return status - DWORD requestStatus = 0; - DWORD cbRequestStatus = sizeof(requestStatus); - - THROW_LAST_ERROR_IF_MSG(!HttpQueryInfoW(urlFile.get(), - HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, - &requestStatus, - &cbRequestStatus, - nullptr), "Query download request status failed."); - - constexpr DWORD TooManyRequest = 429; - - switch (requestStatus) - { - case HTTP_STATUS_OK: - // All good - break; - case TooManyRequest: - case HTTP_STATUS_SERVICE_UNAVAIL: - { - THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(urlFile))); - } - default: - AICLI_LOG(Core, Error, << "Download request failed. Returned status: " << requestStatus); - THROW_HR_MSG(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, requestStatus), "Download request status is not success."); - } - - AICLI_LOG(Core, Verbose, << "Download request status success."); - - // Get content length. Don't fail the download if failed. - LONGLONG contentLength = 0; - DWORD cbContentLength = sizeof(contentLength); - - HttpQueryInfoW( - urlFile.get(), - HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER64, - &contentLength, - &cbContentLength, - nullptr); - AICLI_LOG(Core, Verbose, << "Download size: " << contentLength); - - std::string contentType = Utility::ConvertToUTF8(GetHttpQueryString(urlFile, HTTP_QUERY_CONTENT_TYPE)); - AICLI_LOG(Core, Verbose, << "Content Type: " << contentType); - - // Setup hash engine - SHA256 hashEngine; - - const int bufferSize = 1024 * 1024; // 1MB - auto buffer = std::make_unique(bufferSize); - - BOOL readSuccess = true; - DWORD bytesRead = 0; - LONGLONG bytesDownloaded = 0; - - do - { - if (progress.IsCancelledBy(CancelReason::Any)) - { - AICLI_LOG(Core, Info, << "Download cancelled."); - return {}; - } - - readSuccess = InternetReadFile(urlFile.get(), buffer.get(), bufferSize, &bytesRead); - - THROW_LAST_ERROR_IF_MSG(!readSuccess, "InternetReadFile() failed."); - - hashEngine.Add(buffer.get(), bytesRead); - - dest.write((char*)buffer.get(), bytesRead); - - bytesDownloaded += bytesRead; - - if (bytesRead != 0) - { - progress.OnProgress(bytesDownloaded, contentLength, ProgressType::Bytes); - } - - } while (bytesRead != 0); - - dest.flush(); - - // Check download size matches if content length is provided in response header - if (contentLength > 0) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH, bytesDownloaded != contentLength); - } - - DownloadResult result; - result.SizeInBytes = static_cast(bytesDownloaded); - result.ContentType = std::move(contentType); - result.Sha256Hash = hashEngine.Get(); - AICLI_LOG(Core, Info, << "Download hash: " << SHA256::ConvertToString(result.Sha256Hash)); - - AICLI_LOG(Core, Info, << "Download completed."); - - #pragma warning(pop) - - return result; - } - - std::map GetHeaders(std::string_view url) - { - // TODO: Use proxy info. HttpClient does not support using a custom proxy, only using the system-wide one. - AICLI_LOG(Core, Verbose, << "Retrieving headers from url: " << url); - - HttpBaseProtocolFilter filter; - filter.CacheControl().ReadBehavior(HttpCacheReadBehavior::MostRecent); - - HttpClient client(filter); - client.DefaultRequestHeaders().Connection().Clear(); - client.DefaultRequestHeaders().Append(L"Connection", L"close"); - client.DefaultRequestHeaders().UserAgent().ParseAdd(Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get())); - - winrt::Windows::Foundation::Uri uri{ Utility::ConvertToUTF16(url) }; - HttpRequestMessage request(HttpMethod::Head(), uri); - - HttpResponseMessage response = client.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead).get(); - - switch (response.StatusCode()) - { - case HttpStatusCode::Ok: - // All good - break; - case HttpStatusCode::TooManyRequests: - case HttpStatusCode::ServiceUnavailable: - { - THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response.Headers().RetryAfter()))); - } - default: - THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode())); - } - - std::map result; - - for (const auto& header : response.Headers()) - { - result.emplace(Utility::FoldCase(static_cast(Utility::ConvertToUTF8(header.Key()))), Utility::ConvertToUTF8(header.Value())); - } - - return result; - } - - DownloadResult DownloadToStream( - const std::string& url, - std::ostream& dest, - DownloadType, - IProgressCallback& progress, - std::optional info) - { - THROW_HR_IF(E_INVALIDARG, url.empty()); - return WinINetDownloadToStream(url, dest, progress, info); - } - - DownloadResult Download( - const std::string& url, - const std::filesystem::path& dest, - DownloadType type, - 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()); - - AICLI_LOG(Core, Info, << "Downloading to path: " << dest); - - std::filesystem::create_directories(dest.parent_path()); - - // Only Installers should be downloaded with DO currently, as: - // - Index :: Constantly changing blob at same location is not what DO is for - // - Manifest / InstallerMetadataCollectionInput :: DO overhead is not needed for small files - // - WinGetUtil :: Intentionally not using DO at this time - if (type == DownloadType::Installer) - { - if (Network().GetInstallerDownloader() == InstallerDownloader::DeliveryOptimization) - { - try - { - auto result = DODownload(url, dest, progress, info); - // Since we cannot pre-apply to the file with DO, post-apply the MotW to the file. - // Only do so if the file exists, because cancellation will not throw here. - if (std::filesystem::exists(dest)) - { - ApplyMotwIfApplicable(dest, URLZONE_INTERNET); - } - return result; - } - catch (const wil::ResultException& re) - { - // Fall back to WinINet below unless the specific error is not one that should be ignored. - // We need to be careful not to bypass metered networks or other reasons that might - // intentionally cause the download to be blocked. - HRESULT hr = re.GetErrorCode(); - if (IsDOErrorFatal(hr)) - { - throw; - } - else - { - // Send telemetry so that we can understand the reasons for DO failing - Logging::Telemetry().LogNonFatalDOError(url, hr); - } - } - - // If we reach this point, we are intending to fall through to WinINet. - // Remove any file that may have been placed in the target location. - if (std::filesystem::exists(dest)) - { - std::filesystem::remove(dest); - } - } - } - - std::ofstream emptyDestFile(dest); - emptyDestFile.close(); - ApplyMotwIfApplicable(dest, URLZONE_INTERNET); - - // 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, info); - } - - using namespace std::string_view_literals; - constexpr std::string_view s_http_start = "http://"sv; - constexpr std::string_view s_https_start = "https://"sv; - - bool IsUrlRemote(std::string_view url) - { - // Very simple choice right now: "does it start with http:// or https://"? - if (CaseInsensitiveStartsWith(url, s_http_start) || - CaseInsensitiveStartsWith(url, s_https_start)) - { - return true; - } - - return false; - } - - bool IsUrlSecure(std::string_view url) - { - // Very simple choice right now: "does it start with https://"? - if (CaseInsensitiveStartsWith(url, s_https_start)) - { - return true; - } - - return false; - } - - static inline bool FileSupportsMotw(const std::filesystem::path& path) - { - return SupportsNamedStreams(path); - } - - void ApplyMotwIfApplicable(const std::filesystem::path& filePath, URLZONE zone) - { - AICLI_LOG(Core, Info, << "Started applying motw to " << filePath << " with zone: " << zone); - - if (!FileSupportsMotw(filePath)) - { - AICLI_LOG(Core, Info, << "File system does not support ADS. Skipped applying motw"); - return; - } - - Microsoft::WRL::ComPtr zoneIdentifier; - THROW_IF_FAILED(CoCreateInstance(CLSID_PersistentZoneIdentifier, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&zoneIdentifier))); - THROW_IF_FAILED(zoneIdentifier->SetId(zone)); - - Microsoft::WRL::ComPtr persistFile; - THROW_IF_FAILED(zoneIdentifier.As(&persistFile)); - THROW_IF_FAILED(persistFile->Save(filePath.c_str(), TRUE)); - - AICLI_LOG(Core, Info, << "Finished applying motw"); - } - - void RemoveMotwIfApplicable(const std::filesystem::path& filePath) - { - AICLI_LOG(Core, Info, << "Started removing motw to " << filePath); - - if (!FileSupportsMotw(filePath)) - { - AICLI_LOG(Core, Info, << "File system does not support ADS. Skipped removing motw"); - return; - } - - Microsoft::WRL::ComPtr zoneIdentifier; - THROW_IF_FAILED(CoCreateInstance(CLSID_PersistentZoneIdentifier, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&zoneIdentifier))); - - Microsoft::WRL::ComPtr persistFile; - THROW_IF_FAILED(zoneIdentifier.As(&persistFile)); - - auto hr = persistFile->Load(filePath.c_str(), STGM_READ); - if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || - hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) - { - // IPersistFile::Load can return ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND for - // both "file not found" and "motw not found" depending on the Windows build. - // Check if the file exists to be sure we are on the "motw not found" case. - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !std::filesystem::exists(filePath)); - - AICLI_LOG(Core, Info, << "File does not contain motw. Skipped removing motw"); - return; - } - - THROW_IF_FAILED(hr); - - THROW_IF_FAILED(zoneIdentifier->Remove()); - THROW_IF_FAILED(persistFile->Save(NULL, TRUE)); - - AICLI_LOG(Core, Info, << "Finished removing motw"); - } - - HRESULT ApplyMotwUsingIAttachmentExecuteIfApplicable(const std::filesystem::path& filePath, const std::string& source, URLZONE zoneIfScanFailure) - { - AICLI_LOG(Core, Info, << "Started applying motw using IAttachmentExecute to " << filePath); - - if (!FileSupportsMotw(filePath)) - { - AICLI_LOG(Core, Info, << "File system does not support ADS. Skipped applying motw"); - return S_OK; - } - - // Attachment execution service needs STA to succeed, so we'll create a new thread and CoInitialize with STA. - HRESULT aesSaveResult = S_OK; - auto updateMotw = [&]() -> HRESULT - { - Microsoft::WRL::ComPtr attachmentExecute; - RETURN_IF_FAILED(CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&attachmentExecute))); - RETURN_IF_FAILED(attachmentExecute->SetLocalPath(filePath.c_str())); - RETURN_IF_FAILED(attachmentExecute->SetSource(Utility::ConvertToUTF16(source).c_str())); - - // IAttachmentExecute::Save() expects the local file to be clean (i.e. it won't clear existing motw if it thinks the source url is trusted). - // If removal fails for any reason, log a warning and proceed — a removal failure should not abort the security check. - try - { - RemoveMotwIfApplicable(filePath); - } - catch (...) - { - HRESULT hrRemoveMotw = wil::ResultFromCaughtException(); - AICLI_LOG(Core, Warning, << "RemoveMotwIfApplicable failed before IAttachmentExecute::Save(). Result: " << hrRemoveMotw << ". Proceeding with Save()"); - } - - aesSaveResult = attachmentExecute->Save(); - - // Reapply desired zone upon scan failure. - // Not using SUCCEEDED(hr) to check since there are cases file is missing after a successful scan - if (aesSaveResult != S_OK && std::filesystem::exists(filePath)) - { - ApplyMotwIfApplicable(filePath, zoneIfScanFailure); - } - - RETURN_IF_FAILED(aesSaveResult); - return S_OK; - }; - - HRESULT hr = S_OK; - - ThreadLocalStorage::ThreadGlobals* globals = ThreadLocalStorage::ThreadGlobals::GetForCurrentThread(); - std::thread aesThread([&, globals]() - { - auto globalsCleanup = globals ? globals->SetForCurrentThread() : nullptr; - try - { - hr = LOG_IF_FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); - if (FAILED(hr)) - { - return; - } - - hr = updateMotw(); - CoUninitialize(); - } - catch (...) - { - hr = wil::ResultFromCaughtException(); - AICLI_LOG(Core, Error, << "Exception in IAttachmentExecute thread. Result: " << hr); - } - }); - - aesThread.join(); - - AICLI_LOG(Core, Info, << "Finished applying motw using IAttachmentExecute. Result: " << hr << " IAttachmentExecute::Save() result: " << aesSaveResult); - - // Return the thread's hr when aesSaveResult was never updated (e.g. CoInitializeEx failure or exception - // before IAttachmentExecute::Save() was called), so the caller sees the real failure instead of S_OK. - return (FAILED(hr) && aesSaveResult == S_OK) ? hr : aesSaveResult; - } - - Microsoft::WRL::ComPtr GetReadOnlyStreamFromURI(std::string_view uriStr) - { - Microsoft::WRL::ComPtr inputStream; - if (Utility::IsUrlRemote(uriStr)) - { - // Get an IStream from the input uri and try to create package or bundler reader. - winrt::Windows::Foundation::Uri uri(Utility::ConvertToUTF16(uriStr)); - - winrt::com_ptr httpRandomAccessStream = winrt::make_self(); - - try - { - auto randomAccessStream = httpRandomAccessStream->InitializeAsync(uri).get(); - - ::IUnknown* rasAsIUnknown = (::IUnknown*)winrt::get_abi(randomAccessStream); - THROW_IF_FAILED(CreateStreamOverRandomAccessStream( - rasAsIUnknown, - IID_PPV_ARGS(inputStream.ReleaseAndGetAddressOf()))); - } - catch (const winrt::hresult_error& hre) - { - if (hre.code() == APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE) - { - THROW_EXCEPTION(AppInstaller::Utility::ServiceUnavailableException(httpRandomAccessStream->RetryAfter())); - } - - throw; - } - } - else - { - std::filesystem::path path(Utility::ConvertToUTF16(uriStr)); - THROW_IF_FAILED(SHCreateStreamOnFileEx(path.c_str(), - STGM_READ | STGM_SHARE_DENY_WRITE | STGM_FAILIFTHERE, 0, FALSE, nullptr, &inputStream)); - } - - return inputStream; - } - - std::chrono::seconds GetRetryAfter(const std::wstring& retryAfter) - { - try - { - winrt::hstring hstringValue{ retryAfter }; - HttpDateOrDeltaHeaderValue headerValue = nullptr; - HttpDateOrDeltaHeaderValue::TryParse(hstringValue, headerValue); - return GetRetryAfter(headerValue); - } - catch (...) - { - AICLI_LOG(Core, Error, << "Retry-After value not supported: " << Utility::ConvertToUTF8(retryAfter)); - } - - return 0s; - } - - std::chrono::seconds GetRetryAfter(const HttpResponseMessage& response) - { - return GetRetryAfter(response.Headers().RetryAfter()); - } - - CacheControlPolicy::CacheControlPolicy(std::wstring_view header) - { - static constexpr std::wstring_view s_MaxAge = L"max-age"sv; - - if (header.empty()) - { - return; - } - - std::vector directives = Utility::Split(header, L',', true); - - for (std::wstring_view directive : directives) - { - if (!directive.empty()) - { - // Even if we don't understand the directive, the value was not empty - Present = true; - } - - std::wstring lowerDirective = ToLower(directive); - - if (lowerDirective == L"public"sv) - { - Public = true; - } - else if (lowerDirective == L"no-cache"sv) - { - NoCache = true; - } - else if (lowerDirective == L"no-store"sv) - { - NoStore = true; - } - else if (StartsWith(lowerDirective, s_MaxAge)) - { - std::vector parts = Utility::SplitView(lowerDirective, L'=', true); - if (parts.size() == 2) - { - try - { - MaxAge = std::min(std::stoull(std::wstring{ parts[1] }), MaximumMaxAge); - } - CATCH_LOG(); - } - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "Public/AppInstallerErrors.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerDownloader.h" +#include "Public/AppInstallerSHA256.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerTelemetry.h" +#include "Public/winget/UserSettings.h" +#include "Public/winget/NetworkSettings.h" +#include "Public/winget/Filesystem.h" +#include "DODownloader.h" +#include "HttpStream/HttpRandomAccessStream.h" +#include "Public/winget/ThreadGlobals.h" + +using namespace AppInstaller::Runtime; +using namespace AppInstaller::Settings; +using namespace AppInstaller::Filesystem; +using namespace AppInstaller::Utility::HttpStream; +using namespace winrt::Windows::Web::Http; +using namespace winrt::Windows::Web::Http::Headers; +using namespace winrt::Windows::Web::Http::Filters; + +namespace AppInstaller::Utility +{ + namespace + { + std::wstring GetHttpQueryString(const wil::unique_hinternet& urlFile, DWORD queryProperty) + { + std::wstring result = {}; + DWORD length = 0; + if (!HttpQueryInfoW(urlFile.get(), + queryProperty, + &result[0], + &length, + nullptr)) + { + auto lastError = GetLastError(); + if (lastError == ERROR_INSUFFICIENT_BUFFER) + { + // lpdwBufferLength contains the size, in bytes, of a buffer large enough to receive the requested information + // without the nul char. not the exact buffer size. + auto size = static_cast(length) / sizeof(wchar_t); + result.resize(size + 1); + if (HttpQueryInfoW(urlFile.get(), + queryProperty, + &result[0], + &length, + nullptr)) + { + // because the buffer can be bigger remove possible null chars + result.erase(result.find(L'\0')); + } + else + { + AICLI_LOG(Core, Error, << "Error retrieving header value [" << queryProperty << "]: " << GetLastError()); + result.clear(); + } + } + else + { + AICLI_LOG(Core, Error, << "Error retrieving header [" << queryProperty << "]: " << GetLastError()); + } + } + + return result; + } + + // Gets the retry after value in terms of a delay in seconds + std::chrono::seconds GetRetryAfter(const HttpDateOrDeltaHeaderValue& retryAfter) + { + if (retryAfter) + { + auto delta = retryAfter.Delta(); + if (delta) + { + return std::chrono::duration_cast(delta.GetTimeSpan()); + } + + auto dateTimeRef = retryAfter.Date(); + if (dateTimeRef) + { + auto dateTime = dateTimeRef.GetDateTime(); + auto now = winrt::clock::now(); + + if (dateTime > now) + { + return std::chrono::duration_cast(dateTime - now); + } + } + } + + return 0s; + } + + std::chrono::seconds GetRetryAfter(const wil::unique_hinternet& urlFile) + { + std::wstring retryAfter = GetHttpQueryString(urlFile, HTTP_QUERY_RETRY_AFTER); + return retryAfter.empty() ? 0s : AppInstaller::Utility::GetRetryAfter(retryAfter); + } + } + +#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, + std::optional info) + { + // For AICLI_LOG usages with string literals. + #pragma warning(push) + #pragma warning(disable:26449) + + AICLI_LOG(Core, Info, << "WinINet downloading from url: " << url); + + auto agentWide = Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get()); + wil::unique_hinternet session; + + const auto& proxyUri = Network().GetProxyUri(); + if (proxyUri) + { + AICLI_LOG(Core, Info, << "Using proxy " << proxyUri.value()); + session.reset(InternetOpen( + agentWide.c_str(), + INTERNET_OPEN_TYPE_PROXY, + Utility::ConvertToUTF16(proxyUri.value()).c_str(), + NULL, + 0)); + } + else + { + session.reset(InternetOpen( + agentWide.c_str(), + INTERNET_OPEN_TYPE_PRECONFIG, + NULL, + NULL, + 0)); + } + + 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(), + 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."); + + // Check http return status + DWORD requestStatus = 0; + DWORD cbRequestStatus = sizeof(requestStatus); + + THROW_LAST_ERROR_IF_MSG(!HttpQueryInfoW(urlFile.get(), + HTTP_QUERY_STATUS_CODE | HTTP_QUERY_FLAG_NUMBER, + &requestStatus, + &cbRequestStatus, + nullptr), "Query download request status failed."); + + constexpr DWORD TooManyRequest = 429; + + switch (requestStatus) + { + case HTTP_STATUS_OK: + // All good + break; + case TooManyRequest: + case HTTP_STATUS_SERVICE_UNAVAIL: + { + THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(urlFile))); + } + default: + AICLI_LOG(Core, Error, << "Download request failed. Returned status: " << requestStatus); + THROW_HR_MSG(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, requestStatus), "Download request status is not success."); + } + + AICLI_LOG(Core, Verbose, << "Download request status success."); + + // Get content length. Don't fail the download if failed. + LONGLONG contentLength = 0; + DWORD cbContentLength = sizeof(contentLength); + + HttpQueryInfoW( + urlFile.get(), + HTTP_QUERY_CONTENT_LENGTH | HTTP_QUERY_FLAG_NUMBER64, + &contentLength, + &cbContentLength, + nullptr); + AICLI_LOG(Core, Verbose, << "Download size: " << contentLength); + + std::string contentType = Utility::ConvertToUTF8(GetHttpQueryString(urlFile, HTTP_QUERY_CONTENT_TYPE)); + AICLI_LOG(Core, Verbose, << "Content Type: " << contentType); + + // Setup hash engine + SHA256 hashEngine; + + const int bufferSize = 1024 * 1024; // 1MB + auto buffer = std::make_unique(bufferSize); + + BOOL readSuccess = true; + DWORD bytesRead = 0; + LONGLONG bytesDownloaded = 0; + + do + { + if (progress.IsCancelledBy(CancelReason::Any)) + { + AICLI_LOG(Core, Info, << "Download cancelled."); + return {}; + } + + readSuccess = InternetReadFile(urlFile.get(), buffer.get(), bufferSize, &bytesRead); + + THROW_LAST_ERROR_IF_MSG(!readSuccess, "InternetReadFile() failed."); + + hashEngine.Add(buffer.get(), bytesRead); + + dest.write((char*)buffer.get(), bytesRead); + + bytesDownloaded += bytesRead; + + if (bytesRead != 0) + { + progress.OnProgress(bytesDownloaded, contentLength, ProgressType::Bytes); + } + + } while (bytesRead != 0); + + dest.flush(); + + // Check download size matches if content length is provided in response header + if (contentLength > 0) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH, bytesDownloaded != contentLength); + } + + DownloadResult result; + result.SizeInBytes = static_cast(bytesDownloaded); + result.ContentType = std::move(contentType); + result.Sha256Hash = hashEngine.Get(); + AICLI_LOG(Core, Info, << "Download hash: " << SHA256::ConvertToString(result.Sha256Hash)); + + AICLI_LOG(Core, Info, << "Download completed."); + + #pragma warning(pop) + + return result; + } + + std::map GetHeaders(std::string_view url) + { + // TODO: Use proxy info. HttpClient does not support using a custom proxy, only using the system-wide one. + AICLI_LOG(Core, Verbose, << "Retrieving headers from url: " << url); + + HttpBaseProtocolFilter filter; + filter.CacheControl().ReadBehavior(HttpCacheReadBehavior::MostRecent); + + HttpClient client(filter); + client.DefaultRequestHeaders().Connection().Clear(); + client.DefaultRequestHeaders().Append(L"Connection", L"close"); + client.DefaultRequestHeaders().UserAgent().ParseAdd(Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get())); + + winrt::Windows::Foundation::Uri uri{ Utility::ConvertToUTF16(url) }; + HttpRequestMessage request(HttpMethod::Head(), uri); + + HttpResponseMessage response = client.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead).get(); + + switch (response.StatusCode()) + { + case HttpStatusCode::Ok: + // All good + break; + case HttpStatusCode::TooManyRequests: + case HttpStatusCode::ServiceUnavailable: + { + THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response.Headers().RetryAfter()))); + } + default: + THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode())); + } + + std::map result; + + for (const auto& header : response.Headers()) + { + result.emplace(Utility::FoldCase(static_cast(Utility::ConvertToUTF8(header.Key()))), Utility::ConvertToUTF8(header.Value())); + } + + return result; + } + + DownloadResult DownloadToStream( + const std::string& url, + std::ostream& dest, + DownloadType, + IProgressCallback& progress, + std::optional info) + { + THROW_HR_IF(E_INVALIDARG, url.empty()); + return WinINetDownloadToStream(url, dest, progress, info); + } + + DownloadResult Download( + const std::string& url, + const std::filesystem::path& dest, + DownloadType type, + 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()); + + AICLI_LOG(Core, Info, << "Downloading to path: " << dest); + + std::filesystem::create_directories(dest.parent_path()); + + // Only Installers should be downloaded with DO currently, as: + // - Index :: Constantly changing blob at same location is not what DO is for + // - Manifest / InstallerMetadataCollectionInput :: DO overhead is not needed for small files + // - WinGetUtil :: Intentionally not using DO at this time + if (type == DownloadType::Installer) + { + if (Network().GetInstallerDownloader() == InstallerDownloader::DeliveryOptimization) + { + try + { + auto result = DODownload(url, dest, progress, info); + // Since we cannot pre-apply to the file with DO, post-apply the MotW to the file. + // Only do so if the file exists, because cancellation will not throw here. + if (std::filesystem::exists(dest)) + { + ApplyMotwIfApplicable(dest, URLZONE_INTERNET); + } + return result; + } + catch (const wil::ResultException& re) + { + // Fall back to WinINet below unless the specific error is not one that should be ignored. + // We need to be careful not to bypass metered networks or other reasons that might + // intentionally cause the download to be blocked. + HRESULT hr = re.GetErrorCode(); + if (IsDOErrorFatal(hr)) + { + throw; + } + else + { + // Send telemetry so that we can understand the reasons for DO failing + Logging::Telemetry().LogNonFatalDOError(url, hr); + } + } + + // If we reach this point, we are intending to fall through to WinINet. + // Remove any file that may have been placed in the target location. + if (std::filesystem::exists(dest)) + { + std::filesystem::remove(dest); + } + } + } + + std::ofstream emptyDestFile(dest); + emptyDestFile.close(); + ApplyMotwIfApplicable(dest, URLZONE_INTERNET); + + // 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, info); + } + + using namespace std::string_view_literals; + constexpr std::string_view s_http_start = "http://"sv; + constexpr std::string_view s_https_start = "https://"sv; + + bool IsUrlRemote(std::string_view url) + { + // Very simple choice right now: "does it start with http:// or https://"? + if (CaseInsensitiveStartsWith(url, s_http_start) || + CaseInsensitiveStartsWith(url, s_https_start)) + { + return true; + } + + return false; + } + + bool IsUrlSecure(std::string_view url) + { + // Very simple choice right now: "does it start with https://"? + if (CaseInsensitiveStartsWith(url, s_https_start)) + { + return true; + } + + return false; + } + + static inline bool FileSupportsMotw(const std::filesystem::path& path) + { + return SupportsNamedStreams(path); + } + + void ApplyMotwIfApplicable(const std::filesystem::path& filePath, URLZONE zone) + { + AICLI_LOG(Core, Info, << "Started applying motw to " << filePath << " with zone: " << zone); + + if (!FileSupportsMotw(filePath)) + { + AICLI_LOG(Core, Info, << "File system does not support ADS. Skipped applying motw"); + return; + } + + Microsoft::WRL::ComPtr zoneIdentifier; + THROW_IF_FAILED(CoCreateInstance(CLSID_PersistentZoneIdentifier, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&zoneIdentifier))); + THROW_IF_FAILED(zoneIdentifier->SetId(zone)); + + Microsoft::WRL::ComPtr persistFile; + THROW_IF_FAILED(zoneIdentifier.As(&persistFile)); + THROW_IF_FAILED(persistFile->Save(filePath.c_str(), TRUE)); + + AICLI_LOG(Core, Info, << "Finished applying motw"); + } + + void RemoveMotwIfApplicable(const std::filesystem::path& filePath) + { + AICLI_LOG(Core, Info, << "Started removing motw to " << filePath); + + if (!FileSupportsMotw(filePath)) + { + AICLI_LOG(Core, Info, << "File system does not support ADS. Skipped removing motw"); + return; + } + + Microsoft::WRL::ComPtr zoneIdentifier; + THROW_IF_FAILED(CoCreateInstance(CLSID_PersistentZoneIdentifier, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&zoneIdentifier))); + + Microsoft::WRL::ComPtr persistFile; + THROW_IF_FAILED(zoneIdentifier.As(&persistFile)); + + auto hr = persistFile->Load(filePath.c_str(), STGM_READ); + if (hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || + hr == HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND)) + { + // IPersistFile::Load can return ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND for + // both "file not found" and "motw not found" depending on the Windows build. + // Check if the file exists to be sure we are on the "motw not found" case. + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !std::filesystem::exists(filePath)); + + AICLI_LOG(Core, Info, << "File does not contain motw. Skipped removing motw"); + return; + } + + THROW_IF_FAILED(hr); + + THROW_IF_FAILED(zoneIdentifier->Remove()); + THROW_IF_FAILED(persistFile->Save(NULL, TRUE)); + + AICLI_LOG(Core, Info, << "Finished removing motw"); + } + + HRESULT ApplyMotwUsingIAttachmentExecuteIfApplicable(const std::filesystem::path& filePath, const std::string& source, URLZONE zoneIfScanFailure) + { + AICLI_LOG(Core, Info, << "Started applying motw using IAttachmentExecute to " << filePath); + + if (!FileSupportsMotw(filePath)) + { + AICLI_LOG(Core, Info, << "File system does not support ADS. Skipped applying motw"); + return S_OK; + } + + // Attachment execution service needs STA to succeed, so we'll create a new thread and CoInitialize with STA. + HRESULT aesSaveResult = S_OK; + auto updateMotw = [&]() -> HRESULT + { + Microsoft::WRL::ComPtr attachmentExecute; + RETURN_IF_FAILED(CoCreateInstance(CLSID_AttachmentServices, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&attachmentExecute))); + RETURN_IF_FAILED(attachmentExecute->SetLocalPath(filePath.c_str())); + RETURN_IF_FAILED(attachmentExecute->SetSource(Utility::ConvertToUTF16(source).c_str())); + + // IAttachmentExecute::Save() expects the local file to be clean (i.e. it won't clear existing motw if it thinks the source url is trusted). + // If removal fails for any reason, log a warning and proceed — a removal failure should not abort the security check. + try + { + RemoveMotwIfApplicable(filePath); + } + catch (...) + { + HRESULT hrRemoveMotw = wil::ResultFromCaughtException(); + AICLI_LOG(Core, Warning, << "RemoveMotwIfApplicable failed before IAttachmentExecute::Save(). Result: " << hrRemoveMotw << ". Proceeding with Save()"); + } + + aesSaveResult = attachmentExecute->Save(); + + // Reapply desired zone upon scan failure. + // Not using SUCCEEDED(hr) to check since there are cases file is missing after a successful scan + if (aesSaveResult != S_OK && std::filesystem::exists(filePath)) + { + ApplyMotwIfApplicable(filePath, zoneIfScanFailure); + } + + RETURN_IF_FAILED(aesSaveResult); + return S_OK; + }; + + HRESULT hr = S_OK; + + ThreadLocalStorage::ThreadGlobals* globals = ThreadLocalStorage::ThreadGlobals::GetForCurrentThread(); + std::thread aesThread([&, globals]() + { + auto globalsCleanup = globals ? globals->SetForCurrentThread() : nullptr; + try + { + hr = LOG_IF_FAILED(CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED)); + if (FAILED(hr)) + { + return; + } + + hr = updateMotw(); + CoUninitialize(); + } + catch (...) + { + hr = wil::ResultFromCaughtException(); + AICLI_LOG(Core, Error, << "Exception in IAttachmentExecute thread. Result: " << hr); + } + }); + + aesThread.join(); + + AICLI_LOG(Core, Info, << "Finished applying motw using IAttachmentExecute. Result: " << hr << " IAttachmentExecute::Save() result: " << aesSaveResult); + + // Return the thread's hr when aesSaveResult was never updated (e.g. CoInitializeEx failure or exception + // before IAttachmentExecute::Save() was called), so the caller sees the real failure instead of S_OK. + return (FAILED(hr) && aesSaveResult == S_OK) ? hr : aesSaveResult; + } + + Microsoft::WRL::ComPtr GetReadOnlyStreamFromURI(std::string_view uriStr) + { + Microsoft::WRL::ComPtr inputStream; + if (Utility::IsUrlRemote(uriStr)) + { + // Get an IStream from the input uri and try to create package or bundler reader. + winrt::Windows::Foundation::Uri uri(Utility::ConvertToUTF16(uriStr)); + + winrt::com_ptr httpRandomAccessStream = winrt::make_self(); + + try + { + auto randomAccessStream = httpRandomAccessStream->InitializeAsync(uri).get(); + + ::IUnknown* rasAsIUnknown = (::IUnknown*)winrt::get_abi(randomAccessStream); + THROW_IF_FAILED(CreateStreamOverRandomAccessStream( + rasAsIUnknown, + IID_PPV_ARGS(inputStream.ReleaseAndGetAddressOf()))); + } + catch (const winrt::hresult_error& hre) + { + if (hre.code() == APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE) + { + THROW_EXCEPTION(AppInstaller::Utility::ServiceUnavailableException(httpRandomAccessStream->RetryAfter())); + } + + throw; + } + } + else + { + std::filesystem::path path(Utility::ConvertToUTF16(uriStr)); + THROW_IF_FAILED(SHCreateStreamOnFileEx(path.c_str(), + STGM_READ | STGM_SHARE_DENY_WRITE | STGM_FAILIFTHERE, 0, FALSE, nullptr, &inputStream)); + } + + return inputStream; + } + + std::chrono::seconds GetRetryAfter(const std::wstring& retryAfter) + { + try + { + winrt::hstring hstringValue{ retryAfter }; + HttpDateOrDeltaHeaderValue headerValue = nullptr; + HttpDateOrDeltaHeaderValue::TryParse(hstringValue, headerValue); + return GetRetryAfter(headerValue); + } + catch (...) + { + AICLI_LOG(Core, Error, << "Retry-After value not supported: " << Utility::ConvertToUTF8(retryAfter)); + } + + return 0s; + } + + std::chrono::seconds GetRetryAfter(const HttpResponseMessage& response) + { + return GetRetryAfter(response.Headers().RetryAfter()); + } + + CacheControlPolicy::CacheControlPolicy(std::wstring_view header) + { + static constexpr std::wstring_view s_MaxAge = L"max-age"sv; + + if (header.empty()) + { + return; + } + + std::vector directives = Utility::Split(header, L',', true); + + for (std::wstring_view directive : directives) + { + if (!directive.empty()) + { + // Even if we don't understand the directive, the value was not empty + Present = true; + } + + std::wstring lowerDirective = ToLower(directive); + + if (lowerDirective == L"public"sv) + { + Public = true; + } + else if (lowerDirective == L"no-cache"sv) + { + NoCache = true; + } + else if (lowerDirective == L"no-store"sv) + { + NoStore = true; + } + else if (StartsWith(lowerDirective, s_MaxAge)) + { + std::vector parts = Utility::SplitView(lowerDirective, L'=', true); + if (parts.size() == 2) + { + try + { + MaxAge = std::min(std::stoull(std::wstring{ parts[1] }), MaximumMaxAge); + } + CATCH_LOG(); + } + } + } + } +} diff --git a/src/AppInstallerCommonCore/ExtensionCatalog.cpp b/src/AppInstallerCommonCore/ExtensionCatalog.cpp index 6772c7efa9..42bb49651c 100644 --- a/src/AppInstallerCommonCore/ExtensionCatalog.cpp +++ b/src/AppInstallerCommonCore/ExtensionCatalog.cpp @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/ExtensionCatalog.h" -#include "AppInstallerErrors.h" -#include "AppInstallerLogging.h" -#include "AppInstallerStrings.h" - -namespace AppInstaller::Deployment -{ - namespace AppExt = winrt::Windows::ApplicationModel::AppExtensions; - - Extension::Extension(AppExt::AppExtension extension) : m_extension(extension) {} - - std::filesystem::path Extension::GetPackagePath() const - { - return m_extension.Package().InstalledLocation().Path().c_str(); - } - - std::filesystem::path Extension::GetPublicFolderPath() const - { - auto folder = m_extension.GetPublicFolderAsync().get(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED, !folder); - return folder.Path().c_str(); - } - - winrt::Windows::ApplicationModel::PackageVersion Extension::GetPackageVersion() const - { - return m_extension.Package().Id().Version(); - } - - bool Extension::VerifyContentIntegrity(IProgressCallback& progress) - { - auto operation = m_extension.Package().VerifyContentIntegrityAsync(); - auto removeCancel = progress.SetCancellationFunction([&]() { operation.Cancel(); }); - return operation.get(); - } - - ExtensionCatalog::ExtensionCatalog(std::wstring_view extensionName) - { - m_catalog = AppExt::AppExtensionCatalog::Open(winrt::hstring(extensionName)); - } - - std::optional ExtensionCatalog::FindByPackageFamilyAndId(std::string_view packageFamilyName, std::wstring_view id) const - { - std::wstring wpfn = Utility::ConvertToUTF16(packageFamilyName); - std::optional result; - - auto extensions = m_catalog.FindAllAsync().get(); - for (const auto& extension : extensions) - { - auto info = extension.AppInfo(); - - AICLI_LOG(Core, Info, << "Examining extension: PFN = " << Utility::ConvertToUTF8(info.PackageFamilyName()) << ", ID = " << Utility::ConvertToUTF8(extension.Id())); - - if (info.PackageFamilyName() == wpfn && extension.Id() == id) - { - AICLI_LOG(Core, Info, << "Found matching extension."); - result = Extension{ extension }; - break; - } - } - - if (!result) - { - AICLI_LOG(Core, Info, << "Did not find extension: PFN = " << packageFamilyName << ", ID = " << Utility::ConvertToUTF8(id)); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ExtensionCatalog.h" +#include "AppInstallerErrors.h" +#include "AppInstallerLogging.h" +#include "AppInstallerStrings.h" + +namespace AppInstaller::Deployment +{ + namespace AppExt = winrt::Windows::ApplicationModel::AppExtensions; + + Extension::Extension(AppExt::AppExtension extension) : m_extension(extension) {} + + std::filesystem::path Extension::GetPackagePath() const + { + return m_extension.Package().InstalledLocation().Path().c_str(); + } + + std::filesystem::path Extension::GetPublicFolderPath() const + { + auto folder = m_extension.GetPublicFolderAsync().get(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED, !folder); + return folder.Path().c_str(); + } + + winrt::Windows::ApplicationModel::PackageVersion Extension::GetPackageVersion() const + { + return m_extension.Package().Id().Version(); + } + + bool Extension::VerifyContentIntegrity(IProgressCallback& progress) + { + auto operation = m_extension.Package().VerifyContentIntegrityAsync(); + auto removeCancel = progress.SetCancellationFunction([&]() { operation.Cancel(); }); + return operation.get(); + } + + ExtensionCatalog::ExtensionCatalog(std::wstring_view extensionName) + { + m_catalog = AppExt::AppExtensionCatalog::Open(winrt::hstring(extensionName)); + } + + std::optional ExtensionCatalog::FindByPackageFamilyAndId(std::string_view packageFamilyName, std::wstring_view id) const + { + std::wstring wpfn = Utility::ConvertToUTF16(packageFamilyName); + std::optional result; + + auto extensions = m_catalog.FindAllAsync().get(); + for (const auto& extension : extensions) + { + auto info = extension.AppInfo(); + + AICLI_LOG(Core, Info, << "Examining extension: PFN = " << Utility::ConvertToUTF8(info.PackageFamilyName()) << ", ID = " << Utility::ConvertToUTF8(extension.Id())); + + if (info.PackageFamilyName() == wpfn && extension.Id() == id) + { + AICLI_LOG(Core, Info, << "Found matching extension."); + result = Extension{ extension }; + break; + } + } + + if (!result) + { + AICLI_LOG(Core, Info, << "Did not find extension: PFN = " << packageFamilyName << ", ID = " << Utility::ConvertToUTF8(id)); + } + + return result; + } +} diff --git a/src/AppInstallerCommonCore/FileCache.cpp b/src/AppInstallerCommonCore/FileCache.cpp index 331c44ed16..bbb5d21f22 100644 --- a/src/AppInstallerCommonCore/FileCache.cpp +++ b/src/AppInstallerCommonCore/FileCache.cpp @@ -1,247 +1,247 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/FileCache.h" -#include -#include -#include - -namespace AppInstaller::Caching -{ - namespace anon - { - std::string_view GetNameForType(FileCache::Type type) - { - switch (type) - { - case FileCache::Type::IndexV1_Manifest: return "V1_M"; - case FileCache::Type::IndexV2_PackageVersionData: return "V2_PVD"; - case FileCache::Type::IndexV2_Manifest: return "V2_M"; - case FileCache::Type::Icon: return "Icon"; -#ifndef AICLI_DISABLE_TEST_HOOKS - case FileCache::Type::Tests: return "Tests"; -#endif - } - - THROW_HR(E_UNEXPECTED); - } - - std::unique_ptr GetUpstreamFile(const std::string& basePath, const std::string& relativePath, const Utility::SHA256::HashBuffer& expectedHash) - { - // Until signed files are implemented, fail on an empty hash - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, expectedHash.empty()); - - std::string fullPath = basePath; - if (fullPath.back() != '/') - { - fullPath += '/'; - } - fullPath += relativePath; - - if (Utility::IsUrlRemote(fullPath)) - { - auto result = std::make_unique(); - - AICLI_LOG(Core, Verbose, << "Getting upstream file from remote: " << fullPath); - ProgressCallback emptyCallback; - - constexpr int MaxRetryCount = 2; - constexpr std::chrono::seconds maximumWaitTimeAllowed = 10s; - for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) - { - try - { - auto downloadResult = Utility::DownloadToStream(fullPath, *result, Utility::DownloadType::Manifest, emptyCallback); - - if (!expectedHash.empty() && - !Utility::SHA256::AreEqual(expectedHash, downloadResult.Sha256Hash)) - { - AICLI_LOG(Core, Verbose, << "Invalid hash from [" << fullPath << "]: expected [" << Utility::SHA256::ConvertToString(expectedHash) << "], got [" << Utility::SHA256::ConvertToString(downloadResult.Sha256Hash) << "]"); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); - } - - break; - } - catch (const Utility::ServiceUnavailableException& sue) - { - if (retryCount < MaxRetryCount - 1) - { - auto waitSecondsForRetry = sue.RetryAfter(); - if (waitSecondsForRetry > maximumWaitTimeAllowed) - { - throw; - } - - // TODO: Get real progress callback to allow cancelation. - auto ms = std::chrono::duration_cast(waitSecondsForRetry); - Sleep(static_cast(ms.count())); - } - else - { - throw; - } - } - catch (...) - { - if (retryCount < MaxRetryCount - 1) - { - AICLI_LOG(Core, Verbose, << "Getting upstream file failed, waiting a bit and retrying: " << fullPath); - Sleep(500); - } - else - { - throw; - } - } - } - - return result; - } - else - { - AICLI_LOG(Core, Verbose, << "Getting upstream file from local: " << fullPath); - std::ifstream fileStream{ fullPath, std::ios_base::in | std::ios_base::binary }; - std::string fileContents = Utility::ReadEntireStream(fileStream); - - auto fileContentsHash = Utility::SHA256::ComputeHash(fileContents); - - if (expectedHash.empty() || Utility::SHA256::AreEqual(expectedHash, fileContentsHash)) - { - return std::make_unique(std::move(fileContents)); - } - else - { - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); - } - } - } - } - - FileCache::Details::Details(FileCache::Type type, std::string identifier) : - Type(type), Identifier(std::move(identifier)) - { - switch (type) - { - case Type::IndexV1_Manifest: - case Type::IndexV2_PackageVersionData: - case Type::IndexV2_Manifest: - case Type::Icon: -#ifndef AICLI_DISABLE_TEST_HOOKS - case Type::Tests: -#endif - BasePath = Runtime::PathName::Temp; - break; - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::filesystem::path FileCache::Details::GetCachePath() const - { - std::filesystem::path result = Runtime::GetPathTo(BasePath); - result /= "cache"; - result /= anon::GetNameForType(Type); - result /= Utility::ConvertToUTF16(Identifier); - return result; - } - - FileCache::FileCache(Type type, std::string identifier, std::vector sources) : - m_details(type, std::move(identifier)), m_sources(std::move(sources)) - { - m_cacheBase = m_details.GetCachePath(); - } - - const FileCache::Details& FileCache::GetDetails() const - { - return m_details; - } - - std::unique_ptr FileCache::GetFile(const std::filesystem::path& relativePath, const Utility::SHA256::HashBuffer& expectedHash) const - { - std::filesystem::path cachedFilePath = m_cacheBase / relativePath; - - // Check cache for matching file - try - { - if (std::filesystem::is_regular_file(cachedFilePath)) - { - AICLI_LOG(Core, Verbose, << "Reading cached file [" << cachedFilePath << "]"); - - std::ifstream fileStream{ cachedFilePath, std::ios_base::in | std::ios_base::binary }; - std::string fileContents = Utility::ReadEntireStream(fileStream); - - auto fileContentsHash = Utility::SHA256::ComputeHash(fileContents); - - if (Utility::SHA256::AreEqual(expectedHash, fileContentsHash)) - { - return std::make_unique(std::move(fileContents)); - } - else - { - AICLI_LOG(Core, Verbose, << "Removing cached file [" << cachedFilePath << "] due to hash mismatch; expected [" << - Utility::SHA256::ConvertToString(expectedHash) << "] but was [" << Utility::SHA256::ConvertToString(fileContentsHash) << "]"); - } - } - - std::filesystem::remove_all(cachedFilePath); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION_MSG("Error while attempting to read cached file"); - } - - // Making it here means that we do not have a cached file or it needed to be updated and was removed. - auto result = GetUpstreamFile(relativePath.u8string(), expectedHash); - - // GetUpstreamFile only returns with a successfully verified hash, we just need to write the file out. - // Only log failures as caching is an optimization. - try - { - std::filesystem::create_directories(cachedFilePath.parent_path()); - - AICLI_LOG(Core, Verbose, << "Writing cached file [" << cachedFilePath << "]"); - std::ofstream fileStream{ cachedFilePath, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc }; - LOG_LAST_ERROR_IF(fileStream.fail()); - fileStream << result->str() << std::flush; - LOG_LAST_ERROR_IF(fileStream.fail()); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION_MSG("Error while attempting to write cached file"); - } - - return result; - } - - std::unique_ptr FileCache::GetUpstreamFile(std::string relativePath, const Utility::SHA256::HashBuffer& expectedHash) const - { - // Replace backslashes with forward slashes for HTTP requests (since local can handle them). - Utility::FindAndReplace(relativePath, "\\", "/"); - - std::exception_ptr firstException; - - for (const auto& upstream : m_sources) - { - try - { - return anon::GetUpstreamFile(upstream, relativePath, expectedHash); - } - catch(...) - { - LOG_CAUGHT_EXCEPTION_MSG("GetUpstreamFile failed on source: %hs", upstream.c_str()); - if (!firstException) - { - firstException = std::current_exception(); - } - } - } - - if (firstException) - { - std::rethrow_exception(firstException); - } - - // Somewhat arbitrary error that should only happen if no upstream sources provided. - THROW_HR(E_NOT_SET); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/FileCache.h" +#include +#include +#include + +namespace AppInstaller::Caching +{ + namespace anon + { + std::string_view GetNameForType(FileCache::Type type) + { + switch (type) + { + case FileCache::Type::IndexV1_Manifest: return "V1_M"; + case FileCache::Type::IndexV2_PackageVersionData: return "V2_PVD"; + case FileCache::Type::IndexV2_Manifest: return "V2_M"; + case FileCache::Type::Icon: return "Icon"; +#ifndef AICLI_DISABLE_TEST_HOOKS + case FileCache::Type::Tests: return "Tests"; +#endif + } + + THROW_HR(E_UNEXPECTED); + } + + std::unique_ptr GetUpstreamFile(const std::string& basePath, const std::string& relativePath, const Utility::SHA256::HashBuffer& expectedHash) + { + // Until signed files are implemented, fail on an empty hash + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, expectedHash.empty()); + + std::string fullPath = basePath; + if (fullPath.back() != '/') + { + fullPath += '/'; + } + fullPath += relativePath; + + if (Utility::IsUrlRemote(fullPath)) + { + auto result = std::make_unique(); + + AICLI_LOG(Core, Verbose, << "Getting upstream file from remote: " << fullPath); + ProgressCallback emptyCallback; + + constexpr int MaxRetryCount = 2; + constexpr std::chrono::seconds maximumWaitTimeAllowed = 10s; + for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) + { + try + { + auto downloadResult = Utility::DownloadToStream(fullPath, *result, Utility::DownloadType::Manifest, emptyCallback); + + if (!expectedHash.empty() && + !Utility::SHA256::AreEqual(expectedHash, downloadResult.Sha256Hash)) + { + AICLI_LOG(Core, Verbose, << "Invalid hash from [" << fullPath << "]: expected [" << Utility::SHA256::ConvertToString(expectedHash) << "], got [" << Utility::SHA256::ConvertToString(downloadResult.Sha256Hash) << "]"); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); + } + + break; + } + catch (const Utility::ServiceUnavailableException& sue) + { + if (retryCount < MaxRetryCount - 1) + { + auto waitSecondsForRetry = sue.RetryAfter(); + if (waitSecondsForRetry > maximumWaitTimeAllowed) + { + throw; + } + + // TODO: Get real progress callback to allow cancelation. + auto ms = std::chrono::duration_cast(waitSecondsForRetry); + Sleep(static_cast(ms.count())); + } + else + { + throw; + } + } + catch (...) + { + if (retryCount < MaxRetryCount - 1) + { + AICLI_LOG(Core, Verbose, << "Getting upstream file failed, waiting a bit and retrying: " << fullPath); + Sleep(500); + } + else + { + throw; + } + } + } + + return result; + } + else + { + AICLI_LOG(Core, Verbose, << "Getting upstream file from local: " << fullPath); + std::ifstream fileStream{ fullPath, std::ios_base::in | std::ios_base::binary }; + std::string fileContents = Utility::ReadEntireStream(fileStream); + + auto fileContentsHash = Utility::SHA256::ComputeHash(fileContents); + + if (expectedHash.empty() || Utility::SHA256::AreEqual(expectedHash, fileContentsHash)) + { + return std::make_unique(std::move(fileContents)); + } + else + { + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); + } + } + } + } + + FileCache::Details::Details(FileCache::Type type, std::string identifier) : + Type(type), Identifier(std::move(identifier)) + { + switch (type) + { + case Type::IndexV1_Manifest: + case Type::IndexV2_PackageVersionData: + case Type::IndexV2_Manifest: + case Type::Icon: +#ifndef AICLI_DISABLE_TEST_HOOKS + case Type::Tests: +#endif + BasePath = Runtime::PathName::Temp; + break; + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::filesystem::path FileCache::Details::GetCachePath() const + { + std::filesystem::path result = Runtime::GetPathTo(BasePath); + result /= "cache"; + result /= anon::GetNameForType(Type); + result /= Utility::ConvertToUTF16(Identifier); + return result; + } + + FileCache::FileCache(Type type, std::string identifier, std::vector sources) : + m_details(type, std::move(identifier)), m_sources(std::move(sources)) + { + m_cacheBase = m_details.GetCachePath(); + } + + const FileCache::Details& FileCache::GetDetails() const + { + return m_details; + } + + std::unique_ptr FileCache::GetFile(const std::filesystem::path& relativePath, const Utility::SHA256::HashBuffer& expectedHash) const + { + std::filesystem::path cachedFilePath = m_cacheBase / relativePath; + + // Check cache for matching file + try + { + if (std::filesystem::is_regular_file(cachedFilePath)) + { + AICLI_LOG(Core, Verbose, << "Reading cached file [" << cachedFilePath << "]"); + + std::ifstream fileStream{ cachedFilePath, std::ios_base::in | std::ios_base::binary }; + std::string fileContents = Utility::ReadEntireStream(fileStream); + + auto fileContentsHash = Utility::SHA256::ComputeHash(fileContents); + + if (Utility::SHA256::AreEqual(expectedHash, fileContentsHash)) + { + return std::make_unique(std::move(fileContents)); + } + else + { + AICLI_LOG(Core, Verbose, << "Removing cached file [" << cachedFilePath << "] due to hash mismatch; expected [" << + Utility::SHA256::ConvertToString(expectedHash) << "] but was [" << Utility::SHA256::ConvertToString(fileContentsHash) << "]"); + } + } + + std::filesystem::remove_all(cachedFilePath); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION_MSG("Error while attempting to read cached file"); + } + + // Making it here means that we do not have a cached file or it needed to be updated and was removed. + auto result = GetUpstreamFile(relativePath.u8string(), expectedHash); + + // GetUpstreamFile only returns with a successfully verified hash, we just need to write the file out. + // Only log failures as caching is an optimization. + try + { + std::filesystem::create_directories(cachedFilePath.parent_path()); + + AICLI_LOG(Core, Verbose, << "Writing cached file [" << cachedFilePath << "]"); + std::ofstream fileStream{ cachedFilePath, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc }; + LOG_LAST_ERROR_IF(fileStream.fail()); + fileStream << result->str() << std::flush; + LOG_LAST_ERROR_IF(fileStream.fail()); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION_MSG("Error while attempting to write cached file"); + } + + return result; + } + + std::unique_ptr FileCache::GetUpstreamFile(std::string relativePath, const Utility::SHA256::HashBuffer& expectedHash) const + { + // Replace backslashes with forward slashes for HTTP requests (since local can handle them). + Utility::FindAndReplace(relativePath, "\\", "/"); + + std::exception_ptr firstException; + + for (const auto& upstream : m_sources) + { + try + { + return anon::GetUpstreamFile(upstream, relativePath, expectedHash); + } + catch(...) + { + LOG_CAUGHT_EXCEPTION_MSG("GetUpstreamFile failed on source: %hs", upstream.c_str()); + if (!firstException) + { + firstException = std::current_exception(); + } + } + } + + if (firstException) + { + std::rethrow_exception(firstException); + } + + // Somewhat arbitrary error that should only happen if no upstream sources provided. + THROW_HR(E_NOT_SET); + } +} diff --git a/src/AppInstallerCommonCore/FileLogger.cpp b/src/AppInstallerCommonCore/FileLogger.cpp index a430694ef6..e97d797d10 100644 --- a/src/AppInstallerCommonCore/FileLogger.cpp +++ b/src/AppInstallerCommonCore/FileLogger.cpp @@ -1,234 +1,234 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerFileLogger.h" - -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerDateTime.h" -#include "Public/winget/UserSettings.h" -#include -#include - - -namespace AppInstaller::Logging -{ - using namespace std::string_view_literals; - using namespace std::chrono_literals; - - namespace - { - static constexpr std::string_view s_fileLoggerDefaultFilePrefix = "WinGet"sv; - static constexpr std::string_view s_fileLoggerDefaultFileExt = ".log"sv; - - // Send to a string first to create a single block to write to a file. - std::string ToLogLine(Channel channel, Level level, std::string_view message) - { - std::stringstream strstr; - strstr << std::chrono::system_clock::now() << " <" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message; - return std::move(strstr).str(); - } - - // Determines the difference between the given position and the maximum as an offset. - std::ofstream::off_type CalculateDiff(const std::ofstream::pos_type& position, std::ofstream::off_type maximum) - { - auto offsetPosition = static_cast(position); - return maximum > offsetPosition ? maximum - offsetPosition : 0; - } - } - - FileLogger::FileLogger() : FileLogger(s_fileLoggerDefaultFilePrefix) {} - - FileLogger::FileLogger(const std::filesystem::path& filePath) - { - m_name = GetNameForPath(filePath); - m_filePath = filePath; - InitializeDefaultMaximumFileSize(); - OpenFileLoggerStream(); - } - - FileLogger::FileLogger(const std::string_view fileNamePrefix) - { - m_name = "file"; - m_filePath = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); - m_filePath /= fileNamePrefix.data() + ('-' + Utility::GetCurrentTimeForFilename() + s_fileLoggerDefaultFileExt.data()); - InitializeDefaultMaximumFileSize(); - OpenFileLoggerStream(); - } - - FileLogger::~FileLogger() - { - m_stream.flush(); - // When std::ofstream is constructed from an existing File handle, it does not call fclose on destruction - // Only calling close() explicitly will close the file handle. - m_stream.close(); - } - - FileLogger& FileLogger::SetMaximumSize(std::ofstream::off_type maximumSize) - { - THROW_HR_IF(E_INVALIDARG, maximumSize < 0); - m_maximumSize = maximumSize; - return *this; - } - - std::string FileLogger::GetNameForPath(const std::filesystem::path& filePath) - { - using namespace std::string_literals; - return "file :: "s + filePath.u8string(); - } - - std::string_view FileLogger::DefaultPrefix() - { - return s_fileLoggerDefaultFilePrefix; - } - - std::string_view FileLogger::DefaultExt() - { - return s_fileLoggerDefaultFileExt; - } - - std::string FileLogger::GetName() const - { - return m_name; - } - - void FileLogger::Write(Channel channel, Level level, std::string_view message) noexcept try - { - std::string log = ToLogLine(channel, level, message); - WriteDirect(channel, level, log); - } - catch (...) {} - - void FileLogger::WriteDirect(Channel, Level, std::string_view message) noexcept try - { - HandleMaximumFileSize(message); - m_stream << message << std::endl; - } - catch (...) {} - - void FileLogger::SetTag(Tag tag) noexcept try - { - if (tag == Tag::HeadersComplete) - { - auto currentPosition = m_stream.tellp(); - if (currentPosition != std::ofstream::pos_type{ -1 }) - { - m_headersEnd = currentPosition; - } - } - } - catch (...) {} - - void FileLogger::Add() - { - Log().AddLogger(std::make_unique()); - } - - void FileLogger::Add(const std::filesystem::path& filePath) - { - Log().AddLogger(std::make_unique(filePath)); - } - - void FileLogger::Add(std::string_view fileNamePrefix) - { - Log().AddLogger(std::make_unique(fileNamePrefix)); - } - - void FileLogger::BeginCleanup() - { - BeginCleanup(Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation)); - } - - void FileLogger::BeginCleanup(const std::filesystem::path& filePath) - { - std::thread([filePath]() - { - try - { - const auto& settings = Settings::User(); - - Filesystem::FileLimits fileLimits; - fileLimits.Age = settings.Get(); - fileLimits.TotalSizeInMB = settings.Get(); - fileLimits.Count = settings.Get(); - - auto filesInPath = Filesystem::GetFileInfoFor(filePath); - Filesystem::FilterToFilesExceedingLimits(filesInPath, fileLimits); - - for (const auto& file : filesInPath) - { - std::filesystem::remove(file.Path); - } - } - // Just throw out everything - catch (...) {} - }).detach(); - } - - void FileLogger::OpenFileLoggerStream() - { - // Prevent other writers to our log file, but allow readers - FILE* filePtr = _wfsopen(m_filePath.wstring().c_str(), L"w", _SH_DENYWR); - - if (filePtr) - { - auto closeFile = wil::scope_exit([&]() { fclose(filePtr); }); - - // Prevent inheritance to ensure log file handle is not opened by other processes - THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(reinterpret_cast(_get_osfhandle(_fileno(filePtr))), HANDLE_FLAG_INHERIT, 0)); - - m_stream = std::ofstream{ filePtr }; - closeFile.release(); - } - else - { - AICLI_LOG(Core, Error, << "Failed to open log file " << m_filePath.u8string()); - throw std::system_error(errno, std::generic_category()); - } - } - - void FileLogger::InitializeDefaultMaximumFileSize() - { - m_maximumSize = static_cast(Settings::User().Get()) << 20; - } - - void FileLogger::HandleMaximumFileSize(std::string_view& currentLog) - { - if (m_maximumSize == 0) - { - return; - } - - auto maximumLogSize = static_cast(CalculateDiff(m_headersEnd, m_maximumSize)); - - // In the event that a single log is larger than the maximum - if (currentLog.size() > maximumLogSize) - { - currentLog = currentLog.substr(0, maximumLogSize); - WrapLogFile(); - return; - } - - auto currentPosition = m_stream.tellp(); - if (currentPosition == std::ofstream::pos_type{ -1 }) - { - // The expectation is that if the stream is in an error state the write won't actually happen. - return; - } - - auto availableSpace = static_cast(CalculateDiff(currentPosition, m_maximumSize)); - - if (currentLog.size() > availableSpace) - { - WrapLogFile(); - return; - } - } - - void FileLogger::WrapLogFile() - { - m_stream.seekp(m_headersEnd); - // Yes, we may go over the size limit slightly due to this and the unaccounted for newlines - m_stream << ToLogLine(Channel::Core, Level::Info, "--- log file has wrapped ---") << std::endl; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerFileLogger.h" + +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerDateTime.h" +#include "Public/winget/UserSettings.h" +#include +#include + + +namespace AppInstaller::Logging +{ + using namespace std::string_view_literals; + using namespace std::chrono_literals; + + namespace + { + static constexpr std::string_view s_fileLoggerDefaultFilePrefix = "WinGet"sv; + static constexpr std::string_view s_fileLoggerDefaultFileExt = ".log"sv; + + // Send to a string first to create a single block to write to a file. + std::string ToLogLine(Channel channel, Level level, std::string_view message) + { + std::stringstream strstr; + strstr << std::chrono::system_clock::now() << " <" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message; + return std::move(strstr).str(); + } + + // Determines the difference between the given position and the maximum as an offset. + std::ofstream::off_type CalculateDiff(const std::ofstream::pos_type& position, std::ofstream::off_type maximum) + { + auto offsetPosition = static_cast(position); + return maximum > offsetPosition ? maximum - offsetPosition : 0; + } + } + + FileLogger::FileLogger() : FileLogger(s_fileLoggerDefaultFilePrefix) {} + + FileLogger::FileLogger(const std::filesystem::path& filePath) + { + m_name = GetNameForPath(filePath); + m_filePath = filePath; + InitializeDefaultMaximumFileSize(); + OpenFileLoggerStream(); + } + + FileLogger::FileLogger(const std::string_view fileNamePrefix) + { + m_name = "file"; + m_filePath = Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation); + m_filePath /= fileNamePrefix.data() + ('-' + Utility::GetCurrentTimeForFilename() + s_fileLoggerDefaultFileExt.data()); + InitializeDefaultMaximumFileSize(); + OpenFileLoggerStream(); + } + + FileLogger::~FileLogger() + { + m_stream.flush(); + // When std::ofstream is constructed from an existing File handle, it does not call fclose on destruction + // Only calling close() explicitly will close the file handle. + m_stream.close(); + } + + FileLogger& FileLogger::SetMaximumSize(std::ofstream::off_type maximumSize) + { + THROW_HR_IF(E_INVALIDARG, maximumSize < 0); + m_maximumSize = maximumSize; + return *this; + } + + std::string FileLogger::GetNameForPath(const std::filesystem::path& filePath) + { + using namespace std::string_literals; + return "file :: "s + filePath.u8string(); + } + + std::string_view FileLogger::DefaultPrefix() + { + return s_fileLoggerDefaultFilePrefix; + } + + std::string_view FileLogger::DefaultExt() + { + return s_fileLoggerDefaultFileExt; + } + + std::string FileLogger::GetName() const + { + return m_name; + } + + void FileLogger::Write(Channel channel, Level level, std::string_view message) noexcept try + { + std::string log = ToLogLine(channel, level, message); + WriteDirect(channel, level, log); + } + catch (...) {} + + void FileLogger::WriteDirect(Channel, Level, std::string_view message) noexcept try + { + HandleMaximumFileSize(message); + m_stream << message << std::endl; + } + catch (...) {} + + void FileLogger::SetTag(Tag tag) noexcept try + { + if (tag == Tag::HeadersComplete) + { + auto currentPosition = m_stream.tellp(); + if (currentPosition != std::ofstream::pos_type{ -1 }) + { + m_headersEnd = currentPosition; + } + } + } + catch (...) {} + + void FileLogger::Add() + { + Log().AddLogger(std::make_unique()); + } + + void FileLogger::Add(const std::filesystem::path& filePath) + { + Log().AddLogger(std::make_unique(filePath)); + } + + void FileLogger::Add(std::string_view fileNamePrefix) + { + Log().AddLogger(std::make_unique(fileNamePrefix)); + } + + void FileLogger::BeginCleanup() + { + BeginCleanup(Runtime::GetPathTo(Runtime::PathName::DefaultLogLocation)); + } + + void FileLogger::BeginCleanup(const std::filesystem::path& filePath) + { + std::thread([filePath]() + { + try + { + const auto& settings = Settings::User(); + + Filesystem::FileLimits fileLimits; + fileLimits.Age = settings.Get(); + fileLimits.TotalSizeInMB = settings.Get(); + fileLimits.Count = settings.Get(); + + auto filesInPath = Filesystem::GetFileInfoFor(filePath); + Filesystem::FilterToFilesExceedingLimits(filesInPath, fileLimits); + + for (const auto& file : filesInPath) + { + std::filesystem::remove(file.Path); + } + } + // Just throw out everything + catch (...) {} + }).detach(); + } + + void FileLogger::OpenFileLoggerStream() + { + // Prevent other writers to our log file, but allow readers + FILE* filePtr = _wfsopen(m_filePath.wstring().c_str(), L"w", _SH_DENYWR); + + if (filePtr) + { + auto closeFile = wil::scope_exit([&]() { fclose(filePtr); }); + + // Prevent inheritance to ensure log file handle is not opened by other processes + THROW_IF_WIN32_BOOL_FALSE(SetHandleInformation(reinterpret_cast(_get_osfhandle(_fileno(filePtr))), HANDLE_FLAG_INHERIT, 0)); + + m_stream = std::ofstream{ filePtr }; + closeFile.release(); + } + else + { + AICLI_LOG(Core, Error, << "Failed to open log file " << m_filePath.u8string()); + throw std::system_error(errno, std::generic_category()); + } + } + + void FileLogger::InitializeDefaultMaximumFileSize() + { + m_maximumSize = static_cast(Settings::User().Get()) << 20; + } + + void FileLogger::HandleMaximumFileSize(std::string_view& currentLog) + { + if (m_maximumSize == 0) + { + return; + } + + auto maximumLogSize = static_cast(CalculateDiff(m_headersEnd, m_maximumSize)); + + // In the event that a single log is larger than the maximum + if (currentLog.size() > maximumLogSize) + { + currentLog = currentLog.substr(0, maximumLogSize); + WrapLogFile(); + return; + } + + auto currentPosition = m_stream.tellp(); + if (currentPosition == std::ofstream::pos_type{ -1 }) + { + // The expectation is that if the stream is in an error state the write won't actually happen. + return; + } + + auto availableSpace = static_cast(CalculateDiff(currentPosition, m_maximumSize)); + + if (currentLog.size() > availableSpace) + { + WrapLogFile(); + return; + } + } + + void FileLogger::WrapLogFile() + { + m_stream.seekp(m_headersEnd); + // Yes, we may go over the size limit slightly due to this and the unaccounted for newlines + m_stream << ToLogLine(Channel::Core, Level::Info, "--- log file has wrapped ---") << std::endl; + } +} diff --git a/src/AppInstallerCommonCore/FolderFileWatcher.cpp b/src/AppInstallerCommonCore/FolderFileWatcher.cpp index 9f42cc69f7..62073bfd5a 100644 --- a/src/AppInstallerCommonCore/FolderFileWatcher.cpp +++ b/src/AppInstallerCommonCore/FolderFileWatcher.cpp @@ -1,68 +1,68 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/FolderFileWatcher.h" -#include "AppInstallerStrings.h" - -namespace AppInstaller::Utility -{ - FolderFileWatcher::FolderFileWatcher(const std::filesystem::path& path, const std::optional& ext) : - m_path(path), m_ext(ext), m_changeReader{} - { - } - - void FolderFileWatcher::Start() - { - m_files.clear(); - m_changeReader = wil::make_folder_change_reader(m_path.c_str(), - true, - wil::FolderChangeEvents::FileName, - [this](wil::FolderChangeEvent changeEvent, PCWSTR filePath) - { - switch (changeEvent) - { - // The file was added to the directory. - case wil::FolderChangeEvent::Added: - // The file was renamed and this is the new name. - case wil::FolderChangeEvent::RenameNewName: - { - std::filesystem::path path(filePath); - if (!m_ext.has_value() || Utility::CaseInsensitiveEquals(path.extension().u8string(), *m_ext)) - { - m_files.emplace(path); - } - break; - } - - // The file was removed from the directory. - case wil::FolderChangeEvent::Removed: - // The file was renamed and this is the old name. - case wil::FolderChangeEvent::RenameOldName: - { - std::filesystem::path path(filePath); - if (!m_ext.has_value() || Utility::CaseInsensitiveEquals(path.extension().u8string(), *m_ext)) - { - auto it = m_files.find(path); - if (it != m_files.cend()) - { - m_files.erase(it); - } - } - break; - } - - // The file was modified. This can be a change in the time stamp or attributes. - case wil::FolderChangeEvent::Modified: - // A change happens but it got lost. The result of the IoCompletionCallback is ERROR_NOTIFY_ENUM_DIR. - case wil::FolderChangeEvent::ChangesLost: - default: - break; - } - }); - } - - void FolderFileWatcher::Stop() - { - m_changeReader.reset(); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/FolderFileWatcher.h" +#include "AppInstallerStrings.h" + +namespace AppInstaller::Utility +{ + FolderFileWatcher::FolderFileWatcher(const std::filesystem::path& path, const std::optional& ext) : + m_path(path), m_ext(ext), m_changeReader{} + { + } + + void FolderFileWatcher::Start() + { + m_files.clear(); + m_changeReader = wil::make_folder_change_reader(m_path.c_str(), + true, + wil::FolderChangeEvents::FileName, + [this](wil::FolderChangeEvent changeEvent, PCWSTR filePath) + { + switch (changeEvent) + { + // The file was added to the directory. + case wil::FolderChangeEvent::Added: + // The file was renamed and this is the new name. + case wil::FolderChangeEvent::RenameNewName: + { + std::filesystem::path path(filePath); + if (!m_ext.has_value() || Utility::CaseInsensitiveEquals(path.extension().u8string(), *m_ext)) + { + m_files.emplace(path); + } + break; + } + + // The file was removed from the directory. + case wil::FolderChangeEvent::Removed: + // The file was renamed and this is the old name. + case wil::FolderChangeEvent::RenameOldName: + { + std::filesystem::path path(filePath); + if (!m_ext.has_value() || Utility::CaseInsensitiveEquals(path.extension().u8string(), *m_ext)) + { + auto it = m_files.find(path); + if (it != m_files.cend()) + { + m_files.erase(it); + } + } + break; + } + + // The file was modified. This can be a change in the time stamp or attributes. + case wil::FolderChangeEvent::Modified: + // A change happens but it got lost. The result of the IoCompletionCallback is ERROR_NOTIFY_ENUM_DIR. + case wil::FolderChangeEvent::ChangesLost: + default: + break; + } + }); + } + + void FolderFileWatcher::Stop() + { + m_changeReader.reset(); + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/HttpClientHelper.cpp b/src/AppInstallerCommonCore/HttpClientHelper.cpp index 5d5c783649..ac895185d0 100644 --- a/src/AppInstallerCommonCore/HttpClientHelper.cpp +++ b/src/AppInstallerCommonCore/HttpClientHelper.cpp @@ -1,286 +1,286 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include -#include -#include - -namespace AppInstaller::Http -{ - namespace - { - // If the caller does not pass in a user agent header, put the default one on the request. - void EnsureDefaultUserAgent(web::http::http_request& request) - { - static utility::string_t c_defaultUserAgent = Utility::ConvertToUTF16(AppInstaller::Runtime::GetDefaultUserAgent()); - - if (!request.headers().has(web::http::header_names::user_agent)) - { - request.headers().add(web::http::header_names::user_agent, c_defaultUserAgent); - } - } - - void NativeHandleServerCertificateValidation(web::http::client::native_handle handle, const Certificates::PinningConfiguration& pinningConfiguration, ThreadLocalStorage::ThreadGlobals* threadGlobals) - { - decltype(threadGlobals->SetForCurrentThread()) previousThreadGlobals; - if (threadGlobals) - { - previousThreadGlobals = threadGlobals->SetForCurrentThread(); - } - - HINTERNET requestHandle = reinterpret_cast(handle); - - // Get certificate and pass along to pinning config - wil::unique_cert_context certContext; - DWORD bufferSize = sizeof(&certContext); - THROW_IF_WIN32_BOOL_FALSE(WinHttpQueryOption(requestHandle, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &certContext, &bufferSize)); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH, !pinningConfiguration.Validate(certContext.get())); - } - - std::chrono::seconds GetRetryAfter(const web::http::http_headers& headers) - { - auto retryAfterHeader = headers.find(web::http::header_names::retry_after); - if (retryAfterHeader != headers.end()) - { - return AppInstaller::Utility::GetRetryAfter(retryAfterHeader->second.c_str()); - } - - return 0s; - } - } - - HttpClientHelper::HttpClientHelper(std::shared_ptr stage) - : m_defaultRequestHandlerStage(std::move(stage)) - { - const auto& proxyUri = Settings::Network().GetProxyUri(); - if (proxyUri) - { - AICLI_LOG(Repo, Info, << "Setting proxy for REST HTTP Client helper to " << proxyUri.value()); - m_clientConfig.set_proxy(web::web_proxy{ Utility::ConvertToUTF16(proxyUri.value()) }); - } - else - { - AICLI_LOG(Repo, Info, << "REST HTTP Client helper does not use proxy"); - } - } - - pplx::task HttpClientHelper::Post( - const utility::string_t& uri, - const web::json::value& body, - const HttpClientHelper::HttpRequestHeaders& headers, - const HttpClientHelper::HttpRequestHeaders& authHeaders) const - { - AICLI_LOG(Repo, Info, << "Sending http POST request to: " << utility::conversions::to_utf8string(uri)); - web::http::client::http_client client = GetClient(uri); - web::http::http_request request{ web::http::methods::POST }; - request.headers().set_content_type(web::http::details::mime_types::application_json); - request.set_body(body.serialize()); - - // Add headers - for (auto& pair : headers) - { - request.headers().add(pair.first, pair.second); - } - EnsureDefaultUserAgent(request); - - AICLI_LOG(Repo, Verbose, << "Http POST request details:\n" << utility::conversions::to_utf8string(request.to_string())); - - // Add auth headers after logging - for (auto& pair : authHeaders) - { - request.headers().add(pair.first, pair.second); - } - - return client.request(request); - } - - std::optional HttpClientHelper::HandlePost( - const utility::string_t& uri, - const web::json::value& body, - const HttpClientHelper::HttpRequestHeaders& headers, - const HttpClientHelper::HttpRequestHeaders& authHeaders, - const HttpResponseHandler& customHandler) const try - { - web::http::http_response httpResponse; - Post(uri, body, headers, authHeaders).then([&httpResponse](const web::http::http_response& response) - { - httpResponse = response; - }).wait(); - - if (customHandler) - { - auto handlerResult = customHandler(httpResponse); - if (!handlerResult.UseDefaultHandling) - { - return std::move(handlerResult.Result); - } - } - - return ValidateAndExtractResponse(httpResponse); - } - catch (web::http::http_exception& exception) - { - RethrowAsWilException(exception); - } - - pplx::task HttpClientHelper::Get( - const utility::string_t& uri, - const HttpClientHelper::HttpRequestHeaders& headers, - const HttpClientHelper::HttpRequestHeaders& authHeaders) const - { - AICLI_LOG(Repo, Info, << "Sending http GET request to: " << utility::conversions::to_utf8string(uri)); - web::http::client::http_client client = GetClient(uri); - web::http::http_request request{ web::http::methods::GET }; - request.headers().set_content_type(web::http::details::mime_types::application_json); - - // Add headers - for (auto& pair : headers) - { - request.headers().add(pair.first, pair.second); - } - EnsureDefaultUserAgent(request); - - AICLI_LOG(Repo, Verbose, << "Http GET request details:\n" << utility::conversions::to_utf8string(request.to_string())); - - // Add auth headers after logging - for (auto& pair : authHeaders) - { - request.headers().add(pair.first, pair.second); - } - - return client.request(request); - } - - std::optional HttpClientHelper::HandleGet( - const utility::string_t& uri, - const HttpClientHelper::HttpRequestHeaders& headers, - const HttpClientHelper::HttpRequestHeaders& authHeaders, - const HttpResponseHandler& customHandler) const try - { - web::http::http_response httpResponse; - Get(uri, headers, authHeaders).then([&httpResponse](const web::http::http_response& response) - { - httpResponse = response; - }).wait(); - - if (customHandler) - { - auto handlerResult = customHandler(httpResponse); - if (!handlerResult.UseDefaultHandling) - { - return std::move(handlerResult.Result); - } - } - - return ValidateAndExtractResponse(httpResponse); - } - catch (web::http::http_exception& exception) - { - RethrowAsWilException(exception); - } - - void HttpClientHelper::SetPinningConfiguration(const Certificates::PinningConfiguration& configuration, std::shared_ptr threadGlobals) - { - m_clientConfig.set_nativehandle_servercertificate_validation([pinConfig = configuration, globals = std::move(threadGlobals)](web::http::client::native_handle handle) - { - NativeHandleServerCertificateValidation(handle, pinConfig, globals.get()); - }); - } - - web::http::client::http_client HttpClientHelper::GetClient(const utility::string_t& uri) const - { - web::http::client::http_client client{ uri, m_clientConfig }; - - // Add default custom handlers if any. - if (m_defaultRequestHandlerStage) - { - client.add_handler(m_defaultRequestHandlerStage); - } - - return client; - } - - std::optional HttpClientHelper::ValidateAndExtractResponse(const web::http::http_response& response) const - { - AICLI_LOG(Repo, Info, << "Response status: " << response.status_code()); - // Ensure that we wait for the content to be ready before we log it; otherwise it will be truncated. - AICLI_LOG_LARGE_STRING(Repo, Verbose, << "Response details:", - response.content_ready().then([&](const web::http::http_response&) { return utility::conversions::to_utf8string(response.to_string()); }).get()); - - std::optional result; - switch (response.status_code()) - { - case web::http::status_codes::OK: - result = ExtractJsonResponse(response); - break; - - case web::http::status_codes::NotFound: - THROW_HR(APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); - - case web::http::status_codes::NoContent: - result = {}; - break; - - case web::http::status_codes::BadRequest: - THROW_HR(APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR); - - case web::http::status_codes::TooManyRequests: - case web::http::status_codes::ServiceUnavailable: - THROW_EXCEPTION(AppInstaller::Utility::ServiceUnavailableException(GetRetryAfter(response.headers()))); - - default: - THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.status_code())); - } - - return result; - } - - std::optional HttpClientHelper::ExtractJsonResponse(const web::http::http_response& response) const - { - utility::string_t contentType = response.headers().content_type(); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE, - !contentType._Starts_with(web::http::details::mime_types::application_json)); - - return response.extract_json().get(); - } - - [[noreturn]] void HttpClientHelper::RethrowAsWilException(const web::http::http_exception& exception) - { - // Some http_exceptions have no error code; default to REST internal error. - HRESULT toThrow = APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR; - - // 99% of the time this code comes from GetLastError. - // In a few cases it will be 400; as in the HTTP status code. - // Since that is the one case that http_client_winhttp.cpp uses, we map it specifically. - // In the event that this makes no sense, ERROR_THREAD_MODE_ALREADY_BACKGROUND is Win32 error 400. - int errorValue = exception.error_code().value(); - if (errorValue == web::http::status_codes::BadRequest) - { - toThrow = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, web::http::status_codes::BadRequest); - } - else if (errorValue) - { - toThrow = HRESULT_FROM_WIN32(errorValue); - } - - // Ensure that sufficient stack space remains to include the message. - // We have seen rare crashes due to running out of stack space in this path - // so we will only include the message if there is enough space. - // PrintLoggingMessage usage in WIL always creates a 4K stack buffer (2K of wchar_t), - // so we leave some on top of that as well as the buffer is kept alive into further calls. - static constexpr size_t s_msgMinimum = 5 * 1024; - - if (Runtime::IsStackAvailable(s_msgMinimum)) - { - THROW_HR_MSG(toThrow, "%hs", exception.what()); - } - else - { - THROW_HR(toThrow); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include +#include +#include + +namespace AppInstaller::Http +{ + namespace + { + // If the caller does not pass in a user agent header, put the default one on the request. + void EnsureDefaultUserAgent(web::http::http_request& request) + { + static utility::string_t c_defaultUserAgent = Utility::ConvertToUTF16(AppInstaller::Runtime::GetDefaultUserAgent()); + + if (!request.headers().has(web::http::header_names::user_agent)) + { + request.headers().add(web::http::header_names::user_agent, c_defaultUserAgent); + } + } + + void NativeHandleServerCertificateValidation(web::http::client::native_handle handle, const Certificates::PinningConfiguration& pinningConfiguration, ThreadLocalStorage::ThreadGlobals* threadGlobals) + { + decltype(threadGlobals->SetForCurrentThread()) previousThreadGlobals; + if (threadGlobals) + { + previousThreadGlobals = threadGlobals->SetForCurrentThread(); + } + + HINTERNET requestHandle = reinterpret_cast(handle); + + // Get certificate and pass along to pinning config + wil::unique_cert_context certContext; + DWORD bufferSize = sizeof(&certContext); + THROW_IF_WIN32_BOOL_FALSE(WinHttpQueryOption(requestHandle, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &certContext, &bufferSize)); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH, !pinningConfiguration.Validate(certContext.get())); + } + + std::chrono::seconds GetRetryAfter(const web::http::http_headers& headers) + { + auto retryAfterHeader = headers.find(web::http::header_names::retry_after); + if (retryAfterHeader != headers.end()) + { + return AppInstaller::Utility::GetRetryAfter(retryAfterHeader->second.c_str()); + } + + return 0s; + } + } + + HttpClientHelper::HttpClientHelper(std::shared_ptr stage) + : m_defaultRequestHandlerStage(std::move(stage)) + { + const auto& proxyUri = Settings::Network().GetProxyUri(); + if (proxyUri) + { + AICLI_LOG(Repo, Info, << "Setting proxy for REST HTTP Client helper to " << proxyUri.value()); + m_clientConfig.set_proxy(web::web_proxy{ Utility::ConvertToUTF16(proxyUri.value()) }); + } + else + { + AICLI_LOG(Repo, Info, << "REST HTTP Client helper does not use proxy"); + } + } + + pplx::task HttpClientHelper::Post( + const utility::string_t& uri, + const web::json::value& body, + const HttpClientHelper::HttpRequestHeaders& headers, + const HttpClientHelper::HttpRequestHeaders& authHeaders) const + { + AICLI_LOG(Repo, Info, << "Sending http POST request to: " << utility::conversions::to_utf8string(uri)); + web::http::client::http_client client = GetClient(uri); + web::http::http_request request{ web::http::methods::POST }; + request.headers().set_content_type(web::http::details::mime_types::application_json); + request.set_body(body.serialize()); + + // Add headers + for (auto& pair : headers) + { + request.headers().add(pair.first, pair.second); + } + EnsureDefaultUserAgent(request); + + AICLI_LOG(Repo, Verbose, << "Http POST request details:\n" << utility::conversions::to_utf8string(request.to_string())); + + // Add auth headers after logging + for (auto& pair : authHeaders) + { + request.headers().add(pair.first, pair.second); + } + + return client.request(request); + } + + std::optional HttpClientHelper::HandlePost( + const utility::string_t& uri, + const web::json::value& body, + const HttpClientHelper::HttpRequestHeaders& headers, + const HttpClientHelper::HttpRequestHeaders& authHeaders, + const HttpResponseHandler& customHandler) const try + { + web::http::http_response httpResponse; + Post(uri, body, headers, authHeaders).then([&httpResponse](const web::http::http_response& response) + { + httpResponse = response; + }).wait(); + + if (customHandler) + { + auto handlerResult = customHandler(httpResponse); + if (!handlerResult.UseDefaultHandling) + { + return std::move(handlerResult.Result); + } + } + + return ValidateAndExtractResponse(httpResponse); + } + catch (web::http::http_exception& exception) + { + RethrowAsWilException(exception); + } + + pplx::task HttpClientHelper::Get( + const utility::string_t& uri, + const HttpClientHelper::HttpRequestHeaders& headers, + const HttpClientHelper::HttpRequestHeaders& authHeaders) const + { + AICLI_LOG(Repo, Info, << "Sending http GET request to: " << utility::conversions::to_utf8string(uri)); + web::http::client::http_client client = GetClient(uri); + web::http::http_request request{ web::http::methods::GET }; + request.headers().set_content_type(web::http::details::mime_types::application_json); + + // Add headers + for (auto& pair : headers) + { + request.headers().add(pair.first, pair.second); + } + EnsureDefaultUserAgent(request); + + AICLI_LOG(Repo, Verbose, << "Http GET request details:\n" << utility::conversions::to_utf8string(request.to_string())); + + // Add auth headers after logging + for (auto& pair : authHeaders) + { + request.headers().add(pair.first, pair.second); + } + + return client.request(request); + } + + std::optional HttpClientHelper::HandleGet( + const utility::string_t& uri, + const HttpClientHelper::HttpRequestHeaders& headers, + const HttpClientHelper::HttpRequestHeaders& authHeaders, + const HttpResponseHandler& customHandler) const try + { + web::http::http_response httpResponse; + Get(uri, headers, authHeaders).then([&httpResponse](const web::http::http_response& response) + { + httpResponse = response; + }).wait(); + + if (customHandler) + { + auto handlerResult = customHandler(httpResponse); + if (!handlerResult.UseDefaultHandling) + { + return std::move(handlerResult.Result); + } + } + + return ValidateAndExtractResponse(httpResponse); + } + catch (web::http::http_exception& exception) + { + RethrowAsWilException(exception); + } + + void HttpClientHelper::SetPinningConfiguration(const Certificates::PinningConfiguration& configuration, std::shared_ptr threadGlobals) + { + m_clientConfig.set_nativehandle_servercertificate_validation([pinConfig = configuration, globals = std::move(threadGlobals)](web::http::client::native_handle handle) + { + NativeHandleServerCertificateValidation(handle, pinConfig, globals.get()); + }); + } + + web::http::client::http_client HttpClientHelper::GetClient(const utility::string_t& uri) const + { + web::http::client::http_client client{ uri, m_clientConfig }; + + // Add default custom handlers if any. + if (m_defaultRequestHandlerStage) + { + client.add_handler(m_defaultRequestHandlerStage); + } + + return client; + } + + std::optional HttpClientHelper::ValidateAndExtractResponse(const web::http::http_response& response) const + { + AICLI_LOG(Repo, Info, << "Response status: " << response.status_code()); + // Ensure that we wait for the content to be ready before we log it; otherwise it will be truncated. + AICLI_LOG_LARGE_STRING(Repo, Verbose, << "Response details:", + response.content_ready().then([&](const web::http::http_response&) { return utility::conversions::to_utf8string(response.to_string()); }).get()); + + std::optional result; + switch (response.status_code()) + { + case web::http::status_codes::OK: + result = ExtractJsonResponse(response); + break; + + case web::http::status_codes::NotFound: + THROW_HR(APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND); + + case web::http::status_codes::NoContent: + result = {}; + break; + + case web::http::status_codes::BadRequest: + THROW_HR(APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR); + + case web::http::status_codes::TooManyRequests: + case web::http::status_codes::ServiceUnavailable: + THROW_EXCEPTION(AppInstaller::Utility::ServiceUnavailableException(GetRetryAfter(response.headers()))); + + default: + THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.status_code())); + } + + return result; + } + + std::optional HttpClientHelper::ExtractJsonResponse(const web::http::http_response& response) const + { + utility::string_t contentType = response.headers().content_type(); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE, + !contentType._Starts_with(web::http::details::mime_types::application_json)); + + return response.extract_json().get(); + } + + [[noreturn]] void HttpClientHelper::RethrowAsWilException(const web::http::http_exception& exception) + { + // Some http_exceptions have no error code; default to REST internal error. + HRESULT toThrow = APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR; + + // 99% of the time this code comes from GetLastError. + // In a few cases it will be 400; as in the HTTP status code. + // Since that is the one case that http_client_winhttp.cpp uses, we map it specifically. + // In the event that this makes no sense, ERROR_THREAD_MODE_ALREADY_BACKGROUND is Win32 error 400. + int errorValue = exception.error_code().value(); + if (errorValue == web::http::status_codes::BadRequest) + { + toThrow = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, web::http::status_codes::BadRequest); + } + else if (errorValue) + { + toThrow = HRESULT_FROM_WIN32(errorValue); + } + + // Ensure that sufficient stack space remains to include the message. + // We have seen rare crashes due to running out of stack space in this path + // so we will only include the message if there is enough space. + // PrintLoggingMessage usage in WIL always creates a 4K stack buffer (2K of wchar_t), + // so we leave some on top of that as well as the buffer is kept alive into further calls. + static constexpr size_t s_msgMinimum = 5 * 1024; + + if (Runtime::IsStackAvailable(s_msgMinimum)) + { + THROW_HR_MSG(toThrow, "%hs", exception.what()); + } + else + { + THROW_HR(toThrow); + } + } +} diff --git a/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.cpp b/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.cpp index d5e1401965..1da48efa8c 100644 --- a/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.cpp +++ b/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.cpp @@ -1,190 +1,190 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "Public/AppInstallerStrings.h" -#include "HttpClientWrapper.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerDownloader.h" - -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Security::Cryptography; -using namespace winrt::Windows::Storage; -using namespace winrt::Windows::Storage::Streams; -using namespace winrt::Windows::Web::Http; -using namespace winrt::Windows::Web::Http::Headers; -using namespace winrt::Windows::Web::Http::Filters; - -// Note: this class is used by the HttpRandomAccessStream which is passed to the AppxPackaging COM API -// All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions. -// The HRESULTs will be mapped to UI error code by the appropriate component -namespace AppInstaller::Utility::HttpStream -{ - std::future> HttpClientWrapper::CreateAsync(const Uri& uri) - { - // TODO: Use proxy info. HttpClient does not support using a custom proxy, only using the system-wide one. - std::shared_ptr instance = std::make_shared(); - - // Use an HTTP filter to disable the default caching behavior and use the Most Recent caching behavior instead - // so we don't use a stale cached resource. Note: this wrapper object is used in the custom HTTP stream implementation - // so this affects the parsing of HTTP-based packages/bundles. - HttpBaseProtocolFilter filter; - filter.CacheControl().ReadBehavior(HttpCacheReadBehavior::MostRecent); - instance->m_httpClient = HttpClient(filter); - instance->m_requestUri = uri; - - instance->m_httpClient.DefaultRequestHeaders().Connection().Clear(); - instance->m_httpClient.DefaultRequestHeaders().Append(L"Connection", L"Keep-Alive"); - instance->m_httpClient.DefaultRequestHeaders().UserAgent().ParseAdd(Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get())); - - co_await instance->PopulateInfoAsync(); - - co_return instance; - } - - // this function will issue a HEAD request to determine the size of the file and the redirect URI - std::future HttpClientWrapper::PopulateInfoAsync() - { - HttpRequestMessage request(HttpMethod::Head(), m_requestUri); - - HttpResponseMessage response = co_await m_httpClient.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead); - - switch (response.StatusCode()) - { - case HttpStatusCode::Ok: - // All good - break; - case HttpStatusCode::TooManyRequests: - case HttpStatusCode::ServiceUnavailable: - { - THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response))); - } - default: - THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode())); - } - - // Get the length from the response - if (response.Content().Headers().HasKey(L"Content-Length")) - { - std::wstring contentLength(response.Content().Headers().Lookup(L"Content-Length")); - m_sizeInBytes = std::stoll(contentLength); - } - else - { - m_sizeInBytes = 0; - } - - // Get the extension from the redirect URI - m_redirectUri = response.RequestMessage().RequestUri(); - - m_contentType = response.Content().Headers().HasKey(L"Content-Type") ? - response.Content().Headers().Lookup(L"Content-Type") - : L""; - - // If the size wasn't resolved try with a GET 0-0 request - if (m_sizeInBytes == 0) - { - co_await SendHttpRequestAsync(0, 1); - } - } - -#ifdef WINGET_DISABLE_FOR_FUZZING -#pragma warning( push ) -#pragma warning( disable : 4714) // HRESULT_FROM_WIN32 marked as forceinline not inlined -#endif - - std::future HttpClientWrapper::SendHttpRequestAsync( - _In_ ULONG64 startPosition, - _In_ UINT32 requestedSizeInBytes) - { - unsigned long long endPosition = 0; - - winrt::check_hresult(ULong64Add(startPosition, requestedSizeInBytes, &endPosition)); - - // Subtracting one should be safe, as the consumer of the stream should not request - // an empty range, so this number can't go negative. - endPosition -= 1; - - std::wstring rangeHeaderValue = L"bytes=" + std::to_wstring(startPosition) + L"-" + std::to_wstring(endPosition); - - HttpRequestMessage request(HttpMethod::Get(), m_requestUri); - request.Headers().Append(L"Range", rangeHeaderValue); - - if (!Utility::IsEmptyOrWhitespace(m_etagHeader)) - { - request.Headers().Append(L"If-Match", m_etagHeader); - } - - if (!Utility::IsEmptyOrWhitespace(m_lastModifiedHeader)) - { - request.Headers().Append(L"If-Unmodified-Since", m_lastModifiedHeader); - } - - HttpResponseMessage response = co_await m_httpClient.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead); - HttpContentHeaderCollection contentHeaders = response.Content().Headers(); - - switch (response.StatusCode()) - { - case HttpStatusCode::Ok: - case HttpStatusCode::PartialContent: - // All good - break; - case HttpStatusCode::TooManyRequests: - case HttpStatusCode::ServiceUnavailable: - { - THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response))); - } - default: - THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode())); - } - - if (response.StatusCode() != HttpStatusCode::PartialContent && startPosition != 0) - { - // throw HRESULT used for range-request error - THROW_HR(HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED)); - } - - if (response.Headers().HasKey(L"Accept-Ranges") && - Utility::ToLower(std::wstring(response.Headers().Lookup(L"Accept-Ranges"))) == L"none") - { - // throw HRESULT used for range-request error - THROW_HR(HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED)); - } - - if (Utility::IsEmptyOrWhitespace(m_etagHeader) && response.Headers().HasKey(L"ETag")) - { - m_etagHeader = response.Headers().Lookup(L"ETag"); - } - - if (Utility::IsEmptyOrWhitespace(m_lastModifiedHeader) && contentHeaders.HasKey(L"Last-Modified")) - { - m_lastModifiedHeader = contentHeaders.Lookup(L"Last-Modified"); - } - - // If we don't know the size, parse it from the Content-Range field. - if (m_sizeInBytes == 0 && contentHeaders.HasKey(L"Content-Range")) - { - // format: a-b/x where x is either a number or * - std::wstring contentRange(contentHeaders.Lookup(L"Content-Range")); - std::wstring length = contentRange.substr(contentRange.find(L"/") + 1); - m_sizeInBytes = (length == L"*") ? 0 : std::stoll(length); - } - - co_return co_await response.Content().ReadAsBufferAsync(); - } - -#ifdef WINGET_DISABLE_FOR_FUZZING -#pragma warning( pop ) -#endif - - std::future HttpClientWrapper::DownloadRangeAsync( - const ULONG64 startPosition, - const UINT32 requestedSizeInBytes, - const InputStreamOptions& options) - { - std::vector byteArray(requestedSizeInBytes); - IBuffer buffer = CryptographicBuffer::CreateFromByteArray(byteArray); - - co_return co_await SendHttpRequestAsync(startPosition, requestedSizeInBytes); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "Public/AppInstallerStrings.h" +#include "HttpClientWrapper.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerDownloader.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Security::Cryptography; +using namespace winrt::Windows::Storage; +using namespace winrt::Windows::Storage::Streams; +using namespace winrt::Windows::Web::Http; +using namespace winrt::Windows::Web::Http::Headers; +using namespace winrt::Windows::Web::Http::Filters; + +// Note: this class is used by the HttpRandomAccessStream which is passed to the AppxPackaging COM API +// All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions. +// The HRESULTs will be mapped to UI error code by the appropriate component +namespace AppInstaller::Utility::HttpStream +{ + std::future> HttpClientWrapper::CreateAsync(const Uri& uri) + { + // TODO: Use proxy info. HttpClient does not support using a custom proxy, only using the system-wide one. + std::shared_ptr instance = std::make_shared(); + + // Use an HTTP filter to disable the default caching behavior and use the Most Recent caching behavior instead + // so we don't use a stale cached resource. Note: this wrapper object is used in the custom HTTP stream implementation + // so this affects the parsing of HTTP-based packages/bundles. + HttpBaseProtocolFilter filter; + filter.CacheControl().ReadBehavior(HttpCacheReadBehavior::MostRecent); + instance->m_httpClient = HttpClient(filter); + instance->m_requestUri = uri; + + instance->m_httpClient.DefaultRequestHeaders().Connection().Clear(); + instance->m_httpClient.DefaultRequestHeaders().Append(L"Connection", L"Keep-Alive"); + instance->m_httpClient.DefaultRequestHeaders().UserAgent().ParseAdd(Utility::ConvertToUTF16(Runtime::GetDefaultUserAgent().get())); + + co_await instance->PopulateInfoAsync(); + + co_return instance; + } + + // this function will issue a HEAD request to determine the size of the file and the redirect URI + std::future HttpClientWrapper::PopulateInfoAsync() + { + HttpRequestMessage request(HttpMethod::Head(), m_requestUri); + + HttpResponseMessage response = co_await m_httpClient.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead); + + switch (response.StatusCode()) + { + case HttpStatusCode::Ok: + // All good + break; + case HttpStatusCode::TooManyRequests: + case HttpStatusCode::ServiceUnavailable: + { + THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response))); + } + default: + THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode())); + } + + // Get the length from the response + if (response.Content().Headers().HasKey(L"Content-Length")) + { + std::wstring contentLength(response.Content().Headers().Lookup(L"Content-Length")); + m_sizeInBytes = std::stoll(contentLength); + } + else + { + m_sizeInBytes = 0; + } + + // Get the extension from the redirect URI + m_redirectUri = response.RequestMessage().RequestUri(); + + m_contentType = response.Content().Headers().HasKey(L"Content-Type") ? + response.Content().Headers().Lookup(L"Content-Type") + : L""; + + // If the size wasn't resolved try with a GET 0-0 request + if (m_sizeInBytes == 0) + { + co_await SendHttpRequestAsync(0, 1); + } + } + +#ifdef WINGET_DISABLE_FOR_FUZZING +#pragma warning( push ) +#pragma warning( disable : 4714) // HRESULT_FROM_WIN32 marked as forceinline not inlined +#endif + + std::future HttpClientWrapper::SendHttpRequestAsync( + _In_ ULONG64 startPosition, + _In_ UINT32 requestedSizeInBytes) + { + unsigned long long endPosition = 0; + + winrt::check_hresult(ULong64Add(startPosition, requestedSizeInBytes, &endPosition)); + + // Subtracting one should be safe, as the consumer of the stream should not request + // an empty range, so this number can't go negative. + endPosition -= 1; + + std::wstring rangeHeaderValue = L"bytes=" + std::to_wstring(startPosition) + L"-" + std::to_wstring(endPosition); + + HttpRequestMessage request(HttpMethod::Get(), m_requestUri); + request.Headers().Append(L"Range", rangeHeaderValue); + + if (!Utility::IsEmptyOrWhitespace(m_etagHeader)) + { + request.Headers().Append(L"If-Match", m_etagHeader); + } + + if (!Utility::IsEmptyOrWhitespace(m_lastModifiedHeader)) + { + request.Headers().Append(L"If-Unmodified-Since", m_lastModifiedHeader); + } + + HttpResponseMessage response = co_await m_httpClient.SendRequestAsync(request, HttpCompletionOption::ResponseHeadersRead); + HttpContentHeaderCollection contentHeaders = response.Content().Headers(); + + switch (response.StatusCode()) + { + case HttpStatusCode::Ok: + case HttpStatusCode::PartialContent: + // All good + break; + case HttpStatusCode::TooManyRequests: + case HttpStatusCode::ServiceUnavailable: + { + THROW_EXCEPTION(ServiceUnavailableException(GetRetryAfter(response))); + } + default: + THROW_HR(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_HTTP, response.StatusCode())); + } + + if (response.StatusCode() != HttpStatusCode::PartialContent && startPosition != 0) + { + // throw HRESULT used for range-request error + THROW_HR(HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED)); + } + + if (response.Headers().HasKey(L"Accept-Ranges") && + Utility::ToLower(std::wstring(response.Headers().Lookup(L"Accept-Ranges"))) == L"none") + { + // throw HRESULT used for range-request error + THROW_HR(HRESULT_FROM_WIN32(ERROR_NO_RANGES_PROCESSED)); + } + + if (Utility::IsEmptyOrWhitespace(m_etagHeader) && response.Headers().HasKey(L"ETag")) + { + m_etagHeader = response.Headers().Lookup(L"ETag"); + } + + if (Utility::IsEmptyOrWhitespace(m_lastModifiedHeader) && contentHeaders.HasKey(L"Last-Modified")) + { + m_lastModifiedHeader = contentHeaders.Lookup(L"Last-Modified"); + } + + // If we don't know the size, parse it from the Content-Range field. + if (m_sizeInBytes == 0 && contentHeaders.HasKey(L"Content-Range")) + { + // format: a-b/x where x is either a number or * + std::wstring contentRange(contentHeaders.Lookup(L"Content-Range")); + std::wstring length = contentRange.substr(contentRange.find(L"/") + 1); + m_sizeInBytes = (length == L"*") ? 0 : std::stoll(length); + } + + co_return co_await response.Content().ReadAsBufferAsync(); + } + +#ifdef WINGET_DISABLE_FOR_FUZZING +#pragma warning( pop ) +#endif + + std::future HttpClientWrapper::DownloadRangeAsync( + const ULONG64 startPosition, + const UINT32 requestedSizeInBytes, + const InputStreamOptions& options) + { + std::vector byteArray(requestedSizeInBytes); + IBuffer buffer = CryptographicBuffer::CreateFromByteArray(byteArray); + + co_return co_await SendHttpRequestAsync(startPosition, requestedSizeInBytes); + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.h b/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.h index 599c171224..9924c48e19 100644 --- a/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.h +++ b/src/AppInstallerCommonCore/HttpStream/HttpClientWrapper.h @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::Utility::HttpStream -{ - // Wrapper around HTTP client. When created, an object of this class will send a HTTP - // head request to determine the size of the data source. - class HttpClientWrapper - { - public: - static std::future> CreateAsync(const winrt::Windows::Foundation::Uri& uri); - - std::future DownloadRangeAsync( - const ULONG64 startPosition, - const UINT32 requestedSizeInBytes, - const winrt::Windows::Storage::Streams::InputStreamOptions& options); - - unsigned long long GetFullFileSize() - { - return m_sizeInBytes; - } - - winrt::Windows::Foundation::Uri GetRedirectUri() - { - return m_redirectUri; - } - - std::wstring GetContentType() - { - return m_contentType; - } - - private: - winrt::Windows::Web::Http::HttpClient m_httpClient; - winrt::Windows::Foundation::Uri m_requestUri = nullptr; - winrt::Windows::Foundation::Uri m_redirectUri = nullptr; - std::wstring m_contentType; - unsigned long long m_sizeInBytes = 0; - std::wstring m_etagHeader; - std::wstring m_lastModifiedHeader; - - std::future PopulateInfoAsync(); - - std::future SendHttpRequestAsync( - _In_ ULONG64 startPosition, - _In_ UINT32 requestedSizeInBytes); - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::Utility::HttpStream +{ + // Wrapper around HTTP client. When created, an object of this class will send a HTTP + // head request to determine the size of the data source. + class HttpClientWrapper + { + public: + static std::future> CreateAsync(const winrt::Windows::Foundation::Uri& uri); + + std::future DownloadRangeAsync( + const ULONG64 startPosition, + const UINT32 requestedSizeInBytes, + const winrt::Windows::Storage::Streams::InputStreamOptions& options); + + unsigned long long GetFullFileSize() + { + return m_sizeInBytes; + } + + winrt::Windows::Foundation::Uri GetRedirectUri() + { + return m_redirectUri; + } + + std::wstring GetContentType() + { + return m_contentType; + } + + private: + winrt::Windows::Web::Http::HttpClient m_httpClient; + winrt::Windows::Foundation::Uri m_requestUri = nullptr; + winrt::Windows::Foundation::Uri m_redirectUri = nullptr; + std::wstring m_contentType; + unsigned long long m_sizeInBytes = 0; + std::wstring m_etagHeader; + std::wstring m_lastModifiedHeader; + + std::future PopulateInfoAsync(); + + std::future SendHttpRequestAsync( + _In_ ULONG64 startPosition, + _In_ UINT32 requestedSizeInBytes); + }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.cpp b/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.cpp index 55cc639792..c8fb2fba63 100644 --- a/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.cpp +++ b/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.cpp @@ -1,246 +1,246 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "HttpLocalCache.h" - -using namespace Windows::Storage::Streams; -using namespace winrt::Windows::Storage::Streams; -using namespace winrt::Windows::Security::Cryptography; - -// Note: this class is used by the HttpRandomAccessStream which is passed to the AppxPackaging COM API -// All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions. -// The HRESULTs will be mapped to UI error code by the appropriate component -namespace AppInstaller::Utility::HttpStream -{ - std::future HttpLocalCache::ReadFromCacheAndDownloadIfNecessaryAsync( - const ULONG64 requestedPosition, - const UINT32 requestedSize, - HttpClientWrapper* httpClientWrapper, - InputStreamOptions httpInputStreamOptions) - { - // Increment cache access counter user for implementing LRU replacement - m_accessCounter++; - - // Find all the pages for the given request, and the pages that are missing - std::vector allPages; - std::vector unsatisfiablePages; - FindCachePages(requestedPosition, requestedSize, allPages, unsatisfiablePages); - - // download the missing pages - co_await DownloadAndSaveToCacheAsync( - unsatisfiablePages, - httpClientWrapper, - httpInputStreamOptions); - - // At this point, everything should be in the cache - IBuffer constructedBuffer = {}; - - for (UINT32 i = 0; i < allPages.size(); i++) - { - UINT64 pageOffset = allPages[i]; - IBuffer cachedPageBuffer = ReadPageFromCache(pageOffset); - constructedBuffer = ConcatenateBuffers(constructedBuffer, cachedPageBuffer); - } - - // trim buffer to match requested range - IBuffer requestedBuffer = TrimBufferToSatisfyRequest( - constructedBuffer, - requestedPosition, - requestedSize, - allPages); - - VacateStaleEntriesFromCache(); - - co_return requestedBuffer; - } - - void HttpLocalCache::FindCachePages( - ULONG64 requestedPosition, - UINT32 requestedSize, - std::vector& allPages, - std::vector& unsatisfiablePages) - { - ULONG64 requestedEndPosition; - ULONG64 currentPageOffset; - winrt::check_hresult(ULong64Add(requestedPosition, requestedSize, &requestedEndPosition)); - winrt::check_hresult(ULong64Mult((requestedPosition / PAGE_SIZE), PAGE_SIZE, ¤tPageOffset)); - - // There's always at least one page for the range - do - { - allPages.push_back(currentPageOffset); - - if (m_localCache.find(currentPageOffset) == m_localCache.end()) - { - unsatisfiablePages.push_back(currentPageOffset); - } - - winrt::check_hresult(ULong64Add(currentPageOffset, PAGE_SIZE, ¤tPageOffset)); - - } while (currentPageOffset < requestedEndPosition); - } - - // Breaks the provided buffer into smaller buffers and saves them to the cache at the corresponding - // page offset position, starting at firstPageOffset. The smaller buffers are all PAGE_SIZE bytes, - // except for the one corresponding to the last page in the file - void HttpLocalCache::SaveBufferToCache(const IBuffer& buffer, const ULONG64 firstPageOffset) - { - UINT32 remainingBufferSize = buffer.Length(); - UINT32 currentBufferIndex = 0; - ULONG64 currentPageOffset = firstPageOffset; - - while (remainingBufferSize > 0) - { - // Extract the sub-buffer - UINT32 currentPageSize = std::min(remainingBufferSize, PAGE_SIZE); - IBuffer currentPageBuffer = CreateTrimmedBuffer(buffer, currentBufferIndex, currentPageSize); - - // Add it to the cache - CachedPage currentPage; - currentPage.lastAccessCounter = m_accessCounter; - currentPage.buffer = currentPageBuffer; - m_localCache[currentPageOffset] = currentPage; - - // update loop vars - winrt::check_hresult(UInt32Sub(remainingBufferSize, currentPageSize, &remainingBufferSize)); - winrt::check_hresult(UInt32Add(currentBufferIndex, currentPageSize, ¤tBufferIndex)); - winrt::check_hresult(ULong64Add(currentPageOffset, PAGE_SIZE, ¤tPageOffset)); - } - } - - IBuffer HttpLocalCache::ReadPageFromCache(const ULONG64 pageOffset) - { - if (!(m_localCache.find(pageOffset) != m_localCache.end())) - { - THROW_HR(E_INVALIDARG); - } - - CachedPage& page = m_localCache[pageOffset]; - page.lastAccessCounter = m_accessCounter; - - return page.buffer; - } - - // Trims a buffer that was constructed (by fetching pages from cache and downloading missing pages) - // in order to satisfy a request and return the exact buffer the consumer asked for. - IBuffer HttpLocalCache::TrimBufferToSatisfyRequest( - const IBuffer& constructedBuffer, - const ULONG64 requestedPosition, - const UINT32 requestedSize, - const std::vector allPages) - { - ULONG64 fullBufferStartOffset = allPages[0]; - - ULONG64 trimmedBufferStartRelativeIndex; - winrt::check_hresult(ULong64Sub(requestedPosition, fullBufferStartOffset, &trimmedBufferStartRelativeIndex)); - - IBuffer requestedBuffer = CreateTrimmedBuffer( - constructedBuffer, - (UINT32)trimmedBufferStartRelativeIndex, // Conversion is safe as buffer size is a UINT32. - requestedSize); - - return requestedBuffer; - } - - // Downloads a chunk of the file, saves it to the cache, and returns the corresponding buffer - // If the requested size is 0, this method returns an empty buffer without making HTTP calls - std::future HttpLocalCache::DownloadAndSaveToCacheAsync( - const std::vector unsatisfiablePages, - HttpClientWrapper* httpClientWrapper, - InputStreamOptions httpInputStreamOptions) - { - // Determine the download job - // To make things easy, we will download the contiguous range that includes all the unsatisfiable ranges. - // Note that in theory, this may include cached pages. However, this situation is rarely expected to happen, - // if at all. The package reader usually reads things in chunks of 64 KB or less, so, we should expect to - // always have up to two satisfiable and unsatisfiable pages in total. - UINT64 fileSize = httpClientWrapper->GetFullFileSize(); - ULONG64 downloadJobStartPosition = 0U; - ULONG64 downloadJobEndPosition = 0U; - ULONG64 downloadJobSize = 0U; - if (unsatisfiablePages.size() > 0U) - { - downloadJobStartPosition = unsatisfiablePages[0]; - ULONG64 lastUnsatisfiableJob = unsatisfiablePages[unsatisfiablePages.size() - 1]; - winrt::check_hresult(ULong64Add(lastUnsatisfiableJob, PAGE_SIZE, &downloadJobEndPosition)); - - // make sure to not overflow file size - downloadJobEndPosition = std::min(downloadJobEndPosition, fileSize); - winrt::check_hresult(ULong64Sub(downloadJobEndPosition, downloadJobStartPosition, &downloadJobSize)); - } - - if (downloadJobSize != 0U) - { - // start download job - IBuffer downloadedBuffer = co_await httpClientWrapper->DownloadRangeAsync( - downloadJobStartPosition, - (UINT32)downloadJobSize, - httpInputStreamOptions); - - SaveBufferToCache(downloadedBuffer, downloadJobStartPosition); - } - } - - void HttpLocalCache::VacateStaleEntriesFromCache() - { - // Copy page offsets into vector and sort by the access counter - std::vector> orderedPageOffsets; - for (auto pageIter = m_localCache.begin(); pageIter != m_localCache.end(); pageIter++) - { - orderedPageOffsets.push_back(std::pair(pageIter->first, pageIter->second.lastAccessCounter)); - } - - // Compare function to sort by access counter - auto cmp = [](std::pair const & a, std::pair const & b) - { - return a.second != b.second ? a.second < b.second : a.first < b.first; - }; - - std::sort(orderedPageOffsets.begin(), orderedPageOffsets.end(), cmp); - - for (auto pageIter = orderedPageOffsets.begin(); pageIter != orderedPageOffsets.end(); pageIter++) - { - if (m_localCache.size() > MAX_PAGES) - { - m_localCache.erase(pageIter->first); - } - else - { - break; - } - } - } - - IBuffer HttpLocalCache::CreateTrimmedBuffer( - const IBuffer& originalBuffer, - UINT32 trimStartIndex, - UINT32 size) - { - uint32_t bufferLength = originalBuffer.Length(); - THROW_HR_IF(E_INVALIDARG, trimStartIndex > bufferLength); - - originalBuffer.as<::IInspectable>(); - - // Get the byte array from the IBuffer object - Microsoft::WRL::ComPtr bufferByteAccess; - ::IInspectable* bufferAbi = (::IInspectable*)winrt::get_abi(originalBuffer); - bufferAbi->QueryInterface(IID_PPV_ARGS(&bufferByteAccess)); - byte* byteBuffer = nullptr; - bufferByteAccess->Buffer(&byteBuffer); - - // Create the array of bytes holding the trimmed bytes - IBuffer trimmedBuffer = CryptographicBuffer::CreateFromByteArray( - { byteBuffer + trimStartIndex, std::min(size, bufferLength - trimStartIndex) }); - - return trimmedBuffer; - } - - IBuffer HttpLocalCache::ConcatenateBuffers(const IBuffer& buffer1, const IBuffer& buffer2) - { - DataWriter writer; - writer.WriteBuffer(buffer1); - writer.WriteBuffer(buffer2); - return writer.DetachBuffer(); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "HttpLocalCache.h" + +using namespace Windows::Storage::Streams; +using namespace winrt::Windows::Storage::Streams; +using namespace winrt::Windows::Security::Cryptography; + +// Note: this class is used by the HttpRandomAccessStream which is passed to the AppxPackaging COM API +// All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions. +// The HRESULTs will be mapped to UI error code by the appropriate component +namespace AppInstaller::Utility::HttpStream +{ + std::future HttpLocalCache::ReadFromCacheAndDownloadIfNecessaryAsync( + const ULONG64 requestedPosition, + const UINT32 requestedSize, + HttpClientWrapper* httpClientWrapper, + InputStreamOptions httpInputStreamOptions) + { + // Increment cache access counter user for implementing LRU replacement + m_accessCounter++; + + // Find all the pages for the given request, and the pages that are missing + std::vector allPages; + std::vector unsatisfiablePages; + FindCachePages(requestedPosition, requestedSize, allPages, unsatisfiablePages); + + // download the missing pages + co_await DownloadAndSaveToCacheAsync( + unsatisfiablePages, + httpClientWrapper, + httpInputStreamOptions); + + // At this point, everything should be in the cache + IBuffer constructedBuffer = {}; + + for (UINT32 i = 0; i < allPages.size(); i++) + { + UINT64 pageOffset = allPages[i]; + IBuffer cachedPageBuffer = ReadPageFromCache(pageOffset); + constructedBuffer = ConcatenateBuffers(constructedBuffer, cachedPageBuffer); + } + + // trim buffer to match requested range + IBuffer requestedBuffer = TrimBufferToSatisfyRequest( + constructedBuffer, + requestedPosition, + requestedSize, + allPages); + + VacateStaleEntriesFromCache(); + + co_return requestedBuffer; + } + + void HttpLocalCache::FindCachePages( + ULONG64 requestedPosition, + UINT32 requestedSize, + std::vector& allPages, + std::vector& unsatisfiablePages) + { + ULONG64 requestedEndPosition; + ULONG64 currentPageOffset; + winrt::check_hresult(ULong64Add(requestedPosition, requestedSize, &requestedEndPosition)); + winrt::check_hresult(ULong64Mult((requestedPosition / PAGE_SIZE), PAGE_SIZE, ¤tPageOffset)); + + // There's always at least one page for the range + do + { + allPages.push_back(currentPageOffset); + + if (m_localCache.find(currentPageOffset) == m_localCache.end()) + { + unsatisfiablePages.push_back(currentPageOffset); + } + + winrt::check_hresult(ULong64Add(currentPageOffset, PAGE_SIZE, ¤tPageOffset)); + + } while (currentPageOffset < requestedEndPosition); + } + + // Breaks the provided buffer into smaller buffers and saves them to the cache at the corresponding + // page offset position, starting at firstPageOffset. The smaller buffers are all PAGE_SIZE bytes, + // except for the one corresponding to the last page in the file + void HttpLocalCache::SaveBufferToCache(const IBuffer& buffer, const ULONG64 firstPageOffset) + { + UINT32 remainingBufferSize = buffer.Length(); + UINT32 currentBufferIndex = 0; + ULONG64 currentPageOffset = firstPageOffset; + + while (remainingBufferSize > 0) + { + // Extract the sub-buffer + UINT32 currentPageSize = std::min(remainingBufferSize, PAGE_SIZE); + IBuffer currentPageBuffer = CreateTrimmedBuffer(buffer, currentBufferIndex, currentPageSize); + + // Add it to the cache + CachedPage currentPage; + currentPage.lastAccessCounter = m_accessCounter; + currentPage.buffer = currentPageBuffer; + m_localCache[currentPageOffset] = currentPage; + + // update loop vars + winrt::check_hresult(UInt32Sub(remainingBufferSize, currentPageSize, &remainingBufferSize)); + winrt::check_hresult(UInt32Add(currentBufferIndex, currentPageSize, ¤tBufferIndex)); + winrt::check_hresult(ULong64Add(currentPageOffset, PAGE_SIZE, ¤tPageOffset)); + } + } + + IBuffer HttpLocalCache::ReadPageFromCache(const ULONG64 pageOffset) + { + if (!(m_localCache.find(pageOffset) != m_localCache.end())) + { + THROW_HR(E_INVALIDARG); + } + + CachedPage& page = m_localCache[pageOffset]; + page.lastAccessCounter = m_accessCounter; + + return page.buffer; + } + + // Trims a buffer that was constructed (by fetching pages from cache and downloading missing pages) + // in order to satisfy a request and return the exact buffer the consumer asked for. + IBuffer HttpLocalCache::TrimBufferToSatisfyRequest( + const IBuffer& constructedBuffer, + const ULONG64 requestedPosition, + const UINT32 requestedSize, + const std::vector allPages) + { + ULONG64 fullBufferStartOffset = allPages[0]; + + ULONG64 trimmedBufferStartRelativeIndex; + winrt::check_hresult(ULong64Sub(requestedPosition, fullBufferStartOffset, &trimmedBufferStartRelativeIndex)); + + IBuffer requestedBuffer = CreateTrimmedBuffer( + constructedBuffer, + (UINT32)trimmedBufferStartRelativeIndex, // Conversion is safe as buffer size is a UINT32. + requestedSize); + + return requestedBuffer; + } + + // Downloads a chunk of the file, saves it to the cache, and returns the corresponding buffer + // If the requested size is 0, this method returns an empty buffer without making HTTP calls + std::future HttpLocalCache::DownloadAndSaveToCacheAsync( + const std::vector unsatisfiablePages, + HttpClientWrapper* httpClientWrapper, + InputStreamOptions httpInputStreamOptions) + { + // Determine the download job + // To make things easy, we will download the contiguous range that includes all the unsatisfiable ranges. + // Note that in theory, this may include cached pages. However, this situation is rarely expected to happen, + // if at all. The package reader usually reads things in chunks of 64 KB or less, so, we should expect to + // always have up to two satisfiable and unsatisfiable pages in total. + UINT64 fileSize = httpClientWrapper->GetFullFileSize(); + ULONG64 downloadJobStartPosition = 0U; + ULONG64 downloadJobEndPosition = 0U; + ULONG64 downloadJobSize = 0U; + if (unsatisfiablePages.size() > 0U) + { + downloadJobStartPosition = unsatisfiablePages[0]; + ULONG64 lastUnsatisfiableJob = unsatisfiablePages[unsatisfiablePages.size() - 1]; + winrt::check_hresult(ULong64Add(lastUnsatisfiableJob, PAGE_SIZE, &downloadJobEndPosition)); + + // make sure to not overflow file size + downloadJobEndPosition = std::min(downloadJobEndPosition, fileSize); + winrt::check_hresult(ULong64Sub(downloadJobEndPosition, downloadJobStartPosition, &downloadJobSize)); + } + + if (downloadJobSize != 0U) + { + // start download job + IBuffer downloadedBuffer = co_await httpClientWrapper->DownloadRangeAsync( + downloadJobStartPosition, + (UINT32)downloadJobSize, + httpInputStreamOptions); + + SaveBufferToCache(downloadedBuffer, downloadJobStartPosition); + } + } + + void HttpLocalCache::VacateStaleEntriesFromCache() + { + // Copy page offsets into vector and sort by the access counter + std::vector> orderedPageOffsets; + for (auto pageIter = m_localCache.begin(); pageIter != m_localCache.end(); pageIter++) + { + orderedPageOffsets.push_back(std::pair(pageIter->first, pageIter->second.lastAccessCounter)); + } + + // Compare function to sort by access counter + auto cmp = [](std::pair const & a, std::pair const & b) + { + return a.second != b.second ? a.second < b.second : a.first < b.first; + }; + + std::sort(orderedPageOffsets.begin(), orderedPageOffsets.end(), cmp); + + for (auto pageIter = orderedPageOffsets.begin(); pageIter != orderedPageOffsets.end(); pageIter++) + { + if (m_localCache.size() > MAX_PAGES) + { + m_localCache.erase(pageIter->first); + } + else + { + break; + } + } + } + + IBuffer HttpLocalCache::CreateTrimmedBuffer( + const IBuffer& originalBuffer, + UINT32 trimStartIndex, + UINT32 size) + { + uint32_t bufferLength = originalBuffer.Length(); + THROW_HR_IF(E_INVALIDARG, trimStartIndex > bufferLength); + + originalBuffer.as<::IInspectable>(); + + // Get the byte array from the IBuffer object + Microsoft::WRL::ComPtr bufferByteAccess; + ::IInspectable* bufferAbi = (::IInspectable*)winrt::get_abi(originalBuffer); + bufferAbi->QueryInterface(IID_PPV_ARGS(&bufferByteAccess)); + byte* byteBuffer = nullptr; + bufferByteAccess->Buffer(&byteBuffer); + + // Create the array of bytes holding the trimmed bytes + IBuffer trimmedBuffer = CryptographicBuffer::CreateFromByteArray( + { byteBuffer + trimStartIndex, std::min(size, bufferLength - trimStartIndex) }); + + return trimmedBuffer; + } + + IBuffer HttpLocalCache::ConcatenateBuffers(const IBuffer& buffer1, const IBuffer& buffer2) + { + DataWriter writer; + writer.WriteBuffer(buffer1); + writer.WriteBuffer(buffer2); + return writer.DetachBuffer(); + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.h b/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.h index e7fab8a318..97b04ff8e5 100644 --- a/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.h +++ b/src/AppInstallerCommonCore/HttpStream/HttpLocalCache.h @@ -1,70 +1,70 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -#include "HttpClientWrapper.h" - -namespace AppInstaller::Utility::HttpStream -{ - // Represents an entry in the cache. - struct CachedPage - { - int lastAccessCounter = 0; - winrt::Windows::Storage::Streams::IBuffer buffer; - }; - - // A cache used internally by the custom HttpRandomAccessStream to reduce round-trips - class HttpLocalCache - { - public: - static constexpr UINT32 PAGE_SIZE = 2 << 16; // each entry in the cache is 64 KB - static constexpr UINT32 MAX_PAGES = 200; // cache size capped at 12.5 MB (200 * 64KB) - - // Returns a buffer matching the requested range by reading the parts of the range that are cached - // and downloading the rest using the provided httpClientWrapper object - std::future ReadFromCacheAndDownloadIfNecessaryAsync( - const ULONG64 requestedPosition, - const UINT32 requestedSize, - HttpClientWrapper* httpClientWrapper, - winrt::Windows::Storage::Streams::InputStreamOptions httpInputStreamOptions); - - private: - std::map m_localCache; - UINT32 m_accessCounter = 0U; - - // Returns a vector of all pages corresponding to a range, and another (subset) - // vector of the pages missing from the cache. - void FindCachePages( - const ULONG64 requestedPosition, - const UINT32 requestedSize, - std::vector& allPages, - std::vector& unsatisfiablePages); - - void SaveBufferToCache(const winrt::Windows::Storage::Streams::IBuffer& buffer, const ULONG64 firstPageOffset); - - winrt::Windows::Storage::Streams::IBuffer ReadPageFromCache(const ULONG64 pageOffset); - - void VacateStaleEntriesFromCache(); - - std::future DownloadAndSaveToCacheAsync( - const std::vector unsatisfiablePages, - HttpClientWrapper* httpClientWrapper, - const winrt::Windows::Storage::Streams::InputStreamOptions httpInputStreamOptions); - - winrt::Windows::Storage::Streams::IBuffer TrimBufferToSatisfyRequest( - const winrt::Windows::Storage::Streams::IBuffer& constructedBuffer, - const ULONG64 requestedPosition, - const UINT32 requestedSize, - const std::vector allPages); - - winrt::Windows::Storage::Streams::IBuffer CreateTrimmedBuffer( - const winrt::Windows::Storage::Streams::IBuffer& originalBuffer, - UINT32 trimStartIndex, - UINT32 size); - - winrt::Windows::Storage::Streams::IBuffer ConcatenateBuffers( - const winrt::Windows::Storage::Streams::IBuffer& buffer1, - const winrt::Windows::Storage::Streams::IBuffer& buffer2); - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +#include "HttpClientWrapper.h" + +namespace AppInstaller::Utility::HttpStream +{ + // Represents an entry in the cache. + struct CachedPage + { + int lastAccessCounter = 0; + winrt::Windows::Storage::Streams::IBuffer buffer; + }; + + // A cache used internally by the custom HttpRandomAccessStream to reduce round-trips + class HttpLocalCache + { + public: + static constexpr UINT32 PAGE_SIZE = 2 << 16; // each entry in the cache is 64 KB + static constexpr UINT32 MAX_PAGES = 200; // cache size capped at 12.5 MB (200 * 64KB) + + // Returns a buffer matching the requested range by reading the parts of the range that are cached + // and downloading the rest using the provided httpClientWrapper object + std::future ReadFromCacheAndDownloadIfNecessaryAsync( + const ULONG64 requestedPosition, + const UINT32 requestedSize, + HttpClientWrapper* httpClientWrapper, + winrt::Windows::Storage::Streams::InputStreamOptions httpInputStreamOptions); + + private: + std::map m_localCache; + UINT32 m_accessCounter = 0U; + + // Returns a vector of all pages corresponding to a range, and another (subset) + // vector of the pages missing from the cache. + void FindCachePages( + const ULONG64 requestedPosition, + const UINT32 requestedSize, + std::vector& allPages, + std::vector& unsatisfiablePages); + + void SaveBufferToCache(const winrt::Windows::Storage::Streams::IBuffer& buffer, const ULONG64 firstPageOffset); + + winrt::Windows::Storage::Streams::IBuffer ReadPageFromCache(const ULONG64 pageOffset); + + void VacateStaleEntriesFromCache(); + + std::future DownloadAndSaveToCacheAsync( + const std::vector unsatisfiablePages, + HttpClientWrapper* httpClientWrapper, + const winrt::Windows::Storage::Streams::InputStreamOptions httpInputStreamOptions); + + winrt::Windows::Storage::Streams::IBuffer TrimBufferToSatisfyRequest( + const winrt::Windows::Storage::Streams::IBuffer& constructedBuffer, + const ULONG64 requestedPosition, + const UINT32 requestedSize, + const std::vector allPages); + + winrt::Windows::Storage::Streams::IBuffer CreateTrimmedBuffer( + const winrt::Windows::Storage::Streams::IBuffer& originalBuffer, + UINT32 trimStartIndex, + UINT32 size); + + winrt::Windows::Storage::Streams::IBuffer ConcatenateBuffers( + const winrt::Windows::Storage::Streams::IBuffer& buffer1, + const winrt::Windows::Storage::Streams::IBuffer& buffer2); + }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.cpp b/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.cpp index a2ab0a7821..1e4264996f 100644 --- a/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.cpp +++ b/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.cpp @@ -1,102 +1,102 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "HttpRandomAccessStream.h" -#include "Public/AppInstallerDownloader.h" - -using namespace winrt::Windows::Foundation; -using namespace winrt::Windows::Storage::Streams; - -// Note: the HttpRandomAccessStream is passed to the AppxPackaging COM API -// All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions. -// The HRESULTs will be mapped to UI error code by the appropriate component -namespace AppInstaller::Utility::HttpStream -{ - IAsyncOperation HttpRandomAccessStream::InitializeAsync(const Uri& uri) - { - auto strong_this{ get_strong() }; - - try - { - strong_this->m_httpHelper = co_await HttpClientWrapper::CreateAsync(uri); - strong_this->m_size = strong_this->m_httpHelper->GetFullFileSize(); - strong_this->m_httpLocalCache = std::make_unique(); - } - catch (const ServiceUnavailableException& e) - { - strong_this->m_retryAfter = e.RetryAfter(); - throw; - } - - co_return strong_this.as(); - } - - uint64_t HttpRandomAccessStream::Size() const - { - return m_size; - } - - void HttpRandomAccessStream::Size(uint64_t value) - { - UNREFERENCED_PARAMETER(value); - THROW_HR(E_NOTIMPL); - } - - uint64_t HttpRandomAccessStream::Position() const - { - return m_requestedPosition; - } - - bool HttpRandomAccessStream::CanRead() const - { - return true; - } - - bool HttpRandomAccessStream::CanWrite() const - { - return false; - } - - IInputStream HttpRandomAccessStream::GetInputStreamAt(uint64_t position) const - { - UNREFERENCED_PARAMETER(position); - THROW_HR(E_NOTIMPL); - } - - IOutputStream HttpRandomAccessStream::GetOutputStreamAt(uint64_t position) const - { - UNREFERENCED_PARAMETER(position); - THROW_HR(E_NOTIMPL); - } - - IRandomAccessStream HttpRandomAccessStream::CloneStream() const - { - THROW_HR(E_NOTIMPL); - } - - void HttpRandomAccessStream::Seek(uint64_t position) - { - m_requestedPosition = position; - } - - IAsyncOperationWithProgress HttpRandomAccessStream::ReadAsync( - IBuffer buffer, - uint32_t count, - InputStreamOptions options) - { - IBuffer result = co_await m_httpLocalCache->ReadFromCacheAndDownloadIfNecessaryAsync( - m_requestedPosition, - count, - m_httpHelper.get(), - options); - winrt::check_hresult(ULong64Add(m_requestedPosition, result.Length(), &m_requestedPosition)); - - co_return result; - } - - std::chrono::seconds HttpRandomAccessStream::RetryAfter() const - { - return m_retryAfter; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "HttpRandomAccessStream.h" +#include "Public/AppInstallerDownloader.h" + +using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Storage::Streams; + +// Note: the HttpRandomAccessStream is passed to the AppxPackaging COM API +// All exceptions thrown across dll boundaries should be WinRT exception not custom exceptions. +// The HRESULTs will be mapped to UI error code by the appropriate component +namespace AppInstaller::Utility::HttpStream +{ + IAsyncOperation HttpRandomAccessStream::InitializeAsync(const Uri& uri) + { + auto strong_this{ get_strong() }; + + try + { + strong_this->m_httpHelper = co_await HttpClientWrapper::CreateAsync(uri); + strong_this->m_size = strong_this->m_httpHelper->GetFullFileSize(); + strong_this->m_httpLocalCache = std::make_unique(); + } + catch (const ServiceUnavailableException& e) + { + strong_this->m_retryAfter = e.RetryAfter(); + throw; + } + + co_return strong_this.as(); + } + + uint64_t HttpRandomAccessStream::Size() const + { + return m_size; + } + + void HttpRandomAccessStream::Size(uint64_t value) + { + UNREFERENCED_PARAMETER(value); + THROW_HR(E_NOTIMPL); + } + + uint64_t HttpRandomAccessStream::Position() const + { + return m_requestedPosition; + } + + bool HttpRandomAccessStream::CanRead() const + { + return true; + } + + bool HttpRandomAccessStream::CanWrite() const + { + return false; + } + + IInputStream HttpRandomAccessStream::GetInputStreamAt(uint64_t position) const + { + UNREFERENCED_PARAMETER(position); + THROW_HR(E_NOTIMPL); + } + + IOutputStream HttpRandomAccessStream::GetOutputStreamAt(uint64_t position) const + { + UNREFERENCED_PARAMETER(position); + THROW_HR(E_NOTIMPL); + } + + IRandomAccessStream HttpRandomAccessStream::CloneStream() const + { + THROW_HR(E_NOTIMPL); + } + + void HttpRandomAccessStream::Seek(uint64_t position) + { + m_requestedPosition = position; + } + + IAsyncOperationWithProgress HttpRandomAccessStream::ReadAsync( + IBuffer buffer, + uint32_t count, + InputStreamOptions options) + { + IBuffer result = co_await m_httpLocalCache->ReadFromCacheAndDownloadIfNecessaryAsync( + m_requestedPosition, + count, + m_httpHelper.get(), + options); + winrt::check_hresult(ULong64Add(m_requestedPosition, result.Length(), &m_requestedPosition)); + + co_return result; + } + + std::chrono::seconds HttpRandomAccessStream::RetryAfter() const + { + return m_retryAfter; + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.h b/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.h index 0ed2d0c108..b4b74e578b 100644 --- a/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.h +++ b/src/AppInstallerCommonCore/HttpStream/HttpRandomAccessStream.h @@ -1,45 +1,45 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once -#include "HttpClientWrapper.h" -#include "HttpLocalCache.h" - -using namespace std::chrono_literals; - -namespace AppInstaller::Utility::HttpStream -{ - // Provides an implementation of a random access stream over HTTP that supports - // range-based fetching. This is intended to be used by AppxPackageReader. - // - // Note: If the server doesn't support HTTP ranges, this implementation will throw an exception. - class HttpRandomAccessStream : public winrt::implements< - HttpRandomAccessStream, - winrt::Windows::Storage::Streams::IRandomAccessStream, - winrt::Windows::Storage::Streams::IInputStream> - { - public: - winrt::Windows::Foundation::IAsyncOperation InitializeAsync(const winrt::Windows::Foundation::Uri& uri); - uint64_t Size() const; - void Size(uint64_t value); - uint64_t Position() const; - bool CanRead() const; - bool CanWrite() const; - winrt::Windows::Storage::Streams::IInputStream GetInputStreamAt(uint64_t position) const; - winrt::Windows::Storage::Streams::IOutputStream GetOutputStreamAt(uint64_t position) const; - winrt::Windows::Storage::Streams::IRandomAccessStream CloneStream() const; - void Seek(uint64_t position); - winrt::Windows::Foundation::IAsyncOperationWithProgress ReadAsync( - winrt::Windows::Storage::Streams::IBuffer buffer, - uint32_t count, - winrt::Windows::Storage::Streams::InputStreamOptions options); - std::chrono::seconds RetryAfter() const; - - private: - std::shared_ptr m_httpHelper; - std::unique_ptr m_httpLocalCache; - unsigned long long m_size = 0; - unsigned long long m_requestedPosition = 0; - std::chrono::seconds m_retryAfter = 0s; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once +#include "HttpClientWrapper.h" +#include "HttpLocalCache.h" + +using namespace std::chrono_literals; + +namespace AppInstaller::Utility::HttpStream +{ + // Provides an implementation of a random access stream over HTTP that supports + // range-based fetching. This is intended to be used by AppxPackageReader. + // + // Note: If the server doesn't support HTTP ranges, this implementation will throw an exception. + class HttpRandomAccessStream : public winrt::implements< + HttpRandomAccessStream, + winrt::Windows::Storage::Streams::IRandomAccessStream, + winrt::Windows::Storage::Streams::IInputStream> + { + public: + winrt::Windows::Foundation::IAsyncOperation InitializeAsync(const winrt::Windows::Foundation::Uri& uri); + uint64_t Size() const; + void Size(uint64_t value); + uint64_t Position() const; + bool CanRead() const; + bool CanWrite() const; + winrt::Windows::Storage::Streams::IInputStream GetInputStreamAt(uint64_t position) const; + winrt::Windows::Storage::Streams::IOutputStream GetOutputStreamAt(uint64_t position) const; + winrt::Windows::Storage::Streams::IRandomAccessStream CloneStream() const; + void Seek(uint64_t position); + winrt::Windows::Foundation::IAsyncOperationWithProgress ReadAsync( + winrt::Windows::Storage::Streams::IBuffer buffer, + uint32_t count, + winrt::Windows::Storage::Streams::InputStreamOptions options); + std::chrono::seconds RetryAfter() const; + + private: + std::shared_ptr m_httpHelper; + std::unique_ptr m_httpLocalCache; + unsigned long long m_size = 0; + unsigned long long m_requestedPosition = 0; + std::chrono::seconds m_retryAfter = 0s; + }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Locale.cpp b/src/AppInstallerCommonCore/Locale.cpp index ae8985910c..d8a6ee5004 100644 --- a/src/AppInstallerCommonCore/Locale.cpp +++ b/src/AppInstallerCommonCore/Locale.cpp @@ -1,151 +1,151 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Locale.h" -#include "AppInstallerStrings.h" -#include "AppInstallerLogging.h" - -namespace AppInstaller::Locale -{ - namespace - { - constexpr int MAX_LOCALE_SNAME_LEN = 85; - - // We will just leak this. The module is shared as both functions will always be together. - HMODULE g_bcp47 = (HMODULE)(-1); - typedef bool(WINAPI* IsWellFormedTagFunc)(PCWSTR); - typedef HRESULT(WINAPI* GetDistanceOfClosestLanguageInListFunc)(PCWSTR, PCWSTR, wchar_t, double*); - - HMODULE LoadBcp47ModuleFrom(_In_ PCWSTR moduleName) - { - HMODULE module = LoadLibraryExW(moduleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - if (module != nullptr) - { - // All BCP47 APIs we are interested are always exported from the same dll together. So we just pick anyone for probe. - IsWellFormedTagFunc func = (IsWellFormedTagFunc)(GetProcAddress(module, "IsWellFormedTag")); - if (func != nullptr) - { - return module; - } - FreeLibrary(module); - } - - return nullptr; - } - - HMODULE LoadBcp47Module() - { - HMODULE module = LoadBcp47ModuleFrom(L"bcp47mrm.dll"); - if (module == nullptr) - { - // In downlevel OS, the API is exposed by bcp47langs.dll. - module = LoadBcp47ModuleFrom(L"bcp47langs.dll"); - } - - return module; - } - - void InitializeBcp47Module() - { - HMODULE comparand = (HMODULE)(-1); - if (InterlockedCompareExchangePointer(reinterpret_cast(&g_bcp47), comparand, comparand) == comparand) - { - HMODULE module = LoadBcp47Module(); - InterlockedExchangePointer(reinterpret_cast(&g_bcp47), module); - } - } - } - - bool IsWellFormedBcp47Tag(std::string_view bcp47Tag) - { - // Before new SDK is released, we need to use LoadLibrary/GetProcAddress - InitializeBcp47Module(); - - if (g_bcp47 == nullptr) - { - // Didn't find an implementation. Just return true. - AICLI_LOG(Core, Warning, << "bcp47 module not found."); - return true; - } - - IsWellFormedTagFunc func = (IsWellFormedTagFunc)(GetProcAddress(g_bcp47, "IsWellFormedTag")); - if (func != nullptr) - { - auto wBcp47Tag = Utility::ConvertToUTF16(bcp47Tag); - return func(wBcp47Tag.c_str()); - } - - // Should not reach here. - return TRUE; - } - - double GetDistanceOfLanguage(std::string_view target, std::string_view available) - { - // Before new SDK is released, we need to use LoadLibrary/GetProcAddress - InitializeBcp47Module(); - - if (g_bcp47 == nullptr) - { - // Didn't find an implementation. Just return 0 as no match. - AICLI_LOG(Core, Warning, << "bcp47 module not found."); - return 0; - } - - GetDistanceOfClosestLanguageInListFunc func = - (GetDistanceOfClosestLanguageInListFunc)(GetProcAddress(g_bcp47, "GetDistanceOfClosestLanguageInList")); - if (func != nullptr) - { - double distance = 0; - auto wTarget = Utility::ConvertToUTF16(target); - auto wAvailable = Utility::ConvertToUTF16(available); - - // Do not check HRESULT because the method returns ERROR_NO_MATCH on no match, which is a valid case. - (void)func(wTarget.c_str(), wAvailable.c_str(), L';' /* Not used, we compare one at a time */, &distance); - return distance; - } - - // Should not reach here. - return 0; - } - - std::vector GetUserPreferredLanguages() - { - std::vector result; - - for (const auto& lang : winrt::Windows::System::UserProfile::GlobalizationPreferences::Languages()) - { - result.emplace_back(Utility::ConvertToUTF8(lang)); - } - - return result; - } - - std::vector GetUserPreferredLanguagesUTF16() - { - std::vector result; - - for (const auto& lang : winrt::Windows::System::UserProfile::GlobalizationPreferences::Languages()) - { - result.emplace_back(std::wstring(lang)); - } - - return result; - } - - std::string LocaleIdToBcp47Tag(LCID localeId) - { - WCHAR localeName[MAX_LOCALE_SNAME_LEN] = {0}; - int ret = LCIDToLocaleName( - localeId, - localeName, - MAX_LOCALE_SNAME_LEN, - LOCALE_ALLOW_NEUTRAL_NAMES); - - if (ret <= 0) - { - return {}; - } - - return Utility::ConvertToUTF8(std::wstring(localeName)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Locale.h" +#include "AppInstallerStrings.h" +#include "AppInstallerLogging.h" + +namespace AppInstaller::Locale +{ + namespace + { + constexpr int MAX_LOCALE_SNAME_LEN = 85; + + // We will just leak this. The module is shared as both functions will always be together. + HMODULE g_bcp47 = (HMODULE)(-1); + typedef bool(WINAPI* IsWellFormedTagFunc)(PCWSTR); + typedef HRESULT(WINAPI* GetDistanceOfClosestLanguageInListFunc)(PCWSTR, PCWSTR, wchar_t, double*); + + HMODULE LoadBcp47ModuleFrom(_In_ PCWSTR moduleName) + { + HMODULE module = LoadLibraryExW(moduleName, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (module != nullptr) + { + // All BCP47 APIs we are interested are always exported from the same dll together. So we just pick anyone for probe. + IsWellFormedTagFunc func = (IsWellFormedTagFunc)(GetProcAddress(module, "IsWellFormedTag")); + if (func != nullptr) + { + return module; + } + FreeLibrary(module); + } + + return nullptr; + } + + HMODULE LoadBcp47Module() + { + HMODULE module = LoadBcp47ModuleFrom(L"bcp47mrm.dll"); + if (module == nullptr) + { + // In downlevel OS, the API is exposed by bcp47langs.dll. + module = LoadBcp47ModuleFrom(L"bcp47langs.dll"); + } + + return module; + } + + void InitializeBcp47Module() + { + HMODULE comparand = (HMODULE)(-1); + if (InterlockedCompareExchangePointer(reinterpret_cast(&g_bcp47), comparand, comparand) == comparand) + { + HMODULE module = LoadBcp47Module(); + InterlockedExchangePointer(reinterpret_cast(&g_bcp47), module); + } + } + } + + bool IsWellFormedBcp47Tag(std::string_view bcp47Tag) + { + // Before new SDK is released, we need to use LoadLibrary/GetProcAddress + InitializeBcp47Module(); + + if (g_bcp47 == nullptr) + { + // Didn't find an implementation. Just return true. + AICLI_LOG(Core, Warning, << "bcp47 module not found."); + return true; + } + + IsWellFormedTagFunc func = (IsWellFormedTagFunc)(GetProcAddress(g_bcp47, "IsWellFormedTag")); + if (func != nullptr) + { + auto wBcp47Tag = Utility::ConvertToUTF16(bcp47Tag); + return func(wBcp47Tag.c_str()); + } + + // Should not reach here. + return TRUE; + } + + double GetDistanceOfLanguage(std::string_view target, std::string_view available) + { + // Before new SDK is released, we need to use LoadLibrary/GetProcAddress + InitializeBcp47Module(); + + if (g_bcp47 == nullptr) + { + // Didn't find an implementation. Just return 0 as no match. + AICLI_LOG(Core, Warning, << "bcp47 module not found."); + return 0; + } + + GetDistanceOfClosestLanguageInListFunc func = + (GetDistanceOfClosestLanguageInListFunc)(GetProcAddress(g_bcp47, "GetDistanceOfClosestLanguageInList")); + if (func != nullptr) + { + double distance = 0; + auto wTarget = Utility::ConvertToUTF16(target); + auto wAvailable = Utility::ConvertToUTF16(available); + + // Do not check HRESULT because the method returns ERROR_NO_MATCH on no match, which is a valid case. + (void)func(wTarget.c_str(), wAvailable.c_str(), L';' /* Not used, we compare one at a time */, &distance); + return distance; + } + + // Should not reach here. + return 0; + } + + std::vector GetUserPreferredLanguages() + { + std::vector result; + + for (const auto& lang : winrt::Windows::System::UserProfile::GlobalizationPreferences::Languages()) + { + result.emplace_back(Utility::ConvertToUTF8(lang)); + } + + return result; + } + + std::vector GetUserPreferredLanguagesUTF16() + { + std::vector result; + + for (const auto& lang : winrt::Windows::System::UserProfile::GlobalizationPreferences::Languages()) + { + result.emplace_back(std::wstring(lang)); + } + + return result; + } + + std::string LocaleIdToBcp47Tag(LCID localeId) + { + WCHAR localeName[MAX_LOCALE_SNAME_LEN] = {0}; + int ret = LCIDToLocaleName( + localeId, + localeName, + MAX_LOCALE_SNAME_LEN, + LOCALE_ALLOW_NEUTRAL_NAMES); + + if (ret <= 0) + { + return {}; + } + + return Utility::ConvertToUTF8(std::wstring(localeName)); + } +} diff --git a/src/AppInstallerCommonCore/MSStore.cpp b/src/AppInstallerCommonCore/MSStore.cpp index de0623fac2..d5794fbc4b 100644 --- a/src/AppInstallerCommonCore/MSStore.cpp +++ b/src/AppInstallerCommonCore/MSStore.cpp @@ -1,516 +1,516 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::MSStore -{ - using namespace std::string_view_literals; - using namespace winrt::Windows::Foundation; - using namespace winrt::Windows::Foundation::Collections; - using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; - using namespace winrt::Windows::Management::Deployment; - - namespace - { - // The type of entitlement we were able to acquire/ensure. - enum class EntitlementType - { - None, - User, - Device, - }; - - EntitlementType EnsureFreeEntitlement(const std::wstring& productId, Manifest::ScopeEnum scope) - { - AppInstallManager installManager; - - AICLI_LOG(Core, Info, << "Getting entitlement for ProductId: " << Utility::ConvertToUTF8(productId)); - - // Verifying/Acquiring product ownership - GetEntitlementResult entitlementResult{ nullptr }; - EntitlementType result = EntitlementType::None; - - if (scope == Manifest::ScopeEnum::Machine) - { - AICLI_LOG(Core, Info, << "Get device entitlement (machine scope install)."); - result = EntitlementType::Device; - try - { - entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); - } - CATCH_LOG(); - } - else - { - AICLI_LOG(Core, Info, << "Get user entitlement."); - result = EntitlementType::User; - try - { - entitlementResult = installManager.GetFreeUserEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); - } - CATCH_LOG(); - - if (!entitlementResult || entitlementResult.Status() == GetEntitlementStatus::NoStoreAccount) - { - AICLI_LOG(Core, Info, << "Get device entitlement (no store account)."); - result = EntitlementType::Device; - try - { - entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); - } - CATCH_LOG(); - } - } - - if (entitlementResult && entitlementResult.Status() == GetEntitlementStatus::Succeeded) - { - AICLI_LOG(Core, Info, << "Get entitlement succeeded."); - } - else if (entitlementResult) - { - result = EntitlementType::None; - - if (entitlementResult.Status() == GetEntitlementStatus::NetworkError) - { - AICLI_LOG(Core, Error, << "Get entitlement failed. Network error."); - } - else if (entitlementResult.Status() == GetEntitlementStatus::ServerError) - { - AICLI_LOG(Core, Error, << "Get entitlement failed. Server error."); - } - else - { - AICLI_LOG(Core, Error, << "Get entitlement failed. Unknown status: " << static_cast(entitlementResult.Status())); - } - } - else - { - result = EntitlementType::None; - AICLI_LOG(Core, Error, << "Get entitlement failed. Exception."); - } - - return result; - } - - enum class CheckExistingItemResult - { - None, - Restart, - Cancel, - }; - - CheckExistingItemResult CheckRestartOrCancelForPossibleExistingOperation(const IVectorView& installItems) - { - CheckExistingItemResult result = CheckExistingItemResult::None; - - for (auto const& installItem : installItems) - { - const auto& status = installItem.GetCurrentStatus(); - switch (status.InstallState()) - { - case AppInstallState::Canceled: - case AppInstallState::Error: - // For these states, always do a cancel; - result = CheckExistingItemResult::Cancel; - return result; - case AppInstallState::Paused: - case AppInstallState::PausedLowBattery: - case AppInstallState::PausedWiFiRecommended: - case AppInstallState::PausedWiFiRequired: - case AppInstallState::ReadyToDownload: - // For these states, set result to restart and continue the loop to see if future items need cancel. - result = CheckExistingItemResult::Restart; - break; - } - } - - return result; - } - - bool DoesInstallItemsContainProduct(const IVectorView& installItems, std::wstring_view productId) - { - for (auto const& installItem : installItems) - { - if (Utility::CaseInsensitiveEquals(installItem.ProductId(), productId)) - { - return true; - } - } - - return false; - } - - // Returns true if Restart or Cancel happened. False otherwise. - HRESULT RestartOrCancelExistingOperationIfNecessary(const IVectorView& installItems, AppInstallManager& installManager, std::wstring_view productId) - { - auto existingItemResult = CheckRestartOrCancelForPossibleExistingOperation(installItems); - - if (existingItemResult == CheckExistingItemResult::Cancel || existingItemResult == CheckExistingItemResult::Restart) - { - if (existingItemResult == CheckExistingItemResult::Cancel) - { - installManager.Cancel(productId); - - // Wait for at most 10 seconds for install item to be removed from queue. - for (int i = 0; i < 50; ++i) - { - Sleep(200); - if (!DoesInstallItemsContainProduct(installManager.AppInstallItems(), productId)) - { - return S_OK; - } - } - - RETURN_HR(HRESULT_FROM_WIN32(ERROR_TIMEOUT)); - } - else - { - installManager.Restart(productId); - return S_OK; - } - } - - return S_FALSE; - } - - // Used to detect a signal that a package update is being requested so that we can early out - // on an attempt to update ourself. This is only needed for elevated processes because the - // standard shutdown signals are not sent to elevated processes in the same manner. - struct PackageUpdateMonitor - { - PackageUpdateMonitor() - { - if (Runtime::IsRunningAsAdmin() && Runtime::IsRunningInPackagedContext()) - { - m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentPackage(); - m_updatingEvent = m_catalog.PackageUpdating( - winrt::auto_revoke, [this](winrt::Windows::ApplicationModel::PackageCatalog, winrt::Windows::ApplicationModel::PackageUpdatingEventArgs args) - { - // Deployment always sends a value of 0 before doing any work and a value of 100 when completely done. - constexpr double minProgress = 0; - auto progress = args.Progress(); - if (progress > minProgress) - { - m_isUpdating = true; - } - }); - } - } - - bool IsUpdating() const - { - return m_isUpdating; - } - - private: - winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr; - decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageUpdating(winrt::auto_revoke, nullptr)) m_updatingEvent; - std::atomic_bool m_isUpdating = false; - }; - - // After a successful machine-scope Store install, the package may not be provisioned on older - // Windows versions (fixed in Windows 11 26100). This helper ensures the package is provisioned - // as a best-effort mitigation. - void EnsureProvisionedForMachineScope(const IVectorView& installItems, std::wstring_view productId, IProgressCallback& progress) try - { - PackageManager packageManager; - auto provisionedPackages = packageManager.FindProvisionedPackages(); - - for (auto const& installItem : installItems) - { - if (Utility::CaseInsensitiveEquals(installItem.ProductId(), productId)) - { - std::wstring familyName{ installItem.PackageFamilyName() }; - if (familyName.empty()) - { - continue; - } - - bool isProvisioned = false; - for (auto const& provisionedPkg : provisionedPackages) - { - if (provisionedPkg.Id().FamilyName() == familyName) - { - isProvisioned = true; - break; - } - } - - if (!isProvisioned) - { - AICLI_LOG(Core, Info, << "Package not provisioned after machine scope install, provisioning: " << Utility::ConvertToUTF8(familyName)); - try - { - auto operation = packageManager.ProvisionPackageForAllUsersAsync(familyName); - Deployment::WaitForDeployment(operation, progress); - AICLI_LOG(Core, Info, << "Successfully provisioned package: " << Utility::ConvertToUTF8(familyName)); - } - CATCH_LOG(); - } - } - } - } - CATCH_LOG(); - - HRESULT WaitForOperation(const std::wstring& productId, bool isSilentMode, IVectorView& installItems, IProgressCallback& progress, const PackageUpdateMonitor& monitor) - { - auto cancelIfOperationFailed = wil::scope_exit( - [&]() - { - try - { - AppInstallManager installManager; - installManager.Cancel(productId); - } - CATCH_LOG(); - }); - - for (auto const& installItem : installItems) - { - AICLI_LOG(Core, Info, << - "Started MSStore package execution. ProductId: " << Utility::ConvertToUTF8(installItem.ProductId()) << - " PackageFamilyName: " << Utility::ConvertToUTF8(installItem.PackageFamilyName())); - - if (isSilentMode) - { - installItem.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - installItem.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - } - } - - HRESULT errorCode = S_OK; - - // We are aggregating all AppInstallItem progresses into one. - // Averaging every progress for now until we have a better way to find overall progress. - uint64_t overallProgressMax = 100 * static_cast(installItems.Size()); - uint64_t currentProgress = 0; - - while (currentProgress < overallProgressMax) - { - currentProgress = 0; - - for (auto const& installItem : installItems) - { - const auto& status = installItem.GetCurrentStatus(); - currentProgress += static_cast(status.PercentComplete()); - - errorCode = status.ErrorCode(); - - if (!SUCCEEDED(errorCode)) - { - return errorCode; - } - } - - // It may take a while for Store client to pick up the install request. - // So we show indefinite progress here to avoid a progress bar stuck at 0. - if (currentProgress > 0) - { - progress.OnProgress(currentProgress, overallProgressMax, ProgressType::Percent); - } - - if (progress.IsCancelledBy(CancelReason::User)) - { - for (auto const& installItem : installItems) - { - installItem.Cancel(); - } - } - - // If app shutdown then we have 30s to keep installing, keep going and hope for the best. - else if (progress.IsCancelledBy(CancelReason::AppShutdown) || monitor.IsUpdating()) - { - for (auto const& installItem : installItems) - { - // Insert spiderman meme. - if (installItem.ProductId() == std::wstring{ s_AppInstallerProductId }) - { - AICLI_LOG(Core, Info, << "Asked to shutdown while installing AppInstaller."); - progress.OnProgress(overallProgressMax, overallProgressMax, ProgressType::Percent); - cancelIfOperationFailed.release(); - return S_OK; - } - } - } - - Sleep(100); - } - - if (SUCCEEDED(errorCode)) - { - cancelIfOperationFailed.release(); - } - - return errorCode; - } - } - - HRESULT MSStoreOperation::StartAndWaitForOperation(IProgressCallback& progress) - { - // Best effort verifying/acquiring product ownership. - std::ignore = EnsureFreeEntitlement(m_productId, m_scope); - - if (m_type == MSStoreOperationType::Update) - { - return UpdatePackage(progress); - } - else - { - return InstallPackage(progress); - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - namespace TestHooks - { - static bool* s_ProvisionAfterInstall = nullptr; - - void TestHook_SetProvisionAfterInstall(bool* value) - { - s_ProvisionAfterInstall = value; - } - } -#endif - - HRESULT MSStoreOperation::InstallPackage(IProgressCallback& progress) - { - PackageUpdateMonitor monitor; - - AppInstallManager installManager; - AppInstallOptions installOptions; - - installOptions.AllowForcedAppRestart(m_force); - if (m_isSilentMode) - { - installOptions.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - installOptions.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); - } - - if (m_type == MSStoreOperationType::Repair) - { - // Attempt to repair the installation of an app that is already installed. - installOptions.Repair(true); - } - - if (m_scope == Manifest::ScopeEnum::Machine) - { - installOptions.InstallForAllUsers(true); - } - - IVectorView installItems = installManager.StartProductInstallAsync( - m_productId, // ProductId - winrt::hstring(), // FlightId - L"WinGetCli", // ClientId - winrt::hstring(), - installOptions).get(); - - // Check if we need to restart or cancel existing items. - auto restartOrCancelResult = RestartOrCancelExistingOperationIfNecessary(installItems, installManager, m_productId); - RETURN_IF_FAILED(restartOrCancelResult); - - // If restart or cancel happened, try again. - if (restartOrCancelResult == S_OK) - { - // Try again - installItems = installManager.StartProductInstallAsync( - m_productId, // ProductId - winrt::hstring(), // FlightId - L"WinGetCli", // ClientId - winrt::hstring(), - installOptions).get(); - } - - HRESULT hr = WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); - - // There was a bug in InstallService where admin users were incorrectly identified as non-admin, - // causing false access denied errors; fixed in 10.0.26100.0. On older OS versions, convert any - // failure under these conditions to the error that was previously always returned. - if (FAILED(hr) && - m_scope == Manifest::ScopeEnum::Machine && - !Runtime::IsRunningAsSystem() && - !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) - { - AICLI_LOG(Core, Error, << "Device wide install for msstore type is not supported under admin context on this OS version. Error: " << hr); - return APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED; - } - - bool shouldProvision = m_scope == Manifest::ScopeEnum::Machine; -#ifndef AICLI_DISABLE_TEST_HOOKS - if (TestHooks::s_ProvisionAfterInstall) - { - shouldProvision = *TestHooks::s_ProvisionAfterInstall; - } -#endif - - if (SUCCEEDED(hr) && shouldProvision) - { - // Mitigation: on older OS versions (fixed in Windows 11 26100) the Store install service - // may not provision the package even when InstallForAllUsers was set. - // Explicitly provision if not already provisioned. - EnsureProvisionedForMachineScope(installItems, m_productId, progress); - } - - return hr; - } - - HRESULT MSStoreOperation::UpdatePackage(IProgressCallback& progress) - { - PackageUpdateMonitor monitor; - - AppInstallManager installManager; - AppUpdateOptions updateOptions; - updateOptions.AllowForcedAppRestart(m_force); - - // SearchForUpdateAsync will automatically trigger update if found. - AppInstallItem installItem = installManager.SearchForUpdatesAsync( - m_productId, // ProductId - winrt::hstring(), // SkuId - winrt::hstring(), - winrt::hstring(), // ClientId - updateOptions - ).get(); - - if (!installItem) - { - return APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE; - } - - std::vector installItemVector{ installItem }; - IVectorView installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView(); - - // Check if we need to restart or cancel existing items. - auto restartOrCancelResult = RestartOrCancelExistingOperationIfNecessary(installItems, installManager, m_productId); - RETURN_IF_FAILED(restartOrCancelResult); - - // If restart or cancel happened, try again. - if (restartOrCancelResult == S_OK) - { - // Try again - installItem = installManager.SearchForUpdatesAsync( - m_productId, // ProductId - winrt::hstring(), // SkuId - winrt::hstring(), - winrt::hstring(), // ClientId - updateOptions - ).get(); - - if (!installItem) - { - return APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE; - } - - installItemVector.clear(); - installItemVector.emplace_back(installItem); - installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView(); - } - - return WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::MSStore +{ + using namespace std::string_view_literals; + using namespace winrt::Windows::Foundation; + using namespace winrt::Windows::Foundation::Collections; + using namespace winrt::Windows::ApplicationModel::Store::Preview::InstallControl; + using namespace winrt::Windows::Management::Deployment; + + namespace + { + // The type of entitlement we were able to acquire/ensure. + enum class EntitlementType + { + None, + User, + Device, + }; + + EntitlementType EnsureFreeEntitlement(const std::wstring& productId, Manifest::ScopeEnum scope) + { + AppInstallManager installManager; + + AICLI_LOG(Core, Info, << "Getting entitlement for ProductId: " << Utility::ConvertToUTF8(productId)); + + // Verifying/Acquiring product ownership + GetEntitlementResult entitlementResult{ nullptr }; + EntitlementType result = EntitlementType::None; + + if (scope == Manifest::ScopeEnum::Machine) + { + AICLI_LOG(Core, Info, << "Get device entitlement (machine scope install)."); + result = EntitlementType::Device; + try + { + entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); + } + CATCH_LOG(); + } + else + { + AICLI_LOG(Core, Info, << "Get user entitlement."); + result = EntitlementType::User; + try + { + entitlementResult = installManager.GetFreeUserEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); + } + CATCH_LOG(); + + if (!entitlementResult || entitlementResult.Status() == GetEntitlementStatus::NoStoreAccount) + { + AICLI_LOG(Core, Info, << "Get device entitlement (no store account)."); + result = EntitlementType::Device; + try + { + entitlementResult = installManager.GetFreeDeviceEntitlementAsync(productId, winrt::hstring(), winrt::hstring()).get(); + } + CATCH_LOG(); + } + } + + if (entitlementResult && entitlementResult.Status() == GetEntitlementStatus::Succeeded) + { + AICLI_LOG(Core, Info, << "Get entitlement succeeded."); + } + else if (entitlementResult) + { + result = EntitlementType::None; + + if (entitlementResult.Status() == GetEntitlementStatus::NetworkError) + { + AICLI_LOG(Core, Error, << "Get entitlement failed. Network error."); + } + else if (entitlementResult.Status() == GetEntitlementStatus::ServerError) + { + AICLI_LOG(Core, Error, << "Get entitlement failed. Server error."); + } + else + { + AICLI_LOG(Core, Error, << "Get entitlement failed. Unknown status: " << static_cast(entitlementResult.Status())); + } + } + else + { + result = EntitlementType::None; + AICLI_LOG(Core, Error, << "Get entitlement failed. Exception."); + } + + return result; + } + + enum class CheckExistingItemResult + { + None, + Restart, + Cancel, + }; + + CheckExistingItemResult CheckRestartOrCancelForPossibleExistingOperation(const IVectorView& installItems) + { + CheckExistingItemResult result = CheckExistingItemResult::None; + + for (auto const& installItem : installItems) + { + const auto& status = installItem.GetCurrentStatus(); + switch (status.InstallState()) + { + case AppInstallState::Canceled: + case AppInstallState::Error: + // For these states, always do a cancel; + result = CheckExistingItemResult::Cancel; + return result; + case AppInstallState::Paused: + case AppInstallState::PausedLowBattery: + case AppInstallState::PausedWiFiRecommended: + case AppInstallState::PausedWiFiRequired: + case AppInstallState::ReadyToDownload: + // For these states, set result to restart and continue the loop to see if future items need cancel. + result = CheckExistingItemResult::Restart; + break; + } + } + + return result; + } + + bool DoesInstallItemsContainProduct(const IVectorView& installItems, std::wstring_view productId) + { + for (auto const& installItem : installItems) + { + if (Utility::CaseInsensitiveEquals(installItem.ProductId(), productId)) + { + return true; + } + } + + return false; + } + + // Returns true if Restart or Cancel happened. False otherwise. + HRESULT RestartOrCancelExistingOperationIfNecessary(const IVectorView& installItems, AppInstallManager& installManager, std::wstring_view productId) + { + auto existingItemResult = CheckRestartOrCancelForPossibleExistingOperation(installItems); + + if (existingItemResult == CheckExistingItemResult::Cancel || existingItemResult == CheckExistingItemResult::Restart) + { + if (existingItemResult == CheckExistingItemResult::Cancel) + { + installManager.Cancel(productId); + + // Wait for at most 10 seconds for install item to be removed from queue. + for (int i = 0; i < 50; ++i) + { + Sleep(200); + if (!DoesInstallItemsContainProduct(installManager.AppInstallItems(), productId)) + { + return S_OK; + } + } + + RETURN_HR(HRESULT_FROM_WIN32(ERROR_TIMEOUT)); + } + else + { + installManager.Restart(productId); + return S_OK; + } + } + + return S_FALSE; + } + + // Used to detect a signal that a package update is being requested so that we can early out + // on an attempt to update ourself. This is only needed for elevated processes because the + // standard shutdown signals are not sent to elevated processes in the same manner. + struct PackageUpdateMonitor + { + PackageUpdateMonitor() + { + if (Runtime::IsRunningAsAdmin() && Runtime::IsRunningInPackagedContext()) + { + m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentPackage(); + m_updatingEvent = m_catalog.PackageUpdating( + winrt::auto_revoke, [this](winrt::Windows::ApplicationModel::PackageCatalog, winrt::Windows::ApplicationModel::PackageUpdatingEventArgs args) + { + // Deployment always sends a value of 0 before doing any work and a value of 100 when completely done. + constexpr double minProgress = 0; + auto progress = args.Progress(); + if (progress > minProgress) + { + m_isUpdating = true; + } + }); + } + } + + bool IsUpdating() const + { + return m_isUpdating; + } + + private: + winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr; + decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageUpdating(winrt::auto_revoke, nullptr)) m_updatingEvent; + std::atomic_bool m_isUpdating = false; + }; + + // After a successful machine-scope Store install, the package may not be provisioned on older + // Windows versions (fixed in Windows 11 26100). This helper ensures the package is provisioned + // as a best-effort mitigation. + void EnsureProvisionedForMachineScope(const IVectorView& installItems, std::wstring_view productId, IProgressCallback& progress) try + { + PackageManager packageManager; + auto provisionedPackages = packageManager.FindProvisionedPackages(); + + for (auto const& installItem : installItems) + { + if (Utility::CaseInsensitiveEquals(installItem.ProductId(), productId)) + { + std::wstring familyName{ installItem.PackageFamilyName() }; + if (familyName.empty()) + { + continue; + } + + bool isProvisioned = false; + for (auto const& provisionedPkg : provisionedPackages) + { + if (provisionedPkg.Id().FamilyName() == familyName) + { + isProvisioned = true; + break; + } + } + + if (!isProvisioned) + { + AICLI_LOG(Core, Info, << "Package not provisioned after machine scope install, provisioning: " << Utility::ConvertToUTF8(familyName)); + try + { + auto operation = packageManager.ProvisionPackageForAllUsersAsync(familyName); + Deployment::WaitForDeployment(operation, progress); + AICLI_LOG(Core, Info, << "Successfully provisioned package: " << Utility::ConvertToUTF8(familyName)); + } + CATCH_LOG(); + } + } + } + } + CATCH_LOG(); + + HRESULT WaitForOperation(const std::wstring& productId, bool isSilentMode, IVectorView& installItems, IProgressCallback& progress, const PackageUpdateMonitor& monitor) + { + auto cancelIfOperationFailed = wil::scope_exit( + [&]() + { + try + { + AppInstallManager installManager; + installManager.Cancel(productId); + } + CATCH_LOG(); + }); + + for (auto const& installItem : installItems) + { + AICLI_LOG(Core, Info, << + "Started MSStore package execution. ProductId: " << Utility::ConvertToUTF8(installItem.ProductId()) << + " PackageFamilyName: " << Utility::ConvertToUTF8(installItem.PackageFamilyName())); + + if (isSilentMode) + { + installItem.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + installItem.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + } + } + + HRESULT errorCode = S_OK; + + // We are aggregating all AppInstallItem progresses into one. + // Averaging every progress for now until we have a better way to find overall progress. + uint64_t overallProgressMax = 100 * static_cast(installItems.Size()); + uint64_t currentProgress = 0; + + while (currentProgress < overallProgressMax) + { + currentProgress = 0; + + for (auto const& installItem : installItems) + { + const auto& status = installItem.GetCurrentStatus(); + currentProgress += static_cast(status.PercentComplete()); + + errorCode = status.ErrorCode(); + + if (!SUCCEEDED(errorCode)) + { + return errorCode; + } + } + + // It may take a while for Store client to pick up the install request. + // So we show indefinite progress here to avoid a progress bar stuck at 0. + if (currentProgress > 0) + { + progress.OnProgress(currentProgress, overallProgressMax, ProgressType::Percent); + } + + if (progress.IsCancelledBy(CancelReason::User)) + { + for (auto const& installItem : installItems) + { + installItem.Cancel(); + } + } + + // If app shutdown then we have 30s to keep installing, keep going and hope for the best. + else if (progress.IsCancelledBy(CancelReason::AppShutdown) || monitor.IsUpdating()) + { + for (auto const& installItem : installItems) + { + // Insert spiderman meme. + if (installItem.ProductId() == std::wstring{ s_AppInstallerProductId }) + { + AICLI_LOG(Core, Info, << "Asked to shutdown while installing AppInstaller."); + progress.OnProgress(overallProgressMax, overallProgressMax, ProgressType::Percent); + cancelIfOperationFailed.release(); + return S_OK; + } + } + } + + Sleep(100); + } + + if (SUCCEEDED(errorCode)) + { + cancelIfOperationFailed.release(); + } + + return errorCode; + } + } + + HRESULT MSStoreOperation::StartAndWaitForOperation(IProgressCallback& progress) + { + // Best effort verifying/acquiring product ownership. + std::ignore = EnsureFreeEntitlement(m_productId, m_scope); + + if (m_type == MSStoreOperationType::Update) + { + return UpdatePackage(progress); + } + else + { + return InstallPackage(progress); + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + namespace TestHooks + { + static bool* s_ProvisionAfterInstall = nullptr; + + void TestHook_SetProvisionAfterInstall(bool* value) + { + s_ProvisionAfterInstall = value; + } + } +#endif + + HRESULT MSStoreOperation::InstallPackage(IProgressCallback& progress) + { + PackageUpdateMonitor monitor; + + AppInstallManager installManager; + AppInstallOptions installOptions; + + installOptions.AllowForcedAppRestart(m_force); + if (m_isSilentMode) + { + installOptions.InstallInProgressToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + installOptions.CompletedInstallToastNotificationMode(AppInstallationToastNotificationMode::NoToast); + } + + if (m_type == MSStoreOperationType::Repair) + { + // Attempt to repair the installation of an app that is already installed. + installOptions.Repair(true); + } + + if (m_scope == Manifest::ScopeEnum::Machine) + { + installOptions.InstallForAllUsers(true); + } + + IVectorView installItems = installManager.StartProductInstallAsync( + m_productId, // ProductId + winrt::hstring(), // FlightId + L"WinGetCli", // ClientId + winrt::hstring(), + installOptions).get(); + + // Check if we need to restart or cancel existing items. + auto restartOrCancelResult = RestartOrCancelExistingOperationIfNecessary(installItems, installManager, m_productId); + RETURN_IF_FAILED(restartOrCancelResult); + + // If restart or cancel happened, try again. + if (restartOrCancelResult == S_OK) + { + // Try again + installItems = installManager.StartProductInstallAsync( + m_productId, // ProductId + winrt::hstring(), // FlightId + L"WinGetCli", // ClientId + winrt::hstring(), + installOptions).get(); + } + + HRESULT hr = WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); + + // There was a bug in InstallService where admin users were incorrectly identified as non-admin, + // causing false access denied errors; fixed in 10.0.26100.0. On older OS versions, convert any + // failure under these conditions to the error that was previously always returned. + if (FAILED(hr) && + m_scope == Manifest::ScopeEnum::Machine && + !Runtime::IsRunningAsSystem() && + !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version{ "10.0.26100.0" })) + { + AICLI_LOG(Core, Error, << "Device wide install for msstore type is not supported under admin context on this OS version. Error: " << hr); + return APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED; + } + + bool shouldProvision = m_scope == Manifest::ScopeEnum::Machine; +#ifndef AICLI_DISABLE_TEST_HOOKS + if (TestHooks::s_ProvisionAfterInstall) + { + shouldProvision = *TestHooks::s_ProvisionAfterInstall; + } +#endif + + if (SUCCEEDED(hr) && shouldProvision) + { + // Mitigation: on older OS versions (fixed in Windows 11 26100) the Store install service + // may not provision the package even when InstallForAllUsers was set. + // Explicitly provision if not already provisioned. + EnsureProvisionedForMachineScope(installItems, m_productId, progress); + } + + return hr; + } + + HRESULT MSStoreOperation::UpdatePackage(IProgressCallback& progress) + { + PackageUpdateMonitor monitor; + + AppInstallManager installManager; + AppUpdateOptions updateOptions; + updateOptions.AllowForcedAppRestart(m_force); + + // SearchForUpdateAsync will automatically trigger update if found. + AppInstallItem installItem = installManager.SearchForUpdatesAsync( + m_productId, // ProductId + winrt::hstring(), // SkuId + winrt::hstring(), + winrt::hstring(), // ClientId + updateOptions + ).get(); + + if (!installItem) + { + return APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE; + } + + std::vector installItemVector{ installItem }; + IVectorView installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView(); + + // Check if we need to restart or cancel existing items. + auto restartOrCancelResult = RestartOrCancelExistingOperationIfNecessary(installItems, installManager, m_productId); + RETURN_IF_FAILED(restartOrCancelResult); + + // If restart or cancel happened, try again. + if (restartOrCancelResult == S_OK) + { + // Try again + installItem = installManager.SearchForUpdatesAsync( + m_productId, // ProductId + winrt::hstring(), // SkuId + winrt::hstring(), + winrt::hstring(), // ClientId + updateOptions + ).get(); + + if (!installItem) + { + return APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE; + } + + installItemVector.clear(); + installItemVector.emplace_back(installItem); + installItems = winrt::single_threaded_vector(std::move(installItemVector)).GetView(); + } + + return WaitForOperation(m_productId, m_isSilentMode, installItems, progress, monitor); + } +} diff --git a/src/AppInstallerCommonCore/MSStoreDownload.cpp b/src/AppInstallerCommonCore/MSStoreDownload.cpp index 89d611a3ae..de55e3b300 100644 --- a/src/AppInstallerCommonCore/MSStoreDownload.cpp +++ b/src/AppInstallerCommonCore/MSStoreDownload.cpp @@ -1,1193 +1,1193 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include -#include "AppInstallerMsixInfo.h" -#include "AppInstallerRuntime.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/UserSettings.h" -#ifndef WINGET_DISABLE_FOR_FUZZING -#include -#endif - -namespace AppInstaller::MSStore -{ - using namespace std::string_view_literals; - -#ifndef AICLI_DISABLE_TEST_HOOKS - namespace TestHooks - { - static std::shared_ptr s_DisplayCatalog_HttpPipelineStage_Override = nullptr; - - void SetDisplayCatalogHttpPipelineStage_Override(std::shared_ptr value) - { - s_DisplayCatalog_HttpPipelineStage_Override = value; - } - - static std::function(std::string_view)>* s_SfsClient_AppContents_Override = nullptr; - - void SetSfsClientAppContents_Override(std::function(std::string_view)>* value) - { - s_SfsClient_AppContents_Override = value; - } - - static std::shared_ptr s_Licensing_HttpPipelineStage_Override = nullptr; - - void SetLicensingHttpPipelineStage_Override(std::shared_ptr value) - { - s_Licensing_HttpPipelineStage_Override = value; - } - } -#endif - - namespace DisplayCatalogDetails - { - // Default preferred sku to use - constexpr std::string_view TargetSkuIdValue = "0015"sv; - - // Json response fields - constexpr std::string_view Product = "Product"sv; - constexpr std::string_view DisplaySkuAvailabilities = "DisplaySkuAvailabilities"sv; - constexpr std::string_view Sku = "Sku"sv; - constexpr std::string_view SkuId = "SkuId"sv; - constexpr std::string_view Properties = "Properties"sv; - constexpr std::string_view Packages = "Packages"sv; - constexpr std::string_view Languages = "Languages"sv; - constexpr std::string_view PackageFormat = "PackageFormat"sv; - constexpr std::string_view PackageId = "PackageId"sv; - constexpr std::string_view Architectures = "Architectures"sv; - constexpr std::string_view ContentId = "ContentId"sv; - constexpr std::string_view FulfillmentData = "FulfillmentData"sv; - constexpr std::string_view WuCategoryId = "WuCategoryId"sv; - - // Display catalog rest endpoint - constexpr std::string_view DisplayCatalogRestApi = R"(https://displaycatalog.mp.microsoft.com/v7.0/products/{0}?fieldsTemplate={1}&market={2}&languages={3}&catalogIds={4})"; - constexpr std::string_view Details = "Details"sv; - constexpr std::string_view Neutral = "Neutral"sv; - constexpr std::string_view TargetCatalogId = "4"sv; - - enum class DisplayCatalogPackageFormatEnum - { - Unknown, - AppxBundle, - MsixBundle, - Appx, - Msix, - }; - - DisplayCatalogPackageFormatEnum ConvertToPackageFormatEnum(std::string_view packageFormatStr) - { - std::string packageFormat = Utility::ToLower(packageFormatStr); - if (packageFormat == "appxbundle") - { - return DisplayCatalogPackageFormatEnum::AppxBundle; - } - else if (packageFormat == "msixbundle") - { - return DisplayCatalogPackageFormatEnum::MsixBundle; - } - else if (packageFormat == "appx") - { - return DisplayCatalogPackageFormatEnum::Appx; - } - else if (packageFormat == "msix") - { - return DisplayCatalogPackageFormatEnum::Msix; - } - - AICLI_LOG(Core, Info, << "ConvertToPackageFormatEnum: Unknown package format: " << packageFormatStr); - return DisplayCatalogPackageFormatEnum::Unknown; - } - - struct DisplayCatalogPackage - { - std::string PackageId; - - std::vector Architectures; - - std::vector Languages; - - DisplayCatalogPackageFormatEnum PackageFormat = DisplayCatalogPackageFormatEnum::Unknown; - - // To be used later in sfs-client - std::string WuCategoryId; - - // To be used later in licensing - std::string ContentId; - }; - - // Display catalog package comparison logic. - // The comparator follows similar logic as ManifestComparator. - namespace DisplayCatalogPackageComparison - { - struct DisplayCatalogPackageComparisonField - { - DisplayCatalogPackageComparisonField(std::string_view name) : m_name(name) {} - - virtual ~DisplayCatalogPackageComparisonField() = default; - - std::string_view Name() const { return m_name; } - - virtual bool IsApplicable(const DisplayCatalogPackage& package) = 0; - - virtual bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) = 0; - - private: - std::string_view m_name; - }; - - struct PackageFormatComparator : public DisplayCatalogPackageComparisonField - { - PackageFormatComparator() : DisplayCatalogPackageComparisonField("Package Format") {} - - bool IsApplicable(const DisplayCatalogPackage& package) override - { - return package.PackageFormat != DisplayCatalogPackageFormatEnum::Unknown; - } - - bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) override - { - return IsPackageFormatBundle(first) && !IsPackageFormatBundle(second); - } - - private: - bool IsPackageFormatBundle(const DisplayCatalogPackage& package) - { - return - package.PackageFormat == DisplayCatalogPackageFormatEnum::AppxBundle || - package.PackageFormat == DisplayCatalogPackageFormatEnum::MsixBundle; - } - }; - - struct LocaleComparator : public DisplayCatalogPackageComparisonField - { - LocaleComparator(std::string locale) : DisplayCatalogPackageComparisonField("Locale") - { - if (!locale.empty()) - { - m_locales.emplace_back(std::move(locale)); - m_isRequirement = true; - } - else - { - m_locales = Locale::GetUserPreferredLanguages(); - } - - AICLI_LOG(Core, Verbose, - << "Locale Comparator created with locales: " << Utility::ConvertContainerToString(m_locales) - << " , Is requirement: " << m_isRequirement); - } - - bool IsApplicable(const DisplayCatalogPackage& package) override - { - if (m_isRequirement) - { - for (auto const& locale : m_locales) - { - double distanceScore = GetBestDistanceScoreFromList(locale, package.Languages); - if (distanceScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) - { - return true; - } - } - - return false; - } - else - { - return true; - } - } - - bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) - { - for (auto const& locale : m_locales) - { - double firstScore = GetBestDistanceScoreFromList(locale, first.Languages); - double secondScore = GetBestDistanceScoreFromList(locale, second.Languages); - - if (firstScore >= Locale::MinimumDistanceScoreAsCompatibleMatch || secondScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) - { - return firstScore > secondScore; - } - } - - return false; - } - - private: - double GetBestDistanceScoreFromList(std::string_view targetLocale, const std::vector& locales) - { - double finalScore = 0; - for (auto const& locale : locales) - { - double currentScore = Locale::GetDistanceOfLanguage(targetLocale, locale); - if (currentScore > finalScore) - { - finalScore = currentScore; - } - } - - return finalScore; - } - - std::vector m_locales; - bool m_isRequirement = false; - }; - - struct ArchitectureComparator : public DisplayCatalogPackageComparisonField - { - ArchitectureComparator(Utility::Architecture architecture) : DisplayCatalogPackageComparisonField("Architecture") - { - if (architecture != Utility::Architecture::Unknown) - { - m_architectures.emplace_back(architecture); - m_isRequirement = true; - } - else - { - m_architectures = Utility::GetApplicableArchitectures(); - } - - AICLI_LOG(Core, Verbose, - << "Architecture Comparator created with archs: " << Utility::ConvertContainerToString(m_architectures, Utility::ToString) - << " , Is requirement: " << m_isRequirement); - } - - bool IsApplicable(const DisplayCatalogPackage& package) override - { - if (m_isRequirement) - { - for (auto arch : package.Architectures) - { - if (Utility::IsApplicableArchitecture(arch, m_architectures) > Utility::InapplicableArchitecture) - { - return true; - } - } - - return false; - } - else - { - return true; - } - } - - bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) override - { - for (auto arch : m_architectures) - { - auto firstItr = std::find(first.Architectures.begin(), first.Architectures.end(), arch); - auto secondItr = std::find(second.Architectures.begin(), second.Architectures.end(), arch); - - if (firstItr != first.Architectures.end() && secondItr == second.Architectures.end()) - { - true; - } - else if (secondItr != second.Architectures.end()) - { - return false; - } - } - - return false; - } - - private: - std::vector m_architectures; - bool m_isRequirement = false; - }; - - struct DisplayCatalogPackageComparator - { - DisplayCatalogPackageComparator(std::string requiredLocale, Utility::Architecture requiredArch) - { - // Order of comparators matters. - AddComparator(std::make_unique(requiredLocale)); - AddComparator(std::make_unique(requiredArch)); - AddComparator(std::make_unique()); - } - - // Gets the best installer from the manifest, if at least one is applicable. - std::optional GetPreferredPackage(const std::vector& packages) - { - AICLI_LOG(Core, Verbose, << "Starting display catalog package selection."); - - const DisplayCatalogPackage* result = nullptr; - for (const auto& package : packages) - { - if (IsApplicable(package) && (!result || IsFirstBetter(package, *result))) - { - result = &package; - } - } - - if (result) - { - return *result; - } - else - { - return {}; - } - } - - // Determines if the package is applicable. - bool IsApplicable(const DisplayCatalogPackage& package) - { - for (const auto& comparator : m_comparators) - { - if (!comparator->IsApplicable(package)) - { - return false; - } - } - - return true; - } - - // Determines if the first package is a better choice. - bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) - { - for (const auto& comparator : m_comparators) - { - bool forwardCompare = comparator->IsFirstBetter(first, second); - bool reverseCompare = comparator->IsFirstBetter(second, first); - - if (forwardCompare && reverseCompare) - { - AICLI_LOG(Core, Error, << "Packages are both better than each other?"); - THROW_HR(E_UNEXPECTED); - } - - if (forwardCompare && !reverseCompare) - { - AICLI_LOG(Core, Verbose, << "Package " << first.PackageId << " is better than " << second.PackageId); - return true; - } - } - - AICLI_LOG(Core, Verbose, << "Package " << first.PackageId << " is equivalent in priority to " << second.PackageId); - return false; - } - - private: - void AddComparator(std::unique_ptr&& comparator) - { - if (comparator) - { - m_comparators.emplace_back(std::move(comparator)); - } - } - - std::vector> m_comparators; - }; - } - - // Display catalog API invocation and handling - - utility::string_t GetDisplayCatalogRestApi(std::string_view productId, std::string_view locale) - { - std::vector locales; - if (!locale.empty()) - { - locales.emplace_back(locale); - } - else - { - for (auto const& localeEntry : Locale::GetUserPreferredLanguages()) - { - locales.emplace_back(localeEntry); - } - } - - // Neutral is always added - locales.emplace_back(Neutral); - - auto restEndpoint = AppInstaller::Utility::Format(std::string{ DisplayCatalogRestApi }, - productId, Details, AppInstaller::Runtime::GetOSRegion(), Utility::Join(Utility::LocIndView(","), locales), TargetCatalogId); - - return JSON::GetUtilityString(restEndpoint); - } - - // Response format: - // { - // "Product": { - // "DisplaySkuAvailabilities": [ - // { - // "Sku": { - // "SkuId": "0015", - // ... Sku Contents ... - // } - // } - // ] - // } - // } - std::reference_wrapper GetSkuNodeFromDisplayCatalogResponse(const web::json::value& responseObject) - { - AICLI_LOG(Core, Info, << "Started parsing display catalog response. Try to find target sku: " << TargetSkuIdValue); - - if (responseObject.is_null()) - { - AICLI_LOG(Core, Error, << "Missing DisplayCatalog Response json object."); - THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); - } - - std::optional> product = JSON::GetJsonValueFromNode(responseObject, JSON::GetUtilityString(Product)); - if (!product) - { - AICLI_LOG(Core, Error, << "Missing Product node"); - THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); - } - - auto skuEntries = JSON::GetRawJsonArrayFromJsonNode(product.value().get(), JSON::GetUtilityString(DisplaySkuAvailabilities)); - if (!skuEntries) - { - AICLI_LOG(Core, Error, << "Missing DisplaySkuAvailabilities"); - THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); - } - - for (const auto& skuEntry : skuEntries.value().get()) - { - std::optional> sku = JSON::GetJsonValueFromNode(skuEntry, JSON::GetUtilityString(Sku)); - if (!sku) - { - AICLI_LOG(Core, Error, << "Missing Sku"); - THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); - } - - const auto& skuValue = sku.value().get(); - auto skuId = JSON::GetRawStringValueFromJsonNode(skuValue, JSON::GetUtilityString(SkuId)).value_or(""); - if (TargetSkuIdValue == skuId) - { - AICLI_LOG(Core, Info, << "Target Sku (" << TargetSkuIdValue << ") found"); - return skuValue; - } - } - - AICLI_LOG(Core, Error, << "Target Sku (" << TargetSkuIdValue << ") not found"); - THROW_HR(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); - } - - // Response format: - // { - // "Sku": { - // "Properties": { - // "Packages": [ - // { - // "PackageId": "package id", - // "Architectures": [ "x86", "x64" ], - // "Languages": [ "en", "fr" ], - // "PackageFormat": "AppxBundle", - // "ContentId": "guid", - // "FulfillmentData": { - // "WuCategoryId": "guid", - // } - // } - // ] - // } - // } - // } - std::vector GetDisplayCatalogPackagesFromSkuNode(const web::json::value& jsonObject) - { - AICLI_LOG(Core, Info, << "Started extracting display catalog packages from sku."); - - std::optional> properties = JSON::GetJsonValueFromNode(jsonObject, JSON::GetUtilityString(Properties)); - if (!properties) - { - AICLI_LOG(Core, Error, << "Missing Properties"); - THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); - } - - const auto& propertiesValue = properties.value().get(); - auto packages = JSON::GetRawJsonArrayFromJsonNode(propertiesValue, JSON::GetUtilityString(Packages)); - if (!packages) - { - AICLI_LOG(Core, Error, << "Missing Packages"); - THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); - } - - std::vector displayCatalogPackages; - - for (const auto& packageEntry : packages.value().get()) - { - DisplayCatalogPackage catalogPackage; - - // Package Id - catalogPackage.PackageId = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(PackageId)).value_or(""); - // Architectures - auto architectures = JSON::GetRawStringArrayFromJsonNode(packageEntry, JSON::GetUtilityString(Architectures)); - for (const auto& arch : architectures) - { - auto archEnum = Utility::ConvertToArchitectureEnum(arch); - if (archEnum != Utility::Architecture::Unknown) - { - catalogPackage.Architectures.emplace_back(archEnum); - } - } - // Languages - auto languages = JSON::GetRawStringArrayFromJsonNode(packageEntry, JSON::GetUtilityString(Languages)); - for (const auto& language : languages) - { - catalogPackage.Languages.emplace_back(language); - } - // Package Format - auto packageFormat = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(PackageFormat)).value_or(""); - catalogPackage.PackageFormat = ConvertToPackageFormatEnum(packageFormat); - // Content Id - catalogPackage.ContentId = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(ContentId)).value_or(""); - if (catalogPackage.ContentId.empty()) - { - AICLI_LOG(Core, Warning, << "Missing ContentId"); - // ContentId is required for licensing. Skip this package if missing. - continue; - } - // WuCategoryId - std::optional> fulfillmentData = JSON::GetJsonValueFromNode(packageEntry, JSON::GetUtilityString(FulfillmentData)); - if (!fulfillmentData) - { - AICLI_LOG(Core, Warning, << "Missing FulfillmentData"); - // WuCategoryId is required for sfs-client. Skip this package if missing. - continue; - } - catalogPackage.WuCategoryId = JSON::GetRawStringValueFromJsonNode(fulfillmentData.value().get(), JSON::GetUtilityString(WuCategoryId)).value_or(""); - if (catalogPackage.WuCategoryId.empty()) - { - AICLI_LOG(Core, Warning, << "Missing WuCategoryId"); - // WuCategoryId is required for sfs-client. Skip this package if missing. - continue; - } - - displayCatalogPackages.emplace_back(std::move(catalogPackage)); - } - - return displayCatalogPackages; - } - - DisplayCatalogPackage CallDisplayCatalogAndGetPreferredPackage(std::string_view productId, std::string_view locale, Utility::Architecture architecture, const Http::HttpClientHelper::HttpRequestHeaders& authHeaders) - { - AICLI_LOG(Core, Info, << "CallDisplayCatalogAndGetPreferredPackage with ProductId: " << productId << " Locale: " << locale << " Architecture: " << Utility::ToString(architecture)); - - auto displayCatalogApi = GetDisplayCatalogRestApi(productId, locale); - - AppInstaller::Http::HttpClientHelper httpClientHelper; - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (TestHooks::s_DisplayCatalog_HttpPipelineStage_Override) - { - httpClientHelper = AppInstaller::Http::HttpClientHelper{ TestHooks::s_DisplayCatalog_HttpPipelineStage_Override }; - } -#endif - - std::optional displayCatalogResponseObject = httpClientHelper.HandleGet(displayCatalogApi, {}, authHeaders); - - if (!displayCatalogResponseObject) - { - AICLI_LOG(Core, Error, << "No display catalog json object found"); - THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); - } - - const auto& sku = GetSkuNodeFromDisplayCatalogResponse(displayCatalogResponseObject.value()); - auto displayCatalogPackages = GetDisplayCatalogPackagesFromSkuNode(sku.get()); - - DisplayCatalogPackageComparison::DisplayCatalogPackageComparator packageComparator{ std::string{ locale }, architecture }; - auto preferredPackageResult = packageComparator.GetPreferredPackage(displayCatalogPackages); - - if (!preferredPackageResult) - { - AICLI_LOG(Core, Error, - << "No applicable display catalog package found for ProductId: " << productId - << " , Locale: " << locale << " , Architecture: " << Utility::ToString(architecture)); - - THROW_HR(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); - } - - auto preferredPackage = preferredPackageResult.value(); - - AICLI_LOG(Core, Info, - << "DisplayCatalog package selected. WuCategoryId: " << preferredPackage.WuCategoryId - << " , ContentId: " << preferredPackage.ContentId); - - return preferredPackage; - } - } - -#ifndef WINGET_DISABLE_FOR_FUZZING - namespace SfsClientDetails - { - const std::string SupportedFileTypes[] = { ".msix", ".msixbundle", ".appx", ".appxbundle" }; - - Manifest::PlatformEnum ConvertFromSfsPlatform(std::string_view applicability) - { - if (Utility::CaseInsensitiveStartsWith(applicability, "universal")) - { - return Manifest::PlatformEnum::Universal; - } - else if (Utility::CaseInsensitiveStartsWith(applicability, "desktop")) - { - return Manifest::PlatformEnum::Desktop; - } - else if (Utility::CaseInsensitiveStartsWith(applicability, "iot")) - { - return Manifest::PlatformEnum::IoT; - } - else if (Utility::CaseInsensitiveStartsWith(applicability, "analog")) - { - return Manifest::PlatformEnum::Holographic; - } - else if (Utility::CaseInsensitiveStartsWith(applicability, "ppi")) - { - return Manifest::PlatformEnum::Team; - } - - return Manifest::PlatformEnum::Unknown; - } - - // Parses a string of the form `={,}?`. - struct PlatformApplicability - { - explicit PlatformApplicability(std::string_view input, bool extractVersion = true) : - Platform(ConvertFromSfsPlatform(input)) - { - if (extractVersion) - { - THROW_HR_IF(E_INVALIDARG, input.empty()); - - size_t position = input.find('='); - THROW_HR_IF(E_INVALIDARG, std::string_view::npos == position); - - position += 1; - size_t length = input.size() - position; - if (length > 0 && input.back() == ',') - { - length -= 1; - } - - MinimumVersion = Utility::UInt64Version{ std::string{ input.substr(position, length) } }; - } - } - - Manifest::PlatformEnum Platform; - std::optional MinimumVersion; - }; - - Utility::Architecture ConvertFromSfsArchitecture(SFS::Architecture sfsArchitecture) - { - switch (sfsArchitecture) - { - case SFS::Architecture::Amd64: - return Utility::Architecture::X64; - case SFS::Architecture::x86: - return Utility::Architecture::X86; - case SFS::Architecture::Arm64: - return Utility::Architecture::Arm64; - case SFS::Architecture::Arm: - return Utility::Architecture::Arm; - case SFS::Architecture::None: - return Utility::Architecture::Neutral; - } - - return Utility::Architecture::Unknown; - } - - std::vector GetSfsPackageFileSupportedPlatforms( - const SFS::AppFile& appFile, - Manifest::PlatformEnum requiredPlatform, - const std::optional& targetOSVersion) - { - std::vector supportedPlatforms; - - for (auto const& applicability : appFile.GetApplicabilityDetails().GetPlatformApplicabilityForPackage()) - { - AICLI_LOG(Core, Verbose, << " examining platform [" << applicability << "] for applicability..."); - PlatformApplicability platform(applicability, targetOSVersion.has_value()); - - if (platform.Platform == Manifest::PlatformEnum::Unknown) - { - AICLI_LOG(Core, Verbose, << " not applicable due to unknown platform"); - continue; - } - - if (platform.MinimumVersion && targetOSVersion) - { - if (targetOSVersion.value() < platform.MinimumVersion.value()) - { - AICLI_LOG(Core, Verbose, << " not applicable due to OS version; target [" - << targetOSVersion.value().ToString() << "] is lower than minimum [" - << platform.MinimumVersion.value().ToString() << "]"); - continue; - } - } - - if (platform.Platform == requiredPlatform || requiredPlatform == Manifest::PlatformEnum::Unknown) - { - AICLI_LOG(Core, Verbose, << " applicable"); - supportedPlatforms.emplace_back(platform.Platform); - } - else - { - AICLI_LOG(Core, Verbose, << " not applicable due to platform requirement"); - } - } - - return supportedPlatforms; - } - - std::vector GetSfsPackageFileSupportedArchitectures(const SFS::AppFile& appFile, Utility::Architecture requiredArchitecture) - { - std::vector supportedArchitectures; - - for (auto const& sfsArchitecture : appFile.GetApplicabilityDetails().GetArchitectures()) - { - auto convertedArchitecture = ConvertFromSfsArchitecture(sfsArchitecture); - if (convertedArchitecture == Utility::Architecture::Unknown) - { - continue; - } - - if (requiredArchitecture == Utility::Architecture::Unknown || // No required architecture - convertedArchitecture == requiredArchitecture) - { - supportedArchitectures.emplace_back(convertedArchitecture); - } - } - - return supportedArchitectures; - } - - std::string GetSfsPackageFileExtension(const SFS::AppFile& appFile) - { - return std::filesystem::path{ appFile.GetFileId() }.extension().u8string(); - } - - bool IsFileExtensionSupported(std::string_view fileExtension) - { - for (auto const& supportedFileType : SupportedFileTypes) - { - if (Utility::CaseInsensitiveEquals(supportedFileType, fileExtension)) - { - return true; - } - } - - return false; - } - - // The file name will be {Name}_{Version}_{Platform list}_{Arch list}.{File Extension} - // If the file name is longer than 256, file moniker will be used. - std::string GetSfsPackageFileNameForDownload( - const std::string& packageName, - const Utility::UInt64Version& packageVersion, - const std::vector& supportedPlatforms, - const std::vector& supportedArchitectures, - const std::string& fileExtension, - const std::string& fileMoniker) - { - std::string platformString; - for (auto platform : supportedPlatforms) - { - platformString += std::string{ Manifest::PlatformToString(platform, true) } + '.'; - } - platformString.resize(platformString.size() - 1); - - std::string architectureString; - for (auto architecture : supportedArchitectures) - { - architectureString += std::string{ Utility::ToString(architecture) } + '.'; - } - architectureString.resize(architectureString.size() - 1); - - std::string fileName = - packageName + '_' + - packageVersion.ToString() + '_' + - platformString + '_' + - architectureString + - fileExtension; - - if (fileName.length() < 256) - { - return fileName; - } - else - { - return fileMoniker + fileExtension; - } - } - - void SfsClientLoggingCallback(const SFS::LogData& logData) - { - std::string message = "Message: " + std::string{ logData.message }; - message += " File: " + std::string{ logData.file }; - message += " Line: " + std::to_string(logData.line); - message += " Function: " + std::string{ logData.function }; - - switch (logData.severity) - { - case SFS::LogSeverity::Verbose: - AICLI_LOG(Core, Verbose, << message); - break; - case SFS::LogSeverity::Info: - AICLI_LOG(Core, Info, << message); - break; - case SFS::LogSeverity::Warning: - AICLI_LOG(Core, Warning, << message); - break; - case SFS::LogSeverity::Error: - AICLI_LOG(Core, Error, << message); - break; - } - } - - const std::unique_ptr& GetSfsClientInstance() - { - static std::unique_ptr s_sfsClient; - static std::once_flag s_sfsClientInitializeOnce; - - std::call_once(s_sfsClientInitializeOnce, - [&]() - { - SFS::ClientConfig config; - config.accountId = "storeapps"; - config.instanceId = "storeapps"; - config.logCallbackFn = SfsClientLoggingCallback; - - auto result = SFS::SFSClient::Make(config, s_sfsClient); - if (!result) - { - AICLI_LOG(Core, Error, << "Failed to initialize SfsClient. Error code: " << result.GetCode() << " Message: " << result.GetMsg()); - THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED, "Failed to initialize SfsClient. ErrorCode: %lu Message: %hs", result.GetCode(), result.GetMsg().c_str()); - } - }); - - return s_sfsClient; - } - - std::vector PopulateSfsAppFileToMSStoreDownloadFileVector( - const std::vector& appFiles, - Utility::Architecture requiredArchitecture = Utility::Architecture::Unknown, - Manifest::PlatformEnum requiredPlatform = Manifest::PlatformEnum::Unknown, - const std::optional& targetOSVersion = std::nullopt) - { - using PlatformAndArchitectureKey = std::pair; - - // Since the server may return multiple versions of the same package, we'll use this map to record the one with latest version - // for each Platform|Architecture pair. - std::map downloadFilesMap; - - for (auto const& appFile : appFiles) - { - AICLI_LOG(Core, Info, << "Examining package [" << appFile.GetFileMoniker() << " (" << appFile.GetFileId() << ")] for download..."); - - // Filter out unsupported packages - auto supportedPlatforms = GetSfsPackageFileSupportedPlatforms(appFile, requiredPlatform, targetOSVersion); - if (supportedPlatforms.empty()) - { - AICLI_LOG(Core, Verbose, << " package has no applicable platform."); - continue; - } - auto supportedArchitectures = GetSfsPackageFileSupportedArchitectures(appFile, requiredArchitecture); - if (supportedArchitectures.empty()) - { - AICLI_LOG(Core, Verbose, << " package has no applicable architecture."); - continue; - } - std::string fileExtension = GetSfsPackageFileExtension(appFile); - if (!IsFileExtensionSupported(fileExtension)) - { - AICLI_LOG(Core, Verbose, << " package has unsupported file type [" << fileExtension << "]."); - continue; - } - - MSStoreDownloadFile downloadFile; - downloadFile.Url = appFile.GetUrl(); - // The sha256 hash was base64 encoded - downloadFile.Sha256 = JSON::Base64Decode(appFile.GetHashes().at(SFS::HashType::Sha256)); - auto packageInfo = Msix::GetPackageIdInfoFromFullName(appFile.GetFileMoniker()); - downloadFile.Version = packageInfo.Version; - downloadFile.FileName = GetSfsPackageFileNameForDownload( - packageInfo.Name, packageInfo.Version, supportedPlatforms, - supportedArchitectures, fileExtension, appFile.GetFileMoniker()); - - // Update the platform architecture map with latest package if applicable - for (auto supportedPlatform : supportedPlatforms) - { - for (auto supportedArchitecture : supportedArchitectures) - { - PlatformAndArchitectureKey downloadFileKey{ supportedPlatform, supportedArchitecture }; - if (downloadFile.Version > downloadFilesMap[downloadFileKey].Version) - { - downloadFilesMap[downloadFileKey] = downloadFile; - } - } - } - } - - // Generate MSStoreDownloadFile vector and remove duplication. - std::vector result; - for (auto& downloadFileEntry : downloadFilesMap) - { - if (std::find_if(result.begin(), result.end(), - [&](const MSStoreDownloadFile& downloadFile) - { - return Utility::CaseInsensitiveEquals(downloadFile.FileName, downloadFileEntry.second.FileName); - }) == result.end()) - { - result.emplace_back(std::move(downloadFileEntry.second)); - } - } - - return result; - } - - MSStoreDownloadInfo CallSfsClientAndGetMSStoreDownloadInfo( - std::string_view wuCategoryId, - Utility::Architecture requiredArchitecture, - Manifest::PlatformEnum requiredPlatform, - const std::optional& targetOSVersion) - { - AICLI_LOG(Core, Info, << "CallSfsClientAndGetMSStoreDownloadInfo with WuCategoryId: " << wuCategoryId - << " Architecture: " << Utility::ToString(requiredArchitecture) << " Platform: " << Manifest::PlatformToString(requiredPlatform) - << " Target OS Version: " << (targetOSVersion ? targetOSVersion.value().ToString() : "any")); - - std::vector appContents; - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (TestHooks::s_SfsClient_AppContents_Override) - { - appContents = (*TestHooks::s_SfsClient_AppContents_Override)(wuCategoryId); - } - else -#endif - { - 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) - { - if (requestResult.GetCode() == SFS::Result::Code::HttpNotFound) - { - AICLI_LOG(Core, Error, << "Failed to call SfsClient GetLatestAppDownloadInfo. Package not found."); - THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED, "Failed to call SfsClient GetLatestAppDownloadInfo. Package download not supported."); - } - else - { - AICLI_LOG(Core, Error, << "Failed to call SfsClient GetLatestAppDownloadInfo. Error code: " << requestResult.GetCode() << " Message: " << requestResult.GetMsg()); - THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED, "Failed to call SfsClient GetLatestAppDownloadInfo. ErrorCode: %lu Message: %hs", requestResult.GetCode(), requestResult.GetMsg().c_str()); - } - } - } - - THROW_HR_IF(E_UNEXPECTED, appContents.empty()); - - MSStoreDownloadInfo result; - // Currently for app downloads, the result vector is always size 1. - const auto& appContent = appContents.at(0); - - // Populate main packages - result.MainPackages = PopulateSfsAppFileToMSStoreDownloadFileVector(appContent.GetFiles(), requiredArchitecture, requiredPlatform, targetOSVersion); - - // Populate dependency packages - for (auto const& dependencyEntry : appContent.GetPrerequisites()) - { - // Not passing in required platform for dependencies. Dependencies are mostly Windows.Universal. - auto dependencyPackages = PopulateSfsAppFileToMSStoreDownloadFileVector(dependencyEntry.GetFiles(), requiredArchitecture, Manifest::PlatformEnum::Unknown, targetOSVersion); - std::move(dependencyPackages.begin(), dependencyPackages.end(), std::inserter(result.DependencyPackages, result.DependencyPackages.end())); - } - - if (result.MainPackages.empty()) - { - AICLI_LOG(Core, Error, << "No applicable SFS main package."); - THROW_HR(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE); - } - - return result; - } - } -#endif - - namespace LicensingDetails - { - // Json response fields - constexpr std::string_view License = "license"sv; - constexpr std::string_view Keys = "keys"sv; - constexpr std::string_view Value = "value"sv; - - // Licensing rest endpoint - constexpr std::string_view LicensingRestEndpoint = "https://licensing.md.mp.microsoft.com/v9.0/licenses/offlineContent"; - constexpr std::string_view ContentId = "contentId"sv; - constexpr std::string_view From = "From"sv; - - // Response: - // { - // "license": { - // "keys": [ // returned as array for future, for now only 1 key - // { - // "value": "base64 encoded string" - // } - // ] - // } - // } - std::vector GetLicensing(std::string_view contentId, const Http::HttpClientHelper::HttpRequestHeaders& authHeaders) - { - AICLI_LOG(Core, Error, << "GetLicensing with ContentId: " << contentId); - - AppInstaller::Http::HttpClientHelper httpClientHelper; - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (TestHooks::s_Licensing_HttpPipelineStage_Override) - { - httpClientHelper = AppInstaller::Http::HttpClientHelper{ TestHooks::s_Licensing_HttpPipelineStage_Override }; - } -#endif - - web::json::value requestBody; - requestBody[JSON::GetUtilityString(ContentId)] = web::json::value::string(JSON::GetUtilityString(contentId)); - Http::HttpClientHelper::HttpRequestHeaders requestHeaders; - requestHeaders.insert_or_assign(JSON::GetUtilityString(From), L"winget-cli"); - - std::optional licensingResponseObject = std::nullopt; - try - { - licensingResponseObject = httpClientHelper.HandlePost( - JSON::GetUtilityString(LicensingRestEndpoint), requestBody, requestHeaders, authHeaders); - } - catch (const wil::ResultException& re) - { - if (re.GetErrorCode() == HTTP_E_STATUS_FORBIDDEN) - { - AICLI_LOG(CLI, Error, << "Getting MSStore package license failed. The Microsoft Entra Id account does not have privilege."); - THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN); - } - else - { - AICLI_LOG(CLI, Error, << "Getting MSStore package license failed. Error code: " << re.GetErrorCode()); - THROW_HR(re.GetErrorCode()); - } - } - - if (!licensingResponseObject || licensingResponseObject->is_null()) - { - AICLI_LOG(Core, Error, << "Empty licensing response"); - THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); - } - - std::optional> license = JSON::GetJsonValueFromNode(licensingResponseObject.value(), JSON::GetUtilityString(License)); - if (!license) - { - AICLI_LOG(Core, Error, << "Missing license node"); - THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); - } - - auto keys = JSON::GetRawJsonArrayFromJsonNode(license.value().get(), JSON::GetUtilityString(Keys)); - if (!keys || keys->get().size() == 0) - { - AICLI_LOG(Core, Error, << "Missing keys or empty keys"); - THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); - } - - std::string base64LicenseContent = JSON::GetRawStringValueFromJsonNode(keys->get().at(0), JSON::GetUtilityString(Value)).value_or(""); - if (base64LicenseContent.empty()) - { - AICLI_LOG(Core, Error, << "Missing license content"); - THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); - } - - return JSON::Base64Decode(base64LicenseContent); - } - } - - namespace - { - Http::HttpClientHelper::HttpRequestHeaders GetAuthHeaders(std::unique_ptr& authenticator) - { - if (!authenticator) - { - return {}; - } - - Http::HttpClientHelper::HttpRequestHeaders result; - - auto authResult = authenticator->AuthenticateForToken(); - if (FAILED(authResult.Status)) - { - AICLI_LOG(Repo, Error, << "Authentication failed. Result: " << authResult.Status); - THROW_HR_MSG(authResult.Status, "Failed to authenticate for MicrosoftEntraId"); - } - result.insert_or_assign(web::http::header_names::authorization, JSON::GetUtilityString(Authentication::CreateBearerToken(authResult.Token))); - - return result; - } - } - - MSStoreDownloadContext::MSStoreDownloadContext( - std::string productId, - AppInstaller::Utility::Architecture architecture, - Manifest::PlatformEnum platform, - std::string locale, - AppInstaller::Authentication::AuthenticationArguments authArgs) : - m_productId(std::move(productId)), m_architecture(architecture), m_platform(platform), m_locale(std::move(locale)) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (!TestHooks::s_DisplayCatalog_HttpPipelineStage_Override) -#endif - { - Authentication::MicrosoftEntraIdAuthenticationInfo displayCatalogMicrosoftEntraIdAuthInfo; - displayCatalogMicrosoftEntraIdAuthInfo.Resource = "https://bigcatalog.commerce.microsoft.com"; - Authentication::AuthenticationInfo displayCatalogAuthInfo; - displayCatalogAuthInfo.Type = Authentication::AuthenticationType::MicrosoftEntraId; - displayCatalogAuthInfo.MicrosoftEntraIdInfo = std::move(displayCatalogMicrosoftEntraIdAuthInfo); - - m_displayCatalogAuthenticator = std::make_unique(std::move(displayCatalogAuthInfo), authArgs); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (!TestHooks::s_Licensing_HttpPipelineStage_Override) -#endif - { - Authentication::MicrosoftEntraIdAuthenticationInfo licensingMicrosoftEntraIdAuthInfo; - licensingMicrosoftEntraIdAuthInfo.Resource = "c5e1cb0d-5d24-4b1a-b291-ec684152b2ba"; - Authentication::AuthenticationInfo licensingAuthInfo; - licensingAuthInfo.Type = Authentication::AuthenticationType::MicrosoftEntraId; - licensingAuthInfo.MicrosoftEntraIdInfo = std::move(licensingMicrosoftEntraIdAuthInfo); - - m_licensingAuthenticator = std::make_unique(std::move(licensingAuthInfo), authArgs); - } - } - - void MSStoreDownloadContext::TargetOSVersion(std::optional targetOSVersion) - { - m_targetOSVersion = std::move(targetOSVersion); - } - - MSStoreDownloadInfo MSStoreDownloadContext::GetDownloadInfo() - { -#ifndef WINGET_DISABLE_FOR_FUZZING - auto displayCatalogPackage = DisplayCatalogDetails::CallDisplayCatalogAndGetPreferredPackage(m_productId, m_locale, m_architecture, GetAuthHeaders(m_displayCatalogAuthenticator)); - auto downloadInfo = SfsClientDetails::CallSfsClientAndGetMSStoreDownloadInfo(displayCatalogPackage.WuCategoryId, m_architecture, m_platform, m_targetOSVersion); - downloadInfo.ContentId = displayCatalogPackage.ContentId; - return downloadInfo; -#else - return {}; -#endif - } - - std::vector MSStoreDownloadContext::GetLicense(std::string_view contentId) - { - return LicensingDetails::GetLicensing(contentId, GetAuthHeaders(m_licensingAuthenticator)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include +#include "AppInstallerMsixInfo.h" +#include "AppInstallerRuntime.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/UserSettings.h" +#ifndef WINGET_DISABLE_FOR_FUZZING +#include +#endif + +namespace AppInstaller::MSStore +{ + using namespace std::string_view_literals; + +#ifndef AICLI_DISABLE_TEST_HOOKS + namespace TestHooks + { + static std::shared_ptr s_DisplayCatalog_HttpPipelineStage_Override = nullptr; + + void SetDisplayCatalogHttpPipelineStage_Override(std::shared_ptr value) + { + s_DisplayCatalog_HttpPipelineStage_Override = value; + } + + static std::function(std::string_view)>* s_SfsClient_AppContents_Override = nullptr; + + void SetSfsClientAppContents_Override(std::function(std::string_view)>* value) + { + s_SfsClient_AppContents_Override = value; + } + + static std::shared_ptr s_Licensing_HttpPipelineStage_Override = nullptr; + + void SetLicensingHttpPipelineStage_Override(std::shared_ptr value) + { + s_Licensing_HttpPipelineStage_Override = value; + } + } +#endif + + namespace DisplayCatalogDetails + { + // Default preferred sku to use + constexpr std::string_view TargetSkuIdValue = "0015"sv; + + // Json response fields + constexpr std::string_view Product = "Product"sv; + constexpr std::string_view DisplaySkuAvailabilities = "DisplaySkuAvailabilities"sv; + constexpr std::string_view Sku = "Sku"sv; + constexpr std::string_view SkuId = "SkuId"sv; + constexpr std::string_view Properties = "Properties"sv; + constexpr std::string_view Packages = "Packages"sv; + constexpr std::string_view Languages = "Languages"sv; + constexpr std::string_view PackageFormat = "PackageFormat"sv; + constexpr std::string_view PackageId = "PackageId"sv; + constexpr std::string_view Architectures = "Architectures"sv; + constexpr std::string_view ContentId = "ContentId"sv; + constexpr std::string_view FulfillmentData = "FulfillmentData"sv; + constexpr std::string_view WuCategoryId = "WuCategoryId"sv; + + // Display catalog rest endpoint + constexpr std::string_view DisplayCatalogRestApi = R"(https://displaycatalog.mp.microsoft.com/v7.0/products/{0}?fieldsTemplate={1}&market={2}&languages={3}&catalogIds={4})"; + constexpr std::string_view Details = "Details"sv; + constexpr std::string_view Neutral = "Neutral"sv; + constexpr std::string_view TargetCatalogId = "4"sv; + + enum class DisplayCatalogPackageFormatEnum + { + Unknown, + AppxBundle, + MsixBundle, + Appx, + Msix, + }; + + DisplayCatalogPackageFormatEnum ConvertToPackageFormatEnum(std::string_view packageFormatStr) + { + std::string packageFormat = Utility::ToLower(packageFormatStr); + if (packageFormat == "appxbundle") + { + return DisplayCatalogPackageFormatEnum::AppxBundle; + } + else if (packageFormat == "msixbundle") + { + return DisplayCatalogPackageFormatEnum::MsixBundle; + } + else if (packageFormat == "appx") + { + return DisplayCatalogPackageFormatEnum::Appx; + } + else if (packageFormat == "msix") + { + return DisplayCatalogPackageFormatEnum::Msix; + } + + AICLI_LOG(Core, Info, << "ConvertToPackageFormatEnum: Unknown package format: " << packageFormatStr); + return DisplayCatalogPackageFormatEnum::Unknown; + } + + struct DisplayCatalogPackage + { + std::string PackageId; + + std::vector Architectures; + + std::vector Languages; + + DisplayCatalogPackageFormatEnum PackageFormat = DisplayCatalogPackageFormatEnum::Unknown; + + // To be used later in sfs-client + std::string WuCategoryId; + + // To be used later in licensing + std::string ContentId; + }; + + // Display catalog package comparison logic. + // The comparator follows similar logic as ManifestComparator. + namespace DisplayCatalogPackageComparison + { + struct DisplayCatalogPackageComparisonField + { + DisplayCatalogPackageComparisonField(std::string_view name) : m_name(name) {} + + virtual ~DisplayCatalogPackageComparisonField() = default; + + std::string_view Name() const { return m_name; } + + virtual bool IsApplicable(const DisplayCatalogPackage& package) = 0; + + virtual bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) = 0; + + private: + std::string_view m_name; + }; + + struct PackageFormatComparator : public DisplayCatalogPackageComparisonField + { + PackageFormatComparator() : DisplayCatalogPackageComparisonField("Package Format") {} + + bool IsApplicable(const DisplayCatalogPackage& package) override + { + return package.PackageFormat != DisplayCatalogPackageFormatEnum::Unknown; + } + + bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) override + { + return IsPackageFormatBundle(first) && !IsPackageFormatBundle(second); + } + + private: + bool IsPackageFormatBundle(const DisplayCatalogPackage& package) + { + return + package.PackageFormat == DisplayCatalogPackageFormatEnum::AppxBundle || + package.PackageFormat == DisplayCatalogPackageFormatEnum::MsixBundle; + } + }; + + struct LocaleComparator : public DisplayCatalogPackageComparisonField + { + LocaleComparator(std::string locale) : DisplayCatalogPackageComparisonField("Locale") + { + if (!locale.empty()) + { + m_locales.emplace_back(std::move(locale)); + m_isRequirement = true; + } + else + { + m_locales = Locale::GetUserPreferredLanguages(); + } + + AICLI_LOG(Core, Verbose, + << "Locale Comparator created with locales: " << Utility::ConvertContainerToString(m_locales) + << " , Is requirement: " << m_isRequirement); + } + + bool IsApplicable(const DisplayCatalogPackage& package) override + { + if (m_isRequirement) + { + for (auto const& locale : m_locales) + { + double distanceScore = GetBestDistanceScoreFromList(locale, package.Languages); + if (distanceScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) + { + return true; + } + } + + return false; + } + else + { + return true; + } + } + + bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) + { + for (auto const& locale : m_locales) + { + double firstScore = GetBestDistanceScoreFromList(locale, first.Languages); + double secondScore = GetBestDistanceScoreFromList(locale, second.Languages); + + if (firstScore >= Locale::MinimumDistanceScoreAsCompatibleMatch || secondScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) + { + return firstScore > secondScore; + } + } + + return false; + } + + private: + double GetBestDistanceScoreFromList(std::string_view targetLocale, const std::vector& locales) + { + double finalScore = 0; + for (auto const& locale : locales) + { + double currentScore = Locale::GetDistanceOfLanguage(targetLocale, locale); + if (currentScore > finalScore) + { + finalScore = currentScore; + } + } + + return finalScore; + } + + std::vector m_locales; + bool m_isRequirement = false; + }; + + struct ArchitectureComparator : public DisplayCatalogPackageComparisonField + { + ArchitectureComparator(Utility::Architecture architecture) : DisplayCatalogPackageComparisonField("Architecture") + { + if (architecture != Utility::Architecture::Unknown) + { + m_architectures.emplace_back(architecture); + m_isRequirement = true; + } + else + { + m_architectures = Utility::GetApplicableArchitectures(); + } + + AICLI_LOG(Core, Verbose, + << "Architecture Comparator created with archs: " << Utility::ConvertContainerToString(m_architectures, Utility::ToString) + << " , Is requirement: " << m_isRequirement); + } + + bool IsApplicable(const DisplayCatalogPackage& package) override + { + if (m_isRequirement) + { + for (auto arch : package.Architectures) + { + if (Utility::IsApplicableArchitecture(arch, m_architectures) > Utility::InapplicableArchitecture) + { + return true; + } + } + + return false; + } + else + { + return true; + } + } + + bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) override + { + for (auto arch : m_architectures) + { + auto firstItr = std::find(first.Architectures.begin(), first.Architectures.end(), arch); + auto secondItr = std::find(second.Architectures.begin(), second.Architectures.end(), arch); + + if (firstItr != first.Architectures.end() && secondItr == second.Architectures.end()) + { + true; + } + else if (secondItr != second.Architectures.end()) + { + return false; + } + } + + return false; + } + + private: + std::vector m_architectures; + bool m_isRequirement = false; + }; + + struct DisplayCatalogPackageComparator + { + DisplayCatalogPackageComparator(std::string requiredLocale, Utility::Architecture requiredArch) + { + // Order of comparators matters. + AddComparator(std::make_unique(requiredLocale)); + AddComparator(std::make_unique(requiredArch)); + AddComparator(std::make_unique()); + } + + // Gets the best installer from the manifest, if at least one is applicable. + std::optional GetPreferredPackage(const std::vector& packages) + { + AICLI_LOG(Core, Verbose, << "Starting display catalog package selection."); + + const DisplayCatalogPackage* result = nullptr; + for (const auto& package : packages) + { + if (IsApplicable(package) && (!result || IsFirstBetter(package, *result))) + { + result = &package; + } + } + + if (result) + { + return *result; + } + else + { + return {}; + } + } + + // Determines if the package is applicable. + bool IsApplicable(const DisplayCatalogPackage& package) + { + for (const auto& comparator : m_comparators) + { + if (!comparator->IsApplicable(package)) + { + return false; + } + } + + return true; + } + + // Determines if the first package is a better choice. + bool IsFirstBetter(const DisplayCatalogPackage& first, const DisplayCatalogPackage& second) + { + for (const auto& comparator : m_comparators) + { + bool forwardCompare = comparator->IsFirstBetter(first, second); + bool reverseCompare = comparator->IsFirstBetter(second, first); + + if (forwardCompare && reverseCompare) + { + AICLI_LOG(Core, Error, << "Packages are both better than each other?"); + THROW_HR(E_UNEXPECTED); + } + + if (forwardCompare && !reverseCompare) + { + AICLI_LOG(Core, Verbose, << "Package " << first.PackageId << " is better than " << second.PackageId); + return true; + } + } + + AICLI_LOG(Core, Verbose, << "Package " << first.PackageId << " is equivalent in priority to " << second.PackageId); + return false; + } + + private: + void AddComparator(std::unique_ptr&& comparator) + { + if (comparator) + { + m_comparators.emplace_back(std::move(comparator)); + } + } + + std::vector> m_comparators; + }; + } + + // Display catalog API invocation and handling + + utility::string_t GetDisplayCatalogRestApi(std::string_view productId, std::string_view locale) + { + std::vector locales; + if (!locale.empty()) + { + locales.emplace_back(locale); + } + else + { + for (auto const& localeEntry : Locale::GetUserPreferredLanguages()) + { + locales.emplace_back(localeEntry); + } + } + + // Neutral is always added + locales.emplace_back(Neutral); + + auto restEndpoint = AppInstaller::Utility::Format(std::string{ DisplayCatalogRestApi }, + productId, Details, AppInstaller::Runtime::GetOSRegion(), Utility::Join(Utility::LocIndView(","), locales), TargetCatalogId); + + return JSON::GetUtilityString(restEndpoint); + } + + // Response format: + // { + // "Product": { + // "DisplaySkuAvailabilities": [ + // { + // "Sku": { + // "SkuId": "0015", + // ... Sku Contents ... + // } + // } + // ] + // } + // } + std::reference_wrapper GetSkuNodeFromDisplayCatalogResponse(const web::json::value& responseObject) + { + AICLI_LOG(Core, Info, << "Started parsing display catalog response. Try to find target sku: " << TargetSkuIdValue); + + if (responseObject.is_null()) + { + AICLI_LOG(Core, Error, << "Missing DisplayCatalog Response json object."); + THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); + } + + std::optional> product = JSON::GetJsonValueFromNode(responseObject, JSON::GetUtilityString(Product)); + if (!product) + { + AICLI_LOG(Core, Error, << "Missing Product node"); + THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); + } + + auto skuEntries = JSON::GetRawJsonArrayFromJsonNode(product.value().get(), JSON::GetUtilityString(DisplaySkuAvailabilities)); + if (!skuEntries) + { + AICLI_LOG(Core, Error, << "Missing DisplaySkuAvailabilities"); + THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); + } + + for (const auto& skuEntry : skuEntries.value().get()) + { + std::optional> sku = JSON::GetJsonValueFromNode(skuEntry, JSON::GetUtilityString(Sku)); + if (!sku) + { + AICLI_LOG(Core, Error, << "Missing Sku"); + THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); + } + + const auto& skuValue = sku.value().get(); + auto skuId = JSON::GetRawStringValueFromJsonNode(skuValue, JSON::GetUtilityString(SkuId)).value_or(""); + if (TargetSkuIdValue == skuId) + { + AICLI_LOG(Core, Info, << "Target Sku (" << TargetSkuIdValue << ") found"); + return skuValue; + } + } + + AICLI_LOG(Core, Error, << "Target Sku (" << TargetSkuIdValue << ") not found"); + THROW_HR(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); + } + + // Response format: + // { + // "Sku": { + // "Properties": { + // "Packages": [ + // { + // "PackageId": "package id", + // "Architectures": [ "x86", "x64" ], + // "Languages": [ "en", "fr" ], + // "PackageFormat": "AppxBundle", + // "ContentId": "guid", + // "FulfillmentData": { + // "WuCategoryId": "guid", + // } + // } + // ] + // } + // } + // } + std::vector GetDisplayCatalogPackagesFromSkuNode(const web::json::value& jsonObject) + { + AICLI_LOG(Core, Info, << "Started extracting display catalog packages from sku."); + + std::optional> properties = JSON::GetJsonValueFromNode(jsonObject, JSON::GetUtilityString(Properties)); + if (!properties) + { + AICLI_LOG(Core, Error, << "Missing Properties"); + THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); + } + + const auto& propertiesValue = properties.value().get(); + auto packages = JSON::GetRawJsonArrayFromJsonNode(propertiesValue, JSON::GetUtilityString(Packages)); + if (!packages) + { + AICLI_LOG(Core, Error, << "Missing Packages"); + THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); + } + + std::vector displayCatalogPackages; + + for (const auto& packageEntry : packages.value().get()) + { + DisplayCatalogPackage catalogPackage; + + // Package Id + catalogPackage.PackageId = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(PackageId)).value_or(""); + // Architectures + auto architectures = JSON::GetRawStringArrayFromJsonNode(packageEntry, JSON::GetUtilityString(Architectures)); + for (const auto& arch : architectures) + { + auto archEnum = Utility::ConvertToArchitectureEnum(arch); + if (archEnum != Utility::Architecture::Unknown) + { + catalogPackage.Architectures.emplace_back(archEnum); + } + } + // Languages + auto languages = JSON::GetRawStringArrayFromJsonNode(packageEntry, JSON::GetUtilityString(Languages)); + for (const auto& language : languages) + { + catalogPackage.Languages.emplace_back(language); + } + // Package Format + auto packageFormat = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(PackageFormat)).value_or(""); + catalogPackage.PackageFormat = ConvertToPackageFormatEnum(packageFormat); + // Content Id + catalogPackage.ContentId = JSON::GetRawStringValueFromJsonNode(packageEntry, JSON::GetUtilityString(ContentId)).value_or(""); + if (catalogPackage.ContentId.empty()) + { + AICLI_LOG(Core, Warning, << "Missing ContentId"); + // ContentId is required for licensing. Skip this package if missing. + continue; + } + // WuCategoryId + std::optional> fulfillmentData = JSON::GetJsonValueFromNode(packageEntry, JSON::GetUtilityString(FulfillmentData)); + if (!fulfillmentData) + { + AICLI_LOG(Core, Warning, << "Missing FulfillmentData"); + // WuCategoryId is required for sfs-client. Skip this package if missing. + continue; + } + catalogPackage.WuCategoryId = JSON::GetRawStringValueFromJsonNode(fulfillmentData.value().get(), JSON::GetUtilityString(WuCategoryId)).value_or(""); + if (catalogPackage.WuCategoryId.empty()) + { + AICLI_LOG(Core, Warning, << "Missing WuCategoryId"); + // WuCategoryId is required for sfs-client. Skip this package if missing. + continue; + } + + displayCatalogPackages.emplace_back(std::move(catalogPackage)); + } + + return displayCatalogPackages; + } + + DisplayCatalogPackage CallDisplayCatalogAndGetPreferredPackage(std::string_view productId, std::string_view locale, Utility::Architecture architecture, const Http::HttpClientHelper::HttpRequestHeaders& authHeaders) + { + AICLI_LOG(Core, Info, << "CallDisplayCatalogAndGetPreferredPackage with ProductId: " << productId << " Locale: " << locale << " Architecture: " << Utility::ToString(architecture)); + + auto displayCatalogApi = GetDisplayCatalogRestApi(productId, locale); + + AppInstaller::Http::HttpClientHelper httpClientHelper; + +#ifndef AICLI_DISABLE_TEST_HOOKS + if (TestHooks::s_DisplayCatalog_HttpPipelineStage_Override) + { + httpClientHelper = AppInstaller::Http::HttpClientHelper{ TestHooks::s_DisplayCatalog_HttpPipelineStage_Override }; + } +#endif + + std::optional displayCatalogResponseObject = httpClientHelper.HandleGet(displayCatalogApi, {}, authHeaders); + + if (!displayCatalogResponseObject) + { + AICLI_LOG(Core, Error, << "No display catalog json object found"); + THROW_HR(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED); + } + + const auto& sku = GetSkuNodeFromDisplayCatalogResponse(displayCatalogResponseObject.value()); + auto displayCatalogPackages = GetDisplayCatalogPackagesFromSkuNode(sku.get()); + + DisplayCatalogPackageComparison::DisplayCatalogPackageComparator packageComparator{ std::string{ locale }, architecture }; + auto preferredPackageResult = packageComparator.GetPreferredPackage(displayCatalogPackages); + + if (!preferredPackageResult) + { + AICLI_LOG(Core, Error, + << "No applicable display catalog package found for ProductId: " << productId + << " , Locale: " << locale << " , Architecture: " << Utility::ToString(architecture)); + + THROW_HR(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE); + } + + auto preferredPackage = preferredPackageResult.value(); + + AICLI_LOG(Core, Info, + << "DisplayCatalog package selected. WuCategoryId: " << preferredPackage.WuCategoryId + << " , ContentId: " << preferredPackage.ContentId); + + return preferredPackage; + } + } + +#ifndef WINGET_DISABLE_FOR_FUZZING + namespace SfsClientDetails + { + const std::string SupportedFileTypes[] = { ".msix", ".msixbundle", ".appx", ".appxbundle" }; + + Manifest::PlatformEnum ConvertFromSfsPlatform(std::string_view applicability) + { + if (Utility::CaseInsensitiveStartsWith(applicability, "universal")) + { + return Manifest::PlatformEnum::Universal; + } + else if (Utility::CaseInsensitiveStartsWith(applicability, "desktop")) + { + return Manifest::PlatformEnum::Desktop; + } + else if (Utility::CaseInsensitiveStartsWith(applicability, "iot")) + { + return Manifest::PlatformEnum::IoT; + } + else if (Utility::CaseInsensitiveStartsWith(applicability, "analog")) + { + return Manifest::PlatformEnum::Holographic; + } + else if (Utility::CaseInsensitiveStartsWith(applicability, "ppi")) + { + return Manifest::PlatformEnum::Team; + } + + return Manifest::PlatformEnum::Unknown; + } + + // Parses a string of the form `={,}?`. + struct PlatformApplicability + { + explicit PlatformApplicability(std::string_view input, bool extractVersion = true) : + Platform(ConvertFromSfsPlatform(input)) + { + if (extractVersion) + { + THROW_HR_IF(E_INVALIDARG, input.empty()); + + size_t position = input.find('='); + THROW_HR_IF(E_INVALIDARG, std::string_view::npos == position); + + position += 1; + size_t length = input.size() - position; + if (length > 0 && input.back() == ',') + { + length -= 1; + } + + MinimumVersion = Utility::UInt64Version{ std::string{ input.substr(position, length) } }; + } + } + + Manifest::PlatformEnum Platform; + std::optional MinimumVersion; + }; + + Utility::Architecture ConvertFromSfsArchitecture(SFS::Architecture sfsArchitecture) + { + switch (sfsArchitecture) + { + case SFS::Architecture::Amd64: + return Utility::Architecture::X64; + case SFS::Architecture::x86: + return Utility::Architecture::X86; + case SFS::Architecture::Arm64: + return Utility::Architecture::Arm64; + case SFS::Architecture::Arm: + return Utility::Architecture::Arm; + case SFS::Architecture::None: + return Utility::Architecture::Neutral; + } + + return Utility::Architecture::Unknown; + } + + std::vector GetSfsPackageFileSupportedPlatforms( + const SFS::AppFile& appFile, + Manifest::PlatformEnum requiredPlatform, + const std::optional& targetOSVersion) + { + std::vector supportedPlatforms; + + for (auto const& applicability : appFile.GetApplicabilityDetails().GetPlatformApplicabilityForPackage()) + { + AICLI_LOG(Core, Verbose, << " examining platform [" << applicability << "] for applicability..."); + PlatformApplicability platform(applicability, targetOSVersion.has_value()); + + if (platform.Platform == Manifest::PlatformEnum::Unknown) + { + AICLI_LOG(Core, Verbose, << " not applicable due to unknown platform"); + continue; + } + + if (platform.MinimumVersion && targetOSVersion) + { + if (targetOSVersion.value() < platform.MinimumVersion.value()) + { + AICLI_LOG(Core, Verbose, << " not applicable due to OS version; target [" + << targetOSVersion.value().ToString() << "] is lower than minimum [" + << platform.MinimumVersion.value().ToString() << "]"); + continue; + } + } + + if (platform.Platform == requiredPlatform || requiredPlatform == Manifest::PlatformEnum::Unknown) + { + AICLI_LOG(Core, Verbose, << " applicable"); + supportedPlatforms.emplace_back(platform.Platform); + } + else + { + AICLI_LOG(Core, Verbose, << " not applicable due to platform requirement"); + } + } + + return supportedPlatforms; + } + + std::vector GetSfsPackageFileSupportedArchitectures(const SFS::AppFile& appFile, Utility::Architecture requiredArchitecture) + { + std::vector supportedArchitectures; + + for (auto const& sfsArchitecture : appFile.GetApplicabilityDetails().GetArchitectures()) + { + auto convertedArchitecture = ConvertFromSfsArchitecture(sfsArchitecture); + if (convertedArchitecture == Utility::Architecture::Unknown) + { + continue; + } + + if (requiredArchitecture == Utility::Architecture::Unknown || // No required architecture + convertedArchitecture == requiredArchitecture) + { + supportedArchitectures.emplace_back(convertedArchitecture); + } + } + + return supportedArchitectures; + } + + std::string GetSfsPackageFileExtension(const SFS::AppFile& appFile) + { + return std::filesystem::path{ appFile.GetFileId() }.extension().u8string(); + } + + bool IsFileExtensionSupported(std::string_view fileExtension) + { + for (auto const& supportedFileType : SupportedFileTypes) + { + if (Utility::CaseInsensitiveEquals(supportedFileType, fileExtension)) + { + return true; + } + } + + return false; + } + + // The file name will be {Name}_{Version}_{Platform list}_{Arch list}.{File Extension} + // If the file name is longer than 256, file moniker will be used. + std::string GetSfsPackageFileNameForDownload( + const std::string& packageName, + const Utility::UInt64Version& packageVersion, + const std::vector& supportedPlatforms, + const std::vector& supportedArchitectures, + const std::string& fileExtension, + const std::string& fileMoniker) + { + std::string platformString; + for (auto platform : supportedPlatforms) + { + platformString += std::string{ Manifest::PlatformToString(platform, true) } + '.'; + } + platformString.resize(platformString.size() - 1); + + std::string architectureString; + for (auto architecture : supportedArchitectures) + { + architectureString += std::string{ Utility::ToString(architecture) } + '.'; + } + architectureString.resize(architectureString.size() - 1); + + std::string fileName = + packageName + '_' + + packageVersion.ToString() + '_' + + platformString + '_' + + architectureString + + fileExtension; + + if (fileName.length() < 256) + { + return fileName; + } + else + { + return fileMoniker + fileExtension; + } + } + + void SfsClientLoggingCallback(const SFS::LogData& logData) + { + std::string message = "Message: " + std::string{ logData.message }; + message += " File: " + std::string{ logData.file }; + message += " Line: " + std::to_string(logData.line); + message += " Function: " + std::string{ logData.function }; + + switch (logData.severity) + { + case SFS::LogSeverity::Verbose: + AICLI_LOG(Core, Verbose, << message); + break; + case SFS::LogSeverity::Info: + AICLI_LOG(Core, Info, << message); + break; + case SFS::LogSeverity::Warning: + AICLI_LOG(Core, Warning, << message); + break; + case SFS::LogSeverity::Error: + AICLI_LOG(Core, Error, << message); + break; + } + } + + const std::unique_ptr& GetSfsClientInstance() + { + static std::unique_ptr s_sfsClient; + static std::once_flag s_sfsClientInitializeOnce; + + std::call_once(s_sfsClientInitializeOnce, + [&]() + { + SFS::ClientConfig config; + config.accountId = "storeapps"; + config.instanceId = "storeapps"; + config.logCallbackFn = SfsClientLoggingCallback; + + auto result = SFS::SFSClient::Make(config, s_sfsClient); + if (!result) + { + AICLI_LOG(Core, Error, << "Failed to initialize SfsClient. Error code: " << result.GetCode() << " Message: " << result.GetMsg()); + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED, "Failed to initialize SfsClient. ErrorCode: %lu Message: %hs", result.GetCode(), result.GetMsg().c_str()); + } + }); + + return s_sfsClient; + } + + std::vector PopulateSfsAppFileToMSStoreDownloadFileVector( + const std::vector& appFiles, + Utility::Architecture requiredArchitecture = Utility::Architecture::Unknown, + Manifest::PlatformEnum requiredPlatform = Manifest::PlatformEnum::Unknown, + const std::optional& targetOSVersion = std::nullopt) + { + using PlatformAndArchitectureKey = std::pair; + + // Since the server may return multiple versions of the same package, we'll use this map to record the one with latest version + // for each Platform|Architecture pair. + std::map downloadFilesMap; + + for (auto const& appFile : appFiles) + { + AICLI_LOG(Core, Info, << "Examining package [" << appFile.GetFileMoniker() << " (" << appFile.GetFileId() << ")] for download..."); + + // Filter out unsupported packages + auto supportedPlatforms = GetSfsPackageFileSupportedPlatforms(appFile, requiredPlatform, targetOSVersion); + if (supportedPlatforms.empty()) + { + AICLI_LOG(Core, Verbose, << " package has no applicable platform."); + continue; + } + auto supportedArchitectures = GetSfsPackageFileSupportedArchitectures(appFile, requiredArchitecture); + if (supportedArchitectures.empty()) + { + AICLI_LOG(Core, Verbose, << " package has no applicable architecture."); + continue; + } + std::string fileExtension = GetSfsPackageFileExtension(appFile); + if (!IsFileExtensionSupported(fileExtension)) + { + AICLI_LOG(Core, Verbose, << " package has unsupported file type [" << fileExtension << "]."); + continue; + } + + MSStoreDownloadFile downloadFile; + downloadFile.Url = appFile.GetUrl(); + // The sha256 hash was base64 encoded + downloadFile.Sha256 = JSON::Base64Decode(appFile.GetHashes().at(SFS::HashType::Sha256)); + auto packageInfo = Msix::GetPackageIdInfoFromFullName(appFile.GetFileMoniker()); + downloadFile.Version = packageInfo.Version; + downloadFile.FileName = GetSfsPackageFileNameForDownload( + packageInfo.Name, packageInfo.Version, supportedPlatforms, + supportedArchitectures, fileExtension, appFile.GetFileMoniker()); + + // Update the platform architecture map with latest package if applicable + for (auto supportedPlatform : supportedPlatforms) + { + for (auto supportedArchitecture : supportedArchitectures) + { + PlatformAndArchitectureKey downloadFileKey{ supportedPlatform, supportedArchitecture }; + if (downloadFile.Version > downloadFilesMap[downloadFileKey].Version) + { + downloadFilesMap[downloadFileKey] = downloadFile; + } + } + } + } + + // Generate MSStoreDownloadFile vector and remove duplication. + std::vector result; + for (auto& downloadFileEntry : downloadFilesMap) + { + if (std::find_if(result.begin(), result.end(), + [&](const MSStoreDownloadFile& downloadFile) + { + return Utility::CaseInsensitiveEquals(downloadFile.FileName, downloadFileEntry.second.FileName); + }) == result.end()) + { + result.emplace_back(std::move(downloadFileEntry.second)); + } + } + + return result; + } + + MSStoreDownloadInfo CallSfsClientAndGetMSStoreDownloadInfo( + std::string_view wuCategoryId, + Utility::Architecture requiredArchitecture, + Manifest::PlatformEnum requiredPlatform, + const std::optional& targetOSVersion) + { + AICLI_LOG(Core, Info, << "CallSfsClientAndGetMSStoreDownloadInfo with WuCategoryId: " << wuCategoryId + << " Architecture: " << Utility::ToString(requiredArchitecture) << " Platform: " << Manifest::PlatformToString(requiredPlatform) + << " Target OS Version: " << (targetOSVersion ? targetOSVersion.value().ToString() : "any")); + + std::vector appContents; + +#ifndef AICLI_DISABLE_TEST_HOOKS + if (TestHooks::s_SfsClient_AppContents_Override) + { + appContents = (*TestHooks::s_SfsClient_AppContents_Override)(wuCategoryId); + } + else +#endif + { + 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) + { + if (requestResult.GetCode() == SFS::Result::Code::HttpNotFound) + { + AICLI_LOG(Core, Error, << "Failed to call SfsClient GetLatestAppDownloadInfo. Package not found."); + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED, "Failed to call SfsClient GetLatestAppDownloadInfo. Package download not supported."); + } + else + { + AICLI_LOG(Core, Error, << "Failed to call SfsClient GetLatestAppDownloadInfo. Error code: " << requestResult.GetCode() << " Message: " << requestResult.GetMsg()); + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED, "Failed to call SfsClient GetLatestAppDownloadInfo. ErrorCode: %lu Message: %hs", requestResult.GetCode(), requestResult.GetMsg().c_str()); + } + } + } + + THROW_HR_IF(E_UNEXPECTED, appContents.empty()); + + MSStoreDownloadInfo result; + // Currently for app downloads, the result vector is always size 1. + const auto& appContent = appContents.at(0); + + // Populate main packages + result.MainPackages = PopulateSfsAppFileToMSStoreDownloadFileVector(appContent.GetFiles(), requiredArchitecture, requiredPlatform, targetOSVersion); + + // Populate dependency packages + for (auto const& dependencyEntry : appContent.GetPrerequisites()) + { + // Not passing in required platform for dependencies. Dependencies are mostly Windows.Universal. + auto dependencyPackages = PopulateSfsAppFileToMSStoreDownloadFileVector(dependencyEntry.GetFiles(), requiredArchitecture, Manifest::PlatformEnum::Unknown, targetOSVersion); + std::move(dependencyPackages.begin(), dependencyPackages.end(), std::inserter(result.DependencyPackages, result.DependencyPackages.end())); + } + + if (result.MainPackages.empty()) + { + AICLI_LOG(Core, Error, << "No applicable SFS main package."); + THROW_HR(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE); + } + + return result; + } + } +#endif + + namespace LicensingDetails + { + // Json response fields + constexpr std::string_view License = "license"sv; + constexpr std::string_view Keys = "keys"sv; + constexpr std::string_view Value = "value"sv; + + // Licensing rest endpoint + constexpr std::string_view LicensingRestEndpoint = "https://licensing.md.mp.microsoft.com/v9.0/licenses/offlineContent"; + constexpr std::string_view ContentId = "contentId"sv; + constexpr std::string_view From = "From"sv; + + // Response: + // { + // "license": { + // "keys": [ // returned as array for future, for now only 1 key + // { + // "value": "base64 encoded string" + // } + // ] + // } + // } + std::vector GetLicensing(std::string_view contentId, const Http::HttpClientHelper::HttpRequestHeaders& authHeaders) + { + AICLI_LOG(Core, Error, << "GetLicensing with ContentId: " << contentId); + + AppInstaller::Http::HttpClientHelper httpClientHelper; + +#ifndef AICLI_DISABLE_TEST_HOOKS + if (TestHooks::s_Licensing_HttpPipelineStage_Override) + { + httpClientHelper = AppInstaller::Http::HttpClientHelper{ TestHooks::s_Licensing_HttpPipelineStage_Override }; + } +#endif + + web::json::value requestBody; + requestBody[JSON::GetUtilityString(ContentId)] = web::json::value::string(JSON::GetUtilityString(contentId)); + Http::HttpClientHelper::HttpRequestHeaders requestHeaders; + requestHeaders.insert_or_assign(JSON::GetUtilityString(From), L"winget-cli"); + + std::optional licensingResponseObject = std::nullopt; + try + { + licensingResponseObject = httpClientHelper.HandlePost( + JSON::GetUtilityString(LicensingRestEndpoint), requestBody, requestHeaders, authHeaders); + } + catch (const wil::ResultException& re) + { + if (re.GetErrorCode() == HTTP_E_STATUS_FORBIDDEN) + { + AICLI_LOG(CLI, Error, << "Getting MSStore package license failed. The Microsoft Entra Id account does not have privilege."); + THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN); + } + else + { + AICLI_LOG(CLI, Error, << "Getting MSStore package license failed. Error code: " << re.GetErrorCode()); + THROW_HR(re.GetErrorCode()); + } + } + + if (!licensingResponseObject || licensingResponseObject->is_null()) + { + AICLI_LOG(Core, Error, << "Empty licensing response"); + THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); + } + + std::optional> license = JSON::GetJsonValueFromNode(licensingResponseObject.value(), JSON::GetUtilityString(License)); + if (!license) + { + AICLI_LOG(Core, Error, << "Missing license node"); + THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); + } + + auto keys = JSON::GetRawJsonArrayFromJsonNode(license.value().get(), JSON::GetUtilityString(Keys)); + if (!keys || keys->get().size() == 0) + { + AICLI_LOG(Core, Error, << "Missing keys or empty keys"); + THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); + } + + std::string base64LicenseContent = JSON::GetRawStringValueFromJsonNode(keys->get().at(0), JSON::GetUtilityString(Value)).value_or(""); + if (base64LicenseContent.empty()) + { + AICLI_LOG(Core, Error, << "Missing license content"); + THROW_HR(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED); + } + + return JSON::Base64Decode(base64LicenseContent); + } + } + + namespace + { + Http::HttpClientHelper::HttpRequestHeaders GetAuthHeaders(std::unique_ptr& authenticator) + { + if (!authenticator) + { + return {}; + } + + Http::HttpClientHelper::HttpRequestHeaders result; + + auto authResult = authenticator->AuthenticateForToken(); + if (FAILED(authResult.Status)) + { + AICLI_LOG(Repo, Error, << "Authentication failed. Result: " << authResult.Status); + THROW_HR_MSG(authResult.Status, "Failed to authenticate for MicrosoftEntraId"); + } + result.insert_or_assign(web::http::header_names::authorization, JSON::GetUtilityString(Authentication::CreateBearerToken(authResult.Token))); + + return result; + } + } + + MSStoreDownloadContext::MSStoreDownloadContext( + std::string productId, + AppInstaller::Utility::Architecture architecture, + Manifest::PlatformEnum platform, + std::string locale, + AppInstaller::Authentication::AuthenticationArguments authArgs) : + m_productId(std::move(productId)), m_architecture(architecture), m_platform(platform), m_locale(std::move(locale)) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (!TestHooks::s_DisplayCatalog_HttpPipelineStage_Override) +#endif + { + Authentication::MicrosoftEntraIdAuthenticationInfo displayCatalogMicrosoftEntraIdAuthInfo; + displayCatalogMicrosoftEntraIdAuthInfo.Resource = "https://bigcatalog.commerce.microsoft.com"; + Authentication::AuthenticationInfo displayCatalogAuthInfo; + displayCatalogAuthInfo.Type = Authentication::AuthenticationType::MicrosoftEntraId; + displayCatalogAuthInfo.MicrosoftEntraIdInfo = std::move(displayCatalogMicrosoftEntraIdAuthInfo); + + m_displayCatalogAuthenticator = std::make_unique(std::move(displayCatalogAuthInfo), authArgs); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + if (!TestHooks::s_Licensing_HttpPipelineStage_Override) +#endif + { + Authentication::MicrosoftEntraIdAuthenticationInfo licensingMicrosoftEntraIdAuthInfo; + licensingMicrosoftEntraIdAuthInfo.Resource = "c5e1cb0d-5d24-4b1a-b291-ec684152b2ba"; + Authentication::AuthenticationInfo licensingAuthInfo; + licensingAuthInfo.Type = Authentication::AuthenticationType::MicrosoftEntraId; + licensingAuthInfo.MicrosoftEntraIdInfo = std::move(licensingMicrosoftEntraIdAuthInfo); + + m_licensingAuthenticator = std::make_unique(std::move(licensingAuthInfo), authArgs); + } + } + + void MSStoreDownloadContext::TargetOSVersion(std::optional targetOSVersion) + { + m_targetOSVersion = std::move(targetOSVersion); + } + + MSStoreDownloadInfo MSStoreDownloadContext::GetDownloadInfo() + { +#ifndef WINGET_DISABLE_FOR_FUZZING + auto displayCatalogPackage = DisplayCatalogDetails::CallDisplayCatalogAndGetPreferredPackage(m_productId, m_locale, m_architecture, GetAuthHeaders(m_displayCatalogAuthenticator)); + auto downloadInfo = SfsClientDetails::CallSfsClientAndGetMSStoreDownloadInfo(displayCatalogPackage.WuCategoryId, m_architecture, m_platform, m_targetOSVersion); + downloadInfo.ContentId = displayCatalogPackage.ContentId; + return downloadInfo; +#else + return {}; +#endif + } + + std::vector MSStoreDownloadContext::GetLicense(std::string_view contentId) + { + return LicensingDetails::GetLicensing(contentId, GetAuthHeaders(m_licensingAuthenticator)); + } +} diff --git a/src/AppInstallerCommonCore/Manifest/Manifest.cpp b/src/AppInstallerCommonCore/Manifest/Manifest.cpp index cd4401c086..32612b1147 100644 --- a/src/AppInstallerCommonCore/Manifest/Manifest.cpp +++ b/src/AppInstallerCommonCore/Manifest/Manifest.cpp @@ -1,243 +1,243 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Manifest.h" -#include "winget/Locale.h" -#include "winget/UserSettings.h" - -namespace AppInstaller::Manifest -{ - namespace - { - void AddFoldedStringToSetIfNotEmpty(std::set& set, const string_t& value) - { - if (!value.empty()) - { - set.emplace(Utility::FoldCase(value)); - } - } - } - - void Manifest::ApplyLocale(const std::string& locale) - { - CurrentLocalization = DefaultLocalization; - - // Get target locale from Preferred Languages settings if applicable - std::vector targetLocales; - if (locale.empty()) - { - targetLocales = Locale::GetUserPreferredLanguages(); - } - else - { - targetLocales.emplace_back(locale); - } - - for (auto const& targetLocale : targetLocales) - { - const ManifestLocalization* bestLocalization = nullptr; - double bestScore = Locale::GetDistanceOfLanguage(targetLocale, DefaultLocalization.Locale); - - for (auto const& localization : Localizations) - { - double score = Locale::GetDistanceOfLanguage(targetLocale, localization.Locale); - if (score > bestScore) - { - bestLocalization = &localization; - bestScore = score; - } - } - - // If there's better locale than default And is compatible with target locale, merge and return; - if (bestScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) - { - if (bestLocalization != nullptr) - { - CurrentLocalization.ReplaceOrMergeWith(*bestLocalization); - } - break; - } - } - } - - std::vector Manifest::GetAggregatedTags() const - { - std::vector resultTags = DefaultLocalization.Get(); - - for (const auto& locale : Localizations) - { - auto tags = locale.Get(); - for (const auto& tag : tags) - { - if (std::find(resultTags.begin(), resultTags.end(), tag) == resultTags.end()) - { - resultTags.emplace_back(tag); - } - } - } - - return resultTags; - } - - std::vector Manifest::GetAggregatedCommands() const - { - std::vector resultCommands; - - for (const auto& installer : Installers) - { - for (const auto& command : installer.Commands) - { - if (std::find(resultCommands.begin(), resultCommands.end(), command) == resultCommands.end()) - { - resultCommands.emplace_back(command); - } - } - } - - return resultCommands; - } - - Utility::VersionRange Manifest::GetArpVersionRange() const - { - bool arpVersionFound = false; - Utility::Version minVersion; - Utility::Version maxVersion; - - for (auto const& installer : Installers) - { - if (DoesInstallerTypeSupportArpVersionRange(installer.EffectiveInstallerType())) - { - for (auto const& entry : installer.AppsAndFeaturesEntries) - { - if (!entry.DisplayVersion.empty()) - { - Utility::Version arpVersion{ entry.DisplayVersion }; - - if (!arpVersionFound) - { - // This is the first arp version found, populate both min and max version - minVersion = arpVersion; - maxVersion = arpVersion; - arpVersionFound = true; - continue; - } - - if (arpVersion < minVersion) - { - minVersion = arpVersion; - } - else if (arpVersion > maxVersion) - { - maxVersion = arpVersion; - } - } - } - } - } - - return arpVersionFound ? Utility::VersionRange{ minVersion, maxVersion } : Utility::VersionRange{}; - } - - std::vector Manifest::GetPackageFamilyNames() const - { - return GetSystemReferenceStrings( - [](const ManifestInstaller& i) -> const Utility::NormalizedString& { return i.PackageFamilyName; }); - } - - std::vector Manifest::GetProductCodes() const - { - return GetSystemReferenceStrings( - [](const ManifestInstaller& i) -> const Utility::NormalizedString& { return i.ProductCode; }, - [](const AppsAndFeaturesEntry& e) -> const Utility::NormalizedString& { return e.ProductCode; }); - } - - std::vector Manifest::GetUpgradeCodes() const - { - return GetSystemReferenceStrings( - {}, - [](const AppsAndFeaturesEntry& e) -> const Utility::NormalizedString& { return e.UpgradeCode; }); - } - - std::vector Manifest::GetPackageNames() const - { - std::set set; - - AddFoldedStringToSetIfNotEmpty(set, DefaultLocalization.Get()); - for (const auto& loc : Localizations) - { - AddFoldedStringToSetIfNotEmpty(set, loc.Get()); - } - - // In addition to the names used for our display, add the display names from the ARP entries - for (const auto& installer : Installers) - { - for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) - { - AddFoldedStringToSetIfNotEmpty(set, appsAndFeaturesEntry.DisplayName); - } - } - - std::vector result( - std::make_move_iterator(set.begin()), - std::make_move_iterator(set.end())); - - return result; - } - - std::vector Manifest::GetPublishers() const - { - std::set set; - - AddFoldedStringToSetIfNotEmpty(set, DefaultLocalization.Get()); - for (const auto& loc : Localizations) - { - AddFoldedStringToSetIfNotEmpty(set, loc.Get()); - } - - // In addition to the publishers used for our display, add the publisher from the ARP entries - for (const auto& installer : Installers) - { - for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) - { - AddFoldedStringToSetIfNotEmpty(set, appsAndFeaturesEntry.Publisher); - } - } - - std::vector result( - std::make_move_iterator(set.begin()), - std::make_move_iterator(set.end())); - - return result; - } - - std::vector Manifest::GetSystemReferenceStrings( - std::function extractStringFromInstaller, - std::function extractStringFromAppsAndFeaturesEntry) const - { - std::set set; - - for (const auto& installer : Installers) - { - if (extractStringFromInstaller) - { - const auto& installerString = extractStringFromInstaller(installer); - AddFoldedStringToSetIfNotEmpty(set, installerString); - } - - if (extractStringFromAppsAndFeaturesEntry) - { - for (const auto& entry : installer.AppsAndFeaturesEntries) - { - const auto& entryString = extractStringFromAppsAndFeaturesEntry(entry); - AddFoldedStringToSetIfNotEmpty(set, entryString); - } - } - } - - std::vector result( - std::make_move_iterator(set.begin()), - std::make_move_iterator(set.end())); - - return result; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Manifest.h" +#include "winget/Locale.h" +#include "winget/UserSettings.h" + +namespace AppInstaller::Manifest +{ + namespace + { + void AddFoldedStringToSetIfNotEmpty(std::set& set, const string_t& value) + { + if (!value.empty()) + { + set.emplace(Utility::FoldCase(value)); + } + } + } + + void Manifest::ApplyLocale(const std::string& locale) + { + CurrentLocalization = DefaultLocalization; + + // Get target locale from Preferred Languages settings if applicable + std::vector targetLocales; + if (locale.empty()) + { + targetLocales = Locale::GetUserPreferredLanguages(); + } + else + { + targetLocales.emplace_back(locale); + } + + for (auto const& targetLocale : targetLocales) + { + const ManifestLocalization* bestLocalization = nullptr; + double bestScore = Locale::GetDistanceOfLanguage(targetLocale, DefaultLocalization.Locale); + + for (auto const& localization : Localizations) + { + double score = Locale::GetDistanceOfLanguage(targetLocale, localization.Locale); + if (score > bestScore) + { + bestLocalization = &localization; + bestScore = score; + } + } + + // If there's better locale than default And is compatible with target locale, merge and return; + if (bestScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) + { + if (bestLocalization != nullptr) + { + CurrentLocalization.ReplaceOrMergeWith(*bestLocalization); + } + break; + } + } + } + + std::vector Manifest::GetAggregatedTags() const + { + std::vector resultTags = DefaultLocalization.Get(); + + for (const auto& locale : Localizations) + { + auto tags = locale.Get(); + for (const auto& tag : tags) + { + if (std::find(resultTags.begin(), resultTags.end(), tag) == resultTags.end()) + { + resultTags.emplace_back(tag); + } + } + } + + return resultTags; + } + + std::vector Manifest::GetAggregatedCommands() const + { + std::vector resultCommands; + + for (const auto& installer : Installers) + { + for (const auto& command : installer.Commands) + { + if (std::find(resultCommands.begin(), resultCommands.end(), command) == resultCommands.end()) + { + resultCommands.emplace_back(command); + } + } + } + + return resultCommands; + } + + Utility::VersionRange Manifest::GetArpVersionRange() const + { + bool arpVersionFound = false; + Utility::Version minVersion; + Utility::Version maxVersion; + + for (auto const& installer : Installers) + { + if (DoesInstallerTypeSupportArpVersionRange(installer.EffectiveInstallerType())) + { + for (auto const& entry : installer.AppsAndFeaturesEntries) + { + if (!entry.DisplayVersion.empty()) + { + Utility::Version arpVersion{ entry.DisplayVersion }; + + if (!arpVersionFound) + { + // This is the first arp version found, populate both min and max version + minVersion = arpVersion; + maxVersion = arpVersion; + arpVersionFound = true; + continue; + } + + if (arpVersion < minVersion) + { + minVersion = arpVersion; + } + else if (arpVersion > maxVersion) + { + maxVersion = arpVersion; + } + } + } + } + } + + return arpVersionFound ? Utility::VersionRange{ minVersion, maxVersion } : Utility::VersionRange{}; + } + + std::vector Manifest::GetPackageFamilyNames() const + { + return GetSystemReferenceStrings( + [](const ManifestInstaller& i) -> const Utility::NormalizedString& { return i.PackageFamilyName; }); + } + + std::vector Manifest::GetProductCodes() const + { + return GetSystemReferenceStrings( + [](const ManifestInstaller& i) -> const Utility::NormalizedString& { return i.ProductCode; }, + [](const AppsAndFeaturesEntry& e) -> const Utility::NormalizedString& { return e.ProductCode; }); + } + + std::vector Manifest::GetUpgradeCodes() const + { + return GetSystemReferenceStrings( + {}, + [](const AppsAndFeaturesEntry& e) -> const Utility::NormalizedString& { return e.UpgradeCode; }); + } + + std::vector Manifest::GetPackageNames() const + { + std::set set; + + AddFoldedStringToSetIfNotEmpty(set, DefaultLocalization.Get()); + for (const auto& loc : Localizations) + { + AddFoldedStringToSetIfNotEmpty(set, loc.Get()); + } + + // In addition to the names used for our display, add the display names from the ARP entries + for (const auto& installer : Installers) + { + for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) + { + AddFoldedStringToSetIfNotEmpty(set, appsAndFeaturesEntry.DisplayName); + } + } + + std::vector result( + std::make_move_iterator(set.begin()), + std::make_move_iterator(set.end())); + + return result; + } + + std::vector Manifest::GetPublishers() const + { + std::set set; + + AddFoldedStringToSetIfNotEmpty(set, DefaultLocalization.Get()); + for (const auto& loc : Localizations) + { + AddFoldedStringToSetIfNotEmpty(set, loc.Get()); + } + + // In addition to the publishers used for our display, add the publisher from the ARP entries + for (const auto& installer : Installers) + { + for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) + { + AddFoldedStringToSetIfNotEmpty(set, appsAndFeaturesEntry.Publisher); + } + } + + std::vector result( + std::make_move_iterator(set.begin()), + std::make_move_iterator(set.end())); + + return result; + } + + std::vector Manifest::GetSystemReferenceStrings( + std::function extractStringFromInstaller, + std::function extractStringFromAppsAndFeaturesEntry) const + { + std::set set; + + for (const auto& installer : Installers) + { + if (extractStringFromInstaller) + { + const auto& installerString = extractStringFromInstaller(installer); + AddFoldedStringToSetIfNotEmpty(set, installerString); + } + + if (extractStringFromAppsAndFeaturesEntry) + { + for (const auto& entry : installer.AppsAndFeaturesEntries) + { + const auto& entryString = extractStringFromAppsAndFeaturesEntry(entry); + AddFoldedStringToSetIfNotEmpty(set, entryString); + } + } + } + + std::vector result( + std::make_move_iterator(set.begin()), + std::make_move_iterator(set.end())); + + return result; + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp index eca420ea00..18fb24e5fa 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestCommon.cpp @@ -1,1244 +1,1244 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/ManifestCommon.h" -#include "winget/ManifestValidation.h" - -namespace AppInstaller::Manifest -{ - namespace - { - enum class CompatibilitySet - { - None, - Exe, - Msi, - Msix, - }; - - CompatibilitySet GetCompatibilitySet(InstallerTypeEnum type) - { - switch (type) - { - case InstallerTypeEnum::Inno: - case InstallerTypeEnum::Nullsoft: - case InstallerTypeEnum::Exe: - case InstallerTypeEnum::Burn: - return CompatibilitySet::Exe; - case InstallerTypeEnum::Wix: - case InstallerTypeEnum::Msi: - return CompatibilitySet::Msi; - case InstallerTypeEnum::Msix: - case InstallerTypeEnum::MSStore: - return CompatibilitySet::Msix; - default: - return CompatibilitySet::None; - } - } - } - - ManifestVer::ManifestVer(std::string_view version) - { - bool validationSuccess = true; - - // Separate the extensions out - size_t hyphenPos = version.find_first_of('-'); - if (hyphenPos != std::string_view::npos) - { - // The first part is the main version - Assign(std::string{ version.substr(0, hyphenPos) }, "."); - - // The second part is the extensions - hyphenPos += 1; - while (hyphenPos < version.length()) - { - size_t newPos = version.find_first_of('-', hyphenPos); - - size_t length = (newPos == std::string::npos ? version.length() : newPos) - hyphenPos; - m_extensions.emplace_back(std::string{ version.substr(hyphenPos, length) }, "."); - - hyphenPos += length + 1; - } - } - else - { - Assign(std::string{ version }, "."); - } - - if (m_parts.size() > 3) - { - validationSuccess = false; - } - else - { - for (size_t i = 0; i < m_parts.size(); i++) - { - if (!m_parts[i].Other.empty()) - { - validationSuccess = false; - break; - } - } - - for (const RawVersion& ext : m_extensions) - { - if (ext.GetParts().empty() || ext.GetParts()[0].Integer != 0) - { - validationSuccess = false; - break; - } - } - } - - if (!validationSuccess) - { - std::vector errors; - errors.emplace_back(ManifestError::InvalidFieldValue, "ManifestVersion", std::string{ version }); - THROW_EXCEPTION(ManifestException(std::move(errors))); - } - } - - bool ManifestVer::HasExtension() const - { - return !m_extensions.empty(); - } - - bool ManifestVer::HasExtension(std::string_view extension) const - { - for (const RawVersion& ext : m_extensions) - { - const auto& parts = ext.GetParts(); - if (!parts.empty() && parts[0].Integer == 0 && parts[0].Other == extension) - { - return true; - } - } - - return false; - } - - InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in) - { - std::string inStrLower = Utility::ToLower(in); - InstallerTypeEnum result = InstallerTypeEnum::Unknown; - - if (inStrLower == "inno") - { - result = InstallerTypeEnum::Inno; - } - else if (inStrLower == "wix") - { - result = InstallerTypeEnum::Wix; - } - else if (inStrLower == "msi") - { - result = InstallerTypeEnum::Msi; - } - else if (inStrLower == "nullsoft") - { - result = InstallerTypeEnum::Nullsoft; - } - else if (inStrLower == "zip") - { - result = InstallerTypeEnum::Zip; - } - else if (inStrLower == "appx" || inStrLower == "msix") - { - result = InstallerTypeEnum::Msix; - } - else if (inStrLower == "exe") - { - result = InstallerTypeEnum::Exe; - } - else if (inStrLower == "burn") - { - result = InstallerTypeEnum::Burn; - } - else if (inStrLower == "msstore") - { - result = InstallerTypeEnum::MSStore; - } - else if (inStrLower == "portable") - { - result = InstallerTypeEnum::Portable; - } - else if (inStrLower == "font") - { - result = InstallerTypeEnum::Font; - } - - return result; - } - - UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in) - { - UpdateBehaviorEnum result = UpdateBehaviorEnum::Unknown; - - if (Utility::CaseInsensitiveEquals(in, "install")) - { - result = UpdateBehaviorEnum::Install; - } - else if (Utility::CaseInsensitiveEquals(in, "uninstallprevious")) - { - result = UpdateBehaviorEnum::UninstallPrevious; - } - else if (Utility::CaseInsensitiveEquals(in, "deny")) - { - result = UpdateBehaviorEnum::Deny; - } - - return result; - } - - ScopeEnum ConvertToScopeEnum(std::string_view in) - { - ScopeEnum result = ScopeEnum::Unknown; - - if (Utility::CaseInsensitiveEquals(in, "user")) - { - result = ScopeEnum::User; - } - else if (Utility::CaseInsensitiveEquals(in, "machine")) - { - result = ScopeEnum::Machine; - } - - return result; - } - - InstallModeEnum ConvertToInstallModeEnum(const std::string& in) - { - InstallModeEnum result = InstallModeEnum::Unknown; - - if (Utility::CaseInsensitiveEquals(in, "interactive")) - { - result = InstallModeEnum::Interactive; - } - else if (Utility::CaseInsensitiveEquals(in, "silent")) - { - result = InstallModeEnum::Silent; - } - else if (Utility::CaseInsensitiveEquals(in, "silentWithProgress")) - { - result = InstallModeEnum::SilentWithProgress; - } - - return result; - } - - PlatformEnum ConvertToPlatformEnum(std::string_view in) - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "windows.desktop") - { - return PlatformEnum::Desktop; - } - else if (inStrLower == "windows.universal") - { - return PlatformEnum::Universal; - } - - return PlatformEnum::Unknown; - } - - PlatformEnum ConvertToPlatformEnumForMSStoreDownload(std::string_view in) - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "windows.desktop") - { - return PlatformEnum::Desktop; - } - else if (inStrLower == "windows.universal") - { - return PlatformEnum::Universal; - } - else if (inStrLower == "windows.iot") - { - return PlatformEnum::IoT; - } - else if (inStrLower == "windows.team") - { - return PlatformEnum::Team; - } - else if (inStrLower == "windows.holographic") - { - return PlatformEnum::Holographic; - } - - return PlatformEnum::Unknown; - } - - ElevationRequirementEnum ConvertToElevationRequirementEnum(const std::string& in) - { - ElevationRequirementEnum result = ElevationRequirementEnum::Unknown; - - if (Utility::CaseInsensitiveEquals(in, "elevationRequired")) - { - result = ElevationRequirementEnum::ElevationRequired; - } - else if (Utility::CaseInsensitiveEquals(in, "elevationProhibited")) - { - result = ElevationRequirementEnum::ElevationProhibited; - } - else if (Utility::CaseInsensitiveEquals(in, "elevatesSelf")) - { - result = ElevationRequirementEnum::ElevatesSelf; - } - - return result; - } - - UnsupportedArgumentEnum ConvertToUnsupportedArgumentEnum(const std::string& in) - { - UnsupportedArgumentEnum result = UnsupportedArgumentEnum::Unknown; - - if (Utility::CaseInsensitiveEquals(in, "log")) - { - result = UnsupportedArgumentEnum::Log; - } - else if (Utility::CaseInsensitiveEquals(in, "location")) - { - result = UnsupportedArgumentEnum::Location; - } - - return result; - } - - ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in) - { - if (in == "singleton") - { - return ManifestTypeEnum::Singleton; - } - else if (in == "version") - { - return ManifestTypeEnum::Version; - } - else if (in == "installer") - { - return ManifestTypeEnum::Installer; - } - else if (in == "defaultLocale") - { - return ManifestTypeEnum::DefaultLocale; - } - else if (in == "locale") - { - return ManifestTypeEnum::Locale; - } - else if (in == "merged") - { - return ManifestTypeEnum::Merged; - } - else if (in == "shadow") - { - return ManifestTypeEnum::Shadow; - } - else - { - THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Unsupported ManifestType: %hs", in.c_str()); - } - } - - ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(const std::string& in) - { - std::string inStrLower = Utility::ToLower(in); - ExpectedReturnCodeEnum result = ExpectedReturnCodeEnum::Unknown; - - if (inStrLower == "packageinuse") - { - result = ExpectedReturnCodeEnum::PackageInUse; - } - else if (inStrLower == "packageinusebyapplication") - { - result = ExpectedReturnCodeEnum::PackageInUseByApplication; - } - else if (inStrLower == "installinprogress") - { - result = ExpectedReturnCodeEnum::InstallInProgress; - } - else if (inStrLower == "fileinuse") - { - result = ExpectedReturnCodeEnum::FileInUse; - } - else if (inStrLower == "missingdependency") - { - result = ExpectedReturnCodeEnum::MissingDependency; - } - else if (inStrLower == "diskfull") - { - result = ExpectedReturnCodeEnum::DiskFull; - } - else if (inStrLower == "insufficientmemory") - { - result = ExpectedReturnCodeEnum::InsufficientMemory; - } - else if (inStrLower == "invalidparameter") - { - result = ExpectedReturnCodeEnum::InvalidParameter; - } - else if (inStrLower == "nonetwork") - { - result = ExpectedReturnCodeEnum::NoNetwork; - } - else if (inStrLower == "contactsupport") - { - result = ExpectedReturnCodeEnum::ContactSupport; - } - else if (inStrLower == "rebootrequiredtofinish") - { - result = ExpectedReturnCodeEnum::RebootRequiredToFinish; - } - else if (inStrLower == "rebootrequiredforinstall") - { - result = ExpectedReturnCodeEnum::RebootRequiredForInstall; - } - else if (inStrLower == "rebootinitiated") - { - result = ExpectedReturnCodeEnum::RebootInitiated; - } - else if (inStrLower == "cancelledbyuser") - { - result = ExpectedReturnCodeEnum::CancelledByUser; - } - else if (inStrLower == "alreadyinstalled") - { - result = ExpectedReturnCodeEnum::AlreadyInstalled; - } - else if (inStrLower == "downgrade") - { - result = ExpectedReturnCodeEnum::Downgrade; - } - else if (inStrLower == "blockedbypolicy") - { - result = ExpectedReturnCodeEnum::BlockedByPolicy; - } - else if (inStrLower == "systemnotsupported") - { - result = ExpectedReturnCodeEnum::SystemNotSupported; - } - else if (inStrLower == "custom") - { - result = ExpectedReturnCodeEnum::Custom; - } - - return result; - } - - InstalledFileTypeEnum ConvertToInstalledFileTypeEnum(const std::string& in) - { - std::string inStrLower = Utility::ToLower(in); - InstalledFileTypeEnum result = InstalledFileTypeEnum::Unknown; - - if (inStrLower == "launch") - { - result = InstalledFileTypeEnum::Launch; - } - else if (inStrLower == "uninstall") - { - result = InstalledFileTypeEnum::Uninstall; - } - else if (inStrLower == "other") - { - result = InstalledFileTypeEnum::Other; - } - - return result; - } - - IconFileTypeEnum ConvertToIconFileTypeEnum(std::string_view in) - { - std::string inStrLower = Utility::ToLower(in); - IconFileTypeEnum result = IconFileTypeEnum::Unknown; - - if (inStrLower == "jpeg") - { - result = IconFileTypeEnum::Jpeg; - } - else if (inStrLower == "png") - { - result = IconFileTypeEnum::Png; - } - else if (inStrLower == "ico") - { - result = IconFileTypeEnum::Ico; - } - - return result; - } - - IconThemeEnum ConvertToIconThemeEnum(std::string_view in) - { - std::string inStrLower = Utility::ToLower(in); - IconThemeEnum result = IconThemeEnum::Unknown; - - if (inStrLower == "default") - { - result = IconThemeEnum::Default; - } - else if (inStrLower == "dark") - { - result = IconThemeEnum::Dark; - } - else if (inStrLower == "light") - { - result = IconThemeEnum::Light; - } - else if (inStrLower == "highcontrast") - { - result = IconThemeEnum::HighContrast; - } - - return result; - } - - IconResolutionEnum ConvertToIconResolutionEnum(std::string_view in) - { - std::string inStrLower = Utility::ToLower(in); - IconResolutionEnum result = IconResolutionEnum::Unknown; - - if (inStrLower == "custom") - { - result = IconResolutionEnum::Custom; - } - else if (inStrLower == "16x16") - { - result = IconResolutionEnum::Square16; - } - else if (inStrLower == "20x20") - { - result = IconResolutionEnum::Square20; - } - else if (inStrLower == "24x24") - { - result = IconResolutionEnum::Square24; - } - else if (inStrLower == "30x30") - { - result = IconResolutionEnum::Square30; - } - else if (inStrLower == "32x32") - { - result = IconResolutionEnum::Square32; - } - else if (inStrLower == "36x36") - { - result = IconResolutionEnum::Square36; - } - else if (inStrLower == "40x40") - { - result = IconResolutionEnum::Square40; - } - else if (inStrLower == "48x48") - { - result = IconResolutionEnum::Square48; - } - else if (inStrLower == "60x60") - { - result = IconResolutionEnum::Square60; - } - else if (inStrLower == "64x64") - { - result = IconResolutionEnum::Square64; - } - else if (inStrLower == "72x72") - { - result = IconResolutionEnum::Square72; - } - else if (inStrLower == "80x80") - { - result = IconResolutionEnum::Square80; - } - else if (inStrLower == "96x96") - { - result = IconResolutionEnum::Square96; - } - else if (inStrLower == "256x256") - { - result = IconResolutionEnum::Square256; - } - - return result; - } - - std::string_view InstallerTypeToString(InstallerTypeEnum installerType) - { - switch (installerType) - { - case InstallerTypeEnum::Exe: - return "exe"sv; - case InstallerTypeEnum::Inno: - return "inno"sv; - case InstallerTypeEnum::Msi: - return "msi"sv; - case InstallerTypeEnum::Msix: - return "msix"sv; - case InstallerTypeEnum::Nullsoft: - return "nullsoft"sv; - case InstallerTypeEnum::Wix: - return "wix"sv; - case InstallerTypeEnum::Zip: - return "zip"sv; - case InstallerTypeEnum::Burn: - return "burn"sv; - case InstallerTypeEnum::MSStore: - return "msstore"sv; - case InstallerTypeEnum::Portable: - return "portable"sv; - case InstallerTypeEnum::Font: - return "font"sv; - } - - return "unknown"sv; - } - - std::string_view InstallerSwitchTypeToString(InstallerSwitchType installerSwitchType) - { - switch (installerSwitchType) - { - case InstallerSwitchType::Custom: - return "Custom"sv; - case InstallerSwitchType::Silent: - return "Silent"sv; - case InstallerSwitchType::SilentWithProgress: - return "SilentWithProgress"sv; - case InstallerSwitchType::Interactive: - return "Interactive"sv; - case InstallerSwitchType::Language: - return "Language"sv; - case InstallerSwitchType::Log: - return "Log"sv; - case InstallerSwitchType::InstallLocation: - return "InstallLocation"sv; - case InstallerSwitchType::Update: - return "Upgrade"sv; - case InstallerSwitchType::Repair: - return "Repair"sv; - } - - return "Unknown"sv; - } - - std::string_view ElevationRequirementToString(ElevationRequirementEnum elevationRequirement) - { - switch (elevationRequirement) - { - case ElevationRequirementEnum::ElevationRequired: - return "elevationRequired"sv; - case ElevationRequirementEnum::ElevationProhibited: - return "elevationProhibited"sv; - case ElevationRequirementEnum::ElevatesSelf: - return "elevatesSelf"sv; - } - - return "unknown"sv; - } - - std::string_view UnsupportedArgumentToString(UnsupportedArgumentEnum unsupportedArgument) - { - switch (unsupportedArgument) - { - case UnsupportedArgumentEnum::Log: - return "log"sv; - case UnsupportedArgumentEnum::Location: - return "location"sv; - } - - return "unknown"sv; - } - - std::string_view InstallModeToString(InstallModeEnum installMode) - { - switch (installMode) - { - case InstallModeEnum::Interactive: - return "interactive"sv; - case InstallModeEnum::Silent: - return "silent"sv; - case InstallModeEnum::SilentWithProgress: - return "silentWithProgress"sv; - } - - return "unknown"sv; - } - - std::string_view PlatformToString(PlatformEnum platform, bool shortString) - { - switch (platform) - { - case PlatformEnum::Desktop: - return shortString ? "Desktop" : "Windows.Desktop"sv; - case PlatformEnum::Universal: - return shortString ? "Universal" : "Windows.Universal"sv; - case PlatformEnum::IoT: - return shortString ? "IoT" : "Windows.IoT"sv; - case PlatformEnum::Holographic: - return shortString ? "Holographic" : "Windows.Holographic"sv; - case PlatformEnum::Team: - return shortString ? "Team" : "Windows.Team"sv; - } - - return "Unknown"sv; - } - - std::string_view UpdateBehaviorToString(UpdateBehaviorEnum updateBehavior) - { - switch (updateBehavior) - { - case UpdateBehaviorEnum::Install: - return "install"sv; - case UpdateBehaviorEnum::UninstallPrevious: - return "uninstallPrevious"sv; - case UpdateBehaviorEnum::Deny: - return "deny"sv; - } - - return "unknown"sv; - } - - std::string_view RepairBehaviorToString(RepairBehaviorEnum repairBehavior) - { - switch (repairBehavior) - { - case AppInstaller::Manifest::RepairBehaviorEnum::Modify: - return "modify"sv; - case AppInstaller::Manifest::RepairBehaviorEnum::Installer: - return "installer"sv; - case AppInstaller::Manifest::RepairBehaviorEnum::Uninstaller: - return "uninstaller"sv; - } - - return "unknown"sv; - } - - std::string_view ScopeToString(ScopeEnum scope) - { - switch (scope) - { - case ScopeEnum::User: - return "User"sv; - case ScopeEnum::Machine: - return "Machine"sv; - } - - return "Unknown"sv; - } - - std::string_view InstalledFileTypeToString(InstalledFileTypeEnum installedFileType) - { - switch (installedFileType) - { - case InstalledFileTypeEnum::Launch: - return "launch"sv; - case InstalledFileTypeEnum::Uninstall: - return "uninstall"sv; - case InstalledFileTypeEnum::Other: - return "other"sv; - } - - return "unknown"sv; - } - - std::string_view IconFileTypeToString(IconFileTypeEnum iconFileType) - { - switch (iconFileType) - { - case IconFileTypeEnum::Ico: - return "ico"sv; - case IconFileTypeEnum::Jpeg: - return "jpeg"sv; - case IconFileTypeEnum::Png: - return "png"sv; - } - - return "unknown"sv; - } - - std::string_view IconThemeToString(IconThemeEnum iconTheme) - { - switch (iconTheme) - { - case IconThemeEnum::Default: - return "default"sv; - case IconThemeEnum::Dark: - return "dark"sv; - case IconThemeEnum::Light: - return "light"sv; - case IconThemeEnum::HighContrast: - return "highContrast"sv; - } - - return "unknown"sv; - } - - std::string_view IconResolutionToString(IconResolutionEnum iconResolution) - { - switch (iconResolution) - { - case IconResolutionEnum::Custom: - return "custom"sv; - case IconResolutionEnum::Square16: - return "16x16"sv; - case IconResolutionEnum::Square20: - return "20x20"sv; - case IconResolutionEnum::Square24: - return "24x24"sv; - case IconResolutionEnum::Square30: - return "30x30"sv; - case IconResolutionEnum::Square32: - return "32x32"sv; - case IconResolutionEnum::Square36: - return "36x36"sv; - case IconResolutionEnum::Square40: - return "40x40"sv; - case IconResolutionEnum::Square48: - return "48x48"sv; - case IconResolutionEnum::Square60: - return "60x60"sv; - case IconResolutionEnum::Square64: - return "64x64"sv; - case IconResolutionEnum::Square72: - return "72x72"sv; - case IconResolutionEnum::Square80: - return "80x80"sv; - case IconResolutionEnum::Square96: - return "96x96"sv; - case IconResolutionEnum::Square256: - return "256x256"sv; - } - - return "unknown"sv; - } - - std::string_view ExpectedReturnCodeToString(ExpectedReturnCodeEnum expectedReturnCode) - { - switch (expectedReturnCode) - { - case ExpectedReturnCodeEnum::AlreadyInstalled: - return "alreadyInstalled"sv; - case ExpectedReturnCodeEnum::PackageInUse: - return "packageInUse"sv; - case ExpectedReturnCodeEnum::PackageInUseByApplication: - return "packageInUseByApplication"sv; - case ExpectedReturnCodeEnum::InstallInProgress: - return "installInProgress"sv; - case ExpectedReturnCodeEnum::FileInUse: - return "fileInUse"sv; - case ExpectedReturnCodeEnum::MissingDependency: - return "missingDependency"sv; - case ExpectedReturnCodeEnum::DiskFull: - return "diskFull"sv; - case ExpectedReturnCodeEnum::InsufficientMemory: - return "insufficientMemory"sv; - case ExpectedReturnCodeEnum::InvalidParameter: - return "invalidParameter"sv; - case ExpectedReturnCodeEnum::NoNetwork: - return "noNetwork"sv; - case ExpectedReturnCodeEnum::ContactSupport: - return "contactSupport"sv; - case ExpectedReturnCodeEnum::RebootRequiredToFinish: - return "rebootRequiredToFinish"sv; - case ExpectedReturnCodeEnum::RebootRequiredForInstall: - return "rebootRequiredForInstall"sv; - case ExpectedReturnCodeEnum::RebootInitiated: - return "rebootInitiated"sv; - case ExpectedReturnCodeEnum::CancelledByUser: - return "cancelledByUser"sv; - case ExpectedReturnCodeEnum::Downgrade: - return "downgrade"sv; - case ExpectedReturnCodeEnum::BlockedByPolicy: - return "blockedByPolicy"sv; - case ExpectedReturnCodeEnum::SystemNotSupported: - return "systemNotSupported"sv; - case ExpectedReturnCodeEnum::Custom: - return "custom"sv; - } - - return "unknown"sv; - } - - std::string_view ManifestTypeToString(ManifestTypeEnum manifestType) - { - switch (manifestType) - { - case ManifestTypeEnum::DefaultLocale: - return "defaultLocale"sv; - case ManifestTypeEnum::Installer: - return "installer"sv; - case ManifestTypeEnum::Locale: - return "locale"sv; - case ManifestTypeEnum::Merged: - return "merged"sv; - case ManifestTypeEnum::Singleton: - return "singleton"sv; - case ManifestTypeEnum::Version: - return "version"sv; - } - - return "unknown"sv; - } - - bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType) - { - return (installerType == InstallerTypeEnum::Msix || installerType == InstallerTypeEnum::MSStore); - } - - bool DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(const std::vector& entries) - { - for (const AppsAndFeaturesEntry& entry : entries) - { - if (DoesInstallerTypeUsePackageFamilyName(entry.InstallerType)) - { - return true; - } - } - - return false; - } - - bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType) - { - return ( - installerType == InstallerTypeEnum::Exe || - installerType == InstallerTypeEnum::Inno || - installerType == InstallerTypeEnum::Msi || - installerType == InstallerTypeEnum::Nullsoft || - installerType == InstallerTypeEnum::Wix || - installerType == InstallerTypeEnum::Burn || - installerType == InstallerTypeEnum::Portable - ); - } - - bool DoesInstallerTypeWriteAppsAndFeaturesEntry(InstallerTypeEnum installerType) - { - return ( - installerType == InstallerTypeEnum::Exe || - installerType == InstallerTypeEnum::Inno || - installerType == InstallerTypeEnum::Msi || - installerType == InstallerTypeEnum::Nullsoft || - installerType == InstallerTypeEnum::Wix || - installerType == InstallerTypeEnum::Burn || - installerType == InstallerTypeEnum::Portable - ); - } - - bool DoesInstallerTypeSupportArpVersionRange(InstallerTypeEnum installerType) - { - return ( - installerType == InstallerTypeEnum::Exe || - installerType == InstallerTypeEnum::Inno || - installerType == InstallerTypeEnum::Msi || - installerType == InstallerTypeEnum::Nullsoft || - installerType == InstallerTypeEnum::Wix || - installerType == InstallerTypeEnum::Burn || - installerType == InstallerTypeEnum::Msix - ); - } - - bool DoesInstallerTypeIgnoreScopeFromManifest(InstallerTypeEnum installerType) - { - return - installerType == InstallerTypeEnum::Font || - installerType == InstallerTypeEnum::Portable || - installerType == InstallerTypeEnum::Msix || - installerType == InstallerTypeEnum::MSStore; - } - - bool DoesInstallerTypeRequireAdminForMachineScopeInstall(InstallerTypeEnum installerType) - { - return - installerType == InstallerTypeEnum::Font || - installerType == InstallerTypeEnum::Portable || - installerType == InstallerTypeEnum::MSStore || - installerType == InstallerTypeEnum::Msix; - } - - bool DoesInstallerTypeRequireRepairBehaviorForRepair(InstallerTypeEnum installerType) - { - return - installerType == InstallerTypeEnum::Burn || - installerType == InstallerTypeEnum::Inno || - installerType == InstallerTypeEnum::Nullsoft || - installerType == InstallerTypeEnum::Exe; - } - - bool DoesInstallerTypeUseMsiProperties(InstallerTypeEnum installerType) - { - return - installerType == InstallerTypeEnum::Msi || - installerType == InstallerTypeEnum::Wix; - } - - bool IsArchiveType(InstallerTypeEnum installerType) - { - return (installerType == InstallerTypeEnum::Zip); - } - - bool IsPortableType(InstallerTypeEnum installerType) - { - return (installerType == InstallerTypeEnum::Portable); - } - - bool DoesInstallerTypeSupportMultipleNestedInstallers(InstallerTypeEnum installerType) - { - return ( - installerType == InstallerTypeEnum::Portable || - installerType == InstallerTypeEnum::Font - ); - } - - bool IsNestedInstallerTypeSupported(InstallerTypeEnum nestedInstallerType) - { - return ( - nestedInstallerType == InstallerTypeEnum::Exe || - nestedInstallerType == InstallerTypeEnum::Inno || - nestedInstallerType == InstallerTypeEnum::Msi || - nestedInstallerType == InstallerTypeEnum::Nullsoft || - nestedInstallerType == InstallerTypeEnum::Wix || - nestedInstallerType == InstallerTypeEnum::Burn || - nestedInstallerType == InstallerTypeEnum::Portable || - nestedInstallerType == InstallerTypeEnum::Msix || - nestedInstallerType == InstallerTypeEnum::Font - ); - } - - bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2) - { - // Unknown type cannot be compatible with any other - if (type1 == InstallerTypeEnum::Unknown || type2 == InstallerTypeEnum::Unknown) - { - return false; - } - - // Not unknown, so must be compatible - if (type1 == type2) - { - return true; - } - - CompatibilitySet set1 = GetCompatibilitySet(type1); - CompatibilitySet set2 = GetCompatibilitySet(type2); - - // If either is none, they can't be compatible - if (set1 == CompatibilitySet::None || set2 == CompatibilitySet::None) - { - return false; - } - - return set1 == set2; - } - - std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType) - { - switch (installerType) - { - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Wix: - case InstallerTypeEnum::Msi: - return - { - {InstallerSwitchType::Silent, ManifestInstaller::string_t("/quiet /norestart")}, - {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/passive /norestart")}, - {InstallerSwitchType::Log, ManifestInstaller::string_t("/log \"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, - {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("TARGETDIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")} - }; - case InstallerTypeEnum::Nullsoft: - return - { - {InstallerSwitchType::Silent, ManifestInstaller::string_t("/S")}, - {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/S")}, - {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/D=" + std::string(ARG_TOKEN_INSTALLPATH))} - }; - case InstallerTypeEnum::Inno: - return - { - {InstallerSwitchType::Silent, ManifestInstaller::string_t("/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART")}, - {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/SP- /SILENT /SUPPRESSMSGBOXES /NORESTART")}, - {InstallerSwitchType::Log, ManifestInstaller::string_t("/LOG=\"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, - {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/DIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")} - }; - default: - return {}; - } - } - - RepairBehaviorEnum ConvertToRepairBehaviorEnum(std::string_view in) - { - std::string inStrLower = Utility::ToLower(in); - RepairBehaviorEnum result = RepairBehaviorEnum::Unknown; - - if (inStrLower == "installer") - { - result = RepairBehaviorEnum::Installer; - } - else if (inStrLower == "uninstaller") - { - result = RepairBehaviorEnum::Uninstaller; - } - else if (inStrLower == "modify") - { - result = RepairBehaviorEnum::Modify; - } - - return result; - } - - std::map GetDefaultKnownReturnCodes(InstallerTypeEnum installerType) - { - switch (installerType) - { - case InstallerTypeEnum::Burn: - case InstallerTypeEnum::Wix: - case InstallerTypeEnum::Msi: - // See https://docs.microsoft.com/windows/win32/msi/error-codes - return - { - { ERROR_INSTALL_ALREADY_RUNNING, ExpectedReturnCodeEnum::InstallInProgress }, - { ERROR_DISK_FULL, ExpectedReturnCodeEnum::DiskFull }, - { ERROR_INSTALL_SERVICE_FAILURE, ExpectedReturnCodeEnum::ContactSupport }, - { ERROR_SUCCESS_REBOOT_REQUIRED, ExpectedReturnCodeEnum::RebootRequiredToFinish }, - { ERROR_SUCCESS_REBOOT_INITIATED, ExpectedReturnCodeEnum::RebootInitiated }, - { ERROR_INSTALL_USEREXIT, ExpectedReturnCodeEnum::CancelledByUser }, - { ERROR_PRODUCT_VERSION, ExpectedReturnCodeEnum::AlreadyInstalled }, - { ERROR_INSTALL_REJECTED, ExpectedReturnCodeEnum::SystemNotSupported }, - { ERROR_INSTALL_PACKAGE_REJECTED, ExpectedReturnCodeEnum::BlockedByPolicy }, - { ERROR_INSTALL_TRANSFORM_REJECTED, ExpectedReturnCodeEnum::BlockedByPolicy }, - { ERROR_PATCH_PACKAGE_REJECTED, ExpectedReturnCodeEnum::BlockedByPolicy }, - { ERROR_PATCH_REMOVAL_DISALLOWED, ExpectedReturnCodeEnum::BlockedByPolicy }, - { ERROR_INSTALL_REMOTE_DISALLOWED, ExpectedReturnCodeEnum::BlockedByPolicy }, - { ERROR_INVALID_PARAMETER, ExpectedReturnCodeEnum::InvalidParameter }, - { ERROR_INVALID_TABLE, ExpectedReturnCodeEnum::InvalidParameter }, - { ERROR_INVALID_COMMAND_LINE, ExpectedReturnCodeEnum::InvalidParameter }, - { ERROR_INVALID_PATCH_XML, ExpectedReturnCodeEnum::InvalidParameter }, - { ERROR_INSTALL_LANGUAGE_UNSUPPORTED, ExpectedReturnCodeEnum::SystemNotSupported }, - { ERROR_INSTALL_PLATFORM_UNSUPPORTED, ExpectedReturnCodeEnum::SystemNotSupported }, - }; - case InstallerTypeEnum::Inno: - // See https://jrsoftware.org/ishelp/index.php?topic=setupexitcodes - return - { - { 2, ExpectedReturnCodeEnum::CancelledByUser }, - { 5, ExpectedReturnCodeEnum::CancelledByUser }, - { 8, ExpectedReturnCodeEnum::RebootRequiredForInstall }, - }; - case InstallerTypeEnum::Msix: - // See https://docs.microsoft.com/en-us/windows/win32/appxpkg/troubleshooting - return - { - { HRESULT_FROM_WIN32(ERROR_INSTALL_PREREQUISITE_FAILED), ExpectedReturnCodeEnum::MissingDependency }, - { HRESULT_FROM_WIN32(ERROR_INSTALL_RESOLVE_DEPENDENCY_FAILED), ExpectedReturnCodeEnum::MissingDependency }, - { HRESULT_FROM_WIN32(ERROR_INSTALL_OPTIONAL_PACKAGE_REQUIRES_MAIN_PACKAGE), ExpectedReturnCodeEnum::MissingDependency }, - { HRESULT_FROM_WIN32(ERROR_INSTALL_OUT_OF_DISK_SPACE), ExpectedReturnCodeEnum::DiskFull }, - { HRESULT_FROM_WIN32(ERROR_INSTALL_CANCEL), ExpectedReturnCodeEnum::CancelledByUser }, - { HRESULT_FROM_WIN32(ERROR_PACKAGE_ALREADY_EXISTS), ExpectedReturnCodeEnum::AlreadyInstalled }, - { HRESULT_FROM_WIN32(ERROR_INSTALL_PACKAGE_DOWNGRADE), ExpectedReturnCodeEnum::Downgrade }, - { HRESULT_FROM_WIN32(ERROR_DEPLOYMENT_BLOCKED_BY_POLICY), ExpectedReturnCodeEnum::BlockedByPolicy}, - { HRESULT_FROM_WIN32(ERROR_INSTALL_POLICY_FAILURE), ExpectedReturnCodeEnum::BlockedByPolicy}, - { HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE), ExpectedReturnCodeEnum::PackageInUse }, - { HRESULT_FROM_WIN32(ERROR_INSTALL_WRONG_PROCESSOR_ARCHITECTURE), ExpectedReturnCodeEnum::SystemNotSupported }, - { HRESULT_FROM_WIN32(ERROR_PACKAGE_NOT_SUPPORTED_ON_FILESYSTEM), ExpectedReturnCodeEnum::SystemNotSupported }, - { HRESULT_FROM_WIN32(ERROR_DEPLOYMENT_OPTION_NOT_SUPPORTED), ExpectedReturnCodeEnum::SystemNotSupported }, - { HRESULT_FROM_WIN32(ERROR_PACKAGE_LACKS_CAPABILITY_TO_DEPLOY_ON_HOST), ExpectedReturnCodeEnum::SystemNotSupported }, - }; - default: - return {}; - } - } - - void DependencyList::Add(const Dependency& newDependency) - { - Dependency* existingDependency = this->HasDependency(newDependency); - - if (existingDependency != NULL) - { - if (newDependency.MinVersion > existingDependency->MinVersion) - { - existingDependency->MinVersion = newDependency.MinVersion; - } - } - else - { - m_dependencies.push_back(newDependency); - } - } - - void DependencyList::Add(const DependencyList& otherDependencyList) - { - for (const auto& dependency : otherDependencyList.m_dependencies) - { - this->Add(dependency); - } - } - - bool DependencyList::HasAny() const { return !m_dependencies.empty(); } - bool DependencyList::HasAnyOf(DependencyType type) const - { - for (const auto& dependency : m_dependencies) - { - if (dependency.Type == type) return true; - }; - return false; - } - - Dependency* DependencyList::HasDependency(const Dependency& dependencyToSearch) - { - for (auto& dependency : m_dependencies) - { - if (dependency.Type == dependencyToSearch.Type && ICUCaseInsensitiveEquals(dependency.Id(), dependencyToSearch.Id())) - { - return &dependency; - } - } - return nullptr; - } - - // for testing purposes - bool DependencyList::HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion) const - { - for (const auto& dependency : m_dependencies) - { - if (dependency.Type == type && Utility::ICUCaseInsensitiveEquals(dependency.Id(), id)) - { - if (!minVersion.empty()) - { - return dependency.MinVersion == Utility::Version{ minVersion }; - } - else - { - return true; - } - } - } - return false; - } - - size_t DependencyList::Size() const - { - return m_dependencies.size(); - } - - void DependencyList::ApplyToType(DependencyType type, std::function func) const - { - for (const auto& dependency : m_dependencies) - { - if (dependency.Type == type) func(dependency); - } - } - - void DependencyList::ApplyToAll(std::function func) const - { - for (const auto& dependency : m_dependencies) - { - func(dependency); - } - } - - bool DependencyList::Empty() const - { - return m_dependencies.empty(); - } - - void DependencyList::Clear() { m_dependencies.clear(); } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ManifestCommon.h" +#include "winget/ManifestValidation.h" + +namespace AppInstaller::Manifest +{ + namespace + { + enum class CompatibilitySet + { + None, + Exe, + Msi, + Msix, + }; + + CompatibilitySet GetCompatibilitySet(InstallerTypeEnum type) + { + switch (type) + { + case InstallerTypeEnum::Inno: + case InstallerTypeEnum::Nullsoft: + case InstallerTypeEnum::Exe: + case InstallerTypeEnum::Burn: + return CompatibilitySet::Exe; + case InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + return CompatibilitySet::Msi; + case InstallerTypeEnum::Msix: + case InstallerTypeEnum::MSStore: + return CompatibilitySet::Msix; + default: + return CompatibilitySet::None; + } + } + } + + ManifestVer::ManifestVer(std::string_view version) + { + bool validationSuccess = true; + + // Separate the extensions out + size_t hyphenPos = version.find_first_of('-'); + if (hyphenPos != std::string_view::npos) + { + // The first part is the main version + Assign(std::string{ version.substr(0, hyphenPos) }, "."); + + // The second part is the extensions + hyphenPos += 1; + while (hyphenPos < version.length()) + { + size_t newPos = version.find_first_of('-', hyphenPos); + + size_t length = (newPos == std::string::npos ? version.length() : newPos) - hyphenPos; + m_extensions.emplace_back(std::string{ version.substr(hyphenPos, length) }, "."); + + hyphenPos += length + 1; + } + } + else + { + Assign(std::string{ version }, "."); + } + + if (m_parts.size() > 3) + { + validationSuccess = false; + } + else + { + for (size_t i = 0; i < m_parts.size(); i++) + { + if (!m_parts[i].Other.empty()) + { + validationSuccess = false; + break; + } + } + + for (const RawVersion& ext : m_extensions) + { + if (ext.GetParts().empty() || ext.GetParts()[0].Integer != 0) + { + validationSuccess = false; + break; + } + } + } + + if (!validationSuccess) + { + std::vector errors; + errors.emplace_back(ManifestError::InvalidFieldValue, "ManifestVersion", std::string{ version }); + THROW_EXCEPTION(ManifestException(std::move(errors))); + } + } + + bool ManifestVer::HasExtension() const + { + return !m_extensions.empty(); + } + + bool ManifestVer::HasExtension(std::string_view extension) const + { + for (const RawVersion& ext : m_extensions) + { + const auto& parts = ext.GetParts(); + if (!parts.empty() && parts[0].Integer == 0 && parts[0].Other == extension) + { + return true; + } + } + + return false; + } + + InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in) + { + std::string inStrLower = Utility::ToLower(in); + InstallerTypeEnum result = InstallerTypeEnum::Unknown; + + if (inStrLower == "inno") + { + result = InstallerTypeEnum::Inno; + } + else if (inStrLower == "wix") + { + result = InstallerTypeEnum::Wix; + } + else if (inStrLower == "msi") + { + result = InstallerTypeEnum::Msi; + } + else if (inStrLower == "nullsoft") + { + result = InstallerTypeEnum::Nullsoft; + } + else if (inStrLower == "zip") + { + result = InstallerTypeEnum::Zip; + } + else if (inStrLower == "appx" || inStrLower == "msix") + { + result = InstallerTypeEnum::Msix; + } + else if (inStrLower == "exe") + { + result = InstallerTypeEnum::Exe; + } + else if (inStrLower == "burn") + { + result = InstallerTypeEnum::Burn; + } + else if (inStrLower == "msstore") + { + result = InstallerTypeEnum::MSStore; + } + else if (inStrLower == "portable") + { + result = InstallerTypeEnum::Portable; + } + else if (inStrLower == "font") + { + result = InstallerTypeEnum::Font; + } + + return result; + } + + UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in) + { + UpdateBehaviorEnum result = UpdateBehaviorEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "install")) + { + result = UpdateBehaviorEnum::Install; + } + else if (Utility::CaseInsensitiveEquals(in, "uninstallprevious")) + { + result = UpdateBehaviorEnum::UninstallPrevious; + } + else if (Utility::CaseInsensitiveEquals(in, "deny")) + { + result = UpdateBehaviorEnum::Deny; + } + + return result; + } + + ScopeEnum ConvertToScopeEnum(std::string_view in) + { + ScopeEnum result = ScopeEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "user")) + { + result = ScopeEnum::User; + } + else if (Utility::CaseInsensitiveEquals(in, "machine")) + { + result = ScopeEnum::Machine; + } + + return result; + } + + InstallModeEnum ConvertToInstallModeEnum(const std::string& in) + { + InstallModeEnum result = InstallModeEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "interactive")) + { + result = InstallModeEnum::Interactive; + } + else if (Utility::CaseInsensitiveEquals(in, "silent")) + { + result = InstallModeEnum::Silent; + } + else if (Utility::CaseInsensitiveEquals(in, "silentWithProgress")) + { + result = InstallModeEnum::SilentWithProgress; + } + + return result; + } + + PlatformEnum ConvertToPlatformEnum(std::string_view in) + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "windows.desktop") + { + return PlatformEnum::Desktop; + } + else if (inStrLower == "windows.universal") + { + return PlatformEnum::Universal; + } + + return PlatformEnum::Unknown; + } + + PlatformEnum ConvertToPlatformEnumForMSStoreDownload(std::string_view in) + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "windows.desktop") + { + return PlatformEnum::Desktop; + } + else if (inStrLower == "windows.universal") + { + return PlatformEnum::Universal; + } + else if (inStrLower == "windows.iot") + { + return PlatformEnum::IoT; + } + else if (inStrLower == "windows.team") + { + return PlatformEnum::Team; + } + else if (inStrLower == "windows.holographic") + { + return PlatformEnum::Holographic; + } + + return PlatformEnum::Unknown; + } + + ElevationRequirementEnum ConvertToElevationRequirementEnum(const std::string& in) + { + ElevationRequirementEnum result = ElevationRequirementEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "elevationRequired")) + { + result = ElevationRequirementEnum::ElevationRequired; + } + else if (Utility::CaseInsensitiveEquals(in, "elevationProhibited")) + { + result = ElevationRequirementEnum::ElevationProhibited; + } + else if (Utility::CaseInsensitiveEquals(in, "elevatesSelf")) + { + result = ElevationRequirementEnum::ElevatesSelf; + } + + return result; + } + + UnsupportedArgumentEnum ConvertToUnsupportedArgumentEnum(const std::string& in) + { + UnsupportedArgumentEnum result = UnsupportedArgumentEnum::Unknown; + + if (Utility::CaseInsensitiveEquals(in, "log")) + { + result = UnsupportedArgumentEnum::Log; + } + else if (Utility::CaseInsensitiveEquals(in, "location")) + { + result = UnsupportedArgumentEnum::Location; + } + + return result; + } + + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in) + { + if (in == "singleton") + { + return ManifestTypeEnum::Singleton; + } + else if (in == "version") + { + return ManifestTypeEnum::Version; + } + else if (in == "installer") + { + return ManifestTypeEnum::Installer; + } + else if (in == "defaultLocale") + { + return ManifestTypeEnum::DefaultLocale; + } + else if (in == "locale") + { + return ManifestTypeEnum::Locale; + } + else if (in == "merged") + { + return ManifestTypeEnum::Merged; + } + else if (in == "shadow") + { + return ManifestTypeEnum::Shadow; + } + else + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Unsupported ManifestType: %hs", in.c_str()); + } + } + + ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(const std::string& in) + { + std::string inStrLower = Utility::ToLower(in); + ExpectedReturnCodeEnum result = ExpectedReturnCodeEnum::Unknown; + + if (inStrLower == "packageinuse") + { + result = ExpectedReturnCodeEnum::PackageInUse; + } + else if (inStrLower == "packageinusebyapplication") + { + result = ExpectedReturnCodeEnum::PackageInUseByApplication; + } + else if (inStrLower == "installinprogress") + { + result = ExpectedReturnCodeEnum::InstallInProgress; + } + else if (inStrLower == "fileinuse") + { + result = ExpectedReturnCodeEnum::FileInUse; + } + else if (inStrLower == "missingdependency") + { + result = ExpectedReturnCodeEnum::MissingDependency; + } + else if (inStrLower == "diskfull") + { + result = ExpectedReturnCodeEnum::DiskFull; + } + else if (inStrLower == "insufficientmemory") + { + result = ExpectedReturnCodeEnum::InsufficientMemory; + } + else if (inStrLower == "invalidparameter") + { + result = ExpectedReturnCodeEnum::InvalidParameter; + } + else if (inStrLower == "nonetwork") + { + result = ExpectedReturnCodeEnum::NoNetwork; + } + else if (inStrLower == "contactsupport") + { + result = ExpectedReturnCodeEnum::ContactSupport; + } + else if (inStrLower == "rebootrequiredtofinish") + { + result = ExpectedReturnCodeEnum::RebootRequiredToFinish; + } + else if (inStrLower == "rebootrequiredforinstall") + { + result = ExpectedReturnCodeEnum::RebootRequiredForInstall; + } + else if (inStrLower == "rebootinitiated") + { + result = ExpectedReturnCodeEnum::RebootInitiated; + } + else if (inStrLower == "cancelledbyuser") + { + result = ExpectedReturnCodeEnum::CancelledByUser; + } + else if (inStrLower == "alreadyinstalled") + { + result = ExpectedReturnCodeEnum::AlreadyInstalled; + } + else if (inStrLower == "downgrade") + { + result = ExpectedReturnCodeEnum::Downgrade; + } + else if (inStrLower == "blockedbypolicy") + { + result = ExpectedReturnCodeEnum::BlockedByPolicy; + } + else if (inStrLower == "systemnotsupported") + { + result = ExpectedReturnCodeEnum::SystemNotSupported; + } + else if (inStrLower == "custom") + { + result = ExpectedReturnCodeEnum::Custom; + } + + return result; + } + + InstalledFileTypeEnum ConvertToInstalledFileTypeEnum(const std::string& in) + { + std::string inStrLower = Utility::ToLower(in); + InstalledFileTypeEnum result = InstalledFileTypeEnum::Unknown; + + if (inStrLower == "launch") + { + result = InstalledFileTypeEnum::Launch; + } + else if (inStrLower == "uninstall") + { + result = InstalledFileTypeEnum::Uninstall; + } + else if (inStrLower == "other") + { + result = InstalledFileTypeEnum::Other; + } + + return result; + } + + IconFileTypeEnum ConvertToIconFileTypeEnum(std::string_view in) + { + std::string inStrLower = Utility::ToLower(in); + IconFileTypeEnum result = IconFileTypeEnum::Unknown; + + if (inStrLower == "jpeg") + { + result = IconFileTypeEnum::Jpeg; + } + else if (inStrLower == "png") + { + result = IconFileTypeEnum::Png; + } + else if (inStrLower == "ico") + { + result = IconFileTypeEnum::Ico; + } + + return result; + } + + IconThemeEnum ConvertToIconThemeEnum(std::string_view in) + { + std::string inStrLower = Utility::ToLower(in); + IconThemeEnum result = IconThemeEnum::Unknown; + + if (inStrLower == "default") + { + result = IconThemeEnum::Default; + } + else if (inStrLower == "dark") + { + result = IconThemeEnum::Dark; + } + else if (inStrLower == "light") + { + result = IconThemeEnum::Light; + } + else if (inStrLower == "highcontrast") + { + result = IconThemeEnum::HighContrast; + } + + return result; + } + + IconResolutionEnum ConvertToIconResolutionEnum(std::string_view in) + { + std::string inStrLower = Utility::ToLower(in); + IconResolutionEnum result = IconResolutionEnum::Unknown; + + if (inStrLower == "custom") + { + result = IconResolutionEnum::Custom; + } + else if (inStrLower == "16x16") + { + result = IconResolutionEnum::Square16; + } + else if (inStrLower == "20x20") + { + result = IconResolutionEnum::Square20; + } + else if (inStrLower == "24x24") + { + result = IconResolutionEnum::Square24; + } + else if (inStrLower == "30x30") + { + result = IconResolutionEnum::Square30; + } + else if (inStrLower == "32x32") + { + result = IconResolutionEnum::Square32; + } + else if (inStrLower == "36x36") + { + result = IconResolutionEnum::Square36; + } + else if (inStrLower == "40x40") + { + result = IconResolutionEnum::Square40; + } + else if (inStrLower == "48x48") + { + result = IconResolutionEnum::Square48; + } + else if (inStrLower == "60x60") + { + result = IconResolutionEnum::Square60; + } + else if (inStrLower == "64x64") + { + result = IconResolutionEnum::Square64; + } + else if (inStrLower == "72x72") + { + result = IconResolutionEnum::Square72; + } + else if (inStrLower == "80x80") + { + result = IconResolutionEnum::Square80; + } + else if (inStrLower == "96x96") + { + result = IconResolutionEnum::Square96; + } + else if (inStrLower == "256x256") + { + result = IconResolutionEnum::Square256; + } + + return result; + } + + std::string_view InstallerTypeToString(InstallerTypeEnum installerType) + { + switch (installerType) + { + case InstallerTypeEnum::Exe: + return "exe"sv; + case InstallerTypeEnum::Inno: + return "inno"sv; + case InstallerTypeEnum::Msi: + return "msi"sv; + case InstallerTypeEnum::Msix: + return "msix"sv; + case InstallerTypeEnum::Nullsoft: + return "nullsoft"sv; + case InstallerTypeEnum::Wix: + return "wix"sv; + case InstallerTypeEnum::Zip: + return "zip"sv; + case InstallerTypeEnum::Burn: + return "burn"sv; + case InstallerTypeEnum::MSStore: + return "msstore"sv; + case InstallerTypeEnum::Portable: + return "portable"sv; + case InstallerTypeEnum::Font: + return "font"sv; + } + + return "unknown"sv; + } + + std::string_view InstallerSwitchTypeToString(InstallerSwitchType installerSwitchType) + { + switch (installerSwitchType) + { + case InstallerSwitchType::Custom: + return "Custom"sv; + case InstallerSwitchType::Silent: + return "Silent"sv; + case InstallerSwitchType::SilentWithProgress: + return "SilentWithProgress"sv; + case InstallerSwitchType::Interactive: + return "Interactive"sv; + case InstallerSwitchType::Language: + return "Language"sv; + case InstallerSwitchType::Log: + return "Log"sv; + case InstallerSwitchType::InstallLocation: + return "InstallLocation"sv; + case InstallerSwitchType::Update: + return "Upgrade"sv; + case InstallerSwitchType::Repair: + return "Repair"sv; + } + + return "Unknown"sv; + } + + std::string_view ElevationRequirementToString(ElevationRequirementEnum elevationRequirement) + { + switch (elevationRequirement) + { + case ElevationRequirementEnum::ElevationRequired: + return "elevationRequired"sv; + case ElevationRequirementEnum::ElevationProhibited: + return "elevationProhibited"sv; + case ElevationRequirementEnum::ElevatesSelf: + return "elevatesSelf"sv; + } + + return "unknown"sv; + } + + std::string_view UnsupportedArgumentToString(UnsupportedArgumentEnum unsupportedArgument) + { + switch (unsupportedArgument) + { + case UnsupportedArgumentEnum::Log: + return "log"sv; + case UnsupportedArgumentEnum::Location: + return "location"sv; + } + + return "unknown"sv; + } + + std::string_view InstallModeToString(InstallModeEnum installMode) + { + switch (installMode) + { + case InstallModeEnum::Interactive: + return "interactive"sv; + case InstallModeEnum::Silent: + return "silent"sv; + case InstallModeEnum::SilentWithProgress: + return "silentWithProgress"sv; + } + + return "unknown"sv; + } + + std::string_view PlatformToString(PlatformEnum platform, bool shortString) + { + switch (platform) + { + case PlatformEnum::Desktop: + return shortString ? "Desktop" : "Windows.Desktop"sv; + case PlatformEnum::Universal: + return shortString ? "Universal" : "Windows.Universal"sv; + case PlatformEnum::IoT: + return shortString ? "IoT" : "Windows.IoT"sv; + case PlatformEnum::Holographic: + return shortString ? "Holographic" : "Windows.Holographic"sv; + case PlatformEnum::Team: + return shortString ? "Team" : "Windows.Team"sv; + } + + return "Unknown"sv; + } + + std::string_view UpdateBehaviorToString(UpdateBehaviorEnum updateBehavior) + { + switch (updateBehavior) + { + case UpdateBehaviorEnum::Install: + return "install"sv; + case UpdateBehaviorEnum::UninstallPrevious: + return "uninstallPrevious"sv; + case UpdateBehaviorEnum::Deny: + return "deny"sv; + } + + return "unknown"sv; + } + + std::string_view RepairBehaviorToString(RepairBehaviorEnum repairBehavior) + { + switch (repairBehavior) + { + case AppInstaller::Manifest::RepairBehaviorEnum::Modify: + return "modify"sv; + case AppInstaller::Manifest::RepairBehaviorEnum::Installer: + return "installer"sv; + case AppInstaller::Manifest::RepairBehaviorEnum::Uninstaller: + return "uninstaller"sv; + } + + return "unknown"sv; + } + + std::string_view ScopeToString(ScopeEnum scope) + { + switch (scope) + { + case ScopeEnum::User: + return "User"sv; + case ScopeEnum::Machine: + return "Machine"sv; + } + + return "Unknown"sv; + } + + std::string_view InstalledFileTypeToString(InstalledFileTypeEnum installedFileType) + { + switch (installedFileType) + { + case InstalledFileTypeEnum::Launch: + return "launch"sv; + case InstalledFileTypeEnum::Uninstall: + return "uninstall"sv; + case InstalledFileTypeEnum::Other: + return "other"sv; + } + + return "unknown"sv; + } + + std::string_view IconFileTypeToString(IconFileTypeEnum iconFileType) + { + switch (iconFileType) + { + case IconFileTypeEnum::Ico: + return "ico"sv; + case IconFileTypeEnum::Jpeg: + return "jpeg"sv; + case IconFileTypeEnum::Png: + return "png"sv; + } + + return "unknown"sv; + } + + std::string_view IconThemeToString(IconThemeEnum iconTheme) + { + switch (iconTheme) + { + case IconThemeEnum::Default: + return "default"sv; + case IconThemeEnum::Dark: + return "dark"sv; + case IconThemeEnum::Light: + return "light"sv; + case IconThemeEnum::HighContrast: + return "highContrast"sv; + } + + return "unknown"sv; + } + + std::string_view IconResolutionToString(IconResolutionEnum iconResolution) + { + switch (iconResolution) + { + case IconResolutionEnum::Custom: + return "custom"sv; + case IconResolutionEnum::Square16: + return "16x16"sv; + case IconResolutionEnum::Square20: + return "20x20"sv; + case IconResolutionEnum::Square24: + return "24x24"sv; + case IconResolutionEnum::Square30: + return "30x30"sv; + case IconResolutionEnum::Square32: + return "32x32"sv; + case IconResolutionEnum::Square36: + return "36x36"sv; + case IconResolutionEnum::Square40: + return "40x40"sv; + case IconResolutionEnum::Square48: + return "48x48"sv; + case IconResolutionEnum::Square60: + return "60x60"sv; + case IconResolutionEnum::Square64: + return "64x64"sv; + case IconResolutionEnum::Square72: + return "72x72"sv; + case IconResolutionEnum::Square80: + return "80x80"sv; + case IconResolutionEnum::Square96: + return "96x96"sv; + case IconResolutionEnum::Square256: + return "256x256"sv; + } + + return "unknown"sv; + } + + std::string_view ExpectedReturnCodeToString(ExpectedReturnCodeEnum expectedReturnCode) + { + switch (expectedReturnCode) + { + case ExpectedReturnCodeEnum::AlreadyInstalled: + return "alreadyInstalled"sv; + case ExpectedReturnCodeEnum::PackageInUse: + return "packageInUse"sv; + case ExpectedReturnCodeEnum::PackageInUseByApplication: + return "packageInUseByApplication"sv; + case ExpectedReturnCodeEnum::InstallInProgress: + return "installInProgress"sv; + case ExpectedReturnCodeEnum::FileInUse: + return "fileInUse"sv; + case ExpectedReturnCodeEnum::MissingDependency: + return "missingDependency"sv; + case ExpectedReturnCodeEnum::DiskFull: + return "diskFull"sv; + case ExpectedReturnCodeEnum::InsufficientMemory: + return "insufficientMemory"sv; + case ExpectedReturnCodeEnum::InvalidParameter: + return "invalidParameter"sv; + case ExpectedReturnCodeEnum::NoNetwork: + return "noNetwork"sv; + case ExpectedReturnCodeEnum::ContactSupport: + return "contactSupport"sv; + case ExpectedReturnCodeEnum::RebootRequiredToFinish: + return "rebootRequiredToFinish"sv; + case ExpectedReturnCodeEnum::RebootRequiredForInstall: + return "rebootRequiredForInstall"sv; + case ExpectedReturnCodeEnum::RebootInitiated: + return "rebootInitiated"sv; + case ExpectedReturnCodeEnum::CancelledByUser: + return "cancelledByUser"sv; + case ExpectedReturnCodeEnum::Downgrade: + return "downgrade"sv; + case ExpectedReturnCodeEnum::BlockedByPolicy: + return "blockedByPolicy"sv; + case ExpectedReturnCodeEnum::SystemNotSupported: + return "systemNotSupported"sv; + case ExpectedReturnCodeEnum::Custom: + return "custom"sv; + } + + return "unknown"sv; + } + + std::string_view ManifestTypeToString(ManifestTypeEnum manifestType) + { + switch (manifestType) + { + case ManifestTypeEnum::DefaultLocale: + return "defaultLocale"sv; + case ManifestTypeEnum::Installer: + return "installer"sv; + case ManifestTypeEnum::Locale: + return "locale"sv; + case ManifestTypeEnum::Merged: + return "merged"sv; + case ManifestTypeEnum::Singleton: + return "singleton"sv; + case ManifestTypeEnum::Version: + return "version"sv; + } + + return "unknown"sv; + } + + bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType) + { + return (installerType == InstallerTypeEnum::Msix || installerType == InstallerTypeEnum::MSStore); + } + + bool DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(const std::vector& entries) + { + for (const AppsAndFeaturesEntry& entry : entries) + { + if (DoesInstallerTypeUsePackageFamilyName(entry.InstallerType)) + { + return true; + } + } + + return false; + } + + bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType) + { + return ( + installerType == InstallerTypeEnum::Exe || + installerType == InstallerTypeEnum::Inno || + installerType == InstallerTypeEnum::Msi || + installerType == InstallerTypeEnum::Nullsoft || + installerType == InstallerTypeEnum::Wix || + installerType == InstallerTypeEnum::Burn || + installerType == InstallerTypeEnum::Portable + ); + } + + bool DoesInstallerTypeWriteAppsAndFeaturesEntry(InstallerTypeEnum installerType) + { + return ( + installerType == InstallerTypeEnum::Exe || + installerType == InstallerTypeEnum::Inno || + installerType == InstallerTypeEnum::Msi || + installerType == InstallerTypeEnum::Nullsoft || + installerType == InstallerTypeEnum::Wix || + installerType == InstallerTypeEnum::Burn || + installerType == InstallerTypeEnum::Portable + ); + } + + bool DoesInstallerTypeSupportArpVersionRange(InstallerTypeEnum installerType) + { + return ( + installerType == InstallerTypeEnum::Exe || + installerType == InstallerTypeEnum::Inno || + installerType == InstallerTypeEnum::Msi || + installerType == InstallerTypeEnum::Nullsoft || + installerType == InstallerTypeEnum::Wix || + installerType == InstallerTypeEnum::Burn || + installerType == InstallerTypeEnum::Msix + ); + } + + bool DoesInstallerTypeIgnoreScopeFromManifest(InstallerTypeEnum installerType) + { + return + installerType == InstallerTypeEnum::Font || + installerType == InstallerTypeEnum::Portable || + installerType == InstallerTypeEnum::Msix || + installerType == InstallerTypeEnum::MSStore; + } + + bool DoesInstallerTypeRequireAdminForMachineScopeInstall(InstallerTypeEnum installerType) + { + return + installerType == InstallerTypeEnum::Font || + installerType == InstallerTypeEnum::Portable || + installerType == InstallerTypeEnum::MSStore || + installerType == InstallerTypeEnum::Msix; + } + + bool DoesInstallerTypeRequireRepairBehaviorForRepair(InstallerTypeEnum installerType) + { + return + installerType == InstallerTypeEnum::Burn || + installerType == InstallerTypeEnum::Inno || + installerType == InstallerTypeEnum::Nullsoft || + installerType == InstallerTypeEnum::Exe; + } + + bool DoesInstallerTypeUseMsiProperties(InstallerTypeEnum installerType) + { + return + installerType == InstallerTypeEnum::Msi || + installerType == InstallerTypeEnum::Wix; + } + + bool IsArchiveType(InstallerTypeEnum installerType) + { + return (installerType == InstallerTypeEnum::Zip); + } + + bool IsPortableType(InstallerTypeEnum installerType) + { + return (installerType == InstallerTypeEnum::Portable); + } + + bool DoesInstallerTypeSupportMultipleNestedInstallers(InstallerTypeEnum installerType) + { + return ( + installerType == InstallerTypeEnum::Portable || + installerType == InstallerTypeEnum::Font + ); + } + + bool IsNestedInstallerTypeSupported(InstallerTypeEnum nestedInstallerType) + { + return ( + nestedInstallerType == InstallerTypeEnum::Exe || + nestedInstallerType == InstallerTypeEnum::Inno || + nestedInstallerType == InstallerTypeEnum::Msi || + nestedInstallerType == InstallerTypeEnum::Nullsoft || + nestedInstallerType == InstallerTypeEnum::Wix || + nestedInstallerType == InstallerTypeEnum::Burn || + nestedInstallerType == InstallerTypeEnum::Portable || + nestedInstallerType == InstallerTypeEnum::Msix || + nestedInstallerType == InstallerTypeEnum::Font + ); + } + + bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2) + { + // Unknown type cannot be compatible with any other + if (type1 == InstallerTypeEnum::Unknown || type2 == InstallerTypeEnum::Unknown) + { + return false; + } + + // Not unknown, so must be compatible + if (type1 == type2) + { + return true; + } + + CompatibilitySet set1 = GetCompatibilitySet(type1); + CompatibilitySet set2 = GetCompatibilitySet(type2); + + // If either is none, they can't be compatible + if (set1 == CompatibilitySet::None || set2 == CompatibilitySet::None) + { + return false; + } + + return set1 == set2; + } + + std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType) + { + switch (installerType) + { + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + return + { + {InstallerSwitchType::Silent, ManifestInstaller::string_t("/quiet /norestart")}, + {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/passive /norestart")}, + {InstallerSwitchType::Log, ManifestInstaller::string_t("/log \"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, + {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("TARGETDIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")} + }; + case InstallerTypeEnum::Nullsoft: + return + { + {InstallerSwitchType::Silent, ManifestInstaller::string_t("/S")}, + {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/S")}, + {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/D=" + std::string(ARG_TOKEN_INSTALLPATH))} + }; + case InstallerTypeEnum::Inno: + return + { + {InstallerSwitchType::Silent, ManifestInstaller::string_t("/SP- /VERYSILENT /SUPPRESSMSGBOXES /NORESTART")}, + {InstallerSwitchType::SilentWithProgress, ManifestInstaller::string_t("/SP- /SILENT /SUPPRESSMSGBOXES /NORESTART")}, + {InstallerSwitchType::Log, ManifestInstaller::string_t("/LOG=\"" + std::string(ARG_TOKEN_LOGPATH) + "\"")}, + {InstallerSwitchType::InstallLocation, ManifestInstaller::string_t("/DIR=\"" + std::string(ARG_TOKEN_INSTALLPATH) + "\"")} + }; + default: + return {}; + } + } + + RepairBehaviorEnum ConvertToRepairBehaviorEnum(std::string_view in) + { + std::string inStrLower = Utility::ToLower(in); + RepairBehaviorEnum result = RepairBehaviorEnum::Unknown; + + if (inStrLower == "installer") + { + result = RepairBehaviorEnum::Installer; + } + else if (inStrLower == "uninstaller") + { + result = RepairBehaviorEnum::Uninstaller; + } + else if (inStrLower == "modify") + { + result = RepairBehaviorEnum::Modify; + } + + return result; + } + + std::map GetDefaultKnownReturnCodes(InstallerTypeEnum installerType) + { + switch (installerType) + { + case InstallerTypeEnum::Burn: + case InstallerTypeEnum::Wix: + case InstallerTypeEnum::Msi: + // See https://docs.microsoft.com/windows/win32/msi/error-codes + return + { + { ERROR_INSTALL_ALREADY_RUNNING, ExpectedReturnCodeEnum::InstallInProgress }, + { ERROR_DISK_FULL, ExpectedReturnCodeEnum::DiskFull }, + { ERROR_INSTALL_SERVICE_FAILURE, ExpectedReturnCodeEnum::ContactSupport }, + { ERROR_SUCCESS_REBOOT_REQUIRED, ExpectedReturnCodeEnum::RebootRequiredToFinish }, + { ERROR_SUCCESS_REBOOT_INITIATED, ExpectedReturnCodeEnum::RebootInitiated }, + { ERROR_INSTALL_USEREXIT, ExpectedReturnCodeEnum::CancelledByUser }, + { ERROR_PRODUCT_VERSION, ExpectedReturnCodeEnum::AlreadyInstalled }, + { ERROR_INSTALL_REJECTED, ExpectedReturnCodeEnum::SystemNotSupported }, + { ERROR_INSTALL_PACKAGE_REJECTED, ExpectedReturnCodeEnum::BlockedByPolicy }, + { ERROR_INSTALL_TRANSFORM_REJECTED, ExpectedReturnCodeEnum::BlockedByPolicy }, + { ERROR_PATCH_PACKAGE_REJECTED, ExpectedReturnCodeEnum::BlockedByPolicy }, + { ERROR_PATCH_REMOVAL_DISALLOWED, ExpectedReturnCodeEnum::BlockedByPolicy }, + { ERROR_INSTALL_REMOTE_DISALLOWED, ExpectedReturnCodeEnum::BlockedByPolicy }, + { ERROR_INVALID_PARAMETER, ExpectedReturnCodeEnum::InvalidParameter }, + { ERROR_INVALID_TABLE, ExpectedReturnCodeEnum::InvalidParameter }, + { ERROR_INVALID_COMMAND_LINE, ExpectedReturnCodeEnum::InvalidParameter }, + { ERROR_INVALID_PATCH_XML, ExpectedReturnCodeEnum::InvalidParameter }, + { ERROR_INSTALL_LANGUAGE_UNSUPPORTED, ExpectedReturnCodeEnum::SystemNotSupported }, + { ERROR_INSTALL_PLATFORM_UNSUPPORTED, ExpectedReturnCodeEnum::SystemNotSupported }, + }; + case InstallerTypeEnum::Inno: + // See https://jrsoftware.org/ishelp/index.php?topic=setupexitcodes + return + { + { 2, ExpectedReturnCodeEnum::CancelledByUser }, + { 5, ExpectedReturnCodeEnum::CancelledByUser }, + { 8, ExpectedReturnCodeEnum::RebootRequiredForInstall }, + }; + case InstallerTypeEnum::Msix: + // See https://docs.microsoft.com/en-us/windows/win32/appxpkg/troubleshooting + return + { + { HRESULT_FROM_WIN32(ERROR_INSTALL_PREREQUISITE_FAILED), ExpectedReturnCodeEnum::MissingDependency }, + { HRESULT_FROM_WIN32(ERROR_INSTALL_RESOLVE_DEPENDENCY_FAILED), ExpectedReturnCodeEnum::MissingDependency }, + { HRESULT_FROM_WIN32(ERROR_INSTALL_OPTIONAL_PACKAGE_REQUIRES_MAIN_PACKAGE), ExpectedReturnCodeEnum::MissingDependency }, + { HRESULT_FROM_WIN32(ERROR_INSTALL_OUT_OF_DISK_SPACE), ExpectedReturnCodeEnum::DiskFull }, + { HRESULT_FROM_WIN32(ERROR_INSTALL_CANCEL), ExpectedReturnCodeEnum::CancelledByUser }, + { HRESULT_FROM_WIN32(ERROR_PACKAGE_ALREADY_EXISTS), ExpectedReturnCodeEnum::AlreadyInstalled }, + { HRESULT_FROM_WIN32(ERROR_INSTALL_PACKAGE_DOWNGRADE), ExpectedReturnCodeEnum::Downgrade }, + { HRESULT_FROM_WIN32(ERROR_DEPLOYMENT_BLOCKED_BY_POLICY), ExpectedReturnCodeEnum::BlockedByPolicy}, + { HRESULT_FROM_WIN32(ERROR_INSTALL_POLICY_FAILURE), ExpectedReturnCodeEnum::BlockedByPolicy}, + { HRESULT_FROM_WIN32(ERROR_PACKAGES_IN_USE), ExpectedReturnCodeEnum::PackageInUse }, + { HRESULT_FROM_WIN32(ERROR_INSTALL_WRONG_PROCESSOR_ARCHITECTURE), ExpectedReturnCodeEnum::SystemNotSupported }, + { HRESULT_FROM_WIN32(ERROR_PACKAGE_NOT_SUPPORTED_ON_FILESYSTEM), ExpectedReturnCodeEnum::SystemNotSupported }, + { HRESULT_FROM_WIN32(ERROR_DEPLOYMENT_OPTION_NOT_SUPPORTED), ExpectedReturnCodeEnum::SystemNotSupported }, + { HRESULT_FROM_WIN32(ERROR_PACKAGE_LACKS_CAPABILITY_TO_DEPLOY_ON_HOST), ExpectedReturnCodeEnum::SystemNotSupported }, + }; + default: + return {}; + } + } + + void DependencyList::Add(const Dependency& newDependency) + { + Dependency* existingDependency = this->HasDependency(newDependency); + + if (existingDependency != NULL) + { + if (newDependency.MinVersion > existingDependency->MinVersion) + { + existingDependency->MinVersion = newDependency.MinVersion; + } + } + else + { + m_dependencies.push_back(newDependency); + } + } + + void DependencyList::Add(const DependencyList& otherDependencyList) + { + for (const auto& dependency : otherDependencyList.m_dependencies) + { + this->Add(dependency); + } + } + + bool DependencyList::HasAny() const { return !m_dependencies.empty(); } + bool DependencyList::HasAnyOf(DependencyType type) const + { + for (const auto& dependency : m_dependencies) + { + if (dependency.Type == type) return true; + }; + return false; + } + + Dependency* DependencyList::HasDependency(const Dependency& dependencyToSearch) + { + for (auto& dependency : m_dependencies) + { + if (dependency.Type == dependencyToSearch.Type && ICUCaseInsensitiveEquals(dependency.Id(), dependencyToSearch.Id())) + { + return &dependency; + } + } + return nullptr; + } + + // for testing purposes + bool DependencyList::HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion) const + { + for (const auto& dependency : m_dependencies) + { + if (dependency.Type == type && Utility::ICUCaseInsensitiveEquals(dependency.Id(), id)) + { + if (!minVersion.empty()) + { + return dependency.MinVersion == Utility::Version{ minVersion }; + } + else + { + return true; + } + } + } + return false; + } + + size_t DependencyList::Size() const + { + return m_dependencies.size(); + } + + void DependencyList::ApplyToType(DependencyType type, std::function func) const + { + for (const auto& dependency : m_dependencies) + { + if (dependency.Type == type) func(dependency); + } + } + + void DependencyList::ApplyToAll(std::function func) const + { + for (const auto& dependency : m_dependencies) + { + func(dependency); + } + } + + bool DependencyList::Empty() const + { + return m_dependencies.empty(); + } + + void DependencyList::Clear() { m_dependencies.clear(); } +} diff --git a/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp index 819b72cffb..fc4ddd09e4 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestComparator.cpp @@ -1,916 +1,916 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include -#include -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Manifest -{ - std::ostream& operator<<(std::ostream& out, const ManifestInstaller& installer) - { - return out << '[' << - AppInstaller::Utility::ToString(installer.Arch) << ',' << - AppInstaller::Manifest::InstallerTypeToString(installer.EffectiveInstallerType()) << ',' << - AppInstaller::Manifest::ScopeToString(installer.Scope) << ',' << - installer.Locale << ']'; - } -} - -namespace AppInstaller::Manifest -{ - namespace - { - struct PortableInstallFilter : public details::FilterField - { - PortableInstallFilter() : details::FilterField("Portable Install") {} - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - // Unvirtualized resources restricted capability is only supported for >= 10.0.18362 - // TODO: Add support for OS versions that don't support virtualization. - if (installer.EffectiveInstallerType() == InstallerTypeEnum::Portable && !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version("10.0.18362"))) - { - return InapplicabilityFlags::OSVersion; - } - - return InapplicabilityFlags::None; - } - - std::string ExplainInapplicable(const ManifestInstaller&) override - { - std::string result = "Current OS is lower than supported MinOSVersion (10.0.18362) for Portable install"; - return result; - } - }; - - struct OSVersionFilter : public details::FilterField - { - OSVersionFilter() : details::FilterField("OS Version") {} - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - if (installer.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer.MinOSVersion))) - { - return InapplicabilityFlags::None; - } - - return InapplicabilityFlags::OSVersion; - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result = "Current OS is lower than MinOSVersion "; - result += installer.MinOSVersion; - return result; - } - }; - - struct MachineArchitectureComparator : public details::ComparisonField - { - MachineArchitectureComparator() : details::ComparisonField("Machine Architecture") {} - - MachineArchitectureComparator(std::vector allowedArchitectures) : - details::ComparisonField("Machine Architecture"), m_allowedArchitectures(std::move(allowedArchitectures)) - { - AICLI_LOG(CLI, Verbose, << "Architecture Comparator created with allowed architectures: " << Utility::ConvertContainerToString(m_allowedArchitectures, Utility::ToString)); - } - - static std::unique_ptr Create(const ManifestComparator::Options& options) - { - if (!options.AllowedArchitectures.empty()) - { - // If the incoming data contains elements, we will use them to construct a final allowed list. - // The algorithm is to take elements until we find Unknown, which indicates that any architecture is - // acceptable at this point. The system supported set of architectures will then be placed at the end. - std::vector result; - bool addRemainingApplicableArchitectures = false; - - for (Utility::Architecture architecture : options.AllowedArchitectures) - { - if (architecture == Utility::Architecture::Unknown) - { - addRemainingApplicableArchitectures = true; - break; - } - - // If the architecture is applicable and not already in our result set... - if ((options.SkipApplicabilityCheck || Utility::IsApplicableArchitecture(architecture) != Utility::InapplicableArchitecture) && - Utility::IsApplicableArchitecture(architecture, result) == Utility::InapplicableArchitecture) - { - result.push_back(architecture); - } - } - - if (addRemainingApplicableArchitectures) - { - for (Utility::Architecture architecture : Utility::GetApplicableArchitectures()) - { - if (Utility::IsApplicableArchitecture(architecture, result) == Utility::InapplicableArchitecture) - { - result.push_back(architecture); - } - } - } - - return std::make_unique(std::move(result)); - } - else - { - return std::make_unique(); - } - } - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - if (CheckAllowedArchitecture(installer.Arch) == Utility::InapplicableArchitecture || - IsSystemArchitectureUnsupportedByInstaller(installer)) - { - return InapplicabilityFlags::MachineArchitecture; - } - - return InapplicabilityFlags::None; - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result; - if (Utility::IsApplicableArchitecture(installer.Arch) == Utility::InapplicableArchitecture) - { - result = "Machine is not compatible with "; - result += Utility::ToString(installer.Arch); - } - else if (IsSystemArchitectureUnsupportedByInstaller(installer)) - { - result = "System architecture is unsupported by installer"; - } - else - { - result = "Architecture was excluded by caller : "; - result += Utility::ToString(installer.Arch); - } - - return result; - } - - details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override - { - auto arch1 = CheckAllowedArchitecture(first.Arch); - auto arch2 = CheckAllowedArchitecture(second.Arch); - - if (arch1 > arch2) - { - // A match with the primary architecture is strong - return (first.Arch == GetStrongArchitectureMatch() ? details::ComparisonResult::StrongPositive : details::ComparisonResult::WeakPositive); - } - - return details::ComparisonResult::Negative; - } - - private: - int CheckAllowedArchitecture(Utility::Architecture architecture) - { - if (m_allowedArchitectures.empty()) - { - return Utility::IsApplicableArchitecture(architecture); - } - else - { - return Utility::IsApplicableArchitecture(architecture, m_allowedArchitectures); - } - } - - bool IsSystemArchitectureUnsupportedByInstaller(const ManifestInstaller& installer) - { - auto unsupportedItr = std::find( - installer.UnsupportedOSArchitectures.begin(), - installer.UnsupportedOSArchitectures.end(), - Utility::GetSystemArchitecture()); - return unsupportedItr != installer.UnsupportedOSArchitectures.end(); - } - - Utility::Architecture GetStrongArchitectureMatch() - { - // If we have a preferential order, treat the first entry as strong. - // Otherwise, treat the system architecture as strong (which is always first in the default order). - return m_allowedArchitectures.empty() ? Utility::GetSystemArchitecture() : m_allowedArchitectures.front(); - } - - std::vector m_allowedArchitectures; - }; - - struct InstallerTypeComparator : public details::ComparisonField - { - InstallerTypeComparator(std::vector preference, std::vector requirement) : - details::ComparisonField("Installer Type"), m_preference(std::move(preference)), m_requirement(std::move(requirement)) - { - m_preferenceAsString = Utility::ConvertContainerToString(m_preference, InstallerTypeToString); - m_requirementAsString = Utility::ConvertContainerToString(m_requirement, InstallerTypeToString); - AICLI_LOG(CLI, Verbose, - << "InstallerType Comparator created with Required InstallerTypes: " << m_requirementAsString - << " , Preferred InstallerTypes: " << m_preferenceAsString); - } - - static std::unique_ptr Create(const ManifestComparator::Options& options) - { - std::vector preference; - std::vector requirement; - - if (options.RequestedInstallerType) - { - requirement.emplace_back(options.RequestedInstallerType.value()); - } - else - { - preference = Settings::User().Get(); - requirement = Settings::User().Get(); - - // Apply default precedence order when the user has not configured any installer type preferences or requirements. - if (preference.empty() && requirement.empty()) - { - preference = { - InstallerTypeEnum::MSStore, - InstallerTypeEnum::Msix, - InstallerTypeEnum::Msi, - InstallerTypeEnum::Wix, - InstallerTypeEnum::Burn, - InstallerTypeEnum::Nullsoft, - InstallerTypeEnum::Inno, - InstallerTypeEnum::Exe, - InstallerTypeEnum::Portable, - }; - } - } - - if (!preference.empty() || !requirement.empty()) - { - return std::make_unique(preference, requirement); - } - else - { - return {}; - } - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result = "InstallerType ["; - result += InstallerTypeToString(installer.EffectiveInstallerType()); - result += "] does not match required InstallerTypes: "; - result += m_requirementAsString; - return result; - } - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - if (!m_requirement.empty()) - { - // The installer is applicable if the effective or base installer type matches. - if (ContainsInstallerType(m_requirement, installer.EffectiveInstallerType()) || - ContainsInstallerType(m_requirement, installer.BaseInstallerType)) - { - return InapplicabilityFlags::None; - } - - return InapplicabilityFlags::InstallerType; - } - else - { - return InapplicabilityFlags::None; - } - } - - details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override - { - // If no preferences are set, use requirement ordering instead. - const auto& effectiveOrder = m_preference.empty() ? m_requirement : m_preference; - - if (effectiveOrder.empty()) - { - return details::ComparisonResult::Negative; - } - - for (InstallerTypeEnum installerTypePreference : effectiveOrder) - { - bool isFirstInstallerTypePreferred = - first.EffectiveInstallerType() == installerTypePreference || - first.BaseInstallerType == installerTypePreference; - - bool isSecondInstallerTypePreferred = - second.EffectiveInstallerType() == installerTypePreference || - second.BaseInstallerType == installerTypePreference; - - if (isFirstInstallerTypePreferred && isSecondInstallerTypePreferred) - { - return details::ComparisonResult::Negative; - } - else if (isFirstInstallerTypePreferred != isSecondInstallerTypePreferred) - { - // Treating this as a weak positive because one can use requirements to guarantee the installer type if necessary. - return (isFirstInstallerTypePreferred ? details::ComparisonResult::WeakPositive : details::ComparisonResult::Negative); - } - } - - return details::ComparisonResult::Negative; - } - - private: - std::vector m_preference; - std::vector m_requirement; - std::string m_preferenceAsString; - std::string m_requirementAsString; - - bool ContainsInstallerType(const std::vector& selection, InstallerTypeEnum installerType) - { - return std::find(selection.begin(), selection.end(), installerType) != selection.end(); - } - }; - - struct InstalledTypeFilter : public details::FilterField - { - InstalledTypeFilter(InstallerTypeEnum installedType) : - details::FilterField("Installed Type"), m_installedType(installedType) {} - - static std::unique_ptr Create(const ManifestComparator::Options& options) - { - if (options.CurrentlyInstalledType) - { - InstallerTypeEnum installedType = options.CurrentlyInstalledType.value(); - if (installedType != InstallerTypeEnum::Unknown) - { - return std::make_unique(installedType); - } - } - - return {}; - } - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - return IsInstallerCompatibleWith(installer, m_installedType) ? InapplicabilityFlags::None : InapplicabilityFlags::InstalledType; - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result = "Installed package type '" + std::string{ InstallerTypeToString(m_installedType) } + - "' is not compatible with installer type " + std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }; - - std::string arpInstallerTypes; - for (const auto& entry : installer.AppsAndFeaturesEntries) - { - arpInstallerTypes += " " + std::string{ InstallerTypeToString(entry.InstallerType) }; - } - - if (!arpInstallerTypes.empty()) - { - result += ", or with accepted type(s)" + arpInstallerTypes; - } - - return result; - } - - private: - // The installer is compatible if it's type or any of its ARP entries' type matches the installed type - static bool IsInstallerCompatibleWith(const ManifestInstaller& installer, InstallerTypeEnum type) - { - if (IsInstallerTypeCompatible(installer.EffectiveInstallerType(), type)) - { - return true; - } - - auto itr = std::find_if( - installer.AppsAndFeaturesEntries.begin(), - installer.AppsAndFeaturesEntries.end(), - [=](AppsAndFeaturesEntry arpEntry) { return IsInstallerTypeCompatible(arpEntry.InstallerType, type); }); - if (itr != installer.AppsAndFeaturesEntries.end()) - { - return true; - } - - return false; - } - - InstallerTypeEnum m_installedType; - }; - - struct InstalledScopeFilter : public details::FilterField - { - InstalledScopeFilter(ScopeEnum requirement) : - details::FilterField("Installed Scope"), m_requirement(requirement) {} - - static std::unique_ptr Create(const ManifestComparator::Options& options) - { - // Check for an existing install and require a matching scope. - if (options.CurrentlyInstalledScope) - { - ScopeEnum installedScope = options.CurrentlyInstalledScope.value(); - if (installedScope != ScopeEnum::Unknown) - { - return std::make_unique(installedScope); - } - } - - return {}; - } - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - // We have to assume the unknown scope will match our required scope, or the entire catalog would stop working for upgrade. - if (installer.Scope == ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) - { - return InapplicabilityFlags::None; - } - - return InapplicabilityFlags::InstalledScope; - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result = "Installer scope does not match currently installed scope: "; - result += ScopeToString(installer.Scope); - result += " != "; - result += ScopeToString(m_requirement); - return result; - } - - private: - ScopeEnum m_requirement; - }; - - struct ScopeComparator : public details::ComparisonField - { - ScopeComparator(ScopeEnum preference, ScopeEnum requirement, bool allowUnknownInAdditionToRequired) : - details::ComparisonField("Scope"), m_preference(preference), m_requirement(requirement), m_allowUnknownInAdditionToRequired(allowUnknownInAdditionToRequired) {} - - static std::unique_ptr Create(const ManifestComparator::Options& options) - { - // Preference will always come from settings - ScopeEnum preference = Settings::User().Get(); - - // Requirement may come from args or settings; args overrides settings. - ScopeEnum requirement = ScopeEnum::Unknown; - - if (options.RequestedInstallerScope) - { - requirement = options.RequestedInstallerScope.value(); - } - else - { - requirement = Settings::User().Get(); - } - - bool allowUnknownInAdditionToRequired = false; - if (options.AllowUnknownScope) - { - allowUnknownInAdditionToRequired = options.AllowUnknownScope.value(); - - // Force the required type to be preferred over Unknown - if (requirement != ScopeEnum::Unknown) - { - preference = requirement; - } - } - - if (preference != ScopeEnum::Unknown || requirement != ScopeEnum::Unknown) - { - return std::make_unique(preference, requirement, allowUnknownInAdditionToRequired); - } - else - { - return {}; - } - } - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - // Applicable if one of: - // 1. No requirement (aka is Unknown) - // 2. Requirement met - // 3. Installer scope is Unknown and this has been explicitly allowed - // 4. The installer type is scope agnostic (we can control it) - if (m_requirement == ScopeEnum::Unknown || - installer.Scope == m_requirement || - (installer.Scope == ScopeEnum::Unknown && m_allowUnknownInAdditionToRequired) || - DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) - { - return InapplicabilityFlags::None; - } - - return InapplicabilityFlags::Scope; - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result = "Installer scope does not match required scope: "; - result += ScopeToString(installer.Scope); - result += " != "; - result += ScopeToString(m_requirement); - return result; - } - - details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override - { - if (m_preference != ScopeEnum::Unknown && first.Scope == m_preference && second.Scope != m_preference) - { - // When the second input is unknown, this is a weak result. If it is not (and therefore the opposite of the preference), this is strong. - return (second.Scope == ScopeEnum::Unknown ? details::ComparisonResult::WeakPositive : details::ComparisonResult::StrongPositive); - } - - return details::ComparisonResult::Negative; - } - - private: - ScopeEnum m_preference; - ScopeEnum m_requirement; - bool m_allowUnknownInAdditionToRequired; - }; - - struct LocaleComparator : public details::ComparisonField - { - LocaleComparator(std::vector preference, std::vector requirement, bool isInstalledLocale) : - details::ComparisonField("Locale"), m_preference(std::move(preference)), m_requirement(std::move(requirement)), m_isInstalledLocale(isInstalledLocale) - { - m_requirementAsString = Utility::ConvertContainerToString(m_requirement); - m_preferenceAsString = Utility::ConvertContainerToString(m_preference); - AICLI_LOG(CLI, Verbose, - << "Locale Comparator created with Required Locales: " << m_requirementAsString - << " , Preferred Locales: " << m_preferenceAsString - << " , IsInstalledLocale: " << m_isInstalledLocale); - } - - static std::unique_ptr Create(const ManifestComparator::Options& options) - { - std::vector preference; - std::vector requirement; - // This is for installed locale case, where the locale is a preference but requires at least compatible match. - bool isInstalledLocale = false; - - // Requirement may come from args, previous user intent or settings; args overrides previous user intent then settings. - if (options.RequestedInstallerLocale) - { - requirement.emplace_back(options.RequestedInstallerLocale.value()); - } - else if (options.PreviousUserIntentLocale) - { - requirement.emplace_back(options.PreviousUserIntentLocale.value()); - isInstalledLocale = true; - } - else - { - if (!options.CurrentlyInstalledLocale) - { - // If it's an upgrade of previous package, no need to set requirements from settings - // as previous installed locale will be used later. - requirement = Settings::User().Get(); - } - } - - // Preference will come from previous installed locale, winget settings or Preferred Languages settings. - // Previous installed locale goes first, then winget settings, then Preferred Languages settings. - // Previous installed locale also requires at least compatible locale match. - if (options.CurrentlyInstalledLocale) - { - preference.emplace_back(options.CurrentlyInstalledLocale.value()); - isInstalledLocale = true; - } - else - { - preference = Settings::User().Get(); - if (preference.empty()) - { - preference = Locale::GetUserPreferredLanguages(); - } - } - - if (!preference.empty() || !requirement.empty()) - { - return std::make_unique(preference, requirement, isInstalledLocale); - } - else - { - return {}; - } - } - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - InapplicabilityFlags inapplicableFlag = m_isInstalledLocale ? InapplicabilityFlags::InstalledLocale : InapplicabilityFlags::Locale; - - if (!m_requirement.empty()) - { - // Check if requirement is satisfied - for (auto const& requiredLocale : m_requirement) - { - if (Locale::GetDistanceOfLanguage(requiredLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsPerfectMatch) - { - return InapplicabilityFlags::None; - } - } - - return inapplicableFlag; - } - else if (m_isInstalledLocale && !m_preference.empty()) - { - // For installed locale preference, check at least compatible match for preference - for (auto const& preferredLocale : m_preference) - { - // We have to assume an unknown installer locale will match our installed locale, or the entire catalog would stop working for upgrade. - if (installer.Locale.empty() || - Locale::GetDistanceOfLanguage(preferredLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch) - { - return InapplicabilityFlags::None; - } - } - - return inapplicableFlag; - } - else - { - return InapplicabilityFlags::None; - } - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result = "Installer locale does not match required locale: "; - result += installer.Locale; - result += "Required locales: "; - result += m_requirementAsString; - result += " Or does not satisfy compatible match for Preferred Locales: "; - result += m_preferenceAsString; - return result; - } - - details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override - { - if (m_preference.empty()) - { - return details::ComparisonResult::Negative; - } - - for (auto const& preferredLocale : m_preference) - { - double firstScore = first.Locale.empty() ? Locale::UnknownLanguageDistanceScore : Locale::GetDistanceOfLanguage(preferredLocale, first.Locale); - double secondScore = second.Locale.empty() ? Locale::UnknownLanguageDistanceScore : Locale::GetDistanceOfLanguage(preferredLocale, second.Locale); - - if (firstScore >= Locale::MinimumDistanceScoreAsCompatibleMatch || secondScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) - { - // This could probably be enriched to always check all locales and determine strong/weak based off of the MinimumDistanceScoreAsCompatibleMatch. - return (firstScore > secondScore ? details::ComparisonResult::StrongPositive : details::ComparisonResult::Negative); - } - } - - // At this point, the installer locale matches no preference. - // if first is unknown and second is no match for sure, we might prefer unknown one. - return (first.Locale.empty() && !second.Locale.empty() ? details::ComparisonResult::WeakPositive : details::ComparisonResult::Negative); - } - - private: - std::vector m_preference; - std::vector m_requirement; - std::string m_requirementAsString; - std::string m_preferenceAsString; - bool m_isInstalledLocale = false; - }; - - struct MarketFilter : public details::FilterField - { - MarketFilter(Manifest::string_t market) : details::FilterField("Market"), m_market(market) - { - AICLI_LOG(CLI, Verbose, << "Market Filter created with market: " << m_market); - } - - static std::unique_ptr Create() - { - return std::make_unique(Runtime::GetOSRegion()); - } - - InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override - { - // If both allowed and excluded lists are provided, we only need to check the allowed markets. - if (!installer.Markets.AllowedMarkets.empty()) - { - // Inapplicable if NOT found - if (!IsMarketInList(installer.Markets.AllowedMarkets)) - { - return InapplicabilityFlags::Market; - } - } - else if (!installer.Markets.ExcludedMarkets.empty()) - { - // Inapplicable if found - if (IsMarketInList(installer.Markets.ExcludedMarkets)) - { - return InapplicabilityFlags::Market; - } - } - - return InapplicabilityFlags::None; - } - - std::string ExplainInapplicable(const ManifestInstaller& installer) override - { - std::string result = "Current market '" + m_market + "' does not match installer markets." + - " Allowed markets: " + Utility::ConvertContainerToString(installer.Markets.AllowedMarkets) + - " Excluded markets: " + Utility::ConvertContainerToString(installer.Markets.ExcludedMarkets); - return result; - } - - private: - bool IsMarketInList(const std::vector markets) - { - return markets.end() != std::find_if( - markets.begin(), - markets.end(), - [&](const auto& m) { return Utility::CaseInsensitiveEquals(m, m_market); }); - } - - Manifest::string_t m_market; - }; - } - - ManifestComparator::ManifestComparator(const Options& options) - { - // Filters based on installer's MinOSVersion - AddFilter(std::make_unique()); - // Filters out portable installers if they are not supported by the system - AddFilter(std::make_unique()); - // Filters based on the scope of a currently installed package - AddFilter(InstalledScopeFilter::Create(options)); - // Filters based on the market region of the system - AddFilter(MarketFilter::Create()); - // Filters based on the installer type compatability, including with AppsAndFeaturesEntry declarations - AddFilter(InstalledTypeFilter::Create(options)); - - // Filter order is not important, but comparison order determines priority. - // Note that all comparators are also filters and their comparison function will only be called on - // installers that both match the required criteria. - // - // The comparators are ordered by the `IsFirstBetter` method, which uses the following algorithm: - // - Each comparison between two installers can return one of { Strong, Weak, Negative } - // - Installers are compared in both directions, going through the list of comparators as defined here - // - The first Strong result in either direction is given priority - // - If no Strong results, the first Weak result is used - // - If all Negative results, then the two installers are equal in priority (meaning the first one in the list is kept as "better") - // - // TODO: There are improvements to be made here around ordering, especially in the context of implicit vs explicit vs command line preferences. - - // Filters based on exact matches for requirements or compatible matches for preferences - // Only applies when preference exists: - // Strong if first is compatible and better match than second - // Weak if first is unknown and second is not - AddComparator(LocaleComparator::Create(options)); - // Filters only if a requirement is present and it cannot be satisfied by the installer (including installer types that we can control scope in code) - // Only applies when preference exists: - // Strong if first matches preference and second does not and is not Unknown - // Weak if first matches preference and second is Unknown - AddComparator(ScopeComparator::Create(options)); - // Filters architectures out that are not supported or are not in the preferences/requirements/inputs. - // Strong if first equals the earliest architecture in the allowed list and second does not [default means the system architecture] - // Weak if first is better match for system architecture than second - AddComparator(MachineArchitectureComparator::Create(options)); - // Filters installer types out that are not in preferences or requirements. - // Only applies when preference exists: - // Weak if first is in preference list and second is not - AddComparator(InstallerTypeComparator::Create(options)); - } - - InstallerAndInapplicabilities ManifestComparator::GetPreferredInstaller(const Manifest& manifest) - { - AICLI_LOG(CLI, Verbose, << "Starting installer selection."); - - const ManifestInstaller* result = nullptr; - std::vector inapplicabilitiesInstallers; - - for (const auto& installer : manifest.Installers) - { - auto inapplicabilityInstaller = IsApplicable(installer); - if (inapplicabilityInstaller == InapplicabilityFlags::None) - { - if (!result || IsFirstBetter(installer, *result)) - { - AICLI_LOG(CLI, Verbose, << "Installer " << installer << " is current best choice"); - result = &installer; - } - } - else - { - inapplicabilitiesInstallers.push_back(inapplicabilityInstaller); - } - } - - if (!result) - { - return { {}, std::move(inapplicabilitiesInstallers) }; - } - - return { *result, std::move(inapplicabilitiesInstallers) }; - } - - InapplicabilityFlags ManifestComparator::IsApplicable(const ManifestInstaller& installer) - { - InapplicabilityFlags inapplicabilityResult = InapplicabilityFlags::None; - - for (const auto& filter : m_filters) - { - auto inapplicability = filter->IsApplicable(installer); - if (inapplicability != InapplicabilityFlags::None) - { - AICLI_LOG(CLI, Verbose, << "Installer " << installer << " not applicable: " << filter->ExplainInapplicable(installer)); - WI_SetAllFlags(inapplicabilityResult, inapplicability); - } - } - - return inapplicabilityResult; - } - - bool ManifestComparator::IsFirstBetter( - const ManifestInstaller& first, - const ManifestInstaller& second) - { - // The priority will still be used as a tie-break between weak results. - std::optional firstWeakComparator; - bool firstWeakComparatorResult = false; - - for (auto comparator : m_comparators) - { - details::ComparisonResult forwardCompare = comparator->IsFirstBetter(first, second); - details::ComparisonResult reverseCompare = comparator->IsFirstBetter(second, first); - - // Should not happen, but if it does it points at a serious bug that should be fixed. - if (forwardCompare != details::ComparisonResult::Negative && reverseCompare != details::ComparisonResult::Negative) - { - AICLI_LOG(CLI, Error, << "Installer " << first << " and " << second << " are both better than each other?"); - THROW_HR(E_UNEXPECTED); - } - - if (forwardCompare == details::ComparisonResult::StrongPositive) - { - AICLI_LOG(CLI, Verbose, << "Installer " << first << " is better [strong] than " << second << " due to: " << comparator->Name()); - return true; - } - - if (reverseCompare == details::ComparisonResult::StrongPositive) - { - // Second is better by this comparator, don't allow a lower priority one to override that. - AICLI_LOG(CLI, Verbose, << "Installer " << second << " is better [strong] than " << first << " due to: " << comparator->Name()); - return false; - } - - // Save the first weak result that we get - if (!firstWeakComparator) - { - if (forwardCompare == details::ComparisonResult::WeakPositive) - { - firstWeakComparator = comparator->Name(); - firstWeakComparatorResult = true; - } - else if (reverseCompare == details::ComparisonResult::WeakPositive) - { - firstWeakComparator = comparator->Name(); - firstWeakComparatorResult = false; - } - } - } - - // If we found a weak result (and no strong result because we made it here), return it. - if (firstWeakComparator) - { - if (firstWeakComparatorResult) - { - AICLI_LOG(CLI, Verbose, << "Installer " << first << " is better [weak] than " << second << " due to: " << *firstWeakComparator); - } - else - { - AICLI_LOG(CLI, Verbose, << "Installer " << second << " is better [weak] than " << first << " due to: " << *firstWeakComparator); - } - - return firstWeakComparatorResult; - } - - // Equal, and thus not better - AICLI_LOG(CLI, Verbose, << "Installer " << first << " and " << second << " are equivalent in priority"); - return false; - } - - void ManifestComparator::AddFilter(std::unique_ptr&& filter) - { - if (filter) - { - m_filters.emplace_back(std::move(filter)); - } - } - - void ManifestComparator::AddComparator(std::unique_ptr&& comparator) - { - if (comparator) - { - m_comparators.push_back(comparator.get()); - m_filters.emplace_back(std::move(comparator)); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include +#include +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Manifest +{ + std::ostream& operator<<(std::ostream& out, const ManifestInstaller& installer) + { + return out << '[' << + AppInstaller::Utility::ToString(installer.Arch) << ',' << + AppInstaller::Manifest::InstallerTypeToString(installer.EffectiveInstallerType()) << ',' << + AppInstaller::Manifest::ScopeToString(installer.Scope) << ',' << + installer.Locale << ']'; + } +} + +namespace AppInstaller::Manifest +{ + namespace + { + struct PortableInstallFilter : public details::FilterField + { + PortableInstallFilter() : details::FilterField("Portable Install") {} + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + // Unvirtualized resources restricted capability is only supported for >= 10.0.18362 + // TODO: Add support for OS versions that don't support virtualization. + if (installer.EffectiveInstallerType() == InstallerTypeEnum::Portable && !Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version("10.0.18362"))) + { + return InapplicabilityFlags::OSVersion; + } + + return InapplicabilityFlags::None; + } + + std::string ExplainInapplicable(const ManifestInstaller&) override + { + std::string result = "Current OS is lower than supported MinOSVersion (10.0.18362) for Portable install"; + return result; + } + }; + + struct OSVersionFilter : public details::FilterField + { + OSVersionFilter() : details::FilterField("OS Version") {} + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + if (installer.MinOSVersion.empty() || Runtime::IsCurrentOSVersionGreaterThanOrEqual(Utility::Version(installer.MinOSVersion))) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::OSVersion; + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result = "Current OS is lower than MinOSVersion "; + result += installer.MinOSVersion; + return result; + } + }; + + struct MachineArchitectureComparator : public details::ComparisonField + { + MachineArchitectureComparator() : details::ComparisonField("Machine Architecture") {} + + MachineArchitectureComparator(std::vector allowedArchitectures) : + details::ComparisonField("Machine Architecture"), m_allowedArchitectures(std::move(allowedArchitectures)) + { + AICLI_LOG(CLI, Verbose, << "Architecture Comparator created with allowed architectures: " << Utility::ConvertContainerToString(m_allowedArchitectures, Utility::ToString)); + } + + static std::unique_ptr Create(const ManifestComparator::Options& options) + { + if (!options.AllowedArchitectures.empty()) + { + // If the incoming data contains elements, we will use them to construct a final allowed list. + // The algorithm is to take elements until we find Unknown, which indicates that any architecture is + // acceptable at this point. The system supported set of architectures will then be placed at the end. + std::vector result; + bool addRemainingApplicableArchitectures = false; + + for (Utility::Architecture architecture : options.AllowedArchitectures) + { + if (architecture == Utility::Architecture::Unknown) + { + addRemainingApplicableArchitectures = true; + break; + } + + // If the architecture is applicable and not already in our result set... + if ((options.SkipApplicabilityCheck || Utility::IsApplicableArchitecture(architecture) != Utility::InapplicableArchitecture) && + Utility::IsApplicableArchitecture(architecture, result) == Utility::InapplicableArchitecture) + { + result.push_back(architecture); + } + } + + if (addRemainingApplicableArchitectures) + { + for (Utility::Architecture architecture : Utility::GetApplicableArchitectures()) + { + if (Utility::IsApplicableArchitecture(architecture, result) == Utility::InapplicableArchitecture) + { + result.push_back(architecture); + } + } + } + + return std::make_unique(std::move(result)); + } + else + { + return std::make_unique(); + } + } + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + if (CheckAllowedArchitecture(installer.Arch) == Utility::InapplicableArchitecture || + IsSystemArchitectureUnsupportedByInstaller(installer)) + { + return InapplicabilityFlags::MachineArchitecture; + } + + return InapplicabilityFlags::None; + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result; + if (Utility::IsApplicableArchitecture(installer.Arch) == Utility::InapplicableArchitecture) + { + result = "Machine is not compatible with "; + result += Utility::ToString(installer.Arch); + } + else if (IsSystemArchitectureUnsupportedByInstaller(installer)) + { + result = "System architecture is unsupported by installer"; + } + else + { + result = "Architecture was excluded by caller : "; + result += Utility::ToString(installer.Arch); + } + + return result; + } + + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override + { + auto arch1 = CheckAllowedArchitecture(first.Arch); + auto arch2 = CheckAllowedArchitecture(second.Arch); + + if (arch1 > arch2) + { + // A match with the primary architecture is strong + return (first.Arch == GetStrongArchitectureMatch() ? details::ComparisonResult::StrongPositive : details::ComparisonResult::WeakPositive); + } + + return details::ComparisonResult::Negative; + } + + private: + int CheckAllowedArchitecture(Utility::Architecture architecture) + { + if (m_allowedArchitectures.empty()) + { + return Utility::IsApplicableArchitecture(architecture); + } + else + { + return Utility::IsApplicableArchitecture(architecture, m_allowedArchitectures); + } + } + + bool IsSystemArchitectureUnsupportedByInstaller(const ManifestInstaller& installer) + { + auto unsupportedItr = std::find( + installer.UnsupportedOSArchitectures.begin(), + installer.UnsupportedOSArchitectures.end(), + Utility::GetSystemArchitecture()); + return unsupportedItr != installer.UnsupportedOSArchitectures.end(); + } + + Utility::Architecture GetStrongArchitectureMatch() + { + // If we have a preferential order, treat the first entry as strong. + // Otherwise, treat the system architecture as strong (which is always first in the default order). + return m_allowedArchitectures.empty() ? Utility::GetSystemArchitecture() : m_allowedArchitectures.front(); + } + + std::vector m_allowedArchitectures; + }; + + struct InstallerTypeComparator : public details::ComparisonField + { + InstallerTypeComparator(std::vector preference, std::vector requirement) : + details::ComparisonField("Installer Type"), m_preference(std::move(preference)), m_requirement(std::move(requirement)) + { + m_preferenceAsString = Utility::ConvertContainerToString(m_preference, InstallerTypeToString); + m_requirementAsString = Utility::ConvertContainerToString(m_requirement, InstallerTypeToString); + AICLI_LOG(CLI, Verbose, + << "InstallerType Comparator created with Required InstallerTypes: " << m_requirementAsString + << " , Preferred InstallerTypes: " << m_preferenceAsString); + } + + static std::unique_ptr Create(const ManifestComparator::Options& options) + { + std::vector preference; + std::vector requirement; + + if (options.RequestedInstallerType) + { + requirement.emplace_back(options.RequestedInstallerType.value()); + } + else + { + preference = Settings::User().Get(); + requirement = Settings::User().Get(); + + // Apply default precedence order when the user has not configured any installer type preferences or requirements. + if (preference.empty() && requirement.empty()) + { + preference = { + InstallerTypeEnum::MSStore, + InstallerTypeEnum::Msix, + InstallerTypeEnum::Msi, + InstallerTypeEnum::Wix, + InstallerTypeEnum::Burn, + InstallerTypeEnum::Nullsoft, + InstallerTypeEnum::Inno, + InstallerTypeEnum::Exe, + InstallerTypeEnum::Portable, + }; + } + } + + if (!preference.empty() || !requirement.empty()) + { + return std::make_unique(preference, requirement); + } + else + { + return {}; + } + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result = "InstallerType ["; + result += InstallerTypeToString(installer.EffectiveInstallerType()); + result += "] does not match required InstallerTypes: "; + result += m_requirementAsString; + return result; + } + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + if (!m_requirement.empty()) + { + // The installer is applicable if the effective or base installer type matches. + if (ContainsInstallerType(m_requirement, installer.EffectiveInstallerType()) || + ContainsInstallerType(m_requirement, installer.BaseInstallerType)) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::InstallerType; + } + else + { + return InapplicabilityFlags::None; + } + } + + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override + { + // If no preferences are set, use requirement ordering instead. + const auto& effectiveOrder = m_preference.empty() ? m_requirement : m_preference; + + if (effectiveOrder.empty()) + { + return details::ComparisonResult::Negative; + } + + for (InstallerTypeEnum installerTypePreference : effectiveOrder) + { + bool isFirstInstallerTypePreferred = + first.EffectiveInstallerType() == installerTypePreference || + first.BaseInstallerType == installerTypePreference; + + bool isSecondInstallerTypePreferred = + second.EffectiveInstallerType() == installerTypePreference || + second.BaseInstallerType == installerTypePreference; + + if (isFirstInstallerTypePreferred && isSecondInstallerTypePreferred) + { + return details::ComparisonResult::Negative; + } + else if (isFirstInstallerTypePreferred != isSecondInstallerTypePreferred) + { + // Treating this as a weak positive because one can use requirements to guarantee the installer type if necessary. + return (isFirstInstallerTypePreferred ? details::ComparisonResult::WeakPositive : details::ComparisonResult::Negative); + } + } + + return details::ComparisonResult::Negative; + } + + private: + std::vector m_preference; + std::vector m_requirement; + std::string m_preferenceAsString; + std::string m_requirementAsString; + + bool ContainsInstallerType(const std::vector& selection, InstallerTypeEnum installerType) + { + return std::find(selection.begin(), selection.end(), installerType) != selection.end(); + } + }; + + struct InstalledTypeFilter : public details::FilterField + { + InstalledTypeFilter(InstallerTypeEnum installedType) : + details::FilterField("Installed Type"), m_installedType(installedType) {} + + static std::unique_ptr Create(const ManifestComparator::Options& options) + { + if (options.CurrentlyInstalledType) + { + InstallerTypeEnum installedType = options.CurrentlyInstalledType.value(); + if (installedType != InstallerTypeEnum::Unknown) + { + return std::make_unique(installedType); + } + } + + return {}; + } + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + return IsInstallerCompatibleWith(installer, m_installedType) ? InapplicabilityFlags::None : InapplicabilityFlags::InstalledType; + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result = "Installed package type '" + std::string{ InstallerTypeToString(m_installedType) } + + "' is not compatible with installer type " + std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }; + + std::string arpInstallerTypes; + for (const auto& entry : installer.AppsAndFeaturesEntries) + { + arpInstallerTypes += " " + std::string{ InstallerTypeToString(entry.InstallerType) }; + } + + if (!arpInstallerTypes.empty()) + { + result += ", or with accepted type(s)" + arpInstallerTypes; + } + + return result; + } + + private: + // The installer is compatible if it's type or any of its ARP entries' type matches the installed type + static bool IsInstallerCompatibleWith(const ManifestInstaller& installer, InstallerTypeEnum type) + { + if (IsInstallerTypeCompatible(installer.EffectiveInstallerType(), type)) + { + return true; + } + + auto itr = std::find_if( + installer.AppsAndFeaturesEntries.begin(), + installer.AppsAndFeaturesEntries.end(), + [=](AppsAndFeaturesEntry arpEntry) { return IsInstallerTypeCompatible(arpEntry.InstallerType, type); }); + if (itr != installer.AppsAndFeaturesEntries.end()) + { + return true; + } + + return false; + } + + InstallerTypeEnum m_installedType; + }; + + struct InstalledScopeFilter : public details::FilterField + { + InstalledScopeFilter(ScopeEnum requirement) : + details::FilterField("Installed Scope"), m_requirement(requirement) {} + + static std::unique_ptr Create(const ManifestComparator::Options& options) + { + // Check for an existing install and require a matching scope. + if (options.CurrentlyInstalledScope) + { + ScopeEnum installedScope = options.CurrentlyInstalledScope.value(); + if (installedScope != ScopeEnum::Unknown) + { + return std::make_unique(installedScope); + } + } + + return {}; + } + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + // We have to assume the unknown scope will match our required scope, or the entire catalog would stop working for upgrade. + if (installer.Scope == ScopeEnum::Unknown || installer.Scope == m_requirement || DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::InstalledScope; + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result = "Installer scope does not match currently installed scope: "; + result += ScopeToString(installer.Scope); + result += " != "; + result += ScopeToString(m_requirement); + return result; + } + + private: + ScopeEnum m_requirement; + }; + + struct ScopeComparator : public details::ComparisonField + { + ScopeComparator(ScopeEnum preference, ScopeEnum requirement, bool allowUnknownInAdditionToRequired) : + details::ComparisonField("Scope"), m_preference(preference), m_requirement(requirement), m_allowUnknownInAdditionToRequired(allowUnknownInAdditionToRequired) {} + + static std::unique_ptr Create(const ManifestComparator::Options& options) + { + // Preference will always come from settings + ScopeEnum preference = Settings::User().Get(); + + // Requirement may come from args or settings; args overrides settings. + ScopeEnum requirement = ScopeEnum::Unknown; + + if (options.RequestedInstallerScope) + { + requirement = options.RequestedInstallerScope.value(); + } + else + { + requirement = Settings::User().Get(); + } + + bool allowUnknownInAdditionToRequired = false; + if (options.AllowUnknownScope) + { + allowUnknownInAdditionToRequired = options.AllowUnknownScope.value(); + + // Force the required type to be preferred over Unknown + if (requirement != ScopeEnum::Unknown) + { + preference = requirement; + } + } + + if (preference != ScopeEnum::Unknown || requirement != ScopeEnum::Unknown) + { + return std::make_unique(preference, requirement, allowUnknownInAdditionToRequired); + } + else + { + return {}; + } + } + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + // Applicable if one of: + // 1. No requirement (aka is Unknown) + // 2. Requirement met + // 3. Installer scope is Unknown and this has been explicitly allowed + // 4. The installer type is scope agnostic (we can control it) + if (m_requirement == ScopeEnum::Unknown || + installer.Scope == m_requirement || + (installer.Scope == ScopeEnum::Unknown && m_allowUnknownInAdditionToRequired) || + DoesInstallerTypeIgnoreScopeFromManifest(installer.EffectiveInstallerType())) + { + return InapplicabilityFlags::None; + } + + return InapplicabilityFlags::Scope; + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result = "Installer scope does not match required scope: "; + result += ScopeToString(installer.Scope); + result += " != "; + result += ScopeToString(m_requirement); + return result; + } + + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override + { + if (m_preference != ScopeEnum::Unknown && first.Scope == m_preference && second.Scope != m_preference) + { + // When the second input is unknown, this is a weak result. If it is not (and therefore the opposite of the preference), this is strong. + return (second.Scope == ScopeEnum::Unknown ? details::ComparisonResult::WeakPositive : details::ComparisonResult::StrongPositive); + } + + return details::ComparisonResult::Negative; + } + + private: + ScopeEnum m_preference; + ScopeEnum m_requirement; + bool m_allowUnknownInAdditionToRequired; + }; + + struct LocaleComparator : public details::ComparisonField + { + LocaleComparator(std::vector preference, std::vector requirement, bool isInstalledLocale) : + details::ComparisonField("Locale"), m_preference(std::move(preference)), m_requirement(std::move(requirement)), m_isInstalledLocale(isInstalledLocale) + { + m_requirementAsString = Utility::ConvertContainerToString(m_requirement); + m_preferenceAsString = Utility::ConvertContainerToString(m_preference); + AICLI_LOG(CLI, Verbose, + << "Locale Comparator created with Required Locales: " << m_requirementAsString + << " , Preferred Locales: " << m_preferenceAsString + << " , IsInstalledLocale: " << m_isInstalledLocale); + } + + static std::unique_ptr Create(const ManifestComparator::Options& options) + { + std::vector preference; + std::vector requirement; + // This is for installed locale case, where the locale is a preference but requires at least compatible match. + bool isInstalledLocale = false; + + // Requirement may come from args, previous user intent or settings; args overrides previous user intent then settings. + if (options.RequestedInstallerLocale) + { + requirement.emplace_back(options.RequestedInstallerLocale.value()); + } + else if (options.PreviousUserIntentLocale) + { + requirement.emplace_back(options.PreviousUserIntentLocale.value()); + isInstalledLocale = true; + } + else + { + if (!options.CurrentlyInstalledLocale) + { + // If it's an upgrade of previous package, no need to set requirements from settings + // as previous installed locale will be used later. + requirement = Settings::User().Get(); + } + } + + // Preference will come from previous installed locale, winget settings or Preferred Languages settings. + // Previous installed locale goes first, then winget settings, then Preferred Languages settings. + // Previous installed locale also requires at least compatible locale match. + if (options.CurrentlyInstalledLocale) + { + preference.emplace_back(options.CurrentlyInstalledLocale.value()); + isInstalledLocale = true; + } + else + { + preference = Settings::User().Get(); + if (preference.empty()) + { + preference = Locale::GetUserPreferredLanguages(); + } + } + + if (!preference.empty() || !requirement.empty()) + { + return std::make_unique(preference, requirement, isInstalledLocale); + } + else + { + return {}; + } + } + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + InapplicabilityFlags inapplicableFlag = m_isInstalledLocale ? InapplicabilityFlags::InstalledLocale : InapplicabilityFlags::Locale; + + if (!m_requirement.empty()) + { + // Check if requirement is satisfied + for (auto const& requiredLocale : m_requirement) + { + if (Locale::GetDistanceOfLanguage(requiredLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsPerfectMatch) + { + return InapplicabilityFlags::None; + } + } + + return inapplicableFlag; + } + else if (m_isInstalledLocale && !m_preference.empty()) + { + // For installed locale preference, check at least compatible match for preference + for (auto const& preferredLocale : m_preference) + { + // We have to assume an unknown installer locale will match our installed locale, or the entire catalog would stop working for upgrade. + if (installer.Locale.empty() || + Locale::GetDistanceOfLanguage(preferredLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch) + { + return InapplicabilityFlags::None; + } + } + + return inapplicableFlag; + } + else + { + return InapplicabilityFlags::None; + } + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result = "Installer locale does not match required locale: "; + result += installer.Locale; + result += "Required locales: "; + result += m_requirementAsString; + result += " Or does not satisfy compatible match for Preferred Locales: "; + result += m_preferenceAsString; + return result; + } + + details::ComparisonResult IsFirstBetter(const ManifestInstaller& first, const ManifestInstaller& second) override + { + if (m_preference.empty()) + { + return details::ComparisonResult::Negative; + } + + for (auto const& preferredLocale : m_preference) + { + double firstScore = first.Locale.empty() ? Locale::UnknownLanguageDistanceScore : Locale::GetDistanceOfLanguage(preferredLocale, first.Locale); + double secondScore = second.Locale.empty() ? Locale::UnknownLanguageDistanceScore : Locale::GetDistanceOfLanguage(preferredLocale, second.Locale); + + if (firstScore >= Locale::MinimumDistanceScoreAsCompatibleMatch || secondScore >= Locale::MinimumDistanceScoreAsCompatibleMatch) + { + // This could probably be enriched to always check all locales and determine strong/weak based off of the MinimumDistanceScoreAsCompatibleMatch. + return (firstScore > secondScore ? details::ComparisonResult::StrongPositive : details::ComparisonResult::Negative); + } + } + + // At this point, the installer locale matches no preference. + // if first is unknown and second is no match for sure, we might prefer unknown one. + return (first.Locale.empty() && !second.Locale.empty() ? details::ComparisonResult::WeakPositive : details::ComparisonResult::Negative); + } + + private: + std::vector m_preference; + std::vector m_requirement; + std::string m_requirementAsString; + std::string m_preferenceAsString; + bool m_isInstalledLocale = false; + }; + + struct MarketFilter : public details::FilterField + { + MarketFilter(Manifest::string_t market) : details::FilterField("Market"), m_market(market) + { + AICLI_LOG(CLI, Verbose, << "Market Filter created with market: " << m_market); + } + + static std::unique_ptr Create() + { + return std::make_unique(Runtime::GetOSRegion()); + } + + InapplicabilityFlags IsApplicable(const ManifestInstaller& installer) override + { + // If both allowed and excluded lists are provided, we only need to check the allowed markets. + if (!installer.Markets.AllowedMarkets.empty()) + { + // Inapplicable if NOT found + if (!IsMarketInList(installer.Markets.AllowedMarkets)) + { + return InapplicabilityFlags::Market; + } + } + else if (!installer.Markets.ExcludedMarkets.empty()) + { + // Inapplicable if found + if (IsMarketInList(installer.Markets.ExcludedMarkets)) + { + return InapplicabilityFlags::Market; + } + } + + return InapplicabilityFlags::None; + } + + std::string ExplainInapplicable(const ManifestInstaller& installer) override + { + std::string result = "Current market '" + m_market + "' does not match installer markets." + + " Allowed markets: " + Utility::ConvertContainerToString(installer.Markets.AllowedMarkets) + + " Excluded markets: " + Utility::ConvertContainerToString(installer.Markets.ExcludedMarkets); + return result; + } + + private: + bool IsMarketInList(const std::vector markets) + { + return markets.end() != std::find_if( + markets.begin(), + markets.end(), + [&](const auto& m) { return Utility::CaseInsensitiveEquals(m, m_market); }); + } + + Manifest::string_t m_market; + }; + } + + ManifestComparator::ManifestComparator(const Options& options) + { + // Filters based on installer's MinOSVersion + AddFilter(std::make_unique()); + // Filters out portable installers if they are not supported by the system + AddFilter(std::make_unique()); + // Filters based on the scope of a currently installed package + AddFilter(InstalledScopeFilter::Create(options)); + // Filters based on the market region of the system + AddFilter(MarketFilter::Create()); + // Filters based on the installer type compatability, including with AppsAndFeaturesEntry declarations + AddFilter(InstalledTypeFilter::Create(options)); + + // Filter order is not important, but comparison order determines priority. + // Note that all comparators are also filters and their comparison function will only be called on + // installers that both match the required criteria. + // + // The comparators are ordered by the `IsFirstBetter` method, which uses the following algorithm: + // - Each comparison between two installers can return one of { Strong, Weak, Negative } + // - Installers are compared in both directions, going through the list of comparators as defined here + // - The first Strong result in either direction is given priority + // - If no Strong results, the first Weak result is used + // - If all Negative results, then the two installers are equal in priority (meaning the first one in the list is kept as "better") + // + // TODO: There are improvements to be made here around ordering, especially in the context of implicit vs explicit vs command line preferences. + + // Filters based on exact matches for requirements or compatible matches for preferences + // Only applies when preference exists: + // Strong if first is compatible and better match than second + // Weak if first is unknown and second is not + AddComparator(LocaleComparator::Create(options)); + // Filters only if a requirement is present and it cannot be satisfied by the installer (including installer types that we can control scope in code) + // Only applies when preference exists: + // Strong if first matches preference and second does not and is not Unknown + // Weak if first matches preference and second is Unknown + AddComparator(ScopeComparator::Create(options)); + // Filters architectures out that are not supported or are not in the preferences/requirements/inputs. + // Strong if first equals the earliest architecture in the allowed list and second does not [default means the system architecture] + // Weak if first is better match for system architecture than second + AddComparator(MachineArchitectureComparator::Create(options)); + // Filters installer types out that are not in preferences or requirements. + // Only applies when preference exists: + // Weak if first is in preference list and second is not + AddComparator(InstallerTypeComparator::Create(options)); + } + + InstallerAndInapplicabilities ManifestComparator::GetPreferredInstaller(const Manifest& manifest) + { + AICLI_LOG(CLI, Verbose, << "Starting installer selection."); + + const ManifestInstaller* result = nullptr; + std::vector inapplicabilitiesInstallers; + + for (const auto& installer : manifest.Installers) + { + auto inapplicabilityInstaller = IsApplicable(installer); + if (inapplicabilityInstaller == InapplicabilityFlags::None) + { + if (!result || IsFirstBetter(installer, *result)) + { + AICLI_LOG(CLI, Verbose, << "Installer " << installer << " is current best choice"); + result = &installer; + } + } + else + { + inapplicabilitiesInstallers.push_back(inapplicabilityInstaller); + } + } + + if (!result) + { + return { {}, std::move(inapplicabilitiesInstallers) }; + } + + return { *result, std::move(inapplicabilitiesInstallers) }; + } + + InapplicabilityFlags ManifestComparator::IsApplicable(const ManifestInstaller& installer) + { + InapplicabilityFlags inapplicabilityResult = InapplicabilityFlags::None; + + for (const auto& filter : m_filters) + { + auto inapplicability = filter->IsApplicable(installer); + if (inapplicability != InapplicabilityFlags::None) + { + AICLI_LOG(CLI, Verbose, << "Installer " << installer << " not applicable: " << filter->ExplainInapplicable(installer)); + WI_SetAllFlags(inapplicabilityResult, inapplicability); + } + } + + return inapplicabilityResult; + } + + bool ManifestComparator::IsFirstBetter( + const ManifestInstaller& first, + const ManifestInstaller& second) + { + // The priority will still be used as a tie-break between weak results. + std::optional firstWeakComparator; + bool firstWeakComparatorResult = false; + + for (auto comparator : m_comparators) + { + details::ComparisonResult forwardCompare = comparator->IsFirstBetter(first, second); + details::ComparisonResult reverseCompare = comparator->IsFirstBetter(second, first); + + // Should not happen, but if it does it points at a serious bug that should be fixed. + if (forwardCompare != details::ComparisonResult::Negative && reverseCompare != details::ComparisonResult::Negative) + { + AICLI_LOG(CLI, Error, << "Installer " << first << " and " << second << " are both better than each other?"); + THROW_HR(E_UNEXPECTED); + } + + if (forwardCompare == details::ComparisonResult::StrongPositive) + { + AICLI_LOG(CLI, Verbose, << "Installer " << first << " is better [strong] than " << second << " due to: " << comparator->Name()); + return true; + } + + if (reverseCompare == details::ComparisonResult::StrongPositive) + { + // Second is better by this comparator, don't allow a lower priority one to override that. + AICLI_LOG(CLI, Verbose, << "Installer " << second << " is better [strong] than " << first << " due to: " << comparator->Name()); + return false; + } + + // Save the first weak result that we get + if (!firstWeakComparator) + { + if (forwardCompare == details::ComparisonResult::WeakPositive) + { + firstWeakComparator = comparator->Name(); + firstWeakComparatorResult = true; + } + else if (reverseCompare == details::ComparisonResult::WeakPositive) + { + firstWeakComparator = comparator->Name(); + firstWeakComparatorResult = false; + } + } + } + + // If we found a weak result (and no strong result because we made it here), return it. + if (firstWeakComparator) + { + if (firstWeakComparatorResult) + { + AICLI_LOG(CLI, Verbose, << "Installer " << first << " is better [weak] than " << second << " due to: " << *firstWeakComparator); + } + else + { + AICLI_LOG(CLI, Verbose, << "Installer " << second << " is better [weak] than " << first << " due to: " << *firstWeakComparator); + } + + return firstWeakComparatorResult; + } + + // Equal, and thus not better + AICLI_LOG(CLI, Verbose, << "Installer " << first << " and " << second << " are equivalent in priority"); + return false; + } + + void ManifestComparator::AddFilter(std::unique_ptr&& filter) + { + if (filter) + { + m_filters.emplace_back(std::move(filter)); + } + } + + void ManifestComparator::AddComparator(std::unique_ptr&& comparator) + { + if (comparator) + { + m_comparators.push_back(comparator.get()); + m_filters.emplace_back(std::move(comparator)); + } + } +} diff --git a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp index 878afd0f7d..c3821093c0 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp @@ -1,462 +1,462 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Yaml.h" -#include "winget/JsonSchemaValidation.h" -#include "winget/ManifestCommon.h" -#include "winget/ManifestSchemaValidation.h" -#include "winget/ManifestYamlParser.h" -#include "winget/Resources.h" - -#include - -namespace AppInstaller::Manifest::YamlParser -{ - using namespace std::string_view_literals; - - namespace - { - enum class YamlScalarType - { - String, - Int, - Bool - }; - - // List of fields that use non string scalar types - const std::map ManifestFieldTypes = - { - { "InstallerSuccessCodes"sv, YamlScalarType::Int }, - { "InstallerAbortsTerminal"sv, YamlScalarType::Bool }, - { "InstallLocationRequired"sv, YamlScalarType::Bool }, - { "RequireExplicitUpgrade"sv, YamlScalarType::Bool }, - { "DisplayInstallWarnings"sv, YamlScalarType::Bool }, - { "InstallerReturnCode"sv, YamlScalarType::Int }, - { "DownloadCommandProhibited", YamlScalarType::Bool }, - { "ArchiveBinariesDependOnPath", YamlScalarType::Bool } - }; - - YamlScalarType GetManifestScalarValueType(const std::string& key) - { - auto iter = ManifestFieldTypes.find(key); - if (iter != ManifestFieldTypes.end()) - { - return iter->second; - } - - return YamlScalarType::String; - } - - Json::Value YamlScalarNodeToJson(const YAML::Node& scalarNode, YamlScalarType scalarType) - { - if (scalarType == YamlScalarType::Int) - { - return Json::Value(scalarNode.as()); - } - else if (scalarType == YamlScalarType::Bool) - { - return Json::Value(scalarNode.as()); - } - else - { - return Json::Value(scalarNode.as()); - } - } - - Json::Value ManifestYamlNodeToJson(const YAML::Node& rootNode, YamlScalarType scalarType = YamlScalarType::String) - { - Json::Value result; - - if (rootNode.IsNull()) - { - result = Json::Value::nullSingleton(); - } - else if (rootNode.IsMap()) - { - for (auto const& keyValuePair : rootNode.Mapping()) - { - // We only support string type as key in our manifest - auto key = keyValuePair.first.as(); - result[keyValuePair.first.as()] = ManifestYamlNodeToJson(keyValuePair.second, GetManifestScalarValueType(key)); - } - } - else if (rootNode.IsSequence()) - { - for (auto const& value : rootNode.Sequence()) - { - result.append(ManifestYamlNodeToJson(value, scalarType)); - } - } - else if (rootNode.IsScalar()) - { - result = YamlScalarNodeToJson(rootNode, scalarType); - } - else - { - THROW_HR(E_UNEXPECTED); - } - - return result; - } - - std::vector ParseSchemaHeaderString(const YamlManifestInfo& manifestInfo, const ValidationError::Level& errorLevel, std::string& schemaHeaderUrlString) - { - std::vector errors; - std::string schemaHeader = manifestInfo.DocumentSchemaHeader.SchemaHeader; - - // Remove the leading '#' and any leading/trailing whitespaces - if (schemaHeader[0] == '#') - { - schemaHeader = schemaHeader.substr(1); // Remove the leading '#' - schemaHeader = Utility::Trim(schemaHeader); // Trim leading/trailing whitespaces - } - - // Parse the schema header string as YAML string to get the schema header URL - try - { - auto root = YAML::Load(schemaHeader); - - if (root.IsNull() || (!root.IsNull() && !root.IsDefined())) - { - errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName)); - } - else - { - schemaHeaderUrlString = root[YAML::DocumentSchemaHeader::YamlLanguageServerKey].as(); - } - } - catch (const YAML::Exception&) - { - errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName)); - } - catch (const std::exception&) - { - errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName)); - } - - return errors; - } - - bool ParseSchemaHeaderUrl(const std::string& schemaHeaderValue, std::string& schemaType, std::string& schemaVersion) - { - // Use regex to match the pattern of @"winget-manifest\.(?\w+)\.(?[\d\.]+)\.schema\.json$" - std::regex schemaUrlPattern(R"(winget-manifest\.(\w+)\.([\d\.]+)\.schema\.json$)"); - std::smatch match; - - if (std::regex_search(schemaHeaderValue, match, schemaUrlPattern)) - { - schemaType = match[1].str(); - schemaVersion = match[2].str(); - return true; - } - - return false; - } - - std::vector ValidateSchemaHeaderType(const std::string& headerManifestType, const ManifestTypeEnum& expectedManifestType, const YamlManifestInfo& manifestInfo, ValidationError::Level errorLevel) - { - std::vector errors; - ManifestTypeEnum actualManifestType = ConvertToManifestTypeEnum(headerManifestType); - size_t schemaHeaderTypeIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(headerManifestType) + 1; - - if (actualManifestType != expectedManifestType) - { - errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderManifestTypeMismatch, "", headerManifestType, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderTypeIndex, errorLevel, manifestInfo.FileName)); - } - - return errors; - } - - std::vector ValidateSchemaHeaderVersion(const std::string& headerManifestVersion, const ManifestVer& expectedManifestVersion, const YamlManifestInfo& manifestInfo, ValidationError::Level errorLevel) - { - std::vector errors; - ManifestVer actualHeaderVersion(headerManifestVersion); - size_t schemaHeaderVersionIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(headerManifestVersion) + 1; - - if (actualHeaderVersion != expectedManifestVersion) - { - errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderManifestVersionMismatch, "", headerManifestVersion, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderVersionIndex, errorLevel, manifestInfo.FileName)); - } - - return errors; - } - - bool IsValidSchemaHeaderUrl(const std::string& schemaHeaderUrlString, const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion) - { - // Load the schema file to compare the schema header URL with the schema ID in the schema file - Json::Value schemaFile = LoadSchemaDoc(manifestVersion, manifestInfo.ManifestType); - - if (schemaFile.isMember("$id")) - { - std::string schemaId = schemaFile["$id"].asString(); - - // Prefix schema ID with "schema=" to match the schema header URL pattern and compare it with the schema header URL - schemaId = "$schema=" + schemaId; - - if (Utility::CaseInsensitiveEquals(schemaId, schemaHeaderUrlString)) - { - return true; - } - } - - return false; - } - - ValidationError GetSchemaHeaderUrlPatternMismatchError(const std::string& schemaHeaderUrlString, const YamlManifestInfo& manifestInfo, const ValidationError::Level& errorLevel) - { - size_t schemaHeaderUrlIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(schemaHeaderUrlString) + 1; - - return ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderUrlPatternMismatch, "", manifestInfo.DocumentSchemaHeader.SchemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderUrlIndex, errorLevel, manifestInfo.FileName); - } - - std::vector ValidateSchemaHeaderUrl(const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion, const ValidationError::Level& errorLevel) - { - std::vector errors; - - std::string schemaHeaderUrlString; - // Parse the schema header string to get the schema header URL - auto parserErrors = ParseSchemaHeaderString(manifestInfo, errorLevel, schemaHeaderUrlString); - std::move(parserErrors.begin(), parserErrors.end(), std::inserter(errors, errors.end())); - - if (!errors.empty()) - { - return errors; - } - - std::string manifestTypeString; - std::string manifestVersionString; - - // Parse the schema header URL to get the manifest type and version - if (ParseSchemaHeaderUrl(schemaHeaderUrlString, manifestTypeString, manifestVersionString)) - { - auto headerManifestTypeErrors = ValidateSchemaHeaderType(manifestTypeString, manifestInfo.ManifestType, manifestInfo, errorLevel); - std::move(headerManifestTypeErrors.begin(), headerManifestTypeErrors.end(), std::inserter(errors, errors.end())); - - auto headerManifestVersionErrors = ValidateSchemaHeaderVersion(manifestVersionString, manifestVersion, manifestInfo, errorLevel); - std::move(headerManifestVersionErrors.begin(), headerManifestVersionErrors.end(), std::inserter(errors, errors.end())); - - // Finally, match the entire schema header URL with the schema ID in the schema file to ensure the URL domain matches the schema definition file. - if (!IsValidSchemaHeaderUrl(schemaHeaderUrlString, manifestInfo, manifestVersion)) - { - errors.emplace_back(GetSchemaHeaderUrlPatternMismatchError(schemaHeaderUrlString, manifestInfo, errorLevel)); - } - } - else - { - errors.emplace_back(GetSchemaHeaderUrlPatternMismatchError(schemaHeaderUrlString, manifestInfo, errorLevel)); - } - - return errors; - } - - std::vector ValidateYamlManifestSchemaHeader(const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion, ValidationError::Level errorLevel) - { - std::vector errors; - std::string schemaHeaderString; - - if (manifestInfo.DocumentSchemaHeader.SchemaHeader.empty()) - { - errors.emplace_back(ValidationError::MessageLevelWithFile(ManifestError::SchemaHeaderNotFound, errorLevel, manifestInfo.FileName)); - return errors; - } - - auto parserErrors = ValidateSchemaHeaderUrl(manifestInfo, manifestVersion, errorLevel); - std::move(parserErrors.begin(), parserErrors.end(), std::inserter(errors, errors.end())); - - return errors; - } - } - - Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType) - { - int idx = MANIFESTSCHEMA_NO_RESOURCE; - std::map resourceMap; - - if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_28 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_28_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_28_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_28_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_28_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_12 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_12_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_12_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_12_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_12_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_10 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_10_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_10_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_10_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_10_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_9 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_9_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_9_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_9_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_9_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_9_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_7 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_7_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_7_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_7_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_7_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_7_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_6 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_6_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_6_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_6_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_6_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_6_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_5 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_5_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_5_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_5_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_5_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_5_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_4 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_4_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_4_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_4_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_4_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_4_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_2 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_2_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_2_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_2_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_2_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_2_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_1 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_1_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_1_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_1_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_1_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_1_LOCALE }, - }; - } - else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) - { - resourceMap = { - { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_SINGLETON }, - { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_VERSION }, - { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_INSTALLER }, - { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE }, - { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_LOCALE }, - }; - } - else - { - resourceMap = { - { ManifestTypeEnum::Preview, IDX_MANIFEST_SCHEMA_PREVIEW }, - }; - } - - auto iter = resourceMap.find(manifestType); - if (iter != resourceMap.end()) - { - idx = iter->second; - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - - std::string_view schemaStr = Resource::GetResourceAsString(idx, MANIFESTSCHEMA_RESOURCE_TYPE); - return JsonSchema::LoadSchemaDoc(schemaStr); - } - - std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion) - { - std::vector errors; - // A list of schema validator to avoid multiple loadings of same schema - std::map schemaList; - - for (const auto& entry : manifestList) - { - if (entry.ManifestType == ManifestTypeEnum::Shadow) - { - // There's no schema for a shadow manifest. - continue; - } - - if (schemaList.find(entry.ManifestType) == schemaList.end()) - { - // Copy constructor of valijson::Schema was private - valijson::Schema& newSchema = schemaList.emplace( - std::piecewise_construct, std::make_tuple(entry.ManifestType), std::make_tuple()).first->second; - Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType); - JsonSchema::PopulateSchema(schemaJson, newSchema); - } - - const auto& schema = schemaList.find(entry.ManifestType)->second; - Json::Value manifestJson = ManifestYamlNodeToJson(entry.Root); - valijson::ValidationResults results; - - if (!JsonSchema::Validate(schema, manifestJson, results)) - { - errors.emplace_back(ValidationError::MessageContextWithFile(ManifestError::SchemaError, JsonSchema::GetErrorStringFromResults(results), entry.FileName)); - } - } - - return errors; - } - - std::vector ValidateYamlManifestsSchemaHeader(const std::vector& manifestList, const ManifestVer& manifestVersion, bool treatErrorAsWarning) - { - std::vector errors; - ValidationError::Level errorLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error; - - // Read the manifest schema header and ensure it exists - for (const auto& entry : manifestList) - { - if (entry.ManifestType == ManifestTypeEnum::Shadow) - { - // There's no schema for a shadow manifest. - continue; - } - - auto schemaHeaderErrors = ValidateYamlManifestSchemaHeader(entry, manifestVersion, errorLevel); - std::move(schemaHeaderErrors.begin(), schemaHeaderErrors.end(), std::inserter(errors, errors.end())); - } - - return errors; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Yaml.h" +#include "winget/JsonSchemaValidation.h" +#include "winget/ManifestCommon.h" +#include "winget/ManifestSchemaValidation.h" +#include "winget/ManifestYamlParser.h" +#include "winget/Resources.h" + +#include + +namespace AppInstaller::Manifest::YamlParser +{ + using namespace std::string_view_literals; + + namespace + { + enum class YamlScalarType + { + String, + Int, + Bool + }; + + // List of fields that use non string scalar types + const std::map ManifestFieldTypes = + { + { "InstallerSuccessCodes"sv, YamlScalarType::Int }, + { "InstallerAbortsTerminal"sv, YamlScalarType::Bool }, + { "InstallLocationRequired"sv, YamlScalarType::Bool }, + { "RequireExplicitUpgrade"sv, YamlScalarType::Bool }, + { "DisplayInstallWarnings"sv, YamlScalarType::Bool }, + { "InstallerReturnCode"sv, YamlScalarType::Int }, + { "DownloadCommandProhibited", YamlScalarType::Bool }, + { "ArchiveBinariesDependOnPath", YamlScalarType::Bool } + }; + + YamlScalarType GetManifestScalarValueType(const std::string& key) + { + auto iter = ManifestFieldTypes.find(key); + if (iter != ManifestFieldTypes.end()) + { + return iter->second; + } + + return YamlScalarType::String; + } + + Json::Value YamlScalarNodeToJson(const YAML::Node& scalarNode, YamlScalarType scalarType) + { + if (scalarType == YamlScalarType::Int) + { + return Json::Value(scalarNode.as()); + } + else if (scalarType == YamlScalarType::Bool) + { + return Json::Value(scalarNode.as()); + } + else + { + return Json::Value(scalarNode.as()); + } + } + + Json::Value ManifestYamlNodeToJson(const YAML::Node& rootNode, YamlScalarType scalarType = YamlScalarType::String) + { + Json::Value result; + + if (rootNode.IsNull()) + { + result = Json::Value::nullSingleton(); + } + else if (rootNode.IsMap()) + { + for (auto const& keyValuePair : rootNode.Mapping()) + { + // We only support string type as key in our manifest + auto key = keyValuePair.first.as(); + result[keyValuePair.first.as()] = ManifestYamlNodeToJson(keyValuePair.second, GetManifestScalarValueType(key)); + } + } + else if (rootNode.IsSequence()) + { + for (auto const& value : rootNode.Sequence()) + { + result.append(ManifestYamlNodeToJson(value, scalarType)); + } + } + else if (rootNode.IsScalar()) + { + result = YamlScalarNodeToJson(rootNode, scalarType); + } + else + { + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + std::vector ParseSchemaHeaderString(const YamlManifestInfo& manifestInfo, const ValidationError::Level& errorLevel, std::string& schemaHeaderUrlString) + { + std::vector errors; + std::string schemaHeader = manifestInfo.DocumentSchemaHeader.SchemaHeader; + + // Remove the leading '#' and any leading/trailing whitespaces + if (schemaHeader[0] == '#') + { + schemaHeader = schemaHeader.substr(1); // Remove the leading '#' + schemaHeader = Utility::Trim(schemaHeader); // Trim leading/trailing whitespaces + } + + // Parse the schema header string as YAML string to get the schema header URL + try + { + auto root = YAML::Load(schemaHeader); + + if (root.IsNull() || (!root.IsNull() && !root.IsDefined())) + { + errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName)); + } + else + { + schemaHeaderUrlString = root[YAML::DocumentSchemaHeader::YamlLanguageServerKey].as(); + } + } + catch (const YAML::Exception&) + { + errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName)); + } + catch (const std::exception&) + { + errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::InvalidSchemaHeader, "", schemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, manifestInfo.DocumentSchemaHeader.Mark.column, errorLevel, manifestInfo.FileName)); + } + + return errors; + } + + bool ParseSchemaHeaderUrl(const std::string& schemaHeaderValue, std::string& schemaType, std::string& schemaVersion) + { + // Use regex to match the pattern of @"winget-manifest\.(?\w+)\.(?[\d\.]+)\.schema\.json$" + std::regex schemaUrlPattern(R"(winget-manifest\.(\w+)\.([\d\.]+)\.schema\.json$)"); + std::smatch match; + + if (std::regex_search(schemaHeaderValue, match, schemaUrlPattern)) + { + schemaType = match[1].str(); + schemaVersion = match[2].str(); + return true; + } + + return false; + } + + std::vector ValidateSchemaHeaderType(const std::string& headerManifestType, const ManifestTypeEnum& expectedManifestType, const YamlManifestInfo& manifestInfo, ValidationError::Level errorLevel) + { + std::vector errors; + ManifestTypeEnum actualManifestType = ConvertToManifestTypeEnum(headerManifestType); + size_t schemaHeaderTypeIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(headerManifestType) + 1; + + if (actualManifestType != expectedManifestType) + { + errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderManifestTypeMismatch, "", headerManifestType, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderTypeIndex, errorLevel, manifestInfo.FileName)); + } + + return errors; + } + + std::vector ValidateSchemaHeaderVersion(const std::string& headerManifestVersion, const ManifestVer& expectedManifestVersion, const YamlManifestInfo& manifestInfo, ValidationError::Level errorLevel) + { + std::vector errors; + ManifestVer actualHeaderVersion(headerManifestVersion); + size_t schemaHeaderVersionIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(headerManifestVersion) + 1; + + if (actualHeaderVersion != expectedManifestVersion) + { + errors.emplace_back(ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderManifestVersionMismatch, "", headerManifestVersion, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderVersionIndex, errorLevel, manifestInfo.FileName)); + } + + return errors; + } + + bool IsValidSchemaHeaderUrl(const std::string& schemaHeaderUrlString, const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion) + { + // Load the schema file to compare the schema header URL with the schema ID in the schema file + Json::Value schemaFile = LoadSchemaDoc(manifestVersion, manifestInfo.ManifestType); + + if (schemaFile.isMember("$id")) + { + std::string schemaId = schemaFile["$id"].asString(); + + // Prefix schema ID with "schema=" to match the schema header URL pattern and compare it with the schema header URL + schemaId = "$schema=" + schemaId; + + if (Utility::CaseInsensitiveEquals(schemaId, schemaHeaderUrlString)) + { + return true; + } + } + + return false; + } + + ValidationError GetSchemaHeaderUrlPatternMismatchError(const std::string& schemaHeaderUrlString, const YamlManifestInfo& manifestInfo, const ValidationError::Level& errorLevel) + { + size_t schemaHeaderUrlIndex = manifestInfo.DocumentSchemaHeader.SchemaHeader.find(schemaHeaderUrlString) + 1; + + return ValidationError::MessageContextValueLineLevelWithFile(ManifestError::SchemaHeaderUrlPatternMismatch, "", manifestInfo.DocumentSchemaHeader.SchemaHeader, manifestInfo.DocumentSchemaHeader.Mark.line, schemaHeaderUrlIndex, errorLevel, manifestInfo.FileName); + } + + std::vector ValidateSchemaHeaderUrl(const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion, const ValidationError::Level& errorLevel) + { + std::vector errors; + + std::string schemaHeaderUrlString; + // Parse the schema header string to get the schema header URL + auto parserErrors = ParseSchemaHeaderString(manifestInfo, errorLevel, schemaHeaderUrlString); + std::move(parserErrors.begin(), parserErrors.end(), std::inserter(errors, errors.end())); + + if (!errors.empty()) + { + return errors; + } + + std::string manifestTypeString; + std::string manifestVersionString; + + // Parse the schema header URL to get the manifest type and version + if (ParseSchemaHeaderUrl(schemaHeaderUrlString, manifestTypeString, manifestVersionString)) + { + auto headerManifestTypeErrors = ValidateSchemaHeaderType(manifestTypeString, manifestInfo.ManifestType, manifestInfo, errorLevel); + std::move(headerManifestTypeErrors.begin(), headerManifestTypeErrors.end(), std::inserter(errors, errors.end())); + + auto headerManifestVersionErrors = ValidateSchemaHeaderVersion(manifestVersionString, manifestVersion, manifestInfo, errorLevel); + std::move(headerManifestVersionErrors.begin(), headerManifestVersionErrors.end(), std::inserter(errors, errors.end())); + + // Finally, match the entire schema header URL with the schema ID in the schema file to ensure the URL domain matches the schema definition file. + if (!IsValidSchemaHeaderUrl(schemaHeaderUrlString, manifestInfo, manifestVersion)) + { + errors.emplace_back(GetSchemaHeaderUrlPatternMismatchError(schemaHeaderUrlString, manifestInfo, errorLevel)); + } + } + else + { + errors.emplace_back(GetSchemaHeaderUrlPatternMismatchError(schemaHeaderUrlString, manifestInfo, errorLevel)); + } + + return errors; + } + + std::vector ValidateYamlManifestSchemaHeader(const YamlManifestInfo& manifestInfo, const ManifestVer& manifestVersion, ValidationError::Level errorLevel) + { + std::vector errors; + std::string schemaHeaderString; + + if (manifestInfo.DocumentSchemaHeader.SchemaHeader.empty()) + { + errors.emplace_back(ValidationError::MessageLevelWithFile(ManifestError::SchemaHeaderNotFound, errorLevel, manifestInfo.FileName)); + return errors; + } + + auto parserErrors = ValidateSchemaHeaderUrl(manifestInfo, manifestVersion, errorLevel); + std::move(parserErrors.begin(), parserErrors.end(), std::inserter(errors, errors.end())); + + return errors; + } + } + + Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType) + { + int idx = MANIFESTSCHEMA_NO_RESOURCE; + std::map resourceMap; + + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_28 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_28_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_28_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_28_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_28_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_12 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_12_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_12_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_12_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_12_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_10 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_10_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_10_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_10_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_10_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_9 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_9_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_9_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_9_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_9_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_9_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_7 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_7_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_7_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_7_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_7_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_7_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_6 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_6_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_6_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_6_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_6_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_6_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_5 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_5_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_5_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_5_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_5_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_5_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_4 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_4_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_4_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_4_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_4_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_4_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_2 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_2_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_2_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_2_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_2_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_2_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_1 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_1_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_1_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_1_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_1_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_1_LOCALE }, + }; + } + else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1 }) + { + resourceMap = { + { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_SINGLETON }, + { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_VERSION }, + { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_INSTALLER }, + { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE }, + { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_LOCALE }, + }; + } + else + { + resourceMap = { + { ManifestTypeEnum::Preview, IDX_MANIFEST_SCHEMA_PREVIEW }, + }; + } + + auto iter = resourceMap.find(manifestType); + if (iter != resourceMap.end()) + { + idx = iter->second; + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + + std::string_view schemaStr = Resource::GetResourceAsString(idx, MANIFESTSCHEMA_RESOURCE_TYPE); + return JsonSchema::LoadSchemaDoc(schemaStr); + } + + std::vector ValidateAgainstSchema(const std::vector& manifestList, const ManifestVer& manifestVersion) + { + std::vector errors; + // A list of schema validator to avoid multiple loadings of same schema + std::map schemaList; + + for (const auto& entry : manifestList) + { + if (entry.ManifestType == ManifestTypeEnum::Shadow) + { + // There's no schema for a shadow manifest. + continue; + } + + if (schemaList.find(entry.ManifestType) == schemaList.end()) + { + // Copy constructor of valijson::Schema was private + valijson::Schema& newSchema = schemaList.emplace( + std::piecewise_construct, std::make_tuple(entry.ManifestType), std::make_tuple()).first->second; + Json::Value schemaJson = LoadSchemaDoc(manifestVersion, entry.ManifestType); + JsonSchema::PopulateSchema(schemaJson, newSchema); + } + + const auto& schema = schemaList.find(entry.ManifestType)->second; + Json::Value manifestJson = ManifestYamlNodeToJson(entry.Root); + valijson::ValidationResults results; + + if (!JsonSchema::Validate(schema, manifestJson, results)) + { + errors.emplace_back(ValidationError::MessageContextWithFile(ManifestError::SchemaError, JsonSchema::GetErrorStringFromResults(results), entry.FileName)); + } + } + + return errors; + } + + std::vector ValidateYamlManifestsSchemaHeader(const std::vector& manifestList, const ManifestVer& manifestVersion, bool treatErrorAsWarning) + { + std::vector errors; + ValidationError::Level errorLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error; + + // Read the manifest schema header and ensure it exists + for (const auto& entry : manifestList) + { + if (entry.ManifestType == ManifestTypeEnum::Shadow) + { + // There's no schema for a shadow manifest. + continue; + } + + auto schemaHeaderErrors = ValidateYamlManifestSchemaHeader(entry, manifestVersion, errorLevel); + std::move(schemaHeaderErrors.begin(), schemaHeaderErrors.end(), std::inserter(errors, errors.end())); + } + + return errors; + } +} diff --git a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp index b5c04a796b..edb076e4ff 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp @@ -1,681 +1,681 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerLogging.h" -#include "AppInstallerMsixInfo.h" -#include "winget/MsixManifest.h" -#include "winget/ManifestValidation.h" -#include "winget/MsixManifestValidation.h" -#include "winget/Locale.h" -#include "winget/Filesystem.h" -#include "winget/MsiExecArguments.h" - -namespace AppInstaller::Manifest -{ - namespace - { - constexpr std::array s_AllowedPortableFiletypes = { - L".exe", - }; - - constexpr std::array s_AllowedFontFiletypes = { - L".otf", // OpenType Font - L".ttf", // TrueType Font - L".fnt", // Font - L".ttc", // TrueType Font Collection - L".otc", // OpenType Font Collection - }; - - const auto& GetErrorIdToMessageMap() - { - // Each entry here must have a corresponding WINGET_DEFINE_RESOURCE_STRINGID in - // ManifestValidation.h (ManifestError namespace) and a value in the - // ManifestErrorId enum in src/WinGetUtilInterop/Common/ManifestErrorId.cs. - static std::map ErrorIdToMessageMap = { - { AppInstaller::Manifest::ManifestError::InvalidRootNode, "Encountered unexpected root node."sv }, - { AppInstaller::Manifest::ManifestError::FieldUnknown, "Unknown field."sv }, - { AppInstaller::Manifest::ManifestError::FieldIsNotPascalCase, "All field names should be PascalCased."sv }, - { AppInstaller::Manifest::ManifestError::FieldDuplicate, "Duplicate field found in the manifest."sv }, - { AppInstaller::Manifest::ManifestError::RequiredFieldEmpty, "Required field with empty value."sv }, - { AppInstaller::Manifest::ManifestError::RequiredFieldMissing, "Required field missing."sv }, - { AppInstaller::Manifest::ManifestError::InvalidFieldValue, "Invalid field value."sv }, - { AppInstaller::Manifest::ManifestError::ExeInstallerMissingSilentSwitches, "Silent and SilentWithProgress switches are not specified for InstallerType exe. Please make sure the installer can run unattended."sv }, - { AppInstaller::Manifest::ManifestError::FieldNotSupported, "Field is not supported."sv }, - { AppInstaller::Manifest::ManifestError::FieldValueNotSupported, "Field value is not supported."sv }, - { AppInstaller::Manifest::ManifestError::DuplicateInstallerEntry, "Duplicate installer entry found."sv }, - { AppInstaller::Manifest::ManifestError::DuplicateInstallerHash, "Multiple Installer URLs found with the same InstallerSha256. Please ensure the accuracy of the URLs."sv }, - { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "The specified installer type does not support PackageFamilyName."sv }, - { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportProductCode, "The specified installer type does not support ProductCode."sv }, - { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "The specified installer type does not write to Apps and Features entry."sv }, - { AppInstaller::Manifest::ManifestError::IncompleteMultiFileManifest, "The multi file manifest is incomplete.A multi file manifest must contain at least version, installer and defaultLocale manifest."sv }, - { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestFieldValue, "The multi file manifest has inconsistent field values."sv }, - { AppInstaller::Manifest::ManifestError::DuplicatePortableCommandAlias, "Duplicate portable command alias found."sv }, - { AppInstaller::Manifest::ManifestError::DuplicateRelativeFilePath, "Duplicate relative file path found."sv }, - { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestType, "The multi file manifest should contain only one file with the particular ManifestType."sv }, - { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestLocale, "The multi file manifest contains duplicate PackageLocale."sv }, - { AppInstaller::Manifest::ManifestError::UnsupportedMultiFileManifestType, "The multi file manifest should not contain file with the particular ManifestType."sv }, - { AppInstaller::Manifest::ManifestError::InconsistentInstallerHash, "The values of InstallerSha256 do not match for all instances of the same InstallerUrl."sv }, - { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestDefaultLocale, "DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest."sv }, - { AppInstaller::Manifest::ManifestError::FieldFailedToProcess, "Failed to process field."sv }, - { AppInstaller::Manifest::ManifestError::InvalidBcp47Value, "The locale value is not a well formed bcp47 language tag."sv }, - { AppInstaller::Manifest::ManifestError::BothAllowedAndExcludedMarketsDefined, "Both AllowedMarkets and ExcludedMarkets defined."sv }, - { AppInstaller::Manifest::ManifestError::DuplicateReturnCodeEntry, "Duplicate installer return code found."sv }, - { AppInstaller::Manifest::ManifestError::FieldRequireVerifiedPublisher, "Field usage requires verified publishers."sv }, - { AppInstaller::Manifest::ManifestError::SingleManifestPackageHasDependencies, "Package has a single manifest and is a dependency of other manifests."sv }, - { AppInstaller::Manifest::ManifestError::MultiManifestPackageHasDependencies, "Deleting the manifest will be break the following dependencies."sv }, - { AppInstaller::Manifest::ManifestError::MissingManifestDependenciesNode, "Dependency not found: "sv }, - { AppInstaller::Manifest::ManifestError::NoSuitableMinVersionDependency,"No Suitable Minimum Version: "sv }, - { AppInstaller::Manifest::ManifestError::FoundDependencyLoop, "Loop found."sv }, - { AppInstaller::Manifest::ManifestError::ExceededAppsAndFeaturesEntryLimit, "Only zero or one entry for Apps and Features may be specified for InstallerType portable."sv }, - { AppInstaller::Manifest::ManifestError::ExceededCommandsLimit, "Only zero or one value for Commands may be specified for InstallerType portable."sv }, - { AppInstaller::Manifest::ManifestError::ScopeNotSupported, "Scope is not supported for InstallerType portable."sv }, - { AppInstaller::Manifest::ManifestError::InstallerMsixInconsistencies, "Inconsistent value in the manifest."sv }, - { AppInstaller::Manifest::ManifestError::OptionalFieldMissing, "Optional field missing."sv }, - { AppInstaller::Manifest::ManifestError::InstallerFailedToProcess, "Failed to process installer."sv }, - { AppInstaller::Manifest::ManifestError::NoSupportedPlatforms, "No supported platforms."sv }, - { AppInstaller::Manifest::ManifestError::ApproximateVersionNotAllowed, "Approximate version not allowed."sv }, - { AppInstaller::Manifest::ManifestError::ArpVersionOverlapWithIndex, "DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index. Existing DisplayVersion range in index: "sv }, - { AppInstaller::Manifest::ManifestError::ArpVersionValidationInternalError, "Internal error while validating DisplayVersion against index."sv }, - { AppInstaller::Manifest::ManifestError::ExceededNestedInstallerFilesLimit, "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes."sv }, - { AppInstaller::Manifest::ManifestError::PortableCommandAliasEscapesDirectory, "Portable command alias must not point to a location outside of base directory."sv }, - { AppInstaller::Manifest::ManifestError::RelativeFilePathEscapesDirectory, "Relative file path must not point to a location outside of archive directory."sv }, - { AppInstaller::Manifest::ManifestError::ArpValidationError, "Arp Validation Error."sv }, - { AppInstaller::Manifest::ManifestError::SchemaError, "Schema Error."sv }, - { AppInstaller::Manifest::ManifestError::MsixSignatureHashFailed, "Failed to calculate MSIX signature hash.Please verify that the input file is a valid, signed MSIX."sv }, - { AppInstaller::Manifest::ManifestError::ShadowManifestNotAllowed, "Shadow manifest is not allowed." }, - { AppInstaller::Manifest::ManifestError::SchemaHeaderNotFound, "Schema header not found." }, - { AppInstaller::Manifest::ManifestError::InvalidSchemaHeader , "The schema header is invalid. Please verify that the schema header is present and formatted correctly."sv }, - { AppInstaller::Manifest::ManifestError::SchemaHeaderManifestTypeMismatch , "The manifest type in the schema header does not match the ManifestType property value in the manifest."sv }, - { AppInstaller::Manifest::ManifestError::SchemaHeaderManifestVersionMismatch, "The manifest version in the schema header does not match the ManifestVersion property value in the manifest."sv }, - { AppInstaller::Manifest::ManifestError::SchemaHeaderUrlPatternMismatch, "The schema header URL does not match the expected pattern."sv }, - { AppInstaller::Manifest::ManifestError::InvalidPortableFiletype, "The file type of the referenced file is not allowed."sv }, - { AppInstaller::Manifest::ManifestError::InvalidFontFiletype, "The file type of the referenced file is not a supported font file type."sv }, - { AppInstaller::Manifest::ManifestError::InvalidWindowsFeatureName, "The provided value is not a valid Windows feature name."sv }, - { AppInstaller::Manifest::ManifestError::BlockedMsiProperty, "Contains a blocked MSI property."sv }, - { AppInstaller::Manifest::ManifestError::InvalidMsiSwitches, "Contains invalid MSI switches."sv }, - { AppInstaller::Manifest::ManifestError::ContainsNetworkAddress, "Installer switch contains network address."sv }, - }; - - return ErrorIdToMessageMap; - } - - bool ContainsSharePathSignifier(std::string_view input) - { - return Utility::CaseInsensitiveContainsSubstring(input, "\\\\"); - } - - bool ContainsNetworkAddressSignifier(std::string_view input) - { - return Utility::CaseInsensitiveContainsSubstring(input, "http://") || - Utility::CaseInsensitiveContainsSubstring(input, "https://") || - Utility::CaseInsensitiveContainsSubstring(input, "ftp://"); - } - } - - std::vector ValidateManifest(const Manifest& manifest, const ManifestValidateOption& options) - { - std::vector resultErrors; - - // Channel is not supported currently - if (!manifest.Channel.empty()) - { - resultErrors.emplace_back(ManifestError::FieldNotSupported, "Channel", manifest.Channel); - } - - try - { - // Version value should be successfully parsed - Utility::Version testVersion{ manifest.Version }; - if (testVersion.IsApproximate()) - { - resultErrors.emplace_back(ManifestError::ApproximateVersionNotAllowed, "PackageVersion", manifest.Version); - } - } - catch (const std::exception&) - { - resultErrors.emplace_back(ManifestError::InvalidFieldValue, "PackageVersion", manifest.Version); - } - - auto defaultLocErrors = ValidateManifestLocalization(manifest.DefaultLocalization, !options.FullValidation); - std::move(defaultLocErrors.begin(), defaultLocErrors.end(), std::inserter(resultErrors, resultErrors.end())); - - // Comparison function to check duplicate installer entry. {installerType, arch, language and scope} combination is the key. - // Todo: use the comparator from ManifestComparator when that one is fully implemented. - auto installerCmp = [](const ManifestInstaller& in1, const ManifestInstaller& in2) - { - if (in1.BaseInstallerType != in2.BaseInstallerType) - { - return in1.BaseInstallerType < in2.BaseInstallerType; - } - else if (IsArchiveType(in1.BaseInstallerType)) - { - // Compare nested installer type if base installer type is archive. - if (in1.NestedInstallerType != in2.NestedInstallerType) - { - return in1.NestedInstallerType < in2.NestedInstallerType; - } - } - - if (in1.Arch != in2.Arch) - { - return in1.Arch < in2.Arch; - } - - if (in1.Locale != in2.Locale) - { - return in1.Locale < in2.Locale; - } - - // Unknown is considered equal to all other values for uniqueness. - // If either value is unknown, don't compare them. - if (in1.Scope != in2.Scope && in1.Scope != ScopeEnum::Unknown && in2.Scope != ScopeEnum::Unknown) - { - return in1.Scope < in2.Scope; - } - - return false; - }; - - std::set installerSet(installerCmp); - bool duplicateInstallerFound = false; - - // Set up maps for checking uniqueness across hash <-> url pairs - std::unordered_map urlToChecksum; - std::unordered_map checksumToUrl; - - // Validate installers - for (auto const& installer : manifest.Installers) - { - // If not full validation, for future compatibility, skip validating unknown installers. - if (installer.EffectiveInstallerType() == InstallerTypeEnum::Unknown && !options.FullValidation) - { - continue; - } - - if (!duplicateInstallerFound && !installerSet.insert(installer).second) - { - AICLI_LOG(Core, Error, << "Duplicate installer: Type [" << InstallerTypeToString(installer.EffectiveInstallerType()) << - "], Architecture [" << Utility::ToString(installer.Arch) << "], Locale [" << installer.Locale << - "], Scope [" << ScopeToString(installer.Scope) << "]"); - - resultErrors.emplace_back(ManifestError::DuplicateInstallerEntry); - duplicateInstallerFound = true; - } - - if (installer.Arch == Utility::Architecture::Unknown) - { - resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Architecture"); - } - - if (installer.EffectiveInstallerType() == InstallerTypeEnum::Unknown) - { - resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerType"); - } - - if (installer.UpdateBehavior == UpdateBehaviorEnum::Unknown) - { - resultErrors.emplace_back(ManifestError::InvalidFieldValue, "UpgradeBehavior"); - } - - // Validate system reference strings if they are set at the installer level - // Allow PackageFamilyName to be declared with non msix installers to support nested installer scenarios. But still report as warning to notify user of this uncommon case. - if (!installer.PackageFamilyName.empty() && !(DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()) || DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(installer.AppsAndFeaturesEntries))) - { - resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "InstallerType", std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }, ValidationError::Level::Warning); - } - - if (!installer.ProductCode.empty() && !DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType())) - { - resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportProductCode, "InstallerType", InstallerTypeToString(installer.EffectiveInstallerType())); - } - - if (!installer.AppsAndFeaturesEntries.empty() && !DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType())) - { - resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "InstallerType", InstallerTypeToString(installer.EffectiveInstallerType())); - } - - if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore) - { - if (options.FullValidation) - { - // MSStore type is not supported in community repo - resultErrors.emplace_back( - ManifestError::FieldValueNotSupported, "InstallerType", - InstallerTypeToString(installer.EffectiveInstallerType())); - } - - if (installer.ProductId.empty()) - { - resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "ProductId"); - } - } - else - { - // For other types, Url and Sha256 are required - if (installer.Url.empty()) - { - resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "InstallerUrl"); - } - if (installer.Sha256.empty()) - { - resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "InstallerSha256"); - } - // ProductId should not be used - if (!installer.ProductId.empty()) - { - resultErrors.emplace_back(ManifestError::FieldNotSupported, "ProductId"); - } - - // Ensure that each URL has a one to one mapping with a Sha256 and - // warn if a Sha256 has a one to many mapping with a URL - if (options.FullValidation && !installer.Url.empty() && !installer.Sha256.empty()) - { - std::string checksum = Utility::SHA256::ConvertToString(installer.Sha256); - std::string url = installer.Url; - - auto [urlIterator, urlInserted] = urlToChecksum.try_emplace(url, checksum); - auto [checksumIterator, checksumInserted] = checksumToUrl.try_emplace(checksum, url); - - if (!urlInserted && urlIterator->second != checksum) - { - // If the URL was not inserted, and the value in the map does not match the current Sha256, then - // a single URL corresponds to multiple SHA256 and an error should be thrown - resultErrors.emplace_back(ManifestError::InconsistentInstallerHash, "InstallerUrl", url); - } - - if (!checksumInserted && checksumIterator->second != url) - { - // If the SHA256 was not inserted, and the value in the map does not match the current URL, then - // a single SHA256 corresponds to multiple URLS and a warning should be thrown - resultErrors.emplace_back(ManifestError::DuplicateInstallerHash, "InstallerSha256", checksum, ValidationError::Level::Warning); - } - } - } - - if (installer.EffectiveInstallerType() == InstallerTypeEnum::Exe && - (installer.Switches.find(InstallerSwitchType::SilentWithProgress) == installer.Switches.end() || - installer.Switches.find(InstallerSwitchType::Silent) == installer.Switches.end())) - { - resultErrors.emplace_back(ManifestError::ExeInstallerMissingSilentSwitches, ValidationError::Level::Warning); - } - - // The command field restriction only applies if the base installer type is Portable. - if (installer.BaseInstallerType == InstallerTypeEnum::Portable) - { - if (installer.Commands.size() > 1) - { - resultErrors.emplace_back(ManifestError::ExceededCommandsLimit); - } - } - - if (installer.EffectiveInstallerType() == InstallerTypeEnum::Portable) - { - if (installer.AppsAndFeaturesEntries.size() > 1) - { - resultErrors.emplace_back(ManifestError::ExceededAppsAndFeaturesEntryLimit); - } - if (installer.Scope != ScopeEnum::Unknown) - { - resultErrors.emplace_back(ManifestError::ScopeNotSupported, ValidationError::Level::Warning); - } - } - - if (IsArchiveType(installer.BaseInstallerType)) - { - bool isPortable = installer.NestedInstallerType == InstallerTypeEnum::Portable; - bool isFont = installer.NestedInstallerType == InstallerTypeEnum::Font; - - if (installer.NestedInstallerType == InstallerTypeEnum::Unknown) - { - resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "NestedInstallerType"); - } - if (installer.NestedInstallerFiles.size() == 0) - { - resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "NestedInstallerFiles"); - } - if (!isPortable && !isFont && installer.NestedInstallerFiles.size() != 1) - { - resultErrors.emplace_back(ManifestError::ExceededNestedInstallerFilesLimit, "NestedInstallerFiles"); - } - - std::set commandAliasSet; - std::set relativeFilePathSet; - - for (const auto& nestedInstallerFile : installer.NestedInstallerFiles) - { - if (nestedInstallerFile.RelativeFilePath.empty()) - { - resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "RelativeFilePath"); - break; - } - - // Check that the relative file path does not escape base directory. - if (AppInstaller::Filesystem::PathEscapesBaseDirectory(nestedInstallerFile.RelativeFilePath)) - { - resultErrors.emplace_back(ManifestError::RelativeFilePathEscapesDirectory, "RelativeFilePath"); - } - - // Check for duplicate relative filepath values. - if (!relativeFilePathSet.insert(Utility::ToLower(nestedInstallerFile.RelativeFilePath)).second) - { - resultErrors.emplace_back(ManifestError::DuplicateRelativeFilePath, "RelativeFilePath"); - } - - if (!nestedInstallerFile.PortableCommandAlias.empty()) - { - // Check that the command alias does not escape the base directory. - if (AppInstaller::Filesystem::PathEscapesBaseDirectory(nestedInstallerFile.PortableCommandAlias)) - { - resultErrors.emplace_back(ManifestError::PortableCommandAliasEscapesDirectory, "PortableCommandAlias"); - } - - // Check for duplicate portable command alias values. - const auto& alias = Utility::ToLower(nestedInstallerFile.PortableCommandAlias); - if (!commandAliasSet.insert(alias).second) - { - resultErrors.emplace_back(ManifestError::DuplicatePortableCommandAlias, "PortableCommandAlias"); - break; - } - } - - // If running full validation, check filetype - if (options.FullValidation) - { - std::filesystem::path relativeFilePath{ Utility::ConvertToUTF16(nestedInstallerFile.RelativeFilePath) }; - if (relativeFilePath.has_extension()) - { - const std::wstring lowerExtension = Utility::ToLower(relativeFilePath.extension().wstring()); - - if (isPortable) - { - if (std::find(s_AllowedPortableFiletypes.begin(), s_AllowedPortableFiletypes.end(), lowerExtension) == s_AllowedPortableFiletypes.end()) - { - resultErrors.emplace_back(ManifestError::InvalidPortableFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); - } - } - - if (isFont) - { - if (std::find(s_AllowedFontFiletypes.begin(), s_AllowedFontFiletypes.end(), lowerExtension) == s_AllowedFontFiletypes.end()) - { - resultErrors.emplace_back(ManifestError::InvalidFontFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); - } - } - } - } - } - } - - // Check empty string before calling IsValidUrl to avoid duplicate error reporting. - if (!installer.Url.empty() && IsValidURL(NULL, Utility::ConvertToUTF16(installer.Url).c_str(), 0) == S_FALSE) - { - resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerUrl", installer.Url); - } - - if (!installer.Locale.empty() && !Locale::IsWellFormedBcp47Tag(installer.Locale)) - { - resultErrors.emplace_back(ManifestError::InvalidBcp47Value, "InstallerLocale", installer.Locale); - } - - if (!installer.Markets.AllowedMarkets.empty() && !installer.Markets.ExcludedMarkets.empty()) - { - resultErrors.emplace_back(ManifestError::BothAllowedAndExcludedMarketsDefined); - } - - // Check expected return codes for duplicates between successful and expected error codes - std::set returnCodeSet; - returnCodeSet.insert(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end()); - for (const auto& code : installer.ExpectedReturnCodes) - { - if (!returnCodeSet.insert(code.first).second) - { - resultErrors.emplace_back(ManifestError::DuplicateReturnCodeEntry); - - // Stop checking to avoid repeated errors - break; - } - } - - // Check no approximate version declared for DisplayVersion in AppsAndFeatureEntries - for (auto const& entry : installer.AppsAndFeaturesEntries) - { - if (!entry.DisplayVersion.empty()) - { - try - { - Utility::Version displayVersion{ entry.DisplayVersion }; - if (displayVersion.IsApproximate()) - { - resultErrors.emplace_back(ManifestError::ApproximateVersionNotAllowed, "DisplayVersion", entry.DisplayVersion); - } - } - catch (const std::exception&) - { - resultErrors.emplace_back(ManifestError::InvalidFieldValue, "DisplayVersion", entry.DisplayVersion); - } - } - } - - // Check AuthInfo validity. For full validation (community repo), authentication type must be none. - if (installer.AuthInfo.Type != Authentication::AuthenticationType::None) - { - if (options.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"); - } - } - - installer.Dependencies.ApplyToType(DependencyType::WindowsFeature, [&](const Dependency& dependency) - { - if (!IsValidWindowsFeaturePattern(dependency.Id())) - { - resultErrors.emplace_back(ManifestError::InvalidWindowsFeatureName, dependency.Id()); - } - }); - - if (options.FullValidation) - { - for (const auto& container : installer.DesiredStateConfiguration) - { - if (container.Type == DesiredStateConfigurationContainerType::PowerShell) - { - // PowerShell DSC is not supported in community repo. - resultErrors.emplace_back(ManifestError::FieldNotSupported, "DesiredStateConfiguration.PowerShell"); - break; - } - } - - if (DoesInstallerTypeUseMsiProperties(installer.EffectiveInstallerType())) - { - try - { - for (const auto& item : installer.Switches) - { - if (!item.second.empty()) - { - auto blocked = Msi::ParseMSIArguments(item.second).GetFirstBlockedProperty(); - if (blocked) - { - resultErrors.emplace_back(ManifestError::BlockedMsiProperty, blocked.value()); - } - } - } - } - catch (...) - { - resultErrors.emplace_back(ManifestError::InvalidMsiSwitches); - } - } - - for (const auto& item : installer.Switches) - { - if (!item.second.empty()) - { - if (ContainsSharePathSignifier(item.second)) - { - resultErrors.emplace_back(ManifestError::ContainsNetworkAddress, item.second); - } - else if (ContainsNetworkAddressSignifier(item.second)) - { - resultErrors.emplace_back(ManifestError::ContainsNetworkAddress, item.second, options.ErrorOnNetworkAddressInSwitches ? ValidationError::Level::Error : ValidationError::Level::Warning); - } - } - } - } - } - - // Validate localizations - for (auto const& localization : manifest.Localizations) - { - auto locErrors = ValidateManifestLocalization(localization, !options.FullValidation); - std::move(locErrors.begin(), locErrors.end(), std::inserter(resultErrors, resultErrors.end())); - } - - return resultErrors; - } - - std::vector ValidateManifestLocalization(const ManifestLocalization& localization, bool treatErrorAsWarning) - { - std::vector resultErrors; - - if (!localization.Locale.empty() && !Locale::IsWellFormedBcp47Tag(localization.Locale)) - { - resultErrors.emplace_back(ManifestError::InvalidBcp47Value, "PackageLocale", localization.Locale, treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error); - } - - if (localization.Contains(Localization::Agreements)) - { - const auto& agreements = localization.Get(); - for (const auto& agreement : agreements) - { - // At least one must be present - if (agreement.Label.empty() && agreement.AgreementText.empty() && agreement.AgreementUrl.empty()) - { - resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Agreements", treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error); - } - } - } - - return resultErrors; - } - - std::vector ValidateManifestInstallers(const Manifest& manifest, bool treatErrorAsWarning) - { - std::vector errors; - auto validationErrorLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error; - MsixManifestValidation msixManifestValidation(validationErrorLevel); - for (const auto& installer : manifest.Installers) - { - // Installer msix or msixbundle - if (installer.EffectiveInstallerType() == InstallerTypeEnum::Msix) - { - auto installerErrors = msixManifestValidation.Validate(manifest, installer); - std::move(installerErrors.begin(), installerErrors.end(), std::inserter(errors, errors.end())); - } - } - - return errors; - } - - std::string ValidationError::GetErrorMessage() const - { - const auto& ErrorIdToMessageMap = GetErrorIdToMessageMap(); - const auto itr = ErrorIdToMessageMap.find(Message); - - if (itr != ErrorIdToMessageMap.end()) - { - return std::string(itr->second); - } - - return Utility::ConvertToUTF8(Message); - } - - const std::string& ManifestException::GetManifestErrorMessage() const noexcept - { - if (m_manifestErrorMessage.empty()) - { - if (m_errors.empty()) - { - // Syntax error, yaml parser error is stored in FailureInfo - if (GetFailureInfo().pszMessage) - { - m_manifestErrorMessage = Utility::ConvertToUTF8(GetFailureInfo().pszMessage); - } - } - else - { - for (auto const& error : m_errors) - { - if (error.ErrorLevel == ValidationError::Level::Error) - { - m_manifestErrorMessage += ManifestError::ErrorMessagePrefix; - } - else if (error.ErrorLevel == ValidationError::Level::Warning) - { - m_manifestErrorMessage += ManifestError::WarningMessagePrefix; - } - m_manifestErrorMessage += error.GetErrorMessage(); - - if (!error.Context.empty()) - { - m_manifestErrorMessage += " [" + error.Context + "]"; - } - if (!error.Value.empty()) - { - m_manifestErrorMessage += " Value: " + error.Value; - } - if (error.Line > 0 && error.Column > 0) - { - m_manifestErrorMessage += " Line: " + std::to_string(error.Line) + ", Column: " + std::to_string(error.Column); - } - if (!error.FileName.empty()) - { - m_manifestErrorMessage += " File: " + error.FileName; - } - m_manifestErrorMessage += '\n'; - } - } - } - return m_manifestErrorMessage; - } - - std::string ManifestException::GetManifestErrorJson() const noexcept - { - try - { - Json::Value root; - root["fullMessage"] = GetManifestErrorMessage(); - root["isSyntaxError"] = m_errors.empty(); - - Json::Value errorsArray(Json::arrayValue); - for (auto const& error : m_errors) - { - Json::Value entry; - entry["errorId"] = Utility::ConvertToUTF8(error.Message); - entry["message"] = error.GetErrorMessage(); - entry["context"] = error.Context; - entry["value"] = error.Value; - entry["line"] = static_cast(error.Line); - entry["column"] = static_cast(error.Column); - entry["level"] = (error.ErrorLevel == ValidationError::Level::Error) ? "Error" : "Warning"; - entry["file"] = error.FileName; - errorsArray.append(std::move(entry)); - } - root["errors"] = std::move(errorsArray); - - Json::StreamWriterBuilder writer; - writer["indentation"] = ""; - return Json::writeString(writer, root); - } - catch (...) - { - return {}; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerLogging.h" +#include "AppInstallerMsixInfo.h" +#include "winget/MsixManifest.h" +#include "winget/ManifestValidation.h" +#include "winget/MsixManifestValidation.h" +#include "winget/Locale.h" +#include "winget/Filesystem.h" +#include "winget/MsiExecArguments.h" + +namespace AppInstaller::Manifest +{ + namespace + { + constexpr std::array s_AllowedPortableFiletypes = { + L".exe", + }; + + constexpr std::array s_AllowedFontFiletypes = { + L".otf", // OpenType Font + L".ttf", // TrueType Font + L".fnt", // Font + L".ttc", // TrueType Font Collection + L".otc", // OpenType Font Collection + }; + + const auto& GetErrorIdToMessageMap() + { + // Each entry here must have a corresponding WINGET_DEFINE_RESOURCE_STRINGID in + // ManifestValidation.h (ManifestError namespace) and a value in the + // ManifestErrorId enum in src/WinGetUtilInterop/Common/ManifestErrorId.cs. + static std::map ErrorIdToMessageMap = { + { AppInstaller::Manifest::ManifestError::InvalidRootNode, "Encountered unexpected root node."sv }, + { AppInstaller::Manifest::ManifestError::FieldUnknown, "Unknown field."sv }, + { AppInstaller::Manifest::ManifestError::FieldIsNotPascalCase, "All field names should be PascalCased."sv }, + { AppInstaller::Manifest::ManifestError::FieldDuplicate, "Duplicate field found in the manifest."sv }, + { AppInstaller::Manifest::ManifestError::RequiredFieldEmpty, "Required field with empty value."sv }, + { AppInstaller::Manifest::ManifestError::RequiredFieldMissing, "Required field missing."sv }, + { AppInstaller::Manifest::ManifestError::InvalidFieldValue, "Invalid field value."sv }, + { AppInstaller::Manifest::ManifestError::ExeInstallerMissingSilentSwitches, "Silent and SilentWithProgress switches are not specified for InstallerType exe. Please make sure the installer can run unattended."sv }, + { AppInstaller::Manifest::ManifestError::FieldNotSupported, "Field is not supported."sv }, + { AppInstaller::Manifest::ManifestError::FieldValueNotSupported, "Field value is not supported."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateInstallerEntry, "Duplicate installer entry found."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateInstallerHash, "Multiple Installer URLs found with the same InstallerSha256. Please ensure the accuracy of the URLs."sv }, + { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "The specified installer type does not support PackageFamilyName."sv }, + { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotSupportProductCode, "The specified installer type does not support ProductCode."sv }, + { AppInstaller::Manifest::ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "The specified installer type does not write to Apps and Features entry."sv }, + { AppInstaller::Manifest::ManifestError::IncompleteMultiFileManifest, "The multi file manifest is incomplete.A multi file manifest must contain at least version, installer and defaultLocale manifest."sv }, + { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestFieldValue, "The multi file manifest has inconsistent field values."sv }, + { AppInstaller::Manifest::ManifestError::DuplicatePortableCommandAlias, "Duplicate portable command alias found."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateRelativeFilePath, "Duplicate relative file path found."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestType, "The multi file manifest should contain only one file with the particular ManifestType."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateMultiFileManifestLocale, "The multi file manifest contains duplicate PackageLocale."sv }, + { AppInstaller::Manifest::ManifestError::UnsupportedMultiFileManifestType, "The multi file manifest should not contain file with the particular ManifestType."sv }, + { AppInstaller::Manifest::ManifestError::InconsistentInstallerHash, "The values of InstallerSha256 do not match for all instances of the same InstallerUrl."sv }, + { AppInstaller::Manifest::ManifestError::InconsistentMultiFileManifestDefaultLocale, "DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest."sv }, + { AppInstaller::Manifest::ManifestError::FieldFailedToProcess, "Failed to process field."sv }, + { AppInstaller::Manifest::ManifestError::InvalidBcp47Value, "The locale value is not a well formed bcp47 language tag."sv }, + { AppInstaller::Manifest::ManifestError::BothAllowedAndExcludedMarketsDefined, "Both AllowedMarkets and ExcludedMarkets defined."sv }, + { AppInstaller::Manifest::ManifestError::DuplicateReturnCodeEntry, "Duplicate installer return code found."sv }, + { AppInstaller::Manifest::ManifestError::FieldRequireVerifiedPublisher, "Field usage requires verified publishers."sv }, + { AppInstaller::Manifest::ManifestError::SingleManifestPackageHasDependencies, "Package has a single manifest and is a dependency of other manifests."sv }, + { AppInstaller::Manifest::ManifestError::MultiManifestPackageHasDependencies, "Deleting the manifest will be break the following dependencies."sv }, + { AppInstaller::Manifest::ManifestError::MissingManifestDependenciesNode, "Dependency not found: "sv }, + { AppInstaller::Manifest::ManifestError::NoSuitableMinVersionDependency,"No Suitable Minimum Version: "sv }, + { AppInstaller::Manifest::ManifestError::FoundDependencyLoop, "Loop found."sv }, + { AppInstaller::Manifest::ManifestError::ExceededAppsAndFeaturesEntryLimit, "Only zero or one entry for Apps and Features may be specified for InstallerType portable."sv }, + { AppInstaller::Manifest::ManifestError::ExceededCommandsLimit, "Only zero or one value for Commands may be specified for InstallerType portable."sv }, + { AppInstaller::Manifest::ManifestError::ScopeNotSupported, "Scope is not supported for InstallerType portable."sv }, + { AppInstaller::Manifest::ManifestError::InstallerMsixInconsistencies, "Inconsistent value in the manifest."sv }, + { AppInstaller::Manifest::ManifestError::OptionalFieldMissing, "Optional field missing."sv }, + { AppInstaller::Manifest::ManifestError::InstallerFailedToProcess, "Failed to process installer."sv }, + { AppInstaller::Manifest::ManifestError::NoSupportedPlatforms, "No supported platforms."sv }, + { AppInstaller::Manifest::ManifestError::ApproximateVersionNotAllowed, "Approximate version not allowed."sv }, + { AppInstaller::Manifest::ManifestError::ArpVersionOverlapWithIndex, "DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index. Existing DisplayVersion range in index: "sv }, + { AppInstaller::Manifest::ManifestError::ArpVersionValidationInternalError, "Internal error while validating DisplayVersion against index."sv }, + { AppInstaller::Manifest::ManifestError::ExceededNestedInstallerFilesLimit, "Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes."sv }, + { AppInstaller::Manifest::ManifestError::PortableCommandAliasEscapesDirectory, "Portable command alias must not point to a location outside of base directory."sv }, + { AppInstaller::Manifest::ManifestError::RelativeFilePathEscapesDirectory, "Relative file path must not point to a location outside of archive directory."sv }, + { AppInstaller::Manifest::ManifestError::ArpValidationError, "Arp Validation Error."sv }, + { AppInstaller::Manifest::ManifestError::SchemaError, "Schema Error."sv }, + { AppInstaller::Manifest::ManifestError::MsixSignatureHashFailed, "Failed to calculate MSIX signature hash.Please verify that the input file is a valid, signed MSIX."sv }, + { AppInstaller::Manifest::ManifestError::ShadowManifestNotAllowed, "Shadow manifest is not allowed." }, + { AppInstaller::Manifest::ManifestError::SchemaHeaderNotFound, "Schema header not found." }, + { AppInstaller::Manifest::ManifestError::InvalidSchemaHeader , "The schema header is invalid. Please verify that the schema header is present and formatted correctly."sv }, + { AppInstaller::Manifest::ManifestError::SchemaHeaderManifestTypeMismatch , "The manifest type in the schema header does not match the ManifestType property value in the manifest."sv }, + { AppInstaller::Manifest::ManifestError::SchemaHeaderManifestVersionMismatch, "The manifest version in the schema header does not match the ManifestVersion property value in the manifest."sv }, + { AppInstaller::Manifest::ManifestError::SchemaHeaderUrlPatternMismatch, "The schema header URL does not match the expected pattern."sv }, + { AppInstaller::Manifest::ManifestError::InvalidPortableFiletype, "The file type of the referenced file is not allowed."sv }, + { AppInstaller::Manifest::ManifestError::InvalidFontFiletype, "The file type of the referenced file is not a supported font file type."sv }, + { AppInstaller::Manifest::ManifestError::InvalidWindowsFeatureName, "The provided value is not a valid Windows feature name."sv }, + { AppInstaller::Manifest::ManifestError::BlockedMsiProperty, "Contains a blocked MSI property."sv }, + { AppInstaller::Manifest::ManifestError::InvalidMsiSwitches, "Contains invalid MSI switches."sv }, + { AppInstaller::Manifest::ManifestError::ContainsNetworkAddress, "Installer switch contains network address."sv }, + }; + + return ErrorIdToMessageMap; + } + + bool ContainsSharePathSignifier(std::string_view input) + { + return Utility::CaseInsensitiveContainsSubstring(input, "\\\\"); + } + + bool ContainsNetworkAddressSignifier(std::string_view input) + { + return Utility::CaseInsensitiveContainsSubstring(input, "http://") || + Utility::CaseInsensitiveContainsSubstring(input, "https://") || + Utility::CaseInsensitiveContainsSubstring(input, "ftp://"); + } + } + + std::vector ValidateManifest(const Manifest& manifest, const ManifestValidateOption& options) + { + std::vector resultErrors; + + // Channel is not supported currently + if (!manifest.Channel.empty()) + { + resultErrors.emplace_back(ManifestError::FieldNotSupported, "Channel", manifest.Channel); + } + + try + { + // Version value should be successfully parsed + Utility::Version testVersion{ manifest.Version }; + if (testVersion.IsApproximate()) + { + resultErrors.emplace_back(ManifestError::ApproximateVersionNotAllowed, "PackageVersion", manifest.Version); + } + } + catch (const std::exception&) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "PackageVersion", manifest.Version); + } + + auto defaultLocErrors = ValidateManifestLocalization(manifest.DefaultLocalization, !options.FullValidation); + std::move(defaultLocErrors.begin(), defaultLocErrors.end(), std::inserter(resultErrors, resultErrors.end())); + + // Comparison function to check duplicate installer entry. {installerType, arch, language and scope} combination is the key. + // Todo: use the comparator from ManifestComparator when that one is fully implemented. + auto installerCmp = [](const ManifestInstaller& in1, const ManifestInstaller& in2) + { + if (in1.BaseInstallerType != in2.BaseInstallerType) + { + return in1.BaseInstallerType < in2.BaseInstallerType; + } + else if (IsArchiveType(in1.BaseInstallerType)) + { + // Compare nested installer type if base installer type is archive. + if (in1.NestedInstallerType != in2.NestedInstallerType) + { + return in1.NestedInstallerType < in2.NestedInstallerType; + } + } + + if (in1.Arch != in2.Arch) + { + return in1.Arch < in2.Arch; + } + + if (in1.Locale != in2.Locale) + { + return in1.Locale < in2.Locale; + } + + // Unknown is considered equal to all other values for uniqueness. + // If either value is unknown, don't compare them. + if (in1.Scope != in2.Scope && in1.Scope != ScopeEnum::Unknown && in2.Scope != ScopeEnum::Unknown) + { + return in1.Scope < in2.Scope; + } + + return false; + }; + + std::set installerSet(installerCmp); + bool duplicateInstallerFound = false; + + // Set up maps for checking uniqueness across hash <-> url pairs + std::unordered_map urlToChecksum; + std::unordered_map checksumToUrl; + + // Validate installers + for (auto const& installer : manifest.Installers) + { + // If not full validation, for future compatibility, skip validating unknown installers. + if (installer.EffectiveInstallerType() == InstallerTypeEnum::Unknown && !options.FullValidation) + { + continue; + } + + if (!duplicateInstallerFound && !installerSet.insert(installer).second) + { + AICLI_LOG(Core, Error, << "Duplicate installer: Type [" << InstallerTypeToString(installer.EffectiveInstallerType()) << + "], Architecture [" << Utility::ToString(installer.Arch) << "], Locale [" << installer.Locale << + "], Scope [" << ScopeToString(installer.Scope) << "]"); + + resultErrors.emplace_back(ManifestError::DuplicateInstallerEntry); + duplicateInstallerFound = true; + } + + if (installer.Arch == Utility::Architecture::Unknown) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Architecture"); + } + + if (installer.EffectiveInstallerType() == InstallerTypeEnum::Unknown) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerType"); + } + + if (installer.UpdateBehavior == UpdateBehaviorEnum::Unknown) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "UpgradeBehavior"); + } + + // Validate system reference strings if they are set at the installer level + // Allow PackageFamilyName to be declared with non msix installers to support nested installer scenarios. But still report as warning to notify user of this uncommon case. + if (!installer.PackageFamilyName.empty() && !(DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()) || DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(installer.AppsAndFeaturesEntries))) + { + resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportPackageFamilyName, "InstallerType", std::string{ InstallerTypeToString(installer.EffectiveInstallerType()) }, ValidationError::Level::Warning); + } + + if (!installer.ProductCode.empty() && !DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType())) + { + resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotSupportProductCode, "InstallerType", InstallerTypeToString(installer.EffectiveInstallerType())); + } + + if (!installer.AppsAndFeaturesEntries.empty() && !DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType())) + { + resultErrors.emplace_back(ManifestError::InstallerTypeDoesNotWriteAppsAndFeaturesEntry, "InstallerType", InstallerTypeToString(installer.EffectiveInstallerType())); + } + + if (installer.EffectiveInstallerType() == InstallerTypeEnum::MSStore) + { + if (options.FullValidation) + { + // MSStore type is not supported in community repo + resultErrors.emplace_back( + ManifestError::FieldValueNotSupported, "InstallerType", + InstallerTypeToString(installer.EffectiveInstallerType())); + } + + if (installer.ProductId.empty()) + { + resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "ProductId"); + } + } + else + { + // For other types, Url and Sha256 are required + if (installer.Url.empty()) + { + resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "InstallerUrl"); + } + if (installer.Sha256.empty()) + { + resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "InstallerSha256"); + } + // ProductId should not be used + if (!installer.ProductId.empty()) + { + resultErrors.emplace_back(ManifestError::FieldNotSupported, "ProductId"); + } + + // Ensure that each URL has a one to one mapping with a Sha256 and + // warn if a Sha256 has a one to many mapping with a URL + if (options.FullValidation && !installer.Url.empty() && !installer.Sha256.empty()) + { + std::string checksum = Utility::SHA256::ConvertToString(installer.Sha256); + std::string url = installer.Url; + + auto [urlIterator, urlInserted] = urlToChecksum.try_emplace(url, checksum); + auto [checksumIterator, checksumInserted] = checksumToUrl.try_emplace(checksum, url); + + if (!urlInserted && urlIterator->second != checksum) + { + // If the URL was not inserted, and the value in the map does not match the current Sha256, then + // a single URL corresponds to multiple SHA256 and an error should be thrown + resultErrors.emplace_back(ManifestError::InconsistentInstallerHash, "InstallerUrl", url); + } + + if (!checksumInserted && checksumIterator->second != url) + { + // If the SHA256 was not inserted, and the value in the map does not match the current URL, then + // a single SHA256 corresponds to multiple URLS and a warning should be thrown + resultErrors.emplace_back(ManifestError::DuplicateInstallerHash, "InstallerSha256", checksum, ValidationError::Level::Warning); + } + } + } + + if (installer.EffectiveInstallerType() == InstallerTypeEnum::Exe && + (installer.Switches.find(InstallerSwitchType::SilentWithProgress) == installer.Switches.end() || + installer.Switches.find(InstallerSwitchType::Silent) == installer.Switches.end())) + { + resultErrors.emplace_back(ManifestError::ExeInstallerMissingSilentSwitches, ValidationError::Level::Warning); + } + + // The command field restriction only applies if the base installer type is Portable. + if (installer.BaseInstallerType == InstallerTypeEnum::Portable) + { + if (installer.Commands.size() > 1) + { + resultErrors.emplace_back(ManifestError::ExceededCommandsLimit); + } + } + + if (installer.EffectiveInstallerType() == InstallerTypeEnum::Portable) + { + if (installer.AppsAndFeaturesEntries.size() > 1) + { + resultErrors.emplace_back(ManifestError::ExceededAppsAndFeaturesEntryLimit); + } + if (installer.Scope != ScopeEnum::Unknown) + { + resultErrors.emplace_back(ManifestError::ScopeNotSupported, ValidationError::Level::Warning); + } + } + + if (IsArchiveType(installer.BaseInstallerType)) + { + bool isPortable = installer.NestedInstallerType == InstallerTypeEnum::Portable; + bool isFont = installer.NestedInstallerType == InstallerTypeEnum::Font; + + if (installer.NestedInstallerType == InstallerTypeEnum::Unknown) + { + resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "NestedInstallerType"); + } + if (installer.NestedInstallerFiles.size() == 0) + { + resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "NestedInstallerFiles"); + } + if (!isPortable && !isFont && installer.NestedInstallerFiles.size() != 1) + { + resultErrors.emplace_back(ManifestError::ExceededNestedInstallerFilesLimit, "NestedInstallerFiles"); + } + + std::set commandAliasSet; + std::set relativeFilePathSet; + + for (const auto& nestedInstallerFile : installer.NestedInstallerFiles) + { + if (nestedInstallerFile.RelativeFilePath.empty()) + { + resultErrors.emplace_back(ManifestError::RequiredFieldMissing, "RelativeFilePath"); + break; + } + + // Check that the relative file path does not escape base directory. + if (AppInstaller::Filesystem::PathEscapesBaseDirectory(nestedInstallerFile.RelativeFilePath)) + { + resultErrors.emplace_back(ManifestError::RelativeFilePathEscapesDirectory, "RelativeFilePath"); + } + + // Check for duplicate relative filepath values. + if (!relativeFilePathSet.insert(Utility::ToLower(nestedInstallerFile.RelativeFilePath)).second) + { + resultErrors.emplace_back(ManifestError::DuplicateRelativeFilePath, "RelativeFilePath"); + } + + if (!nestedInstallerFile.PortableCommandAlias.empty()) + { + // Check that the command alias does not escape the base directory. + if (AppInstaller::Filesystem::PathEscapesBaseDirectory(nestedInstallerFile.PortableCommandAlias)) + { + resultErrors.emplace_back(ManifestError::PortableCommandAliasEscapesDirectory, "PortableCommandAlias"); + } + + // Check for duplicate portable command alias values. + const auto& alias = Utility::ToLower(nestedInstallerFile.PortableCommandAlias); + if (!commandAliasSet.insert(alias).second) + { + resultErrors.emplace_back(ManifestError::DuplicatePortableCommandAlias, "PortableCommandAlias"); + break; + } + } + + // If running full validation, check filetype + if (options.FullValidation) + { + std::filesystem::path relativeFilePath{ Utility::ConvertToUTF16(nestedInstallerFile.RelativeFilePath) }; + if (relativeFilePath.has_extension()) + { + const std::wstring lowerExtension = Utility::ToLower(relativeFilePath.extension().wstring()); + + if (isPortable) + { + if (std::find(s_AllowedPortableFiletypes.begin(), s_AllowedPortableFiletypes.end(), lowerExtension) == s_AllowedPortableFiletypes.end()) + { + resultErrors.emplace_back(ManifestError::InvalidPortableFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); + } + } + + if (isFont) + { + if (std::find(s_AllowedFontFiletypes.begin(), s_AllowedFontFiletypes.end(), lowerExtension) == s_AllowedFontFiletypes.end()) + { + resultErrors.emplace_back(ManifestError::InvalidFontFiletype, "RelativeFilePath", nestedInstallerFile.RelativeFilePath); + } + } + } + } + } + } + + // Check empty string before calling IsValidUrl to avoid duplicate error reporting. + if (!installer.Url.empty() && IsValidURL(NULL, Utility::ConvertToUTF16(installer.Url).c_str(), 0) == S_FALSE) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "InstallerUrl", installer.Url); + } + + if (!installer.Locale.empty() && !Locale::IsWellFormedBcp47Tag(installer.Locale)) + { + resultErrors.emplace_back(ManifestError::InvalidBcp47Value, "InstallerLocale", installer.Locale); + } + + if (!installer.Markets.AllowedMarkets.empty() && !installer.Markets.ExcludedMarkets.empty()) + { + resultErrors.emplace_back(ManifestError::BothAllowedAndExcludedMarketsDefined); + } + + // Check expected return codes for duplicates between successful and expected error codes + std::set returnCodeSet; + returnCodeSet.insert(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end()); + for (const auto& code : installer.ExpectedReturnCodes) + { + if (!returnCodeSet.insert(code.first).second) + { + resultErrors.emplace_back(ManifestError::DuplicateReturnCodeEntry); + + // Stop checking to avoid repeated errors + break; + } + } + + // Check no approximate version declared for DisplayVersion in AppsAndFeatureEntries + for (auto const& entry : installer.AppsAndFeaturesEntries) + { + if (!entry.DisplayVersion.empty()) + { + try + { + Utility::Version displayVersion{ entry.DisplayVersion }; + if (displayVersion.IsApproximate()) + { + resultErrors.emplace_back(ManifestError::ApproximateVersionNotAllowed, "DisplayVersion", entry.DisplayVersion); + } + } + catch (const std::exception&) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "DisplayVersion", entry.DisplayVersion); + } + } + } + + // Check AuthInfo validity. For full validation (community repo), authentication type must be none. + if (installer.AuthInfo.Type != Authentication::AuthenticationType::None) + { + if (options.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"); + } + } + + installer.Dependencies.ApplyToType(DependencyType::WindowsFeature, [&](const Dependency& dependency) + { + if (!IsValidWindowsFeaturePattern(dependency.Id())) + { + resultErrors.emplace_back(ManifestError::InvalidWindowsFeatureName, dependency.Id()); + } + }); + + if (options.FullValidation) + { + for (const auto& container : installer.DesiredStateConfiguration) + { + if (container.Type == DesiredStateConfigurationContainerType::PowerShell) + { + // PowerShell DSC is not supported in community repo. + resultErrors.emplace_back(ManifestError::FieldNotSupported, "DesiredStateConfiguration.PowerShell"); + break; + } + } + + if (DoesInstallerTypeUseMsiProperties(installer.EffectiveInstallerType())) + { + try + { + for (const auto& item : installer.Switches) + { + if (!item.second.empty()) + { + auto blocked = Msi::ParseMSIArguments(item.second).GetFirstBlockedProperty(); + if (blocked) + { + resultErrors.emplace_back(ManifestError::BlockedMsiProperty, blocked.value()); + } + } + } + } + catch (...) + { + resultErrors.emplace_back(ManifestError::InvalidMsiSwitches); + } + } + + for (const auto& item : installer.Switches) + { + if (!item.second.empty()) + { + if (ContainsSharePathSignifier(item.second)) + { + resultErrors.emplace_back(ManifestError::ContainsNetworkAddress, item.second); + } + else if (ContainsNetworkAddressSignifier(item.second)) + { + resultErrors.emplace_back(ManifestError::ContainsNetworkAddress, item.second, options.ErrorOnNetworkAddressInSwitches ? ValidationError::Level::Error : ValidationError::Level::Warning); + } + } + } + } + } + + // Validate localizations + for (auto const& localization : manifest.Localizations) + { + auto locErrors = ValidateManifestLocalization(localization, !options.FullValidation); + std::move(locErrors.begin(), locErrors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + return resultErrors; + } + + std::vector ValidateManifestLocalization(const ManifestLocalization& localization, bool treatErrorAsWarning) + { + std::vector resultErrors; + + if (!localization.Locale.empty() && !Locale::IsWellFormedBcp47Tag(localization.Locale)) + { + resultErrors.emplace_back(ManifestError::InvalidBcp47Value, "PackageLocale", localization.Locale, treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error); + } + + if (localization.Contains(Localization::Agreements)) + { + const auto& agreements = localization.Get(); + for (const auto& agreement : agreements) + { + // At least one must be present + if (agreement.Label.empty() && agreement.AgreementText.empty() && agreement.AgreementUrl.empty()) + { + resultErrors.emplace_back(ManifestError::InvalidFieldValue, "Agreements", treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error); + } + } + } + + return resultErrors; + } + + std::vector ValidateManifestInstallers(const Manifest& manifest, bool treatErrorAsWarning) + { + std::vector errors; + auto validationErrorLevel = treatErrorAsWarning ? ValidationError::Level::Warning : ValidationError::Level::Error; + MsixManifestValidation msixManifestValidation(validationErrorLevel); + for (const auto& installer : manifest.Installers) + { + // Installer msix or msixbundle + if (installer.EffectiveInstallerType() == InstallerTypeEnum::Msix) + { + auto installerErrors = msixManifestValidation.Validate(manifest, installer); + std::move(installerErrors.begin(), installerErrors.end(), std::inserter(errors, errors.end())); + } + } + + return errors; + } + + std::string ValidationError::GetErrorMessage() const + { + const auto& ErrorIdToMessageMap = GetErrorIdToMessageMap(); + const auto itr = ErrorIdToMessageMap.find(Message); + + if (itr != ErrorIdToMessageMap.end()) + { + return std::string(itr->second); + } + + return Utility::ConvertToUTF8(Message); + } + + const std::string& ManifestException::GetManifestErrorMessage() const noexcept + { + if (m_manifestErrorMessage.empty()) + { + if (m_errors.empty()) + { + // Syntax error, yaml parser error is stored in FailureInfo + if (GetFailureInfo().pszMessage) + { + m_manifestErrorMessage = Utility::ConvertToUTF8(GetFailureInfo().pszMessage); + } + } + else + { + for (auto const& error : m_errors) + { + if (error.ErrorLevel == ValidationError::Level::Error) + { + m_manifestErrorMessage += ManifestError::ErrorMessagePrefix; + } + else if (error.ErrorLevel == ValidationError::Level::Warning) + { + m_manifestErrorMessage += ManifestError::WarningMessagePrefix; + } + m_manifestErrorMessage += error.GetErrorMessage(); + + if (!error.Context.empty()) + { + m_manifestErrorMessage += " [" + error.Context + "]"; + } + if (!error.Value.empty()) + { + m_manifestErrorMessage += " Value: " + error.Value; + } + if (error.Line > 0 && error.Column > 0) + { + m_manifestErrorMessage += " Line: " + std::to_string(error.Line) + ", Column: " + std::to_string(error.Column); + } + if (!error.FileName.empty()) + { + m_manifestErrorMessage += " File: " + error.FileName; + } + m_manifestErrorMessage += '\n'; + } + } + } + return m_manifestErrorMessage; + } + + std::string ManifestException::GetManifestErrorJson() const noexcept + { + try + { + Json::Value root; + root["fullMessage"] = GetManifestErrorMessage(); + root["isSyntaxError"] = m_errors.empty(); + + Json::Value errorsArray(Json::arrayValue); + for (auto const& error : m_errors) + { + Json::Value entry; + entry["errorId"] = Utility::ConvertToUTF8(error.Message); + entry["message"] = error.GetErrorMessage(); + entry["context"] = error.Context; + entry["value"] = error.Value; + entry["line"] = static_cast(error.Line); + entry["column"] = static_cast(error.Column); + entry["level"] = (error.ErrorLevel == ValidationError::Level::Error) ? "Error" : "Warning"; + entry["file"] = error.FileName; + errorsArray.append(std::move(entry)); + } + root["errors"] = std::move(errorsArray); + + Json::StreamWriterBuilder writer; + writer["indentation"] = ""; + return Json::writeString(writer, root); + } + catch (...) + { + return {}; + } + } +} diff --git a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp index 4fb2f2dcb7..ad02015068 100644 --- a/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp +++ b/src/AppInstallerCommonCore/Manifest/ManifestYamlPopulator.cpp @@ -1,1465 +1,1465 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerSHA256.h" -#include "winget/ManifestYamlPopulator.h" - -namespace AppInstaller::Manifest -{ - using ValidationErrors = std::vector; - using ExpectedReturnCodeInfo = AppInstaller::Manifest::ManifestInstaller::ExpectedReturnCodeInfo; - - namespace - { - template - Ptr* variant_ptr(const VariantManifestPtr& v) { return std::get(v); } - - ManifestInstaller* GetManifestInstallerPtrFromManifest(const VariantManifestPtr& v) { return &(variant_ptr(v)->DefaultInstallerInfo); } - - ManifestLocalization* GetManifestLocalizationPtrFromManifest(const VariantManifestPtr& v) { return &(variant_ptr(v)->DefaultLocalization); } - - ManifestInstaller* GetManifestInstallerPtr(const VariantManifestPtr& v) - { - if (auto installer = std::get_if(&v)) - { - return *installer; - } - - return GetManifestInstallerPtrFromManifest(v); - } - - ManifestLocalization* GetManifestLocalizationPtr(const VariantManifestPtr& v) - { - if (auto localization = std::get_if(&v)) - { - return *localization; - } - - return GetManifestLocalizationPtrFromManifest(v); - } - - // Only used in preview manifest - std::vector SplitMultiValueField(const std::string& input) - { - if (input.empty()) - { - return {}; - } - - std::vector result; - size_t currentPos = 0; - while (currentPos < input.size()) - { - size_t splitPos = input.find(',', currentPos); - if (splitPos == std::string::npos) - { - splitPos = input.size(); - } - - std::string splitVal = input.substr(currentPos, splitPos - currentPos); - Utility::Trim(splitVal); - if (!splitVal.empty()) - { - result.emplace_back(std::move(splitVal)); - } - currentPos = splitPos + 1; - } - - return result; - } - - std::vector ProcessStringSequenceNode(const YAML::Node& node, bool trim = true) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - std::vector result; - - for (auto const& entry : node.Sequence()) - { - std::string value = entry.as(); - if (trim) - { - Utility::Trim(value); - } - - result.emplace_back(std::move(value)); - } - - return result; - } - - std::vector ProcessInstallerSuccessCodeSequenceNode(const YAML::Node& node) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - std::vector result; - - for (auto const& entry : node.Sequence()) - { - result.emplace_back(static_cast(entry.as())); - } - - return result; - } - - std::vector ProcessPlatformSequenceNode(const YAML::Node& node) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - std::vector result; - - for (auto const& entry : node.Sequence()) - { - result.emplace_back(ConvertToPlatformEnum(entry.as())); - } - - return result; - } - - std::vector ProcessInstallModeSequenceNode(const YAML::Node& node) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - std::vector result; - - for (auto const& entry : node.Sequence()) - { - result.emplace_back(ConvertToInstallModeEnum(entry.as())); - } - - return result; - } - - std::vector ProcessArchitectureSequenceNode(const YAML::Node& node) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - std::vector result; - - for (auto const& entry : node.Sequence()) - { - result.emplace_back(Utility::ConvertToArchitectureEnum(entry.as())); - } - - return result; - } - - std::vector ProcessUnsupportedArgumentsSequenceNode(const YAML::Node& node) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - std::vector result; - - for (auto const& entry : node.Sequence()) - { - result.emplace_back(ConvertToUnsupportedArgumentEnum(entry.as())); - } - - return result; - } - - void ProcessDependenciesNode(DependencyType type, const YAML::Node& node, DependencyList* dependencyList) - { - const auto& ids = ProcessStringSequenceNode(node); - for (auto id : ids) - { - dependencyList->Add(Dependency(type, id)); - } - } - } - - std::vector ManifestYamlPopulator::GetRootFieldProcessInfo() - { - // Common fields across versions - std::vector result = - { - { "ManifestVersion", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { /* ManifestVersion already populated. Field listed here for duplicate and PascalCase check */ return {}; } }, - { "Installers", [this](const YAML::Node& value, const VariantManifestPtr&)->ValidationErrors { m_p_installersNode = &value; return {}; } }, - { "Localization", [this](const YAML::Node& value, const VariantManifestPtr&)->ValidationErrors { m_p_localizationsNode = &value; return {}; } }, - { "Channel", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Channel = Utility::Trim(value.as()); return {}; } }, - }; - - // Additional version specific fields - if (m_manifestVersion.get().Major() == 0) - { - std::vector previewRootFields - { - { "Id", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Id = Utility::Trim(value.as()); return {}; } }, - { "Version", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Version = Utility::Trim(value.as()); return {}; } }, - { "AppMoniker", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Moniker = Utility::Trim(value.as()); return {}; } }, - }; - - - std::move(previewRootFields.begin(), previewRootFields.end(), std::inserter(result, result.end())); - } - else if (m_manifestVersion.get().Major() == 1) - { - // Starting v1, we should be only adding new fields for each minor version increase - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) - { - std::vector v1RootFields - { - { "PackageIdentifier", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Id = Utility::Trim(value.as()); return {}; } }, - { "PackageVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Version = Utility::Trim(value.as()); return {}; } }, - { "Moniker", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Moniker = Utility::Trim(value.as()); return {}; } }, - { "ManifestType", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { /* ManifestType already checked. Field listed here for duplicate and PascalCase check */ return {}; } }, - }; - - std::move(v1RootFields.begin(), v1RootFields.end(), std::inserter(result, result.end())); - } - } - - // Root fields mapped as Installer and Localization values - auto rootInstallerFields = GetInstallerFieldProcessInfo(true); - std::move(rootInstallerFields.begin(), rootInstallerFields.end(), std::inserter(result, result.end())); - - auto rootLocalizationFields = GetLocalizationFieldProcessInfo(true); - std::move(rootLocalizationFields.begin(), rootLocalizationFields.end(), std::inserter(result, result.end())); - - return result; - } - - std::vector ManifestYamlPopulator::GetInstallerFieldProcessInfo(bool forRootFields) - { - // Common fields across versions - std::vector result = - { - { "InstallerType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->BaseInstallerType = ConvertToInstallerTypeEnum(value.as()); return {}; } }, - { "PackageFamilyName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->PackageFamilyName = value.as(); return {}; } }, - { "ProductCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ProductCode = value.as(); return {}; } }, - }; - - // Additional version specific fields - if (m_manifestVersion.get().Major() == 0) - { - // Root level and Localization node level - std::vector previewCommonFields = - { - { "UpdateBehavior", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UpdateBehavior = ConvertToUpdateBehaviorEnum(value.as()); return {}; } }, - { "Switches", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, SwitchesFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->Switches))); }}, - }; - - std::move(previewCommonFields.begin(), previewCommonFields.end(), std::inserter(result, result.end())); - - if (!forRootFields) - { - // Installer node only - std::vector installerOnlyFields = - { - { "Arch", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } }, - { "Url", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = value.as(); return {}; } }, - { "Sha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, - { "SignatureSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, - { "Language", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Locale = value.as(); return {}; } }, - { "Scope", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Scope = ConvertToScopeEnum(value.as()); return {}; } }, - }; - - if (m_manifestVersion.get().HasExtension(s_MSStoreExtension)) - { - installerOnlyFields.emplace_back("ProductId", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ProductId = value.as(); return {}; }); - } - - std::move(installerOnlyFields.begin(), installerOnlyFields.end(), std::inserter(result, result.end())); - } - else - { - // Root node only - std::vector rootOnlyFields = - { - { "MinOSVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->MinOSVersion = value.as(); return {}; } }, - { "Commands", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->Commands = SplitMultiValueField(value.as()); return {}; } }, - { "Protocols", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->Protocols = SplitMultiValueField(value.as()); return {}; } }, - { "FileExtensions", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->FileExtensions = SplitMultiValueField(value.as()); return {}; } }, - }; - - std::move(rootOnlyFields.begin(), rootOnlyFields.end(), std::inserter(result, result.end())); - } - } - else if (m_manifestVersion.get().Major() == 1) - { - // Starting v1, we should be only adding new fields for each minor version increase - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) - { - // Root level and Installer node level - std::vector v1CommonFields = - { - { "InstallerLocale", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Locale = value.as(); return {}; } }, - { "Platform", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Platform = ProcessPlatformSequenceNode(value); return {}; } }, - { "MinimumOSVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->MinOSVersion = value.as(); return {}; } }, - { "Scope", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Scope = ConvertToScopeEnum(value.as()); return {}; } }, - { "InstallModes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallModes = ProcessInstallModeSequenceNode(value); return {}; } }, - { "InstallerSwitches", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, SwitchesFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->Switches))); }}, - { "InstallerSuccessCodes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallerSuccessCodes = ProcessInstallerSuccessCodeSequenceNode(value); return {}; } }, - { "UpgradeBehavior", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UpdateBehavior = ConvertToUpdateBehaviorEnum(value.as()); return {}; } }, - { "Commands", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Commands = ProcessStringSequenceNode(value); return {}; } }, - { "Protocols", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Protocols = ProcessStringSequenceNode(value); return {}; } }, - { "FileExtensions", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->FileExtensions = ProcessStringSequenceNode(value); return {}; } }, - { "Dependencies", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, DependenciesFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->Dependencies))); }}, - { "Capabilities", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Capabilities = ProcessStringSequenceNode(value); return {}; } }, - { "RestrictedCapabilities", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->RestrictedCapabilities = ProcessStringSequenceNode(value); return {}; } }, - }; - - std::move(v1CommonFields.begin(), v1CommonFields.end(), std::inserter(result, result.end())); - - if (!forRootFields) - { - // Installer level only fields - std::vector v1InstallerFields = - { - { "Architecture", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } }, - { "InstallerUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = value.as(); return {}; } }, - { "InstallerSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, - { "SignatureSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, - // No custom validation needed at field populating time since we have semantic validation later to block msstore and productId from community repo. - { "MSStoreProductIdentifier", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ProductId = value.as(); return {}; } }, - }; - - std::move(v1InstallerFields.begin(), v1InstallerFields.end(), std::inserter(result, result.end())); - } - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) - { - std::vector fields_v1_1 = - { - { "InstallerAbortsTerminal", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallerAbortsTerminal = value.as(); return {}; } }, - { "InstallLocationRequired", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallLocationRequired = value.as(); return {}; } }, - { "RequireExplicitUpgrade", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->RequireExplicitUpgrade = value.as(); return {}; } }, - { "ReleaseDate", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ReleaseDate = Utility::Trim(value.as()); return {}; } }, - { "UnsupportedOSArchitectures", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UnsupportedOSArchitectures = ProcessArchitectureSequenceNode(value); return {}; } }, - { "ElevationRequirement", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ElevationRequirement = ConvertToElevationRequirementEnum(value.as()); return {}; } }, - { "Markets", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessMarketsNode(value, GetManifestInstallerPtr(v)); } }, - { "AppsAndFeaturesEntries", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessAppsAndFeaturesEntriesNode(value, GetManifestInstallerPtr(v)); } }, - { "ExpectedReturnCodes", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessExpectedReturnCodesNode(value, GetManifestInstallerPtr(v)); } }, - }; - - std::move(fields_v1_1.begin(), fields_v1_1.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) - { - std::vector fields_v1_2 = - { - { "UnsupportedArguments", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UnsupportedArguments = ProcessUnsupportedArgumentsSequenceNode(value); return {}; } }, - { "DisplayInstallWarnings", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->DisplayInstallWarnings = value.as(); return {}; } }, - }; - - std::move(fields_v1_2.begin(), fields_v1_2.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) - { - std::vector fields_v1_4 = - { - { "NestedInstallerType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->NestedInstallerType = ConvertToInstallerTypeEnum(value.as()); return {}; } }, - { "NestedInstallerFiles", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessNestedInstallerFilesNode(value, GetManifestInstallerPtr(v)); } }, - { "InstallationMetadata", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, InstallationMetadataFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->InstallationMetadata))); }}, - }; - - std::move(fields_v1_4.begin(), fields_v1_4.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_6 }) - { - std::vector fields_v1_6 = - { - { "DownloadCommandProhibited", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->DownloadCommandProhibited = value.as(); return {}; }, true }, - }; - - std::move(fields_v1_6.begin(), fields_v1_6.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_7 }) - { - std::vector fields_v1_7 = - { - { "RepairBehavior", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->RepairBehavior = ConvertToRepairBehaviorEnum(value.as()); return {}; } }, - }; - - std::move(fields_v1_7.begin(), fields_v1_7.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_9 }) - { - std::vector fields_v1_9 = - { - { "ArchiveBinariesDependOnPath", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ArchiveBinariesDependOnPath = value.as(); return {}; } }, - }; - - 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())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) - { - std::vector fields_v1_28 = - { - { "DesiredStateConfiguration", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors - { - auto* installer = GetManifestInstallerPtr(v); - installer->DesiredStateConfiguration.clear(); - return ValidateAndProcessFields(value, DesiredStateConfigurationFieldInfos, VariantManifestPtr(&(installer->DesiredStateConfiguration))); - } - }, - }; - - std::move(fields_v1_28.begin(), fields_v1_28.end(), std::inserter(result, result.end())); - } - } - - return result; - } - - std::vector ManifestYamlPopulator::GetSwitchesFieldProcessInfo() - { - // Common fields across versions - std::vector result = - { - { "Custom", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Custom] = value.as(); return{}; } }, - { "Silent", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Silent] = value.as(); return{}; } }, - { "SilentWithProgress", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::SilentWithProgress] = value.as(); return{}; } }, - { "Interactive", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Interactive] = value.as(); return{}; } }, - { "Log", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Log] = value.as(); return{}; } }, - { "InstallLocation", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::InstallLocation] = value.as(); return{}; } }, - }; - - // Additional version specific fields - if (m_manifestVersion.get().Major() == 0) - { - // Language only exists in preview manifests. Though we don't use it in our code yet, keep it here to be consistent with schema. - result.emplace_back("Language", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Language] = value.as(); return{}; }); - result.emplace_back("Update", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Update] = value.as(); return{}; }); - } - else if (m_manifestVersion.get().Major() == 1) - { - result.emplace_back("Upgrade", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Update] = value.as(); return{}; }); - - if (m_manifestVersion.get() >= ManifestVer{s_ManifestVersionV1_7}) - { - result.emplace_back("Repair", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Repair] = value.as(); return{}; }); - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetExpectedReturnCodesFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) - { - result.emplace_back("InstallerReturnCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->InstallerReturnCode = static_cast(value.as()); return {}; }); - result.emplace_back("ReturnResponse", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ReturnResponse = ConvertToExpectedReturnCodeEnum(value.as()); return {}; }); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) - { - result.emplace_back("ReturnResponseUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ReturnResponseUrl = value.as(); return {}; }); - } - - return result; - } - - std::vector ManifestYamlPopulator::GetLocalizationFieldProcessInfo(bool forRootFields) - { - // Common fields across versions - std::vector result = - { - { "Description", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(Utility::Trim(value.as())); return {}; } }, - { "LicenseUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - }; - - // Additional version specific fields - if (m_manifestVersion.get().Major() == 0) - { - // Root level and Localization node level - result.emplace_back("Homepage", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; }); - - if (!forRootFields) - { - // Localization node only - result.emplace_back("Language", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Locale = value.as(); return {}; }); - } - else - { - // Root node only - std::vector rootOnlyFields = - { - { "Name", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(Utility::Trim(value.as())); return {}; } }, - { "Publisher", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(value.as()); return {}; } }, - { "Author", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(value.as()); return {}; } }, - { "License", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(value.as()); return {}; } }, - { "Tags", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(SplitMultiValueField(value.as())); return {}; } }, - }; - - std::move(rootOnlyFields.begin(), rootOnlyFields.end(), std::inserter(result, result.end())); - } - } - else if (m_manifestVersion.get().Major() == 1) - { - // Starting v1, we should be only adding new fields for each minor version increase - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) - { - // Root level and Localization node level - std::vector v1CommonFields = - { - { "PackageLocale", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Locale = value.as(); return {}; } }, - { "Publisher", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "PublisherUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "PublisherSupportUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "PrivacyUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "Author", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "PackageName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(Utility::Trim(value.as())); return {}; } }, - { "PackageUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "License", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "Copyright", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "CopyrightUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "ShortDescription", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(Utility::Trim(value.as())); return {}; } }, - { "Tags", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(ProcessStringSequenceNode(value)); return {}; } }, - }; - - std::move(v1CommonFields.begin(), v1CommonFields.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) - { - std::vector fields_v1_1 = - { - { "Agreements", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessAgreementsNode(value, GetManifestLocalizationPtr(v)); } }, - { "ReleaseNotes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "ReleaseNotesUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - }; - - std::move(fields_v1_1.begin(), fields_v1_1.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) - { - std::vector fields_v1_2 = - { - { "PurchaseUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "InstallationNotes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, - { "Documentations", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDocumentationsNode(value, GetManifestLocalizationPtr(v)); } }, - }; - - std::move(fields_v1_2.begin(), fields_v1_2.end(), std::inserter(result, result.end())); - } - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) - { - std::vector fields_v1_5 = - { - { "Icons", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessIconsNode(value, GetManifestLocalizationPtr(v)); }, true }, - }; - - std::move(fields_v1_5.begin(), fields_v1_5.end(), std::inserter(result, result.end())); - } - } - - return result; - } - - std::vector ManifestYamlPopulator::GetDependenciesFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) - { - result = - { - { "WindowsFeatures", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessDependenciesNode(DependencyType::WindowsFeature, value, variant_ptr(v)); return {}; } }, - { "WindowsLibraries", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessDependenciesNode(DependencyType::WindowsLibrary, value, variant_ptr(v)); return {}; } }, - { "PackageDependencies", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessPackageDependenciesNode(value, variant_ptr(v)); return {}; } }, - { "ExternalDependencies", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessDependenciesNode(DependencyType::External, value, variant_ptr(v)); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetPackageDependenciesFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) - { - result = - { - { "PackageIdentifier", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SetId(Utility::Trim(value.as())); return {}; } }, - { "MinimumVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->MinVersion = Utility::Version(Utility::Trim(value.as())); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetAgreementFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) - { - result = - { - { "AgreementLabel", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Label = Utility::Trim(value.as()); return {}; } }, - { "Agreement", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->AgreementText = Utility::Trim(value.as()); return {}; }, true }, - { "AgreementUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->AgreementUrl = Utility::Trim(value.as()); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetMarketsFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) - { - result = - { - { "AllowedMarkets", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->AllowedMarkets = ProcessStringSequenceNode(value); return {}; } }, - { "ExcludedMarkets", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ExcludedMarkets = ProcessStringSequenceNode(value); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetAppsAndFeaturesEntryFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) - { - result = - { - { "DisplayName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DisplayName = Utility::Trim(value.as()); return {}; } }, - { "Publisher", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Publisher = Utility::Trim(value.as()); return {}; } }, - { "DisplayVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DisplayVersion = Utility::Trim(value.as()); return {}; } }, - { "ProductCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ProductCode = Utility::Trim(value.as()); return {}; } }, - { "UpgradeCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->UpgradeCode = Utility::Trim(value.as()); return {}; } }, - { "InstallerType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->InstallerType = ConvertToInstallerTypeEnum(value.as()); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetDocumentationFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) - { - result = - { - { "DocumentLabel", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DocumentLabel = Utility::Trim(value.as()); return {}; } }, - { "DocumentUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DocumentUrl = Utility::Trim(value.as()); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetIconFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) - { - result = - { - { "IconUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = Utility::Trim(value.as()); return {}; } }, - { "IconFileType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->FileType = ConvertToIconFileTypeEnum(value.as()); return {}; } }, - { "IconResolution", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Resolution = ConvertToIconResolutionEnum(value.as()); return {}; } }, - { "IconTheme", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Theme = ConvertToIconThemeEnum(value.as()); return {}; } }, - { "IconSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetNestedInstallerFileFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) - { - result = - { - { "RelativeFilePath", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RelativeFilePath = Utility::Trim(value.as()); return {}; } }, - { "PortableCommandAlias", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->PortableCommandAlias = Utility::Trim(value.as()); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetInstallationMetadataFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) - { - result = - { - { "DefaultInstallLocation", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DefaultInstallLocation = Utility::Trim(value.as()); return {}; } }, - { "Files", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessInstallationMetadataFilesNode(value, variant_ptr(v)); } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetInstallationMetadataFilesFieldProcessInfo() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) - { - result = - { - { "RelativeFilePath", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RelativeFilePath = Utility::Trim(value.as()); return {}; } }, - { "FileSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->FileSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, - { "FileType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->FileType = ConvertToInstalledFileTypeEnum(value.as()); return {}; } }, - { "InvocationParameter", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->InvocationParameter = Utility::Trim(value.as()); return {}; } }, - { "DisplayName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DisplayName = Utility::Trim(value.as()); return {}; } }, - }; - } - - 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; - - if (m_manifestVersion.get().Major() == 1) - { - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) - { - std::vector fields_v1_5 = - { - { - { "Localization", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessShadowLocalizationNode(value, variant_ptr(v)); } }, - { "ManifestType", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, - { "PackageIdentifier", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, - { "PackageVersion", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, - { "ManifestVersion", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, - }, - }; - - std::move(fields_v1_5.begin(), fields_v1_5.end(), std::inserter(result, result.end())); - } - } - - auto rootLocalizationFields = GetShadowLocalizationFieldProcessInfo(); - std::move(rootLocalizationFields.begin(), rootLocalizationFields.end(), std::inserter(result, result.end())); - - return result; - } - - std::vector ManifestYamlPopulator::GetShadowLocalizationFieldProcessInfo() - { - std::vector result; - - if (m_manifestVersion.get().Major() == 1) - { - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) - { - std::vector fields_v1_5 = - { - { "PackageLocale", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Locale = value.as(); return {}; } }, - { "Icons", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessIconsNode(value, GetManifestLocalizationPtr(v)); } }, - }; - - std::move(fields_v1_5.begin(), fields_v1_5.end(), std::inserter(result, result.end())); - } - } - - return result; - } - - std::vector ManifestYamlPopulator::GetDesiredStateConfigurationFieldInfos() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) - { - result = - { - { "PowerShell", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSC_PowerShellModuleNode(value, variant_ptr>(v)); } }, - { "DSCv3", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors - { - auto* variantValue = variant_ptr>(v); - variantValue->emplace_back(DesiredStateConfigurationContainerType::DSCv3); - return ValidateAndProcessFields(value, DesiredStateConfigurationDSCv3FieldInfos, VariantManifestPtr(&variantValue->back())); - } - }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetDesiredStateConfigurationPowerShellModuleFieldInfos() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) - { - result = - { - { "RepositoryUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RepositoryURL = Utility::Trim(value.as()); return {}; } }, - { "ModuleName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ModuleName = Utility::Trim(value.as()); return {}; } }, - { "Resources", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSC_PowerShellResourcesNode(value, variant_ptr(v)); } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetDesiredStateConfigurationPowerShellResourceFieldInfos() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) - { - result = - { - { "Name", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Name = Utility::Trim(value.as()); return {}; } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetDesiredStateConfigurationDSCv3FieldInfos() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) - { - result = - { - { "Resources", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSCv3ResourcesNode(value, variant_ptr(v)); } }, - }; - } - - return result; - } - - std::vector ManifestYamlPopulator::GetDesiredStateConfigurationDSCv3ResourceFieldInfos() - { - std::vector result = {}; - - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) - { - result = - { - { "Type", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Name = Utility::Trim(value.as()); return {}; } }, - }; - } - - return result; - } - - ValidationErrors ManifestYamlPopulator::ValidateAndProcessFields( - const YAML::Node& rootNode, - const std::vector& fieldInfos, - const VariantManifestPtr& v) - { - ValidationErrors resultErrors; - - if (!rootNode.IsMap() || rootNode.size() == 0) - { - resultErrors.emplace_back(ManifestError::InvalidRootNode, "", "", m_isMergedManifest ? 0 : rootNode.Mark().line, m_isMergedManifest ? 0 : rootNode.Mark().column); - return resultErrors; - } - - // Keeps track of already processed fields. Used to check duplicate fields. - std::set processedFields; - - for (auto const& keyValuePair : rootNode.Mapping()) - { - std::string key = keyValuePair.first.as(); - const YAML::Node& valueNode = keyValuePair.second; - - // We'll do case-insensitive search first and validate correct case later. - auto fieldIter = std::find_if(fieldInfos.begin(), fieldInfos.end(), - [&](auto const& s) - { - return Utility::CaseInsensitiveEquals(s.Name, key); - }); - - if (fieldIter != fieldInfos.end()) - { - const FieldProcessInfo& fieldInfo = *fieldIter; - - // Make sure the found key is in Pascal Case - if (key != fieldInfo.Name) - { - resultErrors.emplace_back(ManifestError::FieldIsNotPascalCase, key, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column); - } - - // Make sure it's not a duplicate key - if (!processedFields.insert(fieldInfo.Name).second) - { - resultErrors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column); - } - - if (fieldInfo.RequireVerifiedPublisher) - { - resultErrors.emplace_back(ManifestError::FieldRequireVerifiedPublisher, fieldInfo.Name, "", - m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column, - m_validateOption.ErrorOnVerifiedPublisherFields ? ValidationError::Level::Error : ValidationError::Level::Warning); - } - - if (!valueNode.IsNull()) - { - try - { - auto errors = fieldInfo.ProcessFunc(valueNode, v); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - } - catch (const std::exception&) - { - resultErrors.emplace_back(ManifestError::FieldFailedToProcess, fieldInfo.Name); - } - } - } - else - { - // For full validation, also reports unrecognized fields as warning - if (m_validateOption.FullValidation) - { - resultErrors.emplace_back(ManifestError::FieldUnknown, key, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column, ValidationError::Level::Warning); - } - } - } - - return resultErrors; - } - - ValidationErrors ManifestYamlPopulator::ProcessPackageDependenciesNode(const YAML::Node& rootNode, DependencyList* dependencyList) - { - ValidationErrors resultErrors; - - for (auto const& entry : rootNode.Sequence()) - { - Dependency packageDependency = Dependency(DependencyType::Package); - auto errors = ValidateAndProcessFields(entry, PackageDependenciesFieldInfos, VariantManifestPtr(&packageDependency)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - dependencyList->Add(std::move(packageDependency)); - } - - return resultErrors; - } - - ValidationErrors ManifestYamlPopulator::ProcessAgreementsNode(const YAML::Node& agreementsNode, ManifestLocalization* localization) - { - THROW_HR_IF(E_INVALIDARG, !agreementsNode.IsSequence()); - - ValidationErrors resultErrors; - std::vector agreements; - - for (auto const& entry : agreementsNode.Sequence()) - { - Agreement agreement; - auto errors = ValidateAndProcessFields(entry, AgreementFieldInfos, VariantManifestPtr(&agreement)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - agreements.emplace_back(std::move(agreement)); - } - - if (!agreements.empty()) - { - localization->Add(std::move(agreements)); - } - - return resultErrors; - } - - std::vector ManifestYamlPopulator::ProcessMarketsNode(const YAML::Node& marketsNode, ManifestInstaller* installer) - { - MarketsInfo markets; - auto errors = ValidateAndProcessFields(marketsNode, MarketsFieldInfos, VariantManifestPtr(&markets)); - installer->Markets = markets; - return errors; - } - - std::vector ManifestYamlPopulator::ProcessAppsAndFeaturesEntriesNode(const YAML::Node& appsAndFeaturesEntriesNode, ManifestInstaller* installer) - { - THROW_HR_IF(E_INVALIDARG, !appsAndFeaturesEntriesNode.IsSequence()); - - ValidationErrors resultErrors; - std::vector appsAndFeaturesEntries; - - for (auto const& entry : appsAndFeaturesEntriesNode.Sequence()) - { - AppsAndFeaturesEntry appsAndFeaturesEntry; - auto errors = ValidateAndProcessFields(entry, AppsAndFeaturesEntryFieldInfos, VariantManifestPtr(&appsAndFeaturesEntry)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - appsAndFeaturesEntries.emplace_back(std::move(appsAndFeaturesEntry)); - } - - installer->AppsAndFeaturesEntries = appsAndFeaturesEntries; - - return resultErrors; - } - - ValidationErrors ManifestYamlPopulator::ProcessExpectedReturnCodesNode(const YAML::Node& returnCodesNode, ManifestInstaller* installer) - { - THROW_HR_IF(E_INVALIDARG, !returnCodesNode.IsSequence()); - - ValidationErrors resultErrors; - std::map returnCodes; - - for (auto const& entry : returnCodesNode.Sequence()) - { - ExpectedReturnCode returnCode; - auto errors = ValidateAndProcessFields(entry, ExpectedReturnCodesFieldInfos, VariantManifestPtr(&returnCode)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - if (!returnCodes.insert({ returnCode.InstallerReturnCode, {returnCode.ReturnResponse, returnCode.ReturnResponseUrl} }).second) - { - resultErrors.emplace_back(ManifestError::DuplicateReturnCodeEntry); - } - } - - installer->ExpectedReturnCodes = returnCodes; - - return resultErrors; - } - - ValidationErrors ManifestYamlPopulator::ProcessDocumentationsNode(const YAML::Node& documentationsNode, ManifestLocalization* localization) - { - THROW_HR_IF(E_INVALIDARG, !documentationsNode.IsSequence()); - - ValidationErrors resultErrors; - std::vector documentations; - - for (auto const& entry : documentationsNode.Sequence()) - { - Documentation documentation; - auto errors = ValidateAndProcessFields(entry, DocumentationFieldInfos, VariantManifestPtr(&documentation)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - documentations.emplace_back(std::move(documentation)); - } - - if (!documentations.empty()) - { - localization->Add(std::move(documentations)); - } - - return resultErrors; - } - - std::vector ManifestYamlPopulator::ProcessIconsNode(const YAML::Node& iconsNode, ManifestLocalization* localization) - { - THROW_HR_IF(E_INVALIDARG, !iconsNode.IsSequence()); - - ValidationErrors resultErrors; - std::vector icons; - - for (auto const& entry : iconsNode.Sequence()) - { - Icon icon; - auto errors = ValidateAndProcessFields(entry, IconFieldInfos, VariantManifestPtr(&icon)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - icons.emplace_back(std::move(icon)); - } - - if (!icons.empty()) - { - localization->Add(std::move(icons)); - } - - return resultErrors; - } - - ValidationErrors ManifestYamlPopulator::ProcessNestedInstallerFilesNode(const YAML::Node& nestedInstallerFilesNode, ManifestInstaller* installer) - { - THROW_HR_IF(E_INVALIDARG, !nestedInstallerFilesNode.IsSequence()); - - ValidationErrors resultErrors; - std::vector nestedInstallerFiles; - - for (auto const& entry : nestedInstallerFilesNode.Sequence()) - { - NestedInstallerFile nestedInstallerFile; - auto errors = ValidateAndProcessFields(entry, NestedInstallerFileFieldInfos, VariantManifestPtr(&nestedInstallerFile)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - nestedInstallerFiles.emplace_back(std::move(nestedInstallerFile)); - } - - if (!nestedInstallerFiles.empty()) - { - installer->NestedInstallerFiles = nestedInstallerFiles; - } - - return resultErrors; - } - - std::vector ManifestYamlPopulator::ProcessInstallationMetadataFilesNode(const YAML::Node& installedFilesNode, InstallationMetadataInfo* installationMetadata) - { - THROW_HR_IF(E_INVALIDARG, !installedFilesNode.IsSequence()); - - ValidationErrors resultErrors; - std::vector installedFiles; - - for (auto const& entry : installedFilesNode.Sequence()) - { - InstalledFile installedFile; - auto errors = ValidateAndProcessFields(entry, InstallationMetadataFilesFieldInfos, VariantManifestPtr(&installedFile)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - installedFiles.emplace_back(std::move(installedFile)); - } - - if (!installedFiles.empty()) - { - installationMetadata->Files = installedFiles; - } - - return resultErrors; - } - - std::vector ManifestYamlPopulator::ProcessShadowLocalizationNode(const YAML::Node& localizationNode, Manifest* manifest) - { - THROW_HR_IF(E_INVALIDARG, !localizationNode.IsSequence()); - - ValidationErrors resultErrors; - auto shadowLocalizationFields = GetShadowLocalizationFieldProcessInfo(); - - for (auto const& entry : localizationNode.Sequence()) - { - ManifestLocalization localization; - auto errors = ValidateAndProcessFields(entry, shadowLocalizationFields, VariantManifestPtr(&localization)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - manifest->Localizations.emplace_back(std::move(std::move(localization))); - } - - return resultErrors; - } - - std::vector ManifestYamlPopulator::ProcessDSC_PowerShellModuleNode(const YAML::Node& node, std::vector* containers) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - ValidationErrors resultErrors; - - for (auto const& entry : node.Sequence()) - { - auto& containerInfo = containers->emplace_back(DesiredStateConfigurationContainerType::PowerShell); - auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationPowerShellModuleFieldInfos, VariantManifestPtr(&containerInfo)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - } - - return resultErrors; - } - - std::vector ManifestYamlPopulator::ProcessDSC_PowerShellResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - ValidationErrors resultErrors; - - for (auto const& entry : node.Sequence()) - { - auto& resourceInfo = container->Resources.emplace_back(); - auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationPowerShellResourceFieldInfos, VariantManifestPtr(&resourceInfo)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - } - - return resultErrors; - } - - std::vector ManifestYamlPopulator::ProcessDSCv3ResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container) - { - THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); - - ValidationErrors resultErrors; - - for (auto const& entry : node.Sequence()) - { - auto& resourceInfo = container->Resources.emplace_back(); - auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationDSCv3ResourceFieldInfos, VariantManifestPtr(&resourceInfo)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - } - - return resultErrors; - } - - - ManifestYamlPopulator::ManifestYamlPopulator(YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, ManifestValidateOption validateOption) : - m_rootNode(rootNode), m_manifest(manifest), m_manifestVersion(manifestVersion), m_validateOption(validateOption) - { - m_isMergedManifest = !m_rootNode.get()["ManifestType"sv].IsNull() && m_rootNode.get()["ManifestType"sv].as() == "merged"; - m_manifest.get().ManifestVersion = m_manifestVersion; - } - - ValidationErrors ManifestYamlPopulator::PopulateManifestInternal() - { - const YAML::Node& rootNode = m_rootNode; - ValidationErrors resultErrors; - - // Prepare field infos - RootFieldInfos = GetRootFieldProcessInfo(); - InstallerFieldInfos = GetInstallerFieldProcessInfo(); - SwitchesFieldInfos = GetSwitchesFieldProcessInfo(); - ExpectedReturnCodesFieldInfos = GetExpectedReturnCodesFieldProcessInfo(); - DependenciesFieldInfos = GetDependenciesFieldProcessInfo(); - PackageDependenciesFieldInfos = GetPackageDependenciesFieldProcessInfo(); - LocalizationFieldInfos = GetLocalizationFieldProcessInfo(); - AgreementFieldInfos = GetAgreementFieldProcessInfo(); - MarketsFieldInfos = GetMarketsFieldProcessInfo(); - AppsAndFeaturesEntryFieldInfos = GetAppsAndFeaturesEntryFieldProcessInfo(); - DocumentationFieldInfos = GetDocumentationFieldProcessInfo(); - IconFieldInfos = GetIconFieldProcessInfo(); - NestedInstallerFileFieldInfos = GetNestedInstallerFileFieldProcessInfo(); - InstallationMetadataFieldInfos = GetInstallationMetadataFieldProcessInfo(); - InstallationMetadataFilesFieldInfos = GetInstallationMetadataFilesFieldProcessInfo(); - AuthenticationFieldInfos = GetAuthenticationFieldInfos(); - MicrosoftEntraIdAuthenticationInfoFieldInfos = GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); - DesiredStateConfigurationFieldInfos = GetDesiredStateConfigurationFieldInfos(); - DesiredStateConfigurationPowerShellModuleFieldInfos = GetDesiredStateConfigurationPowerShellModuleFieldInfos(); - DesiredStateConfigurationPowerShellResourceFieldInfos = GetDesiredStateConfigurationPowerShellResourceFieldInfos(); - DesiredStateConfigurationDSCv3FieldInfos = GetDesiredStateConfigurationDSCv3FieldInfos(); - DesiredStateConfigurationDSCv3ResourceFieldInfos = GetDesiredStateConfigurationDSCv3ResourceFieldInfos(); - - resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos, VariantManifestPtr(&(m_manifest.get()))); - - if (!m_p_installersNode) - { - return resultErrors; - } - - // Populate installers - for (auto const& entry : m_p_installersNode->Sequence()) - { - ManifestInstaller installer = m_manifest.get().DefaultInstallerInfo; - -#define WINGET_STASH_INSTALLER_PROPERTY(_property_,_clear_) \ - auto stashed ## _property_ = std::move(installer. _property_); \ - installer. _property_ . _clear_ (); - -#define WINGET_UNSTASH_INSTALLER_PROPERTY(_property_) \ - installer. _property_ = std::move(stashed ## _property_); - - // Clear these defaults as PackageFamilyName, ProductCode, AppsAndFeaturesEntries need to be copied based on InstallerType - WINGET_STASH_INSTALLER_PROPERTY(PackageFamilyName, clear); - WINGET_STASH_INSTALLER_PROPERTY(ProductCode, clear); - WINGET_STASH_INSTALLER_PROPERTY(AppsAndFeaturesEntries, clear); - // Clear dependencies as installer specific overrides root - WINGET_STASH_INSTALLER_PROPERTY(Dependencies, Clear); - // Clear nested installers as it should only be copied for zip installerType. - installer.NestedInstallerType = InstallerTypeEnum::Unknown; - WINGET_STASH_INSTALLER_PROPERTY(NestedInstallerFiles, clear); - - auto errors = ValidateAndProcessFields(entry, InstallerFieldInfos, VariantManifestPtr(&installer)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - - // Set installer type back before attempting to use it in any of the EffectiveInstallerType calls below - if (IsArchiveType(installer.BaseInstallerType)) - { - if (installer.NestedInstallerFiles.empty()) - { - WINGET_UNSTASH_INSTALLER_PROPERTY(NestedInstallerFiles); - } - - if (installer.NestedInstallerType == InstallerTypeEnum::Unknown) - { - installer.NestedInstallerType = m_manifest.get().DefaultInstallerInfo.NestedInstallerType; - } - } - - // Copy in system reference strings from the root if not set in the installer and appropriate - if (installer.AppsAndFeaturesEntries.empty() && DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType())) - { - WINGET_UNSTASH_INSTALLER_PROPERTY(AppsAndFeaturesEntries); - } - - if (installer.PackageFamilyName.empty() && - (DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()) || - DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(installer.AppsAndFeaturesEntries))) - { - WINGET_UNSTASH_INSTALLER_PROPERTY(PackageFamilyName); - } - - if (installer.ProductCode.empty() && DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType())) - { - WINGET_UNSTASH_INSTALLER_PROPERTY(ProductCode); - } - - // If there are no dependencies on installer use default ones - if (!installer.Dependencies.HasAny()) - { - WINGET_UNSTASH_INSTALLER_PROPERTY(Dependencies); - } - - // Populate installer default switches if not exists - auto defaultSwitches = GetDefaultKnownSwitches(installer.EffectiveInstallerType()); - for (auto const& defaultSwitch : defaultSwitches) - { - if (installer.Switches.find(defaultSwitch.first) == installer.Switches.end()) - { - installer.Switches[defaultSwitch.first] = defaultSwitch.second; - } - } - - // Populate installer default return codes if not present in ExpectedReturnCodes and InstallerSuccessCodes - auto defaultReturnCodes = GetDefaultKnownReturnCodes(installer.EffectiveInstallerType()); - for (auto const& defaultReturnCode : defaultReturnCodes) - { - if (installer.ExpectedReturnCodes.find(defaultReturnCode.first) == installer.ExpectedReturnCodes.end() && - std::find(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end(), defaultReturnCode.first) == installer.InstallerSuccessCodes.end()) - { - installer.ExpectedReturnCodes[defaultReturnCode.first].ReturnResponseEnum = defaultReturnCode.second; - } - } - - m_manifest.get().Installers.emplace_back(std::move(installer)); - } - - // Populate additional localizations - if (m_p_localizationsNode && m_p_localizationsNode->IsSequence()) - { - for (auto const& entry : m_p_localizationsNode->Sequence()) - { - ManifestLocalization localization; - auto errors = ValidateAndProcessFields(entry, LocalizationFieldInfos, VariantManifestPtr(&localization)); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - m_manifest.get().Localizations.emplace_back(std::move(std::move(localization))); - } - } - - return resultErrors; - } - - ValidationErrors ManifestYamlPopulator::InsertShadow(const YAML::Node& shadowNode) - { - Manifest shadowManifest; - - // Process shadow node. - auto resultErrors = ValidateAndProcessFields(shadowNode, GetShadowRootFieldProcessInfo(), VariantManifestPtr(&shadowManifest)); - - // Merge. - if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) - { - // Default localization - if (Utility::ICUCaseInsensitiveEquals(m_manifest.get().DefaultLocalization.Locale, shadowManifest.DefaultLocalization.Locale)) - { - // Icons - if (!m_manifest.get().DefaultLocalization.Contains(Localization::Icons) && - shadowManifest.DefaultLocalization.Contains(Localization::Icons)) - { - m_manifest.get().DefaultLocalization.Add(std::move(shadowManifest.DefaultLocalization.Get())); - - YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; - key.SetScalar("Icons"); - YAML::Node value = shadowNode.GetChildNode("Icons"); - m_rootNode.get().AddMappingNode(std::move(key), std::move(value)); - } - } - - // Localizations - if (!shadowManifest.Localizations.empty()) - { - // Merge manifest object - for (auto const& shadowLocalization : shadowManifest.Localizations) - { - // Manifest - if (auto iter = std::find_if(m_manifest.get().Localizations.begin(), m_manifest.get().Localizations.end(), [&](auto const& l) { return Utility::ICUCaseInsensitiveEquals(l.Locale, shadowLocalization.Locale); }); iter != m_manifest.get().Localizations.end()) - { - if (!(*iter).Contains(Localization::Icons) && - shadowLocalization.Contains(Localization::Icons)) - { - (*iter).Add(std::move(shadowLocalization.Get())); - } - } - else - { - ManifestLocalization localization = shadowLocalization; - m_manifest.get().Localizations.emplace_back(std::move(std::move(localization))); - } - } - - // Merge yaml - auto shadowLocalizationsNode = shadowNode.GetChildNode("Localization"); - if (m_p_localizationsNode) - { - m_rootNode.get().GetChildNode("Localization").MergeSequenceNode(shadowLocalizationsNode, "PackageLocale", true); - } - else - { - YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; - key.SetScalar("Localization"); - m_rootNode.get().AddMappingNode(std::move(key), std::move(shadowLocalizationsNode)); - } - } - } - - return resultErrors; - } - - ValidationErrors ManifestYamlPopulator::PopulateManifest( - YAML::Node& rootNode, - Manifest& manifest, - const ManifestVer& manifestVersion, - ManifestValidateOption validateOption, - const std::optional& shadowNode) - { - ManifestYamlPopulator manifestPopulator(rootNode, manifest, manifestVersion, validateOption); - auto errors = manifestPopulator.PopulateManifestInternal(); - - if (shadowNode.has_value()) - { - auto shadowErrors = manifestPopulator.InsertShadow(shadowNode.value()); - std::move(shadowErrors.begin(), shadowErrors.end(), std::inserter(errors, errors.end())); - } - - return errors; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerSHA256.h" +#include "winget/ManifestYamlPopulator.h" + +namespace AppInstaller::Manifest +{ + using ValidationErrors = std::vector; + using ExpectedReturnCodeInfo = AppInstaller::Manifest::ManifestInstaller::ExpectedReturnCodeInfo; + + namespace + { + template + Ptr* variant_ptr(const VariantManifestPtr& v) { return std::get(v); } + + ManifestInstaller* GetManifestInstallerPtrFromManifest(const VariantManifestPtr& v) { return &(variant_ptr(v)->DefaultInstallerInfo); } + + ManifestLocalization* GetManifestLocalizationPtrFromManifest(const VariantManifestPtr& v) { return &(variant_ptr(v)->DefaultLocalization); } + + ManifestInstaller* GetManifestInstallerPtr(const VariantManifestPtr& v) + { + if (auto installer = std::get_if(&v)) + { + return *installer; + } + + return GetManifestInstallerPtrFromManifest(v); + } + + ManifestLocalization* GetManifestLocalizationPtr(const VariantManifestPtr& v) + { + if (auto localization = std::get_if(&v)) + { + return *localization; + } + + return GetManifestLocalizationPtrFromManifest(v); + } + + // Only used in preview manifest + std::vector SplitMultiValueField(const std::string& input) + { + if (input.empty()) + { + return {}; + } + + std::vector result; + size_t currentPos = 0; + while (currentPos < input.size()) + { + size_t splitPos = input.find(',', currentPos); + if (splitPos == std::string::npos) + { + splitPos = input.size(); + } + + std::string splitVal = input.substr(currentPos, splitPos - currentPos); + Utility::Trim(splitVal); + if (!splitVal.empty()) + { + result.emplace_back(std::move(splitVal)); + } + currentPos = splitPos + 1; + } + + return result; + } + + std::vector ProcessStringSequenceNode(const YAML::Node& node, bool trim = true) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + std::string value = entry.as(); + if (trim) + { + Utility::Trim(value); + } + + result.emplace_back(std::move(value)); + } + + return result; + } + + std::vector ProcessInstallerSuccessCodeSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(static_cast(entry.as())); + } + + return result; + } + + std::vector ProcessPlatformSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(ConvertToPlatformEnum(entry.as())); + } + + return result; + } + + std::vector ProcessInstallModeSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(ConvertToInstallModeEnum(entry.as())); + } + + return result; + } + + std::vector ProcessArchitectureSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(Utility::ConvertToArchitectureEnum(entry.as())); + } + + return result; + } + + std::vector ProcessUnsupportedArgumentsSequenceNode(const YAML::Node& node) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + std::vector result; + + for (auto const& entry : node.Sequence()) + { + result.emplace_back(ConvertToUnsupportedArgumentEnum(entry.as())); + } + + return result; + } + + void ProcessDependenciesNode(DependencyType type, const YAML::Node& node, DependencyList* dependencyList) + { + const auto& ids = ProcessStringSequenceNode(node); + for (auto id : ids) + { + dependencyList->Add(Dependency(type, id)); + } + } + } + + std::vector ManifestYamlPopulator::GetRootFieldProcessInfo() + { + // Common fields across versions + std::vector result = + { + { "ManifestVersion", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { /* ManifestVersion already populated. Field listed here for duplicate and PascalCase check */ return {}; } }, + { "Installers", [this](const YAML::Node& value, const VariantManifestPtr&)->ValidationErrors { m_p_installersNode = &value; return {}; } }, + { "Localization", [this](const YAML::Node& value, const VariantManifestPtr&)->ValidationErrors { m_p_localizationsNode = &value; return {}; } }, + { "Channel", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Channel = Utility::Trim(value.as()); return {}; } }, + }; + + // Additional version specific fields + if (m_manifestVersion.get().Major() == 0) + { + std::vector previewRootFields + { + { "Id", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Id = Utility::Trim(value.as()); return {}; } }, + { "Version", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Version = Utility::Trim(value.as()); return {}; } }, + { "AppMoniker", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Moniker = Utility::Trim(value.as()); return {}; } }, + }; + + + std::move(previewRootFields.begin(), previewRootFields.end(), std::inserter(result, result.end())); + } + else if (m_manifestVersion.get().Major() == 1) + { + // Starting v1, we should be only adding new fields for each minor version increase + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) + { + std::vector v1RootFields + { + { "PackageIdentifier", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Id = Utility::Trim(value.as()); return {}; } }, + { "PackageVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Version = Utility::Trim(value.as()); return {}; } }, + { "Moniker", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Moniker = Utility::Trim(value.as()); return {}; } }, + { "ManifestType", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { /* ManifestType already checked. Field listed here for duplicate and PascalCase check */ return {}; } }, + }; + + std::move(v1RootFields.begin(), v1RootFields.end(), std::inserter(result, result.end())); + } + } + + // Root fields mapped as Installer and Localization values + auto rootInstallerFields = GetInstallerFieldProcessInfo(true); + std::move(rootInstallerFields.begin(), rootInstallerFields.end(), std::inserter(result, result.end())); + + auto rootLocalizationFields = GetLocalizationFieldProcessInfo(true); + std::move(rootLocalizationFields.begin(), rootLocalizationFields.end(), std::inserter(result, result.end())); + + return result; + } + + std::vector ManifestYamlPopulator::GetInstallerFieldProcessInfo(bool forRootFields) + { + // Common fields across versions + std::vector result = + { + { "InstallerType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->BaseInstallerType = ConvertToInstallerTypeEnum(value.as()); return {}; } }, + { "PackageFamilyName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->PackageFamilyName = value.as(); return {}; } }, + { "ProductCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ProductCode = value.as(); return {}; } }, + }; + + // Additional version specific fields + if (m_manifestVersion.get().Major() == 0) + { + // Root level and Localization node level + std::vector previewCommonFields = + { + { "UpdateBehavior", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UpdateBehavior = ConvertToUpdateBehaviorEnum(value.as()); return {}; } }, + { "Switches", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, SwitchesFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->Switches))); }}, + }; + + std::move(previewCommonFields.begin(), previewCommonFields.end(), std::inserter(result, result.end())); + + if (!forRootFields) + { + // Installer node only + std::vector installerOnlyFields = + { + { "Arch", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } }, + { "Url", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = value.as(); return {}; } }, + { "Sha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + { "SignatureSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + { "Language", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Locale = value.as(); return {}; } }, + { "Scope", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Scope = ConvertToScopeEnum(value.as()); return {}; } }, + }; + + if (m_manifestVersion.get().HasExtension(s_MSStoreExtension)) + { + installerOnlyFields.emplace_back("ProductId", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ProductId = value.as(); return {}; }); + } + + std::move(installerOnlyFields.begin(), installerOnlyFields.end(), std::inserter(result, result.end())); + } + else + { + // Root node only + std::vector rootOnlyFields = + { + { "MinOSVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->MinOSVersion = value.as(); return {}; } }, + { "Commands", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->Commands = SplitMultiValueField(value.as()); return {}; } }, + { "Protocols", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->Protocols = SplitMultiValueField(value.as()); return {}; } }, + { "FileExtensions", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtrFromManifest(v)->FileExtensions = SplitMultiValueField(value.as()); return {}; } }, + }; + + std::move(rootOnlyFields.begin(), rootOnlyFields.end(), std::inserter(result, result.end())); + } + } + else if (m_manifestVersion.get().Major() == 1) + { + // Starting v1, we should be only adding new fields for each minor version increase + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) + { + // Root level and Installer node level + std::vector v1CommonFields = + { + { "InstallerLocale", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Locale = value.as(); return {}; } }, + { "Platform", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Platform = ProcessPlatformSequenceNode(value); return {}; } }, + { "MinimumOSVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->MinOSVersion = value.as(); return {}; } }, + { "Scope", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Scope = ConvertToScopeEnum(value.as()); return {}; } }, + { "InstallModes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallModes = ProcessInstallModeSequenceNode(value); return {}; } }, + { "InstallerSwitches", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, SwitchesFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->Switches))); }}, + { "InstallerSuccessCodes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallerSuccessCodes = ProcessInstallerSuccessCodeSequenceNode(value); return {}; } }, + { "UpgradeBehavior", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UpdateBehavior = ConvertToUpdateBehaviorEnum(value.as()); return {}; } }, + { "Commands", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Commands = ProcessStringSequenceNode(value); return {}; } }, + { "Protocols", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Protocols = ProcessStringSequenceNode(value); return {}; } }, + { "FileExtensions", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->FileExtensions = ProcessStringSequenceNode(value); return {}; } }, + { "Dependencies", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, DependenciesFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->Dependencies))); }}, + { "Capabilities", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->Capabilities = ProcessStringSequenceNode(value); return {}; } }, + { "RestrictedCapabilities", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->RestrictedCapabilities = ProcessStringSequenceNode(value); return {}; } }, + }; + + std::move(v1CommonFields.begin(), v1CommonFields.end(), std::inserter(result, result.end())); + + if (!forRootFields) + { + // Installer level only fields + std::vector v1InstallerFields = + { + { "Architecture", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Arch = Utility::ConvertToArchitectureEnum(value.as()); return {}; } }, + { "InstallerUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = value.as(); return {}; } }, + { "InstallerSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + { "SignatureSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SignatureSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + // No custom validation needed at field populating time since we have semantic validation later to block msstore and productId from community repo. + { "MSStoreProductIdentifier", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ProductId = value.as(); return {}; } }, + }; + + std::move(v1InstallerFields.begin(), v1InstallerFields.end(), std::inserter(result, result.end())); + } + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) + { + std::vector fields_v1_1 = + { + { "InstallerAbortsTerminal", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallerAbortsTerminal = value.as(); return {}; } }, + { "InstallLocationRequired", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->InstallLocationRequired = value.as(); return {}; } }, + { "RequireExplicitUpgrade", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->RequireExplicitUpgrade = value.as(); return {}; } }, + { "ReleaseDate", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ReleaseDate = Utility::Trim(value.as()); return {}; } }, + { "UnsupportedOSArchitectures", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UnsupportedOSArchitectures = ProcessArchitectureSequenceNode(value); return {}; } }, + { "ElevationRequirement", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ElevationRequirement = ConvertToElevationRequirementEnum(value.as()); return {}; } }, + { "Markets", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessMarketsNode(value, GetManifestInstallerPtr(v)); } }, + { "AppsAndFeaturesEntries", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessAppsAndFeaturesEntriesNode(value, GetManifestInstallerPtr(v)); } }, + { "ExpectedReturnCodes", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessExpectedReturnCodesNode(value, GetManifestInstallerPtr(v)); } }, + }; + + std::move(fields_v1_1.begin(), fields_v1_1.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) + { + std::vector fields_v1_2 = + { + { "UnsupportedArguments", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->UnsupportedArguments = ProcessUnsupportedArgumentsSequenceNode(value); return {}; } }, + { "DisplayInstallWarnings", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->DisplayInstallWarnings = value.as(); return {}; } }, + }; + + std::move(fields_v1_2.begin(), fields_v1_2.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) + { + std::vector fields_v1_4 = + { + { "NestedInstallerType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->NestedInstallerType = ConvertToInstallerTypeEnum(value.as()); return {}; } }, + { "NestedInstallerFiles", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessNestedInstallerFilesNode(value, GetManifestInstallerPtr(v)); } }, + { "InstallationMetadata", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ValidateAndProcessFields(value, InstallationMetadataFieldInfos, VariantManifestPtr(&(GetManifestInstallerPtr(v)->InstallationMetadata))); }}, + }; + + std::move(fields_v1_4.begin(), fields_v1_4.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_6 }) + { + std::vector fields_v1_6 = + { + { "DownloadCommandProhibited", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->DownloadCommandProhibited = value.as(); return {}; }, true }, + }; + + std::move(fields_v1_6.begin(), fields_v1_6.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_7 }) + { + std::vector fields_v1_7 = + { + { "RepairBehavior", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->RepairBehavior = ConvertToRepairBehaviorEnum(value.as()); return {}; } }, + }; + + std::move(fields_v1_7.begin(), fields_v1_7.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_9 }) + { + std::vector fields_v1_9 = + { + { "ArchiveBinariesDependOnPath", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestInstallerPtr(v)->ArchiveBinariesDependOnPath = value.as(); return {}; } }, + }; + + 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())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + std::vector fields_v1_28 = + { + { "DesiredStateConfiguration", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors + { + auto* installer = GetManifestInstallerPtr(v); + installer->DesiredStateConfiguration.clear(); + return ValidateAndProcessFields(value, DesiredStateConfigurationFieldInfos, VariantManifestPtr(&(installer->DesiredStateConfiguration))); + } + }, + }; + + std::move(fields_v1_28.begin(), fields_v1_28.end(), std::inserter(result, result.end())); + } + } + + return result; + } + + std::vector ManifestYamlPopulator::GetSwitchesFieldProcessInfo() + { + // Common fields across versions + std::vector result = + { + { "Custom", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Custom] = value.as(); return{}; } }, + { "Silent", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Silent] = value.as(); return{}; } }, + { "SilentWithProgress", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::SilentWithProgress] = value.as(); return{}; } }, + { "Interactive", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Interactive] = value.as(); return{}; } }, + { "Log", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Log] = value.as(); return{}; } }, + { "InstallLocation", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::InstallLocation] = value.as(); return{}; } }, + }; + + // Additional version specific fields + if (m_manifestVersion.get().Major() == 0) + { + // Language only exists in preview manifests. Though we don't use it in our code yet, keep it here to be consistent with schema. + result.emplace_back("Language", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Language] = value.as(); return{}; }); + result.emplace_back("Update", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Update] = value.as(); return{}; }); + } + else if (m_manifestVersion.get().Major() == 1) + { + result.emplace_back("Upgrade", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Update] = value.as(); return{}; }); + + if (m_manifestVersion.get() >= ManifestVer{s_ManifestVersionV1_7}) + { + result.emplace_back("Repair", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { (*variant_ptr>(v))[InstallerSwitchType::Repair] = value.as(); return{}; }); + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetExpectedReturnCodesFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) + { + result.emplace_back("InstallerReturnCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->InstallerReturnCode = static_cast(value.as()); return {}; }); + result.emplace_back("ReturnResponse", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ReturnResponse = ConvertToExpectedReturnCodeEnum(value.as()); return {}; }); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) + { + result.emplace_back("ReturnResponseUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ReturnResponseUrl = value.as(); return {}; }); + } + + return result; + } + + std::vector ManifestYamlPopulator::GetLocalizationFieldProcessInfo(bool forRootFields) + { + // Common fields across versions + std::vector result = + { + { "Description", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(Utility::Trim(value.as())); return {}; } }, + { "LicenseUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + }; + + // Additional version specific fields + if (m_manifestVersion.get().Major() == 0) + { + // Root level and Localization node level + result.emplace_back("Homepage", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; }); + + if (!forRootFields) + { + // Localization node only + result.emplace_back("Language", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Locale = value.as(); return {}; }); + } + else + { + // Root node only + std::vector rootOnlyFields = + { + { "Name", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(Utility::Trim(value.as())); return {}; } }, + { "Publisher", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(value.as()); return {}; } }, + { "Author", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(value.as()); return {}; } }, + { "License", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(value.as()); return {}; } }, + { "Tags", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtrFromManifest(v)->Add(SplitMultiValueField(value.as())); return {}; } }, + }; + + std::move(rootOnlyFields.begin(), rootOnlyFields.end(), std::inserter(result, result.end())); + } + } + else if (m_manifestVersion.get().Major() == 1) + { + // Starting v1, we should be only adding new fields for each minor version increase + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) + { + // Root level and Localization node level + std::vector v1CommonFields = + { + { "PackageLocale", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Locale = value.as(); return {}; } }, + { "Publisher", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "PublisherUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "PublisherSupportUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "PrivacyUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "Author", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "PackageName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(Utility::Trim(value.as())); return {}; } }, + { "PackageUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "License", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "Copyright", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "CopyrightUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "ShortDescription", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(Utility::Trim(value.as())); return {}; } }, + { "Tags", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(ProcessStringSequenceNode(value)); return {}; } }, + }; + + std::move(v1CommonFields.begin(), v1CommonFields.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) + { + std::vector fields_v1_1 = + { + { "Agreements", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessAgreementsNode(value, GetManifestLocalizationPtr(v)); } }, + { "ReleaseNotes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "ReleaseNotesUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + }; + + std::move(fields_v1_1.begin(), fields_v1_1.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) + { + std::vector fields_v1_2 = + { + { "PurchaseUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "InstallationNotes", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Add(value.as()); return {}; } }, + { "Documentations", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDocumentationsNode(value, GetManifestLocalizationPtr(v)); } }, + }; + + std::move(fields_v1_2.begin(), fields_v1_2.end(), std::inserter(result, result.end())); + } + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) + { + std::vector fields_v1_5 = + { + { "Icons", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessIconsNode(value, GetManifestLocalizationPtr(v)); }, true }, + }; + + std::move(fields_v1_5.begin(), fields_v1_5.end(), std::inserter(result, result.end())); + } + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDependenciesFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) + { + result = + { + { "WindowsFeatures", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessDependenciesNode(DependencyType::WindowsFeature, value, variant_ptr(v)); return {}; } }, + { "WindowsLibraries", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessDependenciesNode(DependencyType::WindowsLibrary, value, variant_ptr(v)); return {}; } }, + { "PackageDependencies", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessPackageDependenciesNode(value, variant_ptr(v)); return {}; } }, + { "ExternalDependencies", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { ProcessDependenciesNode(DependencyType::External, value, variant_ptr(v)); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetPackageDependenciesFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1 }) + { + result = + { + { "PackageIdentifier", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->SetId(Utility::Trim(value.as())); return {}; } }, + { "MinimumVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->MinVersion = Utility::Version(Utility::Trim(value.as())); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetAgreementFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) + { + result = + { + { "AgreementLabel", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Label = Utility::Trim(value.as()); return {}; } }, + { "Agreement", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->AgreementText = Utility::Trim(value.as()); return {}; }, true }, + { "AgreementUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->AgreementUrl = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetMarketsFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) + { + result = + { + { "AllowedMarkets", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->AllowedMarkets = ProcessStringSequenceNode(value); return {}; } }, + { "ExcludedMarkets", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ExcludedMarkets = ProcessStringSequenceNode(value); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetAppsAndFeaturesEntryFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_1 }) + { + result = + { + { "DisplayName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DisplayName = Utility::Trim(value.as()); return {}; } }, + { "Publisher", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Publisher = Utility::Trim(value.as()); return {}; } }, + { "DisplayVersion", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DisplayVersion = Utility::Trim(value.as()); return {}; } }, + { "ProductCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ProductCode = Utility::Trim(value.as()); return {}; } }, + { "UpgradeCode", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->UpgradeCode = Utility::Trim(value.as()); return {}; } }, + { "InstallerType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->InstallerType = ConvertToInstallerTypeEnum(value.as()); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDocumentationFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_2 }) + { + result = + { + { "DocumentLabel", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DocumentLabel = Utility::Trim(value.as()); return {}; } }, + { "DocumentUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DocumentUrl = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetIconFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) + { + result = + { + { "IconUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Url = Utility::Trim(value.as()); return {}; } }, + { "IconFileType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->FileType = ConvertToIconFileTypeEnum(value.as()); return {}; } }, + { "IconResolution", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Resolution = ConvertToIconResolutionEnum(value.as()); return {}; } }, + { "IconTheme", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Theme = ConvertToIconThemeEnum(value.as()); return {}; } }, + { "IconSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Sha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetNestedInstallerFileFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) + { + result = + { + { "RelativeFilePath", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RelativeFilePath = Utility::Trim(value.as()); return {}; } }, + { "PortableCommandAlias", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->PortableCommandAlias = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetInstallationMetadataFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) + { + result = + { + { "DefaultInstallLocation", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DefaultInstallLocation = Utility::Trim(value.as()); return {}; } }, + { "Files", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessInstallationMetadataFilesNode(value, variant_ptr(v)); } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetInstallationMetadataFilesFieldProcessInfo() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_4 }) + { + result = + { + { "RelativeFilePath", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RelativeFilePath = Utility::Trim(value.as()); return {}; } }, + { "FileSha256", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->FileSha256 = Utility::SHA256::ConvertToBytes(value.as()); return {}; } }, + { "FileType", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->FileType = ConvertToInstalledFileTypeEnum(value.as()); return {}; } }, + { "InvocationParameter", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->InvocationParameter = Utility::Trim(value.as()); return {}; } }, + { "DisplayName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->DisplayName = Utility::Trim(value.as()); return {}; } }, + }; + } + + 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; + + if (m_manifestVersion.get().Major() == 1) + { + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) + { + std::vector fields_v1_5 = + { + { + { "Localization", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessShadowLocalizationNode(value, variant_ptr(v)); } }, + { "ManifestType", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, + { "PackageIdentifier", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, + { "PackageVersion", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, + { "ManifestVersion", [](const YAML::Node&, const VariantManifestPtr&)->ValidationErrors { return {}; } }, + }, + }; + + std::move(fields_v1_5.begin(), fields_v1_5.end(), std::inserter(result, result.end())); + } + } + + auto rootLocalizationFields = GetShadowLocalizationFieldProcessInfo(); + std::move(rootLocalizationFields.begin(), rootLocalizationFields.end(), std::inserter(result, result.end())); + + return result; + } + + std::vector ManifestYamlPopulator::GetShadowLocalizationFieldProcessInfo() + { + std::vector result; + + if (m_manifestVersion.get().Major() == 1) + { + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) + { + std::vector fields_v1_5 = + { + { "PackageLocale", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { GetManifestLocalizationPtr(v)->Locale = value.as(); return {}; } }, + { "Icons", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessIconsNode(value, GetManifestLocalizationPtr(v)); } }, + }; + + std::move(fields_v1_5.begin(), fields_v1_5.end(), std::inserter(result, result.end())); + } + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "PowerShell", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSC_PowerShellModuleNode(value, variant_ptr>(v)); } }, + { "DSCv3", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors + { + auto* variantValue = variant_ptr>(v); + variantValue->emplace_back(DesiredStateConfigurationContainerType::DSCv3); + return ValidateAndProcessFields(value, DesiredStateConfigurationDSCv3FieldInfos, VariantManifestPtr(&variantValue->back())); + } + }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationPowerShellModuleFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "RepositoryUrl", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->RepositoryURL = Utility::Trim(value.as()); return {}; } }, + { "ModuleName", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->ModuleName = Utility::Trim(value.as()); return {}; } }, + { "Resources", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSC_PowerShellResourcesNode(value, variant_ptr(v)); } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationPowerShellResourceFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "Name", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Name = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationDSCv3FieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "Resources", [this](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { return ProcessDSCv3ResourcesNode(value, variant_ptr(v)); } }, + }; + } + + return result; + } + + std::vector ManifestYamlPopulator::GetDesiredStateConfigurationDSCv3ResourceFieldInfos() + { + std::vector result = {}; + + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_28 }) + { + result = + { + { "Type", [](const YAML::Node& value, const VariantManifestPtr& v)->ValidationErrors { variant_ptr(v)->Name = Utility::Trim(value.as()); return {}; } }, + }; + } + + return result; + } + + ValidationErrors ManifestYamlPopulator::ValidateAndProcessFields( + const YAML::Node& rootNode, + const std::vector& fieldInfos, + const VariantManifestPtr& v) + { + ValidationErrors resultErrors; + + if (!rootNode.IsMap() || rootNode.size() == 0) + { + resultErrors.emplace_back(ManifestError::InvalidRootNode, "", "", m_isMergedManifest ? 0 : rootNode.Mark().line, m_isMergedManifest ? 0 : rootNode.Mark().column); + return resultErrors; + } + + // Keeps track of already processed fields. Used to check duplicate fields. + std::set processedFields; + + for (auto const& keyValuePair : rootNode.Mapping()) + { + std::string key = keyValuePair.first.as(); + const YAML::Node& valueNode = keyValuePair.second; + + // We'll do case-insensitive search first and validate correct case later. + auto fieldIter = std::find_if(fieldInfos.begin(), fieldInfos.end(), + [&](auto const& s) + { + return Utility::CaseInsensitiveEquals(s.Name, key); + }); + + if (fieldIter != fieldInfos.end()) + { + const FieldProcessInfo& fieldInfo = *fieldIter; + + // Make sure the found key is in Pascal Case + if (key != fieldInfo.Name) + { + resultErrors.emplace_back(ManifestError::FieldIsNotPascalCase, key, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column); + } + + // Make sure it's not a duplicate key + if (!processedFields.insert(fieldInfo.Name).second) + { + resultErrors.emplace_back(ManifestError::FieldDuplicate, fieldInfo.Name, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column); + } + + if (fieldInfo.RequireVerifiedPublisher) + { + resultErrors.emplace_back(ManifestError::FieldRequireVerifiedPublisher, fieldInfo.Name, "", + m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column, + m_validateOption.ErrorOnVerifiedPublisherFields ? ValidationError::Level::Error : ValidationError::Level::Warning); + } + + if (!valueNode.IsNull()) + { + try + { + auto errors = fieldInfo.ProcessFunc(valueNode, v); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + catch (const std::exception&) + { + resultErrors.emplace_back(ManifestError::FieldFailedToProcess, fieldInfo.Name); + } + } + } + else + { + // For full validation, also reports unrecognized fields as warning + if (m_validateOption.FullValidation) + { + resultErrors.emplace_back(ManifestError::FieldUnknown, key, "", m_isMergedManifest ? 0 : keyValuePair.first.Mark().line, m_isMergedManifest ? 0 : keyValuePair.first.Mark().column, ValidationError::Level::Warning); + } + } + } + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::ProcessPackageDependenciesNode(const YAML::Node& rootNode, DependencyList* dependencyList) + { + ValidationErrors resultErrors; + + for (auto const& entry : rootNode.Sequence()) + { + Dependency packageDependency = Dependency(DependencyType::Package); + auto errors = ValidateAndProcessFields(entry, PackageDependenciesFieldInfos, VariantManifestPtr(&packageDependency)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + dependencyList->Add(std::move(packageDependency)); + } + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::ProcessAgreementsNode(const YAML::Node& agreementsNode, ManifestLocalization* localization) + { + THROW_HR_IF(E_INVALIDARG, !agreementsNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector agreements; + + for (auto const& entry : agreementsNode.Sequence()) + { + Agreement agreement; + auto errors = ValidateAndProcessFields(entry, AgreementFieldInfos, VariantManifestPtr(&agreement)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + agreements.emplace_back(std::move(agreement)); + } + + if (!agreements.empty()) + { + localization->Add(std::move(agreements)); + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessMarketsNode(const YAML::Node& marketsNode, ManifestInstaller* installer) + { + MarketsInfo markets; + auto errors = ValidateAndProcessFields(marketsNode, MarketsFieldInfos, VariantManifestPtr(&markets)); + installer->Markets = markets; + return errors; + } + + std::vector ManifestYamlPopulator::ProcessAppsAndFeaturesEntriesNode(const YAML::Node& appsAndFeaturesEntriesNode, ManifestInstaller* installer) + { + THROW_HR_IF(E_INVALIDARG, !appsAndFeaturesEntriesNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector appsAndFeaturesEntries; + + for (auto const& entry : appsAndFeaturesEntriesNode.Sequence()) + { + AppsAndFeaturesEntry appsAndFeaturesEntry; + auto errors = ValidateAndProcessFields(entry, AppsAndFeaturesEntryFieldInfos, VariantManifestPtr(&appsAndFeaturesEntry)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + appsAndFeaturesEntries.emplace_back(std::move(appsAndFeaturesEntry)); + } + + installer->AppsAndFeaturesEntries = appsAndFeaturesEntries; + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::ProcessExpectedReturnCodesNode(const YAML::Node& returnCodesNode, ManifestInstaller* installer) + { + THROW_HR_IF(E_INVALIDARG, !returnCodesNode.IsSequence()); + + ValidationErrors resultErrors; + std::map returnCodes; + + for (auto const& entry : returnCodesNode.Sequence()) + { + ExpectedReturnCode returnCode; + auto errors = ValidateAndProcessFields(entry, ExpectedReturnCodesFieldInfos, VariantManifestPtr(&returnCode)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + if (!returnCodes.insert({ returnCode.InstallerReturnCode, {returnCode.ReturnResponse, returnCode.ReturnResponseUrl} }).second) + { + resultErrors.emplace_back(ManifestError::DuplicateReturnCodeEntry); + } + } + + installer->ExpectedReturnCodes = returnCodes; + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::ProcessDocumentationsNode(const YAML::Node& documentationsNode, ManifestLocalization* localization) + { + THROW_HR_IF(E_INVALIDARG, !documentationsNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector documentations; + + for (auto const& entry : documentationsNode.Sequence()) + { + Documentation documentation; + auto errors = ValidateAndProcessFields(entry, DocumentationFieldInfos, VariantManifestPtr(&documentation)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + documentations.emplace_back(std::move(documentation)); + } + + if (!documentations.empty()) + { + localization->Add(std::move(documentations)); + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessIconsNode(const YAML::Node& iconsNode, ManifestLocalization* localization) + { + THROW_HR_IF(E_INVALIDARG, !iconsNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector icons; + + for (auto const& entry : iconsNode.Sequence()) + { + Icon icon; + auto errors = ValidateAndProcessFields(entry, IconFieldInfos, VariantManifestPtr(&icon)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + icons.emplace_back(std::move(icon)); + } + + if (!icons.empty()) + { + localization->Add(std::move(icons)); + } + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::ProcessNestedInstallerFilesNode(const YAML::Node& nestedInstallerFilesNode, ManifestInstaller* installer) + { + THROW_HR_IF(E_INVALIDARG, !nestedInstallerFilesNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector nestedInstallerFiles; + + for (auto const& entry : nestedInstallerFilesNode.Sequence()) + { + NestedInstallerFile nestedInstallerFile; + auto errors = ValidateAndProcessFields(entry, NestedInstallerFileFieldInfos, VariantManifestPtr(&nestedInstallerFile)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + nestedInstallerFiles.emplace_back(std::move(nestedInstallerFile)); + } + + if (!nestedInstallerFiles.empty()) + { + installer->NestedInstallerFiles = nestedInstallerFiles; + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessInstallationMetadataFilesNode(const YAML::Node& installedFilesNode, InstallationMetadataInfo* installationMetadata) + { + THROW_HR_IF(E_INVALIDARG, !installedFilesNode.IsSequence()); + + ValidationErrors resultErrors; + std::vector installedFiles; + + for (auto const& entry : installedFilesNode.Sequence()) + { + InstalledFile installedFile; + auto errors = ValidateAndProcessFields(entry, InstallationMetadataFilesFieldInfos, VariantManifestPtr(&installedFile)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + installedFiles.emplace_back(std::move(installedFile)); + } + + if (!installedFiles.empty()) + { + installationMetadata->Files = installedFiles; + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessShadowLocalizationNode(const YAML::Node& localizationNode, Manifest* manifest) + { + THROW_HR_IF(E_INVALIDARG, !localizationNode.IsSequence()); + + ValidationErrors resultErrors; + auto shadowLocalizationFields = GetShadowLocalizationFieldProcessInfo(); + + for (auto const& entry : localizationNode.Sequence()) + { + ManifestLocalization localization; + auto errors = ValidateAndProcessFields(entry, shadowLocalizationFields, VariantManifestPtr(&localization)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + manifest->Localizations.emplace_back(std::move(std::move(localization))); + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessDSC_PowerShellModuleNode(const YAML::Node& node, std::vector* containers) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + ValidationErrors resultErrors; + + for (auto const& entry : node.Sequence()) + { + auto& containerInfo = containers->emplace_back(DesiredStateConfigurationContainerType::PowerShell); + auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationPowerShellModuleFieldInfos, VariantManifestPtr(&containerInfo)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessDSC_PowerShellResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + ValidationErrors resultErrors; + + for (auto const& entry : node.Sequence()) + { + auto& resourceInfo = container->Resources.emplace_back(); + auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationPowerShellResourceFieldInfos, VariantManifestPtr(&resourceInfo)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + return resultErrors; + } + + std::vector ManifestYamlPopulator::ProcessDSCv3ResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container) + { + THROW_HR_IF(E_INVALIDARG, !node.IsSequence()); + + ValidationErrors resultErrors; + + for (auto const& entry : node.Sequence()) + { + auto& resourceInfo = container->Resources.emplace_back(); + auto errors = ValidateAndProcessFields(entry, DesiredStateConfigurationDSCv3ResourceFieldInfos, VariantManifestPtr(&resourceInfo)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + return resultErrors; + } + + + ManifestYamlPopulator::ManifestYamlPopulator(YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, ManifestValidateOption validateOption) : + m_rootNode(rootNode), m_manifest(manifest), m_manifestVersion(manifestVersion), m_validateOption(validateOption) + { + m_isMergedManifest = !m_rootNode.get()["ManifestType"sv].IsNull() && m_rootNode.get()["ManifestType"sv].as() == "merged"; + m_manifest.get().ManifestVersion = m_manifestVersion; + } + + ValidationErrors ManifestYamlPopulator::PopulateManifestInternal() + { + const YAML::Node& rootNode = m_rootNode; + ValidationErrors resultErrors; + + // Prepare field infos + RootFieldInfos = GetRootFieldProcessInfo(); + InstallerFieldInfos = GetInstallerFieldProcessInfo(); + SwitchesFieldInfos = GetSwitchesFieldProcessInfo(); + ExpectedReturnCodesFieldInfos = GetExpectedReturnCodesFieldProcessInfo(); + DependenciesFieldInfos = GetDependenciesFieldProcessInfo(); + PackageDependenciesFieldInfos = GetPackageDependenciesFieldProcessInfo(); + LocalizationFieldInfos = GetLocalizationFieldProcessInfo(); + AgreementFieldInfos = GetAgreementFieldProcessInfo(); + MarketsFieldInfos = GetMarketsFieldProcessInfo(); + AppsAndFeaturesEntryFieldInfos = GetAppsAndFeaturesEntryFieldProcessInfo(); + DocumentationFieldInfos = GetDocumentationFieldProcessInfo(); + IconFieldInfos = GetIconFieldProcessInfo(); + NestedInstallerFileFieldInfos = GetNestedInstallerFileFieldProcessInfo(); + InstallationMetadataFieldInfos = GetInstallationMetadataFieldProcessInfo(); + InstallationMetadataFilesFieldInfos = GetInstallationMetadataFilesFieldProcessInfo(); + AuthenticationFieldInfos = GetAuthenticationFieldInfos(); + MicrosoftEntraIdAuthenticationInfoFieldInfos = GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); + DesiredStateConfigurationFieldInfos = GetDesiredStateConfigurationFieldInfos(); + DesiredStateConfigurationPowerShellModuleFieldInfos = GetDesiredStateConfigurationPowerShellModuleFieldInfos(); + DesiredStateConfigurationPowerShellResourceFieldInfos = GetDesiredStateConfigurationPowerShellResourceFieldInfos(); + DesiredStateConfigurationDSCv3FieldInfos = GetDesiredStateConfigurationDSCv3FieldInfos(); + DesiredStateConfigurationDSCv3ResourceFieldInfos = GetDesiredStateConfigurationDSCv3ResourceFieldInfos(); + + resultErrors = ValidateAndProcessFields(rootNode, RootFieldInfos, VariantManifestPtr(&(m_manifest.get()))); + + if (!m_p_installersNode) + { + return resultErrors; + } + + // Populate installers + for (auto const& entry : m_p_installersNode->Sequence()) + { + ManifestInstaller installer = m_manifest.get().DefaultInstallerInfo; + +#define WINGET_STASH_INSTALLER_PROPERTY(_property_,_clear_) \ + auto stashed ## _property_ = std::move(installer. _property_); \ + installer. _property_ . _clear_ (); + +#define WINGET_UNSTASH_INSTALLER_PROPERTY(_property_) \ + installer. _property_ = std::move(stashed ## _property_); + + // Clear these defaults as PackageFamilyName, ProductCode, AppsAndFeaturesEntries need to be copied based on InstallerType + WINGET_STASH_INSTALLER_PROPERTY(PackageFamilyName, clear); + WINGET_STASH_INSTALLER_PROPERTY(ProductCode, clear); + WINGET_STASH_INSTALLER_PROPERTY(AppsAndFeaturesEntries, clear); + // Clear dependencies as installer specific overrides root + WINGET_STASH_INSTALLER_PROPERTY(Dependencies, Clear); + // Clear nested installers as it should only be copied for zip installerType. + installer.NestedInstallerType = InstallerTypeEnum::Unknown; + WINGET_STASH_INSTALLER_PROPERTY(NestedInstallerFiles, clear); + + auto errors = ValidateAndProcessFields(entry, InstallerFieldInfos, VariantManifestPtr(&installer)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + + // Set installer type back before attempting to use it in any of the EffectiveInstallerType calls below + if (IsArchiveType(installer.BaseInstallerType)) + { + if (installer.NestedInstallerFiles.empty()) + { + WINGET_UNSTASH_INSTALLER_PROPERTY(NestedInstallerFiles); + } + + if (installer.NestedInstallerType == InstallerTypeEnum::Unknown) + { + installer.NestedInstallerType = m_manifest.get().DefaultInstallerInfo.NestedInstallerType; + } + } + + // Copy in system reference strings from the root if not set in the installer and appropriate + if (installer.AppsAndFeaturesEntries.empty() && DoesInstallerTypeWriteAppsAndFeaturesEntry(installer.EffectiveInstallerType())) + { + WINGET_UNSTASH_INSTALLER_PROPERTY(AppsAndFeaturesEntries); + } + + if (installer.PackageFamilyName.empty() && + (DoesInstallerTypeUsePackageFamilyName(installer.EffectiveInstallerType()) || + DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(installer.AppsAndFeaturesEntries))) + { + WINGET_UNSTASH_INSTALLER_PROPERTY(PackageFamilyName); + } + + if (installer.ProductCode.empty() && DoesInstallerTypeUseProductCode(installer.EffectiveInstallerType())) + { + WINGET_UNSTASH_INSTALLER_PROPERTY(ProductCode); + } + + // If there are no dependencies on installer use default ones + if (!installer.Dependencies.HasAny()) + { + WINGET_UNSTASH_INSTALLER_PROPERTY(Dependencies); + } + + // Populate installer default switches if not exists + auto defaultSwitches = GetDefaultKnownSwitches(installer.EffectiveInstallerType()); + for (auto const& defaultSwitch : defaultSwitches) + { + if (installer.Switches.find(defaultSwitch.first) == installer.Switches.end()) + { + installer.Switches[defaultSwitch.first] = defaultSwitch.second; + } + } + + // Populate installer default return codes if not present in ExpectedReturnCodes and InstallerSuccessCodes + auto defaultReturnCodes = GetDefaultKnownReturnCodes(installer.EffectiveInstallerType()); + for (auto const& defaultReturnCode : defaultReturnCodes) + { + if (installer.ExpectedReturnCodes.find(defaultReturnCode.first) == installer.ExpectedReturnCodes.end() && + std::find(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end(), defaultReturnCode.first) == installer.InstallerSuccessCodes.end()) + { + installer.ExpectedReturnCodes[defaultReturnCode.first].ReturnResponseEnum = defaultReturnCode.second; + } + } + + m_manifest.get().Installers.emplace_back(std::move(installer)); + } + + // Populate additional localizations + if (m_p_localizationsNode && m_p_localizationsNode->IsSequence()) + { + for (auto const& entry : m_p_localizationsNode->Sequence()) + { + ManifestLocalization localization; + auto errors = ValidateAndProcessFields(entry, LocalizationFieldInfos, VariantManifestPtr(&localization)); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + m_manifest.get().Localizations.emplace_back(std::move(std::move(localization))); + } + } + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::InsertShadow(const YAML::Node& shadowNode) + { + Manifest shadowManifest; + + // Process shadow node. + auto resultErrors = ValidateAndProcessFields(shadowNode, GetShadowRootFieldProcessInfo(), VariantManifestPtr(&shadowManifest)); + + // Merge. + if (m_manifestVersion.get() >= ManifestVer{ s_ManifestVersionV1_5 }) + { + // Default localization + if (Utility::ICUCaseInsensitiveEquals(m_manifest.get().DefaultLocalization.Locale, shadowManifest.DefaultLocalization.Locale)) + { + // Icons + if (!m_manifest.get().DefaultLocalization.Contains(Localization::Icons) && + shadowManifest.DefaultLocalization.Contains(Localization::Icons)) + { + m_manifest.get().DefaultLocalization.Add(std::move(shadowManifest.DefaultLocalization.Get())); + + YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; + key.SetScalar("Icons"); + YAML::Node value = shadowNode.GetChildNode("Icons"); + m_rootNode.get().AddMappingNode(std::move(key), std::move(value)); + } + } + + // Localizations + if (!shadowManifest.Localizations.empty()) + { + // Merge manifest object + for (auto const& shadowLocalization : shadowManifest.Localizations) + { + // Manifest + if (auto iter = std::find_if(m_manifest.get().Localizations.begin(), m_manifest.get().Localizations.end(), [&](auto const& l) { return Utility::ICUCaseInsensitiveEquals(l.Locale, shadowLocalization.Locale); }); iter != m_manifest.get().Localizations.end()) + { + if (!(*iter).Contains(Localization::Icons) && + shadowLocalization.Contains(Localization::Icons)) + { + (*iter).Add(std::move(shadowLocalization.Get())); + } + } + else + { + ManifestLocalization localization = shadowLocalization; + m_manifest.get().Localizations.emplace_back(std::move(std::move(localization))); + } + } + + // Merge yaml + auto shadowLocalizationsNode = shadowNode.GetChildNode("Localization"); + if (m_p_localizationsNode) + { + m_rootNode.get().GetChildNode("Localization").MergeSequenceNode(shadowLocalizationsNode, "PackageLocale", true); + } + else + { + YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; + key.SetScalar("Localization"); + m_rootNode.get().AddMappingNode(std::move(key), std::move(shadowLocalizationsNode)); + } + } + } + + return resultErrors; + } + + ValidationErrors ManifestYamlPopulator::PopulateManifest( + YAML::Node& rootNode, + Manifest& manifest, + const ManifestVer& manifestVersion, + ManifestValidateOption validateOption, + const std::optional& shadowNode) + { + ManifestYamlPopulator manifestPopulator(rootNode, manifest, manifestVersion, validateOption); + auto errors = manifestPopulator.PopulateManifestInternal(); + + if (shadowNode.has_value()) + { + auto shadowErrors = manifestPopulator.InsertShadow(shadowNode.value()); + std::move(shadowErrors.begin(), shadowErrors.end(), std::inserter(errors, errors.end())); + } + + return errors; + } +} diff --git a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp index d61d9efb78..afedeb17e2 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlParser.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlParser.cpp @@ -1,612 +1,612 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerSHA256.h" -#include "winget/Yaml.h" -#include "winget/ManifestSchemaValidation.h" -#include "winget/ManifestYamlPopulator.h" -#include "winget/ManifestYamlParser.h" - -namespace AppInstaller::Manifest::YamlParser -{ - namespace - { - // Basic V1 manifest required fields check for later manifest consistency check - void ValidateV1ManifestInput(const YamlManifestInfo& entry) - { - std::vector errors; - - if (!entry.Root.IsMap()) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %hs", entry.FileName.c_str()); - } - - if (!entry.Root["PackageIdentifier"]) - { - errors.emplace_back(ValidationError::MessageContextWithFile( - ManifestError::RequiredFieldMissing, "PackageIdentifier", entry.FileName)); - } - - if (!entry.Root["PackageVersion"]) - { - errors.emplace_back(ValidationError::MessageContextWithFile( - ManifestError::RequiredFieldMissing, "PackageVersion", entry.FileName)); - } - - if (!entry.Root["ManifestVersion"]) - { - errors.emplace_back(ValidationError::MessageContextWithFile( - ManifestError::RequiredFieldMissing, "ManifestVersion", entry.FileName)); - } - - if (!entry.Root["ManifestType"]) - { - errors.emplace_back(ValidationError::MessageContextWithFile( - ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestType", entry.FileName)); - } - else - { - auto manifestType = ConvertToManifestTypeEnum(entry.Root["ManifestType"].as()); - - switch (manifestType) - { - case ManifestTypeEnum::Version: - if (!entry.Root["DefaultLocale"]) - { - errors.emplace_back(ValidationError::MessageContextWithFile( - ManifestError::RequiredFieldMissing, "DefaultLocale", entry.FileName)); - } - break; - case ManifestTypeEnum::Singleton: - case ManifestTypeEnum::Locale: - case ManifestTypeEnum::DefaultLocale: - if (!entry.Root["PackageLocale"]) - { - errors.emplace_back(ValidationError::MessageContextWithFile( - ManifestError::RequiredFieldMissing, "PackageLocale", entry.FileName)); - } - break; - } - } - - if (!errors.empty()) - { - ManifestException ex{ std::move(errors) }; - THROW_EXCEPTION(ex); - } - } - - // Input validations: - // - Determine manifest version - // - Check multi file manifest input integrity - // - All manifests use same PackageIdentifier, PackageVersion, ManifestVersion - // - All required types exist and exist only once. i.e. version, installer, defaultLocale - // - No duplicate locales across manifests - // - DefaultLocale matches in version manifest and defaultLocale manifest - // - Validate manifest type correctness - // - Allowed file type in multi file manifest: version, installer, defaultLocale, locale - // - Allowed file type in single file manifest: preview manifest, merged and singleton - ManifestVer ValidateInput(std::vector& input, ManifestValidateOption validateOption) - { - std::vector errors; - - std::string manifestVersionStr; - ManifestVer manifestVersion; - ManifestVer ManifestVersionV1{ s_ManifestVersionV1 }; - bool isMultifileManifest = input.size() > 1; - - // Use the first manifest doc to determine ManifestVersion, there'll be checks for manifest version consistency later - auto& firstYamlManifest = input[0]; - if (!firstYamlManifest.Root.IsMap()) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %hs", firstYamlManifest.FileName.c_str()); - } - - if (firstYamlManifest.Root["ManifestVersion"sv]) - { - manifestVersionStr = firstYamlManifest.Root["ManifestVersion"sv].as(); - } - else - { - manifestVersionStr = s_DefaultManifestVersion; - } - manifestVersion = ManifestVer{ manifestVersionStr }; - - // Check max supported version - if (manifestVersion.Major() > s_MaxSupportedMajorVersion) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION), "Unsupported ManifestVersion: %hs", manifestVersion.ToString().c_str()); - } - - // Preview manifest validations - if (manifestVersion < ManifestVersionV1) - { - // multi file manifest is only supported starting ManifestVersion 1.0.0 - if (isMultifileManifest) - { - THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "Preview manifest does not support multi file manifest format."); - } - - firstYamlManifest.ManifestType = ManifestTypeEnum::Preview; - } - // V1 manifest validations - else - { - // Check required fields used by later consistency check for better error message instead of - // Field Type Not Match error. - for (auto const& entry : input) - { - ValidateV1ManifestInput(entry); - } - - if (isMultifileManifest) - { - // Populates the PackageIdentifier and PackageVersion from first doc for later consistency check - std::string packageId = firstYamlManifest.Root["PackageIdentifier"].as(); - std::string packageVersion = firstYamlManifest.Root["PackageVersion"].as(); - - std::set localesSet; - - bool isVersionManifestFound = false; - bool isInstallerManifestFound = false; - bool isDefaultLocaleManifestFound = false; - bool isShadowManifestFound = false; - std::string defaultLocaleFromVersionManifest; - std::string defaultLocaleFromDefaultLocaleManifest; - - for (auto& entry : input) - { - std::string localPackageId = entry.Root["PackageIdentifier"].as(); - if (localPackageId != packageId) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::InconsistentMultiFileManifestFieldValue, "PackageIdentifier", localPackageId, entry.FileName)); - } - - std::string localPackageVersion = entry.Root["PackageVersion"].as(); - if (localPackageVersion != packageVersion) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::InconsistentMultiFileManifestFieldValue, "PackageVersion", localPackageVersion, entry.FileName)); - } - - std::string localManifestVersion = entry.Root["ManifestVersion"].as(); - if (localManifestVersion != manifestVersionStr) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestVersion", localManifestVersion, entry.FileName)); - } - - std::string manifestTypeStr = entry.Root["ManifestType"sv].as(); - ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); - entry.ManifestType = manifestType; - - switch (manifestType) - { - case ManifestTypeEnum::Version: - if (isVersionManifestFound) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); - } - else - { - isVersionManifestFound = true; - defaultLocaleFromVersionManifest = entry.Root["DefaultLocale"sv].as(); - } - break; - case ManifestTypeEnum::Installer: - if (isInstallerManifestFound) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); - } - else - { - isInstallerManifestFound = true; - } - break; - case ManifestTypeEnum::DefaultLocale: - if (isDefaultLocaleManifestFound) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); - } - else - { - isDefaultLocaleManifestFound = true; - auto packageLocale = entry.Root["PackageLocale"sv].as(); - defaultLocaleFromDefaultLocaleManifest = packageLocale; - - if (localesSet.find(packageLocale) != localesSet.end()) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); - } - else - { - localesSet.insert(packageLocale); - } - } - break; - case ManifestTypeEnum::Locale: - { - auto packageLocale = entry.Root["PackageLocale"sv].as(); - if (localesSet.find(packageLocale) != localesSet.end()) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); - } - else - { - localesSet.insert(packageLocale); - } - break; - } - case ManifestTypeEnum::Shadow: - { - if (!validateOption.AllowShadowManifest) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::ShadowManifestNotAllowed, "ManifestType", manifestTypeStr, entry.FileName)); - } - else if (isShadowManifestFound) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); - } - else - { - isShadowManifestFound = true; - } - break; - } - default: - errors.emplace_back(ValidationError::MessageContextValueWithFile( - ManifestError::UnsupportedMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); - } - } - - if (isVersionManifestFound && isDefaultLocaleManifestFound && defaultLocaleFromDefaultLocaleManifest != defaultLocaleFromVersionManifest) - { - errors.emplace_back(ManifestError::InconsistentMultiFileManifestDefaultLocale); - } - - if (!validateOption.SchemaValidationOnly && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) - { - errors.emplace_back(ManifestError::IncompleteMultiFileManifest); - } - } - else - { - std::string manifestTypeStr = firstYamlManifest.Root["ManifestType"sv].as(); - ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); - firstYamlManifest.ManifestType = manifestType; - - if (validateOption.FullValidation && manifestType == ManifestTypeEnum::Merged) - { - errors.emplace_back(ValidationError::MessageContextValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); - } - - if (!validateOption.SchemaValidationOnly && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) - { - errors.emplace_back(ValidationError::MessageWithFile(ManifestError::IncompleteMultiFileManifest, firstYamlManifest.FileName)); - } - } - } - - if (!errors.empty()) - { - ManifestException ex{ std::move(errors) }; - THROW_EXCEPTION(ex); - } - - return manifestVersion; - } - - // Find a unique required manifest from the input in multi manifest case - const YAML::Node& FindUniqueRequiredDocFromMultiFileManifest(const std::vector& input, ManifestTypeEnum manifestType) - { - auto iter = std::find_if(input.begin(), input.end(), - [=](auto const& s) - { - return s.ManifestType == manifestType; - }); - - THROW_HR_IF(E_UNEXPECTED, iter == input.end()); - - return iter->Root; - } - - std::optional FindUniqueOptionalDocFromMultiFileManifest(std::vector& input, ManifestTypeEnum manifestType) - { - auto iter = std::find_if(input.begin(), input.end(), - [=](auto const& s) - { - return s.ManifestType == manifestType; - }); - - if (iter != input.end()) - { - return iter->Root; - } - - return {}; - } - - // Merge one manifest file to the final merged manifest, basically copying the mapping but excluding certain common fields - void MergeOneManifestToMultiFileManifest(const YAML::Node& input, YAML::Node& destination) - { - THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); - THROW_HR_IF(E_UNEXPECTED, !destination.IsMap()); - - const std::vector FieldsToIgnore = { "PackageIdentifier", "PackageVersion", "ManifestType", "ManifestVersion" }; - - for (auto const& keyValuePair : input.Mapping()) - { - // We only support string type as key in our manifest - if (std::find(FieldsToIgnore.begin(), FieldsToIgnore.end(), keyValuePair.first.as()) == FieldsToIgnore.end()) - { - YAML::Node key = keyValuePair.first; - YAML::Node value = keyValuePair.second; - destination.AddMappingNode(std::move(key), std::move(value)); - } - } - } - - YAML::Node MergeMultiFileManifest(const std::vector& input) - { - // Starts with installer manifest - YAML::Node result = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::Installer); - - // Copy default locale manifest content into manifest root - YAML::Node defaultLocaleManifest = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::DefaultLocale); - MergeOneManifestToMultiFileManifest(defaultLocaleManifest, result); - - // Copy additional locale manifests - YAML::Node localizations{ YAML::Node::Type::Sequence, "", YAML::Mark() }; - for (const auto& entry : input) - { - if (entry.ManifestType == ManifestTypeEnum::Locale) - { - YAML::Node localization{ YAML::Node::Type::Mapping, "", YAML::Mark() }; - MergeOneManifestToMultiFileManifest(entry.Root, localization); - localizations.AddSequenceNode(std::move(localization)); - } - } - - if (localizations.size() > 0) - { - YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; - key.SetScalar("Localization"); - result.AddMappingNode(std::move(key), std::move(localizations)); - } - - result["ManifestType"sv].SetScalar("merged"); - - return result; - } - - void EmitYamlNode(const YAML::Node& input, YAML::Emitter& emitter) - { - if (input.IsMap()) - { - emitter << YAML::BeginMap; - for (auto const& keyValuePair : input.Mapping()) - { - emitter << YAML::Key; - EmitYamlNode(keyValuePair.first, emitter); - emitter << YAML::Value; - EmitYamlNode(keyValuePair.second, emitter); - } - emitter << YAML::EndMap; - } - else if (input.IsSequence()) - { - emitter << YAML::BeginSeq; - for (auto const& value : input.Sequence()) - { - EmitYamlNode(value, emitter); - } - emitter << YAML::EndSeq; - } - else if (input.IsScalar()) - { - emitter << input.as(); - } - else if (input.IsNull()) - { - emitter << ""; - } - else - { - THROW_HR(E_UNEXPECTED); - } - } - - void OutputYamlDoc(const YAML::Node& input, const std::filesystem::path& out) - { - THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); - - YAML::Emitter emitter; - EmitYamlNode(input, emitter); - - std::filesystem::create_directories(out.parent_path()); - std::ofstream outFileStream(out); - emitter.Emit(outFileStream); - outFileStream.close(); - } - - std::vector ParseManifestImpl( - std::vector& input, - Manifest& manifest, - const std::filesystem::path& mergedManifestPath, - ManifestValidateOption validateOption) - { - THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 0, "No manifest file found"); - THROW_HR_IF_MSG(E_INVALIDARG, validateOption.SchemaValidationOnly && !mergedManifestPath.empty(), "Manifest cannot be merged if only schema validation is performed"); - THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 1 && !mergedManifestPath.empty(), "Manifest cannot be merged from a single manifest"); - - auto manifestVersion = ValidateInput(input, validateOption); - - std::vector resultErrors; - - if (validateOption.FullValidation || validateOption.SchemaValidationOnly) - { - resultErrors = ValidateAgainstSchema(input, manifestVersion); - } - - if (validateOption.SchemaValidationOnly) - { - return resultErrors; - } - - // Merge manifests in multi file manifest case - bool isMultiFile = input.size() > 1; - YAML::Node& manifestDoc = input[0].Root; - if (isMultiFile) - { - manifestDoc = MergeMultiFileManifest(input); - } - - auto shadowNode = isMultiFile ? FindUniqueOptionalDocFromMultiFileManifest(input, ManifestTypeEnum::Shadow) : std::optional{}; - - auto errors = ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, validateOption, shadowNode); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - - // Extra semantic validations after basic validation and field population - if (validateOption.FullValidation) - { - errors = ValidateManifest(manifest, validateOption); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - - // Validate the schema header for manifest version 1.7 and above - if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_7 }) - { - // Validate the schema header. - errors = ValidateYamlManifestsSchemaHeader(input, manifestVersion, validateOption.SchemaHeaderValidationAsWarning); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - } - } - - if (validateOption.InstallerValidation) - { - errors = ValidateManifestInstallers(manifest); - std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); - } - - // Output merged manifest if requested - if (!mergedManifestPath.empty()) - { - OutputYamlDoc(manifestDoc, mergedManifestPath); - } - - // If there is only one input file, use its hash for the stream - if (input.size() == 1) - { - manifest.StreamSha256 = std::move(input[0].StreamSha256); - } - - return resultErrors; - } - } - - Manifest CreateFromPath( - const std::filesystem::path& inputPath, - ManifestValidateOption validateOption, - const std::filesystem::path& mergedManifestPath) - { - std::vector docList; - - try - { - if (std::filesystem::is_directory(inputPath)) - { - for (const auto& file : std::filesystem::directory_iterator(inputPath)) - { - THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED), std::filesystem::is_directory(file.path()), "Subdirectory not supported in manifest path"); - - YamlManifestInfo manifestInfo; - YAML::Document doc = YAML::LoadDocument(file.path()); - manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader(); - manifestInfo.Root = std::move(doc).GetRoot(); - manifestInfo.FileName = file.path().filename().u8string(); - docList.emplace_back(std::move(manifestInfo)); - } - } - else - { - YamlManifestInfo manifestInfo; - YAML::Document doc = YAML::LoadDocument(inputPath, manifestInfo.StreamSha256); - manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader(); - manifestInfo.Root = std::move(doc).GetRoot(); - manifestInfo.FileName = inputPath.filename().u8string(); - docList.emplace_back(std::move(manifestInfo)); - } - } - catch (const std::exception& e) - { - THROW_EXCEPTION_MSG(ManifestException(), "%hs", e.what()); - } - - return ParseManifest(docList, validateOption, mergedManifestPath); - } - - Manifest Create( - const std::string& input, - ManifestValidateOption validateOption, - const std::filesystem::path& mergedManifestPath) - { - std::vector docList; - - try - { - YamlManifestInfo manifestInfo; - YAML::Document doc = YAML::LoadDocument(input); - manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader(); - manifestInfo.Root = std::move(doc).GetRoot(); - docList.emplace_back(std::move(manifestInfo)); - } - catch (const std::exception& e) - { - THROW_EXCEPTION_MSG(ManifestException(), "%hs", e.what()); - } - - return ParseManifest(docList, validateOption, mergedManifestPath); - } - - Manifest ParseManifest( - std::vector& input, - ManifestValidateOption validateOption, - const std::filesystem::path& mergedManifestPath) - { - Manifest manifest; - std::vector errors; - - try - { - errors = ParseManifestImpl(input, manifest, mergedManifestPath, validateOption); - } - catch (const ManifestException&) - { - // Prevent ManifestException from being wrapped in another ManifestException - throw; - } - catch (const std::exception& e) - { - THROW_EXCEPTION_MSG(ManifestException(), "%hs", e.what()); - } - - if (!errors.empty()) - { - ManifestException ex{ std::move(errors) }; - - if (validateOption.ThrowOnWarning || !ex.IsWarningOnly()) - { - THROW_EXCEPTION(ex); - } - } - - return manifest; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerSHA256.h" +#include "winget/Yaml.h" +#include "winget/ManifestSchemaValidation.h" +#include "winget/ManifestYamlPopulator.h" +#include "winget/ManifestYamlParser.h" + +namespace AppInstaller::Manifest::YamlParser +{ + namespace + { + // Basic V1 manifest required fields check for later manifest consistency check + void ValidateV1ManifestInput(const YamlManifestInfo& entry) + { + std::vector errors; + + if (!entry.Root.IsMap()) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %hs", entry.FileName.c_str()); + } + + if (!entry.Root["PackageIdentifier"]) + { + errors.emplace_back(ValidationError::MessageContextWithFile( + ManifestError::RequiredFieldMissing, "PackageIdentifier", entry.FileName)); + } + + if (!entry.Root["PackageVersion"]) + { + errors.emplace_back(ValidationError::MessageContextWithFile( + ManifestError::RequiredFieldMissing, "PackageVersion", entry.FileName)); + } + + if (!entry.Root["ManifestVersion"]) + { + errors.emplace_back(ValidationError::MessageContextWithFile( + ManifestError::RequiredFieldMissing, "ManifestVersion", entry.FileName)); + } + + if (!entry.Root["ManifestType"]) + { + errors.emplace_back(ValidationError::MessageContextWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestType", entry.FileName)); + } + else + { + auto manifestType = ConvertToManifestTypeEnum(entry.Root["ManifestType"].as()); + + switch (manifestType) + { + case ManifestTypeEnum::Version: + if (!entry.Root["DefaultLocale"]) + { + errors.emplace_back(ValidationError::MessageContextWithFile( + ManifestError::RequiredFieldMissing, "DefaultLocale", entry.FileName)); + } + break; + case ManifestTypeEnum::Singleton: + case ManifestTypeEnum::Locale: + case ManifestTypeEnum::DefaultLocale: + if (!entry.Root["PackageLocale"]) + { + errors.emplace_back(ValidationError::MessageContextWithFile( + ManifestError::RequiredFieldMissing, "PackageLocale", entry.FileName)); + } + break; + } + } + + if (!errors.empty()) + { + ManifestException ex{ std::move(errors) }; + THROW_EXCEPTION(ex); + } + } + + // Input validations: + // - Determine manifest version + // - Check multi file manifest input integrity + // - All manifests use same PackageIdentifier, PackageVersion, ManifestVersion + // - All required types exist and exist only once. i.e. version, installer, defaultLocale + // - No duplicate locales across manifests + // - DefaultLocale matches in version manifest and defaultLocale manifest + // - Validate manifest type correctness + // - Allowed file type in multi file manifest: version, installer, defaultLocale, locale + // - Allowed file type in single file manifest: preview manifest, merged and singleton + ManifestVer ValidateInput(std::vector& input, ManifestValidateOption validateOption) + { + std::vector errors; + + std::string manifestVersionStr; + ManifestVer manifestVersion; + ManifestVer ManifestVersionV1{ s_ManifestVersionV1 }; + bool isMultifileManifest = input.size() > 1; + + // Use the first manifest doc to determine ManifestVersion, there'll be checks for manifest version consistency later + auto& firstYamlManifest = input[0]; + if (!firstYamlManifest.Root.IsMap()) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "The manifest does not contain a valid root. File: %hs", firstYamlManifest.FileName.c_str()); + } + + if (firstYamlManifest.Root["ManifestVersion"sv]) + { + manifestVersionStr = firstYamlManifest.Root["ManifestVersion"sv].as(); + } + else + { + manifestVersionStr = s_DefaultManifestVersion; + } + manifestVersion = ManifestVer{ manifestVersionStr }; + + // Check max supported version + if (manifestVersion.Major() > s_MaxSupportedMajorVersion) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION), "Unsupported ManifestVersion: %hs", manifestVersion.ToString().c_str()); + } + + // Preview manifest validations + if (manifestVersion < ManifestVersionV1) + { + // multi file manifest is only supported starting ManifestVersion 1.0.0 + if (isMultifileManifest) + { + THROW_EXCEPTION_MSG(ManifestException(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST), "Preview manifest does not support multi file manifest format."); + } + + firstYamlManifest.ManifestType = ManifestTypeEnum::Preview; + } + // V1 manifest validations + else + { + // Check required fields used by later consistency check for better error message instead of + // Field Type Not Match error. + for (auto const& entry : input) + { + ValidateV1ManifestInput(entry); + } + + if (isMultifileManifest) + { + // Populates the PackageIdentifier and PackageVersion from first doc for later consistency check + std::string packageId = firstYamlManifest.Root["PackageIdentifier"].as(); + std::string packageVersion = firstYamlManifest.Root["PackageVersion"].as(); + + std::set localesSet; + + bool isVersionManifestFound = false; + bool isInstallerManifestFound = false; + bool isDefaultLocaleManifestFound = false; + bool isShadowManifestFound = false; + std::string defaultLocaleFromVersionManifest; + std::string defaultLocaleFromDefaultLocaleManifest; + + for (auto& entry : input) + { + std::string localPackageId = entry.Root["PackageIdentifier"].as(); + if (localPackageId != packageId) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "PackageIdentifier", localPackageId, entry.FileName)); + } + + std::string localPackageVersion = entry.Root["PackageVersion"].as(); + if (localPackageVersion != packageVersion) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "PackageVersion", localPackageVersion, entry.FileName)); + } + + std::string localManifestVersion = entry.Root["ManifestVersion"].as(); + if (localManifestVersion != manifestVersionStr) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::InconsistentMultiFileManifestFieldValue, "ManifestVersion", localManifestVersion, entry.FileName)); + } + + std::string manifestTypeStr = entry.Root["ManifestType"sv].as(); + ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); + entry.ManifestType = manifestType; + + switch (manifestType) + { + case ManifestTypeEnum::Version: + if (isVersionManifestFound) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isVersionManifestFound = true; + defaultLocaleFromVersionManifest = entry.Root["DefaultLocale"sv].as(); + } + break; + case ManifestTypeEnum::Installer: + if (isInstallerManifestFound) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isInstallerManifestFound = true; + } + break; + case ManifestTypeEnum::DefaultLocale: + if (isDefaultLocaleManifestFound) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isDefaultLocaleManifestFound = true; + auto packageLocale = entry.Root["PackageLocale"sv].as(); + defaultLocaleFromDefaultLocaleManifest = packageLocale; + + if (localesSet.find(packageLocale) != localesSet.end()) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); + } + else + { + localesSet.insert(packageLocale); + } + } + break; + case ManifestTypeEnum::Locale: + { + auto packageLocale = entry.Root["PackageLocale"sv].as(); + if (localesSet.find(packageLocale) != localesSet.end()) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::DuplicateMultiFileManifestLocale, "PackageLocale", packageLocale, entry.FileName)); + } + else + { + localesSet.insert(packageLocale); + } + break; + } + case ManifestTypeEnum::Shadow: + { + if (!validateOption.AllowShadowManifest) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::ShadowManifestNotAllowed, "ManifestType", manifestTypeStr, entry.FileName)); + } + else if (isShadowManifestFound) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::DuplicateMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + else + { + isShadowManifestFound = true; + } + break; + } + default: + errors.emplace_back(ValidationError::MessageContextValueWithFile( + ManifestError::UnsupportedMultiFileManifestType, "ManifestType", manifestTypeStr, entry.FileName)); + } + } + + if (isVersionManifestFound && isDefaultLocaleManifestFound && defaultLocaleFromDefaultLocaleManifest != defaultLocaleFromVersionManifest) + { + errors.emplace_back(ManifestError::InconsistentMultiFileManifestDefaultLocale); + } + + if (!validateOption.SchemaValidationOnly && !(isVersionManifestFound && isInstallerManifestFound && isDefaultLocaleManifestFound)) + { + errors.emplace_back(ManifestError::IncompleteMultiFileManifest); + } + } + else + { + std::string manifestTypeStr = firstYamlManifest.Root["ManifestType"sv].as(); + ManifestTypeEnum manifestType = ConvertToManifestTypeEnum(manifestTypeStr); + firstYamlManifest.ManifestType = manifestType; + + if (validateOption.FullValidation && manifestType == ManifestTypeEnum::Merged) + { + errors.emplace_back(ValidationError::MessageContextValueWithFile(ManifestError::FieldValueNotSupported, "ManifestType", manifestTypeStr, firstYamlManifest.FileName)); + } + + if (!validateOption.SchemaValidationOnly && manifestType != ManifestTypeEnum::Merged && manifestType != ManifestTypeEnum::Singleton) + { + errors.emplace_back(ValidationError::MessageWithFile(ManifestError::IncompleteMultiFileManifest, firstYamlManifest.FileName)); + } + } + } + + if (!errors.empty()) + { + ManifestException ex{ std::move(errors) }; + THROW_EXCEPTION(ex); + } + + return manifestVersion; + } + + // Find a unique required manifest from the input in multi manifest case + const YAML::Node& FindUniqueRequiredDocFromMultiFileManifest(const std::vector& input, ManifestTypeEnum manifestType) + { + auto iter = std::find_if(input.begin(), input.end(), + [=](auto const& s) + { + return s.ManifestType == manifestType; + }); + + THROW_HR_IF(E_UNEXPECTED, iter == input.end()); + + return iter->Root; + } + + std::optional FindUniqueOptionalDocFromMultiFileManifest(std::vector& input, ManifestTypeEnum manifestType) + { + auto iter = std::find_if(input.begin(), input.end(), + [=](auto const& s) + { + return s.ManifestType == manifestType; + }); + + if (iter != input.end()) + { + return iter->Root; + } + + return {}; + } + + // Merge one manifest file to the final merged manifest, basically copying the mapping but excluding certain common fields + void MergeOneManifestToMultiFileManifest(const YAML::Node& input, YAML::Node& destination) + { + THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); + THROW_HR_IF(E_UNEXPECTED, !destination.IsMap()); + + const std::vector FieldsToIgnore = { "PackageIdentifier", "PackageVersion", "ManifestType", "ManifestVersion" }; + + for (auto const& keyValuePair : input.Mapping()) + { + // We only support string type as key in our manifest + if (std::find(FieldsToIgnore.begin(), FieldsToIgnore.end(), keyValuePair.first.as()) == FieldsToIgnore.end()) + { + YAML::Node key = keyValuePair.first; + YAML::Node value = keyValuePair.second; + destination.AddMappingNode(std::move(key), std::move(value)); + } + } + } + + YAML::Node MergeMultiFileManifest(const std::vector& input) + { + // Starts with installer manifest + YAML::Node result = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::Installer); + + // Copy default locale manifest content into manifest root + YAML::Node defaultLocaleManifest = FindUniqueRequiredDocFromMultiFileManifest(input, ManifestTypeEnum::DefaultLocale); + MergeOneManifestToMultiFileManifest(defaultLocaleManifest, result); + + // Copy additional locale manifests + YAML::Node localizations{ YAML::Node::Type::Sequence, "", YAML::Mark() }; + for (const auto& entry : input) + { + if (entry.ManifestType == ManifestTypeEnum::Locale) + { + YAML::Node localization{ YAML::Node::Type::Mapping, "", YAML::Mark() }; + MergeOneManifestToMultiFileManifest(entry.Root, localization); + localizations.AddSequenceNode(std::move(localization)); + } + } + + if (localizations.size() > 0) + { + YAML::Node key{ YAML::Node::Type::Scalar, "", YAML::Mark() }; + key.SetScalar("Localization"); + result.AddMappingNode(std::move(key), std::move(localizations)); + } + + result["ManifestType"sv].SetScalar("merged"); + + return result; + } + + void EmitYamlNode(const YAML::Node& input, YAML::Emitter& emitter) + { + if (input.IsMap()) + { + emitter << YAML::BeginMap; + for (auto const& keyValuePair : input.Mapping()) + { + emitter << YAML::Key; + EmitYamlNode(keyValuePair.first, emitter); + emitter << YAML::Value; + EmitYamlNode(keyValuePair.second, emitter); + } + emitter << YAML::EndMap; + } + else if (input.IsSequence()) + { + emitter << YAML::BeginSeq; + for (auto const& value : input.Sequence()) + { + EmitYamlNode(value, emitter); + } + emitter << YAML::EndSeq; + } + else if (input.IsScalar()) + { + emitter << input.as(); + } + else if (input.IsNull()) + { + emitter << ""; + } + else + { + THROW_HR(E_UNEXPECTED); + } + } + + void OutputYamlDoc(const YAML::Node& input, const std::filesystem::path& out) + { + THROW_HR_IF(E_UNEXPECTED, !input.IsMap()); + + YAML::Emitter emitter; + EmitYamlNode(input, emitter); + + std::filesystem::create_directories(out.parent_path()); + std::ofstream outFileStream(out); + emitter.Emit(outFileStream); + outFileStream.close(); + } + + std::vector ParseManifestImpl( + std::vector& input, + Manifest& manifest, + const std::filesystem::path& mergedManifestPath, + ManifestValidateOption validateOption) + { + THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 0, "No manifest file found"); + THROW_HR_IF_MSG(E_INVALIDARG, validateOption.SchemaValidationOnly && !mergedManifestPath.empty(), "Manifest cannot be merged if only schema validation is performed"); + THROW_HR_IF_MSG(E_INVALIDARG, input.size() == 1 && !mergedManifestPath.empty(), "Manifest cannot be merged from a single manifest"); + + auto manifestVersion = ValidateInput(input, validateOption); + + std::vector resultErrors; + + if (validateOption.FullValidation || validateOption.SchemaValidationOnly) + { + resultErrors = ValidateAgainstSchema(input, manifestVersion); + } + + if (validateOption.SchemaValidationOnly) + { + return resultErrors; + } + + // Merge manifests in multi file manifest case + bool isMultiFile = input.size() > 1; + YAML::Node& manifestDoc = input[0].Root; + if (isMultiFile) + { + manifestDoc = MergeMultiFileManifest(input); + } + + auto shadowNode = isMultiFile ? FindUniqueOptionalDocFromMultiFileManifest(input, ManifestTypeEnum::Shadow) : std::optional{}; + + auto errors = ManifestYamlPopulator::PopulateManifest(manifestDoc, manifest, manifestVersion, validateOption, shadowNode); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + + // Extra semantic validations after basic validation and field population + if (validateOption.FullValidation) + { + errors = ValidateManifest(manifest, validateOption); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + + // Validate the schema header for manifest version 1.7 and above + if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_7 }) + { + // Validate the schema header. + errors = ValidateYamlManifestsSchemaHeader(input, manifestVersion, validateOption.SchemaHeaderValidationAsWarning); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + } + + if (validateOption.InstallerValidation) + { + errors = ValidateManifestInstallers(manifest); + std::move(errors.begin(), errors.end(), std::inserter(resultErrors, resultErrors.end())); + } + + // Output merged manifest if requested + if (!mergedManifestPath.empty()) + { + OutputYamlDoc(manifestDoc, mergedManifestPath); + } + + // If there is only one input file, use its hash for the stream + if (input.size() == 1) + { + manifest.StreamSha256 = std::move(input[0].StreamSha256); + } + + return resultErrors; + } + } + + Manifest CreateFromPath( + const std::filesystem::path& inputPath, + ManifestValidateOption validateOption, + const std::filesystem::path& mergedManifestPath) + { + std::vector docList; + + try + { + if (std::filesystem::is_directory(inputPath)) + { + for (const auto& file : std::filesystem::directory_iterator(inputPath)) + { + THROW_HR_IF_MSG(HRESULT_FROM_WIN32(ERROR_DIRECTORY_NOT_SUPPORTED), std::filesystem::is_directory(file.path()), "Subdirectory not supported in manifest path"); + + YamlManifestInfo manifestInfo; + YAML::Document doc = YAML::LoadDocument(file.path()); + manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader(); + manifestInfo.Root = std::move(doc).GetRoot(); + manifestInfo.FileName = file.path().filename().u8string(); + docList.emplace_back(std::move(manifestInfo)); + } + } + else + { + YamlManifestInfo manifestInfo; + YAML::Document doc = YAML::LoadDocument(inputPath, manifestInfo.StreamSha256); + manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader(); + manifestInfo.Root = std::move(doc).GetRoot(); + manifestInfo.FileName = inputPath.filename().u8string(); + docList.emplace_back(std::move(manifestInfo)); + } + } + catch (const std::exception& e) + { + THROW_EXCEPTION_MSG(ManifestException(), "%hs", e.what()); + } + + return ParseManifest(docList, validateOption, mergedManifestPath); + } + + Manifest Create( + const std::string& input, + ManifestValidateOption validateOption, + const std::filesystem::path& mergedManifestPath) + { + std::vector docList; + + try + { + YamlManifestInfo manifestInfo; + YAML::Document doc = YAML::LoadDocument(input); + manifestInfo.DocumentSchemaHeader = doc.GetSchemaHeader(); + manifestInfo.Root = std::move(doc).GetRoot(); + docList.emplace_back(std::move(manifestInfo)); + } + catch (const std::exception& e) + { + THROW_EXCEPTION_MSG(ManifestException(), "%hs", e.what()); + } + + return ParseManifest(docList, validateOption, mergedManifestPath); + } + + Manifest ParseManifest( + std::vector& input, + ManifestValidateOption validateOption, + const std::filesystem::path& mergedManifestPath) + { + Manifest manifest; + std::vector errors; + + try + { + errors = ParseManifestImpl(input, manifest, mergedManifestPath, validateOption); + } + catch (const ManifestException&) + { + // Prevent ManifestException from being wrapped in another ManifestException + throw; + } + catch (const std::exception& e) + { + THROW_EXCEPTION_MSG(ManifestException(), "%hs", e.what()); + } + + if (!errors.empty()) + { + ManifestException ex{ std::move(errors) }; + + if (validateOption.ThrowOnWarning || !ex.IsWarningOnly()) + { + THROW_EXCEPTION(ex); + } + } + + return manifest; + } +} diff --git a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp index ad2e381a60..eb12738e01 100644 --- a/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp +++ b/src/AppInstallerCommonCore/Manifest/YamlWriter.cpp @@ -66,12 +66,12 @@ namespace AppInstaller::Manifest::YamlWriter constexpr std::string_view DisplayName = "DisplayName"sv; 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 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 RepairBehavior = "RepairBehavior"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; constexpr std::string_view DesiredStateConfiguration = "DesiredStateConfiguration"sv; constexpr std::string_view DesiredStateConfigurationResources = "Resources"sv; @@ -163,16 +163,16 @@ namespace AppInstaller::Manifest::YamlWriter { \ WRITE_PROPERTY(emitter, key, value) \ } \ - } - + } + #define WRITE_SHA256_PROPERTY_IF_NOT_EMPTY(emitter, key, field) \ { \ if (!field.empty()) \ { \ WRITE_PROPERTY(emitter, key, Utility::SHA256::ConvertToString(field)) \ } \ - } - + } + #define WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(emitter, key, value, enumField, enumType) \ { \ if (enumField != enumType::Unknown) \ @@ -235,7 +235,7 @@ namespace AppInstaller::Manifest::YamlWriter WRITE_PROPERTY(out, IconUrl, icon.Url); WRITE_PROPERTY(out, IconFileType, IconFileTypeToString(icon.FileType)); WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, IconResolution, IconResolutionToString(icon.Resolution), icon.Resolution, IconResolutionEnum); - WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, IconTheme, IconThemeToString(icon.Theme), icon.Theme, IconThemeEnum); + WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, IconTheme, IconThemeToString(icon.Theme), icon.Theme, IconThemeEnum); WRITE_SHA256_PROPERTY_IF_NOT_EMPTY(out, IconSha256, icon.Sha256); out << YAML::EndMap; } @@ -253,10 +253,10 @@ namespace AppInstaller::Manifest::YamlWriter out << YAML::Key << name; out << YAML::BeginSeq; for (const auto& item : items) - { - if (!item.empty()) + { + if (!item.empty()) { - out << item; + out << item; } } out << YAML::EndSeq; @@ -359,9 +359,9 @@ namespace AppInstaller::Manifest::YamlWriter out << YAML::BeginSeq; for (auto const& unsupportedArg : unsupportedArguments) { - if (unsupportedArg != UnsupportedArgumentEnum::Unknown) - { - out << UnsupportedArgumentToString(unsupportedArg); + if (unsupportedArg != UnsupportedArgumentEnum::Unknown) + { + out << UnsupportedArgumentToString(unsupportedArg); } } out << YAML::EndSeq; @@ -377,9 +377,9 @@ namespace AppInstaller::Manifest::YamlWriter out << YAML::Key << UnsupportedOSArchitectures; out << YAML::BeginSeq; for (auto const& architecture : architectures) - { - if (architecture != AppInstaller::Utility::Architecture::Unknown) - { + { + if (architecture != AppInstaller::Utility::Architecture::Unknown) + { out << Utility::ToLower(ToString(architecture)); } } @@ -396,9 +396,9 @@ namespace AppInstaller::Manifest::YamlWriter out << YAML::Key << InstallModes; out << YAML::BeginSeq; for (auto const& installMode : installModes) - { - if (installMode != InstallModeEnum::Unknown) - { + { + if (installMode != InstallModeEnum::Unknown) + { out << InstallModeToString(installMode); } } @@ -414,9 +414,9 @@ namespace AppInstaller::Manifest::YamlWriter out << YAML::Key << Platform; out << YAML::BeginSeq; for (auto const& platform : platforms) - { - if (platform != PlatformEnum::Unknown) - { + { + if (platform != PlatformEnum::Unknown) + { out << PlatformToString(platform); } } @@ -507,8 +507,8 @@ 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) @@ -519,12 +519,12 @@ namespace AppInstaller::Manifest::YamlWriter 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); + 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; @@ -593,87 +593,87 @@ namespace AppInstaller::Manifest::YamlWriter } out << YAML::EndMap; - } - - void ProcessDesiredStateConfiguration(YAML::Emitter& out, const std::vector& containers) - { - if (containers.empty()) - { - return; - } - - out << YAML::Key << DesiredStateConfiguration; + } + + void ProcessDesiredStateConfiguration(YAML::Emitter& out, const std::vector& containers) + { + if (containers.empty()) + { + return; + } + + out << YAML::Key << DesiredStateConfiguration; out << YAML::BeginMap; - - // Process PowerShell containers + + // Process PowerShell containers bool firstPowerShellContainer = true; - for (const auto& container : containers) - { - if (container.Type == DesiredStateConfigurationContainerType::PowerShell) + for (const auto& container : containers) + { + if (container.Type == DesiredStateConfigurationContainerType::PowerShell) { - if (firstPowerShellContainer) - { + if (firstPowerShellContainer) + { out << YAML::Key << DesiredStateConfigurationPowerShell; out << YAML::BeginSeq; firstPowerShellContainer = false; - } - - out << YAML::BeginMap; - - WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellRepositoryURL, container.RepositoryURL); - WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellModuleName, container.ModuleName); - - out << YAML::Key << DesiredStateConfigurationResources; - out << YAML::BeginSeq; - - for (const auto& resource : container.Resources) - { - out << YAML::BeginMap; - WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellResourceName, resource.Name); + } + + out << YAML::BeginMap; + + WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellRepositoryURL, container.RepositoryURL); + WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellModuleName, container.ModuleName); + + out << YAML::Key << DesiredStateConfigurationResources; + out << YAML::BeginSeq; + + for (const auto& resource : container.Resources) + { + out << YAML::BeginMap; + WRITE_PROPERTY(out, DesiredStateConfigurationPowerShellResourceName, resource.Name); out << YAML::EndMap; - } - - out << YAML::EndSeq; + } + + out << YAML::EndSeq; out << YAML::EndMap; } - } - - if (!firstPowerShellContainer) - { - out << YAML::EndSeq; - } - + } + + if (!firstPowerShellContainer) + { + out << YAML::EndSeq; + } + // Process DSCv3 container bool firstDSCv3Container = true; - for (const auto& container : containers) - { - if (container.Type == DesiredStateConfigurationContainerType::DSCv3) + for (const auto& container : containers) + { + if (container.Type == DesiredStateConfigurationContainerType::DSCv3) { - if (firstDSCv3Container) - { + if (firstDSCv3Container) + { out << YAML::Key << DesiredStateConfigurationDSCv3; out << YAML::BeginMap; firstDSCv3Container = false; - } - else - { - THROW_HR_MSG(E_INVALIDARG, "Cannot have multiple DSCv3 containers."); - } - - out << YAML::Key << DesiredStateConfigurationResources; - out << YAML::BeginSeq; - - for (const auto& resource : container.Resources) - { - out << YAML::BeginMap; - WRITE_PROPERTY(out, DesiredStateConfigurationDSCv3ResourceType, resource.Name); + } + else + { + THROW_HR_MSG(E_INVALIDARG, "Cannot have multiple DSCv3 containers."); + } + + out << YAML::Key << DesiredStateConfigurationResources; + out << YAML::BeginSeq; + + for (const auto& resource : container.Resources) + { + out << YAML::BeginMap; + WRITE_PROPERTY(out, DesiredStateConfigurationDSCv3ResourceType, resource.Name); out << YAML::EndMap; - } - + } + out << YAML::EndSeq; out << YAML::EndMap; } - } + } out << YAML::EndMap; } @@ -685,8 +685,8 @@ namespace AppInstaller::Manifest::YamlWriter WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, NestedInstallerType, InstallerTypeToString(installer.NestedInstallerType), installer.NestedInstallerType, InstallerTypeEnum); WRITE_SHA256_PROPERTY_IF_NOT_EMPTY(out, InstallerSha256, installer.Sha256); WRITE_SHA256_PROPERTY_IF_NOT_EMPTY(out, SignatureSha256, installer.SignatureSha256); - WRITE_PROPERTY_IF_EXISTS(out, InstallerUrl, installer.Url); - WRITE_PROPERTY_IF_EXISTS(out, MSStoreProductIdentifier, installer.ProductId); + WRITE_PROPERTY_IF_EXISTS(out, InstallerUrl, installer.Url); + WRITE_PROPERTY_IF_EXISTS(out, MSStoreProductIdentifier, installer.ProductId); WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, Scope, Utility::ToLower(ScopeToString(installer.Scope)), installer.Scope, ScopeEnum); WRITE_PROPERTY_IF_EXISTS(out, InstallerLocale, installer.Locale); @@ -697,11 +697,11 @@ namespace AppInstaller::Manifest::YamlWriter WRITE_BOOL_PROPERTY(out, InstallLocationRequired, installer.InstallLocationRequired); WRITE_BOOL_PROPERTY(out, RequireExplicitUpgrade, installer.RequireExplicitUpgrade); WRITE_BOOL_PROPERTY(out, DisplayInstallWarnings, installer.DisplayInstallWarnings); - WRITE_BOOL_PROPERTY(out, DownloadCommandProhibited, installer.DownloadCommandProhibited); + WRITE_BOOL_PROPERTY(out, DownloadCommandProhibited, installer.DownloadCommandProhibited); WRITE_BOOL_PROPERTY(out, ArchiveBinariesDependOnPath, installer.ArchiveBinariesDependOnPath); WRITE_PROPERTY_IF_EXISTS(out, MinimumOSVersion, installer.MinOSVersion); WRITE_PROPERTY_IF_EXISTS(out, ProductCode, installer.ProductCode); - WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, UpgradeBehavior, UpdateBehaviorToString(installer.UpdateBehavior), installer.UpdateBehavior, UpdateBehaviorEnum); + WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, UpgradeBehavior, UpdateBehaviorToString(installer.UpdateBehavior), installer.UpdateBehavior, UpdateBehaviorEnum); WRITE_ENUM_PROPERTY_IF_NOT_UNKNOWN(out, RepairBehavior, RepairBehaviorToString(installer.RepairBehavior), installer.RepairBehavior, RepairBehaviorEnum); ProcessStringSequence(out, Capabilities, installer.Capabilities); @@ -721,7 +721,7 @@ 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); ProcessDesiredStateConfiguration(out, installer.DesiredStateConfiguration); } @@ -744,9 +744,9 @@ namespace AppInstaller::Manifest::YamlWriter out << YAML::BeginSeq; for (const auto& localization : localizations) - { + { out << YAML::BeginMap; - ProcessLocaleFields(out, localization); + ProcessLocaleFields(out, localization); out << YAML::EndMap; } @@ -801,4 +801,4 @@ namespace AppInstaller::Manifest::YamlWriter emitter.Emit(outFileStream); outFileStream.close(); } -} +} diff --git a/src/AppInstallerCommonCore/MsiExecArguments.cpp b/src/AppInstallerCommonCore/MsiExecArguments.cpp index cc845f89dc..ba28769b41 100644 --- a/src/AppInstallerCommonCore/MsiExecArguments.cpp +++ b/src/AppInstallerCommonCore/MsiExecArguments.cpp @@ -1,598 +1,598 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/MsiExecArguments.h" -#include "Public/AppInstallerErrors.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerStrings.h" - - -namespace AppInstaller::Msi -{ - using namespace std::string_view_literals; - - namespace - { - const char MsiExecQuietOption = 'q'; - const char MsiExecLogOption = 'l'; - - // Description of how a long option is replaced by a short option. - struct TokenReplacement - { - TokenReplacement(std::string_view longOption, std::string_view shortOption) : LongOption(longOption), ShortOption({ shortOption }) {} - TokenReplacement(std::string_view longOption, std::vector&& shortOption) : LongOption(longOption), ShortOption(std::move(shortOption)) {} - std::string_view LongOption; - std::vector ShortOption; - }; - - // Determines whether an argument token is a switch/option. - bool IsSwitch(std::string_view token) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, token.empty()); - return token[0] == '-' || token[0] == '/'; - } - - // Parses the log mode and log file for the Log (/l) option. - // The option has a modifier specifying the log mode (what is logged) - // and a value specifying the log file. - // E.g. /l* log.txt, /lw warnings.txt - void ParseLogOption(std::string_view logModeString, std::string_view logFile, MsiParsedArguments& parsedArgs) - { - if (Utility::IsEmptyOrWhitespace(logFile)) - { - AICLI_LOG(Core, Error, << "MSI log file path cannot be empty"); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - - INSTALLLOGMODE logMode = {}; - INSTALLLOGATTRIBUTES logAttributes = {}; - - // Note: These flags are mostly consecutive bits in the order given, except where indicated. - // Skipped flags are not mapped to a command line option. - std::map ValidLogModes - { - { 'm', INSTALLLOGMODE_FATALEXIT }, - { 'e', INSTALLLOGMODE_ERROR }, - { 'w', INSTALLLOGMODE_WARNING }, - { 'u', INSTALLLOGMODE_USER }, - { 'i', INSTALLLOGMODE_INFO }, - // FILESINUSE - // RESOLVESOURCE - { 'o', INSTALLLOGMODE_OUTOFDISKSPACE }, - { 'a', INSTALLLOGMODE_ACTIONSTART }, - { 'r', INSTALLLOGMODE_ACTIONDATA }, - { 'p', INSTALLLOGMODE_PROPERTYDUMP }, - { 'c', INSTALLLOGMODE_COMMONDATA }, - { 'v', INSTALLLOGMODE_VERBOSE }, - { 'x', INSTALLLOGMODE_EXTRADEBUG }, - // LOGONLYONERROR - // LOGPERFORMANCE - }; - - std::map ValidLogAttributes - { - { '+', INSTALLLOGATTRIBUTES_APPEND }, - { '!', INSTALLLOGATTRIBUTES_FLUSHEACHLINE }, - }; - - bool isLogModeSet = false; - for (char c : logModeString) - { - // Log-all option - if (c == '*') - { - logMode |= AllLogMode; - isLogModeSet = true; - continue; - } - - auto modeItr = ValidLogModes.find(c); - if (modeItr != ValidLogModes.end()) - { - logMode |= modeItr->second; - isLogModeSet = true; - continue; - } - - auto attributeItr = ValidLogAttributes.find(c); - if (attributeItr != ValidLogAttributes.end()) - { - logAttributes |= attributeItr->second; - continue; - } - - AICLI_LOG(Core, Error, << "Unknown msiexec log modifier: " << c); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - - if (!isLogModeSet) - { - logMode = DefaultLogMode; - } - - parsedArgs.LogMode = logMode; - parsedArgs.LogAttributes = logAttributes; - parsedArgs.LogFile = Utility::ConvertToUTF16(logFile); - } - - // Parses the modifier for the UI Level option (/q) - // The modifier starts with a base (b, f, n, r), followed by extra flags (+, -, !). - // E.g. /qn, /qb-! - void ParseQuietOption(std::string_view modifier, MsiParsedArguments& parsedArgs) - { - if (modifier.empty()) - { - // /q is treated as equivalent to /qn - modifier = "n"sv; - } - - // Lower values in INSTALLUILEVEL work like a base enum (e.g. None=2, Basic=3) - // with higher values being modifying flags (e.g. HideCancel=0x20, ProgressOnly=0x40). - // Some steps depend on the base enum, so we keep it separate for easier checking. - INSTALLUILEVEL uiLevelBase = {}; - INSTALLUILEVEL uiLevelModifiers = {}; - - // Parse the base level - switch (std::tolower(modifier[0])) - { - case 'f': - uiLevelBase = INSTALLUILEVEL_FULL; - break; - case 'r': - uiLevelBase = INSTALLUILEVEL_REDUCED; - break; - case 'b': - uiLevelBase = INSTALLUILEVEL_BASIC; - break; - case '+': - uiLevelBase = INSTALLUILEVEL_NONE; - uiLevelModifiers = INSTALLUILEVEL_ENDDIALOG; - break; - case 'n': - uiLevelBase = INSTALLUILEVEL_NONE; - break; - default: - AICLI_LOG(Core, Error, << "Invalid modifier for msiexec /q argument: " << modifier); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - }; - - // Parse the modifiers - for (size_t i = 1; i < modifier.size(); ++i) - { - const char c = modifier[i]; - - if (c == '+') - { - WI_SetFlag(uiLevelModifiers, INSTALLUILEVEL_ENDDIALOG); - } - else if (c == '-') - { - if (uiLevelBase == INSTALLUILEVEL_BASIC) - { - WI_SetFlag(uiLevelModifiers, INSTALLUILEVEL_PROGRESSONLY); - } - else - { - AICLI_LOG(Core, Error, << "msiexec UI option Progress Only (-) is only valid with UI level Basic (b)"); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - } - else if (c == '!') - { - if (uiLevelBase == INSTALLUILEVEL_BASIC) - { - WI_SetFlag(uiLevelModifiers, INSTALLUILEVEL_HIDECANCEL); - } - else - { - AICLI_LOG(Core, Error, << "msiexec UI option Hide Cancel (!) is only valid with UI level Basic (b)"); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - } - } - - // Only deviation from msiexec: - // When using UI Level None, allow showing the UAC prompt. - WI_SetFlagIf(uiLevelModifiers, INSTALLUILEVEL_UACONLY, uiLevelBase == INSTALLUILEVEL_NONE); - - parsedArgs.UILevel = uiLevelBase | uiLevelModifiers; - } - - bool IsWhiteSpace(char c) - { - return c == ' ' || c == '\t'; - } - - // Gets the next token found in the arguments string, starting the search on the given position. - // If there are no more tokens, return empty. - // After finding the token, updates `start` to point to the next place we need to start the next token search. - std::string_view GetNextToken(std::string_view arguments, size_t& start) - { - // Eat leading whitespace - while (start < arguments.size() && IsWhiteSpace(arguments[start])) - { - ++start; - } - - if (start >= arguments.size()) - { - // We reached the end - return {}; - } - - size_t pos = start; - bool seekingSpaceSeparator = ('"' != arguments[pos]); - bool withinQuotes = false; - - // Start looking from the next character - ++pos; - - // Advance until we hit the end or the next separator - while (pos < arguments.size()) - { - bool isSpace = IsWhiteSpace(arguments[pos]); - bool isQuote = ('"' == arguments[pos]); - - if (isSpace || isQuote) - { - // We've encountered one of the two separators we're interested in - if (seekingSpaceSeparator) - { - if (isQuote) - { - // We will ignore space characters enclosed between double quotes - withinQuotes = !withinQuotes; - } - else - { - // This is a space character. If it is between quotes we ignore it; - // otherwise it is a separator. - if (!withinQuotes) - { - break; - } - } - } - else - { - if (isQuote) - { - // we've got what we needed, it is OK to stop - break; - } - } - } - - ++pos; - } - - if (!seekingSpaceSeparator) - { - // We were looking for a terminating " character. - if (pos < arguments.size()) - { - // We move past the " character (it is OK for the end of the line - // to act as the matching " character in some cases) - ++pos; - } - } - - auto result = arguments.substr(start, pos - start); - start = pos; - return result; - } - - // Split the arguments string into tokens. Tokens are delimited by whitespace - // unless quoted. Each token represents an option (like /q), an argument - // for an option, or a property. - std::list TokenizeMsiArguments(std::string_view arguments) - { - size_t start = 0; - std::list result; - auto token = GetNextToken(arguments, start); - while (!token.empty()) - { - result.emplace_back(token); - token = GetNextToken(arguments, start); - } - - return result; - } - - // Parses a token that represents an argument to an option. - // If the value is unquoted, returns it as is. - // If the value is quoted, removes the quotes and replaces escaped characters. - std::string ParseValue(std::string_view valueToken) - { - if (valueToken.empty() || valueToken[0] != '"') - { - // Nothing to do for empty or unquoted tokens - return std::string{ valueToken }; - } - - // Copy the string ignoring the quotes and replacing escaped characters. - // In quoted tokens, the back quote represents double quotes (` means ") - // and can be escaped with back slash (\` means `). - // Note that we accept quoted values with a missing closing quote (the end - // of string signals the end). - std::string result; - for (size_t i = 1; i < valueToken.size(); ++i) - { - if (valueToken[i] == '"') - { - // The tokenizer can leave several pairs of quotes in the token - // but they are not accepted in this case. We only accept the final - // closing quotes. - if (i + 1 == valueToken.size()) - { - break; - } - else - { - AICLI_LOG(Core, Error, << "Invalid msiexec argument: " << valueToken); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - } - - if (i + 1 < valueToken.size() && valueToken[i] == '\\' && valueToken[i + 1] == '`') - { - result += '`'; - ++i; - } - else if (valueToken[i] == '`') - { - result += '"'; - } - else - { - result += valueToken[i]; - } - } - - return result; - } - - // Validates that a token represents a property. - // This checks that the property has the form PropertyName=Value, - // with the value optionally quoted. - std::optional ParsePropertyToken(std::string_view token) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, token.empty()); - - if (token[0] != '%' && !IsCharAlphaNumericA(token[0])) - { - AICLI_LOG(Core, Error, << "Bad property for msiexec: " << token); - return std::nullopt; - } - - // Find the = separator at the end of the property name - size_t pos = 0; - while (pos < token.size() && !IsWhiteSpace(token[pos]) && token[pos] != '=') - { - ++pos; - } - - if (pos == token.size() || token[pos] != '=') - { - AICLI_LOG(Core, Error, << "Expected property for call to msiexec, but couldn't find separator: " << token); - return std::nullopt; - } - - size_t nameLength = pos; - - // Validate the property value. - // It should be completely enclosed in quotes, or not contain white space. - // If quoted, there can be pairs of consecutive quotes that work as escape sequences. - // We accept empty property values. - ++pos; - if (pos == token.size()) - { - // Empty value - return MsiParsedArguments::ParsedProperty{ std::string{ token.substr(0, nameLength) }, {} }; - } - - // If quoted, we will only inspect the values between the quotes. - bool quoted = false; - size_t end = token.size(); - if (token[pos] == '"') - { - ++pos; - - if (pos >= end || token.back() != '"') - { - AICLI_LOG(Core, Error, << "Badly quoted msiexec property: " << token); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - - --end; - quoted = true; - } - - while (pos < end) - { - if (quoted) - { - // For quoted values, any internal quote must be followed by another one. - if (token[pos] == '"') - { - if (pos + 1 < end && token[pos + 1] == '"') - { - // Skip the two quotes - ++pos; - } - else - { - AICLI_LOG(Core, Error, << "Unexpected quotes in msiexec property arg: " << token); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - } - } - else - { - // For unquoted values, we only check that there is no whitespace - if (IsWhiteSpace(token[pos])) - { - AICLI_LOG(Core, Error, << "Unexpected space in msiexec property arg: " << token); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - } - - ++pos; - } - - return MsiParsedArguments::ParsedProperty{ std::string{ token.substr(0, nameLength) }, std::string{ token.substr(nameLength + 1) } }; - } - - // Replaces long options in the arguments (e.g. /quiet), by their short equivalents - // (e.g. /qn). The replacement is done in-place. - void ReplaceLongOptions(std::list& tokens) - { - // We don't handle all possible options because we don't need to. - // Options not handled: - // /update - // /uninstall - // /package - // /help - const std::vector Replacements - { - { "quiet"sv, "/qn"sv }, - { "passive"sv, { "/qb!-"sv, "REBOOTPROMPT=S"sv } }, - { "norestart"sv, "REBOOT=ReallySuppress"sv }, - { "forcerestart"sv, "REBOOT=Force"sv }, - { "promptrestart"sv, "REBOOTPROMPT=\"\""sv }, - { "log"sv, "/l*"sv }, - }; - - auto itr = tokens.begin(); - while (itr != tokens.end()) - { - if (!IsSwitch(*itr)) - { - // We only need to replace switches. - ++itr; - continue; - } - - // Find if there is a replacement for this option. - // We ignore the leading / or - when comparing. - auto option = std::string_view(*itr).substr(1); - auto replacementItr = std::find_if(Replacements.begin(), Replacements.end(), [&](const TokenReplacement& replacement) { return Utility::CaseInsensitiveEquals(replacement.LongOption, option); }); - if (replacementItr == Replacements.end()) - { - // There is no replacement for this switch; - ++itr; - continue; - } - - // Add all the replacements tokens needed before this one, then delete the existing token. - tokens.insert(itr, replacementItr->ShortOption.begin(), replacementItr->ShortOption.end()); - - // Delete the current token an move to the next one. - // We don't need to do anything more to the newly added tokens. - itr = tokens.erase(itr); - } - } - - // Consumes the next argument token in the list. If the token is an option - // that takes an argument, also consumes it. After consuming the token(s), - // removes it from the list and updates the parsed arguments accordingly. - void ConsumeNextToken(std::list& tokens, MsiParsedArguments& parsedArgs) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, tokens.empty()); - - auto token = std::move(tokens.front()); - tokens.pop_front(); - if (!IsSwitch(token)) - { - auto propertyToken = ParsePropertyToken(token); - // Token is a property, i.e. NAME=value. Add it to the parsed args. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT, !propertyToken); - parsedArgs.Properties += L" " + Utility::ConvertToUTF16(token); - parsedArgs.ParsedProperties.emplace_back(std::move(propertyToken).value()); - return; - } - - // Token is an option. - if (token.size() <= 1) - { - AICLI_LOG(Core, Error, << "Invalid command line argument for msiexec: " << token); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - - char option = token[1]; - auto optionModifier = ParseValue(std::string_view(token).substr(2)); - - // Options are case-insensitive - switch (std::tolower(option)) - { - case MsiExecQuietOption: - { - ParseQuietOption(optionModifier, parsedArgs); - break; - } - case MsiExecLogOption: - { - if (tokens.empty()) - { - // Log option must be followed by an option argument - AICLI_LOG(Core, Error, << "msiexec option " << token << " must be followed by a value"); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - - const auto optionValue = ParseValue(tokens.front()); - tokens.pop_front(); - - ParseLogOption(optionModifier, optionValue, parsedArgs); - break; - } - default: - { - AICLI_LOG(Core, Error, << "Invalid option for msiexec: " << token); - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); - } - } - } - } - - std::optional MsiParsedArguments::GetFirstBlockedProperty() const - { - for (const auto& property : ParsedProperties) - { - auto lowerName = Utility::ToLower(property.first); - - for (const auto& blockedName : { - "transforms", - "patch", - "msinewinstance", - "adminproperties", - }) - { - if (blockedName == lowerName) - { - AICLI_LOG(Core, Warning, << "MSI arguments contain blocked property: " << lowerName); - return property.first; - } - } - } - - return std::nullopt; - } - - MsiParsedArguments ParseMSIArguments(std::string_view arguments) - { - // Split the arguments into tokens, which we will process one by one. - auto argumentTokens = TokenizeMsiArguments(arguments); - - // Replace long options so we can work only with short ones. - ReplaceLongOptions(argumentTokens); - - // Process the arguments. - MsiParsedArguments result; - while (!argumentTokens.empty()) - { - ConsumeNextToken(argumentTokens, result); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/MsiExecArguments.h" +#include "Public/AppInstallerErrors.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerStrings.h" + + +namespace AppInstaller::Msi +{ + using namespace std::string_view_literals; + + namespace + { + const char MsiExecQuietOption = 'q'; + const char MsiExecLogOption = 'l'; + + // Description of how a long option is replaced by a short option. + struct TokenReplacement + { + TokenReplacement(std::string_view longOption, std::string_view shortOption) : LongOption(longOption), ShortOption({ shortOption }) {} + TokenReplacement(std::string_view longOption, std::vector&& shortOption) : LongOption(longOption), ShortOption(std::move(shortOption)) {} + std::string_view LongOption; + std::vector ShortOption; + }; + + // Determines whether an argument token is a switch/option. + bool IsSwitch(std::string_view token) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, token.empty()); + return token[0] == '-' || token[0] == '/'; + } + + // Parses the log mode and log file for the Log (/l) option. + // The option has a modifier specifying the log mode (what is logged) + // and a value specifying the log file. + // E.g. /l* log.txt, /lw warnings.txt + void ParseLogOption(std::string_view logModeString, std::string_view logFile, MsiParsedArguments& parsedArgs) + { + if (Utility::IsEmptyOrWhitespace(logFile)) + { + AICLI_LOG(Core, Error, << "MSI log file path cannot be empty"); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + + INSTALLLOGMODE logMode = {}; + INSTALLLOGATTRIBUTES logAttributes = {}; + + // Note: These flags are mostly consecutive bits in the order given, except where indicated. + // Skipped flags are not mapped to a command line option. + std::map ValidLogModes + { + { 'm', INSTALLLOGMODE_FATALEXIT }, + { 'e', INSTALLLOGMODE_ERROR }, + { 'w', INSTALLLOGMODE_WARNING }, + { 'u', INSTALLLOGMODE_USER }, + { 'i', INSTALLLOGMODE_INFO }, + // FILESINUSE + // RESOLVESOURCE + { 'o', INSTALLLOGMODE_OUTOFDISKSPACE }, + { 'a', INSTALLLOGMODE_ACTIONSTART }, + { 'r', INSTALLLOGMODE_ACTIONDATA }, + { 'p', INSTALLLOGMODE_PROPERTYDUMP }, + { 'c', INSTALLLOGMODE_COMMONDATA }, + { 'v', INSTALLLOGMODE_VERBOSE }, + { 'x', INSTALLLOGMODE_EXTRADEBUG }, + // LOGONLYONERROR + // LOGPERFORMANCE + }; + + std::map ValidLogAttributes + { + { '+', INSTALLLOGATTRIBUTES_APPEND }, + { '!', INSTALLLOGATTRIBUTES_FLUSHEACHLINE }, + }; + + bool isLogModeSet = false; + for (char c : logModeString) + { + // Log-all option + if (c == '*') + { + logMode |= AllLogMode; + isLogModeSet = true; + continue; + } + + auto modeItr = ValidLogModes.find(c); + if (modeItr != ValidLogModes.end()) + { + logMode |= modeItr->second; + isLogModeSet = true; + continue; + } + + auto attributeItr = ValidLogAttributes.find(c); + if (attributeItr != ValidLogAttributes.end()) + { + logAttributes |= attributeItr->second; + continue; + } + + AICLI_LOG(Core, Error, << "Unknown msiexec log modifier: " << c); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + + if (!isLogModeSet) + { + logMode = DefaultLogMode; + } + + parsedArgs.LogMode = logMode; + parsedArgs.LogAttributes = logAttributes; + parsedArgs.LogFile = Utility::ConvertToUTF16(logFile); + } + + // Parses the modifier for the UI Level option (/q) + // The modifier starts with a base (b, f, n, r), followed by extra flags (+, -, !). + // E.g. /qn, /qb-! + void ParseQuietOption(std::string_view modifier, MsiParsedArguments& parsedArgs) + { + if (modifier.empty()) + { + // /q is treated as equivalent to /qn + modifier = "n"sv; + } + + // Lower values in INSTALLUILEVEL work like a base enum (e.g. None=2, Basic=3) + // with higher values being modifying flags (e.g. HideCancel=0x20, ProgressOnly=0x40). + // Some steps depend on the base enum, so we keep it separate for easier checking. + INSTALLUILEVEL uiLevelBase = {}; + INSTALLUILEVEL uiLevelModifiers = {}; + + // Parse the base level + switch (std::tolower(modifier[0])) + { + case 'f': + uiLevelBase = INSTALLUILEVEL_FULL; + break; + case 'r': + uiLevelBase = INSTALLUILEVEL_REDUCED; + break; + case 'b': + uiLevelBase = INSTALLUILEVEL_BASIC; + break; + case '+': + uiLevelBase = INSTALLUILEVEL_NONE; + uiLevelModifiers = INSTALLUILEVEL_ENDDIALOG; + break; + case 'n': + uiLevelBase = INSTALLUILEVEL_NONE; + break; + default: + AICLI_LOG(Core, Error, << "Invalid modifier for msiexec /q argument: " << modifier); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + }; + + // Parse the modifiers + for (size_t i = 1; i < modifier.size(); ++i) + { + const char c = modifier[i]; + + if (c == '+') + { + WI_SetFlag(uiLevelModifiers, INSTALLUILEVEL_ENDDIALOG); + } + else if (c == '-') + { + if (uiLevelBase == INSTALLUILEVEL_BASIC) + { + WI_SetFlag(uiLevelModifiers, INSTALLUILEVEL_PROGRESSONLY); + } + else + { + AICLI_LOG(Core, Error, << "msiexec UI option Progress Only (-) is only valid with UI level Basic (b)"); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + } + else if (c == '!') + { + if (uiLevelBase == INSTALLUILEVEL_BASIC) + { + WI_SetFlag(uiLevelModifiers, INSTALLUILEVEL_HIDECANCEL); + } + else + { + AICLI_LOG(Core, Error, << "msiexec UI option Hide Cancel (!) is only valid with UI level Basic (b)"); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + } + } + + // Only deviation from msiexec: + // When using UI Level None, allow showing the UAC prompt. + WI_SetFlagIf(uiLevelModifiers, INSTALLUILEVEL_UACONLY, uiLevelBase == INSTALLUILEVEL_NONE); + + parsedArgs.UILevel = uiLevelBase | uiLevelModifiers; + } + + bool IsWhiteSpace(char c) + { + return c == ' ' || c == '\t'; + } + + // Gets the next token found in the arguments string, starting the search on the given position. + // If there are no more tokens, return empty. + // After finding the token, updates `start` to point to the next place we need to start the next token search. + std::string_view GetNextToken(std::string_view arguments, size_t& start) + { + // Eat leading whitespace + while (start < arguments.size() && IsWhiteSpace(arguments[start])) + { + ++start; + } + + if (start >= arguments.size()) + { + // We reached the end + return {}; + } + + size_t pos = start; + bool seekingSpaceSeparator = ('"' != arguments[pos]); + bool withinQuotes = false; + + // Start looking from the next character + ++pos; + + // Advance until we hit the end or the next separator + while (pos < arguments.size()) + { + bool isSpace = IsWhiteSpace(arguments[pos]); + bool isQuote = ('"' == arguments[pos]); + + if (isSpace || isQuote) + { + // We've encountered one of the two separators we're interested in + if (seekingSpaceSeparator) + { + if (isQuote) + { + // We will ignore space characters enclosed between double quotes + withinQuotes = !withinQuotes; + } + else + { + // This is a space character. If it is between quotes we ignore it; + // otherwise it is a separator. + if (!withinQuotes) + { + break; + } + } + } + else + { + if (isQuote) + { + // we've got what we needed, it is OK to stop + break; + } + } + } + + ++pos; + } + + if (!seekingSpaceSeparator) + { + // We were looking for a terminating " character. + if (pos < arguments.size()) + { + // We move past the " character (it is OK for the end of the line + // to act as the matching " character in some cases) + ++pos; + } + } + + auto result = arguments.substr(start, pos - start); + start = pos; + return result; + } + + // Split the arguments string into tokens. Tokens are delimited by whitespace + // unless quoted. Each token represents an option (like /q), an argument + // for an option, or a property. + std::list TokenizeMsiArguments(std::string_view arguments) + { + size_t start = 0; + std::list result; + auto token = GetNextToken(arguments, start); + while (!token.empty()) + { + result.emplace_back(token); + token = GetNextToken(arguments, start); + } + + return result; + } + + // Parses a token that represents an argument to an option. + // If the value is unquoted, returns it as is. + // If the value is quoted, removes the quotes and replaces escaped characters. + std::string ParseValue(std::string_view valueToken) + { + if (valueToken.empty() || valueToken[0] != '"') + { + // Nothing to do for empty or unquoted tokens + return std::string{ valueToken }; + } + + // Copy the string ignoring the quotes and replacing escaped characters. + // In quoted tokens, the back quote represents double quotes (` means ") + // and can be escaped with back slash (\` means `). + // Note that we accept quoted values with a missing closing quote (the end + // of string signals the end). + std::string result; + for (size_t i = 1; i < valueToken.size(); ++i) + { + if (valueToken[i] == '"') + { + // The tokenizer can leave several pairs of quotes in the token + // but they are not accepted in this case. We only accept the final + // closing quotes. + if (i + 1 == valueToken.size()) + { + break; + } + else + { + AICLI_LOG(Core, Error, << "Invalid msiexec argument: " << valueToken); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + } + + if (i + 1 < valueToken.size() && valueToken[i] == '\\' && valueToken[i + 1] == '`') + { + result += '`'; + ++i; + } + else if (valueToken[i] == '`') + { + result += '"'; + } + else + { + result += valueToken[i]; + } + } + + return result; + } + + // Validates that a token represents a property. + // This checks that the property has the form PropertyName=Value, + // with the value optionally quoted. + std::optional ParsePropertyToken(std::string_view token) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, token.empty()); + + if (token[0] != '%' && !IsCharAlphaNumericA(token[0])) + { + AICLI_LOG(Core, Error, << "Bad property for msiexec: " << token); + return std::nullopt; + } + + // Find the = separator at the end of the property name + size_t pos = 0; + while (pos < token.size() && !IsWhiteSpace(token[pos]) && token[pos] != '=') + { + ++pos; + } + + if (pos == token.size() || token[pos] != '=') + { + AICLI_LOG(Core, Error, << "Expected property for call to msiexec, but couldn't find separator: " << token); + return std::nullopt; + } + + size_t nameLength = pos; + + // Validate the property value. + // It should be completely enclosed in quotes, or not contain white space. + // If quoted, there can be pairs of consecutive quotes that work as escape sequences. + // We accept empty property values. + ++pos; + if (pos == token.size()) + { + // Empty value + return MsiParsedArguments::ParsedProperty{ std::string{ token.substr(0, nameLength) }, {} }; + } + + // If quoted, we will only inspect the values between the quotes. + bool quoted = false; + size_t end = token.size(); + if (token[pos] == '"') + { + ++pos; + + if (pos >= end || token.back() != '"') + { + AICLI_LOG(Core, Error, << "Badly quoted msiexec property: " << token); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + + --end; + quoted = true; + } + + while (pos < end) + { + if (quoted) + { + // For quoted values, any internal quote must be followed by another one. + if (token[pos] == '"') + { + if (pos + 1 < end && token[pos + 1] == '"') + { + // Skip the two quotes + ++pos; + } + else + { + AICLI_LOG(Core, Error, << "Unexpected quotes in msiexec property arg: " << token); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + } + } + else + { + // For unquoted values, we only check that there is no whitespace + if (IsWhiteSpace(token[pos])) + { + AICLI_LOG(Core, Error, << "Unexpected space in msiexec property arg: " << token); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + } + + ++pos; + } + + return MsiParsedArguments::ParsedProperty{ std::string{ token.substr(0, nameLength) }, std::string{ token.substr(nameLength + 1) } }; + } + + // Replaces long options in the arguments (e.g. /quiet), by their short equivalents + // (e.g. /qn). The replacement is done in-place. + void ReplaceLongOptions(std::list& tokens) + { + // We don't handle all possible options because we don't need to. + // Options not handled: + // /update + // /uninstall + // /package + // /help + const std::vector Replacements + { + { "quiet"sv, "/qn"sv }, + { "passive"sv, { "/qb!-"sv, "REBOOTPROMPT=S"sv } }, + { "norestart"sv, "REBOOT=ReallySuppress"sv }, + { "forcerestart"sv, "REBOOT=Force"sv }, + { "promptrestart"sv, "REBOOTPROMPT=\"\""sv }, + { "log"sv, "/l*"sv }, + }; + + auto itr = tokens.begin(); + while (itr != tokens.end()) + { + if (!IsSwitch(*itr)) + { + // We only need to replace switches. + ++itr; + continue; + } + + // Find if there is a replacement for this option. + // We ignore the leading / or - when comparing. + auto option = std::string_view(*itr).substr(1); + auto replacementItr = std::find_if(Replacements.begin(), Replacements.end(), [&](const TokenReplacement& replacement) { return Utility::CaseInsensitiveEquals(replacement.LongOption, option); }); + if (replacementItr == Replacements.end()) + { + // There is no replacement for this switch; + ++itr; + continue; + } + + // Add all the replacements tokens needed before this one, then delete the existing token. + tokens.insert(itr, replacementItr->ShortOption.begin(), replacementItr->ShortOption.end()); + + // Delete the current token an move to the next one. + // We don't need to do anything more to the newly added tokens. + itr = tokens.erase(itr); + } + } + + // Consumes the next argument token in the list. If the token is an option + // that takes an argument, also consumes it. After consuming the token(s), + // removes it from the list and updates the parsed arguments accordingly. + void ConsumeNextToken(std::list& tokens, MsiParsedArguments& parsedArgs) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, tokens.empty()); + + auto token = std::move(tokens.front()); + tokens.pop_front(); + if (!IsSwitch(token)) + { + auto propertyToken = ParsePropertyToken(token); + // Token is a property, i.e. NAME=value. Add it to the parsed args. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT, !propertyToken); + parsedArgs.Properties += L" " + Utility::ConvertToUTF16(token); + parsedArgs.ParsedProperties.emplace_back(std::move(propertyToken).value()); + return; + } + + // Token is an option. + if (token.size() <= 1) + { + AICLI_LOG(Core, Error, << "Invalid command line argument for msiexec: " << token); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + + char option = token[1]; + auto optionModifier = ParseValue(std::string_view(token).substr(2)); + + // Options are case-insensitive + switch (std::tolower(option)) + { + case MsiExecQuietOption: + { + ParseQuietOption(optionModifier, parsedArgs); + break; + } + case MsiExecLogOption: + { + if (tokens.empty()) + { + // Log option must be followed by an option argument + AICLI_LOG(Core, Error, << "msiexec option " << token << " must be followed by a value"); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + + const auto optionValue = ParseValue(tokens.front()); + tokens.pop_front(); + + ParseLogOption(optionModifier, optionValue, parsedArgs); + break; + } + default: + { + AICLI_LOG(Core, Error, << "Invalid option for msiexec: " << token); + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT); + } + } + } + } + + std::optional MsiParsedArguments::GetFirstBlockedProperty() const + { + for (const auto& property : ParsedProperties) + { + auto lowerName = Utility::ToLower(property.first); + + for (const auto& blockedName : { + "transforms", + "patch", + "msinewinstance", + "adminproperties", + }) + { + if (blockedName == lowerName) + { + AICLI_LOG(Core, Warning, << "MSI arguments contain blocked property: " << lowerName); + return property.first; + } + } + } + + return std::nullopt; + } + + MsiParsedArguments ParseMSIArguments(std::string_view arguments) + { + // Split the arguments into tokens, which we will process one by one. + auto argumentTokens = TokenizeMsiArguments(arguments); + + // Replace long options so we can work only with short ones. + ReplaceLongOptions(argumentTokens); + + // Process the arguments. + MsiParsedArguments result; + while (!argumentTokens.empty()) + { + ConsumeNextToken(argumentTokens, result); + } + + return result; + } +} diff --git a/src/AppInstallerCommonCore/MsixInfo.cpp b/src/AppInstallerCommonCore/MsixInfo.cpp index f1bbb72eb8..dcc40d3447 100644 --- a/src/AppInstallerCommonCore/MsixInfo.cpp +++ b/src/AppInstallerCommonCore/MsixInfo.cpp @@ -1,837 +1,837 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerMsixInfo.h" -#include "HttpStream/HttpRandomAccessStream.h" -#include "Public/AppInstallerDownloader.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerDownloader.h" -#include "Public/AppInstallerRuntime.h" - -using namespace winrt::Windows::Storage::Streams; -using namespace Microsoft::WRL; -using namespace AppInstaller::Utility::HttpStream; -using namespace winrt::Windows::Management::Deployment; - -namespace AppInstaller::Msix -{ - namespace - { - // MSIX-specific header placed in the P7X file, before the actual signature - const byte P7xFileId[] = { 0x50, 0x4b, 0x43, 0x58 }; - const DWORD P7xFileIdSize = sizeof(P7xFileId); - - // Gets the version from the manifest reader. - UINT64 GetVersionFromManifestReader(IAppxManifestReader* reader) - { - ComPtr packageId; - THROW_IF_FAILED(reader->GetPackageId(&packageId)); - - UINT64 result = 0; - THROW_IF_FAILED(packageId->GetVersion(&result)); - - return result; - } - - // Gets the UINT64 version from the version struct. - UINT64 GetVersionFromVersion(const winrt::Windows::ApplicationModel::PackageVersion& version) - { - UINT64 result = version.Major; - result = (result << 16) | version.Minor; - result = (result << 16) | version.Build; - result = (result << 16) | version.Revision; - - return result; - } - - // Writes the stream (from current location) to the given file. - void WriteStreamToFile(IStream* stream, UINT64 expectedSize, const std::filesystem::path& target, IProgressCallback& progress) - { - std::filesystem::path tempFile = target; - tempFile += ".dnld"; - - { - std::ofstream file(tempFile, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); - - constexpr ULONG bufferSize = 1 << 20; - std::unique_ptr buffer = std::make_unique(bufferSize); - - UINT64 totalBytesRead = 0; - - while (!progress.IsCancelledBy(CancelReason::Any)) - { - ULONG bytesRead = 0; - HRESULT hr = stream->Read(buffer.get(), bufferSize, &bytesRead); - - if (bytesRead) - { - // If we got bytes, just accept them and keep going. - LOG_IF_FAILED(hr); - - THROW_HR_IF_MSG(E_UNEXPECTED, expectedSize && totalBytesRead + bytesRead > expectedSize, "Read more bytes than expected size"); - - file.write(buffer.get(), bytesRead); - totalBytesRead += bytesRead; - progress.OnProgress(totalBytesRead, expectedSize, ProgressType::Bytes); - } - else - { - // If given a size, and we have read it all, quit - if (expectedSize && totalBytesRead == expectedSize) - { - break; - } - - // If the stream returned an error, throw it - THROW_IF_FAILED(hr); - - // If we were given a size and didn't reach it, throw our own error; - // otherwise assume that this is just normal EOF. - if (expectedSize) - { - THROW_WIN32(ERROR_HANDLE_EOF); - } - else - { - break; - } - } - } - } - - std::filesystem::path backupFile = target; - backupFile += ".bkup"; - if (std::filesystem::exists(target)) - { - if (std::filesystem::exists(backupFile)) - { - std::filesystem::remove(backupFile); - } - std::filesystem::rename(target, backupFile); - } - - std::filesystem::rename(tempFile, target); - } - - // Writes the appx file to the given file. - void WriteAppxFileToFile(IAppxFile* appxFile, const std::filesystem::path& target, IProgressCallback& progress) - { - UINT64 size = 0; - THROW_IF_FAILED(appxFile->GetSize(&size)); - - ComPtr stream; - THROW_IF_FAILED(appxFile->GetStream(&stream)); - - WriteStreamToFile(stream.Get(), size, target, progress); - } - - // Writes the stream (from current location) to the given file handle. - void WriteStreamToFileHandle(IStream* stream, UINT64 expectedSize, HANDLE target, IProgressCallback& progress) - { - constexpr ULONG bufferSize = 1 << 20; - std::unique_ptr buffer = std::make_unique(bufferSize); - - UINT64 totalBytesRead = 0; - - while (!progress.IsCancelledBy(CancelReason::Any)) - { - ULONG bytesRead = 0; - HRESULT hr = stream->Read(buffer.get(), bufferSize, &bytesRead); - - if (bytesRead) - { - // If we got bytes, just accept them and keep going. - LOG_IF_FAILED(hr); - - THROW_HR_IF_MSG(E_UNEXPECTED, expectedSize && totalBytesRead + bytesRead > expectedSize, "Read more bytes than expected size"); - - DWORD bytesWritten = 0; - THROW_LAST_ERROR_IF(!WriteFile(target, buffer.get(), bytesRead, &bytesWritten, nullptr)); - THROW_HR_IF(E_UNEXPECTED, bytesRead != bytesWritten); - totalBytesRead += bytesRead; - progress.OnProgress(totalBytesRead, expectedSize, ProgressType::Bytes); - } - else - { - // If given a size, and we have read it all, quit - if (expectedSize && totalBytesRead == expectedSize) - { - break; - } - - // If the stream returned an error, throw it - THROW_IF_FAILED(hr); - - // If we were given a size and didn't reach it, throw our own error; - // otherwise assume that this is just normal EOF. - if (expectedSize) - { - THROW_WIN32(ERROR_HANDLE_EOF); - } - else - { - break; - } - } - } - } - - // Writes the appx file to the given file handle. - void WriteAppxFileToFileHandle(IAppxFile* appxFile, HANDLE target, IProgressCallback& progress) - { - UINT64 size = 0; - THROW_IF_FAILED(appxFile->GetSize(&size)); - - ComPtr stream; - THROW_IF_FAILED(appxFile->GetStream(&stream)); - - WriteStreamToFileHandle(stream.Get(), size, target, progress); - } - - bool ValidateMsixTrustInfo(const std::filesystem::path& msixPath, bool verifyMicrosoftOrigin) - { - bool result = false; - AICLI_LOG(Core, Info, << "Started trust validation of msix at: " << msixPath); - - try - { - bool verifyChainResult = false; - - // First verify certificate chain if requested. - if (verifyMicrosoftOrigin) - { - auto [certContext, certStore] = GetCertContextFromMsix(msixPath); - - // Get certificate chain context for validation - CERT_CHAIN_PARA certChainParameters = { 0 }; - certChainParameters.cbSize = sizeof(CERT_CHAIN_PARA); - certChainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; - DWORD certChainFlags = CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL; - - wil::unique_cert_chain_context certChainContext; - THROW_LAST_ERROR_IF(!CertGetCertificateChain( - HCCE_LOCAL_MACHINE, - certContext.get(), - NULL, // Use the current system time for CRL validation - certStore.get(), - &certChainParameters, - certChainFlags, - NULL, // Reserved parameter; must be NULL - &certChainContext)); - - // Validate that the certificate chain is rooted in one of the well-known Microsoft root certs - CERT_CHAIN_POLICY_PARA policyParameters = { 0 }; - policyParameters.cbSize = sizeof(CERT_CHAIN_POLICY_PARA); - policyParameters.dwFlags = MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG; - CERT_CHAIN_POLICY_STATUS policyStatus = { 0 }; - policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS); - LPCSTR policyOid = CERT_CHAIN_POLICY_MICROSOFT_ROOT; - BOOL certChainVerifySucceeded = CertVerifyCertificateChainPolicy( - policyOid, - certChainContext.get(), - &policyParameters, - &policyStatus); - - AICLI_LOG(Core, Info, << "Result for certificate chain validation of Microsoft origin: " << policyStatus.dwError); - - verifyChainResult = certChainVerifySucceeded && policyStatus.dwError == ERROR_SUCCESS; - } - else - { - verifyChainResult = true; - } - - // If certificate chain origin validation is success or not requested, then validate the trust info of the file. - if (verifyChainResult) - { - // Set up the structures needed for the WinVerifyTrust call - WINTRUST_FILE_INFO fileInfo = { 0 }; - fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO); - fileInfo.pcwszFilePath = msixPath.c_str(); - - WINTRUST_DATA trustData = { 0 }; - trustData.cbStruct = sizeof(WINTRUST_DATA); - trustData.dwUIChoice = WTD_UI_NONE; - trustData.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; - trustData.dwUnionChoice = WTD_CHOICE_FILE; - trustData.dwStateAction = WTD_STATEACTION_VERIFY; - trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; - trustData.pFile = &fileInfo; - - GUID verifyActionId = WINTRUST_ACTION_GENERIC_VERIFY_V2; - - HRESULT verifyTrustResult = static_cast(WinVerifyTrust(static_cast(INVALID_HANDLE_VALUE), &verifyActionId, &trustData)); - AICLI_LOG(Core, Info, << "Result for trust info validation of the msix: " << verifyTrustResult); - - result = verifyTrustResult == S_OK; - } - } - catch (const wil::ResultException& re) - { - AICLI_LOG(Core, Error, << "Failed during msix trust validation. Error: " << re.GetErrorCode()); - result = false; - } - catch (...) - { - AICLI_LOG(Core, Error, << "Failed during msix trust validation."); - result = false; - } - - return result; - } - } - - bool GetBundleReader( - IStream* inputStream, - IAppxBundleReader** reader) - { - ComPtr bundleFactory; - - // Create a new Appxbundle factory - THROW_IF_FAILED(CoCreateInstance( - __uuidof(AppxBundleFactory), - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(IAppxBundleFactory), - (LPVOID*)(&bundleFactory))); - - HRESULT hr = bundleFactory->CreateBundleReader(inputStream, reader); - - if (SUCCEEDED(hr)) - { - return true; - } - else if (hr == APPX_E_MISSING_REQUIRED_FILE) - { - // APPX_E_MISSING_REQUIRED_FILE returned when trying to open - // an *.msix as an *.msixbundle or vice-versa. - return false; - } - else - { - THROW_HR(hr); - } - } - - bool GetPackageReader( - IStream* inputStream, - IAppxPackageReader** reader) - { - ComPtr appxFactory; - - // Create a new Appx factory - THROW_IF_FAILED(CoCreateInstance( - __uuidof(AppxFactory), - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(IAppxFactory), - (LPVOID*)(&appxFactory))); - - // Create a new package reader using the factory. - HRESULT hr = appxFactory->CreatePackageReader(inputStream, reader); - - if (SUCCEEDED(hr)) - { - return true; - } - else if (hr == APPX_E_MISSING_REQUIRED_FILE) - { - // APPX_E_MISSING_REQUIRED_FILE returned when trying to open - // an *.msix as an *.msixbundle or vice-versa. - return false; - } - else - { - THROW_HR(hr); - } - } - - void GetManifestReader( - IStream* inputStream, - IAppxManifestReader** reader) - { - ComPtr appxFactory; - - THROW_IF_FAILED(CoCreateInstance( - __uuidof(AppxFactory), - nullptr, - CLSCTX_INPROC_SERVER, - __uuidof(IAppxFactory), - (LPVOID*)(&appxFactory))); - - THROW_IF_FAILED(appxFactory->CreateManifestReader(inputStream, reader)); - } - - std::optional GetPackageFullNameFromFamilyName(std::string_view familyName) - { - PackageManager packageManager; - - std::wstring pfn = Utility::ConvertToUTF16(familyName); - - // PackageManager.FindPackages() can find all packages (including provisioned ones) but requires admin. - // For non admin callers, use FindPackagesByPackageFamily where only packages registered to current user will be found. - if (Runtime::IsRunningAsAdmin()) - { - auto packages = packageManager.FindPackages(pfn); - - std::optional result; - for (const auto& package : packages) - { - if (result.has_value()) - { - // More than 1 package found. Don't directly error, let caller deal with it. - AICLI_LOG(Core, Error, << "Multiple packages found for family name: " << familyName); - return {}; - } - - result = Utility::ConvertToUTF8(package.Id().FullName()); - } - - return result; - } - else - { - UINT32 fullNameCount = 0; - UINT32 bufferLength = 0; - UINT32 properties = 0; - LONG findResult = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &fullNameCount, nullptr, &bufferLength, nullptr, &properties); - if (findResult == ERROR_SUCCESS || fullNameCount == 0) - { - // No package found - return {}; - } - else if (findResult != ERROR_INSUFFICIENT_BUFFER) - { - THROW_WIN32(findResult); - } - else if (fullNameCount != 1) - { - // Don't directly error, let caller deal with it - AICLI_LOG(Core, Error, << "Multiple packages found for family name: " << fullNameCount); - return {}; - } - - // fullNameCount == 1 at this point - PWSTR fullNamePtr; - std::wstring buffer(static_cast(bufferLength) + 1, '\0'); - THROW_IF_WIN32_ERROR(FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &fullNameCount, &fullNamePtr, &bufferLength, &buffer[0], &properties)); - if (fullNameCount != 1 || bufferLength == 0) - { - // Something changed in between, abandon - AICLI_LOG(Core, Error, << "Packages found for family name: " << fullNameCount); - return {}; - } - buffer.resize(bufferLength - 1); - return Utility::ConvertToUTF8(buffer); - } - } - - std::string GetPackageFamilyNameFromFullName(std::string_view fullName) - { - std::wstring result; - result.resize(PACKAGE_FAMILY_NAME_MAX_LENGTH + 1); - UINT32 size = static_cast(result.size()); - THROW_IF_WIN32_ERROR(PackageFamilyNameFromFullName(Utility::ConvertToUTF16(fullName).c_str(), &size, &result[0])); - result.resize(size - 1); - return Utility::ConvertToUTF8(result); - } - - std::optional GetPackageLocationFromFullName(std::string_view fullName) - { - std::wstring fn = Utility::ConvertToUTF16(fullName); - - UINT32 length = 0; - LONG returnVal = GetStagedPackagePathByFullName(fn.c_str(), &length, nullptr); - if (returnVal != ERROR_INSUFFICIENT_BUFFER) - { - LOG_WIN32(returnVal); - return {}; - } - - THROW_HR_IF(E_UNEXPECTED, length == 0); - - std::wstring result; - result.resize(length); - - returnVal = GetStagedPackagePathByFullName(fn.c_str(), &length, &result[0]); - if (returnVal != ERROR_SUCCESS) - { - LOG_WIN32(returnVal); - return {}; - } - - result.resize(length - 1); - return { result }; - } - - Msix::PackageIdInfo GetPackageIdInfoFromFullName(std::string_view fullName) - { - std::wstring fullNameWide = Utility::ConvertToUTF16(fullName); - - UINT32 length = 0; - LONG returnVal = PackageIdFromFullName(fullNameWide.c_str(), PACKAGE_INFORMATION_BASIC, &length, nullptr); - if (returnVal != ERROR_INSUFFICIENT_BUFFER) - { - LOG_WIN32(returnVal); - return {}; - } - - THROW_HR_IF(E_UNEXPECTED, length == 0); - - std::unique_ptr packageIdContent = std::make_unique(length); - - returnVal = PackageIdFromFullName(fullNameWide.c_str(), PACKAGE_INFORMATION_BASIC, &length, packageIdContent.get()); - if (returnVal != ERROR_SUCCESS) - { - LOG_WIN32(returnVal); - return {}; - } - - PACKAGE_ID* packageId = (PACKAGE_ID*)packageIdContent.get(); - - return { Utility::ConvertToUTF8(packageId->name), packageId->version.Version }; - } - - GetCertContextResult GetCertContextFromMsix(const std::filesystem::path& msixPath) - { - // Retrieve raw signature from msix - MsixInfo msixInfo{ msixPath }; - auto signature = msixInfo.GetSignature(true); - - // Get the cert content - wil::unique_any signedMessage; - wil::unique_hcertstore certStore; - CRYPT_DATA_BLOB signatureBlob = { 0 }; - signatureBlob.cbData = static_cast(signature.size()); - signatureBlob.pbData = signature.data(); - THROW_LAST_ERROR_IF(!CryptQueryObject( - CERT_QUERY_OBJECT_BLOB, - &signatureBlob, - CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED, - CERT_QUERY_FORMAT_FLAG_BINARY, - 0, // Reserved parameter - NULL, // No encoding info needed - NULL, - NULL, - &certStore, - &signedMessage, - NULL)); - - // Get the signer size and information from the signed data message - // The properties of the signer info will be used to uniquely identify the signing certificate in the certificate store - DWORD signerInfoSize = 0; - THROW_LAST_ERROR_IF(!CryptMsgGetParam( - signedMessage.get(), - CMSG_SIGNER_INFO_PARAM, - 0, - NULL, - &signerInfoSize)); - - // Check that the signer info size is within reasonable bounds; under the max length of a string for the issuer field - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), !(signerInfoSize > 0 && signerInfoSize < STRSAFE_MAX_CCH)); - - std::vector signerInfoBuffer; - signerInfoBuffer.resize(signerInfoSize); - THROW_LAST_ERROR_IF(!CryptMsgGetParam( - signedMessage.get(), - CMSG_SIGNER_INFO_PARAM, - 0, - signerInfoBuffer.data(), - &signerInfoSize)); - - // Get the signing certificate from the certificate store based on the issuer and serial number of the signer info - CMSG_SIGNER_INFO* signerInfo = reinterpret_cast(signerInfoBuffer.data()); - CERT_INFO certInfo; - certInfo.Issuer = signerInfo->Issuer; - certInfo.SerialNumber = signerInfo->SerialNumber; - - wil::unique_cert_context certContext; - certContext.reset(CertGetSubjectCertificateFromStore( - certStore.get(), - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - &certInfo)); - THROW_LAST_ERROR_IF(!certContext.get()); - - return { std::move(certContext), std::move(certStore) }; - } - - MsixInfo::MsixInfo(std::string_view uriStr) - { - m_stream = Utility::GetReadOnlyStreamFromURI(uriStr); - - if (GetBundleReader(m_stream.Get(), &m_bundleReader)) - { - m_isBundle = true; - } - else if (GetPackageReader(m_stream.Get(), &m_packageReader)) - { - m_isBundle = false; - } - else - { - THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INSTALL_OPEN_PACKAGE_FAILED), - "Failed to open uri as msix package or bundle. Uri: %hs", uriStr.data()); - } - } - - std::vector MsixInfo::GetSignature(bool skipP7xFileId) - { - ComPtr signatureFile; - if (m_isBundle) - { - THROW_IF_FAILED(m_bundleReader->GetFootprintFile(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_SIGNATURE, &signatureFile)); - } - else - { - THROW_IF_FAILED(m_packageReader->GetFootprintFile(APPX_FOOTPRINT_FILE_TYPE_SIGNATURE, &signatureFile)); - } - - std::vector signatureContent; - DWORD signatureSize; - - ComPtr signatureStream; - THROW_IF_FAILED(signatureFile->GetStream(&signatureStream)); - - STATSTG stat = { 0 }; - THROW_IF_FAILED(signatureStream->Stat(&stat, STATFLAG_NONAME)); - THROW_HR_IF(E_UNEXPECTED, stat.cbSize.HighPart != 0); // Signature size should be small - signatureSize = stat.cbSize.LowPart; - THROW_HR_IF(E_UNEXPECTED, signatureSize <= P7xFileIdSize); - - if (skipP7xFileId) - { - // Validate msix signature header - byte headerBuffer[P7xFileIdSize]; - DWORD headerRead; - THROW_IF_FAILED(signatureStream->Read(headerBuffer, P7xFileIdSize, &headerRead)); - THROW_HR_IF_MSG(E_UNEXPECTED, headerRead != P7xFileIdSize, "Failed to read signature header"); - THROW_HR_IF_MSG(E_UNEXPECTED, !std::equal(P7xFileId, P7xFileId + P7xFileIdSize, headerBuffer), "Unexpected msix signature header"); - signatureSize -= P7xFileIdSize; - } - - signatureContent.resize(signatureSize); - - DWORD signatureRead; - THROW_IF_FAILED(signatureStream->Read(signatureContent.data(), signatureSize, &signatureRead)); - THROW_HR_IF_MSG(E_UNEXPECTED, signatureRead != signatureSize, "Failed to read the whole signature stream"); - - return signatureContent; - } - - Utility::SHA256::HashBuffer MsixInfo::GetSignatureHash() - { - auto signature = GetSignature(); - return Utility::SHA256::ComputeHash(signature.data(), static_cast(signature.size())); - } - - std::wstring MsixInfo::GetDigest() - { - ComPtr digestProvider; - if (m_isBundle) - { - THROW_IF_FAILED(m_bundleReader.As(&digestProvider)); - } - else - { - THROW_IF_FAILED(m_packageReader.As(&digestProvider)); - } - - wil::unique_cotaskmem_string result; - THROW_IF_FAILED(digestProvider->GetDigest(&result)); - - return result.get(); - } - - std::wstring MsixInfo::GetPackageFullNameWide() - { - ComPtr packageId; - if (m_isBundle) - { - ComPtr manifestReader; - THROW_IF_FAILED(m_bundleReader->GetManifest(&manifestReader)); - THROW_IF_FAILED(manifestReader->GetPackageId(&packageId)); - } - else - { - ComPtr manifestReader; - THROW_IF_FAILED(m_packageReader->GetManifest(&manifestReader)); - THROW_IF_FAILED(manifestReader->GetPackageId(&packageId)); - } - - wil::unique_cotaskmem_string fullName; - THROW_IF_FAILED(packageId->GetPackageFullName(&fullName)); - - return { fullName.get() }; - } - - std::string MsixInfo::GetPackageFullName() - { - return Utility::ConvertToUTF8(GetPackageFullNameWide()); - } - - std::vector> MsixInfo::GetAppPackages(bool includeStub) const - { - if (!m_isBundle) - { - return { m_packageReader }; - } - - std::vector> packages; - - ComPtr manifestReader; - THROW_IF_FAILED(m_bundleReader->GetManifest(&manifestReader)); - - ComPtr packageInfoItems; - THROW_IF_FAILED(manifestReader->GetPackageInfoItems(&packageInfoItems)); - - BOOL hasCurrent = FALSE; - THROW_IF_FAILED(packageInfoItems->GetHasCurrent(&hasCurrent)); - while (hasCurrent) - { - ComPtr packageInfo; - THROW_IF_FAILED(packageInfoItems->GetCurrent(&packageInfo)); - - APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType; - THROW_IF_FAILED(packageInfo->GetPackageType(&packageType)); - - // Check flat bundle case. - UINT64 offset; - THROW_IF_FAILED(packageInfo->GetOffset(&offset)); - bool isContained = offset != 0; - - // Check stub package case. - ComPtr packageInfo4; - THROW_IF_FAILED(packageInfo.As(&packageInfo4)); - BOOL isStub = FALSE; - THROW_IF_FAILED(packageInfo4->GetIsStub(&isStub)); - - if (isContained && (includeStub || !isStub) && - packageType == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) - { - wil::unique_cotaskmem_string fileName; - THROW_IF_FAILED(packageInfo->GetFileName(&fileName)); - - ComPtr packageFile; - THROW_IF_FAILED(m_bundleReader->GetPayloadPackage(fileName.get(), &packageFile)); - - ComPtr stream; - THROW_IF_FAILED(packageFile->GetStream(&stream)); - - ComPtr packageReader; - if (GetPackageReader(stream.Get(), &packageReader)) - { - packages.emplace_back(std::move(packageReader)); - } - else - { - AICLI_LOG(Core, Warning, << "Could not get package reader for bundle payload."); - } - } - - THROW_IF_FAILED(packageInfoItems->MoveNext(&hasCurrent)); - } - - return packages; - } - - std::vector MsixInfo::GetAppPackageManifests(bool includeStub) const - { - std::vector manifests; - auto packages = GetAppPackages(includeStub); - for (const auto& package : packages) - { - ComPtr manifestReader; - THROW_IF_FAILED(package->GetManifest(&manifestReader)); - manifests.emplace_back(std::move(manifestReader)); - } - - return manifests; - } - - bool MsixInfo::IsNewerThan(const std::filesystem::path& otherPackage) - { - THROW_HR_IF(E_NOT_VALID_STATE, m_isBundle); - - MsixInfo other{ otherPackage }; - - THROW_HR_IF(E_INVALIDARG, other.m_isBundle); - - ComPtr otherReader; - THROW_IF_FAILED(other.m_packageReader->GetManifest(&otherReader)); - - ComPtr manifestReader; - THROW_IF_FAILED(m_packageReader->GetManifest(&manifestReader)); - - return (GetVersionFromManifestReader(manifestReader.Get()) > GetVersionFromManifestReader(otherReader.Get())); - } - - bool MsixInfo::IsNewerThan(const winrt::Windows::ApplicationModel::PackageVersion& otherVersion) - { - THROW_HR_IF(E_NOT_VALID_STATE, m_isBundle); - - ComPtr manifestReader; - THROW_IF_FAILED(m_packageReader->GetManifest(&manifestReader)); - - return (GetVersionFromManifestReader(manifestReader.Get()) > GetVersionFromVersion(otherVersion)); - } - - void MsixInfo::WriteToFile(std::string_view packageFile, const std::filesystem::path& target, IProgressCallback& progress) - { - std::wstring fileUTF16 = Utility::ConvertToUTF16(packageFile); - - ComPtr appxFile; - if (m_isBundle) - { - THROW_IF_FAILED(m_bundleReader->GetPayloadPackage(fileUTF16.c_str(), &appxFile)); - } - else - { - THROW_IF_FAILED(m_packageReader->GetPayloadFile(fileUTF16.c_str(), &appxFile)); - } - - WriteAppxFileToFile(appxFile.Get(), target, progress); - } - - void MsixInfo::WriteManifestToFile(const std::filesystem::path& target, IProgressCallback& progress) - { - ComPtr appxFile; - if (m_isBundle) - { - THROW_IF_FAILED(m_bundleReader->GetFootprintFile(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST, &appxFile)); - } - else - { - THROW_IF_FAILED(m_packageReader->GetFootprintFile(APPX_FOOTPRINT_FILE_TYPE_MANIFEST, &appxFile)); - } - - WriteAppxFileToFile(appxFile.Get(), target, progress); - } - - void MsixInfo::WriteToFileHandle(std::string_view packageFile, HANDLE target, IProgressCallback& progress) - { - std::wstring fileUTF16 = Utility::ConvertToUTF16(packageFile); - - ComPtr appxFile; - if (m_isBundle) - { - THROW_IF_FAILED(m_bundleReader->GetPayloadPackage(fileUTF16.c_str(), &appxFile)); - } - else - { - THROW_IF_FAILED(m_packageReader->GetPayloadFile(fileUTF16.c_str(), &appxFile)); - } - - WriteAppxFileToFileHandle(appxFile.Get(), target, progress); - } - - WriteLockedMsixFile::WriteLockedMsixFile(const std::filesystem::path& path) - { - m_file = Utility::ManagedFile::OpenWriteLockedFile(path, 0); - } - - bool WriteLockedMsixFile::ValidateTrustInfo(bool checkMicrosoftOrigin) const - { - return ValidateMsixTrustInfo(m_file.GetFilePath(), checkMicrosoftOrigin); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerMsixInfo.h" +#include "HttpStream/HttpRandomAccessStream.h" +#include "Public/AppInstallerDownloader.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerDownloader.h" +#include "Public/AppInstallerRuntime.h" + +using namespace winrt::Windows::Storage::Streams; +using namespace Microsoft::WRL; +using namespace AppInstaller::Utility::HttpStream; +using namespace winrt::Windows::Management::Deployment; + +namespace AppInstaller::Msix +{ + namespace + { + // MSIX-specific header placed in the P7X file, before the actual signature + const byte P7xFileId[] = { 0x50, 0x4b, 0x43, 0x58 }; + const DWORD P7xFileIdSize = sizeof(P7xFileId); + + // Gets the version from the manifest reader. + UINT64 GetVersionFromManifestReader(IAppxManifestReader* reader) + { + ComPtr packageId; + THROW_IF_FAILED(reader->GetPackageId(&packageId)); + + UINT64 result = 0; + THROW_IF_FAILED(packageId->GetVersion(&result)); + + return result; + } + + // Gets the UINT64 version from the version struct. + UINT64 GetVersionFromVersion(const winrt::Windows::ApplicationModel::PackageVersion& version) + { + UINT64 result = version.Major; + result = (result << 16) | version.Minor; + result = (result << 16) | version.Build; + result = (result << 16) | version.Revision; + + return result; + } + + // Writes the stream (from current location) to the given file. + void WriteStreamToFile(IStream* stream, UINT64 expectedSize, const std::filesystem::path& target, IProgressCallback& progress) + { + std::filesystem::path tempFile = target; + tempFile += ".dnld"; + + { + std::ofstream file(tempFile, std::ios_base::binary | std::ios_base::out | std::ios_base::trunc); + + constexpr ULONG bufferSize = 1 << 20; + std::unique_ptr buffer = std::make_unique(bufferSize); + + UINT64 totalBytesRead = 0; + + while (!progress.IsCancelledBy(CancelReason::Any)) + { + ULONG bytesRead = 0; + HRESULT hr = stream->Read(buffer.get(), bufferSize, &bytesRead); + + if (bytesRead) + { + // If we got bytes, just accept them and keep going. + LOG_IF_FAILED(hr); + + THROW_HR_IF_MSG(E_UNEXPECTED, expectedSize && totalBytesRead + bytesRead > expectedSize, "Read more bytes than expected size"); + + file.write(buffer.get(), bytesRead); + totalBytesRead += bytesRead; + progress.OnProgress(totalBytesRead, expectedSize, ProgressType::Bytes); + } + else + { + // If given a size, and we have read it all, quit + if (expectedSize && totalBytesRead == expectedSize) + { + break; + } + + // If the stream returned an error, throw it + THROW_IF_FAILED(hr); + + // If we were given a size and didn't reach it, throw our own error; + // otherwise assume that this is just normal EOF. + if (expectedSize) + { + THROW_WIN32(ERROR_HANDLE_EOF); + } + else + { + break; + } + } + } + } + + std::filesystem::path backupFile = target; + backupFile += ".bkup"; + if (std::filesystem::exists(target)) + { + if (std::filesystem::exists(backupFile)) + { + std::filesystem::remove(backupFile); + } + std::filesystem::rename(target, backupFile); + } + + std::filesystem::rename(tempFile, target); + } + + // Writes the appx file to the given file. + void WriteAppxFileToFile(IAppxFile* appxFile, const std::filesystem::path& target, IProgressCallback& progress) + { + UINT64 size = 0; + THROW_IF_FAILED(appxFile->GetSize(&size)); + + ComPtr stream; + THROW_IF_FAILED(appxFile->GetStream(&stream)); + + WriteStreamToFile(stream.Get(), size, target, progress); + } + + // Writes the stream (from current location) to the given file handle. + void WriteStreamToFileHandle(IStream* stream, UINT64 expectedSize, HANDLE target, IProgressCallback& progress) + { + constexpr ULONG bufferSize = 1 << 20; + std::unique_ptr buffer = std::make_unique(bufferSize); + + UINT64 totalBytesRead = 0; + + while (!progress.IsCancelledBy(CancelReason::Any)) + { + ULONG bytesRead = 0; + HRESULT hr = stream->Read(buffer.get(), bufferSize, &bytesRead); + + if (bytesRead) + { + // If we got bytes, just accept them and keep going. + LOG_IF_FAILED(hr); + + THROW_HR_IF_MSG(E_UNEXPECTED, expectedSize && totalBytesRead + bytesRead > expectedSize, "Read more bytes than expected size"); + + DWORD bytesWritten = 0; + THROW_LAST_ERROR_IF(!WriteFile(target, buffer.get(), bytesRead, &bytesWritten, nullptr)); + THROW_HR_IF(E_UNEXPECTED, bytesRead != bytesWritten); + totalBytesRead += bytesRead; + progress.OnProgress(totalBytesRead, expectedSize, ProgressType::Bytes); + } + else + { + // If given a size, and we have read it all, quit + if (expectedSize && totalBytesRead == expectedSize) + { + break; + } + + // If the stream returned an error, throw it + THROW_IF_FAILED(hr); + + // If we were given a size and didn't reach it, throw our own error; + // otherwise assume that this is just normal EOF. + if (expectedSize) + { + THROW_WIN32(ERROR_HANDLE_EOF); + } + else + { + break; + } + } + } + } + + // Writes the appx file to the given file handle. + void WriteAppxFileToFileHandle(IAppxFile* appxFile, HANDLE target, IProgressCallback& progress) + { + UINT64 size = 0; + THROW_IF_FAILED(appxFile->GetSize(&size)); + + ComPtr stream; + THROW_IF_FAILED(appxFile->GetStream(&stream)); + + WriteStreamToFileHandle(stream.Get(), size, target, progress); + } + + bool ValidateMsixTrustInfo(const std::filesystem::path& msixPath, bool verifyMicrosoftOrigin) + { + bool result = false; + AICLI_LOG(Core, Info, << "Started trust validation of msix at: " << msixPath); + + try + { + bool verifyChainResult = false; + + // First verify certificate chain if requested. + if (verifyMicrosoftOrigin) + { + auto [certContext, certStore] = GetCertContextFromMsix(msixPath); + + // Get certificate chain context for validation + CERT_CHAIN_PARA certChainParameters = { 0 }; + certChainParameters.cbSize = sizeof(CERT_CHAIN_PARA); + certChainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND; + DWORD certChainFlags = CERT_CHAIN_CACHE_ONLY_URL_RETRIEVAL; + + wil::unique_cert_chain_context certChainContext; + THROW_LAST_ERROR_IF(!CertGetCertificateChain( + HCCE_LOCAL_MACHINE, + certContext.get(), + NULL, // Use the current system time for CRL validation + certStore.get(), + &certChainParameters, + certChainFlags, + NULL, // Reserved parameter; must be NULL + &certChainContext)); + + // Validate that the certificate chain is rooted in one of the well-known Microsoft root certs + CERT_CHAIN_POLICY_PARA policyParameters = { 0 }; + policyParameters.cbSize = sizeof(CERT_CHAIN_POLICY_PARA); + policyParameters.dwFlags = MICROSOFT_ROOT_CERT_CHAIN_POLICY_CHECK_APPLICATION_ROOT_FLAG; + CERT_CHAIN_POLICY_STATUS policyStatus = { 0 }; + policyStatus.cbSize = sizeof(CERT_CHAIN_POLICY_STATUS); + LPCSTR policyOid = CERT_CHAIN_POLICY_MICROSOFT_ROOT; + BOOL certChainVerifySucceeded = CertVerifyCertificateChainPolicy( + policyOid, + certChainContext.get(), + &policyParameters, + &policyStatus); + + AICLI_LOG(Core, Info, << "Result for certificate chain validation of Microsoft origin: " << policyStatus.dwError); + + verifyChainResult = certChainVerifySucceeded && policyStatus.dwError == ERROR_SUCCESS; + } + else + { + verifyChainResult = true; + } + + // If certificate chain origin validation is success or not requested, then validate the trust info of the file. + if (verifyChainResult) + { + // Set up the structures needed for the WinVerifyTrust call + WINTRUST_FILE_INFO fileInfo = { 0 }; + fileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO); + fileInfo.pcwszFilePath = msixPath.c_str(); + + WINTRUST_DATA trustData = { 0 }; + trustData.cbStruct = sizeof(WINTRUST_DATA); + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_WHOLECHAIN; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + trustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + trustData.pFile = &fileInfo; + + GUID verifyActionId = WINTRUST_ACTION_GENERIC_VERIFY_V2; + + HRESULT verifyTrustResult = static_cast(WinVerifyTrust(static_cast(INVALID_HANDLE_VALUE), &verifyActionId, &trustData)); + AICLI_LOG(Core, Info, << "Result for trust info validation of the msix: " << verifyTrustResult); + + result = verifyTrustResult == S_OK; + } + } + catch (const wil::ResultException& re) + { + AICLI_LOG(Core, Error, << "Failed during msix trust validation. Error: " << re.GetErrorCode()); + result = false; + } + catch (...) + { + AICLI_LOG(Core, Error, << "Failed during msix trust validation."); + result = false; + } + + return result; + } + } + + bool GetBundleReader( + IStream* inputStream, + IAppxBundleReader** reader) + { + ComPtr bundleFactory; + + // Create a new Appxbundle factory + THROW_IF_FAILED(CoCreateInstance( + __uuidof(AppxBundleFactory), + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IAppxBundleFactory), + (LPVOID*)(&bundleFactory))); + + HRESULT hr = bundleFactory->CreateBundleReader(inputStream, reader); + + if (SUCCEEDED(hr)) + { + return true; + } + else if (hr == APPX_E_MISSING_REQUIRED_FILE) + { + // APPX_E_MISSING_REQUIRED_FILE returned when trying to open + // an *.msix as an *.msixbundle or vice-versa. + return false; + } + else + { + THROW_HR(hr); + } + } + + bool GetPackageReader( + IStream* inputStream, + IAppxPackageReader** reader) + { + ComPtr appxFactory; + + // Create a new Appx factory + THROW_IF_FAILED(CoCreateInstance( + __uuidof(AppxFactory), + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IAppxFactory), + (LPVOID*)(&appxFactory))); + + // Create a new package reader using the factory. + HRESULT hr = appxFactory->CreatePackageReader(inputStream, reader); + + if (SUCCEEDED(hr)) + { + return true; + } + else if (hr == APPX_E_MISSING_REQUIRED_FILE) + { + // APPX_E_MISSING_REQUIRED_FILE returned when trying to open + // an *.msix as an *.msixbundle or vice-versa. + return false; + } + else + { + THROW_HR(hr); + } + } + + void GetManifestReader( + IStream* inputStream, + IAppxManifestReader** reader) + { + ComPtr appxFactory; + + THROW_IF_FAILED(CoCreateInstance( + __uuidof(AppxFactory), + nullptr, + CLSCTX_INPROC_SERVER, + __uuidof(IAppxFactory), + (LPVOID*)(&appxFactory))); + + THROW_IF_FAILED(appxFactory->CreateManifestReader(inputStream, reader)); + } + + std::optional GetPackageFullNameFromFamilyName(std::string_view familyName) + { + PackageManager packageManager; + + std::wstring pfn = Utility::ConvertToUTF16(familyName); + + // PackageManager.FindPackages() can find all packages (including provisioned ones) but requires admin. + // For non admin callers, use FindPackagesByPackageFamily where only packages registered to current user will be found. + if (Runtime::IsRunningAsAdmin()) + { + auto packages = packageManager.FindPackages(pfn); + + std::optional result; + for (const auto& package : packages) + { + if (result.has_value()) + { + // More than 1 package found. Don't directly error, let caller deal with it. + AICLI_LOG(Core, Error, << "Multiple packages found for family name: " << familyName); + return {}; + } + + result = Utility::ConvertToUTF8(package.Id().FullName()); + } + + return result; + } + else + { + UINT32 fullNameCount = 0; + UINT32 bufferLength = 0; + UINT32 properties = 0; + LONG findResult = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &fullNameCount, nullptr, &bufferLength, nullptr, &properties); + if (findResult == ERROR_SUCCESS || fullNameCount == 0) + { + // No package found + return {}; + } + else if (findResult != ERROR_INSUFFICIENT_BUFFER) + { + THROW_WIN32(findResult); + } + else if (fullNameCount != 1) + { + // Don't directly error, let caller deal with it + AICLI_LOG(Core, Error, << "Multiple packages found for family name: " << fullNameCount); + return {}; + } + + // fullNameCount == 1 at this point + PWSTR fullNamePtr; + std::wstring buffer(static_cast(bufferLength) + 1, '\0'); + THROW_IF_WIN32_ERROR(FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &fullNameCount, &fullNamePtr, &bufferLength, &buffer[0], &properties)); + if (fullNameCount != 1 || bufferLength == 0) + { + // Something changed in between, abandon + AICLI_LOG(Core, Error, << "Packages found for family name: " << fullNameCount); + return {}; + } + buffer.resize(bufferLength - 1); + return Utility::ConvertToUTF8(buffer); + } + } + + std::string GetPackageFamilyNameFromFullName(std::string_view fullName) + { + std::wstring result; + result.resize(PACKAGE_FAMILY_NAME_MAX_LENGTH + 1); + UINT32 size = static_cast(result.size()); + THROW_IF_WIN32_ERROR(PackageFamilyNameFromFullName(Utility::ConvertToUTF16(fullName).c_str(), &size, &result[0])); + result.resize(size - 1); + return Utility::ConvertToUTF8(result); + } + + std::optional GetPackageLocationFromFullName(std::string_view fullName) + { + std::wstring fn = Utility::ConvertToUTF16(fullName); + + UINT32 length = 0; + LONG returnVal = GetStagedPackagePathByFullName(fn.c_str(), &length, nullptr); + if (returnVal != ERROR_INSUFFICIENT_BUFFER) + { + LOG_WIN32(returnVal); + return {}; + } + + THROW_HR_IF(E_UNEXPECTED, length == 0); + + std::wstring result; + result.resize(length); + + returnVal = GetStagedPackagePathByFullName(fn.c_str(), &length, &result[0]); + if (returnVal != ERROR_SUCCESS) + { + LOG_WIN32(returnVal); + return {}; + } + + result.resize(length - 1); + return { result }; + } + + Msix::PackageIdInfo GetPackageIdInfoFromFullName(std::string_view fullName) + { + std::wstring fullNameWide = Utility::ConvertToUTF16(fullName); + + UINT32 length = 0; + LONG returnVal = PackageIdFromFullName(fullNameWide.c_str(), PACKAGE_INFORMATION_BASIC, &length, nullptr); + if (returnVal != ERROR_INSUFFICIENT_BUFFER) + { + LOG_WIN32(returnVal); + return {}; + } + + THROW_HR_IF(E_UNEXPECTED, length == 0); + + std::unique_ptr packageIdContent = std::make_unique(length); + + returnVal = PackageIdFromFullName(fullNameWide.c_str(), PACKAGE_INFORMATION_BASIC, &length, packageIdContent.get()); + if (returnVal != ERROR_SUCCESS) + { + LOG_WIN32(returnVal); + return {}; + } + + PACKAGE_ID* packageId = (PACKAGE_ID*)packageIdContent.get(); + + return { Utility::ConvertToUTF8(packageId->name), packageId->version.Version }; + } + + GetCertContextResult GetCertContextFromMsix(const std::filesystem::path& msixPath) + { + // Retrieve raw signature from msix + MsixInfo msixInfo{ msixPath }; + auto signature = msixInfo.GetSignature(true); + + // Get the cert content + wil::unique_any signedMessage; + wil::unique_hcertstore certStore; + CRYPT_DATA_BLOB signatureBlob = { 0 }; + signatureBlob.cbData = static_cast(signature.size()); + signatureBlob.pbData = signature.data(); + THROW_LAST_ERROR_IF(!CryptQueryObject( + CERT_QUERY_OBJECT_BLOB, + &signatureBlob, + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED, + CERT_QUERY_FORMAT_FLAG_BINARY, + 0, // Reserved parameter + NULL, // No encoding info needed + NULL, + NULL, + &certStore, + &signedMessage, + NULL)); + + // Get the signer size and information from the signed data message + // The properties of the signer info will be used to uniquely identify the signing certificate in the certificate store + DWORD signerInfoSize = 0; + THROW_LAST_ERROR_IF(!CryptMsgGetParam( + signedMessage.get(), + CMSG_SIGNER_INFO_PARAM, + 0, + NULL, + &signerInfoSize)); + + // Check that the signer info size is within reasonable bounds; under the max length of a string for the issuer field + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_DATA), !(signerInfoSize > 0 && signerInfoSize < STRSAFE_MAX_CCH)); + + std::vector signerInfoBuffer; + signerInfoBuffer.resize(signerInfoSize); + THROW_LAST_ERROR_IF(!CryptMsgGetParam( + signedMessage.get(), + CMSG_SIGNER_INFO_PARAM, + 0, + signerInfoBuffer.data(), + &signerInfoSize)); + + // Get the signing certificate from the certificate store based on the issuer and serial number of the signer info + CMSG_SIGNER_INFO* signerInfo = reinterpret_cast(signerInfoBuffer.data()); + CERT_INFO certInfo; + certInfo.Issuer = signerInfo->Issuer; + certInfo.SerialNumber = signerInfo->SerialNumber; + + wil::unique_cert_context certContext; + certContext.reset(CertGetSubjectCertificateFromStore( + certStore.get(), + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + &certInfo)); + THROW_LAST_ERROR_IF(!certContext.get()); + + return { std::move(certContext), std::move(certStore) }; + } + + MsixInfo::MsixInfo(std::string_view uriStr) + { + m_stream = Utility::GetReadOnlyStreamFromURI(uriStr); + + if (GetBundleReader(m_stream.Get(), &m_bundleReader)) + { + m_isBundle = true; + } + else if (GetPackageReader(m_stream.Get(), &m_packageReader)) + { + m_isBundle = false; + } + else + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_INSTALL_OPEN_PACKAGE_FAILED), + "Failed to open uri as msix package or bundle. Uri: %hs", uriStr.data()); + } + } + + std::vector MsixInfo::GetSignature(bool skipP7xFileId) + { + ComPtr signatureFile; + if (m_isBundle) + { + THROW_IF_FAILED(m_bundleReader->GetFootprintFile(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_SIGNATURE, &signatureFile)); + } + else + { + THROW_IF_FAILED(m_packageReader->GetFootprintFile(APPX_FOOTPRINT_FILE_TYPE_SIGNATURE, &signatureFile)); + } + + std::vector signatureContent; + DWORD signatureSize; + + ComPtr signatureStream; + THROW_IF_FAILED(signatureFile->GetStream(&signatureStream)); + + STATSTG stat = { 0 }; + THROW_IF_FAILED(signatureStream->Stat(&stat, STATFLAG_NONAME)); + THROW_HR_IF(E_UNEXPECTED, stat.cbSize.HighPart != 0); // Signature size should be small + signatureSize = stat.cbSize.LowPart; + THROW_HR_IF(E_UNEXPECTED, signatureSize <= P7xFileIdSize); + + if (skipP7xFileId) + { + // Validate msix signature header + byte headerBuffer[P7xFileIdSize]; + DWORD headerRead; + THROW_IF_FAILED(signatureStream->Read(headerBuffer, P7xFileIdSize, &headerRead)); + THROW_HR_IF_MSG(E_UNEXPECTED, headerRead != P7xFileIdSize, "Failed to read signature header"); + THROW_HR_IF_MSG(E_UNEXPECTED, !std::equal(P7xFileId, P7xFileId + P7xFileIdSize, headerBuffer), "Unexpected msix signature header"); + signatureSize -= P7xFileIdSize; + } + + signatureContent.resize(signatureSize); + + DWORD signatureRead; + THROW_IF_FAILED(signatureStream->Read(signatureContent.data(), signatureSize, &signatureRead)); + THROW_HR_IF_MSG(E_UNEXPECTED, signatureRead != signatureSize, "Failed to read the whole signature stream"); + + return signatureContent; + } + + Utility::SHA256::HashBuffer MsixInfo::GetSignatureHash() + { + auto signature = GetSignature(); + return Utility::SHA256::ComputeHash(signature.data(), static_cast(signature.size())); + } + + std::wstring MsixInfo::GetDigest() + { + ComPtr digestProvider; + if (m_isBundle) + { + THROW_IF_FAILED(m_bundleReader.As(&digestProvider)); + } + else + { + THROW_IF_FAILED(m_packageReader.As(&digestProvider)); + } + + wil::unique_cotaskmem_string result; + THROW_IF_FAILED(digestProvider->GetDigest(&result)); + + return result.get(); + } + + std::wstring MsixInfo::GetPackageFullNameWide() + { + ComPtr packageId; + if (m_isBundle) + { + ComPtr manifestReader; + THROW_IF_FAILED(m_bundleReader->GetManifest(&manifestReader)); + THROW_IF_FAILED(manifestReader->GetPackageId(&packageId)); + } + else + { + ComPtr manifestReader; + THROW_IF_FAILED(m_packageReader->GetManifest(&manifestReader)); + THROW_IF_FAILED(manifestReader->GetPackageId(&packageId)); + } + + wil::unique_cotaskmem_string fullName; + THROW_IF_FAILED(packageId->GetPackageFullName(&fullName)); + + return { fullName.get() }; + } + + std::string MsixInfo::GetPackageFullName() + { + return Utility::ConvertToUTF8(GetPackageFullNameWide()); + } + + std::vector> MsixInfo::GetAppPackages(bool includeStub) const + { + if (!m_isBundle) + { + return { m_packageReader }; + } + + std::vector> packages; + + ComPtr manifestReader; + THROW_IF_FAILED(m_bundleReader->GetManifest(&manifestReader)); + + ComPtr packageInfoItems; + THROW_IF_FAILED(manifestReader->GetPackageInfoItems(&packageInfoItems)); + + BOOL hasCurrent = FALSE; + THROW_IF_FAILED(packageInfoItems->GetHasCurrent(&hasCurrent)); + while (hasCurrent) + { + ComPtr packageInfo; + THROW_IF_FAILED(packageInfoItems->GetCurrent(&packageInfo)); + + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType; + THROW_IF_FAILED(packageInfo->GetPackageType(&packageType)); + + // Check flat bundle case. + UINT64 offset; + THROW_IF_FAILED(packageInfo->GetOffset(&offset)); + bool isContained = offset != 0; + + // Check stub package case. + ComPtr packageInfo4; + THROW_IF_FAILED(packageInfo.As(&packageInfo4)); + BOOL isStub = FALSE; + THROW_IF_FAILED(packageInfo4->GetIsStub(&isStub)); + + if (isContained && (includeStub || !isStub) && + packageType == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + wil::unique_cotaskmem_string fileName; + THROW_IF_FAILED(packageInfo->GetFileName(&fileName)); + + ComPtr packageFile; + THROW_IF_FAILED(m_bundleReader->GetPayloadPackage(fileName.get(), &packageFile)); + + ComPtr stream; + THROW_IF_FAILED(packageFile->GetStream(&stream)); + + ComPtr packageReader; + if (GetPackageReader(stream.Get(), &packageReader)) + { + packages.emplace_back(std::move(packageReader)); + } + else + { + AICLI_LOG(Core, Warning, << "Could not get package reader for bundle payload."); + } + } + + THROW_IF_FAILED(packageInfoItems->MoveNext(&hasCurrent)); + } + + return packages; + } + + std::vector MsixInfo::GetAppPackageManifests(bool includeStub) const + { + std::vector manifests; + auto packages = GetAppPackages(includeStub); + for (const auto& package : packages) + { + ComPtr manifestReader; + THROW_IF_FAILED(package->GetManifest(&manifestReader)); + manifests.emplace_back(std::move(manifestReader)); + } + + return manifests; + } + + bool MsixInfo::IsNewerThan(const std::filesystem::path& otherPackage) + { + THROW_HR_IF(E_NOT_VALID_STATE, m_isBundle); + + MsixInfo other{ otherPackage }; + + THROW_HR_IF(E_INVALIDARG, other.m_isBundle); + + ComPtr otherReader; + THROW_IF_FAILED(other.m_packageReader->GetManifest(&otherReader)); + + ComPtr manifestReader; + THROW_IF_FAILED(m_packageReader->GetManifest(&manifestReader)); + + return (GetVersionFromManifestReader(manifestReader.Get()) > GetVersionFromManifestReader(otherReader.Get())); + } + + bool MsixInfo::IsNewerThan(const winrt::Windows::ApplicationModel::PackageVersion& otherVersion) + { + THROW_HR_IF(E_NOT_VALID_STATE, m_isBundle); + + ComPtr manifestReader; + THROW_IF_FAILED(m_packageReader->GetManifest(&manifestReader)); + + return (GetVersionFromManifestReader(manifestReader.Get()) > GetVersionFromVersion(otherVersion)); + } + + void MsixInfo::WriteToFile(std::string_view packageFile, const std::filesystem::path& target, IProgressCallback& progress) + { + std::wstring fileUTF16 = Utility::ConvertToUTF16(packageFile); + + ComPtr appxFile; + if (m_isBundle) + { + THROW_IF_FAILED(m_bundleReader->GetPayloadPackage(fileUTF16.c_str(), &appxFile)); + } + else + { + THROW_IF_FAILED(m_packageReader->GetPayloadFile(fileUTF16.c_str(), &appxFile)); + } + + WriteAppxFileToFile(appxFile.Get(), target, progress); + } + + void MsixInfo::WriteManifestToFile(const std::filesystem::path& target, IProgressCallback& progress) + { + ComPtr appxFile; + if (m_isBundle) + { + THROW_IF_FAILED(m_bundleReader->GetFootprintFile(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST, &appxFile)); + } + else + { + THROW_IF_FAILED(m_packageReader->GetFootprintFile(APPX_FOOTPRINT_FILE_TYPE_MANIFEST, &appxFile)); + } + + WriteAppxFileToFile(appxFile.Get(), target, progress); + } + + void MsixInfo::WriteToFileHandle(std::string_view packageFile, HANDLE target, IProgressCallback& progress) + { + std::wstring fileUTF16 = Utility::ConvertToUTF16(packageFile); + + ComPtr appxFile; + if (m_isBundle) + { + THROW_IF_FAILED(m_bundleReader->GetPayloadPackage(fileUTF16.c_str(), &appxFile)); + } + else + { + THROW_IF_FAILED(m_packageReader->GetPayloadFile(fileUTF16.c_str(), &appxFile)); + } + + WriteAppxFileToFileHandle(appxFile.Get(), target, progress); + } + + WriteLockedMsixFile::WriteLockedMsixFile(const std::filesystem::path& path) + { + m_file = Utility::ManagedFile::OpenWriteLockedFile(path, 0); + } + + bool WriteLockedMsixFile::ValidateTrustInfo(bool checkMicrosoftOrigin) const + { + return ValidateMsixTrustInfo(m_file.GetFilePath(), checkMicrosoftOrigin); + } +} diff --git a/src/AppInstallerCommonCore/NameNormalization.cpp b/src/AppInstallerCommonCore/NameNormalization.cpp index d26a1d3879..361dfaf90e 100644 --- a/src/AppInstallerCommonCore/NameNormalization.cpp +++ b/src/AppInstallerCommonCore/NameNormalization.cpp @@ -1,528 +1,528 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/NameNormalization.h" -#include "Public/AppInstallerStrings.h" -#include "Public/winget/Regex.h" - - -namespace AppInstaller::Utility -{ - namespace - { - struct InterimNameNormalizationResult - { - std::wstring Name; - Architecture Architecture = Architecture::Unknown; - std::wstring Locale; - }; - - struct InterimPublisherNormalizationResult - { - std::wstring Publisher; - }; - - // To maintain consistency, changes that result in different output must be done in a new version. - // This can potentially be ignored (if thought through) when the changes will only increase the - // number of matches being made, with no impact to existing matches. For instance, removing an - // arbitrary new processor architecture from names would hopefully only affect existing packages - // that were not matching properly. Fixing a bug that was causing bad strings to be produced would - // be impactful, and thus should likely result in a new iteration. - class NormalizationInitial : public details::INameNormalizer - { - static std::wstring PrepareForValidation(std::string_view value) - { - std::wstring result = Utility::Normalize(ConvertToUTF16(value)); - Trim(result); - size_t atPos = result.find(L"@@", 3); - if (atPos != std::wstring::npos) - { - result = result.substr(0, atPos); - } - return result; - } - - // If the string is wrapped with some character groups, remove them. - // Returns true if string was wrapped; false if not. - static bool Unwrap(std::wstring& value) - { - if (value.length() >= 2) - { - bool unwrap = false; - - switch (value[0]) - { - case L'"': - unwrap = value.back() == L'"'; - break; - - case L'(': - unwrap = value.back() == L')'; - break; - } - - if (unwrap) - { - value = value.substr(1, value.length() - 2); - return true; - } - } - - return false; - } - - // Removes all matches from the input string. - static bool Remove(const Regex::Expression& re, std::wstring& input) - { - std::wstring output = re.Replace(input, {}); - bool result = (output != input); - input = std::move(output); - return result; - } - - // Removes the architecture and returns the value, if any - Architecture RemoveArchitecture(std::wstring& value) const - { - Architecture result = Architecture::Unknown; - - // Must detect this first because "32/64 bit" is a superstring of "64 bit" - if (Remove(Architecture32Or64Bit, value)) - { - // If the program is 32 and 64 bit in the same installer, leave as unknown. - } - // Must detect 64 bit before 32 bit because of "x86-64" being a superstring of "x86" - else if (Remove(ArchitectureX64, value) || Remove(Architecture64Bit, value)) - { - result = Architecture::X64; - } - else if (Remove(ArchitectureX32, value) || Remove(Architecture32Bit, value)) - { - result = Architecture::X86; - } - - return result; - } - - // Removes all matches for the given regular expressions - static bool RemoveAll(const std::vector& regexes, std::wstring& value) - { - bool result = false; - - for (const auto& re : regexes) - { - result = Remove(*re, value) || result; - } - - return result; - } - - // Removes all locales and returns the common value, if any - std::wstring RemoveLocale(std::wstring& value) const - { - bool localeFound = false; - std::wstring result; - - std::wstring newValue; - auto newValueInserter = std::back_inserter(newValue); - - Locale.ForEach(value, - [&](bool isMatch, std::wstring_view text) - { - bool copy = !isMatch; - - if (isMatch) - { - std::wstring foldedText = ConvertToUTF16(FoldCase(text)); - - // Ensure that the value is in the locale list - auto bound = std::lower_bound(Locales.begin(), Locales.end(), foldedText); - - if (bound == Locales.end() || *bound != foldedText) - { - // Match was not a locale in our list, so copy it out - copy = true; - } - else if (!localeFound) - { - // First/only match, just extract the value - result = foldedText; - localeFound = true; - } - else if (!result.empty()) - { - // For some reason, there are multiple locales listed. - // See if they have anything in common. - if (result != foldedText) - { - // Not completely the same (expected), see if they are at least the same language - result.erase(result.find(L'-')); - foldedText.erase(foldedText.find(L'-')); - - if (result != foldedText) - { - // Not the same language, abandon having a locale and just clean them - result.clear(); - } - } - } - } - - if (copy) - { - std::copy(text.begin(), text.end(), newValueInserter); - } - - return true; - }); - - value = std::move(newValue); - - return result; - } - - // Splits the string based on the regex matches, excluding empty/whitespace strings - // and any values found in the exclusions. - static std::vector Split(const Regex::Expression& re, const std::wstring& value, const std::vector& exclusions, bool stopOnExclusion = false) - { - std::vector result; - - re.ForEach(value, - [&](bool, std::wstring_view text) - { - if (IsEmptyOrWhitespace(text)) - { - return true; - } - - // Do not stop for an exclusion if it is the first word found - if (!result.empty()) - { - std::wstring foldedText = ConvertToUTF16(FoldCase(text)); - - auto bound = std::lower_bound(exclusions.begin(), exclusions.end(), foldedText); - - if (bound != exclusions.end() && *bound == foldedText) - { - return !stopOnExclusion; - } - } - - result.emplace_back(std::wstring{ text }); - return true; - }); - - return result; - } - - // Joins all of the given strings into a single value - static std::wstring Join(const std::vector& values, const std::wstring& separator = {}) - { - std::wstring result; - - bool isFirst = true; - for (const auto& v : values) - { - if (isFirst) - { - isFirst = false; - } - else - { - result += separator; - } - - result += v; - } - - return result; - } - - static constexpr Regex::Options reOptions = Regex::Options::CaseInsensitive; - - // Architecture - Regex::Expression ArchitectureX32{ R"((?<=^|[^\p{L}\p{Nd}])(X32|X86)(?=\P{Nd}|$)(?:\sEDITION)?)", reOptions }; - Regex::Expression ArchitectureX64{ R"((?<=^|[^\p{L}\p{Nd}])(X64|AMD64|X86([\p{Pd}\p{Pc}]64))(?=\P{Nd}|$)(?:\sEDITION)?)", reOptions }; - Regex::Expression Architecture32Bit{ R"((?<=^|[^\p{L}\p{Nd}])(32[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions }; - Regex::Expression Architecture64Bit{ R"((?<=^|[^\p{L}\p{Nd}])(64[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions }; - Regex::Expression Architecture32Or64Bit{ R"((?<=^|[^\p{L}\p{Nd}])((64[\\\/]32|32[\\\/]64)[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions }; - - // Locale - Regex::Expression Locale{ R"((? ProgramNameRegexes - { - &Roblox, - &Bomgar, - &PrefixParens, - &EmptyParens, - &FilePathGHS, - &FilePathParens, - &FilePathQuotes, - &FilePath, - &VersionLetter, - &VersionDelimited, - &Version, - &EN, - &NonNestedBracket, - &BracketEnclosed, - &URIProtocol, - &LeadingSymbols, - &TrailingSymbols - }; - - const std::vector PublisherNameRegexes - { - &VersionDelimited, - &Version, - &NonNestedBracket, - &BracketEnclosed, - &URIProtocol, - &NonLetters, - &TrailingNonLetters, - &AcronymSeparators - }; - - // Add values here but use Locales in code. - const std::vector LocaleViews - { - L"AF-ZA", L"AM-ET", L"AR-AE", L"AR-BH", L"AR-DZ", L"AR-EG", L"AR-IQ", L"AR-JO", L"AR-KW", L"AR-LB", L"AR-LY", - L"AR-MA", L"ARN-CL", L"AR-OM", L"AR-QA", L"AR-SA", L"AR-SY", L"AR-TN", L"AR-YE", L"AS-IN", L"BA-RU", L"BE-BY", - L"BG-BG", L"BN-BD", L"BN-IN", L"BO-CN", L"BR-FR", L"CA-ES", L"CA-ES-VALENCIA", - L"CO-FR", L"CS-CZ", L"CY-GB", L"DA-DK", L"DE-AT", - L"DE-CH", L"DE-DE", L"DE-LI", L"DE-LU", L"DSB-DE", L"DV-MV", L"EL-GR", L"EN-AU", L"EN-BZ", L"EN-CA", L"EN-GB", - L"EN-IE", L"EN-IN", L"EN-JM", L"EN-MY", L"EN-NZ", L"EN-PH", L"EN-SG", L"EN-TT", L"EN-US", L"EN-ZA", L"EN-ZW", - L"ES-AR", L"ES-BO", L"ES-CL", L"ES-CO", L"ES-CR", L"ES-DO", L"ES-EC", L"ES-ES", L"ES-GT", L"ES-HN", L"ES-MX", - L"ES-NI", L"ES-PA", L"ES-PE", L"ES-PR", L"ES-PY", L"ES-SV", L"ES-US", L"ES-UY", L"ES-VE", L"ET-EE", L"EU-ES", - L"FA-IR", L"FI-FI", L"FIL-PH", L"FO-FO", L"FR-BE", L"FR-CA", L"FR-CH", L"FR-FR", L"FR-LU", L"FR-MC", L"FY-NL", - L"GA-IE", L"GD-DB", L"GL-ES", L"GSW-FR", L"GU-IN", L"HE-IL", L"HI-IN", L"HR-BA", L"HR-HR", L"HSB-DE", L"HU-HU", - L"HY-AM", L"ID-ID", L"IG-NG", L"II-CN", L"IS-IS", L"IT-CH", L"IT-IT", L"JA-JP", L"KA-GE", L"KK-KZ", L"KL-GL", - L"KM-KH", L"KN-IN", L"KOK-IN", L"KO-KR", L"KY-KG", L"LB-LU", L"LO-LA", L"LT-LT", L"LV-LV", L"MI-NZ", L"MK-MK", - L"ML-IN", L"MN-MN", L"MOH-CA", L"MR-IN", L"MS-BN", L"MS-MY", L"MT-MT", L"NB-NO", L"NE-NP", L"NL-BE", L"NL-NL", - L"NN-NO", L"NSO-ZA", L"OC-FR", L"OR-IN", L"PA-IN", L"PL-PL", L"PRS-AF", L"PS-AF", L"PT-BR", L"PT-PT", L"QUT-GT", - L"QUZ-BO", L"QUZ-EC", L"QUZ-PE", L"RM-CH", L"RO-RO", L"RU-RU", L"RW-RW", L"SAH-RU", L"SA-IN", L"SE-FI", L"SE-NO", - L"SE-SE", L"SI-LK", L"SK-SK", L"SL-SI", L"SMA-NO", L"SMA-SE", L"SMJ-NO", L"SMJ-SE", L"SMN-FI", L"SMS-FI", L"SQ-AL", - L"SV-FI", L"SV-SE", L"SW-KE", L"SYR-SY", L"TA-IN", L"TE-IN", L"TH-TH", L"TK-TM", L"TN-ZA", L"TR-TR", L"TT-RU", - L"UG-CN", L"UK-UA", L"UR-PK", L"VI-VN", L"WO-SN", L"XH-ZA", L"YO-NG", L"ZH-CN", L"ZH-HK", L"ZH-MO", L"ZH-SG", - L"ZH-TW", L"ZU-ZA", L"AZ-CYRL-AZ", L"AZ-LATN-AZ", L"BS-CYRL-BA", L"BS-LATN-BA", L"HA-LATN-NG", L"IU-CANS-CA", - L"IU-LATN-CA", L"MN-MONG-CN", L"SR-CYRL-BA", L"SR-CYRL-CS", L"SR-CYRL-ME", L"SR-CYRL-RS", L"SR-LATN-BA", - L"SR-LATN-CS", L"SR-LATN-ME", L"SR-LATN-RS", L"TG-CYRL-TJ", L"TZM-LATN-DZ", L"UZ-CYRL-UZ", L"UZ-LATN-UZ", - }; - - // The folded and sorted version of LocaleViews. - const std::vector Locales; - - // Add values here but use LegalEntitySuffixes in code. - const std::vector LegalEntitySuffixViews - { - // Acronyms - L"AB", L"AD", L"AG", L"APS", L"AS", L"ASA", L"BV", L"CO", L"CV", L"DOO", L"eV", L"GES", L"GESMBH", L"GMBH", L"INC", L"KG", - L"KS", L"PS", L"LLC", L"LP", L"LTD", L"LTDA", L"MBH", L"NV", L"PLC", L"SL", L"PTY", L"PVT", L"SA", L"SARL", - L"SC", L"SCA", L"SL", L"SP", L"SPA", L"SRL", L"SRO", - - // Words - L"COMPANY", L"CORP", L"CORPORATION", L"HOLDING", L"HOLDINGS", L"INCORPORATED", L"LIMITED", L"SUBSIDIARY" - }; - - // The folded and sorted version of LocaleViews. - const std::vector LegalEntitySuffixes; - - const bool PreserveWhiteSpace; - - static std::vector FoldAndSort(const std::vector& input) - { - std::vector result; - std::transform(input.begin(), input.end(), std::back_inserter(result), [](const std::wstring_view wsv) { return Utility::ConvertToUTF16(Utility::FoldCase(wsv)); }); - std::sort(result.begin(), result.end()); - return result; - } - - InterimNameNormalizationResult NormalizeNameInternal(std::string_view name) const - { - InterimNameNormalizationResult result; - result.Name = PrepareForValidation(name); - while (Unwrap(result.Name)); // remove wrappers - - // handle (large majority of) SAP Business Object programs - if (SAPPackage.IsMatch(result.Name)) - { - return result; - } - - result.Architecture = RemoveArchitecture(result.Name); - result.Locale = RemoveLocale(result.Name); - - // Extract KB numbers from their parens and preserve them - result.Name = KBNumbers.Replace(result.Name, L"$1"); - - // Repeatedly remove matches for the regexes to create the minimum name - while (RemoveAll(ProgramNameRegexes, result.Name)); - - auto tokens = Split(ProgramNameSplit, result.Name, LegalEntitySuffixes); - - // Re-join the tokens and drop all undesired characters - if (PreserveWhiteSpace) - { - result.Name = Join(tokens, L" "); - Remove(NonLetterDigitOrSpace, result.Name); - } - else - { - result.Name = Join(tokens); - Remove(NonLettersAndDigits, result.Name); - } - - return result; - } - - InterimPublisherNormalizationResult NormalizePublisherInternal(std::string_view publisher) const - { - InterimPublisherNormalizationResult result; - - result.Publisher = PrepareForValidation(publisher); - while (Unwrap(result.Publisher)); // remove wrappers - - while (RemoveAll(PublisherNameRegexes, result.Publisher)); - - auto tokens = Split(PublisherNameSplit, result.Publisher, LegalEntitySuffixes, true); - - // Re-join the tokens and drop all undesired characters - if (PreserveWhiteSpace) - { - result.Publisher = Join(tokens, L" "); - Remove(NonLetterDigitOrSpace, result.Publisher); - } - else - { - result.Publisher = Join(tokens); - Remove(NonLettersAndDigits, result.Publisher); - } - - return result; - } - - public: - NormalizationInitial(bool preserveWhiteSpace) : Locales(FoldAndSort(LocaleViews)), LegalEntitySuffixes(FoldAndSort(LegalEntitySuffixViews)), PreserveWhiteSpace(preserveWhiteSpace) - { - } - - NormalizedName Normalize(std::string_view name, std::string_view publisher) const override - { - InterimNameNormalizationResult nameResult = NormalizeNameInternal(name); - InterimPublisherNormalizationResult pubResult = NormalizePublisherInternal(publisher); - - NormalizedName result; - result.Name(ConvertToUTF8(nameResult.Name)); - result.Architecture(nameResult.Architecture); - result.Locale(ConvertToUTF8(nameResult.Locale)); - result.Publisher(ConvertToUTF8(pubResult.Publisher)); - - return result; - } - - NormalizedName NormalizeName(std::string_view name) const override - { - InterimNameNormalizationResult nameResult = NormalizeNameInternal(name); - - NormalizedName result; - result.Name(ConvertToUTF8(nameResult.Name)); - result.Architecture(nameResult.Architecture); - result.Locale(ConvertToUTF8(nameResult.Locale)); - - return result; - } - - std::string NormalizePublisher(std::string_view publisher) const override - { - InterimPublisherNormalizationResult pubResult = NormalizePublisherInternal(publisher); - - return ConvertToUTF8(pubResult.Publisher); - } - }; - } - - NameNormalizer::NameNormalizer(NormalizationVersion version) - { - switch (version) - { - case AppInstaller::Utility::NormalizationVersion::Initial: - m_normalizer = std::make_unique(false); - break; - case AppInstaller::Utility::NormalizationVersion::InitialPreserveWhiteSpace: - m_normalizer = std::make_unique(true); - break; - default: - THROW_HR(E_INVALIDARG); - } - } - - NormalizedName NameNormalizer::Normalize(std::string_view name, std::string_view publisher) const - { - return m_normalizer->Normalize(name, publisher); - } - - NormalizedName NameNormalizer::NormalizeName(std::string_view name) const - { - return m_normalizer->NormalizeName(name); - } - - std::string NameNormalizer::NormalizePublisher(std::string_view publisher) const - { - return m_normalizer->NormalizePublisher(publisher); - } - - std::string NormalizedName::GetNormalizedName(NormalizationField fieldsToInclude) const - { - std::string result = Name(); - - if (WI_IsFlagSet(fieldsToInclude, NormalizationField::Architecture) && m_arch != Utility::Architecture::Unknown) - { - result += '(' + std::string(Utility::ToString(m_arch)) + ')'; - } - - return result; - } - - NormalizationField NormalizedName::GetNormalizedFields() const - { - NormalizationField result = NormalizationField::None; - - if (m_arch != Utility::Architecture::Unknown) - { - result |= NormalizationField::Architecture; - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/NameNormalization.h" +#include "Public/AppInstallerStrings.h" +#include "Public/winget/Regex.h" + + +namespace AppInstaller::Utility +{ + namespace + { + struct InterimNameNormalizationResult + { + std::wstring Name; + Architecture Architecture = Architecture::Unknown; + std::wstring Locale; + }; + + struct InterimPublisherNormalizationResult + { + std::wstring Publisher; + }; + + // To maintain consistency, changes that result in different output must be done in a new version. + // This can potentially be ignored (if thought through) when the changes will only increase the + // number of matches being made, with no impact to existing matches. For instance, removing an + // arbitrary new processor architecture from names would hopefully only affect existing packages + // that were not matching properly. Fixing a bug that was causing bad strings to be produced would + // be impactful, and thus should likely result in a new iteration. + class NormalizationInitial : public details::INameNormalizer + { + static std::wstring PrepareForValidation(std::string_view value) + { + std::wstring result = Utility::Normalize(ConvertToUTF16(value)); + Trim(result); + size_t atPos = result.find(L"@@", 3); + if (atPos != std::wstring::npos) + { + result = result.substr(0, atPos); + } + return result; + } + + // If the string is wrapped with some character groups, remove them. + // Returns true if string was wrapped; false if not. + static bool Unwrap(std::wstring& value) + { + if (value.length() >= 2) + { + bool unwrap = false; + + switch (value[0]) + { + case L'"': + unwrap = value.back() == L'"'; + break; + + case L'(': + unwrap = value.back() == L')'; + break; + } + + if (unwrap) + { + value = value.substr(1, value.length() - 2); + return true; + } + } + + return false; + } + + // Removes all matches from the input string. + static bool Remove(const Regex::Expression& re, std::wstring& input) + { + std::wstring output = re.Replace(input, {}); + bool result = (output != input); + input = std::move(output); + return result; + } + + // Removes the architecture and returns the value, if any + Architecture RemoveArchitecture(std::wstring& value) const + { + Architecture result = Architecture::Unknown; + + // Must detect this first because "32/64 bit" is a superstring of "64 bit" + if (Remove(Architecture32Or64Bit, value)) + { + // If the program is 32 and 64 bit in the same installer, leave as unknown. + } + // Must detect 64 bit before 32 bit because of "x86-64" being a superstring of "x86" + else if (Remove(ArchitectureX64, value) || Remove(Architecture64Bit, value)) + { + result = Architecture::X64; + } + else if (Remove(ArchitectureX32, value) || Remove(Architecture32Bit, value)) + { + result = Architecture::X86; + } + + return result; + } + + // Removes all matches for the given regular expressions + static bool RemoveAll(const std::vector& regexes, std::wstring& value) + { + bool result = false; + + for (const auto& re : regexes) + { + result = Remove(*re, value) || result; + } + + return result; + } + + // Removes all locales and returns the common value, if any + std::wstring RemoveLocale(std::wstring& value) const + { + bool localeFound = false; + std::wstring result; + + std::wstring newValue; + auto newValueInserter = std::back_inserter(newValue); + + Locale.ForEach(value, + [&](bool isMatch, std::wstring_view text) + { + bool copy = !isMatch; + + if (isMatch) + { + std::wstring foldedText = ConvertToUTF16(FoldCase(text)); + + // Ensure that the value is in the locale list + auto bound = std::lower_bound(Locales.begin(), Locales.end(), foldedText); + + if (bound == Locales.end() || *bound != foldedText) + { + // Match was not a locale in our list, so copy it out + copy = true; + } + else if (!localeFound) + { + // First/only match, just extract the value + result = foldedText; + localeFound = true; + } + else if (!result.empty()) + { + // For some reason, there are multiple locales listed. + // See if they have anything in common. + if (result != foldedText) + { + // Not completely the same (expected), see if they are at least the same language + result.erase(result.find(L'-')); + foldedText.erase(foldedText.find(L'-')); + + if (result != foldedText) + { + // Not the same language, abandon having a locale and just clean them + result.clear(); + } + } + } + } + + if (copy) + { + std::copy(text.begin(), text.end(), newValueInserter); + } + + return true; + }); + + value = std::move(newValue); + + return result; + } + + // Splits the string based on the regex matches, excluding empty/whitespace strings + // and any values found in the exclusions. + static std::vector Split(const Regex::Expression& re, const std::wstring& value, const std::vector& exclusions, bool stopOnExclusion = false) + { + std::vector result; + + re.ForEach(value, + [&](bool, std::wstring_view text) + { + if (IsEmptyOrWhitespace(text)) + { + return true; + } + + // Do not stop for an exclusion if it is the first word found + if (!result.empty()) + { + std::wstring foldedText = ConvertToUTF16(FoldCase(text)); + + auto bound = std::lower_bound(exclusions.begin(), exclusions.end(), foldedText); + + if (bound != exclusions.end() && *bound == foldedText) + { + return !stopOnExclusion; + } + } + + result.emplace_back(std::wstring{ text }); + return true; + }); + + return result; + } + + // Joins all of the given strings into a single value + static std::wstring Join(const std::vector& values, const std::wstring& separator = {}) + { + std::wstring result; + + bool isFirst = true; + for (const auto& v : values) + { + if (isFirst) + { + isFirst = false; + } + else + { + result += separator; + } + + result += v; + } + + return result; + } + + static constexpr Regex::Options reOptions = Regex::Options::CaseInsensitive; + + // Architecture + Regex::Expression ArchitectureX32{ R"((?<=^|[^\p{L}\p{Nd}])(X32|X86)(?=\P{Nd}|$)(?:\sEDITION)?)", reOptions }; + Regex::Expression ArchitectureX64{ R"((?<=^|[^\p{L}\p{Nd}])(X64|AMD64|X86([\p{Pd}\p{Pc}]64))(?=\P{Nd}|$)(?:\sEDITION)?)", reOptions }; + Regex::Expression Architecture32Bit{ R"((?<=^|[^\p{L}\p{Nd}])(32[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions }; + Regex::Expression Architecture64Bit{ R"((?<=^|[^\p{L}\p{Nd}])(64[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions }; + Regex::Expression Architecture32Or64Bit{ R"((?<=^|[^\p{L}\p{Nd}])((64[\\\/]32|32[\\\/]64)[\p{Pd}\p{Pc}\p{Z}]?BIT)S?(?:\sEDITION)?)", reOptions }; + + // Locale + Regex::Expression Locale{ R"((? ProgramNameRegexes + { + &Roblox, + &Bomgar, + &PrefixParens, + &EmptyParens, + &FilePathGHS, + &FilePathParens, + &FilePathQuotes, + &FilePath, + &VersionLetter, + &VersionDelimited, + &Version, + &EN, + &NonNestedBracket, + &BracketEnclosed, + &URIProtocol, + &LeadingSymbols, + &TrailingSymbols + }; + + const std::vector PublisherNameRegexes + { + &VersionDelimited, + &Version, + &NonNestedBracket, + &BracketEnclosed, + &URIProtocol, + &NonLetters, + &TrailingNonLetters, + &AcronymSeparators + }; + + // Add values here but use Locales in code. + const std::vector LocaleViews + { + L"AF-ZA", L"AM-ET", L"AR-AE", L"AR-BH", L"AR-DZ", L"AR-EG", L"AR-IQ", L"AR-JO", L"AR-KW", L"AR-LB", L"AR-LY", + L"AR-MA", L"ARN-CL", L"AR-OM", L"AR-QA", L"AR-SA", L"AR-SY", L"AR-TN", L"AR-YE", L"AS-IN", L"BA-RU", L"BE-BY", + L"BG-BG", L"BN-BD", L"BN-IN", L"BO-CN", L"BR-FR", L"CA-ES", L"CA-ES-VALENCIA", + L"CO-FR", L"CS-CZ", L"CY-GB", L"DA-DK", L"DE-AT", + L"DE-CH", L"DE-DE", L"DE-LI", L"DE-LU", L"DSB-DE", L"DV-MV", L"EL-GR", L"EN-AU", L"EN-BZ", L"EN-CA", L"EN-GB", + L"EN-IE", L"EN-IN", L"EN-JM", L"EN-MY", L"EN-NZ", L"EN-PH", L"EN-SG", L"EN-TT", L"EN-US", L"EN-ZA", L"EN-ZW", + L"ES-AR", L"ES-BO", L"ES-CL", L"ES-CO", L"ES-CR", L"ES-DO", L"ES-EC", L"ES-ES", L"ES-GT", L"ES-HN", L"ES-MX", + L"ES-NI", L"ES-PA", L"ES-PE", L"ES-PR", L"ES-PY", L"ES-SV", L"ES-US", L"ES-UY", L"ES-VE", L"ET-EE", L"EU-ES", + L"FA-IR", L"FI-FI", L"FIL-PH", L"FO-FO", L"FR-BE", L"FR-CA", L"FR-CH", L"FR-FR", L"FR-LU", L"FR-MC", L"FY-NL", + L"GA-IE", L"GD-DB", L"GL-ES", L"GSW-FR", L"GU-IN", L"HE-IL", L"HI-IN", L"HR-BA", L"HR-HR", L"HSB-DE", L"HU-HU", + L"HY-AM", L"ID-ID", L"IG-NG", L"II-CN", L"IS-IS", L"IT-CH", L"IT-IT", L"JA-JP", L"KA-GE", L"KK-KZ", L"KL-GL", + L"KM-KH", L"KN-IN", L"KOK-IN", L"KO-KR", L"KY-KG", L"LB-LU", L"LO-LA", L"LT-LT", L"LV-LV", L"MI-NZ", L"MK-MK", + L"ML-IN", L"MN-MN", L"MOH-CA", L"MR-IN", L"MS-BN", L"MS-MY", L"MT-MT", L"NB-NO", L"NE-NP", L"NL-BE", L"NL-NL", + L"NN-NO", L"NSO-ZA", L"OC-FR", L"OR-IN", L"PA-IN", L"PL-PL", L"PRS-AF", L"PS-AF", L"PT-BR", L"PT-PT", L"QUT-GT", + L"QUZ-BO", L"QUZ-EC", L"QUZ-PE", L"RM-CH", L"RO-RO", L"RU-RU", L"RW-RW", L"SAH-RU", L"SA-IN", L"SE-FI", L"SE-NO", + L"SE-SE", L"SI-LK", L"SK-SK", L"SL-SI", L"SMA-NO", L"SMA-SE", L"SMJ-NO", L"SMJ-SE", L"SMN-FI", L"SMS-FI", L"SQ-AL", + L"SV-FI", L"SV-SE", L"SW-KE", L"SYR-SY", L"TA-IN", L"TE-IN", L"TH-TH", L"TK-TM", L"TN-ZA", L"TR-TR", L"TT-RU", + L"UG-CN", L"UK-UA", L"UR-PK", L"VI-VN", L"WO-SN", L"XH-ZA", L"YO-NG", L"ZH-CN", L"ZH-HK", L"ZH-MO", L"ZH-SG", + L"ZH-TW", L"ZU-ZA", L"AZ-CYRL-AZ", L"AZ-LATN-AZ", L"BS-CYRL-BA", L"BS-LATN-BA", L"HA-LATN-NG", L"IU-CANS-CA", + L"IU-LATN-CA", L"MN-MONG-CN", L"SR-CYRL-BA", L"SR-CYRL-CS", L"SR-CYRL-ME", L"SR-CYRL-RS", L"SR-LATN-BA", + L"SR-LATN-CS", L"SR-LATN-ME", L"SR-LATN-RS", L"TG-CYRL-TJ", L"TZM-LATN-DZ", L"UZ-CYRL-UZ", L"UZ-LATN-UZ", + }; + + // The folded and sorted version of LocaleViews. + const std::vector Locales; + + // Add values here but use LegalEntitySuffixes in code. + const std::vector LegalEntitySuffixViews + { + // Acronyms + L"AB", L"AD", L"AG", L"APS", L"AS", L"ASA", L"BV", L"CO", L"CV", L"DOO", L"eV", L"GES", L"GESMBH", L"GMBH", L"INC", L"KG", + L"KS", L"PS", L"LLC", L"LP", L"LTD", L"LTDA", L"MBH", L"NV", L"PLC", L"SL", L"PTY", L"PVT", L"SA", L"SARL", + L"SC", L"SCA", L"SL", L"SP", L"SPA", L"SRL", L"SRO", + + // Words + L"COMPANY", L"CORP", L"CORPORATION", L"HOLDING", L"HOLDINGS", L"INCORPORATED", L"LIMITED", L"SUBSIDIARY" + }; + + // The folded and sorted version of LocaleViews. + const std::vector LegalEntitySuffixes; + + const bool PreserveWhiteSpace; + + static std::vector FoldAndSort(const std::vector& input) + { + std::vector result; + std::transform(input.begin(), input.end(), std::back_inserter(result), [](const std::wstring_view wsv) { return Utility::ConvertToUTF16(Utility::FoldCase(wsv)); }); + std::sort(result.begin(), result.end()); + return result; + } + + InterimNameNormalizationResult NormalizeNameInternal(std::string_view name) const + { + InterimNameNormalizationResult result; + result.Name = PrepareForValidation(name); + while (Unwrap(result.Name)); // remove wrappers + + // handle (large majority of) SAP Business Object programs + if (SAPPackage.IsMatch(result.Name)) + { + return result; + } + + result.Architecture = RemoveArchitecture(result.Name); + result.Locale = RemoveLocale(result.Name); + + // Extract KB numbers from their parens and preserve them + result.Name = KBNumbers.Replace(result.Name, L"$1"); + + // Repeatedly remove matches for the regexes to create the minimum name + while (RemoveAll(ProgramNameRegexes, result.Name)); + + auto tokens = Split(ProgramNameSplit, result.Name, LegalEntitySuffixes); + + // Re-join the tokens and drop all undesired characters + if (PreserveWhiteSpace) + { + result.Name = Join(tokens, L" "); + Remove(NonLetterDigitOrSpace, result.Name); + } + else + { + result.Name = Join(tokens); + Remove(NonLettersAndDigits, result.Name); + } + + return result; + } + + InterimPublisherNormalizationResult NormalizePublisherInternal(std::string_view publisher) const + { + InterimPublisherNormalizationResult result; + + result.Publisher = PrepareForValidation(publisher); + while (Unwrap(result.Publisher)); // remove wrappers + + while (RemoveAll(PublisherNameRegexes, result.Publisher)); + + auto tokens = Split(PublisherNameSplit, result.Publisher, LegalEntitySuffixes, true); + + // Re-join the tokens and drop all undesired characters + if (PreserveWhiteSpace) + { + result.Publisher = Join(tokens, L" "); + Remove(NonLetterDigitOrSpace, result.Publisher); + } + else + { + result.Publisher = Join(tokens); + Remove(NonLettersAndDigits, result.Publisher); + } + + return result; + } + + public: + NormalizationInitial(bool preserveWhiteSpace) : Locales(FoldAndSort(LocaleViews)), LegalEntitySuffixes(FoldAndSort(LegalEntitySuffixViews)), PreserveWhiteSpace(preserveWhiteSpace) + { + } + + NormalizedName Normalize(std::string_view name, std::string_view publisher) const override + { + InterimNameNormalizationResult nameResult = NormalizeNameInternal(name); + InterimPublisherNormalizationResult pubResult = NormalizePublisherInternal(publisher); + + NormalizedName result; + result.Name(ConvertToUTF8(nameResult.Name)); + result.Architecture(nameResult.Architecture); + result.Locale(ConvertToUTF8(nameResult.Locale)); + result.Publisher(ConvertToUTF8(pubResult.Publisher)); + + return result; + } + + NormalizedName NormalizeName(std::string_view name) const override + { + InterimNameNormalizationResult nameResult = NormalizeNameInternal(name); + + NormalizedName result; + result.Name(ConvertToUTF8(nameResult.Name)); + result.Architecture(nameResult.Architecture); + result.Locale(ConvertToUTF8(nameResult.Locale)); + + return result; + } + + std::string NormalizePublisher(std::string_view publisher) const override + { + InterimPublisherNormalizationResult pubResult = NormalizePublisherInternal(publisher); + + return ConvertToUTF8(pubResult.Publisher); + } + }; + } + + NameNormalizer::NameNormalizer(NormalizationVersion version) + { + switch (version) + { + case AppInstaller::Utility::NormalizationVersion::Initial: + m_normalizer = std::make_unique(false); + break; + case AppInstaller::Utility::NormalizationVersion::InitialPreserveWhiteSpace: + m_normalizer = std::make_unique(true); + break; + default: + THROW_HR(E_INVALIDARG); + } + } + + NormalizedName NameNormalizer::Normalize(std::string_view name, std::string_view publisher) const + { + return m_normalizer->Normalize(name, publisher); + } + + NormalizedName NameNormalizer::NormalizeName(std::string_view name) const + { + return m_normalizer->NormalizeName(name); + } + + std::string NameNormalizer::NormalizePublisher(std::string_view publisher) const + { + return m_normalizer->NormalizePublisher(publisher); + } + + std::string NormalizedName::GetNormalizedName(NormalizationField fieldsToInclude) const + { + std::string result = Name(); + + if (WI_IsFlagSet(fieldsToInclude, NormalizationField::Architecture) && m_arch != Utility::Architecture::Unknown) + { + result += '(' + std::string(Utility::ToString(m_arch)) + ')'; + } + + return result; + } + + NormalizationField NormalizedName::GetNormalizedFields() const + { + NormalizationField result = NormalizationField::None; + + if (m_arch != Utility::Architecture::Unknown) + { + result |= NormalizationField::Architecture; + } + + return result; + } +} diff --git a/src/AppInstallerCommonCore/NetworkSettings.cpp b/src/AppInstallerCommonCore/NetworkSettings.cpp index 2cf8e24b7d..56b2d8d57d 100644 --- a/src/AppInstallerCommonCore/NetworkSettings.cpp +++ b/src/AppInstallerCommonCore/NetworkSettings.cpp @@ -37,8 +37,8 @@ namespace AppInstaller::Settings else if (setting == InstallerDownloader::Default) { return InstallerDownloader::DeliveryOptimization; - } - else + } + else { return setting; } @@ -46,11 +46,11 @@ namespace AppInstaller::Settings NetworkSettings::NetworkSettings() { - // Get the default proxy - try + // Get the default proxy + try { m_proxyUri = GetAdminSetting(StringAdminSetting::DefaultProxy); - } + } CATCH_LOG() AICLI_LOG(Core, Info, << "Default proxy is " << (m_proxyUri ? m_proxyUri.value() : "not set")); } @@ -65,4 +65,4 @@ namespace AppInstaller::Settings { return NetworkSettings::Instance(); } -} +} diff --git a/src/AppInstallerCommonCore/OutputDebugStringLogger.cpp b/src/AppInstallerCommonCore/OutputDebugStringLogger.cpp index 54300ce00a..52f13593da 100644 --- a/src/AppInstallerCommonCore/OutputDebugStringLogger.cpp +++ b/src/AppInstallerCommonCore/OutputDebugStringLogger.cpp @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/OutputDebugStringLogger.h" - -namespace AppInstaller::Logging -{ - namespace - { - static constexpr std::string_view s_OutputDebugStringLoggerName = "OutputDebugStringLogger"; - } - - std::string OutputDebugStringLogger::GetName() const - { - return std::string{ s_OutputDebugStringLoggerName }; - } - - void OutputDebugStringLogger::Write(Channel channel, Level level, std::string_view message) noexcept try - { - std::stringstream strstr; - strstr << "<" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message << std::endl; - std::string formattedMessage = std::move(strstr).str(); - - OutputDebugStringA(formattedMessage.c_str()); - } - catch (...) - { - // Just eat any exceptions here; better than losing logs - } - - void OutputDebugStringLogger::WriteDirect(Channel, Level, std::string_view message) noexcept try - { - std::string nullTerminatedMessage{ message }; - OutputDebugStringA(nullTerminatedMessage.c_str()); - } - catch (...) - { - // Just eat any exceptions here; better than losing logs - } - - void OutputDebugStringLogger::Add() - { - Log().AddLogger(std::make_unique()); - } - - void OutputDebugStringLogger::Remove() - { - Log().RemoveLogger(std::string{ s_OutputDebugStringLoggerName }); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/OutputDebugStringLogger.h" + +namespace AppInstaller::Logging +{ + namespace + { + static constexpr std::string_view s_OutputDebugStringLoggerName = "OutputDebugStringLogger"; + } + + std::string OutputDebugStringLogger::GetName() const + { + return std::string{ s_OutputDebugStringLoggerName }; + } + + void OutputDebugStringLogger::Write(Channel channel, Level level, std::string_view message) noexcept try + { + std::stringstream strstr; + strstr << "<" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message << std::endl; + std::string formattedMessage = std::move(strstr).str(); + + OutputDebugStringA(formattedMessage.c_str()); + } + catch (...) + { + // Just eat any exceptions here; better than losing logs + } + + void OutputDebugStringLogger::WriteDirect(Channel, Level, std::string_view message) noexcept try + { + std::string nullTerminatedMessage{ message }; + OutputDebugStringA(nullTerminatedMessage.c_str()); + } + catch (...) + { + // Just eat any exceptions here; better than losing logs + } + + void OutputDebugStringLogger::Add() + { + Log().AddLogger(std::make_unique()); + } + + void OutputDebugStringLogger::Remove() + { + Log().RemoveLogger(std::string{ s_OutputDebugStringLoggerName }); + } +} diff --git a/src/AppInstallerCommonCore/PackageVersionDataManifest.cpp b/src/AppInstallerCommonCore/PackageVersionDataManifest.cpp index 5792a0f6a7..f086a27b4b 100644 --- a/src/AppInstallerCommonCore/PackageVersionDataManifest.cpp +++ b/src/AppInstallerCommonCore/PackageVersionDataManifest.cpp @@ -1,189 +1,189 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/PackageVersionDataManifest.h" -#include "Public/winget/Yaml.h" -#include -#include -#include - -using namespace std::string_view_literals; - -namespace AppInstaller::Manifest -{ - // These shortened names save some bytes since humans are neither authoring them nor reading them (except to debug). - static constexpr std::string_view s_FieldName_SchemaVersion = "sV"sv; - static constexpr std::string_view s_FieldName_VersionData = "vD"sv; - static constexpr std::string_view s_FieldName_Version = "v"sv; - static constexpr std::string_view s_FieldName_ArpMinVersion = "aMiV"sv; - static constexpr std::string_view s_FieldName_ArpMaxVersion = "aMaV"sv; - static constexpr std::string_view s_FieldName_RelativePath = "rP"sv; - static constexpr std::string_view s_FieldName_Sha256Hash = "s256H"sv; - - static constexpr std::string_view s_SchemaVersion_1_0 = "1.0"sv; - - static constexpr DWORD CompressionAlgorithm = COMPRESS_ALGORITHM_MSZIP; - static constexpr bool CompressionSetLevel1 = false; - - namespace anon - { - std::string GetRequiredChildString(const YAML::Node& node, std::string_view childName) - { - const YAML::Node& childNode = node.GetChildNode(childName); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !childNode.IsScalar()); - return childNode.as(); - } - - std::optional GetOptionalChildString(const YAML::Node& node, std::string_view childName) - { - const YAML::Node& childNode = node.GetChildNode(childName); - return childNode.IsScalar() ? std::make_optional(childNode.as()) : std::nullopt; - } - - void Deserialize_1_0(const YAML::Node& document, PackageVersionDataManifest& manifest) - { - const YAML::Node& versionDataItems = document.GetChildNode(s_FieldName_VersionData); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !versionDataItems.IsSequence()); - - for (const YAML::Node& item : versionDataItems.Sequence()) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !item.IsMap()); - - PackageVersionDataManifest::VersionData versionData; - - versionData.Version.Assign(GetRequiredChildString(item, s_FieldName_Version)); - versionData.ArpMinVersion = GetOptionalChildString(item, s_FieldName_ArpMinVersion); - versionData.ArpMaxVersion = GetOptionalChildString(item, s_FieldName_ArpMaxVersion); - versionData.ManifestRelativePath = GetRequiredChildString(item, s_FieldName_RelativePath); - versionData.ManifestHash = GetRequiredChildString(item, s_FieldName_Sha256Hash); - - manifest.AddVersion(std::move(versionData)); - } - } - } - - std::string_view PackageVersionDataManifest::VersionManifestFileName() - { - return "versionData.yml"sv; - } - - std::string_view PackageVersionDataManifest::VersionManifestCompressedFileName() - { - return "versionData.mszyml"sv; - } - - std::filesystem::path PackageVersionDataManifest::GetRelativeDirectoryPath(std::string_view packageIdentifier, std::string_view manifestHash) - { - std::filesystem::path result = "packages"; - result /= Utility::ConvertToUTF16(packageIdentifier); - result /= manifestHash.substr(0, 8); - return result; - } - - Compression::Compressor PackageVersionDataManifest::CreateCompressor() - { - Compression::Compressor result(CompressionAlgorithm); - if constexpr (CompressionSetLevel1) - { - result.SetInformation(COMPRESS_INFORMATION_CLASS_LEVEL, 1); - } - return result; - } - - Compression::Decompressor PackageVersionDataManifest::CreateDecompressor() - { - return Compression::Decompressor(CompressionAlgorithm); - } - - PackageVersionDataManifest::VersionData::VersionData( - const Utility::VersionAndChannel& versionAndChannel, - std::optional arpMinVersion, - std::optional arpMaxVersion, - std::optional relativePath, - std::optional manifestHash) : - Version(versionAndChannel.GetVersion()), - ArpMinVersion(std::move(arpMinVersion)), - ArpMaxVersion(std::move(arpMaxVersion)), - ManifestRelativePath(std::move(relativePath).value_or("")), - ManifestHash(std::move(manifestHash).value_or("")) - { - if (ArpMinVersion && ArpMinVersion->empty()) - { - ArpMinVersion.reset(); - } - - if (ArpMaxVersion && ArpMaxVersion->empty()) - { - ArpMaxVersion.reset(); - } - } - - void PackageVersionDataManifest::AddVersion(VersionData&& versionData) - { - m_versions.emplace_back(std::move(versionData)); - } - - const std::vector& PackageVersionDataManifest::Versions() const - { - return m_versions; - } - - std::string PackageVersionDataManifest::Serialize() - { - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << s_FieldName_SchemaVersion << YAML::Value << s_SchemaVersion_1_0; - - out << YAML::Key << s_FieldName_VersionData; - out << YAML::BeginSeq; - - for (const auto& version : m_versions) - { - out << YAML::BeginMap; - out << YAML::Key << s_FieldName_Version << YAML::Value << version.Version.ToString(); - if (version.ArpMinVersion) - { - out << YAML::Key << s_FieldName_ArpMinVersion << YAML::Value << version.ArpMinVersion.value(); - } - if (version.ArpMaxVersion) - { - out << YAML::Key << s_FieldName_ArpMaxVersion << YAML::Value << version.ArpMaxVersion.value(); - } - out << YAML::Key << s_FieldName_RelativePath << YAML::Value << version.ManifestRelativePath; - out << YAML::Key << s_FieldName_Sha256Hash << YAML::Value << version.ManifestHash; - out << YAML::EndMap; - } - - out << YAML::EndSeq; - out << YAML::EndMap; - - return out.str(); - } - - void PackageVersionDataManifest::Deserialize(std::string_view input) - { - AICLI_LOG_LARGE_STRING(Core, Verbose, << "PackageVersionDataManifest deserializing:", input); - - YAML::Node document = YAML::Load(input); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !document.IsMap()); - - const YAML::Node& schemaVersionNode = document.GetChildNode(s_FieldName_SchemaVersion); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !schemaVersionNode.IsScalar()); - - Utility::Version schemaVersion{ schemaVersionNode.as() }; - - if (schemaVersion.PartAt(0).Integer == 1) - { - anon::Deserialize_1_0(document, *this); - } - else - { - THROW_HR(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION); - } - } - - void PackageVersionDataManifest::Deserialize(const std::vector& input) - { - Deserialize(std::string_view{ reinterpret_cast(input.data()), input.size() }); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/PackageVersionDataManifest.h" +#include "Public/winget/Yaml.h" +#include +#include +#include + +using namespace std::string_view_literals; + +namespace AppInstaller::Manifest +{ + // These shortened names save some bytes since humans are neither authoring them nor reading them (except to debug). + static constexpr std::string_view s_FieldName_SchemaVersion = "sV"sv; + static constexpr std::string_view s_FieldName_VersionData = "vD"sv; + static constexpr std::string_view s_FieldName_Version = "v"sv; + static constexpr std::string_view s_FieldName_ArpMinVersion = "aMiV"sv; + static constexpr std::string_view s_FieldName_ArpMaxVersion = "aMaV"sv; + static constexpr std::string_view s_FieldName_RelativePath = "rP"sv; + static constexpr std::string_view s_FieldName_Sha256Hash = "s256H"sv; + + static constexpr std::string_view s_SchemaVersion_1_0 = "1.0"sv; + + static constexpr DWORD CompressionAlgorithm = COMPRESS_ALGORITHM_MSZIP; + static constexpr bool CompressionSetLevel1 = false; + + namespace anon + { + std::string GetRequiredChildString(const YAML::Node& node, std::string_view childName) + { + const YAML::Node& childNode = node.GetChildNode(childName); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !childNode.IsScalar()); + return childNode.as(); + } + + std::optional GetOptionalChildString(const YAML::Node& node, std::string_view childName) + { + const YAML::Node& childNode = node.GetChildNode(childName); + return childNode.IsScalar() ? std::make_optional(childNode.as()) : std::nullopt; + } + + void Deserialize_1_0(const YAML::Node& document, PackageVersionDataManifest& manifest) + { + const YAML::Node& versionDataItems = document.GetChildNode(s_FieldName_VersionData); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !versionDataItems.IsSequence()); + + for (const YAML::Node& item : versionDataItems.Sequence()) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !item.IsMap()); + + PackageVersionDataManifest::VersionData versionData; + + versionData.Version.Assign(GetRequiredChildString(item, s_FieldName_Version)); + versionData.ArpMinVersion = GetOptionalChildString(item, s_FieldName_ArpMinVersion); + versionData.ArpMaxVersion = GetOptionalChildString(item, s_FieldName_ArpMaxVersion); + versionData.ManifestRelativePath = GetRequiredChildString(item, s_FieldName_RelativePath); + versionData.ManifestHash = GetRequiredChildString(item, s_FieldName_Sha256Hash); + + manifest.AddVersion(std::move(versionData)); + } + } + } + + std::string_view PackageVersionDataManifest::VersionManifestFileName() + { + return "versionData.yml"sv; + } + + std::string_view PackageVersionDataManifest::VersionManifestCompressedFileName() + { + return "versionData.mszyml"sv; + } + + std::filesystem::path PackageVersionDataManifest::GetRelativeDirectoryPath(std::string_view packageIdentifier, std::string_view manifestHash) + { + std::filesystem::path result = "packages"; + result /= Utility::ConvertToUTF16(packageIdentifier); + result /= manifestHash.substr(0, 8); + return result; + } + + Compression::Compressor PackageVersionDataManifest::CreateCompressor() + { + Compression::Compressor result(CompressionAlgorithm); + if constexpr (CompressionSetLevel1) + { + result.SetInformation(COMPRESS_INFORMATION_CLASS_LEVEL, 1); + } + return result; + } + + Compression::Decompressor PackageVersionDataManifest::CreateDecompressor() + { + return Compression::Decompressor(CompressionAlgorithm); + } + + PackageVersionDataManifest::VersionData::VersionData( + const Utility::VersionAndChannel& versionAndChannel, + std::optional arpMinVersion, + std::optional arpMaxVersion, + std::optional relativePath, + std::optional manifestHash) : + Version(versionAndChannel.GetVersion()), + ArpMinVersion(std::move(arpMinVersion)), + ArpMaxVersion(std::move(arpMaxVersion)), + ManifestRelativePath(std::move(relativePath).value_or("")), + ManifestHash(std::move(manifestHash).value_or("")) + { + if (ArpMinVersion && ArpMinVersion->empty()) + { + ArpMinVersion.reset(); + } + + if (ArpMaxVersion && ArpMaxVersion->empty()) + { + ArpMaxVersion.reset(); + } + } + + void PackageVersionDataManifest::AddVersion(VersionData&& versionData) + { + m_versions.emplace_back(std::move(versionData)); + } + + const std::vector& PackageVersionDataManifest::Versions() const + { + return m_versions; + } + + std::string PackageVersionDataManifest::Serialize() + { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << s_FieldName_SchemaVersion << YAML::Value << s_SchemaVersion_1_0; + + out << YAML::Key << s_FieldName_VersionData; + out << YAML::BeginSeq; + + for (const auto& version : m_versions) + { + out << YAML::BeginMap; + out << YAML::Key << s_FieldName_Version << YAML::Value << version.Version.ToString(); + if (version.ArpMinVersion) + { + out << YAML::Key << s_FieldName_ArpMinVersion << YAML::Value << version.ArpMinVersion.value(); + } + if (version.ArpMaxVersion) + { + out << YAML::Key << s_FieldName_ArpMaxVersion << YAML::Value << version.ArpMaxVersion.value(); + } + out << YAML::Key << s_FieldName_RelativePath << YAML::Value << version.ManifestRelativePath; + out << YAML::Key << s_FieldName_Sha256Hash << YAML::Value << version.ManifestHash; + out << YAML::EndMap; + } + + out << YAML::EndSeq; + out << YAML::EndMap; + + return out.str(); + } + + void PackageVersionDataManifest::Deserialize(std::string_view input) + { + AICLI_LOG_LARGE_STRING(Core, Verbose, << "PackageVersionDataManifest deserializing:", input); + + YAML::Node document = YAML::Load(input); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !document.IsMap()); + + const YAML::Node& schemaVersionNode = document.GetChildNode(s_FieldName_SchemaVersion); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, !schemaVersionNode.IsScalar()); + + Utility::Version schemaVersion{ schemaVersionNode.as() }; + + if (schemaVersion.PartAt(0).Integer == 1) + { + anon::Deserialize_1_0(document, *this); + } + else + { + THROW_HR(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION); + } + } + + void PackageVersionDataManifest::Deserialize(const std::vector& input) + { + Deserialize(std::string_view{ reinterpret_cast(input.data()), input.size() }); + } +} diff --git a/src/AppInstallerCommonCore/PathVariable.cpp b/src/AppInstallerCommonCore/PathVariable.cpp index c8a58bb804..88716ce64f 100644 --- a/src/AppInstallerCommonCore/PathVariable.cpp +++ b/src/AppInstallerCommonCore/PathVariable.cpp @@ -1,136 +1,136 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/PathVariable.h" -#include - -using namespace AppInstaller::Utility; - -namespace AppInstaller::Registry::Environment -{ - namespace - { - constexpr std::wstring_view s_PathName = L"Path"; - constexpr std::wstring_view s_PathSubkey_User = L"Environment"; - constexpr std::wstring_view s_PathSubkey_Machine = L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; - - void EnsurePathValueEndsWithSemicolon(std::string& value) - { - if (value.back() != ';') - { - value += ';'; - } - } - - std::string ExpandPathValue(const std::string& value) - { - std::string result; - std::vector pathEntries = Split(value, ';'); - for (std::string& pathEntry : pathEntries) - { - if (!pathEntry.empty()) - { - result += AppInstaller::Filesystem::GetExpandedPath(pathEntry).u8string(); - result += ';'; - } - } - return result; - } - } - - PathVariable::PathVariable(Manifest::ScopeEnum scope, bool readOnly) : m_scope(scope), m_readOnly(readOnly) - { - if (m_readOnly) - { - if (m_scope == Manifest::ScopeEnum::Machine) - { - m_key = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, std::wstring{ s_PathSubkey_Machine }); - } - else - { - m_key = Registry::Key::OpenIfExists(HKEY_CURRENT_USER, std::wstring{ s_PathSubkey_User }); - } - } - else - { - if (m_scope == Manifest::ScopeEnum::Machine) - { - m_key = Registry::Key::Create(HKEY_LOCAL_MACHINE, std::wstring{ s_PathSubkey_Machine }); - } - else - { - m_key = Registry::Key::Create(HKEY_CURRENT_USER, std::wstring{ s_PathSubkey_User }); - } - } - } - - std::string PathVariable::GetPathValue() - { - std::wstring pathName = std::wstring{ s_PathName }; - return Normalize(m_key[pathName]->GetValue()); - } - - bool PathVariable::Contains(const std::filesystem::path& target) - { - std::string targetString = Normalize(target.u8string()); - return (GetPathValue().find(targetString) != std::string::npos); - } - - bool PathVariable::Remove(const std::filesystem::path& target) - { - THROW_HR_IF(E_ACCESSDENIED, m_readOnly); - - if (Contains(target)) - { - std::string targetString = Normalize(target.u8string()); - std::string pathValue = GetPathValue(); - FindAndReplace(pathValue, targetString, ""); - FindAndReplace(pathValue, ";;", ";"); - SetPathValue(pathValue); - return true; - } - else - { - return false; - } - } - - bool PathVariable::Append(const std::filesystem::path& target) - { - THROW_HR_IF(E_ACCESSDENIED, m_readOnly); - - if (!Contains(target)) - { - std::string targetString = Normalize(target.u8string()); - std::string pathValue = GetPathValue(); - EnsurePathValueEndsWithSemicolon(pathValue); - pathValue += targetString; - EnsurePathValueEndsWithSemicolon(pathValue); - SetPathValue(pathValue); - return true; - } - else - { - return false; - } - } - - void PathVariable::SetPathValue(const std::string& value) - { - THROW_HR_IF(E_ACCESSDENIED, m_readOnly); - - std::wstring pathName = std::wstring{ s_PathName }; - m_key.SetValue(pathName, ConvertToUTF16(value), REG_EXPAND_SZ); - SendNotifyMessageW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)TEXT("Environment")); - - } - - bool RefreshPathVariableForCurrentProcess() - { - // Path values must be expanded before assigning to process environment for proper refresh. - std::string systemPathValue = ExpandPathValue(PathVariable(Manifest::ScopeEnum::Machine, true).GetPathValue()); - std::string userPathValue = ExpandPathValue(PathVariable(Manifest::ScopeEnum::User, true).GetPathValue()); - std::wstring pathValue = ConvertToUTF16(systemPathValue + userPathValue); - return _wputenv_s(L"PATH", pathValue.c_str()) == 0; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/PathVariable.h" +#include + +using namespace AppInstaller::Utility; + +namespace AppInstaller::Registry::Environment +{ + namespace + { + constexpr std::wstring_view s_PathName = L"Path"; + constexpr std::wstring_view s_PathSubkey_User = L"Environment"; + constexpr std::wstring_view s_PathSubkey_Machine = L"SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Environment"; + + void EnsurePathValueEndsWithSemicolon(std::string& value) + { + if (value.back() != ';') + { + value += ';'; + } + } + + std::string ExpandPathValue(const std::string& value) + { + std::string result; + std::vector pathEntries = Split(value, ';'); + for (std::string& pathEntry : pathEntries) + { + if (!pathEntry.empty()) + { + result += AppInstaller::Filesystem::GetExpandedPath(pathEntry).u8string(); + result += ';'; + } + } + return result; + } + } + + PathVariable::PathVariable(Manifest::ScopeEnum scope, bool readOnly) : m_scope(scope), m_readOnly(readOnly) + { + if (m_readOnly) + { + if (m_scope == Manifest::ScopeEnum::Machine) + { + m_key = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, std::wstring{ s_PathSubkey_Machine }); + } + else + { + m_key = Registry::Key::OpenIfExists(HKEY_CURRENT_USER, std::wstring{ s_PathSubkey_User }); + } + } + else + { + if (m_scope == Manifest::ScopeEnum::Machine) + { + m_key = Registry::Key::Create(HKEY_LOCAL_MACHINE, std::wstring{ s_PathSubkey_Machine }); + } + else + { + m_key = Registry::Key::Create(HKEY_CURRENT_USER, std::wstring{ s_PathSubkey_User }); + } + } + } + + std::string PathVariable::GetPathValue() + { + std::wstring pathName = std::wstring{ s_PathName }; + return Normalize(m_key[pathName]->GetValue()); + } + + bool PathVariable::Contains(const std::filesystem::path& target) + { + std::string targetString = Normalize(target.u8string()); + return (GetPathValue().find(targetString) != std::string::npos); + } + + bool PathVariable::Remove(const std::filesystem::path& target) + { + THROW_HR_IF(E_ACCESSDENIED, m_readOnly); + + if (Contains(target)) + { + std::string targetString = Normalize(target.u8string()); + std::string pathValue = GetPathValue(); + FindAndReplace(pathValue, targetString, ""); + FindAndReplace(pathValue, ";;", ";"); + SetPathValue(pathValue); + return true; + } + else + { + return false; + } + } + + bool PathVariable::Append(const std::filesystem::path& target) + { + THROW_HR_IF(E_ACCESSDENIED, m_readOnly); + + if (!Contains(target)) + { + std::string targetString = Normalize(target.u8string()); + std::string pathValue = GetPathValue(); + EnsurePathValueEndsWithSemicolon(pathValue); + pathValue += targetString; + EnsurePathValueEndsWithSemicolon(pathValue); + SetPathValue(pathValue); + return true; + } + else + { + return false; + } + } + + void PathVariable::SetPathValue(const std::string& value) + { + THROW_HR_IF(E_ACCESSDENIED, m_readOnly); + + std::wstring pathName = std::wstring{ s_PathName }; + m_key.SetValue(pathName, ConvertToUTF16(value), REG_EXPAND_SZ); + SendNotifyMessageW(HWND_BROADCAST, WM_SETTINGCHANGE, 0, (LPARAM)TEXT("Environment")); + + } + + bool RefreshPathVariableForCurrentProcess() + { + // Path values must be expanded before assigning to process environment for proper refresh. + std::string systemPathValue = ExpandPathValue(PathVariable(Manifest::ScopeEnum::Machine, true).GetPathValue()); + std::string userPathValue = ExpandPathValue(PathVariable(Manifest::ScopeEnum::User, true).GetPathValue()); + std::wstring pathValue = ConvertToUTF16(systemPathValue + userPathValue); + return _wputenv_s(L"PATH", pathValue.c_str()) == 0; + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Pin.cpp b/src/AppInstallerCommonCore/Pin.cpp index 717040b194..b3f76d5de2 100644 --- a/src/AppInstallerCommonCore/Pin.cpp +++ b/src/AppInstallerCommonCore/Pin.cpp @@ -1,123 +1,123 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Pin.h" - -using namespace AppInstaller::Utility; - -namespace AppInstaller::Pinning -{ - using namespace std::string_view_literals; - - namespace - { - // Source ID to use for the installed source; it does not match any installed source. - // This does match with the actual ID of the source, but it doesn't really matter - // as it is handled specially when we see it. - constexpr std::string_view s_installedSourceId = "*PredefinedInstalledSource"sv; - } - - PinType ConvertToPinTypeEnum(std::string_view in) - { - if (Utility::CaseInsensitiveEquals(in, "Blocking"sv)) - { - return PinType::Blocking; - } - else if (Utility::CaseInsensitiveEquals(in, "Pinning"sv)) - { - return PinType::Pinning; - } - else if (Utility::CaseInsensitiveEquals(in, "Gating"sv)) - { - return PinType::Gating; - } - else if (Utility::CaseInsensitiveEquals(in, "PinnedByManifest"sv)) - { - return PinType::PinnedByManifest; - } - else - { - return PinType::Unknown; - } - } - - std::string_view ToString(PinType type) - { - switch (type) - { - case PinType::Blocking: - return "Blocking"sv; - case PinType::Pinning: - return "Pinning"sv; - case PinType::Gating: - return "Gating"sv; - case PinType::PinnedByManifest: - return "PinnedByManifest"sv; - case PinType::Unknown: - default: - return "Unknown"; - } - } - - bool IsStricter(PinType first, PinType second) - { - return first > second; - } - - PinType Stricter(PinType first, PinType second) - { - return IsStricter(first, second) ? first : second; - } - - std::string PinKey::ToString() const - { - std::stringstream ss; - ss << "Package=[" << PackageId << "] Source=[" << SourceId << "]"; - return ss.str(); - } - - PinKey PinKey::GetPinKeyForInstalled(std::string_view systemReferenceString) - { - return { systemReferenceString, s_installedSourceId }; - } - - bool PinKey::IsForInstalled() const - { - return SourceId == s_installedSourceId; - } - - std::string Pin::ToString() const - { - std::stringstream ss; - ss << m_key.ToString() << " Type=[" << Pinning::ToString(m_type) << ']'; - - if (m_type == PinType::Gating) - { - ss << " GatedVersion=[" << m_gatedVersion.ToString() << ']'; - } - - return ss.str(); - } - - Pin Pin::CreateBlockingPin(PinKey&& pinKey) - { - return { PinType::Blocking, std::move(pinKey) }; - } - - Pin Pin::CreatePinningPin(PinKey&& pinKey) - { - return { PinType::Pinning, std::move(pinKey) }; - } - - Pin Pin::CreateGatingPin(PinKey&& pinKey, GatedVersion&& gatedVersion) - { - return { PinType::Gating, std::move(pinKey), std::move(gatedVersion) }; - } - - bool Pin::operator==(const Pin& other) const - { - return m_type == other.m_type && - m_key == other.m_key && - m_gatedVersion == other.m_gatedVersion; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Pin.h" + +using namespace AppInstaller::Utility; + +namespace AppInstaller::Pinning +{ + using namespace std::string_view_literals; + + namespace + { + // Source ID to use for the installed source; it does not match any installed source. + // This does match with the actual ID of the source, but it doesn't really matter + // as it is handled specially when we see it. + constexpr std::string_view s_installedSourceId = "*PredefinedInstalledSource"sv; + } + + PinType ConvertToPinTypeEnum(std::string_view in) + { + if (Utility::CaseInsensitiveEquals(in, "Blocking"sv)) + { + return PinType::Blocking; + } + else if (Utility::CaseInsensitiveEquals(in, "Pinning"sv)) + { + return PinType::Pinning; + } + else if (Utility::CaseInsensitiveEquals(in, "Gating"sv)) + { + return PinType::Gating; + } + else if (Utility::CaseInsensitiveEquals(in, "PinnedByManifest"sv)) + { + return PinType::PinnedByManifest; + } + else + { + return PinType::Unknown; + } + } + + std::string_view ToString(PinType type) + { + switch (type) + { + case PinType::Blocking: + return "Blocking"sv; + case PinType::Pinning: + return "Pinning"sv; + case PinType::Gating: + return "Gating"sv; + case PinType::PinnedByManifest: + return "PinnedByManifest"sv; + case PinType::Unknown: + default: + return "Unknown"; + } + } + + bool IsStricter(PinType first, PinType second) + { + return first > second; + } + + PinType Stricter(PinType first, PinType second) + { + return IsStricter(first, second) ? first : second; + } + + std::string PinKey::ToString() const + { + std::stringstream ss; + ss << "Package=[" << PackageId << "] Source=[" << SourceId << "]"; + return ss.str(); + } + + PinKey PinKey::GetPinKeyForInstalled(std::string_view systemReferenceString) + { + return { systemReferenceString, s_installedSourceId }; + } + + bool PinKey::IsForInstalled() const + { + return SourceId == s_installedSourceId; + } + + std::string Pin::ToString() const + { + std::stringstream ss; + ss << m_key.ToString() << " Type=[" << Pinning::ToString(m_type) << ']'; + + if (m_type == PinType::Gating) + { + ss << " GatedVersion=[" << m_gatedVersion.ToString() << ']'; + } + + return ss.str(); + } + + Pin Pin::CreateBlockingPin(PinKey&& pinKey) + { + return { PinType::Blocking, std::move(pinKey) }; + } + + Pin Pin::CreatePinningPin(PinKey&& pinKey) + { + return { PinType::Pinning, std::move(pinKey) }; + } + + Pin Pin::CreateGatingPin(PinKey&& pinKey, GatedVersion&& gatedVersion) + { + return { PinType::Gating, std::move(pinKey), std::move(gatedVersion) }; + } + + bool Pin::operator==(const Pin& other) const + { + return m_type == other.m_type && + m_key == other.m_key && + m_gatedVersion == other.m_gatedVersion; + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/PortableARPEntry.cpp b/src/AppInstallerCommonCore/PortableARPEntry.cpp index 084341067a..06230b0a3b 100644 --- a/src/AppInstallerCommonCore/PortableARPEntry.cpp +++ b/src/AppInstallerCommonCore/PortableARPEntry.cpp @@ -1,124 +1,124 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/PortableARPEntry.h" -#include "winget/Manifest.h" - -using namespace AppInstaller::Utility; - -#define VALUENAMECASE(valueName) case PortableValueName::valueName: return s_##valueName; - -namespace AppInstaller::Registry::Portable -{ - namespace - { - constexpr std::wstring_view s_UninstallRegistryX64 = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; - constexpr std::wstring_view s_UninstallRegistryX86 = L"Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; - constexpr std::wstring_view s_DisplayName = L"DisplayName"; - constexpr std::wstring_view s_DisplayVersion = L"DisplayVersion"; - constexpr std::wstring_view s_Publisher = L"Publisher"; - constexpr std::wstring_view s_InstallDate = L"InstallDate"; - constexpr std::wstring_view s_URLInfoAbout = L"URLInfoAbout"; - constexpr std::wstring_view s_HelpLink = L"HelpLink"; - constexpr std::wstring_view s_UninstallString = L"UninstallString"; - constexpr std::wstring_view s_WinGetInstallerType = L"WinGetInstallerType"; - constexpr std::wstring_view s_InstallLocation = L"InstallLocation"; - constexpr std::wstring_view s_PortableTargetFullPath = L"TargetFullPath"; - constexpr std::wstring_view s_PortableSymlinkFullPath = L"SymlinkFullPath"; - constexpr std::wstring_view s_SHA256 = L"SHA256"; - constexpr std::wstring_view s_WinGetPackageIdentifier = L"WinGetPackageIdentifier"; - constexpr std::wstring_view s_WinGetSourceIdentifier = L"WinGetSourceIdentifier"; - constexpr std::wstring_view s_InstallDirectoryCreated = L"InstallDirectoryCreated"; - constexpr std::wstring_view s_InstallDirectoryAddedToPath = L"InstallDirectoryAddedToPath"; - } - - PortableARPEntry::PortableARPEntry(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode) - { - m_scope = scope; - m_arch = arch; - m_productCode = productCode; - - if (m_scope == Manifest::ScopeEnum::Machine) - { - m_root = HKEY_LOCAL_MACHINE; - if (m_arch == Utility::Architecture::X64) - { - m_subKey = s_UninstallRegistryX64; - m_samDesired = KEY_WOW64_64KEY; - } - else - { - m_subKey = s_UninstallRegistryX86; - m_samDesired = KEY_WOW64_32KEY; - } - } - else - { - // HKCU uninstall registry share the x64 registry view. - m_root = HKEY_CURRENT_USER; - m_subKey = s_UninstallRegistryX64; - m_samDesired = KEY_WOW64_64KEY; - } - - m_subKey += L"\\" + ConvertToUTF16(m_productCode); - m_key = Key::OpenIfExists(m_root, m_subKey, 0, KEY_ALL_ACCESS); - if (m_key != NULL) - { - m_exists = true; - } - else - { - m_exists = false; - m_key = Key::Create(m_root, m_subKey); - } - } - - std::wstring_view ToString(PortableValueName valueName) - { - switch (valueName) - { - VALUENAMECASE(DisplayName); - VALUENAMECASE(DisplayVersion); - VALUENAMECASE(Publisher); - VALUENAMECASE(InstallDate); - VALUENAMECASE(URLInfoAbout); - VALUENAMECASE(HelpLink); - VALUENAMECASE(UninstallString); - VALUENAMECASE(WinGetInstallerType); - VALUENAMECASE(InstallLocation); - VALUENAMECASE(PortableTargetFullPath); - VALUENAMECASE(PortableSymlinkFullPath); - VALUENAMECASE(SHA256); - VALUENAMECASE(WinGetPackageIdentifier); - VALUENAMECASE(WinGetSourceIdentifier); - VALUENAMECASE(InstallDirectoryCreated); - VALUENAMECASE(InstallDirectoryAddedToPath); - default: return {}; - } - } - - std::optional PortableARPEntry::operator[](PortableValueName valueName) const - { - return m_key[std::wstring{ ToString(valueName) }]; - } - - void PortableARPEntry::SetValue(PortableValueName valueName, const std::wstring& value) - { - m_key.SetValue(std::wstring{ ToString(valueName) }, value, REG_SZ); - } - - void PortableARPEntry::SetValue(PortableValueName valueName, const std::string_view& value) - { - m_key.SetValue(std::wstring{ ToString(valueName) }, ConvertToUTF16(value), REG_SZ); - } - - void PortableARPEntry::SetValue(PortableValueName valueName, bool& value) - { - m_key.SetValue(std::wstring{ ToString(valueName) }, value); - } - - void PortableARPEntry::Delete() - { - Registry::Key::Delete(m_root, m_subKey, m_samDesired); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/PortableARPEntry.h" +#include "winget/Manifest.h" + +using namespace AppInstaller::Utility; + +#define VALUENAMECASE(valueName) case PortableValueName::valueName: return s_##valueName; + +namespace AppInstaller::Registry::Portable +{ + namespace + { + constexpr std::wstring_view s_UninstallRegistryX64 = L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; + constexpr std::wstring_view s_UninstallRegistryX86 = L"Software\\Wow6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall"; + constexpr std::wstring_view s_DisplayName = L"DisplayName"; + constexpr std::wstring_view s_DisplayVersion = L"DisplayVersion"; + constexpr std::wstring_view s_Publisher = L"Publisher"; + constexpr std::wstring_view s_InstallDate = L"InstallDate"; + constexpr std::wstring_view s_URLInfoAbout = L"URLInfoAbout"; + constexpr std::wstring_view s_HelpLink = L"HelpLink"; + constexpr std::wstring_view s_UninstallString = L"UninstallString"; + constexpr std::wstring_view s_WinGetInstallerType = L"WinGetInstallerType"; + constexpr std::wstring_view s_InstallLocation = L"InstallLocation"; + constexpr std::wstring_view s_PortableTargetFullPath = L"TargetFullPath"; + constexpr std::wstring_view s_PortableSymlinkFullPath = L"SymlinkFullPath"; + constexpr std::wstring_view s_SHA256 = L"SHA256"; + constexpr std::wstring_view s_WinGetPackageIdentifier = L"WinGetPackageIdentifier"; + constexpr std::wstring_view s_WinGetSourceIdentifier = L"WinGetSourceIdentifier"; + constexpr std::wstring_view s_InstallDirectoryCreated = L"InstallDirectoryCreated"; + constexpr std::wstring_view s_InstallDirectoryAddedToPath = L"InstallDirectoryAddedToPath"; + } + + PortableARPEntry::PortableARPEntry(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode) + { + m_scope = scope; + m_arch = arch; + m_productCode = productCode; + + if (m_scope == Manifest::ScopeEnum::Machine) + { + m_root = HKEY_LOCAL_MACHINE; + if (m_arch == Utility::Architecture::X64) + { + m_subKey = s_UninstallRegistryX64; + m_samDesired = KEY_WOW64_64KEY; + } + else + { + m_subKey = s_UninstallRegistryX86; + m_samDesired = KEY_WOW64_32KEY; + } + } + else + { + // HKCU uninstall registry share the x64 registry view. + m_root = HKEY_CURRENT_USER; + m_subKey = s_UninstallRegistryX64; + m_samDesired = KEY_WOW64_64KEY; + } + + m_subKey += L"\\" + ConvertToUTF16(m_productCode); + m_key = Key::OpenIfExists(m_root, m_subKey, 0, KEY_ALL_ACCESS); + if (m_key != NULL) + { + m_exists = true; + } + else + { + m_exists = false; + m_key = Key::Create(m_root, m_subKey); + } + } + + std::wstring_view ToString(PortableValueName valueName) + { + switch (valueName) + { + VALUENAMECASE(DisplayName); + VALUENAMECASE(DisplayVersion); + VALUENAMECASE(Publisher); + VALUENAMECASE(InstallDate); + VALUENAMECASE(URLInfoAbout); + VALUENAMECASE(HelpLink); + VALUENAMECASE(UninstallString); + VALUENAMECASE(WinGetInstallerType); + VALUENAMECASE(InstallLocation); + VALUENAMECASE(PortableTargetFullPath); + VALUENAMECASE(PortableSymlinkFullPath); + VALUENAMECASE(SHA256); + VALUENAMECASE(WinGetPackageIdentifier); + VALUENAMECASE(WinGetSourceIdentifier); + VALUENAMECASE(InstallDirectoryCreated); + VALUENAMECASE(InstallDirectoryAddedToPath); + default: return {}; + } + } + + std::optional PortableARPEntry::operator[](PortableValueName valueName) const + { + return m_key[std::wstring{ ToString(valueName) }]; + } + + void PortableARPEntry::SetValue(PortableValueName valueName, const std::wstring& value) + { + m_key.SetValue(std::wstring{ ToString(valueName) }, value, REG_SZ); + } + + void PortableARPEntry::SetValue(PortableValueName valueName, const std::string_view& value) + { + m_key.SetValue(std::wstring{ ToString(valueName) }, ConvertToUTF16(value), REG_SZ); + } + + void PortableARPEntry::SetValue(PortableValueName valueName, bool& value) + { + m_key.SetValue(std::wstring{ ToString(valueName) }, value); + } + + void PortableARPEntry::Delete() + { + Registry::Key::Delete(m_root, m_subKey, m_samDesired); + } } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Progress.cpp b/src/AppInstallerCommonCore/Progress.cpp index b9860c159f..5057978c8f 100644 --- a/src/AppInstallerCommonCore/Progress.cpp +++ b/src/AppInstallerCommonCore/Progress.cpp @@ -1,155 +1,155 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerProgress.h" -#include - -namespace AppInstaller -{ - HRESULT ToHRESULT(CancelReason reason) - { - HRESULT hr = E_ABORT; - - switch (reason) - { - case CancelReason::CtrlCSignal: - hr = APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED; - break; - case CancelReason::AppShutdown: - hr = APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED; - break; - } - - return hr; - } - - ProgressCallback::ProgressCallback(IProgressSink* sink) : m_sink(sink) - { - } - - void ProgressCallback::BeginProgress() - { - IProgressSink* sink = GetSink(); - if (sink) - { - sink->BeginProgress(); - } - } - - void ProgressCallback::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) - { - IProgressSink* sink = GetSink(); - if (sink) - { - sink->OnProgress(current, maximum, type); - } - } - - void ProgressCallback::SetProgressMessage(std::string_view message) - { - IProgressSink* sink = GetSink(); - if (sink) - { - sink->SetProgressMessage(message); - } - } - - void ProgressCallback::EndProgress(bool hideProgressWhenDone) - { - IProgressSink* sink = GetSink(); - if (sink) - { - sink->EndProgress(hideProgressWhenDone); - } - }; - - bool ProgressCallback::IsCancelledBy(CancelReason cancelReasons) - { - THROW_HR_IF(E_UNEXPECTED, cancelReasons == CancelReason::None); - return WI_IsAnyFlagSet(cancelReasons, m_cancelReason); - } - - [[nodiscard]] IProgressCallback::CancelFunctionRemoval ProgressCallback::SetCancellationFunction(std::function&& f) - { - m_cancellationFunction = std::move(f); - if (m_cancellationFunction) - { - return IProgressCallback::CancelFunctionRemoval(this); - } - else - { - return {}; - } - } - - bool ProgressCallback::Wait(IProgressCallback& progress, std::chrono::milliseconds millisecondsToWait) - { - wil::unique_event calledEvent; - calledEvent.create(); - - auto cancellationFunc = progress.SetCancellationFunction([&calledEvent]() - { - calledEvent.SetEvent(); - }); - - if (calledEvent.wait(static_cast(millisecondsToWait.count()))) - { - return false; - } - - return true; - } - - void ProgressCallback::Cancel(CancelReason reason) - { - m_cancelReason = reason; - if (m_cancellationFunction) - { - m_cancellationFunction(); - } - } - - IProgressSink* ProgressCallback::GetSink() - { - return m_sink.load(); - } - - PartialPercentProgressCallback::PartialPercentProgressCallback(IProgressCallback& baseCallback, uint64_t globalMax) : - m_baseCallback(baseCallback), m_globalMax(globalMax) - { - } - - void PartialPercentProgressCallback::BeginProgress() - { - THROW_HR(E_NOTIMPL); - } - - void PartialPercentProgressCallback::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) - { - THROW_HR_IF(E_UNEXPECTED, ProgressType::Percent != type); - - m_baseCallback.OnProgress(m_rangeMin + (m_rangeMax - m_rangeMin) * current / maximum, m_globalMax, type); - } - - void PartialPercentProgressCallback::SetProgressMessage(std::string_view message) - { - m_baseCallback.SetProgressMessage(message); - } - - void PartialPercentProgressCallback::EndProgress(bool) - { - THROW_HR(E_NOTIMPL); - } - - IProgressCallback::CancelFunctionRemoval PartialPercentProgressCallback::SetCancellationFunction(std::function&& f) - { - return m_baseCallback.SetCancellationFunction(std::move(f)); - } - - void PartialPercentProgressCallback::SetRange(uint64_t rangeMin, uint64_t rangeMax) - { - THROW_HR_IF(E_INVALIDARG, rangeMin > rangeMax || rangeMax > m_globalMax); - m_rangeMin = rangeMin; - m_rangeMax = rangeMax; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerProgress.h" +#include + +namespace AppInstaller +{ + HRESULT ToHRESULT(CancelReason reason) + { + HRESULT hr = E_ABORT; + + switch (reason) + { + case CancelReason::CtrlCSignal: + hr = APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED; + break; + case CancelReason::AppShutdown: + hr = APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED; + break; + } + + return hr; + } + + ProgressCallback::ProgressCallback(IProgressSink* sink) : m_sink(sink) + { + } + + void ProgressCallback::BeginProgress() + { + IProgressSink* sink = GetSink(); + if (sink) + { + sink->BeginProgress(); + } + } + + void ProgressCallback::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) + { + IProgressSink* sink = GetSink(); + if (sink) + { + sink->OnProgress(current, maximum, type); + } + } + + void ProgressCallback::SetProgressMessage(std::string_view message) + { + IProgressSink* sink = GetSink(); + if (sink) + { + sink->SetProgressMessage(message); + } + } + + void ProgressCallback::EndProgress(bool hideProgressWhenDone) + { + IProgressSink* sink = GetSink(); + if (sink) + { + sink->EndProgress(hideProgressWhenDone); + } + }; + + bool ProgressCallback::IsCancelledBy(CancelReason cancelReasons) + { + THROW_HR_IF(E_UNEXPECTED, cancelReasons == CancelReason::None); + return WI_IsAnyFlagSet(cancelReasons, m_cancelReason); + } + + [[nodiscard]] IProgressCallback::CancelFunctionRemoval ProgressCallback::SetCancellationFunction(std::function&& f) + { + m_cancellationFunction = std::move(f); + if (m_cancellationFunction) + { + return IProgressCallback::CancelFunctionRemoval(this); + } + else + { + return {}; + } + } + + bool ProgressCallback::Wait(IProgressCallback& progress, std::chrono::milliseconds millisecondsToWait) + { + wil::unique_event calledEvent; + calledEvent.create(); + + auto cancellationFunc = progress.SetCancellationFunction([&calledEvent]() + { + calledEvent.SetEvent(); + }); + + if (calledEvent.wait(static_cast(millisecondsToWait.count()))) + { + return false; + } + + return true; + } + + void ProgressCallback::Cancel(CancelReason reason) + { + m_cancelReason = reason; + if (m_cancellationFunction) + { + m_cancellationFunction(); + } + } + + IProgressSink* ProgressCallback::GetSink() + { + return m_sink.load(); + } + + PartialPercentProgressCallback::PartialPercentProgressCallback(IProgressCallback& baseCallback, uint64_t globalMax) : + m_baseCallback(baseCallback), m_globalMax(globalMax) + { + } + + void PartialPercentProgressCallback::BeginProgress() + { + THROW_HR(E_NOTIMPL); + } + + void PartialPercentProgressCallback::OnProgress(uint64_t current, uint64_t maximum, ProgressType type) + { + THROW_HR_IF(E_UNEXPECTED, ProgressType::Percent != type); + + m_baseCallback.OnProgress(m_rangeMin + (m_rangeMax - m_rangeMin) * current / maximum, m_globalMax, type); + } + + void PartialPercentProgressCallback::SetProgressMessage(std::string_view message) + { + m_baseCallback.SetProgressMessage(message); + } + + void PartialPercentProgressCallback::EndProgress(bool) + { + THROW_HR(E_NOTIMPL); + } + + IProgressCallback::CancelFunctionRemoval PartialPercentProgressCallback::SetCancellationFunction(std::function&& f) + { + return m_baseCallback.SetCancellationFunction(std::move(f)); + } + + void PartialPercentProgressCallback::SetRange(uint64_t rangeMin, uint64_t rangeMax) + { + THROW_HR_IF(E_INVALIDARG, rangeMin > rangeMax || rangeMax > m_globalMax); + m_rangeMin = rangeMin; + m_rangeMax = rangeMax; + } +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h b/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h index 17f3fac897..8b12528eb5 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerArchitecture.h @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller::Utility -{ - static const int InapplicableArchitecture = -1; - - enum class Architecture - { - Unknown = -1, - Neutral, - X86, - X64, - Arm, - Arm64 - }; - - // Converts a string to corresponding enum - Architecture ConvertToArchitectureEnum(std::string_view archStr); - - // Converts an ProcessorArchitecture to an Architecture - std::optional ConvertToArchitectureEnum(winrt::Windows::System::ProcessorArchitecture architecture); - - // Converts an Architecture to a string_view - LocIndView ToString(Architecture architecture); - - // Gets the system's architecture as Architecture enum - AppInstaller::Utility::Architecture GetSystemArchitecture(); - - // Gets the set of architectures that are applicable to the current system - const std::vector& GetApplicableArchitectures(); - - // Gets the set of architectures that are supported by winget - const std::vector& GetAllArchitectures(); - - // Gets if an architecture is applicable to the system - // Returns the priority in the applicable architecture list if the architecture is applicable. 0 has lowest priority. - // Returns -1 if the architecture is not applicable. - int IsApplicableArchitecture(Architecture arch); - - // Gets if an architecture is applicable to the given list - // Returns the priority in the applicable architecture list if the architecture is applicable. 0 has lowest priority. - // Returns -1 if the architecture is not applicable. - int IsApplicableArchitecture(Architecture arch, const std::vector& allowedArchitectures); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller::Utility +{ + static const int InapplicableArchitecture = -1; + + enum class Architecture + { + Unknown = -1, + Neutral, + X86, + X64, + Arm, + Arm64 + }; + + // Converts a string to corresponding enum + Architecture ConvertToArchitectureEnum(std::string_view archStr); + + // Converts an ProcessorArchitecture to an Architecture + std::optional ConvertToArchitectureEnum(winrt::Windows::System::ProcessorArchitecture architecture); + + // Converts an Architecture to a string_view + LocIndView ToString(Architecture architecture); + + // Gets the system's architecture as Architecture enum + AppInstaller::Utility::Architecture GetSystemArchitecture(); + + // Gets the set of architectures that are applicable to the current system + const std::vector& GetApplicableArchitectures(); + + // Gets the set of architectures that are supported by winget + const std::vector& GetAllArchitectures(); + + // Gets if an architecture is applicable to the system + // Returns the priority in the applicable architecture list if the architecture is applicable. 0 has lowest priority. + // Returns -1 if the architecture is not applicable. + int IsApplicableArchitecture(Architecture arch); + + // Gets if an architecture is applicable to the given list + // Returns the priority in the applicable architecture list if the architecture is applicable. 0 has lowest priority. + // Returns -1 if the architecture is not applicable. + int IsApplicableArchitecture(Architecture arch, const std::vector& allowedArchitectures); +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h index 2216abf224..40055f4d2f 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerDeployment.h @@ -1,77 +1,77 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Deployment -{ - // A set of optional values useful across many of the deployment functions. - struct Options - { - Options() = default; - explicit Options(bool skipReputationCheck) : SkipReputationCheck(skipReputationCheck) {} - - // Avoid using APIs that make a reputation check. - bool SkipReputationCheck = false; - - // The pairs of URI+Digest to enforce. - std::vector> ExpectedDigests; - }; - - // Waits for a deployment operation. - HRESULT WaitForDeployment( - winrt::Windows::Foundation::IAsyncOperationWithProgress& deployOperation, - IProgressCallback& callback, - bool throwOnError = true); - - // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, - // Otherwise, calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync - void AddPackage( - const winrt::Windows::Foundation::Uri& uri, - const Options& options, - IProgressCallback& callback); - - // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, - // Otherwise, calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync. - // If the Add function fails due to the package being in use, we fall back to stage and register, which allows - // a deferred registration. - // Returns true if the registration was deferred; false if not. - bool AddPackageWithDeferredFallback( - std::string_view uri, - const Options& options, - IProgressCallback& callback); - - // Calls winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync - void RemovePackage( - std::string_view packageFullName, - winrt::Windows::Management::Deployment::RemovalOptions options, - IProgressCallback& callback); - - // Calls winrt::Windows::Management::Deployment::PackageManager::StagePackageAsync - // winrt::Windows::Management::Deployment::PackageManager::ProvisionPackageForAllUsersAsync - // winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFullNameAsync if not running as system - bool AddPackageMachineScope( - std::string_view uri, - const Options& options, - IProgressCallback& callback); - - // Calls winrt::Windows::Management::Deployment::PackageManager::DeprovisionPackageForAllUsersAsync - // winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync with RemoveForAllUsers - void RemovePackageMachineScope( - std::string_view packageFamilyName, - std::string_view packageFullName, - IProgressCallback& callback); - - // Calls winrt::Windows::Management::Deployment::PackageManager::FindPackagesForUser - bool IsRegistered(std::string_view packageFamilyName); - - // Calls winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFamilyNameAsync - void RegisterPackage( - std::string_view packageFamilyName, - IProgressCallback& callback); - - // Determines if the ExpectedDigests property (and thus feture) is supported on the current version of Windows. - bool IsExpectedDigestsSupported(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Deployment +{ + // A set of optional values useful across many of the deployment functions. + struct Options + { + Options() = default; + explicit Options(bool skipReputationCheck) : SkipReputationCheck(skipReputationCheck) {} + + // Avoid using APIs that make a reputation check. + bool SkipReputationCheck = false; + + // The pairs of URI+Digest to enforce. + std::vector> ExpectedDigests; + }; + + // Waits for a deployment operation. + HRESULT WaitForDeployment( + winrt::Windows::Foundation::IAsyncOperationWithProgress& deployOperation, + IProgressCallback& callback, + bool throwOnError = true); + + // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, + // Otherwise, calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync + void AddPackage( + const winrt::Windows::Foundation::Uri& uri, + const Options& options, + IProgressCallback& callback); + + // Calls winrt::Windows::Management::Deployment::PackageManager::AddPackageAsync if skipSmartScreen is true, + // Otherwise, calls winrt::Windows::Management::Deployment::PackageManager::RequestAddPackageAsync. + // If the Add function fails due to the package being in use, we fall back to stage and register, which allows + // a deferred registration. + // Returns true if the registration was deferred; false if not. + bool AddPackageWithDeferredFallback( + std::string_view uri, + const Options& options, + IProgressCallback& callback); + + // Calls winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync + void RemovePackage( + std::string_view packageFullName, + winrt::Windows::Management::Deployment::RemovalOptions options, + IProgressCallback& callback); + + // Calls winrt::Windows::Management::Deployment::PackageManager::StagePackageAsync + // winrt::Windows::Management::Deployment::PackageManager::ProvisionPackageForAllUsersAsync + // winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFullNameAsync if not running as system + bool AddPackageMachineScope( + std::string_view uri, + const Options& options, + IProgressCallback& callback); + + // Calls winrt::Windows::Management::Deployment::PackageManager::DeprovisionPackageForAllUsersAsync + // winrt::Windows::Management::Deployment::PackageManager::RemovePackageAsync with RemoveForAllUsers + void RemovePackageMachineScope( + std::string_view packageFamilyName, + std::string_view packageFullName, + IProgressCallback& callback); + + // Calls winrt::Windows::Management::Deployment::PackageManager::FindPackagesForUser + bool IsRegistered(std::string_view packageFamilyName); + + // Calls winrt::Windows::Management::Deployment::PackageManager::RegisterPackageByFamilyNameAsync + void RegisterPackage( + std::string_view packageFamilyName, + IProgressCallback& callback); + + // Determines if the ExpectedDigests property (and thus feture) is supported on the current version of Windows. + bool IsExpectedDigestsSupported(); +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h index 9969f163f8..cdd9e4f5de 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerDownloader.h @@ -1,145 +1,145 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -namespace AppInstaller::Utility -{ - // The type of data being downloaded; determines what code should - // be used when downloading. - enum class DownloadType - { - Index, - Manifest, - WinGetUtil, - Installer, - InstallerMetadataCollectionInput, - 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. - struct DownloadResult - { - std::vector Sha256Hash; - uint64_t SizeInBytes = 0; - std::optional ContentType; - }; - - // An exception that indicates that a remote service is too busy/unavailable and may contain data on when to try again. - struct ServiceUnavailableException : public wil::ResultException - { - ServiceUnavailableException(std::chrono::seconds retryAfter = 0s) : wil::ResultException(APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE), m_retryAfter(retryAfter) {} - - std::chrono::seconds RetryAfter() const { return m_retryAfter; } - - private: - std::chrono::seconds m_retryAfter; - }; - - // Downloads a file from the given URL and places it in the given location. - // url: The url to be downloaded from. http->https redirection is allowed. - // dest: The stream to be downloaded to. - // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. - // downloadInfo: Optional. Currently only used by DO to identify the download. - DownloadResult DownloadToStream( - const std::string& url, - std::ostream& dest, - DownloadType type, - IProgressCallback& progress, - std::optional downloadInfo = {}); - - // Downloads a file from the given URL and places it in the given location. - // url: The url to be downloaded from. http->https redirection is allowed. - // dest: The path to local file to be downloaded to. - // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. - // downloadInfo: Optional. Currently only used by DO to identify the download. - DownloadResult Download( - const std::string& url, - const std::filesystem::path& dest, - DownloadType type, - IProgressCallback& progress, - std::optional downloadInfo = {}); - - // Gets the headers for the given URL. - std::map GetHeaders(std::string_view url); - - // Determines if the given url is a remote location. - bool IsUrlRemote(std::string_view url); - - // Determines if the given url is secured. - bool IsUrlSecure(std::string_view url); - - // Apply Mark of the web if the target file is on NTFS, otherwise does nothing. - void ApplyMotwIfApplicable(const std::filesystem::path& filePath, URLZONE zone); - - // Remove Mark of the web if the target file is on NTFS, otherwise does nothing. - void RemoveMotwIfApplicable(const std::filesystem::path& filePath); - - // Apply Mark of the web using IAttachmentExecute::Save if the target file is on NTFS, otherwise does nothing. - // This method only does a best effort since Attachment Execution Service may be disabled. - // If IAttachmentExecute::Save is successfully invoked and the scan failed, the failure HRESULT is returned. - // zoneIfScanFailure: URLZONE to apply if IAttachmentExecute::Save scan failed. - HRESULT ApplyMotwUsingIAttachmentExecuteIfApplicable(const std::filesystem::path& filePath, const std::string& source, URLZONE zoneIfScanFailure); - - // Function to read-only create a stream from a uri string (url address or file system path) - ::Microsoft::WRL::ComPtr GetReadOnlyStreamFromURI(std::string_view uriStr); - - // Gets the retry after value in terms of a delay in seconds. - std::chrono::seconds GetRetryAfter(const std::wstring& retryAfter); - - // Gets the retry after value in terms of a delay in seconds. - std::chrono::seconds GetRetryAfter(const winrt::Windows::Web::Http::HttpResponseMessage& response); - - // Data about the cache-control header. - struct CacheControlPolicy - { - // Limit max age to a year - static constexpr unsigned long long MaximumMaxAge = 60 * 60 * 24 * 365; - - CacheControlPolicy() = default; - CacheControlPolicy(std::wstring_view header); - - // True only if the cache-control header was present and contained at least one directive. - bool Present = false; - - // The max-age directive; in seconds. - unsigned long long MaxAge = 0; - - // The no-cache directive; indicates that the cache should always revalidate. - bool NoCache = false; - - // The no-store directive; indicates that the data should not be cached. - bool NoStore = false; - - // The public directive; indicates that the data is not user specific. - bool Public = false; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace AppInstaller::Utility +{ + // The type of data being downloaded; determines what code should + // be used when downloading. + enum class DownloadType + { + Index, + Manifest, + WinGetUtil, + Installer, + InstallerMetadataCollectionInput, + 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. + struct DownloadResult + { + std::vector Sha256Hash; + uint64_t SizeInBytes = 0; + std::optional ContentType; + }; + + // An exception that indicates that a remote service is too busy/unavailable and may contain data on when to try again. + struct ServiceUnavailableException : public wil::ResultException + { + ServiceUnavailableException(std::chrono::seconds retryAfter = 0s) : wil::ResultException(APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE), m_retryAfter(retryAfter) {} + + std::chrono::seconds RetryAfter() const { return m_retryAfter; } + + private: + std::chrono::seconds m_retryAfter; + }; + + // Downloads a file from the given URL and places it in the given location. + // url: The url to be downloaded from. http->https redirection is allowed. + // dest: The stream to be downloaded to. + // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. + // downloadInfo: Optional. Currently only used by DO to identify the download. + DownloadResult DownloadToStream( + const std::string& url, + std::ostream& dest, + DownloadType type, + IProgressCallback& progress, + std::optional downloadInfo = {}); + + // Downloads a file from the given URL and places it in the given location. + // url: The url to be downloaded from. http->https redirection is allowed. + // dest: The path to local file to be downloaded to. + // computeHash: Optional. Indicates if SHA256 hash should be calculated when downloading. + // downloadInfo: Optional. Currently only used by DO to identify the download. + DownloadResult Download( + const std::string& url, + const std::filesystem::path& dest, + DownloadType type, + IProgressCallback& progress, + std::optional downloadInfo = {}); + + // Gets the headers for the given URL. + std::map GetHeaders(std::string_view url); + + // Determines if the given url is a remote location. + bool IsUrlRemote(std::string_view url); + + // Determines if the given url is secured. + bool IsUrlSecure(std::string_view url); + + // Apply Mark of the web if the target file is on NTFS, otherwise does nothing. + void ApplyMotwIfApplicable(const std::filesystem::path& filePath, URLZONE zone); + + // Remove Mark of the web if the target file is on NTFS, otherwise does nothing. + void RemoveMotwIfApplicable(const std::filesystem::path& filePath); + + // Apply Mark of the web using IAttachmentExecute::Save if the target file is on NTFS, otherwise does nothing. + // This method only does a best effort since Attachment Execution Service may be disabled. + // If IAttachmentExecute::Save is successfully invoked and the scan failed, the failure HRESULT is returned. + // zoneIfScanFailure: URLZONE to apply if IAttachmentExecute::Save scan failed. + HRESULT ApplyMotwUsingIAttachmentExecuteIfApplicable(const std::filesystem::path& filePath, const std::string& source, URLZONE zoneIfScanFailure); + + // Function to read-only create a stream from a uri string (url address or file system path) + ::Microsoft::WRL::ComPtr GetReadOnlyStreamFromURI(std::string_view uriStr); + + // Gets the retry after value in terms of a delay in seconds. + std::chrono::seconds GetRetryAfter(const std::wstring& retryAfter); + + // Gets the retry after value in terms of a delay in seconds. + std::chrono::seconds GetRetryAfter(const winrt::Windows::Web::Http::HttpResponseMessage& response); + + // Data about the cache-control header. + struct CacheControlPolicy + { + // Limit max age to a year + static constexpr unsigned long long MaximumMaxAge = 60 * 60 * 24 * 365; + + CacheControlPolicy() = default; + CacheControlPolicy(std::wstring_view header); + + // True only if the cache-control header was present and contained at least one directive. + bool Present = false; + + // The max-age directive; in seconds. + unsigned long long MaxAge = 0; + + // The no-cache directive; indicates that the cache should always revalidate. + bool NoCache = false; + + // The no-store directive; indicates that the data should not be cached. + bool NoStore = false; + + // The public directive; indicates that the data is not user specific. + bool Public = false; + }; +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerFileLogger.h b/src/AppInstallerCommonCore/Public/AppInstallerFileLogger.h index 18d20c7b1e..f59dafb03f 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerFileLogger.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerFileLogger.h @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include -#include -#include - -namespace AppInstaller::Logging -{ - // Logs to a file. - struct FileLogger : public ILogger - { - FileLogger(); - explicit FileLogger(const std::filesystem::path& filePath); - explicit FileLogger(const std::string_view fileNamePrefix); - - ~FileLogger(); - - FileLogger(const FileLogger&) = delete; - FileLogger& operator=(const FileLogger&) = delete; - - FileLogger(FileLogger&&) = default; - FileLogger& operator=(FileLogger&&) = default; - - // The default value for the maximum size comes from settings. - // Setting the maximum size to 0 will disable the maximum. - FileLogger& SetMaximumSize(std::ofstream::off_type maximumSize); - - static std::string GetNameForPath(const std::filesystem::path& filePath); - - static std::string_view DefaultPrefix(); - static std::string_view DefaultExt(); - - // ILogger - std::string GetName() const override; - - void Write(Channel channel, Level level, std::string_view message) noexcept override; - - void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; - - void SetTag(Tag tag) noexcept override; - - // Adds a FileLogger to the current Log - static void Add(); - static void Add(const std::filesystem::path& filePath); - static void Add(std::string_view fileNamePrefix); - - // Starts a background task to clean up old log files. - static void BeginCleanup(); - static void BeginCleanup(const std::filesystem::path& filePath); - - private: - std::string m_name; - std::filesystem::path m_filePath; - std::ofstream m_stream; - std::ofstream::pos_type m_headersEnd = 0; - std::ofstream::off_type m_maximumSize = 0; - - void OpenFileLoggerStream(); - - // Initializes the default maximum file size. - void InitializeDefaultMaximumFileSize(); - - // Determines if the logger needs to wrap back to the beginning, doing so when needed. - // May also shrink the given view if it exceeds the overall maximum. - void HandleMaximumFileSize(std::string_view& currentLog); - - // Resets the log file state so that it will overwrite the data portion. - void WrapLogFile(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include +#include +#include + +namespace AppInstaller::Logging +{ + // Logs to a file. + struct FileLogger : public ILogger + { + FileLogger(); + explicit FileLogger(const std::filesystem::path& filePath); + explicit FileLogger(const std::string_view fileNamePrefix); + + ~FileLogger(); + + FileLogger(const FileLogger&) = delete; + FileLogger& operator=(const FileLogger&) = delete; + + FileLogger(FileLogger&&) = default; + FileLogger& operator=(FileLogger&&) = default; + + // The default value for the maximum size comes from settings. + // Setting the maximum size to 0 will disable the maximum. + FileLogger& SetMaximumSize(std::ofstream::off_type maximumSize); + + static std::string GetNameForPath(const std::filesystem::path& filePath); + + static std::string_view DefaultPrefix(); + static std::string_view DefaultExt(); + + // ILogger + std::string GetName() const override; + + void Write(Channel channel, Level level, std::string_view message) noexcept override; + + void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; + + void SetTag(Tag tag) noexcept override; + + // Adds a FileLogger to the current Log + static void Add(); + static void Add(const std::filesystem::path& filePath); + static void Add(std::string_view fileNamePrefix); + + // Starts a background task to clean up old log files. + static void BeginCleanup(); + static void BeginCleanup(const std::filesystem::path& filePath); + + private: + std::string m_name; + std::filesystem::path m_filePath; + std::ofstream m_stream; + std::ofstream::pos_type m_headersEnd = 0; + std::ofstream::off_type m_maximumSize = 0; + + void OpenFileLoggerStream(); + + // Initializes the default maximum file size. + void InitializeDefaultMaximumFileSize(); + + // Determines if the logger needs to wrap back to the beginning, doing so when needed. + // May also shrink the given view if it exceeds the overall maximum. + void HandleMaximumFileSize(std::string_view& currentLog); + + // Resets the log file state so that it will overwrite the data portion. + void WrapLogFile(); + }; +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h b/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h index 156756e86c..6f6a47145c 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerMsixInfo.h @@ -1,137 +1,137 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AppInstallerProgress.h" -#include "winget/ManagedFile.h" -#include "winget/Manifest.h" -#include "winget/MsixManifest.h" -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace AppInstaller::Msix -{ - // Function to create an AppxBundle package reader given the input file name. - // Returns true if success, false if the input stream is of wrong type. - bool GetBundleReader( - IStream* inputStream, - IAppxBundleReader** reader); - - // Function to create an Appx package reader given the input file name. - // Returns true if success, false if the input stream is of wrong type. - bool GetPackageReader( - IStream* inputStream, - IAppxPackageReader** reader); - - // Function to create an Appx manifest reader given the input file name. - void GetManifestReader( - IStream* inputStream, - IAppxManifestReader** reader); - - // Gets the package full name from the family name. - // This will be the one registered for the current user, if any. - std::optional GetPackageFullNameFromFamilyName(std::string_view familyName); - - // Gets the package family name from the given full name. - std::string GetPackageFamilyNameFromFullName(std::string_view fullName); - - // Gets the package location from the given full name. - std::optional GetPackageLocationFromFullName(std::string_view fullName); - - struct PackageIdInfo - { - std::string Name; - AppInstaller::Utility::UInt64Version Version; - }; - - // Gets the package id info from the given full name. - PackageIdInfo GetPackageIdInfoFromFullName(std::string_view fullName); - - // MsixInfo class handles all appx/msix related query. - struct MsixInfo - { - MsixInfo(std::string_view uriStr); - - template, int> = 0> - MsixInfo(const T& path) : MsixInfo(path.u8string()) {} - - MsixInfo(const MsixInfo&) = default; - MsixInfo& operator=(const MsixInfo&) = default; - - MsixInfo(MsixInfo&&) = default; - MsixInfo& operator=(MsixInfo&&) = default; - - inline bool GetIsBundle() - { - return m_isBundle; - } - - // Full content of AppxSignature.p7x - // If skipP7xFileId is true, returns content of converted .p7s - std::vector GetSignature(bool skipP7xFileId = false); - - // Gets the signature sha256 hash. - Utility::SHA256::HashBuffer GetSignatureHash(); - - // Gets the digest of the package. - std::wstring GetDigest(); - - // Gets the package full name. - std::wstring GetPackageFullNameWide(); - std::string GetPackageFullName(); - - // Gets a value indicating whether the referenced info is newer than the given package. - bool IsNewerThan(const std::filesystem::path& otherPackage); - - bool IsNewerThan(const winrt::Windows::ApplicationModel::PackageVersion& otherVersion); - - // Writes the package file to the given path. - void WriteToFile(std::string_view packageFile, const std::filesystem::path& target, IProgressCallback& progress); - - // Writes the package's manifest to the given path. - void WriteManifestToFile(const std::filesystem::path& target, IProgressCallback& progress); - - // Writes the package file to the given file handle. - void WriteToFileHandle(std::string_view packageFile, HANDLE target, IProgressCallback& progress); - - // Get application package manifests from msix and msixbundle. - std::vector GetAppPackageManifests(bool includeStub = false) const; - - private: - bool m_isBundle; - Microsoft::WRL::ComPtr m_stream; - Microsoft::WRL::ComPtr m_bundleReader; - Microsoft::WRL::ComPtr m_packageReader; - - // Get application packages. Ignore stub packages if any. - std::vector> GetAppPackages(bool includeStub = false) const; - }; - - struct GetCertContextResult - { - wil::unique_cert_context CertContext; - wil::unique_hcertstore CertStore; - }; - - // Get cert context from a signed msix/msixbundle file. - GetCertContextResult GetCertContextFromMsix(const std::filesystem::path& msixPath); - - struct WriteLockedMsixFile - { - WriteLockedMsixFile(const std::filesystem::path& path); - - bool ValidateTrustInfo(bool checkMicrosoftOrigin) const; - - private: - Utility::ManagedFile m_file; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AppInstallerProgress.h" +#include "winget/ManagedFile.h" +#include "winget/Manifest.h" +#include "winget/MsixManifest.h" +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace AppInstaller::Msix +{ + // Function to create an AppxBundle package reader given the input file name. + // Returns true if success, false if the input stream is of wrong type. + bool GetBundleReader( + IStream* inputStream, + IAppxBundleReader** reader); + + // Function to create an Appx package reader given the input file name. + // Returns true if success, false if the input stream is of wrong type. + bool GetPackageReader( + IStream* inputStream, + IAppxPackageReader** reader); + + // Function to create an Appx manifest reader given the input file name. + void GetManifestReader( + IStream* inputStream, + IAppxManifestReader** reader); + + // Gets the package full name from the family name. + // This will be the one registered for the current user, if any. + std::optional GetPackageFullNameFromFamilyName(std::string_view familyName); + + // Gets the package family name from the given full name. + std::string GetPackageFamilyNameFromFullName(std::string_view fullName); + + // Gets the package location from the given full name. + std::optional GetPackageLocationFromFullName(std::string_view fullName); + + struct PackageIdInfo + { + std::string Name; + AppInstaller::Utility::UInt64Version Version; + }; + + // Gets the package id info from the given full name. + PackageIdInfo GetPackageIdInfoFromFullName(std::string_view fullName); + + // MsixInfo class handles all appx/msix related query. + struct MsixInfo + { + MsixInfo(std::string_view uriStr); + + template, int> = 0> + MsixInfo(const T& path) : MsixInfo(path.u8string()) {} + + MsixInfo(const MsixInfo&) = default; + MsixInfo& operator=(const MsixInfo&) = default; + + MsixInfo(MsixInfo&&) = default; + MsixInfo& operator=(MsixInfo&&) = default; + + inline bool GetIsBundle() + { + return m_isBundle; + } + + // Full content of AppxSignature.p7x + // If skipP7xFileId is true, returns content of converted .p7s + std::vector GetSignature(bool skipP7xFileId = false); + + // Gets the signature sha256 hash. + Utility::SHA256::HashBuffer GetSignatureHash(); + + // Gets the digest of the package. + std::wstring GetDigest(); + + // Gets the package full name. + std::wstring GetPackageFullNameWide(); + std::string GetPackageFullName(); + + // Gets a value indicating whether the referenced info is newer than the given package. + bool IsNewerThan(const std::filesystem::path& otherPackage); + + bool IsNewerThan(const winrt::Windows::ApplicationModel::PackageVersion& otherVersion); + + // Writes the package file to the given path. + void WriteToFile(std::string_view packageFile, const std::filesystem::path& target, IProgressCallback& progress); + + // Writes the package's manifest to the given path. + void WriteManifestToFile(const std::filesystem::path& target, IProgressCallback& progress); + + // Writes the package file to the given file handle. + void WriteToFileHandle(std::string_view packageFile, HANDLE target, IProgressCallback& progress); + + // Get application package manifests from msix and msixbundle. + std::vector GetAppPackageManifests(bool includeStub = false) const; + + private: + bool m_isBundle; + Microsoft::WRL::ComPtr m_stream; + Microsoft::WRL::ComPtr m_bundleReader; + Microsoft::WRL::ComPtr m_packageReader; + + // Get application packages. Ignore stub packages if any. + std::vector> GetAppPackages(bool includeStub = false) const; + }; + + struct GetCertContextResult + { + wil::unique_cert_context CertContext; + wil::unique_hcertstore CertStore; + }; + + // Get cert context from a signed msix/msixbundle file. + GetCertContextResult GetCertContextFromMsix(const std::filesystem::path& msixPath); + + struct WriteLockedMsixFile + { + WriteLockedMsixFile(const std::filesystem::path& path); + + bool ValidateTrustInfo(bool checkMicrosoftOrigin) const; + + private: + Utility::ManagedFile m_file; + }; +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerProgress.h b/src/AppInstallerCommonCore/Public/AppInstallerProgress.h index dd049e0d37..cc9d43b7ac 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerProgress.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerProgress.h @@ -1,147 +1,147 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller -{ - // Forward declaration - struct ProgressCallback; - struct IProgressCallback; - - namespace details - { - // For SetCancellationFunction return. - inline void RemoveCancellationFunction(IProgressCallback* callback); - } - - // The semantic meaning of the progress values. - enum class ProgressType : uint32_t - { - // Progress will not be sent. - None, - Bytes, - Percent, - }; - - // The reason why progress is cancelled. - enum class CancelReason : uint32_t - { - None = 0x0, - Abort = 0x1, - CtrlCSignal = 0x2, - User = Abort | CtrlCSignal, - AppShutdown = 0x4, - Any = 0xFFFFFFFF - }; - - DEFINE_ENUM_FLAG_OPERATORS(CancelReason); - - // Gets the HRESULT associated with the given reason. - HRESULT ToHRESULT(CancelReason reason); - - // Interface that provides a callback to inform of cancellation. - struct ICancellable - { - // Inform of cancellation with provided reason. - // When `force` is true, it is expected to happen regardless of user intent. - virtual void Cancel(CancelReason reason, bool force) = 0; - }; - - // Interface that only receives progress, and does not participate in cancellation. - // This allows a sink be simple, and let ProgressCallback handle the complications - // of cancel state. - struct IProgressSink - { - // Called as progress is made. - // If maximum is 0, the maximum is unknown. - virtual void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) = 0; - - // Sets a message for the current progress state. - virtual void SetProgressMessage(std::string_view message) = 0; - - // Called as progress begins. - virtual void BeginProgress() = 0; - - // Called as progress ends. - virtual void EndProgress(bool hideProgressWhenDone) = 0; - }; - - // Callback interface given to the worker to report to. - // Also enables the caller to request cancellation. - struct IProgressCallback : public IProgressSink - { - using CancelFunctionRemoval = wil::unique_any; - - // Returns a value indicating if the future has been cancelled. - virtual bool IsCancelledBy(CancelReason cancelReasons) = 0; - - // Sets a cancellation function that will be called when the operation is to be cancelled. - [[nodiscard]] virtual CancelFunctionRemoval SetCancellationFunction(std::function&& f) = 0; - }; - - // Implementation of IProgressCallback. - struct ProgressCallback : public IProgressCallback - { - ProgressCallback() = default; - ProgressCallback(IProgressSink* sink); - - static bool Wait(IProgressCallback& progress, std::chrono::milliseconds ms); - - void BeginProgress() override; - - void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; - - void SetProgressMessage(std::string_view message) override; - - void EndProgress(bool hideProgressWhenDone) override; - - bool IsCancelledBy(CancelReason cancelReasons) override; - - [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; - - void Cancel(CancelReason reason = CancelReason::Abort); - - IProgressSink* GetSink(); - - private: - std::atomic m_sink = nullptr; - std::function m_cancellationFunction; - CancelReason m_cancelReason = CancelReason::None; - }; - - // A progress callback that reports its progress as a partial range of percentage to its base progress callback - struct PartialPercentProgressCallback : public ProgressCallback - { - PartialPercentProgressCallback(IProgressCallback& baseCallback, uint64_t globalMax); - - void BeginProgress() override; - - void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; - - void SetProgressMessage(std::string_view message) override; - - void EndProgress(bool hideProgressWhenDone) override; - - [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; - - void SetRange(uint64_t rangeMin, uint64_t rangeMax); - - private: - IProgressCallback& m_baseCallback; - uint64_t m_rangeMin = 0; - uint64_t m_rangeMax = 0; - uint64_t m_globalMax = 0; - }; - - namespace details - { - inline void RemoveCancellationFunction(IProgressCallback* callback) - { - (void)callback->SetCancellationFunction(nullptr); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller +{ + // Forward declaration + struct ProgressCallback; + struct IProgressCallback; + + namespace details + { + // For SetCancellationFunction return. + inline void RemoveCancellationFunction(IProgressCallback* callback); + } + + // The semantic meaning of the progress values. + enum class ProgressType : uint32_t + { + // Progress will not be sent. + None, + Bytes, + Percent, + }; + + // The reason why progress is cancelled. + enum class CancelReason : uint32_t + { + None = 0x0, + Abort = 0x1, + CtrlCSignal = 0x2, + User = Abort | CtrlCSignal, + AppShutdown = 0x4, + Any = 0xFFFFFFFF + }; + + DEFINE_ENUM_FLAG_OPERATORS(CancelReason); + + // Gets the HRESULT associated with the given reason. + HRESULT ToHRESULT(CancelReason reason); + + // Interface that provides a callback to inform of cancellation. + struct ICancellable + { + // Inform of cancellation with provided reason. + // When `force` is true, it is expected to happen regardless of user intent. + virtual void Cancel(CancelReason reason, bool force) = 0; + }; + + // Interface that only receives progress, and does not participate in cancellation. + // This allows a sink be simple, and let ProgressCallback handle the complications + // of cancel state. + struct IProgressSink + { + // Called as progress is made. + // If maximum is 0, the maximum is unknown. + virtual void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) = 0; + + // Sets a message for the current progress state. + virtual void SetProgressMessage(std::string_view message) = 0; + + // Called as progress begins. + virtual void BeginProgress() = 0; + + // Called as progress ends. + virtual void EndProgress(bool hideProgressWhenDone) = 0; + }; + + // Callback interface given to the worker to report to. + // Also enables the caller to request cancellation. + struct IProgressCallback : public IProgressSink + { + using CancelFunctionRemoval = wil::unique_any; + + // Returns a value indicating if the future has been cancelled. + virtual bool IsCancelledBy(CancelReason cancelReasons) = 0; + + // Sets a cancellation function that will be called when the operation is to be cancelled. + [[nodiscard]] virtual CancelFunctionRemoval SetCancellationFunction(std::function&& f) = 0; + }; + + // Implementation of IProgressCallback. + struct ProgressCallback : public IProgressCallback + { + ProgressCallback() = default; + ProgressCallback(IProgressSink* sink); + + static bool Wait(IProgressCallback& progress, std::chrono::milliseconds ms); + + void BeginProgress() override; + + void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; + + void SetProgressMessage(std::string_view message) override; + + void EndProgress(bool hideProgressWhenDone) override; + + bool IsCancelledBy(CancelReason cancelReasons) override; + + [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; + + void Cancel(CancelReason reason = CancelReason::Abort); + + IProgressSink* GetSink(); + + private: + std::atomic m_sink = nullptr; + std::function m_cancellationFunction; + CancelReason m_cancelReason = CancelReason::None; + }; + + // A progress callback that reports its progress as a partial range of percentage to its base progress callback + struct PartialPercentProgressCallback : public ProgressCallback + { + PartialPercentProgressCallback(IProgressCallback& baseCallback, uint64_t globalMax); + + void BeginProgress() override; + + void OnProgress(uint64_t current, uint64_t maximum, ProgressType type) override; + + void SetProgressMessage(std::string_view message) override; + + void EndProgress(bool hideProgressWhenDone) override; + + [[nodiscard]] IProgressCallback::CancelFunctionRemoval SetCancellationFunction(std::function&& f) override; + + void SetRange(uint64_t rangeMin, uint64_t rangeMax); + + private: + IProgressCallback& m_baseCallback; + uint64_t m_rangeMin = 0; + uint64_t m_rangeMax = 0; + uint64_t m_globalMax = 0; + }; + + namespace details + { + inline void RemoveCancellationFunction(IProgressCallback* callback) + { + (void)callback->SetCancellationFunction(nullptr); + } + } +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h index 1d56c84435..bc6f54a7a3 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerRuntime.h @@ -1,97 +1,97 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace AppInstaller::Runtime -{ - // Sets the runtime path state name globally. - void SetRuntimePathStateName(std::string name); - - // A path to be retrieved based on the runtime. - enum class PathName - { - // The temporary file location. - Temp, - // The local state (file) storage location. - LocalState, - // The default location where log files are located. - DefaultLogLocation, - // The location that standard type settings are stored. - // In a packaged context, this returns a prepend value for the container name. - StandardSettings, - // The location that user file type settings are stored. - UserFileSettings, - // The location where secure settings data is stored (for reading). - SecureSettingsForRead, - // The location where secure settings data is stored (for writing). - SecureSettingsForWrite, - // The value of %USERPROFILE%. - UserProfile, - // The location where portable packages are installed to with user scope. - PortablePackageUserRoot, - // The location where portable packages are installed to with machine scope. - PortablePackageMachineRoot, - // The location where portable packages are installed to with machine scope (x86). - PortablePackageMachineRootX86, - // The location where symlinks to portable packages are stored under user scope. - PortableLinksUserLocation, - // The location where symlinks to portable packages are stored under machine scope. - PortableLinksMachineLocation, - // The root location for the package containing the winget application. - 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. - CLIExecutable, - // The directory containing the CLI executable file. - MCPExecutable, - // The location of the image assets, if it exists. - ImageAssets, - // The location where fonts are installed with user scope. - FontsUserInstallLocation, - // The location where fonts are installed with machine scope. - FontsMachineInstallLocation, - // The location that standard type settings are stored in files. - StandardFileSettings, - // Always one more than the last path; for being able to iterate paths in tests. - Max - }; - - // Gets the PathDetails used for the given path. - // This is exposed primarily to allow for testing, GetPathTo should be preferred. - Filesystem::PathDetails GetPathDetailsFor(PathName path, bool forDisplay = false); - - // Gets the path to the requested location. - inline std::filesystem::path GetPathTo(PathName path, bool forDisplay = false) - { - return Filesystem::GetPathTo(path, forDisplay); - } - - // Replaces the substring in the path with the user profile environment variable. - void ReplaceProfilePathsWithEnvironmentVariable(std::filesystem::path& path); - - // Gets a new temp file path. - std::filesystem::path GetNewTempFilePath(); - - // Determines whether developer mode is enabled. - bool IsDevModeEnabled(); - - // Gets the default user agent string for the Windows Package Manager. - Utility::LocIndString GetDefaultUserAgent(); - - // Gets the user agent string from passed in caller for the Windows Package Manager. - Utility::LocIndString GetUserAgent(std::string_view caller); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace AppInstaller::Runtime +{ + // Sets the runtime path state name globally. + void SetRuntimePathStateName(std::string name); + + // A path to be retrieved based on the runtime. + enum class PathName + { + // The temporary file location. + Temp, + // The local state (file) storage location. + LocalState, + // The default location where log files are located. + DefaultLogLocation, + // The location that standard type settings are stored. + // In a packaged context, this returns a prepend value for the container name. + StandardSettings, + // The location that user file type settings are stored. + UserFileSettings, + // The location where secure settings data is stored (for reading). + SecureSettingsForRead, + // The location where secure settings data is stored (for writing). + SecureSettingsForWrite, + // The value of %USERPROFILE%. + UserProfile, + // The location where portable packages are installed to with user scope. + PortablePackageUserRoot, + // The location where portable packages are installed to with machine scope. + PortablePackageMachineRoot, + // The location where portable packages are installed to with machine scope (x86). + PortablePackageMachineRootX86, + // The location where symlinks to portable packages are stored under user scope. + PortableLinksUserLocation, + // The location where symlinks to portable packages are stored under machine scope. + PortableLinksMachineLocation, + // The root location for the package containing the winget application. + 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. + CLIExecutable, + // The directory containing the CLI executable file. + MCPExecutable, + // The location of the image assets, if it exists. + ImageAssets, + // The location where fonts are installed with user scope. + FontsUserInstallLocation, + // The location where fonts are installed with machine scope. + FontsMachineInstallLocation, + // The location that standard type settings are stored in files. + StandardFileSettings, + // Always one more than the last path; for being able to iterate paths in tests. + Max + }; + + // Gets the PathDetails used for the given path. + // This is exposed primarily to allow for testing, GetPathTo should be preferred. + Filesystem::PathDetails GetPathDetailsFor(PathName path, bool forDisplay = false); + + // Gets the path to the requested location. + inline std::filesystem::path GetPathTo(PathName path, bool forDisplay = false) + { + return Filesystem::GetPathTo(path, forDisplay); + } + + // Replaces the substring in the path with the user profile environment variable. + void ReplaceProfilePathsWithEnvironmentVariable(std::filesystem::path& path); + + // Gets a new temp file path. + std::filesystem::path GetNewTempFilePath(); + + // Determines whether developer mode is enabled. + bool IsDevModeEnabled(); + + // Gets the default user agent string for the Windows Package Manager. + Utility::LocIndString GetDefaultUserAgent(); + + // Gets the user agent string from passed in caller for the Windows Package Manager. + Utility::LocIndString GetUserAgent(std::string_view caller); +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerSynchronization.h b/src/AppInstallerCommonCore/Public/AppInstallerSynchronization.h index 0749af59a7..b48f0a4970 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerSynchronization.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerSynchronization.h @@ -1,59 +1,59 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include -#include -#include - -using namespace std::chrono_literals; - - -namespace AppInstaller::Synchronization -{ - // This is a standard named mutex. - // It must be acquired and released (or destroyed) on the same thread, just as all Windows mutexes must be. - struct CrossProcessLock - { - CrossProcessLock(std::string_view name); - CrossProcessLock(const std::wstring& name); - - ~CrossProcessLock(); - - CrossProcessLock(const CrossProcessLock&) = delete; - CrossProcessLock& operator=(const CrossProcessLock&) = delete; - - CrossProcessLock(CrossProcessLock&&) = default; - CrossProcessLock& operator=(CrossProcessLock&&) = default; - - // Acquires the lock; cancellation is enabled via the progress object. - // Returns true when the lock is acquired and false if the wait is cancelled. - bool Acquire(IProgressCallback& progress); - - // Optionally release the lock before destroying the object. - void Release(); - - // Attempts to acquire the mutex without a wait. - // Returns true if it was able, false if not. - bool TryAcquireNoWait(); - - // Indicates whether the lock is held. - operator bool() const; - - private: - wil::unique_mutex m_mutex; - wil::mutex_release_scope_exit m_lock; - DWORD m_lockThreadId = 0; - }; - - // This lock is used to prevent multiple winget related processes from attempting to install (or uninstall) at the same time. - // It must be acquired and released (or destroyed) on the same thread, just as all Windows mutexes must be. - struct CrossProcessInstallLock : public CrossProcessLock - { - CrossProcessInstallLock() : CrossProcessLock(L"WinGetCrossProcessInstallLock") {} - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include +#include +#include + +using namespace std::chrono_literals; + + +namespace AppInstaller::Synchronization +{ + // This is a standard named mutex. + // It must be acquired and released (or destroyed) on the same thread, just as all Windows mutexes must be. + struct CrossProcessLock + { + CrossProcessLock(std::string_view name); + CrossProcessLock(const std::wstring& name); + + ~CrossProcessLock(); + + CrossProcessLock(const CrossProcessLock&) = delete; + CrossProcessLock& operator=(const CrossProcessLock&) = delete; + + CrossProcessLock(CrossProcessLock&&) = default; + CrossProcessLock& operator=(CrossProcessLock&&) = default; + + // Acquires the lock; cancellation is enabled via the progress object. + // Returns true when the lock is acquired and false if the wait is cancelled. + bool Acquire(IProgressCallback& progress); + + // Optionally release the lock before destroying the object. + void Release(); + + // Attempts to acquire the mutex without a wait. + // Returns true if it was able, false if not. + bool TryAcquireNoWait(); + + // Indicates whether the lock is held. + operator bool() const; + + private: + wil::unique_mutex m_mutex; + wil::mutex_release_scope_exit m_lock; + DWORD m_lockThreadId = 0; + }; + + // This lock is used to prevent multiple winget related processes from attempting to install (or uninstall) at the same time. + // It must be acquired and released (or destroyed) on the same thread, just as all Windows mutexes must be. + struct CrossProcessInstallLock : public CrossProcessLock + { + CrossProcessInstallLock() : CrossProcessLock(L"WinGetCrossProcessInstallLock") {} + }; +} diff --git a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h index 5ac44d7fa1..3067c1421c 100644 --- a/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h +++ b/src/AppInstallerCommonCore/Public/AppInstallerTelemetry.h @@ -1,356 +1,356 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include -#include -#include -#include -#include - -namespace AppInstaller::Settings -{ - struct UserSettings; -} - -namespace AppInstaller::Logging -{ - enum class ExecutionLevel : UINT32 - { - User = 0, - Admin = 1, - System = 2, - }; - - enum class FailureTypeEnum : UINT32 - { - None = 0x0, - - // Failure type from FailureInfo in result_macros.h - ResultException = 0x1, // THROW_... - ResultReturn = 0x2, // RETURN_..._LOG or RETURN_..._MSG - ResultLog = 0x3, // LOG_... - ResultFailFast = 0x4, // FAIL_FAST_... - - // Other failure types from LogException() - Unknown = 0x10000, - WinrtHResultError = 0x10001, - ResourceOpen = 0x10002, - StdException = 0x10003, - - // Command termination - CommandTermination = 0x20000, - }; - - // Contains all fields logged through the TelemetryTraceLogger. Last write wins. - // This will be used to report a summary event upon destruction of the TelemetryTraceLogger. - struct TelemetrySummary - { - TelemetrySummary() = default; - - // Selectively copy member fields for copy constructor; - TelemetrySummary(const TelemetrySummary& other); - TelemetrySummary& operator=(const TelemetrySummary&) = default; - - TelemetrySummary(TelemetrySummary&&) = default; - TelemetrySummary& operator=(TelemetrySummary&&) = default; - - // Log wil failure, exception, command termination - HRESULT FailureHResult = S_OK; - std::wstring FailureMessage; - std::string FailureModule; - UINT32 FailureThreadId = 0; - FailureTypeEnum FailureType = FailureTypeEnum::None; - std::string FailureFile; - UINT32 FailureLine = 0; - - // LogStartup - bool IsCOMCall = false; - ExecutionLevel ExecutionLevel = ExecutionLevel::User; - - // LogCommand - std::string Command; - - // LogCommandSuccess - bool CommandSuccess = false; - - // LogIsManifestLocal - bool IsManifestLocal = false; - - // LogManifestFields, LogAppFound - std::string PackageIdentifier; - std::string PackageName; - std::string PackageVersion; - std::string Channel; - std::string SourceIdentifier; - - // LogSelectedInstaller - INT32 InstallerArchitecture = -1; - std::string InstallerUrl; - std::string InstallerType; - std::string InstallerScope; - std::string InstallerLocale; - - // LogSearchRequest - std::string SearchType; - std::string SearchQuery; - std::string SearchId; - std::string SearchName; - std::string SearchMoniker; - std::string SearchTag; - std::string SearchCommand; - UINT64 SearchMaximum = 0; - std::string SearchRequest; - - // LogSearchResultCount - UINT64 SearchResultCount = 0; - - // LogInstallerHashMismatch - std::vector HashMismatchExpected; - std::vector HashMismatchActual; - bool HashMismatchOverride = false; - uint64_t HashMismatchActualSize = 0; - std::string HashMismatchContentType; - - // LogInstallerFailure - std::string InstallerExecutionType; - UINT32 InstallerErrorCode = 0; - - // LogUninstallerFailure - std::string UninstallerExecutionType; - UINT32 UninstallerErrorCode = 0; - - // LogRepairFailure - std::string RepairExecutionType; - UINT32 RepairErrorCode = 0; - - // LogSuccessfulInstallARPChange - UINT64 ChangesToARP = 0; - UINT64 MatchesInARP = 0; - UINT64 ChangesThatMatch = 0; - UINT64 ARPLanguage = 0; - std::string ARPName; - std::string ARPVersion; - std::string ARPPublisher; - - // LogNonFatalDOError - std::string DOUrl; - HRESULT DOHResult = S_OK; - }; - - // This type contains the registration lifetime of the telemetry trace logging provider. - // Due to the nature of trace logging, specific methods should be added per desired trace. - // As there should not be a significantly large number of individual telemetry events, - // this should not become a burden. - struct TelemetryTraceLogger - { - TelemetryTraceLogger(bool useSummary = true); - - ~TelemetryTraceLogger(); - - TelemetryTraceLogger(const TelemetryTraceLogger&) = default; - TelemetryTraceLogger& operator=(const TelemetryTraceLogger&) = default; - - TelemetryTraceLogger(TelemetryTraceLogger&&) = default; - TelemetryTraceLogger& operator=(TelemetryTraceLogger&&) = default; - - // Control whether this trace logger is enabled at runtime. - bool DisableRuntime(); - void EnableRuntime(); - - // Return address of m_activityId - const GUID* GetActivityId() const; - - // Return address of m_parentActivityId - const GUID* GetParentActivityId() const; - - // Capture if UserSettings is enabled and set user profile path - void Initialize(); - - // Try to capture if UserSettings is enabled and set user profile path, returns whether the action is successfully completed. - // There is a possible circular dependency with the user settings. When initializing the telemetry, we need to read the settings - // to make sure it's not disabled, but a failure when reading the settings would trigger a telemetry event. We work around that - // by avoiding initialization (and thus disabling telemetry) until we have successfully read the settings. Subsequent calls to - // TryInitialize() would finish the initialization. - bool TryInitialize(); - - // Store the passed in name of the Caller for COM calls - void SetCaller(const std::string& caller); - - // Store the passed in Telemetry Correlation Json for COM calls - void SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept; - - void SetExecutionStage(uint32_t stage) noexcept; - - std::unique_ptr CreateSubTraceLogger() const; - - // Logs the failure info. - void LogFailure(const wil::FailureInfo& failure) const noexcept; - - // Logs the initial process startup. - void LogStartup(bool isCOMCall = false) const noexcept; - - // Logs the invoked command. - void LogCommand(std::string_view commandName) const noexcept; - - // Logs the invoked command success. - void LogCommandSuccess(std::string_view commandName) const noexcept; - - // Logs the invoked command termination. - void LogCommandTermination(HRESULT hr, std::string_view file, size_t line) const noexcept; - - // Logs the invoked command termination. - void LogException(FailureTypeEnum type, std::string_view message) const noexcept; - - // Logs whether the manifest used in workflow is local - void LogIsManifestLocal(bool isLocalManifest) const noexcept; - - // Logs the Manifest fields. - void LogManifestFields(std::string_view id, std::string_view name, std::string_view version) const noexcept; - - // Logs when there is no matching App found for search - void LogNoAppMatch() const noexcept; - - // Logs when there is multiple matching Apps found for search - void LogMultiAppMatch() const noexcept; - - // Logs the name and Id of app found - void LogAppFound(std::string_view name, std::string_view id) const noexcept; - - // Logs the selected installer details - void LogSelectedInstaller(int arch, std::string_view url, std::string_view installerType, std::string_view scope, std::string_view language) const noexcept; - - // Logs details of a search request. - void LogSearchRequest( - std::string_view type, - std::string_view query, - std::string_view id, - std::string_view name, - std::string_view moniker, - std::string_view tag, - std::string_view command, - size_t maximum, - std::string_view request) const noexcept; - - // Logs the Search Result - void LogSearchResultCount(uint64_t resultCount) const noexcept; - - // Logs a mismatch between the expected and actual hash values. - void LogInstallerHashMismatch( - std::string_view id, - std::string_view version, - std::string_view channel, - const std::vector& expected, - const std::vector& actual, - bool overrideHashMismatch, - uint64_t downloadSizeInBytes, - const std::optional& contentType) const noexcept; - - // Logs a failed installation attempt. - void LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept; - - // Logs a failed uninstallation attempt. - void LogUninstallerFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept; - - // Logs a failed repair attempt. - void LogRepairFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept; - - // Logs data about the changes that ocurred in the ARP entries based on an install. - // First 4 arguments are well known values for the package that we installed. - // The next 3 are counts of the number of packages in each category. - // The last 4 are the fields directly from the ARP entry that has been determined to be related to the package that - // was installed, or they will be empty if there is no data or ambiguity about which entry should be logged. - virtual void LogSuccessfulInstallARPChange( - std::string_view sourceIdentifier, - std::string_view packageIdentifier, - std::string_view packageVersion, - std::string_view packageChannel, - size_t changesToARP, - size_t matchesInARP, - size_t countOfIntersectionOfChangesAndMatches, - std::string_view arpName, - std::string_view arpVersion, - std::string_view arpPublisher, - std::string_view arpLanguage) const noexcept; - - void LogNonFatalDOError(std::string_view url, HRESULT hr) const noexcept; - - // Logs details of a preindexed package source update (full or delta). - // Nullable datetime fields use a null optional; nullable float fields use -1.0. - void LogPreindexedPackageUpdate( - std::string_view sourceId, - std::optional previousIndexPublishedAt, - std::chrono::system_clock::time_point newIndexPublishedAt, - bool usedDeltaDownload, - std::optional previousBaselinePublishedAt, - std::optional newBaselinePublishedAt, - bool baselineUpdated, - uint64_t downloadedBytes, - bool isManualUpdate) const noexcept; - - protected: - bool IsTelemetryEnabled() const noexcept; - - // Initializes flags that determine whether telemetry is enabled. - void InitializeInternal(const AppInstaller::Settings::UserSettings& userSettings); - - // Used to anonymize a string to the best of our ability. - // Should primarily be used on failure messages or paths if needed. - std::wstring AnonymizeString(const wchar_t* input) const noexcept; - std::wstring AnonymizeString(std::wstring_view input) const noexcept; - - // Flags used to determine whether to send telemetry. All of them are set during initialization and - // are CopyConstructibleAtomic to minimize the impact of multiple simultaneous initialization attempts. - // m_isSettingEnabled starts as false so we can don't send telemetry until we have read the - // settings and confirmed that it is enabled. - CopyConstructibleAtomic m_isSettingEnabled{ false }; - - // We may decide to disable telemetry at runtime, for example, for command line completion. - CopyConstructibleAtomic m_isRuntimeEnabled{ true }; - - // We wait for initialization of the other flags before sending any events. - CopyConstructibleAtomic m_isInitialized{ false }; - - CopyConstructibleAtomic m_executionStage{ 0 }; - - GUID m_activityId = GUID_NULL; - GUID m_parentActivityId = GUID_NULL; - std::wstring m_telemetryCorrelationJsonW = L"{}"; - std::string m_caller; - - bool m_useSummary = true; - mutable TelemetrySummary m_summary; - - // TODO: This and all related code could be removed after transition to summary event in back end. - uint32_t m_subExecutionId; - }; - - // Helper to make the call sites look clean. - TelemetryTraceLogger& Telemetry(); - - // Turns on wil failure telemetry and logging. - void EnableWilFailureTelemetry(); - - // TODO: Temporary code to keep existing telemetry behavior for command execution cases. - void UseGlobalTelemetryLoggerActivityIdOnly(); - - // An RAII object to disable telemetry during its lifetime. - // Primarily used by the complete command to prevent messy input from spamming us. - struct DisableTelemetryScope - { - DisableTelemetryScope(); - - DisableTelemetryScope(const DisableTelemetryScope&) = delete; - DisableTelemetryScope& operator=(const DisableTelemetryScope&) = delete; - - DisableTelemetryScope(DisableTelemetryScope&&) = default; - DisableTelemetryScope& operator=(DisableTelemetryScope&&) = default; - - ~DisableTelemetryScope(); - - private: - DestructionToken m_token; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include +#include +#include +#include +#include + +namespace AppInstaller::Settings +{ + struct UserSettings; +} + +namespace AppInstaller::Logging +{ + enum class ExecutionLevel : UINT32 + { + User = 0, + Admin = 1, + System = 2, + }; + + enum class FailureTypeEnum : UINT32 + { + None = 0x0, + + // Failure type from FailureInfo in result_macros.h + ResultException = 0x1, // THROW_... + ResultReturn = 0x2, // RETURN_..._LOG or RETURN_..._MSG + ResultLog = 0x3, // LOG_... + ResultFailFast = 0x4, // FAIL_FAST_... + + // Other failure types from LogException() + Unknown = 0x10000, + WinrtHResultError = 0x10001, + ResourceOpen = 0x10002, + StdException = 0x10003, + + // Command termination + CommandTermination = 0x20000, + }; + + // Contains all fields logged through the TelemetryTraceLogger. Last write wins. + // This will be used to report a summary event upon destruction of the TelemetryTraceLogger. + struct TelemetrySummary + { + TelemetrySummary() = default; + + // Selectively copy member fields for copy constructor; + TelemetrySummary(const TelemetrySummary& other); + TelemetrySummary& operator=(const TelemetrySummary&) = default; + + TelemetrySummary(TelemetrySummary&&) = default; + TelemetrySummary& operator=(TelemetrySummary&&) = default; + + // Log wil failure, exception, command termination + HRESULT FailureHResult = S_OK; + std::wstring FailureMessage; + std::string FailureModule; + UINT32 FailureThreadId = 0; + FailureTypeEnum FailureType = FailureTypeEnum::None; + std::string FailureFile; + UINT32 FailureLine = 0; + + // LogStartup + bool IsCOMCall = false; + ExecutionLevel ExecutionLevel = ExecutionLevel::User; + + // LogCommand + std::string Command; + + // LogCommandSuccess + bool CommandSuccess = false; + + // LogIsManifestLocal + bool IsManifestLocal = false; + + // LogManifestFields, LogAppFound + std::string PackageIdentifier; + std::string PackageName; + std::string PackageVersion; + std::string Channel; + std::string SourceIdentifier; + + // LogSelectedInstaller + INT32 InstallerArchitecture = -1; + std::string InstallerUrl; + std::string InstallerType; + std::string InstallerScope; + std::string InstallerLocale; + + // LogSearchRequest + std::string SearchType; + std::string SearchQuery; + std::string SearchId; + std::string SearchName; + std::string SearchMoniker; + std::string SearchTag; + std::string SearchCommand; + UINT64 SearchMaximum = 0; + std::string SearchRequest; + + // LogSearchResultCount + UINT64 SearchResultCount = 0; + + // LogInstallerHashMismatch + std::vector HashMismatchExpected; + std::vector HashMismatchActual; + bool HashMismatchOverride = false; + uint64_t HashMismatchActualSize = 0; + std::string HashMismatchContentType; + + // LogInstallerFailure + std::string InstallerExecutionType; + UINT32 InstallerErrorCode = 0; + + // LogUninstallerFailure + std::string UninstallerExecutionType; + UINT32 UninstallerErrorCode = 0; + + // LogRepairFailure + std::string RepairExecutionType; + UINT32 RepairErrorCode = 0; + + // LogSuccessfulInstallARPChange + UINT64 ChangesToARP = 0; + UINT64 MatchesInARP = 0; + UINT64 ChangesThatMatch = 0; + UINT64 ARPLanguage = 0; + std::string ARPName; + std::string ARPVersion; + std::string ARPPublisher; + + // LogNonFatalDOError + std::string DOUrl; + HRESULT DOHResult = S_OK; + }; + + // This type contains the registration lifetime of the telemetry trace logging provider. + // Due to the nature of trace logging, specific methods should be added per desired trace. + // As there should not be a significantly large number of individual telemetry events, + // this should not become a burden. + struct TelemetryTraceLogger + { + TelemetryTraceLogger(bool useSummary = true); + + ~TelemetryTraceLogger(); + + TelemetryTraceLogger(const TelemetryTraceLogger&) = default; + TelemetryTraceLogger& operator=(const TelemetryTraceLogger&) = default; + + TelemetryTraceLogger(TelemetryTraceLogger&&) = default; + TelemetryTraceLogger& operator=(TelemetryTraceLogger&&) = default; + + // Control whether this trace logger is enabled at runtime. + bool DisableRuntime(); + void EnableRuntime(); + + // Return address of m_activityId + const GUID* GetActivityId() const; + + // Return address of m_parentActivityId + const GUID* GetParentActivityId() const; + + // Capture if UserSettings is enabled and set user profile path + void Initialize(); + + // Try to capture if UserSettings is enabled and set user profile path, returns whether the action is successfully completed. + // There is a possible circular dependency with the user settings. When initializing the telemetry, we need to read the settings + // to make sure it's not disabled, but a failure when reading the settings would trigger a telemetry event. We work around that + // by avoiding initialization (and thus disabling telemetry) until we have successfully read the settings. Subsequent calls to + // TryInitialize() would finish the initialization. + bool TryInitialize(); + + // Store the passed in name of the Caller for COM calls + void SetCaller(const std::string& caller); + + // Store the passed in Telemetry Correlation Json for COM calls + void SetTelemetryCorrelationJson(const std::wstring_view jsonStr_view) noexcept; + + void SetExecutionStage(uint32_t stage) noexcept; + + std::unique_ptr CreateSubTraceLogger() const; + + // Logs the failure info. + void LogFailure(const wil::FailureInfo& failure) const noexcept; + + // Logs the initial process startup. + void LogStartup(bool isCOMCall = false) const noexcept; + + // Logs the invoked command. + void LogCommand(std::string_view commandName) const noexcept; + + // Logs the invoked command success. + void LogCommandSuccess(std::string_view commandName) const noexcept; + + // Logs the invoked command termination. + void LogCommandTermination(HRESULT hr, std::string_view file, size_t line) const noexcept; + + // Logs the invoked command termination. + void LogException(FailureTypeEnum type, std::string_view message) const noexcept; + + // Logs whether the manifest used in workflow is local + void LogIsManifestLocal(bool isLocalManifest) const noexcept; + + // Logs the Manifest fields. + void LogManifestFields(std::string_view id, std::string_view name, std::string_view version) const noexcept; + + // Logs when there is no matching App found for search + void LogNoAppMatch() const noexcept; + + // Logs when there is multiple matching Apps found for search + void LogMultiAppMatch() const noexcept; + + // Logs the name and Id of app found + void LogAppFound(std::string_view name, std::string_view id) const noexcept; + + // Logs the selected installer details + void LogSelectedInstaller(int arch, std::string_view url, std::string_view installerType, std::string_view scope, std::string_view language) const noexcept; + + // Logs details of a search request. + void LogSearchRequest( + std::string_view type, + std::string_view query, + std::string_view id, + std::string_view name, + std::string_view moniker, + std::string_view tag, + std::string_view command, + size_t maximum, + std::string_view request) const noexcept; + + // Logs the Search Result + void LogSearchResultCount(uint64_t resultCount) const noexcept; + + // Logs a mismatch between the expected and actual hash values. + void LogInstallerHashMismatch( + std::string_view id, + std::string_view version, + std::string_view channel, + const std::vector& expected, + const std::vector& actual, + bool overrideHashMismatch, + uint64_t downloadSizeInBytes, + const std::optional& contentType) const noexcept; + + // Logs a failed installation attempt. + void LogInstallerFailure(std::string_view id, std::string_view version, std::string_view channel, std::string_view type, uint32_t errorCode) const noexcept; + + // Logs a failed uninstallation attempt. + void LogUninstallerFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept; + + // Logs a failed repair attempt. + void LogRepairFailure(std::string_view id, std::string_view version, std::string_view type, uint32_t errorCode) const noexcept; + + // Logs data about the changes that ocurred in the ARP entries based on an install. + // First 4 arguments are well known values for the package that we installed. + // The next 3 are counts of the number of packages in each category. + // The last 4 are the fields directly from the ARP entry that has been determined to be related to the package that + // was installed, or they will be empty if there is no data or ambiguity about which entry should be logged. + virtual void LogSuccessfulInstallARPChange( + std::string_view sourceIdentifier, + std::string_view packageIdentifier, + std::string_view packageVersion, + std::string_view packageChannel, + size_t changesToARP, + size_t matchesInARP, + size_t countOfIntersectionOfChangesAndMatches, + std::string_view arpName, + std::string_view arpVersion, + std::string_view arpPublisher, + std::string_view arpLanguage) const noexcept; + + void LogNonFatalDOError(std::string_view url, HRESULT hr) const noexcept; + + // Logs details of a preindexed package source update (full or delta). + // Nullable datetime fields use a null optional; nullable float fields use -1.0. + void LogPreindexedPackageUpdate( + std::string_view sourceId, + std::optional previousIndexPublishedAt, + std::chrono::system_clock::time_point newIndexPublishedAt, + bool usedDeltaDownload, + std::optional previousBaselinePublishedAt, + std::optional newBaselinePublishedAt, + bool baselineUpdated, + uint64_t downloadedBytes, + bool isManualUpdate) const noexcept; + + protected: + bool IsTelemetryEnabled() const noexcept; + + // Initializes flags that determine whether telemetry is enabled. + void InitializeInternal(const AppInstaller::Settings::UserSettings& userSettings); + + // Used to anonymize a string to the best of our ability. + // Should primarily be used on failure messages or paths if needed. + std::wstring AnonymizeString(const wchar_t* input) const noexcept; + std::wstring AnonymizeString(std::wstring_view input) const noexcept; + + // Flags used to determine whether to send telemetry. All of them are set during initialization and + // are CopyConstructibleAtomic to minimize the impact of multiple simultaneous initialization attempts. + // m_isSettingEnabled starts as false so we can don't send telemetry until we have read the + // settings and confirmed that it is enabled. + CopyConstructibleAtomic m_isSettingEnabled{ false }; + + // We may decide to disable telemetry at runtime, for example, for command line completion. + CopyConstructibleAtomic m_isRuntimeEnabled{ true }; + + // We wait for initialization of the other flags before sending any events. + CopyConstructibleAtomic m_isInitialized{ false }; + + CopyConstructibleAtomic m_executionStage{ 0 }; + + GUID m_activityId = GUID_NULL; + GUID m_parentActivityId = GUID_NULL; + std::wstring m_telemetryCorrelationJsonW = L"{}"; + std::string m_caller; + + bool m_useSummary = true; + mutable TelemetrySummary m_summary; + + // TODO: This and all related code could be removed after transition to summary event in back end. + uint32_t m_subExecutionId; + }; + + // Helper to make the call sites look clean. + TelemetryTraceLogger& Telemetry(); + + // Turns on wil failure telemetry and logging. + void EnableWilFailureTelemetry(); + + // TODO: Temporary code to keep existing telemetry behavior for command execution cases. + void UseGlobalTelemetryLoggerActivityIdOnly(); + + // An RAII object to disable telemetry during its lifetime. + // Primarily used by the complete command to prevent messy input from spamming us. + struct DisableTelemetryScope + { + DisableTelemetryScope(); + + DisableTelemetryScope(const DisableTelemetryScope&) = delete; + DisableTelemetryScope& operator=(const DisableTelemetryScope&) = delete; + + DisableTelemetryScope(DisableTelemetryScope&&) = default; + DisableTelemetryScope& operator=(DisableTelemetryScope&&) = default; + + ~DisableTelemetryScope(); + + private: + DestructionToken m_token; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h index 90e468e62a..14a78ddcc7 100644 --- a/src/AppInstallerCommonCore/Public/winget/AdminSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/AdminSettings.h @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#include -#include "winget/GroupPolicy.h" - -namespace AppInstaller::Settings -{ - // Enum of admin settings. - enum class BoolAdminSetting : size_t - { - Unknown = 0, - LocalManifestFiles, - BypassCertificatePinningForMicrosoftStore, - InstallerHashOverride, - LocalArchiveMalwareScanOverride, - ProxyCommandLineOptions, - ConfigurationProcessorPath, - Max, - }; - - enum class StringAdminSetting : size_t - { - Unknown = 0, - DefaultProxy, - Max, - }; - - BoolAdminSetting StringToBoolAdminSetting(std::string_view in); - StringAdminSetting StringToStringAdminSetting(std::string_view in); - - Utility::LocIndView AdminSettingToString(BoolAdminSetting setting); - Utility::LocIndView AdminSettingToString(StringAdminSetting setting); - - // Returns true if the value is set. - // Group policy overriding the setting can prevent the value from being set - bool EnableAdminSetting(BoolAdminSetting setting); - bool DisableAdminSetting(BoolAdminSetting setting); - bool SetAdminSetting(StringAdminSetting setting, std::string_view value); - bool ResetAdminSetting(StringAdminSetting setting); - void ResetAllAdminSettings(); - - bool IsAdminSettingEnabled(BoolAdminSetting setting); - std::optional GetAdminSetting(StringAdminSetting setting); - - std::vector GetAllBoolAdminSettings(); - std::vector GetAllStringAdminSettings(); - - TogglePolicy::Policy GetAdminSettingPolicy(BoolAdminSetting setting); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include +#include "winget/GroupPolicy.h" + +namespace AppInstaller::Settings +{ + // Enum of admin settings. + enum class BoolAdminSetting : size_t + { + Unknown = 0, + LocalManifestFiles, + BypassCertificatePinningForMicrosoftStore, + InstallerHashOverride, + LocalArchiveMalwareScanOverride, + ProxyCommandLineOptions, + ConfigurationProcessorPath, + Max, + }; + + enum class StringAdminSetting : size_t + { + Unknown = 0, + DefaultProxy, + Max, + }; + + BoolAdminSetting StringToBoolAdminSetting(std::string_view in); + StringAdminSetting StringToStringAdminSetting(std::string_view in); + + Utility::LocIndView AdminSettingToString(BoolAdminSetting setting); + Utility::LocIndView AdminSettingToString(StringAdminSetting setting); + + // Returns true if the value is set. + // Group policy overriding the setting can prevent the value from being set + bool EnableAdminSetting(BoolAdminSetting setting); + bool DisableAdminSetting(BoolAdminSetting setting); + bool SetAdminSetting(StringAdminSetting setting, std::string_view value); + bool ResetAdminSetting(StringAdminSetting setting); + void ResetAllAdminSettings(); + + bool IsAdminSettingEnabled(BoolAdminSetting setting); + std::optional GetAdminSetting(StringAdminSetting setting); + + std::vector GetAllBoolAdminSettings(); + std::vector GetAllStringAdminSettings(); + + TogglePolicy::Policy GetAdminSettingPolicy(BoolAdminSetting setting); +} diff --git a/src/AppInstallerCommonCore/Public/winget/Archive.h b/src/AppInstallerCommonCore/Public/winget/Archive.h index 3ca16a3b3e..df3f530bbe 100644 --- a/src/AppInstallerCommonCore/Public/winget/Archive.h +++ b/src/AppInstallerCommonCore/Public/winget/Archive.h @@ -1,18 +1,18 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::Archive -{ - enum class ExtractionMethod - { - // Default archive extraction method is ShellApi. - ShellApi, - Tar, - }; - - HRESULT TryExtractArchive(const std::filesystem::path& archivePath, const std::filesystem::path& destPath); - - bool ScanZipFile(const std::filesystem::path& zipPath); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Archive +{ + enum class ExtractionMethod + { + // Default archive extraction method is ShellApi. + ShellApi, + Tar, + }; + + HRESULT TryExtractArchive(const std::filesystem::path& archivePath, const std::filesystem::path& destPath); + + bool ScanZipFile(const std::filesystem::path& zipPath); +} diff --git a/src/AppInstallerCommonCore/Public/winget/Authentication.h b/src/AppInstallerCommonCore/Public/winget/Authentication.h index 1d7ee5a0e8..0bf561a7de 100644 --- a/src/AppInstallerCommonCore/Public/winget/Authentication.h +++ b/src/AppInstallerCommonCore/Public/winget/Authentication.h @@ -1,142 +1,142 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include "AppInstallerErrors.h" - -namespace AppInstaller::Authentication -{ - // The authentication type supported - enum class AuthenticationType - { - Unknown, - None, - MicrosoftEntraId, - MicrosoftEntraIdForAzureBlobStorage, - }; - - std::string_view AuthenticationTypeToString(AuthenticationType in); - AuthenticationType ConvertToAuthenticationType(std::string_view in); - - // The authentication modes - enum class AuthenticationMode - { - Unknown, - - // Always do interactive authentication on first request, following requests may use cached result. - Interactive, - - // Try silent flow first. If failed, use interactive flow. - SilentPreferred, - - // Only do silent flow. If failed, the authentication failed. - Silent, - }; - - std::string_view AuthenticationModeToString(AuthenticationMode in); - AuthenticationMode ConvertToAuthenticationMode(std::string_view in); - - // Authentication info for Microsoft Entra Id authentication; - struct MicrosoftEntraIdAuthenticationInfo - { - // Resource is required - std::string Resource; - - // Scope is optional - std::string Scope; - - bool operator<(const MicrosoftEntraIdAuthenticationInfo& other) const; - }; - - // Authentication info struct used to initialize Authenticator, this is from source information. - struct AuthenticationInfo - { - 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() const; - }; - - // Authentication arguments struct used to initialize Authenticator, this is from user input. - struct AuthenticationArguments - { - AuthenticationMode Mode = AuthenticationMode::Unknown; - - // Optional. If set, the value will be used to acquire the specific account and also be validated with authentication result. - std::string AuthenticationAccount; - }; - - // The authentication result - struct AuthenticationResult - { - // Default to failed. S_OK on authentication success. - HRESULT Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED; - - // The token result on authentication success. - std::string Token; - }; - - // Individual authentication provider interface. Authenticator will delegate authentication to authentication provider. - struct IAuthenticationProvider - { - virtual ~IAuthenticationProvider() = default; - - // Authenticate and return string result. - virtual AuthenticationResult AuthenticateForToken() = 0; - }; - - // The public facing authenticator - struct Authenticator - { - Authenticator(AuthenticationInfo info, AuthenticationArguments args); - - // Authenticate and return string result. - AuthenticationResult AuthenticateForToken(); - - private: - std::unique_ptr m_authProvider; - }; - - // This is the class for authentication window parent window. - // When authenticating interactively, some api needs handle to a parent window. - // This class will initiate a new thread and create a hidden window but with foreground priority (best effort). - // This class handles terminating the window thread on destruction - struct AuthenticationWindowBase - { - // The constructor will initiate the authentication parent window and thread - AuthenticationWindowBase(); - - AuthenticationWindowBase(const AuthenticationWindowBase&) = delete; - AuthenticationWindowBase& operator=(const AuthenticationWindowBase&) = delete; - - AuthenticationWindowBase(AuthenticationWindowBase&&) = delete; - AuthenticationWindowBase& operator=(AuthenticationWindowBase&&) = delete; - - // Get the native window handle - HWND GetHandle(); - - // The destructor will terminate the authentication parent window and thread - ~AuthenticationWindowBase(); - - private: - HWND m_windowHandle; - DWORD m_windowThreadId; - std::thread m_windowThread; - // In case PostThreadMessage() fails, let window thread exit immediately. - std::atomic m_terminateWindowThread = false; - - void InitializeWindowThread(); - static LRESULT WINAPI WindowProcessFunction(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); - }; - - // Create bearer token from a raw token - std::string CreateBearerToken(std::string rawToken); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include "AppInstallerErrors.h" + +namespace AppInstaller::Authentication +{ + // The authentication type supported + enum class AuthenticationType + { + Unknown, + None, + MicrosoftEntraId, + MicrosoftEntraIdForAzureBlobStorage, + }; + + std::string_view AuthenticationTypeToString(AuthenticationType in); + AuthenticationType ConvertToAuthenticationType(std::string_view in); + + // The authentication modes + enum class AuthenticationMode + { + Unknown, + + // Always do interactive authentication on first request, following requests may use cached result. + Interactive, + + // Try silent flow first. If failed, use interactive flow. + SilentPreferred, + + // Only do silent flow. If failed, the authentication failed. + Silent, + }; + + std::string_view AuthenticationModeToString(AuthenticationMode in); + AuthenticationMode ConvertToAuthenticationMode(std::string_view in); + + // Authentication info for Microsoft Entra Id authentication; + struct MicrosoftEntraIdAuthenticationInfo + { + // Resource is required + std::string Resource; + + // Scope is optional + std::string Scope; + + bool operator<(const MicrosoftEntraIdAuthenticationInfo& other) const; + }; + + // Authentication info struct used to initialize Authenticator, this is from source information. + struct AuthenticationInfo + { + 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() const; + }; + + // Authentication arguments struct used to initialize Authenticator, this is from user input. + struct AuthenticationArguments + { + AuthenticationMode Mode = AuthenticationMode::Unknown; + + // Optional. If set, the value will be used to acquire the specific account and also be validated with authentication result. + std::string AuthenticationAccount; + }; + + // The authentication result + struct AuthenticationResult + { + // Default to failed. S_OK on authentication success. + HRESULT Status = APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED; + + // The token result on authentication success. + std::string Token; + }; + + // Individual authentication provider interface. Authenticator will delegate authentication to authentication provider. + struct IAuthenticationProvider + { + virtual ~IAuthenticationProvider() = default; + + // Authenticate and return string result. + virtual AuthenticationResult AuthenticateForToken() = 0; + }; + + // The public facing authenticator + struct Authenticator + { + Authenticator(AuthenticationInfo info, AuthenticationArguments args); + + // Authenticate and return string result. + AuthenticationResult AuthenticateForToken(); + + private: + std::unique_ptr m_authProvider; + }; + + // This is the class for authentication window parent window. + // When authenticating interactively, some api needs handle to a parent window. + // This class will initiate a new thread and create a hidden window but with foreground priority (best effort). + // This class handles terminating the window thread on destruction + struct AuthenticationWindowBase + { + // The constructor will initiate the authentication parent window and thread + AuthenticationWindowBase(); + + AuthenticationWindowBase(const AuthenticationWindowBase&) = delete; + AuthenticationWindowBase& operator=(const AuthenticationWindowBase&) = delete; + + AuthenticationWindowBase(AuthenticationWindowBase&&) = delete; + AuthenticationWindowBase& operator=(AuthenticationWindowBase&&) = delete; + + // Get the native window handle + HWND GetHandle(); + + // The destructor will terminate the authentication parent window and thread + ~AuthenticationWindowBase(); + + private: + HWND m_windowHandle; + DWORD m_windowThreadId; + std::thread m_windowThread; + // In case PostThreadMessage() fails, let window thread exit immediately. + std::atomic m_terminateWindowThread = false; + + void InitializeWindowThread(); + static LRESULT WINAPI WindowProcessFunction(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + }; + + // Create bearer token from a raw token + std::string CreateBearerToken(std::string rawToken); +} diff --git a/src/AppInstallerCommonCore/Public/winget/Debugging.h b/src/AppInstallerCommonCore/Public/winget/Debugging.h index d10442ebc9..5009de2f9d 100644 --- a/src/AppInstallerCommonCore/Public/winget/Debugging.h +++ b/src/AppInstallerCommonCore/Public/winget/Debugging.h @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::Debugging -{ - // Enables a self initiated minidump on certain process level failures. - // Only the first call to EnableSelfInitiatedMinidump has any effect. - void EnableSelfInitiatedMinidump(); - - // Enables a self initiated minidump on certain process level failures. - // Creates the minidump in the given location. - // Only the first call to EnableSelfInitiatedMinidump has any effect. - void EnableSelfInitiatedMinidump(const std::filesystem::path& filePath); - - // Forces the minidump to be written. - void WriteMinidump(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Debugging +{ + // Enables a self initiated minidump on certain process level failures. + // Only the first call to EnableSelfInitiatedMinidump has any effect. + void EnableSelfInitiatedMinidump(); + + // Enables a self initiated minidump on certain process level failures. + // Creates the minidump in the given location. + // Only the first call to EnableSelfInitiatedMinidump has any effect. + void EnableSelfInitiatedMinidump(const std::filesystem::path& filePath); + + // Forces the minidump to be written. + void WriteMinidump(); +} diff --git a/src/AppInstallerCommonCore/Public/winget/ExtensionCatalog.h b/src/AppInstallerCommonCore/Public/winget/ExtensionCatalog.h index 8308280226..d6da293bfa 100644 --- a/src/AppInstallerCommonCore/Public/winget/ExtensionCatalog.h +++ b/src/AppInstallerCommonCore/Public/winget/ExtensionCatalog.h @@ -1,52 +1,52 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include - -#include -#include -#include - -namespace AppInstaller::Deployment -{ - using namespace std::string_view_literals; - constexpr std::wstring_view SourceExtensionName = L"com.microsoft.winget.source"sv; - - constexpr std::wstring_view IndexDBId = L"IndexDB"sv; - - // Wraps an AppExtension. - struct Extension - { - Extension(winrt::Windows::ApplicationModel::AppExtensions::AppExtension extension); - - // Gets the location of the package root. - std::filesystem::path GetPackagePath() const; - - // Gets the location of the directory shared by the extension. - std::filesystem::path GetPublicFolderPath() const; - - // Get the version of the package. - winrt::Windows::ApplicationModel::PackageVersion GetPackageVersion() const; - - // Verifies the integrity of the extension. - bool VerifyContentIntegrity(IProgressCallback& progress); - - private: - winrt::Windows::ApplicationModel::AppExtensions::AppExtension m_extension; - }; - - // Wraps an AppExtensionCatalog. - struct ExtensionCatalog - { - ExtensionCatalog(std::wstring_view extensionName); - - // Finds an extension by its package family name and id. - std::optional FindByPackageFamilyAndId(std::string_view packageFamilyName, std::wstring_view id) const; - - private: - winrt::Windows::ApplicationModel::AppExtensions::AppExtensionCatalog m_catalog = nullptr; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include + +#include +#include +#include + +namespace AppInstaller::Deployment +{ + using namespace std::string_view_literals; + constexpr std::wstring_view SourceExtensionName = L"com.microsoft.winget.source"sv; + + constexpr std::wstring_view IndexDBId = L"IndexDB"sv; + + // Wraps an AppExtension. + struct Extension + { + Extension(winrt::Windows::ApplicationModel::AppExtensions::AppExtension extension); + + // Gets the location of the package root. + std::filesystem::path GetPackagePath() const; + + // Gets the location of the directory shared by the extension. + std::filesystem::path GetPublicFolderPath() const; + + // Get the version of the package. + winrt::Windows::ApplicationModel::PackageVersion GetPackageVersion() const; + + // Verifies the integrity of the extension. + bool VerifyContentIntegrity(IProgressCallback& progress); + + private: + winrt::Windows::ApplicationModel::AppExtensions::AppExtension m_extension; + }; + + // Wraps an AppExtensionCatalog. + struct ExtensionCatalog + { + ExtensionCatalog(std::wstring_view extensionName); + + // Finds an extension by its package family name and id. + std::optional FindByPackageFamilyAndId(std::string_view packageFamilyName, std::wstring_view id) const; + + private: + winrt::Windows::ApplicationModel::AppExtensions::AppExtensionCatalog m_catalog = nullptr; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/FileCache.h b/src/AppInstallerCommonCore/Public/winget/FileCache.h index 82fd2af153..b35a71428b 100644 --- a/src/AppInstallerCommonCore/Public/winget/FileCache.h +++ b/src/AppInstallerCommonCore/Public/winget/FileCache.h @@ -1,64 +1,64 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - -namespace AppInstaller::Caching -{ - // A file cache for relatively small files (they are always full loaded into memory due to the hash enforcement). - struct FileCache - { - // The supported file cache types. - enum class Type - { - // Manifests for index V1. - IndexV1_Manifest, - // Package version data files for index V2. - IndexV2_PackageVersionData, - // Manifests for index V2. - IndexV2_Manifest, - // Icon for use during show command when sixel rendering is enabled. - Icon, -#ifndef AICLI_DISABLE_TEST_HOOKS - // The test type. - Tests, -#endif - }; - - // Contains information about a specific file cache instance. - struct Details - { - Details(Type type, std::string identifier); - - Runtime::PathName BasePath; - Type Type; - std::string Identifier; - - // Gets the full path to the cache directory. - std::filesystem::path GetCachePath() const; - }; - - // Construct a file cache for the given instance. - FileCache(Type type, std::string identifier, std::vector sources); - - // Gets the details for this file cache. - const Details& GetDetails() const; - - // Gets a stream containing the contents of the requested file. - // The hash must match for this function to return successfully. - std::unique_ptr GetFile(const std::filesystem::path& relativePath, const Utility::SHA256::HashBuffer& expectedHash) const; - - private: - // Gets a stream containing the contents of the requested file from an upstream source. - // The hash must match for this function to return successfully. - std::unique_ptr GetUpstreamFile(std::string relativePath, const Utility::SHA256::HashBuffer& expectedHash) const; - - Details m_details; - std::vector m_sources; - std::filesystem::path m_cacheBase; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + +namespace AppInstaller::Caching +{ + // A file cache for relatively small files (they are always full loaded into memory due to the hash enforcement). + struct FileCache + { + // The supported file cache types. + enum class Type + { + // Manifests for index V1. + IndexV1_Manifest, + // Package version data files for index V2. + IndexV2_PackageVersionData, + // Manifests for index V2. + IndexV2_Manifest, + // Icon for use during show command when sixel rendering is enabled. + Icon, +#ifndef AICLI_DISABLE_TEST_HOOKS + // The test type. + Tests, +#endif + }; + + // Contains information about a specific file cache instance. + struct Details + { + Details(Type type, std::string identifier); + + Runtime::PathName BasePath; + Type Type; + std::string Identifier; + + // Gets the full path to the cache directory. + std::filesystem::path GetCachePath() const; + }; + + // Construct a file cache for the given instance. + FileCache(Type type, std::string identifier, std::vector sources); + + // Gets the details for this file cache. + const Details& GetDetails() const; + + // Gets a stream containing the contents of the requested file. + // The hash must match for this function to return successfully. + std::unique_ptr GetFile(const std::filesystem::path& relativePath, const Utility::SHA256::HashBuffer& expectedHash) const; + + private: + // Gets a stream containing the contents of the requested file from an upstream source. + // The hash must match for this function to return successfully. + std::unique_ptr GetUpstreamFile(std::string relativePath, const Utility::SHA256::HashBuffer& expectedHash) const; + + Details m_details; + std::vector m_sources; + std::filesystem::path m_cacheBase; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/FolderFileWatcher.h b/src/AppInstallerCommonCore/Public/winget/FolderFileWatcher.h index cbf26b7bff..e00d11d3c4 100644 --- a/src/AppInstallerCommonCore/Public/winget/FolderFileWatcher.h +++ b/src/AppInstallerCommonCore/Public/winget/FolderFileWatcher.h @@ -1,36 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#pragma warning( push ) -#pragma warning ( disable : 6387 28196 ) -#include -#pragma warning( pop ) - -namespace AppInstaller::Utility -{ - // Watch for new/renamed files recursively in a given directory with an optional extension. - struct FolderFileWatcher - { - FolderFileWatcher(const std::filesystem::path& path, const std::optional& ext = std::nullopt); - ~FolderFileWatcher() {}; - - FolderFileWatcher(const FolderFileWatcher&) = delete; - FolderFileWatcher& operator=(const FolderFileWatcher&) = delete; - - FolderFileWatcher(FolderFileWatcher&&) = default; - FolderFileWatcher& operator=(FolderFileWatcher&&) = default; - - void Start(); - void Stop(); - - const std::unordered_set& Files() { return m_files; } - const std::filesystem::path& FolderPath() { return m_path; } - - private: - std::filesystem::path m_path; - std::optional m_ext; - std::unordered_set m_files; - wil::unique_folder_change_reader m_changeReader; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#pragma warning( push ) +#pragma warning ( disable : 6387 28196 ) +#include +#pragma warning( pop ) + +namespace AppInstaller::Utility +{ + // Watch for new/renamed files recursively in a given directory with an optional extension. + struct FolderFileWatcher + { + FolderFileWatcher(const std::filesystem::path& path, const std::optional& ext = std::nullopt); + ~FolderFileWatcher() {}; + + FolderFileWatcher(const FolderFileWatcher&) = delete; + FolderFileWatcher& operator=(const FolderFileWatcher&) = delete; + + FolderFileWatcher(FolderFileWatcher&&) = default; + FolderFileWatcher& operator=(FolderFileWatcher&&) = default; + + void Start(); + void Stop(); + + const std::unordered_set& Files() { return m_files; } + const std::filesystem::path& FolderPath() { return m_path; } + + private: + std::filesystem::path m_path; + std::optional m_ext; + std::unordered_set m_files; + wil::unique_folder_change_reader m_changeReader; + }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/HttpClientHelper.h b/src/AppInstallerCommonCore/Public/winget/HttpClientHelper.h index eeedfb2c00..1035b685bc 100644 --- a/src/AppInstallerCommonCore/Public/winget/HttpClientHelper.h +++ b/src/AppInstallerCommonCore/Public/winget/HttpClientHelper.h @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Http -{ - struct HttpClientHelper - { - using HttpRequestHeaders = std::unordered_map; - - struct HttpResponseHandlerResult - { - // The custom response handler result. Default is empty. - std::optional Result = std::nullopt; - - // Indicates whether to use default handling logic by HttpClientHelper instead (i.e. the custom response handler does not handle the specific response). - bool UseDefaultHandling = false; - }; - - using HttpResponseHandler = std::function; - - HttpClientHelper(std::shared_ptr = {}); - - pplx::task Post(const utility::string_t& uri, const web::json::value& body, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}) const; - - std::optional HandlePost(const utility::string_t& uri, const web::json::value& body, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}, const HttpResponseHandler& customHandler = {}) const; - - pplx::task Get(const utility::string_t& uri, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}) const; - - std::optional HandleGet(const utility::string_t& uri, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}, const HttpResponseHandler& customHandler = {}) const; - - void SetPinningConfiguration(const Certificates::PinningConfiguration& configuration, std::shared_ptr threadGlobals = {}); - - protected: - std::optional ValidateAndExtractResponse(const web::http::http_response& response) const; - - std::optional ExtractJsonResponse(const web::http::http_response& response) const; - - private: - web::http::client::http_client GetClient(const utility::string_t& uri) const; - - // Translates a cpprestsdk http_exception to a WIL exception. - static void RethrowAsWilException(const web::http::http_exception& exception); - - std::shared_ptr m_defaultRequestHandlerStage; - web::http::client::http_client_config m_clientConfig; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Http +{ + struct HttpClientHelper + { + using HttpRequestHeaders = std::unordered_map; + + struct HttpResponseHandlerResult + { + // The custom response handler result. Default is empty. + std::optional Result = std::nullopt; + + // Indicates whether to use default handling logic by HttpClientHelper instead (i.e. the custom response handler does not handle the specific response). + bool UseDefaultHandling = false; + }; + + using HttpResponseHandler = std::function; + + HttpClientHelper(std::shared_ptr = {}); + + pplx::task Post(const utility::string_t& uri, const web::json::value& body, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}) const; + + std::optional HandlePost(const utility::string_t& uri, const web::json::value& body, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}, const HttpResponseHandler& customHandler = {}) const; + + pplx::task Get(const utility::string_t& uri, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}) const; + + std::optional HandleGet(const utility::string_t& uri, const HttpRequestHeaders& headers = {}, const HttpRequestHeaders& authHeaders = {}, const HttpResponseHandler& customHandler = {}) const; + + void SetPinningConfiguration(const Certificates::PinningConfiguration& configuration, std::shared_ptr threadGlobals = {}); + + protected: + std::optional ValidateAndExtractResponse(const web::http::http_response& response) const; + + std::optional ExtractJsonResponse(const web::http::http_response& response) const; + + private: + web::http::client::http_client GetClient(const utility::string_t& uri) const; + + // Translates a cpprestsdk http_exception to a WIL exception. + static void RethrowAsWilException(const web::http::http_exception& exception); + + std::shared_ptr m_defaultRequestHandlerStage; + web::http::client::http_client_config m_clientConfig; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/Locale.h b/src/AppInstallerCommonCore/Public/winget/Locale.h index 5ae86a8de4..fcbe60dbc4 100644 --- a/src/AppInstallerCommonCore/Public/winget/Locale.h +++ b/src/AppInstallerCommonCore/Public/winget/Locale.h @@ -1,28 +1,28 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::Locale -{ - static constexpr double MinimumDistanceScoreAsPerfectMatch = 1.0; - static constexpr double MinimumDistanceScoreAsCompatibleMatch = 0.9; - static constexpr double UnknownLanguageDistanceScore = 0.0; - - // Check if a bcp47 language tag is well formed - bool IsWellFormedBcp47Tag(std::string_view bcp47Tag); - - // Get a score of language distance between target and available. The return value range is 0 to 1. - // With 1 meaning perfect match and 0 meaning no match. - double GetDistanceOfLanguage(std::string_view target, std::string_view available); - - // Get the list of user Preferred Languages from settings. Returns an empty vector in rare cases of failure. - std::vector GetUserPreferredLanguages(); - - // Get the list of user Preferred Languages from settings. Returns an empty vector in rare cases of failure. - std::vector GetUserPreferredLanguagesUTF16(); - - // Get the bcp47 tag from a locale id. Returns empty string if conversion cannot be performed. - std::string LocaleIdToBcp47Tag(LCID localeId); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::Locale +{ + static constexpr double MinimumDistanceScoreAsPerfectMatch = 1.0; + static constexpr double MinimumDistanceScoreAsCompatibleMatch = 0.9; + static constexpr double UnknownLanguageDistanceScore = 0.0; + + // Check if a bcp47 language tag is well formed + bool IsWellFormedBcp47Tag(std::string_view bcp47Tag); + + // Get a score of language distance between target and available. The return value range is 0 to 1. + // With 1 meaning perfect match and 0 meaning no match. + double GetDistanceOfLanguage(std::string_view target, std::string_view available); + + // Get the list of user Preferred Languages from settings. Returns an empty vector in rare cases of failure. + std::vector GetUserPreferredLanguages(); + + // Get the list of user Preferred Languages from settings. Returns an empty vector in rare cases of failure. + std::vector GetUserPreferredLanguagesUTF16(); + + // Get the bcp47 tag from a locale id. Returns empty string if conversion cannot be performed. + std::string LocaleIdToBcp47Tag(LCID localeId); +} diff --git a/src/AppInstallerCommonCore/Public/winget/MSStore.h b/src/AppInstallerCommonCore/Public/winget/MSStore.h index 8dd67491a4..1db918ea79 100644 --- a/src/AppInstallerCommonCore/Public/winget/MSStore.h +++ b/src/AppInstallerCommonCore/Public/winget/MSStore.h @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Manifest.h" -#include - -#include -#include - -#include - -namespace AppInstaller::MSStore -{ - using namespace std::string_view_literals; - static constexpr std::wstring_view s_AppInstallerProductId = L"9NBLGGH4NNS1"sv; - - enum class MSStoreOperationType - { - Install, - Update, - Repair, - }; - - struct MSStoreOperation - { - MSStoreOperation(MSStoreOperationType type, const std::wstring& productId, Manifest::ScopeEnum scope, bool isSilentMode, bool force) : - m_type(type), m_productId(productId), m_scope(scope), m_isSilentMode(isSilentMode), m_force(force) - { - } - - MSStoreOperation(const MSStoreOperation&) = delete; - MSStoreOperation& operator=(const MSStoreOperation&) = delete; - - MSStoreOperation(MSStoreOperation&&) = delete; - MSStoreOperation& operator=(MSStoreOperation&&) = delete; - - HRESULT StartAndWaitForOperation(IProgressCallback& progress); - - private: - HRESULT InstallPackage(IProgressCallback& progress); - HRESULT UpdatePackage(IProgressCallback& progress); - - MSStoreOperationType m_type; - std::wstring m_productId; - Manifest::ScopeEnum m_scope; - bool m_isSilentMode; - bool m_force; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Manifest.h" +#include + +#include +#include + +#include + +namespace AppInstaller::MSStore +{ + using namespace std::string_view_literals; + static constexpr std::wstring_view s_AppInstallerProductId = L"9NBLGGH4NNS1"sv; + + enum class MSStoreOperationType + { + Install, + Update, + Repair, + }; + + struct MSStoreOperation + { + MSStoreOperation(MSStoreOperationType type, const std::wstring& productId, Manifest::ScopeEnum scope, bool isSilentMode, bool force) : + m_type(type), m_productId(productId), m_scope(scope), m_isSilentMode(isSilentMode), m_force(force) + { + } + + MSStoreOperation(const MSStoreOperation&) = delete; + MSStoreOperation& operator=(const MSStoreOperation&) = delete; + + MSStoreOperation(MSStoreOperation&&) = delete; + MSStoreOperation& operator=(MSStoreOperation&&) = delete; + + HRESULT StartAndWaitForOperation(IProgressCallback& progress); + + private: + HRESULT InstallPackage(IProgressCallback& progress); + HRESULT UpdatePackage(IProgressCallback& progress); + + MSStoreOperationType m_type; + std::wstring m_productId; + Manifest::ScopeEnum m_scope; + bool m_isSilentMode; + bool m_force; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/MSStoreDownload.h b/src/AppInstallerCommonCore/Public/winget/MSStoreDownload.h index be3f087d3d..132a4a05d1 100644 --- a/src/AppInstallerCommonCore/Public/winget/MSStoreDownload.h +++ b/src/AppInstallerCommonCore/Public/winget/MSStoreDownload.h @@ -1,61 +1,61 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include "winget/Authentication.h" -#include "winget/ManifestCommon.h" - -#include -#include -#include -#include -#include - -namespace AppInstaller::MSStore -{ - // Struct representing 1 MSStore package file download info - struct MSStoreDownloadFile - { - std::string Url; - AppInstaller::Utility::SHA256::HashBuffer Sha256; - std::string FileName; - AppInstaller::Utility::UInt64Version Version = 0; - }; - - struct MSStoreDownloadInfo - { - std::vector MainPackages; - std::vector DependencyPackages; - - std::string ContentId; - }; - - struct MSStoreDownloadContext - { - MSStoreDownloadContext( - std::string productId, - AppInstaller::Utility::Architecture architecture, - AppInstaller::Manifest::PlatformEnum platform, - std::string locale, - AppInstaller::Authentication::AuthenticationArguments authArgs); - - void TargetOSVersion(std::optional targetOSVersion); - - // Calls display catalog API and sfs-client to get download info. - MSStoreDownloadInfo GetDownloadInfo(); - - // Gets license for the corresponding packages - std::vector GetLicense(std::string_view contentId); - - private: - std::string m_productId; - AppInstaller::Utility::Architecture m_architecture = AppInstaller::Utility::Architecture::Unknown; - AppInstaller::Manifest::PlatformEnum m_platform = AppInstaller::Manifest::PlatformEnum::Unknown; - std::optional m_targetOSVersion = std::nullopt; - std::string m_locale; - std::unique_ptr m_displayCatalogAuthenticator; - std::unique_ptr m_licensingAuthenticator; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include "winget/Authentication.h" +#include "winget/ManifestCommon.h" + +#include +#include +#include +#include +#include + +namespace AppInstaller::MSStore +{ + // Struct representing 1 MSStore package file download info + struct MSStoreDownloadFile + { + std::string Url; + AppInstaller::Utility::SHA256::HashBuffer Sha256; + std::string FileName; + AppInstaller::Utility::UInt64Version Version = 0; + }; + + struct MSStoreDownloadInfo + { + std::vector MainPackages; + std::vector DependencyPackages; + + std::string ContentId; + }; + + struct MSStoreDownloadContext + { + MSStoreDownloadContext( + std::string productId, + AppInstaller::Utility::Architecture architecture, + AppInstaller::Manifest::PlatformEnum platform, + std::string locale, + AppInstaller::Authentication::AuthenticationArguments authArgs); + + void TargetOSVersion(std::optional targetOSVersion); + + // Calls display catalog API and sfs-client to get download info. + MSStoreDownloadInfo GetDownloadInfo(); + + // Gets license for the corresponding packages + std::vector GetLicense(std::string_view contentId); + + private: + std::string m_productId; + AppInstaller::Utility::Architecture m_architecture = AppInstaller::Utility::Architecture::Unknown; + AppInstaller::Manifest::PlatformEnum m_platform = AppInstaller::Manifest::PlatformEnum::Unknown; + std::optional m_targetOSVersion = std::nullopt; + std::string m_locale; + std::unique_ptr m_displayCatalogAuthenticator; + std::unique_ptr m_licensingAuthenticator; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/Manifest.h b/src/AppInstallerCommonCore/Public/winget/Manifest.h index d46a87a1c8..725b4dd56c 100644 --- a/src/AppInstallerCommonCore/Public/winget/Manifest.h +++ b/src/AppInstallerCommonCore/Public/winget/Manifest.h @@ -1,75 +1,75 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - -#include - -namespace AppInstaller::Manifest -{ - // Representation of the parsed manifest file. - struct Manifest - { - using string_t = Utility::NormalizedString; - - string_t Id; - - string_t Version; - - string_t Channel; - - string_t Moniker; - - ManifestVer ManifestVersion; - - ManifestInstaller DefaultInstallerInfo; - - std::vector Installers; - - ManifestLocalization DefaultLocalization; - - std::vector Localizations; - - ManifestLocalization CurrentLocalization; - - // ApplyLocale will update the CurrentLocalization according to the specified locale - // If locale is empty, user setting locale will be used - void ApplyLocale(const std::string& locale = {}); - - // Get all tags across localizations - std::vector GetAggregatedTags() const; - - // Get all commands across installers - std::vector GetAggregatedCommands() const; - - // Gets ARP version range if declared, otherwise an empty range is returned - Utility::VersionRange GetArpVersionRange() const; - - // Get package family names across installers, Case folded. - std::vector GetPackageFamilyNames() const; - - // Get product codes across installers, Case folded. - std::vector GetProductCodes() const; - - // Get upgrade codes across installers, Case folded. - std::vector GetUpgradeCodes() const; - - // Get package names across localizations and installers, Case folded. - std::vector GetPackageNames() const; - - // Get publishers across localizations and installers, Case folded. - std::vector GetPublishers() const; - - // If not empty, the SHA256 hash of the manifest stream itself. - Utility::SHA256::HashBuffer StreamSha256; - - private: - std::vector GetSystemReferenceStrings( - std::function extractStringFromInstaller = {}, - std::function extractStringFromAppsAndFeaturesEntry = {}) const; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + +#include + +namespace AppInstaller::Manifest +{ + // Representation of the parsed manifest file. + struct Manifest + { + using string_t = Utility::NormalizedString; + + string_t Id; + + string_t Version; + + string_t Channel; + + string_t Moniker; + + ManifestVer ManifestVersion; + + ManifestInstaller DefaultInstallerInfo; + + std::vector Installers; + + ManifestLocalization DefaultLocalization; + + std::vector Localizations; + + ManifestLocalization CurrentLocalization; + + // ApplyLocale will update the CurrentLocalization according to the specified locale + // If locale is empty, user setting locale will be used + void ApplyLocale(const std::string& locale = {}); + + // Get all tags across localizations + std::vector GetAggregatedTags() const; + + // Get all commands across installers + std::vector GetAggregatedCommands() const; + + // Gets ARP version range if declared, otherwise an empty range is returned + Utility::VersionRange GetArpVersionRange() const; + + // Get package family names across installers, Case folded. + std::vector GetPackageFamilyNames() const; + + // Get product codes across installers, Case folded. + std::vector GetProductCodes() const; + + // Get upgrade codes across installers, Case folded. + std::vector GetUpgradeCodes() const; + + // Get package names across localizations and installers, Case folded. + std::vector GetPackageNames() const; + + // Get publishers across localizations and installers, Case folded. + std::vector GetPublishers() const; + + // If not empty, the SHA256 hash of the manifest stream itself. + Utility::SHA256::HashBuffer StreamSha256; + + private: + std::vector GetSystemReferenceStrings( + std::function extractStringFromInstaller = {}, + std::function extractStringFromAppsAndFeaturesEntry = {}) const; + }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h index 50a2fa2902..f97a21f34c 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestCommon.h @@ -1,526 +1,526 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Manifest -{ - // Forward declaration - struct ManifestInstaller; - - using string_t = Utility::NormalizedString; - using namespace std::string_view_literals; - using Utility::RawVersion; - - // The maximum supported major version known about by this code. - constexpr uint64_t s_MaxSupportedMajorVersion = 1; - - // The default manifest version assigned to manifests without a ManifestVersion field. - constexpr std::string_view s_DefaultManifestVersion = "0.1.0"sv; - - // V1 manifest version for GA - constexpr std::string_view s_ManifestVersionV1 = "1.0.0"sv; - - // V1.1 manifest version - constexpr std::string_view s_ManifestVersionV1_1 = "1.1.0"sv; - - // V1.2 manifest version - constexpr std::string_view s_ManifestVersionV1_2 = "1.2.0"sv; - - // V1.4 manifest version - constexpr std::string_view s_ManifestVersionV1_4 = "1.4.0"sv; - - // V1.5 manifest version - constexpr std::string_view s_ManifestVersionV1_5 = "1.5.0"sv; - - // V1.6 manifest version - constexpr std::string_view s_ManifestVersionV1_6 = "1.6.0"sv; - - // V1.7 manifest version - constexpr std::string_view s_ManifestVersionV1_7 = "1.7.0"sv; - - // V1.9 manifest version - constexpr std::string_view s_ManifestVersionV1_9 = "1.9.0"sv; - - // V1.10 manifest version - constexpr std::string_view s_ManifestVersionV1_10 = "1.10.0"sv; - - // V1.12 manifest version - constexpr std::string_view s_ManifestVersionV1_12 = "1.12.0"sv; - - // V1.28 manifest version - constexpr std::string_view s_ManifestVersionV1_28 = "1.28.0"sv; - - // Any new manifest version must also be added to src\WinGetUtilInterop\Manifest\ManifestVersion.cs. - - // The manifest extension for the MS Store - constexpr std::string_view s_MSStoreExtension = "msstore"sv; - - struct ManifestValidateOption - { - ManifestValidateOption() = default; - explicit ManifestValidateOption(bool fullValidation) : FullValidation(fullValidation) {} - - bool SchemaValidationOnly = false; - bool ErrorOnVerifiedPublisherFields = false; - bool InstallerValidation = false; - bool ErrorOnNetworkAddressInSwitches = false; - - // Options not exposed in winget util - bool FullValidation = false; - bool ThrowOnWarning = false; - bool AllowShadowManifest = false; - bool SchemaHeaderValidationAsWarning = false; - }; - - // ManifestVer is inherited from Utility::Version and is a more restricted version. - // ManifestVer is used to specify the version of app manifest itself. - // ManifestVer is a 3 part version in the format of [0-65535].[0-65535].[0-65535] - // and optionally a following tag in the format of -[SomeString] for experimental purpose. - struct ManifestVer : public Utility::Version - { - ManifestVer() = default; - - ManifestVer(std::string_view version); - - uint64_t Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } - uint64_t Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } - uint64_t Patch() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } - - bool HasExtension() const; - - bool HasExtension(std::string_view extension) const; - - private: - std::vector m_extensions; - }; - - enum class InstallerTypeEnum - { - Unknown, - Inno, - Wix, - Msi, - Nullsoft, - Zip, - Msix, - Exe, - Burn, - MSStore, - Portable, - Font, - }; - - enum class UpdateBehaviorEnum - { - Unknown, - Install, - UninstallPrevious, - Deny, - }; - - enum class InstallerSwitchType - { - Custom, - Silent, - SilentWithProgress, - Interactive, - Language, - Log, - InstallLocation, - Update, - Repair, - }; - - enum class RepairBehaviorEnum - { - Unknown, - Modify, - Installer, - Uninstaller, - }; - - enum class ScopeEnum - { - Unknown, - User, - Machine, - }; - - enum class InstallModeEnum - { - Unknown, - Interactive, - Silent, - SilentWithProgress, - }; - - enum class ExpectedReturnCodeEnum - { - Unknown, - PackageInUse, - PackageInUseByApplication, - InstallInProgress, - FileInUse, - MissingDependency, - DiskFull, - InsufficientMemory, - InvalidParameter, - NoNetwork, - ContactSupport, - RebootRequiredToFinish, - RebootRequiredForInstall, - RebootInitiated, - CancelledByUser, - AlreadyInstalled, - Downgrade, - BlockedByPolicy, - SystemNotSupported, - Custom, - }; - - enum class PlatformEnum - { - Unknown, - Universal, - Desktop, - IoT, - Team, - Holographic, - }; - - enum class ElevationRequirementEnum - { - Unknown, - ElevationRequired, - ElevationProhibited, - ElevatesSelf, - }; - - enum class UnsupportedArgumentEnum - { - Unknown, - Log, - Location - }; - - enum class InstalledFileTypeEnum - { - Unknown, - Launch, - Uninstall, - Other, - }; - - enum class ManifestTypeEnum - { - Singleton, - Version, - Installer, - DefaultLocale, - Locale, - Merged, - Preview, - Shadow, - }; - - enum class DependencyType - { - WindowsFeature, - WindowsLibrary, - Package, - External - }; - - enum class IconFileTypeEnum - { - Unknown, - Jpeg, - Png, - Ico, - }; - - enum class IconThemeEnum - { - Unknown, - Default, - Light, - Dark, - HighContrast, - }; - - // Icon resolutions from https://learn.microsoft.com/en-us/windows/apps/design/style/iconography/app-icon-construction#app-icon - enum class IconResolutionEnum - { - Unknown, - Custom, - Square16, - Square20, - Square24, - Square30, - Square32, - Square36, - Square40, - Square48, - Square60, - Square64, - Square72, - Square80, - Square96, - Square256, - }; - - struct ExpectedReturnCode - { - DWORD InstallerReturnCode = 0; - ExpectedReturnCodeEnum ReturnResponse = ExpectedReturnCodeEnum::Unknown; - string_t ReturnResponseUrl; - }; - - struct Dependency - { - DependencyType Type; - const string_t& Id() const { return m_id; }; - std::optional MinVersion; - - Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), m_id(std::move(id)), MinVersion(Utility::Version(std::move(minVersion))), m_foldedId(FoldCase(m_id)) {} - Dependency(DependencyType type, string_t id) : Type(type), m_id(std::move(id)), m_foldedId(FoldCase(m_id)){} - Dependency(DependencyType type) : Type(type) {} - - bool operator ==(const Dependency& rhs) const - { - return Type == rhs.Type && m_foldedId == rhs.m_foldedId && MinVersion == rhs.MinVersion; - } - - bool operator <(const Dependency& rhs) const - { - return m_foldedId < rhs.m_foldedId; - } - - bool IsVersionOk(const Utility::Version& version) - { - return MinVersion <= version; - } - - // m_foldedId should be set whenever Id is set - void SetId(string_t id) - { - m_id = std::move(id); - m_foldedId = FoldCase(m_id); - } - - private: - string_t m_id; - std::string m_foldedId; - }; - - struct DependencyList - { - void Add(const Dependency& newDependency); - void Add(const DependencyList& otherDependencyList); - bool HasAny() const; - bool HasAnyOf(DependencyType type) const; - Dependency* HasDependency(const Dependency& dependencyToSearch); - void ApplyToType(DependencyType type, std::function func) const; - void ApplyToAll(std::function func) const; - bool Empty() const; - void Clear(); - bool HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion = "") const; - size_t Size() const; - - private: - std::vector m_dependencies; - }; - - struct AppsAndFeaturesEntry - { - string_t DisplayName; - string_t Publisher; - string_t DisplayVersion; - string_t ProductCode; - string_t UpgradeCode; - InstallerTypeEnum InstallerType = InstallerTypeEnum::Unknown; - }; - - struct MarketsInfo - { - std::vector AllowedMarkets; - std::vector ExcludedMarkets; - }; - - struct NestedInstallerFile - { - string_t RelativeFilePath; - string_t PortableCommandAlias; - }; - - struct InstalledFile - { - string_t RelativeFilePath; - std::vector FileSha256; - InstalledFileTypeEnum FileType = InstalledFileTypeEnum::Other; - string_t InvocationParameter; - string_t DisplayName; - }; - - struct InstallationMetadataInfo - { - string_t DefaultInstallLocation; - std::vector Files; - - // Checks if there are any installation metadata available. - bool HasData() const { return !DefaultInstallLocation.empty() || !Files.empty(); } - - void Clear() { DefaultInstallLocation.clear(); Files.clear(); } - }; - - // Information about a specific DSC resource. - struct DesiredStateConfigurationResourceInfo - { - string_t Name; - }; - - // The type of resource container. - enum class DesiredStateConfigurationContainerType - { - PowerShell, - DSCv3, - }; - - // Information about a DSC container. - // Contains the union of properties relevant to all container types. - struct DesiredStateConfigurationContainerInfo - { - DesiredStateConfigurationContainerInfo(DesiredStateConfigurationContainerType type) : Type(type) {} - - DesiredStateConfigurationContainerInfo(const string_t& repositoryUrl, const string_t& moduleName, std::vector resources) : - Type(DesiredStateConfigurationContainerType::PowerShell), RepositoryURL(repositoryUrl), ModuleName(moduleName), Resources(std::move(resources)) {} - - DesiredStateConfigurationContainerInfo(std::vector resources) : - Type(DesiredStateConfigurationContainerType::DSCv3), Resources(std::move(resources)) {} - - DesiredStateConfigurationContainerType Type; - - // For Type == PowerShell - string_t RepositoryURL; - string_t ModuleName; - - // For all types - std::vector Resources; - }; - - InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); - - UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); - - ScopeEnum ConvertToScopeEnum(std::string_view in); - - InstallModeEnum ConvertToInstallModeEnum(const std::string& in); - - PlatformEnum ConvertToPlatformEnum(std::string_view in); - - PlatformEnum ConvertToPlatformEnumForMSStoreDownload(std::string_view in); - - ElevationRequirementEnum ConvertToElevationRequirementEnum(const std::string& in); - - UnsupportedArgumentEnum ConvertToUnsupportedArgumentEnum(const std::string& in); - - ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in); - - ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(const std::string& in); - - InstalledFileTypeEnum ConvertToInstalledFileTypeEnum(const std::string& in); - - IconFileTypeEnum ConvertToIconFileTypeEnum(std::string_view in); - - IconThemeEnum ConvertToIconThemeEnum(std::string_view in); - - IconResolutionEnum ConvertToIconResolutionEnum(std::string_view in); - - RepairBehaviorEnum ConvertToRepairBehaviorEnum(std::string_view in); - - std::string_view InstallerTypeToString(InstallerTypeEnum installerType); - - std::string_view InstallerSwitchTypeToString(InstallerSwitchType installerSwitchType); - - std::string_view ElevationRequirementToString(ElevationRequirementEnum elevationRequirement); - - std::string_view InstallModeToString(InstallModeEnum installMode); - - std::string_view UnsupportedArgumentToString(UnsupportedArgumentEnum unsupportedArgument); - - std::string_view UpdateBehaviorToString(UpdateBehaviorEnum updateBehavior); - - std::string_view RepairBehaviorToString(RepairBehaviorEnum repairBehavior); - - // Short string representation does not contain "Windows." - std::string_view PlatformToString(PlatformEnum platform, bool shortString = false); - - std::string_view ScopeToString(ScopeEnum scope); - - std::string_view InstalledFileTypeToString(InstalledFileTypeEnum installedFileType); - - std::string_view IconFileTypeToString(IconFileTypeEnum iconFileType); - - std::string_view IconThemeToString(IconThemeEnum iconTheme); - - std::string_view IconResolutionToString(IconResolutionEnum iconResolution); - - std::string_view ManifestTypeToString(ManifestTypeEnum manifestType); - - std::string_view ExpectedReturnCodeToString(ExpectedReturnCodeEnum expectedReturnCode); - - // Gets a value indicating whether the given installer uses the PackageFamilyName system reference. - bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); - - // Gets a value indicating whether any of the ARP entries uses the PackageFamilyName system reference. - bool DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(const std::vector& entries); - - // Gets a value indicating whether the given installer uses the ProductCode system reference. - bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer writes ARP entry. - bool DoesInstallerTypeWriteAppsAndFeaturesEntry(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer type supports ARP version range. - bool DoesInstallerTypeSupportArpVersionRange(InstallerTypeEnum installer); - - // Gets a value indicating whether the given installer ignores the Scope value from the manifest. - bool DoesInstallerTypeIgnoreScopeFromManifest(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer requires admin for install. - bool DoesInstallerTypeRequireAdminForMachineScopeInstall(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer requires RepairBehavior for repair. - bool DoesInstallerTypeRequireRepairBehaviorForRepair(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer type uses MSI properties in its command line. - bool DoesInstallerTypeUseMsiProperties(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer type is an archive. - bool IsArchiveType(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer type is a portable. - bool IsPortableType(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given installer type supports multiple nested installers. - bool DoesInstallerTypeSupportMultipleNestedInstallers(InstallerTypeEnum installerType); - - // Gets a value indicating whether the given nested installer type is supported. - bool IsNestedInstallerTypeSupported(InstallerTypeEnum nestedInstallerType); - - // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible - bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); - - // Get a list of default switches for known installer types - std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType); - - // Get a list of default return codes for known installer types - std::map GetDefaultKnownReturnCodes(InstallerTypeEnum installerType); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Manifest +{ + // Forward declaration + struct ManifestInstaller; + + using string_t = Utility::NormalizedString; + using namespace std::string_view_literals; + using Utility::RawVersion; + + // The maximum supported major version known about by this code. + constexpr uint64_t s_MaxSupportedMajorVersion = 1; + + // The default manifest version assigned to manifests without a ManifestVersion field. + constexpr std::string_view s_DefaultManifestVersion = "0.1.0"sv; + + // V1 manifest version for GA + constexpr std::string_view s_ManifestVersionV1 = "1.0.0"sv; + + // V1.1 manifest version + constexpr std::string_view s_ManifestVersionV1_1 = "1.1.0"sv; + + // V1.2 manifest version + constexpr std::string_view s_ManifestVersionV1_2 = "1.2.0"sv; + + // V1.4 manifest version + constexpr std::string_view s_ManifestVersionV1_4 = "1.4.0"sv; + + // V1.5 manifest version + constexpr std::string_view s_ManifestVersionV1_5 = "1.5.0"sv; + + // V1.6 manifest version + constexpr std::string_view s_ManifestVersionV1_6 = "1.6.0"sv; + + // V1.7 manifest version + constexpr std::string_view s_ManifestVersionV1_7 = "1.7.0"sv; + + // V1.9 manifest version + constexpr std::string_view s_ManifestVersionV1_9 = "1.9.0"sv; + + // V1.10 manifest version + constexpr std::string_view s_ManifestVersionV1_10 = "1.10.0"sv; + + // V1.12 manifest version + constexpr std::string_view s_ManifestVersionV1_12 = "1.12.0"sv; + + // V1.28 manifest version + constexpr std::string_view s_ManifestVersionV1_28 = "1.28.0"sv; + + // Any new manifest version must also be added to src\WinGetUtilInterop\Manifest\ManifestVersion.cs. + + // The manifest extension for the MS Store + constexpr std::string_view s_MSStoreExtension = "msstore"sv; + + struct ManifestValidateOption + { + ManifestValidateOption() = default; + explicit ManifestValidateOption(bool fullValidation) : FullValidation(fullValidation) {} + + bool SchemaValidationOnly = false; + bool ErrorOnVerifiedPublisherFields = false; + bool InstallerValidation = false; + bool ErrorOnNetworkAddressInSwitches = false; + + // Options not exposed in winget util + bool FullValidation = false; + bool ThrowOnWarning = false; + bool AllowShadowManifest = false; + bool SchemaHeaderValidationAsWarning = false; + }; + + // ManifestVer is inherited from Utility::Version and is a more restricted version. + // ManifestVer is used to specify the version of app manifest itself. + // ManifestVer is a 3 part version in the format of [0-65535].[0-65535].[0-65535] + // and optionally a following tag in the format of -[SomeString] for experimental purpose. + struct ManifestVer : public Utility::Version + { + ManifestVer() = default; + + ManifestVer(std::string_view version); + + uint64_t Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } + uint64_t Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } + uint64_t Patch() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } + + bool HasExtension() const; + + bool HasExtension(std::string_view extension) const; + + private: + std::vector m_extensions; + }; + + enum class InstallerTypeEnum + { + Unknown, + Inno, + Wix, + Msi, + Nullsoft, + Zip, + Msix, + Exe, + Burn, + MSStore, + Portable, + Font, + }; + + enum class UpdateBehaviorEnum + { + Unknown, + Install, + UninstallPrevious, + Deny, + }; + + enum class InstallerSwitchType + { + Custom, + Silent, + SilentWithProgress, + Interactive, + Language, + Log, + InstallLocation, + Update, + Repair, + }; + + enum class RepairBehaviorEnum + { + Unknown, + Modify, + Installer, + Uninstaller, + }; + + enum class ScopeEnum + { + Unknown, + User, + Machine, + }; + + enum class InstallModeEnum + { + Unknown, + Interactive, + Silent, + SilentWithProgress, + }; + + enum class ExpectedReturnCodeEnum + { + Unknown, + PackageInUse, + PackageInUseByApplication, + InstallInProgress, + FileInUse, + MissingDependency, + DiskFull, + InsufficientMemory, + InvalidParameter, + NoNetwork, + ContactSupport, + RebootRequiredToFinish, + RebootRequiredForInstall, + RebootInitiated, + CancelledByUser, + AlreadyInstalled, + Downgrade, + BlockedByPolicy, + SystemNotSupported, + Custom, + }; + + enum class PlatformEnum + { + Unknown, + Universal, + Desktop, + IoT, + Team, + Holographic, + }; + + enum class ElevationRequirementEnum + { + Unknown, + ElevationRequired, + ElevationProhibited, + ElevatesSelf, + }; + + enum class UnsupportedArgumentEnum + { + Unknown, + Log, + Location + }; + + enum class InstalledFileTypeEnum + { + Unknown, + Launch, + Uninstall, + Other, + }; + + enum class ManifestTypeEnum + { + Singleton, + Version, + Installer, + DefaultLocale, + Locale, + Merged, + Preview, + Shadow, + }; + + enum class DependencyType + { + WindowsFeature, + WindowsLibrary, + Package, + External + }; + + enum class IconFileTypeEnum + { + Unknown, + Jpeg, + Png, + Ico, + }; + + enum class IconThemeEnum + { + Unknown, + Default, + Light, + Dark, + HighContrast, + }; + + // Icon resolutions from https://learn.microsoft.com/en-us/windows/apps/design/style/iconography/app-icon-construction#app-icon + enum class IconResolutionEnum + { + Unknown, + Custom, + Square16, + Square20, + Square24, + Square30, + Square32, + Square36, + Square40, + Square48, + Square60, + Square64, + Square72, + Square80, + Square96, + Square256, + }; + + struct ExpectedReturnCode + { + DWORD InstallerReturnCode = 0; + ExpectedReturnCodeEnum ReturnResponse = ExpectedReturnCodeEnum::Unknown; + string_t ReturnResponseUrl; + }; + + struct Dependency + { + DependencyType Type; + const string_t& Id() const { return m_id; }; + std::optional MinVersion; + + Dependency(DependencyType type, string_t id, string_t minVersion) : Type(type), m_id(std::move(id)), MinVersion(Utility::Version(std::move(minVersion))), m_foldedId(FoldCase(m_id)) {} + Dependency(DependencyType type, string_t id) : Type(type), m_id(std::move(id)), m_foldedId(FoldCase(m_id)){} + Dependency(DependencyType type) : Type(type) {} + + bool operator ==(const Dependency& rhs) const + { + return Type == rhs.Type && m_foldedId == rhs.m_foldedId && MinVersion == rhs.MinVersion; + } + + bool operator <(const Dependency& rhs) const + { + return m_foldedId < rhs.m_foldedId; + } + + bool IsVersionOk(const Utility::Version& version) + { + return MinVersion <= version; + } + + // m_foldedId should be set whenever Id is set + void SetId(string_t id) + { + m_id = std::move(id); + m_foldedId = FoldCase(m_id); + } + + private: + string_t m_id; + std::string m_foldedId; + }; + + struct DependencyList + { + void Add(const Dependency& newDependency); + void Add(const DependencyList& otherDependencyList); + bool HasAny() const; + bool HasAnyOf(DependencyType type) const; + Dependency* HasDependency(const Dependency& dependencyToSearch); + void ApplyToType(DependencyType type, std::function func) const; + void ApplyToAll(std::function func) const; + bool Empty() const; + void Clear(); + bool HasExactDependency(DependencyType type, const string_t& id, const string_t& minVersion = "") const; + size_t Size() const; + + private: + std::vector m_dependencies; + }; + + struct AppsAndFeaturesEntry + { + string_t DisplayName; + string_t Publisher; + string_t DisplayVersion; + string_t ProductCode; + string_t UpgradeCode; + InstallerTypeEnum InstallerType = InstallerTypeEnum::Unknown; + }; + + struct MarketsInfo + { + std::vector AllowedMarkets; + std::vector ExcludedMarkets; + }; + + struct NestedInstallerFile + { + string_t RelativeFilePath; + string_t PortableCommandAlias; + }; + + struct InstalledFile + { + string_t RelativeFilePath; + std::vector FileSha256; + InstalledFileTypeEnum FileType = InstalledFileTypeEnum::Other; + string_t InvocationParameter; + string_t DisplayName; + }; + + struct InstallationMetadataInfo + { + string_t DefaultInstallLocation; + std::vector Files; + + // Checks if there are any installation metadata available. + bool HasData() const { return !DefaultInstallLocation.empty() || !Files.empty(); } + + void Clear() { DefaultInstallLocation.clear(); Files.clear(); } + }; + + // Information about a specific DSC resource. + struct DesiredStateConfigurationResourceInfo + { + string_t Name; + }; + + // The type of resource container. + enum class DesiredStateConfigurationContainerType + { + PowerShell, + DSCv3, + }; + + // Information about a DSC container. + // Contains the union of properties relevant to all container types. + struct DesiredStateConfigurationContainerInfo + { + DesiredStateConfigurationContainerInfo(DesiredStateConfigurationContainerType type) : Type(type) {} + + DesiredStateConfigurationContainerInfo(const string_t& repositoryUrl, const string_t& moduleName, std::vector resources) : + Type(DesiredStateConfigurationContainerType::PowerShell), RepositoryURL(repositoryUrl), ModuleName(moduleName), Resources(std::move(resources)) {} + + DesiredStateConfigurationContainerInfo(std::vector resources) : + Type(DesiredStateConfigurationContainerType::DSCv3), Resources(std::move(resources)) {} + + DesiredStateConfigurationContainerType Type; + + // For Type == PowerShell + string_t RepositoryURL; + string_t ModuleName; + + // For all types + std::vector Resources; + }; + + InstallerTypeEnum ConvertToInstallerTypeEnum(const std::string& in); + + UpdateBehaviorEnum ConvertToUpdateBehaviorEnum(const std::string& in); + + ScopeEnum ConvertToScopeEnum(std::string_view in); + + InstallModeEnum ConvertToInstallModeEnum(const std::string& in); + + PlatformEnum ConvertToPlatformEnum(std::string_view in); + + PlatformEnum ConvertToPlatformEnumForMSStoreDownload(std::string_view in); + + ElevationRequirementEnum ConvertToElevationRequirementEnum(const std::string& in); + + UnsupportedArgumentEnum ConvertToUnsupportedArgumentEnum(const std::string& in); + + ManifestTypeEnum ConvertToManifestTypeEnum(const std::string& in); + + ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(const std::string& in); + + InstalledFileTypeEnum ConvertToInstalledFileTypeEnum(const std::string& in); + + IconFileTypeEnum ConvertToIconFileTypeEnum(std::string_view in); + + IconThemeEnum ConvertToIconThemeEnum(std::string_view in); + + IconResolutionEnum ConvertToIconResolutionEnum(std::string_view in); + + RepairBehaviorEnum ConvertToRepairBehaviorEnum(std::string_view in); + + std::string_view InstallerTypeToString(InstallerTypeEnum installerType); + + std::string_view InstallerSwitchTypeToString(InstallerSwitchType installerSwitchType); + + std::string_view ElevationRequirementToString(ElevationRequirementEnum elevationRequirement); + + std::string_view InstallModeToString(InstallModeEnum installMode); + + std::string_view UnsupportedArgumentToString(UnsupportedArgumentEnum unsupportedArgument); + + std::string_view UpdateBehaviorToString(UpdateBehaviorEnum updateBehavior); + + std::string_view RepairBehaviorToString(RepairBehaviorEnum repairBehavior); + + // Short string representation does not contain "Windows." + std::string_view PlatformToString(PlatformEnum platform, bool shortString = false); + + std::string_view ScopeToString(ScopeEnum scope); + + std::string_view InstalledFileTypeToString(InstalledFileTypeEnum installedFileType); + + std::string_view IconFileTypeToString(IconFileTypeEnum iconFileType); + + std::string_view IconThemeToString(IconThemeEnum iconTheme); + + std::string_view IconResolutionToString(IconResolutionEnum iconResolution); + + std::string_view ManifestTypeToString(ManifestTypeEnum manifestType); + + std::string_view ExpectedReturnCodeToString(ExpectedReturnCodeEnum expectedReturnCode); + + // Gets a value indicating whether the given installer uses the PackageFamilyName system reference. + bool DoesInstallerTypeUsePackageFamilyName(InstallerTypeEnum installerType); + + // Gets a value indicating whether any of the ARP entries uses the PackageFamilyName system reference. + bool DoAnyAppsAndFeaturesEntriesUsePackageFamilyName(const std::vector& entries); + + // Gets a value indicating whether the given installer uses the ProductCode system reference. + bool DoesInstallerTypeUseProductCode(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer writes ARP entry. + bool DoesInstallerTypeWriteAppsAndFeaturesEntry(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer type supports ARP version range. + bool DoesInstallerTypeSupportArpVersionRange(InstallerTypeEnum installer); + + // Gets a value indicating whether the given installer ignores the Scope value from the manifest. + bool DoesInstallerTypeIgnoreScopeFromManifest(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer requires admin for install. + bool DoesInstallerTypeRequireAdminForMachineScopeInstall(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer requires RepairBehavior for repair. + bool DoesInstallerTypeRequireRepairBehaviorForRepair(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer type uses MSI properties in its command line. + bool DoesInstallerTypeUseMsiProperties(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer type is an archive. + bool IsArchiveType(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer type is a portable. + bool IsPortableType(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given installer type supports multiple nested installers. + bool DoesInstallerTypeSupportMultipleNestedInstallers(InstallerTypeEnum installerType); + + // Gets a value indicating whether the given nested installer type is supported. + bool IsNestedInstallerTypeSupported(InstallerTypeEnum nestedInstallerType); + + // Checks whether 2 installer types are compatible. E.g. inno and exe are update compatible + bool IsInstallerTypeCompatible(InstallerTypeEnum type1, InstallerTypeEnum type2); + + // Get a list of default switches for known installer types + std::map GetDefaultKnownSwitches(InstallerTypeEnum installerType); + + // Get a list of default return codes for known installer types + std::map GetDefaultKnownReturnCodes(InstallerTypeEnum installerType); +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestComparator.h b/src/AppInstallerCommonCore/Public/winget/ManifestComparator.h index 96f199efcc..1820b8a097 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestComparator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestComparator.h @@ -1,137 +1,137 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::Manifest -{ - // Flags to indicate why an installer was not applicable - enum class InapplicabilityFlags : int - { - None = 0x0, - OSVersion = 0x1, - InstalledScope = 0x2, - InstalledType = 0x4, - InstalledLocale = 0x8, - Locale = 0x10, - Scope = 0x20, - MachineArchitecture = 0x40, - Market = 0x80, - InstallerType = 0x100, - }; - - DEFINE_ENUM_FLAG_OPERATORS(InapplicabilityFlags); - - namespace details - { - // An interface for defining new filters based on user inputs. - struct FilterField - { - FilterField(std::string_view name) : m_name(name) {} - - virtual ~FilterField() = default; - - std::string_view Name() const { return m_name; } - - // Determines if the installer is applicable based on this field alone. - virtual InapplicabilityFlags IsApplicable(const AppInstaller::Manifest::ManifestInstaller& installer) = 0; - - // Explains why the filter regarded this installer as inapplicable. - // Will only be called when IsApplicable returns false. - virtual std::string ExplainInapplicable(const AppInstaller::Manifest::ManifestInstaller& installer) = 0; - - private: - std::string_view m_name; - }; - - // The result of ComparisonField::IsFirstBetter - enum class ComparisonResult - { - // The first input is not better than the second input. - Negative, - // The first input is somewhat better than the second input. - // If another comparison has a strong positive result, it will override a weak result. - WeakPositive, - // The first input is definitely better than the second input. - StrongPositive, - }; - - // An interface for defining new comparisons based on user inputs. - struct ComparisonField : public FilterField - { - using FilterField::FilterField; - - virtual ~ComparisonField() = default; - - // Determines if the first installer is a better choice based on this field alone. - virtual ComparisonResult IsFirstBetter(const AppInstaller::Manifest::ManifestInstaller& first, const AppInstaller::Manifest::ManifestInstaller& second) = 0; - }; - } - - struct InstallerAndInapplicabilities - { - std::optional installer; - std::vector inapplicabilitiesInstaller; - }; - - // Class in charge of comparing manifest entries - struct ManifestComparator - { - // Options that affect the comparisons. - struct Options - { - // The allowed architectures and a value indicating whether to perform applicability checks. - std::vector AllowedArchitectures; - bool SkipApplicabilityCheck = false; - - // The requested installer type. - std::optional RequestedInstallerType; - - // The currently installed type. - std::optional CurrentlyInstalledType; - - // The requested installer scope and a value indicating whether and unknown scope is acceptable. - std::optional RequestedInstallerScope; - std::optional AllowUnknownScope; - - // The currently installed scope. - std::optional CurrentlyInstalledScope; - - // The requested installer locale. - std::optional RequestedInstallerLocale; - - // Get the currently installed locale intent and value. - std::optional PreviousUserIntentLocale; - std::optional CurrentlyInstalledLocale; - }; - - ManifestComparator(const Options& options); - - // Gets the best installer from the manifest, if at least one is applicable. - InstallerAndInapplicabilities GetPreferredInstaller(const AppInstaller::Manifest::Manifest& manifest); - - // Determines if an installer is applicable. - InapplicabilityFlags IsApplicable(const AppInstaller::Manifest::ManifestInstaller& installer); - - // Determines if the first installer is a better choice. - bool IsFirstBetter( - const AppInstaller::Manifest::ManifestInstaller& first, - const AppInstaller::Manifest::ManifestInstaller& second); - - private: - void AddFilter(std::unique_ptr&& filter); - void AddComparator(std::unique_ptr&& comparator); - - std::vector> m_filters; - // Non-owning pointers to values in m_filters. - std::vector m_comparators; - }; - -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::Manifest +{ + // Flags to indicate why an installer was not applicable + enum class InapplicabilityFlags : int + { + None = 0x0, + OSVersion = 0x1, + InstalledScope = 0x2, + InstalledType = 0x4, + InstalledLocale = 0x8, + Locale = 0x10, + Scope = 0x20, + MachineArchitecture = 0x40, + Market = 0x80, + InstallerType = 0x100, + }; + + DEFINE_ENUM_FLAG_OPERATORS(InapplicabilityFlags); + + namespace details + { + // An interface for defining new filters based on user inputs. + struct FilterField + { + FilterField(std::string_view name) : m_name(name) {} + + virtual ~FilterField() = default; + + std::string_view Name() const { return m_name; } + + // Determines if the installer is applicable based on this field alone. + virtual InapplicabilityFlags IsApplicable(const AppInstaller::Manifest::ManifestInstaller& installer) = 0; + + // Explains why the filter regarded this installer as inapplicable. + // Will only be called when IsApplicable returns false. + virtual std::string ExplainInapplicable(const AppInstaller::Manifest::ManifestInstaller& installer) = 0; + + private: + std::string_view m_name; + }; + + // The result of ComparisonField::IsFirstBetter + enum class ComparisonResult + { + // The first input is not better than the second input. + Negative, + // The first input is somewhat better than the second input. + // If another comparison has a strong positive result, it will override a weak result. + WeakPositive, + // The first input is definitely better than the second input. + StrongPositive, + }; + + // An interface for defining new comparisons based on user inputs. + struct ComparisonField : public FilterField + { + using FilterField::FilterField; + + virtual ~ComparisonField() = default; + + // Determines if the first installer is a better choice based on this field alone. + virtual ComparisonResult IsFirstBetter(const AppInstaller::Manifest::ManifestInstaller& first, const AppInstaller::Manifest::ManifestInstaller& second) = 0; + }; + } + + struct InstallerAndInapplicabilities + { + std::optional installer; + std::vector inapplicabilitiesInstaller; + }; + + // Class in charge of comparing manifest entries + struct ManifestComparator + { + // Options that affect the comparisons. + struct Options + { + // The allowed architectures and a value indicating whether to perform applicability checks. + std::vector AllowedArchitectures; + bool SkipApplicabilityCheck = false; + + // The requested installer type. + std::optional RequestedInstallerType; + + // The currently installed type. + std::optional CurrentlyInstalledType; + + // The requested installer scope and a value indicating whether and unknown scope is acceptable. + std::optional RequestedInstallerScope; + std::optional AllowUnknownScope; + + // The currently installed scope. + std::optional CurrentlyInstalledScope; + + // The requested installer locale. + std::optional RequestedInstallerLocale; + + // Get the currently installed locale intent and value. + std::optional PreviousUserIntentLocale; + std::optional CurrentlyInstalledLocale; + }; + + ManifestComparator(const Options& options); + + // Gets the best installer from the manifest, if at least one is applicable. + InstallerAndInapplicabilities GetPreferredInstaller(const AppInstaller::Manifest::Manifest& manifest); + + // Determines if an installer is applicable. + InapplicabilityFlags IsApplicable(const AppInstaller::Manifest::ManifestInstaller& installer); + + // Determines if the first installer is a better choice. + bool IsFirstBetter( + const AppInstaller::Manifest::ManifestInstaller& first, + const AppInstaller::Manifest::ManifestInstaller& second); + + private: + void AddFilter(std::unique_ptr&& filter); + void AddComparator(std::unique_ptr&& comparator); + + std::vector> m_filters; + // Non-owning pointers to values in m_filters. + std::vector m_comparators; + }; + +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h index 3ec7c9346f..cc0f3a7f4b 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestInstaller.h @@ -1,126 +1,126 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -#include -#include - -namespace AppInstaller::Manifest -{ - using namespace std::string_view_literals; - - // Token specified in installer args will be replaced by proper value. - static constexpr std::string_view ARG_TOKEN_LOGPATH = ""sv; - static constexpr std::string_view ARG_TOKEN_INSTALLPATH = ""sv; - - struct ManifestInstaller - { - using string_t = Utility::NormalizedString; - - AppInstaller::Utility::Architecture Arch = AppInstaller::Utility::Architecture::Unknown; - - string_t Url; - - std::vector Sha256; - - // Optional. Only used by appx/msix type. If provided, Appinstaller will - // validate appx/msix signature and perform streaming install. - std::vector SignatureSha256; - - // Store Product Id - string_t ProductId; - - string_t Locale; - - std::vector Platform; - - string_t MinOSVersion; - - // If present, has more precedence than root - InstallerTypeEnum BaseInstallerType = InstallerTypeEnum::Unknown; - - InstallerTypeEnum NestedInstallerType = InstallerTypeEnum::Unknown; - - InstallerTypeEnum EffectiveInstallerType() const - { - return IsArchiveType(BaseInstallerType) ? NestedInstallerType : BaseInstallerType; - } - - std::vector NestedInstallerFiles; - - ScopeEnum Scope = ScopeEnum::Unknown; - - std::vector InstallModes; - - // If present, has more precedence than root - std::map Switches; - - std::vector InstallerSuccessCodes; - - struct ExpectedReturnCodeInfo - { - ExpectedReturnCodeEnum ReturnResponseEnum = ExpectedReturnCodeEnum::Unknown; - string_t ReturnResponseUrl; - }; - - std::map ExpectedReturnCodes; - - UpdateBehaviorEnum UpdateBehavior = UpdateBehaviorEnum::Install; - - RepairBehaviorEnum RepairBehavior = RepairBehaviorEnum::Unknown; - - std::vector Commands; - - std::vector Protocols; - - std::vector FileExtensions; - - // Package family name for MSIX packaged installers. - string_t PackageFamilyName; - - // Product code for ARP (Add/Remove Programs) installers. - string_t ProductCode; - - // For msix only - std::vector Capabilities; - - // For msix only - std::vector RestrictedCapabilities; - - DependencyList Dependencies; - - bool InstallerAbortsTerminal = false; - - string_t ReleaseDate; - - bool InstallLocationRequired = false; - - bool RequireExplicitUpgrade = false; - - bool DisplayInstallWarnings = false; - - std::vector UnsupportedArguments; - - std::vector UnsupportedOSArchitectures; - - std::vector AppsAndFeaturesEntries; - - ElevationRequirementEnum ElevationRequirement = ElevationRequirementEnum::Unknown; - - MarketsInfo Markets; - - InstallationMetadataInfo InstallationMetadata; - - bool DownloadCommandProhibited = false; - - bool ArchiveBinariesDependOnPath = false; - - Authentication::AuthenticationInfo AuthInfo; - - std::vector DesiredStateConfiguration; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +#include +#include + +namespace AppInstaller::Manifest +{ + using namespace std::string_view_literals; + + // Token specified in installer args will be replaced by proper value. + static constexpr std::string_view ARG_TOKEN_LOGPATH = ""sv; + static constexpr std::string_view ARG_TOKEN_INSTALLPATH = ""sv; + + struct ManifestInstaller + { + using string_t = Utility::NormalizedString; + + AppInstaller::Utility::Architecture Arch = AppInstaller::Utility::Architecture::Unknown; + + string_t Url; + + std::vector Sha256; + + // Optional. Only used by appx/msix type. If provided, Appinstaller will + // validate appx/msix signature and perform streaming install. + std::vector SignatureSha256; + + // Store Product Id + string_t ProductId; + + string_t Locale; + + std::vector Platform; + + string_t MinOSVersion; + + // If present, has more precedence than root + InstallerTypeEnum BaseInstallerType = InstallerTypeEnum::Unknown; + + InstallerTypeEnum NestedInstallerType = InstallerTypeEnum::Unknown; + + InstallerTypeEnum EffectiveInstallerType() const + { + return IsArchiveType(BaseInstallerType) ? NestedInstallerType : BaseInstallerType; + } + + std::vector NestedInstallerFiles; + + ScopeEnum Scope = ScopeEnum::Unknown; + + std::vector InstallModes; + + // If present, has more precedence than root + std::map Switches; + + std::vector InstallerSuccessCodes; + + struct ExpectedReturnCodeInfo + { + ExpectedReturnCodeEnum ReturnResponseEnum = ExpectedReturnCodeEnum::Unknown; + string_t ReturnResponseUrl; + }; + + std::map ExpectedReturnCodes; + + UpdateBehaviorEnum UpdateBehavior = UpdateBehaviorEnum::Install; + + RepairBehaviorEnum RepairBehavior = RepairBehaviorEnum::Unknown; + + std::vector Commands; + + std::vector Protocols; + + std::vector FileExtensions; + + // Package family name for MSIX packaged installers. + string_t PackageFamilyName; + + // Product code for ARP (Add/Remove Programs) installers. + string_t ProductCode; + + // For msix only + std::vector Capabilities; + + // For msix only + std::vector RestrictedCapabilities; + + DependencyList Dependencies; + + bool InstallerAbortsTerminal = false; + + string_t ReleaseDate; + + bool InstallLocationRequired = false; + + bool RequireExplicitUpgrade = false; + + bool DisplayInstallWarnings = false; + + std::vector UnsupportedArguments; + + std::vector UnsupportedOSArchitectures; + + std::vector AppsAndFeaturesEntries; + + ElevationRequirementEnum ElevationRequirement = ElevationRequirementEnum::Unknown; + + MarketsInfo Markets; + + InstallationMetadataInfo InstallationMetadata; + + bool DownloadCommandProhibited = false; + + bool ArchiveBinariesDependOnPath = false; + + Authentication::AuthenticationInfo AuthInfo; + + std::vector DesiredStateConfiguration; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h index 9e070e047c..b2516451d0 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestLocalization.h @@ -1,151 +1,151 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include - -namespace AppInstaller::Manifest -{ - using string_t = Utility::NormalizedString; - - enum class Localization : size_t - { - Publisher, - PublisherUrl, - PublisherSupportUrl, - PrivacyUrl, - Author, - PackageName, - PackageUrl, - License, - LicenseUrl, - Copyright, - CopyrightUrl, - ShortDescription, - Description, - Tags, - Agreements, - Documentations, - ReleaseNotes, - ReleaseNotesUrl, - PurchaseUrl, - InstallationNotes, - Icons, - Max - }; - - struct Agreement - { - string_t Label; - string_t AgreementText; - string_t AgreementUrl; - }; - - struct Documentation - { - string_t DocumentLabel; - string_t DocumentUrl; - }; - - struct Icon - { - string_t Url; - IconFileTypeEnum FileType = IconFileTypeEnum::Unknown; - IconResolutionEnum Resolution = IconResolutionEnum::Unknown; - IconThemeEnum Theme = IconThemeEnum::Unknown; - std::vector Sha256; - }; - - namespace details - { - template - struct LocalizationMapping - { - using value_t = string_t; - }; - - template <> - struct LocalizationMapping - { - using value_t = std::vector; - }; - - template <> - struct LocalizationMapping - { - using value_t = std::vector; - }; - - template <> - struct LocalizationMapping - { - using value_t = std::vector; - }; - - template <> - struct LocalizationMapping - { - using value_t = std::vector; - }; - - // Used to deduce the LocalizationVariant type; making a variant that includes std::monostate and all LocalizationMapping types. - template - inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } - - // Holds data of any type listed in a LocalizationMapping. - using LocalizationVariant = decltype(Deduce(std::make_index_sequence(Localization::Max)>())); - - // Gets the index into the variant for the given Localization. - constexpr inline size_t LocalizationIndex(Localization l) { return static_cast(l) + 1; } - } - - struct ManifestLocalization - { - string_t Locale; - - // Adds a value to the Localization data, or overwrites an existing entry. - template - void Add(typename details::LocalizationMapping::value_t&& v) - { - m_data[L].emplace(std::forward::value_t>(v)); - } - template - void Add(const typename details::LocalizationMapping::value_t& v) - { - m_data[L].emplace(v); - } - - // Return a value indicating whether the given localization type exists. - bool Contains(Localization l) const { return (m_data.find(l) != m_data.end()); } - - // Gets the localization value if exists, otherwise empty for easier access - template - typename details::LocalizationMapping::value_t Get() const - { - auto itr = m_data.find(L); - if (itr == m_data.end()) - { - return {}; - } - else - { - return std::get(itr->second); - } - } - - void ReplaceOrMergeWith(const ManifestLocalization& other) - { - for (auto const& entry : other.m_data) - { - this->m_data[entry.first] = entry.second; - } - - this->Locale = other.Locale; - } - - private: - std::map m_data; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include + +namespace AppInstaller::Manifest +{ + using string_t = Utility::NormalizedString; + + enum class Localization : size_t + { + Publisher, + PublisherUrl, + PublisherSupportUrl, + PrivacyUrl, + Author, + PackageName, + PackageUrl, + License, + LicenseUrl, + Copyright, + CopyrightUrl, + ShortDescription, + Description, + Tags, + Agreements, + Documentations, + ReleaseNotes, + ReleaseNotesUrl, + PurchaseUrl, + InstallationNotes, + Icons, + Max + }; + + struct Agreement + { + string_t Label; + string_t AgreementText; + string_t AgreementUrl; + }; + + struct Documentation + { + string_t DocumentLabel; + string_t DocumentUrl; + }; + + struct Icon + { + string_t Url; + IconFileTypeEnum FileType = IconFileTypeEnum::Unknown; + IconResolutionEnum Resolution = IconResolutionEnum::Unknown; + IconThemeEnum Theme = IconThemeEnum::Unknown; + std::vector Sha256; + }; + + namespace details + { + template + struct LocalizationMapping + { + using value_t = string_t; + }; + + template <> + struct LocalizationMapping + { + using value_t = std::vector; + }; + + template <> + struct LocalizationMapping + { + using value_t = std::vector; + }; + + template <> + struct LocalizationMapping + { + using value_t = std::vector; + }; + + template <> + struct LocalizationMapping + { + using value_t = std::vector; + }; + + // Used to deduce the LocalizationVariant type; making a variant that includes std::monostate and all LocalizationMapping types. + template + inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } + + // Holds data of any type listed in a LocalizationMapping. + using LocalizationVariant = decltype(Deduce(std::make_index_sequence(Localization::Max)>())); + + // Gets the index into the variant for the given Localization. + constexpr inline size_t LocalizationIndex(Localization l) { return static_cast(l) + 1; } + } + + struct ManifestLocalization + { + string_t Locale; + + // Adds a value to the Localization data, or overwrites an existing entry. + template + void Add(typename details::LocalizationMapping::value_t&& v) + { + m_data[L].emplace(std::forward::value_t>(v)); + } + template + void Add(const typename details::LocalizationMapping::value_t& v) + { + m_data[L].emplace(v); + } + + // Return a value indicating whether the given localization type exists. + bool Contains(Localization l) const { return (m_data.find(l) != m_data.end()); } + + // Gets the localization value if exists, otherwise empty for easier access + template + typename details::LocalizationMapping::value_t Get() const + { + auto itr = m_data.find(L); + if (itr == m_data.end()) + { + return {}; + } + else + { + return std::get(itr->second); + } + } + + void ReplaceOrMergeWith(const ManifestLocalization& other) + { + for (auto const& entry : other.m_data) + { + this->m_data[entry.first] = entry.second; + } + + this->Locale = other.Locale; + } + + private: + std::map m_data; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h index a170caf4bd..682e5433d9 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestSchemaValidation.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ManifestCommon.h" -#include "ManifestValidation.h" - -#include - -namespace AppInstaller::Manifest::YamlParser -{ - // Forward declarations - struct YamlManifestInfo; - - // Load manifest schema as parsed json doc - Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType); - - // Validate a list of individual manifests against schema - std::vector ValidateAgainstSchema( - const std::vector& manifestList, - const ManifestVer& manifestVersion); - - // Validate the schema header of a list of manifests - std::vector ValidateYamlManifestsSchemaHeader( - const std::vector& manifestList, - const ManifestVer& manifestVersion, - bool treatErrorAsWarning = true); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ManifestCommon.h" +#include "ManifestValidation.h" + +#include + +namespace AppInstaller::Manifest::YamlParser +{ + // Forward declarations + struct YamlManifestInfo; + + // Load manifest schema as parsed json doc + Json::Value LoadSchemaDoc(const ManifestVer& manifestVersion, ManifestTypeEnum manifestType); + + // Validate a list of individual manifests against schema + std::vector ValidateAgainstSchema( + const std::vector& manifestList, + const ManifestVer& manifestVersion); + + // Validate the schema header of a list of manifests + std::vector ValidateYamlManifestsSchemaHeader( + const std::vector& manifestList, + const ManifestVer& manifestVersion, + bool treatErrorAsWarning = true); +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h index 55eef941ac..e530cbef78 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestValidation.h @@ -1,226 +1,226 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include -#include - -namespace YAML { class Node; } - -namespace AppInstaller::Manifest -{ - namespace ManifestError - { - - const char* const ErrorMessagePrefix = "Manifest Error: "; - const char* const WarningMessagePrefix = "Manifest Warning: "; - - // Each StringId below must have a corresponding entry in: - // - ErrorIdToMessageMap in ManifestValidation.cpp (the human-readable message) - // - ManifestErrorId enum in src/WinGetUtilInterop/Common/ManifestErrorId.cs (the C# interop enum) - WINGET_DEFINE_RESOURCE_STRINGID(ApproximateVersionNotAllowed); - WINGET_DEFINE_RESOURCE_STRINGID(ArpValidationError); - WINGET_DEFINE_RESOURCE_STRINGID(ArpVersionOverlapWithIndex); - WINGET_DEFINE_RESOURCE_STRINGID(ArpVersionValidationInternalError); - WINGET_DEFINE_RESOURCE_STRINGID(BlockedMsiProperty); - WINGET_DEFINE_RESOURCE_STRINGID(BothAllowedAndExcludedMarketsDefined); - WINGET_DEFINE_RESOURCE_STRINGID(ContainsNetworkAddress); - WINGET_DEFINE_RESOURCE_STRINGID(DuplicatePortableCommandAlias); - WINGET_DEFINE_RESOURCE_STRINGID(DuplicateRelativeFilePath); - WINGET_DEFINE_RESOURCE_STRINGID(DuplicateMultiFileManifestLocale); - WINGET_DEFINE_RESOURCE_STRINGID(DuplicateMultiFileManifestType); - WINGET_DEFINE_RESOURCE_STRINGID(DuplicateInstallerEntry); - WINGET_DEFINE_RESOURCE_STRINGID(DuplicateInstallerHash); - WINGET_DEFINE_RESOURCE_STRINGID(DuplicateReturnCodeEntry); - WINGET_DEFINE_RESOURCE_STRINGID(ExceededAppsAndFeaturesEntryLimit); - WINGET_DEFINE_RESOURCE_STRINGID(ExceededCommandsLimit); - WINGET_DEFINE_RESOURCE_STRINGID(ExceededNestedInstallerFilesLimit); - WINGET_DEFINE_RESOURCE_STRINGID(ExeInstallerMissingSilentSwitches); - WINGET_DEFINE_RESOURCE_STRINGID(FieldDuplicate); - WINGET_DEFINE_RESOURCE_STRINGID(FieldFailedToProcess); - WINGET_DEFINE_RESOURCE_STRINGID(FieldIsNotPascalCase); - WINGET_DEFINE_RESOURCE_STRINGID(FieldNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(FieldRequireVerifiedPublisher); - WINGET_DEFINE_RESOURCE_STRINGID(FieldUnknown); - WINGET_DEFINE_RESOURCE_STRINGID(FieldValueNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(FoundDependencyLoop); - WINGET_DEFINE_RESOURCE_STRINGID(IncompleteMultiFileManifest); - WINGET_DEFINE_RESOURCE_STRINGID(InconsistentInstallerHash); - WINGET_DEFINE_RESOURCE_STRINGID(InconsistentMultiFileManifestDefaultLocale); - WINGET_DEFINE_RESOURCE_STRINGID(InconsistentMultiFileManifestFieldValue); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedToProcess); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerMsixInconsistencies); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotSupportPackageFamilyName); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotSupportProductCode); - WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotWriteAppsAndFeaturesEntry); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidBcp47Value); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidFieldValue); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidMsiSwitches); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidRootNode); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidWindowsFeatureName); - WINGET_DEFINE_RESOURCE_STRINGID(MissingManifestDependenciesNode); - WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); - WINGET_DEFINE_RESOURCE_STRINGID(MultiManifestPackageHasDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(NoSuitableMinVersionDependency); - WINGET_DEFINE_RESOURCE_STRINGID(NoSupportedPlatforms); - WINGET_DEFINE_RESOURCE_STRINGID(OptionalFieldMissing); - WINGET_DEFINE_RESOURCE_STRINGID(PortableCommandAliasEscapesDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(RelativeFilePathEscapesDirectory); - WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldEmpty); - WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldMissing); - WINGET_DEFINE_RESOURCE_STRINGID(SchemaError); - WINGET_DEFINE_RESOURCE_STRINGID(ScopeNotSupported); - WINGET_DEFINE_RESOURCE_STRINGID(ShadowManifestNotAllowed); - WINGET_DEFINE_RESOURCE_STRINGID(SingleManifestPackageHasDependencies); - WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedMultiFileManifestType); - WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderNotFound); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidSchemaHeader); - WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderManifestTypeMismatch); - WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderManifestVersionMismatch); - WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderUrlPatternMismatch); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidPortableFiletype); - WINGET_DEFINE_RESOURCE_STRINGID(InvalidFontFiletype); - } - - struct ValidationError - { - enum class Level - { - Warning, - Error - }; - - AppInstaller::StringResource::StringId Message; - std::string Context = {}; - std::string Value = {}; - // line and column are 1 based - size_t Line = 0; - size_t Column = 0; - Level ErrorLevel = Level::Error; - std::string FileName; - - ValidationError(AppInstaller::StringResource::StringId message) : - Message(std::move(message)) {} - - ValidationError(AppInstaller::StringResource::StringId message, Level level) : - Message(std::move(message)), ErrorLevel(level) {} - - ValidationError(AppInstaller::StringResource::StringId message, std::string context) : - Message(std::move(message)), Context(std::move(context)) {} - - ValidationError(AppInstaller::StringResource::StringId message, std::string context, Level level) : - Message(std::move(message)), Context(std::move(context)), ErrorLevel(level) {} - - ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string_view value) : - Message(std::move(message)), Context(std::move(context)), Value(value) {} - - ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value) : - Message(std::move(message)), Context(std::move(context)), Value(std::move(value)) {} - - ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, Level level) : - Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), ErrorLevel(level) {} - - ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column) : - Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), Line(line), Column(column) {} - - ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column, Level level) : - Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), Line(line), Column(column), ErrorLevel(level) {} - - std::string GetErrorMessage() const; - - static ValidationError MessageWithFile(AppInstaller::StringResource::StringId message, std::string file) - { - ValidationError error{ message }; - error.FileName = file; - return error; - } - - static ValidationError MessageContextWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string file) - { - ValidationError error{ message, context }; - error.FileName = file; - return error; - } - - static ValidationError MessageContextValueWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string value, std::string file) - { - ValidationError error{ message, context, value }; - error.FileName = file; - return error; - } - - static ValidationError MessageLevelWithFile(AppInstaller::StringResource::StringId message, Level level, std::string file) - { - ValidationError error{ message, level }; - error.FileName = file; - return error; - } - - static ValidationError MessageContextValueLineLevelWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column , Level level , std::string file) - { - ValidationError error{ message, context, value, line, column, level }; - error.FileName = file; - return error; - } - }; - - struct ManifestException : public wil::ResultException - { - ManifestException(std::vector&& errors = {}, HRESULT hr = APPINSTALLER_CLI_ERROR_MANIFEST_FAILED) : - wil::ResultException(hr), m_errors(std::move(errors)) - { - auto p = [&](ValidationError const& e) { - return e.ErrorLevel == ValidationError::Level::Error; - }; - - m_warningOnly = !m_errors.empty() && std::find_if(m_errors.begin(), m_errors.end(), p) == m_errors.end(); - } - - ManifestException(HRESULT hr) : ManifestException({}, hr) {} - - // Error message without wil diagnostic info - const std::string& GetManifestErrorMessage() const noexcept; - - bool IsWarningOnly() const noexcept - { - return m_warningOnly; - } - - const char* what() const noexcept override - { - if (m_whatMessage.empty()) - { - m_whatMessage = ResultException::what(); - - if (!m_errors.empty()) - { - m_whatMessage += GetManifestErrorMessage(); - } - } - return m_whatMessage.c_str(); - } - - const std::vector& Errors() const { return m_errors; } - - // Error information as a JSON string. The JSON object contains: - // "fullMessage": the same string as GetManifestErrorMessage() - // "isSyntaxError": true if this is a YAML syntax error (no structured ValidationErrors) - // "errors": array of objects with fields: errorId, message, context, value, line, column, level, file - std::string GetManifestErrorJson() const noexcept; - - private: - std::vector m_errors; - mutable std::string m_whatMessage; - mutable std::string m_manifestErrorMessage; - bool m_warningOnly; - }; - - // fullValidation: bool to set if manifest validation should perform extra validation that is not required for reading a manifest. - std::vector ValidateManifest(const Manifest& manifest, const ManifestValidateOption& options); - std::vector ValidateManifestLocalization(const ManifestLocalization& localization, bool treatErrorAsWarning = false); - std::vector ValidateManifestInstallers(const Manifest& manifest, bool treatErrorAsWarning = false); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include +#include + +namespace YAML { class Node; } + +namespace AppInstaller::Manifest +{ + namespace ManifestError + { + + const char* const ErrorMessagePrefix = "Manifest Error: "; + const char* const WarningMessagePrefix = "Manifest Warning: "; + + // Each StringId below must have a corresponding entry in: + // - ErrorIdToMessageMap in ManifestValidation.cpp (the human-readable message) + // - ManifestErrorId enum in src/WinGetUtilInterop/Common/ManifestErrorId.cs (the C# interop enum) + WINGET_DEFINE_RESOURCE_STRINGID(ApproximateVersionNotAllowed); + WINGET_DEFINE_RESOURCE_STRINGID(ArpValidationError); + WINGET_DEFINE_RESOURCE_STRINGID(ArpVersionOverlapWithIndex); + WINGET_DEFINE_RESOURCE_STRINGID(ArpVersionValidationInternalError); + WINGET_DEFINE_RESOURCE_STRINGID(BlockedMsiProperty); + WINGET_DEFINE_RESOURCE_STRINGID(BothAllowedAndExcludedMarketsDefined); + WINGET_DEFINE_RESOURCE_STRINGID(ContainsNetworkAddress); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicatePortableCommandAlias); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateRelativeFilePath); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateMultiFileManifestLocale); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateMultiFileManifestType); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateInstallerEntry); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateInstallerHash); + WINGET_DEFINE_RESOURCE_STRINGID(DuplicateReturnCodeEntry); + WINGET_DEFINE_RESOURCE_STRINGID(ExceededAppsAndFeaturesEntryLimit); + WINGET_DEFINE_RESOURCE_STRINGID(ExceededCommandsLimit); + WINGET_DEFINE_RESOURCE_STRINGID(ExceededNestedInstallerFilesLimit); + WINGET_DEFINE_RESOURCE_STRINGID(ExeInstallerMissingSilentSwitches); + WINGET_DEFINE_RESOURCE_STRINGID(FieldDuplicate); + WINGET_DEFINE_RESOURCE_STRINGID(FieldFailedToProcess); + WINGET_DEFINE_RESOURCE_STRINGID(FieldIsNotPascalCase); + WINGET_DEFINE_RESOURCE_STRINGID(FieldNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(FieldRequireVerifiedPublisher); + WINGET_DEFINE_RESOURCE_STRINGID(FieldUnknown); + WINGET_DEFINE_RESOURCE_STRINGID(FieldValueNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(FoundDependencyLoop); + WINGET_DEFINE_RESOURCE_STRINGID(IncompleteMultiFileManifest); + WINGET_DEFINE_RESOURCE_STRINGID(InconsistentInstallerHash); + WINGET_DEFINE_RESOURCE_STRINGID(InconsistentMultiFileManifestDefaultLocale); + WINGET_DEFINE_RESOURCE_STRINGID(InconsistentMultiFileManifestFieldValue); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerFailedToProcess); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerMsixInconsistencies); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotSupportPackageFamilyName); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotSupportProductCode); + WINGET_DEFINE_RESOURCE_STRINGID(InstallerTypeDoesNotWriteAppsAndFeaturesEntry); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidBcp47Value); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidFieldValue); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidMsiSwitches); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidRootNode); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidWindowsFeatureName); + WINGET_DEFINE_RESOURCE_STRINGID(MissingManifestDependenciesNode); + WINGET_DEFINE_RESOURCE_STRINGID(MsixSignatureHashFailed); + WINGET_DEFINE_RESOURCE_STRINGID(MultiManifestPackageHasDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(NoSuitableMinVersionDependency); + WINGET_DEFINE_RESOURCE_STRINGID(NoSupportedPlatforms); + WINGET_DEFINE_RESOURCE_STRINGID(OptionalFieldMissing); + WINGET_DEFINE_RESOURCE_STRINGID(PortableCommandAliasEscapesDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(RelativeFilePathEscapesDirectory); + WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldEmpty); + WINGET_DEFINE_RESOURCE_STRINGID(RequiredFieldMissing); + WINGET_DEFINE_RESOURCE_STRINGID(SchemaError); + WINGET_DEFINE_RESOURCE_STRINGID(ScopeNotSupported); + WINGET_DEFINE_RESOURCE_STRINGID(ShadowManifestNotAllowed); + WINGET_DEFINE_RESOURCE_STRINGID(SingleManifestPackageHasDependencies); + WINGET_DEFINE_RESOURCE_STRINGID(UnsupportedMultiFileManifestType); + WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderNotFound); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidSchemaHeader); + WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderManifestTypeMismatch); + WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderManifestVersionMismatch); + WINGET_DEFINE_RESOURCE_STRINGID(SchemaHeaderUrlPatternMismatch); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidPortableFiletype); + WINGET_DEFINE_RESOURCE_STRINGID(InvalidFontFiletype); + } + + struct ValidationError + { + enum class Level + { + Warning, + Error + }; + + AppInstaller::StringResource::StringId Message; + std::string Context = {}; + std::string Value = {}; + // line and column are 1 based + size_t Line = 0; + size_t Column = 0; + Level ErrorLevel = Level::Error; + std::string FileName; + + ValidationError(AppInstaller::StringResource::StringId message) : + Message(std::move(message)) {} + + ValidationError(AppInstaller::StringResource::StringId message, Level level) : + Message(std::move(message)), ErrorLevel(level) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context) : + Message(std::move(message)), Context(std::move(context)) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context, Level level) : + Message(std::move(message)), Context(std::move(context)), ErrorLevel(level) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string_view value) : + Message(std::move(message)), Context(std::move(context)), Value(value) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, Level level) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), ErrorLevel(level) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), Line(line), Column(column) {} + + ValidationError(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column, Level level) : + Message(std::move(message)), Context(std::move(context)), Value(std::move(value)), Line(line), Column(column), ErrorLevel(level) {} + + std::string GetErrorMessage() const; + + static ValidationError MessageWithFile(AppInstaller::StringResource::StringId message, std::string file) + { + ValidationError error{ message }; + error.FileName = file; + return error; + } + + static ValidationError MessageContextWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string file) + { + ValidationError error{ message, context }; + error.FileName = file; + return error; + } + + static ValidationError MessageContextValueWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string value, std::string file) + { + ValidationError error{ message, context, value }; + error.FileName = file; + return error; + } + + static ValidationError MessageLevelWithFile(AppInstaller::StringResource::StringId message, Level level, std::string file) + { + ValidationError error{ message, level }; + error.FileName = file; + return error; + } + + static ValidationError MessageContextValueLineLevelWithFile(AppInstaller::StringResource::StringId message, std::string context, std::string value, size_t line, size_t column , Level level , std::string file) + { + ValidationError error{ message, context, value, line, column, level }; + error.FileName = file; + return error; + } + }; + + struct ManifestException : public wil::ResultException + { + ManifestException(std::vector&& errors = {}, HRESULT hr = APPINSTALLER_CLI_ERROR_MANIFEST_FAILED) : + wil::ResultException(hr), m_errors(std::move(errors)) + { + auto p = [&](ValidationError const& e) { + return e.ErrorLevel == ValidationError::Level::Error; + }; + + m_warningOnly = !m_errors.empty() && std::find_if(m_errors.begin(), m_errors.end(), p) == m_errors.end(); + } + + ManifestException(HRESULT hr) : ManifestException({}, hr) {} + + // Error message without wil diagnostic info + const std::string& GetManifestErrorMessage() const noexcept; + + bool IsWarningOnly() const noexcept + { + return m_warningOnly; + } + + const char* what() const noexcept override + { + if (m_whatMessage.empty()) + { + m_whatMessage = ResultException::what(); + + if (!m_errors.empty()) + { + m_whatMessage += GetManifestErrorMessage(); + } + } + return m_whatMessage.c_str(); + } + + const std::vector& Errors() const { return m_errors; } + + // Error information as a JSON string. The JSON object contains: + // "fullMessage": the same string as GetManifestErrorMessage() + // "isSyntaxError": true if this is a YAML syntax error (no structured ValidationErrors) + // "errors": array of objects with fields: errorId, message, context, value, line, column, level, file + std::string GetManifestErrorJson() const noexcept; + + private: + std::vector m_errors; + mutable std::string m_whatMessage; + mutable std::string m_manifestErrorMessage; + bool m_warningOnly; + }; + + // fullValidation: bool to set if manifest validation should perform extra validation that is not required for reading a manifest. + std::vector ValidateManifest(const Manifest& manifest, const ManifestValidateOption& options); + std::vector ValidateManifestLocalization(const ManifestLocalization& localization, bool treatErrorAsWarning = false); + std::vector ValidateManifestInstallers(const Manifest& manifest, bool treatErrorAsWarning = false); +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h index 509b3bf86b..2674949047 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlParser.h @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - -namespace AppInstaller::Manifest::YamlParser -{ - struct YamlManifestInfo - { - // Root node of a yaml manifest file - YAML::Node Root; - - // File name of the manifest file if applicable for error reporting - std::string FileName; - - // Schema header string found in the manifest file - YAML::DocumentSchemaHeader DocumentSchemaHeader; - - // The SHA256 hash of the stream - Utility::SHA256::HashBuffer StreamSha256; - - ManifestTypeEnum ManifestType = ManifestTypeEnum::Preview; - }; - - Manifest CreateFromPath( - const std::filesystem::path& inputPath, - ManifestValidateOption validateOption = {}, - const std::filesystem::path& mergedManifestPath = {}); - - Manifest Create( - const std::string& input, - ManifestValidateOption validateOption = {}, - const std::filesystem::path& mergedManifestPath = {}); - - Manifest ParseManifest( - std::vector& input, - ManifestValidateOption validateOption = {}, - const std::filesystem::path& mergedManifestPath = {}); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + +namespace AppInstaller::Manifest::YamlParser +{ + struct YamlManifestInfo + { + // Root node of a yaml manifest file + YAML::Node Root; + + // File name of the manifest file if applicable for error reporting + std::string FileName; + + // Schema header string found in the manifest file + YAML::DocumentSchemaHeader DocumentSchemaHeader; + + // The SHA256 hash of the stream + Utility::SHA256::HashBuffer StreamSha256; + + ManifestTypeEnum ManifestType = ManifestTypeEnum::Preview; + }; + + Manifest CreateFromPath( + const std::filesystem::path& inputPath, + ManifestValidateOption validateOption = {}, + const std::filesystem::path& mergedManifestPath = {}); + + Manifest Create( + const std::string& input, + ManifestValidateOption validateOption = {}, + const std::filesystem::path& mergedManifestPath = {}); + + Manifest ParseManifest( + std::vector& input, + ManifestValidateOption validateOption = {}, + const std::filesystem::path& mergedManifestPath = {}); +} diff --git a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h index f7e32c5592..2844e5c294 100644 --- a/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h +++ b/src/AppInstallerCommonCore/Public/winget/ManifestYamlPopulator.h @@ -1,144 +1,144 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Manifest -{ - // Add here new manifest pointer types. - using VariantManifestPtr = std::variant< - Agreement*, - AppsAndFeaturesEntry*, - Dependency*, - DependencyList*, - DesiredStateConfigurationContainerInfo*, - DesiredStateConfigurationResourceInfo*, - Documentation*, - ExpectedReturnCode*, - Icon*, - InstallationMetadataInfo*, - InstalledFile*, - Manifest*, - ManifestInstaller*, - ManifestLocalization*, - MarketsInfo*, - NestedInstallerFile*, - AppInstaller::Authentication::AuthenticationInfo*, - AppInstaller::Authentication::MicrosoftEntraIdAuthenticationInfo*, - std::map*, - std::vector* - >; - - struct ManifestYamlPopulator - { - static std::vector PopulateManifest( - YAML::Node& rootNode, - Manifest& manifest, - const ManifestVer& manifestVersion, - ManifestValidateOption validateOption, - const std::optional& shadowNode); - - private: - - ManifestYamlPopulator(YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, ManifestValidateOption validateOption); - - std::reference_wrapper m_rootNode; - std::reference_wrapper m_manifest; - std::reference_wrapper m_manifestVersion; - bool m_isMergedManifest = false; - ManifestValidateOption m_validateOption; - - // Struct mapping a manifest field to its population logic - struct FieldProcessInfo - { - FieldProcessInfo(std::string name, std::function(const YAML::Node&, const VariantManifestPtr& v)> func, bool requireVerifiedPublisher = false) : - Name(std::move(name)), ProcessFunc(func), RequireVerifiedPublisher(requireVerifiedPublisher) {} - - std::string Name; - std::function(const YAML::Node&, const VariantManifestPtr& v)> ProcessFunc; - bool RequireVerifiedPublisher = false; - }; - - std::vector RootFieldInfos; - std::vector InstallerFieldInfos; - std::vector SwitchesFieldInfos; - std::vector ExpectedReturnCodesFieldInfos; - std::vector DependenciesFieldInfos; - std::vector PackageDependenciesFieldInfos; - std::vector LocalizationFieldInfos; - std::vector AgreementFieldInfos; - std::vector MarketsFieldInfos; - std::vector AppsAndFeaturesEntryFieldInfos; - std::vector DocumentationFieldInfos; - std::vector IconFieldInfos; - std::vector NestedInstallerFileFieldInfos; - std::vector InstallationMetadataFieldInfos; - std::vector InstallationMetadataFilesFieldInfos; - std::vector AuthenticationFieldInfos; - std::vector MicrosoftEntraIdAuthenticationInfoFieldInfos; - std::vector DesiredStateConfigurationFieldInfos; - std::vector DesiredStateConfigurationPowerShellModuleFieldInfos; - std::vector DesiredStateConfigurationPowerShellResourceFieldInfos; - std::vector DesiredStateConfigurationDSCv3FieldInfos; - std::vector DesiredStateConfigurationDSCv3ResourceFieldInfos; - - // Cache of Installers node and Localization node - YAML::Node const* m_p_installersNode = nullptr; - YAML::Node const* m_p_localizationsNode = nullptr; - - std::vector GetRootFieldProcessInfo(); - std::vector GetInstallerFieldProcessInfo(bool forRootFields = false); - std::vector GetSwitchesFieldProcessInfo(); - std::vector GetExpectedReturnCodesFieldProcessInfo(); - std::vector GetDependenciesFieldProcessInfo(); - std::vector GetPackageDependenciesFieldProcessInfo(); - std::vector GetLocalizationFieldProcessInfo(bool forRootFields = false); - std::vector GetAgreementFieldProcessInfo(); - std::vector GetMarketsFieldProcessInfo(); - std::vector GetAppsAndFeaturesEntryFieldProcessInfo(); - std::vector GetDocumentationFieldProcessInfo(); - std::vector GetIconFieldProcessInfo(); - std::vector GetNestedInstallerFileFieldProcessInfo(); - std::vector GetInstallationMetadataFieldProcessInfo(); - std::vector GetInstallationMetadataFilesFieldProcessInfo(); - std::vector GetAuthenticationFieldInfos(); - std::vector GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); - std::vector GetDesiredStateConfigurationFieldInfos(); - std::vector GetDesiredStateConfigurationPowerShellModuleFieldInfos(); - std::vector GetDesiredStateConfigurationPowerShellResourceFieldInfos(); - std::vector GetDesiredStateConfigurationDSCv3FieldInfos(); - std::vector GetDesiredStateConfigurationDSCv3ResourceFieldInfos(); - - // Shadow - std::vector GetShadowRootFieldProcessInfo(); - std::vector GetShadowLocalizationFieldProcessInfo(); - - // This method takes YAML root node and list of manifest field info. - // Yaml lib does not support case-insensitive search and it allows duplicate keys. If duplicate keys exist, - // the value is undefined. So in this method, we will iterate through the node map and process each individual - // pair ourselves. This also helps with generating aggregated error rather than throwing on first failure. - std::vector ValidateAndProcessFields( - const YAML::Node& rootNode, - const std::vector& fieldInfos, - const VariantManifestPtr& v); - - std::vector ProcessPackageDependenciesNode(const YAML::Node& rootNode, DependencyList* dependencyList); - std::vector ProcessAgreementsNode(const YAML::Node& agreementsNode, ManifestLocalization* localization); - std::vector ProcessMarketsNode(const YAML::Node& marketsNode, AppInstaller::Manifest::ManifestInstaller* installer); - std::vector ProcessAppsAndFeaturesEntriesNode(const YAML::Node& appsAndFeaturesEntriesNode, AppInstaller::Manifest::ManifestInstaller* installer); - std::vector ProcessExpectedReturnCodesNode(const YAML::Node& returnCodesNode, AppInstaller::Manifest::ManifestInstaller* installer); - std::vector ProcessDocumentationsNode(const YAML::Node& documentationsNode, ManifestLocalization* localization); - std::vector ProcessIconsNode(const YAML::Node& iconsNode, ManifestLocalization* localization); - std::vector ProcessNestedInstallerFilesNode(const YAML::Node& nestedInstallerFilesNode, AppInstaller::Manifest::ManifestInstaller* installer); - std::vector ProcessInstallationMetadataFilesNode(const YAML::Node& installedFilesNode, InstallationMetadataInfo* installationMetadata); - std::vector ProcessShadowLocalizationNode(const YAML::Node& localizationNode, Manifest* manifest); - std::vector ProcessDSC_PowerShellModuleNode(const YAML::Node& node, std::vector* containers); - std::vector ProcessDSC_PowerShellResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container); - std::vector ProcessDSCv3ResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container); - - std::vector PopulateManifestInternal(); - std::vector InsertShadow(const YAML::Node& shadowNode); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Manifest +{ + // Add here new manifest pointer types. + using VariantManifestPtr = std::variant< + Agreement*, + AppsAndFeaturesEntry*, + Dependency*, + DependencyList*, + DesiredStateConfigurationContainerInfo*, + DesiredStateConfigurationResourceInfo*, + Documentation*, + ExpectedReturnCode*, + Icon*, + InstallationMetadataInfo*, + InstalledFile*, + Manifest*, + ManifestInstaller*, + ManifestLocalization*, + MarketsInfo*, + NestedInstallerFile*, + AppInstaller::Authentication::AuthenticationInfo*, + AppInstaller::Authentication::MicrosoftEntraIdAuthenticationInfo*, + std::map*, + std::vector* + >; + + struct ManifestYamlPopulator + { + static std::vector PopulateManifest( + YAML::Node& rootNode, + Manifest& manifest, + const ManifestVer& manifestVersion, + ManifestValidateOption validateOption, + const std::optional& shadowNode); + + private: + + ManifestYamlPopulator(YAML::Node& rootNode, Manifest& manifest, const ManifestVer& manifestVersion, ManifestValidateOption validateOption); + + std::reference_wrapper m_rootNode; + std::reference_wrapper m_manifest; + std::reference_wrapper m_manifestVersion; + bool m_isMergedManifest = false; + ManifestValidateOption m_validateOption; + + // Struct mapping a manifest field to its population logic + struct FieldProcessInfo + { + FieldProcessInfo(std::string name, std::function(const YAML::Node&, const VariantManifestPtr& v)> func, bool requireVerifiedPublisher = false) : + Name(std::move(name)), ProcessFunc(func), RequireVerifiedPublisher(requireVerifiedPublisher) {} + + std::string Name; + std::function(const YAML::Node&, const VariantManifestPtr& v)> ProcessFunc; + bool RequireVerifiedPublisher = false; + }; + + std::vector RootFieldInfos; + std::vector InstallerFieldInfos; + std::vector SwitchesFieldInfos; + std::vector ExpectedReturnCodesFieldInfos; + std::vector DependenciesFieldInfos; + std::vector PackageDependenciesFieldInfos; + std::vector LocalizationFieldInfos; + std::vector AgreementFieldInfos; + std::vector MarketsFieldInfos; + std::vector AppsAndFeaturesEntryFieldInfos; + std::vector DocumentationFieldInfos; + std::vector IconFieldInfos; + std::vector NestedInstallerFileFieldInfos; + std::vector InstallationMetadataFieldInfos; + std::vector InstallationMetadataFilesFieldInfos; + std::vector AuthenticationFieldInfos; + std::vector MicrosoftEntraIdAuthenticationInfoFieldInfos; + std::vector DesiredStateConfigurationFieldInfos; + std::vector DesiredStateConfigurationPowerShellModuleFieldInfos; + std::vector DesiredStateConfigurationPowerShellResourceFieldInfos; + std::vector DesiredStateConfigurationDSCv3FieldInfos; + std::vector DesiredStateConfigurationDSCv3ResourceFieldInfos; + + // Cache of Installers node and Localization node + YAML::Node const* m_p_installersNode = nullptr; + YAML::Node const* m_p_localizationsNode = nullptr; + + std::vector GetRootFieldProcessInfo(); + std::vector GetInstallerFieldProcessInfo(bool forRootFields = false); + std::vector GetSwitchesFieldProcessInfo(); + std::vector GetExpectedReturnCodesFieldProcessInfo(); + std::vector GetDependenciesFieldProcessInfo(); + std::vector GetPackageDependenciesFieldProcessInfo(); + std::vector GetLocalizationFieldProcessInfo(bool forRootFields = false); + std::vector GetAgreementFieldProcessInfo(); + std::vector GetMarketsFieldProcessInfo(); + std::vector GetAppsAndFeaturesEntryFieldProcessInfo(); + std::vector GetDocumentationFieldProcessInfo(); + std::vector GetIconFieldProcessInfo(); + std::vector GetNestedInstallerFileFieldProcessInfo(); + std::vector GetInstallationMetadataFieldProcessInfo(); + std::vector GetInstallationMetadataFilesFieldProcessInfo(); + std::vector GetAuthenticationFieldInfos(); + std::vector GetMicrosoftEntraIdAuthenticationInfoFieldInfos(); + std::vector GetDesiredStateConfigurationFieldInfos(); + std::vector GetDesiredStateConfigurationPowerShellModuleFieldInfos(); + std::vector GetDesiredStateConfigurationPowerShellResourceFieldInfos(); + std::vector GetDesiredStateConfigurationDSCv3FieldInfos(); + std::vector GetDesiredStateConfigurationDSCv3ResourceFieldInfos(); + + // Shadow + std::vector GetShadowRootFieldProcessInfo(); + std::vector GetShadowLocalizationFieldProcessInfo(); + + // This method takes YAML root node and list of manifest field info. + // Yaml lib does not support case-insensitive search and it allows duplicate keys. If duplicate keys exist, + // the value is undefined. So in this method, we will iterate through the node map and process each individual + // pair ourselves. This also helps with generating aggregated error rather than throwing on first failure. + std::vector ValidateAndProcessFields( + const YAML::Node& rootNode, + const std::vector& fieldInfos, + const VariantManifestPtr& v); + + std::vector ProcessPackageDependenciesNode(const YAML::Node& rootNode, DependencyList* dependencyList); + std::vector ProcessAgreementsNode(const YAML::Node& agreementsNode, ManifestLocalization* localization); + std::vector ProcessMarketsNode(const YAML::Node& marketsNode, AppInstaller::Manifest::ManifestInstaller* installer); + std::vector ProcessAppsAndFeaturesEntriesNode(const YAML::Node& appsAndFeaturesEntriesNode, AppInstaller::Manifest::ManifestInstaller* installer); + std::vector ProcessExpectedReturnCodesNode(const YAML::Node& returnCodesNode, AppInstaller::Manifest::ManifestInstaller* installer); + std::vector ProcessDocumentationsNode(const YAML::Node& documentationsNode, ManifestLocalization* localization); + std::vector ProcessIconsNode(const YAML::Node& iconsNode, ManifestLocalization* localization); + std::vector ProcessNestedInstallerFilesNode(const YAML::Node& nestedInstallerFilesNode, AppInstaller::Manifest::ManifestInstaller* installer); + std::vector ProcessInstallationMetadataFilesNode(const YAML::Node& installedFilesNode, InstallationMetadataInfo* installationMetadata); + std::vector ProcessShadowLocalizationNode(const YAML::Node& localizationNode, Manifest* manifest); + std::vector ProcessDSC_PowerShellModuleNode(const YAML::Node& node, std::vector* containers); + std::vector ProcessDSC_PowerShellResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container); + std::vector ProcessDSCv3ResourcesNode(const YAML::Node& node, DesiredStateConfigurationContainerInfo* container); + + std::vector PopulateManifestInternal(); + std::vector InsertShadow(const YAML::Node& shadowNode); + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/MsiExecArguments.h b/src/AppInstallerCommonCore/Public/winget/MsiExecArguments.h index 07ea66d8ad..6d4a812d18 100644 --- a/src/AppInstallerCommonCore/Public/winget/MsiExecArguments.h +++ b/src/AppInstallerCommonCore/Public/winget/MsiExecArguments.h @@ -1,72 +1,72 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - - -// This file defines parsing of the command line arguments passed to msiexec.exe. -// -// Some packages require the UAC prompt for installing even on silent installs. This -// can be done with the MSI API using the INSTALLUILEVEL_UACONLY flag, but msiexec.exe -// does not provide a way to use it. So, we use the MSI API directly instead of -// through msiexec.exe. Since msiexec.exe does some parsing of command line arguments -// before handing off to the API, we replicate that parsing here. -// -// Since we care only about installation, we simplify the parsing by assuming that -// the command line has the form -// msiexec.exe /i (MSI file) [Other args...] - -namespace AppInstaller::Msi -{ - DEFINE_ENUM_FLAG_OPERATORS(INSTALLUILEVEL); - DEFINE_ENUM_FLAG_OPERATORS(INSTALLLOGMODE); - DEFINE_ENUM_FLAG_OPERATORS(INSTALLLOGATTRIBUTES); - - constexpr INSTALLLOGMODE DefaultLogMode = - INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING | INSTALLLOGMODE_INFO | - INSTALLLOGMODE_OUTOFDISKSPACE | INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA; - - // All but the four flags that always have to be set explicitly (Verbose, ExtraDebug, LogOnlyOnError, LogPerformance) - constexpr INSTALLLOGMODE AllLogMode = - INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING | INSTALLLOGMODE_USER | INSTALLLOGMODE_INFO | - INSTALLLOGMODE_OUTOFDISKSPACE | INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA | - INSTALLLOGMODE_PROPERTYDUMP | INSTALLLOGMODE_COMMONDATA; - - // Arguments parsed from a command line string. - // Arguments currently supported are: - // - Logging options (/l) - // - Quiet options (/q) - // - Properties (PROPERTY=Value) - struct MsiParsedArguments - { - // Logging options. See: MsiEnableLog() - INSTALLLOGMODE LogMode = {}; - std::optional LogFile; - INSTALLLOGATTRIBUTES LogAttributes = {}; - - // UI options. See: MsiSetInternalUI() - INSTALLUILEVEL UILevel = INSTALLUILEVEL_DEFAULT; - - // Properties string - std::wstring Properties; - - using ParsedProperty = std::pair; - - // Contains the properties as split into name and value portions. - std::vector ParsedProperties; - - // Checks for properties that are blocked in some cases. - std::optional GetFirstBlockedProperty() const; - }; - - // Parses a command line string for msiexec. - // This function assumes that the full command line will have the form - // msiexec.exe /i package.msi [arguments] - // and that it is only parsing the [arguments] part. - // - // Note: This does not match msiexec exactly. It does not support options - // unrelated to install, nor all options for install (e.g. /n). - MsiParsedArguments ParseMSIArguments(std::string_view arguments); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + + +// This file defines parsing of the command line arguments passed to msiexec.exe. +// +// Some packages require the UAC prompt for installing even on silent installs. This +// can be done with the MSI API using the INSTALLUILEVEL_UACONLY flag, but msiexec.exe +// does not provide a way to use it. So, we use the MSI API directly instead of +// through msiexec.exe. Since msiexec.exe does some parsing of command line arguments +// before handing off to the API, we replicate that parsing here. +// +// Since we care only about installation, we simplify the parsing by assuming that +// the command line has the form +// msiexec.exe /i (MSI file) [Other args...] + +namespace AppInstaller::Msi +{ + DEFINE_ENUM_FLAG_OPERATORS(INSTALLUILEVEL); + DEFINE_ENUM_FLAG_OPERATORS(INSTALLLOGMODE); + DEFINE_ENUM_FLAG_OPERATORS(INSTALLLOGATTRIBUTES); + + constexpr INSTALLLOGMODE DefaultLogMode = + INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING | INSTALLLOGMODE_INFO | + INSTALLLOGMODE_OUTOFDISKSPACE | INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA; + + // All but the four flags that always have to be set explicitly (Verbose, ExtraDebug, LogOnlyOnError, LogPerformance) + constexpr INSTALLLOGMODE AllLogMode = + INSTALLLOGMODE_FATALEXIT | INSTALLLOGMODE_ERROR | INSTALLLOGMODE_WARNING | INSTALLLOGMODE_USER | INSTALLLOGMODE_INFO | + INSTALLLOGMODE_OUTOFDISKSPACE | INSTALLLOGMODE_ACTIONSTART | INSTALLLOGMODE_ACTIONDATA | + INSTALLLOGMODE_PROPERTYDUMP | INSTALLLOGMODE_COMMONDATA; + + // Arguments parsed from a command line string. + // Arguments currently supported are: + // - Logging options (/l) + // - Quiet options (/q) + // - Properties (PROPERTY=Value) + struct MsiParsedArguments + { + // Logging options. See: MsiEnableLog() + INSTALLLOGMODE LogMode = {}; + std::optional LogFile; + INSTALLLOGATTRIBUTES LogAttributes = {}; + + // UI options. See: MsiSetInternalUI() + INSTALLUILEVEL UILevel = INSTALLUILEVEL_DEFAULT; + + // Properties string + std::wstring Properties; + + using ParsedProperty = std::pair; + + // Contains the properties as split into name and value portions. + std::vector ParsedProperties; + + // Checks for properties that are blocked in some cases. + std::optional GetFirstBlockedProperty() const; + }; + + // Parses a command line string for msiexec. + // This function assumes that the full command line will have the form + // msiexec.exe /i package.msi [arguments] + // and that it is only parsing the [arguments] part. + // + // Note: This does not match msiexec exactly. It does not support options + // unrelated to install, nor all options for install (e.g. /n). + MsiParsedArguments ParseMSIArguments(std::string_view arguments); +} diff --git a/src/AppInstallerCommonCore/Public/winget/NameNormalization.h b/src/AppInstallerCommonCore/Public/winget/NameNormalization.h index f665970ffb..7a7d9e5e75 100644 --- a/src/AppInstallerCommonCore/Public/winget/NameNormalization.h +++ b/src/AppInstallerCommonCore/Public/winget/NameNormalization.h @@ -1,95 +1,95 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include - - -namespace AppInstaller::Utility -{ - // The specific version of normalization being used. - enum class NormalizationVersion - { - Initial, - InitialPreserveWhiteSpace, - }; - - // List of name normalization fields. Architecture, locale, etc. - // Currently only architecture is used. - enum class NormalizationField : uint32_t - { - None = 0x0, - Architecture = 0x1, - }; - - DEFINE_ENUM_FLAG_OPERATORS(NormalizationField); - - struct NameNormalizer; - - // A package publisher and name that has been normalized, allowing direct - // comparison across versions and many other facet. Also allows use in - // generating and Id for local packages. - struct NormalizedName - { - NormalizedName() = default; - - const std::string& Name() const { return m_name; } - void Name(std::string&& name) { m_name = std::move(name); } - void Name(std::string_view name) { m_name = name; } - - Utility::Architecture Architecture() const { return m_arch; } - void Architecture(Utility::Architecture arch) { m_arch = arch; } - - const std::string& Locale() const { return m_locale; } - void Locale(std::string&& locale) { m_locale = std::move(locale); } - - const std::string& Publisher() const { return m_publisher; } - void Publisher(std::string&& publisher) { m_publisher = std::move(publisher); } - void Publisher(std::string_view publisher) { m_publisher = publisher; } - - // Gets normalized name with additional normalization fields included. - std::string GetNormalizedName(NormalizationField fieldsToInclude) const; - // Gets a flag indicating the list of fields detected in normalization. - NormalizationField GetNormalizedFields() const; - - private: - std::string m_name; - Utility::Architecture m_arch = Utility::Architecture::Unknown; - std::string m_locale; - std::string m_publisher; - }; - - namespace details - { - // NameNormalizer interface to allow different versions. - struct INameNormalizer - { - virtual ~INameNormalizer() = default; - - // Normalize both the name and publisher at the same time. - virtual NormalizedName Normalize(std::string_view name, std::string_view publisher) const = 0; - - // Normalize only the name. - virtual NormalizedName NormalizeName(std::string_view name) const = 0; - - // Normalize only the publisher. - virtual std::string NormalizePublisher(std::string_view publisher) const = 0; - }; - } - - // Helper that manages the lifetime of the internals required to - // execute the name normalization. - struct NameNormalizer - { - NameNormalizer(NormalizationVersion version); - - NormalizedName Normalize(std::string_view name, std::string_view publisher) const; - NormalizedName NormalizeName(std::string_view name) const; - std::string NormalizePublisher(std::string_view publisher) const; - - private: - std::unique_ptr m_normalizer; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include + + +namespace AppInstaller::Utility +{ + // The specific version of normalization being used. + enum class NormalizationVersion + { + Initial, + InitialPreserveWhiteSpace, + }; + + // List of name normalization fields. Architecture, locale, etc. + // Currently only architecture is used. + enum class NormalizationField : uint32_t + { + None = 0x0, + Architecture = 0x1, + }; + + DEFINE_ENUM_FLAG_OPERATORS(NormalizationField); + + struct NameNormalizer; + + // A package publisher and name that has been normalized, allowing direct + // comparison across versions and many other facet. Also allows use in + // generating and Id for local packages. + struct NormalizedName + { + NormalizedName() = default; + + const std::string& Name() const { return m_name; } + void Name(std::string&& name) { m_name = std::move(name); } + void Name(std::string_view name) { m_name = name; } + + Utility::Architecture Architecture() const { return m_arch; } + void Architecture(Utility::Architecture arch) { m_arch = arch; } + + const std::string& Locale() const { return m_locale; } + void Locale(std::string&& locale) { m_locale = std::move(locale); } + + const std::string& Publisher() const { return m_publisher; } + void Publisher(std::string&& publisher) { m_publisher = std::move(publisher); } + void Publisher(std::string_view publisher) { m_publisher = publisher; } + + // Gets normalized name with additional normalization fields included. + std::string GetNormalizedName(NormalizationField fieldsToInclude) const; + // Gets a flag indicating the list of fields detected in normalization. + NormalizationField GetNormalizedFields() const; + + private: + std::string m_name; + Utility::Architecture m_arch = Utility::Architecture::Unknown; + std::string m_locale; + std::string m_publisher; + }; + + namespace details + { + // NameNormalizer interface to allow different versions. + struct INameNormalizer + { + virtual ~INameNormalizer() = default; + + // Normalize both the name and publisher at the same time. + virtual NormalizedName Normalize(std::string_view name, std::string_view publisher) const = 0; + + // Normalize only the name. + virtual NormalizedName NormalizeName(std::string_view name) const = 0; + + // Normalize only the publisher. + virtual std::string NormalizePublisher(std::string_view publisher) const = 0; + }; + } + + // Helper that manages the lifetime of the internals required to + // execute the name normalization. + struct NameNormalizer + { + NameNormalizer(NormalizationVersion version); + + NormalizedName Normalize(std::string_view name, std::string_view publisher) const; + NormalizedName NormalizeName(std::string_view name) const; + std::string NormalizePublisher(std::string_view publisher) const; + + private: + std::unique_ptr m_normalizer; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/OutputDebugStringLogger.h b/src/AppInstallerCommonCore/Public/winget/OutputDebugStringLogger.h index 751bf4b1f8..2d181b5459 100644 --- a/src/AppInstallerCommonCore/Public/winget/OutputDebugStringLogger.h +++ b/src/AppInstallerCommonCore/Public/winget/OutputDebugStringLogger.h @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::Logging -{ - // Sends logs to the OutputDebugString function. - // Intended for use during initialization debugging. - struct OutputDebugStringLogger : ILogger - { - OutputDebugStringLogger() = default; - - ~OutputDebugStringLogger() = default; - - // ILogger - std::string GetName() const override; - - void Write(Channel channel, Level, std::string_view message) noexcept override; - - void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; - - // Adds OutputDebugStringLogger to the current Log - static void Add(); - - // Removes OutputDebugStringLogger from the current Log - static void Remove(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Logging +{ + // Sends logs to the OutputDebugString function. + // Intended for use during initialization debugging. + struct OutputDebugStringLogger : ILogger + { + OutputDebugStringLogger() = default; + + ~OutputDebugStringLogger() = default; + + // ILogger + std::string GetName() const override; + + void Write(Channel channel, Level, std::string_view message) noexcept override; + + void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; + + // Adds OutputDebugStringLogger to the current Log + static void Add(); + + // Removes OutputDebugStringLogger from the current Log + static void Remove(); + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/PackageVersionDataManifest.h b/src/AppInstallerCommonCore/Public/winget/PackageVersionDataManifest.h index 4e5b8d017f..d2015c8973 100644 --- a/src/AppInstallerCommonCore/Public/winget/PackageVersionDataManifest.h +++ b/src/AppInstallerCommonCore/Public/winget/PackageVersionDataManifest.h @@ -1,66 +1,66 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - - -namespace AppInstaller::Manifest -{ - // Contains the manifest that stores package version data for index v2 - struct PackageVersionDataManifest - { - // The file name to use for the package version data manifest. - static std::string_view VersionManifestFileName(); - - // The file name to use for the compressed package version data manifest. - static std::string_view VersionManifestCompressedFileName(); - - // Gets the relative path to the package version manifest from the inputs. - static std::filesystem::path GetRelativeDirectoryPath(std::string_view packageIdentifier, std::string_view manifestHash); - - // Creates the compressor used by the PackageVersionDataManifest. - static Compression::Compressor CreateCompressor(); - - // Creates the decompressor used by the PackageVersionDataManifest. - static Compression::Decompressor CreateDecompressor(); - - // Data on an individual version. - struct VersionData - { - VersionData() = default; - - VersionData( - const Utility::VersionAndChannel& versionAndChannel, - std::optional arpMinVersion, - std::optional arpMaxVersion, - std::optional relativePath, - std::optional manifestHash); - - Utility::Version Version; - std::optional ArpMinVersion; - std::optional ArpMaxVersion; - std::string ManifestRelativePath; - std::string ManifestHash; - }; - - // Adds the given version data to the manifest. - void AddVersion(VersionData&& versionData); - - // Gets the version data in this object. - const std::vector& Versions() const; - - // Returns a serialized version of the current manifest data. - std::string Serialize(); - - // Parses the input into this objects data. - void Deserialize(std::string_view input); - - // Parses the input into this objects data. - void Deserialize(const std::vector& input); - - private: - std::vector m_versions; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + + +namespace AppInstaller::Manifest +{ + // Contains the manifest that stores package version data for index v2 + struct PackageVersionDataManifest + { + // The file name to use for the package version data manifest. + static std::string_view VersionManifestFileName(); + + // The file name to use for the compressed package version data manifest. + static std::string_view VersionManifestCompressedFileName(); + + // Gets the relative path to the package version manifest from the inputs. + static std::filesystem::path GetRelativeDirectoryPath(std::string_view packageIdentifier, std::string_view manifestHash); + + // Creates the compressor used by the PackageVersionDataManifest. + static Compression::Compressor CreateCompressor(); + + // Creates the decompressor used by the PackageVersionDataManifest. + static Compression::Decompressor CreateDecompressor(); + + // Data on an individual version. + struct VersionData + { + VersionData() = default; + + VersionData( + const Utility::VersionAndChannel& versionAndChannel, + std::optional arpMinVersion, + std::optional arpMaxVersion, + std::optional relativePath, + std::optional manifestHash); + + Utility::Version Version; + std::optional ArpMinVersion; + std::optional ArpMaxVersion; + std::string ManifestRelativePath; + std::string ManifestHash; + }; + + // Adds the given version data to the manifest. + void AddVersion(VersionData&& versionData); + + // Gets the version data in this object. + const std::vector& Versions() const; + + // Returns a serialized version of the current manifest data. + std::string Serialize(); + + // Parses the input into this objects data. + void Deserialize(std::string_view input); + + // Parses the input into this objects data. + void Deserialize(const std::vector& input); + + private: + std::vector m_versions; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/PathVariable.h b/src/AppInstallerCommonCore/Public/winget/PathVariable.h index db92f10197..b81bc06e38 100644 --- a/src/AppInstallerCommonCore/Public/winget/PathVariable.h +++ b/src/AppInstallerCommonCore/Public/winget/PathVariable.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winget/Manifest.h" -#include "winget/Registry.h" - -namespace AppInstaller::Registry::Environment -{ - bool RefreshPathVariableForCurrentProcess(); - - struct PathVariable - { - PathVariable(Manifest::ScopeEnum scope, bool readOnly = false); - - // Returns the PATH variable as a string. - std::string GetPathValue(); - - // Checks if the PATH variable contains the target path. - bool Contains(const std::filesystem::path& target); - - // Returns a value indicating whether the target path was removed from the PATH variable. - bool Remove(const std::filesystem::path& target); - - // Returns a value indicating whether the target path was appended to the PATH variable. - bool Append(const std::filesystem::path& target); - - private: - void SetPathValue(const std::string& value); - Registry::Key m_key; - Manifest::ScopeEnum m_scope; - bool m_readOnly; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winget/Manifest.h" +#include "winget/Registry.h" + +namespace AppInstaller::Registry::Environment +{ + bool RefreshPathVariableForCurrentProcess(); + + struct PathVariable + { + PathVariable(Manifest::ScopeEnum scope, bool readOnly = false); + + // Returns the PATH variable as a string. + std::string GetPathValue(); + + // Checks if the PATH variable contains the target path. + bool Contains(const std::filesystem::path& target); + + // Returns a value indicating whether the target path was removed from the PATH variable. + bool Remove(const std::filesystem::path& target); + + // Returns a value indicating whether the target path was appended to the PATH variable. + bool Append(const std::filesystem::path& target); + + private: + void SetPathValue(const std::string& value); + Registry::Key m_key; + Manifest::ScopeEnum m_scope; + bool m_readOnly; + }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/Pin.h b/src/AppInstallerCommonCore/Public/winget/Pin.h index 2039b81a5e..74df4947ac 100644 --- a/src/AppInstallerCommonCore/Public/winget/Pin.h +++ b/src/AppInstallerCommonCore/Public/winget/Pin.h @@ -1,118 +1,118 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winget/Manifest.h" -#include "AppInstallerVersions.h" - -namespace AppInstaller::Pinning -{ - // The pin types are ordered by how "strict" they are. - // Meaning, the one that is more restrictive goes later. - // This is used to decide which pin to report if there are multiple pins. - enum class PinType : int64_t - { - // Unknown pin type or not pinned - Unknown, - // Pinned by the manifest using the RequiresExplicitUpgrade field. - // Behaves the same as Pinning pins - PinnedByManifest, - // The package is excluded from 'upgrade --all', unless '--include-pinned' is added. - // 'upgrade ' is not blocked. - Pinning, - // The package is pinned to a specific version range. - Gating, - // The package is blocked from 'upgrade --all' and 'upgrade '. - // User has to unblock to allow update. - Blocking, - }; - - std::string_view ToString(PinType type); - PinType ConvertToPinTypeEnum(std::string_view in); - - // Determines which of two pin types is more strict. - bool IsStricter(PinType first, PinType second); - - // Returns the stricter of two pin types. - PinType Stricter(PinType first, PinType second); - - // The set of values needed to uniquely identify a Pin. - // A Pin can apply to an installed package or to an available package. - // Pins on available packages can persist when an app is updated outside of winget, - // but it's hard to have them work when there are multiple installed packages for the same available package. - // Pins on installed packages work fine when there are multiple installed packages for the same available, - // but they break when the package is updated outside of winget. - struct PinKey - { - PinKey() = default; - PinKey(const Manifest::Manifest::string_t& packageId, std::string_view sourceId) - : PackageId(packageId), SourceId(sourceId) {} - - // Gets a pin key that refers to an installed package by its ProductCode or PackageFamilyName. - // The sourceId used is a special string to distinguish from available packages. - static PinKey GetPinKeyForInstalled(std::string_view systemReferenceString); - - bool IsForInstalled() const; - - bool operator==(const PinKey& other) const - { - return PackageId == other.PackageId - && SourceId == other.SourceId; - } - - bool operator!=(const PinKey& other) const - { - return !(*this == other); - } - - bool operator<(const PinKey& 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(PackageId, SourceId) < std::tie(other.PackageId, other.SourceId); - } - - // Used for logging - std::string ToString() const; - - std::string PackageId; - std::string SourceId; - }; - - struct Pin - { - Pin(const Pin&) = default; - Pin& operator=(const Pin& other) = default; - - Pin(Pin&&) = default; - Pin& operator=(Pin&&) = default; - - static Pin CreateBlockingPin(PinKey&& pinKey); - static Pin CreatePinningPin(PinKey&& pinKey); - static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion&& gatedVersion); - - static Pin CreateBlockingPin(const PinKey& pinKey) { return CreateBlockingPin(PinKey{ pinKey }); } - static Pin CreatePinningPin(const PinKey& pinKey) { return CreatePinningPin(PinKey{ pinKey }); } - static Pin CreateGatingPin(const PinKey& pinKey, const Utility::GatedVersion& gatedVersion) { return CreateGatingPin(PinKey{ pinKey }, Utility::GatedVersion{ gatedVersion }); } - - PinType GetType() const { return m_type; } - const PinKey& GetKey() const { return m_key; } - const Utility::GatedVersion& GetGatedVersion() const { return m_gatedVersion; } - - bool operator==(const Pin& other) const; - bool operator<(const Pin& other) const - { - return std::make_pair(m_type, m_key) < std::make_pair(other.m_type, other.m_key); - } - - // Used for logging - std::string ToString() const; - - private: - Pin(PinType type, PinKey&& pinKey, Utility::GatedVersion&& gatedVersion = {}) - : m_type(type), m_key(std::move(pinKey)), m_gatedVersion(std::move(gatedVersion)) {} - - PinType m_type = PinType::Unknown; - PinKey m_key; - Utility::GatedVersion m_gatedVersion; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winget/Manifest.h" +#include "AppInstallerVersions.h" + +namespace AppInstaller::Pinning +{ + // The pin types are ordered by how "strict" they are. + // Meaning, the one that is more restrictive goes later. + // This is used to decide which pin to report if there are multiple pins. + enum class PinType : int64_t + { + // Unknown pin type or not pinned + Unknown, + // Pinned by the manifest using the RequiresExplicitUpgrade field. + // Behaves the same as Pinning pins + PinnedByManifest, + // The package is excluded from 'upgrade --all', unless '--include-pinned' is added. + // 'upgrade ' is not blocked. + Pinning, + // The package is pinned to a specific version range. + Gating, + // The package is blocked from 'upgrade --all' and 'upgrade '. + // User has to unblock to allow update. + Blocking, + }; + + std::string_view ToString(PinType type); + PinType ConvertToPinTypeEnum(std::string_view in); + + // Determines which of two pin types is more strict. + bool IsStricter(PinType first, PinType second); + + // Returns the stricter of two pin types. + PinType Stricter(PinType first, PinType second); + + // The set of values needed to uniquely identify a Pin. + // A Pin can apply to an installed package or to an available package. + // Pins on available packages can persist when an app is updated outside of winget, + // but it's hard to have them work when there are multiple installed packages for the same available package. + // Pins on installed packages work fine when there are multiple installed packages for the same available, + // but they break when the package is updated outside of winget. + struct PinKey + { + PinKey() = default; + PinKey(const Manifest::Manifest::string_t& packageId, std::string_view sourceId) + : PackageId(packageId), SourceId(sourceId) {} + + // Gets a pin key that refers to an installed package by its ProductCode or PackageFamilyName. + // The sourceId used is a special string to distinguish from available packages. + static PinKey GetPinKeyForInstalled(std::string_view systemReferenceString); + + bool IsForInstalled() const; + + bool operator==(const PinKey& other) const + { + return PackageId == other.PackageId + && SourceId == other.SourceId; + } + + bool operator!=(const PinKey& other) const + { + return !(*this == other); + } + + bool operator<(const PinKey& 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(PackageId, SourceId) < std::tie(other.PackageId, other.SourceId); + } + + // Used for logging + std::string ToString() const; + + std::string PackageId; + std::string SourceId; + }; + + struct Pin + { + Pin(const Pin&) = default; + Pin& operator=(const Pin& other) = default; + + Pin(Pin&&) = default; + Pin& operator=(Pin&&) = default; + + static Pin CreateBlockingPin(PinKey&& pinKey); + static Pin CreatePinningPin(PinKey&& pinKey); + static Pin CreateGatingPin(PinKey&& pinKey, Utility::GatedVersion&& gatedVersion); + + static Pin CreateBlockingPin(const PinKey& pinKey) { return CreateBlockingPin(PinKey{ pinKey }); } + static Pin CreatePinningPin(const PinKey& pinKey) { return CreatePinningPin(PinKey{ pinKey }); } + static Pin CreateGatingPin(const PinKey& pinKey, const Utility::GatedVersion& gatedVersion) { return CreateGatingPin(PinKey{ pinKey }, Utility::GatedVersion{ gatedVersion }); } + + PinType GetType() const { return m_type; } + const PinKey& GetKey() const { return m_key; } + const Utility::GatedVersion& GetGatedVersion() const { return m_gatedVersion; } + + bool operator==(const Pin& other) const; + bool operator<(const Pin& other) const + { + return std::make_pair(m_type, m_key) < std::make_pair(other.m_type, other.m_key); + } + + // Used for logging + std::string ToString() const; + + private: + Pin(PinType type, PinKey&& pinKey, Utility::GatedVersion&& gatedVersion = {}) + : m_type(type), m_key(std::move(pinKey)), m_gatedVersion(std::move(gatedVersion)) {} + + PinType m_type = PinType::Unknown; + PinKey m_key; + Utility::GatedVersion m_gatedVersion; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/PortableARPEntry.h b/src/AppInstallerCommonCore/Public/winget/PortableARPEntry.h index 46d428cc8d..a6355660c3 100644 --- a/src/AppInstallerCommonCore/Public/winget/PortableARPEntry.h +++ b/src/AppInstallerCommonCore/Public/winget/PortableARPEntry.h @@ -1,61 +1,61 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Manifest.h" - -namespace AppInstaller::Registry::Portable -{ - enum class PortableValueName - { - DisplayName, - DisplayVersion, - HelpLink, - InstallDate, - InstallDirectoryCreated, - InstallLocation, - PortableSymlinkFullPath, - PortableTargetFullPath, - Publisher, - SHA256, - URLInfoAbout, - UninstallString, - WinGetInstallerType, - WinGetPackageIdentifier, - WinGetSourceIdentifier, - InstallDirectoryAddedToPath, - }; - - std::wstring_view ToString(PortableValueName valueName); - - struct PortableARPEntry : Registry::Key - { - PortableARPEntry(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode); - - std::optional operator[](PortableValueName valueName) const; - - bool Exists() { return m_exists; } - - void SetValue(PortableValueName valueName, const std::wstring& value); - void SetValue(PortableValueName valueName, const std::string_view& value); - void SetValue(PortableValueName valueName, bool& value); - - void Delete(); - - Registry::Key GetKey() { return m_key; }; - Manifest::ScopeEnum GetScope() { return m_scope; }; - Utility::Architecture GetArchitecture() { return m_arch; }; - std::string GetProductCode() { return m_productCode; }; - - private: - bool m_exists = false; - std::string m_productCode; - Key m_key; - HKEY m_root; - std::wstring m_subKey; - DWORD m_samDesired; - Manifest::ScopeEnum m_scope; - Utility::Architecture m_arch; - }; - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Manifest.h" + +namespace AppInstaller::Registry::Portable +{ + enum class PortableValueName + { + DisplayName, + DisplayVersion, + HelpLink, + InstallDate, + InstallDirectoryCreated, + InstallLocation, + PortableSymlinkFullPath, + PortableTargetFullPath, + Publisher, + SHA256, + URLInfoAbout, + UninstallString, + WinGetInstallerType, + WinGetPackageIdentifier, + WinGetSourceIdentifier, + InstallDirectoryAddedToPath, + }; + + std::wstring_view ToString(PortableValueName valueName); + + struct PortableARPEntry : Registry::Key + { + PortableARPEntry(Manifest::ScopeEnum scope, Utility::Architecture arch, const std::string& productCode); + + std::optional operator[](PortableValueName valueName) const; + + bool Exists() { return m_exists; } + + void SetValue(PortableValueName valueName, const std::wstring& value); + void SetValue(PortableValueName valueName, const std::string_view& value); + void SetValue(PortableValueName valueName, bool& value); + + void Delete(); + + Registry::Key GetKey() { return m_key; }; + Manifest::ScopeEnum GetScope() { return m_scope; }; + Utility::Architecture GetArchitecture() { return m_arch; }; + std::string GetProductCode() { return m_productCode; }; + + private: + bool m_exists = false; + std::string m_productCode; + Key m_key; + HKEY m_root; + std::wstring m_subKey; + DWORD m_samDesired; + Manifest::ScopeEnum m_scope; + Utility::Architecture m_arch; + }; + } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/PortableFileEntry.h b/src/AppInstallerCommonCore/Public/winget/PortableFileEntry.h index 6c7976d2db..26c834ff7c 100644 --- a/src/AppInstallerCommonCore/Public/winget/PortableFileEntry.h +++ b/src/AppInstallerCommonCore/Public/winget/PortableFileEntry.h @@ -1,81 +1,81 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AppInstallerSHA256.h" -#include -#include - -namespace AppInstaller::Portable -{ - // File type enum of the portable file - enum class PortableFileType - { - Unknown, - File, - Directory, - Symlink - }; - - // Metadata representation of a portable file placed down during installation - struct PortableFileEntry - { - // Version 1.0 - PortableFileType FileType = PortableFileType::Unknown; - std::string SHA256; - std::string SymlinkTarget; - std::filesystem::path CurrentPath; - - void SetFilePath(const std::filesystem::path& path) - { - if (FileType != PortableFileType::Symlink) - { - m_filePath = std::filesystem::weakly_canonical(path); - } - else - { - m_filePath = path; - } - }; - - std::filesystem::path GetFilePath() const { return m_filePath; }; - - static PortableFileEntry CreateFileEntry(const std::filesystem::path& currentPath, const std::filesystem::path& targetPath, const std::string& sha256) - { - PortableFileEntry fileEntry; - fileEntry.FileType = PortableFileType::File; - fileEntry.CurrentPath = currentPath; - fileEntry.SetFilePath(targetPath); - - if (sha256.empty()) - { - fileEntry.SHA256 = Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHashFromFile(currentPath)); - } - else - { - fileEntry.SHA256 = sha256; - } - return fileEntry; - } - - static PortableFileEntry CreateSymlinkEntry(const std::filesystem::path& symlinkPath, const std::filesystem::path& targetPath) - { - PortableFileEntry symlinkEntry; - symlinkEntry.FileType = PortableFileType::Symlink; - symlinkEntry.SetFilePath(symlinkPath); - symlinkEntry.SymlinkTarget = targetPath.u8string(); - return symlinkEntry; - } - - static PortableFileEntry CreateDirectoryEntry(const std::filesystem::path& currentPath, const std::filesystem::path& directoryPath) - { - PortableFileEntry directoryEntry; - directoryEntry.FileType = PortableFileType::Directory; - directoryEntry.CurrentPath = currentPath; - directoryEntry.SetFilePath(directoryPath); - return directoryEntry; - } - - private: - std::filesystem::path m_filePath; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AppInstallerSHA256.h" +#include +#include + +namespace AppInstaller::Portable +{ + // File type enum of the portable file + enum class PortableFileType + { + Unknown, + File, + Directory, + Symlink + }; + + // Metadata representation of a portable file placed down during installation + struct PortableFileEntry + { + // Version 1.0 + PortableFileType FileType = PortableFileType::Unknown; + std::string SHA256; + std::string SymlinkTarget; + std::filesystem::path CurrentPath; + + void SetFilePath(const std::filesystem::path& path) + { + if (FileType != PortableFileType::Symlink) + { + m_filePath = std::filesystem::weakly_canonical(path); + } + else + { + m_filePath = path; + } + }; + + std::filesystem::path GetFilePath() const { return m_filePath; }; + + static PortableFileEntry CreateFileEntry(const std::filesystem::path& currentPath, const std::filesystem::path& targetPath, const std::string& sha256) + { + PortableFileEntry fileEntry; + fileEntry.FileType = PortableFileType::File; + fileEntry.CurrentPath = currentPath; + fileEntry.SetFilePath(targetPath); + + if (sha256.empty()) + { + fileEntry.SHA256 = Utility::SHA256::ConvertToString(Utility::SHA256::ComputeHashFromFile(currentPath)); + } + else + { + fileEntry.SHA256 = sha256; + } + return fileEntry; + } + + static PortableFileEntry CreateSymlinkEntry(const std::filesystem::path& symlinkPath, const std::filesystem::path& targetPath) + { + PortableFileEntry symlinkEntry; + symlinkEntry.FileType = PortableFileType::Symlink; + symlinkEntry.SetFilePath(symlinkPath); + symlinkEntry.SymlinkTarget = targetPath.u8string(); + return symlinkEntry; + } + + static PortableFileEntry CreateDirectoryEntry(const std::filesystem::path& currentPath, const std::filesystem::path& directoryPath) + { + PortableFileEntry directoryEntry; + directoryEntry.FileType = PortableFileType::Directory; + directoryEntry.CurrentPath = currentPath; + directoryEntry.SetFilePath(directoryPath); + return directoryEntry; + } + + private: + std::filesystem::path m_filePath; + }; } \ No newline at end of file diff --git a/src/AppInstallerCommonCore/Public/winget/Regex.h b/src/AppInstallerCommonCore/Public/winget/Regex.h index e3cd69a86e..54804728d6 100644 --- a/src/AppInstallerCommonCore/Public/winget/Regex.h +++ b/src/AppInstallerCommonCore/Public/winget/Regex.h @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - - -namespace AppInstaller::Regex -{ - // Options for regular expression use. - enum class Options - { - None = 0, - CaseInsensitive, - }; - - // Stores the compiled regular expression. - // All pattern strings are considered UTF-8. - // All input strings are considered UTF-16, as this is what ICU operates on internally. - struct Expression - { - Expression(); - Expression(std::string_view pattern, Options options = Options::None); - - Expression(const Expression&); - Expression& operator=(const Expression&); - - Expression(Expression&&) noexcept; - Expression& operator=(Expression&&) noexcept; - - ~Expression(); - - // Determines if the expression contains a value. - operator bool() const; - - // Returns a value indicating whether the *entire* input matches the expression. - bool IsMatch(std::wstring_view input) const; - - // Replaces all matches in the input with the replacement. - std::wstring Replace(std::wstring_view input, std::wstring_view replacement) const; - - // For each section of the input, invoke the given functor. This allows the caller - // to iterate over the entire string, taking action as appropriate for each part. - // The parameters are: - // bool :: indicates whether this section was a match - // string_view :: the text for the section - // The functor should return true to continue the loop, or false to break it. - void ForEach(std::wstring_view input, const std::function& f) const; - - private: - struct impl; - std::unique_ptr pImpl; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + + +namespace AppInstaller::Regex +{ + // Options for regular expression use. + enum class Options + { + None = 0, + CaseInsensitive, + }; + + // Stores the compiled regular expression. + // All pattern strings are considered UTF-8. + // All input strings are considered UTF-16, as this is what ICU operates on internally. + struct Expression + { + Expression(); + Expression(std::string_view pattern, Options options = Options::None); + + Expression(const Expression&); + Expression& operator=(const Expression&); + + Expression(Expression&&) noexcept; + Expression& operator=(Expression&&) noexcept; + + ~Expression(); + + // Determines if the expression contains a value. + operator bool() const; + + // Returns a value indicating whether the *entire* input matches the expression. + bool IsMatch(std::wstring_view input) const; + + // Replaces all matches in the input with the replacement. + std::wstring Replace(std::wstring_view input, std::wstring_view replacement) const; + + // For each section of the input, invoke the given functor. This allows the caller + // to iterate over the entire string, taking action as appropriate for each part. + // The parameters are: + // bool :: indicates whether this section was a match + // string_view :: the text for the section + // The functor should return true to continue the loop, or false to break it. + void ForEach(std::wstring_view input, const std::function& f) const; + + private: + struct impl; + std::unique_ptr pImpl; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/Rest.h b/src/AppInstallerCommonCore/Public/winget/Rest.h index a8f903580c..f08b8bdbf3 100644 --- a/src/AppInstallerCommonCore/Public/winget/Rest.h +++ b/src/AppInstallerCommonCore/Public/winget/Rest.h @@ -1,20 +1,20 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::Rest -{ - utility::string_t GetRestAPIBaseUri(std::string restApiUri); - - bool IsValidUri(const utility::string_t& restApiUri); - - utility::string_t AppendPathToUri(const utility::string_t& restApiUri, const utility::string_t& path); - - utility::string_t MakeQueryParam(std::string_view queryName, const std::string& queryValue); - - utility::string_t AppendQueryParamsToUri(const utility::string_t& uri, const std::map& queryParameters); - - std::vector GetUniqueItems(const std::vector& list); -} - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Rest +{ + utility::string_t GetRestAPIBaseUri(std::string restApiUri); + + bool IsValidUri(const utility::string_t& restApiUri); + + utility::string_t AppendPathToUri(const utility::string_t& restApiUri, const utility::string_t& path); + + utility::string_t MakeQueryParam(std::string_view queryName, const std::string& queryValue); + + utility::string_t AppendQueryParamsToUri(const utility::string_t& uri, const std::map& queryParameters); + + std::vector GetUniqueItems(const std::vector& list); +} + diff --git a/src/AppInstallerCommonCore/Public/winget/SelfManagement.h b/src/AppInstallerCommonCore/Public/winget/SelfManagement.h index 9dc6a5fd7b..a3793f55d2 100644 --- a/src/AppInstallerCommonCore/Public/winget/SelfManagement.h +++ b/src/AppInstallerCommonCore/Public/winget/SelfManagement.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::SelfManagement -{ - // Gets the stub preference for the current package. - // Returns true if the package is set to prefer stubs. - // Returns false if the package is set to prefer the full package, - // or the current process is not packaged. - bool IsStubPreferred(); - - // Sets the stub preference for the current package. - // It is an error to set the preference if the process is not packaged, - // or the preference can otherwise not be set (older version of Windows). - void SetStubPreferred(bool preferStub); - - // Gets a value indicating whether the current package is the stub package. - bool IsStubPackage(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::SelfManagement +{ + // Gets the stub preference for the current package. + // Returns true if the package is set to prefer stubs. + // Returns false if the package is set to prefer the full package, + // or the current process is not packaged. + bool IsStubPreferred(); + + // Sets the stub preference for the current package. + // It is an error to set the preference if the process is not packaged, + // or the preference can otherwise not be set (older version of Windows). + void SetStubPreferred(bool preferStub); + + // Gets a value indicating whether the current package is the stub package. + bool IsStubPackage(); +} diff --git a/src/AppInstallerCommonCore/Public/winget/Settings.h b/src/AppInstallerCommonCore/Public/winget/Settings.h index 5abe0c7f89..b14bab0d80 100644 --- a/src/AppInstallerCommonCore/Public/winget/Settings.h +++ b/src/AppInstallerCommonCore/Public/winget/Settings.h @@ -1,114 +1,114 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - - namespace details - { - // A settings container. - struct ISettingsContainer - { - virtual ~ISettingsContainer() = default; - - // Gets a stream containing the setting's value, if present. - // If the setting does not exist, returns an empty value. - virtual std::unique_ptr Get() = 0; - - // Sets the setting to the given value. - virtual bool Set(std::string_view value) = 0; - - // Deletes the setting. - virtual void Remove() = 0; - - // Gets the path to the setting, if reasonable. - virtual std::filesystem::path PathTo() = 0; - }; - } - - // Allows settings to be classified and treated differently base on any number of factors. - // Names should still be unique, as there is no guarantee made about types mapping to unique roots. - enum class Type - { - // A Standard setting stream has no special requirements (limited to 8K contents and no embedded null characters). - Standard, - // A UserFile setting stream should be located in a file that is easily editable by the user. - UserFile, - // A settings stream that should not be modified except by admin privileges. - Secure, - // A settings stream that is encrypted. It does not require admin privileges to write to. - Encrypted, - // A setting stream has should be stored in a file, removing the limitations of the Standard type. - StandardFile, - }; - - // Converts the Type enum to a string. - std::string_view ToString(Type type); - - // A stream definition, combining both type and path. - // The well known values in Streams should be used by product code, while tests may directly create them. - struct StreamDefinition - { - constexpr StreamDefinition(Type type, std::string_view name) : Type(type), Name(name) {} - - // The type of stream. - Type Type; - - // The name is used as a file name in some situations. - std::string_view Name; - }; - - // A setting stream; provides access to functionality on the stream. - struct Stream - { - // The set of well known settings streams. - // Changing these values can result in data loss. - - // The set of sources as defined by the user. - constexpr static StreamDefinition UserSources{ Type::Secure, "user_sources"sv }; - // The metadata about all sources. - constexpr static StreamDefinition SourcesMetadata{ Type::Standard, "sources_metadata"sv }; - // The primary user settings file. - constexpr static StreamDefinition PrimaryUserSettings{ Type::UserFile, "settings.json"sv }; - // The backup user settings file. - constexpr static StreamDefinition BackupUserSettings{ Type::UserFile, "settings.json.backup"sv }; - // The admin settings. - constexpr static StreamDefinition AdminSettings{ Type::Secure, "admin_settings"sv }; - // The REST information cache. - constexpr static StreamDefinition RestInformationCache{ Type::Encrypted, "rest_information"sv }; - - // Gets a Stream for the StreamDefinition. - // If the stream is synchronized, attempts to Set the value can fail due to another writer - // having changed the underlying stream. - Stream(const StreamDefinition& streamDefinition); - - const StreamDefinition& Definition() const { return m_streamDefinition; } - - // Gets the stream if present. - // If the setting stream does not exist, returns an empty value (see operator bool). - std::unique_ptr Get(); - - // Sets the stream to the given value. - // Returns true if successful; false if the underlying stream has changed. - [[nodiscard]] bool Set(std::string_view value); - - // Deletes the setting stream. - void Remove(); - - // Gets the name of the stream. - std::string_view GetName() const; - - // Gets the path to the stream. - std::filesystem::path GetPath() const; - - private: - const StreamDefinition m_streamDefinition; - std::unique_ptr m_container; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + + namespace details + { + // A settings container. + struct ISettingsContainer + { + virtual ~ISettingsContainer() = default; + + // Gets a stream containing the setting's value, if present. + // If the setting does not exist, returns an empty value. + virtual std::unique_ptr Get() = 0; + + // Sets the setting to the given value. + virtual bool Set(std::string_view value) = 0; + + // Deletes the setting. + virtual void Remove() = 0; + + // Gets the path to the setting, if reasonable. + virtual std::filesystem::path PathTo() = 0; + }; + } + + // Allows settings to be classified and treated differently base on any number of factors. + // Names should still be unique, as there is no guarantee made about types mapping to unique roots. + enum class Type + { + // A Standard setting stream has no special requirements (limited to 8K contents and no embedded null characters). + Standard, + // A UserFile setting stream should be located in a file that is easily editable by the user. + UserFile, + // A settings stream that should not be modified except by admin privileges. + Secure, + // A settings stream that is encrypted. It does not require admin privileges to write to. + Encrypted, + // A setting stream has should be stored in a file, removing the limitations of the Standard type. + StandardFile, + }; + + // Converts the Type enum to a string. + std::string_view ToString(Type type); + + // A stream definition, combining both type and path. + // The well known values in Streams should be used by product code, while tests may directly create them. + struct StreamDefinition + { + constexpr StreamDefinition(Type type, std::string_view name) : Type(type), Name(name) {} + + // The type of stream. + Type Type; + + // The name is used as a file name in some situations. + std::string_view Name; + }; + + // A setting stream; provides access to functionality on the stream. + struct Stream + { + // The set of well known settings streams. + // Changing these values can result in data loss. + + // The set of sources as defined by the user. + constexpr static StreamDefinition UserSources{ Type::Secure, "user_sources"sv }; + // The metadata about all sources. + constexpr static StreamDefinition SourcesMetadata{ Type::Standard, "sources_metadata"sv }; + // The primary user settings file. + constexpr static StreamDefinition PrimaryUserSettings{ Type::UserFile, "settings.json"sv }; + // The backup user settings file. + constexpr static StreamDefinition BackupUserSettings{ Type::UserFile, "settings.json.backup"sv }; + // The admin settings. + constexpr static StreamDefinition AdminSettings{ Type::Secure, "admin_settings"sv }; + // The REST information cache. + constexpr static StreamDefinition RestInformationCache{ Type::Encrypted, "rest_information"sv }; + + // Gets a Stream for the StreamDefinition. + // If the stream is synchronized, attempts to Set the value can fail due to another writer + // having changed the underlying stream. + Stream(const StreamDefinition& streamDefinition); + + const StreamDefinition& Definition() const { return m_streamDefinition; } + + // Gets the stream if present. + // If the setting stream does not exist, returns an empty value (see operator bool). + std::unique_ptr Get(); + + // Sets the stream to the given value. + // Returns true if successful; false if the underlying stream has changed. + [[nodiscard]] bool Set(std::string_view value); + + // Deletes the setting stream. + void Remove(); + + // Gets the name of the stream. + std::string_view GetName() const; + + // Gets the path to the stream. + std::filesystem::path GetPath() const; + + private: + const StreamDefinition m_streamDefinition; + std::unique_ptr m_container; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/StdErrLogger.h b/src/AppInstallerCommonCore/Public/winget/StdErrLogger.h index 2208590778..ef46a14e93 100644 --- a/src/AppInstallerCommonCore/Public/winget/StdErrLogger.h +++ b/src/AppInstallerCommonCore/Public/winget/StdErrLogger.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::Logging -{ - // Sends logs to the stderr stream. - struct StdErrLogger : ILogger - { - StdErrLogger() = default; - - ~StdErrLogger() = default; - - // ILogger - std::string GetName() const override; - - void Write(Channel channel, Level level, std::string_view message) noexcept override; - - void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; - - // Adds OutputDebugStringLogger to the current Log - static void Add(); - - // Removes OutputDebugStringLogger from the current Log - static void Remove(); - - private: - Level m_level = Level::Error; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Logging +{ + // Sends logs to the stderr stream. + struct StdErrLogger : ILogger + { + StdErrLogger() = default; + + ~StdErrLogger() = default; + + // ILogger + std::string GetName() const override; + + void Write(Channel channel, Level level, std::string_view message) noexcept override; + + void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; + + // Adds OutputDebugStringLogger to the current Log + static void Add(); + + // Removes OutputDebugStringLogger from the current Log + static void Remove(); + + private: + Level m_level = Level::Error; + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/TraceLogger.h b/src/AppInstallerCommonCore/Public/winget/TraceLogger.h index e65ca398d9..0ec1746e40 100644 --- a/src/AppInstallerCommonCore/Public/winget/TraceLogger.h +++ b/src/AppInstallerCommonCore/Public/winget/TraceLogger.h @@ -1,30 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include -#include - -namespace AppInstaller::Logging -{ - // Log ETW events for tracing. - // Doesn't save events to a file on disk. - struct TraceLogger : ILogger - { - TraceLogger() = default; - - ~TraceLogger() = default; - - // ILogger - std::string GetName() const override; - - void Write(Channel channel, Level, std::string_view message) noexcept override; - - void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; - - // Adds a TraceLogger to the current Log - static void Add(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include +#include + +namespace AppInstaller::Logging +{ + // Log ETW events for tracing. + // Doesn't save events to a file on disk. + struct TraceLogger : ILogger + { + TraceLogger() = default; + + ~TraceLogger() = default; + + // ILogger + std::string GetName() const override; + + void Write(Channel channel, Level, std::string_view message) noexcept override; + + void WriteDirect(Channel channel, Level level, std::string_view message) noexcept override; + + // Adds a TraceLogger to the current Log + static void Add(); + }; +} diff --git a/src/AppInstallerCommonCore/Public/winget/UserSettings.h b/src/AppInstallerCommonCore/Public/winget/UserSettings.h index 9909d9acd3..0b194d3ce9 100644 --- a/src/AppInstallerCommonCore/Public/winget/UserSettings.h +++ b/src/AppInstallerCommonCore/Public/winget/UserSettings.h @@ -1,314 +1,314 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AppInstallerStrings.h" -#include "AppInstallerLogging.h" -#include "winget/Archive.h" -#include "winget/GroupPolicy.h" -#include "winget/Resources.h" -#include "winget/ManifestCommon.h" - -#include -#include -#include -#include -#include -#include -#include - -#include "AppInstallerArchitecture.h" - -using namespace std::chrono_literals; -using namespace std::string_view_literals; - -namespace AppInstaller::Settings -{ - // The type of argument. - enum class UserSettingsType - { - // Settings files don't exist. A file is created on the first call to the settings command. - Default, - // Loaded settings.json - Standard, - // Loaded settings.json.backup - Backup, - // Loaded from custom settings content - Custom, - }; - - // The visual style of the progress bar. - enum class VisualStyle - { - NoVT, - Retro, - Accent, - Rainbow, - Sixel, - Disabled, - }; - - // Sort field for output ordering. Flag-bit values enable bitmask composition - // via ComputeSortFieldMask, so the constructor can skip unused field computation. - enum class SortField : uint32_t - { - None = 0x0, // Zero value for bitmask initialization; not a user-facing sort field - Name = 0x1, - Id = 0x2, - Version = 0x4, - Source = 0x8, - Available = 0x10, - Relevance = 0x20, // Preserves current natural order (source-defined relevance ranking) - Max = 0x40, // Sentinel for iteration via GetAllExponentialEnumValues; not a valid sort field - }; - - DEFINE_ENUM_FLAG_OPERATORS(SortField); - - // Converts a string to SortField. Returns std::nullopt for unrecognized values. - std::optional ConvertToSortField(std::string_view value); - - // Sort direction for output ordering. - enum class SortDirection - { - Ascending, - Descending, - }; - - // The download code to use for *installers*. - enum class InstallerDownloader - { - Default, - WinInet, - DeliveryOptimization, - }; - - // Enum of settings. - // Must start at 0 to enable direct access to variant in UserSettings. - // Max must be last and unused. - // How to add a setting - // 1 - Add to enum. - // 2 - Implement SettingMap specialization via SETTINGMAPPING_SPECIALIZATION - // Validate will be called by ValidateAll without any more changes. - enum class Setting : size_t - { - // Visual - ProgressBarVisualStyle, - AnonymizePathForDisplay, - EnableSixelDisplay, - // Source - AutoUpdateTimeInMinutes, - // Experimental - EFExperimentalCmd, - EFExperimentalArg, - EFDirectMSI, - EFResume, - EFFonts, - EFSourcePriority, - // Telemetry - TelemetryDisable, - // Install behavior - InstallScopePreference, - InstallScopeRequirement, - InstallArchitecturePreference, - InstallArchitectureRequirement, - InstallLocalePreference, - InstallLocaleRequirement, - InstallerTypePreference, - InstallerTypeRequirement, - InstallDefaultRoot, - InstallSkipDependencies, - ArchiveExtractionMethod, - DisableInstallNotes, - PortablePackageUserRoot, - PortablePackageMachineRoot, - MaxResumes, - // Network - NetworkDownloader, - NetworkDOProgressTimeoutInSeconds, - NetworkWingetAlternateSourceURL, - // Logging - LoggingLevelPreference, - LoggingChannelPreference, - LoggingFileNameStrategy, - LoggingFileAgeLimitInDays, - LoggingFileTotalSizeLimitInMB, - LoggingFileIndividualSizeLimitInMB, - LoggingFileCountLimit, - // Uninstall behavior - UninstallPurgePortablePackage, - // Download behavior - DownloadDefaultDirectory, - // Configure behavior - ConfigureDefaultModuleRoot, - // Interactivity - InteractivityDisable, - // Output behavior - OutputSortOrder, - OutputSortDirection, -#ifndef AICLI_DISABLE_TEST_HOOKS - // Debug - EnableSelfInitiatedMinidump, - KeepAllLogFiles, -#endif - Max - }; - - namespace details - { - template - struct SettingMapping - { - // json_t - type the setting in json. - // value_t - the type of this setting. - // DefaultValue - the value_t default value when setting is absent or semantically wrong. - // Path - json path to the property. See Json::Path in json.h for syntax. So far, this is sufficient - // but since is "brief" and "untested" we might implement our own if needed. - // Validate - Function that does semantic validation. - }; - -#define SETTINGMAPPING_SPECIALIZATION_POLICY(_setting_, _json_, _value_, _default_, _path_, _valuePolicy_) \ - template <> \ - struct SettingMapping<_setting_> \ - { \ - using json_t = _json_; \ - using value_t = _value_; \ - inline static const value_t DefaultValue = _default_; \ - static constexpr std::string_view Path = _path_; \ - static std::optional Validate(const json_t& value); \ - static constexpr ValuePolicy Policy = _valuePolicy_; \ - using policy_t = GroupPolicy::ValueType; \ - static_assert(Policy == ValuePolicy::None || std::is_same::value); \ - } - -#define SETTINGMAPPING_SPECIALIZATION(_setting_, _json_, _value_, _default_, _path_) \ - SETTINGMAPPING_SPECIALIZATION_POLICY(_setting_, _json_, _value_, _default_, _path_, ValuePolicy::None) - - // Visual - SETTINGMAPPING_SPECIALIZATION(Setting::ProgressBarVisualStyle, std::string, VisualStyle, VisualStyle::Accent, ".visual.progressBar"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::AnonymizePathForDisplay, bool, bool, true, ".visual.anonymizeDisplayedPaths"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::EnableSixelDisplay, bool, bool, false, ".visual.enableSixels"sv); - // Source - SETTINGMAPPING_SPECIALIZATION_POLICY(Setting::AutoUpdateTimeInMinutes, uint32_t, std::chrono::minutes, 15min, ".source.autoUpdateIntervalInMinutes"sv, ValuePolicy::SourceAutoUpdateIntervalInMinutes); - // Experimental - SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalCmd, bool, bool, false, ".experimentalFeatures.experimentalCmd"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::EFResume, bool, bool, false, ".experimentalFeatures.resume"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::EFFonts, bool, bool, false, ".experimentalFeatures.fonts"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::EFSourcePriority, bool, bool, false, ".experimentalFeatures.sourcePriority"sv); - // Telemetry - SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); - // Install behavior - SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitecturePreference, std::vector, std::vector, {}, ".installBehavior.preferences.architectures"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitectureRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.architectures"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopePreference, std::string, Manifest::ScopeEnum, Manifest::ScopeEnum::User, ".installBehavior.preferences.scope"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopeRequirement, std::string, Manifest::ScopeEnum, Manifest::ScopeEnum::Unknown, ".installBehavior.requirements.scope"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocalePreference, std::vector, std::vector, {}, ".installBehavior.preferences.locale"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocaleRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.locale"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallerTypePreference, std::vector, std::vector, {}, ".installBehavior.preferences.installerTypes"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallerTypeRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.installerTypes"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallSkipDependencies, bool, bool, false, ".installBehavior.skipDependencies"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::ArchiveExtractionMethod, std::string, Archive::ExtractionMethod, Archive::ExtractionMethod::ShellApi, ".installBehavior.archiveExtractionMethod"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::DisableInstallNotes, bool, bool, false, ".installBehavior.disableInstallNotes"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::PortablePackageUserRoot, std::string, std::filesystem::path, {}, ".installBehavior.portablePackageUserRoot"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::PortablePackageMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portablePackageMachineRoot"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::InstallDefaultRoot, std::string, std::filesystem::path, {}, ".installBehavior.defaultInstallRoot"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::MaxResumes, uint32_t, int, 3, ".installBehavior.maxResumes"sv); - // Uninstall behavior - SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); - // Download behavior - SETTINGMAPPING_SPECIALIZATION(Setting::DownloadDefaultDirectory, std::string, std::filesystem::path, {}, ".downloadBehavior.defaultDownloadDirectory"sv); - // Configure behavior - SETTINGMAPPING_SPECIALIZATION(Setting::ConfigureDefaultModuleRoot, std::string, std::filesystem::path, {}, ".configureBehavior.defaultModuleRoot"sv); - - // Network - SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDOProgressTimeoutInSeconds, uint32_t, std::chrono::seconds, 60s, ".network.doProgressTimeoutInSeconds"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::NetworkWingetAlternateSourceURL, bool, bool, true, ".network.enableWingetAlternateSourceURL"sv); -#ifndef AICLI_DISABLE_TEST_HOOKS - // Debug - SETTINGMAPPING_SPECIALIZATION(Setting::EnableSelfInitiatedMinidump, bool, bool, false, ".debugging.enableSelfInitiatedMinidump"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::KeepAllLogFiles, bool, bool, false, ".debugging.keepAllLogFiles"sv); -#endif - // Logging - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingLevelPreference, std::string, Logging::Level, Logging::Level::Info, ".logging.level"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingChannelPreference, std::vector, Logging::Channel, Logging::Channel::Defaults, ".logging.channels"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileNameStrategy, std::string, Logging::LogNameStrategy, Logging::LogNameStrategy::Manifest, ".logging.fileNameStrategy"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileAgeLimitInDays, uint32_t, std::chrono::hours, (7 * 24h), ".logging.file.ageLimitInDays"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileTotalSizeLimitInMB, uint32_t, uint32_t, 128, ".logging.file.totalSizeLimitInMB"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileIndividualSizeLimitInMB, uint32_t, uint32_t, 16, ".logging.file.individualSizeLimitInMB"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileCountLimit, uint32_t, uint32_t, 0, ".logging.file.countLimit"sv); - // Interactivity - SETTINGMAPPING_SPECIALIZATION(Setting::InteractivityDisable, bool, bool, false, ".interactivity.disable"sv); - // Output behavior - SETTINGMAPPING_SPECIALIZATION(Setting::OutputSortOrder, std::vector, std::vector, std::vector{}, ".output.sortOrder"sv); - SETTINGMAPPING_SPECIALIZATION(Setting::OutputSortDirection, std::string, SortDirection, SortDirection::Ascending, ".output.sortDirection"sv); - - // Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types. - template - inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } - - // Holds data of any type listed in a SettingMapping. - using SettingVariant = decltype(Deduce(std::make_index_sequence(Setting::Max)>())); - - // Gets the index into the variant for the given Setting. - constexpr inline size_t SettingIndex(Setting s) { return static_cast(s) + 1; } - } - - // Representation of the parsed settings file. - struct UserSettings - { - // Jsoncpp doesn't provide line number and column for an individual Json::Value node. - struct Warning - { - Warning(StringResource::StringId message) : Message(message) {} - Warning(StringResource::StringId message, std::string_view settingPath) : Message(message), Path(settingPath) {} - Warning(StringResource::StringId message, std::string_view settingPath, std::string_view settingValue, bool isField = true) : - Message(message), Path(settingPath), Data(settingValue), IsFieldWarning(isField) {} - - StringResource::StringId Message; - Utility::LocIndString Path; - Utility::LocIndString Data; - bool IsFieldWarning = true; - }; - - static UserSettings const& Instance(const std::optional& content = std::nullopt); - - static std::filesystem::path SettingsFilePath(bool forDisplay = false); - - UserSettings(const UserSettings&) = delete; - UserSettings& operator=(const UserSettings&) = delete; - - UserSettings(UserSettings&&) = delete; - UserSettings& operator=(UserSettings&&) = delete; - - UserSettingsType GetType() const { return m_type; } - std::vector const& GetWarnings() const { return m_warnings; } - - void PrepareToShellExecuteFile() const; - - // Gets setting value, if its not in the map it returns the default value. - template - typename details::SettingMapping::value_t Get() const - { - auto itr = m_settings.find(S); - if (itr == m_settings.end()) - { - return details::SettingMapping::DefaultValue; - } - - return std::get(itr->second); - } - - protected: - UserSettingsType m_type = UserSettingsType::Default; - std::vector m_warnings; - std::map m_settings; - - UserSettings(const std::optional& content = std::nullopt); - ~UserSettings() = default; - }; - - const UserSettings* TryGetUser(); - UserSettings const& User(); - bool TryInitializeCustomUserSettings(std::string content); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AppInstallerStrings.h" +#include "AppInstallerLogging.h" +#include "winget/Archive.h" +#include "winget/GroupPolicy.h" +#include "winget/Resources.h" +#include "winget/ManifestCommon.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "AppInstallerArchitecture.h" + +using namespace std::chrono_literals; +using namespace std::string_view_literals; + +namespace AppInstaller::Settings +{ + // The type of argument. + enum class UserSettingsType + { + // Settings files don't exist. A file is created on the first call to the settings command. + Default, + // Loaded settings.json + Standard, + // Loaded settings.json.backup + Backup, + // Loaded from custom settings content + Custom, + }; + + // The visual style of the progress bar. + enum class VisualStyle + { + NoVT, + Retro, + Accent, + Rainbow, + Sixel, + Disabled, + }; + + // Sort field for output ordering. Flag-bit values enable bitmask composition + // via ComputeSortFieldMask, so the constructor can skip unused field computation. + enum class SortField : uint32_t + { + None = 0x0, // Zero value for bitmask initialization; not a user-facing sort field + Name = 0x1, + Id = 0x2, + Version = 0x4, + Source = 0x8, + Available = 0x10, + Relevance = 0x20, // Preserves current natural order (source-defined relevance ranking) + Max = 0x40, // Sentinel for iteration via GetAllExponentialEnumValues; not a valid sort field + }; + + DEFINE_ENUM_FLAG_OPERATORS(SortField); + + // Converts a string to SortField. Returns std::nullopt for unrecognized values. + std::optional ConvertToSortField(std::string_view value); + + // Sort direction for output ordering. + enum class SortDirection + { + Ascending, + Descending, + }; + + // The download code to use for *installers*. + enum class InstallerDownloader + { + Default, + WinInet, + DeliveryOptimization, + }; + + // Enum of settings. + // Must start at 0 to enable direct access to variant in UserSettings. + // Max must be last and unused. + // How to add a setting + // 1 - Add to enum. + // 2 - Implement SettingMap specialization via SETTINGMAPPING_SPECIALIZATION + // Validate will be called by ValidateAll without any more changes. + enum class Setting : size_t + { + // Visual + ProgressBarVisualStyle, + AnonymizePathForDisplay, + EnableSixelDisplay, + // Source + AutoUpdateTimeInMinutes, + // Experimental + EFExperimentalCmd, + EFExperimentalArg, + EFDirectMSI, + EFResume, + EFFonts, + EFSourcePriority, + // Telemetry + TelemetryDisable, + // Install behavior + InstallScopePreference, + InstallScopeRequirement, + InstallArchitecturePreference, + InstallArchitectureRequirement, + InstallLocalePreference, + InstallLocaleRequirement, + InstallerTypePreference, + InstallerTypeRequirement, + InstallDefaultRoot, + InstallSkipDependencies, + ArchiveExtractionMethod, + DisableInstallNotes, + PortablePackageUserRoot, + PortablePackageMachineRoot, + MaxResumes, + // Network + NetworkDownloader, + NetworkDOProgressTimeoutInSeconds, + NetworkWingetAlternateSourceURL, + // Logging + LoggingLevelPreference, + LoggingChannelPreference, + LoggingFileNameStrategy, + LoggingFileAgeLimitInDays, + LoggingFileTotalSizeLimitInMB, + LoggingFileIndividualSizeLimitInMB, + LoggingFileCountLimit, + // Uninstall behavior + UninstallPurgePortablePackage, + // Download behavior + DownloadDefaultDirectory, + // Configure behavior + ConfigureDefaultModuleRoot, + // Interactivity + InteractivityDisable, + // Output behavior + OutputSortOrder, + OutputSortDirection, +#ifndef AICLI_DISABLE_TEST_HOOKS + // Debug + EnableSelfInitiatedMinidump, + KeepAllLogFiles, +#endif + Max + }; + + namespace details + { + template + struct SettingMapping + { + // json_t - type the setting in json. + // value_t - the type of this setting. + // DefaultValue - the value_t default value when setting is absent or semantically wrong. + // Path - json path to the property. See Json::Path in json.h for syntax. So far, this is sufficient + // but since is "brief" and "untested" we might implement our own if needed. + // Validate - Function that does semantic validation. + }; + +#define SETTINGMAPPING_SPECIALIZATION_POLICY(_setting_, _json_, _value_, _default_, _path_, _valuePolicy_) \ + template <> \ + struct SettingMapping<_setting_> \ + { \ + using json_t = _json_; \ + using value_t = _value_; \ + inline static const value_t DefaultValue = _default_; \ + static constexpr std::string_view Path = _path_; \ + static std::optional Validate(const json_t& value); \ + static constexpr ValuePolicy Policy = _valuePolicy_; \ + using policy_t = GroupPolicy::ValueType; \ + static_assert(Policy == ValuePolicy::None || std::is_same::value); \ + } + +#define SETTINGMAPPING_SPECIALIZATION(_setting_, _json_, _value_, _default_, _path_) \ + SETTINGMAPPING_SPECIALIZATION_POLICY(_setting_, _json_, _value_, _default_, _path_, ValuePolicy::None) + + // Visual + SETTINGMAPPING_SPECIALIZATION(Setting::ProgressBarVisualStyle, std::string, VisualStyle, VisualStyle::Accent, ".visual.progressBar"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::AnonymizePathForDisplay, bool, bool, true, ".visual.anonymizeDisplayedPaths"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EnableSixelDisplay, bool, bool, false, ".visual.enableSixels"sv); + // Source + SETTINGMAPPING_SPECIALIZATION_POLICY(Setting::AutoUpdateTimeInMinutes, uint32_t, std::chrono::minutes, 15min, ".source.autoUpdateIntervalInMinutes"sv, ValuePolicy::SourceAutoUpdateIntervalInMinutes); + // Experimental + SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalCmd, bool, bool, false, ".experimentalFeatures.experimentalCmd"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFExperimentalArg, bool, bool, false, ".experimentalFeatures.experimentalArg"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFDirectMSI, bool, bool, false, ".experimentalFeatures.directMSI"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFResume, bool, bool, false, ".experimentalFeatures.resume"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFFonts, bool, bool, false, ".experimentalFeatures.fonts"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::EFSourcePriority, bool, bool, false, ".experimentalFeatures.sourcePriority"sv); + // Telemetry + SETTINGMAPPING_SPECIALIZATION(Setting::TelemetryDisable, bool, bool, false, ".telemetry.disable"sv); + // Install behavior + SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitecturePreference, std::vector, std::vector, {}, ".installBehavior.preferences.architectures"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallArchitectureRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.architectures"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopePreference, std::string, Manifest::ScopeEnum, Manifest::ScopeEnum::User, ".installBehavior.preferences.scope"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallScopeRequirement, std::string, Manifest::ScopeEnum, Manifest::ScopeEnum::Unknown, ".installBehavior.requirements.scope"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocalePreference, std::vector, std::vector, {}, ".installBehavior.preferences.locale"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallLocaleRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.locale"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallerTypePreference, std::vector, std::vector, {}, ".installBehavior.preferences.installerTypes"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallerTypeRequirement, std::vector, std::vector, {}, ".installBehavior.requirements.installerTypes"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallSkipDependencies, bool, bool, false, ".installBehavior.skipDependencies"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::ArchiveExtractionMethod, std::string, Archive::ExtractionMethod, Archive::ExtractionMethod::ShellApi, ".installBehavior.archiveExtractionMethod"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::DisableInstallNotes, bool, bool, false, ".installBehavior.disableInstallNotes"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::PortablePackageUserRoot, std::string, std::filesystem::path, {}, ".installBehavior.portablePackageUserRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::PortablePackageMachineRoot, std::string, std::filesystem::path, {}, ".installBehavior.portablePackageMachineRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::InstallDefaultRoot, std::string, std::filesystem::path, {}, ".installBehavior.defaultInstallRoot"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::MaxResumes, uint32_t, int, 3, ".installBehavior.maxResumes"sv); + // Uninstall behavior + SETTINGMAPPING_SPECIALIZATION(Setting::UninstallPurgePortablePackage, bool, bool, false, ".uninstallBehavior.purgePortablePackage"sv); + // Download behavior + SETTINGMAPPING_SPECIALIZATION(Setting::DownloadDefaultDirectory, std::string, std::filesystem::path, {}, ".downloadBehavior.defaultDownloadDirectory"sv); + // Configure behavior + SETTINGMAPPING_SPECIALIZATION(Setting::ConfigureDefaultModuleRoot, std::string, std::filesystem::path, {}, ".configureBehavior.defaultModuleRoot"sv); + + // Network + SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDownloader, std::string, InstallerDownloader, InstallerDownloader::Default, ".network.downloader"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::NetworkDOProgressTimeoutInSeconds, uint32_t, std::chrono::seconds, 60s, ".network.doProgressTimeoutInSeconds"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::NetworkWingetAlternateSourceURL, bool, bool, true, ".network.enableWingetAlternateSourceURL"sv); +#ifndef AICLI_DISABLE_TEST_HOOKS + // Debug + SETTINGMAPPING_SPECIALIZATION(Setting::EnableSelfInitiatedMinidump, bool, bool, false, ".debugging.enableSelfInitiatedMinidump"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::KeepAllLogFiles, bool, bool, false, ".debugging.keepAllLogFiles"sv); +#endif + // Logging + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingLevelPreference, std::string, Logging::Level, Logging::Level::Info, ".logging.level"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingChannelPreference, std::vector, Logging::Channel, Logging::Channel::Defaults, ".logging.channels"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileNameStrategy, std::string, Logging::LogNameStrategy, Logging::LogNameStrategy::Manifest, ".logging.fileNameStrategy"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileAgeLimitInDays, uint32_t, std::chrono::hours, (7 * 24h), ".logging.file.ageLimitInDays"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileTotalSizeLimitInMB, uint32_t, uint32_t, 128, ".logging.file.totalSizeLimitInMB"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileIndividualSizeLimitInMB, uint32_t, uint32_t, 16, ".logging.file.individualSizeLimitInMB"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::LoggingFileCountLimit, uint32_t, uint32_t, 0, ".logging.file.countLimit"sv); + // Interactivity + SETTINGMAPPING_SPECIALIZATION(Setting::InteractivityDisable, bool, bool, false, ".interactivity.disable"sv); + // Output behavior + SETTINGMAPPING_SPECIALIZATION(Setting::OutputSortOrder, std::vector, std::vector, std::vector{}, ".output.sortOrder"sv); + SETTINGMAPPING_SPECIALIZATION(Setting::OutputSortDirection, std::string, SortDirection, SortDirection::Ascending, ".output.sortDirection"sv); + + // Used to deduce the SettingVariant type; making a variant that includes std::monostate and all SettingMapping types. + template + inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } + + // Holds data of any type listed in a SettingMapping. + using SettingVariant = decltype(Deduce(std::make_index_sequence(Setting::Max)>())); + + // Gets the index into the variant for the given Setting. + constexpr inline size_t SettingIndex(Setting s) { return static_cast(s) + 1; } + } + + // Representation of the parsed settings file. + struct UserSettings + { + // Jsoncpp doesn't provide line number and column for an individual Json::Value node. + struct Warning + { + Warning(StringResource::StringId message) : Message(message) {} + Warning(StringResource::StringId message, std::string_view settingPath) : Message(message), Path(settingPath) {} + Warning(StringResource::StringId message, std::string_view settingPath, std::string_view settingValue, bool isField = true) : + Message(message), Path(settingPath), Data(settingValue), IsFieldWarning(isField) {} + + StringResource::StringId Message; + Utility::LocIndString Path; + Utility::LocIndString Data; + bool IsFieldWarning = true; + }; + + static UserSettings const& Instance(const std::optional& content = std::nullopt); + + static std::filesystem::path SettingsFilePath(bool forDisplay = false); + + UserSettings(const UserSettings&) = delete; + UserSettings& operator=(const UserSettings&) = delete; + + UserSettings(UserSettings&&) = delete; + UserSettings& operator=(UserSettings&&) = delete; + + UserSettingsType GetType() const { return m_type; } + std::vector const& GetWarnings() const { return m_warnings; } + + void PrepareToShellExecuteFile() const; + + // Gets setting value, if its not in the map it returns the default value. + template + typename details::SettingMapping::value_t Get() const + { + auto itr = m_settings.find(S); + if (itr == m_settings.end()) + { + return details::SettingMapping::DefaultValue; + } + + return std::get(itr->second); + } + + protected: + UserSettingsType m_type = UserSettingsType::Default; + std::vector m_warnings; + std::map m_settings; + + UserSettings(const std::optional& content = std::nullopt); + ~UserSettings() = default; + }; + + const UserSettings* TryGetUser(); + UserSettings const& User(); + bool TryInitializeCustomUserSettings(std::string content); +} diff --git a/src/AppInstallerCommonCore/Regex.cpp b/src/AppInstallerCommonCore/Regex.cpp index 934be59b39..37f7d21279 100644 --- a/src/AppInstallerCommonCore/Regex.cpp +++ b/src/AppInstallerCommonCore/Regex.cpp @@ -1,276 +1,276 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/Regex.h" -#include "Public/AppInstallerErrors.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerLanguageUtilities.h" - -#define WINGET_THROW_REGEX_ERROR_IF_FAILED(_err_,_func_) \ - if (U_FAILURE(_err_)) \ - { \ - AICLI_LOG(Core, Error, << #_func_ " returned " << _err_); \ - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR); \ - } - - -namespace AppInstaller::Regex -{ - struct Expression::impl - { - using uregex_ptr = wil::unique_any; - using utext_ptr = wil::unique_any; - - // Create caches the original ICU regex objects in a static map and hands out copies of them - // when requested. Since we have a limited set, this is a very simple cache-all-forever pattern. - static std::unique_ptr Create(std::string_view pattern, Options options) - { - struct key - { - std::string pattern; - Options options = Options::None; - - bool operator<(const key& other) const - { - if (pattern < other.pattern) - { - return true; - } - else if (pattern == other.pattern) - { - return ToIntegral(options) < ToIntegral(other.options); - } - else - { - return false; - } - } - }; - - struct statics - { - std::map map; - wil::srwlock lock; - }; - - static statics s_regex_cache; - - key requested; - requested.pattern = pattern; - requested.options = options; - - { - // Attempt to find in the cache - auto sharedLock = s_regex_cache.lock.lock_shared(); - - auto itr = s_regex_cache.map.find(requested); - if (itr != s_regex_cache.map.end()) - { - return std::make_unique(itr->second); - } - } - - auto exclusiveLock = s_regex_cache.lock.lock_exclusive(); - - // Check if another thread created it while we waited for the lock. - auto itr = s_regex_cache.map.find(requested); - if (itr != s_regex_cache.map.end()) - { - return std::make_unique(itr->second); - } - else - { - return std::make_unique(s_regex_cache.map.emplace(std::move(requested), impl{ pattern, options }).first->second); - } - } - - impl(std::string_view pattern, Options options) - { - UErrorCode uec = U_ZERO_ERROR; - - utext_ptr patternUtext{ utext_openUTF8(nullptr, pattern.data(), pattern.length(), &uec) }; - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8); - - // For now, just handle the one option - uint32_t flags = 0; - - if (options == Options::CaseInsensitive) - { - flags = UREGEX_CASE_INSENSITIVE; - } - - UParseError parseError{}; - - m_regex.reset(uregex_openUText(patternUtext.get(), flags, &parseError, &uec)); - - if (U_FAILURE(uec)) - { - AICLI_LOG(Core, Error, << "uregex_openUText failed with error [" << uec << "] at line " << parseError.line << ", position " << parseError.offset << '\n' << pattern); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR); - } - } - - impl(const impl& other) - { - UErrorCode uec = U_ZERO_ERROR; - - m_regex.reset(uregex_clone(other.m_regex.get(), &uec)); - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_clone); - } - - impl& operator=(const impl& other) - { - *this = impl{ other }; - return *this; - } - - impl(impl&&) = default; - impl& operator=(impl&&) = default; - - ~impl() = default; - - bool IsMatch(std::wstring_view input) const - { - UErrorCode uec = U_ZERO_ERROR; - - SetText(input); - - UBool result = uregex_matches(m_regex.get(), -1, &uec); - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_matches); - - return !!result; - } - - std::wstring Replace(std::wstring_view input, std::wstring_view replacement) const - { - UErrorCode uec = U_ZERO_ERROR; - - SetText(input); - - std::u16string_view u16replacement = Convert(replacement); - utext_ptr replacementUtext{ utext_openUChars(nullptr, u16replacement.data(), u16replacement.length(), &uec) }; - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8); - - utext_ptr resultUText{ uregex_replaceAllUText(m_regex.get(), replacementUtext.get(), nullptr, &uec) }; - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_replaceAllUText); - - int64_t cch = utext_nativeLength(resultUText.get()); - std::wstring result(static_cast(cch), '\0'); - - utext_extract(resultUText.get(), 0, std::numeric_limits::max(), reinterpret_cast(&result[0]), static_cast(result.size()), &uec); - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_extract); - - return result; - } - - void ForEach(std::wstring_view input, const std::function&f) const - { - UErrorCode uec = U_ZERO_ERROR; - - SetText(input); - int32_t startPos = 0; - - while (uregex_findNext(m_regex.get(), &uec)) - { - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext); - - int32_t pos = uregex_start(m_regex.get(), 0, &uec); - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_start); - THROW_HR_IF(E_UNEXPECTED, pos == -1); - - // First, send off the unmatched part before the match - if (pos > startPos) - { - if (!f(false, input.substr(startPos, static_cast(pos) - startPos))) - { - return; - } - } - - // Now send the matched part - int32_t end = uregex_end(m_regex.get(), 0, &uec); - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_end); - THROW_HR_IF(E_UNEXPECTED, end == -1); - - if (!f(true, input.substr(pos, static_cast(end) - pos))) - { - return; - } - - startPos = end; - } - - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext); - - // Finally, send any remaining part - if (input.length() > static_cast(startPos)) - { - f(false, input.substr(startPos)); - } - } - - private: - static std::u16string_view Convert(std::wstring_view input) - { - static_assert(sizeof(wchar_t) == sizeof(char16_t), "wchar_t and char16_t must be the same size"); - return { reinterpret_cast(input.data()), input.size() }; - } - - void SetText(std::wstring_view input) const - { - UErrorCode uec = U_ZERO_ERROR; - - std::u16string_view u16 = Convert(input); - - uregex_setText(m_regex.get(), u16.data(), static_cast(u16.length()), &uec); - WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_setText); - } - - uregex_ptr m_regex; - }; - - Expression::Expression() = default; - - Expression::Expression(std::string_view pattern, Options options) : pImpl(impl::Create(pattern, options)) {} - - Expression::Expression(const Expression& other) - { - if (other.pImpl) - { - pImpl = std::make_unique(*other.pImpl); - } - } - - Expression& Expression::operator=(const Expression& other) - { - return *this = Expression{ other }; - } - - Expression::Expression(Expression&&) noexcept = default; - Expression& Expression::operator=(Expression&&) noexcept = default; - - Expression::~Expression() = default; - - Expression::operator bool() const - { - return static_cast(pImpl); - } - - bool Expression::IsMatch(std::wstring_view input) const - { - THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); - return pImpl->IsMatch(input); - } - - std::wstring Expression::Replace(std::wstring_view input, std::wstring_view replacement) const - { - THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); - return pImpl->Replace(input, replacement); - } - - void Expression::ForEach(std::wstring_view input, const std::function& f) const - { - THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); - return pImpl->ForEach(input, f); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Regex.h" +#include "Public/AppInstallerErrors.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerLanguageUtilities.h" + +#define WINGET_THROW_REGEX_ERROR_IF_FAILED(_err_,_func_) \ + if (U_FAILURE(_err_)) \ + { \ + AICLI_LOG(Core, Error, << #_func_ " returned " << _err_); \ + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR); \ + } + + +namespace AppInstaller::Regex +{ + struct Expression::impl + { + using uregex_ptr = wil::unique_any; + using utext_ptr = wil::unique_any; + + // Create caches the original ICU regex objects in a static map and hands out copies of them + // when requested. Since we have a limited set, this is a very simple cache-all-forever pattern. + static std::unique_ptr Create(std::string_view pattern, Options options) + { + struct key + { + std::string pattern; + Options options = Options::None; + + bool operator<(const key& other) const + { + if (pattern < other.pattern) + { + return true; + } + else if (pattern == other.pattern) + { + return ToIntegral(options) < ToIntegral(other.options); + } + else + { + return false; + } + } + }; + + struct statics + { + std::map map; + wil::srwlock lock; + }; + + static statics s_regex_cache; + + key requested; + requested.pattern = pattern; + requested.options = options; + + { + // Attempt to find in the cache + auto sharedLock = s_regex_cache.lock.lock_shared(); + + auto itr = s_regex_cache.map.find(requested); + if (itr != s_regex_cache.map.end()) + { + return std::make_unique(itr->second); + } + } + + auto exclusiveLock = s_regex_cache.lock.lock_exclusive(); + + // Check if another thread created it while we waited for the lock. + auto itr = s_regex_cache.map.find(requested); + if (itr != s_regex_cache.map.end()) + { + return std::make_unique(itr->second); + } + else + { + return std::make_unique(s_regex_cache.map.emplace(std::move(requested), impl{ pattern, options }).first->second); + } + } + + impl(std::string_view pattern, Options options) + { + UErrorCode uec = U_ZERO_ERROR; + + utext_ptr patternUtext{ utext_openUTF8(nullptr, pattern.data(), pattern.length(), &uec) }; + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8); + + // For now, just handle the one option + uint32_t flags = 0; + + if (options == Options::CaseInsensitive) + { + flags = UREGEX_CASE_INSENSITIVE; + } + + UParseError parseError{}; + + m_regex.reset(uregex_openUText(patternUtext.get(), flags, &parseError, &uec)); + + if (U_FAILURE(uec)) + { + AICLI_LOG(Core, Error, << "uregex_openUText failed with error [" << uec << "] at line " << parseError.line << ", position " << parseError.offset << '\n' << pattern); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR); + } + } + + impl(const impl& other) + { + UErrorCode uec = U_ZERO_ERROR; + + m_regex.reset(uregex_clone(other.m_regex.get(), &uec)); + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_clone); + } + + impl& operator=(const impl& other) + { + *this = impl{ other }; + return *this; + } + + impl(impl&&) = default; + impl& operator=(impl&&) = default; + + ~impl() = default; + + bool IsMatch(std::wstring_view input) const + { + UErrorCode uec = U_ZERO_ERROR; + + SetText(input); + + UBool result = uregex_matches(m_regex.get(), -1, &uec); + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_matches); + + return !!result; + } + + std::wstring Replace(std::wstring_view input, std::wstring_view replacement) const + { + UErrorCode uec = U_ZERO_ERROR; + + SetText(input); + + std::u16string_view u16replacement = Convert(replacement); + utext_ptr replacementUtext{ utext_openUChars(nullptr, u16replacement.data(), u16replacement.length(), &uec) }; + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_openUTF8); + + utext_ptr resultUText{ uregex_replaceAllUText(m_regex.get(), replacementUtext.get(), nullptr, &uec) }; + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_replaceAllUText); + + int64_t cch = utext_nativeLength(resultUText.get()); + std::wstring result(static_cast(cch), '\0'); + + utext_extract(resultUText.get(), 0, std::numeric_limits::max(), reinterpret_cast(&result[0]), static_cast(result.size()), &uec); + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, utext_extract); + + return result; + } + + void ForEach(std::wstring_view input, const std::function&f) const + { + UErrorCode uec = U_ZERO_ERROR; + + SetText(input); + int32_t startPos = 0; + + while (uregex_findNext(m_regex.get(), &uec)) + { + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext); + + int32_t pos = uregex_start(m_regex.get(), 0, &uec); + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_start); + THROW_HR_IF(E_UNEXPECTED, pos == -1); + + // First, send off the unmatched part before the match + if (pos > startPos) + { + if (!f(false, input.substr(startPos, static_cast(pos) - startPos))) + { + return; + } + } + + // Now send the matched part + int32_t end = uregex_end(m_regex.get(), 0, &uec); + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_end); + THROW_HR_IF(E_UNEXPECTED, end == -1); + + if (!f(true, input.substr(pos, static_cast(end) - pos))) + { + return; + } + + startPos = end; + } + + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_findNext); + + // Finally, send any remaining part + if (input.length() > static_cast(startPos)) + { + f(false, input.substr(startPos)); + } + } + + private: + static std::u16string_view Convert(std::wstring_view input) + { + static_assert(sizeof(wchar_t) == sizeof(char16_t), "wchar_t and char16_t must be the same size"); + return { reinterpret_cast(input.data()), input.size() }; + } + + void SetText(std::wstring_view input) const + { + UErrorCode uec = U_ZERO_ERROR; + + std::u16string_view u16 = Convert(input); + + uregex_setText(m_regex.get(), u16.data(), static_cast(u16.length()), &uec); + WINGET_THROW_REGEX_ERROR_IF_FAILED(uec, uregex_setText); + } + + uregex_ptr m_regex; + }; + + Expression::Expression() = default; + + Expression::Expression(std::string_view pattern, Options options) : pImpl(impl::Create(pattern, options)) {} + + Expression::Expression(const Expression& other) + { + if (other.pImpl) + { + pImpl = std::make_unique(*other.pImpl); + } + } + + Expression& Expression::operator=(const Expression& other) + { + return *this = Expression{ other }; + } + + Expression::Expression(Expression&&) noexcept = default; + Expression& Expression::operator=(Expression&&) noexcept = default; + + Expression::~Expression() = default; + + Expression::operator bool() const + { + return static_cast(pImpl); + } + + bool Expression::IsMatch(std::wstring_view input) const + { + THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); + return pImpl->IsMatch(input); + } + + std::wstring Expression::Replace(std::wstring_view input, std::wstring_view replacement) const + { + THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); + return pImpl->Replace(input, replacement); + } + + void Expression::ForEach(std::wstring_view input, const std::function& f) const + { + THROW_HR_IF(E_NOT_VALID_STATE, !pImpl); + return pImpl->ForEach(input, f); + } +} diff --git a/src/AppInstallerCommonCore/Rest.cpp b/src/AppInstallerCommonCore/Rest.cpp index cf0b63f9ee..f486904f0b 100644 --- a/src/AppInstallerCommonCore/Rest.cpp +++ b/src/AppInstallerCommonCore/Rest.cpp @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerStrings.h" -#include "winget/Rest.h" -#include - -namespace AppInstaller::Rest -{ - utility::string_t GetRestAPIBaseUri(std::string uri) - { - // Trim - if (!uri.empty()) - { - uri = AppInstaller::Utility::Trim(uri); - - // Remove trailing forward slash - if (uri.back() == '/') - { - uri.pop_back(); - } - } - - // Encode the Uri - return web::uri::encode_uri(JSON::GetUtilityString(uri)); - } - - bool IsValidUri(const utility::string_t& restApiUri) - { - return web::uri::validate(restApiUri); - } - - utility::string_t AppendPathToUri(const utility::string_t& restApiUri, const utility::string_t& path) - { - web::uri_builder builder(restApiUri); - builder.append_path(path, true); - return builder.to_string(); - } - - utility::string_t MakeQueryParam(std::string_view queryName, const std::string& queryValue) - { - std::string queryParam; - queryParam.append(queryName).append("=").append(queryValue); - - return utility::conversions::to_string_t(queryParam); - } - - utility::string_t AppendQueryParamsToUri(const utility::string_t& uri, const std::map& queryParameters) - { - web::http::uri_builder builder{ uri }; - - for (auto& pair : queryParameters) - { - builder.append_query(MakeQueryParam(pair.first, pair.second), true); - } - - return builder.to_string(); - } - - std::vector GetUniqueItems(const std::vector& list) - { - std::set set; - for (const auto& item : list) - { - set.emplace(item); - } - - std::vector result{ set.begin(), set.end() }; - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerStrings.h" +#include "winget/Rest.h" +#include + +namespace AppInstaller::Rest +{ + utility::string_t GetRestAPIBaseUri(std::string uri) + { + // Trim + if (!uri.empty()) + { + uri = AppInstaller::Utility::Trim(uri); + + // Remove trailing forward slash + if (uri.back() == '/') + { + uri.pop_back(); + } + } + + // Encode the Uri + return web::uri::encode_uri(JSON::GetUtilityString(uri)); + } + + bool IsValidUri(const utility::string_t& restApiUri) + { + return web::uri::validate(restApiUri); + } + + utility::string_t AppendPathToUri(const utility::string_t& restApiUri, const utility::string_t& path) + { + web::uri_builder builder(restApiUri); + builder.append_path(path, true); + return builder.to_string(); + } + + utility::string_t MakeQueryParam(std::string_view queryName, const std::string& queryValue) + { + std::string queryParam; + queryParam.append(queryName).append("=").append(queryValue); + + return utility::conversions::to_string_t(queryParam); + } + + utility::string_t AppendQueryParamsToUri(const utility::string_t& uri, const std::map& queryParameters) + { + web::http::uri_builder builder{ uri }; + + for (auto& pair : queryParameters) + { + builder.append_query(MakeQueryParam(pair.first, pair.second), true); + } + + return builder.to_string(); + } + + std::vector GetUniqueItems(const std::vector& list) + { + std::set set; + for (const auto& item : list) + { + set.emplace(item); + } + + std::vector result{ set.begin(), set.end() }; + return result; + } +} diff --git a/src/AppInstallerCommonCore/Runtime.cpp b/src/AppInstallerCommonCore/Runtime.cpp index 1618776842..56f3feb1f6 100644 --- a/src/AppInstallerCommonCore/Runtime.cpp +++ b/src/AppInstallerCommonCore/Runtime.cpp @@ -1,610 +1,610 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerStrings.h" -#include "Public/winget/UserSettings.h" -#include "Public/winget/Registry.h" -#include - - -#define WINGET_DEFAULT_LOG_DIRECTORY "DiagOutputDir" - -namespace AppInstaller::Runtime -{ - using namespace Utility; - using namespace Settings; - using namespace Filesystem; - - namespace - { - using namespace std::string_view_literals; - constexpr std::string_view s_DefaultTempDirectory = "WinGet"sv; - constexpr std::string_view s_SettingsFile_Relative = "Settings"sv; - constexpr std::string_view s_SecureSettings_Base = "Microsoft\\WinGet"sv; - constexpr std::string_view s_SecureSettings_UserRelative = "settings"sv; - constexpr std::string_view s_SecureSettings_Relative_Unpackaged = "win"sv; - constexpr std::string_view s_PortablePackageUserRoot_Base = "Microsoft"sv; - constexpr std::string_view s_PortablePackageRoot = "WinGet"sv; - 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; -#else - constexpr std::string_view s_ImageAssetsDirectoryRelative = "Images"sv; -#endif - constexpr std::string_view s_CheckpointsDirectory = "Checkpoints"sv; - constexpr std::string_view s_DevModeSubkey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock"sv; - constexpr std::string_view s_AllowDevelopmentWithoutDevLicense = "AllowDevelopmentWithoutDevLicense"sv; -#ifndef WINGET_DISABLE_FOR_FUZZING - constexpr std::string_view s_SecureSettings_Relative_Packaged = "pkg"sv; -#endif - constexpr std::string_view s_RuntimePath_Unpackaged_DefaultState = "defaultState"sv; - - constexpr std::string_view s_UserProfileEnvironmentVariable = "%USERPROFILE%"; - constexpr std::string_view s_LocalAppDataEnvironmentVariable = "%LOCALAPPDATA%"; - constexpr std::string_view s_WindowsApps_Base = "Microsoft\\WindowsApps"sv; - constexpr std::string_view s_WinGetDev_Exe = "wingetdev.exe"; - constexpr std::string_view s_WinGet_Exe = "winget.exe"; - constexpr std::string_view s_WinGetMCPDev_Exe = "WindowsPackageManagerMCPServerDev.exe"; - constexpr std::string_view s_WinGetMCP_Exe = "WindowsPackageManagerMCPServer.exe"; - - static std::optional s_runtimePathStateName; - static wil::srwlock s_runtimePathStateNameLock; - - // Gets the path to the root of the package containing the current process. - std::filesystem::path GetPackagePath() - { - wchar_t packageFullName[PACKAGE_FULL_NAME_MAX_LENGTH + 1]; - UINT32 nameLength = ARRAYSIZE(packageFullName); - THROW_IF_WIN32_ERROR(GetPackageFullName(GetCurrentProcess(), &nameLength, packageFullName)); - - UINT32 pathLength = 0; - LONG result = GetPackagePathByFullName(packageFullName, &pathLength, nullptr); - THROW_HR_IF(HRESULT_FROM_WIN32(result), result != ERROR_INSUFFICIENT_BUFFER); - - std::unique_ptr buffer = std::make_unique(pathLength); - THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName, &pathLength, buffer.get())); - - return { buffer.get() }; - } - - // Gets the path to the directory containing the currently executing binary file. - std::filesystem::path GetBinaryDirectoryPath() - { - HMODULE moduleHandle = NULL; - THROW_IF_WIN32_BOOL_FALSE(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - (LPCWSTR)&GetBinaryDirectoryPath, &moduleHandle)); - - // Get the path for this module. - wil::unique_process_heap_string binaryPath; - THROW_IF_FAILED(wil::GetModuleFileNameW(moduleHandle, binaryPath)); - - std::filesystem::path resultFilePath{ binaryPath.get() }; - return resultFilePath.parent_path(); - } - - std::unique_ptr GetPACKAGE_ID() - { - UINT32 bufferLength = 0; - LONG gcpiResult = GetCurrentPackageId(&bufferLength, nullptr); - THROW_HR_IF(E_UNEXPECTED, gcpiResult != ERROR_INSUFFICIENT_BUFFER); - - std::unique_ptr buffer = std::make_unique(bufferLength); - - gcpiResult = GetCurrentPackageId(&bufferLength, buffer.get()); - if (FAILED_WIN32_LOG(gcpiResult)) - { - return {}; - } - - return buffer; - } - - // Gets the package name; only succeeds if running in a packaged context. - std::string GetPackageName() - { - std::unique_ptr buffer = GetPACKAGE_ID(); - if (!buffer) - { - return {}; - } - - PACKAGE_ID* packageId = reinterpret_cast(buffer.get()); - return Utility::ConvertToUTF8(packageId->name); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static std::map s_Path_TestHook_Overrides; -#endif - - // Gets the user's temp path - std::filesystem::path GetPathToUserTemp(bool forDisplay) - { - if (forDisplay && Settings::User().Get()) - { - return "%TEMP%"; - } - else - { - wchar_t tempPath[MAX_PATH + 1]; - DWORD tempChars = GetTempPathW(ARRAYSIZE(tempPath), tempPath); - THROW_LAST_ERROR_IF(!tempChars); - THROW_HR_IF(E_UNEXPECTED, tempChars > ARRAYSIZE(tempPath)); - return { std::wstring_view{ tempPath, static_cast(tempChars) } }; - } - } - - // Gets the current user's SID for use in paths. - std::filesystem::path GetUserSID() - { - auto userToken = wil::get_token_information(); - - wil::unique_hlocal_string sidString; - THROW_IF_WIN32_BOOL_FALSE(ConvertSidToStringSidW(userToken->User.Sid, &sidString)); - return { sidString.get() }; - } - - std::string GetRuntimePathStateName() - { - std::string result; - auto lock = s_runtimePathStateNameLock.lock_shared(); - - if (s_runtimePathStateName.has_value()) - { - result = s_runtimePathStateName.value(); - } - - if (Utility::IsEmptyOrWhitespace(result)) - { - result = s_RuntimePath_Unpackaged_DefaultState; - } - - return result; - } - } - - void SetRuntimePathStateName(std::string name) - { - auto suitablePathPart = MakeSuitablePathPart(name); - auto lock = s_runtimePathStateNameLock.lock_exclusive(); - s_runtimePathStateName.emplace(std::move(suitablePathPart)); - } - - // Contains all of the paths that are common between the runtime contexts. - PathDetails GetPathDetailsCommon(PathName path, bool forDisplay) - { - PathDetails result; - // We should not create directories by default when they are retrieved for display purposes. - result.Create = !forDisplay; - - bool mayBeInProfilePath = false; - - switch (path) - { - case PathName::UserProfile: - result.Path = (forDisplay && Settings::User().Get()) ? s_UserProfileEnvironmentVariable : GetKnownFolderPath(FOLDERID_Profile); - result.Create = false; - break; - case PathName::PortablePackageUserRoot: - result.Path = Settings::User().Get(); - if (result.Path.empty()) - { - result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); - result.Path /= s_PortablePackageUserRoot_Base; - result.Path /= s_PortablePackageRoot; - result.Path /= s_PortablePackagesDirectory; - } - mayBeInProfilePath = true; - break; - case PathName::PortablePackageMachineRoot: - result.Path = Settings::User().Get(); - if (result.Path.empty()) - { - result.Path = GetKnownFolderPath(FOLDERID_ProgramFiles); - result.Path /= s_PortablePackageRoot; - result.Path /= s_PortablePackagesDirectory; - } - break; - case PathName::PortablePackageMachineRootX86: - result.Path = Settings::User().Get(); - if (result.Path.empty()) - { - result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX86); - result.Path /= s_PortablePackageRoot; - result.Path /= s_PortablePackagesDirectory; - } - break; - case PathName::PortableLinksUserLocation: - result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); - result.Path /= s_PortablePackageUserRoot_Base; - result.Path /= s_PortablePackageRoot; - result.Path /= s_LinksDirectory; - mayBeInProfilePath = true; - break; - case PathName::PortableLinksMachineLocation: - result.Path = GetKnownFolderPath(FOLDERID_ProgramFiles); - result.Path /= s_PortablePackageRoot; - result.Path /= s_LinksDirectory; - break; - case PathName::UserProfileDownloads: - result.Path = GetKnownFolderPath(FOLDERID_Downloads); - mayBeInProfilePath = true; - break; - case PathName::FontsUserInstallLocation: - result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); - result.Path /= s_FontsInstallDirectory; - mayBeInProfilePath = true; - break; - 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); - } - - if (mayBeInProfilePath && forDisplay && Settings::User().Get()) - { - ReplaceProfilePathsWithEnvironmentVariable(result.Path); - } - - return result; - } - -#ifndef WINGET_DISABLE_FOR_FUZZING - PathDetails GetPathDetailsForPackagedContext(PathName path, bool forDisplay) - { - PathDetails result; - // We should not create directories by default when they are retrieved for display purposes. - result.Create = !forDisplay; - - auto appStorage = winrt::Windows::Storage::ApplicationData::Current(); - bool mayBeInProfilePath = false; - - switch (path) - { - case PathName::Temp: - result.Path = GetPathToUserTemp(forDisplay) / s_DefaultTempDirectory; - result.SetOwner(ACEPrincipal::CurrentUser); - result.ACL[ACEPrincipal::System] = ACEPermissions::All; - result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - break; - case PathName::LocalState: - case PathName::UserFileSettings: - result.Path.assign(appStorage.LocalFolder().Path().c_str()); - mayBeInProfilePath = true; - break; - case PathName::StandardFileSettings: - result.Path.assign(appStorage.LocalFolder().Path().c_str()); - result.Path /= s_SettingsFile_Relative; - mayBeInProfilePath = true; - break; - case PathName::DefaultLogLocation: - // To enable UIF collection through Feedback hub, we must put our logs here. - result.Path.assign(appStorage.LocalFolder().Path().c_str()); - result.Path /= WINGET_DEFAULT_LOG_DIRECTORY; - mayBeInProfilePath = true; - break; - case PathName::StandardSettings: - result.Create = false; - break; - case PathName::SecureSettingsForRead: - case PathName::SecureSettingsForWrite: - result.Path = GetKnownFolderPath(FOLDERID_ProgramData); - result.Path /= s_SecureSettings_Base; - result.Path /= GetUserSID(); - result.Path /= s_SecureSettings_UserRelative; - result.Path /= s_SecureSettings_Relative_Packaged; - result.Path /= GetPackageName(); - if (path == PathName::SecureSettingsForWrite) - { - result.SetOwner(ACEPrincipal::Admins); - // When running as system, we do not set current user permissions to avoid permission conflicts. - if (!IsRunningAsSystem()) - { - result.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; - } - result.ACL[ACEPrincipal::System] = ACEPermissions::All; - } - else - { - result.Create = false; - } - break; - case PathName::UserProfile: - case PathName::PortablePackageMachineRoot: - case PathName::PortablePackageMachineRootX86: - case PathName::PortableLinksMachineLocation: - case PathName::PortableLinksUserLocation: - case PathName::PortablePackageUserRoot: - case PathName::UserProfileDownloads: - case PathName::FontsUserInstallLocation: - case PathName::FontsMachineInstallLocation: - case PathName::ConfigurationModules: - result = GetPathDetailsCommon(path, forDisplay); - break; - case PathName::SelfPackageRoot: - case PathName::ImageAssets: - result.Path = GetPackagePath(); - result.Create = false; - if (path == PathName::ImageAssets) - { - result.Path /= s_ImageAssetsDirectoryRelative; - } - break; - case PathName::CheckpointsLocation: - result = GetPathDetailsForPackagedContext(PathName::LocalState, forDisplay); - result.Path /= s_CheckpointsDirectory; - break; - case PathName::CLIExecutable: - case PathName::MCPExecutable: - result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); - result.Path /= s_WindowsApps_Base; - result.Path /= GetPackageFamilyName(); - - if (path == PathName::CLIExecutable) - { -#if USE_PROD_CLSIDS - result.Path /= s_WinGet_Exe; -#else - result.Path /= s_WinGetDev_Exe; -#endif - } - else if (path == PathName::MCPExecutable) - { -#if USE_PROD_CLSIDS - result.Path /= s_WinGetMCP_Exe; -#else - result.Path /= s_WinGetMCPDev_Exe; -#endif - } - - result.Create = false; - mayBeInProfilePath = true; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - if (mayBeInProfilePath && forDisplay && Settings::User().Get()) - { - ReplaceProfilePathsWithEnvironmentVariable(result.Path); - } - - return result; - } -#endif - - PathDetails GetPathDetailsForUnpackagedContext(PathName path, bool forDisplay) - { - PathDetails result; - // We should not create directories by default when they are retrieved for display purposes. - result.Create = !forDisplay; - bool anonymize = forDisplay && Settings::User().Get(); - - switch (path) - { - case PathName::Temp: - case PathName::DefaultLogLocation: - { - result.Path = GetPathToUserTemp(forDisplay); - result.Path /= s_DefaultTempDirectory; - result.Path /= GetRuntimePathStateName(); - if (path == PathName::Temp) - { - result.SetOwner(ACEPrincipal::CurrentUser); - result.ACL[ACEPrincipal::System] = ACEPermissions::All; - result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - } - } - break; - case PathName::LocalState: - result = Filesystem::GetPathDetailsFor(Filesystem::PathName::UnpackagedLocalStateRoot, anonymize); - result.Create = !forDisplay; - result.Path /= GetRuntimePathStateName(); - break; - case PathName::StandardSettings: - case PathName::StandardFileSettings: - case PathName::UserFileSettings: - result = Filesystem::GetPathDetailsFor(Filesystem::PathName::UnpackagedSettingsRoot, anonymize); - result.Create = !forDisplay; - result.Path /= GetRuntimePathStateName(); - break; - case PathName::SecureSettingsForRead: - case PathName::SecureSettingsForWrite: - result.Path = GetKnownFolderPath(FOLDERID_ProgramData); - result.Path /= s_SecureSettings_Base; - result.Path /= GetUserSID(); - result.Path /= s_SecureSettings_UserRelative; - result.Path /= s_SecureSettings_Relative_Unpackaged; - result.Path /= GetRuntimePathStateName(); - if (path == PathName::SecureSettingsForWrite) - { - result.SetOwner(ACEPrincipal::Admins); - // When running as system, we do not set current user permissions to avoid permission conflicts. - if (!IsRunningAsSystem()) - { - result.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; - } - result.ACL[ACEPrincipal::System] = ACEPermissions::All; - } - else - { - result.Create = false; - } - break; - case PathName::UserProfile: - case PathName::PortablePackageMachineRoot: - case PathName::PortablePackageMachineRootX86: - case PathName::PortableLinksMachineLocation: - case PathName::PortableLinksUserLocation: - case PathName::PortablePackageUserRoot: - case PathName::UserProfileDownloads: - case PathName::FontsUserInstallLocation: - case PathName::FontsMachineInstallLocation: - case PathName::ConfigurationModules: - result = GetPathDetailsCommon(path, forDisplay); - break; - case PathName::SelfPackageRoot: - case PathName::CLIExecutable: - case PathName::MCPExecutable: - case PathName::ImageAssets: - result.Path = GetBinaryDirectoryPath(); - result.Create = false; - if (path == PathName::CLIExecutable) - { - result.Path /= s_WinGet_Exe; - } - else if (path == PathName::MCPExecutable) - { - result.Path /= s_WinGetMCP_Exe; - } - else if (path == PathName::ImageAssets) - { - result.Path /= s_ImageAssetsDirectoryRelative; - if (!std::filesystem::is_directory(result.Path)) - { - result.Path.clear(); - } - } - break; - case PathName::CheckpointsLocation: - result = GetPathDetailsForUnpackagedContext(PathName::LocalState, forDisplay); - result.Path /= s_CheckpointsDirectory; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return result; - } - - PathDetails GetPathDetailsFor(PathName path, bool forDisplay) - { - PathDetails result; - -#ifndef WINGET_DISABLE_FOR_FUZZING - if (IsRunningInPackagedContext()) - { - result = GetPathDetailsForPackagedContext(path, forDisplay); - } - else -#endif - { - result = GetPathDetailsForUnpackagedContext(path, forDisplay); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Override the value after letting the normal code path run - auto itr = s_Path_TestHook_Overrides.find(path); - if (itr != s_Path_TestHook_Overrides.end()) - { - result = itr->second; - } -#endif - - return result; - } - - // Try to replace LOCALAPPDATA first as it is the likely location, fall back to trying USERPROFILE. - void ReplaceProfilePathsWithEnvironmentVariable(std::filesystem::path& path) - { - if (!ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_LocalAppData), s_LocalAppDataEnvironmentVariable)) - { - ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_Profile), s_UserProfileEnvironmentVariable); - } - } - - std::filesystem::path GetNewTempFilePath() - { - GUID guid; - THROW_IF_FAILED(CoCreateGuid(&guid)); - WCHAR tempFileName[256]; - THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(guid, tempFileName, ARRAYSIZE(tempFileName)) == 0); - auto tempFilePath = Runtime::GetPathTo(Runtime::PathName::Temp); - tempFilePath /= tempFileName; - - return tempFilePath; - } - - // Determines whether developer mode is enabled. - // Does not account for the group policy value which takes precedence over this registry value. - bool IsDevModeEnabled() - { - const auto& devModeSubKey = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, s_DevModeSubkey, 0, KEY_READ|KEY_WOW64_64KEY); - const auto& devModeEnabled = devModeSubKey[s_AllowDevelopmentWithoutDevLicense]; - if (devModeEnabled.has_value()) - { - return devModeEnabled->GetValue() == 1; - } - else - { - return false; - } - } - - // Using "standard" user agent format - // Keeping `winget-cli` for historical reasons - Utility::LocIndString GetDefaultUserAgent() - { - std::ostringstream strstr; - strstr << - "winget-cli" << - " WindowsPackageManager/" << GetClientVersion() << - " DesktopAppInstaller/" << GetPackageVersion(); - return Utility::LocIndString{ strstr.str() }; - } - - Utility::LocIndString GetUserAgent(std::string_view caller) - { - auto escapedCaller = winrt::Windows::Foundation::Uri::EscapeComponent(Utility::ConvertToUTF16(caller)); - - std::ostringstream strstr; - strstr << - Utility::ConvertToUTF8(escapedCaller) << - " WindowsPackageManager/" << GetClientVersion() << - " DesktopAppInstaller/" << GetPackageVersion(); - return Utility::LocIndString{ strstr.str() }; - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path) - { - if (s_Path_TestHook_Overrides.count(target)) - { - s_Path_TestHook_Overrides[target].Path = path; - } - else - { - PathDetails details = GetPathDetailsFor(target); - details.Path = path; - s_Path_TestHook_Overrides[target] = std::move(details); - } - } - - void TestHook_SetPathOverride(PathName target, const PathDetails& details) - { - s_Path_TestHook_Overrides[target] = details; - } - - void TestHook_ClearPathOverrides() - { - s_Path_TestHook_Overrides.clear(); - } -#endif -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerStrings.h" +#include "Public/winget/UserSettings.h" +#include "Public/winget/Registry.h" +#include + + +#define WINGET_DEFAULT_LOG_DIRECTORY "DiagOutputDir" + +namespace AppInstaller::Runtime +{ + using namespace Utility; + using namespace Settings; + using namespace Filesystem; + + namespace + { + using namespace std::string_view_literals; + constexpr std::string_view s_DefaultTempDirectory = "WinGet"sv; + constexpr std::string_view s_SettingsFile_Relative = "Settings"sv; + constexpr std::string_view s_SecureSettings_Base = "Microsoft\\WinGet"sv; + constexpr std::string_view s_SecureSettings_UserRelative = "settings"sv; + constexpr std::string_view s_SecureSettings_Relative_Unpackaged = "win"sv; + constexpr std::string_view s_PortablePackageUserRoot_Base = "Microsoft"sv; + constexpr std::string_view s_PortablePackageRoot = "WinGet"sv; + 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; +#else + constexpr std::string_view s_ImageAssetsDirectoryRelative = "Images"sv; +#endif + constexpr std::string_view s_CheckpointsDirectory = "Checkpoints"sv; + constexpr std::string_view s_DevModeSubkey = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock"sv; + constexpr std::string_view s_AllowDevelopmentWithoutDevLicense = "AllowDevelopmentWithoutDevLicense"sv; +#ifndef WINGET_DISABLE_FOR_FUZZING + constexpr std::string_view s_SecureSettings_Relative_Packaged = "pkg"sv; +#endif + constexpr std::string_view s_RuntimePath_Unpackaged_DefaultState = "defaultState"sv; + + constexpr std::string_view s_UserProfileEnvironmentVariable = "%USERPROFILE%"; + constexpr std::string_view s_LocalAppDataEnvironmentVariable = "%LOCALAPPDATA%"; + constexpr std::string_view s_WindowsApps_Base = "Microsoft\\WindowsApps"sv; + constexpr std::string_view s_WinGetDev_Exe = "wingetdev.exe"; + constexpr std::string_view s_WinGet_Exe = "winget.exe"; + constexpr std::string_view s_WinGetMCPDev_Exe = "WindowsPackageManagerMCPServerDev.exe"; + constexpr std::string_view s_WinGetMCP_Exe = "WindowsPackageManagerMCPServer.exe"; + + static std::optional s_runtimePathStateName; + static wil::srwlock s_runtimePathStateNameLock; + + // Gets the path to the root of the package containing the current process. + std::filesystem::path GetPackagePath() + { + wchar_t packageFullName[PACKAGE_FULL_NAME_MAX_LENGTH + 1]; + UINT32 nameLength = ARRAYSIZE(packageFullName); + THROW_IF_WIN32_ERROR(GetPackageFullName(GetCurrentProcess(), &nameLength, packageFullName)); + + UINT32 pathLength = 0; + LONG result = GetPackagePathByFullName(packageFullName, &pathLength, nullptr); + THROW_HR_IF(HRESULT_FROM_WIN32(result), result != ERROR_INSUFFICIENT_BUFFER); + + std::unique_ptr buffer = std::make_unique(pathLength); + THROW_IF_WIN32_ERROR(GetPackagePathByFullName(packageFullName, &pathLength, buffer.get())); + + return { buffer.get() }; + } + + // Gets the path to the directory containing the currently executing binary file. + std::filesystem::path GetBinaryDirectoryPath() + { + HMODULE moduleHandle = NULL; + THROW_IF_WIN32_BOOL_FALSE(GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + (LPCWSTR)&GetBinaryDirectoryPath, &moduleHandle)); + + // Get the path for this module. + wil::unique_process_heap_string binaryPath; + THROW_IF_FAILED(wil::GetModuleFileNameW(moduleHandle, binaryPath)); + + std::filesystem::path resultFilePath{ binaryPath.get() }; + return resultFilePath.parent_path(); + } + + std::unique_ptr GetPACKAGE_ID() + { + UINT32 bufferLength = 0; + LONG gcpiResult = GetCurrentPackageId(&bufferLength, nullptr); + THROW_HR_IF(E_UNEXPECTED, gcpiResult != ERROR_INSUFFICIENT_BUFFER); + + std::unique_ptr buffer = std::make_unique(bufferLength); + + gcpiResult = GetCurrentPackageId(&bufferLength, buffer.get()); + if (FAILED_WIN32_LOG(gcpiResult)) + { + return {}; + } + + return buffer; + } + + // Gets the package name; only succeeds if running in a packaged context. + std::string GetPackageName() + { + std::unique_ptr buffer = GetPACKAGE_ID(); + if (!buffer) + { + return {}; + } + + PACKAGE_ID* packageId = reinterpret_cast(buffer.get()); + return Utility::ConvertToUTF8(packageId->name); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + static std::map s_Path_TestHook_Overrides; +#endif + + // Gets the user's temp path + std::filesystem::path GetPathToUserTemp(bool forDisplay) + { + if (forDisplay && Settings::User().Get()) + { + return "%TEMP%"; + } + else + { + wchar_t tempPath[MAX_PATH + 1]; + DWORD tempChars = GetTempPathW(ARRAYSIZE(tempPath), tempPath); + THROW_LAST_ERROR_IF(!tempChars); + THROW_HR_IF(E_UNEXPECTED, tempChars > ARRAYSIZE(tempPath)); + return { std::wstring_view{ tempPath, static_cast(tempChars) } }; + } + } + + // Gets the current user's SID for use in paths. + std::filesystem::path GetUserSID() + { + auto userToken = wil::get_token_information(); + + wil::unique_hlocal_string sidString; + THROW_IF_WIN32_BOOL_FALSE(ConvertSidToStringSidW(userToken->User.Sid, &sidString)); + return { sidString.get() }; + } + + std::string GetRuntimePathStateName() + { + std::string result; + auto lock = s_runtimePathStateNameLock.lock_shared(); + + if (s_runtimePathStateName.has_value()) + { + result = s_runtimePathStateName.value(); + } + + if (Utility::IsEmptyOrWhitespace(result)) + { + result = s_RuntimePath_Unpackaged_DefaultState; + } + + return result; + } + } + + void SetRuntimePathStateName(std::string name) + { + auto suitablePathPart = MakeSuitablePathPart(name); + auto lock = s_runtimePathStateNameLock.lock_exclusive(); + s_runtimePathStateName.emplace(std::move(suitablePathPart)); + } + + // Contains all of the paths that are common between the runtime contexts. + PathDetails GetPathDetailsCommon(PathName path, bool forDisplay) + { + PathDetails result; + // We should not create directories by default when they are retrieved for display purposes. + result.Create = !forDisplay; + + bool mayBeInProfilePath = false; + + switch (path) + { + case PathName::UserProfile: + result.Path = (forDisplay && Settings::User().Get()) ? s_UserProfileEnvironmentVariable : GetKnownFolderPath(FOLDERID_Profile); + result.Create = false; + break; + case PathName::PortablePackageUserRoot: + result.Path = Settings::User().Get(); + if (result.Path.empty()) + { + result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); + result.Path /= s_PortablePackageUserRoot_Base; + result.Path /= s_PortablePackageRoot; + result.Path /= s_PortablePackagesDirectory; + } + mayBeInProfilePath = true; + break; + case PathName::PortablePackageMachineRoot: + result.Path = Settings::User().Get(); + if (result.Path.empty()) + { + result.Path = GetKnownFolderPath(FOLDERID_ProgramFiles); + result.Path /= s_PortablePackageRoot; + result.Path /= s_PortablePackagesDirectory; + } + break; + case PathName::PortablePackageMachineRootX86: + result.Path = Settings::User().Get(); + if (result.Path.empty()) + { + result.Path = GetKnownFolderPath(FOLDERID_ProgramFilesX86); + result.Path /= s_PortablePackageRoot; + result.Path /= s_PortablePackagesDirectory; + } + break; + case PathName::PortableLinksUserLocation: + result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); + result.Path /= s_PortablePackageUserRoot_Base; + result.Path /= s_PortablePackageRoot; + result.Path /= s_LinksDirectory; + mayBeInProfilePath = true; + break; + case PathName::PortableLinksMachineLocation: + result.Path = GetKnownFolderPath(FOLDERID_ProgramFiles); + result.Path /= s_PortablePackageRoot; + result.Path /= s_LinksDirectory; + break; + case PathName::UserProfileDownloads: + result.Path = GetKnownFolderPath(FOLDERID_Downloads); + mayBeInProfilePath = true; + break; + case PathName::FontsUserInstallLocation: + result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); + result.Path /= s_FontsInstallDirectory; + mayBeInProfilePath = true; + break; + 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); + } + + if (mayBeInProfilePath && forDisplay && Settings::User().Get()) + { + ReplaceProfilePathsWithEnvironmentVariable(result.Path); + } + + return result; + } + +#ifndef WINGET_DISABLE_FOR_FUZZING + PathDetails GetPathDetailsForPackagedContext(PathName path, bool forDisplay) + { + PathDetails result; + // We should not create directories by default when they are retrieved for display purposes. + result.Create = !forDisplay; + + auto appStorage = winrt::Windows::Storage::ApplicationData::Current(); + bool mayBeInProfilePath = false; + + switch (path) + { + case PathName::Temp: + result.Path = GetPathToUserTemp(forDisplay) / s_DefaultTempDirectory; + result.SetOwner(ACEPrincipal::CurrentUser); + result.ACL[ACEPrincipal::System] = ACEPermissions::All; + result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + break; + case PathName::LocalState: + case PathName::UserFileSettings: + result.Path.assign(appStorage.LocalFolder().Path().c_str()); + mayBeInProfilePath = true; + break; + case PathName::StandardFileSettings: + result.Path.assign(appStorage.LocalFolder().Path().c_str()); + result.Path /= s_SettingsFile_Relative; + mayBeInProfilePath = true; + break; + case PathName::DefaultLogLocation: + // To enable UIF collection through Feedback hub, we must put our logs here. + result.Path.assign(appStorage.LocalFolder().Path().c_str()); + result.Path /= WINGET_DEFAULT_LOG_DIRECTORY; + mayBeInProfilePath = true; + break; + case PathName::StandardSettings: + result.Create = false; + break; + case PathName::SecureSettingsForRead: + case PathName::SecureSettingsForWrite: + result.Path = GetKnownFolderPath(FOLDERID_ProgramData); + result.Path /= s_SecureSettings_Base; + result.Path /= GetUserSID(); + result.Path /= s_SecureSettings_UserRelative; + result.Path /= s_SecureSettings_Relative_Packaged; + result.Path /= GetPackageName(); + if (path == PathName::SecureSettingsForWrite) + { + result.SetOwner(ACEPrincipal::Admins); + // When running as system, we do not set current user permissions to avoid permission conflicts. + if (!IsRunningAsSystem()) + { + result.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; + } + result.ACL[ACEPrincipal::System] = ACEPermissions::All; + } + else + { + result.Create = false; + } + break; + case PathName::UserProfile: + case PathName::PortablePackageMachineRoot: + case PathName::PortablePackageMachineRootX86: + case PathName::PortableLinksMachineLocation: + case PathName::PortableLinksUserLocation: + case PathName::PortablePackageUserRoot: + case PathName::UserProfileDownloads: + case PathName::FontsUserInstallLocation: + case PathName::FontsMachineInstallLocation: + case PathName::ConfigurationModules: + result = GetPathDetailsCommon(path, forDisplay); + break; + case PathName::SelfPackageRoot: + case PathName::ImageAssets: + result.Path = GetPackagePath(); + result.Create = false; + if (path == PathName::ImageAssets) + { + result.Path /= s_ImageAssetsDirectoryRelative; + } + break; + case PathName::CheckpointsLocation: + result = GetPathDetailsForPackagedContext(PathName::LocalState, forDisplay); + result.Path /= s_CheckpointsDirectory; + break; + case PathName::CLIExecutable: + case PathName::MCPExecutable: + result.Path = GetKnownFolderPath(FOLDERID_LocalAppData); + result.Path /= s_WindowsApps_Base; + result.Path /= GetPackageFamilyName(); + + if (path == PathName::CLIExecutable) + { +#if USE_PROD_CLSIDS + result.Path /= s_WinGet_Exe; +#else + result.Path /= s_WinGetDev_Exe; +#endif + } + else if (path == PathName::MCPExecutable) + { +#if USE_PROD_CLSIDS + result.Path /= s_WinGetMCP_Exe; +#else + result.Path /= s_WinGetMCPDev_Exe; +#endif + } + + result.Create = false; + mayBeInProfilePath = true; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + if (mayBeInProfilePath && forDisplay && Settings::User().Get()) + { + ReplaceProfilePathsWithEnvironmentVariable(result.Path); + } + + return result; + } +#endif + + PathDetails GetPathDetailsForUnpackagedContext(PathName path, bool forDisplay) + { + PathDetails result; + // We should not create directories by default when they are retrieved for display purposes. + result.Create = !forDisplay; + bool anonymize = forDisplay && Settings::User().Get(); + + switch (path) + { + case PathName::Temp: + case PathName::DefaultLogLocation: + { + result.Path = GetPathToUserTemp(forDisplay); + result.Path /= s_DefaultTempDirectory; + result.Path /= GetRuntimePathStateName(); + if (path == PathName::Temp) + { + result.SetOwner(ACEPrincipal::CurrentUser); + result.ACL[ACEPrincipal::System] = ACEPermissions::All; + result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + } + } + break; + case PathName::LocalState: + result = Filesystem::GetPathDetailsFor(Filesystem::PathName::UnpackagedLocalStateRoot, anonymize); + result.Create = !forDisplay; + result.Path /= GetRuntimePathStateName(); + break; + case PathName::StandardSettings: + case PathName::StandardFileSettings: + case PathName::UserFileSettings: + result = Filesystem::GetPathDetailsFor(Filesystem::PathName::UnpackagedSettingsRoot, anonymize); + result.Create = !forDisplay; + result.Path /= GetRuntimePathStateName(); + break; + case PathName::SecureSettingsForRead: + case PathName::SecureSettingsForWrite: + result.Path = GetKnownFolderPath(FOLDERID_ProgramData); + result.Path /= s_SecureSettings_Base; + result.Path /= GetUserSID(); + result.Path /= s_SecureSettings_UserRelative; + result.Path /= s_SecureSettings_Relative_Unpackaged; + result.Path /= GetRuntimePathStateName(); + if (path == PathName::SecureSettingsForWrite) + { + result.SetOwner(ACEPrincipal::Admins); + // When running as system, we do not set current user permissions to avoid permission conflicts. + if (!IsRunningAsSystem()) + { + result.ACL[ACEPrincipal::CurrentUser] = ACEPermissions::ReadExecute; + } + result.ACL[ACEPrincipal::System] = ACEPermissions::All; + } + else + { + result.Create = false; + } + break; + case PathName::UserProfile: + case PathName::PortablePackageMachineRoot: + case PathName::PortablePackageMachineRootX86: + case PathName::PortableLinksMachineLocation: + case PathName::PortableLinksUserLocation: + case PathName::PortablePackageUserRoot: + case PathName::UserProfileDownloads: + case PathName::FontsUserInstallLocation: + case PathName::FontsMachineInstallLocation: + case PathName::ConfigurationModules: + result = GetPathDetailsCommon(path, forDisplay); + break; + case PathName::SelfPackageRoot: + case PathName::CLIExecutable: + case PathName::MCPExecutable: + case PathName::ImageAssets: + result.Path = GetBinaryDirectoryPath(); + result.Create = false; + if (path == PathName::CLIExecutable) + { + result.Path /= s_WinGet_Exe; + } + else if (path == PathName::MCPExecutable) + { + result.Path /= s_WinGetMCP_Exe; + } + else if (path == PathName::ImageAssets) + { + result.Path /= s_ImageAssetsDirectoryRelative; + if (!std::filesystem::is_directory(result.Path)) + { + result.Path.clear(); + } + } + break; + case PathName::CheckpointsLocation: + result = GetPathDetailsForUnpackagedContext(PathName::LocalState, forDisplay); + result.Path /= s_CheckpointsDirectory; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + PathDetails GetPathDetailsFor(PathName path, bool forDisplay) + { + PathDetails result; + +#ifndef WINGET_DISABLE_FOR_FUZZING + if (IsRunningInPackagedContext()) + { + result = GetPathDetailsForPackagedContext(path, forDisplay); + } + else +#endif + { + result = GetPathDetailsForUnpackagedContext(path, forDisplay); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Override the value after letting the normal code path run + auto itr = s_Path_TestHook_Overrides.find(path); + if (itr != s_Path_TestHook_Overrides.end()) + { + result = itr->second; + } +#endif + + return result; + } + + // Try to replace LOCALAPPDATA first as it is the likely location, fall back to trying USERPROFILE. + void ReplaceProfilePathsWithEnvironmentVariable(std::filesystem::path& path) + { + if (!ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_LocalAppData), s_LocalAppDataEnvironmentVariable)) + { + ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_Profile), s_UserProfileEnvironmentVariable); + } + } + + std::filesystem::path GetNewTempFilePath() + { + GUID guid; + THROW_IF_FAILED(CoCreateGuid(&guid)); + WCHAR tempFileName[256]; + THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(guid, tempFileName, ARRAYSIZE(tempFileName)) == 0); + auto tempFilePath = Runtime::GetPathTo(Runtime::PathName::Temp); + tempFilePath /= tempFileName; + + return tempFilePath; + } + + // Determines whether developer mode is enabled. + // Does not account for the group policy value which takes precedence over this registry value. + bool IsDevModeEnabled() + { + const auto& devModeSubKey = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, s_DevModeSubkey, 0, KEY_READ|KEY_WOW64_64KEY); + const auto& devModeEnabled = devModeSubKey[s_AllowDevelopmentWithoutDevLicense]; + if (devModeEnabled.has_value()) + { + return devModeEnabled->GetValue() == 1; + } + else + { + return false; + } + } + + // Using "standard" user agent format + // Keeping `winget-cli` for historical reasons + Utility::LocIndString GetDefaultUserAgent() + { + std::ostringstream strstr; + strstr << + "winget-cli" << + " WindowsPackageManager/" << GetClientVersion() << + " DesktopAppInstaller/" << GetPackageVersion(); + return Utility::LocIndString{ strstr.str() }; + } + + Utility::LocIndString GetUserAgent(std::string_view caller) + { + auto escapedCaller = winrt::Windows::Foundation::Uri::EscapeComponent(Utility::ConvertToUTF16(caller)); + + std::ostringstream strstr; + strstr << + Utility::ConvertToUTF8(escapedCaller) << + " WindowsPackageManager/" << GetClientVersion() << + " DesktopAppInstaller/" << GetPackageVersion(); + return Utility::LocIndString{ strstr.str() }; + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + void TestHook_SetPathOverride(PathName target, const std::filesystem::path& path) + { + if (s_Path_TestHook_Overrides.count(target)) + { + s_Path_TestHook_Overrides[target].Path = path; + } + else + { + PathDetails details = GetPathDetailsFor(target); + details.Path = path; + s_Path_TestHook_Overrides[target] = std::move(details); + } + } + + void TestHook_SetPathOverride(PathName target, const PathDetails& details) + { + s_Path_TestHook_Overrides[target] = details; + } + + void TestHook_ClearPathOverrides() + { + s_Path_TestHook_Overrides.clear(); + } +#endif +} diff --git a/src/AppInstallerCommonCore/SelfManagement.cpp b/src/AppInstallerCommonCore/SelfManagement.cpp index 0f4b0c5f30..88aa3b5f82 100644 --- a/src/AppInstallerCommonCore/SelfManagement.cpp +++ b/src/AppInstallerCommonCore/SelfManagement.cpp @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/ExperimentalFeature.h" -#include "winget/SelfManagement.h" -#include "AppInstallerRuntime.h" - -namespace AppInstaller::SelfManagement -{ - using namespace AppInstaller::Settings; - using namespace std::string_view_literals; - using namespace winrt::Windows::ApplicationModel; - using namespace winrt::Windows::Management::Deployment; - using namespace winrt::Windows::Services::Store; - - // Always use AppInstaller's package family name for wingetdev - static constexpr std::wstring_view s_AppInstallerPfn = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"sv; - constexpr std::wstring_view s_RemoteServerFileName = L"DotNet\\ConfigurationRemotingServer.exe"; - - bool IsStubPreferred() - { - winrt::hstring packageFamilyName{ s_AppInstallerPfn }; - - PackageManager packageManager; - auto packageManager9 = packageManager.try_as(); - - if (!packageManager9) - { - // If the API isn't present, then the only option is full package. - return false; - } - - auto preference = packageManager9.GetPackageStubPreference(packageFamilyName); - - return preference == PackageStubPreference::Stub; - } - - void SetStubPreferred(bool preferStub) - { - winrt::hstring packageFamilyName{ s_AppInstallerPfn }; - - PackageManager packageManager; - auto packageManager9 = packageManager.try_as(); - - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION), !packageManager9); - - packageManager9.SetPackageStubPreference(packageFamilyName, preferStub ? PackageStubPreference::Stub : PackageStubPreference::Full); - } - - bool IsStubPackage() - { - // The right way to do it is to call FindPackage APIs from PackageManager, but that requires admin. - std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot) / s_RemoteServerFileName; - return !std::filesystem::exists(serverPath); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ExperimentalFeature.h" +#include "winget/SelfManagement.h" +#include "AppInstallerRuntime.h" + +namespace AppInstaller::SelfManagement +{ + using namespace AppInstaller::Settings; + using namespace std::string_view_literals; + using namespace winrt::Windows::ApplicationModel; + using namespace winrt::Windows::Management::Deployment; + using namespace winrt::Windows::Services::Store; + + // Always use AppInstaller's package family name for wingetdev + static constexpr std::wstring_view s_AppInstallerPfn = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"sv; + constexpr std::wstring_view s_RemoteServerFileName = L"DotNet\\ConfigurationRemotingServer.exe"; + + bool IsStubPreferred() + { + winrt::hstring packageFamilyName{ s_AppInstallerPfn }; + + PackageManager packageManager; + auto packageManager9 = packageManager.try_as(); + + if (!packageManager9) + { + // If the API isn't present, then the only option is full package. + return false; + } + + auto preference = packageManager9.GetPackageStubPreference(packageFamilyName); + + return preference == PackageStubPreference::Stub; + } + + void SetStubPreferred(bool preferStub) + { + winrt::hstring packageFamilyName{ s_AppInstallerPfn }; + + PackageManager packageManager; + auto packageManager9 = packageManager.try_as(); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OLD_WIN_VERSION), !packageManager9); + + packageManager9.SetPackageStubPreference(packageFamilyName, preferStub ? PackageStubPreference::Stub : PackageStubPreference::Full); + } + + bool IsStubPackage() + { + // The right way to do it is to call FindPackage APIs from PackageManager, but that requires admin. + std::filesystem::path serverPath = Runtime::GetPathTo(Runtime::PathName::SelfPackageRoot) / s_RemoteServerFileName; + return !std::filesystem::exists(serverPath); + } +} diff --git a/src/AppInstallerCommonCore/Settings.cpp b/src/AppInstallerCommonCore/Settings.cpp index b7694a7920..edde816038 100644 --- a/src/AppInstallerCommonCore/Settings.cpp +++ b/src/AppInstallerCommonCore/Settings.cpp @@ -1,544 +1,544 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/Settings.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerRuntime.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerSHA256.h" -#include "Public/winget/Yaml.h" - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - using namespace Runtime; - using namespace Utility; - - namespace - { - void ValidateSettingNamePath(const std::filesystem::path& name) - { - THROW_HR_IF(E_INVALIDARG, !name.has_relative_path()); - THROW_HR_IF(E_INVALIDARG, name.has_root_path()); - THROW_HR_IF(E_INVALIDARG, !name.has_filename()); - } - - void LogSettingAction(std::string_view action, const StreamDefinition& def) - { - AICLI_LOG(Core, Verbose, << "Setting action: " << action << ", Type: " << ToString(def.Type) << ", Name: " << def.Name); - } - -#ifndef WINGET_DISABLE_FOR_FUZZING - // A settings container backed by the ApplicationDataContainer functionality. - struct ApplicationDataSettingsContainer : public details::ISettingsContainer - { - using Container = winrt::Windows::Storage::ApplicationDataContainer; - - ApplicationDataSettingsContainer(const Container& container, const std::filesystem::path& name) - { - m_parentContainer = GetRelativeContainer(container, name.parent_path()); - m_settingName = winrt::to_hstring(name.filename().c_str()); - } - - static Container GetRelativeContainer(const Container& container, const std::filesystem::path& offset) - { - auto result = container; - - for (const auto& part : offset) - { - auto partHstring = winrt::to_hstring(part.c_str()); - result = result.CreateContainer(partHstring, winrt::Windows::Storage::ApplicationDataCreateDisposition::Always); - } - - return result; - } - - std::unique_ptr Get() override - { - auto settingsValues = m_parentContainer.Values(); - if (settingsValues.HasKey(m_settingName)) - { - auto value = winrt::unbox_value(settingsValues.Lookup(m_settingName)); - return std::make_unique(Utility::ConvertToUTF8(value.c_str())); - } - else - { - return {}; - } - } - - bool Set(std::string_view value) override - { - m_parentContainer.Values().Insert(m_settingName, winrt::box_value(winrt::to_hstring(value))); - return true; - } - - void Remove() override - { - m_parentContainer.Values().Remove(m_settingName); - } - - std::filesystem::path PathTo() override - { - THROW_HR(E_UNEXPECTED); - } - - private: - Container m_parentContainer = nullptr; - winrt::hstring m_settingName; - }; -#endif - - // A settings container backed by the filesystem. - struct FileSettingsContainer : public details::ISettingsContainer - { - FileSettingsContainer(std::filesystem::path root, const std::filesystem::path& name) : m_settingFile(std::move(root)) - { - m_settingFile /= name; - } - - std::unique_ptr Get() override - { - if (std::filesystem::exists(m_settingFile)) - { - auto result = std::make_unique(m_settingFile, std::ios_base::in | std::ios_base::binary); - THROW_LAST_ERROR_IF(result->fail()); - return result; - } - else - { - return {}; - } - } - - bool Set(std::string_view value) override - { - EnsureParentPath(); - - std::ofstream stream(m_settingFile, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); - THROW_LAST_ERROR_IF(stream.fail()); - stream << value << std::flush; - THROW_LAST_ERROR_IF(stream.fail()); - - return true; - } - - void Remove() override - { - std::filesystem::remove(m_settingFile); - } - - std::filesystem::path PathTo() override - { - return m_settingFile; - } - - private: - void EnsureParentPath() - { - std::filesystem::create_directories(m_settingFile.parent_path()); - } - - std::filesystem::path m_settingFile; - }; - - // A settings container that manages safely writing to its value with exchange semantics. - // Only allows Set to succeed if the hash value of the setting is the same as the last time it was read. - struct ExchangeSettingsContainer : public details::ISettingsContainer - { - ExchangeSettingsContainer(std::unique_ptr&& container, const std::string_view& name) : - m_container(std::move(container)), m_name(name) {} - - std::unique_ptr Get() override - { - return GetInternal(m_hash); - } - - bool Set(std::string_view value) override - { - THROW_HR_IF(E_UNEXPECTED, value.size() > std::numeric_limits::max()); - - // If Set is called without ever reading the value, then we can assume that caller wants - // to overwrite it regardless. Also, we don't have any previous value to compare against - // anyway so the only other option would be to always reject it. - if (m_hash) - { - std::optional currentHash; - std::ignore = GetInternal(currentHash); - - if (currentHash && !SHA256::AreEqual(m_hash.value(), currentHash.value())) - { - AICLI_LOG(Core, Verbose, << "Setting value for '" << m_name << "' has changed since last read; rejecting Set"); - return false; - } - } - - SHA256::HashBuffer newHash = SHA256::ComputeHash(reinterpret_cast(value.data()), static_cast(value.size())); - if (m_container->Set(value)) - { - m_hash = std::move(newHash); - return true; - } - else - { - return false; - } - } - - void Remove() override - { - m_container->Remove(); - m_hash.reset(); - } - - std::filesystem::path PathTo() override - { - return m_container->PathTo(); - } - - protected: - std::optional GetAsString() - { - return GetAsStringInternal(m_hash); - } - - std::string_view m_name; - std::optional m_hash; - - private: - std::optional GetAsStringInternal(std::optional& hashStorage) - { - std::unique_ptr stream = m_container->Get(); - - if (!stream) - { - // If no stream exists, then no hashing needs to be done. - // Return an empty hash vector to indicate the attempted read but no result. - hashStorage.emplace(); - return std::nullopt; - } - - std::string streamContents = Utility::ReadEntireStream(*stream); - THROW_HR_IF(E_UNEXPECTED, streamContents.size() > std::numeric_limits::max()); - - hashStorage = SHA256::ComputeHash(reinterpret_cast(streamContents.c_str()), static_cast(streamContents.size())); - - return streamContents; - } - - std::unique_ptr GetInternal(std::optional& hashStorage) - { - auto string = GetAsStringInternal(hashStorage); - - // Return a stream over the contents that we read in and hashed, to prevent a race. - return string ? std::make_unique(string.value()) : nullptr; - } - - std::unique_ptr m_container; - }; - - // A settings container wrapper that enforces security. - struct SecureSettingsContainer : public ExchangeSettingsContainer - { - constexpr static std::string_view NodeName_Sha256 = "SHA256"sv; - - SecureSettingsContainer(std::unique_ptr&& container, const std::string_view& name) : - ExchangeSettingsContainer(std::move(container), name), m_secure(GetPathTo(PathName::SecureSettingsForRead), name) {} - - private: - struct VerificationData - { - bool Found = false; - SHA256::HashBuffer Hash; - }; - - VerificationData GetVerificationData() - { - std::unique_ptr stream = m_secure.Get(); - - if (!stream) - { - return {}; - } - - std::string streamContents = Utility::ReadEntireStream(*stream); - - YAML::Node document; - try - { - document = YAML::Load(streamContents); - } - catch (const std::runtime_error& e) - { - AICLI_LOG(Core, Error, << "Secure setting metadata for '" << m_name << "' contained invalid YAML (" << e.what() << "):\n" << streamContents); - return {}; - } - - std::string hashString; - - try - { - hashString = document[NodeName_Sha256].as(); - } - catch (const std::runtime_error& e) - { - AICLI_LOG(Core, Error, << "Secure setting metadata for '" << m_name << "' contained invalid YAML (" << e.what() << "):\n" << streamContents); - return {}; - } - - VerificationData result; - result.Found = true; - result.Hash = SHA256::ConvertToBytes(hashString); - - return result; - } - - bool SetVerificationData(VerificationData data) - { - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << NodeName_Sha256 << YAML::Value << SHA256::ConvertToString(data.Hash); - out << YAML::EndMap; - - bool result = m_secure.Set(out.str()); - if (!result) - { - AICLI_LOG(Core, Warning, << "Failed to write verification data for '" << m_name << "'"); - } - return result; - } - - public: - std::unique_ptr Get() override - { - std::unique_ptr stream = ExchangeSettingsContainer::Get(); - - if (!stream) - { - // If no stream exists, then no verification needs to be done. - return stream; - } - - VerificationData verData = GetVerificationData(); - - // This case should be very rare, so a very identifiable error is helpful. - // Plus the text for this one is fairly on point for what has happened. - THROW_HR_IF(SPAPI_E_FILE_HASH_NOT_IN_CATALOG, !verData.Found); - - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_DATA_CHECKSUM_ERROR), !SHA256::AreEqual(m_hash.value(), verData.Hash)); - - // ExchangeSettingsContainer already produces an in memory stream that we can use. - return stream; - } - - bool Set(std::string_view value) override - { - // Force the creation of the secure settings location with appropriate ACLs - GetPathTo(PathName::SecureSettingsForWrite); - - bool exchangeResult = ExchangeSettingsContainer::Set(value); - - if (exchangeResult) - { - VerificationData verData; - verData.Hash = m_hash.value(); - - if (!SetVerificationData(verData)) - { - AICLI_LOG(Core, Error, << "Failed to write verification data for '" << m_name << "'. Reverting setting write to maintain consistency."); - AICLI_LOG(Core, Warning, << "This failure may be caused by insufficient permissions or disk space issues"); - // Verification data write failed, so we need to revert the main write - // to maintain consistency - Remove(); - return false; - } - } - - return exchangeResult; - } - - void Remove() override - { - ExchangeSettingsContainer::Remove(); - m_secure.Remove(); - } - - std::filesystem::path PathTo() override - { - THROW_HR(E_UNEXPECTED); - } - - private: - FileSettingsContainer m_secure; - }; - - // A settings container wrapper that enforces privacy. - struct EncryptedSettingsContainer : public ExchangeSettingsContainer - { - EncryptedSettingsContainer(std::unique_ptr&& container, const std::string_view& name) : - ExchangeSettingsContainer(std::move(container), name) { - } - - std::unique_ptr Get() override - { - std::optional stream = ExchangeSettingsContainer::GetAsString(); - - if (!stream) - { - // If no stream exists, then nothing needs to be done. - return nullptr; - } - - DATA_BLOB data{}; - data.pbData = reinterpret_cast(stream->data()); - data.cbData = static_cast(stream->size()); - - DATA_BLOB out{}; - auto freeOut = wil::scope_exit([&]() { LocalFree(out.pbData); }); - - THROW_IF_WIN32_BOOL_FALSE(CryptUnprotectData(&data, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &out)); - - return std::make_unique(std::string{ reinterpret_cast(out.pbData), out.cbData }); - } - - bool Set(std::string_view value) override - { - DATA_BLOB data{}; - data.pbData = reinterpret_cast(const_cast(value.data())); - data.cbData = static_cast(value.size()); - - DATA_BLOB out{}; - auto freeOut = wil::scope_exit([&]() { LocalFree(out.pbData); }); - - THROW_IF_WIN32_BOOL_FALSE(CryptProtectData(&data, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &out)); - - return ExchangeSettingsContainer::Set(std::string_view{ reinterpret_cast(out.pbData), out.cbData }); - } - - std::filesystem::path PathTo() override - { - THROW_HR(E_UNEXPECTED); - } - }; - - std::unique_ptr GetRawSettingsContainer(Type type, const std::string_view& name) - { -#ifndef WINGET_DISABLE_FOR_FUZZING - if (IsRunningInPackagedContext()) - { - switch (type) - { - case Type::Standard: - return std::make_unique( - ApplicationDataSettingsContainer::GetRelativeContainer( - winrt::Windows::Storage::ApplicationData::Current().LocalSettings(), GetPathTo(PathName::StandardSettings)), - name); - case Type::StandardFile: - return std::make_unique(GetPathTo(PathName::StandardFileSettings), name); - default: - THROW_HR(E_UNEXPECTED); - } - } - else -#endif - { - switch (type) - { - case Type::Standard: - case Type::StandardFile: - return std::make_unique(GetPathTo(PathName::StandardSettings), name); - default: - THROW_HR(E_UNEXPECTED); - } - } - } - - // The default is not a raw container, so we wrap some of the underlying containers to enable higher order behaviors. - std::unique_ptr GetSettingsContainer(Type type, const std::string_view& name) - { - switch (type) - { - case Type::Standard: - // Standard settings should use exchange semantics to prevent overwrites - return std::make_unique(GetRawSettingsContainer(type, name), name); - - case Type::UserFile: - // User file settings are not typically modified by us, so there is no need for exchange - return std::make_unique(GetPathTo(PathName::UserFileSettings), name); - - case Type::Secure: - // Secure settings add hash verification on reads on top of exchange semantics - return std::make_unique(GetRawSettingsContainer(Type::Standard, name), name); - - case Type::Encrypted: - // Encrypted settings add encryption on top of exchange semantics - return std::make_unique(GetRawSettingsContainer(Type::StandardFile, name), name); - - case Type::StandardFile: - // Standard settings should use exchange semantics to prevent overwrites - return std::make_unique(GetRawSettingsContainer(type, name), name); - - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::unique_ptr GetSettingsContainer(const StreamDefinition& streamDefinition) - { - return GetSettingsContainer(streamDefinition.Type, streamDefinition.Name); - } - } - - std::string_view ToString(Type type) - { - switch (type) - { - case Type::Standard: - return "Standard"sv; - case Type::UserFile: - return "UserFile"sv; - case Type::Secure: - return "Secure"sv; - case Type::Encrypted: - return "Encrypted"sv; - case Type::StandardFile: - return "StandardFile"sv; - default: - THROW_HR(E_UNEXPECTED); - } - } - - Stream::Stream(const StreamDefinition& streamDefinition) : - m_streamDefinition(streamDefinition), m_container(GetSettingsContainer(streamDefinition)) - { - ValidateSettingNamePath(m_streamDefinition.Name); - } - - std::unique_ptr Stream::Get() - { - LogSettingAction("Get", m_streamDefinition); - return m_container->Get(); - } - - [[nodiscard]] bool Stream::Set(std::string_view value) - { - LogSettingAction("Set", m_streamDefinition); - return m_container->Set(value); - } - - void Stream::Remove() - { - LogSettingAction("Remove", m_streamDefinition); - m_container->Remove(); - } - - std::string_view Stream::GetName() const - { - return m_streamDefinition.Name; - } - - std::filesystem::path Stream::GetPath() const - { - return m_container->PathTo(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Settings.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerRuntime.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerSHA256.h" +#include "Public/winget/Yaml.h" + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Runtime; + using namespace Utility; + + namespace + { + void ValidateSettingNamePath(const std::filesystem::path& name) + { + THROW_HR_IF(E_INVALIDARG, !name.has_relative_path()); + THROW_HR_IF(E_INVALIDARG, name.has_root_path()); + THROW_HR_IF(E_INVALIDARG, !name.has_filename()); + } + + void LogSettingAction(std::string_view action, const StreamDefinition& def) + { + AICLI_LOG(Core, Verbose, << "Setting action: " << action << ", Type: " << ToString(def.Type) << ", Name: " << def.Name); + } + +#ifndef WINGET_DISABLE_FOR_FUZZING + // A settings container backed by the ApplicationDataContainer functionality. + struct ApplicationDataSettingsContainer : public details::ISettingsContainer + { + using Container = winrt::Windows::Storage::ApplicationDataContainer; + + ApplicationDataSettingsContainer(const Container& container, const std::filesystem::path& name) + { + m_parentContainer = GetRelativeContainer(container, name.parent_path()); + m_settingName = winrt::to_hstring(name.filename().c_str()); + } + + static Container GetRelativeContainer(const Container& container, const std::filesystem::path& offset) + { + auto result = container; + + for (const auto& part : offset) + { + auto partHstring = winrt::to_hstring(part.c_str()); + result = result.CreateContainer(partHstring, winrt::Windows::Storage::ApplicationDataCreateDisposition::Always); + } + + return result; + } + + std::unique_ptr Get() override + { + auto settingsValues = m_parentContainer.Values(); + if (settingsValues.HasKey(m_settingName)) + { + auto value = winrt::unbox_value(settingsValues.Lookup(m_settingName)); + return std::make_unique(Utility::ConvertToUTF8(value.c_str())); + } + else + { + return {}; + } + } + + bool Set(std::string_view value) override + { + m_parentContainer.Values().Insert(m_settingName, winrt::box_value(winrt::to_hstring(value))); + return true; + } + + void Remove() override + { + m_parentContainer.Values().Remove(m_settingName); + } + + std::filesystem::path PathTo() override + { + THROW_HR(E_UNEXPECTED); + } + + private: + Container m_parentContainer = nullptr; + winrt::hstring m_settingName; + }; +#endif + + // A settings container backed by the filesystem. + struct FileSettingsContainer : public details::ISettingsContainer + { + FileSettingsContainer(std::filesystem::path root, const std::filesystem::path& name) : m_settingFile(std::move(root)) + { + m_settingFile /= name; + } + + std::unique_ptr Get() override + { + if (std::filesystem::exists(m_settingFile)) + { + auto result = std::make_unique(m_settingFile, std::ios_base::in | std::ios_base::binary); + THROW_LAST_ERROR_IF(result->fail()); + return result; + } + else + { + return {}; + } + } + + bool Set(std::string_view value) override + { + EnsureParentPath(); + + std::ofstream stream(m_settingFile, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + THROW_LAST_ERROR_IF(stream.fail()); + stream << value << std::flush; + THROW_LAST_ERROR_IF(stream.fail()); + + return true; + } + + void Remove() override + { + std::filesystem::remove(m_settingFile); + } + + std::filesystem::path PathTo() override + { + return m_settingFile; + } + + private: + void EnsureParentPath() + { + std::filesystem::create_directories(m_settingFile.parent_path()); + } + + std::filesystem::path m_settingFile; + }; + + // A settings container that manages safely writing to its value with exchange semantics. + // Only allows Set to succeed if the hash value of the setting is the same as the last time it was read. + struct ExchangeSettingsContainer : public details::ISettingsContainer + { + ExchangeSettingsContainer(std::unique_ptr&& container, const std::string_view& name) : + m_container(std::move(container)), m_name(name) {} + + std::unique_ptr Get() override + { + return GetInternal(m_hash); + } + + bool Set(std::string_view value) override + { + THROW_HR_IF(E_UNEXPECTED, value.size() > std::numeric_limits::max()); + + // If Set is called without ever reading the value, then we can assume that caller wants + // to overwrite it regardless. Also, we don't have any previous value to compare against + // anyway so the only other option would be to always reject it. + if (m_hash) + { + std::optional currentHash; + std::ignore = GetInternal(currentHash); + + if (currentHash && !SHA256::AreEqual(m_hash.value(), currentHash.value())) + { + AICLI_LOG(Core, Verbose, << "Setting value for '" << m_name << "' has changed since last read; rejecting Set"); + return false; + } + } + + SHA256::HashBuffer newHash = SHA256::ComputeHash(reinterpret_cast(value.data()), static_cast(value.size())); + if (m_container->Set(value)) + { + m_hash = std::move(newHash); + return true; + } + else + { + return false; + } + } + + void Remove() override + { + m_container->Remove(); + m_hash.reset(); + } + + std::filesystem::path PathTo() override + { + return m_container->PathTo(); + } + + protected: + std::optional GetAsString() + { + return GetAsStringInternal(m_hash); + } + + std::string_view m_name; + std::optional m_hash; + + private: + std::optional GetAsStringInternal(std::optional& hashStorage) + { + std::unique_ptr stream = m_container->Get(); + + if (!stream) + { + // If no stream exists, then no hashing needs to be done. + // Return an empty hash vector to indicate the attempted read but no result. + hashStorage.emplace(); + return std::nullopt; + } + + std::string streamContents = Utility::ReadEntireStream(*stream); + THROW_HR_IF(E_UNEXPECTED, streamContents.size() > std::numeric_limits::max()); + + hashStorage = SHA256::ComputeHash(reinterpret_cast(streamContents.c_str()), static_cast(streamContents.size())); + + return streamContents; + } + + std::unique_ptr GetInternal(std::optional& hashStorage) + { + auto string = GetAsStringInternal(hashStorage); + + // Return a stream over the contents that we read in and hashed, to prevent a race. + return string ? std::make_unique(string.value()) : nullptr; + } + + std::unique_ptr m_container; + }; + + // A settings container wrapper that enforces security. + struct SecureSettingsContainer : public ExchangeSettingsContainer + { + constexpr static std::string_view NodeName_Sha256 = "SHA256"sv; + + SecureSettingsContainer(std::unique_ptr&& container, const std::string_view& name) : + ExchangeSettingsContainer(std::move(container), name), m_secure(GetPathTo(PathName::SecureSettingsForRead), name) {} + + private: + struct VerificationData + { + bool Found = false; + SHA256::HashBuffer Hash; + }; + + VerificationData GetVerificationData() + { + std::unique_ptr stream = m_secure.Get(); + + if (!stream) + { + return {}; + } + + std::string streamContents = Utility::ReadEntireStream(*stream); + + YAML::Node document; + try + { + document = YAML::Load(streamContents); + } + catch (const std::runtime_error& e) + { + AICLI_LOG(Core, Error, << "Secure setting metadata for '" << m_name << "' contained invalid YAML (" << e.what() << "):\n" << streamContents); + return {}; + } + + std::string hashString; + + try + { + hashString = document[NodeName_Sha256].as(); + } + catch (const std::runtime_error& e) + { + AICLI_LOG(Core, Error, << "Secure setting metadata for '" << m_name << "' contained invalid YAML (" << e.what() << "):\n" << streamContents); + return {}; + } + + VerificationData result; + result.Found = true; + result.Hash = SHA256::ConvertToBytes(hashString); + + return result; + } + + bool SetVerificationData(VerificationData data) + { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << NodeName_Sha256 << YAML::Value << SHA256::ConvertToString(data.Hash); + out << YAML::EndMap; + + bool result = m_secure.Set(out.str()); + if (!result) + { + AICLI_LOG(Core, Warning, << "Failed to write verification data for '" << m_name << "'"); + } + return result; + } + + public: + std::unique_ptr Get() override + { + std::unique_ptr stream = ExchangeSettingsContainer::Get(); + + if (!stream) + { + // If no stream exists, then no verification needs to be done. + return stream; + } + + VerificationData verData = GetVerificationData(); + + // This case should be very rare, so a very identifiable error is helpful. + // Plus the text for this one is fairly on point for what has happened. + THROW_HR_IF(SPAPI_E_FILE_HASH_NOT_IN_CATALOG, !verData.Found); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_DATA_CHECKSUM_ERROR), !SHA256::AreEqual(m_hash.value(), verData.Hash)); + + // ExchangeSettingsContainer already produces an in memory stream that we can use. + return stream; + } + + bool Set(std::string_view value) override + { + // Force the creation of the secure settings location with appropriate ACLs + GetPathTo(PathName::SecureSettingsForWrite); + + bool exchangeResult = ExchangeSettingsContainer::Set(value); + + if (exchangeResult) + { + VerificationData verData; + verData.Hash = m_hash.value(); + + if (!SetVerificationData(verData)) + { + AICLI_LOG(Core, Error, << "Failed to write verification data for '" << m_name << "'. Reverting setting write to maintain consistency."); + AICLI_LOG(Core, Warning, << "This failure may be caused by insufficient permissions or disk space issues"); + // Verification data write failed, so we need to revert the main write + // to maintain consistency + Remove(); + return false; + } + } + + return exchangeResult; + } + + void Remove() override + { + ExchangeSettingsContainer::Remove(); + m_secure.Remove(); + } + + std::filesystem::path PathTo() override + { + THROW_HR(E_UNEXPECTED); + } + + private: + FileSettingsContainer m_secure; + }; + + // A settings container wrapper that enforces privacy. + struct EncryptedSettingsContainer : public ExchangeSettingsContainer + { + EncryptedSettingsContainer(std::unique_ptr&& container, const std::string_view& name) : + ExchangeSettingsContainer(std::move(container), name) { + } + + std::unique_ptr Get() override + { + std::optional stream = ExchangeSettingsContainer::GetAsString(); + + if (!stream) + { + // If no stream exists, then nothing needs to be done. + return nullptr; + } + + DATA_BLOB data{}; + data.pbData = reinterpret_cast(stream->data()); + data.cbData = static_cast(stream->size()); + + DATA_BLOB out{}; + auto freeOut = wil::scope_exit([&]() { LocalFree(out.pbData); }); + + THROW_IF_WIN32_BOOL_FALSE(CryptUnprotectData(&data, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &out)); + + return std::make_unique(std::string{ reinterpret_cast(out.pbData), out.cbData }); + } + + bool Set(std::string_view value) override + { + DATA_BLOB data{}; + data.pbData = reinterpret_cast(const_cast(value.data())); + data.cbData = static_cast(value.size()); + + DATA_BLOB out{}; + auto freeOut = wil::scope_exit([&]() { LocalFree(out.pbData); }); + + THROW_IF_WIN32_BOOL_FALSE(CryptProtectData(&data, NULL, NULL, NULL, NULL, CRYPTPROTECT_UI_FORBIDDEN, &out)); + + return ExchangeSettingsContainer::Set(std::string_view{ reinterpret_cast(out.pbData), out.cbData }); + } + + std::filesystem::path PathTo() override + { + THROW_HR(E_UNEXPECTED); + } + }; + + std::unique_ptr GetRawSettingsContainer(Type type, const std::string_view& name) + { +#ifndef WINGET_DISABLE_FOR_FUZZING + if (IsRunningInPackagedContext()) + { + switch (type) + { + case Type::Standard: + return std::make_unique( + ApplicationDataSettingsContainer::GetRelativeContainer( + winrt::Windows::Storage::ApplicationData::Current().LocalSettings(), GetPathTo(PathName::StandardSettings)), + name); + case Type::StandardFile: + return std::make_unique(GetPathTo(PathName::StandardFileSettings), name); + default: + THROW_HR(E_UNEXPECTED); + } + } + else +#endif + { + switch (type) + { + case Type::Standard: + case Type::StandardFile: + return std::make_unique(GetPathTo(PathName::StandardSettings), name); + default: + THROW_HR(E_UNEXPECTED); + } + } + } + + // The default is not a raw container, so we wrap some of the underlying containers to enable higher order behaviors. + std::unique_ptr GetSettingsContainer(Type type, const std::string_view& name) + { + switch (type) + { + case Type::Standard: + // Standard settings should use exchange semantics to prevent overwrites + return std::make_unique(GetRawSettingsContainer(type, name), name); + + case Type::UserFile: + // User file settings are not typically modified by us, so there is no need for exchange + return std::make_unique(GetPathTo(PathName::UserFileSettings), name); + + case Type::Secure: + // Secure settings add hash verification on reads on top of exchange semantics + return std::make_unique(GetRawSettingsContainer(Type::Standard, name), name); + + case Type::Encrypted: + // Encrypted settings add encryption on top of exchange semantics + return std::make_unique(GetRawSettingsContainer(Type::StandardFile, name), name); + + case Type::StandardFile: + // Standard settings should use exchange semantics to prevent overwrites + return std::make_unique(GetRawSettingsContainer(type, name), name); + + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::unique_ptr GetSettingsContainer(const StreamDefinition& streamDefinition) + { + return GetSettingsContainer(streamDefinition.Type, streamDefinition.Name); + } + } + + std::string_view ToString(Type type) + { + switch (type) + { + case Type::Standard: + return "Standard"sv; + case Type::UserFile: + return "UserFile"sv; + case Type::Secure: + return "Secure"sv; + case Type::Encrypted: + return "Encrypted"sv; + case Type::StandardFile: + return "StandardFile"sv; + default: + THROW_HR(E_UNEXPECTED); + } + } + + Stream::Stream(const StreamDefinition& streamDefinition) : + m_streamDefinition(streamDefinition), m_container(GetSettingsContainer(streamDefinition)) + { + ValidateSettingNamePath(m_streamDefinition.Name); + } + + std::unique_ptr Stream::Get() + { + LogSettingAction("Get", m_streamDefinition); + return m_container->Get(); + } + + [[nodiscard]] bool Stream::Set(std::string_view value) + { + LogSettingAction("Set", m_streamDefinition); + return m_container->Set(value); + } + + void Stream::Remove() + { + LogSettingAction("Remove", m_streamDefinition); + m_container->Remove(); + } + + std::string_view Stream::GetName() const + { + return m_streamDefinition.Name; + } + + std::filesystem::path Stream::GetPath() const + { + return m_container->PathTo(); + } +} diff --git a/src/AppInstallerCommonCore/StdErrLogger.cpp b/src/AppInstallerCommonCore/StdErrLogger.cpp index d1341d8531..80d9ce79a0 100644 --- a/src/AppInstallerCommonCore/StdErrLogger.cpp +++ b/src/AppInstallerCommonCore/StdErrLogger.cpp @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/StdErrLogger.h" - -namespace AppInstaller::Logging -{ - namespace - { - static constexpr std::string_view s_StdErrLoggerName = "StdErrLogger"; - } - - std::string StdErrLogger::GetName() const - { - return std::string{ s_StdErrLoggerName }; - } - - void StdErrLogger::Write(Channel channel, Level level, std::string_view message) noexcept try - { - if (level >= m_level) - { - std::cerr << "<" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message << std::endl; - } - } - catch (...) - { - // Just eat any exceptions here; better than losing logs - } - - void StdErrLogger::WriteDirect(Channel, Level level, std::string_view message) noexcept try - { - if (level >= m_level) - { - std::cerr.write(message.data(), static_cast(message.size())); - } - } - catch (...) - { - // Just eat any exceptions here; better than losing logs - } - - void StdErrLogger::Add() - { - Log().AddLogger(std::make_unique()); - } - - void StdErrLogger::Remove() - { - Log().RemoveLogger(std::string{ s_StdErrLoggerName }); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/StdErrLogger.h" + +namespace AppInstaller::Logging +{ + namespace + { + static constexpr std::string_view s_StdErrLoggerName = "StdErrLogger"; + } + + std::string StdErrLogger::GetName() const + { + return std::string{ s_StdErrLoggerName }; + } + + void StdErrLogger::Write(Channel channel, Level level, std::string_view message) noexcept try + { + if (level >= m_level) + { + std::cerr << "<" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message << std::endl; + } + } + catch (...) + { + // Just eat any exceptions here; better than losing logs + } + + void StdErrLogger::WriteDirect(Channel, Level level, std::string_view message) noexcept try + { + if (level >= m_level) + { + std::cerr.write(message.data(), static_cast(message.size())); + } + } + catch (...) + { + // Just eat any exceptions here; better than losing logs + } + + void StdErrLogger::Add() + { + Log().AddLogger(std::make_unique()); + } + + void StdErrLogger::Remove() + { + Log().RemoveLogger(std::string{ s_StdErrLoggerName }); + } +} diff --git a/src/AppInstallerCommonCore/Synchronization.cpp b/src/AppInstallerCommonCore/Synchronization.cpp index 2f3e154e5d..0ecb85ca87 100644 --- a/src/AppInstallerCommonCore/Synchronization.cpp +++ b/src/AppInstallerCommonCore/Synchronization.cpp @@ -1,73 +1,73 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include - - -namespace AppInstaller::Synchronization -{ - // The amount of time that we wait in between checking for cancellation - constexpr std::chrono::milliseconds s_CrossProcessInstallLock_WaitLoopTime = 250ms; - - CrossProcessLock::CrossProcessLock(std::string_view name) : CrossProcessLock(Utility::ConvertToUTF16(name)) - { - } - - CrossProcessLock::CrossProcessLock(const std::wstring& name) - { - m_mutex.create(name.c_str(), 0, SYNCHRONIZE); - } - - CrossProcessLock::~CrossProcessLock() - { - Release(); - } - - bool CrossProcessLock::Acquire(IProgressCallback& progress) - { - while (!progress.IsCancelledBy(CancelReason::Any)) - { - auto lock = m_mutex.acquire(nullptr, static_cast(std::chrono::duration_cast(s_CrossProcessInstallLock_WaitLoopTime).count())); - - if (lock) - { - m_lockThreadId = GetCurrentThreadId(); - m_lock = std::move(lock); - return true; - } - } - - return false; - } - - void CrossProcessLock::Release() - { - if (m_lock) - { - // Ensure that we are in fact always releasing on the same thread that acquired the lock. - // This is to force crashes rather than deadlocks in the event that we make a design error that leads to that. - FAIL_FAST_IF(m_lockThreadId != GetCurrentThreadId()); - m_lock.reset(); - } - } - - bool CrossProcessLock::TryAcquireNoWait() - { - auto lock = m_mutex.acquire(nullptr, 0); - - if (lock) - { - m_lockThreadId = GetCurrentThreadId(); - m_lock = std::move(lock); - return true; - } - - return false; - } - - CrossProcessLock::operator bool() const - { - return static_cast(m_lock); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include + + +namespace AppInstaller::Synchronization +{ + // The amount of time that we wait in between checking for cancellation + constexpr std::chrono::milliseconds s_CrossProcessInstallLock_WaitLoopTime = 250ms; + + CrossProcessLock::CrossProcessLock(std::string_view name) : CrossProcessLock(Utility::ConvertToUTF16(name)) + { + } + + CrossProcessLock::CrossProcessLock(const std::wstring& name) + { + m_mutex.create(name.c_str(), 0, SYNCHRONIZE); + } + + CrossProcessLock::~CrossProcessLock() + { + Release(); + } + + bool CrossProcessLock::Acquire(IProgressCallback& progress) + { + while (!progress.IsCancelledBy(CancelReason::Any)) + { + auto lock = m_mutex.acquire(nullptr, static_cast(std::chrono::duration_cast(s_CrossProcessInstallLock_WaitLoopTime).count())); + + if (lock) + { + m_lockThreadId = GetCurrentThreadId(); + m_lock = std::move(lock); + return true; + } + } + + return false; + } + + void CrossProcessLock::Release() + { + if (m_lock) + { + // Ensure that we are in fact always releasing on the same thread that acquired the lock. + // This is to force crashes rather than deadlocks in the event that we make a design error that leads to that. + FAIL_FAST_IF(m_lockThreadId != GetCurrentThreadId()); + m_lock.reset(); + } + } + + bool CrossProcessLock::TryAcquireNoWait() + { + auto lock = m_mutex.acquire(nullptr, 0); + + if (lock) + { + m_lockThreadId = GetCurrentThreadId(); + m_lock = std::move(lock); + return true; + } + + return false; + } + + CrossProcessLock::operator bool() const + { + return static_cast(m_lock); + } +} diff --git a/src/AppInstallerCommonCore/ThreadGlobals.cpp b/src/AppInstallerCommonCore/ThreadGlobals.cpp index 0d390ab234..8787debc0e 100644 --- a/src/AppInstallerCommonCore/ThreadGlobals.cpp +++ b/src/AppInstallerCommonCore/ThreadGlobals.cpp @@ -1,60 +1,60 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/ThreadGlobals.h" - -namespace AppInstaller::ThreadLocalStorage -{ - using namespace AppInstaller::Logging; - - WingetThreadGlobals::WingetThreadGlobals(WingetThreadGlobals& parent, create_sub_thread_globals_t) - { - parent.Initialize(); - m_pDiagnosticLogger = parent.m_pDiagnosticLogger; - m_pTelemetryLogger = parent.m_pTelemetryLogger->CreateSubTraceLogger(); - // Flip the initialization flag - std::call_once(m_loggerInitOnceFlag, []() {}); - } - - DiagnosticLogger& WingetThreadGlobals::GetDiagnosticLogger() - { - return *(m_pDiagnosticLogger); - } - - void* WingetThreadGlobals::GetTelemetryObject() - { - return m_pTelemetryLogger.get(); - } - - TelemetryTraceLogger& WingetThreadGlobals::GetTelemetryLogger() - { - return *(m_pTelemetryLogger); - } - - std::unique_ptr WingetThreadGlobals::SetForCurrentThread() - { - Initialize(); - return ThreadGlobals::SetForCurrentThread(); - } - - void WingetThreadGlobals::Initialize() - { - try - { - std::call_once(m_loggerInitOnceFlag, [this]() - { - m_pDiagnosticLogger = std::make_unique(); - m_pTelemetryLogger = std::make_unique(); - - // The above make_unique for TelemetryTraceLogger will either create an object or will throw which is caught below. - m_pTelemetryLogger->Initialize(); - }); - } - catch (...) - { - // May throw std::system_error if any condition prevents calls to call_once from executing as specified - // May throw std::bad_alloc or any exception thrown by the constructor of TelemetryTraceLogger - // Loggers are best effort and shouldn't block core functionality. So eat up the exceptions here - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/ThreadGlobals.h" + +namespace AppInstaller::ThreadLocalStorage +{ + using namespace AppInstaller::Logging; + + WingetThreadGlobals::WingetThreadGlobals(WingetThreadGlobals& parent, create_sub_thread_globals_t) + { + parent.Initialize(); + m_pDiagnosticLogger = parent.m_pDiagnosticLogger; + m_pTelemetryLogger = parent.m_pTelemetryLogger->CreateSubTraceLogger(); + // Flip the initialization flag + std::call_once(m_loggerInitOnceFlag, []() {}); + } + + DiagnosticLogger& WingetThreadGlobals::GetDiagnosticLogger() + { + return *(m_pDiagnosticLogger); + } + + void* WingetThreadGlobals::GetTelemetryObject() + { + return m_pTelemetryLogger.get(); + } + + TelemetryTraceLogger& WingetThreadGlobals::GetTelemetryLogger() + { + return *(m_pTelemetryLogger); + } + + std::unique_ptr WingetThreadGlobals::SetForCurrentThread() + { + Initialize(); + return ThreadGlobals::SetForCurrentThread(); + } + + void WingetThreadGlobals::Initialize() + { + try + { + std::call_once(m_loggerInitOnceFlag, [this]() + { + m_pDiagnosticLogger = std::make_unique(); + m_pTelemetryLogger = std::make_unique(); + + // The above make_unique for TelemetryTraceLogger will either create an object or will throw which is caught below. + m_pTelemetryLogger->Initialize(); + }); + } + catch (...) + { + // May throw std::system_error if any condition prevents calls to call_once from executing as specified + // May throw std::bad_alloc or any exception thrown by the constructor of TelemetryTraceLogger + // Loggers are best effort and shouldn't block core functionality. So eat up the exceptions here + } + } +} diff --git a/src/AppInstallerCommonCore/TraceLogger.cpp b/src/AppInstallerCommonCore/TraceLogger.cpp index bb95e42ee9..48547b1571 100644 --- a/src/AppInstallerCommonCore/TraceLogger.cpp +++ b/src/AppInstallerCommonCore/TraceLogger.cpp @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/TraceLogger.h" -#include "Public/AppInstallerTelemetry.h" -#include "Public/winget/ThreadGlobals.h" - -namespace AppInstaller::Logging -{ - void TraceLogger::Write(Channel channel, Level level, std::string_view message) noexcept try - { - // Send to a string first to create a single block to log to a trace. - std::stringstream strstr; - strstr << std::chrono::system_clock::now() << " <" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message << std::endl; - - TraceLoggingWriteActivity(g_hTraceProvider, - "Diagnostics", - Telemetry().GetActivityId(), - Telemetry().GetParentActivityId(), - TraceLoggingString(strstr.str().c_str(), "LogMessage")); - } - catch (...) - { - // Just eat any exceptions here; better to lose logs than functionality - } - - void TraceLogger::WriteDirect(Channel, Level, std::string_view message) noexcept try - { - TraceLoggingWriteActivity(g_hTraceProvider, - "Diagnostics", - Telemetry().GetActivityId(), - Telemetry().GetParentActivityId(), - TraceLoggingCountedUtf8String(message.data(), static_cast(message.size()), "LogMessage")); - } - catch (...) - { - // Just eat any exceptions here; better to lose logs than functionality - } - - std::string TraceLogger::GetName() const - { - return "Trace"; - } - - void TraceLogger::Add() - { - Log().AddLogger(std::make_unique()); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/TraceLogger.h" +#include "Public/AppInstallerTelemetry.h" +#include "Public/winget/ThreadGlobals.h" + +namespace AppInstaller::Logging +{ + void TraceLogger::Write(Channel channel, Level level, std::string_view message) noexcept try + { + // Send to a string first to create a single block to log to a trace. + std::stringstream strstr; + strstr << std::chrono::system_clock::now() << " <" << GetLevelChar(level) << "> [" << std::setw(GetMaxChannelNameLength()) << std::left << std::setfill(' ') << GetChannelName(channel) << "] " << message << std::endl; + + TraceLoggingWriteActivity(g_hTraceProvider, + "Diagnostics", + Telemetry().GetActivityId(), + Telemetry().GetParentActivityId(), + TraceLoggingString(strstr.str().c_str(), "LogMessage")); + } + catch (...) + { + // Just eat any exceptions here; better to lose logs than functionality + } + + void TraceLogger::WriteDirect(Channel, Level, std::string_view message) noexcept try + { + TraceLoggingWriteActivity(g_hTraceProvider, + "Diagnostics", + Telemetry().GetActivityId(), + Telemetry().GetParentActivityId(), + TraceLoggingCountedUtf8String(message.data(), static_cast(message.size()), "LogMessage")); + } + catch (...) + { + // Just eat any exceptions here; better to lose logs than functionality + } + + std::string TraceLogger::GetName() const + { + return "Trace"; + } + + void TraceLogger::Add() + { + Log().AddLogger(std::make_unique()); + } +} diff --git a/src/AppInstallerCommonCore/UserSettings.cpp b/src/AppInstallerCommonCore/UserSettings.cpp index 38cdd0847a..a7f9bb3b93 100644 --- a/src/AppInstallerCommonCore/UserSettings.cpp +++ b/src/AppInstallerCommonCore/UserSettings.cpp @@ -1,739 +1,739 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AppInstallerRuntime.h" -#include "AppInstallerLanguageUtilities.h" -#include "AppInstallerLogging.h" -#include "winget/JsonUtil.h" -#include "winget/Settings.h" -#include "winget/UserSettings.h" -#include "winget/filesystem.h" - -#include "AppInstallerArchitecture.h" -#include "winget/Locale.h" - -namespace AppInstaller::Settings -{ - using namespace std::string_view_literals; - using namespace Runtime; - using namespace Utility; - using namespace Logging; - using namespace JSON; - using namespace Filesystem; - - static constexpr std::string_view s_SettingEmpty = - R"({ - "$schema": "https://aka.ms/winget-settings.schema.json", - - // For documentation on these settings, see: https://aka.ms/winget-settings - // "source": { - // "autoUpdateIntervalInMinutes": 5 - // }, -})"sv; - - namespace - { - template - inline std::string GetValueString(T value) - { - std::string convertedValue; - - if constexpr (std::is_arithmetic_v) - { - convertedValue = std::to_string(value); - } - else - { - convertedValue = value; - } - - return convertedValue; - } - - template<> - inline std::string GetValueString(std::vector value) - { - std::string convertedValue = "["; - - bool first = true; - for (auto const& entry : value) - { - if (first) - { - first = false; - } - else - { - convertedValue += ", "; - } - - convertedValue += entry; - } - - convertedValue += ']'; - - return convertedValue; - } - - std::optional ParseSettingsContent(const std::string& content, std::string_view settingName, std::vector& warnings) - { - Json::Value root; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); - std::string error; - - if (reader->parse(content.c_str(), content.c_str() + content.size(), &root, &error)) - { - return root; - } - - AICLI_LOG(Core, Error, << "Error parsing " << settingName << ": " << error); - warnings.emplace_back(StringResource::String::SettingsWarningParseError, settingName, error, false); - - return {}; - } - - std::optional ParseFile(const StreamDefinition& setting, std::vector& warnings) - { - try - { - auto stream = Stream{ setting }.Get(); - if (stream) - { - std::string settingsContentStr = Utility::ReadEntireStream(*stream); - return ParseSettingsContent(settingsContentStr, setting.Name, warnings); - } - } - catch (const std::exception& e) - { - AICLI_LOG(Core, Error, << "Failed to read " << setting.Name << "; Reason: " << e.what()); - } - catch (...) - { - AICLI_LOG(Core, Error, << "Failed to read " << setting.Name << "; Reason unknown."); - } - - return {}; - } - - template - std::optional::json_t> GetValueFromPolicy() - { - return GroupPolicies().GetValue::Policy>(); - } - - template - void Validate( - Json::Value& root, - std::map& settings, - std::vector& warnings) - { - // jsoncpp doesn't support std::string_view yet. - auto path = std::string(details::SettingMapping::Path); - - // Settings set by Group Policy override anything else. See if there is one. - auto policyValue = GetValueFromPolicy(); - if (policyValue.has_value()) - { - // If the value is valid, use it. - // Otherwise, fall back to default. - // In any case, we do not need to read the setting from the JSON. - auto validatedValue = details::SettingMapping::Validate(policyValue.value()); - if (validatedValue.has_value()) - { - // Add it to the map - settings[S].emplace( - std::forward::value_t>(validatedValue.value())); - AICLI_LOG(Core, Verbose, << "Valid setting from Group Policy. Field: " << path << " Value: " << GetValueString(policyValue.value())); - } - else - { - auto valueAsString = GetValueString(policyValue.value()); - AICLI_LOG(Core, Error, << "Invalid setting from Group Policy. Field: " << path << " Value: " << valueAsString); - warnings.emplace_back(StringResource::String::SettingsWarningInvalidValueFromPolicy, path, valueAsString); - } - - return; - } - - const Json::Path jsonPath(path); - Json::Value result = jsonPath.resolve(root); - if (!result.isNull()) - { - auto jsonValue = GetValue::json_t>(result); - - if (jsonValue.has_value()) - { - auto validatedValue = details::SettingMapping::Validate(jsonValue.value()); - - if (validatedValue.has_value()) - { - // Finally add it to the map - settings[S].emplace( - std::forward::value_t>(validatedValue.value())); - AICLI_LOG(Core, Verbose, << "Valid setting. Field: " << path << " Value: " << GetValueString(jsonValue.value())); - } - else - { - auto valueAsString = GetValueString(jsonValue.value()); - AICLI_LOG(Core, Error, << "Invalid field value. Field: " << path << " Value: " << valueAsString); - warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldValue, path, valueAsString); - } - } - else - { - AICLI_LOG(Core, Error, << "Invalid field format. Field: " << path << " Using default"); - warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldFormat, path); - } - } - else - { - AICLI_LOG(Core, Verbose, << "Setting " << path << " not found. Using default"); - } - } - - template - void ValidateAll( - Json::Value& root, - std::map& settings, - std::vector& warnings, - std::index_sequence) - { - // Use folding to call each setting validate function. - (FoldHelper{}, ..., Validate(S)>(root, settings, warnings)); - } - - std::optional ValidatePathValue(std::string_view value) - { - std::filesystem::path path = ConvertToUTF16(value); - if (!path.is_absolute()) - { - return {}; - } - - return path; - } - } - - std::optional ConvertToSortField(std::string_view value) - { - static constexpr std::string_view s_sortField_relevance = "relevance"; - static constexpr std::string_view s_sortField_name = "name"; - static constexpr std::string_view s_sortField_id = "id"; - static constexpr std::string_view s_sortField_version = "version"; - static constexpr std::string_view s_sortField_source = "source"; - static constexpr std::string_view s_sortField_available = "available"; - - std::string lowered = Utility::ToLower(value); - - if (lowered == s_sortField_relevance) return SortField::Relevance; - if (lowered == s_sortField_name) return SortField::Name; - if (lowered == s_sortField_id) return SortField::Id; - if (lowered == s_sortField_version) return SortField::Version; - if (lowered == s_sortField_source) return SortField::Source; - if (lowered == s_sortField_available) return SortField::Available; - return std::nullopt; - } - - namespace details - { -#define WINGET_VALIDATE_SIGNATURE(_setting_) \ - std::optional::value_t> \ - SettingMapping::Validate(const SettingMapping::json_t& value) - - // Stamps out a validate function that simply returns the input value. -#define WINGET_VALIDATE_PASS_THROUGH(_setting_) \ - WINGET_VALIDATE_SIGNATURE(_setting_) \ - { \ - return value; \ - } - - WINGET_VALIDATE_SIGNATURE(AutoUpdateTimeInMinutes) - { - return std::chrono::minutes(value); - } - - WINGET_VALIDATE_SIGNATURE(ProgressBarVisualStyle) - { - std::string lowerValue = ToLower(value); - - if (value == "accent") - { - return VisualStyle::Accent; - } - else if (value == "rainbow") - { - return VisualStyle::Rainbow; - } - else if (value == "retro") - { - return VisualStyle::Retro; - } - else if (value == "sixel") - { - return VisualStyle::Sixel; - } - else if (value == "disabled") - { - return VisualStyle::Disabled; - } - - return {}; - } - - WINGET_VALIDATE_PASS_THROUGH(EnableSixelDisplay) - WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) - WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) - WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) - WINGET_VALIDATE_PASS_THROUGH(EFResume) - WINGET_VALIDATE_PASS_THROUGH(EFFonts) - WINGET_VALIDATE_PASS_THROUGH(EFSourcePriority) - WINGET_VALIDATE_PASS_THROUGH(AnonymizePathForDisplay) - WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) - WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) - WINGET_VALIDATE_PASS_THROUGH(InstallSkipDependencies) - WINGET_VALIDATE_PASS_THROUGH(DisableInstallNotes) - WINGET_VALIDATE_PASS_THROUGH(UninstallPurgePortablePackage) - WINGET_VALIDATE_PASS_THROUGH(NetworkWingetAlternateSourceURL) - WINGET_VALIDATE_PASS_THROUGH(MaxResumes) - WINGET_VALIDATE_PASS_THROUGH(LoggingFileTotalSizeLimitInMB) - WINGET_VALIDATE_PASS_THROUGH(LoggingFileIndividualSizeLimitInMB) - WINGET_VALIDATE_PASS_THROUGH(LoggingFileCountLimit) - -#ifndef AICLI_DISABLE_TEST_HOOKS - WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) - WINGET_VALIDATE_PASS_THROUGH(KeepAllLogFiles) -#endif - - WINGET_VALIDATE_SIGNATURE(PortablePackageUserRoot) - { - return ValidatePathValue(value); - } - - WINGET_VALIDATE_SIGNATURE(PortablePackageMachineRoot) - { - return ValidatePathValue(value); - } - - WINGET_VALIDATE_SIGNATURE(ArchiveExtractionMethod) - { - static constexpr std::string_view s_archiveExtractionMethod_shellApi = "shellApi"; - static constexpr std::string_view s_archiveExtractionMethod_tar = "tar"; - - if (Utility::CaseInsensitiveEquals(value, s_archiveExtractionMethod_tar)) - { - return Archive::ExtractionMethod::Tar; - } - else if (Utility::CaseInsensitiveEquals(value, s_archiveExtractionMethod_shellApi)) - { - return Archive::ExtractionMethod::ShellApi; - } - - return {}; - } - - WINGET_VALIDATE_SIGNATURE(InstallArchitecturePreference) - { - std::vector archs; - for (auto const& i : value) - { - Utility::Architecture arch = Utility::ConvertToArchitectureEnum(i); - if (Utility::IsApplicableArchitecture(arch) == Utility::InapplicableArchitecture) - { - return {}; - } - archs.emplace_back(arch); - } - return archs; - } - - WINGET_VALIDATE_SIGNATURE(InstallArchitectureRequirement) - { - return SettingMapping::Validate(value); - } - - WINGET_VALIDATE_SIGNATURE(InstallScopePreference) - { - static constexpr std::string_view s_scope_user = "user"; - static constexpr std::string_view s_scope_machine = "machine"; - - if (Utility::CaseInsensitiveEquals(value, s_scope_user)) - { - return Manifest::ScopeEnum::User; - } - else if (Utility::CaseInsensitiveEquals(value, s_scope_machine)) - { - return Manifest::ScopeEnum::Machine; - } - - return {}; - } - - WINGET_VALIDATE_SIGNATURE(InstallScopeRequirement) - { - return SettingMapping::Validate(value); - } - - WINGET_VALIDATE_SIGNATURE(InstallLocalePreference) - { - for (auto const& entry : value) - { - if (!Locale::IsWellFormedBcp47Tag(entry)) - { - return {}; - } - } - - return value; - } - - WINGET_VALIDATE_SIGNATURE(InstallLocaleRequirement) - { - return SettingMapping::Validate(value); - } - - WINGET_VALIDATE_SIGNATURE(InstallerTypePreference) - { - std::vector installerTypes; - for (auto const& i : value) - { - Manifest::InstallerTypeEnum installerType = Manifest::ConvertToInstallerTypeEnum(i); - if (installerType == Manifest::InstallerTypeEnum::Unknown) - { - return {}; - } - installerTypes.emplace_back(installerType); - } - return installerTypes; - } - - WINGET_VALIDATE_SIGNATURE(InstallerTypeRequirement) - { - return SettingMapping::Validate(value); - } - - WINGET_VALIDATE_SIGNATURE(InstallDefaultRoot) - { - return ValidatePathValue(value); - } - - WINGET_VALIDATE_SIGNATURE(DownloadDefaultDirectory) - { - return ValidatePathValue(value); - } - - WINGET_VALIDATE_SIGNATURE(ConfigureDefaultModuleRoot) - { - return ValidatePathValue(value); - } - - WINGET_VALIDATE_SIGNATURE(NetworkDownloader) - { - static constexpr std::string_view s_downloader_default = "default"; - static constexpr std::string_view s_downloader_wininet = "wininet"; - static constexpr std::string_view s_downloader_do = "do"; - - if (Utility::CaseInsensitiveEquals(value, s_downloader_default)) - { - return InstallerDownloader::Default; - } - else if (Utility::CaseInsensitiveEquals(value, s_downloader_wininet)) - { - return InstallerDownloader::WinInet; - } - else if (Utility::CaseInsensitiveEquals(value, s_downloader_do)) - { - return InstallerDownloader::DeliveryOptimization; - } - - return {}; - } - - WINGET_VALIDATE_SIGNATURE(NetworkDOProgressTimeoutInSeconds) - { - return std::chrono::seconds(value); - } - - WINGET_VALIDATE_SIGNATURE(LoggingLevelPreference) - { - // logging preference possible values - static constexpr std::string_view s_logging_verbose = "verbose"; - static constexpr std::string_view s_logging_info = "info"; - static constexpr std::string_view s_logging_warning = "warning"; - static constexpr std::string_view s_logging_error = "error"; - static constexpr std::string_view s_logging_critical = "critical"; - - if (Utility::CaseInsensitiveEquals(value, s_logging_verbose)) - { - return Level::Verbose; - } - else if (Utility::CaseInsensitiveEquals(value, s_logging_info)) - { - return Level::Info; - } - else if (Utility::CaseInsensitiveEquals(value, s_logging_warning)) - { - return Level::Warning; - } - else if (Utility::CaseInsensitiveEquals(value, s_logging_error)) - { - return Level::Error; - } - else if (Utility::CaseInsensitiveEquals(value, s_logging_critical)) - { - return Level::Crit; - } - return {}; - } - - WINGET_VALIDATE_SIGNATURE(LoggingChannelPreference) - { - Logging::Channel result = Logging::Channel::None; - - for (auto const& entry : value) - { - result |= GetChannelFromName(entry); - } - - return result; - } - - WINGET_VALIDATE_SIGNATURE(LoggingFileNameStrategy) - { - // logging name strategy possible values - static constexpr std::string_view s_strategy_manifest = "manifest"; - static constexpr std::string_view s_strategy_timestamp = "timestamp"; - static constexpr std::string_view s_strategy_guid = "guid"; - static constexpr std::string_view s_strategy_shortguid = "shortguid"; - - if (Utility::CaseInsensitiveEquals(value, s_strategy_manifest)) - { - return LogNameStrategy::Manifest; - } - else if (Utility::CaseInsensitiveEquals(value, s_strategy_timestamp)) - { - return LogNameStrategy::Timestamp; - } - else if (Utility::CaseInsensitiveEquals(value, s_strategy_guid)) - { - return LogNameStrategy::Guid; - } - else if (Utility::CaseInsensitiveEquals(value, s_strategy_shortguid)) - { - return LogNameStrategy::ShortGuid; - } - return {}; - } - - WINGET_VALIDATE_SIGNATURE(LoggingFileAgeLimitInDays) - { - return value * 24h; - } - - WINGET_VALIDATE_SIGNATURE(OutputSortOrder) - { - std::vector fields; - for (auto const& entry : value) - { - auto field = ConvertToSortField(entry); - if (!field) - { - return {}; - } - fields.emplace_back(field.value()); - } - return fields; - } - - WINGET_VALIDATE_SIGNATURE(OutputSortDirection) - { - static constexpr std::string_view s_sortDirection_ascending = "ascending"; - static constexpr std::string_view s_sortDirection_descending = "descending"; - - if (Utility::CaseInsensitiveEquals(value, s_sortDirection_ascending)) - { - return SortDirection::Ascending; - } - else if (Utility::CaseInsensitiveEquals(value, s_sortDirection_descending)) - { - return SortDirection::Descending; - } - - return {}; - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static UserSettings* s_UserSettings_Override = nullptr; - - void SetUserSettingsOverride(UserSettings* value) - { - s_UserSettings_Override = value; - } -#endif - - static std::atomic_bool s_userSettingsInitialized{ false }; - static std::atomic_bool s_userSettingsInInitialization{ false }; - - UserSettings const& UserSettings::Instance(const std::optional& content) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_UserSettings_Override) - { - return *s_UserSettings_Override; - } -#endif - if (!s_userSettingsInitialized) - { - s_userSettingsInInitialization = true; - } - - static UserSettings userSettings(content); - s_userSettingsInitialized = true; - s_userSettingsInInitialization = false; - - return userSettings; - } - - const UserSettings* TryGetUser() - { - if (s_userSettingsInitialized) - { - return &UserSettings::Instance(); - } - - // Try to initialize UserSettings, return nullptr if it's already in initialization. - if (s_userSettingsInInitialization) - { - return nullptr; - } - - return &UserSettings::Instance(); - } - - UserSettings const& User() - { - return UserSettings::Instance(); - } - - bool TryInitializeCustomUserSettings(std::string content) - { - if (s_userSettingsInitialized || s_userSettingsInInitialization) - { - return false; - } - - return UserSettings::Instance(std::move(content)).GetType() == UserSettingsType::Custom; - } - - UserSettings::UserSettings(const std::optional& content) : m_type(UserSettingsType::Default) - { - Json::Value settingsRoot = Json::Value::nullSingleton(); - - // Settings can be loaded from settings.json or settings.json.backup files. - // 0 - Use default (empty) settings if disabled by group policy. - // if - // 1 - Use passed in settings content if available. - // else - // 2 - Use settings.json if exists and passes parsing. - // 3 - Use settings.backup.json if settings.json fails to parse. - // finally - // 4 - Use default (empty) if both settings files fail to load. - - if (!GroupPolicies().IsEnabled(TogglePolicy::Policy::Settings)) - { - AICLI_LOG(Core, Info, << "Ignoring settings file due to group policy. Using default values."); - return; - } - - if (content.has_value()) - { - auto settingsJson = ParseSettingsContent(content.value(), "CustomSettings", m_warnings); - if (settingsJson.has_value()) - { - AICLI_LOG(Core, Info, << "Settings loaded from custom settings"); - m_type = UserSettingsType::Custom; - settingsRoot = settingsJson.value(); - } - } - else - { - auto settingsJson = ParseFile(Stream::PrimaryUserSettings, m_warnings); - if (settingsJson.has_value()) - { - AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::PrimaryUserSettings.Name); - m_type = UserSettingsType::Standard; - settingsRoot = settingsJson.value(); - } - - // Settings didn't parse or doesn't exist, try with backup. - if (settingsRoot.isNull()) - { - auto settingsBackupJson = ParseFile(Stream::BackupUserSettings, m_warnings); - if (settingsBackupJson.has_value()) - { - AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::BackupUserSettings.Name); - m_warnings.emplace_back(StringResource::String::SettingsWarningLoadedBackupSettings); - m_type = UserSettingsType::Backup; - settingsRoot = settingsBackupJson.value(); - } - else - { - // Settings and back up didn't parse or exist. If they exist then warn the user. - auto settingsPath = Stream{ Stream::PrimaryUserSettings }.GetPath(); - auto backupPath = Stream{ Stream::BackupUserSettings }.GetPath(); - if (std::filesystem::exists(settingsPath) || std::filesystem::exists(backupPath)) - { - m_warnings.emplace_back(StringResource::String::SettingsWarningUsingDefault); - } - } - } - } - - if (!settingsRoot.isNull()) - { - ValidateAll(settingsRoot, m_settings, m_warnings, std::make_index_sequence(Setting::Max)>()); - } - else - { - AICLI_LOG(Core, Info, << "Valid settings file not found. Using default values."); - } - } - - void UserSettings::PrepareToShellExecuteFile() const - { - UserSettingsType userSettingType = GetType(); - - if (userSettingType == UserSettingsType::Default) - { - Stream primarySettings{ Stream::PrimaryUserSettings }; - - // Create settings file if it doesn't exist. - if (!std::filesystem::exists(primarySettings.GetPath())) - { - std::ignore = primarySettings.Set(s_SettingEmpty); - AICLI_LOG(Core, Info, << "Created new settings file"); - } - } - else if (userSettingType == UserSettingsType::Standard) - { - // Settings file was loaded correctly, create backup. - auto from = SettingsFilePath(); - auto to = Stream{ Stream::BackupUserSettings }.GetPath(); - std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); - AICLI_LOG(Core, Info, << "Copied settings to backup file"); - } - } - - std::filesystem::path UserSettings::SettingsFilePath(bool forDisplay) - { - auto path = Stream{ Stream::PrimaryUserSettings }.GetPath(); - - if (forDisplay && Settings::User().Get()) - { - ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%"); - } - - return path; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AppInstallerRuntime.h" +#include "AppInstallerLanguageUtilities.h" +#include "AppInstallerLogging.h" +#include "winget/JsonUtil.h" +#include "winget/Settings.h" +#include "winget/UserSettings.h" +#include "winget/filesystem.h" + +#include "AppInstallerArchitecture.h" +#include "winget/Locale.h" + +namespace AppInstaller::Settings +{ + using namespace std::string_view_literals; + using namespace Runtime; + using namespace Utility; + using namespace Logging; + using namespace JSON; + using namespace Filesystem; + + static constexpr std::string_view s_SettingEmpty = + R"({ + "$schema": "https://aka.ms/winget-settings.schema.json", + + // For documentation on these settings, see: https://aka.ms/winget-settings + // "source": { + // "autoUpdateIntervalInMinutes": 5 + // }, +})"sv; + + namespace + { + template + inline std::string GetValueString(T value) + { + std::string convertedValue; + + if constexpr (std::is_arithmetic_v) + { + convertedValue = std::to_string(value); + } + else + { + convertedValue = value; + } + + return convertedValue; + } + + template<> + inline std::string GetValueString(std::vector value) + { + std::string convertedValue = "["; + + bool first = true; + for (auto const& entry : value) + { + if (first) + { + first = false; + } + else + { + convertedValue += ", "; + } + + convertedValue += entry; + } + + convertedValue += ']'; + + return convertedValue; + } + + std::optional ParseSettingsContent(const std::string& content, std::string_view settingName, std::vector& warnings) + { + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + std::string error; + + if (reader->parse(content.c_str(), content.c_str() + content.size(), &root, &error)) + { + return root; + } + + AICLI_LOG(Core, Error, << "Error parsing " << settingName << ": " << error); + warnings.emplace_back(StringResource::String::SettingsWarningParseError, settingName, error, false); + + return {}; + } + + std::optional ParseFile(const StreamDefinition& setting, std::vector& warnings) + { + try + { + auto stream = Stream{ setting }.Get(); + if (stream) + { + std::string settingsContentStr = Utility::ReadEntireStream(*stream); + return ParseSettingsContent(settingsContentStr, setting.Name, warnings); + } + } + catch (const std::exception& e) + { + AICLI_LOG(Core, Error, << "Failed to read " << setting.Name << "; Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(Core, Error, << "Failed to read " << setting.Name << "; Reason unknown."); + } + + return {}; + } + + template + std::optional::json_t> GetValueFromPolicy() + { + return GroupPolicies().GetValue::Policy>(); + } + + template + void Validate( + Json::Value& root, + std::map& settings, + std::vector& warnings) + { + // jsoncpp doesn't support std::string_view yet. + auto path = std::string(details::SettingMapping::Path); + + // Settings set by Group Policy override anything else. See if there is one. + auto policyValue = GetValueFromPolicy(); + if (policyValue.has_value()) + { + // If the value is valid, use it. + // Otherwise, fall back to default. + // In any case, we do not need to read the setting from the JSON. + auto validatedValue = details::SettingMapping::Validate(policyValue.value()); + if (validatedValue.has_value()) + { + // Add it to the map + settings[S].emplace( + std::forward::value_t>(validatedValue.value())); + AICLI_LOG(Core, Verbose, << "Valid setting from Group Policy. Field: " << path << " Value: " << GetValueString(policyValue.value())); + } + else + { + auto valueAsString = GetValueString(policyValue.value()); + AICLI_LOG(Core, Error, << "Invalid setting from Group Policy. Field: " << path << " Value: " << valueAsString); + warnings.emplace_back(StringResource::String::SettingsWarningInvalidValueFromPolicy, path, valueAsString); + } + + return; + } + + const Json::Path jsonPath(path); + Json::Value result = jsonPath.resolve(root); + if (!result.isNull()) + { + auto jsonValue = GetValue::json_t>(result); + + if (jsonValue.has_value()) + { + auto validatedValue = details::SettingMapping::Validate(jsonValue.value()); + + if (validatedValue.has_value()) + { + // Finally add it to the map + settings[S].emplace( + std::forward::value_t>(validatedValue.value())); + AICLI_LOG(Core, Verbose, << "Valid setting. Field: " << path << " Value: " << GetValueString(jsonValue.value())); + } + else + { + auto valueAsString = GetValueString(jsonValue.value()); + AICLI_LOG(Core, Error, << "Invalid field value. Field: " << path << " Value: " << valueAsString); + warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldValue, path, valueAsString); + } + } + else + { + AICLI_LOG(Core, Error, << "Invalid field format. Field: " << path << " Using default"); + warnings.emplace_back(StringResource::String::SettingsWarningInvalidFieldFormat, path); + } + } + else + { + AICLI_LOG(Core, Verbose, << "Setting " << path << " not found. Using default"); + } + } + + template + void ValidateAll( + Json::Value& root, + std::map& settings, + std::vector& warnings, + std::index_sequence) + { + // Use folding to call each setting validate function. + (FoldHelper{}, ..., Validate(S)>(root, settings, warnings)); + } + + std::optional ValidatePathValue(std::string_view value) + { + std::filesystem::path path = ConvertToUTF16(value); + if (!path.is_absolute()) + { + return {}; + } + + return path; + } + } + + std::optional ConvertToSortField(std::string_view value) + { + static constexpr std::string_view s_sortField_relevance = "relevance"; + static constexpr std::string_view s_sortField_name = "name"; + static constexpr std::string_view s_sortField_id = "id"; + static constexpr std::string_view s_sortField_version = "version"; + static constexpr std::string_view s_sortField_source = "source"; + static constexpr std::string_view s_sortField_available = "available"; + + std::string lowered = Utility::ToLower(value); + + if (lowered == s_sortField_relevance) return SortField::Relevance; + if (lowered == s_sortField_name) return SortField::Name; + if (lowered == s_sortField_id) return SortField::Id; + if (lowered == s_sortField_version) return SortField::Version; + if (lowered == s_sortField_source) return SortField::Source; + if (lowered == s_sortField_available) return SortField::Available; + return std::nullopt; + } + + namespace details + { +#define WINGET_VALIDATE_SIGNATURE(_setting_) \ + std::optional::value_t> \ + SettingMapping::Validate(const SettingMapping::json_t& value) + + // Stamps out a validate function that simply returns the input value. +#define WINGET_VALIDATE_PASS_THROUGH(_setting_) \ + WINGET_VALIDATE_SIGNATURE(_setting_) \ + { \ + return value; \ + } + + WINGET_VALIDATE_SIGNATURE(AutoUpdateTimeInMinutes) + { + return std::chrono::minutes(value); + } + + WINGET_VALIDATE_SIGNATURE(ProgressBarVisualStyle) + { + std::string lowerValue = ToLower(value); + + if (value == "accent") + { + return VisualStyle::Accent; + } + else if (value == "rainbow") + { + return VisualStyle::Rainbow; + } + else if (value == "retro") + { + return VisualStyle::Retro; + } + else if (value == "sixel") + { + return VisualStyle::Sixel; + } + else if (value == "disabled") + { + return VisualStyle::Disabled; + } + + return {}; + } + + WINGET_VALIDATE_PASS_THROUGH(EnableSixelDisplay) + WINGET_VALIDATE_PASS_THROUGH(EFExperimentalCmd) + WINGET_VALIDATE_PASS_THROUGH(EFExperimentalArg) + WINGET_VALIDATE_PASS_THROUGH(EFDirectMSI) + WINGET_VALIDATE_PASS_THROUGH(EFResume) + WINGET_VALIDATE_PASS_THROUGH(EFFonts) + WINGET_VALIDATE_PASS_THROUGH(EFSourcePriority) + WINGET_VALIDATE_PASS_THROUGH(AnonymizePathForDisplay) + WINGET_VALIDATE_PASS_THROUGH(TelemetryDisable) + WINGET_VALIDATE_PASS_THROUGH(InteractivityDisable) + WINGET_VALIDATE_PASS_THROUGH(InstallSkipDependencies) + WINGET_VALIDATE_PASS_THROUGH(DisableInstallNotes) + WINGET_VALIDATE_PASS_THROUGH(UninstallPurgePortablePackage) + WINGET_VALIDATE_PASS_THROUGH(NetworkWingetAlternateSourceURL) + WINGET_VALIDATE_PASS_THROUGH(MaxResumes) + WINGET_VALIDATE_PASS_THROUGH(LoggingFileTotalSizeLimitInMB) + WINGET_VALIDATE_PASS_THROUGH(LoggingFileIndividualSizeLimitInMB) + WINGET_VALIDATE_PASS_THROUGH(LoggingFileCountLimit) + +#ifndef AICLI_DISABLE_TEST_HOOKS + WINGET_VALIDATE_PASS_THROUGH(EnableSelfInitiatedMinidump) + WINGET_VALIDATE_PASS_THROUGH(KeepAllLogFiles) +#endif + + WINGET_VALIDATE_SIGNATURE(PortablePackageUserRoot) + { + return ValidatePathValue(value); + } + + WINGET_VALIDATE_SIGNATURE(PortablePackageMachineRoot) + { + return ValidatePathValue(value); + } + + WINGET_VALIDATE_SIGNATURE(ArchiveExtractionMethod) + { + static constexpr std::string_view s_archiveExtractionMethod_shellApi = "shellApi"; + static constexpr std::string_view s_archiveExtractionMethod_tar = "tar"; + + if (Utility::CaseInsensitiveEquals(value, s_archiveExtractionMethod_tar)) + { + return Archive::ExtractionMethod::Tar; + } + else if (Utility::CaseInsensitiveEquals(value, s_archiveExtractionMethod_shellApi)) + { + return Archive::ExtractionMethod::ShellApi; + } + + return {}; + } + + WINGET_VALIDATE_SIGNATURE(InstallArchitecturePreference) + { + std::vector archs; + for (auto const& i : value) + { + Utility::Architecture arch = Utility::ConvertToArchitectureEnum(i); + if (Utility::IsApplicableArchitecture(arch) == Utility::InapplicableArchitecture) + { + return {}; + } + archs.emplace_back(arch); + } + return archs; + } + + WINGET_VALIDATE_SIGNATURE(InstallArchitectureRequirement) + { + return SettingMapping::Validate(value); + } + + WINGET_VALIDATE_SIGNATURE(InstallScopePreference) + { + static constexpr std::string_view s_scope_user = "user"; + static constexpr std::string_view s_scope_machine = "machine"; + + if (Utility::CaseInsensitiveEquals(value, s_scope_user)) + { + return Manifest::ScopeEnum::User; + } + else if (Utility::CaseInsensitiveEquals(value, s_scope_machine)) + { + return Manifest::ScopeEnum::Machine; + } + + return {}; + } + + WINGET_VALIDATE_SIGNATURE(InstallScopeRequirement) + { + return SettingMapping::Validate(value); + } + + WINGET_VALIDATE_SIGNATURE(InstallLocalePreference) + { + for (auto const& entry : value) + { + if (!Locale::IsWellFormedBcp47Tag(entry)) + { + return {}; + } + } + + return value; + } + + WINGET_VALIDATE_SIGNATURE(InstallLocaleRequirement) + { + return SettingMapping::Validate(value); + } + + WINGET_VALIDATE_SIGNATURE(InstallerTypePreference) + { + std::vector installerTypes; + for (auto const& i : value) + { + Manifest::InstallerTypeEnum installerType = Manifest::ConvertToInstallerTypeEnum(i); + if (installerType == Manifest::InstallerTypeEnum::Unknown) + { + return {}; + } + installerTypes.emplace_back(installerType); + } + return installerTypes; + } + + WINGET_VALIDATE_SIGNATURE(InstallerTypeRequirement) + { + return SettingMapping::Validate(value); + } + + WINGET_VALIDATE_SIGNATURE(InstallDefaultRoot) + { + return ValidatePathValue(value); + } + + WINGET_VALIDATE_SIGNATURE(DownloadDefaultDirectory) + { + return ValidatePathValue(value); + } + + WINGET_VALIDATE_SIGNATURE(ConfigureDefaultModuleRoot) + { + return ValidatePathValue(value); + } + + WINGET_VALIDATE_SIGNATURE(NetworkDownloader) + { + static constexpr std::string_view s_downloader_default = "default"; + static constexpr std::string_view s_downloader_wininet = "wininet"; + static constexpr std::string_view s_downloader_do = "do"; + + if (Utility::CaseInsensitiveEquals(value, s_downloader_default)) + { + return InstallerDownloader::Default; + } + else if (Utility::CaseInsensitiveEquals(value, s_downloader_wininet)) + { + return InstallerDownloader::WinInet; + } + else if (Utility::CaseInsensitiveEquals(value, s_downloader_do)) + { + return InstallerDownloader::DeliveryOptimization; + } + + return {}; + } + + WINGET_VALIDATE_SIGNATURE(NetworkDOProgressTimeoutInSeconds) + { + return std::chrono::seconds(value); + } + + WINGET_VALIDATE_SIGNATURE(LoggingLevelPreference) + { + // logging preference possible values + static constexpr std::string_view s_logging_verbose = "verbose"; + static constexpr std::string_view s_logging_info = "info"; + static constexpr std::string_view s_logging_warning = "warning"; + static constexpr std::string_view s_logging_error = "error"; + static constexpr std::string_view s_logging_critical = "critical"; + + if (Utility::CaseInsensitiveEquals(value, s_logging_verbose)) + { + return Level::Verbose; + } + else if (Utility::CaseInsensitiveEquals(value, s_logging_info)) + { + return Level::Info; + } + else if (Utility::CaseInsensitiveEquals(value, s_logging_warning)) + { + return Level::Warning; + } + else if (Utility::CaseInsensitiveEquals(value, s_logging_error)) + { + return Level::Error; + } + else if (Utility::CaseInsensitiveEquals(value, s_logging_critical)) + { + return Level::Crit; + } + return {}; + } + + WINGET_VALIDATE_SIGNATURE(LoggingChannelPreference) + { + Logging::Channel result = Logging::Channel::None; + + for (auto const& entry : value) + { + result |= GetChannelFromName(entry); + } + + return result; + } + + WINGET_VALIDATE_SIGNATURE(LoggingFileNameStrategy) + { + // logging name strategy possible values + static constexpr std::string_view s_strategy_manifest = "manifest"; + static constexpr std::string_view s_strategy_timestamp = "timestamp"; + static constexpr std::string_view s_strategy_guid = "guid"; + static constexpr std::string_view s_strategy_shortguid = "shortguid"; + + if (Utility::CaseInsensitiveEquals(value, s_strategy_manifest)) + { + return LogNameStrategy::Manifest; + } + else if (Utility::CaseInsensitiveEquals(value, s_strategy_timestamp)) + { + return LogNameStrategy::Timestamp; + } + else if (Utility::CaseInsensitiveEquals(value, s_strategy_guid)) + { + return LogNameStrategy::Guid; + } + else if (Utility::CaseInsensitiveEquals(value, s_strategy_shortguid)) + { + return LogNameStrategy::ShortGuid; + } + return {}; + } + + WINGET_VALIDATE_SIGNATURE(LoggingFileAgeLimitInDays) + { + return value * 24h; + } + + WINGET_VALIDATE_SIGNATURE(OutputSortOrder) + { + std::vector fields; + for (auto const& entry : value) + { + auto field = ConvertToSortField(entry); + if (!field) + { + return {}; + } + fields.emplace_back(field.value()); + } + return fields; + } + + WINGET_VALIDATE_SIGNATURE(OutputSortDirection) + { + static constexpr std::string_view s_sortDirection_ascending = "ascending"; + static constexpr std::string_view s_sortDirection_descending = "descending"; + + if (Utility::CaseInsensitiveEquals(value, s_sortDirection_ascending)) + { + return SortDirection::Ascending; + } + else if (Utility::CaseInsensitiveEquals(value, s_sortDirection_descending)) + { + return SortDirection::Descending; + } + + return {}; + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + static UserSettings* s_UserSettings_Override = nullptr; + + void SetUserSettingsOverride(UserSettings* value) + { + s_UserSettings_Override = value; + } +#endif + + static std::atomic_bool s_userSettingsInitialized{ false }; + static std::atomic_bool s_userSettingsInInitialization{ false }; + + UserSettings const& UserSettings::Instance(const std::optional& content) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_UserSettings_Override) + { + return *s_UserSettings_Override; + } +#endif + if (!s_userSettingsInitialized) + { + s_userSettingsInInitialization = true; + } + + static UserSettings userSettings(content); + s_userSettingsInitialized = true; + s_userSettingsInInitialization = false; + + return userSettings; + } + + const UserSettings* TryGetUser() + { + if (s_userSettingsInitialized) + { + return &UserSettings::Instance(); + } + + // Try to initialize UserSettings, return nullptr if it's already in initialization. + if (s_userSettingsInInitialization) + { + return nullptr; + } + + return &UserSettings::Instance(); + } + + UserSettings const& User() + { + return UserSettings::Instance(); + } + + bool TryInitializeCustomUserSettings(std::string content) + { + if (s_userSettingsInitialized || s_userSettingsInInitialization) + { + return false; + } + + return UserSettings::Instance(std::move(content)).GetType() == UserSettingsType::Custom; + } + + UserSettings::UserSettings(const std::optional& content) : m_type(UserSettingsType::Default) + { + Json::Value settingsRoot = Json::Value::nullSingleton(); + + // Settings can be loaded from settings.json or settings.json.backup files. + // 0 - Use default (empty) settings if disabled by group policy. + // if + // 1 - Use passed in settings content if available. + // else + // 2 - Use settings.json if exists and passes parsing. + // 3 - Use settings.backup.json if settings.json fails to parse. + // finally + // 4 - Use default (empty) if both settings files fail to load. + + if (!GroupPolicies().IsEnabled(TogglePolicy::Policy::Settings)) + { + AICLI_LOG(Core, Info, << "Ignoring settings file due to group policy. Using default values."); + return; + } + + if (content.has_value()) + { + auto settingsJson = ParseSettingsContent(content.value(), "CustomSettings", m_warnings); + if (settingsJson.has_value()) + { + AICLI_LOG(Core, Info, << "Settings loaded from custom settings"); + m_type = UserSettingsType::Custom; + settingsRoot = settingsJson.value(); + } + } + else + { + auto settingsJson = ParseFile(Stream::PrimaryUserSettings, m_warnings); + if (settingsJson.has_value()) + { + AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::PrimaryUserSettings.Name); + m_type = UserSettingsType::Standard; + settingsRoot = settingsJson.value(); + } + + // Settings didn't parse or doesn't exist, try with backup. + if (settingsRoot.isNull()) + { + auto settingsBackupJson = ParseFile(Stream::BackupUserSettings, m_warnings); + if (settingsBackupJson.has_value()) + { + AICLI_LOG(Core, Info, << "Settings loaded from " << Stream::BackupUserSettings.Name); + m_warnings.emplace_back(StringResource::String::SettingsWarningLoadedBackupSettings); + m_type = UserSettingsType::Backup; + settingsRoot = settingsBackupJson.value(); + } + else + { + // Settings and back up didn't parse or exist. If they exist then warn the user. + auto settingsPath = Stream{ Stream::PrimaryUserSettings }.GetPath(); + auto backupPath = Stream{ Stream::BackupUserSettings }.GetPath(); + if (std::filesystem::exists(settingsPath) || std::filesystem::exists(backupPath)) + { + m_warnings.emplace_back(StringResource::String::SettingsWarningUsingDefault); + } + } + } + } + + if (!settingsRoot.isNull()) + { + ValidateAll(settingsRoot, m_settings, m_warnings, std::make_index_sequence(Setting::Max)>()); + } + else + { + AICLI_LOG(Core, Info, << "Valid settings file not found. Using default values."); + } + } + + void UserSettings::PrepareToShellExecuteFile() const + { + UserSettingsType userSettingType = GetType(); + + if (userSettingType == UserSettingsType::Default) + { + Stream primarySettings{ Stream::PrimaryUserSettings }; + + // Create settings file if it doesn't exist. + if (!std::filesystem::exists(primarySettings.GetPath())) + { + std::ignore = primarySettings.Set(s_SettingEmpty); + AICLI_LOG(Core, Info, << "Created new settings file"); + } + } + else if (userSettingType == UserSettingsType::Standard) + { + // Settings file was loaded correctly, create backup. + auto from = SettingsFilePath(); + auto to = Stream{ Stream::BackupUserSettings }.GetPath(); + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + AICLI_LOG(Core, Info, << "Copied settings to backup file"); + } + } + + std::filesystem::path UserSettings::SettingsFilePath(bool forDisplay) + { + auto path = Stream{ Stream::PrimaryUserSettings }.GetPath(); + + if (forDisplay && Settings::User().Get()) + { + ReplaceCommonPathPrefix(path, GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%"); + } + + return path; + } +} diff --git a/src/AppInstallerCommonCore/packages.config b/src/AppInstallerCommonCore/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/AppInstallerCommonCore/packages.config +++ b/src/AppInstallerCommonCore/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/AppInstallerCommonCore/pch.h b/src/AppInstallerCommonCore/pch.h index 365fea37af..f9c8fdfe25 100644 --- a/src/AppInstallerCommonCore/pch.h +++ b/src/AppInstallerCommonCore/pch.h @@ -1,116 +1,116 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#define NOMINMAX -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "TraceLogging.h" - -#define YAML_DECLARE_STATIC -#include - -// TODO: See if we can get down to having just one JSON parser... -#include - -#pragma warning( push ) -#pragma warning ( disable : 4458 4100 4702 6031 26439 ) -#include -#include -#include -#include -#pragma warning( pop ) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef WINGET_DISABLE_FOR_FUZZING -#pragma warning( push ) -#pragma warning ( disable : 26495 26439 ) -#include -#include -#include -#pragma warning( pop ) -#endif - -#pragma warning( push ) -#pragma warning ( disable : 6001 6285 6287 6340 6387 6388 26451 26495 28196 ) -#include -#include -#include -#include -#include -#include -#include -#include -#include -#pragma warning( pop ) - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -// Stream/buffer helper APIs -#include -#include - -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define NOMINMAX +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "TraceLogging.h" + +#define YAML_DECLARE_STATIC +#include + +// TODO: See if we can get down to having just one JSON parser... +#include + +#pragma warning( push ) +#pragma warning ( disable : 4458 4100 4702 6031 26439 ) +#include +#include +#include +#include +#pragma warning( pop ) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef WINGET_DISABLE_FOR_FUZZING +#pragma warning( push ) +#pragma warning ( disable : 26495 26439 ) +#include +#include +#include +#pragma warning( pop ) +#endif + +#pragma warning( push ) +#pragma warning ( disable : 6001 6285 6287 6340 6387 6388 26451 26495 28196 ) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning( pop ) + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// Stream/buffer helper APIs +#include +#include + +#include +#include diff --git a/src/AppInstallerRepositoryCore/ARPCorrelation.cpp b/src/AppInstallerRepositoryCore/ARPCorrelation.cpp index 1e8fe8eb49..871a932d07 100644 --- a/src/AppInstallerRepositoryCore/ARPCorrelation.cpp +++ b/src/AppInstallerRepositoryCore/ARPCorrelation.cpp @@ -1,294 +1,294 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/ARPCorrelation.h" -#include "winget/ARPCorrelationAlgorithms.h" -#include "winget/Manifest.h" -#include "winget/NameNormalization.h" -#include "winget/RepositorySearch.h" -#include "winget/RepositorySource.h" - -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Utility; - -namespace AppInstaller::Repository::Correlation -{ - namespace - { - constexpr double MatchingThreshold = 0.5; - constexpr double MinimumDifferentiationThreshold = 0.05; - - IARPMatchConfidenceAlgorithm& InstanceInternal(std::optional algorithmOverride = {}) - { - static WordsEditDistanceMatchConfidenceAlgorithm s_algorithm; - static IARPMatchConfidenceAlgorithm* s_override = nullptr; - - if (algorithmOverride.has_value()) - { - s_override = algorithmOverride.value(); - } - - if (s_override) - { - return *s_override; - } - else - { - return s_algorithm; - } - } - } - - IARPMatchConfidenceAlgorithm& IARPMatchConfidenceAlgorithm::Instance() - { - return InstanceInternal(); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - void IARPMatchConfidenceAlgorithm::OverrideInstance(IARPMatchConfidenceAlgorithm* algorithmOverride) - { - InstanceInternal(algorithmOverride); - } - - void IARPMatchConfidenceAlgorithm::ResetInstance() - { - InstanceInternal(nullptr); - } -#endif - - // Find the best match using heuristics - ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( - const Manifest::Manifest& manifest, - const std::vector& arpEntries) - { - // TODO: In the future we can make different passes with different algorithms until we find a match - return FindARPEntryForNewlyInstalledPackageWithHeuristics(manifest, arpEntries, IARPMatchConfidenceAlgorithm::Instance()); - } - - ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( - const AppInstaller::Manifest::Manifest& manifest, - const std::vector& arpEntries, - IARPMatchConfidenceAlgorithm& algorithm) - { - if (arpEntries.empty()) - { - AICLI_LOG(Repo, Warning, << "Empty ARP entries given"); - return {}; - } - - AICLI_LOG(Repo, Verbose, << "Looking for best match in ARP for manifest " << manifest.Id); - - algorithm.Init(manifest); - - ARPHeuristicsCorrelationResult result; - result.Measures.reserve(arpEntries.size()); - - for (const auto& arpEntry : arpEntries) - { - auto score = algorithm.ComputeConfidence(arpEntry); - AICLI_LOG(Repo, Verbose, << "Match confidence for " << arpEntry.Entry->GetProperty(PackageProperty::Id) << ": " << score); - - result.Measures.emplace_back(CorrelationMeasure{ score, arpEntry.Entry->GetLatestVersion() }); - } - - std::sort(result.Measures.begin(), result.Measures.end(), [](const CorrelationMeasure& a, const CorrelationMeasure& b) { return a.Measure > b.Measure; }); - - if (result.Measures[0].Measure < MatchingThreshold) - { - AICLI_LOG(Repo, Verbose, << "Maximum score [" << result.Measures[0].Measure << "] is lower than threshold [" << MatchingThreshold << "]"); - result.Reason = "maximum score below threshold"; - } - else if (result.Measures.size() >= 2 && (result.Measures[0].Measure - result.Measures[1].Measure) < MinimumDifferentiationThreshold) - { - AICLI_LOG(Repo, Verbose, << "Top two scores, [" << result.Measures[0].Measure << "] and [" << result.Measures[1].Measure << "] are not significantly different [" << MinimumDifferentiationThreshold << "]"); - result.Reason = "top two scores are not significantly different"; - } - else - { - AICLI_LOG(Repo, Verbose, << "Best match is " << result.Measures[0].Package->GetProperty(PackageVersionProperty::Id)); - result.Package = result.Measures[0].Package; - result.Reason = "heuristics match"; - } - - return result; - } - - void ARPCorrelationData::CapturePreInstallSnapshot() - { - ProgressCallback empty; - Repository::Source preInstallARP = Repository::Source(PredefinedSource::ARP); - preInstallARP.Open(empty); - - for (const auto& entry : preInstallARP.Search({}).Matches) - { - auto installed = entry.Package->GetInstalled()->GetLatestVersion(); - if (installed) - { - m_preInstallSnapshot.emplace_back(std::make_tuple( - installed->GetProperty(PackageVersionProperty::Id), - installed->GetProperty(PackageVersionProperty::Version), - installed->GetProperty(PackageVersionProperty::Channel))); - } - } - - std::sort(m_preInstallSnapshot.begin(), m_preInstallSnapshot.end()); - } - - void ARPCorrelationData::CapturePostInstallSnapshot() - { - ProgressCallback empty; - m_postInstallSnapshotSource = Repository::Source(PredefinedSource::ARP); - m_postInstallSnapshotSource.Open(empty); - - for (auto& entry : m_postInstallSnapshotSource.Search({}).Matches) - { - auto installed = entry.Package->GetInstalled()->GetLatestVersion(); - - if (installed) - { - auto entryKey = std::make_tuple( - installed->GetProperty(PackageVersionProperty::Id), - installed->GetProperty(PackageVersionProperty::Version), - installed->GetProperty(PackageVersionProperty::Channel)); - - auto itr = std::lower_bound(m_preInstallSnapshot.begin(), m_preInstallSnapshot.end(), entryKey); - m_postInstallSnapshot.emplace_back(entry.Package->GetInstalled(), itr == m_preInstallSnapshot.end() || *itr != entryKey); - } - } - } - - ARPCorrelationResult ARPCorrelationData::CorrelateForNewlyInstalled(const Manifest::Manifest& manifest, const ARPCorrelationSettings& settings) - { - AICLI_LOG(Repo, Verbose, << "Finding ARP entry matching newly installed package"); - - // Also attempt to find the entry based on the manifest data - - SearchRequest manifestSearchRequest; - AppInstaller::Manifest::Manifest::string_t defaultPublisher; - if (manifest.DefaultLocalization.Contains(Localization::Publisher)) - { - defaultPublisher = manifest.DefaultLocalization.Get(); - } - - // The default localization must contain the name or we cannot do this lookup - if (manifest.DefaultLocalization.Contains(Localization::PackageName)) - { - AppInstaller::Manifest::Manifest::string_t defaultName = manifest.DefaultLocalization.Get(); - manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, defaultName, defaultPublisher)); - - for (const auto& loc : manifest.Localizations) - { - if (loc.Contains(Localization::PackageName) || loc.Contains(Localization::Publisher)) - { - manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, - loc.Contains(Localization::PackageName) ? loc.Get() : defaultName, - loc.Contains(Localization::Publisher) ? loc.Get() : defaultPublisher)); - } - } - } - - std::set productCodes; - std::set upgradeCodes; - for (const auto& installer : manifest.Installers) - { - if (!installer.ProductCode.empty()) - { - // Add each ProductCode only once - if (productCodes.insert(installer.ProductCode).second) - { - manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, installer.ProductCode)); - } - } - - for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) - { - if (!appsAndFeaturesEntry.DisplayName.empty()) - { - manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, - appsAndFeaturesEntry.DisplayName, - appsAndFeaturesEntry.Publisher.empty() ? defaultPublisher : appsAndFeaturesEntry.Publisher)); - } - - // Add each ProductCode and UpgradeCode only once; - if (!appsAndFeaturesEntry.ProductCode.empty() && productCodes.insert(appsAndFeaturesEntry.ProductCode).second) - { - manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, appsAndFeaturesEntry.ProductCode)); - } - if (!appsAndFeaturesEntry.UpgradeCode.empty() && upgradeCodes.insert(appsAndFeaturesEntry.UpgradeCode).second) - { - manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::UpgradeCode, MatchType::Exact, appsAndFeaturesEntry.UpgradeCode)); - } - } - } - - SearchResult findByManifest; - - // Don't execute this search if it would just find everything - if (!manifestSearchRequest.IsForEverything()) - { - findByManifest = m_postInstallSnapshotSource.Search(manifestSearchRequest); - } - - // Cross reference the changes with the search results - std::vector> packagesInBoth; - - for (const auto& change : m_postInstallSnapshot) - { - if (change.IsNewOrUpdated) - { - for (const auto& byManifest : findByManifest.Matches) - { - if (change.Entry->IsSame(byManifest.Package->GetInstalled().get())) - { - packagesInBoth.emplace_back(change.Entry); - break; - } - } - } - } - - // We now have all of the package changes; time to report them. - // - // The set of cases we could have for finding packages based on the manifest: - // 0 packages :: The manifest data does not match the ARP information. - // 1 package :: Golden path; this should be what we installed. - // 2+ packages :: The data in the manifest is either too broad or we have - // a problem with our name normalization. - - // Find the package that we are going to log - ARPCorrelationResult result; - // TODO: Find a good way to consider the other heuristics in these stats. - result.ChangesToARP = std::count_if(m_postInstallSnapshot.begin(), m_postInstallSnapshot.end(), [](const ARPEntry& e) { return e.IsNewOrUpdated; }); - result.MatchesInARP = findByManifest.Matches.size(); - result.CountOfIntersectionOfChangesAndMatches = packagesInBoth.size(); - - // If there is only a single common package (changed and matches), it is almost certainly the correct one. - if (settings.AllowNormalization && packagesInBoth.size() == 1) - { - result.Package = packagesInBoth[0]->GetLatestVersion(); - result.Reason = "normalization match and new/changed"; - } - // If it wasn't changed but we still find a match, that is the best thing to report. - else if (settings.AllowNormalization && findByManifest.Matches.size() == 1) - { - result.Package = findByManifest.Matches[0].Package->GetInstalled()->GetLatestVersion(); - result.Reason = "normalization match (not new/changed)"; - } - else if (settings.AllowSingleChange && result.ChangesToARP == 1) - { - result.Package = std::find_if(m_postInstallSnapshot.begin(), m_postInstallSnapshot.end(), [](const ARPEntry& e) { return e.IsNewOrUpdated; })->Entry->GetLatestVersion(); - result.Reason = "only new/changed value"; - } - else - { - // We were not able to find an exact match, so we now run some heuristics - // to try and match the package with some ARP entry by assigning them scores. - AICLI_LOG(Repo, Verbose, << "No exact ARP match found. Trying to find one with heuristics"); - - result = FindARPEntryForNewlyInstalledPackageWithHeuristics(manifest, m_postInstallSnapshot); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ARPCorrelation.h" +#include "winget/ARPCorrelationAlgorithms.h" +#include "winget/Manifest.h" +#include "winget/NameNormalization.h" +#include "winget/RepositorySearch.h" +#include "winget/RepositorySource.h" + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility; + +namespace AppInstaller::Repository::Correlation +{ + namespace + { + constexpr double MatchingThreshold = 0.5; + constexpr double MinimumDifferentiationThreshold = 0.05; + + IARPMatchConfidenceAlgorithm& InstanceInternal(std::optional algorithmOverride = {}) + { + static WordsEditDistanceMatchConfidenceAlgorithm s_algorithm; + static IARPMatchConfidenceAlgorithm* s_override = nullptr; + + if (algorithmOverride.has_value()) + { + s_override = algorithmOverride.value(); + } + + if (s_override) + { + return *s_override; + } + else + { + return s_algorithm; + } + } + } + + IARPMatchConfidenceAlgorithm& IARPMatchConfidenceAlgorithm::Instance() + { + return InstanceInternal(); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + void IARPMatchConfidenceAlgorithm::OverrideInstance(IARPMatchConfidenceAlgorithm* algorithmOverride) + { + InstanceInternal(algorithmOverride); + } + + void IARPMatchConfidenceAlgorithm::ResetInstance() + { + InstanceInternal(nullptr); + } +#endif + + // Find the best match using heuristics + ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( + const Manifest::Manifest& manifest, + const std::vector& arpEntries) + { + // TODO: In the future we can make different passes with different algorithms until we find a match + return FindARPEntryForNewlyInstalledPackageWithHeuristics(manifest, arpEntries, IARPMatchConfidenceAlgorithm::Instance()); + } + + ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( + const AppInstaller::Manifest::Manifest& manifest, + const std::vector& arpEntries, + IARPMatchConfidenceAlgorithm& algorithm) + { + if (arpEntries.empty()) + { + AICLI_LOG(Repo, Warning, << "Empty ARP entries given"); + return {}; + } + + AICLI_LOG(Repo, Verbose, << "Looking for best match in ARP for manifest " << manifest.Id); + + algorithm.Init(manifest); + + ARPHeuristicsCorrelationResult result; + result.Measures.reserve(arpEntries.size()); + + for (const auto& arpEntry : arpEntries) + { + auto score = algorithm.ComputeConfidence(arpEntry); + AICLI_LOG(Repo, Verbose, << "Match confidence for " << arpEntry.Entry->GetProperty(PackageProperty::Id) << ": " << score); + + result.Measures.emplace_back(CorrelationMeasure{ score, arpEntry.Entry->GetLatestVersion() }); + } + + std::sort(result.Measures.begin(), result.Measures.end(), [](const CorrelationMeasure& a, const CorrelationMeasure& b) { return a.Measure > b.Measure; }); + + if (result.Measures[0].Measure < MatchingThreshold) + { + AICLI_LOG(Repo, Verbose, << "Maximum score [" << result.Measures[0].Measure << "] is lower than threshold [" << MatchingThreshold << "]"); + result.Reason = "maximum score below threshold"; + } + else if (result.Measures.size() >= 2 && (result.Measures[0].Measure - result.Measures[1].Measure) < MinimumDifferentiationThreshold) + { + AICLI_LOG(Repo, Verbose, << "Top two scores, [" << result.Measures[0].Measure << "] and [" << result.Measures[1].Measure << "] are not significantly different [" << MinimumDifferentiationThreshold << "]"); + result.Reason = "top two scores are not significantly different"; + } + else + { + AICLI_LOG(Repo, Verbose, << "Best match is " << result.Measures[0].Package->GetProperty(PackageVersionProperty::Id)); + result.Package = result.Measures[0].Package; + result.Reason = "heuristics match"; + } + + return result; + } + + void ARPCorrelationData::CapturePreInstallSnapshot() + { + ProgressCallback empty; + Repository::Source preInstallARP = Repository::Source(PredefinedSource::ARP); + preInstallARP.Open(empty); + + for (const auto& entry : preInstallARP.Search({}).Matches) + { + auto installed = entry.Package->GetInstalled()->GetLatestVersion(); + if (installed) + { + m_preInstallSnapshot.emplace_back(std::make_tuple( + installed->GetProperty(PackageVersionProperty::Id), + installed->GetProperty(PackageVersionProperty::Version), + installed->GetProperty(PackageVersionProperty::Channel))); + } + } + + std::sort(m_preInstallSnapshot.begin(), m_preInstallSnapshot.end()); + } + + void ARPCorrelationData::CapturePostInstallSnapshot() + { + ProgressCallback empty; + m_postInstallSnapshotSource = Repository::Source(PredefinedSource::ARP); + m_postInstallSnapshotSource.Open(empty); + + for (auto& entry : m_postInstallSnapshotSource.Search({}).Matches) + { + auto installed = entry.Package->GetInstalled()->GetLatestVersion(); + + if (installed) + { + auto entryKey = std::make_tuple( + installed->GetProperty(PackageVersionProperty::Id), + installed->GetProperty(PackageVersionProperty::Version), + installed->GetProperty(PackageVersionProperty::Channel)); + + auto itr = std::lower_bound(m_preInstallSnapshot.begin(), m_preInstallSnapshot.end(), entryKey); + m_postInstallSnapshot.emplace_back(entry.Package->GetInstalled(), itr == m_preInstallSnapshot.end() || *itr != entryKey); + } + } + } + + ARPCorrelationResult ARPCorrelationData::CorrelateForNewlyInstalled(const Manifest::Manifest& manifest, const ARPCorrelationSettings& settings) + { + AICLI_LOG(Repo, Verbose, << "Finding ARP entry matching newly installed package"); + + // Also attempt to find the entry based on the manifest data + + SearchRequest manifestSearchRequest; + AppInstaller::Manifest::Manifest::string_t defaultPublisher; + if (manifest.DefaultLocalization.Contains(Localization::Publisher)) + { + defaultPublisher = manifest.DefaultLocalization.Get(); + } + + // The default localization must contain the name or we cannot do this lookup + if (manifest.DefaultLocalization.Contains(Localization::PackageName)) + { + AppInstaller::Manifest::Manifest::string_t defaultName = manifest.DefaultLocalization.Get(); + manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, defaultName, defaultPublisher)); + + for (const auto& loc : manifest.Localizations) + { + if (loc.Contains(Localization::PackageName) || loc.Contains(Localization::Publisher)) + { + manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, + loc.Contains(Localization::PackageName) ? loc.Get() : defaultName, + loc.Contains(Localization::Publisher) ? loc.Get() : defaultPublisher)); + } + } + } + + std::set productCodes; + std::set upgradeCodes; + for (const auto& installer : manifest.Installers) + { + if (!installer.ProductCode.empty()) + { + // Add each ProductCode only once + if (productCodes.insert(installer.ProductCode).second) + { + manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, installer.ProductCode)); + } + } + + for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) + { + if (!appsAndFeaturesEntry.DisplayName.empty()) + { + manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::NormalizedNameAndPublisher, MatchType::Exact, + appsAndFeaturesEntry.DisplayName, + appsAndFeaturesEntry.Publisher.empty() ? defaultPublisher : appsAndFeaturesEntry.Publisher)); + } + + // Add each ProductCode and UpgradeCode only once; + if (!appsAndFeaturesEntry.ProductCode.empty() && productCodes.insert(appsAndFeaturesEntry.ProductCode).second) + { + manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::ProductCode, MatchType::Exact, appsAndFeaturesEntry.ProductCode)); + } + if (!appsAndFeaturesEntry.UpgradeCode.empty() && upgradeCodes.insert(appsAndFeaturesEntry.UpgradeCode).second) + { + manifestSearchRequest.Inclusions.emplace_back(PackageMatchFilter(PackageMatchField::UpgradeCode, MatchType::Exact, appsAndFeaturesEntry.UpgradeCode)); + } + } + } + + SearchResult findByManifest; + + // Don't execute this search if it would just find everything + if (!manifestSearchRequest.IsForEverything()) + { + findByManifest = m_postInstallSnapshotSource.Search(manifestSearchRequest); + } + + // Cross reference the changes with the search results + std::vector> packagesInBoth; + + for (const auto& change : m_postInstallSnapshot) + { + if (change.IsNewOrUpdated) + { + for (const auto& byManifest : findByManifest.Matches) + { + if (change.Entry->IsSame(byManifest.Package->GetInstalled().get())) + { + packagesInBoth.emplace_back(change.Entry); + break; + } + } + } + } + + // We now have all of the package changes; time to report them. + // + // The set of cases we could have for finding packages based on the manifest: + // 0 packages :: The manifest data does not match the ARP information. + // 1 package :: Golden path; this should be what we installed. + // 2+ packages :: The data in the manifest is either too broad or we have + // a problem with our name normalization. + + // Find the package that we are going to log + ARPCorrelationResult result; + // TODO: Find a good way to consider the other heuristics in these stats. + result.ChangesToARP = std::count_if(m_postInstallSnapshot.begin(), m_postInstallSnapshot.end(), [](const ARPEntry& e) { return e.IsNewOrUpdated; }); + result.MatchesInARP = findByManifest.Matches.size(); + result.CountOfIntersectionOfChangesAndMatches = packagesInBoth.size(); + + // If there is only a single common package (changed and matches), it is almost certainly the correct one. + if (settings.AllowNormalization && packagesInBoth.size() == 1) + { + result.Package = packagesInBoth[0]->GetLatestVersion(); + result.Reason = "normalization match and new/changed"; + } + // If it wasn't changed but we still find a match, that is the best thing to report. + else if (settings.AllowNormalization && findByManifest.Matches.size() == 1) + { + result.Package = findByManifest.Matches[0].Package->GetInstalled()->GetLatestVersion(); + result.Reason = "normalization match (not new/changed)"; + } + else if (settings.AllowSingleChange && result.ChangesToARP == 1) + { + result.Package = std::find_if(m_postInstallSnapshot.begin(), m_postInstallSnapshot.end(), [](const ARPEntry& e) { return e.IsNewOrUpdated; })->Entry->GetLatestVersion(); + result.Reason = "only new/changed value"; + } + else + { + // We were not able to find an exact match, so we now run some heuristics + // to try and match the package with some ARP entry by assigning them scores. + AICLI_LOG(Repo, Verbose, << "No exact ARP match found. Trying to find one with heuristics"); + + result = FindARPEntryForNewlyInstalledPackageWithHeuristics(manifest, m_postInstallSnapshot); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/ARPCorrelationAlgorithms.cpp b/src/AppInstallerRepositoryCore/ARPCorrelationAlgorithms.cpp index 0f5bfd548b..d1e6d023e3 100644 --- a/src/AppInstallerRepositoryCore/ARPCorrelationAlgorithms.cpp +++ b/src/AppInstallerRepositoryCore/ARPCorrelationAlgorithms.cpp @@ -1,197 +1,197 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/ARPCorrelationAlgorithms.h" - -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Utility; - -namespace AppInstaller::Repository::Correlation -{ - using WordSequence = WordsEditDistanceMatchConfidenceAlgorithm::WordSequence; - - namespace - { - // A simple matrix class to hold score tables without having to allocate multiple arrays. - struct Matrix - { - Matrix(size_t rows, size_t columns) : m_rows(rows), m_columns(columns), m_data(rows* columns) {} - - double& At(size_t i, size_t j) - { - return m_data[i * m_columns + j]; - } - - private: - size_t m_rows; - size_t m_columns; - std::vector m_data; - }; - - double EditDistanceScore(const std::vector& s1, const std::vector& s2) - { - // Naive implementation of edit distance (scaled over the sequence size). - // This considers only the operations of adding and removing elements. - - if (s1.empty() || s2.empty()) - { - return 0; - } - - // distance[i, j] = distance between s1[0:i] and s2[0:j] - // We don't need to hold more than two rows at a time, but it's simpler to keep the whole table. - Matrix distance(s1.size() + 1, s2.size() + 1); - - for (size_t i = 0; i < s1.size(); ++i) - { - for (size_t j = 0; j < s2.size(); ++j) - { - double& d = distance.At(i, j); - if (s1[i] == s2[j]) - { - // If the two elements are equal, the distance is the same as from one element before. - // In case we are on the first element of one of the two sequences, the distance is - // equal to the cost of adding all the previous elements in the other - if (i == 0) - { - d = static_cast(j); - } - else if (j == 0) - { - d = static_cast(i); - } - else - { - d = distance.At(i - 1, j - 1); - } - } - else - { - // If the two elements are distinct, the score is the cost of removing the last element - // in one sequence plus the cost of editing the remainder of both. - if (i > 0 && j > 0) - { - d = 1 + std::min(distance.At(i - 1, j), distance.At(i, j - 1)); - } - else if (i > 0) - { - d = 1 + distance.At(i - 1, j); - } - else if (j > 0) - { - d = 1 + distance.At(i, j - 1); - } - else - { - // Remove one and add the other - d = 2; - } - } - } - } - - // Maximum distance is equal to the sum of both lengths (removing all elements from one and adding all the elements from the other). - // We use that to scale to [0,1]. - // A smaller distance represents a higher match, so we subtract from 1 for the final score - double editDistance = distance.At(s1.size() - 1, s2.size() - 1); - return 1 - editDistance / (static_cast(s1.size()) + static_cast(s2.size())); - } - } - - WordsEditDistanceMatchConfidenceAlgorithm::NameAndPublisher::NameAndPublisher(const WordSequence& name, const WordSequence& publisher) : Name(name), Publisher(publisher) - { - NamePublisher.insert(NamePublisher.end(), publisher.begin(), publisher.end()); - NamePublisher.insert(NamePublisher.end(), name.begin(), name.end()); - } - - WordsEditDistanceMatchConfidenceAlgorithm::NameAndPublisher::NameAndPublisher(WordSequence&& name, WordSequence&& publisher) : Name(std::move(name)), Publisher(std::move(publisher)) - { - NamePublisher.insert(NamePublisher.end(), publisher.begin(), publisher.end()); - NamePublisher.insert(NamePublisher.end(), name.begin(), name.end()); - - } - - void WordsEditDistanceMatchConfidenceAlgorithm::Init(const AppInstaller::Manifest::Manifest& manifest) - { - // We will use the name and publisher from each localization. - m_namesAndPublishers.clear(); - - WordSequence defaultPublisher; - if (manifest.DefaultLocalization.Contains(Manifest::Localization::Publisher)) - { - defaultPublisher = NormalizeAndPreparePublisher(manifest.DefaultLocalization.Get()); - } - - if (manifest.DefaultLocalization.Contains(Manifest::Localization::PackageName)) - { - WordSequence defaultName = NormalizeAndPrepareName(manifest.DefaultLocalization.Get()); - m_namesAndPublishers.emplace_back(defaultName, defaultPublisher); - - for (const auto& loc : manifest.Localizations) - { - if (loc.Contains(Manifest::Localization::PackageName) || loc.Contains(Manifest::Localization::Publisher)) - { - auto name = loc.Contains(Manifest::Localization::PackageName) ? NormalizeAndPrepareName(loc.Get()) : defaultName; - auto publisher = loc.Contains(Manifest::Localization::Publisher) ? NormalizeAndPreparePublisher(loc.Get()) : defaultPublisher; - - m_namesAndPublishers.emplace_back(std::move(name), std::move(publisher)); - } - } - } - } - - double WordsEditDistanceMatchConfidenceAlgorithm::ComputeConfidence(const ARPEntry& arpEntry) const - { - // Name and Publisher are available as multi properties, but for ARP entries there will only be 0 or 1 values. - NameAndPublisher arpNameAndPublisher( - NormalizeAndPrepareName(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Name).get()), - NormalizeAndPreparePublisher(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Publisher).get())); - - // Get the best score across all localizations - double bestMatchingScore = 0; - for (const auto& manifestNameAndPublisher : m_namesAndPublishers) - { - // Sometimes the publisher may be included in the name, for example Microsoft PowerToys as opposed to simply PowerToys. - // This may happen both in the ARP entry and the manifest. We try adding it in case it is in one but not in both. - auto nameScore = EditDistanceScore(manifestNameAndPublisher.Name, arpNameAndPublisher.Name); - - // Ignore cases where the name is not at all similar to avoid matching due to publisher only - if (nameScore < m_nameMatchingScoreMinThreshold) - { - continue; - } - - auto publisherScore = EditDistanceScore(manifestNameAndPublisher.Publisher, arpNameAndPublisher.Publisher); - auto namePublisherScore = std::max( - EditDistanceScore(manifestNameAndPublisher.NamePublisher, arpNameAndPublisher.Name), - EditDistanceScore(manifestNameAndPublisher.Name, arpNameAndPublisher.NamePublisher)); - - // Use the best between considering name and publisher as a single string or separately. - auto score = std::max( - nameScore * m_nameMatchingScoreWeight + publisherScore * (1 - m_nameMatchingScoreWeight), - namePublisherScore); - bestMatchingScore = std::max(bestMatchingScore, score); - } - - // Factor in whether this entry is new - auto result = bestMatchingScore * m_stringMatchingWeight + (arpEntry.IsNewOrUpdated ? 1 : 0) * (1 - m_stringMatchingWeight); - - return result; - } - - WordSequence WordsEditDistanceMatchConfidenceAlgorithm::PrepareString(std::string_view s) const - { - return Utility::SplitIntoWords(Utility::FoldCase(s)); - } - - WordSequence WordsEditDistanceMatchConfidenceAlgorithm::NormalizeAndPrepareName(std::string_view name) const - { - return PrepareString(m_normalizer.NormalizeName(name).Name()); - } - - WordSequence WordsEditDistanceMatchConfidenceAlgorithm::NormalizeAndPreparePublisher(std::string_view publisher) const - { - return PrepareString(m_normalizer.NormalizePublisher(publisher)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ARPCorrelationAlgorithms.h" + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility; + +namespace AppInstaller::Repository::Correlation +{ + using WordSequence = WordsEditDistanceMatchConfidenceAlgorithm::WordSequence; + + namespace + { + // A simple matrix class to hold score tables without having to allocate multiple arrays. + struct Matrix + { + Matrix(size_t rows, size_t columns) : m_rows(rows), m_columns(columns), m_data(rows* columns) {} + + double& At(size_t i, size_t j) + { + return m_data[i * m_columns + j]; + } + + private: + size_t m_rows; + size_t m_columns; + std::vector m_data; + }; + + double EditDistanceScore(const std::vector& s1, const std::vector& s2) + { + // Naive implementation of edit distance (scaled over the sequence size). + // This considers only the operations of adding and removing elements. + + if (s1.empty() || s2.empty()) + { + return 0; + } + + // distance[i, j] = distance between s1[0:i] and s2[0:j] + // We don't need to hold more than two rows at a time, but it's simpler to keep the whole table. + Matrix distance(s1.size() + 1, s2.size() + 1); + + for (size_t i = 0; i < s1.size(); ++i) + { + for (size_t j = 0; j < s2.size(); ++j) + { + double& d = distance.At(i, j); + if (s1[i] == s2[j]) + { + // If the two elements are equal, the distance is the same as from one element before. + // In case we are on the first element of one of the two sequences, the distance is + // equal to the cost of adding all the previous elements in the other + if (i == 0) + { + d = static_cast(j); + } + else if (j == 0) + { + d = static_cast(i); + } + else + { + d = distance.At(i - 1, j - 1); + } + } + else + { + // If the two elements are distinct, the score is the cost of removing the last element + // in one sequence plus the cost of editing the remainder of both. + if (i > 0 && j > 0) + { + d = 1 + std::min(distance.At(i - 1, j), distance.At(i, j - 1)); + } + else if (i > 0) + { + d = 1 + distance.At(i - 1, j); + } + else if (j > 0) + { + d = 1 + distance.At(i, j - 1); + } + else + { + // Remove one and add the other + d = 2; + } + } + } + } + + // Maximum distance is equal to the sum of both lengths (removing all elements from one and adding all the elements from the other). + // We use that to scale to [0,1]. + // A smaller distance represents a higher match, so we subtract from 1 for the final score + double editDistance = distance.At(s1.size() - 1, s2.size() - 1); + return 1 - editDistance / (static_cast(s1.size()) + static_cast(s2.size())); + } + } + + WordsEditDistanceMatchConfidenceAlgorithm::NameAndPublisher::NameAndPublisher(const WordSequence& name, const WordSequence& publisher) : Name(name), Publisher(publisher) + { + NamePublisher.insert(NamePublisher.end(), publisher.begin(), publisher.end()); + NamePublisher.insert(NamePublisher.end(), name.begin(), name.end()); + } + + WordsEditDistanceMatchConfidenceAlgorithm::NameAndPublisher::NameAndPublisher(WordSequence&& name, WordSequence&& publisher) : Name(std::move(name)), Publisher(std::move(publisher)) + { + NamePublisher.insert(NamePublisher.end(), publisher.begin(), publisher.end()); + NamePublisher.insert(NamePublisher.end(), name.begin(), name.end()); + + } + + void WordsEditDistanceMatchConfidenceAlgorithm::Init(const AppInstaller::Manifest::Manifest& manifest) + { + // We will use the name and publisher from each localization. + m_namesAndPublishers.clear(); + + WordSequence defaultPublisher; + if (manifest.DefaultLocalization.Contains(Manifest::Localization::Publisher)) + { + defaultPublisher = NormalizeAndPreparePublisher(manifest.DefaultLocalization.Get()); + } + + if (manifest.DefaultLocalization.Contains(Manifest::Localization::PackageName)) + { + WordSequence defaultName = NormalizeAndPrepareName(manifest.DefaultLocalization.Get()); + m_namesAndPublishers.emplace_back(defaultName, defaultPublisher); + + for (const auto& loc : manifest.Localizations) + { + if (loc.Contains(Manifest::Localization::PackageName) || loc.Contains(Manifest::Localization::Publisher)) + { + auto name = loc.Contains(Manifest::Localization::PackageName) ? NormalizeAndPrepareName(loc.Get()) : defaultName; + auto publisher = loc.Contains(Manifest::Localization::Publisher) ? NormalizeAndPreparePublisher(loc.Get()) : defaultPublisher; + + m_namesAndPublishers.emplace_back(std::move(name), std::move(publisher)); + } + } + } + } + + double WordsEditDistanceMatchConfidenceAlgorithm::ComputeConfidence(const ARPEntry& arpEntry) const + { + // Name and Publisher are available as multi properties, but for ARP entries there will only be 0 or 1 values. + NameAndPublisher arpNameAndPublisher( + NormalizeAndPrepareName(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Name).get()), + NormalizeAndPreparePublisher(arpEntry.Entry->GetLatestVersion()->GetProperty(PackageVersionProperty::Publisher).get())); + + // Get the best score across all localizations + double bestMatchingScore = 0; + for (const auto& manifestNameAndPublisher : m_namesAndPublishers) + { + // Sometimes the publisher may be included in the name, for example Microsoft PowerToys as opposed to simply PowerToys. + // This may happen both in the ARP entry and the manifest. We try adding it in case it is in one but not in both. + auto nameScore = EditDistanceScore(manifestNameAndPublisher.Name, arpNameAndPublisher.Name); + + // Ignore cases where the name is not at all similar to avoid matching due to publisher only + if (nameScore < m_nameMatchingScoreMinThreshold) + { + continue; + } + + auto publisherScore = EditDistanceScore(manifestNameAndPublisher.Publisher, arpNameAndPublisher.Publisher); + auto namePublisherScore = std::max( + EditDistanceScore(manifestNameAndPublisher.NamePublisher, arpNameAndPublisher.Name), + EditDistanceScore(manifestNameAndPublisher.Name, arpNameAndPublisher.NamePublisher)); + + // Use the best between considering name and publisher as a single string or separately. + auto score = std::max( + nameScore * m_nameMatchingScoreWeight + publisherScore * (1 - m_nameMatchingScoreWeight), + namePublisherScore); + bestMatchingScore = std::max(bestMatchingScore, score); + } + + // Factor in whether this entry is new + auto result = bestMatchingScore * m_stringMatchingWeight + (arpEntry.IsNewOrUpdated ? 1 : 0) * (1 - m_stringMatchingWeight); + + return result; + } + + WordSequence WordsEditDistanceMatchConfidenceAlgorithm::PrepareString(std::string_view s) const + { + return Utility::SplitIntoWords(Utility::FoldCase(s)); + } + + WordSequence WordsEditDistanceMatchConfidenceAlgorithm::NormalizeAndPrepareName(std::string_view name) const + { + return PrepareString(m_normalizer.NormalizeName(name).Name()); + } + + WordSequence WordsEditDistanceMatchConfidenceAlgorithm::NormalizeAndPreparePublisher(std::string_view publisher) const + { + return PrepareString(m_normalizer.NormalizePublisher(publisher)); + } +} diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj index a73d7d4ec2..34cc155a46 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj @@ -1,527 +1,527 @@ - - - - - true - true - true - 15.0 - {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} - Win32Proj - AppInstallerRepositoryCore - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - StaticLibrary - - - true - true - - - false - true - false - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - - - false - Windows - Windows - - - - - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - false - - - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + true + true + true + 15.0 + {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + Win32Proj + AppInstallerRepositoryCore + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + StaticLibrary + + + true + true + + + false + true + false + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + + + false + Windows + Windows + + + + + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + false + + + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters index 289d392c25..64f1b186c2 100644 --- a/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters +++ b/src/AppInstallerRepositoryCore/AppInstallerRepositoryCore.vcxproj.filters @@ -1,823 +1,823 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {2d97761a-4967-4ece-b5fd-d96f346731ed} - - - {7996cf09-4e36-4009-b48d-53ffc3bfa956} - - - {cff561cd-211b-4cab-87f5-39359eef1e8d} - - - {69ce2e35-fe7f-41af-bd47-91a70131d167} - - - {aef8989c-44fd-4848-ae2c-c81d3f2e2c72} - - - {dc8b6163-e9b5-4d05-87bc-d6098afe0f92} - - - {7f16f6e0-12c6-4bb9-885e-3f8b666d5f74} - - - {15a1ee83-4118-40fe-ba43-c54d7185d505} - - - {8a93b62f-d065-4048-b43a-a2b14b70c330} - - - {6bcbaf7a-289f-4d0b-b128-67bef903745c} - - - {15639b2c-ce61-4a18-995a-a73cf1a5817e} - - - {9d8095ed-07de-4bc9-bfe7-630b781586d0} - - - {2cc20cdb-dcb2-4e0e-b04f-e2d838146100} - - - {aa7315bc-4eb0-4280-9572-f5a25f6e73ad} - - - {dcae9c55-cdd7-4381-8acd-3554896608a5} - - - {e31c8e5b-ed2c-43c8-b91b-db8ec4c52f71} - - - {84a55def-9fb8-4c90-8d5a-2cedc171940b} - - - {edef5ff7-9bfe-48f8-a179-e343d1a8b57f} - - - {4f5950e2-1713-4c1e-84ce-f868cfcb3a68} - - - {d9d70cf5-ce04-4db2-a0ec-970dd0ad22b6} - - - {f131c993-1136-4f4c-85a9-8606c6d297a8} - - - {78cf34a8-b868-4393-ad9c-630940569186} - - - {f05e19bb-2161-4ab0-9d04-2dfa2d3eb3c6} - - - {21da32f5-b918-436e-96a9-465525f90259} - - - {b2e78f3d-931e-432c-8485-255b1dbc9db7} - - - {f610927a-6f1d-42c5-9ad9-b59790091944} - - - {a3f9c7ed-f487-40d6-9ee7-e9a052e55c29} - - - {f42acfce-4fc0-435b-a9a2-bf5528aecbcc} - - - {7fd6c265-81c0-4c6e-87b2-24bef117e21d} - - - {34442899-29e5-4183-96ba-a1e8740146be} - - - {8edd7018-8836-4b15-84c1-998391e19038} - - - {7464e3ff-7a60-4bb6-8806-70562382043b} - - - {da801426-6d3b-40ca-b204-152e7e18067b} - - - {1a1efb9f-7332-4094-bb98-a4c51ea68b24} - - - {c29ae113-5eb6-41af-b64d-fac925dcf2c2} - - - {2075b51e-aa11-473a-bae0-e0d4366f926b} - - - {a3b7c8d1-e2f4-5a6b-9c0d-1e2f3a4b5c6d} - - - {b4c8d9e2-f3a5-6b7c-0d1e-2f3a4b5c6d7e} - - - - - Header Files - - - Microsoft - - - Microsoft\Schema - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Header Files - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_1 - - - Microsoft - - - Header Files - - - Microsoft\Schema\1_1 - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Rest\Schema\1_0 - - - Rest\Schema - - - Rest - - - Rest - - - Rest - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Rest\Schema - - - Microsoft\Schema\1_3 - - - Microsoft\Schema\1_3 - - - Header Files - - - Header Files - - - Microsoft - - - Rest\Schema - - - Rest\Schema\1_1 - - - Rest\Schema\1_1\Json - - - Rest\Schema\1_1\Json - - - Microsoft - - - Public\winget - - - Public\winget - - - Public\winget - - - Header Files - - - Microsoft\Schema\1_4 - - - Microsoft\Schema\1_4 - - - Header Files - - - Header Files - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Microsoft\Schema\1_5 - - - Microsoft\Schema\1_5 - - - Header Files - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\Portable_1_0 - - - Microsoft\Schema\Portable_1_0 - - - Microsoft\Schema - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_4 - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_5\Json - - - Rest\Schema\1_5 - - - Rest\Schema - - - Rest\Schema - - - Microsoft\Schema - - - Microsoft - - - Microsoft\Schema\Pinning_1_0 - - - Microsoft\Schema\Pinning_1_0 - - - Public\winget - - - Header Files - - - Public\winget - - - Rest\Schema\1_6\Json - - - Rest\Schema\1_6 - - - Microsoft\Schema\1_7 - - - Microsoft\Schema - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Checkpoint_1_0 - - - Public\winget - - - Header Files - - - Rest\Schema\1_7 - - - Rest\Schema - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Rest\Schema\1_7\Json - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema - - - Microsoft - - - Microsoft - - - Rest\Schema\1_9\Json - - - Rest\Schema\1_9 - - - Rest\Schema\1_10 - - - Rest\Schema\1_10\Json - - - Rest\Schema\1_12 - - - Rest\Schema\1_12\Json - - - Rest\Schema\1_28 - - - Rest\Schema\1_28\Json - - - Rest - - - Header Files - - - - - Source Files - - - Microsoft - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_0 - - - Source Files - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_1 - - - Microsoft\Schema\1_0 - - - Microsoft\Schema\1_1 - - - Microsoft - - - Source Files - - - Microsoft\Schema\1_1 - - - Microsoft - - - Microsoft - - - Microsoft\Schema\1_2 - - - Microsoft\Schema\1_2 - - - Rest\Schema\1_0 - - - Rest - - - Rest - - - Rest - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Rest\Schema\1_0\Json - - - Microsoft\Schema\1_3 - - - Source Files - - - Source Files - - - Rest\Schema - - - Microsoft - - - Rest\Schema\1_1 - - - Rest\Schema\1_1\Json - - - Rest\Schema\1_1\Json - - - Microsoft - - - Source Files - - - Source Files - - - Microsoft\Schema\1_4 - - - Microsoft\Schema\1_4 - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Microsoft\Schema\1_5 - - - Source Files - - - Microsoft\Schema\1_6 - - - Microsoft\Schema\1_6 - - - Microsoft - - - Microsoft\Schema\Portable_1_0 - - - Microsoft\Schema\Portable_1_0 - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_4 - - - Rest\Schema\1_4\Json - - - Rest\Schema\1_5\Json - - - Rest\Schema\1_5 - - - Rest\Schema - - - Rest\Schema - - - Source Files - - - Source Files - - - Microsoft\Schema\Pinning_1_0 - - - Source Files - - - Source Files - - - Rest\Schema\1_6\Json - - - Rest\Schema\1_6 - - - Microsoft\Schema\1_7 - - - Source Files - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Checkpoint_1_0 - - - Source Files - - - Microsoft\Schema\Checkpoint_1_0 - - - Microsoft\Schema\Pinning_1_0 - - - Rest\Schema\1_7 - - - Rest\Schema - - - Source Files - - - Source Files - - - Rest\Schema\1_7\Json - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft\Schema - - - Microsoft\Schema\2_0 - - - Microsoft\Schema\2_0 - - - Microsoft - - - Microsoft - - - Rest\Schema\1_9\Json - - - Rest\Schema\1_9 - - - Rest\Schema\1_10 - - - Rest\Schema\1_10\Json - - - Rest\Schema\1_12 - - - Rest\Schema\1_12\Json - - - Rest\Schema\1_28 - - - Rest\Schema\1_28\Json - - - Rest - - - Source Files - - - - - - - Microsoft - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {2d97761a-4967-4ece-b5fd-d96f346731ed} + + + {7996cf09-4e36-4009-b48d-53ffc3bfa956} + + + {cff561cd-211b-4cab-87f5-39359eef1e8d} + + + {69ce2e35-fe7f-41af-bd47-91a70131d167} + + + {aef8989c-44fd-4848-ae2c-c81d3f2e2c72} + + + {dc8b6163-e9b5-4d05-87bc-d6098afe0f92} + + + {7f16f6e0-12c6-4bb9-885e-3f8b666d5f74} + + + {15a1ee83-4118-40fe-ba43-c54d7185d505} + + + {8a93b62f-d065-4048-b43a-a2b14b70c330} + + + {6bcbaf7a-289f-4d0b-b128-67bef903745c} + + + {15639b2c-ce61-4a18-995a-a73cf1a5817e} + + + {9d8095ed-07de-4bc9-bfe7-630b781586d0} + + + {2cc20cdb-dcb2-4e0e-b04f-e2d838146100} + + + {aa7315bc-4eb0-4280-9572-f5a25f6e73ad} + + + {dcae9c55-cdd7-4381-8acd-3554896608a5} + + + {e31c8e5b-ed2c-43c8-b91b-db8ec4c52f71} + + + {84a55def-9fb8-4c90-8d5a-2cedc171940b} + + + {edef5ff7-9bfe-48f8-a179-e343d1a8b57f} + + + {4f5950e2-1713-4c1e-84ce-f868cfcb3a68} + + + {d9d70cf5-ce04-4db2-a0ec-970dd0ad22b6} + + + {f131c993-1136-4f4c-85a9-8606c6d297a8} + + + {78cf34a8-b868-4393-ad9c-630940569186} + + + {f05e19bb-2161-4ab0-9d04-2dfa2d3eb3c6} + + + {21da32f5-b918-436e-96a9-465525f90259} + + + {b2e78f3d-931e-432c-8485-255b1dbc9db7} + + + {f610927a-6f1d-42c5-9ad9-b59790091944} + + + {a3f9c7ed-f487-40d6-9ee7-e9a052e55c29} + + + {f42acfce-4fc0-435b-a9a2-bf5528aecbcc} + + + {7fd6c265-81c0-4c6e-87b2-24bef117e21d} + + + {34442899-29e5-4183-96ba-a1e8740146be} + + + {8edd7018-8836-4b15-84c1-998391e19038} + + + {7464e3ff-7a60-4bb6-8806-70562382043b} + + + {da801426-6d3b-40ca-b204-152e7e18067b} + + + {1a1efb9f-7332-4094-bb98-a4c51ea68b24} + + + {c29ae113-5eb6-41af-b64d-fac925dcf2c2} + + + {2075b51e-aa11-473a-bae0-e0d4366f926b} + + + {a3b7c8d1-e2f4-5a6b-9c0d-1e2f3a4b5c6d} + + + {b4c8d9e2-f3a5-6b7c-0d1e-2f3a4b5c6d7e} + + + + + Header Files + + + Microsoft + + + Microsoft\Schema + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Header Files + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_1 + + + Microsoft + + + Header Files + + + Microsoft\Schema\1_1 + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Rest\Schema\1_0 + + + Rest\Schema + + + Rest + + + Rest + + + Rest + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Rest\Schema + + + Microsoft\Schema\1_3 + + + Microsoft\Schema\1_3 + + + Header Files + + + Header Files + + + Microsoft + + + Rest\Schema + + + Rest\Schema\1_1 + + + Rest\Schema\1_1\Json + + + Rest\Schema\1_1\Json + + + Microsoft + + + Public\winget + + + Public\winget + + + Public\winget + + + Header Files + + + Microsoft\Schema\1_4 + + + Microsoft\Schema\1_4 + + + Header Files + + + Header Files + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Microsoft\Schema\1_5 + + + Microsoft\Schema\1_5 + + + Header Files + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_4 + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_5\Json + + + Rest\Schema\1_5 + + + Rest\Schema + + + Rest\Schema + + + Microsoft\Schema + + + Microsoft + + + Microsoft\Schema\Pinning_1_0 + + + Microsoft\Schema\Pinning_1_0 + + + Public\winget + + + Header Files + + + Public\winget + + + Rest\Schema\1_6\Json + + + Rest\Schema\1_6 + + + Microsoft\Schema\1_7 + + + Microsoft\Schema + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Checkpoint_1_0 + + + Public\winget + + + Header Files + + + Rest\Schema\1_7 + + + Rest\Schema + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Rest\Schema\1_7\Json + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema + + + Microsoft + + + Microsoft + + + Rest\Schema\1_9\Json + + + Rest\Schema\1_9 + + + Rest\Schema\1_10 + + + Rest\Schema\1_10\Json + + + Rest\Schema\1_12 + + + Rest\Schema\1_12\Json + + + Rest\Schema\1_28 + + + Rest\Schema\1_28\Json + + + Rest + + + Header Files + + + + + Source Files + + + Microsoft + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_0 + + + Source Files + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_1 + + + Microsoft\Schema\1_0 + + + Microsoft\Schema\1_1 + + + Microsoft + + + Source Files + + + Microsoft\Schema\1_1 + + + Microsoft + + + Microsoft + + + Microsoft\Schema\1_2 + + + Microsoft\Schema\1_2 + + + Rest\Schema\1_0 + + + Rest + + + Rest + + + Rest + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Rest\Schema\1_0\Json + + + Microsoft\Schema\1_3 + + + Source Files + + + Source Files + + + Rest\Schema + + + Microsoft + + + Rest\Schema\1_1 + + + Rest\Schema\1_1\Json + + + Rest\Schema\1_1\Json + + + Microsoft + + + Source Files + + + Source Files + + + Microsoft\Schema\1_4 + + + Microsoft\Schema\1_4 + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Microsoft\Schema\1_5 + + + Source Files + + + Microsoft\Schema\1_6 + + + Microsoft\Schema\1_6 + + + Microsoft + + + Microsoft\Schema\Portable_1_0 + + + Microsoft\Schema\Portable_1_0 + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_4 + + + Rest\Schema\1_4\Json + + + Rest\Schema\1_5\Json + + + Rest\Schema\1_5 + + + Rest\Schema + + + Rest\Schema + + + Source Files + + + Source Files + + + Microsoft\Schema\Pinning_1_0 + + + Source Files + + + Source Files + + + Rest\Schema\1_6\Json + + + Rest\Schema\1_6 + + + Microsoft\Schema\1_7 + + + Source Files + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Checkpoint_1_0 + + + Source Files + + + Microsoft\Schema\Checkpoint_1_0 + + + Microsoft\Schema\Pinning_1_0 + + + Rest\Schema\1_7 + + + Rest\Schema + + + Source Files + + + Source Files + + + Rest\Schema\1_7\Json + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft\Schema + + + Microsoft\Schema\2_0 + + + Microsoft\Schema\2_0 + + + Microsoft + + + Microsoft + + + Rest\Schema\1_9\Json + + + Rest\Schema\1_9 + + + Rest\Schema\1_10 + + + Rest\Schema\1_10\Json + + + Rest\Schema\1_12 + + + Rest\Schema\1_12\Json + + + Rest\Schema\1_28 + + + Rest\Schema\1_28\Json + + + Rest + + + Source Files + + + + + + + Microsoft + + \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/ArpVersionValidation.cpp b/src/AppInstallerRepositoryCore/ArpVersionValidation.cpp index 5668d3d5f9..8687bd6c45 100644 --- a/src/AppInstallerRepositoryCore/ArpVersionValidation.cpp +++ b/src/AppInstallerRepositoryCore/ArpVersionValidation.cpp @@ -1,89 +1,89 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "pch.h" -#include "ArpVersionValidation.h" -#include - -namespace AppInstaller::Repository -{ - namespace - { - std::vector GetArpVersionRangesByPackageRowId(const Microsoft::SQLiteIndex* index, Microsoft::SQLiteIndex::IdType packageRowId, const Utility::VersionAndChannel& excludeVersionAndChannel = {}) - { - std::vector result; - - auto versionKeys = index->GetVersionKeysById(packageRowId); - for (auto const& versionKey : versionKeys) - { - // For manifest update, the manifest to be updated does not need to be checked. - // In unlikely cases if both version 1.0.0 and 1.0 of the same package exist, we compare raw values here as what sqlite index does. - if (versionKey.VersionAndChannel.GetVersion().ToString() == excludeVersionAndChannel.GetVersion().ToString() && - versionKey.VersionAndChannel.GetChannel().ToString() == excludeVersionAndChannel.GetChannel().ToString()) - { - continue; - } - - auto arpMinVersion = index->GetPropertyByPrimaryId(versionKey.ManifestId, PackageVersionProperty::ArpMinVersion).value_or(""); - auto arpMaxVersion = index->GetPropertyByPrimaryId(versionKey.ManifestId, PackageVersionProperty::ArpMaxVersion).value_or(""); - - // Either both empty or both not empty - THROW_HR_IF(E_UNEXPECTED, arpMinVersion.empty() != arpMaxVersion.empty()); - - if (!arpMinVersion.empty() && !arpMaxVersion.empty()) - { - result.emplace_back(Utility::VersionRange{ Utility::Version{ std::move(arpMinVersion) }, Utility::Version{ std::move(arpMaxVersion) } }); - } - } - - return result; - } - } - - void ValidateManifestArpVersion(const Microsoft::SQLiteIndex* index, const Manifest::Manifest& manifest) - { - try - { - auto manifestArpVersionRange = manifest.GetArpVersionRange(); - if (manifestArpVersionRange.IsEmpty()) - { - return; - } - - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id); - auto searchResult = index->Search(request); - if (searchResult.Matches.empty()) - { - return; - } - - auto arpVersionRangesInIndex = GetArpVersionRangesByPackageRowId(index, searchResult.Matches[0].first, { Utility::Version{ manifest.Version }, Utility::Channel{ manifest.Channel } }); - for (auto const& arpInIndex : arpVersionRangesInIndex) - { - if (manifestArpVersionRange.Overlaps(arpInIndex)) - { - std::string context = (" [" + arpInIndex.GetMinVersion().ToString() + ", " + arpInIndex.GetMaxVersion().ToString() + "]"); - auto validationError = Manifest::ValidationError(Manifest::ManifestError::ArpVersionOverlapWithIndex, context); - - AICLI_LOG(Repo, Error, << validationError.GetErrorMessage() << context); - THROW_EXCEPTION(Manifest::ManifestException( - { validationError }, - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); - } - } - } - catch (const Manifest::ManifestException&) - { - // Prevent ManifestException from being wrapped in another ManifestException - throw; - } - catch (...) - { - AICLI_LOG(Repo, Error, << "ValidateManifestArpVersion() encountered internal error."); - THROW_EXCEPTION(Manifest::ManifestException( - { Manifest::ValidationError(Manifest::ManifestError::ArpVersionValidationInternalError) }, - APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "pch.h" +#include "ArpVersionValidation.h" +#include + +namespace AppInstaller::Repository +{ + namespace + { + std::vector GetArpVersionRangesByPackageRowId(const Microsoft::SQLiteIndex* index, Microsoft::SQLiteIndex::IdType packageRowId, const Utility::VersionAndChannel& excludeVersionAndChannel = {}) + { + std::vector result; + + auto versionKeys = index->GetVersionKeysById(packageRowId); + for (auto const& versionKey : versionKeys) + { + // For manifest update, the manifest to be updated does not need to be checked. + // In unlikely cases if both version 1.0.0 and 1.0 of the same package exist, we compare raw values here as what sqlite index does. + if (versionKey.VersionAndChannel.GetVersion().ToString() == excludeVersionAndChannel.GetVersion().ToString() && + versionKey.VersionAndChannel.GetChannel().ToString() == excludeVersionAndChannel.GetChannel().ToString()) + { + continue; + } + + auto arpMinVersion = index->GetPropertyByPrimaryId(versionKey.ManifestId, PackageVersionProperty::ArpMinVersion).value_or(""); + auto arpMaxVersion = index->GetPropertyByPrimaryId(versionKey.ManifestId, PackageVersionProperty::ArpMaxVersion).value_or(""); + + // Either both empty or both not empty + THROW_HR_IF(E_UNEXPECTED, arpMinVersion.empty() != arpMaxVersion.empty()); + + if (!arpMinVersion.empty() && !arpMaxVersion.empty()) + { + result.emplace_back(Utility::VersionRange{ Utility::Version{ std::move(arpMinVersion) }, Utility::Version{ std::move(arpMaxVersion) } }); + } + } + + return result; + } + } + + void ValidateManifestArpVersion(const Microsoft::SQLiteIndex* index, const Manifest::Manifest& manifest) + { + try + { + auto manifestArpVersionRange = manifest.GetArpVersionRange(); + if (manifestArpVersionRange.IsEmpty()) + { + return; + } + + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, manifest.Id); + auto searchResult = index->Search(request); + if (searchResult.Matches.empty()) + { + return; + } + + auto arpVersionRangesInIndex = GetArpVersionRangesByPackageRowId(index, searchResult.Matches[0].first, { Utility::Version{ manifest.Version }, Utility::Channel{ manifest.Channel } }); + for (auto const& arpInIndex : arpVersionRangesInIndex) + { + if (manifestArpVersionRange.Overlaps(arpInIndex)) + { + std::string context = (" [" + arpInIndex.GetMinVersion().ToString() + ", " + arpInIndex.GetMaxVersion().ToString() + "]"); + auto validationError = Manifest::ValidationError(Manifest::ManifestError::ArpVersionOverlapWithIndex, context); + + AICLI_LOG(Repo, Error, << validationError.GetErrorMessage() << context); + THROW_EXCEPTION(Manifest::ManifestException( + { validationError }, + APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); + } + } + } + catch (const Manifest::ManifestException&) + { + // Prevent ManifestException from being wrapped in another ManifestException + throw; + } + catch (...) + { + AICLI_LOG(Repo, Error, << "ValidateManifestArpVersion() encountered internal error."); + THROW_EXCEPTION(Manifest::ManifestException( + { Manifest::ValidationError(Manifest::ManifestError::ArpVersionValidationInternalError) }, + APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED)); + } + } +} diff --git a/src/AppInstallerRepositoryCore/CompositeSource.cpp b/src/AppInstallerRepositoryCore/CompositeSource.cpp index 9a0b5bfed7..9b47c95107 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.cpp +++ b/src/AppInstallerRepositoryCore/CompositeSource.cpp @@ -1,1780 +1,1780 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "CompositeSource.h" -#include - -using namespace AppInstaller::Settings; - -namespace AppInstaller::Repository -{ - using namespace std::string_view_literals; - - namespace anon - { - Utility::VersionAndChannel GetVACFromVersion(IPackageVersion* packageVersion) - { - return { - Utility::Version(packageVersion->GetProperty(PackageVersionProperty::Version)), - Utility::Channel(packageVersion->GetProperty(PackageVersionProperty::Channel)) - }; - } - - // Returns true for fields that provide a strong match; one that is not based on a heuristic. - bool IsStrongMatchField(PackageMatchField field) - { - switch (field) - { - case AppInstaller::Repository::PackageMatchField::PackageFamilyName: - case AppInstaller::Repository::PackageMatchField::ProductCode: - case AppInstaller::Repository::PackageMatchField::UpgradeCode: - return true; - } - - return false; - } - - // Gets the only available package from the composite, ensuring this fact in test contexts. - std::shared_ptr OnlyAvailable(const std::shared_ptr& composite) - { - std::vector> availablePackages = composite->GetAvailable(); - -#ifndef AICLI_DISABLE_TEST_HOOKS - THROW_HR_IF(E_UNEXPECTED, availablePackages.size() != 1); -#endif - - return std::move(availablePackages.front()); - } - - // Move returns if there is only one package in the matches that is strong; otherwise returns an empty value. - std::shared_ptr FindOnlyStrongMatchFieldResult(std::vector& matches) - { - std::shared_ptr result; - - for (auto&& match : matches) - { - AICLI_LOG(Repo, Info, << " Checking match with package id: " << match.Package->GetProperty(PackageProperty::Id)); - - if (IsStrongMatchField(match.MatchCriteria.Field)) - { - if (!result) - { - result = std::move(match.Package); - } - else - { - AICLI_LOG(Repo, Info, << " Found multiple packages with strong match fields"); - result.reset(); - break; - } - } - } - - return result; - } - - // Gets a single matching package from the results - template - std::shared_ptr GetMatchingPackage(std::vector& matches, MultipleIntro&& multipleIntro, Indeterminate&& indeterminate) - { - if (matches.empty()) - { - return {}; - } - else if (matches.size() == 1) - { - return std::move(matches[0].Package); - } - else - { - multipleIntro(); - - auto result = FindOnlyStrongMatchFieldResult(matches); - - if (!result) - { - indeterminate(); - } - - return result; - } - } - - // For a given package from a tracking catalog, get the latest write time. - // Look at all versions rather than just the latest to account for the potential of downgrading. - std::chrono::system_clock::time_point GetLatestTrackingWriteTime( - const std::shared_ptr& trackingPackage) - { - std::chrono::system_clock::time_point result{}; - - for (const auto& key : trackingPackage->GetVersionKeys()) - { - auto version = trackingPackage->GetVersion(key); - if (version) - { - auto metadata = version->GetMetadata(); - auto itr = metadata.find(PackageVersionMetadata::TrackingWriteTime); - if (itr != metadata.end()) - { - std::int64_t unixEpoch = 0; - try - { - unixEpoch = std::stoll(itr->second); - } - CATCH_LOG(); - - std::chrono::system_clock::time_point versionTime = Utility::ConvertUnixEpochToSystemClock(unixEpoch); - - if (versionTime > result) - { - result = versionTime; - } - } - } - } - - return result; - } - - // An installed package's version reported in ARP does not necessarily match the versions used for the manifest. - // This function uses the data in the manifest to map the installed version string to the version used by the manifest. - // - // TODO: Note: Currently this function assumes the all versions in the available package is from one source. - // Even though a composite package can have available packages from multiple sources, we only call this function - // for the default (first) available package. If we ever need to consider other sources, this function needs to be revisited. - std::string GetMappedInstalledVersion(const std::string& installedVersion, const std::shared_ptr& availablePackage) - { - // Perform an initial check to see if the latest version has a mapping; if it does not, do not attempt any more. - auto latestVersion = availablePackage->GetLatestVersion(); - if (latestVersion) - { - auto version = latestVersion->GetProperty(PackageVersionProperty::Version); - auto arpMinVersion = latestVersion->GetProperty(PackageVersionProperty::ArpMinVersion); - auto arpMaxVersion = latestVersion->GetProperty(PackageVersionProperty::ArpMaxVersion); - - if ((arpMinVersion.empty() || arpMinVersion == version) && (arpMaxVersion.empty() || arpMaxVersion == version)) - { - return installedVersion; - } - } - - // Stores raw versions value strings to run a preliminary check whether version mapping is needed. - std::vector> rawVersionValues; - auto versionKeys = availablePackage->GetVersionKeys(); - bool shouldTryPerformMapping = false; - - for (auto const& versionKey : versionKeys) - { - auto availableVersion = availablePackage->GetVersion(versionKey); - std::string arpMinVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMinVersion); - std::string arpMaxVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMaxVersion); - - if (!arpMinVersion.empty() && !arpMaxVersion.empty()) - { - std::string manifestVersion = versionKey.Version; - - if (!shouldTryPerformMapping && (arpMinVersion != manifestVersion || arpMaxVersion != manifestVersion)) - { - shouldTryPerformMapping = true; - } - - rawVersionValues.emplace_back(std::make_tuple(std::move(manifestVersion), std::move(arpMinVersion), std::move(arpMaxVersion))); - } - } - - if (!shouldTryPerformMapping) - { - return installedVersion; - } - - // Construct a map between manifest version and arp version range. The map is ordered in descending by package version. - std::vector> arpVersionMap; - - for (auto& tuple : rawVersionValues) - { - auto&& [manifestVersion, arpMinVersion, arpMaxVersion] = std::move(tuple); - Utility::VersionRange arpVersionRange{ Utility::Version(std::move(arpMinVersion)), Utility::Version(std::move(arpMaxVersion)) }; - Utility::Version manifestVer{ std::move(manifestVersion) }; - // Skip mapping to unknown version - if (!manifestVer.IsUnknown()) - { - arpVersionMap.emplace_back(std::make_pair(std::move(manifestVer), std::move(arpVersionRange))); - } - } - - // Go through the arp version map and determine what mapping should be performed. - // shouldPerformMapping is true when at least 1 arp version range is different from the package version. - bool shouldPerformMapping = false; - bool isArpVersionRangeInDescendingOrder = true; - const Utility::VersionRange* previousVersionRange = nullptr; - - for (auto const& pair : arpVersionMap) - { - // If arp version range is not same as package version, should perform mapping - // This check is still needed to account for 1.0 == 1.0.0 cases - if (!shouldPerformMapping && !pair.second.IsSameAsSingleVersion(pair.first)) - { - shouldPerformMapping = true; - } - - if (!previousVersionRange) - { - // This is the first non empty arp version range - previousVersionRange = &pair.second; - } - else if (isArpVersionRangeInDescendingOrder) - { - // The arp version range should be less than previous range - if (pair.second < *previousVersionRange) - { - previousVersionRange = &pair.second; - } - else - { - isArpVersionRangeInDescendingOrder = false; - } - } - } - - // Now perform arp version mapping - if (shouldPerformMapping) - { - Utility::Version installed{ installedVersion }; - for (auto const& pair : arpVersionMap) - { - // If the installed version is in the arp version range - if (pair.second.ContainsVersion(installed)) - { - return pair.first.ToString(); - } - } - - // At this point, no mapping found. Perform approximate mapping if applicable. - // We'll start from end of the vector because we try to find closest less than version if possible. - if (isArpVersionRangeInDescendingOrder) - { - const Utility::Version* lastGreaterThanVersion = nullptr; - auto it = arpVersionMap.rbegin(); - while (it != arpVersionMap.rend()) - { - const auto& pair = *it; - if (installed < pair.second.GetMinVersion()) - { - return Utility::Version{ pair.first, Utility::Version::ApproximateComparator::LessThan }.ToString(); - } - else - { - lastGreaterThanVersion = &pair.first; - } - - it++; - } - - // No approximate less than version found, approximate greater than version will be returned. - if (lastGreaterThanVersion) - { - return Utility::Version{ *lastGreaterThanVersion, Utility::Version::ApproximateComparator::GreaterThan }.ToString(); - } - } - } - - // return the input installed version if no mapping is performed or found. - return installedVersion; - } - - // A composite package installed version that allows us to override the source or the version. - struct CompositeInstalledVersion : public IPackageVersion - { - CompositeInstalledVersion(std::shared_ptr baseInstalledVersion, Source trackingSource, std::shared_ptr trackingPackageVersion, std::string overrideVersion = {}) : - m_baseInstalledVersion(std::move(baseInstalledVersion)), m_trackingSource(std::move(trackingSource)), m_trackingPackageVersion(std::move(trackingPackageVersion)), m_overrideVersion(std::move(overrideVersion)) - {} - - Utility::LocIndString GetProperty(PackageVersionProperty property) const override - { - // If there is an override version, use it. - if (property == PackageVersionProperty::Version && !m_overrideVersion.empty()) - { - return Utility::LocIndString{ m_overrideVersion }; - } - - return m_baseInstalledVersion->GetProperty(property); - } - - std::vector GetMultiProperty(PackageVersionMultiProperty property) const override - { - return m_baseInstalledVersion->GetMultiProperty(property); - } - - Manifest::Manifest GetManifest() override - { - return m_baseInstalledVersion->GetManifest(); - } - - Source GetSource() const override - { - // If there is a tracking source, use it instead to indicate that it came from there. - if (m_trackingSource) - { - return m_trackingSource; - } - - return m_baseInstalledVersion->GetSource(); - } - - Metadata GetMetadata() const override - { - auto result = m_baseInstalledVersion->GetMetadata(); - - // Populate metadata from tracking package version if not present in base installed version. - if (m_trackingPackageVersion) - { - auto trackingMetadata = m_trackingPackageVersion->GetMetadata(); - for (auto metadataItem : { PackageVersionMetadata::InstalledArchitecture, PackageVersionMetadata::InstalledLocale, - PackageVersionMetadata::UserIntentArchitecture, PackageVersionMetadata::UserIntentLocale, PackageVersionMetadata::PinnedState, - PackageVersionMetadata::InitialOverrideArguments, PackageVersionMetadata::InitialCustomSwitches }) - { - auto itr = trackingMetadata.find(metadataItem); - auto existingItr = result.find(metadataItem); - if (itr != trackingMetadata.end() && existingItr == result.end()) - { - result[metadataItem] = itr->second; - } - } - } - - return result; - } - - private: - std::shared_ptr m_baseInstalledVersion; - Source m_trackingSource; - std::string m_overrideVersion; - std::shared_ptr m_trackingPackageVersion; - }; - - // An IPackage for the installed package of a CompositePackage. - struct CompositeInstalledPackage : public IPackage - { - static constexpr IPackageType PackageType = IPackageType::CompositeInstalledPackage; - - CompositeInstalledPackage(std::shared_ptr package) - { - AddPackageAndVersionKeyData(std::move(package)); - } - - Utility::LocIndString GetProperty(PackageProperty property) const override - { - THROW_HR_IF(E_UNEXPECTED, m_packages.empty() || m_versionKeyData.empty()); - - // Use the highest version for package properties - return m_packages[m_versionKeyData[0].PackageIndex]->GetProperty(property); - } - - std::vector GetMultiProperty(PackageMultiProperty property) const override - { - std::vector result; - - for (const auto& package : m_packages) - { - for (auto&& string : package->GetMultiProperty(property)) - { - auto itr = std::lower_bound(result.begin(), result.end(), string); - - if (itr == result.end() || *itr != string) - { - result.emplace(itr, std::move(string)); - } - } - } - - return result; - } - - std::vector GetVersionKeys() const override - { - return { m_versionKeyData.begin(), m_versionKeyData.end() }; - } - - std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override - { - std::shared_ptr installedVersion; - std::string overrideVersion; - - for (const VersionKeyData& key : m_versionKeyData) - { - if (key.IsMatch(versionKey)) - { - installedVersion = key.InstalledVersion; - overrideVersion = key.Version; - break; - } - } - - if (installedVersion) - { - // Get the appropriate tracking version or latest if it is not found. - // The tracking package uses the mapped version. - std::shared_ptr trackingPackageVersion; - if (m_trackingPackage) - { - // Remove our use of the package id as source - PackageVersionKey versionKey_NoSource = versionKey; - versionKey_NoSource.SourceId.clear(); - - trackingPackageVersion = m_trackingPackage->GetVersion(versionKey_NoSource); - - if (!trackingPackageVersion) - { - trackingPackageVersion = m_trackingPackage->GetLatestVersion(); - } - } - - return std::make_shared(std::move(installedVersion), m_trackingSource, std::move(trackingPackageVersion), std::move(overrideVersion)); - } - - return nullptr; - } - - std::shared_ptr GetLatestVersion() const override - { - return GetVersion({}); - } - - Source GetSource() const override - { - // If there is a tracking source, use it instead to indicate that it came from there. - // Otherwise, all of the installed packages should be from the same source. - return m_trackingSource ? m_trackingSource : m_packages[0]->GetSource(); - } - - bool IsSame(const IPackage* other) const override - { - const CompositeInstalledPackage* otherPackage = PackageCast(other); - - if (otherPackage) - { - if (m_packages.size() != otherPackage->m_packages.size()) - { - return false; - } - - for (const auto& subPackage : m_packages) - { - bool foundSame = false; - - for (const auto& otherSubPackage : otherPackage->m_packages) - { - if (subPackage->IsSame(otherSubPackage.get())) - { - foundSame = true; - break; - } - } - - if (!foundSame) - { - return false; - } - } - - return true; - } - - return false; - } - - const void* CastTo(IPackageType type) const override - { - if (type == PackageType) - { - return this; - } - - return nullptr; - } - - void SetTracking( - Source trackingSource, - std::shared_ptr trackingPackage, - std::chrono::system_clock::time_point trackingWriteTime) - { - m_trackingSource = std::move(trackingSource); - m_trackingPackage = std::move(trackingPackage); - m_trackingWriteTime = trackingWriteTime; - } - - Source GetTrackingSource() const - { - return m_trackingSource; - } - - const std::shared_ptr& GetTrackingPackage() const - { - return m_trackingPackage; - } - - std::chrono::system_clock::time_point GetTrackingPackageWriteTime() const - { - return m_trackingWriteTime; - } - - bool ContainsInstalledPackage(const IPackage* installedPackage) const - { - for (const auto& package : m_packages) - { - if (package->IsSame(installedPackage)) - { - return true; - } - } - - return false; - } - - void FoldInstalledIn(const std::shared_ptr& other) - { - for (const auto& package : other->m_packages) - { - AddPackageAndVersionKeyData(package); - } - } - - // Set a version that will override the version string from the installed package - void SetOverrideInstalledVersion(const std::shared_ptr& availablePackage) - { - if (availablePackage) - { - m_availablePackageVersionOverride = availablePackage; - - for (auto& key : m_versionKeyData) - { - if (Manifest::DoesInstallerTypeSupportArpVersionRange(key.InstalledType)) - { - key.Version = GetMappedInstalledVersion(key.InstalledVersion->GetProperty(PackageVersionProperty::Version), availablePackage); - key.VersionAndChannel = Utility::VersionAndChannel{ key.Version, key.Channel }; - } - } - } - } - - bool IsEmpty() const - { - return m_versionKeyData.empty(); - } - - private: - // Contains information about all of the version keys. - // We use the `SourceId` field to store the installed package identifier so that we can disambiguate keys is they have the same version. - struct VersionKeyData : public PackageVersionKey - { - size_t PackageIndex; - std::shared_ptr InstalledVersion; - Manifest::InstallerTypeEnum InstalledType; - Utility::VersionAndChannel VersionAndChannel; - - bool operator<(const VersionKeyData& other) const - { - return VersionAndChannel < other.VersionAndChannel; - } - }; - - // Adds the package and version key data to the composite. - // The version keys are then sorted so that the first (index 0) in the vector has the highest version. - // Note that it may tied for highest version if, for instance, the same version is installed for different architectures. - void AddPackageAndVersionKeyData(std::shared_ptr package) - { - // We don't want this to happen, but it could. Rather than a crash, we will log it and move on. - if (!package) - { - AICLI_LOG(Repo, Verbose, << "AddPackageAndVersionKeyData called with an empty package"); - return; - } - - size_t packageIndex = m_packages.size(); - std::string packageIdentifier = package->GetProperty(PackageProperty::Id); - bool versionAdded = false; - - for (const auto& versionKey : package->GetVersionKeys()) - { - VersionKeyData keyData{ versionKey }; - - keyData.PackageIndex = packageIndex; - keyData.InstalledVersion = package->GetVersion(versionKey); - - if (!keyData.InstalledVersion) - { - AICLI_LOG(Repo, Verbose, << "AddPackageAndVersionKeyData: Package [" << packageIdentifier << "] did not return a version for [" << versionKey.Version << "]"); - continue; - } - - // We use the `SourceId` field to store the installed package identifier so that we can disambiguate keys if they have the same version. - keyData.SourceId = packageIdentifier; - - keyData.InstalledType = Manifest::ConvertToInstallerTypeEnum(keyData.InstalledVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); - if (m_availablePackageVersionOverride && Manifest::DoesInstallerTypeSupportArpVersionRange(keyData.InstalledType)) - { - keyData.Version = GetMappedInstalledVersion(keyData.InstalledVersion->GetProperty(PackageVersionProperty::Version), m_availablePackageVersionOverride); - } - - keyData.VersionAndChannel = Utility::VersionAndChannel{ keyData.Version, keyData.Channel }; - - m_versionKeyData.emplace_back(std::move(keyData)); - versionAdded = true; - } - - if (versionAdded) - { - m_packages.emplace_back(std::move(package)); - - std::sort(m_versionKeyData.begin(), m_versionKeyData.end()); - } - } - - std::vector> m_packages; - std::vector m_versionKeyData; - Source m_trackingSource; - std::shared_ptr m_trackingPackage; - std::chrono::system_clock::time_point m_trackingWriteTime = std::chrono::system_clock::time_point::min(); - std::shared_ptr m_availablePackageVersionOverride; - }; - - // An ICompositePackage for the CompositeSource. - struct CompositePackage : public ICompositePackage - { - // The availablePackage may only contain one available package within it, as it is expected to be the output of a search on a single source. - CompositePackage(const std::shared_ptr& installedPackage, const std::shared_ptr& availablePackage = {}, bool setPrimary = false) - { - if (installedPackage) - { - m_installedPackage = std::make_shared(installedPackage->GetInstalled()); - - // If the installed package result existed, but didn't actually create any installed versions, drop it. - if (m_installedPackage->IsEmpty()) - { - m_installedPackage.reset(); - } - } - - AddAvailablePackage(availablePackage, setPrimary); - } - - Utility::LocIndString GetProperty(PackageProperty property) const override - { - IPackage* truth = nullptr; - if (m_primaryAvailablePackage) - { - truth = m_primaryAvailablePackage.get(); - } - if (!truth && !m_availablePackages.empty()) - { - truth = m_availablePackages[0].get(); - } - if (!truth) - { - truth = m_installedPackage.get(); - } - - THROW_HR_IF(E_UNEXPECTED, !truth); - - return truth->GetProperty(property); - } - - std::shared_ptr GetInstalled() override - { - return m_installedPackage; - } - - std::vector> GetAvailable() override - { - return m_availablePackages; - } - - const std::vector>& GetAvailablePackages() - { - return m_availablePackages; - } - - bool IsSameAsAnyAvailable(const IPackage* other) const - { - if (other) - { - for (const auto& availablePackage : m_availablePackages) - { - if (other->IsSame(availablePackage.get())) - { - return true; - } - } - } - - return false; - } - - const std::shared_ptr& GetInstalledPackage() const - { - return m_installedPackage; - } - - bool ContainsInstalledPackage(const IPackage* installedPackage) const - { - return m_installedPackage ? m_installedPackage->ContainsInstalledPackage(installedPackage) : false; - } - - void AddAvailablePackage(const std::shared_ptr& availablePackage, bool setPrimary = false) - { - if (availablePackage) - { - m_availablePackages.emplace_back(OnlyAvailable(availablePackage)); - - if (setPrimary) - { - m_primaryAvailablePackage = m_availablePackages.back(); - } - - // Set override for primary or with the first available version found - if (setPrimary || m_availablePackages.size() == 1) - { - TrySetOverrideInstalledVersion(m_availablePackages.back()); - } - } - } - - std::shared_ptr& GetPrimaryAvailablePackage() - { - return m_primaryAvailablePackage; - } - - Source GetTrackingSource() const - { - return m_installedPackage ? m_installedPackage->GetTrackingSource() : Source{}; - } - - std::shared_ptr GetTrackingPackage() const - { - return m_installedPackage ? m_installedPackage->GetTrackingPackage() : std::shared_ptr{}; - } - - std::chrono::system_clock::time_point GetTrackingPackageWriteTime() const - { - return m_installedPackage ? m_installedPackage->GetTrackingPackageWriteTime() : std::chrono::system_clock::time_point::min(); - } - - void SetTracking( - Source trackingSource, - std::shared_ptr trackingPackage, - std::chrono::system_clock::time_point trackingWriteTime) - { - if (m_installedPackage) - { - m_installedPackage->SetTracking(std::move(trackingSource), std::move(trackingPackage), trackingWriteTime); - } - } - - void FoldInstalledIn(const std::shared_ptr& other) - { - if (other->m_installedPackage) - { - if (m_installedPackage) - { - m_installedPackage->FoldInstalledIn(other->m_installedPackage); - } - else - { - m_installedPackage = other->m_installedPackage; - } - } - } - - private: - // Try to set a version that will override the version string from the installed package - void TrySetOverrideInstalledVersion(const std::shared_ptr& availablePackage) - { - if (m_installedPackage && availablePackage) - { - m_installedPackage->SetOverrideInstalledVersion(availablePackage); - } - } - - std::shared_ptr m_installedPackage; - std::shared_ptr m_primaryAvailablePackage; - std::vector> m_availablePackages; - }; - - // The comparator compares the ResultMatch by MatchType first, then Field in a predefined order. - struct ResultMatchComparator - { - template - bool operator() ( - const U& match1, - const V& match2) - { - if (match1.MatchCriteria.Type != match2.MatchCriteria.Type) - { - return match1.MatchCriteria.Type < match2.MatchCriteria.Type; - } - - if (match1.MatchCriteria.Field != match2.MatchCriteria.Field) - { - return match1.MatchCriteria.Field < match2.MatchCriteria.Field; - } - - return false; - } - }; - - template - void SortResultMatches(std::vector& matches) - { - std::stable_sort(matches.begin(), matches.end(), ResultMatchComparator()); - } - - // A copy of the standard match that holds a CompositePackage instead. - struct CompositeResultMatch - { - std::shared_ptr Package; - PackageMatchFilter MatchCriteria; - - CompositeResultMatch(std::shared_ptr p, PackageMatchFilter f) : Package(std::move(p)), MatchCriteria(std::move(f)) {} - }; - - // Stores data to enable correlation between installed and available packages. - struct CompositeResult - { - // A system reference string. - struct SystemReferenceString - { - SystemReferenceString(PackageMatchField field, Utility::LocIndString string) : - Field(field), String1(Utility::FoldCase(string)) {} - - SystemReferenceString(PackageMatchField field, Utility::LocIndString string1, Utility::LocIndString string2) : - Field(field), String1(Utility::FoldCase(string1)), String2(Utility::FoldCase(string2)) {} - - bool operator<(const SystemReferenceString& other) const - { - if (Field != other.Field) - { - return Field < other.Field; - } - - if (String1 != other.String1) - { - return String1 < other.String1; - } - - return String2 < other.String2; - } - - bool operator==(const SystemReferenceString& other) const - { - return Field == other.Field && String1 == other.String1 && String2 == other.String2; - } - - void AddToFilters( - std::vector& filters) const - { - switch (Field) - { - case PackageMatchField::NormalizedNameAndPublisher: - filters.emplace_back(PackageMatchFilter(Field, MatchType::Exact, String1.get(), String2.get())); - break; - - default: - filters.emplace_back(PackageMatchFilter(Field, MatchType::Exact, String1.get())); - } - } - - private: - PackageMatchField Field; - Utility::LocIndString String1; - Utility::LocIndString String2; - }; - - // Data relevant to correlation for a package. - struct PackageData - { - std::set SystemReferenceStrings; - - void AddIfNotPresent(SystemReferenceString&& srs) - { - if (SystemReferenceStrings.find(srs) == SystemReferenceStrings.end()) - { - SystemReferenceStrings.emplace(std::move(srs)); - } - } - - SearchRequest CreateInclusionsSearchRequest(SearchPurpose searchPurpose) const - { - SearchRequest result; - for (const auto& srs : SystemReferenceStrings) - { - srs.AddToFilters(result.Inclusions); - } - result.Purpose = searchPurpose; - return result; - } - - std::shared_ptr AddSystemReferenceStringsFromTrackingPackage(const PackageTrackingCatalog& trackingCatalog, const Utility::LocIndString& identifier, std::string_view sourceIdentifier) - { - SearchRequest trackingRequest; - trackingRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, identifier.get()); - - SearchResult trackingResult = trackingCatalog.Search(trackingRequest); - std::shared_ptr result; - - if (trackingResult.Matches.size() == 1) - { - result = OnlyAvailable(trackingResult.Matches[0].Package); - AddSystemReferenceStrings(result.get()); - } - else if (trackingResult.Matches.size() > 1) - { - AICLI_LOG(Repo, Warning, << "Found " << trackingResult.Matches.size() << " results for Id [" << identifier << "] in tracking catalog for: " << sourceIdentifier); - } - - return result; - } - - void AddSystemReferenceStrings(IPackage* package) - { - GetSystemReferenceStrings( - package, - PackageMultiProperty::PackageFamilyName, - PackageMatchField::PackageFamilyName); - - GetSystemReferenceStrings( - package, - PackageMultiProperty::ProductCode, - PackageMatchField::ProductCode); - - GetSystemReferenceStrings( - package, - PackageMultiProperty::UpgradeCode, - PackageMatchField::UpgradeCode); - - GetNameAndPublisher( - package); - } - - void AddSystemReferenceStringsFromManifest(const Manifest::Manifest& manifest) - { - for (const auto& pfn : manifest.GetPackageFamilyNames()) - { - AddIfNotPresent(SystemReferenceString{ PackageMatchField::PackageFamilyName, Utility::LocIndString{ pfn } }); - } - for (const auto& productCode : manifest.GetProductCodes()) - { - AddIfNotPresent(SystemReferenceString{ PackageMatchField::ProductCode, Utility::LocIndString{ productCode } }); - } - for (const auto& upgradeCode : manifest.GetUpgradeCodes()) - { - AddIfNotPresent(SystemReferenceString{ PackageMatchField::UpgradeCode, Utility::LocIndString{ upgradeCode } }); - } - for (const auto& name : manifest.GetPackageNames()) - { - for (const auto& publisher : manifest.GetPublishers()) - { - AddIfNotPresent(SystemReferenceString{ - PackageMatchField::NormalizedNameAndPublisher, - Utility::LocIndString{ name }, - Utility::LocIndString{ publisher } }); - } - } - } - - private: - void GetSystemReferenceStrings( - IPackage* package, - PackageMultiProperty prop, - PackageMatchField field) - { - for (auto&& string : package->GetMultiProperty(prop)) - { - AddIfNotPresent(SystemReferenceString{ field, std::move(string) }); - } - } - - void GetNameAndPublisher( - IPackage* package) - { - // Unfortunately the names and publishers are unique and not tied to each other strictly, so we need - // to go broad on the matches. Future work can hopefully make name and publisher operate more as a unit, - // but for now we have to search for the cartesian of these... - auto names = package->GetMultiProperty(PackageMultiProperty::NormalizedName); - auto publishers = package->GetMultiProperty(PackageMultiProperty::NormalizedPublisher); - - for (const auto& name : names) - { - for (const auto& publisher : publishers) - { - AddIfNotPresent(SystemReferenceString{ - PackageMatchField::NormalizedNameAndPublisher, - name, - publisher }); - } - } - } - }; - - // For a given package, prepares the results for it. - PackageData GetSystemReferenceStrings(IPackage* package) - { - PackageData result; - result.AddSystemReferenceStrings(package); - return result; - } - - // Check for a package already in the result that should have been correlated already. - // If we find one, see if we should upgrade it's match criteria. - // If we don't, return package data for further use. - // downloadManifests: when creating system reference strings, also download manifests to get more data. - std::optional CheckForExistingResultFromAvailablePackageMatch(const ResultMatch& availableMatch, bool downloadManifests) - { - std::shared_ptr availablePackage = OnlyAvailable(availableMatch.Package); - - for (auto& match : Matches) - { - if (match.Package->IsSameAsAnyAvailable(availablePackage.get())) - { - if (ResultMatchComparator{}(availableMatch, match)) - { - match.MatchCriteria = availableMatch.MatchCriteria; - } - - return {}; - } - } - - PackageData result; - result.AddSystemReferenceStrings(availablePackage.get()); - - if (downloadManifests) - { - constexpr int c_downloadManifestsLimit = 3; - int manifestsDownloaded = 0; - for (auto const& versionKey : availablePackage->GetVersionKeys()) - { - auto packageVersion = availablePackage->GetVersion(versionKey); - - auto manifest = packageVersion->GetManifest(); - result.AddSystemReferenceStringsFromManifest(manifest); - manifestsDownloaded++; - - if (manifestsDownloaded >= c_downloadManifestsLimit) - { - break; - } - } - } - - return result; - } - - // Determines if the results contain the given installed package. - bool ContainsInstalledPackage(const IPackage* installedPackage) const - { - for (auto& match : Matches) - { - if (match.Package->ContainsInstalledPackage(installedPackage)) - { - return true; - } - } - - return false; - } - - // Determines if the results contain the given installed package. - std::shared_ptr FindInstalledPackage(const IPackage* installedPackage) const - { - for (auto& match : Matches) - { - if (match.Package->ContainsInstalledPackage(installedPackage)) - { - return match.Package; - } - } - - return {}; - } - - // *Destructively* converts the result to the standard variant. - SearchResult ConvertToSearchResult() - { - FoldResults(); - - SearchResult result; - - result.Matches.reserve(Matches.size()); - for (auto& match : Matches) - { - result.Matches.emplace_back(std::move(match.Package), std::move(match.MatchCriteria)); - } - - result.Truncated = Truncated; - - result.Failures = std::move(Failures); - - return result; - } - - bool AddFailureIfSourceNotPresent(SearchResult::Failure&& failure) - { - auto itr = std::find_if(Failures.begin(), Failures.end(), - [&failure](const SearchResult::Failure& present) { - return present.SourceName == failure.SourceName; - }); - - if (itr == Failures.end()) - { - Failures.emplace_back(std::move(failure)); - return true; - } - - return false; - } - - SearchResult SearchAndHandleFailures(const Source& source, const SearchRequest& request) - { - SearchResult result; - - try - { - result = source.Search(request); - } - catch (...) - { - if (AddFailureIfSourceNotPresent({ source.GetDetails().Name, std::current_exception() })) - { - LOG_CAUGHT_EXCEPTION(); - AICLI_LOG(Repo, Warning, << "Failed to search source for correlation: " << source.GetDetails().Name); - } - } - - // Move failures into the result - for (SearchResult::Failure& failure : result.Failures) - { - AddFailureIfSourceNotPresent(std::move(failure)); - } - - return result; - } - - // Group results in an attempt to have a single result that covers all installed versions. - // This is expected to be called immediately after the installed search portion, - // when each result will contain a single installed version and some number of available packages. - // - // The folds that happen are: - // 1. When results have the same primary available package (the primary available package is set due to tracking data) - // 2. When a result has no primary available package, but another result does have a primary that matches one of the available - // a. Choose the latest primary if there are multiple - // 3. When multiple results have no primary available package and share the same available package set - // a. There are many potential additional rules that could be made here, but we will start with the simplest version. - // - // Potential improvements: - // 1. Attempting correlation of non-primary available packages to allow folding in more complex cases - // a. For example, if installed A has {source1:package1, source2:package2} and installed B has {source1:package1}, can we - // make sure that source1:package1 and source2:package2 are in fact "the same" to confidently say that installed A and B - // are side by side versions. - // 2. Attempt correlation by installed data only - // a. We can potentially detect multiple instances of the same installed item with the same correlation logic turned back on - // the installed source. This would allow for folding even when the package is not in any available source. - void FoldResults() - { - // The key to uniquely identify the package in the map - struct InstalledResultFoldKey - { - InstalledResultFoldKey() = default; - - InstalledResultFoldKey(const std::shared_ptr& package) - { - std::shared_ptr latestAvailable = package->GetLatestVersion(); - if (latestAvailable) - { - SourceIdentifier = latestAvailable->GetSource().GetIdentifier(); - PackageIdentifier = latestAvailable->GetProperty(PackageVersionProperty::Id); - } - } - - // Hash operation - size_t operator()(const InstalledResultFoldKey& value) const noexcept - { - std::hash hashString; - return hashString(value.SourceIdentifier) ^ (hashString(value.PackageIdentifier) << 1); - } - - bool operator==(const InstalledResultFoldKey& other) const noexcept - { - // Treat both empty as invalid and never equal - if (SourceIdentifier.empty() && PackageIdentifier.empty()) - { - return false; - } - - return SourceIdentifier == other.SourceIdentifier && PackageIdentifier == other.PackageIdentifier; - } - - std::string SourceIdentifier; - std::string PackageIdentifier; - }; - - // The data for a package in the map - struct InstalledResultFoldData - { - InstalledResultFoldData() = default; - explicit InstalledResultFoldData(size_t primaryPackageIndex) : PrimaryPackageIndex(primaryPackageIndex) {} - - std::optional PrimaryPackageIndex; - std::vector NonPrimaryPackageIndices; - }; - - std::unordered_map foldData; - - // Attempt to fold all primary package matches first. - // Packages without primaries will still be indexed into the hash table. - for (size_t i = 0; i < Matches.size(); ++i) - { - CompositeResultMatch& currentMatch = Matches[i]; - - // Check current match for fold target - if (currentMatch.Package->GetPrimaryAvailablePackage()) - { - InstalledResultFoldKey key{ currentMatch.Package->GetPrimaryAvailablePackage() }; - - auto itr = foldData.find(key); - if (itr != foldData.end()) - { - if (itr->second.PrimaryPackageIndex) - { - Matches[itr->second.PrimaryPackageIndex.value()].Package->FoldInstalledIn(currentMatch.Package); - currentMatch.Package.reset(); - } - else - { - itr->second.PrimaryPackageIndex = i; - } - } - else - { - foldData[key] = InstalledResultFoldData{ i }; - } - } - else - { - for (const auto& availablePackage : currentMatch.Package->GetAvailablePackages()) - { - InstalledResultFoldKey key{ availablePackage }; - - auto itr = foldData.find(key); - if (itr == foldData.end()) - { - itr = foldData.insert({ key, {} }).first; - } - - itr->second.NonPrimaryPackageIndices.emplace_back(i); - } - } - } - - // After primary matches are folded, attempt to fold results without primary matches. - // The latest primary match will be preferred. - for (size_t i = 0; i < Matches.size(); ++i) - { - CompositeResultMatch& currentMatch = Matches[i]; - - // Skip any matches that we have already folded - if (!currentMatch.Package) - { - continue; - } - - if (!currentMatch.Package->GetPrimaryAvailablePackage()) - { - InstalledResultFoldData* latestPrimaryAvailable = nullptr; - std::vector availableFoldData; - - for (const auto& availablePackage : currentMatch.Package->GetAvailablePackages()) - { - auto& packageFoldData = foldData.at(availablePackage); - - if (packageFoldData.PrimaryPackageIndex) - { - if (!latestPrimaryAvailable || - Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package->GetTrackingPackageWriteTime() < Matches[packageFoldData.PrimaryPackageIndex.value()].Package->GetTrackingPackageWriteTime()) - { - latestPrimaryAvailable = &packageFoldData; - } - } - else - { - availableFoldData.emplace_back(&packageFoldData); - } - } - - if (latestPrimaryAvailable) - { - Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package->FoldInstalledIn(currentMatch.Package); - currentMatch.Package.reset(); - - // If the result with the primary is later, move it forward - if (latestPrimaryAvailable->PrimaryPackageIndex.value() > i) - { - currentMatch.Package = std::move(Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package); - Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package.reset(); - latestPrimaryAvailable->PrimaryPackageIndex = i; - } - continue; - } - - // First, find the intersection of all results that contain all of the packages from this result. - std::vector candidateMatches; - for (size_t j = 0; j < availableFoldData.size(); ++j) - { - InstalledResultFoldData* packageFoldData = availableFoldData[j]; - - if (j == 0) - { - candidateMatches = packageFoldData->NonPrimaryPackageIndices; - } - else - { - std::vector temp; - std::set_intersection( - candidateMatches.begin(), candidateMatches.end(), - packageFoldData->NonPrimaryPackageIndices.begin(), packageFoldData->NonPrimaryPackageIndices.end(), - std::back_inserter(temp)); - candidateMatches = std::move(temp); - } - } - - // Now exclude both our own result and any that have a different (larger) number of available packages - candidateMatches.erase(std::remove_if(candidateMatches.begin(), candidateMatches.end(), - [&](size_t index) { return index == i || Matches[index].Package->GetAvailablePackages().size() != currentMatch.Package->GetAvailablePackages().size(); }), - candidateMatches.end()); - - // All of these remaining values should be folded in to our result - for (size_t foldTarget : candidateMatches) - { - currentMatch.Package->FoldInstalledIn(Matches[foldTarget].Package); - Matches[foldTarget].Package.reset(); - } - } - } - - // Get rid of the folded results; we reset the Package to indicate that it is no longer valid - Matches.erase(std::remove_if(Matches.begin(), Matches.end(), [&](const CompositeResultMatch& match) { return !match.Package; }), Matches.end()); - } - - std::vector Matches; - bool Truncated = false; - std::vector Failures; - }; - - std::shared_ptr GetTrackedPackageFromAvailableSource(CompositeResult& result, const Source& source, const Utility::LocIndString& identifier) - { - SearchRequest directRequest; - directRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, identifier.get()); - - SearchResult directResult = result.SearchAndHandleFailures(source, directRequest); - - if (directResult.Matches.empty()) - { - AICLI_LOG(Repo, Warning, << "Did not find Id [" << identifier << "] in tracked source: " << source.GetDetails().Name); - } - else if (directResult.Matches.size() == 1) - { - return directResult.Matches[0].Package; - } - else - { - AICLI_LOG(Repo, Warning, << "Found multiple results for Id [" << identifier << "] in tracked source: " << source.GetDetails().Name); - } - - return {}; - } - } - - using namespace anon; - - CompositeSource::CompositeSource(std::string identifier) - { - m_details.Identifier = std::move(identifier); - } - - const SourceDetails& CompositeSource::GetDetails() const - { - return m_details; - } - - const std::string& CompositeSource::GetIdentifier() const - { - return m_details.Identifier; - } - - // The composite search needs to take several steps to get results, and due to the - // potential for different information spread across multiple sources, base searches - // need to be performed in both installed and available. - // - // If an installed source is present, then the searches should only return packages - // that are installed. This means that the base searches against available sources - // will only return results where a match is found in the installed source. - SearchResult CompositeSource::Search(const SearchRequest& request) const - { - if (m_installedSource) - { - return SearchInstalled(request); - } - else - { - return SearchAvailable(request); - } - } - - void* CompositeSource::CastTo(ISourceType type) - { - if (type == SourceType) - { - return this; - } - - return nullptr; - } - - void CompositeSource::AddAvailableSource(const Source& source) - { - m_availableSources.emplace_back(source); - } - - void CompositeSource::SetInstalledSource(Source source, CompositeSearchBehavior searchBehavior) - { - m_installedSource = std::move(source); - m_searchBehavior = searchBehavior; - } - - // An installed search first finds all installed packages that match the request, then correlates with available sources. - // Next the search is performed against the available sources and correlated with the installed source. A result will only - // be added if there exists an installed package that was not found by the initial search. - // This allows for search terms to find installed packages by their available metadata, as well as the local values. - // - // Search flow: - // Installed :: Search incoming request - // For each result - // For each available source - // Tracking :: Search system references - // If tracking found - // Available :: Search tracking ID - // If no available, for each available source - // Available :: Search system references - // - // For each available source - // Tracking :: Search incoming request - // For each result - // Installed :: Search system references - // If found - // Available :: Search tracking ID - // Available :: Search incoming request - // For each result - // Installed :: Search system references - SearchResult CompositeSource::SearchInstalled(const SearchRequest& request) const - { - CompositeResult result; - - // If the search behavior is for AllPackages or Installed then the result can contain packages that are - // only in the Installed source, but do not have an AvailableVersion. - if (m_searchBehavior == CompositeSearchBehavior::AllPackages || m_searchBehavior == CompositeSearchBehavior::Installed) - { - // Search installed source (allow exceptions out as we own the installed source) - SearchResult installedResult = m_installedSource.Search(request); - result.Truncated = installedResult.Truncated; - - for (auto&& match : installedResult.Matches) - { - if (!match.Package) - { - // Ensure that the crash from installedVersion below is not from the actual package being null. - AICLI_LOG(Repo, Warning, << "CompositeSource: The match of the package (matched on " << - ToString(match.MatchCriteria.Field) << " => '" << match.MatchCriteria.Value << - "') was null and is being dropped from the results."); - continue; - } - - std::shared_ptr compositePackage = std::make_shared(match.Package); - auto installedPackage = compositePackage->GetInstalled(); - - if (!installedPackage) - { - // One would think that the installed package coming directly from our own installed source - // would never be null, but it is sometimes. Rather than making users suffer through crashes - // that break their entire experience, lets log a few things and then ignore this match. - AICLI_LOG(Repo, Warning, << "CompositeSource: The installed version of the package '" << - match.Package->GetProperty(PackageProperty::Id) << "' was null and is being dropped from the results."); - continue; - } - - auto installedPackageData = result.GetSystemReferenceStrings(installedPackage.get()); - - // Create a search request to run against all available sources - if (!installedPackageData.SystemReferenceStrings.empty()) - { - SearchRequest systemReferenceSearch = installedPackageData.CreateInclusionsSearchRequest(SearchPurpose::CorrelationToAvailable); - AICLI_LOG(Repo, Verbose, << "Finding available package from installed package using system reference search: " << systemReferenceSearch.ToString()); - - // Search sources and add to result - for (const auto& source : m_availableSources) - { - AICLI_LOG(Repo, Verbose, << " ... searching source: " << source.GetDetails().Name << " [" << source.GetIdentifier() << ']'); - - // Find the tracking result with the latest timestamp. - auto trackingCatalog = source.GetTrackingCatalog(); - SearchResult trackingResult = trackingCatalog.Search(systemReferenceSearch); - - std::shared_ptr trackingPackage; - std::chrono::system_clock::time_point trackingPackageTime; - bool trackingSet = false; - - for (const auto& trackingMatch : trackingResult.Matches) - { - auto candidateTime = GetLatestTrackingWriteTime(OnlyAvailable(trackingMatch.Package)); - - if (!trackingPackage || candidateTime > trackingPackageTime) - { - trackingPackage = OnlyAvailable(trackingMatch.Package); - trackingPackageTime = candidateTime; - } - } - - if (trackingPackage && trackingPackageTime > compositePackage->GetTrackingPackageWriteTime()) - { - AICLI_LOG(Repo, Verbose, << " ... setting latest tracking package to: " << trackingPackage->GetProperty(PackageProperty::Id)); - compositePackage->SetTracking(source, trackingPackage, trackingPackageTime); - trackingSet = true; - } - - // Attempt to correlate local packages against this source if supported. - SearchResult availableResult; - if (source.GetDetails().SupportInstalledSearchCorrelation) - { - availableResult = result.SearchAndHandleFailures(source, systemReferenceSearch); - } - - auto availablePackage = GetMatchingPackage(availableResult.Matches, - [&]() { - AICLI_LOG(Repo, Info, - << "Found multiple matches for installed package [" << installedPackage->GetProperty(PackageProperty::Id) << - "] in source [" << source.GetIdentifier() << "] when searching for [" << systemReferenceSearch.ToString() << "]"); - }, [&] { - AICLI_LOG(Repo, Warning, << " Appropriate available package could not be determined"); - }); - - if (trackingPackage) - { - auto trackingIdentifier = trackingPackage->GetProperty(PackageProperty::Id); - - // We always want to take the available search result if it exists as the package may have been updated. - if (availablePackage) - { - auto availableIdentifier = availablePackage->GetProperty(PackageProperty::Id); - if (!Utility::ICUCaseInsensitiveEquals(availableIdentifier, trackingIdentifier)) - { - AICLI_LOG(Repo, Verbose, << " ... overriding tracking package (" << trackingIdentifier << ") with available package (" << availableIdentifier << ")"); - } - } - else - { - AICLI_LOG(Repo, Verbose, << " ... using tracking package: " << trackingIdentifier); - availablePackage = GetTrackedPackageFromAvailableSource(result, source, trackingIdentifier); - } - } - - if (availablePackage) - { - AICLI_LOG(Repo, Verbose, << " ... adding available package: " << availablePackage->GetProperty(PackageProperty::Id)); - compositePackage->AddAvailablePackage(availablePackage, trackingSet); - } - } - } - - // Move the installed result into the composite result - result.Matches.emplace_back(std::move(compositePackage), std::move(match.MatchCriteria)); - } - - // Optimization for the "everything installed" case, no need to allow for reverse correlations - if (request.IsForEverything() && m_searchBehavior == CompositeSearchBehavior::Installed) - { - return result.ConvertToSearchResult(); - } - } - - // Search available sources - for (const auto& source : m_availableSources) - { - auto trackingCatalog = source.GetTrackingCatalog(); - - SearchResult availableResult = result.SearchAndHandleFailures(source, request); - bool downloadManifests = source.QueryFeatureFlag(SourceFeatureFlag::ManifestMayContainAdditionalSystemReferenceStrings); - - for (auto&& match : availableResult.Matches) - { - // Check for the package already in the result. - // In cases that PackageData will be created, also download manifests for system reference strings - // when search result is small (currently limiting to 1). - auto packageData = result.CheckForExistingResultFromAvailablePackageMatch(match, downloadManifests && availableResult.Matches.size() == 1); - - // If found existing package in the result, continue - if (!packageData) - { - continue; - } - - // Use data from the tracking catalog as it can potentially get better correlations - auto trackingPackage = packageData->AddSystemReferenceStringsFromTrackingPackage(trackingCatalog, match.Package->GetProperty(PackageProperty::Id), source.GetDetails().Name); - - // If no package was found that was already in the results, do a correlation lookup with the installed - // source to create a new composite package entry if we find any packages there. - bool foundInstalledMatch = false; - if (!packageData->SystemReferenceStrings.empty()) - { - // Create a search request to run against the installed source - SearchRequest systemReferenceSearch = packageData->CreateInclusionsSearchRequest(SearchPurpose::CorrelationToInstalled); - - AICLI_LOG(Repo, Verbose, << "Finding installed package from available package using system reference search: " << systemReferenceSearch.ToString()); - // Correlate against installed (allow exceptions out as we own the installed source) - SearchResult installedCrossRef = m_installedSource.Search(systemReferenceSearch); - - for (const auto& installedMatch : installedCrossRef.Matches) - { - if (!IsStrongMatchField(installedMatch.MatchCriteria.Field)) - { - // For weak correlations, do an installed -> available check to ensure that there are no other strong correlations. - SearchResult correlationConfirmation; - if (source.GetDetails().SupportInstalledSearchCorrelation) - { - correlationConfirmation = result.SearchAndHandleFailures(source, result.GetSystemReferenceStrings(installedMatch.Package->GetInstalled().get()).CreateInclusionsSearchRequest(SearchPurpose::CorrelationToAvailable)); - } - - if (correlationConfirmation.Matches.empty()) - { - // We probably made the correlation due to tracking data, keep it. - } - else if (correlationConfirmation.Matches.size() > 1) - { - // There is contention for the correlation. - AICLI_LOG(Repo, Verbose, << " ... installed package [" << installedMatch.Package->GetProperty(PackageProperty::Id) << - "] had multiple correlations and is being ignored as a match for [" << match.Package->GetProperty(PackageProperty::Id) << "]"); - continue; - } - else if (!OnlyAvailable(correlationConfirmation.Matches[0].Package)->IsSame(OnlyAvailable(match.Package).get())) - { - // The only correlation is not to the current package. - AICLI_LOG(Repo, Verbose, << " ... installed package [" << installedMatch.Package->GetProperty(PackageProperty::Id) << - "] was found through available package [" << match.Package->GetProperty(PackageProperty::Id) << "], but only correlated to [" << - correlationConfirmation.Matches[0].Package->GetProperty(PackageProperty::Id) << "] and is being ignored"); - continue; - } - } - - // Now that we know we need to add this available package, determine how exactly - std::shared_ptr resultPackage = result.FindInstalledPackage(installedMatch.Package->GetInstalled().get()); - - if (resultPackage) - { - // Check for a package from the same source already present on the result package. - bool foundSameSource = false; - - for (const auto& availablePackage : resultPackage->GetAvailablePackages()) - { - if (availablePackage->GetSource() == source) - { - // TODO: May need to add more data so that we can choose the proper correlation, but it may also be very difficult to get through - // the gauntlet of other checks and arrive in this situation. - AICLI_LOG(Repo, Verbose, << " ... found [" << availablePackage->GetProperty(PackageProperty::Id) << - "] already correlated to [" << installedMatch.Package->GetProperty(PackageProperty::Id) << "] from the same source [" << - source.GetDetails().Name << "] as [" << match.Package->GetProperty(PackageProperty::Id) << "]; ignoring the second correlation"); - foundSameSource = true; - } - } - - if (foundSameSource) - { - continue; - } - } - else - { - result.Matches.emplace_back(std::make_shared(installedMatch.Package), match.MatchCriteria); - resultPackage = result.Matches.back().Package; - } - - bool setPrimary = false; - if (trackingPackage) - { - auto trackingPackageTime = GetLatestTrackingWriteTime(trackingPackage); - - if (trackingPackageTime > resultPackage->GetTrackingPackageWriteTime()) - { - resultPackage->SetTracking(source, std::move(trackingPackage), trackingPackageTime); - setPrimary = true; - } - } - - resultPackage->AddAvailablePackage(std::move(match.Package), setPrimary); - - foundInstalledMatch = true; - } - } - - // If there was no correlation for this package, add it without one. - if ((m_searchBehavior == CompositeSearchBehavior::AllPackages || m_searchBehavior == CompositeSearchBehavior::AvailablePackages) && !foundInstalledMatch) - { - result.Matches.emplace_back(std::make_shared(std::shared_ptr{}, std::move(match.Package)), match.MatchCriteria); - } - } - } - - SortResultMatches(result.Matches); - - if (request.MaximumResults > 0 && result.Matches.size() > request.MaximumResults) - { - result.Truncated = true; - result.Matches.erase(result.Matches.begin() + request.MaximumResults, result.Matches.end()); - } - - return result.ConvertToSearchResult(); - } - - // An available search goes through each source, searching individually and then sorting the full result set. - SearchResult CompositeSource::SearchAvailable(const SearchRequest& request) const - { - SearchResult result; - - // Search available sources - for (const auto& source : m_availableSources) - { - SearchResult oneSourceResult; - - try - { - oneSourceResult = source.Search(request); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - AICLI_LOG(Repo, Warning, << "Failed to search source: " << source.GetDetails().Name); - result.Failures.emplace_back(SearchResult::Failure{ source.GetDetails().Name, std::current_exception() }); - } - - // Move into the single result - std::move(oneSourceResult.Matches.begin(), oneSourceResult.Matches.end(), std::back_inserter(result.Matches)); - std::move(oneSourceResult.Failures.begin(), oneSourceResult.Failures.end(), std::back_inserter(result.Failures)); - } - - SortResultMatches(result.Matches); - - if (request.MaximumResults > 0 && result.Matches.size() > request.MaximumResults) - { - result.Truncated = true; - result.Matches.erase(result.Matches.begin() + request.MaximumResults, result.Matches.end()); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "CompositeSource.h" +#include + +using namespace AppInstaller::Settings; + +namespace AppInstaller::Repository +{ + using namespace std::string_view_literals; + + namespace anon + { + Utility::VersionAndChannel GetVACFromVersion(IPackageVersion* packageVersion) + { + return { + Utility::Version(packageVersion->GetProperty(PackageVersionProperty::Version)), + Utility::Channel(packageVersion->GetProperty(PackageVersionProperty::Channel)) + }; + } + + // Returns true for fields that provide a strong match; one that is not based on a heuristic. + bool IsStrongMatchField(PackageMatchField field) + { + switch (field) + { + case AppInstaller::Repository::PackageMatchField::PackageFamilyName: + case AppInstaller::Repository::PackageMatchField::ProductCode: + case AppInstaller::Repository::PackageMatchField::UpgradeCode: + return true; + } + + return false; + } + + // Gets the only available package from the composite, ensuring this fact in test contexts. + std::shared_ptr OnlyAvailable(const std::shared_ptr& composite) + { + std::vector> availablePackages = composite->GetAvailable(); + +#ifndef AICLI_DISABLE_TEST_HOOKS + THROW_HR_IF(E_UNEXPECTED, availablePackages.size() != 1); +#endif + + return std::move(availablePackages.front()); + } + + // Move returns if there is only one package in the matches that is strong; otherwise returns an empty value. + std::shared_ptr FindOnlyStrongMatchFieldResult(std::vector& matches) + { + std::shared_ptr result; + + for (auto&& match : matches) + { + AICLI_LOG(Repo, Info, << " Checking match with package id: " << match.Package->GetProperty(PackageProperty::Id)); + + if (IsStrongMatchField(match.MatchCriteria.Field)) + { + if (!result) + { + result = std::move(match.Package); + } + else + { + AICLI_LOG(Repo, Info, << " Found multiple packages with strong match fields"); + result.reset(); + break; + } + } + } + + return result; + } + + // Gets a single matching package from the results + template + std::shared_ptr GetMatchingPackage(std::vector& matches, MultipleIntro&& multipleIntro, Indeterminate&& indeterminate) + { + if (matches.empty()) + { + return {}; + } + else if (matches.size() == 1) + { + return std::move(matches[0].Package); + } + else + { + multipleIntro(); + + auto result = FindOnlyStrongMatchFieldResult(matches); + + if (!result) + { + indeterminate(); + } + + return result; + } + } + + // For a given package from a tracking catalog, get the latest write time. + // Look at all versions rather than just the latest to account for the potential of downgrading. + std::chrono::system_clock::time_point GetLatestTrackingWriteTime( + const std::shared_ptr& trackingPackage) + { + std::chrono::system_clock::time_point result{}; + + for (const auto& key : trackingPackage->GetVersionKeys()) + { + auto version = trackingPackage->GetVersion(key); + if (version) + { + auto metadata = version->GetMetadata(); + auto itr = metadata.find(PackageVersionMetadata::TrackingWriteTime); + if (itr != metadata.end()) + { + std::int64_t unixEpoch = 0; + try + { + unixEpoch = std::stoll(itr->second); + } + CATCH_LOG(); + + std::chrono::system_clock::time_point versionTime = Utility::ConvertUnixEpochToSystemClock(unixEpoch); + + if (versionTime > result) + { + result = versionTime; + } + } + } + } + + return result; + } + + // An installed package's version reported in ARP does not necessarily match the versions used for the manifest. + // This function uses the data in the manifest to map the installed version string to the version used by the manifest. + // + // TODO: Note: Currently this function assumes the all versions in the available package is from one source. + // Even though a composite package can have available packages from multiple sources, we only call this function + // for the default (first) available package. If we ever need to consider other sources, this function needs to be revisited. + std::string GetMappedInstalledVersion(const std::string& installedVersion, const std::shared_ptr& availablePackage) + { + // Perform an initial check to see if the latest version has a mapping; if it does not, do not attempt any more. + auto latestVersion = availablePackage->GetLatestVersion(); + if (latestVersion) + { + auto version = latestVersion->GetProperty(PackageVersionProperty::Version); + auto arpMinVersion = latestVersion->GetProperty(PackageVersionProperty::ArpMinVersion); + auto arpMaxVersion = latestVersion->GetProperty(PackageVersionProperty::ArpMaxVersion); + + if ((arpMinVersion.empty() || arpMinVersion == version) && (arpMaxVersion.empty() || arpMaxVersion == version)) + { + return installedVersion; + } + } + + // Stores raw versions value strings to run a preliminary check whether version mapping is needed. + std::vector> rawVersionValues; + auto versionKeys = availablePackage->GetVersionKeys(); + bool shouldTryPerformMapping = false; + + for (auto const& versionKey : versionKeys) + { + auto availableVersion = availablePackage->GetVersion(versionKey); + std::string arpMinVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMinVersion); + std::string arpMaxVersion = availableVersion->GetProperty(PackageVersionProperty::ArpMaxVersion); + + if (!arpMinVersion.empty() && !arpMaxVersion.empty()) + { + std::string manifestVersion = versionKey.Version; + + if (!shouldTryPerformMapping && (arpMinVersion != manifestVersion || arpMaxVersion != manifestVersion)) + { + shouldTryPerformMapping = true; + } + + rawVersionValues.emplace_back(std::make_tuple(std::move(manifestVersion), std::move(arpMinVersion), std::move(arpMaxVersion))); + } + } + + if (!shouldTryPerformMapping) + { + return installedVersion; + } + + // Construct a map between manifest version and arp version range. The map is ordered in descending by package version. + std::vector> arpVersionMap; + + for (auto& tuple : rawVersionValues) + { + auto&& [manifestVersion, arpMinVersion, arpMaxVersion] = std::move(tuple); + Utility::VersionRange arpVersionRange{ Utility::Version(std::move(arpMinVersion)), Utility::Version(std::move(arpMaxVersion)) }; + Utility::Version manifestVer{ std::move(manifestVersion) }; + // Skip mapping to unknown version + if (!manifestVer.IsUnknown()) + { + arpVersionMap.emplace_back(std::make_pair(std::move(manifestVer), std::move(arpVersionRange))); + } + } + + // Go through the arp version map and determine what mapping should be performed. + // shouldPerformMapping is true when at least 1 arp version range is different from the package version. + bool shouldPerformMapping = false; + bool isArpVersionRangeInDescendingOrder = true; + const Utility::VersionRange* previousVersionRange = nullptr; + + for (auto const& pair : arpVersionMap) + { + // If arp version range is not same as package version, should perform mapping + // This check is still needed to account for 1.0 == 1.0.0 cases + if (!shouldPerformMapping && !pair.second.IsSameAsSingleVersion(pair.first)) + { + shouldPerformMapping = true; + } + + if (!previousVersionRange) + { + // This is the first non empty arp version range + previousVersionRange = &pair.second; + } + else if (isArpVersionRangeInDescendingOrder) + { + // The arp version range should be less than previous range + if (pair.second < *previousVersionRange) + { + previousVersionRange = &pair.second; + } + else + { + isArpVersionRangeInDescendingOrder = false; + } + } + } + + // Now perform arp version mapping + if (shouldPerformMapping) + { + Utility::Version installed{ installedVersion }; + for (auto const& pair : arpVersionMap) + { + // If the installed version is in the arp version range + if (pair.second.ContainsVersion(installed)) + { + return pair.first.ToString(); + } + } + + // At this point, no mapping found. Perform approximate mapping if applicable. + // We'll start from end of the vector because we try to find closest less than version if possible. + if (isArpVersionRangeInDescendingOrder) + { + const Utility::Version* lastGreaterThanVersion = nullptr; + auto it = arpVersionMap.rbegin(); + while (it != arpVersionMap.rend()) + { + const auto& pair = *it; + if (installed < pair.second.GetMinVersion()) + { + return Utility::Version{ pair.first, Utility::Version::ApproximateComparator::LessThan }.ToString(); + } + else + { + lastGreaterThanVersion = &pair.first; + } + + it++; + } + + // No approximate less than version found, approximate greater than version will be returned. + if (lastGreaterThanVersion) + { + return Utility::Version{ *lastGreaterThanVersion, Utility::Version::ApproximateComparator::GreaterThan }.ToString(); + } + } + } + + // return the input installed version if no mapping is performed or found. + return installedVersion; + } + + // A composite package installed version that allows us to override the source or the version. + struct CompositeInstalledVersion : public IPackageVersion + { + CompositeInstalledVersion(std::shared_ptr baseInstalledVersion, Source trackingSource, std::shared_ptr trackingPackageVersion, std::string overrideVersion = {}) : + m_baseInstalledVersion(std::move(baseInstalledVersion)), m_trackingSource(std::move(trackingSource)), m_trackingPackageVersion(std::move(trackingPackageVersion)), m_overrideVersion(std::move(overrideVersion)) + {} + + Utility::LocIndString GetProperty(PackageVersionProperty property) const override + { + // If there is an override version, use it. + if (property == PackageVersionProperty::Version && !m_overrideVersion.empty()) + { + return Utility::LocIndString{ m_overrideVersion }; + } + + return m_baseInstalledVersion->GetProperty(property); + } + + std::vector GetMultiProperty(PackageVersionMultiProperty property) const override + { + return m_baseInstalledVersion->GetMultiProperty(property); + } + + Manifest::Manifest GetManifest() override + { + return m_baseInstalledVersion->GetManifest(); + } + + Source GetSource() const override + { + // If there is a tracking source, use it instead to indicate that it came from there. + if (m_trackingSource) + { + return m_trackingSource; + } + + return m_baseInstalledVersion->GetSource(); + } + + Metadata GetMetadata() const override + { + auto result = m_baseInstalledVersion->GetMetadata(); + + // Populate metadata from tracking package version if not present in base installed version. + if (m_trackingPackageVersion) + { + auto trackingMetadata = m_trackingPackageVersion->GetMetadata(); + for (auto metadataItem : { PackageVersionMetadata::InstalledArchitecture, PackageVersionMetadata::InstalledLocale, + PackageVersionMetadata::UserIntentArchitecture, PackageVersionMetadata::UserIntentLocale, PackageVersionMetadata::PinnedState, + PackageVersionMetadata::InitialOverrideArguments, PackageVersionMetadata::InitialCustomSwitches }) + { + auto itr = trackingMetadata.find(metadataItem); + auto existingItr = result.find(metadataItem); + if (itr != trackingMetadata.end() && existingItr == result.end()) + { + result[metadataItem] = itr->second; + } + } + } + + return result; + } + + private: + std::shared_ptr m_baseInstalledVersion; + Source m_trackingSource; + std::string m_overrideVersion; + std::shared_ptr m_trackingPackageVersion; + }; + + // An IPackage for the installed package of a CompositePackage. + struct CompositeInstalledPackage : public IPackage + { + static constexpr IPackageType PackageType = IPackageType::CompositeInstalledPackage; + + CompositeInstalledPackage(std::shared_ptr package) + { + AddPackageAndVersionKeyData(std::move(package)); + } + + Utility::LocIndString GetProperty(PackageProperty property) const override + { + THROW_HR_IF(E_UNEXPECTED, m_packages.empty() || m_versionKeyData.empty()); + + // Use the highest version for package properties + return m_packages[m_versionKeyData[0].PackageIndex]->GetProperty(property); + } + + std::vector GetMultiProperty(PackageMultiProperty property) const override + { + std::vector result; + + for (const auto& package : m_packages) + { + for (auto&& string : package->GetMultiProperty(property)) + { + auto itr = std::lower_bound(result.begin(), result.end(), string); + + if (itr == result.end() || *itr != string) + { + result.emplace(itr, std::move(string)); + } + } + } + + return result; + } + + std::vector GetVersionKeys() const override + { + return { m_versionKeyData.begin(), m_versionKeyData.end() }; + } + + std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override + { + std::shared_ptr installedVersion; + std::string overrideVersion; + + for (const VersionKeyData& key : m_versionKeyData) + { + if (key.IsMatch(versionKey)) + { + installedVersion = key.InstalledVersion; + overrideVersion = key.Version; + break; + } + } + + if (installedVersion) + { + // Get the appropriate tracking version or latest if it is not found. + // The tracking package uses the mapped version. + std::shared_ptr trackingPackageVersion; + if (m_trackingPackage) + { + // Remove our use of the package id as source + PackageVersionKey versionKey_NoSource = versionKey; + versionKey_NoSource.SourceId.clear(); + + trackingPackageVersion = m_trackingPackage->GetVersion(versionKey_NoSource); + + if (!trackingPackageVersion) + { + trackingPackageVersion = m_trackingPackage->GetLatestVersion(); + } + } + + return std::make_shared(std::move(installedVersion), m_trackingSource, std::move(trackingPackageVersion), std::move(overrideVersion)); + } + + return nullptr; + } + + std::shared_ptr GetLatestVersion() const override + { + return GetVersion({}); + } + + Source GetSource() const override + { + // If there is a tracking source, use it instead to indicate that it came from there. + // Otherwise, all of the installed packages should be from the same source. + return m_trackingSource ? m_trackingSource : m_packages[0]->GetSource(); + } + + bool IsSame(const IPackage* other) const override + { + const CompositeInstalledPackage* otherPackage = PackageCast(other); + + if (otherPackage) + { + if (m_packages.size() != otherPackage->m_packages.size()) + { + return false; + } + + for (const auto& subPackage : m_packages) + { + bool foundSame = false; + + for (const auto& otherSubPackage : otherPackage->m_packages) + { + if (subPackage->IsSame(otherSubPackage.get())) + { + foundSame = true; + break; + } + } + + if (!foundSame) + { + return false; + } + } + + return true; + } + + return false; + } + + const void* CastTo(IPackageType type) const override + { + if (type == PackageType) + { + return this; + } + + return nullptr; + } + + void SetTracking( + Source trackingSource, + std::shared_ptr trackingPackage, + std::chrono::system_clock::time_point trackingWriteTime) + { + m_trackingSource = std::move(trackingSource); + m_trackingPackage = std::move(trackingPackage); + m_trackingWriteTime = trackingWriteTime; + } + + Source GetTrackingSource() const + { + return m_trackingSource; + } + + const std::shared_ptr& GetTrackingPackage() const + { + return m_trackingPackage; + } + + std::chrono::system_clock::time_point GetTrackingPackageWriteTime() const + { + return m_trackingWriteTime; + } + + bool ContainsInstalledPackage(const IPackage* installedPackage) const + { + for (const auto& package : m_packages) + { + if (package->IsSame(installedPackage)) + { + return true; + } + } + + return false; + } + + void FoldInstalledIn(const std::shared_ptr& other) + { + for (const auto& package : other->m_packages) + { + AddPackageAndVersionKeyData(package); + } + } + + // Set a version that will override the version string from the installed package + void SetOverrideInstalledVersion(const std::shared_ptr& availablePackage) + { + if (availablePackage) + { + m_availablePackageVersionOverride = availablePackage; + + for (auto& key : m_versionKeyData) + { + if (Manifest::DoesInstallerTypeSupportArpVersionRange(key.InstalledType)) + { + key.Version = GetMappedInstalledVersion(key.InstalledVersion->GetProperty(PackageVersionProperty::Version), availablePackage); + key.VersionAndChannel = Utility::VersionAndChannel{ key.Version, key.Channel }; + } + } + } + } + + bool IsEmpty() const + { + return m_versionKeyData.empty(); + } + + private: + // Contains information about all of the version keys. + // We use the `SourceId` field to store the installed package identifier so that we can disambiguate keys is they have the same version. + struct VersionKeyData : public PackageVersionKey + { + size_t PackageIndex; + std::shared_ptr InstalledVersion; + Manifest::InstallerTypeEnum InstalledType; + Utility::VersionAndChannel VersionAndChannel; + + bool operator<(const VersionKeyData& other) const + { + return VersionAndChannel < other.VersionAndChannel; + } + }; + + // Adds the package and version key data to the composite. + // The version keys are then sorted so that the first (index 0) in the vector has the highest version. + // Note that it may tied for highest version if, for instance, the same version is installed for different architectures. + void AddPackageAndVersionKeyData(std::shared_ptr package) + { + // We don't want this to happen, but it could. Rather than a crash, we will log it and move on. + if (!package) + { + AICLI_LOG(Repo, Verbose, << "AddPackageAndVersionKeyData called with an empty package"); + return; + } + + size_t packageIndex = m_packages.size(); + std::string packageIdentifier = package->GetProperty(PackageProperty::Id); + bool versionAdded = false; + + for (const auto& versionKey : package->GetVersionKeys()) + { + VersionKeyData keyData{ versionKey }; + + keyData.PackageIndex = packageIndex; + keyData.InstalledVersion = package->GetVersion(versionKey); + + if (!keyData.InstalledVersion) + { + AICLI_LOG(Repo, Verbose, << "AddPackageAndVersionKeyData: Package [" << packageIdentifier << "] did not return a version for [" << versionKey.Version << "]"); + continue; + } + + // We use the `SourceId` field to store the installed package identifier so that we can disambiguate keys if they have the same version. + keyData.SourceId = packageIdentifier; + + keyData.InstalledType = Manifest::ConvertToInstallerTypeEnum(keyData.InstalledVersion->GetMetadata()[PackageVersionMetadata::InstalledType]); + if (m_availablePackageVersionOverride && Manifest::DoesInstallerTypeSupportArpVersionRange(keyData.InstalledType)) + { + keyData.Version = GetMappedInstalledVersion(keyData.InstalledVersion->GetProperty(PackageVersionProperty::Version), m_availablePackageVersionOverride); + } + + keyData.VersionAndChannel = Utility::VersionAndChannel{ keyData.Version, keyData.Channel }; + + m_versionKeyData.emplace_back(std::move(keyData)); + versionAdded = true; + } + + if (versionAdded) + { + m_packages.emplace_back(std::move(package)); + + std::sort(m_versionKeyData.begin(), m_versionKeyData.end()); + } + } + + std::vector> m_packages; + std::vector m_versionKeyData; + Source m_trackingSource; + std::shared_ptr m_trackingPackage; + std::chrono::system_clock::time_point m_trackingWriteTime = std::chrono::system_clock::time_point::min(); + std::shared_ptr m_availablePackageVersionOverride; + }; + + // An ICompositePackage for the CompositeSource. + struct CompositePackage : public ICompositePackage + { + // The availablePackage may only contain one available package within it, as it is expected to be the output of a search on a single source. + CompositePackage(const std::shared_ptr& installedPackage, const std::shared_ptr& availablePackage = {}, bool setPrimary = false) + { + if (installedPackage) + { + m_installedPackage = std::make_shared(installedPackage->GetInstalled()); + + // If the installed package result existed, but didn't actually create any installed versions, drop it. + if (m_installedPackage->IsEmpty()) + { + m_installedPackage.reset(); + } + } + + AddAvailablePackage(availablePackage, setPrimary); + } + + Utility::LocIndString GetProperty(PackageProperty property) const override + { + IPackage* truth = nullptr; + if (m_primaryAvailablePackage) + { + truth = m_primaryAvailablePackage.get(); + } + if (!truth && !m_availablePackages.empty()) + { + truth = m_availablePackages[0].get(); + } + if (!truth) + { + truth = m_installedPackage.get(); + } + + THROW_HR_IF(E_UNEXPECTED, !truth); + + return truth->GetProperty(property); + } + + std::shared_ptr GetInstalled() override + { + return m_installedPackage; + } + + std::vector> GetAvailable() override + { + return m_availablePackages; + } + + const std::vector>& GetAvailablePackages() + { + return m_availablePackages; + } + + bool IsSameAsAnyAvailable(const IPackage* other) const + { + if (other) + { + for (const auto& availablePackage : m_availablePackages) + { + if (other->IsSame(availablePackage.get())) + { + return true; + } + } + } + + return false; + } + + const std::shared_ptr& GetInstalledPackage() const + { + return m_installedPackage; + } + + bool ContainsInstalledPackage(const IPackage* installedPackage) const + { + return m_installedPackage ? m_installedPackage->ContainsInstalledPackage(installedPackage) : false; + } + + void AddAvailablePackage(const std::shared_ptr& availablePackage, bool setPrimary = false) + { + if (availablePackage) + { + m_availablePackages.emplace_back(OnlyAvailable(availablePackage)); + + if (setPrimary) + { + m_primaryAvailablePackage = m_availablePackages.back(); + } + + // Set override for primary or with the first available version found + if (setPrimary || m_availablePackages.size() == 1) + { + TrySetOverrideInstalledVersion(m_availablePackages.back()); + } + } + } + + std::shared_ptr& GetPrimaryAvailablePackage() + { + return m_primaryAvailablePackage; + } + + Source GetTrackingSource() const + { + return m_installedPackage ? m_installedPackage->GetTrackingSource() : Source{}; + } + + std::shared_ptr GetTrackingPackage() const + { + return m_installedPackage ? m_installedPackage->GetTrackingPackage() : std::shared_ptr{}; + } + + std::chrono::system_clock::time_point GetTrackingPackageWriteTime() const + { + return m_installedPackage ? m_installedPackage->GetTrackingPackageWriteTime() : std::chrono::system_clock::time_point::min(); + } + + void SetTracking( + Source trackingSource, + std::shared_ptr trackingPackage, + std::chrono::system_clock::time_point trackingWriteTime) + { + if (m_installedPackage) + { + m_installedPackage->SetTracking(std::move(trackingSource), std::move(trackingPackage), trackingWriteTime); + } + } + + void FoldInstalledIn(const std::shared_ptr& other) + { + if (other->m_installedPackage) + { + if (m_installedPackage) + { + m_installedPackage->FoldInstalledIn(other->m_installedPackage); + } + else + { + m_installedPackage = other->m_installedPackage; + } + } + } + + private: + // Try to set a version that will override the version string from the installed package + void TrySetOverrideInstalledVersion(const std::shared_ptr& availablePackage) + { + if (m_installedPackage && availablePackage) + { + m_installedPackage->SetOverrideInstalledVersion(availablePackage); + } + } + + std::shared_ptr m_installedPackage; + std::shared_ptr m_primaryAvailablePackage; + std::vector> m_availablePackages; + }; + + // The comparator compares the ResultMatch by MatchType first, then Field in a predefined order. + struct ResultMatchComparator + { + template + bool operator() ( + const U& match1, + const V& match2) + { + if (match1.MatchCriteria.Type != match2.MatchCriteria.Type) + { + return match1.MatchCriteria.Type < match2.MatchCriteria.Type; + } + + if (match1.MatchCriteria.Field != match2.MatchCriteria.Field) + { + return match1.MatchCriteria.Field < match2.MatchCriteria.Field; + } + + return false; + } + }; + + template + void SortResultMatches(std::vector& matches) + { + std::stable_sort(matches.begin(), matches.end(), ResultMatchComparator()); + } + + // A copy of the standard match that holds a CompositePackage instead. + struct CompositeResultMatch + { + std::shared_ptr Package; + PackageMatchFilter MatchCriteria; + + CompositeResultMatch(std::shared_ptr p, PackageMatchFilter f) : Package(std::move(p)), MatchCriteria(std::move(f)) {} + }; + + // Stores data to enable correlation between installed and available packages. + struct CompositeResult + { + // A system reference string. + struct SystemReferenceString + { + SystemReferenceString(PackageMatchField field, Utility::LocIndString string) : + Field(field), String1(Utility::FoldCase(string)) {} + + SystemReferenceString(PackageMatchField field, Utility::LocIndString string1, Utility::LocIndString string2) : + Field(field), String1(Utility::FoldCase(string1)), String2(Utility::FoldCase(string2)) {} + + bool operator<(const SystemReferenceString& other) const + { + if (Field != other.Field) + { + return Field < other.Field; + } + + if (String1 != other.String1) + { + return String1 < other.String1; + } + + return String2 < other.String2; + } + + bool operator==(const SystemReferenceString& other) const + { + return Field == other.Field && String1 == other.String1 && String2 == other.String2; + } + + void AddToFilters( + std::vector& filters) const + { + switch (Field) + { + case PackageMatchField::NormalizedNameAndPublisher: + filters.emplace_back(PackageMatchFilter(Field, MatchType::Exact, String1.get(), String2.get())); + break; + + default: + filters.emplace_back(PackageMatchFilter(Field, MatchType::Exact, String1.get())); + } + } + + private: + PackageMatchField Field; + Utility::LocIndString String1; + Utility::LocIndString String2; + }; + + // Data relevant to correlation for a package. + struct PackageData + { + std::set SystemReferenceStrings; + + void AddIfNotPresent(SystemReferenceString&& srs) + { + if (SystemReferenceStrings.find(srs) == SystemReferenceStrings.end()) + { + SystemReferenceStrings.emplace(std::move(srs)); + } + } + + SearchRequest CreateInclusionsSearchRequest(SearchPurpose searchPurpose) const + { + SearchRequest result; + for (const auto& srs : SystemReferenceStrings) + { + srs.AddToFilters(result.Inclusions); + } + result.Purpose = searchPurpose; + return result; + } + + std::shared_ptr AddSystemReferenceStringsFromTrackingPackage(const PackageTrackingCatalog& trackingCatalog, const Utility::LocIndString& identifier, std::string_view sourceIdentifier) + { + SearchRequest trackingRequest; + trackingRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, identifier.get()); + + SearchResult trackingResult = trackingCatalog.Search(trackingRequest); + std::shared_ptr result; + + if (trackingResult.Matches.size() == 1) + { + result = OnlyAvailable(trackingResult.Matches[0].Package); + AddSystemReferenceStrings(result.get()); + } + else if (trackingResult.Matches.size() > 1) + { + AICLI_LOG(Repo, Warning, << "Found " << trackingResult.Matches.size() << " results for Id [" << identifier << "] in tracking catalog for: " << sourceIdentifier); + } + + return result; + } + + void AddSystemReferenceStrings(IPackage* package) + { + GetSystemReferenceStrings( + package, + PackageMultiProperty::PackageFamilyName, + PackageMatchField::PackageFamilyName); + + GetSystemReferenceStrings( + package, + PackageMultiProperty::ProductCode, + PackageMatchField::ProductCode); + + GetSystemReferenceStrings( + package, + PackageMultiProperty::UpgradeCode, + PackageMatchField::UpgradeCode); + + GetNameAndPublisher( + package); + } + + void AddSystemReferenceStringsFromManifest(const Manifest::Manifest& manifest) + { + for (const auto& pfn : manifest.GetPackageFamilyNames()) + { + AddIfNotPresent(SystemReferenceString{ PackageMatchField::PackageFamilyName, Utility::LocIndString{ pfn } }); + } + for (const auto& productCode : manifest.GetProductCodes()) + { + AddIfNotPresent(SystemReferenceString{ PackageMatchField::ProductCode, Utility::LocIndString{ productCode } }); + } + for (const auto& upgradeCode : manifest.GetUpgradeCodes()) + { + AddIfNotPresent(SystemReferenceString{ PackageMatchField::UpgradeCode, Utility::LocIndString{ upgradeCode } }); + } + for (const auto& name : manifest.GetPackageNames()) + { + for (const auto& publisher : manifest.GetPublishers()) + { + AddIfNotPresent(SystemReferenceString{ + PackageMatchField::NormalizedNameAndPublisher, + Utility::LocIndString{ name }, + Utility::LocIndString{ publisher } }); + } + } + } + + private: + void GetSystemReferenceStrings( + IPackage* package, + PackageMultiProperty prop, + PackageMatchField field) + { + for (auto&& string : package->GetMultiProperty(prop)) + { + AddIfNotPresent(SystemReferenceString{ field, std::move(string) }); + } + } + + void GetNameAndPublisher( + IPackage* package) + { + // Unfortunately the names and publishers are unique and not tied to each other strictly, so we need + // to go broad on the matches. Future work can hopefully make name and publisher operate more as a unit, + // but for now we have to search for the cartesian of these... + auto names = package->GetMultiProperty(PackageMultiProperty::NormalizedName); + auto publishers = package->GetMultiProperty(PackageMultiProperty::NormalizedPublisher); + + for (const auto& name : names) + { + for (const auto& publisher : publishers) + { + AddIfNotPresent(SystemReferenceString{ + PackageMatchField::NormalizedNameAndPublisher, + name, + publisher }); + } + } + } + }; + + // For a given package, prepares the results for it. + PackageData GetSystemReferenceStrings(IPackage* package) + { + PackageData result; + result.AddSystemReferenceStrings(package); + return result; + } + + // Check for a package already in the result that should have been correlated already. + // If we find one, see if we should upgrade it's match criteria. + // If we don't, return package data for further use. + // downloadManifests: when creating system reference strings, also download manifests to get more data. + std::optional CheckForExistingResultFromAvailablePackageMatch(const ResultMatch& availableMatch, bool downloadManifests) + { + std::shared_ptr availablePackage = OnlyAvailable(availableMatch.Package); + + for (auto& match : Matches) + { + if (match.Package->IsSameAsAnyAvailable(availablePackage.get())) + { + if (ResultMatchComparator{}(availableMatch, match)) + { + match.MatchCriteria = availableMatch.MatchCriteria; + } + + return {}; + } + } + + PackageData result; + result.AddSystemReferenceStrings(availablePackage.get()); + + if (downloadManifests) + { + constexpr int c_downloadManifestsLimit = 3; + int manifestsDownloaded = 0; + for (auto const& versionKey : availablePackage->GetVersionKeys()) + { + auto packageVersion = availablePackage->GetVersion(versionKey); + + auto manifest = packageVersion->GetManifest(); + result.AddSystemReferenceStringsFromManifest(manifest); + manifestsDownloaded++; + + if (manifestsDownloaded >= c_downloadManifestsLimit) + { + break; + } + } + } + + return result; + } + + // Determines if the results contain the given installed package. + bool ContainsInstalledPackage(const IPackage* installedPackage) const + { + for (auto& match : Matches) + { + if (match.Package->ContainsInstalledPackage(installedPackage)) + { + return true; + } + } + + return false; + } + + // Determines if the results contain the given installed package. + std::shared_ptr FindInstalledPackage(const IPackage* installedPackage) const + { + for (auto& match : Matches) + { + if (match.Package->ContainsInstalledPackage(installedPackage)) + { + return match.Package; + } + } + + return {}; + } + + // *Destructively* converts the result to the standard variant. + SearchResult ConvertToSearchResult() + { + FoldResults(); + + SearchResult result; + + result.Matches.reserve(Matches.size()); + for (auto& match : Matches) + { + result.Matches.emplace_back(std::move(match.Package), std::move(match.MatchCriteria)); + } + + result.Truncated = Truncated; + + result.Failures = std::move(Failures); + + return result; + } + + bool AddFailureIfSourceNotPresent(SearchResult::Failure&& failure) + { + auto itr = std::find_if(Failures.begin(), Failures.end(), + [&failure](const SearchResult::Failure& present) { + return present.SourceName == failure.SourceName; + }); + + if (itr == Failures.end()) + { + Failures.emplace_back(std::move(failure)); + return true; + } + + return false; + } + + SearchResult SearchAndHandleFailures(const Source& source, const SearchRequest& request) + { + SearchResult result; + + try + { + result = source.Search(request); + } + catch (...) + { + if (AddFailureIfSourceNotPresent({ source.GetDetails().Name, std::current_exception() })) + { + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Warning, << "Failed to search source for correlation: " << source.GetDetails().Name); + } + } + + // Move failures into the result + for (SearchResult::Failure& failure : result.Failures) + { + AddFailureIfSourceNotPresent(std::move(failure)); + } + + return result; + } + + // Group results in an attempt to have a single result that covers all installed versions. + // This is expected to be called immediately after the installed search portion, + // when each result will contain a single installed version and some number of available packages. + // + // The folds that happen are: + // 1. When results have the same primary available package (the primary available package is set due to tracking data) + // 2. When a result has no primary available package, but another result does have a primary that matches one of the available + // a. Choose the latest primary if there are multiple + // 3. When multiple results have no primary available package and share the same available package set + // a. There are many potential additional rules that could be made here, but we will start with the simplest version. + // + // Potential improvements: + // 1. Attempting correlation of non-primary available packages to allow folding in more complex cases + // a. For example, if installed A has {source1:package1, source2:package2} and installed B has {source1:package1}, can we + // make sure that source1:package1 and source2:package2 are in fact "the same" to confidently say that installed A and B + // are side by side versions. + // 2. Attempt correlation by installed data only + // a. We can potentially detect multiple instances of the same installed item with the same correlation logic turned back on + // the installed source. This would allow for folding even when the package is not in any available source. + void FoldResults() + { + // The key to uniquely identify the package in the map + struct InstalledResultFoldKey + { + InstalledResultFoldKey() = default; + + InstalledResultFoldKey(const std::shared_ptr& package) + { + std::shared_ptr latestAvailable = package->GetLatestVersion(); + if (latestAvailable) + { + SourceIdentifier = latestAvailable->GetSource().GetIdentifier(); + PackageIdentifier = latestAvailable->GetProperty(PackageVersionProperty::Id); + } + } + + // Hash operation + size_t operator()(const InstalledResultFoldKey& value) const noexcept + { + std::hash hashString; + return hashString(value.SourceIdentifier) ^ (hashString(value.PackageIdentifier) << 1); + } + + bool operator==(const InstalledResultFoldKey& other) const noexcept + { + // Treat both empty as invalid and never equal + if (SourceIdentifier.empty() && PackageIdentifier.empty()) + { + return false; + } + + return SourceIdentifier == other.SourceIdentifier && PackageIdentifier == other.PackageIdentifier; + } + + std::string SourceIdentifier; + std::string PackageIdentifier; + }; + + // The data for a package in the map + struct InstalledResultFoldData + { + InstalledResultFoldData() = default; + explicit InstalledResultFoldData(size_t primaryPackageIndex) : PrimaryPackageIndex(primaryPackageIndex) {} + + std::optional PrimaryPackageIndex; + std::vector NonPrimaryPackageIndices; + }; + + std::unordered_map foldData; + + // Attempt to fold all primary package matches first. + // Packages without primaries will still be indexed into the hash table. + for (size_t i = 0; i < Matches.size(); ++i) + { + CompositeResultMatch& currentMatch = Matches[i]; + + // Check current match for fold target + if (currentMatch.Package->GetPrimaryAvailablePackage()) + { + InstalledResultFoldKey key{ currentMatch.Package->GetPrimaryAvailablePackage() }; + + auto itr = foldData.find(key); + if (itr != foldData.end()) + { + if (itr->second.PrimaryPackageIndex) + { + Matches[itr->second.PrimaryPackageIndex.value()].Package->FoldInstalledIn(currentMatch.Package); + currentMatch.Package.reset(); + } + else + { + itr->second.PrimaryPackageIndex = i; + } + } + else + { + foldData[key] = InstalledResultFoldData{ i }; + } + } + else + { + for (const auto& availablePackage : currentMatch.Package->GetAvailablePackages()) + { + InstalledResultFoldKey key{ availablePackage }; + + auto itr = foldData.find(key); + if (itr == foldData.end()) + { + itr = foldData.insert({ key, {} }).first; + } + + itr->second.NonPrimaryPackageIndices.emplace_back(i); + } + } + } + + // After primary matches are folded, attempt to fold results without primary matches. + // The latest primary match will be preferred. + for (size_t i = 0; i < Matches.size(); ++i) + { + CompositeResultMatch& currentMatch = Matches[i]; + + // Skip any matches that we have already folded + if (!currentMatch.Package) + { + continue; + } + + if (!currentMatch.Package->GetPrimaryAvailablePackage()) + { + InstalledResultFoldData* latestPrimaryAvailable = nullptr; + std::vector availableFoldData; + + for (const auto& availablePackage : currentMatch.Package->GetAvailablePackages()) + { + auto& packageFoldData = foldData.at(availablePackage); + + if (packageFoldData.PrimaryPackageIndex) + { + if (!latestPrimaryAvailable || + Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package->GetTrackingPackageWriteTime() < Matches[packageFoldData.PrimaryPackageIndex.value()].Package->GetTrackingPackageWriteTime()) + { + latestPrimaryAvailable = &packageFoldData; + } + } + else + { + availableFoldData.emplace_back(&packageFoldData); + } + } + + if (latestPrimaryAvailable) + { + Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package->FoldInstalledIn(currentMatch.Package); + currentMatch.Package.reset(); + + // If the result with the primary is later, move it forward + if (latestPrimaryAvailable->PrimaryPackageIndex.value() > i) + { + currentMatch.Package = std::move(Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package); + Matches[latestPrimaryAvailable->PrimaryPackageIndex.value()].Package.reset(); + latestPrimaryAvailable->PrimaryPackageIndex = i; + } + continue; + } + + // First, find the intersection of all results that contain all of the packages from this result. + std::vector candidateMatches; + for (size_t j = 0; j < availableFoldData.size(); ++j) + { + InstalledResultFoldData* packageFoldData = availableFoldData[j]; + + if (j == 0) + { + candidateMatches = packageFoldData->NonPrimaryPackageIndices; + } + else + { + std::vector temp; + std::set_intersection( + candidateMatches.begin(), candidateMatches.end(), + packageFoldData->NonPrimaryPackageIndices.begin(), packageFoldData->NonPrimaryPackageIndices.end(), + std::back_inserter(temp)); + candidateMatches = std::move(temp); + } + } + + // Now exclude both our own result and any that have a different (larger) number of available packages + candidateMatches.erase(std::remove_if(candidateMatches.begin(), candidateMatches.end(), + [&](size_t index) { return index == i || Matches[index].Package->GetAvailablePackages().size() != currentMatch.Package->GetAvailablePackages().size(); }), + candidateMatches.end()); + + // All of these remaining values should be folded in to our result + for (size_t foldTarget : candidateMatches) + { + currentMatch.Package->FoldInstalledIn(Matches[foldTarget].Package); + Matches[foldTarget].Package.reset(); + } + } + } + + // Get rid of the folded results; we reset the Package to indicate that it is no longer valid + Matches.erase(std::remove_if(Matches.begin(), Matches.end(), [&](const CompositeResultMatch& match) { return !match.Package; }), Matches.end()); + } + + std::vector Matches; + bool Truncated = false; + std::vector Failures; + }; + + std::shared_ptr GetTrackedPackageFromAvailableSource(CompositeResult& result, const Source& source, const Utility::LocIndString& identifier) + { + SearchRequest directRequest; + directRequest.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, identifier.get()); + + SearchResult directResult = result.SearchAndHandleFailures(source, directRequest); + + if (directResult.Matches.empty()) + { + AICLI_LOG(Repo, Warning, << "Did not find Id [" << identifier << "] in tracked source: " << source.GetDetails().Name); + } + else if (directResult.Matches.size() == 1) + { + return directResult.Matches[0].Package; + } + else + { + AICLI_LOG(Repo, Warning, << "Found multiple results for Id [" << identifier << "] in tracked source: " << source.GetDetails().Name); + } + + return {}; + } + } + + using namespace anon; + + CompositeSource::CompositeSource(std::string identifier) + { + m_details.Identifier = std::move(identifier); + } + + const SourceDetails& CompositeSource::GetDetails() const + { + return m_details; + } + + const std::string& CompositeSource::GetIdentifier() const + { + return m_details.Identifier; + } + + // The composite search needs to take several steps to get results, and due to the + // potential for different information spread across multiple sources, base searches + // need to be performed in both installed and available. + // + // If an installed source is present, then the searches should only return packages + // that are installed. This means that the base searches against available sources + // will only return results where a match is found in the installed source. + SearchResult CompositeSource::Search(const SearchRequest& request) const + { + if (m_installedSource) + { + return SearchInstalled(request); + } + else + { + return SearchAvailable(request); + } + } + + void* CompositeSource::CastTo(ISourceType type) + { + if (type == SourceType) + { + return this; + } + + return nullptr; + } + + void CompositeSource::AddAvailableSource(const Source& source) + { + m_availableSources.emplace_back(source); + } + + void CompositeSource::SetInstalledSource(Source source, CompositeSearchBehavior searchBehavior) + { + m_installedSource = std::move(source); + m_searchBehavior = searchBehavior; + } + + // An installed search first finds all installed packages that match the request, then correlates with available sources. + // Next the search is performed against the available sources and correlated with the installed source. A result will only + // be added if there exists an installed package that was not found by the initial search. + // This allows for search terms to find installed packages by their available metadata, as well as the local values. + // + // Search flow: + // Installed :: Search incoming request + // For each result + // For each available source + // Tracking :: Search system references + // If tracking found + // Available :: Search tracking ID + // If no available, for each available source + // Available :: Search system references + // + // For each available source + // Tracking :: Search incoming request + // For each result + // Installed :: Search system references + // If found + // Available :: Search tracking ID + // Available :: Search incoming request + // For each result + // Installed :: Search system references + SearchResult CompositeSource::SearchInstalled(const SearchRequest& request) const + { + CompositeResult result; + + // If the search behavior is for AllPackages or Installed then the result can contain packages that are + // only in the Installed source, but do not have an AvailableVersion. + if (m_searchBehavior == CompositeSearchBehavior::AllPackages || m_searchBehavior == CompositeSearchBehavior::Installed) + { + // Search installed source (allow exceptions out as we own the installed source) + SearchResult installedResult = m_installedSource.Search(request); + result.Truncated = installedResult.Truncated; + + for (auto&& match : installedResult.Matches) + { + if (!match.Package) + { + // Ensure that the crash from installedVersion below is not from the actual package being null. + AICLI_LOG(Repo, Warning, << "CompositeSource: The match of the package (matched on " << + ToString(match.MatchCriteria.Field) << " => '" << match.MatchCriteria.Value << + "') was null and is being dropped from the results."); + continue; + } + + std::shared_ptr compositePackage = std::make_shared(match.Package); + auto installedPackage = compositePackage->GetInstalled(); + + if (!installedPackage) + { + // One would think that the installed package coming directly from our own installed source + // would never be null, but it is sometimes. Rather than making users suffer through crashes + // that break their entire experience, lets log a few things and then ignore this match. + AICLI_LOG(Repo, Warning, << "CompositeSource: The installed version of the package '" << + match.Package->GetProperty(PackageProperty::Id) << "' was null and is being dropped from the results."); + continue; + } + + auto installedPackageData = result.GetSystemReferenceStrings(installedPackage.get()); + + // Create a search request to run against all available sources + if (!installedPackageData.SystemReferenceStrings.empty()) + { + SearchRequest systemReferenceSearch = installedPackageData.CreateInclusionsSearchRequest(SearchPurpose::CorrelationToAvailable); + AICLI_LOG(Repo, Verbose, << "Finding available package from installed package using system reference search: " << systemReferenceSearch.ToString()); + + // Search sources and add to result + for (const auto& source : m_availableSources) + { + AICLI_LOG(Repo, Verbose, << " ... searching source: " << source.GetDetails().Name << " [" << source.GetIdentifier() << ']'); + + // Find the tracking result with the latest timestamp. + auto trackingCatalog = source.GetTrackingCatalog(); + SearchResult trackingResult = trackingCatalog.Search(systemReferenceSearch); + + std::shared_ptr trackingPackage; + std::chrono::system_clock::time_point trackingPackageTime; + bool trackingSet = false; + + for (const auto& trackingMatch : trackingResult.Matches) + { + auto candidateTime = GetLatestTrackingWriteTime(OnlyAvailable(trackingMatch.Package)); + + if (!trackingPackage || candidateTime > trackingPackageTime) + { + trackingPackage = OnlyAvailable(trackingMatch.Package); + trackingPackageTime = candidateTime; + } + } + + if (trackingPackage && trackingPackageTime > compositePackage->GetTrackingPackageWriteTime()) + { + AICLI_LOG(Repo, Verbose, << " ... setting latest tracking package to: " << trackingPackage->GetProperty(PackageProperty::Id)); + compositePackage->SetTracking(source, trackingPackage, trackingPackageTime); + trackingSet = true; + } + + // Attempt to correlate local packages against this source if supported. + SearchResult availableResult; + if (source.GetDetails().SupportInstalledSearchCorrelation) + { + availableResult = result.SearchAndHandleFailures(source, systemReferenceSearch); + } + + auto availablePackage = GetMatchingPackage(availableResult.Matches, + [&]() { + AICLI_LOG(Repo, Info, + << "Found multiple matches for installed package [" << installedPackage->GetProperty(PackageProperty::Id) << + "] in source [" << source.GetIdentifier() << "] when searching for [" << systemReferenceSearch.ToString() << "]"); + }, [&] { + AICLI_LOG(Repo, Warning, << " Appropriate available package could not be determined"); + }); + + if (trackingPackage) + { + auto trackingIdentifier = trackingPackage->GetProperty(PackageProperty::Id); + + // We always want to take the available search result if it exists as the package may have been updated. + if (availablePackage) + { + auto availableIdentifier = availablePackage->GetProperty(PackageProperty::Id); + if (!Utility::ICUCaseInsensitiveEquals(availableIdentifier, trackingIdentifier)) + { + AICLI_LOG(Repo, Verbose, << " ... overriding tracking package (" << trackingIdentifier << ") with available package (" << availableIdentifier << ")"); + } + } + else + { + AICLI_LOG(Repo, Verbose, << " ... using tracking package: " << trackingIdentifier); + availablePackage = GetTrackedPackageFromAvailableSource(result, source, trackingIdentifier); + } + } + + if (availablePackage) + { + AICLI_LOG(Repo, Verbose, << " ... adding available package: " << availablePackage->GetProperty(PackageProperty::Id)); + compositePackage->AddAvailablePackage(availablePackage, trackingSet); + } + } + } + + // Move the installed result into the composite result + result.Matches.emplace_back(std::move(compositePackage), std::move(match.MatchCriteria)); + } + + // Optimization for the "everything installed" case, no need to allow for reverse correlations + if (request.IsForEverything() && m_searchBehavior == CompositeSearchBehavior::Installed) + { + return result.ConvertToSearchResult(); + } + } + + // Search available sources + for (const auto& source : m_availableSources) + { + auto trackingCatalog = source.GetTrackingCatalog(); + + SearchResult availableResult = result.SearchAndHandleFailures(source, request); + bool downloadManifests = source.QueryFeatureFlag(SourceFeatureFlag::ManifestMayContainAdditionalSystemReferenceStrings); + + for (auto&& match : availableResult.Matches) + { + // Check for the package already in the result. + // In cases that PackageData will be created, also download manifests for system reference strings + // when search result is small (currently limiting to 1). + auto packageData = result.CheckForExistingResultFromAvailablePackageMatch(match, downloadManifests && availableResult.Matches.size() == 1); + + // If found existing package in the result, continue + if (!packageData) + { + continue; + } + + // Use data from the tracking catalog as it can potentially get better correlations + auto trackingPackage = packageData->AddSystemReferenceStringsFromTrackingPackage(trackingCatalog, match.Package->GetProperty(PackageProperty::Id), source.GetDetails().Name); + + // If no package was found that was already in the results, do a correlation lookup with the installed + // source to create a new composite package entry if we find any packages there. + bool foundInstalledMatch = false; + if (!packageData->SystemReferenceStrings.empty()) + { + // Create a search request to run against the installed source + SearchRequest systemReferenceSearch = packageData->CreateInclusionsSearchRequest(SearchPurpose::CorrelationToInstalled); + + AICLI_LOG(Repo, Verbose, << "Finding installed package from available package using system reference search: " << systemReferenceSearch.ToString()); + // Correlate against installed (allow exceptions out as we own the installed source) + SearchResult installedCrossRef = m_installedSource.Search(systemReferenceSearch); + + for (const auto& installedMatch : installedCrossRef.Matches) + { + if (!IsStrongMatchField(installedMatch.MatchCriteria.Field)) + { + // For weak correlations, do an installed -> available check to ensure that there are no other strong correlations. + SearchResult correlationConfirmation; + if (source.GetDetails().SupportInstalledSearchCorrelation) + { + correlationConfirmation = result.SearchAndHandleFailures(source, result.GetSystemReferenceStrings(installedMatch.Package->GetInstalled().get()).CreateInclusionsSearchRequest(SearchPurpose::CorrelationToAvailable)); + } + + if (correlationConfirmation.Matches.empty()) + { + // We probably made the correlation due to tracking data, keep it. + } + else if (correlationConfirmation.Matches.size() > 1) + { + // There is contention for the correlation. + AICLI_LOG(Repo, Verbose, << " ... installed package [" << installedMatch.Package->GetProperty(PackageProperty::Id) << + "] had multiple correlations and is being ignored as a match for [" << match.Package->GetProperty(PackageProperty::Id) << "]"); + continue; + } + else if (!OnlyAvailable(correlationConfirmation.Matches[0].Package)->IsSame(OnlyAvailable(match.Package).get())) + { + // The only correlation is not to the current package. + AICLI_LOG(Repo, Verbose, << " ... installed package [" << installedMatch.Package->GetProperty(PackageProperty::Id) << + "] was found through available package [" << match.Package->GetProperty(PackageProperty::Id) << "], but only correlated to [" << + correlationConfirmation.Matches[0].Package->GetProperty(PackageProperty::Id) << "] and is being ignored"); + continue; + } + } + + // Now that we know we need to add this available package, determine how exactly + std::shared_ptr resultPackage = result.FindInstalledPackage(installedMatch.Package->GetInstalled().get()); + + if (resultPackage) + { + // Check for a package from the same source already present on the result package. + bool foundSameSource = false; + + for (const auto& availablePackage : resultPackage->GetAvailablePackages()) + { + if (availablePackage->GetSource() == source) + { + // TODO: May need to add more data so that we can choose the proper correlation, but it may also be very difficult to get through + // the gauntlet of other checks and arrive in this situation. + AICLI_LOG(Repo, Verbose, << " ... found [" << availablePackage->GetProperty(PackageProperty::Id) << + "] already correlated to [" << installedMatch.Package->GetProperty(PackageProperty::Id) << "] from the same source [" << + source.GetDetails().Name << "] as [" << match.Package->GetProperty(PackageProperty::Id) << "]; ignoring the second correlation"); + foundSameSource = true; + } + } + + if (foundSameSource) + { + continue; + } + } + else + { + result.Matches.emplace_back(std::make_shared(installedMatch.Package), match.MatchCriteria); + resultPackage = result.Matches.back().Package; + } + + bool setPrimary = false; + if (trackingPackage) + { + auto trackingPackageTime = GetLatestTrackingWriteTime(trackingPackage); + + if (trackingPackageTime > resultPackage->GetTrackingPackageWriteTime()) + { + resultPackage->SetTracking(source, std::move(trackingPackage), trackingPackageTime); + setPrimary = true; + } + } + + resultPackage->AddAvailablePackage(std::move(match.Package), setPrimary); + + foundInstalledMatch = true; + } + } + + // If there was no correlation for this package, add it without one. + if ((m_searchBehavior == CompositeSearchBehavior::AllPackages || m_searchBehavior == CompositeSearchBehavior::AvailablePackages) && !foundInstalledMatch) + { + result.Matches.emplace_back(std::make_shared(std::shared_ptr{}, std::move(match.Package)), match.MatchCriteria); + } + } + } + + SortResultMatches(result.Matches); + + if (request.MaximumResults > 0 && result.Matches.size() > request.MaximumResults) + { + result.Truncated = true; + result.Matches.erase(result.Matches.begin() + request.MaximumResults, result.Matches.end()); + } + + return result.ConvertToSearchResult(); + } + + // An available search goes through each source, searching individually and then sorting the full result set. + SearchResult CompositeSource::SearchAvailable(const SearchRequest& request) const + { + SearchResult result; + + // Search available sources + for (const auto& source : m_availableSources) + { + SearchResult oneSourceResult; + + try + { + oneSourceResult = source.Search(request); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Warning, << "Failed to search source: " << source.GetDetails().Name); + result.Failures.emplace_back(SearchResult::Failure{ source.GetDetails().Name, std::current_exception() }); + } + + // Move into the single result + std::move(oneSourceResult.Matches.begin(), oneSourceResult.Matches.end(), std::back_inserter(result.Matches)); + std::move(oneSourceResult.Failures.begin(), oneSourceResult.Failures.end(), std::back_inserter(result.Failures)); + } + + SortResultMatches(result.Matches); + + if (request.MaximumResults > 0 && result.Matches.size() > request.MaximumResults) + { + result.Truncated = true; + result.Matches.erase(result.Matches.begin() + request.MaximumResults, result.Matches.end()); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/CompositeSource.h b/src/AppInstallerRepositoryCore/CompositeSource.h index 715ecd3b0e..fb68ac021d 100644 --- a/src/AppInstallerRepositoryCore/CompositeSource.h +++ b/src/AppInstallerRepositoryCore/CompositeSource.h @@ -1,64 +1,64 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" - -namespace AppInstaller::Repository -{ - struct CompositeSource : public ISource - { - static constexpr ISourceType SourceType = ISourceType::CompositeSource; - - explicit CompositeSource(std::string identifier); - - CompositeSource(const CompositeSource&) = delete; - CompositeSource& operator=(const CompositeSource&) = delete; - - CompositeSource(CompositeSource&&) = default; - CompositeSource& operator=(CompositeSource&&) = default; - - ~CompositeSource() = default; - - // ISource - - // Get the source's details. - const SourceDetails& GetDetails() const override; - - // Gets the source's identifier; a unique identifier independent of the name - // that will not change between a remove/add or between additional adds. - // Must be suitable for filesystem names. - const std::string& GetIdentifier() const override; - - // Execute a search on the source. - SearchResult Search(const SearchRequest& request) const override; - - // Casts to the requested type. - void* CastTo(ISourceType type) override; - - // ~ISource - - // Adds an available source to be aggregated. - void AddAvailableSource(const Source& source); - - // Gets the available sources if the source is composite. - std::vector GetAvailableSources() const { return m_availableSources; } - - // Checks if any available sources are present - bool HasAvailableSource() const { return !m_availableSources.empty(); } - - // Sets the installed source to be composited. - void SetInstalledSource(Source source, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior::Installed); - - private: - // Performs a search when an installed source is present. - SearchResult SearchInstalled(const SearchRequest& request) const; - - // Performs a search when no installed source is present. - SearchResult SearchAvailable(const SearchRequest& request) const; - - Source m_installedSource; - std::vector m_availableSources; - SourceDetails m_details; - CompositeSearchBehavior m_searchBehavior = CompositeSearchBehavior::Installed; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" + +namespace AppInstaller::Repository +{ + struct CompositeSource : public ISource + { + static constexpr ISourceType SourceType = ISourceType::CompositeSource; + + explicit CompositeSource(std::string identifier); + + CompositeSource(const CompositeSource&) = delete; + CompositeSource& operator=(const CompositeSource&) = delete; + + CompositeSource(CompositeSource&&) = default; + CompositeSource& operator=(CompositeSource&&) = default; + + ~CompositeSource() = default; + + // ISource + + // Get the source's details. + const SourceDetails& GetDetails() const override; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names. + const std::string& GetIdentifier() const override; + + // Execute a search on the source. + SearchResult Search(const SearchRequest& request) const override; + + // Casts to the requested type. + void* CastTo(ISourceType type) override; + + // ~ISource + + // Adds an available source to be aggregated. + void AddAvailableSource(const Source& source); + + // Gets the available sources if the source is composite. + std::vector GetAvailableSources() const { return m_availableSources; } + + // Checks if any available sources are present + bool HasAvailableSource() const { return !m_availableSources.empty(); } + + // Sets the installed source to be composited. + void SetInstalledSource(Source source, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior::Installed); + + private: + // Performs a search when an installed source is present. + SearchResult SearchInstalled(const SearchRequest& request) const; + + // Performs a search when no installed source is present. + SearchResult SearchAvailable(const SearchRequest& request) const; + + Source m_installedSource; + std::vector m_availableSources; + SourceDetails m_details; + CompositeSearchBehavior m_searchBehavior = CompositeSearchBehavior::Installed; + }; +} diff --git a/src/AppInstallerRepositoryCore/ISource.h b/src/AppInstallerRepositoryCore/ISource.h index 9c511f7c1a..9878083fdd 100644 --- a/src/AppInstallerRepositoryCore/ISource.h +++ b/src/AppInstallerRepositoryCore/ISource.h @@ -1,123 +1,123 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Public/winget/RepositorySource.h" -#include -#include -#include - -namespace AppInstaller::Repository -{ - // To allow for runtime casting from ISource to the specific types, this enum contains all of the ISource implementations. - enum class ISourceType - { - TestSource, - ConfigurableTestSource, - RestSource, - SQLiteIndexSource, - CompositeSource, - IMutablePackageSource, - OpenExceptionProxy, - }; - - // Internal interface for interacting with a source from outside of the repository lib. - struct ISource - { - virtual ~ISource() = default; - - // Gets the source's identifier; a unique identifier independent of the name - // that will not change between a remove/add or between additional adds. - // Must be suitable for filesystem names unless the source is internal to winget, - // in which case the identifier should begin with a '*' character. - virtual const std::string& GetIdentifier() const = 0; - - // Get the source's configuration from settings. Source details can be used during opening the source. - virtual const SourceDetails& GetDetails() const = 0; - - // Get the source's information after the source is opened. - virtual SourceInformation GetInformation() const { return {}; }; - - // Query the value of the given feature flag. - // The default state of any new flag is false. - virtual bool QueryFeatureFlag(SourceFeatureFlag) const { return false; } - - // Execute a search on the source. - virtual SearchResult Search(const SearchRequest& request) const = 0; - - // Gets this object as the requested type, or null if it is not the requested type. - virtual void* CastTo(ISourceType type) = 0; - }; - - // Does the equivalent of a dynamic_pointer_cast, but without it to allow RTTI to be disabled. - template - std::shared_ptr SourceCast(const std::shared_ptr& source) - { - if (!source) - { - return {}; - } - - void* castResult = source->CastTo(SourceType::SourceType); - - if (!castResult) - { - return {}; - } - - return std::shared_ptr(source, reinterpret_cast(castResult)); - } - - // Internal interface to represent source information; basically SourceDetails but with methods to enable differential behaviors. - struct ISourceReference - { - virtual ~ISourceReference() = default; - - // Gets the source's identifier; a unique identifier independent of the name - // that will not change between a remove/add or between additional adds. - // Must be suitable for filesystem names unless the source is internal to winget, - // in which case the identifier should begin with a '*' character. - virtual std::string GetIdentifier() = 0; - - // Get the source's configuration details from settings. - virtual SourceDetails& GetDetails() = 0; - - // Get the source's information. - virtual SourceInformation GetInformation() { return {}; } - - // Set custom header. Returns false if custom header is not supported. - virtual bool SetCustomHeader(std::optional) { return false; } - - // Set caller. - virtual void SetCaller(std::string) {} - - // Set authentication arguments. - virtual void SetAuthenticationArguments(Authentication::AuthenticationArguments) {} - - // Set a custom server certificate validation callback. - // Return true from the callback to accept the connection, false to reject. - virtual void SetServerCertificateValidationCallback(std::function) {} - - // Determine if the source needs to be updated before being opened. - virtual bool ShouldUpdateBeforeOpen(const std::optional&) { return false; } - - // Opens the source. This function should throw upon open failure rather than returning an empty pointer. - virtual std::shared_ptr Open(IProgressCallback& progress) = 0; - - // Sets thread globals for the source. This allows the option for sources that operate on other threads to log properly. - virtual void SetThreadGlobals(const std::shared_ptr&) {} - }; - - // Internal interface extension to ISource for databases that can be updated after creation, like InstallingPackages - struct IMutablePackageSource - { - static constexpr AppInstaller::Repository::ISourceType SourceType = AppInstaller::Repository::ISourceType::IMutablePackageSource; - - virtual ~IMutablePackageSource() = default; - - // Adds a package version to the source. - virtual void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; - - // Removes a package version from the source. - virtual void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Public/winget/RepositorySource.h" +#include +#include +#include + +namespace AppInstaller::Repository +{ + // To allow for runtime casting from ISource to the specific types, this enum contains all of the ISource implementations. + enum class ISourceType + { + TestSource, + ConfigurableTestSource, + RestSource, + SQLiteIndexSource, + CompositeSource, + IMutablePackageSource, + OpenExceptionProxy, + }; + + // Internal interface for interacting with a source from outside of the repository lib. + struct ISource + { + virtual ~ISource() = default; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names unless the source is internal to winget, + // in which case the identifier should begin with a '*' character. + virtual const std::string& GetIdentifier() const = 0; + + // Get the source's configuration from settings. Source details can be used during opening the source. + virtual const SourceDetails& GetDetails() const = 0; + + // Get the source's information after the source is opened. + virtual SourceInformation GetInformation() const { return {}; }; + + // Query the value of the given feature flag. + // The default state of any new flag is false. + virtual bool QueryFeatureFlag(SourceFeatureFlag) const { return false; } + + // Execute a search on the source. + virtual SearchResult Search(const SearchRequest& request) const = 0; + + // Gets this object as the requested type, or null if it is not the requested type. + virtual void* CastTo(ISourceType type) = 0; + }; + + // Does the equivalent of a dynamic_pointer_cast, but without it to allow RTTI to be disabled. + template + std::shared_ptr SourceCast(const std::shared_ptr& source) + { + if (!source) + { + return {}; + } + + void* castResult = source->CastTo(SourceType::SourceType); + + if (!castResult) + { + return {}; + } + + return std::shared_ptr(source, reinterpret_cast(castResult)); + } + + // Internal interface to represent source information; basically SourceDetails but with methods to enable differential behaviors. + struct ISourceReference + { + virtual ~ISourceReference() = default; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names unless the source is internal to winget, + // in which case the identifier should begin with a '*' character. + virtual std::string GetIdentifier() = 0; + + // Get the source's configuration details from settings. + virtual SourceDetails& GetDetails() = 0; + + // Get the source's information. + virtual SourceInformation GetInformation() { return {}; } + + // Set custom header. Returns false if custom header is not supported. + virtual bool SetCustomHeader(std::optional) { return false; } + + // Set caller. + virtual void SetCaller(std::string) {} + + // Set authentication arguments. + virtual void SetAuthenticationArguments(Authentication::AuthenticationArguments) {} + + // Set a custom server certificate validation callback. + // Return true from the callback to accept the connection, false to reject. + virtual void SetServerCertificateValidationCallback(std::function) {} + + // Determine if the source needs to be updated before being opened. + virtual bool ShouldUpdateBeforeOpen(const std::optional&) { return false; } + + // Opens the source. This function should throw upon open failure rather than returning an empty pointer. + virtual std::shared_ptr Open(IProgressCallback& progress) = 0; + + // Sets thread globals for the source. This allows the option for sources that operate on other threads to log properly. + virtual void SetThreadGlobals(const std::shared_ptr&) {} + }; + + // Internal interface extension to ISource for databases that can be updated after creation, like InstallingPackages + struct IMutablePackageSource + { + static constexpr AppInstaller::Repository::ISourceType SourceType = AppInstaller::Repository::ISourceType::IMutablePackageSource; + + virtual ~IMutablePackageSource() = default; + + // Adds a package version to the source. + virtual void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; + + // Removes a package version from the source. + virtual void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) = 0; + }; +} diff --git a/src/AppInstallerRepositoryCore/IconDefs.h b/src/AppInstallerRepositoryCore/IconDefs.h index 05e8d0bd25..65a7144116 100644 --- a/src/AppInstallerRepositoryCore/IconDefs.h +++ b/src/AppInstallerRepositoryCore/IconDefs.h @@ -1,73 +1,73 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#pragma once - -// Icon file structure and definitions: -// https://msdn.microsoft.com/en-us/library/ms997538.aspx - -// .ico icon structures - -// Icon entry struct -typedef struct -{ - BYTE bWidth; // Width, in pixels, of the image - BYTE bHeight; // Height, in pixels, of the image - BYTE bColorCount; // Number of colors in image (0 if >=8bpp) - BYTE bReserved; // Reserved ( must be 0) - WORD wPlanes; // Color Planes - WORD wBitCount; // Bits per pixel - DWORD dwBytesInRes; // How many bytes in this resource? - DWORD dwImageOffset; // Where in the file is this image? -} ICONDIRENTRY, *LPICONDIRENTRY; - - -// Icon directory struct -typedef struct -{ - WORD idReserved; // Reserved (must be 0) - WORD idType; // Resource Type (1 for icons) - WORD idCount; // How many images? - ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em) -} ICONDIR, *LPICONDIR; - - -// Image struct -typedef struct -{ - BITMAPINFOHEADER icHeader; // DIB header - RGBQUAD icColors[1]; // Color table - BYTE icXOR[1]; // DIB bits for XOR mask - BYTE icAND[1]; // DIB bits for AND mask -} ICONIMAGE, * LPICONIMAGE; - - -// .exe and .dll icon structures - -// #pragmas are used here to insure that the structure's -// packing in memory matches the packing of the EXE or DLL. -#pragma pack( push ) -#pragma pack( 2 ) - -typedef struct -{ - BYTE bWidth; // Width, in pixels, of the image - BYTE bHeight; // Height, in pixels, of the image - BYTE bColorCount; // Number of colors in image (0 if >=8bpp) - BYTE bReserved; // Reserved - WORD wPlanes; // Color Planes - WORD wBitCount; // Bits per pixel - DWORD dwBytesInRes; // how many bytes in this resource? - WORD nID; // the ID -} GRPICONDIRENTRY, *LPGRPICONDIRENTRY; - - -typedef struct -{ - WORD idReserved; // Reserved (must be 0) - WORD idType; // Resource type (1 for icons) - WORD idCount; // How many images? - GRPICONDIRENTRY idEntries[1]; // The entries for each image -} GRPICONDIR, *LPGRPICONDIR; - -#pragma pack( pop ) +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#pragma once + +// Icon file structure and definitions: +// https://msdn.microsoft.com/en-us/library/ms997538.aspx + +// .ico icon structures + +// Icon entry struct +typedef struct +{ + BYTE bWidth; // Width, in pixels, of the image + BYTE bHeight; // Height, in pixels, of the image + BYTE bColorCount; // Number of colors in image (0 if >=8bpp) + BYTE bReserved; // Reserved ( must be 0) + WORD wPlanes; // Color Planes + WORD wBitCount; // Bits per pixel + DWORD dwBytesInRes; // How many bytes in this resource? + DWORD dwImageOffset; // Where in the file is this image? +} ICONDIRENTRY, *LPICONDIRENTRY; + + +// Icon directory struct +typedef struct +{ + WORD idReserved; // Reserved (must be 0) + WORD idType; // Resource Type (1 for icons) + WORD idCount; // How many images? + ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em) +} ICONDIR, *LPICONDIR; + + +// Image struct +typedef struct +{ + BITMAPINFOHEADER icHeader; // DIB header + RGBQUAD icColors[1]; // Color table + BYTE icXOR[1]; // DIB bits for XOR mask + BYTE icAND[1]; // DIB bits for AND mask +} ICONIMAGE, * LPICONIMAGE; + + +// .exe and .dll icon structures + +// #pragmas are used here to insure that the structure's +// packing in memory matches the packing of the EXE or DLL. +#pragma pack( push ) +#pragma pack( 2 ) + +typedef struct +{ + BYTE bWidth; // Width, in pixels, of the image + BYTE bHeight; // Height, in pixels, of the image + BYTE bColorCount; // Number of colors in image (0 if >=8bpp) + BYTE bReserved; // Reserved + WORD wPlanes; // Color Planes + WORD wBitCount; // Bits per pixel + DWORD dwBytesInRes; // how many bytes in this resource? + WORD nID; // the ID +} GRPICONDIRENTRY, *LPGRPICONDIRENTRY; + + +typedef struct +{ + WORD idReserved; // Reserved (must be 0) + WORD idType; // Resource type (1 for icons) + WORD idCount; // How many images? + GRPICONDIRENTRY idEntries[1]; // The entries for each image +} GRPICONDIR, *LPGRPICONDIR; + +#pragma pack( pop ) diff --git a/src/AppInstallerRepositoryCore/InstalledFilesCorrelation.cpp b/src/AppInstallerRepositoryCore/InstalledFilesCorrelation.cpp index 8278204afd..c396bc413c 100644 --- a/src/AppInstallerRepositoryCore/InstalledFilesCorrelation.cpp +++ b/src/AppInstallerRepositoryCore/InstalledFilesCorrelation.cpp @@ -1,322 +1,322 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/InstalledFilesCorrelation.h" -#include -#include - -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Utility; - -namespace AppInstaller::Repository::Correlation -{ - namespace - { - constexpr std::string_view s_ShellLinkFileExtension = ".lnk"sv; - const std::vector> s_CandidateInstallLocationRoots = - { - { Filesystem::GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%" }, - { Filesystem::GetKnownFolderPath(FOLDERID_ProgramFiles), "%PROGRAMFILES%" }, - { Filesystem::GetKnownFolderPath(FOLDERID_ProgramFilesX86), "%PROGRAMFILES(X86)%" }, - }; - - // Contains shell link info - struct ShellLinkFileInfo - { - std::filesystem::path Path; - std::string Args; - std::string DisplayName; - }; - - std::optional ParseShellLinkFile(const std::filesystem::path& linkFile) - { - try - { - AICLI_LOG(Repo, Info, << "Parsing link file at " << linkFile); - - ShellLinkFileInfo result; - - Microsoft::WRL::ComPtr shellLink; - THROW_IF_FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink))); - Microsoft::WRL::ComPtr persistFile; - THROW_IF_FAILED(shellLink.As(&persistFile)); - THROW_IF_FAILED(persistFile->Load(linkFile.wstring().c_str(), STGM_READ)); - THROW_IF_FAILED(shellLink->Resolve(nullptr, SLR_NO_UI | SLR_NOUPDATE | SLR_NOSEARCH | SLR_NOTRACK | SLR_NOLINKINFO)); - - { - // Parse Path from shell link - std::wstring buffer; - buffer.resize(MAX_PATH); - HRESULT hr = S_OK; - for (int retry = 0; retry < 5; retry++) - { - hr = shellLink->GetPath( - &buffer[0], - static_cast(buffer.size()), - nullptr, - 0 - ); - - if (SUCCEEDED(hr)) - { - buffer.erase(std::find(buffer.begin(), buffer.end(), L'\0'), buffer.end()); - result.Path = buffer; - break; - } - else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) - { - buffer.resize(buffer.size() * 2); - } - else - { - THROW_IF_FAILED(hr); - } - } - } - - { - // Parse arguments from shell link - std::wstring buffer; - buffer.resize(MAX_PATH); - HRESULT hr = S_OK; - for (int retry = 0; retry < 5; retry++) - { - hr = shellLink->GetArguments( - &buffer[0], - static_cast(buffer.size())); - - if (SUCCEEDED(hr)) - { - buffer.erase(std::find(buffer.begin(), buffer.end(), L'\0'), buffer.end()); - result.Args = Utility::ConvertToUTF8(buffer); - break; - } - else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) - { - buffer.resize(buffer.size() * 2); - } - else - { - THROW_IF_FAILED(hr); - } - } - } - - // Use shell link file name (minus extension) as display name. - result.DisplayName = linkFile.stem().u8string(); - - AICLI_LOG(Repo, Info, << "Link file parsed. Path: " << result.Path << " Args: " << result.Args << " DisplayName: " << result.DisplayName); - - return result; - } - catch (...) - { - AICLI_LOG(Repo, Error, << "Failed to parse link file at " << linkFile); - return {}; - } - } - - // Returns nullopt if path is not under base. - std::optional GetRelativePath(const std::filesystem::path& path, const std::filesystem::path& base) - { - auto canonicalPath = std::filesystem::weakly_canonical(path); - auto canonicalBase = std::filesystem::weakly_canonical(base); - - auto relativePath = std::filesystem::relative(canonicalPath, canonicalBase); - if (!relativePath.empty() && *relativePath.begin() != "." && *relativePath.begin() != "..") - { - return relativePath; - } - else - { - return {}; - } - } - - std::optional CheckOneInstallLocation(const std::filesystem::path& childFile, const std::filesystem::path& baseFolder) - { - auto relativePath = GetRelativePath(childFile, baseFolder); - if (relativePath) - { - // TODO: Here we assume the install location is the top directory of relative path. - auto installLocation = baseFolder / *relativePath->begin(); - if (std::filesystem::exists(installLocation) && std::filesystem::is_directory(installLocation)) - { - return installLocation; - } - } - - return {}; - } - - // If install location is not provided in arp entry, try LocalAppData folder and Program Files folders. - std::optional CheckInstallLocation(const std::filesystem::path& path) - { - for (auto const& entry : s_CandidateInstallLocationRoots) - { - auto installLocation = CheckOneInstallLocation(path, entry.first); - if (installLocation) - { - return installLocation; - } - } - - return {}; - } - - // TODO: basic heuristics to determine file type. - AppInstaller::Manifest::InstalledFileTypeEnum GetInstalledFileType(const ShellLinkFileInfo& linkInfo) - { - Manifest::InstalledFileTypeEnum result = Manifest::InstalledFileTypeEnum::Other; - - if (Utility::CaseInsensitiveContainsSubstring(linkInfo.Path.u8string(), "uninstall") || - Utility::CaseInsensitiveContainsSubstring(linkInfo.Path.u8string(), "unins000") || - Utility::CaseInsensitiveContainsSubstring(linkInfo.Args, "uninstall") || - Utility::CaseInsensitiveContainsSubstring(linkInfo.DisplayName, "uninstall")) - { - result = Manifest::InstalledFileTypeEnum::Uninstall; - } - else if (Utility::CaseInsensitiveEquals(linkInfo.Path.extension().u8string(), ".exe")) - { - result = Manifest::InstalledFileTypeEnum::Launch; - } - - return result; - } - - std::string GetUnexpandedInstallLocation(const std::filesystem::path& installLocation) - { - // Try to match the candidate install location roots first. - std::filesystem::path resultInstallLocation = installLocation; - for (auto const& entry : s_CandidateInstallLocationRoots) - { - if (Filesystem::ReplaceCommonPathPrefix(resultInstallLocation, entry.first, entry.second)) - { - return resultInstallLocation.u8string(); - } - } - - // Then try PathUnExpandEnvStrings OS api - std::wstring installLocationWString = installLocation.wstring(); - std::wstring buffer; - buffer.resize(installLocationWString.size() + 20); - if (PathUnExpandEnvStrings( - installLocationWString.c_str(), - &buffer[0], - static_cast(buffer.size()))) - { - buffer.resize(buffer.find(L'\0')); - return Utility::ConvertToUTF8(buffer); - } - - return resultInstallLocation.u8string(); - } - } - - InstalledFilesCorrelation::InstalledFilesCorrelation() - { - m_fileWatchers.emplace_back(Filesystem::GetKnownFolderPath(FOLDERID_CommonStartMenu), std::string{ s_ShellLinkFileExtension }); - m_fileWatchers.emplace_back(Filesystem::GetKnownFolderPath(FOLDERID_StartMenu), std::string{ s_ShellLinkFileExtension }); - } - - void InstalledFilesCorrelation::StartFileWatcher() - { - m_files.clear(); - - for (auto& watcher : m_fileWatchers) - { - watcher.Start(); - } - } - - void InstalledFilesCorrelation::StopFileWatcher() - { - for (auto& watcher : m_fileWatchers) - { - watcher.Stop(); - } - - for (auto& watcher : m_fileWatchers) - { - FileWatcherFiles files; - files.Folder = watcher.FolderPath(); - - for (auto const& file : watcher.Files()) - { - files.Files.emplace_back(file); - } - - m_files.emplace_back(std::move(files)); - } - } - - InstallationMetadata InstalledFilesCorrelation::CorrelateForNewlyInstalled( - const Manifest::Manifest&, - const std::string& arpInstallLocation) - { - InstallationMetadata result; - - std::filesystem::path installLocation; - // Use arp install location if provided - if (!arpInstallLocation.empty()) - { - installLocation = Filesystem::GetExpandedPath(arpInstallLocation); - } - - for (auto const& files : m_files) - { - for (auto const& file : files.Files) - { - // TODO: we only watch shell link files at the moment. - auto linkInfo = ParseShellLinkFile(files.Folder / file); - if (linkInfo) - { - auto installedFileType = GetInstalledFileType(linkInfo.value()); - - // Collect installed files metadata if exist - if (std::filesystem::exists(linkInfo->Path) && std::filesystem::is_regular_file(linkInfo->Path)) - { - if (installLocation.empty()) - { - // TODO: In most cases, installed files are under same folder, so use the first file to determine install location at the moment. - auto location = CheckInstallLocation(linkInfo->Path); - if (!location) - { - continue; - } - - installLocation = location.value(); - } - - auto relativePath = GetRelativePath(linkInfo->Path, installLocation); - if (relativePath) - { - AppInstaller::Manifest::InstalledFile fileEntry; - fileEntry.RelativeFilePath = relativePath->string(); - std::ifstream in{ linkInfo->Path, std::ifstream::binary }; - fileEntry.FileSha256 = Utility::SHA256::ComputeHash(in); - fileEntry.InvocationParameter = linkInfo->Args; - fileEntry.DisplayName = linkInfo->DisplayName; - fileEntry.FileType = installedFileType; - result.InstalledFiles.Files.emplace_back(std::move(fileEntry)); - } - } - - // Collect short cut paths - InstalledStartupLinkFile linkFile; - linkFile.RelativeFilePath = file.u8string(); - linkFile.FileType = installedFileType; - result.StartupLinkFiles.emplace_back(linkFile); - } - } - } - - if (!installLocation.empty()) - { - result.InstalledFiles.DefaultInstallLocation = GetUnexpandedInstallLocation(installLocation); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/InstalledFilesCorrelation.h" +#include +#include + +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Utility; + +namespace AppInstaller::Repository::Correlation +{ + namespace + { + constexpr std::string_view s_ShellLinkFileExtension = ".lnk"sv; + const std::vector> s_CandidateInstallLocationRoots = + { + { Filesystem::GetKnownFolderPath(FOLDERID_LocalAppData), "%LOCALAPPDATA%" }, + { Filesystem::GetKnownFolderPath(FOLDERID_ProgramFiles), "%PROGRAMFILES%" }, + { Filesystem::GetKnownFolderPath(FOLDERID_ProgramFilesX86), "%PROGRAMFILES(X86)%" }, + }; + + // Contains shell link info + struct ShellLinkFileInfo + { + std::filesystem::path Path; + std::string Args; + std::string DisplayName; + }; + + std::optional ParseShellLinkFile(const std::filesystem::path& linkFile) + { + try + { + AICLI_LOG(Repo, Info, << "Parsing link file at " << linkFile); + + ShellLinkFileInfo result; + + Microsoft::WRL::ComPtr shellLink; + THROW_IF_FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&shellLink))); + Microsoft::WRL::ComPtr persistFile; + THROW_IF_FAILED(shellLink.As(&persistFile)); + THROW_IF_FAILED(persistFile->Load(linkFile.wstring().c_str(), STGM_READ)); + THROW_IF_FAILED(shellLink->Resolve(nullptr, SLR_NO_UI | SLR_NOUPDATE | SLR_NOSEARCH | SLR_NOTRACK | SLR_NOLINKINFO)); + + { + // Parse Path from shell link + std::wstring buffer; + buffer.resize(MAX_PATH); + HRESULT hr = S_OK; + for (int retry = 0; retry < 5; retry++) + { + hr = shellLink->GetPath( + &buffer[0], + static_cast(buffer.size()), + nullptr, + 0 + ); + + if (SUCCEEDED(hr)) + { + buffer.erase(std::find(buffer.begin(), buffer.end(), L'\0'), buffer.end()); + result.Path = buffer; + break; + } + else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + buffer.resize(buffer.size() * 2); + } + else + { + THROW_IF_FAILED(hr); + } + } + } + + { + // Parse arguments from shell link + std::wstring buffer; + buffer.resize(MAX_PATH); + HRESULT hr = S_OK; + for (int retry = 0; retry < 5; retry++) + { + hr = shellLink->GetArguments( + &buffer[0], + static_cast(buffer.size())); + + if (SUCCEEDED(hr)) + { + buffer.erase(std::find(buffer.begin(), buffer.end(), L'\0'), buffer.end()); + result.Args = Utility::ConvertToUTF8(buffer); + break; + } + else if (hr == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)) + { + buffer.resize(buffer.size() * 2); + } + else + { + THROW_IF_FAILED(hr); + } + } + } + + // Use shell link file name (minus extension) as display name. + result.DisplayName = linkFile.stem().u8string(); + + AICLI_LOG(Repo, Info, << "Link file parsed. Path: " << result.Path << " Args: " << result.Args << " DisplayName: " << result.DisplayName); + + return result; + } + catch (...) + { + AICLI_LOG(Repo, Error, << "Failed to parse link file at " << linkFile); + return {}; + } + } + + // Returns nullopt if path is not under base. + std::optional GetRelativePath(const std::filesystem::path& path, const std::filesystem::path& base) + { + auto canonicalPath = std::filesystem::weakly_canonical(path); + auto canonicalBase = std::filesystem::weakly_canonical(base); + + auto relativePath = std::filesystem::relative(canonicalPath, canonicalBase); + if (!relativePath.empty() && *relativePath.begin() != "." && *relativePath.begin() != "..") + { + return relativePath; + } + else + { + return {}; + } + } + + std::optional CheckOneInstallLocation(const std::filesystem::path& childFile, const std::filesystem::path& baseFolder) + { + auto relativePath = GetRelativePath(childFile, baseFolder); + if (relativePath) + { + // TODO: Here we assume the install location is the top directory of relative path. + auto installLocation = baseFolder / *relativePath->begin(); + if (std::filesystem::exists(installLocation) && std::filesystem::is_directory(installLocation)) + { + return installLocation; + } + } + + return {}; + } + + // If install location is not provided in arp entry, try LocalAppData folder and Program Files folders. + std::optional CheckInstallLocation(const std::filesystem::path& path) + { + for (auto const& entry : s_CandidateInstallLocationRoots) + { + auto installLocation = CheckOneInstallLocation(path, entry.first); + if (installLocation) + { + return installLocation; + } + } + + return {}; + } + + // TODO: basic heuristics to determine file type. + AppInstaller::Manifest::InstalledFileTypeEnum GetInstalledFileType(const ShellLinkFileInfo& linkInfo) + { + Manifest::InstalledFileTypeEnum result = Manifest::InstalledFileTypeEnum::Other; + + if (Utility::CaseInsensitiveContainsSubstring(linkInfo.Path.u8string(), "uninstall") || + Utility::CaseInsensitiveContainsSubstring(linkInfo.Path.u8string(), "unins000") || + Utility::CaseInsensitiveContainsSubstring(linkInfo.Args, "uninstall") || + Utility::CaseInsensitiveContainsSubstring(linkInfo.DisplayName, "uninstall")) + { + result = Manifest::InstalledFileTypeEnum::Uninstall; + } + else if (Utility::CaseInsensitiveEquals(linkInfo.Path.extension().u8string(), ".exe")) + { + result = Manifest::InstalledFileTypeEnum::Launch; + } + + return result; + } + + std::string GetUnexpandedInstallLocation(const std::filesystem::path& installLocation) + { + // Try to match the candidate install location roots first. + std::filesystem::path resultInstallLocation = installLocation; + for (auto const& entry : s_CandidateInstallLocationRoots) + { + if (Filesystem::ReplaceCommonPathPrefix(resultInstallLocation, entry.first, entry.second)) + { + return resultInstallLocation.u8string(); + } + } + + // Then try PathUnExpandEnvStrings OS api + std::wstring installLocationWString = installLocation.wstring(); + std::wstring buffer; + buffer.resize(installLocationWString.size() + 20); + if (PathUnExpandEnvStrings( + installLocationWString.c_str(), + &buffer[0], + static_cast(buffer.size()))) + { + buffer.resize(buffer.find(L'\0')); + return Utility::ConvertToUTF8(buffer); + } + + return resultInstallLocation.u8string(); + } + } + + InstalledFilesCorrelation::InstalledFilesCorrelation() + { + m_fileWatchers.emplace_back(Filesystem::GetKnownFolderPath(FOLDERID_CommonStartMenu), std::string{ s_ShellLinkFileExtension }); + m_fileWatchers.emplace_back(Filesystem::GetKnownFolderPath(FOLDERID_StartMenu), std::string{ s_ShellLinkFileExtension }); + } + + void InstalledFilesCorrelation::StartFileWatcher() + { + m_files.clear(); + + for (auto& watcher : m_fileWatchers) + { + watcher.Start(); + } + } + + void InstalledFilesCorrelation::StopFileWatcher() + { + for (auto& watcher : m_fileWatchers) + { + watcher.Stop(); + } + + for (auto& watcher : m_fileWatchers) + { + FileWatcherFiles files; + files.Folder = watcher.FolderPath(); + + for (auto const& file : watcher.Files()) + { + files.Files.emplace_back(file); + } + + m_files.emplace_back(std::move(files)); + } + } + + InstallationMetadata InstalledFilesCorrelation::CorrelateForNewlyInstalled( + const Manifest::Manifest&, + const std::string& arpInstallLocation) + { + InstallationMetadata result; + + std::filesystem::path installLocation; + // Use arp install location if provided + if (!arpInstallLocation.empty()) + { + installLocation = Filesystem::GetExpandedPath(arpInstallLocation); + } + + for (auto const& files : m_files) + { + for (auto const& file : files.Files) + { + // TODO: we only watch shell link files at the moment. + auto linkInfo = ParseShellLinkFile(files.Folder / file); + if (linkInfo) + { + auto installedFileType = GetInstalledFileType(linkInfo.value()); + + // Collect installed files metadata if exist + if (std::filesystem::exists(linkInfo->Path) && std::filesystem::is_regular_file(linkInfo->Path)) + { + if (installLocation.empty()) + { + // TODO: In most cases, installed files are under same folder, so use the first file to determine install location at the moment. + auto location = CheckInstallLocation(linkInfo->Path); + if (!location) + { + continue; + } + + installLocation = location.value(); + } + + auto relativePath = GetRelativePath(linkInfo->Path, installLocation); + if (relativePath) + { + AppInstaller::Manifest::InstalledFile fileEntry; + fileEntry.RelativeFilePath = relativePath->string(); + std::ifstream in{ linkInfo->Path, std::ifstream::binary }; + fileEntry.FileSha256 = Utility::SHA256::ComputeHash(in); + fileEntry.InvocationParameter = linkInfo->Args; + fileEntry.DisplayName = linkInfo->DisplayName; + fileEntry.FileType = installedFileType; + result.InstalledFiles.Files.emplace_back(std::move(fileEntry)); + } + } + + // Collect short cut paths + InstalledStartupLinkFile linkFile; + linkFile.RelativeFilePath = file.u8string(); + linkFile.FileType = installedFileType; + result.StartupLinkFiles.emplace_back(linkFile); + } + } + } + + if (!installLocation.empty()) + { + result.InstalledFiles.DefaultInstallLocation = GetUnexpandedInstallLocation(installLocation); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/InstallerMetadataCollectionContext.cpp b/src/AppInstallerRepositoryCore/InstallerMetadataCollectionContext.cpp index 08da4c0c14..a1290da97a 100644 --- a/src/AppInstallerRepositoryCore/InstallerMetadataCollectionContext.cpp +++ b/src/AppInstallerRepositoryCore/InstallerMetadataCollectionContext.cpp @@ -1,1524 +1,1524 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/InstallerMetadataCollectionContext.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller::Utility; - -namespace AppInstaller::Repository::Metadata -{ - namespace - { - struct ProductMetadataFields_1_N - { - ProductMetadataFields_1_N(const Version& version) - { - if (::AppInstaller::Utility::Version{ "1.1" } <= version) - { - SchemaVersion = L"1.1"; - Scope = L"scope"; - } - - if (::AppInstaller::Utility::Version{ "1.2" } <= version) - { - SchemaVersion = L"1.2"; - InstalledFiles = L"installedFiles"; - DefaultInstallLocation = L"DefaultInstallLocation"; - InstallationMetadataFiles = L"Files"; - InstalledFileRelativeFilePath = L"RelativeFilePath"; - InstalledFileSha256 = L"FileSha256"; - InstalledFileType = L"FileType"; - InstalledFileInvocationParameter = L"InvocationParameter"; - InstalledFileDisplayName = L"DisplayName"; - InstalledStartupLinks = L"startupLinks"; - InstalledStartupLinkPath = L"RelativeFilePath"; - InstalledStartupLinkType = L"FileType"; - Icons = L"icons"; - IconContent = L"IconContent"; - IconSha256 = L"IconSha256"; - IconFileType = L"IconFileType"; - IconResolution = L"IconResolution"; - IconTheme = L"IconTheme"; - } - } - - utility::string_t SchemaVersion = L"1.0"; - - // 1.0 - utility::string_t ProductVersionMin = L"productVersionMin"; - utility::string_t ProductVersionMax = L"productVersionMax"; - utility::string_t Metadata = L"metadata"; - utility::string_t InstallerHash = L"installerHash"; - utility::string_t SubmissionIdentifier = L"submissionIdentifier"; - utility::string_t Version = L"version"; - utility::string_t AppsAndFeaturesEntries = L"AppsAndFeaturesEntries"; - utility::string_t Historical = L"historical"; - - // AppsAndFeaturesEntry fields. - utility::string_t DisplayName = L"DisplayName"; - utility::string_t Publisher = L"Publisher"; - utility::string_t DisplayVersion = L"DisplayVersion"; - utility::string_t ProductCode = L"ProductCode"; - utility::string_t UpgradeCode = L"UpgradeCode"; - utility::string_t InstallerType = L"InstallerType"; - - utility::string_t VersionMin = L"versionMin"; - utility::string_t VersionMax = L"versionMax"; - utility::string_t Names = L"names"; - utility::string_t Publishers = L"publishers"; - utility::string_t ProductCodes = L"productCodes"; - utility::string_t UpgradeCodes = L"upgradeCodes"; - - // 1.1 - utility::string_t Scope; - - // 1.2 - - // Installed files - utility::string_t InstalledFiles; - utility::string_t DefaultInstallLocation; - utility::string_t InstallationMetadataFiles; - utility::string_t InstalledFileRelativeFilePath; - utility::string_t InstalledFileSha256; - utility::string_t InstalledFileType; - utility::string_t InstalledFileInvocationParameter; - utility::string_t InstalledFileDisplayName; - // Startup links - utility::string_t InstalledStartupLinks; - utility::string_t InstalledStartupLinkPath; - utility::string_t InstalledStartupLinkType; - // Icons - utility::string_t Icons; - utility::string_t IconContent; - utility::string_t IconSha256; - utility::string_t IconFileType; - utility::string_t IconResolution; - utility::string_t IconTheme; - }; - - struct OutputFields_1_0 - { - utility::string_t Version = L"version"; - utility::string_t SubmissionData = L"submissionData"; - utility::string_t InstallerHash = L"installerHash"; - utility::string_t Status = L"status"; - utility::string_t Metadata = L"metadata"; - utility::string_t Diagnostics = L"diagnostics"; - }; - - struct DiagnosticFields - { - // Error case - utility::string_t ErrorHR = L"errorHR"; - utility::string_t ErrorText = L"errorText"; - - // Non-error case - utility::string_t Reason = L"reason"; - utility::string_t ChangedEntryCount = L"changedEntryCount"; - utility::string_t MatchedEntryCount = L"matchedEntryCount"; - utility::string_t IntersectionCount = L"intersectionCount"; - utility::string_t CorrelationMeasures = L"correlationMeasures"; - utility::string_t Value = L"value"; - utility::string_t Name = L"name"; - utility::string_t Publisher = L"publisher"; - }; - - std::string GetRequiredString(const web::json::value& value, const utility::string_t& field) - { - auto optString = AppInstaller::JSON::GetRawStringValueFromJsonNode(value, field); - if (!optString) - { - AICLI_LOG(Repo, Error, << "Required field '" << Utility::ConvertToUTF8(field) << "' was not present"); - THROW_HR(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); - } - return std::move(optString).value(); - } - - void AddFieldIfNotEmpty(web::json::value& value, const utility::string_t& field, std::string_view string) - { - if (!string.empty()) - { - value[field] = AppInstaller::JSON::GetStringValue(string); - } - } - - web::json::value CreateStringArray(const std::set& values) - { - web::json::value result; - size_t index = 0; - - for (const std::string& value : values) - { - result[index++] = AppInstaller::JSON::GetStringValue(value); - } - - return result; - } - - bool AddIfNotPresentAndNotEmpty(std::set& strings, const std::set& filter, const std::string& string) - { - if (string.empty() || filter.find(string) != filter.end()) - { - return false; - } - - strings.emplace(string); - return true; - } - - bool AddIfNotPresentAndNotEmpty(std::set& strings, const std::string& string) - { - return AddIfNotPresentAndNotEmpty(strings, strings, string); - } - - void AddIfNotPresent(std::set& strings, std::set& filter, const std::set& inputs) - { - for (const std::string& input : inputs) - { - if (AddIfNotPresentAndNotEmpty(strings, filter, input)) - { - filter.emplace(input); - } - } - } - - void FilterAndAddToEntries(Manifest::AppsAndFeaturesEntry&& newEntry, std::vector& entries) - { - // Erase all duplicated data from the new entry - for (const auto& entry : entries) - { -#define WINGET_ERASE_IF_SAME(_value_) if (entry._value_ == newEntry._value_) { newEntry._value_.clear(); } - WINGET_ERASE_IF_SAME(DisplayName); - WINGET_ERASE_IF_SAME(DisplayVersion); - WINGET_ERASE_IF_SAME(ProductCode); - WINGET_ERASE_IF_SAME(Publisher); - WINGET_ERASE_IF_SAME(UpgradeCode); -#undef WINGET_ERASE_IF_SAME - - if (entry.InstallerType == newEntry.InstallerType) - { - newEntry.InstallerType = Manifest::InstallerTypeEnum::Unknown; - } - } - - // If anything remains, add it - if (!newEntry.DisplayName.empty() || !newEntry.DisplayVersion.empty() || !newEntry.ProductCode.empty() || - !newEntry.Publisher.empty() || !newEntry.UpgradeCode.empty() || newEntry.InstallerType != Manifest::InstallerTypeEnum::Unknown) - { - entries.emplace_back(std::move(newEntry)); - } - } - - std::optional GetStringFromFutureSchema(const web::json::value& value, const utility::string_t& field) - { - if (field.empty()) - { - return {}; - } - - return AppInstaller::JSON::GetRawStringValueFromJsonNode(value, field); - } - - void SetStringFromFutureSchema(web::json::value& json, const utility::string_t& field, std::string_view value) - { - if (!field.empty()) - { - json[field] = AppInstaller::JSON::GetStringValue(value); - } - } - - // For installed files merging, we remove conflicting entries, like scope. Indicating we are not certain some files will always be installed. - void MergeInstalledFilesMetadata(Manifest::InstallationMetadataInfo& existing, const Manifest::InstallationMetadataInfo& incoming) - { - if (!Utility::CaseInsensitiveEquals(existing.DefaultInstallLocation, incoming.DefaultInstallLocation)) - { - existing.Clear(); - return; - } - - auto existingItr = existing.Files.begin(); - while (existingItr != existing.Files.end()) - { - auto itr = std::find_if(incoming.Files.begin(), incoming.Files.end(), [&](const Manifest::InstalledFile& entry) - { - return Utility::CaseInsensitiveEquals(existingItr->RelativeFilePath, entry.RelativeFilePath); - }); - - if (itr == incoming.Files.end()) - { - existingItr = existing.Files.erase(existingItr); - } - else - { - if (existingItr->InvocationParameter != itr->InvocationParameter) - { - existingItr->InvocationParameter.clear(); - } - if (!Utility::CaseInsensitiveEquals(existingItr->DisplayName, itr->DisplayName)) - { - existingItr->DisplayName.clear(); - } - if (!Utility::SHA256::AreEqual(existingItr->FileSha256, itr->FileSha256)) - { - existingItr->FileSha256.clear(); - } - if (existingItr->FileType != itr->FileType) - { - existingItr->FileType = Manifest::InstalledFileTypeEnum::Unknown; - } - - ++existingItr; - } - } - } - - // For startup link files merging, we add non duplicate entries, like ProductCodes. Indicating possible startup links an installer could potentially add. - void MergeStartupLinkFilesMetadata(std::vector& existing, const std::vector& incoming) - { - for (auto const& incomingEntry : incoming) - { - auto itr = std::find_if(existing.begin(), existing.end(), [&](const Correlation::InstalledStartupLinkFile& entry) - { - return Utility::CaseInsensitiveEquals(incomingEntry.RelativeFilePath, entry.RelativeFilePath); - }); - - if (itr == existing.end()) - { - existing.emplace_back(incomingEntry); - } - else if (itr->FileType != incomingEntry.FileType) - { - // Set conflicting file type to Unknown. - itr->FileType = AppInstaller::Manifest::InstalledFileTypeEnum::Unknown; - } - } - } - - // TODO: This method could be moved to rest response parser and reused when winget supports launch - // scenarios (i.e. when startup links info are exposed in winget manifest). - std::optional> DeserializeInstalledStartupLinks( - const web::json::value& startupLinkFiles, - const ProductMetadataFields_1_N& fields) - { - if (startupLinkFiles.is_null() || !startupLinkFiles.is_array()) - { - return {}; - } - - std::vector startupLinks; - for (auto const& startupLink : startupLinkFiles.as_array()) - { - Correlation::InstalledStartupLinkFile fileEntry; - - std::optional relativeFilePath = AppInstaller::JSON::GetRawStringValueFromJsonNode(startupLink, fields.InstalledStartupLinkPath); - if (!AppInstaller::JSON::IsValidNonEmptyStringValue(relativeFilePath)) - { - AICLI_LOG(Repo, Error, << "Missing RelativeFilePath in Installed Startup Link Files."); - return {}; - } - - fileEntry.RelativeFilePath = std::move(*relativeFilePath); - - std::optional fileType = AppInstaller::JSON::GetRawStringValueFromJsonNode(startupLink, fields.InstalledStartupLinkType); - if (AppInstaller::JSON::IsValidNonEmptyStringValue(fileType)) - { - fileEntry.FileType = Manifest::ConvertToInstalledFileTypeEnum(*fileType); - } - - startupLinks.emplace_back(std::move(fileEntry)); - } - - return startupLinks; - } - - std::vector DeserializeExtractedIcons( - const web::json::value& icons, - const ProductMetadataFields_1_N& fields) - { - if (icons.is_null() || !icons.is_array()) - { - return {}; - } - - std::vector result; - for (auto const& iconInfo : icons.as_array()) - { - ExtractedIconInfo iconInfoEntry; - - auto content = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconContent); - if (!AppInstaller::JSON::IsValidNonEmptyStringValue(content)) - { - AICLI_LOG(Repo, Error, << "Missing IconContent in Extracted Icons."); - return {}; - } - - iconInfoEntry.IconContent = AppInstaller::JSON::Base64Decode(*content); - - std::optional sha256 = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconSha256); - if (AppInstaller::JSON::IsValidNonEmptyStringValue(sha256)) - { - iconInfoEntry.IconSha256 = Utility::SHA256::ConvertToBytes(*sha256); - } - - std::optional fileType = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconFileType); - if (AppInstaller::JSON::IsValidNonEmptyStringValue(fileType)) - { - iconInfoEntry.IconFileType = Manifest::ConvertToIconFileTypeEnum(*fileType); - } - - std::optional theme = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconTheme); - if (AppInstaller::JSON::IsValidNonEmptyStringValue(theme)) - { - iconInfoEntry.IconTheme = Manifest::ConvertToIconThemeEnum(*theme); - } - - std::optional resolution = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconResolution); - if (AppInstaller::JSON::IsValidNonEmptyStringValue(resolution)) - { - iconInfoEntry.IconResolution = Manifest::ConvertToIconResolutionEnum(*resolution); - } - - result.emplace_back(std::move(iconInfoEntry)); - } - - return result; - } - } - - void ProductMetadata::Clear() - { - SchemaVersion = {}; - ProductVersionMin = {}; - ProductVersionMax = {}; - InstallerMetadataMap.clear(); - HistoricalMetadataList.clear(); - } - - void ProductMetadata::FromJson(const web::json::value& json) - { - Clear(); - - utility::string_t versionFieldName = L"version"; - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, json.is_null()); - - SchemaVersion = Version{ GetRequiredString(json, versionFieldName) }; - AICLI_LOG(Repo, Info, << "Parsing metadata JSON version " << SchemaVersion.ToString()); - - if (SchemaVersion.PartAt(0).Integer == 1) - { - FromJson_1_N(json); - } - else - { - AICLI_LOG(Repo, Error, << "Don't know how to handle metadata version " << SchemaVersion.ToString()); - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - - // Sort the historical data with oldest last (thus b < a) - std::sort(HistoricalMetadataList.begin(), HistoricalMetadataList.end(), - [](const HistoricalMetadata& a, const HistoricalMetadata& b) { - return b.ProductVersionMin < a.ProductVersionMin; - }); - } - - web::json::value ProductMetadata::ToJson(const Utility::Version& schemaVersion, size_t maximumSizeInBytes) - { - SchemaVersion = schemaVersion; - AICLI_LOG(Repo, Info, << "Creating metadata JSON version " << SchemaVersion.ToString()); - - using ToJsonFunctionPointer = web::json::value(ProductMetadata::*)(); - ToJsonFunctionPointer toJsonFunction = nullptr; - - if (SchemaVersion.PartAt(0).Integer == 1) - { - toJsonFunction = &ProductMetadata::ToJson_1_N; - } - else - { - AICLI_LOG(Repo, Error, << "Don't know how to handle metadata version " << SchemaVersion.ToString()); - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - - // Constrain the result based on maximum size given - web::json::value result = (this->*toJsonFunction)(); - - while (maximumSizeInBytes) - { - // Determine current size - std::ostringstream temp; - result.serialize(temp); - - std::string tempStr = temp.str(); - if (tempStr.length() > maximumSizeInBytes) - { - if (!DropOldestHistoricalData()) - { - AICLI_LOG(Repo, Error, << "Could not remove any more historical data to get under " << maximumSizeInBytes << " bytes"); - AICLI_LOG(Repo, Info, << " Smallest size was " << tempStr.length() << " bytes with value:\n" << tempStr); - THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE)); - } - result = (this->*toJsonFunction)(); - } - else - { - break; - } - } - - return result; - } - - void ProductMetadata::CopyFrom(const ProductMetadata& source, std::string_view submissionIdentifier) - { - // If the source has no installer metadata, consider it empty - if (source.InstallerMetadataMap.empty()) - { - return; - } - - // With the same submission, just copy over all of the data - if (source.InstallerMetadataMap.begin()->second.SubmissionIdentifier == submissionIdentifier) - { - *this = source; - return; - } - - // This is a new submission, so we must move all of the data to historical and update the older historical data - // First, create a new historical entry for the current metadata - HistoricalMetadata currentHistory; - - currentHistory.ProductVersionMin = source.ProductVersionMin; - currentHistory.ProductVersionMax = source.ProductVersionMax; - - for (const auto& metadataItem : source.InstallerMetadataMap) - { - for (const auto& entry : metadataItem.second.AppsAndFeaturesEntries) - { - AddIfNotPresentAndNotEmpty(currentHistory.Names, entry.DisplayName); - AddIfNotPresentAndNotEmpty(currentHistory.Publishers, entry.Publisher); - AddIfNotPresentAndNotEmpty(currentHistory.ProductCodes, entry.ProductCode); - AddIfNotPresentAndNotEmpty(currentHistory.UpgradeCodes, entry.UpgradeCode); - } - } - - // Copy the data in so that we can continue using currentHistory to track all strings - HistoricalMetadataList.emplace_back(currentHistory); - - // Now, copy over the other historical data, filtering out anything we have seen - for (const auto& historical : source.HistoricalMetadataList) - { - HistoricalMetadata copied; - copied.ProductVersionMin = historical.ProductVersionMin; - copied.ProductVersionMax = historical.ProductVersionMax; - AddIfNotPresent(copied.Names, currentHistory.Names, historical.Names); - AddIfNotPresent(copied.Publishers, currentHistory.Publishers, historical.Publishers); - AddIfNotPresent(copied.ProductCodes, currentHistory.ProductCodes, historical.ProductCodes); - AddIfNotPresent(copied.UpgradeCodes, currentHistory.UpgradeCodes, historical.UpgradeCodes); - - if (!copied.Names.empty() || !copied.Publishers.empty() || !copied.ProductCodes.empty() || !copied.UpgradeCodes.empty()) - { - HistoricalMetadataList.emplace_back(std::move(copied)); - } - } - } - - void ProductMetadata::FromJson_1_N(const web::json::value& json) - { - AICLI_LOG(Repo, Info, << "Parsing metadata JSON " << SchemaVersion.ToString() << " fields"); - - ProductMetadataFields_1_N fields{ SchemaVersion }; - - auto productVersionMinString = AppInstaller::JSON::GetRawStringValueFromJsonNode(json, fields.ProductVersionMin); - if (productVersionMinString) - { - ProductVersionMin = Version{ std::move(productVersionMinString).value() }; - } - - auto productVersionMaxString = AppInstaller::JSON::GetRawStringValueFromJsonNode(json, fields.ProductVersionMax); - if (productVersionMaxString) - { - ProductVersionMax = Version{ std::move(productVersionMaxString).value() }; - } - - // The 1.0 version of metadata uses the 1.5 version of REST - JSON::ManifestJSONParser parser{ Version{ "1.5" } }; - - std::string submissionIdentifierVerification; - - auto metadataArray = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(json, fields.Metadata); - if (metadataArray) - { - for (const auto& item : metadataArray->get()) - { - std::string installerHashString = GetRequiredString(item, fields.InstallerHash); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, InstallerMetadataMap.find(installerHashString) != InstallerMetadataMap.end()); - - InstallerMetadata installerMetadata; - - installerMetadata.SubmissionIdentifier = GetRequiredString(item, fields.SubmissionIdentifier); - if (submissionIdentifierVerification.empty()) - { - submissionIdentifierVerification = installerMetadata.SubmissionIdentifier; - } - else if (submissionIdentifierVerification != installerMetadata.SubmissionIdentifier) - { - AICLI_LOG(Repo, Error, << "Different submission identifiers found in metadata: '" << - submissionIdentifierVerification << "' and '" << installerMetadata.SubmissionIdentifier << "'"); - THROW_HR(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); - } - - auto appsAndFeatures = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(item, fields.AppsAndFeaturesEntries); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !appsAndFeatures); - installerMetadata.AppsAndFeaturesEntries = parser.DeserializeAppsAndFeaturesEntries(appsAndFeatures.value()); - - installerMetadata.Scope = GetStringFromFutureSchema(item, fields.Scope).value_or(std::string{}); - - if (!fields.InstalledFiles.empty()) - { - auto installedFiles = AppInstaller::JSON::GetJsonValueFromNode(item, fields.InstalledFiles); - if (installedFiles) - { - installerMetadata.InstalledFiles = parser.DeserializeInstallationMetadata(installedFiles->get()); - } - } - - if (!fields.InstalledStartupLinks.empty()) - { - auto startupLinks = AppInstaller::JSON::GetJsonValueFromNode(item, fields.InstalledStartupLinks); - if (startupLinks) - { - installerMetadata.StartupLinkFiles = DeserializeInstalledStartupLinks(startupLinks->get(), fields); - } - } - - if (!fields.Icons.empty()) - { - auto icons = AppInstaller::JSON::GetJsonValueFromNode(item, fields.Icons); - if (icons) - { - installerMetadata.Icons = DeserializeExtractedIcons(icons->get(), fields); - } - } - - InstallerMetadataMap[installerHashString] = std::move(installerMetadata); - } - } - - auto historicalArray = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(json, fields.Historical); - if (historicalArray) - { - for (const auto& item : historicalArray->get()) - { - HistoricalMetadata historicalMetadata; - - historicalMetadata.ProductVersionMin = Version{ GetRequiredString(item, fields.VersionMin) }; - historicalMetadata.ProductVersionMax = Version{ GetRequiredString(item, fields.VersionMax) }; - historicalMetadata.Names = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.Names); - historicalMetadata.Publishers = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.Publishers); - historicalMetadata.ProductCodes = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.ProductCodes); - historicalMetadata.UpgradeCodes = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.UpgradeCodes); - - HistoricalMetadataList.emplace_back(std::move(historicalMetadata)); - } - } - } - - web::json::value ProductMetadata::ToJson_1_N() - { - AICLI_LOG(Repo, Info, << "Creating metadata JSON " << SchemaVersion.ToString() << " fields"); - - ProductMetadataFields_1_N fields{ SchemaVersion }; - - web::json::value result; - - result[fields.Version] = web::json::value::string(fields.SchemaVersion); - result[fields.ProductVersionMin] = AppInstaller::JSON::GetStringValue(ProductVersionMin.ToString()); - result[fields.ProductVersionMax] = AppInstaller::JSON::GetStringValue(ProductVersionMax.ToString()); - - web::json::value metadataArray = web::json::value::array(); - size_t metadataItemIndex = 0; - for (const auto& item : InstallerMetadataMap) - { - web::json::value itemValue; - - itemValue[fields.InstallerHash] = AppInstaller::JSON::GetStringValue(item.first); - itemValue[fields.SubmissionIdentifier] = AppInstaller::JSON::GetStringValue(item.second.SubmissionIdentifier); - SetStringFromFutureSchema(itemValue, fields.Scope, item.second.Scope); - if (!fields.InstalledFiles.empty() && item.second.InstalledFiles.has_value()) - { - web::json::value installationMetadata; - - installationMetadata[fields.DefaultInstallLocation] = AppInstaller::JSON::GetStringValue(item.second.InstalledFiles->DefaultInstallLocation); - - web::json::value installedFilesArray = web::json::value::array(); - size_t installedFileIndex = 0; - for (const auto& entry : item.second.InstalledFiles->Files) - { - web::json::value entryValue; - AddFieldIfNotEmpty(entryValue, fields.InstalledFileRelativeFilePath, entry.RelativeFilePath); - AddFieldIfNotEmpty(entryValue, fields.InstalledFileInvocationParameter, entry.InvocationParameter); - AddFieldIfNotEmpty(entryValue, fields.InstalledFileDisplayName, entry.DisplayName); - entryValue[fields.InstalledFileType] = AppInstaller::JSON::GetStringValue(Manifest::InstalledFileTypeToString(entry.FileType)); - if (!entry.FileSha256.empty()) - { - entryValue[fields.InstalledFileSha256] = AppInstaller::JSON::GetStringValue(SHA256::ConvertToString(entry.FileSha256)); - } - installedFilesArray[installedFileIndex++] = std::move(entryValue); - } - installationMetadata[fields.InstallationMetadataFiles] = std::move(installedFilesArray); - - itemValue[fields.InstalledFiles] = std::move(installationMetadata); - } - - if (!fields.InstalledStartupLinks.empty() && item.second.StartupLinkFiles.has_value()) - { - web::json::value startupLinkFilesArray = web::json::value::array(); - size_t startupLinkFileIndex = 0; - for (const auto& entry : item.second.StartupLinkFiles.value()) - { - web::json::value entryValue; - entryValue[fields.InstalledStartupLinkPath] = AppInstaller::JSON::GetStringValue(entry.RelativeFilePath); - entryValue[fields.InstalledStartupLinkType] = AppInstaller::JSON::GetStringValue(Manifest::InstalledFileTypeToString(entry.FileType)); - - startupLinkFilesArray[startupLinkFileIndex++] = std::move(entryValue); - } - - itemValue[fields.InstalledStartupLinks] = std::move(startupLinkFilesArray); - } - - if (!fields.Icons.empty() && !item.second.Icons.empty()) - { - web::json::value iconsArray = web::json::value::array(); - size_t iconIndex = 0; - for (const auto& entry : item.second.Icons) - { - web::json::value entryValue; - entryValue[fields.IconContent] = AppInstaller::JSON::GetStringValue(AppInstaller::JSON::Base64Encode(entry.IconContent)); - if (!entry.IconSha256.empty()) - { - entryValue[fields.IconSha256] = AppInstaller::JSON::GetStringValue(SHA256::ConvertToString(entry.IconSha256)); - } - entryValue[fields.IconFileType] = AppInstaller::JSON::GetStringValue(Manifest::IconFileTypeToString(entry.IconFileType)); - entryValue[fields.IconTheme] = AppInstaller::JSON::GetStringValue(Manifest::IconThemeToString(entry.IconTheme)); - entryValue[fields.IconResolution] = AppInstaller::JSON::GetStringValue(Manifest::IconResolutionToString(entry.IconResolution)); - - iconsArray[iconIndex++] = std::move(entryValue); - } - - itemValue[fields.Icons] = std::move(iconsArray); - } - - web::json::value appsAndFeaturesArray = web::json::value::array(); - size_t appsAndFeaturesEntryIndex = 0; - for (const auto& entry : item.second.AppsAndFeaturesEntries) - { - web::json::value entryValue; - - AddFieldIfNotEmpty(entryValue, fields.DisplayName, entry.DisplayName); - AddFieldIfNotEmpty(entryValue, fields.Publisher, entry.Publisher); - AddFieldIfNotEmpty(entryValue, fields.DisplayVersion, entry.DisplayVersion); - AddFieldIfNotEmpty(entryValue, fields.ProductCode, entry.ProductCode); - AddFieldIfNotEmpty(entryValue, fields.UpgradeCode, entry.UpgradeCode); - if (entry.InstallerType != Manifest::InstallerTypeEnum::Unknown) - { - entryValue[fields.InstallerType] = AppInstaller::JSON::GetStringValue(Manifest::InstallerTypeToString(entry.InstallerType)); - } - - appsAndFeaturesArray[appsAndFeaturesEntryIndex++] = std::move(entryValue); - } - - itemValue[fields.AppsAndFeaturesEntries] = std::move(appsAndFeaturesArray); - - metadataArray[metadataItemIndex++] = std::move(itemValue); - } - - result[fields.Metadata] = std::move(metadataArray); - - web::json::value historicalArray = web::json::value::array(); - size_t historicalItemIndex = 0; - for (const auto& item : HistoricalMetadataList) - { - web::json::value itemValue; - - itemValue[fields.VersionMin] = AppInstaller::JSON::GetStringValue(item.ProductVersionMin.ToString()); - itemValue[fields.VersionMax] = AppInstaller::JSON::GetStringValue(item.ProductVersionMax.ToString()); - itemValue[fields.Names] = CreateStringArray(item.Names); - itemValue[fields.Publishers] = CreateStringArray(item.Publishers); - itemValue[fields.ProductCodes] = CreateStringArray(item.ProductCodes); - itemValue[fields.UpgradeCodes] = CreateStringArray(item.UpgradeCodes); - - historicalArray[historicalItemIndex++] = std::move(itemValue); - } - - result[fields.Historical] = std::move(historicalArray); - - return result; - } - - bool ProductMetadata::DropOldestHistoricalData() - { - if (HistoricalMetadataList.empty()) - { - return false; - } - - HistoricalMetadataList.pop_back(); - return true; - } - - InstallerMetadataCollectionContext::InstallerMetadataCollectionContext() : - m_correlationData(std::make_unique()), - m_installedFilesCorrelation(std::make_unique()) - {} - - InstallerMetadataCollectionContext::InstallerMetadataCollectionContext( - std::unique_ptr correlationData, - std::unique_ptr installedFilesCorrelation, - const std::wstring& json) : - m_correlationData(std::move(correlationData)), m_installedFilesCorrelation(std::move(installedFilesCorrelation)) - { - auto threadGlobalsLifetime = InitializeLogging({}); - InitializePreinstallState(json); - } - - std::unique_ptr InstallerMetadataCollectionContext::FromFile(const std::filesystem::path& file, const std::filesystem::path& logFile) - { - THROW_HR_IF(E_INVALIDARG, file.empty()); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !std::filesystem::exists(file)); - - std::unique_ptr result = std::make_unique(); - auto threadGlobalsLifetime = result->InitializeLogging(logFile); - - AICLI_LOG(Repo, Info, << "Opening InstallerMetadataCollectionContext input file: " << file); - std::ifstream fileStream{ file }; - - auto content = ReadEntireStream(fileStream); - // CppRestSdk's implementation of json parsing does not work with '\0', so trimming them here - content.erase(std::find(content.begin(), content.end(), '\0'), content.end()); - - result->InitializePreinstallState(ConvertToUTF16(content)); - - return result; - } - - std::unique_ptr InstallerMetadataCollectionContext::FromURI(std::wstring_view uri, const std::filesystem::path& logFile) - { - THROW_HR_IF(E_INVALIDARG, uri.empty()); - - std::unique_ptr result = std::make_unique(); - auto threadGlobalsLifetime = result->InitializeLogging(logFile); - - std::string utf8Uri = ConvertToUTF8(uri); - THROW_HR_IF(E_INVALIDARG, !IsUrlRemote(utf8Uri)); - - AICLI_LOG(Repo, Info, << "Downloading InstallerMetadataCollectionContext input file: " << utf8Uri); - - std::ostringstream jsonStream; - ProgressCallback emptyCallback; - - const int MaxRetryCount = 2; - for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) - { - try - { - auto downloadHash = DownloadToStream(utf8Uri, jsonStream, DownloadType::InstallerMetadataCollectionInput, emptyCallback); - break; - } - catch (...) - { - if (retryCount < MaxRetryCount - 1) - { - AICLI_LOG(Repo, Info, << " Downloading InstallerMetadataCollectionContext input failed, waiting a bit and retrying..."); - Sleep(500); - } - else - { - throw; - } - } - } - - result->InitializePreinstallState(ConvertToUTF16(jsonStream.str())); - - return result; - } - - std::unique_ptr InstallerMetadataCollectionContext::FromJSON(const std::wstring& json, const std::filesystem::path& logFile) - { - THROW_HR_IF(E_INVALIDARG, json.empty()); - - std::unique_ptr result = std::make_unique(); - auto threadGlobalsLifetime = result->InitializeLogging(logFile); - result->InitializePreinstallState(json); - - return result; - } - - void InstallerMetadataCollectionContext::Complete(const std::filesystem::path& output) - { - auto threadGlobalsLifetime = m_threadGlobals.SetForCurrentThread(); - - THROW_HR_IF(E_INVALIDARG, !output.has_filename()); - - if (output.has_parent_path()) - { - std::filesystem::create_directories(output.parent_path()); - } - - std::ofstream outputStream{ output }; - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED), !outputStream); - - CompleteWithThreadGlobalsSet(outputStream); - } - - void InstallerMetadataCollectionContext::Complete(std::ostream& output) - { - auto threadGlobalsLifetime = m_threadGlobals.SetForCurrentThread(); - CompleteWithThreadGlobalsSet(output); - } - - std::wstring InstallerMetadataCollectionContext::Merge(const std::wstring& json, size_t maximumSizeInBytes, const std::filesystem::path& logFile) - { - ThreadLocalStorage::WingetThreadGlobals threadGlobals; - auto globalsLifetime = InitializeLogging(threadGlobals, logFile); - - AICLI_LOG(Repo, Info, << "Parsing input JSON:\n" << ConvertToUTF8(json)); - - // Parse and validate JSON - try - { - utility::string_t versionFieldName = L"version"; - - web::json::value inputValue = web::json::value::parse(json); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, inputValue.is_null()); - - Version inputVersion = Version{ GetRequiredString(inputValue, versionFieldName) }; - AICLI_LOG(Repo, Info, << "Parsing input JSON version " << inputVersion.ToString()); - - web::json::value mergedResult; - - if (inputVersion.PartAt(0).Integer == 1) - { - mergedResult = Merge_1_0(inputValue, maximumSizeInBytes); - } - else - { - AICLI_LOG(Repo, Error, << "Don't know how to handle version " << inputVersion.ToString()); - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - - std::wostringstream outputStream; - mergedResult.serialize(outputStream); - - return std::move(outputStream).str(); - } - catch (const web::json::json_exception& exc) - { - AICLI_LOG(Repo, Error, << "Exception parsing input JSON: " << exc.what()); - } - - // We will return within the try or throw a non-json exception, so if we get here it was a json exception. - THROW_HR(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); - } - - void InstallerMetadataCollectionContext::CompleteWithThreadGlobalsSet(std::ostream& output) - { - web::json::value outputJSON; - - if (!ContainsError()) - { - try - { - // Collect post-install system state - m_correlationData->CapturePostInstallSnapshot(); - m_installedFilesCorrelation->StopFileWatcher(); - - ComputeOutputData(); - - // Construct output JSON - AICLI_LOG(Repo, Info, << "Creating output JSON version for input version " << m_inputVersion.ToString()); - - if (m_inputVersion.PartAt(0).Integer == 1) - { - // We only have one version currently, so use that as long as the major version is 1 - outputJSON = CreateOutputJson_1_0(); - } - else - { - AICLI_LOG(Repo, Error, << "Don't know how to output for version " << m_inputVersion.ToString()); - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - } - catch (...) - { - CollectErrorDataFromException(std::current_exception()); - } - } - - if (ContainsError()) - { - // We only have one version currently - outputJSON = CreateErrorJson_1_0(); - } - - // Write output - outputJSON.serialize(output); - } - - std::unique_ptr InstallerMetadataCollectionContext::InitializeLogging(ThreadLocalStorage::WingetThreadGlobals& threadGlobals, const std::filesystem::path& logFile) - { - auto threadGlobalsLifetime = threadGlobals.SetForCurrentThread(); - - Logging::Log().SetLevel(Logging::Level::Info); - Logging::Log().SetEnabledChannels(Logging::Channel::All); - Logging::EnableWilFailureTelemetry(); - Logging::TraceLogger::Add(); - - if (!logFile.empty()) - { - Logging::FileLogger::Add(logFile); - } - - Logging::Telemetry().SetCaller("installer-metadata-collection"); - Logging::Telemetry().LogStartup(); - - return threadGlobalsLifetime; - } - - std::unique_ptr InstallerMetadataCollectionContext::InitializeLogging(const std::filesystem::path& logFile) - { - return InitializeLogging(m_threadGlobals, logFile); - } - - void InstallerMetadataCollectionContext::InitializePreinstallState(const std::wstring& json) - { - try - { - AICLI_LOG(Repo, Info, << "Parsing input JSON:\n" << ConvertToUTF8(json)); - - // Parse and validate JSON - try - { - utility::string_t versionFieldName = L"version"; - - web::json::value inputValue = web::json::value::parse(json); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, inputValue.is_null()); - - m_inputVersion = Version{ GetRequiredString(inputValue, versionFieldName) }; - AICLI_LOG(Repo, Info, << "Parsing input JSON version " << m_inputVersion.ToString()); - - if (m_inputVersion.PartAt(0).Integer == 1) - { - // We only have one version currently, so use that as long as the major version is 1 - ParseInputJson_1_0(inputValue); - } - else - { - AICLI_LOG(Repo, Error, << "Don't know how to handle version " << m_inputVersion.ToString()); - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - } - catch (const web::json::json_exception& exc) - { - AICLI_LOG(Repo, Error, << "Exception parsing input JSON: " << exc.what()); - throw; - } - - // Collect pre-install system state - m_correlationData->CapturePreInstallSnapshot(); - m_installedFilesCorrelation->StartFileWatcher(); - } - catch (...) - { - CollectErrorDataFromException(std::current_exception()); - } - } - - void InstallerMetadataCollectionContext::ComputeOutputData() - { - // Copy the metadata from the current; this function takes care of moving data to historical if the submission is new. - m_outputMetadata.CopyFrom(m_currentMetadata, m_submissionIdentifier); - - Correlation::ARPCorrelationSettings settings; - std::string arpInstallLocation; - // As this code is typically run in a controlled environment, we can assume that a single value change is very likely the correct value. - settings.AllowSingleChange = true; - - // ARP entry correlation - Correlation::ARPCorrelationResult correlationResult = m_correlationData->CorrelateForNewlyInstalled(m_incomingManifest, settings); - - if (correlationResult.Package) - { - auto& package = correlationResult.Package; - - // Update min and max versions based on the version of the correlated package - Version packageVersion{ package->GetProperty(PackageVersionProperty::Version) }; - - if (m_outputMetadata.ProductVersionMin.IsEmpty() || packageVersion < m_outputMetadata.ProductVersionMin) - { - m_outputMetadata.ProductVersionMin = packageVersion; - } - - if (m_outputMetadata.ProductVersionMax.IsEmpty() || m_outputMetadata.ProductVersionMax < packageVersion) - { - m_outputMetadata.ProductVersionMax = packageVersion; - } - - // Create the AppsAndFeaturesEntry that we need to add - Manifest::AppsAndFeaturesEntry newEntry; - auto packageMetadata = package->GetMetadata(); - - // Arp installed location will be used in later installed files correlation. - arpInstallLocation = packageMetadata[PackageVersionMetadata::InstalledLocation]; - - // TODO: Use some amount of normalization here to prevent things like versions being in the name from bloating the data - newEntry.DisplayName = package->GetProperty(PackageVersionProperty::Name).get(); - newEntry.DisplayVersion = packageVersion.ToString(); - if (packageMetadata.count(PackageVersionMetadata::InstalledType)) - { - newEntry.InstallerType = Manifest::ConvertToInstallerTypeEnum(packageMetadata[PackageVersionMetadata::InstalledType]); - } - auto productCodes = package->GetMultiProperty(PackageVersionMultiProperty::ProductCode); - if (!productCodes.empty()) - { - newEntry.ProductCode = std::move(productCodes[0]).get(); - } - newEntry.Publisher = package->GetProperty(PackageVersionProperty::Publisher).get(); - // TODO: Support upgrade code throughout the code base... - - Manifest::ScopeEnum scope = Manifest::ConvertToScopeEnum(packageMetadata[PackageVersionMetadata::InstalledScope]); - - // ARP entry icon extraction upon ARP correlation success - auto icons = ExtractIconFromArpEntry(newEntry.ProductCode, scope); - - // Add or update the metadata for the installer hash - auto itr = m_outputMetadata.InstallerMetadataMap.find(m_installerHash); - - if (itr == m_outputMetadata.InstallerMetadataMap.end()) - { - // New entry needed - ProductMetadata::InstallerMetadata newMetadata; - - newMetadata.SubmissionIdentifier = m_submissionIdentifier; - newMetadata.AppsAndFeaturesEntries.emplace_back(std::move(newEntry)); - - if (scope != Manifest::ScopeEnum::Unknown) - { - newMetadata.Scope = Manifest::ScopeToString(scope); - } - - if (!icons.empty()) - { - newMetadata.Icons = std::move(icons); - } - - m_outputMetadata.InstallerMetadataMap[m_installerHash] = std::move(newMetadata); - } - else - { - if (itr->second.Scope.empty()) - { - itr->second.Scope = Manifest::ScopeToString(scope); - } - // If there is a conflicting scope already present, force it to Unknown - else if (scope != Manifest::ScopeEnum::Unknown && Manifest::ConvertToScopeEnum(itr->second.Scope) != scope) - { - itr->second.Scope = Manifest::ScopeToString(Manifest::ScopeEnum::Unknown); - } - - // We will always use the latest extracted icons upon confliction. - if (!icons.empty()) - { - itr->second.Icons = std::move(icons); - } - - // Existing entry for installer hash, add/update the entry - FilterAndAddToEntries(std::move(newEntry), itr->second.AppsAndFeaturesEntries); - } - } - - // Installation files correlation - auto installationMetadata = m_installedFilesCorrelation->CorrelateForNewlyInstalled(m_incomingManifest, arpInstallLocation); - - if (installationMetadata.InstalledFiles.HasData() || !installationMetadata.StartupLinkFiles.empty()) - { - // Add or update the metadata for the installer hash - auto itr = m_outputMetadata.InstallerMetadataMap.find(m_installerHash); - - if (itr == m_outputMetadata.InstallerMetadataMap.end()) - { - // New entry needed - ProductMetadata::InstallerMetadata newMetadata; - - newMetadata.SubmissionIdentifier = m_submissionIdentifier; - - if (installationMetadata.InstalledFiles.HasData()) - { - newMetadata.InstalledFiles = std::move(installationMetadata.InstalledFiles); - } - if (!installationMetadata.StartupLinkFiles.empty()) - { - newMetadata.StartupLinkFiles = std::move(installationMetadata.StartupLinkFiles); - } - - m_outputMetadata.InstallerMetadataMap[m_installerHash] = std::move(newMetadata); - } - else - { - // Add new or merge with existing entry - if (installationMetadata.InstalledFiles.HasData()) - { - if (!itr->second.InstalledFiles.has_value()) - { - itr->second.InstalledFiles = std::move(installationMetadata.InstalledFiles); - } - else - { - MergeInstalledFilesMetadata(*(itr->second.InstalledFiles), installationMetadata.InstalledFiles); - } - } - - if (!installationMetadata.StartupLinkFiles.empty()) - { - if (!itr->second.StartupLinkFiles.has_value()) - { - itr->second.StartupLinkFiles = std::move(installationMetadata.StartupLinkFiles); - } - else - { - MergeStartupLinkFilesMetadata(*(itr->second.StartupLinkFiles), installationMetadata.StartupLinkFiles); - } - } - } - } - - if (correlationResult.Package) - { - m_outputStatus = OutputStatus::Success; - } - else - { - m_outputStatus = OutputStatus::LowConfidence; - } - - // Create the diagnostics data, based on the other values from the correlation result. - DiagnosticFields fields; - - m_outputDiagnostics[fields.Reason] = AppInstaller::JSON::GetStringValue(correlationResult.Reason); - m_outputDiagnostics[fields.ChangedEntryCount] = web::json::value::number(static_cast(correlationResult.ChangesToARP)); - m_outputDiagnostics[fields.MatchedEntryCount] = web::json::value::number(static_cast(correlationResult.MatchesInARP)); - m_outputDiagnostics[fields.IntersectionCount] = web::json::value::number(static_cast(correlationResult.CountOfIntersectionOfChangesAndMatches)); - - constexpr size_t MaximumDiagnosticMeasures = 10; - web::json::value measuresArray = web::json::value::array(); - for (size_t i = 0; i < correlationResult.Measures.size() && i < MaximumDiagnosticMeasures; ++i) - { - web::json::value measureValue; - const auto& measure = correlationResult.Measures[i]; - - measureValue[fields.Value] = web::json::value::number(measure.Measure); - measureValue[fields.Name] = AppInstaller::JSON::GetStringValue(measure.Package->GetProperty(PackageVersionProperty::Name)); - measureValue[fields.Publisher] = AppInstaller::JSON::GetStringValue(measure.Package->GetProperty(PackageVersionProperty::Publisher)); - - measuresArray[i] = std::move(measureValue); - } - - m_outputDiagnostics[fields.CorrelationMeasures] = std::move(measuresArray); - } - - void InstallerMetadataCollectionContext::ParseInputJson_1_0(web::json::value& input) - { - AICLI_LOG(Repo, Info, << "Parsing input JSON 1.0 fields"); - - // Field names - utility::string_t metadataVersionFieldName = L"supportedMetadataVersion"; - utility::string_t metadataFieldName = L"currentMetadata"; - utility::string_t submissionDataFieldName = L"submissionData"; - utility::string_t submissionIdentifierFieldName = L"submissionIdentifier"; - utility::string_t packageDataFieldName = L"packageData"; - utility::string_t installerHashFieldName = L"installerHash"; - utility::string_t defaultLocaleFieldName = L"DefaultLocale"; - utility::string_t localesFieldName = L"Locales"; - - // root fields - m_supportedMetadataVersion = Version{ GetRequiredString(input, metadataVersionFieldName) }; - - auto currentMetadataValue = AppInstaller::JSON::GetJsonValueFromNode(input, metadataFieldName); - if (currentMetadataValue) - { - m_currentMetadata.FromJson(currentMetadataValue.value()); - } - - // submissionData fields - auto submissionDataValue = AppInstaller::JSON::GetJsonValueFromNode(input, submissionDataFieldName); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !submissionDataValue); - m_submissionData = submissionDataValue.value(); - - m_submissionIdentifier = GetRequiredString(m_submissionData, submissionIdentifierFieldName); - - // packageData fields - auto packageDataValue = AppInstaller::JSON::GetJsonValueFromNode(input, packageDataFieldName); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !packageDataValue); - - m_installerHash = GetRequiredString(packageDataValue.value(), installerHashFieldName); - - // The 1.0 version of input uses the 1.5 version of REST - JSON::ManifestJSONParser parser{ Version{ "1.5" }}; - - { - auto defaultLocaleValue = AppInstaller::JSON::GetJsonValueFromNode(packageDataValue.value(), defaultLocaleFieldName); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !defaultLocaleValue); - - auto defaultLocale = parser.DeserializeLocale(defaultLocaleValue.value()); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, - !defaultLocale || - !defaultLocale->Contains(Manifest::Localization::PackageName) || - !defaultLocale->Contains(Manifest::Localization::Publisher)); - - m_incomingManifest.DefaultLocalization = std::move(defaultLocale).value(); - - auto localesArray = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(packageDataValue.value(), localesFieldName); - if (localesArray) - { - for (const auto& locale : localesArray->get()) - { - auto localization = parser.DeserializeLocale(locale); - if (localization) - { - m_incomingManifest.Localizations.emplace_back(std::move(localization).value()); - } - } - } - } - } - - web::json::value InstallerMetadataCollectionContext::CreateOutputJson_1_0() - { - AICLI_LOG(Repo, Info, << "Setting output JSON 1.0 fields"); - - OutputFields_1_0 fields; - - web::json::value result; - - result[fields.Version] = web::json::value::string(L"1.0"); - result[fields.SubmissionData] = m_submissionData; - result[fields.InstallerHash] = AppInstaller::JSON::GetStringValue(m_installerHash); - - // Limit output status to 1.0 known values - OutputStatus statusToUse = OutputStatus::Unknown; - if (m_outputStatus == OutputStatus::Success || m_outputStatus == OutputStatus::Error || m_outputStatus == OutputStatus::LowConfidence) - { - statusToUse = m_outputStatus; - } - result[fields.Status] = web::json::value::string(ToString(statusToUse)); - - if (m_outputStatus == OutputStatus::Success) - { - result[fields.Metadata] = m_outputMetadata.ToJson(m_supportedMetadataVersion, 0); - } - - result[fields.Diagnostics] = m_outputDiagnostics; - - return result; - } - - utility::string_t InstallerMetadataCollectionContext::ToString(OutputStatus status) - { - switch (status) - { - case OutputStatus::Success: return L"Success"; - case OutputStatus::Error: return L"Error"; - case OutputStatus::LowConfidence: return L"LowConfidence"; - } - - // For both the status value of Unknown and anything else - return L"Unknown"; - } - - bool InstallerMetadataCollectionContext::ContainsError() const - { - return m_outputStatus == OutputStatus::Error; - } - - void InstallerMetadataCollectionContext::CollectErrorDataFromException(std::exception_ptr exception) - { - m_outputStatus = OutputStatus::Error; - - try - { - std::rethrow_exception(exception); - } - catch (const wil::ResultException& re) - { - m_errorHR = re.GetErrorCode(); - m_errorText = GetUserPresentableMessage(re); - } - catch (const winrt::hresult_error& hre) - { - m_errorHR = hre.code(); - m_errorText = GetUserPresentableMessage(hre); - } - catch (const std::exception& e) - { - m_errorHR = E_FAIL; - m_errorText = GetUserPresentableMessage(e); - } - catch (...) - { - m_errorHR = E_UNEXPECTED; - m_errorText = "An unexpected exception type was thrown."; - } - } - - web::json::value InstallerMetadataCollectionContext::CreateErrorJson_1_0() - { - AICLI_LOG(Repo, Info, << "Setting error JSON 1.0 fields"); - - OutputFields_1_0 fields; - DiagnosticFields diagnosticFields; - - web::json::value result; - - result[fields.Version] = web::json::value::string(L"1.0"); - result[fields.SubmissionData] = m_submissionData; - result[fields.InstallerHash] = AppInstaller::JSON::GetStringValue(m_installerHash); - result[fields.Status] = web::json::value::string(ToString(OutputStatus::Error)); - result[fields.Metadata] = web::json::value::null(); - - web::json::value error; - - error[diagnosticFields.ErrorHR] = web::json::value::number(static_cast(m_errorHR)); - error[diagnosticFields.ErrorText] = AppInstaller::JSON::GetStringValue(m_errorText); - - result[fields.Diagnostics] = std::move(error); - - return result; - } - - web::json::value InstallerMetadataCollectionContext::Merge_1_0(web::json::value& input, size_t maximumSizeInBytes) - { - AICLI_LOG(Repo, Info, << "Merging 1.0 input metadatas"); - - utility::string_t metadatasFieldName = L"metadatas"; - - auto metadatasValue = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(input, metadatasFieldName); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !metadatasValue); - - std::vector metadatas; - for (const auto& value : metadatasValue->get()) - { - ProductMetadata current; - current.FromJson(value); - metadatas.emplace_back(std::move(current)); - } - - THROW_HR_IF(E_NOT_SET, metadatas.empty()); - - // Require that all merging values use the same submission - for (const ProductMetadata& metadata : metadatas) - { - const std::string& firstSubmission = metadatas[0].InstallerMetadataMap.begin()->second.SubmissionIdentifier; - const std::string& metadataSubmission = metadata.InstallerMetadataMap.begin()->second.SubmissionIdentifier; - if (firstSubmission != metadataSubmission) - { - AICLI_LOG(Repo, Info, << "Found submission identifier mismatch: " << firstSubmission << " != " << metadataSubmission); - THROW_HR(E_NOT_VALID_STATE); - } - } - - // Do the actual merging - ProductMetadata resultMetadata; - - // The historical data should be the same across the board, so we can just copy the first one. - resultMetadata.HistoricalMetadataList = metadatas[0].HistoricalMetadataList; - - for (const ProductMetadata& metadata : metadatas) - { - // Get the minimum and maximum versions from the individual values - if (resultMetadata.ProductVersionMin.IsEmpty() || metadata.ProductVersionMin < resultMetadata.ProductVersionMin) - { - resultMetadata.ProductVersionMin = metadata.ProductVersionMin; - } - - if (resultMetadata.ProductVersionMax < metadata.ProductVersionMax) - { - resultMetadata.ProductVersionMax = metadata.ProductVersionMax; - } - - if (resultMetadata.SchemaVersion < metadata.SchemaVersion) - { - resultMetadata.SchemaVersion = metadata.SchemaVersion; - } - - for (const auto& installerMetadata : metadata.InstallerMetadataMap) - { - auto itr = resultMetadata.InstallerMetadataMap.find(installerMetadata.first); - if (itr == resultMetadata.InstallerMetadataMap.end()) - { - // Installer hash not in the result, so just copy it - resultMetadata.InstallerMetadataMap.emplace(installerMetadata); - } - else - { - if (itr->second.Scope.empty()) - { - itr->second.Scope = installerMetadata.second.Scope; - } - else if (!installerMetadata.second.Scope.empty()) - { - // If there is a conflicting scope already present, force it to Unknown - if (Manifest::ConvertToScopeEnum(itr->second.Scope) != Manifest::ConvertToScopeEnum(installerMetadata.second.Scope)) - { - itr->second.Scope = Manifest::ScopeToString(Manifest::ScopeEnum::Unknown); - } - } - - // We will always use the latest extracted icons upon confliction. - if (!installerMetadata.second.Icons.empty()) - { - itr->second.Icons = installerMetadata.second.Icons; - } - - if (!itr->second.InstalledFiles.has_value()) - { - itr->second.InstalledFiles = installerMetadata.second.InstalledFiles; - } - else if (installerMetadata.second.InstalledFiles.has_value()) - { - MergeInstalledFilesMetadata(*(itr->second.InstalledFiles), *(installerMetadata.second.InstalledFiles)); - } - - if (!itr->second.StartupLinkFiles.has_value()) - { - itr->second.StartupLinkFiles = installerMetadata.second.StartupLinkFiles; - } - else if (installerMetadata.second.StartupLinkFiles.has_value()) - { - MergeStartupLinkFilesMetadata(*(itr->second.StartupLinkFiles), *(installerMetadata.second.StartupLinkFiles)); - } - - // Merge into existing installer data - for (const auto& targetEntry : installerMetadata.second.AppsAndFeaturesEntries) - { - FilterAndAddToEntries(Manifest::AppsAndFeaturesEntry{ targetEntry }, itr->second.AppsAndFeaturesEntries); - } - } - } - } - - // Convert to JSON - return resultMetadata.ToJson(resultMetadata.SchemaVersion, maximumSizeInBytes); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/InstallerMetadataCollectionContext.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller::Utility; + +namespace AppInstaller::Repository::Metadata +{ + namespace + { + struct ProductMetadataFields_1_N + { + ProductMetadataFields_1_N(const Version& version) + { + if (::AppInstaller::Utility::Version{ "1.1" } <= version) + { + SchemaVersion = L"1.1"; + Scope = L"scope"; + } + + if (::AppInstaller::Utility::Version{ "1.2" } <= version) + { + SchemaVersion = L"1.2"; + InstalledFiles = L"installedFiles"; + DefaultInstallLocation = L"DefaultInstallLocation"; + InstallationMetadataFiles = L"Files"; + InstalledFileRelativeFilePath = L"RelativeFilePath"; + InstalledFileSha256 = L"FileSha256"; + InstalledFileType = L"FileType"; + InstalledFileInvocationParameter = L"InvocationParameter"; + InstalledFileDisplayName = L"DisplayName"; + InstalledStartupLinks = L"startupLinks"; + InstalledStartupLinkPath = L"RelativeFilePath"; + InstalledStartupLinkType = L"FileType"; + Icons = L"icons"; + IconContent = L"IconContent"; + IconSha256 = L"IconSha256"; + IconFileType = L"IconFileType"; + IconResolution = L"IconResolution"; + IconTheme = L"IconTheme"; + } + } + + utility::string_t SchemaVersion = L"1.0"; + + // 1.0 + utility::string_t ProductVersionMin = L"productVersionMin"; + utility::string_t ProductVersionMax = L"productVersionMax"; + utility::string_t Metadata = L"metadata"; + utility::string_t InstallerHash = L"installerHash"; + utility::string_t SubmissionIdentifier = L"submissionIdentifier"; + utility::string_t Version = L"version"; + utility::string_t AppsAndFeaturesEntries = L"AppsAndFeaturesEntries"; + utility::string_t Historical = L"historical"; + + // AppsAndFeaturesEntry fields. + utility::string_t DisplayName = L"DisplayName"; + utility::string_t Publisher = L"Publisher"; + utility::string_t DisplayVersion = L"DisplayVersion"; + utility::string_t ProductCode = L"ProductCode"; + utility::string_t UpgradeCode = L"UpgradeCode"; + utility::string_t InstallerType = L"InstallerType"; + + utility::string_t VersionMin = L"versionMin"; + utility::string_t VersionMax = L"versionMax"; + utility::string_t Names = L"names"; + utility::string_t Publishers = L"publishers"; + utility::string_t ProductCodes = L"productCodes"; + utility::string_t UpgradeCodes = L"upgradeCodes"; + + // 1.1 + utility::string_t Scope; + + // 1.2 + + // Installed files + utility::string_t InstalledFiles; + utility::string_t DefaultInstallLocation; + utility::string_t InstallationMetadataFiles; + utility::string_t InstalledFileRelativeFilePath; + utility::string_t InstalledFileSha256; + utility::string_t InstalledFileType; + utility::string_t InstalledFileInvocationParameter; + utility::string_t InstalledFileDisplayName; + // Startup links + utility::string_t InstalledStartupLinks; + utility::string_t InstalledStartupLinkPath; + utility::string_t InstalledStartupLinkType; + // Icons + utility::string_t Icons; + utility::string_t IconContent; + utility::string_t IconSha256; + utility::string_t IconFileType; + utility::string_t IconResolution; + utility::string_t IconTheme; + }; + + struct OutputFields_1_0 + { + utility::string_t Version = L"version"; + utility::string_t SubmissionData = L"submissionData"; + utility::string_t InstallerHash = L"installerHash"; + utility::string_t Status = L"status"; + utility::string_t Metadata = L"metadata"; + utility::string_t Diagnostics = L"diagnostics"; + }; + + struct DiagnosticFields + { + // Error case + utility::string_t ErrorHR = L"errorHR"; + utility::string_t ErrorText = L"errorText"; + + // Non-error case + utility::string_t Reason = L"reason"; + utility::string_t ChangedEntryCount = L"changedEntryCount"; + utility::string_t MatchedEntryCount = L"matchedEntryCount"; + utility::string_t IntersectionCount = L"intersectionCount"; + utility::string_t CorrelationMeasures = L"correlationMeasures"; + utility::string_t Value = L"value"; + utility::string_t Name = L"name"; + utility::string_t Publisher = L"publisher"; + }; + + std::string GetRequiredString(const web::json::value& value, const utility::string_t& field) + { + auto optString = AppInstaller::JSON::GetRawStringValueFromJsonNode(value, field); + if (!optString) + { + AICLI_LOG(Repo, Error, << "Required field '" << Utility::ConvertToUTF8(field) << "' was not present"); + THROW_HR(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); + } + return std::move(optString).value(); + } + + void AddFieldIfNotEmpty(web::json::value& value, const utility::string_t& field, std::string_view string) + { + if (!string.empty()) + { + value[field] = AppInstaller::JSON::GetStringValue(string); + } + } + + web::json::value CreateStringArray(const std::set& values) + { + web::json::value result; + size_t index = 0; + + for (const std::string& value : values) + { + result[index++] = AppInstaller::JSON::GetStringValue(value); + } + + return result; + } + + bool AddIfNotPresentAndNotEmpty(std::set& strings, const std::set& filter, const std::string& string) + { + if (string.empty() || filter.find(string) != filter.end()) + { + return false; + } + + strings.emplace(string); + return true; + } + + bool AddIfNotPresentAndNotEmpty(std::set& strings, const std::string& string) + { + return AddIfNotPresentAndNotEmpty(strings, strings, string); + } + + void AddIfNotPresent(std::set& strings, std::set& filter, const std::set& inputs) + { + for (const std::string& input : inputs) + { + if (AddIfNotPresentAndNotEmpty(strings, filter, input)) + { + filter.emplace(input); + } + } + } + + void FilterAndAddToEntries(Manifest::AppsAndFeaturesEntry&& newEntry, std::vector& entries) + { + // Erase all duplicated data from the new entry + for (const auto& entry : entries) + { +#define WINGET_ERASE_IF_SAME(_value_) if (entry._value_ == newEntry._value_) { newEntry._value_.clear(); } + WINGET_ERASE_IF_SAME(DisplayName); + WINGET_ERASE_IF_SAME(DisplayVersion); + WINGET_ERASE_IF_SAME(ProductCode); + WINGET_ERASE_IF_SAME(Publisher); + WINGET_ERASE_IF_SAME(UpgradeCode); +#undef WINGET_ERASE_IF_SAME + + if (entry.InstallerType == newEntry.InstallerType) + { + newEntry.InstallerType = Manifest::InstallerTypeEnum::Unknown; + } + } + + // If anything remains, add it + if (!newEntry.DisplayName.empty() || !newEntry.DisplayVersion.empty() || !newEntry.ProductCode.empty() || + !newEntry.Publisher.empty() || !newEntry.UpgradeCode.empty() || newEntry.InstallerType != Manifest::InstallerTypeEnum::Unknown) + { + entries.emplace_back(std::move(newEntry)); + } + } + + std::optional GetStringFromFutureSchema(const web::json::value& value, const utility::string_t& field) + { + if (field.empty()) + { + return {}; + } + + return AppInstaller::JSON::GetRawStringValueFromJsonNode(value, field); + } + + void SetStringFromFutureSchema(web::json::value& json, const utility::string_t& field, std::string_view value) + { + if (!field.empty()) + { + json[field] = AppInstaller::JSON::GetStringValue(value); + } + } + + // For installed files merging, we remove conflicting entries, like scope. Indicating we are not certain some files will always be installed. + void MergeInstalledFilesMetadata(Manifest::InstallationMetadataInfo& existing, const Manifest::InstallationMetadataInfo& incoming) + { + if (!Utility::CaseInsensitiveEquals(existing.DefaultInstallLocation, incoming.DefaultInstallLocation)) + { + existing.Clear(); + return; + } + + auto existingItr = existing.Files.begin(); + while (existingItr != existing.Files.end()) + { + auto itr = std::find_if(incoming.Files.begin(), incoming.Files.end(), [&](const Manifest::InstalledFile& entry) + { + return Utility::CaseInsensitiveEquals(existingItr->RelativeFilePath, entry.RelativeFilePath); + }); + + if (itr == incoming.Files.end()) + { + existingItr = existing.Files.erase(existingItr); + } + else + { + if (existingItr->InvocationParameter != itr->InvocationParameter) + { + existingItr->InvocationParameter.clear(); + } + if (!Utility::CaseInsensitiveEquals(existingItr->DisplayName, itr->DisplayName)) + { + existingItr->DisplayName.clear(); + } + if (!Utility::SHA256::AreEqual(existingItr->FileSha256, itr->FileSha256)) + { + existingItr->FileSha256.clear(); + } + if (existingItr->FileType != itr->FileType) + { + existingItr->FileType = Manifest::InstalledFileTypeEnum::Unknown; + } + + ++existingItr; + } + } + } + + // For startup link files merging, we add non duplicate entries, like ProductCodes. Indicating possible startup links an installer could potentially add. + void MergeStartupLinkFilesMetadata(std::vector& existing, const std::vector& incoming) + { + for (auto const& incomingEntry : incoming) + { + auto itr = std::find_if(existing.begin(), existing.end(), [&](const Correlation::InstalledStartupLinkFile& entry) + { + return Utility::CaseInsensitiveEquals(incomingEntry.RelativeFilePath, entry.RelativeFilePath); + }); + + if (itr == existing.end()) + { + existing.emplace_back(incomingEntry); + } + else if (itr->FileType != incomingEntry.FileType) + { + // Set conflicting file type to Unknown. + itr->FileType = AppInstaller::Manifest::InstalledFileTypeEnum::Unknown; + } + } + } + + // TODO: This method could be moved to rest response parser and reused when winget supports launch + // scenarios (i.e. when startup links info are exposed in winget manifest). + std::optional> DeserializeInstalledStartupLinks( + const web::json::value& startupLinkFiles, + const ProductMetadataFields_1_N& fields) + { + if (startupLinkFiles.is_null() || !startupLinkFiles.is_array()) + { + return {}; + } + + std::vector startupLinks; + for (auto const& startupLink : startupLinkFiles.as_array()) + { + Correlation::InstalledStartupLinkFile fileEntry; + + std::optional relativeFilePath = AppInstaller::JSON::GetRawStringValueFromJsonNode(startupLink, fields.InstalledStartupLinkPath); + if (!AppInstaller::JSON::IsValidNonEmptyStringValue(relativeFilePath)) + { + AICLI_LOG(Repo, Error, << "Missing RelativeFilePath in Installed Startup Link Files."); + return {}; + } + + fileEntry.RelativeFilePath = std::move(*relativeFilePath); + + std::optional fileType = AppInstaller::JSON::GetRawStringValueFromJsonNode(startupLink, fields.InstalledStartupLinkType); + if (AppInstaller::JSON::IsValidNonEmptyStringValue(fileType)) + { + fileEntry.FileType = Manifest::ConvertToInstalledFileTypeEnum(*fileType); + } + + startupLinks.emplace_back(std::move(fileEntry)); + } + + return startupLinks; + } + + std::vector DeserializeExtractedIcons( + const web::json::value& icons, + const ProductMetadataFields_1_N& fields) + { + if (icons.is_null() || !icons.is_array()) + { + return {}; + } + + std::vector result; + for (auto const& iconInfo : icons.as_array()) + { + ExtractedIconInfo iconInfoEntry; + + auto content = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconContent); + if (!AppInstaller::JSON::IsValidNonEmptyStringValue(content)) + { + AICLI_LOG(Repo, Error, << "Missing IconContent in Extracted Icons."); + return {}; + } + + iconInfoEntry.IconContent = AppInstaller::JSON::Base64Decode(*content); + + std::optional sha256 = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconSha256); + if (AppInstaller::JSON::IsValidNonEmptyStringValue(sha256)) + { + iconInfoEntry.IconSha256 = Utility::SHA256::ConvertToBytes(*sha256); + } + + std::optional fileType = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconFileType); + if (AppInstaller::JSON::IsValidNonEmptyStringValue(fileType)) + { + iconInfoEntry.IconFileType = Manifest::ConvertToIconFileTypeEnum(*fileType); + } + + std::optional theme = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconTheme); + if (AppInstaller::JSON::IsValidNonEmptyStringValue(theme)) + { + iconInfoEntry.IconTheme = Manifest::ConvertToIconThemeEnum(*theme); + } + + std::optional resolution = AppInstaller::JSON::GetRawStringValueFromJsonNode(iconInfo, fields.IconResolution); + if (AppInstaller::JSON::IsValidNonEmptyStringValue(resolution)) + { + iconInfoEntry.IconResolution = Manifest::ConvertToIconResolutionEnum(*resolution); + } + + result.emplace_back(std::move(iconInfoEntry)); + } + + return result; + } + } + + void ProductMetadata::Clear() + { + SchemaVersion = {}; + ProductVersionMin = {}; + ProductVersionMax = {}; + InstallerMetadataMap.clear(); + HistoricalMetadataList.clear(); + } + + void ProductMetadata::FromJson(const web::json::value& json) + { + Clear(); + + utility::string_t versionFieldName = L"version"; + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, json.is_null()); + + SchemaVersion = Version{ GetRequiredString(json, versionFieldName) }; + AICLI_LOG(Repo, Info, << "Parsing metadata JSON version " << SchemaVersion.ToString()); + + if (SchemaVersion.PartAt(0).Integer == 1) + { + FromJson_1_N(json); + } + else + { + AICLI_LOG(Repo, Error, << "Don't know how to handle metadata version " << SchemaVersion.ToString()); + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + + // Sort the historical data with oldest last (thus b < a) + std::sort(HistoricalMetadataList.begin(), HistoricalMetadataList.end(), + [](const HistoricalMetadata& a, const HistoricalMetadata& b) { + return b.ProductVersionMin < a.ProductVersionMin; + }); + } + + web::json::value ProductMetadata::ToJson(const Utility::Version& schemaVersion, size_t maximumSizeInBytes) + { + SchemaVersion = schemaVersion; + AICLI_LOG(Repo, Info, << "Creating metadata JSON version " << SchemaVersion.ToString()); + + using ToJsonFunctionPointer = web::json::value(ProductMetadata::*)(); + ToJsonFunctionPointer toJsonFunction = nullptr; + + if (SchemaVersion.PartAt(0).Integer == 1) + { + toJsonFunction = &ProductMetadata::ToJson_1_N; + } + else + { + AICLI_LOG(Repo, Error, << "Don't know how to handle metadata version " << SchemaVersion.ToString()); + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + + // Constrain the result based on maximum size given + web::json::value result = (this->*toJsonFunction)(); + + while (maximumSizeInBytes) + { + // Determine current size + std::ostringstream temp; + result.serialize(temp); + + std::string tempStr = temp.str(); + if (tempStr.length() > maximumSizeInBytes) + { + if (!DropOldestHistoricalData()) + { + AICLI_LOG(Repo, Error, << "Could not remove any more historical data to get under " << maximumSizeInBytes << " bytes"); + AICLI_LOG(Repo, Info, << " Smallest size was " << tempStr.length() << " bytes with value:\n" << tempStr); + THROW_HR(HRESULT_FROM_WIN32(ERROR_FILE_TOO_LARGE)); + } + result = (this->*toJsonFunction)(); + } + else + { + break; + } + } + + return result; + } + + void ProductMetadata::CopyFrom(const ProductMetadata& source, std::string_view submissionIdentifier) + { + // If the source has no installer metadata, consider it empty + if (source.InstallerMetadataMap.empty()) + { + return; + } + + // With the same submission, just copy over all of the data + if (source.InstallerMetadataMap.begin()->second.SubmissionIdentifier == submissionIdentifier) + { + *this = source; + return; + } + + // This is a new submission, so we must move all of the data to historical and update the older historical data + // First, create a new historical entry for the current metadata + HistoricalMetadata currentHistory; + + currentHistory.ProductVersionMin = source.ProductVersionMin; + currentHistory.ProductVersionMax = source.ProductVersionMax; + + for (const auto& metadataItem : source.InstallerMetadataMap) + { + for (const auto& entry : metadataItem.second.AppsAndFeaturesEntries) + { + AddIfNotPresentAndNotEmpty(currentHistory.Names, entry.DisplayName); + AddIfNotPresentAndNotEmpty(currentHistory.Publishers, entry.Publisher); + AddIfNotPresentAndNotEmpty(currentHistory.ProductCodes, entry.ProductCode); + AddIfNotPresentAndNotEmpty(currentHistory.UpgradeCodes, entry.UpgradeCode); + } + } + + // Copy the data in so that we can continue using currentHistory to track all strings + HistoricalMetadataList.emplace_back(currentHistory); + + // Now, copy over the other historical data, filtering out anything we have seen + for (const auto& historical : source.HistoricalMetadataList) + { + HistoricalMetadata copied; + copied.ProductVersionMin = historical.ProductVersionMin; + copied.ProductVersionMax = historical.ProductVersionMax; + AddIfNotPresent(copied.Names, currentHistory.Names, historical.Names); + AddIfNotPresent(copied.Publishers, currentHistory.Publishers, historical.Publishers); + AddIfNotPresent(copied.ProductCodes, currentHistory.ProductCodes, historical.ProductCodes); + AddIfNotPresent(copied.UpgradeCodes, currentHistory.UpgradeCodes, historical.UpgradeCodes); + + if (!copied.Names.empty() || !copied.Publishers.empty() || !copied.ProductCodes.empty() || !copied.UpgradeCodes.empty()) + { + HistoricalMetadataList.emplace_back(std::move(copied)); + } + } + } + + void ProductMetadata::FromJson_1_N(const web::json::value& json) + { + AICLI_LOG(Repo, Info, << "Parsing metadata JSON " << SchemaVersion.ToString() << " fields"); + + ProductMetadataFields_1_N fields{ SchemaVersion }; + + auto productVersionMinString = AppInstaller::JSON::GetRawStringValueFromJsonNode(json, fields.ProductVersionMin); + if (productVersionMinString) + { + ProductVersionMin = Version{ std::move(productVersionMinString).value() }; + } + + auto productVersionMaxString = AppInstaller::JSON::GetRawStringValueFromJsonNode(json, fields.ProductVersionMax); + if (productVersionMaxString) + { + ProductVersionMax = Version{ std::move(productVersionMaxString).value() }; + } + + // The 1.0 version of metadata uses the 1.5 version of REST + JSON::ManifestJSONParser parser{ Version{ "1.5" } }; + + std::string submissionIdentifierVerification; + + auto metadataArray = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(json, fields.Metadata); + if (metadataArray) + { + for (const auto& item : metadataArray->get()) + { + std::string installerHashString = GetRequiredString(item, fields.InstallerHash); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, InstallerMetadataMap.find(installerHashString) != InstallerMetadataMap.end()); + + InstallerMetadata installerMetadata; + + installerMetadata.SubmissionIdentifier = GetRequiredString(item, fields.SubmissionIdentifier); + if (submissionIdentifierVerification.empty()) + { + submissionIdentifierVerification = installerMetadata.SubmissionIdentifier; + } + else if (submissionIdentifierVerification != installerMetadata.SubmissionIdentifier) + { + AICLI_LOG(Repo, Error, << "Different submission identifiers found in metadata: '" << + submissionIdentifierVerification << "' and '" << installerMetadata.SubmissionIdentifier << "'"); + THROW_HR(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); + } + + auto appsAndFeatures = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(item, fields.AppsAndFeaturesEntries); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !appsAndFeatures); + installerMetadata.AppsAndFeaturesEntries = parser.DeserializeAppsAndFeaturesEntries(appsAndFeatures.value()); + + installerMetadata.Scope = GetStringFromFutureSchema(item, fields.Scope).value_or(std::string{}); + + if (!fields.InstalledFiles.empty()) + { + auto installedFiles = AppInstaller::JSON::GetJsonValueFromNode(item, fields.InstalledFiles); + if (installedFiles) + { + installerMetadata.InstalledFiles = parser.DeserializeInstallationMetadata(installedFiles->get()); + } + } + + if (!fields.InstalledStartupLinks.empty()) + { + auto startupLinks = AppInstaller::JSON::GetJsonValueFromNode(item, fields.InstalledStartupLinks); + if (startupLinks) + { + installerMetadata.StartupLinkFiles = DeserializeInstalledStartupLinks(startupLinks->get(), fields); + } + } + + if (!fields.Icons.empty()) + { + auto icons = AppInstaller::JSON::GetJsonValueFromNode(item, fields.Icons); + if (icons) + { + installerMetadata.Icons = DeserializeExtractedIcons(icons->get(), fields); + } + } + + InstallerMetadataMap[installerHashString] = std::move(installerMetadata); + } + } + + auto historicalArray = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(json, fields.Historical); + if (historicalArray) + { + for (const auto& item : historicalArray->get()) + { + HistoricalMetadata historicalMetadata; + + historicalMetadata.ProductVersionMin = Version{ GetRequiredString(item, fields.VersionMin) }; + historicalMetadata.ProductVersionMax = Version{ GetRequiredString(item, fields.VersionMax) }; + historicalMetadata.Names = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.Names); + historicalMetadata.Publishers = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.Publishers); + historicalMetadata.ProductCodes = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.ProductCodes); + historicalMetadata.UpgradeCodes = AppInstaller::JSON::GetRawStringSetFromJsonNode(item, fields.UpgradeCodes); + + HistoricalMetadataList.emplace_back(std::move(historicalMetadata)); + } + } + } + + web::json::value ProductMetadata::ToJson_1_N() + { + AICLI_LOG(Repo, Info, << "Creating metadata JSON " << SchemaVersion.ToString() << " fields"); + + ProductMetadataFields_1_N fields{ SchemaVersion }; + + web::json::value result; + + result[fields.Version] = web::json::value::string(fields.SchemaVersion); + result[fields.ProductVersionMin] = AppInstaller::JSON::GetStringValue(ProductVersionMin.ToString()); + result[fields.ProductVersionMax] = AppInstaller::JSON::GetStringValue(ProductVersionMax.ToString()); + + web::json::value metadataArray = web::json::value::array(); + size_t metadataItemIndex = 0; + for (const auto& item : InstallerMetadataMap) + { + web::json::value itemValue; + + itemValue[fields.InstallerHash] = AppInstaller::JSON::GetStringValue(item.first); + itemValue[fields.SubmissionIdentifier] = AppInstaller::JSON::GetStringValue(item.second.SubmissionIdentifier); + SetStringFromFutureSchema(itemValue, fields.Scope, item.second.Scope); + if (!fields.InstalledFiles.empty() && item.second.InstalledFiles.has_value()) + { + web::json::value installationMetadata; + + installationMetadata[fields.DefaultInstallLocation] = AppInstaller::JSON::GetStringValue(item.second.InstalledFiles->DefaultInstallLocation); + + web::json::value installedFilesArray = web::json::value::array(); + size_t installedFileIndex = 0; + for (const auto& entry : item.second.InstalledFiles->Files) + { + web::json::value entryValue; + AddFieldIfNotEmpty(entryValue, fields.InstalledFileRelativeFilePath, entry.RelativeFilePath); + AddFieldIfNotEmpty(entryValue, fields.InstalledFileInvocationParameter, entry.InvocationParameter); + AddFieldIfNotEmpty(entryValue, fields.InstalledFileDisplayName, entry.DisplayName); + entryValue[fields.InstalledFileType] = AppInstaller::JSON::GetStringValue(Manifest::InstalledFileTypeToString(entry.FileType)); + if (!entry.FileSha256.empty()) + { + entryValue[fields.InstalledFileSha256] = AppInstaller::JSON::GetStringValue(SHA256::ConvertToString(entry.FileSha256)); + } + installedFilesArray[installedFileIndex++] = std::move(entryValue); + } + installationMetadata[fields.InstallationMetadataFiles] = std::move(installedFilesArray); + + itemValue[fields.InstalledFiles] = std::move(installationMetadata); + } + + if (!fields.InstalledStartupLinks.empty() && item.second.StartupLinkFiles.has_value()) + { + web::json::value startupLinkFilesArray = web::json::value::array(); + size_t startupLinkFileIndex = 0; + for (const auto& entry : item.second.StartupLinkFiles.value()) + { + web::json::value entryValue; + entryValue[fields.InstalledStartupLinkPath] = AppInstaller::JSON::GetStringValue(entry.RelativeFilePath); + entryValue[fields.InstalledStartupLinkType] = AppInstaller::JSON::GetStringValue(Manifest::InstalledFileTypeToString(entry.FileType)); + + startupLinkFilesArray[startupLinkFileIndex++] = std::move(entryValue); + } + + itemValue[fields.InstalledStartupLinks] = std::move(startupLinkFilesArray); + } + + if (!fields.Icons.empty() && !item.second.Icons.empty()) + { + web::json::value iconsArray = web::json::value::array(); + size_t iconIndex = 0; + for (const auto& entry : item.second.Icons) + { + web::json::value entryValue; + entryValue[fields.IconContent] = AppInstaller::JSON::GetStringValue(AppInstaller::JSON::Base64Encode(entry.IconContent)); + if (!entry.IconSha256.empty()) + { + entryValue[fields.IconSha256] = AppInstaller::JSON::GetStringValue(SHA256::ConvertToString(entry.IconSha256)); + } + entryValue[fields.IconFileType] = AppInstaller::JSON::GetStringValue(Manifest::IconFileTypeToString(entry.IconFileType)); + entryValue[fields.IconTheme] = AppInstaller::JSON::GetStringValue(Manifest::IconThemeToString(entry.IconTheme)); + entryValue[fields.IconResolution] = AppInstaller::JSON::GetStringValue(Manifest::IconResolutionToString(entry.IconResolution)); + + iconsArray[iconIndex++] = std::move(entryValue); + } + + itemValue[fields.Icons] = std::move(iconsArray); + } + + web::json::value appsAndFeaturesArray = web::json::value::array(); + size_t appsAndFeaturesEntryIndex = 0; + for (const auto& entry : item.second.AppsAndFeaturesEntries) + { + web::json::value entryValue; + + AddFieldIfNotEmpty(entryValue, fields.DisplayName, entry.DisplayName); + AddFieldIfNotEmpty(entryValue, fields.Publisher, entry.Publisher); + AddFieldIfNotEmpty(entryValue, fields.DisplayVersion, entry.DisplayVersion); + AddFieldIfNotEmpty(entryValue, fields.ProductCode, entry.ProductCode); + AddFieldIfNotEmpty(entryValue, fields.UpgradeCode, entry.UpgradeCode); + if (entry.InstallerType != Manifest::InstallerTypeEnum::Unknown) + { + entryValue[fields.InstallerType] = AppInstaller::JSON::GetStringValue(Manifest::InstallerTypeToString(entry.InstallerType)); + } + + appsAndFeaturesArray[appsAndFeaturesEntryIndex++] = std::move(entryValue); + } + + itemValue[fields.AppsAndFeaturesEntries] = std::move(appsAndFeaturesArray); + + metadataArray[metadataItemIndex++] = std::move(itemValue); + } + + result[fields.Metadata] = std::move(metadataArray); + + web::json::value historicalArray = web::json::value::array(); + size_t historicalItemIndex = 0; + for (const auto& item : HistoricalMetadataList) + { + web::json::value itemValue; + + itemValue[fields.VersionMin] = AppInstaller::JSON::GetStringValue(item.ProductVersionMin.ToString()); + itemValue[fields.VersionMax] = AppInstaller::JSON::GetStringValue(item.ProductVersionMax.ToString()); + itemValue[fields.Names] = CreateStringArray(item.Names); + itemValue[fields.Publishers] = CreateStringArray(item.Publishers); + itemValue[fields.ProductCodes] = CreateStringArray(item.ProductCodes); + itemValue[fields.UpgradeCodes] = CreateStringArray(item.UpgradeCodes); + + historicalArray[historicalItemIndex++] = std::move(itemValue); + } + + result[fields.Historical] = std::move(historicalArray); + + return result; + } + + bool ProductMetadata::DropOldestHistoricalData() + { + if (HistoricalMetadataList.empty()) + { + return false; + } + + HistoricalMetadataList.pop_back(); + return true; + } + + InstallerMetadataCollectionContext::InstallerMetadataCollectionContext() : + m_correlationData(std::make_unique()), + m_installedFilesCorrelation(std::make_unique()) + {} + + InstallerMetadataCollectionContext::InstallerMetadataCollectionContext( + std::unique_ptr correlationData, + std::unique_ptr installedFilesCorrelation, + const std::wstring& json) : + m_correlationData(std::move(correlationData)), m_installedFilesCorrelation(std::move(installedFilesCorrelation)) + { + auto threadGlobalsLifetime = InitializeLogging({}); + InitializePreinstallState(json); + } + + std::unique_ptr InstallerMetadataCollectionContext::FromFile(const std::filesystem::path& file, const std::filesystem::path& logFile) + { + THROW_HR_IF(E_INVALIDARG, file.empty()); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), !std::filesystem::exists(file)); + + std::unique_ptr result = std::make_unique(); + auto threadGlobalsLifetime = result->InitializeLogging(logFile); + + AICLI_LOG(Repo, Info, << "Opening InstallerMetadataCollectionContext input file: " << file); + std::ifstream fileStream{ file }; + + auto content = ReadEntireStream(fileStream); + // CppRestSdk's implementation of json parsing does not work with '\0', so trimming them here + content.erase(std::find(content.begin(), content.end(), '\0'), content.end()); + + result->InitializePreinstallState(ConvertToUTF16(content)); + + return result; + } + + std::unique_ptr InstallerMetadataCollectionContext::FromURI(std::wstring_view uri, const std::filesystem::path& logFile) + { + THROW_HR_IF(E_INVALIDARG, uri.empty()); + + std::unique_ptr result = std::make_unique(); + auto threadGlobalsLifetime = result->InitializeLogging(logFile); + + std::string utf8Uri = ConvertToUTF8(uri); + THROW_HR_IF(E_INVALIDARG, !IsUrlRemote(utf8Uri)); + + AICLI_LOG(Repo, Info, << "Downloading InstallerMetadataCollectionContext input file: " << utf8Uri); + + std::ostringstream jsonStream; + ProgressCallback emptyCallback; + + const int MaxRetryCount = 2; + for (int retryCount = 0; retryCount < MaxRetryCount; ++retryCount) + { + try + { + auto downloadHash = DownloadToStream(utf8Uri, jsonStream, DownloadType::InstallerMetadataCollectionInput, emptyCallback); + break; + } + catch (...) + { + if (retryCount < MaxRetryCount - 1) + { + AICLI_LOG(Repo, Info, << " Downloading InstallerMetadataCollectionContext input failed, waiting a bit and retrying..."); + Sleep(500); + } + else + { + throw; + } + } + } + + result->InitializePreinstallState(ConvertToUTF16(jsonStream.str())); + + return result; + } + + std::unique_ptr InstallerMetadataCollectionContext::FromJSON(const std::wstring& json, const std::filesystem::path& logFile) + { + THROW_HR_IF(E_INVALIDARG, json.empty()); + + std::unique_ptr result = std::make_unique(); + auto threadGlobalsLifetime = result->InitializeLogging(logFile); + result->InitializePreinstallState(json); + + return result; + } + + void InstallerMetadataCollectionContext::Complete(const std::filesystem::path& output) + { + auto threadGlobalsLifetime = m_threadGlobals.SetForCurrentThread(); + + THROW_HR_IF(E_INVALIDARG, !output.has_filename()); + + if (output.has_parent_path()) + { + std::filesystem::create_directories(output.parent_path()); + } + + std::ofstream outputStream{ output }; + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_OPEN_FAILED), !outputStream); + + CompleteWithThreadGlobalsSet(outputStream); + } + + void InstallerMetadataCollectionContext::Complete(std::ostream& output) + { + auto threadGlobalsLifetime = m_threadGlobals.SetForCurrentThread(); + CompleteWithThreadGlobalsSet(output); + } + + std::wstring InstallerMetadataCollectionContext::Merge(const std::wstring& json, size_t maximumSizeInBytes, const std::filesystem::path& logFile) + { + ThreadLocalStorage::WingetThreadGlobals threadGlobals; + auto globalsLifetime = InitializeLogging(threadGlobals, logFile); + + AICLI_LOG(Repo, Info, << "Parsing input JSON:\n" << ConvertToUTF8(json)); + + // Parse and validate JSON + try + { + utility::string_t versionFieldName = L"version"; + + web::json::value inputValue = web::json::value::parse(json); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, inputValue.is_null()); + + Version inputVersion = Version{ GetRequiredString(inputValue, versionFieldName) }; + AICLI_LOG(Repo, Info, << "Parsing input JSON version " << inputVersion.ToString()); + + web::json::value mergedResult; + + if (inputVersion.PartAt(0).Integer == 1) + { + mergedResult = Merge_1_0(inputValue, maximumSizeInBytes); + } + else + { + AICLI_LOG(Repo, Error, << "Don't know how to handle version " << inputVersion.ToString()); + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + + std::wostringstream outputStream; + mergedResult.serialize(outputStream); + + return std::move(outputStream).str(); + } + catch (const web::json::json_exception& exc) + { + AICLI_LOG(Repo, Error, << "Exception parsing input JSON: " << exc.what()); + } + + // We will return within the try or throw a non-json exception, so if we get here it was a json exception. + THROW_HR(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE); + } + + void InstallerMetadataCollectionContext::CompleteWithThreadGlobalsSet(std::ostream& output) + { + web::json::value outputJSON; + + if (!ContainsError()) + { + try + { + // Collect post-install system state + m_correlationData->CapturePostInstallSnapshot(); + m_installedFilesCorrelation->StopFileWatcher(); + + ComputeOutputData(); + + // Construct output JSON + AICLI_LOG(Repo, Info, << "Creating output JSON version for input version " << m_inputVersion.ToString()); + + if (m_inputVersion.PartAt(0).Integer == 1) + { + // We only have one version currently, so use that as long as the major version is 1 + outputJSON = CreateOutputJson_1_0(); + } + else + { + AICLI_LOG(Repo, Error, << "Don't know how to output for version " << m_inputVersion.ToString()); + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + } + catch (...) + { + CollectErrorDataFromException(std::current_exception()); + } + } + + if (ContainsError()) + { + // We only have one version currently + outputJSON = CreateErrorJson_1_0(); + } + + // Write output + outputJSON.serialize(output); + } + + std::unique_ptr InstallerMetadataCollectionContext::InitializeLogging(ThreadLocalStorage::WingetThreadGlobals& threadGlobals, const std::filesystem::path& logFile) + { + auto threadGlobalsLifetime = threadGlobals.SetForCurrentThread(); + + Logging::Log().SetLevel(Logging::Level::Info); + Logging::Log().SetEnabledChannels(Logging::Channel::All); + Logging::EnableWilFailureTelemetry(); + Logging::TraceLogger::Add(); + + if (!logFile.empty()) + { + Logging::FileLogger::Add(logFile); + } + + Logging::Telemetry().SetCaller("installer-metadata-collection"); + Logging::Telemetry().LogStartup(); + + return threadGlobalsLifetime; + } + + std::unique_ptr InstallerMetadataCollectionContext::InitializeLogging(const std::filesystem::path& logFile) + { + return InitializeLogging(m_threadGlobals, logFile); + } + + void InstallerMetadataCollectionContext::InitializePreinstallState(const std::wstring& json) + { + try + { + AICLI_LOG(Repo, Info, << "Parsing input JSON:\n" << ConvertToUTF8(json)); + + // Parse and validate JSON + try + { + utility::string_t versionFieldName = L"version"; + + web::json::value inputValue = web::json::value::parse(json); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, inputValue.is_null()); + + m_inputVersion = Version{ GetRequiredString(inputValue, versionFieldName) }; + AICLI_LOG(Repo, Info, << "Parsing input JSON version " << m_inputVersion.ToString()); + + if (m_inputVersion.PartAt(0).Integer == 1) + { + // We only have one version currently, so use that as long as the major version is 1 + ParseInputJson_1_0(inputValue); + } + else + { + AICLI_LOG(Repo, Error, << "Don't know how to handle version " << m_inputVersion.ToString()); + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + } + catch (const web::json::json_exception& exc) + { + AICLI_LOG(Repo, Error, << "Exception parsing input JSON: " << exc.what()); + throw; + } + + // Collect pre-install system state + m_correlationData->CapturePreInstallSnapshot(); + m_installedFilesCorrelation->StartFileWatcher(); + } + catch (...) + { + CollectErrorDataFromException(std::current_exception()); + } + } + + void InstallerMetadataCollectionContext::ComputeOutputData() + { + // Copy the metadata from the current; this function takes care of moving data to historical if the submission is new. + m_outputMetadata.CopyFrom(m_currentMetadata, m_submissionIdentifier); + + Correlation::ARPCorrelationSettings settings; + std::string arpInstallLocation; + // As this code is typically run in a controlled environment, we can assume that a single value change is very likely the correct value. + settings.AllowSingleChange = true; + + // ARP entry correlation + Correlation::ARPCorrelationResult correlationResult = m_correlationData->CorrelateForNewlyInstalled(m_incomingManifest, settings); + + if (correlationResult.Package) + { + auto& package = correlationResult.Package; + + // Update min and max versions based on the version of the correlated package + Version packageVersion{ package->GetProperty(PackageVersionProperty::Version) }; + + if (m_outputMetadata.ProductVersionMin.IsEmpty() || packageVersion < m_outputMetadata.ProductVersionMin) + { + m_outputMetadata.ProductVersionMin = packageVersion; + } + + if (m_outputMetadata.ProductVersionMax.IsEmpty() || m_outputMetadata.ProductVersionMax < packageVersion) + { + m_outputMetadata.ProductVersionMax = packageVersion; + } + + // Create the AppsAndFeaturesEntry that we need to add + Manifest::AppsAndFeaturesEntry newEntry; + auto packageMetadata = package->GetMetadata(); + + // Arp installed location will be used in later installed files correlation. + arpInstallLocation = packageMetadata[PackageVersionMetadata::InstalledLocation]; + + // TODO: Use some amount of normalization here to prevent things like versions being in the name from bloating the data + newEntry.DisplayName = package->GetProperty(PackageVersionProperty::Name).get(); + newEntry.DisplayVersion = packageVersion.ToString(); + if (packageMetadata.count(PackageVersionMetadata::InstalledType)) + { + newEntry.InstallerType = Manifest::ConvertToInstallerTypeEnum(packageMetadata[PackageVersionMetadata::InstalledType]); + } + auto productCodes = package->GetMultiProperty(PackageVersionMultiProperty::ProductCode); + if (!productCodes.empty()) + { + newEntry.ProductCode = std::move(productCodes[0]).get(); + } + newEntry.Publisher = package->GetProperty(PackageVersionProperty::Publisher).get(); + // TODO: Support upgrade code throughout the code base... + + Manifest::ScopeEnum scope = Manifest::ConvertToScopeEnum(packageMetadata[PackageVersionMetadata::InstalledScope]); + + // ARP entry icon extraction upon ARP correlation success + auto icons = ExtractIconFromArpEntry(newEntry.ProductCode, scope); + + // Add or update the metadata for the installer hash + auto itr = m_outputMetadata.InstallerMetadataMap.find(m_installerHash); + + if (itr == m_outputMetadata.InstallerMetadataMap.end()) + { + // New entry needed + ProductMetadata::InstallerMetadata newMetadata; + + newMetadata.SubmissionIdentifier = m_submissionIdentifier; + newMetadata.AppsAndFeaturesEntries.emplace_back(std::move(newEntry)); + + if (scope != Manifest::ScopeEnum::Unknown) + { + newMetadata.Scope = Manifest::ScopeToString(scope); + } + + if (!icons.empty()) + { + newMetadata.Icons = std::move(icons); + } + + m_outputMetadata.InstallerMetadataMap[m_installerHash] = std::move(newMetadata); + } + else + { + if (itr->second.Scope.empty()) + { + itr->second.Scope = Manifest::ScopeToString(scope); + } + // If there is a conflicting scope already present, force it to Unknown + else if (scope != Manifest::ScopeEnum::Unknown && Manifest::ConvertToScopeEnum(itr->second.Scope) != scope) + { + itr->second.Scope = Manifest::ScopeToString(Manifest::ScopeEnum::Unknown); + } + + // We will always use the latest extracted icons upon confliction. + if (!icons.empty()) + { + itr->second.Icons = std::move(icons); + } + + // Existing entry for installer hash, add/update the entry + FilterAndAddToEntries(std::move(newEntry), itr->second.AppsAndFeaturesEntries); + } + } + + // Installation files correlation + auto installationMetadata = m_installedFilesCorrelation->CorrelateForNewlyInstalled(m_incomingManifest, arpInstallLocation); + + if (installationMetadata.InstalledFiles.HasData() || !installationMetadata.StartupLinkFiles.empty()) + { + // Add or update the metadata for the installer hash + auto itr = m_outputMetadata.InstallerMetadataMap.find(m_installerHash); + + if (itr == m_outputMetadata.InstallerMetadataMap.end()) + { + // New entry needed + ProductMetadata::InstallerMetadata newMetadata; + + newMetadata.SubmissionIdentifier = m_submissionIdentifier; + + if (installationMetadata.InstalledFiles.HasData()) + { + newMetadata.InstalledFiles = std::move(installationMetadata.InstalledFiles); + } + if (!installationMetadata.StartupLinkFiles.empty()) + { + newMetadata.StartupLinkFiles = std::move(installationMetadata.StartupLinkFiles); + } + + m_outputMetadata.InstallerMetadataMap[m_installerHash] = std::move(newMetadata); + } + else + { + // Add new or merge with existing entry + if (installationMetadata.InstalledFiles.HasData()) + { + if (!itr->second.InstalledFiles.has_value()) + { + itr->second.InstalledFiles = std::move(installationMetadata.InstalledFiles); + } + else + { + MergeInstalledFilesMetadata(*(itr->second.InstalledFiles), installationMetadata.InstalledFiles); + } + } + + if (!installationMetadata.StartupLinkFiles.empty()) + { + if (!itr->second.StartupLinkFiles.has_value()) + { + itr->second.StartupLinkFiles = std::move(installationMetadata.StartupLinkFiles); + } + else + { + MergeStartupLinkFilesMetadata(*(itr->second.StartupLinkFiles), installationMetadata.StartupLinkFiles); + } + } + } + } + + if (correlationResult.Package) + { + m_outputStatus = OutputStatus::Success; + } + else + { + m_outputStatus = OutputStatus::LowConfidence; + } + + // Create the diagnostics data, based on the other values from the correlation result. + DiagnosticFields fields; + + m_outputDiagnostics[fields.Reason] = AppInstaller::JSON::GetStringValue(correlationResult.Reason); + m_outputDiagnostics[fields.ChangedEntryCount] = web::json::value::number(static_cast(correlationResult.ChangesToARP)); + m_outputDiagnostics[fields.MatchedEntryCount] = web::json::value::number(static_cast(correlationResult.MatchesInARP)); + m_outputDiagnostics[fields.IntersectionCount] = web::json::value::number(static_cast(correlationResult.CountOfIntersectionOfChangesAndMatches)); + + constexpr size_t MaximumDiagnosticMeasures = 10; + web::json::value measuresArray = web::json::value::array(); + for (size_t i = 0; i < correlationResult.Measures.size() && i < MaximumDiagnosticMeasures; ++i) + { + web::json::value measureValue; + const auto& measure = correlationResult.Measures[i]; + + measureValue[fields.Value] = web::json::value::number(measure.Measure); + measureValue[fields.Name] = AppInstaller::JSON::GetStringValue(measure.Package->GetProperty(PackageVersionProperty::Name)); + measureValue[fields.Publisher] = AppInstaller::JSON::GetStringValue(measure.Package->GetProperty(PackageVersionProperty::Publisher)); + + measuresArray[i] = std::move(measureValue); + } + + m_outputDiagnostics[fields.CorrelationMeasures] = std::move(measuresArray); + } + + void InstallerMetadataCollectionContext::ParseInputJson_1_0(web::json::value& input) + { + AICLI_LOG(Repo, Info, << "Parsing input JSON 1.0 fields"); + + // Field names + utility::string_t metadataVersionFieldName = L"supportedMetadataVersion"; + utility::string_t metadataFieldName = L"currentMetadata"; + utility::string_t submissionDataFieldName = L"submissionData"; + utility::string_t submissionIdentifierFieldName = L"submissionIdentifier"; + utility::string_t packageDataFieldName = L"packageData"; + utility::string_t installerHashFieldName = L"installerHash"; + utility::string_t defaultLocaleFieldName = L"DefaultLocale"; + utility::string_t localesFieldName = L"Locales"; + + // root fields + m_supportedMetadataVersion = Version{ GetRequiredString(input, metadataVersionFieldName) }; + + auto currentMetadataValue = AppInstaller::JSON::GetJsonValueFromNode(input, metadataFieldName); + if (currentMetadataValue) + { + m_currentMetadata.FromJson(currentMetadataValue.value()); + } + + // submissionData fields + auto submissionDataValue = AppInstaller::JSON::GetJsonValueFromNode(input, submissionDataFieldName); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !submissionDataValue); + m_submissionData = submissionDataValue.value(); + + m_submissionIdentifier = GetRequiredString(m_submissionData, submissionIdentifierFieldName); + + // packageData fields + auto packageDataValue = AppInstaller::JSON::GetJsonValueFromNode(input, packageDataFieldName); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !packageDataValue); + + m_installerHash = GetRequiredString(packageDataValue.value(), installerHashFieldName); + + // The 1.0 version of input uses the 1.5 version of REST + JSON::ManifestJSONParser parser{ Version{ "1.5" }}; + + { + auto defaultLocaleValue = AppInstaller::JSON::GetJsonValueFromNode(packageDataValue.value(), defaultLocaleFieldName); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !defaultLocaleValue); + + auto defaultLocale = parser.DeserializeLocale(defaultLocaleValue.value()); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, + !defaultLocale || + !defaultLocale->Contains(Manifest::Localization::PackageName) || + !defaultLocale->Contains(Manifest::Localization::Publisher)); + + m_incomingManifest.DefaultLocalization = std::move(defaultLocale).value(); + + auto localesArray = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(packageDataValue.value(), localesFieldName); + if (localesArray) + { + for (const auto& locale : localesArray->get()) + { + auto localization = parser.DeserializeLocale(locale); + if (localization) + { + m_incomingManifest.Localizations.emplace_back(std::move(localization).value()); + } + } + } + } + } + + web::json::value InstallerMetadataCollectionContext::CreateOutputJson_1_0() + { + AICLI_LOG(Repo, Info, << "Setting output JSON 1.0 fields"); + + OutputFields_1_0 fields; + + web::json::value result; + + result[fields.Version] = web::json::value::string(L"1.0"); + result[fields.SubmissionData] = m_submissionData; + result[fields.InstallerHash] = AppInstaller::JSON::GetStringValue(m_installerHash); + + // Limit output status to 1.0 known values + OutputStatus statusToUse = OutputStatus::Unknown; + if (m_outputStatus == OutputStatus::Success || m_outputStatus == OutputStatus::Error || m_outputStatus == OutputStatus::LowConfidence) + { + statusToUse = m_outputStatus; + } + result[fields.Status] = web::json::value::string(ToString(statusToUse)); + + if (m_outputStatus == OutputStatus::Success) + { + result[fields.Metadata] = m_outputMetadata.ToJson(m_supportedMetadataVersion, 0); + } + + result[fields.Diagnostics] = m_outputDiagnostics; + + return result; + } + + utility::string_t InstallerMetadataCollectionContext::ToString(OutputStatus status) + { + switch (status) + { + case OutputStatus::Success: return L"Success"; + case OutputStatus::Error: return L"Error"; + case OutputStatus::LowConfidence: return L"LowConfidence"; + } + + // For both the status value of Unknown and anything else + return L"Unknown"; + } + + bool InstallerMetadataCollectionContext::ContainsError() const + { + return m_outputStatus == OutputStatus::Error; + } + + void InstallerMetadataCollectionContext::CollectErrorDataFromException(std::exception_ptr exception) + { + m_outputStatus = OutputStatus::Error; + + try + { + std::rethrow_exception(exception); + } + catch (const wil::ResultException& re) + { + m_errorHR = re.GetErrorCode(); + m_errorText = GetUserPresentableMessage(re); + } + catch (const winrt::hresult_error& hre) + { + m_errorHR = hre.code(); + m_errorText = GetUserPresentableMessage(hre); + } + catch (const std::exception& e) + { + m_errorHR = E_FAIL; + m_errorText = GetUserPresentableMessage(e); + } + catch (...) + { + m_errorHR = E_UNEXPECTED; + m_errorText = "An unexpected exception type was thrown."; + } + } + + web::json::value InstallerMetadataCollectionContext::CreateErrorJson_1_0() + { + AICLI_LOG(Repo, Info, << "Setting error JSON 1.0 fields"); + + OutputFields_1_0 fields; + DiagnosticFields diagnosticFields; + + web::json::value result; + + result[fields.Version] = web::json::value::string(L"1.0"); + result[fields.SubmissionData] = m_submissionData; + result[fields.InstallerHash] = AppInstaller::JSON::GetStringValue(m_installerHash); + result[fields.Status] = web::json::value::string(ToString(OutputStatus::Error)); + result[fields.Metadata] = web::json::value::null(); + + web::json::value error; + + error[diagnosticFields.ErrorHR] = web::json::value::number(static_cast(m_errorHR)); + error[diagnosticFields.ErrorText] = AppInstaller::JSON::GetStringValue(m_errorText); + + result[fields.Diagnostics] = std::move(error); + + return result; + } + + web::json::value InstallerMetadataCollectionContext::Merge_1_0(web::json::value& input, size_t maximumSizeInBytes) + { + AICLI_LOG(Repo, Info, << "Merging 1.0 input metadatas"); + + utility::string_t metadatasFieldName = L"metadatas"; + + auto metadatasValue = AppInstaller::JSON::GetRawJsonArrayFromJsonNode(input, metadatasFieldName); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, !metadatasValue); + + std::vector metadatas; + for (const auto& value : metadatasValue->get()) + { + ProductMetadata current; + current.FromJson(value); + metadatas.emplace_back(std::move(current)); + } + + THROW_HR_IF(E_NOT_SET, metadatas.empty()); + + // Require that all merging values use the same submission + for (const ProductMetadata& metadata : metadatas) + { + const std::string& firstSubmission = metadatas[0].InstallerMetadataMap.begin()->second.SubmissionIdentifier; + const std::string& metadataSubmission = metadata.InstallerMetadataMap.begin()->second.SubmissionIdentifier; + if (firstSubmission != metadataSubmission) + { + AICLI_LOG(Repo, Info, << "Found submission identifier mismatch: " << firstSubmission << " != " << metadataSubmission); + THROW_HR(E_NOT_VALID_STATE); + } + } + + // Do the actual merging + ProductMetadata resultMetadata; + + // The historical data should be the same across the board, so we can just copy the first one. + resultMetadata.HistoricalMetadataList = metadatas[0].HistoricalMetadataList; + + for (const ProductMetadata& metadata : metadatas) + { + // Get the minimum and maximum versions from the individual values + if (resultMetadata.ProductVersionMin.IsEmpty() || metadata.ProductVersionMin < resultMetadata.ProductVersionMin) + { + resultMetadata.ProductVersionMin = metadata.ProductVersionMin; + } + + if (resultMetadata.ProductVersionMax < metadata.ProductVersionMax) + { + resultMetadata.ProductVersionMax = metadata.ProductVersionMax; + } + + if (resultMetadata.SchemaVersion < metadata.SchemaVersion) + { + resultMetadata.SchemaVersion = metadata.SchemaVersion; + } + + for (const auto& installerMetadata : metadata.InstallerMetadataMap) + { + auto itr = resultMetadata.InstallerMetadataMap.find(installerMetadata.first); + if (itr == resultMetadata.InstallerMetadataMap.end()) + { + // Installer hash not in the result, so just copy it + resultMetadata.InstallerMetadataMap.emplace(installerMetadata); + } + else + { + if (itr->second.Scope.empty()) + { + itr->second.Scope = installerMetadata.second.Scope; + } + else if (!installerMetadata.second.Scope.empty()) + { + // If there is a conflicting scope already present, force it to Unknown + if (Manifest::ConvertToScopeEnum(itr->second.Scope) != Manifest::ConvertToScopeEnum(installerMetadata.second.Scope)) + { + itr->second.Scope = Manifest::ScopeToString(Manifest::ScopeEnum::Unknown); + } + } + + // We will always use the latest extracted icons upon confliction. + if (!installerMetadata.second.Icons.empty()) + { + itr->second.Icons = installerMetadata.second.Icons; + } + + if (!itr->second.InstalledFiles.has_value()) + { + itr->second.InstalledFiles = installerMetadata.second.InstalledFiles; + } + else if (installerMetadata.second.InstalledFiles.has_value()) + { + MergeInstalledFilesMetadata(*(itr->second.InstalledFiles), *(installerMetadata.second.InstalledFiles)); + } + + if (!itr->second.StartupLinkFiles.has_value()) + { + itr->second.StartupLinkFiles = installerMetadata.second.StartupLinkFiles; + } + else if (installerMetadata.second.StartupLinkFiles.has_value()) + { + MergeStartupLinkFilesMetadata(*(itr->second.StartupLinkFiles), *(installerMetadata.second.StartupLinkFiles)); + } + + // Merge into existing installer data + for (const auto& targetEntry : installerMetadata.second.AppsAndFeaturesEntries) + { + FilterAndAddToEntries(Manifest::AppsAndFeaturesEntry{ targetEntry }, itr->second.AppsAndFeaturesEntries); + } + } + } + } + + // Convert to JSON + return resultMetadata.ToJson(resultMetadata.SchemaVersion, maximumSizeInBytes); + } +} diff --git a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp index 1043dc83f5..4e77e823fa 100644 --- a/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp +++ b/src/AppInstallerRepositoryCore/ManifestJSONParser.cpp @@ -1,109 +1,109 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/ManifestJSONParser.h" -#include "Rest/Schema/1_0/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_1/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_4/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_5/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_6/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_7/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_9/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_10/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_12/Json/ManifestDeserializer.h" -#include "Rest/Schema/1_28/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::JSON -{ - struct ManifestJSONParser::impl - { - // The deserializer. We only have one lineage (1.0+) right now. - std::unique_ptr m_deserializer; - }; - - ManifestJSONParser::ManifestJSONParser(const Utility::Version& responseSchemaVersion) - { - const auto& parts = responseSchemaVersion.GetParts(); - THROW_HR_IF(E_INVALIDARG, parts.empty()); - - m_pImpl = std::make_unique(); - - if (parts[0].Integer == 1) - { - if (parts.size() == 1 || parts[1].Integer == 0) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 4) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 5) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 6) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 7) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 9) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 10) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 12) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else if (parts.size() > 1 && parts[1].Integer < 28) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else - { - m_pImpl->m_deserializer = std::make_unique(); - } - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - } - - ManifestJSONParser::ManifestJSONParser(ManifestJSONParser&&) noexcept = default; - ManifestJSONParser& ManifestJSONParser::operator=(ManifestJSONParser&&) noexcept = default; - - ManifestJSONParser::~ManifestJSONParser() = default; - - std::vector ManifestJSONParser::Deserialize(const web::json::value& response) const - { - return m_pImpl->m_deserializer->Deserialize(response); - } - - std::vector ManifestJSONParser::DeserializeData(const web::json::value& data) const - { - return m_pImpl->m_deserializer->DeserializeData(data); - } - - std::vector ManifestJSONParser::DeserializeAppsAndFeaturesEntries(const web::json::array& data) const - { - return m_pImpl->m_deserializer->DeserializeAppsAndFeaturesEntries(data); - } - - std::optional ManifestJSONParser::DeserializeLocale(const web::json::value& locale) const - { - return m_pImpl->m_deserializer->DeserializeLocale(locale); - } - - std::optional ManifestJSONParser::DeserializeInstallationMetadata(const web::json::value& installationMetadata) const - { - return m_pImpl->m_deserializer->DeserializeInstallationMetadata(installationMetadata); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/ManifestJSONParser.h" +#include "Rest/Schema/1_0/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_1/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_4/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_5/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_6/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_7/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_9/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_10/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_12/Json/ManifestDeserializer.h" +#include "Rest/Schema/1_28/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::JSON +{ + struct ManifestJSONParser::impl + { + // The deserializer. We only have one lineage (1.0+) right now. + std::unique_ptr m_deserializer; + }; + + ManifestJSONParser::ManifestJSONParser(const Utility::Version& responseSchemaVersion) + { + const auto& parts = responseSchemaVersion.GetParts(); + THROW_HR_IF(E_INVALIDARG, parts.empty()); + + m_pImpl = std::make_unique(); + + if (parts[0].Integer == 1) + { + if (parts.size() == 1 || parts[1].Integer == 0) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 4) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 5) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 6) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 7) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 9) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 10) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 12) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else if (parts.size() > 1 && parts[1].Integer < 28) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else + { + m_pImpl->m_deserializer = std::make_unique(); + } + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + } + + ManifestJSONParser::ManifestJSONParser(ManifestJSONParser&&) noexcept = default; + ManifestJSONParser& ManifestJSONParser::operator=(ManifestJSONParser&&) noexcept = default; + + ManifestJSONParser::~ManifestJSONParser() = default; + + std::vector ManifestJSONParser::Deserialize(const web::json::value& response) const + { + return m_pImpl->m_deserializer->Deserialize(response); + } + + std::vector ManifestJSONParser::DeserializeData(const web::json::value& data) const + { + return m_pImpl->m_deserializer->DeserializeData(data); + } + + std::vector ManifestJSONParser::DeserializeAppsAndFeaturesEntries(const web::json::array& data) const + { + return m_pImpl->m_deserializer->DeserializeAppsAndFeaturesEntries(data); + } + + std::optional ManifestJSONParser::DeserializeLocale(const web::json::value& locale) const + { + return m_pImpl->m_deserializer->DeserializeLocale(locale); + } + + std::optional ManifestJSONParser::DeserializeInstallationMetadata(const web::json::value& installationMetadata) const + { + return m_pImpl->m_deserializer->DeserializeInstallationMetadata(installationMetadata); + } +} diff --git a/src/AppInstallerRepositoryCore/MatchCriteriaResolver.cpp b/src/AppInstallerRepositoryCore/MatchCriteriaResolver.cpp index 48074c62e9..46d7fd72b5 100644 --- a/src/AppInstallerRepositoryCore/MatchCriteriaResolver.cpp +++ b/src/AppInstallerRepositoryCore/MatchCriteriaResolver.cpp @@ -87,84 +87,84 @@ namespace AppInstaller::Repository THROW_HR(E_UNEXPECTED); } } - + // Gets the best match type for the given field value and required minimum match type. std::optional GetBestMatchType(const RequestMatch& request, MatchType mustBeBetterThanMatchType, const Utility::NormalizedString& value) - { - if (request.Value.empty()) + { + if (request.Value.empty()) { return std::nullopt; - } - - for (auto matchType : { MatchType::Exact, MatchType::CaseInsensitive, MatchType::StartsWith, MatchType::Substring }) + } + + for (auto matchType : { MatchType::Exact, MatchType::CaseInsensitive, MatchType::StartsWith, MatchType::Substring }) { - if (matchType >= mustBeBetterThanMatchType) - { + if (matchType >= mustBeBetterThanMatchType) + { break; - } + } auto matchFunction = GetMatchTypeFunction(matchType); - if (matchFunction(value, request.Value)) + if (matchFunction(value, request.Value)) { return matchType; } - } + } return std::nullopt; } - + // Gets the best match type for the given field value and required minimum match type. std::optional GetBestMatchType(const SearchRequest& request, PackageMatchField field, MatchType mustBeBetterThanMatchType, const Utility::NormalizedString& value) - { - std::optional result; - - if (request.Query) + { + std::optional result; + + if (request.Query) { - result = GetBestMatchType(request.Query.value(), mustBeBetterThanMatchType, value); - - if (result) + result = GetBestMatchType(request.Query.value(), mustBeBetterThanMatchType, value); + + if (result) { mustBeBetterThanMatchType = result.value(); } - } - - for (const auto& filter : request.Filters) + } + + for (const auto& filter : request.Filters) { - if (result.value_or(MatchType::Wildcard) == MatchType::Exact) + if (result.value_or(MatchType::Wildcard) == MatchType::Exact) { break; - } - - if (filter.Field == field) - { - std::optional filterResult = GetBestMatchType(filter, mustBeBetterThanMatchType, value); - - if (filterResult) - { + } + + if (filter.Field == field) + { + std::optional filterResult = GetBestMatchType(filter, mustBeBetterThanMatchType, value); + + if (filterResult) + { result = std::move(filterResult); mustBeBetterThanMatchType = result.value(); - } + } } - } - + } + return result; - } - - // Gets the best match and updates the result if it should be updated. - // Returns true to indicate that an exact match has been found. - bool UpdatePackageMatchFilterCheck(const SearchRequest& request, PackageMatchField field, PackageMatchFilter& result, const Utility::LocIndString& propertyValue) - { - Utility::NormalizedString normalizedValue = propertyValue.get(); - auto bestMatch = GetBestMatchType(request, field, result.Type, normalizedValue); - - if (bestMatch && bestMatch.value() < result.Type) + } + + // Gets the best match and updates the result if it should be updated. + // Returns true to indicate that an exact match has been found. + bool UpdatePackageMatchFilterCheck(const SearchRequest& request, PackageMatchField field, PackageMatchFilter& result, const Utility::LocIndString& propertyValue) + { + Utility::NormalizedString normalizedValue = propertyValue.get(); + auto bestMatch = GetBestMatchType(request, field, result.Type, normalizedValue); + + if (bestMatch && bestMatch.value() < result.Type) { - result.Type = bestMatch.value(); - result.Field = field; + result.Type = bestMatch.value(); + result.Field = field; result.Value = std::move(normalizedValue); - } - + } + return MatchType::Exact == result.Type; } } @@ -180,9 +180,9 @@ namespace AppInstaller::Repository if (propertyValue.empty()) { continue; - } - - if (UpdatePackageMatchFilterCheck(request, field, result, propertyValue)) + } + + if (UpdatePackageMatchFilterCheck(request, field, result, propertyValue)) { break; } @@ -192,26 +192,26 @@ namespace AppInstaller::Repository for (auto field : { PackageMatchField::Command, PackageMatchField::Tag, PackageMatchField::PackageFamilyName, PackageMatchField::ProductCode, PackageMatchField::UpgradeCode }) { - if (MatchType::Exact == result.Type) + if (MatchType::Exact == result.Type) { break; - } + } auto propertyValues = packageVersion->GetMultiProperty(GetPackageVersionMultiPropertyFor(field)); if (propertyValues.empty()) { continue; - } - - for (const auto& propertyValue : propertyValues) - { - if (UpdatePackageMatchFilterCheck(request, field, result, propertyValue)) + } + + for (const auto& propertyValue : propertyValues) + { + if (UpdatePackageMatchFilterCheck(request, field, result, propertyValue)) { break; - } + } } - } - + } + return result; } } diff --git a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp index 063e02284a..0d436f5b0d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.cpp @@ -1,588 +1,588 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ARPHelper.h" -#include "winget/PortableARPEntry.h" - -namespace AppInstaller::Repository::Microsoft -{ - using namespace AppInstaller::Registry::Portable; - - namespace - { - // "Unpacks" a GUID in the format used by the UpgradesCode registry key into the usual format. - // Returns empty if it is not a valid GUID - std::optional TryUnpackUpgradeCodeGuid(std::string_view packed) - { - // A GUID is made up of 4 parts: - // - Part 1 is made up of one 4 byte block - // - Parts 2 and 3 are made up of one 2 byte block - // - Part 4 is made up of eight 1 byte blocks - // - // The GUID strings we have in the manifests represent all of this in hex in order, - // with dashes between each part, and after the second byte of Part 4. - // The "packed" GUIDs in the registry place the blocks in the same order, - // without dashes and with opposite endian-ness. - // - // For example - // ARP: {FECAFEB5-8D0E-4AE4-8FA0-745BAA835C35} - // FECAFEB5 8D0E 4AE4 8F A0 74 5B AA 83 5C 35 - // Part 1 P2 P3 <------ Part 4 -------> - // 5BEFACEF E0D8 4EA4 F8 0A 47 B5 AA 38 C5 53 - // UpgradeCode: 5BEFACEFE0D84EA4F80A47B5AA38C553 - // - // The conversion can be done by mapping each location in the packed string - // to the appropriate location in the unpacked string. - constexpr size_t PackedLength = 32; - if (packed.length() != PackedLength || !std::all_of(packed.begin(), packed.end(), isxdigit)) - { - return {}; - } - - // PositionMapping[i] is the position to which the i-th char is mapped - // I.e., unpacked[ PositionMapping[i] ] = packed[i] - constexpr size_t PositionMapping[PackedLength] = - { - 8,7,6,5,4,3,2,1, - 13,12,11,10, - 18,17,16,15, - 21,20, 23,22, - 26,25, 28,27, 30,29, 32,31, 34,33, 36,35, - }; - - std::string unpacked("{00000000-0000-0000-0000-000000000000}"); - for (size_t i = 0; i < PackedLength; ++i) - { - unpacked[PositionMapping[i]] = packed[i]; - } - - return unpacked; - } - - // Gets a mapping from ProductCode to UpgradeCode for MSI packages. - std::map GetUpgradeCodes() - { - // The UpgradeCode is not stored in the ARP registry keys, so we have to get it separately. - // We could use MsiGetProductProperty or MsiGetProperty from the MSI API to query it, - // but it is very slow. - // - // The UpgradeCode is also stored in the registry under - // HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes - // (Note that this key is not documented, so it is possible that it will change but very unlikely...) - // - // Under 'UpgradeCodes' there is one key for each upgrade code, and each upgrade code key - // contains the product code as a value. All the upgrade codes and product codes are GUIDs, - // but represented in an unusual way - see TryUnpackUpgradeCodeGuid() - - AICLI_LOG(Repo, Info, << "Reading MSI UpgradeCodes"); - std::map upgradeCodes; - - try - { - // There is no UpgradeCodes key on the x86 view of the registry - Registry::Key upgradeCodesKey = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UpgradeCodes", 0, KEY_READ | KEY_WOW64_64KEY); - - if (upgradeCodesKey) - { - for (const auto& upgradeCodeKeyRef : upgradeCodesKey) - { - std::string keyName; - - try - { - keyName = upgradeCodeKeyRef.Name(); - auto upgradeCode = TryUnpackUpgradeCodeGuid(keyName); - if (upgradeCode) - { - auto upgradeCodeKey = upgradeCodeKeyRef.Open(); - for (const auto& productCodeValue : upgradeCodeKey.Values()) - { - auto productCode = TryUnpackUpgradeCodeGuid(productCodeValue.Name()); - if (productCode) - { - upgradeCodes[*productCode] = *upgradeCode; - } - } - } - } - CATCH_LOG_MSG("Failed to read upgrade code: %hs", keyName.c_str()); - } - } - } - CATCH_LOG_MSG("Failed to read upgrade codes."); - - return upgradeCodes; - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - using GetARPKeyFunc = std::function; - static GetARPKeyFunc s_GetARPKey_Override; - - void SetGetARPKeyOverride(GetARPKeyFunc value) - { - s_GetARPKey_Override = value; - } -#endif - - Registry::Key ARPHelper::GetARPKey(Manifest::ScopeEnum scope, Utility::Architecture architecture) const - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_GetARPKey_Override) - { - return s_GetARPKey_Override(scope, architecture); - } -#endif - - HKEY rootKey = NULL; - - switch (scope) - { - case Manifest::ScopeEnum::User: - rootKey = HKEY_CURRENT_USER; - break; - case Manifest::ScopeEnum::Machine: - rootKey = HKEY_LOCAL_MACHINE; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - bool isValid = false; - REGSAM access = KEY_READ; - - switch (Utility::GetSystemArchitecture()) - { - case Utility::Architecture::X86: - switch (architecture) - { - case Utility::Architecture::X86: - isValid = true; - break; - } - break; - case Utility::Architecture::X64: - switch (architecture) - { - case Utility::Architecture::X86: - if (scope == Manifest::ScopeEnum::Machine) - { - access |= KEY_WOW64_32KEY; - isValid = true; - } - break; - case Utility::Architecture::X64: - access |= KEY_WOW64_64KEY; - isValid = true; - break; - } - break; - case Utility::Architecture::Arm: - switch (architecture) - { - case Utility::Architecture::Arm: - isValid = true; - break; - } - break; - case Utility::Architecture::Arm64: - switch (architecture) - { - case Utility::Architecture::X86: - if (scope == Manifest::ScopeEnum::Machine) - { -#ifdef _ARM_ - // Not accessible if this is an ARM process - AICLI_LOG(Repo, Warning, << "Cannot enumerate x86 machine ARP entries when current process is ARM"); -#else - access |= KEY_WOW64_32KEY; - isValid = true; -#endif - } - break; - case Utility::Architecture::Arm64: - access |= KEY_WOW64_64KEY; - isValid = true; - break; - } - break; - } - - if (isValid) - { - return Registry::Key::OpenIfExists(rootKey, SubKeyPath, 0, access); - } - else - { - return {}; - } - } - - Registry::Key ARPHelper::FindARPEntry(const std::string& productCode, Manifest::ScopeEnum scope) const - { - if (productCode.empty()) - { - return {}; - } - - std::vector scopesToSearch; - if (scope == Manifest::ScopeEnum::Unknown) - { - scopesToSearch = { Manifest::ScopeEnum::User, Manifest::ScopeEnum::Machine }; - } - else - { - scopesToSearch = { scope }; - } - - for (auto scopeToSearch : scopesToSearch) - { - for (auto architecture : Utility::GetApplicableArchitectures()) - { - Registry::Key arpRootKey = GetARPKey(scopeToSearch, architecture); - if (arpRootKey) - { - for (const auto& entry : arpRootKey) - { - if (Utility::CaseInsensitiveEquals(productCode, entry.Name())) - { - return entry.Open(); - } - } - } - } - } - - return {}; - } - - bool ARPHelper::GetBoolValue(const Registry::Key& arpKey, const std::wstring& name) - { - auto value = arpKey[name]; - return (value && value->GetType() == Registry::Value::Type::DWord && value->GetValue()); - } - - std::string ARPHelper::GetStringValue(const Registry::Key& arpKey, const std::wstring& name) - { - auto value = arpKey[name]; - if (value && value->GetType() == Registry::Value::Type::String) - { - return value->GetValue(); - } - - return {}; - } - - std::string ARPHelper::DetermineVersion(const Registry::Key& arpKey) const - { - // First check DisplayVersion for a complete version string - auto displayVersion = arpKey[DisplayVersion]; - if (displayVersion && displayVersion->GetType() == Registry::Value::Type::String) - { - std::string result = displayVersion->GetValue(); - if (!result.empty()) - { - return result; - } - } - - // Next attempt VersionMajor.VersionMinor, then MajorVersion.MinorVersion - for (const auto& names : { std::make_pair(std::ref(VersionMajor), std::ref(VersionMinor)), std::make_pair(std::ref(MajorVersion), std::ref(MinorVersion)) }) - { - auto majorVersion = arpKey[names.first]; - auto minorVersion = arpKey[names.second]; - if (majorVersion || minorVersion) - { - uint32_t majorVersionInt = 0; - uint32_t minorVersionInt = 0; - - if (majorVersion && majorVersion->GetType() == Registry::Value::Type::DWord) - { - majorVersionInt = majorVersion->GetValue(); - } - - if (minorVersion && minorVersion->GetType() == Registry::Value::Type::DWord) - { - minorVersionInt = minorVersion->GetValue(); - } - - if (majorVersionInt || minorVersionInt) - { - std::ostringstream strstr; - strstr << majorVersionInt << '.' << minorVersionInt; - return strstr.str(); - } - } - } - - // Finally attempt to turn the Version DWORD into a version string - auto version = arpKey[Version]; - if (version && version->GetType() == Registry::Value::Type::DWord) - { - uint32_t versionInt = version->GetValue(); - if (versionInt) - { - std::ostringstream strstr; - strstr << ((versionInt & 0xFF000000) >> 24) << '.' << ((versionInt & 0x00FF0000) >> 16) << '.' << (versionInt & 0x0000FFFF); - return strstr.str(); - } - } - - return Utility::Version::CreateUnknown().ToString(); - } - - void ARPHelper::AddMetadataIfPresent(const Registry::Key& key, const std::wstring& name, SQLiteIndex& index, SQLiteIndex::IdType manifestId, PackageVersionMetadata metadata) const - { - auto value = key[name]; - if (value) - { - std::string valueString; - - if (value->GetType() == Registry::Value::Type::String) - { - valueString = value->GetValue(); - } - else if (value->GetType() == Registry::Value::Type::ExpandString) - { - valueString = value->GetValue(); - } - else if (value->GetType() == Registry::Value::Type::DWord) - { - DWORD dwordValue = value->GetValue(); - if (name == Language) - { - valueString = Locale::LocaleIdToBcp47Tag(dwordValue); - } - else - { - std::ostringstream strstr; - strstr << dwordValue; - valueString = strstr.str(); - } - } - - if (!valueString.empty()) - { - index.SetMetadataByManifestId(manifestId, metadata, valueString); - } - } - } - - void ARPHelper::PopulateIndexFromARP(SQLiteIndex& index, Manifest::ScopeEnum scope) const - { - auto upgradeCodes = GetUpgradeCodes(); - - for (auto architecture : Utility::GetApplicableArchitectures()) - { - Registry::Key arpRootKey = GetARPKey(scope, architecture); - - if (arpRootKey) - { - PopulateIndexFromKey(index, arpRootKey, Manifest::ScopeToString(scope), Utility::ToString(architecture), upgradeCodes); - } - } - } - - void ARPHelper::PopulateIndexFromKey(SQLiteIndex& index, const Registry::Key& key, std::string_view scope, std::string_view architecture, const std::map& upgradeCodes) const - { - AICLI_LOG(Repo, Verbose, << "Examining ARP entries for " << scope << " | " << architecture); - - for (const auto& arpEntry : key) - { - std::string productCode; - - try - { - productCode = arpEntry.Name(); - - Manifest::Manifest manifest; - manifest.DefaultLocalization.Add({ "ARP" }); - - // Construct a unique name for this entry - const char separator = '\\'; - - std::ostringstream stream; - stream << "ARP" << separator << scope << separator << architecture << separator << productCode; - - manifest.Id = stream.str(); - - manifest.Installers.emplace_back(); - // TODO: This likely needs some cleanup applied, as it looks like INNO tends to append an "_is#" - // that might vary across machines/installs. There may be other things we want to clean up as well, - // like trimming spaces at the ends, or removing the version string from the product code - // if it is present. - manifest.Installers[0].ProductCode = productCode; - - Registry::Key arpKey = arpEntry.Open(); - - // Ignore entries that are listed as SystemComponent - if (GetBoolValue(arpKey, SystemComponent)) - { - AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because it is a SystemComponent"); - continue; - } - - // If no name is provided, ignore this entry - auto displayName = arpKey[DisplayName]; - if (!displayName || displayName->GetType() != Registry::Value::Type::String) - { - AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is not a REG_SZ value"); - continue; - } - auto displayNameValue = displayName->GetValue(); - if (displayNameValue.empty()) - { - AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is empty"); - continue; - } - - manifest.DefaultLocalization.Add(displayNameValue); - // Add DisplayName to ARP entries too - // This is to help normalized publisher and name correlation where ARP DisplayName matching - // will be getting improved in future iterations. - manifest.Installers[0].AppsAndFeaturesEntries.emplace_back(); - manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayName = displayNameValue; - - // If no version can be determined, ignore this entry - manifest.Version = DetermineVersion(arpKey); - if (manifest.Version.empty()) - { - AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because a version could not be determined"); - continue; - } - - auto publisher = arpKey[Publisher]; - if (publisher && publisher->GetType() == Registry::Value::Type::String) - { - manifest.DefaultLocalization.Add(publisher->GetValue()); - - // If Publisher is set, change the Id using name normalization - // TODO: Figure out how to actually make this work since there are often instances of the same - // data in x64 and x86 entries that will collide. - //auto normalizedName = index.NormalizeName( - // manifest.DefaultLocalization.Get(), - // manifest.DefaultLocalization.Get()); - //manifest.Id = normalizedName.Publisher() + '.' + normalizedName.Name(); - } - - // Pick up WindowsInstaller to determine if this is an MSI install. - // TODO: Could also determine Inno (and maybe other types) through detecting other keys here. - auto installedType = Manifest::InstallerTypeEnum::Exe; - - if (GetBoolValue(arpKey, WindowsInstaller)) - { - installedType = Manifest::InstallerTypeEnum::Msi; - - // If this is an MSI, look up the UpgradeCode - auto upgradeCodeItr = upgradeCodes.find(productCode); - if (upgradeCodeItr != upgradeCodes.end()) - { - manifest.Installers[0].AppsAndFeaturesEntries[0].UpgradeCode = upgradeCodeItr->second; - } - } - - // TODO: If we want to keep the constructed manifest around to allow for `show` type commands - // against installed packages, we should use URLInfoAbout/HelpLink for the Homepage. - - // TODO: Determine the best way to handle duplicates; sometimes the same package will be listed under - // both x64 and x86 locations for ARP. - // For now, we will attempt to insert and catch. - std::optional manifestIdOpt; - - try - { - // Use the ProductCode as a unique key for the path - manifestIdOpt = index.AddManifest(manifest); - } - catch (...) - { - // Ignore errors if they occur, they are most likely a duplicate value - } - - if (!manifestIdOpt) - { - AICLI_LOG(Repo, Warning, - << "Ignoring duplicate ARP entry " << scope << '|' << architecture << '|' << productCode << " [" << manifest.DefaultLocalization.Get() << "]"); - continue; - } - - SQLiteIndex::IdType manifestId = manifestIdOpt.value(); - - // Pass scope along to metadata. - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledScope, scope); - - // TODO: Pass along architecture, although there are cases where it is not clear what architecture the package - // is from it's ARP location, despite it very clearly being a specific architecture. And note that user - // scope does not have separate ARP locations, so every architecture would appear as native. - - // Publisher is needed for certain scenarios but we don't store it from the manifest - if (manifest.DefaultLocalization.Contains(Manifest::Localization::Publisher)) - { - index.SetMetadataByManifestId( - manifestId, PackageVersionMetadata::Publisher, - manifest.DefaultLocalization.Get()); - } - - // Pick up InstallLocation when upgrade supports remove/install to enable this location - // to survive across the removal. - AddMetadataIfPresent(arpKey, InstallLocation, index, manifestId, PackageVersionMetadata::InstalledLocation); - - // Pick up UninstallString and QuietUninstallString for uninstall. - AddMetadataIfPresent(arpKey, UninstallString, index, manifestId, PackageVersionMetadata::StandardUninstallCommand); - AddMetadataIfPresent(arpKey, QuietUninstallString, index, manifestId, PackageVersionMetadata::SilentUninstallCommand); - - // Pick up ModifyPath for repair. - AddMetadataIfPresent(arpKey, ModifyPath, index, manifestId, PackageVersionMetadata::StandardModifyCommand); - AddMetadataIfPresent(arpKey, NoModify, index, manifestId, PackageVersionMetadata::NoModify); - AddMetadataIfPresent(arpKey, NoRepair, index, manifestId, PackageVersionMetadata::NoRepair); - - // Pick up Language to enable proper selection of language for upgrade. - AddMetadataIfPresent(arpKey, Language, index, manifestId, PackageVersionMetadata::InstalledLocale); - - if (Manifest::ConvertToInstallerTypeEnum(GetStringValue(arpKey, std::wstring{ ToString(PortableValueName::WinGetInstallerType) })) == Manifest::InstallerTypeEnum::Portable) - { - // Portable uninstall requires the installed architecture for locating the entry in the registry. - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, architecture); - installedType = Manifest::InstallerTypeEnum::Portable; - } - - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, Manifest::InstallerTypeToString(installedType)); - } - catch (...) - { - AICLI_LOG(Repo, Warning, << "Failed to read ARP entry, ignoring it: " << scope << '|' << architecture << '|' << productCode); - LOG_CAUGHT_EXCEPTION(); - } - } - } - - std::vector ARPHelper::CreateRegistryWatchers(Manifest::ScopeEnum scope, std::function callback) - { - std::vector result; - - auto addToResult = [&](Manifest::ScopeEnum scopeToUse) - { - for (auto architecture : Utility::GetApplicableArchitectures()) - { - Registry::Key arpRootKey = GetARPKey(scopeToUse, architecture); - - if (arpRootKey) - { - result.emplace_back(wil::make_registry_watcher(arpRootKey, L"", true, [scopeToUse, architecture, callback](wil::RegistryChangeKind change) { callback(scopeToUse, architecture, change); })); - } - } - }; - - if (scope == Manifest::ScopeEnum::Unknown) - { - addToResult(Manifest::ScopeEnum::User); - addToResult(Manifest::ScopeEnum::Machine); - } - else - { - addToResult(scope); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ARPHelper.h" +#include "winget/PortableARPEntry.h" + +namespace AppInstaller::Repository::Microsoft +{ + using namespace AppInstaller::Registry::Portable; + + namespace + { + // "Unpacks" a GUID in the format used by the UpgradesCode registry key into the usual format. + // Returns empty if it is not a valid GUID + std::optional TryUnpackUpgradeCodeGuid(std::string_view packed) + { + // A GUID is made up of 4 parts: + // - Part 1 is made up of one 4 byte block + // - Parts 2 and 3 are made up of one 2 byte block + // - Part 4 is made up of eight 1 byte blocks + // + // The GUID strings we have in the manifests represent all of this in hex in order, + // with dashes between each part, and after the second byte of Part 4. + // The "packed" GUIDs in the registry place the blocks in the same order, + // without dashes and with opposite endian-ness. + // + // For example + // ARP: {FECAFEB5-8D0E-4AE4-8FA0-745BAA835C35} + // FECAFEB5 8D0E 4AE4 8F A0 74 5B AA 83 5C 35 + // Part 1 P2 P3 <------ Part 4 -------> + // 5BEFACEF E0D8 4EA4 F8 0A 47 B5 AA 38 C5 53 + // UpgradeCode: 5BEFACEFE0D84EA4F80A47B5AA38C553 + // + // The conversion can be done by mapping each location in the packed string + // to the appropriate location in the unpacked string. + constexpr size_t PackedLength = 32; + if (packed.length() != PackedLength || !std::all_of(packed.begin(), packed.end(), isxdigit)) + { + return {}; + } + + // PositionMapping[i] is the position to which the i-th char is mapped + // I.e., unpacked[ PositionMapping[i] ] = packed[i] + constexpr size_t PositionMapping[PackedLength] = + { + 8,7,6,5,4,3,2,1, + 13,12,11,10, + 18,17,16,15, + 21,20, 23,22, + 26,25, 28,27, 30,29, 32,31, 34,33, 36,35, + }; + + std::string unpacked("{00000000-0000-0000-0000-000000000000}"); + for (size_t i = 0; i < PackedLength; ++i) + { + unpacked[PositionMapping[i]] = packed[i]; + } + + return unpacked; + } + + // Gets a mapping from ProductCode to UpgradeCode for MSI packages. + std::map GetUpgradeCodes() + { + // The UpgradeCode is not stored in the ARP registry keys, so we have to get it separately. + // We could use MsiGetProductProperty or MsiGetProperty from the MSI API to query it, + // but it is very slow. + // + // The UpgradeCode is also stored in the registry under + // HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Installer\UpgradeCodes + // (Note that this key is not documented, so it is possible that it will change but very unlikely...) + // + // Under 'UpgradeCodes' there is one key for each upgrade code, and each upgrade code key + // contains the product code as a value. All the upgrade codes and product codes are GUIDs, + // but represented in an unusual way - see TryUnpackUpgradeCodeGuid() + + AICLI_LOG(Repo, Info, << "Reading MSI UpgradeCodes"); + std::map upgradeCodes; + + try + { + // There is no UpgradeCodes key on the x86 view of the registry + Registry::Key upgradeCodesKey = Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer\\UpgradeCodes", 0, KEY_READ | KEY_WOW64_64KEY); + + if (upgradeCodesKey) + { + for (const auto& upgradeCodeKeyRef : upgradeCodesKey) + { + std::string keyName; + + try + { + keyName = upgradeCodeKeyRef.Name(); + auto upgradeCode = TryUnpackUpgradeCodeGuid(keyName); + if (upgradeCode) + { + auto upgradeCodeKey = upgradeCodeKeyRef.Open(); + for (const auto& productCodeValue : upgradeCodeKey.Values()) + { + auto productCode = TryUnpackUpgradeCodeGuid(productCodeValue.Name()); + if (productCode) + { + upgradeCodes[*productCode] = *upgradeCode; + } + } + } + } + CATCH_LOG_MSG("Failed to read upgrade code: %hs", keyName.c_str()); + } + } + } + CATCH_LOG_MSG("Failed to read upgrade codes."); + + return upgradeCodes; + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + using GetARPKeyFunc = std::function; + static GetARPKeyFunc s_GetARPKey_Override; + + void SetGetARPKeyOverride(GetARPKeyFunc value) + { + s_GetARPKey_Override = value; + } +#endif + + Registry::Key ARPHelper::GetARPKey(Manifest::ScopeEnum scope, Utility::Architecture architecture) const + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_GetARPKey_Override) + { + return s_GetARPKey_Override(scope, architecture); + } +#endif + + HKEY rootKey = NULL; + + switch (scope) + { + case Manifest::ScopeEnum::User: + rootKey = HKEY_CURRENT_USER; + break; + case Manifest::ScopeEnum::Machine: + rootKey = HKEY_LOCAL_MACHINE; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + bool isValid = false; + REGSAM access = KEY_READ; + + switch (Utility::GetSystemArchitecture()) + { + case Utility::Architecture::X86: + switch (architecture) + { + case Utility::Architecture::X86: + isValid = true; + break; + } + break; + case Utility::Architecture::X64: + switch (architecture) + { + case Utility::Architecture::X86: + if (scope == Manifest::ScopeEnum::Machine) + { + access |= KEY_WOW64_32KEY; + isValid = true; + } + break; + case Utility::Architecture::X64: + access |= KEY_WOW64_64KEY; + isValid = true; + break; + } + break; + case Utility::Architecture::Arm: + switch (architecture) + { + case Utility::Architecture::Arm: + isValid = true; + break; + } + break; + case Utility::Architecture::Arm64: + switch (architecture) + { + case Utility::Architecture::X86: + if (scope == Manifest::ScopeEnum::Machine) + { +#ifdef _ARM_ + // Not accessible if this is an ARM process + AICLI_LOG(Repo, Warning, << "Cannot enumerate x86 machine ARP entries when current process is ARM"); +#else + access |= KEY_WOW64_32KEY; + isValid = true; +#endif + } + break; + case Utility::Architecture::Arm64: + access |= KEY_WOW64_64KEY; + isValid = true; + break; + } + break; + } + + if (isValid) + { + return Registry::Key::OpenIfExists(rootKey, SubKeyPath, 0, access); + } + else + { + return {}; + } + } + + Registry::Key ARPHelper::FindARPEntry(const std::string& productCode, Manifest::ScopeEnum scope) const + { + if (productCode.empty()) + { + return {}; + } + + std::vector scopesToSearch; + if (scope == Manifest::ScopeEnum::Unknown) + { + scopesToSearch = { Manifest::ScopeEnum::User, Manifest::ScopeEnum::Machine }; + } + else + { + scopesToSearch = { scope }; + } + + for (auto scopeToSearch : scopesToSearch) + { + for (auto architecture : Utility::GetApplicableArchitectures()) + { + Registry::Key arpRootKey = GetARPKey(scopeToSearch, architecture); + if (arpRootKey) + { + for (const auto& entry : arpRootKey) + { + if (Utility::CaseInsensitiveEquals(productCode, entry.Name())) + { + return entry.Open(); + } + } + } + } + } + + return {}; + } + + bool ARPHelper::GetBoolValue(const Registry::Key& arpKey, const std::wstring& name) + { + auto value = arpKey[name]; + return (value && value->GetType() == Registry::Value::Type::DWord && value->GetValue()); + } + + std::string ARPHelper::GetStringValue(const Registry::Key& arpKey, const std::wstring& name) + { + auto value = arpKey[name]; + if (value && value->GetType() == Registry::Value::Type::String) + { + return value->GetValue(); + } + + return {}; + } + + std::string ARPHelper::DetermineVersion(const Registry::Key& arpKey) const + { + // First check DisplayVersion for a complete version string + auto displayVersion = arpKey[DisplayVersion]; + if (displayVersion && displayVersion->GetType() == Registry::Value::Type::String) + { + std::string result = displayVersion->GetValue(); + if (!result.empty()) + { + return result; + } + } + + // Next attempt VersionMajor.VersionMinor, then MajorVersion.MinorVersion + for (const auto& names : { std::make_pair(std::ref(VersionMajor), std::ref(VersionMinor)), std::make_pair(std::ref(MajorVersion), std::ref(MinorVersion)) }) + { + auto majorVersion = arpKey[names.first]; + auto minorVersion = arpKey[names.second]; + if (majorVersion || minorVersion) + { + uint32_t majorVersionInt = 0; + uint32_t minorVersionInt = 0; + + if (majorVersion && majorVersion->GetType() == Registry::Value::Type::DWord) + { + majorVersionInt = majorVersion->GetValue(); + } + + if (minorVersion && minorVersion->GetType() == Registry::Value::Type::DWord) + { + minorVersionInt = minorVersion->GetValue(); + } + + if (majorVersionInt || minorVersionInt) + { + std::ostringstream strstr; + strstr << majorVersionInt << '.' << minorVersionInt; + return strstr.str(); + } + } + } + + // Finally attempt to turn the Version DWORD into a version string + auto version = arpKey[Version]; + if (version && version->GetType() == Registry::Value::Type::DWord) + { + uint32_t versionInt = version->GetValue(); + if (versionInt) + { + std::ostringstream strstr; + strstr << ((versionInt & 0xFF000000) >> 24) << '.' << ((versionInt & 0x00FF0000) >> 16) << '.' << (versionInt & 0x0000FFFF); + return strstr.str(); + } + } + + return Utility::Version::CreateUnknown().ToString(); + } + + void ARPHelper::AddMetadataIfPresent(const Registry::Key& key, const std::wstring& name, SQLiteIndex& index, SQLiteIndex::IdType manifestId, PackageVersionMetadata metadata) const + { + auto value = key[name]; + if (value) + { + std::string valueString; + + if (value->GetType() == Registry::Value::Type::String) + { + valueString = value->GetValue(); + } + else if (value->GetType() == Registry::Value::Type::ExpandString) + { + valueString = value->GetValue(); + } + else if (value->GetType() == Registry::Value::Type::DWord) + { + DWORD dwordValue = value->GetValue(); + if (name == Language) + { + valueString = Locale::LocaleIdToBcp47Tag(dwordValue); + } + else + { + std::ostringstream strstr; + strstr << dwordValue; + valueString = strstr.str(); + } + } + + if (!valueString.empty()) + { + index.SetMetadataByManifestId(manifestId, metadata, valueString); + } + } + } + + void ARPHelper::PopulateIndexFromARP(SQLiteIndex& index, Manifest::ScopeEnum scope) const + { + auto upgradeCodes = GetUpgradeCodes(); + + for (auto architecture : Utility::GetApplicableArchitectures()) + { + Registry::Key arpRootKey = GetARPKey(scope, architecture); + + if (arpRootKey) + { + PopulateIndexFromKey(index, arpRootKey, Manifest::ScopeToString(scope), Utility::ToString(architecture), upgradeCodes); + } + } + } + + void ARPHelper::PopulateIndexFromKey(SQLiteIndex& index, const Registry::Key& key, std::string_view scope, std::string_view architecture, const std::map& upgradeCodes) const + { + AICLI_LOG(Repo, Verbose, << "Examining ARP entries for " << scope << " | " << architecture); + + for (const auto& arpEntry : key) + { + std::string productCode; + + try + { + productCode = arpEntry.Name(); + + Manifest::Manifest manifest; + manifest.DefaultLocalization.Add({ "ARP" }); + + // Construct a unique name for this entry + const char separator = '\\'; + + std::ostringstream stream; + stream << "ARP" << separator << scope << separator << architecture << separator << productCode; + + manifest.Id = stream.str(); + + manifest.Installers.emplace_back(); + // TODO: This likely needs some cleanup applied, as it looks like INNO tends to append an "_is#" + // that might vary across machines/installs. There may be other things we want to clean up as well, + // like trimming spaces at the ends, or removing the version string from the product code + // if it is present. + manifest.Installers[0].ProductCode = productCode; + + Registry::Key arpKey = arpEntry.Open(); + + // Ignore entries that are listed as SystemComponent + if (GetBoolValue(arpKey, SystemComponent)) + { + AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because it is a SystemComponent"); + continue; + } + + // If no name is provided, ignore this entry + auto displayName = arpKey[DisplayName]; + if (!displayName || displayName->GetType() != Registry::Value::Type::String) + { + AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is not a REG_SZ value"); + continue; + } + auto displayNameValue = displayName->GetValue(); + if (displayNameValue.empty()) + { + AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because DisplayName is empty"); + continue; + } + + manifest.DefaultLocalization.Add(displayNameValue); + // Add DisplayName to ARP entries too + // This is to help normalized publisher and name correlation where ARP DisplayName matching + // will be getting improved in future iterations. + manifest.Installers[0].AppsAndFeaturesEntries.emplace_back(); + manifest.Installers[0].AppsAndFeaturesEntries[0].DisplayName = displayNameValue; + + // If no version can be determined, ignore this entry + manifest.Version = DetermineVersion(arpKey); + if (manifest.Version.empty()) + { + AICLI_LOG(Repo, Verbose, << "Skipping " << productCode << " because a version could not be determined"); + continue; + } + + auto publisher = arpKey[Publisher]; + if (publisher && publisher->GetType() == Registry::Value::Type::String) + { + manifest.DefaultLocalization.Add(publisher->GetValue()); + + // If Publisher is set, change the Id using name normalization + // TODO: Figure out how to actually make this work since there are often instances of the same + // data in x64 and x86 entries that will collide. + //auto normalizedName = index.NormalizeName( + // manifest.DefaultLocalization.Get(), + // manifest.DefaultLocalization.Get()); + //manifest.Id = normalizedName.Publisher() + '.' + normalizedName.Name(); + } + + // Pick up WindowsInstaller to determine if this is an MSI install. + // TODO: Could also determine Inno (and maybe other types) through detecting other keys here. + auto installedType = Manifest::InstallerTypeEnum::Exe; + + if (GetBoolValue(arpKey, WindowsInstaller)) + { + installedType = Manifest::InstallerTypeEnum::Msi; + + // If this is an MSI, look up the UpgradeCode + auto upgradeCodeItr = upgradeCodes.find(productCode); + if (upgradeCodeItr != upgradeCodes.end()) + { + manifest.Installers[0].AppsAndFeaturesEntries[0].UpgradeCode = upgradeCodeItr->second; + } + } + + // TODO: If we want to keep the constructed manifest around to allow for `show` type commands + // against installed packages, we should use URLInfoAbout/HelpLink for the Homepage. + + // TODO: Determine the best way to handle duplicates; sometimes the same package will be listed under + // both x64 and x86 locations for ARP. + // For now, we will attempt to insert and catch. + std::optional manifestIdOpt; + + try + { + // Use the ProductCode as a unique key for the path + manifestIdOpt = index.AddManifest(manifest); + } + catch (...) + { + // Ignore errors if they occur, they are most likely a duplicate value + } + + if (!manifestIdOpt) + { + AICLI_LOG(Repo, Warning, + << "Ignoring duplicate ARP entry " << scope << '|' << architecture << '|' << productCode << " [" << manifest.DefaultLocalization.Get() << "]"); + continue; + } + + SQLiteIndex::IdType manifestId = manifestIdOpt.value(); + + // Pass scope along to metadata. + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledScope, scope); + + // TODO: Pass along architecture, although there are cases where it is not clear what architecture the package + // is from it's ARP location, despite it very clearly being a specific architecture. And note that user + // scope does not have separate ARP locations, so every architecture would appear as native. + + // Publisher is needed for certain scenarios but we don't store it from the manifest + if (manifest.DefaultLocalization.Contains(Manifest::Localization::Publisher)) + { + index.SetMetadataByManifestId( + manifestId, PackageVersionMetadata::Publisher, + manifest.DefaultLocalization.Get()); + } + + // Pick up InstallLocation when upgrade supports remove/install to enable this location + // to survive across the removal. + AddMetadataIfPresent(arpKey, InstallLocation, index, manifestId, PackageVersionMetadata::InstalledLocation); + + // Pick up UninstallString and QuietUninstallString for uninstall. + AddMetadataIfPresent(arpKey, UninstallString, index, manifestId, PackageVersionMetadata::StandardUninstallCommand); + AddMetadataIfPresent(arpKey, QuietUninstallString, index, manifestId, PackageVersionMetadata::SilentUninstallCommand); + + // Pick up ModifyPath for repair. + AddMetadataIfPresent(arpKey, ModifyPath, index, manifestId, PackageVersionMetadata::StandardModifyCommand); + AddMetadataIfPresent(arpKey, NoModify, index, manifestId, PackageVersionMetadata::NoModify); + AddMetadataIfPresent(arpKey, NoRepair, index, manifestId, PackageVersionMetadata::NoRepair); + + // Pick up Language to enable proper selection of language for upgrade. + AddMetadataIfPresent(arpKey, Language, index, manifestId, PackageVersionMetadata::InstalledLocale); + + if (Manifest::ConvertToInstallerTypeEnum(GetStringValue(arpKey, std::wstring{ ToString(PortableValueName::WinGetInstallerType) })) == Manifest::InstallerTypeEnum::Portable) + { + // Portable uninstall requires the installed architecture for locating the entry in the registry. + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, architecture); + installedType = Manifest::InstallerTypeEnum::Portable; + } + + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, Manifest::InstallerTypeToString(installedType)); + } + catch (...) + { + AICLI_LOG(Repo, Warning, << "Failed to read ARP entry, ignoring it: " << scope << '|' << architecture << '|' << productCode); + LOG_CAUGHT_EXCEPTION(); + } + } + } + + std::vector ARPHelper::CreateRegistryWatchers(Manifest::ScopeEnum scope, std::function callback) + { + std::vector result; + + auto addToResult = [&](Manifest::ScopeEnum scopeToUse) + { + for (auto architecture : Utility::GetApplicableArchitectures()) + { + Registry::Key arpRootKey = GetARPKey(scopeToUse, architecture); + + if (arpRootKey) + { + result.emplace_back(wil::make_registry_watcher(arpRootKey, L"", true, [scopeToUse, architecture, callback](wil::RegistryChangeKind change) { callback(scopeToUse, architecture, change); })); + } + } + }; + + if (scope == Manifest::ScopeEnum::Unknown) + { + addToResult(Manifest::ScopeEnum::User); + addToResult(Manifest::ScopeEnum::Machine); + } + else + { + addToResult(scope); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h index d00e4fd64f..88b4ccac4c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h +++ b/src/AppInstallerRepositoryCore/Microsoft/ARPHelper.h @@ -1,101 +1,101 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/SQLiteIndex.h" -#include -#include -#include -#include -#include - -#include -#include - -namespace AppInstaller::Repository::Microsoft -{ - // A helper to find the various locations that contain ARP (Add/Remove Programs) entries. - struct ARPHelper - { - // See https://docs.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key for details. - const std::wstring SubKeyPath{ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall" }; - - // REG_SZ - const std::wstring DisplayName{ L"DisplayName" }; - // REG_SZ - const std::wstring Publisher{ L"Publisher" }; - // REG_SZ - const std::wstring DisplayVersion{ L"DisplayVersion" }; - // REG_DWORD (ex. 0xMMmmbbbb, M[ajor], m[inor], b[uild]) - const std::wstring Version{ L"Version" }; - // REG_DWORD - const std::wstring VersionMajor{ L"VersionMajor" }; - // REG_DWORD - const std::wstring VersionMinor{ L"VersionMinor" }; - // REG_DWORD - const std::wstring MajorVersion{ L"MajorVersion" }; - // REG_DWORD - const std::wstring MinorVersion{ L"MinorVersion" }; - // REG_SZ - const std::wstring URLInfoAbout{ L"URLInfoAbout" }; - // REG_SZ - const std::wstring HelpLink{ L"HelpLink" }; - // REG_SZ - const std::wstring InstallLocation{ L"InstallLocation" }; - // REG_DWORD (ex. 1033 [en-us]) - const std::wstring Language{ L"Language" }; - // REG_SZ (ex. "english") - const std::wstring InnoSetupLanguage{ L"Inno Setup: Language" }; - // REG_EXPAND_SZ - const std::wstring UninstallString{ L"UninstallString" }; - // REG_EXPAND_SZ - const std::wstring QuietUninstallString{ L"QuietUninstallString" }; - // REG_DWORD (bool, true indicates MSI) - const std::wstring WindowsInstaller{ L"WindowsInstaller" }; - // REG_DWORD (bool) - const std::wstring SystemComponent{ L"SystemComponent" }; - // REG_SZ - const std::wstring DisplayIcon{ L"DisplayIcon" }; - // REG_DWORD - const std::wstring NoModify{ L"NoModify" }; - // REG_DWORD - const std::wstring NoRepair{ L"NoRepair" }; - // REG_SZ - const std::wstring ModifyPath{ L"ModifyPath" }; - - // Gets the registry key associated with the given scope and architecture on this platform. - // May return an empty key if there is no valid location (bad combination or not found). - Registry::Key GetARPKey(Manifest::ScopeEnum scope, Utility::Architecture architecture) const; - - // Gets the arp registry key associated with the given scope and product code. - // May return an empty key if not found. - Registry::Key FindARPEntry(const std::string& productCode, AppInstaller::Manifest::ScopeEnum scope = AppInstaller::Manifest::ScopeEnum::Unknown) const; - - // Returns true IFF the value exists and contains a non-zero DWORD. - static bool GetBoolValue(const Registry::Key& arpKey, const std::wstring& name); - - // Returns the string value if it exists. - static std::string GetStringValue(const Registry::Key& arpKey, const std::wstring& name); - - // Determines the version from an ARP entry. - // The priority is: - // DisplayVersion - // Version - // MajorVersion, MinorVersion - std::string DetermineVersion(const Registry::Key& arpKey) const; - - // Reads a value and adds it to the metadata if it exists. - void AddMetadataIfPresent(const Registry::Key& key, const std::wstring& name, SQLiteIndex& index, SQLiteIndex::IdType manifestId, PackageVersionMetadata metadata) const; - - // Populates the index with the ARP entries from the given scope (machine/user). - // Handles all of the architectures for the given scope. - void PopulateIndexFromARP(SQLiteIndex& index, Manifest::ScopeEnum scope) const; - - // Populates the index with the ARP entries from the given key. - // This entry point is primarily to allow unit tests to operate of arbitrary keys; - // product code should use PopulateIndexFromARP. - void PopulateIndexFromKey(SQLiteIndex& index, const Registry::Key& key, std::string_view scope, std::string_view architecture, const std::map& upgradeCodes = {}) const; - - // Creates registry watchers for the given scope - std::vector CreateRegistryWatchers(Manifest::ScopeEnum scope, std::function callback); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/SQLiteIndex.h" +#include +#include +#include +#include +#include + +#include +#include + +namespace AppInstaller::Repository::Microsoft +{ + // A helper to find the various locations that contain ARP (Add/Remove Programs) entries. + struct ARPHelper + { + // See https://docs.microsoft.com/en-us/windows/win32/msi/uninstall-registry-key for details. + const std::wstring SubKeyPath{ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall" }; + + // REG_SZ + const std::wstring DisplayName{ L"DisplayName" }; + // REG_SZ + const std::wstring Publisher{ L"Publisher" }; + // REG_SZ + const std::wstring DisplayVersion{ L"DisplayVersion" }; + // REG_DWORD (ex. 0xMMmmbbbb, M[ajor], m[inor], b[uild]) + const std::wstring Version{ L"Version" }; + // REG_DWORD + const std::wstring VersionMajor{ L"VersionMajor" }; + // REG_DWORD + const std::wstring VersionMinor{ L"VersionMinor" }; + // REG_DWORD + const std::wstring MajorVersion{ L"MajorVersion" }; + // REG_DWORD + const std::wstring MinorVersion{ L"MinorVersion" }; + // REG_SZ + const std::wstring URLInfoAbout{ L"URLInfoAbout" }; + // REG_SZ + const std::wstring HelpLink{ L"HelpLink" }; + // REG_SZ + const std::wstring InstallLocation{ L"InstallLocation" }; + // REG_DWORD (ex. 1033 [en-us]) + const std::wstring Language{ L"Language" }; + // REG_SZ (ex. "english") + const std::wstring InnoSetupLanguage{ L"Inno Setup: Language" }; + // REG_EXPAND_SZ + const std::wstring UninstallString{ L"UninstallString" }; + // REG_EXPAND_SZ + const std::wstring QuietUninstallString{ L"QuietUninstallString" }; + // REG_DWORD (bool, true indicates MSI) + const std::wstring WindowsInstaller{ L"WindowsInstaller" }; + // REG_DWORD (bool) + const std::wstring SystemComponent{ L"SystemComponent" }; + // REG_SZ + const std::wstring DisplayIcon{ L"DisplayIcon" }; + // REG_DWORD + const std::wstring NoModify{ L"NoModify" }; + // REG_DWORD + const std::wstring NoRepair{ L"NoRepair" }; + // REG_SZ + const std::wstring ModifyPath{ L"ModifyPath" }; + + // Gets the registry key associated with the given scope and architecture on this platform. + // May return an empty key if there is no valid location (bad combination or not found). + Registry::Key GetARPKey(Manifest::ScopeEnum scope, Utility::Architecture architecture) const; + + // Gets the arp registry key associated with the given scope and product code. + // May return an empty key if not found. + Registry::Key FindARPEntry(const std::string& productCode, AppInstaller::Manifest::ScopeEnum scope = AppInstaller::Manifest::ScopeEnum::Unknown) const; + + // Returns true IFF the value exists and contains a non-zero DWORD. + static bool GetBoolValue(const Registry::Key& arpKey, const std::wstring& name); + + // Returns the string value if it exists. + static std::string GetStringValue(const Registry::Key& arpKey, const std::wstring& name); + + // Determines the version from an ARP entry. + // The priority is: + // DisplayVersion + // Version + // MajorVersion, MinorVersion + std::string DetermineVersion(const Registry::Key& arpKey) const; + + // Reads a value and adds it to the metadata if it exists. + void AddMetadataIfPresent(const Registry::Key& key, const std::wstring& name, SQLiteIndex& index, SQLiteIndex::IdType manifestId, PackageVersionMetadata metadata) const; + + // Populates the index with the ARP entries from the given scope (machine/user). + // Handles all of the architectures for the given scope. + void PopulateIndexFromARP(SQLiteIndex& index, Manifest::ScopeEnum scope) const; + + // Populates the index with the ARP entries from the given key. + // This entry point is primarily to allow unit tests to operate of arbitrary keys; + // product code should use PopulateIndexFromARP. + void PopulateIndexFromKey(SQLiteIndex& index, const Registry::Key& key, std::string_view scope, std::string_view architecture, const std::map& upgradeCodes = {}) const; + + // Creates registry watchers for the given scope + std::vector CreateRegistryWatchers(Manifest::ScopeEnum scope, std::function callback); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.cpp index 58e182d4f8..50eafdbfa0 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.cpp @@ -1,323 +1,323 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/ConfigurableTestSourceFactory.h" - -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::Repository::Microsoft -{ - namespace - { - // The configuration defined for a source. - // This can be added to as new scenarios are needed for testing. - struct TestSourceConfiguration - { - TestSourceConfiguration(const std::string& config) - { - Json::Value root; - Json::CharReaderBuilder builder; - const std::unique_ptr reader(builder.newCharReader()); - - std::string error; - - if (reader->parse(config.c_str(), config.c_str() + config.size(), &root, &error)) - { - // TODO: If this becomes more dynamic, refactor the UserSettings code to make it easier to leverage here - ParseHR(root, ".OpenHR", OpenHR); - ParseHR(root, ".SearchHR", SearchHR); - ParseBool(root, ".ContainsPackage", ContainsPackage); - } - else - { - AICLI_LOG(Repo, Error, << "Error parsing test source config: " << error); - THROW_HR_MSG(E_INVALIDARG, "%hs", error.c_str()); - } - } - - // The HR to throw on Factory::Create (if FAILED) - HRESULT OpenHR = S_OK; - - // The HR to throw on Source::Search (if FAILED) - HRESULT SearchHR = S_OK; - - // If a result should be returned by search. - bool ContainsPackage = false; - - private: - static void ParseHR(const Json::Value& root, const std::string& path, HRESULT& hr) - { - const Json::Path jsonPath(path); - Json::Value node = jsonPath.resolve(root); - if (!node.isNull()) - { - if (node.isIntegral()) - { - hr = node.asInt(); - } - else if (node.isString()) - { - hr = static_cast(std::strtoll(node.asString().c_str(), nullptr, 0)); - } - } - } - - static void ParseBool(const Json::Value& root, const std::string& path, bool& value) - { - const Json::Path jsonPath(path); - Json::Value node = jsonPath.resolve(root); - if (!node.isNull()) - { - if (node.isBool()) - { - value = node.asBool(); - } - } - } - }; - - // A test package that contains test data. - struct TestPackage : public std::enable_shared_from_this, public ICompositePackage, public IPackage, public IPackageVersion - { - TestPackage(std::shared_ptr source) : m_source(std::move(source)) {} - - Utility::LocIndString GetProperty(PackageProperty property) const override - { - switch (property) - { - case PackageProperty::Id: - return "ConfigurableTestSource Package Identifier"_lis; - case PackageProperty::Name: - return "ConfigurableTestSource Package Name"_lis; - } - - return {}; - } - - std::shared_ptr GetInstalled() override - { - return nullptr; - } - - std::vector> GetAvailable() override - { - return { shared_from_this() }; - } - - std::vector GetMultiProperty(PackageMultiProperty) const override - { - return {}; - } - - Source GetSource() const override - { - return { m_source }; - } - - bool IsSame(const IPackage*) const override - { - return false; - } - - const void* CastTo(IPackageType) const override - { - return nullptr; - } - - std::vector GetVersionKeys() const override - { - return { {} }; - } - - std::shared_ptr GetVersion(const PackageVersionKey&) const override - { - return std::static_pointer_cast(NonConstSharedFromThis()); - } - - std::shared_ptr GetLatestVersion() const override - { - return std::static_pointer_cast(NonConstSharedFromThis()); - } - - Utility::LocIndString GetProperty(PackageVersionProperty property) const override - { - switch (property) - { - case PackageVersionProperty::Id: - return "ConfigurableTestSource Package Version Identifier"_lis; - case PackageVersionProperty::Name: - return "ConfigurableTestSource Package Version Name"_lis; - case PackageVersionProperty::SourceIdentifier: - return "ConfigurableTestSource Package Version Source Identifier"_lis; - case PackageVersionProperty::SourceName: - return "ConfigurableTestSource Package Version Source Name"_lis; - case PackageVersionProperty::Version: - return "ConfigurableTestSource Package Version Version"_lis; - case PackageVersionProperty::Channel: - return "ConfigurableTestSource Package Version Channel"_lis; - case PackageVersionProperty::RelativePath: - return "ConfigurableTestSource Package Version Relative Path"_lis; - case PackageVersionProperty::ManifestSHA256Hash: - return "ConfigurableTestSource Package Version Manifest SHA 256 Hash"_lis; - case PackageVersionProperty::Publisher: - return "ConfigurableTestSource Package Version Publisher"_lis; - case PackageVersionProperty::ArpMinVersion: - return "ConfigurableTestSource Package Version Arp Min Version"_lis; - case PackageVersionProperty::ArpMaxVersion: - return "ConfigurableTestSource Package Version Arp Max Version"_lis; - case PackageVersionProperty::Moniker: - return "ConfigurableTestSource Package Version Moniker"_lis; - } - - return {}; - } - - std::vector GetMultiProperty(PackageVersionMultiProperty) const override - { - return {}; - } - - Manifest::Manifest GetManifest() override - { - Manifest::Manifest result; - - result.Id = "ConfigurableTestSource Manifest Identifier"; - result.CurrentLocalization.Add("ConfigurableTestSource Manifest Name"); - - return result; - } - - Metadata GetMetadata() const override - { - return {}; - } - - private: - std::shared_ptr NonConstSharedFromThis() const - { - return const_cast(this)->shared_from_this(); - } - - std::shared_ptr m_source; - }; - - // The configurable source itself. - struct ConfigurableTestSource : public std::enable_shared_from_this, public ISource - { - static constexpr ISourceType SourceType = ISourceType::ConfigurableTestSource; - - ConfigurableTestSource(const SourceDetails& details, const TestSourceConfiguration& config) : - m_details(details), m_config(config) {} - - const SourceDetails& GetDetails() const override { return m_details; } - - const std::string& GetIdentifier() const override { return m_details.Identifier; } - - SearchResult Search(const SearchRequest&) const override - { - THROW_IF_FAILED(m_config.SearchHR); - - SearchResult result; - - if (m_config.ContainsPackage) - { - std::shared_ptr package = std::make_shared(NonConstSharedFromThis()); - PackageMatchFilter packageFilter{ {}, {} }; - - result.Matches.emplace_back(std::move(package), std::move(packageFilter)); - } - - return result; - } - - void* CastTo(ISourceType type) override - { - if (type == SourceType) - { - return this; - } - - return nullptr; - } - - private: - std::shared_ptr NonConstSharedFromThis() const - { - return const_cast(this)->shared_from_this(); - } - - SourceDetails m_details; - TestSourceConfiguration m_config; - }; - - struct ConfigurableTestSourceReference : public ISourceReference - { - ConfigurableTestSourceReference(const SourceDetails& details) : m_details(details) - { - m_details.Identifier = "*ConfigurableTestSource"; - } - - std::string GetIdentifier() override { return m_details.Identifier; } - - SourceDetails& GetDetails() override { return m_details; }; - - bool SetCustomHeader(std::optional) override { return true; } - - std::shared_ptr Open(IProgressCallback&) override - { - // enables `source add` with FAILED(OpenHR) - TestSourceConfiguration config{ m_details.Arg }; - THROW_IF_FAILED(config.OpenHR); - return std::make_shared(m_details, config); - } - - private: - SourceDetails m_details; - }; - - // The actual factory implementation. - struct ConfigurableTestSourceFactoryImpl : public ISourceFactory - { - std::string_view TypeName() const override final - { - return ConfigurableTestSourceFactory::Type(); - } - - std::shared_ptr Create(const SourceDetails& details) override final - { - return std::make_shared(details); - } - - bool Add(SourceDetails& details, IProgressCallback&) override final - { - // Attempt to parse the configuration so that we can fail at the appropriate point - TestSourceConfiguration config{ details.Arg }; - return true; - } - - bool Update(const SourceDetails&, IProgressCallback&) override final - { - return true; - } - - bool BackgroundUpdate(const SourceDetails&, IProgressCallback&) override final - { - return true; - } - - bool Remove(const SourceDetails&, IProgressCallback&) override final - { - return true; - } - }; - } - - std::unique_ptr ConfigurableTestSourceFactory::Create() - { - return std::make_unique(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/ConfigurableTestSourceFactory.h" + +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::Repository::Microsoft +{ + namespace + { + // The configuration defined for a source. + // This can be added to as new scenarios are needed for testing. + struct TestSourceConfiguration + { + TestSourceConfiguration(const std::string& config) + { + Json::Value root; + Json::CharReaderBuilder builder; + const std::unique_ptr reader(builder.newCharReader()); + + std::string error; + + if (reader->parse(config.c_str(), config.c_str() + config.size(), &root, &error)) + { + // TODO: If this becomes more dynamic, refactor the UserSettings code to make it easier to leverage here + ParseHR(root, ".OpenHR", OpenHR); + ParseHR(root, ".SearchHR", SearchHR); + ParseBool(root, ".ContainsPackage", ContainsPackage); + } + else + { + AICLI_LOG(Repo, Error, << "Error parsing test source config: " << error); + THROW_HR_MSG(E_INVALIDARG, "%hs", error.c_str()); + } + } + + // The HR to throw on Factory::Create (if FAILED) + HRESULT OpenHR = S_OK; + + // The HR to throw on Source::Search (if FAILED) + HRESULT SearchHR = S_OK; + + // If a result should be returned by search. + bool ContainsPackage = false; + + private: + static void ParseHR(const Json::Value& root, const std::string& path, HRESULT& hr) + { + const Json::Path jsonPath(path); + Json::Value node = jsonPath.resolve(root); + if (!node.isNull()) + { + if (node.isIntegral()) + { + hr = node.asInt(); + } + else if (node.isString()) + { + hr = static_cast(std::strtoll(node.asString().c_str(), nullptr, 0)); + } + } + } + + static void ParseBool(const Json::Value& root, const std::string& path, bool& value) + { + const Json::Path jsonPath(path); + Json::Value node = jsonPath.resolve(root); + if (!node.isNull()) + { + if (node.isBool()) + { + value = node.asBool(); + } + } + } + }; + + // A test package that contains test data. + struct TestPackage : public std::enable_shared_from_this, public ICompositePackage, public IPackage, public IPackageVersion + { + TestPackage(std::shared_ptr source) : m_source(std::move(source)) {} + + Utility::LocIndString GetProperty(PackageProperty property) const override + { + switch (property) + { + case PackageProperty::Id: + return "ConfigurableTestSource Package Identifier"_lis; + case PackageProperty::Name: + return "ConfigurableTestSource Package Name"_lis; + } + + return {}; + } + + std::shared_ptr GetInstalled() override + { + return nullptr; + } + + std::vector> GetAvailable() override + { + return { shared_from_this() }; + } + + std::vector GetMultiProperty(PackageMultiProperty) const override + { + return {}; + } + + Source GetSource() const override + { + return { m_source }; + } + + bool IsSame(const IPackage*) const override + { + return false; + } + + const void* CastTo(IPackageType) const override + { + return nullptr; + } + + std::vector GetVersionKeys() const override + { + return { {} }; + } + + std::shared_ptr GetVersion(const PackageVersionKey&) const override + { + return std::static_pointer_cast(NonConstSharedFromThis()); + } + + std::shared_ptr GetLatestVersion() const override + { + return std::static_pointer_cast(NonConstSharedFromThis()); + } + + Utility::LocIndString GetProperty(PackageVersionProperty property) const override + { + switch (property) + { + case PackageVersionProperty::Id: + return "ConfigurableTestSource Package Version Identifier"_lis; + case PackageVersionProperty::Name: + return "ConfigurableTestSource Package Version Name"_lis; + case PackageVersionProperty::SourceIdentifier: + return "ConfigurableTestSource Package Version Source Identifier"_lis; + case PackageVersionProperty::SourceName: + return "ConfigurableTestSource Package Version Source Name"_lis; + case PackageVersionProperty::Version: + return "ConfigurableTestSource Package Version Version"_lis; + case PackageVersionProperty::Channel: + return "ConfigurableTestSource Package Version Channel"_lis; + case PackageVersionProperty::RelativePath: + return "ConfigurableTestSource Package Version Relative Path"_lis; + case PackageVersionProperty::ManifestSHA256Hash: + return "ConfigurableTestSource Package Version Manifest SHA 256 Hash"_lis; + case PackageVersionProperty::Publisher: + return "ConfigurableTestSource Package Version Publisher"_lis; + case PackageVersionProperty::ArpMinVersion: + return "ConfigurableTestSource Package Version Arp Min Version"_lis; + case PackageVersionProperty::ArpMaxVersion: + return "ConfigurableTestSource Package Version Arp Max Version"_lis; + case PackageVersionProperty::Moniker: + return "ConfigurableTestSource Package Version Moniker"_lis; + } + + return {}; + } + + std::vector GetMultiProperty(PackageVersionMultiProperty) const override + { + return {}; + } + + Manifest::Manifest GetManifest() override + { + Manifest::Manifest result; + + result.Id = "ConfigurableTestSource Manifest Identifier"; + result.CurrentLocalization.Add("ConfigurableTestSource Manifest Name"); + + return result; + } + + Metadata GetMetadata() const override + { + return {}; + } + + private: + std::shared_ptr NonConstSharedFromThis() const + { + return const_cast(this)->shared_from_this(); + } + + std::shared_ptr m_source; + }; + + // The configurable source itself. + struct ConfigurableTestSource : public std::enable_shared_from_this, public ISource + { + static constexpr ISourceType SourceType = ISourceType::ConfigurableTestSource; + + ConfigurableTestSource(const SourceDetails& details, const TestSourceConfiguration& config) : + m_details(details), m_config(config) {} + + const SourceDetails& GetDetails() const override { return m_details; } + + const std::string& GetIdentifier() const override { return m_details.Identifier; } + + SearchResult Search(const SearchRequest&) const override + { + THROW_IF_FAILED(m_config.SearchHR); + + SearchResult result; + + if (m_config.ContainsPackage) + { + std::shared_ptr package = std::make_shared(NonConstSharedFromThis()); + PackageMatchFilter packageFilter{ {}, {} }; + + result.Matches.emplace_back(std::move(package), std::move(packageFilter)); + } + + return result; + } + + void* CastTo(ISourceType type) override + { + if (type == SourceType) + { + return this; + } + + return nullptr; + } + + private: + std::shared_ptr NonConstSharedFromThis() const + { + return const_cast(this)->shared_from_this(); + } + + SourceDetails m_details; + TestSourceConfiguration m_config; + }; + + struct ConfigurableTestSourceReference : public ISourceReference + { + ConfigurableTestSourceReference(const SourceDetails& details) : m_details(details) + { + m_details.Identifier = "*ConfigurableTestSource"; + } + + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + bool SetCustomHeader(std::optional) override { return true; } + + std::shared_ptr Open(IProgressCallback&) override + { + // enables `source add` with FAILED(OpenHR) + TestSourceConfiguration config{ m_details.Arg }; + THROW_IF_FAILED(config.OpenHR); + return std::make_shared(m_details, config); + } + + private: + SourceDetails m_details; + }; + + // The actual factory implementation. + struct ConfigurableTestSourceFactoryImpl : public ISourceFactory + { + std::string_view TypeName() const override final + { + return ConfigurableTestSourceFactory::Type(); + } + + std::shared_ptr Create(const SourceDetails& details) override final + { + return std::make_shared(details); + } + + bool Add(SourceDetails& details, IProgressCallback&) override final + { + // Attempt to parse the configuration so that we can fail at the appropriate point + TestSourceConfiguration config{ details.Arg }; + return true; + } + + bool Update(const SourceDetails&, IProgressCallback&) override final + { + return true; + } + + bool BackgroundUpdate(const SourceDetails&, IProgressCallback&) override final + { + return true; + } + + bool Remove(const SourceDetails&, IProgressCallback&) override final + { + return true; + } + }; + } + + std::unique_ptr ConfigurableTestSourceFactory::Create() + { + return std::make_unique(); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.h index 4b56256fa4..c4b4aa5716 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.h +++ b/src/AppInstallerRepositoryCore/Microsoft/ConfigurableTestSourceFactory.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include "SourceFactory.h" - -#include - -namespace AppInstaller::Repository::Microsoft -{ - using namespace std::string_view_literals; - - // A source for use in manual or E2E tests that can be configured to fail as needed. - struct ConfigurableTestSourceFactory - { - // Get the type string for this source. - static constexpr std::string_view Type() - { - using namespace std::string_view_literals; - return "Microsoft.Test.Configurable"sv; - } - - // Creates a source factory for this type. - static std::unique_ptr Create(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include "SourceFactory.h" + +#include + +namespace AppInstaller::Repository::Microsoft +{ + using namespace std::string_view_literals; + + // A source for use in manual or E2E tests that can be configured to fail as needed. + struct ConfigurableTestSourceFactory + { + // Get the type string for this source. + static constexpr std::string_view Type() + { + using namespace std::string_view_literals; + return "Microsoft.Test.Configurable"sv; + } + + // Creates a source factory for this type. + static std::unique_ptr Create(); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp index 645cfb127d..9494fb6643 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.cpp @@ -1,212 +1,212 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PinningIndex.h" -#include -#include "Schema/Pinning_1_0/PinningIndexInterface.h" - -namespace AppInstaller::Repository::Microsoft -{ -#ifndef AICLI_DISABLE_TEST_HOOKS - std::optional s_PinningIndexOverride{}; - void TestHook_SetPinningIndex_Override(std::optional&& indexPath) - { - s_PinningIndexOverride = std::move(indexPath); - } -#endif - - namespace - { - std::filesystem::path GetPinningDatabasePath() - { - const auto DefaultPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; - - return -#ifndef AICLI_DISABLE_TEST_HOOKS - s_PinningIndexOverride.has_value() ? s_PinningIndexOverride.value() : -#endif - DefaultPath; - } - - std::shared_ptr OpenDatabaseIfExists(const std::filesystem::path& path, SQLite::SQLiteStorageBase::OpenDisposition openDisposition) - { - AICLI_LOG(Repo, Info, << "Attempting to open pinning database: " << path); - - try - { - if (std::filesystem::exists(path)) - { - if (std::filesystem::is_regular_file(path)) - { - try - { - AICLI_LOG(Repo, Info, << "... opening existing pinning database"); - return std::make_shared(PinningIndex::Open(path.u8string(), openDisposition)); - } - CATCH_LOG(); - - AICLI_LOG(Repo, Info, << "... deleting bad pinning database file"); - std::filesystem::remove_all(path); - } - else - { - AICLI_LOG(Repo, Info, << "... deleting pinning database path that is a directory"); - std::filesystem::remove_all(path); - } - } - } - CATCH_LOG(); - - return {}; - } - } - - PinningIndex PinningIndex::CreateNew(const std::string& filePath, SQLite::Version version) - { - AICLI_LOG(Repo, Info, << "Creating new Pinning Index with version [" << version << "] at '" << filePath << "'"); - PinningIndex result{ filePath, version }; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "pinningindex_createnew"); - - // Use calculated version, as incoming version could be 'latest' - result.m_version.SetSchemaVersion(result.m_dbconn); - - result.m_interface->CreateTables(result.m_dbconn); - - result.SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - std::shared_ptr PinningIndex::OpenIfExists(OpenDisposition openDisposition) - { - return OpenDatabaseIfExists(GetPinningDatabasePath(), openDisposition); - } - - std::shared_ptr PinningIndex::OpenOrCreateDefault(OpenDisposition openDisposition) - { - const auto databasePath = GetPinningDatabasePath(); - - std::shared_ptr result = OpenDatabaseIfExists(databasePath, openDisposition); - - if (!result) - { - AICLI_LOG(Repo, Info, << "... creating pinning database"); - - try - { - result = std::make_shared(PinningIndex::CreateNew(databasePath.u8string())); - } - CATCH_LOG(); - } - - return result; - } - - PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Adding Pin " << pin.ToString()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_addpin"); - - IdType result = m_interface->AddPin(m_dbconn, pin); - - SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - bool PinningIndex::UpdatePin(const Pinning::Pin& pin) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Updating Pin " << pin.ToString()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_updatepin"); - - bool result = m_interface->UpdatePin(m_dbconn, pin).first; - - if (result) - { - SetLastWriteTime(); - savepoint.Commit(); - } - - return result; - } - - void PinningIndex::AddOrUpdatePin(const Pinning::Pin& pin) - { - auto existingPin = GetPin(pin.GetKey()); - if (existingPin.has_value()) - { - UpdatePin(pin); - } - else - { - AddPin(pin); - } - } - - void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Removing Pin " << pinKey.ToString()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_removePin"); - - m_interface->RemovePin(m_dbconn, pinKey); - - SetLastWriteTime(); - - savepoint.Commit(); - } - - std::optional PinningIndex::GetPin(const Pinning::PinKey& pinKey) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetPin(m_dbconn, pinKey); - } - - std::vector PinningIndex::GetAllPins() - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetAllPins(m_dbconn); - } - - bool PinningIndex::ResetAllPins(std::string_view sourceId) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->ResetAllPins(m_dbconn, sourceId); - } - - std::unique_ptr PinningIndex::CreateIPinningIndex() const - { - if (m_version == SQLite::Version{ 1, 0 } || - m_version.MajorVersion == 1 || - m_version.IsLatest()) - { - return std::make_unique(); - } - - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - - PinningIndex::PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : - SQLiteStorageBase(target, disposition, std::move(indexFile)) - { - AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); - m_interface = CreateIPinningIndex(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); - } - - PinningIndex::PinningIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) - { - m_interface = CreateIPinningIndex(); - m_version = m_interface->GetVersion(); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinningIndex.h" +#include +#include "Schema/Pinning_1_0/PinningIndexInterface.h" + +namespace AppInstaller::Repository::Microsoft +{ +#ifndef AICLI_DISABLE_TEST_HOOKS + std::optional s_PinningIndexOverride{}; + void TestHook_SetPinningIndex_Override(std::optional&& indexPath) + { + s_PinningIndexOverride = std::move(indexPath); + } +#endif + + namespace + { + std::filesystem::path GetPinningDatabasePath() + { + const auto DefaultPath = Runtime::GetPathTo(Runtime::PathName::LocalState) / "pinning.db"; + + return +#ifndef AICLI_DISABLE_TEST_HOOKS + s_PinningIndexOverride.has_value() ? s_PinningIndexOverride.value() : +#endif + DefaultPath; + } + + std::shared_ptr OpenDatabaseIfExists(const std::filesystem::path& path, SQLite::SQLiteStorageBase::OpenDisposition openDisposition) + { + AICLI_LOG(Repo, Info, << "Attempting to open pinning database: " << path); + + try + { + if (std::filesystem::exists(path)) + { + if (std::filesystem::is_regular_file(path)) + { + try + { + AICLI_LOG(Repo, Info, << "... opening existing pinning database"); + return std::make_shared(PinningIndex::Open(path.u8string(), openDisposition)); + } + CATCH_LOG(); + + AICLI_LOG(Repo, Info, << "... deleting bad pinning database file"); + std::filesystem::remove_all(path); + } + else + { + AICLI_LOG(Repo, Info, << "... deleting pinning database path that is a directory"); + std::filesystem::remove_all(path); + } + } + } + CATCH_LOG(); + + return {}; + } + } + + PinningIndex PinningIndex::CreateNew(const std::string& filePath, SQLite::Version version) + { + AICLI_LOG(Repo, Info, << "Creating new Pinning Index with version [" << version << "] at '" << filePath << "'"); + PinningIndex result{ filePath, version }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "pinningindex_createnew"); + + // Use calculated version, as incoming version could be 'latest' + result.m_version.SetSchemaVersion(result.m_dbconn); + + result.m_interface->CreateTables(result.m_dbconn); + + result.SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + std::shared_ptr PinningIndex::OpenIfExists(OpenDisposition openDisposition) + { + return OpenDatabaseIfExists(GetPinningDatabasePath(), openDisposition); + } + + std::shared_ptr PinningIndex::OpenOrCreateDefault(OpenDisposition openDisposition) + { + const auto databasePath = GetPinningDatabasePath(); + + std::shared_ptr result = OpenDatabaseIfExists(databasePath, openDisposition); + + if (!result) + { + AICLI_LOG(Repo, Info, << "... creating pinning database"); + + try + { + result = std::make_shared(PinningIndex::CreateNew(databasePath.u8string())); + } + CATCH_LOG(); + } + + return result; + } + + PinningIndex::IdType PinningIndex::AddPin(const Pinning::Pin& pin) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Adding Pin " << pin.ToString()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_addpin"); + + IdType result = m_interface->AddPin(m_dbconn, pin); + + SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + bool PinningIndex::UpdatePin(const Pinning::Pin& pin) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Updating Pin " << pin.ToString()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningindex_updatepin"); + + bool result = m_interface->UpdatePin(m_dbconn, pin).first; + + if (result) + { + SetLastWriteTime(); + savepoint.Commit(); + } + + return result; + } + + void PinningIndex::AddOrUpdatePin(const Pinning::Pin& pin) + { + auto existingPin = GetPin(pin.GetKey()); + if (existingPin.has_value()) + { + UpdatePin(pin); + } + else + { + AddPin(pin); + } + } + + void PinningIndex::RemovePin(const Pinning::PinKey& pinKey) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Removing Pin " << pinKey.ToString()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "pinningIndex_removePin"); + + m_interface->RemovePin(m_dbconn, pinKey); + + SetLastWriteTime(); + + savepoint.Commit(); + } + + std::optional PinningIndex::GetPin(const Pinning::PinKey& pinKey) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetPin(m_dbconn, pinKey); + } + + std::vector PinningIndex::GetAllPins() + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetAllPins(m_dbconn); + } + + bool PinningIndex::ResetAllPins(std::string_view sourceId) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->ResetAllPins(m_dbconn, sourceId); + } + + std::unique_ptr PinningIndex::CreateIPinningIndex() const + { + if (m_version == SQLite::Version{ 1, 0 } || + m_version.MajorVersion == 1 || + m_version.IsLatest()) + { + return std::make_unique(); + } + + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + + PinningIndex::PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + SQLiteStorageBase(target, disposition, std::move(indexFile)) + { + AICLI_LOG(Repo, Info, << "Opened Pinning Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); + m_interface = CreateIPinningIndex(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); + } + + PinningIndex::PinningIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) + { + m_interface = CreateIPinningIndex(); + m_version = m_interface->GetVersion(); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h index 672f8dba8f..96d2019f52 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PinningIndex.h @@ -1,75 +1,75 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Microsoft/Schema/IPinningIndex.h" -#include -#include -#include - -namespace AppInstaller::Repository::Microsoft -{ - struct PinningIndex : SQLite::SQLiteStorageBase - { - // An id that refers to a specific Pinning file. - using IdType = SQLite::rowid_t; - - PinningIndex(const PinningIndex&) = delete; - PinningIndex& operator=(const PinningIndex&) = delete; - - PinningIndex(PinningIndex&&) = default; - PinningIndex& operator=(PinningIndex&&) = default; - - // Creates a new PinningIndex database of the given version. - static PinningIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest()); - - // Opens an existing PinningIndex database. - static PinningIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}) - { - return { filePath, disposition, std::move(indexFile) }; - } - - // Opens the PinningIndex database on the default path if it exists. - // Returns nullptr in case of error. - static std::shared_ptr OpenIfExists(OpenDisposition openDisposition = OpenDisposition::Read); - - // Opens or creates a PinningIndex database on the default path. - // openDisposition is only used when opening an existing database. - // Returns nullptr in case of error. - static std::shared_ptr OpenOrCreateDefault(OpenDisposition openDisposition = OpenDisposition::ReadWrite); - - // Adds a pin to the index. - IdType AddPin(const Pinning::Pin& pin); - - // Updates a pin type, and gated version if needed. - // Return value indicates whether there were any changes. - bool UpdatePin(const Pinning::Pin& pin); - - // Adds a pin or updates it if it already exists. - void AddOrUpdatePin(const Pinning::Pin& pin); - - // Removes a pin from the index. - void RemovePin(const Pinning::PinKey& pinKey); - - // Returns the current pin for a given package if it exists. - std::optional GetPin(const Pinning::PinKey& pinKey); - - // Returns a vector containing all the existing pins. - std::vector GetAllPins(); - - // Deletes all pins from a given source, or from all sources if none is specified - bool ResetAllPins(std::string_view sourceId = {}); - - private: - // Constructor used to open an existing index. - PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); - - // Constructor used to create a new index. - PinningIndex(const std::string& target, SQLite::Version version); - - // Creates the IPinningIndex interface object for this version. - std::unique_ptr CreateIPinningIndex() const; - - std::unique_ptr m_interface; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Microsoft/Schema/IPinningIndex.h" +#include +#include +#include + +namespace AppInstaller::Repository::Microsoft +{ + struct PinningIndex : SQLite::SQLiteStorageBase + { + // An id that refers to a specific Pinning file. + using IdType = SQLite::rowid_t; + + PinningIndex(const PinningIndex&) = delete; + PinningIndex& operator=(const PinningIndex&) = delete; + + PinningIndex(PinningIndex&&) = default; + PinningIndex& operator=(PinningIndex&&) = default; + + // Creates a new PinningIndex database of the given version. + static PinningIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest()); + + // Opens an existing PinningIndex database. + static PinningIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}) + { + return { filePath, disposition, std::move(indexFile) }; + } + + // Opens the PinningIndex database on the default path if it exists. + // Returns nullptr in case of error. + static std::shared_ptr OpenIfExists(OpenDisposition openDisposition = OpenDisposition::Read); + + // Opens or creates a PinningIndex database on the default path. + // openDisposition is only used when opening an existing database. + // Returns nullptr in case of error. + static std::shared_ptr OpenOrCreateDefault(OpenDisposition openDisposition = OpenDisposition::ReadWrite); + + // Adds a pin to the index. + IdType AddPin(const Pinning::Pin& pin); + + // Updates a pin type, and gated version if needed. + // Return value indicates whether there were any changes. + bool UpdatePin(const Pinning::Pin& pin); + + // Adds a pin or updates it if it already exists. + void AddOrUpdatePin(const Pinning::Pin& pin); + + // Removes a pin from the index. + void RemovePin(const Pinning::PinKey& pinKey); + + // Returns the current pin for a given package if it exists. + std::optional GetPin(const Pinning::PinKey& pinKey); + + // Returns a vector containing all the existing pins. + std::vector GetAllPins(); + + // Deletes all pins from a given source, or from all sources if none is specified + bool ResetAllPins(std::string_view sourceId = {}); + + private: + // Constructor used to open an existing index. + PinningIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + // Constructor used to create a new index. + PinningIndex(const std::string& target, SQLite::Version version); + + // Creates the IPinningIndex interface object for this version. + std::unique_ptr CreateIPinningIndex() const; + + std::unique_ptr m_interface; + }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.cpp index fa56e23a0e..45eb76c999 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PortableIndex.cpp @@ -1,146 +1,146 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/PortableIndex.h" -#include "Microsoft/Schema/IPortableIndex.h" -#include "Microsoft/Schema/Portable_1_0/PortableTable.h" -#include -#include "Schema/Portable_1_0/PortableIndexInterface.h" -#include - -namespace AppInstaller::Repository::Microsoft -{ - PortableIndex::PortableIndex(PortableIndex&&) = default; - PortableIndex& PortableIndex::operator=(PortableIndex&&) = default; - - PortableIndex::~PortableIndex() = default; - - PortableIndex PortableIndex::CreateNew(const std::string& filePath, SQLite::Version version) - { - AICLI_LOG(Repo, Info, << "Creating new Portable Index with version [" << version << "] at '" << filePath << "'"); - PortableIndex result{ filePath, version }; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "portableindex_createnew"); - - // Use calculated version, as incoming version could be 'latest' - result.m_version.SetSchemaVersion(result.m_dbconn); - - result.m_interface->CreateTable(result.m_dbconn); - - const auto& filePathUTF16 = Utility::ConvertToUTF16(filePath); - SetFileAttributes(filePathUTF16.c_str(), GetFileAttributes(filePathUTF16.c_str()) | FILE_ATTRIBUTE_HIDDEN); - - result.SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - PortableIndex PortableIndex::Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile) - { - return { filePath, disposition, std::move(indexFile) }; - } - - PortableIndex::IdType PortableIndex::AddPortableFile(const Portable::PortableFileEntry& file) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Adding portable file for [" << file.GetFilePath() << "]"); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_addfile"); - - IdType result = m_interface->AddPortableFile(m_dbconn, file); - - SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - void PortableIndex::RemovePortableFile(const Portable::PortableFileEntry& file) - { - AICLI_LOG(Repo, Verbose, << "Removing portable file [" << file.GetFilePath() << "]"); - std::lock_guard lockInterface{ *m_interfaceLock }; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_removefile"); - - m_interface->RemovePortableFile(m_dbconn, file); - - SetLastWriteTime(); - - savepoint.Commit(); - } - - bool PortableIndex::UpdatePortableFile(const Portable::PortableFileEntry& file) - { - AICLI_LOG(Repo, Verbose, << "Updating portable file [" << file.GetFilePath() << "]"); - std::lock_guard lockInterface{ *m_interfaceLock }; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_updatefile"); - - bool result = m_interface->UpdatePortableFile(m_dbconn, file).first; - - if (result) - { - SetLastWriteTime(); - savepoint.Commit(); - } - - return result; - } - - bool PortableIndex::Exists(const Portable::PortableFileEntry& file) - { - AICLI_LOG(Repo, Verbose, << "Checking if portable file exists [" << file.GetFilePath() << "]"); - return m_interface->Exists(m_dbconn, file); - } - - bool PortableIndex::IsEmpty() - { - return m_interface->IsEmpty(m_dbconn); - } - - void PortableIndex::AddOrUpdatePortableFile(const Portable::PortableFileEntry& file) - { - if (Exists(file)) - { - UpdatePortableFile(file); - } - else - { - AddPortableFile(file); - } - } - - std::vector PortableIndex::GetAllPortableFiles() - { - return m_interface->GetAllPortableFiles(m_dbconn); - } - - std::unique_ptr PortableIndex::CreateIPortableIndex() const - { - if (m_version == SQLite::Version{ 1, 0 } || - m_version.MajorVersion == 1 || - m_version.IsLatest()) - { - return std::make_unique(); - } - - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - - PortableIndex::PortableIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : - SQLiteStorageBase(target, disposition, std::move(indexFile)) - { - AICLI_LOG(Repo, Info, << "Opened Portable Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); - m_interface = CreateIPortableIndex(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); - } - - PortableIndex::PortableIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) - { - m_interface = CreateIPortableIndex(); - m_version = m_interface->GetVersion(); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/PortableIndex.h" +#include "Microsoft/Schema/IPortableIndex.h" +#include "Microsoft/Schema/Portable_1_0/PortableTable.h" +#include +#include "Schema/Portable_1_0/PortableIndexInterface.h" +#include + +namespace AppInstaller::Repository::Microsoft +{ + PortableIndex::PortableIndex(PortableIndex&&) = default; + PortableIndex& PortableIndex::operator=(PortableIndex&&) = default; + + PortableIndex::~PortableIndex() = default; + + PortableIndex PortableIndex::CreateNew(const std::string& filePath, SQLite::Version version) + { + AICLI_LOG(Repo, Info, << "Creating new Portable Index with version [" << version << "] at '" << filePath << "'"); + PortableIndex result{ filePath, version }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "portableindex_createnew"); + + // Use calculated version, as incoming version could be 'latest' + result.m_version.SetSchemaVersion(result.m_dbconn); + + result.m_interface->CreateTable(result.m_dbconn); + + const auto& filePathUTF16 = Utility::ConvertToUTF16(filePath); + SetFileAttributes(filePathUTF16.c_str(), GetFileAttributes(filePathUTF16.c_str()) | FILE_ATTRIBUTE_HIDDEN); + + result.SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + PortableIndex PortableIndex::Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile) + { + return { filePath, disposition, std::move(indexFile) }; + } + + PortableIndex::IdType PortableIndex::AddPortableFile(const Portable::PortableFileEntry& file) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Adding portable file for [" << file.GetFilePath() << "]"); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_addfile"); + + IdType result = m_interface->AddPortableFile(m_dbconn, file); + + SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + void PortableIndex::RemovePortableFile(const Portable::PortableFileEntry& file) + { + AICLI_LOG(Repo, Verbose, << "Removing portable file [" << file.GetFilePath() << "]"); + std::lock_guard lockInterface{ *m_interfaceLock }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_removefile"); + + m_interface->RemovePortableFile(m_dbconn, file); + + SetLastWriteTime(); + + savepoint.Commit(); + } + + bool PortableIndex::UpdatePortableFile(const Portable::PortableFileEntry& file) + { + AICLI_LOG(Repo, Verbose, << "Updating portable file [" << file.GetFilePath() << "]"); + std::lock_guard lockInterface{ *m_interfaceLock }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "portableindex_updatefile"); + + bool result = m_interface->UpdatePortableFile(m_dbconn, file).first; + + if (result) + { + SetLastWriteTime(); + savepoint.Commit(); + } + + return result; + } + + bool PortableIndex::Exists(const Portable::PortableFileEntry& file) + { + AICLI_LOG(Repo, Verbose, << "Checking if portable file exists [" << file.GetFilePath() << "]"); + return m_interface->Exists(m_dbconn, file); + } + + bool PortableIndex::IsEmpty() + { + return m_interface->IsEmpty(m_dbconn); + } + + void PortableIndex::AddOrUpdatePortableFile(const Portable::PortableFileEntry& file) + { + if (Exists(file)) + { + UpdatePortableFile(file); + } + else + { + AddPortableFile(file); + } + } + + std::vector PortableIndex::GetAllPortableFiles() + { + return m_interface->GetAllPortableFiles(m_dbconn); + } + + std::unique_ptr PortableIndex::CreateIPortableIndex() const + { + if (m_version == SQLite::Version{ 1, 0 } || + m_version.MajorVersion == 1 || + m_version.IsLatest()) + { + return std::make_unique(); + } + + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + + PortableIndex::PortableIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + SQLiteStorageBase(target, disposition, std::move(indexFile)) + { + AICLI_LOG(Repo, Info, << "Opened Portable Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); + m_interface = CreateIPortableIndex(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); + } + + PortableIndex::PortableIndex(const std::string& target, SQLite::Version version) : SQLiteStorageBase(target, version) + { + m_interface = CreateIPortableIndex(); + m_version = m_interface->GetVersion(); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp index 4538d9024e..53a73e1835 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.cpp @@ -1,878 +1,878 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/PreIndexedPackageSourceFactory.h" -#include "Microsoft/SQLiteIndex.h" -#include "Microsoft/SQLiteIndexSource.h" -#include "SourceUpdateChecks.h" - -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Microsoft -{ - namespace - { - static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageFileName = "source.msix"sv; - static constexpr std::string_view s_PreIndexedPackageSourceFactory_V2_PackageFileName = "source2.msix"sv; - static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageVersionHeader = "x-ms-meta-sourceversion"sv; - static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFileName = "index.db"sv; - // TODO: This being hard coded to force using the Public directory name is not ideal. - static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFilePath = "Public\\index.db"sv; - - // Construct the package location from the given details. - // Currently expects that the arg is an https uri pointing to the root of the data. - std::string GetPackageLocation(const std::string& basePath, std::string_view fileName) - { - std::string result = basePath; - if (result.back() != '/') - { - result += '/'; - } - result += fileName; - return result; - } - - // Gets the set of package locations that should be tried, in order. - std::vector GetPackageLocations(const SourceDetails& details) - { - THROW_HR_IF(E_INVALIDARG, details.Arg.empty()); - - std::vector result; - - result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_V2_PackageFileName)); - result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_PackageFileName)); - - if (!details.AlternateArg.empty()) - { - result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_V2_PackageFileName)); - result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_PackageFileName)); - } - - return result; - } - - // Abstracts the fallback for package location when the MsixInfo is needed. - struct PreIndexedPackageInfo - { - template - PreIndexedPackageInfo(const SourceDetails& details, LocationCheck&& locationCheck) - { - std::vector potentialLocations = GetPackageLocations(details); - - for (const auto& location : potentialLocations) - { - locationCheck(location); - } - - std::exception_ptr primaryException; - - for (const auto& location : potentialLocations) - { - try - { - m_msixInfo = std::make_unique(location); - m_packageLocation = location; - return; - } - catch (...) - { - LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageInfo failed on location: %hs", location.c_str()); - if (!primaryException) - { - primaryException = std::current_exception(); - } - } - } - - std::rethrow_exception(primaryException); - } - - const std::string& PackageLocation() const { return m_packageLocation; } - Msix::MsixInfo& MsixInfo() { return *m_msixInfo; } - - private: - std::string m_packageLocation; - std::unique_ptr m_msixInfo; - }; - - // Abstracts the fallback for package location when an update is being done. - struct PreIndexedPackageUpdateCheck - { - PreIndexedPackageUpdateCheck(const SourceDetails& details) - { - std::vector potentialLocations = GetPackageLocations(details); - - std::exception_ptr primaryException; - - for (const auto& location : potentialLocations) - { - try - { - m_availableVersion = GetAvailableVersionFrom(location); - m_packageLocation = location; - return; - } - catch (...) - { - LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageUpdateCheck failed on location: %hs", location.c_str()); - if (!primaryException) - { - primaryException = std::current_exception(); - } - } - } - - std::rethrow_exception(primaryException); - } - - const std::string& PackageLocation() const { return m_packageLocation; } - const Msix::PackageVersion& AvailableVersion() const { return m_availableVersion; } - - private: - std::string m_packageLocation; - Msix::PackageVersion m_availableVersion; - - Msix::PackageVersion GetAvailableVersionFrom(const std::string& packageLocation) - { - if (Utility::IsUrlRemote(packageLocation)) - { - std::map headers = Utility::GetHeaders(packageLocation); - auto itr = headers.find(std::string{ s_PreIndexedPackageSourceFactory_PackageVersionHeader }); - if (itr != headers.end()) - { - AICLI_LOG(Repo, Verbose, << "Header indicates version is: " << itr->second); - return { itr->second }; - } - - // We did not find the header we were looking for, log the ones we did find - AICLI_LOG(Repo, Verbose, << "Did not find " << s_PreIndexedPackageSourceFactory_PackageVersionHeader << " in:\n" << [&]() - { - std::ostringstream headerLog; - for (const auto& header : headers) - { - headerLog << " " << header.first << " : " << header.second << '\n'; - } - return std::move(headerLog).str(); - }()); - } - - AICLI_LOG(Repo, Verbose, << "Reading package data to determine version"); - Msix::MsixInfo info{ packageLocation }; - auto manifest = info.GetAppPackageManifests(); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, manifest.size() > 1); - THROW_HR_IF(E_UNEXPECTED, manifest.size() == 0); - - return manifest[0].GetIdentity().GetVersion(); - } - }; - - // Gets the package family name from the details. - std::string GetPackageFamilyNameFromDetails(const SourceDetails& details) - { - THROW_HR_IF(E_UNEXPECTED, details.Data.empty()); - return details.Data; - } - - // Creates a name for the cross process reader-writer lock given the details. - std::string CreateNameForCPL(const SourceDetails& details) - { - // The only relevant data is the package family name - return "PreIndexedSourceCPL_"s + GetPackageFamilyNameFromDetails(details); - } - - // The base class for a package that comes from a preindexed packaged source. - struct PreIndexedFactoryBase : public ISourceFactory - { - std::string_view TypeName() const override final - { - return PreIndexedPackageSourceFactory::Type(); - } - - std::shared_ptr Create(const SourceDetails& details) override final - { - // With more than one source implementation, we will probably need to probe first - THROW_HR_IF(E_INVALIDARG, !details.Type.empty() && details.Type != PreIndexedPackageSourceFactory::Type()); - - return CreateInternal(details); - } - - virtual std::shared_ptr CreateInternal(const SourceDetails& details) = 0; - - bool Add(SourceDetails& details, IProgressCallback& progress) override final - { - if (details.Type.empty()) - { - // With more than one source implementation, we will probably need to probe first - details.Type = PreIndexedPackageSourceFactory::Type(); - AICLI_LOG(Repo, Info, << "Initializing source type: " << details.Name << " => " << details.Type); - } - else - { - THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); - } - - PreIndexedPackageInfo packageInfo(details, [](const std::string& packageLocation) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, Utility::IsUrlRemote(packageLocation) && !Utility::IsUrlSecure(packageLocation)); - }); - - AICLI_LOG(Repo, Info, << "Initializing source from: " << details.Name << " => " << packageInfo.PackageLocation()); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, packageInfo.MsixInfo().GetIsBundle()); - - auto fullName = packageInfo.MsixInfo().GetPackageFullName(); - AICLI_LOG(Repo, Info, << "Found package full name: " << details.Name << " => " << fullName); - - details.Data = Msix::GetPackageFamilyNameFromFullName(fullName); - details.Identifier = Msix::GetPackageFamilyNameFromFullName(fullName); - - auto lock = LockExclusive(details, progress); - if (!lock) - { - return false; - } - - std::optional downloadedBytes; - bool result = UpdateInternal(packageInfo.PackageLocation(), details, progress, downloadedBytes); - - if (downloadedBytes) - { - try - { - auto manifests = packageInfo.MsixInfo().GetAppPackageManifests(); - if (!manifests.empty()) - { - Logging::Telemetry().LogPreindexedPackageUpdate( - details.Identifier, - std::nullopt, - Utility::GetTimePointFromVersion(manifests[0].GetIdentity().GetVersion()), - false, - std::nullopt, - std::nullopt, - false, - downloadedBytes.value(), - true); - } - } - CATCH_LOG(); - } - - return result; - } - - bool Update(const SourceDetails& details, IProgressCallback& progress) override final - { - return UpdateBase(details, false, progress); - } - - bool BackgroundUpdate(const SourceDetails& details, IProgressCallback& progress) override final - { - return UpdateBase(details, true, progress); - } - - // Retrieves the currently cached version of the package. - virtual std::optional GetCurrentVersion(const SourceDetails& details) = 0; - - virtual bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) = 0; - - bool Remove(const SourceDetails& details, IProgressCallback& progress) override final - { - THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); - auto lock = LockExclusive(details, progress); - if (!lock) - { - return false; - } - - return RemoveInternal(details, progress); - } - - virtual bool RemoveInternal(const SourceDetails& details, IProgressCallback&) = 0; - - private: - Synchronization::CrossProcessLock LockExclusive(const SourceDetails& details, IProgressCallback& progress, bool isBackground = false) - { - Synchronization::CrossProcessLock result(CreateNameForCPL(details)); - - if (isBackground) - { - // If this is a background update, don't wait on the lock. - result.TryAcquireNoWait(); - } - else - { - result.Acquire(progress); - } - - return result; - } - - bool UpdateBase(const SourceDetails& details, bool isBackground, IProgressCallback& progress) - { - THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); - - std::optional currentVersion = GetCurrentVersion(details); - PreIndexedPackageUpdateCheck updateCheck(details); - - if (currentVersion) - { - if (currentVersion.value() >= updateCheck.AvailableVersion()) - { - AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() << - ") was not newer than existing (" << currentVersion.value().ToString() << "), no update needed"); - return true; - } - else - { - AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() << - ") was newer than existing (" << currentVersion.value().ToString() << "), updating"); - } - } - - if (progress.IsCancelledBy(CancelReason::Any)) - { - AICLI_LOG(Repo, Info, << "Cancelling update upon request"); - return false; - } - - auto lock = LockExclusive(details, progress, isBackground); - if (!lock) - { - return false; - } - - std::optional downloadedBytes = 0; - bool result = UpdateInternal(updateCheck.PackageLocation(), details, progress, downloadedBytes); - - if (downloadedBytes) - { - std::optional previousIndexPublishedAt; - if (currentVersion) - { - previousIndexPublishedAt = Utility::GetTimePointFromVersion(currentVersion.value()); - } - - Logging::Telemetry().LogPreindexedPackageUpdate( - details.Identifier, - previousIndexPublishedAt, - Utility::GetTimePointFromVersion(updateCheck.AvailableVersion()), - false, - std::nullopt, - std::nullopt, - false, - downloadedBytes.value(), - !isBackground); - } - - return result; - } - }; - - // Optimistic packaged source open may call this without the cross process lock and retry under the lock on failure. - std::optional GetExtensionFromDetails(const SourceDetails& details) - { - Deployment::ExtensionCatalog catalog(Deployment::SourceExtensionName); - return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); - } - - std::optional PackagedContextGetCurrentVersion(const SourceDetails& details) - { - auto extension = GetExtensionFromDetails(details); - - if (extension) - { - auto version = extension->GetPackageVersion(); - return Msix::PackageVersion{ version.Major, version.Minor, version.Build, version.Revision }; - } - else - { - return std::nullopt; - } - } - - // Constructs the location that we will write files to. - std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) - { - std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); - result /= PreIndexedPackageSourceFactory::Type(); - result /= GetPackageFamilyNameFromDetails(details); - return result; - } - - std::optional DesktopContextGetCurrentVersion(const SourceDetails& details) - { - std::filesystem::path packageState = GetStatePathFromDetails(details); - std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; - - if (std::filesystem::exists(packagePath)) - { - // If we already have a trusted index package, use it to determine if we need to update or not. - Msix::WriteLockedMsixFile indexPackage{ packagePath }; - if (indexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) - { - Msix::MsixInfo msixInfo{ packagePath }; - auto manifest = msixInfo.GetAppPackageManifests(); - - if (manifest.size() == 1) - { - return manifest[0].GetIdentity().GetVersion(); - } - } - } - - return std::nullopt; - } - - bool CheckForUpdateBeforeOpen(const SourceDetails& details, std::optional currentVersion, const std::optional& requestedUpdateInterval) - { - // If we can't find a good package, then we have to update to operate - if (!currentVersion) - { - AICLI_LOG(Repo, Verbose, << "Source `" << details.Name << "` has no data"); - return true; - } - - using namespace std::chrono_literals; - using clock = std::chrono::system_clock; - - // Attempt to convert the package version to a time_point - clock::time_point versionTime = Utility::GetTimePointFromVersion(currentVersion.value()); - - // Since we expect that the version time indicates creation time, don't let it be far in the future. - auto now = clock::now(); - if (versionTime > now && versionTime - now > 24h) - { - versionTime = clock::time_point::min(); - } - - // Use the later of the version and last update times - clock::time_point timeToCheck = (versionTime > details.LastUpdateTime ? versionTime : details.LastUpdateTime); - - return IsAfterUpdateCheckTime(details.Name, timeToCheck, requestedUpdateInterval); - } - - struct PackagedSourceOpenTimer - { - using clock = std::chrono::steady_clock; - - struct SingleTimer - { - SingleTimer(long long& durationMs) : m_durationMs(durationMs), m_start(clock::now()) {} - - ~SingleTimer() - { - Stop(); - } - - void Stop() - { - if (!m_stopped) - { - m_durationMs += std::chrono::duration_cast(clock::now() - m_start).count(); - m_stopped = true; - } - } - - private: - long long& m_durationMs; - clock::time_point m_start; - bool m_stopped = false; - }; - - PackagedSourceOpenTimer(const std::string& sourceName) : m_sourceName(sourceName), m_start(clock::now()) {} - - ~PackagedSourceOpenTimer() - { - const auto totalMs = std::chrono::duration_cast(clock::now() - m_start).count(); - AICLI_LOG(Repo, Info, << "Packaged source open for '" << m_sourceName << "' " << (m_succeeded ? "succeeded" : "failed") << - " in " << totalMs << " ms [extensionLookup=" << m_extensionLookupMs << - " ms, verifyContentIntegrity=" << m_verifyContentIntegrityMs << - " ms, sqliteOpen=" << m_sqliteOpenMs << " ms, mode=" << (m_usedLockFallback ? "fallbackLocked" : "optimistic") << "]"); - } - - SingleTimer MeasureExtensionLookup() { return SingleTimer{ m_extensionLookupMs }; } - SingleTimer MeasureVerifyContentIntegrity() { return SingleTimer{ m_verifyContentIntegrityMs }; } - SingleTimer MeasureSQLiteOpen() { return SingleTimer{ m_sqliteOpenMs }; } - void MarkFallbackLocked() { m_usedLockFallback = true; } - void MarkSucceeded() { m_succeeded = true; } - - private: - const std::string& m_sourceName; - clock::time_point m_start; - bool m_succeeded = false; - bool m_usedLockFallback = false; - long long m_extensionLookupMs = 0; - long long m_verifyContentIntegrityMs = 0; - long long m_sqliteOpenMs = 0; - }; - - SQLiteIndex OpenPackagedContextIndex(const SourceDetails& details, IProgressCallback& progress, PackagedSourceOpenTimer& openTimer) - { - auto extensionLookupTimer = openTimer.MeasureExtensionLookup(); - auto extension = GetExtensionFromDetails(details); - extensionLookupTimer.Stop(); - if (!extension) - { - AICLI_LOG(Repo, Info, << "Package not found " << details.Data); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); - } - - auto verifyTimer = openTimer.MeasureVerifyContentIntegrity(); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NEEDS_REMEDIATION), !extension->VerifyContentIntegrity(progress)); - verifyTimer.Stop(); - - // To work around an issue with accessing the public folder, we are temporarily - // constructing the location ourself. This was already the case for the non-packaged - // runtime, and we can fix both in the future. The only problem with this is that - // the directory in the extension *must* be Public, rather than one set by the creator. - std::filesystem::path indexLocation = extension->GetPackagePath(); - indexLocation /= s_PreIndexedPackageSourceFactory_IndexFilePath; - - auto sqliteOpenTimer = openTimer.MeasureSQLiteOpen(); - auto index = SQLiteIndex::Open(indexLocation.u8string(), SQLiteIndex::OpenDisposition::Immutable); - sqliteOpenTimer.Stop(); - - return index; - } - - struct PackagedContextSourceReference : public ISourceReference - { - PackagedContextSourceReference(const SourceDetails& details) : m_details(details) - { - if (!m_details.Data.empty()) - { - m_details.Identifier = GetPackageFamilyNameFromDetails(details); - } - } - - std::string GetIdentifier() override { return m_details.Identifier; } - - SourceDetails& GetDetails() override { return m_details; }; - - bool ShouldUpdateBeforeOpen(const std::optional& requestedUpdateInterval) override - { - return CheckForUpdateBeforeOpen(m_details, PackagedContextGetCurrentVersion(m_details), requestedUpdateInterval); - } - - std::shared_ptr Open(IProgressCallback& progress) override - { - PackagedSourceOpenTimer openTimer{ m_details.Name }; - auto completeOpen = [&](SQLiteIndex index) - { - // We didn't use to store the source identifier, so we compute it here in case it's - // missing from the details. - m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); - openTimer.MarkSucceeded(); - return std::make_shared(m_details, std::move(index), false, true); - }; - - std::optional index; - bool retryUnderLock = false; - - try - { - index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); - } - catch (...) - { - if (progress.IsCancelledBy(CancelReason::Any)) - { - throw; - } - - LOG_CAUGHT_EXCEPTION_MSG("Optimistic packaged source open failed, retrying under lock for source: %hs", m_details.Name.c_str()); - retryUnderLock = true; - } - - if (retryUnderLock) - { - openTimer.MarkFallbackLocked(); - Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); - if (!lock.Acquire(progress)) - { - return {}; - } - - index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); - } - - return completeOpen(std::move(index.value())); - } - - private: - SourceDetails m_details; - }; - - // Source factory for running within a packaged context - struct PackagedContextFactory : public PreIndexedFactoryBase - { - std::shared_ptr CreateInternal(const SourceDetails& details) override - { - return std::make_shared(details); - } - - std::optional GetCurrentVersion(const SourceDetails& details) override - { - return PackagedContextGetCurrentVersion(details); - } - - bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) override - { - // Due to complications with deployment, download the file and deploy from - // a local source while we investigate further. - bool download = Utility::IsUrlRemote(packageLocation); - std::filesystem::path localFile; - - if (download) - { - localFile = Runtime::GetPathTo(Runtime::PathName::Temp); - localFile /= GetPackageFamilyNameFromDetails(details) + ".msix"; - - auto downloadResult = Utility::Download(packageLocation, localFile, Utility::DownloadType::Index, progress); - downloadedBytes = downloadResult.SizeInBytes; - } - else - { - localFile = Utility::ConvertToUTF16(packageLocation); - } - - // Verify the local file - Msix::WriteLockedMsixFile fileLock{ localFile }; - Msix::MsixInfo localMsixInfo{ localFile }; - - // The package should not be a bundle - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, localMsixInfo.GetIsBundle()); - - // Ensure that family name has not changed - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, - GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(localMsixInfo.GetPackageFullName())); - - if (!fileLock.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) - { - AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); - } - - winrt::Windows::Foundation::Uri uri = winrt::Windows::Foundation::Uri(localFile.c_str()); - Deployment::AddPackage( - uri, - Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) }, - progress); - - if (download) - { - try - { - // If successful, delete the file - std::filesystem::remove(localFile); - } - CATCH_LOG(); - } - - return true; - } - - bool RemoveInternal(const SourceDetails& details, IProgressCallback& callback) override - { - auto fullName = Msix::GetPackageFullNameFromFamilyName(GetPackageFamilyNameFromDetails(details)); - - if (!fullName) - { - AICLI_LOG(Repo, Info, << "No full name found for family name: " << GetPackageFamilyNameFromDetails(details)); - } - else - { - AICLI_LOG(Repo, Info, << "Removing package: " << *fullName); - Deployment::RemovePackage(*fullName, winrt::Windows::Management::Deployment::RemovalOptions::None, callback); - } - - return true; - } - }; - - struct DesktopContextSourceReference : public ISourceReference - { - DesktopContextSourceReference(const SourceDetails& details) : m_details(details) - { - if (!m_details.Data.empty()) - { - m_details.Identifier = GetPackageFamilyNameFromDetails(details); - } - } - - std::string GetIdentifier() override { return m_details.Identifier; } - - SourceDetails& GetDetails() override { return m_details; }; - - bool ShouldUpdateBeforeOpen(const std::optional& requestedUpdateInterval) override - { - return CheckForUpdateBeforeOpen(m_details, DesktopContextGetCurrentVersion(m_details), requestedUpdateInterval); - } - - std::shared_ptr Open(IProgressCallback& progress) override - { - Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); - if (!lock.Acquire(progress)) - { - return {}; - } - - std::filesystem::path packageLocation = GetStatePathFromDetails(m_details); - packageLocation /= s_PreIndexedPackageSourceFactory_PackageFileName; - - if (!std::filesystem::exists(packageLocation)) - { - AICLI_LOG(Repo, Info, << "Data not found at " << packageLocation); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); - } - - // Put a write exclusive lock on the index package. - Msix::WriteLockedMsixFile indexPackage{ packageLocation }; - - // Validate index package trust info. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, !indexPackage.ValidateTrustInfo(WI_IsFlagSet(m_details.TrustLevel, SourceTrustLevel::StoreOrigin))); - - // Create a temp lock exclusive index file. - auto tempIndexFilePath = Runtime::GetNewTempFilePath(); - auto tempIndexFile = Utility::ManagedFile::CreateWriteLockedFile(tempIndexFilePath, GENERIC_WRITE, true); - - // Populate temp index file. - Msix::MsixInfo packageInfo(packageLocation); - packageInfo.WriteToFileHandle(s_PreIndexedPackageSourceFactory_IndexFilePath, tempIndexFile.GetFileHandle(), progress); - - if (progress.IsCancelledBy(CancelReason::Any)) - { - AICLI_LOG(Repo, Info, << "Cancelling open upon request"); - return {}; - } - - SQLiteIndex index = SQLiteIndex::Open(tempIndexFile.GetFilePath().u8string(), SQLiteIndex::OpenDisposition::Immutable, std::move(tempIndexFile)); - - // We didn't use to store the source identifier, so we compute it here in case it's - // missing from the details. - m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); - return std::make_shared(m_details, std::move(index), false, true); - } - - private: - SourceDetails m_details; - }; - - // Source factory for running outside of a package. - struct DesktopContextFactory : public PreIndexedFactoryBase - { - std::shared_ptr CreateInternal(const SourceDetails& details) override - { - return std::make_shared(details); - } - - std::optional GetCurrentVersion(const SourceDetails& details) override - { - return DesktopContextGetCurrentVersion(details); - } - - bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) override - { - // We will extract the manifest and index files directly to this location - std::filesystem::path packageState = GetStatePathFromDetails(details); - std::filesystem::create_directories(packageState); - - std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; - - std::filesystem::path tempPackagePath = packagePath.u8string() + ".dnld.msix"; - auto removeTempFileOnExit = wil::scope_exit([&]() - { - try - { - std::filesystem::remove(tempPackagePath); - } - catch (...) - { - AICLI_LOG(Repo, Info, << "Failed to remove temp index file at: " << tempPackagePath); - } - }); - - if (Utility::IsUrlRemote(packageLocation)) - { - auto downloadResult = AppInstaller::Utility::Download(packageLocation, tempPackagePath, AppInstaller::Utility::DownloadType::Index, progress); - downloadedBytes = downloadResult.SizeInBytes; - } - else - { - std::filesystem::copy(packageLocation, tempPackagePath); - progress.OnProgress(100, 100, ProgressType::Percent); - } - - if (progress.IsCancelledBy(CancelReason::Any)) - { - AICLI_LOG(Repo, Info, << "Cancelling update upon request"); - return false; - } - - { - // Extra scope to release the file lock right after trust validation. - Msix::WriteLockedMsixFile tempIndexPackage{ tempPackagePath }; - Msix::MsixInfo tempMsixInfo{ tempPackagePath }; - - // The package should not be a bundle - THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, tempMsixInfo.GetIsBundle()); - - // Ensure that family name has not changed - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, - GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(tempMsixInfo.GetPackageFullName())); - - if (!tempIndexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) - { - AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); - THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); - } - } - - std::filesystem::rename(tempPackagePath, packagePath); - AICLI_LOG(Repo, Info, << "Source update success."); - - removeTempFileOnExit.release(); - - return true; - } - - bool RemoveInternal(const SourceDetails& details, IProgressCallback&) override - { - std::filesystem::path packageState = GetStatePathFromDetails(details); - - if (!std::filesystem::exists(packageState)) - { - AICLI_LOG(Repo, Info, << "No state found for source: " << packageState.u8string()); - } - else - { - AICLI_LOG(Repo, Info, << "Removing state found for source: " << packageState.u8string()); - std::filesystem::remove_all(packageState); - } - - return true; - } - }; - } - - std::unique_ptr PreIndexedPackageSourceFactory::Create() - { - if (Runtime::IsRunningInPackagedContext()) - { - return std::make_unique(); - } - else - { - return std::make_unique(); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/PreIndexedPackageSourceFactory.h" +#include "Microsoft/SQLiteIndex.h" +#include "Microsoft/SQLiteIndexSource.h" +#include "SourceUpdateChecks.h" + +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Microsoft +{ + namespace + { + static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageFileName = "source.msix"sv; + static constexpr std::string_view s_PreIndexedPackageSourceFactory_V2_PackageFileName = "source2.msix"sv; + static constexpr std::string_view s_PreIndexedPackageSourceFactory_PackageVersionHeader = "x-ms-meta-sourceversion"sv; + static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFileName = "index.db"sv; + // TODO: This being hard coded to force using the Public directory name is not ideal. + static constexpr std::string_view s_PreIndexedPackageSourceFactory_IndexFilePath = "Public\\index.db"sv; + + // Construct the package location from the given details. + // Currently expects that the arg is an https uri pointing to the root of the data. + std::string GetPackageLocation(const std::string& basePath, std::string_view fileName) + { + std::string result = basePath; + if (result.back() != '/') + { + result += '/'; + } + result += fileName; + return result; + } + + // Gets the set of package locations that should be tried, in order. + std::vector GetPackageLocations(const SourceDetails& details) + { + THROW_HR_IF(E_INVALIDARG, details.Arg.empty()); + + std::vector result; + + result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_V2_PackageFileName)); + result.emplace_back(GetPackageLocation(details.Arg, s_PreIndexedPackageSourceFactory_PackageFileName)); + + if (!details.AlternateArg.empty()) + { + result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_V2_PackageFileName)); + result.emplace_back(GetPackageLocation(details.AlternateArg, s_PreIndexedPackageSourceFactory_PackageFileName)); + } + + return result; + } + + // Abstracts the fallback for package location when the MsixInfo is needed. + struct PreIndexedPackageInfo + { + template + PreIndexedPackageInfo(const SourceDetails& details, LocationCheck&& locationCheck) + { + std::vector potentialLocations = GetPackageLocations(details); + + for (const auto& location : potentialLocations) + { + locationCheck(location); + } + + std::exception_ptr primaryException; + + for (const auto& location : potentialLocations) + { + try + { + m_msixInfo = std::make_unique(location); + m_packageLocation = location; + return; + } + catch (...) + { + LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageInfo failed on location: %hs", location.c_str()); + if (!primaryException) + { + primaryException = std::current_exception(); + } + } + } + + std::rethrow_exception(primaryException); + } + + const std::string& PackageLocation() const { return m_packageLocation; } + Msix::MsixInfo& MsixInfo() { return *m_msixInfo; } + + private: + std::string m_packageLocation; + std::unique_ptr m_msixInfo; + }; + + // Abstracts the fallback for package location when an update is being done. + struct PreIndexedPackageUpdateCheck + { + PreIndexedPackageUpdateCheck(const SourceDetails& details) + { + std::vector potentialLocations = GetPackageLocations(details); + + std::exception_ptr primaryException; + + for (const auto& location : potentialLocations) + { + try + { + m_availableVersion = GetAvailableVersionFrom(location); + m_packageLocation = location; + return; + } + catch (...) + { + LOG_CAUGHT_EXCEPTION_MSG("PreIndexedPackageUpdateCheck failed on location: %hs", location.c_str()); + if (!primaryException) + { + primaryException = std::current_exception(); + } + } + } + + std::rethrow_exception(primaryException); + } + + const std::string& PackageLocation() const { return m_packageLocation; } + const Msix::PackageVersion& AvailableVersion() const { return m_availableVersion; } + + private: + std::string m_packageLocation; + Msix::PackageVersion m_availableVersion; + + Msix::PackageVersion GetAvailableVersionFrom(const std::string& packageLocation) + { + if (Utility::IsUrlRemote(packageLocation)) + { + std::map headers = Utility::GetHeaders(packageLocation); + auto itr = headers.find(std::string{ s_PreIndexedPackageSourceFactory_PackageVersionHeader }); + if (itr != headers.end()) + { + AICLI_LOG(Repo, Verbose, << "Header indicates version is: " << itr->second); + return { itr->second }; + } + + // We did not find the header we were looking for, log the ones we did find + AICLI_LOG(Repo, Verbose, << "Did not find " << s_PreIndexedPackageSourceFactory_PackageVersionHeader << " in:\n" << [&]() + { + std::ostringstream headerLog; + for (const auto& header : headers) + { + headerLog << " " << header.first << " : " << header.second << '\n'; + } + return std::move(headerLog).str(); + }()); + } + + AICLI_LOG(Repo, Verbose, << "Reading package data to determine version"); + Msix::MsixInfo info{ packageLocation }; + auto manifest = info.GetAppPackageManifests(); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, manifest.size() > 1); + THROW_HR_IF(E_UNEXPECTED, manifest.size() == 0); + + return manifest[0].GetIdentity().GetVersion(); + } + }; + + // Gets the package family name from the details. + std::string GetPackageFamilyNameFromDetails(const SourceDetails& details) + { + THROW_HR_IF(E_UNEXPECTED, details.Data.empty()); + return details.Data; + } + + // Creates a name for the cross process reader-writer lock given the details. + std::string CreateNameForCPL(const SourceDetails& details) + { + // The only relevant data is the package family name + return "PreIndexedSourceCPL_"s + GetPackageFamilyNameFromDetails(details); + } + + // The base class for a package that comes from a preindexed packaged source. + struct PreIndexedFactoryBase : public ISourceFactory + { + std::string_view TypeName() const override final + { + return PreIndexedPackageSourceFactory::Type(); + } + + std::shared_ptr Create(const SourceDetails& details) override final + { + // With more than one source implementation, we will probably need to probe first + THROW_HR_IF(E_INVALIDARG, !details.Type.empty() && details.Type != PreIndexedPackageSourceFactory::Type()); + + return CreateInternal(details); + } + + virtual std::shared_ptr CreateInternal(const SourceDetails& details) = 0; + + bool Add(SourceDetails& details, IProgressCallback& progress) override final + { + if (details.Type.empty()) + { + // With more than one source implementation, we will probably need to probe first + details.Type = PreIndexedPackageSourceFactory::Type(); + AICLI_LOG(Repo, Info, << "Initializing source type: " << details.Name << " => " << details.Type); + } + else + { + THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); + } + + PreIndexedPackageInfo packageInfo(details, [](const std::string& packageLocation) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, Utility::IsUrlRemote(packageLocation) && !Utility::IsUrlSecure(packageLocation)); + }); + + AICLI_LOG(Repo, Info, << "Initializing source from: " << details.Name << " => " << packageInfo.PackageLocation()); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, packageInfo.MsixInfo().GetIsBundle()); + + auto fullName = packageInfo.MsixInfo().GetPackageFullName(); + AICLI_LOG(Repo, Info, << "Found package full name: " << details.Name << " => " << fullName); + + details.Data = Msix::GetPackageFamilyNameFromFullName(fullName); + details.Identifier = Msix::GetPackageFamilyNameFromFullName(fullName); + + auto lock = LockExclusive(details, progress); + if (!lock) + { + return false; + } + + std::optional downloadedBytes; + bool result = UpdateInternal(packageInfo.PackageLocation(), details, progress, downloadedBytes); + + if (downloadedBytes) + { + try + { + auto manifests = packageInfo.MsixInfo().GetAppPackageManifests(); + if (!manifests.empty()) + { + Logging::Telemetry().LogPreindexedPackageUpdate( + details.Identifier, + std::nullopt, + Utility::GetTimePointFromVersion(manifests[0].GetIdentity().GetVersion()), + false, + std::nullopt, + std::nullopt, + false, + downloadedBytes.value(), + true); + } + } + CATCH_LOG(); + } + + return result; + } + + bool Update(const SourceDetails& details, IProgressCallback& progress) override final + { + return UpdateBase(details, false, progress); + } + + bool BackgroundUpdate(const SourceDetails& details, IProgressCallback& progress) override final + { + return UpdateBase(details, true, progress); + } + + // Retrieves the currently cached version of the package. + virtual std::optional GetCurrentVersion(const SourceDetails& details) = 0; + + virtual bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) = 0; + + bool Remove(const SourceDetails& details, IProgressCallback& progress) override final + { + THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); + auto lock = LockExclusive(details, progress); + if (!lock) + { + return false; + } + + return RemoveInternal(details, progress); + } + + virtual bool RemoveInternal(const SourceDetails& details, IProgressCallback&) = 0; + + private: + Synchronization::CrossProcessLock LockExclusive(const SourceDetails& details, IProgressCallback& progress, bool isBackground = false) + { + Synchronization::CrossProcessLock result(CreateNameForCPL(details)); + + if (isBackground) + { + // If this is a background update, don't wait on the lock. + result.TryAcquireNoWait(); + } + else + { + result.Acquire(progress); + } + + return result; + } + + bool UpdateBase(const SourceDetails& details, bool isBackground, IProgressCallback& progress) + { + THROW_HR_IF(E_INVALIDARG, details.Type != PreIndexedPackageSourceFactory::Type()); + + std::optional currentVersion = GetCurrentVersion(details); + PreIndexedPackageUpdateCheck updateCheck(details); + + if (currentVersion) + { + if (currentVersion.value() >= updateCheck.AvailableVersion()) + { + AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() << + ") was not newer than existing (" << currentVersion.value().ToString() << "), no update needed"); + return true; + } + else + { + AICLI_LOG(Repo, Verbose, << "Remote source data (" << updateCheck.AvailableVersion().ToString() << + ") was newer than existing (" << currentVersion.value().ToString() << "), updating"); + } + } + + if (progress.IsCancelledBy(CancelReason::Any)) + { + AICLI_LOG(Repo, Info, << "Cancelling update upon request"); + return false; + } + + auto lock = LockExclusive(details, progress, isBackground); + if (!lock) + { + return false; + } + + std::optional downloadedBytes = 0; + bool result = UpdateInternal(updateCheck.PackageLocation(), details, progress, downloadedBytes); + + if (downloadedBytes) + { + std::optional previousIndexPublishedAt; + if (currentVersion) + { + previousIndexPublishedAt = Utility::GetTimePointFromVersion(currentVersion.value()); + } + + Logging::Telemetry().LogPreindexedPackageUpdate( + details.Identifier, + previousIndexPublishedAt, + Utility::GetTimePointFromVersion(updateCheck.AvailableVersion()), + false, + std::nullopt, + std::nullopt, + false, + downloadedBytes.value(), + !isBackground); + } + + return result; + } + }; + + // Optimistic packaged source open may call this without the cross process lock and retry under the lock on failure. + std::optional GetExtensionFromDetails(const SourceDetails& details) + { + Deployment::ExtensionCatalog catalog(Deployment::SourceExtensionName); + return catalog.FindByPackageFamilyAndId(GetPackageFamilyNameFromDetails(details), Deployment::IndexDBId); + } + + std::optional PackagedContextGetCurrentVersion(const SourceDetails& details) + { + auto extension = GetExtensionFromDetails(details); + + if (extension) + { + auto version = extension->GetPackageVersion(); + return Msix::PackageVersion{ version.Major, version.Minor, version.Build, version.Revision }; + } + else + { + return std::nullopt; + } + } + + // Constructs the location that we will write files to. + std::filesystem::path GetStatePathFromDetails(const SourceDetails& details) + { + std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); + result /= PreIndexedPackageSourceFactory::Type(); + result /= GetPackageFamilyNameFromDetails(details); + return result; + } + + std::optional DesktopContextGetCurrentVersion(const SourceDetails& details) + { + std::filesystem::path packageState = GetStatePathFromDetails(details); + std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; + + if (std::filesystem::exists(packagePath)) + { + // If we already have a trusted index package, use it to determine if we need to update or not. + Msix::WriteLockedMsixFile indexPackage{ packagePath }; + if (indexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) + { + Msix::MsixInfo msixInfo{ packagePath }; + auto manifest = msixInfo.GetAppPackageManifests(); + + if (manifest.size() == 1) + { + return manifest[0].GetIdentity().GetVersion(); + } + } + } + + return std::nullopt; + } + + bool CheckForUpdateBeforeOpen(const SourceDetails& details, std::optional currentVersion, const std::optional& requestedUpdateInterval) + { + // If we can't find a good package, then we have to update to operate + if (!currentVersion) + { + AICLI_LOG(Repo, Verbose, << "Source `" << details.Name << "` has no data"); + return true; + } + + using namespace std::chrono_literals; + using clock = std::chrono::system_clock; + + // Attempt to convert the package version to a time_point + clock::time_point versionTime = Utility::GetTimePointFromVersion(currentVersion.value()); + + // Since we expect that the version time indicates creation time, don't let it be far in the future. + auto now = clock::now(); + if (versionTime > now && versionTime - now > 24h) + { + versionTime = clock::time_point::min(); + } + + // Use the later of the version and last update times + clock::time_point timeToCheck = (versionTime > details.LastUpdateTime ? versionTime : details.LastUpdateTime); + + return IsAfterUpdateCheckTime(details.Name, timeToCheck, requestedUpdateInterval); + } + + struct PackagedSourceOpenTimer + { + using clock = std::chrono::steady_clock; + + struct SingleTimer + { + SingleTimer(long long& durationMs) : m_durationMs(durationMs), m_start(clock::now()) {} + + ~SingleTimer() + { + Stop(); + } + + void Stop() + { + if (!m_stopped) + { + m_durationMs += std::chrono::duration_cast(clock::now() - m_start).count(); + m_stopped = true; + } + } + + private: + long long& m_durationMs; + clock::time_point m_start; + bool m_stopped = false; + }; + + PackagedSourceOpenTimer(const std::string& sourceName) : m_sourceName(sourceName), m_start(clock::now()) {} + + ~PackagedSourceOpenTimer() + { + const auto totalMs = std::chrono::duration_cast(clock::now() - m_start).count(); + AICLI_LOG(Repo, Info, << "Packaged source open for '" << m_sourceName << "' " << (m_succeeded ? "succeeded" : "failed") << + " in " << totalMs << " ms [extensionLookup=" << m_extensionLookupMs << + " ms, verifyContentIntegrity=" << m_verifyContentIntegrityMs << + " ms, sqliteOpen=" << m_sqliteOpenMs << " ms, mode=" << (m_usedLockFallback ? "fallbackLocked" : "optimistic") << "]"); + } + + SingleTimer MeasureExtensionLookup() { return SingleTimer{ m_extensionLookupMs }; } + SingleTimer MeasureVerifyContentIntegrity() { return SingleTimer{ m_verifyContentIntegrityMs }; } + SingleTimer MeasureSQLiteOpen() { return SingleTimer{ m_sqliteOpenMs }; } + void MarkFallbackLocked() { m_usedLockFallback = true; } + void MarkSucceeded() { m_succeeded = true; } + + private: + const std::string& m_sourceName; + clock::time_point m_start; + bool m_succeeded = false; + bool m_usedLockFallback = false; + long long m_extensionLookupMs = 0; + long long m_verifyContentIntegrityMs = 0; + long long m_sqliteOpenMs = 0; + }; + + SQLiteIndex OpenPackagedContextIndex(const SourceDetails& details, IProgressCallback& progress, PackagedSourceOpenTimer& openTimer) + { + auto extensionLookupTimer = openTimer.MeasureExtensionLookup(); + auto extension = GetExtensionFromDetails(details); + extensionLookupTimer.Stop(); + if (!extension) + { + AICLI_LOG(Repo, Info, << "Package not found " << details.Data); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); + } + + auto verifyTimer = openTimer.MeasureVerifyContentIntegrity(); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NEEDS_REMEDIATION), !extension->VerifyContentIntegrity(progress)); + verifyTimer.Stop(); + + // To work around an issue with accessing the public folder, we are temporarily + // constructing the location ourself. This was already the case for the non-packaged + // runtime, and we can fix both in the future. The only problem with this is that + // the directory in the extension *must* be Public, rather than one set by the creator. + std::filesystem::path indexLocation = extension->GetPackagePath(); + indexLocation /= s_PreIndexedPackageSourceFactory_IndexFilePath; + + auto sqliteOpenTimer = openTimer.MeasureSQLiteOpen(); + auto index = SQLiteIndex::Open(indexLocation.u8string(), SQLiteIndex::OpenDisposition::Immutable); + sqliteOpenTimer.Stop(); + + return index; + } + + struct PackagedContextSourceReference : public ISourceReference + { + PackagedContextSourceReference(const SourceDetails& details) : m_details(details) + { + if (!m_details.Data.empty()) + { + m_details.Identifier = GetPackageFamilyNameFromDetails(details); + } + } + + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + bool ShouldUpdateBeforeOpen(const std::optional& requestedUpdateInterval) override + { + return CheckForUpdateBeforeOpen(m_details, PackagedContextGetCurrentVersion(m_details), requestedUpdateInterval); + } + + std::shared_ptr Open(IProgressCallback& progress) override + { + PackagedSourceOpenTimer openTimer{ m_details.Name }; + auto completeOpen = [&](SQLiteIndex index) + { + // We didn't use to store the source identifier, so we compute it here in case it's + // missing from the details. + m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); + openTimer.MarkSucceeded(); + return std::make_shared(m_details, std::move(index), false, true); + }; + + std::optional index; + bool retryUnderLock = false; + + try + { + index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); + } + catch (...) + { + if (progress.IsCancelledBy(CancelReason::Any)) + { + throw; + } + + LOG_CAUGHT_EXCEPTION_MSG("Optimistic packaged source open failed, retrying under lock for source: %hs", m_details.Name.c_str()); + retryUnderLock = true; + } + + if (retryUnderLock) + { + openTimer.MarkFallbackLocked(); + Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); + if (!lock.Acquire(progress)) + { + return {}; + } + + index.emplace(OpenPackagedContextIndex(m_details, progress, openTimer)); + } + + return completeOpen(std::move(index.value())); + } + + private: + SourceDetails m_details; + }; + + // Source factory for running within a packaged context + struct PackagedContextFactory : public PreIndexedFactoryBase + { + std::shared_ptr CreateInternal(const SourceDetails& details) override + { + return std::make_shared(details); + } + + std::optional GetCurrentVersion(const SourceDetails& details) override + { + return PackagedContextGetCurrentVersion(details); + } + + bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) override + { + // Due to complications with deployment, download the file and deploy from + // a local source while we investigate further. + bool download = Utility::IsUrlRemote(packageLocation); + std::filesystem::path localFile; + + if (download) + { + localFile = Runtime::GetPathTo(Runtime::PathName::Temp); + localFile /= GetPackageFamilyNameFromDetails(details) + ".msix"; + + auto downloadResult = Utility::Download(packageLocation, localFile, Utility::DownloadType::Index, progress); + downloadedBytes = downloadResult.SizeInBytes; + } + else + { + localFile = Utility::ConvertToUTF16(packageLocation); + } + + // Verify the local file + Msix::WriteLockedMsixFile fileLock{ localFile }; + Msix::MsixInfo localMsixInfo{ localFile }; + + // The package should not be a bundle + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, localMsixInfo.GetIsBundle()); + + // Ensure that family name has not changed + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, + GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(localMsixInfo.GetPackageFullName())); + + if (!fileLock.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) + { + AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); + } + + winrt::Windows::Foundation::Uri uri = winrt::Windows::Foundation::Uri(localFile.c_str()); + Deployment::AddPackage( + uri, + Deployment::Options{ WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::Trusted) }, + progress); + + if (download) + { + try + { + // If successful, delete the file + std::filesystem::remove(localFile); + } + CATCH_LOG(); + } + + return true; + } + + bool RemoveInternal(const SourceDetails& details, IProgressCallback& callback) override + { + auto fullName = Msix::GetPackageFullNameFromFamilyName(GetPackageFamilyNameFromDetails(details)); + + if (!fullName) + { + AICLI_LOG(Repo, Info, << "No full name found for family name: " << GetPackageFamilyNameFromDetails(details)); + } + else + { + AICLI_LOG(Repo, Info, << "Removing package: " << *fullName); + Deployment::RemovePackage(*fullName, winrt::Windows::Management::Deployment::RemovalOptions::None, callback); + } + + return true; + } + }; + + struct DesktopContextSourceReference : public ISourceReference + { + DesktopContextSourceReference(const SourceDetails& details) : m_details(details) + { + if (!m_details.Data.empty()) + { + m_details.Identifier = GetPackageFamilyNameFromDetails(details); + } + } + + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + bool ShouldUpdateBeforeOpen(const std::optional& requestedUpdateInterval) override + { + return CheckForUpdateBeforeOpen(m_details, DesktopContextGetCurrentVersion(m_details), requestedUpdateInterval); + } + + std::shared_ptr Open(IProgressCallback& progress) override + { + Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details)); + if (!lock.Acquire(progress)) + { + return {}; + } + + std::filesystem::path packageLocation = GetStatePathFromDetails(m_details); + packageLocation /= s_PreIndexedPackageSourceFactory_PackageFileName; + + if (!std::filesystem::exists(packageLocation)) + { + AICLI_LOG(Repo, Info, << "Data not found at " << packageLocation); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING); + } + + // Put a write exclusive lock on the index package. + Msix::WriteLockedMsixFile indexPackage{ packageLocation }; + + // Validate index package trust info. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, !indexPackage.ValidateTrustInfo(WI_IsFlagSet(m_details.TrustLevel, SourceTrustLevel::StoreOrigin))); + + // Create a temp lock exclusive index file. + auto tempIndexFilePath = Runtime::GetNewTempFilePath(); + auto tempIndexFile = Utility::ManagedFile::CreateWriteLockedFile(tempIndexFilePath, GENERIC_WRITE, true); + + // Populate temp index file. + Msix::MsixInfo packageInfo(packageLocation); + packageInfo.WriteToFileHandle(s_PreIndexedPackageSourceFactory_IndexFilePath, tempIndexFile.GetFileHandle(), progress); + + if (progress.IsCancelledBy(CancelReason::Any)) + { + AICLI_LOG(Repo, Info, << "Cancelling open upon request"); + return {}; + } + + SQLiteIndex index = SQLiteIndex::Open(tempIndexFile.GetFilePath().u8string(), SQLiteIndex::OpenDisposition::Immutable, std::move(tempIndexFile)); + + // We didn't use to store the source identifier, so we compute it here in case it's + // missing from the details. + m_details.Identifier = GetPackageFamilyNameFromDetails(m_details); + return std::make_shared(m_details, std::move(index), false, true); + } + + private: + SourceDetails m_details; + }; + + // Source factory for running outside of a package. + struct DesktopContextFactory : public PreIndexedFactoryBase + { + std::shared_ptr CreateInternal(const SourceDetails& details) override + { + return std::make_shared(details); + } + + std::optional GetCurrentVersion(const SourceDetails& details) override + { + return DesktopContextGetCurrentVersion(details); + } + + bool UpdateInternal(const std::string& packageLocation, const SourceDetails& details, IProgressCallback& progress, std::optional& downloadedBytes) override + { + // We will extract the manifest and index files directly to this location + std::filesystem::path packageState = GetStatePathFromDetails(details); + std::filesystem::create_directories(packageState); + + std::filesystem::path packagePath = packageState / s_PreIndexedPackageSourceFactory_PackageFileName; + + std::filesystem::path tempPackagePath = packagePath.u8string() + ".dnld.msix"; + auto removeTempFileOnExit = wil::scope_exit([&]() + { + try + { + std::filesystem::remove(tempPackagePath); + } + catch (...) + { + AICLI_LOG(Repo, Info, << "Failed to remove temp index file at: " << tempPackagePath); + } + }); + + if (Utility::IsUrlRemote(packageLocation)) + { + auto downloadResult = AppInstaller::Utility::Download(packageLocation, tempPackagePath, AppInstaller::Utility::DownloadType::Index, progress); + downloadedBytes = downloadResult.SizeInBytes; + } + else + { + std::filesystem::copy(packageLocation, tempPackagePath); + progress.OnProgress(100, 100, ProgressType::Percent); + } + + if (progress.IsCancelledBy(CancelReason::Any)) + { + AICLI_LOG(Repo, Info, << "Cancelling update upon request"); + return false; + } + + { + // Extra scope to release the file lock right after trust validation. + Msix::WriteLockedMsixFile tempIndexPackage{ tempPackagePath }; + Msix::MsixInfo tempMsixInfo{ tempPackagePath }; + + // The package should not be a bundle + THROW_HR_IF(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, tempMsixInfo.GetIsBundle()); + + // Ensure that family name has not changed + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, + GetPackageFamilyNameFromDetails(details) != Msix::GetPackageFamilyNameFromFullName(tempMsixInfo.GetPackageFullName())); + + if (!tempIndexPackage.ValidateTrustInfo(WI_IsFlagSet(details.TrustLevel, SourceTrustLevel::StoreOrigin))) + { + AICLI_LOG(Repo, Error, << "Source update failed. Source package failed trust validation."); + THROW_HR(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE); + } + } + + std::filesystem::rename(tempPackagePath, packagePath); + AICLI_LOG(Repo, Info, << "Source update success."); + + removeTempFileOnExit.release(); + + return true; + } + + bool RemoveInternal(const SourceDetails& details, IProgressCallback&) override + { + std::filesystem::path packageState = GetStatePathFromDetails(details); + + if (!std::filesystem::exists(packageState)) + { + AICLI_LOG(Repo, Info, << "No state found for source: " << packageState.u8string()); + } + else + { + AICLI_LOG(Repo, Info, << "Removing state found for source: " << packageState.u8string()); + std::filesystem::remove_all(packageState); + } + + return true; + } + }; + } + + std::unique_ptr PreIndexedPackageSourceFactory::Create() + { + if (Runtime::IsRunningInPackagedContext()) + { + return std::make_unique(); + } + else + { + return std::make_unique(); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.h index 9e5698f411..838750a314 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PreIndexedPackageSourceFactory.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include "SourceFactory.h" - -#include - -namespace AppInstaller::Repository::Microsoft -{ - using namespace std::string_view_literals; - - // A source where the index is precomputed and stored on a server within an optional MSIX package. - // In addition, the manifest files are also individually available on the server. - // Arg :: Expected to be a fully qualified path to the root of the data. - // This can be a web location such as https://somewhere/ or a local file share \\somewhere\ - // Under this path there must exist an MSIX package called "index.msix". - // This must have a file called "index.db" contained within, which is a SQLiteIndex. - // The index's paths refer to relative locations under the Arg value. - // Data :: The package family name of the package at Arg + /index.msix. - struct PreIndexedPackageSourceFactory - { - // Get the type string for this source. - static constexpr std::string_view Type() - { - using namespace std::string_view_literals; - return "Microsoft.PreIndexed.Package"sv; - } - - // Creates a source factory for this type. - static std::unique_ptr Create(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include "SourceFactory.h" + +#include + +namespace AppInstaller::Repository::Microsoft +{ + using namespace std::string_view_literals; + + // A source where the index is precomputed and stored on a server within an optional MSIX package. + // In addition, the manifest files are also individually available on the server. + // Arg :: Expected to be a fully qualified path to the root of the data. + // This can be a web location such as https://somewhere/ or a local file share \\somewhere\ + // Under this path there must exist an MSIX package called "index.msix". + // This must have a file called "index.db" contained within, which is a SQLiteIndex. + // The index's paths refer to relative locations under the Arg value. + // Data :: The package family name of the package at Arg + /index.msix. + struct PreIndexedPackageSourceFactory + { + // Get the type string for this source. + static constexpr std::string_view Type() + { + using namespace std::string_view_literals; + return "Microsoft.PreIndexed.Package"sv; + } + + // Creates a source factory for this type. + static std::unique_ptr Create(); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp index ca93275329..40edd79e16 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.cpp @@ -1,504 +1,504 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/ARPHelper.h" -#include "Microsoft/FontHelper.h" -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include "Microsoft/SQLiteIndex.h" -#include "Microsoft/SQLiteIndexSource.h" -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Microsoft -{ - namespace - { - std::optional GetCachedMSIXName(const Utility::NormalizedString& id, const Utility::Version& version, SQLiteIndex& cacheData) - { - SearchRequest searchRequest; - searchRequest.Inclusions.emplace_back(PackageMatchField::Id, MatchType::Exact, id); - - SQLiteIndex::SearchResult searchResult = cacheData.Search(searchRequest); - - if (searchResult.Matches.empty()) - { - return std::nullopt; - } - - if (searchResult.Matches.size() != 1) - { - // This is very unexpected, but just log it and carry on - AICLI_LOG(Repo, Warning, << "Found multiple (" << searchResult.Matches.size() << ") cache entries for: " << id); - return std::nullopt; - } - - auto versionKeys = cacheData.GetVersionKeysById(searchResult.Matches[0].first); - const SQLiteIndex::VersionKey* versionKey = nullptr; - - for (const auto& key : versionKeys) - { - if (key.VersionAndChannel.GetVersion() == version) - { - versionKey = &key; - break; - } - } - - if (!versionKey) - { - return std::nullopt; - } - - return cacheData.GetPropertyByPrimaryId(versionKey->ManifestId, PackageVersionProperty::Name); - } - - // Populates the index with the entries from MSIX. - void PopulateIndexFromMSIX(SQLiteIndex& index, Manifest::ScopeEnum scope, SQLiteIndex* cacheData = nullptr) - { - using namespace winrt::Windows::ApplicationModel; - using namespace winrt::Windows::Management::Deployment; - using namespace winrt::Windows::Foundation::Collections; - - AICLI_LOG(Repo, Verbose, << "Examining MSIX entries for " << ScopeToString(scope)); - - IIterable packages; - PackageManager packageManager; - - if (scope == Manifest::ScopeEnum::Machine) - { - // May not be present on our oldest supported systems; simply ignore for the time being. - IPackageManager9 packageManager9 = packageManager.try_as(); - if (packageManager9) - { - try - { - packages = packageManager.FindProvisionedPackages(); - } - catch (const winrt::hresult_error& hre) - { - // Historically this API has not been accessible unelevated; if it fails, try to carry on - AICLI_LOG(Repo, Warning, << "FindProvisionedPackages failed, bypassing provisioned packages: 0x" << Logging::SetHRFormat << hre.code()); - } - } - else - { - AICLI_LOG(Repo, Warning, << "FindProvisionedPackages is not available on this version of Windows"); - } - } - else - { - // TODO: Consider if Optional packages should also be enumerated - for (PackageTypes types : { PackageTypes::Main | PackageTypes::Framework, PackageTypes::Main, PackageTypes::Framework }) - { - try - { - packages = packageManager.FindPackagesForUserWithPackageTypes({}, types); - break; - } - catch (const winrt::hresult_error& hre) - { - if (hre.code() == E_NOT_SET) - { - // This OS issue occurs frequently enough that we will attempt to work around it by enumerating progressively fewer packages - AICLI_LOG(Repo, Warning, << "FindPackagesForUserWithPackageTypes returned E_NOT_SET for types: " << ToIntegral(types)); - } - else - { - throw; - } - } - } - } - - // Failed to retrieve even an empty package list; make sure that these cases have a log to indicate why. - if (!packages) - { - AICLI_LOG(Repo, Warning, << "MSIX package list not populated"); - return; - } - - // Reuse the same manifest object, as we will be setting the same values every time. - Manifest::Manifest manifest; - // Add one installer for storing the package family name. - manifest.Installers.emplace_back(); - // Every package will have the same tags currently. - manifest.DefaultLocalization.Add({ "msix" }); - - // Fields in the index but not populated: - // AppMoniker - Not sure what we would put. - // Channel - We don't know this information here. - // Commands - We could open the manifest and look for these eventually. - // Tags - Not sure what else we could put in here. - for (const auto& package : packages) - { - // System packages are part of the OS, and cannot be managed by the user. - // Filter them out as there is no point in showing them in a package manager. - auto signatureKind = package.SignatureKind(); - if (signatureKind == PackageSignatureKind::System) - { - continue; - } - - auto packageId = package.Id(); - Utility::NormalizedString fullName = Utility::ConvertToUTF8(packageId.FullName()); - Utility::NormalizedString familyName = Utility::ConvertToUTF8(packageId.FamilyName()); - - manifest.Id = "MSIX\\" + fullName; - - // Get version - std::ostringstream strstr; - auto packageVersion = packageId.Version(); - strstr << packageVersion.Major << '.' << packageVersion.Minor << '.' << packageVersion.Build << '.' << packageVersion.Revision; - - manifest.Version = strstr.str(); - - // Determine package name - bool isPackageNameSet = false; - - // Look for the name in the cache data first - if (cacheData) - { - std::optional cachedName = GetCachedMSIXName(manifest.Id, manifest.Version, *cacheData); - - if (cachedName) - { - manifest.DefaultLocalization.Add(cachedName.value()); - isPackageNameSet = true; - } - } - - // Attempt to get the DisplayName. Since this will retrieve the localized value, it has a chance to fail. - // Rather than completely skip this package in that case, we will simply fall back to using the package name below. - if (!isPackageNameSet && !Runtime::IsRunningAsSystem()) - { - try - { - auto displayName = Utility::ConvertToUTF8(package.DisplayName()); - if (!displayName.empty()) - { - manifest.DefaultLocalization.Add(displayName); - isPackageNameSet = true; - } - } - catch (const winrt::hresult_error& hre) - { - AICLI_LOG(Repo, Warning, << "winrt::hresult_error[0x" << Logging::SetHRFormat << hre.code() << ": " << - Utility::ConvertToUTF8(hre.message()) << "] exception thrown when getting DisplayName for " << fullName); - } - catch (...) - { - AICLI_LOG(Repo, Warning, << "Unknown exception thrown when getting DisplayName for " << fullName); - } - } - - if (!isPackageNameSet) - { - manifest.DefaultLocalization.Add(Utility::ConvertToUTF8(packageId.Name())); - } - - manifest.Installers[0].PackageFamilyName = familyName; - - // Use the full name as a unique key for the path - auto manifestId = index.AddManifest(manifest); - - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, - Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msix)); - - auto architecture = Utility::ConvertToArchitectureEnum(packageId.Architecture()); - if (architecture) - { - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, - ToString(architecture.value())); - } - - // May not be present on our oldest supported systems; simply ignore for the time being. - IPackage8 package8 = package.try_as(); - if (package8) - { - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledLocation, - Utility::ConvertToUTF8(package8.InstalledPath())); - } - else - { - AICLI_LOG(Repo, Warning, << "Windows::ApplicationModel::Package::InstalledPath is not available on this version of Windows"); - } - } - } - - SQLiteIndex CreateAndPopulateIndex(PredefinedInstalledSourceFactory::Filter filter) - { - AICLI_LOG(Repo, Verbose, << "Creating PredefinedInstalledSource with filter [" << PredefinedInstalledSourceFactory::FilterToString(filter) << ']'); - - // Create an in memory index - SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless); - - // Put installed packages into the index - if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP || - filter == PredefinedInstalledSourceFactory::Filter::User || filter == PredefinedInstalledSourceFactory::Filter::Machine) - { - ARPHelper arpHelper; - if (filter != PredefinedInstalledSourceFactory::Filter::User) - { - arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::Machine); - } - if (filter != PredefinedInstalledSourceFactory::Filter::Machine) - { - arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::User); - } - } - - if (filter == PredefinedInstalledSourceFactory::Filter::None || - filter == PredefinedInstalledSourceFactory::Filter::MSIX || - filter == PredefinedInstalledSourceFactory::Filter::User) - { - PopulateIndexFromMSIX(index, Manifest::ScopeEnum::User); - } - else if (filter == PredefinedInstalledSourceFactory::Filter::Machine) - { - PopulateIndexFromMSIX(index, Manifest::ScopeEnum::Machine); - } - - // Put Installed Fonts into the index. - if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP || - filter == PredefinedInstalledSourceFactory::Filter::User || filter == PredefinedInstalledSourceFactory::Filter::Machine) - { - FontHelper fontHelper; - if (filter != PredefinedInstalledSourceFactory::Filter::User) - { - fontHelper.PopulateIndex(index, Manifest::ScopeEnum::Machine); - } - - if (filter != PredefinedInstalledSourceFactory::Filter::Machine) - { - fontHelper.PopulateIndex(index, Manifest::ScopeEnum::User); - } - } - - AICLI_LOG(Repo, Verbose, << " ... finished creating PredefinedInstalledSource"); - - return index; - } - - struct CachedInstalledIndex - { - struct Singleton : public WinRT::COMStaticStorageBase - { - Singleton() : COMStaticStorageBase(L"WindowsPackageManager.CachedInstalledIndex") {} - }; - - CachedInstalledIndex() - { - ARPHelper arpHelper; - m_registryWatchers = arpHelper.CreateRegistryWatchers(Manifest::ScopeEnum::Unknown, - [this](Manifest::ScopeEnum, Utility::Architecture, wil::RegistryChangeKind) { ForceNextUpdate(); }); - - FontHelper fontHelper; - fontHelper.AddRegistryWatchers(Manifest::ScopeEnum::Unknown, - [this](Manifest::ScopeEnum, wil::RegistryChangeKind) { ForceNextUpdate(); }, m_registryWatchers); - - m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentUser(); - m_eventRevoker = m_catalog.PackageStatusChanged(winrt::auto_revoke, [this](auto...) { ForceNextUpdate(); }); - } - - void UpdateIndexIfNeeded() - { - auto sharedLock = m_lock.lock_shared(); - if (CheckForUpdate()) - { - // Upgrade to exclusive - sharedLock.reset(); - auto exclusiveLock = m_lock.lock_exclusive(); - - if (CheckForUpdate()) - { - // TODO: To support servicing, the initial implementation of update will simply leverage - // some data from the existing index to speed up the MSIX populate function. - // In a larger update, we may want to make it possible to actually update the cache directly. - // We may even persist the cache to disk to speed things up further. - - // Set the update indicator to false before we start reading so that an external change can - // reindicate a need to update in the middle. But in the event that we error here, set it back to true - // to prevent an error from blocking further attempts. - m_forceNextUpdate = false; - auto scopeExit = wil::scope_exit([&]() { m_forceNextUpdate = true; }); - - // Populate from ARP using standard mechanism. - SQLiteIndex update = CreateAndPopulateIndex(PredefinedInstalledSourceFactory::Filter::ARP); - - // Populate from MSIX, using localization data from the existing index if applicable. - PopulateIndexFromMSIX(update, Manifest::ScopeEnum::User, m_index.get()); - - m_index = std::make_unique(std::move(update)); - scopeExit.release(); - } - } - } - - SQLiteIndex GetCopy() - { - auto lock = m_lock.lock_shared(); - THROW_HR_IF(E_POINTER, !m_index); - return SQLiteIndex::CopyFrom(SQLITE_MEMORY_DB_CONNECTION_TARGET, *m_index); - } - - void ForceNextUpdate() - { - m_forceNextUpdate = true; - } - - private: - bool CheckForUpdate() - { - return (!m_index || m_forceNextUpdate.load()); - } - - wil::srwlock m_lock; - std::atomic_bool m_forceNextUpdate{ false }; - std::unique_ptr m_index; - std::vector m_registryWatchers; - winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr; - decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageStatusChanged(winrt::auto_revoke, nullptr)) m_eventRevoker; - }; - - struct PredefinedInstalledSourceReference : public ISourceReference - { - PredefinedInstalledSourceReference(const SourceDetails& details) : m_details(details) - { - m_details.Identifier = "*PredefinedInstalledSource"; - - if (PredefinedInstalledSourceFactory::StringToFilter(m_details.Arg) == PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate) - { - GetCachedInstalledIndex()->ForceNextUpdate(); - } - } - - std::string GetIdentifier() override { return m_details.Identifier; } - - SourceDetails& GetDetails() override { return m_details; }; - - std::shared_ptr Open(IProgressCallback& progress) override - { - // TODO: Maybe we do need to use it? - UNREFERENCED_PARAMETER(progress); - - // Determine the filter - PredefinedInstalledSourceFactory::Filter filter = PredefinedInstalledSourceFactory::StringToFilter(m_details.Arg); - - // Only cache for the unfiltered install data - if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate) - { - std::shared_ptr cachedIndex = GetCachedInstalledIndex(); - cachedIndex->UpdateIndexIfNeeded(); - return std::make_shared(m_details, cachedIndex->GetCopy(), true); - } - else - { - return std::make_shared(m_details, CreateAndPopulateIndex(filter), true); - } - } - - private: - std::shared_ptr GetCachedInstalledIndex() - { - static CachedInstalledIndex::Singleton s_installedIndex; - return s_installedIndex.Get(); - } - - SourceDetails m_details; - }; - - // The factory for the predefined installed source. - struct Factory : public ISourceFactory - { - std::string_view TypeName() const override final - { - return PredefinedInstalledSourceFactory::Type(); - } - - std::shared_ptr Create(const SourceDetails& details) override final - { - THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedInstalledSourceFactory::Type()); - - return std::make_shared(details); - } - - bool Add(SourceDetails&, IProgressCallback&) override final - { - // Add should never be needed, as this is predefined. - THROW_HR(E_NOTIMPL); - } - - bool Update(const SourceDetails&, IProgressCallback&) override final - { - // Update could be used later, but not for now. - THROW_HR(E_NOTIMPL); - } - - bool Remove(const SourceDetails&, IProgressCallback&) override final - { - // Similar to add, remove should never be needed. - THROW_HR(E_NOTIMPL); - } - }; - } - - std::string_view PredefinedInstalledSourceFactory::FilterToString(Filter filter) - { - switch (filter) - { - case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::None: - return "None"sv; - case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::ARP: - return "ARP"sv; - case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX: - return "MSIX"sv; - case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::User: - return "User"sv; - case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::Machine: - return "Machine"sv; - case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate: - return "NoneWithForcedCacheUpdate"sv; - default: - return "Unknown"sv; - } - } - - PredefinedInstalledSourceFactory::Filter PredefinedInstalledSourceFactory::StringToFilter(std::string_view filter) - { - if (filter == FilterToString(Filter::ARP)) - { - return Filter::ARP; - } - else if (filter == FilterToString(Filter::MSIX)) - { - return Filter::MSIX; - } - else if (filter == FilterToString(Filter::User)) - { - return Filter::User; - } - else if (filter == FilterToString(Filter::Machine)) - { - return Filter::Machine; - } - else if (filter == FilterToString(Filter::NoneWithForcedCacheUpdate)) - { - return Filter::NoneWithForcedCacheUpdate; - } - else - { - return Filter::None; - } - } - - std::unique_ptr PredefinedInstalledSourceFactory::Create() - { - return std::make_unique(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/ARPHelper.h" +#include "Microsoft/FontHelper.h" +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include "Microsoft/SQLiteIndex.h" +#include "Microsoft/SQLiteIndexSource.h" +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Microsoft +{ + namespace + { + std::optional GetCachedMSIXName(const Utility::NormalizedString& id, const Utility::Version& version, SQLiteIndex& cacheData) + { + SearchRequest searchRequest; + searchRequest.Inclusions.emplace_back(PackageMatchField::Id, MatchType::Exact, id); + + SQLiteIndex::SearchResult searchResult = cacheData.Search(searchRequest); + + if (searchResult.Matches.empty()) + { + return std::nullopt; + } + + if (searchResult.Matches.size() != 1) + { + // This is very unexpected, but just log it and carry on + AICLI_LOG(Repo, Warning, << "Found multiple (" << searchResult.Matches.size() << ") cache entries for: " << id); + return std::nullopt; + } + + auto versionKeys = cacheData.GetVersionKeysById(searchResult.Matches[0].first); + const SQLiteIndex::VersionKey* versionKey = nullptr; + + for (const auto& key : versionKeys) + { + if (key.VersionAndChannel.GetVersion() == version) + { + versionKey = &key; + break; + } + } + + if (!versionKey) + { + return std::nullopt; + } + + return cacheData.GetPropertyByPrimaryId(versionKey->ManifestId, PackageVersionProperty::Name); + } + + // Populates the index with the entries from MSIX. + void PopulateIndexFromMSIX(SQLiteIndex& index, Manifest::ScopeEnum scope, SQLiteIndex* cacheData = nullptr) + { + using namespace winrt::Windows::ApplicationModel; + using namespace winrt::Windows::Management::Deployment; + using namespace winrt::Windows::Foundation::Collections; + + AICLI_LOG(Repo, Verbose, << "Examining MSIX entries for " << ScopeToString(scope)); + + IIterable packages; + PackageManager packageManager; + + if (scope == Manifest::ScopeEnum::Machine) + { + // May not be present on our oldest supported systems; simply ignore for the time being. + IPackageManager9 packageManager9 = packageManager.try_as(); + if (packageManager9) + { + try + { + packages = packageManager.FindProvisionedPackages(); + } + catch (const winrt::hresult_error& hre) + { + // Historically this API has not been accessible unelevated; if it fails, try to carry on + AICLI_LOG(Repo, Warning, << "FindProvisionedPackages failed, bypassing provisioned packages: 0x" << Logging::SetHRFormat << hre.code()); + } + } + else + { + AICLI_LOG(Repo, Warning, << "FindProvisionedPackages is not available on this version of Windows"); + } + } + else + { + // TODO: Consider if Optional packages should also be enumerated + for (PackageTypes types : { PackageTypes::Main | PackageTypes::Framework, PackageTypes::Main, PackageTypes::Framework }) + { + try + { + packages = packageManager.FindPackagesForUserWithPackageTypes({}, types); + break; + } + catch (const winrt::hresult_error& hre) + { + if (hre.code() == E_NOT_SET) + { + // This OS issue occurs frequently enough that we will attempt to work around it by enumerating progressively fewer packages + AICLI_LOG(Repo, Warning, << "FindPackagesForUserWithPackageTypes returned E_NOT_SET for types: " << ToIntegral(types)); + } + else + { + throw; + } + } + } + } + + // Failed to retrieve even an empty package list; make sure that these cases have a log to indicate why. + if (!packages) + { + AICLI_LOG(Repo, Warning, << "MSIX package list not populated"); + return; + } + + // Reuse the same manifest object, as we will be setting the same values every time. + Manifest::Manifest manifest; + // Add one installer for storing the package family name. + manifest.Installers.emplace_back(); + // Every package will have the same tags currently. + manifest.DefaultLocalization.Add({ "msix" }); + + // Fields in the index but not populated: + // AppMoniker - Not sure what we would put. + // Channel - We don't know this information here. + // Commands - We could open the manifest and look for these eventually. + // Tags - Not sure what else we could put in here. + for (const auto& package : packages) + { + // System packages are part of the OS, and cannot be managed by the user. + // Filter them out as there is no point in showing them in a package manager. + auto signatureKind = package.SignatureKind(); + if (signatureKind == PackageSignatureKind::System) + { + continue; + } + + auto packageId = package.Id(); + Utility::NormalizedString fullName = Utility::ConvertToUTF8(packageId.FullName()); + Utility::NormalizedString familyName = Utility::ConvertToUTF8(packageId.FamilyName()); + + manifest.Id = "MSIX\\" + fullName; + + // Get version + std::ostringstream strstr; + auto packageVersion = packageId.Version(); + strstr << packageVersion.Major << '.' << packageVersion.Minor << '.' << packageVersion.Build << '.' << packageVersion.Revision; + + manifest.Version = strstr.str(); + + // Determine package name + bool isPackageNameSet = false; + + // Look for the name in the cache data first + if (cacheData) + { + std::optional cachedName = GetCachedMSIXName(manifest.Id, manifest.Version, *cacheData); + + if (cachedName) + { + manifest.DefaultLocalization.Add(cachedName.value()); + isPackageNameSet = true; + } + } + + // Attempt to get the DisplayName. Since this will retrieve the localized value, it has a chance to fail. + // Rather than completely skip this package in that case, we will simply fall back to using the package name below. + if (!isPackageNameSet && !Runtime::IsRunningAsSystem()) + { + try + { + auto displayName = Utility::ConvertToUTF8(package.DisplayName()); + if (!displayName.empty()) + { + manifest.DefaultLocalization.Add(displayName); + isPackageNameSet = true; + } + } + catch (const winrt::hresult_error& hre) + { + AICLI_LOG(Repo, Warning, << "winrt::hresult_error[0x" << Logging::SetHRFormat << hre.code() << ": " << + Utility::ConvertToUTF8(hre.message()) << "] exception thrown when getting DisplayName for " << fullName); + } + catch (...) + { + AICLI_LOG(Repo, Warning, << "Unknown exception thrown when getting DisplayName for " << fullName); + } + } + + if (!isPackageNameSet) + { + manifest.DefaultLocalization.Add(Utility::ConvertToUTF8(packageId.Name())); + } + + manifest.Installers[0].PackageFamilyName = familyName; + + // Use the full name as a unique key for the path + auto manifestId = index.AddManifest(manifest); + + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledType, + Manifest::InstallerTypeToString(Manifest::InstallerTypeEnum::Msix)); + + auto architecture = Utility::ConvertToArchitectureEnum(packageId.Architecture()); + if (architecture) + { + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, + ToString(architecture.value())); + } + + // May not be present on our oldest supported systems; simply ignore for the time being. + IPackage8 package8 = package.try_as(); + if (package8) + { + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledLocation, + Utility::ConvertToUTF8(package8.InstalledPath())); + } + else + { + AICLI_LOG(Repo, Warning, << "Windows::ApplicationModel::Package::InstalledPath is not available on this version of Windows"); + } + } + } + + SQLiteIndex CreateAndPopulateIndex(PredefinedInstalledSourceFactory::Filter filter) + { + AICLI_LOG(Repo, Verbose, << "Creating PredefinedInstalledSource with filter [" << PredefinedInstalledSourceFactory::FilterToString(filter) << ']'); + + // Create an in memory index + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless); + + // Put installed packages into the index + if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP || + filter == PredefinedInstalledSourceFactory::Filter::User || filter == PredefinedInstalledSourceFactory::Filter::Machine) + { + ARPHelper arpHelper; + if (filter != PredefinedInstalledSourceFactory::Filter::User) + { + arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::Machine); + } + if (filter != PredefinedInstalledSourceFactory::Filter::Machine) + { + arpHelper.PopulateIndexFromARP(index, Manifest::ScopeEnum::User); + } + } + + if (filter == PredefinedInstalledSourceFactory::Filter::None || + filter == PredefinedInstalledSourceFactory::Filter::MSIX || + filter == PredefinedInstalledSourceFactory::Filter::User) + { + PopulateIndexFromMSIX(index, Manifest::ScopeEnum::User); + } + else if (filter == PredefinedInstalledSourceFactory::Filter::Machine) + { + PopulateIndexFromMSIX(index, Manifest::ScopeEnum::Machine); + } + + // Put Installed Fonts into the index. + if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::ARP || + filter == PredefinedInstalledSourceFactory::Filter::User || filter == PredefinedInstalledSourceFactory::Filter::Machine) + { + FontHelper fontHelper; + if (filter != PredefinedInstalledSourceFactory::Filter::User) + { + fontHelper.PopulateIndex(index, Manifest::ScopeEnum::Machine); + } + + if (filter != PredefinedInstalledSourceFactory::Filter::Machine) + { + fontHelper.PopulateIndex(index, Manifest::ScopeEnum::User); + } + } + + AICLI_LOG(Repo, Verbose, << " ... finished creating PredefinedInstalledSource"); + + return index; + } + + struct CachedInstalledIndex + { + struct Singleton : public WinRT::COMStaticStorageBase + { + Singleton() : COMStaticStorageBase(L"WindowsPackageManager.CachedInstalledIndex") {} + }; + + CachedInstalledIndex() + { + ARPHelper arpHelper; + m_registryWatchers = arpHelper.CreateRegistryWatchers(Manifest::ScopeEnum::Unknown, + [this](Manifest::ScopeEnum, Utility::Architecture, wil::RegistryChangeKind) { ForceNextUpdate(); }); + + FontHelper fontHelper; + fontHelper.AddRegistryWatchers(Manifest::ScopeEnum::Unknown, + [this](Manifest::ScopeEnum, wil::RegistryChangeKind) { ForceNextUpdate(); }, m_registryWatchers); + + m_catalog = winrt::Windows::ApplicationModel::PackageCatalog::OpenForCurrentUser(); + m_eventRevoker = m_catalog.PackageStatusChanged(winrt::auto_revoke, [this](auto...) { ForceNextUpdate(); }); + } + + void UpdateIndexIfNeeded() + { + auto sharedLock = m_lock.lock_shared(); + if (CheckForUpdate()) + { + // Upgrade to exclusive + sharedLock.reset(); + auto exclusiveLock = m_lock.lock_exclusive(); + + if (CheckForUpdate()) + { + // TODO: To support servicing, the initial implementation of update will simply leverage + // some data from the existing index to speed up the MSIX populate function. + // In a larger update, we may want to make it possible to actually update the cache directly. + // We may even persist the cache to disk to speed things up further. + + // Set the update indicator to false before we start reading so that an external change can + // reindicate a need to update in the middle. But in the event that we error here, set it back to true + // to prevent an error from blocking further attempts. + m_forceNextUpdate = false; + auto scopeExit = wil::scope_exit([&]() { m_forceNextUpdate = true; }); + + // Populate from ARP using standard mechanism. + SQLiteIndex update = CreateAndPopulateIndex(PredefinedInstalledSourceFactory::Filter::ARP); + + // Populate from MSIX, using localization data from the existing index if applicable. + PopulateIndexFromMSIX(update, Manifest::ScopeEnum::User, m_index.get()); + + m_index = std::make_unique(std::move(update)); + scopeExit.release(); + } + } + } + + SQLiteIndex GetCopy() + { + auto lock = m_lock.lock_shared(); + THROW_HR_IF(E_POINTER, !m_index); + return SQLiteIndex::CopyFrom(SQLITE_MEMORY_DB_CONNECTION_TARGET, *m_index); + } + + void ForceNextUpdate() + { + m_forceNextUpdate = true; + } + + private: + bool CheckForUpdate() + { + return (!m_index || m_forceNextUpdate.load()); + } + + wil::srwlock m_lock; + std::atomic_bool m_forceNextUpdate{ false }; + std::unique_ptr m_index; + std::vector m_registryWatchers; + winrt::Windows::ApplicationModel::PackageCatalog m_catalog = nullptr; + decltype(winrt::Windows::ApplicationModel::PackageCatalog{ nullptr }.PackageStatusChanged(winrt::auto_revoke, nullptr)) m_eventRevoker; + }; + + struct PredefinedInstalledSourceReference : public ISourceReference + { + PredefinedInstalledSourceReference(const SourceDetails& details) : m_details(details) + { + m_details.Identifier = "*PredefinedInstalledSource"; + + if (PredefinedInstalledSourceFactory::StringToFilter(m_details.Arg) == PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate) + { + GetCachedInstalledIndex()->ForceNextUpdate(); + } + } + + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + std::shared_ptr Open(IProgressCallback& progress) override + { + // TODO: Maybe we do need to use it? + UNREFERENCED_PARAMETER(progress); + + // Determine the filter + PredefinedInstalledSourceFactory::Filter filter = PredefinedInstalledSourceFactory::StringToFilter(m_details.Arg); + + // Only cache for the unfiltered install data + if (filter == PredefinedInstalledSourceFactory::Filter::None || filter == PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate) + { + std::shared_ptr cachedIndex = GetCachedInstalledIndex(); + cachedIndex->UpdateIndexIfNeeded(); + return std::make_shared(m_details, cachedIndex->GetCopy(), true); + } + else + { + return std::make_shared(m_details, CreateAndPopulateIndex(filter), true); + } + } + + private: + std::shared_ptr GetCachedInstalledIndex() + { + static CachedInstalledIndex::Singleton s_installedIndex; + return s_installedIndex.Get(); + } + + SourceDetails m_details; + }; + + // The factory for the predefined installed source. + struct Factory : public ISourceFactory + { + std::string_view TypeName() const override final + { + return PredefinedInstalledSourceFactory::Type(); + } + + std::shared_ptr Create(const SourceDetails& details) override final + { + THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedInstalledSourceFactory::Type()); + + return std::make_shared(details); + } + + bool Add(SourceDetails&, IProgressCallback&) override final + { + // Add should never be needed, as this is predefined. + THROW_HR(E_NOTIMPL); + } + + bool Update(const SourceDetails&, IProgressCallback&) override final + { + // Update could be used later, but not for now. + THROW_HR(E_NOTIMPL); + } + + bool Remove(const SourceDetails&, IProgressCallback&) override final + { + // Similar to add, remove should never be needed. + THROW_HR(E_NOTIMPL); + } + }; + } + + std::string_view PredefinedInstalledSourceFactory::FilterToString(Filter filter) + { + switch (filter) + { + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::None: + return "None"sv; + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::ARP: + return "ARP"sv; + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX: + return "MSIX"sv; + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::User: + return "User"sv; + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::Machine: + return "Machine"sv; + case AppInstaller::Repository::Microsoft::PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate: + return "NoneWithForcedCacheUpdate"sv; + default: + return "Unknown"sv; + } + } + + PredefinedInstalledSourceFactory::Filter PredefinedInstalledSourceFactory::StringToFilter(std::string_view filter) + { + if (filter == FilterToString(Filter::ARP)) + { + return Filter::ARP; + } + else if (filter == FilterToString(Filter::MSIX)) + { + return Filter::MSIX; + } + else if (filter == FilterToString(Filter::User)) + { + return Filter::User; + } + else if (filter == FilterToString(Filter::Machine)) + { + return Filter::Machine; + } + else if (filter == FilterToString(Filter::NoneWithForcedCacheUpdate)) + { + return Filter::NoneWithForcedCacheUpdate; + } + else + { + return Filter::None; + } + } + + std::unique_ptr PredefinedInstalledSourceFactory::Create() + { + return std::make_unique(); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h index cb506bb94c..ac64815e8f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedInstalledSourceFactory.h @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include "SourceFactory.h" - -#include - -namespace AppInstaller::Repository::Microsoft -{ - using namespace std::string_view_literals; - - // A source of installed packages on the local system. - // Arg :: A value indicating how the list is to be filtered. - // Data :: Not used. - struct PredefinedInstalledSourceFactory - { - // Get the type string for this source. - static constexpr std::string_view Type() - { - return "Microsoft.Predefined.Installed"sv; - } - - // The filtering level for the source. - enum class Filter - { - // Contains user ARP, machine ARP, user MSIX, user Fonts and machine Fonts - None, - // Contains user ARP and machine ARP - ARP, - // Contains user MSIX - MSIX, - // Contains user ARP and user MSIX and user Fonts - User, - // Contains machine ARP and machine MSIX and machine Fonts - Machine, - // Same as None but creating the source reference causes the next Open to always update the cache - NoneWithForcedCacheUpdate, - }; - - // Converts a filter to its string. - static std::string_view FilterToString(Filter filter); - - // Converts a string to its filter value. - static Filter StringToFilter(std::string_view filter); - - // Creates a source factory for this type. - static std::unique_ptr Create(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include "SourceFactory.h" + +#include + +namespace AppInstaller::Repository::Microsoft +{ + using namespace std::string_view_literals; + + // A source of installed packages on the local system. + // Arg :: A value indicating how the list is to be filtered. + // Data :: Not used. + struct PredefinedInstalledSourceFactory + { + // Get the type string for this source. + static constexpr std::string_view Type() + { + return "Microsoft.Predefined.Installed"sv; + } + + // The filtering level for the source. + enum class Filter + { + // Contains user ARP, machine ARP, user MSIX, user Fonts and machine Fonts + None, + // Contains user ARP and machine ARP + ARP, + // Contains user MSIX + MSIX, + // Contains user ARP and user MSIX and user Fonts + User, + // Contains machine ARP and machine MSIX and machine Fonts + Machine, + // Same as None but creating the source reference causes the next Open to always update the cache + NoneWithForcedCacheUpdate, + }; + + // Converts a filter to its string. + static std::string_view FilterToString(Filter filter); + + // Converts a string to its filter value. + static Filter StringToFilter(std::string_view filter); + + // Creates a source factory for this type. + static std::unique_ptr Create(); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp index 53accca45c..23c645a485 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.cpp @@ -1,109 +1,109 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/ARPHelper.h" -#include "Microsoft/PredefinedWriteableSourceFactory.h" -#include "Microsoft/SQLiteIndex.h" -#include "Microsoft/SQLiteIndexSource.h" -#include - -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Microsoft -{ - namespace - { - // The factory for the predefined installing source. - struct PredefinedWriteableSourceFactoryImpl : public ISourceFactory - { - std::string_view TypeName() const override final - { - return PredefinedWriteableSourceFactory::Type(); - } - - std::shared_ptr Create(const SourceDetails& details) override final; - - bool Add(SourceDetails&, IProgressCallback&) override final - { - // Add should never be needed, as this is predefined. - THROW_HR(E_NOTIMPL); - } - - bool Update(const SourceDetails&, IProgressCallback&) override final - { - // Update could be used later, but not for now. - THROW_HR(E_NOTIMPL); - } - - bool Remove(const SourceDetails&, IProgressCallback&) override final - { - // Similar to add, remove should never be needed. - THROW_HR(E_NOTIMPL); - } - }; - - struct PredefinedWriteableSourceReference : public ISourceReference - { - PredefinedWriteableSourceReference(const SourceDetails& details) : m_details(details) - { - m_details.Identifier = "*PredefinedWriteableSource"; - } - - std::string GetIdentifier() override { return m_details.Identifier; } - - SourceDetails& GetDetails() override { return m_details; }; - - std::shared_ptr Open(IProgressCallback&) override - { - // Installing is the only type right now so just return the Installing source to all callers. - // Since the source is writeable, it must be shared by all callers that try to open it - // since queries on one instance would not see what was written on another instance. - std::call_once(g_InstallingSourceOnceFlag, - [&]() - { - // Create an in memory index without paths or dependencies - SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport); - - g_sharedSource = std::make_shared(m_details, std::move(index), true); - }); - - return g_sharedSource; - } - - private: - SourceDetails m_details; - static std::shared_ptr g_sharedSource; - static std::once_flag g_InstallingSourceOnceFlag; - }; - - std::shared_ptr PredefinedWriteableSourceReference::g_sharedSource = nullptr; - std::once_flag PredefinedWriteableSourceReference::g_InstallingSourceOnceFlag; - - std::shared_ptr PredefinedWriteableSourceFactoryImpl::Create(const SourceDetails& details) - { - THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedWriteableSourceFactory::Type()); - - return std::make_shared(details); - } - } - - std::string_view PredefinedWriteableSourceFactory::TypeToString(WriteableType type) - { - switch (type) - { - case AppInstaller::Repository::Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing: - return "Installing"sv; - default: - return "Unknown"sv; - } - } - - std::unique_ptr PredefinedWriteableSourceFactory::Create() - { - return std::make_unique(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/ARPHelper.h" +#include "Microsoft/PredefinedWriteableSourceFactory.h" +#include "Microsoft/SQLiteIndex.h" +#include "Microsoft/SQLiteIndexSource.h" +#include + +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Microsoft +{ + namespace + { + // The factory for the predefined installing source. + struct PredefinedWriteableSourceFactoryImpl : public ISourceFactory + { + std::string_view TypeName() const override final + { + return PredefinedWriteableSourceFactory::Type(); + } + + std::shared_ptr Create(const SourceDetails& details) override final; + + bool Add(SourceDetails&, IProgressCallback&) override final + { + // Add should never be needed, as this is predefined. + THROW_HR(E_NOTIMPL); + } + + bool Update(const SourceDetails&, IProgressCallback&) override final + { + // Update could be used later, but not for now. + THROW_HR(E_NOTIMPL); + } + + bool Remove(const SourceDetails&, IProgressCallback&) override final + { + // Similar to add, remove should never be needed. + THROW_HR(E_NOTIMPL); + } + }; + + struct PredefinedWriteableSourceReference : public ISourceReference + { + PredefinedWriteableSourceReference(const SourceDetails& details) : m_details(details) + { + m_details.Identifier = "*PredefinedWriteableSource"; + } + + std::string GetIdentifier() override { return m_details.Identifier; } + + SourceDetails& GetDetails() override { return m_details; }; + + std::shared_ptr Open(IProgressCallback&) override + { + // Installing is the only type right now so just return the Installing source to all callers. + // Since the source is writeable, it must be shared by all callers that try to open it + // since queries on one instance would not see what was written on another instance. + std::call_once(g_InstallingSourceOnceFlag, + [&]() + { + // Create an in memory index without paths or dependencies + SQLiteIndex index = SQLiteIndex::CreateNew(SQLITE_MEMORY_DB_CONNECTION_TARGET, SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport); + + g_sharedSource = std::make_shared(m_details, std::move(index), true); + }); + + return g_sharedSource; + } + + private: + SourceDetails m_details; + static std::shared_ptr g_sharedSource; + static std::once_flag g_InstallingSourceOnceFlag; + }; + + std::shared_ptr PredefinedWriteableSourceReference::g_sharedSource = nullptr; + std::once_flag PredefinedWriteableSourceReference::g_InstallingSourceOnceFlag; + + std::shared_ptr PredefinedWriteableSourceFactoryImpl::Create(const SourceDetails& details) + { + THROW_HR_IF(E_INVALIDARG, details.Type != PredefinedWriteableSourceFactory::Type()); + + return std::make_shared(details); + } + } + + std::string_view PredefinedWriteableSourceFactory::TypeToString(WriteableType type) + { + switch (type) + { + case AppInstaller::Repository::Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing: + return "Installing"sv; + default: + return "Unknown"sv; + } + } + + std::unique_ptr PredefinedWriteableSourceFactory::Create() + { + return std::make_unique(); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h index d1a75d15d7..c510c3d4e3 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h +++ b/src/AppInstallerRepositoryCore/Microsoft/PredefinedWriteableSourceFactory.h @@ -1,36 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include "SourceFactory.h" - -#include - -namespace AppInstaller::Repository::Microsoft -{ - using namespace std::string_view_literals; - - // A source of installing packages on the local system. - // Arg :: A value indicating the type of writeable source - // Data :: Not used. - struct PredefinedWriteableSourceFactory - { - // Get the type string for this source. - static constexpr std::string_view Type() - { - return "Microsoft.Predefined.Writeable"sv; - } - - // The type for the source. - enum class WriteableType - { - Installing - }; - - // Converts a type to its string. - static std::string_view TypeToString(WriteableType type); - - // Creates a source factory for this type. - static std::unique_ptr Create(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include "SourceFactory.h" + +#include + +namespace AppInstaller::Repository::Microsoft +{ + using namespace std::string_view_literals; + + // A source of installing packages on the local system. + // Arg :: A value indicating the type of writeable source + // Data :: Not used. + struct PredefinedWriteableSourceFactory + { + // Get the type string for this source. + static constexpr std::string_view Type() + { + return "Microsoft.Predefined.Writeable"sv; + } + + // The type for the source. + enum class WriteableType + { + Installing + }; + + // Converts a type to its string. + static std::string_view TypeToString(WriteableType type); + + // Creates a source factory for this type. + static std::unique_ptr Create(); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/README.md b/src/AppInstallerRepositoryCore/Microsoft/README.md index 74220fed64..e83635b60b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/README.md +++ b/src/AppInstallerRepositoryCore/Microsoft/README.md @@ -1,9 +1,9 @@ -### Design -The object stack is as such: -- SQLiteIndex :: Houses the database connection and the proper interface with which to interact with it -- ISQLiteIndex :: Interface that creates a uniform model to use against all schemas -- Schema::V*::Interface :: Actual implementation of ISQLiteIndex for specific schema version - -The code that needs to interact with an index will create a SQLiteIndex object. That in turn will open the database, determine the schema version, then create the appropriate ISQLiteIndex providing object. All queries and changes to the index will go through the SQLiteIndex object. - -When a change to the schema is needed, a new schema directory should be created. This can pull code from the schemas before it, only updating the specific table(s) that are needed. Then Version code should be updated to create the new Interface as appropriate. Any new methods needed on ISQLiteIndex should be added, and SQLiteIndexBase to implement them in terms of the older functions. Only the new schema should need to implement the new functions. **Once shipped, one should never need to update code within an existing schema, save for bug fixes.** +### Design +The object stack is as such: +- SQLiteIndex :: Houses the database connection and the proper interface with which to interact with it +- ISQLiteIndex :: Interface that creates a uniform model to use against all schemas +- Schema::V*::Interface :: Actual implementation of ISQLiteIndex for specific schema version + +The code that needs to interact with an index will create a SQLiteIndex object. That in turn will open the database, determine the schema version, then create the appropriate ISQLiteIndex providing object. All queries and changes to the index will go through the SQLiteIndex object. + +When a change to the schema is needed, a new schema directory should be created. This can pull code from the schemas before it, only updating the specific table(s) that are needed. Then Version code should be updated to create the new Interface as appropriate. Any new methods needed on ISQLiteIndex should be added, and SQLiteIndexBase to implement them in terms of the older functions. Only the new schema should need to implement the new functions. **Once shipped, one should never need to update code within an existing schema, save for bug fixes.** diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp index 509ec2b526..61247ebf5f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.cpp @@ -1,379 +1,379 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SQLiteIndex.h" -#include -#include "ArpVersionValidation.h" -#include - -namespace AppInstaller::Repository::Microsoft -{ - namespace - { - size_t GetPageSizeFromOptions(SQLiteIndex::CreateOptions options) - { - return WI_IsFlagSet(options, SQLiteIndex::CreateOptions::LargePageSize) ? 65536 : 0; - } - } - - SQLiteIndex SQLiteIndex::CreateNew(const std::string& filePath, SQLite::Version version, CreateOptions options) - { - AICLI_LOG(Repo, Info, << "Creating new SQLite Index with version [" << version << "] at '" << filePath << "'"); - SQLiteIndex result{ filePath, version, options }; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "sqliteindex_createnew"); - - // Use calculated version, as incoming version could be 'latest' - result.m_version.SetSchemaVersion(result.m_dbconn); - - result.m_interface->CreateTables(result.m_dbconn, options); - - result.SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - SQLiteIndex SQLiteIndex::Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile) - { - return { filePath, disposition, std::move(indexFile) }; - } - - SQLiteIndex SQLiteIndex::CopyFrom(const std::string& filePath, SQLiteIndex& source) - { - return { filePath, source }; - } - - SQLiteIndex::SQLiteIndex(const std::string& target, const SQLite::Version& version, CreateOptions options) : SQLiteStorageBase(target, version, GetPageSizeFromOptions(options)) - { - m_dbconn.EnableICU(); - m_interface = Schema::CreateISQLiteIndex(version); - m_version = m_interface->GetVersion(); - SetDatabaseFilePath(target); - } - - SQLiteIndex::SQLiteIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : - SQLiteStorageBase(target, disposition, std::move(indexFile)) - { - m_dbconn.EnableICU(); - AICLI_LOG(Repo, Info, << "Opened SQLite Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); - m_interface = Schema::CreateISQLiteIndex(m_version); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); - SetDatabaseFilePath(target); - } - - SQLiteIndex::SQLiteIndex(const std::string& target, SQLiteIndex& source) : - SQLiteStorageBase(target, source) - { - m_dbconn.EnableICU(); - m_interface = Schema::CreateISQLiteIndex(m_version); - SetDatabaseFilePath(target); - } - - void SQLiteIndex::SetDatabaseFilePath(const std::string& target) - { - if (target != SQLITE_MEMORY_DB_CONNECTION_TARGET) - { - m_contextData.Add(Utility::ConvertToUTF16(target)); - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - void SQLiteIndex::ForceVersion(const SQLite::Version& version) - { - m_interface = Schema::CreateISQLiteIndex(version); - } - - SQLite::Version SQLiteIndex::GetLatestVersion() - { - return Schema::CreateISQLiteIndex(SQLite::Version::Latest())->GetVersion(); - } - - const Schema::SQLiteIndexContextData& SQLiteIndex::GetContextData() const - { - return m_contextData; - } -#endif - - SQLiteIndex::IdType SQLiteIndex::AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) - { - AICLI_LOG(Repo, Verbose, << "Adding manifest from file [" << manifestPath << "]"); - - Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); - return AddManifestInternal(manifest, relativePath); - } - - SQLiteIndex::IdType SQLiteIndex::AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - return AddManifestInternal(manifest, relativePath); - } - - SQLiteIndex::IdType SQLiteIndex::AddManifest(const Manifest::Manifest& manifest) - { - return AddManifestInternal(manifest, {}); - } - - SQLiteIndex::IdType SQLiteIndex::AddManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return AddManifestInternalHoldingLock(manifest, relativePath); - } - - SQLiteIndex::IdType SQLiteIndex::AddManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath) - { - AICLI_LOG(Repo, Verbose, << "Adding manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_addmanifest"); - - IdType result = m_interface->AddManifest(m_dbconn, manifest, relativePath); - - SetLastWriteTime(); - - savepoint.Commit(); - - return result; - } - - bool SQLiteIndex::UpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) - { - AICLI_LOG(Repo, Verbose, << "Updating manifest from file [" << manifestPath << "]"); - - Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); - return UpdateManifestInternal(manifest, relativePath); - } - - bool SQLiteIndex::UpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - return UpdateManifestInternal(manifest, relativePath); - } - - bool SQLiteIndex::UpdateManifest(const Manifest::Manifest& manifest) - { - return UpdateManifestInternal(manifest, {}); - } - - bool SQLiteIndex::UpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return UpdateManifestInternalHoldingLock(manifest, relativePath); - } - - bool SQLiteIndex::UpdateManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath) - { - AICLI_LOG(Repo, Verbose, << "Updating manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_updatemanifest"); - - bool result = m_interface->UpdateManifest(m_dbconn, manifest, relativePath).first; - - if (result) - { - SetLastWriteTime(); - - savepoint.Commit(); - } - - return result; - } - - bool SQLiteIndex::AddOrUpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) - { - AICLI_LOG(Repo, Verbose, << "Adding or Updating manifest from file [" << manifestPath << "]"); - - Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); - return AddOrUpdateManifestInternal(manifest, relativePath); - } - - bool SQLiteIndex::AddOrUpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - return AddOrUpdateManifestInternal(manifest, relativePath); - } - - bool SQLiteIndex::AddOrUpdateManifest(const Manifest::Manifest& manifest) - { - return AddOrUpdateManifestInternal(manifest, {}); - } - - bool SQLiteIndex::AddOrUpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Adding or Updating manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); - - if (m_interface->GetManifestIdByManifest(m_dbconn, manifest)) - { - UpdateManifestInternalHoldingLock(manifest, relativePath); - return false; - } - else - { - AddManifestInternalHoldingLock(manifest, relativePath); - return true; - } - } - - void SQLiteIndex::RemoveManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) - { - AICLI_LOG(Repo, Verbose, << "Removing manifest from file [" << manifestPath << "]"); - - Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); - RemoveManifest(manifest, relativePath); - } - - void SQLiteIndex::RemoveManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - AICLI_LOG(Repo, Verbose, << "Removing manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath << "]"); - RemoveManifest(manifest); - } - - void SQLiteIndex::RemoveManifest(const Manifest::Manifest& manifest) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_removemanifest"); - - m_interface->RemoveManifest(m_dbconn, manifest); - - SetLastWriteTime(); - - savepoint.Commit(); - } - - void SQLiteIndex::RemoveManifestById(IdType manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "SQLiteIndex_RemoveManifestById"); - - m_interface->RemoveManifestById(m_dbconn, manifestId); - - SetLastWriteTime(); - - savepoint.Commit(); - } - - void SQLiteIndex::PrepareForPackaging() - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Info, << "Preparing index for packaging"); - - m_interface->PrepareForPackaging(Schema::SQLiteIndexContext{ m_dbconn, m_contextData }); - } - - bool SQLiteIndex::CheckConsistency(bool log) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Info, << "Checking index consistency..."); - - bool result = m_interface->CheckConsistency(m_dbconn, log); - - AICLI_LOG(Repo, Info, << "...index *WAS" << (result ? "*" : " NOT*") << " consistent."); - - return result; - } - - Schema::ISQLiteIndex::SearchResult SQLiteIndex::Search(const SearchRequest& request) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - AICLI_LOG(Repo, Verbose, << "Performing search: " << request.ToString()); - - return m_interface->Search(m_dbconn, request); - } - - std::optional SQLiteIndex::GetPropertyByPrimaryId(IdType primaryId, PackageVersionProperty property) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetPropertyByPrimaryId(m_dbconn, primaryId, property); - } - - std::vector SQLiteIndex::GetMultiPropertyByPrimaryId(IdType primaryId, PackageVersionMultiProperty property) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetMultiPropertyByPrimaryId(m_dbconn, primaryId, property); - } - - std::optional SQLiteIndex::GetManifestIdByKey(IdType id, std::string_view version, std::string_view channel) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetManifestIdByKey(m_dbconn, id, version, channel); - } - - std::optional SQLiteIndex::GetManifestIdByManifest(const Manifest::Manifest& manifest) const - { - return m_interface->GetManifestIdByManifest(m_dbconn, manifest); - } - - std::vector SQLiteIndex::GetVersionKeysById(IdType id) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetVersionKeysById(m_dbconn, id); - } - - SQLiteIndex::MetadataResult SQLiteIndex::GetMetadataByManifestId(SQLite::rowid_t manifestId) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->GetMetadataByManifestId(m_dbconn, manifestId); - } - - void SQLiteIndex::SetMetadataByManifestId(IdType manifestId, PackageVersionMetadata metadata, std::string_view value) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - m_interface->SetMetadataByManifestId(m_dbconn, manifestId, metadata, value); - } - - Utility::NormalizedName SQLiteIndex::NormalizeName(std::string_view name, std::string_view publisher) const - { - std::lock_guard lockInterface{ *m_interfaceLock }; - return m_interface->NormalizeName(name, publisher); - } - - std::set> SQLiteIndex::GetDependenciesByManifestRowId(SQLite::rowid_t manifestRowId) const - { - return m_interface->GetDependenciesByManifestRowId(m_dbconn, manifestRowId); - } - - std::vector> SQLiteIndex::GetDependentsById(AppInstaller::Manifest::string_t packageId) const - { - return m_interface->GetDependentsById(m_dbconn, packageId); - } - - bool SQLiteIndex::MigrateTo(SQLite::Version version) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_migrate_to"); - - AICLI_LOG(Repo, Info, << "Attempting to migrate index from [" << m_interface->GetVersion() << "] to [" << version << "]..."); - std::unique_ptr newInterface = Schema::CreateISQLiteIndex(version); - - bool result = newInterface->MigrateFrom(m_dbconn, m_interface.get()); - - AICLI_LOG(Repo, Info, << "...migration was " << (result ? "" : "NOT ") << "successful"); - if (result) - { - version.SetSchemaVersion(m_dbconn); - SetLastWriteTime(); - savepoint.Commit(); - - m_version = version; - m_interface = std::move(newInterface); - } - - return result; - } - - void SQLiteIndex::SetProperty(Property property, const std::string& value) - { - std::lock_guard lockInterface{ *m_interfaceLock }; - - switch (property) - { - case Property::PackageUpdateTrackingBaseTime: - m_interface->SetProperty(m_dbconn, Schema::Property::PackageUpdateTrackingBaseTime, value); - break; - case Property::IntermediateFileOutputPath: - { - std::filesystem::path pathValue{ Utility::ConvertToUTF16(value) }; - THROW_HR_IF(E_INVALIDARG, pathValue.empty() || pathValue.is_relative()); - m_contextData.Add(std::move(pathValue)); - } - break; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SQLiteIndex.h" +#include +#include "ArpVersionValidation.h" +#include + +namespace AppInstaller::Repository::Microsoft +{ + namespace + { + size_t GetPageSizeFromOptions(SQLiteIndex::CreateOptions options) + { + return WI_IsFlagSet(options, SQLiteIndex::CreateOptions::LargePageSize) ? 65536 : 0; + } + } + + SQLiteIndex SQLiteIndex::CreateNew(const std::string& filePath, SQLite::Version version, CreateOptions options) + { + AICLI_LOG(Repo, Info, << "Creating new SQLite Index with version [" << version << "] at '" << filePath << "'"); + SQLiteIndex result{ filePath, version, options }; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(result.m_dbconn, "sqliteindex_createnew"); + + // Use calculated version, as incoming version could be 'latest' + result.m_version.SetSchemaVersion(result.m_dbconn); + + result.m_interface->CreateTables(result.m_dbconn, options); + + result.SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + SQLiteIndex SQLiteIndex::Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile) + { + return { filePath, disposition, std::move(indexFile) }; + } + + SQLiteIndex SQLiteIndex::CopyFrom(const std::string& filePath, SQLiteIndex& source) + { + return { filePath, source }; + } + + SQLiteIndex::SQLiteIndex(const std::string& target, const SQLite::Version& version, CreateOptions options) : SQLiteStorageBase(target, version, GetPageSizeFromOptions(options)) + { + m_dbconn.EnableICU(); + m_interface = Schema::CreateISQLiteIndex(version); + m_version = m_interface->GetVersion(); + SetDatabaseFilePath(target); + } + + SQLiteIndex::SQLiteIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile) : + SQLiteStorageBase(target, disposition, std::move(indexFile)) + { + m_dbconn.EnableICU(); + AICLI_LOG(Repo, Info, << "Opened SQLite Index with version [" << m_version << "], last write [" << GetLastWriteTime() << "]"); + m_interface = Schema::CreateISQLiteIndex(m_version); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, disposition == SQLiteStorageBase::OpenDisposition::ReadWrite && m_version != m_interface->GetVersion()); + SetDatabaseFilePath(target); + } + + SQLiteIndex::SQLiteIndex(const std::string& target, SQLiteIndex& source) : + SQLiteStorageBase(target, source) + { + m_dbconn.EnableICU(); + m_interface = Schema::CreateISQLiteIndex(m_version); + SetDatabaseFilePath(target); + } + + void SQLiteIndex::SetDatabaseFilePath(const std::string& target) + { + if (target != SQLITE_MEMORY_DB_CONNECTION_TARGET) + { + m_contextData.Add(Utility::ConvertToUTF16(target)); + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + void SQLiteIndex::ForceVersion(const SQLite::Version& version) + { + m_interface = Schema::CreateISQLiteIndex(version); + } + + SQLite::Version SQLiteIndex::GetLatestVersion() + { + return Schema::CreateISQLiteIndex(SQLite::Version::Latest())->GetVersion(); + } + + const Schema::SQLiteIndexContextData& SQLiteIndex::GetContextData() const + { + return m_contextData; + } +#endif + + SQLiteIndex::IdType SQLiteIndex::AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) + { + AICLI_LOG(Repo, Verbose, << "Adding manifest from file [" << manifestPath << "]"); + + Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); + return AddManifestInternal(manifest, relativePath); + } + + SQLiteIndex::IdType SQLiteIndex::AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + return AddManifestInternal(manifest, relativePath); + } + + SQLiteIndex::IdType SQLiteIndex::AddManifest(const Manifest::Manifest& manifest) + { + return AddManifestInternal(manifest, {}); + } + + SQLiteIndex::IdType SQLiteIndex::AddManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return AddManifestInternalHoldingLock(manifest, relativePath); + } + + SQLiteIndex::IdType SQLiteIndex::AddManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath) + { + AICLI_LOG(Repo, Verbose, << "Adding manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_addmanifest"); + + IdType result = m_interface->AddManifest(m_dbconn, manifest, relativePath); + + SetLastWriteTime(); + + savepoint.Commit(); + + return result; + } + + bool SQLiteIndex::UpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) + { + AICLI_LOG(Repo, Verbose, << "Updating manifest from file [" << manifestPath << "]"); + + Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); + return UpdateManifestInternal(manifest, relativePath); + } + + bool SQLiteIndex::UpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + return UpdateManifestInternal(manifest, relativePath); + } + + bool SQLiteIndex::UpdateManifest(const Manifest::Manifest& manifest) + { + return UpdateManifestInternal(manifest, {}); + } + + bool SQLiteIndex::UpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return UpdateManifestInternalHoldingLock(manifest, relativePath); + } + + bool SQLiteIndex::UpdateManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath) + { + AICLI_LOG(Repo, Verbose, << "Updating manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_updatemanifest"); + + bool result = m_interface->UpdateManifest(m_dbconn, manifest, relativePath).first; + + if (result) + { + SetLastWriteTime(); + + savepoint.Commit(); + } + + return result; + } + + bool SQLiteIndex::AddOrUpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) + { + AICLI_LOG(Repo, Verbose, << "Adding or Updating manifest from file [" << manifestPath << "]"); + + Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); + return AddOrUpdateManifestInternal(manifest, relativePath); + } + + bool SQLiteIndex::AddOrUpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + return AddOrUpdateManifestInternal(manifest, relativePath); + } + + bool SQLiteIndex::AddOrUpdateManifest(const Manifest::Manifest& manifest) + { + return AddOrUpdateManifestInternal(manifest, {}); + } + + bool SQLiteIndex::AddOrUpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Adding or Updating manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath.value_or("") << "]"); + + if (m_interface->GetManifestIdByManifest(m_dbconn, manifest)) + { + UpdateManifestInternalHoldingLock(manifest, relativePath); + return false; + } + else + { + AddManifestInternalHoldingLock(manifest, relativePath); + return true; + } + } + + void SQLiteIndex::RemoveManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath) + { + AICLI_LOG(Repo, Verbose, << "Removing manifest from file [" << manifestPath << "]"); + + Manifest::Manifest manifest = Manifest::YamlParser::CreateFromPath(manifestPath); + RemoveManifest(manifest, relativePath); + } + + void SQLiteIndex::RemoveManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + AICLI_LOG(Repo, Verbose, << "Removing manifest for [" << manifest.Id << ", " << manifest.Version << "] at relative path [" << relativePath << "]"); + RemoveManifest(manifest); + } + + void SQLiteIndex::RemoveManifest(const Manifest::Manifest& manifest) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_removemanifest"); + + m_interface->RemoveManifest(m_dbconn, manifest); + + SetLastWriteTime(); + + savepoint.Commit(); + } + + void SQLiteIndex::RemoveManifestById(IdType manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "SQLiteIndex_RemoveManifestById"); + + m_interface->RemoveManifestById(m_dbconn, manifestId); + + SetLastWriteTime(); + + savepoint.Commit(); + } + + void SQLiteIndex::PrepareForPackaging() + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Info, << "Preparing index for packaging"); + + m_interface->PrepareForPackaging(Schema::SQLiteIndexContext{ m_dbconn, m_contextData }); + } + + bool SQLiteIndex::CheckConsistency(bool log) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Info, << "Checking index consistency..."); + + bool result = m_interface->CheckConsistency(m_dbconn, log); + + AICLI_LOG(Repo, Info, << "...index *WAS" << (result ? "*" : " NOT*") << " consistent."); + + return result; + } + + Schema::ISQLiteIndex::SearchResult SQLiteIndex::Search(const SearchRequest& request) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + AICLI_LOG(Repo, Verbose, << "Performing search: " << request.ToString()); + + return m_interface->Search(m_dbconn, request); + } + + std::optional SQLiteIndex::GetPropertyByPrimaryId(IdType primaryId, PackageVersionProperty property) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetPropertyByPrimaryId(m_dbconn, primaryId, property); + } + + std::vector SQLiteIndex::GetMultiPropertyByPrimaryId(IdType primaryId, PackageVersionMultiProperty property) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetMultiPropertyByPrimaryId(m_dbconn, primaryId, property); + } + + std::optional SQLiteIndex::GetManifestIdByKey(IdType id, std::string_view version, std::string_view channel) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetManifestIdByKey(m_dbconn, id, version, channel); + } + + std::optional SQLiteIndex::GetManifestIdByManifest(const Manifest::Manifest& manifest) const + { + return m_interface->GetManifestIdByManifest(m_dbconn, manifest); + } + + std::vector SQLiteIndex::GetVersionKeysById(IdType id) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetVersionKeysById(m_dbconn, id); + } + + SQLiteIndex::MetadataResult SQLiteIndex::GetMetadataByManifestId(SQLite::rowid_t manifestId) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->GetMetadataByManifestId(m_dbconn, manifestId); + } + + void SQLiteIndex::SetMetadataByManifestId(IdType manifestId, PackageVersionMetadata metadata, std::string_view value) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + m_interface->SetMetadataByManifestId(m_dbconn, manifestId, metadata, value); + } + + Utility::NormalizedName SQLiteIndex::NormalizeName(std::string_view name, std::string_view publisher) const + { + std::lock_guard lockInterface{ *m_interfaceLock }; + return m_interface->NormalizeName(name, publisher); + } + + std::set> SQLiteIndex::GetDependenciesByManifestRowId(SQLite::rowid_t manifestRowId) const + { + return m_interface->GetDependenciesByManifestRowId(m_dbconn, manifestRowId); + } + + std::vector> SQLiteIndex::GetDependentsById(AppInstaller::Manifest::string_t packageId) const + { + return m_interface->GetDependentsById(m_dbconn, packageId); + } + + bool SQLiteIndex::MigrateTo(SQLite::Version version) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(m_dbconn, "sqliteindex_migrate_to"); + + AICLI_LOG(Repo, Info, << "Attempting to migrate index from [" << m_interface->GetVersion() << "] to [" << version << "]..."); + std::unique_ptr newInterface = Schema::CreateISQLiteIndex(version); + + bool result = newInterface->MigrateFrom(m_dbconn, m_interface.get()); + + AICLI_LOG(Repo, Info, << "...migration was " << (result ? "" : "NOT ") << "successful"); + if (result) + { + version.SetSchemaVersion(m_dbconn); + SetLastWriteTime(); + savepoint.Commit(); + + m_version = version; + m_interface = std::move(newInterface); + } + + return result; + } + + void SQLiteIndex::SetProperty(Property property, const std::string& value) + { + std::lock_guard lockInterface{ *m_interfaceLock }; + + switch (property) + { + case Property::PackageUpdateTrackingBaseTime: + m_interface->SetProperty(m_dbconn, Schema::Property::PackageUpdateTrackingBaseTime, value); + break; + case Property::IntermediateFileOutputPath: + { + std::filesystem::path pathValue{ Utility::ConvertToUTF16(value) }; + THROW_HR_IF(E_INVALIDARG, pathValue.empty() || pathValue.is_relative()); + m_contextData.Add(std::move(pathValue)); + } + break; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h index 6f132dc46d..be63be9632 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndex.h @@ -1,199 +1,199 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Microsoft/Schema/ISQLiteIndex.h" -#include -#include -#include "ISource.h" -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Repository::Microsoft -{ - // Holds the connection to the database, as well as the appropriate functionality to interface with it. - struct SQLiteIndex : SQLite::SQLiteStorageBase - { - // An id that refers to a specific application. - using IdType = SQLite::rowid_t; - - // The return type of Search - using SearchResult = Schema::ISQLiteIndex::SearchResult; - - // The return type of GetMetadataByManifestId - using MetadataResult = Schema::ISQLiteIndex::MetadataResult; - - // Options for creating a new index. - using CreateOptions = Schema::ISQLiteIndex::CreateOptions; - - // The type of version keys. - using VersionKey = Schema::ISQLiteIndex::VersionKey; - - SQLiteIndex(const SQLiteIndex&) = delete; - SQLiteIndex& operator=(const SQLiteIndex&) = delete; - - SQLiteIndex(SQLiteIndex&&) = default; - SQLiteIndex& operator=(SQLiteIndex&&) = default; - - // Creates a new index database of the given version. - static SQLiteIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest(), CreateOptions options = CreateOptions::None); - - // Opens an existing SQLiteIndex database. - static SQLiteIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}); - - // Creates a copy of the given index. - static SQLiteIndex CopyFrom(const std::string& filePath, SQLiteIndex& source); - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Changes the version of the interface being used to operate on the database. - // Should only be used for testing. - void ForceVersion(const SQLite::Version& version); - - // Gets the latest version of the index schema (the actual numbers, not just the latest sentinel values). - static SQLite::Version GetLatestVersion(); - - // Gets the context data for testing. - const Schema::SQLiteIndexContextData& GetContextData() const; -#endif - - // Adds the manifest at the repository relative path to the index. - // If the function succeeds, the manifest has been added. - // Returns the manifest id. - IdType AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); - - // Adds the manifest at the repository relative path to the index. - // If the function succeeds, the manifest has been added. - // Returns the manifest id. - IdType AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - - // Adds the manifest to the index. - // If the function succeeds, the manifest has been added. - // Returns the manifest id. - IdType AddManifest(const Manifest::Manifest& manifest); - - // Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the index was modified by the function. - bool UpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); - - // Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the index was modified by the function. - bool UpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - - // Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the index was modified by the function. - bool UpdateManifest(const Manifest::Manifest& manifest); - - // Adds or updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the manifest was added (true) or updated (false). - bool AddOrUpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); - - // Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the manifest was added (true) or updated (false). - bool AddOrUpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - - // Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the manifest was added (true) or updated (false). - bool AddOrUpdateManifest(const Manifest::Manifest& manifest); - - // Removes the manifest with matching { Id, Version, Channel } from the index. - void RemoveManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); - - // Removes the manifest with matching { Id, Version, Channel } from the index. - void RemoveManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - - // Removes the manifest with matching { Id, Version, Channel } from the index. - void RemoveManifest(const Manifest::Manifest& manifest); - - // Removes the manifest with the given id. - void RemoveManifestById(IdType manifestId); - - // Removes data that is no longer needed for an index that is to be published. - void PrepareForPackaging(); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - bool CheckConsistency(bool log = false) const; - - // Performs a search based on the given criteria. - SearchResult Search(const SearchRequest& request) const; - - // Gets the string for the given property and primary id, if present. - std::optional GetPropertyByPrimaryId(IdType primaryId, PackageVersionProperty property) const; - - // Gets the string values for the given property and primary id, if present. - std::vector GetMultiPropertyByPrimaryId(IdType primaryId, PackageVersionMultiProperty property) const; - - // Gets the manifest id for the given { id, version, channel }, if present. - // If version is empty, gets the value for the 'latest' version. - std::optional GetManifestIdByKey(IdType id, std::string_view version, std::string_view channel) const; - - // Gets the manifest id for the given manifest, if present. - std::optional GetManifestIdByManifest(const Manifest::Manifest& manifest) const; - - // Gets all versions and channels for the given id. - std::vector GetVersionKeysById(IdType id) const; - - // Gets the string for the given metadata and manifest id, if present. - MetadataResult GetMetadataByManifestId(SQLite::rowid_t manifestId) const; - - // Sets the string for the given metadata and manifest id. - void SetMetadataByManifestId(IdType manifestId, PackageVersionMetadata metadata, std::string_view value); - - // Normalizes a name using the internal rules used by the index. - // Largely a utility function; should not be used to do work on behalf of the index by the caller. - Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const; - - // Get all the dependencies for a specific manifest. - std::set> GetDependenciesByManifestRowId(SQLite::rowid_t manifestRowId) const; - std::vector> GetDependentsById(AppInstaller::Manifest::string_t packageId) const; - - // Migrates the index to the target version. - // Returns false to indicate that the requested migration is not supported. - bool MigrateTo(SQLite::Version version); - - // The property values that can be set. - enum class Property - { - PackageUpdateTrackingBaseTime, - IntermediateFileOutputPath, - }; - - // Sets the given property. - // Some properties will persist into the database. - void SetProperty(Property property, const std::string& value); - - private: - // Constructor used to create a new index. - SQLiteIndex(const std::string& target, const SQLite::Version& version, CreateOptions options); - - // Constructor used to open an existing index. - SQLiteIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); - - // Constructor used to copy the given index. - SQLiteIndex(const std::string& target, SQLiteIndex& source); - - // Sets the database file path in the context data if appropriate. - void SetDatabaseFilePath(const std::string& target); - - // Internal functions to normalize on the relativePath being present. - IdType AddManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); - IdType AddManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath); - bool UpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); - bool UpdateManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath); - bool AddOrUpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); - - std::unique_ptr m_interface; - Schema::SQLiteIndexContextData m_contextData; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Microsoft/Schema/ISQLiteIndex.h" +#include +#include +#include "ISource.h" +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Repository::Microsoft +{ + // Holds the connection to the database, as well as the appropriate functionality to interface with it. + struct SQLiteIndex : SQLite::SQLiteStorageBase + { + // An id that refers to a specific application. + using IdType = SQLite::rowid_t; + + // The return type of Search + using SearchResult = Schema::ISQLiteIndex::SearchResult; + + // The return type of GetMetadataByManifestId + using MetadataResult = Schema::ISQLiteIndex::MetadataResult; + + // Options for creating a new index. + using CreateOptions = Schema::ISQLiteIndex::CreateOptions; + + // The type of version keys. + using VersionKey = Schema::ISQLiteIndex::VersionKey; + + SQLiteIndex(const SQLiteIndex&) = delete; + SQLiteIndex& operator=(const SQLiteIndex&) = delete; + + SQLiteIndex(SQLiteIndex&&) = default; + SQLiteIndex& operator=(SQLiteIndex&&) = default; + + // Creates a new index database of the given version. + static SQLiteIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest(), CreateOptions options = CreateOptions::None); + + // Opens an existing SQLiteIndex database. + static SQLiteIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}); + + // Creates a copy of the given index. + static SQLiteIndex CopyFrom(const std::string& filePath, SQLiteIndex& source); + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Changes the version of the interface being used to operate on the database. + // Should only be used for testing. + void ForceVersion(const SQLite::Version& version); + + // Gets the latest version of the index schema (the actual numbers, not just the latest sentinel values). + static SQLite::Version GetLatestVersion(); + + // Gets the context data for testing. + const Schema::SQLiteIndexContextData& GetContextData() const; +#endif + + // Adds the manifest at the repository relative path to the index. + // If the function succeeds, the manifest has been added. + // Returns the manifest id. + IdType AddManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); + + // Adds the manifest at the repository relative path to the index. + // If the function succeeds, the manifest has been added. + // Returns the manifest id. + IdType AddManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Adds the manifest to the index. + // If the function succeeds, the manifest has been added. + // Returns the manifest id. + IdType AddManifest(const Manifest::Manifest& manifest); + + // Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the index was modified by the function. + bool UpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); + + // Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the index was modified by the function. + bool UpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the index was modified by the function. + bool UpdateManifest(const Manifest::Manifest& manifest); + + // Adds or updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the manifest was added (true) or updated (false). + bool AddOrUpdateManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); + + // Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the manifest was added (true) or updated (false). + bool AddOrUpdateManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the manifest was added (true) or updated (false). + bool AddOrUpdateManifest(const Manifest::Manifest& manifest); + + // Removes the manifest with matching { Id, Version, Channel } from the index. + void RemoveManifest(const std::filesystem::path& manifestPath, const std::filesystem::path& relativePath); + + // Removes the manifest with matching { Id, Version, Channel } from the index. + void RemoveManifest(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Removes the manifest with matching { Id, Version, Channel } from the index. + void RemoveManifest(const Manifest::Manifest& manifest); + + // Removes the manifest with the given id. + void RemoveManifestById(IdType manifestId); + + // Removes data that is no longer needed for an index that is to be published. + void PrepareForPackaging(); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + bool CheckConsistency(bool log = false) const; + + // Performs a search based on the given criteria. + SearchResult Search(const SearchRequest& request) const; + + // Gets the string for the given property and primary id, if present. + std::optional GetPropertyByPrimaryId(IdType primaryId, PackageVersionProperty property) const; + + // Gets the string values for the given property and primary id, if present. + std::vector GetMultiPropertyByPrimaryId(IdType primaryId, PackageVersionMultiProperty property) const; + + // Gets the manifest id for the given { id, version, channel }, if present. + // If version is empty, gets the value for the 'latest' version. + std::optional GetManifestIdByKey(IdType id, std::string_view version, std::string_view channel) const; + + // Gets the manifest id for the given manifest, if present. + std::optional GetManifestIdByManifest(const Manifest::Manifest& manifest) const; + + // Gets all versions and channels for the given id. + std::vector GetVersionKeysById(IdType id) const; + + // Gets the string for the given metadata and manifest id, if present. + MetadataResult GetMetadataByManifestId(SQLite::rowid_t manifestId) const; + + // Sets the string for the given metadata and manifest id. + void SetMetadataByManifestId(IdType manifestId, PackageVersionMetadata metadata, std::string_view value); + + // Normalizes a name using the internal rules used by the index. + // Largely a utility function; should not be used to do work on behalf of the index by the caller. + Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const; + + // Get all the dependencies for a specific manifest. + std::set> GetDependenciesByManifestRowId(SQLite::rowid_t manifestRowId) const; + std::vector> GetDependentsById(AppInstaller::Manifest::string_t packageId) const; + + // Migrates the index to the target version. + // Returns false to indicate that the requested migration is not supported. + bool MigrateTo(SQLite::Version version); + + // The property values that can be set. + enum class Property + { + PackageUpdateTrackingBaseTime, + IntermediateFileOutputPath, + }; + + // Sets the given property. + // Some properties will persist into the database. + void SetProperty(Property property, const std::string& value); + + private: + // Constructor used to create a new index. + SQLiteIndex(const std::string& target, const SQLite::Version& version, CreateOptions options); + + // Constructor used to open an existing index. + SQLiteIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + // Constructor used to copy the given index. + SQLiteIndex(const std::string& target, SQLiteIndex& source); + + // Sets the database file path in the context data if appropriate. + void SetDatabaseFilePath(const std::string& target); + + // Internal functions to normalize on the relativePath being present. + IdType AddManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); + IdType AddManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath); + bool UpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); + bool UpdateManifestInternalHoldingLock(const Manifest::Manifest& manifest, const std::optional& relativePath); + bool AddOrUpdateManifestInternal(const Manifest::Manifest& manifest, const std::optional& relativePath); + + std::unique_ptr m_interface; + Schema::SQLiteIndexContextData m_contextData; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp index 5cfccd3290..00b6ead7ac 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.cpp @@ -1,144 +1,144 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/SQLiteIndexSource.h" -#include "Microsoft/SQLiteIndexSourceV1.h" -#include "Microsoft/SQLiteIndexSourceV2.h" -#include "Microsoft/PreIndexedPackageSourceFactory.h" -#include -#include - -using namespace AppInstaller::Utility; - - -namespace AppInstaller::Repository::Microsoft -{ - namespace details - { - SourceReference::SourceReference(const std::shared_ptr& source) : - m_source(source) {} - - std::shared_ptr SourceReference::GetReferenceSource() const - { - std::shared_ptr source = m_source.lock(); - THROW_HR_IF(E_NOT_VALID_STATE, !source); - return source; - } - } - - SQLiteIndexSource::SQLiteIndexSource( - const SourceDetails& details, - SQLiteIndex&& index, - bool isInstalledSource, - bool requireManifestHash) : - m_details(details), m_isInstalled(isInstalledSource), m_index(std::move(index)), m_requireManifestHash(requireManifestHash) - { - std::vector cacheSources; - cacheSources.push_back(m_details.Arg); - if (!m_details.AlternateArg.empty()) - { - cacheSources.push_back(m_details.AlternateArg); - } - - switch (m_index.GetVersion().MajorVersion) - { - case 1: - m_manifestCache = std::make_shared(Caching::FileCache::Type::IndexV1_Manifest, m_details.Identifier, std::move(cacheSources)); - break; - case 2: - m_manifestCache = std::make_shared(Caching::FileCache::Type::IndexV2_Manifest, m_details.Identifier, cacheSources); - m_packageVersionDataCache = std::make_shared(Caching::FileCache::Type::IndexV2_PackageVersionData, m_details.Identifier, std::move(cacheSources)); - break; - default: - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - } - - const SourceDetails& SQLiteIndexSource::GetDetails() const - { - return m_details; - } - - const std::string& SQLiteIndexSource::GetIdentifier() const - { - return m_details.Identifier; - } - - SearchResult SQLiteIndexSource::Search(const SearchRequest& request) const - { - auto indexResults = m_index.Search(request); - - SearchResult result; - std::shared_ptr sharedThis = NonConstSharedFromThis(); - uint32_t majorVersion = m_index.GetVersion().MajorVersion; - - for (auto& indexResult : indexResults.Matches) - { - std::shared_ptr package; - - switch (majorVersion) - { - case 1: - package = std::make_shared(sharedThis, indexResult.first, m_manifestCache, m_isInstalled); - break; - case 2: - package = std::make_shared(sharedThis, indexResult.first, m_manifestCache, m_packageVersionDataCache, m_isInstalled); - break; - default: - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - - result.Matches.emplace_back( - std::move(package), - std::move(indexResult.second)); - } - - result.Truncated = indexResults.Truncated; - return result; - } - - void* SQLiteIndexSource::CastTo(ISourceType type) - { - if (type == SourceType) - { - return this; - } - - return nullptr; - } - - bool SQLiteIndexSource::IsSame(const SQLiteIndexSource* other) const - { - return (other && GetIdentifier() == other->GetIdentifier()); - } - - std::shared_ptr SQLiteIndexSource::NonConstSharedFromThis() const - { - return const_cast(this)->shared_from_this(); - } - - SQLiteIndexWriteableSource::SQLiteIndexWriteableSource(const SourceDetails& details, SQLiteIndex&& index, bool isInstalledSource) : - SQLiteIndexSource(details, std::move(index), isInstalledSource) - { - } - - void* SQLiteIndexWriteableSource::CastTo(ISourceType type) - { - if (type == ISourceType::IMutablePackageSource) - { - return static_cast(this); - } - - return SQLiteIndexSource::CastTo(type); - } - - void SQLiteIndexWriteableSource::AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - m_index.AddManifest(manifest, relativePath); - } - - void SQLiteIndexWriteableSource::RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - m_index.RemoveManifest(manifest, relativePath); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/SQLiteIndexSource.h" +#include "Microsoft/SQLiteIndexSourceV1.h" +#include "Microsoft/SQLiteIndexSourceV2.h" +#include "Microsoft/PreIndexedPackageSourceFactory.h" +#include +#include + +using namespace AppInstaller::Utility; + + +namespace AppInstaller::Repository::Microsoft +{ + namespace details + { + SourceReference::SourceReference(const std::shared_ptr& source) : + m_source(source) {} + + std::shared_ptr SourceReference::GetReferenceSource() const + { + std::shared_ptr source = m_source.lock(); + THROW_HR_IF(E_NOT_VALID_STATE, !source); + return source; + } + } + + SQLiteIndexSource::SQLiteIndexSource( + const SourceDetails& details, + SQLiteIndex&& index, + bool isInstalledSource, + bool requireManifestHash) : + m_details(details), m_isInstalled(isInstalledSource), m_index(std::move(index)), m_requireManifestHash(requireManifestHash) + { + std::vector cacheSources; + cacheSources.push_back(m_details.Arg); + if (!m_details.AlternateArg.empty()) + { + cacheSources.push_back(m_details.AlternateArg); + } + + switch (m_index.GetVersion().MajorVersion) + { + case 1: + m_manifestCache = std::make_shared(Caching::FileCache::Type::IndexV1_Manifest, m_details.Identifier, std::move(cacheSources)); + break; + case 2: + m_manifestCache = std::make_shared(Caching::FileCache::Type::IndexV2_Manifest, m_details.Identifier, cacheSources); + m_packageVersionDataCache = std::make_shared(Caching::FileCache::Type::IndexV2_PackageVersionData, m_details.Identifier, std::move(cacheSources)); + break; + default: + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + } + + const SourceDetails& SQLiteIndexSource::GetDetails() const + { + return m_details; + } + + const std::string& SQLiteIndexSource::GetIdentifier() const + { + return m_details.Identifier; + } + + SearchResult SQLiteIndexSource::Search(const SearchRequest& request) const + { + auto indexResults = m_index.Search(request); + + SearchResult result; + std::shared_ptr sharedThis = NonConstSharedFromThis(); + uint32_t majorVersion = m_index.GetVersion().MajorVersion; + + for (auto& indexResult : indexResults.Matches) + { + std::shared_ptr package; + + switch (majorVersion) + { + case 1: + package = std::make_shared(sharedThis, indexResult.first, m_manifestCache, m_isInstalled); + break; + case 2: + package = std::make_shared(sharedThis, indexResult.first, m_manifestCache, m_packageVersionDataCache, m_isInstalled); + break; + default: + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + + result.Matches.emplace_back( + std::move(package), + std::move(indexResult.second)); + } + + result.Truncated = indexResults.Truncated; + return result; + } + + void* SQLiteIndexSource::CastTo(ISourceType type) + { + if (type == SourceType) + { + return this; + } + + return nullptr; + } + + bool SQLiteIndexSource::IsSame(const SQLiteIndexSource* other) const + { + return (other && GetIdentifier() == other->GetIdentifier()); + } + + std::shared_ptr SQLiteIndexSource::NonConstSharedFromThis() const + { + return const_cast(this)->shared_from_this(); + } + + SQLiteIndexWriteableSource::SQLiteIndexWriteableSource(const SourceDetails& details, SQLiteIndex&& index, bool isInstalledSource) : + SQLiteIndexSource(details, std::move(index), isInstalledSource) + { + } + + void* SQLiteIndexWriteableSource::CastTo(ISourceType type) + { + if (type == ISourceType::IMutablePackageSource) + { + return static_cast(this); + } + + return SQLiteIndexSource::CastTo(type); + } + + void SQLiteIndexWriteableSource::AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + m_index.AddManifest(manifest, relativePath); + } + + void SQLiteIndexWriteableSource::RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + m_index.RemoveManifest(manifest, relativePath); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h index 46120e2ad9..47e0d2e007 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSource.h @@ -1,99 +1,99 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/SQLiteIndex.h" -#include "ISource.h" -#include -#include - - -namespace AppInstaller::Repository::Microsoft -{ - // A source that holds a SQLiteIndex and lock. - struct SQLiteIndexSource : public std::enable_shared_from_this, public ISource - { - static constexpr ISourceType SourceType = ISourceType::SQLiteIndexSource; - - SQLiteIndexSource( - const SourceDetails& details, - SQLiteIndex&& index, - bool isInstalledSource = false, - bool requireManifestHash = false); - - SQLiteIndexSource(const SQLiteIndexSource&) = delete; - SQLiteIndexSource& operator=(const SQLiteIndexSource&) = delete; - - SQLiteIndexSource(SQLiteIndexSource&&) = default; - SQLiteIndexSource& operator=(SQLiteIndexSource&&) = default; - - ~SQLiteIndexSource() = default; - - // Get the source's details. - const SourceDetails& GetDetails() const override; - - // Gets the source's identifier; a unique identifier independent of the name - // that will not change between a remove/add or between additional adds. - // Must be suitable for filesystem names. - const std::string& GetIdentifier() const override; - - // Execute a search on the source. - SearchResult Search(const SearchRequest& request) const override; - - // Casts to the requested type. - void* CastTo(ISourceType type) override; - - // Gets the index. - SQLiteIndex& GetIndex() { return m_index; } - const SQLiteIndex& GetIndex() const { return m_index; } - - // Determines if the other source refers to the same as this. - bool IsSame(const SQLiteIndexSource* other) const; - - bool RequireManifestHash() const { return m_requireManifestHash; } - - private: - std::shared_ptr NonConstSharedFromThis() const; - - SourceDetails m_details; - bool m_requireManifestHash; - bool m_isInstalled; - std::shared_ptr m_manifestCache; - std::shared_ptr m_packageVersionDataCache; - - protected: - SQLiteIndex m_index; - }; - - // A source that holds a SQLiteIndex and lock. - struct SQLiteIndexWriteableSource : public SQLiteIndexSource, public IMutablePackageSource - { - SQLiteIndexWriteableSource( - const SourceDetails& details, - SQLiteIndex&& index, - bool isInstalledSource = false); - - // Casts to the requested type. - void* CastTo(ISourceType type) override; - - // Adds a package version to the source. - void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - - // Removes a package version from the source. - void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - }; - - namespace details - { - // For the IPackage(Version) implementations that need to hold a weak reference to a SQLiteIndexSource. - struct SourceReference - { - SourceReference(const std::shared_ptr& source); - - protected: - std::shared_ptr GetReferenceSource() const; - - private: - std::weak_ptr m_source; - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/SQLiteIndex.h" +#include "ISource.h" +#include +#include + + +namespace AppInstaller::Repository::Microsoft +{ + // A source that holds a SQLiteIndex and lock. + struct SQLiteIndexSource : public std::enable_shared_from_this, public ISource + { + static constexpr ISourceType SourceType = ISourceType::SQLiteIndexSource; + + SQLiteIndexSource( + const SourceDetails& details, + SQLiteIndex&& index, + bool isInstalledSource = false, + bool requireManifestHash = false); + + SQLiteIndexSource(const SQLiteIndexSource&) = delete; + SQLiteIndexSource& operator=(const SQLiteIndexSource&) = delete; + + SQLiteIndexSource(SQLiteIndexSource&&) = default; + SQLiteIndexSource& operator=(SQLiteIndexSource&&) = default; + + ~SQLiteIndexSource() = default; + + // Get the source's details. + const SourceDetails& GetDetails() const override; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names. + const std::string& GetIdentifier() const override; + + // Execute a search on the source. + SearchResult Search(const SearchRequest& request) const override; + + // Casts to the requested type. + void* CastTo(ISourceType type) override; + + // Gets the index. + SQLiteIndex& GetIndex() { return m_index; } + const SQLiteIndex& GetIndex() const { return m_index; } + + // Determines if the other source refers to the same as this. + bool IsSame(const SQLiteIndexSource* other) const; + + bool RequireManifestHash() const { return m_requireManifestHash; } + + private: + std::shared_ptr NonConstSharedFromThis() const; + + SourceDetails m_details; + bool m_requireManifestHash; + bool m_isInstalled; + std::shared_ptr m_manifestCache; + std::shared_ptr m_packageVersionDataCache; + + protected: + SQLiteIndex m_index; + }; + + // A source that holds a SQLiteIndex and lock. + struct SQLiteIndexWriteableSource : public SQLiteIndexSource, public IMutablePackageSource + { + SQLiteIndexWriteableSource( + const SourceDetails& details, + SQLiteIndex&& index, + bool isInstalledSource = false); + + // Casts to the requested type. + void* CastTo(ISourceType type) override; + + // Adds a package version to the source. + void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Removes a package version from the source. + void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + }; + + namespace details + { + // For the IPackage(Version) implementations that need to hold a weak reference to a SQLiteIndexSource. + struct SourceReference + { + SourceReference(const std::shared_ptr& source); + + protected: + std::shared_ptr GetReferenceSource() const; + + private: + std::weak_ptr m_source; + }; + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.cpp index 651f2b014b..0f4673c25d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.cpp @@ -1,274 +1,274 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/SQLiteIndexSourceV1.h" -#include - -using namespace AppInstaller::Utility; - - -namespace AppInstaller::Repository::Microsoft::details::V1 -{ - // The IPackageVersion implementation for V1 index. - struct PackageVersion : public SourceReference, public IPackageVersion - { - PackageVersion(const std::shared_ptr& source, SQLiteIndex::IdType manifestId, const std::shared_ptr& manifestCache) : - SourceReference(source), m_manifestId(manifestId), m_manifestCache(manifestCache) {} - - // Inherited via IPackageVersion - LocIndString GetProperty(PackageVersionProperty property) const override - { - switch (property) - { - case PackageVersionProperty::SourceIdentifier: - return LocIndString{ GetReferenceSource()->GetIdentifier() }; - case PackageVersionProperty::SourceName: - return LocIndString{ GetReferenceSource()->GetDetails().Name }; - default: - // Values coming from the index will always be localized/independent. - std::optional optValue = GetReferenceSource()->GetIndex().GetPropertyByPrimaryId(m_manifestId, property); - return LocIndString{ optValue ? optValue.value() : std::string{} }; - } - } - - std::vector GetMultiProperty(PackageVersionMultiProperty property) const override - { - std::vector result; - - for (auto&& value : GetReferenceSource()->GetIndex().GetMultiPropertyByPrimaryId(m_manifestId, property)) - { - // Values coming from the index will always be localized/independent. - result.emplace_back(std::move(value)); - } - - return result; - } - - Manifest::Manifest GetManifest() override - { - std::shared_ptr source = GetReferenceSource(); - - std::optional relativePathOpt = source->GetIndex().GetPropertyByPrimaryId(m_manifestId, PackageVersionProperty::RelativePath); - THROW_HR_IF(E_NOT_SET, !relativePathOpt); - - std::optional manifestHashString = source->GetIndex().GetPropertyByPrimaryId(m_manifestId, PackageVersionProperty::ManifestSHA256Hash); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, source->RequireManifestHash() && !manifestHashString); - - SHA256::HashBuffer manifestSHA256; - if (manifestHashString) - { - manifestSHA256 = SHA256::ConvertToBytes(manifestHashString.value()); - } - - std::unique_ptr manifestStream = m_manifestCache->GetFile(ConvertToUTF16(relativePathOpt.value()), manifestSHA256); - return Manifest::YamlParser::Create(ReadEntireStream(*manifestStream)); - } - - Source GetSource() const override - { - return Source{ GetReferenceSource() }; - } - - IPackageVersion::Metadata GetMetadata() const override - { - auto metadata = GetReferenceSource()->GetIndex().GetMetadataByManifestId(m_manifestId); - - IPackageVersion::Metadata result; - for (auto&& data : metadata) - { - result.emplace(std::move(data)); - } - - return result; - } - - private: - SQLiteIndex::IdType m_manifestId; - std::shared_ptr m_manifestCache; - }; - - SQLitePackage::SQLitePackage(const std::shared_ptr& source, SQLiteIndex::IdType idId, const std::shared_ptr& manifestCache, bool isInstalled) : - SourceReference(source), m_idId(idId), m_manifestCache(manifestCache), m_isInstalled(isInstalled) {} - - LocIndString SQLitePackage::GetProperty(PackageProperty property) const - { - LocIndString result; - - std::shared_ptr truth = GetLatestVersion(); - if (truth) - { - switch (property) - { - case PackageProperty::Id: - return truth->GetProperty(PackageVersionProperty::Id); - case PackageProperty::Name: - return truth->GetProperty(PackageVersionProperty::Name); - default: - THROW_HR(E_UNEXPECTED); - } - } - else - { - AICLI_LOG(Repo, Verbose, << "SQLitePackage: No manifest was found for the package with id# '" << m_idId << "'"); - } - - return result; - } - - std::vector SQLitePackage::GetMultiProperty(PackageMultiProperty property) const - { - std::shared_ptr source = GetReferenceSource(); - std::vector result; - PackageVersionMultiProperty mappedProperty = PackageMultiPropertyToPackageVersionMultiProperty(property); - - for (const auto& version : source->GetIndex().GetVersionKeysById(m_idId)) - { - for (auto&& string : source->GetIndex().GetMultiPropertyByPrimaryId(version.ManifestId, mappedProperty)) - { - auto itr = std::lower_bound(result.begin(), result.end(), string); - - if (itr == result.end() || itr->get() != string) - { - result.emplace(itr, std::move(string)); - } - } - } - - return result; - } - - std::vector SQLitePackage::GetVersionKeys() const - { - std::shared_ptr source = GetReferenceSource(); - - { - auto sharedLock = m_versionKeysLock.lock_shared(); - - if (!m_versionKeys.empty()) - { - return m_versionKeys; - } - } - - auto exclusiveLock = m_versionKeysLock.lock_exclusive(); - - if (!m_versionKeys.empty()) - { - return m_versionKeys; - } - - std::vector versions = source->GetIndex().GetVersionKeysById(m_idId); - - for (const auto& vk : versions) - { - std::string version = vk.VersionAndChannel.GetVersion().ToString(); - std::string channel = vk.VersionAndChannel.GetChannel().ToString(); - m_versionKeys.emplace_back(source->GetIdentifier(), version, channel); - m_versionKeysMap.emplace(MapKey{ std::move(version), std::move(channel) }, vk.ManifestId); - } - - return m_versionKeys; - } - - std::shared_ptr SQLitePackage::GetLatestVersion() const - { - std::shared_ptr source = GetReferenceSource(); - std::optional manifestId = source->GetIndex().GetManifestIdByKey(m_idId, {}, {}); - - if (manifestId) - { - return std::make_shared(source, manifestId.value(), m_manifestCache); - } - - return {}; - } - - std::shared_ptr SQLitePackage::GetVersion(const PackageVersionKey& versionKey) const - { - std::shared_ptr source = GetReferenceSource(); - - // Ensure that this key targets this (or any) source - if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) - { - return {}; - } - - std::optional manifestId; - - { - MapKey requested{ versionKey.Version, versionKey.Channel }; - auto sharedLock = m_versionKeysLock.lock_shared(); - - auto itr = m_versionKeysMap.find(requested); - if (itr != m_versionKeysMap.end()) - { - manifestId = itr->second; - } - } - - if (!manifestId) - { - manifestId = source->GetIndex().GetManifestIdByKey(m_idId, versionKey.Version, versionKey.Channel); - } - - if (manifestId) - { - return std::make_shared(source, manifestId.value(), m_manifestCache); - } - - return {}; - } - - Source SQLitePackage::GetSource() const - { - return Source{ GetReferenceSource() }; - } - - bool SQLitePackage::IsSame(const IPackage* other) const - { - const SQLitePackage* otherSQLite = PackageCast(other); - - if (otherSQLite) - { - return GetReferenceSource()->IsSame(otherSQLite->GetReferenceSource().get()) && m_idId == otherSQLite->m_idId; - } - - return false; - } - - const void* SQLitePackage::CastTo(IPackageType type) const - { - if (type == PackageType) - { - return this; - } - - return nullptr; - } - - std::shared_ptr SQLitePackage::GetInstalled() - { - return m_isInstalled ? shared_from_this() : std::shared_ptr{}; - } - - std::vector> SQLitePackage::GetAvailable() - { - return m_isInstalled ? std::vector>{} : std::vector>{ shared_from_this() }; - } - - bool SQLitePackage::MapKey::operator<(const MapKey& other) const - { - if (Version < other.Version) - { - return true; - } - else if (Version == other.Version) - { - return Channel < other.Channel; - } - else - { - return false; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/SQLiteIndexSourceV1.h" +#include + +using namespace AppInstaller::Utility; + + +namespace AppInstaller::Repository::Microsoft::details::V1 +{ + // The IPackageVersion implementation for V1 index. + struct PackageVersion : public SourceReference, public IPackageVersion + { + PackageVersion(const std::shared_ptr& source, SQLiteIndex::IdType manifestId, const std::shared_ptr& manifestCache) : + SourceReference(source), m_manifestId(manifestId), m_manifestCache(manifestCache) {} + + // Inherited via IPackageVersion + LocIndString GetProperty(PackageVersionProperty property) const override + { + switch (property) + { + case PackageVersionProperty::SourceIdentifier: + return LocIndString{ GetReferenceSource()->GetIdentifier() }; + case PackageVersionProperty::SourceName: + return LocIndString{ GetReferenceSource()->GetDetails().Name }; + default: + // Values coming from the index will always be localized/independent. + std::optional optValue = GetReferenceSource()->GetIndex().GetPropertyByPrimaryId(m_manifestId, property); + return LocIndString{ optValue ? optValue.value() : std::string{} }; + } + } + + std::vector GetMultiProperty(PackageVersionMultiProperty property) const override + { + std::vector result; + + for (auto&& value : GetReferenceSource()->GetIndex().GetMultiPropertyByPrimaryId(m_manifestId, property)) + { + // Values coming from the index will always be localized/independent. + result.emplace_back(std::move(value)); + } + + return result; + } + + Manifest::Manifest GetManifest() override + { + std::shared_ptr source = GetReferenceSource(); + + std::optional relativePathOpt = source->GetIndex().GetPropertyByPrimaryId(m_manifestId, PackageVersionProperty::RelativePath); + THROW_HR_IF(E_NOT_SET, !relativePathOpt); + + std::optional manifestHashString = source->GetIndex().GetPropertyByPrimaryId(m_manifestId, PackageVersionProperty::ManifestSHA256Hash); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, source->RequireManifestHash() && !manifestHashString); + + SHA256::HashBuffer manifestSHA256; + if (manifestHashString) + { + manifestSHA256 = SHA256::ConvertToBytes(manifestHashString.value()); + } + + std::unique_ptr manifestStream = m_manifestCache->GetFile(ConvertToUTF16(relativePathOpt.value()), manifestSHA256); + return Manifest::YamlParser::Create(ReadEntireStream(*manifestStream)); + } + + Source GetSource() const override + { + return Source{ GetReferenceSource() }; + } + + IPackageVersion::Metadata GetMetadata() const override + { + auto metadata = GetReferenceSource()->GetIndex().GetMetadataByManifestId(m_manifestId); + + IPackageVersion::Metadata result; + for (auto&& data : metadata) + { + result.emplace(std::move(data)); + } + + return result; + } + + private: + SQLiteIndex::IdType m_manifestId; + std::shared_ptr m_manifestCache; + }; + + SQLitePackage::SQLitePackage(const std::shared_ptr& source, SQLiteIndex::IdType idId, const std::shared_ptr& manifestCache, bool isInstalled) : + SourceReference(source), m_idId(idId), m_manifestCache(manifestCache), m_isInstalled(isInstalled) {} + + LocIndString SQLitePackage::GetProperty(PackageProperty property) const + { + LocIndString result; + + std::shared_ptr truth = GetLatestVersion(); + if (truth) + { + switch (property) + { + case PackageProperty::Id: + return truth->GetProperty(PackageVersionProperty::Id); + case PackageProperty::Name: + return truth->GetProperty(PackageVersionProperty::Name); + default: + THROW_HR(E_UNEXPECTED); + } + } + else + { + AICLI_LOG(Repo, Verbose, << "SQLitePackage: No manifest was found for the package with id# '" << m_idId << "'"); + } + + return result; + } + + std::vector SQLitePackage::GetMultiProperty(PackageMultiProperty property) const + { + std::shared_ptr source = GetReferenceSource(); + std::vector result; + PackageVersionMultiProperty mappedProperty = PackageMultiPropertyToPackageVersionMultiProperty(property); + + for (const auto& version : source->GetIndex().GetVersionKeysById(m_idId)) + { + for (auto&& string : source->GetIndex().GetMultiPropertyByPrimaryId(version.ManifestId, mappedProperty)) + { + auto itr = std::lower_bound(result.begin(), result.end(), string); + + if (itr == result.end() || itr->get() != string) + { + result.emplace(itr, std::move(string)); + } + } + } + + return result; + } + + std::vector SQLitePackage::GetVersionKeys() const + { + std::shared_ptr source = GetReferenceSource(); + + { + auto sharedLock = m_versionKeysLock.lock_shared(); + + if (!m_versionKeys.empty()) + { + return m_versionKeys; + } + } + + auto exclusiveLock = m_versionKeysLock.lock_exclusive(); + + if (!m_versionKeys.empty()) + { + return m_versionKeys; + } + + std::vector versions = source->GetIndex().GetVersionKeysById(m_idId); + + for (const auto& vk : versions) + { + std::string version = vk.VersionAndChannel.GetVersion().ToString(); + std::string channel = vk.VersionAndChannel.GetChannel().ToString(); + m_versionKeys.emplace_back(source->GetIdentifier(), version, channel); + m_versionKeysMap.emplace(MapKey{ std::move(version), std::move(channel) }, vk.ManifestId); + } + + return m_versionKeys; + } + + std::shared_ptr SQLitePackage::GetLatestVersion() const + { + std::shared_ptr source = GetReferenceSource(); + std::optional manifestId = source->GetIndex().GetManifestIdByKey(m_idId, {}, {}); + + if (manifestId) + { + return std::make_shared(source, manifestId.value(), m_manifestCache); + } + + return {}; + } + + std::shared_ptr SQLitePackage::GetVersion(const PackageVersionKey& versionKey) const + { + std::shared_ptr source = GetReferenceSource(); + + // Ensure that this key targets this (or any) source + if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) + { + return {}; + } + + std::optional manifestId; + + { + MapKey requested{ versionKey.Version, versionKey.Channel }; + auto sharedLock = m_versionKeysLock.lock_shared(); + + auto itr = m_versionKeysMap.find(requested); + if (itr != m_versionKeysMap.end()) + { + manifestId = itr->second; + } + } + + if (!manifestId) + { + manifestId = source->GetIndex().GetManifestIdByKey(m_idId, versionKey.Version, versionKey.Channel); + } + + if (manifestId) + { + return std::make_shared(source, manifestId.value(), m_manifestCache); + } + + return {}; + } + + Source SQLitePackage::GetSource() const + { + return Source{ GetReferenceSource() }; + } + + bool SQLitePackage::IsSame(const IPackage* other) const + { + const SQLitePackage* otherSQLite = PackageCast(other); + + if (otherSQLite) + { + return GetReferenceSource()->IsSame(otherSQLite->GetReferenceSource().get()) && m_idId == otherSQLite->m_idId; + } + + return false; + } + + const void* SQLitePackage::CastTo(IPackageType type) const + { + if (type == PackageType) + { + return this; + } + + return nullptr; + } + + std::shared_ptr SQLitePackage::GetInstalled() + { + return m_isInstalled ? shared_from_this() : std::shared_ptr{}; + } + + std::vector> SQLitePackage::GetAvailable() + { + return m_isInstalled ? std::vector>{} : std::vector>{ shared_from_this() }; + } + + bool SQLitePackage::MapKey::operator<(const MapKey& other) const + { + if (Version < other.Version) + { + return true; + } + else if (Version == other.Version) + { + return Channel < other.Channel; + } + else + { + return false; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.h index 164daa6832..fef35e05ee 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.h +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV1.h @@ -1,57 +1,57 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/SQLiteIndexSource.h" - - -namespace AppInstaller::Repository::Microsoft::details::V1 -{ - // The IPackage implementation for V1 index. - struct SQLitePackage : public std::enable_shared_from_this, public SourceReference, public IPackage, public ICompositePackage - { - static constexpr IPackageType PackageType = IPackageType::SQLitePackage1; - - SQLitePackage(const std::shared_ptr& source, SQLiteIndex::IdType idId, const std::shared_ptr& manifestCache, bool isInstalled); - - // Inherited via IPackage - Utility::LocIndString GetProperty(PackageProperty property) const; - - std::vector GetMultiProperty(PackageMultiProperty property) const override; - - std::vector GetVersionKeys() const override; - - std::shared_ptr GetLatestVersion() const override; - - std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override; - - Source GetSource() const override; - - bool IsSame(const IPackage* other) const override; - - const void* CastTo(IPackageType type) const override; - - // Inherited via ICompositePackage - std::shared_ptr GetInstalled() override; - - std::vector> GetAvailable() override; - - private: - // Contains the information needed to map a version key to it's rows. - struct MapKey - { - Utility::NormalizedString Version; - Utility::NormalizedString Channel; - - bool operator<(const MapKey& other) const; - }; - - SQLiteIndex::IdType m_idId; - std::shared_ptr m_manifestCache; - bool m_isInstalled; - - // To avoid removing const from the interface - mutable wil::srwlock m_versionKeysLock; - mutable std::vector m_versionKeys; - mutable std::map m_versionKeysMap; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/SQLiteIndexSource.h" + + +namespace AppInstaller::Repository::Microsoft::details::V1 +{ + // The IPackage implementation for V1 index. + struct SQLitePackage : public std::enable_shared_from_this, public SourceReference, public IPackage, public ICompositePackage + { + static constexpr IPackageType PackageType = IPackageType::SQLitePackage1; + + SQLitePackage(const std::shared_ptr& source, SQLiteIndex::IdType idId, const std::shared_ptr& manifestCache, bool isInstalled); + + // Inherited via IPackage + Utility::LocIndString GetProperty(PackageProperty property) const; + + std::vector GetMultiProperty(PackageMultiProperty property) const override; + + std::vector GetVersionKeys() const override; + + std::shared_ptr GetLatestVersion() const override; + + std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override; + + Source GetSource() const override; + + bool IsSame(const IPackage* other) const override; + + const void* CastTo(IPackageType type) const override; + + // Inherited via ICompositePackage + std::shared_ptr GetInstalled() override; + + std::vector> GetAvailable() override; + + private: + // Contains the information needed to map a version key to it's rows. + struct MapKey + { + Utility::NormalizedString Version; + Utility::NormalizedString Channel; + + bool operator<(const MapKey& other) const; + }; + + SQLiteIndex::IdType m_idId; + std::shared_ptr m_manifestCache; + bool m_isInstalled; + + // To avoid removing const from the interface + mutable wil::srwlock m_versionKeysLock; + mutable std::vector m_versionKeys; + mutable std::map m_versionKeysMap; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.cpp b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.cpp index 05e2d025ae..2a41d5e684 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.cpp @@ -1,614 +1,614 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/SQLiteIndexSourceV2.h" -#include - -using namespace AppInstaller::Utility; - - -namespace AppInstaller::Repository::Microsoft::details::V2 -{ - // Get the relative path and hash for the package version data manifest. - std::pair CreatePackageVersionDataRelativePath(const std::shared_ptr& source, SQLiteIndex::IdType packageRowId) - { - const SQLiteIndex& index = source->GetIndex(); - - std::string identifier = index.GetPropertyByPrimaryId(packageRowId, PackageVersionProperty::Id).value(); - std::string hash = index.GetPropertyByPrimaryId(packageRowId, PackageVersionProperty::ManifestSHA256Hash).value(); - std::filesystem::path relativePath = Manifest::PackageVersionDataManifest::GetRelativeDirectoryPath(identifier, hash) / Manifest::PackageVersionDataManifest::VersionManifestCompressedFileName(); - - return std::make_pair(std::move(relativePath), std::move(hash)); - } - - // Gets package version data for the given package in the index. - Manifest::PackageVersionDataManifest GetPackageVersionData(const std::shared_ptr& source, SQLiteIndex::IdType packageRowId, const Caching::FileCache& fileCache) - { - auto pathAndHash = CreatePackageVersionDataRelativePath(source, packageRowId); - auto fileStream = fileCache.GetFile(pathAndHash.first, SHA256::ConvertToBytes(pathAndHash.second)); - auto fileBytes = ReadEntireStreamAsByteArray(*fileStream); - - Manifest::PackageVersionDataManifest result; - result.Deserialize(Manifest::PackageVersionDataManifest::CreateDecompressor().Decompress(fileBytes)); - - return result; - } - - // The IPackageVersion implementation for V2 index. - struct PackageVersion : public SourceReference, public IPackageVersion - { - PackageVersion( - const std::shared_ptr& source, - SQLiteIndex::IdType packageRowId, - std::optional packageVersionData, - const std::shared_ptr& manifestCache, - const std::shared_ptr& packageVersionDataCache) : - SourceReference(source), - m_packageRowId(packageRowId), - m_packageVersionData(std::move(packageVersionData)), - m_manifestCache(manifestCache), - m_packageVersionDataCache(packageVersionDataCache) - {} - - // Inherited via IPackageVersion - LocIndString GetProperty(PackageVersionProperty property) const override - { - switch (property) - { - case PackageVersionProperty::SourceIdentifier: - return LocIndString{ GetReferenceSource()->GetIdentifier() }; - case PackageVersionProperty::SourceName: - return LocIndString{ GetReferenceSource()->GetDetails().Name }; - case PackageVersionProperty::RelativePath: - case PackageVersionProperty::ManifestSHA256Hash: - { - // These values can only come from the version data. - EnsurePackageVersionData(); - return GetPropertyFromVersionData(property); - } - break; - case PackageVersionProperty::Publisher: - { - // These values can only come from the manifest. - EnsureManifest(); - return GetPropertyFromManifest(property); - } - break; - case PackageVersionProperty::Id: - case PackageVersionProperty::Name: - case PackageVersionProperty::Moniker: - { - // These properties can come from the manifest or the index. - // The index values will be for the latest version rather than this specific one though. - auto sharedLock = m_versionAndManifestLock.lock_shared(); - - if (m_manifest) - { - return GetPropertyFromManifestWithLock(property); - } - else - { - return GetPropertyFromIndex(property); - } - } - break; - case PackageVersionProperty::Version: - case PackageVersionProperty::Channel: - case PackageVersionProperty::ArpMinVersion: - case PackageVersionProperty::ArpMaxVersion: - { - // These properties can come from the manifest, version data, or the index. - // The index values are only for the latest version, but we should always already have the version data - // for any version that is not the latest. - auto sharedLock = m_versionAndManifestLock.lock_shared(); - - if (m_manifest) - { - return GetPropertyFromManifestWithLock(property); - } - else if (m_packageVersionData) - { - return GetPropertyFromVersionDataWithLock(property); - } - else - { - return GetPropertyFromIndex(property); - } - } - break; - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::vector GetMultiProperty(PackageVersionMultiProperty property) const override - { - switch (property) - { - case PackageVersionMultiProperty::Locale: - { - // These values can only come from the manifest. - EnsureManifest(); - return GetMultiPropertyFromManifest(property); - } - break; - case PackageVersionMultiProperty::PackageFamilyName: - case PackageVersionMultiProperty::ProductCode: - case PackageVersionMultiProperty::UpgradeCode: - case PackageVersionMultiProperty::Name: - case PackageVersionMultiProperty::Publisher: - case PackageVersionMultiProperty::Tag: - case PackageVersionMultiProperty::Command: - { - // These properties can come from the manifest or the index. - // The index values will be for all versions rather than this specific one though. - auto sharedLock = m_versionAndManifestLock.lock_shared(); - - if (m_manifest) - { - return GetMultiPropertyFromManifestWithLock(property); - } - else - { - return GetMultiPropertyFromIndex(property); - } - } - break; - default: - THROW_HR(E_UNEXPECTED); - } - } - - Manifest::Manifest GetManifest() override - { - EnsureManifest(); - auto sharedLock = m_versionAndManifestLock.lock_shared(); - return m_manifest.value(); - } - - Source GetSource() const override - { - return Source{ GetReferenceSource() }; - } - - IPackageVersion::Metadata GetMetadata() const override - { - return {}; - } - - private: - // Ensures that the package version data is present. - void EnsurePackageVersionData() const - { - { - auto sharedLock = m_versionAndManifestLock.lock_shared(); - if (m_packageVersionData) - { - return; - } - } - - auto exclusiveLock = m_versionAndManifestLock.lock_exclusive(); - if (m_packageVersionData) - { - return; - } - - Manifest::PackageVersionDataManifest packageVersionDataManifest = GetPackageVersionData(GetReferenceSource(), m_packageRowId, *m_packageVersionDataCache); - - for (const auto& versionData : packageVersionDataManifest.Versions()) - { - // We should only ever be looking for the latest version here. - if (!m_packageVersionData || m_packageVersionData->Version < versionData.Version) - { - m_packageVersionData = versionData; - } - } - } - - // Ensures that the manifest is present. - void EnsureManifest() const - { - { - auto sharedLock = m_versionAndManifestLock.lock_shared(); - if (m_manifest) - { - return; - } - } - - // We will need the package version data to get the manifest. - EnsurePackageVersionData(); - - auto exclusiveLock = m_versionAndManifestLock.lock_exclusive(); - if (m_manifest) - { - return; - } - - std::unique_ptr manifestStream = - m_manifestCache->GetFile(ConvertToUTF16(m_packageVersionData->ManifestRelativePath), SHA256::ConvertToBytes(m_packageVersionData->ManifestHash)); - m_manifest = Manifest::YamlParser::Create(ReadEntireStream(*manifestStream)); - m_manifest->ApplyLocale(); - } - - LocIndString GetPropertyFromIndex(PackageVersionProperty property) const - { - switch (property) - { - case PackageVersionProperty::Id: - case PackageVersionProperty::Name: - case PackageVersionProperty::Moniker: - case PackageVersionProperty::Version: - case PackageVersionProperty::ArpMinVersion: - case PackageVersionProperty::ArpMaxVersion: - { - // Values coming from the index will always be localized/independent. - std::optional optValue = GetReferenceSource()->GetIndex().GetPropertyByPrimaryId(m_packageRowId, property); - return LocIndString{ optValue ? optValue.value() : std::string{} }; - } - default: - return {}; - } - } - - LocIndString GetPropertyFromVersionData(PackageVersionProperty property) const - { - auto sharedLock = m_versionAndManifestLock.lock_shared(); - return GetPropertyFromVersionDataWithLock(property); - } - - LocIndString GetPropertyFromVersionDataWithLock(PackageVersionProperty property) const - { - std::string result; - - switch (property) - { - case PackageVersionProperty::RelativePath: - result = m_packageVersionData->ManifestRelativePath; - break; - case PackageVersionProperty::ManifestSHA256Hash: - result = m_packageVersionData->ManifestHash; - break; - case PackageVersionProperty::Version: - result = m_packageVersionData->Version.ToString(); - break; - case PackageVersionProperty::ArpMinVersion: - result = m_packageVersionData->ArpMinVersion.value_or(""); - break; - case PackageVersionProperty::ArpMaxVersion: - result = m_packageVersionData->ArpMaxVersion.value_or(""); - break; - } - - return LocIndString{ std::move(result) }; - } - - LocIndString GetPropertyFromManifest(PackageVersionProperty property) const - { - auto sharedLock = m_versionAndManifestLock.lock_shared(); - return GetPropertyFromManifestWithLock(property); - } - - LocIndString GetPropertyFromManifestWithLock(PackageVersionProperty property) const - { - std::string result; - - switch (property) - { - case PackageVersionProperty::Publisher: - result = m_manifest->CurrentLocalization.Get(); - break; - case PackageVersionProperty::Id: - result = m_manifest->Id; - break; - case PackageVersionProperty::Name: - result = m_manifest->CurrentLocalization.Get(); - break; - case PackageVersionProperty::Moniker: - result = m_manifest->Moniker; - break; - case PackageVersionProperty::Version: - result = m_manifest->Version; - break; - case PackageVersionProperty::Channel: - result = m_manifest->Channel; - break; - case PackageVersionProperty::ArpMinVersion: - { - auto versionRange = m_manifest->GetArpVersionRange(); - if (!versionRange.IsEmpty()) - { - result = versionRange.GetMinVersion().ToString(); - } - } - break; - case PackageVersionProperty::ArpMaxVersion: - { - auto versionRange = m_manifest->GetArpVersionRange(); - if (!versionRange.IsEmpty()) - { - result = versionRange.GetMaxVersion().ToString(); - } - } - break; - } - - return LocIndString{ std::move(result) }; - } - - std::vector GetMultiPropertyFromIndex(PackageVersionMultiProperty property) const - { - std::vector result; - - for (auto&& value : GetReferenceSource()->GetIndex().GetMultiPropertyByPrimaryId(m_packageRowId, property)) - { - // Values coming from the index will always be localized/independent. - result.emplace_back(std::move(value)); - } - - return result; - } - - std::vector GetMultiPropertyFromManifest(PackageVersionMultiProperty property) const - { - auto sharedLock = m_versionAndManifestLock.lock_shared(); - return GetMultiPropertyFromManifestWithLock(property); - } - - std::vector GetMultiPropertyFromManifestWithLock(PackageVersionMultiProperty property) const - { - std::vector intermediate; - - switch (property) - { - case PackageVersionMultiProperty::PackageFamilyName: - intermediate = m_manifest->GetPackageFamilyNames(); - break; - case PackageVersionMultiProperty::ProductCode: - intermediate = m_manifest->GetProductCodes(); - break; - case PackageVersionMultiProperty::UpgradeCode: - intermediate = m_manifest->GetUpgradeCodes(); - break; - case PackageVersionMultiProperty::Name: - intermediate = m_manifest->GetPackageNames(); - break; - case PackageVersionMultiProperty::Publisher: - intermediate = m_manifest->GetPublishers(); - break; - case PackageVersionMultiProperty::Locale: - for (const auto& localization : m_manifest->Localizations) - { - intermediate.emplace_back(localization.Locale); - } - break; - case PackageVersionMultiProperty::Tag: - intermediate = m_manifest->GetAggregatedTags(); - break; - case PackageVersionMultiProperty::Command: - intermediate = m_manifest->GetAggregatedCommands(); - break; - } - - std::vector result; - - for (auto&& value : intermediate) - { - // Values coming from the manifest will always be localized/independent. - result.emplace_back(std::move(value)); - } - - return result; - } - - SQLiteIndex::IdType m_packageRowId; - - mutable wil::srwlock m_versionAndManifestLock; - mutable std::optional m_packageVersionData; - mutable std::optional m_manifest; - - std::shared_ptr m_manifestCache; - std::shared_ptr m_packageVersionDataCache; - }; - - SQLitePackage::SQLitePackage( - const std::shared_ptr& source, - SQLiteIndex::IdType packageRowId, - const std::shared_ptr& manifestCache, - const std::shared_ptr& packageVersionDataCache, - bool isInstalled) : - SourceReference(source), - m_packageRowId(packageRowId), - m_manifestCache(manifestCache), - m_packageVersionDataCache(packageVersionDataCache), - m_isInstalled(isInstalled) - {} - - LocIndString SQLitePackage::GetProperty(PackageProperty property) const - { - std::optional result; - std::shared_ptr source = GetReferenceSource(); - - switch (property) - { - case PackageProperty::Id: - result = source->GetIndex().GetPropertyByPrimaryId(m_packageRowId, PackageVersionProperty::Id); - break; - case PackageProperty::Name: - result = source->GetIndex().GetPropertyByPrimaryId(m_packageRowId, PackageVersionProperty::Name); - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return LocIndString{ result ? std::move(result).value() : std::string{} }; - } - - std::vector SQLitePackage::GetMultiProperty(PackageMultiProperty property) const - { - std::vector result; - - for (auto&& value : GetReferenceSource()->GetIndex().GetMultiPropertyByPrimaryId(m_packageRowId, PackageMultiPropertyToPackageVersionMultiProperty(property))) - { - // Values coming from the index will always be localized/independent. - result.emplace_back(std::move(value)); - } - - return result; - } - - std::vector SQLitePackage::GetVersionKeys() const - { - std::shared_ptr source = GetReferenceSource(); - - { - auto sharedLock = m_versionKeysLock.lock_shared(); - - if (!m_versionKeys.empty()) - { - return m_versionKeys; - } - } - - EnsurePackageVersionData(source); - - auto sharedLock = m_versionKeysLock.lock_shared(); - return m_versionKeys; - } - - std::shared_ptr SQLitePackage::GetLatestVersion() const - { - std::shared_ptr source = GetReferenceSource(); - auto sharedLock = m_versionKeysLock.lock_shared(); - return std::make_shared(source, m_packageRowId, m_latestVersionData, m_manifestCache, m_packageVersionDataCache); - } - - std::shared_ptr SQLitePackage::GetVersion(const PackageVersionKey& versionKey) const - { - std::shared_ptr source = GetReferenceSource(); - - // Ensure that this key targets this (or any) source - if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) - { - return {}; - } - - std::optional versionData; - - // Check for a latest version request. - if (versionKey.IsDefaultLatest()) - { - auto sharedLock = m_versionKeysLock.lock_shared(); - return std::make_shared(source, m_packageRowId, m_latestVersionData, m_manifestCache, m_packageVersionDataCache); - } - - EnsurePackageVersionData(source); - - { - MapKey requested{ versionKey.Version, versionKey.Channel }; - auto sharedLock = m_versionKeysLock.lock_shared(); - - auto itr = m_versionKeysMap.find(requested); - if (itr != m_versionKeysMap.end()) - { - versionData = itr->second; - } - } - - if (versionData) - { - return std::make_shared(source, m_packageRowId, std::move(versionData), m_manifestCache, m_packageVersionDataCache); - } - - return {}; - } - - Source SQLitePackage::GetSource() const - { - return Source{ GetReferenceSource() }; - } - - bool SQLitePackage::IsSame(const IPackage* other) const - { - const SQLitePackage* otherSQLite = PackageCast(other); - - if (otherSQLite) - { - return GetReferenceSource()->IsSame(otherSQLite->GetReferenceSource().get()) && m_packageRowId == otherSQLite->m_packageRowId; - } - - return false; - } - - const void* SQLitePackage::CastTo(IPackageType type) const - { - if (type == PackageType) - { - return this; - } - - return nullptr; - } - - std::shared_ptr SQLitePackage::GetInstalled() - { - return m_isInstalled ? shared_from_this() : std::shared_ptr{}; - } - - std::vector> SQLitePackage::GetAvailable() - { - return m_isInstalled ? std::vector>{} : std::vector>{ shared_from_this() }; - } - - bool SQLitePackage::MapKey::operator<(const MapKey& other) const - { - if (Version < other.Version) - { - return true; - } - else if (Version == other.Version) - { - return Channel < other.Channel; - } - else - { - return false; - } - } - - // Ensures that we have the package version data present. - void SQLitePackage::EnsurePackageVersionData(const std::shared_ptr& source) const - { - { - auto sharedLock = m_versionKeysLock.lock_shared(); - - if (!m_versionKeys.empty()) - { - return; - } - } - - auto exclusiveLock = m_versionKeysLock.lock_exclusive(); - - if (!m_versionKeys.empty()) - { - return; - } - - Manifest::PackageVersionDataManifest packageVersionDataManifest = GetPackageVersionData(source, m_packageRowId, *m_packageVersionDataCache); - - for (const auto& versionData : packageVersionDataManifest.Versions()) - { - std::string version = versionData.Version.ToString(); - std::string channel; - m_versionKeys.emplace_back(source->GetIdentifier(), version, channel); - m_versionKeysMap.emplace(MapKey{ std::move(version), std::move(channel) }, versionData); - - if (!m_latestVersionData || m_latestVersionData->Version < versionData.Version) - { - m_latestVersionData = versionData; - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/SQLiteIndexSourceV2.h" +#include + +using namespace AppInstaller::Utility; + + +namespace AppInstaller::Repository::Microsoft::details::V2 +{ + // Get the relative path and hash for the package version data manifest. + std::pair CreatePackageVersionDataRelativePath(const std::shared_ptr& source, SQLiteIndex::IdType packageRowId) + { + const SQLiteIndex& index = source->GetIndex(); + + std::string identifier = index.GetPropertyByPrimaryId(packageRowId, PackageVersionProperty::Id).value(); + std::string hash = index.GetPropertyByPrimaryId(packageRowId, PackageVersionProperty::ManifestSHA256Hash).value(); + std::filesystem::path relativePath = Manifest::PackageVersionDataManifest::GetRelativeDirectoryPath(identifier, hash) / Manifest::PackageVersionDataManifest::VersionManifestCompressedFileName(); + + return std::make_pair(std::move(relativePath), std::move(hash)); + } + + // Gets package version data for the given package in the index. + Manifest::PackageVersionDataManifest GetPackageVersionData(const std::shared_ptr& source, SQLiteIndex::IdType packageRowId, const Caching::FileCache& fileCache) + { + auto pathAndHash = CreatePackageVersionDataRelativePath(source, packageRowId); + auto fileStream = fileCache.GetFile(pathAndHash.first, SHA256::ConvertToBytes(pathAndHash.second)); + auto fileBytes = ReadEntireStreamAsByteArray(*fileStream); + + Manifest::PackageVersionDataManifest result; + result.Deserialize(Manifest::PackageVersionDataManifest::CreateDecompressor().Decompress(fileBytes)); + + return result; + } + + // The IPackageVersion implementation for V2 index. + struct PackageVersion : public SourceReference, public IPackageVersion + { + PackageVersion( + const std::shared_ptr& source, + SQLiteIndex::IdType packageRowId, + std::optional packageVersionData, + const std::shared_ptr& manifestCache, + const std::shared_ptr& packageVersionDataCache) : + SourceReference(source), + m_packageRowId(packageRowId), + m_packageVersionData(std::move(packageVersionData)), + m_manifestCache(manifestCache), + m_packageVersionDataCache(packageVersionDataCache) + {} + + // Inherited via IPackageVersion + LocIndString GetProperty(PackageVersionProperty property) const override + { + switch (property) + { + case PackageVersionProperty::SourceIdentifier: + return LocIndString{ GetReferenceSource()->GetIdentifier() }; + case PackageVersionProperty::SourceName: + return LocIndString{ GetReferenceSource()->GetDetails().Name }; + case PackageVersionProperty::RelativePath: + case PackageVersionProperty::ManifestSHA256Hash: + { + // These values can only come from the version data. + EnsurePackageVersionData(); + return GetPropertyFromVersionData(property); + } + break; + case PackageVersionProperty::Publisher: + { + // These values can only come from the manifest. + EnsureManifest(); + return GetPropertyFromManifest(property); + } + break; + case PackageVersionProperty::Id: + case PackageVersionProperty::Name: + case PackageVersionProperty::Moniker: + { + // These properties can come from the manifest or the index. + // The index values will be for the latest version rather than this specific one though. + auto sharedLock = m_versionAndManifestLock.lock_shared(); + + if (m_manifest) + { + return GetPropertyFromManifestWithLock(property); + } + else + { + return GetPropertyFromIndex(property); + } + } + break; + case PackageVersionProperty::Version: + case PackageVersionProperty::Channel: + case PackageVersionProperty::ArpMinVersion: + case PackageVersionProperty::ArpMaxVersion: + { + // These properties can come from the manifest, version data, or the index. + // The index values are only for the latest version, but we should always already have the version data + // for any version that is not the latest. + auto sharedLock = m_versionAndManifestLock.lock_shared(); + + if (m_manifest) + { + return GetPropertyFromManifestWithLock(property); + } + else if (m_packageVersionData) + { + return GetPropertyFromVersionDataWithLock(property); + } + else + { + return GetPropertyFromIndex(property); + } + } + break; + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::vector GetMultiProperty(PackageVersionMultiProperty property) const override + { + switch (property) + { + case PackageVersionMultiProperty::Locale: + { + // These values can only come from the manifest. + EnsureManifest(); + return GetMultiPropertyFromManifest(property); + } + break; + case PackageVersionMultiProperty::PackageFamilyName: + case PackageVersionMultiProperty::ProductCode: + case PackageVersionMultiProperty::UpgradeCode: + case PackageVersionMultiProperty::Name: + case PackageVersionMultiProperty::Publisher: + case PackageVersionMultiProperty::Tag: + case PackageVersionMultiProperty::Command: + { + // These properties can come from the manifest or the index. + // The index values will be for all versions rather than this specific one though. + auto sharedLock = m_versionAndManifestLock.lock_shared(); + + if (m_manifest) + { + return GetMultiPropertyFromManifestWithLock(property); + } + else + { + return GetMultiPropertyFromIndex(property); + } + } + break; + default: + THROW_HR(E_UNEXPECTED); + } + } + + Manifest::Manifest GetManifest() override + { + EnsureManifest(); + auto sharedLock = m_versionAndManifestLock.lock_shared(); + return m_manifest.value(); + } + + Source GetSource() const override + { + return Source{ GetReferenceSource() }; + } + + IPackageVersion::Metadata GetMetadata() const override + { + return {}; + } + + private: + // Ensures that the package version data is present. + void EnsurePackageVersionData() const + { + { + auto sharedLock = m_versionAndManifestLock.lock_shared(); + if (m_packageVersionData) + { + return; + } + } + + auto exclusiveLock = m_versionAndManifestLock.lock_exclusive(); + if (m_packageVersionData) + { + return; + } + + Manifest::PackageVersionDataManifest packageVersionDataManifest = GetPackageVersionData(GetReferenceSource(), m_packageRowId, *m_packageVersionDataCache); + + for (const auto& versionData : packageVersionDataManifest.Versions()) + { + // We should only ever be looking for the latest version here. + if (!m_packageVersionData || m_packageVersionData->Version < versionData.Version) + { + m_packageVersionData = versionData; + } + } + } + + // Ensures that the manifest is present. + void EnsureManifest() const + { + { + auto sharedLock = m_versionAndManifestLock.lock_shared(); + if (m_manifest) + { + return; + } + } + + // We will need the package version data to get the manifest. + EnsurePackageVersionData(); + + auto exclusiveLock = m_versionAndManifestLock.lock_exclusive(); + if (m_manifest) + { + return; + } + + std::unique_ptr manifestStream = + m_manifestCache->GetFile(ConvertToUTF16(m_packageVersionData->ManifestRelativePath), SHA256::ConvertToBytes(m_packageVersionData->ManifestHash)); + m_manifest = Manifest::YamlParser::Create(ReadEntireStream(*manifestStream)); + m_manifest->ApplyLocale(); + } + + LocIndString GetPropertyFromIndex(PackageVersionProperty property) const + { + switch (property) + { + case PackageVersionProperty::Id: + case PackageVersionProperty::Name: + case PackageVersionProperty::Moniker: + case PackageVersionProperty::Version: + case PackageVersionProperty::ArpMinVersion: + case PackageVersionProperty::ArpMaxVersion: + { + // Values coming from the index will always be localized/independent. + std::optional optValue = GetReferenceSource()->GetIndex().GetPropertyByPrimaryId(m_packageRowId, property); + return LocIndString{ optValue ? optValue.value() : std::string{} }; + } + default: + return {}; + } + } + + LocIndString GetPropertyFromVersionData(PackageVersionProperty property) const + { + auto sharedLock = m_versionAndManifestLock.lock_shared(); + return GetPropertyFromVersionDataWithLock(property); + } + + LocIndString GetPropertyFromVersionDataWithLock(PackageVersionProperty property) const + { + std::string result; + + switch (property) + { + case PackageVersionProperty::RelativePath: + result = m_packageVersionData->ManifestRelativePath; + break; + case PackageVersionProperty::ManifestSHA256Hash: + result = m_packageVersionData->ManifestHash; + break; + case PackageVersionProperty::Version: + result = m_packageVersionData->Version.ToString(); + break; + case PackageVersionProperty::ArpMinVersion: + result = m_packageVersionData->ArpMinVersion.value_or(""); + break; + case PackageVersionProperty::ArpMaxVersion: + result = m_packageVersionData->ArpMaxVersion.value_or(""); + break; + } + + return LocIndString{ std::move(result) }; + } + + LocIndString GetPropertyFromManifest(PackageVersionProperty property) const + { + auto sharedLock = m_versionAndManifestLock.lock_shared(); + return GetPropertyFromManifestWithLock(property); + } + + LocIndString GetPropertyFromManifestWithLock(PackageVersionProperty property) const + { + std::string result; + + switch (property) + { + case PackageVersionProperty::Publisher: + result = m_manifest->CurrentLocalization.Get(); + break; + case PackageVersionProperty::Id: + result = m_manifest->Id; + break; + case PackageVersionProperty::Name: + result = m_manifest->CurrentLocalization.Get(); + break; + case PackageVersionProperty::Moniker: + result = m_manifest->Moniker; + break; + case PackageVersionProperty::Version: + result = m_manifest->Version; + break; + case PackageVersionProperty::Channel: + result = m_manifest->Channel; + break; + case PackageVersionProperty::ArpMinVersion: + { + auto versionRange = m_manifest->GetArpVersionRange(); + if (!versionRange.IsEmpty()) + { + result = versionRange.GetMinVersion().ToString(); + } + } + break; + case PackageVersionProperty::ArpMaxVersion: + { + auto versionRange = m_manifest->GetArpVersionRange(); + if (!versionRange.IsEmpty()) + { + result = versionRange.GetMaxVersion().ToString(); + } + } + break; + } + + return LocIndString{ std::move(result) }; + } + + std::vector GetMultiPropertyFromIndex(PackageVersionMultiProperty property) const + { + std::vector result; + + for (auto&& value : GetReferenceSource()->GetIndex().GetMultiPropertyByPrimaryId(m_packageRowId, property)) + { + // Values coming from the index will always be localized/independent. + result.emplace_back(std::move(value)); + } + + return result; + } + + std::vector GetMultiPropertyFromManifest(PackageVersionMultiProperty property) const + { + auto sharedLock = m_versionAndManifestLock.lock_shared(); + return GetMultiPropertyFromManifestWithLock(property); + } + + std::vector GetMultiPropertyFromManifestWithLock(PackageVersionMultiProperty property) const + { + std::vector intermediate; + + switch (property) + { + case PackageVersionMultiProperty::PackageFamilyName: + intermediate = m_manifest->GetPackageFamilyNames(); + break; + case PackageVersionMultiProperty::ProductCode: + intermediate = m_manifest->GetProductCodes(); + break; + case PackageVersionMultiProperty::UpgradeCode: + intermediate = m_manifest->GetUpgradeCodes(); + break; + case PackageVersionMultiProperty::Name: + intermediate = m_manifest->GetPackageNames(); + break; + case PackageVersionMultiProperty::Publisher: + intermediate = m_manifest->GetPublishers(); + break; + case PackageVersionMultiProperty::Locale: + for (const auto& localization : m_manifest->Localizations) + { + intermediate.emplace_back(localization.Locale); + } + break; + case PackageVersionMultiProperty::Tag: + intermediate = m_manifest->GetAggregatedTags(); + break; + case PackageVersionMultiProperty::Command: + intermediate = m_manifest->GetAggregatedCommands(); + break; + } + + std::vector result; + + for (auto&& value : intermediate) + { + // Values coming from the manifest will always be localized/independent. + result.emplace_back(std::move(value)); + } + + return result; + } + + SQLiteIndex::IdType m_packageRowId; + + mutable wil::srwlock m_versionAndManifestLock; + mutable std::optional m_packageVersionData; + mutable std::optional m_manifest; + + std::shared_ptr m_manifestCache; + std::shared_ptr m_packageVersionDataCache; + }; + + SQLitePackage::SQLitePackage( + const std::shared_ptr& source, + SQLiteIndex::IdType packageRowId, + const std::shared_ptr& manifestCache, + const std::shared_ptr& packageVersionDataCache, + bool isInstalled) : + SourceReference(source), + m_packageRowId(packageRowId), + m_manifestCache(manifestCache), + m_packageVersionDataCache(packageVersionDataCache), + m_isInstalled(isInstalled) + {} + + LocIndString SQLitePackage::GetProperty(PackageProperty property) const + { + std::optional result; + std::shared_ptr source = GetReferenceSource(); + + switch (property) + { + case PackageProperty::Id: + result = source->GetIndex().GetPropertyByPrimaryId(m_packageRowId, PackageVersionProperty::Id); + break; + case PackageProperty::Name: + result = source->GetIndex().GetPropertyByPrimaryId(m_packageRowId, PackageVersionProperty::Name); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return LocIndString{ result ? std::move(result).value() : std::string{} }; + } + + std::vector SQLitePackage::GetMultiProperty(PackageMultiProperty property) const + { + std::vector result; + + for (auto&& value : GetReferenceSource()->GetIndex().GetMultiPropertyByPrimaryId(m_packageRowId, PackageMultiPropertyToPackageVersionMultiProperty(property))) + { + // Values coming from the index will always be localized/independent. + result.emplace_back(std::move(value)); + } + + return result; + } + + std::vector SQLitePackage::GetVersionKeys() const + { + std::shared_ptr source = GetReferenceSource(); + + { + auto sharedLock = m_versionKeysLock.lock_shared(); + + if (!m_versionKeys.empty()) + { + return m_versionKeys; + } + } + + EnsurePackageVersionData(source); + + auto sharedLock = m_versionKeysLock.lock_shared(); + return m_versionKeys; + } + + std::shared_ptr SQLitePackage::GetLatestVersion() const + { + std::shared_ptr source = GetReferenceSource(); + auto sharedLock = m_versionKeysLock.lock_shared(); + return std::make_shared(source, m_packageRowId, m_latestVersionData, m_manifestCache, m_packageVersionDataCache); + } + + std::shared_ptr SQLitePackage::GetVersion(const PackageVersionKey& versionKey) const + { + std::shared_ptr source = GetReferenceSource(); + + // Ensure that this key targets this (or any) source + if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) + { + return {}; + } + + std::optional versionData; + + // Check for a latest version request. + if (versionKey.IsDefaultLatest()) + { + auto sharedLock = m_versionKeysLock.lock_shared(); + return std::make_shared(source, m_packageRowId, m_latestVersionData, m_manifestCache, m_packageVersionDataCache); + } + + EnsurePackageVersionData(source); + + { + MapKey requested{ versionKey.Version, versionKey.Channel }; + auto sharedLock = m_versionKeysLock.lock_shared(); + + auto itr = m_versionKeysMap.find(requested); + if (itr != m_versionKeysMap.end()) + { + versionData = itr->second; + } + } + + if (versionData) + { + return std::make_shared(source, m_packageRowId, std::move(versionData), m_manifestCache, m_packageVersionDataCache); + } + + return {}; + } + + Source SQLitePackage::GetSource() const + { + return Source{ GetReferenceSource() }; + } + + bool SQLitePackage::IsSame(const IPackage* other) const + { + const SQLitePackage* otherSQLite = PackageCast(other); + + if (otherSQLite) + { + return GetReferenceSource()->IsSame(otherSQLite->GetReferenceSource().get()) && m_packageRowId == otherSQLite->m_packageRowId; + } + + return false; + } + + const void* SQLitePackage::CastTo(IPackageType type) const + { + if (type == PackageType) + { + return this; + } + + return nullptr; + } + + std::shared_ptr SQLitePackage::GetInstalled() + { + return m_isInstalled ? shared_from_this() : std::shared_ptr{}; + } + + std::vector> SQLitePackage::GetAvailable() + { + return m_isInstalled ? std::vector>{} : std::vector>{ shared_from_this() }; + } + + bool SQLitePackage::MapKey::operator<(const MapKey& other) const + { + if (Version < other.Version) + { + return true; + } + else if (Version == other.Version) + { + return Channel < other.Channel; + } + else + { + return false; + } + } + + // Ensures that we have the package version data present. + void SQLitePackage::EnsurePackageVersionData(const std::shared_ptr& source) const + { + { + auto sharedLock = m_versionKeysLock.lock_shared(); + + if (!m_versionKeys.empty()) + { + return; + } + } + + auto exclusiveLock = m_versionKeysLock.lock_exclusive(); + + if (!m_versionKeys.empty()) + { + return; + } + + Manifest::PackageVersionDataManifest packageVersionDataManifest = GetPackageVersionData(source, m_packageRowId, *m_packageVersionDataCache); + + for (const auto& versionData : packageVersionDataManifest.Versions()) + { + std::string version = versionData.Version.ToString(); + std::string channel; + m_versionKeys.emplace_back(source->GetIdentifier(), version, channel); + m_versionKeysMap.emplace(MapKey{ std::move(version), std::move(channel) }, versionData); + + if (!m_latestVersionData || m_latestVersionData->Version < versionData.Version) + { + m_latestVersionData = versionData; + } + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.h b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.h index 9b964155d4..45e2e4fff8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.h +++ b/src/AppInstallerRepositoryCore/Microsoft/SQLiteIndexSourceV2.h @@ -1,68 +1,68 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/SQLiteIndexSource.h" -#include - - -namespace AppInstaller::Repository::Microsoft::details::V2 -{ - // The IPackage implementation for V2 index. - struct SQLitePackage : public std::enable_shared_from_this, public SourceReference, public IPackage, public ICompositePackage - { - static constexpr IPackageType PackageType = IPackageType::SQLitePackage2; - - SQLitePackage( - const std::shared_ptr& source, - SQLiteIndex::IdType packageRowId, - const std::shared_ptr& manifestCache, - const std::shared_ptr& packageVersionDataCache, - bool isInstalled); - - // Inherited via IPackage - Utility::LocIndString GetProperty(PackageProperty property) const; - - std::vector GetMultiProperty(PackageMultiProperty property) const override; - - std::vector GetVersionKeys() const override; - - std::shared_ptr GetLatestVersion() const override; - - std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override; - - Source GetSource() const override; - - bool IsSame(const IPackage* other) const override; - - const void* CastTo(IPackageType type) const override; - - // Inherited via ICompositePackage - std::shared_ptr GetInstalled() override; - - std::vector> GetAvailable() override; - - private: - // Contains the information needed to map a version key to it's rows. - struct MapKey - { - Utility::Version Version; - Utility::NormalizedString Channel; - - bool operator<(const MapKey& other) const; - }; - - // Ensures that we have the package version data present. - void EnsurePackageVersionData(const std::shared_ptr& source) const; - - SQLiteIndex::IdType m_packageRowId; - std::shared_ptr m_manifestCache; - std::shared_ptr m_packageVersionDataCache; - bool m_isInstalled; - - // To avoid removing const from the interface - mutable wil::srwlock m_versionKeysLock; - mutable std::vector m_versionKeys; - mutable std::map m_versionKeysMap; - mutable std::optional m_latestVersionData; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/SQLiteIndexSource.h" +#include + + +namespace AppInstaller::Repository::Microsoft::details::V2 +{ + // The IPackage implementation for V2 index. + struct SQLitePackage : public std::enable_shared_from_this, public SourceReference, public IPackage, public ICompositePackage + { + static constexpr IPackageType PackageType = IPackageType::SQLitePackage2; + + SQLitePackage( + const std::shared_ptr& source, + SQLiteIndex::IdType packageRowId, + const std::shared_ptr& manifestCache, + const std::shared_ptr& packageVersionDataCache, + bool isInstalled); + + // Inherited via IPackage + Utility::LocIndString GetProperty(PackageProperty property) const; + + std::vector GetMultiProperty(PackageMultiProperty property) const override; + + std::vector GetVersionKeys() const override; + + std::shared_ptr GetLatestVersion() const override; + + std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override; + + Source GetSource() const override; + + bool IsSame(const IPackage* other) const override; + + const void* CastTo(IPackageType type) const override; + + // Inherited via ICompositePackage + std::shared_ptr GetInstalled() override; + + std::vector> GetAvailable() override; + + private: + // Contains the information needed to map a version key to it's rows. + struct MapKey + { + Utility::Version Version; + Utility::NormalizedString Channel; + + bool operator<(const MapKey& other) const; + }; + + // Ensures that we have the package version data present. + void EnsurePackageVersionData(const std::shared_ptr& source) const; + + SQLiteIndex::IdType m_packageRowId; + std::shared_ptr m_manifestCache; + std::shared_ptr m_packageVersionDataCache; + bool m_isInstalled; + + // To avoid removing const from the interface + mutable wil::srwlock m_versionKeysLock; + mutable std::vector m_versionKeys; + mutable std::map m_versionKeysMap; + mutable std::optional m_latestVersionData; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h index e3b59f5e52..e0718e6a46 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ChannelTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToOneTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct ChannelTableInfo - { - inline static constexpr std::string_view TableName() { return "channels"sv; } - inline static constexpr std::string_view ValueName() { return "channel"sv; } - }; - } - - // The table for Channel. - using ChannelTable = OneToOneTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToOneTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct ChannelTableInfo + { + inline static constexpr std::string_view TableName() { return "channels"sv; } + inline static constexpr std::string_view ValueName() { return "channel"sv; } + }; + } + + // The table for Channel. + using ChannelTable = OneToOneTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/CommandsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/CommandsTable.h index 76cf8b77a9..04073b3197 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/CommandsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/CommandsTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct CommandsTableInfo - { - inline static constexpr std::string_view TableName() { return "commands"sv; } - inline static constexpr std::string_view ValueName() { return "command"sv; } - }; - } - - // The table for Commands. - using CommandsTable = OneToManyTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct CommandsTableInfo + { + inline static constexpr std::string_view TableName() { return "commands"sv; } + inline static constexpr std::string_view ValueName() { return "command"sv; } + }; + } + + // The table for Commands. + using CommandsTable = OneToManyTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/IdTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/IdTable.h index 1c72416ea0..3775ced440 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/IdTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/IdTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToOneTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct IdTableInfo - { - inline static constexpr std::string_view TableName() { return "ids"sv; } - inline static constexpr std::string_view ValueName() { return "id"sv; } - }; - } - - // The table for Id. - using IdTable = OneToOneTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToOneTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct IdTableInfo + { + inline static constexpr std::string_view TableName() { return "ids"sv; } + inline static constexpr std::string_view ValueName() { return "id"sv; } + }; + } + + // The table for Id. + using IdTable = OneToOneTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.h index 8f23e5d3fe..ee6db71205 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface.h @@ -1,69 +1,69 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Microsoft/Schema/1_0/SearchResultsTable.h" -#include "Microsoft/Schema/1_0/OneToManyTable.h" - -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - // Interface to this schema version exposed through ISQLiteIndex. - struct Interface : public ISQLiteIndex - { - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection, CreateOptions options) override; - SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - SQLite::rowid_t RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) override; - void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; - void PrepareForPackaging(SQLite::Connection& connection) override; - bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; - SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const override; - std::optional GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const override; - std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; - std::optional GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const override; - std::optional GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const override; - std::vector GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const override; - - // Version 1.1 - MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; - void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; - - // Version 1.2 - Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const override; - - // Version 1.4 Get all the dependencies for a specific manifest. - std::set> GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const override; - std::vector> GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const override; - - // Version 1.7 - void DropTables(SQLite::Connection& connection) override; - - // Version 2.0 - bool MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) override; - - protected: - virtual bool NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const; - - // Creates the search results table. - virtual std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const; - - // Executes all relevant searches for the query. - virtual void PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const; - - // Gets a property already knowing that the manifest id is valid. - virtual std::optional GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const; - - // Gets the one to many table schema to use. - virtual OneToManyTableSchema GetOneToManyTableSchema() const; - - // Force the database to shrink the file size. - // This *must* be done outside of an active transaction. - void Vacuum(const SQLite::Connection& connection); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Microsoft/Schema/1_0/SearchResultsTable.h" +#include "Microsoft/Schema/1_0/OneToManyTable.h" + +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + // Interface to this schema version exposed through ISQLiteIndex. + struct Interface : public ISQLiteIndex + { + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection, CreateOptions options) override; + SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + SQLite::rowid_t RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) override; + void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; + void PrepareForPackaging(SQLite::Connection& connection) override; + bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; + SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const override; + std::optional GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const override; + std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; + std::optional GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const override; + std::optional GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const override; + std::vector GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const override; + + // Version 1.1 + MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; + void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; + + // Version 1.2 + Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const override; + + // Version 1.4 Get all the dependencies for a specific manifest. + std::set> GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const override; + std::vector> GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const override; + + // Version 1.7 + void DropTables(SQLite::Connection& connection) override; + + // Version 2.0 + bool MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) override; + + protected: + virtual bool NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const; + + // Creates the search results table. + virtual std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const; + + // Executes all relevant searches for the query. + virtual void PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const; + + // Gets a property already knowing that the manifest id is valid. + virtual std::optional GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const; + + // Gets the one to many table schema to use. + virtual OneToManyTableSchema GetOneToManyTableSchema() const; + + // Force the database to shrink the file size. + // This *must* be done outside of an active transaction. + void Vacuum(const SQLite::Connection& connection); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp index fc92c0e9d9..c8f3d7707a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/Interface_1_0.cpp @@ -1,668 +1,668 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_0/Interface.h" - -#include "Microsoft/Schema/1_0/IdTable.h" -#include "Microsoft/Schema/1_0/NameTable.h" -#include "Microsoft/Schema/1_0/MonikerTable.h" -#include "Microsoft/Schema/1_0/VersionTable.h" -#include "Microsoft/Schema/1_0/ChannelTable.h" - -#include "Microsoft/Schema/1_0/PathPartTable.h" - -#include "Microsoft/Schema/1_0/ManifestTable.h" - -#include "Microsoft/Schema/1_0/TagsTable.h" -#include "Microsoft/Schema/1_0/CommandsTable.h" - -#include "Microsoft/Schema/1_0/SearchResultsTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace - { - // Gets an existing manifest by its rowid., if it exists. - std::optional GetExistingManifestId(const SQLite::Connection& connection, const Manifest::Manifest& manifest) - { - std::optional idId = IdTable::SelectIdByValue(connection, manifest.Id, true); - if (!idId) - { - AICLI_LOG(Repo, Verbose, << "Did not find an Id { " << manifest.Id << " }"); - return {}; - } - - std::optional versionId = VersionTable::SelectIdByValue(connection, manifest.Version, true); - if (!versionId) - { - AICLI_LOG(Repo, Verbose, << "Did not find a Version { " << manifest.Version << " }"); - return {}; - } - - std::optional channelId = ChannelTable::SelectIdByValue(connection, manifest.Channel, true); - if (!channelId) - { - AICLI_LOG(Repo, Verbose, << "Did not find a Channel { " << manifest.Channel << " }"); - return {}; - } - - auto result = ManifestTable::SelectByValueIds(connection, { idId.value(), versionId.value(), channelId.value() }); - - if (!result) - { - AICLI_LOG(Repo, Verbose, << "Did not find a manifest row for { " << manifest.Id << ", " << manifest.Version << ", " << manifest.Channel << " }"); - } - - return result; - } - - // Gets a manifest id by the given key values. - std::optional StaticGetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version = "", std::string_view channel = "") - { - std::optional channelIdOpt = ChannelTable::SelectIdByValue(connection, channel, true); - if (!channelIdOpt && !channel.empty()) - { - AICLI_LOG(Repo, Info, << "Did not find a Channel { " << channel << " }"); - return {}; - } - - std::optional versionIdOpt; - std::vector> versionStrings; - - if (channelIdOpt) - { - versionStrings = ManifestTable::GetAllValuesByIds(connection, { id, channelIdOpt.value() }); - } - else - { - versionStrings = ManifestTable::GetAllValuesByIds(connection, { id }); - } - - if (versionStrings.empty()) - { - AICLI_LOG(Repo, Info, << "Did not find any Versions { " << id << ", " << channel << " }"); - return {}; - } - - // Convert the strings to Versions and sort them - struct VersionAndRow - { - SQLite::rowid_t Row = 0; - Utility::Version Version; - - bool operator<(const VersionAndRow& other) const { return Version < other.Version; } - }; - - std::vector versions; - for (auto& v : versionStrings) - { - versions.emplace_back(VersionAndRow{ v.first, std::move(v.second) }); - } - - std::sort(versions.begin(), versions.end()); - - if (version.empty()) - { - // Get the last version in the list (the highest version) - versionIdOpt = versions.back().Row; - } - else - { - VersionAndRow requested; - requested.Version = Utility::Version{ std::string(version) }; - - auto itr = std::lower_bound(versions.begin(), versions.end(), requested); - if (itr != versions.end() && itr->Version == requested.Version) - { - versionIdOpt = itr->Row; - } - } - - if (!versionIdOpt) - { - AICLI_LOG(Repo, Info, << "Did not find a Version for { " << version << " }"); - return {}; - } - - if (channelIdOpt) - { - return ManifestTable::SelectByValueIds(connection, { id, versionIdOpt.value(), channelIdOpt.value() }); - } - else - { - return ManifestTable::SelectByValueIds(connection, { id, versionIdOpt.value() }); - } - } - - bool NotNeededInternal(const SQLite::Connection& connection, std::string_view, std::string_view valueName, SQLite::rowid_t id) - { - return !ManifestTable::IsValueReferenced(connection, valueName, id); - } - - // Updates the manifest column and related table based on the given value. - template - void UpdateManifestValueById(SQLite::Connection& connection, const typename Table::value_t& value, SQLite::rowid_t manifestId, bool overwriteLikeMatch = false) - { - auto [oldValueId] = ManifestTable::GetIdsById(connection, manifestId); - - SQLite::rowid_t newValueId = Table::EnsureExists(connection, value, overwriteLikeMatch); - - ManifestTable::UpdateValueIdById
(connection, manifestId, newValueId); - - if (NotNeededInternal(connection, Table::TableName(), Table::ValueName(), oldValueId)) - { - Table::DeleteById(connection, oldValueId); - } - } - } - - SQLite::Version Interface::GetVersion() const - { - return { 1, 0 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_0"); - - IdTable::Create_deprecated(connection); - NameTable::Create_deprecated(connection); - MonikerTable::Create_deprecated(connection); - VersionTable::Create_deprecated(connection); - ChannelTable::Create_deprecated(connection); - - PathPartTable::Create_deprecated(connection); - - ManifestTable::Create_deprecated(connection, { - { IdTable::ValueName(), true, false }, - { NameTable::ValueName(), false, false }, - { MonikerTable::ValueName(), false, false }, - { VersionTable::ValueName(), true, false }, - { ChannelTable::ValueName(), true, false }, - { PathPartTable::ValueName(), false, WI_IsFlagClear(options, CreateOptions::SupportPathless) } - }); - - TagsTable::Create(connection, GetOneToManyTableSchema()); - CommandsTable::Create(connection, GetOneToManyTableSchema()); - - savepoint.Commit(); - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - auto manifestResult = GetExistingManifestId(connection, manifest); - - // If this manifest is already present, we can't add it. - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), manifestResult.has_value()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_0"); - - auto [pathAdded, pathLeafId] = PathPartTable::EnsurePathExists(connection, relativePath, true); - - // If we get false from the function, this manifest path already exists in the index. - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), relativePath && !pathAdded); - - // Ensure that all of the 1:1 data exists. - SQLite::rowid_t idId = IdTable::EnsureExists(connection, manifest.Id, true); - SQLite::rowid_t nameId = NameTable::EnsureExists(connection, manifest.DefaultLocalization.Get()); - SQLite::rowid_t monikerId = MonikerTable::EnsureExists(connection, manifest.Moniker); - SQLite::rowid_t versionId = VersionTable::EnsureExists(connection, manifest.Version); - SQLite::rowid_t channelId = ChannelTable::EnsureExists(connection, manifest.Channel); - - // Insert the manifest entry. - SQLite::rowid_t manifestId = ManifestTable::Insert(connection, { - { IdTable::ValueName(), idId}, - { NameTable::ValueName(), nameId }, - { MonikerTable::ValueName(), monikerId }, - { VersionTable::ValueName(), versionId }, - { ChannelTable::ValueName(), channelId }, - { PathPartTable::ValueName(), pathLeafId } - }); - - // Add all of the 1:N data. - TagsTable::EnsureExistsAndInsert(connection, manifest.GetAggregatedTags(), manifestId); - CommandsTable::EnsureExistsAndInsert(connection, manifest.GetAggregatedCommands(), manifestId); - - savepoint.Commit(); - - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - auto manifestResult = GetExistingManifestId(connection, manifest); - - // If the manifest doesn't actually exist, fail the update. - THROW_HR_IF(E_NOT_SET, !manifestResult); - - SQLite::rowid_t manifestId = manifestResult.value(); - - auto [idInIndex, nameInIndex, monikerInIndex, versionInIndex, channelInIndex] = - ManifestTable::GetValuesById(connection, manifestId); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_0"); - bool indexModified = false; - - // Id, Version, and Channel may have changed casing. If so, they too need to be updated. - if (idInIndex != manifest.Id) - { - UpdateManifestValueById(connection, manifest.Id, manifestId, true); - indexModified = true; - } - - if (versionInIndex != manifest.Version) - { - UpdateManifestValueById(connection, manifest.Version, manifestId); - indexModified = true; - } - - if (channelInIndex != manifest.Channel) - { - UpdateManifestValueById(connection, manifest.Channel, manifestId); - indexModified = true; - } - - auto packageName = manifest.DefaultLocalization.Get(); - if (nameInIndex != packageName) - { - UpdateManifestValueById(connection, packageName, manifestId); - indexModified = true; - } - - if (monikerInIndex != manifest.Moniker) - { - UpdateManifestValueById(connection, manifest.Moniker, manifestId); - indexModified = true; - } - - // Update path table if necessary - auto [existingPathLeafId] = ManifestTable::GetIdsById(connection, manifestId); - auto [pathAdded, newPathLeafId] = PathPartTable::EnsurePathExists(connection, relativePath, true); - - if (relativePath && pathAdded) - { - // Path was added, so we need to update the manifest table and delete the old path - ManifestTable::UpdateValueIdById(connection, manifestId, newPathLeafId); - PathPartTable::RemovePathById(connection, existingPathLeafId); - indexModified = true; - } - else - { - // The path already existed, so it must either match the existing manifest path or it is an error - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPathLeafId != newPathLeafId); - } - - // Update all 1:N tables as necessary - indexModified = TagsTable::UpdateIfNeededByManifestId(connection, manifest.GetAggregatedTags(), manifestId) || indexModified; - indexModified = CommandsTable::UpdateIfNeededByManifestId(connection, manifest.GetAggregatedCommands(), manifestId) || indexModified; - - savepoint.Commit(); - - return { indexModified, manifestId }; - } - - SQLite::rowid_t Interface::RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) - { - auto manifestResult = GetExistingManifestId(connection, manifest); - - // If the manifest doesn't actually exist, fail the remove. - THROW_HR_IF(E_NOT_SET, !manifestResult); - - RemoveManifestById(connection, manifestResult.value()); - - return manifestResult.value(); - } - - void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - // Get the ids of the values from the manifest table - auto [idId, nameId, monikerId, versionId, channelId, pathLeafId] = - ManifestTable::GetIdsById(connection, manifestId); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_0"); - - // Remove the manifest row - ManifestTable::DeleteById(connection, manifestId); - - // Remove all of the 1:1 data that is no longer referenced. - if (NotNeeded(connection, IdTable::TableName(), IdTable::ValueName(), idId)) - { - IdTable::DeleteById(connection, idId); - } - - if (NotNeeded(connection, NameTable::TableName(), NameTable::ValueName(), nameId)) - { - NameTable::DeleteById(connection, nameId); - } - - if (NotNeeded(connection, MonikerTable::TableName(), MonikerTable::ValueName(), monikerId)) - { - MonikerTable::DeleteById(connection, monikerId); - } - - if (NotNeeded(connection, VersionTable::TableName(), VersionTable::ValueName(), versionId)) - { - VersionTable::DeleteById(connection, versionId); - } - - if (NotNeeded(connection, ChannelTable::TableName(), ChannelTable::ValueName(), channelId)) - { - ChannelTable::DeleteById(connection, channelId); - } - - // Remove the path - PathPartTable::RemovePathById(connection, pathLeafId); - - // Remove all of the 1:N data that is no longer referenced. - TagsTable::DeleteIfNotNeededByManifestId(connection, manifestId); - CommandsTable::DeleteIfNotNeededByManifestId(connection, manifestId); - - savepoint.Commit(); - } - - bool Interface::NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const - { - return NotNeededInternal(connection, tableName, valueName, id); - } - - void Interface::PrepareForPackaging(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_0"); - - IdTable::PrepareForPackaging_deprecated(connection); - NameTable::PrepareForPackaging_deprecated(connection); - MonikerTable::PrepareForPackaging_deprecated(connection); - VersionTable::PrepareForPackaging_deprecated(connection); - ChannelTable::PrepareForPackaging_deprecated(connection); - - PathPartTable::PrepareForPackaging_deprecated(connection); - - ManifestTable::PrepareForPackaging_deprecated(connection, { - VersionTable::ValueName(), - ChannelTable::ValueName(), - PathPartTable::ValueName(), - }); - - TagsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); - CommandsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); - - savepoint.Commit(); - - Vacuum(connection); - } - - bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const - { - bool result = true; - -#define AICLI_CHECK_CONSISTENCY(_check_) \ - if (result || log) \ - { \ - result = _check_ && result; \ - } - - // Check the manifest table references to it's 1:1 tables - AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); - - // Check the 1:1 tables' consistency - AICLI_CHECK_CONSISTENCY(IdTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(NameTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(MonikerTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(VersionTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(ChannelTable::CheckConsistency(connection, log)); - - // Check the pathparts table for consistency - AICLI_CHECK_CONSISTENCY(PathPartTable::CheckConsistency(connection, log)); - - // Check the 1:N map tables for consistency - AICLI_CHECK_CONSISTENCY(TagsTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(CommandsTable::CheckConsistency(connection, log)); - -#undef AICLI_CHECK_CONSISTENCY - - return result; - } - - ISQLiteIndex::SearchResult Interface::Search(const SQLite::Connection& connection, const SearchRequest& request) const - { - if (request.IsForEverything()) - { - std::vector ids = IdTable::GetAllRowIds(connection, request.MaximumResults); - - SearchResult result; - for (SQLite::rowid_t id : ids) - { - result.Matches.emplace_back(std::make_pair(id, PackageMatchFilter(PackageMatchField::Id, MatchType::Wildcard))); - } - - result.Truncated = (request.MaximumResults && IdTable::GetCount(connection) > request.MaximumResults); - - return result; - } - - // First phase, create the search results table and populate it with the initial results. - // If the Query is provided, we search across many fields and put results in together. - // If Inclusions has fields, we add these to the data. - // If neither is defined, we take the first filter and use it as the initial results search. - std::unique_ptr resultsTable = CreateSearchResultsTable(connection); - bool inclusionsAttempted = false; - - if (request.Query) - { - // Perform searches across multiple tables to populate the initial results. - PerformQuerySearch(*resultsTable.get(), request.Query.value()); - - inclusionsAttempted = true; - } - - if (!request.Inclusions.empty()) - { - for (auto include : request.Inclusions) - { - for (MatchType match : GetDefaultMatchTypeOrder(include.Type)) - { - include.Type = match; - resultsTable->SearchOnField(include); - } - } - - inclusionsAttempted = true; - } - - size_t filterIndex = 0; - if (!inclusionsAttempted) - { - THROW_HR_IF(E_UNEXPECTED, request.Filters.empty()); - - // Perform search for just the field matching the first filter - PackageMatchFilter filter = request.Filters[0]; - - for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) - { - filter.Type = match; - resultsTable->SearchOnField(filter); - } - - // Skip the filter as we already know everything matches - filterIndex = 1; - } - - // Remove any duplicate manifest entries - resultsTable->RemoveDuplicateManifestRows(); - - // Second phase, for remaining filters, flag matching search results, then remove unflagged values. - for (size_t i = filterIndex; i < request.Filters.size(); ++i) - { - PackageMatchFilter filter = request.Filters[i]; - - resultsTable->PrepareToFilter(); - - for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) - { - filter.Type = match; - resultsTable->FilterOnField(filter); - } - - resultsTable->CompleteFilter(); - } - - return resultsTable->GetSearchResults(request.MaximumResults); - } - - std::optional Interface::GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const - { - return GetPropertyByManifestIdInternal(connection, primaryId, property); - } - - std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const - { - switch (property) - { - case PackageVersionMultiProperty::Tag: - return TagsTable::GetValuesByManifestId(connection, primaryId); - case PackageVersionMultiProperty::Command: - return CommandsTable::GetValuesByManifestId(connection, primaryId); - default: - return {}; - } - } - - std::optional Interface::GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const - { - return StaticGetManifestIdByKey(connection, id, version, channel); - } - - std::optional Interface::GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const - { - return GetExistingManifestId(connection, manifest); - } - - std::set> Interface::GetDependenciesByManifestRowId(const SQLite::Connection&, SQLite::rowid_t) const - { - return {}; - } - - std::vector> Interface::GetDependentsById(const SQLite::Connection&, AppInstaller::Manifest::string_t) const - { - return {}; - } - - void Interface::DropTables(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_0"); - - IdTable::Drop(connection); - NameTable::Drop(connection); - MonikerTable::Drop(connection); - VersionTable::Drop(connection); - ChannelTable::Drop(connection); - - PathPartTable::Drop(connection); - - ManifestTable::Drop(connection); - - TagsTable::Drop(connection); - CommandsTable::Drop(connection); - - savepoint.Commit(); - } - - bool Interface::MigrateFrom(SQLite::Connection&, const ISQLiteIndex*) - { - return false; - } - - std::vector Interface::GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const - { - auto versionsAndChannels = ManifestTable::GetAllValuesById(connection, id); - - std::vector result; - result.reserve(versionsAndChannels.size()); - for (auto&& vac : versionsAndChannels) - { - result.emplace_back(ISQLiteIndex::VersionKey{ Utility::VersionAndChannel{ Utility::Version{ std::move(std::get<1>(vac)) }, Utility::Channel{ std::move(std::get<2>(vac)) } }, std::get<0>(vac) }); - } - - std::sort(result.begin(), result.end()); - - return result; - } - - ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection&, SQLite::rowid_t) const - { - return {}; - } - - void Interface::SetMetadataByManifestId(SQLite::Connection&, SQLite::rowid_t, PackageVersionMetadata, std::string_view) - { - } - - Utility::NormalizedName Interface::NormalizeName(std::string_view name, std::string_view publisher) const - { - Utility::NormalizedName result; - result.Name(name); - result.Publisher(publisher); - return result; - } - - std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const - { - return std::make_unique(connection); - } - - void Interface::PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const - { - // Arbitrary values to create a reusable filter with the given value. - PackageMatchFilter filter(PackageMatchField::Id, MatchType::Exact, query.Value); - - for (MatchType match : GetDefaultMatchTypeOrder(query.Type)) - { - filter.Type = match; - - for (auto field : { PackageMatchField::Id, PackageMatchField::Name, PackageMatchField::Moniker, PackageMatchField::Command, PackageMatchField::Tag }) - { - filter.Field = field; - resultsTable.SearchOnField(filter); - } - } - } - - std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const - { - switch (property) - { - case AppInstaller::Repository::PackageVersionProperty::Id: - return ManifestTable::GetValueById(connection, manifestId); - case AppInstaller::Repository::PackageVersionProperty::Name: - return ManifestTable::GetValueById(connection, manifestId); - case AppInstaller::Repository::PackageVersionProperty::Version: - return ManifestTable::GetValueById(connection, manifestId); - case AppInstaller::Repository::PackageVersionProperty::Channel: - return ManifestTable::GetValueById(connection, manifestId); - case AppInstaller::Repository::PackageVersionProperty::RelativePath: - return PathPartTable::GetPathById(connection, std::get<0>(ManifestTable::GetIdsById(connection, manifestId))); - case AppInstaller::Repository::PackageVersionProperty::Moniker: - return ManifestTable::GetValueById(connection, manifestId); - default: - return {}; - } - } - - OneToManyTableSchema Interface::GetOneToManyTableSchema() const - { - return OneToManyTableSchema::Version_1_0; - } - - void Interface::Vacuum(const SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Vacuum(); - builder.Execute(connection); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_0/Interface.h" + +#include "Microsoft/Schema/1_0/IdTable.h" +#include "Microsoft/Schema/1_0/NameTable.h" +#include "Microsoft/Schema/1_0/MonikerTable.h" +#include "Microsoft/Schema/1_0/VersionTable.h" +#include "Microsoft/Schema/1_0/ChannelTable.h" + +#include "Microsoft/Schema/1_0/PathPartTable.h" + +#include "Microsoft/Schema/1_0/ManifestTable.h" + +#include "Microsoft/Schema/1_0/TagsTable.h" +#include "Microsoft/Schema/1_0/CommandsTable.h" + +#include "Microsoft/Schema/1_0/SearchResultsTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace + { + // Gets an existing manifest by its rowid., if it exists. + std::optional GetExistingManifestId(const SQLite::Connection& connection, const Manifest::Manifest& manifest) + { + std::optional idId = IdTable::SelectIdByValue(connection, manifest.Id, true); + if (!idId) + { + AICLI_LOG(Repo, Verbose, << "Did not find an Id { " << manifest.Id << " }"); + return {}; + } + + std::optional versionId = VersionTable::SelectIdByValue(connection, manifest.Version, true); + if (!versionId) + { + AICLI_LOG(Repo, Verbose, << "Did not find a Version { " << manifest.Version << " }"); + return {}; + } + + std::optional channelId = ChannelTable::SelectIdByValue(connection, manifest.Channel, true); + if (!channelId) + { + AICLI_LOG(Repo, Verbose, << "Did not find a Channel { " << manifest.Channel << " }"); + return {}; + } + + auto result = ManifestTable::SelectByValueIds(connection, { idId.value(), versionId.value(), channelId.value() }); + + if (!result) + { + AICLI_LOG(Repo, Verbose, << "Did not find a manifest row for { " << manifest.Id << ", " << manifest.Version << ", " << manifest.Channel << " }"); + } + + return result; + } + + // Gets a manifest id by the given key values. + std::optional StaticGetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version = "", std::string_view channel = "") + { + std::optional channelIdOpt = ChannelTable::SelectIdByValue(connection, channel, true); + if (!channelIdOpt && !channel.empty()) + { + AICLI_LOG(Repo, Info, << "Did not find a Channel { " << channel << " }"); + return {}; + } + + std::optional versionIdOpt; + std::vector> versionStrings; + + if (channelIdOpt) + { + versionStrings = ManifestTable::GetAllValuesByIds(connection, { id, channelIdOpt.value() }); + } + else + { + versionStrings = ManifestTable::GetAllValuesByIds(connection, { id }); + } + + if (versionStrings.empty()) + { + AICLI_LOG(Repo, Info, << "Did not find any Versions { " << id << ", " << channel << " }"); + return {}; + } + + // Convert the strings to Versions and sort them + struct VersionAndRow + { + SQLite::rowid_t Row = 0; + Utility::Version Version; + + bool operator<(const VersionAndRow& other) const { return Version < other.Version; } + }; + + std::vector versions; + for (auto& v : versionStrings) + { + versions.emplace_back(VersionAndRow{ v.first, std::move(v.second) }); + } + + std::sort(versions.begin(), versions.end()); + + if (version.empty()) + { + // Get the last version in the list (the highest version) + versionIdOpt = versions.back().Row; + } + else + { + VersionAndRow requested; + requested.Version = Utility::Version{ std::string(version) }; + + auto itr = std::lower_bound(versions.begin(), versions.end(), requested); + if (itr != versions.end() && itr->Version == requested.Version) + { + versionIdOpt = itr->Row; + } + } + + if (!versionIdOpt) + { + AICLI_LOG(Repo, Info, << "Did not find a Version for { " << version << " }"); + return {}; + } + + if (channelIdOpt) + { + return ManifestTable::SelectByValueIds(connection, { id, versionIdOpt.value(), channelIdOpt.value() }); + } + else + { + return ManifestTable::SelectByValueIds(connection, { id, versionIdOpt.value() }); + } + } + + bool NotNeededInternal(const SQLite::Connection& connection, std::string_view, std::string_view valueName, SQLite::rowid_t id) + { + return !ManifestTable::IsValueReferenced(connection, valueName, id); + } + + // Updates the manifest column and related table based on the given value. + template + void UpdateManifestValueById(SQLite::Connection& connection, const typename Table::value_t& value, SQLite::rowid_t manifestId, bool overwriteLikeMatch = false) + { + auto [oldValueId] = ManifestTable::GetIdsById
(connection, manifestId); + + SQLite::rowid_t newValueId = Table::EnsureExists(connection, value, overwriteLikeMatch); + + ManifestTable::UpdateValueIdById
(connection, manifestId, newValueId); + + if (NotNeededInternal(connection, Table::TableName(), Table::ValueName(), oldValueId)) + { + Table::DeleteById(connection, oldValueId); + } + } + } + + SQLite::Version Interface::GetVersion() const + { + return { 1, 0 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_0"); + + IdTable::Create_deprecated(connection); + NameTable::Create_deprecated(connection); + MonikerTable::Create_deprecated(connection); + VersionTable::Create_deprecated(connection); + ChannelTable::Create_deprecated(connection); + + PathPartTable::Create_deprecated(connection); + + ManifestTable::Create_deprecated(connection, { + { IdTable::ValueName(), true, false }, + { NameTable::ValueName(), false, false }, + { MonikerTable::ValueName(), false, false }, + { VersionTable::ValueName(), true, false }, + { ChannelTable::ValueName(), true, false }, + { PathPartTable::ValueName(), false, WI_IsFlagClear(options, CreateOptions::SupportPathless) } + }); + + TagsTable::Create(connection, GetOneToManyTableSchema()); + CommandsTable::Create(connection, GetOneToManyTableSchema()); + + savepoint.Commit(); + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + auto manifestResult = GetExistingManifestId(connection, manifest); + + // If this manifest is already present, we can't add it. + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), manifestResult.has_value()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_0"); + + auto [pathAdded, pathLeafId] = PathPartTable::EnsurePathExists(connection, relativePath, true); + + // If we get false from the function, this manifest path already exists in the index. + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), relativePath && !pathAdded); + + // Ensure that all of the 1:1 data exists. + SQLite::rowid_t idId = IdTable::EnsureExists(connection, manifest.Id, true); + SQLite::rowid_t nameId = NameTable::EnsureExists(connection, manifest.DefaultLocalization.Get()); + SQLite::rowid_t monikerId = MonikerTable::EnsureExists(connection, manifest.Moniker); + SQLite::rowid_t versionId = VersionTable::EnsureExists(connection, manifest.Version); + SQLite::rowid_t channelId = ChannelTable::EnsureExists(connection, manifest.Channel); + + // Insert the manifest entry. + SQLite::rowid_t manifestId = ManifestTable::Insert(connection, { + { IdTable::ValueName(), idId}, + { NameTable::ValueName(), nameId }, + { MonikerTable::ValueName(), monikerId }, + { VersionTable::ValueName(), versionId }, + { ChannelTable::ValueName(), channelId }, + { PathPartTable::ValueName(), pathLeafId } + }); + + // Add all of the 1:N data. + TagsTable::EnsureExistsAndInsert(connection, manifest.GetAggregatedTags(), manifestId); + CommandsTable::EnsureExistsAndInsert(connection, manifest.GetAggregatedCommands(), manifestId); + + savepoint.Commit(); + + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + auto manifestResult = GetExistingManifestId(connection, manifest); + + // If the manifest doesn't actually exist, fail the update. + THROW_HR_IF(E_NOT_SET, !manifestResult); + + SQLite::rowid_t manifestId = manifestResult.value(); + + auto [idInIndex, nameInIndex, monikerInIndex, versionInIndex, channelInIndex] = + ManifestTable::GetValuesById(connection, manifestId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_0"); + bool indexModified = false; + + // Id, Version, and Channel may have changed casing. If so, they too need to be updated. + if (idInIndex != manifest.Id) + { + UpdateManifestValueById(connection, manifest.Id, manifestId, true); + indexModified = true; + } + + if (versionInIndex != manifest.Version) + { + UpdateManifestValueById(connection, manifest.Version, manifestId); + indexModified = true; + } + + if (channelInIndex != manifest.Channel) + { + UpdateManifestValueById(connection, manifest.Channel, manifestId); + indexModified = true; + } + + auto packageName = manifest.DefaultLocalization.Get(); + if (nameInIndex != packageName) + { + UpdateManifestValueById(connection, packageName, manifestId); + indexModified = true; + } + + if (monikerInIndex != manifest.Moniker) + { + UpdateManifestValueById(connection, manifest.Moniker, manifestId); + indexModified = true; + } + + // Update path table if necessary + auto [existingPathLeafId] = ManifestTable::GetIdsById(connection, manifestId); + auto [pathAdded, newPathLeafId] = PathPartTable::EnsurePathExists(connection, relativePath, true); + + if (relativePath && pathAdded) + { + // Path was added, so we need to update the manifest table and delete the old path + ManifestTable::UpdateValueIdById(connection, manifestId, newPathLeafId); + PathPartTable::RemovePathById(connection, existingPathLeafId); + indexModified = true; + } + else + { + // The path already existed, so it must either match the existing manifest path or it is an error + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPathLeafId != newPathLeafId); + } + + // Update all 1:N tables as necessary + indexModified = TagsTable::UpdateIfNeededByManifestId(connection, manifest.GetAggregatedTags(), manifestId) || indexModified; + indexModified = CommandsTable::UpdateIfNeededByManifestId(connection, manifest.GetAggregatedCommands(), manifestId) || indexModified; + + savepoint.Commit(); + + return { indexModified, manifestId }; + } + + SQLite::rowid_t Interface::RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) + { + auto manifestResult = GetExistingManifestId(connection, manifest); + + // If the manifest doesn't actually exist, fail the remove. + THROW_HR_IF(E_NOT_SET, !manifestResult); + + RemoveManifestById(connection, manifestResult.value()); + + return manifestResult.value(); + } + + void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + // Get the ids of the values from the manifest table + auto [idId, nameId, monikerId, versionId, channelId, pathLeafId] = + ManifestTable::GetIdsById(connection, manifestId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_0"); + + // Remove the manifest row + ManifestTable::DeleteById(connection, manifestId); + + // Remove all of the 1:1 data that is no longer referenced. + if (NotNeeded(connection, IdTable::TableName(), IdTable::ValueName(), idId)) + { + IdTable::DeleteById(connection, idId); + } + + if (NotNeeded(connection, NameTable::TableName(), NameTable::ValueName(), nameId)) + { + NameTable::DeleteById(connection, nameId); + } + + if (NotNeeded(connection, MonikerTable::TableName(), MonikerTable::ValueName(), monikerId)) + { + MonikerTable::DeleteById(connection, monikerId); + } + + if (NotNeeded(connection, VersionTable::TableName(), VersionTable::ValueName(), versionId)) + { + VersionTable::DeleteById(connection, versionId); + } + + if (NotNeeded(connection, ChannelTable::TableName(), ChannelTable::ValueName(), channelId)) + { + ChannelTable::DeleteById(connection, channelId); + } + + // Remove the path + PathPartTable::RemovePathById(connection, pathLeafId); + + // Remove all of the 1:N data that is no longer referenced. + TagsTable::DeleteIfNotNeededByManifestId(connection, manifestId); + CommandsTable::DeleteIfNotNeededByManifestId(connection, manifestId); + + savepoint.Commit(); + } + + bool Interface::NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const + { + return NotNeededInternal(connection, tableName, valueName, id); + } + + void Interface::PrepareForPackaging(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_0"); + + IdTable::PrepareForPackaging_deprecated(connection); + NameTable::PrepareForPackaging_deprecated(connection); + MonikerTable::PrepareForPackaging_deprecated(connection); + VersionTable::PrepareForPackaging_deprecated(connection); + ChannelTable::PrepareForPackaging_deprecated(connection); + + PathPartTable::PrepareForPackaging_deprecated(connection); + + ManifestTable::PrepareForPackaging_deprecated(connection, { + VersionTable::ValueName(), + ChannelTable::ValueName(), + PathPartTable::ValueName(), + }); + + TagsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); + CommandsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); + + savepoint.Commit(); + + Vacuum(connection); + } + + bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const + { + bool result = true; + +#define AICLI_CHECK_CONSISTENCY(_check_) \ + if (result || log) \ + { \ + result = _check_ && result; \ + } + + // Check the manifest table references to it's 1:1 tables + AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(ManifestTable::CheckConsistency(connection, log)); + + // Check the 1:1 tables' consistency + AICLI_CHECK_CONSISTENCY(IdTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(NameTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(MonikerTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(VersionTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(ChannelTable::CheckConsistency(connection, log)); + + // Check the pathparts table for consistency + AICLI_CHECK_CONSISTENCY(PathPartTable::CheckConsistency(connection, log)); + + // Check the 1:N map tables for consistency + AICLI_CHECK_CONSISTENCY(TagsTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(CommandsTable::CheckConsistency(connection, log)); + +#undef AICLI_CHECK_CONSISTENCY + + return result; + } + + ISQLiteIndex::SearchResult Interface::Search(const SQLite::Connection& connection, const SearchRequest& request) const + { + if (request.IsForEverything()) + { + std::vector ids = IdTable::GetAllRowIds(connection, request.MaximumResults); + + SearchResult result; + for (SQLite::rowid_t id : ids) + { + result.Matches.emplace_back(std::make_pair(id, PackageMatchFilter(PackageMatchField::Id, MatchType::Wildcard))); + } + + result.Truncated = (request.MaximumResults && IdTable::GetCount(connection) > request.MaximumResults); + + return result; + } + + // First phase, create the search results table and populate it with the initial results. + // If the Query is provided, we search across many fields and put results in together. + // If Inclusions has fields, we add these to the data. + // If neither is defined, we take the first filter and use it as the initial results search. + std::unique_ptr resultsTable = CreateSearchResultsTable(connection); + bool inclusionsAttempted = false; + + if (request.Query) + { + // Perform searches across multiple tables to populate the initial results. + PerformQuerySearch(*resultsTable.get(), request.Query.value()); + + inclusionsAttempted = true; + } + + if (!request.Inclusions.empty()) + { + for (auto include : request.Inclusions) + { + for (MatchType match : GetDefaultMatchTypeOrder(include.Type)) + { + include.Type = match; + resultsTable->SearchOnField(include); + } + } + + inclusionsAttempted = true; + } + + size_t filterIndex = 0; + if (!inclusionsAttempted) + { + THROW_HR_IF(E_UNEXPECTED, request.Filters.empty()); + + // Perform search for just the field matching the first filter + PackageMatchFilter filter = request.Filters[0]; + + for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) + { + filter.Type = match; + resultsTable->SearchOnField(filter); + } + + // Skip the filter as we already know everything matches + filterIndex = 1; + } + + // Remove any duplicate manifest entries + resultsTable->RemoveDuplicateManifestRows(); + + // Second phase, for remaining filters, flag matching search results, then remove unflagged values. + for (size_t i = filterIndex; i < request.Filters.size(); ++i) + { + PackageMatchFilter filter = request.Filters[i]; + + resultsTable->PrepareToFilter(); + + for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) + { + filter.Type = match; + resultsTable->FilterOnField(filter); + } + + resultsTable->CompleteFilter(); + } + + return resultsTable->GetSearchResults(request.MaximumResults); + } + + std::optional Interface::GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const + { + return GetPropertyByManifestIdInternal(connection, primaryId, property); + } + + std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const + { + switch (property) + { + case PackageVersionMultiProperty::Tag: + return TagsTable::GetValuesByManifestId(connection, primaryId); + case PackageVersionMultiProperty::Command: + return CommandsTable::GetValuesByManifestId(connection, primaryId); + default: + return {}; + } + } + + std::optional Interface::GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const + { + return StaticGetManifestIdByKey(connection, id, version, channel); + } + + std::optional Interface::GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const + { + return GetExistingManifestId(connection, manifest); + } + + std::set> Interface::GetDependenciesByManifestRowId(const SQLite::Connection&, SQLite::rowid_t) const + { + return {}; + } + + std::vector> Interface::GetDependentsById(const SQLite::Connection&, AppInstaller::Manifest::string_t) const + { + return {}; + } + + void Interface::DropTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_0"); + + IdTable::Drop(connection); + NameTable::Drop(connection); + MonikerTable::Drop(connection); + VersionTable::Drop(connection); + ChannelTable::Drop(connection); + + PathPartTable::Drop(connection); + + ManifestTable::Drop(connection); + + TagsTable::Drop(connection); + CommandsTable::Drop(connection); + + savepoint.Commit(); + } + + bool Interface::MigrateFrom(SQLite::Connection&, const ISQLiteIndex*) + { + return false; + } + + std::vector Interface::GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const + { + auto versionsAndChannels = ManifestTable::GetAllValuesById(connection, id); + + std::vector result; + result.reserve(versionsAndChannels.size()); + for (auto&& vac : versionsAndChannels) + { + result.emplace_back(ISQLiteIndex::VersionKey{ Utility::VersionAndChannel{ Utility::Version{ std::move(std::get<1>(vac)) }, Utility::Channel{ std::move(std::get<2>(vac)) } }, std::get<0>(vac) }); + } + + std::sort(result.begin(), result.end()); + + return result; + } + + ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection&, SQLite::rowid_t) const + { + return {}; + } + + void Interface::SetMetadataByManifestId(SQLite::Connection&, SQLite::rowid_t, PackageVersionMetadata, std::string_view) + { + } + + Utility::NormalizedName Interface::NormalizeName(std::string_view name, std::string_view publisher) const + { + Utility::NormalizedName result; + result.Name(name); + result.Publisher(publisher); + return result; + } + + std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const + { + return std::make_unique(connection); + } + + void Interface::PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const + { + // Arbitrary values to create a reusable filter with the given value. + PackageMatchFilter filter(PackageMatchField::Id, MatchType::Exact, query.Value); + + for (MatchType match : GetDefaultMatchTypeOrder(query.Type)) + { + filter.Type = match; + + for (auto field : { PackageMatchField::Id, PackageMatchField::Name, PackageMatchField::Moniker, PackageMatchField::Command, PackageMatchField::Tag }) + { + filter.Field = field; + resultsTable.SearchOnField(filter); + } + } + } + + std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const + { + switch (property) + { + case AppInstaller::Repository::PackageVersionProperty::Id: + return ManifestTable::GetValueById(connection, manifestId); + case AppInstaller::Repository::PackageVersionProperty::Name: + return ManifestTable::GetValueById(connection, manifestId); + case AppInstaller::Repository::PackageVersionProperty::Version: + return ManifestTable::GetValueById(connection, manifestId); + case AppInstaller::Repository::PackageVersionProperty::Channel: + return ManifestTable::GetValueById(connection, manifestId); + case AppInstaller::Repository::PackageVersionProperty::RelativePath: + return PathPartTable::GetPathById(connection, std::get<0>(ManifestTable::GetIdsById(connection, manifestId))); + case AppInstaller::Repository::PackageVersionProperty::Moniker: + return ManifestTable::GetValueById(connection, manifestId); + default: + return {}; + } + } + + OneToManyTableSchema Interface::GetOneToManyTableSchema() const + { + return OneToManyTableSchema::Version_1_0; + } + + void Interface::Vacuum(const SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Vacuum(); + builder.Execute(connection); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp index 6c1b2cad0e..99405659ef 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.cpp @@ -1,533 +1,533 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestTable.h" -#include -#include "OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - using namespace std::string_view_literals; - static constexpr std::string_view s_ManifestTable_Table_Name = "manifest"sv; - static constexpr std::string_view s_ManifestTable_Index_Separator = "_"sv; - static constexpr std::string_view s_ManifestTable_Index_Suffix = "_index"sv; - - namespace details - { - std::optional ManifestTableSelectByValueIds( - const SQLite::Connection& connection, - std::initializer_list values, - std::initializer_list ids) - { - THROW_HR_IF(E_INVALIDARG, values.size() != ids.size()); - - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::RowIDName).From(s_ManifestTable_Table_Name); - - bool isFirst = true; - - for (const auto& value : values) - { - if (isFirst) - { - builder.Where(value).Equals(SQLite::Builder::Unbound); - isFirst = false; - } - else - { - builder.And(value).Equals(SQLite::Builder::Unbound); - } - } - - builder.Limit(1); - - SQLite::Statement select = builder.Prepare(connection); - - int bindIndex = 0; - for (const auto& id : ids) - { - select.Bind(++bindIndex, id); - } - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - SQLite::Statement ManifestTableGetIdsById_Statement( - const SQLite::Connection& connection, - SQLite::rowid_t id, - std::initializer_list values, - bool stepAndVerify) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(values).From(s_ManifestTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement result = builder.Prepare(connection); - - if (stepAndVerify) - { - THROW_HR_IF(E_NOT_SET, !result.Step()); - } - - return result; - } - - // Creates a statement and executes it, select the actual values for a given manifest id. - // Ex. - // SELECT [ids].[id] FROM [manifest] - // JOIN [ids] ON [manifest].[id] = [ids].[rowid] - // WHERE [manifest].[rowid] = 1 - SQLite::Statement ManifestTableGetValuesById_Statement( - const SQLite::Connection& connection, - SQLite::rowid_t id, - std::initializer_list columns, - std::initializer_list manifestColumnNames, - bool stepAndVerify) - { - THROW_HR_IF(E_UNEXPECTED, manifestColumnNames.size() != columns.size()); - - using QCol = SQLite::Builder::QualifiedColumn; - - SQLite::Builder::StatementBuilder builder; - builder.Select(columns).From(s_ManifestTable_Table_Name); - - // join tables - auto columnItr = columns.begin(); - auto manifestColumnNameItr = manifestColumnNames.begin(); - while (columnItr != columns.end()) - { - builder.Join(columnItr->Table).On(QCol{ s_ManifestTable_Table_Name, *manifestColumnNameItr }, QCol{ columnItr->Table, SQLite::RowIDName }); - - columnItr++; - manifestColumnNameItr++; - } - - builder.Where(QCol{ s_ManifestTable_Table_Name, SQLite::RowIDName }).Equals(id); - - SQLite::Statement result = builder.Prepare(connection); - - if (stepAndVerify) - { - THROW_HR_IF(E_NOT_SET, !result.Step()); - } - - return result; - } - - SQLite::Statement ManifestTableGetAllValuesByIds_Statement( - const SQLite::Connection& connection, - std::initializer_list valueColumns, - std::initializer_list joinColumns, - std::initializer_list idColumns, - std::initializer_list ids) - { - using QCol = SQLite::Builder::QualifiedColumn; - - THROW_HR_IF(E_INVALIDARG, idColumns.size() != ids.size()); - - SQLite::Builder::StatementBuilder builder; - builder.Select(valueColumns).From(s_ManifestTable_Table_Name); - - for (const auto& joinColumn : joinColumns) - { - builder.Join(joinColumn.Table).On(QCol{ s_ManifestTable_Table_Name, joinColumn.Column }, QCol{ joinColumn.Table, SQLite::RowIDName }); - } - - bool isFirst = true; - - for (const auto& idColumn : idColumns) - { - if (isFirst) - { - builder.Where(idColumn).Equals(SQLite::Builder::Unbound); - isFirst = false; - } - else - { - builder.And(idColumn).Equals(SQLite::Builder::Unbound); - } - } - - SQLite::Statement select = builder.Prepare(connection); - - int bindIndex = 0; - for (const auto& id : ids) - { - select.Bind(++bindIndex, id); - } - - return select; - } - - std::vector> ManifestTableGetAllValuesByIds( - const SQLite::Connection& connection, - std::initializer_list valueColumns, - std::initializer_list joinColumns, - std::initializer_list idColumns, - std::initializer_list ids) - { - auto select = ManifestTableGetAllValuesByIds_Statement(connection, valueColumns, joinColumns, idColumns, ids); - - std::vector> result; - while (select.Step()) - { - result.emplace_back(select.GetColumn(0), select.GetColumn(1)); - } - return result; - } - - std::vector ManifestTableBuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::initializer_list columns, - std::initializer_list isOneToOnes, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) - { - using QCol = SQLite::Builder::QualifiedColumn; - - // Build a statement like: - // SELECT manifest.rowid as m, ids.id as v from manifest - // join ids on manifest.id = ids.rowid - // where ids.id = - // OR - // SELECT manifest.rowid as m, tags.tag as v from manifest - // join tags_map on manifest.rowid = tags_map.manifest - // join tags on tags_map.tag = tags.rowid - // where tags.tag = - // Where the joins and where portions are repeated for each table in question. - builder.Select(). - Column(QCol(s_ManifestTable_Table_Name, SQLite::RowIDName)).As(manifestAlias); - - // Value will be captured for single tables references, and left empty for multi-tables - if (columns.size() == 1) - { - builder.Column(*columns.begin()); - } - else - { - builder.Value(std::string_view{}); - } - - builder.As(valueAlias).From(s_ManifestTable_Table_Name); - - // Create join clauses - THROW_HR_IF(E_INVALIDARG, columns.size() != isOneToOnes.size()); - auto columnItr = columns.begin(); - auto isOneToOneItr = isOneToOnes.begin(); - - for (; columnItr != columns.end(); ++columnItr, ++isOneToOneItr) - { - const SQLite::Builder::QualifiedColumn& column = *columnItr; - - if (*isOneToOneItr) - { - builder. - Join(column.Table).On(QCol(s_ManifestTable_Table_Name, column.Column), QCol(column.Table, SQLite::RowIDName)); - } - else - { - std::string mapTableName = details::OneToManyTableGetMapTableName(column.Table); - builder. - Join(mapTableName).On(QCol(s_ManifestTable_Table_Name, SQLite::RowIDName), QCol(mapTableName, details::OneToManyTableGetManifestColumnName())). - Join(column.Table).On(QCol(mapTableName, column.Column), QCol(column.Table, SQLite::RowIDName)); - } - } - - std::vector result; - - // Create where clause - for (const SQLite::Builder::QualifiedColumn& column : columns) - { - if (result.empty()) - { - builder.Where(column); - } - else - { - builder.And(column); - } - - if (useLike) - { - builder.Like(SQLite::Builder::Unbound); - result.push_back(builder.GetLastBindIndex()); - builder.Escape(SQLite::EscapeCharForLike); - } - else - { - builder.Equals(SQLite::Builder::Unbound); - result.push_back(builder.GetLastBindIndex()); - } - } - - return result; - } - - SQLite::Statement ManifestTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName) - { - SQLite::Builder::StatementBuilder builder; - builder.Update(s_ManifestTable_Table_Name).Set().Column(valueName).Equals(SQLite::Builder::Unbound).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); - - return builder.Prepare(connection); - } - - bool ManifestTableCheckConsistency(const SQLite::Connection& connection, const SQLite::Builder::QualifiedColumn& target, std::string_view manifestColumnName, bool log) - { - using QCol = SQLite::Builder::QualifiedColumn; - - // Build a select statement to find manifest rows containing references to 1:1 tables with nonexistent rowids - // Such as: - // Select manifest.rowid, manifest.id, ids.id from manifest left outer join ids on manifest.id = ids.rowid where ids.id is NULL - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_ManifestTable_Table_Name, SQLite::RowIDName), QCol(s_ManifestTable_Table_Name, manifestColumnName) }). - From(s_ManifestTable_Table_Name). - LeftOuterJoin(target.Table).On(QCol(s_ManifestTable_Table_Name, manifestColumnName), QCol(target.Table, SQLite::RowIDName)). - Where(target).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - bool result = true; - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] manifest [" << select.GetColumn(0) << "] refers to " << target.Table << " [" << select.GetColumn(1) << "]"); - } - - return result; - } - } - - std::string_view ManifestTable::TableName() - { - return s_ManifestTable_Table_Name; - } - - // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. - // The resulting database will function the same, but give us control to drop the indices to reduce space. - void ManifestTable::Create(SQLite::Connection& connection, std::initializer_list values) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createManifestTable_v1_1"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_ManifestTable_Table_Name).BeginColumns(); - - // Add an integer primary key to keep the manifest rowid consistent - createTableBuilder.Column(IntegerPrimaryKey()); - - for (const ManifestColumnInfo& value : values) - { - createTableBuilder.Column(ColumnBuilder(value.Name, Type::Int64).NotNull()); - } - - createTableBuilder.EndColumns(); - - createTableBuilder.Execute(connection); - - // Create a unique index with the primary key values - StatementBuilder pkIndexBuilder; - - pkIndexBuilder.CreateUniqueIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Suffix }).On(s_ManifestTable_Table_Name).BeginColumns(); - - for (const ManifestColumnInfo& value : values) - { - if (value.PrimaryKey) - { - pkIndexBuilder.Column(value.Name); - } - } - - pkIndexBuilder.EndColumns(); - - pkIndexBuilder.Execute(connection); - - // Create an index on every value to improve performance - for (const ManifestColumnInfo& value : values) - { - StatementBuilder createIndexBuilder; - - if (value.Unique) - { - createIndexBuilder.CreateUniqueIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value.Name, s_ManifestTable_Index_Suffix }); - } - else - { - createIndexBuilder.CreateIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value.Name, s_ManifestTable_Index_Suffix }); - } - - createIndexBuilder.On(s_ManifestTable_Table_Name).Columns(value.Name); - - createIndexBuilder.Execute(connection); - } - - savepoint.Commit(); - } - - void ManifestTable::AddColumn(SQLite::Connection& connection, AddedColumnInfo value) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addColumnManifestTable_v1_3"); - - StatementBuilder alterTableBuilder; - alterTableBuilder.AlterTable(s_ManifestTable_Table_Name).Add(value.Name, value.Type); - - alterTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - void ManifestTable::Create_deprecated(SQLite::Connection& connection, std::initializer_list values) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createManifestTable_v1_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_ManifestTable_Table_Name).BeginColumns(); - - for (const ManifestColumnInfo& value : values) - { - createTableBuilder.Column(ColumnBuilder(value.Name, Type::Int64).NotNull().Unique(value.Unique)); - } - - PrimaryKeyBuilder pkBuilder; - for (const ManifestColumnInfo& value : values) - { - if (value.PrimaryKey) - { - pkBuilder.Column(value.Name); - } - } - - createTableBuilder.Column(pkBuilder).EndColumns(); - - createTableBuilder.Execute(connection); - - // Create an index on every value to improve performance - for (const ManifestColumnInfo& value : values) - { - StatementBuilder createIndexBuilder; - createIndexBuilder.CreateIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value.Name, s_ManifestTable_Index_Suffix }). - On(s_ManifestTable_Table_Name).Columns(value.Name); - - createIndexBuilder.Execute(connection); - } - - savepoint.Commit(); - } - - void ManifestTable::Drop(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(s_ManifestTable_Table_Name); - - dropTableBuilder.Execute(connection); - } - - SQLite::rowid_t ManifestTable::Insert(SQLite::Connection& connection, std::initializer_list values) - { - SQLite::Builder::StatementBuilder builder; - builder.InsertInto(s_ManifestTable_Table_Name).BeginColumns(); - - for (const ManifestOneToOneValue& value : values) - { - builder.Column(value.Name); - } - - builder.EndColumns().BeginValues(); - - for (const ManifestOneToOneValue& value : values) - { - builder.Value(value.Value); - } - - builder.EndValues(); - - builder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - bool ManifestTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_ManifestTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) != 0); - } - - void ManifestTable::DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_ManifestTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - } - - void ManifestTable::PrepareForPackaging(SQLite::Connection& connection, std::initializer_list values) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpManifestTable_v1_1"); - - PrepareForPackaging_deprecated(connection, values); - - SQLite::Builder::StatementBuilder dropPKIndexBuilder; - dropPKIndexBuilder.DropIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Suffix }); - dropPKIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void ManifestTable::PrepareForPackaging_deprecated(SQLite::Connection& connection, std::initializer_list values) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpManifestTable_v1_0"); - - // Drop the index on the requested values - for (std::string_view value : values) - { - SQLite::Builder::StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value, s_ManifestTable_Index_Suffix }); - - dropIndexBuilder.Execute(connection); - } - - savepoint.Commit(); - } - - bool ManifestTable::IsValueReferenced(const SQLite::Connection& connection, std::string_view valueName, SQLite::rowid_t valueRowId) - { - return details::ManifestTableSelectByValueIds(connection, { valueName }, { valueRowId }).has_value(); - } - - bool ManifestTable::IsEmpty(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_ManifestTable_Table_Name); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) == 0); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestTable.h" +#include +#include "OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_ManifestTable_Table_Name = "manifest"sv; + static constexpr std::string_view s_ManifestTable_Index_Separator = "_"sv; + static constexpr std::string_view s_ManifestTable_Index_Suffix = "_index"sv; + + namespace details + { + std::optional ManifestTableSelectByValueIds( + const SQLite::Connection& connection, + std::initializer_list values, + std::initializer_list ids) + { + THROW_HR_IF(E_INVALIDARG, values.size() != ids.size()); + + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_ManifestTable_Table_Name); + + bool isFirst = true; + + for (const auto& value : values) + { + if (isFirst) + { + builder.Where(value).Equals(SQLite::Builder::Unbound); + isFirst = false; + } + else + { + builder.And(value).Equals(SQLite::Builder::Unbound); + } + } + + builder.Limit(1); + + SQLite::Statement select = builder.Prepare(connection); + + int bindIndex = 0; + for (const auto& id : ids) + { + select.Bind(++bindIndex, id); + } + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + SQLite::Statement ManifestTableGetIdsById_Statement( + const SQLite::Connection& connection, + SQLite::rowid_t id, + std::initializer_list values, + bool stepAndVerify) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(values).From(s_ManifestTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement result = builder.Prepare(connection); + + if (stepAndVerify) + { + THROW_HR_IF(E_NOT_SET, !result.Step()); + } + + return result; + } + + // Creates a statement and executes it, select the actual values for a given manifest id. + // Ex. + // SELECT [ids].[id] FROM [manifest] + // JOIN [ids] ON [manifest].[id] = [ids].[rowid] + // WHERE [manifest].[rowid] = 1 + SQLite::Statement ManifestTableGetValuesById_Statement( + const SQLite::Connection& connection, + SQLite::rowid_t id, + std::initializer_list columns, + std::initializer_list manifestColumnNames, + bool stepAndVerify) + { + THROW_HR_IF(E_UNEXPECTED, manifestColumnNames.size() != columns.size()); + + using QCol = SQLite::Builder::QualifiedColumn; + + SQLite::Builder::StatementBuilder builder; + builder.Select(columns).From(s_ManifestTable_Table_Name); + + // join tables + auto columnItr = columns.begin(); + auto manifestColumnNameItr = manifestColumnNames.begin(); + while (columnItr != columns.end()) + { + builder.Join(columnItr->Table).On(QCol{ s_ManifestTable_Table_Name, *manifestColumnNameItr }, QCol{ columnItr->Table, SQLite::RowIDName }); + + columnItr++; + manifestColumnNameItr++; + } + + builder.Where(QCol{ s_ManifestTable_Table_Name, SQLite::RowIDName }).Equals(id); + + SQLite::Statement result = builder.Prepare(connection); + + if (stepAndVerify) + { + THROW_HR_IF(E_NOT_SET, !result.Step()); + } + + return result; + } + + SQLite::Statement ManifestTableGetAllValuesByIds_Statement( + const SQLite::Connection& connection, + std::initializer_list valueColumns, + std::initializer_list joinColumns, + std::initializer_list idColumns, + std::initializer_list ids) + { + using QCol = SQLite::Builder::QualifiedColumn; + + THROW_HR_IF(E_INVALIDARG, idColumns.size() != ids.size()); + + SQLite::Builder::StatementBuilder builder; + builder.Select(valueColumns).From(s_ManifestTable_Table_Name); + + for (const auto& joinColumn : joinColumns) + { + builder.Join(joinColumn.Table).On(QCol{ s_ManifestTable_Table_Name, joinColumn.Column }, QCol{ joinColumn.Table, SQLite::RowIDName }); + } + + bool isFirst = true; + + for (const auto& idColumn : idColumns) + { + if (isFirst) + { + builder.Where(idColumn).Equals(SQLite::Builder::Unbound); + isFirst = false; + } + else + { + builder.And(idColumn).Equals(SQLite::Builder::Unbound); + } + } + + SQLite::Statement select = builder.Prepare(connection); + + int bindIndex = 0; + for (const auto& id : ids) + { + select.Bind(++bindIndex, id); + } + + return select; + } + + std::vector> ManifestTableGetAllValuesByIds( + const SQLite::Connection& connection, + std::initializer_list valueColumns, + std::initializer_list joinColumns, + std::initializer_list idColumns, + std::initializer_list ids) + { + auto select = ManifestTableGetAllValuesByIds_Statement(connection, valueColumns, joinColumns, idColumns, ids); + + std::vector> result; + while (select.Step()) + { + result.emplace_back(select.GetColumn(0), select.GetColumn(1)); + } + return result; + } + + std::vector ManifestTableBuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::initializer_list columns, + std::initializer_list isOneToOnes, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) + { + using QCol = SQLite::Builder::QualifiedColumn; + + // Build a statement like: + // SELECT manifest.rowid as m, ids.id as v from manifest + // join ids on manifest.id = ids.rowid + // where ids.id = + // OR + // SELECT manifest.rowid as m, tags.tag as v from manifest + // join tags_map on manifest.rowid = tags_map.manifest + // join tags on tags_map.tag = tags.rowid + // where tags.tag = + // Where the joins and where portions are repeated for each table in question. + builder.Select(). + Column(QCol(s_ManifestTable_Table_Name, SQLite::RowIDName)).As(manifestAlias); + + // Value will be captured for single tables references, and left empty for multi-tables + if (columns.size() == 1) + { + builder.Column(*columns.begin()); + } + else + { + builder.Value(std::string_view{}); + } + + builder.As(valueAlias).From(s_ManifestTable_Table_Name); + + // Create join clauses + THROW_HR_IF(E_INVALIDARG, columns.size() != isOneToOnes.size()); + auto columnItr = columns.begin(); + auto isOneToOneItr = isOneToOnes.begin(); + + for (; columnItr != columns.end(); ++columnItr, ++isOneToOneItr) + { + const SQLite::Builder::QualifiedColumn& column = *columnItr; + + if (*isOneToOneItr) + { + builder. + Join(column.Table).On(QCol(s_ManifestTable_Table_Name, column.Column), QCol(column.Table, SQLite::RowIDName)); + } + else + { + std::string mapTableName = details::OneToManyTableGetMapTableName(column.Table); + builder. + Join(mapTableName).On(QCol(s_ManifestTable_Table_Name, SQLite::RowIDName), QCol(mapTableName, details::OneToManyTableGetManifestColumnName())). + Join(column.Table).On(QCol(mapTableName, column.Column), QCol(column.Table, SQLite::RowIDName)); + } + } + + std::vector result; + + // Create where clause + for (const SQLite::Builder::QualifiedColumn& column : columns) + { + if (result.empty()) + { + builder.Where(column); + } + else + { + builder.And(column); + } + + if (useLike) + { + builder.Like(SQLite::Builder::Unbound); + result.push_back(builder.GetLastBindIndex()); + builder.Escape(SQLite::EscapeCharForLike); + } + else + { + builder.Equals(SQLite::Builder::Unbound); + result.push_back(builder.GetLastBindIndex()); + } + } + + return result; + } + + SQLite::Statement ManifestTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName) + { + SQLite::Builder::StatementBuilder builder; + builder.Update(s_ManifestTable_Table_Name).Set().Column(valueName).Equals(SQLite::Builder::Unbound).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); + + return builder.Prepare(connection); + } + + bool ManifestTableCheckConsistency(const SQLite::Connection& connection, const SQLite::Builder::QualifiedColumn& target, std::string_view manifestColumnName, bool log) + { + using QCol = SQLite::Builder::QualifiedColumn; + + // Build a select statement to find manifest rows containing references to 1:1 tables with nonexistent rowids + // Such as: + // Select manifest.rowid, manifest.id, ids.id from manifest left outer join ids on manifest.id = ids.rowid where ids.id is NULL + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_ManifestTable_Table_Name, SQLite::RowIDName), QCol(s_ManifestTable_Table_Name, manifestColumnName) }). + From(s_ManifestTable_Table_Name). + LeftOuterJoin(target.Table).On(QCol(s_ManifestTable_Table_Name, manifestColumnName), QCol(target.Table, SQLite::RowIDName)). + Where(target).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + bool result = true; + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] manifest [" << select.GetColumn(0) << "] refers to " << target.Table << " [" << select.GetColumn(1) << "]"); + } + + return result; + } + } + + std::string_view ManifestTable::TableName() + { + return s_ManifestTable_Table_Name; + } + + // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. + // The resulting database will function the same, but give us control to drop the indices to reduce space. + void ManifestTable::Create(SQLite::Connection& connection, std::initializer_list values) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createManifestTable_v1_1"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_ManifestTable_Table_Name).BeginColumns(); + + // Add an integer primary key to keep the manifest rowid consistent + createTableBuilder.Column(IntegerPrimaryKey()); + + for (const ManifestColumnInfo& value : values) + { + createTableBuilder.Column(ColumnBuilder(value.Name, Type::Int64).NotNull()); + } + + createTableBuilder.EndColumns(); + + createTableBuilder.Execute(connection); + + // Create a unique index with the primary key values + StatementBuilder pkIndexBuilder; + + pkIndexBuilder.CreateUniqueIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Suffix }).On(s_ManifestTable_Table_Name).BeginColumns(); + + for (const ManifestColumnInfo& value : values) + { + if (value.PrimaryKey) + { + pkIndexBuilder.Column(value.Name); + } + } + + pkIndexBuilder.EndColumns(); + + pkIndexBuilder.Execute(connection); + + // Create an index on every value to improve performance + for (const ManifestColumnInfo& value : values) + { + StatementBuilder createIndexBuilder; + + if (value.Unique) + { + createIndexBuilder.CreateUniqueIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value.Name, s_ManifestTable_Index_Suffix }); + } + else + { + createIndexBuilder.CreateIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value.Name, s_ManifestTable_Index_Suffix }); + } + + createIndexBuilder.On(s_ManifestTable_Table_Name).Columns(value.Name); + + createIndexBuilder.Execute(connection); + } + + savepoint.Commit(); + } + + void ManifestTable::AddColumn(SQLite::Connection& connection, AddedColumnInfo value) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addColumnManifestTable_v1_3"); + + StatementBuilder alterTableBuilder; + alterTableBuilder.AlterTable(s_ManifestTable_Table_Name).Add(value.Name, value.Type); + + alterTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + void ManifestTable::Create_deprecated(SQLite::Connection& connection, std::initializer_list values) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createManifestTable_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_ManifestTable_Table_Name).BeginColumns(); + + for (const ManifestColumnInfo& value : values) + { + createTableBuilder.Column(ColumnBuilder(value.Name, Type::Int64).NotNull().Unique(value.Unique)); + } + + PrimaryKeyBuilder pkBuilder; + for (const ManifestColumnInfo& value : values) + { + if (value.PrimaryKey) + { + pkBuilder.Column(value.Name); + } + } + + createTableBuilder.Column(pkBuilder).EndColumns(); + + createTableBuilder.Execute(connection); + + // Create an index on every value to improve performance + for (const ManifestColumnInfo& value : values) + { + StatementBuilder createIndexBuilder; + createIndexBuilder.CreateIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value.Name, s_ManifestTable_Index_Suffix }). + On(s_ManifestTable_Table_Name).Columns(value.Name); + + createIndexBuilder.Execute(connection); + } + + savepoint.Commit(); + } + + void ManifestTable::Drop(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(s_ManifestTable_Table_Name); + + dropTableBuilder.Execute(connection); + } + + SQLite::rowid_t ManifestTable::Insert(SQLite::Connection& connection, std::initializer_list values) + { + SQLite::Builder::StatementBuilder builder; + builder.InsertInto(s_ManifestTable_Table_Name).BeginColumns(); + + for (const ManifestOneToOneValue& value : values) + { + builder.Column(value.Name); + } + + builder.EndColumns().BeginValues(); + + for (const ManifestOneToOneValue& value : values) + { + builder.Value(value.Value); + } + + builder.EndValues(); + + builder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + bool ManifestTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_ManifestTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) != 0); + } + + void ManifestTable::DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_ManifestTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + } + + void ManifestTable::PrepareForPackaging(SQLite::Connection& connection, std::initializer_list values) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpManifestTable_v1_1"); + + PrepareForPackaging_deprecated(connection, values); + + SQLite::Builder::StatementBuilder dropPKIndexBuilder; + dropPKIndexBuilder.DropIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Suffix }); + dropPKIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void ManifestTable::PrepareForPackaging_deprecated(SQLite::Connection& connection, std::initializer_list values) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpManifestTable_v1_0"); + + // Drop the index on the requested values + for (std::string_view value : values) + { + SQLite::Builder::StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex({ s_ManifestTable_Table_Name, s_ManifestTable_Index_Separator, value, s_ManifestTable_Index_Suffix }); + + dropIndexBuilder.Execute(connection); + } + + savepoint.Commit(); + } + + bool ManifestTable::IsValueReferenced(const SQLite::Connection& connection, std::string_view valueName, SQLite::rowid_t valueRowId) + { + return details::ManifestTableSelectByValueIds(connection, { valueName }, { valueRowId }).has_value(); + } + + bool ManifestTable::IsEmpty(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_ManifestTable_Table_Name); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) == 0); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.h index d5c5ba6628..164d49cbc3 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/ManifestTable.h @@ -1,242 +1,242 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "Microsoft/Schema/1_0/VirtualTableBase.h" -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - template - std::string_view GetManifestTableColumnName() - { - if constexpr (std::is_base_of()) - { - return Table::ManifestColumnName(); - } - else - { - return Table::ValueName(); - } - } - - // Selects a manifest by the given value id. - std::optional ManifestTableSelectByValueIds( - const SQLite::Connection& connection, - std::initializer_list values, - std::initializer_list ids); - - // Gets the requested ids for the manifest with the given rowid. - SQLite::Statement ManifestTableGetIdsById_Statement( - const SQLite::Connection& connection, - SQLite::rowid_t id, - std::initializer_list values, - bool stepAndVerify = true); - - // Gets the requested values for the manifest with the given rowid. - SQLite::Statement ManifestTableGetValuesById_Statement( - const SQLite::Connection& connection, - SQLite::rowid_t id, - std::initializer_list columns, - std::initializer_list manifestColumnNames, - bool stepAndVerify = true); - - // Gets all values for rows that match the given ids. - SQLite::Statement ManifestTableGetAllValuesByIds_Statement( - const SQLite::Connection& connection, - std::initializer_list valueColumns, - std::initializer_list joinColumns, - std::initializer_list idColumns, - std::initializer_list ids); - - // Gets all values for rows that match the given ids. - std::vector> ManifestTableGetAllValuesByIds( - const SQLite::Connection& connection, - std::initializer_list valueColumns, - std::initializer_list joinColumns, - std::initializer_list idColumns, - std::initializer_list ids); - - // Builds the search select statement base on the given values. - std::vector ManifestTableBuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::initializer_list columns, - std::initializer_list isOneToOnes, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike); - - // Prepares a statement to update the value of a single column for the manifest with the given rowid. - // The first bind value will be the value to set. - // The second bind value will be the manifest rowid to modify. - SQLite::Statement ManifestTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - bool ManifestTableCheckConsistency(const SQLite::Connection& connection, const SQLite::Builder::QualifiedColumn& target, std::string_view manifestColumnName, bool log); - } - - // Info on the manifest columns. - struct ManifestColumnInfo - { - std::string_view Name; - bool PrimaryKey; - bool Unique; - }; - - // Information on a column being added via ALTER TABLE - struct AddedColumnInfo - { - std::string_view Name; - SQLite::Builder::Type Type; - }; - - // A value that is 1:1 with the manifest. - struct ManifestOneToOneValue - { - std::string_view Name; - SQLite::rowid_t Value; - }; - - // A table that represents a single manifest - struct ManifestTable - { - // Get the table name. - static std::string_view TableName(); - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection, std::initializer_list values); - - // Alters the table, adding the columns provided. - static void AddColumn(SQLite::Connection& connection, AddedColumnInfo value); - - // Creates the table with standard primary keys. - static void Create_deprecated(SQLite::Connection& connection, std::initializer_list values); - - // Drops the table. - static void Drop(SQLite::Connection& connection); - - // Insert the given values into the table. - static SQLite::rowid_t Insert(SQLite::Connection& connection, std::initializer_list values); - - // Gets a value indicating whether the manifest with rowid id exists. - static bool ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id); - - // Select the first rowid of the manifest with the given value. - template - static std::optional SelectByValueIds(const SQLite::Connection& connection, std::initializer_list ids) - { - static_assert(sizeof...(Tables) >= 1); - return details::ManifestTableSelectByValueIds(connection, { Tables::ValueName()... }, ids); - } - - // Gets the ids requested for the manifest with the given rowid. - template - static auto GetIdsById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - return details::ManifestTableGetIdsById_Statement(connection, id, { details::GetManifestTableColumnName()...}).GetRow(); - } - - // Gets the id requested for the manifest with the given rowid, if it exists. - template - static std::optional GetIdById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - auto statement = details::ManifestTableGetIdsById_Statement(connection, id, { details::GetManifestTableColumnName
() }, false); - if (statement.Step()) { return statement.GetColumn(0); } - else { return std::nullopt; } - } - - // Gets the values requested for the manifest with the given rowid. - template - static auto GetValuesById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - return details::ManifestTableGetValuesById_Statement(connection, id, { SQLite::Builder::QualifiedColumn{ Tables::TableName(), Tables::ValueName() }... }, { details::GetManifestTableColumnName()... }).GetRow(); - } - - // Gets the value requested for the manifest with the given rowid, if it exists. - template - static std::optional GetValueById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - auto statement = details::ManifestTableGetValuesById_Statement(connection, id, { SQLite::Builder::QualifiedColumn{ Table::TableName(), Table::ValueName() } }, { details::GetManifestTableColumnName
() }, false); - if (statement.Step()) { return statement.GetColumn(0); } - else { return std::nullopt; } - } - - // Gets the row ids and values for rows that match the given ids. - template - static std::vector> GetAllValuesByIds(const SQLite::Connection& connection, std::initializer_list ids) - { - return details::ManifestTableGetAllValuesByIds(connection, - { SQLite::Builder::QualifiedColumn{ ValueTable::TableName(), SQLite::RowIDName }, SQLite::Builder::QualifiedColumn{ ValueTable::TableName(), ValueTable::ValueName() } }, - { SQLite::Builder::QualifiedColumn{ ValueTable::TableName(), ValueTable::ValueName() } }, - { IdTables::ValueName()... }, ids); - } - - // Gets all values for rows that match the given id. - template - static std::vector> GetAllValuesById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - auto stmt = details::ManifestTableGetAllValuesByIds_Statement(connection, - { SQLite::Builder::QualifiedColumn{ TableName(), SQLite::RowIDName }, SQLite::Builder::QualifiedColumn{ ValueTables::TableName(), ValueTables::ValueName() }... }, - { SQLite::Builder::QualifiedColumn{ ValueTables::TableName(), ValueTables::ValueName() }... }, - { IdTable::ValueName() }, { id }); - std::vector> result; - while (stmt.Step()) - { - result.emplace_back(stmt.GetRow()); - } - return result; - } - - // Builds the search select statement base on the given values. - // If more than one table is provided, no value will be captured. - // The return value is the bind indices of the values to match against. - template - static std::vector BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view manifestAlias, std::string_view valueAlias, bool useLike) - { - return details::ManifestTableBuildSearchStatement(builder, { SQLite::Builder::QualifiedColumn{ Table::TableName(), Table::ValueName() }... }, { Table::IsOneToOne()... }, manifestAlias, valueAlias, useLike); - } - - // Update the value of a single column for the manifest with the given rowid. - template - static void UpdateValueIdById(SQLite::Connection& connection, SQLite::rowid_t id, const typename Table::id_t& value) - { - auto stmt = details::ManifestTableUpdateValueIdById_Statement(connection, details::GetManifestTableColumnName
()); - stmt.Bind(1, value); - stmt.Bind(2, id); - stmt.Execute(); - } - - // Deletes the manifest row with the given rowid. - static void DeleteById(SQLite::Connection& connection, SQLite::rowid_t id); - - // Removes data that is no longer needed for an index that is to be published. - static void PrepareForPackaging(SQLite::Connection& connection, std::initializer_list values); - - // Removes data that is no longer needed for an index that is to be published. - static void PrepareForPackaging_deprecated(SQLite::Connection& connection, std::initializer_list values); - - // Checks if the row id is present in the column denoted by the value supplied. - static bool IsValueReferenced(const SQLite::Connection& connection, std::string_view valueName, SQLite::rowid_t valueRowId); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - template - static bool CheckConsistency(const SQLite::Connection& connection, bool log) - { - return details::ManifestTableCheckConsistency( - connection, SQLite::Builder::QualifiedColumn{ Table::TableName(), Table::ValueName() }, details::GetManifestTableColumnName
(), log); - } - - // Determines if the table is empty. - static bool IsEmpty(SQLite::Connection& connection); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Microsoft/Schema/1_0/VirtualTableBase.h" +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + template + std::string_view GetManifestTableColumnName() + { + if constexpr (std::is_base_of()) + { + return Table::ManifestColumnName(); + } + else + { + return Table::ValueName(); + } + } + + // Selects a manifest by the given value id. + std::optional ManifestTableSelectByValueIds( + const SQLite::Connection& connection, + std::initializer_list values, + std::initializer_list ids); + + // Gets the requested ids for the manifest with the given rowid. + SQLite::Statement ManifestTableGetIdsById_Statement( + const SQLite::Connection& connection, + SQLite::rowid_t id, + std::initializer_list values, + bool stepAndVerify = true); + + // Gets the requested values for the manifest with the given rowid. + SQLite::Statement ManifestTableGetValuesById_Statement( + const SQLite::Connection& connection, + SQLite::rowid_t id, + std::initializer_list columns, + std::initializer_list manifestColumnNames, + bool stepAndVerify = true); + + // Gets all values for rows that match the given ids. + SQLite::Statement ManifestTableGetAllValuesByIds_Statement( + const SQLite::Connection& connection, + std::initializer_list valueColumns, + std::initializer_list joinColumns, + std::initializer_list idColumns, + std::initializer_list ids); + + // Gets all values for rows that match the given ids. + std::vector> ManifestTableGetAllValuesByIds( + const SQLite::Connection& connection, + std::initializer_list valueColumns, + std::initializer_list joinColumns, + std::initializer_list idColumns, + std::initializer_list ids); + + // Builds the search select statement base on the given values. + std::vector ManifestTableBuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::initializer_list columns, + std::initializer_list isOneToOnes, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike); + + // Prepares a statement to update the value of a single column for the manifest with the given rowid. + // The first bind value will be the value to set. + // The second bind value will be the manifest rowid to modify. + SQLite::Statement ManifestTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + bool ManifestTableCheckConsistency(const SQLite::Connection& connection, const SQLite::Builder::QualifiedColumn& target, std::string_view manifestColumnName, bool log); + } + + // Info on the manifest columns. + struct ManifestColumnInfo + { + std::string_view Name; + bool PrimaryKey; + bool Unique; + }; + + // Information on a column being added via ALTER TABLE + struct AddedColumnInfo + { + std::string_view Name; + SQLite::Builder::Type Type; + }; + + // A value that is 1:1 with the manifest. + struct ManifestOneToOneValue + { + std::string_view Name; + SQLite::rowid_t Value; + }; + + // A table that represents a single manifest + struct ManifestTable + { + // Get the table name. + static std::string_view TableName(); + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection, std::initializer_list values); + + // Alters the table, adding the columns provided. + static void AddColumn(SQLite::Connection& connection, AddedColumnInfo value); + + // Creates the table with standard primary keys. + static void Create_deprecated(SQLite::Connection& connection, std::initializer_list values); + + // Drops the table. + static void Drop(SQLite::Connection& connection); + + // Insert the given values into the table. + static SQLite::rowid_t Insert(SQLite::Connection& connection, std::initializer_list values); + + // Gets a value indicating whether the manifest with rowid id exists. + static bool ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id); + + // Select the first rowid of the manifest with the given value. + template + static std::optional SelectByValueIds(const SQLite::Connection& connection, std::initializer_list ids) + { + static_assert(sizeof...(Tables) >= 1); + return details::ManifestTableSelectByValueIds(connection, { Tables::ValueName()... }, ids); + } + + // Gets the ids requested for the manifest with the given rowid. + template + static auto GetIdsById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + return details::ManifestTableGetIdsById_Statement(connection, id, { details::GetManifestTableColumnName()...}).GetRow(); + } + + // Gets the id requested for the manifest with the given rowid, if it exists. + template + static std::optional GetIdById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + auto statement = details::ManifestTableGetIdsById_Statement(connection, id, { details::GetManifestTableColumnName
() }, false); + if (statement.Step()) { return statement.GetColumn(0); } + else { return std::nullopt; } + } + + // Gets the values requested for the manifest with the given rowid. + template + static auto GetValuesById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + return details::ManifestTableGetValuesById_Statement(connection, id, { SQLite::Builder::QualifiedColumn{ Tables::TableName(), Tables::ValueName() }... }, { details::GetManifestTableColumnName()... }).GetRow(); + } + + // Gets the value requested for the manifest with the given rowid, if it exists. + template + static std::optional GetValueById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + auto statement = details::ManifestTableGetValuesById_Statement(connection, id, { SQLite::Builder::QualifiedColumn{ Table::TableName(), Table::ValueName() } }, { details::GetManifestTableColumnName
() }, false); + if (statement.Step()) { return statement.GetColumn(0); } + else { return std::nullopt; } + } + + // Gets the row ids and values for rows that match the given ids. + template + static std::vector> GetAllValuesByIds(const SQLite::Connection& connection, std::initializer_list ids) + { + return details::ManifestTableGetAllValuesByIds(connection, + { SQLite::Builder::QualifiedColumn{ ValueTable::TableName(), SQLite::RowIDName }, SQLite::Builder::QualifiedColumn{ ValueTable::TableName(), ValueTable::ValueName() } }, + { SQLite::Builder::QualifiedColumn{ ValueTable::TableName(), ValueTable::ValueName() } }, + { IdTables::ValueName()... }, ids); + } + + // Gets all values for rows that match the given id. + template + static std::vector> GetAllValuesById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + auto stmt = details::ManifestTableGetAllValuesByIds_Statement(connection, + { SQLite::Builder::QualifiedColumn{ TableName(), SQLite::RowIDName }, SQLite::Builder::QualifiedColumn{ ValueTables::TableName(), ValueTables::ValueName() }... }, + { SQLite::Builder::QualifiedColumn{ ValueTables::TableName(), ValueTables::ValueName() }... }, + { IdTable::ValueName() }, { id }); + std::vector> result; + while (stmt.Step()) + { + result.emplace_back(stmt.GetRow()); + } + return result; + } + + // Builds the search select statement base on the given values. + // If more than one table is provided, no value will be captured. + // The return value is the bind indices of the values to match against. + template + static std::vector BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view manifestAlias, std::string_view valueAlias, bool useLike) + { + return details::ManifestTableBuildSearchStatement(builder, { SQLite::Builder::QualifiedColumn{ Table::TableName(), Table::ValueName() }... }, { Table::IsOneToOne()... }, manifestAlias, valueAlias, useLike); + } + + // Update the value of a single column for the manifest with the given rowid. + template + static void UpdateValueIdById(SQLite::Connection& connection, SQLite::rowid_t id, const typename Table::id_t& value) + { + auto stmt = details::ManifestTableUpdateValueIdById_Statement(connection, details::GetManifestTableColumnName
()); + stmt.Bind(1, value); + stmt.Bind(2, id); + stmt.Execute(); + } + + // Deletes the manifest row with the given rowid. + static void DeleteById(SQLite::Connection& connection, SQLite::rowid_t id); + + // Removes data that is no longer needed for an index that is to be published. + static void PrepareForPackaging(SQLite::Connection& connection, std::initializer_list values); + + // Removes data that is no longer needed for an index that is to be published. + static void PrepareForPackaging_deprecated(SQLite::Connection& connection, std::initializer_list values); + + // Checks if the row id is present in the column denoted by the value supplied. + static bool IsValueReferenced(const SQLite::Connection& connection, std::string_view valueName, SQLite::rowid_t valueRowId); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + template + static bool CheckConsistency(const SQLite::Connection& connection, bool log) + { + return details::ManifestTableCheckConsistency( + connection, SQLite::Builder::QualifiedColumn{ Table::TableName(), Table::ValueName() }, details::GetManifestTableColumnName
(), log); + } + + // Determines if the table is empty. + static bool IsEmpty(SQLite::Connection& connection); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/MonikerTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/MonikerTable.h index e61e1573d5..07465b779d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/MonikerTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/MonikerTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToOneTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct MonikerTableInfo - { - inline static constexpr std::string_view TableName() { return "monikers"sv; } - inline static constexpr std::string_view ValueName() { return "moniker"sv; } - }; - } - - // The table for Moniker. - using MonikerTable = OneToOneTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToOneTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct MonikerTableInfo + { + inline static constexpr std::string_view TableName() { return "monikers"sv; } + inline static constexpr std::string_view ValueName() { return "moniker"sv; } + }; + } + + // The table for Moniker. + using MonikerTable = OneToOneTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h index 571ceb331c..8fbbd76ef5 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/NameTable.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToOneTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct NameTableInfo - { - inline static constexpr std::string_view TableName() { return "names"sv; } - inline static constexpr std::string_view ValueName() { return "name"sv; } - }; - } - - // The table for Name. - // TODO: Currently only indexing name from default locale, might need to be OneToMany table - using NameTable = OneToOneTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToOneTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct NameTableInfo + { + inline static constexpr std::string_view TableName() { return "names"sv; } + inline static constexpr std::string_view ValueName() { return "name"sv; } + }; + } + + // The table for Name. + // TODO: Currently only indexing name from default locale, might need to be OneToMany table + using NameTable = OneToOneTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.cpp index 2ec8e8dad1..4946b95e3a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.cpp @@ -1,468 +1,468 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_0/OneToManyTable.h" -#include "Microsoft/Schema/1_0/OneToOneTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_0/IdTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - static constexpr std::string_view s_OneToManyTable_MapTable_ManifestName = "manifest"sv; - static constexpr std::string_view s_OneToManyTable_MapTable_Suffix = "_map"sv; - static constexpr std::string_view s_OneToManyTable_MapTable_PrimaryKeyIndexSuffix = "_pkindex"sv; - static constexpr std::string_view s_OneToManyTable_MapTable_IndexSuffix = "_index"sv; - - namespace - { - // Create the mapping table insert statement for multiple use. - // Bind the rowid of the value to 2. - SQLite::Statement CreateMappingInsertStatementForManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) - { - SQLite::Builder::StatementBuilder insertMappingBuilder; - insertMappingBuilder.InsertInto({ tableName, s_OneToManyTable_MapTable_Suffix }). - Columns({ s_OneToManyTable_MapTable_ManifestName, valueName }).Values(manifestId, SQLite::Builder::Unbound); - - return insertMappingBuilder.Prepare(connection); - } - - // Get a collection of the value ids associated with the given manifest id. - std::vector GetValueIdsByManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) - { - std::vector result; - - SQLite::Builder::StatementBuilder selectMappingBuilder; - selectMappingBuilder.Select(valueName).From({ tableName, s_OneToManyTable_MapTable_Suffix }).Where(s_OneToManyTable_MapTable_ManifestName).Equals(manifestId); - - SQLite::Statement selectMappingStatement = selectMappingBuilder.Prepare(connection); - - while (selectMappingStatement.Step()) - { - result.push_back(selectMappingStatement.GetColumn(0)); - } - - return result; - } - - struct DeleteValueIfNotNeededStatements - { - DeleteValueIfNotNeededStatements(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) - { - SQLite::Builder::StatementBuilder selectValueMappingBuilder; - selectValueMappingBuilder.Select(s_OneToManyTable_MapTable_ManifestName).From({ tableName, s_OneToManyTable_MapTable_Suffix }).Where(valueName).Equals(SQLite::Builder::Unbound).Limit(1); - - SelectIfAnyMappingsByValueId = selectValueMappingBuilder.Prepare(connection); - - SQLite::Builder::StatementBuilder deleteValueBuilder; - deleteValueBuilder.DeleteFrom(tableName).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); - - DeleteValueById = deleteValueBuilder.Prepare(connection); - } - - void Execute(SQLite::rowid_t valueId) - { - SelectIfAnyMappingsByValueId.Reset(); - SelectIfAnyMappingsByValueId.Bind(1, valueId); - - // If no rows are found, we can delete the data. - if (!SelectIfAnyMappingsByValueId.Step()) - { - DeleteValueById.Reset(); - DeleteValueById.Bind(1, valueId); - - DeleteValueById.Execute(); - } - } - - private: - // Bind valid rowid to 1. - SQLite::Statement SelectIfAnyMappingsByValueId; - // Bind valid rowid to 1. - SQLite::Statement DeleteValueById; - }; - - bool SchemaVersionUsesNamedIndices(OneToManyTableSchema schemaVersion) - { - return schemaVersion != OneToManyTableSchema::Version_1_0; - } - } - - std::string OneToManyTableGetMapTableName(std::string_view tableName) - { - std::string result(tableName); - result += s_OneToManyTable_MapTable_Suffix; - return result; - } - - std::string_view OneToManyTableGetManifestColumnName() - { - return s_OneToManyTable_MapTable_ManifestName; - } - - void CreateOneToManyTable(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v1_0"); - - // Create the data table as a 1:1 - CreateOneToOneTable(connection, tableName, valueName, SchemaVersionUsesNamedIndices(schemaVersion)); - - switch (schemaVersion) - { - case OneToManyTableSchema::Version_1_0: - { - // Create the mapping table - StatementBuilder createMapTableBuilder; - createMapTableBuilder.CreateTable({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ - ColumnBuilder(s_OneToManyTable_MapTable_ManifestName, Type::Int64).NotNull(), - ColumnBuilder(valueName, Type::Int64).NotNull(), - PrimaryKeyBuilder({ valueName, s_OneToManyTable_MapTable_ManifestName }) - }); - - createMapTableBuilder.Execute(connection); - } - break; - case OneToManyTableSchema::Version_1_1: - { - // Create the mapping table - StatementBuilder createMapTableBuilder; - createMapTableBuilder.CreateTable({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ - ColumnBuilder(s_OneToManyTable_MapTable_ManifestName, Type::Int64).NotNull(), - ColumnBuilder(valueName, Type::Int64).NotNull() - }); - - createMapTableBuilder.Execute(connection); - - StatementBuilder pkIndexBuilder; - pkIndexBuilder.CreateUniqueIndex({ tableName, s_OneToManyTable_MapTable_Suffix, s_OneToManyTable_MapTable_PrimaryKeyIndexSuffix }). - On({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ valueName, s_OneToManyTable_MapTable_ManifestName }); - pkIndexBuilder.Execute(connection); - } - break; - case OneToManyTableSchema::Version_1_7: - { - // Create the mapping table - StatementBuilder createMapTableBuilder; - createMapTableBuilder.CreateTable({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ - ColumnBuilder(s_OneToManyTable_MapTable_ManifestName, Type::Int64).NotNull(), - ColumnBuilder(valueName, Type::Int64).NotNull(), - PrimaryKeyBuilder({ valueName, s_OneToManyTable_MapTable_ManifestName }) - }).WithoutRowID(); - - createMapTableBuilder.Execute(connection); - } - break; - default: - THROW_HR(E_UNEXPECTED); - } - - StatementBuilder createMapTableIndexBuilder; - createMapTableIndexBuilder.CreateIndex({ tableName, s_OneToManyTable_MapTable_Suffix, s_OneToManyTable_MapTable_IndexSuffix }). - On({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ s_OneToManyTable_MapTable_ManifestName, valueName }); - - createMapTableIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void DropOneToManyTable(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_drop_v1_0"); - - DropOneToOneTable(connection, tableName); - - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable({ tableName, s_OneToManyTable_MapTable_Suffix }); - - dropTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - std::vector OneToManyTableGetValuesByManifestId( - const SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - SQLite::rowid_t manifestId) - { - using QCol = SQLite::Builder::QualifiedColumn; - - std::vector result; - - SQLite::Builder::StatementBuilder builder; - builder.Select(QCol(tableName, valueName)). - From({ tableName, s_OneToManyTable_MapTable_Suffix }).As("map").Join(tableName). - On(QCol("map", valueName), QCol(tableName, SQLite::RowIDName)).Where(QCol("map", s_OneToManyTable_MapTable_ManifestName)).Equals(manifestId); - - SQLite::Statement statement = builder.Prepare(connection); - - while (statement.Step()) - { - result.emplace_back(statement.GetColumn(0)); - } - - return result; - } - - void OneToManyTableEnsureExistsAndInsert(SQLite::Connection& connection, - std::string_view tableName, std::string_view valueName, - const std::vector& values, SQLite::rowid_t manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensureandinsert_v1_0"); - - SQLite::Statement insertMapping = CreateMappingInsertStatementForManifestId(connection, tableName, valueName, manifestId); - - for (const std::string& value : values) - { - // First, ensure that the data exists - SQLite::rowid_t dataId = OneToOneTableEnsureExists(connection, tableName, valueName, value); - - // Second, insert into the mapping table - insertMapping.Reset(); - insertMapping.Bind(2, dataId); - - insertMapping.Execute(); - } - - savepoint.Commit(); - } - - bool OneToManyTableUpdateIfNeededByManifestId(SQLite::Connection& connection, - std::string_view tableName, std::string_view valueName, - const std::vector& values, SQLite::rowid_t manifestId) - { - std::vector oldValueIds = GetValueIdsByManifestId(connection, tableName, valueName, manifestId); - bool modificationNeeded = false; - - SQLite::Statement insertMapping = CreateMappingInsertStatementForManifestId(connection, tableName, valueName, manifestId); - - for (const std::string& value : values) - { - SQLite::rowid_t valueId = OneToOneTableEnsureExists(connection, tableName, valueName, value); - - auto itr = std::find(oldValueIds.begin(), oldValueIds.end(), valueId); - if (itr != oldValueIds.end()) - { - oldValueIds.erase(itr); - } - else - { - modificationNeeded = true; - - insertMapping.Reset(); - insertMapping.Bind(2, valueId); - - insertMapping.Execute(); - } - } - - // All incoming values are now present, we just need to delete the remaining old ones. - SQLite::Builder::StatementBuilder deleteBuilder; - deleteBuilder.DeleteFrom({ tableName, s_OneToManyTable_MapTable_Suffix }). - Where(s_OneToManyTable_MapTable_ManifestName).Equals(manifestId).And(valueName).Equals(SQLite::Builder::Unbound); - - SQLite::Statement deleteStatement = deleteBuilder.Prepare(connection); - - DeleteValueIfNotNeededStatements dvinns(connection, tableName, valueName); - - for (SQLite::rowid_t valueId : oldValueIds) - { - modificationNeeded = true; - - // First, delete the mapping - deleteStatement.Reset(); - deleteStatement.Bind(2, valueId); - - deleteStatement.Execute(); - - // Second, delete the value itself if not needed - dvinns.Execute(valueId); - } - - return modificationNeeded; - } - - void OneToManyTableDeleteIfNotNeededByManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_deleteifnotneeded_v1_0"); - - // Get values referenced by the manifest id. - std::vector values = GetValueIdsByManifestId(connection, tableName, valueName, manifestId); - - // Delete the mapping table rows with the manifest id. - SQLite::Builder::StatementBuilder deleteBuilder; - deleteBuilder.DeleteFrom({ tableName, s_OneToManyTable_MapTable_Suffix }).Where(s_OneToManyTable_MapTable_ManifestName).Equals(manifestId); - - deleteBuilder.Execute(connection); - - // For each value, see if any references exist - DeleteValueIfNotNeededStatements dvinns(connection, tableName, valueName); - - for (SQLite::rowid_t value : values) - { - dvinns.Execute(value); - } - - savepoint.Commit(); - } - - void OneToManyTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, OneToManyTableSchema schemaVersion, bool preserveManifestIndex, bool preserveValuesIndex) - { - if (!preserveManifestIndex) - { - SQLite::Builder::StatementBuilder dropMapTableIndexBuilder; - dropMapTableIndexBuilder.DropIndex({ tableName, s_OneToManyTable_MapTable_Suffix, s_OneToManyTable_MapTable_IndexSuffix }); - - dropMapTableIndexBuilder.Execute(connection); - } - - OneToOneTablePrepareForPackaging(connection, tableName, SchemaVersionUsesNamedIndices(schemaVersion), preserveValuesIndex); - } - - bool OneToManyTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - using QCol = SQLite::Builder::QualifiedColumn; - constexpr std::string_view s_map = "map"sv; - - bool result = true; - - { - // Build a select statement to find map rows containing references to manifests with nonexistent rowids - // Such as: - // Select map.rowid, map.manifest from tags_map as map left outer join manifest on map.manifest = manifest.rowid where manifest.id is null - - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_map, s_OneToManyTable_MapTable_ManifestName), QCol(s_map, valueName) }). - From({ tableName, s_OneToManyTable_MapTable_Suffix }).As(s_map). - LeftOuterJoin(ManifestTable::TableName()).On(QCol(s_map, s_OneToManyTable_MapTable_ManifestName), QCol(ManifestTable::TableName(), SQLite::RowIDName)). - Where(QCol(ManifestTable::TableName(), SQLite::RowIDName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTable_MapTable_Suffix << " [" << select.GetColumn(0) << - ", " << select.GetColumn(1) << "] refers to invalid " << ManifestTable::TableName()); - } - } - - if (!result && !log) - { - return result; - } - - { - // Build a select statement to find map rows containing references to 1:1 tables with nonexistent rowids - // Such as: - // Select map.rowid, map.tag from tags_map as map left outer join tags on map.tag = tags.rowid where tags.tag is null - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_map, s_OneToManyTable_MapTable_ManifestName), QCol(s_map, valueName) }). - From({ tableName, s_OneToManyTable_MapTable_Suffix }).As(s_map). - LeftOuterJoin(tableName).On(QCol(s_map, valueName), QCol(tableName, SQLite::RowIDName)). - Where(QCol(tableName, valueName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - bool secondaryResult = true; - - while (select.Step()) - { - secondaryResult = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTable_MapTable_Suffix << " [" << select.GetColumn(0) << - ", " << select.GetColumn(1) << "] refers to invalid " << tableName); - } - - result = result && secondaryResult; - } - - if (!result && !log) - { - return result; - } - - result = OneToOneTableCheckConsistency(connection, tableName, valueName, log) && result; - - return result; - } - - bool OneToManyTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder countBuilder; - countBuilder.Select(SQLite::Builder::RowCount).From(tableName); - - SQLite::Statement countStatement = countBuilder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - SQLite::Builder::StatementBuilder countMapBuilder; - countMapBuilder.Select(SQLite::Builder::RowCount).From({ tableName, s_OneToManyTable_MapTable_Suffix }); - - SQLite::Statement countMapStatement = countMapBuilder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countMapStatement.Step()); - - return ((countStatement.GetColumn(0) == 0) && (countMapStatement.GetColumn(0) == 0)); - } - - SQLite::Statement OneToManyTablePrepareMapDataFoldingStatement(const SQLite::Connection& connection, std::string_view tableName) - { - using namespace SQLite::Builder; - StatementBuilder builder; - - // Create a statement that will collapse (and dedupe) all rows in the map to the latest (max) manifest for a given id, like: - // UPDATE OR REPLACE map SET manifest = (SELECT MAX(rowid) FROM manifest_table WHERE id = ?1) WHERE manifest IN (SELECT rowid FROM manifest_table WHERE id = ?1) - builder.UpdateOrReplace({ tableName, s_OneToManyTable_MapTable_Suffix }).Set().Column(s_OneToManyTable_MapTable_ManifestName).Equals() - .BeginParenthetical() - .Select().Column(Aggregate::Max, SQLite::RowIDName).From(ManifestTable::TableName()).Where(IdTable::ValueName()).Equals(Unbound, 1) - .EndParenthetical() - .Where(s_OneToManyTable_MapTable_ManifestName).In() - .BeginParenthetical() - .Select(SQLite::RowIDName).From(ManifestTable::TableName()).Where(IdTable::ValueName()).Equals(Unbound, 1) - .EndParenthetical(); - - return builder.Prepare(connection); - } - } - - std::optional OneToManyTableGetMapDataFoldingManifestTargetId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - using namespace SQLite::Builder; - StatementBuilder builder; - - // Select the maximum manifest rowid from the manifests whose id is the same as the row with the given manifest rowid, like: - // SELECT MAX(rowid) FROM manifest_table WHERE id = (SELECT id FROM manifest_table WHERE rowid = ?) - builder.Select().Column(Aggregate::Max, SQLite::RowIDName).From(ManifestTable::TableName()).Where(IdTable::ValueName()).Equals() - .BeginParenthetical() - .Select(IdTable::ValueName()).From(ManifestTable::TableName()).Where(SQLite::RowIDName).Equals(manifestId) - .EndParenthetical(); - - SQLite::Statement statement = builder.Prepare(connection); - - if (statement.Step() && !statement.GetColumnIsNull(0)) - { - return statement.GetColumn(0); - } - - return std::nullopt; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_0/OneToManyTable.h" +#include "Microsoft/Schema/1_0/OneToOneTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_0/IdTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + static constexpr std::string_view s_OneToManyTable_MapTable_ManifestName = "manifest"sv; + static constexpr std::string_view s_OneToManyTable_MapTable_Suffix = "_map"sv; + static constexpr std::string_view s_OneToManyTable_MapTable_PrimaryKeyIndexSuffix = "_pkindex"sv; + static constexpr std::string_view s_OneToManyTable_MapTable_IndexSuffix = "_index"sv; + + namespace + { + // Create the mapping table insert statement for multiple use. + // Bind the rowid of the value to 2. + SQLite::Statement CreateMappingInsertStatementForManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) + { + SQLite::Builder::StatementBuilder insertMappingBuilder; + insertMappingBuilder.InsertInto({ tableName, s_OneToManyTable_MapTable_Suffix }). + Columns({ s_OneToManyTable_MapTable_ManifestName, valueName }).Values(manifestId, SQLite::Builder::Unbound); + + return insertMappingBuilder.Prepare(connection); + } + + // Get a collection of the value ids associated with the given manifest id. + std::vector GetValueIdsByManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) + { + std::vector result; + + SQLite::Builder::StatementBuilder selectMappingBuilder; + selectMappingBuilder.Select(valueName).From({ tableName, s_OneToManyTable_MapTable_Suffix }).Where(s_OneToManyTable_MapTable_ManifestName).Equals(manifestId); + + SQLite::Statement selectMappingStatement = selectMappingBuilder.Prepare(connection); + + while (selectMappingStatement.Step()) + { + result.push_back(selectMappingStatement.GetColumn(0)); + } + + return result; + } + + struct DeleteValueIfNotNeededStatements + { + DeleteValueIfNotNeededStatements(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) + { + SQLite::Builder::StatementBuilder selectValueMappingBuilder; + selectValueMappingBuilder.Select(s_OneToManyTable_MapTable_ManifestName).From({ tableName, s_OneToManyTable_MapTable_Suffix }).Where(valueName).Equals(SQLite::Builder::Unbound).Limit(1); + + SelectIfAnyMappingsByValueId = selectValueMappingBuilder.Prepare(connection); + + SQLite::Builder::StatementBuilder deleteValueBuilder; + deleteValueBuilder.DeleteFrom(tableName).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); + + DeleteValueById = deleteValueBuilder.Prepare(connection); + } + + void Execute(SQLite::rowid_t valueId) + { + SelectIfAnyMappingsByValueId.Reset(); + SelectIfAnyMappingsByValueId.Bind(1, valueId); + + // If no rows are found, we can delete the data. + if (!SelectIfAnyMappingsByValueId.Step()) + { + DeleteValueById.Reset(); + DeleteValueById.Bind(1, valueId); + + DeleteValueById.Execute(); + } + } + + private: + // Bind valid rowid to 1. + SQLite::Statement SelectIfAnyMappingsByValueId; + // Bind valid rowid to 1. + SQLite::Statement DeleteValueById; + }; + + bool SchemaVersionUsesNamedIndices(OneToManyTableSchema schemaVersion) + { + return schemaVersion != OneToManyTableSchema::Version_1_0; + } + } + + std::string OneToManyTableGetMapTableName(std::string_view tableName) + { + std::string result(tableName); + result += s_OneToManyTable_MapTable_Suffix; + return result; + } + + std::string_view OneToManyTableGetManifestColumnName() + { + return s_OneToManyTable_MapTable_ManifestName; + } + + void CreateOneToManyTable(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v1_0"); + + // Create the data table as a 1:1 + CreateOneToOneTable(connection, tableName, valueName, SchemaVersionUsesNamedIndices(schemaVersion)); + + switch (schemaVersion) + { + case OneToManyTableSchema::Version_1_0: + { + // Create the mapping table + StatementBuilder createMapTableBuilder; + createMapTableBuilder.CreateTable({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ + ColumnBuilder(s_OneToManyTable_MapTable_ManifestName, Type::Int64).NotNull(), + ColumnBuilder(valueName, Type::Int64).NotNull(), + PrimaryKeyBuilder({ valueName, s_OneToManyTable_MapTable_ManifestName }) + }); + + createMapTableBuilder.Execute(connection); + } + break; + case OneToManyTableSchema::Version_1_1: + { + // Create the mapping table + StatementBuilder createMapTableBuilder; + createMapTableBuilder.CreateTable({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ + ColumnBuilder(s_OneToManyTable_MapTable_ManifestName, Type::Int64).NotNull(), + ColumnBuilder(valueName, Type::Int64).NotNull() + }); + + createMapTableBuilder.Execute(connection); + + StatementBuilder pkIndexBuilder; + pkIndexBuilder.CreateUniqueIndex({ tableName, s_OneToManyTable_MapTable_Suffix, s_OneToManyTable_MapTable_PrimaryKeyIndexSuffix }). + On({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ valueName, s_OneToManyTable_MapTable_ManifestName }); + pkIndexBuilder.Execute(connection); + } + break; + case OneToManyTableSchema::Version_1_7: + { + // Create the mapping table + StatementBuilder createMapTableBuilder; + createMapTableBuilder.CreateTable({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ + ColumnBuilder(s_OneToManyTable_MapTable_ManifestName, Type::Int64).NotNull(), + ColumnBuilder(valueName, Type::Int64).NotNull(), + PrimaryKeyBuilder({ valueName, s_OneToManyTable_MapTable_ManifestName }) + }).WithoutRowID(); + + createMapTableBuilder.Execute(connection); + } + break; + default: + THROW_HR(E_UNEXPECTED); + } + + StatementBuilder createMapTableIndexBuilder; + createMapTableIndexBuilder.CreateIndex({ tableName, s_OneToManyTable_MapTable_Suffix, s_OneToManyTable_MapTable_IndexSuffix }). + On({ tableName, s_OneToManyTable_MapTable_Suffix }).Columns({ s_OneToManyTable_MapTable_ManifestName, valueName }); + + createMapTableIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void DropOneToManyTable(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_drop_v1_0"); + + DropOneToOneTable(connection, tableName); + + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable({ tableName, s_OneToManyTable_MapTable_Suffix }); + + dropTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::vector OneToManyTableGetValuesByManifestId( + const SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + SQLite::rowid_t manifestId) + { + using QCol = SQLite::Builder::QualifiedColumn; + + std::vector result; + + SQLite::Builder::StatementBuilder builder; + builder.Select(QCol(tableName, valueName)). + From({ tableName, s_OneToManyTable_MapTable_Suffix }).As("map").Join(tableName). + On(QCol("map", valueName), QCol(tableName, SQLite::RowIDName)).Where(QCol("map", s_OneToManyTable_MapTable_ManifestName)).Equals(manifestId); + + SQLite::Statement statement = builder.Prepare(connection); + + while (statement.Step()) + { + result.emplace_back(statement.GetColumn(0)); + } + + return result; + } + + void OneToManyTableEnsureExistsAndInsert(SQLite::Connection& connection, + std::string_view tableName, std::string_view valueName, + const std::vector& values, SQLite::rowid_t manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensureandinsert_v1_0"); + + SQLite::Statement insertMapping = CreateMappingInsertStatementForManifestId(connection, tableName, valueName, manifestId); + + for (const std::string& value : values) + { + // First, ensure that the data exists + SQLite::rowid_t dataId = OneToOneTableEnsureExists(connection, tableName, valueName, value); + + // Second, insert into the mapping table + insertMapping.Reset(); + insertMapping.Bind(2, dataId); + + insertMapping.Execute(); + } + + savepoint.Commit(); + } + + bool OneToManyTableUpdateIfNeededByManifestId(SQLite::Connection& connection, + std::string_view tableName, std::string_view valueName, + const std::vector& values, SQLite::rowid_t manifestId) + { + std::vector oldValueIds = GetValueIdsByManifestId(connection, tableName, valueName, manifestId); + bool modificationNeeded = false; + + SQLite::Statement insertMapping = CreateMappingInsertStatementForManifestId(connection, tableName, valueName, manifestId); + + for (const std::string& value : values) + { + SQLite::rowid_t valueId = OneToOneTableEnsureExists(connection, tableName, valueName, value); + + auto itr = std::find(oldValueIds.begin(), oldValueIds.end(), valueId); + if (itr != oldValueIds.end()) + { + oldValueIds.erase(itr); + } + else + { + modificationNeeded = true; + + insertMapping.Reset(); + insertMapping.Bind(2, valueId); + + insertMapping.Execute(); + } + } + + // All incoming values are now present, we just need to delete the remaining old ones. + SQLite::Builder::StatementBuilder deleteBuilder; + deleteBuilder.DeleteFrom({ tableName, s_OneToManyTable_MapTable_Suffix }). + Where(s_OneToManyTable_MapTable_ManifestName).Equals(manifestId).And(valueName).Equals(SQLite::Builder::Unbound); + + SQLite::Statement deleteStatement = deleteBuilder.Prepare(connection); + + DeleteValueIfNotNeededStatements dvinns(connection, tableName, valueName); + + for (SQLite::rowid_t valueId : oldValueIds) + { + modificationNeeded = true; + + // First, delete the mapping + deleteStatement.Reset(); + deleteStatement.Bind(2, valueId); + + deleteStatement.Execute(); + + // Second, delete the value itself if not needed + dvinns.Execute(valueId); + } + + return modificationNeeded; + } + + void OneToManyTableDeleteIfNotNeededByManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_deleteifnotneeded_v1_0"); + + // Get values referenced by the manifest id. + std::vector values = GetValueIdsByManifestId(connection, tableName, valueName, manifestId); + + // Delete the mapping table rows with the manifest id. + SQLite::Builder::StatementBuilder deleteBuilder; + deleteBuilder.DeleteFrom({ tableName, s_OneToManyTable_MapTable_Suffix }).Where(s_OneToManyTable_MapTable_ManifestName).Equals(manifestId); + + deleteBuilder.Execute(connection); + + // For each value, see if any references exist + DeleteValueIfNotNeededStatements dvinns(connection, tableName, valueName); + + for (SQLite::rowid_t value : values) + { + dvinns.Execute(value); + } + + savepoint.Commit(); + } + + void OneToManyTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, OneToManyTableSchema schemaVersion, bool preserveManifestIndex, bool preserveValuesIndex) + { + if (!preserveManifestIndex) + { + SQLite::Builder::StatementBuilder dropMapTableIndexBuilder; + dropMapTableIndexBuilder.DropIndex({ tableName, s_OneToManyTable_MapTable_Suffix, s_OneToManyTable_MapTable_IndexSuffix }); + + dropMapTableIndexBuilder.Execute(connection); + } + + OneToOneTablePrepareForPackaging(connection, tableName, SchemaVersionUsesNamedIndices(schemaVersion), preserveValuesIndex); + } + + bool OneToManyTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + using QCol = SQLite::Builder::QualifiedColumn; + constexpr std::string_view s_map = "map"sv; + + bool result = true; + + { + // Build a select statement to find map rows containing references to manifests with nonexistent rowids + // Such as: + // Select map.rowid, map.manifest from tags_map as map left outer join manifest on map.manifest = manifest.rowid where manifest.id is null + + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_map, s_OneToManyTable_MapTable_ManifestName), QCol(s_map, valueName) }). + From({ tableName, s_OneToManyTable_MapTable_Suffix }).As(s_map). + LeftOuterJoin(ManifestTable::TableName()).On(QCol(s_map, s_OneToManyTable_MapTable_ManifestName), QCol(ManifestTable::TableName(), SQLite::RowIDName)). + Where(QCol(ManifestTable::TableName(), SQLite::RowIDName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTable_MapTable_Suffix << " [" << select.GetColumn(0) << + ", " << select.GetColumn(1) << "] refers to invalid " << ManifestTable::TableName()); + } + } + + if (!result && !log) + { + return result; + } + + { + // Build a select statement to find map rows containing references to 1:1 tables with nonexistent rowids + // Such as: + // Select map.rowid, map.tag from tags_map as map left outer join tags on map.tag = tags.rowid where tags.tag is null + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_map, s_OneToManyTable_MapTable_ManifestName), QCol(s_map, valueName) }). + From({ tableName, s_OneToManyTable_MapTable_Suffix }).As(s_map). + LeftOuterJoin(tableName).On(QCol(s_map, valueName), QCol(tableName, SQLite::RowIDName)). + Where(QCol(tableName, valueName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + bool secondaryResult = true; + + while (select.Step()) + { + secondaryResult = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTable_MapTable_Suffix << " [" << select.GetColumn(0) << + ", " << select.GetColumn(1) << "] refers to invalid " << tableName); + } + + result = result && secondaryResult; + } + + if (!result && !log) + { + return result; + } + + result = OneToOneTableCheckConsistency(connection, tableName, valueName, log) && result; + + return result; + } + + bool OneToManyTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder countBuilder; + countBuilder.Select(SQLite::Builder::RowCount).From(tableName); + + SQLite::Statement countStatement = countBuilder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + SQLite::Builder::StatementBuilder countMapBuilder; + countMapBuilder.Select(SQLite::Builder::RowCount).From({ tableName, s_OneToManyTable_MapTable_Suffix }); + + SQLite::Statement countMapStatement = countMapBuilder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countMapStatement.Step()); + + return ((countStatement.GetColumn(0) == 0) && (countMapStatement.GetColumn(0) == 0)); + } + + SQLite::Statement OneToManyTablePrepareMapDataFoldingStatement(const SQLite::Connection& connection, std::string_view tableName) + { + using namespace SQLite::Builder; + StatementBuilder builder; + + // Create a statement that will collapse (and dedupe) all rows in the map to the latest (max) manifest for a given id, like: + // UPDATE OR REPLACE map SET manifest = (SELECT MAX(rowid) FROM manifest_table WHERE id = ?1) WHERE manifest IN (SELECT rowid FROM manifest_table WHERE id = ?1) + builder.UpdateOrReplace({ tableName, s_OneToManyTable_MapTable_Suffix }).Set().Column(s_OneToManyTable_MapTable_ManifestName).Equals() + .BeginParenthetical() + .Select().Column(Aggregate::Max, SQLite::RowIDName).From(ManifestTable::TableName()).Where(IdTable::ValueName()).Equals(Unbound, 1) + .EndParenthetical() + .Where(s_OneToManyTable_MapTable_ManifestName).In() + .BeginParenthetical() + .Select(SQLite::RowIDName).From(ManifestTable::TableName()).Where(IdTable::ValueName()).Equals(Unbound, 1) + .EndParenthetical(); + + return builder.Prepare(connection); + } + } + + std::optional OneToManyTableGetMapDataFoldingManifestTargetId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + using namespace SQLite::Builder; + StatementBuilder builder; + + // Select the maximum manifest rowid from the manifests whose id is the same as the row with the given manifest rowid, like: + // SELECT MAX(rowid) FROM manifest_table WHERE id = (SELECT id FROM manifest_table WHERE rowid = ?) + builder.Select().Column(Aggregate::Max, SQLite::RowIDName).From(ManifestTable::TableName()).Where(IdTable::ValueName()).Equals() + .BeginParenthetical() + .Select(IdTable::ValueName()).From(ManifestTable::TableName()).Where(SQLite::RowIDName).Equals(manifestId) + .EndParenthetical(); + + SQLite::Statement statement = builder.Prepare(connection); + + if (statement.Step() && !statement.GetColumnIsNull(0)) + { + return statement.GetColumn(0); + } + + return std::nullopt; + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.h index cabb3c4f65..170d41c4ca 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToManyTable.h @@ -1,162 +1,162 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - // Allow the different schema version to indicate which they are. - enum class OneToManyTableSchema - { - // Does not used a named unique index for either table. - // Map table has primary key and rowid. - Version_1_0, - // Uses a named unique index for both tables. - // Map table has rowid. - Version_1_1, - // Uses a named unique index for data table. (No change from 1.1) - // Map table has primary key and no rowid. - Version_1_7, - }; - - namespace details - { - // Returns the map table name for a given table. - std::string OneToManyTableGetMapTableName(std::string_view tableName); - - // Returns the manifest column name. - std::string_view OneToManyTableGetManifestColumnName(); - - // Create the tables. - void CreateOneToManyTable(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName); - - // Drop the tables. - void DropOneToManyTable(SQLite::Connection& connection, std::string_view tableName); - - // Gets all values associated with the given manifest id. - std::vector OneToManyTableGetValuesByManifestId( - const SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - SQLite::rowid_t manifestId); - - // Ensures that the value exists and inserts mapping entries. - void OneToManyTableEnsureExistsAndInsert(SQLite::Connection& connection, - std::string_view tableName, std::string_view valueName, - const std::vector& values, SQLite::rowid_t manifestId); - - // Updates the mapping table to represent the given values for the manifest. - bool OneToManyTableUpdateIfNeededByManifestId(SQLite::Connection& connection, - std::string_view tableName, std::string_view valueName, - const std::vector& values, SQLite::rowid_t manifestId); - - // Deletes the mapping rows for the given manifest, then removes any unused data rows. - void OneToManyTableDeleteIfNotNeededByManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId); - - // Removes data that is no longer needed for an index that is to be published. - void OneToManyTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, OneToManyTableSchema schemaVersion, bool preserveManifestIndex, bool preserveValuesIndex); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - bool OneToManyTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); - - // Determines if the table is empty. - bool OneToManyTableIsEmpty(SQLite::Connection& connection, std::string_view tableName); - - // Prepares a statement for replacing all of the map data to point to a single manifest for a given id. - SQLite::Statement OneToManyTablePrepareMapDataFoldingStatement(const SQLite::Connection& connection, std::string_view tableName); - } - - // Gets the manifest id that the PrepareMapDataFoldingStatement will fold to for the given manifest id. - std::optional OneToManyTableGetMapDataFoldingManifestTargetId(const SQLite::Connection& connection, SQLite::rowid_t manifestId); - - // A table that represents a value that is 1:N with a primary entry. - template - struct OneToManyTable - { - // The name of the table. - static constexpr std::string_view TableName() - { - return TableInfo::TableName(); - } - - // The value name of the table. - static constexpr std::string_view ValueName() - { - return TableInfo::ValueName(); - } - - // Value indicating type. - static constexpr bool IsOneToOne() - { - return false; - } - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection, OneToManyTableSchema schemaVersion) - { - details::CreateOneToManyTable(connection, schemaVersion, TableInfo::TableName(), TableInfo::ValueName()); - } - - // Drops the table. - static void Drop(SQLite::Connection& connection) - { - details::DropOneToManyTable(connection, TableInfo::TableName()); - } - - // Gets all values associated with the given manifest id. - static std::vector GetValuesByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - return details::OneToManyTableGetValuesByManifestId(connection, TableInfo::TableName(), TableInfo::ValueName(), manifestId); - } - - // Ensures that all values exist in the data table, and inserts into the mapping table for the given manifest id. - static void EnsureExistsAndInsert(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t manifestId) - { - details::OneToManyTableEnsureExistsAndInsert(connection, TableInfo::TableName(), TableInfo::ValueName(), values, manifestId); - } - - // Updates the mapping table to represent the given values for the manifest. - static bool UpdateIfNeededByManifestId(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t manifestId) - { - return details::OneToManyTableUpdateIfNeededByManifestId(connection, TableInfo::TableName(), TableInfo::ValueName(), values, manifestId); - } - - // Deletes the mapping rows for the given manifest, then removes any unused data rows. - static void DeleteIfNotNeededByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - details::OneToManyTableDeleteIfNotNeededByManifestId(connection, TableInfo::TableName(), TableInfo::ValueName(), manifestId); - } - - // Removes data that is no longer needed for an index that is to be published. - // Preserving the manifest index will improve the efficiency of finding the values associated with a manifest. - // Preserving the values index will improve searching when it is primarily done by equality. - static void PrepareForPackaging(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, bool preserveManifestIndex, bool preserveValuesIndex) - { - details::OneToManyTablePrepareForPackaging(connection, TableInfo::TableName(), schemaVersion, preserveManifestIndex, preserveValuesIndex); - } - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - static bool CheckConsistency(const SQLite::Connection& connection, bool log) - { - return details::OneToManyTableCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); - } - - // Determines if the table is empty. - static bool IsEmpty(SQLite::Connection& connection) - { - return details::OneToManyTableIsEmpty(connection, TableInfo::TableName()); - } - - // Prepares a statement for replacing all of the map data to point to a single manifest for a given id. - static SQLite::Statement PrepareMapDataFoldingStatement(const SQLite::Connection& connection) - { - return details::OneToManyTablePrepareMapDataFoldingStatement(connection, TableInfo::TableName()); - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + // Allow the different schema version to indicate which they are. + enum class OneToManyTableSchema + { + // Does not used a named unique index for either table. + // Map table has primary key and rowid. + Version_1_0, + // Uses a named unique index for both tables. + // Map table has rowid. + Version_1_1, + // Uses a named unique index for data table. (No change from 1.1) + // Map table has primary key and no rowid. + Version_1_7, + }; + + namespace details + { + // Returns the map table name for a given table. + std::string OneToManyTableGetMapTableName(std::string_view tableName); + + // Returns the manifest column name. + std::string_view OneToManyTableGetManifestColumnName(); + + // Create the tables. + void CreateOneToManyTable(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName); + + // Drop the tables. + void DropOneToManyTable(SQLite::Connection& connection, std::string_view tableName); + + // Gets all values associated with the given manifest id. + std::vector OneToManyTableGetValuesByManifestId( + const SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + SQLite::rowid_t manifestId); + + // Ensures that the value exists and inserts mapping entries. + void OneToManyTableEnsureExistsAndInsert(SQLite::Connection& connection, + std::string_view tableName, std::string_view valueName, + const std::vector& values, SQLite::rowid_t manifestId); + + // Updates the mapping table to represent the given values for the manifest. + bool OneToManyTableUpdateIfNeededByManifestId(SQLite::Connection& connection, + std::string_view tableName, std::string_view valueName, + const std::vector& values, SQLite::rowid_t manifestId); + + // Deletes the mapping rows for the given manifest, then removes any unused data rows. + void OneToManyTableDeleteIfNotNeededByManifestId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId); + + // Removes data that is no longer needed for an index that is to be published. + void OneToManyTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, OneToManyTableSchema schemaVersion, bool preserveManifestIndex, bool preserveValuesIndex); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + bool OneToManyTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); + + // Determines if the table is empty. + bool OneToManyTableIsEmpty(SQLite::Connection& connection, std::string_view tableName); + + // Prepares a statement for replacing all of the map data to point to a single manifest for a given id. + SQLite::Statement OneToManyTablePrepareMapDataFoldingStatement(const SQLite::Connection& connection, std::string_view tableName); + } + + // Gets the manifest id that the PrepareMapDataFoldingStatement will fold to for the given manifest id. + std::optional OneToManyTableGetMapDataFoldingManifestTargetId(const SQLite::Connection& connection, SQLite::rowid_t manifestId); + + // A table that represents a value that is 1:N with a primary entry. + template + struct OneToManyTable + { + // The name of the table. + static constexpr std::string_view TableName() + { + return TableInfo::TableName(); + } + + // The value name of the table. + static constexpr std::string_view ValueName() + { + return TableInfo::ValueName(); + } + + // Value indicating type. + static constexpr bool IsOneToOne() + { + return false; + } + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection, OneToManyTableSchema schemaVersion) + { + details::CreateOneToManyTable(connection, schemaVersion, TableInfo::TableName(), TableInfo::ValueName()); + } + + // Drops the table. + static void Drop(SQLite::Connection& connection) + { + details::DropOneToManyTable(connection, TableInfo::TableName()); + } + + // Gets all values associated with the given manifest id. + static std::vector GetValuesByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + return details::OneToManyTableGetValuesByManifestId(connection, TableInfo::TableName(), TableInfo::ValueName(), manifestId); + } + + // Ensures that all values exist in the data table, and inserts into the mapping table for the given manifest id. + static void EnsureExistsAndInsert(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t manifestId) + { + details::OneToManyTableEnsureExistsAndInsert(connection, TableInfo::TableName(), TableInfo::ValueName(), values, manifestId); + } + + // Updates the mapping table to represent the given values for the manifest. + static bool UpdateIfNeededByManifestId(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t manifestId) + { + return details::OneToManyTableUpdateIfNeededByManifestId(connection, TableInfo::TableName(), TableInfo::ValueName(), values, manifestId); + } + + // Deletes the mapping rows for the given manifest, then removes any unused data rows. + static void DeleteIfNotNeededByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + details::OneToManyTableDeleteIfNotNeededByManifestId(connection, TableInfo::TableName(), TableInfo::ValueName(), manifestId); + } + + // Removes data that is no longer needed for an index that is to be published. + // Preserving the manifest index will improve the efficiency of finding the values associated with a manifest. + // Preserving the values index will improve searching when it is primarily done by equality. + static void PrepareForPackaging(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, bool preserveManifestIndex, bool preserveValuesIndex) + { + details::OneToManyTablePrepareForPackaging(connection, TableInfo::TableName(), schemaVersion, preserveManifestIndex, preserveValuesIndex); + } + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + static bool CheckConsistency(const SQLite::Connection& connection, bool log) + { + return details::OneToManyTableCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); + } + + // Determines if the table is empty. + static bool IsEmpty(SQLite::Connection& connection) + { + return details::OneToManyTableIsEmpty(connection, TableInfo::TableName()); + } + + // Prepares a statement for replacing all of the map data to point to a single manifest for a given id. + static SQLite::Statement PrepareMapDataFoldingStatement(const SQLite::Connection& connection) + { + return details::OneToManyTablePrepareMapDataFoldingStatement(connection, TableInfo::TableName()); + } + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp index 2301d0d4b3..cccbcd2c6d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.cpp @@ -1,217 +1,217 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_0/OneToOneTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - static constexpr std::string_view s_OneToOneTable_IndexSuffix = "_pkindex"sv; - - void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool useNamedIndices) - { - using namespace SQLite::Builder; - - // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. - // The resulting database will function the same, but give us control to drop the indices to reduce space. - if (useNamedIndices) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v1_1"); - - StatementBuilder createTableBuilder; - - createTableBuilder.CreateTable(tableName).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(valueName, Type::Text).NotNull() - }); - - createTableBuilder.Execute(connection); - - StatementBuilder indexBuilder; - indexBuilder.CreateUniqueIndex({ tableName, s_OneToOneTable_IndexSuffix }).On(tableName).Columns(valueName); - indexBuilder.Execute(connection); - - savepoint.Commit(); - } - else - { - StatementBuilder createTableBuilder; - - createTableBuilder.CreateTable(tableName).Columns({ - ColumnBuilder(valueName, Type::Text).NotNull().PrimaryKey() - }); - - createTableBuilder.Execute(connection); - } - } - - void DropOneToOneTable(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(tableName); - - dropTableBuilder.Execute(connection); - } - - std::optional OneToOneTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); - - if (useLike) - { - selectBuilder.LikeWithEscape(value); - } - else - { - selectBuilder.Equals(value); - } - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - std::optional OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - std::vector OneToOneTableGetAllRowIds(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, size_t limit) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(tableName).OrderBy(valueName); - - if (limit) - { - selectBuilder.Limit(limit); - } - - SQLite::Statement select = selectBuilder.Prepare(connection); - - std::vector result; - while (select.Step()) - { - result.emplace_back(select.GetColumn(0)); - } - return result; - } - - SQLite::rowid_t OneToOneTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch) - { - auto selectResult = OneToOneTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); - if (selectResult) - { - if (overwriteLikeMatch) - { - // If the value in the table is not an exact match, overwrite it with the incoming value - auto tableValue = OneToOneTableSelectValueById(connection, tableName, valueName, selectResult.value()); - if (tableValue.value() != value) - { - SQLite::Builder::StatementBuilder updateBuilder; - updateBuilder.Update(tableName).Set().Column(valueName).Equals(value).Where(SQLite::RowIDName).Equals(selectResult); - - updateBuilder.Execute(connection); - } - } - - return selectResult.value(); - } - - SQLite::Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); - - insertBuilder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - void OneToOneTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, bool useNamedIndices, bool preserveValuesIndex) - { - if (useNamedIndices && !preserveValuesIndex) - { - SQLite::Builder::StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex({ tableName, s_OneToOneTable_IndexSuffix }); - dropIndexBuilder.Execute(connection); - } - } - - uint64_t OneToOneTableGetCount(const SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(tableName); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return static_cast(countStatement.GetColumn(0)); - } - - bool OneToOneTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) - { - return (OneToOneTableGetCount(connection, tableName) == 0); - } - - void OneToOneTableDeleteById(SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(tableName).Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - } - - bool OneToOneTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - // Build a select statement to find values that contain an embedded null character - // Such as: - // Select count(*) from table where instr(value,char(0))>0 - SQLite::Builder::StatementBuilder builder; - builder. - Select({ SQLite::RowIDName, valueName }). - From(tableName). - WhereValueContainsEmbeddedNullCharacter(valueName); - - SQLite::Statement select = builder.Prepare(connection); - bool result = true; - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); - } - - return result; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_0/OneToOneTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + static constexpr std::string_view s_OneToOneTable_IndexSuffix = "_pkindex"sv; + + void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool useNamedIndices) + { + using namespace SQLite::Builder; + + // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. + // The resulting database will function the same, but give us control to drop the indices to reduce space. + if (useNamedIndices) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v1_1"); + + StatementBuilder createTableBuilder; + + createTableBuilder.CreateTable(tableName).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(valueName, Type::Text).NotNull() + }); + + createTableBuilder.Execute(connection); + + StatementBuilder indexBuilder; + indexBuilder.CreateUniqueIndex({ tableName, s_OneToOneTable_IndexSuffix }).On(tableName).Columns(valueName); + indexBuilder.Execute(connection); + + savepoint.Commit(); + } + else + { + StatementBuilder createTableBuilder; + + createTableBuilder.CreateTable(tableName).Columns({ + ColumnBuilder(valueName, Type::Text).NotNull().PrimaryKey() + }); + + createTableBuilder.Execute(connection); + } + } + + void DropOneToOneTable(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(tableName); + + dropTableBuilder.Execute(connection); + } + + std::optional OneToOneTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); + + if (useLike) + { + selectBuilder.LikeWithEscape(value); + } + else + { + selectBuilder.Equals(value); + } + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + std::optional OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + std::vector OneToOneTableGetAllRowIds(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, size_t limit) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(SQLite::RowIDName).From(tableName).OrderBy(valueName); + + if (limit) + { + selectBuilder.Limit(limit); + } + + SQLite::Statement select = selectBuilder.Prepare(connection); + + std::vector result; + while (select.Step()) + { + result.emplace_back(select.GetColumn(0)); + } + return result; + } + + SQLite::rowid_t OneToOneTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch) + { + auto selectResult = OneToOneTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); + if (selectResult) + { + if (overwriteLikeMatch) + { + // If the value in the table is not an exact match, overwrite it with the incoming value + auto tableValue = OneToOneTableSelectValueById(connection, tableName, valueName, selectResult.value()); + if (tableValue.value() != value) + { + SQLite::Builder::StatementBuilder updateBuilder; + updateBuilder.Update(tableName).Set().Column(valueName).Equals(value).Where(SQLite::RowIDName).Equals(selectResult); + + updateBuilder.Execute(connection); + } + } + + return selectResult.value(); + } + + SQLite::Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); + + insertBuilder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + void OneToOneTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, bool useNamedIndices, bool preserveValuesIndex) + { + if (useNamedIndices && !preserveValuesIndex) + { + SQLite::Builder::StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex({ tableName, s_OneToOneTable_IndexSuffix }); + dropIndexBuilder.Execute(connection); + } + } + + uint64_t OneToOneTableGetCount(const SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(tableName); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return static_cast(countStatement.GetColumn(0)); + } + + bool OneToOneTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) + { + return (OneToOneTableGetCount(connection, tableName) == 0); + } + + void OneToOneTableDeleteById(SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(tableName).Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + } + + bool OneToOneTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + // Build a select statement to find values that contain an embedded null character + // Such as: + // Select count(*) from table where instr(value,char(0))>0 + SQLite::Builder::StatementBuilder builder; + builder. + Select({ SQLite::RowIDName, valueName }). + From(tableName). + WhereValueContainsEmbeddedNullCharacter(valueName); + + SQLite::Statement select = builder.Prepare(connection); + bool result = true; + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); + } + + return result; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.h index 940d809c57..60467f6d66 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/OneToOneTable.h @@ -1,156 +1,156 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - // Creates the table. - void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool useNamedIndices); - - // Drops the table. - void DropOneToOneTable(SQLite::Connection& connection, std::string_view tableName); - - // Selects the value from the table, returning the rowid if it exists. - std::optional OneToOneTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike = false); - - // Selects the value from the table, returning the rowid if it exists. - std::optional OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id); - - // Gets all row ids from the table. - std::vector OneToOneTableGetAllRowIds(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, size_t limit); - - // Ensures that the values exists in the table. - SQLite::rowid_t OneToOneTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch = false); - - // Removes data that is no longer needed for an index that is to be published. - void OneToOneTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, bool useNamedIndices, bool preserveValuesIndex); - - // Gets the total number of rows in the table. - uint64_t OneToOneTableGetCount(const SQLite::Connection& connection, std::string_view tableName); - - // Determines if the table is empty. - bool OneToOneTableIsEmpty(SQLite::Connection& connection, std::string_view tableName); - - // Removes the given row by its rowid if it is no longer referenced. - void OneToOneTableDeleteById(SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t id); - - // Checks the consistency of the table. - bool OneToOneTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); - } - - // A table that represents a value that is 1:1 with a primary entry. - template - struct OneToOneTable - { - // The value type. - using value_t = std::string; - - // The id type - using id_t = SQLite::rowid_t; - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection) - { - details::CreateOneToOneTable(connection, TableInfo::TableName(), TableInfo::ValueName(), true); - } - - // Creates the table with standard primary keys. - static void Create_deprecated(SQLite::Connection& connection) - { - details::CreateOneToOneTable(connection, TableInfo::TableName(), TableInfo::ValueName(), false); - } - - // Drops the table. - static void Drop(SQLite::Connection& connection) - { - details::DropOneToOneTable(connection, TableInfo::TableName()); - } - - // The name of the table. - static constexpr std::string_view TableName() - { - return TableInfo::TableName(); - } - - // The value name of the table. - static constexpr std::string_view ValueName() - { - return TableInfo::ValueName(); - } - - // Value indicating type. - static constexpr bool IsOneToOne() - { - return true; - } - - // Selects the value from the table, returning the rowid if it exists. - static std::optional SelectIdByValue(const SQLite::Connection& connection, std::string_view value, bool useLike = false) - { - return details::OneToOneTableSelectIdByValue(connection, TableInfo::TableName(), TableInfo::ValueName(), value, useLike); - } - - // Selects the value from the table, returning it if it exists. - static std::optional SelectValueById(SQLite::Connection& connection, id_t id) - { - return details::OneToOneTableSelectValueById(connection, TableInfo::TableName(), TableInfo::ValueName(), id); - } - - // Gets all row ids from the table. - static std::vector GetAllRowIds(const SQLite::Connection& connection, size_t limit = 0) - { - return details::OneToOneTableGetAllRowIds(connection, TableInfo::TableName(), TableInfo::ValueName(), limit); - } - - // Ensures that the given value exists in the table, returning the rowid. - static SQLite::rowid_t EnsureExists(SQLite::Connection& connection, std::string_view value, bool overwriteLikeMatch = false) - { - return details::OneToOneTableEnsureExists(connection, TableInfo::TableName(), TableInfo::ValueName(), value, overwriteLikeMatch); - } - - // Removes the given row by its rowid if it is no longer referenced. - static void DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) - { - return details::OneToOneTableDeleteById(connection, TableInfo::TableName(), id); - } - - // Removes data that is no longer needed for an index that is to be published. - // Preserving the values index will improve searching when it is primarily done by equality. - static void PrepareForPackaging(SQLite::Connection& connection, bool preserveValuesIndex = false) - { - details::OneToOneTablePrepareForPackaging(connection, TableInfo::TableName(), true, preserveValuesIndex); - } - - // Removes data that is no longer needed for an index that is to be published. - static void PrepareForPackaging_deprecated(SQLite::Connection& connection) - { - details::OneToOneTablePrepareForPackaging(connection, TableInfo::TableName(), false, false); - } - - // Gets the total number of rows in the table. - static uint64_t GetCount(const SQLite::Connection& connection) - { - return details::OneToOneTableGetCount(connection, TableInfo::TableName()); - } - - // Determines if the table is empty. - static bool IsEmpty(SQLite::Connection& connection) - { - return details::OneToOneTableIsEmpty(connection, TableInfo::TableName()); - } - - // Checks the consistency of the table. - static bool CheckConsistency(const SQLite::Connection& connection, bool log) - { - return details::OneToOneTableCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + // Creates the table. + void CreateOneToOneTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool useNamedIndices); + + // Drops the table. + void DropOneToOneTable(SQLite::Connection& connection, std::string_view tableName); + + // Selects the value from the table, returning the rowid if it exists. + std::optional OneToOneTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike = false); + + // Selects the value from the table, returning the rowid if it exists. + std::optional OneToOneTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id); + + // Gets all row ids from the table. + std::vector OneToOneTableGetAllRowIds(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, size_t limit); + + // Ensures that the values exists in the table. + SQLite::rowid_t OneToOneTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch = false); + + // Removes data that is no longer needed for an index that is to be published. + void OneToOneTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName, bool useNamedIndices, bool preserveValuesIndex); + + // Gets the total number of rows in the table. + uint64_t OneToOneTableGetCount(const SQLite::Connection& connection, std::string_view tableName); + + // Determines if the table is empty. + bool OneToOneTableIsEmpty(SQLite::Connection& connection, std::string_view tableName); + + // Removes the given row by its rowid if it is no longer referenced. + void OneToOneTableDeleteById(SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t id); + + // Checks the consistency of the table. + bool OneToOneTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); + } + + // A table that represents a value that is 1:1 with a primary entry. + template + struct OneToOneTable + { + // The value type. + using value_t = std::string; + + // The id type + using id_t = SQLite::rowid_t; + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection) + { + details::CreateOneToOneTable(connection, TableInfo::TableName(), TableInfo::ValueName(), true); + } + + // Creates the table with standard primary keys. + static void Create_deprecated(SQLite::Connection& connection) + { + details::CreateOneToOneTable(connection, TableInfo::TableName(), TableInfo::ValueName(), false); + } + + // Drops the table. + static void Drop(SQLite::Connection& connection) + { + details::DropOneToOneTable(connection, TableInfo::TableName()); + } + + // The name of the table. + static constexpr std::string_view TableName() + { + return TableInfo::TableName(); + } + + // The value name of the table. + static constexpr std::string_view ValueName() + { + return TableInfo::ValueName(); + } + + // Value indicating type. + static constexpr bool IsOneToOne() + { + return true; + } + + // Selects the value from the table, returning the rowid if it exists. + static std::optional SelectIdByValue(const SQLite::Connection& connection, std::string_view value, bool useLike = false) + { + return details::OneToOneTableSelectIdByValue(connection, TableInfo::TableName(), TableInfo::ValueName(), value, useLike); + } + + // Selects the value from the table, returning it if it exists. + static std::optional SelectValueById(SQLite::Connection& connection, id_t id) + { + return details::OneToOneTableSelectValueById(connection, TableInfo::TableName(), TableInfo::ValueName(), id); + } + + // Gets all row ids from the table. + static std::vector GetAllRowIds(const SQLite::Connection& connection, size_t limit = 0) + { + return details::OneToOneTableGetAllRowIds(connection, TableInfo::TableName(), TableInfo::ValueName(), limit); + } + + // Ensures that the given value exists in the table, returning the rowid. + static SQLite::rowid_t EnsureExists(SQLite::Connection& connection, std::string_view value, bool overwriteLikeMatch = false) + { + return details::OneToOneTableEnsureExists(connection, TableInfo::TableName(), TableInfo::ValueName(), value, overwriteLikeMatch); + } + + // Removes the given row by its rowid if it is no longer referenced. + static void DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) + { + return details::OneToOneTableDeleteById(connection, TableInfo::TableName(), id); + } + + // Removes data that is no longer needed for an index that is to be published. + // Preserving the values index will improve searching when it is primarily done by equality. + static void PrepareForPackaging(SQLite::Connection& connection, bool preserveValuesIndex = false) + { + details::OneToOneTablePrepareForPackaging(connection, TableInfo::TableName(), true, preserveValuesIndex); + } + + // Removes data that is no longer needed for an index that is to be published. + static void PrepareForPackaging_deprecated(SQLite::Connection& connection) + { + details::OneToOneTablePrepareForPackaging(connection, TableInfo::TableName(), false, false); + } + + // Gets the total number of rows in the table. + static uint64_t GetCount(const SQLite::Connection& connection) + { + return details::OneToOneTableGetCount(connection, TableInfo::TableName()); + } + + // Determines if the table is empty. + static bool IsEmpty(SQLite::Connection& connection) + { + return details::OneToOneTableIsEmpty(connection, TableInfo::TableName()); + } + + // Checks the consistency of the table. + static bool CheckConsistency(const SQLite::Connection& connection, bool log) + { + return details::OneToOneTableCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); + } + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.cpp index 8bd287e145..249224029d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.cpp @@ -1,453 +1,453 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PathPartTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - using namespace std::string_view_literals; - static constexpr std::string_view s_PathPartTable_Table_Name = "pathparts"sv; - static constexpr std::string_view s_PathPartTable_PrimaryKeyIndex_Name = "pathparts_pkindex"sv; - static constexpr std::string_view s_PathPartTable_ParentIndex_Name = "pathparts_parentidx"sv; - static constexpr std::string_view s_PathPartTable_ParentValue_Name = "parent"sv; - static constexpr std::string_view s_PathPartTable_PartValue_Name = "pathpart"sv; - - namespace - { - // Attempts to select a path part given the input. - // Returns an no value if none exists, or the rowid of the part if it is found. - std::optional SelectPathPart(SQLite::Connection& connection, std::optional parent, std::string_view part) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::RowIDName).From(s_PathPartTable_Table_Name). - Where(s_PathPartTable_ParentValue_Name).Equals(parent).And(s_PathPartTable_PartValue_Name).Equals(part); - - SQLite::Statement select = builder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - // Inserts the given path part into the table, returning the rowid of the inserted row. - SQLite::rowid_t InsertPathPart(SQLite::Connection& connection, std::optional parent, std::string_view part) - { - THROW_HR_IF(E_INVALIDARG, part.empty()); - - SQLite::Builder::StatementBuilder builder; - builder.InsertInto(s_PathPartTable_Table_Name).Columns({ s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }).Values(parent, part); - - builder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - // Inserts the no path part into the table. - SQLite::rowid_t InsertNoPathPart(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder. - InsertInto(s_PathPartTable_Table_Name). - Columns({ SQLite::RowIDName, s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }). - Values(PathPartTable::NoPathId, std::optional{}, std::string_view{}); - - builder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - // Gets the parent of a given part by id. - // This should only be called when the part must exist, as it will throw if not found. - std::optional GetParentById(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(s_PathPartTable_ParentValue_Name).From(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement select = builder.Prepare(connection); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED, !select.Step()); - - if (!select.GetColumnIsNull(0)) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - // Determines if any part references this one as their parent. - bool IsLeafPart(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PathPartTable_Table_Name).Where(s_PathPartTable_ParentValue_Name).Equals(id); - - SQLite::Statement select = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !select.Step()); - - // No rows with this as a parent means it is a leaf. - return (select.GetColumn(0) == 0); - } - - // Removes the given part by id. - void RemovePartById(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - } - } - - // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. - // The resulting database will function the same, but give us control to drop the indices to reduce space. - void PathPartTable::Create(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPathParts_v1_1"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PathPartTable_Table_Name).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_PathPartTable_ParentValue_Name, Type::Int64), - ColumnBuilder(s_PathPartTable_PartValue_Name, Type::Text).NotNull() - }); - - createTableBuilder.Execute(connection); - - StatementBuilder createPKIndexBuilder; - createPKIndexBuilder.CreateUniqueIndex(s_PathPartTable_PrimaryKeyIndex_Name).On(s_PathPartTable_Table_Name).Columns({ s_PathPartTable_PartValue_Name, s_PathPartTable_ParentValue_Name }); - createPKIndexBuilder.Execute(connection); - - StatementBuilder createIndexBuilder; - createIndexBuilder.CreateIndex(s_PathPartTable_ParentIndex_Name).On(s_PathPartTable_Table_Name).Columns(s_PathPartTable_ParentValue_Name); - createIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void PathPartTable::Create_deprecated(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPathParts_v1_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PathPartTable_Table_Name).Columns({ - ColumnBuilder(s_PathPartTable_ParentValue_Name, Type::Int64), - ColumnBuilder(s_PathPartTable_PartValue_Name, Type::Text).NotNull(), - PrimaryKeyBuilder({ s_PathPartTable_PartValue_Name, s_PathPartTable_ParentValue_Name }) - }); - - createTableBuilder.Execute(connection); - - StatementBuilder createIndexBuilder; - createIndexBuilder.CreateIndex(s_PathPartTable_ParentIndex_Name).On(s_PathPartTable_Table_Name).Columns(s_PathPartTable_ParentValue_Name); - - createIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void PathPartTable::Drop(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(s_PathPartTable_Table_Name); - - dropTableBuilder.Execute(connection); - } - - std::string_view PathPartTable::TableName() - { - return s_PathPartTable_Table_Name; - } - - std::string_view PathPartTable::ValueName() - { - return s_PathPartTable_PartValue_Name; - } - - std::tuple EnsurePathExistsInternal(SQLite::Connection& connection, const std::filesystem::path& relativePath, bool createIfNotFound) - { - THROW_HR_IF(E_INVALIDARG, !relativePath.has_relative_path()); - THROW_HR_IF(E_INVALIDARG, relativePath.has_root_path()); - THROW_HR_IF(E_INVALIDARG, !relativePath.has_filename()); - - std::unique_ptr savepoint; - if (createIfNotFound) - { - savepoint = std::make_unique(SQLite::Savepoint::Create(connection, "ensurepathexists_v1_0")); - } - - bool partsAdded = false; - - std::optional parent; - for (const auto& part : relativePath) - { - std::string utf8part = part.u8string(); - std::optional current = SelectPathPart(connection, parent, utf8part); - - if (!current) - { - if (createIfNotFound) - { - partsAdded = true; - current = InsertPathPart(connection, parent, utf8part); - } - else - { - // Current part was not found, and we were told not to create. - // Return false to indicate that the path does not exist. - return {}; - } - } - - parent = current; - } - - if (savepoint) - { - savepoint->Commit(); - } - - // If we get this far, the path exists. - // If we were asked to create it, return whether we needed to or it was already present. - // If not, then true indicates that it exists. - return { (createIfNotFound ? partsAdded : true), parent.value() }; - } - - std::tuple PathPartTable::EnsurePathExists(SQLite::Connection& connection, const std::optional& relativePath, bool createIfNotFound) - { - if (relativePath) - { - return EnsurePathExistsInternal(connection, relativePath.value(), createIfNotFound); - } - - std::unique_ptr savepoint; - if (createIfNotFound) - { - savepoint = std::make_unique(SQLite::Savepoint::Create(connection, "ensurepathexists_v1_0")); - } - - bool partsAdded = false; - - std::optional noPathPart = SelectPathPart(connection, {}, {}); - - if (!noPathPart) - { - if (createIfNotFound) - { - partsAdded = true; - noPathPart = InsertNoPathPart(connection); - } - else - { - // Not found, and we were told not to create. - // Return false to indicate that the path does not exist. - return {}; - } - } - - if (savepoint) - { - savepoint->Commit(); - } - - // If we get this far, the path exists. - // If we were asked to create it, return whether we needed to or it was already present. - // If not, then true indicates that it exists. - return { (createIfNotFound ? partsAdded : true), noPathPart.value() }; - } - - std::optional PathPartTable::GetPathById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }). - From(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); - - SQLite::Statement select = builder.Prepare(connection); - - SQLite::rowid_t currentPart = id; - std::string result; - - while (true) - { - select.Reset(); - select.Bind(1, currentPart); - - if (select.Step()) - { - std::string partValue = select.GetColumn(1); - if (result.empty()) - { - result = partValue; - } - else - { - result = partValue + '/' + result; - } - - if (select.GetColumnIsNull(0)) - { - // If the parent of this column is null, then we have reached the relative root - break; - } - else - { - currentPart = select.GetColumn(0); - } - } - else - { - if (currentPart == id) - { - // The given id did not reference an actual path - return {}; - } - else - { - // We found a broken path - AICLI_LOG(Repo, Error, << "Path part references an invalid parent: " << currentPart); - THROW_HR(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED); - } - } - } - - return result; - } - - void PathPartTable::RemovePathById(SQLite::Connection& connection, SQLite::rowid_t id) - { - // Don't bother removing the pathless id - if (id == NoPathId) - { - return; - } - - SQLite::rowid_t currentPartToRemove = id; - while (IsLeafPart(connection, currentPartToRemove)) - { - std::optional parent = GetParentById(connection, currentPartToRemove); - RemovePartById(connection, currentPartToRemove); - - // If parent was NULL, this was a root part and we can stop - if (!parent) - { - break; - } - else - { - currentPartToRemove = parent.value(); - } - } - } - - void PathPartTable::PrepareForPackaging(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpPathParts_v1_1"); - - PrepareForPackaging_deprecated(connection); - - SQLite::Builder::StatementBuilder dropPKIndexBuilder; - dropPKIndexBuilder.DropIndex(s_PathPartTable_PrimaryKeyIndex_Name); - dropPKIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void PathPartTable::PrepareForPackaging_deprecated(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex(s_PathPartTable_ParentIndex_Name); - dropIndexBuilder.Execute(connection); - } - - bool PathPartTable::CheckConsistency(const SQLite::Connection& connection, bool log) - { - using QCol = SQLite::Builder::QualifiedColumn; - - // Build a select statement to find pathpart rows containing references to parents with nonexistent rowids - // Such as: - // Select l.rowid, l.parent from pathparts as l left outer join pathparts as r on l.parent = r.rowid where l.parent is not null and r.pathpart is null - constexpr std::string_view s_left = "left"sv; - constexpr std::string_view s_right = "right"sv; - bool result = true; - - { - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_left, SQLite::RowIDName), QCol(s_left, s_PathPartTable_ParentValue_Name) }). - From(s_PathPartTable_Table_Name).As(s_left). - LeftOuterJoin(s_PathPartTable_Table_Name).As(s_right).On(QCol(s_left, s_PathPartTable_ParentValue_Name), QCol(s_right, SQLite::RowIDName)). - Where(QCol(s_left, s_PathPartTable_ParentValue_Name)).IsNotNull().And(QCol(s_right, s_PathPartTable_PartValue_Name)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] pathparts [" << select.GetColumn(0) << "] refers to " << s_PathPartTable_ParentValue_Name << " [" << select.GetColumn(1) << "]"); - } - } - - if (!result && !log) - { - return result; - } - - { - // Build a select statement to find values that contain an embedded null character - // Such as: - // Select count(*) from table where instr(value,char(0))>0 - SQLite::Builder::StatementBuilder builder; - builder. - Select({ SQLite::RowIDName, s_PathPartTable_PartValue_Name }). - From(s_PathPartTable_Table_Name). - WhereValueContainsEmbeddedNullCharacter(s_PathPartTable_PartValue_Name); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << s_PathPartTable_Table_Name << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); - } - } - - return result; - } - - bool PathPartTable::IsEmpty(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PathPartTable_Table_Name); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) == 0); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PathPartTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_PathPartTable_Table_Name = "pathparts"sv; + static constexpr std::string_view s_PathPartTable_PrimaryKeyIndex_Name = "pathparts_pkindex"sv; + static constexpr std::string_view s_PathPartTable_ParentIndex_Name = "pathparts_parentidx"sv; + static constexpr std::string_view s_PathPartTable_ParentValue_Name = "parent"sv; + static constexpr std::string_view s_PathPartTable_PartValue_Name = "pathpart"sv; + + namespace + { + // Attempts to select a path part given the input. + // Returns an no value if none exists, or the rowid of the part if it is found. + std::optional SelectPathPart(SQLite::Connection& connection, std::optional parent, std::string_view part) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PathPartTable_Table_Name). + Where(s_PathPartTable_ParentValue_Name).Equals(parent).And(s_PathPartTable_PartValue_Name).Equals(part); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + // Inserts the given path part into the table, returning the rowid of the inserted row. + SQLite::rowid_t InsertPathPart(SQLite::Connection& connection, std::optional parent, std::string_view part) + { + THROW_HR_IF(E_INVALIDARG, part.empty()); + + SQLite::Builder::StatementBuilder builder; + builder.InsertInto(s_PathPartTable_Table_Name).Columns({ s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }).Values(parent, part); + + builder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + // Inserts the no path part into the table. + SQLite::rowid_t InsertNoPathPart(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder. + InsertInto(s_PathPartTable_Table_Name). + Columns({ SQLite::RowIDName, s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }). + Values(PathPartTable::NoPathId, std::optional{}, std::string_view{}); + + builder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + // Gets the parent of a given part by id. + // This should only be called when the part must exist, as it will throw if not found. + std::optional GetParentById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(s_PathPartTable_ParentValue_Name).From(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement select = builder.Prepare(connection); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED, !select.Step()); + + if (!select.GetColumnIsNull(0)) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + // Determines if any part references this one as their parent. + bool IsLeafPart(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PathPartTable_Table_Name).Where(s_PathPartTable_ParentValue_Name).Equals(id); + + SQLite::Statement select = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !select.Step()); + + // No rows with this as a parent means it is a leaf. + return (select.GetColumn(0) == 0); + } + + // Removes the given part by id. + void RemovePartById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + } + } + + // Starting in V1.1, all code should be going this route of creating named indices rather than using primary or unique keys on columns. + // The resulting database will function the same, but give us control to drop the indices to reduce space. + void PathPartTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPathParts_v1_1"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PathPartTable_Table_Name).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_PathPartTable_ParentValue_Name, Type::Int64), + ColumnBuilder(s_PathPartTable_PartValue_Name, Type::Text).NotNull() + }); + + createTableBuilder.Execute(connection); + + StatementBuilder createPKIndexBuilder; + createPKIndexBuilder.CreateUniqueIndex(s_PathPartTable_PrimaryKeyIndex_Name).On(s_PathPartTable_Table_Name).Columns({ s_PathPartTable_PartValue_Name, s_PathPartTable_ParentValue_Name }); + createPKIndexBuilder.Execute(connection); + + StatementBuilder createIndexBuilder; + createIndexBuilder.CreateIndex(s_PathPartTable_ParentIndex_Name).On(s_PathPartTable_Table_Name).Columns(s_PathPartTable_ParentValue_Name); + createIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void PathPartTable::Create_deprecated(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPathParts_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PathPartTable_Table_Name).Columns({ + ColumnBuilder(s_PathPartTable_ParentValue_Name, Type::Int64), + ColumnBuilder(s_PathPartTable_PartValue_Name, Type::Text).NotNull(), + PrimaryKeyBuilder({ s_PathPartTable_PartValue_Name, s_PathPartTable_ParentValue_Name }) + }); + + createTableBuilder.Execute(connection); + + StatementBuilder createIndexBuilder; + createIndexBuilder.CreateIndex(s_PathPartTable_ParentIndex_Name).On(s_PathPartTable_Table_Name).Columns(s_PathPartTable_ParentValue_Name); + + createIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void PathPartTable::Drop(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(s_PathPartTable_Table_Name); + + dropTableBuilder.Execute(connection); + } + + std::string_view PathPartTable::TableName() + { + return s_PathPartTable_Table_Name; + } + + std::string_view PathPartTable::ValueName() + { + return s_PathPartTable_PartValue_Name; + } + + std::tuple EnsurePathExistsInternal(SQLite::Connection& connection, const std::filesystem::path& relativePath, bool createIfNotFound) + { + THROW_HR_IF(E_INVALIDARG, !relativePath.has_relative_path()); + THROW_HR_IF(E_INVALIDARG, relativePath.has_root_path()); + THROW_HR_IF(E_INVALIDARG, !relativePath.has_filename()); + + std::unique_ptr savepoint; + if (createIfNotFound) + { + savepoint = std::make_unique(SQLite::Savepoint::Create(connection, "ensurepathexists_v1_0")); + } + + bool partsAdded = false; + + std::optional parent; + for (const auto& part : relativePath) + { + std::string utf8part = part.u8string(); + std::optional current = SelectPathPart(connection, parent, utf8part); + + if (!current) + { + if (createIfNotFound) + { + partsAdded = true; + current = InsertPathPart(connection, parent, utf8part); + } + else + { + // Current part was not found, and we were told not to create. + // Return false to indicate that the path does not exist. + return {}; + } + } + + parent = current; + } + + if (savepoint) + { + savepoint->Commit(); + } + + // If we get this far, the path exists. + // If we were asked to create it, return whether we needed to or it was already present. + // If not, then true indicates that it exists. + return { (createIfNotFound ? partsAdded : true), parent.value() }; + } + + std::tuple PathPartTable::EnsurePathExists(SQLite::Connection& connection, const std::optional& relativePath, bool createIfNotFound) + { + if (relativePath) + { + return EnsurePathExistsInternal(connection, relativePath.value(), createIfNotFound); + } + + std::unique_ptr savepoint; + if (createIfNotFound) + { + savepoint = std::make_unique(SQLite::Savepoint::Create(connection, "ensurepathexists_v1_0")); + } + + bool partsAdded = false; + + std::optional noPathPart = SelectPathPart(connection, {}, {}); + + if (!noPathPart) + { + if (createIfNotFound) + { + partsAdded = true; + noPathPart = InsertNoPathPart(connection); + } + else + { + // Not found, and we were told not to create. + // Return false to indicate that the path does not exist. + return {}; + } + } + + if (savepoint) + { + savepoint->Commit(); + } + + // If we get this far, the path exists. + // If we were asked to create it, return whether we needed to or it was already present. + // If not, then true indicates that it exists. + return { (createIfNotFound ? partsAdded : true), noPathPart.value() }; + } + + std::optional PathPartTable::GetPathById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ s_PathPartTable_ParentValue_Name, s_PathPartTable_PartValue_Name }). + From(s_PathPartTable_Table_Name).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); + + SQLite::Statement select = builder.Prepare(connection); + + SQLite::rowid_t currentPart = id; + std::string result; + + while (true) + { + select.Reset(); + select.Bind(1, currentPart); + + if (select.Step()) + { + std::string partValue = select.GetColumn(1); + if (result.empty()) + { + result = partValue; + } + else + { + result = partValue + '/' + result; + } + + if (select.GetColumnIsNull(0)) + { + // If the parent of this column is null, then we have reached the relative root + break; + } + else + { + currentPart = select.GetColumn(0); + } + } + else + { + if (currentPart == id) + { + // The given id did not reference an actual path + return {}; + } + else + { + // We found a broken path + AICLI_LOG(Repo, Error, << "Path part references an invalid parent: " << currentPart); + THROW_HR(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED); + } + } + } + + return result; + } + + void PathPartTable::RemovePathById(SQLite::Connection& connection, SQLite::rowid_t id) + { + // Don't bother removing the pathless id + if (id == NoPathId) + { + return; + } + + SQLite::rowid_t currentPartToRemove = id; + while (IsLeafPart(connection, currentPartToRemove)) + { + std::optional parent = GetParentById(connection, currentPartToRemove); + RemovePartById(connection, currentPartToRemove); + + // If parent was NULL, this was a root part and we can stop + if (!parent) + { + break; + } + else + { + currentPartToRemove = parent.value(); + } + } + } + + void PathPartTable::PrepareForPackaging(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpPathParts_v1_1"); + + PrepareForPackaging_deprecated(connection); + + SQLite::Builder::StatementBuilder dropPKIndexBuilder; + dropPKIndexBuilder.DropIndex(s_PathPartTable_PrimaryKeyIndex_Name); + dropPKIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void PathPartTable::PrepareForPackaging_deprecated(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex(s_PathPartTable_ParentIndex_Name); + dropIndexBuilder.Execute(connection); + } + + bool PathPartTable::CheckConsistency(const SQLite::Connection& connection, bool log) + { + using QCol = SQLite::Builder::QualifiedColumn; + + // Build a select statement to find pathpart rows containing references to parents with nonexistent rowids + // Such as: + // Select l.rowid, l.parent from pathparts as l left outer join pathparts as r on l.parent = r.rowid where l.parent is not null and r.pathpart is null + constexpr std::string_view s_left = "left"sv; + constexpr std::string_view s_right = "right"sv; + bool result = true; + + { + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_left, SQLite::RowIDName), QCol(s_left, s_PathPartTable_ParentValue_Name) }). + From(s_PathPartTable_Table_Name).As(s_left). + LeftOuterJoin(s_PathPartTable_Table_Name).As(s_right).On(QCol(s_left, s_PathPartTable_ParentValue_Name), QCol(s_right, SQLite::RowIDName)). + Where(QCol(s_left, s_PathPartTable_ParentValue_Name)).IsNotNull().And(QCol(s_right, s_PathPartTable_PartValue_Name)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] pathparts [" << select.GetColumn(0) << "] refers to " << s_PathPartTable_ParentValue_Name << " [" << select.GetColumn(1) << "]"); + } + } + + if (!result && !log) + { + return result; + } + + { + // Build a select statement to find values that contain an embedded null character + // Such as: + // Select count(*) from table where instr(value,char(0))>0 + SQLite::Builder::StatementBuilder builder; + builder. + Select({ SQLite::RowIDName, s_PathPartTable_PartValue_Name }). + From(s_PathPartTable_Table_Name). + WhereValueContainsEmbeddedNullCharacter(s_PathPartTable_PartValue_Name); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << s_PathPartTable_Table_Name << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); + } + } + + return result; + } + + bool PathPartTable::IsEmpty(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PathPartTable_Table_Name); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) == 0); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.h index fcdcb96ab2..ef37bc694b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/PathPartTable.h @@ -1,69 +1,69 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - // A table that represents the parts of a path - struct PathPartTable - { - // The id type - using id_t = SQLite::rowid_t; - - // The id used when no path is present. - constexpr static id_t NoPathId = -1; - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - - // Creates the table with standard primary keys. - static void Create_deprecated(SQLite::Connection& connection); - - // Drops the table. - static void Drop(SQLite::Connection& connection); - - // Gets the table name. - static std::string_view TableName(); - - // Gets the value name. - static std::string_view ValueName(); - - // Ensure that the given relative path exists within the path parts table. - // If createIfNotFound is true, the function will add the parts as needed. - // The result bool will indicate whether it was necessary to add the path (true), - // or it was already present (false). - // If createIfNotFound is false, the function will simply determine if the path is present. - // The result bool will indicate whether the path was found (true), or not (false). - // In all cases except createIfNotFound == false and result bool == false, the int64_t value - // will be valid and the rowid of the final path part in the path. - // If relativePath is not provided, will always map to an entry with NoPathId. - static std::tuple EnsurePathExists(SQLite::Connection& connection, const std::optional& relativePath, bool createIfNotFound); - - // Gets the path string using the given id as the leaf. - static std::optional GetPathById(const SQLite::Connection& connection, SQLite::rowid_t id); - - // Removes the path that terminates at the given id. - // Will not remove a path part if it is referenced. - static void RemovePathById(SQLite::Connection& connection, SQLite::rowid_t id); - - // Removes data that is no longer needed for an index that is to be published. - static void PrepareForPackaging(SQLite::Connection& connection); - - // Removes data that is no longer needed for an index that is to be published. - static void PrepareForPackaging_deprecated(SQLite::Connection& connection); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - static bool CheckConsistency(const SQLite::Connection& connection, bool log); - - // Determines if the table is empty. - static bool IsEmpty(SQLite::Connection& connection); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + // A table that represents the parts of a path + struct PathPartTable + { + // The id type + using id_t = SQLite::rowid_t; + + // The id used when no path is present. + constexpr static id_t NoPathId = -1; + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + // Creates the table with standard primary keys. + static void Create_deprecated(SQLite::Connection& connection); + + // Drops the table. + static void Drop(SQLite::Connection& connection); + + // Gets the table name. + static std::string_view TableName(); + + // Gets the value name. + static std::string_view ValueName(); + + // Ensure that the given relative path exists within the path parts table. + // If createIfNotFound is true, the function will add the parts as needed. + // The result bool will indicate whether it was necessary to add the path (true), + // or it was already present (false). + // If createIfNotFound is false, the function will simply determine if the path is present. + // The result bool will indicate whether the path was found (true), or not (false). + // In all cases except createIfNotFound == false and result bool == false, the int64_t value + // will be valid and the rowid of the final path part in the path. + // If relativePath is not provided, will always map to an entry with NoPathId. + static std::tuple EnsurePathExists(SQLite::Connection& connection, const std::optional& relativePath, bool createIfNotFound); + + // Gets the path string using the given id as the leaf. + static std::optional GetPathById(const SQLite::Connection& connection, SQLite::rowid_t id); + + // Removes the path that terminates at the given id. + // Will not remove a path part if it is referenced. + static void RemovePathById(SQLite::Connection& connection, SQLite::rowid_t id); + + // Removes data that is no longer needed for an index that is to be published. + static void PrepareForPackaging(SQLite::Connection& connection); + + // Removes data that is no longer needed for an index that is to be published. + static void PrepareForPackaging_deprecated(SQLite::Connection& connection); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + static bool CheckConsistency(const SQLite::Connection& connection, bool log); + + // Determines if the table is empty. + static bool IsEmpty(SQLite::Connection& connection); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable.h index 124a12178b..c544cc09fb 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable.h @@ -1,65 +1,65 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Public/winget/RepositorySearch.h" - -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - // Table for holding temporary search results. - struct SearchResultsTable : public SQLite::TempTable - { - SearchResultsTable(const SQLite::Connection& connection); - - SearchResultsTable(const SearchResultsTable&) = delete; - SearchResultsTable& operator=(const SearchResultsTable&) = delete; - - SearchResultsTable(SearchResultsTable&&) = default; - SearchResultsTable& operator=(SearchResultsTable&&) = default; - - // Performs the requested search type on the requested field. - void SearchOnField(const PackageMatchFilter& filter); - - // Removes rows with manifest ids whose sort order is below the highest one. - void RemoveDuplicateManifestRows(); - - // Prepares the table for a filtering pass. - void PrepareToFilter(); - - // Performs the requested filter type on the requested field. - void FilterOnField(const PackageMatchFilter& filter); - - // Completes a filtering pass, removing filtered rows. - void CompleteFilter(); - - // Gets the results from the table. - ISQLiteIndex::SearchResult GetSearchResults(size_t limit = 0); - - protected: - // Builds the search statement for the specified field and match type. - std::vector BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const; - - virtual std::vector BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const; - - static bool MatchUsesLike(MatchType match); - void BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value); - - virtual void BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex); - - private: - const SQLite::Connection& m_connection; - int m_sortOrdinalValue = 0; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Public/winget/RepositorySearch.h" + +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + // Table for holding temporary search results. + struct SearchResultsTable : public SQLite::TempTable + { + SearchResultsTable(const SQLite::Connection& connection); + + SearchResultsTable(const SearchResultsTable&) = delete; + SearchResultsTable& operator=(const SearchResultsTable&) = delete; + + SearchResultsTable(SearchResultsTable&&) = default; + SearchResultsTable& operator=(SearchResultsTable&&) = default; + + // Performs the requested search type on the requested field. + void SearchOnField(const PackageMatchFilter& filter); + + // Removes rows with manifest ids whose sort order is below the highest one. + void RemoveDuplicateManifestRows(); + + // Prepares the table for a filtering pass. + void PrepareToFilter(); + + // Performs the requested filter type on the requested field. + void FilterOnField(const PackageMatchFilter& filter); + + // Completes a filtering pass, removing filtered rows. + void CompleteFilter(); + + // Gets the results from the table. + ISQLiteIndex::SearchResult GetSearchResults(size_t limit = 0); + + protected: + // Builds the search statement for the specified field and match type. + std::vector BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const; + + virtual std::vector BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const; + + static bool MatchUsesLike(MatchType match); + void BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value); + + virtual void BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex); + + private: + const SQLite::Connection& m_connection; + int m_sortOrdinalValue = 0; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp index dd34ecbc57..a86a443bdd 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/SearchResultsTable_1_0.cpp @@ -1,302 +1,302 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResultsTable.h" -#include - -#include "Microsoft/Schema/1_0/IdTable.h" -#include "Microsoft/Schema/1_0/NameTable.h" -#include "Microsoft/Schema/1_0/MonikerTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_0/TagsTable.h" -#include "Microsoft/Schema/1_0/CommandsTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace - { - using namespace std::string_literals; - using namespace std::string_view_literals; - - constexpr std::string_view s_SearchResultsTable_Manifest = "manifest"sv; - constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; - constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; - constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; - constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; - constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; - - constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; - - constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_ManifestAlias = "m"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; - } - - SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : - m_connection(connection) - { - using namespace SQLite::Builder; - - { - StatementBuilder builder; - builder.CreateTable(GetQualifiedName()).BeginColumns(); - - builder.Column(ColumnBuilder(s_SearchResultsTable_Manifest, Type::RowId).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); - - builder.EndColumns(); - - builder.Execute(m_connection); - } - - InitDropStatement(m_connection); - - { - SQLite::Builder::QualifiedTable index = GetQualifiedName(); - std::string indexName(index.Table); - indexName += s_SearchResultsTable_Index_Suffix; - index.Table = indexName; - - StatementBuilder builder; - builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Manifest); - - builder.Execute(m_connection); - } - } - - void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - int sortOrdinal = m_sortOrdinalValue++; - - // Create an insert statement to select values into the table as requested. - // The goal is a statement like this: - // INSERT INTO - // SELECT valueTable.m, , , valueTable.v, , FROM - // (SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = ) AS valueTable - // Where the subselect is built by the owning table. - StatementBuilder builder; - builder.InsertInto(GetQualifiedName()).Select(). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ManifestAlias)). - Value(filter.Field). - Value(filter.Type). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). - Value(sortOrdinal). - Value(false). - From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::RemoveDuplicateManifestRows() - { - using namespace SQLite::Builder; - - // Create a delete statement to leave only one row with a given manifest. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // DELETE from where rowid not in ( - // SELECT rowid from ( - // SELECT rowid, min(sort) from group by manifest - // ) - // ) - StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). - Select(SQLite::RowIDName).From().BeginParenthetical(). - Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Manifest). - EndParenthetical(). - EndParenthetical(); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); - } - - void SearchResultsTable::PrepareToFilter() - { - // Reset all filter values to unselected - SQLite::Builder::StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(false); - - builder.Execute(m_connection); - } - - void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - // Create an update statement to mark rows that are found by the search. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // UPDATE set filter = 1 where manifest in ( - // SELECT m from ( - // SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = - // ) - // ) - StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(true).Where(s_SearchResultsTable_Manifest).In().BeginParenthetical(). - Select(s_SearchResultsTable_SubSelect_ManifestAlias).From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().EndParenthetical(); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::CompleteFilter() - { - // Delete all unselected values - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); - } - - ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) - { - constexpr std::string_view tempTableAlias = "t"sv; - - using namespace SQLite::Builder; - using QCol = QualifiedColumn; - - // Select all unique ids from the results table, and their highest ordered match. - // The goal is a statement like this: - // SELECT m.id, field, match, value, min(sort) from join manifest on rowid = manifest group by m.id order by t.sort - // Through the "group by m.id", we will only ever have one row per id, and the "min(sort)" returns us one of the rows that matched - // through the earliest search. We also order by the sort value to have the earliest search matches first in the list - StatementBuilder builder; - builder.Select(). - Column(QCol(ManifestTable::TableName(), IdTable::ValueName())). - Column(QCol(tempTableAlias, s_SearchResultsTable_MatchField)). - Column(QCol(tempTableAlias, s_SearchResultsTable_MatchType)). - Column(QCol(tempTableAlias, s_SearchResultsTable_MatchValue)). - Column(Aggregate::Min, QCol(tempTableAlias, s_SearchResultsTable_SortValue)). - From(GetQualifiedName()).As(tempTableAlias). - Join(ManifestTable::TableName()).On(QCol(tempTableAlias, s_SearchResultsTable_Manifest), QCol(ManifestTable::TableName(), SQLite::RowIDName)). - GroupBy(QCol(ManifestTable::TableName(), IdTable::ValueName())).OrderBy(QCol(tempTableAlias, s_SearchResultsTable_SortValue)); - - SQLite::Statement select = builder.Prepare(m_connection); - - ISQLiteIndex::SearchResult result; - while (select.Step()) - { - if (limit && result.Matches.size() >= limit) - { - break; - } - - result.Matches.emplace_back(select.GetColumn(0), - PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); - } - - result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); - - return result; - } - - std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const - { - return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_ManifestAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); - } - - std::vector SearchResultsTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const - { - switch (field) - { - case PackageMatchField::Id: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Name: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Moniker: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Tag: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::Command: - return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - default: - return {}; - } - } - - bool SearchResultsTable::MatchUsesLike(MatchType match) - { - return (match != MatchType::Exact); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) - { - std::string valueToUse; - - if (MatchUsesLike(match)) - { - valueToUse = SQLite::EscapeStringForLike(value); - } - else - { - valueToUse = value; - } - - switch (match) - { - case AppInstaller::Repository::MatchType::StartsWith: - valueToUse += '%'; - break; - case AppInstaller::Repository::MatchType::Substring: - valueToUse = "%"s + valueToUse + '%'; - break; - default: - // No changes required for others. - break; - } - - statement.Bind(bindIndex, valueToUse); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) - { - // TODO: Implement these more complex match types - if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) - { - AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); - return; - } - - BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResultsTable.h" +#include + +#include "Microsoft/Schema/1_0/IdTable.h" +#include "Microsoft/Schema/1_0/NameTable.h" +#include "Microsoft/Schema/1_0/MonikerTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_0/TagsTable.h" +#include "Microsoft/Schema/1_0/CommandsTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace + { + using namespace std::string_literals; + using namespace std::string_view_literals; + + constexpr std::string_view s_SearchResultsTable_Manifest = "manifest"sv; + constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; + constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; + constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; + constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; + constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; + + constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; + + constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_ManifestAlias = "m"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; + } + + SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : + m_connection(connection) + { + using namespace SQLite::Builder; + + { + StatementBuilder builder; + builder.CreateTable(GetQualifiedName()).BeginColumns(); + + builder.Column(ColumnBuilder(s_SearchResultsTable_Manifest, Type::RowId).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); + + builder.EndColumns(); + + builder.Execute(m_connection); + } + + InitDropStatement(m_connection); + + { + SQLite::Builder::QualifiedTable index = GetQualifiedName(); + std::string indexName(index.Table); + indexName += s_SearchResultsTable_Index_Suffix; + index.Table = indexName; + + StatementBuilder builder; + builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Manifest); + + builder.Execute(m_connection); + } + } + + void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + int sortOrdinal = m_sortOrdinalValue++; + + // Create an insert statement to select values into the table as requested. + // The goal is a statement like this: + // INSERT INTO + // SELECT valueTable.m, , , valueTable.v, , FROM + // (SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = ) AS valueTable + // Where the subselect is built by the owning table. + StatementBuilder builder; + builder.InsertInto(GetQualifiedName()).Select(). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ManifestAlias)). + Value(filter.Field). + Value(filter.Type). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). + Value(sortOrdinal). + Value(false). + From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::RemoveDuplicateManifestRows() + { + using namespace SQLite::Builder; + + // Create a delete statement to leave only one row with a given manifest. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // DELETE from where rowid not in ( + // SELECT rowid from ( + // SELECT rowid, min(sort) from group by manifest + // ) + // ) + StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). + Select(SQLite::RowIDName).From().BeginParenthetical(). + Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Manifest). + EndParenthetical(). + EndParenthetical(); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); + } + + void SearchResultsTable::PrepareToFilter() + { + // Reset all filter values to unselected + SQLite::Builder::StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(false); + + builder.Execute(m_connection); + } + + void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + // Create an update statement to mark rows that are found by the search. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // UPDATE set filter = 1 where manifest in ( + // SELECT m from ( + // SELECT manifest.rowid as m, manifest.id as v from manifest join ids on manifest.id = ids.rowid where ids.id = + // ) + // ) + StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(true).Where(s_SearchResultsTable_Manifest).In().BeginParenthetical(). + Select(s_SearchResultsTable_SubSelect_ManifestAlias).From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().EndParenthetical(); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::CompleteFilter() + { + // Delete all unselected values + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); + } + + ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) + { + constexpr std::string_view tempTableAlias = "t"sv; + + using namespace SQLite::Builder; + using QCol = QualifiedColumn; + + // Select all unique ids from the results table, and their highest ordered match. + // The goal is a statement like this: + // SELECT m.id, field, match, value, min(sort) from join manifest on rowid = manifest group by m.id order by t.sort + // Through the "group by m.id", we will only ever have one row per id, and the "min(sort)" returns us one of the rows that matched + // through the earliest search. We also order by the sort value to have the earliest search matches first in the list + StatementBuilder builder; + builder.Select(). + Column(QCol(ManifestTable::TableName(), IdTable::ValueName())). + Column(QCol(tempTableAlias, s_SearchResultsTable_MatchField)). + Column(QCol(tempTableAlias, s_SearchResultsTable_MatchType)). + Column(QCol(tempTableAlias, s_SearchResultsTable_MatchValue)). + Column(Aggregate::Min, QCol(tempTableAlias, s_SearchResultsTable_SortValue)). + From(GetQualifiedName()).As(tempTableAlias). + Join(ManifestTable::TableName()).On(QCol(tempTableAlias, s_SearchResultsTable_Manifest), QCol(ManifestTable::TableName(), SQLite::RowIDName)). + GroupBy(QCol(ManifestTable::TableName(), IdTable::ValueName())).OrderBy(QCol(tempTableAlias, s_SearchResultsTable_SortValue)); + + SQLite::Statement select = builder.Prepare(m_connection); + + ISQLiteIndex::SearchResult result; + while (select.Step()) + { + if (limit && result.Matches.size() >= limit) + { + break; + } + + result.Matches.emplace_back(select.GetColumn(0), + PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); + } + + result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); + + return result; + } + + std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const + { + return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_ManifestAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); + } + + std::vector SearchResultsTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const + { + switch (field) + { + case PackageMatchField::Id: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Name: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Moniker: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Tag: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::Command: + return ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + default: + return {}; + } + } + + bool SearchResultsTable::MatchUsesLike(MatchType match) + { + return (match != MatchType::Exact); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) + { + std::string valueToUse; + + if (MatchUsesLike(match)) + { + valueToUse = SQLite::EscapeStringForLike(value); + } + else + { + valueToUse = value; + } + + switch (match) + { + case AppInstaller::Repository::MatchType::StartsWith: + valueToUse += '%'; + break; + case AppInstaller::Repository::MatchType::Substring: + valueToUse = "%"s + valueToUse + '%'; + break; + default: + // No changes required for others. + break; + } + + statement.Bind(bindIndex, valueToUse); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) + { + // TODO: Implement these more complex match types + if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) + { + AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); + return; + } + + BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/TagsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/TagsTable.h index 940a1e3db7..2a10e4cfc0 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/TagsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/TagsTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct TagsTableInfo - { - inline static constexpr std::string_view TableName() { return "tags"sv; } - inline static constexpr std::string_view ValueName() { return "tag"sv; } - }; - } - - // The table for Tags. - using TagsTable = OneToManyTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct TagsTableInfo + { + inline static constexpr std::string_view TableName() { return "tags"sv; } + inline static constexpr std::string_view ValueName() { return "tag"sv; } + }; + } + + // The table for Tags. + using TagsTable = OneToManyTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VersionTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VersionTable.h index f3b560f454..b28ee973e5 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VersionTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VersionTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToOneTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct VersionTableInfo - { - inline static constexpr std::string_view TableName() { return "versions"sv; } - inline static constexpr std::string_view ValueName() { return "version"sv; } - }; - } - - // The table for Version. - using VersionTable = OneToOneTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToOneTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct VersionTableInfo + { + inline static constexpr std::string_view TableName() { return "versions"sv; } + inline static constexpr std::string_view ValueName() { return "version"sv; } + }; + } + + // The table for Version. + using VersionTable = OneToOneTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VirtualTableBase.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VirtualTableBase.h index 6667c6be5e..b2e1425b2e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VirtualTableBase.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_0/VirtualTableBase.h @@ -1,20 +1,20 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -namespace AppInstaller::Repository::Microsoft::Schema::V1_0 -{ - // Since we have multiple manifest columns pointing to same table now(i.e. versions table), and previous assumption - // is manifest table column always has same name as referenced table column (i.e. manifest.version and versions.version), - // we need to differentiate manifest column name and referenced table column name(i.e. manifest.arp_min_version and versions.version) - // An optional ManifestColumnName() is added to virtual table for the above purpose, and can be used in the future if needed. - // To let the template codes better determine virtual tables, the following struct is created. - - // Struct used as the base for virtual tables. - // Future virtual tables reusing an existing table should derive from this and implement - // static std::string_view ManifestColumnName(); - // in addition to regular table info methods. - struct VirtualTableBase - { - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +namespace AppInstaller::Repository::Microsoft::Schema::V1_0 +{ + // Since we have multiple manifest columns pointing to same table now(i.e. versions table), and previous assumption + // is manifest table column always has same name as referenced table column (i.e. manifest.version and versions.version), + // we need to differentiate manifest column name and referenced table column name(i.e. manifest.arp_min_version and versions.version) + // An optional ManifestColumnName() is added to virtual table for the above purpose, and can be used in the future if needed. + // To let the template codes better determine virtual tables, the following struct is created. + + // Struct used as the base for virtual tables. + // Future virtual tables reusing an existing table should derive from this and implement + // static std::string_view ManifestColumnName(); + // in addition to regular table info methods. + struct VirtualTableBase + { + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface.h index eaf05442bc..de3bc9d7f2 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface.h @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Microsoft/Schema/1_0/Interface.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - // Interface to this schema version exposed through ISQLiteIndex. - struct Interface : public V1_0::Interface - { - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection, CreateOptions options) override; - SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; - void PrepareForPackaging(SQLite::Connection& connection) override; - bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; - SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const override; - std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; - - // Version 1.1 - MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; - void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; - - // Version 1.7 - void DropTables(SQLite::Connection& connection) override; - - protected: - std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const override; - void PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const override; - V1_0::OneToManyTableSchema GetOneToManyTableSchema() const override; - - virtual SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const; - virtual void PrepareForPackaging(SQLite::Connection& connection, bool vacuum); - - // Gets a property already knowing that the manifest id is valid. - virtual std::optional GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Microsoft/Schema/1_0/Interface.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + // Interface to this schema version exposed through ISQLiteIndex. + struct Interface : public V1_0::Interface + { + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection, CreateOptions options) override; + SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; + void PrepareForPackaging(SQLite::Connection& connection) override; + bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; + SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const override; + std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; + + // Version 1.1 + MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; + void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; + + // Version 1.7 + void DropTables(SQLite::Connection& connection) override; + + protected: + std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const override; + void PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const override; + V1_0::OneToManyTableSchema GetOneToManyTableSchema() const override; + + virtual SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const; + virtual void PrepareForPackaging(SQLite::Connection& connection, bool vacuum); + + // Gets a property already knowing that the manifest id is valid. + virtual std::optional GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface_1_1.cpp index 5260b9b917..92473ced99 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/Interface_1_1.cpp @@ -1,293 +1,293 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_1/Interface.h" - -#include "Microsoft/Schema/1_0/IdTable.h" -#include "Microsoft/Schema/1_0/NameTable.h" -#include "Microsoft/Schema/1_0/MonikerTable.h" -#include "Microsoft/Schema/1_0/VersionTable.h" -#include "Microsoft/Schema/1_0/ChannelTable.h" - -#include "Microsoft/Schema/1_0/PathPartTable.h" - -#include "Microsoft/Schema/1_0/ManifestTable.h" - -#include "Microsoft/Schema/1_0/TagsTable.h" -#include "Microsoft/Schema/1_0/CommandsTable.h" -#include "Microsoft/Schema/1_1/PackageFamilyNameTable.h" -#include "Microsoft/Schema/1_1/ProductCodeTable.h" - -#include "Microsoft/Schema/1_1/SearchResultsTable.h" - -#include "Microsoft/Schema/1_1/ManifestMetadataTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - SQLite::Version Interface::GetVersion() const - { - return { 1, 1 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_1"); - - V1_0::IdTable::Create(connection); - V1_0::NameTable::Create(connection); - V1_0::MonikerTable::Create(connection); - V1_0::VersionTable::Create(connection); - V1_0::ChannelTable::Create(connection); - - V1_0::PathPartTable::Create(connection); - - V1_0::ManifestTable::Create(connection, { - { V1_0::IdTable::ValueName(), true, false }, - { V1_0::NameTable::ValueName(), false, false }, - { V1_0::MonikerTable::ValueName(), false, false }, - { V1_0::VersionTable::ValueName(), true, false }, - { V1_0::ChannelTable::ValueName(), true, false }, - { V1_0::PathPartTable::ValueName(), false, WI_IsFlagClear(options, CreateOptions::SupportPathless) } - }); - - V1_0::TagsTable::Create(connection, GetOneToManyTableSchema()); - V1_0::CommandsTable::Create(connection, GetOneToManyTableSchema()); - PackageFamilyNameTable::Create(connection, GetOneToManyTableSchema()); - ProductCodeTable::Create(connection, GetOneToManyTableSchema()); - - savepoint.Commit(); - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_1"); - - SQLite::rowid_t manifestId = V1_0::Interface::AddManifest(connection, manifest, relativePath); - - // Add the new 1.1 data - // These system reference strings are all stored with their cases folded so that they can be - // looked up ordinally; enabling the index to provide efficient searches. - PackageFamilyNameTable::EnsureExistsAndInsert(connection, manifest.GetPackageFamilyNames(), manifestId); - ProductCodeTable::EnsureExistsAndInsert(connection, manifest.GetProductCodes(), manifestId); - - savepoint.Commit(); - - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_1"); - - auto [indexModified, manifestId] = V1_0::Interface::UpdateManifest(connection, manifest, relativePath); - - // Update new 1:N tables as necessary - indexModified = PackageFamilyNameTable::UpdateIfNeededByManifestId(connection, manifest.GetPackageFamilyNames(), manifestId) || indexModified; - indexModified = ProductCodeTable::UpdateIfNeededByManifestId(connection, manifest.GetProductCodes(), manifestId) || indexModified; - - savepoint.Commit(); - - return { indexModified, manifestId }; - } - - void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_1"); - - V1_0::Interface::RemoveManifestById(connection, manifestId); - - // Remove all of the new 1:N data that is no longer referenced. - PackageFamilyNameTable::DeleteIfNotNeededByManifestId(connection, manifestId); - ProductCodeTable::DeleteIfNotNeededByManifestId(connection, manifestId); - - if (ManifestMetadataTable::Exists(connection)) - { - ManifestMetadataTable::DeleteByManifestId(connection, manifestId); - } - - savepoint.Commit(); - } - - void Interface::PrepareForPackaging(SQLite::Connection& connection) - { - PrepareForPackaging(connection, true); - } - - bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const - { - bool result = V1_0::Interface::CheckConsistency(connection, log); - - // If the v1.0 index was consistent, or if full logging of inconsistency was requested, check the v1.1 data. - if (result || log) - { - result = PackageFamilyNameTable::CheckConsistency(connection, log) && result; - } - - if (result || log) - { - result = ProductCodeTable::CheckConsistency(connection, log) && result; - } - - return result; - } - - ISQLiteIndex::SearchResult Interface::Search(const SQLite::Connection& connection, const SearchRequest& request) const - { - SearchRequest updatedRequest = request; - return SearchInternal(connection, updatedRequest); - } - - std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const - { - switch (property) - { - case PackageVersionMultiProperty::PackageFamilyName: - return PackageFamilyNameTable::GetValuesByManifestId(connection, primaryId); - case PackageVersionMultiProperty::ProductCode: - return ProductCodeTable::GetValuesByManifestId(connection, primaryId); - default: - return V1_0::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); - } - } - - ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const - { - ISQLiteIndex::MetadataResult result; - - if (ManifestMetadataTable::Exists(connection)) - { - result = ManifestMetadataTable::GetMetadataByManifestId(connection, manifestId); - } - - return result; - } - - void Interface::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "setmetadatabymanifestid_v1_1"); - - if (!ManifestMetadataTable::Exists(connection)) - { - ManifestMetadataTable::Create(connection); - } - - ManifestMetadataTable::SetMetadataByManifestId(connection, manifestId, metadata, value); - - savepoint.Commit(); - } - - void Interface::DropTables(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_1"); - - V1_0::Interface::DropTables(connection); - - PackageFamilyNameTable::Drop(connection); - ProductCodeTable::Drop(connection); - - ManifestMetadataTable::Drop(connection); - - savepoint.Commit(); - } - - std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const - { - return std::make_unique(connection); - } - - void Interface::PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const - { - // First, do an exact match search for the folded system reference strings - // We do this first because it is exact, and likely won't match anything else if it matches this. - PackageMatchFilter filter(PackageMatchField::PackageFamilyName, MatchType::Exact, Utility::FoldCase(query.Value)); - resultsTable.SearchOnField(filter); - - filter.Field = PackageMatchField::ProductCode; - resultsTable.SearchOnField(filter); - - // Then do the 1.0 search - V1_0::Interface::PerformQuerySearch(resultsTable, query); - } - - V1_0::OneToManyTableSchema Interface::GetOneToManyTableSchema() const - { - return V1_0::OneToManyTableSchema::Version_1_1; - } - - ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const - { - // Update any system reference strings to be folded - auto foldIfNeeded = [](PackageMatchFilter& filter) - { - if ((filter.Field == PackageMatchField::PackageFamilyName || filter.Field == PackageMatchField::ProductCode) && - filter.Type == MatchType::Exact) - { - filter.Value = Utility::FoldCase(filter.Value); - } - }; - - for (auto& inclusion : request.Inclusions) - { - foldIfNeeded(inclusion); - } - - for (auto& filter : request.Filters) - { - foldIfNeeded(filter); - } - - return V1_0::Interface::Search(connection, request); - } - - void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_1"); - - V1_0::IdTable::PrepareForPackaging(connection); - V1_0::NameTable::PrepareForPackaging(connection); - V1_0::MonikerTable::PrepareForPackaging(connection); - V1_0::VersionTable::PrepareForPackaging(connection); - V1_0::ChannelTable::PrepareForPackaging(connection); - - V1_0::PathPartTable::PrepareForPackaging(connection); - - V1_0::ManifestTable::PrepareForPackaging(connection, { - V1_0::VersionTable::ValueName(), - V1_0::ChannelTable::ValueName(), - V1_0::PathPartTable::ValueName(), - }); - - V1_0::TagsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); - V1_0::CommandsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); - PackageFamilyNameTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); - ProductCodeTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); - - savepoint.Commit(); - - if (vacuum) - { - Vacuum(connection); - } - } - - std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const - { - switch (property) - { - case AppInstaller::Repository::PackageVersionProperty::Publisher: - { - // Publisher is not a primary data member in this version, but it may be stored in the metadata - if (ManifestMetadataTable::Exists(connection)) - { - return ManifestMetadataTable::GetMetadataByManifestIdAndMetadata(connection, manifestId, PackageVersionMetadata::Publisher); - } - - // No metadata, so no publisher - return {}; - } - default: - return V1_0::Interface::GetPropertyByManifestIdInternal(connection, manifestId, property); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_1/Interface.h" + +#include "Microsoft/Schema/1_0/IdTable.h" +#include "Microsoft/Schema/1_0/NameTable.h" +#include "Microsoft/Schema/1_0/MonikerTable.h" +#include "Microsoft/Schema/1_0/VersionTable.h" +#include "Microsoft/Schema/1_0/ChannelTable.h" + +#include "Microsoft/Schema/1_0/PathPartTable.h" + +#include "Microsoft/Schema/1_0/ManifestTable.h" + +#include "Microsoft/Schema/1_0/TagsTable.h" +#include "Microsoft/Schema/1_0/CommandsTable.h" +#include "Microsoft/Schema/1_1/PackageFamilyNameTable.h" +#include "Microsoft/Schema/1_1/ProductCodeTable.h" + +#include "Microsoft/Schema/1_1/SearchResultsTable.h" + +#include "Microsoft/Schema/1_1/ManifestMetadataTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + SQLite::Version Interface::GetVersion() const + { + return { 1, 1 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_1"); + + V1_0::IdTable::Create(connection); + V1_0::NameTable::Create(connection); + V1_0::MonikerTable::Create(connection); + V1_0::VersionTable::Create(connection); + V1_0::ChannelTable::Create(connection); + + V1_0::PathPartTable::Create(connection); + + V1_0::ManifestTable::Create(connection, { + { V1_0::IdTable::ValueName(), true, false }, + { V1_0::NameTable::ValueName(), false, false }, + { V1_0::MonikerTable::ValueName(), false, false }, + { V1_0::VersionTable::ValueName(), true, false }, + { V1_0::ChannelTable::ValueName(), true, false }, + { V1_0::PathPartTable::ValueName(), false, WI_IsFlagClear(options, CreateOptions::SupportPathless) } + }); + + V1_0::TagsTable::Create(connection, GetOneToManyTableSchema()); + V1_0::CommandsTable::Create(connection, GetOneToManyTableSchema()); + PackageFamilyNameTable::Create(connection, GetOneToManyTableSchema()); + ProductCodeTable::Create(connection, GetOneToManyTableSchema()); + + savepoint.Commit(); + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_1"); + + SQLite::rowid_t manifestId = V1_0::Interface::AddManifest(connection, manifest, relativePath); + + // Add the new 1.1 data + // These system reference strings are all stored with their cases folded so that they can be + // looked up ordinally; enabling the index to provide efficient searches. + PackageFamilyNameTable::EnsureExistsAndInsert(connection, manifest.GetPackageFamilyNames(), manifestId); + ProductCodeTable::EnsureExistsAndInsert(connection, manifest.GetProductCodes(), manifestId); + + savepoint.Commit(); + + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_1"); + + auto [indexModified, manifestId] = V1_0::Interface::UpdateManifest(connection, manifest, relativePath); + + // Update new 1:N tables as necessary + indexModified = PackageFamilyNameTable::UpdateIfNeededByManifestId(connection, manifest.GetPackageFamilyNames(), manifestId) || indexModified; + indexModified = ProductCodeTable::UpdateIfNeededByManifestId(connection, manifest.GetProductCodes(), manifestId) || indexModified; + + savepoint.Commit(); + + return { indexModified, manifestId }; + } + + void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_1"); + + V1_0::Interface::RemoveManifestById(connection, manifestId); + + // Remove all of the new 1:N data that is no longer referenced. + PackageFamilyNameTable::DeleteIfNotNeededByManifestId(connection, manifestId); + ProductCodeTable::DeleteIfNotNeededByManifestId(connection, manifestId); + + if (ManifestMetadataTable::Exists(connection)) + { + ManifestMetadataTable::DeleteByManifestId(connection, manifestId); + } + + savepoint.Commit(); + } + + void Interface::PrepareForPackaging(SQLite::Connection& connection) + { + PrepareForPackaging(connection, true); + } + + bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const + { + bool result = V1_0::Interface::CheckConsistency(connection, log); + + // If the v1.0 index was consistent, or if full logging of inconsistency was requested, check the v1.1 data. + if (result || log) + { + result = PackageFamilyNameTable::CheckConsistency(connection, log) && result; + } + + if (result || log) + { + result = ProductCodeTable::CheckConsistency(connection, log) && result; + } + + return result; + } + + ISQLiteIndex::SearchResult Interface::Search(const SQLite::Connection& connection, const SearchRequest& request) const + { + SearchRequest updatedRequest = request; + return SearchInternal(connection, updatedRequest); + } + + std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const + { + switch (property) + { + case PackageVersionMultiProperty::PackageFamilyName: + return PackageFamilyNameTable::GetValuesByManifestId(connection, primaryId); + case PackageVersionMultiProperty::ProductCode: + return ProductCodeTable::GetValuesByManifestId(connection, primaryId); + default: + return V1_0::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); + } + } + + ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const + { + ISQLiteIndex::MetadataResult result; + + if (ManifestMetadataTable::Exists(connection)) + { + result = ManifestMetadataTable::GetMetadataByManifestId(connection, manifestId); + } + + return result; + } + + void Interface::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "setmetadatabymanifestid_v1_1"); + + if (!ManifestMetadataTable::Exists(connection)) + { + ManifestMetadataTable::Create(connection); + } + + ManifestMetadataTable::SetMetadataByManifestId(connection, manifestId, metadata, value); + + savepoint.Commit(); + } + + void Interface::DropTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_1"); + + V1_0::Interface::DropTables(connection); + + PackageFamilyNameTable::Drop(connection); + ProductCodeTable::Drop(connection); + + ManifestMetadataTable::Drop(connection); + + savepoint.Commit(); + } + + std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const + { + return std::make_unique(connection); + } + + void Interface::PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const + { + // First, do an exact match search for the folded system reference strings + // We do this first because it is exact, and likely won't match anything else if it matches this. + PackageMatchFilter filter(PackageMatchField::PackageFamilyName, MatchType::Exact, Utility::FoldCase(query.Value)); + resultsTable.SearchOnField(filter); + + filter.Field = PackageMatchField::ProductCode; + resultsTable.SearchOnField(filter); + + // Then do the 1.0 search + V1_0::Interface::PerformQuerySearch(resultsTable, query); + } + + V1_0::OneToManyTableSchema Interface::GetOneToManyTableSchema() const + { + return V1_0::OneToManyTableSchema::Version_1_1; + } + + ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const + { + // Update any system reference strings to be folded + auto foldIfNeeded = [](PackageMatchFilter& filter) + { + if ((filter.Field == PackageMatchField::PackageFamilyName || filter.Field == PackageMatchField::ProductCode) && + filter.Type == MatchType::Exact) + { + filter.Value = Utility::FoldCase(filter.Value); + } + }; + + for (auto& inclusion : request.Inclusions) + { + foldIfNeeded(inclusion); + } + + for (auto& filter : request.Filters) + { + foldIfNeeded(filter); + } + + return V1_0::Interface::Search(connection, request); + } + + void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_1"); + + V1_0::IdTable::PrepareForPackaging(connection); + V1_0::NameTable::PrepareForPackaging(connection); + V1_0::MonikerTable::PrepareForPackaging(connection); + V1_0::VersionTable::PrepareForPackaging(connection); + V1_0::ChannelTable::PrepareForPackaging(connection); + + V1_0::PathPartTable::PrepareForPackaging(connection); + + V1_0::ManifestTable::PrepareForPackaging(connection, { + V1_0::VersionTable::ValueName(), + V1_0::ChannelTable::ValueName(), + V1_0::PathPartTable::ValueName(), + }); + + V1_0::TagsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); + V1_0::CommandsTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), false, false); + PackageFamilyNameTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); + ProductCodeTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); + + savepoint.Commit(); + + if (vacuum) + { + Vacuum(connection); + } + } + + std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const + { + switch (property) + { + case AppInstaller::Repository::PackageVersionProperty::Publisher: + { + // Publisher is not a primary data member in this version, but it may be stored in the metadata + if (ManifestMetadataTable::Exists(connection)) + { + return ManifestMetadataTable::GetMetadataByManifestIdAndMetadata(connection, manifestId, PackageVersionMetadata::Publisher); + } + + // No metadata, so no publisher + return {}; + } + default: + return V1_0::Interface::GetPropertyByManifestIdInternal(connection, manifestId, property); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp index dd461b5f41..d7c93bf177 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.cpp @@ -1,131 +1,131 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestMetadataTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - using namespace SQLite; - - static constexpr std::string_view s_ManifestMetadataTable_Table_Name = "manifest_metadata"sv; - static constexpr std::string_view s_ManifestMetadataTable_PrimaryKeyIndex_Name = "manifest_metadata_pk"sv; - static constexpr std::string_view s_ManifestMetadataTable_Manifest_Column = "manifest"sv; - static constexpr std::string_view s_ManifestMetadataTable_Metadata_Column = "metadata"sv; - static constexpr std::string_view s_ManifestMetadataTable_Value_Column = "value"sv; - - bool ManifestMetadataTable::Exists(const SQLite::Connection& connection) - { - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). - Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_ManifestMetadataTable_Table_Name); - - Statement statement = builder.Prepare(connection); - THROW_HR_IF(E_UNEXPECTED, !statement.Step()); - return statement.GetColumn(0) != 0; - } - - void ManifestMetadataTable::Create(SQLite::Connection& connection) - { - using namespace Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createmanifestmetadata_v1_1"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_ManifestMetadataTable_Table_Name).Columns({ - ColumnBuilder(s_ManifestMetadataTable_Manifest_Column, Type::Int64).NotNull(), - ColumnBuilder(s_ManifestMetadataTable_Metadata_Column, Type::Int64).NotNull(), - ColumnBuilder(s_ManifestMetadataTable_Value_Column, Type::Text) - }); - - createTableBuilder.Execute(connection); - - StatementBuilder createPKIndexBuilder; - createPKIndexBuilder.CreateUniqueIndex(s_ManifestMetadataTable_PrimaryKeyIndex_Name).On(s_ManifestMetadataTable_Table_Name). - Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column }); - createPKIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void ManifestMetadataTable::Drop(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTableIfExists(s_ManifestMetadataTable_Table_Name); - - dropTableBuilder.Execute(connection); - } - - ISQLiteIndex::MetadataResult ManifestMetadataTable::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - using namespace Builder; - - StatementBuilder builder; - builder.Select({ s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }).From(s_ManifestMetadataTable_Table_Name). - Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); - - Statement statement = builder.Prepare(connection); - - ISQLiteIndex::MetadataResult result; - while (statement.Step()) - { - result.emplace_back(std::make_pair(statement.GetColumn(0), statement.GetColumn(1))); - } - - return result; - } - - std::optional ManifestMetadataTable::GetMetadataByManifestIdAndMetadata(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata) - { - using namespace Builder; - - StatementBuilder builder; - builder.Select(s_ManifestMetadataTable_Value_Column).From(s_ManifestMetadataTable_Table_Name). - Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId). - And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); - - Statement statement = builder.Prepare(connection); - - if (statement.Step()) - { - return statement.GetColumn(0); - } - - return {}; - } - - void ManifestMetadataTable::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) - { - using namespace Builder; - - // First, we attempt to update an existing row. If not changes occurred, we then insert the new value. - // UPSERT (aka ON CONFLICT) is not available to us, as it was only introduced in 3.24.0 (2018-06-04), - // and we need to support Windows 10 (17763) which was released in 2017. - StatementBuilder updateBuilder; - updateBuilder.Update(s_ManifestMetadataTable_Table_Name).Set().Column(s_ManifestMetadataTable_Value_Column).Equals(value). - Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId).And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); - - updateBuilder.Execute(connection); - - // No changes means we need to insert the row - if (connection.GetChanges() == 0) - { - StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_ManifestMetadataTable_Table_Name). - Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }) - .Values(manifestId, metadata, value); - - insertBuilder.Execute(connection); - } - } - - void ManifestMetadataTable::DeleteByManifestId(SQLite::Connection & connection, SQLite::rowid_t manifestId) - { - using namespace Builder; - - StatementBuilder builder; - builder.DeleteFrom(s_ManifestMetadataTable_Table_Name).Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); - builder.Execute(connection); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestMetadataTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + using namespace SQLite; + + static constexpr std::string_view s_ManifestMetadataTable_Table_Name = "manifest_metadata"sv; + static constexpr std::string_view s_ManifestMetadataTable_PrimaryKeyIndex_Name = "manifest_metadata_pk"sv; + static constexpr std::string_view s_ManifestMetadataTable_Manifest_Column = "manifest"sv; + static constexpr std::string_view s_ManifestMetadataTable_Metadata_Column = "metadata"sv; + static constexpr std::string_view s_ManifestMetadataTable_Value_Column = "value"sv; + + bool ManifestMetadataTable::Exists(const SQLite::Connection& connection) + { + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). + Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_ManifestMetadataTable_Table_Name); + + Statement statement = builder.Prepare(connection); + THROW_HR_IF(E_UNEXPECTED, !statement.Step()); + return statement.GetColumn(0) != 0; + } + + void ManifestMetadataTable::Create(SQLite::Connection& connection) + { + using namespace Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createmanifestmetadata_v1_1"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_ManifestMetadataTable_Table_Name).Columns({ + ColumnBuilder(s_ManifestMetadataTable_Manifest_Column, Type::Int64).NotNull(), + ColumnBuilder(s_ManifestMetadataTable_Metadata_Column, Type::Int64).NotNull(), + ColumnBuilder(s_ManifestMetadataTable_Value_Column, Type::Text) + }); + + createTableBuilder.Execute(connection); + + StatementBuilder createPKIndexBuilder; + createPKIndexBuilder.CreateUniqueIndex(s_ManifestMetadataTable_PrimaryKeyIndex_Name).On(s_ManifestMetadataTable_Table_Name). + Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column }); + createPKIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void ManifestMetadataTable::Drop(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTableIfExists(s_ManifestMetadataTable_Table_Name); + + dropTableBuilder.Execute(connection); + } + + ISQLiteIndex::MetadataResult ManifestMetadataTable::GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + using namespace Builder; + + StatementBuilder builder; + builder.Select({ s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }).From(s_ManifestMetadataTable_Table_Name). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); + + Statement statement = builder.Prepare(connection); + + ISQLiteIndex::MetadataResult result; + while (statement.Step()) + { + result.emplace_back(std::make_pair(statement.GetColumn(0), statement.GetColumn(1))); + } + + return result; + } + + std::optional ManifestMetadataTable::GetMetadataByManifestIdAndMetadata(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata) + { + using namespace Builder; + + StatementBuilder builder; + builder.Select(s_ManifestMetadataTable_Value_Column).From(s_ManifestMetadataTable_Table_Name). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId). + And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); + + Statement statement = builder.Prepare(connection); + + if (statement.Step()) + { + return statement.GetColumn(0); + } + + return {}; + } + + void ManifestMetadataTable::SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) + { + using namespace Builder; + + // First, we attempt to update an existing row. If not changes occurred, we then insert the new value. + // UPSERT (aka ON CONFLICT) is not available to us, as it was only introduced in 3.24.0 (2018-06-04), + // and we need to support Windows 10 (17763) which was released in 2017. + StatementBuilder updateBuilder; + updateBuilder.Update(s_ManifestMetadataTable_Table_Name).Set().Column(s_ManifestMetadataTable_Value_Column).Equals(value). + Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId).And(s_ManifestMetadataTable_Metadata_Column).Equals(metadata); + + updateBuilder.Execute(connection); + + // No changes means we need to insert the row + if (connection.GetChanges() == 0) + { + StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_ManifestMetadataTable_Table_Name). + Columns({ s_ManifestMetadataTable_Manifest_Column, s_ManifestMetadataTable_Metadata_Column, s_ManifestMetadataTable_Value_Column }) + .Values(manifestId, metadata, value); + + insertBuilder.Execute(connection); + } + } + + void ManifestMetadataTable::DeleteByManifestId(SQLite::Connection & connection, SQLite::rowid_t manifestId) + { + using namespace Builder; + + StatementBuilder builder; + builder.DeleteFrom(s_ManifestMetadataTable_Table_Name).Where(s_ManifestMetadataTable_Manifest_Column).Equals(manifestId); + builder.Execute(connection); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h index dd9a42c4dd..cd399ce017 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ManifestMetadataTable.h @@ -1,44 +1,44 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Public/winget/RepositorySearch.h" - -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - // A table for storing arbitrary metadata on individual manifests. - // The table and all metadata are optional. - struct ManifestMetadataTable - { - // Determine if the table currently exists in the database. - static bool Exists(const SQLite::Connection& connection); - - // Creates the table in the database. - static void Create(SQLite::Connection& connection); - - // Drops the table. - static void Drop(SQLite::Connection& connection); - - // Gets all metadata associated with the given manifest. - // The table must exist. - static ISQLiteIndex::MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId); - - // Gets the specific metadata value for the manifest, if it exists. - // The table must exist. - static std::optional GetMetadataByManifestIdAndMetadata(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata); - - // Sets the metadata value for the given manifest. - // The table must exist. - static void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value); - - // Removes all metadata values for the given manifest. - // The table must exist. - static void DeleteByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Public/winget/RepositorySearch.h" + +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + // A table for storing arbitrary metadata on individual manifests. + // The table and all metadata are optional. + struct ManifestMetadataTable + { + // Determine if the table currently exists in the database. + static bool Exists(const SQLite::Connection& connection); + + // Creates the table in the database. + static void Create(SQLite::Connection& connection); + + // Drops the table. + static void Drop(SQLite::Connection& connection); + + // Gets all metadata associated with the given manifest. + // The table must exist. + static ISQLiteIndex::MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId); + + // Gets the specific metadata value for the manifest, if it exists. + // The table must exist. + static std::optional GetMetadataByManifestIdAndMetadata(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata); + + // Sets the metadata value for the given manifest. + // The table must exist. + static void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value); + + // Removes all metadata values for the given manifest. + // The table must exist. + static void DeleteByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/PackageFamilyNameTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/PackageFamilyNameTable.h index 1c79add033..151c26ab9c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/PackageFamilyNameTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/PackageFamilyNameTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - namespace details - { - using namespace std::string_view_literals; - - struct PackageFamilyNameTableInfo - { - inline static constexpr std::string_view TableName() { return "pfns"sv; } - inline static constexpr std::string_view ValueName() { return "pfn"sv; } - }; - } - - // The table for PackageFamilyName. - using PackageFamilyNameTable = V1_0::OneToManyTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + namespace details + { + using namespace std::string_view_literals; + + struct PackageFamilyNameTableInfo + { + inline static constexpr std::string_view TableName() { return "pfns"sv; } + inline static constexpr std::string_view ValueName() { return "pfn"sv; } + }; + } + + // The table for PackageFamilyName. + using PackageFamilyNameTable = V1_0::OneToManyTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ProductCodeTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ProductCodeTable.h index c30d519339..808852269b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ProductCodeTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/ProductCodeTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - namespace details - { - using namespace std::string_view_literals; - - struct ProductCodeTableInfo - { - inline static constexpr std::string_view TableName() { return "productcodes"sv; } - inline static constexpr std::string_view ValueName() { return "productcode"sv; } - }; - } - - // The table for ProductCode. - using ProductCodeTable = V1_0::OneToManyTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + namespace details + { + using namespace std::string_view_literals; + + struct ProductCodeTableInfo + { + inline static constexpr std::string_view TableName() { return "productcodes"sv; } + inline static constexpr std::string_view ValueName() { return "productcode"sv; } + }; + } + + // The table for ProductCode. + using ProductCodeTable = V1_0::OneToManyTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable.h index ae1f39c639..86989af0b8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable.h @@ -1,28 +1,28 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/SearchResultsTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - // Table for holding temporary search results. - struct SearchResultsTable : public V1_0::SearchResultsTable - { - SearchResultsTable(const SQLite::Connection& connection) : V1_0::SearchResultsTable(connection) {} - - SearchResultsTable(const SearchResultsTable&) = delete; - SearchResultsTable& operator=(const SearchResultsTable&) = delete; - - SearchResultsTable(SearchResultsTable&&) = default; - SearchResultsTable& operator=(SearchResultsTable&&) = default; - - protected: - std::vector BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/SearchResultsTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + // Table for holding temporary search results. + struct SearchResultsTable : public V1_0::SearchResultsTable + { + SearchResultsTable(const SQLite::Connection& connection) : V1_0::SearchResultsTable(connection) {} + + SearchResultsTable(const SearchResultsTable&) = delete; + SearchResultsTable& operator=(const SearchResultsTable&) = delete; + + SearchResultsTable(SearchResultsTable&&) = default; + SearchResultsTable& operator=(SearchResultsTable&&) = default; + + protected: + std::vector BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable_1_1.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable_1_1.cpp index 1135f02d8f..5ed8605aa4 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_1/SearchResultsTable_1_1.cpp @@ -1,30 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResultsTable.h" - -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_1/PackageFamilyNameTable.h" -#include "Microsoft/Schema/1_1/ProductCodeTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_1 -{ - std::vector SearchResultsTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const - { - switch (field) - { - case PackageMatchField::PackageFamilyName: - return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - case PackageMatchField::ProductCode: - return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - default: - return V1_0::SearchResultsTable::BuildSearchStatement(builder, field, manifestAlias, valueAlias, useLike); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResultsTable.h" + +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_1/PackageFamilyNameTable.h" +#include "Microsoft/Schema/1_1/ProductCodeTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_1 +{ + std::vector SearchResultsTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const + { + switch (field) + { + case PackageMatchField::PackageFamilyName: + return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + case PackageMatchField::ProductCode: + return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + default: + return V1_0::SearchResultsTable::BuildSearchStatement(builder, field, manifestAlias, valueAlias, useLike); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface.h index 87d1a4003c..e1ff002724 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface.h @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Microsoft/Schema/1_1/Interface.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_2 -{ - // Interface to this schema version exposed through ISQLiteIndex. - struct Interface : public V1_1::Interface - { - Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); - - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection, CreateOptions options) override; - SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; - bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; - std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; - - // Version 1.2 - Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const override; - - // Version 1.7 - void DropTables(SQLite::Connection& connection) override; - - protected: - std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const override; - SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const override; - void PrepareForPackaging(SQLite::Connection& connection, bool vacuum) override; - - // The name normalization utility - Utility::NameNormalizer m_normalizer; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Microsoft/Schema/1_1/Interface.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_2 +{ + // Interface to this schema version exposed through ISQLiteIndex. + struct Interface : public V1_1::Interface + { + Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); + + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection, CreateOptions options) override; + SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; + bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; + std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; + + // Version 1.2 + Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const override; + + // Version 1.7 + void DropTables(SQLite::Connection& connection) override; + + protected: + std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const override; + SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const override; + void PrepareForPackaging(SQLite::Connection& connection, bool vacuum) override; + + // The name normalization utility + Utility::NameNormalizer m_normalizer; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface_1_2.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface_1_2.cpp index 480da730a6..de3a415439 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface_1_2.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/Interface_1_2.cpp @@ -1,345 +1,345 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_2/Interface.h" - -#include "Microsoft/Schema/1_2/NormalizedPackageNameTable.h" -#include "Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h" - -#include "Microsoft/Schema/1_2/SearchResultsTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_2 -{ - namespace anon - { - void AddNormalizedName( - const Utility::NameNormalizer& normalizer, - const Manifest::string_t& name, - std::vector& out, - Utility::NormalizationField fieldsToInclude = Utility::NormalizationField::None) - { - Utility::NormalizedString value = normalizer.NormalizeName(Utility::FoldCase(name)).GetNormalizedName(fieldsToInclude); - if (std::find(out.begin(), out.end(), value) == out.end()) - { - out.emplace_back(std::move(value)); - } - } - - void AddLocalizationNormalizedName(const Utility::NameNormalizer& normalizer, const Manifest::ManifestLocalization& localization, std::vector& out) - { - if (localization.Contains(Manifest::Localization::PackageName)) - { - AddNormalizedName(normalizer, localization.Get(), out); - } - } - - void AddNormalizedPublisher(const Utility::NameNormalizer& normalizer, const Manifest::string_t& publisher, std::vector& out) - { - Utility::NormalizedString value = normalizer.NormalizePublisher(Utility::FoldCase(publisher)); - if (std::find(out.begin(), out.end(), value) == out.end()) - { - out.emplace_back(std::move(value)); - } - } - - void AddLocalizationNormalizedPublisher(const Utility::NameNormalizer& normalizer, const Manifest::ManifestLocalization& localization, std::vector& out) - { - if (localization.Contains(Manifest::Localization::Publisher)) - { - AddNormalizedPublisher(normalizer, localization.Get(), out); - } - } - - std::vector GetNormalizedNames(const Utility::NameNormalizer& normalizer, const Manifest::Manifest& manifest) - { - std::vector result; - - AddLocalizationNormalizedName(normalizer, manifest.DefaultLocalization, result); - for (const auto& loc : manifest.Localizations) - { - AddLocalizationNormalizedName(normalizer, loc, result); - } - - // In addition to the names used for our display, add the display names from the ARP entries - for (const auto& installer : manifest.Installers) - { - for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) - { - if (!appsAndFeaturesEntry.DisplayName.empty()) - { - AddNormalizedName(normalizer, appsAndFeaturesEntry.DisplayName, result); - // For arp display name, also add a copy with architecture info for more accurate correlation. - AddNormalizedName(normalizer, appsAndFeaturesEntry.DisplayName, result, Utility::NormalizationField::Architecture); - } - } - } - - return result; - } - - std::vector GetNormalizedPublishers(const Utility::NameNormalizer& normalizer, const Manifest::Manifest& manifest) - { - std::vector result; - - AddLocalizationNormalizedPublisher(normalizer, manifest.DefaultLocalization, result); - for (const auto& loc : manifest.Localizations) - { - AddLocalizationNormalizedPublisher(normalizer, loc, result); - } - - // In addition to the publishers used for our display, add the publishers from the ARP entries - for (const auto& installer : manifest.Installers) - { - for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) - { - if (!appsAndFeaturesEntry.Publisher.empty()) - { - AddNormalizedPublisher(normalizer, appsAndFeaturesEntry.Publisher, result); - } - } - } - - return result; - } - - // Update NormalizedNameAndPublisher with normalization and folding - // Returns true if the normalized name contains normalization field of fieldsToInclude - bool UpdateNormalizedNameAndPublisher( - PackageMatchFilter& filter, - const Utility::NameNormalizer& normalizer, - Utility::NormalizationField fieldsToInclude) - { - Utility::NormalizedName normalized = normalizer.Normalize(Utility::FoldCase(filter.Value), Utility::FoldCase(filter.Additional.value())); - filter.Value = normalized.GetNormalizedName(fieldsToInclude); - filter.Additional = normalized.Publisher(); - return WI_AreAllFlagsSet(normalized.GetNormalizedFields(), fieldsToInclude); - } - - // Update NormalizedNameAndPublisher with normalization and folding - // Returns true if any of normalized name contains normalization field of fieldsToInclude - bool UpdatePackageMatchFilters( - std::vector& filters, - const Utility::NameNormalizer& normalizer, - Utility::NormalizationField normalizedNameFieldsToFilter = Utility::NormalizationField::None) - { - bool normalizedNameFieldsFound = false; - for (auto itr = filters.begin(); itr != filters.end();) - { - if (itr->Field == PackageMatchField::NormalizedNameAndPublisher && itr->Type == MatchType::Exact) - { - if (!UpdateNormalizedNameAndPublisher(*itr, normalizer, normalizedNameFieldsToFilter)) - { - // If not matched, this package match filter will be removed. - // For example, if caller is trying to search with arch info only, values without arch will be removed from search. - itr = filters.erase(itr); - continue; - } - - normalizedNameFieldsFound = true; - } - - ++itr; - } - - return normalizedNameFieldsFound; - } - } - - Interface::Interface(Utility::NormalizationVersion normVersion) : m_normalizer(normVersion) - { - } - - SQLite::Version Interface::GetVersion() const - { - return { 1, 2 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_2"); - - V1_1::Interface::CreateTables(connection, options); - - // While the name and publisher should be linked per-locale, we are not implementing that here. - // This will mean that one can match cross locale name and publisher, but the chance that this - // leads to a confusion between packages is very small. More likely would be intentional attempts - // to confuse the correlation, which could be fairly easily carried out even with linked values. - NormalizedPackageNameTable::Create(connection, GetOneToManyTableSchema()); - NormalizedPackagePublisherTable::Create(connection, GetOneToManyTableSchema()); - - savepoint.Commit(); - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_2"); - - SQLite::rowid_t manifestId = V1_1::Interface::AddManifest(connection, manifest, relativePath); - - // Add the new 1.2 data - // These normalized strings are all stored with their cases folded so that they can be - // looked up ordinally; enabling the index to provide efficient searches. - NormalizedPackageNameTable::EnsureExistsAndInsert(connection, anon::GetNormalizedNames(m_normalizer, manifest), manifestId); - NormalizedPackagePublisherTable::EnsureExistsAndInsert(connection, anon::GetNormalizedPublishers(m_normalizer, manifest), manifestId); - - savepoint.Commit(); - - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_2"); - - auto [indexModified, manifestId] = V1_1::Interface::UpdateManifest(connection, manifest, relativePath); - - // Update new 1.2 tables as necessary - indexModified = NormalizedPackageNameTable::UpdateIfNeededByManifestId(connection, anon::GetNormalizedNames(m_normalizer, manifest), manifestId) || indexModified; - indexModified = NormalizedPackagePublisherTable::UpdateIfNeededByManifestId(connection, anon::GetNormalizedPublishers(m_normalizer, manifest), manifestId) || indexModified; - - savepoint.Commit(); - - return { indexModified, manifestId }; - } - - void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_2"); - - V1_1::Interface::RemoveManifestById(connection, manifestId); - - // Remove all of the new 1.2 data that is no longer referenced. - NormalizedPackageNameTable::DeleteIfNotNeededByManifestId(connection, manifestId); - NormalizedPackagePublisherTable::DeleteIfNotNeededByManifestId(connection, manifestId); - - savepoint.Commit(); - } - - bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const - { - bool result = V1_1::Interface::CheckConsistency(connection, log); - - // If the v1.1 index was consistent, or if full logging of inconsistency was requested, check the v1.2 data. - if (result || log) - { - result = NormalizedPackageNameTable::CheckConsistency(connection, log) && result; - } - - if (result || log) - { - result = NormalizedPackagePublisherTable::CheckConsistency(connection, log) && result; - } - - return result; - } - - std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const - { - switch (property) - { - // These values are not right, as they are normalized. But they are good enough for now and all we have. - case PackageVersionMultiProperty::Name: - return NormalizedPackageNameTable::GetValuesByManifestId(connection, primaryId); - case PackageVersionMultiProperty::Publisher: - return NormalizedPackagePublisherTable::GetValuesByManifestId(connection, primaryId); - default: - return V1_1::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); - } - } - - Utility::NormalizedName Interface::NormalizeName(std::string_view name, std::string_view publisher) const - { - return m_normalizer.Normalize(name, publisher); - } - - void Interface::DropTables(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_2"); - - V1_1::Interface::DropTables(connection); - - NormalizedPackageNameTable::Drop(connection); - NormalizedPackagePublisherTable::Drop(connection); - - savepoint.Commit(); - } - - std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const - { - return std::make_unique(connection); - } - - ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const - { - if (request.Purpose == SearchPurpose::CorrelationToInstalled) - { - // Correlate from available package to installed package - // For available package to installed package mapping, only one try is needed. - // For example, if ARP DisplayName contains arch, then the installed package's ARP DisplayName should also include arch. - auto candidateInclusionsWithArch = request.Inclusions; - if (anon::UpdatePackageMatchFilters(candidateInclusionsWithArch, m_normalizer, Utility::NormalizationField::Architecture)) - { - // If DisplayNames contain arch, only use Inclusions with arch for search - request.Inclusions = candidateInclusionsWithArch; - } - else - { - // Otherwise, just update the Inclusions with normalization - anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); - } - - return V1_1::Interface::SearchInternal(connection, request); - } - else if (request.Purpose == SearchPurpose::CorrelationToAvailable) - { - // For installed package to available package correlation, - // try the search with NormalizedName with Arch first, if not found, try with all values. - // This can be extended in the future for more granular search requests. - std::vector candidateSearches; - auto candidateSearchWithArch = request; - if (anon::UpdatePackageMatchFilters(candidateSearchWithArch.Inclusions, m_normalizer, Utility::NormalizationField::Architecture)) - { - candidateSearches.emplace_back(std::move(candidateSearchWithArch)); - } - anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); - candidateSearches.emplace_back(request); - - SearchResult result; - for (auto& candidateSearch : candidateSearches) - { - result = V1_1::Interface::SearchInternal(connection, candidateSearch); - if (!result.Matches.empty()) - { - break; - } - } - - return result; - } - else - { - anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); - anon::UpdatePackageMatchFilters(request.Filters, m_normalizer); - - return V1_1::Interface::SearchInternal(connection, request); - } - } - - void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_2"); - - V1_1::Interface::PrepareForPackaging(connection, false); - - NormalizedPackageNameTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); - NormalizedPackagePublisherTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); - - savepoint.Commit(); - - if (vacuum) - { - Vacuum(connection); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_2/Interface.h" + +#include "Microsoft/Schema/1_2/NormalizedPackageNameTable.h" +#include "Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h" + +#include "Microsoft/Schema/1_2/SearchResultsTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_2 +{ + namespace anon + { + void AddNormalizedName( + const Utility::NameNormalizer& normalizer, + const Manifest::string_t& name, + std::vector& out, + Utility::NormalizationField fieldsToInclude = Utility::NormalizationField::None) + { + Utility::NormalizedString value = normalizer.NormalizeName(Utility::FoldCase(name)).GetNormalizedName(fieldsToInclude); + if (std::find(out.begin(), out.end(), value) == out.end()) + { + out.emplace_back(std::move(value)); + } + } + + void AddLocalizationNormalizedName(const Utility::NameNormalizer& normalizer, const Manifest::ManifestLocalization& localization, std::vector& out) + { + if (localization.Contains(Manifest::Localization::PackageName)) + { + AddNormalizedName(normalizer, localization.Get(), out); + } + } + + void AddNormalizedPublisher(const Utility::NameNormalizer& normalizer, const Manifest::string_t& publisher, std::vector& out) + { + Utility::NormalizedString value = normalizer.NormalizePublisher(Utility::FoldCase(publisher)); + if (std::find(out.begin(), out.end(), value) == out.end()) + { + out.emplace_back(std::move(value)); + } + } + + void AddLocalizationNormalizedPublisher(const Utility::NameNormalizer& normalizer, const Manifest::ManifestLocalization& localization, std::vector& out) + { + if (localization.Contains(Manifest::Localization::Publisher)) + { + AddNormalizedPublisher(normalizer, localization.Get(), out); + } + } + + std::vector GetNormalizedNames(const Utility::NameNormalizer& normalizer, const Manifest::Manifest& manifest) + { + std::vector result; + + AddLocalizationNormalizedName(normalizer, manifest.DefaultLocalization, result); + for (const auto& loc : manifest.Localizations) + { + AddLocalizationNormalizedName(normalizer, loc, result); + } + + // In addition to the names used for our display, add the display names from the ARP entries + for (const auto& installer : manifest.Installers) + { + for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) + { + if (!appsAndFeaturesEntry.DisplayName.empty()) + { + AddNormalizedName(normalizer, appsAndFeaturesEntry.DisplayName, result); + // For arp display name, also add a copy with architecture info for more accurate correlation. + AddNormalizedName(normalizer, appsAndFeaturesEntry.DisplayName, result, Utility::NormalizationField::Architecture); + } + } + } + + return result; + } + + std::vector GetNormalizedPublishers(const Utility::NameNormalizer& normalizer, const Manifest::Manifest& manifest) + { + std::vector result; + + AddLocalizationNormalizedPublisher(normalizer, manifest.DefaultLocalization, result); + for (const auto& loc : manifest.Localizations) + { + AddLocalizationNormalizedPublisher(normalizer, loc, result); + } + + // In addition to the publishers used for our display, add the publishers from the ARP entries + for (const auto& installer : manifest.Installers) + { + for (const auto& appsAndFeaturesEntry : installer.AppsAndFeaturesEntries) + { + if (!appsAndFeaturesEntry.Publisher.empty()) + { + AddNormalizedPublisher(normalizer, appsAndFeaturesEntry.Publisher, result); + } + } + } + + return result; + } + + // Update NormalizedNameAndPublisher with normalization and folding + // Returns true if the normalized name contains normalization field of fieldsToInclude + bool UpdateNormalizedNameAndPublisher( + PackageMatchFilter& filter, + const Utility::NameNormalizer& normalizer, + Utility::NormalizationField fieldsToInclude) + { + Utility::NormalizedName normalized = normalizer.Normalize(Utility::FoldCase(filter.Value), Utility::FoldCase(filter.Additional.value())); + filter.Value = normalized.GetNormalizedName(fieldsToInclude); + filter.Additional = normalized.Publisher(); + return WI_AreAllFlagsSet(normalized.GetNormalizedFields(), fieldsToInclude); + } + + // Update NormalizedNameAndPublisher with normalization and folding + // Returns true if any of normalized name contains normalization field of fieldsToInclude + bool UpdatePackageMatchFilters( + std::vector& filters, + const Utility::NameNormalizer& normalizer, + Utility::NormalizationField normalizedNameFieldsToFilter = Utility::NormalizationField::None) + { + bool normalizedNameFieldsFound = false; + for (auto itr = filters.begin(); itr != filters.end();) + { + if (itr->Field == PackageMatchField::NormalizedNameAndPublisher && itr->Type == MatchType::Exact) + { + if (!UpdateNormalizedNameAndPublisher(*itr, normalizer, normalizedNameFieldsToFilter)) + { + // If not matched, this package match filter will be removed. + // For example, if caller is trying to search with arch info only, values without arch will be removed from search. + itr = filters.erase(itr); + continue; + } + + normalizedNameFieldsFound = true; + } + + ++itr; + } + + return normalizedNameFieldsFound; + } + } + + Interface::Interface(Utility::NormalizationVersion normVersion) : m_normalizer(normVersion) + { + } + + SQLite::Version Interface::GetVersion() const + { + return { 1, 2 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_2"); + + V1_1::Interface::CreateTables(connection, options); + + // While the name and publisher should be linked per-locale, we are not implementing that here. + // This will mean that one can match cross locale name and publisher, but the chance that this + // leads to a confusion between packages is very small. More likely would be intentional attempts + // to confuse the correlation, which could be fairly easily carried out even with linked values. + NormalizedPackageNameTable::Create(connection, GetOneToManyTableSchema()); + NormalizedPackagePublisherTable::Create(connection, GetOneToManyTableSchema()); + + savepoint.Commit(); + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_2"); + + SQLite::rowid_t manifestId = V1_1::Interface::AddManifest(connection, manifest, relativePath); + + // Add the new 1.2 data + // These normalized strings are all stored with their cases folded so that they can be + // looked up ordinally; enabling the index to provide efficient searches. + NormalizedPackageNameTable::EnsureExistsAndInsert(connection, anon::GetNormalizedNames(m_normalizer, manifest), manifestId); + NormalizedPackagePublisherTable::EnsureExistsAndInsert(connection, anon::GetNormalizedPublishers(m_normalizer, manifest), manifestId); + + savepoint.Commit(); + + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_2"); + + auto [indexModified, manifestId] = V1_1::Interface::UpdateManifest(connection, manifest, relativePath); + + // Update new 1.2 tables as necessary + indexModified = NormalizedPackageNameTable::UpdateIfNeededByManifestId(connection, anon::GetNormalizedNames(m_normalizer, manifest), manifestId) || indexModified; + indexModified = NormalizedPackagePublisherTable::UpdateIfNeededByManifestId(connection, anon::GetNormalizedPublishers(m_normalizer, manifest), manifestId) || indexModified; + + savepoint.Commit(); + + return { indexModified, manifestId }; + } + + void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_2"); + + V1_1::Interface::RemoveManifestById(connection, manifestId); + + // Remove all of the new 1.2 data that is no longer referenced. + NormalizedPackageNameTable::DeleteIfNotNeededByManifestId(connection, manifestId); + NormalizedPackagePublisherTable::DeleteIfNotNeededByManifestId(connection, manifestId); + + savepoint.Commit(); + } + + bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const + { + bool result = V1_1::Interface::CheckConsistency(connection, log); + + // If the v1.1 index was consistent, or if full logging of inconsistency was requested, check the v1.2 data. + if (result || log) + { + result = NormalizedPackageNameTable::CheckConsistency(connection, log) && result; + } + + if (result || log) + { + result = NormalizedPackagePublisherTable::CheckConsistency(connection, log) && result; + } + + return result; + } + + std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const + { + switch (property) + { + // These values are not right, as they are normalized. But they are good enough for now and all we have. + case PackageVersionMultiProperty::Name: + return NormalizedPackageNameTable::GetValuesByManifestId(connection, primaryId); + case PackageVersionMultiProperty::Publisher: + return NormalizedPackagePublisherTable::GetValuesByManifestId(connection, primaryId); + default: + return V1_1::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); + } + } + + Utility::NormalizedName Interface::NormalizeName(std::string_view name, std::string_view publisher) const + { + return m_normalizer.Normalize(name, publisher); + } + + void Interface::DropTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_2"); + + V1_1::Interface::DropTables(connection); + + NormalizedPackageNameTable::Drop(connection); + NormalizedPackagePublisherTable::Drop(connection); + + savepoint.Commit(); + } + + std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const + { + return std::make_unique(connection); + } + + ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const + { + if (request.Purpose == SearchPurpose::CorrelationToInstalled) + { + // Correlate from available package to installed package + // For available package to installed package mapping, only one try is needed. + // For example, if ARP DisplayName contains arch, then the installed package's ARP DisplayName should also include arch. + auto candidateInclusionsWithArch = request.Inclusions; + if (anon::UpdatePackageMatchFilters(candidateInclusionsWithArch, m_normalizer, Utility::NormalizationField::Architecture)) + { + // If DisplayNames contain arch, only use Inclusions with arch for search + request.Inclusions = candidateInclusionsWithArch; + } + else + { + // Otherwise, just update the Inclusions with normalization + anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); + } + + return V1_1::Interface::SearchInternal(connection, request); + } + else if (request.Purpose == SearchPurpose::CorrelationToAvailable) + { + // For installed package to available package correlation, + // try the search with NormalizedName with Arch first, if not found, try with all values. + // This can be extended in the future for more granular search requests. + std::vector candidateSearches; + auto candidateSearchWithArch = request; + if (anon::UpdatePackageMatchFilters(candidateSearchWithArch.Inclusions, m_normalizer, Utility::NormalizationField::Architecture)) + { + candidateSearches.emplace_back(std::move(candidateSearchWithArch)); + } + anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); + candidateSearches.emplace_back(request); + + SearchResult result; + for (auto& candidateSearch : candidateSearches) + { + result = V1_1::Interface::SearchInternal(connection, candidateSearch); + if (!result.Matches.empty()) + { + break; + } + } + + return result; + } + else + { + anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); + anon::UpdatePackageMatchFilters(request.Filters, m_normalizer); + + return V1_1::Interface::SearchInternal(connection, request); + } + } + + void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_2"); + + V1_1::Interface::PrepareForPackaging(connection, false); + + NormalizedPackageNameTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); + NormalizedPackagePublisherTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); + + savepoint.Commit(); + + if (vacuum) + { + Vacuum(connection); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackageNameTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackageNameTable.h index 799d2f0071..56a3d12366 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackageNameTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackageNameTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_2 -{ - namespace details - { - using namespace std::string_view_literals; - - struct NormalizedPackageNameTableInfo - { - inline static constexpr std::string_view TableName() { return "norm_names"sv; } - inline static constexpr std::string_view ValueName() { return "norm_name"sv; } - }; - } - - // The table for NormalizedPackageName. - using NormalizedPackageNameTable = V1_0::OneToManyTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_2 +{ + namespace details + { + using namespace std::string_view_literals; + + struct NormalizedPackageNameTableInfo + { + inline static constexpr std::string_view TableName() { return "norm_names"sv; } + inline static constexpr std::string_view ValueName() { return "norm_name"sv; } + }; + } + + // The table for NormalizedPackageName. + using NormalizedPackageNameTable = V1_0::OneToManyTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h index 46f08742ff..16ebbd0d5e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_2 -{ - namespace details - { - using namespace std::string_view_literals; - - struct NormalizedPackagePublisherTableInfo - { - inline static constexpr std::string_view TableName() { return "norm_publishers"sv; } - inline static constexpr std::string_view ValueName() { return "norm_publisher"sv; } - }; - } - - // The table for NormalizedPackagePublisher. - using NormalizedPackagePublisherTable = V1_0::OneToManyTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_2 +{ + namespace details + { + using namespace std::string_view_literals; + + struct NormalizedPackagePublisherTableInfo + { + inline static constexpr std::string_view TableName() { return "norm_publishers"sv; } + inline static constexpr std::string_view ValueName() { return "norm_publisher"sv; } + }; + } + + // The table for NormalizedPackagePublisher. + using NormalizedPackagePublisherTable = V1_0::OneToManyTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable.h index 331868aa6e..1f926a5931 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_1/SearchResultsTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_2 -{ - // Table for holding temporary search results. - struct SearchResultsTable : public V1_1::SearchResultsTable - { - SearchResultsTable(const SQLite::Connection& connection) : V1_1::SearchResultsTable(connection) {} - - SearchResultsTable(const SearchResultsTable&) = delete; - SearchResultsTable& operator=(const SearchResultsTable&) = delete; - - SearchResultsTable(SearchResultsTable&&) = default; - SearchResultsTable& operator=(SearchResultsTable&&) = default; - - protected: - std::vector BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const override; - - // Import all overrides of this function - using V1_0::SearchResultsTable::BindStatementForMatchType; - - void BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_1/SearchResultsTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_2 +{ + // Table for holding temporary search results. + struct SearchResultsTable : public V1_1::SearchResultsTable + { + SearchResultsTable(const SQLite::Connection& connection) : V1_1::SearchResultsTable(connection) {} + + SearchResultsTable(const SearchResultsTable&) = delete; + SearchResultsTable& operator=(const SearchResultsTable&) = delete; + + SearchResultsTable(SearchResultsTable&&) = default; + SearchResultsTable& operator=(SearchResultsTable&&) = default; + + protected: + std::vector BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const override; + + // Import all overrides of this function + using V1_0::SearchResultsTable::BindStatementForMatchType; + + void BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable_1_2.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable_1_2.cpp index dee470a1b4..4470c9fdb8 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable_1_2.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_2/SearchResultsTable_1_2.cpp @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResultsTable.h" - -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_2/NormalizedPackageNameTable.h" -#include "Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_2 -{ - std::vector SearchResultsTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const - { - switch (field) - { - case PackageMatchField::NormalizedNameAndPublisher: - return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - default: - return V1_1::SearchResultsTable::BuildSearchStatement(builder, field, manifestAlias, valueAlias, useLike); - } - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) - { - V1_0::SearchResultsTable::BindStatementForMatchType(statement, filter, bindIndex); - - if (filter.Field == PackageMatchField::NormalizedNameAndPublisher) - { - BindStatementForMatchType(statement, filter.Type, bindIndex[1], filter.Additional.value()); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResultsTable.h" + +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_2/NormalizedPackageNameTable.h" +#include "Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_2 +{ + std::vector SearchResultsTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const + { + switch (field) + { + case PackageMatchField::NormalizedNameAndPublisher: + return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + default: + return V1_1::SearchResultsTable::BuildSearchStatement(builder, field, manifestAlias, valueAlias, useLike); + } + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) + { + V1_0::SearchResultsTable::BindStatementForMatchType(statement, filter, bindIndex); + + if (filter.Field == PackageMatchField::NormalizedNameAndPublisher) + { + BindStatementForMatchType(statement, filter.Type, bindIndex[1], filter.Additional.value()); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/HashVirtualTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/HashVirtualTable.h index 731a499ffc..76c6c52bbf 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/HashVirtualTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/HashVirtualTable.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include - -using namespace std::string_view_literals; - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_3 -{ - // A virtual table used to add a direct column onto the manifest table. - struct HashVirtualTable - { - // The id type (which is actually the value for this virtual table) - using id_t = SQLite::blob_t; - - // The name of the column. - static constexpr std::string_view ValueName() - { - return "hash"sv; - } - - // The value type of the column. - static constexpr SQLite::Builder::Type SQLiteType() - { - return SQLite::Builder::Type::Blob; - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include + +using namespace std::string_view_literals; + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_3 +{ + // A virtual table used to add a direct column onto the manifest table. + struct HashVirtualTable + { + // The id type (which is actually the value for this virtual table) + using id_t = SQLite::blob_t; + + // The name of the column. + static constexpr std::string_view ValueName() + { + return "hash"sv; + } + + // The value type of the column. + static constexpr SQLite::Builder::Type SQLiteType() + { + return SQLite::Builder::Type::Blob; + } + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface.h index d476f97166..49628350fd 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Microsoft/Schema/1_2/Interface.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_3 -{ - // Interface to this schema version exposed through ISQLiteIndex. - struct Interface : public V1_2::Interface - { - Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); - - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection, CreateOptions options) override; - SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - - protected: - // Gets a property already knowing that the manifest id is valid. - std::optional GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Microsoft/Schema/1_2/Interface.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_3 +{ + // Interface to this schema version exposed through ISQLiteIndex. + struct Interface : public V1_2::Interface + { + Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); + + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection, CreateOptions options) override; + SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + + protected: + // Gets a property already knowing that the manifest id is valid. + std::optional GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp index 383cc93e35..e328bf106a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_3/Interface_1_3.cpp @@ -1,90 +1,90 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_3/Interface.h" -#include - -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_3/HashVirtualTable.h" -#include - -namespace AppInstaller::Repository::Microsoft::Schema::V1_3 -{ - Interface::Interface(Utility::NormalizationVersion normVersion) : V1_2::Interface(normVersion) - { - } - - SQLite::Version Interface::GetVersion() const - { - return { 1, 3 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_3"); - - V1_2::Interface::CreateTables(connection, options); - - V1_0::ManifestTable::AddColumn(connection, { HashVirtualTable::ValueName(), HashVirtualTable::SQLiteType() }); - - savepoint.Commit(); - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_3"); - - SQLite::rowid_t manifestId = V1_2::Interface::AddManifest(connection, manifest, relativePath); - - // Set the hash value if provided - if (!manifest.StreamSha256.empty()) - { - THROW_HR_IF(E_INVALIDARG, manifest.StreamSha256.size() != Utility::SHA256::HashBufferSizeInBytes); - V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, manifest.StreamSha256); - } - - savepoint.Commit(); - - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_3"); - - auto [indexModified, manifestId] = V1_2::Interface::UpdateManifest(connection, manifest, relativePath); - - // Set the hash value if provided - if (!manifest.StreamSha256.empty()) - { - THROW_HR_IF(E_INVALIDARG, manifest.StreamSha256.size() != Utility::SHA256::HashBufferSizeInBytes); - - auto currentHash = std::get<0>(V1_0::ManifestTable::GetIdsById(connection, manifestId)); - - if (currentHash.size() != Utility::SHA256::HashBufferSizeInBytes || - !std::equal(currentHash.begin(), currentHash.end(), manifest.StreamSha256.begin())) - { - V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, manifest.StreamSha256); - indexModified = true; - } - } - - savepoint.Commit(); - - return { indexModified, manifestId }; - } - - std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const - { - switch (property) - { - case AppInstaller::Repository::PackageVersionProperty::ManifestSHA256Hash: - { - std::optional hash = V1_0::ManifestTable::GetIdById(connection, manifestId); - return (!hash || hash->empty()) ? std::optional{} : Utility::SHA256::ConvertToString(hash.value()); - } - default: - return V1_2::Interface::GetPropertyByManifestIdInternal(connection, manifestId, property); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_3/Interface.h" +#include + +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_3/HashVirtualTable.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema::V1_3 +{ + Interface::Interface(Utility::NormalizationVersion normVersion) : V1_2::Interface(normVersion) + { + } + + SQLite::Version Interface::GetVersion() const + { + return { 1, 3 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_3"); + + V1_2::Interface::CreateTables(connection, options); + + V1_0::ManifestTable::AddColumn(connection, { HashVirtualTable::ValueName(), HashVirtualTable::SQLiteType() }); + + savepoint.Commit(); + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_3"); + + SQLite::rowid_t manifestId = V1_2::Interface::AddManifest(connection, manifest, relativePath); + + // Set the hash value if provided + if (!manifest.StreamSha256.empty()) + { + THROW_HR_IF(E_INVALIDARG, manifest.StreamSha256.size() != Utility::SHA256::HashBufferSizeInBytes); + V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, manifest.StreamSha256); + } + + savepoint.Commit(); + + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_3"); + + auto [indexModified, manifestId] = V1_2::Interface::UpdateManifest(connection, manifest, relativePath); + + // Set the hash value if provided + if (!manifest.StreamSha256.empty()) + { + THROW_HR_IF(E_INVALIDARG, manifest.StreamSha256.size() != Utility::SHA256::HashBufferSizeInBytes); + + auto currentHash = std::get<0>(V1_0::ManifestTable::GetIdsById(connection, manifestId)); + + if (currentHash.size() != Utility::SHA256::HashBufferSizeInBytes || + !std::equal(currentHash.begin(), currentHash.end(), manifest.StreamSha256.begin())) + { + V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, manifest.StreamSha256); + indexModified = true; + } + } + + savepoint.Commit(); + + return { indexModified, manifestId }; + } + + std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const + { + switch (property) + { + case AppInstaller::Repository::PackageVersionProperty::ManifestSHA256Hash: + { + std::optional hash = V1_0::ManifestTable::GetIdById(connection, manifestId); + return (!hash || hash->empty()) ? std::optional{} : Utility::SHA256::ConvertToString(hash.value()); + } + default: + return V1_2::Interface::GetPropertyByManifestIdInternal(connection, manifestId, property); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.cpp index 1741cfae0b..46050c26ee 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.cpp @@ -1,577 +1,577 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DependenciesTable.h" -#include -#include "winget\DependenciesGraph.h" -#include "Microsoft/Schema/1_0/OneToOneTable.h" -#include "Microsoft/Schema/1_0/IdTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_0/VersionTable.h" -#include "Microsoft/Schema/1_0/Interface.h" -#include "Microsoft/Schema/1_0/ChannelTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::V1_4 -{ - using namespace AppInstaller; - using namespace std::string_view_literals; - using namespace SQLite::Builder; - using namespace Schema::V1_0; - using QCol = SQLite::Builder::QualifiedColumn; - - static constexpr std::string_view s_DependenciesTable_Table_Name = "dependencies"sv; - static constexpr std::string_view s_DependenciesTable_Index_Name = "dependencies_pkindex"sv; - static constexpr std::string_view s_DependenciesTable_Manifest_Column_Name = "manifest"sv; - static constexpr std::string_view s_DependenciesTable_MinVersion_Column_Name = "min_version"sv; - static constexpr std::string_view s_DependenciesTable_PackageId_Column_Name = "package_id"; - - namespace - { - struct DependencyTableRow - { - SQLite::rowid_t m_packageRowId; - SQLite::rowid_t m_manifestRowId; - - // Ideally this should be version row id, the version string is more needed than row id, - // this prevents converting back and forth between version row id and version string. - std::optional m_version; - - bool operator <(const DependencyTableRow& rhs) const - { - auto lhsVersion = m_version.has_value() ? m_version.value() : ""; - auto rhsVersion = rhs.m_version.has_value() ? rhs.m_version.value() : ""; - return std::tie(m_packageRowId, m_manifestRowId, lhsVersion) < std::tie(rhs.m_packageRowId, rhs.m_manifestRowId, rhsVersion); - } - }; - - void ThrowOnMissingPackageNodes(std::vector& missingPackageNodes) - { - if (!missingPackageNodes.empty()) - { - std::string missingPackages{ missingPackageNodes.begin()->Id()}; - std::for_each( - missingPackageNodes.begin() + 1, - missingPackageNodes.end(), - [&](auto& dep) { missingPackages.append(", " + dep.Id()); }); - THROW_HR_MSG(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE, "Missing packages: %hs", missingPackages.c_str()); - } - } - - std::set GetAndLinkDependencies( - SQLite::Connection& connection, - const Manifest::Manifest& manifest, - SQLite::rowid_t manifestRowId, - Manifest::DependencyType dependencyType) - { - std::set dependencies; - std::vector missingPackageNodes; - - for (const auto& installer : manifest.Installers) - { - installer.Dependencies.ApplyToType(dependencyType, [&](Manifest::Dependency dependency) - { - auto packageRowId = IdTable::SelectIdByValue(connection, dependency.Id(), true); - std::optional version; - - if (!packageRowId.has_value()) - { - missingPackageNodes.emplace_back(dependency); - return; - } - - if (dependency.MinVersion.has_value()) - { - version = dependency.MinVersion.value().ToString(); - } - - dependencies.emplace(DependencyTableRow{ packageRowId.value(), manifestRowId, version }); - }); - } - - ThrowOnMissingPackageNodes(missingPackageNodes); - - return dependencies; - } - - bool RemoveDependenciesByRowIds(SQLite::Connection& connection, std::vector dependencyTableRows) - { - using namespace SQLite::Builder; - bool tableUpdated = false; - if (dependencyTableRows.empty()) - { - return tableUpdated; - } - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "remove_dependencies_by_rowid"); - - SQLite::Builder::StatementBuilder builder; - builder - .DeleteFrom(s_DependenciesTable_Table_Name) - .Where(s_DependenciesTable_PackageId_Column_Name).Equals(Unbound) - .And(s_DependenciesTable_Manifest_Column_Name).Equals(Unbound); - - - SQLite::Statement deleteStmt = builder.Prepare(connection); - for (auto row : dependencyTableRows) - { - deleteStmt.Reset(); - deleteStmt.Bind(1, row.m_packageRowId); - deleteStmt.Bind(2, row.m_manifestRowId); - deleteStmt.Execute(); - tableUpdated = true; - } - - savepoint.Commit(); - return tableUpdated; - } - - bool InsertManifestDependencies( - SQLite::Connection& connection, - std::set& dependenciesTableRows) - { - using namespace SQLite::Builder; - using namespace Schema::V1_0; - bool tableUpdated = false; - - StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_DependenciesTable_Table_Name) - .Columns({ s_DependenciesTable_Manifest_Column_Name, s_DependenciesTable_MinVersion_Column_Name, s_DependenciesTable_PackageId_Column_Name }) - .Values(Unbound, Unbound, Unbound); - SQLite::Statement insert = insertBuilder.Prepare(connection); - - for (const auto& dep : dependenciesTableRows) - { - insert.Reset(); - insert.Bind(1, dep.m_manifestRowId); - - if (dep.m_version.has_value()) - { - insert.Bind(2, VersionTable::EnsureExists(connection, dep.m_version.value())); - } - else - { - insert.Bind(2, nullptr); - } - - insert.Bind(3, dep.m_packageRowId); - - insert.Execute(); - tableUpdated = true; - } - - return tableUpdated; - } - } - - bool DependenciesTable::Exists(const SQLite::Connection& connection) - { - using namespace SQLite; - - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). - Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_DependenciesTable_Table_Name); - - Statement statement = builder.Prepare(connection); - THROW_HR_IF(E_UNEXPECTED, !statement.Step()); - return statement.GetColumn(0) != 0; - } - - std::string_view DependenciesTable::TableName() - { - return s_DependenciesTable_Table_Name; - } - - void DependenciesTable::Create(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createDependencyTable_v1_4"); - constexpr std::string_view dependencyIndexByVersionId = "dependencies_version_id_index"; - constexpr std::string_view dependencyIndexByPackageId = "dependencies_package_id_index"; - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(TableName()).BeginColumns(); - createTableBuilder.Column(IntegerPrimaryKey()); - - std::array notNullableDependenciesColumns - { - DependenciesTableColumnInfo{ s_DependenciesTable_Manifest_Column_Name }, - DependenciesTableColumnInfo{ s_DependenciesTable_PackageId_Column_Name } - }; - - std::array nullableDependenciesColumns - { - DependenciesTableColumnInfo{ s_DependenciesTable_MinVersion_Column_Name } - }; - - // Add dependencies column tables not null columns. - for (const DependenciesTableColumnInfo& value : notNullableDependenciesColumns) - { - createTableBuilder.Column(ColumnBuilder(value.Name, Type::RowId).NotNull()); - } - - // Add dependencies column tables null columns. - for (const DependenciesTableColumnInfo& value : nullableDependenciesColumns) - { - createTableBuilder.Column(ColumnBuilder(value.Name, Type::RowId)); - } - - createTableBuilder.EndColumns(); - - createTableBuilder.Execute(connection); - - // Primary key index by package rowid and manifest rowid. - StatementBuilder createPKIndexBuilder; - createPKIndexBuilder.CreateUniqueIndex(s_DependenciesTable_Index_Name).On(s_DependenciesTable_Table_Name).Columns({ s_DependenciesTable_Manifest_Column_Name, s_DependenciesTable_PackageId_Column_Name }); - createPKIndexBuilder.Execute(connection); - - // Index of dependency by Manifest id. - StatementBuilder createIndexByManifestIdBuilder; - createIndexByManifestIdBuilder.CreateIndex(dependencyIndexByVersionId).On(s_DependenciesTable_Table_Name).Columns({ s_DependenciesTable_MinVersion_Column_Name }); - createIndexByManifestIdBuilder.Execute(connection); - - // Index of dependency by package id. - StatementBuilder createIndexByPackageIdBuilder; - createIndexByPackageIdBuilder.CreateIndex(dependencyIndexByPackageId).On(s_DependenciesTable_Table_Name).Columns({ s_DependenciesTable_PackageId_Column_Name }); - createIndexByPackageIdBuilder.Execute(connection); - - savepoint.Commit(); - } - - void DependenciesTable::Drop(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTableIfExists(s_DependenciesTable_Table_Name); - - dropTableBuilder.Execute(connection); - } - - void DependenciesTable::AddDependencies(SQLite::Connection& connection, const Manifest::Manifest& manifest, SQLite::rowid_t manifestRowId) - { - if (!Exists(connection)) - { - return; - } - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "add_dependencies_v1_4"); - - auto dependencies = GetAndLinkDependencies(connection, manifest, manifestRowId, Manifest::DependencyType::Package); - if (!dependencies.size()) - { - return; - } - - InsertManifestDependencies(connection, dependencies); - - savepoint.Commit(); - } - - bool DependenciesTable::UpdateDependencies(SQLite::Connection& connection, const Manifest::Manifest& manifest, SQLite::rowid_t manifestRowId) - { - if (!Exists(connection)) - { - return false; - } - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "update_dependencies_v1_4"); - - const auto dependencies = GetAndLinkDependencies(connection, manifest, manifestRowId, Manifest::DependencyType::Package); - auto existingDependencies = GetDependenciesByManifestRowId(connection, manifestRowId); - - // Get dependencies to add. - std::set toAddDependencies; - std::copy_if( - dependencies.begin(), - dependencies.end(), - std::inserter(toAddDependencies, toAddDependencies.begin()), - [&](DependencyTableRow dep) - { - Utility::NormalizedString version = dep.m_version.has_value() ? dep.m_version.value() : ""; - return existingDependencies.find(std::make_pair(dep.m_packageRowId, version)) == existingDependencies.end(); - } - ); - - // Get dependencies to remove. - std::vector toRemoveDependencies; - std::for_each( - existingDependencies.begin(), - existingDependencies.end(), - [&](std::pair row) - { - if (dependencies.find(DependencyTableRow{row.first, manifestRowId, row.second}) == dependencies.end()) - { - toRemoveDependencies.emplace_back(DependencyTableRow{ row.first, manifestRowId }); - } - } - ); - - bool tableUpdated = RemoveDependenciesByRowIds(connection, toRemoveDependencies); - tableUpdated = InsertManifestDependencies(connection, toAddDependencies) || tableUpdated; - savepoint.Commit(); - - return tableUpdated; - } - - void DependenciesTable::RemoveDependencies(SQLite::Connection& connection, SQLite::rowid_t manifestRowId) - { - if (!Exists(connection)) - { - return; - } - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "remove_dependencies_by_manifest_v1_4"); - - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_DependenciesTable_Table_Name).Where(s_DependenciesTable_Manifest_Column_Name).Equals(manifestRowId); - - builder.Execute(connection); - savepoint.Commit(); - } - - std::vector> DependenciesTable::GetDependentsById(const SQLite::Connection& connection, Manifest::string_t packageId) - { - constexpr std::string_view depTableAlias = "dep"; - constexpr std::string_view minVersionAlias = "minV"; - constexpr std::string_view packageIdAlias = "pId"; - - - StatementBuilder builder; - // Find all manifest that depend on this package. Use outer join for joining version table as min_version may be NULL. - // SELECT [dep].[manifest], [dep].[min_version], [pId].[id], [minV].[version] FROM [dependencies] AS [dep] - // LEFT OUTER JOIN [versions] AS [minV] ON [dep].[min_version] = [minV].[rowid] - // JOIN [ids] AS [pId] ON [pId].[rowid] = [dep].[package_id] - // WHERE [pId].[id] = ? - builder.Select() - .Column(QCol(depTableAlias, s_DependenciesTable_Manifest_Column_Name)) - .Column(QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)) - .Column(QCol(packageIdAlias, IdTable::ValueName())) - .Column(QCol(minVersionAlias, VersionTable::ValueName())) - .From({ s_DependenciesTable_Table_Name }).As(depTableAlias) - .LeftOuterJoin({ VersionTable::TableName() }).As(minVersionAlias) - .On(QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name), QCol(minVersionAlias, SQLite::RowIDName)) - .Join({ IdTable::TableName() }).As(packageIdAlias) - .On(QCol(packageIdAlias, SQLite::RowIDName), QCol(depTableAlias, s_DependenciesTable_PackageId_Column_Name)) - .Where(QCol(packageIdAlias, IdTable::ValueName())).Equals(Unbound); - - SQLite::Statement stmt = builder.Prepare(connection); - stmt.Bind(1, std::string{ packageId }); - - std::vector> resultSet; - - while (stmt.Step()) - { - Utility::NormalizedString version = ""; - if (!stmt.GetColumnIsNull(1)) - { - // If min_version is not NULL, use the corresponding value from Version table. - version = stmt.GetColumn(3); - } - resultSet.emplace_back( - std::make_pair(stmt.GetColumn(0), std::move(version))); - } - - return resultSet; - } - - std::set> DependenciesTable::GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) - { - SQLite::Builder::StatementBuilder builder; - - constexpr std::string_view depTableAlias = "dep"; - constexpr std::string_view minVersionAlias = "minV"; - - std::set> resultSet; - - // Use Outer join since min_version could have NULL value. - // SELECT [dep].[package_id], [dep].[min_version], [minV].[version] FROM [dependencies] AS [dep] - // LEFT OUTER JOIN [versions] AS [minV] ON [minV].[rowid] = [dep].[min_version] - // WHERE [dep].[manifest] = ? - builder.Select() - .Column(QCol(depTableAlias, s_DependenciesTable_PackageId_Column_Name)) - .Column(QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)) - .Column(QCol(minVersionAlias, VersionTable::ValueName())) - .From({ s_DependenciesTable_Table_Name }).As(depTableAlias) - .LeftOuterJoin({ VersionTable::TableName() }).As(minVersionAlias) - .On(QCol(minVersionAlias, SQLite::RowIDName), QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)) - .Where(QCol(depTableAlias, s_DependenciesTable_Manifest_Column_Name)).Equals(Unbound); - - SQLite::Statement select = builder.Prepare(connection); - - select.Bind(1, manifestRowId); - while (select.Step()) - { - Utility::NormalizedString version = ""; - if (!select.GetColumnIsNull(1)) - { - // If min_version is not NULL, use the corresponding value from Version table. - version = select.GetColumn(2); - } - resultSet.emplace(std::make_pair(select.GetColumn(0), std::move(version))); - } - - return resultSet; - } - - void DependenciesTable::PrepareForPackaging(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareForPacking_V1_4"); - - StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex({ s_DependenciesTable_Index_Name }); - dropIndexBuilder.Execute(connection); - - StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable({ s_DependenciesTable_Table_Name }); - dropTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - bool DependenciesTable::CheckConsistency(const SQLite::Connection& connection, bool log) - { - StatementBuilder builder; - - if (!Exists(connection)) - { - return true; - } - - builder.Select(QCol(s_DependenciesTable_Table_Name, SQLite::RowIDName)) - .From(s_DependenciesTable_Table_Name) - .LeftOuterJoin(IdTable::TableName()) - .On(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_PackageId_Column_Name), QCol(IdTable::TableName(), SQLite::RowIDName)) - .LeftOuterJoin(ManifestTable::TableName()) - .On(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_Manifest_Column_Name), QCol(ManifestTable::TableName(), SQLite::RowIDName)) - .LeftOuterJoin(VersionTable::TableName()) - .On(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_MinVersion_Column_Name), QCol(VersionTable::TableName(), SQLite::RowIDName)) - .Where(QCol(ManifestTable::TableName(), SQLite::RowIDName)).IsNull() - .Or(QCol(VersionTable::TableName(), SQLite::RowIDName)).IsNull().And(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_MinVersion_Column_Name)).IsNotNull() - .Or(QCol(IdTable::TableName(), SQLite::RowIDName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - - bool result = true; - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] row in [" << s_DependenciesTable_Table_Name << "] rowid [" << select.GetColumn(0) << "]"); - } - - return result; - } - - bool DependenciesTable::IsValueReferenced(const SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t valueRowId) - { - if (!Exists(connection)) - { - return false; - } - - StatementBuilder builder; - - if (tableName != V1_0::VersionTable::TableName()) - { - return false; - } - - std::array columns = { s_DependenciesTable_MinVersion_Column_Name }; - bool referenced = false; - - for(auto column: columns) - { - builder.Select(SQLite::RowIDName).From(s_DependenciesTable_Table_Name).Where(column).Equals(Unbound).Limit(1); - - SQLite::Statement select = builder.Prepare(connection); - - select.Bind(1, valueRowId); - if (select.Step()) - { - referenced = true; - break; - } - } - - return referenced; - } - - std::vector DependenciesTable::GetDependenciesMinVersionsRowIdByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) - { - if (!Exists(connection)) - { - return {}; - } - - StatementBuilder builder; - - std::vector result; - - // Find all versions for manifest row. - // SELECT [min_version] FROM [dependencies] - // WHERE [manifest] = ? - builder.Select() - .Column(s_DependenciesTable_MinVersion_Column_Name) - .From({ s_DependenciesTable_Table_Name }) - .Where(s_DependenciesTable_Manifest_Column_Name).Equals(Unbound); - - auto select = builder.Prepare(connection); - - select.Bind(1, manifestRowId); - - while (select.Step()) - { - if (!select.GetColumnIsNull(0)) - { - result.emplace_back(select.GetColumn(0)); - } - } - - return result; - } - - std::vector> DependenciesTable::GetAllDependenciesWithMinVersions(const SQLite::Connection& connection) - { - if (!Exists(connection)) - { - return {}; - } - - std::vector> result; - - constexpr std::string_view depTableAlias = "dep"; - constexpr std::string_view minVersionAlias = "minV"; - - StatementBuilder builder; - - // SELECT [dep].[package_id], [minV].[version] FROM [dependencies] AS [dep] - // JOIN [versions] AS [minV] ON [minV].[rowid] = [dep].[min_version] - builder.Select() - .Column(QCol(depTableAlias, s_DependenciesTable_PackageId_Column_Name)) - .Column(QCol(minVersionAlias, VersionTable::ValueName())) - .From({ s_DependenciesTable_Table_Name }).As(depTableAlias) - .Join({ VersionTable::TableName() }).As(minVersionAlias) - .On(QCol(minVersionAlias, SQLite::RowIDName), QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - Utility::NormalizedString version = ""; - if (!select.GetColumnIsNull(1)) - { - version = select.GetColumn(1); - } - - if (!version.empty()) - { - result.emplace_back(std::make_pair(select.GetColumn(0), version)); - } - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DependenciesTable.h" +#include +#include "winget\DependenciesGraph.h" +#include "Microsoft/Schema/1_0/OneToOneTable.h" +#include "Microsoft/Schema/1_0/IdTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_0/VersionTable.h" +#include "Microsoft/Schema/1_0/Interface.h" +#include "Microsoft/Schema/1_0/ChannelTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::V1_4 +{ + using namespace AppInstaller; + using namespace std::string_view_literals; + using namespace SQLite::Builder; + using namespace Schema::V1_0; + using QCol = SQLite::Builder::QualifiedColumn; + + static constexpr std::string_view s_DependenciesTable_Table_Name = "dependencies"sv; + static constexpr std::string_view s_DependenciesTable_Index_Name = "dependencies_pkindex"sv; + static constexpr std::string_view s_DependenciesTable_Manifest_Column_Name = "manifest"sv; + static constexpr std::string_view s_DependenciesTable_MinVersion_Column_Name = "min_version"sv; + static constexpr std::string_view s_DependenciesTable_PackageId_Column_Name = "package_id"; + + namespace + { + struct DependencyTableRow + { + SQLite::rowid_t m_packageRowId; + SQLite::rowid_t m_manifestRowId; + + // Ideally this should be version row id, the version string is more needed than row id, + // this prevents converting back and forth between version row id and version string. + std::optional m_version; + + bool operator <(const DependencyTableRow& rhs) const + { + auto lhsVersion = m_version.has_value() ? m_version.value() : ""; + auto rhsVersion = rhs.m_version.has_value() ? rhs.m_version.value() : ""; + return std::tie(m_packageRowId, m_manifestRowId, lhsVersion) < std::tie(rhs.m_packageRowId, rhs.m_manifestRowId, rhsVersion); + } + }; + + void ThrowOnMissingPackageNodes(std::vector& missingPackageNodes) + { + if (!missingPackageNodes.empty()) + { + std::string missingPackages{ missingPackageNodes.begin()->Id()}; + std::for_each( + missingPackageNodes.begin() + 1, + missingPackageNodes.end(), + [&](auto& dep) { missingPackages.append(", " + dep.Id()); }); + THROW_HR_MSG(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE, "Missing packages: %hs", missingPackages.c_str()); + } + } + + std::set GetAndLinkDependencies( + SQLite::Connection& connection, + const Manifest::Manifest& manifest, + SQLite::rowid_t manifestRowId, + Manifest::DependencyType dependencyType) + { + std::set dependencies; + std::vector missingPackageNodes; + + for (const auto& installer : manifest.Installers) + { + installer.Dependencies.ApplyToType(dependencyType, [&](Manifest::Dependency dependency) + { + auto packageRowId = IdTable::SelectIdByValue(connection, dependency.Id(), true); + std::optional version; + + if (!packageRowId.has_value()) + { + missingPackageNodes.emplace_back(dependency); + return; + } + + if (dependency.MinVersion.has_value()) + { + version = dependency.MinVersion.value().ToString(); + } + + dependencies.emplace(DependencyTableRow{ packageRowId.value(), manifestRowId, version }); + }); + } + + ThrowOnMissingPackageNodes(missingPackageNodes); + + return dependencies; + } + + bool RemoveDependenciesByRowIds(SQLite::Connection& connection, std::vector dependencyTableRows) + { + using namespace SQLite::Builder; + bool tableUpdated = false; + if (dependencyTableRows.empty()) + { + return tableUpdated; + } + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "remove_dependencies_by_rowid"); + + SQLite::Builder::StatementBuilder builder; + builder + .DeleteFrom(s_DependenciesTable_Table_Name) + .Where(s_DependenciesTable_PackageId_Column_Name).Equals(Unbound) + .And(s_DependenciesTable_Manifest_Column_Name).Equals(Unbound); + + + SQLite::Statement deleteStmt = builder.Prepare(connection); + for (auto row : dependencyTableRows) + { + deleteStmt.Reset(); + deleteStmt.Bind(1, row.m_packageRowId); + deleteStmt.Bind(2, row.m_manifestRowId); + deleteStmt.Execute(); + tableUpdated = true; + } + + savepoint.Commit(); + return tableUpdated; + } + + bool InsertManifestDependencies( + SQLite::Connection& connection, + std::set& dependenciesTableRows) + { + using namespace SQLite::Builder; + using namespace Schema::V1_0; + bool tableUpdated = false; + + StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_DependenciesTable_Table_Name) + .Columns({ s_DependenciesTable_Manifest_Column_Name, s_DependenciesTable_MinVersion_Column_Name, s_DependenciesTable_PackageId_Column_Name }) + .Values(Unbound, Unbound, Unbound); + SQLite::Statement insert = insertBuilder.Prepare(connection); + + for (const auto& dep : dependenciesTableRows) + { + insert.Reset(); + insert.Bind(1, dep.m_manifestRowId); + + if (dep.m_version.has_value()) + { + insert.Bind(2, VersionTable::EnsureExists(connection, dep.m_version.value())); + } + else + { + insert.Bind(2, nullptr); + } + + insert.Bind(3, dep.m_packageRowId); + + insert.Execute(); + tableUpdated = true; + } + + return tableUpdated; + } + } + + bool DependenciesTable::Exists(const SQLite::Connection& connection) + { + using namespace SQLite; + + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). + Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_DependenciesTable_Table_Name); + + Statement statement = builder.Prepare(connection); + THROW_HR_IF(E_UNEXPECTED, !statement.Step()); + return statement.GetColumn(0) != 0; + } + + std::string_view DependenciesTable::TableName() + { + return s_DependenciesTable_Table_Name; + } + + void DependenciesTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createDependencyTable_v1_4"); + constexpr std::string_view dependencyIndexByVersionId = "dependencies_version_id_index"; + constexpr std::string_view dependencyIndexByPackageId = "dependencies_package_id_index"; + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(TableName()).BeginColumns(); + createTableBuilder.Column(IntegerPrimaryKey()); + + std::array notNullableDependenciesColumns + { + DependenciesTableColumnInfo{ s_DependenciesTable_Manifest_Column_Name }, + DependenciesTableColumnInfo{ s_DependenciesTable_PackageId_Column_Name } + }; + + std::array nullableDependenciesColumns + { + DependenciesTableColumnInfo{ s_DependenciesTable_MinVersion_Column_Name } + }; + + // Add dependencies column tables not null columns. + for (const DependenciesTableColumnInfo& value : notNullableDependenciesColumns) + { + createTableBuilder.Column(ColumnBuilder(value.Name, Type::RowId).NotNull()); + } + + // Add dependencies column tables null columns. + for (const DependenciesTableColumnInfo& value : nullableDependenciesColumns) + { + createTableBuilder.Column(ColumnBuilder(value.Name, Type::RowId)); + } + + createTableBuilder.EndColumns(); + + createTableBuilder.Execute(connection); + + // Primary key index by package rowid and manifest rowid. + StatementBuilder createPKIndexBuilder; + createPKIndexBuilder.CreateUniqueIndex(s_DependenciesTable_Index_Name).On(s_DependenciesTable_Table_Name).Columns({ s_DependenciesTable_Manifest_Column_Name, s_DependenciesTable_PackageId_Column_Name }); + createPKIndexBuilder.Execute(connection); + + // Index of dependency by Manifest id. + StatementBuilder createIndexByManifestIdBuilder; + createIndexByManifestIdBuilder.CreateIndex(dependencyIndexByVersionId).On(s_DependenciesTable_Table_Name).Columns({ s_DependenciesTable_MinVersion_Column_Name }); + createIndexByManifestIdBuilder.Execute(connection); + + // Index of dependency by package id. + StatementBuilder createIndexByPackageIdBuilder; + createIndexByPackageIdBuilder.CreateIndex(dependencyIndexByPackageId).On(s_DependenciesTable_Table_Name).Columns({ s_DependenciesTable_PackageId_Column_Name }); + createIndexByPackageIdBuilder.Execute(connection); + + savepoint.Commit(); + } + + void DependenciesTable::Drop(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTableIfExists(s_DependenciesTable_Table_Name); + + dropTableBuilder.Execute(connection); + } + + void DependenciesTable::AddDependencies(SQLite::Connection& connection, const Manifest::Manifest& manifest, SQLite::rowid_t manifestRowId) + { + if (!Exists(connection)) + { + return; + } + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "add_dependencies_v1_4"); + + auto dependencies = GetAndLinkDependencies(connection, manifest, manifestRowId, Manifest::DependencyType::Package); + if (!dependencies.size()) + { + return; + } + + InsertManifestDependencies(connection, dependencies); + + savepoint.Commit(); + } + + bool DependenciesTable::UpdateDependencies(SQLite::Connection& connection, const Manifest::Manifest& manifest, SQLite::rowid_t manifestRowId) + { + if (!Exists(connection)) + { + return false; + } + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "update_dependencies_v1_4"); + + const auto dependencies = GetAndLinkDependencies(connection, manifest, manifestRowId, Manifest::DependencyType::Package); + auto existingDependencies = GetDependenciesByManifestRowId(connection, manifestRowId); + + // Get dependencies to add. + std::set toAddDependencies; + std::copy_if( + dependencies.begin(), + dependencies.end(), + std::inserter(toAddDependencies, toAddDependencies.begin()), + [&](DependencyTableRow dep) + { + Utility::NormalizedString version = dep.m_version.has_value() ? dep.m_version.value() : ""; + return existingDependencies.find(std::make_pair(dep.m_packageRowId, version)) == existingDependencies.end(); + } + ); + + // Get dependencies to remove. + std::vector toRemoveDependencies; + std::for_each( + existingDependencies.begin(), + existingDependencies.end(), + [&](std::pair row) + { + if (dependencies.find(DependencyTableRow{row.first, manifestRowId, row.second}) == dependencies.end()) + { + toRemoveDependencies.emplace_back(DependencyTableRow{ row.first, manifestRowId }); + } + } + ); + + bool tableUpdated = RemoveDependenciesByRowIds(connection, toRemoveDependencies); + tableUpdated = InsertManifestDependencies(connection, toAddDependencies) || tableUpdated; + savepoint.Commit(); + + return tableUpdated; + } + + void DependenciesTable::RemoveDependencies(SQLite::Connection& connection, SQLite::rowid_t manifestRowId) + { + if (!Exists(connection)) + { + return; + } + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ s_DependenciesTable_Table_Name } + "remove_dependencies_by_manifest_v1_4"); + + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_DependenciesTable_Table_Name).Where(s_DependenciesTable_Manifest_Column_Name).Equals(manifestRowId); + + builder.Execute(connection); + savepoint.Commit(); + } + + std::vector> DependenciesTable::GetDependentsById(const SQLite::Connection& connection, Manifest::string_t packageId) + { + constexpr std::string_view depTableAlias = "dep"; + constexpr std::string_view minVersionAlias = "minV"; + constexpr std::string_view packageIdAlias = "pId"; + + + StatementBuilder builder; + // Find all manifest that depend on this package. Use outer join for joining version table as min_version may be NULL. + // SELECT [dep].[manifest], [dep].[min_version], [pId].[id], [minV].[version] FROM [dependencies] AS [dep] + // LEFT OUTER JOIN [versions] AS [minV] ON [dep].[min_version] = [minV].[rowid] + // JOIN [ids] AS [pId] ON [pId].[rowid] = [dep].[package_id] + // WHERE [pId].[id] = ? + builder.Select() + .Column(QCol(depTableAlias, s_DependenciesTable_Manifest_Column_Name)) + .Column(QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)) + .Column(QCol(packageIdAlias, IdTable::ValueName())) + .Column(QCol(minVersionAlias, VersionTable::ValueName())) + .From({ s_DependenciesTable_Table_Name }).As(depTableAlias) + .LeftOuterJoin({ VersionTable::TableName() }).As(minVersionAlias) + .On(QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name), QCol(minVersionAlias, SQLite::RowIDName)) + .Join({ IdTable::TableName() }).As(packageIdAlias) + .On(QCol(packageIdAlias, SQLite::RowIDName), QCol(depTableAlias, s_DependenciesTable_PackageId_Column_Name)) + .Where(QCol(packageIdAlias, IdTable::ValueName())).Equals(Unbound); + + SQLite::Statement stmt = builder.Prepare(connection); + stmt.Bind(1, std::string{ packageId }); + + std::vector> resultSet; + + while (stmt.Step()) + { + Utility::NormalizedString version = ""; + if (!stmt.GetColumnIsNull(1)) + { + // If min_version is not NULL, use the corresponding value from Version table. + version = stmt.GetColumn(3); + } + resultSet.emplace_back( + std::make_pair(stmt.GetColumn(0), std::move(version))); + } + + return resultSet; + } + + std::set> DependenciesTable::GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) + { + SQLite::Builder::StatementBuilder builder; + + constexpr std::string_view depTableAlias = "dep"; + constexpr std::string_view minVersionAlias = "minV"; + + std::set> resultSet; + + // Use Outer join since min_version could have NULL value. + // SELECT [dep].[package_id], [dep].[min_version], [minV].[version] FROM [dependencies] AS [dep] + // LEFT OUTER JOIN [versions] AS [minV] ON [minV].[rowid] = [dep].[min_version] + // WHERE [dep].[manifest] = ? + builder.Select() + .Column(QCol(depTableAlias, s_DependenciesTable_PackageId_Column_Name)) + .Column(QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)) + .Column(QCol(minVersionAlias, VersionTable::ValueName())) + .From({ s_DependenciesTable_Table_Name }).As(depTableAlias) + .LeftOuterJoin({ VersionTable::TableName() }).As(minVersionAlias) + .On(QCol(minVersionAlias, SQLite::RowIDName), QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)) + .Where(QCol(depTableAlias, s_DependenciesTable_Manifest_Column_Name)).Equals(Unbound); + + SQLite::Statement select = builder.Prepare(connection); + + select.Bind(1, manifestRowId); + while (select.Step()) + { + Utility::NormalizedString version = ""; + if (!select.GetColumnIsNull(1)) + { + // If min_version is not NULL, use the corresponding value from Version table. + version = select.GetColumn(2); + } + resultSet.emplace(std::make_pair(select.GetColumn(0), std::move(version))); + } + + return resultSet; + } + + void DependenciesTable::PrepareForPackaging(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareForPacking_V1_4"); + + StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex({ s_DependenciesTable_Index_Name }); + dropIndexBuilder.Execute(connection); + + StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable({ s_DependenciesTable_Table_Name }); + dropTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + bool DependenciesTable::CheckConsistency(const SQLite::Connection& connection, bool log) + { + StatementBuilder builder; + + if (!Exists(connection)) + { + return true; + } + + builder.Select(QCol(s_DependenciesTable_Table_Name, SQLite::RowIDName)) + .From(s_DependenciesTable_Table_Name) + .LeftOuterJoin(IdTable::TableName()) + .On(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_PackageId_Column_Name), QCol(IdTable::TableName(), SQLite::RowIDName)) + .LeftOuterJoin(ManifestTable::TableName()) + .On(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_Manifest_Column_Name), QCol(ManifestTable::TableName(), SQLite::RowIDName)) + .LeftOuterJoin(VersionTable::TableName()) + .On(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_MinVersion_Column_Name), QCol(VersionTable::TableName(), SQLite::RowIDName)) + .Where(QCol(ManifestTable::TableName(), SQLite::RowIDName)).IsNull() + .Or(QCol(VersionTable::TableName(), SQLite::RowIDName)).IsNull().And(QCol(s_DependenciesTable_Table_Name, s_DependenciesTable_MinVersion_Column_Name)).IsNotNull() + .Or(QCol(IdTable::TableName(), SQLite::RowIDName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + + bool result = true; + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] row in [" << s_DependenciesTable_Table_Name << "] rowid [" << select.GetColumn(0) << "]"); + } + + return result; + } + + bool DependenciesTable::IsValueReferenced(const SQLite::Connection& connection, std::string_view tableName, SQLite::rowid_t valueRowId) + { + if (!Exists(connection)) + { + return false; + } + + StatementBuilder builder; + + if (tableName != V1_0::VersionTable::TableName()) + { + return false; + } + + std::array columns = { s_DependenciesTable_MinVersion_Column_Name }; + bool referenced = false; + + for(auto column: columns) + { + builder.Select(SQLite::RowIDName).From(s_DependenciesTable_Table_Name).Where(column).Equals(Unbound).Limit(1); + + SQLite::Statement select = builder.Prepare(connection); + + select.Bind(1, valueRowId); + if (select.Step()) + { + referenced = true; + break; + } + } + + return referenced; + } + + std::vector DependenciesTable::GetDependenciesMinVersionsRowIdByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) + { + if (!Exists(connection)) + { + return {}; + } + + StatementBuilder builder; + + std::vector result; + + // Find all versions for manifest row. + // SELECT [min_version] FROM [dependencies] + // WHERE [manifest] = ? + builder.Select() + .Column(s_DependenciesTable_MinVersion_Column_Name) + .From({ s_DependenciesTable_Table_Name }) + .Where(s_DependenciesTable_Manifest_Column_Name).Equals(Unbound); + + auto select = builder.Prepare(connection); + + select.Bind(1, manifestRowId); + + while (select.Step()) + { + if (!select.GetColumnIsNull(0)) + { + result.emplace_back(select.GetColumn(0)); + } + } + + return result; + } + + std::vector> DependenciesTable::GetAllDependenciesWithMinVersions(const SQLite::Connection& connection) + { + if (!Exists(connection)) + { + return {}; + } + + std::vector> result; + + constexpr std::string_view depTableAlias = "dep"; + constexpr std::string_view minVersionAlias = "minV"; + + StatementBuilder builder; + + // SELECT [dep].[package_id], [minV].[version] FROM [dependencies] AS [dep] + // JOIN [versions] AS [minV] ON [minV].[rowid] = [dep].[min_version] + builder.Select() + .Column(QCol(depTableAlias, s_DependenciesTable_PackageId_Column_Name)) + .Column(QCol(minVersionAlias, VersionTable::ValueName())) + .From({ s_DependenciesTable_Table_Name }).As(depTableAlias) + .Join({ VersionTable::TableName() }).As(minVersionAlias) + .On(QCol(minVersionAlias, SQLite::RowIDName), QCol(depTableAlias, s_DependenciesTable_MinVersion_Column_Name)); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + Utility::NormalizedString version = ""; + if (!select.GetColumnIsNull(1)) + { + version = select.GetColumn(1); + } + + if (!version.empty()) + { + result.emplace_back(std::make_pair(select.GetColumn(0), version)); + } + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.h index 9ff9b1985d..cc9707c200 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/DependenciesTable.h @@ -21,8 +21,8 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_4 static std::string_view TableName(); // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - + static void Create(SQLite::Connection& connection); + // Drops the table. static void Drop(SQLite::Connection& connection); @@ -58,4 +58,4 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_4 // Get all dependencies with min versions in the dependencies table, used during consistency check. Returning a list of pair. static std::vector> GetAllDependenciesWithMinVersions(const SQLite::Connection& connection); }; -} +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface.h index 44ef5d70db..c58f1a1c08 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface.h @@ -33,4 +33,4 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_4 // Semantic check to validate dependencies with min versions are satisfied. bool ValidateDependenciesWithMinVersions(const SQLite::Connection& connection, bool log) const; }; -} +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface_1_4.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface_1_4.cpp index eb0bf9340c..d0948b34b3 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface_1_4.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_4/Interface_1_4.cpp @@ -1,188 +1,188 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_4/Interface.h" -#include "Microsoft/Schema/1_0/VersionTable.h" - -#include "Microsoft/Schema/1_4/DependenciesTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::V1_4 -{ - Interface::Interface(Utility::NormalizationVersion normVersion) : V1_3::Interface(normVersion) - { - } - - SQLite::Version Interface::GetVersion() const - { - return { 1, 4 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_4"); - - V1_3::Interface::CreateTables(connection, options); - - if (WI_IsFlagClear(options, CreateOptions::DisableDependenciesSupport)) - { - DependenciesTable::Create(connection); - } - - savepoint.Commit(); - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_4"); - - SQLite::rowid_t manifestId = V1_3::Interface::AddManifest(connection, manifest, relativePath); - - DependenciesTable::AddDependencies(connection, manifest, manifestId); - - savepoint.Commit(); - - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_4"); - - auto [indexModified, manifestId] = V1_3::Interface::UpdateManifest(connection, manifest, relativePath); - - bool dependenciesModified = DependenciesTable::UpdateDependencies(connection, manifest, manifestId); - indexModified = indexModified || dependenciesModified; - - savepoint.Commit(); - - return { indexModified, manifestId }; - } - - void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - // Get all versions that need cleaning from the version table. - auto minVersions = DependenciesTable::GetDependenciesMinVersionsRowIdByManifestId(connection, manifestId); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removemanifest_v1_4"); - - // Removes dependences for the manifest id. - DependenciesTable::RemoveDependencies(connection, manifestId); - - // Removes the manifest. - V1_3::Interface::RemoveManifestById(connection, manifestId); - - // Remove the versions that are not needed. - for (auto minVersion : minVersions) - { - if (NotNeeded(connection, Schema::V1_0::VersionTable::TableName(), Schema::V1_0::VersionTable::ValueName(), minVersion)) - { - Schema::V1_0::VersionTable::DeleteById(connection, minVersion); - } - } - - savepoint.Commit(); - } - - bool Interface::NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const - { - bool result = V1_0::Interface::NotNeeded(connection, tableName, valueName, id); - - return !DependenciesTable::IsValueReferenced(connection, tableName, id) && result; - } - - void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_4"); - - V1_3::Interface::PrepareForPackaging(connection, false); - - DependenciesTable::PrepareForPackaging(connection); - - savepoint.Commit(); - - if (vacuum) - { - Vacuum(connection); - } - } - - bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const - { - bool result = V1_3::Interface::CheckConsistency(connection, log); - - // If the v1.3 index was consistent, or if full logging of inconsistency was requested, check the v1.4 data. - if (result || log) - { - result = DependenciesTable::CheckConsistency(connection, log) && result; - } - - if (result || log) - { - result = ValidateDependenciesWithMinVersions(connection, log) && result; - } - - return result; - } - - std::set> Interface::GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const - { - return DependenciesTable::GetDependenciesByManifestRowId(connection, manifestRowId); - } - - std::vector> Interface::GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const - { - return DependenciesTable::GetDependentsById(connection, packageId); - } - - void Interface::DropTables(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_4"); - - V1_2::Interface::DropTables(connection); - - DependenciesTable::Drop(connection); - - savepoint.Commit(); - } - - bool Interface::ValidateDependenciesWithMinVersions(const SQLite::Connection& connection, bool log) const - { - try - { - bool result = true; - // A map to store already checked dependency package latest versions. - std::map checkedVersions; - - auto dependencies = DependenciesTable::GetAllDependenciesWithMinVersions(connection); - for (auto const& dependency : dependencies) - { - // If the dependency package has not been checked yet, add to the map. - if (checkedVersions.find(dependency.first) == checkedVersions.end()) - { - auto versionKeys = GetVersionKeysById(connection, dependency.first); - THROW_HR_IF(E_UNEXPECTED, versionKeys.empty()); - checkedVersions.emplace(dependency.first, versionKeys[0].VersionAndChannel.GetVersion()); - } - - // If the latest version is less than min version required, fail the validation. - if (checkedVersions[dependency.first] < Utility::Version{ dependency.second }) - { - AICLI_LOG(Repo, Error, << "Dependency with min version not satisfied. Dependency package row id: " << dependency.first << " min version: " << dependency.second); - result = false; - - if (!log) - { - break; - } - } - } - - return result; - } - catch (...) - { - AICLI_LOG(Repo, Error, << "ValidateDependenciesWithMinVersions() encountered internal error. Returning false."); - return false; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_4/Interface.h" +#include "Microsoft/Schema/1_0/VersionTable.h" + +#include "Microsoft/Schema/1_4/DependenciesTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::V1_4 +{ + Interface::Interface(Utility::NormalizationVersion normVersion) : V1_3::Interface(normVersion) + { + } + + SQLite::Version Interface::GetVersion() const + { + return { 1, 4 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_4"); + + V1_3::Interface::CreateTables(connection, options); + + if (WI_IsFlagClear(options, CreateOptions::DisableDependenciesSupport)) + { + DependenciesTable::Create(connection); + } + + savepoint.Commit(); + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_4"); + + SQLite::rowid_t manifestId = V1_3::Interface::AddManifest(connection, manifest, relativePath); + + DependenciesTable::AddDependencies(connection, manifest, manifestId); + + savepoint.Commit(); + + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_4"); + + auto [indexModified, manifestId] = V1_3::Interface::UpdateManifest(connection, manifest, relativePath); + + bool dependenciesModified = DependenciesTable::UpdateDependencies(connection, manifest, manifestId); + indexModified = indexModified || dependenciesModified; + + savepoint.Commit(); + + return { indexModified, manifestId }; + } + + void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + // Get all versions that need cleaning from the version table. + auto minVersions = DependenciesTable::GetDependenciesMinVersionsRowIdByManifestId(connection, manifestId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removemanifest_v1_4"); + + // Removes dependences for the manifest id. + DependenciesTable::RemoveDependencies(connection, manifestId); + + // Removes the manifest. + V1_3::Interface::RemoveManifestById(connection, manifestId); + + // Remove the versions that are not needed. + for (auto minVersion : minVersions) + { + if (NotNeeded(connection, Schema::V1_0::VersionTable::TableName(), Schema::V1_0::VersionTable::ValueName(), minVersion)) + { + Schema::V1_0::VersionTable::DeleteById(connection, minVersion); + } + } + + savepoint.Commit(); + } + + bool Interface::NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const + { + bool result = V1_0::Interface::NotNeeded(connection, tableName, valueName, id); + + return !DependenciesTable::IsValueReferenced(connection, tableName, id) && result; + } + + void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_4"); + + V1_3::Interface::PrepareForPackaging(connection, false); + + DependenciesTable::PrepareForPackaging(connection); + + savepoint.Commit(); + + if (vacuum) + { + Vacuum(connection); + } + } + + bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const + { + bool result = V1_3::Interface::CheckConsistency(connection, log); + + // If the v1.3 index was consistent, or if full logging of inconsistency was requested, check the v1.4 data. + if (result || log) + { + result = DependenciesTable::CheckConsistency(connection, log) && result; + } + + if (result || log) + { + result = ValidateDependenciesWithMinVersions(connection, log) && result; + } + + return result; + } + + std::set> Interface::GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const + { + return DependenciesTable::GetDependenciesByManifestRowId(connection, manifestRowId); + } + + std::vector> Interface::GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const + { + return DependenciesTable::GetDependentsById(connection, packageId); + } + + void Interface::DropTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_4"); + + V1_2::Interface::DropTables(connection); + + DependenciesTable::Drop(connection); + + savepoint.Commit(); + } + + bool Interface::ValidateDependenciesWithMinVersions(const SQLite::Connection& connection, bool log) const + { + try + { + bool result = true; + // A map to store already checked dependency package latest versions. + std::map checkedVersions; + + auto dependencies = DependenciesTable::GetAllDependenciesWithMinVersions(connection); + for (auto const& dependency : dependencies) + { + // If the dependency package has not been checked yet, add to the map. + if (checkedVersions.find(dependency.first) == checkedVersions.end()) + { + auto versionKeys = GetVersionKeysById(connection, dependency.first); + THROW_HR_IF(E_UNEXPECTED, versionKeys.empty()); + checkedVersions.emplace(dependency.first, versionKeys[0].VersionAndChannel.GetVersion()); + } + + // If the latest version is less than min version required, fail the validation. + if (checkedVersions[dependency.first] < Utility::Version{ dependency.second }) + { + AICLI_LOG(Repo, Error, << "Dependency with min version not satisfied. Dependency package row id: " << dependency.first << " min version: " << dependency.second); + result = false; + + if (!log) + { + break; + } + } + } + + return result; + } + catch (...) + { + AICLI_LOG(Repo, Error, << "ValidateDependenciesWithMinVersions() encountered internal error. Returning false."); + return false; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/ArpVersionVirtualTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/ArpVersionVirtualTable.h index 17c6da8765..7811530243 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/ArpVersionVirtualTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/ArpVersionVirtualTable.h @@ -1,68 +1,68 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Microsoft/Schema/1_0/VersionTable.h" -#include "Microsoft/Schema/1_0/VirtualTableBase.h" -#include - -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Microsoft::Schema::V1_5 -{ - // A virtual table used to add Arp min version to ManifestTable, the values are stored in VersionTable. - struct ArpMinVersionVirtualTable : public V1_0::VirtualTableBase - { - // The id type - using id_t = V1_0::VersionTable::id_t; - - // The value type - using value_t = V1_0::VersionTable::value_t; - - // The name of the table. - static constexpr std::string_view TableName() - { - return V1_0::VersionTable::TableName(); - } - - // The value name of the column. - static constexpr std::string_view ValueName() - { - return V1_0::VersionTable::ValueName(); - } - - // The value name of the manifest table column. - static constexpr std::string_view ManifestColumnName() - { - return "arp_min_version"sv; - } - }; - - // A virtual table used to add Arp max version to ManifestTable, the values are stored in VersionTable. - struct ArpMaxVersionVirtualTable : public V1_0::VirtualTableBase - { - // The id type - using id_t = V1_0::VersionTable::id_t; - - // The value type - using value_t = V1_0::VersionTable::value_t; - - // The name of the table. - static constexpr std::string_view TableName() - { - return V1_0::VersionTable::TableName(); - } - - // The value name of the column. - static constexpr std::string_view ValueName() - { - return V1_0::VersionTable::ValueName(); - } - - // The value name of the manifest table column. - static constexpr std::string_view ManifestColumnName() - { - return "arp_max_version"sv; - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Microsoft/Schema/1_0/VersionTable.h" +#include "Microsoft/Schema/1_0/VirtualTableBase.h" +#include + +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Microsoft::Schema::V1_5 +{ + // A virtual table used to add Arp min version to ManifestTable, the values are stored in VersionTable. + struct ArpMinVersionVirtualTable : public V1_0::VirtualTableBase + { + // The id type + using id_t = V1_0::VersionTable::id_t; + + // The value type + using value_t = V1_0::VersionTable::value_t; + + // The name of the table. + static constexpr std::string_view TableName() + { + return V1_0::VersionTable::TableName(); + } + + // The value name of the column. + static constexpr std::string_view ValueName() + { + return V1_0::VersionTable::ValueName(); + } + + // The value name of the manifest table column. + static constexpr std::string_view ManifestColumnName() + { + return "arp_min_version"sv; + } + }; + + // A virtual table used to add Arp max version to ManifestTable, the values are stored in VersionTable. + struct ArpMaxVersionVirtualTable : public V1_0::VirtualTableBase + { + // The id type + using id_t = V1_0::VersionTable::id_t; + + // The value type + using value_t = V1_0::VersionTable::value_t; + + // The name of the table. + static constexpr std::string_view TableName() + { + return V1_0::VersionTable::TableName(); + } + + // The value name of the column. + static constexpr std::string_view ValueName() + { + return V1_0::VersionTable::ValueName(); + } + + // The value name of the manifest table column. + static constexpr std::string_view ManifestColumnName() + { + return "arp_max_version"sv; + } + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface.h index 932a472b10..d68bd779d2 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface.h @@ -26,11 +26,11 @@ namespace AppInstaller::Repository::Microsoft::Schema::V1_5 // Gets a property already knowing that the manifest id is valid. std::optional GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const override; - private: - // Gets the ARP version ranges for the given package identifier. - std::vector GetArpVersionRanges(const SQLite::Connection& connection, SQLite::rowid_t packageIdentifier) const; + private: + // Gets the ARP version ranges for the given package identifier. + std::vector GetArpVersionRanges(const SQLite::Connection& connection, SQLite::rowid_t packageIdentifier) const; // Semantic check to validate all arp version ranges within the index bool ValidateArpVersionConsistency(const SQLite::Connection& connection, bool log) const; }; -} +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface_1_5.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface_1_5.cpp index db6a274026..c443238925 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface_1_5.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_5/Interface_1_5.cpp @@ -1,285 +1,285 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_5/Interface.h" -#include "Microsoft/Schema/1_5/ArpVersionVirtualTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_0/IdTable.h" -#include "Microsoft/Schema/1_0/VersionTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::V1_5 -{ - Interface::Interface(Utility::NormalizationVersion normVersion) : V1_4::Interface(normVersion) - { - } - - SQLite::Version Interface::GetVersion() const - { - return { 1, 5 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_5"); - - V1_4::Interface::CreateTables(connection, options); - - V1_0::ManifestTable::AddColumn(connection, { ArpMinVersionVirtualTable::ManifestColumnName(), SQLite::Builder::Type::RowId }); - V1_0::ManifestTable::AddColumn(connection, { ArpMaxVersionVirtualTable::ManifestColumnName(), SQLite::Builder::Type::RowId }); - - savepoint.Commit(); - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_5"); - - SQLite::rowid_t manifestId = V1_4::Interface::AddManifest(connection, manifest, relativePath); - - auto arpVersionRange = manifest.GetArpVersionRange(); - Manifest::string_t arpMinVersion, arpMaxVersion; - - if (!arpVersionRange.IsEmpty()) - { - // Check to see if adding this version range will create a conflict - SQLite::rowid_t packageIdentifier = V1_0::ManifestTable::GetIdById(connection, manifestId).value(); - std::vector ranges = GetArpVersionRanges(connection, packageIdentifier); - ranges.push_back(arpVersionRange); - - if (Utility::HasOverlapInVersionRanges(ranges)) - { - AICLI_LOG(Repo, Error, << "Overlapped Arp version ranges found for package. All ranges currently in index followed by new range:\n" << [&]() { - std::stringstream stream; - for (const auto& range : ranges) - { - stream << '[' << range.GetMinVersion().ToString() << "] - [" << range.GetMaxVersion().ToString() << "]\n"; - } - return std::move(stream).str(); - }()); - THROW_HR(APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); - } - - arpMinVersion = arpVersionRange.GetMinVersion().ToString(); - arpMaxVersion = arpVersionRange.GetMaxVersion().ToString(); - } - - SQLite::rowid_t arpMinVersionId = V1_0::VersionTable::EnsureExists(connection, arpMinVersion); - SQLite::rowid_t arpMaxVersionId = V1_0::VersionTable::EnsureExists(connection, arpMaxVersion); - V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMinVersionId); - V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMaxVersionId); - - savepoint.Commit(); - - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_5"); - - auto [indexModified, manifestId] = V1_4::Interface::UpdateManifest(connection, manifest, relativePath); - - auto [oldMinVersionId, oldMaxVersionId] = - V1_0::ManifestTable::GetIdsById(connection, manifestId); - - auto arpVersionRange = manifest.GetArpVersionRange(); - Manifest::string_t arpMinVersion = arpVersionRange.IsEmpty() ? "" : arpVersionRange.GetMinVersion().ToString(); - Manifest::string_t arpMaxVersion = arpVersionRange.IsEmpty() ? "" : arpVersionRange.GetMaxVersion().ToString(); - - SQLite::rowid_t arpMinVersionId = V1_0::VersionTable::EnsureExists(connection, arpMinVersion); - SQLite::rowid_t arpMaxVersionId = V1_0::VersionTable::EnsureExists(connection, arpMaxVersion); - - // For cleaning up the old entries after update if applicable - bool cleanOldMinVersionId = false; - bool cleanOldMaxVersionId = false; - - if (arpMinVersionId != oldMinVersionId) - { - V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMinVersionId); - cleanOldMinVersionId = true; - indexModified = true; - } - - if (arpMaxVersionId != oldMaxVersionId) - { - V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMaxVersionId); - cleanOldMaxVersionId = true; - indexModified = true; - } - - if (!arpVersionRange.IsEmpty()) - { - // Check to see if the new set of version ranges created a conflict. - // We could have done this before attempting the update but it would be more complex and SQLite gives us easy rollback. - SQLite::rowid_t packageIdentifier = V1_0::ManifestTable::GetIdById(connection, manifestId).value(); - std::vector ranges = GetArpVersionRanges(connection, packageIdentifier); - - if (Utility::HasOverlapInVersionRanges(ranges)) - { - AICLI_LOG(Repo, Error, << "Overlapped Arp version ranges found for package. Ranges that would be present with attempted upgrade:\n" << [&]() { - std::stringstream stream; - for (const auto& range : ranges) - { - stream << '[' << range.GetMinVersion().ToString() << "] - [" << range.GetMaxVersion().ToString() << "]\n"; - } - return std::move(stream).str(); - }()); - THROW_HR(APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); - } - } - - if (cleanOldMinVersionId && NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), oldMinVersionId)) - { - V1_0::VersionTable::DeleteById(connection, oldMinVersionId); - } - - if (cleanOldMaxVersionId && oldMaxVersionId != oldMinVersionId && NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), oldMaxVersionId)) - { - V1_0::VersionTable::DeleteById(connection, oldMaxVersionId); - } - - savepoint.Commit(); - - return { indexModified, manifestId }; - } - - void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - // Get the old arp version ids of the values from the manifest table - auto [arpMinVersionId, arpMaxVersionId] = - V1_0::ManifestTable::GetIdsById(connection, manifestId); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_5"); - - // Removes the manifest. - V1_4::Interface::RemoveManifestById(connection, manifestId); - - // Remove the versions that are not needed. - if (NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), arpMinVersionId)) - { - V1_0::VersionTable::DeleteById(connection, arpMinVersionId); - } - - if (arpMaxVersionId != arpMinVersionId && NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), arpMaxVersionId)) - { - V1_0::VersionTable::DeleteById(connection, arpMaxVersionId); - } - - savepoint.Commit(); - } - - bool Interface::NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const - { - bool result = V1_4::Interface::NotNeeded(connection, tableName, valueName, id); - - if (result && tableName == V1_0::VersionTable::TableName()) - { - if (valueName != V1_0::VersionTable::ValueName()) - { - result = !V1_0::ManifestTable::IsValueReferenced(connection, V1_0::VersionTable::ValueName(), id) && result; - } - if (valueName != ArpMinVersionVirtualTable::ManifestColumnName()) - { - result = !V1_0::ManifestTable::IsValueReferenced(connection, ArpMinVersionVirtualTable::ManifestColumnName(), id) && result; - } - if (valueName != ArpMaxVersionVirtualTable::ManifestColumnName()) - { - result = !V1_0::ManifestTable::IsValueReferenced(connection, ArpMaxVersionVirtualTable::ManifestColumnName(), id) && result; - } - } - - return result; - } - - bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const - { - bool result = V1_4::Interface::CheckConsistency(connection, log); - - // If the v1.4 index was consistent, or if full logging of inconsistency was requested, check the v1.5 data. - if (result || log) - { - result = V1_0::ManifestTable::CheckConsistency(connection, log) && result; - } - - if (result || log) - { - result = V1_0::ManifestTable::CheckConsistency(connection, log) && result; - } - - if (result || log) - { - result = ValidateArpVersionConsistency(connection, log) && result; - } - - return result; - } - - std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const - { - switch (property) - { - case AppInstaller::Repository::PackageVersionProperty::ArpMinVersion: - return V1_0::ManifestTable::GetValueById(connection, manifestId); - case AppInstaller::Repository::PackageVersionProperty::ArpMaxVersion: - return V1_0::ManifestTable::GetValueById(connection, manifestId); - default: - return V1_4::Interface::GetPropertyByManifestIdInternal(connection, manifestId, property); - } - } - - std::vector Interface::GetArpVersionRanges(const SQLite::Connection& connection, SQLite::rowid_t packageIdentifier) const - { - std::vector ranges; - auto versionKeys = GetVersionKeysById(connection, packageIdentifier); - for (auto const& versionKey : versionKeys) - { - auto arpMinVersion = GetPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionProperty::ArpMinVersion).value_or(""); - auto arpMaxVersion = GetPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionProperty::ArpMaxVersion).value_or(""); - - // Either both empty or both not empty - THROW_HR_IF(E_UNEXPECTED, arpMinVersion.empty() != arpMaxVersion.empty()); - - if (!arpMinVersion.empty() && !arpMaxVersion.empty()) - { - ranges.emplace_back(Utility::VersionRange{ Utility::Version{ std::move(arpMinVersion) }, Utility::Version{ std::move(arpMaxVersion) } }); - } - } - return ranges; - } - - bool Interface::ValidateArpVersionConsistency(const SQLite::Connection& connection, bool log) const - { - try - { - bool result = true; - - // Search everything - SearchRequest request; - auto searchResult = Search(connection, request); - for (auto const& match : searchResult.Matches) - { - // Get arp version ranges for each package to check - std::vector ranges = GetArpVersionRanges(connection, match.first); - - // Check overlap - if (Utility::HasOverlapInVersionRanges(ranges)) - { - AICLI_LOG(Repo, Error, << "Overlapped Arp version ranges found for package. PackageRowId: " << match.first); - result = false; - - if (!log) - { - break; - } - } - } - - return result; - } - catch (...) - { - AICLI_LOG(Repo, Error, << "ValidateArpVersionConsistency() encountered internal error. Returning false."); - return false; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_5/Interface.h" +#include "Microsoft/Schema/1_5/ArpVersionVirtualTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_0/IdTable.h" +#include "Microsoft/Schema/1_0/VersionTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::V1_5 +{ + Interface::Interface(Utility::NormalizationVersion normVersion) : V1_4::Interface(normVersion) + { + } + + SQLite::Version Interface::GetVersion() const + { + return { 1, 5 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_5"); + + V1_4::Interface::CreateTables(connection, options); + + V1_0::ManifestTable::AddColumn(connection, { ArpMinVersionVirtualTable::ManifestColumnName(), SQLite::Builder::Type::RowId }); + V1_0::ManifestTable::AddColumn(connection, { ArpMaxVersionVirtualTable::ManifestColumnName(), SQLite::Builder::Type::RowId }); + + savepoint.Commit(); + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_5"); + + SQLite::rowid_t manifestId = V1_4::Interface::AddManifest(connection, manifest, relativePath); + + auto arpVersionRange = manifest.GetArpVersionRange(); + Manifest::string_t arpMinVersion, arpMaxVersion; + + if (!arpVersionRange.IsEmpty()) + { + // Check to see if adding this version range will create a conflict + SQLite::rowid_t packageIdentifier = V1_0::ManifestTable::GetIdById(connection, manifestId).value(); + std::vector ranges = GetArpVersionRanges(connection, packageIdentifier); + ranges.push_back(arpVersionRange); + + if (Utility::HasOverlapInVersionRanges(ranges)) + { + AICLI_LOG(Repo, Error, << "Overlapped Arp version ranges found for package. All ranges currently in index followed by new range:\n" << [&]() { + std::stringstream stream; + for (const auto& range : ranges) + { + stream << '[' << range.GetMinVersion().ToString() << "] - [" << range.GetMaxVersion().ToString() << "]\n"; + } + return std::move(stream).str(); + }()); + THROW_HR(APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); + } + + arpMinVersion = arpVersionRange.GetMinVersion().ToString(); + arpMaxVersion = arpVersionRange.GetMaxVersion().ToString(); + } + + SQLite::rowid_t arpMinVersionId = V1_0::VersionTable::EnsureExists(connection, arpMinVersion); + SQLite::rowid_t arpMaxVersionId = V1_0::VersionTable::EnsureExists(connection, arpMaxVersion); + V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMinVersionId); + V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMaxVersionId); + + savepoint.Commit(); + + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_5"); + + auto [indexModified, manifestId] = V1_4::Interface::UpdateManifest(connection, manifest, relativePath); + + auto [oldMinVersionId, oldMaxVersionId] = + V1_0::ManifestTable::GetIdsById(connection, manifestId); + + auto arpVersionRange = manifest.GetArpVersionRange(); + Manifest::string_t arpMinVersion = arpVersionRange.IsEmpty() ? "" : arpVersionRange.GetMinVersion().ToString(); + Manifest::string_t arpMaxVersion = arpVersionRange.IsEmpty() ? "" : arpVersionRange.GetMaxVersion().ToString(); + + SQLite::rowid_t arpMinVersionId = V1_0::VersionTable::EnsureExists(connection, arpMinVersion); + SQLite::rowid_t arpMaxVersionId = V1_0::VersionTable::EnsureExists(connection, arpMaxVersion); + + // For cleaning up the old entries after update if applicable + bool cleanOldMinVersionId = false; + bool cleanOldMaxVersionId = false; + + if (arpMinVersionId != oldMinVersionId) + { + V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMinVersionId); + cleanOldMinVersionId = true; + indexModified = true; + } + + if (arpMaxVersionId != oldMaxVersionId) + { + V1_0::ManifestTable::UpdateValueIdById(connection, manifestId, arpMaxVersionId); + cleanOldMaxVersionId = true; + indexModified = true; + } + + if (!arpVersionRange.IsEmpty()) + { + // Check to see if the new set of version ranges created a conflict. + // We could have done this before attempting the update but it would be more complex and SQLite gives us easy rollback. + SQLite::rowid_t packageIdentifier = V1_0::ManifestTable::GetIdById(connection, manifestId).value(); + std::vector ranges = GetArpVersionRanges(connection, packageIdentifier); + + if (Utility::HasOverlapInVersionRanges(ranges)) + { + AICLI_LOG(Repo, Error, << "Overlapped Arp version ranges found for package. Ranges that would be present with attempted upgrade:\n" << [&]() { + std::stringstream stream; + for (const auto& range : ranges) + { + stream << '[' << range.GetMinVersion().ToString() << "] - [" << range.GetMaxVersion().ToString() << "]\n"; + } + return std::move(stream).str(); + }()); + THROW_HR(APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED); + } + } + + if (cleanOldMinVersionId && NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), oldMinVersionId)) + { + V1_0::VersionTable::DeleteById(connection, oldMinVersionId); + } + + if (cleanOldMaxVersionId && oldMaxVersionId != oldMinVersionId && NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), oldMaxVersionId)) + { + V1_0::VersionTable::DeleteById(connection, oldMaxVersionId); + } + + savepoint.Commit(); + + return { indexModified, manifestId }; + } + + void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + // Get the old arp version ids of the values from the manifest table + auto [arpMinVersionId, arpMaxVersionId] = + V1_0::ManifestTable::GetIdsById(connection, manifestId); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_5"); + + // Removes the manifest. + V1_4::Interface::RemoveManifestById(connection, manifestId); + + // Remove the versions that are not needed. + if (NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), arpMinVersionId)) + { + V1_0::VersionTable::DeleteById(connection, arpMinVersionId); + } + + if (arpMaxVersionId != arpMinVersionId && NotNeeded(connection, V1_0::VersionTable::TableName(), V1_0::VersionTable::ValueName(), arpMaxVersionId)) + { + V1_0::VersionTable::DeleteById(connection, arpMaxVersionId); + } + + savepoint.Commit(); + } + + bool Interface::NotNeeded(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t id) const + { + bool result = V1_4::Interface::NotNeeded(connection, tableName, valueName, id); + + if (result && tableName == V1_0::VersionTable::TableName()) + { + if (valueName != V1_0::VersionTable::ValueName()) + { + result = !V1_0::ManifestTable::IsValueReferenced(connection, V1_0::VersionTable::ValueName(), id) && result; + } + if (valueName != ArpMinVersionVirtualTable::ManifestColumnName()) + { + result = !V1_0::ManifestTable::IsValueReferenced(connection, ArpMinVersionVirtualTable::ManifestColumnName(), id) && result; + } + if (valueName != ArpMaxVersionVirtualTable::ManifestColumnName()) + { + result = !V1_0::ManifestTable::IsValueReferenced(connection, ArpMaxVersionVirtualTable::ManifestColumnName(), id) && result; + } + } + + return result; + } + + bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const + { + bool result = V1_4::Interface::CheckConsistency(connection, log); + + // If the v1.4 index was consistent, or if full logging of inconsistency was requested, check the v1.5 data. + if (result || log) + { + result = V1_0::ManifestTable::CheckConsistency(connection, log) && result; + } + + if (result || log) + { + result = V1_0::ManifestTable::CheckConsistency(connection, log) && result; + } + + if (result || log) + { + result = ValidateArpVersionConsistency(connection, log) && result; + } + + return result; + } + + std::optional Interface::GetPropertyByManifestIdInternal(const SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionProperty property) const + { + switch (property) + { + case AppInstaller::Repository::PackageVersionProperty::ArpMinVersion: + return V1_0::ManifestTable::GetValueById(connection, manifestId); + case AppInstaller::Repository::PackageVersionProperty::ArpMaxVersion: + return V1_0::ManifestTable::GetValueById(connection, manifestId); + default: + return V1_4::Interface::GetPropertyByManifestIdInternal(connection, manifestId, property); + } + } + + std::vector Interface::GetArpVersionRanges(const SQLite::Connection& connection, SQLite::rowid_t packageIdentifier) const + { + std::vector ranges; + auto versionKeys = GetVersionKeysById(connection, packageIdentifier); + for (auto const& versionKey : versionKeys) + { + auto arpMinVersion = GetPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionProperty::ArpMinVersion).value_or(""); + auto arpMaxVersion = GetPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionProperty::ArpMaxVersion).value_or(""); + + // Either both empty or both not empty + THROW_HR_IF(E_UNEXPECTED, arpMinVersion.empty() != arpMaxVersion.empty()); + + if (!arpMinVersion.empty() && !arpMaxVersion.empty()) + { + ranges.emplace_back(Utility::VersionRange{ Utility::Version{ std::move(arpMinVersion) }, Utility::Version{ std::move(arpMaxVersion) } }); + } + } + return ranges; + } + + bool Interface::ValidateArpVersionConsistency(const SQLite::Connection& connection, bool log) const + { + try + { + bool result = true; + + // Search everything + SearchRequest request; + auto searchResult = Search(connection, request); + for (auto const& match : searchResult.Matches) + { + // Get arp version ranges for each package to check + std::vector ranges = GetArpVersionRanges(connection, match.first); + + // Check overlap + if (Utility::HasOverlapInVersionRanges(ranges)) + { + AICLI_LOG(Repo, Error, << "Overlapped Arp version ranges found for package. PackageRowId: " << match.first); + result = false; + + if (!log) + { + break; + } + } + } + + return result; + } + catch (...) + { + AICLI_LOG(Repo, Error, << "ValidateArpVersionConsistency() encountered internal error. Returning false."); + return false; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface.h index aa8d87e7cf..c385e1caf6 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface.h @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Microsoft/Schema/1_5/Interface.h" - -namespace AppInstaller::Repository::Microsoft::Schema::V1_6 -{ - // Interface to this schema version exposed through ISQLiteIndex. - struct Interface : public V1_5::Interface - { - Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); - - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection, CreateOptions options) override; - SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; - bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; - std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; - - // Version 1.7 - void DropTables(SQLite::Connection& connection) override; - - protected: - std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const override; - void PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const override; - ISQLiteIndex::SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const; - void PrepareForPackaging(SQLite::Connection& connection, bool vacuum) override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Microsoft/Schema/1_5/Interface.h" + +namespace AppInstaller::Repository::Microsoft::Schema::V1_6 +{ + // Interface to this schema version exposed through ISQLiteIndex. + struct Interface : public V1_5::Interface + { + Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); + + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection, CreateOptions options) override; + SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; + bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; + std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; + + // Version 1.7 + void DropTables(SQLite::Connection& connection) override; + + protected: + std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const override; + void PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const override; + ISQLiteIndex::SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const; + void PrepareForPackaging(SQLite::Connection& connection, bool vacuum) override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface_1_6.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface_1_6.cpp index d5dd4e1a7d..90afb2259b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface_1_6.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/Interface_1_6.cpp @@ -1,165 +1,165 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_6/Interface.h" -#include "Microsoft/Schema/1_6/UpgradeCodeTable.h" -#include "Microsoft/Schema/1_6/SearchResultsTable.h" -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_0/VersionTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::V1_6 -{ - Interface::Interface(Utility::NormalizationVersion normVersion) : V1_5::Interface(normVersion) - { - } - - SQLite::Version Interface::GetVersion() const - { - return { 1, 6 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_6"); - - V1_5::Interface::CreateTables(connection, options); - - UpgradeCodeTable::Create(connection, GetOneToManyTableSchema()); - - savepoint.Commit(); - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_6"); - - SQLite::rowid_t manifestId = V1_5::Interface::AddManifest(connection, manifest, relativePath); - - // Add the new 1.6 data - // These system reference strings are all stored with their cases folded so that they can be - // looked up ordinally; enabling the index to provide efficient searches. - UpgradeCodeTable::EnsureExistsAndInsert(connection, manifest.GetUpgradeCodes(), manifestId); - - savepoint.Commit(); - - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_6"); - - auto [indexModified, manifestId] = V1_5::Interface::UpdateManifest(connection, manifest, relativePath); - - // Update new 1:N tables as necessary - indexModified = UpgradeCodeTable::UpdateIfNeededByManifestId(connection, manifest.GetUpgradeCodes(), manifestId) || indexModified; - - savepoint.Commit(); - - return { indexModified, manifestId }; - } - - void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_6"); - - // Removes the manifest. - V1_5::Interface::RemoveManifestById(connection, manifestId); - - // Remove all of the new 1:N data that is no longer referenced. - UpgradeCodeTable::DeleteIfNotNeededByManifestId(connection, manifestId); - - savepoint.Commit(); - } - - bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const - { - bool result = V1_5::Interface::CheckConsistency(connection, log); - - // If the v1.5 index was consistent, or if full logging of inconsistency was requested, check the v1.6 data. - if (result || log) - { - result = UpgradeCodeTable::CheckConsistency(connection, log) && result; - } - - return result; - } - - std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const - { - switch (property) - { - case PackageVersionMultiProperty::UpgradeCode: - return UpgradeCodeTable::GetValuesByManifestId(connection, primaryId); - default: - return V1_5::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); - } - } - - void Interface::DropTables(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_6"); - - V1_4::Interface::DropTables(connection); - - UpgradeCodeTable::Drop(connection); - - savepoint.Commit(); - } - - std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const - { - return std::make_unique(connection); - } - - void Interface::PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const - { - // First, do an exact match search for the folded system reference strings - // We do this first because it is exact, and likely won't match anything else if it matches this. - PackageMatchFilter filter(PackageMatchField::UpgradeCode, MatchType::Exact, Utility::FoldCase(query.Value)); - resultsTable.SearchOnField(filter); - - // Then do the 1.5 search - V1_5::Interface::PerformQuerySearch(resultsTable, query); - } - - ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const - { - // Update any system reference strings to be folded - auto foldIfNeeded = [](PackageMatchFilter& filter) - { - if (filter.Field == PackageMatchField::UpgradeCode && filter.Type == MatchType::Exact) - { - filter.Value = Utility::FoldCase(filter.Value); - } - }; - - for (auto& inclusion : request.Inclusions) - { - foldIfNeeded(inclusion); - } - - for (auto& filter : request.Filters) - { - foldIfNeeded(filter); - } - - return V1_5::Interface::SearchInternal(connection, request); - } - - void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_6"); - - V1_5::Interface::PrepareForPackaging(connection, false); - - UpgradeCodeTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); - - savepoint.Commit(); - - if (vacuum) - { - Vacuum(connection); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_6/Interface.h" +#include "Microsoft/Schema/1_6/UpgradeCodeTable.h" +#include "Microsoft/Schema/1_6/SearchResultsTable.h" +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_0/VersionTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::V1_6 +{ + Interface::Interface(Utility::NormalizationVersion normVersion) : V1_5::Interface(normVersion) + { + } + + SQLite::Version Interface::GetVersion() const + { + return { 1, 6 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v1_6"); + + V1_5::Interface::CreateTables(connection, options); + + UpgradeCodeTable::Create(connection, GetOneToManyTableSchema()); + + savepoint.Commit(); + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addmanifest_v1_6"); + + SQLite::rowid_t manifestId = V1_5::Interface::AddManifest(connection, manifest, relativePath); + + // Add the new 1.6 data + // These system reference strings are all stored with their cases folded so that they can be + // looked up ordinally; enabling the index to provide efficient searches. + UpgradeCodeTable::EnsureExistsAndInsert(connection, manifest.GetUpgradeCodes(), manifestId); + + savepoint.Commit(); + + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatemanifest_v1_6"); + + auto [indexModified, manifestId] = V1_5::Interface::UpdateManifest(connection, manifest, relativePath); + + // Update new 1:N tables as necessary + indexModified = UpgradeCodeTable::UpdateIfNeededByManifestId(connection, manifest.GetUpgradeCodes(), manifestId) || indexModified; + + savepoint.Commit(); + + return { indexModified, manifestId }; + } + + void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "RemoveManifestById_v1_6"); + + // Removes the manifest. + V1_5::Interface::RemoveManifestById(connection, manifestId); + + // Remove all of the new 1:N data that is no longer referenced. + UpgradeCodeTable::DeleteIfNotNeededByManifestId(connection, manifestId); + + savepoint.Commit(); + } + + bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const + { + bool result = V1_5::Interface::CheckConsistency(connection, log); + + // If the v1.5 index was consistent, or if full logging of inconsistency was requested, check the v1.6 data. + if (result || log) + { + result = UpgradeCodeTable::CheckConsistency(connection, log) && result; + } + + return result; + } + + std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const + { + switch (property) + { + case PackageVersionMultiProperty::UpgradeCode: + return UpgradeCodeTable::GetValuesByManifestId(connection, primaryId); + default: + return V1_5::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); + } + } + + void Interface::DropTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v1_6"); + + V1_4::Interface::DropTables(connection); + + UpgradeCodeTable::Drop(connection); + + savepoint.Commit(); + } + + std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const + { + return std::make_unique(connection); + } + + void Interface::PerformQuerySearch(V1_0::SearchResultsTable& resultsTable, const RequestMatch& query) const + { + // First, do an exact match search for the folded system reference strings + // We do this first because it is exact, and likely won't match anything else if it matches this. + PackageMatchFilter filter(PackageMatchField::UpgradeCode, MatchType::Exact, Utility::FoldCase(query.Value)); + resultsTable.SearchOnField(filter); + + // Then do the 1.5 search + V1_5::Interface::PerformQuerySearch(resultsTable, query); + } + + ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const + { + // Update any system reference strings to be folded + auto foldIfNeeded = [](PackageMatchFilter& filter) + { + if (filter.Field == PackageMatchField::UpgradeCode && filter.Type == MatchType::Exact) + { + filter.Value = Utility::FoldCase(filter.Value); + } + }; + + for (auto& inclusion : request.Inclusions) + { + foldIfNeeded(inclusion); + } + + for (auto& filter : request.Filters) + { + foldIfNeeded(filter); + } + + return V1_5::Interface::SearchInternal(connection, request); + } + + void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_6"); + + V1_5::Interface::PrepareForPackaging(connection, false); + + UpgradeCodeTable::PrepareForPackaging(connection, GetOneToManyTableSchema(), true, true); + + savepoint.Commit(); + + if (vacuum) + { + Vacuum(connection); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable.h index 7521f35e99..e7f17e1dc1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable.h @@ -1,28 +1,28 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_2/SearchResultsTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_6 -{ - // Table for holding temporary search results. - struct SearchResultsTable : public V1_2::SearchResultsTable - { - SearchResultsTable(const SQLite::Connection& connection) : V1_2::SearchResultsTable(connection) {} - - SearchResultsTable(const SearchResultsTable&) = delete; - SearchResultsTable& operator=(const SearchResultsTable&) = delete; - - SearchResultsTable(SearchResultsTable&&) = default; - SearchResultsTable& operator=(SearchResultsTable&&) = default; - - protected: - std::vector BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_2/SearchResultsTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_6 +{ + // Table for holding temporary search results. + struct SearchResultsTable : public V1_2::SearchResultsTable + { + SearchResultsTable(const SQLite::Connection& connection) : V1_2::SearchResultsTable(connection) {} + + SearchResultsTable(const SearchResultsTable&) = delete; + SearchResultsTable& operator=(const SearchResultsTable&) = delete; + + SearchResultsTable(SearchResultsTable&&) = default; + SearchResultsTable& operator=(SearchResultsTable&&) = default; + + protected: + std::vector BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable_1_6.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable_1_6.cpp index 0673244701..c7deeb18d9 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable_1_6.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/SearchResultsTable_1_6.cpp @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResultsTable.h" - -#include "Microsoft/Schema/1_0/ManifestTable.h" -#include "Microsoft/Schema/1_6/UpgradeCodeTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_6 -{ - std::vector SearchResultsTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const - { - switch (field) - { - case PackageMatchField::UpgradeCode: - return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); - default: - return V1_2::SearchResultsTable::BuildSearchStatement(builder, field, manifestAlias, valueAlias, useLike); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResultsTable.h" + +#include "Microsoft/Schema/1_0/ManifestTable.h" +#include "Microsoft/Schema/1_6/UpgradeCodeTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_6 +{ + std::vector SearchResultsTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const + { + switch (field) + { + case PackageMatchField::UpgradeCode: + return V1_0::ManifestTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike); + default: + return V1_2::SearchResultsTable::BuildSearchStatement(builder, field, manifestAlias, valueAlias, useLike); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/UpgradeCodeTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/UpgradeCodeTable.h index defff644d0..819f8ba11a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/UpgradeCodeTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_6/UpgradeCodeTable.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/1_0/OneToManyTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V1_6 -{ - namespace details - { - using namespace std::string_view_literals; - - struct UpgradeCodeTableInfo - { - inline static constexpr std::string_view TableName() { return "upgradecodes"sv; } - inline static constexpr std::string_view ValueName() { return "upgradecode"sv; } - }; - } - - // The table for UpgradeCode. - using UpgradeCodeTable = V1_0::OneToManyTable; -} - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/1_0/OneToManyTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V1_6 +{ + namespace details + { + using namespace std::string_view_literals; + + struct UpgradeCodeTableInfo + { + inline static constexpr std::string_view TableName() { return "upgradecodes"sv; } + inline static constexpr std::string_view ValueName() { return "upgradecode"sv; } + }; + } + + // The table for UpgradeCode. + using UpgradeCodeTable = V1_0::OneToManyTable; +} + diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface.h index 2c04853fab..e944f19bff 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface.h @@ -1,30 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Microsoft/Schema/1_6/Interface.h" - -namespace AppInstaller::Repository::Microsoft::Schema::V1_7 -{ - using namespace std::string_view_literals; - - // Version 1.7 - static constexpr std::string_view s_MetadataValueName_MapDataFolded = "mapDataFolded"sv; - static constexpr char s_MetadataValue_MapDataFolded_Separator = ';'; - - // Interface to this schema version exposed through ISQLiteIndex. - struct Interface : public V1_6::Interface - { - static constexpr std::string_view MapDataFolded_VersionSpecifier = "1.7"sv; - - Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); - - // Version 1.0 - SQLite::Version GetVersion() const override; - std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; - - protected: - void PrepareForPackaging(SQLite::Connection& connection, bool vacuum) override; - V1_0::OneToManyTableSchema GetOneToManyTableSchema() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Microsoft/Schema/1_6/Interface.h" + +namespace AppInstaller::Repository::Microsoft::Schema::V1_7 +{ + using namespace std::string_view_literals; + + // Version 1.7 + static constexpr std::string_view s_MetadataValueName_MapDataFolded = "mapDataFolded"sv; + static constexpr char s_MetadataValue_MapDataFolded_Separator = ';'; + + // Interface to this schema version exposed through ISQLiteIndex. + struct Interface : public V1_6::Interface + { + static constexpr std::string_view MapDataFolded_VersionSpecifier = "1.7"sv; + + Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); + + // Version 1.0 + SQLite::Version GetVersion() const override; + std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; + + protected: + void PrepareForPackaging(SQLite::Connection& connection, bool vacuum) override; + V1_0::OneToManyTableSchema GetOneToManyTableSchema() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface_1_7.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface_1_7.cpp index 8041f5dd1d..f6e4c7c766 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface_1_7.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/1_7/Interface_1_7.cpp @@ -1,114 +1,114 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/1_7/Interface.h" -#include -#include "Microsoft/Schema/1_0/CommandsTable.h" -#include "Microsoft/Schema/1_0/IdTable.h" -#include "Microsoft/Schema/1_0/TagsTable.h" -#include "Microsoft/Schema/1_1/PackageFamilyNameTable.h" -#include "Microsoft/Schema/1_2/NormalizedPackageNameTable.h" -#include "Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h" -#include "Microsoft/Schema/1_6/UpgradeCodeTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::V1_7 -{ - namespace - { - bool ShouldFoldPropertyLookup(const SQLite::Connection& connection) - { - // Get the metadata indicator that we folded these multi properties. - // If it contains the value for folding these properties in the 1.7 manner, also fold the incoming manifest - // to the same value that it would have been folded to so that all manifest entries will have all of these properties. - std::optional mapDataFolded = SQLite::MetadataTable::TryGetNamedValue(connection, s_MetadataValueName_MapDataFolded); - - if (mapDataFolded) - { - std::vector foldedSplit = Utility::Split(mapDataFolded.value(), s_MetadataValue_MapDataFolded_Separator); - - for (const std::string& splitValue : foldedSplit) - { - if (splitValue == Interface::MapDataFolded_VersionSpecifier) - { - return true; - } - } - } - - return false; - } - } - - Interface::Interface(Utility::NormalizationVersion normVersion) : V1_6::Interface(normVersion) - { - } - - SQLite::Version Interface::GetVersion() const - { - return { 1, 7 }; - } - - std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const - { - if (property == PackageVersionMultiProperty::PackageFamilyName || - property == PackageVersionMultiProperty::Name || - property == PackageVersionMultiProperty::Publisher || - property == PackageVersionMultiProperty::UpgradeCode) - { - if (ShouldFoldPropertyLookup(connection)) - { - std::optional maximumManifestId = V1_0::OneToManyTableGetMapDataFoldingManifestTargetId(connection, primaryId); - if (maximumManifestId) - { - primaryId = maximumManifestId.value(); - } - } - } - - return V1_6::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); - } - - void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_7"); - - // Remove data that is not particularly interesting per-manifest - std::array dataRemovalStatements{ - V1_0::CommandsTable::PrepareMapDataFoldingStatement(connection), - V1_0::TagsTable::PrepareMapDataFoldingStatement(connection), - V1_1::PackageFamilyNameTable::PrepareMapDataFoldingStatement(connection), - V1_2::NormalizedPackageNameTable::PrepareMapDataFoldingStatement(connection), - V1_2::NormalizedPackagePublisherTable::PrepareMapDataFoldingStatement(connection), - V1_6::UpgradeCodeTable::PrepareMapDataFoldingStatement(connection), - }; - - std::vector allIdentifiers = V1_0::IdTable::GetAllRowIds(connection); - - for (SQLite::rowid_t id : allIdentifiers) - { - for (SQLite::Statement& statement : dataRemovalStatements) - { - statement.Reset(); - statement.Bind(1, id); - - statement.Execute(); - } - } - - SQLite::MetadataTable::SetNamedValue(connection, s_MetadataValueName_MapDataFolded, MapDataFolded_VersionSpecifier); - - V1_6::Interface::PrepareForPackaging(connection, false); - - savepoint.Commit(); - - if (vacuum) - { - Vacuum(connection); - } - } - - V1_0::OneToManyTableSchema Interface::GetOneToManyTableSchema() const - { - return V1_0::OneToManyTableSchema::Version_1_7; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/1_7/Interface.h" +#include +#include "Microsoft/Schema/1_0/CommandsTable.h" +#include "Microsoft/Schema/1_0/IdTable.h" +#include "Microsoft/Schema/1_0/TagsTable.h" +#include "Microsoft/Schema/1_1/PackageFamilyNameTable.h" +#include "Microsoft/Schema/1_2/NormalizedPackageNameTable.h" +#include "Microsoft/Schema/1_2/NormalizedPackagePublisherTable.h" +#include "Microsoft/Schema/1_6/UpgradeCodeTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::V1_7 +{ + namespace + { + bool ShouldFoldPropertyLookup(const SQLite::Connection& connection) + { + // Get the metadata indicator that we folded these multi properties. + // If it contains the value for folding these properties in the 1.7 manner, also fold the incoming manifest + // to the same value that it would have been folded to so that all manifest entries will have all of these properties. + std::optional mapDataFolded = SQLite::MetadataTable::TryGetNamedValue(connection, s_MetadataValueName_MapDataFolded); + + if (mapDataFolded) + { + std::vector foldedSplit = Utility::Split(mapDataFolded.value(), s_MetadataValue_MapDataFolded_Separator); + + for (const std::string& splitValue : foldedSplit) + { + if (splitValue == Interface::MapDataFolded_VersionSpecifier) + { + return true; + } + } + } + + return false; + } + } + + Interface::Interface(Utility::NormalizationVersion normVersion) : V1_6::Interface(normVersion) + { + } + + SQLite::Version Interface::GetVersion() const + { + return { 1, 7 }; + } + + std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const + { + if (property == PackageVersionMultiProperty::PackageFamilyName || + property == PackageVersionMultiProperty::Name || + property == PackageVersionMultiProperty::Publisher || + property == PackageVersionMultiProperty::UpgradeCode) + { + if (ShouldFoldPropertyLookup(connection)) + { + std::optional maximumManifestId = V1_0::OneToManyTableGetMapDataFoldingManifestTargetId(connection, primaryId); + if (maximumManifestId) + { + primaryId = maximumManifestId.value(); + } + } + } + + return V1_6::Interface::GetMultiPropertyByPrimaryId(connection, primaryId, property); + } + + void Interface::PrepareForPackaging(SQLite::Connection& connection, bool vacuum) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v1_7"); + + // Remove data that is not particularly interesting per-manifest + std::array dataRemovalStatements{ + V1_0::CommandsTable::PrepareMapDataFoldingStatement(connection), + V1_0::TagsTable::PrepareMapDataFoldingStatement(connection), + V1_1::PackageFamilyNameTable::PrepareMapDataFoldingStatement(connection), + V1_2::NormalizedPackageNameTable::PrepareMapDataFoldingStatement(connection), + V1_2::NormalizedPackagePublisherTable::PrepareMapDataFoldingStatement(connection), + V1_6::UpgradeCodeTable::PrepareMapDataFoldingStatement(connection), + }; + + std::vector allIdentifiers = V1_0::IdTable::GetAllRowIds(connection); + + for (SQLite::rowid_t id : allIdentifiers) + { + for (SQLite::Statement& statement : dataRemovalStatements) + { + statement.Reset(); + statement.Bind(1, id); + + statement.Execute(); + } + } + + SQLite::MetadataTable::SetNamedValue(connection, s_MetadataValueName_MapDataFolded, MapDataFolded_VersionSpecifier); + + V1_6::Interface::PrepareForPackaging(connection, false); + + savepoint.Commit(); + + if (vacuum) + { + Vacuum(connection); + } + } + + V1_0::OneToManyTableSchema Interface::GetOneToManyTableSchema() const + { + return V1_0::OneToManyTableSchema::Version_1_7; + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/CommandsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/CommandsTable.h index 1f2b0fb06b..5f28a7b1f4 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/CommandsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/CommandsTable.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct CommandsTableInfo - { - inline static constexpr std::string_view TableName() { return "commands2"sv; } - inline static constexpr std::string_view ValueName() { return "command"sv; } - }; - } - - using CommandsTable = OneToManyTableWithMap; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct CommandsTableInfo + { + inline static constexpr std::string_view TableName() { return "commands2"sv; } + inline static constexpr std::string_view ValueName() { return "command"sv; } + }; + } + + using CommandsTable = OneToManyTableWithMap; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface.h index 2b86d1e32e..361fc28e8d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface.h @@ -1,98 +1,98 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Microsoft/Schema/2_0/SearchResultsTable.h" -#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" - -#include -#include - -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - // Version 2.0 - static constexpr std::string_view s_MetadataValueName_PackageUpdateTrackingBaseTime = "updateTrackingBase"sv; - - // Interface to this schema version exposed through ISQLiteIndex. - struct Interface : public ISQLiteIndex - { - Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); - - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection, CreateOptions options) override; - SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; - SQLite::rowid_t RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) override; - void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; - void PrepareForPackaging(SQLite::Connection& connection) override; - void PrepareForPackaging(const SQLiteIndexContext& context) override; - bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; - SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const override; - std::optional GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const override; - std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; - std::optional GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const override; - std::optional GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const override; - std::vector GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const override; - - // Version 1.1 - MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; - void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; - - // Version 1.2 - Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const override; - - // Version 1.4 Get all the dependencies for a specific manifest. - std::set> GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const override; - std::vector> GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const override; - - // Version 1.7 - void DropTables(SQLite::Connection& connection) override; - - // Version 2.0 - bool MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) override; - void SetProperty(SQLite::Connection& connection, Property property, const std::string& value) override; - - protected: - // Creates the search results table. - virtual std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const; - - // Executes all relevant searches for the query. - virtual void PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const; - - // Gets the one to many table schema to use. - virtual OneToManyTableSchema GetOneToManyTableSchema() const; - - // Executes search on a request that can be modified. - virtual SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const; - - // Executes search on the given request. - SearchResult BasicSearchInternal(const SQLite::Connection& connection, const SearchRequest& request) const; - - // Prepares for packaging, optionally vacuuming the database. - virtual void PrepareForPackaging(const SQLiteIndexContext& context, bool vacuum); - - // Force the database to shrink the file size. - // This *must* be done outside of an active transaction. - void Vacuum(const SQLite::Connection& connection); - - // If before PrepareForPackaging is called, this should find the internal interface schema version and create the object. - // If after PrepareForPackaging is called, this should not find the internal interface schema version and allow the code to fall through. - // requireInternalInterface should be set to true for modifying functions. - void EnsureInternalInterface(const SQLite::Connection& connection, bool requireInternalInterface = false) const; - - // Allows derived types to move to a different internal schema version. - virtual std::unique_ptr CreateInternalInterface() const; - - // If EnsureInternalInterface has been called. - mutable bool m_internalInterfaceChecked = false; - - // Interface to the data before PrepareForPackaging is called. - mutable std::unique_ptr m_internalInterface; - - // The name normalization utility - Utility::NameNormalizer m_normalizer; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Microsoft/Schema/2_0/SearchResultsTable.h" +#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" + +#include +#include + +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + // Version 2.0 + static constexpr std::string_view s_MetadataValueName_PackageUpdateTrackingBaseTime = "updateTrackingBase"sv; + + // Interface to this schema version exposed through ISQLiteIndex. + struct Interface : public ISQLiteIndex + { + Interface(Utility::NormalizationVersion normVersion = Utility::NormalizationVersion::Initial); + + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection, CreateOptions options) override; + SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + std::pair UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) override; + SQLite::rowid_t RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) override; + void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) override; + void PrepareForPackaging(SQLite::Connection& connection) override; + void PrepareForPackaging(const SQLiteIndexContext& context) override; + bool CheckConsistency(const SQLite::Connection& connection, bool log) const override; + SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const override; + std::optional GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const override; + std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const override; + std::optional GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const override; + std::optional GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const override; + std::vector GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const override; + + // Version 1.1 + MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const override; + void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) override; + + // Version 1.2 + Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const override; + + // Version 1.4 Get all the dependencies for a specific manifest. + std::set> GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const override; + std::vector> GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const override; + + // Version 1.7 + void DropTables(SQLite::Connection& connection) override; + + // Version 2.0 + bool MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) override; + void SetProperty(SQLite::Connection& connection, Property property, const std::string& value) override; + + protected: + // Creates the search results table. + virtual std::unique_ptr CreateSearchResultsTable(const SQLite::Connection& connection) const; + + // Executes all relevant searches for the query. + virtual void PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const; + + // Gets the one to many table schema to use. + virtual OneToManyTableSchema GetOneToManyTableSchema() const; + + // Executes search on a request that can be modified. + virtual SearchResult SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const; + + // Executes search on the given request. + SearchResult BasicSearchInternal(const SQLite::Connection& connection, const SearchRequest& request) const; + + // Prepares for packaging, optionally vacuuming the database. + virtual void PrepareForPackaging(const SQLiteIndexContext& context, bool vacuum); + + // Force the database to shrink the file size. + // This *must* be done outside of an active transaction. + void Vacuum(const SQLite::Connection& connection); + + // If before PrepareForPackaging is called, this should find the internal interface schema version and create the object. + // If after PrepareForPackaging is called, this should not find the internal interface schema version and allow the code to fall through. + // requireInternalInterface should be set to true for modifying functions. + void EnsureInternalInterface(const SQLite::Connection& connection, bool requireInternalInterface = false) const; + + // Allows derived types to move to a different internal schema version. + virtual std::unique_ptr CreateInternalInterface() const; + + // If EnsureInternalInterface has been called. + mutable bool m_internalInterfaceChecked = false; + + // Interface to the data before PrepareForPackaging is called. + mutable std::unique_ptr m_internalInterface; + + // The name normalization utility + Utility::NameNormalizer m_normalizer; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface_2_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface_2_0.cpp index 79c1f03bea..99c25fe319 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface_2_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/Interface_2_0.cpp @@ -1,788 +1,788 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include "Microsoft/Schema/2_0/Interface.h" - -#include "Microsoft/Schema/2_0/PackagesTable.h" - -#include "Microsoft/Schema/2_0/TagsTable.h" -#include "Microsoft/Schema/2_0/CommandsTable.h" -#include "Microsoft/Schema/2_0/PackageFamilyNameTable.h" -#include "Microsoft/Schema/2_0/ProductCodeTable.h" -#include "Microsoft/Schema/2_0/NormalizedPackageNameTable.h" -#include "Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h" -#include "Microsoft/Schema/2_0/UpgradeCodeTable.h" - -#include "Microsoft/Schema/2_0/SearchResultsTable.h" -#include "Microsoft/Schema/2_0/PackageUpdateTrackingTable.h" - -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace anon - { - // Folds the values of the fields that are stored folded. - void FoldPackageMatchFilters(std::vector& filters) - { - for (auto& filter : filters) - { - if ((filter.Field == PackageMatchField::PackageFamilyName || filter.Field == PackageMatchField::ProductCode || filter.Field == PackageMatchField::UpgradeCode) && - filter.Type == MatchType::Exact) - { - filter.Value = Utility::FoldCase(filter.Value); - } - } - } - - // Update NormalizedNameAndPublisher with normalization and folding - // Returns true if the normalized name contains normalization field of fieldsToInclude - bool UpdateNormalizedNameAndPublisher( - PackageMatchFilter& filter, - const Utility::NameNormalizer& normalizer, - Utility::NormalizationField fieldsToInclude) - { - Utility::NormalizedName normalized = normalizer.Normalize(Utility::FoldCase(filter.Value), Utility::FoldCase(filter.Additional.value())); - filter.Value = normalized.GetNormalizedName(fieldsToInclude); - filter.Additional = normalized.Publisher(); - return WI_AreAllFlagsSet(normalized.GetNormalizedFields(), fieldsToInclude); - } - - bool UpdatePackageMatchFilters( - std::vector& filters, - const Utility::NameNormalizer& normalizer, - Utility::NormalizationField normalizedNameFieldsToFilter = Utility::NormalizationField::None) - { - bool normalizedNameFieldsFound = false; - for (auto itr = filters.begin(); itr != filters.end();) - { - if (itr->Field == PackageMatchField::NormalizedNameAndPublisher && itr->Type == MatchType::Exact) - { - if (!UpdateNormalizedNameAndPublisher(*itr, normalizer, normalizedNameFieldsToFilter)) - { - // If not matched, this package match filter will be removed. - // For example, if caller is trying to search with arch info only, values without arch will be removed from search. - itr = filters.erase(itr); - continue; - } - - normalizedNameFieldsFound = true; - } - - ++itr; - } - - return normalizedNameFieldsFound; - } - } - - Interface::Interface(Utility::NormalizationVersion normVersion) : m_normalizer(normVersion) - { - } - - SQLite::Version Interface::GetVersion() const - { - return { 2, 0 }; - } - - void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) - { - m_internalInterface = CreateInternalInterface(); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v2_0"); - - // We only create the internal tables at this point, the actual 2.0 tables are created in PrepareForPackaging - m_internalInterface->CreateTables(connection, options); - - savepoint.Commit(); - - m_internalInterfaceChecked = true; - } - - SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - EnsureInternalInterface(connection, true); - SQLite::rowid_t manifestId = m_internalInterface->AddManifest(connection, manifest, relativePath); - PackageUpdateTrackingTable::Update(connection, m_internalInterface.get(), m_internalInterface->GetPropertyByPrimaryId(connection, manifestId, PackageVersionProperty::Id).value()); - return manifestId; - } - - std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) - { - EnsureInternalInterface(connection, true); - std::pair result = m_internalInterface->UpdateManifest(connection, manifest, relativePath); - if (result.first) - { - PackageUpdateTrackingTable::Update(connection, m_internalInterface.get(), m_internalInterface->GetPropertyByPrimaryId(connection, result.second, PackageVersionProperty::Id).value()); - } - return result; - } - - SQLite::rowid_t Interface::RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) - { - EnsureInternalInterface(connection, true); - std::optional result = m_internalInterface->GetManifestIdByManifest(connection, manifest); - - // If the manifest doesn't actually exist, fail the remove. - THROW_HR_IF(E_NOT_SET, !result); - - SQLite::rowid_t manifestId = result.value(); - RemoveManifestById(connection, manifestId); - - return manifestId; - } - - void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) - { - EnsureInternalInterface(connection, true); - std::optional identifier = m_internalInterface->GetPropertyByPrimaryId(connection, manifestId, PackageVersionProperty::Id); - m_internalInterface->RemoveManifestById(connection, manifestId); - if (identifier) - { - PackageUpdateTrackingTable::Update(connection, m_internalInterface.get(), identifier.value()); - } - } - - void Interface::PrepareForPackaging(SQLite::Connection&) - { - // We implement the context version - THROW_HR(E_NOTIMPL); - } - - void Interface::PrepareForPackaging(const SQLiteIndexContext& context) - { - EnsureInternalInterface(context.Connection, true); - PrepareForPackaging(context, true); - } - - bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const - { - EnsureInternalInterface(connection); - - bool result = true; - -#define AICLI_CHECK_CONSISTENCY(_check_) \ - if (result || log) \ - { \ - result = _check_ && result; \ - } - - if (m_internalInterface) - { - AICLI_CHECK_CONSISTENCY(m_internalInterface->CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(PackageUpdateTrackingTable::CheckConsistency(connection, m_internalInterface.get(), log)); - - return result; - } - - AICLI_CHECK_CONSISTENCY((PackagesTable::CheckConsistency< - PackagesTable::IdColumn, - PackagesTable::NameColumn, - PackagesTable::MonikerColumn, - PackagesTable::LatestVersionColumn, - PackagesTable::ARPMinVersionColumn, - PackagesTable::ARPMaxVersionColumn>(connection, log))); - - // Check the 1:N map tables for consistency - AICLI_CHECK_CONSISTENCY(TagsTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(CommandsTable::CheckConsistency(connection, log)); - - AICLI_CHECK_CONSISTENCY(PackageFamilyNameTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(ProductCodeTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(NormalizedPackageNameTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(NormalizedPackagePublisherTable::CheckConsistency(connection, log)); - AICLI_CHECK_CONSISTENCY(UpgradeCodeTable::CheckConsistency(connection, log)); - -#undef AICLI_CHECK_CONSISTENCY - - return result; - } - - ISQLiteIndex::SearchResult Interface::Search(const SQLite::Connection& connection, const SearchRequest& request) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->Search(connection, request); - } - - SearchRequest requestCopy = request; - return SearchInternal(connection, requestCopy); - } - - std::optional Interface::GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->GetPropertyByPrimaryId(connection, primaryId, property); - } - - switch (property) - { - case PackageVersionProperty::Id: - return PackagesTable::GetValueById(connection, primaryId); - case PackageVersionProperty::Name: - return PackagesTable::GetValueById(connection, primaryId); - case PackageVersionProperty::Version: - return PackagesTable::GetValueById(connection, primaryId); - case PackageVersionProperty::Channel: - return ""; - case PackageVersionProperty::ManifestSHA256Hash: - { - std::optional hash = PackagesTable::GetValueById(connection, primaryId); - return (!hash || hash->empty()) ? std::optional{} : Utility::SHA256::ConvertToString(hash.value()); - } - case PackageVersionProperty::ArpMinVersion: - return PackagesTable::GetValueById(connection, primaryId); - case PackageVersionProperty::ArpMaxVersion: - return PackagesTable::GetValueById(connection, primaryId); - case PackageVersionProperty::Moniker: - return PackagesTable::GetValueById(connection, primaryId); - default: - return {}; - } - } - - std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->GetMultiPropertyByPrimaryId(connection, primaryId, property); - } - - switch (property) - { - case PackageVersionMultiProperty::PackageFamilyName: - return PackageFamilyNameTable::GetValuesByPrimaryId(connection, primaryId); - case PackageVersionMultiProperty::ProductCode: - return ProductCodeTable::GetValuesByPrimaryId(connection, primaryId); - // These values are not right, as they are normalized. But they are good enough for now and all we have. - case PackageVersionMultiProperty::Name: - return NormalizedPackageNameTable::GetValuesByPrimaryId(connection, primaryId); - case PackageVersionMultiProperty::Publisher: - return NormalizedPackagePublisherTable::GetValuesByPrimaryId(connection, primaryId); - case PackageVersionMultiProperty::UpgradeCode: - return UpgradeCodeTable::GetValuesByPrimaryId(connection, primaryId); - case PackageVersionMultiProperty::Tag: - return TagsTable::GetValuesByPrimaryId(connection, primaryId); - case PackageVersionMultiProperty::Command: - return CommandsTable::GetValuesByPrimaryId(connection, primaryId); - default: - return {}; - } - } - - std::optional Interface::GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->GetManifestIdByKey(connection, id, version, channel); - } - - THROW_HR(E_NOT_VALID_STATE); - } - - std::optional Interface::GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->GetManifestIdByManifest(connection, manifest); - } - - THROW_HR(E_NOT_VALID_STATE); - } - - std::vector Interface::GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->GetVersionKeysById(connection, id); - } - - THROW_HR(E_NOT_VALID_STATE); - } - - ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection&, SQLite::rowid_t) const - { - return {}; - } - - void Interface::SetMetadataByManifestId(SQLite::Connection&, SQLite::rowid_t, PackageVersionMetadata, std::string_view) - { - } - - Utility::NormalizedName Interface::NormalizeName(std::string_view name, std::string_view publisher) const - { - if (m_internalInterface) - { - return m_internalInterface->NormalizeName(name, publisher); - } - - return m_normalizer.Normalize(name, publisher); - } - - std::set> Interface::GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t rowid) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->GetDependenciesByManifestRowId(connection, rowid); - } - - THROW_HR(E_NOT_VALID_STATE); - } - - std::vector> Interface::GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t id) const - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->GetDependentsById(connection, id); - } - - THROW_HR(E_NOT_VALID_STATE); - } - - void Interface::DropTables(SQLite::Connection& connection) - { - EnsureInternalInterface(connection); - - if (m_internalInterface) - { - return m_internalInterface->DropTables(connection); - } - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v2_0"); - - PackagesTable::Drop(connection); - - TagsTable::Drop(connection); - CommandsTable::Drop(connection); - - PackageFamilyNameTable::Drop(connection); - ProductCodeTable::Drop(connection); - NormalizedPackageNameTable::Drop(connection); - NormalizedPackagePublisherTable::Drop(connection); - UpgradeCodeTable::Drop(connection); - - savepoint.Commit(); - } - - bool Interface::MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) - { - THROW_HR_IF_NULL(E_POINTER, current); - - auto currentVersion = current->GetVersion(); - if (currentVersion.MajorVersion != 1 || currentVersion.MinorVersion != 7) - { - return false; - } - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "migrate_from_v2_0"); - - // We only need to insert all of the existing packages into the update tracking table. - PackageUpdateTrackingTable::EnsureExists(connection); - SearchResult allPackages = current->Search(connection, {}); - - for (const auto& packageMatch : allPackages.Matches) - { - std::vector versionKeys = current->GetVersionKeysById(connection, packageMatch.first); - ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; - PackageUpdateTrackingTable::Update(connection, current, current->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(), false); - } - - savepoint.Commit(); - return true; - } - - void Interface::SetProperty(SQLite::Connection& connection, Property property, const std::string& value) - { - switch (property) - { - case Property::PackageUpdateTrackingBaseTime: - { - int64_t baseTime = 0; - if (value.empty()) - { - baseTime = Utility::GetCurrentUnixEpoch(); - } - else - { - baseTime = std::stoll(value); - } - SQLite::MetadataTable::SetNamedValue(connection, s_MetadataValueName_PackageUpdateTrackingBaseTime, std::to_string(baseTime)); - } - break; - - default: - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - } - - std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const - { - return std::make_unique(connection); - } - - void Interface::PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const - { - // First, do an exact match search for the folded system reference strings - // We do this first because it is exact, and likely won't match anything else if it matches this. - PackageMatchFilter filter(PackageMatchField::Unknown, MatchType::Exact, Utility::FoldCase(query.Value)); - - for (PackageMatchField field : { PackageMatchField::PackageFamilyName, PackageMatchField::ProductCode, PackageMatchField::UpgradeCode }) - { - filter.Field = field; - resultsTable.SearchOnField(filter); - } - - // Now search on the unfolded value - filter.Value = query.Value; - - for (MatchType match : GetDefaultMatchTypeOrder(query.Type)) - { - filter.Type = match; - - for (auto field : { PackageMatchField::Id, PackageMatchField::Name, PackageMatchField::Moniker, PackageMatchField::Command, PackageMatchField::Tag }) - { - filter.Field = field; - resultsTable.SearchOnField(filter); - } - } - } - - OneToManyTableSchema Interface::GetOneToManyTableSchema() const - { - return OneToManyTableSchema::Version_2_0; - } - - ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const - { - anon::FoldPackageMatchFilters(request.Inclusions); - anon::FoldPackageMatchFilters(request.Filters); - - if (request.Purpose == SearchPurpose::CorrelationToInstalled) - { - // Correlate from available package to installed package - // For available package to installed package mapping, only one try is needed. - // For example, if ARP DisplayName contains arch, then the installed package's ARP DisplayName should also include arch. - auto candidateInclusionsWithArch = request.Inclusions; - if (anon::UpdatePackageMatchFilters(candidateInclusionsWithArch, m_normalizer, Utility::NormalizationField::Architecture)) - { - // If DisplayNames contain arch, only use Inclusions with arch for search - request.Inclusions = candidateInclusionsWithArch; - } - else - { - // Otherwise, just update the Inclusions with normalization - anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); - } - - return BasicSearchInternal(connection, request); - } - else if (request.Purpose == SearchPurpose::CorrelationToAvailable) - { - // For installed package to available package correlation, - // try the search with NormalizedName with Arch first, if not found, try with all values. - // This can be extended in the future for more granular search requests. - std::vector candidateSearches; - auto candidateSearchWithArch = request; - if (anon::UpdatePackageMatchFilters(candidateSearchWithArch.Inclusions, m_normalizer, Utility::NormalizationField::Architecture)) - { - candidateSearches.emplace_back(std::move(candidateSearchWithArch)); - } - anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); - candidateSearches.emplace_back(request); - - SearchResult result; - for (auto& candidateSearch : candidateSearches) - { - result = BasicSearchInternal(connection, candidateSearch); - if (!result.Matches.empty()) - { - break; - } - } - - return result; - } - else - { - anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); - anon::UpdatePackageMatchFilters(request.Filters, m_normalizer); - - return BasicSearchInternal(connection, request); - } - } - - ISQLiteIndex::SearchResult Interface::BasicSearchInternal(const SQLite::Connection& connection, const SearchRequest& request) const - { - if (request.IsForEverything()) - { - std::vector ids = PackagesTable::GetAllRowIds(connection, PackagesTable::IdColumn::Name, request.MaximumResults); - - SearchResult result; - for (SQLite::rowid_t id : ids) - { - result.Matches.emplace_back(std::make_pair(id, PackageMatchFilter(PackageMatchField::Id, MatchType::Wildcard))); - } - - result.Truncated = (request.MaximumResults && PackagesTable::GetCount(connection) > request.MaximumResults); - - return result; - } - - // First phase, create the search results table and populate it with the initial results. - // If the Query is provided, we search across many fields and put results in together. - // If Inclusions has fields, we add these to the data. - // If neither is defined, we take the first filter and use it as the initial results search. - std::unique_ptr resultsTable = CreateSearchResultsTable(connection); - bool inclusionsAttempted = false; - - if (request.Query) - { - // Perform searches across multiple tables to populate the initial results. - PerformQuerySearch(*resultsTable.get(), request.Query.value()); - - inclusionsAttempted = true; - } - - if (!request.Inclusions.empty()) - { - for (auto include : request.Inclusions) - { - for (MatchType match : GetDefaultMatchTypeOrder(include.Type)) - { - include.Type = match; - resultsTable->SearchOnField(include); - } - } - - inclusionsAttempted = true; - } - - size_t filterIndex = 0; - if (!inclusionsAttempted) - { - THROW_HR_IF(E_UNEXPECTED, request.Filters.empty()); - - // Perform search for just the field matching the first filter - PackageMatchFilter filter = request.Filters[0]; - - for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) - { - filter.Type = match; - resultsTable->SearchOnField(filter); - } - - // Skip the filter as we already know everything matches - filterIndex = 1; - } - - // Remove any duplicate manifest entries - resultsTable->RemoveDuplicatePackageRows(); - - // Second phase, for remaining filters, flag matching search results, then remove unflagged values. - for (size_t i = filterIndex; i < request.Filters.size(); ++i) - { - PackageMatchFilter filter = request.Filters[i]; - - resultsTable->PrepareToFilter(); - - for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) - { - filter.Type = match; - resultsTable->FilterOnField(filter); - } - - resultsTable->CompleteFilter(); - } - - return resultsTable->GetSearchResults(request.MaximumResults); - } - - void Interface::PrepareForPackaging(const SQLiteIndexContext& context, bool vacuum) - { - SQLite::Connection& connection = context.Connection; - - // Get the base time from metadata - int64_t updateBaseTime = 0; - std::optional updateBaseTimeString = SQLite::MetadataTable::TryGetNamedValue(connection, s_MetadataValueName_PackageUpdateTrackingBaseTime); - if (updateBaseTimeString && !updateBaseTimeString->empty()) - { - updateBaseTime = std::stoll(updateBaseTimeString.value()); - } - - // Get the output directory or use the file path - std::filesystem::path baseOutputDirectory; - - if (context.Data.Contains(Property::IntermediateFileOutputPath)) - { - baseOutputDirectory = context.Data.Get(); - } - else if (context.Data.Contains(Property::DatabaseFilePath)) - { - baseOutputDirectory = context.Data.Get(); - baseOutputDirectory = baseOutputDirectory.parent_path(); - } - - THROW_WIN32_IF(ERROR_INVALID_STATE, baseOutputDirectory.empty() || baseOutputDirectory.is_relative()); - - // Output all of the changed package version manifests since the base time to the target location - for (const auto& packageData : PackageUpdateTrackingTable::GetUpdatesSince(connection, updateBaseTime)) - { - std::filesystem::path packageDirectory = baseOutputDirectory / - Manifest::PackageVersionDataManifest::GetRelativeDirectoryPath(packageData.PackageIdentifier, Utility::SHA256::ConvertToString(packageData.Hash)); - - std::filesystem::create_directories(packageDirectory); - - std::filesystem::path manifestPath = packageDirectory / Manifest::PackageVersionDataManifest::VersionManifestCompressedFileName(); - - AICLI_LOG(Repo, Info, << "Writing PackageVersionDataManifest for [" << packageData.PackageIdentifier << "] to [" << manifestPath << "]"); - - std::ofstream stream(manifestPath, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); - THROW_LAST_ERROR_IF(stream.fail()); - stream.write(reinterpret_cast(packageData.Manifest.data()), packageData.Manifest.size()); - THROW_LAST_ERROR_IF(stream.fail()); - stream.flush(); - } - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v2_0"); - - // Create the 2.0 data tables - PackagesTable::Create< - PackagesTable::IdColumn, - PackagesTable::NameColumn, - PackagesTable::MonikerColumn, - PackagesTable::LatestVersionColumn, - PackagesTable::ARPMinVersionColumn, - PackagesTable::ARPMaxVersionColumn, - PackagesTable::HashColumn - >(connection); - - TagsTable::Create(connection, GetOneToManyTableSchema()); - CommandsTable::Create(connection, GetOneToManyTableSchema()); - - PackageFamilyNameTable::Create(connection); - ProductCodeTable::Create(connection); - NormalizedPackageNameTable::Create(connection); - NormalizedPackagePublisherTable::Create(connection); - UpgradeCodeTable::Create(connection); - - // Copy data from 1.7 tables to 2.0 tables - SearchResult allPackages = m_internalInterface->Search(connection, {}); - - for (const auto& packageMatch : allPackages.Matches) - { - std::vector versionKeys = m_internalInterface->GetVersionKeysById(connection, packageMatch.first); - ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; - - std::string packageIdentifier = m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(); - - std::vector packageData{ - { PackagesTable::IdColumn::Name, packageIdentifier }, - { PackagesTable::NameColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Name).value() }, - { PackagesTable::LatestVersionColumn::Name, latestVersionKey.VersionAndChannel.GetVersion().ToString() }, - }; - - auto addIfPresent = [&](std::string_view name, std::optional&& value) - { - if (value && !value->empty()) - { - packageData.emplace_back(PackagesTable::NameValuePair{ name, std::move(value).value() }); - } - }; - - addIfPresent(PackagesTable::MonikerColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Moniker).value()); - addIfPresent(PackagesTable::ARPMinVersionColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::ArpMinVersion).value()); - addIfPresent(PackagesTable::ARPMaxVersionColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::ArpMaxVersion).value()); - - SQLite::rowid_t packageId = PackagesTable::Insert(connection, packageData); - - PackagesTable::UpdateValueIdById(connection, packageId, PackageUpdateTrackingTable::GetDataHash(connection, packageIdentifier)); - - for (const auto& versionKey : versionKeys) - { - TagsTable::EnsureExistsAndInsert(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Tag), packageId); - CommandsTable::EnsureExistsAndInsert(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Command), packageId); - - PackageFamilyNameTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::PackageFamilyName), packageId); - ProductCodeTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::ProductCode), packageId); - NormalizedPackageNameTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Name), packageId); - NormalizedPackagePublisherTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Publisher), packageId); - UpgradeCodeTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::UpgradeCode), packageId); - } - } - - PackagesTable::PrepareForPackaging< - PackagesTable::IdColumn, - PackagesTable::NameColumn, - PackagesTable::MonikerColumn, - PackagesTable::LatestVersionColumn, - PackagesTable::ARPMinVersionColumn, - PackagesTable::ARPMaxVersionColumn, - PackagesTable::HashColumn - >(connection); - - TagsTable::PrepareForPackaging(connection); - CommandsTable::PrepareForPackaging(connection); - - PackageUpdateTrackingTable::Drop(connection); - - // The tables based on SystemReferenceStringTable don't need a prepare currently - - // Drop 1.7 tables - m_internalInterface->DropTables(connection); - - savepoint.Commit(); - - m_internalInterface.reset(); - - if (vacuum) - { - Vacuum(connection); - } - } - - void Interface::Vacuum(const SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Vacuum(); - builder.Execute(connection); - } - - void Interface::EnsureInternalInterface(const SQLite::Connection& connection, bool requireInternalInterface) const - { - if (!m_internalInterfaceChecked) - { - if (!PackagesTable::Exists(connection)) - { - m_internalInterface = CreateInternalInterface(); - } - - m_internalInterfaceChecked = true; - } - - THROW_HR_IF(E_NOT_VALID_STATE, requireInternalInterface && !m_internalInterface); - } - - std::unique_ptr Interface::CreateInternalInterface() const - { - return CreateISQLiteIndex({ 1, 7 }); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "Microsoft/Schema/2_0/Interface.h" + +#include "Microsoft/Schema/2_0/PackagesTable.h" + +#include "Microsoft/Schema/2_0/TagsTable.h" +#include "Microsoft/Schema/2_0/CommandsTable.h" +#include "Microsoft/Schema/2_0/PackageFamilyNameTable.h" +#include "Microsoft/Schema/2_0/ProductCodeTable.h" +#include "Microsoft/Schema/2_0/NormalizedPackageNameTable.h" +#include "Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h" +#include "Microsoft/Schema/2_0/UpgradeCodeTable.h" + +#include "Microsoft/Schema/2_0/SearchResultsTable.h" +#include "Microsoft/Schema/2_0/PackageUpdateTrackingTable.h" + +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace anon + { + // Folds the values of the fields that are stored folded. + void FoldPackageMatchFilters(std::vector& filters) + { + for (auto& filter : filters) + { + if ((filter.Field == PackageMatchField::PackageFamilyName || filter.Field == PackageMatchField::ProductCode || filter.Field == PackageMatchField::UpgradeCode) && + filter.Type == MatchType::Exact) + { + filter.Value = Utility::FoldCase(filter.Value); + } + } + } + + // Update NormalizedNameAndPublisher with normalization and folding + // Returns true if the normalized name contains normalization field of fieldsToInclude + bool UpdateNormalizedNameAndPublisher( + PackageMatchFilter& filter, + const Utility::NameNormalizer& normalizer, + Utility::NormalizationField fieldsToInclude) + { + Utility::NormalizedName normalized = normalizer.Normalize(Utility::FoldCase(filter.Value), Utility::FoldCase(filter.Additional.value())); + filter.Value = normalized.GetNormalizedName(fieldsToInclude); + filter.Additional = normalized.Publisher(); + return WI_AreAllFlagsSet(normalized.GetNormalizedFields(), fieldsToInclude); + } + + bool UpdatePackageMatchFilters( + std::vector& filters, + const Utility::NameNormalizer& normalizer, + Utility::NormalizationField normalizedNameFieldsToFilter = Utility::NormalizationField::None) + { + bool normalizedNameFieldsFound = false; + for (auto itr = filters.begin(); itr != filters.end();) + { + if (itr->Field == PackageMatchField::NormalizedNameAndPublisher && itr->Type == MatchType::Exact) + { + if (!UpdateNormalizedNameAndPublisher(*itr, normalizer, normalizedNameFieldsToFilter)) + { + // If not matched, this package match filter will be removed. + // For example, if caller is trying to search with arch info only, values without arch will be removed from search. + itr = filters.erase(itr); + continue; + } + + normalizedNameFieldsFound = true; + } + + ++itr; + } + + return normalizedNameFieldsFound; + } + } + + Interface::Interface(Utility::NormalizationVersion normVersion) : m_normalizer(normVersion) + { + } + + SQLite::Version Interface::GetVersion() const + { + return { 2, 0 }; + } + + void Interface::CreateTables(SQLite::Connection& connection, CreateOptions options) + { + m_internalInterface = CreateInternalInterface(); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createtables_v2_0"); + + // We only create the internal tables at this point, the actual 2.0 tables are created in PrepareForPackaging + m_internalInterface->CreateTables(connection, options); + + savepoint.Commit(); + + m_internalInterfaceChecked = true; + } + + SQLite::rowid_t Interface::AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + EnsureInternalInterface(connection, true); + SQLite::rowid_t manifestId = m_internalInterface->AddManifest(connection, manifest, relativePath); + PackageUpdateTrackingTable::Update(connection, m_internalInterface.get(), m_internalInterface->GetPropertyByPrimaryId(connection, manifestId, PackageVersionProperty::Id).value()); + return manifestId; + } + + std::pair Interface::UpdateManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) + { + EnsureInternalInterface(connection, true); + std::pair result = m_internalInterface->UpdateManifest(connection, manifest, relativePath); + if (result.first) + { + PackageUpdateTrackingTable::Update(connection, m_internalInterface.get(), m_internalInterface->GetPropertyByPrimaryId(connection, result.second, PackageVersionProperty::Id).value()); + } + return result; + } + + SQLite::rowid_t Interface::RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) + { + EnsureInternalInterface(connection, true); + std::optional result = m_internalInterface->GetManifestIdByManifest(connection, manifest); + + // If the manifest doesn't actually exist, fail the remove. + THROW_HR_IF(E_NOT_SET, !result); + + SQLite::rowid_t manifestId = result.value(); + RemoveManifestById(connection, manifestId); + + return manifestId; + } + + void Interface::RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) + { + EnsureInternalInterface(connection, true); + std::optional identifier = m_internalInterface->GetPropertyByPrimaryId(connection, manifestId, PackageVersionProperty::Id); + m_internalInterface->RemoveManifestById(connection, manifestId); + if (identifier) + { + PackageUpdateTrackingTable::Update(connection, m_internalInterface.get(), identifier.value()); + } + } + + void Interface::PrepareForPackaging(SQLite::Connection&) + { + // We implement the context version + THROW_HR(E_NOTIMPL); + } + + void Interface::PrepareForPackaging(const SQLiteIndexContext& context) + { + EnsureInternalInterface(context.Connection, true); + PrepareForPackaging(context, true); + } + + bool Interface::CheckConsistency(const SQLite::Connection& connection, bool log) const + { + EnsureInternalInterface(connection); + + bool result = true; + +#define AICLI_CHECK_CONSISTENCY(_check_) \ + if (result || log) \ + { \ + result = _check_ && result; \ + } + + if (m_internalInterface) + { + AICLI_CHECK_CONSISTENCY(m_internalInterface->CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(PackageUpdateTrackingTable::CheckConsistency(connection, m_internalInterface.get(), log)); + + return result; + } + + AICLI_CHECK_CONSISTENCY((PackagesTable::CheckConsistency< + PackagesTable::IdColumn, + PackagesTable::NameColumn, + PackagesTable::MonikerColumn, + PackagesTable::LatestVersionColumn, + PackagesTable::ARPMinVersionColumn, + PackagesTable::ARPMaxVersionColumn>(connection, log))); + + // Check the 1:N map tables for consistency + AICLI_CHECK_CONSISTENCY(TagsTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(CommandsTable::CheckConsistency(connection, log)); + + AICLI_CHECK_CONSISTENCY(PackageFamilyNameTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(ProductCodeTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(NormalizedPackageNameTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(NormalizedPackagePublisherTable::CheckConsistency(connection, log)); + AICLI_CHECK_CONSISTENCY(UpgradeCodeTable::CheckConsistency(connection, log)); + +#undef AICLI_CHECK_CONSISTENCY + + return result; + } + + ISQLiteIndex::SearchResult Interface::Search(const SQLite::Connection& connection, const SearchRequest& request) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->Search(connection, request); + } + + SearchRequest requestCopy = request; + return SearchInternal(connection, requestCopy); + } + + std::optional Interface::GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->GetPropertyByPrimaryId(connection, primaryId, property); + } + + switch (property) + { + case PackageVersionProperty::Id: + return PackagesTable::GetValueById(connection, primaryId); + case PackageVersionProperty::Name: + return PackagesTable::GetValueById(connection, primaryId); + case PackageVersionProperty::Version: + return PackagesTable::GetValueById(connection, primaryId); + case PackageVersionProperty::Channel: + return ""; + case PackageVersionProperty::ManifestSHA256Hash: + { + std::optional hash = PackagesTable::GetValueById(connection, primaryId); + return (!hash || hash->empty()) ? std::optional{} : Utility::SHA256::ConvertToString(hash.value()); + } + case PackageVersionProperty::ArpMinVersion: + return PackagesTable::GetValueById(connection, primaryId); + case PackageVersionProperty::ArpMaxVersion: + return PackagesTable::GetValueById(connection, primaryId); + case PackageVersionProperty::Moniker: + return PackagesTable::GetValueById(connection, primaryId); + default: + return {}; + } + } + + std::vector Interface::GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->GetMultiPropertyByPrimaryId(connection, primaryId, property); + } + + switch (property) + { + case PackageVersionMultiProperty::PackageFamilyName: + return PackageFamilyNameTable::GetValuesByPrimaryId(connection, primaryId); + case PackageVersionMultiProperty::ProductCode: + return ProductCodeTable::GetValuesByPrimaryId(connection, primaryId); + // These values are not right, as they are normalized. But they are good enough for now and all we have. + case PackageVersionMultiProperty::Name: + return NormalizedPackageNameTable::GetValuesByPrimaryId(connection, primaryId); + case PackageVersionMultiProperty::Publisher: + return NormalizedPackagePublisherTable::GetValuesByPrimaryId(connection, primaryId); + case PackageVersionMultiProperty::UpgradeCode: + return UpgradeCodeTable::GetValuesByPrimaryId(connection, primaryId); + case PackageVersionMultiProperty::Tag: + return TagsTable::GetValuesByPrimaryId(connection, primaryId); + case PackageVersionMultiProperty::Command: + return CommandsTable::GetValuesByPrimaryId(connection, primaryId); + default: + return {}; + } + } + + std::optional Interface::GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->GetManifestIdByKey(connection, id, version, channel); + } + + THROW_HR(E_NOT_VALID_STATE); + } + + std::optional Interface::GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->GetManifestIdByManifest(connection, manifest); + } + + THROW_HR(E_NOT_VALID_STATE); + } + + std::vector Interface::GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->GetVersionKeysById(connection, id); + } + + THROW_HR(E_NOT_VALID_STATE); + } + + ISQLiteIndex::MetadataResult Interface::GetMetadataByManifestId(const SQLite::Connection&, SQLite::rowid_t) const + { + return {}; + } + + void Interface::SetMetadataByManifestId(SQLite::Connection&, SQLite::rowid_t, PackageVersionMetadata, std::string_view) + { + } + + Utility::NormalizedName Interface::NormalizeName(std::string_view name, std::string_view publisher) const + { + if (m_internalInterface) + { + return m_internalInterface->NormalizeName(name, publisher); + } + + return m_normalizer.Normalize(name, publisher); + } + + std::set> Interface::GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t rowid) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->GetDependenciesByManifestRowId(connection, rowid); + } + + THROW_HR(E_NOT_VALID_STATE); + } + + std::vector> Interface::GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t id) const + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->GetDependentsById(connection, id); + } + + THROW_HR(E_NOT_VALID_STATE); + } + + void Interface::DropTables(SQLite::Connection& connection) + { + EnsureInternalInterface(connection); + + if (m_internalInterface) + { + return m_internalInterface->DropTables(connection); + } + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "drop_tables_v2_0"); + + PackagesTable::Drop(connection); + + TagsTable::Drop(connection); + CommandsTable::Drop(connection); + + PackageFamilyNameTable::Drop(connection); + ProductCodeTable::Drop(connection); + NormalizedPackageNameTable::Drop(connection); + NormalizedPackagePublisherTable::Drop(connection); + UpgradeCodeTable::Drop(connection); + + savepoint.Commit(); + } + + bool Interface::MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) + { + THROW_HR_IF_NULL(E_POINTER, current); + + auto currentVersion = current->GetVersion(); + if (currentVersion.MajorVersion != 1 || currentVersion.MinorVersion != 7) + { + return false; + } + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "migrate_from_v2_0"); + + // We only need to insert all of the existing packages into the update tracking table. + PackageUpdateTrackingTable::EnsureExists(connection); + SearchResult allPackages = current->Search(connection, {}); + + for (const auto& packageMatch : allPackages.Matches) + { + std::vector versionKeys = current->GetVersionKeysById(connection, packageMatch.first); + ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; + PackageUpdateTrackingTable::Update(connection, current, current->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(), false); + } + + savepoint.Commit(); + return true; + } + + void Interface::SetProperty(SQLite::Connection& connection, Property property, const std::string& value) + { + switch (property) + { + case Property::PackageUpdateTrackingBaseTime: + { + int64_t baseTime = 0; + if (value.empty()) + { + baseTime = Utility::GetCurrentUnixEpoch(); + } + else + { + baseTime = std::stoll(value); + } + SQLite::MetadataTable::SetNamedValue(connection, s_MetadataValueName_PackageUpdateTrackingBaseTime, std::to_string(baseTime)); + } + break; + + default: + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + } + + std::unique_ptr Interface::CreateSearchResultsTable(const SQLite::Connection& connection) const + { + return std::make_unique(connection); + } + + void Interface::PerformQuerySearch(SearchResultsTable& resultsTable, const RequestMatch& query) const + { + // First, do an exact match search for the folded system reference strings + // We do this first because it is exact, and likely won't match anything else if it matches this. + PackageMatchFilter filter(PackageMatchField::Unknown, MatchType::Exact, Utility::FoldCase(query.Value)); + + for (PackageMatchField field : { PackageMatchField::PackageFamilyName, PackageMatchField::ProductCode, PackageMatchField::UpgradeCode }) + { + filter.Field = field; + resultsTable.SearchOnField(filter); + } + + // Now search on the unfolded value + filter.Value = query.Value; + + for (MatchType match : GetDefaultMatchTypeOrder(query.Type)) + { + filter.Type = match; + + for (auto field : { PackageMatchField::Id, PackageMatchField::Name, PackageMatchField::Moniker, PackageMatchField::Command, PackageMatchField::Tag }) + { + filter.Field = field; + resultsTable.SearchOnField(filter); + } + } + } + + OneToManyTableSchema Interface::GetOneToManyTableSchema() const + { + return OneToManyTableSchema::Version_2_0; + } + + ISQLiteIndex::SearchResult Interface::SearchInternal(const SQLite::Connection& connection, SearchRequest& request) const + { + anon::FoldPackageMatchFilters(request.Inclusions); + anon::FoldPackageMatchFilters(request.Filters); + + if (request.Purpose == SearchPurpose::CorrelationToInstalled) + { + // Correlate from available package to installed package + // For available package to installed package mapping, only one try is needed. + // For example, if ARP DisplayName contains arch, then the installed package's ARP DisplayName should also include arch. + auto candidateInclusionsWithArch = request.Inclusions; + if (anon::UpdatePackageMatchFilters(candidateInclusionsWithArch, m_normalizer, Utility::NormalizationField::Architecture)) + { + // If DisplayNames contain arch, only use Inclusions with arch for search + request.Inclusions = candidateInclusionsWithArch; + } + else + { + // Otherwise, just update the Inclusions with normalization + anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); + } + + return BasicSearchInternal(connection, request); + } + else if (request.Purpose == SearchPurpose::CorrelationToAvailable) + { + // For installed package to available package correlation, + // try the search with NormalizedName with Arch first, if not found, try with all values. + // This can be extended in the future for more granular search requests. + std::vector candidateSearches; + auto candidateSearchWithArch = request; + if (anon::UpdatePackageMatchFilters(candidateSearchWithArch.Inclusions, m_normalizer, Utility::NormalizationField::Architecture)) + { + candidateSearches.emplace_back(std::move(candidateSearchWithArch)); + } + anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); + candidateSearches.emplace_back(request); + + SearchResult result; + for (auto& candidateSearch : candidateSearches) + { + result = BasicSearchInternal(connection, candidateSearch); + if (!result.Matches.empty()) + { + break; + } + } + + return result; + } + else + { + anon::UpdatePackageMatchFilters(request.Inclusions, m_normalizer); + anon::UpdatePackageMatchFilters(request.Filters, m_normalizer); + + return BasicSearchInternal(connection, request); + } + } + + ISQLiteIndex::SearchResult Interface::BasicSearchInternal(const SQLite::Connection& connection, const SearchRequest& request) const + { + if (request.IsForEverything()) + { + std::vector ids = PackagesTable::GetAllRowIds(connection, PackagesTable::IdColumn::Name, request.MaximumResults); + + SearchResult result; + for (SQLite::rowid_t id : ids) + { + result.Matches.emplace_back(std::make_pair(id, PackageMatchFilter(PackageMatchField::Id, MatchType::Wildcard))); + } + + result.Truncated = (request.MaximumResults && PackagesTable::GetCount(connection) > request.MaximumResults); + + return result; + } + + // First phase, create the search results table and populate it with the initial results. + // If the Query is provided, we search across many fields and put results in together. + // If Inclusions has fields, we add these to the data. + // If neither is defined, we take the first filter and use it as the initial results search. + std::unique_ptr resultsTable = CreateSearchResultsTable(connection); + bool inclusionsAttempted = false; + + if (request.Query) + { + // Perform searches across multiple tables to populate the initial results. + PerformQuerySearch(*resultsTable.get(), request.Query.value()); + + inclusionsAttempted = true; + } + + if (!request.Inclusions.empty()) + { + for (auto include : request.Inclusions) + { + for (MatchType match : GetDefaultMatchTypeOrder(include.Type)) + { + include.Type = match; + resultsTable->SearchOnField(include); + } + } + + inclusionsAttempted = true; + } + + size_t filterIndex = 0; + if (!inclusionsAttempted) + { + THROW_HR_IF(E_UNEXPECTED, request.Filters.empty()); + + // Perform search for just the field matching the first filter + PackageMatchFilter filter = request.Filters[0]; + + for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) + { + filter.Type = match; + resultsTable->SearchOnField(filter); + } + + // Skip the filter as we already know everything matches + filterIndex = 1; + } + + // Remove any duplicate manifest entries + resultsTable->RemoveDuplicatePackageRows(); + + // Second phase, for remaining filters, flag matching search results, then remove unflagged values. + for (size_t i = filterIndex; i < request.Filters.size(); ++i) + { + PackageMatchFilter filter = request.Filters[i]; + + resultsTable->PrepareToFilter(); + + for (MatchType match : GetDefaultMatchTypeOrder(filter.Type)) + { + filter.Type = match; + resultsTable->FilterOnField(filter); + } + + resultsTable->CompleteFilter(); + } + + return resultsTable->GetSearchResults(request.MaximumResults); + } + + void Interface::PrepareForPackaging(const SQLiteIndexContext& context, bool vacuum) + { + SQLite::Connection& connection = context.Connection; + + // Get the base time from metadata + int64_t updateBaseTime = 0; + std::optional updateBaseTimeString = SQLite::MetadataTable::TryGetNamedValue(connection, s_MetadataValueName_PackageUpdateTrackingBaseTime); + if (updateBaseTimeString && !updateBaseTimeString->empty()) + { + updateBaseTime = std::stoll(updateBaseTimeString.value()); + } + + // Get the output directory or use the file path + std::filesystem::path baseOutputDirectory; + + if (context.Data.Contains(Property::IntermediateFileOutputPath)) + { + baseOutputDirectory = context.Data.Get(); + } + else if (context.Data.Contains(Property::DatabaseFilePath)) + { + baseOutputDirectory = context.Data.Get(); + baseOutputDirectory = baseOutputDirectory.parent_path(); + } + + THROW_WIN32_IF(ERROR_INVALID_STATE, baseOutputDirectory.empty() || baseOutputDirectory.is_relative()); + + // Output all of the changed package version manifests since the base time to the target location + for (const auto& packageData : PackageUpdateTrackingTable::GetUpdatesSince(connection, updateBaseTime)) + { + std::filesystem::path packageDirectory = baseOutputDirectory / + Manifest::PackageVersionDataManifest::GetRelativeDirectoryPath(packageData.PackageIdentifier, Utility::SHA256::ConvertToString(packageData.Hash)); + + std::filesystem::create_directories(packageDirectory); + + std::filesystem::path manifestPath = packageDirectory / Manifest::PackageVersionDataManifest::VersionManifestCompressedFileName(); + + AICLI_LOG(Repo, Info, << "Writing PackageVersionDataManifest for [" << packageData.PackageIdentifier << "] to [" << manifestPath << "]"); + + std::ofstream stream(manifestPath, std::ios_base::out | std::ios_base::binary | std::ios_base::trunc); + THROW_LAST_ERROR_IF(stream.fail()); + stream.write(reinterpret_cast(packageData.Manifest.data()), packageData.Manifest.size()); + THROW_LAST_ERROR_IF(stream.fail()); + stream.flush(); + } + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "prepareforpackaging_v2_0"); + + // Create the 2.0 data tables + PackagesTable::Create< + PackagesTable::IdColumn, + PackagesTable::NameColumn, + PackagesTable::MonikerColumn, + PackagesTable::LatestVersionColumn, + PackagesTable::ARPMinVersionColumn, + PackagesTable::ARPMaxVersionColumn, + PackagesTable::HashColumn + >(connection); + + TagsTable::Create(connection, GetOneToManyTableSchema()); + CommandsTable::Create(connection, GetOneToManyTableSchema()); + + PackageFamilyNameTable::Create(connection); + ProductCodeTable::Create(connection); + NormalizedPackageNameTable::Create(connection); + NormalizedPackagePublisherTable::Create(connection); + UpgradeCodeTable::Create(connection); + + // Copy data from 1.7 tables to 2.0 tables + SearchResult allPackages = m_internalInterface->Search(connection, {}); + + for (const auto& packageMatch : allPackages.Matches) + { + std::vector versionKeys = m_internalInterface->GetVersionKeysById(connection, packageMatch.first); + ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; + + std::string packageIdentifier = m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(); + + std::vector packageData{ + { PackagesTable::IdColumn::Name, packageIdentifier }, + { PackagesTable::NameColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Name).value() }, + { PackagesTable::LatestVersionColumn::Name, latestVersionKey.VersionAndChannel.GetVersion().ToString() }, + }; + + auto addIfPresent = [&](std::string_view name, std::optional&& value) + { + if (value && !value->empty()) + { + packageData.emplace_back(PackagesTable::NameValuePair{ name, std::move(value).value() }); + } + }; + + addIfPresent(PackagesTable::MonikerColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Moniker).value()); + addIfPresent(PackagesTable::ARPMinVersionColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::ArpMinVersion).value()); + addIfPresent(PackagesTable::ARPMaxVersionColumn::Name, m_internalInterface->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::ArpMaxVersion).value()); + + SQLite::rowid_t packageId = PackagesTable::Insert(connection, packageData); + + PackagesTable::UpdateValueIdById(connection, packageId, PackageUpdateTrackingTable::GetDataHash(connection, packageIdentifier)); + + for (const auto& versionKey : versionKeys) + { + TagsTable::EnsureExistsAndInsert(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Tag), packageId); + CommandsTable::EnsureExistsAndInsert(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Command), packageId); + + PackageFamilyNameTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::PackageFamilyName), packageId); + ProductCodeTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::ProductCode), packageId); + NormalizedPackageNameTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Name), packageId); + NormalizedPackagePublisherTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::Publisher), packageId); + UpgradeCodeTable::EnsureExists(connection, m_internalInterface->GetMultiPropertyByPrimaryId(connection, versionKey.ManifestId, PackageVersionMultiProperty::UpgradeCode), packageId); + } + } + + PackagesTable::PrepareForPackaging< + PackagesTable::IdColumn, + PackagesTable::NameColumn, + PackagesTable::MonikerColumn, + PackagesTable::LatestVersionColumn, + PackagesTable::ARPMinVersionColumn, + PackagesTable::ARPMaxVersionColumn, + PackagesTable::HashColumn + >(connection); + + TagsTable::PrepareForPackaging(connection); + CommandsTable::PrepareForPackaging(connection); + + PackageUpdateTrackingTable::Drop(connection); + + // The tables based on SystemReferenceStringTable don't need a prepare currently + + // Drop 1.7 tables + m_internalInterface->DropTables(connection); + + savepoint.Commit(); + + m_internalInterface.reset(); + + if (vacuum) + { + Vacuum(connection); + } + } + + void Interface::Vacuum(const SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Vacuum(); + builder.Execute(connection); + } + + void Interface::EnsureInternalInterface(const SQLite::Connection& connection, bool requireInternalInterface) const + { + if (!m_internalInterfaceChecked) + { + if (!PackagesTable::Exists(connection)) + { + m_internalInterface = CreateInternalInterface(); + } + + m_internalInterfaceChecked = true; + } + + THROW_HR_IF(E_NOT_VALID_STATE, requireInternalInterface && !m_internalInterface); + } + + std::unique_ptr Interface::CreateInternalInterface() const + { + return CreateISQLiteIndex({ 1, 7 }); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackageNameTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackageNameTable.h index bc23579398..68e55da3a1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackageNameTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackageNameTable.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct NormalizedPackageNameTableInfo - { - inline static constexpr std::string_view TableName() { return "norm_names2"sv; } - inline static constexpr std::string_view ValueName() { return "norm_name"sv; } - }; - } - - // The table for Commands. - using NormalizedPackageNameTable = SystemReferenceStringTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct NormalizedPackageNameTableInfo + { + inline static constexpr std::string_view TableName() { return "norm_names2"sv; } + inline static constexpr std::string_view ValueName() { return "norm_name"sv; } + }; + } + + // The table for Commands. + using NormalizedPackageNameTable = SystemReferenceStringTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h index d452db7636..b0493e859a 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct NormalizedPackagePublisherTableInfo - { - inline static constexpr std::string_view TableName() { return "norm_publishers2"sv; } - inline static constexpr std::string_view ValueName() { return "norm_publisher"sv; } - }; - } - - using NormalizedPackagePublisherTable = SystemReferenceStringTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct NormalizedPackagePublisherTableInfo + { + inline static constexpr std::string_view TableName() { return "norm_publishers2"sv; } + inline static constexpr std::string_view ValueName() { return "norm_publisher"sv; } + }; + } + + using NormalizedPackagePublisherTable = SystemReferenceStringTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp index c0b2b75dde..76f650db82 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.cpp @@ -1,450 +1,450 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" -#include "Microsoft/Schema/2_0/PackagesTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using PrimaryTable = PackagesTable; - - using namespace std::string_view_literals; - static constexpr std::string_view s_OneToManyTableWithMap_MapTable_PrimaryName = "package"sv; - static constexpr std::string_view s_OneToManyTableWithMap_MapTable_Suffix = "_map"sv; - static constexpr std::string_view s_OneToManyTableWithMap_MapTable_IndexSuffix = "_index"sv; - static constexpr std::string_view s_OneToManyTableWithMap_PrimaryKeyIndexSuffix = "_pkindex"sv; - - namespace anon - { - // Create the mapping table insert statement for multiple use. - // Bind the rowid of the value to 2. - SQLite::Statement CreateMappingInsertStatementForPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) - { - SQLite::Builder::StatementBuilder insertMappingBuilder; - insertMappingBuilder.InsertOrIgnore({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }). - Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }).Values(manifestId, SQLite::Builder::Unbound); - - return insertMappingBuilder.Prepare(connection); - } - - // Get a collection of the value ids associated with the given primary id. - std::vector GetValueIdsByPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) - { - std::vector result; - - SQLite::Builder::StatementBuilder selectMappingBuilder; - selectMappingBuilder.Select(valueName).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Where(s_OneToManyTableWithMap_MapTable_PrimaryName).Equals(manifestId); - - SQLite::Statement selectMappingStatement = selectMappingBuilder.Prepare(connection); - - while (selectMappingStatement.Step()) - { - result.push_back(selectMappingStatement.GetColumn(0)); - } - - return result; - } - - void CreateDataTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); - - StatementBuilder createTableBuilder; - - createTableBuilder.CreateTable(tableName).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(valueName, Type::Text).NotNull() - }); - - createTableBuilder.Execute(connection); - - StatementBuilder indexBuilder; - indexBuilder.CreateUniqueIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }).On(tableName).Columns(valueName); - indexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void DropDataTable(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(tableName); - - dropTableBuilder.Execute(connection); - } - - std::optional DataTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); - - if (useLike) - { - selectBuilder.LikeWithEscape(value); - } - else - { - selectBuilder.Equals(value); - } - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - std::optional DataTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t rowid) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(rowid); - - SQLite::Statement select = selectBuilder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - SQLite::rowid_t DataTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch = false) - { - auto selectResult = DataTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); - if (selectResult) - { - if (overwriteLikeMatch) - { - // If the value in the table is not an exact match, overwrite it with the incoming value - auto tableValue = DataTableSelectValueById(connection, tableName, valueName, selectResult.value()); - if (tableValue.value() != value) - { - SQLite::Builder::StatementBuilder updateBuilder; - updateBuilder.Update(tableName).Set().Column(valueName).Equals(value).Where(SQLite::RowIDName).Equals(selectResult); - - updateBuilder.Execute(connection); - } - } - - return selectResult.value(); - } - - SQLite::Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); - - insertBuilder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - void DataTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }); - dropIndexBuilder.Execute(connection); - } - - bool DataTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - // Build a select statement to find values that contain an embedded null character - // Such as: - // Select count(*) from table where instr(value,char(0))>0 - SQLite::Builder::StatementBuilder builder; - builder. - Select({ SQLite::RowIDName, valueName }). - From(tableName). - WhereValueContainsEmbeddedNullCharacter(valueName); - - SQLite::Statement select = builder.Prepare(connection); - bool result = true; - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); - } - - return result; - } - } - - std::string OneToManyTableWithMapGetMapTableName(std::string_view tableName) - { - std::string result(tableName); - result += s_OneToManyTableWithMap_MapTable_Suffix; - return result; - } - - std::string_view OneToManyTableWithMapGetManifestColumnName() - { - return s_OneToManyTableWithMap_MapTable_PrimaryName; - } - - void CreateOneToManyTableWithMap(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); - - // Create the data table as a 1:1 - anon::CreateDataTable(connection, tableName, valueName); - - switch (schemaVersion) - { - case OneToManyTableSchema::Version_2_0: - { - // Create the mapping table - StatementBuilder createMapTableBuilder; - createMapTableBuilder.CreateTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ - ColumnBuilder(valueName, Type::Int64).NotNull(), - ColumnBuilder(s_OneToManyTableWithMap_MapTable_PrimaryName, Type::Int64).NotNull(), - PrimaryKeyBuilder({ valueName, s_OneToManyTableWithMap_MapTable_PrimaryName }) - }).WithoutRowID(); - - createMapTableBuilder.Execute(connection); - } - break; - default: - THROW_HR(E_UNEXPECTED); - } - - StatementBuilder createMapTableIndexBuilder; - createMapTableIndexBuilder.CreateIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }). - On({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }); - - createMapTableIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - void DropOneToManyTableWithMap(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_drop_v2_0"); - - anon::DropDataTable(connection, tableName); - - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); - - dropTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - std::vector OneToManyTableWithMapGetValuesByPrimaryId( - const SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - SQLite::rowid_t manifestId) - { - using QCol = SQLite::Builder::QualifiedColumn; - - std::vector result; - - SQLite::Builder::StatementBuilder builder; - builder.Select(QCol(tableName, valueName)). - From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As("map").Join(tableName). - On(QCol("map", valueName), QCol(tableName, SQLite::RowIDName)).Where(QCol("map", s_OneToManyTableWithMap_MapTable_PrimaryName)).Equals(manifestId); - - SQLite::Statement statement = builder.Prepare(connection); - - while (statement.Step()) - { - result.emplace_back(statement.GetColumn(0)); - } - - return result; - } - - void OneToManyTableWithMapEnsureExistsAndInsert(SQLite::Connection& connection, - std::string_view tableName, std::string_view valueName, - const std::vector& values, SQLite::rowid_t manifestId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensureandinsert_v2_0"); - - SQLite::Statement insertMapping = anon::CreateMappingInsertStatementForPrimaryId(connection, tableName, valueName, manifestId); - - for (const std::string& value : values) - { - // First, ensure that the data exists - SQLite::rowid_t dataId = anon::DataTableEnsureExists(connection, tableName, valueName, value); - - // Second, insert into the mapping table - insertMapping.Reset(); - insertMapping.Bind(2, dataId); - - insertMapping.Execute(); - } - - savepoint.Commit(); - } - - void OneToManyTableWithMapPrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropMapTableIndexBuilder; - dropMapTableIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }); - - dropMapTableIndexBuilder.Execute(connection); - - anon::DataTablePrepareForPackaging(connection, tableName); - } - - bool OneToManyTableWithMapCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - using QCol = SQLite::Builder::QualifiedColumn; - constexpr std::string_view s_map = "map"sv; - - bool result = true; - - { - // Build a select statement to find map rows containing references to primaries with nonexistent rowids - // Such as: - // Select map.rowid, map.primary from tags_map as map left outer join primary on map.primary = primary.rowid where primary.id is null - - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). - From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). - LeftOuterJoin(details::PrimaryTable::TableName()).On(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)). - Where(QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << - ", " << select.GetColumn(1) << "] refers to invalid " << details::PrimaryTable::TableName()); - } - } - - if (!result && !log) - { - return result; - } - - { - // Build a select statement to find map rows containing references to 1:1 tables with nonexistent rowids - // Such as: - // Select map.rowid, map.tag from tags_map as map left outer join tags on map.tag = tags.rowid where tags.tag is null - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). - From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). - LeftOuterJoin(tableName).On(QCol(s_map, valueName), QCol(tableName, SQLite::RowIDName)). - Where(QCol(tableName, valueName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - bool secondaryResult = true; - - while (select.Step()) - { - secondaryResult = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << - ", " << select.GetColumn(1) << "] refers to invalid " << tableName); - } - - result = result && secondaryResult; - } - - if (!result && !log) - { - return result; - } - - result = anon::DataTableCheckConsistency(connection, tableName, valueName, log) && result; - - return result; - } - - bool OneToManyTableWithMapIsEmpty(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder countBuilder; - countBuilder.Select(SQLite::Builder::RowCount).From(tableName); - - SQLite::Statement countStatement = countBuilder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - SQLite::Builder::StatementBuilder countMapBuilder; - countMapBuilder.Select(SQLite::Builder::RowCount).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); - - SQLite::Statement countMapStatement = countMapBuilder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countMapStatement.Step()); - - return ((countStatement.GetColumn(0) == 0) && (countMapStatement.GetColumn(0) == 0)); - } - - int OneToManyTableWithMapBuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view tableName, - std::string_view valueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike) - { - using QCol = SQLite::Builder::QualifiedColumn; - constexpr std::string_view s_map = "map"sv; - - // Build a statement like: - // SELECT map.package as p, table.value as v from table - // join map on table.rowid = map.value - // where table.value = - builder.Select(). - Column(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName)).As(primaryAlias). - Column(QCol(tableName, valueName)).As(valueAlias). - From(tableName). - Join({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map).On(QCol(tableName, SQLite::RowIDName), QCol(s_map, valueName)). - Where(QCol(tableName, valueName)); - - int result = -1; - - if (useLike) - { - builder.Like(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - builder.Escape(SQLite::EscapeCharForLike); - } - else - { - builder.Equals(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - } - - return result; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" +#include "Microsoft/Schema/2_0/PackagesTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using PrimaryTable = PackagesTable; + + using namespace std::string_view_literals; + static constexpr std::string_view s_OneToManyTableWithMap_MapTable_PrimaryName = "package"sv; + static constexpr std::string_view s_OneToManyTableWithMap_MapTable_Suffix = "_map"sv; + static constexpr std::string_view s_OneToManyTableWithMap_MapTable_IndexSuffix = "_index"sv; + static constexpr std::string_view s_OneToManyTableWithMap_PrimaryKeyIndexSuffix = "_pkindex"sv; + + namespace anon + { + // Create the mapping table insert statement for multiple use. + // Bind the rowid of the value to 2. + SQLite::Statement CreateMappingInsertStatementForPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) + { + SQLite::Builder::StatementBuilder insertMappingBuilder; + insertMappingBuilder.InsertOrIgnore({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }). + Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }).Values(manifestId, SQLite::Builder::Unbound); + + return insertMappingBuilder.Prepare(connection); + } + + // Get a collection of the value ids associated with the given primary id. + std::vector GetValueIdsByPrimaryId(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t manifestId) + { + std::vector result; + + SQLite::Builder::StatementBuilder selectMappingBuilder; + selectMappingBuilder.Select(valueName).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Where(s_OneToManyTableWithMap_MapTable_PrimaryName).Equals(manifestId); + + SQLite::Statement selectMappingStatement = selectMappingBuilder.Prepare(connection); + + while (selectMappingStatement.Step()) + { + result.push_back(selectMappingStatement.GetColumn(0)); + } + + return result; + } + + void CreateDataTable(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); + + StatementBuilder createTableBuilder; + + createTableBuilder.CreateTable(tableName).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(valueName, Type::Text).NotNull() + }); + + createTableBuilder.Execute(connection); + + StatementBuilder indexBuilder; + indexBuilder.CreateUniqueIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }).On(tableName).Columns(valueName); + indexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void DropDataTable(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(tableName); + + dropTableBuilder.Execute(connection); + } + + std::optional DataTableSelectIdByValue(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool useLike) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(SQLite::RowIDName).From(tableName).Where(valueName); + + if (useLike) + { + selectBuilder.LikeWithEscape(value); + } + else + { + selectBuilder.Equals(value); + } + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + std::optional DataTableSelectValueById(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, SQLite::rowid_t rowid) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(valueName).From(tableName).Where(SQLite::RowIDName).Equals(rowid); + + SQLite::Statement select = selectBuilder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + SQLite::rowid_t DataTableEnsureExists(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, std::string_view value, bool overwriteLikeMatch = false) + { + auto selectResult = DataTableSelectIdByValue(connection, tableName, valueName, value, overwriteLikeMatch); + if (selectResult) + { + if (overwriteLikeMatch) + { + // If the value in the table is not an exact match, overwrite it with the incoming value + auto tableValue = DataTableSelectValueById(connection, tableName, valueName, selectResult.value()); + if (tableValue.value() != value) + { + SQLite::Builder::StatementBuilder updateBuilder; + updateBuilder.Update(tableName).Set().Column(valueName).Equals(value).Where(SQLite::RowIDName).Equals(selectResult); + + updateBuilder.Execute(connection); + } + } + + return selectResult.value(); + } + + SQLite::Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(tableName).Columns(valueName).Values(value); + + insertBuilder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + void DataTablePrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_PrimaryKeyIndexSuffix }); + dropIndexBuilder.Execute(connection); + } + + bool DataTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + // Build a select statement to find values that contain an embedded null character + // Such as: + // Select count(*) from table where instr(value,char(0))>0 + SQLite::Builder::StatementBuilder builder; + builder. + Select({ SQLite::RowIDName, valueName }). + From(tableName). + WhereValueContainsEmbeddedNullCharacter(valueName); + + SQLite::Statement select = builder.Prepare(connection); + bool result = true; + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << select.GetColumn(1) << "]"); + } + + return result; + } + } + + std::string OneToManyTableWithMapGetMapTableName(std::string_view tableName) + { + std::string result(tableName); + result += s_OneToManyTableWithMap_MapTable_Suffix; + return result; + } + + std::string_view OneToManyTableWithMapGetManifestColumnName() + { + return s_OneToManyTableWithMap_MapTable_PrimaryName; + } + + void CreateOneToManyTableWithMap(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); + + // Create the data table as a 1:1 + anon::CreateDataTable(connection, tableName, valueName); + + switch (schemaVersion) + { + case OneToManyTableSchema::Version_2_0: + { + // Create the mapping table + StatementBuilder createMapTableBuilder; + createMapTableBuilder.CreateTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ + ColumnBuilder(valueName, Type::Int64).NotNull(), + ColumnBuilder(s_OneToManyTableWithMap_MapTable_PrimaryName, Type::Int64).NotNull(), + PrimaryKeyBuilder({ valueName, s_OneToManyTableWithMap_MapTable_PrimaryName }) + }).WithoutRowID(); + + createMapTableBuilder.Execute(connection); + } + break; + default: + THROW_HR(E_UNEXPECTED); + } + + StatementBuilder createMapTableIndexBuilder; + createMapTableIndexBuilder.CreateIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }). + On({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).Columns({ s_OneToManyTableWithMap_MapTable_PrimaryName, valueName }); + + createMapTableIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + void DropOneToManyTableWithMap(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_drop_v2_0"); + + anon::DropDataTable(connection, tableName); + + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); + + dropTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::vector OneToManyTableWithMapGetValuesByPrimaryId( + const SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + SQLite::rowid_t manifestId) + { + using QCol = SQLite::Builder::QualifiedColumn; + + std::vector result; + + SQLite::Builder::StatementBuilder builder; + builder.Select(QCol(tableName, valueName)). + From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As("map").Join(tableName). + On(QCol("map", valueName), QCol(tableName, SQLite::RowIDName)).Where(QCol("map", s_OneToManyTableWithMap_MapTable_PrimaryName)).Equals(manifestId); + + SQLite::Statement statement = builder.Prepare(connection); + + while (statement.Step()) + { + result.emplace_back(statement.GetColumn(0)); + } + + return result; + } + + void OneToManyTableWithMapEnsureExistsAndInsert(SQLite::Connection& connection, + std::string_view tableName, std::string_view valueName, + const std::vector& values, SQLite::rowid_t manifestId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensureandinsert_v2_0"); + + SQLite::Statement insertMapping = anon::CreateMappingInsertStatementForPrimaryId(connection, tableName, valueName, manifestId); + + for (const std::string& value : values) + { + // First, ensure that the data exists + SQLite::rowid_t dataId = anon::DataTableEnsureExists(connection, tableName, valueName, value); + + // Second, insert into the mapping table + insertMapping.Reset(); + insertMapping.Bind(2, dataId); + + insertMapping.Execute(); + } + + savepoint.Commit(); + } + + void OneToManyTableWithMapPrepareForPackaging(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropMapTableIndexBuilder; + dropMapTableIndexBuilder.DropIndex({ tableName, s_OneToManyTableWithMap_MapTable_Suffix, s_OneToManyTableWithMap_MapTable_IndexSuffix }); + + dropMapTableIndexBuilder.Execute(connection); + + anon::DataTablePrepareForPackaging(connection, tableName); + } + + bool OneToManyTableWithMapCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + using QCol = SQLite::Builder::QualifiedColumn; + constexpr std::string_view s_map = "map"sv; + + bool result = true; + + { + // Build a select statement to find map rows containing references to primaries with nonexistent rowids + // Such as: + // Select map.rowid, map.primary from tags_map as map left outer join primary on map.primary = primary.rowid where primary.id is null + + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). + From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). + LeftOuterJoin(details::PrimaryTable::TableName()).On(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)). + Where(QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << + ", " << select.GetColumn(1) << "] refers to invalid " << details::PrimaryTable::TableName()); + } + } + + if (!result && !log) + { + return result; + } + + { + // Build a select statement to find map rows containing references to 1:1 tables with nonexistent rowids + // Such as: + // Select map.rowid, map.tag from tags_map as map left outer join tags on map.tag = tags.rowid where tags.tag is null + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName), QCol(s_map, valueName) }). + From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map). + LeftOuterJoin(tableName).On(QCol(s_map, valueName), QCol(tableName, SQLite::RowIDName)). + Where(QCol(tableName, valueName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + bool secondaryResult = true; + + while (select.Step()) + { + secondaryResult = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << s_OneToManyTableWithMap_MapTable_Suffix << " [" << select.GetColumn(0) << + ", " << select.GetColumn(1) << "] refers to invalid " << tableName); + } + + result = result && secondaryResult; + } + + if (!result && !log) + { + return result; + } + + result = anon::DataTableCheckConsistency(connection, tableName, valueName, log) && result; + + return result; + } + + bool OneToManyTableWithMapIsEmpty(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder countBuilder; + countBuilder.Select(SQLite::Builder::RowCount).From(tableName); + + SQLite::Statement countStatement = countBuilder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + SQLite::Builder::StatementBuilder countMapBuilder; + countMapBuilder.Select(SQLite::Builder::RowCount).From({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }); + + SQLite::Statement countMapStatement = countMapBuilder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countMapStatement.Step()); + + return ((countStatement.GetColumn(0) == 0) && (countMapStatement.GetColumn(0) == 0)); + } + + int OneToManyTableWithMapBuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view tableName, + std::string_view valueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike) + { + using QCol = SQLite::Builder::QualifiedColumn; + constexpr std::string_view s_map = "map"sv; + + // Build a statement like: + // SELECT map.package as p, table.value as v from table + // join map on table.rowid = map.value + // where table.value = + builder.Select(). + Column(QCol(s_map, s_OneToManyTableWithMap_MapTable_PrimaryName)).As(primaryAlias). + Column(QCol(tableName, valueName)).As(valueAlias). + From(tableName). + Join({ tableName, s_OneToManyTableWithMap_MapTable_Suffix }).As(s_map).On(QCol(tableName, SQLite::RowIDName), QCol(s_map, valueName)). + Where(QCol(tableName, valueName)); + + int result = -1; + + if (useLike) + { + builder.Like(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + builder.Escape(SQLite::EscapeCharForLike); + } + else + { + builder.Equals(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + } + + return result; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.h index 5f6b48f3c1..2748ea60f4 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/OneToManyTableWithMap.h @@ -1,137 +1,137 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - // Allow the different schema version to indicate which they are. - enum class OneToManyTableSchema - { - // Uses a named unique index for data table. - // Map table has primary key and no rowid. - // Column order is consistent with primary key order. - Version_2_0, - }; - - namespace details - { - // Returns the map table name for a given table. - std::string OneToManyTableGetMapTableName(std::string_view tableName); - - // Returns the primary column name. - std::string_view OneToManyTableGetManifestColumnName(); - - // Create the tables. - void CreateOneToManyTableWithMap(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName); - - // Drops the tables. - void DropOneToManyTableWithMap(SQLite::Connection& connection, std::string_view tableName); - - // Gets all values associated with the given primary id. - std::vector OneToManyTableWithMapGetValuesByPrimaryId( - const SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - SQLite::rowid_t primaryId); - - // Ensures that the value exists and inserts mapping entries. - void OneToManyTableWithMapEnsureExistsAndInsert(SQLite::Connection& connection, - std::string_view tableName, std::string_view valueName, - const std::vector& values, SQLite::rowid_t primaryId); - - // Removes data that is no longer needed for an index that is to be published. - void OneToManyTableWithMapPrepareForPackaging(SQLite::Connection& connection, std::string_view tableName); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - bool OneToManyTableWithMapCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); - - // Determines if the table is empty. - bool OneToManyTableWithMapIsEmpty(SQLite::Connection& connection, std::string_view tableName); - - // Builds the search select statement base on the given value. - // The return value is the bind index of the value to match against. - int OneToManyTableWithMapBuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view tableName, - std::string_view valueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike); - } - - // A table that represents a value that is 1:N with a primary entry. - template - struct OneToManyTableWithMap - { - // The name of the table. - static constexpr std::string_view TableName() - { - return TableInfo::TableName(); - } - - // The value name of the table. - static constexpr std::string_view ValueName() - { - return TableInfo::ValueName(); - } - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection, OneToManyTableSchema schemaVersion) - { - details::CreateOneToManyTableWithMap(connection, schemaVersion, TableInfo::TableName(), TableInfo::ValueName()); - } - - // Drops the tables. - static void Drop(SQLite::Connection& connection) - { - details::DropOneToManyTableWithMap(connection, TableInfo::TableName()); - } - - // Gets all values associated with the given primary id. - static std::vector GetValuesByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId) - { - return details::OneToManyTableWithMapGetValuesByPrimaryId(connection, TableInfo::TableName(), TableInfo::ValueName(), primaryId); - } - - // Ensures that all values exist in the data table, and inserts into the mapping table for the given primary id. - static void EnsureExistsAndInsert(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t primaryId) - { - details::OneToManyTableWithMapEnsureExistsAndInsert(connection, TableInfo::TableName(), TableInfo::ValueName(), values, primaryId); - } - - // Removes data that is no longer needed for an index that is to be published. - // Preserving the primary index will improve the efficiency of finding the values associated with a primary. - // Preserving the values index will improve searching when it is primarily done by equality. - static void PrepareForPackaging(SQLite::Connection& connection) - { - details::OneToManyTableWithMapPrepareForPackaging(connection, TableInfo::TableName()); - } - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - static bool CheckConsistency(const SQLite::Connection& connection, bool log) - { - return details::OneToManyTableWithMapCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); - } - - // Determines if the table is empty. - static bool IsEmpty(SQLite::Connection& connection) - { - return details::OneToManyTableWithMapIsEmpty(connection, TableInfo::TableName()); - } - - // Builds the search select statement base on the given value. - // The return value is the bind index of the value to match against. - static int BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view primaryAlias, std::string_view valueAlias, bool useLike) - { - return details::OneToManyTableWithMapBuildSearchStatement(builder, TableInfo::TableName(), TableInfo::ValueName(), primaryAlias, valueAlias, useLike); - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + // Allow the different schema version to indicate which they are. + enum class OneToManyTableSchema + { + // Uses a named unique index for data table. + // Map table has primary key and no rowid. + // Column order is consistent with primary key order. + Version_2_0, + }; + + namespace details + { + // Returns the map table name for a given table. + std::string OneToManyTableGetMapTableName(std::string_view tableName); + + // Returns the primary column name. + std::string_view OneToManyTableGetManifestColumnName(); + + // Create the tables. + void CreateOneToManyTableWithMap(SQLite::Connection& connection, OneToManyTableSchema schemaVersion, std::string_view tableName, std::string_view valueName); + + // Drops the tables. + void DropOneToManyTableWithMap(SQLite::Connection& connection, std::string_view tableName); + + // Gets all values associated with the given primary id. + std::vector OneToManyTableWithMapGetValuesByPrimaryId( + const SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + SQLite::rowid_t primaryId); + + // Ensures that the value exists and inserts mapping entries. + void OneToManyTableWithMapEnsureExistsAndInsert(SQLite::Connection& connection, + std::string_view tableName, std::string_view valueName, + const std::vector& values, SQLite::rowid_t primaryId); + + // Removes data that is no longer needed for an index that is to be published. + void OneToManyTableWithMapPrepareForPackaging(SQLite::Connection& connection, std::string_view tableName); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + bool OneToManyTableWithMapCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); + + // Determines if the table is empty. + bool OneToManyTableWithMapIsEmpty(SQLite::Connection& connection, std::string_view tableName); + + // Builds the search select statement base on the given value. + // The return value is the bind index of the value to match against. + int OneToManyTableWithMapBuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view tableName, + std::string_view valueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike); + } + + // A table that represents a value that is 1:N with a primary entry. + template + struct OneToManyTableWithMap + { + // The name of the table. + static constexpr std::string_view TableName() + { + return TableInfo::TableName(); + } + + // The value name of the table. + static constexpr std::string_view ValueName() + { + return TableInfo::ValueName(); + } + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection, OneToManyTableSchema schemaVersion) + { + details::CreateOneToManyTableWithMap(connection, schemaVersion, TableInfo::TableName(), TableInfo::ValueName()); + } + + // Drops the tables. + static void Drop(SQLite::Connection& connection) + { + details::DropOneToManyTableWithMap(connection, TableInfo::TableName()); + } + + // Gets all values associated with the given primary id. + static std::vector GetValuesByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId) + { + return details::OneToManyTableWithMapGetValuesByPrimaryId(connection, TableInfo::TableName(), TableInfo::ValueName(), primaryId); + } + + // Ensures that all values exist in the data table, and inserts into the mapping table for the given primary id. + static void EnsureExistsAndInsert(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t primaryId) + { + details::OneToManyTableWithMapEnsureExistsAndInsert(connection, TableInfo::TableName(), TableInfo::ValueName(), values, primaryId); + } + + // Removes data that is no longer needed for an index that is to be published. + // Preserving the primary index will improve the efficiency of finding the values associated with a primary. + // Preserving the values index will improve searching when it is primarily done by equality. + static void PrepareForPackaging(SQLite::Connection& connection) + { + details::OneToManyTableWithMapPrepareForPackaging(connection, TableInfo::TableName()); + } + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + static bool CheckConsistency(const SQLite::Connection& connection, bool log) + { + return details::OneToManyTableWithMapCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); + } + + // Determines if the table is empty. + static bool IsEmpty(SQLite::Connection& connection) + { + return details::OneToManyTableWithMapIsEmpty(connection, TableInfo::TableName()); + } + + // Builds the search select statement base on the given value. + // The return value is the bind index of the value to match against. + static int BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view primaryAlias, std::string_view valueAlias, bool useLike) + { + return details::OneToManyTableWithMapBuildSearchStatement(builder, TableInfo::TableName(), TableInfo::ValueName(), primaryAlias, valueAlias, useLike); + } + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageFamilyNameTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageFamilyNameTable.h index c9dcb318e7..7818cc5b39 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageFamilyNameTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageFamilyNameTable.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct PackageFamilyNameTableInfo - { - inline static constexpr std::string_view TableName() { return "pfns2"sv; } - inline static constexpr std::string_view ValueName() { return "pfn"sv; } - }; - } - - using PackageFamilyNameTable = SystemReferenceStringTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct PackageFamilyNameTableInfo + { + inline static constexpr std::string_view TableName() { return "pfns2"sv; } + inline static constexpr std::string_view ValueName() { return "pfn"sv; } + }; + } + + using PackageFamilyNameTable = SystemReferenceStringTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp index 1728a5d6e7..cf253fd8a1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.cpp @@ -1,249 +1,249 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackageUpdateTrackingTable.h" -#include -#include - -using namespace AppInstaller::SQLite; - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - using namespace std::string_view_literals; - static constexpr std::string_view s_PUTT_Table_Name = "update_tracking"sv; - static constexpr std::string_view s_PUTT_WriteTimeIndex_Name = "update_tracking_write_idx"sv; - static constexpr std::string_view s_PUTT_Package = "package"sv; - static constexpr std::string_view s_PUTT_WriteTime = "write_time"sv; - static constexpr std::string_view s_PUTT_Manifest = "manifest"sv; - static constexpr std::string_view s_PUTT_Hash = "hash"sv; - - std::string_view PackageUpdateTrackingTable::TableName() - { - return s_PUTT_Table_Name; - } - - void PackageUpdateTrackingTable::Create(SQLite::Connection& connection) - { - using namespace Builder; - - StatementBuilder builder; - builder.CreateTable(s_PUTT_Table_Name).BeginColumns(); - - builder.Column(IntegerPrimaryKey()); - builder.Column(ColumnBuilder(s_PUTT_Package, Type::Text).NotNull()); - builder.Column(ColumnBuilder(s_PUTT_WriteTime, Type::Int64).NotNull()); - builder.Column(ColumnBuilder(s_PUTT_Manifest, Type::Blob).NotNull()); - builder.Column(ColumnBuilder(s_PUTT_Hash, Type::Blob).NotNull()); - - builder.EndColumns(); - - builder.Execute(connection); - - StatementBuilder indexBuilder; - indexBuilder.CreateIndex(s_PUTT_WriteTimeIndex_Name).On(s_PUTT_Table_Name).Columns(s_PUTT_WriteTime); - indexBuilder.Execute(connection); - } - - void PackageUpdateTrackingTable::EnsureExists(SQLite::Connection& connection) - { - if (!Exists(connection)) - { - Create(connection); - } - } - - void PackageUpdateTrackingTable::Drop(SQLite::Connection& connection) - { - Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(s_PUTT_Table_Name); - - dropTableBuilder.Execute(connection); - } - - bool PackageUpdateTrackingTable::Exists(const SQLite::Connection& connection) - { - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). - Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_PUTT_Table_Name); - - Statement statement = builder.Prepare(connection); - THROW_HR_IF(E_UNEXPECTED, !statement.Step()); - return statement.GetColumn(0) != 0; - } - - void PackageUpdateTrackingTable::Update(SQLite::Connection& connection, const ISQLiteIndex* internalIndex, const std::string& packageIdentifier, bool ensureTable) - { - if (ensureTable) - { - EnsureExists(connection); - } - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier); - auto result = internalIndex->Search(connection, request); - - if (result.Matches.empty()) - { - // Remove any existing package update row - Builder::StatementBuilder deleteBuilder; - deleteBuilder.DeleteFrom(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); - - deleteBuilder.Execute(connection); - } - else - { - THROW_HR_IF(E_UNEXPECTED, result.Matches.size() != 1); - - // Insert or update the package row - std::vector versionKeys = internalIndex->GetVersionKeysById(connection, result.Matches[0].first); - - Manifest::PackageVersionDataManifest manifest; - - for (const auto& key : versionKeys) - { - Manifest::PackageVersionDataManifest::VersionData versionData{ - key.VersionAndChannel, - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMinVersion), - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMaxVersion), - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::RelativePath), - internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ManifestSHA256Hash) - }; - - manifest.AddVersion(std::move(versionData)); - } - - std::string manifestString = manifest.Serialize(); - - auto compressor = Manifest::PackageVersionDataManifest::CreateCompressor(); - std::vector compressedManifest = compressor.Compress(manifestString); - - Utility::SHA256::HashBuffer manifestHash = Utility::SHA256::ComputeHash(compressedManifest); - int64_t currentTime = Utility::GetCurrentUnixEpoch(); - - // First attempt to update the row and then insert it if no modification occurred. - Builder::StatementBuilder updateBuilder; - updateBuilder.Update(s_PUTT_Table_Name).Set(). - Column(s_PUTT_WriteTime).Equals(currentTime). - Column(s_PUTT_Manifest).Equals(compressedManifest). - Column(s_PUTT_Hash).Equals(manifestHash). - Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); - - updateBuilder.Execute(connection); - - if (connection.GetChanges() == 0) - { - Builder::StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_PUTT_Table_Name). - Columns({ s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). - Values(packageIdentifier, currentTime, compressedManifest, manifestHash); - - insertBuilder.Execute(connection); - } - } - } - - bool PackageUpdateTrackingTable::CheckConsistency(const SQLite::Connection& connection, ISQLiteIndex* internalIndex, bool log) - { - bool result = true; - - // Ensure that all data in the update table matches the internal index - for (const PackageData& packageData : GetUpdatesSince(connection, 0)) - { - auto manifestHash = Utility::SHA256::ComputeHash(packageData.Manifest); - if (!Utility::SHA256::AreEqual(packageData.Hash, manifestHash)) - { - if (!log) - { - return false; - } - - result = false; - AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Hash << "] in table [" << s_PUTT_Table_Name << - "] at row [" << packageData.RowID << "]; the hash of the manifest value does not match the hash in the row"); - } - - SearchRequest request; - request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageData.PackageIdentifier); - - if (internalIndex->Search(connection, request).Matches.empty()) - { - if (!log) - { - return false; - } - - result = false; - AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Package << "] in table [" << s_PUTT_Table_Name << - "] at row [" << packageData.RowID << "]; the package [" << packageData.PackageIdentifier << "] was not found in the internal index"); - } - } - - // Ensure that all packages in the internal index are present in the update table - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(s_PUTT_Table_Name).Where(s_PUTT_Package).Like(Builder::Unbound).Escape(EscapeCharForLike); - - Statement select = builder.Prepare(connection); - - for (const auto& packageMatch : internalIndex->Search(connection, {}).Matches) - { - std::vector versionKeys = internalIndex->GetVersionKeysById(connection, packageMatch.first); - ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; - - std::string packageIdentifier = internalIndex->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(); - - select.Reset(); - select.Bind(1, packageIdentifier); - select.Step(); - - if (select.GetColumn(0) != 1) - { - if (!log) - { - return false; - } - - result = false; - AICLI_LOG(Repo, Info, << " [INVALID] value [" << packageIdentifier << "] in the internal index was not found in [" << s_PUTT_Table_Name << "]"); - } - } - - return result; - } - - std::vector PackageUpdateTrackingTable::GetUpdatesSince(const SQLite::Connection& connection, int64_t updateBaseTime) - { - Builder::StatementBuilder builder; - builder.Select({ RowIDName, s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). - From(s_PUTT_Table_Name).Where(s_PUTT_WriteTime).IsGreaterThanOrEqualTo(updateBaseTime); - - Statement select = builder.Prepare(connection); - - std::vector result; - - while (select.Step()) - { - PackageData item; - item.RowID = select.GetColumn(0); - item.PackageIdentifier = select.GetColumn(1); - item.WriteTime = select.GetColumn(2); - item.Manifest = select.GetColumn(3); - item.Hash = select.GetColumn(4); - - result.emplace_back(std::move(item)); - } - - return result; - } - - SQLite::blob_t PackageUpdateTrackingTable::GetDataHash(const SQLite::Connection& connection, const std::string& packageIdentifier) - { - Builder::StatementBuilder builder; - builder.Select(s_PUTT_Hash).From(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); - - Statement select = builder.Prepare(connection); - - THROW_HR_IF(E_NOT_SET, !select.Step()); - - return select.GetColumn(0); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageUpdateTrackingTable.h" +#include +#include + +using namespace AppInstaller::SQLite; + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_PUTT_Table_Name = "update_tracking"sv; + static constexpr std::string_view s_PUTT_WriteTimeIndex_Name = "update_tracking_write_idx"sv; + static constexpr std::string_view s_PUTT_Package = "package"sv; + static constexpr std::string_view s_PUTT_WriteTime = "write_time"sv; + static constexpr std::string_view s_PUTT_Manifest = "manifest"sv; + static constexpr std::string_view s_PUTT_Hash = "hash"sv; + + std::string_view PackageUpdateTrackingTable::TableName() + { + return s_PUTT_Table_Name; + } + + void PackageUpdateTrackingTable::Create(SQLite::Connection& connection) + { + using namespace Builder; + + StatementBuilder builder; + builder.CreateTable(s_PUTT_Table_Name).BeginColumns(); + + builder.Column(IntegerPrimaryKey()); + builder.Column(ColumnBuilder(s_PUTT_Package, Type::Text).NotNull()); + builder.Column(ColumnBuilder(s_PUTT_WriteTime, Type::Int64).NotNull()); + builder.Column(ColumnBuilder(s_PUTT_Manifest, Type::Blob).NotNull()); + builder.Column(ColumnBuilder(s_PUTT_Hash, Type::Blob).NotNull()); + + builder.EndColumns(); + + builder.Execute(connection); + + StatementBuilder indexBuilder; + indexBuilder.CreateIndex(s_PUTT_WriteTimeIndex_Name).On(s_PUTT_Table_Name).Columns(s_PUTT_WriteTime); + indexBuilder.Execute(connection); + } + + void PackageUpdateTrackingTable::EnsureExists(SQLite::Connection& connection) + { + if (!Exists(connection)) + { + Create(connection); + } + } + + void PackageUpdateTrackingTable::Drop(SQLite::Connection& connection) + { + Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(s_PUTT_Table_Name); + + dropTableBuilder.Execute(connection); + } + + bool PackageUpdateTrackingTable::Exists(const SQLite::Connection& connection) + { + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). + Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_PUTT_Table_Name); + + Statement statement = builder.Prepare(connection); + THROW_HR_IF(E_UNEXPECTED, !statement.Step()); + return statement.GetColumn(0) != 0; + } + + void PackageUpdateTrackingTable::Update(SQLite::Connection& connection, const ISQLiteIndex* internalIndex, const std::string& packageIdentifier, bool ensureTable) + { + if (ensureTable) + { + EnsureExists(connection); + } + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier); + auto result = internalIndex->Search(connection, request); + + if (result.Matches.empty()) + { + // Remove any existing package update row + Builder::StatementBuilder deleteBuilder; + deleteBuilder.DeleteFrom(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); + + deleteBuilder.Execute(connection); + } + else + { + THROW_HR_IF(E_UNEXPECTED, result.Matches.size() != 1); + + // Insert or update the package row + std::vector versionKeys = internalIndex->GetVersionKeysById(connection, result.Matches[0].first); + + Manifest::PackageVersionDataManifest manifest; + + for (const auto& key : versionKeys) + { + Manifest::PackageVersionDataManifest::VersionData versionData{ + key.VersionAndChannel, + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMinVersion), + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ArpMaxVersion), + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::RelativePath), + internalIndex->GetPropertyByPrimaryId(connection, key.ManifestId, PackageVersionProperty::ManifestSHA256Hash) + }; + + manifest.AddVersion(std::move(versionData)); + } + + std::string manifestString = manifest.Serialize(); + + auto compressor = Manifest::PackageVersionDataManifest::CreateCompressor(); + std::vector compressedManifest = compressor.Compress(manifestString); + + Utility::SHA256::HashBuffer manifestHash = Utility::SHA256::ComputeHash(compressedManifest); + int64_t currentTime = Utility::GetCurrentUnixEpoch(); + + // First attempt to update the row and then insert it if no modification occurred. + Builder::StatementBuilder updateBuilder; + updateBuilder.Update(s_PUTT_Table_Name).Set(). + Column(s_PUTT_WriteTime).Equals(currentTime). + Column(s_PUTT_Manifest).Equals(compressedManifest). + Column(s_PUTT_Hash).Equals(manifestHash). + Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); + + updateBuilder.Execute(connection); + + if (connection.GetChanges() == 0) + { + Builder::StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_PUTT_Table_Name). + Columns({ s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). + Values(packageIdentifier, currentTime, compressedManifest, manifestHash); + + insertBuilder.Execute(connection); + } + } + } + + bool PackageUpdateTrackingTable::CheckConsistency(const SQLite::Connection& connection, ISQLiteIndex* internalIndex, bool log) + { + bool result = true; + + // Ensure that all data in the update table matches the internal index + for (const PackageData& packageData : GetUpdatesSince(connection, 0)) + { + auto manifestHash = Utility::SHA256::ComputeHash(packageData.Manifest); + if (!Utility::SHA256::AreEqual(packageData.Hash, manifestHash)) + { + if (!log) + { + return false; + } + + result = false; + AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Hash << "] in table [" << s_PUTT_Table_Name << + "] at row [" << packageData.RowID << "]; the hash of the manifest value does not match the hash in the row"); + } + + SearchRequest request; + request.Inclusions.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageData.PackageIdentifier); + + if (internalIndex->Search(connection, request).Matches.empty()) + { + if (!log) + { + return false; + } + + result = false; + AICLI_LOG(Repo, Info, << " [INVALID] value [" << s_PUTT_Package << "] in table [" << s_PUTT_Table_Name << + "] at row [" << packageData.RowID << "]; the package [" << packageData.PackageIdentifier << "] was not found in the internal index"); + } + } + + // Ensure that all packages in the internal index are present in the update table + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(s_PUTT_Table_Name).Where(s_PUTT_Package).Like(Builder::Unbound).Escape(EscapeCharForLike); + + Statement select = builder.Prepare(connection); + + for (const auto& packageMatch : internalIndex->Search(connection, {}).Matches) + { + std::vector versionKeys = internalIndex->GetVersionKeysById(connection, packageMatch.first); + ISQLiteIndex::VersionKey& latestVersionKey = versionKeys[0]; + + std::string packageIdentifier = internalIndex->GetPropertyByPrimaryId(connection, latestVersionKey.ManifestId, PackageVersionProperty::Id).value(); + + select.Reset(); + select.Bind(1, packageIdentifier); + select.Step(); + + if (select.GetColumn(0) != 1) + { + if (!log) + { + return false; + } + + result = false; + AICLI_LOG(Repo, Info, << " [INVALID] value [" << packageIdentifier << "] in the internal index was not found in [" << s_PUTT_Table_Name << "]"); + } + } + + return result; + } + + std::vector PackageUpdateTrackingTable::GetUpdatesSince(const SQLite::Connection& connection, int64_t updateBaseTime) + { + Builder::StatementBuilder builder; + builder.Select({ RowIDName, s_PUTT_Package, s_PUTT_WriteTime, s_PUTT_Manifest, s_PUTT_Hash }). + From(s_PUTT_Table_Name).Where(s_PUTT_WriteTime).IsGreaterThanOrEqualTo(updateBaseTime); + + Statement select = builder.Prepare(connection); + + std::vector result; + + while (select.Step()) + { + PackageData item; + item.RowID = select.GetColumn(0); + item.PackageIdentifier = select.GetColumn(1); + item.WriteTime = select.GetColumn(2); + item.Manifest = select.GetColumn(3); + item.Hash = select.GetColumn(4); + + result.emplace_back(std::move(item)); + } + + return result; + } + + SQLite::blob_t PackageUpdateTrackingTable::GetDataHash(const SQLite::Connection& connection, const std::string& packageIdentifier) + { + Builder::StatementBuilder builder; + builder.Select(s_PUTT_Hash).From(s_PUTT_Table_Name).Where(s_PUTT_Package).LikeWithEscape(packageIdentifier); + + Statement select = builder.Prepare(connection); + + THROW_HR_IF(E_NOT_SET, !select.Step()); + + return select.GetColumn(0); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.h index 1af63d2c0a..5cf429bd1f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackageUpdateTrackingTable.h @@ -1,52 +1,52 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/ISQLiteIndex.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - // Table for tracking the updates to the internal table so that prepare can output - // only the necessary package manifests. - struct PackageUpdateTrackingTable - { - // Get the table name. - static std::string_view TableName(); - - // Creates the table. - static void Create(SQLite::Connection& connection); - - // Creates the table if it does not exist. - static void EnsureExists(SQLite::Connection& connection); - - // Drops the table. - static void Drop(SQLite::Connection& connection); - - // Determine if the table currently exists in the database. - static bool Exists(const SQLite::Connection& connection); - - // Updates the tracking table for the given package identifier in the internal index. - static void Update(SQLite::Connection& connection, const ISQLiteIndex* internalIndex, const std::string& packageIdentifier, bool ensureTable = true); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - static bool CheckConsistency(const SQLite::Connection& connection, ISQLiteIndex* internalIndex, bool log); - - // Data on a single row in the table. - struct PackageData - { - SQLite::rowid_t RowID = 0; - std::string PackageIdentifier; - int64_t WriteTime = 0; - SQLite::blob_t Manifest; - SQLite::blob_t Hash; - }; - - // Gets the data on updates that have been written since the given base time. - static std::vector GetUpdatesSince(const SQLite::Connection& connection, int64_t updateBaseTime); - - // Gets the data hash for the given package identifier. - static SQLite::blob_t GetDataHash(const SQLite::Connection& connection, const std::string& packageIdentifier); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/ISQLiteIndex.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + // Table for tracking the updates to the internal table so that prepare can output + // only the necessary package manifests. + struct PackageUpdateTrackingTable + { + // Get the table name. + static std::string_view TableName(); + + // Creates the table. + static void Create(SQLite::Connection& connection); + + // Creates the table if it does not exist. + static void EnsureExists(SQLite::Connection& connection); + + // Drops the table. + static void Drop(SQLite::Connection& connection); + + // Determine if the table currently exists in the database. + static bool Exists(const SQLite::Connection& connection); + + // Updates the tracking table for the given package identifier in the internal index. + static void Update(SQLite::Connection& connection, const ISQLiteIndex* internalIndex, const std::string& packageIdentifier, bool ensureTable = true); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + static bool CheckConsistency(const SQLite::Connection& connection, ISQLiteIndex* internalIndex, bool log); + + // Data on a single row in the table. + struct PackageData + { + SQLite::rowid_t RowID = 0; + std::string PackageIdentifier; + int64_t WriteTime = 0; + SQLite::blob_t Manifest; + SQLite::blob_t Hash; + }; + + // Gets the data on updates that have been written since the given base time. + static std::vector GetUpdatesSince(const SQLite::Connection& connection, int64_t updateBaseTime); + + // Gets the data hash for the given package identifier. + static SQLite::blob_t GetDataHash(const SQLite::Connection& connection, const std::string& packageIdentifier); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.cpp index 1e74c1e69c..c58a8bfb0b 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.cpp @@ -1,329 +1,329 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackagesTable.h" -#include -#include "OneToManyTableWithMap.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - using namespace std::string_view_literals; - static constexpr std::string_view s_PackagesTable_Table_Name = "packages"sv; - static constexpr std::string_view s_PackagesTable_Index_Separator = "_"sv; - static constexpr std::string_view s_PackagesTable_Index_Suffix = "_index"sv; - - namespace details - { - void PackagesTableCreate(SQLite::Connection& connection, std::initializer_list values) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPackagesTable_v2_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PackagesTable_Table_Name).BeginColumns(); - - // Add an integer primary key to keep the manifest rowid consistent - createTableBuilder.Column(IntegerPrimaryKey()); - - for (const ColumnInfo& value : values) - { - ColumnBuilder columnBuilder(value.Name, value.Type); - - if (!value.AllowNull) - { - columnBuilder.NotNull(); - } - - createTableBuilder.Column(columnBuilder); - } - - createTableBuilder.EndColumns(); - - createTableBuilder.Execute(connection); - - // Create a unique index with the primary key values - StatementBuilder pkIndexBuilder; - - pkIndexBuilder.CreateUniqueIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Suffix }).On(s_PackagesTable_Table_Name).BeginColumns(); - - for (const ColumnInfo& value : values) - { - if (value.PrimaryKey) - { - pkIndexBuilder.Column(value.Name); - } - } - - pkIndexBuilder.EndColumns(); - - pkIndexBuilder.Execute(connection); - - // Create an index on every value to improve performance - for (const ColumnInfo& value : values) - { - StatementBuilder createIndexBuilder; - - createIndexBuilder.CreateIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Separator, value.Name, s_PackagesTable_Index_Suffix }); - createIndexBuilder.On(s_PackagesTable_Table_Name).Columns(value.Name); - - createIndexBuilder.Execute(connection); - } - - savepoint.Commit(); - } - - // Creates a statement and executes it, select the actual values for a given manifest id. - // Ex. - // SELECT [ids].[id] FROM [manifest] - // JOIN [ids] ON [manifest].[id] = [ids].[rowid] - // WHERE [manifest].[rowid] = 1 - SQLite::Statement PackagesTableGetValuesById_Statement( - const SQLite::Connection& connection, - SQLite::rowid_t id, - std::initializer_list columns, - bool stepAndVerify) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(columns).From(s_PackagesTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement result = builder.Prepare(connection); - - if (stepAndVerify) - { - THROW_HR_IF(E_NOT_SET, !result.Step()); - } - - return result; - } - - SQLite::Statement PackagesTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName) - { - SQLite::Builder::StatementBuilder builder; - builder.Update(s_PackagesTable_Table_Name).Set().Column(valueName).Equals(SQLite::Builder::Unbound).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); - - return builder.Prepare(connection); - } - - void PackagesTablePrepareForPackaging(SQLite::Connection& connection, std::initializer_list values) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpPackagesTable_v2_0"); - - // Drop the index on the requested values - for (const auto& value : values) - { - SQLite::Builder::StatementBuilder dropIndexBuilder; - dropIndexBuilder.DropIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Separator, value.Name, s_PackagesTable_Index_Suffix }); - - dropIndexBuilder.Execute(connection); - } - - SQLite::Builder::StatementBuilder dropPKIndexBuilder; - dropPKIndexBuilder.DropIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Suffix }); - dropPKIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - bool PackagesTableCheckColumnForNulls(const SQLite::Connection& connection, std::string_view valueName, bool log) - { - // Build a select statement to find values that contain an embedded null character - // Such as: - // Select count(*) from table where instr(value,char(0))>0 - SQLite::Builder::StatementBuilder builder; - builder. - Select({ SQLite::RowIDName, valueName }). - From(s_PackagesTable_Table_Name). - WhereValueContainsEmbeddedNullCharacter(valueName); - - SQLite::Statement select = builder.Prepare(connection); - bool result = true; - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] value [" << valueName << "] in table [" << s_PackagesTable_Table_Name << - "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << - select.GetColumn(1) << "]"); - } - - return result; - } - - bool PackagesTableCheckConsistency(const SQLite::Connection& connection, std::initializer_list values, bool log) - { - bool result = true; - - for (const auto& value : values) - { - if (result || log) - { - result = details::PackagesTableCheckColumnForNulls(connection, value, log) && result; - } - } - - return result; - } - } - - std::string_view PackagesTable::TableName() - { - return s_PackagesTable_Table_Name; - } - - void PackagesTable::Drop(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(s_PackagesTable_Table_Name); - - dropTableBuilder.Execute(connection); - } - - bool PackagesTable::Exists(const SQLite::Connection& connection) - { - using namespace SQLite; - - Builder::StatementBuilder builder; - builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). - Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_PackagesTable_Table_Name); - - Statement statement = builder.Prepare(connection); - THROW_HR_IF(E_UNEXPECTED, !statement.Step()); - return statement.GetColumn(0) != 0; - } - - void PackagesTable::AddColumn(SQLite::Connection& connection, const ColumnInfo& value) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addColumnPackagesTable_v2_0"); - - StatementBuilder alterTableBuilder; - alterTableBuilder.AlterTable(s_PackagesTable_Table_Name).Add(value.Name, value.Type); - - alterTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - SQLite::rowid_t PackagesTable::Insert(SQLite::Connection& connection, const std::vector& values) - { - SQLite::Builder::StatementBuilder builder; - builder.InsertInto(s_PackagesTable_Table_Name).BeginColumns(); - - for (const NameValuePair& value : values) - { - builder.Column(value.Name); - } - - builder.EndColumns().BeginValues(); - - for (const NameValuePair& value : values) - { - builder.Value(value.Value); - } - - builder.EndValues(); - - builder.Execute(connection); - - return connection.GetLastInsertRowID(); - } - - bool PackagesTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PackagesTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) != 0); - } - - std::vector PackagesTable::GetAllRowIds(const SQLite::Connection& connection, std::string_view orderByColumn, size_t limit) - { - SQLite::Builder::StatementBuilder selectBuilder; - selectBuilder.Select(SQLite::RowIDName).From(s_PackagesTable_Table_Name).OrderBy(orderByColumn); - - if (limit) - { - selectBuilder.Limit(limit); - } - - SQLite::Statement select = selectBuilder.Prepare(connection); - - std::vector result; - while (select.Step()) - { - result.emplace_back(select.GetColumn(0)); - } - return result; - } - - uint64_t PackagesTable::GetCount(const SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PackagesTable_Table_Name); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return static_cast(countStatement.GetColumn(0)); - } - - int PackagesTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view valueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike) - { - using QCol = SQLite::Builder::QualifiedColumn; - - // Build a statement like: - // SELECT packages.rowid as p, packages.id as v from packages - // where packages.id = - builder.Select(). - Column(QCol(s_PackagesTable_Table_Name, SQLite::RowIDName)).As(primaryAlias). - Column(QCol(s_PackagesTable_Table_Name, valueName)).As(valueAlias). - From(s_PackagesTable_Table_Name).Where(QCol(s_PackagesTable_Table_Name, valueName)); - - int result = -1; - - if (useLike) - { - builder.Like(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - builder.Escape(SQLite::EscapeCharForLike); - } - else - { - builder.Equals(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - } - - return result; - } - - bool PackagesTable::IsEmpty(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PackagesTable_Table_Name); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) == 0); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackagesTable.h" +#include +#include "OneToManyTableWithMap.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_PackagesTable_Table_Name = "packages"sv; + static constexpr std::string_view s_PackagesTable_Index_Separator = "_"sv; + static constexpr std::string_view s_PackagesTable_Index_Suffix = "_index"sv; + + namespace details + { + void PackagesTableCreate(SQLite::Connection& connection, std::initializer_list values) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPackagesTable_v2_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PackagesTable_Table_Name).BeginColumns(); + + // Add an integer primary key to keep the manifest rowid consistent + createTableBuilder.Column(IntegerPrimaryKey()); + + for (const ColumnInfo& value : values) + { + ColumnBuilder columnBuilder(value.Name, value.Type); + + if (!value.AllowNull) + { + columnBuilder.NotNull(); + } + + createTableBuilder.Column(columnBuilder); + } + + createTableBuilder.EndColumns(); + + createTableBuilder.Execute(connection); + + // Create a unique index with the primary key values + StatementBuilder pkIndexBuilder; + + pkIndexBuilder.CreateUniqueIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Suffix }).On(s_PackagesTable_Table_Name).BeginColumns(); + + for (const ColumnInfo& value : values) + { + if (value.PrimaryKey) + { + pkIndexBuilder.Column(value.Name); + } + } + + pkIndexBuilder.EndColumns(); + + pkIndexBuilder.Execute(connection); + + // Create an index on every value to improve performance + for (const ColumnInfo& value : values) + { + StatementBuilder createIndexBuilder; + + createIndexBuilder.CreateIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Separator, value.Name, s_PackagesTable_Index_Suffix }); + createIndexBuilder.On(s_PackagesTable_Table_Name).Columns(value.Name); + + createIndexBuilder.Execute(connection); + } + + savepoint.Commit(); + } + + // Creates a statement and executes it, select the actual values for a given manifest id. + // Ex. + // SELECT [ids].[id] FROM [manifest] + // JOIN [ids] ON [manifest].[id] = [ids].[rowid] + // WHERE [manifest].[rowid] = 1 + SQLite::Statement PackagesTableGetValuesById_Statement( + const SQLite::Connection& connection, + SQLite::rowid_t id, + std::initializer_list columns, + bool stepAndVerify) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(columns).From(s_PackagesTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement result = builder.Prepare(connection); + + if (stepAndVerify) + { + THROW_HR_IF(E_NOT_SET, !result.Step()); + } + + return result; + } + + SQLite::Statement PackagesTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName) + { + SQLite::Builder::StatementBuilder builder; + builder.Update(s_PackagesTable_Table_Name).Set().Column(valueName).Equals(SQLite::Builder::Unbound).Where(SQLite::RowIDName).Equals(SQLite::Builder::Unbound); + + return builder.Prepare(connection); + } + + void PackagesTablePrepareForPackaging(SQLite::Connection& connection, std::initializer_list values) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "pfpPackagesTable_v2_0"); + + // Drop the index on the requested values + for (const auto& value : values) + { + SQLite::Builder::StatementBuilder dropIndexBuilder; + dropIndexBuilder.DropIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Separator, value.Name, s_PackagesTable_Index_Suffix }); + + dropIndexBuilder.Execute(connection); + } + + SQLite::Builder::StatementBuilder dropPKIndexBuilder; + dropPKIndexBuilder.DropIndex({ s_PackagesTable_Table_Name, s_PackagesTable_Index_Suffix }); + dropPKIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + bool PackagesTableCheckColumnForNulls(const SQLite::Connection& connection, std::string_view valueName, bool log) + { + // Build a select statement to find values that contain an embedded null character + // Such as: + // Select count(*) from table where instr(value,char(0))>0 + SQLite::Builder::StatementBuilder builder; + builder. + Select({ SQLite::RowIDName, valueName }). + From(s_PackagesTable_Table_Name). + WhereValueContainsEmbeddedNullCharacter(valueName); + + SQLite::Statement select = builder.Prepare(connection); + bool result = true; + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] value [" << valueName << "] in table [" << s_PackagesTable_Table_Name << + "] at row [" << select.GetColumn(0) << "] contains an embedded null character and starts with [" << + select.GetColumn(1) << "]"); + } + + return result; + } + + bool PackagesTableCheckConsistency(const SQLite::Connection& connection, std::initializer_list values, bool log) + { + bool result = true; + + for (const auto& value : values) + { + if (result || log) + { + result = details::PackagesTableCheckColumnForNulls(connection, value, log) && result; + } + } + + return result; + } + } + + std::string_view PackagesTable::TableName() + { + return s_PackagesTable_Table_Name; + } + + void PackagesTable::Drop(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(s_PackagesTable_Table_Name); + + dropTableBuilder.Execute(connection); + } + + bool PackagesTable::Exists(const SQLite::Connection& connection) + { + using namespace SQLite; + + Builder::StatementBuilder builder; + builder.Select(Builder::RowCount).From(Builder::Schema::MainTable). + Where(Builder::Schema::TypeColumn).Equals(Builder::Schema::Type_Table).And(Builder::Schema::NameColumn).Equals(s_PackagesTable_Table_Name); + + Statement statement = builder.Prepare(connection); + THROW_HR_IF(E_UNEXPECTED, !statement.Step()); + return statement.GetColumn(0) != 0; + } + + void PackagesTable::AddColumn(SQLite::Connection& connection, const ColumnInfo& value) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addColumnPackagesTable_v2_0"); + + StatementBuilder alterTableBuilder; + alterTableBuilder.AlterTable(s_PackagesTable_Table_Name).Add(value.Name, value.Type); + + alterTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + SQLite::rowid_t PackagesTable::Insert(SQLite::Connection& connection, const std::vector& values) + { + SQLite::Builder::StatementBuilder builder; + builder.InsertInto(s_PackagesTable_Table_Name).BeginColumns(); + + for (const NameValuePair& value : values) + { + builder.Column(value.Name); + } + + builder.EndColumns().BeginValues(); + + for (const NameValuePair& value : values) + { + builder.Value(value.Value); + } + + builder.EndValues(); + + builder.Execute(connection); + + return connection.GetLastInsertRowID(); + } + + bool PackagesTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PackagesTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) != 0); + } + + std::vector PackagesTable::GetAllRowIds(const SQLite::Connection& connection, std::string_view orderByColumn, size_t limit) + { + SQLite::Builder::StatementBuilder selectBuilder; + selectBuilder.Select(SQLite::RowIDName).From(s_PackagesTable_Table_Name).OrderBy(orderByColumn); + + if (limit) + { + selectBuilder.Limit(limit); + } + + SQLite::Statement select = selectBuilder.Prepare(connection); + + std::vector result; + while (select.Step()) + { + result.emplace_back(select.GetColumn(0)); + } + return result; + } + + uint64_t PackagesTable::GetCount(const SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PackagesTable_Table_Name); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return static_cast(countStatement.GetColumn(0)); + } + + int PackagesTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view valueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike) + { + using QCol = SQLite::Builder::QualifiedColumn; + + // Build a statement like: + // SELECT packages.rowid as p, packages.id as v from packages + // where packages.id = + builder.Select(). + Column(QCol(s_PackagesTable_Table_Name, SQLite::RowIDName)).As(primaryAlias). + Column(QCol(s_PackagesTable_Table_Name, valueName)).As(valueAlias). + From(s_PackagesTable_Table_Name).Where(QCol(s_PackagesTable_Table_Name, valueName)); + + int result = -1; + + if (useLike) + { + builder.Like(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + builder.Escape(SQLite::EscapeCharForLike); + } + else + { + builder.Equals(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + } + + return result; + } + + bool PackagesTable::IsEmpty(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PackagesTable_Table_Name); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) == 0); + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.h index 2de32ec0e8..cb879092b9 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/PackagesTable.h @@ -1,206 +1,206 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - // Info on the columns. - struct ColumnInfo - { - template - static constexpr ColumnInfo Create() - { - ColumnInfo result; - result.Name = Column::Name; - result.Type = Column::Type; - result.PrimaryKey = Column::PrimaryKey; - result.AllowNull = Column::AllowNull; - return result; - } - - std::string_view Name; - SQLite::Builder::Type Type = {}; - bool PrimaryKey = false; - bool AllowNull = false; - }; - - // Creates the table. - void PackagesTableCreate(SQLite::Connection& connection, std::initializer_list values); - - // Gets the requested values for the manifest with the given rowid. - SQLite::Statement PackagesTableGetValuesById_Statement( - const SQLite::Connection& connection, - SQLite::rowid_t id, - std::initializer_list columns, - bool stepAndVerify = true); - - // Prepares a statement to update the value of a single column for the manifest with the given rowid. - // The first bind value will be the value to set. - // The second bind value will be the manifest rowid to modify. - SQLite::Statement PackagesTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName); - - // Removes data that is no longer needed for an index that is to be published. - void PackagesTablePrepareForPackaging(SQLite::Connection& connection, std::initializer_list values); - - // Checks for embedded nulls in the database. - bool PackagesTableCheckConsistency(const SQLite::Connection& connection, std::initializer_list values, bool log); - } - - // A table in which each row represents a single package. - struct PackagesTable - { - // Get the table name. - static std::string_view TableName(); - - struct IdColumn - { - static constexpr std::string_view Name = "id"sv; - static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; - static constexpr bool PrimaryKey = true; - static constexpr bool AllowNull = false; - }; - - struct NameColumn - { - static constexpr std::string_view Name = "name"sv; - static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; - static constexpr bool PrimaryKey = false; - static constexpr bool AllowNull = false; - }; - - struct MonikerColumn - { - static constexpr std::string_view Name = "moniker"sv; - static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; - static constexpr bool PrimaryKey = false; - static constexpr bool AllowNull = true; - }; - - struct LatestVersionColumn - { - static constexpr std::string_view Name = "latest_version"sv; - static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; - static constexpr bool PrimaryKey = false; - static constexpr bool AllowNull = false; - }; - - struct ARPMinVersionColumn - { - static constexpr std::string_view Name = "arp_min_version"sv; - static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; - static constexpr bool PrimaryKey = false; - static constexpr bool AllowNull = true; - }; - - struct ARPMaxVersionColumn - { - static constexpr std::string_view Name = "arp_max_version"sv; - static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; - static constexpr bool PrimaryKey = false; - static constexpr bool AllowNull = true; - }; - - struct HashColumn - { - static constexpr std::string_view Name = "hash"sv; - static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Blob; - static constexpr bool PrimaryKey = false; - static constexpr bool AllowNull = true; - }; - - using ColumnInfo = details::ColumnInfo; - - // Creates the table. - template - static void Create(SQLite::Connection& connection) - { - details::PackagesTableCreate(connection, { ColumnInfo::Create()... }); - } - - // Drops the table. - static void Drop(SQLite::Connection& connection); - - // Determine if the table currently exists in the database. - static bool Exists(const SQLite::Connection& connection); - - // Alters the table, adding the column provided. - static void AddColumn(SQLite::Connection& connection, const ColumnInfo& value); - - // A string value for the package. - struct NameValuePair - { - std::string_view Name; - std::string Value; - }; - - // Insert the given values into the table. - static SQLite::rowid_t Insert(SQLite::Connection& connection, const std::vector& values); - - // Gets a value indicating whether the package with rowid exists. - static bool ExistsById(const SQLite::Connection& connection, SQLite::rowid_t rowid); - - // Gets all row ids from the table. - static std::vector GetAllRowIds(const SQLite::Connection& connection, std::string_view orderByColumn, size_t limit = 0); - - // Gets the total number of rows in the table. - static uint64_t GetCount(const SQLite::Connection& connection); - - // Gets the values requested for the package with the given rowid. - template - static auto GetValuesById(const SQLite::Connection& connection, SQLite::rowid_t rowid) - { - return details::PackagesTableGetValuesById_Statement(connection, rowid, { Columns::Name... }).GetRow::value_t...>(); - } - - // Gets the value requested for the package with the given rowid, if it exists. - template - static std::optional::value_t> GetValueById(const SQLite::Connection& connection, SQLite::rowid_t rowid) - { - auto statement = details::PackagesTableGetValuesById_Statement(connection, rowid, { Column::Name }, false); - if (statement.Step()) { return statement.GetColumn::value_t>(0); } - else { return std::nullopt; } - } - - // Builds the search select statement base on the given value. - // The return value is the bind index of the value to match against. - static int BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view valueName, std::string_view primaryAlias, std::string_view valueAlias, bool useLike); - - // Update the value of a single column for the package with the given rowid. - template - static void UpdateValueIdById(SQLite::Connection& connection, SQLite::rowid_t id, const typename SQLite::Builder::TypeInfo::value_t& value) - { - auto stmt = details::PackagesTableUpdateValueIdById_Statement(connection, Column::Name); - stmt.Bind(1, value); - stmt.Bind(2, id); - stmt.Execute(); - } - - // Removes data that is no longer needed for an index that is to be published. - template - static void PrepareForPackaging(SQLite::Connection& connection) - { - details::PackagesTablePrepareForPackaging(connection, { ColumnInfo::Create()... }); - } - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - template - static bool CheckConsistency(const SQLite::Connection& connection, bool log) - { - return details::PackagesTableCheckConsistency(connection, { Columns::Name... }, log); - } - - // Determines if the table is empty. - static bool IsEmpty(SQLite::Connection& connection); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + // Info on the columns. + struct ColumnInfo + { + template + static constexpr ColumnInfo Create() + { + ColumnInfo result; + result.Name = Column::Name; + result.Type = Column::Type; + result.PrimaryKey = Column::PrimaryKey; + result.AllowNull = Column::AllowNull; + return result; + } + + std::string_view Name; + SQLite::Builder::Type Type = {}; + bool PrimaryKey = false; + bool AllowNull = false; + }; + + // Creates the table. + void PackagesTableCreate(SQLite::Connection& connection, std::initializer_list values); + + // Gets the requested values for the manifest with the given rowid. + SQLite::Statement PackagesTableGetValuesById_Statement( + const SQLite::Connection& connection, + SQLite::rowid_t id, + std::initializer_list columns, + bool stepAndVerify = true); + + // Prepares a statement to update the value of a single column for the manifest with the given rowid. + // The first bind value will be the value to set. + // The second bind value will be the manifest rowid to modify. + SQLite::Statement PackagesTableUpdateValueIdById_Statement(SQLite::Connection& connection, std::string_view valueName); + + // Removes data that is no longer needed for an index that is to be published. + void PackagesTablePrepareForPackaging(SQLite::Connection& connection, std::initializer_list values); + + // Checks for embedded nulls in the database. + bool PackagesTableCheckConsistency(const SQLite::Connection& connection, std::initializer_list values, bool log); + } + + // A table in which each row represents a single package. + struct PackagesTable + { + // Get the table name. + static std::string_view TableName(); + + struct IdColumn + { + static constexpr std::string_view Name = "id"sv; + static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; + static constexpr bool PrimaryKey = true; + static constexpr bool AllowNull = false; + }; + + struct NameColumn + { + static constexpr std::string_view Name = "name"sv; + static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; + static constexpr bool PrimaryKey = false; + static constexpr bool AllowNull = false; + }; + + struct MonikerColumn + { + static constexpr std::string_view Name = "moniker"sv; + static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; + static constexpr bool PrimaryKey = false; + static constexpr bool AllowNull = true; + }; + + struct LatestVersionColumn + { + static constexpr std::string_view Name = "latest_version"sv; + static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; + static constexpr bool PrimaryKey = false; + static constexpr bool AllowNull = false; + }; + + struct ARPMinVersionColumn + { + static constexpr std::string_view Name = "arp_min_version"sv; + static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; + static constexpr bool PrimaryKey = false; + static constexpr bool AllowNull = true; + }; + + struct ARPMaxVersionColumn + { + static constexpr std::string_view Name = "arp_max_version"sv; + static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Text; + static constexpr bool PrimaryKey = false; + static constexpr bool AllowNull = true; + }; + + struct HashColumn + { + static constexpr std::string_view Name = "hash"sv; + static constexpr SQLite::Builder::Type Type = SQLite::Builder::Type::Blob; + static constexpr bool PrimaryKey = false; + static constexpr bool AllowNull = true; + }; + + using ColumnInfo = details::ColumnInfo; + + // Creates the table. + template + static void Create(SQLite::Connection& connection) + { + details::PackagesTableCreate(connection, { ColumnInfo::Create()... }); + } + + // Drops the table. + static void Drop(SQLite::Connection& connection); + + // Determine if the table currently exists in the database. + static bool Exists(const SQLite::Connection& connection); + + // Alters the table, adding the column provided. + static void AddColumn(SQLite::Connection& connection, const ColumnInfo& value); + + // A string value for the package. + struct NameValuePair + { + std::string_view Name; + std::string Value; + }; + + // Insert the given values into the table. + static SQLite::rowid_t Insert(SQLite::Connection& connection, const std::vector& values); + + // Gets a value indicating whether the package with rowid exists. + static bool ExistsById(const SQLite::Connection& connection, SQLite::rowid_t rowid); + + // Gets all row ids from the table. + static std::vector GetAllRowIds(const SQLite::Connection& connection, std::string_view orderByColumn, size_t limit = 0); + + // Gets the total number of rows in the table. + static uint64_t GetCount(const SQLite::Connection& connection); + + // Gets the values requested for the package with the given rowid. + template + static auto GetValuesById(const SQLite::Connection& connection, SQLite::rowid_t rowid) + { + return details::PackagesTableGetValuesById_Statement(connection, rowid, { Columns::Name... }).GetRow::value_t...>(); + } + + // Gets the value requested for the package with the given rowid, if it exists. + template + static std::optional::value_t> GetValueById(const SQLite::Connection& connection, SQLite::rowid_t rowid) + { + auto statement = details::PackagesTableGetValuesById_Statement(connection, rowid, { Column::Name }, false); + if (statement.Step()) { return statement.GetColumn::value_t>(0); } + else { return std::nullopt; } + } + + // Builds the search select statement base on the given value. + // The return value is the bind index of the value to match against. + static int BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view valueName, std::string_view primaryAlias, std::string_view valueAlias, bool useLike); + + // Update the value of a single column for the package with the given rowid. + template + static void UpdateValueIdById(SQLite::Connection& connection, SQLite::rowid_t id, const typename SQLite::Builder::TypeInfo::value_t& value) + { + auto stmt = details::PackagesTableUpdateValueIdById_Statement(connection, Column::Name); + stmt.Bind(1, value); + stmt.Bind(2, id); + stmt.Execute(); + } + + // Removes data that is no longer needed for an index that is to be published. + template + static void PrepareForPackaging(SQLite::Connection& connection) + { + details::PackagesTablePrepareForPackaging(connection, { ColumnInfo::Create()... }); + } + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + template + static bool CheckConsistency(const SQLite::Connection& connection, bool log) + { + return details::PackagesTableCheckConsistency(connection, { Columns::Name... }, log); + } + + // Determines if the table is empty. + static bool IsEmpty(SQLite::Connection& connection); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/ProductCodeTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/ProductCodeTable.h index db823c339d..bfe7310093 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/ProductCodeTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/ProductCodeTable.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct ProductCodeTableInfo - { - inline static constexpr std::string_view TableName() { return "productcodes2"sv; } - inline static constexpr std::string_view ValueName() { return "productcode"sv; } - }; - } - - using ProductCodeTable = SystemReferenceStringTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct ProductCodeTableInfo + { + inline static constexpr std::string_view TableName() { return "productcodes2"sv; } + inline static constexpr std::string_view ValueName() { return "productcode"sv; } + }; + } + + using ProductCodeTable = SystemReferenceStringTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable.h index dcf51fe998..53d14631d6 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable.h @@ -1,65 +1,65 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "Microsoft/Schema/ISQLiteIndex.h" -#include "Public/winget/RepositorySearch.h" - -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - // Table for holding temporary search results. - struct SearchResultsTable : public SQLite::TempTable - { - SearchResultsTable(const SQLite::Connection& connection); - - SearchResultsTable(const SearchResultsTable&) = delete; - SearchResultsTable& operator=(const SearchResultsTable&) = delete; - - SearchResultsTable(SearchResultsTable&&) = default; - SearchResultsTable& operator=(SearchResultsTable&&) = default; - - // Performs the requested search type on the requested field. - void SearchOnField(const PackageMatchFilter& filter); - - // Removes rows with package ids whose sort order is below the highest one. - void RemoveDuplicatePackageRows(); - - // Prepares the table for a filtering pass. - void PrepareToFilter(); - - // Performs the requested filter type on the requested field. - void FilterOnField(const PackageMatchFilter& filter); - - // Completes a filtering pass, removing filtered rows. - void CompleteFilter(); - - // Gets the results from the table. - ISQLiteIndex::SearchResult GetSearchResults(size_t limit = 0); - - protected: - // Builds the search statement for the specified field and match type. - std::vector BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const; - - virtual std::vector BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const; - - static bool MatchUsesLike(MatchType match); - void BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value); - - virtual void BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex); - - private: - const SQLite::Connection& m_connection; - int m_sortOrdinalValue = 0; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Microsoft/Schema/ISQLiteIndex.h" +#include "Public/winget/RepositorySearch.h" + +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + // Table for holding temporary search results. + struct SearchResultsTable : public SQLite::TempTable + { + SearchResultsTable(const SQLite::Connection& connection); + + SearchResultsTable(const SearchResultsTable&) = delete; + SearchResultsTable& operator=(const SearchResultsTable&) = delete; + + SearchResultsTable(SearchResultsTable&&) = default; + SearchResultsTable& operator=(SearchResultsTable&&) = default; + + // Performs the requested search type on the requested field. + void SearchOnField(const PackageMatchFilter& filter); + + // Removes rows with package ids whose sort order is below the highest one. + void RemoveDuplicatePackageRows(); + + // Prepares the table for a filtering pass. + void PrepareToFilter(); + + // Performs the requested filter type on the requested field. + void FilterOnField(const PackageMatchFilter& filter); + + // Completes a filtering pass, removing filtered rows. + void CompleteFilter(); + + // Gets the results from the table. + ISQLiteIndex::SearchResult GetSearchResults(size_t limit = 0); + + protected: + // Builds the search statement for the specified field and match type. + std::vector BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const; + + virtual std::vector BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const; + + static bool MatchUsesLike(MatchType match); + void BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value); + + virtual void BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex); + + private: + const SQLite::Connection& m_connection; + int m_sortOrdinalValue = 0; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp index 52dea8d7f0..ae0d43bbe3 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SearchResultsTable_2_0.cpp @@ -1,320 +1,320 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResultsTable.h" -#include - -#include "Microsoft/Schema/2_0/PackagesTable.h" -#include "Microsoft/Schema/2_0/TagsTable.h" -#include "Microsoft/Schema/2_0/CommandsTable.h" -#include "Microsoft/Schema/2_0/PackageFamilyNameTable.h" -#include "Microsoft/Schema/2_0/ProductCodeTable.h" -#include "Microsoft/Schema/2_0/UpgradeCodeTable.h" -#include "Microsoft/Schema/2_0/NormalizedPackageNameTable.h" -#include "Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace - { - using namespace std::string_literals; - using namespace std::string_view_literals; - - constexpr std::string_view s_SearchResultsTable_Package = "package"sv; - constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; - constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; - constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; - constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; - constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; - - constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; - - constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_PackageAlias = "p"sv; - constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; - } - - SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : - m_connection(connection) - { - using namespace SQLite::Builder; - - { - StatementBuilder builder; - builder.CreateTable(GetQualifiedName()).BeginColumns(); - - builder.Column(ColumnBuilder(s_SearchResultsTable_Package, Type::RowId).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); - builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); - - builder.EndColumns(); - - builder.Execute(m_connection); - } - - InitDropStatement(m_connection); - - { - SQLite::Builder::QualifiedTable index = GetQualifiedName(); - std::string indexName(index.Table); - indexName += s_SearchResultsTable_Index_Suffix; - index.Table = indexName; - - StatementBuilder builder; - builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Package); - - builder.Execute(m_connection); - } - } - - void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - int sortOrdinal = m_sortOrdinalValue++; - - // Create an insert statement to select values into the table as requested. - // The goal is a statement like this: - // INSERT INTO - // SELECT valueTable.p, , , valueTable.v, , FROM - // (SELECT packages.rowid as p, packages.id as v from packages where packages.id = ) AS valueTable - // Where the subselect is built by the owning table. - StatementBuilder builder; - builder.InsertInto(GetQualifiedName()).Select(). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_PackageAlias)). - Value(filter.Field). - Value(filter.Type). - Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). - Value(sortOrdinal). - Value(false). - From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::RemoveDuplicatePackageRows() - { - using namespace SQLite::Builder; - - // Create a delete statement to leave only one row with a given package. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // DELETE from where rowid not in ( - // SELECT rowid from ( - // SELECT rowid, min(sort) from group by package - // ) - // ) - StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). - Select(SQLite::RowIDName).From().BeginParenthetical(). - Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Package). - EndParenthetical(). - EndParenthetical(); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); - } - - void SearchResultsTable::PrepareToFilter() - { - // Reset all filter values to unselected - SQLite::Builder::StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(false); - - builder.Execute(m_connection); - } - - void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) - { - using namespace SQLite::Builder; - - // Create an update statement to mark rows that are found by the search. - // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. - // The goal is a statement like this: - // UPDATE set filter = 1 where package in ( - // SELECT p from ( - // SELECT packages.rowid as p, packages.id as v from packages where packages.id = - // ) - // ) - StatementBuilder builder; - builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(true).Where(s_SearchResultsTable_Package).In().BeginParenthetical(). - Select(s_SearchResultsTable_SubSelect_PackageAlias).From().BeginParenthetical(); - - // Add the field specific portion - std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); - - if (bindIndex.empty()) - { - AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); - return; - } - - builder.EndParenthetical().EndParenthetical(); - - SQLite::Statement statement = builder.Prepare(m_connection); - BindStatementForMatchType(statement, filter, bindIndex); - statement.Execute(); - AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); - } - - void SearchResultsTable::CompleteFilter() - { - // Delete all unselected values - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); - - builder.Execute(m_connection); - AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); - } - - ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) - { - using namespace SQLite::Builder; - using QCol = QualifiedColumn; - - // Select all of the results from the table; it is expected that RemoveDuplicatePackageRows has been called. - StatementBuilder builder; - builder.Select({ - s_SearchResultsTable_Package, - s_SearchResultsTable_MatchField, - s_SearchResultsTable_MatchType, - s_SearchResultsTable_MatchValue, - }). - From(GetQualifiedName()).OrderBy(s_SearchResultsTable_SortValue); - - SQLite::Statement select = builder.Prepare(m_connection); - - ISQLiteIndex::SearchResult result; - while (select.Step()) - { - if (limit && result.Matches.size() >= limit) - { - break; - } - - result.Matches.emplace_back(select.GetColumn(0), - PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); - } - - result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); - - return result; - } - - std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const - { - return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_PackageAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); - } - - std::vector SearchResultsTable::BuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - PackageMatchField field, - std::string_view manifestAlias, - std::string_view valueAlias, - bool useLike) const - { - std::vector result; - - switch (field) - { - case PackageMatchField::Id: - result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::IdColumn::Name, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Name: - result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::NameColumn::Name, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Moniker: - result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::MonikerColumn::Name, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Tag: - result.push_back(TagsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::Command: - result.push_back(CommandsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::PackageFamilyName: - result.push_back(PackageFamilyNameTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::ProductCode: - result.push_back(ProductCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::UpgradeCode: - result.push_back(UpgradeCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); - break; - case PackageMatchField::NormalizedNameAndPublisher: - result = NormalizedPackageNameTable::BuildPairedSearchStatement(builder, manifestAlias, valueAlias, useLike); - break; - } - - return result; - } - - bool SearchResultsTable::MatchUsesLike(MatchType match) - { - return (match != MatchType::Exact); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) - { - std::string valueToUse; - - if (MatchUsesLike(match)) - { - valueToUse = SQLite::EscapeStringForLike(value); - } - else - { - valueToUse = value; - } - - switch (match) - { - case AppInstaller::Repository::MatchType::StartsWith: - valueToUse += '%'; - break; - case AppInstaller::Repository::MatchType::Substring: - valueToUse = "%"s + valueToUse + '%'; - break; - default: - // No changes required for others. - break; - } - - statement.Bind(bindIndex, valueToUse); - } - - void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) - { - // TODO: Implement these more complex match types - if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) - { - AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); - return; - } - - BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); - - if (filter.Field == PackageMatchField::NormalizedNameAndPublisher) - { - BindStatementForMatchType(statement, filter.Type, bindIndex[1], filter.Additional.value()); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResultsTable.h" +#include + +#include "Microsoft/Schema/2_0/PackagesTable.h" +#include "Microsoft/Schema/2_0/TagsTable.h" +#include "Microsoft/Schema/2_0/CommandsTable.h" +#include "Microsoft/Schema/2_0/PackageFamilyNameTable.h" +#include "Microsoft/Schema/2_0/ProductCodeTable.h" +#include "Microsoft/Schema/2_0/UpgradeCodeTable.h" +#include "Microsoft/Schema/2_0/NormalizedPackageNameTable.h" +#include "Microsoft/Schema/2_0/NormalizedPackagePublisherTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace + { + using namespace std::string_literals; + using namespace std::string_view_literals; + + constexpr std::string_view s_SearchResultsTable_Package = "package"sv; + constexpr std::string_view s_SearchResultsTable_MatchField = "field"sv; + constexpr std::string_view s_SearchResultsTable_MatchType = "match"sv; + constexpr std::string_view s_SearchResultsTable_MatchValue = "value"sv; + constexpr std::string_view s_SearchResultsTable_SortValue = "sort"sv; + constexpr std::string_view s_SearchResultsTable_Filter = "filter"sv; + + constexpr std::string_view s_SearchResultsTable_Index_Suffix = "_i_m"sv; + + constexpr std::string_view s_SearchResultsTable_SubSelect_TableAlias = "valueTable"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_PackageAlias = "p"sv; + constexpr std::string_view s_SearchResultsTable_SubSelect_ValueAlias = "v"sv; + } + + SearchResultsTable::SearchResultsTable(const SQLite::Connection& connection) : + m_connection(connection) + { + using namespace SQLite::Builder; + + { + StatementBuilder builder; + builder.CreateTable(GetQualifiedName()).BeginColumns(); + + builder.Column(ColumnBuilder(s_SearchResultsTable_Package, Type::RowId).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchField, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchType, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_MatchValue, Type::Text).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_SortValue, Type::Int).NotNull()); + builder.Column(ColumnBuilder(s_SearchResultsTable_Filter, Type::Bool).NotNull()); + + builder.EndColumns(); + + builder.Execute(m_connection); + } + + InitDropStatement(m_connection); + + { + SQLite::Builder::QualifiedTable index = GetQualifiedName(); + std::string indexName(index.Table); + indexName += s_SearchResultsTable_Index_Suffix; + index.Table = indexName; + + StatementBuilder builder; + builder.CreateIndex(indexName).On(GetQualifiedName().Table).Columns(s_SearchResultsTable_Package); + + builder.Execute(m_connection); + } + } + + void SearchResultsTable::SearchOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + int sortOrdinal = m_sortOrdinalValue++; + + // Create an insert statement to select values into the table as requested. + // The goal is a statement like this: + // INSERT INTO + // SELECT valueTable.p, , , valueTable.v, , FROM + // (SELECT packages.rowid as p, packages.id as v from packages where packages.id = ) AS valueTable + // Where the subselect is built by the owning table. + StatementBuilder builder; + builder.InsertInto(GetQualifiedName()).Select(). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_PackageAlias)). + Value(filter.Field). + Value(filter.Type). + Column(QualifiedColumn(s_SearchResultsTable_SubSelect_TableAlias, s_SearchResultsTable_SubSelect_ValueAlias)). + Value(sortOrdinal). + Value(false). + From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().As(s_SearchResultsTable_SubSelect_TableAlias); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Search found " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::RemoveDuplicatePackageRows() + { + using namespace SQLite::Builder; + + // Create a delete statement to leave only one row with a given package. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // DELETE from where rowid not in ( + // SELECT rowid from ( + // SELECT rowid, min(sort) from group by package + // ) + // ) + StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(SQLite::RowIDName).Not().In().BeginParenthetical(). + Select(SQLite::RowIDName).From().BeginParenthetical(). + Select().Column(SQLite::RowIDName).Column(Aggregate::Min, s_SearchResultsTable_SortValue).From(GetQualifiedName()).GroupBy(s_SearchResultsTable_Package). + EndParenthetical(). + EndParenthetical(); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Removed " << m_connection.GetChanges() << " duplicate rows"); + } + + void SearchResultsTable::PrepareToFilter() + { + // Reset all filter values to unselected + SQLite::Builder::StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(false); + + builder.Execute(m_connection); + } + + void SearchResultsTable::FilterOnField(const PackageMatchFilter& filter) + { + using namespace SQLite::Builder; + + // Create an update statement to mark rows that are found by the search. + // This will arbitrarily choose one of the rows if multiple have the same lowest sort order. + // The goal is a statement like this: + // UPDATE set filter = 1 where package in ( + // SELECT p from ( + // SELECT packages.rowid as p, packages.id as v from packages where packages.id = + // ) + // ) + StatementBuilder builder; + builder.Update(GetQualifiedName()).Set().Column(s_SearchResultsTable_Filter).Equals(true).Where(s_SearchResultsTable_Package).In().BeginParenthetical(). + Select(s_SearchResultsTable_SubSelect_PackageAlias).From().BeginParenthetical(); + + // Add the field specific portion + std::vector bindIndex = BuildSearchStatement(builder, filter.Field, filter.Type); + + if (bindIndex.empty()) + { + AICLI_LOG(Repo, Verbose, << "PackageMatchField not supported in this version: " << ToString(filter.Field)); + return; + } + + builder.EndParenthetical().EndParenthetical(); + + SQLite::Statement statement = builder.Prepare(m_connection); + BindStatementForMatchType(statement, filter, bindIndex); + statement.Execute(); + AICLI_LOG(SQL, Verbose, << "Filter kept " << m_connection.GetChanges() << " rows"); + } + + void SearchResultsTable::CompleteFilter() + { + // Delete all unselected values + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(GetQualifiedName()).Where(s_SearchResultsTable_Filter).Equals(false); + + builder.Execute(m_connection); + AICLI_LOG(SQL, Verbose, << "Filter deleted " << m_connection.GetChanges() << " rows"); + } + + ISQLiteIndex::SearchResult SearchResultsTable::GetSearchResults(size_t limit) + { + using namespace SQLite::Builder; + using QCol = QualifiedColumn; + + // Select all of the results from the table; it is expected that RemoveDuplicatePackageRows has been called. + StatementBuilder builder; + builder.Select({ + s_SearchResultsTable_Package, + s_SearchResultsTable_MatchField, + s_SearchResultsTable_MatchType, + s_SearchResultsTable_MatchValue, + }). + From(GetQualifiedName()).OrderBy(s_SearchResultsTable_SortValue); + + SQLite::Statement select = builder.Prepare(m_connection); + + ISQLiteIndex::SearchResult result; + while (select.Step()) + { + if (limit && result.Matches.size() >= limit) + { + break; + } + + result.Matches.emplace_back(select.GetColumn(0), + PackageMatchFilter(select.GetColumn(1), select.GetColumn(2), select.GetColumn(3))); + } + + result.Truncated = (select.GetState() != SQLite::Statement::State::Completed); + + return result; + } + + std::vector SearchResultsTable::BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, PackageMatchField field, MatchType match) const + { + return BuildSearchStatement(builder, field, s_SearchResultsTable_SubSelect_PackageAlias, s_SearchResultsTable_SubSelect_ValueAlias, MatchUsesLike(match)); + } + + std::vector SearchResultsTable::BuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + PackageMatchField field, + std::string_view manifestAlias, + std::string_view valueAlias, + bool useLike) const + { + std::vector result; + + switch (field) + { + case PackageMatchField::Id: + result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::IdColumn::Name, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Name: + result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::NameColumn::Name, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Moniker: + result.push_back(PackagesTable::BuildSearchStatement(builder, PackagesTable::MonikerColumn::Name, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Tag: + result.push_back(TagsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::Command: + result.push_back(CommandsTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::PackageFamilyName: + result.push_back(PackageFamilyNameTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::ProductCode: + result.push_back(ProductCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::UpgradeCode: + result.push_back(UpgradeCodeTable::BuildSearchStatement(builder, manifestAlias, valueAlias, useLike)); + break; + case PackageMatchField::NormalizedNameAndPublisher: + result = NormalizedPackageNameTable::BuildPairedSearchStatement(builder, manifestAlias, valueAlias, useLike); + break; + } + + return result; + } + + bool SearchResultsTable::MatchUsesLike(MatchType match) + { + return (match != MatchType::Exact); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, MatchType match, int bindIndex, std::string_view value) + { + std::string valueToUse; + + if (MatchUsesLike(match)) + { + valueToUse = SQLite::EscapeStringForLike(value); + } + else + { + valueToUse = value; + } + + switch (match) + { + case AppInstaller::Repository::MatchType::StartsWith: + valueToUse += '%'; + break; + case AppInstaller::Repository::MatchType::Substring: + valueToUse = "%"s + valueToUse + '%'; + break; + default: + // No changes required for others. + break; + } + + statement.Bind(bindIndex, valueToUse); + } + + void SearchResultsTable::BindStatementForMatchType(SQLite::Statement& statement, const PackageMatchFilter& filter, const std::vector& bindIndex) + { + // TODO: Implement these more complex match types + if (filter.Type == MatchType::Wildcard || filter.Type == MatchType::Fuzzy || filter.Type == MatchType::FuzzySubstring) + { + AICLI_LOG(Repo, Verbose, << "Specific match type not implemented, skipping: " << ToString(filter.Type)); + return; + } + + BindStatementForMatchType(statement, filter.Type, bindIndex[0], filter.Value); + + if (filter.Field == PackageMatchField::NormalizedNameAndPublisher) + { + BindStatementForMatchType(statement, filter.Type, bindIndex[1], filter.Additional.value()); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.cpp index 59b9d9156b..c44603a9d7 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.cpp @@ -1,266 +1,266 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" -#include "Microsoft/Schema/2_0/PackagesTable.h" -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using PrimaryTable = PackagesTable; - - using namespace std::string_view_literals; - static constexpr std::string_view s_SystemReferenceStringTable_PrimaryName = "package"sv; - - std::string_view SystemReferenceStringTableGetPrimaryColumnName() - { - return s_SystemReferenceStringTable_PrimaryName; - } - - void SystemReferenceStringTableCreate(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(tableName).Columns({ - ColumnBuilder(valueName, Type::Text).NotNull(), - ColumnBuilder(s_SystemReferenceStringTable_PrimaryName, Type::RowId).NotNull(), - PrimaryKeyBuilder({ valueName, s_SystemReferenceStringTable_PrimaryName }) - }).WithoutRowID(); - - createTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - void SystemReferenceStringTableDrop(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder dropTableBuilder; - dropTableBuilder.DropTable(tableName); - - dropTableBuilder.Execute(connection); - } - - std::vector SystemReferenceStringTableGetValuesByPrimaryId( - const SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - SQLite::rowid_t primaryId) - { - std::vector result; - - SQLite::Builder::StatementBuilder builder; - builder.Select(valueName). - From(tableName).Where(s_SystemReferenceStringTable_PrimaryName).Equals(primaryId); - - SQLite::Statement statement = builder.Prepare(connection); - - while (statement.Step()) - { - result.emplace_back(statement.GetColumn(0)); - } - - return result; - } - - void SystemReferenceStringTableEnsureExists( - SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - const std::vector& values, - SQLite::rowid_t primaryId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensure_v2_0"); - - SQLite::Builder::StatementBuilder builder; - - builder.InsertOrIgnore(tableName). - Columns({ valueName, s_SystemReferenceStringTable_PrimaryName }).Values(SQLite::Builder::Unbound, primaryId); - - SQLite::Statement insertStatement = builder.Prepare(connection); - - for (const std::string& value : values) - { - // Second, insert into the mapping table - insertStatement.Reset(); - insertStatement.Bind(1, value); - - insertStatement.Execute(); - } - - savepoint.Commit(); - } - - bool SystemReferenceStringTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) - { - using QCol = SQLite::Builder::QualifiedColumn; - - bool result = true; - - { - // Build a select statement to find rows containing references to primaries with nonexistent rowids - // Such as: - // Select data.data, data.primary from data left outer join primary on data.primary = primary.rowid where primary.id is null - - SQLite::Builder::StatementBuilder builder; - builder. - Select({ QCol(tableName, valueName), QCol(tableName, s_SystemReferenceStringTable_PrimaryName) }). - From(tableName). - LeftOuterJoin(details::PrimaryTable::TableName()).On(QCol(tableName, s_SystemReferenceStringTable_PrimaryName), QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)). - Where(QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)).IsNull(); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << " [" << select.GetColumn(0) << - ", " << select.GetColumn(1) << "] refers to invalid " << details::PrimaryTable::TableName()); - } - } - - if (!result && !log) - { - return result; - } - - // Build a select statement to find values that contain an embedded null character - // Such as: - // Select count(*) from table where instr(value,char(0))>0 - SQLite::Builder::StatementBuilder builder; - builder. - Select({ valueName, s_SystemReferenceStringTable_PrimaryName }). - From(tableName). - WhereValueContainsEmbeddedNullCharacter(valueName); - - SQLite::Statement select = builder.Prepare(connection); - - while (select.Step()) - { - result = false; - - if (!log) - { - break; - } - - AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] for primary [" << select.GetColumn(1) << "] contains an embedded null character and starts with [" << select.GetColumn(0) << "]"); - } - - return result; - } - - bool SystemReferenceStringTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) - { - SQLite::Builder::StatementBuilder countBuilder; - countBuilder.Select(SQLite::Builder::RowCount).From(tableName); - - SQLite::Statement countStatement = countBuilder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return countStatement.GetColumn(0) == 0; - } - - int SystemReferenceStringTableBuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view tableName, - std::string_view valueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike) - { - using QCol = SQLite::Builder::QualifiedColumn; - - // Build a statement like: - // SELECT table.package as p, table.value as v from table - // where table.value = - builder.Select(). - Column(s_SystemReferenceStringTable_PrimaryName).As(primaryAlias). - Column(valueName).As(valueAlias). - From(tableName). - Where(valueName); - - int result = -1; - - if (useLike) - { - builder.Like(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - builder.Escape(SQLite::EscapeCharForLike); - } - else - { - builder.Equals(SQLite::Builder::Unbound); - result = builder.GetLastBindIndex(); - } - - return result; - } - - std::vector SystemReferenceStringTableBuildPairedSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view tableName, - std::string_view valueName, - std::string_view pairedTableName, - std::string_view pairedValueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike) - { - using QCol = SQLite::Builder::QualifiedColumn; - - // Build a statement like: - // SELECT table.package as p, '' as v from table - // join paired on table.package = paired.package - // where table.value = and paired.pairedValue = - builder.Select(). - Column(QCol(tableName, s_SystemReferenceStringTable_PrimaryName)).As(primaryAlias). - Value(std::string_view{}).As(valueAlias). - From(tableName). - Join(pairedTableName).On(QCol(tableName, s_SystemReferenceStringTable_PrimaryName), QCol(pairedTableName, s_SystemReferenceStringTable_PrimaryName)). - Where(QCol(tableName, valueName)); - - std::vector result; - - if (useLike) - { - builder.Like(SQLite::Builder::Unbound); - result.push_back(builder.GetLastBindIndex()); - builder.Escape(SQLite::EscapeCharForLike); - } - else - { - builder.Equals(SQLite::Builder::Unbound); - result.push_back(builder.GetLastBindIndex()); - } - - builder.And(QCol(pairedTableName, pairedValueName)); - - if (useLike) - { - builder.Like(SQLite::Builder::Unbound); - result.push_back(builder.GetLastBindIndex()); - builder.Escape(SQLite::EscapeCharForLike); - } - else - { - builder.Equals(SQLite::Builder::Unbound); - result.push_back(builder.GetLastBindIndex()); - } - - return result; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" +#include "Microsoft/Schema/2_0/PackagesTable.h" +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using PrimaryTable = PackagesTable; + + using namespace std::string_view_literals; + static constexpr std::string_view s_SystemReferenceStringTable_PrimaryName = "package"sv; + + std::string_view SystemReferenceStringTableGetPrimaryColumnName() + { + return s_SystemReferenceStringTable_PrimaryName; + } + + void SystemReferenceStringTableCreate(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_create_v2_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(tableName).Columns({ + ColumnBuilder(valueName, Type::Text).NotNull(), + ColumnBuilder(s_SystemReferenceStringTable_PrimaryName, Type::RowId).NotNull(), + PrimaryKeyBuilder({ valueName, s_SystemReferenceStringTable_PrimaryName }) + }).WithoutRowID(); + + createTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + void SystemReferenceStringTableDrop(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder dropTableBuilder; + dropTableBuilder.DropTable(tableName); + + dropTableBuilder.Execute(connection); + } + + std::vector SystemReferenceStringTableGetValuesByPrimaryId( + const SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + SQLite::rowid_t primaryId) + { + std::vector result; + + SQLite::Builder::StatementBuilder builder; + builder.Select(valueName). + From(tableName).Where(s_SystemReferenceStringTable_PrimaryName).Equals(primaryId); + + SQLite::Statement statement = builder.Prepare(connection); + + while (statement.Step()) + { + result.emplace_back(statement.GetColumn(0)); + } + + return result; + } + + void SystemReferenceStringTableEnsureExists( + SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + const std::vector& values, + SQLite::rowid_t primaryId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, std::string{ tableName } + "_ensure_v2_0"); + + SQLite::Builder::StatementBuilder builder; + + builder.InsertOrIgnore(tableName). + Columns({ valueName, s_SystemReferenceStringTable_PrimaryName }).Values(SQLite::Builder::Unbound, primaryId); + + SQLite::Statement insertStatement = builder.Prepare(connection); + + for (const std::string& value : values) + { + // Second, insert into the mapping table + insertStatement.Reset(); + insertStatement.Bind(1, value); + + insertStatement.Execute(); + } + + savepoint.Commit(); + } + + bool SystemReferenceStringTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log) + { + using QCol = SQLite::Builder::QualifiedColumn; + + bool result = true; + + { + // Build a select statement to find rows containing references to primaries with nonexistent rowids + // Such as: + // Select data.data, data.primary from data left outer join primary on data.primary = primary.rowid where primary.id is null + + SQLite::Builder::StatementBuilder builder; + builder. + Select({ QCol(tableName, valueName), QCol(tableName, s_SystemReferenceStringTable_PrimaryName) }). + From(tableName). + LeftOuterJoin(details::PrimaryTable::TableName()).On(QCol(tableName, s_SystemReferenceStringTable_PrimaryName), QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)). + Where(QCol(details::PrimaryTable::TableName(), SQLite::RowIDName)).IsNull(); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] " << tableName << " [" << select.GetColumn(0) << + ", " << select.GetColumn(1) << "] refers to invalid " << details::PrimaryTable::TableName()); + } + } + + if (!result && !log) + { + return result; + } + + // Build a select statement to find values that contain an embedded null character + // Such as: + // Select count(*) from table where instr(value,char(0))>0 + SQLite::Builder::StatementBuilder builder; + builder. + Select({ valueName, s_SystemReferenceStringTable_PrimaryName }). + From(tableName). + WhereValueContainsEmbeddedNullCharacter(valueName); + + SQLite::Statement select = builder.Prepare(connection); + + while (select.Step()) + { + result = false; + + if (!log) + { + break; + } + + AICLI_LOG(Repo, Info, << " [INVALID] value in table [" << tableName << "] for primary [" << select.GetColumn(1) << "] contains an embedded null character and starts with [" << select.GetColumn(0) << "]"); + } + + return result; + } + + bool SystemReferenceStringTableIsEmpty(SQLite::Connection& connection, std::string_view tableName) + { + SQLite::Builder::StatementBuilder countBuilder; + countBuilder.Select(SQLite::Builder::RowCount).From(tableName); + + SQLite::Statement countStatement = countBuilder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return countStatement.GetColumn(0) == 0; + } + + int SystemReferenceStringTableBuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view tableName, + std::string_view valueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike) + { + using QCol = SQLite::Builder::QualifiedColumn; + + // Build a statement like: + // SELECT table.package as p, table.value as v from table + // where table.value = + builder.Select(). + Column(s_SystemReferenceStringTable_PrimaryName).As(primaryAlias). + Column(valueName).As(valueAlias). + From(tableName). + Where(valueName); + + int result = -1; + + if (useLike) + { + builder.Like(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + builder.Escape(SQLite::EscapeCharForLike); + } + else + { + builder.Equals(SQLite::Builder::Unbound); + result = builder.GetLastBindIndex(); + } + + return result; + } + + std::vector SystemReferenceStringTableBuildPairedSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view tableName, + std::string_view valueName, + std::string_view pairedTableName, + std::string_view pairedValueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike) + { + using QCol = SQLite::Builder::QualifiedColumn; + + // Build a statement like: + // SELECT table.package as p, '' as v from table + // join paired on table.package = paired.package + // where table.value = and paired.pairedValue = + builder.Select(). + Column(QCol(tableName, s_SystemReferenceStringTable_PrimaryName)).As(primaryAlias). + Value(std::string_view{}).As(valueAlias). + From(tableName). + Join(pairedTableName).On(QCol(tableName, s_SystemReferenceStringTable_PrimaryName), QCol(pairedTableName, s_SystemReferenceStringTable_PrimaryName)). + Where(QCol(tableName, valueName)); + + std::vector result; + + if (useLike) + { + builder.Like(SQLite::Builder::Unbound); + result.push_back(builder.GetLastBindIndex()); + builder.Escape(SQLite::EscapeCharForLike); + } + else + { + builder.Equals(SQLite::Builder::Unbound); + result.push_back(builder.GetLastBindIndex()); + } + + builder.And(QCol(pairedTableName, pairedValueName)); + + if (useLike) + { + builder.Like(SQLite::Builder::Unbound); + result.push_back(builder.GetLastBindIndex()); + builder.Escape(SQLite::EscapeCharForLike); + } + else + { + builder.Equals(SQLite::Builder::Unbound); + result.push_back(builder.GetLastBindIndex()); + } + + return result; + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.h index b4036d541e..fa9e55d5b9 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/SystemReferenceStringTable.h @@ -1,138 +1,138 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - // Returns the primary column name. - std::string_view SystemReferenceStringTableGetPrimaryColumnName(); - - // Create the table. - void SystemReferenceStringTableCreate(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName); - - // Drops the table. - void SystemReferenceStringTableDrop(SQLite::Connection& connection, std::string_view tableName); - - // Gets all values associated with the given primary id. - std::vector SystemReferenceStringTableGetValuesByPrimaryId( - const SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - SQLite::rowid_t primaryId); - - // Ensures that the value exists and inserts mapping entries. - void SystemReferenceStringTableEnsureExists( - SQLite::Connection& connection, - std::string_view tableName, - std::string_view valueName, - const std::vector& values, - SQLite::rowid_t primaryId); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - bool SystemReferenceStringTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); - - // Determines if the table is empty. - bool SystemReferenceStringTableIsEmpty(SQLite::Connection& connection, std::string_view tableName); - - // Builds the search select statement base on the given value. - // The return value is the bind index of the value to match against. - int SystemReferenceStringTableBuildSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view tableName, - std::string_view valueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike); - - // Builds the search select statement base on the given value. - // The return value is the bind index of the value to match against. - std::vector SystemReferenceStringTableBuildPairedSearchStatement( - SQLite::Builder::StatementBuilder& builder, - std::string_view tableName, - std::string_view valueName, - std::string_view pairedTableName, - std::string_view pairedValueName, - std::string_view primaryAlias, - std::string_view valueAlias, - bool useLike); - } - - // A table that represents a value that is 1:N with a primary entry. - template - struct SystemReferenceStringTable - { - // The name of the table. - static constexpr std::string_view TableName() - { - return TableInfo::TableName(); - } - - // The value name of the table. - static constexpr std::string_view ValueName() - { - return TableInfo::ValueName(); - } - - // Creates the table. - static void Create(SQLite::Connection& connection) - { - details::SystemReferenceStringTableCreate(connection, TableInfo::TableName(), TableInfo::ValueName()); - } - - // Drops the table. - static void Drop(SQLite::Connection& connection) - { - details::SystemReferenceStringTableDrop(connection, TableInfo::TableName()); - } - - // Gets all values associated with the given primary id. - static std::vector GetValuesByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId) - { - return details::SystemReferenceStringTableGetValuesByPrimaryId(connection, TableInfo::TableName(), TableInfo::ValueName(), primaryId); - } - - // Ensures that all values exist in the data table, and inserts into the mapping table for the given primary id. - static void EnsureExists(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t primaryId) - { - details::SystemReferenceStringTableEnsureExists(connection, TableInfo::TableName(), TableInfo::ValueName(), values, primaryId); - } - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - static bool CheckConsistency(const SQLite::Connection& connection, bool log) - { - return details::SystemReferenceStringTableCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); - } - - // Determines if the table is empty. - static bool IsEmpty(SQLite::Connection& connection) - { - return details::SystemReferenceStringTableIsEmpty(connection, TableInfo::TableName()); - } - - // Builds the search select statement base on the given value. - // The return value is the bind index of the value to match against. - static int BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view primaryAlias, std::string_view valueAlias, bool useLike) - { - return details::SystemReferenceStringTableBuildSearchStatement(builder, TableInfo::TableName(), TableInfo::ValueName(), primaryAlias, valueAlias, useLike); - } - - // Builds the search select statement base on the given value. - // The return value is the bind index of the value to match against. - template - static std::vector BuildPairedSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view primaryAlias, std::string_view valueAlias, bool useLike) - { - return details::SystemReferenceStringTableBuildPairedSearchStatement(builder, TableInfo::TableName(), TableInfo::ValueName(), PairedTable::TableName(), PairedTable::ValueName(), primaryAlias, valueAlias, useLike); - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + // Returns the primary column name. + std::string_view SystemReferenceStringTableGetPrimaryColumnName(); + + // Create the table. + void SystemReferenceStringTableCreate(SQLite::Connection& connection, std::string_view tableName, std::string_view valueName); + + // Drops the table. + void SystemReferenceStringTableDrop(SQLite::Connection& connection, std::string_view tableName); + + // Gets all values associated with the given primary id. + std::vector SystemReferenceStringTableGetValuesByPrimaryId( + const SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + SQLite::rowid_t primaryId); + + // Ensures that the value exists and inserts mapping entries. + void SystemReferenceStringTableEnsureExists( + SQLite::Connection& connection, + std::string_view tableName, + std::string_view valueName, + const std::vector& values, + SQLite::rowid_t primaryId); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + bool SystemReferenceStringTableCheckConsistency(const SQLite::Connection& connection, std::string_view tableName, std::string_view valueName, bool log); + + // Determines if the table is empty. + bool SystemReferenceStringTableIsEmpty(SQLite::Connection& connection, std::string_view tableName); + + // Builds the search select statement base on the given value. + // The return value is the bind index of the value to match against. + int SystemReferenceStringTableBuildSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view tableName, + std::string_view valueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike); + + // Builds the search select statement base on the given value. + // The return value is the bind index of the value to match against. + std::vector SystemReferenceStringTableBuildPairedSearchStatement( + SQLite::Builder::StatementBuilder& builder, + std::string_view tableName, + std::string_view valueName, + std::string_view pairedTableName, + std::string_view pairedValueName, + std::string_view primaryAlias, + std::string_view valueAlias, + bool useLike); + } + + // A table that represents a value that is 1:N with a primary entry. + template + struct SystemReferenceStringTable + { + // The name of the table. + static constexpr std::string_view TableName() + { + return TableInfo::TableName(); + } + + // The value name of the table. + static constexpr std::string_view ValueName() + { + return TableInfo::ValueName(); + } + + // Creates the table. + static void Create(SQLite::Connection& connection) + { + details::SystemReferenceStringTableCreate(connection, TableInfo::TableName(), TableInfo::ValueName()); + } + + // Drops the table. + static void Drop(SQLite::Connection& connection) + { + details::SystemReferenceStringTableDrop(connection, TableInfo::TableName()); + } + + // Gets all values associated with the given primary id. + static std::vector GetValuesByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId) + { + return details::SystemReferenceStringTableGetValuesByPrimaryId(connection, TableInfo::TableName(), TableInfo::ValueName(), primaryId); + } + + // Ensures that all values exist in the data table, and inserts into the mapping table for the given primary id. + static void EnsureExists(SQLite::Connection& connection, const std::vector& values, SQLite::rowid_t primaryId) + { + details::SystemReferenceStringTableEnsureExists(connection, TableInfo::TableName(), TableInfo::ValueName(), values, primaryId); + } + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + static bool CheckConsistency(const SQLite::Connection& connection, bool log) + { + return details::SystemReferenceStringTableCheckConsistency(connection, TableInfo::TableName(), TableInfo::ValueName(), log); + } + + // Determines if the table is empty. + static bool IsEmpty(SQLite::Connection& connection) + { + return details::SystemReferenceStringTableIsEmpty(connection, TableInfo::TableName()); + } + + // Builds the search select statement base on the given value. + // The return value is the bind index of the value to match against. + static int BuildSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view primaryAlias, std::string_view valueAlias, bool useLike) + { + return details::SystemReferenceStringTableBuildSearchStatement(builder, TableInfo::TableName(), TableInfo::ValueName(), primaryAlias, valueAlias, useLike); + } + + // Builds the search select statement base on the given value. + // The return value is the bind index of the value to match against. + template + static std::vector BuildPairedSearchStatement(SQLite::Builder::StatementBuilder& builder, std::string_view primaryAlias, std::string_view valueAlias, bool useLike) + { + return details::SystemReferenceStringTableBuildPairedSearchStatement(builder, TableInfo::TableName(), TableInfo::ValueName(), PairedTable::TableName(), PairedTable::ValueName(), primaryAlias, valueAlias, useLike); + } + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/TagsTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/TagsTable.h index ecb38e9510..1d77f0e5a3 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/TagsTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/TagsTable.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct TagsTableInfo - { - inline static constexpr std::string_view TableName() { return "tags2"sv; } - inline static constexpr std::string_view ValueName() { return "tag"sv; } - }; - } - - using TagsTable = OneToManyTableWithMap; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/2_0/OneToManyTableWithMap.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct TagsTableInfo + { + inline static constexpr std::string_view TableName() { return "tags2"sv; } + inline static constexpr std::string_view ValueName() { return "tag"sv; } + }; + } + + using TagsTable = OneToManyTableWithMap; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/UpgradeCodeTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/UpgradeCodeTable.h index a4b38eb096..6092a0f64c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/UpgradeCodeTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/2_0/UpgradeCodeTable.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" - - -namespace AppInstaller::Repository::Microsoft::Schema::V2_0 -{ - namespace details - { - using namespace std::string_view_literals; - - struct UpgradeCodeTableInfo - { - inline static constexpr std::string_view TableName() { return "upgradecodes2"sv; } - inline static constexpr std::string_view ValueName() { return "upgradecode"sv; } - }; - } - - using UpgradeCodeTable = SystemReferenceStringTable; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/2_0/SystemReferenceStringTable.h" + + +namespace AppInstaller::Repository::Microsoft::Schema::V2_0 +{ + namespace details + { + using namespace std::string_view_literals; + + struct UpgradeCodeTableInfo + { + inline static constexpr std::string_view TableName() { return "upgradecodes2"sv; } + inline static constexpr std::string_view ValueName() { return "upgradecode"sv; } + }; + } + + using UpgradeCodeTable = SystemReferenceStringTable; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h index 8eba0be3d6..2daced6a3e 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPinningIndex.h @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "winget/Pin.h" - -namespace AppInstaller::Repository::Microsoft::Schema -{ - struct IPinningIndex - { - virtual ~IPinningIndex() = default; - - // Gets the schema version that this index interface is built for. - virtual SQLite::Version GetVersion() const = 0; - - // Creates all of the version dependent tables within the database. - virtual void CreateTables(SQLite::Connection& connection) = 0; - - // Version 1.0 - // Adds a pin to the index. - virtual SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; - - // Updates an existing pin in the index. - // The return value indicates whether the index was modified by the function. - virtual std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; - - // Removes a pin from the index. - virtual SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; - - // Returns the current pin for a given package if it exists. - virtual std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; - - // Returns a vector containing all the existing pins. - virtual std::vector GetAllPins(SQLite::Connection& connection) = 0; - - // Removes all the pins from a given source, or from all sources if none is specified. - // Returns a value indicating whether any pin was deleted. - virtual bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) = 0; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "winget/Pin.h" + +namespace AppInstaller::Repository::Microsoft::Schema +{ + struct IPinningIndex + { + virtual ~IPinningIndex() = default; + + // Gets the schema version that this index interface is built for. + virtual SQLite::Version GetVersion() const = 0; + + // Creates all of the version dependent tables within the database. + virtual void CreateTables(SQLite::Connection& connection) = 0; + + // Version 1.0 + // Adds a pin to the index. + virtual SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; + + // Updates an existing pin in the index. + // The return value indicates whether the index was modified by the function. + virtual std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) = 0; + + // Removes a pin from the index. + virtual SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; + + // Returns the current pin for a given package if it exists. + virtual std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) = 0; + + // Returns a vector containing all the existing pins. + virtual std::vector GetAllPins(SQLite::Connection& connection) = 0; + + // Removes all the pins from a given source, or from all sources if none is specified. + // Returns a value indicating whether any pin was deleted. + virtual bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) = 0; + }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h index f35dc7bb51..a0e144cecf 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/IPortableIndex.h @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "winget/PortableFileEntry.h" -#include - -namespace AppInstaller::Repository::Microsoft::Schema -{ - struct IPortableIndex - { - virtual ~IPortableIndex() = default; - - // Gets the schema version that this index interface is built for. - virtual SQLite::Version GetVersion() const = 0; - - // Creates all of the version dependent tables within the database. - virtual void CreateTable(SQLite::Connection& connection) = 0; - - // Version 1.0 - // Adds a portable file to the index. - virtual SQLite::rowid_t AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; - - // Removes a portable file from the index. - virtual SQLite::rowid_t RemovePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; - - // Updates the file with matching FilePath in the index. - // The return value indicates whether the index was modified by the function. - virtual std::pair UpdatePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; - - // Returns a bool value indicating whether the PortableFile already exists in the index. - virtual bool Exists(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; - - // Returns a bool value indicating whether the index is empty. - virtual bool IsEmpty(SQLite::Connection& connection) = 0; - - // Returns a vector including all the portable files recorded in the index. - virtual std::vector GetAllPortableFiles(SQLite::Connection& connection) = 0; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "winget/PortableFileEntry.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema +{ + struct IPortableIndex + { + virtual ~IPortableIndex() = default; + + // Gets the schema version that this index interface is built for. + virtual SQLite::Version GetVersion() const = 0; + + // Creates all of the version dependent tables within the database. + virtual void CreateTable(SQLite::Connection& connection) = 0; + + // Version 1.0 + // Adds a portable file to the index. + virtual SQLite::rowid_t AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; + + // Removes a portable file from the index. + virtual SQLite::rowid_t RemovePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; + + // Updates the file with matching FilePath in the index. + // The return value indicates whether the index was modified by the function. + virtual std::pair UpdatePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; + + // Returns a bool value indicating whether the PortableFile already exists in the index. + virtual bool Exists(SQLite::Connection& connection, const Portable::PortableFileEntry& file) = 0; + + // Returns a bool value indicating whether the index is empty. + virtual bool IsEmpty(SQLite::Connection& connection) = 0; + + // Returns a vector including all the portable files recorded in the index. + virtual std::vector GetAllPortableFiles(SQLite::Connection& connection) = 0; + }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.cpp index df753a14dc..4e21d059ca 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.cpp @@ -1,86 +1,86 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/ISQLiteIndex.h" - -#include "Microsoft/Schema/1_0/Interface.h" -#include "Microsoft/Schema/1_1/Interface.h" -#include "Microsoft/Schema/1_2/Interface.h" -#include "Microsoft/Schema/1_3/Interface.h" -#include "Microsoft/Schema/1_4/Interface.h" -#include "Microsoft/Schema/1_5/Interface.h" -#include "Microsoft/Schema/1_6/Interface.h" -#include "Microsoft/Schema/1_7/Interface.h" -#include "Microsoft/Schema/2_0/Interface.h" - -namespace AppInstaller::Repository::Microsoft::Schema -{ - void ISQLiteIndex::PrepareForPackaging(const SQLiteIndexContext& context) - { - PrepareForPackaging(context.Connection); - } - - void ISQLiteIndex::SetProperty(SQLite::Connection&, Property, const std::string&) - { - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - - std::unique_ptr CreateISQLiteIndex(const SQLite::Version& version) - { - if (version.MajorVersion == 1 || - version.IsLatest()) - { - constexpr std::array(*)(), 8> versionCreatorMap = - { - []() { return std::unique_ptr(std::make_unique()); }, - []() { return std::unique_ptr(std::make_unique()); }, - []() { return std::unique_ptr(std::make_unique()); }, - []() { return std::unique_ptr(std::make_unique()); }, - []() { return std::unique_ptr(std::make_unique()); }, - []() { return std::unique_ptr(std::make_unique()); }, - []() { return std::unique_ptr(std::make_unique()); }, - []() { return std::unique_ptr(std::make_unique()); }, - }; - - return versionCreatorMap[std::min(static_cast(version.MinorVersion), versionCreatorMap.size() - 1)](); - } - - // Version 2.0 is designed solely for minimizing the size of the index for transport. - // Unless it is prepared for packaging, it will be identical to a 1.N index. - if (version.MajorVersion == 2) - { - constexpr std::array(*)(), 1> versionCreatorMap = - { - []() { return std::unique_ptr(std::make_unique()); }, - }; - - return versionCreatorMap[std::min(static_cast(version.MinorVersion), versionCreatorMap.size() - 1)](); - } - - // We do not have the capacity to operate on this schema version - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - - std::vector GetDefaultMatchTypeOrder(MatchType type) - { - switch (type) - { - case MatchType::Exact: - return { MatchType::Exact }; - case MatchType::CaseInsensitive: - return { MatchType::CaseInsensitive }; - case MatchType::StartsWith: - return { MatchType::CaseInsensitive, MatchType::StartsWith }; - case MatchType::Substring: - return { MatchType::CaseInsensitive, MatchType::Substring }; - case MatchType::Wildcard: - return { MatchType::Wildcard }; - case MatchType::Fuzzy: - return { MatchType::CaseInsensitive, MatchType::Fuzzy }; - case MatchType::FuzzySubstring: - return { MatchType::CaseInsensitive, MatchType::Fuzzy, MatchType::Substring, MatchType::FuzzySubstring }; - default: - THROW_HR(E_UNEXPECTED); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/ISQLiteIndex.h" + +#include "Microsoft/Schema/1_0/Interface.h" +#include "Microsoft/Schema/1_1/Interface.h" +#include "Microsoft/Schema/1_2/Interface.h" +#include "Microsoft/Schema/1_3/Interface.h" +#include "Microsoft/Schema/1_4/Interface.h" +#include "Microsoft/Schema/1_5/Interface.h" +#include "Microsoft/Schema/1_6/Interface.h" +#include "Microsoft/Schema/1_7/Interface.h" +#include "Microsoft/Schema/2_0/Interface.h" + +namespace AppInstaller::Repository::Microsoft::Schema +{ + void ISQLiteIndex::PrepareForPackaging(const SQLiteIndexContext& context) + { + PrepareForPackaging(context.Connection); + } + + void ISQLiteIndex::SetProperty(SQLite::Connection&, Property, const std::string&) + { + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + + std::unique_ptr CreateISQLiteIndex(const SQLite::Version& version) + { + if (version.MajorVersion == 1 || + version.IsLatest()) + { + constexpr std::array(*)(), 8> versionCreatorMap = + { + []() { return std::unique_ptr(std::make_unique()); }, + []() { return std::unique_ptr(std::make_unique()); }, + []() { return std::unique_ptr(std::make_unique()); }, + []() { return std::unique_ptr(std::make_unique()); }, + []() { return std::unique_ptr(std::make_unique()); }, + []() { return std::unique_ptr(std::make_unique()); }, + []() { return std::unique_ptr(std::make_unique()); }, + []() { return std::unique_ptr(std::make_unique()); }, + }; + + return versionCreatorMap[std::min(static_cast(version.MinorVersion), versionCreatorMap.size() - 1)](); + } + + // Version 2.0 is designed solely for minimizing the size of the index for transport. + // Unless it is prepared for packaging, it will be identical to a 1.N index. + if (version.MajorVersion == 2) + { + constexpr std::array(*)(), 1> versionCreatorMap = + { + []() { return std::unique_ptr(std::make_unique()); }, + }; + + return versionCreatorMap[std::min(static_cast(version.MinorVersion), versionCreatorMap.size() - 1)](); + } + + // We do not have the capacity to operate on this schema version + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + + std::vector GetDefaultMatchTypeOrder(MatchType type) + { + switch (type) + { + case MatchType::Exact: + return { MatchType::Exact }; + case MatchType::CaseInsensitive: + return { MatchType::CaseInsensitive }; + case MatchType::StartsWith: + return { MatchType::CaseInsensitive, MatchType::StartsWith }; + case MatchType::Substring: + return { MatchType::CaseInsensitive, MatchType::Substring }; + case MatchType::Wildcard: + return { MatchType::Wildcard }; + case MatchType::Fuzzy: + return { MatchType::CaseInsensitive, MatchType::Fuzzy }; + case MatchType::FuzzySubstring: + return { MatchType::CaseInsensitive, MatchType::Fuzzy, MatchType::Substring, MatchType::FuzzySubstring }; + default: + THROW_HR(E_UNEXPECTED); + } + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h index e953c4ea8f..12de8e786f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/ISQLiteIndex.h @@ -1,163 +1,163 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "ISource.h" -#include "Microsoft/Schema/SQLiteIndexContextData.h" -#include -#include -#include - -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema -{ - // Contains the database connection and any other data that the owning index might need to pass in. - struct SQLiteIndexContext - { - SQLite::Connection& Connection; - SQLiteIndexContextData& Data; - }; - - // The common interface used to interact with all schema versions of the index. - struct ISQLiteIndex - { - virtual ~ISQLiteIndex() = default; - - // The non-version specific return value of Search. - // New fields must have initializers to their down-schema defaults. - struct SearchResult - { - std::vector> Matches; - bool Truncated = false; - }; - - // The non-version specific return value of GetMetadataByManifestId. - using MetadataResult = std::vector>; - - // Version 1.0 - - // Gets the schema version that this index interface is built for. - virtual SQLite::Version GetVersion() const = 0; - - // Options for creating the index. - enum class CreateOptions - { - // Standard - None = 0x0, - // Enable support for passing in nullopt values to Add/UpdateManifest - SupportPathless = 0x1, - // Disable support for dependencies - DisableDependenciesSupport = 0x2, - // Use maximum page size in SQLite. - // This was part of an exploration of ways to reduce file size but ultimately led to a larger - // compressed file with a worse ratio (limited testing but was significant enough to warrant abandonment). - // Leaving this here as a valid null result to prevent future maintainers from needing to investigate. - LargePageSize = 0x4, - }; - - // Contains both the object representation of the version key and the rows. - struct VersionKey - { - Utility::VersionAndChannel VersionAndChannel; - SQLite::rowid_t ManifestId; - - bool operator<(const VersionKey& other) const { return VersionAndChannel < other.VersionAndChannel; } - }; - - // Creates all of the version dependent tables within the database. - virtual void CreateTables(SQLite::Connection& connection, CreateOptions options) = 0; - - // Adds the manifest at the repository relative path to the index. - virtual SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) = 0; - - // Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the index was modified by the function. - virtual std::pair UpdateManifest( - SQLite::Connection& connection, - const Manifest::Manifest& manifest, - const std::optional& relativePath) = 0; - - // Removes the manifest with matching { Id, Version, Channel } from the index. - virtual SQLite::rowid_t RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) = 0; - - // Removes the manifest with the given id. - virtual void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) = 0; - - // Removes data that is no longer needed for an index that is to be published. - virtual void PrepareForPackaging(SQLite::Connection& connection) = 0; - - // Removes data that is no longer needed for an index that is to be published. - virtual void PrepareForPackaging(const SQLiteIndexContext& context); - - // Checks the consistency of the index to ensure that every referenced row exists. - // Returns true if index is consistent; false if it is not. - virtual bool CheckConsistency(const SQLite::Connection& connection, bool log) const = 0; - - // Performs a search based on the given criteria. - virtual SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const = 0; - - // Gets the string for the given property and primary id, if present. - virtual std::optional GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const = 0; - - // Gets the string values for the given property and primary id, if present. - virtual std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const = 0; - - // Gets the manifest id for the given { id, version, channel }, if present. - // If version is empty, gets the value for the 'latest' version. - virtual std::optional GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const = 0; - - // Gets the manifest id for the given manifest, if present. - virtual std::optional GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const = 0; - - // Gets all versions and channels for the given id. - virtual std::vector GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const = 0; - - // Version 1.1 - - // Gets the string for the given metadata and manifest id, if present. - virtual MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const = 0; - - // Sets the string for the given metadata and manifest id. - virtual void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) = 0; - - // Version 1.2 - - // Normalizes a name using the internal rules used by the index. - // Largely a utility function; should not be used to do work on behalf of the index by the caller. - virtual Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const = 0; - - // Version 1.4 - - // Get all the dependencies for a specific manifest. - virtual std::set> GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const = 0; - - virtual std::vector> GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const = 0; - - // Version 1.7 - - // Drops all tables that would have been created. - virtual void DropTables(SQLite::Connection& connection) = 0; - - // Version 2.0 - - // Migrates from the current interface given. - // Returns true if supported; false if not. - // Throws on errors that occur during an attempted migration. - virtual bool MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) = 0; - - // Set the property value. - virtual void SetProperty(SQLite::Connection& connection, Property property, const std::string& value); - }; - - DEFINE_ENUM_FLAG_OPERATORS(ISQLiteIndex::CreateOptions); - - // Creates the ISQLiteIndex interface object for the given version. - std::unique_ptr CreateISQLiteIndex(const SQLite::Version& version); - - // For a given match type, gets the set of match types that are more specific subsets of it. - std::vector GetDefaultMatchTypeOrder(MatchType type); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "ISource.h" +#include "Microsoft/Schema/SQLiteIndexContextData.h" +#include +#include +#include + +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema +{ + // Contains the database connection and any other data that the owning index might need to pass in. + struct SQLiteIndexContext + { + SQLite::Connection& Connection; + SQLiteIndexContextData& Data; + }; + + // The common interface used to interact with all schema versions of the index. + struct ISQLiteIndex + { + virtual ~ISQLiteIndex() = default; + + // The non-version specific return value of Search. + // New fields must have initializers to their down-schema defaults. + struct SearchResult + { + std::vector> Matches; + bool Truncated = false; + }; + + // The non-version specific return value of GetMetadataByManifestId. + using MetadataResult = std::vector>; + + // Version 1.0 + + // Gets the schema version that this index interface is built for. + virtual SQLite::Version GetVersion() const = 0; + + // Options for creating the index. + enum class CreateOptions + { + // Standard + None = 0x0, + // Enable support for passing in nullopt values to Add/UpdateManifest + SupportPathless = 0x1, + // Disable support for dependencies + DisableDependenciesSupport = 0x2, + // Use maximum page size in SQLite. + // This was part of an exploration of ways to reduce file size but ultimately led to a larger + // compressed file with a worse ratio (limited testing but was significant enough to warrant abandonment). + // Leaving this here as a valid null result to prevent future maintainers from needing to investigate. + LargePageSize = 0x4, + }; + + // Contains both the object representation of the version key and the rows. + struct VersionKey + { + Utility::VersionAndChannel VersionAndChannel; + SQLite::rowid_t ManifestId; + + bool operator<(const VersionKey& other) const { return VersionAndChannel < other.VersionAndChannel; } + }; + + // Creates all of the version dependent tables within the database. + virtual void CreateTables(SQLite::Connection& connection, CreateOptions options) = 0; + + // Adds the manifest at the repository relative path to the index. + virtual SQLite::rowid_t AddManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest, const std::optional& relativePath) = 0; + + // Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the index was modified by the function. + virtual std::pair UpdateManifest( + SQLite::Connection& connection, + const Manifest::Manifest& manifest, + const std::optional& relativePath) = 0; + + // Removes the manifest with matching { Id, Version, Channel } from the index. + virtual SQLite::rowid_t RemoveManifest(SQLite::Connection& connection, const Manifest::Manifest& manifest) = 0; + + // Removes the manifest with the given id. + virtual void RemoveManifestById(SQLite::Connection& connection, SQLite::rowid_t manifestId) = 0; + + // Removes data that is no longer needed for an index that is to be published. + virtual void PrepareForPackaging(SQLite::Connection& connection) = 0; + + // Removes data that is no longer needed for an index that is to be published. + virtual void PrepareForPackaging(const SQLiteIndexContext& context); + + // Checks the consistency of the index to ensure that every referenced row exists. + // Returns true if index is consistent; false if it is not. + virtual bool CheckConsistency(const SQLite::Connection& connection, bool log) const = 0; + + // Performs a search based on the given criteria. + virtual SearchResult Search(const SQLite::Connection& connection, const SearchRequest& request) const = 0; + + // Gets the string for the given property and primary id, if present. + virtual std::optional GetPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionProperty property) const = 0; + + // Gets the string values for the given property and primary id, if present. + virtual std::vector GetMultiPropertyByPrimaryId(const SQLite::Connection& connection, SQLite::rowid_t primaryId, PackageVersionMultiProperty property) const = 0; + + // Gets the manifest id for the given { id, version, channel }, if present. + // If version is empty, gets the value for the 'latest' version. + virtual std::optional GetManifestIdByKey(const SQLite::Connection& connection, SQLite::rowid_t id, std::string_view version, std::string_view channel) const = 0; + + // Gets the manifest id for the given manifest, if present. + virtual std::optional GetManifestIdByManifest(const SQLite::Connection& connection, const Manifest::Manifest& manifest) const = 0; + + // Gets all versions and channels for the given id. + virtual std::vector GetVersionKeysById(const SQLite::Connection& connection, SQLite::rowid_t id) const = 0; + + // Version 1.1 + + // Gets the string for the given metadata and manifest id, if present. + virtual MetadataResult GetMetadataByManifestId(const SQLite::Connection& connection, SQLite::rowid_t manifestId) const = 0; + + // Sets the string for the given metadata and manifest id. + virtual void SetMetadataByManifestId(SQLite::Connection& connection, SQLite::rowid_t manifestId, PackageVersionMetadata metadata, std::string_view value) = 0; + + // Version 1.2 + + // Normalizes a name using the internal rules used by the index. + // Largely a utility function; should not be used to do work on behalf of the index by the caller. + virtual Utility::NormalizedName NormalizeName(std::string_view name, std::string_view publisher) const = 0; + + // Version 1.4 + + // Get all the dependencies for a specific manifest. + virtual std::set> GetDependenciesByManifestRowId(const SQLite::Connection& connection, SQLite::rowid_t manifestRowId) const = 0; + + virtual std::vector> GetDependentsById(const SQLite::Connection& connection, AppInstaller::Manifest::string_t packageId) const = 0; + + // Version 1.7 + + // Drops all tables that would have been created. + virtual void DropTables(SQLite::Connection& connection) = 0; + + // Version 2.0 + + // Migrates from the current interface given. + // Returns true if supported; false if not. + // Throws on errors that occur during an attempted migration. + virtual bool MigrateFrom(SQLite::Connection& connection, const ISQLiteIndex* current) = 0; + + // Set the property value. + virtual void SetProperty(SQLite::Connection& connection, Property property, const std::string& value); + }; + + DEFINE_ENUM_FLAG_OPERATORS(ISQLiteIndex::CreateOptions); + + // Creates the ISQLiteIndex interface object for the given version. + std::unique_ptr CreateISQLiteIndex(const SQLite::Version& version); + + // For a given match type, gets the set of match types that are more specific subsets of it. + std::vector GetDefaultMatchTypeOrder(MatchType type); +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp index 370c3f1e29..f540645553 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.cpp @@ -1,196 +1,196 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PinTable.h" -#include -#include "Microsoft/Schema/IPinningIndex.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - namespace - { - std::optional GetPinFromRow( - std::string_view packageId, - std::string_view sourceId, - Pinning::PinType type, - std::string_view version) - - { - switch (type) - { - case Pinning::PinType::Blocking: - return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); - case Pinning::PinType::Pinning: - return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); - case Pinning::PinType::Gating: - return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); - default: - return {}; - } - } - } - - using namespace std::string_view_literals; - static constexpr std::string_view s_PinTable_Table_Name = "pin"sv; - static constexpr std::string_view s_PinTable_PackageId_Column = "package_id"sv; - static constexpr std::string_view s_PinTable_SourceId_Column = "source_id"sv; - static constexpr std::string_view s_PinTable_Type_Column = "type"sv; - static constexpr std::string_view s_PinTable_Version_Column = "version"sv; - static constexpr std::string_view s_PinTable_Index = "pin_index"sv; - - std::string_view PinTable::TableName() - { - return s_PinTable_Table_Name; - } - - void PinTable::Create(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); - - createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PinTable_Version_Column, Type::Text).NotNull()); - - createTableBuilder.EndColumns(); - createTableBuilder.Execute(connection); - - // Create an index over the pairs package,source - StatementBuilder createIndexBuilder; - createIndexBuilder.CreateUniqueIndex(s_PinTable_Index).On(s_PinTable_Table_Name) - .Columns({ s_PinTable_PackageId_Column, s_PinTable_SourceId_Column }); - createIndexBuilder.Execute(connection); - - savepoint.Commit(); - } - - std::optional PinTable::GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) - .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) - .And(s_PinTable_SourceId_Column).Equals((std::string_view)pinKey.SourceId); - - SQLite::Statement select = builder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) - { - SQLite::Builder::StatementBuilder builder; - const auto& pinKey = pin.GetKey(); - builder.InsertInto(s_PinTable_Table_Name) - .Columns({ - s_PinTable_PackageId_Column, - s_PinTable_SourceId_Column, - s_PinTable_Type_Column, - s_PinTable_Version_Column }) - .Values( - (std::string_view)pinKey.PackageId, - pinKey.SourceId, - pin.GetType(), - pin.GetGatedVersion().ToString()); - - builder.Execute(connection); - return connection.GetLastInsertRowID(); - } - - bool PinTable::UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) - { - SQLite::Builder::StatementBuilder builder; - const auto& pinKey = pin.GetKey(); - builder.Update(s_PinTable_Table_Name).Set() - .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) - .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) - .Column(s_PinTable_Type_Column).Equals(pin.GetType()) - .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) - .Where(SQLite::RowIDName).Equals(pinId); - - builder.Execute(connection); - return connection.GetChanges() != 0; - } - - void PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); - builder.Execute(connection); - } - - std::optional PinTable::GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ - s_PinTable_PackageId_Column, - s_PinTable_SourceId_Column, - s_PinTable_Type_Column, - s_PinTable_Version_Column }) - .From(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); - - SQLite::Statement select = builder.Prepare(connection); - - if (!select.Step()) - { - return {}; - } - - auto [packageId, sourceId, pinType, gatedVersion] = - select.GetRow(); - return GetPinFromRow(packageId, sourceId, pinType, gatedVersion); - } - - std::vector PinTable::GetAllPins(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ - s_PinTable_PackageId_Column, - s_PinTable_SourceId_Column, - s_PinTable_Type_Column, - s_PinTable_Version_Column }) - .From(s_PinTable_Table_Name); - - SQLite::Statement select = builder.Prepare(connection); - - std::vector pins; - while (select.Step()) - { - auto [packageId, sourceId, pinType, gatedVersion] = - select.GetRow(); - auto pin = GetPinFromRow(packageId, sourceId, pinType, gatedVersion); - if (pin) - { - pins.push_back(std::move(pin.value())); - } - } - - return pins; - } - - bool PinTable::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PinTable_Table_Name); - - if (!sourceId.empty()) - { - builder.Where(s_PinTable_SourceId_Column).Equals(sourceId); - } - - builder.Execute(connection); - - return connection.GetChanges() != 0; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PinTable.h" +#include +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + namespace + { + std::optional GetPinFromRow( + std::string_view packageId, + std::string_view sourceId, + Pinning::PinType type, + std::string_view version) + + { + switch (type) + { + case Pinning::PinType::Blocking: + return Pinning::Pin::CreateBlockingPin({ packageId, sourceId }); + case Pinning::PinType::Pinning: + return Pinning::Pin::CreatePinningPin({ packageId, sourceId }); + case Pinning::PinType::Gating: + return Pinning::Pin::CreateGatingPin({ packageId, sourceId }, Utility::GatedVersion{ version }); + default: + return {}; + } + } + } + + using namespace std::string_view_literals; + static constexpr std::string_view s_PinTable_Table_Name = "pin"sv; + static constexpr std::string_view s_PinTable_PackageId_Column = "package_id"sv; + static constexpr std::string_view s_PinTable_SourceId_Column = "source_id"sv; + static constexpr std::string_view s_PinTable_Type_Column = "type"sv; + static constexpr std::string_view s_PinTable_Version_Column = "version"sv; + static constexpr std::string_view s_PinTable_Index = "pin_index"sv; + + std::string_view PinTable::TableName() + { + return s_PinTable_Table_Name; + } + + void PinTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PinTable_Table_Name).BeginColumns(); + + createTableBuilder.Column(ColumnBuilder(s_PinTable_PackageId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_SourceId_Column, Type::Text).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Type_Column, Type::Int64).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PinTable_Version_Column, Type::Text).NotNull()); + + createTableBuilder.EndColumns(); + createTableBuilder.Execute(connection); + + // Create an index over the pairs package,source + StatementBuilder createIndexBuilder; + createIndexBuilder.CreateUniqueIndex(s_PinTable_Index).On(s_PinTable_Table_Name) + .Columns({ s_PinTable_PackageId_Column, s_PinTable_SourceId_Column }); + createIndexBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::optional PinTable::GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PinTable_Table_Name) + .Where(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) + .And(s_PinTable_SourceId_Column).Equals((std::string_view)pinKey.SourceId); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + SQLite::rowid_t PinTable::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + SQLite::Builder::StatementBuilder builder; + const auto& pinKey = pin.GetKey(); + builder.InsertInto(s_PinTable_Table_Name) + .Columns({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column }) + .Values( + (std::string_view)pinKey.PackageId, + pinKey.SourceId, + pin.GetType(), + pin.GetGatedVersion().ToString()); + + builder.Execute(connection); + return connection.GetLastInsertRowID(); + } + + bool PinTable::UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin) + { + SQLite::Builder::StatementBuilder builder; + const auto& pinKey = pin.GetKey(); + builder.Update(s_PinTable_Table_Name).Set() + .Column(s_PinTable_PackageId_Column).Equals((std::string_view)pinKey.PackageId) + .Column(s_PinTable_SourceId_Column).Equals(pinKey.SourceId) + .Column(s_PinTable_Type_Column).Equals(pin.GetType()) + .Column(s_PinTable_Version_Column).Equals(pin.GetGatedVersion().ToString()) + .Where(SQLite::RowIDName).Equals(pinId); + + builder.Execute(connection); + return connection.GetChanges() != 0; + } + + void PinTable::RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + builder.Execute(connection); + } + + std::optional PinTable::GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column }) + .From(s_PinTable_Table_Name).Where(SQLite::RowIDName).Equals(pinId); + + SQLite::Statement select = builder.Prepare(connection); + + if (!select.Step()) + { + return {}; + } + + auto [packageId, sourceId, pinType, gatedVersion] = + select.GetRow(); + return GetPinFromRow(packageId, sourceId, pinType, gatedVersion); + } + + std::vector PinTable::GetAllPins(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ + s_PinTable_PackageId_Column, + s_PinTable_SourceId_Column, + s_PinTable_Type_Column, + s_PinTable_Version_Column }) + .From(s_PinTable_Table_Name); + + SQLite::Statement select = builder.Prepare(connection); + + std::vector pins; + while (select.Step()) + { + auto [packageId, sourceId, pinType, gatedVersion] = + select.GetRow(); + auto pin = GetPinFromRow(packageId, sourceId, pinType, gatedVersion); + if (pin) + { + pins.push_back(std::move(pin.value())); + } + } + + return pins; + } + + bool PinTable::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PinTable_Table_Name); + + if (!sourceId.empty()) + { + builder.Where(s_PinTable_SourceId_Column).Equals(sourceId); + } + + builder.Execute(connection); + + return connection.GetChanges() != 0; + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h index 4bfafd616a..59476d478f 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinTable.h @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "Microsoft/Schema/IPinningIndex.h" -#include - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - struct PinTable - { - // Get the table name. - static std::string_view TableName(); - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - - // Gets the row ID for the pin, if it exists. - static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); - - // Adds a new pin. Returns the row ID of the added pin. - static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); - - // Updates an existing pin. - // Returns a value indicating whether there were any changes. - static bool UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); - - // Removes a pin given its row ID. - static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); - - // Gets a pin by its row ID if it exists. - // Used for testing - static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); - - // Gets all the currently existing pins. - static std::vector GetAllPins(SQLite::Connection& connection); - - // Resets all pins from a given source, or from all sources if none is specified. - // Returns a value indicating whether there were any changes. - static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Microsoft/Schema/IPinningIndex.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + struct PinTable + { + // Get the table name. + static std::string_view TableName(); + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + // Gets the row ID for the pin, if it exists. + static std::optional GetIdByPinKey(SQLite::Connection& connection, const Pinning::PinKey& pinKey); + + // Adds a new pin. Returns the row ID of the added pin. + static SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin); + + // Updates an existing pin. + // Returns a value indicating whether there were any changes. + static bool UpdatePinById(SQLite::Connection& connection, SQLite::rowid_t pinId, const Pinning::Pin& pin); + + // Removes a pin given its row ID. + static void RemovePinById(SQLite::Connection& connection, SQLite::rowid_t pinId); + + // Gets a pin by its row ID if it exists. + // Used for testing + static std::optional GetPinById(SQLite::Connection& connection, const SQLite::rowid_t pinId); + + // Gets all the currently existing pins. + static std::vector GetAllPins(SQLite::Connection& connection); + + // Resets all pins from a given source, or from all sources if none is specified. + // Returns a value indicating whether there were any changes. + static bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId = {}); + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h index 7d7a38f0dc..50dc138d1c 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Microsoft/Schema/IPinningIndex.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - struct PinningIndexInterface : public IPinningIndex - { - // Version 1.0 - SQLite::Version GetVersion() const override; - void CreateTables(SQLite::Connection& connection) override; - - private: - SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; - SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; - std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; - std::vector GetAllPins(SQLite::Connection& connection) override; - bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Microsoft/Schema/IPinningIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + struct PinningIndexInterface : public IPinningIndex + { + // Version 1.0 + SQLite::Version GetVersion() const override; + void CreateTables(SQLite::Connection& connection) override; + + private: + SQLite::rowid_t AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + std::pair UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) override; + SQLite::rowid_t RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + std::optional GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) override; + std::vector GetAllPins(SQLite::Connection& connection) override; + bool ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp index 2d81684846..1cf25a345d 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Pinning_1_0/PinningIndexInterface_1_0.cpp @@ -1,106 +1,106 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h" -#include "Microsoft/Schema/Pinning_1_0/PinTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 -{ - namespace - { - std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto result = PinTable::GetIdByPinKey(connection, pinKey); - - if (!result) - { - AICLI_LOG(Repo, Verbose, << "Did not find pin " << pinKey.ToString()); - } - - return result; - } - - } - - // Version 1.0 - SQLite::Version PinningIndexInterface::GetVersion() const - { - return { 1, 0 }; - } - - void PinningIndexInterface::CreateTables(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); - Pinning_V1_0::PinTable::Create(connection); - savepoint.Commit(); - } - - SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) - { - auto existingPin = GetExistingPinId(connection, pin.GetKey()); - - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); - SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); - - savepoint.Commit(); - return pinId; - } - - std::pair PinningIndexInterface::UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) - { - auto existingPinId = GetExistingPinId(connection, pin.GetKey()); - - // If the pin doesn't exist, fail the update - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); - - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_0"); - bool status = PinTable::UpdatePinById(connection, existingPinId.value(), pin); - - savepoint.Commit(); - return { status, existingPinId.value() }; - } - - SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto existingPinId = GetExistingPinId(connection, pinKey); - - // If the pin doesn't exist, fail the remove - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); - - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); - PinTable::RemovePinById(connection, existingPinId.value()); - - savepoint.Commit(); - return existingPinId.value(); - } - - std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) - { - auto existingPinId = GetExistingPinId(connection, pinKey); - - if (!existingPinId) - { - return {}; - } - - return PinTable::GetPinById(connection, existingPinId.value()); - } - - std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) - { - return PinTable::GetAllPins(connection); - } - - bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetpins_v1_0"); - bool result = PinTable::ResetAllPins(connection, sourceId); - savepoint.Commit(); - - return result; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/Pinning_1_0/PinningIndexInterface.h" +#include "Microsoft/Schema/Pinning_1_0/PinTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Pinning_V1_0 +{ + namespace + { + std::optional GetExistingPinId(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto result = PinTable::GetIdByPinKey(connection, pinKey); + + if (!result) + { + AICLI_LOG(Repo, Verbose, << "Did not find pin " << pinKey.ToString()); + } + + return result; + } + + } + + // Version 1.0 + SQLite::Version PinningIndexInterface::GetVersion() const + { + return { 1, 0 }; + } + + void PinningIndexInterface::CreateTables(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createpintable_v1_0"); + Pinning_V1_0::PinTable::Create(connection); + savepoint.Commit(); + } + + SQLite::rowid_t PinningIndexInterface::AddPin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPin = GetExistingPinId(connection, pin.GetKey()); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), existingPin.has_value()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addpin_v1_0"); + SQLite::rowid_t pinId = PinTable::AddPin(connection, pin); + + savepoint.Commit(); + return pinId; + } + + std::pair PinningIndexInterface::UpdatePin(SQLite::Connection& connection, const Pinning::Pin& pin) + { + auto existingPinId = GetExistingPinId(connection, pin.GetKey()); + + // If the pin doesn't exist, fail the update + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updatepin_v1_0"); + bool status = PinTable::UpdatePinById(connection, existingPinId.value(), pin); + + savepoint.Commit(); + return { status, existingPinId.value() }; + } + + SQLite::rowid_t PinningIndexInterface::RemovePin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + // If the pin doesn't exist, fail the remove + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_NOT_FOUND), !existingPinId); + + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removepin_v1_0"); + PinTable::RemovePinById(connection, existingPinId.value()); + + savepoint.Commit(); + return existingPinId.value(); + } + + std::optional PinningIndexInterface::GetPin(SQLite::Connection& connection, const Pinning::PinKey& pinKey) + { + auto existingPinId = GetExistingPinId(connection, pinKey); + + if (!existingPinId) + { + return {}; + } + + return PinTable::GetPinById(connection, existingPinId.value()); + } + + std::vector PinningIndexInterface::GetAllPins(SQLite::Connection& connection) + { + return PinTable::GetAllPins(connection); + } + + bool PinningIndexInterface::ResetAllPins(SQLite::Connection& connection, std::string_view sourceId) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "resetpins_v1_0"); + bool result = PinTable::ResetAllPins(connection, sourceId); + savepoint.Commit(); + + return result; + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface_1_0.cpp index 25d35e8191..3cd614e528 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableIndexInterface_1_0.cpp @@ -1,91 +1,91 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Microsoft/Schema/Portable_1_0/PortableIndexInterface.h" -#include "Microsoft/Schema/Portable_1_0/PortableTable.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 -{ - namespace - { - std::optional GetExistingPortableFileId(const SQLite::Connection& connection, const Portable::PortableFileEntry& file) - { - auto result = PortableTable::SelectByFilePath(connection, file.GetFilePath()); - - if (!result) - { - AICLI_LOG(Repo, Verbose, << "Did not find a portable file with the path { " << file.GetFilePath() << " }"); - } - - return result; - } - } - - SQLite::Version PortableIndexInterface::GetVersion() const - { - return { 1, 0 }; - } - - void PortableIndexInterface::CreateTable(SQLite::Connection& connection) - { - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createportabletable_v1_0"); - Portable_V1_0::PortableTable::Create(connection); - savepoint.Commit(); - } - - SQLite::rowid_t PortableIndexInterface::AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) - { - auto portableEntryResult = GetExistingPortableFileId(connection, file); - - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), portableEntryResult.has_value()); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addportablefile_v1_0"); - SQLite::rowid_t portableFileId = PortableTable::AddPortableFile(connection, file); - - savepoint.Commit(); - return portableFileId; - } - - SQLite::rowid_t PortableIndexInterface::RemovePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) - { - auto portableEntryResult = GetExistingPortableFileId(connection, file); - - // If the portable file doesn't actually exist, fail the remove. - THROW_HR_IF(E_NOT_SET, !portableEntryResult); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removeportablefile_v1_0"); - PortableTable::RemovePortableFileById(connection, portableEntryResult.value()); - - savepoint.Commit(); - return portableEntryResult.value(); - } - - std::pair PortableIndexInterface::UpdatePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) - { - auto portableEntryResult = GetExistingPortableFileId(connection, file); - - // If the portable file doesn't actually exist, fail the update. - THROW_HR_IF(E_NOT_SET, !portableEntryResult); - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updateportablefile_v1_0"); - bool status = PortableTable::UpdatePortableFileById(connection, portableEntryResult.value(), file); - - savepoint.Commit(); - return { status, portableEntryResult.value() }; - } - - bool PortableIndexInterface::Exists(SQLite::Connection& connection, const Portable::PortableFileEntry& file) - { - return GetExistingPortableFileId(connection, file).has_value(); - } - - bool PortableIndexInterface::IsEmpty(SQLite::Connection& connection) - { - return PortableTable::IsEmpty(connection); - } - - std::vector PortableIndexInterface::GetAllPortableFiles(SQLite::Connection& connection) - { - return PortableTable::GetAllPortableFiles(connection); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Microsoft/Schema/Portable_1_0/PortableIndexInterface.h" +#include "Microsoft/Schema/Portable_1_0/PortableTable.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + namespace + { + std::optional GetExistingPortableFileId(const SQLite::Connection& connection, const Portable::PortableFileEntry& file) + { + auto result = PortableTable::SelectByFilePath(connection, file.GetFilePath()); + + if (!result) + { + AICLI_LOG(Repo, Verbose, << "Did not find a portable file with the path { " << file.GetFilePath() << " }"); + } + + return result; + } + } + + SQLite::Version PortableIndexInterface::GetVersion() const + { + return { 1, 0 }; + } + + void PortableIndexInterface::CreateTable(SQLite::Connection& connection) + { + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createportabletable_v1_0"); + Portable_V1_0::PortableTable::Create(connection); + savepoint.Commit(); + } + + SQLite::rowid_t PortableIndexInterface::AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) + { + auto portableEntryResult = GetExistingPortableFileId(connection, file); + + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS), portableEntryResult.has_value()); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "addportablefile_v1_0"); + SQLite::rowid_t portableFileId = PortableTable::AddPortableFile(connection, file); + + savepoint.Commit(); + return portableFileId; + } + + SQLite::rowid_t PortableIndexInterface::RemovePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) + { + auto portableEntryResult = GetExistingPortableFileId(connection, file); + + // If the portable file doesn't actually exist, fail the remove. + THROW_HR_IF(E_NOT_SET, !portableEntryResult); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "removeportablefile_v1_0"); + PortableTable::RemovePortableFileById(connection, portableEntryResult.value()); + + savepoint.Commit(); + return portableEntryResult.value(); + } + + std::pair PortableIndexInterface::UpdatePortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) + { + auto portableEntryResult = GetExistingPortableFileId(connection, file); + + // If the portable file doesn't actually exist, fail the update. + THROW_HR_IF(E_NOT_SET, !portableEntryResult); + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "updateportablefile_v1_0"); + bool status = PortableTable::UpdatePortableFileById(connection, portableEntryResult.value(), file); + + savepoint.Commit(); + return { status, portableEntryResult.value() }; + } + + bool PortableIndexInterface::Exists(SQLite::Connection& connection, const Portable::PortableFileEntry& file) + { + return GetExistingPortableFileId(connection, file).has_value(); + } + + bool PortableIndexInterface::IsEmpty(SQLite::Connection& connection) + { + return PortableTable::IsEmpty(connection); + } + + std::vector PortableIndexInterface::GetAllPortableFiles(SQLite::Connection& connection) + { + return PortableTable::GetAllPortableFiles(connection); + } } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp index 7557493f97..6286121fb1 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.cpp @@ -1,178 +1,178 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PortableTable.h" -#include -#include "Microsoft/Schema/IPortableIndex.h" - -namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 -{ - using namespace std::string_view_literals; - static constexpr std::string_view s_PortableTable_Table_Name = "portable"sv; - static constexpr std::string_view s_PortableTable_FilePath_Column = "filepath"sv; - static constexpr std::string_view s_PortableTable_FileType_Column = "filetype"sv; - static constexpr std::string_view s_PortableTable_SHA256_Column = "sha256"sv; - static constexpr std::string_view s_PortableTable_SymlinkTarget_Column = "symlinktarget"sv; - - std::string_view PortableTable::TableName() - { - return s_PortableTable_Table_Name; - } - - void PortableTable::Create(SQLite::Connection& connection) - { - using namespace SQLite::Builder; - - SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPortableTable_v1_0"); - - StatementBuilder createTableBuilder; - createTableBuilder.CreateTable(s_PortableTable_Table_Name).BeginColumns(); - - createTableBuilder.Column(ColumnBuilder(s_PortableTable_FilePath_Column, Type::Text).NotNull().Unique().CollateNoCase()); - createTableBuilder.Column(ColumnBuilder(s_PortableTable_FileType_Column, Type::Int64).NotNull()); - createTableBuilder.Column(ColumnBuilder(s_PortableTable_SHA256_Column, Type::Blob)); - createTableBuilder.Column(ColumnBuilder(s_PortableTable_SymlinkTarget_Column, Type::Text)); - - createTableBuilder.EndColumns(); - createTableBuilder.Execute(connection); - - savepoint.Commit(); - } - - std::optional PortableTable::SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::RowIDName).From(s_PortableTable_Table_Name).Where(s_PortableTable_FilePath_Column); - builder.Equals(path.u8string()); - - SQLite::Statement select = builder.Prepare(connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - else - { - return {}; - } - } - - void PortableTable::RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - builder.Execute(connection); - } - - SQLite::rowid_t PortableTable::AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) - { - SQLite::Builder::StatementBuilder builder; - builder.InsertInto(s_PortableTable_Table_Name) - .Columns({ s_PortableTable_FilePath_Column, - s_PortableTable_FileType_Column, - s_PortableTable_SHA256_Column, - s_PortableTable_SymlinkTarget_Column }) - .Values(file.GetFilePath().u8string(), file.FileType, file.SHA256, file.SymlinkTarget); - - builder.Execute(connection); - return connection.GetLastInsertRowID(); - } - - bool PortableTable::UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const Portable::PortableFileEntry& file) - { - SQLite::Builder::StatementBuilder builder; - builder.Update(s_PortableTable_Table_Name).Set() - .Column(s_PortableTable_FilePath_Column).Equals(file.GetFilePath().u8string()) - .Column(s_PortableTable_FileType_Column).Equals(file.FileType) - .Column(s_PortableTable_SHA256_Column).Equals(file.SHA256) - .Column(s_PortableTable_SymlinkTarget_Column).Equals(file.SymlinkTarget) - .Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - return connection.GetChanges() != 0; - } - - std::optional PortableTable::GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ s_PortableTable_FilePath_Column, - s_PortableTable_FileType_Column, - s_PortableTable_SHA256_Column, - s_PortableTable_SymlinkTarget_Column}) - .From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement select = builder.Prepare(connection); - - Portable::PortableFileEntry portableFile; - if (select.Step()) - { - auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); - portableFile.FileType = fileType; - portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); - portableFile.SHA256 = std::move(sha256); - portableFile.SymlinkTarget = std::move(symlinkTarget); - return portableFile; - } - else - { - return {}; - } - } - - bool PortableTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) != 0); - } - - void PortableTable::DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) - { - SQLite::Builder::StatementBuilder builder; - builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); - - builder.Execute(connection); - } - - bool PortableTable::IsEmpty(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name); - - SQLite::Statement countStatement = builder.Prepare(connection); - - THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); - - return (countStatement.GetColumn(0) == 0); - } - - std::vector PortableTable::GetAllPortableFiles(SQLite::Connection& connection) - { - SQLite::Builder::StatementBuilder builder; - builder.Select({ s_PortableTable_FilePath_Column, - s_PortableTable_FileType_Column, - s_PortableTable_SHA256_Column, - s_PortableTable_SymlinkTarget_Column }) - .From(s_PortableTable_Table_Name); - - SQLite::Statement select = builder.Prepare(connection); - std::vector result; - while (select.Step()) - { - Portable::PortableFileEntry portableFile; - auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); - portableFile.FileType = fileType; - portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); - portableFile.SHA256 = std::move(sha256); - portableFile.SymlinkTarget = std::move(symlinkTarget); - result.emplace_back(std::move(portableFile)); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PortableTable.h" +#include +#include "Microsoft/Schema/IPortableIndex.h" + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + using namespace std::string_view_literals; + static constexpr std::string_view s_PortableTable_Table_Name = "portable"sv; + static constexpr std::string_view s_PortableTable_FilePath_Column = "filepath"sv; + static constexpr std::string_view s_PortableTable_FileType_Column = "filetype"sv; + static constexpr std::string_view s_PortableTable_SHA256_Column = "sha256"sv; + static constexpr std::string_view s_PortableTable_SymlinkTarget_Column = "symlinktarget"sv; + + std::string_view PortableTable::TableName() + { + return s_PortableTable_Table_Name; + } + + void PortableTable::Create(SQLite::Connection& connection) + { + using namespace SQLite::Builder; + + SQLite::Savepoint savepoint = SQLite::Savepoint::Create(connection, "createPortableTable_v1_0"); + + StatementBuilder createTableBuilder; + createTableBuilder.CreateTable(s_PortableTable_Table_Name).BeginColumns(); + + createTableBuilder.Column(ColumnBuilder(s_PortableTable_FilePath_Column, Type::Text).NotNull().Unique().CollateNoCase()); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_FileType_Column, Type::Int64).NotNull()); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_SHA256_Column, Type::Blob)); + createTableBuilder.Column(ColumnBuilder(s_PortableTable_SymlinkTarget_Column, Type::Text)); + + createTableBuilder.EndColumns(); + createTableBuilder.Execute(connection); + + savepoint.Commit(); + } + + std::optional PortableTable::SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::RowIDName).From(s_PortableTable_Table_Name).Where(s_PortableTable_FilePath_Column); + builder.Equals(path.u8string()); + + SQLite::Statement select = builder.Prepare(connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + else + { + return {}; + } + } + + void PortableTable::RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + builder.Execute(connection); + } + + SQLite::rowid_t PortableTable::AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file) + { + SQLite::Builder::StatementBuilder builder; + builder.InsertInto(s_PortableTable_Table_Name) + .Columns({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column }) + .Values(file.GetFilePath().u8string(), file.FileType, file.SHA256, file.SymlinkTarget); + + builder.Execute(connection); + return connection.GetLastInsertRowID(); + } + + bool PortableTable::UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const Portable::PortableFileEntry& file) + { + SQLite::Builder::StatementBuilder builder; + builder.Update(s_PortableTable_Table_Name).Set() + .Column(s_PortableTable_FilePath_Column).Equals(file.GetFilePath().u8string()) + .Column(s_PortableTable_FileType_Column).Equals(file.FileType) + .Column(s_PortableTable_SHA256_Column).Equals(file.SHA256) + .Column(s_PortableTable_SymlinkTarget_Column).Equals(file.SymlinkTarget) + .Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + return connection.GetChanges() != 0; + } + + std::optional PortableTable::GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column}) + .From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement select = builder.Prepare(connection); + + Portable::PortableFileEntry portableFile; + if (select.Step()) + { + auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); + portableFile.FileType = fileType; + portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); + portableFile.SHA256 = std::move(sha256); + portableFile.SymlinkTarget = std::move(symlinkTarget); + return portableFile; + } + else + { + return {}; + } + } + + bool PortableTable::ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) != 0); + } + + void PortableTable::DeleteById(SQLite::Connection& connection, SQLite::rowid_t id) + { + SQLite::Builder::StatementBuilder builder; + builder.DeleteFrom(s_PortableTable_Table_Name).Where(SQLite::RowIDName).Equals(id); + + builder.Execute(connection); + } + + bool PortableTable::IsEmpty(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select(SQLite::Builder::RowCount).From(s_PortableTable_Table_Name); + + SQLite::Statement countStatement = builder.Prepare(connection); + + THROW_HR_IF(E_UNEXPECTED, !countStatement.Step()); + + return (countStatement.GetColumn(0) == 0); + } + + std::vector PortableTable::GetAllPortableFiles(SQLite::Connection& connection) + { + SQLite::Builder::StatementBuilder builder; + builder.Select({ s_PortableTable_FilePath_Column, + s_PortableTable_FileType_Column, + s_PortableTable_SHA256_Column, + s_PortableTable_SymlinkTarget_Column }) + .From(s_PortableTable_Table_Name); + + SQLite::Statement select = builder.Prepare(connection); + std::vector result; + while (select.Step()) + { + Portable::PortableFileEntry portableFile; + auto [filePath, fileType, sha256, symlinkTarget] = select.GetRow(); + portableFile.FileType = fileType; + portableFile.SetFilePath(Utility::ConvertToUTF16(filePath)); + portableFile.SHA256 = std::move(sha256); + portableFile.SymlinkTarget = std::move(symlinkTarget); + result.emplace_back(std::move(portableFile)); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h index 713f65c6c3..390144fc81 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/Portable_1_0/PortableTable.h @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "Microsoft/Schema/IPortableIndex.h" -#include - -namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 -{ - // A table the represents a single portable file - struct PortableTable - { - // Get the table name. - static std::string_view TableName(); - - // Creates the table with named indices. - static void Create(SQLite::Connection& connection); - - // Gets a value indicating whether the portable file with rowid id exists. - static bool ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id); - - // Deletes the portable file row with the given rowid - static void DeleteById(SQLite::Connection& connection, SQLite::rowid_t id); - - // Gets a value indicating whether the table is empty. - static bool IsEmpty(SQLite::Connection& connection); - - // Selects the portable file by filepath from the table, returning the rowid if it exists. - static std::optional SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path); - - // Selects the portable file by rowid from the table, returning the portable file object if it exists. - static std::optional GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id); - - // Adds the portable file into the table. - static SQLite::rowid_t AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file); - - // Removes the portable file from the table by id. - static void RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id); - - // Updates the portable file in the table by id. - static bool UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const Portable::PortableFileEntry& file); - - // Gets all portable files recorded in the index. - static std::vector GetAllPortableFiles(SQLite::Connection& connection); - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Microsoft/Schema/IPortableIndex.h" +#include + +namespace AppInstaller::Repository::Microsoft::Schema::Portable_V1_0 +{ + // A table the represents a single portable file + struct PortableTable + { + // Get the table name. + static std::string_view TableName(); + + // Creates the table with named indices. + static void Create(SQLite::Connection& connection); + + // Gets a value indicating whether the portable file with rowid id exists. + static bool ExistsById(const SQLite::Connection& connection, SQLite::rowid_t id); + + // Deletes the portable file row with the given rowid + static void DeleteById(SQLite::Connection& connection, SQLite::rowid_t id); + + // Gets a value indicating whether the table is empty. + static bool IsEmpty(SQLite::Connection& connection); + + // Selects the portable file by filepath from the table, returning the rowid if it exists. + static std::optional SelectByFilePath(const SQLite::Connection& connection, const std::filesystem::path& path); + + // Selects the portable file by rowid from the table, returning the portable file object if it exists. + static std::optional GetPortableFileById(const SQLite::Connection& connection, SQLite::rowid_t id); + + // Adds the portable file into the table. + static SQLite::rowid_t AddPortableFile(SQLite::Connection& connection, const Portable::PortableFileEntry& file); + + // Removes the portable file from the table by id. + static void RemovePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id); + + // Updates the portable file in the table by id. + static bool UpdatePortableFileById(SQLite::Connection& connection, SQLite::rowid_t id, const Portable::PortableFileEntry& file); + + // Gets all portable files recorded in the index. + static std::vector GetAllPortableFiles(SQLite::Connection& connection); + }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Microsoft/Schema/SQLiteIndexContextData.h b/src/AppInstallerRepositoryCore/Microsoft/Schema/SQLiteIndexContextData.h index 46ac29f44c..e8d5670649 100644 --- a/src/AppInstallerRepositoryCore/Microsoft/Schema/SQLiteIndexContextData.h +++ b/src/AppInstallerRepositoryCore/Microsoft/Schema/SQLiteIndexContextData.h @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - - -namespace AppInstaller::Repository::Microsoft::Schema -{ - // Names a property - enum class Property : size_t - { - PackageUpdateTrackingBaseTime, - IntermediateFileOutputPath, - DatabaseFilePath, - Max - }; - - namespace details - { - template - struct PropertyMapping - { - // value_t type specifies the type of this property - }; - - template <> - struct PropertyMapping - { - using value_t = int64_t; - static constexpr bool SetThroughInterface = true; - }; - - template <> - struct PropertyMapping - { - using value_t = std::filesystem::path; - static constexpr bool SetThroughInterface = false; - }; - - template <> - struct PropertyMapping - { - using value_t = std::filesystem::path; - static constexpr bool SetThroughInterface = false; - }; - } - - using SQLiteIndexContextData = EnumBasedVariantMap; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + + +namespace AppInstaller::Repository::Microsoft::Schema +{ + // Names a property + enum class Property : size_t + { + PackageUpdateTrackingBaseTime, + IntermediateFileOutputPath, + DatabaseFilePath, + Max + }; + + namespace details + { + template + struct PropertyMapping + { + // value_t type specifies the type of this property + }; + + template <> + struct PropertyMapping + { + using value_t = int64_t; + static constexpr bool SetThroughInterface = true; + }; + + template <> + struct PropertyMapping + { + using value_t = std::filesystem::path; + static constexpr bool SetThroughInterface = false; + }; + + template <> + struct PropertyMapping + { + using value_t = std::filesystem::path; + static constexpr bool SetThroughInterface = false; + }; + } + + using SQLiteIndexContextData = EnumBasedVariantMap; +} diff --git a/src/AppInstallerRepositoryCore/PackageDependenciesValidation.cpp b/src/AppInstallerRepositoryCore/PackageDependenciesValidation.cpp index 53fe2ce544..9e39de6d63 100644 --- a/src/AppInstallerRepositoryCore/PackageDependenciesValidation.cpp +++ b/src/AppInstallerRepositoryCore/PackageDependenciesValidation.cpp @@ -233,4 +233,4 @@ namespace AppInstaller::Repository return true; } -} +} diff --git a/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp b/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp index c69090d7e1..bb816f526c 100644 --- a/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp +++ b/src/AppInstallerRepositoryCore/PackageInstalledStatus.cpp @@ -1,247 +1,247 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/InstalledStatus.h" -#include "Public/winget/PackageVersionSelection.h" -#include - -using namespace AppInstaller::Settings; -using namespace std::chrono_literals; - -namespace AppInstaller::Repository -{ - namespace - { - HRESULT CheckInstalledLocationStatus(const std::filesystem::path& installedLocation) - { - HRESULT installLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE; - if (!installedLocation.empty()) - { - // Use the none throw version, if the directory cannot be reached, it's treated as not found and later file checks are not performed. - std::error_code error; - installLocationStatus = - std::filesystem::exists(installedLocation, error) && std::filesystem::is_directory(installedLocation, error) ? - WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND : - WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND; - } - - return installLocationStatus; - } - - // Map to cache already calculated file hashes. - struct FilePathComparator - { - bool operator()(const std::filesystem::path& a, const std::filesystem::path& b) const - { - if (std::filesystem::equivalent(a, b)) - { - return false; - } - - return a < b; - } - }; - using FileHashMap = std::map; - - HRESULT CheckInstalledFileStatus( - const std::filesystem::path& filePath, - const Utility::SHA256::HashBuffer& expectedHash, - FileHashMap& fileHashes) - { - HRESULT fileStatus = WINGET_INSTALLED_STATUS_FILE_NOT_FOUND; - try - { - if (std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath)) - { - fileStatus = WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK; - if (!expectedHash.empty()) - { - auto itr = fileHashes.find(filePath); - if (itr == fileHashes.end()) - { - // If not found in cache, compute the hash. - std::ifstream in{ filePath, std::ifstream::binary }; - itr = fileHashes.emplace(filePath, Utility::SHA256::ComputeHash(in)).first; - } - - fileStatus = Utility::SHA256::AreEqual(expectedHash, itr->second) ? - WINGET_INSTALLED_STATUS_FILE_HASH_MATCH : WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH; - } - } - } - catch (...) - { - fileStatus = WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR; - } - - return fileStatus; - } - - std::vector CheckInstalledStatusInternal( - const std::shared_ptr& package, - InstalledStatusType checkTypes) - { - using namespace AppInstaller::Manifest; - - std::vector result; - bool checkFileHash = false; - std::shared_ptr installedVersion = GetInstalledVersion(package); - std::shared_ptr availableVersion; - FileHashMap fileHashes; - - // Variables for metadata from installed version. - InstallerTypeEnum installedType = InstallerTypeEnum::Unknown; - ScopeEnum installedScope = ScopeEnum::Unknown; - std::filesystem::path installedLocation; - std::string installedLocale; - Utility::Architecture installedArchitecture = Utility::Architecture::Unknown; - HRESULT installedLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE; - - std::shared_ptr availableVersions = GetAvailableVersionsForInstalledVersion(package); - - // Prepare installed metadata from installed version. - // Determine the available package version to be used for installed status checking. - // Only perform file hash check if we find an available version that matches installed version. - if (installedVersion) - { - // Installed metadata. - auto installedMetadata = installedVersion->GetMetadata(); - installedType = ConvertToInstallerTypeEnum(installedMetadata[PackageVersionMetadata::InstalledType]); - installedScope = ConvertToScopeEnum(installedMetadata[PackageVersionMetadata::InstalledScope]); - installedLocation = Filesystem::GetExpandedPath(installedMetadata[PackageVersionMetadata::InstalledLocation]); - installedLocale = installedMetadata[PackageVersionMetadata::InstalledLocale]; - installedArchitecture = Utility::ConvertToArchitectureEnum(installedMetadata[PackageVersionMetadata::InstalledArchitecture]); - installedLocationStatus = CheckInstalledLocationStatus(installedLocation); - - // Determine available version. - Utility::Version installedVersionAsVersion{ installedVersion->GetProperty(PackageVersionProperty::Version) }; - auto installedChannel = installedVersion->GetProperty(PackageVersionProperty::Channel); - PackageVersionKey versionKey; - versionKey.Channel = installedChannel.get(); - - if (installedVersionAsVersion.IsApproximate()) - { - // Use the base version as available version if installed version is mapped to be an approximate. - versionKey.Version = installedVersionAsVersion.GetBaseVersion().ToString(); - availableVersion = availableVersions->GetVersion(versionKey); - // It's unexpected if the installed version is already mapped to some version. - THROW_HR_IF(E_UNEXPECTED, !availableVersion); - } - else - { - versionKey.Version = installedVersionAsVersion.ToString(); - availableVersion = availableVersions->GetVersion(versionKey); - if (availableVersion) - { - checkFileHash = true; - } - } - } - - if (!availableVersion) - { - // No installed version, or installed version not found in available versions, - // then attempt to check installed status using latest version. - availableVersion = availableVersions->GetLatestVersion(); - THROW_HR_IF(E_UNEXPECTED, !availableVersion); - } - - auto manifest = availableVersion->GetManifest(); - for (auto const& installer : manifest.Installers) - { - InstallerInstalledStatus installerStatus; - installerStatus.Installer = installer; - - // ARP related checks - if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllAppsAndFeaturesEntryChecks)) - { - bool isMatchingInstaller = - installedVersion && - IsInstallerTypeCompatible(installedType, installer.EffectiveInstallerType()) && - (installedScope == ScopeEnum::Unknown || installer.Scope == ScopeEnum::Unknown || installedScope == installer.Scope) && // Treat unknown scope as compatible - (installedArchitecture == Utility::Architecture::Unknown || installer.Arch == Utility::Architecture::Neutral || installedArchitecture == installer.Arch) && // Treat unknown installed architecture as compatible - (installedLocale.empty() || installer.Locale.empty() || !Locale::IsWellFormedBcp47Tag(installedLocale) || Locale::GetDistanceOfLanguage(installedLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch); // Treat invalid locale as compatible - - // ARP entry status - if (WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntry)) - { - installerStatus.Status.emplace_back( - InstalledStatusType::AppsAndFeaturesEntry, - "", - isMatchingInstaller ? WINGET_INSTALLED_STATUS_ARP_ENTRY_FOUND : WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND); - } - - // ARP install location status - if (isMatchingInstaller && WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocation)) - { - installerStatus.Status.emplace_back( - InstalledStatusType::AppsAndFeaturesEntryInstallLocation, - installedLocation.u8string(), - installedLocationStatus); - } - - // ARP install location files - if (isMatchingInstaller && - installedLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND && - WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile)) - { - for (auto const& file : installer.InstallationMetadata.Files) - { - std::filesystem::path filePath = installedLocation / Utility::ConvertToUTF16(file.RelativeFilePath); - auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes); - - installerStatus.Status.emplace_back( - InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile, - filePath.u8string(), - fileStatus); - } - } - } - - // Default install location related checks - if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllDefaultInstallLocationChecks) && installer.InstallationMetadata.HasData()) - { - auto defaultInstalledLocation = Filesystem::GetExpandedPath(installer.InstallationMetadata.DefaultInstallLocation); - HRESULT defaultInstalledLocationStatus = CheckInstalledLocationStatus(defaultInstalledLocation); - - // Default install location status - if (WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocation)) - { - installerStatus.Status.emplace_back( - InstalledStatusType::DefaultInstallLocation, - defaultInstalledLocation.u8string(), - defaultInstalledLocationStatus); - } - - // Default install location files - if (defaultInstalledLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND && - WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocationFile)) - { - for (auto const& file : installer.InstallationMetadata.Files) - { - std::filesystem::path filePath = defaultInstalledLocation / Utility::ConvertToUTF16(file.RelativeFilePath); - auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes); - - installerStatus.Status.emplace_back( - InstalledStatusType::DefaultInstallLocationFile, - filePath.u8string(), - fileStatus); - } - } - } - - if (!installerStatus.Status.empty()) - { - result.emplace_back(std::move(installerStatus)); - } - } - - return result; - } - } - - std::vector CheckPackageInstalledStatus(const std::shared_ptr& package, InstalledStatusType checkTypes) - { - return CheckInstalledStatusInternal(package, checkTypes); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/InstalledStatus.h" +#include "Public/winget/PackageVersionSelection.h" +#include + +using namespace AppInstaller::Settings; +using namespace std::chrono_literals; + +namespace AppInstaller::Repository +{ + namespace + { + HRESULT CheckInstalledLocationStatus(const std::filesystem::path& installedLocation) + { + HRESULT installLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE; + if (!installedLocation.empty()) + { + // Use the none throw version, if the directory cannot be reached, it's treated as not found and later file checks are not performed. + std::error_code error; + installLocationStatus = + std::filesystem::exists(installedLocation, error) && std::filesystem::is_directory(installedLocation, error) ? + WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND : + WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND; + } + + return installLocationStatus; + } + + // Map to cache already calculated file hashes. + struct FilePathComparator + { + bool operator()(const std::filesystem::path& a, const std::filesystem::path& b) const + { + if (std::filesystem::equivalent(a, b)) + { + return false; + } + + return a < b; + } + }; + using FileHashMap = std::map; + + HRESULT CheckInstalledFileStatus( + const std::filesystem::path& filePath, + const Utility::SHA256::HashBuffer& expectedHash, + FileHashMap& fileHashes) + { + HRESULT fileStatus = WINGET_INSTALLED_STATUS_FILE_NOT_FOUND; + try + { + if (std::filesystem::exists(filePath) && std::filesystem::is_regular_file(filePath)) + { + fileStatus = WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK; + if (!expectedHash.empty()) + { + auto itr = fileHashes.find(filePath); + if (itr == fileHashes.end()) + { + // If not found in cache, compute the hash. + std::ifstream in{ filePath, std::ifstream::binary }; + itr = fileHashes.emplace(filePath, Utility::SHA256::ComputeHash(in)).first; + } + + fileStatus = Utility::SHA256::AreEqual(expectedHash, itr->second) ? + WINGET_INSTALLED_STATUS_FILE_HASH_MATCH : WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH; + } + } + } + catch (...) + { + fileStatus = WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR; + } + + return fileStatus; + } + + std::vector CheckInstalledStatusInternal( + const std::shared_ptr& package, + InstalledStatusType checkTypes) + { + using namespace AppInstaller::Manifest; + + std::vector result; + bool checkFileHash = false; + std::shared_ptr installedVersion = GetInstalledVersion(package); + std::shared_ptr availableVersion; + FileHashMap fileHashes; + + // Variables for metadata from installed version. + InstallerTypeEnum installedType = InstallerTypeEnum::Unknown; + ScopeEnum installedScope = ScopeEnum::Unknown; + std::filesystem::path installedLocation; + std::string installedLocale; + Utility::Architecture installedArchitecture = Utility::Architecture::Unknown; + HRESULT installedLocationStatus = WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE; + + std::shared_ptr availableVersions = GetAvailableVersionsForInstalledVersion(package); + + // Prepare installed metadata from installed version. + // Determine the available package version to be used for installed status checking. + // Only perform file hash check if we find an available version that matches installed version. + if (installedVersion) + { + // Installed metadata. + auto installedMetadata = installedVersion->GetMetadata(); + installedType = ConvertToInstallerTypeEnum(installedMetadata[PackageVersionMetadata::InstalledType]); + installedScope = ConvertToScopeEnum(installedMetadata[PackageVersionMetadata::InstalledScope]); + installedLocation = Filesystem::GetExpandedPath(installedMetadata[PackageVersionMetadata::InstalledLocation]); + installedLocale = installedMetadata[PackageVersionMetadata::InstalledLocale]; + installedArchitecture = Utility::ConvertToArchitectureEnum(installedMetadata[PackageVersionMetadata::InstalledArchitecture]); + installedLocationStatus = CheckInstalledLocationStatus(installedLocation); + + // Determine available version. + Utility::Version installedVersionAsVersion{ installedVersion->GetProperty(PackageVersionProperty::Version) }; + auto installedChannel = installedVersion->GetProperty(PackageVersionProperty::Channel); + PackageVersionKey versionKey; + versionKey.Channel = installedChannel.get(); + + if (installedVersionAsVersion.IsApproximate()) + { + // Use the base version as available version if installed version is mapped to be an approximate. + versionKey.Version = installedVersionAsVersion.GetBaseVersion().ToString(); + availableVersion = availableVersions->GetVersion(versionKey); + // It's unexpected if the installed version is already mapped to some version. + THROW_HR_IF(E_UNEXPECTED, !availableVersion); + } + else + { + versionKey.Version = installedVersionAsVersion.ToString(); + availableVersion = availableVersions->GetVersion(versionKey); + if (availableVersion) + { + checkFileHash = true; + } + } + } + + if (!availableVersion) + { + // No installed version, or installed version not found in available versions, + // then attempt to check installed status using latest version. + availableVersion = availableVersions->GetLatestVersion(); + THROW_HR_IF(E_UNEXPECTED, !availableVersion); + } + + auto manifest = availableVersion->GetManifest(); + for (auto const& installer : manifest.Installers) + { + InstallerInstalledStatus installerStatus; + installerStatus.Installer = installer; + + // ARP related checks + if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllAppsAndFeaturesEntryChecks)) + { + bool isMatchingInstaller = + installedVersion && + IsInstallerTypeCompatible(installedType, installer.EffectiveInstallerType()) && + (installedScope == ScopeEnum::Unknown || installer.Scope == ScopeEnum::Unknown || installedScope == installer.Scope) && // Treat unknown scope as compatible + (installedArchitecture == Utility::Architecture::Unknown || installer.Arch == Utility::Architecture::Neutral || installedArchitecture == installer.Arch) && // Treat unknown installed architecture as compatible + (installedLocale.empty() || installer.Locale.empty() || !Locale::IsWellFormedBcp47Tag(installedLocale) || Locale::GetDistanceOfLanguage(installedLocale, installer.Locale) >= Locale::MinimumDistanceScoreAsCompatibleMatch); // Treat invalid locale as compatible + + // ARP entry status + if (WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntry)) + { + installerStatus.Status.emplace_back( + InstalledStatusType::AppsAndFeaturesEntry, + "", + isMatchingInstaller ? WINGET_INSTALLED_STATUS_ARP_ENTRY_FOUND : WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND); + } + + // ARP install location status + if (isMatchingInstaller && WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocation)) + { + installerStatus.Status.emplace_back( + InstalledStatusType::AppsAndFeaturesEntryInstallLocation, + installedLocation.u8string(), + installedLocationStatus); + } + + // ARP install location files + if (isMatchingInstaller && + installedLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND && + WI_IsFlagSet(checkTypes, InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile)) + { + for (auto const& file : installer.InstallationMetadata.Files) + { + std::filesystem::path filePath = installedLocation / Utility::ConvertToUTF16(file.RelativeFilePath); + auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes); + + installerStatus.Status.emplace_back( + InstalledStatusType::AppsAndFeaturesEntryInstallLocationFile, + filePath.u8string(), + fileStatus); + } + } + } + + // Default install location related checks + if (WI_IsAnyFlagSet(checkTypes, InstalledStatusType::AllDefaultInstallLocationChecks) && installer.InstallationMetadata.HasData()) + { + auto defaultInstalledLocation = Filesystem::GetExpandedPath(installer.InstallationMetadata.DefaultInstallLocation); + HRESULT defaultInstalledLocationStatus = CheckInstalledLocationStatus(defaultInstalledLocation); + + // Default install location status + if (WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocation)) + { + installerStatus.Status.emplace_back( + InstalledStatusType::DefaultInstallLocation, + defaultInstalledLocation.u8string(), + defaultInstalledLocationStatus); + } + + // Default install location files + if (defaultInstalledLocationStatus == WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND && + WI_IsFlagSet(checkTypes, InstalledStatusType::DefaultInstallLocationFile)) + { + for (auto const& file : installer.InstallationMetadata.Files) + { + std::filesystem::path filePath = defaultInstalledLocation / Utility::ConvertToUTF16(file.RelativeFilePath); + auto fileStatus = CheckInstalledFileStatus(filePath, checkFileHash ? file.FileSha256 : Utility::SHA256::HashBuffer{}, fileHashes); + + installerStatus.Status.emplace_back( + InstalledStatusType::DefaultInstallLocationFile, + filePath.u8string(), + fileStatus); + } + } + } + + if (!installerStatus.Status.empty()) + { + result.emplace_back(std::move(installerStatus)); + } + } + + return result; + } + } + + std::vector CheckPackageInstalledStatus(const std::shared_ptr& package, InstalledStatusType checkTypes) + { + return CheckInstalledStatusInternal(package, checkTypes); + } +} diff --git a/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp b/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp index 76a2311cfb..bcbfb91292 100644 --- a/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp +++ b/src/AppInstallerRepositoryCore/PackageTrackingCatalog.cpp @@ -1,322 +1,322 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/PackageTrackingCatalog.h" -#include "PackageTrackingCatalogSourceFactory.h" -#include "winget/Pin.h" -#include "winget/RepositorySource.h" -#include "Microsoft/SQLiteIndexSource.h" -#include "AppInstallerDateTime.h" - -using namespace std::string_literals; -using namespace AppInstaller::Repository::Microsoft; - - -namespace AppInstaller::Repository -{ - namespace - { - constexpr std::string_view c_PackageTrackingFileName = "installed.db"; - constexpr std::string_view c_PackageTrackingCorruptedFileName = "installed-corrupted.db"; - - std::string CreateNameForCPL(const std::string& pathName) - { - return "PackageTrackingCPL_"s + pathName; - } - - std::filesystem::path GetPackageTrackingFilePath(const std::string& pathName) - { - std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); - result /= pathName; - result /= c_PackageTrackingFileName; - return result; - } - - // Call while holding the CrossProcessLock - SQLiteIndex CreateOnlyTrackingIndex(const std::filesystem::path& trackingDB) - { - return SQLiteIndex::CreateNew(trackingDB.u8string(), SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport); - } - - // Call while holding the CrossProcessLock - SQLiteIndex CreateOrOpenTrackingIndex(const std::filesystem::path& trackingDB) - { - if (!std::filesystem::exists(trackingDB)) - { - std::filesystem::create_directories(trackingDB.parent_path()); - return CreateOnlyTrackingIndex(trackingDB); - } - else - { - try - { - // TODO: Check schema version and upgrade as necessary when there is a relevant new schema. - // Could write this all now but it will be better tested when there is a new schema. - return SQLiteIndex::Open(trackingDB.u8string(), SQLiteIndex::OpenDisposition::ReadWrite); - } - catch(...) - { - LOG_CAUGHT_EXCEPTION_MSG("Exception opening tracking catalog"); - } - - // Move existing database and create a new one - std::filesystem::path destination{ trackingDB }; - destination.replace_filename(c_PackageTrackingCorruptedFileName); - SQLite::SQLiteStorageBase::RenameSQLiteDatabase(trackingDB, destination, true); - - return CreateOnlyTrackingIndex(trackingDB); - } - } - - struct PackageTrackingCatalogSourceReference : public ISourceReference - { - PackageTrackingCatalogSourceReference(const SourceDetails& details) : m_details(details) {} - - SourceDetails& GetDetails() override - { - return m_details; - } - - std::string GetIdentifier() override - { - return m_details.Identifier; - } - - std::shared_ptr Open(IProgressCallback& callback) override - { - m_details.Arg = Utility::MakeSuitablePathPart(m_details.Data); - std::filesystem::path trackingDB = GetPackageTrackingFilePath(m_details.Arg); - - Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details.Arg)); - if (!lock.Acquire(callback)) - { - return {}; - } - - return std::make_shared(m_details, CreateOrOpenTrackingIndex(trackingDB)); - } - - private: - // Store the identifier of the source in the Data field. - SourceDetails m_details; - }; - - struct PackageTrackingCatalogSourceFactoryImpl : public ISourceFactory - { - std::string_view TypeName() const override final - { - return PackageTrackingCatalogSourceFactory::Type(); - } - - std::shared_ptr Create(const SourceDetails& details) override final - { - THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, PackageTrackingCatalogSourceFactory::Type())); - - return std::make_shared(details); - } - - bool Add(SourceDetails&, IProgressCallback&) override final - { - THROW_HR(E_NOTIMPL); - } - - bool Update(const SourceDetails&, IProgressCallback&) override final - { - THROW_HR(E_NOTIMPL); - } - - bool Remove(const SourceDetails& details, IProgressCallback& progress) override final - { - THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, PackageTrackingCatalogSourceFactory::Type())); - - std::string pathName = Utility::MakeSuitablePathPart(details.Data); - - Synchronization::CrossProcessLock lock(CreateNameForCPL(pathName)); - if (!lock.Acquire(progress)) - { - return false; - } - - std::filesystem::path trackingDB = GetPackageTrackingFilePath(pathName); - - if (std::filesystem::exists(trackingDB)) - { - std::filesystem::remove(trackingDB); - } - - return true; - } - }; - } - - struct PackageTrackingCatalog::implementation - { - std::shared_ptr Source; - }; - - PackageTrackingCatalog::PackageTrackingCatalog() = default; - PackageTrackingCatalog::PackageTrackingCatalog(const PackageTrackingCatalog&) = default; - PackageTrackingCatalog& PackageTrackingCatalog::operator=(const PackageTrackingCatalog&) = default; - PackageTrackingCatalog::PackageTrackingCatalog(PackageTrackingCatalog&&) noexcept = default; - PackageTrackingCatalog& PackageTrackingCatalog::operator=(PackageTrackingCatalog&&) noexcept = default; - PackageTrackingCatalog::~PackageTrackingCatalog() = default; - - PackageTrackingCatalog PackageTrackingCatalog::CreateForSource(const Source& source) - { - // Not a valid source for tracking - const std::string sourceIdentifier = source.GetIdentifier(); - if (sourceIdentifier.empty() || !source.ContainsAvailablePackages()) - { - THROW_HR(E_INVALIDARG); - } - - // Create fake details for the source while stashing some information that might be helpful for debugging - SourceDetails details; - details.Type = PackageTrackingCatalogSourceFactory::Type(); - details.Identifier = "*Tracking"; - details.Name = "Tracking for "s + source.GetDetails().Name; - details.Origin = SourceOrigin::PackageTracking; - details.Data = sourceIdentifier; - - ProgressCallback dummyProgress; - - PackageTrackingCatalog result; - result.m_implementation = std::make_shared(); - result.m_implementation->Source = SourceCast(ISourceFactory::GetForType(details.Type)->Create(details)->Open(dummyProgress)); - - return result; - } - - void PackageTrackingCatalog::RemoveForSource(const std::string& identifier) - { - if (identifier.empty()) - { - THROW_HR(E_INVALIDARG); - } - - // Create details to pass to the factory; the identifier of the source is passed in the Data field. - SourceDetails dummyDetails; - dummyDetails.Type = PackageTrackingCatalogSourceFactory::Type(); - dummyDetails.Data = identifier; - - ProgressCallback dummyProgress; - - ISourceFactory::GetForType(dummyDetails.Type)->Remove(dummyDetails, dummyProgress); - } - - PackageTrackingCatalog::operator bool() const - { - return static_cast(m_implementation); - } - - SearchResult PackageTrackingCatalog::Search(const SearchRequest& request) const - { - return m_implementation->Source->Search(request); - } - - struct PackageTrackingCatalog::Version::implementation - { - SQLiteIndex::IdType Id; - }; - - PackageTrackingCatalog::Version::Version(const Version&) = default; - PackageTrackingCatalog::Version& PackageTrackingCatalog::Version::operator=(const Version&) = default; - PackageTrackingCatalog::Version::Version(Version&&) noexcept = default; - PackageTrackingCatalog::Version& PackageTrackingCatalog::Version::operator=(Version&&) noexcept = default; - PackageTrackingCatalog::Version::~Version() = default; - - PackageTrackingCatalog::Version::Version(PackageTrackingCatalog& catalog, std::shared_ptr&& value) : - m_catalog(&catalog), m_implementation(std::move(value)) {} - - void PackageTrackingCatalog::Version::SetMetadata(PackageVersionMetadata metadata, const Utility::NormalizedString& value) - { - auto& index = m_catalog->m_implementation->Source->GetIndex(); - index.SetMetadataByManifestId(m_implementation->Id, metadata, value); - } - - PackageTrackingCatalog::Version PackageTrackingCatalog::RecordInstall( - Manifest::Manifest& manifest, - const Manifest::ManifestInstaller& installer, - bool isUpgrade) - { - // TODO: Store additional information from these if needed - UNREFERENCED_PARAMETER(isUpgrade); - - auto& index = m_implementation->Source->GetIndex(); - - // Strip ARP version information from the manifest if it is present - for (auto& arpRangeRemovedInstaller : manifest.Installers) - { - for (auto& arpRangeRemovedEntry : arpRangeRemovedInstaller.AppsAndFeaturesEntries) - { - arpRangeRemovedEntry.DisplayVersion.clear(); - } - } - - // Check for an existing manifest that matches this one (could be reinstalling) - auto manifestIdOpt = index.GetManifestIdByManifest(manifest); - - if (manifestIdOpt) - { - index.UpdateManifest(manifest); - } - else - { - manifestIdOpt = index.AddManifest(manifest); - } - - SQLiteIndex::IdType manifestId = manifestIdOpt.value(); - - // Write additional metadata for package tracking - std::ostringstream strstr; - strstr << Utility::GetCurrentUnixEpoch(); - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::TrackingWriteTime, strstr.str()); - - if (installer.RequireExplicitUpgrade) - { - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::PinnedState, ToString(Pinning::PinType::PinnedByManifest)); - } - - // Record installed architecture and locale if applicable - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, ToString(installer.Arch)); - if (!installer.Locale.empty()) - { - index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledLocale, installer.Locale); - } - - std::shared_ptr result = std::make_shared(); - result->Id = manifestId; - return { *this, std::move(result) }; - } - - void PackageTrackingCatalog::RecordUninstall(const Utility::LocIndString& packageIdentifier) - { - auto& index = m_implementation->Source->GetIndex(); - - SearchRequest idSearch; - idSearch.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier.get()); - auto searchResult = index.Search(idSearch); - - for (const auto& match : searchResult.Matches) - { - auto versions = index.GetVersionKeysById(match.first); - - for (const auto& version : versions) - { - index.RemoveManifestById(version.ManifestId); - } - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - std::filesystem::path PackageTrackingCatalog::GetFilePath() const - { - return m_implementation->Source->GetIndex().GetContextData().Get(); - } -#endif - - std::unique_ptr PackageTrackingCatalogSourceFactory::Create() - { - return std::make_unique(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/PackageTrackingCatalog.h" +#include "PackageTrackingCatalogSourceFactory.h" +#include "winget/Pin.h" +#include "winget/RepositorySource.h" +#include "Microsoft/SQLiteIndexSource.h" +#include "AppInstallerDateTime.h" + +using namespace std::string_literals; +using namespace AppInstaller::Repository::Microsoft; + + +namespace AppInstaller::Repository +{ + namespace + { + constexpr std::string_view c_PackageTrackingFileName = "installed.db"; + constexpr std::string_view c_PackageTrackingCorruptedFileName = "installed-corrupted.db"; + + std::string CreateNameForCPL(const std::string& pathName) + { + return "PackageTrackingCPL_"s + pathName; + } + + std::filesystem::path GetPackageTrackingFilePath(const std::string& pathName) + { + std::filesystem::path result = Runtime::GetPathTo(Runtime::PathName::LocalState); + result /= pathName; + result /= c_PackageTrackingFileName; + return result; + } + + // Call while holding the CrossProcessLock + SQLiteIndex CreateOnlyTrackingIndex(const std::filesystem::path& trackingDB) + { + return SQLiteIndex::CreateNew(trackingDB.u8string(), SQLite::Version::Latest(), SQLiteIndex::CreateOptions::SupportPathless | SQLiteIndex::CreateOptions::DisableDependenciesSupport); + } + + // Call while holding the CrossProcessLock + SQLiteIndex CreateOrOpenTrackingIndex(const std::filesystem::path& trackingDB) + { + if (!std::filesystem::exists(trackingDB)) + { + std::filesystem::create_directories(trackingDB.parent_path()); + return CreateOnlyTrackingIndex(trackingDB); + } + else + { + try + { + // TODO: Check schema version and upgrade as necessary when there is a relevant new schema. + // Could write this all now but it will be better tested when there is a new schema. + return SQLiteIndex::Open(trackingDB.u8string(), SQLiteIndex::OpenDisposition::ReadWrite); + } + catch(...) + { + LOG_CAUGHT_EXCEPTION_MSG("Exception opening tracking catalog"); + } + + // Move existing database and create a new one + std::filesystem::path destination{ trackingDB }; + destination.replace_filename(c_PackageTrackingCorruptedFileName); + SQLite::SQLiteStorageBase::RenameSQLiteDatabase(trackingDB, destination, true); + + return CreateOnlyTrackingIndex(trackingDB); + } + } + + struct PackageTrackingCatalogSourceReference : public ISourceReference + { + PackageTrackingCatalogSourceReference(const SourceDetails& details) : m_details(details) {} + + SourceDetails& GetDetails() override + { + return m_details; + } + + std::string GetIdentifier() override + { + return m_details.Identifier; + } + + std::shared_ptr Open(IProgressCallback& callback) override + { + m_details.Arg = Utility::MakeSuitablePathPart(m_details.Data); + std::filesystem::path trackingDB = GetPackageTrackingFilePath(m_details.Arg); + + Synchronization::CrossProcessLock lock(CreateNameForCPL(m_details.Arg)); + if (!lock.Acquire(callback)) + { + return {}; + } + + return std::make_shared(m_details, CreateOrOpenTrackingIndex(trackingDB)); + } + + private: + // Store the identifier of the source in the Data field. + SourceDetails m_details; + }; + + struct PackageTrackingCatalogSourceFactoryImpl : public ISourceFactory + { + std::string_view TypeName() const override final + { + return PackageTrackingCatalogSourceFactory::Type(); + } + + std::shared_ptr Create(const SourceDetails& details) override final + { + THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, PackageTrackingCatalogSourceFactory::Type())); + + return std::make_shared(details); + } + + bool Add(SourceDetails&, IProgressCallback&) override final + { + THROW_HR(E_NOTIMPL); + } + + bool Update(const SourceDetails&, IProgressCallback&) override final + { + THROW_HR(E_NOTIMPL); + } + + bool Remove(const SourceDetails& details, IProgressCallback& progress) override final + { + THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, PackageTrackingCatalogSourceFactory::Type())); + + std::string pathName = Utility::MakeSuitablePathPart(details.Data); + + Synchronization::CrossProcessLock lock(CreateNameForCPL(pathName)); + if (!lock.Acquire(progress)) + { + return false; + } + + std::filesystem::path trackingDB = GetPackageTrackingFilePath(pathName); + + if (std::filesystem::exists(trackingDB)) + { + std::filesystem::remove(trackingDB); + } + + return true; + } + }; + } + + struct PackageTrackingCatalog::implementation + { + std::shared_ptr Source; + }; + + PackageTrackingCatalog::PackageTrackingCatalog() = default; + PackageTrackingCatalog::PackageTrackingCatalog(const PackageTrackingCatalog&) = default; + PackageTrackingCatalog& PackageTrackingCatalog::operator=(const PackageTrackingCatalog&) = default; + PackageTrackingCatalog::PackageTrackingCatalog(PackageTrackingCatalog&&) noexcept = default; + PackageTrackingCatalog& PackageTrackingCatalog::operator=(PackageTrackingCatalog&&) noexcept = default; + PackageTrackingCatalog::~PackageTrackingCatalog() = default; + + PackageTrackingCatalog PackageTrackingCatalog::CreateForSource(const Source& source) + { + // Not a valid source for tracking + const std::string sourceIdentifier = source.GetIdentifier(); + if (sourceIdentifier.empty() || !source.ContainsAvailablePackages()) + { + THROW_HR(E_INVALIDARG); + } + + // Create fake details for the source while stashing some information that might be helpful for debugging + SourceDetails details; + details.Type = PackageTrackingCatalogSourceFactory::Type(); + details.Identifier = "*Tracking"; + details.Name = "Tracking for "s + source.GetDetails().Name; + details.Origin = SourceOrigin::PackageTracking; + details.Data = sourceIdentifier; + + ProgressCallback dummyProgress; + + PackageTrackingCatalog result; + result.m_implementation = std::make_shared(); + result.m_implementation->Source = SourceCast(ISourceFactory::GetForType(details.Type)->Create(details)->Open(dummyProgress)); + + return result; + } + + void PackageTrackingCatalog::RemoveForSource(const std::string& identifier) + { + if (identifier.empty()) + { + THROW_HR(E_INVALIDARG); + } + + // Create details to pass to the factory; the identifier of the source is passed in the Data field. + SourceDetails dummyDetails; + dummyDetails.Type = PackageTrackingCatalogSourceFactory::Type(); + dummyDetails.Data = identifier; + + ProgressCallback dummyProgress; + + ISourceFactory::GetForType(dummyDetails.Type)->Remove(dummyDetails, dummyProgress); + } + + PackageTrackingCatalog::operator bool() const + { + return static_cast(m_implementation); + } + + SearchResult PackageTrackingCatalog::Search(const SearchRequest& request) const + { + return m_implementation->Source->Search(request); + } + + struct PackageTrackingCatalog::Version::implementation + { + SQLiteIndex::IdType Id; + }; + + PackageTrackingCatalog::Version::Version(const Version&) = default; + PackageTrackingCatalog::Version& PackageTrackingCatalog::Version::operator=(const Version&) = default; + PackageTrackingCatalog::Version::Version(Version&&) noexcept = default; + PackageTrackingCatalog::Version& PackageTrackingCatalog::Version::operator=(Version&&) noexcept = default; + PackageTrackingCatalog::Version::~Version() = default; + + PackageTrackingCatalog::Version::Version(PackageTrackingCatalog& catalog, std::shared_ptr&& value) : + m_catalog(&catalog), m_implementation(std::move(value)) {} + + void PackageTrackingCatalog::Version::SetMetadata(PackageVersionMetadata metadata, const Utility::NormalizedString& value) + { + auto& index = m_catalog->m_implementation->Source->GetIndex(); + index.SetMetadataByManifestId(m_implementation->Id, metadata, value); + } + + PackageTrackingCatalog::Version PackageTrackingCatalog::RecordInstall( + Manifest::Manifest& manifest, + const Manifest::ManifestInstaller& installer, + bool isUpgrade) + { + // TODO: Store additional information from these if needed + UNREFERENCED_PARAMETER(isUpgrade); + + auto& index = m_implementation->Source->GetIndex(); + + // Strip ARP version information from the manifest if it is present + for (auto& arpRangeRemovedInstaller : manifest.Installers) + { + for (auto& arpRangeRemovedEntry : arpRangeRemovedInstaller.AppsAndFeaturesEntries) + { + arpRangeRemovedEntry.DisplayVersion.clear(); + } + } + + // Check for an existing manifest that matches this one (could be reinstalling) + auto manifestIdOpt = index.GetManifestIdByManifest(manifest); + + if (manifestIdOpt) + { + index.UpdateManifest(manifest); + } + else + { + manifestIdOpt = index.AddManifest(manifest); + } + + SQLiteIndex::IdType manifestId = manifestIdOpt.value(); + + // Write additional metadata for package tracking + std::ostringstream strstr; + strstr << Utility::GetCurrentUnixEpoch(); + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::TrackingWriteTime, strstr.str()); + + if (installer.RequireExplicitUpgrade) + { + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::PinnedState, ToString(Pinning::PinType::PinnedByManifest)); + } + + // Record installed architecture and locale if applicable + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledArchitecture, ToString(installer.Arch)); + if (!installer.Locale.empty()) + { + index.SetMetadataByManifestId(manifestId, PackageVersionMetadata::InstalledLocale, installer.Locale); + } + + std::shared_ptr result = std::make_shared(); + result->Id = manifestId; + return { *this, std::move(result) }; + } + + void PackageTrackingCatalog::RecordUninstall(const Utility::LocIndString& packageIdentifier) + { + auto& index = m_implementation->Source->GetIndex(); + + SearchRequest idSearch; + idSearch.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, packageIdentifier.get()); + auto searchResult = index.Search(idSearch); + + for (const auto& match : searchResult.Matches) + { + auto versions = index.GetVersionKeysById(match.first); + + for (const auto& version : versions) + { + index.RemoveManifestById(version.ManifestId); + } + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + std::filesystem::path PackageTrackingCatalog::GetFilePath() const + { + return m_implementation->Source->GetIndex().GetContextData().Get(); + } +#endif + + std::unique_ptr PackageTrackingCatalogSourceFactory::Create() + { + return std::make_unique(); + } +} diff --git a/src/AppInstallerRepositoryCore/PackageTrackingCatalogSourceFactory.h b/src/AppInstallerRepositoryCore/PackageTrackingCatalogSourceFactory.h index dabbe2c660..39cbed338d 100644 --- a/src/AppInstallerRepositoryCore/PackageTrackingCatalogSourceFactory.h +++ b/src/AppInstallerRepositoryCore/PackageTrackingCatalogSourceFactory.h @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "SourceFactory.h" - -using namespace std::string_view_literals; - - -namespace AppInstaller::Repository -{ - // Enable test to inject a custom tracking source by integrating the internal source creation into the standard flows. - // If overriding, the opened ISource must be a SQLiteIndexSource or the code will fail. - struct PackageTrackingCatalogSourceFactory - { - // Get the type string for this source. - static constexpr std::string_view Type() - { - using namespace std::string_view_literals; - return "Microsoft.PackageTracking"sv; - } - - // Creates a source factory for this type. - static std::unique_ptr Create(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SourceFactory.h" + +using namespace std::string_view_literals; + + +namespace AppInstaller::Repository +{ + // Enable test to inject a custom tracking source by integrating the internal source creation into the standard flows. + // If overriding, the opened ISource must be a SQLiteIndexSource or the code will fail. + struct PackageTrackingCatalogSourceFactory + { + // Get the type string for this source. + static constexpr std::string_view Type() + { + using namespace std::string_view_literals; + return "Microsoft.PackageTracking"sv; + } + + // Creates a source factory for this type. + static std::unique_ptr Create(); + }; +} diff --git a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp index 5303b6e248..1546e9a52c 100644 --- a/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp +++ b/src/AppInstallerRepositoryCore/PackageVersionSelection.cpp @@ -1,308 +1,308 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/PackageVersionSelection.h" -#include "Public/winget/RepositorySource.h" -#include "Public/winget/PinningData.h" - - -namespace AppInstaller::Repository -{ - namespace - { - std::shared_ptr GetAvailablePackageFromSource(const std::vector>& packages, const std::string_view sourceIdentifier) - { - for (const std::shared_ptr& package : packages) - { - if (sourceIdentifier == package->GetSource().GetIdentifier()) - { - return package; - } - } - - return {}; - } - - struct AvailablePackageVersionCollection : public IPackageVersionCollection - { - AvailablePackageVersionCollection(const std::shared_ptr& composite, const std::shared_ptr& installedVersion) : - m_packages(composite->GetAvailable()) - { - if (!installedVersion) - { - return; - } - - m_channel = installedVersion->GetProperty(PackageVersionProperty::Channel); - - // Remove the packages that are not from the installed source. - Source installedVersionSource = installedVersion->GetSource(); - if (installedVersionSource && installedVersionSource.ContainsAvailablePackages()) - { - m_packages.erase(std::remove_if(m_packages.begin(), m_packages.end(), [&](const std::shared_ptr& p) { return installedVersionSource != p->GetSource(); }), m_packages.end()); - } - } - - std::vector GetVersionKeys() const override - { - std::vector result; - - for (const std::shared_ptr& package : m_packages) - { - std::vector versionKeys = package->GetVersionKeys(); - std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); - } - - // Remove all elements whose channel does not match the installed package. - if (m_channel) - { - result.erase( - std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, m_channel.value()); }), - result.end()); - } - - // Put latest versions at the front; for versions available from multiple sources maintain the order they were added in - std::stable_sort(result.begin(), result.end()); - - return result; - } - - std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override - { - // If there is a specific source, just use that package's result - std::shared_ptr package; - - if (!versionKey.SourceId.empty()) - { - package = GetAvailablePackageFromSource(m_packages, versionKey.SourceId); - } - else if (m_packages.size() == 1) - { - package = m_packages[0]; - } - else if (versionKey.IsDefaultLatest()) - { - std::shared_ptr result; - Utility::Version resultVersion; - - for (const auto& p : m_packages) - { - std::shared_ptr latest = p->GetLatestVersion(); - Utility::Version version { latest->GetProperty(PackageVersionProperty::Version) }; - - if (!result || resultVersion < version) - { - result = std::move(latest); - resultVersion = std::move(version); - } - } - - return result; - } - else - { - // Otherwise, find the first version that matches - std::vector versions = GetVersionKeys(); - - for (const PackageVersionKey& key : versions) - { - if (key.IsMatch(versionKey)) - { - package = GetAvailablePackageFromSource(m_packages, key.SourceId); - break; - } - } - } - - return package ? package->GetVersion(versionKey) : nullptr; - } - - std::shared_ptr GetLatestVersion() const override - { - return GetVersion({ "", "", m_channel.value_or("") }); - } - - private: - std::optional m_channel; - std::vector> m_packages; - }; - } - - std::shared_ptr GetAvailableVersionsForInstalledVersion(const std::shared_ptr& composite) - { - return std::make_shared(composite, GetInstalledVersion(composite)); - } - - std::shared_ptr GetAvailableVersionsForInstalledVersion( - const std::shared_ptr& composite, - const std::shared_ptr& installedVersion) - { - return std::make_shared(composite, installedVersion); - } - - std::shared_ptr GetAllAvailableVersions(const std::shared_ptr& composite) - { - return GetAvailableVersionsForInstalledVersion(composite, nullptr); - } - - std::shared_ptr GetInstalledVersion(const std::shared_ptr& composite) - { - auto installedPackage = composite->GetInstalled(); - return installedPackage ? installedPackage->GetLatestVersion() : nullptr; - } - - std::shared_ptr GetAvailablePackageFromSource(const std::shared_ptr& composite, const std::string_view sourceIdentifier) - { - return GetAvailablePackageFromSource(composite->GetAvailable(), sourceIdentifier); - } - - LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr& composite) - { - using namespace AppInstaller::Pinning; - - LatestApplicableVersionData result; - - auto installedVersion = AppInstaller::Repository::GetInstalledVersion(composite); - auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(composite, installedVersion); - - PinningData pinningData{ PinningData::Disposition::ReadOnly }; - auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, installedVersion); - - AppInstaller::Manifest::ManifestComparator::Options options; - if (installedVersion) - { - GetManifestComparatorOptionsFromMetadata(options, installedVersion->GetMetadata()); - } - AppInstaller::Manifest::ManifestComparator manifestComparator{ options }; - - auto availableVersionKeys = availableVersions->GetVersionKeys(); - for (const auto& availableVersionKey : availableVersionKeys) - { - auto availableVersion = availableVersions->GetVersion(availableVersionKey); - - if (installedVersion && !evaluator.IsUpdate(availableVersion)) - { - // Version too low or different channel for upgrade - continue; - } - - if (evaluator.EvaluatePinType(availableVersion) != AppInstaller::Pinning::PinType::Unknown) - { - // Pinned - continue; - } - - auto manifestComparatorResult = manifestComparator.GetPreferredInstaller(availableVersion->GetManifest()); - if (!manifestComparatorResult.installer.has_value()) - { - // No applicable installer - continue; - } - - result.LatestApplicableVersion = availableVersion; - if (installedVersion) - { - result.UpdateAvailable = true; - } - - break; - } - - return result; - } - - void GetManifestComparatorOptionsFromMetadata(AppInstaller::Manifest::ManifestComparator::Options& options, const IPackageVersion::Metadata& metadata, bool includeAllowedArchitectures) - { - auto installedTypeItr = metadata.find(Repository::PackageVersionMetadata::InstalledType); - if (installedTypeItr != metadata.end()) - { - options.CurrentlyInstalledType = Manifest::ConvertToInstallerTypeEnum(installedTypeItr->second); - } - - auto installedScopeItr = metadata.find(Repository::PackageVersionMetadata::InstalledScope); - if (installedScopeItr != metadata.end()) - { - options.CurrentlyInstalledScope = Manifest::ConvertToScopeEnum(installedScopeItr->second); - } - - auto userIntentLocaleItr = metadata.find(Repository::PackageVersionMetadata::UserIntentLocale); - if (userIntentLocaleItr != metadata.end()) - { - options.PreviousUserIntentLocale = userIntentLocaleItr->second; - } - - auto installedLocaleItr = metadata.find(Repository::PackageVersionMetadata::InstalledLocale); - if (installedLocaleItr != metadata.end()) - { - options.CurrentlyInstalledLocale = installedLocaleItr->second; - } - - if (includeAllowedArchitectures) - { - auto userIntentItr = metadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); - if (userIntentItr != metadata.end()) - { - // For upgrade, user intent from previous install is considered requirement - options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(userIntentItr->second)); - } - else - { - auto installedItr = metadata.find(Repository::PackageVersionMetadata::InstalledArchitecture); - if (installedItr != metadata.end()) - { - // For upgrade, previous installed architecture should be considered first preference and is always allowed. - // Then check settings requirements and preferences. - options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(installedItr->second)); - } - - std::vector requiredArchitectures = Settings::User().Get(); - std::vector optionalArchitectures = Settings::User().Get(); - - if (!requiredArchitectures.empty()) - { - // Required architecture list from settings if applicable - options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), requiredArchitectures.begin(), requiredArchitectures.end()); - } - else - { - // Preferred architecture list from settings if applicable, add Unknown to indicate allowing remaining applicable - if (!optionalArchitectures.empty()) - { - options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), optionalArchitectures.begin(), optionalArchitectures.end()); - } - - options.AllowedArchitectures.emplace_back(Utility::Architecture::Unknown); - } - } - } - } - - std::optional GetSourcePriority(const std::shared_ptr& composite) - { - auto installed = composite->GetInstalled(); - - if (installed) - { - auto installedVersion = installed->GetLatestVersion(); - - if (installedVersion) - { - auto installedSource = installedVersion->GetSource(); - - if (installedSource.ContainsAvailablePackages()) - { - return installedSource.GetDetails().Priority; - } - } - } - - auto available = composite->GetAvailable(); - - if (!available.empty()) - { - return available.front()->GetSource().GetDetails().Priority; - } - - return std::nullopt; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/PackageVersionSelection.h" +#include "Public/winget/RepositorySource.h" +#include "Public/winget/PinningData.h" + + +namespace AppInstaller::Repository +{ + namespace + { + std::shared_ptr GetAvailablePackageFromSource(const std::vector>& packages, const std::string_view sourceIdentifier) + { + for (const std::shared_ptr& package : packages) + { + if (sourceIdentifier == package->GetSource().GetIdentifier()) + { + return package; + } + } + + return {}; + } + + struct AvailablePackageVersionCollection : public IPackageVersionCollection + { + AvailablePackageVersionCollection(const std::shared_ptr& composite, const std::shared_ptr& installedVersion) : + m_packages(composite->GetAvailable()) + { + if (!installedVersion) + { + return; + } + + m_channel = installedVersion->GetProperty(PackageVersionProperty::Channel); + + // Remove the packages that are not from the installed source. + Source installedVersionSource = installedVersion->GetSource(); + if (installedVersionSource && installedVersionSource.ContainsAvailablePackages()) + { + m_packages.erase(std::remove_if(m_packages.begin(), m_packages.end(), [&](const std::shared_ptr& p) { return installedVersionSource != p->GetSource(); }), m_packages.end()); + } + } + + std::vector GetVersionKeys() const override + { + std::vector result; + + for (const std::shared_ptr& package : m_packages) + { + std::vector versionKeys = package->GetVersionKeys(); + std::copy(versionKeys.begin(), versionKeys.end(), std::back_inserter(result)); + } + + // Remove all elements whose channel does not match the installed package. + if (m_channel) + { + result.erase( + std::remove_if(result.begin(), result.end(), [&](const PackageVersionKey& pvk) { return !Utility::ICUCaseInsensitiveEquals(pvk.Channel, m_channel.value()); }), + result.end()); + } + + // Put latest versions at the front; for versions available from multiple sources maintain the order they were added in + std::stable_sort(result.begin(), result.end()); + + return result; + } + + std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override + { + // If there is a specific source, just use that package's result + std::shared_ptr package; + + if (!versionKey.SourceId.empty()) + { + package = GetAvailablePackageFromSource(m_packages, versionKey.SourceId); + } + else if (m_packages.size() == 1) + { + package = m_packages[0]; + } + else if (versionKey.IsDefaultLatest()) + { + std::shared_ptr result; + Utility::Version resultVersion; + + for (const auto& p : m_packages) + { + std::shared_ptr latest = p->GetLatestVersion(); + Utility::Version version { latest->GetProperty(PackageVersionProperty::Version) }; + + if (!result || resultVersion < version) + { + result = std::move(latest); + resultVersion = std::move(version); + } + } + + return result; + } + else + { + // Otherwise, find the first version that matches + std::vector versions = GetVersionKeys(); + + for (const PackageVersionKey& key : versions) + { + if (key.IsMatch(versionKey)) + { + package = GetAvailablePackageFromSource(m_packages, key.SourceId); + break; + } + } + } + + return package ? package->GetVersion(versionKey) : nullptr; + } + + std::shared_ptr GetLatestVersion() const override + { + return GetVersion({ "", "", m_channel.value_or("") }); + } + + private: + std::optional m_channel; + std::vector> m_packages; + }; + } + + std::shared_ptr GetAvailableVersionsForInstalledVersion(const std::shared_ptr& composite) + { + return std::make_shared(composite, GetInstalledVersion(composite)); + } + + std::shared_ptr GetAvailableVersionsForInstalledVersion( + const std::shared_ptr& composite, + const std::shared_ptr& installedVersion) + { + return std::make_shared(composite, installedVersion); + } + + std::shared_ptr GetAllAvailableVersions(const std::shared_ptr& composite) + { + return GetAvailableVersionsForInstalledVersion(composite, nullptr); + } + + std::shared_ptr GetInstalledVersion(const std::shared_ptr& composite) + { + auto installedPackage = composite->GetInstalled(); + return installedPackage ? installedPackage->GetLatestVersion() : nullptr; + } + + std::shared_ptr GetAvailablePackageFromSource(const std::shared_ptr& composite, const std::string_view sourceIdentifier) + { + return GetAvailablePackageFromSource(composite->GetAvailable(), sourceIdentifier); + } + + LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr& composite) + { + using namespace AppInstaller::Pinning; + + LatestApplicableVersionData result; + + auto installedVersion = AppInstaller::Repository::GetInstalledVersion(composite); + auto availableVersions = AppInstaller::Repository::GetAvailableVersionsForInstalledVersion(composite, installedVersion); + + PinningData pinningData{ PinningData::Disposition::ReadOnly }; + auto evaluator = pinningData.CreatePinStateEvaluator(PinBehavior::ConsiderPins, installedVersion); + + AppInstaller::Manifest::ManifestComparator::Options options; + if (installedVersion) + { + GetManifestComparatorOptionsFromMetadata(options, installedVersion->GetMetadata()); + } + AppInstaller::Manifest::ManifestComparator manifestComparator{ options }; + + auto availableVersionKeys = availableVersions->GetVersionKeys(); + for (const auto& availableVersionKey : availableVersionKeys) + { + auto availableVersion = availableVersions->GetVersion(availableVersionKey); + + if (installedVersion && !evaluator.IsUpdate(availableVersion)) + { + // Version too low or different channel for upgrade + continue; + } + + if (evaluator.EvaluatePinType(availableVersion) != AppInstaller::Pinning::PinType::Unknown) + { + // Pinned + continue; + } + + auto manifestComparatorResult = manifestComparator.GetPreferredInstaller(availableVersion->GetManifest()); + if (!manifestComparatorResult.installer.has_value()) + { + // No applicable installer + continue; + } + + result.LatestApplicableVersion = availableVersion; + if (installedVersion) + { + result.UpdateAvailable = true; + } + + break; + } + + return result; + } + + void GetManifestComparatorOptionsFromMetadata(AppInstaller::Manifest::ManifestComparator::Options& options, const IPackageVersion::Metadata& metadata, bool includeAllowedArchitectures) + { + auto installedTypeItr = metadata.find(Repository::PackageVersionMetadata::InstalledType); + if (installedTypeItr != metadata.end()) + { + options.CurrentlyInstalledType = Manifest::ConvertToInstallerTypeEnum(installedTypeItr->second); + } + + auto installedScopeItr = metadata.find(Repository::PackageVersionMetadata::InstalledScope); + if (installedScopeItr != metadata.end()) + { + options.CurrentlyInstalledScope = Manifest::ConvertToScopeEnum(installedScopeItr->second); + } + + auto userIntentLocaleItr = metadata.find(Repository::PackageVersionMetadata::UserIntentLocale); + if (userIntentLocaleItr != metadata.end()) + { + options.PreviousUserIntentLocale = userIntentLocaleItr->second; + } + + auto installedLocaleItr = metadata.find(Repository::PackageVersionMetadata::InstalledLocale); + if (installedLocaleItr != metadata.end()) + { + options.CurrentlyInstalledLocale = installedLocaleItr->second; + } + + if (includeAllowedArchitectures) + { + auto userIntentItr = metadata.find(Repository::PackageVersionMetadata::UserIntentArchitecture); + if (userIntentItr != metadata.end()) + { + // For upgrade, user intent from previous install is considered requirement + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(userIntentItr->second)); + } + else + { + auto installedItr = metadata.find(Repository::PackageVersionMetadata::InstalledArchitecture); + if (installedItr != metadata.end()) + { + // For upgrade, previous installed architecture should be considered first preference and is always allowed. + // Then check settings requirements and preferences. + options.AllowedArchitectures.emplace_back(Utility::ConvertToArchitectureEnum(installedItr->second)); + } + + std::vector requiredArchitectures = Settings::User().Get(); + std::vector optionalArchitectures = Settings::User().Get(); + + if (!requiredArchitectures.empty()) + { + // Required architecture list from settings if applicable + options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), requiredArchitectures.begin(), requiredArchitectures.end()); + } + else + { + // Preferred architecture list from settings if applicable, add Unknown to indicate allowing remaining applicable + if (!optionalArchitectures.empty()) + { + options.AllowedArchitectures.insert(options.AllowedArchitectures.end(), optionalArchitectures.begin(), optionalArchitectures.end()); + } + + options.AllowedArchitectures.emplace_back(Utility::Architecture::Unknown); + } + } + } + } + + std::optional GetSourcePriority(const std::shared_ptr& composite) + { + auto installed = composite->GetInstalled(); + + if (installed) + { + auto installedVersion = installed->GetLatestVersion(); + + if (installedVersion) + { + auto installedSource = installedVersion->GetSource(); + + if (installedSource.ContainsAvailablePackages()) + { + return installedSource.GetDetails().Priority; + } + } + } + + auto available = composite->GetAvailable(); + + if (!available.empty()) + { + return available.front()->GetSource().GetDetails().Priority; + } + + return std::nullopt; + } +} diff --git a/src/AppInstallerRepositoryCore/PinningData.cpp b/src/AppInstallerRepositoryCore/PinningData.cpp index ac29bb0d0a..bcc54394b6 100644 --- a/src/AppInstallerRepositoryCore/PinningData.cpp +++ b/src/AppInstallerRepositoryCore/PinningData.cpp @@ -1,216 +1,216 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/PinningData.h" -#include "Microsoft/PinningIndex.h" -#include "Public/winget/RepositorySource.h" - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Microsoft; - -namespace AppInstaller::Pinning -{ - namespace - { - // Evaluates the pinning state of a version for a single pin. - PinType EvaluatePinnedStateForVersion( - const Utility::Version& version, - const std::optional& pin, - PinBehavior behavior) - { - if (pin) - { - if (pin->GetType() == PinType::Blocking - || (pin->GetType() == PinType::Pinning && behavior != PinBehavior::IncludePinned) - || (pin->GetType() == PinType::Gating && !pin->GetGatedVersion().IsValidVersion(version))) - { - return pin->GetType(); - } - } - - return PinType::Unknown; - } - - // Gets the pinned state for an available version that may have a pin, - // and optionally an additional pin that could come from the installed version. - // If both pins are present, we return the one that is the most strict. - Pinning::PinType GetPinnedStateForVersion( - const Utility::Version& version, - const std::optional& availablePin, - const std::optional& installedPin, - PinBehavior behavior) - { - if (behavior == PinBehavior::IgnorePins) - { - return Pinning::PinType::Unknown; - } - - return Stricter( - EvaluatePinnedStateForVersion(version, availablePin, behavior), - EvaluatePinnedStateForVersion(version, installedPin, behavior)); - } - } - - PinningData::PinningData() = default; - PinningData::PinningData(const PinningData&) = default; - PinningData& PinningData::operator=(const PinningData&) = default; - PinningData::PinningData(PinningData&&) noexcept = default; - PinningData& PinningData::operator=(PinningData&&) noexcept = default; - PinningData::~PinningData() = default; - - PinningData::PinningData(Disposition disposition) - { - if (disposition == Disposition::ReadOnly) - { - m_database = PinningIndex::OpenIfExists(SQLiteStorageBase::OpenDisposition::Read); - } - else - { - m_database = PinningIndex::OpenOrCreateDefault(SQLiteStorageBase::OpenDisposition::ReadWrite); - } - } - - PinningData::operator bool() const - { - return IsDatabaseConnected(); - } - - bool PinningData::IsDatabaseConnected() const - { - return static_cast(m_database); - } - - void PinningData::AddOrUpdatePin(const Pin& pin) - { - THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); - m_database->AddOrUpdatePin(pin); - } - - void PinningData::RemovePin(const PinKey& pinKey) - { - THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); - m_database->RemovePin(pinKey); - } - - std::optional PinningData::GetPin(const PinKey& pinKey) - { - return IsDatabaseConnected() ? m_database->GetPin(pinKey) : std::nullopt; - } - - std::vector PinningData::GetAllPins() - { - return IsDatabaseConnected() ? m_database->GetAllPins() : std::vector{}; - } - - bool PinningData::ResetAllPins(std::string_view sourceId) - { - THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); - return m_database->ResetAllPins(sourceId); - } - - PinningData::PinStateEvaluator::PinStateEvaluator( - PinBehavior behavior, - std::shared_ptr database, - const std::shared_ptr& installedVersion) : - m_behavior(behavior), m_database(std::move(database)) - { - if (m_behavior == PinBehavior::IgnorePins || !installedVersion) - { - // Because the database isn't guaranteed to be present, align ignoring pins with there being no pins to ignore. - // Also do not consider pins when there is no installed version. This is to remain consistent with the previous - // implementation. If this is to be changed, more install paths will need to be do pinning checks to ensure - // that one could, for instance, block the install of a package. - m_database.reset(); - } - else if (m_database) - { - PinKey key = PinKey::GetPinKeyForInstalled(installedVersion->GetProperty(PackageVersionProperty::Id)); - m_installedPin = m_database->GetPin(key); - } - - if (installedVersion) - { - m_installedVersion = Utility::VersionAndChannel{ - Utility::Version{ installedVersion->GetProperty(PackageVersionProperty::Version) }, - Utility::Channel{ installedVersion->GetProperty(PackageVersionProperty::Channel) } - }; - } - } - - PinningData::PinStateEvaluator::PinStateEvaluator(const PinStateEvaluator&) = default; - PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(const PinStateEvaluator&) = default; - PinningData::PinStateEvaluator::PinStateEvaluator(PinStateEvaluator&&) noexcept = default; - PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(PinStateEvaluator&&) noexcept = default; - - PinningData::PinStateEvaluator::~PinStateEvaluator() = default; - - std::shared_ptr PinningData::PinStateEvaluator::GetLatestAvailableVersionForPins(const std::shared_ptr& package) - { - if (!m_database) - { - return package->GetLatestVersion(); - } - - auto availableVersionKeys = package->GetVersionKeys(); - - // Skip until we find a version that isn't pinned - for (const auto& availableVersion : availableVersionKeys) - { - std::shared_ptr packageVersion = package->GetVersion(availableVersion); - if (EvaluatePinType(packageVersion) == Pinning::PinType::Unknown) - { - return packageVersion; - } - } - - return {}; - } - - bool PinningData::PinStateEvaluator::IsUpdate(const std::shared_ptr& availableVersion) - { - if (m_installedVersion && availableVersion) - { - Utility::VersionAndChannel availableVersionAndChannel{ - Utility::Version{ availableVersion->GetProperty(PackageVersionProperty::Version) }, - Utility::Channel{ availableVersion->GetProperty(PackageVersionProperty::Channel) } - }; - - return m_installedVersion->IsUpdatedBy(availableVersionAndChannel); - } - - return false; - } - - PinType PinningData::PinStateEvaluator::EvaluatePinType(const std::shared_ptr& packageVersion) - { - if (!m_database || !packageVersion) - { - return PinType::Unknown; - } - - std::optional incomingPin; - - PinKey pinKey{ packageVersion->GetProperty(PackageVersionProperty::Id).get(), packageVersion->GetSource().GetIdentifier()}; - auto itr = m_availablePins.find(pinKey); - if (itr == m_availablePins.end()) - { - incomingPin = m_database->GetPin(pinKey); - m_availablePins[pinKey] = incomingPin; - } - else - { - incomingPin = itr->second; - } - - return GetPinnedStateForVersion(packageVersion->GetProperty(PackageVersionProperty::Version).get(), incomingPin, m_installedPin, m_behavior); - } - - // Creates an object for use in evaluating pinning data for a given package - PinningData::PinStateEvaluator PinningData::CreatePinStateEvaluator( - PinBehavior behavior, - const std::shared_ptr& installedVersion) - { - return { behavior, m_database, installedVersion }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/PinningData.h" +#include "Microsoft/PinningIndex.h" +#include "Public/winget/RepositorySource.h" + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Microsoft; + +namespace AppInstaller::Pinning +{ + namespace + { + // Evaluates the pinning state of a version for a single pin. + PinType EvaluatePinnedStateForVersion( + const Utility::Version& version, + const std::optional& pin, + PinBehavior behavior) + { + if (pin) + { + if (pin->GetType() == PinType::Blocking + || (pin->GetType() == PinType::Pinning && behavior != PinBehavior::IncludePinned) + || (pin->GetType() == PinType::Gating && !pin->GetGatedVersion().IsValidVersion(version))) + { + return pin->GetType(); + } + } + + return PinType::Unknown; + } + + // Gets the pinned state for an available version that may have a pin, + // and optionally an additional pin that could come from the installed version. + // If both pins are present, we return the one that is the most strict. + Pinning::PinType GetPinnedStateForVersion( + const Utility::Version& version, + const std::optional& availablePin, + const std::optional& installedPin, + PinBehavior behavior) + { + if (behavior == PinBehavior::IgnorePins) + { + return Pinning::PinType::Unknown; + } + + return Stricter( + EvaluatePinnedStateForVersion(version, availablePin, behavior), + EvaluatePinnedStateForVersion(version, installedPin, behavior)); + } + } + + PinningData::PinningData() = default; + PinningData::PinningData(const PinningData&) = default; + PinningData& PinningData::operator=(const PinningData&) = default; + PinningData::PinningData(PinningData&&) noexcept = default; + PinningData& PinningData::operator=(PinningData&&) noexcept = default; + PinningData::~PinningData() = default; + + PinningData::PinningData(Disposition disposition) + { + if (disposition == Disposition::ReadOnly) + { + m_database = PinningIndex::OpenIfExists(SQLiteStorageBase::OpenDisposition::Read); + } + else + { + m_database = PinningIndex::OpenOrCreateDefault(SQLiteStorageBase::OpenDisposition::ReadWrite); + } + } + + PinningData::operator bool() const + { + return IsDatabaseConnected(); + } + + bool PinningData::IsDatabaseConnected() const + { + return static_cast(m_database); + } + + void PinningData::AddOrUpdatePin(const Pin& pin) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + m_database->AddOrUpdatePin(pin); + } + + void PinningData::RemovePin(const PinKey& pinKey) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + m_database->RemovePin(pinKey); + } + + std::optional PinningData::GetPin(const PinKey& pinKey) + { + return IsDatabaseConnected() ? m_database->GetPin(pinKey) : std::nullopt; + } + + std::vector PinningData::GetAllPins() + { + return IsDatabaseConnected() ? m_database->GetAllPins() : std::vector{}; + } + + bool PinningData::ResetAllPins(std::string_view sourceId) + { + THROW_HR_IF(E_NOT_VALID_STATE, !IsDatabaseConnected()); + return m_database->ResetAllPins(sourceId); + } + + PinningData::PinStateEvaluator::PinStateEvaluator( + PinBehavior behavior, + std::shared_ptr database, + const std::shared_ptr& installedVersion) : + m_behavior(behavior), m_database(std::move(database)) + { + if (m_behavior == PinBehavior::IgnorePins || !installedVersion) + { + // Because the database isn't guaranteed to be present, align ignoring pins with there being no pins to ignore. + // Also do not consider pins when there is no installed version. This is to remain consistent with the previous + // implementation. If this is to be changed, more install paths will need to be do pinning checks to ensure + // that one could, for instance, block the install of a package. + m_database.reset(); + } + else if (m_database) + { + PinKey key = PinKey::GetPinKeyForInstalled(installedVersion->GetProperty(PackageVersionProperty::Id)); + m_installedPin = m_database->GetPin(key); + } + + if (installedVersion) + { + m_installedVersion = Utility::VersionAndChannel{ + Utility::Version{ installedVersion->GetProperty(PackageVersionProperty::Version) }, + Utility::Channel{ installedVersion->GetProperty(PackageVersionProperty::Channel) } + }; + } + } + + PinningData::PinStateEvaluator::PinStateEvaluator(const PinStateEvaluator&) = default; + PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(const PinStateEvaluator&) = default; + PinningData::PinStateEvaluator::PinStateEvaluator(PinStateEvaluator&&) noexcept = default; + PinningData::PinStateEvaluator& PinningData::PinStateEvaluator::operator=(PinStateEvaluator&&) noexcept = default; + + PinningData::PinStateEvaluator::~PinStateEvaluator() = default; + + std::shared_ptr PinningData::PinStateEvaluator::GetLatestAvailableVersionForPins(const std::shared_ptr& package) + { + if (!m_database) + { + return package->GetLatestVersion(); + } + + auto availableVersionKeys = package->GetVersionKeys(); + + // Skip until we find a version that isn't pinned + for (const auto& availableVersion : availableVersionKeys) + { + std::shared_ptr packageVersion = package->GetVersion(availableVersion); + if (EvaluatePinType(packageVersion) == Pinning::PinType::Unknown) + { + return packageVersion; + } + } + + return {}; + } + + bool PinningData::PinStateEvaluator::IsUpdate(const std::shared_ptr& availableVersion) + { + if (m_installedVersion && availableVersion) + { + Utility::VersionAndChannel availableVersionAndChannel{ + Utility::Version{ availableVersion->GetProperty(PackageVersionProperty::Version) }, + Utility::Channel{ availableVersion->GetProperty(PackageVersionProperty::Channel) } + }; + + return m_installedVersion->IsUpdatedBy(availableVersionAndChannel); + } + + return false; + } + + PinType PinningData::PinStateEvaluator::EvaluatePinType(const std::shared_ptr& packageVersion) + { + if (!m_database || !packageVersion) + { + return PinType::Unknown; + } + + std::optional incomingPin; + + PinKey pinKey{ packageVersion->GetProperty(PackageVersionProperty::Id).get(), packageVersion->GetSource().GetIdentifier()}; + auto itr = m_availablePins.find(pinKey); + if (itr == m_availablePins.end()) + { + incomingPin = m_database->GetPin(pinKey); + m_availablePins[pinKey] = incomingPin; + } + else + { + incomingPin = itr->second; + } + + return GetPinnedStateForVersion(packageVersion->GetProperty(PackageVersionProperty::Version).get(), incomingPin, m_installedPin, m_behavior); + } + + // Creates an object for use in evaluating pinning data for a given package + PinningData::PinStateEvaluator PinningData::CreatePinStateEvaluator( + PinBehavior behavior, + const std::shared_ptr& installedVersion) + { + return { behavior, m_database, installedVersion }; + } +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelation.h b/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelation.h index c2b7ff22ae..18bde5ce80 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelation.h +++ b/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelation.h @@ -1,154 +1,154 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#include -#include - -#include -#include -#include - -namespace AppInstaller -{ - namespace Manifest - { - struct Manifest; - struct ManifestLocalization; - } - - namespace Repository - { - struct IPackage; - struct IPackageVersion; - struct Source; - } -} - -namespace AppInstaller::Repository::Correlation -{ - // Contains the { Id, Version, Channel } - using ARPEntrySnapshot = std::tuple; - - // Struct holding all the data from an ARP entry we use for the correlation - struct ARPEntry - { - ARPEntry(std::shared_ptr entry, bool isNewOrUpdated) : Entry(std::move(entry)), IsNewOrUpdated(isNewOrUpdated) {} - - // Data found in the ARP entry - std::shared_ptr Entry; - - // Whether this entry changed with the current installation - bool IsNewOrUpdated; - }; - - // One of the possible options that could be chosen for correlation. - struct CorrelationMeasure - { - // The value that the correlation algorithm assigned to the match with the package. - double Measure{}; - - // The package that was measured. - std::shared_ptr Package{}; - }; - - // The result of a heuristics correlation attempt. - struct ARPHeuristicsCorrelationResult - { - // Correlated package from ARP - std::shared_ptr Package{}; - - // The reason for the correlation (for diagnostics). - std::string Reason; - - // The correlation metrics and their associated ARP package information (for diagnostics). - std::vector Measures; - }; - - // The result of a correlation attempt. - struct ARPCorrelationResult : public ARPHeuristicsCorrelationResult - { - // Number of ARP entries that are new or updated - size_t ChangesToARP{}; - - // Number of ARP entries that match with the installed package - size_t MatchesInARP{}; - - // Number of changed ARP entries that match the installed package - size_t CountOfIntersectionOfChangesAndMatches{}; - - ARPCorrelationResult& operator=(ARPHeuristicsCorrelationResult&& other) - { - *static_cast(this) = std::move(other); - return *this; - } - }; - - // Allows callers finer control over how the correlation result will be chosen. - // The values appear in order of their application in the correlation algorithm, meaning that a later - // setting that is set to true can be preempted by an earlier setting, if a correlation occurs with the - // earlier setting. - // The default values are chosen to reflect what is used after an install on a consumer system. - struct ARPCorrelationSettings - { - // This setting controls whether the name and publisher normalization algorithm will be used for correlation. - // When true, normalization will be the first choice for correlation. This means that a normalized name+publisher - // match will result in correlation (unless there are multiple matches). - // When false, normalization will only be used for the statistics (MatchesInARP), but the correlation result package - // will not be based on normalization. - bool AllowNormalization = true; - - // This settings controls whether a single changed ARP entry is sufficient to result in correlation. - // When true, if only a single ARP entry is detected as new or changed, it will be chosen as the correlated result. - bool AllowSingleChange = false; - }; - - struct IARPMatchConfidenceAlgorithm - { - virtual ~IARPMatchConfidenceAlgorithm() = default; - virtual void Init(const AppInstaller::Manifest::Manifest& manifest) = 0; - virtual double ComputeConfidence(const ARPEntry& arpEntry) const = 0; - - // Returns an instance of the algorithm we will actually use. - // We may use multiple instances/specializations for testing and experimentation. - static IARPMatchConfidenceAlgorithm& Instance(); - -#ifndef AICLI_DISABLE_TEST_HOOKS - static void OverrideInstance(IARPMatchConfidenceAlgorithm* algorithmOverride); - static void ResetInstance(); -#endif - }; - - ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( - const AppInstaller::Manifest::Manifest& manifest, - const std::vector& arpEntries); - - ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( - const AppInstaller::Manifest::Manifest& manifest, - const std::vector& arpEntries, - IARPMatchConfidenceAlgorithm& algorithm); - - // Holds data needed for ARP correlation, as well as functions to run correlation on the collected data. - struct ARPCorrelationData - { - ARPCorrelationData() = default; - virtual ~ARPCorrelationData() = default; - - // Captures the ARP state before the package installation. - void CapturePreInstallSnapshot(); - - // Captures the ARP state differences after the package installation. - void CapturePostInstallSnapshot(); - - // Correlates the given manifest against the data previously collected with capture calls. - virtual ARPCorrelationResult CorrelateForNewlyInstalled(const Manifest::Manifest& manifest, const ARPCorrelationSettings& settings = {}); - - const std::vector& GetPreInstallSnapshot() const { return m_preInstallSnapshot; } - - private: - std::vector m_preInstallSnapshot; - - Source m_postInstallSnapshotSource; - std::vector m_postInstallSnapshot; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include +#include + +#include +#include +#include + +namespace AppInstaller +{ + namespace Manifest + { + struct Manifest; + struct ManifestLocalization; + } + + namespace Repository + { + struct IPackage; + struct IPackageVersion; + struct Source; + } +} + +namespace AppInstaller::Repository::Correlation +{ + // Contains the { Id, Version, Channel } + using ARPEntrySnapshot = std::tuple; + + // Struct holding all the data from an ARP entry we use for the correlation + struct ARPEntry + { + ARPEntry(std::shared_ptr entry, bool isNewOrUpdated) : Entry(std::move(entry)), IsNewOrUpdated(isNewOrUpdated) {} + + // Data found in the ARP entry + std::shared_ptr Entry; + + // Whether this entry changed with the current installation + bool IsNewOrUpdated; + }; + + // One of the possible options that could be chosen for correlation. + struct CorrelationMeasure + { + // The value that the correlation algorithm assigned to the match with the package. + double Measure{}; + + // The package that was measured. + std::shared_ptr Package{}; + }; + + // The result of a heuristics correlation attempt. + struct ARPHeuristicsCorrelationResult + { + // Correlated package from ARP + std::shared_ptr Package{}; + + // The reason for the correlation (for diagnostics). + std::string Reason; + + // The correlation metrics and their associated ARP package information (for diagnostics). + std::vector Measures; + }; + + // The result of a correlation attempt. + struct ARPCorrelationResult : public ARPHeuristicsCorrelationResult + { + // Number of ARP entries that are new or updated + size_t ChangesToARP{}; + + // Number of ARP entries that match with the installed package + size_t MatchesInARP{}; + + // Number of changed ARP entries that match the installed package + size_t CountOfIntersectionOfChangesAndMatches{}; + + ARPCorrelationResult& operator=(ARPHeuristicsCorrelationResult&& other) + { + *static_cast(this) = std::move(other); + return *this; + } + }; + + // Allows callers finer control over how the correlation result will be chosen. + // The values appear in order of their application in the correlation algorithm, meaning that a later + // setting that is set to true can be preempted by an earlier setting, if a correlation occurs with the + // earlier setting. + // The default values are chosen to reflect what is used after an install on a consumer system. + struct ARPCorrelationSettings + { + // This setting controls whether the name and publisher normalization algorithm will be used for correlation. + // When true, normalization will be the first choice for correlation. This means that a normalized name+publisher + // match will result in correlation (unless there are multiple matches). + // When false, normalization will only be used for the statistics (MatchesInARP), but the correlation result package + // will not be based on normalization. + bool AllowNormalization = true; + + // This settings controls whether a single changed ARP entry is sufficient to result in correlation. + // When true, if only a single ARP entry is detected as new or changed, it will be chosen as the correlated result. + bool AllowSingleChange = false; + }; + + struct IARPMatchConfidenceAlgorithm + { + virtual ~IARPMatchConfidenceAlgorithm() = default; + virtual void Init(const AppInstaller::Manifest::Manifest& manifest) = 0; + virtual double ComputeConfidence(const ARPEntry& arpEntry) const = 0; + + // Returns an instance of the algorithm we will actually use. + // We may use multiple instances/specializations for testing and experimentation. + static IARPMatchConfidenceAlgorithm& Instance(); + +#ifndef AICLI_DISABLE_TEST_HOOKS + static void OverrideInstance(IARPMatchConfidenceAlgorithm* algorithmOverride); + static void ResetInstance(); +#endif + }; + + ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( + const AppInstaller::Manifest::Manifest& manifest, + const std::vector& arpEntries); + + ARPHeuristicsCorrelationResult FindARPEntryForNewlyInstalledPackageWithHeuristics( + const AppInstaller::Manifest::Manifest& manifest, + const std::vector& arpEntries, + IARPMatchConfidenceAlgorithm& algorithm); + + // Holds data needed for ARP correlation, as well as functions to run correlation on the collected data. + struct ARPCorrelationData + { + ARPCorrelationData() = default; + virtual ~ARPCorrelationData() = default; + + // Captures the ARP state before the package installation. + void CapturePreInstallSnapshot(); + + // Captures the ARP state differences after the package installation. + void CapturePostInstallSnapshot(); + + // Correlates the given manifest against the data previously collected with capture calls. + virtual ARPCorrelationResult CorrelateForNewlyInstalled(const Manifest::Manifest& manifest, const ARPCorrelationSettings& settings = {}); + + const std::vector& GetPreInstallSnapshot() const { return m_preInstallSnapshot; } + + private: + std::vector m_preInstallSnapshot; + + Source m_postInstallSnapshotSource; + std::vector m_postInstallSnapshot; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelationAlgorithms.h b/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelationAlgorithms.h index 970e8bb07a..a634039e10 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelationAlgorithms.h +++ b/src/AppInstallerRepositoryCore/Public/winget/ARPCorrelationAlgorithms.h @@ -1,62 +1,62 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#include -#include -#include -#include -#include - -namespace AppInstaller::Repository::Correlation -{ - struct EmptyMatchConfidenceAlgorithm : public IARPMatchConfidenceAlgorithm - { - void Init(const AppInstaller::Manifest::Manifest&) override {} - double ComputeConfidence(const ARPEntry&) const override { return 0; } - }; - - // Algorithm that computes the match confidence by looking at the edit distance between - // the sequences of words in the name and publisher. - // The edit distance for this allows only adding or removing words, not editing them, - // as that would make any two names too similar. - struct WordsEditDistanceMatchConfidenceAlgorithm : public IARPMatchConfidenceAlgorithm - { - using WordSequence = std::vector; - - struct NameAndPublisher - { - NameAndPublisher(const WordSequence& name, const WordSequence& publisher); - NameAndPublisher(WordSequence&& name, WordSequence&& publisher); - - WordSequence Name; - WordSequence Publisher; - WordSequence NamePublisher; - }; - - void Init(const AppInstaller::Manifest::Manifest& manifest) override; - double ComputeConfidence(const ARPEntry& arpEntry) const override; - - private: - WordSequence PrepareString(std::string_view s) const; - WordSequence NormalizeAndPrepareName(std::string_view name) const; - WordSequence NormalizeAndPreparePublisher(std::string_view publisher) const; - - AppInstaller::Utility::NameNormalizer m_normalizer{ AppInstaller::Utility::NormalizationVersion::InitialPreserveWhiteSpace }; - std::vector m_namesAndPublishers; - - // Parameters for the algorithm - - // How much weight to give to the string matching score. - // The rest is assigned by whether the entry is new. - const double m_stringMatchingWeight = 0.8; - - // How much weight to give to the matching score of the package name; - // the rest is assigned to the publisher score. - const double m_nameMatchingScoreWeight = 2. / 3.; - - // Minimum score needed in the name for us to accept a match. - // This prevents us from being misled by a long match in the publisher. - const double m_nameMatchingScoreMinThreshold = 0.2; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include +#include + +namespace AppInstaller::Repository::Correlation +{ + struct EmptyMatchConfidenceAlgorithm : public IARPMatchConfidenceAlgorithm + { + void Init(const AppInstaller::Manifest::Manifest&) override {} + double ComputeConfidence(const ARPEntry&) const override { return 0; } + }; + + // Algorithm that computes the match confidence by looking at the edit distance between + // the sequences of words in the name and publisher. + // The edit distance for this allows only adding or removing words, not editing them, + // as that would make any two names too similar. + struct WordsEditDistanceMatchConfidenceAlgorithm : public IARPMatchConfidenceAlgorithm + { + using WordSequence = std::vector; + + struct NameAndPublisher + { + NameAndPublisher(const WordSequence& name, const WordSequence& publisher); + NameAndPublisher(WordSequence&& name, WordSequence&& publisher); + + WordSequence Name; + WordSequence Publisher; + WordSequence NamePublisher; + }; + + void Init(const AppInstaller::Manifest::Manifest& manifest) override; + double ComputeConfidence(const ARPEntry& arpEntry) const override; + + private: + WordSequence PrepareString(std::string_view s) const; + WordSequence NormalizeAndPrepareName(std::string_view name) const; + WordSequence NormalizeAndPreparePublisher(std::string_view publisher) const; + + AppInstaller::Utility::NameNormalizer m_normalizer{ AppInstaller::Utility::NormalizationVersion::InitialPreserveWhiteSpace }; + std::vector m_namesAndPublishers; + + // Parameters for the algorithm + + // How much weight to give to the string matching score. + // The rest is assigned by whether the entry is new. + const double m_stringMatchingWeight = 0.8; + + // How much weight to give to the matching score of the package name; + // the rest is assigned to the publisher score. + const double m_nameMatchingScoreWeight = 2. / 3.; + + // Minimum score needed in the name for us to accept a match. + // This prevents us from being misled by a long match in the publisher. + const double m_nameMatchingScoreMinThreshold = 0.2; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/InstalledFilesCorrelation.h b/src/AppInstallerRepositoryCore/Public/winget/InstalledFilesCorrelation.h index 35d9f0808a..f95f64727a 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/InstalledFilesCorrelation.h +++ b/src/AppInstallerRepositoryCore/Public/winget/InstalledFilesCorrelation.h @@ -1,58 +1,58 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Repository::Correlation -{ - // TODO: This definition could be moved to Manifest when winget supports launch scenarios. - struct InstalledStartupLinkFile - { - // Relative file path to the startup menu folder. - // Same installers write the links to user startup folder or machine startup folder depending on the scope - // the installers were running for. So only relative file paths were collected. And seems enough. - AppInstaller::Manifest::string_t RelativeFilePath; - // Heuristic startup link type. - AppInstaller::Manifest::InstalledFileTypeEnum FileType = AppInstaller::Manifest::InstalledFileTypeEnum::Unknown; - }; - - struct InstallationMetadata - { - // Installed files metadata. Currently only capturing files pointed by a startup link. - AppInstaller::Manifest::InstallationMetadataInfo InstalledFiles; - // Startup links metadata. - std::vector StartupLinkFiles; - }; - - struct InstalledFilesCorrelation - { - // Constructor initializes the file watchers. - InstalledFilesCorrelation(); - virtual ~InstalledFilesCorrelation() = default; - - // Start the file watcher before the package installation. - virtual void StartFileWatcher(); - - // Stop the file watcher after the package installation. - virtual void StopFileWatcher(); - - // Correlates the given manifest against the data previously collected with capture calls. - virtual InstallationMetadata CorrelateForNewlyInstalled( - const Manifest::Manifest& manifest, - const std::string& arpInstallLocation); - - private: - struct FileWatcherFiles - { - // FileWatcher folder base. - std::filesystem::path Folder; - // List of files represented as relative file path to the base folder. - std::vector Files; - }; - - std::vector m_fileWatchers; - std::vector m_files; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Repository::Correlation +{ + // TODO: This definition could be moved to Manifest when winget supports launch scenarios. + struct InstalledStartupLinkFile + { + // Relative file path to the startup menu folder. + // Same installers write the links to user startup folder or machine startup folder depending on the scope + // the installers were running for. So only relative file paths were collected. And seems enough. + AppInstaller::Manifest::string_t RelativeFilePath; + // Heuristic startup link type. + AppInstaller::Manifest::InstalledFileTypeEnum FileType = AppInstaller::Manifest::InstalledFileTypeEnum::Unknown; + }; + + struct InstallationMetadata + { + // Installed files metadata. Currently only capturing files pointed by a startup link. + AppInstaller::Manifest::InstallationMetadataInfo InstalledFiles; + // Startup links metadata. + std::vector StartupLinkFiles; + }; + + struct InstalledFilesCorrelation + { + // Constructor initializes the file watchers. + InstalledFilesCorrelation(); + virtual ~InstalledFilesCorrelation() = default; + + // Start the file watcher before the package installation. + virtual void StartFileWatcher(); + + // Stop the file watcher after the package installation. + virtual void StopFileWatcher(); + + // Correlates the given manifest against the data previously collected with capture calls. + virtual InstallationMetadata CorrelateForNewlyInstalled( + const Manifest::Manifest& manifest, + const std::string& arpInstallLocation); + + private: + struct FileWatcherFiles + { + // FileWatcher folder base. + std::filesystem::path Folder; + // List of files represented as relative file path to the base folder. + std::vector Files; + }; + + std::vector m_fileWatchers; + std::vector m_files; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/InstalledStatus.h b/src/AppInstallerRepositoryCore/Public/winget/InstalledStatus.h index fc2d803338..975ca868c5 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/InstalledStatus.h +++ b/src/AppInstallerRepositoryCore/Public/winget/InstalledStatus.h @@ -1,64 +1,64 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include - - -namespace AppInstaller::Repository -{ - // Defines the installed status check type. - enum class InstalledStatusType : uint32_t - { - // None is checked. - None = 0x0, - // Check Apps and Features entry. - AppsAndFeaturesEntry = 0x0001, - // Check Apps and Features entry install location if applicable. - AppsAndFeaturesEntryInstallLocation = 0x0002, - // Check Apps and Features entry install location with installed files if applicable. - AppsAndFeaturesEntryInstallLocationFile = 0x0004, - // Check default install location if applicable. - DefaultInstallLocation = 0x0008, - // Check default install location with installed files if applicable. - DefaultInstallLocationFile = 0x0010, - - // Below are helper values for calling CheckInstalledStatus as input. - // AppsAndFeaturesEntry related checks - AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, - // DefaultInstallLocation related checks - AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, - // All checks - AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, - }; - - DEFINE_ENUM_FLAG_OPERATORS(InstalledStatusType); - - // Struct representing an individual installed status. - struct InstalledStatus - { - // The installed status type. - InstalledStatusType Type = InstalledStatusType::None; - // The installed status path. - Utility::NormalizedString Path; - // The installed status result. - HRESULT Status; - - InstalledStatus(InstalledStatusType type, Utility::NormalizedString path, HRESULT status) : - Type(type), Path(std::move(path)), Status(status) {} - }; - - // Struct representing installed status from an installer. - struct InstallerInstalledStatus - { - Manifest::ManifestInstaller Installer; - std::vector Status; - }; - - // Checks installed status of a package. - std::vector CheckPackageInstalledStatus(const std::shared_ptr& package, InstalledStatusType checkTypes = InstalledStatusType::AllChecks); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include + + +namespace AppInstaller::Repository +{ + // Defines the installed status check type. + enum class InstalledStatusType : uint32_t + { + // None is checked. + None = 0x0, + // Check Apps and Features entry. + AppsAndFeaturesEntry = 0x0001, + // Check Apps and Features entry install location if applicable. + AppsAndFeaturesEntryInstallLocation = 0x0002, + // Check Apps and Features entry install location with installed files if applicable. + AppsAndFeaturesEntryInstallLocationFile = 0x0004, + // Check default install location if applicable. + DefaultInstallLocation = 0x0008, + // Check default install location with installed files if applicable. + DefaultInstallLocationFile = 0x0010, + + // Below are helper values for calling CheckInstalledStatus as input. + // AppsAndFeaturesEntry related checks + AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, + // DefaultInstallLocation related checks + AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, + // All checks + AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, + }; + + DEFINE_ENUM_FLAG_OPERATORS(InstalledStatusType); + + // Struct representing an individual installed status. + struct InstalledStatus + { + // The installed status type. + InstalledStatusType Type = InstalledStatusType::None; + // The installed status path. + Utility::NormalizedString Path; + // The installed status result. + HRESULT Status; + + InstalledStatus(InstalledStatusType type, Utility::NormalizedString path, HRESULT status) : + Type(type), Path(std::move(path)), Status(status) {} + }; + + // Struct representing installed status from an installer. + struct InstallerInstalledStatus + { + Manifest::ManifestInstaller Installer; + std::vector Status; + }; + + // Checks installed status of a package. + std::vector CheckPackageInstalledStatus(const std::shared_ptr& package, InstalledStatusType checkTypes = InstalledStatusType::AllChecks); +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/InstallerMetadataCollectionContext.h b/src/AppInstallerRepositoryCore/Public/winget/InstallerMetadataCollectionContext.h index b7d6c1e866..0fa309b832 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/InstallerMetadataCollectionContext.h +++ b/src/AppInstallerRepositoryCore/Public/winget/InstallerMetadataCollectionContext.h @@ -1,187 +1,187 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Repository::Metadata -{ - // The overall metadata that we collect. - struct ProductMetadata - { - ProductMetadata() = default; - - // Removes all stored data. - void Clear(); - - // Load the metadata from an existing JSON blob. - void FromJson(const web::json::value& json); - - // Create a JSON value for the metadata using the given schema version. - web::json::value ToJson(const Utility::Version& schemaVersion, size_t maximumSizeInBytes); - - // Copies the metadata from the source. If the given submission identifier does not match - // the source, it's data is moved to historical. - void CopyFrom(const ProductMetadata& source, std::string_view submissionIdentifier); - - // The installer specific metadata that we collect. - struct InstallerMetadata - { - friend ProductMetadata; - - // 1.0 - std::string SubmissionIdentifier; - std::vector AppsAndFeaturesEntries; - - // 1.1 - // If Scope value is empty, the value is not set before. If the value is Unknown, a conflicting value is encountered. - std::string Scope; - - // 1.2 - // If std::nullopt, the value is not set before. If the value is empty(i.e. !HasData()), a conflicting value is encountered. - std::optional InstalledFiles; - // If std::nullopt, the value is not set before. If the vector is empty, conflicting values are encountered. - std::optional> StartupLinkFiles; - // Extracted icons - std::vector Icons; - }; - - // Metadata from previous product revisions. - struct HistoricalMetadata - { - // 1.0 - Utility::Version ProductVersionMin; - Utility::Version ProductVersionMax; - std::set Names; - std::set Publishers; - std::set ProductCodes; - std::set UpgradeCodes; - }; - - // 1.0 - Utility::Version SchemaVersion; - Utility::Version ProductVersionMin; - Utility::Version ProductVersionMax; - // Map from installer hash to metadata - std::map InstallerMetadataMap; - std::vector HistoricalMetadataList; - - private: - void FromJson_1_N(const web::json::value& json); - web::json::value ToJson_1_N(); - - // Removes the historical data with the oldest version. - // Returns true if something was removed; false it not. - bool DropOldestHistoricalData(); - }; - - // Contains the functions and data used for collecting metadata from installers. - struct InstallerMetadataCollectionContext - { - InstallerMetadataCollectionContext(); - InstallerMetadataCollectionContext( - std::unique_ptr correlationData, - std::unique_ptr installedFilesCorrelation, - const std::wstring& json); - - InstallerMetadataCollectionContext(const InstallerMetadataCollectionContext&) = delete; - InstallerMetadataCollectionContext& operator=(const InstallerMetadataCollectionContext&) = delete; - - InstallerMetadataCollectionContext(InstallerMetadataCollectionContext&&) = default; - InstallerMetadataCollectionContext& operator=(InstallerMetadataCollectionContext&&) = default; - - // Create from various forms of JSON input to prevent type collisions on constructor. - static std::unique_ptr FromFile(const std::filesystem::path& file, const std::filesystem::path& logFile); - static std::unique_ptr FromURI(std::wstring_view uri, const std::filesystem::path& logFile); - static std::unique_ptr FromJSON(const std::wstring& json, const std::filesystem::path& logFile); - - // Completes the collection, writing to the given location. - void Complete(const std::filesystem::path& output); - - // Completes the collection, writing to the given location. - void Complete(std::ostream& output); - - static std::wstring Merge(const std::wstring& json, size_t maximumSizeInBytes, const std::filesystem::path& logFile); - - private: - // Initializes the context runtime, including the log file if provided. - static std::unique_ptr InitializeLogging(ThreadLocalStorage::WingetThreadGlobals& threadGlobals, const std::filesystem::path& logFile); - std::unique_ptr InitializeLogging(const std::filesystem::path& logFile); - - // Sets the collection context input and the preinstall state. - void InitializePreinstallState(const std::wstring& json); - - // Creates the output ProductMetadata and diagnostics objects for output - void ComputeOutputData(); - - // Callers should set the thread globals before calling this. - void CompleteWithThreadGlobalsSet(std::ostream& output); - - // Parse version 1.0 of input JSON - void ParseInputJson_1_0(web::json::value& input); - - // Create version 1.0 of output JSON - web::json::value CreateOutputJson_1_0(); - - // Determines whether an error has occurred in the context. - bool ContainsError() const; - - // Collects information from the exception for error reporting. - void CollectErrorDataFromException(std::exception_ptr exception); - - // Create version 1.0 of error JSON - web::json::value CreateErrorJson_1_0(); - - // Merge using merge input version 1.0 - static web::json::value Merge_1_0(web::json::value& input, size_t maximumSizeInBytes); - - ThreadLocalStorage::WingetThreadGlobals m_threadGlobals; - - // Parsed input - Utility::Version m_inputVersion; - Utility::Version m_supportedMetadataVersion; - ProductMetadata m_currentMetadata; - web::json::value m_submissionData; - std::string m_submissionIdentifier; - std::string m_installerHash; - Manifest::Manifest m_incomingManifest; - - std::unique_ptr m_correlationData; - std::unique_ptr m_installedFilesCorrelation; - - // Output data - enum class OutputStatus - { - // Version 1.0 status values - Unknown, - Success, - Error, - LowConfidence, - }; - - // Convert status to a JSON string value - static utility::string_t ToString(OutputStatus status); - - OutputStatus m_outputStatus = OutputStatus::Unknown; - ProductMetadata m_outputMetadata; - web::json::value m_outputDiagnostics; - - // Error data storage - HRESULT m_errorHR = S_OK; - std::string m_errorText; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Repository::Metadata +{ + // The overall metadata that we collect. + struct ProductMetadata + { + ProductMetadata() = default; + + // Removes all stored data. + void Clear(); + + // Load the metadata from an existing JSON blob. + void FromJson(const web::json::value& json); + + // Create a JSON value for the metadata using the given schema version. + web::json::value ToJson(const Utility::Version& schemaVersion, size_t maximumSizeInBytes); + + // Copies the metadata from the source. If the given submission identifier does not match + // the source, it's data is moved to historical. + void CopyFrom(const ProductMetadata& source, std::string_view submissionIdentifier); + + // The installer specific metadata that we collect. + struct InstallerMetadata + { + friend ProductMetadata; + + // 1.0 + std::string SubmissionIdentifier; + std::vector AppsAndFeaturesEntries; + + // 1.1 + // If Scope value is empty, the value is not set before. If the value is Unknown, a conflicting value is encountered. + std::string Scope; + + // 1.2 + // If std::nullopt, the value is not set before. If the value is empty(i.e. !HasData()), a conflicting value is encountered. + std::optional InstalledFiles; + // If std::nullopt, the value is not set before. If the vector is empty, conflicting values are encountered. + std::optional> StartupLinkFiles; + // Extracted icons + std::vector Icons; + }; + + // Metadata from previous product revisions. + struct HistoricalMetadata + { + // 1.0 + Utility::Version ProductVersionMin; + Utility::Version ProductVersionMax; + std::set Names; + std::set Publishers; + std::set ProductCodes; + std::set UpgradeCodes; + }; + + // 1.0 + Utility::Version SchemaVersion; + Utility::Version ProductVersionMin; + Utility::Version ProductVersionMax; + // Map from installer hash to metadata + std::map InstallerMetadataMap; + std::vector HistoricalMetadataList; + + private: + void FromJson_1_N(const web::json::value& json); + web::json::value ToJson_1_N(); + + // Removes the historical data with the oldest version. + // Returns true if something was removed; false it not. + bool DropOldestHistoricalData(); + }; + + // Contains the functions and data used for collecting metadata from installers. + struct InstallerMetadataCollectionContext + { + InstallerMetadataCollectionContext(); + InstallerMetadataCollectionContext( + std::unique_ptr correlationData, + std::unique_ptr installedFilesCorrelation, + const std::wstring& json); + + InstallerMetadataCollectionContext(const InstallerMetadataCollectionContext&) = delete; + InstallerMetadataCollectionContext& operator=(const InstallerMetadataCollectionContext&) = delete; + + InstallerMetadataCollectionContext(InstallerMetadataCollectionContext&&) = default; + InstallerMetadataCollectionContext& operator=(InstallerMetadataCollectionContext&&) = default; + + // Create from various forms of JSON input to prevent type collisions on constructor. + static std::unique_ptr FromFile(const std::filesystem::path& file, const std::filesystem::path& logFile); + static std::unique_ptr FromURI(std::wstring_view uri, const std::filesystem::path& logFile); + static std::unique_ptr FromJSON(const std::wstring& json, const std::filesystem::path& logFile); + + // Completes the collection, writing to the given location. + void Complete(const std::filesystem::path& output); + + // Completes the collection, writing to the given location. + void Complete(std::ostream& output); + + static std::wstring Merge(const std::wstring& json, size_t maximumSizeInBytes, const std::filesystem::path& logFile); + + private: + // Initializes the context runtime, including the log file if provided. + static std::unique_ptr InitializeLogging(ThreadLocalStorage::WingetThreadGlobals& threadGlobals, const std::filesystem::path& logFile); + std::unique_ptr InitializeLogging(const std::filesystem::path& logFile); + + // Sets the collection context input and the preinstall state. + void InitializePreinstallState(const std::wstring& json); + + // Creates the output ProductMetadata and diagnostics objects for output + void ComputeOutputData(); + + // Callers should set the thread globals before calling this. + void CompleteWithThreadGlobalsSet(std::ostream& output); + + // Parse version 1.0 of input JSON + void ParseInputJson_1_0(web::json::value& input); + + // Create version 1.0 of output JSON + web::json::value CreateOutputJson_1_0(); + + // Determines whether an error has occurred in the context. + bool ContainsError() const; + + // Collects information from the exception for error reporting. + void CollectErrorDataFromException(std::exception_ptr exception); + + // Create version 1.0 of error JSON + web::json::value CreateErrorJson_1_0(); + + // Merge using merge input version 1.0 + static web::json::value Merge_1_0(web::json::value& input, size_t maximumSizeInBytes); + + ThreadLocalStorage::WingetThreadGlobals m_threadGlobals; + + // Parsed input + Utility::Version m_inputVersion; + Utility::Version m_supportedMetadataVersion; + ProductMetadata m_currentMetadata; + web::json::value m_submissionData; + std::string m_submissionIdentifier; + std::string m_installerHash; + Manifest::Manifest m_incomingManifest; + + std::unique_ptr m_correlationData; + std::unique_ptr m_installedFilesCorrelation; + + // Output data + enum class OutputStatus + { + // Version 1.0 status values + Unknown, + Success, + Error, + LowConfidence, + }; + + // Convert status to a JSON string value + static utility::string_t ToString(OutputStatus status); + + OutputStatus m_outputStatus = OutputStatus::Unknown; + ProductMetadata m_outputMetadata; + web::json::value m_outputDiagnostics; + + // Error data storage + HRESULT m_errorHR = S_OK; + std::string m_errorText; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/ManifestJSONParser.h b/src/AppInstallerRepositoryCore/Public/winget/ManifestJSONParser.h index c2cd471276..75f169d4c8 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/ManifestJSONParser.h +++ b/src/AppInstallerRepositoryCore/Public/winget/ManifestJSONParser.h @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include - -namespace AppInstaller::Repository::JSON -{ - // Exposes functions for parsing JSON REST responses to manifest requests. - struct ManifestJSONParser - { - ManifestJSONParser(const Utility::Version& responseSchemaVersion); - - ManifestJSONParser(const ManifestJSONParser&) = delete; - ManifestJSONParser& operator=(const ManifestJSONParser&) = delete; - - ManifestJSONParser(ManifestJSONParser&&) noexcept; - ManifestJSONParser& operator=(ManifestJSONParser&&) noexcept; - - ~ManifestJSONParser(); - - // Deserializes the manifests from the REST response object root. - // May potentially contain multiple versions of the same package. - std::vector Deserialize(const web::json::value& response) const; - - // Deserializes the manifests from the Data field of the REST response object. - // May potentially contain multiple versions of the same package. - std::vector DeserializeData(const web::json::value& data) const; - - // Deserializes the AppsAndFeaturesEntries node, returning the set of values below it. - std::vector DeserializeAppsAndFeaturesEntries(const web::json::array& data) const; - - // Deserializes the locale node; returning an object if a proper locale was found. - std::optional DeserializeLocale(const web::json::value& locale) const; - - // Deserializes the InstallationMetadata node; returning an object if a proper InstallationMetadata was found. - std::optional DeserializeInstallationMetadata(const web::json::value& installationMetadata) const; - - private: - struct impl; - std::unique_ptr m_pImpl; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include + +namespace AppInstaller::Repository::JSON +{ + // Exposes functions for parsing JSON REST responses to manifest requests. + struct ManifestJSONParser + { + ManifestJSONParser(const Utility::Version& responseSchemaVersion); + + ManifestJSONParser(const ManifestJSONParser&) = delete; + ManifestJSONParser& operator=(const ManifestJSONParser&) = delete; + + ManifestJSONParser(ManifestJSONParser&&) noexcept; + ManifestJSONParser& operator=(ManifestJSONParser&&) noexcept; + + ~ManifestJSONParser(); + + // Deserializes the manifests from the REST response object root. + // May potentially contain multiple versions of the same package. + std::vector Deserialize(const web::json::value& response) const; + + // Deserializes the manifests from the Data field of the REST response object. + // May potentially contain multiple versions of the same package. + std::vector DeserializeData(const web::json::value& data) const; + + // Deserializes the AppsAndFeaturesEntries node, returning the set of values below it. + std::vector DeserializeAppsAndFeaturesEntries(const web::json::array& data) const; + + // Deserializes the locale node; returning an object if a proper locale was found. + std::optional DeserializeLocale(const web::json::value& locale) const; + + // Deserializes the InstallationMetadata node; returning an object if a proper InstallationMetadata was found. + std::optional DeserializeInstallationMetadata(const web::json::value& installationMetadata) const; + + private: + struct impl; + std::unique_ptr m_pImpl; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h b/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h index 95fe26664f..9fe110683a 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PackageTrackingCatalog.h @@ -1,83 +1,83 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include - -#ifndef AICLI_DISABLE_TEST_HOOKS -#include -#endif - -namespace AppInstaller::Repository -{ - struct Source; - struct SearchRequest; - struct SearchResult; - enum class PackageVersionMetadata; - - // A catalog for tracking package actions from a given source. - struct PackageTrackingCatalog - { - friend Source; - - PackageTrackingCatalog(); - PackageTrackingCatalog(const PackageTrackingCatalog&); - PackageTrackingCatalog& operator=(const PackageTrackingCatalog&); - PackageTrackingCatalog(PackageTrackingCatalog&&) noexcept; - PackageTrackingCatalog& operator=(PackageTrackingCatalog&&) noexcept; - ~PackageTrackingCatalog(); - - // Removes the package tracking catalog for a given source identifier. - static void RemoveForSource(const std::string& identifier); - - // Determines if the current object holds anything. - operator bool() const; - - // Execute a search against the catalog. - // Note that the packages in the results have the versions under "available" in order to - // expose all versions contained therein (in the event that this is deemed useful at some point). - SearchResult Search(const SearchRequest& request) const; - - // Enables more granular control over the metadata in the tracking catalog if necessary. - struct Version - { - friend PackageTrackingCatalog; - - Version(const Version&); - Version& operator=(const Version&); - Version(Version&&) noexcept; - Version& operator=(Version&&) noexcept; - ~Version(); - - // Set the given metadata value. - void SetMetadata(PackageVersionMetadata metadata, const Utility::NormalizedString& value); - - private: - struct implementation; - Version(PackageTrackingCatalog& catalog, std::shared_ptr&& value); - std::shared_ptr m_implementation; - PackageTrackingCatalog* m_catalog; - }; - - // Records an installation of the given package. - Version RecordInstall(Manifest::Manifest& manifest, const Manifest::ManifestInstaller& installer, bool isUpgrade); - - // Records an uninstall of the given package. - void RecordUninstall(const Utility::LocIndString& packageIdentifier); - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Gets the path to the database file. - std::filesystem::path GetFilePath() const; -#endif - - protected: - // Creates or opens the tracking catalog for the given source. - static PackageTrackingCatalog CreateForSource(const Source& source); - - private: - struct implementation; - std::shared_ptr m_implementation; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include + +#ifndef AICLI_DISABLE_TEST_HOOKS +#include +#endif + +namespace AppInstaller::Repository +{ + struct Source; + struct SearchRequest; + struct SearchResult; + enum class PackageVersionMetadata; + + // A catalog for tracking package actions from a given source. + struct PackageTrackingCatalog + { + friend Source; + + PackageTrackingCatalog(); + PackageTrackingCatalog(const PackageTrackingCatalog&); + PackageTrackingCatalog& operator=(const PackageTrackingCatalog&); + PackageTrackingCatalog(PackageTrackingCatalog&&) noexcept; + PackageTrackingCatalog& operator=(PackageTrackingCatalog&&) noexcept; + ~PackageTrackingCatalog(); + + // Removes the package tracking catalog for a given source identifier. + static void RemoveForSource(const std::string& identifier); + + // Determines if the current object holds anything. + operator bool() const; + + // Execute a search against the catalog. + // Note that the packages in the results have the versions under "available" in order to + // expose all versions contained therein (in the event that this is deemed useful at some point). + SearchResult Search(const SearchRequest& request) const; + + // Enables more granular control over the metadata in the tracking catalog if necessary. + struct Version + { + friend PackageTrackingCatalog; + + Version(const Version&); + Version& operator=(const Version&); + Version(Version&&) noexcept; + Version& operator=(Version&&) noexcept; + ~Version(); + + // Set the given metadata value. + void SetMetadata(PackageVersionMetadata metadata, const Utility::NormalizedString& value); + + private: + struct implementation; + Version(PackageTrackingCatalog& catalog, std::shared_ptr&& value); + std::shared_ptr m_implementation; + PackageTrackingCatalog* m_catalog; + }; + + // Records an installation of the given package. + Version RecordInstall(Manifest::Manifest& manifest, const Manifest::ManifestInstaller& installer, bool isUpgrade); + + // Records an uninstall of the given package. + void RecordUninstall(const Utility::LocIndString& packageIdentifier); + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Gets the path to the database file. + std::filesystem::path GetFilePath() const; +#endif + + protected: + // Creates or opens the tracking catalog for the given source. + static PackageTrackingCatalog CreateForSource(const Source& source); + + private: + struct implementation; + std::shared_ptr m_implementation; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h index 2a56d78d92..eb1cd0b104 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PackageVersionSelection.h @@ -1,44 +1,44 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - - -namespace AppInstaller::Repository -{ - // Gets an IPackageVersionCollection that represents the available package versions for the installed version. - // If we have tracking data, will remove packages not from the tracked source. Will also remove versions that do not correspond to the tracked channel. - // This function uses the latest installed version as a temporary convenience until side-by-side is implemented. - std::shared_ptr GetAvailableVersionsForInstalledVersion(const std::shared_ptr& composite); - - // Gets an IPackageVersionCollection that represents the available package versions for the given installed version. - std::shared_ptr GetAvailableVersionsForInstalledVersion( - const std::shared_ptr& composite, - const std::shared_ptr& installedVersion); - - // Equivalent to `GetAvailableVersionsForInstalledVersion(composite, nullptr)` to make the intent more clear that the caller wants to ignore any installed - // package information. - std::shared_ptr GetAllAvailableVersions(const std::shared_ptr& composite); - - // Gets the installed version, or a null if there isn't one. - std::shared_ptr GetInstalledVersion(const std::shared_ptr& composite); - - // Gets the available IPackage corresponding to the given source identifier. - std::shared_ptr GetAvailablePackageFromSource(const std::shared_ptr& composite, const std::string_view sourceIdentifier); - - struct LatestApplicableVersionData - { - std::shared_ptr LatestApplicableVersion; - bool UpdateAvailable = false; - }; - - // Determines the default install version and whether an update is available. - LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr& composite); - - // Fills the options from the given metadata, optionally including the allowed architectures. - void GetManifestComparatorOptionsFromMetadata(AppInstaller::Manifest::ManifestComparator::Options& options, const IPackageVersion::Metadata& metadata, bool includeAllowedArchitectures = true); - - // Gets the source priority for a given composite package, taking into account installed relationships. - std::optional GetSourcePriority(const std::shared_ptr& composite); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + + +namespace AppInstaller::Repository +{ + // Gets an IPackageVersionCollection that represents the available package versions for the installed version. + // If we have tracking data, will remove packages not from the tracked source. Will also remove versions that do not correspond to the tracked channel. + // This function uses the latest installed version as a temporary convenience until side-by-side is implemented. + std::shared_ptr GetAvailableVersionsForInstalledVersion(const std::shared_ptr& composite); + + // Gets an IPackageVersionCollection that represents the available package versions for the given installed version. + std::shared_ptr GetAvailableVersionsForInstalledVersion( + const std::shared_ptr& composite, + const std::shared_ptr& installedVersion); + + // Equivalent to `GetAvailableVersionsForInstalledVersion(composite, nullptr)` to make the intent more clear that the caller wants to ignore any installed + // package information. + std::shared_ptr GetAllAvailableVersions(const std::shared_ptr& composite); + + // Gets the installed version, or a null if there isn't one. + std::shared_ptr GetInstalledVersion(const std::shared_ptr& composite); + + // Gets the available IPackage corresponding to the given source identifier. + std::shared_ptr GetAvailablePackageFromSource(const std::shared_ptr& composite, const std::string_view sourceIdentifier); + + struct LatestApplicableVersionData + { + std::shared_ptr LatestApplicableVersion; + bool UpdateAvailable = false; + }; + + // Determines the default install version and whether an update is available. + LatestApplicableVersionData GetLatestApplicableVersion(const std::shared_ptr& composite); + + // Fills the options from the given metadata, optionally including the allowed architectures. + void GetManifestComparatorOptionsFromMetadata(AppInstaller::Manifest::ManifestComparator::Options& options, const IPackageVersion::Metadata& metadata, bool includeAllowedArchitectures = true); + + // Gets the source priority for a given composite package, taking into account installed relationships. + std::optional GetSourcePriority(const std::shared_ptr& composite); +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PinningData.h b/src/AppInstallerRepositoryCore/Public/winget/PinningData.h index ca1e3b36ce..639801962c 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PinningData.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PinningData.h @@ -1,108 +1,108 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Repository::Microsoft -{ - struct PinningIndex; -} - -namespace AppInstaller::Pinning -{ - // Possible ways to consider pins when getting a package's available versions - enum class PinBehavior - { - // Ignore pins, returns all available versions. - IgnorePins, - // Include available versions for packages with a Pinning pin. - // Blocking pins and Gating pins still respected. - IncludePinned, - // Respect all the types of pins. - ConsiderPins, - }; - - // The public representation of the pinning database. - struct PinningData - { - // Creates an empty pinning data. - PinningData(); - - // Enum to make the pinning data disposition clear in the caller. - enum class Disposition - { - // The data can only be read. - ReadOnly, - // The data can be read and written. - ReadWrite, - }; - - // Creates a usable pinning data with the given read/write capability. - PinningData(Disposition disposition); - - PinningData(const PinningData&); - PinningData& operator=(const PinningData&); - PinningData(PinningData&&) noexcept; - PinningData& operator=(PinningData&&) noexcept; - ~PinningData(); - - // Determines if the pinning database is opened - operator bool() const; - bool IsDatabaseConnected() const; - - // Pass through functions to the index itself - void AddOrUpdatePin(const Pin& pin); - void RemovePin(const PinKey& pinKey); - std::optional GetPin(const PinKey& pinKey); - std::vector GetAllPins(); - bool ResetAllPins(std::string_view sourceId = {}); - - // A type used for evaluating the pinning state for a given package. - struct PinStateEvaluator - { - PinStateEvaluator( - PinBehavior behavior, - std::shared_ptr database, - const std::shared_ptr& installedVersion); - - PinStateEvaluator(const PinStateEvaluator&); - PinStateEvaluator& operator=(const PinStateEvaluator&); - PinStateEvaluator(PinStateEvaluator&&) noexcept; - PinStateEvaluator& operator=(PinStateEvaluator&&) noexcept; - - ~PinStateEvaluator(); - - // Gets the latest available package version that fits within the pinning restrictions. - // This should be the package object that contains available versions associated with the installed version for which this evaluator was created. - std::shared_ptr GetLatestAvailableVersionForPins(const std::shared_ptr& package); - - // Determines if the given version is an update to the installed version that this object was created with. - // This should be a version associated with the installed version for which this evaluator was created. - bool IsUpdate(const std::shared_ptr& availableVersion); - - // Determines the pin type to apply to the given version. - PinType EvaluatePinType(const std::shared_ptr& packageVersion); - - private: - PinBehavior m_behavior; - std::shared_ptr m_database; - std::optional m_installedPin; - std::optional m_installedVersion; - // Cache pins for available version to reduce database lookups. - std::map> m_availablePins; - }; - - // Creates an object for use in evaluating pinning data for a given package - PinStateEvaluator CreatePinStateEvaluator( - PinBehavior behavior, - const std::shared_ptr& installedVersion); - - private: - std::shared_ptr m_database; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Repository::Microsoft +{ + struct PinningIndex; +} + +namespace AppInstaller::Pinning +{ + // Possible ways to consider pins when getting a package's available versions + enum class PinBehavior + { + // Ignore pins, returns all available versions. + IgnorePins, + // Include available versions for packages with a Pinning pin. + // Blocking pins and Gating pins still respected. + IncludePinned, + // Respect all the types of pins. + ConsiderPins, + }; + + // The public representation of the pinning database. + struct PinningData + { + // Creates an empty pinning data. + PinningData(); + + // Enum to make the pinning data disposition clear in the caller. + enum class Disposition + { + // The data can only be read. + ReadOnly, + // The data can be read and written. + ReadWrite, + }; + + // Creates a usable pinning data with the given read/write capability. + PinningData(Disposition disposition); + + PinningData(const PinningData&); + PinningData& operator=(const PinningData&); + PinningData(PinningData&&) noexcept; + PinningData& operator=(PinningData&&) noexcept; + ~PinningData(); + + // Determines if the pinning database is opened + operator bool() const; + bool IsDatabaseConnected() const; + + // Pass through functions to the index itself + void AddOrUpdatePin(const Pin& pin); + void RemovePin(const PinKey& pinKey); + std::optional GetPin(const PinKey& pinKey); + std::vector GetAllPins(); + bool ResetAllPins(std::string_view sourceId = {}); + + // A type used for evaluating the pinning state for a given package. + struct PinStateEvaluator + { + PinStateEvaluator( + PinBehavior behavior, + std::shared_ptr database, + const std::shared_ptr& installedVersion); + + PinStateEvaluator(const PinStateEvaluator&); + PinStateEvaluator& operator=(const PinStateEvaluator&); + PinStateEvaluator(PinStateEvaluator&&) noexcept; + PinStateEvaluator& operator=(PinStateEvaluator&&) noexcept; + + ~PinStateEvaluator(); + + // Gets the latest available package version that fits within the pinning restrictions. + // This should be the package object that contains available versions associated with the installed version for which this evaluator was created. + std::shared_ptr GetLatestAvailableVersionForPins(const std::shared_ptr& package); + + // Determines if the given version is an update to the installed version that this object was created with. + // This should be a version associated with the installed version for which this evaluator was created. + bool IsUpdate(const std::shared_ptr& availableVersion); + + // Determines the pin type to apply to the given version. + PinType EvaluatePinType(const std::shared_ptr& packageVersion); + + private: + PinBehavior m_behavior; + std::shared_ptr m_database; + std::optional m_installedPin; + std::optional m_installedVersion; + // Cache pins for available version to reduce database lookups. + std::map> m_availablePins; + }; + + // Creates an object for use in evaluating pinning data for a given package + PinStateEvaluator CreatePinStateEvaluator( + PinBehavior behavior, + const std::shared_ptr& installedVersion); + + private: + std::shared_ptr m_database; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/PortableIndex.h b/src/AppInstallerRepositoryCore/Public/winget/PortableIndex.h index 7145132ada..0adbe21b4c 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/PortableIndex.h +++ b/src/AppInstallerRepositoryCore/Public/winget/PortableIndex.h @@ -1,63 +1,63 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -using namespace AppInstaller::Portable; - -namespace AppInstaller::Repository::Microsoft -{ - namespace Schema - { - struct IPortableIndex; - } - - struct PortableIndex : SQLite::SQLiteStorageBase - { - // An id that refers to a specific portable file. - using IdType = SQLite::rowid_t; - - PortableIndex(const PortableIndex&) = delete; - PortableIndex& operator=(const PortableIndex&) = delete; - - PortableIndex(PortableIndex&&); - PortableIndex& operator=(PortableIndex&&); - - ~PortableIndex(); - - // Creates a new PortableIndex database of the given version. - static PortableIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest()); - - // Opens an existing PortableIndex database. - static PortableIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}); - - IdType AddPortableFile(const Portable::PortableFileEntry& file); - - void RemovePortableFile(const Portable::PortableFileEntry& file); - - bool UpdatePortableFile(const Portable::PortableFileEntry& file); - - void AddOrUpdatePortableFile(const Portable::PortableFileEntry& file); - - std::vector GetAllPortableFiles(); - - bool Exists(const Portable::PortableFileEntry& file); - - bool IsEmpty(); - - private: - // Constructor used to open an existing index. - PortableIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); - - // Constructor used to create a new index. - PortableIndex(const std::string& target, SQLite::Version version); - - // Creates the IPortableIndex interface object for this version. - std::unique_ptr CreateIPortableIndex() const; - - std::unique_ptr m_interface; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +using namespace AppInstaller::Portable; + +namespace AppInstaller::Repository::Microsoft +{ + namespace Schema + { + struct IPortableIndex; + } + + struct PortableIndex : SQLite::SQLiteStorageBase + { + // An id that refers to a specific portable file. + using IdType = SQLite::rowid_t; + + PortableIndex(const PortableIndex&) = delete; + PortableIndex& operator=(const PortableIndex&) = delete; + + PortableIndex(PortableIndex&&); + PortableIndex& operator=(PortableIndex&&); + + ~PortableIndex(); + + // Creates a new PortableIndex database of the given version. + static PortableIndex CreateNew(const std::string& filePath, SQLite::Version version = SQLite::Version::Latest()); + + // Opens an existing PortableIndex database. + static PortableIndex Open(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& indexFile = {}); + + IdType AddPortableFile(const Portable::PortableFileEntry& file); + + void RemovePortableFile(const Portable::PortableFileEntry& file); + + bool UpdatePortableFile(const Portable::PortableFileEntry& file); + + void AddOrUpdatePortableFile(const Portable::PortableFileEntry& file); + + std::vector GetAllPortableFiles(); + + bool Exists(const Portable::PortableFileEntry& file); + + bool IsEmpty(); + + private: + // Constructor used to open an existing index. + PortableIndex(const std::string& target, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + // Constructor used to create a new index. + PortableIndex(const std::string& target, SQLite::Version version); + + // Creates the IPortableIndex interface object for this version. + std::unique_ptr CreateIPortableIndex() const; + + std::unique_ptr m_interface; + }; } \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h index 676063ab02..2a1bff82a1 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySearch.h @@ -1,449 +1,449 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository -{ - struct Source; - - // The type of matching to perform during a search. - // The values must be declared in order of preference in search results. - enum class MatchType - { - Exact = 0, - CaseInsensitive, - StartsWith, - Fuzzy, - Substring, - FuzzySubstring, - Wildcard, - }; - - // Convert a MatchType to a string. - std::string_view ToString(MatchType type); - - // The field to match on. - // The values must be declared in order of preference in search results. - enum class PackageMatchField - { - Id = 0, - Name, - Moniker, - Command, - Tag, - PackageFamilyName, - ProductCode, - UpgradeCode, - NormalizedNameAndPublisher, - Market, - Unknown = 9999 - }; - - // Convert a PackageMatchField to a string. - std::string_view ToString(PackageMatchField matchField); - - // Parse a string to PackageMatchField. - PackageMatchField StringToPackageMatchField(std::string_view field); - - // A single match to be performed during a search. - struct RequestMatch - { - MatchType Type; - Utility::NormalizedString Value; - std::optional Additional; - - RequestMatch(MatchType t) : Type(t) {} - RequestMatch(MatchType t, Utility::NormalizedString& v) : Type(t), Value(v) {} - RequestMatch(MatchType t, const Utility::NormalizedString& v) : Type(t), Value(v) {} - RequestMatch(MatchType t, Utility::NormalizedString&& v) : Type(t), Value(std::move(v)) {} - RequestMatch(MatchType t, std::string_view v1, std::string_view v2) : Type(t), Value(v1), Additional(Utility::NormalizedString{ v2 }) {} - }; - - // A match on a specific field to be performed during a search. - struct PackageMatchFilter : public RequestMatch - { - PackageMatchField Field; - - PackageMatchFilter(PackageMatchField f, MatchType t) : RequestMatch(t), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, const Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString&& v) : RequestMatch(t, std::move(v)), Field(f) { EnsureRequiredValues(); } - PackageMatchFilter(PackageMatchField f, MatchType t, std::string_view v1, std::string_view v2) : RequestMatch(t, v1, v2), Field(f) { EnsureRequiredValues(); } - - protected: - void EnsureRequiredValues() - { - // Ensure that the second value always exists when it should - if (Field == PackageMatchField::NormalizedNameAndPublisher && !Additional) - { - Additional = Utility::NormalizedString{}; - } - } - }; - - // The search purpose of the search request. - enum class SearchPurpose - { - // Default search purpose. - Default, - // The result is used for correlation to an installed package. - CorrelationToInstalled, - // The result is used for correlation to an available package. - CorrelationToAvailable, - }; - - // Container for data used to filter the available manifests in a source. - // It can be thought of as: - // (Query || Inclusions...) && Filters... - // If Query and Inclusions are both empty, the starting data set will be the entire database. - // Everything && Filters... - struct SearchRequest - { - // The generic query matches against a source defined set of fields. - std::optional Query; - - // Specific fields used to include more data. - // If Query is defined, this can add more rows afterward. - // If Query is not defined, this is the only set of data included. - std::vector Inclusions; - - // Specific fields used to filter the data further. - std::vector Filters; - - // The search purpose of the search request. - SearchPurpose Purpose = SearchPurpose::Default; - - // The maximum number of results to return. - // The default of 0 will place no limit. - size_t MaximumResults{}; - - // Returns a value indicating whether this request is for all available data. - bool IsForEverything() const; - - // Returns a string summarizing the search request. - std::string ToString() const; - }; - - // A property of a package version. - enum class PackageVersionProperty - { - Id, - Name, - SourceIdentifier, - SourceName, - Version, - Channel, - RelativePath, - // Returned in hexadecimal format - ManifestSHA256Hash, - Publisher, - ArpMinVersion, - ArpMaxVersion, - Moniker, - }; - - // A property of a package version that can have multiple values. - enum class PackageVersionMultiProperty - { - // The package family names (PFN) associated with the package version - PackageFamilyName, - // The product codes associated with the package version. - ProductCode, - // The upgrade codes associated with the package version. - UpgradeCode, - // TODO: Fully implement these 3; the data is not yet in the index source (name and publisher are hacks and locale is not present) - // For future usage of these, be aware of the limitations. - // The package names for the version; ideally these would match in number and order with both Publisher and Locale. - Name, - // The publisher values for the version; ideally these would match in number and order with both Name and Locale. - Publisher, - // The locale of the matching Name and Publisher values; ideally these would match in number and order with both Name and Publisher. - // May be empty if there is only a single value for Name and Publisher. - Locale, - // The tags associated with a package version. - Tag, - // The commands associated with a package version. - Command, - }; - - // A metadata item of a package version. These values are persisted and cannot be changed. - enum class PackageVersionMetadata : int32_t - { - // The InstallerType of an installed package - InstalledType, - // The Scope of an installed package - InstalledScope, - // The system path where the package is installed - InstalledLocation, - // The standard uninstall command; which may be interactive - StandardUninstallCommand, - // An uninstall command that should be non-interactive - SilentUninstallCommand, - // The publisher of the package - Publisher, - // The locale of the package - InstalledLocale, - // The write time for the given version - TrackingWriteTime, - // The Architecture of an installed package - InstalledArchitecture, - // The pinned state of the installed package - // As a package can have multiple pins for multiple sources, this is the strictest pin - PinnedState, - // The Architecture of user intent - UserIntentArchitecture, - // The locale of user intent - UserIntentLocale, - // The standard modify command; which may be interactive - StandardModifyCommand, - // No Modify flag - NoModify, - // No Repair flag - NoRepair, - // The --override arguments provided by the user when initially installing the package; preserved on upgrade - InitialOverrideArguments, - // The --custom switches provided by the user when initially installing the package; preserved on upgrade - InitialCustomSwitches, - }; - - // Convert a PackageVersionMetadata to a string. - std::string_view ToString(PackageVersionMetadata pvm); - - // A single package version. - struct IPackageVersion - { - using Metadata = std::map; - - virtual ~IPackageVersion() = default; - - // Gets a property of this package version. - virtual Utility::LocIndString GetProperty(PackageVersionProperty property) const = 0; - - // Gets a property of this package version that can have multiple values. - virtual std::vector GetMultiProperty(PackageVersionMultiProperty property) const = 0; - - // Gets the manifest of this package version. - virtual Manifest::Manifest GetManifest() = 0; - - // Gets the source where this package version is from. - virtual Source GetSource() const = 0; - - // Gets any metadata associated with this package version. - // Primarily stores data on installed packages. - virtual Metadata GetMetadata() const = 0; - }; - - // A key to identify a package version within a package. - struct PackageVersionKey - { - PackageVersionKey() = default; - - PackageVersionKey(std::string sourceId, Utility::NormalizedString version, Utility::NormalizedString channel) : - SourceId(std::move(sourceId)), Version(std::move(version)), Channel(std::move(channel)) {} - - // The source id that this version came from. - std::string SourceId; - - // The version. - Utility::NormalizedString Version; - - // The channel. - Utility::NormalizedString Channel; - - bool operator<(const PackageVersionKey& other) const - { - // Sort using only the version and channel. - // The order for the sources depends on the context. - return Utility::VersionAndChannel({ Version }, { Channel }) < Utility::VersionAndChannel({ other.Version }, { other.Channel }); - } - - // Determines if a well defined key (this one) is matched by the provided key. - // The provided key may use empty values to indicate no specific matching requirements. - bool IsMatch(const PackageVersionKey& other) const; - - // Determines if this version is the simple "latest" targeting version (version and channel are both empty). - bool IsDefaultLatest() const; - }; - - // A property of a package. - enum class PackageProperty - { - Id, - Name, - }; - - // A property of a package that can have multiple values. - enum class PackageMultiProperty - { - // The package family names (PFN) associated with the package. - PackageFamilyName, - // The product codes associated with the package. - ProductCode, - // The upgrade codes associated with the package. - UpgradeCode, - // The normalized names for the package. - NormalizedName, - // The normalized publisher names for the package. - NormalizedPublisher, - // The tags associated with the package. - Tag, - // The commands associated with the package. - Command, - }; - - // Maps the package multi-property value to its package version multi-property value for internal use. - PackageVersionMultiProperty PackageMultiPropertyToPackageVersionMultiProperty(PackageMultiProperty property); - - // To allow for runtime casting from IPackage to the specific types, this enum contains all of the IPackage implementations. - enum class IPackageType - { - TestPackage, - RestPackage, - SQLitePackage1, - SQLitePackage2, - PinnablePackage, - CompositeInstalledPackage, - }; - - // Contains a collection of package versions. - struct IPackageVersionCollection - { - virtual ~IPackageVersionCollection() = default; - - // Gets all versions of this package. - // The versions will be returned in sorted, descending order. - // Ex. { 4, 3, 2, 1 } - virtual std::vector GetVersionKeys() const = 0; - - // Gets a specific version of this package. - virtual std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const = 0; - - // A convenience method to effectively call `GetVersion(GetVersionKeys[0])`. - virtual std::shared_ptr GetLatestVersion() const = 0; - }; - - // Contains information about a package and its versions from a single source. - struct IPackage : public IPackageVersionCollection - { - virtual ~IPackage() = default; - - // Gets a property of this package. - virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; - - // Gets a property of this package that can have multiple values. - virtual std::vector GetMultiProperty(PackageMultiProperty property) const = 0; - - // Gets the source that this package is from. - virtual Source GetSource() const = 0; - - // Determines if the given IPackage refers to the same package as this one. - virtual bool IsSame(const IPackage*) const = 0; - - // Gets this object as the requested type, or null if it is not the requested type. - virtual const void* CastTo(IPackageType type) const = 0; - }; - - // Contains information about the graph of packages related to a search. - struct ICompositePackage - { - virtual ~ICompositePackage() = default; - - // Gets a property of this package result. - virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; - - // Gets the installed package information. - virtual std::shared_ptr GetInstalled() = 0; - - // Gets all of the available packages for this result. - // There will be at most one package per source in this list. - virtual std::vector> GetAvailable() = 0; - }; - - // Does the equivalent of a dynamic_cast, but without it to allow RTTI to be disabled. - // Example usage: - // bool IsSame(const IPackage* other) const override - // { - // const MyPackage* otherAsMyType = PackageCast(other); - // ... - // } - template - PackageType PackageCast(const IPackage* package) - { - static_assert(std::is_pointer_v, "The target type of the PackageCast must be a pointer; use the same type as if this were a dynamic_cast."); - if (!package) - { - return nullptr; - } - using ActualPackageType = std::remove_pointer_t>; - return reinterpret_cast(package->CastTo(ActualPackageType::PackageType)); - } - - // A single result from the search. - struct ResultMatch - { - // The package found by the search request. - std::shared_ptr Package; - - // The highest order field on which the package matched the search. - PackageMatchFilter MatchCriteria; - - ResultMatch(std::shared_ptr p, PackageMatchFilter f) : Package(std::move(p)), MatchCriteria(std::move(f)) {} - }; - - // Search result data. - struct SearchResult - { - // Contains a failure from the Search. - struct Failure - { - std::string SourceName; - std::exception_ptr Exception; - }; - - // The full set of results from the search. - std::vector Matches; - - // If true, the results were truncated by the given SearchRequest::MaximumResults. - bool Truncated = false; - - // Present if the Search was against a composite source and one failed, but not limited to that scenario. - std::vector Failures; - }; - - struct UnsupportedRequestException : public wil::ResultException - { - UnsupportedRequestException() : wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST) {} - - UnsupportedRequestException( - std::vector unsupportedPackageMatchFields, - std::vector requiredPackageMatchFields, - std::vector unsupportedQueryParameters, - std::vector requiredQueryParameters) : - wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST), - UnsupportedPackageMatchFields(std::move(unsupportedPackageMatchFields)), RequiredPackageMatchFields(std::move(requiredPackageMatchFields)), - UnsupportedQueryParameters(std::move(unsupportedQueryParameters)), RequiredQueryParameters(std::move(requiredQueryParameters)) {} - - std::vector UnsupportedPackageMatchFields; - std::vector RequiredPackageMatchFields; - std::vector UnsupportedQueryParameters; - std::vector RequiredQueryParameters; - - const char* what() const noexcept override; - - private: - mutable std::string m_whatMessage; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository +{ + struct Source; + + // The type of matching to perform during a search. + // The values must be declared in order of preference in search results. + enum class MatchType + { + Exact = 0, + CaseInsensitive, + StartsWith, + Fuzzy, + Substring, + FuzzySubstring, + Wildcard, + }; + + // Convert a MatchType to a string. + std::string_view ToString(MatchType type); + + // The field to match on. + // The values must be declared in order of preference in search results. + enum class PackageMatchField + { + Id = 0, + Name, + Moniker, + Command, + Tag, + PackageFamilyName, + ProductCode, + UpgradeCode, + NormalizedNameAndPublisher, + Market, + Unknown = 9999 + }; + + // Convert a PackageMatchField to a string. + std::string_view ToString(PackageMatchField matchField); + + // Parse a string to PackageMatchField. + PackageMatchField StringToPackageMatchField(std::string_view field); + + // A single match to be performed during a search. + struct RequestMatch + { + MatchType Type; + Utility::NormalizedString Value; + std::optional Additional; + + RequestMatch(MatchType t) : Type(t) {} + RequestMatch(MatchType t, Utility::NormalizedString& v) : Type(t), Value(v) {} + RequestMatch(MatchType t, const Utility::NormalizedString& v) : Type(t), Value(v) {} + RequestMatch(MatchType t, Utility::NormalizedString&& v) : Type(t), Value(std::move(v)) {} + RequestMatch(MatchType t, std::string_view v1, std::string_view v2) : Type(t), Value(v1), Additional(Utility::NormalizedString{ v2 }) {} + }; + + // A match on a specific field to be performed during a search. + struct PackageMatchFilter : public RequestMatch + { + PackageMatchField Field; + + PackageMatchFilter(PackageMatchField f, MatchType t) : RequestMatch(t), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, const Utility::NormalizedString& v) : RequestMatch(t, v), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, Utility::NormalizedString&& v) : RequestMatch(t, std::move(v)), Field(f) { EnsureRequiredValues(); } + PackageMatchFilter(PackageMatchField f, MatchType t, std::string_view v1, std::string_view v2) : RequestMatch(t, v1, v2), Field(f) { EnsureRequiredValues(); } + + protected: + void EnsureRequiredValues() + { + // Ensure that the second value always exists when it should + if (Field == PackageMatchField::NormalizedNameAndPublisher && !Additional) + { + Additional = Utility::NormalizedString{}; + } + } + }; + + // The search purpose of the search request. + enum class SearchPurpose + { + // Default search purpose. + Default, + // The result is used for correlation to an installed package. + CorrelationToInstalled, + // The result is used for correlation to an available package. + CorrelationToAvailable, + }; + + // Container for data used to filter the available manifests in a source. + // It can be thought of as: + // (Query || Inclusions...) && Filters... + // If Query and Inclusions are both empty, the starting data set will be the entire database. + // Everything && Filters... + struct SearchRequest + { + // The generic query matches against a source defined set of fields. + std::optional Query; + + // Specific fields used to include more data. + // If Query is defined, this can add more rows afterward. + // If Query is not defined, this is the only set of data included. + std::vector Inclusions; + + // Specific fields used to filter the data further. + std::vector Filters; + + // The search purpose of the search request. + SearchPurpose Purpose = SearchPurpose::Default; + + // The maximum number of results to return. + // The default of 0 will place no limit. + size_t MaximumResults{}; + + // Returns a value indicating whether this request is for all available data. + bool IsForEverything() const; + + // Returns a string summarizing the search request. + std::string ToString() const; + }; + + // A property of a package version. + enum class PackageVersionProperty + { + Id, + Name, + SourceIdentifier, + SourceName, + Version, + Channel, + RelativePath, + // Returned in hexadecimal format + ManifestSHA256Hash, + Publisher, + ArpMinVersion, + ArpMaxVersion, + Moniker, + }; + + // A property of a package version that can have multiple values. + enum class PackageVersionMultiProperty + { + // The package family names (PFN) associated with the package version + PackageFamilyName, + // The product codes associated with the package version. + ProductCode, + // The upgrade codes associated with the package version. + UpgradeCode, + // TODO: Fully implement these 3; the data is not yet in the index source (name and publisher are hacks and locale is not present) + // For future usage of these, be aware of the limitations. + // The package names for the version; ideally these would match in number and order with both Publisher and Locale. + Name, + // The publisher values for the version; ideally these would match in number and order with both Name and Locale. + Publisher, + // The locale of the matching Name and Publisher values; ideally these would match in number and order with both Name and Publisher. + // May be empty if there is only a single value for Name and Publisher. + Locale, + // The tags associated with a package version. + Tag, + // The commands associated with a package version. + Command, + }; + + // A metadata item of a package version. These values are persisted and cannot be changed. + enum class PackageVersionMetadata : int32_t + { + // The InstallerType of an installed package + InstalledType, + // The Scope of an installed package + InstalledScope, + // The system path where the package is installed + InstalledLocation, + // The standard uninstall command; which may be interactive + StandardUninstallCommand, + // An uninstall command that should be non-interactive + SilentUninstallCommand, + // The publisher of the package + Publisher, + // The locale of the package + InstalledLocale, + // The write time for the given version + TrackingWriteTime, + // The Architecture of an installed package + InstalledArchitecture, + // The pinned state of the installed package + // As a package can have multiple pins for multiple sources, this is the strictest pin + PinnedState, + // The Architecture of user intent + UserIntentArchitecture, + // The locale of user intent + UserIntentLocale, + // The standard modify command; which may be interactive + StandardModifyCommand, + // No Modify flag + NoModify, + // No Repair flag + NoRepair, + // The --override arguments provided by the user when initially installing the package; preserved on upgrade + InitialOverrideArguments, + // The --custom switches provided by the user when initially installing the package; preserved on upgrade + InitialCustomSwitches, + }; + + // Convert a PackageVersionMetadata to a string. + std::string_view ToString(PackageVersionMetadata pvm); + + // A single package version. + struct IPackageVersion + { + using Metadata = std::map; + + virtual ~IPackageVersion() = default; + + // Gets a property of this package version. + virtual Utility::LocIndString GetProperty(PackageVersionProperty property) const = 0; + + // Gets a property of this package version that can have multiple values. + virtual std::vector GetMultiProperty(PackageVersionMultiProperty property) const = 0; + + // Gets the manifest of this package version. + virtual Manifest::Manifest GetManifest() = 0; + + // Gets the source where this package version is from. + virtual Source GetSource() const = 0; + + // Gets any metadata associated with this package version. + // Primarily stores data on installed packages. + virtual Metadata GetMetadata() const = 0; + }; + + // A key to identify a package version within a package. + struct PackageVersionKey + { + PackageVersionKey() = default; + + PackageVersionKey(std::string sourceId, Utility::NormalizedString version, Utility::NormalizedString channel) : + SourceId(std::move(sourceId)), Version(std::move(version)), Channel(std::move(channel)) {} + + // The source id that this version came from. + std::string SourceId; + + // The version. + Utility::NormalizedString Version; + + // The channel. + Utility::NormalizedString Channel; + + bool operator<(const PackageVersionKey& other) const + { + // Sort using only the version and channel. + // The order for the sources depends on the context. + return Utility::VersionAndChannel({ Version }, { Channel }) < Utility::VersionAndChannel({ other.Version }, { other.Channel }); + } + + // Determines if a well defined key (this one) is matched by the provided key. + // The provided key may use empty values to indicate no specific matching requirements. + bool IsMatch(const PackageVersionKey& other) const; + + // Determines if this version is the simple "latest" targeting version (version and channel are both empty). + bool IsDefaultLatest() const; + }; + + // A property of a package. + enum class PackageProperty + { + Id, + Name, + }; + + // A property of a package that can have multiple values. + enum class PackageMultiProperty + { + // The package family names (PFN) associated with the package. + PackageFamilyName, + // The product codes associated with the package. + ProductCode, + // The upgrade codes associated with the package. + UpgradeCode, + // The normalized names for the package. + NormalizedName, + // The normalized publisher names for the package. + NormalizedPublisher, + // The tags associated with the package. + Tag, + // The commands associated with the package. + Command, + }; + + // Maps the package multi-property value to its package version multi-property value for internal use. + PackageVersionMultiProperty PackageMultiPropertyToPackageVersionMultiProperty(PackageMultiProperty property); + + // To allow for runtime casting from IPackage to the specific types, this enum contains all of the IPackage implementations. + enum class IPackageType + { + TestPackage, + RestPackage, + SQLitePackage1, + SQLitePackage2, + PinnablePackage, + CompositeInstalledPackage, + }; + + // Contains a collection of package versions. + struct IPackageVersionCollection + { + virtual ~IPackageVersionCollection() = default; + + // Gets all versions of this package. + // The versions will be returned in sorted, descending order. + // Ex. { 4, 3, 2, 1 } + virtual std::vector GetVersionKeys() const = 0; + + // Gets a specific version of this package. + virtual std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const = 0; + + // A convenience method to effectively call `GetVersion(GetVersionKeys[0])`. + virtual std::shared_ptr GetLatestVersion() const = 0; + }; + + // Contains information about a package and its versions from a single source. + struct IPackage : public IPackageVersionCollection + { + virtual ~IPackage() = default; + + // Gets a property of this package. + virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; + + // Gets a property of this package that can have multiple values. + virtual std::vector GetMultiProperty(PackageMultiProperty property) const = 0; + + // Gets the source that this package is from. + virtual Source GetSource() const = 0; + + // Determines if the given IPackage refers to the same package as this one. + virtual bool IsSame(const IPackage*) const = 0; + + // Gets this object as the requested type, or null if it is not the requested type. + virtual const void* CastTo(IPackageType type) const = 0; + }; + + // Contains information about the graph of packages related to a search. + struct ICompositePackage + { + virtual ~ICompositePackage() = default; + + // Gets a property of this package result. + virtual Utility::LocIndString GetProperty(PackageProperty property) const = 0; + + // Gets the installed package information. + virtual std::shared_ptr GetInstalled() = 0; + + // Gets all of the available packages for this result. + // There will be at most one package per source in this list. + virtual std::vector> GetAvailable() = 0; + }; + + // Does the equivalent of a dynamic_cast, but without it to allow RTTI to be disabled. + // Example usage: + // bool IsSame(const IPackage* other) const override + // { + // const MyPackage* otherAsMyType = PackageCast(other); + // ... + // } + template + PackageType PackageCast(const IPackage* package) + { + static_assert(std::is_pointer_v, "The target type of the PackageCast must be a pointer; use the same type as if this were a dynamic_cast."); + if (!package) + { + return nullptr; + } + using ActualPackageType = std::remove_pointer_t>; + return reinterpret_cast(package->CastTo(ActualPackageType::PackageType)); + } + + // A single result from the search. + struct ResultMatch + { + // The package found by the search request. + std::shared_ptr Package; + + // The highest order field on which the package matched the search. + PackageMatchFilter MatchCriteria; + + ResultMatch(std::shared_ptr p, PackageMatchFilter f) : Package(std::move(p)), MatchCriteria(std::move(f)) {} + }; + + // Search result data. + struct SearchResult + { + // Contains a failure from the Search. + struct Failure + { + std::string SourceName; + std::exception_ptr Exception; + }; + + // The full set of results from the search. + std::vector Matches; + + // If true, the results were truncated by the given SearchRequest::MaximumResults. + bool Truncated = false; + + // Present if the Search was against a composite source and one failed, but not limited to that scenario. + std::vector Failures; + }; + + struct UnsupportedRequestException : public wil::ResultException + { + UnsupportedRequestException() : wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST) {} + + UnsupportedRequestException( + std::vector unsupportedPackageMatchFields, + std::vector requiredPackageMatchFields, + std::vector unsupportedQueryParameters, + std::vector requiredQueryParameters) : + wil::ResultException(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST), + UnsupportedPackageMatchFields(std::move(unsupportedPackageMatchFields)), RequiredPackageMatchFields(std::move(requiredPackageMatchFields)), + UnsupportedQueryParameters(std::move(unsupportedQueryParameters)), RequiredQueryParameters(std::move(requiredQueryParameters)) {} + + std::vector UnsupportedPackageMatchFields; + std::vector RequiredPackageMatchFields; + std::vector UnsupportedQueryParameters; + std::vector RequiredQueryParameters; + + const char* what() const noexcept override; + + private: + mutable std::string m_whatMessage; + }; +} diff --git a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h index 53e2565474..f772a8f8a1 100644 --- a/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h +++ b/src/AppInstallerRepositoryCore/Public/winget/RepositorySource.h @@ -1,389 +1,389 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::Repository -{ - // The interval is of 100 nano seconds precision.This is used by file date period and the Windows::Foundation::TimeSpan exposed in COM api. - using TimeSpan = std::chrono::duration, std::nano>>; - - struct ISourceReference; - struct ISource; - - // Defines the origin of the source details. - enum class SourceOrigin - { - Default, - User, - Predefined, - GroupPolicy, - Metadata, - PackageTracking, - }; - - // Defines the trust level of the source. - enum class SourceTrustLevel : uint32_t - { - None = 0x00000000, - Trusted = 0x00000001, - StoreOrigin = 0x00000002, - }; - - DEFINE_ENUM_FLAG_OPERATORS(SourceTrustLevel); - - // Converts a string_view to the corresponding SourceTrustLevel enum. - SourceTrustLevel ConvertToSourceTrustLevelEnum(std::string_view trustLevel); - - // Converts a vector of trust level strings to the corresponding SourceTrustLevel enum flag. - SourceTrustLevel ConvertToSourceTrustLevelFlag(std::vector values); - - // Converts a SourceTrustLevel flag to a list of trust level strings. - std::vector SourceTrustLevelFlagToList(SourceTrustLevel trustLevel); - - // Converts a SourceTrustLevel enum to the corresponding string. - std::string_view SourceTrustLevelEnumToString(SourceTrustLevel trustLevel); - - // Gets the full trust level string name for display. - std::string GetSourceTrustLevelForDisplay(SourceTrustLevel trustLevel); - - std::string_view ToString(SourceOrigin origin); - - // Fields that require user agreements. - enum class ImplicitAgreementFieldEnum : int - { - None = 0x0, - Market = 0x1, - }; - - DEFINE_ENUM_FLAG_OPERATORS(ImplicitAgreementFieldEnum); - - // A predefined source. - // These sources are not under the direct control of the user, such as packages installed on the system. - enum class PredefinedSource - { - // Default behavior. Contains ARP packages installed as for user and for machine, MSIX packages for current user. - Installed, - // Only contains packages installed as for user - InstalledUser, - // Only contains packages installed as for machine - InstalledMachine, - ARP, - MSIX, - Installing, - // Same as `Installed`, but creating the source reference for this is sufficient to cause the cache to be updated - // on next Open of any `Installed` or `InstalledForceCacheUpdate`. - InstalledForceCacheUpdate, - }; - - // A well known source. - // These come with the app and can be disabled but not removed. - enum class WellKnownSource - { - WinGet, - MicrosoftStore, - DesktopFrameworks, - WinGetFont, - }; - - // Search behavior for composite sources. - // Only relevant for composite sources with an installed source, not for aggregates of multiple available sources. - // Installed and available packages in the result are always correlated when possible. - enum class CompositeSearchBehavior - { - // Search only installed packages. - Installed, - // Search both installed and available packages. - AllPackages, - // Search only available packages. - AvailablePackages, - }; - - // Interface for source configurations. Source configurations are used to get a source reference without opening the source. - struct SourceDetails - { - // The name of the source. - std::string Name; - - // The type of the source. - std::string Type; - - // The argument used when adding the source. - std::string Arg; - - // The source's extra data string. - std::string Data; - - // The source's unique identifier. - std::string Identifier; - - // The origin of the source. - SourceOrigin Origin = SourceOrigin::Default; - - // The trust level of the source - SourceTrustLevel TrustLevel = SourceTrustLevel::None; - - // The last time that this source was updated. - std::chrono::system_clock::time_point LastUpdateTime = {}; - - // Stores the earliest time that a background update should be attempted. - std::chrono::system_clock::time_point DoNotUpdateBefore = {}; - - // Whether the source supports InstalledSource correlation. - bool SupportInstalledSearchCorrelation = true; - - // The configuration of how the server certificate will be validated. - Certificates::PinningConfiguration CertificatePinningConfiguration; - - // This value is used as an alternative to the `Arg` value if it is failing to function properly. - // The alternate location must point to identical data or inconsistencies may arise. - std::string AlternateArg; - - // Whether the source should be hidden by default unless explicitly declared. - bool Explicit = false; - - // Value used for sorting the sources and making decisions - // (like preferring one source over the other if both have a package to install). - // Higher values come first in priority order. - int32_t Priority = 0; - }; - - // Check if a source matches a well known source - std::optional CheckForWellKnownSource(const SourceDetails& sourceDetails); - - // Individual source agreement entry. Label will be highlighted in the display as the key of the agreement entry. - struct SourceAgreement - { - SourceAgreement() = default; - - SourceAgreement(std::string label, std::string text, std::string url) : - Label(std::move(label)), Text(std::move(text)), Url(std::move(url)) {} - - std::string Label; - std::string Text; - std::string Url; - }; - - // Interface for retrieving information about a source after opening the source. - struct SourceInformation - { - // Identifier of the source agreements. This is used to identify if source agreements have changed. - std::string SourceAgreementsIdentifier; - - // List of source agreements that require user to accept. - std::vector SourceAgreements; - - // Unsupported match fields in search request. If this field is in the filters, the request may fail. - std::vector UnsupportedPackageMatchFields; - - // Required match fields in search request. If this field is not found in the filters, the request may fail(except Market). - std::vector RequiredPackageMatchFields; - - // Unsupported query parameters in get manifest request. - std::vector UnsupportedQueryParameters; - - // Required query parameters in get manifest request. - std::vector RequiredQueryParameters; - - // Source authentication info. - Authentication::AuthenticationInfo Authentication; - }; - - // Contains information about edits to a source. - struct SourceEdit - { - SourceEdit() = default; - - // The Explicit property of a source. - std::optional Explicit; - - // The Priority property of a source. - std::optional Priority; - }; - - // Allows calling code to inquire about specific features of an ISource implementation. - // The default state of any new flag is false. - enum class SourceFeatureFlag - { - // If true, the manifests for this source may contain more data than is available from just the - // version information found from a search. - ManifestMayContainAdditionalSystemReferenceStrings, - }; - - // Represents a source which would be interacted from outside of repository lib. - struct Source - { - // Default constructor with an empty source. - Source(); - - // Constructor to get a named source, passing empty string will get all available sources. - Source(std::string_view name); - - // Constructor to get a PredefinedSource. Like installed source, etc. - Source(PredefinedSource source); - - // Constructor to get a source coming with winget. Like winget community source, etc. - Source(WellKnownSource source); - - // Constructor for a source to be added. - Source(std::string_view name, std::string_view arg, std::string_view type, SourceTrustLevel trustLevel, const SourceEdit& additionalProperties); - - // Constructor for creating a composite source from a list of available sources. - Source(const std::vector& availableSources); - - // Constructor for creating a composite source from an installed source and available source(may be composite already). - Source( - const Source& installedSource, - const Source& availableSource, - CompositeSearchBehavior searchBehavior = CompositeSearchBehavior::Installed); - - // Constructor for creating a Source object from an existing ISource. - // Should only be used internally by ISource implementations to return the value from IPackageVersion::GetSource. - Source(std::shared_ptr source); - - // Bool operator to check if a source reference is successfully acquired. - // Theoretically, the constructor could just throw when CreateSource returns empty. - // To avoid putting try catch everywhere, we use bool operator here. - operator bool() const; - - // Determines if the sources are equivalent. - // Currently only works for individual sources, not composites. - bool operator==(const Source& other) const; - bool operator!=(const Source& other) const; - - // Gets the source's identifier; a unique identifier independent of the name - // that will not change between a remove/add or between additional adds. - // Must be suitable for filesystem names unless the source is internal to winget, - // in which case the identifier should begin with a '*' character. - std::string GetIdentifier() const; - - // Get the source's configuration details from settings. - const SourceDetails& GetDetails() const; - - // Get the source's information. - SourceInformation GetInformation() const; - - // Query the value of the given feature flag. - // The default state of any new flag is false. - bool QueryFeatureFlag(SourceFeatureFlag flag) const; - - // Returns true if the origin type can contain available packages. - bool ContainsAvailablePackages() const; - - // Set custom header. Must be set before Open to have effect. - bool SetCustomHeader(std::optional header); - - // Set caller. Must be set before Open to have effect. - void SetCaller(std::string caller); - - // Set authentication arguments. Must be set before Open to have effect. - void SetAuthenticationArguments(Authentication::AuthenticationArguments args); - - // Set a custom server certificate validation callback. Must be set before Open to have effect. - // Return true from the callback to accept the connection, false to reject. - // Only invoked when the certificate pinning group policy is not configured. - void SetServerCertificateValidationCallback(std::function callback); - - // Set thread globals. Must be set before Open to have effect. - void SetThreadGlobals(const std::shared_ptr& threadGlobals); - - // Set background update check interval. - void SetBackgroundUpdateInterval(TimeSpan interval); - - // Indicates that we are only interested in the PackageTrackingCatalog for the source. - // Must be set before Open to have effect, and will prevent the underlying source from being updated or opened. - void InstalledPackageInformationOnly(bool value); - - // Determines if this source refers to the given well known source. - bool IsWellKnownSource(WellKnownSource wellKnownSource) const; - - // Execute a search on the source. - SearchResult Search(const SearchRequest& request) const; - - /* Source agreements */ - - // Get required agreement fields info. - ImplicitAgreementFieldEnum GetAgreementFieldsFromSourceInformation() const; - - // Checks the source agreements and returns if agreements are satisfied. - bool CheckSourceAgreements() const; - - // Saves the accepted source agreements in metadata. - void SaveAcceptedSourceAgreements() const; - - /* Composite sources */ - - // Gets a value indicating whether this source is a composite of other sources, - // and thus the packages may come from disparate sources as well. - bool IsComposite() const; - - // Gets the available sources if the source is composite. - std::vector GetAvailableSources() const; - - /* Writable sources */ - - // Adds a package version to the source. - void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - - // Removes a package version from the source. - void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); - - /* Source operations */ - - // Opens the source. This function should throw upon open failure rather than returning an empty pointer. - std::vector Open(IProgressCallback& progress); - - // Add source. Source add command. - bool Add(IProgressCallback& progress); - - // Update Source. Source update command. - std::vector Update(IProgressCallback& progress); - - // Remove source. Source remove command. - bool Remove(IProgressCallback& progress); - - // Edit source. Source edit command. - void Edit(const SourceEdit& edits); - - // Determines if this source is a valid edit of otherSource. - // Returns true if this source qualifies as an edit of the other source. - bool RequiresChanges(const SourceEdit& edits); - - // Gets the tracking catalog for the current source. - PackageTrackingCatalog GetTrackingCatalog() const; - - // Drop source. Source reset command. - static bool DropSource(std::string_view name); - - // Get a list of all available SourceDetails. - static std::vector GetCurrentSources(); - - // Get a default source type is the source type used when adding a source without specifying a type. - static std::string_view GetDefaultSourceType(); - - private: - void InitializeSourceReference(std::string_view name); - - std::vector> m_sourceReferences; - std::shared_ptr m_source; - bool m_isSourceToBeAdded = false; - bool m_isComposite = false; - std::optional m_backgroundUpdateInterval; - bool m_installedPackageInformationOnly = false; - mutable std::shared_ptr m_trackingCatalog; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::Repository +{ + // The interval is of 100 nano seconds precision.This is used by file date period and the Windows::Foundation::TimeSpan exposed in COM api. + using TimeSpan = std::chrono::duration, std::nano>>; + + struct ISourceReference; + struct ISource; + + // Defines the origin of the source details. + enum class SourceOrigin + { + Default, + User, + Predefined, + GroupPolicy, + Metadata, + PackageTracking, + }; + + // Defines the trust level of the source. + enum class SourceTrustLevel : uint32_t + { + None = 0x00000000, + Trusted = 0x00000001, + StoreOrigin = 0x00000002, + }; + + DEFINE_ENUM_FLAG_OPERATORS(SourceTrustLevel); + + // Converts a string_view to the corresponding SourceTrustLevel enum. + SourceTrustLevel ConvertToSourceTrustLevelEnum(std::string_view trustLevel); + + // Converts a vector of trust level strings to the corresponding SourceTrustLevel enum flag. + SourceTrustLevel ConvertToSourceTrustLevelFlag(std::vector values); + + // Converts a SourceTrustLevel flag to a list of trust level strings. + std::vector SourceTrustLevelFlagToList(SourceTrustLevel trustLevel); + + // Converts a SourceTrustLevel enum to the corresponding string. + std::string_view SourceTrustLevelEnumToString(SourceTrustLevel trustLevel); + + // Gets the full trust level string name for display. + std::string GetSourceTrustLevelForDisplay(SourceTrustLevel trustLevel); + + std::string_view ToString(SourceOrigin origin); + + // Fields that require user agreements. + enum class ImplicitAgreementFieldEnum : int + { + None = 0x0, + Market = 0x1, + }; + + DEFINE_ENUM_FLAG_OPERATORS(ImplicitAgreementFieldEnum); + + // A predefined source. + // These sources are not under the direct control of the user, such as packages installed on the system. + enum class PredefinedSource + { + // Default behavior. Contains ARP packages installed as for user and for machine, MSIX packages for current user. + Installed, + // Only contains packages installed as for user + InstalledUser, + // Only contains packages installed as for machine + InstalledMachine, + ARP, + MSIX, + Installing, + // Same as `Installed`, but creating the source reference for this is sufficient to cause the cache to be updated + // on next Open of any `Installed` or `InstalledForceCacheUpdate`. + InstalledForceCacheUpdate, + }; + + // A well known source. + // These come with the app and can be disabled but not removed. + enum class WellKnownSource + { + WinGet, + MicrosoftStore, + DesktopFrameworks, + WinGetFont, + }; + + // Search behavior for composite sources. + // Only relevant for composite sources with an installed source, not for aggregates of multiple available sources. + // Installed and available packages in the result are always correlated when possible. + enum class CompositeSearchBehavior + { + // Search only installed packages. + Installed, + // Search both installed and available packages. + AllPackages, + // Search only available packages. + AvailablePackages, + }; + + // Interface for source configurations. Source configurations are used to get a source reference without opening the source. + struct SourceDetails + { + // The name of the source. + std::string Name; + + // The type of the source. + std::string Type; + + // The argument used when adding the source. + std::string Arg; + + // The source's extra data string. + std::string Data; + + // The source's unique identifier. + std::string Identifier; + + // The origin of the source. + SourceOrigin Origin = SourceOrigin::Default; + + // The trust level of the source + SourceTrustLevel TrustLevel = SourceTrustLevel::None; + + // The last time that this source was updated. + std::chrono::system_clock::time_point LastUpdateTime = {}; + + // Stores the earliest time that a background update should be attempted. + std::chrono::system_clock::time_point DoNotUpdateBefore = {}; + + // Whether the source supports InstalledSource correlation. + bool SupportInstalledSearchCorrelation = true; + + // The configuration of how the server certificate will be validated. + Certificates::PinningConfiguration CertificatePinningConfiguration; + + // This value is used as an alternative to the `Arg` value if it is failing to function properly. + // The alternate location must point to identical data or inconsistencies may arise. + std::string AlternateArg; + + // Whether the source should be hidden by default unless explicitly declared. + bool Explicit = false; + + // Value used for sorting the sources and making decisions + // (like preferring one source over the other if both have a package to install). + // Higher values come first in priority order. + int32_t Priority = 0; + }; + + // Check if a source matches a well known source + std::optional CheckForWellKnownSource(const SourceDetails& sourceDetails); + + // Individual source agreement entry. Label will be highlighted in the display as the key of the agreement entry. + struct SourceAgreement + { + SourceAgreement() = default; + + SourceAgreement(std::string label, std::string text, std::string url) : + Label(std::move(label)), Text(std::move(text)), Url(std::move(url)) {} + + std::string Label; + std::string Text; + std::string Url; + }; + + // Interface for retrieving information about a source after opening the source. + struct SourceInformation + { + // Identifier of the source agreements. This is used to identify if source agreements have changed. + std::string SourceAgreementsIdentifier; + + // List of source agreements that require user to accept. + std::vector SourceAgreements; + + // Unsupported match fields in search request. If this field is in the filters, the request may fail. + std::vector UnsupportedPackageMatchFields; + + // Required match fields in search request. If this field is not found in the filters, the request may fail(except Market). + std::vector RequiredPackageMatchFields; + + // Unsupported query parameters in get manifest request. + std::vector UnsupportedQueryParameters; + + // Required query parameters in get manifest request. + std::vector RequiredQueryParameters; + + // Source authentication info. + Authentication::AuthenticationInfo Authentication; + }; + + // Contains information about edits to a source. + struct SourceEdit + { + SourceEdit() = default; + + // The Explicit property of a source. + std::optional Explicit; + + // The Priority property of a source. + std::optional Priority; + }; + + // Allows calling code to inquire about specific features of an ISource implementation. + // The default state of any new flag is false. + enum class SourceFeatureFlag + { + // If true, the manifests for this source may contain more data than is available from just the + // version information found from a search. + ManifestMayContainAdditionalSystemReferenceStrings, + }; + + // Represents a source which would be interacted from outside of repository lib. + struct Source + { + // Default constructor with an empty source. + Source(); + + // Constructor to get a named source, passing empty string will get all available sources. + Source(std::string_view name); + + // Constructor to get a PredefinedSource. Like installed source, etc. + Source(PredefinedSource source); + + // Constructor to get a source coming with winget. Like winget community source, etc. + Source(WellKnownSource source); + + // Constructor for a source to be added. + Source(std::string_view name, std::string_view arg, std::string_view type, SourceTrustLevel trustLevel, const SourceEdit& additionalProperties); + + // Constructor for creating a composite source from a list of available sources. + Source(const std::vector& availableSources); + + // Constructor for creating a composite source from an installed source and available source(may be composite already). + Source( + const Source& installedSource, + const Source& availableSource, + CompositeSearchBehavior searchBehavior = CompositeSearchBehavior::Installed); + + // Constructor for creating a Source object from an existing ISource. + // Should only be used internally by ISource implementations to return the value from IPackageVersion::GetSource. + Source(std::shared_ptr source); + + // Bool operator to check if a source reference is successfully acquired. + // Theoretically, the constructor could just throw when CreateSource returns empty. + // To avoid putting try catch everywhere, we use bool operator here. + operator bool() const; + + // Determines if the sources are equivalent. + // Currently only works for individual sources, not composites. + bool operator==(const Source& other) const; + bool operator!=(const Source& other) const; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names unless the source is internal to winget, + // in which case the identifier should begin with a '*' character. + std::string GetIdentifier() const; + + // Get the source's configuration details from settings. + const SourceDetails& GetDetails() const; + + // Get the source's information. + SourceInformation GetInformation() const; + + // Query the value of the given feature flag. + // The default state of any new flag is false. + bool QueryFeatureFlag(SourceFeatureFlag flag) const; + + // Returns true if the origin type can contain available packages. + bool ContainsAvailablePackages() const; + + // Set custom header. Must be set before Open to have effect. + bool SetCustomHeader(std::optional header); + + // Set caller. Must be set before Open to have effect. + void SetCaller(std::string caller); + + // Set authentication arguments. Must be set before Open to have effect. + void SetAuthenticationArguments(Authentication::AuthenticationArguments args); + + // Set a custom server certificate validation callback. Must be set before Open to have effect. + // Return true from the callback to accept the connection, false to reject. + // Only invoked when the certificate pinning group policy is not configured. + void SetServerCertificateValidationCallback(std::function callback); + + // Set thread globals. Must be set before Open to have effect. + void SetThreadGlobals(const std::shared_ptr& threadGlobals); + + // Set background update check interval. + void SetBackgroundUpdateInterval(TimeSpan interval); + + // Indicates that we are only interested in the PackageTrackingCatalog for the source. + // Must be set before Open to have effect, and will prevent the underlying source from being updated or opened. + void InstalledPackageInformationOnly(bool value); + + // Determines if this source refers to the given well known source. + bool IsWellKnownSource(WellKnownSource wellKnownSource) const; + + // Execute a search on the source. + SearchResult Search(const SearchRequest& request) const; + + /* Source agreements */ + + // Get required agreement fields info. + ImplicitAgreementFieldEnum GetAgreementFieldsFromSourceInformation() const; + + // Checks the source agreements and returns if agreements are satisfied. + bool CheckSourceAgreements() const; + + // Saves the accepted source agreements in metadata. + void SaveAcceptedSourceAgreements() const; + + /* Composite sources */ + + // Gets a value indicating whether this source is a composite of other sources, + // and thus the packages may come from disparate sources as well. + bool IsComposite() const; + + // Gets the available sources if the source is composite. + std::vector GetAvailableSources() const; + + /* Writable sources */ + + // Adds a package version to the source. + void AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + // Removes a package version from the source. + void RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath); + + /* Source operations */ + + // Opens the source. This function should throw upon open failure rather than returning an empty pointer. + std::vector Open(IProgressCallback& progress); + + // Add source. Source add command. + bool Add(IProgressCallback& progress); + + // Update Source. Source update command. + std::vector Update(IProgressCallback& progress); + + // Remove source. Source remove command. + bool Remove(IProgressCallback& progress); + + // Edit source. Source edit command. + void Edit(const SourceEdit& edits); + + // Determines if this source is a valid edit of otherSource. + // Returns true if this source qualifies as an edit of the other source. + bool RequiresChanges(const SourceEdit& edits); + + // Gets the tracking catalog for the current source. + PackageTrackingCatalog GetTrackingCatalog() const; + + // Drop source. Source reset command. + static bool DropSource(std::string_view name); + + // Get a list of all available SourceDetails. + static std::vector GetCurrentSources(); + + // Get a default source type is the source type used when adding a source without specifying a type. + static std::string_view GetDefaultSourceType(); + + private: + void InitializeSourceReference(std::string_view name); + + std::vector> m_sourceReferences; + std::shared_ptr m_source; + bool m_isSourceToBeAdded = false; + bool m_isComposite = false; + std::optional m_backgroundUpdateInterval; + bool m_installedPackageInformationOnly = false; + mutable std::shared_ptr m_trackingCatalog; + }; +} diff --git a/src/AppInstallerRepositoryCore/RepositorySearch.cpp b/src/AppInstallerRepositoryCore/RepositorySearch.cpp index faa100dbfb..358600a9d1 100644 --- a/src/AppInstallerRepositoryCore/RepositorySearch.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySearch.cpp @@ -1,255 +1,255 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/RepositorySearch.h" - -using namespace AppInstaller::Settings; -using namespace std::chrono_literals; - -namespace AppInstaller::Repository -{ - namespace - { - std::string GetStringVectorMessage(const std::vector& input) - { - std::string result; - bool first = true; - for (auto const& field : input) - { - if (first) - { - result += field; - first = false; - } - else - { - result += ", " + field; - } - } - return result; - } - } - - bool SearchRequest::IsForEverything() const - { - return (!Query.has_value() && Inclusions.empty() && Filters.empty()); - } - - std::string SearchRequest::ToString() const - { - std::ostringstream result; - - result << "Query:"; - if (Query) - { - result << '\'' << Query.value().Value << "'[" << Repository::ToString(Query.value().Type) << ']'; - } - else - { - result << "[none]"; - } - - for (const auto& include : Inclusions) - { - result << " Include:" << Repository::ToString(include.Field) << "='" << include.Value << "'"; - if (include.Additional) - { - result << "+'" << include.Additional.value() << "'"; - } - result << "[" << Repository::ToString(include.Type) << "]"; - } - - for (const auto& filter : Filters) - { - result << " Filter:" << Repository::ToString(filter.Field) << "='" << filter.Value << "'[" << Repository::ToString(filter.Type) << "]"; - } - - if (MaximumResults) - { - result << " Limit:" << MaximumResults; - } - - return result.str(); - } - - std::string_view ToString(PackageVersionMetadata pvm) - { - switch (pvm) - { - case PackageVersionMetadata::InstalledType: return "InstalledType"sv; - case PackageVersionMetadata::InstalledScope: return "InstalledScope"sv; - case PackageVersionMetadata::InstalledLocation: return "InstalledLocation"sv; - case PackageVersionMetadata::StandardUninstallCommand: return "StandardUninstallCommand"sv; - case PackageVersionMetadata::SilentUninstallCommand: return "SilentUninstallCommand"sv; - case PackageVersionMetadata::Publisher: return "Publisher"sv; - case PackageVersionMetadata::InstalledLocale: return "InstalledLocale"sv; - case PackageVersionMetadata::TrackingWriteTime: return "TrackingWriteTime"sv; - case PackageVersionMetadata::InstalledArchitecture: return "InstalledArchitecture"sv; - case PackageVersionMetadata::PinnedState: return "PinnedState"sv; - case PackageVersionMetadata::UserIntentArchitecture: return "UserIntentArchitecture"sv; - case PackageVersionMetadata::UserIntentLocale: return "UserIntentLocale"sv; - case PackageVersionMetadata::InitialOverrideArguments: return "InitialOverrideArguments"sv; - case PackageVersionMetadata::InitialCustomSwitches: return "InitialCustomSwitches"sv; - default: return "Unknown"sv; - } - } - - bool PackageVersionKey::IsMatch(const PackageVersionKey& other) const - { - return - ((other.SourceId.empty() || other.SourceId == SourceId) && - (other.Version.empty() || Utility::Version{ other.Version } == Utility::Version{ Version }) && - (other.Channel.empty() || Utility::ICUCaseInsensitiveEquals(other.Channel, Channel))); - } - - bool PackageVersionKey::IsDefaultLatest() const - { - return Version.empty() && Channel.empty(); - } - - PackageVersionMultiProperty PackageMultiPropertyToPackageVersionMultiProperty(PackageMultiProperty property) - { - switch (property) - { - case PackageMultiProperty::PackageFamilyName: return PackageVersionMultiProperty::PackageFamilyName; - case PackageMultiProperty::ProductCode: return PackageVersionMultiProperty::ProductCode; - case PackageMultiProperty::UpgradeCode: return PackageVersionMultiProperty::UpgradeCode; - case PackageMultiProperty::NormalizedName: return PackageVersionMultiProperty::Name; - case PackageMultiProperty::NormalizedPublisher: return PackageVersionMultiProperty::Publisher; - case PackageMultiProperty::Tag: return PackageVersionMultiProperty::Tag; - case PackageMultiProperty::Command: return PackageVersionMultiProperty::Command; - default: - THROW_HR_MSG(E_UNEXPECTED, "PackageMultiProperty must map to a PackageVersionMultiProperty"); - } - } - - const char* UnsupportedRequestException::what() const noexcept - { - if (m_whatMessage.empty()) - { - m_whatMessage = "The request is not supported."; - - if (!UnsupportedPackageMatchFields.empty()) - { - m_whatMessage += "Unsupported Package Match Fields: " + GetStringVectorMessage(UnsupportedPackageMatchFields); - } - if (!RequiredPackageMatchFields.empty()) - { - m_whatMessage += "Required Package Match Fields: " + GetStringVectorMessage(RequiredPackageMatchFields); - } - if (!UnsupportedQueryParameters.empty()) - { - m_whatMessage += "Unsupported Query Parameters: " + GetStringVectorMessage(UnsupportedQueryParameters); - } - if (!RequiredQueryParameters.empty()) - { - m_whatMessage += "Required Query Parameters: " + GetStringVectorMessage(RequiredQueryParameters); - } - } - return m_whatMessage.c_str(); - } - - std::string_view ToString(MatchType type) - { - using namespace std::string_view_literals; - - switch (type) - { - case MatchType::Exact: - return "Exact"sv; - case MatchType::CaseInsensitive: - return "CaseInsensitive"sv; - case MatchType::StartsWith: - return "StartsWith"sv; - case MatchType::Substring: - return "Substring"sv; - case MatchType::Wildcard: - return "Wildcard"sv; - case MatchType::Fuzzy: - return "Fuzzy"sv; - case MatchType::FuzzySubstring: - return "FuzzySubstring"sv; - } - - return "UnknownMatchType"sv; - } - - std::string_view ToString(PackageMatchField matchField) - { - using namespace std::string_view_literals; - - switch (matchField) - { - case PackageMatchField::Command: - return "Command"sv; - case PackageMatchField::Id: - return "Id"sv; - case PackageMatchField::Moniker: - return "Moniker"sv; - case PackageMatchField::Name: - return "Name"sv; - case PackageMatchField::Tag: - return "Tag"sv; - case PackageMatchField::PackageFamilyName: - return "PackageFamilyName"sv; - case PackageMatchField::ProductCode: - return "ProductCode"sv; - case PackageMatchField::UpgradeCode: - return "UpgradeCode"sv; - case PackageMatchField::NormalizedNameAndPublisher: - return "NormalizedNameAndPublisher"sv; - case PackageMatchField::Market: - return "Market"sv; - } - - return "UnknownMatchField"sv; - } - - PackageMatchField StringToPackageMatchField(std::string_view field) - { - std::string toLower = Utility::ToLower(field); - - if (toLower == "command") - { - return PackageMatchField::Command; - } - else if (toLower == "id") - { - return PackageMatchField::Id; - } - else if (toLower == "moniker") - { - return PackageMatchField::Moniker; - } - else if (toLower == "name") - { - return PackageMatchField::Name; - } - else if (toLower == "tag") - { - return PackageMatchField::Tag; - } - else if (toLower == "packagefamilyname") - { - return PackageMatchField::PackageFamilyName; - } - else if (toLower == "productcode") - { - return PackageMatchField::ProductCode; - } - else if (toLower == "upgradecode") - { - return PackageMatchField::UpgradeCode; - } - else if (toLower == "normalizednameandpublisher") - { - return PackageMatchField::NormalizedNameAndPublisher; - } - else if (toLower == "market") - { - return PackageMatchField::Market; - } - - return PackageMatchField::Unknown; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/RepositorySearch.h" + +using namespace AppInstaller::Settings; +using namespace std::chrono_literals; + +namespace AppInstaller::Repository +{ + namespace + { + std::string GetStringVectorMessage(const std::vector& input) + { + std::string result; + bool first = true; + for (auto const& field : input) + { + if (first) + { + result += field; + first = false; + } + else + { + result += ", " + field; + } + } + return result; + } + } + + bool SearchRequest::IsForEverything() const + { + return (!Query.has_value() && Inclusions.empty() && Filters.empty()); + } + + std::string SearchRequest::ToString() const + { + std::ostringstream result; + + result << "Query:"; + if (Query) + { + result << '\'' << Query.value().Value << "'[" << Repository::ToString(Query.value().Type) << ']'; + } + else + { + result << "[none]"; + } + + for (const auto& include : Inclusions) + { + result << " Include:" << Repository::ToString(include.Field) << "='" << include.Value << "'"; + if (include.Additional) + { + result << "+'" << include.Additional.value() << "'"; + } + result << "[" << Repository::ToString(include.Type) << "]"; + } + + for (const auto& filter : Filters) + { + result << " Filter:" << Repository::ToString(filter.Field) << "='" << filter.Value << "'[" << Repository::ToString(filter.Type) << "]"; + } + + if (MaximumResults) + { + result << " Limit:" << MaximumResults; + } + + return result.str(); + } + + std::string_view ToString(PackageVersionMetadata pvm) + { + switch (pvm) + { + case PackageVersionMetadata::InstalledType: return "InstalledType"sv; + case PackageVersionMetadata::InstalledScope: return "InstalledScope"sv; + case PackageVersionMetadata::InstalledLocation: return "InstalledLocation"sv; + case PackageVersionMetadata::StandardUninstallCommand: return "StandardUninstallCommand"sv; + case PackageVersionMetadata::SilentUninstallCommand: return "SilentUninstallCommand"sv; + case PackageVersionMetadata::Publisher: return "Publisher"sv; + case PackageVersionMetadata::InstalledLocale: return "InstalledLocale"sv; + case PackageVersionMetadata::TrackingWriteTime: return "TrackingWriteTime"sv; + case PackageVersionMetadata::InstalledArchitecture: return "InstalledArchitecture"sv; + case PackageVersionMetadata::PinnedState: return "PinnedState"sv; + case PackageVersionMetadata::UserIntentArchitecture: return "UserIntentArchitecture"sv; + case PackageVersionMetadata::UserIntentLocale: return "UserIntentLocale"sv; + case PackageVersionMetadata::InitialOverrideArguments: return "InitialOverrideArguments"sv; + case PackageVersionMetadata::InitialCustomSwitches: return "InitialCustomSwitches"sv; + default: return "Unknown"sv; + } + } + + bool PackageVersionKey::IsMatch(const PackageVersionKey& other) const + { + return + ((other.SourceId.empty() || other.SourceId == SourceId) && + (other.Version.empty() || Utility::Version{ other.Version } == Utility::Version{ Version }) && + (other.Channel.empty() || Utility::ICUCaseInsensitiveEquals(other.Channel, Channel))); + } + + bool PackageVersionKey::IsDefaultLatest() const + { + return Version.empty() && Channel.empty(); + } + + PackageVersionMultiProperty PackageMultiPropertyToPackageVersionMultiProperty(PackageMultiProperty property) + { + switch (property) + { + case PackageMultiProperty::PackageFamilyName: return PackageVersionMultiProperty::PackageFamilyName; + case PackageMultiProperty::ProductCode: return PackageVersionMultiProperty::ProductCode; + case PackageMultiProperty::UpgradeCode: return PackageVersionMultiProperty::UpgradeCode; + case PackageMultiProperty::NormalizedName: return PackageVersionMultiProperty::Name; + case PackageMultiProperty::NormalizedPublisher: return PackageVersionMultiProperty::Publisher; + case PackageMultiProperty::Tag: return PackageVersionMultiProperty::Tag; + case PackageMultiProperty::Command: return PackageVersionMultiProperty::Command; + default: + THROW_HR_MSG(E_UNEXPECTED, "PackageMultiProperty must map to a PackageVersionMultiProperty"); + } + } + + const char* UnsupportedRequestException::what() const noexcept + { + if (m_whatMessage.empty()) + { + m_whatMessage = "The request is not supported."; + + if (!UnsupportedPackageMatchFields.empty()) + { + m_whatMessage += "Unsupported Package Match Fields: " + GetStringVectorMessage(UnsupportedPackageMatchFields); + } + if (!RequiredPackageMatchFields.empty()) + { + m_whatMessage += "Required Package Match Fields: " + GetStringVectorMessage(RequiredPackageMatchFields); + } + if (!UnsupportedQueryParameters.empty()) + { + m_whatMessage += "Unsupported Query Parameters: " + GetStringVectorMessage(UnsupportedQueryParameters); + } + if (!RequiredQueryParameters.empty()) + { + m_whatMessage += "Required Query Parameters: " + GetStringVectorMessage(RequiredQueryParameters); + } + } + return m_whatMessage.c_str(); + } + + std::string_view ToString(MatchType type) + { + using namespace std::string_view_literals; + + switch (type) + { + case MatchType::Exact: + return "Exact"sv; + case MatchType::CaseInsensitive: + return "CaseInsensitive"sv; + case MatchType::StartsWith: + return "StartsWith"sv; + case MatchType::Substring: + return "Substring"sv; + case MatchType::Wildcard: + return "Wildcard"sv; + case MatchType::Fuzzy: + return "Fuzzy"sv; + case MatchType::FuzzySubstring: + return "FuzzySubstring"sv; + } + + return "UnknownMatchType"sv; + } + + std::string_view ToString(PackageMatchField matchField) + { + using namespace std::string_view_literals; + + switch (matchField) + { + case PackageMatchField::Command: + return "Command"sv; + case PackageMatchField::Id: + return "Id"sv; + case PackageMatchField::Moniker: + return "Moniker"sv; + case PackageMatchField::Name: + return "Name"sv; + case PackageMatchField::Tag: + return "Tag"sv; + case PackageMatchField::PackageFamilyName: + return "PackageFamilyName"sv; + case PackageMatchField::ProductCode: + return "ProductCode"sv; + case PackageMatchField::UpgradeCode: + return "UpgradeCode"sv; + case PackageMatchField::NormalizedNameAndPublisher: + return "NormalizedNameAndPublisher"sv; + case PackageMatchField::Market: + return "Market"sv; + } + + return "UnknownMatchField"sv; + } + + PackageMatchField StringToPackageMatchField(std::string_view field) + { + std::string toLower = Utility::ToLower(field); + + if (toLower == "command") + { + return PackageMatchField::Command; + } + else if (toLower == "id") + { + return PackageMatchField::Id; + } + else if (toLower == "moniker") + { + return PackageMatchField::Moniker; + } + else if (toLower == "name") + { + return PackageMatchField::Name; + } + else if (toLower == "tag") + { + return PackageMatchField::Tag; + } + else if (toLower == "packagefamilyname") + { + return PackageMatchField::PackageFamilyName; + } + else if (toLower == "productcode") + { + return PackageMatchField::ProductCode; + } + else if (toLower == "upgradecode") + { + return PackageMatchField::UpgradeCode; + } + else if (toLower == "normalizednameandpublisher") + { + return PackageMatchField::NormalizedNameAndPublisher; + } + else if (toLower == "market") + { + return PackageMatchField::Market; + } + + return PackageMatchField::Unknown; + } +} diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index dc4db9bbfe..ac273fc0c8 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -1,1150 +1,1150 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ISource.h" -#include "CompositeSource.h" -#include "SourceFactory.h" -#include "SourceList.h" -#include "SourcePolicy.h" -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include "Microsoft/PredefinedWriteableSourceFactory.h" -#include "Microsoft/PreIndexedPackageSourceFactory.h" -#include "Rest/RestSourceFactory.h" -#include "PackageTrackingCatalogSourceFactory.h" -#include "SourceUpdateChecks.h" - -#ifndef AICLI_DISABLE_TEST_HOOKS -#include "Microsoft/ConfigurableTestSourceFactory.h" -#endif - -#include - -using namespace AppInstaller::Settings; -using namespace std::chrono_literals; -using namespace AppInstaller::Utility::literals; - -namespace AppInstaller::Repository -{ - namespace - { -#ifndef AICLI_DISABLE_TEST_HOOKS - static std::map()>> s_Sources_TestHook_SourceFactories; -#endif - - std::shared_ptr CreateSourceFromDetails(const SourceDetails& details) - { - return ISourceFactory::GetForType(details.Type)->Create(details); - } - - std::chrono::milliseconds GetMillisecondsToWait(std::chrono::seconds retryAfter, size_t randomMultiplier = 1) - { - if (retryAfter != 0s) - { - return std::chrono::duration_cast(retryAfter); - } - else - { - // Add a bit of randomness to the retry wait time - std::default_random_engine randomEngine(std::random_device{}()); - std::uniform_int_distribution distribution(2000, 10000); - - return std::chrono::milliseconds(distribution(randomEngine) * randomMultiplier); - } - } - - struct AddOrUpdateResult - { - bool UpdateChecked = false; - bool MetadataWritten = false; - }; - - template - AddOrUpdateResult AddOrUpdateFromDetails(SourceDetails& details, MemberFunc member, IProgressCallback& progress) - { - AddOrUpdateResult result; - - auto factory = ISourceFactory::GetForType(details.Type); - - // If we are instructed to wait longer than this, just fail rather than retrying. - constexpr std::chrono::seconds maximumWaitTimeAllowed = 60s; - std::chrono::seconds waitSecondsForRetry = 0s; - - // Attempt; if it fails, wait a short time and retry. - try - { - result.UpdateChecked = (factory.get()->*member)(details, progress); - if (result.UpdateChecked) - { - details.LastUpdateTime = std::chrono::system_clock::now(); - result.MetadataWritten = true; - } - return result; - } - catch (const Utility::ServiceUnavailableException& sue) - { - waitSecondsForRetry = sue.RetryAfter(); - - // Do not retry if the server tell us to wait more than the max time allowed. - if (waitSecondsForRetry > maximumWaitTimeAllowed) - { - details.DoNotUpdateBefore = std::chrono::system_clock::now() + waitSecondsForRetry; - AICLI_LOG(Repo, Info, << "Source `" << details.Name << "` unavailable first try, setting DoNotUpdateBefore to " << details.DoNotUpdateBefore); - result.MetadataWritten = true; - return result; - } - } - CATCH_LOG(); - - std::chrono::milliseconds millisecondsToWait = GetMillisecondsToWait(waitSecondsForRetry); - - AICLI_LOG(Repo, Info, << "Source add/update failed, waiting " << millisecondsToWait.count() << " milliseconds and retrying: " << details.Name); - - if (!ProgressCallback::Wait(progress, millisecondsToWait)) - { - AICLI_LOG(Repo, Info, << "Source second try cancelled."); - return {}; - } - - try - { - // If this one fails, maybe the problem is persistent. - result.UpdateChecked = (factory.get()->*member)(details, progress); - if (result.UpdateChecked) - { - details.LastUpdateTime = std::chrono::system_clock::now(); - result.MetadataWritten = true; - } - } - catch (const Utility::ServiceUnavailableException& sue) - { - details.DoNotUpdateBefore = std::chrono::system_clock::now() + GetMillisecondsToWait(sue.RetryAfter(), 3); - AICLI_LOG(Repo, Info, << "Source `" << details.Name << "` unavailable second try, setting DoNotUpdateBefore to " << details.DoNotUpdateBefore); - result.MetadataWritten = true; - } - - return result; - } - - AddOrUpdateResult AddSourceFromDetails(SourceDetails& details, IProgressCallback& progress) - { - return AddOrUpdateFromDetails(details, &ISourceFactory::Add, progress); - } - - AddOrUpdateResult UpdateSourceFromDetails(SourceDetails& details, IProgressCallback& progress) - { - return AddOrUpdateFromDetails(details, &ISourceFactory::Update, progress); - } - - AddOrUpdateResult BackgroundUpdateSourceFromDetails(SourceDetails& details, IProgressCallback& progress) - { - return AddOrUpdateFromDetails(details, &ISourceFactory::BackgroundUpdate, progress); - } - - bool RemoveSourceFromDetails(const SourceDetails& details, IProgressCallback& progress) - { - auto factory = ISourceFactory::GetForType(details.Type); - - return factory->Remove(details, progress); - } - - bool ContainsAvailablePackagesInternal(SourceOrigin origin) - { - return (origin == SourceOrigin::Default || origin == SourceOrigin::GroupPolicy || origin == SourceOrigin::User); - } - - SourceDetails GetPredefinedSourceDetails(PredefinedSource source) - { - SourceDetails details; - details.Origin = SourceOrigin::Predefined; - - switch (source) - { - case PredefinedSource::Installed: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::None); - return details; - case PredefinedSource::InstalledForceCacheUpdate: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate); - return details; - case PredefinedSource::InstalledUser: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::User); - return details; - case PredefinedSource::InstalledMachine: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::Machine); - return details; - case PredefinedSource::ARP: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::ARP); - return details; - case PredefinedSource::MSIX: - details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); - details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX); - return details; - case PredefinedSource::Installing: - details.Type = Microsoft::PredefinedWriteableSourceFactory::Type(); - // As long as there is only one type this is not particularly needed, but Arg is exposed publicly - // so this is used here for consistency with other predefined sources. - details.Arg = Microsoft::PredefinedWriteableSourceFactory::TypeToString(Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing); - return details; - } - - THROW_HR(E_UNEXPECTED); - } - - // Carries the exception from an OpenSource call and presents it back at search time. - struct OpenExceptionProxy : public ISource, std::enable_shared_from_this - { - static constexpr ISourceType SourceType = ISourceType::OpenExceptionProxy; - - OpenExceptionProxy(const SourceDetails& details, std::exception_ptr exception) : - m_details(details), m_exception(std::move(exception)) {} - - const SourceDetails& GetDetails() const override { return m_details; } - - const std::string& GetIdentifier() const override { return m_details.Identifier; } - - SearchResult Search(const SearchRequest&) const override - { - SearchResult result; - result.Failures.emplace_back(SearchResult::Failure{ GetDetails().Name, m_exception }); - return result; - } - - void* CastTo(ISourceType type) override - { - if (type == SourceType) - { - return this; - } - - return nullptr; - } - - private: - SourceDetails m_details; - std::exception_ptr m_exception; - }; - - // A wrapper that doesn't actually forward the search requests. - struct TrackingOnlySourceWrapper : public ISource - { - TrackingOnlySourceWrapper(std::shared_ptr wrapped) : m_wrapped(std::move(wrapped)) - { - m_identifier = m_wrapped->GetIdentifier(); - } - - const std::string& GetIdentifier() const override { return m_identifier; } - - SourceDetails& GetDetails() const override { return m_wrapped->GetDetails(); } - - SourceInformation GetInformation() const override { return m_wrapped->GetInformation(); } - - SearchResult Search(const SearchRequest&) const override { return {}; } - - void* CastTo(ISourceType) override { return nullptr; } - - private: - std::shared_ptr m_wrapped; - std::string m_identifier; - }; - - // A wrapper to create another wrapper. - struct TrackingOnlyReferenceWrapper : public ISourceReference - { - TrackingOnlyReferenceWrapper(std::shared_ptr wrapped) : m_wrapped(std::move(wrapped)) {} - - std::string GetIdentifier() override { return m_wrapped->GetIdentifier(); } - - SourceDetails& GetDetails() override { return m_wrapped->GetDetails(); } - - SourceInformation GetInformation() override { return m_wrapped->GetInformation(); } - - bool SetCustomHeader(std::optional) override { return false; } - - void SetCaller(std::string caller) override { m_wrapped->SetCaller(std::move(caller)); } - - std::shared_ptr Open(IProgressCallback&) override - { - return std::make_shared(m_wrapped); - } - - private: - std::shared_ptr m_wrapped; - }; - } - - std::unique_ptr ISourceFactory::GetForType(std::string_view type) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - // Tests can ensure case matching - auto itr = s_Sources_TestHook_SourceFactories.find(std::string(type)); - if (itr != s_Sources_TestHook_SourceFactories.end()) - { - return itr->second(); - } - - if (Utility::CaseInsensitiveEquals(Microsoft::ConfigurableTestSourceFactory::Type(), type)) - { - return Microsoft::ConfigurableTestSourceFactory::Create(); - } -#endif - - // For now, enable an empty type to represent the only one we have. - if (type.empty() || - Utility::CaseInsensitiveEquals(Microsoft::PreIndexedPackageSourceFactory::Type(), type)) - { - return Microsoft::PreIndexedPackageSourceFactory::Create(); - } - // Should always come from code, so no need for case insensitivity - else if (Microsoft::PredefinedInstalledSourceFactory::Type() == type) - { - return Microsoft::PredefinedInstalledSourceFactory::Create(); - } - // Should always come from code, so no need for case insensitivity - else if (Microsoft::PredefinedWriteableSourceFactory::Type() == type) - { - return Microsoft::PredefinedWriteableSourceFactory::Create(); - } - // Should always come from code, so no need for case insensitivity - else if (PackageTrackingCatalogSourceFactory::Type() == type) - { - return PackageTrackingCatalogSourceFactory::Create(); - } - else if (Utility::CaseInsensitiveEquals(Rest::RestSourceFactory::Type(), type)) - { - return Rest::RestSourceFactory::Create(); - } - - THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE); - } - - SourceTrustLevel ConvertToSourceTrustLevelEnum(std::string_view trustLevel) - { - std::string lowerTrustLevel = Utility::ToLower(trustLevel); - - if (lowerTrustLevel == "storeorigin") - { - return SourceTrustLevel::StoreOrigin; - } - else if (lowerTrustLevel == "trusted") - { - return SourceTrustLevel::Trusted; - } - else if (lowerTrustLevel == "none") - { - return SourceTrustLevel::None; - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); - } - } - - std::string_view SourceTrustLevelEnumToString(SourceTrustLevel trustLevel) - { - switch (trustLevel) - { - case SourceTrustLevel::StoreOrigin: - return "StoreOrigin"sv; - case SourceTrustLevel::Trusted: - return "Trusted"sv; - case SourceTrustLevel::None: - return "None"sv; - } - - return "Unknown"sv; - } - - SourceTrustLevel ConvertToSourceTrustLevelFlag(std::vector trustLevels) - { - Repository::SourceTrustLevel result = Repository::SourceTrustLevel::None; - for (auto& trustLevel : trustLevels) - { - Repository::SourceTrustLevel trustLevelEnum = ConvertToSourceTrustLevelEnum(trustLevel); - if (trustLevelEnum == Repository::SourceTrustLevel::None) - { - return Repository::SourceTrustLevel::None; - } - else if (trustLevelEnum == Repository::SourceTrustLevel::Trusted) - { - WI_SetFlag(result, Repository::SourceTrustLevel::Trusted); - } - else if (trustLevelEnum == Repository::SourceTrustLevel::StoreOrigin) - { - WI_SetFlag(result, Repository::SourceTrustLevel::StoreOrigin); - } - else - { - THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Invalid source trust level."); - } - } - - return result; - } - - std::vector SourceTrustLevelFlagToList(SourceTrustLevel trustLevel) - { - std::vector result; - - if (WI_IsFlagSet(trustLevel, Repository::SourceTrustLevel::Trusted)) - { - result.emplace_back(Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::Trusted)); - } - if (WI_IsFlagSet(trustLevel, Repository::SourceTrustLevel::StoreOrigin)) - { - result.emplace_back(Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::StoreOrigin)); - } - - return result; - } - - std::string GetSourceTrustLevelForDisplay(SourceTrustLevel trustLevel) - { - std::vector trustLevelList = Repository::SourceTrustLevelFlagToList(trustLevel); - std::vector locIndList(trustLevelList.begin(), trustLevelList.end()); - return Utility::Join("|"_liv, locIndList); - } - - std::string_view ToString(SourceOrigin origin) - { - switch (origin) - { - case SourceOrigin::Default: - return "Default"sv; - case SourceOrigin::User: - return "User"sv; - case SourceOrigin::Predefined: - return "Predefined"sv; - case SourceOrigin::GroupPolicy: - return "GroupPolicy"sv; - case SourceOrigin::Metadata: - return "Metadata"sv; - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::optional CheckForWellKnownSource(const SourceDetails& sourceDetails) - { - return CheckForWellKnownSourceMatch(sourceDetails.Name, sourceDetails.Arg, sourceDetails.Type); - } - - Source::Source() {} - - Source::Source(std::string_view name) - { - InitializeSourceReference(name); - } - - Source::Source(PredefinedSource source) - { - SourceDetails details = GetPredefinedSourceDetails(source); - m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); - } - - Source::Source(WellKnownSource source) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !IsWellKnownSourceEnabled(source)); - - auto details = GetWellKnownSourceDetailsInternal(source); - - // Populate metadata - SourceList sourceList; - auto sourceDetailsWithMetadata = sourceList.GetSource(details.Name); - if (sourceDetailsWithMetadata) - { - sourceDetailsWithMetadata->CopyMetadataFieldsTo(details); - } - - m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); - } - - Source::Source(std::string_view name, std::string_view arg, std::string_view type, SourceTrustLevel trustLevel, const SourceEdit& additionalProperties) - { - m_isSourceToBeAdded = true; - SourceDetails details; - - std::optional wellKnownSourceCheck = CheckForWellKnownSourceMatch(name, arg, type); - - if (wellKnownSourceCheck) - { - details = GetWellKnownSourceDetailsInternal(wellKnownSourceCheck.value()); - } - else - { - details.Name = name; - details.Arg = arg; - details.Type = type; - details.TrustLevel = trustLevel; - if (additionalProperties.Explicit) - { - details.Explicit = additionalProperties.Explicit.value(); - } - if (additionalProperties.Priority) - { - details.Priority = additionalProperties.Priority.value(); - } - } - - m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); - } - - Source::Source(const std::vector& availableSources) - { - std::shared_ptr compositeSource = std::make_shared("*CompositeSource"); - - for (const auto& availableSource : availableSources) - { - THROW_HR_IF(E_INVALIDARG, !availableSource.m_source || availableSource.IsComposite()); - compositeSource->AddAvailableSource(availableSource.m_source); - } - - m_source = compositeSource; - m_isComposite = true; - } - - Source::Source(const Source& installedSource, const Source& availableSource, CompositeSearchBehavior searchBehavior) - { - THROW_HR_IF(E_INVALIDARG, !installedSource.m_source || installedSource.m_isComposite || !availableSource.m_source); - - std::shared_ptr compositeSource = SourceCast(availableSource.m_source); - - if (!compositeSource) - { - compositeSource = std::make_shared("*CompositeSource"); - compositeSource->AddAvailableSource(availableSource.m_source); - } - - compositeSource->SetInstalledSource(installedSource, searchBehavior); - - m_source = compositeSource; - m_isComposite = true; - } - - Source::Source(std::shared_ptr source) : m_source(std::move(source)) {} - - Source::operator bool() const - { - return !m_sourceReferences.empty() || m_source != nullptr; - } - - void Source::InitializeSourceReference(std::string_view name) - { - SourceList sourceList; - - if (name.empty()) - { - auto currentSources = sourceList.GetCurrentSourceRefs(); - if (currentSources.empty()) - { - AICLI_LOG(Repo, Info, << "Default source requested, but no sources configured"); - } - else if (currentSources.size() == 1) - { - if (!currentSources[0].get().Explicit) - { - AICLI_LOG(Repo, Info, << "Default source requested, only 1 source available, using the only source: " << currentSources[0].get().Name); - InitializeSourceReference(currentSources[0].get().Name); - } - else - { - AICLI_LOG(Repo, Info, << "Skipping explicit source reference " << currentSources[0].get().Name); - } - } - else - { - AICLI_LOG(Repo, Info, << "Default source requested, multiple sources available, adding all to source references."); - - for (auto& source : currentSources) - { - if (!source.get().Explicit) - { - AICLI_LOG(Repo, Info, << "Adding to source references " << source.get().Name); - m_sourceReferences.emplace_back(CreateSourceFromDetails(source)); - } - else - { - AICLI_LOG(Repo, Info, << "Skipping explicit source reference " << source.get().Name); - } - } - - if (m_sourceReferences.size() > 1) - { - m_isComposite = true; - } - } - } - else - { - auto source = sourceList.GetCurrentSource(name); - if (!source) - { - AICLI_LOG(Repo, Info, << "Named source requested, but not found: " << name); - } - else - { - AICLI_LOG(Repo, Info, << "Named source requested, found: " << source->Name); - m_sourceReferences.emplace_back(CreateSourceFromDetails(*source)); - } - } - } - - bool Source::operator==(const Source& other) const - { - SourceDetails thisDetails = GetDetails(); - SourceDetails otherDetails = other.GetDetails(); - - return (thisDetails.Type == otherDetails.Type && thisDetails.Identifier == otherDetails.Identifier); - } - - bool Source::operator!=(const Source& other) const - { - return !operator==(other); - } - - std::string Source::GetIdentifier() const - { - if (m_source) - { - return m_source->GetIdentifier(); - } - else if (m_sourceReferences.size() == 1) - { - return m_sourceReferences[0]->GetIdentifier(); - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - } - - const SourceDetails& Source::GetDetails() const - { - if (m_source) - { - return m_source->GetDetails(); - } - else if (m_sourceReferences.size() == 1) - { - return m_sourceReferences[0]->GetDetails(); - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - } - - SourceInformation Source::GetInformation() const - { - if (m_source && !m_isComposite) - { - return m_source->GetInformation(); - } - else if (m_sourceReferences.size() == 1) - { - return m_sourceReferences[0]->GetInformation(); - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - } - - bool Source::QueryFeatureFlag(SourceFeatureFlag flag) const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); - return m_source->QueryFeatureFlag(flag); - } - - bool Source::ContainsAvailablePackages() const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), IsComposite()); - return ContainsAvailablePackagesInternal(GetDetails().Origin); - } - - bool Source::SetCustomHeader(std::optional header) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_sourceReferences.size() != 1); - return m_sourceReferences[0]->SetCustomHeader(header); - } - - void Source::SetCaller(std::string caller) - { - for (auto& sourceReference : m_sourceReferences) - { - sourceReference->SetCaller(caller); - } - } - - void Source::SetAuthenticationArguments(Authentication::AuthenticationArguments args) - { - for (auto& sourceReference : m_sourceReferences) - { - sourceReference->SetAuthenticationArguments(args); - } - } - - void Source::SetServerCertificateValidationCallback(std::function callback) - { - for (auto& sourceReference : m_sourceReferences) - { - sourceReference->SetServerCertificateValidationCallback(callback); - } - } - - void Source::SetThreadGlobals(const std::shared_ptr& threadGlobals) - { - for (auto& sourceReference : m_sourceReferences) - { - sourceReference->SetThreadGlobals(threadGlobals); - } - } - - void Source::SetBackgroundUpdateInterval(TimeSpan interval) - { - m_backgroundUpdateInterval = interval; - } - - void Source::InstalledPackageInformationOnly(bool value) - { - m_installedPackageInformationOnly = value; - } - - bool Source::IsWellKnownSource(WellKnownSource wellKnownSource) const - { - SourceDetails details = GetDetails(); - auto wellKnown = CheckForWellKnownSourceMatch(details.Name, details.Arg, details.Type); - return wellKnown && wellKnown.value() == wellKnownSource; - } - - SearchResult Source::Search(const SearchRequest& request) const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); - return m_source->Search(request); - } - - ImplicitAgreementFieldEnum Source::GetAgreementFieldsFromSourceInformation() const - { - ImplicitAgreementFieldEnum result = ImplicitAgreementFieldEnum::None; - - auto info = GetInformation(); - if (info.RequiredPackageMatchFields.end() != std::find_if(info.RequiredPackageMatchFields.begin(), info.RequiredPackageMatchFields.end(), [&](const auto& field) { return Utility::CaseInsensitiveEquals(field, "market"); }) || - info.RequiredQueryParameters.end() != std::find_if(info.RequiredQueryParameters.begin(), info.RequiredQueryParameters.end(), [&](const auto& param) { return Utility::CaseInsensitiveEquals(param, "market"); })) - { - WI_SetFlag(result, ImplicitAgreementFieldEnum::Market); - } - - return result; - } - - bool Source::CheckSourceAgreements() const - { - auto sourceName = GetDetails().Name; - auto agreementFields = GetAgreementFieldsFromSourceInformation(); - auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier; - - SourceList sourceList; - return sourceList.CheckSourceAgreements(sourceName, agreementsIdentifier, agreementFields); - } - - void Source::SaveAcceptedSourceAgreements() const - { - auto sourceName = GetDetails().Name; - auto agreementFields = GetAgreementFieldsFromSourceInformation(); - auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier; - - SourceList sourceList; - return sourceList.SaveAcceptedSourceAgreements(sourceName, agreementsIdentifier, agreementFields); - } - - bool Source::IsComposite() const - { - return m_isComposite; - } - - std::vector Source::GetAvailableSources() const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source || !m_isComposite); - - auto compositeSource = SourceCast(m_source); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !compositeSource); - - return compositeSource->GetAvailableSources(); - } - - void Source::AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); - auto writableSource = SourceCast(m_source); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource); - writableSource->AddPackageVersion(manifest, relativePath); - } - - void Source::RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); - auto writableSource = SourceCast(m_source); - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource); - writableSource->RemovePackageVersion(manifest, relativePath); - } - - std::vector Source::Open(IProgressCallback& progress) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.empty()); - - std::vector result; - - if (!m_source) - { - std::vector>* sourceReferencesToOpen = nullptr; - std::vector> sourceReferencesForTrackingOnly; - std::unique_ptr sourceList; - - if (m_installedPackageInformationOnly) - { - sourceReferencesToOpen = &sourceReferencesForTrackingOnly; - - // Create a wrapper for each reference - for (auto& sourceReference : m_sourceReferences) - { - sourceReferencesForTrackingOnly.emplace_back(std::make_shared(sourceReference)); - } - } - else - { - // Check for updates before opening. - for (auto& sourceReference : m_sourceReferences) - { - if (ShouldUpdateBeforeOpen(sourceReference.get(), m_backgroundUpdateInterval)) - { - auto& details = sourceReference->GetDetails(); - - try - { - // TODO: Consider adding a context callback to indicate we are doing the same action - // to avoid the progress bar fill up multiple times. - AddOrUpdateResult updateResult = BackgroundUpdateSourceFromDetails(details, progress); - - if (updateResult.MetadataWritten) - { - if (sourceList == nullptr) - { - sourceList = std::make_unique(); - } - - auto detailsInternal = sourceList->GetSource(details.Name); - detailsInternal->CopyMetadataFieldsFrom(details); - sourceList->SaveMetadata(*detailsInternal); - } - - if (!updateResult.UpdateChecked) - { - AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); - result.emplace_back(details); - } - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - AICLI_LOG(Repo, Warning, << "Failed to update source: " << details.Name); - result.emplace_back(details); - } - } - } - - sourceReferencesToOpen = &m_sourceReferences; - } - - if (sourceReferencesToOpen->size() > 1) - { - AICLI_LOG(Repo, Info, << "Multiple sources available, creating aggregated source."); - auto aggregatedSource = std::make_shared("*DefaultSource"); - std::vector> openExceptionProxies; - - for (auto& sourceReference : *sourceReferencesToOpen) - { - AICLI_LOG(Repo, Info, << "Adding to aggregated source: " << sourceReference->GetDetails().Name); - - try - - { - aggregatedSource->AddAvailableSource(sourceReference->Open(progress)); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - AICLI_LOG(Repo, Warning, << "Failed to open available source: " << sourceReference->GetDetails().Name); - openExceptionProxies.emplace_back(std::make_shared(sourceReference->GetDetails(), std::current_exception())); - } - } - - // If all sources failed to open, then throw an exception that is specific to this case. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, !aggregatedSource->HasAvailableSource()); - - // Place all of the proxies into the source to be searched later - for (auto& proxy : openExceptionProxies) - { - aggregatedSource->AddAvailableSource(Source{ std::move(proxy) }); - } - - m_source = aggregatedSource; - } - else - { - m_source = (*sourceReferencesToOpen)[0]->Open(progress); - } - } - - return result; - } - - bool Source::Add(IProgressCallback& progress) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_isSourceToBeAdded || m_sourceReferences.size() != 1); - - auto& sourceDetails = m_sourceReferences[0]->GetDetails(); - - // If the source type is empty, use a default. - // AddSourceForDetails will also check for empty, but we need the actual type before that for validation. - if (sourceDetails.Type.empty()) - { - sourceDetails.Type = GetDefaultSourceType(); - } - - AICLI_LOG(Repo, Info, << "Adding source: Name[" << sourceDetails.Name << "], Type[" << sourceDetails.Type << "], Arg[" << sourceDetails.Arg << "]"); - - // Check all sources for the given name. - SourceList sourceList; - - auto source = sourceList.GetSource(sourceDetails.Name); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, source != nullptr && source->Origin != SourceOrigin::Metadata && !source->IsTombstone); - - // Check sources allowed by group policy - auto blockingPolicy = GetPolicyBlockingUserSource(sourceDetails.Name, sourceDetails.Type, sourceDetails.Arg, false); - if (blockingPolicy != TogglePolicy::Policy::None) - { - throw GroupPolicyException(blockingPolicy); - } - - sourceDetails.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(0); - - // Allow the origin to stay as Default if the incoming details match a well known value - if (!(sourceDetails.Origin == SourceOrigin::Default && CheckForWellKnownSourceMatch(sourceDetails.Name, sourceDetails.Arg, sourceDetails.Type))) - { - sourceDetails.Origin = SourceOrigin::User; - } - - bool result = AddSourceFromDetails(sourceDetails, progress).UpdateChecked; - if (result) - { - sourceList.AddSource(sourceDetails); - SaveAcceptedSourceAgreements(); - m_isSourceToBeAdded = false; - AICLI_LOG(Repo, Info, << "Source created with extra data: " << sourceDetails.Data); - } - - return result; - } - - std::vector Source::Update(IProgressCallback& progress) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_source || m_sourceReferences.empty()); - - SourceList sourceList; - std::vector result; - - for (auto& sourceReference : m_sourceReferences) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !ContainsAvailablePackagesInternal(sourceReference->GetDetails().Origin)); - - auto& details = sourceReference->GetDetails(); - AICLI_LOG(Repo, Info, << "Named source to be updated, found: " << details.Name); - - try - { - // TODO: Consider adding a context callback to indicate we are doing the same action - // to avoid the progress bar fill up multiple times. - AddOrUpdateResult updateResult = UpdateSourceFromDetails(details, progress); - - if (updateResult.MetadataWritten) - { - auto detailsInternal = sourceList.GetSource(details.Name); - detailsInternal->CopyMetadataFieldsFrom(details); - sourceList.SaveMetadata(*detailsInternal); - } - - if (!updateResult.UpdateChecked) - { - AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); - result.emplace_back(details); - } - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); - result.emplace_back(details); - } - } - - return result; - } - - bool Source::Remove(IProgressCallback& progress) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.size() != 1 || m_source); - - const auto& details = m_sourceReferences[0]->GetDetails(); - AICLI_LOG(Repo, Info, << "Named source to be removed, found: " << details.Name << " [" << ToString(details.Origin) << ']'); - - EnsureSourceIsRemovable(details); - - bool result = RemoveSourceFromDetails(details, progress); - if (result) - { - SourceList sourceList; - sourceList.RemoveSource(details); - } - - return result; - } - - void Source::Edit(const SourceEdit& edits) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.size() != 1 || m_source); - - auto& details = m_sourceReferences[0]->GetDetails(); - AICLI_LOG(Repo, Info, << "Named source to be edited, found: " << details.Name << " [" << ToString(details.Origin) << ']'); - - // This is intentionally the same policy checks as Remove. If the source cannot be removed then it cannot be edited. - EnsureSourceIsRemovable(details); - - if (RequiresChanges(edits)) - { - if (edits.Explicit.has_value()) - { - details.Explicit = edits.Explicit.value(); - } - - if (edits.Priority.has_value()) - { - details.Priority = edits.Priority.value(); - } - - // Apply the edits and update source list. - SourceList sourceList; - sourceList.EditSource(details); - } - } - - bool Source::RequiresChanges(const SourceEdit& edits) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_sourceReferences.size() != 1); - - const auto& details = m_sourceReferences[0]->GetDetails(); - - bool isChanged = false; - - if (edits.Explicit.has_value() && edits.Explicit.value() != details.Explicit) - { - isChanged = true; - } - - if (edits.Priority.has_value() && edits.Priority.value() != details.Priority) - { - isChanged = true; - } - - return isChanged; - } - - PackageTrackingCatalog Source::GetTrackingCatalog() const - { - // With C++20, consider removing the shared_ptr here and making the one inside PackageTrackingCatalog atomic. - std::shared_ptr currentTrackingCatalog = std::atomic_load(&m_trackingCatalog); - if (!currentTrackingCatalog) - { - std::shared_ptr newTrackingCatalog = std::make_shared(PackageTrackingCatalog::CreateForSource(*this)); - - if (std::atomic_compare_exchange_strong(&m_trackingCatalog, ¤tTrackingCatalog, newTrackingCatalog)) - { - currentTrackingCatalog = newTrackingCatalog; - } - } - - return *currentTrackingCatalog; - } - - std::vector Source::GetCurrentSources() - { - SourceList sourceList; - - std::vector result; - for (auto&& source : sourceList.GetCurrentSourceRefs()) - { - result.emplace_back(std::move(source)); - } - - return result; - } - - bool Source::DropSource(std::string_view name) - { - if (name.empty()) - { - SourceList::RemoveSettingsStreams(); - return true; - } - else - { - SourceList sourceList; - - auto source = sourceList.GetSource(name); - if (!source) - { - AICLI_LOG(Repo, Info, << "Named source to be dropped, but not found: " << name); - return false; - } - else - { - AICLI_LOG(Repo, Info, << "Named source to be dropped, found: " << source->Name); - - EnsureSourceIsRemovable(*source); - - // For default sources, overrides of default sources, and tombstones masking - // default sources, reset to clean state instead of tombstoning/removing. - if (source->Origin == SourceOrigin::Default || - (source->Origin == SourceOrigin::User && (source->IsOverride || source->IsTombstone))) - { - sourceList.ResetSource(*source); - } - else - { - sourceList.RemoveSource(*source); - } - - return true; - } - } - } - - std::string_view Source::GetDefaultSourceType() - { - return ISourceFactory::GetForType("")->TypeName(); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - void TestHook_SetSourceFactoryOverride(const std::string& type, std::function()>&& factory) - { - s_Sources_TestHook_SourceFactories[type] = std::move(factory); - } - - void TestHook_ClearSourceFactoryOverrides() - { - s_Sources_TestHook_SourceFactories.clear(); - } -#endif -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ISource.h" +#include "CompositeSource.h" +#include "SourceFactory.h" +#include "SourceList.h" +#include "SourcePolicy.h" +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include "Microsoft/PredefinedWriteableSourceFactory.h" +#include "Microsoft/PreIndexedPackageSourceFactory.h" +#include "Rest/RestSourceFactory.h" +#include "PackageTrackingCatalogSourceFactory.h" +#include "SourceUpdateChecks.h" + +#ifndef AICLI_DISABLE_TEST_HOOKS +#include "Microsoft/ConfigurableTestSourceFactory.h" +#endif + +#include + +using namespace AppInstaller::Settings; +using namespace std::chrono_literals; +using namespace AppInstaller::Utility::literals; + +namespace AppInstaller::Repository +{ + namespace + { +#ifndef AICLI_DISABLE_TEST_HOOKS + static std::map()>> s_Sources_TestHook_SourceFactories; +#endif + + std::shared_ptr CreateSourceFromDetails(const SourceDetails& details) + { + return ISourceFactory::GetForType(details.Type)->Create(details); + } + + std::chrono::milliseconds GetMillisecondsToWait(std::chrono::seconds retryAfter, size_t randomMultiplier = 1) + { + if (retryAfter != 0s) + { + return std::chrono::duration_cast(retryAfter); + } + else + { + // Add a bit of randomness to the retry wait time + std::default_random_engine randomEngine(std::random_device{}()); + std::uniform_int_distribution distribution(2000, 10000); + + return std::chrono::milliseconds(distribution(randomEngine) * randomMultiplier); + } + } + + struct AddOrUpdateResult + { + bool UpdateChecked = false; + bool MetadataWritten = false; + }; + + template + AddOrUpdateResult AddOrUpdateFromDetails(SourceDetails& details, MemberFunc member, IProgressCallback& progress) + { + AddOrUpdateResult result; + + auto factory = ISourceFactory::GetForType(details.Type); + + // If we are instructed to wait longer than this, just fail rather than retrying. + constexpr std::chrono::seconds maximumWaitTimeAllowed = 60s; + std::chrono::seconds waitSecondsForRetry = 0s; + + // Attempt; if it fails, wait a short time and retry. + try + { + result.UpdateChecked = (factory.get()->*member)(details, progress); + if (result.UpdateChecked) + { + details.LastUpdateTime = std::chrono::system_clock::now(); + result.MetadataWritten = true; + } + return result; + } + catch (const Utility::ServiceUnavailableException& sue) + { + waitSecondsForRetry = sue.RetryAfter(); + + // Do not retry if the server tell us to wait more than the max time allowed. + if (waitSecondsForRetry > maximumWaitTimeAllowed) + { + details.DoNotUpdateBefore = std::chrono::system_clock::now() + waitSecondsForRetry; + AICLI_LOG(Repo, Info, << "Source `" << details.Name << "` unavailable first try, setting DoNotUpdateBefore to " << details.DoNotUpdateBefore); + result.MetadataWritten = true; + return result; + } + } + CATCH_LOG(); + + std::chrono::milliseconds millisecondsToWait = GetMillisecondsToWait(waitSecondsForRetry); + + AICLI_LOG(Repo, Info, << "Source add/update failed, waiting " << millisecondsToWait.count() << " milliseconds and retrying: " << details.Name); + + if (!ProgressCallback::Wait(progress, millisecondsToWait)) + { + AICLI_LOG(Repo, Info, << "Source second try cancelled."); + return {}; + } + + try + { + // If this one fails, maybe the problem is persistent. + result.UpdateChecked = (factory.get()->*member)(details, progress); + if (result.UpdateChecked) + { + details.LastUpdateTime = std::chrono::system_clock::now(); + result.MetadataWritten = true; + } + } + catch (const Utility::ServiceUnavailableException& sue) + { + details.DoNotUpdateBefore = std::chrono::system_clock::now() + GetMillisecondsToWait(sue.RetryAfter(), 3); + AICLI_LOG(Repo, Info, << "Source `" << details.Name << "` unavailable second try, setting DoNotUpdateBefore to " << details.DoNotUpdateBefore); + result.MetadataWritten = true; + } + + return result; + } + + AddOrUpdateResult AddSourceFromDetails(SourceDetails& details, IProgressCallback& progress) + { + return AddOrUpdateFromDetails(details, &ISourceFactory::Add, progress); + } + + AddOrUpdateResult UpdateSourceFromDetails(SourceDetails& details, IProgressCallback& progress) + { + return AddOrUpdateFromDetails(details, &ISourceFactory::Update, progress); + } + + AddOrUpdateResult BackgroundUpdateSourceFromDetails(SourceDetails& details, IProgressCallback& progress) + { + return AddOrUpdateFromDetails(details, &ISourceFactory::BackgroundUpdate, progress); + } + + bool RemoveSourceFromDetails(const SourceDetails& details, IProgressCallback& progress) + { + auto factory = ISourceFactory::GetForType(details.Type); + + return factory->Remove(details, progress); + } + + bool ContainsAvailablePackagesInternal(SourceOrigin origin) + { + return (origin == SourceOrigin::Default || origin == SourceOrigin::GroupPolicy || origin == SourceOrigin::User); + } + + SourceDetails GetPredefinedSourceDetails(PredefinedSource source) + { + SourceDetails details; + details.Origin = SourceOrigin::Predefined; + + switch (source) + { + case PredefinedSource::Installed: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::None); + return details; + case PredefinedSource::InstalledForceCacheUpdate: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::NoneWithForcedCacheUpdate); + return details; + case PredefinedSource::InstalledUser: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::User); + return details; + case PredefinedSource::InstalledMachine: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::Machine); + return details; + case PredefinedSource::ARP: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::ARP); + return details; + case PredefinedSource::MSIX: + details.Type = Microsoft::PredefinedInstalledSourceFactory::Type(); + details.Arg = Microsoft::PredefinedInstalledSourceFactory::FilterToString(Microsoft::PredefinedInstalledSourceFactory::Filter::MSIX); + return details; + case PredefinedSource::Installing: + details.Type = Microsoft::PredefinedWriteableSourceFactory::Type(); + // As long as there is only one type this is not particularly needed, but Arg is exposed publicly + // so this is used here for consistency with other predefined sources. + details.Arg = Microsoft::PredefinedWriteableSourceFactory::TypeToString(Microsoft::PredefinedWriteableSourceFactory::WriteableType::Installing); + return details; + } + + THROW_HR(E_UNEXPECTED); + } + + // Carries the exception from an OpenSource call and presents it back at search time. + struct OpenExceptionProxy : public ISource, std::enable_shared_from_this + { + static constexpr ISourceType SourceType = ISourceType::OpenExceptionProxy; + + OpenExceptionProxy(const SourceDetails& details, std::exception_ptr exception) : + m_details(details), m_exception(std::move(exception)) {} + + const SourceDetails& GetDetails() const override { return m_details; } + + const std::string& GetIdentifier() const override { return m_details.Identifier; } + + SearchResult Search(const SearchRequest&) const override + { + SearchResult result; + result.Failures.emplace_back(SearchResult::Failure{ GetDetails().Name, m_exception }); + return result; + } + + void* CastTo(ISourceType type) override + { + if (type == SourceType) + { + return this; + } + + return nullptr; + } + + private: + SourceDetails m_details; + std::exception_ptr m_exception; + }; + + // A wrapper that doesn't actually forward the search requests. + struct TrackingOnlySourceWrapper : public ISource + { + TrackingOnlySourceWrapper(std::shared_ptr wrapped) : m_wrapped(std::move(wrapped)) + { + m_identifier = m_wrapped->GetIdentifier(); + } + + const std::string& GetIdentifier() const override { return m_identifier; } + + SourceDetails& GetDetails() const override { return m_wrapped->GetDetails(); } + + SourceInformation GetInformation() const override { return m_wrapped->GetInformation(); } + + SearchResult Search(const SearchRequest&) const override { return {}; } + + void* CastTo(ISourceType) override { return nullptr; } + + private: + std::shared_ptr m_wrapped; + std::string m_identifier; + }; + + // A wrapper to create another wrapper. + struct TrackingOnlyReferenceWrapper : public ISourceReference + { + TrackingOnlyReferenceWrapper(std::shared_ptr wrapped) : m_wrapped(std::move(wrapped)) {} + + std::string GetIdentifier() override { return m_wrapped->GetIdentifier(); } + + SourceDetails& GetDetails() override { return m_wrapped->GetDetails(); } + + SourceInformation GetInformation() override { return m_wrapped->GetInformation(); } + + bool SetCustomHeader(std::optional) override { return false; } + + void SetCaller(std::string caller) override { m_wrapped->SetCaller(std::move(caller)); } + + std::shared_ptr Open(IProgressCallback&) override + { + return std::make_shared(m_wrapped); + } + + private: + std::shared_ptr m_wrapped; + }; + } + + std::unique_ptr ISourceFactory::GetForType(std::string_view type) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + // Tests can ensure case matching + auto itr = s_Sources_TestHook_SourceFactories.find(std::string(type)); + if (itr != s_Sources_TestHook_SourceFactories.end()) + { + return itr->second(); + } + + if (Utility::CaseInsensitiveEquals(Microsoft::ConfigurableTestSourceFactory::Type(), type)) + { + return Microsoft::ConfigurableTestSourceFactory::Create(); + } +#endif + + // For now, enable an empty type to represent the only one we have. + if (type.empty() || + Utility::CaseInsensitiveEquals(Microsoft::PreIndexedPackageSourceFactory::Type(), type)) + { + return Microsoft::PreIndexedPackageSourceFactory::Create(); + } + // Should always come from code, so no need for case insensitivity + else if (Microsoft::PredefinedInstalledSourceFactory::Type() == type) + { + return Microsoft::PredefinedInstalledSourceFactory::Create(); + } + // Should always come from code, so no need for case insensitivity + else if (Microsoft::PredefinedWriteableSourceFactory::Type() == type) + { + return Microsoft::PredefinedWriteableSourceFactory::Create(); + } + // Should always come from code, so no need for case insensitivity + else if (PackageTrackingCatalogSourceFactory::Type() == type) + { + return PackageTrackingCatalogSourceFactory::Create(); + } + else if (Utility::CaseInsensitiveEquals(Rest::RestSourceFactory::Type(), type)) + { + return Rest::RestSourceFactory::Create(); + } + + THROW_HR(APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE); + } + + SourceTrustLevel ConvertToSourceTrustLevelEnum(std::string_view trustLevel) + { + std::string lowerTrustLevel = Utility::ToLower(trustLevel); + + if (lowerTrustLevel == "storeorigin") + { + return SourceTrustLevel::StoreOrigin; + } + else if (lowerTrustLevel == "trusted") + { + return SourceTrustLevel::Trusted; + } + else if (lowerTrustLevel == "none") + { + return SourceTrustLevel::None; + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED)); + } + } + + std::string_view SourceTrustLevelEnumToString(SourceTrustLevel trustLevel) + { + switch (trustLevel) + { + case SourceTrustLevel::StoreOrigin: + return "StoreOrigin"sv; + case SourceTrustLevel::Trusted: + return "Trusted"sv; + case SourceTrustLevel::None: + return "None"sv; + } + + return "Unknown"sv; + } + + SourceTrustLevel ConvertToSourceTrustLevelFlag(std::vector trustLevels) + { + Repository::SourceTrustLevel result = Repository::SourceTrustLevel::None; + for (auto& trustLevel : trustLevels) + { + Repository::SourceTrustLevel trustLevelEnum = ConvertToSourceTrustLevelEnum(trustLevel); + if (trustLevelEnum == Repository::SourceTrustLevel::None) + { + return Repository::SourceTrustLevel::None; + } + else if (trustLevelEnum == Repository::SourceTrustLevel::Trusted) + { + WI_SetFlag(result, Repository::SourceTrustLevel::Trusted); + } + else if (trustLevelEnum == Repository::SourceTrustLevel::StoreOrigin) + { + WI_SetFlag(result, Repository::SourceTrustLevel::StoreOrigin); + } + else + { + THROW_HR_MSG(HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED), "Invalid source trust level."); + } + } + + return result; + } + + std::vector SourceTrustLevelFlagToList(SourceTrustLevel trustLevel) + { + std::vector result; + + if (WI_IsFlagSet(trustLevel, Repository::SourceTrustLevel::Trusted)) + { + result.emplace_back(Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::Trusted)); + } + if (WI_IsFlagSet(trustLevel, Repository::SourceTrustLevel::StoreOrigin)) + { + result.emplace_back(Repository::SourceTrustLevelEnumToString(Repository::SourceTrustLevel::StoreOrigin)); + } + + return result; + } + + std::string GetSourceTrustLevelForDisplay(SourceTrustLevel trustLevel) + { + std::vector trustLevelList = Repository::SourceTrustLevelFlagToList(trustLevel); + std::vector locIndList(trustLevelList.begin(), trustLevelList.end()); + return Utility::Join("|"_liv, locIndList); + } + + std::string_view ToString(SourceOrigin origin) + { + switch (origin) + { + case SourceOrigin::Default: + return "Default"sv; + case SourceOrigin::User: + return "User"sv; + case SourceOrigin::Predefined: + return "Predefined"sv; + case SourceOrigin::GroupPolicy: + return "GroupPolicy"sv; + case SourceOrigin::Metadata: + return "Metadata"sv; + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::optional CheckForWellKnownSource(const SourceDetails& sourceDetails) + { + return CheckForWellKnownSourceMatch(sourceDetails.Name, sourceDetails.Arg, sourceDetails.Type); + } + + Source::Source() {} + + Source::Source(std::string_view name) + { + InitializeSourceReference(name); + } + + Source::Source(PredefinedSource source) + { + SourceDetails details = GetPredefinedSourceDetails(source); + m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); + } + + Source::Source(WellKnownSource source) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !IsWellKnownSourceEnabled(source)); + + auto details = GetWellKnownSourceDetailsInternal(source); + + // Populate metadata + SourceList sourceList; + auto sourceDetailsWithMetadata = sourceList.GetSource(details.Name); + if (sourceDetailsWithMetadata) + { + sourceDetailsWithMetadata->CopyMetadataFieldsTo(details); + } + + m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); + } + + Source::Source(std::string_view name, std::string_view arg, std::string_view type, SourceTrustLevel trustLevel, const SourceEdit& additionalProperties) + { + m_isSourceToBeAdded = true; + SourceDetails details; + + std::optional wellKnownSourceCheck = CheckForWellKnownSourceMatch(name, arg, type); + + if (wellKnownSourceCheck) + { + details = GetWellKnownSourceDetailsInternal(wellKnownSourceCheck.value()); + } + else + { + details.Name = name; + details.Arg = arg; + details.Type = type; + details.TrustLevel = trustLevel; + if (additionalProperties.Explicit) + { + details.Explicit = additionalProperties.Explicit.value(); + } + if (additionalProperties.Priority) + { + details.Priority = additionalProperties.Priority.value(); + } + } + + m_sourceReferences.emplace_back(CreateSourceFromDetails(details)); + } + + Source::Source(const std::vector& availableSources) + { + std::shared_ptr compositeSource = std::make_shared("*CompositeSource"); + + for (const auto& availableSource : availableSources) + { + THROW_HR_IF(E_INVALIDARG, !availableSource.m_source || availableSource.IsComposite()); + compositeSource->AddAvailableSource(availableSource.m_source); + } + + m_source = compositeSource; + m_isComposite = true; + } + + Source::Source(const Source& installedSource, const Source& availableSource, CompositeSearchBehavior searchBehavior) + { + THROW_HR_IF(E_INVALIDARG, !installedSource.m_source || installedSource.m_isComposite || !availableSource.m_source); + + std::shared_ptr compositeSource = SourceCast(availableSource.m_source); + + if (!compositeSource) + { + compositeSource = std::make_shared("*CompositeSource"); + compositeSource->AddAvailableSource(availableSource.m_source); + } + + compositeSource->SetInstalledSource(installedSource, searchBehavior); + + m_source = compositeSource; + m_isComposite = true; + } + + Source::Source(std::shared_ptr source) : m_source(std::move(source)) {} + + Source::operator bool() const + { + return !m_sourceReferences.empty() || m_source != nullptr; + } + + void Source::InitializeSourceReference(std::string_view name) + { + SourceList sourceList; + + if (name.empty()) + { + auto currentSources = sourceList.GetCurrentSourceRefs(); + if (currentSources.empty()) + { + AICLI_LOG(Repo, Info, << "Default source requested, but no sources configured"); + } + else if (currentSources.size() == 1) + { + if (!currentSources[0].get().Explicit) + { + AICLI_LOG(Repo, Info, << "Default source requested, only 1 source available, using the only source: " << currentSources[0].get().Name); + InitializeSourceReference(currentSources[0].get().Name); + } + else + { + AICLI_LOG(Repo, Info, << "Skipping explicit source reference " << currentSources[0].get().Name); + } + } + else + { + AICLI_LOG(Repo, Info, << "Default source requested, multiple sources available, adding all to source references."); + + for (auto& source : currentSources) + { + if (!source.get().Explicit) + { + AICLI_LOG(Repo, Info, << "Adding to source references " << source.get().Name); + m_sourceReferences.emplace_back(CreateSourceFromDetails(source)); + } + else + { + AICLI_LOG(Repo, Info, << "Skipping explicit source reference " << source.get().Name); + } + } + + if (m_sourceReferences.size() > 1) + { + m_isComposite = true; + } + } + } + else + { + auto source = sourceList.GetCurrentSource(name); + if (!source) + { + AICLI_LOG(Repo, Info, << "Named source requested, but not found: " << name); + } + else + { + AICLI_LOG(Repo, Info, << "Named source requested, found: " << source->Name); + m_sourceReferences.emplace_back(CreateSourceFromDetails(*source)); + } + } + } + + bool Source::operator==(const Source& other) const + { + SourceDetails thisDetails = GetDetails(); + SourceDetails otherDetails = other.GetDetails(); + + return (thisDetails.Type == otherDetails.Type && thisDetails.Identifier == otherDetails.Identifier); + } + + bool Source::operator!=(const Source& other) const + { + return !operator==(other); + } + + std::string Source::GetIdentifier() const + { + if (m_source) + { + return m_source->GetIdentifier(); + } + else if (m_sourceReferences.size() == 1) + { + return m_sourceReferences[0]->GetIdentifier(); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + } + + const SourceDetails& Source::GetDetails() const + { + if (m_source) + { + return m_source->GetDetails(); + } + else if (m_sourceReferences.size() == 1) + { + return m_sourceReferences[0]->GetDetails(); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + } + + SourceInformation Source::GetInformation() const + { + if (m_source && !m_isComposite) + { + return m_source->GetInformation(); + } + else if (m_sourceReferences.size() == 1) + { + return m_sourceReferences[0]->GetInformation(); + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + } + + bool Source::QueryFeatureFlag(SourceFeatureFlag flag) const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); + return m_source->QueryFeatureFlag(flag); + } + + bool Source::ContainsAvailablePackages() const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), IsComposite()); + return ContainsAvailablePackagesInternal(GetDetails().Origin); + } + + bool Source::SetCustomHeader(std::optional header) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_sourceReferences.size() != 1); + return m_sourceReferences[0]->SetCustomHeader(header); + } + + void Source::SetCaller(std::string caller) + { + for (auto& sourceReference : m_sourceReferences) + { + sourceReference->SetCaller(caller); + } + } + + void Source::SetAuthenticationArguments(Authentication::AuthenticationArguments args) + { + for (auto& sourceReference : m_sourceReferences) + { + sourceReference->SetAuthenticationArguments(args); + } + } + + void Source::SetServerCertificateValidationCallback(std::function callback) + { + for (auto& sourceReference : m_sourceReferences) + { + sourceReference->SetServerCertificateValidationCallback(callback); + } + } + + void Source::SetThreadGlobals(const std::shared_ptr& threadGlobals) + { + for (auto& sourceReference : m_sourceReferences) + { + sourceReference->SetThreadGlobals(threadGlobals); + } + } + + void Source::SetBackgroundUpdateInterval(TimeSpan interval) + { + m_backgroundUpdateInterval = interval; + } + + void Source::InstalledPackageInformationOnly(bool value) + { + m_installedPackageInformationOnly = value; + } + + bool Source::IsWellKnownSource(WellKnownSource wellKnownSource) const + { + SourceDetails details = GetDetails(); + auto wellKnown = CheckForWellKnownSourceMatch(details.Name, details.Arg, details.Type); + return wellKnown && wellKnown.value() == wellKnownSource; + } + + SearchResult Source::Search(const SearchRequest& request) const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); + return m_source->Search(request); + } + + ImplicitAgreementFieldEnum Source::GetAgreementFieldsFromSourceInformation() const + { + ImplicitAgreementFieldEnum result = ImplicitAgreementFieldEnum::None; + + auto info = GetInformation(); + if (info.RequiredPackageMatchFields.end() != std::find_if(info.RequiredPackageMatchFields.begin(), info.RequiredPackageMatchFields.end(), [&](const auto& field) { return Utility::CaseInsensitiveEquals(field, "market"); }) || + info.RequiredQueryParameters.end() != std::find_if(info.RequiredQueryParameters.begin(), info.RequiredQueryParameters.end(), [&](const auto& param) { return Utility::CaseInsensitiveEquals(param, "market"); })) + { + WI_SetFlag(result, ImplicitAgreementFieldEnum::Market); + } + + return result; + } + + bool Source::CheckSourceAgreements() const + { + auto sourceName = GetDetails().Name; + auto agreementFields = GetAgreementFieldsFromSourceInformation(); + auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier; + + SourceList sourceList; + return sourceList.CheckSourceAgreements(sourceName, agreementsIdentifier, agreementFields); + } + + void Source::SaveAcceptedSourceAgreements() const + { + auto sourceName = GetDetails().Name; + auto agreementFields = GetAgreementFieldsFromSourceInformation(); + auto agreementsIdentifier = GetInformation().SourceAgreementsIdentifier; + + SourceList sourceList; + return sourceList.SaveAcceptedSourceAgreements(sourceName, agreementsIdentifier, agreementFields); + } + + bool Source::IsComposite() const + { + return m_isComposite; + } + + std::vector Source::GetAvailableSources() const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source || !m_isComposite); + + auto compositeSource = SourceCast(m_source); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !compositeSource); + + return compositeSource->GetAvailableSources(); + } + + void Source::AddPackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); + auto writableSource = SourceCast(m_source); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource); + writableSource->AddPackageVersion(manifest, relativePath); + } + + void Source::RemovePackageVersion(const Manifest::Manifest& manifest, const std::filesystem::path& relativePath) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_source); + auto writableSource = SourceCast(m_source); + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !writableSource); + writableSource->RemovePackageVersion(manifest, relativePath); + } + + std::vector Source::Open(IProgressCallback& progress) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.empty()); + + std::vector result; + + if (!m_source) + { + std::vector>* sourceReferencesToOpen = nullptr; + std::vector> sourceReferencesForTrackingOnly; + std::unique_ptr sourceList; + + if (m_installedPackageInformationOnly) + { + sourceReferencesToOpen = &sourceReferencesForTrackingOnly; + + // Create a wrapper for each reference + for (auto& sourceReference : m_sourceReferences) + { + sourceReferencesForTrackingOnly.emplace_back(std::make_shared(sourceReference)); + } + } + else + { + // Check for updates before opening. + for (auto& sourceReference : m_sourceReferences) + { + if (ShouldUpdateBeforeOpen(sourceReference.get(), m_backgroundUpdateInterval)) + { + auto& details = sourceReference->GetDetails(); + + try + { + // TODO: Consider adding a context callback to indicate we are doing the same action + // to avoid the progress bar fill up multiple times. + AddOrUpdateResult updateResult = BackgroundUpdateSourceFromDetails(details, progress); + + if (updateResult.MetadataWritten) + { + if (sourceList == nullptr) + { + sourceList = std::make_unique(); + } + + auto detailsInternal = sourceList->GetSource(details.Name); + detailsInternal->CopyMetadataFieldsFrom(details); + sourceList->SaveMetadata(*detailsInternal); + } + + if (!updateResult.UpdateChecked) + { + AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); + result.emplace_back(details); + } + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Warning, << "Failed to update source: " << details.Name); + result.emplace_back(details); + } + } + } + + sourceReferencesToOpen = &m_sourceReferences; + } + + if (sourceReferencesToOpen->size() > 1) + { + AICLI_LOG(Repo, Info, << "Multiple sources available, creating aggregated source."); + auto aggregatedSource = std::make_shared("*DefaultSource"); + std::vector> openExceptionProxies; + + for (auto& sourceReference : *sourceReferencesToOpen) + { + AICLI_LOG(Repo, Info, << "Adding to aggregated source: " << sourceReference->GetDetails().Name); + + try + + { + aggregatedSource->AddAvailableSource(sourceReference->Open(progress)); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Warning, << "Failed to open available source: " << sourceReference->GetDetails().Name); + openExceptionProxies.emplace_back(std::make_shared(sourceReference->GetDetails(), std::current_exception())); + } + } + + // If all sources failed to open, then throw an exception that is specific to this case. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, !aggregatedSource->HasAvailableSource()); + + // Place all of the proxies into the source to be searched later + for (auto& proxy : openExceptionProxies) + { + aggregatedSource->AddAvailableSource(Source{ std::move(proxy) }); + } + + m_source = aggregatedSource; + } + else + { + m_source = (*sourceReferencesToOpen)[0]->Open(progress); + } + } + + return result; + } + + bool Source::Add(IProgressCallback& progress) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !m_isSourceToBeAdded || m_sourceReferences.size() != 1); + + auto& sourceDetails = m_sourceReferences[0]->GetDetails(); + + // If the source type is empty, use a default. + // AddSourceForDetails will also check for empty, but we need the actual type before that for validation. + if (sourceDetails.Type.empty()) + { + sourceDetails.Type = GetDefaultSourceType(); + } + + AICLI_LOG(Repo, Info, << "Adding source: Name[" << sourceDetails.Name << "], Type[" << sourceDetails.Type << "], Arg[" << sourceDetails.Arg << "]"); + + // Check all sources for the given name. + SourceList sourceList; + + auto source = sourceList.GetSource(sourceDetails.Name); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, source != nullptr && source->Origin != SourceOrigin::Metadata && !source->IsTombstone); + + // Check sources allowed by group policy + auto blockingPolicy = GetPolicyBlockingUserSource(sourceDetails.Name, sourceDetails.Type, sourceDetails.Arg, false); + if (blockingPolicy != TogglePolicy::Policy::None) + { + throw GroupPolicyException(blockingPolicy); + } + + sourceDetails.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(0); + + // Allow the origin to stay as Default if the incoming details match a well known value + if (!(sourceDetails.Origin == SourceOrigin::Default && CheckForWellKnownSourceMatch(sourceDetails.Name, sourceDetails.Arg, sourceDetails.Type))) + { + sourceDetails.Origin = SourceOrigin::User; + } + + bool result = AddSourceFromDetails(sourceDetails, progress).UpdateChecked; + if (result) + { + sourceList.AddSource(sourceDetails); + SaveAcceptedSourceAgreements(); + m_isSourceToBeAdded = false; + AICLI_LOG(Repo, Info, << "Source created with extra data: " << sourceDetails.Data); + } + + return result; + } + + std::vector Source::Update(IProgressCallback& progress) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_source || m_sourceReferences.empty()); + + SourceList sourceList; + std::vector result; + + for (auto& sourceReference : m_sourceReferences) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !ContainsAvailablePackagesInternal(sourceReference->GetDetails().Origin)); + + auto& details = sourceReference->GetDetails(); + AICLI_LOG(Repo, Info, << "Named source to be updated, found: " << details.Name); + + try + { + // TODO: Consider adding a context callback to indicate we are doing the same action + // to avoid the progress bar fill up multiple times. + AddOrUpdateResult updateResult = UpdateSourceFromDetails(details, progress); + + if (updateResult.MetadataWritten) + { + auto detailsInternal = sourceList.GetSource(details.Name); + detailsInternal->CopyMetadataFieldsFrom(details); + sourceList.SaveMetadata(*detailsInternal); + } + + if (!updateResult.UpdateChecked) + { + AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); + result.emplace_back(details); + } + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + AICLI_LOG(Repo, Error, << "Failed to update source: " << details.Name); + result.emplace_back(details); + } + } + + return result; + } + + bool Source::Remove(IProgressCallback& progress) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.size() != 1 || m_source); + + const auto& details = m_sourceReferences[0]->GetDetails(); + AICLI_LOG(Repo, Info, << "Named source to be removed, found: " << details.Name << " [" << ToString(details.Origin) << ']'); + + EnsureSourceIsRemovable(details); + + bool result = RemoveSourceFromDetails(details, progress); + if (result) + { + SourceList sourceList; + sourceList.RemoveSource(details); + } + + return result; + } + + void Source::Edit(const SourceEdit& edits) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_isSourceToBeAdded || m_sourceReferences.size() != 1 || m_source); + + auto& details = m_sourceReferences[0]->GetDetails(); + AICLI_LOG(Repo, Info, << "Named source to be edited, found: " << details.Name << " [" << ToString(details.Origin) << ']'); + + // This is intentionally the same policy checks as Remove. If the source cannot be removed then it cannot be edited. + EnsureSourceIsRemovable(details); + + if (RequiresChanges(edits)) + { + if (edits.Explicit.has_value()) + { + details.Explicit = edits.Explicit.value(); + } + + if (edits.Priority.has_value()) + { + details.Priority = edits.Priority.value(); + } + + // Apply the edits and update source list. + SourceList sourceList; + sourceList.EditSource(details); + } + } + + bool Source::RequiresChanges(const SourceEdit& edits) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_sourceReferences.size() != 1); + + const auto& details = m_sourceReferences[0]->GetDetails(); + + bool isChanged = false; + + if (edits.Explicit.has_value() && edits.Explicit.value() != details.Explicit) + { + isChanged = true; + } + + if (edits.Priority.has_value() && edits.Priority.value() != details.Priority) + { + isChanged = true; + } + + return isChanged; + } + + PackageTrackingCatalog Source::GetTrackingCatalog() const + { + // With C++20, consider removing the shared_ptr here and making the one inside PackageTrackingCatalog atomic. + std::shared_ptr currentTrackingCatalog = std::atomic_load(&m_trackingCatalog); + if (!currentTrackingCatalog) + { + std::shared_ptr newTrackingCatalog = std::make_shared(PackageTrackingCatalog::CreateForSource(*this)); + + if (std::atomic_compare_exchange_strong(&m_trackingCatalog, ¤tTrackingCatalog, newTrackingCatalog)) + { + currentTrackingCatalog = newTrackingCatalog; + } + } + + return *currentTrackingCatalog; + } + + std::vector Source::GetCurrentSources() + { + SourceList sourceList; + + std::vector result; + for (auto&& source : sourceList.GetCurrentSourceRefs()) + { + result.emplace_back(std::move(source)); + } + + return result; + } + + bool Source::DropSource(std::string_view name) + { + if (name.empty()) + { + SourceList::RemoveSettingsStreams(); + return true; + } + else + { + SourceList sourceList; + + auto source = sourceList.GetSource(name); + if (!source) + { + AICLI_LOG(Repo, Info, << "Named source to be dropped, but not found: " << name); + return false; + } + else + { + AICLI_LOG(Repo, Info, << "Named source to be dropped, found: " << source->Name); + + EnsureSourceIsRemovable(*source); + + // For default sources, overrides of default sources, and tombstones masking + // default sources, reset to clean state instead of tombstoning/removing. + if (source->Origin == SourceOrigin::Default || + (source->Origin == SourceOrigin::User && (source->IsOverride || source->IsTombstone))) + { + sourceList.ResetSource(*source); + } + else + { + sourceList.RemoveSource(*source); + } + + return true; + } + } + } + + std::string_view Source::GetDefaultSourceType() + { + return ISourceFactory::GetForType("")->TypeName(); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + void TestHook_SetSourceFactoryOverride(const std::string& type, std::function()>&& factory) + { + s_Sources_TestHook_SourceFactories[type] = std::move(factory); + } + + void TestHook_ClearSourceFactoryOverrides() + { + s_Sources_TestHook_SourceFactories.clear(); + } +#endif +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp index 1202e91c33..26fd141d73 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.cpp @@ -1,248 +1,248 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RestClient.h" -#include "RestInformationCache.h" -#include "Rest/Schema/1_0/Interface.h" -#include "Rest/Schema/1_1/Interface.h" -#include "Rest/Schema/1_4/Interface.h" -#include "Rest/Schema/1_5/Interface.h" -#include "Rest/Schema/1_6/Interface.h" -#include "Rest/Schema/1_7/Interface.h" -#include "Rest/Schema/1_9/Interface.h" -#include "Rest/Schema/1_10/Interface.h" -#include "Rest/Schema/1_12/Interface.h" -#include "Rest/Schema/1_28/Interface.h" -#include "Rest/Schema/InformationResponseDeserializer.h" -#include "Rest/Schema/CommonRestConstants.h" -#include -#include -#include -#include - -using namespace AppInstaller::Repository::Rest::Schema; -using namespace AppInstaller::Repository::Rest::Schema::V1_0; -using namespace AppInstaller::Utility; -using namespace AppInstaller::Http; - -namespace AppInstaller::Repository::Rest -{ - // Supported versions - std::set WingetSupportedContracts = { - Version_1_0_0, - Version_1_1_0, - Version_1_4_0, - Version_1_5_0, - Version_1_6_0, - Version_1_7_0, - Version_1_9_0, - Version_1_10_0, - Version_1_12_0, - Version_1_28_0, - }; - - constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv; - constexpr size_t WindowsPackageManagerHeaderMaxLength = 1024; - - namespace - { - HttpClientHelper::HttpRequestHeaders GetHeaders(const std::optional& customHeader, std::string_view caller) - { - HttpClientHelper::HttpRequestHeaders headers; - - if (customHeader) - { - AICLI_LOG(Repo, Verbose, << "Custom header found: " << customHeader.value()); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH, customHeader.value().size() > WindowsPackageManagerHeaderMaxLength); - headers.emplace(JSON::GetUtilityString(WindowsPackageManagerHeader), JSON::GetUtilityString(customHeader.value())); - } - - if (!caller.empty()) - { - AICLI_LOG(Repo, Verbose, << "User agent caller found: " << caller); - std::wstring userAgentWide = JSON::GetUtilityString(Runtime::GetUserAgent(caller)); - try - { - // Replace user profile if the caller binary is under user profile. - userAgentWide = Utility::ReplaceWhileCopying(userAgentWide, Runtime::GetPathTo(Runtime::PathName::UserProfile).wstring(), L"%USERPROFILE%"); - } - CATCH_LOG(); - headers.emplace(web::http::header_names::user_agent, userAgentWide); - } - - return headers; - } - } - - RestClient::RestClient(std::unique_ptr supportedInterface, std::string sourceIdentifier) - : m_interface(std::move(supportedInterface)), m_sourceIdentifier(std::move(sourceIdentifier)) - { - } - - std::optional RestClient::GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const - { - return m_interface->GetManifestByVersion(packageId, version, channel); - } - - IRestClient::SearchResult RestClient::Search(const SearchRequest& request) const - { - return m_interface->Search(request); - } - - std::string RestClient::GetSourceIdentifier() const - { - return m_sourceIdentifier; - } - - IRestClient::Information RestClient::GetSourceInformation() const - { - return m_interface->GetSourceInformation(); - } - - std::optional RestClient::GetLatestCommonVersion( - const std::vector& serverSupportedVersions, - const std::set& wingetSupportedVersions) - { - std::set commonVersions; - for (auto& version : serverSupportedVersions) - { - Version versionInfo(version); - auto itr = std::find_if(wingetSupportedVersions.begin(), wingetSupportedVersions.end(), - [&](const Version& v) - { - // Only check major and minor version match if applicable - if (v.GetParts().size() >= 2) - { - return versionInfo.GetParts().size() >= 2 && - versionInfo.GetParts().at(0) == v.GetParts().at(0) && - versionInfo.GetParts().at(1) == v.GetParts().at(1); - } - else - { - return versionInfo == v; - } - }); - if (itr != wingetSupportedVersions.end()) - { - commonVersions.insert(*itr); - } - } - - if (commonVersions.empty()) - { - return {}; - } - - return *commonVersions.rbegin(); - } - - 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)); - utility::string_t endpoint = AppInstaller::Rest::AppendPathToUri(restEndpoint, JSON::GetUtilityString(InformationGetEndpoint)); - - // Check the cache for a valid information entry - RestInformationCache informationCache; - std::optional result = informationCache.Get(endpoint, customHeader, caller); - - if (!result) - { - // Not in cache, make REST call to retrieve it - auto headers = GetHeaders(customHeader, caller); - CacheControlPolicy cacheControl; - - std::optional response = helper.HandleGet( - endpoint, - headers, - {}, - [&](const web::http::http_response& httpResponse) - { - cacheControl = CacheControlPolicy{ httpResponse.headers().cache_control() }; - return Http::HttpClientHelper::HttpResponseHandlerResult{ std::nullopt, true }; - }); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !response); - - InformationResponseDeserializer responseDeserializer; - result = responseDeserializer.Deserialize(response.value()); - - // Cache the information value as requested - informationCache.Cache(endpoint, customHeader, caller, cacheControl, std::move(response).value()); - } - - return std::move(result).value(); - } - - std::unique_ptr RestClient::GetSupportedInterface( - const std::string& api, - const HttpClientHelper::HttpRequestHeaders& additionalHeaders, - const IRestClient::Information& information, - const Authentication::AuthenticationArguments& authArgs, - const Version& version, - const HttpClientHelper& helper) - { - if (version == Version_1_0_0) - { - return std::make_unique(api, helper); - } - else if (version == Version_1_1_0) - { - return std::make_unique(api, helper, information, additionalHeaders); - } - else if (version == Version_1_4_0) - { - return std::make_unique(api, helper, information, additionalHeaders); - } - else if (version == Version_1_5_0) - { - return std::make_unique(api, helper, information, additionalHeaders); - } - else if (version == Version_1_6_0) - { - return std::make_unique(api, helper, information, additionalHeaders); - } - else if (version == Version_1_7_0) - { - return std::make_unique(api, helper, information, additionalHeaders, authArgs); - } - else if (version == Version_1_9_0) - { - return std::make_unique(api, helper, information, additionalHeaders, authArgs); - } - else if (version == Version_1_10_0) - { - return std::make_unique(api, helper, information, additionalHeaders, authArgs); - } - else if (version == Version_1_12_0) - { - return std::make_unique(api, helper, information, additionalHeaders, authArgs); - } - else if (version == Version_1_28_0) - { - return std::make_unique(api, helper, information, additionalHeaders, authArgs); - } - - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); - } - - 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); - - std::optional latestCommonVersion = GetLatestCommonVersion(information.ServerSupportedVersions, WingetSupportedContracts); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !latestCommonVersion); - - std::unique_ptr supportedInterface = GetSupportedInterface(utility::conversions::to_utf8string(restEndpoint), headers, information, authArgs, latestCommonVersion.value(), helper); - return RestClient{ std::move(supportedInterface), information.SourceIdentifier }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RestClient.h" +#include "RestInformationCache.h" +#include "Rest/Schema/1_0/Interface.h" +#include "Rest/Schema/1_1/Interface.h" +#include "Rest/Schema/1_4/Interface.h" +#include "Rest/Schema/1_5/Interface.h" +#include "Rest/Schema/1_6/Interface.h" +#include "Rest/Schema/1_7/Interface.h" +#include "Rest/Schema/1_9/Interface.h" +#include "Rest/Schema/1_10/Interface.h" +#include "Rest/Schema/1_12/Interface.h" +#include "Rest/Schema/1_28/Interface.h" +#include "Rest/Schema/InformationResponseDeserializer.h" +#include "Rest/Schema/CommonRestConstants.h" +#include +#include +#include +#include + +using namespace AppInstaller::Repository::Rest::Schema; +using namespace AppInstaller::Repository::Rest::Schema::V1_0; +using namespace AppInstaller::Utility; +using namespace AppInstaller::Http; + +namespace AppInstaller::Repository::Rest +{ + // Supported versions + std::set WingetSupportedContracts = { + Version_1_0_0, + Version_1_1_0, + Version_1_4_0, + Version_1_5_0, + Version_1_6_0, + Version_1_7_0, + Version_1_9_0, + Version_1_10_0, + Version_1_12_0, + Version_1_28_0, + }; + + constexpr std::string_view WindowsPackageManagerHeader = "Windows-Package-Manager"sv; + constexpr size_t WindowsPackageManagerHeaderMaxLength = 1024; + + namespace + { + HttpClientHelper::HttpRequestHeaders GetHeaders(const std::optional& customHeader, std::string_view caller) + { + HttpClientHelper::HttpRequestHeaders headers; + + if (customHeader) + { + AICLI_LOG(Repo, Verbose, << "Custom header found: " << customHeader.value()); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH, customHeader.value().size() > WindowsPackageManagerHeaderMaxLength); + headers.emplace(JSON::GetUtilityString(WindowsPackageManagerHeader), JSON::GetUtilityString(customHeader.value())); + } + + if (!caller.empty()) + { + AICLI_LOG(Repo, Verbose, << "User agent caller found: " << caller); + std::wstring userAgentWide = JSON::GetUtilityString(Runtime::GetUserAgent(caller)); + try + { + // Replace user profile if the caller binary is under user profile. + userAgentWide = Utility::ReplaceWhileCopying(userAgentWide, Runtime::GetPathTo(Runtime::PathName::UserProfile).wstring(), L"%USERPROFILE%"); + } + CATCH_LOG(); + headers.emplace(web::http::header_names::user_agent, userAgentWide); + } + + return headers; + } + } + + RestClient::RestClient(std::unique_ptr supportedInterface, std::string sourceIdentifier) + : m_interface(std::move(supportedInterface)), m_sourceIdentifier(std::move(sourceIdentifier)) + { + } + + std::optional RestClient::GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const + { + return m_interface->GetManifestByVersion(packageId, version, channel); + } + + IRestClient::SearchResult RestClient::Search(const SearchRequest& request) const + { + return m_interface->Search(request); + } + + std::string RestClient::GetSourceIdentifier() const + { + return m_sourceIdentifier; + } + + IRestClient::Information RestClient::GetSourceInformation() const + { + return m_interface->GetSourceInformation(); + } + + std::optional RestClient::GetLatestCommonVersion( + const std::vector& serverSupportedVersions, + const std::set& wingetSupportedVersions) + { + std::set commonVersions; + for (auto& version : serverSupportedVersions) + { + Version versionInfo(version); + auto itr = std::find_if(wingetSupportedVersions.begin(), wingetSupportedVersions.end(), + [&](const Version& v) + { + // Only check major and minor version match if applicable + if (v.GetParts().size() >= 2) + { + return versionInfo.GetParts().size() >= 2 && + versionInfo.GetParts().at(0) == v.GetParts().at(0) && + versionInfo.GetParts().at(1) == v.GetParts().at(1); + } + else + { + return versionInfo == v; + } + }); + if (itr != wingetSupportedVersions.end()) + { + commonVersions.insert(*itr); + } + } + + if (commonVersions.empty()) + { + return {}; + } + + return *commonVersions.rbegin(); + } + + 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)); + utility::string_t endpoint = AppInstaller::Rest::AppendPathToUri(restEndpoint, JSON::GetUtilityString(InformationGetEndpoint)); + + // Check the cache for a valid information entry + RestInformationCache informationCache; + std::optional result = informationCache.Get(endpoint, customHeader, caller); + + if (!result) + { + // Not in cache, make REST call to retrieve it + auto headers = GetHeaders(customHeader, caller); + CacheControlPolicy cacheControl; + + std::optional response = helper.HandleGet( + endpoint, + headers, + {}, + [&](const web::http::http_response& httpResponse) + { + cacheControl = CacheControlPolicy{ httpResponse.headers().cache_control() }; + return Http::HttpClientHelper::HttpResponseHandlerResult{ std::nullopt, true }; + }); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !response); + + InformationResponseDeserializer responseDeserializer; + result = responseDeserializer.Deserialize(response.value()); + + // Cache the information value as requested + informationCache.Cache(endpoint, customHeader, caller, cacheControl, std::move(response).value()); + } + + return std::move(result).value(); + } + + std::unique_ptr RestClient::GetSupportedInterface( + const std::string& api, + const HttpClientHelper::HttpRequestHeaders& additionalHeaders, + const IRestClient::Information& information, + const Authentication::AuthenticationArguments& authArgs, + const Version& version, + const HttpClientHelper& helper) + { + if (version == Version_1_0_0) + { + return std::make_unique(api, helper); + } + else if (version == Version_1_1_0) + { + return std::make_unique(api, helper, information, additionalHeaders); + } + else if (version == Version_1_4_0) + { + return std::make_unique(api, helper, information, additionalHeaders); + } + else if (version == Version_1_5_0) + { + return std::make_unique(api, helper, information, additionalHeaders); + } + else if (version == Version_1_6_0) + { + return std::make_unique(api, helper, information, additionalHeaders); + } + else if (version == Version_1_7_0) + { + return std::make_unique(api, helper, information, additionalHeaders, authArgs); + } + else if (version == Version_1_9_0) + { + return std::make_unique(api, helper, information, additionalHeaders, authArgs); + } + else if (version == Version_1_10_0) + { + return std::make_unique(api, helper, information, additionalHeaders, authArgs); + } + else if (version == Version_1_12_0) + { + return std::make_unique(api, helper, information, additionalHeaders, authArgs); + } + else if (version == Version_1_28_0) + { + return std::make_unique(api, helper, information, additionalHeaders, authArgs); + } + + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION); + } + + 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); + + std::optional latestCommonVersion = GetLatestCommonVersion(information.ServerSupportedVersions, WingetSupportedContracts); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !latestCommonVersion); + + std::unique_ptr supportedInterface = GetSupportedInterface(utility::conversions::to_utf8string(restEndpoint), headers, information, authArgs, latestCommonVersion.value(), helper); + return RestClient{ std::move(supportedInterface), information.SourceIdentifier }; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestClient.h b/src/AppInstallerRepositoryCore/Rest/RestClient.h index fc02291eb0..9961e339f9 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestClient.h +++ b/src/AppInstallerRepositoryCore/Rest/RestClient.h @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Rest/Schema/IRestClient.h" -#include "ISource.h" -#include - -namespace AppInstaller::Repository::Rest -{ - struct RestClient - { - RestClient(const RestClient&) = delete; - RestClient& operator=(const RestClient&) = delete; - - RestClient(RestClient&&) = default; - RestClient& operator=(RestClient&&) = default; - - // Performs a search based on the given criteria. - Schema::IRestClient::SearchResult Search(const SearchRequest& request) const; - - std::optional GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const; - - std::string GetSourceIdentifier() const; - - Schema::IRestClient::Information GetSourceInformation() const; - - 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, const std::optional& customHeader, std::string_view caller, const Http::HttpClientHelper& helper); - - static std::unique_ptr GetSupportedInterface( - const std::string& restApi, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, - const Schema::IRestClient::Information& information, - const Authentication::AuthenticationArguments& authArgs, - const AppInstaller::Utility::Version& version, - const Http::HttpClientHelper& helper); - - // Creates the rest client. Full validation performed (just as opening the source) - 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); - - std::unique_ptr m_interface; - std::string m_sourceIdentifier; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Rest/Schema/IRestClient.h" +#include "ISource.h" +#include + +namespace AppInstaller::Repository::Rest +{ + struct RestClient + { + RestClient(const RestClient&) = delete; + RestClient& operator=(const RestClient&) = delete; + + RestClient(RestClient&&) = default; + RestClient& operator=(RestClient&&) = default; + + // Performs a search based on the given criteria. + Schema::IRestClient::SearchResult Search(const SearchRequest& request) const; + + std::optional GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const; + + std::string GetSourceIdentifier() const; + + Schema::IRestClient::Information GetSourceInformation() const; + + 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, const std::optional& customHeader, std::string_view caller, const Http::HttpClientHelper& helper); + + static std::unique_ptr GetSupportedInterface( + const std::string& restApi, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, + const Schema::IRestClient::Information& information, + const Authentication::AuthenticationArguments& authArgs, + const AppInstaller::Utility::Version& version, + const Http::HttpClientHelper& helper); + + // Creates the rest client. Full validation performed (just as opening the source) + 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); + + std::unique_ptr m_interface; + std::string m_sourceIdentifier; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestInformationCache.cpp b/src/AppInstallerRepositoryCore/Rest/RestInformationCache.cpp index 0a9568032f..5eb2e0e326 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestInformationCache.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestInformationCache.cpp @@ -1,249 +1,249 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RestInformationCache.h" -#include "Rest/Schema/InformationResponseDeserializer.h" -#include - -namespace AppInstaller::Repository::Rest -{ - namespace - { - constexpr std::wstring_view s_EndpointName = L"endpoint"sv; - constexpr std::wstring_view s_HashName = L"hash"sv; - constexpr std::wstring_view s_ExpirationName = L"expiration"sv; - constexpr std::wstring_view s_DataName = L"data"sv; - - // Calculates the hash of values that might change per-call. - Utility::SHA256::HashBuffer GetHash(const std::optional& customHeader, std::string_view caller) - { - std::stringstream stream; - if (customHeader) - { - stream << customHeader.value(); - } - stream << '|' << caller; - - return Utility::SHA256::ComputeHash(stream); - } - - uint64_t CalculateExpiration(std::chrono::seconds duration) - { - // If no expiration information is provided, use 1 minute - if (!duration.count()) - { - duration = 60s; - } - - return Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now() + duration); - } - } - - std::optional RestInformationCache::Get(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller) -#ifdef AICLI_DISABLE_TEST_HOOKS - try -#endif - { - LoadCacheView(); - - Utility::SHA256::HashBuffer hashValue = GetHash(customHeader, caller); - CacheItem* item = FindCacheItem(endpoint, hashValue); - - // If we don't find a private match, see if there is a public one. - if (!item) - { - item = FindCacheItem(endpoint, {}); - } - - if (!item) - { - return std::nullopt; - } - - Schema::InformationResponseDeserializer responseDeserializer; - return responseDeserializer.Deserialize(item->Data); - } -#ifdef AICLI_DISABLE_TEST_HOOKS - catch (...) - { - LOG_CAUGHT_EXCEPTION_MSG("RestInformationCache::Get exception"); - return std::nullopt; - } -#endif - - void RestInformationCache::Cache(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller, const Utility::CacheControlPolicy& cacheControl, web::json::value response) -#ifdef AICLI_DISABLE_TEST_HOOKS - try -#endif - { - // If requested, do not cache this response. - // Since this data is small, treat no-cache as no-store. - if (cacheControl.NoStore || cacheControl.NoCache) - { - return; - } - - // If not public, we use the header values to differentiate the cache items. - Utility::SHA256::HashBuffer hashValue; - if (!cacheControl.Public) - { - hashValue = GetHash(customHeader, caller); - } - - uint64_t expirationEpoch = CalculateExpiration(std::chrono::seconds{ cacheControl.MaxAge }); - - // Due to the exchange semantics on the setting stream, we may have to retry storing the value. - for (int i = 0; i < 10; ++i) - { - CacheItem* item = FindCacheItem(endpoint, hashValue); - - if (!item) - { - item = &m_cacheView.emplace_back(); - - item->Endpoint = endpoint; - item->Hash = hashValue; - } - - item->UnixEpochExpiration = expirationEpoch; - item->Data = std::move(response); - - if (StoreCacheView()) - { - AICLI_LOG(Repo, Verbose, << "RestInformationCache stored information for: " << Utility::ConvertToUTF8(endpoint)); - return; - } - else - { - // Extract the response back from the item for the next iteration - response = std::move(item->Data); - - // Failed to store due to the cache changing, reload and try again. - LoadCacheView(); - } - } - - AICLI_LOG(Repo, Warning, << "RestInformationCache failed to store information cache after 10 attempts."); - } -#ifdef AICLI_DISABLE_TEST_HOOKS - CATCH_LOG(); -#endif - - void RestInformationCache::LoadCacheView() - { - using namespace web::json; - - std::unique_ptr stream = m_settingsStream.Get(); - m_cacheView.clear(); - - if (!stream) - { - return; - } - - value cacheValue = value::parse(*stream); - - if (!cacheValue.is_array()) - { - AICLI_LOG(Repo, Warning, << "RestInformationCache value was not an array."); - return; - } - - array& cacheArray = cacheValue.as_array(); - - for (const value& cacheItemValue : cacheArray) - { - if (!cacheItemValue.is_object()) - { - AICLI_LOG(Repo, Warning, << "RestInformationCache cache item was not an object."); - continue; - } - - std::optional expiration = JSON::GetRawUInt64ValueFromJsonNode(cacheItemValue, std::wstring{ s_ExpirationName }); - if (!expiration) - { - AICLI_LOG(Repo, Warning, << "RestInformationCache cache item missing expiration."); - continue; - } - - if (std::chrono::system_clock::now() > Utility::ConvertUnixEpochToSystemClock(expiration.value())) - { - AICLI_LOG(Repo, Verbose, << "RestInformationCache cache item has expired."); - continue; - } - - std::optional endpoint = JSON::GetWideStringValueFromJsonNode(cacheItemValue, std::wstring{ s_EndpointName }); - if (!JSON::IsValidNonEmptyStringValue(endpoint)) - { - AICLI_LOG(Repo, Warning, << "RestInformationCache cache item missing endpoint."); - continue; - } - - CacheItem cacheItem; - cacheItem.Endpoint = endpoint.value(); - cacheItem.UnixEpochExpiration = expiration.value(); - - std::optional hash = JSON::GetWideStringValueFromJsonNode(cacheItemValue, std::wstring{ s_HashName }); - if (JSON::IsValidNonEmptyStringValue(hash)) - { - cacheItem.Hash = Utility::SHA256::ConvertToBytes(hash.value()); - } - - auto dataValue = JSON::GetJsonValueFromNode(cacheItemValue, std::wstring{ s_DataName }); - if (!dataValue) - { - AICLI_LOG(Repo, Warning, << "RestInformationCache cache item missing data."); - continue; - } - - cacheItem.Data = dataValue.value().get(); - if (cacheItem.Data.is_null()) - { - AICLI_LOG(Repo, Warning, << "RestInformationCache cache item data value null."); - continue; - } - - m_cacheView.emplace_back(std::move(cacheItem)); - } - } - - RestInformationCache::CacheItem* RestInformationCache::FindCacheItem(const std::wstring& endpoint, const Utility::SHA256::HashBuffer& hash) - { - for (CacheItem& item : m_cacheView) - { - if (item.Endpoint == endpoint && - Utility::SHA256::AreEqual(item.Hash, hash)) - { - return &item; - } - } - - return nullptr; - } - - [[nodiscard]] bool RestInformationCache::StoreCacheView() - { - using namespace web::json; - - value cacheValue = value::array(); - array& cacheArray = cacheValue.as_array(); - - for (const CacheItem& item : m_cacheView) - { - value cacheItemValue = value::object(); - object& cacheItemObject = cacheItemValue.as_object(); - - cacheItemObject[std::wstring{ s_EndpointName }] = value::value(item.Endpoint); - cacheItemObject[std::wstring{ s_HashName }] = value::value(Utility::ConvertToUTF16(Utility::ConvertToHexString(item.Hash))); - cacheItemObject[std::wstring{ s_ExpirationName }] = value::value(item.UnixEpochExpiration); - cacheItemObject[std::wstring{ s_DataName }] = item.Data; - - cacheArray[cacheArray.size()] = std::move(cacheItemValue); - } - - std::stringstream stream; - cacheValue.serialize(stream); - - return m_settingsStream.Set(std::move(stream).str()); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RestInformationCache.h" +#include "Rest/Schema/InformationResponseDeserializer.h" +#include + +namespace AppInstaller::Repository::Rest +{ + namespace + { + constexpr std::wstring_view s_EndpointName = L"endpoint"sv; + constexpr std::wstring_view s_HashName = L"hash"sv; + constexpr std::wstring_view s_ExpirationName = L"expiration"sv; + constexpr std::wstring_view s_DataName = L"data"sv; + + // Calculates the hash of values that might change per-call. + Utility::SHA256::HashBuffer GetHash(const std::optional& customHeader, std::string_view caller) + { + std::stringstream stream; + if (customHeader) + { + stream << customHeader.value(); + } + stream << '|' << caller; + + return Utility::SHA256::ComputeHash(stream); + } + + uint64_t CalculateExpiration(std::chrono::seconds duration) + { + // If no expiration information is provided, use 1 minute + if (!duration.count()) + { + duration = 60s; + } + + return Utility::ConvertSystemClockToUnixEpoch(std::chrono::system_clock::now() + duration); + } + } + + std::optional RestInformationCache::Get(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller) +#ifdef AICLI_DISABLE_TEST_HOOKS + try +#endif + { + LoadCacheView(); + + Utility::SHA256::HashBuffer hashValue = GetHash(customHeader, caller); + CacheItem* item = FindCacheItem(endpoint, hashValue); + + // If we don't find a private match, see if there is a public one. + if (!item) + { + item = FindCacheItem(endpoint, {}); + } + + if (!item) + { + return std::nullopt; + } + + Schema::InformationResponseDeserializer responseDeserializer; + return responseDeserializer.Deserialize(item->Data); + } +#ifdef AICLI_DISABLE_TEST_HOOKS + catch (...) + { + LOG_CAUGHT_EXCEPTION_MSG("RestInformationCache::Get exception"); + return std::nullopt; + } +#endif + + void RestInformationCache::Cache(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller, const Utility::CacheControlPolicy& cacheControl, web::json::value response) +#ifdef AICLI_DISABLE_TEST_HOOKS + try +#endif + { + // If requested, do not cache this response. + // Since this data is small, treat no-cache as no-store. + if (cacheControl.NoStore || cacheControl.NoCache) + { + return; + } + + // If not public, we use the header values to differentiate the cache items. + Utility::SHA256::HashBuffer hashValue; + if (!cacheControl.Public) + { + hashValue = GetHash(customHeader, caller); + } + + uint64_t expirationEpoch = CalculateExpiration(std::chrono::seconds{ cacheControl.MaxAge }); + + // Due to the exchange semantics on the setting stream, we may have to retry storing the value. + for (int i = 0; i < 10; ++i) + { + CacheItem* item = FindCacheItem(endpoint, hashValue); + + if (!item) + { + item = &m_cacheView.emplace_back(); + + item->Endpoint = endpoint; + item->Hash = hashValue; + } + + item->UnixEpochExpiration = expirationEpoch; + item->Data = std::move(response); + + if (StoreCacheView()) + { + AICLI_LOG(Repo, Verbose, << "RestInformationCache stored information for: " << Utility::ConvertToUTF8(endpoint)); + return; + } + else + { + // Extract the response back from the item for the next iteration + response = std::move(item->Data); + + // Failed to store due to the cache changing, reload and try again. + LoadCacheView(); + } + } + + AICLI_LOG(Repo, Warning, << "RestInformationCache failed to store information cache after 10 attempts."); + } +#ifdef AICLI_DISABLE_TEST_HOOKS + CATCH_LOG(); +#endif + + void RestInformationCache::LoadCacheView() + { + using namespace web::json; + + std::unique_ptr stream = m_settingsStream.Get(); + m_cacheView.clear(); + + if (!stream) + { + return; + } + + value cacheValue = value::parse(*stream); + + if (!cacheValue.is_array()) + { + AICLI_LOG(Repo, Warning, << "RestInformationCache value was not an array."); + return; + } + + array& cacheArray = cacheValue.as_array(); + + for (const value& cacheItemValue : cacheArray) + { + if (!cacheItemValue.is_object()) + { + AICLI_LOG(Repo, Warning, << "RestInformationCache cache item was not an object."); + continue; + } + + std::optional expiration = JSON::GetRawUInt64ValueFromJsonNode(cacheItemValue, std::wstring{ s_ExpirationName }); + if (!expiration) + { + AICLI_LOG(Repo, Warning, << "RestInformationCache cache item missing expiration."); + continue; + } + + if (std::chrono::system_clock::now() > Utility::ConvertUnixEpochToSystemClock(expiration.value())) + { + AICLI_LOG(Repo, Verbose, << "RestInformationCache cache item has expired."); + continue; + } + + std::optional endpoint = JSON::GetWideStringValueFromJsonNode(cacheItemValue, std::wstring{ s_EndpointName }); + if (!JSON::IsValidNonEmptyStringValue(endpoint)) + { + AICLI_LOG(Repo, Warning, << "RestInformationCache cache item missing endpoint."); + continue; + } + + CacheItem cacheItem; + cacheItem.Endpoint = endpoint.value(); + cacheItem.UnixEpochExpiration = expiration.value(); + + std::optional hash = JSON::GetWideStringValueFromJsonNode(cacheItemValue, std::wstring{ s_HashName }); + if (JSON::IsValidNonEmptyStringValue(hash)) + { + cacheItem.Hash = Utility::SHA256::ConvertToBytes(hash.value()); + } + + auto dataValue = JSON::GetJsonValueFromNode(cacheItemValue, std::wstring{ s_DataName }); + if (!dataValue) + { + AICLI_LOG(Repo, Warning, << "RestInformationCache cache item missing data."); + continue; + } + + cacheItem.Data = dataValue.value().get(); + if (cacheItem.Data.is_null()) + { + AICLI_LOG(Repo, Warning, << "RestInformationCache cache item data value null."); + continue; + } + + m_cacheView.emplace_back(std::move(cacheItem)); + } + } + + RestInformationCache::CacheItem* RestInformationCache::FindCacheItem(const std::wstring& endpoint, const Utility::SHA256::HashBuffer& hash) + { + for (CacheItem& item : m_cacheView) + { + if (item.Endpoint == endpoint && + Utility::SHA256::AreEqual(item.Hash, hash)) + { + return &item; + } + } + + return nullptr; + } + + [[nodiscard]] bool RestInformationCache::StoreCacheView() + { + using namespace web::json; + + value cacheValue = value::array(); + array& cacheArray = cacheValue.as_array(); + + for (const CacheItem& item : m_cacheView) + { + value cacheItemValue = value::object(); + object& cacheItemObject = cacheItemValue.as_object(); + + cacheItemObject[std::wstring{ s_EndpointName }] = value::value(item.Endpoint); + cacheItemObject[std::wstring{ s_HashName }] = value::value(Utility::ConvertToUTF16(Utility::ConvertToHexString(item.Hash))); + cacheItemObject[std::wstring{ s_ExpirationName }] = value::value(item.UnixEpochExpiration); + cacheItemObject[std::wstring{ s_DataName }] = item.Data; + + cacheArray[cacheArray.size()] = std::move(cacheItemValue); + } + + std::stringstream stream; + cacheValue.serialize(stream); + + return m_settingsStream.Set(std::move(stream).str()); + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestInformationCache.h b/src/AppInstallerRepositoryCore/Rest/RestInformationCache.h index 5c01a2b17e..14fd611a4b 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestInformationCache.h +++ b/src/AppInstallerRepositoryCore/Rest/RestInformationCache.h @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/IRestClient.h" -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Repository::Rest -{ - // Provides access to cached responses to the /information request. - struct RestInformationCache - { - // Attempts to get a cached information response for the provided inputs. - std::optional Get(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller); - - // Stores the information response as appropriate. - void Cache(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller, const Utility::CacheControlPolicy& cacheControl, web::json::value response); - - private: - struct CacheItem - { - std::wstring Endpoint; - Utility::SHA256::HashBuffer Hash; - uint64_t UnixEpochExpiration = 0; - web::json::value Data; - }; - - // Reads from the cache, constructing our view of the items it contains. - // Discards any expired items while reading the cache. - void LoadCacheView(); - - // Finds the cache item for the given inputs, or nullptr if it is not found. - CacheItem* FindCacheItem(const std::wstring& endpoint, const Utility::SHA256::HashBuffer& hash); - - // Attempts to store the current cache view back to the cache. - // Returns true if successful; false if the cache was updated since our last read and a retry is necessary. - [[nodiscard]] bool StoreCacheView(); - - Settings::Stream m_settingsStream{ Settings::Stream::RestInformationCache }; - std::vector m_cacheView; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/IRestClient.h" +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Repository::Rest +{ + // Provides access to cached responses to the /information request. + struct RestInformationCache + { + // Attempts to get a cached information response for the provided inputs. + std::optional Get(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller); + + // Stores the information response as appropriate. + void Cache(const std::wstring& endpoint, const std::optional& customHeader, std::string_view caller, const Utility::CacheControlPolicy& cacheControl, web::json::value response); + + private: + struct CacheItem + { + std::wstring Endpoint; + Utility::SHA256::HashBuffer Hash; + uint64_t UnixEpochExpiration = 0; + web::json::value Data; + }; + + // Reads from the cache, constructing our view of the items it contains. + // Discards any expired items while reading the cache. + void LoadCacheView(); + + // Finds the cache item for the given inputs, or nullptr if it is not found. + CacheItem* FindCacheItem(const std::wstring& endpoint, const Utility::SHA256::HashBuffer& hash); + + // Attempts to store the current cache view back to the cache. + // Returns true if successful; false if the cache was updated since our last read and a retry is necessary. + [[nodiscard]] bool StoreCacheView(); + + Settings::Stream m_settingsStream{ Settings::Stream::RestInformationCache }; + std::vector m_cacheView; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp index d851ff6b28..ebfcde708b 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSource.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSource.cpp @@ -1,548 +1,548 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RestSource.h" -#include "MatchCriteriaResolver.h" - -using namespace AppInstaller::Utility; - -namespace AppInstaller::Repository::Rest -{ - namespace - { - using namespace AppInstaller::Repository::Rest::Schema; - - // The source reference used by package objects. - struct SourceReference - { - SourceReference(const std::shared_ptr& source) : - m_source(source) {} - - protected: - std::shared_ptr GetReferenceSource() const - { - std::shared_ptr source = m_source.lock(); - THROW_HR_IF(E_NOT_VALID_STATE, !source); - return source; - } - - private: - std::weak_ptr m_source; - }; - - // The IPackage implementation for Available packages from RestSource. - struct RestPackage : public std::enable_shared_from_this, public SourceReference, public IPackage, public ICompositePackage - { - static constexpr IPackageType PackageType = IPackageType::RestPackage; - - RestPackage(const std::shared_ptr& source, IRestClient::Package&& package) : - SourceReference(source), m_package(std::move(package)) - { - SortVersionsInternal(); - } - - // Inherited via IPackage - Utility::LocIndString GetProperty(PackageProperty property) const override - { - switch (property) - { - case PackageProperty::Id: - return Utility::LocIndString{ m_package.PackageInformation.PackageIdentifier }; - case PackageProperty::Name: - return Utility::LocIndString{ m_package.PackageInformation.PackageName }; - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::vector GetMultiProperty(PackageMultiProperty property) const override; - - std::vector GetVersionKeys() const override - { - std::shared_ptr source = GetReferenceSource(); - std::scoped_lock versionsLock{ m_packageVersionsLock }; - - std::vector result; - for (const auto& versionInfo : m_package.Versions) - { - result.emplace_back( - source->GetIdentifier(), versionInfo.VersionAndChannel.GetVersion().ToString(), versionInfo.VersionAndChannel.GetChannel().ToString()); - } - - return result; - } - - std::shared_ptr GetLatestVersion() const override - { - std::scoped_lock versionsLock{ m_packageVersionsLock }; - return GetLatestVersionInternal(); - } - - std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override; - - Source GetSource() const override - { - return Source{ GetReferenceSource() }; - } - - bool IsSame(const IPackage* other) const override - { - const RestPackage* otherPackage = PackageCast(other); - - if (otherPackage) - { - return GetReferenceSource()->IsSame(otherPackage->GetReferenceSource().get()) && - Utility::CaseInsensitiveEquals(m_package.PackageInformation.PackageIdentifier, otherPackage->m_package.PackageInformation.PackageIdentifier); - } - - return false; - } - - const void* CastTo(IPackageType type) const override - { - if (type == PackageType) - { - return this; - } - - return nullptr; - } - - // Inherited via ICompositePackage - std::shared_ptr GetInstalled() override - { - return {}; - } - - std::vector> GetAvailable() override - { - return std::vector>{ shared_from_this() }; - } - - // Helpers for PackageVersion interop - const IRestClient::PackageInfo& PackageInfo() const - { - return m_package.PackageInformation; - } - - // This function is designed to handle the case where the only version that is returned by the - // initial search is Unknown. In that case, we perform a search intended to trigger the optimized - // path and directly get all manifests. - bool HandleSingleUnknownVersion(IRestClient::VersionInfo& versionInfo) - { - // If the calling version is unknown then we want to update it if we already - // have the results in the package. - if (versionInfo.VersionAndChannel.GetVersion().IsUnknown() && !versionInfo.Manifest) - { - std::scoped_lock versionsLock{ m_packageVersionsLock }; - if (m_package.Versions.size() == 1 && m_package.Versions[0].VersionAndChannel.GetVersion().IsUnknown() && !m_package.Versions[0].Manifest) - { - SearchRequest request; - request.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, m_package.PackageInformation.PackageIdentifier); - - IRestClient::SearchResult result = GetReferenceSource()->GetRestClient().Search(request); - - if (result.Matches.size() == 1) - { - m_package.Versions = std::move(result.Matches[0].Versions); - SortVersionsInternal(); - } - else - { - // Unexpected, but just leave things as they are - AICLI_LOG(Repo, Warning, << "Found " << result.Matches.size() << " matches for optimized search of " << m_package.PackageInformation.PackageIdentifier); - } - } - - if (!m_package.Versions.empty()) - { - // The results are now sorted; either take the last one if it is unknown - // or the first one if it is not (aka latest). - if (m_package.Versions.back().VersionAndChannel.GetVersion().IsUnknown()) - { - versionInfo = m_package.Versions.back(); - } - else - { - versionInfo = m_package.Versions.front(); - } - } - - return true; - } - - return false; - } - - private: - std::shared_ptr NonConstSharedFromThis() const - { - return const_cast(this)->shared_from_this(); - } - - // Must hold m_packageVersionsLock while calling this - std::shared_ptr GetLatestVersionInternal() const; - - // Must hold m_packageVersionsLock while calling this - void SortVersionsInternal() - { - std::sort(m_package.Versions.begin(), m_package.Versions.end(), - [](const IRestClient::VersionInfo& a, const IRestClient::VersionInfo& b) - { - return a.VersionAndChannel < b.VersionAndChannel; - }); - } - - IRestClient::Package m_package; - // Protects access to m_package.Versions - mutable std::mutex m_packageVersionsLock; - }; - - void GetMultiPropertyValues( - const RestPackage* package, - const IRestClient::VersionInfo& versionInfo, - PackageVersionMultiProperty property, - std::vector& result, - void (*Action)(std::vector&, Utility::LocIndString&&)) - { - switch (property) - { - case PackageVersionMultiProperty::PackageFamilyName: - for (const std::string& pfn : versionInfo.PackageFamilyNames) - { - Action(result, Utility::LocIndString{ pfn }); - } - break; - case PackageVersionMultiProperty::ProductCode: - for (const std::string& productCode : versionInfo.ProductCodes) - { - Action(result, Utility::LocIndString{ productCode }); - } - break; - case PackageVersionMultiProperty::UpgradeCode: - for (const std::string& upgradeCode : versionInfo.UpgradeCodes) - { - Action(result, Utility::LocIndString{ upgradeCode }); - } - break; - case PackageVersionMultiProperty::Name: - if (versionInfo.Manifest) - { - for (auto&& name : versionInfo.Manifest->GetPackageNames()) - { - Action(result, Utility::LocIndString{ std::move(name) }); - } - } - else - { - Action(result, Utility::LocIndString{ package->PackageInfo().PackageName }); - } - break; - case PackageVersionMultiProperty::Publisher: - if (versionInfo.Manifest) - { - for (auto&& publisher : versionInfo.Manifest->GetPublishers()) - { - Action(result, Utility::LocIndString{ std::move(publisher) }); - } - } - else - { - Action(result, Utility::LocIndString{ package->PackageInfo().Publisher }); - } - break; - case PackageVersionMultiProperty::Locale: - if (versionInfo.Manifest) - { - Action(result, Utility::LocIndString{ versionInfo.Manifest->DefaultLocalization.Locale }); - for (const auto& loc : versionInfo.Manifest->Localizations) - { - Action(result, Utility::LocIndString{ loc.Locale }); - } - } - break; - } - } - - std::vector RestPackage::GetMultiProperty(PackageMultiProperty property) const - { - std::scoped_lock versionsLock{ m_packageVersionsLock }; - std::vector result; - PackageVersionMultiProperty mappedProperty = PackageMultiPropertyToPackageVersionMultiProperty(property); - - for (const auto& versionInfo : m_package.Versions) - { - GetMultiPropertyValues( - this, - versionInfo, - mappedProperty, - result, - [](std::vector& result, Utility::LocIndString&& string) - { - auto itr = std::lower_bound(result.begin(), result.end(), string); - - if (itr == result.end() || *itr != string) - { - result.emplace(itr, std::move(string)); - } - }); - } - - return result; - } - - // The IPackageVersion impl for RestSource. - struct PackageVersion : public SourceReference, public IPackageVersion - { - PackageVersion( - const std::shared_ptr& source, std::shared_ptr&& package, IRestClient::VersionInfo versionInfo) - : SourceReference(source), m_package(std::move(package)), m_versionInfo(std::move(versionInfo)) {} - - // Inherited via IPackageVersion - Utility::LocIndString GetProperty(PackageVersionProperty property) const override - { - switch (property) - { - case PackageVersionProperty::SourceIdentifier: - return Utility::LocIndString{ GetReferenceSource()->GetIdentifier() }; - case PackageVersionProperty::SourceName: - return Utility::LocIndString{ GetReferenceSource()->GetDetails().Name }; - case PackageVersionProperty::Id: - return Utility::LocIndString{ m_package->PackageInfo().PackageIdentifier }; - case PackageVersionProperty::Name: - return Utility::LocIndString{ m_package->PackageInfo().PackageName }; - case PackageVersionProperty::Version: - return Utility::LocIndString{ m_versionInfo.VersionAndChannel.GetVersion().ToString() }; - case PackageVersionProperty::Channel: - return Utility::LocIndString{ m_versionInfo.VersionAndChannel.GetChannel().ToString() }; - case PackageVersionProperty::Publisher: - return Utility::LocIndString{ m_package->PackageInfo().Publisher }; - case PackageVersionProperty::ArpMinVersion: - if (!m_versionInfo.ArpVersions.empty()) - { - return Utility::LocIndString{ m_versionInfo.ArpVersions.front().ToString() }; - } - else if (m_versionInfo.Manifest) - { - auto arpVersionRange = m_versionInfo.Manifest->GetArpVersionRange(); - return arpVersionRange.IsEmpty() ? Utility::LocIndString{} : Utility::LocIndString{ arpVersionRange.GetMinVersion().ToString() }; - } - else - { - return {}; - } - case PackageVersionProperty::ArpMaxVersion: - if (!m_versionInfo.ArpVersions.empty()) - { - return Utility::LocIndString{ m_versionInfo.ArpVersions.back().ToString() }; - } - else if (m_versionInfo.Manifest) - { - auto arpVersionRange = m_versionInfo.Manifest->GetArpVersionRange(); - return arpVersionRange.IsEmpty() ? Utility::LocIndString{} : Utility::LocIndString{ arpVersionRange.GetMaxVersion().ToString() }; - } - else - { - return {}; - } - default: - return {}; - } - } - - std::vector GetMultiProperty(PackageVersionMultiProperty property) const override - { - std::vector result; - - GetMultiPropertyValues( - m_package.get(), - m_versionInfo, - property, - result, - [](std::vector& result, Utility::LocIndString&& string) - { - result.emplace_back(std::move(string)); - }); - - return result; - } - - Manifest::Manifest GetManifest() override - { - AICLI_LOG(Repo, Verbose, << "Getting manifest"); - - if (m_versionInfo.Manifest) - { - return m_versionInfo.Manifest.value(); - } - - if (m_package->HandleSingleUnknownVersion(m_versionInfo) && - m_versionInfo.Manifest) - { - return m_versionInfo.Manifest.value(); - } - - std::optional manifest = GetReferenceSource()->GetRestClient().GetManifestByVersion( - m_package->PackageInfo().PackageIdentifier, m_versionInfo.VersionAndChannel.GetVersion().ToString(), m_versionInfo.VersionAndChannel.GetChannel().ToString()); - - if (!manifest) - { - AICLI_LOG(Repo, Verbose, << "Valid manifest not found for package: " << m_package->PackageInfo().PackageIdentifier); - return {}; - } - - m_versionInfo.Manifest = std::move(manifest.value()); - return m_versionInfo.Manifest.value(); - } - - Source GetSource() const override - { - return Source{ GetReferenceSource() }; - } - - IPackageVersion::Metadata GetMetadata() const override - { - IPackageVersion::Metadata result; - return result; - } - - private: - std::shared_ptr m_package; - IRestClient::VersionInfo m_versionInfo; - }; - - std::shared_ptr RestPackage::GetVersion(const PackageVersionKey& versionKey) const - { - std::shared_ptr source = GetReferenceSource(); - std::scoped_lock versionsLock{ m_packageVersionsLock }; - - // Ensure that this key targets this (or any) source - if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) - { - return {}; - } - - std::shared_ptr packageVersion; - if (!versionKey.Version.empty() && !versionKey.Channel.empty()) - { - for (const auto& versionInfo : m_package.Versions) - { - if (CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetVersion().ToString(), versionKey.Version) - && CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetChannel().ToString(), versionKey.Channel)) - { - packageVersion = std::make_shared(source, NonConstSharedFromThis(), versionInfo); - break; - } - } - } - else if (versionKey.Version.empty() && versionKey.Channel.empty()) - { - packageVersion = GetLatestVersionInternal(); - } - else if (versionKey.Version.empty()) - { - for (const auto& versionInfo : m_package.Versions) - { - if (CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetChannel().ToString(), versionKey.Channel)) - { - packageVersion = std::make_shared(source, NonConstSharedFromThis(), versionInfo); - break; - } - } - } - else if (versionKey.Channel.empty()) - { - for (const auto& versionInfo : m_package.Versions) - { - if (CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetVersion().ToString(), versionKey.Version)) - { - packageVersion = std::make_shared(source, NonConstSharedFromThis(), versionInfo); - break; - } - } - } - - return packageVersion; - } - - std::shared_ptr RestPackage::GetLatestVersionInternal() const - { - return std::make_shared(GetReferenceSource(), NonConstSharedFromThis(), m_package.Versions.front()); - } - } - - RestSource::RestSource(const SourceDetails& details, SourceInformation information, RestClient&& restClient) - : m_details(details), m_information(std::move(information)), m_restClient(std::move(restClient)) - { - } - - const std::string& RestSource::GetIdentifier() const - { - return m_details.Identifier; - } - - const SourceDetails& RestSource::GetDetails() const - { - return m_details; - } - - SourceInformation RestSource::GetInformation() const - { - return m_information; - } - - bool RestSource::QueryFeatureFlag(SourceFeatureFlag flag) const - { - switch (flag) - { - case SourceFeatureFlag::ManifestMayContainAdditionalSystemReferenceStrings: - return true; - } - - return false; - } - - SearchResult RestSource::Search(const SearchRequest& request) const - { - IRestClient::SearchResult results = m_restClient.Search(request); - SearchResult searchResult; - - std::shared_ptr sharedThis = NonConstSharedFromThis(); - for (auto& result : results.Matches) - { - std::shared_ptr package = std::make_shared(sharedThis, std::move(result)); - PackageMatchFilter packageFilter{ FindBestMatchCriteria(request, package->GetLatestVersion().get()) }; - - searchResult.Matches.emplace_back(std::move(package), std::move(packageFilter)); - } - - searchResult.Truncated = results.Truncated; - - return searchResult; - } - - void* RestSource::CastTo(ISourceType type) - { - if (type == SourceType) - { - return this; - } - - return nullptr; - } - - const RestClient& RestSource::GetRestClient() const - { - return m_restClient; - } - - bool RestSource::IsSame(const RestSource* other) const - { - return (other && GetIdentifier() == other->GetIdentifier()); - } - - std::shared_ptr RestSource::NonConstSharedFromThis() const - { - return const_cast(this)->shared_from_this(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RestSource.h" +#include "MatchCriteriaResolver.h" + +using namespace AppInstaller::Utility; + +namespace AppInstaller::Repository::Rest +{ + namespace + { + using namespace AppInstaller::Repository::Rest::Schema; + + // The source reference used by package objects. + struct SourceReference + { + SourceReference(const std::shared_ptr& source) : + m_source(source) {} + + protected: + std::shared_ptr GetReferenceSource() const + { + std::shared_ptr source = m_source.lock(); + THROW_HR_IF(E_NOT_VALID_STATE, !source); + return source; + } + + private: + std::weak_ptr m_source; + }; + + // The IPackage implementation for Available packages from RestSource. + struct RestPackage : public std::enable_shared_from_this, public SourceReference, public IPackage, public ICompositePackage + { + static constexpr IPackageType PackageType = IPackageType::RestPackage; + + RestPackage(const std::shared_ptr& source, IRestClient::Package&& package) : + SourceReference(source), m_package(std::move(package)) + { + SortVersionsInternal(); + } + + // Inherited via IPackage + Utility::LocIndString GetProperty(PackageProperty property) const override + { + switch (property) + { + case PackageProperty::Id: + return Utility::LocIndString{ m_package.PackageInformation.PackageIdentifier }; + case PackageProperty::Name: + return Utility::LocIndString{ m_package.PackageInformation.PackageName }; + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::vector GetMultiProperty(PackageMultiProperty property) const override; + + std::vector GetVersionKeys() const override + { + std::shared_ptr source = GetReferenceSource(); + std::scoped_lock versionsLock{ m_packageVersionsLock }; + + std::vector result; + for (const auto& versionInfo : m_package.Versions) + { + result.emplace_back( + source->GetIdentifier(), versionInfo.VersionAndChannel.GetVersion().ToString(), versionInfo.VersionAndChannel.GetChannel().ToString()); + } + + return result; + } + + std::shared_ptr GetLatestVersion() const override + { + std::scoped_lock versionsLock{ m_packageVersionsLock }; + return GetLatestVersionInternal(); + } + + std::shared_ptr GetVersion(const PackageVersionKey& versionKey) const override; + + Source GetSource() const override + { + return Source{ GetReferenceSource() }; + } + + bool IsSame(const IPackage* other) const override + { + const RestPackage* otherPackage = PackageCast(other); + + if (otherPackage) + { + return GetReferenceSource()->IsSame(otherPackage->GetReferenceSource().get()) && + Utility::CaseInsensitiveEquals(m_package.PackageInformation.PackageIdentifier, otherPackage->m_package.PackageInformation.PackageIdentifier); + } + + return false; + } + + const void* CastTo(IPackageType type) const override + { + if (type == PackageType) + { + return this; + } + + return nullptr; + } + + // Inherited via ICompositePackage + std::shared_ptr GetInstalled() override + { + return {}; + } + + std::vector> GetAvailable() override + { + return std::vector>{ shared_from_this() }; + } + + // Helpers for PackageVersion interop + const IRestClient::PackageInfo& PackageInfo() const + { + return m_package.PackageInformation; + } + + // This function is designed to handle the case where the only version that is returned by the + // initial search is Unknown. In that case, we perform a search intended to trigger the optimized + // path and directly get all manifests. + bool HandleSingleUnknownVersion(IRestClient::VersionInfo& versionInfo) + { + // If the calling version is unknown then we want to update it if we already + // have the results in the package. + if (versionInfo.VersionAndChannel.GetVersion().IsUnknown() && !versionInfo.Manifest) + { + std::scoped_lock versionsLock{ m_packageVersionsLock }; + if (m_package.Versions.size() == 1 && m_package.Versions[0].VersionAndChannel.GetVersion().IsUnknown() && !m_package.Versions[0].Manifest) + { + SearchRequest request; + request.Filters.emplace_back(PackageMatchField::Id, MatchType::CaseInsensitive, m_package.PackageInformation.PackageIdentifier); + + IRestClient::SearchResult result = GetReferenceSource()->GetRestClient().Search(request); + + if (result.Matches.size() == 1) + { + m_package.Versions = std::move(result.Matches[0].Versions); + SortVersionsInternal(); + } + else + { + // Unexpected, but just leave things as they are + AICLI_LOG(Repo, Warning, << "Found " << result.Matches.size() << " matches for optimized search of " << m_package.PackageInformation.PackageIdentifier); + } + } + + if (!m_package.Versions.empty()) + { + // The results are now sorted; either take the last one if it is unknown + // or the first one if it is not (aka latest). + if (m_package.Versions.back().VersionAndChannel.GetVersion().IsUnknown()) + { + versionInfo = m_package.Versions.back(); + } + else + { + versionInfo = m_package.Versions.front(); + } + } + + return true; + } + + return false; + } + + private: + std::shared_ptr NonConstSharedFromThis() const + { + return const_cast(this)->shared_from_this(); + } + + // Must hold m_packageVersionsLock while calling this + std::shared_ptr GetLatestVersionInternal() const; + + // Must hold m_packageVersionsLock while calling this + void SortVersionsInternal() + { + std::sort(m_package.Versions.begin(), m_package.Versions.end(), + [](const IRestClient::VersionInfo& a, const IRestClient::VersionInfo& b) + { + return a.VersionAndChannel < b.VersionAndChannel; + }); + } + + IRestClient::Package m_package; + // Protects access to m_package.Versions + mutable std::mutex m_packageVersionsLock; + }; + + void GetMultiPropertyValues( + const RestPackage* package, + const IRestClient::VersionInfo& versionInfo, + PackageVersionMultiProperty property, + std::vector& result, + void (*Action)(std::vector&, Utility::LocIndString&&)) + { + switch (property) + { + case PackageVersionMultiProperty::PackageFamilyName: + for (const std::string& pfn : versionInfo.PackageFamilyNames) + { + Action(result, Utility::LocIndString{ pfn }); + } + break; + case PackageVersionMultiProperty::ProductCode: + for (const std::string& productCode : versionInfo.ProductCodes) + { + Action(result, Utility::LocIndString{ productCode }); + } + break; + case PackageVersionMultiProperty::UpgradeCode: + for (const std::string& upgradeCode : versionInfo.UpgradeCodes) + { + Action(result, Utility::LocIndString{ upgradeCode }); + } + break; + case PackageVersionMultiProperty::Name: + if (versionInfo.Manifest) + { + for (auto&& name : versionInfo.Manifest->GetPackageNames()) + { + Action(result, Utility::LocIndString{ std::move(name) }); + } + } + else + { + Action(result, Utility::LocIndString{ package->PackageInfo().PackageName }); + } + break; + case PackageVersionMultiProperty::Publisher: + if (versionInfo.Manifest) + { + for (auto&& publisher : versionInfo.Manifest->GetPublishers()) + { + Action(result, Utility::LocIndString{ std::move(publisher) }); + } + } + else + { + Action(result, Utility::LocIndString{ package->PackageInfo().Publisher }); + } + break; + case PackageVersionMultiProperty::Locale: + if (versionInfo.Manifest) + { + Action(result, Utility::LocIndString{ versionInfo.Manifest->DefaultLocalization.Locale }); + for (const auto& loc : versionInfo.Manifest->Localizations) + { + Action(result, Utility::LocIndString{ loc.Locale }); + } + } + break; + } + } + + std::vector RestPackage::GetMultiProperty(PackageMultiProperty property) const + { + std::scoped_lock versionsLock{ m_packageVersionsLock }; + std::vector result; + PackageVersionMultiProperty mappedProperty = PackageMultiPropertyToPackageVersionMultiProperty(property); + + for (const auto& versionInfo : m_package.Versions) + { + GetMultiPropertyValues( + this, + versionInfo, + mappedProperty, + result, + [](std::vector& result, Utility::LocIndString&& string) + { + auto itr = std::lower_bound(result.begin(), result.end(), string); + + if (itr == result.end() || *itr != string) + { + result.emplace(itr, std::move(string)); + } + }); + } + + return result; + } + + // The IPackageVersion impl for RestSource. + struct PackageVersion : public SourceReference, public IPackageVersion + { + PackageVersion( + const std::shared_ptr& source, std::shared_ptr&& package, IRestClient::VersionInfo versionInfo) + : SourceReference(source), m_package(std::move(package)), m_versionInfo(std::move(versionInfo)) {} + + // Inherited via IPackageVersion + Utility::LocIndString GetProperty(PackageVersionProperty property) const override + { + switch (property) + { + case PackageVersionProperty::SourceIdentifier: + return Utility::LocIndString{ GetReferenceSource()->GetIdentifier() }; + case PackageVersionProperty::SourceName: + return Utility::LocIndString{ GetReferenceSource()->GetDetails().Name }; + case PackageVersionProperty::Id: + return Utility::LocIndString{ m_package->PackageInfo().PackageIdentifier }; + case PackageVersionProperty::Name: + return Utility::LocIndString{ m_package->PackageInfo().PackageName }; + case PackageVersionProperty::Version: + return Utility::LocIndString{ m_versionInfo.VersionAndChannel.GetVersion().ToString() }; + case PackageVersionProperty::Channel: + return Utility::LocIndString{ m_versionInfo.VersionAndChannel.GetChannel().ToString() }; + case PackageVersionProperty::Publisher: + return Utility::LocIndString{ m_package->PackageInfo().Publisher }; + case PackageVersionProperty::ArpMinVersion: + if (!m_versionInfo.ArpVersions.empty()) + { + return Utility::LocIndString{ m_versionInfo.ArpVersions.front().ToString() }; + } + else if (m_versionInfo.Manifest) + { + auto arpVersionRange = m_versionInfo.Manifest->GetArpVersionRange(); + return arpVersionRange.IsEmpty() ? Utility::LocIndString{} : Utility::LocIndString{ arpVersionRange.GetMinVersion().ToString() }; + } + else + { + return {}; + } + case PackageVersionProperty::ArpMaxVersion: + if (!m_versionInfo.ArpVersions.empty()) + { + return Utility::LocIndString{ m_versionInfo.ArpVersions.back().ToString() }; + } + else if (m_versionInfo.Manifest) + { + auto arpVersionRange = m_versionInfo.Manifest->GetArpVersionRange(); + return arpVersionRange.IsEmpty() ? Utility::LocIndString{} : Utility::LocIndString{ arpVersionRange.GetMaxVersion().ToString() }; + } + else + { + return {}; + } + default: + return {}; + } + } + + std::vector GetMultiProperty(PackageVersionMultiProperty property) const override + { + std::vector result; + + GetMultiPropertyValues( + m_package.get(), + m_versionInfo, + property, + result, + [](std::vector& result, Utility::LocIndString&& string) + { + result.emplace_back(std::move(string)); + }); + + return result; + } + + Manifest::Manifest GetManifest() override + { + AICLI_LOG(Repo, Verbose, << "Getting manifest"); + + if (m_versionInfo.Manifest) + { + return m_versionInfo.Manifest.value(); + } + + if (m_package->HandleSingleUnknownVersion(m_versionInfo) && + m_versionInfo.Manifest) + { + return m_versionInfo.Manifest.value(); + } + + std::optional manifest = GetReferenceSource()->GetRestClient().GetManifestByVersion( + m_package->PackageInfo().PackageIdentifier, m_versionInfo.VersionAndChannel.GetVersion().ToString(), m_versionInfo.VersionAndChannel.GetChannel().ToString()); + + if (!manifest) + { + AICLI_LOG(Repo, Verbose, << "Valid manifest not found for package: " << m_package->PackageInfo().PackageIdentifier); + return {}; + } + + m_versionInfo.Manifest = std::move(manifest.value()); + return m_versionInfo.Manifest.value(); + } + + Source GetSource() const override + { + return Source{ GetReferenceSource() }; + } + + IPackageVersion::Metadata GetMetadata() const override + { + IPackageVersion::Metadata result; + return result; + } + + private: + std::shared_ptr m_package; + IRestClient::VersionInfo m_versionInfo; + }; + + std::shared_ptr RestPackage::GetVersion(const PackageVersionKey& versionKey) const + { + std::shared_ptr source = GetReferenceSource(); + std::scoped_lock versionsLock{ m_packageVersionsLock }; + + // Ensure that this key targets this (or any) source + if (!versionKey.SourceId.empty() && versionKey.SourceId != source->GetIdentifier()) + { + return {}; + } + + std::shared_ptr packageVersion; + if (!versionKey.Version.empty() && !versionKey.Channel.empty()) + { + for (const auto& versionInfo : m_package.Versions) + { + if (CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetVersion().ToString(), versionKey.Version) + && CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetChannel().ToString(), versionKey.Channel)) + { + packageVersion = std::make_shared(source, NonConstSharedFromThis(), versionInfo); + break; + } + } + } + else if (versionKey.Version.empty() && versionKey.Channel.empty()) + { + packageVersion = GetLatestVersionInternal(); + } + else if (versionKey.Version.empty()) + { + for (const auto& versionInfo : m_package.Versions) + { + if (CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetChannel().ToString(), versionKey.Channel)) + { + packageVersion = std::make_shared(source, NonConstSharedFromThis(), versionInfo); + break; + } + } + } + else if (versionKey.Channel.empty()) + { + for (const auto& versionInfo : m_package.Versions) + { + if (CaseInsensitiveEquals(versionInfo.VersionAndChannel.GetVersion().ToString(), versionKey.Version)) + { + packageVersion = std::make_shared(source, NonConstSharedFromThis(), versionInfo); + break; + } + } + } + + return packageVersion; + } + + std::shared_ptr RestPackage::GetLatestVersionInternal() const + { + return std::make_shared(GetReferenceSource(), NonConstSharedFromThis(), m_package.Versions.front()); + } + } + + RestSource::RestSource(const SourceDetails& details, SourceInformation information, RestClient&& restClient) + : m_details(details), m_information(std::move(information)), m_restClient(std::move(restClient)) + { + } + + const std::string& RestSource::GetIdentifier() const + { + return m_details.Identifier; + } + + const SourceDetails& RestSource::GetDetails() const + { + return m_details; + } + + SourceInformation RestSource::GetInformation() const + { + return m_information; + } + + bool RestSource::QueryFeatureFlag(SourceFeatureFlag flag) const + { + switch (flag) + { + case SourceFeatureFlag::ManifestMayContainAdditionalSystemReferenceStrings: + return true; + } + + return false; + } + + SearchResult RestSource::Search(const SearchRequest& request) const + { + IRestClient::SearchResult results = m_restClient.Search(request); + SearchResult searchResult; + + std::shared_ptr sharedThis = NonConstSharedFromThis(); + for (auto& result : results.Matches) + { + std::shared_ptr package = std::make_shared(sharedThis, std::move(result)); + PackageMatchFilter packageFilter{ FindBestMatchCriteria(request, package->GetLatestVersion().get()) }; + + searchResult.Matches.emplace_back(std::move(package), std::move(packageFilter)); + } + + searchResult.Truncated = results.Truncated; + + return searchResult; + } + + void* RestSource::CastTo(ISourceType type) + { + if (type == SourceType) + { + return this; + } + + return nullptr; + } + + const RestClient& RestSource::GetRestClient() const + { + return m_restClient; + } + + bool RestSource::IsSame(const RestSource* other) const + { + return (other && GetIdentifier() == other->GetIdentifier()); + } + + std::shared_ptr RestSource::NonConstSharedFromThis() const + { + return const_cast(this)->shared_from_this(); + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestSource.h b/src/AppInstallerRepositoryCore/Rest/RestSource.h index 711c393eae..9ff31caaa8 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSource.h +++ b/src/AppInstallerRepositoryCore/Rest/RestSource.h @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include "RestClient.h" - -namespace AppInstaller::Repository::Rest -{ - // A source that holds a RestSource. - struct RestSource : public std::enable_shared_from_this, public ISource - { - static constexpr ISourceType SourceType = ISourceType::RestSource; - - RestSource(const SourceDetails& details, SourceInformation information, RestClient&& restClient); - - RestSource(const RestSource&) = delete; - RestSource& operator=(const RestSource&) = delete; - - RestSource(RestSource&&) = default; - RestSource& operator=(RestSource&&) = default; - - ~RestSource() = default; - - // Gets the source's identifier; a unique identifier independent of the name - // that will not change between a remove/add or between additional adds. - // Must be suitable for filesystem names. - const std::string& GetIdentifier() const override; - - // Get the source's details. - const SourceDetails& GetDetails() const override; - - SourceInformation GetInformation() const override; - - bool QueryFeatureFlag(SourceFeatureFlag flag) const override; - - // Execute a search on the source. - SearchResult Search(const SearchRequest& request) const override; - - // Casts to the requested type. - void* CastTo(ISourceType type) override; - - // Gets the rest client. - const RestClient& GetRestClient() const; - - // Determines if the other source refers to the same as this. - bool IsSame(const RestSource* other) const; - - private: - std::shared_ptr NonConstSharedFromThis() const; - - SourceDetails m_details; - SourceInformation m_information; - RestClient m_restClient; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include "RestClient.h" + +namespace AppInstaller::Repository::Rest +{ + // A source that holds a RestSource. + struct RestSource : public std::enable_shared_from_this, public ISource + { + static constexpr ISourceType SourceType = ISourceType::RestSource; + + RestSource(const SourceDetails& details, SourceInformation information, RestClient&& restClient); + + RestSource(const RestSource&) = delete; + RestSource& operator=(const RestSource&) = delete; + + RestSource(RestSource&&) = default; + RestSource& operator=(RestSource&&) = default; + + ~RestSource() = default; + + // Gets the source's identifier; a unique identifier independent of the name + // that will not change between a remove/add or between additional adds. + // Must be suitable for filesystem names. + const std::string& GetIdentifier() const override; + + // Get the source's details. + const SourceDetails& GetDetails() const override; + + SourceInformation GetInformation() const override; + + bool QueryFeatureFlag(SourceFeatureFlag flag) const override; + + // Execute a search on the source. + SearchResult Search(const SearchRequest& request) const override; + + // Casts to the requested type. + void* CastTo(ISourceType type) override; + + // Gets the rest client. + const RestClient& GetRestClient() const; + + // Determines if the other source refers to the same as this. + bool IsSame(const RestSource* other) const; + + private: + std::shared_ptr NonConstSharedFromThis() const; + + SourceDetails m_details; + SourceInformation m_information; + RestClient m_restClient; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp index 25555a9fd6..8074f348b8 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp +++ b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.cpp @@ -1,161 +1,161 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RestSourceFactory.h" -#include "RestClient.h" -#include "RestSource.h" - -using namespace std::string_literals; -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Rest -{ - namespace - { - struct RestSourceReference : public ISourceReference - { - RestSourceReference(const SourceDetails& details) : m_details(details) {} - - SourceDetails& GetDetails() override { return m_details; }; - - std::string GetIdentifier() override - { - Initialize(); - return m_details.Identifier; - } - - SourceInformation GetInformation() override - { - Initialize(); - return m_information; - } - - // Set custom header. Returns false if custom header is not supported. - bool SetCustomHeader(std::optional header) override - { - m_customHeader = header; - return true; - } - - void SetCaller(std::string caller) override - { - m_caller = std::move(caller); - } - - void SetAuthenticationArguments(Authentication::AuthenticationArguments authArgs) override - { - m_authArgs = std::move(authArgs); - } - - void SetServerCertificateValidationCallback(std::function callback) override - { - Certificates::PinningConfiguration config; - config.AddChain(std::make_shared(std::move(callback))); - m_overridePinningConfiguration = std::move(config); - } - - std::shared_ptr Open(IProgressCallback&) override - { - Initialize(); - 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)); - } - - void SetThreadGlobals(const std::shared_ptr& threadGlobals) override - { - m_threadGlobals = threadGlobals; - } - - private: - void Initialize() - { - std::call_once(m_initializeFlag, - [&]() - { - const auto& pinConfig = m_overridePinningConfiguration.has_value() - ? *m_overridePinningConfiguration - : m_details.CertificatePinningConfiguration; - m_httpClientHelper.SetPinningConfiguration(pinConfig, m_threadGlobals); - m_restClientInformation = RestClient::GetInformation(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper); - - m_details.Identifier = m_restClientInformation.SourceIdentifier; - - 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 = m_restClientInformation.SourceAgreementsIdentifier; - for (auto const& agreement : m_restClientInformation.SourceAgreements) - { - m_information.SourceAgreements.emplace_back(agreement.Label, agreement.Text, agreement.Url); - } - - 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; - std::optional m_overridePinningConfiguration; - std::once_flag m_initializeFlag; - std::shared_ptr m_threadGlobals; - }; - - // The base class for data that comes from a rest based source. - struct RestSourceFactoryImpl : public ISourceFactory - { - std::string_view TypeName() const override final - { - return RestSourceFactory::Type(); - } - - std::shared_ptr Create(const SourceDetails& details) override final - { - THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); - - return std::make_shared(details); - } - - bool Add(SourceDetails& details, IProgressCallback&) override final - { - if (details.Type.empty()) - { - details.Type = RestSourceFactory::Type(); - } - else - { - THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); - } - - // Check if URL is remote and secure - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE, !Utility::IsUrlRemote(details.Arg)); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, !Utility::IsUrlSecure(details.Arg)); - - return true; - } - - bool Update(const SourceDetails& details, IProgressCallback&) override final - { - THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); - return true; - } - - bool Remove(const SourceDetails& details, IProgressCallback&) override final - { - THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); - return true; - } - }; - } - - std::unique_ptr RestSourceFactory::Create() - { - return std::make_unique(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "RestSourceFactory.h" +#include "RestClient.h" +#include "RestSource.h" + +using namespace std::string_literals; +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Rest +{ + namespace + { + struct RestSourceReference : public ISourceReference + { + RestSourceReference(const SourceDetails& details) : m_details(details) {} + + SourceDetails& GetDetails() override { return m_details; }; + + std::string GetIdentifier() override + { + Initialize(); + return m_details.Identifier; + } + + SourceInformation GetInformation() override + { + Initialize(); + return m_information; + } + + // Set custom header. Returns false if custom header is not supported. + bool SetCustomHeader(std::optional header) override + { + m_customHeader = header; + return true; + } + + void SetCaller(std::string caller) override + { + m_caller = std::move(caller); + } + + void SetAuthenticationArguments(Authentication::AuthenticationArguments authArgs) override + { + m_authArgs = std::move(authArgs); + } + + void SetServerCertificateValidationCallback(std::function callback) override + { + Certificates::PinningConfiguration config; + config.AddChain(std::make_shared(std::move(callback))); + m_overridePinningConfiguration = std::move(config); + } + + std::shared_ptr Open(IProgressCallback&) override + { + Initialize(); + 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)); + } + + void SetThreadGlobals(const std::shared_ptr& threadGlobals) override + { + m_threadGlobals = threadGlobals; + } + + private: + void Initialize() + { + std::call_once(m_initializeFlag, + [&]() + { + const auto& pinConfig = m_overridePinningConfiguration.has_value() + ? *m_overridePinningConfiguration + : m_details.CertificatePinningConfiguration; + m_httpClientHelper.SetPinningConfiguration(pinConfig, m_threadGlobals); + m_restClientInformation = RestClient::GetInformation(m_details.Arg, m_customHeader, m_caller, m_httpClientHelper); + + m_details.Identifier = m_restClientInformation.SourceIdentifier; + + 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 = m_restClientInformation.SourceAgreementsIdentifier; + for (auto const& agreement : m_restClientInformation.SourceAgreements) + { + m_information.SourceAgreements.emplace_back(agreement.Label, agreement.Text, agreement.Url); + } + + 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; + std::optional m_overridePinningConfiguration; + std::once_flag m_initializeFlag; + std::shared_ptr m_threadGlobals; + }; + + // The base class for data that comes from a rest based source. + struct RestSourceFactoryImpl : public ISourceFactory + { + std::string_view TypeName() const override final + { + return RestSourceFactory::Type(); + } + + std::shared_ptr Create(const SourceDetails& details) override final + { + THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); + + return std::make_shared(details); + } + + bool Add(SourceDetails& details, IProgressCallback&) override final + { + if (details.Type.empty()) + { + details.Type = RestSourceFactory::Type(); + } + else + { + THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); + } + + // Check if URL is remote and secure + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE, !Utility::IsUrlRemote(details.Arg)); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, !Utility::IsUrlSecure(details.Arg)); + + return true; + } + + bool Update(const SourceDetails& details, IProgressCallback&) override final + { + THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); + return true; + } + + bool Remove(const SourceDetails& details, IProgressCallback&) override final + { + THROW_HR_IF(E_INVALIDARG, !Utility::CaseInsensitiveEquals(details.Type, RestSourceFactory::Type())); + return true; + } + }; + } + + std::unique_ptr RestSourceFactory::Create() + { + return std::make_unique(); + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.h b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.h index 18b5c2d55f..4cd8fbefee 100644 --- a/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.h +++ b/src/AppInstallerRepositoryCore/Rest/RestSourceFactory.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include "SourceFactory.h" -#include - -namespace AppInstaller::Repository::Rest -{ - using namespace std::string_view_literals; - - // A source where the information is stored on a REST based server. - // In addition, the manifest information is also available on the server. - // Arg :: Expected to be a API which supports querying functionality. - struct RestSourceFactory - { - // Get the type string for this source. - static constexpr std::string_view Type() - { - using namespace std::string_view_literals; - return "Microsoft.Rest"sv; - } - - // Creates a source factory for this type. - static std::unique_ptr Create(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include "SourceFactory.h" +#include + +namespace AppInstaller::Repository::Rest +{ + using namespace std::string_view_literals; + + // A source where the information is stored on a REST based server. + // In addition, the manifest information is also available on the server. + // Arg :: Expected to be a API which supports querying functionality. + struct RestSourceFactory + { + // Get the type string for this source. + static constexpr std::string_view Type() + { + using namespace std::string_view_literals; + return "Microsoft.Rest"sv; + } + + // Creates a source factory for this type. + static std::unique_ptr Create(); + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Interface.h index f4cb8aef64..cbf22d8ea7 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Interface.h @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/IRestClient.h" -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_0 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public IRestClient - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - IRestClient::Information GetSourceInformation() const override; - IRestClient::SearchResult Search(const SearchRequest& request) const override; - std::optional GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const override; - std::vector GetManifests(const std::string& packageId, const std::map& params = {}) const override; - - protected: - bool MeetsOptimizedSearchCriteria(const SearchRequest& request) const; - IRestClient::SearchResult OptimizedSearch(const SearchRequest& request) const; - IRestClient::SearchResult SearchInternal(const SearchRequest& request) const; - - // Check query params against source information and update if necessary. - virtual std::map GetValidatedQueryParams(const std::map& params) const; - - // Check search request against source information and get json search body. - virtual web::json::value GetValidatedSearchBody(const SearchRequest& searchRequest) const; - - virtual SearchResult GetSearchResult(const web::json::value& searchResponseObject) const; - virtual std::vector GetParsedManifests(const web::json::value& manifestsResponseObject) const; - - // Gets auth headers if source requires authentication for access. - virtual Http::HttpClientHelper::HttpRequestHeaders GetAuthHeaders() const; - - Http::HttpClientHelper::HttpRequestHeaders m_requiredRestApiHeaders; - - private: - std::string m_restApiUri; - utility::string_t m_searchEndpoint; - Http::HttpClientHelper m_httpClientHelper; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/IRestClient.h" +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_0 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public IRestClient + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + IRestClient::Information GetSourceInformation() const override; + IRestClient::SearchResult Search(const SearchRequest& request) const override; + std::optional GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const override; + std::vector GetManifests(const std::string& packageId, const std::map& params = {}) const override; + + protected: + bool MeetsOptimizedSearchCriteria(const SearchRequest& request) const; + IRestClient::SearchResult OptimizedSearch(const SearchRequest& request) const; + IRestClient::SearchResult SearchInternal(const SearchRequest& request) const; + + // Check query params against source information and update if necessary. + virtual std::map GetValidatedQueryParams(const std::map& params) const; + + // Check search request against source information and get json search body. + virtual web::json::value GetValidatedSearchBody(const SearchRequest& searchRequest) const; + + virtual SearchResult GetSearchResult(const web::json::value& searchResponseObject) const; + virtual std::vector GetParsedManifests(const web::json::value& manifestsResponseObject) const; + + // Gets auth headers if source requires authentication for access. + virtual Http::HttpClientHelper::HttpRequestHeaders GetAuthHeaders() const; + + Http::HttpClientHelper::HttpRequestHeaders m_requiredRestApiHeaders; + + private: + std::string m_restApiUri; + utility::string_t m_searchEndpoint; + Http::HttpClientHelper m_httpClientHelper; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h index f4f3833f43..a85698e8c6 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer.h @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_0::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer - { - // Gets the manifest from the given json object received from a REST request - std::vector Deserialize(const web::json::value& responseJsonObject) const; - - // Gets the manifest from the given json Data field - std::vector DeserializeData(const web::json::value& dataJsonObject) const; - - // Deserializes the AppsAndFeaturesEntries node, returning the set of values below it. - virtual std::vector DeserializeAppsAndFeaturesEntries(const web::json::array& entries) const; - - // Deserializes the locale; requires that the PackageLocale be set to return an object. - virtual std::optional DeserializeLocale(const web::json::value& localeJsonObject) const; - - // Deserializes the locale; requires that the PackageLocale be set to return an object. - virtual std::optional DeserializeInstallationMetadata(const web::json::value& installationMetadataJsonObject) const; - - protected: - - template - inline void TryParseStringLocaleField(Manifest::ManifestLocalization& manifestLocale, const web::json::value& localeJsonObject, std::string_view localeJsonFieldName) const - { - auto value = AppInstaller::JSON::GetRawStringValueFromJsonNode(localeJsonObject, AppInstaller::JSON::GetUtilityString(localeJsonFieldName)); - - if (AppInstaller::JSON::IsValidNonEmptyStringValue(value)) - { - manifestLocale.Add(value.value()); - } - } - - virtual std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const; - - virtual std::map DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const; - - std::optional DeserializeDependency(const web::json::value& dependenciesJsonObject) const; - - virtual Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const; - - virtual Manifest::UpdateBehaviorEnum ConvertToUpdateBehavior(std::string_view in) const; - - std::vector ConvertToManifestStringArray(const std::vector& values) const; - - virtual Manifest::ManifestVer GetManifestVersion() const; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_0::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer + { + // Gets the manifest from the given json object received from a REST request + std::vector Deserialize(const web::json::value& responseJsonObject) const; + + // Gets the manifest from the given json Data field + std::vector DeserializeData(const web::json::value& dataJsonObject) const; + + // Deserializes the AppsAndFeaturesEntries node, returning the set of values below it. + virtual std::vector DeserializeAppsAndFeaturesEntries(const web::json::array& entries) const; + + // Deserializes the locale; requires that the PackageLocale be set to return an object. + virtual std::optional DeserializeLocale(const web::json::value& localeJsonObject) const; + + // Deserializes the locale; requires that the PackageLocale be set to return an object. + virtual std::optional DeserializeInstallationMetadata(const web::json::value& installationMetadataJsonObject) const; + + protected: + + template + inline void TryParseStringLocaleField(Manifest::ManifestLocalization& manifestLocale, const web::json::value& localeJsonObject, std::string_view localeJsonFieldName) const + { + auto value = AppInstaller::JSON::GetRawStringValueFromJsonNode(localeJsonObject, AppInstaller::JSON::GetUtilityString(localeJsonFieldName)); + + if (AppInstaller::JSON::IsValidNonEmptyStringValue(value)) + { + manifestLocale.Add(value.value()); + } + } + + virtual std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const; + + virtual std::map DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const; + + std::optional DeserializeDependency(const web::json::value& dependenciesJsonObject) const; + + virtual Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const; + + virtual Manifest::UpdateBehaviorEnum ConvertToUpdateBehavior(std::string_view in) const; + + std::vector ConvertToManifestStringArray(const std::vector& values) const; + + virtual Manifest::ManifestVer GetManifestVersion() const; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp index 7b0184210d..081f2f50e0 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/ManifestDeserializer_1_0.cpp @@ -1,569 +1,569 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_0/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include "ManifestDeserializer.h" -#include -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_0::Json -{ - namespace - { - // Manifest response constants specific to this deserializer - constexpr std::string_view PackageIdentifier = "PackageIdentifier"sv; - constexpr std::string_view PackageFamilyName = "PackageFamilyName"sv; - constexpr std::string_view ProductCode = "ProductCode"sv; - constexpr std::string_view Versions = "Versions"sv; - constexpr std::string_view PackageVersion = "PackageVersion"sv; - constexpr std::string_view Channel = "Channel"sv; - - // Locale - constexpr std::string_view DefaultLocale = "DefaultLocale"sv; - constexpr std::string_view Locales = "Locales"sv; - constexpr std::string_view PackageLocale = "PackageLocale"sv; - constexpr std::string_view Publisher = "Publisher"sv; - constexpr std::string_view PublisherUrl = "PublisherUrl"sv; - constexpr std::string_view PublisherSupportUrl = "PublisherSupportUrl"sv; - constexpr std::string_view PrivacyUrl = "PrivacyUrl"sv; - constexpr std::string_view Author = "Author"sv; - constexpr std::string_view PackageName = "PackageName"sv; - constexpr std::string_view PackageUrl = "PackageUrl"sv; - constexpr std::string_view License = "License"sv; - constexpr std::string_view LicenseUrl = "LicenseUrl"sv; - constexpr std::string_view Copyright = "Copyright"sv; - constexpr std::string_view CopyrightUrl = "CopyrightUrl"sv; - constexpr std::string_view ShortDescription = "ShortDescription"sv; - constexpr std::string_view Description = "Description"sv; - constexpr std::string_view Tags = "Tags"sv; - constexpr std::string_view Moniker = "Moniker"sv; - - // Installer - constexpr std::string_view Installers = "Installers"sv; - constexpr std::string_view InstallerIdentifier = "InstallerIdentifier"sv; - constexpr std::string_view InstallerSha256 = "InstallerSha256"sv; - constexpr std::string_view InstallerUrl = "InstallerUrl"sv; - constexpr std::string_view Architecture = "Architecture"sv; - constexpr std::string_view InstallerLocale = "InstallerLocale"sv; - constexpr std::string_view Platform = "Platform"sv; - constexpr std::string_view MinimumOSVersion = "MinimumOSVersion"sv; - constexpr std::string_view InstallerType = "InstallerType"sv; - constexpr std::string_view Scope = "Scope"sv; - constexpr std::string_view SignatureSha256 = "SignatureSha256"sv; - constexpr std::string_view InstallModes = "InstallModes"sv; - - // Installer switches - constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; - constexpr std::string_view Silent = "Silent"sv; - constexpr std::string_view SilentWithProgress = "SilentWithProgress"sv; - constexpr std::string_view Interactive = "Interactive"sv; - constexpr std::string_view InstallLocation = "InstallLocation"sv; - constexpr std::string_view Log = "Log"sv; - constexpr std::string_view Upgrade = "Upgrade"sv; - constexpr std::string_view Custom = "Custom"sv; - - constexpr std::string_view InstallerSuccessCodes = "InstallerSuccessCodes"sv; - constexpr std::string_view UpgradeBehavior = "UpgradeBehavior"sv; - constexpr std::string_view Commands = "Commands"sv; - constexpr std::string_view Protocols = "Protocols"sv; - constexpr std::string_view FileExtensions = "FileExtensions"sv; - - // Dependencies - constexpr std::string_view Dependencies = "Dependencies"sv; - constexpr std::string_view WindowsFeatures = "WindowsFeatures"sv; - constexpr std::string_view WindowsLibraries = "WindowsLibraries"sv; - constexpr std::string_view PackageDependencies = "PackageDependencies"sv; - constexpr std::string_view MinimumVersion = "MinimumVersion"sv; - constexpr std::string_view ExternalDependencies = "ExternalDependencies"sv; - - constexpr std::string_view Capabilities = "Capabilities"sv; - constexpr std::string_view RestrictedCapabilities = "RestrictedCapabilities"sv; - - void TryParseInstallerSwitchField( - std::map& installerSwitches, - InstallerSwitchType switchType, - const web::json::value& switchesJsonObject, - std::string_view switchJsonFieldName) - { - auto value = JSON::GetRawStringValueFromJsonNode(switchesJsonObject, JSON::GetUtilityString(switchJsonFieldName)); - - if (JSON::IsValidNonEmptyStringValue(value)) - { - installerSwitches[switchType] = value.value(); - } - } - } - - std::vector ManifestDeserializer::Deserialize(const web::json::value& responseJsonObject) const - { - if (responseJsonObject.is_null()) - { - AICLI_LOG(Repo, Error, << "Missing json object."); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - try - { - std::optional> manifestObject = - JSON::GetJsonValueFromNode(responseJsonObject, JSON::GetUtilityString(Data)); - - if (!manifestObject || manifestObject.value().get().is_null()) - { - AICLI_LOG(Repo, Verbose, << "No manifest results returned."); - return {}; - } - - return DeserializeData(manifestObject.value()); - } - catch (const wil::ResultException&) - { - throw; - } - catch (const std::exception& e) - { - AICLI_LOG(Repo, Error, << "Error encountered while deserializing manifest. Reason: " << e.what()); - } - catch (...) - { - AICLI_LOG(Repo, Error, << "Error encountered while deserializing manifest..."); - } - - // If we make it here, there was an exception above that we didn't throw. - // This will convert it into our standard error. - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - std::vector ManifestDeserializer::DeserializeData(const web::json::value& dataJsonObject) const - { - THROW_HR_IF(E_INVALIDARG, dataJsonObject.is_null()); - - std::vector manifests; - - std::optional id = JSON::GetRawStringValueFromJsonNode(dataJsonObject, JSON::GetUtilityString(PackageIdentifier)); - if (!JSON::IsValidNonEmptyStringValue(id)) - { - AICLI_LOG(Repo, Error, << "Missing package identifier."); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - std::optional> versions = JSON::GetRawJsonArrayFromJsonNode(dataJsonObject, JSON::GetUtilityString(Versions)); - if (!versions || versions.value().get().size() == 0) - { - AICLI_LOG(Repo, Error, << "Missing versions in package: " << id.value()); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - for (auto& versionItem : versions.value().get()) - { - Manifest::Manifest manifest; - - manifest.ManifestVersion = GetManifestVersion(); - - manifest.Id = id.value(); - - std::optional packageVersion = JSON::GetRawStringValueFromJsonNode(versionItem, JSON::GetUtilityString(PackageVersion)); - if (!JSON::IsValidNonEmptyStringValue(packageVersion)) - { - AICLI_LOG(Repo, Error, << "Missing package version in package: " << manifest.Id); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - manifest.Version = std::move(packageVersion.value()); - - manifest.Channel = JSON::GetRawStringValueFromJsonNode(versionItem, JSON::GetUtilityString(Channel)).value_or(""); - - // Default locale - std::optional> defaultLocale = - JSON::GetJsonValueFromNode(versionItem, JSON::GetUtilityString(DefaultLocale)); - if (!defaultLocale) - { - AICLI_LOG(Repo, Error, << "Missing default locale in package: " << manifest.Id); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - else - { - std::optional defaultLocaleObject = DeserializeLocale(defaultLocale.value().get()); - if (!defaultLocaleObject) - { - AICLI_LOG(Repo, Error, << "Missing default locale in package: " << manifest.Id); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - if (!defaultLocaleObject.value().Contains(Manifest::Localization::PackageName) || - !defaultLocaleObject.value().Contains(Manifest::Localization::Publisher) || - !defaultLocaleObject.value().Contains(Manifest::Localization::ShortDescription)) - { - AICLI_LOG(Repo, Error, << "Missing PackageName, Publisher or ShortDescription in default locale: " << manifest.Id); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - manifest.DefaultLocalization = std::move(defaultLocaleObject.value()); - - // Moniker is in Default locale - manifest.Moniker = JSON::GetRawStringValueFromJsonNode(defaultLocale.value().get(), JSON::GetUtilityString(Moniker)).value_or(""); - } - - // Installers - std::optional> installers = JSON::GetRawJsonArrayFromJsonNode(versionItem, JSON::GetUtilityString(Installers)); - if (!installers || installers.value().get().size() == 0) - { - AICLI_LOG(Repo, Error, << "Missing installers in package: " << manifest.Id); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - for (auto& installer : installers.value().get()) - { - std::optional installerObject = DeserializeInstaller(installer); - if (installerObject) - { - // Merge default switches after parsing. - auto defaultSwitches = Manifest::GetDefaultKnownSwitches(installerObject->EffectiveInstallerType()); - for (auto const& defaultSwitch : defaultSwitches) - { - if (installerObject->Switches.find(defaultSwitch.first) == installerObject->Switches.end()) - { - installerObject->Switches[defaultSwitch.first] = defaultSwitch.second; - } - } - - manifest.Installers.emplace_back(std::move(installerObject.value())); - } - } - - if (manifest.Installers.size() == 0) - { - AICLI_LOG(Repo, Error, << "Missing valid installers in package: " << manifest.Id); - THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); - } - - // Other locales - std::optional> locales = JSON::GetRawJsonArrayFromJsonNode(versionItem, JSON::GetUtilityString(Locales)); - if (locales) - { - for (auto& locale : locales.value().get()) - { - std::optional localeObject = DeserializeLocale(locale); - if (localeObject) - { - manifest.Localizations.emplace_back(std::move(localeObject.value())); - } - } - } - - manifests.emplace_back(std::move(manifest)); - } - - return manifests; - } - - std::vector ManifestDeserializer::DeserializeAppsAndFeaturesEntries(const web::json::array&) const - { - return {}; - } - - std::optional ManifestDeserializer::DeserializeInstallationMetadata(const web::json::value&) const - { - return {}; - } - - std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const - { - if (localeJsonObject.is_null()) - { - return {}; - } - - Manifest::ManifestLocalization locale; - std::optional packageLocale = JSON::GetRawStringValueFromJsonNode(localeJsonObject, JSON::GetUtilityString(PackageLocale)); - if (!JSON::IsValidNonEmptyStringValue(packageLocale)) - { - AICLI_LOG(Repo, Error, << "Missing package locale."); - return {}; - } - locale.Locale = std::move(packageLocale.value()); - - TryParseStringLocaleField(locale, localeJsonObject, PackageName); - TryParseStringLocaleField(locale, localeJsonObject, Publisher); - TryParseStringLocaleField(locale, localeJsonObject, ShortDescription); - TryParseStringLocaleField(locale, localeJsonObject, PublisherUrl); - TryParseStringLocaleField(locale, localeJsonObject, PublisherSupportUrl); - TryParseStringLocaleField(locale, localeJsonObject, PrivacyUrl); - TryParseStringLocaleField(locale, localeJsonObject, Author); - TryParseStringLocaleField(locale, localeJsonObject, PackageUrl); - TryParseStringLocaleField(locale, localeJsonObject, License); - TryParseStringLocaleField(locale, localeJsonObject, LicenseUrl); - TryParseStringLocaleField(locale, localeJsonObject, Copyright); - TryParseStringLocaleField(locale, localeJsonObject, CopyrightUrl); - TryParseStringLocaleField(locale, localeJsonObject, Description); - - auto tags = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Tags))); - if (!tags.empty()) - { - locale.Add(tags); - } - - return locale; - } - - std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const - { - if (installerJsonObject.is_null()) - { - return {}; - } - - Manifest::ManifestInstaller installer; - - installer.Url = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerUrl)).value_or(""); - - std::optional sha256 = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerSha256)); - if (JSON::IsValidNonEmptyStringValue(sha256)) - { - installer.Sha256 = Utility::SHA256::ConvertToBytes(sha256.value()); - } - - std::optional arch = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(Architecture)); - if (!JSON::IsValidNonEmptyStringValue(arch)) - { - AICLI_LOG(Repo, Error, << "Missing installer architecture."); - return {}; - } - installer.Arch = Utility::ConvertToArchitectureEnum(arch.value()); - - std::optional installerType = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerType)); - if (!JSON::IsValidNonEmptyStringValue(installerType)) - { - AICLI_LOG(Repo, Error, << "Missing installer type."); - return {}; - } - installer.BaseInstallerType = ConvertToInstallerType(installerType.value()); - installer.Locale = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerLocale)).value_or(""); - - // platform - std::optional> platforms = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Platform)); - if (platforms) - { - for (auto& platform : platforms.value().get()) - { - std::optional platformValue = JSON::GetRawStringValueFromJsonValue(platform); - if (platformValue) - { - installer.Platform.emplace_back(Manifest::ConvertToPlatformEnum(platformValue.value())); - } - } - } - - installer.MinOSVersion = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(MinimumOSVersion)).value_or(""); - std::optional scope = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(Scope)); - if (scope) - { - installer.Scope = Manifest::ConvertToScopeEnum(scope.value()); - } - - std::optional signatureSha256 = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(SignatureSha256)); - if (signatureSha256) - { - installer.SignatureSha256 = Utility::SHA256::ConvertToBytes(signatureSha256.value()); - } - - // Install modes - std::optional> installModes = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallModes)); - if (installModes) - { - for (auto& mode : installModes.value().get()) - { - std::optional modeObject = JSON::GetRawStringValueFromJsonValue(mode); - if (modeObject) - { - installer.InstallModes.emplace_back(Manifest::ConvertToInstallModeEnum(modeObject.value())); - } - } - } - - // Installer Switches - std::optional> switches = - JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(InstallerSwitches)); - if (switches) - { - installer.Switches = DeserializeInstallerSwitches(switches.value().get()); - } - - // Installer SuccessCodes - std::optional> installSuccessCodes = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerSuccessCodes)); - if (installSuccessCodes) - { - for (auto& code : installSuccessCodes.value().get()) - { - std::optional codeValue = JSON::GetRawIntValueFromJsonValue(code); - if (codeValue) - { - installer.InstallerSuccessCodes.emplace_back(std::move(codeValue.value())); - } - } - } - - std::optional updateBehavior = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(UpgradeBehavior)); - if (updateBehavior) - { - installer.UpdateBehavior = ConvertToUpdateBehavior(updateBehavior.value()); - } - - installer.Commands = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Commands))); - installer.Protocols = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Protocols))); - installer.FileExtensions = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(FileExtensions))); - - // Dependencies - std::optional> dependenciesObject = - JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(Dependencies)); - if (dependenciesObject) - { - std::optional dependencyList = DeserializeDependency(dependenciesObject.value().get()); - if (dependencyList) - { - installer.Dependencies = std::move(dependencyList.value()); - } - } - - installer.PackageFamilyName = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(PackageFamilyName)).value_or(""); - installer.ProductCode = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ProductCode)).value_or(""); - installer.Capabilities = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Capabilities))); - installer.RestrictedCapabilities = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(RestrictedCapabilities))); - - return installer; - } - - std::map ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const - { - std::map installerSwitches; - - TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Silent, installerSwitchesJsonObject, Silent); - TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::SilentWithProgress, installerSwitchesJsonObject, SilentWithProgress); - TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Interactive, installerSwitchesJsonObject, Interactive); - TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::InstallLocation, installerSwitchesJsonObject, InstallLocation); - TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Log, installerSwitchesJsonObject, Log); - TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Update, installerSwitchesJsonObject, Upgrade); - TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Custom, installerSwitchesJsonObject, Custom); - - return installerSwitches; - } - - std::optional ManifestDeserializer::DeserializeDependency(const web::json::value& dependenciesObject) const - { - if (dependenciesObject.is_null()) - { - return {}; - } - - Manifest::DependencyList dependencyList; - - auto wfIds = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(WindowsFeatures))); - for (auto&& id : wfIds) - { - dependencyList.Add(Dependency(DependencyType::WindowsFeature, std::move(id))); - }; - - const auto& wlIds = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(WindowsLibraries))); - for (auto id : wlIds) - { - dependencyList.Add(Dependency(DependencyType::WindowsLibrary, id)); - }; - - const auto& extIds = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(ExternalDependencies))); - for (auto id : extIds) - { - dependencyList.Add(Dependency(DependencyType::External, id)); - }; - - // Package Dependencies - std::optional> packageDependencies = JSON::GetRawJsonArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(PackageDependencies)); - if (packageDependencies) - { - for (auto& packageDependency : packageDependencies.value().get()) - { - std::optional id = JSON::GetRawStringValueFromJsonNode(packageDependency, JSON::GetUtilityString(PackageIdentifier)); - if (id) - { - Dependency pkg{ DependencyType::Package, std::move(id.value()) , JSON::GetRawStringValueFromJsonNode(packageDependency, JSON::GetUtilityString(MinimumVersion)).value_or("") }; - dependencyList.Add(std::move(pkg)); - } - } - } - - return dependencyList; - } - - Manifest::InstallerTypeEnum ManifestDeserializer::ConvertToInstallerType(std::string_view in) const - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "inno") - { - return InstallerTypeEnum::Inno; - } - else if (inStrLower == "wix") - { - return InstallerTypeEnum::Wix; - } - else if (inStrLower == "msi") - { - return InstallerTypeEnum::Msi; - } - else if (inStrLower == "nullsoft") - { - return InstallerTypeEnum::Nullsoft; - } - else if (inStrLower == "zip") - { - return InstallerTypeEnum::Zip; - } - else if (inStrLower == "appx" || inStrLower == "msix") - { - return InstallerTypeEnum::Msix; - } - else if (inStrLower == "exe") - { - return InstallerTypeEnum::Exe; - } - else if (inStrLower == "burn") - { - return InstallerTypeEnum::Burn; - } - - return InstallerTypeEnum::Unknown; - } - - Manifest::UpdateBehaviorEnum ManifestDeserializer::ConvertToUpdateBehavior(std::string_view in) const - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "install") - { - return UpdateBehaviorEnum::Install; - } - else if (inStrLower == "uninstallprevious") - { - return UpdateBehaviorEnum::UninstallPrevious; - } - - return UpdateBehaviorEnum::Unknown; - } - - std::vector ManifestDeserializer::ConvertToManifestStringArray(const std::vector& values) const - { - std::vector result; - for (const auto& value : values) - { - result.emplace_back(value); - } - - return result; - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_0/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include "ManifestDeserializer.h" +#include +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_0::Json +{ + namespace + { + // Manifest response constants specific to this deserializer + constexpr std::string_view PackageIdentifier = "PackageIdentifier"sv; + constexpr std::string_view PackageFamilyName = "PackageFamilyName"sv; + constexpr std::string_view ProductCode = "ProductCode"sv; + constexpr std::string_view Versions = "Versions"sv; + constexpr std::string_view PackageVersion = "PackageVersion"sv; + constexpr std::string_view Channel = "Channel"sv; + + // Locale + constexpr std::string_view DefaultLocale = "DefaultLocale"sv; + constexpr std::string_view Locales = "Locales"sv; + constexpr std::string_view PackageLocale = "PackageLocale"sv; + constexpr std::string_view Publisher = "Publisher"sv; + constexpr std::string_view PublisherUrl = "PublisherUrl"sv; + constexpr std::string_view PublisherSupportUrl = "PublisherSupportUrl"sv; + constexpr std::string_view PrivacyUrl = "PrivacyUrl"sv; + constexpr std::string_view Author = "Author"sv; + constexpr std::string_view PackageName = "PackageName"sv; + constexpr std::string_view PackageUrl = "PackageUrl"sv; + constexpr std::string_view License = "License"sv; + constexpr std::string_view LicenseUrl = "LicenseUrl"sv; + constexpr std::string_view Copyright = "Copyright"sv; + constexpr std::string_view CopyrightUrl = "CopyrightUrl"sv; + constexpr std::string_view ShortDescription = "ShortDescription"sv; + constexpr std::string_view Description = "Description"sv; + constexpr std::string_view Tags = "Tags"sv; + constexpr std::string_view Moniker = "Moniker"sv; + + // Installer + constexpr std::string_view Installers = "Installers"sv; + constexpr std::string_view InstallerIdentifier = "InstallerIdentifier"sv; + constexpr std::string_view InstallerSha256 = "InstallerSha256"sv; + constexpr std::string_view InstallerUrl = "InstallerUrl"sv; + constexpr std::string_view Architecture = "Architecture"sv; + constexpr std::string_view InstallerLocale = "InstallerLocale"sv; + constexpr std::string_view Platform = "Platform"sv; + constexpr std::string_view MinimumOSVersion = "MinimumOSVersion"sv; + constexpr std::string_view InstallerType = "InstallerType"sv; + constexpr std::string_view Scope = "Scope"sv; + constexpr std::string_view SignatureSha256 = "SignatureSha256"sv; + constexpr std::string_view InstallModes = "InstallModes"sv; + + // Installer switches + constexpr std::string_view InstallerSwitches = "InstallerSwitches"sv; + constexpr std::string_view Silent = "Silent"sv; + constexpr std::string_view SilentWithProgress = "SilentWithProgress"sv; + constexpr std::string_view Interactive = "Interactive"sv; + constexpr std::string_view InstallLocation = "InstallLocation"sv; + constexpr std::string_view Log = "Log"sv; + constexpr std::string_view Upgrade = "Upgrade"sv; + constexpr std::string_view Custom = "Custom"sv; + + constexpr std::string_view InstallerSuccessCodes = "InstallerSuccessCodes"sv; + constexpr std::string_view UpgradeBehavior = "UpgradeBehavior"sv; + constexpr std::string_view Commands = "Commands"sv; + constexpr std::string_view Protocols = "Protocols"sv; + constexpr std::string_view FileExtensions = "FileExtensions"sv; + + // Dependencies + constexpr std::string_view Dependencies = "Dependencies"sv; + constexpr std::string_view WindowsFeatures = "WindowsFeatures"sv; + constexpr std::string_view WindowsLibraries = "WindowsLibraries"sv; + constexpr std::string_view PackageDependencies = "PackageDependencies"sv; + constexpr std::string_view MinimumVersion = "MinimumVersion"sv; + constexpr std::string_view ExternalDependencies = "ExternalDependencies"sv; + + constexpr std::string_view Capabilities = "Capabilities"sv; + constexpr std::string_view RestrictedCapabilities = "RestrictedCapabilities"sv; + + void TryParseInstallerSwitchField( + std::map& installerSwitches, + InstallerSwitchType switchType, + const web::json::value& switchesJsonObject, + std::string_view switchJsonFieldName) + { + auto value = JSON::GetRawStringValueFromJsonNode(switchesJsonObject, JSON::GetUtilityString(switchJsonFieldName)); + + if (JSON::IsValidNonEmptyStringValue(value)) + { + installerSwitches[switchType] = value.value(); + } + } + } + + std::vector ManifestDeserializer::Deserialize(const web::json::value& responseJsonObject) const + { + if (responseJsonObject.is_null()) + { + AICLI_LOG(Repo, Error, << "Missing json object."); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + try + { + std::optional> manifestObject = + JSON::GetJsonValueFromNode(responseJsonObject, JSON::GetUtilityString(Data)); + + if (!manifestObject || manifestObject.value().get().is_null()) + { + AICLI_LOG(Repo, Verbose, << "No manifest results returned."); + return {}; + } + + return DeserializeData(manifestObject.value()); + } + catch (const wil::ResultException&) + { + throw; + } + catch (const std::exception& e) + { + AICLI_LOG(Repo, Error, << "Error encountered while deserializing manifest. Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(Repo, Error, << "Error encountered while deserializing manifest..."); + } + + // If we make it here, there was an exception above that we didn't throw. + // This will convert it into our standard error. + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + std::vector ManifestDeserializer::DeserializeData(const web::json::value& dataJsonObject) const + { + THROW_HR_IF(E_INVALIDARG, dataJsonObject.is_null()); + + std::vector manifests; + + std::optional id = JSON::GetRawStringValueFromJsonNode(dataJsonObject, JSON::GetUtilityString(PackageIdentifier)); + if (!JSON::IsValidNonEmptyStringValue(id)) + { + AICLI_LOG(Repo, Error, << "Missing package identifier."); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + std::optional> versions = JSON::GetRawJsonArrayFromJsonNode(dataJsonObject, JSON::GetUtilityString(Versions)); + if (!versions || versions.value().get().size() == 0) + { + AICLI_LOG(Repo, Error, << "Missing versions in package: " << id.value()); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + for (auto& versionItem : versions.value().get()) + { + Manifest::Manifest manifest; + + manifest.ManifestVersion = GetManifestVersion(); + + manifest.Id = id.value(); + + std::optional packageVersion = JSON::GetRawStringValueFromJsonNode(versionItem, JSON::GetUtilityString(PackageVersion)); + if (!JSON::IsValidNonEmptyStringValue(packageVersion)) + { + AICLI_LOG(Repo, Error, << "Missing package version in package: " << manifest.Id); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + manifest.Version = std::move(packageVersion.value()); + + manifest.Channel = JSON::GetRawStringValueFromJsonNode(versionItem, JSON::GetUtilityString(Channel)).value_or(""); + + // Default locale + std::optional> defaultLocale = + JSON::GetJsonValueFromNode(versionItem, JSON::GetUtilityString(DefaultLocale)); + if (!defaultLocale) + { + AICLI_LOG(Repo, Error, << "Missing default locale in package: " << manifest.Id); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + else + { + std::optional defaultLocaleObject = DeserializeLocale(defaultLocale.value().get()); + if (!defaultLocaleObject) + { + AICLI_LOG(Repo, Error, << "Missing default locale in package: " << manifest.Id); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + if (!defaultLocaleObject.value().Contains(Manifest::Localization::PackageName) || + !defaultLocaleObject.value().Contains(Manifest::Localization::Publisher) || + !defaultLocaleObject.value().Contains(Manifest::Localization::ShortDescription)) + { + AICLI_LOG(Repo, Error, << "Missing PackageName, Publisher or ShortDescription in default locale: " << manifest.Id); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + manifest.DefaultLocalization = std::move(defaultLocaleObject.value()); + + // Moniker is in Default locale + manifest.Moniker = JSON::GetRawStringValueFromJsonNode(defaultLocale.value().get(), JSON::GetUtilityString(Moniker)).value_or(""); + } + + // Installers + std::optional> installers = JSON::GetRawJsonArrayFromJsonNode(versionItem, JSON::GetUtilityString(Installers)); + if (!installers || installers.value().get().size() == 0) + { + AICLI_LOG(Repo, Error, << "Missing installers in package: " << manifest.Id); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + for (auto& installer : installers.value().get()) + { + std::optional installerObject = DeserializeInstaller(installer); + if (installerObject) + { + // Merge default switches after parsing. + auto defaultSwitches = Manifest::GetDefaultKnownSwitches(installerObject->EffectiveInstallerType()); + for (auto const& defaultSwitch : defaultSwitches) + { + if (installerObject->Switches.find(defaultSwitch.first) == installerObject->Switches.end()) + { + installerObject->Switches[defaultSwitch.first] = defaultSwitch.second; + } + } + + manifest.Installers.emplace_back(std::move(installerObject.value())); + } + } + + if (manifest.Installers.size() == 0) + { + AICLI_LOG(Repo, Error, << "Missing valid installers in package: " << manifest.Id); + THROW_HR(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA); + } + + // Other locales + std::optional> locales = JSON::GetRawJsonArrayFromJsonNode(versionItem, JSON::GetUtilityString(Locales)); + if (locales) + { + for (auto& locale : locales.value().get()) + { + std::optional localeObject = DeserializeLocale(locale); + if (localeObject) + { + manifest.Localizations.emplace_back(std::move(localeObject.value())); + } + } + } + + manifests.emplace_back(std::move(manifest)); + } + + return manifests; + } + + std::vector ManifestDeserializer::DeserializeAppsAndFeaturesEntries(const web::json::array&) const + { + return {}; + } + + std::optional ManifestDeserializer::DeserializeInstallationMetadata(const web::json::value&) const + { + return {}; + } + + std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const + { + if (localeJsonObject.is_null()) + { + return {}; + } + + Manifest::ManifestLocalization locale; + std::optional packageLocale = JSON::GetRawStringValueFromJsonNode(localeJsonObject, JSON::GetUtilityString(PackageLocale)); + if (!JSON::IsValidNonEmptyStringValue(packageLocale)) + { + AICLI_LOG(Repo, Error, << "Missing package locale."); + return {}; + } + locale.Locale = std::move(packageLocale.value()); + + TryParseStringLocaleField(locale, localeJsonObject, PackageName); + TryParseStringLocaleField(locale, localeJsonObject, Publisher); + TryParseStringLocaleField(locale, localeJsonObject, ShortDescription); + TryParseStringLocaleField(locale, localeJsonObject, PublisherUrl); + TryParseStringLocaleField(locale, localeJsonObject, PublisherSupportUrl); + TryParseStringLocaleField(locale, localeJsonObject, PrivacyUrl); + TryParseStringLocaleField(locale, localeJsonObject, Author); + TryParseStringLocaleField(locale, localeJsonObject, PackageUrl); + TryParseStringLocaleField(locale, localeJsonObject, License); + TryParseStringLocaleField(locale, localeJsonObject, LicenseUrl); + TryParseStringLocaleField(locale, localeJsonObject, Copyright); + TryParseStringLocaleField(locale, localeJsonObject, CopyrightUrl); + TryParseStringLocaleField(locale, localeJsonObject, Description); + + auto tags = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Tags))); + if (!tags.empty()) + { + locale.Add(tags); + } + + return locale; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + if (installerJsonObject.is_null()) + { + return {}; + } + + Manifest::ManifestInstaller installer; + + installer.Url = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerUrl)).value_or(""); + + std::optional sha256 = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerSha256)); + if (JSON::IsValidNonEmptyStringValue(sha256)) + { + installer.Sha256 = Utility::SHA256::ConvertToBytes(sha256.value()); + } + + std::optional arch = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(Architecture)); + if (!JSON::IsValidNonEmptyStringValue(arch)) + { + AICLI_LOG(Repo, Error, << "Missing installer architecture."); + return {}; + } + installer.Arch = Utility::ConvertToArchitectureEnum(arch.value()); + + std::optional installerType = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerType)); + if (!JSON::IsValidNonEmptyStringValue(installerType)) + { + AICLI_LOG(Repo, Error, << "Missing installer type."); + return {}; + } + installer.BaseInstallerType = ConvertToInstallerType(installerType.value()); + installer.Locale = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerLocale)).value_or(""); + + // platform + std::optional> platforms = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Platform)); + if (platforms) + { + for (auto& platform : platforms.value().get()) + { + std::optional platformValue = JSON::GetRawStringValueFromJsonValue(platform); + if (platformValue) + { + installer.Platform.emplace_back(Manifest::ConvertToPlatformEnum(platformValue.value())); + } + } + } + + installer.MinOSVersion = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(MinimumOSVersion)).value_or(""); + std::optional scope = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(Scope)); + if (scope) + { + installer.Scope = Manifest::ConvertToScopeEnum(scope.value()); + } + + std::optional signatureSha256 = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(SignatureSha256)); + if (signatureSha256) + { + installer.SignatureSha256 = Utility::SHA256::ConvertToBytes(signatureSha256.value()); + } + + // Install modes + std::optional> installModes = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallModes)); + if (installModes) + { + for (auto& mode : installModes.value().get()) + { + std::optional modeObject = JSON::GetRawStringValueFromJsonValue(mode); + if (modeObject) + { + installer.InstallModes.emplace_back(Manifest::ConvertToInstallModeEnum(modeObject.value())); + } + } + } + + // Installer Switches + std::optional> switches = + JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(InstallerSwitches)); + if (switches) + { + installer.Switches = DeserializeInstallerSwitches(switches.value().get()); + } + + // Installer SuccessCodes + std::optional> installSuccessCodes = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerSuccessCodes)); + if (installSuccessCodes) + { + for (auto& code : installSuccessCodes.value().get()) + { + std::optional codeValue = JSON::GetRawIntValueFromJsonValue(code); + if (codeValue) + { + installer.InstallerSuccessCodes.emplace_back(std::move(codeValue.value())); + } + } + } + + std::optional updateBehavior = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(UpgradeBehavior)); + if (updateBehavior) + { + installer.UpdateBehavior = ConvertToUpdateBehavior(updateBehavior.value()); + } + + installer.Commands = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Commands))); + installer.Protocols = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Protocols))); + installer.FileExtensions = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(FileExtensions))); + + // Dependencies + std::optional> dependenciesObject = + JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(Dependencies)); + if (dependenciesObject) + { + std::optional dependencyList = DeserializeDependency(dependenciesObject.value().get()); + if (dependencyList) + { + installer.Dependencies = std::move(dependencyList.value()); + } + } + + installer.PackageFamilyName = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(PackageFamilyName)).value_or(""); + installer.ProductCode = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ProductCode)).value_or(""); + installer.Capabilities = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(Capabilities))); + installer.RestrictedCapabilities = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(RestrictedCapabilities))); + + return installer; + } + + std::map ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const + { + std::map installerSwitches; + + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Silent, installerSwitchesJsonObject, Silent); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::SilentWithProgress, installerSwitchesJsonObject, SilentWithProgress); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Interactive, installerSwitchesJsonObject, Interactive); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::InstallLocation, installerSwitchesJsonObject, InstallLocation); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Log, installerSwitchesJsonObject, Log); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Update, installerSwitchesJsonObject, Upgrade); + TryParseInstallerSwitchField(installerSwitches, InstallerSwitchType::Custom, installerSwitchesJsonObject, Custom); + + return installerSwitches; + } + + std::optional ManifestDeserializer::DeserializeDependency(const web::json::value& dependenciesObject) const + { + if (dependenciesObject.is_null()) + { + return {}; + } + + Manifest::DependencyList dependencyList; + + auto wfIds = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(WindowsFeatures))); + for (auto&& id : wfIds) + { + dependencyList.Add(Dependency(DependencyType::WindowsFeature, std::move(id))); + }; + + const auto& wlIds = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(WindowsLibraries))); + for (auto id : wlIds) + { + dependencyList.Add(Dependency(DependencyType::WindowsLibrary, id)); + }; + + const auto& extIds = ConvertToManifestStringArray(JSON::GetRawStringArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(ExternalDependencies))); + for (auto id : extIds) + { + dependencyList.Add(Dependency(DependencyType::External, id)); + }; + + // Package Dependencies + std::optional> packageDependencies = JSON::GetRawJsonArrayFromJsonNode(dependenciesObject, JSON::GetUtilityString(PackageDependencies)); + if (packageDependencies) + { + for (auto& packageDependency : packageDependencies.value().get()) + { + std::optional id = JSON::GetRawStringValueFromJsonNode(packageDependency, JSON::GetUtilityString(PackageIdentifier)); + if (id) + { + Dependency pkg{ DependencyType::Package, std::move(id.value()) , JSON::GetRawStringValueFromJsonNode(packageDependency, JSON::GetUtilityString(MinimumVersion)).value_or("") }; + dependencyList.Add(std::move(pkg)); + } + } + } + + return dependencyList; + } + + Manifest::InstallerTypeEnum ManifestDeserializer::ConvertToInstallerType(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "inno") + { + return InstallerTypeEnum::Inno; + } + else if (inStrLower == "wix") + { + return InstallerTypeEnum::Wix; + } + else if (inStrLower == "msi") + { + return InstallerTypeEnum::Msi; + } + else if (inStrLower == "nullsoft") + { + return InstallerTypeEnum::Nullsoft; + } + else if (inStrLower == "zip") + { + return InstallerTypeEnum::Zip; + } + else if (inStrLower == "appx" || inStrLower == "msix") + { + return InstallerTypeEnum::Msix; + } + else if (inStrLower == "exe") + { + return InstallerTypeEnum::Exe; + } + else if (inStrLower == "burn") + { + return InstallerTypeEnum::Burn; + } + + return InstallerTypeEnum::Unknown; + } + + Manifest::UpdateBehaviorEnum ManifestDeserializer::ConvertToUpdateBehavior(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "install") + { + return UpdateBehaviorEnum::Install; + } + else if (inStrLower == "uninstallprevious") + { + return UpdateBehaviorEnum::UninstallPrevious; + } + + return UpdateBehaviorEnum::Unknown; + } + + std::vector ManifestDeserializer::ConvertToManifestStringArray(const std::vector& values) const + { + std::vector result; + for (const auto& value : values) + { + result.emplace_back(value); + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer.h index 307a134015..e36b834597 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Rest/Schema/IRestClient.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_0::Json -{ - // Search Result Serializer. - struct SearchRequestSerializer - { - web::json::value Serialize(const SearchRequest& searchRequest) const; - - protected: - std::optional SerializeSearchRequest(const SearchRequest& searchRequest) const; - - std::optional GetRequestMatchJsonObject(const AppInstaller::Repository::RequestMatch& requestMatch) const; - - std::optional GetPackageMatchFilterJsonObject(const PackageMatchFilter& packageMatchFilter) const; - - virtual std::optional ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Rest/Schema/IRestClient.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_0::Json +{ + // Search Result Serializer. + struct SearchRequestSerializer + { + web::json::value Serialize(const SearchRequest& searchRequest) const; + + protected: + std::optional SerializeSearchRequest(const SearchRequest& searchRequest) const; + + std::optional GetRequestMatchJsonObject(const AppInstaller::Repository::RequestMatch& requestMatch) const; + + std::optional GetPackageMatchFilterJsonObject(const PackageMatchFilter& packageMatchFilter) const; + + virtual std::optional ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer_1_0.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer_1_0.cpp index 094599edc5..a44ce132c2 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchRequestSerializer_1_0.cpp @@ -1,200 +1,200 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/IRestClient.h" -#include "SearchRequestSerializer.h" -#include -#include "Rest/Schema/CommonRestConstants.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_0::Json -{ - namespace - { - // Search request constants - constexpr std::string_view Query = "Query"sv; - constexpr std::string_view Filters = "Filters"sv; - constexpr std::string_view Inclusions = "Inclusions"sv; - constexpr std::string_view MaximumResults = "MaximumResults"sv; - constexpr std::string_view RequestMatch = "RequestMatch"sv; - constexpr std::string_view KeyWord = "KeyWord"sv; - constexpr std::string_view MatchType = "MatchType"sv; - constexpr std::string_view PackageMatchField = "PackageMatchField"sv; - constexpr std::string_view FetchAllManifests = "FetchAllManifests"sv; - - std::optional ConvertMatchTypeToString(AppInstaller::Repository::MatchType type) - { - // Match types supported by Rest API schema. - switch (type) - { - case MatchType::Exact: - return "Exact"sv; - case MatchType::CaseInsensitive: - return "CaseInsensitive"sv; - case MatchType::StartsWith: - return "StartsWith"sv; - case MatchType::Substring: - return "Substring"sv; - case MatchType::Wildcard: - return "Wildcard"sv; - case MatchType::Fuzzy: - return "Fuzzy"sv; - case MatchType::FuzzySubstring: - return "FuzzySubstring"sv; - } - - return {}; - } - } - - web::json::value SearchRequestSerializer::Serialize(const SearchRequest& searchRequest) const - { - std::optional result = SerializeSearchRequest(searchRequest); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR, !result); - - return result.value(); - } - - std::optional SearchRequestSerializer::SerializeSearchRequest(const SearchRequest& searchRequest) const - { - try - { - web::json::value json_body; - if (searchRequest.MaximumResults > 0) - { - json_body[JSON::GetUtilityString(MaximumResults)] = searchRequest.MaximumResults; - } - - if (searchRequest.IsForEverything()) - { - json_body[JSON::GetUtilityString(FetchAllManifests)] = web::json::value::boolean(true); - return json_body; - } - - if (searchRequest.Query) - { - auto& requestMatch = searchRequest.Query.value(); - web::json::value requestMatchObject = web::json::value::object(); - std::optional requestMatchJson = GetRequestMatchJsonObject(requestMatch); - if (requestMatchJson) - { - json_body[JSON::GetUtilityString(Query)] = std::move(requestMatchJson.value()); - } - } - - if (!searchRequest.Filters.empty()) - { - web::json::value filters = web::json::value::array(); - - int i = 0; - for (auto& filter : searchRequest.Filters) - { - std::optional jsonObject = GetPackageMatchFilterJsonObject(filter); - - if (jsonObject) - { - filters[i++] = std::move(jsonObject.value()); - } - } - - json_body[JSON::GetUtilityString(Filters)] = filters; - } - - if (!searchRequest.Inclusions.empty()) - { - web::json::value inclusions = web::json::value::array(); - - int i = 0; - for (auto& inclusion : searchRequest.Inclusions) - { - std::optional jsonObject = GetPackageMatchFilterJsonObject(inclusion); - - if (jsonObject) - { - inclusions[i++] = std::move(jsonObject.value()); - } - } - - json_body[JSON::GetUtilityString(Inclusions)] = inclusions; - } - - return json_body; - } - catch (const std::exception& e) - { - AICLI_LOG(Repo, Error, << "Error occurred while serializing search request. Reason: " << e.what()); - } - catch (...) - { - AICLI_LOG(Repo, Error, << "Error occurred while serializing search request"); - } - - return {}; - } - - std::optional SearchRequestSerializer::GetPackageMatchFilterJsonObject(const PackageMatchFilter& packageMatchFilter) const - { - web::json::value filter = web::json::value::object(); - std::optional matchField = ConvertPackageMatchFieldToString(packageMatchFilter.Field); - - if (!matchField) - { - AICLI_LOG(Repo, Warning, << "Skipping unsupported package match field: " << packageMatchFilter.Field); - return {}; - } - - filter[JSON::GetUtilityString(PackageMatchField)] = web::json::value::string(JSON::GetUtilityString(matchField.value())); - std::optional requestMatchJson = GetRequestMatchJsonObject(packageMatchFilter); - - if (!requestMatchJson) - { - AICLI_LOG(Repo, Warning, << "Skipping unsupported request match object."); - return {}; - } - - filter[JSON::GetUtilityString(RequestMatch)] = std::move(requestMatchJson.value()); - return filter; - } - - std::optional SearchRequestSerializer::GetRequestMatchJsonObject(const AppInstaller::Repository::RequestMatch& requestMatch) const - { - web::json::value match = web::json::value::object(); - match[JSON::GetUtilityString(KeyWord)] = web::json::value::string(JSON::GetUtilityString(requestMatch.Value)); - - std::optional matchType = ConvertMatchTypeToString(requestMatch.Type); - if (!matchType) - { - AICLI_LOG(Repo, Warning, << "Skipping unsupported match type: " << requestMatch.Type); - return {}; - } - - match[JSON::GetUtilityString(MatchType)] = web::json::value::string(JSON::GetUtilityString(matchType.value())); - return match; - } - - std::optional SearchRequestSerializer::ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const - { - // Match fields supported by Rest API schema. - switch (field) - { - case PackageMatchField::Command: - return "Command"sv; - case PackageMatchField::Id: - return "PackageIdentifier"sv; - case PackageMatchField::Moniker: - return "Moniker"sv; - case PackageMatchField::Name: - return "PackageName"sv; - case PackageMatchField::Tag: - return "Tag"sv; - case PackageMatchField::PackageFamilyName: - return "PackageFamilyName"sv; - case PackageMatchField::ProductCode: - return "ProductCode"sv; - case PackageMatchField::NormalizedNameAndPublisher: - return "NormalizedPackageNameAndPublisher"sv; - } - - return {}; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/IRestClient.h" +#include "SearchRequestSerializer.h" +#include +#include "Rest/Schema/CommonRestConstants.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_0::Json +{ + namespace + { + // Search request constants + constexpr std::string_view Query = "Query"sv; + constexpr std::string_view Filters = "Filters"sv; + constexpr std::string_view Inclusions = "Inclusions"sv; + constexpr std::string_view MaximumResults = "MaximumResults"sv; + constexpr std::string_view RequestMatch = "RequestMatch"sv; + constexpr std::string_view KeyWord = "KeyWord"sv; + constexpr std::string_view MatchType = "MatchType"sv; + constexpr std::string_view PackageMatchField = "PackageMatchField"sv; + constexpr std::string_view FetchAllManifests = "FetchAllManifests"sv; + + std::optional ConvertMatchTypeToString(AppInstaller::Repository::MatchType type) + { + // Match types supported by Rest API schema. + switch (type) + { + case MatchType::Exact: + return "Exact"sv; + case MatchType::CaseInsensitive: + return "CaseInsensitive"sv; + case MatchType::StartsWith: + return "StartsWith"sv; + case MatchType::Substring: + return "Substring"sv; + case MatchType::Wildcard: + return "Wildcard"sv; + case MatchType::Fuzzy: + return "Fuzzy"sv; + case MatchType::FuzzySubstring: + return "FuzzySubstring"sv; + } + + return {}; + } + } + + web::json::value SearchRequestSerializer::Serialize(const SearchRequest& searchRequest) const + { + std::optional result = SerializeSearchRequest(searchRequest); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR, !result); + + return result.value(); + } + + std::optional SearchRequestSerializer::SerializeSearchRequest(const SearchRequest& searchRequest) const + { + try + { + web::json::value json_body; + if (searchRequest.MaximumResults > 0) + { + json_body[JSON::GetUtilityString(MaximumResults)] = searchRequest.MaximumResults; + } + + if (searchRequest.IsForEverything()) + { + json_body[JSON::GetUtilityString(FetchAllManifests)] = web::json::value::boolean(true); + return json_body; + } + + if (searchRequest.Query) + { + auto& requestMatch = searchRequest.Query.value(); + web::json::value requestMatchObject = web::json::value::object(); + std::optional requestMatchJson = GetRequestMatchJsonObject(requestMatch); + if (requestMatchJson) + { + json_body[JSON::GetUtilityString(Query)] = std::move(requestMatchJson.value()); + } + } + + if (!searchRequest.Filters.empty()) + { + web::json::value filters = web::json::value::array(); + + int i = 0; + for (auto& filter : searchRequest.Filters) + { + std::optional jsonObject = GetPackageMatchFilterJsonObject(filter); + + if (jsonObject) + { + filters[i++] = std::move(jsonObject.value()); + } + } + + json_body[JSON::GetUtilityString(Filters)] = filters; + } + + if (!searchRequest.Inclusions.empty()) + { + web::json::value inclusions = web::json::value::array(); + + int i = 0; + for (auto& inclusion : searchRequest.Inclusions) + { + std::optional jsonObject = GetPackageMatchFilterJsonObject(inclusion); + + if (jsonObject) + { + inclusions[i++] = std::move(jsonObject.value()); + } + } + + json_body[JSON::GetUtilityString(Inclusions)] = inclusions; + } + + return json_body; + } + catch (const std::exception& e) + { + AICLI_LOG(Repo, Error, << "Error occurred while serializing search request. Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(Repo, Error, << "Error occurred while serializing search request"); + } + + return {}; + } + + std::optional SearchRequestSerializer::GetPackageMatchFilterJsonObject(const PackageMatchFilter& packageMatchFilter) const + { + web::json::value filter = web::json::value::object(); + std::optional matchField = ConvertPackageMatchFieldToString(packageMatchFilter.Field); + + if (!matchField) + { + AICLI_LOG(Repo, Warning, << "Skipping unsupported package match field: " << packageMatchFilter.Field); + return {}; + } + + filter[JSON::GetUtilityString(PackageMatchField)] = web::json::value::string(JSON::GetUtilityString(matchField.value())); + std::optional requestMatchJson = GetRequestMatchJsonObject(packageMatchFilter); + + if (!requestMatchJson) + { + AICLI_LOG(Repo, Warning, << "Skipping unsupported request match object."); + return {}; + } + + filter[JSON::GetUtilityString(RequestMatch)] = std::move(requestMatchJson.value()); + return filter; + } + + std::optional SearchRequestSerializer::GetRequestMatchJsonObject(const AppInstaller::Repository::RequestMatch& requestMatch) const + { + web::json::value match = web::json::value::object(); + match[JSON::GetUtilityString(KeyWord)] = web::json::value::string(JSON::GetUtilityString(requestMatch.Value)); + + std::optional matchType = ConvertMatchTypeToString(requestMatch.Type); + if (!matchType) + { + AICLI_LOG(Repo, Warning, << "Skipping unsupported match type: " << requestMatch.Type); + return {}; + } + + match[JSON::GetUtilityString(MatchType)] = web::json::value::string(JSON::GetUtilityString(matchType.value())); + return match; + } + + std::optional SearchRequestSerializer::ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const + { + // Match fields supported by Rest API schema. + switch (field) + { + case PackageMatchField::Command: + return "Command"sv; + case PackageMatchField::Id: + return "PackageIdentifier"sv; + case PackageMatchField::Moniker: + return "Moniker"sv; + case PackageMatchField::Name: + return "PackageName"sv; + case PackageMatchField::Tag: + return "Tag"sv; + case PackageMatchField::PackageFamilyName: + return "PackageFamilyName"sv; + case PackageMatchField::ProductCode: + return "ProductCode"sv; + case PackageMatchField::NormalizedNameAndPublisher: + return "NormalizedPackageNameAndPublisher"sv; + } + + return {}; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer.h index 85f18de11b..0d87a174f7 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer.h @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Rest/Schema/IRestClient.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_0::Json -{ - // Search Result Deserializer. - struct SearchResponseDeserializer - { - // Gets the search result for given version - IRestClient::SearchResult Deserialize(const web::json::value& searchResultJsonObject) const; - - protected: - virtual std::optional DeserializeSearchResult(const web::json::value& searchResultJsonObject) const; - virtual std::optional DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Rest/Schema/IRestClient.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_0::Json +{ + // Search Result Deserializer. + struct SearchResponseDeserializer + { + // Gets the search result for given version + IRestClient::SearchResult Deserialize(const web::json::value& searchResultJsonObject) const; + + protected: + virtual std::optional DeserializeSearchResult(const web::json::value& searchResultJsonObject) const; + virtual std::optional DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer_1_0.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer_1_0.cpp index d7e4c88173..378464f628 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/Json/SearchResponseDeserializer_1_0.cpp @@ -1,128 +1,128 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include "SearchResponseDeserializer.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_0::Json -{ - namespace - { - // Search response constants - constexpr std::string_view PackageIdentifier = "PackageIdentifier"sv; - constexpr std::string_view PackageName = "PackageName"sv; - constexpr std::string_view Publisher = "Publisher"sv; - constexpr std::string_view PackageFamilyNames = "PackageFamilyNames"sv; - constexpr std::string_view ProductCodes = "ProductCodes"sv; - constexpr std::string_view Versions = "Versions"sv; - constexpr std::string_view PackageVersion = "PackageVersion"sv; - constexpr std::string_view Channel = "Channel"sv; - } - - IRestClient::SearchResult SearchResponseDeserializer::Deserialize(const web::json::value& searchResponseObject) const - { - std::optional response = DeserializeSearchResult(searchResponseObject); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA, !response); - - return response.value(); - } - - std::optional SearchResponseDeserializer::DeserializeSearchResult(const web::json::value& searchResponseObject) const - { - // Make search result from json output. - if (searchResponseObject.is_null()) - { - AICLI_LOG(Repo, Error, << "Missing json object."); - return {}; - } - - IRestClient::SearchResult result; - try - { - std::optional> dataArray = JSON::GetRawJsonArrayFromJsonNode(searchResponseObject, JSON::GetUtilityString(Data)); - if (!dataArray || dataArray.value().get().size() == 0) - { - AICLI_LOG(Repo, Verbose, << "No search results returned."); - return result; - } - - for (auto& manifestItem : dataArray.value().get()) - { - std::optional packageId = JSON::GetRawStringValueFromJsonNode(manifestItem, JSON::GetUtilityString(PackageIdentifier)); - std::optional packageName = JSON::GetRawStringValueFromJsonNode(manifestItem, JSON::GetUtilityString(PackageName)); - std::optional publisher = JSON::GetRawStringValueFromJsonNode(manifestItem, JSON::GetUtilityString(Publisher)); - - if (!JSON::IsValidNonEmptyStringValue(packageId) || !JSON::IsValidNonEmptyStringValue(packageName) || !JSON::IsValidNonEmptyStringValue(publisher)) - { - AICLI_LOG(Repo, Error, << "Missing required package fields in manifest search results."); - return {}; - } - - std::optional> versionValue = JSON::GetRawJsonArrayFromJsonNode(manifestItem, JSON::GetUtilityString(Versions)); - std::vector versionList; - - if (versionValue) - { - for (auto& versionItem : versionValue.value().get()) - { - auto versionInfo = DeserializeVersionInfo(versionItem); - if (!versionInfo.has_value()) - { - AICLI_LOG(Repo, Error, << "Received incomplete package version in package: " << packageId.value()); - return {}; - } - - versionList.emplace_back(std::move(*versionInfo)); - } - } - - if (versionList.size() == 0) - { - AICLI_LOG(Repo, Error, << "Received no versions in package: " << packageId.value()); - return {}; - } - - IRestClient::PackageInfo packageInfo{ - std::move(packageId.value()), std::move(packageName.value()), std::move(publisher.value()) }; - IRestClient::Package package{ std::move(packageInfo), std::move(versionList) }; - result.Matches.emplace_back(std::move(package)); - } - - return result; - } - catch (const std::exception& e) - { - AICLI_LOG(Repo, Error, << "Error encountered while deserializing search result. Reason: " << e.what()); - } - catch (...) - { - AICLI_LOG(Repo, Error, << "Error encountered while deserializing search result..."); - } - - return {}; - } - - std::optional SearchResponseDeserializer::DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const - { - std::optional version = JSON::GetRawStringValueFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(PackageVersion)); - if (!JSON::IsValidNonEmptyStringValue(version)) - { - AICLI_LOG(Repo, Error, << "Received incomplete package version"); - return {}; - } - - std::string channel = JSON::GetRawStringValueFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(Channel)).value_or(""); - std::vector packageFamilyNames = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(PackageFamilyNames))); - std::vector productCodes = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(ProductCodes))); - - return IRestClient::VersionInfo{ - AppInstaller::Utility::VersionAndChannel{std::move(version.value()), std::move(channel)}, - {}, - std::move(packageFamilyNames), - std::move(productCodes) }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include "SearchResponseDeserializer.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_0::Json +{ + namespace + { + // Search response constants + constexpr std::string_view PackageIdentifier = "PackageIdentifier"sv; + constexpr std::string_view PackageName = "PackageName"sv; + constexpr std::string_view Publisher = "Publisher"sv; + constexpr std::string_view PackageFamilyNames = "PackageFamilyNames"sv; + constexpr std::string_view ProductCodes = "ProductCodes"sv; + constexpr std::string_view Versions = "Versions"sv; + constexpr std::string_view PackageVersion = "PackageVersion"sv; + constexpr std::string_view Channel = "Channel"sv; + } + + IRestClient::SearchResult SearchResponseDeserializer::Deserialize(const web::json::value& searchResponseObject) const + { + std::optional response = DeserializeSearchResult(searchResponseObject); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA, !response); + + return response.value(); + } + + std::optional SearchResponseDeserializer::DeserializeSearchResult(const web::json::value& searchResponseObject) const + { + // Make search result from json output. + if (searchResponseObject.is_null()) + { + AICLI_LOG(Repo, Error, << "Missing json object."); + return {}; + } + + IRestClient::SearchResult result; + try + { + std::optional> dataArray = JSON::GetRawJsonArrayFromJsonNode(searchResponseObject, JSON::GetUtilityString(Data)); + if (!dataArray || dataArray.value().get().size() == 0) + { + AICLI_LOG(Repo, Verbose, << "No search results returned."); + return result; + } + + for (auto& manifestItem : dataArray.value().get()) + { + std::optional packageId = JSON::GetRawStringValueFromJsonNode(manifestItem, JSON::GetUtilityString(PackageIdentifier)); + std::optional packageName = JSON::GetRawStringValueFromJsonNode(manifestItem, JSON::GetUtilityString(PackageName)); + std::optional publisher = JSON::GetRawStringValueFromJsonNode(manifestItem, JSON::GetUtilityString(Publisher)); + + if (!JSON::IsValidNonEmptyStringValue(packageId) || !JSON::IsValidNonEmptyStringValue(packageName) || !JSON::IsValidNonEmptyStringValue(publisher)) + { + AICLI_LOG(Repo, Error, << "Missing required package fields in manifest search results."); + return {}; + } + + std::optional> versionValue = JSON::GetRawJsonArrayFromJsonNode(manifestItem, JSON::GetUtilityString(Versions)); + std::vector versionList; + + if (versionValue) + { + for (auto& versionItem : versionValue.value().get()) + { + auto versionInfo = DeserializeVersionInfo(versionItem); + if (!versionInfo.has_value()) + { + AICLI_LOG(Repo, Error, << "Received incomplete package version in package: " << packageId.value()); + return {}; + } + + versionList.emplace_back(std::move(*versionInfo)); + } + } + + if (versionList.size() == 0) + { + AICLI_LOG(Repo, Error, << "Received no versions in package: " << packageId.value()); + return {}; + } + + IRestClient::PackageInfo packageInfo{ + std::move(packageId.value()), std::move(packageName.value()), std::move(publisher.value()) }; + IRestClient::Package package{ std::move(packageInfo), std::move(versionList) }; + result.Matches.emplace_back(std::move(package)); + } + + return result; + } + catch (const std::exception& e) + { + AICLI_LOG(Repo, Error, << "Error encountered while deserializing search result. Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(Repo, Error, << "Error encountered while deserializing search result..."); + } + + return {}; + } + + std::optional SearchResponseDeserializer::DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const + { + std::optional version = JSON::GetRawStringValueFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(PackageVersion)); + if (!JSON::IsValidNonEmptyStringValue(version)) + { + AICLI_LOG(Repo, Error, << "Received incomplete package version"); + return {}; + } + + std::string channel = JSON::GetRawStringValueFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(Channel)).value_or(""); + std::vector packageFamilyNames = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(PackageFamilyNames))); + std::vector productCodes = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(ProductCodes))); + + return IRestClient::VersionInfo{ + AppInstaller::Utility::VersionAndChannel{std::move(version.value()), std::move(channel)}, + {}, + std::move(packageFamilyNames), + std::move(productCodes) }; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/RestInterface_1_0.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/RestInterface_1_0.cpp index 33aa8b10ea..689ad7bf33 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_0/RestInterface_1_0.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_0/RestInterface_1_0.cpp @@ -1,306 +1,306 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_0/Interface.h" -#include "Rest/Schema/IRestClient.h" -#include -#include -#include -#include -#include -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/SearchResponseParser.h" -#include "Rest/Schema/SearchRequestComposer.h" - -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Rest::Schema::V1_0 -{ - namespace - { - // Query params - constexpr std::string_view VersionQueryParam = "Version"sv; - constexpr std::string_view ChannelQueryParam = "Channel"sv; - - utility::string_t GetSearchEndpoint(const std::string& restApiUri) - { - return AppInstaller::Rest::AppendPathToUri(AppInstaller::JSON::GetUtilityString(restApiUri), AppInstaller::JSON::GetUtilityString(ManifestSearchPostEndpoint)); - } - - utility::string_t GetManifestByVersionEndpoint( - const std::string& restApiUri, const std::string& packageId, const std::map& queryParameters) - { - utility::string_t getManifestEndpoint = AppInstaller::Rest::AppendPathToUri( - AppInstaller::JSON::GetUtilityString(restApiUri), AppInstaller::JSON::GetUtilityString(ManifestByVersionAndChannelGetEndpoint)); - - utility::string_t getManifestWithPackageIdPath = AppInstaller::Rest::AppendPathToUri(getManifestEndpoint, AppInstaller::JSON::GetUtilityString(packageId)); - - // Create the endpoint with query parameters - return AppInstaller::Rest::AppendQueryParamsToUri(getManifestWithPackageIdPath, queryParameters); - } - - std::optional GetContinuationToken(const web::json::value& jsonObject) - { - std::optional continuationToken = AppInstaller::JSON::GetRawStringValueFromJsonNode(jsonObject, AppInstaller::JSON::GetUtilityString(ContinuationToken)); - - if (continuationToken) - { - return utility::conversions::to_string_t(continuationToken.value()); - } - - return {}; - } - - AppInstaller::Http::HttpClientHelper::HttpResponseHandlerResult CustomRestCallResponseHandler(const web::http::http_response& response) - { - AppInstaller::Http::HttpClientHelper::HttpResponseHandlerResult result; - result.UseDefaultHandling = true; - - if (response.status_code() == web::http::status_codes::NotFound && - response.headers().content_type()._Starts_with(web::http::details::mime_types::application_json)) - { - auto responseJson = response.extract_json().get(); - if (responseJson.is_object() && responseJson.has_field(L"code") && responseJson.has_field(L"message")) - { - // We'll treat 404 with json response containing code and message fields as empty result. - // Leave the HttpResponseHandlerResult result empty and disable default HttpClientHelper handling. - result.UseDefaultHandling = false; - } - } - - return result; - } - } - - Interface::Interface(const std::string& restApi, const Http::HttpClientHelper& httpClientHelper) : m_restApiUri(restApi), m_httpClientHelper(httpClientHelper) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(AppInstaller::JSON::GetUtilityString(restApi))); - - m_searchEndpoint = GetSearchEndpoint(m_restApiUri); - m_requiredRestApiHeaders.emplace(AppInstaller::JSON::GetUtilityString(ContractVersion), AppInstaller::JSON::GetUtilityString(Version_1_0_0.ToString())); - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_0_0; - } - - IRestClient::Information Interface::GetSourceInformation() const - { - return {}; - } - - IRestClient::SearchResult Interface::Search(const SearchRequest& request) const - { - // Optimization - if (MeetsOptimizedSearchCriteria(request)) - { - return OptimizedSearch(request); - } - - return SearchInternal(request); - } - - IRestClient::SearchResult Interface::SearchInternal(const SearchRequest& request) const - { - SearchResult results; - utility::string_t continuationToken; - Http::HttpClientHelper::HttpRequestHeaders searchHeaders = m_requiredRestApiHeaders; - do - { - if (!continuationToken.empty()) - { - AICLI_LOG(Repo, Verbose, << "Received continuation token. Retrieving more results."); - searchHeaders.insert_or_assign(AppInstaller::JSON::GetUtilityString(ContinuationToken), continuationToken); - } - - std::optional jsonObject = m_httpClientHelper.HandlePost(m_searchEndpoint, GetValidatedSearchBody(request), searchHeaders, GetAuthHeaders(), CustomRestCallResponseHandler); - - utility::string_t ct; - if (jsonObject) - { - SearchResult currentResult = GetSearchResult(jsonObject.value()); - - size_t insertElements = !request.MaximumResults ? currentResult.Matches.size() : - std::min(currentResult.Matches.size(), request.MaximumResults - results.Matches.size()); - - if (insertElements < currentResult.Matches.size()) - { - results.Truncated = true; - } - - std::move(currentResult.Matches.begin(), std::next(currentResult.Matches.begin(), insertElements), std::inserter(results.Matches, results.Matches.end())); - ct = GetContinuationToken(jsonObject.value()).value_or(L""); - } - - continuationToken = ct; - - } while (!continuationToken.empty() && (!request.MaximumResults || results.Matches.size() < request.MaximumResults)); - - if (!continuationToken.empty()) - { - results.Truncated = true; - } - - if (results.Matches.empty()) - { - AICLI_LOG(Repo, Verbose, << "No search results returned by rest source"); - } - - return results; - } - - std::optional Interface::GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const - { - std::map queryParams; - if (!version.empty()) - { - queryParams.emplace(VersionQueryParam, version); - } - - if (!channel.empty()) - { - queryParams.emplace(ChannelQueryParam, channel); - } - - std::vector manifests = GetManifests(packageId, queryParams); - - if (!manifests.empty()) - { - for (Manifest::Manifest manifest : manifests) - { - if (Utility::CaseInsensitiveEquals(manifest.Version, version) && - Utility::CaseInsensitiveEquals(manifest.Channel, channel)) - { - return manifest; - } - } - } - - return {}; - } - - bool Interface::MeetsOptimizedSearchCriteria(const SearchRequest& request) const - { - // Optimization: If the user wants to install a certain package with an exact match on package id and a particular rest source, we will - // call the package manifest endpoint to get the manifest directly instead of running a search for it. - if (!request.Query && request.Inclusions.size() == 0 && - request.Filters.size() == 1 && request.Filters[0].Field == PackageMatchField::Id && - (request.Filters[0].Type == MatchType::Exact || request.Filters[0].Type == MatchType::CaseInsensitive)) - { - AICLI_LOG(Repo, Verbose, << "Search request meets optimized search criteria."); - return true; - } - - return false; - } - - IRestClient::SearchResult Interface::OptimizedSearch(const SearchRequest& request) const - { - SearchResult searchResult; - std::vector manifests = GetManifests(request.Filters[0].Value); - - if (!manifests.empty()) - { - auto& manifest = manifests.at(0); - PackageInfo packageInfo = PackageInfo{ - manifest.Id, - manifest.DefaultLocalization.Get(), - manifest.DefaultLocalization.Get() }; - - // Add all the versions to the package info object - std::vector versions; - for (auto& manifestVersion : manifests) - { - auto packageFamilyNames = manifestVersion.GetPackageFamilyNames(); - auto productCodes = manifestVersion.GetProductCodes(); - auto arpVersionRange = manifestVersion.GetArpVersionRange(); - auto upgradeCodes = manifestVersion.GetUpgradeCodes(); - - versions.emplace_back( - VersionInfo{ - AppInstaller::Utility::VersionAndChannel {manifestVersion.Version, manifestVersion.Channel}, - manifestVersion, - std::vector{ packageFamilyNames.begin(), packageFamilyNames.end()}, - std::vector{ productCodes.begin(), productCodes.end()}, - arpVersionRange.IsEmpty() ? std::vector{} : std::vector{ arpVersionRange.GetMinVersion(), arpVersionRange.GetMaxVersion() }, - std::vector{ upgradeCodes.begin(), upgradeCodes.end()} }); - } - - Package package = Package{ std::move(packageInfo), std::move(versions) }; - searchResult.Matches.emplace_back(std::move(package)); - } - - return searchResult; - } - - std::vector Interface::GetManifests(const std::string& packageId, const std::map& params) const - { - auto validatedParams = GetValidatedQueryParams(params); - - std::vector results; - utility::string_t continuationToken; - Http::HttpClientHelper::HttpRequestHeaders searchHeaders = m_requiredRestApiHeaders; - std::optional jsonObject = m_httpClientHelper.HandleGet(GetManifestByVersionEndpoint(m_restApiUri, packageId, validatedParams), searchHeaders, GetAuthHeaders(), CustomRestCallResponseHandler); - - if (!jsonObject) - { - AICLI_LOG(Repo, Verbose, << "No results were returned by the rest source for package id: " << packageId); - return results; - } - - // Parse json and return Manifests - std::vector manifests = GetParsedManifests(jsonObject.value()); - - // Manifest validation - for (auto& manifestItem : manifests) - { - std::vector validationErrors = - AppInstaller::Manifest::ValidateManifest(manifestItem, AppInstaller::Manifest::ManifestValidateOption{ false }); - - int errors = 0; - for (auto& error : validationErrors) - { - if (error.ErrorLevel == Manifest::ValidationError::Level::Error) - { - AICLI_LOG(Repo, Error, << "Received manifest contains validation error: " << error.GetErrorMessage()); - errors++; - } - } - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA, errors > 0); - - results.emplace_back(manifestItem); - } - - return results; - } - - std::map Interface::GetValidatedQueryParams(const std::map& params) const - { - return params; - } - - web::json::value Interface::GetValidatedSearchBody(const SearchRequest& searchRequest) const - { - SearchRequestComposer searchRequestComposer{ GetVersion() }; - return searchRequestComposer.Serialize(searchRequest); - } - - IRestClient::SearchResult Interface::GetSearchResult(const web::json::value& searchResponseObject) const - { - SearchResponseParser searchResponseParser{ GetVersion() }; - return searchResponseParser.Deserialize(searchResponseObject); - } - - std::vector Interface::GetParsedManifests(const web::json::value& manifestsResponseObject) const - { - JSON::ManifestJSONParser manifestParser{ GetVersion() }; - return manifestParser.Deserialize(manifestsResponseObject); - } - - Http::HttpClientHelper::HttpRequestHeaders Interface::GetAuthHeaders() const - { - return {}; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_0/Interface.h" +#include "Rest/Schema/IRestClient.h" +#include +#include +#include +#include +#include +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/SearchResponseParser.h" +#include "Rest/Schema/SearchRequestComposer.h" + +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Rest::Schema::V1_0 +{ + namespace + { + // Query params + constexpr std::string_view VersionQueryParam = "Version"sv; + constexpr std::string_view ChannelQueryParam = "Channel"sv; + + utility::string_t GetSearchEndpoint(const std::string& restApiUri) + { + return AppInstaller::Rest::AppendPathToUri(AppInstaller::JSON::GetUtilityString(restApiUri), AppInstaller::JSON::GetUtilityString(ManifestSearchPostEndpoint)); + } + + utility::string_t GetManifestByVersionEndpoint( + const std::string& restApiUri, const std::string& packageId, const std::map& queryParameters) + { + utility::string_t getManifestEndpoint = AppInstaller::Rest::AppendPathToUri( + AppInstaller::JSON::GetUtilityString(restApiUri), AppInstaller::JSON::GetUtilityString(ManifestByVersionAndChannelGetEndpoint)); + + utility::string_t getManifestWithPackageIdPath = AppInstaller::Rest::AppendPathToUri(getManifestEndpoint, AppInstaller::JSON::GetUtilityString(packageId)); + + // Create the endpoint with query parameters + return AppInstaller::Rest::AppendQueryParamsToUri(getManifestWithPackageIdPath, queryParameters); + } + + std::optional GetContinuationToken(const web::json::value& jsonObject) + { + std::optional continuationToken = AppInstaller::JSON::GetRawStringValueFromJsonNode(jsonObject, AppInstaller::JSON::GetUtilityString(ContinuationToken)); + + if (continuationToken) + { + return utility::conversions::to_string_t(continuationToken.value()); + } + + return {}; + } + + AppInstaller::Http::HttpClientHelper::HttpResponseHandlerResult CustomRestCallResponseHandler(const web::http::http_response& response) + { + AppInstaller::Http::HttpClientHelper::HttpResponseHandlerResult result; + result.UseDefaultHandling = true; + + if (response.status_code() == web::http::status_codes::NotFound && + response.headers().content_type()._Starts_with(web::http::details::mime_types::application_json)) + { + auto responseJson = response.extract_json().get(); + if (responseJson.is_object() && responseJson.has_field(L"code") && responseJson.has_field(L"message")) + { + // We'll treat 404 with json response containing code and message fields as empty result. + // Leave the HttpResponseHandlerResult result empty and disable default HttpClientHelper handling. + result.UseDefaultHandling = false; + } + } + + return result; + } + } + + Interface::Interface(const std::string& restApi, const Http::HttpClientHelper& httpClientHelper) : m_restApiUri(restApi), m_httpClientHelper(httpClientHelper) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, !AppInstaller::Rest::IsValidUri(AppInstaller::JSON::GetUtilityString(restApi))); + + m_searchEndpoint = GetSearchEndpoint(m_restApiUri); + m_requiredRestApiHeaders.emplace(AppInstaller::JSON::GetUtilityString(ContractVersion), AppInstaller::JSON::GetUtilityString(Version_1_0_0.ToString())); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_0_0; + } + + IRestClient::Information Interface::GetSourceInformation() const + { + return {}; + } + + IRestClient::SearchResult Interface::Search(const SearchRequest& request) const + { + // Optimization + if (MeetsOptimizedSearchCriteria(request)) + { + return OptimizedSearch(request); + } + + return SearchInternal(request); + } + + IRestClient::SearchResult Interface::SearchInternal(const SearchRequest& request) const + { + SearchResult results; + utility::string_t continuationToken; + Http::HttpClientHelper::HttpRequestHeaders searchHeaders = m_requiredRestApiHeaders; + do + { + if (!continuationToken.empty()) + { + AICLI_LOG(Repo, Verbose, << "Received continuation token. Retrieving more results."); + searchHeaders.insert_or_assign(AppInstaller::JSON::GetUtilityString(ContinuationToken), continuationToken); + } + + std::optional jsonObject = m_httpClientHelper.HandlePost(m_searchEndpoint, GetValidatedSearchBody(request), searchHeaders, GetAuthHeaders(), CustomRestCallResponseHandler); + + utility::string_t ct; + if (jsonObject) + { + SearchResult currentResult = GetSearchResult(jsonObject.value()); + + size_t insertElements = !request.MaximumResults ? currentResult.Matches.size() : + std::min(currentResult.Matches.size(), request.MaximumResults - results.Matches.size()); + + if (insertElements < currentResult.Matches.size()) + { + results.Truncated = true; + } + + std::move(currentResult.Matches.begin(), std::next(currentResult.Matches.begin(), insertElements), std::inserter(results.Matches, results.Matches.end())); + ct = GetContinuationToken(jsonObject.value()).value_or(L""); + } + + continuationToken = ct; + + } while (!continuationToken.empty() && (!request.MaximumResults || results.Matches.size() < request.MaximumResults)); + + if (!continuationToken.empty()) + { + results.Truncated = true; + } + + if (results.Matches.empty()) + { + AICLI_LOG(Repo, Verbose, << "No search results returned by rest source"); + } + + return results; + } + + std::optional Interface::GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const + { + std::map queryParams; + if (!version.empty()) + { + queryParams.emplace(VersionQueryParam, version); + } + + if (!channel.empty()) + { + queryParams.emplace(ChannelQueryParam, channel); + } + + std::vector manifests = GetManifests(packageId, queryParams); + + if (!manifests.empty()) + { + for (Manifest::Manifest manifest : manifests) + { + if (Utility::CaseInsensitiveEquals(manifest.Version, version) && + Utility::CaseInsensitiveEquals(manifest.Channel, channel)) + { + return manifest; + } + } + } + + return {}; + } + + bool Interface::MeetsOptimizedSearchCriteria(const SearchRequest& request) const + { + // Optimization: If the user wants to install a certain package with an exact match on package id and a particular rest source, we will + // call the package manifest endpoint to get the manifest directly instead of running a search for it. + if (!request.Query && request.Inclusions.size() == 0 && + request.Filters.size() == 1 && request.Filters[0].Field == PackageMatchField::Id && + (request.Filters[0].Type == MatchType::Exact || request.Filters[0].Type == MatchType::CaseInsensitive)) + { + AICLI_LOG(Repo, Verbose, << "Search request meets optimized search criteria."); + return true; + } + + return false; + } + + IRestClient::SearchResult Interface::OptimizedSearch(const SearchRequest& request) const + { + SearchResult searchResult; + std::vector manifests = GetManifests(request.Filters[0].Value); + + if (!manifests.empty()) + { + auto& manifest = manifests.at(0); + PackageInfo packageInfo = PackageInfo{ + manifest.Id, + manifest.DefaultLocalization.Get(), + manifest.DefaultLocalization.Get() }; + + // Add all the versions to the package info object + std::vector versions; + for (auto& manifestVersion : manifests) + { + auto packageFamilyNames = manifestVersion.GetPackageFamilyNames(); + auto productCodes = manifestVersion.GetProductCodes(); + auto arpVersionRange = manifestVersion.GetArpVersionRange(); + auto upgradeCodes = manifestVersion.GetUpgradeCodes(); + + versions.emplace_back( + VersionInfo{ + AppInstaller::Utility::VersionAndChannel {manifestVersion.Version, manifestVersion.Channel}, + manifestVersion, + std::vector{ packageFamilyNames.begin(), packageFamilyNames.end()}, + std::vector{ productCodes.begin(), productCodes.end()}, + arpVersionRange.IsEmpty() ? std::vector{} : std::vector{ arpVersionRange.GetMinVersion(), arpVersionRange.GetMaxVersion() }, + std::vector{ upgradeCodes.begin(), upgradeCodes.end()} }); + } + + Package package = Package{ std::move(packageInfo), std::move(versions) }; + searchResult.Matches.emplace_back(std::move(package)); + } + + return searchResult; + } + + std::vector Interface::GetManifests(const std::string& packageId, const std::map& params) const + { + auto validatedParams = GetValidatedQueryParams(params); + + std::vector results; + utility::string_t continuationToken; + Http::HttpClientHelper::HttpRequestHeaders searchHeaders = m_requiredRestApiHeaders; + std::optional jsonObject = m_httpClientHelper.HandleGet(GetManifestByVersionEndpoint(m_restApiUri, packageId, validatedParams), searchHeaders, GetAuthHeaders(), CustomRestCallResponseHandler); + + if (!jsonObject) + { + AICLI_LOG(Repo, Verbose, << "No results were returned by the rest source for package id: " << packageId); + return results; + } + + // Parse json and return Manifests + std::vector manifests = GetParsedManifests(jsonObject.value()); + + // Manifest validation + for (auto& manifestItem : manifests) + { + std::vector validationErrors = + AppInstaller::Manifest::ValidateManifest(manifestItem, AppInstaller::Manifest::ManifestValidateOption{ false }); + + int errors = 0; + for (auto& error : validationErrors) + { + if (error.ErrorLevel == Manifest::ValidationError::Level::Error) + { + AICLI_LOG(Repo, Error, << "Received manifest contains validation error: " << error.GetErrorMessage()); + errors++; + } + } + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA, errors > 0); + + results.emplace_back(manifestItem); + } + + return results; + } + + std::map Interface::GetValidatedQueryParams(const std::map& params) const + { + return params; + } + + web::json::value Interface::GetValidatedSearchBody(const SearchRequest& searchRequest) const + { + SearchRequestComposer searchRequestComposer{ GetVersion() }; + return searchRequestComposer.Serialize(searchRequest); + } + + IRestClient::SearchResult Interface::GetSearchResult(const web::json::value& searchResponseObject) const + { + SearchResponseParser searchResponseParser{ GetVersion() }; + return searchResponseParser.Deserialize(searchResponseObject); + } + + std::vector Interface::GetParsedManifests(const web::json::value& manifestsResponseObject) const + { + JSON::ManifestJSONParser manifestParser{ GetVersion() }; + return manifestParser.Deserialize(manifestsResponseObject); + } + + Http::HttpClientHelper::HttpRequestHeaders Interface::GetAuthHeaders() const + { + return {}; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h index ad18e57651..bd070c0543 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Interface.h @@ -1,36 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_0/Interface.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_1 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public V1_0::Interface - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - IRestClient::Information GetSourceInformation() const override; - - protected: - // Check query params against source information and update if necessary. - std::map GetValidatedQueryParams(const std::map& params) const override; - - // Check search request against source information and get json search body. - web::json::value GetValidatedSearchBody(const SearchRequest& searchRequest) const override; - - SearchResult GetSearchResult(const web::json::value& searchResponseObject) const override; - std::vector GetParsedManifests(const web::json::value& manifestsResponseObject) const override; - - PackageMatchField ConvertStringToPackageMatchField(std::string_view field) const; - - IRestClient::Information m_information; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_0/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_1 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_0::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + IRestClient::Information GetSourceInformation() const override; + + protected: + // Check query params against source information and update if necessary. + std::map GetValidatedQueryParams(const std::map& params) const override; + + // Check search request against source information and get json search body. + web::json::value GetValidatedSearchBody(const SearchRequest& searchRequest) const override; + + SearchResult GetSearchResult(const web::json::value& searchResponseObject) const override; + std::vector GetParsedManifests(const web::json::value& manifestsResponseObject) const override; + + PackageMatchField ConvertStringToPackageMatchField(std::string_view field) const; + + IRestClient::Information m_information; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer.h index a7e3f5b275..2fd628c6c8 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_0/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_1::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer : public V1_0::Json::ManifestDeserializer - { - std::vector DeserializeAppsAndFeaturesEntries(const web::json::array& entries) const override; - - std::optional DeserializeLocale(const web::json::value& localeJsonObject) const override; - - protected: - std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; - - Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const override; - - virtual Manifest::ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(std::string_view in) const; - - virtual Manifest::ManifestInstaller::ExpectedReturnCodeInfo DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const; - - Manifest::ManifestVer GetManifestVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_0/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_1::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_0::Json::ManifestDeserializer + { + std::vector DeserializeAppsAndFeaturesEntries(const web::json::array& entries) const override; + + std::optional DeserializeLocale(const web::json::value& localeJsonObject) const override; + + protected: + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const override; + + virtual Manifest::ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(std::string_view in) const; + + virtual Manifest::ManifestInstaller::ExpectedReturnCodeInfo DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer_1_1.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer_1_1.cpp index adffc7ee02..d5d057cb8d 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/ManifestDeserializer_1_1.cpp @@ -1,295 +1,295 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestDeserializer.h" -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_1::Json -{ - namespace - { - // Installer - constexpr std::string_view MSStoreProductIdentifier = "MSStoreProductIdentifier"sv; - constexpr std::string_view ReleaseDate = "ReleaseDate"sv; - constexpr std::string_view InstallerAbortsTerminal = "InstallerAbortsTerminal"sv; - constexpr std::string_view InstallLocationRequired = "InstallLocationRequired"sv; - constexpr std::string_view RequireExplicitUpgrade = "RequireExplicitUpgrade"sv; - constexpr std::string_view UnsupportedOSArchitectures = "UnsupportedOSArchitectures"sv; - constexpr std::string_view AppsAndFeaturesEntries = "AppsAndFeaturesEntries"sv; - constexpr std::string_view DisplayName = "DisplayName"sv; - constexpr std::string_view Publisher = "Publisher"sv; - constexpr std::string_view DisplayVersion = "DisplayVersion"sv; - constexpr std::string_view ProductCode = "ProductCode"sv; - constexpr std::string_view UpgradeCode = "UpgradeCode"sv; - constexpr std::string_view InstallerType = "InstallerType"sv; - constexpr std::string_view Markets = "Markets"sv; - constexpr std::string_view AllowedMarkets = "AllowedMarkets"sv; - constexpr std::string_view ExcludedMarkets = "ExcludedMarkets"sv; - constexpr std::string_view ElevationRequirement = "ElevationRequirement"sv; - constexpr std::string_view ExpectedReturnCodes = "ExpectedReturnCodes"sv; - constexpr std::string_view InstallerReturnCode = "InstallerReturnCode"sv; - constexpr std::string_view ReturnResponse = "ReturnResponse"sv; - - // Locale - constexpr std::string_view ReleaseNotes = "ReleaseNotes"sv; - constexpr std::string_view ReleaseNotesUrl = "ReleaseNotesUrl"sv; - constexpr std::string_view Agreements = "Agreements"sv; - constexpr std::string_view AgreementLabel = "AgreementLabel"sv; - constexpr std::string_view Agreement = "Agreement"sv; - constexpr std::string_view AgreementUrl = "AgreementUrl"sv; - } - - std::vector ManifestDeserializer::DeserializeAppsAndFeaturesEntries(const web::json::array& entries) const - { - std::vector result; - - for (auto& arpEntryNode : entries) - { - AppsAndFeaturesEntry arpEntry; - arpEntry.DisplayName = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(DisplayName)).value_or(""); - arpEntry.Publisher = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(Publisher)).value_or(""); - arpEntry.DisplayVersion = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(DisplayVersion)).value_or(""); - arpEntry.ProductCode = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(ProductCode)).value_or(""); - arpEntry.UpgradeCode = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(UpgradeCode)).value_or(""); - arpEntry.InstallerType = ConvertToInstallerType(JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(InstallerType)).value_or("")); - - // Only add when at least one field is valid - if (!arpEntry.DisplayName.empty() || !arpEntry.Publisher.empty() || !arpEntry.DisplayVersion.empty() || - !arpEntry.ProductCode.empty() || !arpEntry.UpgradeCode.empty() || arpEntry.InstallerType != InstallerTypeEnum::Unknown) - { - result.emplace_back(std::move(arpEntry)); - } - } - - return result; - } - - Manifest::InstallerTypeEnum ManifestDeserializer::ConvertToInstallerType(std::string_view in) const - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "msstore") - { - return InstallerTypeEnum::MSStore; - } - - return V1_0::Json::ManifestDeserializer::ConvertToInstallerType(inStrLower); - } - - Manifest::ExpectedReturnCodeEnum ManifestDeserializer::ConvertToExpectedReturnCodeEnum(std::string_view in) const - { - std::string inStrLower = Utility::ToLower(in); - ExpectedReturnCodeEnum result = ExpectedReturnCodeEnum::Unknown; - - if (inStrLower == "packageinuse") - { - result = ExpectedReturnCodeEnum::PackageInUse; - } - else if (inStrLower == "installinprogress") - { - result = ExpectedReturnCodeEnum::InstallInProgress; - } - else if (inStrLower == "fileinuse") - { - result = ExpectedReturnCodeEnum::FileInUse; - } - else if (inStrLower == "missingdependency") - { - result = ExpectedReturnCodeEnum::MissingDependency; - } - else if (inStrLower == "diskfull") - { - result = ExpectedReturnCodeEnum::DiskFull; - } - else if (inStrLower == "insufficientmemory") - { - result = ExpectedReturnCodeEnum::InsufficientMemory; - } - else if (inStrLower == "nonetwork") - { - result = ExpectedReturnCodeEnum::NoNetwork; - } - else if (inStrLower == "contactsupport") - { - result = ExpectedReturnCodeEnum::ContactSupport; - } - else if (inStrLower == "rebootrequiredtofinish") - { - result = ExpectedReturnCodeEnum::RebootRequiredToFinish; - } - else if (inStrLower == "rebootrequiredforinstall") - { - result = ExpectedReturnCodeEnum::RebootRequiredForInstall; - } - else if (inStrLower == "rebootinitiated") - { - result = ExpectedReturnCodeEnum::RebootInitiated; - } - else if (inStrLower == "cancelledbyuser") - { - result = ExpectedReturnCodeEnum::CancelledByUser; - } - else if (inStrLower == "alreadyinstalled") - { - result = ExpectedReturnCodeEnum::AlreadyInstalled; - } - else if (inStrLower == "downgrade") - { - result = ExpectedReturnCodeEnum::Downgrade; - } - else if (inStrLower == "blockedbypolicy") - { - result = ExpectedReturnCodeEnum::BlockedByPolicy; - } - - return result; - } - - Manifest::ManifestInstaller::ExpectedReturnCodeInfo ManifestDeserializer::DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const - { - ExpectedReturnCodeEnum returnResponse = ConvertToExpectedReturnCodeEnum(JSON::GetRawStringValueFromJsonNode(expectedReturnCodeJsonObject, JSON::GetUtilityString(ReturnResponse)).value_or("")); - - return { returnResponse, "" }; - } - - std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const - { - auto result = V1_0::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); - - if (result) - { - auto& installer = result.value(); - - installer.ProductId = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(MSStoreProductIdentifier)).value_or(""); - installer.ReleaseDate = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ReleaseDate)).value_or(""); - installer.InstallerAbortsTerminal = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerAbortsTerminal)).value_or(false); - installer.InstallLocationRequired = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallLocationRequired)).value_or(false); - installer.RequireExplicitUpgrade = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(RequireExplicitUpgrade)).value_or(false); - installer.ElevationRequirement = Manifest::ConvertToElevationRequirementEnum( - JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ElevationRequirement)).value_or("")); - - // list of unsupported OS architectures - std::optional> unsupportedOSArchitectures = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(UnsupportedOSArchitectures)); - if (unsupportedOSArchitectures) - { - for (auto& archValue : unsupportedOSArchitectures.value().get()) - { - std::optional arch = JSON::GetRawStringValueFromJsonValue(archValue); - if (JSON::IsValidNonEmptyStringValue(arch)) - { - auto archEnum = Utility::ConvertToArchitectureEnum(arch.value()); - - if (archEnum == Utility::Architecture::Neutral) - { - AICLI_LOG(Repo, Error, << "Unsupported OS architectures cannot contain neutral value."); - return {}; - } - - if (archEnum != Utility::Architecture::Unknown) - { - installer.UnsupportedOSArchitectures.emplace_back(archEnum); - } - } - } - } - - // Apps and Features Entries - std::optional> arpEntriesNode = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(AppsAndFeaturesEntries)); - if (arpEntriesNode) - { - installer.AppsAndFeaturesEntries = DeserializeAppsAndFeaturesEntries(arpEntriesNode.value()); - } - - // Markets - std::optional> marketsNode = JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(Markets)); - if (marketsNode && !marketsNode.value().get().is_null()) - { - installer.Markets.ExcludedMarkets = V1_0::Json::ManifestDeserializer::ConvertToManifestStringArray( - JSON::GetRawStringArrayFromJsonNode(marketsNode.value().get(), JSON::GetUtilityString(ExcludedMarkets))); - installer.Markets.AllowedMarkets = V1_0::Json::ManifestDeserializer::ConvertToManifestStringArray( - JSON::GetRawStringArrayFromJsonNode(marketsNode.value().get(), JSON::GetUtilityString(AllowedMarkets))); - } - - // Expected return codes - std::optional> expectedReturnCodesNode = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(ExpectedReturnCodes)); - if (expectedReturnCodesNode) - { - for (auto& returnCodeNode : expectedReturnCodesNode.value().get()) - { - DWORD installerReturnCode = static_cast(JSON::GetRawIntValueFromJsonNode(returnCodeNode, JSON::GetUtilityString(InstallerReturnCode)).value_or(0)); - auto returnCodeInfo = DeserializeExpectedReturnCodeInfo(returnCodeNode); - - // Only add when it is valid - if (installerReturnCode != 0 && returnCodeInfo.ReturnResponseEnum != ExpectedReturnCodeEnum::Unknown) - { - if (!installer.ExpectedReturnCodes.insert({ installerReturnCode, std::move(returnCodeInfo) }).second) - { - AICLI_LOG(Repo, Error, << "Expected return codes cannot have repeated value."); - return {}; - } - } - } - } - - // Populate installer default return codes if not present in ExpectedReturnCodes and InstallerSuccessCodes - auto defaultReturnCodes = GetDefaultKnownReturnCodes(installer.EffectiveInstallerType()); - for (auto const& defaultReturnCode : defaultReturnCodes) - { - if (installer.ExpectedReturnCodes.find(defaultReturnCode.first) == installer.ExpectedReturnCodes.end() && - std::find(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end(), defaultReturnCode.first) == installer.InstallerSuccessCodes.end()) - { - installer.ExpectedReturnCodes[defaultReturnCode.first].ReturnResponseEnum = defaultReturnCode.second; - } - } - } - - return result; - } - - std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const - { - auto result = V1_0::Json::ManifestDeserializer::DeserializeLocale(localeJsonObject); - - if (result) - { - auto& locale = result.value(); - - TryParseStringLocaleField(locale, localeJsonObject, ReleaseNotes); - TryParseStringLocaleField(locale, localeJsonObject, ReleaseNotesUrl); - - // Agreements - auto agreementsNode = JSON::GetRawJsonArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Agreements)); - if (agreementsNode) - { - std::vector agreements; - for (auto const& agreementNode : agreementsNode.value().get()) - { - Manifest::Agreement agreementEntry; - - agreementEntry.Label = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(AgreementLabel)).value_or(""); - agreementEntry.AgreementText = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(Agreement)).value_or(""); - agreementEntry.AgreementUrl = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(AgreementUrl)).value_or(""); - - if (!agreementEntry.Label.empty() || !agreementEntry.AgreementText.empty() || !agreementEntry.AgreementUrl.empty()) - { - agreements.emplace_back(std::move(agreementEntry)); - } - } - - if (!agreements.empty()) - { - locale.Add(std::move(agreements)); - } - } - } - - return result; - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1_1; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_1::Json +{ + namespace + { + // Installer + constexpr std::string_view MSStoreProductIdentifier = "MSStoreProductIdentifier"sv; + constexpr std::string_view ReleaseDate = "ReleaseDate"sv; + constexpr std::string_view InstallerAbortsTerminal = "InstallerAbortsTerminal"sv; + constexpr std::string_view InstallLocationRequired = "InstallLocationRequired"sv; + constexpr std::string_view RequireExplicitUpgrade = "RequireExplicitUpgrade"sv; + constexpr std::string_view UnsupportedOSArchitectures = "UnsupportedOSArchitectures"sv; + constexpr std::string_view AppsAndFeaturesEntries = "AppsAndFeaturesEntries"sv; + constexpr std::string_view DisplayName = "DisplayName"sv; + constexpr std::string_view Publisher = "Publisher"sv; + constexpr std::string_view DisplayVersion = "DisplayVersion"sv; + constexpr std::string_view ProductCode = "ProductCode"sv; + constexpr std::string_view UpgradeCode = "UpgradeCode"sv; + constexpr std::string_view InstallerType = "InstallerType"sv; + constexpr std::string_view Markets = "Markets"sv; + constexpr std::string_view AllowedMarkets = "AllowedMarkets"sv; + constexpr std::string_view ExcludedMarkets = "ExcludedMarkets"sv; + constexpr std::string_view ElevationRequirement = "ElevationRequirement"sv; + constexpr std::string_view ExpectedReturnCodes = "ExpectedReturnCodes"sv; + constexpr std::string_view InstallerReturnCode = "InstallerReturnCode"sv; + constexpr std::string_view ReturnResponse = "ReturnResponse"sv; + + // Locale + constexpr std::string_view ReleaseNotes = "ReleaseNotes"sv; + constexpr std::string_view ReleaseNotesUrl = "ReleaseNotesUrl"sv; + constexpr std::string_view Agreements = "Agreements"sv; + constexpr std::string_view AgreementLabel = "AgreementLabel"sv; + constexpr std::string_view Agreement = "Agreement"sv; + constexpr std::string_view AgreementUrl = "AgreementUrl"sv; + } + + std::vector ManifestDeserializer::DeserializeAppsAndFeaturesEntries(const web::json::array& entries) const + { + std::vector result; + + for (auto& arpEntryNode : entries) + { + AppsAndFeaturesEntry arpEntry; + arpEntry.DisplayName = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(DisplayName)).value_or(""); + arpEntry.Publisher = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(Publisher)).value_or(""); + arpEntry.DisplayVersion = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(DisplayVersion)).value_or(""); + arpEntry.ProductCode = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(ProductCode)).value_or(""); + arpEntry.UpgradeCode = JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(UpgradeCode)).value_or(""); + arpEntry.InstallerType = ConvertToInstallerType(JSON::GetRawStringValueFromJsonNode(arpEntryNode, JSON::GetUtilityString(InstallerType)).value_or("")); + + // Only add when at least one field is valid + if (!arpEntry.DisplayName.empty() || !arpEntry.Publisher.empty() || !arpEntry.DisplayVersion.empty() || + !arpEntry.ProductCode.empty() || !arpEntry.UpgradeCode.empty() || arpEntry.InstallerType != InstallerTypeEnum::Unknown) + { + result.emplace_back(std::move(arpEntry)); + } + } + + return result; + } + + Manifest::InstallerTypeEnum ManifestDeserializer::ConvertToInstallerType(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "msstore") + { + return InstallerTypeEnum::MSStore; + } + + return V1_0::Json::ManifestDeserializer::ConvertToInstallerType(inStrLower); + } + + Manifest::ExpectedReturnCodeEnum ManifestDeserializer::ConvertToExpectedReturnCodeEnum(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + ExpectedReturnCodeEnum result = ExpectedReturnCodeEnum::Unknown; + + if (inStrLower == "packageinuse") + { + result = ExpectedReturnCodeEnum::PackageInUse; + } + else if (inStrLower == "installinprogress") + { + result = ExpectedReturnCodeEnum::InstallInProgress; + } + else if (inStrLower == "fileinuse") + { + result = ExpectedReturnCodeEnum::FileInUse; + } + else if (inStrLower == "missingdependency") + { + result = ExpectedReturnCodeEnum::MissingDependency; + } + else if (inStrLower == "diskfull") + { + result = ExpectedReturnCodeEnum::DiskFull; + } + else if (inStrLower == "insufficientmemory") + { + result = ExpectedReturnCodeEnum::InsufficientMemory; + } + else if (inStrLower == "nonetwork") + { + result = ExpectedReturnCodeEnum::NoNetwork; + } + else if (inStrLower == "contactsupport") + { + result = ExpectedReturnCodeEnum::ContactSupport; + } + else if (inStrLower == "rebootrequiredtofinish") + { + result = ExpectedReturnCodeEnum::RebootRequiredToFinish; + } + else if (inStrLower == "rebootrequiredforinstall") + { + result = ExpectedReturnCodeEnum::RebootRequiredForInstall; + } + else if (inStrLower == "rebootinitiated") + { + result = ExpectedReturnCodeEnum::RebootInitiated; + } + else if (inStrLower == "cancelledbyuser") + { + result = ExpectedReturnCodeEnum::CancelledByUser; + } + else if (inStrLower == "alreadyinstalled") + { + result = ExpectedReturnCodeEnum::AlreadyInstalled; + } + else if (inStrLower == "downgrade") + { + result = ExpectedReturnCodeEnum::Downgrade; + } + else if (inStrLower == "blockedbypolicy") + { + result = ExpectedReturnCodeEnum::BlockedByPolicy; + } + + return result; + } + + Manifest::ManifestInstaller::ExpectedReturnCodeInfo ManifestDeserializer::DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const + { + ExpectedReturnCodeEnum returnResponse = ConvertToExpectedReturnCodeEnum(JSON::GetRawStringValueFromJsonNode(expectedReturnCodeJsonObject, JSON::GetUtilityString(ReturnResponse)).value_or("")); + + return { returnResponse, "" }; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_0::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + installer.ProductId = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(MSStoreProductIdentifier)).value_or(""); + installer.ReleaseDate = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ReleaseDate)).value_or(""); + installer.InstallerAbortsTerminal = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallerAbortsTerminal)).value_or(false); + installer.InstallLocationRequired = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(InstallLocationRequired)).value_or(false); + installer.RequireExplicitUpgrade = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(RequireExplicitUpgrade)).value_or(false); + installer.ElevationRequirement = Manifest::ConvertToElevationRequirementEnum( + JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ElevationRequirement)).value_or("")); + + // list of unsupported OS architectures + std::optional> unsupportedOSArchitectures = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(UnsupportedOSArchitectures)); + if (unsupportedOSArchitectures) + { + for (auto& archValue : unsupportedOSArchitectures.value().get()) + { + std::optional arch = JSON::GetRawStringValueFromJsonValue(archValue); + if (JSON::IsValidNonEmptyStringValue(arch)) + { + auto archEnum = Utility::ConvertToArchitectureEnum(arch.value()); + + if (archEnum == Utility::Architecture::Neutral) + { + AICLI_LOG(Repo, Error, << "Unsupported OS architectures cannot contain neutral value."); + return {}; + } + + if (archEnum != Utility::Architecture::Unknown) + { + installer.UnsupportedOSArchitectures.emplace_back(archEnum); + } + } + } + } + + // Apps and Features Entries + std::optional> arpEntriesNode = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(AppsAndFeaturesEntries)); + if (arpEntriesNode) + { + installer.AppsAndFeaturesEntries = DeserializeAppsAndFeaturesEntries(arpEntriesNode.value()); + } + + // Markets + std::optional> marketsNode = JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(Markets)); + if (marketsNode && !marketsNode.value().get().is_null()) + { + installer.Markets.ExcludedMarkets = V1_0::Json::ManifestDeserializer::ConvertToManifestStringArray( + JSON::GetRawStringArrayFromJsonNode(marketsNode.value().get(), JSON::GetUtilityString(ExcludedMarkets))); + installer.Markets.AllowedMarkets = V1_0::Json::ManifestDeserializer::ConvertToManifestStringArray( + JSON::GetRawStringArrayFromJsonNode(marketsNode.value().get(), JSON::GetUtilityString(AllowedMarkets))); + } + + // Expected return codes + std::optional> expectedReturnCodesNode = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(ExpectedReturnCodes)); + if (expectedReturnCodesNode) + { + for (auto& returnCodeNode : expectedReturnCodesNode.value().get()) + { + DWORD installerReturnCode = static_cast(JSON::GetRawIntValueFromJsonNode(returnCodeNode, JSON::GetUtilityString(InstallerReturnCode)).value_or(0)); + auto returnCodeInfo = DeserializeExpectedReturnCodeInfo(returnCodeNode); + + // Only add when it is valid + if (installerReturnCode != 0 && returnCodeInfo.ReturnResponseEnum != ExpectedReturnCodeEnum::Unknown) + { + if (!installer.ExpectedReturnCodes.insert({ installerReturnCode, std::move(returnCodeInfo) }).second) + { + AICLI_LOG(Repo, Error, << "Expected return codes cannot have repeated value."); + return {}; + } + } + } + } + + // Populate installer default return codes if not present in ExpectedReturnCodes and InstallerSuccessCodes + auto defaultReturnCodes = GetDefaultKnownReturnCodes(installer.EffectiveInstallerType()); + for (auto const& defaultReturnCode : defaultReturnCodes) + { + if (installer.ExpectedReturnCodes.find(defaultReturnCode.first) == installer.ExpectedReturnCodes.end() && + std::find(installer.InstallerSuccessCodes.begin(), installer.InstallerSuccessCodes.end(), defaultReturnCode.first) == installer.InstallerSuccessCodes.end()) + { + installer.ExpectedReturnCodes[defaultReturnCode.first].ReturnResponseEnum = defaultReturnCode.second; + } + } + } + + return result; + } + + std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const + { + auto result = V1_0::Json::ManifestDeserializer::DeserializeLocale(localeJsonObject); + + if (result) + { + auto& locale = result.value(); + + TryParseStringLocaleField(locale, localeJsonObject, ReleaseNotes); + TryParseStringLocaleField(locale, localeJsonObject, ReleaseNotesUrl); + + // Agreements + auto agreementsNode = JSON::GetRawJsonArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Agreements)); + if (agreementsNode) + { + std::vector agreements; + for (auto const& agreementNode : agreementsNode.value().get()) + { + Manifest::Agreement agreementEntry; + + agreementEntry.Label = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(AgreementLabel)).value_or(""); + agreementEntry.AgreementText = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(Agreement)).value_or(""); + agreementEntry.AgreementUrl = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(AgreementUrl)).value_or(""); + + if (!agreementEntry.Label.empty() || !agreementEntry.AgreementText.empty() || !agreementEntry.AgreementUrl.empty()) + { + agreements.emplace_back(std::move(agreementEntry)); + } + } + + if (!agreements.empty()) + { + locale.Add(std::move(agreements)); + } + } + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_1; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer.h index fdf608c5e9..135f4a70d9 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer.h @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Rest/Schema/1_0/Json/SearchRequestSerializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_1::Json -{ - // Search Result Serializer. - struct SearchRequestSerializer : public V1_0::Json::SearchRequestSerializer - { - protected: - std::optional ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Rest/Schema/1_0/Json/SearchRequestSerializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_1::Json +{ + // Search Result Serializer. + struct SearchRequestSerializer : public V1_0::Json::SearchRequestSerializer + { + protected: + std::optional ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer_1_1.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer_1_1.cpp index 60f37b1c63..f0c9ce2703 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/Json/SearchRequestSerializer_1_1.cpp @@ -1,17 +1,17 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchRequestSerializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_1::Json -{ - std::optional SearchRequestSerializer::ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const - { - if (field == PackageMatchField::Market) - { - return "Market"sv; - } - - return V1_0::Json::SearchRequestSerializer::ConvertPackageMatchFieldToString(field); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchRequestSerializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_1::Json +{ + std::optional SearchRequestSerializer::ConvertPackageMatchFieldToString(AppInstaller::Repository::PackageMatchField field) const + { + if (field == PackageMatchField::Market) + { + return "Market"sv; + } + + return V1_0::Json::SearchRequestSerializer::ConvertPackageMatchFieldToString(field); + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp index 586bc54f85..9bc0644c68 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_1/RestInterface_1_1.cpp @@ -1,213 +1,213 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_1/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include -#include - -using namespace std::string_view_literals; - -namespace AppInstaller::Repository::Rest::Schema::V1_1 -{ - namespace - { - // Query params - constexpr std::string_view MarketQueryParam = "Market"sv; - - // Response constants - constexpr std::string_view UnsupportedPackageMatchFields = "UnsupportedPackageMatchFields"sv; - constexpr std::string_view RequiredPackageMatchFields = "RequiredPackageMatchFields"sv; - constexpr std::string_view UnsupportedQueryParameters = "UnsupportedQueryParameters"sv; - constexpr std::string_view RequiredQueryParameters = "RequiredQueryParameters"sv; - } - - Interface::Interface( - const std::string& restApi, - const Http::HttpClientHelper& httpClientHelper, - IRestClient::Information information, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_0::Interface(restApi, httpClientHelper), m_information(std::move(information)) - { - m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_1_0.ToString()); - - if (!additionalHeaders.empty()) - { - m_requiredRestApiHeaders.insert(additionalHeaders.begin(), additionalHeaders.end()); - } - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_1_0; - } - - IRestClient::Information Interface::GetSourceInformation() const - { - return m_information; - } - - std::map Interface::GetValidatedQueryParams(const std::map& params) const - { - std::map result = params; - - for (auto const& param : m_information.RequiredQueryParameters) - { - if (params.end() == std::find_if(params.begin(), params.end(), [&](const auto& pair) { return Utility::CaseInsensitiveEquals(pair.first, param); })) - { - if (Utility::CaseInsensitiveEquals(param, MarketQueryParam)) - { - result.emplace(MarketQueryParam, Runtime::GetOSRegion()); - continue; - } - - AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Required query Parameter: " << param); - throw UnsupportedRequestException({}, {}, {}, m_information.RequiredQueryParameters); - } - } - - for (auto const& param : m_information.UnsupportedQueryParameters) - { - if (params.end() != std::find_if(params.begin(), params.end(), [&](const auto& pair) { return Utility::CaseInsensitiveEquals(pair.first, param); })) - { - AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Unsupported query Parameter: " << param); - throw UnsupportedRequestException({}, {}, m_information.UnsupportedQueryParameters, {}); - } - } - - return result; - } - - web::json::value Interface::GetValidatedSearchBody(const SearchRequest& searchRequest) const - { - SearchRequest resultSearchRequest = searchRequest; - - for (auto const& field : m_information.RequiredPackageMatchFields) - { - PackageMatchField matchField = ConvertStringToPackageMatchField(field); - - if (searchRequest.Filters.end() == std::find_if(searchRequest.Filters.begin(), searchRequest.Filters.end(), [&](const PackageMatchFilter& filter) { return filter.Field == matchField; })) - { - if (matchField == PackageMatchField::Market) - { - resultSearchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Market, MatchType::CaseInsensitive, Runtime::GetOSRegion())); - continue; - } - - AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Required package match field: " << field); - throw UnsupportedRequestException({}, m_information.RequiredPackageMatchFields, {}, {}); - } - } - - for (auto const& field : m_information.UnsupportedPackageMatchFields) - { - PackageMatchField matchField = ConvertStringToPackageMatchField(field); - - if (matchField == PackageMatchField::Unknown) - { - continue; - } - - if (searchRequest.Inclusions.end() != std::find_if(searchRequest.Inclusions.begin(), searchRequest.Inclusions.end(), [&](const PackageMatchFilter& inclusion) { return inclusion.Field == matchField; })) - { - AICLI_LOG(Repo, Info, << "Search request Inclusions contains package match field not supported by the rest source. Ignoring the field. Unsupported package match field: " << field); - - resultSearchRequest.Inclusions.erase( - std::remove_if( - resultSearchRequest.Inclusions.begin(), resultSearchRequest.Inclusions.end(), - [&](const PackageMatchFilter& inclusion) { return inclusion.Field == matchField; }), - resultSearchRequest.Inclusions.end()); - } - - if (searchRequest.Filters.end() != std::find_if(searchRequest.Filters.begin(), searchRequest.Filters.end(), [&](const PackageMatchFilter& filter) { return filter.Field == matchField; })) - { - AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Unsupported package match field: " << field); - throw UnsupportedRequestException(m_information.UnsupportedPackageMatchFields, {}, {}, {}); - } - } - - return V1_0::Interface::GetValidatedSearchBody(resultSearchRequest); - } - - IRestClient::SearchResult Interface::GetSearchResult(const web::json::value& searchResponseObject) const - { - IRestClient::SearchResult result = V1_0::Interface::GetSearchResult(searchResponseObject); - - if (result.Matches.size() == 0) - { - auto requiredPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(searchResponseObject, JSON::GetUtilityString(RequiredPackageMatchFields)); - auto unsupportedPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(searchResponseObject, JSON::GetUtilityString(UnsupportedPackageMatchFields)); - - if (requiredPackageMatchFields.size() != 0 || unsupportedPackageMatchFields.size() != 0) - { - AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source"); - throw UnsupportedRequestException(std::move(unsupportedPackageMatchFields), std::move(requiredPackageMatchFields), {}, {}); - } - } - - return result; - } - - std::vector Interface::GetParsedManifests(const web::json::value& manifestsResponseObject) const - { - auto result = V1_0::Interface::GetParsedManifests(manifestsResponseObject); - - if (result.size() == 0) - { - auto requiredQueryParameters = JSON::GetRawStringArrayFromJsonNode(manifestsResponseObject, JSON::GetUtilityString(RequiredQueryParameters)); - auto unsupportedQueryParameters = JSON::GetRawStringArrayFromJsonNode(manifestsResponseObject, JSON::GetUtilityString(UnsupportedQueryParameters)); - - if (requiredQueryParameters.size() != 0 || unsupportedQueryParameters.size() != 0) - { - AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source"); - throw UnsupportedRequestException({}, {}, std::move(unsupportedQueryParameters), std::move(requiredQueryParameters)); - } - } - - return result; - } - - PackageMatchField Interface::ConvertStringToPackageMatchField(std::string_view field) const - { - std::string toLower = Utility::ToLower(field); - - if (toLower == "command") - { - return PackageMatchField::Command; - } - else if (toLower == "packageidentifier") - { - return PackageMatchField::Id; - } - else if (toLower == "moniker") - { - return PackageMatchField::Moniker; - } - else if (toLower == "packagename") - { - return PackageMatchField::Name; - } - else if (toLower == "tag") - { - return PackageMatchField::Tag; - } - else if (toLower == "packagefamilyname") - { - return PackageMatchField::PackageFamilyName; - } - else if (toLower == "productcode") - { - return PackageMatchField::ProductCode; - } - else if (toLower == "normalizedpackagenameandpublisher") - { - return PackageMatchField::NormalizedNameAndPublisher; - } - else if (toLower == "market") - { - return PackageMatchField::Market; - } - - return PackageMatchField::Unknown; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_1/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +using namespace std::string_view_literals; + +namespace AppInstaller::Repository::Rest::Schema::V1_1 +{ + namespace + { + // Query params + constexpr std::string_view MarketQueryParam = "Market"sv; + + // Response constants + constexpr std::string_view UnsupportedPackageMatchFields = "UnsupportedPackageMatchFields"sv; + constexpr std::string_view RequiredPackageMatchFields = "RequiredPackageMatchFields"sv; + constexpr std::string_view UnsupportedQueryParameters = "UnsupportedQueryParameters"sv; + constexpr std::string_view RequiredQueryParameters = "RequiredQueryParameters"sv; + } + + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_0::Interface(restApi, httpClientHelper), m_information(std::move(information)) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_1_0.ToString()); + + if (!additionalHeaders.empty()) + { + m_requiredRestApiHeaders.insert(additionalHeaders.begin(), additionalHeaders.end()); + } + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_1_0; + } + + IRestClient::Information Interface::GetSourceInformation() const + { + return m_information; + } + + std::map Interface::GetValidatedQueryParams(const std::map& params) const + { + std::map result = params; + + for (auto const& param : m_information.RequiredQueryParameters) + { + if (params.end() == std::find_if(params.begin(), params.end(), [&](const auto& pair) { return Utility::CaseInsensitiveEquals(pair.first, param); })) + { + if (Utility::CaseInsensitiveEquals(param, MarketQueryParam)) + { + result.emplace(MarketQueryParam, Runtime::GetOSRegion()); + continue; + } + + AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Required query Parameter: " << param); + throw UnsupportedRequestException({}, {}, {}, m_information.RequiredQueryParameters); + } + } + + for (auto const& param : m_information.UnsupportedQueryParameters) + { + if (params.end() != std::find_if(params.begin(), params.end(), [&](const auto& pair) { return Utility::CaseInsensitiveEquals(pair.first, param); })) + { + AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Unsupported query Parameter: " << param); + throw UnsupportedRequestException({}, {}, m_information.UnsupportedQueryParameters, {}); + } + } + + return result; + } + + web::json::value Interface::GetValidatedSearchBody(const SearchRequest& searchRequest) const + { + SearchRequest resultSearchRequest = searchRequest; + + for (auto const& field : m_information.RequiredPackageMatchFields) + { + PackageMatchField matchField = ConvertStringToPackageMatchField(field); + + if (searchRequest.Filters.end() == std::find_if(searchRequest.Filters.begin(), searchRequest.Filters.end(), [&](const PackageMatchFilter& filter) { return filter.Field == matchField; })) + { + if (matchField == PackageMatchField::Market) + { + resultSearchRequest.Filters.emplace_back(PackageMatchFilter(PackageMatchField::Market, MatchType::CaseInsensitive, Runtime::GetOSRegion())); + continue; + } + + AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Required package match field: " << field); + throw UnsupportedRequestException({}, m_information.RequiredPackageMatchFields, {}, {}); + } + } + + for (auto const& field : m_information.UnsupportedPackageMatchFields) + { + PackageMatchField matchField = ConvertStringToPackageMatchField(field); + + if (matchField == PackageMatchField::Unknown) + { + continue; + } + + if (searchRequest.Inclusions.end() != std::find_if(searchRequest.Inclusions.begin(), searchRequest.Inclusions.end(), [&](const PackageMatchFilter& inclusion) { return inclusion.Field == matchField; })) + { + AICLI_LOG(Repo, Info, << "Search request Inclusions contains package match field not supported by the rest source. Ignoring the field. Unsupported package match field: " << field); + + resultSearchRequest.Inclusions.erase( + std::remove_if( + resultSearchRequest.Inclusions.begin(), resultSearchRequest.Inclusions.end(), + [&](const PackageMatchFilter& inclusion) { return inclusion.Field == matchField; }), + resultSearchRequest.Inclusions.end()); + } + + if (searchRequest.Filters.end() != std::find_if(searchRequest.Filters.begin(), searchRequest.Filters.end(), [&](const PackageMatchFilter& filter) { return filter.Field == matchField; })) + { + AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source. Unsupported package match field: " << field); + throw UnsupportedRequestException(m_information.UnsupportedPackageMatchFields, {}, {}, {}); + } + } + + return V1_0::Interface::GetValidatedSearchBody(resultSearchRequest); + } + + IRestClient::SearchResult Interface::GetSearchResult(const web::json::value& searchResponseObject) const + { + IRestClient::SearchResult result = V1_0::Interface::GetSearchResult(searchResponseObject); + + if (result.Matches.size() == 0) + { + auto requiredPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(searchResponseObject, JSON::GetUtilityString(RequiredPackageMatchFields)); + auto unsupportedPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(searchResponseObject, JSON::GetUtilityString(UnsupportedPackageMatchFields)); + + if (requiredPackageMatchFields.size() != 0 || unsupportedPackageMatchFields.size() != 0) + { + AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source"); + throw UnsupportedRequestException(std::move(unsupportedPackageMatchFields), std::move(requiredPackageMatchFields), {}, {}); + } + } + + return result; + } + + std::vector Interface::GetParsedManifests(const web::json::value& manifestsResponseObject) const + { + auto result = V1_0::Interface::GetParsedManifests(manifestsResponseObject); + + if (result.size() == 0) + { + auto requiredQueryParameters = JSON::GetRawStringArrayFromJsonNode(manifestsResponseObject, JSON::GetUtilityString(RequiredQueryParameters)); + auto unsupportedQueryParameters = JSON::GetRawStringArrayFromJsonNode(manifestsResponseObject, JSON::GetUtilityString(UnsupportedQueryParameters)); + + if (requiredQueryParameters.size() != 0 || unsupportedQueryParameters.size() != 0) + { + AICLI_LOG(Repo, Error, << "Search request is not supported by the rest source"); + throw UnsupportedRequestException({}, {}, std::move(unsupportedQueryParameters), std::move(requiredQueryParameters)); + } + } + + return result; + } + + PackageMatchField Interface::ConvertStringToPackageMatchField(std::string_view field) const + { + std::string toLower = Utility::ToLower(field); + + if (toLower == "command") + { + return PackageMatchField::Command; + } + else if (toLower == "packageidentifier") + { + return PackageMatchField::Id; + } + else if (toLower == "moniker") + { + return PackageMatchField::Moniker; + } + else if (toLower == "packagename") + { + return PackageMatchField::Name; + } + else if (toLower == "tag") + { + return PackageMatchField::Tag; + } + else if (toLower == "packagefamilyname") + { + return PackageMatchField::PackageFamilyName; + } + else if (toLower == "productcode") + { + return PackageMatchField::ProductCode; + } + else if (toLower == "normalizedpackagenameandpublisher") + { + return PackageMatchField::NormalizedNameAndPublisher; + } + else if (toLower == "market") + { + return PackageMatchField::Market; + } + + return PackageMatchField::Unknown; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Interface.h index 7ff88a28e3..9c5510a558 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Interface.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_9/Interface.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_10 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public V1_9::Interface - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_9/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_10 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_9::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer.h index 202440543f..6933372621 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer.h @@ -1,17 +1,17 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_9/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_10::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer : public V1_9::Json::ManifestDeserializer - { - protected: - - std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; - - Manifest::ManifestVer GetManifestVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_9/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_10::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_9::Json::ManifestDeserializer + { + protected: + + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer_1_10.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer_1_10.cpp index d4d5ce643c..7ce238632f 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer_1_10.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/Json/ManifestDeserializer_1_10.cpp @@ -1,30 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestDeserializer.h" -#include "Rest/Schema/AuthenticationInfoParser.h" -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_10::Json -{ - std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const - { - auto result = V1_9::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); - - if (result) - { - auto& installer = result.value(); - - installer.AuthInfo = ParseAuthenticationInfo(installerJsonObject, ParseAuthenticationInfoType::Installer, GetManifestVersion()); - } - - return result; - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1_10; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include "Rest/Schema/AuthenticationInfoParser.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_10::Json +{ + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_9::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + installer.AuthInfo = ParseAuthenticationInfo(installerJsonObject, ParseAuthenticationInfoType::Installer, GetManifestVersion()); + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_10; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/RestInterface_1_10.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/RestInterface_1_10.cpp index c53f1387d2..88fda53ac3 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_10/RestInterface_1_10.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_10/RestInterface_1_10.cpp @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_10/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_10 -{ - Interface::Interface( - const std::string& restApi, - const Http::HttpClientHelper& httpClientHelper, - IRestClient::Information information, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, - Authentication::AuthenticationArguments authArgs) : V1_9::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders, std::move(authArgs)) - { - m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_10_0.ToString()); - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_10_0; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_10/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_10 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, + Authentication::AuthenticationArguments authArgs) : V1_9::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders, std::move(authArgs)) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_10_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_10_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer_1_28.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer_1_28.cpp index 4224c0a98d..9974a835bd 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer_1_28.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_28/Json/ManifestDeserializer_1_28.cpp @@ -65,9 +65,9 @@ namespace AppInstaller::Repository::Rest::Schema::V1_28::Json } } } - - if (!resources.empty()) - { + + if (!resources.empty()) + { installer.DesiredStateConfiguration.emplace_back(std::move(*repositoryUrl), std::move(*moduleName), std::move(resources)); } } @@ -95,9 +95,9 @@ namespace AppInstaller::Repository::Rest::Schema::V1_28::Json } } } - - if (!resources.empty()) - { + + if (!resources.empty()) + { installer.DesiredStateConfiguration.emplace_back(std::move(resources)); } } diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h index 32764d6099..dd18df8705 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Interface.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_1/Interface.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_4 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public V1_1::Interface - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_1/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_4 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_1::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer.h index 514d2fde21..8ac3365d59 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_1/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_4::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer : public V1_1::Json::ManifestDeserializer - { - std::optional DeserializeLocale(const web::json::value& localeJsonObject) const override; - - std::optional DeserializeInstallationMetadata(const web::json::value& installationMetadataJsonObject) const override; - - protected: - std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; - - Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const override; - - Manifest::ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(std::string_view in) const override; - - Manifest::ManifestInstaller::ExpectedReturnCodeInfo DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const override; - - Manifest::ManifestVer GetManifestVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_1/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_4::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_1::Json::ManifestDeserializer + { + std::optional DeserializeLocale(const web::json::value& localeJsonObject) const override; + + std::optional DeserializeInstallationMetadata(const web::json::value& installationMetadataJsonObject) const override; + + protected: + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + Manifest::InstallerTypeEnum ConvertToInstallerType(std::string_view in) const override; + + Manifest::ExpectedReturnCodeEnum ConvertToExpectedReturnCodeEnum(std::string_view in) const override; + + Manifest::ManifestInstaller::ExpectedReturnCodeInfo DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer_1_4.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer_1_4.cpp index 813f413a4b..3abc5306e8 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer_1_4.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/ManifestDeserializer_1_4.cpp @@ -1,233 +1,233 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestDeserializer.h" -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_4::Json -{ - namespace - { - // Installer - constexpr std::string_view ReturnResponseUrl = "ReturnResponseUrl"sv; - constexpr std::string_view NestedInstallerType = "NestedInstallerType"sv; - constexpr std::string_view DisplayInstallWarnings = "DisplayInstallWarnings"sv; - constexpr std::string_view UnsupportedArguments = "UnsupportedArguments"sv; - constexpr std::string_view NestedInstallerFiles = "NestedInstallerFiles"sv; - constexpr std::string_view NestedInstallerFileRelativeFilePath = "RelativeFilePath"sv; - constexpr std::string_view PortableCommandAlias = "PortableCommandAlias"sv; - constexpr std::string_view InstallationMetadata = "InstallationMetadata"sv; - constexpr std::string_view DefaultInstallLocation = "DefaultInstallLocation"sv; - constexpr std::string_view InstallationMetadataFiles = "Files"sv; - constexpr std::string_view InstallationMetadataRelativeFilePath = "RelativeFilePath"sv; - constexpr std::string_view FileSha256 = "FileSha256"sv; - constexpr std::string_view FileType = "FileType"sv; - constexpr std::string_view InvocationParameter = "InvocationParameter"sv; - constexpr std::string_view DisplayName = "DisplayName"sv; - - // Locale - constexpr std::string_view InstallationNotes = "InstallationNotes"sv; - constexpr std::string_view PurchaseUrl = "PurchaseUrl"sv; - constexpr std::string_view Documentations = "Documentations"sv; - constexpr std::string_view DocumentLabel = "DocumentLabel"sv; - constexpr std::string_view DocumentUrl = "DocumentUrl"sv; - } - - Manifest::InstallerTypeEnum ManifestDeserializer::ConvertToInstallerType(std::string_view in) const - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "portable") - { - return InstallerTypeEnum::Portable; - } - - return V1_1::Json::ManifestDeserializer::ConvertToInstallerType(inStrLower); - } - - Manifest::ExpectedReturnCodeEnum ManifestDeserializer::ConvertToExpectedReturnCodeEnum(std::string_view in) const - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "custom") - { - return ExpectedReturnCodeEnum::Custom; - } - else if (inStrLower == "packageinusebyapplication") - { - return ExpectedReturnCodeEnum::PackageInUseByApplication; - } - else if (inStrLower == "invalidparameter") - { - return ExpectedReturnCodeEnum::InvalidParameter; - } - else if (inStrLower == "systemnotsupported") - { - return ExpectedReturnCodeEnum::SystemNotSupported; - } - - return V1_1::Json::ManifestDeserializer::ConvertToExpectedReturnCodeEnum(inStrLower); - } - - Manifest::ManifestInstaller::ExpectedReturnCodeInfo ManifestDeserializer::DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const - { - auto result = V1_1::Json::ManifestDeserializer::DeserializeExpectedReturnCodeInfo(expectedReturnCodeJsonObject); - result.ReturnResponseUrl = JSON::GetRawStringValueFromJsonNode(expectedReturnCodeJsonObject, JSON::GetUtilityString(ReturnResponseUrl)).value_or(""); - return result; - } - - std::optional ManifestDeserializer::DeserializeInstallationMetadata(const web::json::value& installationMetadataJsonObject) const - { - if (installationMetadataJsonObject.is_null() || !installationMetadataJsonObject.is_object()) - { - return {}; - } - - Manifest::InstallationMetadataInfo installationMetadata; - installationMetadata.DefaultInstallLocation = JSON::GetRawStringValueFromJsonNode(installationMetadataJsonObject, JSON::GetUtilityString(DefaultInstallLocation)).value_or(""); - - auto filesNode = JSON::GetRawJsonArrayFromJsonNode(installationMetadataJsonObject, JSON::GetUtilityString(InstallationMetadataFiles)); - if (filesNode) - { - for (auto const& fileNode : filesNode->get()) - { - std::optional relativeFilePath = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(InstallationMetadataRelativeFilePath)); - if (!JSON::IsValidNonEmptyStringValue(relativeFilePath)) - { - AICLI_LOG(Repo, Error, << "Missing RelativeFilePath in InstallationMetadata Files."); - return {}; - } - - Manifest::InstalledFile installedFile; - installedFile.RelativeFilePath = std::move(*relativeFilePath); - installedFile.InvocationParameter = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(InvocationParameter)).value_or(""); - installedFile.DisplayName = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(DisplayName)).value_or(""); - - std::optional sha256 = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(FileSha256)); - if (JSON::IsValidNonEmptyStringValue(sha256)) - { - installedFile.FileSha256 = Utility::SHA256::ConvertToBytes(*sha256); - } - - std::optional fileType = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(FileType)); - if (JSON::IsValidNonEmptyStringValue(fileType)) - { - installedFile.FileType = Manifest::ConvertToInstalledFileTypeEnum(*fileType); - } - - installationMetadata.Files.emplace_back(std::move(installedFile)); - } - } - - return installationMetadata; - } - - std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const - { - auto result = V1_1::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); - - if (result) - { - auto& installer = result.value(); - - std::optional nestedInstallerType = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(NestedInstallerType)); - if (nestedInstallerType) - { - installer.NestedInstallerType = ConvertToInstallerType(*nestedInstallerType); - } - - installer.DisplayInstallWarnings = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(DisplayInstallWarnings)).value_or(false); - - // UnsupportedArguments - auto unsupportedArguments = JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(UnsupportedArguments)); - for (auto const& unsupportedArgument : unsupportedArguments) - { - auto unsupportedArgumentEnum = Manifest::ConvertToUnsupportedArgumentEnum(unsupportedArgument); - if (unsupportedArgumentEnum != Manifest::UnsupportedArgumentEnum::Unknown) - { - installer.UnsupportedArguments.emplace_back(unsupportedArgumentEnum); - } - } - - // NestedInstallerFiles - auto nestedInstallerFilesNode = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(NestedInstallerFiles)); - if (nestedInstallerFilesNode) - { - for (auto const& nestedInstallerFileNode : nestedInstallerFilesNode->get()) - { - std::optional relativeFilePath = JSON::GetRawStringValueFromJsonNode(nestedInstallerFileNode, JSON::GetUtilityString(NestedInstallerFileRelativeFilePath)); - if (!JSON::IsValidNonEmptyStringValue(relativeFilePath)) - { - AICLI_LOG(Repo, Error, << "Missing RelativeFilePath in NestedInstallerFiles."); - return {}; - } - - Manifest::NestedInstallerFile nestedInstallerFile; - nestedInstallerFile.RelativeFilePath = std::move(*relativeFilePath); - nestedInstallerFile.PortableCommandAlias = JSON::GetRawStringValueFromJsonNode(nestedInstallerFileNode, JSON::GetUtilityString(PortableCommandAlias)).value_or(""); - - installer.NestedInstallerFiles.emplace_back(std::move(nestedInstallerFile)); - } - } - - // InstallationMetadata - auto installationMetadataNode = JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(InstallationMetadata)); - if (installationMetadataNode) - { - auto installationMetadata = DeserializeInstallationMetadata(installationMetadataNode->get()); - if (installationMetadata) - { - installer.InstallationMetadata = std::move(*installationMetadata); - } - } - } - - return result; - } - - std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const - { - auto result = V1_1::Json::ManifestDeserializer::DeserializeLocale(localeJsonObject); - - if (result) - { - auto& locale = result.value(); - - TryParseStringLocaleField(locale, localeJsonObject, InstallationNotes); - TryParseStringLocaleField(locale, localeJsonObject, PurchaseUrl); - - // Documentations - auto documentationsNode = JSON::GetRawJsonArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Documentations)); - if (documentationsNode) - { - std::vector documentations; - for (auto const& documentationNode : documentationsNode->get()) - { - Manifest::Documentation documentationEntry; - - documentationEntry.DocumentLabel = JSON::GetRawStringValueFromJsonNode(documentationNode, JSON::GetUtilityString(DocumentLabel)).value_or(""); - documentationEntry.DocumentUrl = JSON::GetRawStringValueFromJsonNode(documentationNode, JSON::GetUtilityString(DocumentUrl)).value_or(""); - - if (!documentationEntry.DocumentLabel.empty() || !documentationEntry.DocumentUrl.empty()) - { - documentations.emplace_back(std::move(documentationEntry)); - } - } - - if (!documentations.empty()) - { - locale.Add(std::move(documentations)); - } - } - } - - return result; - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1_4; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_4::Json +{ + namespace + { + // Installer + constexpr std::string_view ReturnResponseUrl = "ReturnResponseUrl"sv; + constexpr std::string_view NestedInstallerType = "NestedInstallerType"sv; + constexpr std::string_view DisplayInstallWarnings = "DisplayInstallWarnings"sv; + constexpr std::string_view UnsupportedArguments = "UnsupportedArguments"sv; + constexpr std::string_view NestedInstallerFiles = "NestedInstallerFiles"sv; + constexpr std::string_view NestedInstallerFileRelativeFilePath = "RelativeFilePath"sv; + constexpr std::string_view PortableCommandAlias = "PortableCommandAlias"sv; + constexpr std::string_view InstallationMetadata = "InstallationMetadata"sv; + constexpr std::string_view DefaultInstallLocation = "DefaultInstallLocation"sv; + constexpr std::string_view InstallationMetadataFiles = "Files"sv; + constexpr std::string_view InstallationMetadataRelativeFilePath = "RelativeFilePath"sv; + constexpr std::string_view FileSha256 = "FileSha256"sv; + constexpr std::string_view FileType = "FileType"sv; + constexpr std::string_view InvocationParameter = "InvocationParameter"sv; + constexpr std::string_view DisplayName = "DisplayName"sv; + + // Locale + constexpr std::string_view InstallationNotes = "InstallationNotes"sv; + constexpr std::string_view PurchaseUrl = "PurchaseUrl"sv; + constexpr std::string_view Documentations = "Documentations"sv; + constexpr std::string_view DocumentLabel = "DocumentLabel"sv; + constexpr std::string_view DocumentUrl = "DocumentUrl"sv; + } + + Manifest::InstallerTypeEnum ManifestDeserializer::ConvertToInstallerType(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "portable") + { + return InstallerTypeEnum::Portable; + } + + return V1_1::Json::ManifestDeserializer::ConvertToInstallerType(inStrLower); + } + + Manifest::ExpectedReturnCodeEnum ManifestDeserializer::ConvertToExpectedReturnCodeEnum(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "custom") + { + return ExpectedReturnCodeEnum::Custom; + } + else if (inStrLower == "packageinusebyapplication") + { + return ExpectedReturnCodeEnum::PackageInUseByApplication; + } + else if (inStrLower == "invalidparameter") + { + return ExpectedReturnCodeEnum::InvalidParameter; + } + else if (inStrLower == "systemnotsupported") + { + return ExpectedReturnCodeEnum::SystemNotSupported; + } + + return V1_1::Json::ManifestDeserializer::ConvertToExpectedReturnCodeEnum(inStrLower); + } + + Manifest::ManifestInstaller::ExpectedReturnCodeInfo ManifestDeserializer::DeserializeExpectedReturnCodeInfo(const web::json::value& expectedReturnCodeJsonObject) const + { + auto result = V1_1::Json::ManifestDeserializer::DeserializeExpectedReturnCodeInfo(expectedReturnCodeJsonObject); + result.ReturnResponseUrl = JSON::GetRawStringValueFromJsonNode(expectedReturnCodeJsonObject, JSON::GetUtilityString(ReturnResponseUrl)).value_or(""); + return result; + } + + std::optional ManifestDeserializer::DeserializeInstallationMetadata(const web::json::value& installationMetadataJsonObject) const + { + if (installationMetadataJsonObject.is_null() || !installationMetadataJsonObject.is_object()) + { + return {}; + } + + Manifest::InstallationMetadataInfo installationMetadata; + installationMetadata.DefaultInstallLocation = JSON::GetRawStringValueFromJsonNode(installationMetadataJsonObject, JSON::GetUtilityString(DefaultInstallLocation)).value_or(""); + + auto filesNode = JSON::GetRawJsonArrayFromJsonNode(installationMetadataJsonObject, JSON::GetUtilityString(InstallationMetadataFiles)); + if (filesNode) + { + for (auto const& fileNode : filesNode->get()) + { + std::optional relativeFilePath = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(InstallationMetadataRelativeFilePath)); + if (!JSON::IsValidNonEmptyStringValue(relativeFilePath)) + { + AICLI_LOG(Repo, Error, << "Missing RelativeFilePath in InstallationMetadata Files."); + return {}; + } + + Manifest::InstalledFile installedFile; + installedFile.RelativeFilePath = std::move(*relativeFilePath); + installedFile.InvocationParameter = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(InvocationParameter)).value_or(""); + installedFile.DisplayName = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(DisplayName)).value_or(""); + + std::optional sha256 = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(FileSha256)); + if (JSON::IsValidNonEmptyStringValue(sha256)) + { + installedFile.FileSha256 = Utility::SHA256::ConvertToBytes(*sha256); + } + + std::optional fileType = JSON::GetRawStringValueFromJsonNode(fileNode, JSON::GetUtilityString(FileType)); + if (JSON::IsValidNonEmptyStringValue(fileType)) + { + installedFile.FileType = Manifest::ConvertToInstalledFileTypeEnum(*fileType); + } + + installationMetadata.Files.emplace_back(std::move(installedFile)); + } + } + + return installationMetadata; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_1::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + std::optional nestedInstallerType = JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(NestedInstallerType)); + if (nestedInstallerType) + { + installer.NestedInstallerType = ConvertToInstallerType(*nestedInstallerType); + } + + installer.DisplayInstallWarnings = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(DisplayInstallWarnings)).value_or(false); + + // UnsupportedArguments + auto unsupportedArguments = JSON::GetRawStringArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(UnsupportedArguments)); + for (auto const& unsupportedArgument : unsupportedArguments) + { + auto unsupportedArgumentEnum = Manifest::ConvertToUnsupportedArgumentEnum(unsupportedArgument); + if (unsupportedArgumentEnum != Manifest::UnsupportedArgumentEnum::Unknown) + { + installer.UnsupportedArguments.emplace_back(unsupportedArgumentEnum); + } + } + + // NestedInstallerFiles + auto nestedInstallerFilesNode = JSON::GetRawJsonArrayFromJsonNode(installerJsonObject, JSON::GetUtilityString(NestedInstallerFiles)); + if (nestedInstallerFilesNode) + { + for (auto const& nestedInstallerFileNode : nestedInstallerFilesNode->get()) + { + std::optional relativeFilePath = JSON::GetRawStringValueFromJsonNode(nestedInstallerFileNode, JSON::GetUtilityString(NestedInstallerFileRelativeFilePath)); + if (!JSON::IsValidNonEmptyStringValue(relativeFilePath)) + { + AICLI_LOG(Repo, Error, << "Missing RelativeFilePath in NestedInstallerFiles."); + return {}; + } + + Manifest::NestedInstallerFile nestedInstallerFile; + nestedInstallerFile.RelativeFilePath = std::move(*relativeFilePath); + nestedInstallerFile.PortableCommandAlias = JSON::GetRawStringValueFromJsonNode(nestedInstallerFileNode, JSON::GetUtilityString(PortableCommandAlias)).value_or(""); + + installer.NestedInstallerFiles.emplace_back(std::move(nestedInstallerFile)); + } + } + + // InstallationMetadata + auto installationMetadataNode = JSON::GetJsonValueFromNode(installerJsonObject, JSON::GetUtilityString(InstallationMetadata)); + if (installationMetadataNode) + { + auto installationMetadata = DeserializeInstallationMetadata(installationMetadataNode->get()); + if (installationMetadata) + { + installer.InstallationMetadata = std::move(*installationMetadata); + } + } + } + + return result; + } + + std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const + { + auto result = V1_1::Json::ManifestDeserializer::DeserializeLocale(localeJsonObject); + + if (result) + { + auto& locale = result.value(); + + TryParseStringLocaleField(locale, localeJsonObject, InstallationNotes); + TryParseStringLocaleField(locale, localeJsonObject, PurchaseUrl); + + // Documentations + auto documentationsNode = JSON::GetRawJsonArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Documentations)); + if (documentationsNode) + { + std::vector documentations; + for (auto const& documentationNode : documentationsNode->get()) + { + Manifest::Documentation documentationEntry; + + documentationEntry.DocumentLabel = JSON::GetRawStringValueFromJsonNode(documentationNode, JSON::GetUtilityString(DocumentLabel)).value_or(""); + documentationEntry.DocumentUrl = JSON::GetRawStringValueFromJsonNode(documentationNode, JSON::GetUtilityString(DocumentUrl)).value_or(""); + + if (!documentationEntry.DocumentLabel.empty() || !documentationEntry.DocumentUrl.empty()) + { + documentations.emplace_back(std::move(documentationEntry)); + } + } + + if (!documentations.empty()) + { + locale.Add(std::move(documentations)); + } + } + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_4; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer.h index 87332f0f12..70e1fd0a1e 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer.h @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Rest/Schema/1_0/Json/SearchResponseDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_4::Json -{ - // Search Result Deserializer. - struct SearchResponseDeserializer : public V1_0::Json::SearchResponseDeserializer - { - protected: - std::optional DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Rest/Schema/1_0/Json/SearchResponseDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_4::Json +{ + // Search Result Deserializer. + struct SearchResponseDeserializer : public V1_0::Json::SearchResponseDeserializer + { + protected: + std::optional DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer_1_4.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer_1_4.cpp index b0dc84bce1..497a3ea831 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer_1_4.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/Json/SearchResponseDeserializer_1_4.cpp @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResponseDeserializer.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_4::Json -{ - namespace - { - // Search response constants - constexpr std::string_view UpgradeCodes = "UpgradeCodes"sv; - constexpr std::string_view AppsAndFeaturesEntryVersions = "AppsAndFeaturesEntryVersions"sv; - } - - std::optional SearchResponseDeserializer::DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const - { - auto result = V1_0::Json::SearchResponseDeserializer::DeserializeVersionInfo(versionInfoJsonObject); - if (result.has_value()) - { - result->UpgradeCodes = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(UpgradeCodes))); - auto arpVersions = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(AppsAndFeaturesEntryVersions))); - for (auto const& version : arpVersions) - { - result->ArpVersions.emplace_back(Utility::Version{ version }); - } - // Sort the arp versions for later querying - std::sort(result->ArpVersions.begin(), result->ArpVersions.end()); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResponseDeserializer.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_4::Json +{ + namespace + { + // Search response constants + constexpr std::string_view UpgradeCodes = "UpgradeCodes"sv; + constexpr std::string_view AppsAndFeaturesEntryVersions = "AppsAndFeaturesEntryVersions"sv; + } + + std::optional SearchResponseDeserializer::DeserializeVersionInfo(const web::json::value& versionInfoJsonObject) const + { + auto result = V1_0::Json::SearchResponseDeserializer::DeserializeVersionInfo(versionInfoJsonObject); + if (result.has_value()) + { + result->UpgradeCodes = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(UpgradeCodes))); + auto arpVersions = AppInstaller::Rest::GetUniqueItems(JSON::GetRawStringArrayFromJsonNode(versionInfoJsonObject, JSON::GetUtilityString(AppsAndFeaturesEntryVersions))); + for (auto const& version : arpVersions) + { + result->ArpVersions.emplace_back(Utility::Version{ version }); + } + // Sort the arp versions for later querying + std::sort(result->ArpVersions.begin(), result->ArpVersions.end()); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp index 8b2842a268..4140d5d14d 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_4/RestInterface_1_4.cpp @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_4/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_4 -{ - Interface::Interface( - const std::string& restApi, - const Http::HttpClientHelper& httpClientHelper, - IRestClient::Information information, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_1::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) - { - m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_4_0.ToString()); - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_4_0; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_4/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_4 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_1::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_4_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_4_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h index 4d15ea05a2..f8c42af93a 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Interface.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_4/Interface.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_5 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public V1_4::Interface - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_4/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_5 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_4::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer.h index 6f9d4bcf2c..8a7c8fe2ee 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer.h @@ -1,17 +1,17 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_4/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_5::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer : public V1_4::Json::ManifestDeserializer - { - std::optional DeserializeLocale(const web::json::value& localeJsonObject) const override; - - protected: - - Manifest::ManifestVer GetManifestVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_4/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_5::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_4::Json::ManifestDeserializer + { + std::optional DeserializeLocale(const web::json::value& localeJsonObject) const override; + + protected: + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer_1_5.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer_1_5.cpp index 3958847754..d3b837c6f8 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer_1_5.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/Json/ManifestDeserializer_1_5.cpp @@ -1,78 +1,78 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestDeserializer.h" -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_5::Json -{ - namespace - { - // Locale - constexpr std::string_view Icons = "Icons"sv; - constexpr std::string_view IconUrl = "IconUrl"sv; - constexpr std::string_view IconFileType = "IconFileType"sv; - constexpr std::string_view IconResolution = "IconResolution"sv; - constexpr std::string_view IconTheme = "IconTheme"sv; - constexpr std::string_view IconSha256 = "IconSha256"sv; - } - - std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const - { - auto result = V1_4::Json::ManifestDeserializer::DeserializeLocale(localeJsonObject); - - if (result) - { - auto& locale = result.value(); - - // Icons - auto iconsNode = JSON::GetRawJsonArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Icons)); - if (iconsNode) - { - std::vector icons; - for (auto const& iconNode : iconsNode->get()) - { - Manifest::Icon iconEntry; - - iconEntry.Url = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconUrl)).value_or(""); - if (iconEntry.Url.empty()) - { - continue; - } - - auto fileType = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconFileType)).value_or(""); - if (fileType.empty()) - { - continue; - } - - iconEntry.FileType = Manifest::ConvertToIconFileTypeEnum(fileType); - iconEntry.Resolution = Manifest::ConvertToIconResolutionEnum(JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconResolution)).value_or("")); - iconEntry.Theme = Manifest::ConvertToIconThemeEnum(JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconTheme)).value_or("")); - - std::optional sha256 = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconSha256)); - if (JSON::IsValidNonEmptyStringValue(sha256)) - { - iconEntry.Sha256 = Utility::SHA256::ConvertToBytes(*sha256); - } - - icons.emplace_back(std::move(iconEntry)); - } - - if (!icons.empty()) - { - locale.Add(std::move(icons)); - } - } - } - - return result; - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1_5; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_5::Json +{ + namespace + { + // Locale + constexpr std::string_view Icons = "Icons"sv; + constexpr std::string_view IconUrl = "IconUrl"sv; + constexpr std::string_view IconFileType = "IconFileType"sv; + constexpr std::string_view IconResolution = "IconResolution"sv; + constexpr std::string_view IconTheme = "IconTheme"sv; + constexpr std::string_view IconSha256 = "IconSha256"sv; + } + + std::optional ManifestDeserializer::DeserializeLocale(const web::json::value& localeJsonObject) const + { + auto result = V1_4::Json::ManifestDeserializer::DeserializeLocale(localeJsonObject); + + if (result) + { + auto& locale = result.value(); + + // Icons + auto iconsNode = JSON::GetRawJsonArrayFromJsonNode(localeJsonObject, JSON::GetUtilityString(Icons)); + if (iconsNode) + { + std::vector icons; + for (auto const& iconNode : iconsNode->get()) + { + Manifest::Icon iconEntry; + + iconEntry.Url = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconUrl)).value_or(""); + if (iconEntry.Url.empty()) + { + continue; + } + + auto fileType = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconFileType)).value_or(""); + if (fileType.empty()) + { + continue; + } + + iconEntry.FileType = Manifest::ConvertToIconFileTypeEnum(fileType); + iconEntry.Resolution = Manifest::ConvertToIconResolutionEnum(JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconResolution)).value_or("")); + iconEntry.Theme = Manifest::ConvertToIconThemeEnum(JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconTheme)).value_or("")); + + std::optional sha256 = JSON::GetRawStringValueFromJsonNode(iconNode, JSON::GetUtilityString(IconSha256)); + if (JSON::IsValidNonEmptyStringValue(sha256)) + { + iconEntry.Sha256 = Utility::SHA256::ConvertToBytes(*sha256); + } + + icons.emplace_back(std::move(iconEntry)); + } + + if (!icons.empty()) + { + locale.Add(std::move(icons)); + } + } + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_5; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp index 1501a7db19..0368db6538 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_5/RestInterface_1_5.cpp @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_5/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_5 -{ - Interface::Interface( - const std::string& restApi, - const Http::HttpClientHelper& httpClientHelper, - IRestClient::Information information, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_4::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) - { - m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_5_0.ToString()); - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_5_0; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_5/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_5 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_4::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_5_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_5_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h index 21947db52f..09e13d328b 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Interface.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_5/Interface.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_6 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public V1_5::Interface - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_5/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_6 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_5::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h index 6cbe8103b3..7d6f79bcec 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer.h @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_5/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_6::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer : public V1_5::Json::ManifestDeserializer - { - protected: - - std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; - - Manifest::UpdateBehaviorEnum ConvertToUpdateBehavior(std::string_view in) const override; - - Manifest::ManifestVer GetManifestVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_5/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_6::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_5::Json::ManifestDeserializer + { + protected: + + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + Manifest::UpdateBehaviorEnum ConvertToUpdateBehavior(std::string_view in) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp index a2bf65364b..e0f1c0e930 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/Json/ManifestDeserializer_1_6.cpp @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestDeserializer.h" -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_6::Json -{ - namespace - { - // Installer - constexpr std::string_view DownloadCommandProhibited = "DownloadCommandProhibited"sv; - } - - std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const - { - auto result = V1_5::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); - - if (result) - { - auto& installer = result.value(); - - installer.DownloadCommandProhibited = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(DownloadCommandProhibited)).value_or(false); - } - - return result; - } - - Manifest::UpdateBehaviorEnum ManifestDeserializer::ConvertToUpdateBehavior(std::string_view in) const - { - std::string inStrLower = Utility::ToLower(in); - - if (inStrLower == "deny") - { - return UpdateBehaviorEnum::Deny; - } - - return V1_5::Json::ManifestDeserializer::ConvertToUpdateBehavior(inStrLower); - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1_6; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_6::Json +{ + namespace + { + // Installer + constexpr std::string_view DownloadCommandProhibited = "DownloadCommandProhibited"sv; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_5::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + installer.DownloadCommandProhibited = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(DownloadCommandProhibited)).value_or(false); + } + + return result; + } + + Manifest::UpdateBehaviorEnum ManifestDeserializer::ConvertToUpdateBehavior(std::string_view in) const + { + std::string inStrLower = Utility::ToLower(in); + + if (inStrLower == "deny") + { + return UpdateBehaviorEnum::Deny; + } + + return V1_5::Json::ManifestDeserializer::ConvertToUpdateBehavior(inStrLower); + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_6; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp index e0bc7ba7a9..9cdc276c6e 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_6/RestInterface_1_6.cpp @@ -1,25 +1,25 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_6/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_6 -{ - Interface::Interface( - const std::string& restApi, - const Http::HttpClientHelper& httpClientHelper, - IRestClient::Information information, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_5::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) - { - m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_6_0.ToString()); - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_6_0; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_6/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_6 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders) : V1_5::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_6_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_6_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Interface.h index d4deb464ca..0034b36e0c 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Interface.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_6/Interface.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_7 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public V1_6::Interface - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - - Http::HttpClientHelper::HttpRequestHeaders GetAuthHeaders() const override; - - protected: - std::unique_ptr m_authenticator; - Authentication::AuthenticationArguments m_authArgs; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_6/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_7 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_6::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + + Http::HttpClientHelper::HttpRequestHeaders GetAuthHeaders() const override; + + protected: + std::unique_ptr m_authenticator; + Authentication::AuthenticationArguments m_authArgs; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h index d15326c1d6..e06e9dfaaf 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer.h @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_6/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_7::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer : public V1_6::Json::ManifestDeserializer - { - protected: - - std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; - - std::map DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const override; - - Manifest::ManifestVer GetManifestVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_6/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_7::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_6::Json::ManifestDeserializer + { + protected: + + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + std::map DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp index 3d2533837a..3e60630b44 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/Json/ManifestDeserializer_1_7.cpp @@ -1,50 +1,50 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestDeserializer.h" -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_7::Json -{ - namespace - { - // Installer - constexpr std::string_view InstallerSwitchRepair = "Repair"sv; - constexpr std::string_view RepairBehavior = "RepairBehavior"sv; - } - - std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const - { - auto result = V1_6::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); - - if (result) - { - auto& installer = result.value(); - - installer.RepairBehavior = Manifest::ConvertToRepairBehaviorEnum(JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(RepairBehavior)).value_or("")); - } - - return result; - } - - std::map ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const - { - auto installerSwitches = V1_6::Json::ManifestDeserializer::DeserializeInstallerSwitches(installerSwitchesJsonObject); - - auto repairValue = JSON::GetRawStringValueFromJsonNode(installerSwitchesJsonObject, JSON::GetUtilityString(InstallerSwitchRepair)); - - if (JSON::IsValidNonEmptyStringValue(repairValue)) - { - installerSwitches[Manifest::InstallerSwitchType::Repair] = repairValue.value(); - } - - return installerSwitches; - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1_7; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_7::Json +{ + namespace + { + // Installer + constexpr std::string_view InstallerSwitchRepair = "Repair"sv; + constexpr std::string_view RepairBehavior = "RepairBehavior"sv; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_6::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + installer.RepairBehavior = Manifest::ConvertToRepairBehaviorEnum(JSON::GetRawStringValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(RepairBehavior)).value_or("")); + } + + return result; + } + + std::map ManifestDeserializer::DeserializeInstallerSwitches(const web::json::value& installerSwitchesJsonObject) const + { + auto installerSwitches = V1_6::Json::ManifestDeserializer::DeserializeInstallerSwitches(installerSwitchesJsonObject); + + auto repairValue = JSON::GetRawStringValueFromJsonNode(installerSwitchesJsonObject, JSON::GetUtilityString(InstallerSwitchRepair)); + + if (JSON::IsValidNonEmptyStringValue(repairValue)) + { + installerSwitches[Manifest::InstallerSwitchType::Repair] = repairValue.value(); + } + + return installerSwitches; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_7; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/RestInterface_1_7.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/RestInterface_1_7.cpp index 5614c64acf..f862d6325c 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_7/RestInterface_1_7.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_7/RestInterface_1_7.cpp @@ -1,55 +1,55 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_7/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_7 -{ - Interface::Interface( - const std::string& restApi, - const Http::HttpClientHelper& httpClientHelper, - IRestClient::Information information, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, - Authentication::AuthenticationArguments authArgs) : V1_6::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders), m_authArgs(std::move(authArgs)) - { - m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_7_0.ToString()); - - if (m_information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId) - { - AICLI_LOG(Repo, Info, << "Creating authenticator for MicrosoftEntraId authentication. Source Identifier: " << m_information.SourceIdentifier); - m_authenticator = std::make_unique(m_information.Authentication, m_authArgs); - } - else if (m_information.Authentication.Type == Authentication::AuthenticationType::Unknown) - { - AICLI_LOG(Repo, Error, << "Authentication type unknown for rest source. Source Identifier: " << m_information.SourceIdentifier); - THROW_HR(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); - } - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_7_0; - } - - Http::HttpClientHelper::HttpRequestHeaders Interface::GetAuthHeaders() const - { - Http::HttpClientHelper::HttpRequestHeaders result; - - if (m_information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId) - { - auto authResult = m_authenticator->AuthenticateForToken(); - if (FAILED(authResult.Status)) - { - AICLI_LOG(Repo, Error, << "Authentication failed. Result: " << authResult.Status); - THROW_HR_MSG(authResult.Status, "Failed to authenticate for MicrosoftEntraId"); - } - result.insert_or_assign(web::http::header_names::authorization, JSON::GetUtilityString(Authentication::CreateBearerToken(authResult.Token))); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_7/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_7 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, + Authentication::AuthenticationArguments authArgs) : V1_6::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders), m_authArgs(std::move(authArgs)) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_7_0.ToString()); + + if (m_information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId) + { + AICLI_LOG(Repo, Info, << "Creating authenticator for MicrosoftEntraId authentication. Source Identifier: " << m_information.SourceIdentifier); + m_authenticator = std::make_unique(m_information.Authentication, m_authArgs); + } + else if (m_information.Authentication.Type == Authentication::AuthenticationType::Unknown) + { + AICLI_LOG(Repo, Error, << "Authentication type unknown for rest source. Source Identifier: " << m_information.SourceIdentifier); + THROW_HR(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + } + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_7_0; + } + + Http::HttpClientHelper::HttpRequestHeaders Interface::GetAuthHeaders() const + { + Http::HttpClientHelper::HttpRequestHeaders result; + + if (m_information.Authentication.Type == Authentication::AuthenticationType::MicrosoftEntraId) + { + auto authResult = m_authenticator->AuthenticateForToken(); + if (FAILED(authResult.Status)) + { + AICLI_LOG(Repo, Error, << "Authentication failed. Result: " << authResult.Status); + THROW_HR_MSG(authResult.Status, "Failed to authenticate for MicrosoftEntraId"); + } + result.insert_or_assign(web::http::header_names::authorization, JSON::GetUtilityString(Authentication::CreateBearerToken(authResult.Token))); + } + + return result; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Interface.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Interface.h index d9fb755c45..451927e387 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Interface.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Interface.h @@ -1,21 +1,21 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_7/Interface.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_9 -{ - // Interface to this schema version exposed through IRestClient. - struct Interface : public V1_7::Interface - { - Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); - - Interface(const Interface&) = delete; - Interface& operator=(const Interface&) = delete; - - Interface(Interface&&) = default; - Interface& operator=(Interface&&) = default; - - Utility::Version GetVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_7/Interface.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_9 +{ + // Interface to this schema version exposed through IRestClient. + struct Interface : public V1_7::Interface + { + Interface(const std::string& restApi, const Http::HttpClientHelper& helper, IRestClient::Information information, const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders = {}, Authentication::AuthenticationArguments authArgs = {}); + + Interface(const Interface&) = delete; + Interface& operator=(const Interface&) = delete; + + Interface(Interface&&) = default; + Interface& operator=(Interface&&) = default; + + Utility::Version GetVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer.h index 09dd1da76d..0c08701a58 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer.h @@ -1,17 +1,17 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/1_7/Json/ManifestDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema::V1_9::Json -{ - // Manifest Deserializer. - struct ManifestDeserializer : public V1_7::Json::ManifestDeserializer - { - protected: - - std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; - - Manifest::ManifestVer GetManifestVersion() const override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/1_7/Json/ManifestDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema::V1_9::Json +{ + // Manifest Deserializer. + struct ManifestDeserializer : public V1_7::Json::ManifestDeserializer + { + protected: + + std::optional DeserializeInstaller(const web::json::value& installerJsonObject) const override; + + Manifest::ManifestVer GetManifestVersion() const override; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer_1_9.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer_1_9.cpp index 714d3fe048..349581a297 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer_1_9.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/Json/ManifestDeserializer_1_9.cpp @@ -1,35 +1,35 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ManifestDeserializer.h" -#include - -using namespace AppInstaller::Manifest; - -namespace AppInstaller::Repository::Rest::Schema::V1_9::Json -{ - namespace - { - // Installer - constexpr std::string_view ArchiveBinariesDependOnPath = "ArchiveBinariesDependOnPath"sv; - } - - std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const - { - auto result = V1_7::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); - - if (result) - { - auto& installer = result.value(); - - installer.ArchiveBinariesDependOnPath = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ArchiveBinariesDependOnPath)).value_or(false); - } - - return result; - } - - Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const - { - return Manifest::s_ManifestVersionV1_9; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ManifestDeserializer.h" +#include + +using namespace AppInstaller::Manifest; + +namespace AppInstaller::Repository::Rest::Schema::V1_9::Json +{ + namespace + { + // Installer + constexpr std::string_view ArchiveBinariesDependOnPath = "ArchiveBinariesDependOnPath"sv; + } + + std::optional ManifestDeserializer::DeserializeInstaller(const web::json::value& installerJsonObject) const + { + auto result = V1_7::Json::ManifestDeserializer::DeserializeInstaller(installerJsonObject); + + if (result) + { + auto& installer = result.value(); + + installer.ArchiveBinariesDependOnPath = JSON::GetRawBoolValueFromJsonNode(installerJsonObject, JSON::GetUtilityString(ArchiveBinariesDependOnPath)).value_or(false); + } + + return result; + } + + Manifest::ManifestVer ManifestDeserializer::GetManifestVersion() const + { + return Manifest::s_ManifestVersionV1_9; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/RestInterface_1_9.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/RestInterface_1_9.cpp index b741b96530..f50412c1f2 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/1_9/RestInterface_1_9.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/1_9/RestInterface_1_9.cpp @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/1_9/Interface.h" -#include "Rest/Schema/CommonRestConstants.h" -#include "Rest/Schema/IRestClient.h" -#include -#include - -namespace AppInstaller::Repository::Rest::Schema::V1_9 -{ - Interface::Interface( - const std::string& restApi, - const Http::HttpClientHelper& httpClientHelper, - IRestClient::Information information, - const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, - Authentication::AuthenticationArguments authArgs) : V1_7::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders, std::move(authArgs)) - { - m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_9_0.ToString()); - } - - Utility::Version Interface::GetVersion() const - { - return Version_1_9_0; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/1_9/Interface.h" +#include "Rest/Schema/CommonRestConstants.h" +#include "Rest/Schema/IRestClient.h" +#include +#include + +namespace AppInstaller::Repository::Rest::Schema::V1_9 +{ + Interface::Interface( + const std::string& restApi, + const Http::HttpClientHelper& httpClientHelper, + IRestClient::Information information, + const Http::HttpClientHelper::HttpRequestHeaders& additionalHeaders, + Authentication::AuthenticationArguments authArgs) : V1_7::Interface(restApi, httpClientHelper, std::move(information), additionalHeaders, std::move(authArgs)) + { + m_requiredRestApiHeaders[JSON::GetUtilityString(ContractVersion)] = JSON::GetUtilityString(Version_1_9_0.ToString()); + } + + Utility::Version Interface::GetVersion() const + { + return Version_1_9_0; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp index de4819f28e..05fc79d7f6 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.cpp @@ -1,113 +1,113 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AuthenticationInfoParser.h" -#include - -namespace AppInstaller::Repository::Rest::Schema -{ - namespace - { - // Authentication info constants - 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 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: - // "Authentication": { - // "AuthenticationType": "microsoftEntraId", - // "MicrosoftEntraIdAuthenticationInfo" : { - // "Resource": "GUID", - // "Scope" : "test" - // } - // } - Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, ParseAuthenticationInfoType parseType, std::optional manifestVersion) - { - auto authenticationObject = JSON::GetJsonValueFromNode(dataObject, JSON::GetUtilityString(Authentication)); - if (!authenticationObject) - { - AICLI_LOG(Repo, Info, << "Authentication node not found. Assuming authentication type none."); - return {}; - } - - const auto& authenticationObjectNode = authenticationObject.value().get(); - if (authenticationObjectNode.is_null()) - { - AICLI_LOG(Repo, Info, << "Authentication node is null. Assuming authentication type none."); - return {}; - } - - Authentication::AuthenticationInfo result; - result.Type = Authentication::AuthenticationType::Unknown; - - 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)); - 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)); - if (microsoftEntraIdInfoObject) - { - const auto& microsoftEntraIdInfoNode = microsoftEntraIdInfoObject.value().get(); - - Authentication::MicrosoftEntraIdAuthenticationInfo microsoftEntraIdInfo; - - auto resourceString = JSON::GetRawStringValueFromJsonNode(microsoftEntraIdInfoNode, JSON::GetUtilityString(MicrosoftEntraId_Resource)); - // Resource required if MicrosoftEntraIdAuthenticationInfo exists and is not null. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, !JSON::IsValidNonEmptyStringValue(resourceString)); - microsoftEntraIdInfo.Resource = std::move(resourceString.value()); - - auto scopeString = JSON::GetRawStringValueFromJsonNode(microsoftEntraIdInfoNode, JSON::GetUtilityString(MicrosoftEntraId_Scope)); - if (JSON::IsValidNonEmptyStringValue(scopeString)) - { - microsoftEntraIdInfo.Scope = std::move(scopeString.value()); - } - - result.MicrosoftEntraIdInfo = std::move(microsoftEntraIdInfo); - } - - result.UpdateRequiredFieldsIfNecessary(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, !result.ValidateIntegrity()); - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "AuthenticationInfoParser.h" +#include + +namespace AppInstaller::Repository::Rest::Schema +{ + namespace + { + // Authentication info constants + 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 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: + // "Authentication": { + // "AuthenticationType": "microsoftEntraId", + // "MicrosoftEntraIdAuthenticationInfo" : { + // "Resource": "GUID", + // "Scope" : "test" + // } + // } + Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, ParseAuthenticationInfoType parseType, std::optional manifestVersion) + { + auto authenticationObject = JSON::GetJsonValueFromNode(dataObject, JSON::GetUtilityString(Authentication)); + if (!authenticationObject) + { + AICLI_LOG(Repo, Info, << "Authentication node not found. Assuming authentication type none."); + return {}; + } + + const auto& authenticationObjectNode = authenticationObject.value().get(); + if (authenticationObjectNode.is_null()) + { + AICLI_LOG(Repo, Info, << "Authentication node is null. Assuming authentication type none."); + return {}; + } + + Authentication::AuthenticationInfo result; + result.Type = Authentication::AuthenticationType::Unknown; + + 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)); + 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)); + if (microsoftEntraIdInfoObject) + { + const auto& microsoftEntraIdInfoNode = microsoftEntraIdInfoObject.value().get(); + + Authentication::MicrosoftEntraIdAuthenticationInfo microsoftEntraIdInfo; + + auto resourceString = JSON::GetRawStringValueFromJsonNode(microsoftEntraIdInfoNode, JSON::GetUtilityString(MicrosoftEntraId_Resource)); + // Resource required if MicrosoftEntraIdAuthenticationInfo exists and is not null. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, !JSON::IsValidNonEmptyStringValue(resourceString)); + microsoftEntraIdInfo.Resource = std::move(resourceString.value()); + + auto scopeString = JSON::GetRawStringValueFromJsonNode(microsoftEntraIdInfoNode, JSON::GetUtilityString(MicrosoftEntraId_Scope)); + if (JSON::IsValidNonEmptyStringValue(scopeString)) + { + microsoftEntraIdInfo.Scope = std::move(scopeString.value()); + } + + 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 ce68d4bcdc..3a78078c98 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/AuthenticationInfoParser.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -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: - // "Authentication": { - // "AuthenticationType": "microsoftEntraId", - // "MicrosoftEntraIdAuthenticationInfo" : { - // "Resource": "GUID", - // "Scope" : "test" - // } - // } - Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, ParseAuthenticationInfoType parseType, std::optional manifestVersion = {}); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +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: + // "Authentication": { + // "AuthenticationType": "microsoftEntraId", + // "MicrosoftEntraIdAuthenticationInfo" : { + // "Resource": "GUID", + // "Scope" : "test" + // } + // } + Authentication::AuthenticationInfo ParseAuthenticationInfo(const web::json::value& dataObject, ParseAuthenticationInfoType parseType, std::optional manifestVersion = {}); +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h index 2454e33e3e..5e365cfe97 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/CommonRestConstants.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::Repository::Rest::Schema -{ - // Winget supported contract versions - const Utility::Version Version_1_0_0{ "1.0.0" }; - const Utility::Version Version_1_1_0{ "1.1.0" }; - const Utility::Version Version_1_4_0{ "1.4.0" }; - const Utility::Version Version_1_5_0{ "1.5.0" }; - const Utility::Version Version_1_6_0{ "1.6.0" }; - const Utility::Version Version_1_7_0{ "1.7.0" }; - const Utility::Version Version_1_9_0{ "1.9.0" }; - const Utility::Version Version_1_10_0{ "1.10.0" }; - const Utility::Version Version_1_12_0{ "1.12.0" }; - const Utility::Version Version_1_28_0{ "1.28.0" }; - - // General API response constants - constexpr std::string_view Data = "Data"sv; - constexpr std::string_view ContinuationToken = "ContinuationToken"sv; - - // General API Header constant - constexpr std::string_view ContractVersion = "Version"sv; - - // General endpoint constants - constexpr std::string_view InformationGetEndpoint = "/information"sv; - constexpr std::string_view ManifestSearchPostEndpoint = "/manifestSearch"sv; - constexpr std::string_view ManifestByVersionAndChannelGetEndpoint = "/packageManifests/"sv; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::Repository::Rest::Schema +{ + // Winget supported contract versions + const Utility::Version Version_1_0_0{ "1.0.0" }; + const Utility::Version Version_1_1_0{ "1.1.0" }; + const Utility::Version Version_1_4_0{ "1.4.0" }; + const Utility::Version Version_1_5_0{ "1.5.0" }; + const Utility::Version Version_1_6_0{ "1.6.0" }; + const Utility::Version Version_1_7_0{ "1.7.0" }; + const Utility::Version Version_1_9_0{ "1.9.0" }; + const Utility::Version Version_1_10_0{ "1.10.0" }; + const Utility::Version Version_1_12_0{ "1.12.0" }; + const Utility::Version Version_1_28_0{ "1.28.0" }; + + // General API response constants + constexpr std::string_view Data = "Data"sv; + constexpr std::string_view ContinuationToken = "ContinuationToken"sv; + + // General API Header constant + constexpr std::string_view ContractVersion = "Version"sv; + + // General endpoint constants + constexpr std::string_view InformationGetEndpoint = "/information"sv; + constexpr std::string_view ManifestSearchPostEndpoint = "/manifestSearch"sv; + constexpr std::string_view ManifestByVersionAndChannelGetEndpoint = "/packageManifests/"sv; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h b/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h index 3a5a544a33..ac248c9453 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/IRestClient.h @@ -1,99 +1,99 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Repository::Rest::Schema -{ - // The common interface used to interact with RestAPI responses. - struct IRestClient - { - virtual ~IRestClient() = default; - - struct PackageInfo - { - std::string PackageIdentifier; - std::string PackageName; - std::string Publisher; - - PackageInfo(std::string packageIdentifier, std::string packageName, std::string publisher) - : PackageIdentifier(std::move(packageIdentifier)), PackageName(std::move(packageName)), Publisher(std::move(publisher)) {} - }; - - // NOTE: When changes are made to VersionInfo struct, remember to update the OptimizedSearch path in RestInterface1_0 - // where VersionInfo struct was directly created from manifest. - struct VersionInfo - { - AppInstaller::Utility::VersionAndChannel VersionAndChannel; - std::optional Manifest; - std::vector PackageFamilyNames; - std::vector ProductCodes; - std::vector ArpVersions; - std::vector UpgradeCodes; - - VersionInfo(AppInstaller::Utility::VersionAndChannel versionAndChannel, std::optional manifest, std::vector packageFamilyNames = {}, std::vector productCodes = {}, std::vector arpVersions = {}, std::vector upgradeCodes = {}) - : VersionAndChannel(std::move(versionAndChannel)), Manifest(std::move(manifest)), PackageFamilyNames(std::move(packageFamilyNames)), ProductCodes(std::move(productCodes)), ArpVersions(std::move(arpVersions)), UpgradeCodes(std::move(upgradeCodes)) {} - }; - - // Minimal information retrieved for any search request. - struct Package - { - PackageInfo PackageInformation; - std::vector Versions; - - Package(PackageInfo packageInfo, std::vector versions) - : PackageInformation(std::move(packageInfo)), Versions(std::move(versions)) {} - }; - - struct SearchResult - { - std::vector Matches; - bool Truncated = false; - }; - - struct SourceAgreementEntry - { - std::string Label; - std::string Text; - std::string Url; - }; - - // Information endpoint models - struct Information - { - std::string SourceIdentifier; - std::vector ServerSupportedVersions; - std::string SourceAgreementsIdentifier; - std::vector SourceAgreements; - std::vector UnsupportedPackageMatchFields; - std::vector RequiredPackageMatchFields; - std::vector UnsupportedQueryParameters; - std::vector RequiredQueryParameters; - Authentication::AuthenticationInfo Authentication; - - Information() {} - Information(std::string sourceId, std::vector versions) - : SourceIdentifier(std::move(sourceId)), ServerSupportedVersions(std::move(versions)) {} - }; - - // Get interface version. - virtual Utility::Version GetVersion() const = 0; - - // Get source information. - virtual Information GetSourceInformation() const = 0; - - // Performs a search based on the given criteria. - virtual SearchResult Search(const SearchRequest& request) const = 0; - - // Gets the manifest for given version - virtual std::optional GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const = 0; - - // Gets the manifests for given query parameters - virtual std::vector GetManifests(const std::string& packageId, const std::map& params = {}) const = 0; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Repository::Rest::Schema +{ + // The common interface used to interact with RestAPI responses. + struct IRestClient + { + virtual ~IRestClient() = default; + + struct PackageInfo + { + std::string PackageIdentifier; + std::string PackageName; + std::string Publisher; + + PackageInfo(std::string packageIdentifier, std::string packageName, std::string publisher) + : PackageIdentifier(std::move(packageIdentifier)), PackageName(std::move(packageName)), Publisher(std::move(publisher)) {} + }; + + // NOTE: When changes are made to VersionInfo struct, remember to update the OptimizedSearch path in RestInterface1_0 + // where VersionInfo struct was directly created from manifest. + struct VersionInfo + { + AppInstaller::Utility::VersionAndChannel VersionAndChannel; + std::optional Manifest; + std::vector PackageFamilyNames; + std::vector ProductCodes; + std::vector ArpVersions; + std::vector UpgradeCodes; + + VersionInfo(AppInstaller::Utility::VersionAndChannel versionAndChannel, std::optional manifest, std::vector packageFamilyNames = {}, std::vector productCodes = {}, std::vector arpVersions = {}, std::vector upgradeCodes = {}) + : VersionAndChannel(std::move(versionAndChannel)), Manifest(std::move(manifest)), PackageFamilyNames(std::move(packageFamilyNames)), ProductCodes(std::move(productCodes)), ArpVersions(std::move(arpVersions)), UpgradeCodes(std::move(upgradeCodes)) {} + }; + + // Minimal information retrieved for any search request. + struct Package + { + PackageInfo PackageInformation; + std::vector Versions; + + Package(PackageInfo packageInfo, std::vector versions) + : PackageInformation(std::move(packageInfo)), Versions(std::move(versions)) {} + }; + + struct SearchResult + { + std::vector Matches; + bool Truncated = false; + }; + + struct SourceAgreementEntry + { + std::string Label; + std::string Text; + std::string Url; + }; + + // Information endpoint models + struct Information + { + std::string SourceIdentifier; + std::vector ServerSupportedVersions; + std::string SourceAgreementsIdentifier; + std::vector SourceAgreements; + std::vector UnsupportedPackageMatchFields; + std::vector RequiredPackageMatchFields; + std::vector UnsupportedQueryParameters; + std::vector RequiredQueryParameters; + Authentication::AuthenticationInfo Authentication; + + Information() {} + Information(std::string sourceId, std::vector versions) + : SourceIdentifier(std::move(sourceId)), ServerSupportedVersions(std::move(versions)) {} + }; + + // Get interface version. + virtual Utility::Version GetVersion() const = 0; + + // Get source information. + virtual Information GetSourceInformation() const = 0; + + // Performs a search based on the given criteria. + virtual SearchResult Search(const SearchRequest& request) const = 0; + + // Gets the manifest for given version + virtual std::optional GetManifestByVersion(const std::string& packageId, const std::string& version, const std::string& channel) const = 0; + + // Gets the manifests for given query parameters + virtual std::vector GetManifests(const std::string& packageId, const std::map& params = {}) const = 0; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp index 5d67cc6b90..c3989adbc1 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.cpp @@ -1,142 +1,142 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Rest/Schema/IRestClient.h" -#include -#include "Rest/Schema/CommonRestConstants.h" -#include "AuthenticationInfoParser.h" -#include "InformationResponseDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema -{ - namespace - { - // Information response constants - constexpr std::string_view SourceIdentifier = "SourceIdentifier"sv; - constexpr std::string_view ServerSupportedVersions = "ServerSupportedVersions"sv; - - constexpr std::string_view SourceAgreements = "SourceAgreements"sv; - constexpr std::string_view SourceAgreementsIdentifier = "AgreementsIdentifier"sv; - constexpr std::string_view SourceAgreementsContent = "Agreements"sv; - constexpr std::string_view SourceAgreementLabel = "AgreementLabel"sv; - constexpr std::string_view SourceAgreementText = "Agreement"sv; - constexpr std::string_view SourceAgreementUrl = "AgreementUrl"sv; - - constexpr std::string_view UnsupportedPackageMatchFields = "UnsupportedPackageMatchFields"sv; - constexpr std::string_view RequiredPackageMatchFields = "RequiredPackageMatchFields"sv; - constexpr std::string_view UnsupportedQueryParameters = "UnsupportedQueryParameters"sv; - constexpr std::string_view RequiredQueryParameters = "RequiredQueryParameters"sv; - } - - IRestClient::Information InformationResponseDeserializer::Deserialize(const web::json::value& dataObject) const - { - // Get information result from json output. - std::optional information = DeserializeInformation(dataObject); - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !information); - - return information.value(); - } - - std::optional InformationResponseDeserializer::DeserializeInformation(const web::json::value& dataObject) const - { - try - { - if (dataObject.is_null()) - { - AICLI_LOG(Repo, Error, << "Missing json object."); - return {}; - } - - std::optional> data = JSON::GetJsonValueFromNode(dataObject, JSON::GetUtilityString(Data)); - if (!data) - { - AICLI_LOG(Repo, Error, << "Missing data"); - return {}; - } - - const auto& dataValue = data.value().get(); - std::optional sourceId = JSON::GetRawStringValueFromJsonNode(dataValue, JSON::GetUtilityString(SourceIdentifier)); - if (!JSON::IsValidNonEmptyStringValue(sourceId)) - { - AICLI_LOG(Repo, Error, << "Missing source identifier"); - return {}; - } - - std::vector allVersions = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(ServerSupportedVersions)); - if (allVersions.size() == 0) - { - AICLI_LOG(Repo, Error, << "Missing supported versions."); - return {}; - } - - IRestClient::Information info{ std::move(sourceId.value()), std::move(allVersions) }; - - auto agreements = JSON::GetJsonValueFromNode(dataValue, JSON::GetUtilityString(SourceAgreements)); - if (agreements) - { - const auto& agreementsValue = agreements.value().get(); - - auto agreementsIdentifier = JSON::GetRawStringValueFromJsonNode(agreementsValue, JSON::GetUtilityString(SourceAgreementsIdentifier)); - if (!JSON::IsValidNonEmptyStringValue(agreementsIdentifier)) - { - AICLI_LOG(Repo, Error, << "SourceAgreements node exists but AgreementsIdentifier is missing."); - return {}; - } - - info.SourceAgreementsIdentifier = std::move(agreementsIdentifier.value()); - - auto agreementsContent = JSON::GetRawJsonArrayFromJsonNode(agreementsValue, JSON::GetUtilityString(SourceAgreementsContent)); - if (agreementsContent) - { - for (auto const& agreementNode : agreementsContent.value().get()) - { - IRestClient::SourceAgreementEntry agreementEntry; - - std::optional label = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(SourceAgreementLabel)); - if (JSON::IsValidNonEmptyStringValue(label)) - { - agreementEntry.Label = std::move(label.value()); - } - - std::optional text = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(SourceAgreementText)); - if (JSON::IsValidNonEmptyStringValue(text)) - { - agreementEntry.Text = std::move(text.value()); - } - - std::optional url = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(SourceAgreementUrl)); - if (JSON::IsValidNonEmptyStringValue(url)) - { - agreementEntry.Url = std::move(url.value()); - } - - if (!agreementEntry.Label.empty() || !agreementEntry.Text.empty() || !agreementEntry.Url.empty()) - { - info.SourceAgreements.emplace_back(std::move(agreementEntry)); - } - } - } - } - - info.RequiredPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(RequiredPackageMatchFields)); - info.UnsupportedPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(UnsupportedPackageMatchFields)); - info.RequiredQueryParameters = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(RequiredQueryParameters)); - info.UnsupportedQueryParameters = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(UnsupportedQueryParameters)); - - info.Authentication = ParseAuthenticationInfo(dataValue, ParseAuthenticationInfoType::Source); - - return info; - } - catch (const std::exception& e) - { - AICLI_LOG(Repo, Error, << "Error encountered while deserializing Information. Reason: " << e.what()); - } - catch (...) - { - AICLI_LOG(Repo, Error, << "Received invalid information."); - } - - return {}; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Rest/Schema/IRestClient.h" +#include +#include "Rest/Schema/CommonRestConstants.h" +#include "AuthenticationInfoParser.h" +#include "InformationResponseDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema +{ + namespace + { + // Information response constants + constexpr std::string_view SourceIdentifier = "SourceIdentifier"sv; + constexpr std::string_view ServerSupportedVersions = "ServerSupportedVersions"sv; + + constexpr std::string_view SourceAgreements = "SourceAgreements"sv; + constexpr std::string_view SourceAgreementsIdentifier = "AgreementsIdentifier"sv; + constexpr std::string_view SourceAgreementsContent = "Agreements"sv; + constexpr std::string_view SourceAgreementLabel = "AgreementLabel"sv; + constexpr std::string_view SourceAgreementText = "Agreement"sv; + constexpr std::string_view SourceAgreementUrl = "AgreementUrl"sv; + + constexpr std::string_view UnsupportedPackageMatchFields = "UnsupportedPackageMatchFields"sv; + constexpr std::string_view RequiredPackageMatchFields = "RequiredPackageMatchFields"sv; + constexpr std::string_view UnsupportedQueryParameters = "UnsupportedQueryParameters"sv; + constexpr std::string_view RequiredQueryParameters = "RequiredQueryParameters"sv; + } + + IRestClient::Information InformationResponseDeserializer::Deserialize(const web::json::value& dataObject) const + { + // Get information result from json output. + std::optional information = DeserializeInformation(dataObject); + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, !information); + + return information.value(); + } + + std::optional InformationResponseDeserializer::DeserializeInformation(const web::json::value& dataObject) const + { + try + { + if (dataObject.is_null()) + { + AICLI_LOG(Repo, Error, << "Missing json object."); + return {}; + } + + std::optional> data = JSON::GetJsonValueFromNode(dataObject, JSON::GetUtilityString(Data)); + if (!data) + { + AICLI_LOG(Repo, Error, << "Missing data"); + return {}; + } + + const auto& dataValue = data.value().get(); + std::optional sourceId = JSON::GetRawStringValueFromJsonNode(dataValue, JSON::GetUtilityString(SourceIdentifier)); + if (!JSON::IsValidNonEmptyStringValue(sourceId)) + { + AICLI_LOG(Repo, Error, << "Missing source identifier"); + return {}; + } + + std::vector allVersions = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(ServerSupportedVersions)); + if (allVersions.size() == 0) + { + AICLI_LOG(Repo, Error, << "Missing supported versions."); + return {}; + } + + IRestClient::Information info{ std::move(sourceId.value()), std::move(allVersions) }; + + auto agreements = JSON::GetJsonValueFromNode(dataValue, JSON::GetUtilityString(SourceAgreements)); + if (agreements) + { + const auto& agreementsValue = agreements.value().get(); + + auto agreementsIdentifier = JSON::GetRawStringValueFromJsonNode(agreementsValue, JSON::GetUtilityString(SourceAgreementsIdentifier)); + if (!JSON::IsValidNonEmptyStringValue(agreementsIdentifier)) + { + AICLI_LOG(Repo, Error, << "SourceAgreements node exists but AgreementsIdentifier is missing."); + return {}; + } + + info.SourceAgreementsIdentifier = std::move(agreementsIdentifier.value()); + + auto agreementsContent = JSON::GetRawJsonArrayFromJsonNode(agreementsValue, JSON::GetUtilityString(SourceAgreementsContent)); + if (agreementsContent) + { + for (auto const& agreementNode : agreementsContent.value().get()) + { + IRestClient::SourceAgreementEntry agreementEntry; + + std::optional label = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(SourceAgreementLabel)); + if (JSON::IsValidNonEmptyStringValue(label)) + { + agreementEntry.Label = std::move(label.value()); + } + + std::optional text = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(SourceAgreementText)); + if (JSON::IsValidNonEmptyStringValue(text)) + { + agreementEntry.Text = std::move(text.value()); + } + + std::optional url = JSON::GetRawStringValueFromJsonNode(agreementNode, JSON::GetUtilityString(SourceAgreementUrl)); + if (JSON::IsValidNonEmptyStringValue(url)) + { + agreementEntry.Url = std::move(url.value()); + } + + if (!agreementEntry.Label.empty() || !agreementEntry.Text.empty() || !agreementEntry.Url.empty()) + { + info.SourceAgreements.emplace_back(std::move(agreementEntry)); + } + } + } + } + + info.RequiredPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(RequiredPackageMatchFields)); + info.UnsupportedPackageMatchFields = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(UnsupportedPackageMatchFields)); + info.RequiredQueryParameters = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(RequiredQueryParameters)); + info.UnsupportedQueryParameters = JSON::GetRawStringArrayFromJsonNode(dataValue, JSON::GetUtilityString(UnsupportedQueryParameters)); + + info.Authentication = ParseAuthenticationInfo(dataValue, ParseAuthenticationInfoType::Source); + + return info; + } + catch (const std::exception& e) + { + AICLI_LOG(Repo, Error, << "Error encountered while deserializing Information. Reason: " << e.what()); + } + catch (...) + { + AICLI_LOG(Repo, Error, << "Received invalid information."); + } + + return {}; + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.h b/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.h index d9d1a20d90..23d7139c01 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/InformationResponseDeserializer.h @@ -1,17 +1,17 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Rest/Schema/IRestClient.h" - -namespace AppInstaller::Repository::Rest::Schema -{ - // Information response Deserializer. - struct InformationResponseDeserializer - { - // Gets the information model for given response - IRestClient::Information Deserialize(const web::json::value& dataObject) const; - - protected: - std::optional DeserializeInformation(const web::json::value& dataObject) const; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Rest/Schema/IRestClient.h" + +namespace AppInstaller::Repository::Rest::Schema +{ + // Information response Deserializer. + struct InformationResponseDeserializer + { + // Gets the information model for given response + IRestClient::Information Deserialize(const web::json::value& dataObject) const; + + protected: + std::optional DeserializeInformation(const web::json::value& dataObject) const; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.cpp index 0332b2e78f..dc245fcd52 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.cpp @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchRequestComposer.h" -#include "Rest/Schema/1_0/Json/SearchRequestSerializer.h" -#include "Rest/Schema/1_1/Json/SearchRequestSerializer.h" - -namespace AppInstaller::Repository::Rest::Schema -{ - struct SearchRequestComposer::impl - { - // The serializer. We only have one lineage (1.0+) right now. - std::unique_ptr m_serializer; - }; - - SearchRequestComposer::SearchRequestComposer(SearchRequestComposer&&) noexcept = default; - SearchRequestComposer& SearchRequestComposer::operator=(SearchRequestComposer&&) noexcept = default; - - SearchRequestComposer::~SearchRequestComposer() = default; - - SearchRequestComposer::SearchRequestComposer(const Utility::Version& schemaVersion) - { - const auto& parts = schemaVersion.GetParts(); - THROW_HR_IF(E_INVALIDARG, parts.empty()); - - m_pImpl = std::make_unique(); - - if (parts[0].Integer == 1) - { - if (parts.size() == 1 || parts[1].Integer == 0) - { - m_pImpl->m_serializer = std::make_unique(); - } - else - { - m_pImpl->m_serializer = std::make_unique(); - } - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - } - - web::json::value SearchRequestComposer::Serialize(const AppInstaller::Repository::SearchRequest& searchRequest) const - { - return m_pImpl->m_serializer->Serialize(searchRequest); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchRequestComposer.h" +#include "Rest/Schema/1_0/Json/SearchRequestSerializer.h" +#include "Rest/Schema/1_1/Json/SearchRequestSerializer.h" + +namespace AppInstaller::Repository::Rest::Schema +{ + struct SearchRequestComposer::impl + { + // The serializer. We only have one lineage (1.0+) right now. + std::unique_ptr m_serializer; + }; + + SearchRequestComposer::SearchRequestComposer(SearchRequestComposer&&) noexcept = default; + SearchRequestComposer& SearchRequestComposer::operator=(SearchRequestComposer&&) noexcept = default; + + SearchRequestComposer::~SearchRequestComposer() = default; + + SearchRequestComposer::SearchRequestComposer(const Utility::Version& schemaVersion) + { + const auto& parts = schemaVersion.GetParts(); + THROW_HR_IF(E_INVALIDARG, parts.empty()); + + m_pImpl = std::make_unique(); + + if (parts[0].Integer == 1) + { + if (parts.size() == 1 || parts[1].Integer == 0) + { + m_pImpl->m_serializer = std::make_unique(); + } + else + { + m_pImpl->m_serializer = std::make_unique(); + } + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + } + + web::json::value SearchRequestComposer::Serialize(const AppInstaller::Repository::SearchRequest& searchRequest) const + { + return m_pImpl->m_serializer->Serialize(searchRequest); + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.h b/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.h index 9f3cedaae3..b491a60169 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/SearchRequestComposer.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include - -namespace AppInstaller::Repository::Rest::Schema -{ - // Exposes functions for creating JSON REST request body from search requests. - struct SearchRequestComposer - { - SearchRequestComposer(const Utility::Version& schemaVersion); - - SearchRequestComposer(const SearchRequestComposer&) = delete; - SearchRequestComposer& operator=(const SearchRequestComposer&) = delete; - - SearchRequestComposer(SearchRequestComposer&&) noexcept; - SearchRequestComposer& operator=(SearchRequestComposer&&) noexcept; - - ~SearchRequestComposer(); - - // Create search request rest call body from a given SearchRequest - web::json::value Serialize(const AppInstaller::Repository::SearchRequest& searchRequest) const; - - private: - struct impl; - std::unique_ptr m_pImpl; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include + +namespace AppInstaller::Repository::Rest::Schema +{ + // Exposes functions for creating JSON REST request body from search requests. + struct SearchRequestComposer + { + SearchRequestComposer(const Utility::Version& schemaVersion); + + SearchRequestComposer(const SearchRequestComposer&) = delete; + SearchRequestComposer& operator=(const SearchRequestComposer&) = delete; + + SearchRequestComposer(SearchRequestComposer&&) noexcept; + SearchRequestComposer& operator=(SearchRequestComposer&&) noexcept; + + ~SearchRequestComposer(); + + // Create search request rest call body from a given SearchRequest + web::json::value Serialize(const AppInstaller::Repository::SearchRequest& searchRequest) const; + + private: + struct impl; + std::unique_ptr m_pImpl; + }; +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.cpp b/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.cpp index 91661f904e..5712c8b322 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.cpp +++ b/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.cpp @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SearchResponseParser.h" -#include "Rest/Schema/1_0/Json/SearchResponseDeserializer.h" -#include "Rest/Schema/1_4/Json/SearchResponseDeserializer.h" - -namespace AppInstaller::Repository::Rest::Schema -{ - struct SearchResponseParser::impl - { - // The deserializer. We only have one lineage (1.0+) right now. - std::unique_ptr m_deserializer; - }; - - SearchResponseParser::SearchResponseParser(SearchResponseParser&&) noexcept = default; - SearchResponseParser& SearchResponseParser::operator=(SearchResponseParser&&) noexcept = default; - - SearchResponseParser::~SearchResponseParser() = default; - - SearchResponseParser::SearchResponseParser(const Utility::Version& schemaVersion) - { - const auto& parts = schemaVersion.GetParts(); - THROW_HR_IF(E_INVALIDARG, parts.empty()); - - m_pImpl = std::make_unique(); - - if (parts[0].Integer == 1) - { - if (parts.size() == 1 || parts[1].Integer < 4) - { - m_pImpl->m_deserializer = std::make_unique(); - } - else - { - m_pImpl->m_deserializer = std::make_unique(); - } - } - else - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); - } - } - - IRestClient::SearchResult SearchResponseParser::Deserialize(const web::json::value& searchResultJsonObject) const - { - return m_pImpl->m_deserializer->Deserialize(searchResultJsonObject); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SearchResponseParser.h" +#include "Rest/Schema/1_0/Json/SearchResponseDeserializer.h" +#include "Rest/Schema/1_4/Json/SearchResponseDeserializer.h" + +namespace AppInstaller::Repository::Rest::Schema +{ + struct SearchResponseParser::impl + { + // The deserializer. We only have one lineage (1.0+) right now. + std::unique_ptr m_deserializer; + }; + + SearchResponseParser::SearchResponseParser(SearchResponseParser&&) noexcept = default; + SearchResponseParser& SearchResponseParser::operator=(SearchResponseParser&&) noexcept = default; + + SearchResponseParser::~SearchResponseParser() = default; + + SearchResponseParser::SearchResponseParser(const Utility::Version& schemaVersion) + { + const auto& parts = schemaVersion.GetParts(); + THROW_HR_IF(E_INVALIDARG, parts.empty()); + + m_pImpl = std::make_unique(); + + if (parts[0].Integer == 1) + { + if (parts.size() == 1 || parts[1].Integer < 4) + { + m_pImpl->m_deserializer = std::make_unique(); + } + else + { + m_pImpl->m_deserializer = std::make_unique(); + } + } + else + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_UNSUPPORTED_TYPE)); + } + } + + IRestClient::SearchResult SearchResponseParser::Deserialize(const web::json::value& searchResultJsonObject) const + { + return m_pImpl->m_deserializer->Deserialize(searchResultJsonObject); + } +} diff --git a/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.h b/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.h index a810e54174..5cfadce8a6 100644 --- a/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.h +++ b/src/AppInstallerRepositoryCore/Rest/Schema/SearchResponseParser.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include "Rest/Schema/IRestClient.h" - -#include -#include - -namespace AppInstaller::Repository::Rest::Schema -{ - // Exposes functions for parsing JSON REST responses to IRestClient SearchResult. - struct SearchResponseParser - { - SearchResponseParser(const Utility::Version& schemaVersion); - - SearchResponseParser(const SearchResponseParser&) = delete; - SearchResponseParser& operator=(const SearchResponseParser&) = delete; - - SearchResponseParser(SearchResponseParser&&) noexcept; - SearchResponseParser& operator=(SearchResponseParser&&) noexcept; - - ~SearchResponseParser(); - - // Gets the search result for response object - IRestClient::SearchResult Deserialize(const web::json::value& searchResultJsonObject) const; - - private: - struct impl; - std::unique_ptr m_pImpl; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include "Rest/Schema/IRestClient.h" + +#include +#include + +namespace AppInstaller::Repository::Rest::Schema +{ + // Exposes functions for parsing JSON REST responses to IRestClient SearchResult. + struct SearchResponseParser + { + SearchResponseParser(const Utility::Version& schemaVersion); + + SearchResponseParser(const SearchResponseParser&) = delete; + SearchResponseParser& operator=(const SearchResponseParser&) = delete; + + SearchResponseParser(SearchResponseParser&&) noexcept; + SearchResponseParser& operator=(SearchResponseParser&&) noexcept; + + ~SearchResponseParser(); + + // Gets the search result for response object + IRestClient::SearchResult Deserialize(const web::json::value& searchResultJsonObject) const; + + private: + struct impl; + std::unique_ptr m_pImpl; + }; +} diff --git a/src/AppInstallerRepositoryCore/SourceFactory.h b/src/AppInstallerRepositoryCore/SourceFactory.h index 0445d950aa..85a6ffd25a 100644 --- a/src/AppInstallerRepositoryCore/SourceFactory.h +++ b/src/AppInstallerRepositoryCore/SourceFactory.h @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include - -#include - - -namespace AppInstaller::Repository -{ - // Interface for manipulating a source based on its details. - struct ISourceFactory - { - virtual ~ISourceFactory() = default; - - // Gets the name of the source type. - virtual std::string_view TypeName() const = 0; - - // Creates a source object from the given details. - virtual std::shared_ptr Create(const SourceDetails& details) = 0; - - // Adds the source from the given details, writing back to the details any changes. - // Return value indicates whether the action completed. - virtual bool Add(SourceDetails& details, IProgressCallback& progress) = 0; - - // Updates the source from the given details (may not change the details). - // Return value indicates whether the action completed. - virtual bool Update(const SourceDetails& details, IProgressCallback& progress) = 0; - - // Updates the source from the given details (may not change the details). - // This version is for use in automatic, background updates to the source. - // It is done this way to preserve the signature for use with member function pointers. - // Return value indicates whether the action completed. - virtual bool BackgroundUpdate(const SourceDetails& details, IProgressCallback& progress) - { - return Update(details, progress); - } - - // Removes the source from the given details. - // Return value indicates whether the action completed. - virtual bool Remove(const SourceDetails& details, IProgressCallback& progress) = 0; - - // Gets the factory for the given type. - static std::unique_ptr GetForType(std::string_view type); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include + +#include + + +namespace AppInstaller::Repository +{ + // Interface for manipulating a source based on its details. + struct ISourceFactory + { + virtual ~ISourceFactory() = default; + + // Gets the name of the source type. + virtual std::string_view TypeName() const = 0; + + // Creates a source object from the given details. + virtual std::shared_ptr Create(const SourceDetails& details) = 0; + + // Adds the source from the given details, writing back to the details any changes. + // Return value indicates whether the action completed. + virtual bool Add(SourceDetails& details, IProgressCallback& progress) = 0; + + // Updates the source from the given details (may not change the details). + // Return value indicates whether the action completed. + virtual bool Update(const SourceDetails& details, IProgressCallback& progress) = 0; + + // Updates the source from the given details (may not change the details). + // This version is for use in automatic, background updates to the source. + // It is done this way to preserve the signature for use with member function pointers. + // Return value indicates whether the action completed. + virtual bool BackgroundUpdate(const SourceDetails& details, IProgressCallback& progress) + { + return Update(details, progress); + } + + // Removes the source from the given details. + // Return value indicates whether the action completed. + virtual bool Remove(const SourceDetails& details, IProgressCallback& progress) = 0; + + // Gets the factory for the given type. + static std::unique_ptr GetForType(std::string_view type); + }; +} diff --git a/src/AppInstallerRepositoryCore/SourceList.cpp b/src/AppInstallerRepositoryCore/SourceList.cpp index 87c5c4e302..33b3622c99 100644 --- a/src/AppInstallerRepositoryCore/SourceList.cpp +++ b/src/AppInstallerRepositoryCore/SourceList.cpp @@ -1,1071 +1,1071 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SourceList.h" -#include "SourcePolicy.h" -#include "Microsoft/PreIndexedPackageSourceFactory.h" -#include "Rest/RestSourceFactory.h" - -#include -#include -#include - -using namespace AppInstaller::Settings; -using namespace std::string_view_literals; - -namespace AppInstaller::Repository -{ - namespace - { - constexpr std::string_view s_SourcesYaml_Sources = "Sources"sv; - constexpr std::string_view s_SourcesYaml_Source_Name = "Name"sv; - constexpr std::string_view s_SourcesYaml_Source_Type = "Type"sv; - constexpr std::string_view s_SourcesYaml_Source_Arg = "Arg"sv; - constexpr std::string_view s_SourcesYaml_Source_Data = "Data"sv; - constexpr std::string_view s_SourcesYaml_Source_Identifier = "Identifier"sv; - constexpr std::string_view s_SourcesYaml_Source_IsTombstone = "IsTombstone"sv; - constexpr std::string_view s_SourcesYaml_Source_IsOverride = "IsOverride"sv; - constexpr std::string_view s_SourcesYaml_Source_Explicit = "Explicit"sv; - constexpr std::string_view s_SourcesYaml_Source_TrustLevel = "TrustLevel"sv; - constexpr std::string_view s_SourcesYaml_Source_Priority = "Priority"sv; - - constexpr std::string_view s_MetadataYaml_Sources = "Sources"sv; - constexpr std::string_view s_MetadataYaml_Source_Name = "Name"sv; - constexpr std::string_view s_MetadataYaml_Source_LastUpdate = "LastUpdate"sv; - constexpr std::string_view s_MetadataYaml_Source_DoNotUpdateBefore = "DoNotUpdateBefore"sv; - constexpr std::string_view s_MetadataYaml_Source_AcceptedAgreementsIdentifier = "AcceptedAgreementsIdentifier"sv; - constexpr std::string_view s_MetadataYaml_Source_AcceptedAgreementFields = "AcceptedAgreementFields"sv; - - constexpr std::string_view s_Source_WingetCommunityDefault_Name = "winget"sv; - constexpr std::string_view s_Source_WingetCommunityDefault_Arg = "https://cdn.winget.microsoft.com/cache"sv; - constexpr std::string_view s_Source_WingetCommunityDefault_Data = "Microsoft.Winget.Source_8wekyb3d8bbwe"sv; - constexpr std::string_view s_Source_WingetCommunityDefault_Identifier = "Microsoft.Winget.Source_8wekyb3d8bbwe"sv; - - constexpr std::string_view s_Source_MSStoreDefault_Name = "msstore"sv; - constexpr std::string_view s_Source_MSStoreDefault_Arg = "https://storeedgefd.dsx.mp.microsoft.com/v9.0"sv; - constexpr std::string_view s_Source_MSStoreDefault_Identifier = "StoreEdgeFD"sv; - - constexpr std::string_view s_Source_DesktopFrameworks_Name = "microsoft.builtin.desktop.frameworks"sv; - constexpr std::string_view s_Source_DesktopFrameworks_Arg = "https://cdn.winget.microsoft.com/platform"sv; - constexpr std::string_view s_Source_DesktopFrameworks_Data = "Microsoft.Winget.Platform.Source_8wekyb3d8bbwe"sv; - constexpr std::string_view s_Source_DesktopFrameworks_Identifier = "Microsoft.Winget.Platform.Source_8wekyb3d8bbwe"sv; - - constexpr std::string_view s_Source_WingetCommunityFont_Name = "winget-font"sv; - constexpr std::string_view s_Source_WingetCommunityFont_Arg = "https://cdn.winget.microsoft.com/fonts"sv; - constexpr std::string_view s_Source_WingetCommunityFont_Data = "Microsoft.Winget.Fonts.Source_8wekyb3d8bbwe"sv; - constexpr std::string_view s_Source_WingetCommunityFont_Identifier = "Microsoft.Winget.Fonts.Source_8wekyb3d8bbwe"sv; - - // Attempts to read a single scalar value from the node. - template - bool TryReadScalar(std::string_view settingName, const std::string& settingValue, const YAML::Node& sourceNode, std::string_view name, Value& value, bool required = true) - { - YAML::Node valueNode = sourceNode[std::string{ name }]; - - if (!valueNode || !valueNode.IsScalar()) - { - if (required) - { - AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (" << name << " is invalid within a source):\n" << settingValue); - } - return false; - } - - value = valueNode.as(); - return true; - } - - // Attempts to read the source details from the given stream. - // Results are all or nothing; if any failures occur, no details are returned. - bool TryReadSourceDetails( - std::string_view settingName, - std::istream& stream, - std::string_view rootName, - std::function parse, - std::vector& sourceDetails) - { - std::vector result; - std::string settingValue = Utility::ReadEntireStream(stream); - - YAML::Node document; - try - { - document = YAML::Load(settingValue); - } - catch (const std::exception& e) - { - AICLI_LOG(YAML, Error, << "Setting '" << settingName << "' contained invalid YAML (" << e.what() << "):\n" << settingValue); - return false; - } - - try - { - YAML::Node sources = document[rootName]; - if (!sources) - { - AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (missing " << rootName << "):\n" << settingValue); - return false; - } - - if (sources.IsNull()) - { - // An empty sources is an acceptable thing. - return true; - } - - if (!sources.IsSequence()) - { - AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (" << rootName << " was not a sequence):\n" << settingValue); - return false; - } - - for (const auto& source : sources.Sequence()) - { - SourceDetailsInternal details; - if (!parse(details, settingValue, source)) - { - return false; - } - - result.emplace_back(std::move(details)); - } - } - catch (const std::exception& e) - { - AICLI_LOG(YAML, Error, << "Setting '" << settingName << "' contained unexpected YAML (" << e.what() << "):\n" << settingValue); - return false; - } - - sourceDetails = std::move(result); - return true; - } - - // Gets the source details from a particular setting, or an empty optional if no setting exists. - std::optional> TryGetSourcesFromSetting( - Settings::Stream& setting, - std::string_view rootName, - std::function parse) - { - auto sourcesStream = setting.Get(); - if (!sourcesStream) - { - // Note that this case is different than the one in which all sources have been removed. - return {}; - } - else - { - std::vector result; - if (!TryReadSourceDetails(setting.GetName(), *sourcesStream, rootName, parse, result)) - { - AICLI_LOG(YAML, Error, << "Ignoring corrupted source data."); - } - return result; - } - } - - // Gets the source details from a particular setting. - std::vector GetSourcesFromSetting( - Settings::Stream& setting, - std::string_view rootName, - std::function parse) - { - return TryGetSourcesFromSetting(setting, rootName, parse).value_or(std::vector{}); - } - - // Sets the sources for a particular setting, from a particular origin. - [[nodiscard]] bool SetSourcesToSettingWithFilter(Settings::Stream& setting, SourceOrigin origin, const std::vector& sources) - { - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << s_SourcesYaml_Sources; - out << YAML::BeginSeq; - - for (const auto& details : sources) - { - if (details.Origin == origin) - { - out << YAML::BeginMap; - out << YAML::Key << s_SourcesYaml_Source_Name << YAML::Value << details.Name; - out << YAML::Key << s_SourcesYaml_Source_Type << YAML::Value << details.Type; - out << YAML::Key << s_SourcesYaml_Source_Arg << YAML::Value << details.Arg; - out << YAML::Key << s_SourcesYaml_Source_Data << YAML::Value << details.Data; - out << YAML::Key << s_SourcesYaml_Source_Identifier << YAML::Value << details.Identifier; - out << YAML::Key << s_SourcesYaml_Source_IsTombstone << YAML::Value << details.IsTombstone; - out << YAML::Key << s_SourcesYaml_Source_IsOverride << YAML::Value << details.IsOverride; - out << YAML::Key << s_SourcesYaml_Source_Explicit << YAML::Value << details.Explicit; - out << YAML::Key << s_SourcesYaml_Source_TrustLevel << YAML::Value << static_cast(details.TrustLevel); - out << YAML::Key << s_SourcesYaml_Source_Priority << YAML::Value << details.Priority; - out << YAML::EndMap; - } - } - - out << YAML::EndSeq; - out << YAML::EndMap; - - return setting.Set(out.str()); - } - - // Assumes that names match already - bool DoSourceDetailsInternalMatch(const SourceDetailsInternal& left, const SourceDetailsInternal& right) - { - return left.Arg == right.Arg && - left.Identifier == right.Identifier && - Utility::CaseInsensitiveEquals(left.Type, right.Type); - } - - bool ShouldBeHidden(const SourceDetailsInternal& details) - { - return details.IsTombstone || details.Origin == SourceOrigin::Metadata || !details.IsVisible; - } - } - - void SourceDetailsInternal::CopyMetadataFieldsTo(SourceDetailsInternal& target) - { - if (LastUpdateTime > target.LastUpdateTime) - { - target.LastUpdateTime = LastUpdateTime; - } - - if (DoNotUpdateBefore > target.DoNotUpdateBefore) - { - target.DoNotUpdateBefore = DoNotUpdateBefore; - } - - target.AcceptedAgreementFields = AcceptedAgreementFields; - target.AcceptedAgreementsIdentifier = AcceptedAgreementsIdentifier; - } - - void SourceDetailsInternal::CopyMetadataFieldsFrom(const SourceDetails& source) - { - LastUpdateTime = source.LastUpdateTime; - DoNotUpdateBefore = source.DoNotUpdateBefore; - } - - void SourceDetailsInternal::ResetMetadataFields() - { - LastUpdateTime = {}; - DoNotUpdateBefore = {}; - AcceptedAgreementFields = 0; - AcceptedAgreementsIdentifier = {}; - } - - void SourceDetailsInternal::CopyOverrideFieldsFrom(const SourceDetails& overrideSource) - { - // These are the supported Override fields. - Explicit = overrideSource.Explicit; - Priority = overrideSource.Priority; - } - - bool SourceDetailsInternal::operator<(const SourceDetailsInternal& other) const - { - // Higher values come first in ordering and must be "less than" for standard sorting - return Priority > other.Priority; - } - - std::string_view GetWellKnownSourceName(WellKnownSource source) - { - switch (source) - { - case WellKnownSource::WinGet: - return s_Source_WingetCommunityDefault_Name; - case WellKnownSource::MicrosoftStore: - return s_Source_MSStoreDefault_Name; - case WellKnownSource::DesktopFrameworks: - return s_Source_DesktopFrameworks_Name; - case WellKnownSource::WinGetFont: - return s_Source_WingetCommunityFont_Name; - } - - return {}; - } - - std::string_view GetWellKnownSourceArg(WellKnownSource source) - { - switch (source) - { - case WellKnownSource::WinGet: - return s_Source_WingetCommunityDefault_Arg; - case WellKnownSource::MicrosoftStore: - return s_Source_MSStoreDefault_Arg; - case WellKnownSource::DesktopFrameworks: - return s_Source_DesktopFrameworks_Arg; - case WellKnownSource::WinGetFont: - return s_Source_WingetCommunityFont_Arg; - } - - return {}; - } - - std::string_view GetWellKnownSourceIdentifier(WellKnownSource source) - { - switch (source) - { - case WellKnownSource::WinGet: - return s_Source_WingetCommunityDefault_Identifier; - case WellKnownSource::MicrosoftStore: - return s_Source_MSStoreDefault_Identifier; - case WellKnownSource::DesktopFrameworks: - return s_Source_DesktopFrameworks_Identifier; - case WellKnownSource::WinGetFont: - return s_Source_WingetCommunityFont_Identifier; - } - - return {}; - } - - std::optional CheckForWellKnownSourceMatch(std::string_view name, std::string_view arg, std::string_view type) - { - if (name == s_Source_WingetCommunityDefault_Name && arg == s_Source_WingetCommunityDefault_Arg && type == Microsoft::PreIndexedPackageSourceFactory::Type()) - { - return WellKnownSource::WinGet; - } - - if (name == s_Source_MSStoreDefault_Name && arg == s_Source_MSStoreDefault_Arg && type == Rest::RestSourceFactory::Type()) - { - return WellKnownSource::MicrosoftStore; - } - - if (name == s_Source_DesktopFrameworks_Name && arg == s_Source_DesktopFrameworks_Arg && type == Microsoft::PreIndexedPackageSourceFactory::Type()) - { - return WellKnownSource::DesktopFrameworks; - } - - if (name == s_Source_WingetCommunityFont_Name && arg == s_Source_WingetCommunityFont_Arg && type == Rest::RestSourceFactory::Type()) - { - return WellKnownSource::WinGetFont; - } - - return {}; - } - - SourceDetailsInternal GetWellKnownSourceDetailsInternal(WellKnownSource source) - { - switch (source) - { - case WellKnownSource::WinGet: - { - SourceDetailsInternal details; - details.Origin = SourceOrigin::Default; - details.Name = s_Source_WingetCommunityDefault_Name; - details.Type = Microsoft::PreIndexedPackageSourceFactory::Type(); - details.Arg = s_Source_WingetCommunityDefault_Arg; - details.Data = s_Source_WingetCommunityDefault_Data; - details.Identifier = s_Source_WingetCommunityDefault_Identifier; - details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin; - return details; - } - case WellKnownSource::MicrosoftStore: - { - SourceDetailsInternal details; - details.Origin = SourceOrigin::Default; - details.Name = s_Source_MSStoreDefault_Name; - details.Type = Rest::RestSourceFactory::Type(); - details.Arg = s_Source_MSStoreDefault_Arg; - details.Identifier = s_Source_MSStoreDefault_Identifier; - details.TrustLevel = SourceTrustLevel::Trusted; - details.SupportInstalledSearchCorrelation = false; - - if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::BypassCertificatePinningForMicrosoftStore)) - { - using namespace AppInstaller::Certificates; - - PinningChain chain; - auto chainElement = chain.Root(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chainElement = chainElement.Next(); - chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - PinningChain chain2; - auto chainElement2 = chain2.Root(); - chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); - chainElement2 = chainElement2.Next(); - chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - chainElement2 = chainElement2.Next(); - chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); - - // See https://aka.ms/AzureTLSCAs (internal) for the source of these CAs - PinningChain chain3; - chain3.PartialChain().Root()-> - LoadCertificate(IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2, CERTIFICATE_RESOURCE_TYPE). - SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); - - PinningChain chain4; - chain4.PartialChain().Root()-> - LoadCertificate(IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2, CERTIFICATE_RESOURCE_TYPE). - SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); - - details.CertificatePinningConfiguration = PinningConfiguration("Microsoft Store Source"); - details.CertificatePinningConfiguration.AddChain(std::move(chain)); - details.CertificatePinningConfiguration.AddChain(std::move(chain2)); - details.CertificatePinningConfiguration.AddChain(std::move(chain3)); - details.CertificatePinningConfiguration.AddChain(std::move(chain4)); - } - - return details; - } - case WellKnownSource::DesktopFrameworks: - { - SourceDetailsInternal details; - details.Origin = SourceOrigin::Default; - details.Name = s_Source_DesktopFrameworks_Name; - details.Type = Microsoft::PreIndexedPackageSourceFactory::Type(); - details.Arg = s_Source_DesktopFrameworks_Arg; - details.Data = s_Source_DesktopFrameworks_Data; - details.Identifier = s_Source_DesktopFrameworks_Identifier; - details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin; - details.IsVisible = false; - return details; - } - case WellKnownSource::WinGetFont: - { - SourceDetailsInternal details; - details.Origin = SourceOrigin::Default; - details.Name = s_Source_WingetCommunityFont_Name; - details.Type = Microsoft::PreIndexedPackageSourceFactory::Type(); - details.Arg = s_Source_WingetCommunityFont_Arg; - details.Data = s_Source_WingetCommunityFont_Data; - details.Identifier = s_Source_WingetCommunityFont_Identifier; - details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin; - details.Explicit = true; - return details; - } - } - - THROW_HR(E_UNEXPECTED); - } - - SourceList::SourceList() : m_userSourcesStream(Stream::UserSources), m_metadataStream(Stream::SourcesMetadata) - { - OverwriteSourceList(); - OverwriteMetadata(); - } - - std::vector> SourceList::GetCurrentSourceRefs() - { - std::vector> result; - - for (auto& s : m_sourceList) - { - if (!ShouldBeHidden(s)) - { - result.emplace_back(std::ref(s)); - } - else - { - AICLI_LOG(Repo, Verbose, << "GetCurrentSourceRefs: Source named '" << s.Name << "' from origin " << ToString(s.Origin) << " is hidden and is dropped."); - } - } - - return result; - } - - auto SourceList::FindSource(std::string_view name, bool includeHidden) - { - return std::find_if(m_sourceList.begin(), m_sourceList.end(), - [name, includeHidden](const SourceDetailsInternal& sd) - { - return Utility::ICUCaseInsensitiveEquals(sd.Name, name) && - (includeHidden || !ShouldBeHidden(sd)); - }); - } - - bool SourceList::TryFindSourceByOrigin(std::string_view name, SourceOrigin origin, SourceDetailsInternal& targetSourceOut, bool includeHidden) - { - auto defaultSources = GetSourcesByOrigin(origin); - auto iter = std::find_if(defaultSources.begin(), defaultSources.end(), - [name, includeHidden](const SourceDetailsInternal& sd) - { - return Utility::ICUCaseInsensitiveEquals(sd.Name, name) && - (includeHidden || !ShouldBeHidden(sd)); - }); - - if (iter == defaultSources.end()) - { - return false; - } - - targetSourceOut = (*iter); - return true; - } - - SourceDetailsInternal* SourceList::GetCurrentSource(std::string_view name) - { - auto itr = FindSource(name); - return itr == m_sourceList.end() ? nullptr : &(*itr); - } - - SourceDetailsInternal* SourceList::GetSource(std::string_view name) - { - auto itr = FindSource(name, true); - return itr == m_sourceList.end() ? nullptr : &(*itr); - } - - void SourceList::AddSource(const SourceDetailsInternal& details) - { - bool sourcesSet = false; - - for (size_t i = 0; !sourcesSet && i < 10; ++i) - { - auto itr = FindSource(details.Name, true); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, - itr != m_sourceList.end() && itr->Origin != SourceOrigin::Metadata && !itr->IsTombstone); - - // Erase the source's entry if applicable - if (itr != m_sourceList.end()) - { - m_sourceList.erase(itr); - } - - m_sourceList.emplace_back(details); - - sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); - - if (!sourcesSet) - { - OverwriteSourceList(); - OverwriteMetadata(); - } - } - - THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); - - SaveMetadataInternal(details); - } - - void SourceList::RemoveSource(const SourceDetailsInternal& detailsRef) - { - // Copy the incoming details because we might destroy the referenced structure - // when reloading the source details from settings. - SourceDetailsInternal details = detailsRef; - bool sourcesSet = false; - - for (size_t i = 0; !sourcesSet && i < 10; ++i) - { - switch (details.Origin) - { - case SourceOrigin::Default: - { - auto target = FindSource(details.Name, true); - if (target == m_sourceList.end()) - { - THROW_HR_MSG(E_UNEXPECTED, "Default source not in SourceList"); - } - - if (!target->IsTombstone) - { - SourceDetailsInternal tombstone; - tombstone.Name = details.Name; - tombstone.IsTombstone = true; - tombstone.Origin = SourceOrigin::User; - m_sourceList.emplace_back(std::move(tombstone)); - } - } - break; - case SourceOrigin::User: - { - auto target = FindSource(details.Name); - if (target == m_sourceList.end()) - { - // Assumed that an update to the sources removed it first - return; - } - - // If this is an override of a default source, turn this into a tombstone instead of removing it. - if (target->IsOverride) - { - target->IsOverride = false; - target->IsTombstone = true; - break; - } - - m_sourceList.erase(target); - } - break; - case SourceOrigin::GroupPolicy: - // This should have already been blocked higher up. - AICLI_LOG(Repo, Error, << "Attempting to remove Group Policy source: " << details.Name); - THROW_HR(E_UNEXPECTED); - default: - THROW_HR(E_UNEXPECTED); - } - - sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); - - if (!sourcesSet) - { - OverwriteSourceList(); - OverwriteMetadata(); - } - } - - THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); - - SaveMetadataInternal(details, true); - } - - void SourceList::EditSource(const SourceDetailsInternal& detailsRef) - { - // Copy the incoming details because we might destroy the referenced structure - // when reloading the source details from settings. - SourceDetailsInternal details = detailsRef; - bool sourcesSet = false; - - for (size_t i = 0; !sourcesSet && i < 10; ++i) - { - switch (details.Origin) - { - case SourceOrigin::Default: - { - auto target = FindSource(details.Name, true); - if (target == m_sourceList.end()) - { - THROW_HR_MSG(E_UNEXPECTED, "Default source not in SourceList"); - } - - if (!target->IsTombstone) - { - // Copy the original and then apply the override fields. - SourceDetailsInternal override = *target; - override.Origin = SourceOrigin::User; - override.IsOverride = true; - override.CopyOverrideFieldsFrom(details); - m_sourceList.emplace_back(std::move(override)); - } - } - break; - case SourceOrigin::User: - { - auto target = FindSource(details.Name); - if (target == m_sourceList.end()) - { - // Assumed that an update to the sources removed it first - return; - } - - // Editing a User Source is just replacing the fields that can be edited. - target->CopyOverrideFieldsFrom(details); - } - break; - case SourceOrigin::GroupPolicy: - // This should have already been blocked higher up. - AICLI_LOG(Repo, Error, << "Attempting to edit a Group Policy source: " << details.Name); - THROW_HR(E_UNEXPECTED); - default: - THROW_HR(E_UNEXPECTED); - } - - sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); - - if (!sourcesSet) - { - OverwriteSourceList(); - OverwriteMetadata(); - } - } - - THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); - - SaveMetadataInternal(details, true); - } - - void SourceList::ResetSource(const SourceDetailsInternal& detailsRef) - { - // Copy the incoming details because we might destroy the referenced structure - // when reloading the source details from settings. - SourceDetailsInternal details = detailsRef; - bool sourcesSet = false; - - for (size_t i = 0; !sourcesSet && i < 10; ++i) - { - // Remove any user-level entry for this source (tombstone or override). - auto itr = FindSource(details.Name, true); - if (itr != m_sourceList.end() && itr->Origin == SourceOrigin::User) - { - m_sourceList.erase(itr); - } - - sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); - - if (!sourcesSet) - { - OverwriteSourceList(); - OverwriteMetadata(); - } - } - - THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); - - // Reload to bring back the Default source (now that the user entry is removed). - OverwriteSourceList(); - - ResetMetadataInternal(details); - } - - void SourceList::SaveMetadata(const SourceDetailsInternal& details) - { - SaveMetadataInternal(details); - } - - bool SourceList::CheckSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields) - { - if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty()) - { - // No agreements to be accepted. - return true; - } - - auto detailsInternal = GetCurrentSource(sourceName); - if (!detailsInternal) - { - // Source not found. - return false; - } - - return static_cast(agreementFields) == detailsInternal->AcceptedAgreementFields && - agreementsIdentifier == detailsInternal->AcceptedAgreementsIdentifier; - } - - void SourceList::SaveAcceptedSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields) - { - if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty()) - { - // No agreements to be accepted. - return; - } - - auto detailsInternal = GetCurrentSource(sourceName); - if (!detailsInternal) - { - // No source to update. - return; - } - - detailsInternal->AcceptedAgreementFields = static_cast(agreementFields); - detailsInternal->AcceptedAgreementsIdentifier = agreementsIdentifier; - - SaveMetadataInternal(*detailsInternal); - } - - void SourceList::RemoveSettingsStreams() - { - Stream{ Stream::UserSources }.Remove(); - Stream{ Stream::SourcesMetadata }.Remove(); - } - - void SourceList::OverwriteSourceList() - { - m_sourceList.clear(); - - for (SourceOrigin origin : { SourceOrigin::GroupPolicy, SourceOrigin::User, SourceOrigin::Default }) - { - auto forOrigin = GetSourcesByOrigin(origin); - - for (auto&& source : forOrigin) - { - auto foundSource = GetSource(source.Name); - if (!foundSource) - { - // Name not already defined, add it - m_sourceList.emplace_back(std::move(source)); - } - else - { - AICLI_LOG(Repo, Info, << "Source named '" << foundSource->Name << "' is already defined at origin " << ToString(foundSource->Origin) << - ". The source from origin " << ToString(origin) << " is dropped."); - } - } - } - - if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) - { - std::stable_sort(m_sourceList.begin(), m_sourceList.end()); - } - } - - void SourceList::OverwriteMetadata() - { - auto metadata = GetMetadata(); - for (auto& metaSource : metadata) - { - auto source = GetSource(metaSource.Name); - if (source) - { - metaSource.CopyMetadataFieldsTo(*source); - } - else - { - m_sourceList.emplace_back(std::move(metaSource)); - } - } - } - - // Gets the sources from a particular origin. - std::vector SourceList::GetSourcesByOrigin(SourceOrigin origin) - { - std::vector result; - - switch (origin) - { - case SourceOrigin::Default: - { - if (IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore)) - { - result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::MicrosoftStore)); - } - - if (IsWellKnownSourceEnabled(WellKnownSource::WinGet)) - { - result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::WinGet)); - } - - if (IsWellKnownSourceEnabled(WellKnownSource::WinGetFont)) - { - result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::WinGetFont)); - } - - // Since the source is not visible outside, this is added just to have the source in the internal - // list for tracking updates. Thus there is no need to check a policy. - result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::DesktopFrameworks)); - } - break; - case SourceOrigin::User: - { - std::vector userSources = GetSourcesFromSetting( - m_userSourcesStream, - s_SourcesYaml_Sources, - [&](SourceDetailsInternal& details, const std::string& settingValue, const YAML::Node& source) - { - std::string_view name = m_userSourcesStream.GetName(); - if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Name, details.Name)) { return false; } - if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Type, details.Type)) { return false; } - if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Arg, details.Arg)) { return false; } - if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Data, details.Data)) { return false; } - if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_IsTombstone, details.IsTombstone)) { return false; } - TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Explicit, details.Explicit, false); - TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Identifier, details.Identifier, false); - TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_IsOverride, details.IsOverride, false); - TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Priority, details.Priority, false); - - int64_t trustLevelValue; - if (TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_TrustLevel, trustLevelValue, false)) - { - details.TrustLevel = static_cast(trustLevelValue); - } - - return true; - }); - - for (auto& source : userSources) - { - // Check source against list of allowed sources and drop tombstones for required sources - if (!IsUserSourceAllowedByPolicy(source.Name, source.Type, source.Arg, source.IsTombstone)) - { - AICLI_LOG(Repo, Warning, << "User source " << source.Name << " dropped because of group policy"); - continue; - } - - // If this is an override source, we need to get the target of the override and apply the override data on top of it. - if (source.IsOverride) - { - SourceDetailsInternal override; - if (!TryFindSourceByOrigin(source.Name, SourceOrigin::Default, override)) - { - // The default source may be disabled, in which case it may not be returned in the list of default sources. - AICLI_LOG(Repo, Warning, << "User source " << source.Name << " is an override for a nonexistent Default Source."); - continue; - } - - override.CopyOverrideFieldsFrom(source); - override.Origin = SourceOrigin::User; - override.IsOverride = true; - result.emplace_back(std::move(override)); - AICLI_LOG(Repo, Info, << "User source " << source.Name << " is overriding the Default source of the same name."); - continue; - } - - result.emplace_back(std::move(source)); - } - } - break; - case SourceOrigin::GroupPolicy: - { - if (GroupPolicies().GetState(TogglePolicy::Policy::AdditionalSources) == PolicyState::Enabled) - { - AICLI_LOG(Repo, Verbose, << "Additional sources GP is enabled..."); - auto additionalSourcesOpt = GroupPolicies().GetValue(); - if (additionalSourcesOpt.has_value()) - { - const auto& additionalSources = additionalSourcesOpt.value(); - for (const auto& additionalSource : additionalSources) - { - AICLI_LOG(Repo, Verbose, << "... with configured source " << additionalSource.Name); - SourceDetailsInternal details; - details.Name = additionalSource.Name; - details.Type = additionalSource.Type; - details.Arg = additionalSource.Arg; - details.Data = additionalSource.Data; - details.Identifier = additionalSource.Identifier; - details.Origin = SourceOrigin::GroupPolicy; - details.Explicit = additionalSource.Explicit; -#ifndef AICLI_DISABLE_TEST_HOOKS - details.CertificatePinningConfiguration = additionalSource.PinningConfiguration; -#endif - try - { - details.TrustLevel = Repository::ConvertToSourceTrustLevelFlag(additionalSource.TrustLevel); - } - catch (...) - { - details.TrustLevel = Repository::SourceTrustLevel::None; - AICLI_LOG(Repo, Verbose, << "Invalid source trust level from policy. Trust level set to None."); - } - - result.emplace_back(std::move(details)); - } - } - else - { - AICLI_LOG(Repo, Verbose, << "... but has no values."); - } - } - else - { - AICLI_LOG(Repo, Verbose, << "Additional sources GP is not enabled."); - } - } - break; - default: - THROW_HR(E_UNEXPECTED); - } - - for (auto& source : result) - { - source.Origin = origin; - } - - return result; - } - - bool SourceList::SetSourcesByOrigin(SourceOrigin origin, const std::vector& sources) - { - switch (origin) - { - case SourceOrigin::User: - return SetSourcesToSettingWithFilter(m_userSourcesStream, SourceOrigin::User, sources); - } - - THROW_HR(E_UNEXPECTED); - } - - std::vector SourceList::GetMetadata() - { - return GetSourcesFromSetting( - m_metadataStream, - s_MetadataYaml_Sources, - [&](SourceDetailsInternal& details, const std::string& settingValue, const YAML::Node& source) - { - details.Origin = SourceOrigin::Metadata; - std::string_view name = m_metadataStream.GetName(); - if (!TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_Name, details.Name)) { return false; } - - int64_t lastUpdateInEpoch{}; - if (!TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_LastUpdate, lastUpdateInEpoch)) { return false; } - details.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(lastUpdateInEpoch); - - int64_t doNotUpdateBeforeInEpoch{}; - if (TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_DoNotUpdateBefore, doNotUpdateBeforeInEpoch, false)) - { - details.DoNotUpdateBefore = Utility::ConvertUnixEpochToSystemClock(doNotUpdateBeforeInEpoch); - } - - TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_AcceptedAgreementsIdentifier, details.AcceptedAgreementsIdentifier, false); - TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_AcceptedAgreementFields, details.AcceptedAgreementFields, false); - return true; - }); - } - - bool SourceList::SetMetadata(const std::vector& sources) - { - YAML::Emitter out; - out << YAML::BeginMap; - out << YAML::Key << s_MetadataYaml_Sources; - out << YAML::BeginSeq; - - for (const auto& details : sources) - { - out << YAML::BeginMap; - out << YAML::Key << s_MetadataYaml_Source_Name << YAML::Value << details.Name; - out << YAML::Key << s_MetadataYaml_Source_LastUpdate << YAML::Value << Utility::ConvertSystemClockToUnixEpoch(details.LastUpdateTime); - out << YAML::Key << s_MetadataYaml_Source_DoNotUpdateBefore << YAML::Value << Utility::ConvertSystemClockToUnixEpoch(details.DoNotUpdateBefore); - out << YAML::Key << s_MetadataYaml_Source_AcceptedAgreementsIdentifier << YAML::Value << details.AcceptedAgreementsIdentifier; - out << YAML::Key << s_MetadataYaml_Source_AcceptedAgreementFields << YAML::Value << details.AcceptedAgreementFields; - out << YAML::EndMap; - } - - out << YAML::EndSeq; - out << YAML::EndMap; - - return m_metadataStream.Set(out.str()); - } - - void SourceList::SaveMetadataInternal(const SourceDetailsInternal& detailsRef, bool remove) - { - // Copy the incoming details because we might overwrite the metadata - // when reloading the source details from settings. - SourceDetailsInternal details = detailsRef; - bool metadataSet = false; - - for (size_t i = 0; !metadataSet && i < 10; ++i) - { - metadataSet = SetMetadata(m_sourceList); - - if (!metadataSet) - { - OverwriteMetadata(); - - auto target = FindSource(details.Name, true); - if (target == m_sourceList.end()) - { - // Didn't find the metadata, so we consider this a success - return; - } - - if (remove) - { - // The remove will have removed the source but not the metadata. - // Remove it again here. - m_sourceList.erase(target); - } - else - { - // Update the freshly read metadata with the update that was requested. - details.CopyMetadataFieldsTo(*target); - } - } - } - - THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); - } - - void SourceList::ResetMetadataInternal(const SourceDetailsInternal& details) - { - bool metadataSet = false; - - for (size_t i = 0; !metadataSet && i < 10; ++i) - { - // Load fresh metadata from storage so that other sources' metadata is preserved. - OverwriteMetadata(); - - auto target = FindSource(details.Name, true); - if (target != m_sourceList.end()) - { - target->ResetMetadataFields(); - } - - metadataSet = SetMetadata(m_sourceList); - } - - THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SourceList.h" +#include "SourcePolicy.h" +#include "Microsoft/PreIndexedPackageSourceFactory.h" +#include "Rest/RestSourceFactory.h" + +#include +#include +#include + +using namespace AppInstaller::Settings; +using namespace std::string_view_literals; + +namespace AppInstaller::Repository +{ + namespace + { + constexpr std::string_view s_SourcesYaml_Sources = "Sources"sv; + constexpr std::string_view s_SourcesYaml_Source_Name = "Name"sv; + constexpr std::string_view s_SourcesYaml_Source_Type = "Type"sv; + constexpr std::string_view s_SourcesYaml_Source_Arg = "Arg"sv; + constexpr std::string_view s_SourcesYaml_Source_Data = "Data"sv; + constexpr std::string_view s_SourcesYaml_Source_Identifier = "Identifier"sv; + constexpr std::string_view s_SourcesYaml_Source_IsTombstone = "IsTombstone"sv; + constexpr std::string_view s_SourcesYaml_Source_IsOverride = "IsOverride"sv; + constexpr std::string_view s_SourcesYaml_Source_Explicit = "Explicit"sv; + constexpr std::string_view s_SourcesYaml_Source_TrustLevel = "TrustLevel"sv; + constexpr std::string_view s_SourcesYaml_Source_Priority = "Priority"sv; + + constexpr std::string_view s_MetadataYaml_Sources = "Sources"sv; + constexpr std::string_view s_MetadataYaml_Source_Name = "Name"sv; + constexpr std::string_view s_MetadataYaml_Source_LastUpdate = "LastUpdate"sv; + constexpr std::string_view s_MetadataYaml_Source_DoNotUpdateBefore = "DoNotUpdateBefore"sv; + constexpr std::string_view s_MetadataYaml_Source_AcceptedAgreementsIdentifier = "AcceptedAgreementsIdentifier"sv; + constexpr std::string_view s_MetadataYaml_Source_AcceptedAgreementFields = "AcceptedAgreementFields"sv; + + constexpr std::string_view s_Source_WingetCommunityDefault_Name = "winget"sv; + constexpr std::string_view s_Source_WingetCommunityDefault_Arg = "https://cdn.winget.microsoft.com/cache"sv; + constexpr std::string_view s_Source_WingetCommunityDefault_Data = "Microsoft.Winget.Source_8wekyb3d8bbwe"sv; + constexpr std::string_view s_Source_WingetCommunityDefault_Identifier = "Microsoft.Winget.Source_8wekyb3d8bbwe"sv; + + constexpr std::string_view s_Source_MSStoreDefault_Name = "msstore"sv; + constexpr std::string_view s_Source_MSStoreDefault_Arg = "https://storeedgefd.dsx.mp.microsoft.com/v9.0"sv; + constexpr std::string_view s_Source_MSStoreDefault_Identifier = "StoreEdgeFD"sv; + + constexpr std::string_view s_Source_DesktopFrameworks_Name = "microsoft.builtin.desktop.frameworks"sv; + constexpr std::string_view s_Source_DesktopFrameworks_Arg = "https://cdn.winget.microsoft.com/platform"sv; + constexpr std::string_view s_Source_DesktopFrameworks_Data = "Microsoft.Winget.Platform.Source_8wekyb3d8bbwe"sv; + constexpr std::string_view s_Source_DesktopFrameworks_Identifier = "Microsoft.Winget.Platform.Source_8wekyb3d8bbwe"sv; + + constexpr std::string_view s_Source_WingetCommunityFont_Name = "winget-font"sv; + constexpr std::string_view s_Source_WingetCommunityFont_Arg = "https://cdn.winget.microsoft.com/fonts"sv; + constexpr std::string_view s_Source_WingetCommunityFont_Data = "Microsoft.Winget.Fonts.Source_8wekyb3d8bbwe"sv; + constexpr std::string_view s_Source_WingetCommunityFont_Identifier = "Microsoft.Winget.Fonts.Source_8wekyb3d8bbwe"sv; + + // Attempts to read a single scalar value from the node. + template + bool TryReadScalar(std::string_view settingName, const std::string& settingValue, const YAML::Node& sourceNode, std::string_view name, Value& value, bool required = true) + { + YAML::Node valueNode = sourceNode[std::string{ name }]; + + if (!valueNode || !valueNode.IsScalar()) + { + if (required) + { + AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (" << name << " is invalid within a source):\n" << settingValue); + } + return false; + } + + value = valueNode.as(); + return true; + } + + // Attempts to read the source details from the given stream. + // Results are all or nothing; if any failures occur, no details are returned. + bool TryReadSourceDetails( + std::string_view settingName, + std::istream& stream, + std::string_view rootName, + std::function parse, + std::vector& sourceDetails) + { + std::vector result; + std::string settingValue = Utility::ReadEntireStream(stream); + + YAML::Node document; + try + { + document = YAML::Load(settingValue); + } + catch (const std::exception& e) + { + AICLI_LOG(YAML, Error, << "Setting '" << settingName << "' contained invalid YAML (" << e.what() << "):\n" << settingValue); + return false; + } + + try + { + YAML::Node sources = document[rootName]; + if (!sources) + { + AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (missing " << rootName << "):\n" << settingValue); + return false; + } + + if (sources.IsNull()) + { + // An empty sources is an acceptable thing. + return true; + } + + if (!sources.IsSequence()) + { + AICLI_LOG(Repo, Error, << "Setting '" << settingName << "' did not contain the expected format (" << rootName << " was not a sequence):\n" << settingValue); + return false; + } + + for (const auto& source : sources.Sequence()) + { + SourceDetailsInternal details; + if (!parse(details, settingValue, source)) + { + return false; + } + + result.emplace_back(std::move(details)); + } + } + catch (const std::exception& e) + { + AICLI_LOG(YAML, Error, << "Setting '" << settingName << "' contained unexpected YAML (" << e.what() << "):\n" << settingValue); + return false; + } + + sourceDetails = std::move(result); + return true; + } + + // Gets the source details from a particular setting, or an empty optional if no setting exists. + std::optional> TryGetSourcesFromSetting( + Settings::Stream& setting, + std::string_view rootName, + std::function parse) + { + auto sourcesStream = setting.Get(); + if (!sourcesStream) + { + // Note that this case is different than the one in which all sources have been removed. + return {}; + } + else + { + std::vector result; + if (!TryReadSourceDetails(setting.GetName(), *sourcesStream, rootName, parse, result)) + { + AICLI_LOG(YAML, Error, << "Ignoring corrupted source data."); + } + return result; + } + } + + // Gets the source details from a particular setting. + std::vector GetSourcesFromSetting( + Settings::Stream& setting, + std::string_view rootName, + std::function parse) + { + return TryGetSourcesFromSetting(setting, rootName, parse).value_or(std::vector{}); + } + + // Sets the sources for a particular setting, from a particular origin. + [[nodiscard]] bool SetSourcesToSettingWithFilter(Settings::Stream& setting, SourceOrigin origin, const std::vector& sources) + { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << s_SourcesYaml_Sources; + out << YAML::BeginSeq; + + for (const auto& details : sources) + { + if (details.Origin == origin) + { + out << YAML::BeginMap; + out << YAML::Key << s_SourcesYaml_Source_Name << YAML::Value << details.Name; + out << YAML::Key << s_SourcesYaml_Source_Type << YAML::Value << details.Type; + out << YAML::Key << s_SourcesYaml_Source_Arg << YAML::Value << details.Arg; + out << YAML::Key << s_SourcesYaml_Source_Data << YAML::Value << details.Data; + out << YAML::Key << s_SourcesYaml_Source_Identifier << YAML::Value << details.Identifier; + out << YAML::Key << s_SourcesYaml_Source_IsTombstone << YAML::Value << details.IsTombstone; + out << YAML::Key << s_SourcesYaml_Source_IsOverride << YAML::Value << details.IsOverride; + out << YAML::Key << s_SourcesYaml_Source_Explicit << YAML::Value << details.Explicit; + out << YAML::Key << s_SourcesYaml_Source_TrustLevel << YAML::Value << static_cast(details.TrustLevel); + out << YAML::Key << s_SourcesYaml_Source_Priority << YAML::Value << details.Priority; + out << YAML::EndMap; + } + } + + out << YAML::EndSeq; + out << YAML::EndMap; + + return setting.Set(out.str()); + } + + // Assumes that names match already + bool DoSourceDetailsInternalMatch(const SourceDetailsInternal& left, const SourceDetailsInternal& right) + { + return left.Arg == right.Arg && + left.Identifier == right.Identifier && + Utility::CaseInsensitiveEquals(left.Type, right.Type); + } + + bool ShouldBeHidden(const SourceDetailsInternal& details) + { + return details.IsTombstone || details.Origin == SourceOrigin::Metadata || !details.IsVisible; + } + } + + void SourceDetailsInternal::CopyMetadataFieldsTo(SourceDetailsInternal& target) + { + if (LastUpdateTime > target.LastUpdateTime) + { + target.LastUpdateTime = LastUpdateTime; + } + + if (DoNotUpdateBefore > target.DoNotUpdateBefore) + { + target.DoNotUpdateBefore = DoNotUpdateBefore; + } + + target.AcceptedAgreementFields = AcceptedAgreementFields; + target.AcceptedAgreementsIdentifier = AcceptedAgreementsIdentifier; + } + + void SourceDetailsInternal::CopyMetadataFieldsFrom(const SourceDetails& source) + { + LastUpdateTime = source.LastUpdateTime; + DoNotUpdateBefore = source.DoNotUpdateBefore; + } + + void SourceDetailsInternal::ResetMetadataFields() + { + LastUpdateTime = {}; + DoNotUpdateBefore = {}; + AcceptedAgreementFields = 0; + AcceptedAgreementsIdentifier = {}; + } + + void SourceDetailsInternal::CopyOverrideFieldsFrom(const SourceDetails& overrideSource) + { + // These are the supported Override fields. + Explicit = overrideSource.Explicit; + Priority = overrideSource.Priority; + } + + bool SourceDetailsInternal::operator<(const SourceDetailsInternal& other) const + { + // Higher values come first in ordering and must be "less than" for standard sorting + return Priority > other.Priority; + } + + std::string_view GetWellKnownSourceName(WellKnownSource source) + { + switch (source) + { + case WellKnownSource::WinGet: + return s_Source_WingetCommunityDefault_Name; + case WellKnownSource::MicrosoftStore: + return s_Source_MSStoreDefault_Name; + case WellKnownSource::DesktopFrameworks: + return s_Source_DesktopFrameworks_Name; + case WellKnownSource::WinGetFont: + return s_Source_WingetCommunityFont_Name; + } + + return {}; + } + + std::string_view GetWellKnownSourceArg(WellKnownSource source) + { + switch (source) + { + case WellKnownSource::WinGet: + return s_Source_WingetCommunityDefault_Arg; + case WellKnownSource::MicrosoftStore: + return s_Source_MSStoreDefault_Arg; + case WellKnownSource::DesktopFrameworks: + return s_Source_DesktopFrameworks_Arg; + case WellKnownSource::WinGetFont: + return s_Source_WingetCommunityFont_Arg; + } + + return {}; + } + + std::string_view GetWellKnownSourceIdentifier(WellKnownSource source) + { + switch (source) + { + case WellKnownSource::WinGet: + return s_Source_WingetCommunityDefault_Identifier; + case WellKnownSource::MicrosoftStore: + return s_Source_MSStoreDefault_Identifier; + case WellKnownSource::DesktopFrameworks: + return s_Source_DesktopFrameworks_Identifier; + case WellKnownSource::WinGetFont: + return s_Source_WingetCommunityFont_Identifier; + } + + return {}; + } + + std::optional CheckForWellKnownSourceMatch(std::string_view name, std::string_view arg, std::string_view type) + { + if (name == s_Source_WingetCommunityDefault_Name && arg == s_Source_WingetCommunityDefault_Arg && type == Microsoft::PreIndexedPackageSourceFactory::Type()) + { + return WellKnownSource::WinGet; + } + + if (name == s_Source_MSStoreDefault_Name && arg == s_Source_MSStoreDefault_Arg && type == Rest::RestSourceFactory::Type()) + { + return WellKnownSource::MicrosoftStore; + } + + if (name == s_Source_DesktopFrameworks_Name && arg == s_Source_DesktopFrameworks_Arg && type == Microsoft::PreIndexedPackageSourceFactory::Type()) + { + return WellKnownSource::DesktopFrameworks; + } + + if (name == s_Source_WingetCommunityFont_Name && arg == s_Source_WingetCommunityFont_Arg && type == Rest::RestSourceFactory::Type()) + { + return WellKnownSource::WinGetFont; + } + + return {}; + } + + SourceDetailsInternal GetWellKnownSourceDetailsInternal(WellKnownSource source) + { + switch (source) + { + case WellKnownSource::WinGet: + { + SourceDetailsInternal details; + details.Origin = SourceOrigin::Default; + details.Name = s_Source_WingetCommunityDefault_Name; + details.Type = Microsoft::PreIndexedPackageSourceFactory::Type(); + details.Arg = s_Source_WingetCommunityDefault_Arg; + details.Data = s_Source_WingetCommunityDefault_Data; + details.Identifier = s_Source_WingetCommunityDefault_Identifier; + details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin; + return details; + } + case WellKnownSource::MicrosoftStore: + { + SourceDetailsInternal details; + details.Origin = SourceOrigin::Default; + details.Name = s_Source_MSStoreDefault_Name; + details.Type = Rest::RestSourceFactory::Type(); + details.Arg = s_Source_MSStoreDefault_Arg; + details.Identifier = s_Source_MSStoreDefault_Identifier; + details.TrustLevel = SourceTrustLevel::Trusted; + details.SupportInstalledSearchCorrelation = false; + + if (!Settings::IsAdminSettingEnabled(Settings::BoolAdminSetting::BypassCertificatePinningForMicrosoftStore)) + { + using namespace AppInstaller::Certificates; + + PinningChain chain; + auto chainElement = chain.Root(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chainElement = chainElement.Next(); + chainElement->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_1, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + PinningChain chain2; + auto chainElement2 = chain2.Root(); + chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_ROOT_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::PublicKey); + chainElement2 = chainElement2.Next(); + chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_INTERMEDIATE_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + chainElement2 = chainElement2.Next(); + chainElement2->LoadCertificate(IDX_CERTIFICATE_STORE_LEAF_2, CERTIFICATE_RESOURCE_TYPE).SetPinning(PinningVerificationType::Subject | PinningVerificationType::Issuer); + + // See https://aka.ms/AzureTLSCAs (internal) for the source of these CAs + PinningChain chain3; + chain3.PartialChain().Root()-> + LoadCertificate(IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2, CERTIFICATE_RESOURCE_TYPE). + SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); + + PinningChain chain4; + chain4.PartialChain().Root()-> + LoadCertificate(IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2, CERTIFICATE_RESOURCE_TYPE). + SetPinning(PinningVerificationType::PublicKey | PinningVerificationType::AnyIssuer | PinningVerificationType::RequireNonLeaf); + + details.CertificatePinningConfiguration = PinningConfiguration("Microsoft Store Source"); + details.CertificatePinningConfiguration.AddChain(std::move(chain)); + details.CertificatePinningConfiguration.AddChain(std::move(chain2)); + details.CertificatePinningConfiguration.AddChain(std::move(chain3)); + details.CertificatePinningConfiguration.AddChain(std::move(chain4)); + } + + return details; + } + case WellKnownSource::DesktopFrameworks: + { + SourceDetailsInternal details; + details.Origin = SourceOrigin::Default; + details.Name = s_Source_DesktopFrameworks_Name; + details.Type = Microsoft::PreIndexedPackageSourceFactory::Type(); + details.Arg = s_Source_DesktopFrameworks_Arg; + details.Data = s_Source_DesktopFrameworks_Data; + details.Identifier = s_Source_DesktopFrameworks_Identifier; + details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin; + details.IsVisible = false; + return details; + } + case WellKnownSource::WinGetFont: + { + SourceDetailsInternal details; + details.Origin = SourceOrigin::Default; + details.Name = s_Source_WingetCommunityFont_Name; + details.Type = Microsoft::PreIndexedPackageSourceFactory::Type(); + details.Arg = s_Source_WingetCommunityFont_Arg; + details.Data = s_Source_WingetCommunityFont_Data; + details.Identifier = s_Source_WingetCommunityFont_Identifier; + details.TrustLevel = SourceTrustLevel::Trusted | SourceTrustLevel::StoreOrigin; + details.Explicit = true; + return details; + } + } + + THROW_HR(E_UNEXPECTED); + } + + SourceList::SourceList() : m_userSourcesStream(Stream::UserSources), m_metadataStream(Stream::SourcesMetadata) + { + OverwriteSourceList(); + OverwriteMetadata(); + } + + std::vector> SourceList::GetCurrentSourceRefs() + { + std::vector> result; + + for (auto& s : m_sourceList) + { + if (!ShouldBeHidden(s)) + { + result.emplace_back(std::ref(s)); + } + else + { + AICLI_LOG(Repo, Verbose, << "GetCurrentSourceRefs: Source named '" << s.Name << "' from origin " << ToString(s.Origin) << " is hidden and is dropped."); + } + } + + return result; + } + + auto SourceList::FindSource(std::string_view name, bool includeHidden) + { + return std::find_if(m_sourceList.begin(), m_sourceList.end(), + [name, includeHidden](const SourceDetailsInternal& sd) + { + return Utility::ICUCaseInsensitiveEquals(sd.Name, name) && + (includeHidden || !ShouldBeHidden(sd)); + }); + } + + bool SourceList::TryFindSourceByOrigin(std::string_view name, SourceOrigin origin, SourceDetailsInternal& targetSourceOut, bool includeHidden) + { + auto defaultSources = GetSourcesByOrigin(origin); + auto iter = std::find_if(defaultSources.begin(), defaultSources.end(), + [name, includeHidden](const SourceDetailsInternal& sd) + { + return Utility::ICUCaseInsensitiveEquals(sd.Name, name) && + (includeHidden || !ShouldBeHidden(sd)); + }); + + if (iter == defaultSources.end()) + { + return false; + } + + targetSourceOut = (*iter); + return true; + } + + SourceDetailsInternal* SourceList::GetCurrentSource(std::string_view name) + { + auto itr = FindSource(name); + return itr == m_sourceList.end() ? nullptr : &(*itr); + } + + SourceDetailsInternal* SourceList::GetSource(std::string_view name) + { + auto itr = FindSource(name, true); + return itr == m_sourceList.end() ? nullptr : &(*itr); + } + + void SourceList::AddSource(const SourceDetailsInternal& details) + { + bool sourcesSet = false; + + for (size_t i = 0; !sourcesSet && i < 10; ++i) + { + auto itr = FindSource(details.Name, true); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, + itr != m_sourceList.end() && itr->Origin != SourceOrigin::Metadata && !itr->IsTombstone); + + // Erase the source's entry if applicable + if (itr != m_sourceList.end()) + { + m_sourceList.erase(itr); + } + + m_sourceList.emplace_back(details); + + sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); + + if (!sourcesSet) + { + OverwriteSourceList(); + OverwriteMetadata(); + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); + + SaveMetadataInternal(details); + } + + void SourceList::RemoveSource(const SourceDetailsInternal& detailsRef) + { + // Copy the incoming details because we might destroy the referenced structure + // when reloading the source details from settings. + SourceDetailsInternal details = detailsRef; + bool sourcesSet = false; + + for (size_t i = 0; !sourcesSet && i < 10; ++i) + { + switch (details.Origin) + { + case SourceOrigin::Default: + { + auto target = FindSource(details.Name, true); + if (target == m_sourceList.end()) + { + THROW_HR_MSG(E_UNEXPECTED, "Default source not in SourceList"); + } + + if (!target->IsTombstone) + { + SourceDetailsInternal tombstone; + tombstone.Name = details.Name; + tombstone.IsTombstone = true; + tombstone.Origin = SourceOrigin::User; + m_sourceList.emplace_back(std::move(tombstone)); + } + } + break; + case SourceOrigin::User: + { + auto target = FindSource(details.Name); + if (target == m_sourceList.end()) + { + // Assumed that an update to the sources removed it first + return; + } + + // If this is an override of a default source, turn this into a tombstone instead of removing it. + if (target->IsOverride) + { + target->IsOverride = false; + target->IsTombstone = true; + break; + } + + m_sourceList.erase(target); + } + break; + case SourceOrigin::GroupPolicy: + // This should have already been blocked higher up. + AICLI_LOG(Repo, Error, << "Attempting to remove Group Policy source: " << details.Name); + THROW_HR(E_UNEXPECTED); + default: + THROW_HR(E_UNEXPECTED); + } + + sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); + + if (!sourcesSet) + { + OverwriteSourceList(); + OverwriteMetadata(); + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); + + SaveMetadataInternal(details, true); + } + + void SourceList::EditSource(const SourceDetailsInternal& detailsRef) + { + // Copy the incoming details because we might destroy the referenced structure + // when reloading the source details from settings. + SourceDetailsInternal details = detailsRef; + bool sourcesSet = false; + + for (size_t i = 0; !sourcesSet && i < 10; ++i) + { + switch (details.Origin) + { + case SourceOrigin::Default: + { + auto target = FindSource(details.Name, true); + if (target == m_sourceList.end()) + { + THROW_HR_MSG(E_UNEXPECTED, "Default source not in SourceList"); + } + + if (!target->IsTombstone) + { + // Copy the original and then apply the override fields. + SourceDetailsInternal override = *target; + override.Origin = SourceOrigin::User; + override.IsOverride = true; + override.CopyOverrideFieldsFrom(details); + m_sourceList.emplace_back(std::move(override)); + } + } + break; + case SourceOrigin::User: + { + auto target = FindSource(details.Name); + if (target == m_sourceList.end()) + { + // Assumed that an update to the sources removed it first + return; + } + + // Editing a User Source is just replacing the fields that can be edited. + target->CopyOverrideFieldsFrom(details); + } + break; + case SourceOrigin::GroupPolicy: + // This should have already been blocked higher up. + AICLI_LOG(Repo, Error, << "Attempting to edit a Group Policy source: " << details.Name); + THROW_HR(E_UNEXPECTED); + default: + THROW_HR(E_UNEXPECTED); + } + + sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); + + if (!sourcesSet) + { + OverwriteSourceList(); + OverwriteMetadata(); + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); + + SaveMetadataInternal(details, true); + } + + void SourceList::ResetSource(const SourceDetailsInternal& detailsRef) + { + // Copy the incoming details because we might destroy the referenced structure + // when reloading the source details from settings. + SourceDetailsInternal details = detailsRef; + bool sourcesSet = false; + + for (size_t i = 0; !sourcesSet && i < 10; ++i) + { + // Remove any user-level entry for this source (tombstone or override). + auto itr = FindSource(details.Name, true); + if (itr != m_sourceList.end() && itr->Origin == SourceOrigin::User) + { + m_sourceList.erase(itr); + } + + sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); + + if (!sourcesSet) + { + OverwriteSourceList(); + OverwriteMetadata(); + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); + + // Reload to bring back the Default source (now that the user entry is removed). + OverwriteSourceList(); + + ResetMetadataInternal(details); + } + + void SourceList::SaveMetadata(const SourceDetailsInternal& details) + { + SaveMetadataInternal(details); + } + + bool SourceList::CheckSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields) + { + if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty()) + { + // No agreements to be accepted. + return true; + } + + auto detailsInternal = GetCurrentSource(sourceName); + if (!detailsInternal) + { + // Source not found. + return false; + } + + return static_cast(agreementFields) == detailsInternal->AcceptedAgreementFields && + agreementsIdentifier == detailsInternal->AcceptedAgreementsIdentifier; + } + + void SourceList::SaveAcceptedSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields) + { + if (agreementFields == ImplicitAgreementFieldEnum::None && agreementsIdentifier.empty()) + { + // No agreements to be accepted. + return; + } + + auto detailsInternal = GetCurrentSource(sourceName); + if (!detailsInternal) + { + // No source to update. + return; + } + + detailsInternal->AcceptedAgreementFields = static_cast(agreementFields); + detailsInternal->AcceptedAgreementsIdentifier = agreementsIdentifier; + + SaveMetadataInternal(*detailsInternal); + } + + void SourceList::RemoveSettingsStreams() + { + Stream{ Stream::UserSources }.Remove(); + Stream{ Stream::SourcesMetadata }.Remove(); + } + + void SourceList::OverwriteSourceList() + { + m_sourceList.clear(); + + for (SourceOrigin origin : { SourceOrigin::GroupPolicy, SourceOrigin::User, SourceOrigin::Default }) + { + auto forOrigin = GetSourcesByOrigin(origin); + + for (auto&& source : forOrigin) + { + auto foundSource = GetSource(source.Name); + if (!foundSource) + { + // Name not already defined, add it + m_sourceList.emplace_back(std::move(source)); + } + else + { + AICLI_LOG(Repo, Info, << "Source named '" << foundSource->Name << "' is already defined at origin " << ToString(foundSource->Origin) << + ". The source from origin " << ToString(origin) << " is dropped."); + } + } + } + + if (ExperimentalFeature::IsEnabled(ExperimentalFeature::Feature::SourcePriority)) + { + std::stable_sort(m_sourceList.begin(), m_sourceList.end()); + } + } + + void SourceList::OverwriteMetadata() + { + auto metadata = GetMetadata(); + for (auto& metaSource : metadata) + { + auto source = GetSource(metaSource.Name); + if (source) + { + metaSource.CopyMetadataFieldsTo(*source); + } + else + { + m_sourceList.emplace_back(std::move(metaSource)); + } + } + } + + // Gets the sources from a particular origin. + std::vector SourceList::GetSourcesByOrigin(SourceOrigin origin) + { + std::vector result; + + switch (origin) + { + case SourceOrigin::Default: + { + if (IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore)) + { + result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::MicrosoftStore)); + } + + if (IsWellKnownSourceEnabled(WellKnownSource::WinGet)) + { + result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::WinGet)); + } + + if (IsWellKnownSourceEnabled(WellKnownSource::WinGetFont)) + { + result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::WinGetFont)); + } + + // Since the source is not visible outside, this is added just to have the source in the internal + // list for tracking updates. Thus there is no need to check a policy. + result.emplace_back(GetWellKnownSourceDetailsInternal(WellKnownSource::DesktopFrameworks)); + } + break; + case SourceOrigin::User: + { + std::vector userSources = GetSourcesFromSetting( + m_userSourcesStream, + s_SourcesYaml_Sources, + [&](SourceDetailsInternal& details, const std::string& settingValue, const YAML::Node& source) + { + std::string_view name = m_userSourcesStream.GetName(); + if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Name, details.Name)) { return false; } + if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Type, details.Type)) { return false; } + if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Arg, details.Arg)) { return false; } + if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Data, details.Data)) { return false; } + if (!TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_IsTombstone, details.IsTombstone)) { return false; } + TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Explicit, details.Explicit, false); + TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Identifier, details.Identifier, false); + TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_IsOverride, details.IsOverride, false); + TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_Priority, details.Priority, false); + + int64_t trustLevelValue; + if (TryReadScalar(name, settingValue, source, s_SourcesYaml_Source_TrustLevel, trustLevelValue, false)) + { + details.TrustLevel = static_cast(trustLevelValue); + } + + return true; + }); + + for (auto& source : userSources) + { + // Check source against list of allowed sources and drop tombstones for required sources + if (!IsUserSourceAllowedByPolicy(source.Name, source.Type, source.Arg, source.IsTombstone)) + { + AICLI_LOG(Repo, Warning, << "User source " << source.Name << " dropped because of group policy"); + continue; + } + + // If this is an override source, we need to get the target of the override and apply the override data on top of it. + if (source.IsOverride) + { + SourceDetailsInternal override; + if (!TryFindSourceByOrigin(source.Name, SourceOrigin::Default, override)) + { + // The default source may be disabled, in which case it may not be returned in the list of default sources. + AICLI_LOG(Repo, Warning, << "User source " << source.Name << " is an override for a nonexistent Default Source."); + continue; + } + + override.CopyOverrideFieldsFrom(source); + override.Origin = SourceOrigin::User; + override.IsOverride = true; + result.emplace_back(std::move(override)); + AICLI_LOG(Repo, Info, << "User source " << source.Name << " is overriding the Default source of the same name."); + continue; + } + + result.emplace_back(std::move(source)); + } + } + break; + case SourceOrigin::GroupPolicy: + { + if (GroupPolicies().GetState(TogglePolicy::Policy::AdditionalSources) == PolicyState::Enabled) + { + AICLI_LOG(Repo, Verbose, << "Additional sources GP is enabled..."); + auto additionalSourcesOpt = GroupPolicies().GetValue(); + if (additionalSourcesOpt.has_value()) + { + const auto& additionalSources = additionalSourcesOpt.value(); + for (const auto& additionalSource : additionalSources) + { + AICLI_LOG(Repo, Verbose, << "... with configured source " << additionalSource.Name); + SourceDetailsInternal details; + details.Name = additionalSource.Name; + details.Type = additionalSource.Type; + details.Arg = additionalSource.Arg; + details.Data = additionalSource.Data; + details.Identifier = additionalSource.Identifier; + details.Origin = SourceOrigin::GroupPolicy; + details.Explicit = additionalSource.Explicit; +#ifndef AICLI_DISABLE_TEST_HOOKS + details.CertificatePinningConfiguration = additionalSource.PinningConfiguration; +#endif + try + { + details.TrustLevel = Repository::ConvertToSourceTrustLevelFlag(additionalSource.TrustLevel); + } + catch (...) + { + details.TrustLevel = Repository::SourceTrustLevel::None; + AICLI_LOG(Repo, Verbose, << "Invalid source trust level from policy. Trust level set to None."); + } + + result.emplace_back(std::move(details)); + } + } + else + { + AICLI_LOG(Repo, Verbose, << "... but has no values."); + } + } + else + { + AICLI_LOG(Repo, Verbose, << "Additional sources GP is not enabled."); + } + } + break; + default: + THROW_HR(E_UNEXPECTED); + } + + for (auto& source : result) + { + source.Origin = origin; + } + + return result; + } + + bool SourceList::SetSourcesByOrigin(SourceOrigin origin, const std::vector& sources) + { + switch (origin) + { + case SourceOrigin::User: + return SetSourcesToSettingWithFilter(m_userSourcesStream, SourceOrigin::User, sources); + } + + THROW_HR(E_UNEXPECTED); + } + + std::vector SourceList::GetMetadata() + { + return GetSourcesFromSetting( + m_metadataStream, + s_MetadataYaml_Sources, + [&](SourceDetailsInternal& details, const std::string& settingValue, const YAML::Node& source) + { + details.Origin = SourceOrigin::Metadata; + std::string_view name = m_metadataStream.GetName(); + if (!TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_Name, details.Name)) { return false; } + + int64_t lastUpdateInEpoch{}; + if (!TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_LastUpdate, lastUpdateInEpoch)) { return false; } + details.LastUpdateTime = Utility::ConvertUnixEpochToSystemClock(lastUpdateInEpoch); + + int64_t doNotUpdateBeforeInEpoch{}; + if (TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_DoNotUpdateBefore, doNotUpdateBeforeInEpoch, false)) + { + details.DoNotUpdateBefore = Utility::ConvertUnixEpochToSystemClock(doNotUpdateBeforeInEpoch); + } + + TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_AcceptedAgreementsIdentifier, details.AcceptedAgreementsIdentifier, false); + TryReadScalar(name, settingValue, source, s_MetadataYaml_Source_AcceptedAgreementFields, details.AcceptedAgreementFields, false); + return true; + }); + } + + bool SourceList::SetMetadata(const std::vector& sources) + { + YAML::Emitter out; + out << YAML::BeginMap; + out << YAML::Key << s_MetadataYaml_Sources; + out << YAML::BeginSeq; + + for (const auto& details : sources) + { + out << YAML::BeginMap; + out << YAML::Key << s_MetadataYaml_Source_Name << YAML::Value << details.Name; + out << YAML::Key << s_MetadataYaml_Source_LastUpdate << YAML::Value << Utility::ConvertSystemClockToUnixEpoch(details.LastUpdateTime); + out << YAML::Key << s_MetadataYaml_Source_DoNotUpdateBefore << YAML::Value << Utility::ConvertSystemClockToUnixEpoch(details.DoNotUpdateBefore); + out << YAML::Key << s_MetadataYaml_Source_AcceptedAgreementsIdentifier << YAML::Value << details.AcceptedAgreementsIdentifier; + out << YAML::Key << s_MetadataYaml_Source_AcceptedAgreementFields << YAML::Value << details.AcceptedAgreementFields; + out << YAML::EndMap; + } + + out << YAML::EndSeq; + out << YAML::EndMap; + + return m_metadataStream.Set(out.str()); + } + + void SourceList::SaveMetadataInternal(const SourceDetailsInternal& detailsRef, bool remove) + { + // Copy the incoming details because we might overwrite the metadata + // when reloading the source details from settings. + SourceDetailsInternal details = detailsRef; + bool metadataSet = false; + + for (size_t i = 0; !metadataSet && i < 10; ++i) + { + metadataSet = SetMetadata(m_sourceList); + + if (!metadataSet) + { + OverwriteMetadata(); + + auto target = FindSource(details.Name, true); + if (target == m_sourceList.end()) + { + // Didn't find the metadata, so we consider this a success + return; + } + + if (remove) + { + // The remove will have removed the source but not the metadata. + // Remove it again here. + m_sourceList.erase(target); + } + else + { + // Update the freshly read metadata with the update that was requested. + details.CopyMetadataFieldsTo(*target); + } + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); + } + + void SourceList::ResetMetadataInternal(const SourceDetailsInternal& details) + { + bool metadataSet = false; + + for (size_t i = 0; !metadataSet && i < 10; ++i) + { + // Load fresh metadata from storage so that other sources' metadata is preserved. + OverwriteMetadata(); + + auto target = FindSource(details.Name, true); + if (target != m_sourceList.end()) + { + target->ResetMetadataFields(); + } + + metadataSet = SetMetadata(m_sourceList); + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); + } +} diff --git a/src/AppInstallerRepositoryCore/SourceList.h b/src/AppInstallerRepositoryCore/SourceList.h index d5f87aff40..2230715209 100644 --- a/src/AppInstallerRepositoryCore/SourceList.h +++ b/src/AppInstallerRepositoryCore/SourceList.h @@ -1,125 +1,125 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ISource.h" -#include - - -namespace AppInstaller::Repository -{ - // Built-in values for default sources - std::string_view GetWellKnownSourceName(WellKnownSource source); - std::string_view GetWellKnownSourceArg(WellKnownSource source); - std::string_view GetWellKnownSourceIdentifier(WellKnownSource source); - std::optional CheckForWellKnownSourceMatch(std::string_view name, std::string_view arg, std::string_view type); - - // SourceDetails with additional data used internally. - struct SourceDetailsInternal : public SourceDetails - { - SourceDetailsInternal() = default; - SourceDetailsInternal(const SourceDetails& details) : SourceDetails(details) {} - - // Copies the metadata fields to this target. - void CopyMetadataFieldsTo(SourceDetailsInternal& target); - - // Copies the metadata fields from this source. This only include partial metadata. - void CopyMetadataFieldsFrom(const SourceDetails& source); - - // Resets all metadata fields to their default (clean-install) state. - void ResetMetadataFields(); - - // Copies the overridden fields from the target source to this source. This is only the supported override fields. - void CopyOverrideFieldsFrom(const SourceDetails& overrideSource); - - // Sorts by Priority with higher values coming first in the order. - bool operator<(const SourceDetailsInternal& other) const; - - // If true, this is a tombstone, marking the deletion of a source at a lower priority origin. - bool IsTombstone = false; - - // If true, this is an override of a source at a lower priority. An override source only defines - // changes on top of the lower priority source, otherwise uses the same as the lower priority source. - bool IsOverride = false; - - // If false, this is not visible in GetCurrentSource or GetAllSources, it's only available when explicitly requested. - bool IsVisible = true; - - // Accepted agreements info. - std::string AcceptedAgreementsIdentifier; - int AcceptedAgreementFields = 0; - }; - - // Gets the internal details for a well known source. - SourceDetailsInternal GetWellKnownSourceDetailsInternal(WellKnownSource source); - - // Struct containing internal implementation of the source list. - // This contains all source data; including tombstoned sources. - struct SourceList - { - SourceList(); - - // Get a list of current sources references which can be used to update the contents in place. - // e.g. update the LastTimeUpdated value of sources. - std::vector> GetCurrentSourceRefs(); - - // Current source means source that's not in tombstone - SourceDetailsInternal* GetCurrentSource(std::string_view name); - - // Source includes ones in tombstone - SourceDetailsInternal* GetSource(std::string_view name); - - // Add/remove/edit a current source - void AddSource(const SourceDetailsInternal& details); - void RemoveSource(const SourceDetailsInternal& details); - void EditSource(const SourceDetailsInternal& details); - - // Reset a default source: removes any user-level entry (tombstone or override) - // and clears the source's metadata, restoring it to clean-install state. - void ResetSource(const SourceDetailsInternal& details); - - // Save source metadata; the particular source with the metadata update is given. - // The given source must already be in the internal source list. - void SaveMetadata(const SourceDetailsInternal& details); - - // Checks the source agreements and returns if agreements are satisfied. - bool CheckSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields); - - // Save agreements information. - void SaveAcceptedSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields); - - // Removes all settings streams associated with the source list. - // Implements `winget source reset --force`. - static void RemoveSettingsStreams(); - - private: - // Overwrites the source list with all sources. - void OverwriteSourceList(); - - // Overwrites the source list with the current metadata. - void OverwriteMetadata(); - - // calls std::find_if and return the iterator. - auto FindSource(std::string_view name, bool includeHidden = false); - - // Tries to find a named source from the specified origin. - [[nodiscard]] bool TryFindSourceByOrigin(std::string_view name, SourceOrigin origin, SourceDetailsInternal& targetSourceOut, bool includeHidden = false); - - std::vector GetSourcesByOrigin(SourceOrigin origin); - // Does *NOT* set metadata; call SaveMetadataInternal afterward. - [[nodiscard]] bool SetSourcesByOrigin(SourceOrigin origin, const std::vector& sources); - - std::vector GetMetadata(); - [[nodiscard]] bool SetMetadata(const std::vector& sources); - - // Save source metadata; the particular source with the metadata update is given. - // If remove is true, the given source is being removed. - void SaveMetadataInternal(const SourceDetailsInternal& details, bool remove = false); - - // Clears all metadata for the named source and persists the change. - void ResetMetadataInternal(const SourceDetailsInternal& details); - - std::vector m_sourceList; - Settings::Stream m_userSourcesStream; - Settings::Stream m_metadataStream; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ISource.h" +#include + + +namespace AppInstaller::Repository +{ + // Built-in values for default sources + std::string_view GetWellKnownSourceName(WellKnownSource source); + std::string_view GetWellKnownSourceArg(WellKnownSource source); + std::string_view GetWellKnownSourceIdentifier(WellKnownSource source); + std::optional CheckForWellKnownSourceMatch(std::string_view name, std::string_view arg, std::string_view type); + + // SourceDetails with additional data used internally. + struct SourceDetailsInternal : public SourceDetails + { + SourceDetailsInternal() = default; + SourceDetailsInternal(const SourceDetails& details) : SourceDetails(details) {} + + // Copies the metadata fields to this target. + void CopyMetadataFieldsTo(SourceDetailsInternal& target); + + // Copies the metadata fields from this source. This only include partial metadata. + void CopyMetadataFieldsFrom(const SourceDetails& source); + + // Resets all metadata fields to their default (clean-install) state. + void ResetMetadataFields(); + + // Copies the overridden fields from the target source to this source. This is only the supported override fields. + void CopyOverrideFieldsFrom(const SourceDetails& overrideSource); + + // Sorts by Priority with higher values coming first in the order. + bool operator<(const SourceDetailsInternal& other) const; + + // If true, this is a tombstone, marking the deletion of a source at a lower priority origin. + bool IsTombstone = false; + + // If true, this is an override of a source at a lower priority. An override source only defines + // changes on top of the lower priority source, otherwise uses the same as the lower priority source. + bool IsOverride = false; + + // If false, this is not visible in GetCurrentSource or GetAllSources, it's only available when explicitly requested. + bool IsVisible = true; + + // Accepted agreements info. + std::string AcceptedAgreementsIdentifier; + int AcceptedAgreementFields = 0; + }; + + // Gets the internal details for a well known source. + SourceDetailsInternal GetWellKnownSourceDetailsInternal(WellKnownSource source); + + // Struct containing internal implementation of the source list. + // This contains all source data; including tombstoned sources. + struct SourceList + { + SourceList(); + + // Get a list of current sources references which can be used to update the contents in place. + // e.g. update the LastTimeUpdated value of sources. + std::vector> GetCurrentSourceRefs(); + + // Current source means source that's not in tombstone + SourceDetailsInternal* GetCurrentSource(std::string_view name); + + // Source includes ones in tombstone + SourceDetailsInternal* GetSource(std::string_view name); + + // Add/remove/edit a current source + void AddSource(const SourceDetailsInternal& details); + void RemoveSource(const SourceDetailsInternal& details); + void EditSource(const SourceDetailsInternal& details); + + // Reset a default source: removes any user-level entry (tombstone or override) + // and clears the source's metadata, restoring it to clean-install state. + void ResetSource(const SourceDetailsInternal& details); + + // Save source metadata; the particular source with the metadata update is given. + // The given source must already be in the internal source list. + void SaveMetadata(const SourceDetailsInternal& details); + + // Checks the source agreements and returns if agreements are satisfied. + bool CheckSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields); + + // Save agreements information. + void SaveAcceptedSourceAgreements(std::string_view sourceName, std::string_view agreementsIdentifier, ImplicitAgreementFieldEnum agreementFields); + + // Removes all settings streams associated with the source list. + // Implements `winget source reset --force`. + static void RemoveSettingsStreams(); + + private: + // Overwrites the source list with all sources. + void OverwriteSourceList(); + + // Overwrites the source list with the current metadata. + void OverwriteMetadata(); + + // calls std::find_if and return the iterator. + auto FindSource(std::string_view name, bool includeHidden = false); + + // Tries to find a named source from the specified origin. + [[nodiscard]] bool TryFindSourceByOrigin(std::string_view name, SourceOrigin origin, SourceDetailsInternal& targetSourceOut, bool includeHidden = false); + + std::vector GetSourcesByOrigin(SourceOrigin origin); + // Does *NOT* set metadata; call SaveMetadataInternal afterward. + [[nodiscard]] bool SetSourcesByOrigin(SourceOrigin origin, const std::vector& sources); + + std::vector GetMetadata(); + [[nodiscard]] bool SetMetadata(const std::vector& sources); + + // Save source metadata; the particular source with the metadata update is given. + // If remove is true, the given source is being removed. + void SaveMetadataInternal(const SourceDetailsInternal& details, bool remove = false); + + // Clears all metadata for the named source and persists the change. + void ResetMetadataInternal(const SourceDetailsInternal& details); + + std::vector m_sourceList; + Settings::Stream m_userSourcesStream; + Settings::Stream m_metadataStream; + }; +} diff --git a/src/AppInstallerRepositoryCore/SourcePolicy.cpp b/src/AppInstallerRepositoryCore/SourcePolicy.cpp index e3e71d51fa..22fabc7a80 100644 --- a/src/AppInstallerRepositoryCore/SourcePolicy.cpp +++ b/src/AppInstallerRepositoryCore/SourcePolicy.cpp @@ -1,234 +1,234 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SourcePolicy.h" -#include "Microsoft/PreIndexedPackageSourceFactory.h" -#include "Rest/RestSourceFactory.h" - -using namespace AppInstaller::Settings; - - -namespace AppInstaller::Repository -{ - namespace - { - // Checks whether a default source is enabled with the current settings. - // onlyExplicit determines whether we consider the not-configured state to be enabled or not. - bool IsDefaultSourceEnabled(WellKnownSource sourceToLog, ExperimentalFeature::Feature feature, bool onlyExplicit, TogglePolicy::Policy policy) - { - if (!ExperimentalFeature::IsEnabled(feature)) - { - // No need to log here - return false; - } - - if (onlyExplicit) - { - // No need to log here - return GroupPolicies().GetState(policy) == PolicyState::Enabled; - } - - if (!GroupPolicies().IsEnabled(policy)) - { - AICLI_LOG(Repo, Info, << "The default source " << GetWellKnownSourceName(sourceToLog) << " is disabled due to Group Policy"); - return false; - } - - return true; - } - - template - std::optional FindSourceInPolicy(std::string_view name, std::string_view type, std::string_view arg) - { - auto sourcesOpt = GroupPolicies().GetValue

(); - if (!sourcesOpt.has_value()) - { - return std::nullopt; - } - - const auto& sources = sourcesOpt.value(); - auto source = std::find_if( - sources.begin(), - sources.end(), - [&](const SourceFromPolicy& policySource) - { - return Utility::ICUCaseInsensitiveEquals(name, policySource.Name) && Utility::ICUCaseInsensitiveEquals(type, policySource.Type) && arg == policySource.Arg; - }); - - if (source == sources.end()) - { - return std::nullopt; - } - - return *source; - } - - template - bool IsSourceInPolicy(std::string_view name, std::string_view type, std::string_view arg) - { - return FindSourceInPolicy

(name, type, arg).has_value(); - } - } - - // Checks whether the Group Policy allows this user source. - // If it does it returns None, otherwise it returns which policy is blocking it. - // Note that this applies to user sources that are being added as well as user sources - // that already existed when the Group Policy came into effect. - TogglePolicy::Policy GetPolicyBlockingUserSource(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone) - { - // Reasons for not allowing: - // 1. The source is a tombstone for default source that is explicitly enabled - // 2. The source is a default source that is disabled - // 3. The source has the same name as a default source that is explicitly enabled (to prevent shadowing) - // 4. Allowed sources are disabled, blocking all user sources - // 5. There is an explicit list of allowed sources and this source is not in it - // - // We don't need to check sources added by policy as those have higher priority. - // - // Use the name and arg to match sources as we don't have the identifier before adding. - - // Case 1: - // The source is a tombstone and we need the policy to be explicitly enabled. - if (isTombstone) - { - if (name == GetWellKnownSourceName(WellKnownSource::WinGet) && IsWellKnownSourceEnabled(WellKnownSource::WinGet, true)) - { - return TogglePolicy::Policy::DefaultSource; - } - - if (name == GetWellKnownSourceName(WellKnownSource::MicrosoftStore) && IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore, true)) - { - return TogglePolicy::Policy::MSStoreSource; - } - - if (name == GetWellKnownSourceName(WellKnownSource::WinGetFont) && IsWellKnownSourceEnabled(WellKnownSource::WinGetFont, true)) - { - return TogglePolicy::Policy::FontSource; - } - - // Any other tombstone is allowed - return TogglePolicy::Policy::None; - } - - // Case 2: - // - The source is not a tombstone and we don't need the policy to be explicitly enabled. - // - Check only against the source argument and type as the user source may have a different name. - // - Do a case-insensitive check as the domain portion of the URL is case-insensitive, - // and we don't need case sensitivity for the rest as we control the domain. - if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::WinGet)) && - Utility::CaseInsensitiveEquals(type, Microsoft::PreIndexedPackageSourceFactory::Type())) - { - return IsWellKnownSourceEnabled(WellKnownSource::WinGet) ? TogglePolicy::Policy::None : TogglePolicy::Policy::DefaultSource; - } - - if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::MicrosoftStore)) && - Utility::CaseInsensitiveEquals(type, Rest::RestSourceFactory::Type())) - { - return IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore) ? TogglePolicy::Policy::None : TogglePolicy::Policy::MSStoreSource; - } - - if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::WinGetFont)) && - Utility::CaseInsensitiveEquals(type, Microsoft::PreIndexedPackageSourceFactory::Type())) - { - return IsWellKnownSourceEnabled(WellKnownSource::WinGetFont) ? TogglePolicy::Policy::None : TogglePolicy::Policy::FontSource; - } - - // Case 3: - // If the source has the same name as a default source, it is shadowing with a different argument - // (as it didn't match above). We only care if Group Policy requires the default source. - if (name == GetWellKnownSourceName(WellKnownSource::WinGet) && IsWellKnownSourceEnabled(WellKnownSource::WinGet, true)) - { - AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows the default source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); - return TogglePolicy::Policy::DefaultSource; - } - - if (name == GetWellKnownSourceName(WellKnownSource::MicrosoftStore) && IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore, true)) - { - AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows a default MS Store source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); - return TogglePolicy::Policy::MSStoreSource; - } - - if (name == GetWellKnownSourceName(WellKnownSource::WinGetFont) && IsWellKnownSourceEnabled(WellKnownSource::WinGetFont, true)) - { - AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows a default font source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); - return TogglePolicy::Policy::FontSource; - } - - // Case 4: - // The guard in the source add command should already block adding. - // This check drops existing user sources. - auto allowedSourcesPolicy = GroupPolicies().GetState(TogglePolicy::Policy::AllowedSources); - if (allowedSourcesPolicy == PolicyState::Disabled) - { - AICLI_LOG(Repo, Warning, << "User sources are disabled by Group Policy"); - return TogglePolicy::Policy::AllowedSources; - } - - // Case 5: - if (allowedSourcesPolicy == PolicyState::Enabled) - { - if (!IsSourceInPolicy(name, type, arg)) - { - AICLI_LOG(Repo, Warning, << "Source is not in the Group Policy allowed list. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); - return TogglePolicy::Policy::AllowedSources; - } - } - - return TogglePolicy::Policy::None; - } - - bool IsUserSourceAllowedByPolicy(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone) - { - return GetPolicyBlockingUserSource(name, type, arg, isTombstone) == TogglePolicy::Policy::None; - } - - bool IsWellKnownSourceEnabled(WellKnownSource source, bool onlyExplicit) - { - switch (source) - { - case AppInstaller::Repository::WellKnownSource::WinGet: - return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::DefaultSource); - case AppInstaller::Repository::WellKnownSource::MicrosoftStore: - return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::MSStoreSource); - case AppInstaller::Repository::WellKnownSource::WinGetFont: - return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::FontSource); - case AppInstaller::Repository::WellKnownSource::DesktopFrameworks: - // No corresponding policy available for this source. - return true; - } - - return false; - } - - void EnsureSourceIsRemovable(const SourceDetailsInternal& source) - { - // Block removing sources added by Group Policy - if (source.Origin == SourceOrigin::GroupPolicy) - { - AICLI_LOG(Repo, Error, << "Cannot remove source added by Group Policy"); - throw GroupPolicyException(TogglePolicy::Policy::AdditionalSources); - } - - // Block removing default sources required by Group Policy. - if (source.Origin == SourceOrigin::Default) - { - if (GroupPolicies().GetState(TogglePolicy::Policy::DefaultSource) == PolicyState::Enabled && - source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::WinGet)) - { - throw GroupPolicyException(TogglePolicy::Policy::DefaultSource); - } - - if (GroupPolicies().GetState(TogglePolicy::Policy::MSStoreSource) == PolicyState::Enabled && - source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::MicrosoftStore)) - { - throw GroupPolicyException(TogglePolicy::Policy::MSStoreSource); - } - - if (GroupPolicies().GetState(TogglePolicy::Policy::FontSource) == PolicyState::Enabled && - source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::WinGetFont)) - { - throw GroupPolicyException(TogglePolicy::Policy::FontSource); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SourcePolicy.h" +#include "Microsoft/PreIndexedPackageSourceFactory.h" +#include "Rest/RestSourceFactory.h" + +using namespace AppInstaller::Settings; + + +namespace AppInstaller::Repository +{ + namespace + { + // Checks whether a default source is enabled with the current settings. + // onlyExplicit determines whether we consider the not-configured state to be enabled or not. + bool IsDefaultSourceEnabled(WellKnownSource sourceToLog, ExperimentalFeature::Feature feature, bool onlyExplicit, TogglePolicy::Policy policy) + { + if (!ExperimentalFeature::IsEnabled(feature)) + { + // No need to log here + return false; + } + + if (onlyExplicit) + { + // No need to log here + return GroupPolicies().GetState(policy) == PolicyState::Enabled; + } + + if (!GroupPolicies().IsEnabled(policy)) + { + AICLI_LOG(Repo, Info, << "The default source " << GetWellKnownSourceName(sourceToLog) << " is disabled due to Group Policy"); + return false; + } + + return true; + } + + template + std::optional FindSourceInPolicy(std::string_view name, std::string_view type, std::string_view arg) + { + auto sourcesOpt = GroupPolicies().GetValue

(); + if (!sourcesOpt.has_value()) + { + return std::nullopt; + } + + const auto& sources = sourcesOpt.value(); + auto source = std::find_if( + sources.begin(), + sources.end(), + [&](const SourceFromPolicy& policySource) + { + return Utility::ICUCaseInsensitiveEquals(name, policySource.Name) && Utility::ICUCaseInsensitiveEquals(type, policySource.Type) && arg == policySource.Arg; + }); + + if (source == sources.end()) + { + return std::nullopt; + } + + return *source; + } + + template + bool IsSourceInPolicy(std::string_view name, std::string_view type, std::string_view arg) + { + return FindSourceInPolicy

(name, type, arg).has_value(); + } + } + + // Checks whether the Group Policy allows this user source. + // If it does it returns None, otherwise it returns which policy is blocking it. + // Note that this applies to user sources that are being added as well as user sources + // that already existed when the Group Policy came into effect. + TogglePolicy::Policy GetPolicyBlockingUserSource(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone) + { + // Reasons for not allowing: + // 1. The source is a tombstone for default source that is explicitly enabled + // 2. The source is a default source that is disabled + // 3. The source has the same name as a default source that is explicitly enabled (to prevent shadowing) + // 4. Allowed sources are disabled, blocking all user sources + // 5. There is an explicit list of allowed sources and this source is not in it + // + // We don't need to check sources added by policy as those have higher priority. + // + // Use the name and arg to match sources as we don't have the identifier before adding. + + // Case 1: + // The source is a tombstone and we need the policy to be explicitly enabled. + if (isTombstone) + { + if (name == GetWellKnownSourceName(WellKnownSource::WinGet) && IsWellKnownSourceEnabled(WellKnownSource::WinGet, true)) + { + return TogglePolicy::Policy::DefaultSource; + } + + if (name == GetWellKnownSourceName(WellKnownSource::MicrosoftStore) && IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore, true)) + { + return TogglePolicy::Policy::MSStoreSource; + } + + if (name == GetWellKnownSourceName(WellKnownSource::WinGetFont) && IsWellKnownSourceEnabled(WellKnownSource::WinGetFont, true)) + { + return TogglePolicy::Policy::FontSource; + } + + // Any other tombstone is allowed + return TogglePolicy::Policy::None; + } + + // Case 2: + // - The source is not a tombstone and we don't need the policy to be explicitly enabled. + // - Check only against the source argument and type as the user source may have a different name. + // - Do a case-insensitive check as the domain portion of the URL is case-insensitive, + // and we don't need case sensitivity for the rest as we control the domain. + if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::WinGet)) && + Utility::CaseInsensitiveEquals(type, Microsoft::PreIndexedPackageSourceFactory::Type())) + { + return IsWellKnownSourceEnabled(WellKnownSource::WinGet) ? TogglePolicy::Policy::None : TogglePolicy::Policy::DefaultSource; + } + + if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::MicrosoftStore)) && + Utility::CaseInsensitiveEquals(type, Rest::RestSourceFactory::Type())) + { + return IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore) ? TogglePolicy::Policy::None : TogglePolicy::Policy::MSStoreSource; + } + + if (Utility::CaseInsensitiveEquals(arg, GetWellKnownSourceArg(WellKnownSource::WinGetFont)) && + Utility::CaseInsensitiveEquals(type, Microsoft::PreIndexedPackageSourceFactory::Type())) + { + return IsWellKnownSourceEnabled(WellKnownSource::WinGetFont) ? TogglePolicy::Policy::None : TogglePolicy::Policy::FontSource; + } + + // Case 3: + // If the source has the same name as a default source, it is shadowing with a different argument + // (as it didn't match above). We only care if Group Policy requires the default source. + if (name == GetWellKnownSourceName(WellKnownSource::WinGet) && IsWellKnownSourceEnabled(WellKnownSource::WinGet, true)) + { + AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows the default source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); + return TogglePolicy::Policy::DefaultSource; + } + + if (name == GetWellKnownSourceName(WellKnownSource::MicrosoftStore) && IsWellKnownSourceEnabled(WellKnownSource::MicrosoftStore, true)) + { + AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows a default MS Store source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); + return TogglePolicy::Policy::MSStoreSource; + } + + if (name == GetWellKnownSourceName(WellKnownSource::WinGetFont) && IsWellKnownSourceEnabled(WellKnownSource::WinGetFont, true)) + { + AICLI_LOG(Repo, Warning, << "User source is not allowed as it shadows a default font source. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); + return TogglePolicy::Policy::FontSource; + } + + // Case 4: + // The guard in the source add command should already block adding. + // This check drops existing user sources. + auto allowedSourcesPolicy = GroupPolicies().GetState(TogglePolicy::Policy::AllowedSources); + if (allowedSourcesPolicy == PolicyState::Disabled) + { + AICLI_LOG(Repo, Warning, << "User sources are disabled by Group Policy"); + return TogglePolicy::Policy::AllowedSources; + } + + // Case 5: + if (allowedSourcesPolicy == PolicyState::Enabled) + { + if (!IsSourceInPolicy(name, type, arg)) + { + AICLI_LOG(Repo, Warning, << "Source is not in the Group Policy allowed list. Name [" << name << "]. Arg [" << arg << "] Type [" << type << ']'); + return TogglePolicy::Policy::AllowedSources; + } + } + + return TogglePolicy::Policy::None; + } + + bool IsUserSourceAllowedByPolicy(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone) + { + return GetPolicyBlockingUserSource(name, type, arg, isTombstone) == TogglePolicy::Policy::None; + } + + bool IsWellKnownSourceEnabled(WellKnownSource source, bool onlyExplicit) + { + switch (source) + { + case AppInstaller::Repository::WellKnownSource::WinGet: + return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::DefaultSource); + case AppInstaller::Repository::WellKnownSource::MicrosoftStore: + return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::MSStoreSource); + case AppInstaller::Repository::WellKnownSource::WinGetFont: + return IsDefaultSourceEnabled(source, ExperimentalFeature::Feature::None, onlyExplicit, TogglePolicy::Policy::FontSource); + case AppInstaller::Repository::WellKnownSource::DesktopFrameworks: + // No corresponding policy available for this source. + return true; + } + + return false; + } + + void EnsureSourceIsRemovable(const SourceDetailsInternal& source) + { + // Block removing sources added by Group Policy + if (source.Origin == SourceOrigin::GroupPolicy) + { + AICLI_LOG(Repo, Error, << "Cannot remove source added by Group Policy"); + throw GroupPolicyException(TogglePolicy::Policy::AdditionalSources); + } + + // Block removing default sources required by Group Policy. + if (source.Origin == SourceOrigin::Default) + { + if (GroupPolicies().GetState(TogglePolicy::Policy::DefaultSource) == PolicyState::Enabled && + source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::WinGet)) + { + throw GroupPolicyException(TogglePolicy::Policy::DefaultSource); + } + + if (GroupPolicies().GetState(TogglePolicy::Policy::MSStoreSource) == PolicyState::Enabled && + source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::MicrosoftStore)) + { + throw GroupPolicyException(TogglePolicy::Policy::MSStoreSource); + } + + if (GroupPolicies().GetState(TogglePolicy::Policy::FontSource) == PolicyState::Enabled && + source.Identifier == GetWellKnownSourceIdentifier(WellKnownSource::WinGetFont)) + { + throw GroupPolicyException(TogglePolicy::Policy::FontSource); + } + } + } +} diff --git a/src/AppInstallerRepositoryCore/SourcePolicy.h b/src/AppInstallerRepositoryCore/SourcePolicy.h index 535949c608..3f3afb3c13 100644 --- a/src/AppInstallerRepositoryCore/SourcePolicy.h +++ b/src/AppInstallerRepositoryCore/SourcePolicy.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "SourceList.h" -#include - -#include - - -namespace AppInstaller::Repository -{ - // Checks whether the Group Policy allows this user source. - // If it does it returns None, otherwise it returns which policy is blocking it. - // Note that this applies to user sources that are being added as well as user sources - // that already existed when the Group Policy came into effect. - Settings::TogglePolicy::Policy GetPolicyBlockingUserSource(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone); - - // Helper that converts the result of GetPolicyBlockingUserSource into a bool. - bool IsUserSourceAllowedByPolicy(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone); - - // Determines if a well known source is enabled; if onlyExplicit is true, it must be explicitly enabled by group policy. - bool IsWellKnownSourceEnabled(WellKnownSource source, bool onlyExplicit = false); - - // Checks that the specified source is removable per policy; throwing if it is not. - void EnsureSourceIsRemovable(const SourceDetailsInternal& source); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "SourceList.h" +#include + +#include + + +namespace AppInstaller::Repository +{ + // Checks whether the Group Policy allows this user source. + // If it does it returns None, otherwise it returns which policy is blocking it. + // Note that this applies to user sources that are being added as well as user sources + // that already existed when the Group Policy came into effect. + Settings::TogglePolicy::Policy GetPolicyBlockingUserSource(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone); + + // Helper that converts the result of GetPolicyBlockingUserSource into a bool. + bool IsUserSourceAllowedByPolicy(std::string_view name, std::string_view type, std::string_view arg, bool isTombstone); + + // Determines if a well known source is enabled; if onlyExplicit is true, it must be explicitly enabled by group policy. + bool IsWellKnownSourceEnabled(WellKnownSource source, bool onlyExplicit = false); + + // Checks that the specified source is removable per policy; throwing if it is not. + void EnsureSourceIsRemovable(const SourceDetailsInternal& source); +} diff --git a/src/AppInstallerRepositoryCore/SourceUpdateChecks.cpp b/src/AppInstallerRepositoryCore/SourceUpdateChecks.cpp index 783bf95d7e..ed640e953e 100644 --- a/src/AppInstallerRepositoryCore/SourceUpdateChecks.cpp +++ b/src/AppInstallerRepositoryCore/SourceUpdateChecks.cpp @@ -1,74 +1,74 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SourceUpdateChecks.h" -#include "ISource.h" -#include - -using namespace std::chrono_literals; - -namespace AppInstaller::Repository -{ - bool ShouldUpdateBeforeOpen(ISourceReference* sourceReference, const std::optional& requestedUpdateInterval) - { - const SourceDetails& details = sourceReference->GetDetails(); - - // Always respect this value to prevent server overloading - if (IsBeforeDoNotUpdateBeforeTime(details)) - { - return false; - } - - // Allow the source reference to decide beyond this - return sourceReference->ShouldUpdateBeforeOpen(requestedUpdateInterval); - } - - bool IsBeforeDoNotUpdateBeforeTime(const SourceDetails& details) - { - if (std::chrono::system_clock::now() < details.DoNotUpdateBefore) - { - AICLI_LOG(Repo, Info, << "Background update for `" << details.Name << "` is suppressed until: " << details.DoNotUpdateBefore); - return true; - } - else - { - return false; - } - } - - bool IsAfterUpdateCheckTime(const SourceDetails& details, std::optional requestedUpdateInterval) - { - return IsAfterUpdateCheckTime(details.Name, details.LastUpdateTime, requestedUpdateInterval); - } - - bool IsAfterUpdateCheckTime(std::string_view name, std::chrono::system_clock::time_point lastUpdateTime, std::optional requestedUpdateInterval) - { - constexpr static TimeSpan s_ZeroMins = 0min; - - TimeSpan autoUpdateTime; - if (requestedUpdateInterval) - { - autoUpdateTime = requestedUpdateInterval.value(); - } - else - { - autoUpdateTime = Settings::User().Get(); - } - - // A value of zero means no auto update, to get update the source run `winget update` - if (autoUpdateTime != s_ZeroMins) - { - auto timeSinceLastUpdate = std::chrono::system_clock::now() - lastUpdateTime; - if (timeSinceLastUpdate > autoUpdateTime) - { - AICLI_LOG(Repo, Info, << "Source `" << name << "` after auto update time [" << - (requestedUpdateInterval ? "(override) " : "") << - std::chrono::duration_cast(autoUpdateTime).count() << " mins]; it has been at least " << - std::chrono::duration_cast(timeSinceLastUpdate).count() << " mins"); - return true; - } - } - - return false; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SourceUpdateChecks.h" +#include "ISource.h" +#include + +using namespace std::chrono_literals; + +namespace AppInstaller::Repository +{ + bool ShouldUpdateBeforeOpen(ISourceReference* sourceReference, const std::optional& requestedUpdateInterval) + { + const SourceDetails& details = sourceReference->GetDetails(); + + // Always respect this value to prevent server overloading + if (IsBeforeDoNotUpdateBeforeTime(details)) + { + return false; + } + + // Allow the source reference to decide beyond this + return sourceReference->ShouldUpdateBeforeOpen(requestedUpdateInterval); + } + + bool IsBeforeDoNotUpdateBeforeTime(const SourceDetails& details) + { + if (std::chrono::system_clock::now() < details.DoNotUpdateBefore) + { + AICLI_LOG(Repo, Info, << "Background update for `" << details.Name << "` is suppressed until: " << details.DoNotUpdateBefore); + return true; + } + else + { + return false; + } + } + + bool IsAfterUpdateCheckTime(const SourceDetails& details, std::optional requestedUpdateInterval) + { + return IsAfterUpdateCheckTime(details.Name, details.LastUpdateTime, requestedUpdateInterval); + } + + bool IsAfterUpdateCheckTime(std::string_view name, std::chrono::system_clock::time_point lastUpdateTime, std::optional requestedUpdateInterval) + { + constexpr static TimeSpan s_ZeroMins = 0min; + + TimeSpan autoUpdateTime; + if (requestedUpdateInterval) + { + autoUpdateTime = requestedUpdateInterval.value(); + } + else + { + autoUpdateTime = Settings::User().Get(); + } + + // A value of zero means no auto update, to get update the source run `winget update` + if (autoUpdateTime != s_ZeroMins) + { + auto timeSinceLastUpdate = std::chrono::system_clock::now() - lastUpdateTime; + if (timeSinceLastUpdate > autoUpdateTime) + { + AICLI_LOG(Repo, Info, << "Source `" << name << "` after auto update time [" << + (requestedUpdateInterval ? "(override) " : "") << + std::chrono::duration_cast(autoUpdateTime).count() << " mins]; it has been at least " << + std::chrono::duration_cast(timeSinceLastUpdate).count() << " mins"); + return true; + } + } + + return false; + } +} diff --git a/src/AppInstallerRepositoryCore/SourceUpdateChecks.h b/src/AppInstallerRepositoryCore/SourceUpdateChecks.h index 250f8421ef..4e744e20c5 100644 --- a/src/AppInstallerRepositoryCore/SourceUpdateChecks.h +++ b/src/AppInstallerRepositoryCore/SourceUpdateChecks.h @@ -1,20 +1,20 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Public/winget/RepositorySource.h" -#include - -namespace AppInstaller::Repository -{ - // Determines if the given source should update before opening. - bool ShouldUpdateBeforeOpen(ISourceReference* sourceReference, const std::optional& requestedUpdateInterval); - - // Determines if the current time is before a previously stored "do note update before" time. - bool IsBeforeDoNotUpdateBeforeTime(const SourceDetails& details); - - // Determines if the given details and desired update interval indicate an update check should occur. - bool IsAfterUpdateCheckTime(const SourceDetails& details, std::optional requestedUpdateInterval); - - // Determines if the given details and desired update interval indicate an update check should occur. - bool IsAfterUpdateCheckTime(std::string_view name, std::chrono::system_clock::time_point lastUpdateTime, std::optional requestedUpdateInterval); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Public/winget/RepositorySource.h" +#include + +namespace AppInstaller::Repository +{ + // Determines if the given source should update before opening. + bool ShouldUpdateBeforeOpen(ISourceReference* sourceReference, const std::optional& requestedUpdateInterval); + + // Determines if the current time is before a previously stored "do note update before" time. + bool IsBeforeDoNotUpdateBeforeTime(const SourceDetails& details); + + // Determines if the given details and desired update interval indicate an update check should occur. + bool IsAfterUpdateCheckTime(const SourceDetails& details, std::optional requestedUpdateInterval); + + // Determines if the given details and desired update interval indicate an update check should occur. + bool IsAfterUpdateCheckTime(std::string_view name, std::chrono::system_clock::time_point lastUpdateTime, std::optional requestedUpdateInterval); +} diff --git a/src/AppInstallerRepositoryCore/packages.config b/src/AppInstallerRepositoryCore/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/AppInstallerRepositoryCore/packages.config +++ b/src/AppInstallerRepositoryCore/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/AppInstallerRepositoryCore/pch.h b/src/AppInstallerRepositoryCore/pch.h index a0a55ee61d..28a29b1764 100644 --- a/src/AppInstallerRepositoryCore/pch.h +++ b/src/AppInstallerRepositoryCore/pch.h @@ -5,7 +5,7 @@ #define NOMINMAX #include #include -#include +#include #include #include #include @@ -16,13 +16,13 @@ #include #include #include -#include +#include #include #pragma warning( pop ) #include -#include +#include #include #include #include @@ -43,7 +43,7 @@ #include #include #include -#include +#include #include #include #include @@ -56,15 +56,15 @@ #include #include -#include - +#include + #pragma warning( push ) #pragma warning ( disable : 26495 26439 ) #include #include #include -#pragma warning( pop ) - +#pragma warning( pop ) + #include #include #include @@ -80,4 +80,4 @@ #include #include #include -#include +#include diff --git a/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj b/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj index 1ae20dfe22..adf3c4c6c6 100644 --- a/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj +++ b/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj @@ -1,424 +1,424 @@ - - - - - true - true - true - 15.0 - {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51} - Win32Proj - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - Fuzzing - x64 - - - Fuzzing - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - StaticLibrary - - - true - true - - - false - true - false - - - false - true - false - - - false - false - false - true - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _NO_ASYNCRTIMP;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - - - false - Windows - Windows - - - - - _NO_ASYNCRTIMP;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - false - false - - - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - false - false - false - - - true - true - false - Windows - Windows - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - true - true - true - true - true - false - false - false - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - - - true - true - false - Windows - Windows - Windows - - - - - MaxSpeed - true - true - _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD;WINGET_DISABLE_FOR_FUZZING;_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION - $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) - true - stdcpp17 - MultiThreaded - %(AdditionalOptions) /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div - false - - - true - true - false - Windows - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - + + + + + true + true + true + 15.0 + {F3F6E699-BC5D-4950-8A05-E49DD9EB0D51} + Win32Proj + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + Fuzzing + x64 + + + Fuzzing + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + StaticLibrary + + + true + true + + + false + true + false + + + false + true + false + + + false + false + false + true + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _NO_ASYNCRTIMP;_DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + + + false + Windows + Windows + + + + + _NO_ASYNCRTIMP;WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + false + false + + + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + true + true + true + true + true + false + false + false + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + + + true + true + false + Windows + Windows + Windows + + + + + MaxSpeed + true + true + _NO_ASYNCRTIMP;NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD;WINGET_DISABLE_FOR_FUZZING;_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION + $(ProjectDir);$(ProjectDir)Public;$(ProjectDir)Telemetry;$(ProjectDir)..\binver;%(AdditionalIncludeDirectories) + true + stdcpp17 + MultiThreaded + %(AdditionalOptions) /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div + false + + + true + true + false + Windows + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + diff --git a/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj.filters b/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj.filters index 4c865ad740..61457db664 100644 --- a/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj.filters +++ b/src/AppInstallerSharedLib/AppInstallerSharedLib.vcxproj.filters @@ -1,252 +1,252 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {5cdf3fa3-e657-4d84-81bb-f740aa476143} - - - {41035fd6-dc74-4464-b9b1-4ffe95d6789c} - - - {3a5b2424-6c80-4edc-85bc-f371f2e93a33} - - - {d5b3a812-cc25-4826-b380-0488cd0944e1} - - - {2403870a-5bb9-461f-8558-877c23ec487b} - - - - - Header Files - - - Public - - - Public - - - Public - - - Public - - - Public - - - Header Files - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public - - - Public - - - Public\Telemetry - Do Not Modify - - - Public\Telemetry - Do Not Modify - - - Public\winget - - - Public\winget - - - Header Files - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - ICU - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - Public\winget - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - SQLite - - - SQLite - - - SQLite - - - SQLite - - - SQLite - - - ICU - - - SQLite - - - Source Files - - - Source Files - - - Source Files - - - SQLite - - - Source Files - - - - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {5cdf3fa3-e657-4d84-81bb-f740aa476143} + + + {41035fd6-dc74-4464-b9b1-4ffe95d6789c} + + + {3a5b2424-6c80-4edc-85bc-f371f2e93a33} + + + {d5b3a812-cc25-4826-b380-0488cd0944e1} + + + {2403870a-5bb9-461f-8558-877c23ec487b} + + + + + Header Files + + + Public + + + Public + + + Public + + + Public + + + Public + + + Header Files + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public + + + Public + + + Public\Telemetry - Do Not Modify + + + Public\Telemetry - Do Not Modify + + + Public\winget + + + Public\winget + + + Header Files + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + ICU + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + Public\winget + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + SQLite + + + SQLite + + + SQLite + + + SQLite + + + SQLite + + + ICU + + + SQLite + + + Source Files + + + Source Files + + + Source Files + + + SQLite + + + Source Files + + + + + + + + + \ No newline at end of file diff --git a/src/AppInstallerSharedLib/AppInstallerStrings.cpp b/src/AppInstallerSharedLib/AppInstallerStrings.cpp index 19b17eeee1..3ece225136 100644 --- a/src/AppInstallerSharedLib/AppInstallerStrings.cpp +++ b/src/AppInstallerSharedLib/AppInstallerStrings.cpp @@ -1,1162 +1,1162 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerErrors.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerSHA256.h" - -namespace AppInstaller::Utility -{ - // Same as std::isspace(char) -#define AICLI_SPACE_CHARS " \f\n\r\t\v"sv - - using namespace std::string_view_literals; - constexpr std::string_view s_SpaceChars = AICLI_SPACE_CHARS; - constexpr std::wstring_view s_WideSpaceChars = L"" AICLI_SPACE_CHARS; - - namespace - { - // Contains the ICU objects necessary to do break iteration. - struct ICUBreakIterator - { - ICUBreakIterator(std::string_view input, UBreakIteratorType type) - { - UErrorCode err = U_ZERO_ERROR; - - m_text.reset(utext_openUTF8(nullptr, input.data(), wil::safe_cast(input.length()), &err)); - if (U_FAILURE(err)) - { - AICLI_LOG(Core, Error, << "utext_openUTF8 returned " << err); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); - } - - m_brk.reset(ubrk_open(type, nullptr, nullptr, 0, &err)); - if (U_FAILURE(err)) - { - AICLI_LOG(Core, Error, << "ubrk_open returned " << err); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); - } - - ubrk_setUText(m_brk.get(), m_text.get(), &err); - if (U_FAILURE(err)) - { - AICLI_LOG(Core, Error, << "ubrk_setUText returned " << err); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); - } - - int32_t i = ubrk_first(m_brk.get()); - if (i != 0) - { - AICLI_LOG(Core, Error, << "ubrk_first returned " << i); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); - } - } - - // Gets the current break value; the byte offset or UBRK_DONE. - int32_t CurrentBreak() const { return m_currentBrk; } - - // Gets the current byte offset, throwing if the value is UBRK_DONE or negative. - size_t CurrentOffset() const - { - THROW_HR_IF(E_NOT_VALID_STATE, m_currentBrk < 0); - return static_cast(m_currentBrk); - } - - // Returns the byte offset of the next break in the string - int32_t Next() - { - m_currentBrk = ubrk_next(m_brk.get()); - return m_currentBrk; - } - - // Returns the byte offset of the next count'th break in the string - int32_t Advance(size_t count) - { - for (size_t i = 0; i < count && m_currentBrk != UBRK_DONE; ++i) - { - Next(); - } - return m_currentBrk; - } - - // Returns code point of the character at m_currentBrk, or U_SENTINEL if m_currentBrk points to the end. - UChar32 CurrentCodePoint() - { - return utext_char32At(m_text.get(), m_currentBrk); - } - - // Returns the status from the break rule that determined the most recently break position. - int32_t CurrentRuleStatus() - { - return ubrk_getRuleStatus(m_brk.get()); - } - - private: - wil::unique_any m_text; - wil::unique_any m_brk; - int32_t m_currentBrk = 0; - }; - - template - StringType& TrimTemplate(StringType& input, std::basic_string_view spaceChars) - { - if (!input.empty()) - { - size_t begin = input.find_first_not_of(spaceChars); - size_t end = input.find_last_not_of(spaceChars); - - if (begin == StringType::npos || end == StringType::npos) - { - input = {}; - } - else if (begin != 0 || end != input.length() - 1) - { - input = input.substr(begin, (end - begin) + 1); - } - } - - return input; - } - - template - StringType TrimCopyTemplate(const StringType& input) - { - StringType result = input; - Utility::Trim(result); - return result; - } - - template - StringType TrimMoveTemplate(StringType&& input) - { - StringType result = std::move(input); - Utility::Trim(result); - return result; - } - - template - std::vector SplitTemplate(const StringType& input, typename StringType::value_type separator, bool trim) - { - std::vector result; - size_t startIndex = 0; - size_t endIndex = 0; - - while ((endIndex = input.find(separator, startIndex)) != StringType::npos) - { - StringType substring = input.substr(startIndex, endIndex - startIndex); - - if (trim) - { - Utility::Trim(substring); - } - - result.emplace_back(std::move(substring)); - startIndex = endIndex + 1; - } - - result.emplace_back(trim ? Utility::Trim(input.substr(startIndex)) : input.substr(startIndex)); - return result; - } - } - - bool CaseInsensitiveEquals(std::string_view a, std::string_view b) - { - return ToLower(a) == ToLower(b); - } - - bool CaseInsensitiveEquals(std::wstring_view a, std::wstring_view b) - { - return ToLower(a) == ToLower(b); - } - - bool CaseInsensitiveContains(const std::vector& a, std::string_view b) - { - auto B = ToLower(b); - return std::any_of(a.begin(), a.end(), [&](const std::string_view& s) { return ToLower(s) == B; }); - } - - bool StartsWith(std::wstring_view a, std::wstring_view b) - { - return a.length() >= b.length() && a.substr(0, b.length()) == b; - } - - bool CaseInsensitiveStartsWith(std::string_view a, std::string_view b) - { - return a.length() >= b.length() && CaseInsensitiveEquals(a.substr(0, b.length()), b); - } - - bool CaseInsensitiveStartsWith(std::wstring_view a, std::wstring_view b) - { - return a.length() >= b.length() && CaseInsensitiveEquals(a.substr(0, b.length()), b); - } - - bool CaseInsensitiveContainsSubstring(std::string_view a, std::string_view b) - { - auto it = std::search( - a.begin(), a.end(), - b.begin(), b.end(), - [](char ch1, char ch2) { return std::tolower(ch1) == std::tolower(ch2); } - ); - return (it != a.end()); - } - - bool ContainsSubstring(std::string_view a, std::string_view b) - { - auto it = std::search( - a.begin(), a.end(), - b.begin(), b.end() - ); - return (it != a.end()); - } - - bool ICUCaseInsensitiveEquals(std::string_view a, std::string_view b) - { - return FoldCase(a) == FoldCase(b); - } - - bool ICUCaseInsensitiveStartsWith(std::string_view a, std::string_view b) - { - return a.length() >= b.length() && ICUCaseInsensitiveEquals(a.substr(0, b.length()), b); - } - - std::string ConvertToUTF8(std::wstring_view input) - { - if (input.empty()) - { - return {}; - } - - int utf8ByteCount = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0, nullptr, nullptr); - THROW_LAST_ERROR_IF(utf8ByteCount == 0); - - // Since the string view should not contain the null char, the result won't either. - // This allows us to use the resulting size value directly in the string constructor. - std::string result(wil::safe_cast(utf8ByteCount), '\0'); - - int utf8BytesWritten = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size()), nullptr, nullptr); - FAIL_FAST_HR_IF(E_UNEXPECTED, utf8ByteCount != utf8BytesWritten); - - return result; - } - - std::wstring ConvertToUTF16(std::string_view input, UINT codePage) - { - if (input.empty()) - { - return {}; - } - - int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0); - THROW_LAST_ERROR_IF(utf16CharCount == 0); - - // Since the string view should not contain the null char, the result won't either. - // This allows us to use the resulting size value directly in the string constructor. - std::wstring result(wil::safe_cast(utf16CharCount), L'\0'); - - int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size())); - FAIL_FAST_HR_IF(E_UNEXPECTED, utf16CharCount != utf16CharsWritten); - - return result; - } - - std::optional TryConvertToUTF16(std::string_view input, UINT codePage) - { - if (input.empty()) - { - return std::wstring{}; - } - - int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0); - if (utf16CharCount == 0) - { - return {}; - } - - // Since the string view should not contain the null char, the result won't either. - // This allows us to use the resulting size value directly in the string constructor. - std::wstring result(wil::safe_cast(utf16CharCount), L'\0'); - - int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size())); - if (utf16CharCount != utf16CharsWritten) - { - return {}; - } - - return std::optional{ result }; - } - - std::u32string ConvertToUTF32(std::string_view input) - { - if (input.empty()) - { - return {}; - } - - UErrorCode errorCode = UErrorCode::U_ZERO_ERROR; - auto utf32ByteCount= ucnv_convert("UTF-32", "UTF-8", nullptr, 0, input.data(), static_cast(input.size()), &errorCode); - - if (errorCode != U_BUFFER_OVERFLOW_ERROR) - { - AICLI_LOG(Core, Error, << "ucnv_convert returned " << errorCode); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR); - } - - FAIL_FAST_HR_IF(E_UNEXPECTED, utf32ByteCount % sizeof(char32_t) != 0); - auto utf32CharCount = utf32ByteCount / sizeof(char32_t); - std::u32string result(utf32CharCount, U'\0'); - - errorCode = UErrorCode::U_ZERO_ERROR; - - auto utf32BytesWritten = ucnv_convert("UTF-32", "UTF-8", (char*)(result.data()), utf32ByteCount, input.data(), static_cast(input.size()), &errorCode); - - // The size we pass to ucnv_convert is not enough for it to put in the null terminator, - // which wouldn't work anyways as it puts a single byte. - if (errorCode != U_STRING_NOT_TERMINATED_WARNING) - { - AICLI_LOG(Core, Error, << "ucnv_convert returned " << errorCode); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR); - } - - FAIL_FAST_HR_IF(E_UNEXPECTED, utf32ByteCount != utf32BytesWritten); - - return result; - } - - size_t UTF8Length(std::string_view input) - { - ICUBreakIterator itr{ input, UBRK_CHARACTER }; - - size_t numGraphemeClusters = 0; - - while (itr.Next() != UBRK_DONE) - { - numGraphemeClusters++; - } - - return numGraphemeClusters; - } - - size_t UTF8ColumnWidth(const NormalizedUTF8& input) - { - ICUBreakIterator itr{ input, UBRK_CHARACTER }; - - size_t columnWidth = 0; - UChar32 currentCP = 0; - - currentCP = itr.CurrentCodePoint(); - while (itr.Next() != UBRK_DONE && currentCP != U_SENTINEL) - { - int32_t width = u_getIntPropertyValue(currentCP, UCHAR_EAST_ASIAN_WIDTH); - columnWidth += width == U_EA_FULLWIDTH || width == U_EA_WIDE ? 2 : 1; - - currentCP = itr.CurrentCodePoint(); - } - - return columnWidth; - } - - std::string_view UTF8Substring(std::string_view input, size_t offset, size_t count) - { - ICUBreakIterator itr{ input, UBRK_CHARACTER }; - - // Offset was past end, throw just like std::string::substr - if (itr.Advance(offset) == UBRK_DONE) - { - throw std::out_of_range("UTF8Substring: offset past end of input"); - } - - size_t utf8Offset = itr.CurrentOffset(); - size_t utf8Count = 0; - - // Count past end, convert to npos to get all of string - if (itr.Advance(count) == UBRK_DONE) - { - utf8Count = std::string_view::npos; - } - else - { - utf8Count = itr.CurrentOffset() - utf8Offset; - } - - return input.substr(utf8Offset, utf8Count); - } - - std::string UTF8TrimRightToColumnWidth(const NormalizedUTF8& input, size_t expectedWidth, size_t& actualWidth) - { - ICUBreakIterator itr{ input, UBRK_CHARACTER }; - - size_t columnWidth = 0; - UChar32 currentCP = 0; - int32_t currentBrk = 0; - int32_t nextBrk = 0; - - currentCP = itr.CurrentCodePoint(); - currentBrk = itr.CurrentBreak(); - nextBrk = itr.Next(); - while (nextBrk != UBRK_DONE && currentCP != U_SENTINEL) - { - int32_t width = u_getIntPropertyValue(currentCP, UCHAR_EAST_ASIAN_WIDTH); - int charWidth = width == U_EA_FULLWIDTH || width == U_EA_WIDE ? 2 : 1; - columnWidth += charWidth; - - if (columnWidth > expectedWidth) - { - columnWidth -= charWidth; - break; - } - - currentCP = itr.CurrentCodePoint(); - currentBrk = nextBrk; - nextBrk = itr.Next(); - } - - actualWidth = columnWidth; - - return input.substr(0, currentBrk); - } - - std::string Normalize(std::string_view input, NORM_FORM form) - { - if (input.empty()) - { - return {}; - } - - return ConvertToUTF8(Normalize(ConvertToUTF16(input), form)); - } - - std::wstring Normalize(std::wstring_view input, NORM_FORM form) - { - if (input.empty()) - { - return {}; - } - - std::wstring result; - - int cchEstimate = NormalizeString(form, input.data(), static_cast(input.length()), NULL, 0); - for (;;) - { - result.resize(cchEstimate); - cchEstimate = NormalizeString(form, input.data(), static_cast(input.length()), &result[0], cchEstimate); - - if (cchEstimate > 0) - { - result.resize(cchEstimate); - return result; - } - else - { - DWORD dwError = GetLastError(); - THROW_LAST_ERROR_IF(dwError != ERROR_INSUFFICIENT_BUFFER); - - // New guess is negative of the return value. - cchEstimate = -cchEstimate; - - THROW_HR_IF_MSG(E_UNEXPECTED, static_cast(cchEstimate) <= result.size(), "New estimate should never be less than previous value"); - } - } - } - - void ReplaceEmbeddedNullCharacters(std::string& s, char c) - { - for (size_t i = 0; i < s.length(); ++i) - { - if (s[i] == '\0') - { - s[i] = c; - } - } - } - - std::string ToLower(std::string_view in) - { - std::string result(in); - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return result; - } - - std::wstring ToLower(std::wstring_view in) - { - std::wstring result(in); - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned short c) { return std::towlower(c); }); - return result; - } - - std::string FoldCase(std::string_view input) - { - if (input.empty()) - { - return {}; - } - - wil::unique_any caseMap; - UErrorCode errorCode = UErrorCode::U_ZERO_ERROR; - caseMap.reset(ucasemap_open(nullptr, U_FOLD_CASE_DEFAULT, &errorCode)); - - if (U_FAILURE(errorCode)) - { - AICLI_LOG(Core, Error, << "ucasemap_open returned " << errorCode); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); - } - - int32_t cch = ucasemap_utf8FoldCase(caseMap.get(), nullptr, 0, input.data(), static_cast(input.size()), &errorCode); - if (errorCode != U_BUFFER_OVERFLOW_ERROR) - { - AICLI_LOG(Core, Error, << "ucasemap_utf8FoldCase returned " << errorCode); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); - } - - errorCode = UErrorCode::U_ZERO_ERROR; - - std::string result(cch, '\0'); - cch = ucasemap_utf8FoldCase(caseMap.get(), &result[0], cch, input.data(), static_cast(input.size()), &errorCode); - if (U_FAILURE(errorCode)) - { - AICLI_LOG(Core, Error, << "ucasemap_utf8FoldCase returned " << errorCode); - THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); - } - - while (result.back() == '\0') - { - result.pop_back(); - } - - return result; - } - - NormalizedString FoldCase(const NormalizedString& input) - { - NormalizedString result; - result.assign(FoldCase(static_cast(input))); - return result; - } - - bool IsEmptyOrWhitespace(std::string_view str) - { - if (str.empty()) - { - return true; - } - - return str.find_last_not_of(s_SpaceChars) == std::string_view::npos; - } - - bool IsEmptyOrWhitespace(std::wstring_view str) - { - if (str.empty()) - { - return true; - } - - return str.find_last_not_of(s_WideSpaceChars) == std::wstring_view::npos; - } - - bool FindAndReplace(std::string& inputStr, std::string_view token, std::string_view value) - { - bool result = false; - std::string::size_type pos = 0u; - while ((pos = inputStr.find(token, pos)) != std::string::npos) - { - result = true; - inputStr.replace(pos, token.length(), value); - pos += value.length(); - } - return result; - } - - std::wstring ReplaceWhileCopying(std::wstring_view input, std::wstring_view token, std::wstring_view value) - { - if (token.empty()) - { - return std::wstring{ input }; - } - - std::wstring result; - result.reserve(input.size()); - - std::wstring::size_type pos = 0u; - do - { - std::wstring::size_type findPos = input.find(token, pos); - - if (findPos == std::wstring::npos) - { - result.append(input.substr(pos)); - } - else - { - result.append(input.substr(pos, findPos - pos)); - result.append(value); - findPos += token.length(); - } - - pos = findPos; - } - while (pos != std::wstring::npos); - - return result; - } - - std::string& Trim(std::string& str) - { - return TrimTemplate(str, s_SpaceChars); - } - - std::string Trim(const std::string& str) - { - return TrimCopyTemplate(str); - } - - std::string Trim(std::string&& str) - { - return TrimMoveTemplate(std::move(str)); - } - - std::string_view& Trim(std::string_view& str) - { - return TrimTemplate(str, s_SpaceChars); - } - - std::string_view Trim(std::string_view&& str) - { - return TrimCopyTemplate(str); - } - - std::wstring& Trim(std::wstring& str) - { - return TrimTemplate(str, s_WideSpaceChars); - } - - std::wstring Trim(const std::wstring& str) - { - return TrimCopyTemplate(str); - } - - std::wstring Trim(std::wstring&& str) - { - return TrimMoveTemplate(std::move(str)); - } - - std::wstring_view& Trim(std::wstring_view& str) - { - return TrimTemplate(str, s_WideSpaceChars); - } - - std::wstring_view Trim(std::wstring_view&& str) - { - return TrimCopyTemplate(str); - } - - std::string ReadEntireStream(std::istream& stream) - { - std::streampos currentPos = stream.tellg(); - stream.seekg(0, std::ios_base::end); - - auto offset = stream.tellg() - currentPos; - stream.seekg(currentPos); - - // Don't allow use of this API for reading very large streams. - THROW_HR_IF(E_OUTOFMEMORY, offset > static_cast(std::numeric_limits::max())); - std::string result(static_cast(offset), '\0'); - stream.read(&result[0], offset); - - return result; - } - - std::vector ReadEntireStreamAsByteArray(std::istream& stream) - { - std::streampos currentPos = stream.tellg(); - stream.seekg(0, std::ios_base::end); - - auto offset = stream.tellg() - currentPos; - stream.seekg(currentPos); - - // Don't allow use of this API for reading very large streams. - THROW_HR_IF(E_OUTOFMEMORY, offset > static_cast(std::numeric_limits::max())); - std::vector result; - result.resize(static_cast(offset)); - stream.read(reinterpret_cast(result.data()), offset); - - return result; - } - - std::wstring ExpandEnvironmentVariables(const std::wstring& input) - { - if (input.empty()) - { - return {}; - } - - DWORD charCount = ExpandEnvironmentStringsW(input.c_str(), nullptr, 0); - THROW_LAST_ERROR_IF(charCount == 0); - - std::wstring result(wil::safe_cast(charCount), L'\0'); - - DWORD charCountWritten = ExpandEnvironmentStringsW(input.c_str(), &result[0], charCount); - THROW_HR_IF(E_UNEXPECTED, charCount != charCountWritten); - - if (result.back() == L'\0') - { - result.resize(result.size() - 1); - } - - return result; - } - - // Follow the rules at https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file to replace - // invalid characters in a candidate path part. - // Additionally, based on https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits - // limit the number of characters to 255. - std::string MakeSuitablePathPart(std::string_view candidate) - { - constexpr char replaceChar = '_'; - constexpr std::string_view illegalChars = R"(<>:"/\|?*)"; - constexpr size_t pathLengthLimit = 255; - - // First, walk the string and replace illegal characters - std::string result; - result.reserve(candidate.size()); - - ICUBreakIterator itr{ candidate, UBRK_CHARACTER }; - size_t resultBreakCount = 0; - - while (itr.CurrentBreak() != UBRK_DONE && itr.CurrentOffset() < candidate.size() && resultBreakCount <= pathLengthLimit) - { - UChar32 current = itr.CurrentCodePoint(); - bool isIllegal = current < 32 || (current < 256 && illegalChars.find(static_cast(current)) != std::string::npos); - - int32_t offset = itr.CurrentBreak(); - int32_t nextOffset = itr.Next(); - - // Don't allow a . at the end of a name - if (static_cast(nextOffset) >= candidate.size()) - { - if (current == static_cast('.')) - { - isIllegal = true; - } - } - - if (isIllegal) - { - result.append(1, replaceChar); - } - else - { - size_t count = (nextOffset == UBRK_DONE ? std::string::npos : static_cast(nextOffset) - static_cast(offset)); - result.append(candidate.substr(static_cast(offset), count)); - } - - ++resultBreakCount; - } - - // If there are too many characters for a single path; switch to a hash. - // This should basically never happen, but if it does it will prevent collisions better. - if (resultBreakCount > pathLengthLimit) - { - return SHA256::ConvertToString(SHA256::ComputeHash(candidate)); - } - - // Second, look for any newly formed illegal names. - // For now just error on these cases; they should not happen often. - for (const auto& illegalName : { - "."sv, "CON"sv, "PRN"sv, "AUX"sv, "NUL"sv, "COM1"sv, "COM2"sv, "COM3"sv, "COM4"sv, "COM5"sv, "COM6"sv, "COM7"sv, "COM8"sv, "COM9"sv, - "LPT1"sv, "LPT2"sv, "LPT3"sv, "LPT4"sv, "LPT5"sv, "LPT6"sv, "LPT7"sv, "LPT8"sv, "LPT9"sv }) - { - // Either equals the illegal name (starts with and same length) or starts with and the first character after is a . - if (CaseInsensitiveStartsWith(result, illegalName) && (result.size() == illegalName.size() || result[illegalName.size()] == '.')) - { - THROW_HR(E_INVALIDARG); - } - } - - return result; - } - - std::pair SplitFileNameFromURI(std::string_view uri) - { - std::filesystem::path filename = GetFileNameFromURI(uri); - return { std::string{ uri.substr(0, uri.size() - filename.u8string().size()) }, filename }; - } - - std::filesystem::path GetFileNameFromURI(std::string_view uri) - { - winrt::Windows::Foundation::Uri winrtUri{ winrt::hstring{ ConvertToUTF16(uri) } }; - std::filesystem::path path{ static_cast(winrtUri.Path()) }; - - return path.filename(); - } - - std::vector SplitIntoWords(std::string_view input) - { - ICUBreakIterator itr{ input, UBRK_WORD }; - std::size_t currentOffset = 0; - - std::vector result; - while (itr.Next() != UBRK_DONE) - { - std::size_t nextOffset = itr.CurrentOffset(); - - // Ignore spaces and punctuation, accept words and numbers - if (itr.CurrentRuleStatus() != UBRK_WORD_NONE) - { - auto wordSize = nextOffset - currentOffset; - result.emplace_back(input, currentOffset, wordSize); - } - - currentOffset = nextOffset; - } - - return result; - } - - std::vector SplitIntoLines(std::string_view input, size_t maximum) - { - std::size_t currentOffset = 0; - std::vector result; - - while (currentOffset < input.size() && (!maximum || result.size() < maximum)) - { - std::size_t nextOffset = input.find_first_of("\r\n", currentOffset); - if (nextOffset == std::string_view::npos) - { - nextOffset = input.size(); - } - - if (nextOffset - currentOffset > 1) - { - result.emplace_back(input.substr(currentOffset, nextOffset - currentOffset)); - } - - currentOffset = nextOffset + 1; - } - - return result; - } - - bool LimitOutputLines(std::vector& lines, size_t lineWidth, size_t maximum) - { - size_t totalLines = 0; - size_t currentLine = 0; - bool result = false; - - for (; currentLine < lines.size() && totalLines < maximum; ++currentLine) - { - size_t currentLineWidth = UTF8ColumnWidth(lines[currentLine]); - // If current line is empty, the cost is 1 line (0 + 1). - // If not, round up to the next line count (by rounding down through integer division after subtracting 1 + 1). - size_t currentLineActualLineCount = (currentLineWidth ? (currentLineWidth - 1) / lineWidth : 0) + 1; - - // The current line may be too big to be the last line, or it may be just the right size but we will end up trimming - // additional lines. In either case, append an ellipsis to indicate that we trimmed the value. - size_t availableLines = maximum - totalLines; - if (currentLineActualLineCount > availableLines || - (currentLineActualLineCount == availableLines && currentLine != lines.size() - 1)) - { - size_t actualWidth = 0; - std::string trimmedLine = UTF8TrimRightToColumnWidth(lines[currentLine], (availableLines * lineWidth) - 1, actualWidth); - trimmedLine += "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (�) character - lines[currentLine] = trimmedLine; - - currentLineActualLineCount = availableLines; - result = true; - } - - totalLines += currentLineActualLineCount; - } - - // Drop any unprocessed lines - if (currentLine != lines.size()) - { - lines.resize(currentLine); - result = true; - } - - return result; - } - - std::string ConvertToHexString(const std::vector& buffer, size_t byteCount) - { - if (byteCount && buffer.size() != byteCount) - { - THROW_HR_MSG(E_INVALIDARG, "ConvertToHexString: Invalid buffer size"); - } - - std::string result(2 * buffer.size(), '\0'); - static constexpr std::array hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; - - for (size_t i = 0; i < buffer.size(); ++i) - { - result[2 * i] = hexChars[(buffer[i] >> 4) & 0xF]; - result[2 * i + 1] = hexChars[buffer[i] & 0xF]; - } - - return result; - } - - std::vector ParseFromHexString(const std::string& value, size_t byteCount) - { - if ((byteCount && value.size() != (2 * byteCount)) || - (value.size() % 2)) - { - THROW_HR_MSG(E_INVALIDARG, "ParseFromHexString: Invalid value size"); - } - - const char* valuePtr = value.c_str(); - std::vector result; - result.resize(value.size() / 2); - - for (size_t i = 0; i < result.size(); i++) - { - sscanf_s(valuePtr + 2 * i, "%02hhx", &result[i]); - } - - return result; - } - - template - static std::string JoinInternal(std::string_view separator, const std::vector& vector) - { - auto vectorSize = vector.size(); - if (vectorSize == 0) - { - return {}; - } - - std::ostringstream ssJoin; - ssJoin << vector[0]; - for (size_t i = 1; i < vectorSize; ++i) - { - ssJoin << separator << vector[i]; - } - return ssJoin.str(); - } - - LocIndString Join(LocIndView separator, const std::vector& vector) - { - return LocIndString{ JoinInternal(separator, vector) }; - } - - std::string Join(std::string_view separator, const std::vector& vector) - { - return JoinInternal(separator, vector); - } - - std::vector Split(const std::string& input, char separator, bool trim) - { - return SplitTemplate(input, separator, trim); - } - - std::vector SplitView(const std::string& input, char separator, bool trim) - { - return SplitTemplate(static_cast(input), separator, trim); - } - - std::vector Split(std::string_view input, char separator, bool trim) - { - return SplitTemplate(input, separator, trim); - } - - std::vector Split(const std::wstring& input, wchar_t separator, bool trim) - { - return SplitTemplate(input, separator, trim); - } - - std::vector SplitView(const std::wstring& input, wchar_t separator, bool trim) - { - return SplitTemplate(static_cast(input), separator, trim); - } - - std::vector Split(std::wstring_view input, wchar_t separator, bool trim) - { - return SplitTemplate(input, separator, trim); - } - - std::string_view ConvertBoolToString(bool value) - { - return value ? "true"sv : "false"sv; - } - - std::optional TryConvertStringToBool(const std::string_view& input) - { - try - { - if (CaseInsensitiveEquals(input, "false"sv)) - { - return { false }; - } - - if (CaseInsensitiveEquals(input, "true"sv)) - { - return { true }; - } - - return {}; - } - catch (...) - { - return {}; - } - } - - std::optional TryConvertStringToInt32(const std::string_view& input) - { - int32_t result = 0; - auto parseResult = std::from_chars(input.data(), input.data() + input.length(), result); - - std::optional optionalResult; - if (parseResult.ec == std::errc{}) - { - optionalResult = result; - } - - return optionalResult; - } - - std::string ConvertGuidToString(const GUID& value) - { - wchar_t buffer[40]; - THROW_HR_IF(E_UNEXPECTED, !StringFromGUID2(value, buffer, ARRAYSIZE(buffer))); - return ConvertToUTF8(buffer); - } - - std::wstring CreateNewGuidNameWString() - { - GUID guid; - THROW_IF_FAILED(CoCreateGuid(&guid)); - - wchar_t buffer[40]; - THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(guid, buffer, ARRAYSIZE(buffer)) != 39); - - return std::wstring{ &buffer[1], 36 }; - } - - bool IsDwordFlagSet(const std::string& value) - { - if (std::empty(value)) - { - return false; - } - - try - { - DWORD dwordValue = std::stoul(value); - - // If the value is 0, then it is not set. - return dwordValue != 0; - } - catch (...) - { - return false; - } - } - - size_t FindControlCodeToConvert(std::string_view input, size_t offset) - { - size_t nextControl = offset; - while (nextControl < input.size()) - { - char currentChar = input[nextControl]; - - // Convert all low controls except tab, line feed and carriage return - if (currentChar >= 0 && currentChar < 0x20 && - currentChar != '\t' && - currentChar != '\n' && - currentChar != '\r') - { - break; - } - - // Convert the Delete control - if (currentChar == 0x7F) - { - break; - } - - ++nextControl; - } - - return nextControl < input.size() ? nextControl : std::string::npos; - } - - std::string ConvertControlCodesToPictures(std::string_view input) - { - std::string result; - size_t pos = 0; - - while (pos < input.size()) - { - size_t nextControl = FindControlCodeToConvert(input, pos); - - if (nextControl == std::string::npos) - { - // No more control codes found - result += input.substr(pos); - break; - } - else - { - result += input.substr(pos, nextControl - pos); - - char currentChar = input[nextControl]; - - if (currentChar >= 0 && currentChar < 0x20) - { - // ASCII 0x00 - 0x1F => UTF-8 0x2400 - 0x241F - // Then manually converted to UTF-8 since only the last character is affected - result += '\xE2'; - result += '\x90'; - result += ('\x80' + currentChar); - } - else if (currentChar == 0x7F) - { - // UTF-8 for control picture of DELETE - result += "\xE2\x90\xA1"; - } - - pos = nextControl + 1; - } - } - - return result; - } - - std::string GetRandomString(size_t size) - { - static constexpr char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; - static std::default_random_engine randomEngine(std::random_device{}()); - static std::uniform_int_distribution distribution(0, 35); - - std::string result; - result.resize(size); - - for (size_t i = 0; i < size; i++) - { - result[i] = chars[distribution(randomEngine)]; - } - - return result; - } - - bool IsValidWindowsFeaturePattern(std::string_view value) - { - if (value.empty()) - { - return false; - } - - for (char c : value) - { - if (!std::isalnum(static_cast(c)) && c != '-' && c != '_') - { - return false; - } - } - - return true; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerErrors.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerSHA256.h" + +namespace AppInstaller::Utility +{ + // Same as std::isspace(char) +#define AICLI_SPACE_CHARS " \f\n\r\t\v"sv + + using namespace std::string_view_literals; + constexpr std::string_view s_SpaceChars = AICLI_SPACE_CHARS; + constexpr std::wstring_view s_WideSpaceChars = L"" AICLI_SPACE_CHARS; + + namespace + { + // Contains the ICU objects necessary to do break iteration. + struct ICUBreakIterator + { + ICUBreakIterator(std::string_view input, UBreakIteratorType type) + { + UErrorCode err = U_ZERO_ERROR; + + m_text.reset(utext_openUTF8(nullptr, input.data(), wil::safe_cast(input.length()), &err)); + if (U_FAILURE(err)) + { + AICLI_LOG(Core, Error, << "utext_openUTF8 returned " << err); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); + } + + m_brk.reset(ubrk_open(type, nullptr, nullptr, 0, &err)); + if (U_FAILURE(err)) + { + AICLI_LOG(Core, Error, << "ubrk_open returned " << err); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); + } + + ubrk_setUText(m_brk.get(), m_text.get(), &err); + if (U_FAILURE(err)) + { + AICLI_LOG(Core, Error, << "ubrk_setUText returned " << err); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); + } + + int32_t i = ubrk_first(m_brk.get()); + if (i != 0) + { + AICLI_LOG(Core, Error, << "ubrk_first returned " << i); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR); + } + } + + // Gets the current break value; the byte offset or UBRK_DONE. + int32_t CurrentBreak() const { return m_currentBrk; } + + // Gets the current byte offset, throwing if the value is UBRK_DONE or negative. + size_t CurrentOffset() const + { + THROW_HR_IF(E_NOT_VALID_STATE, m_currentBrk < 0); + return static_cast(m_currentBrk); + } + + // Returns the byte offset of the next break in the string + int32_t Next() + { + m_currentBrk = ubrk_next(m_brk.get()); + return m_currentBrk; + } + + // Returns the byte offset of the next count'th break in the string + int32_t Advance(size_t count) + { + for (size_t i = 0; i < count && m_currentBrk != UBRK_DONE; ++i) + { + Next(); + } + return m_currentBrk; + } + + // Returns code point of the character at m_currentBrk, or U_SENTINEL if m_currentBrk points to the end. + UChar32 CurrentCodePoint() + { + return utext_char32At(m_text.get(), m_currentBrk); + } + + // Returns the status from the break rule that determined the most recently break position. + int32_t CurrentRuleStatus() + { + return ubrk_getRuleStatus(m_brk.get()); + } + + private: + wil::unique_any m_text; + wil::unique_any m_brk; + int32_t m_currentBrk = 0; + }; + + template + StringType& TrimTemplate(StringType& input, std::basic_string_view spaceChars) + { + if (!input.empty()) + { + size_t begin = input.find_first_not_of(spaceChars); + size_t end = input.find_last_not_of(spaceChars); + + if (begin == StringType::npos || end == StringType::npos) + { + input = {}; + } + else if (begin != 0 || end != input.length() - 1) + { + input = input.substr(begin, (end - begin) + 1); + } + } + + return input; + } + + template + StringType TrimCopyTemplate(const StringType& input) + { + StringType result = input; + Utility::Trim(result); + return result; + } + + template + StringType TrimMoveTemplate(StringType&& input) + { + StringType result = std::move(input); + Utility::Trim(result); + return result; + } + + template + std::vector SplitTemplate(const StringType& input, typename StringType::value_type separator, bool trim) + { + std::vector result; + size_t startIndex = 0; + size_t endIndex = 0; + + while ((endIndex = input.find(separator, startIndex)) != StringType::npos) + { + StringType substring = input.substr(startIndex, endIndex - startIndex); + + if (trim) + { + Utility::Trim(substring); + } + + result.emplace_back(std::move(substring)); + startIndex = endIndex + 1; + } + + result.emplace_back(trim ? Utility::Trim(input.substr(startIndex)) : input.substr(startIndex)); + return result; + } + } + + bool CaseInsensitiveEquals(std::string_view a, std::string_view b) + { + return ToLower(a) == ToLower(b); + } + + bool CaseInsensitiveEquals(std::wstring_view a, std::wstring_view b) + { + return ToLower(a) == ToLower(b); + } + + bool CaseInsensitiveContains(const std::vector& a, std::string_view b) + { + auto B = ToLower(b); + return std::any_of(a.begin(), a.end(), [&](const std::string_view& s) { return ToLower(s) == B; }); + } + + bool StartsWith(std::wstring_view a, std::wstring_view b) + { + return a.length() >= b.length() && a.substr(0, b.length()) == b; + } + + bool CaseInsensitiveStartsWith(std::string_view a, std::string_view b) + { + return a.length() >= b.length() && CaseInsensitiveEquals(a.substr(0, b.length()), b); + } + + bool CaseInsensitiveStartsWith(std::wstring_view a, std::wstring_view b) + { + return a.length() >= b.length() && CaseInsensitiveEquals(a.substr(0, b.length()), b); + } + + bool CaseInsensitiveContainsSubstring(std::string_view a, std::string_view b) + { + auto it = std::search( + a.begin(), a.end(), + b.begin(), b.end(), + [](char ch1, char ch2) { return std::tolower(ch1) == std::tolower(ch2); } + ); + return (it != a.end()); + } + + bool ContainsSubstring(std::string_view a, std::string_view b) + { + auto it = std::search( + a.begin(), a.end(), + b.begin(), b.end() + ); + return (it != a.end()); + } + + bool ICUCaseInsensitiveEquals(std::string_view a, std::string_view b) + { + return FoldCase(a) == FoldCase(b); + } + + bool ICUCaseInsensitiveStartsWith(std::string_view a, std::string_view b) + { + return a.length() >= b.length() && ICUCaseInsensitiveEquals(a.substr(0, b.length()), b); + } + + std::string ConvertToUTF8(std::wstring_view input) + { + if (input.empty()) + { + return {}; + } + + int utf8ByteCount = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0, nullptr, nullptr); + THROW_LAST_ERROR_IF(utf8ByteCount == 0); + + // Since the string view should not contain the null char, the result won't either. + // This allows us to use the resulting size value directly in the string constructor. + std::string result(wil::safe_cast(utf8ByteCount), '\0'); + + int utf8BytesWritten = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size()), nullptr, nullptr); + FAIL_FAST_HR_IF(E_UNEXPECTED, utf8ByteCount != utf8BytesWritten); + + return result; + } + + std::wstring ConvertToUTF16(std::string_view input, UINT codePage) + { + if (input.empty()) + { + return {}; + } + + int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0); + THROW_LAST_ERROR_IF(utf16CharCount == 0); + + // Since the string view should not contain the null char, the result won't either. + // This allows us to use the resulting size value directly in the string constructor. + std::wstring result(wil::safe_cast(utf16CharCount), L'\0'); + + int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size())); + FAIL_FAST_HR_IF(E_UNEXPECTED, utf16CharCount != utf16CharsWritten); + + return result; + } + + std::optional TryConvertToUTF16(std::string_view input, UINT codePage) + { + if (input.empty()) + { + return std::wstring{}; + } + + int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0); + if (utf16CharCount == 0) + { + return {}; + } + + // Since the string view should not contain the null char, the result won't either. + // This allows us to use the resulting size value directly in the string constructor. + std::wstring result(wil::safe_cast(utf16CharCount), L'\0'); + + int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size())); + if (utf16CharCount != utf16CharsWritten) + { + return {}; + } + + return std::optional{ result }; + } + + std::u32string ConvertToUTF32(std::string_view input) + { + if (input.empty()) + { + return {}; + } + + UErrorCode errorCode = UErrorCode::U_ZERO_ERROR; + auto utf32ByteCount= ucnv_convert("UTF-32", "UTF-8", nullptr, 0, input.data(), static_cast(input.size()), &errorCode); + + if (errorCode != U_BUFFER_OVERFLOW_ERROR) + { + AICLI_LOG(Core, Error, << "ucnv_convert returned " << errorCode); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR); + } + + FAIL_FAST_HR_IF(E_UNEXPECTED, utf32ByteCount % sizeof(char32_t) != 0); + auto utf32CharCount = utf32ByteCount / sizeof(char32_t); + std::u32string result(utf32CharCount, U'\0'); + + errorCode = UErrorCode::U_ZERO_ERROR; + + auto utf32BytesWritten = ucnv_convert("UTF-32", "UTF-8", (char*)(result.data()), utf32ByteCount, input.data(), static_cast(input.size()), &errorCode); + + // The size we pass to ucnv_convert is not enough for it to put in the null terminator, + // which wouldn't work anyways as it puts a single byte. + if (errorCode != U_STRING_NOT_TERMINATED_WARNING) + { + AICLI_LOG(Core, Error, << "ucnv_convert returned " << errorCode); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR); + } + + FAIL_FAST_HR_IF(E_UNEXPECTED, utf32ByteCount != utf32BytesWritten); + + return result; + } + + size_t UTF8Length(std::string_view input) + { + ICUBreakIterator itr{ input, UBRK_CHARACTER }; + + size_t numGraphemeClusters = 0; + + while (itr.Next() != UBRK_DONE) + { + numGraphemeClusters++; + } + + return numGraphemeClusters; + } + + size_t UTF8ColumnWidth(const NormalizedUTF8& input) + { + ICUBreakIterator itr{ input, UBRK_CHARACTER }; + + size_t columnWidth = 0; + UChar32 currentCP = 0; + + currentCP = itr.CurrentCodePoint(); + while (itr.Next() != UBRK_DONE && currentCP != U_SENTINEL) + { + int32_t width = u_getIntPropertyValue(currentCP, UCHAR_EAST_ASIAN_WIDTH); + columnWidth += width == U_EA_FULLWIDTH || width == U_EA_WIDE ? 2 : 1; + + currentCP = itr.CurrentCodePoint(); + } + + return columnWidth; + } + + std::string_view UTF8Substring(std::string_view input, size_t offset, size_t count) + { + ICUBreakIterator itr{ input, UBRK_CHARACTER }; + + // Offset was past end, throw just like std::string::substr + if (itr.Advance(offset) == UBRK_DONE) + { + throw std::out_of_range("UTF8Substring: offset past end of input"); + } + + size_t utf8Offset = itr.CurrentOffset(); + size_t utf8Count = 0; + + // Count past end, convert to npos to get all of string + if (itr.Advance(count) == UBRK_DONE) + { + utf8Count = std::string_view::npos; + } + else + { + utf8Count = itr.CurrentOffset() - utf8Offset; + } + + return input.substr(utf8Offset, utf8Count); + } + + std::string UTF8TrimRightToColumnWidth(const NormalizedUTF8& input, size_t expectedWidth, size_t& actualWidth) + { + ICUBreakIterator itr{ input, UBRK_CHARACTER }; + + size_t columnWidth = 0; + UChar32 currentCP = 0; + int32_t currentBrk = 0; + int32_t nextBrk = 0; + + currentCP = itr.CurrentCodePoint(); + currentBrk = itr.CurrentBreak(); + nextBrk = itr.Next(); + while (nextBrk != UBRK_DONE && currentCP != U_SENTINEL) + { + int32_t width = u_getIntPropertyValue(currentCP, UCHAR_EAST_ASIAN_WIDTH); + int charWidth = width == U_EA_FULLWIDTH || width == U_EA_WIDE ? 2 : 1; + columnWidth += charWidth; + + if (columnWidth > expectedWidth) + { + columnWidth -= charWidth; + break; + } + + currentCP = itr.CurrentCodePoint(); + currentBrk = nextBrk; + nextBrk = itr.Next(); + } + + actualWidth = columnWidth; + + return input.substr(0, currentBrk); + } + + std::string Normalize(std::string_view input, NORM_FORM form) + { + if (input.empty()) + { + return {}; + } + + return ConvertToUTF8(Normalize(ConvertToUTF16(input), form)); + } + + std::wstring Normalize(std::wstring_view input, NORM_FORM form) + { + if (input.empty()) + { + return {}; + } + + std::wstring result; + + int cchEstimate = NormalizeString(form, input.data(), static_cast(input.length()), NULL, 0); + for (;;) + { + result.resize(cchEstimate); + cchEstimate = NormalizeString(form, input.data(), static_cast(input.length()), &result[0], cchEstimate); + + if (cchEstimate > 0) + { + result.resize(cchEstimate); + return result; + } + else + { + DWORD dwError = GetLastError(); + THROW_LAST_ERROR_IF(dwError != ERROR_INSUFFICIENT_BUFFER); + + // New guess is negative of the return value. + cchEstimate = -cchEstimate; + + THROW_HR_IF_MSG(E_UNEXPECTED, static_cast(cchEstimate) <= result.size(), "New estimate should never be less than previous value"); + } + } + } + + void ReplaceEmbeddedNullCharacters(std::string& s, char c) + { + for (size_t i = 0; i < s.length(); ++i) + { + if (s[i] == '\0') + { + s[i] = c; + } + } + } + + std::string ToLower(std::string_view in) + { + std::string result(in); + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return result; + } + + std::wstring ToLower(std::wstring_view in) + { + std::wstring result(in); + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned short c) { return std::towlower(c); }); + return result; + } + + std::string FoldCase(std::string_view input) + { + if (input.empty()) + { + return {}; + } + + wil::unique_any caseMap; + UErrorCode errorCode = UErrorCode::U_ZERO_ERROR; + caseMap.reset(ucasemap_open(nullptr, U_FOLD_CASE_DEFAULT, &errorCode)); + + if (U_FAILURE(errorCode)) + { + AICLI_LOG(Core, Error, << "ucasemap_open returned " << errorCode); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); + } + + int32_t cch = ucasemap_utf8FoldCase(caseMap.get(), nullptr, 0, input.data(), static_cast(input.size()), &errorCode); + if (errorCode != U_BUFFER_OVERFLOW_ERROR) + { + AICLI_LOG(Core, Error, << "ucasemap_utf8FoldCase returned " << errorCode); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); + } + + errorCode = UErrorCode::U_ZERO_ERROR; + + std::string result(cch, '\0'); + cch = ucasemap_utf8FoldCase(caseMap.get(), &result[0], cch, input.data(), static_cast(input.size()), &errorCode); + if (U_FAILURE(errorCode)) + { + AICLI_LOG(Core, Error, << "ucasemap_utf8FoldCase returned " << errorCode); + THROW_HR(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR); + } + + while (result.back() == '\0') + { + result.pop_back(); + } + + return result; + } + + NormalizedString FoldCase(const NormalizedString& input) + { + NormalizedString result; + result.assign(FoldCase(static_cast(input))); + return result; + } + + bool IsEmptyOrWhitespace(std::string_view str) + { + if (str.empty()) + { + return true; + } + + return str.find_last_not_of(s_SpaceChars) == std::string_view::npos; + } + + bool IsEmptyOrWhitespace(std::wstring_view str) + { + if (str.empty()) + { + return true; + } + + return str.find_last_not_of(s_WideSpaceChars) == std::wstring_view::npos; + } + + bool FindAndReplace(std::string& inputStr, std::string_view token, std::string_view value) + { + bool result = false; + std::string::size_type pos = 0u; + while ((pos = inputStr.find(token, pos)) != std::string::npos) + { + result = true; + inputStr.replace(pos, token.length(), value); + pos += value.length(); + } + return result; + } + + std::wstring ReplaceWhileCopying(std::wstring_view input, std::wstring_view token, std::wstring_view value) + { + if (token.empty()) + { + return std::wstring{ input }; + } + + std::wstring result; + result.reserve(input.size()); + + std::wstring::size_type pos = 0u; + do + { + std::wstring::size_type findPos = input.find(token, pos); + + if (findPos == std::wstring::npos) + { + result.append(input.substr(pos)); + } + else + { + result.append(input.substr(pos, findPos - pos)); + result.append(value); + findPos += token.length(); + } + + pos = findPos; + } + while (pos != std::wstring::npos); + + return result; + } + + std::string& Trim(std::string& str) + { + return TrimTemplate(str, s_SpaceChars); + } + + std::string Trim(const std::string& str) + { + return TrimCopyTemplate(str); + } + + std::string Trim(std::string&& str) + { + return TrimMoveTemplate(std::move(str)); + } + + std::string_view& Trim(std::string_view& str) + { + return TrimTemplate(str, s_SpaceChars); + } + + std::string_view Trim(std::string_view&& str) + { + return TrimCopyTemplate(str); + } + + std::wstring& Trim(std::wstring& str) + { + return TrimTemplate(str, s_WideSpaceChars); + } + + std::wstring Trim(const std::wstring& str) + { + return TrimCopyTemplate(str); + } + + std::wstring Trim(std::wstring&& str) + { + return TrimMoveTemplate(std::move(str)); + } + + std::wstring_view& Trim(std::wstring_view& str) + { + return TrimTemplate(str, s_WideSpaceChars); + } + + std::wstring_view Trim(std::wstring_view&& str) + { + return TrimCopyTemplate(str); + } + + std::string ReadEntireStream(std::istream& stream) + { + std::streampos currentPos = stream.tellg(); + stream.seekg(0, std::ios_base::end); + + auto offset = stream.tellg() - currentPos; + stream.seekg(currentPos); + + // Don't allow use of this API for reading very large streams. + THROW_HR_IF(E_OUTOFMEMORY, offset > static_cast(std::numeric_limits::max())); + std::string result(static_cast(offset), '\0'); + stream.read(&result[0], offset); + + return result; + } + + std::vector ReadEntireStreamAsByteArray(std::istream& stream) + { + std::streampos currentPos = stream.tellg(); + stream.seekg(0, std::ios_base::end); + + auto offset = stream.tellg() - currentPos; + stream.seekg(currentPos); + + // Don't allow use of this API for reading very large streams. + THROW_HR_IF(E_OUTOFMEMORY, offset > static_cast(std::numeric_limits::max())); + std::vector result; + result.resize(static_cast(offset)); + stream.read(reinterpret_cast(result.data()), offset); + + return result; + } + + std::wstring ExpandEnvironmentVariables(const std::wstring& input) + { + if (input.empty()) + { + return {}; + } + + DWORD charCount = ExpandEnvironmentStringsW(input.c_str(), nullptr, 0); + THROW_LAST_ERROR_IF(charCount == 0); + + std::wstring result(wil::safe_cast(charCount), L'\0'); + + DWORD charCountWritten = ExpandEnvironmentStringsW(input.c_str(), &result[0], charCount); + THROW_HR_IF(E_UNEXPECTED, charCount != charCountWritten); + + if (result.back() == L'\0') + { + result.resize(result.size() - 1); + } + + return result; + } + + // Follow the rules at https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file to replace + // invalid characters in a candidate path part. + // Additionally, based on https://docs.microsoft.com/en-us/windows/win32/fileio/filesystem-functionality-comparison#limits + // limit the number of characters to 255. + std::string MakeSuitablePathPart(std::string_view candidate) + { + constexpr char replaceChar = '_'; + constexpr std::string_view illegalChars = R"(<>:"/\|?*)"; + constexpr size_t pathLengthLimit = 255; + + // First, walk the string and replace illegal characters + std::string result; + result.reserve(candidate.size()); + + ICUBreakIterator itr{ candidate, UBRK_CHARACTER }; + size_t resultBreakCount = 0; + + while (itr.CurrentBreak() != UBRK_DONE && itr.CurrentOffset() < candidate.size() && resultBreakCount <= pathLengthLimit) + { + UChar32 current = itr.CurrentCodePoint(); + bool isIllegal = current < 32 || (current < 256 && illegalChars.find(static_cast(current)) != std::string::npos); + + int32_t offset = itr.CurrentBreak(); + int32_t nextOffset = itr.Next(); + + // Don't allow a . at the end of a name + if (static_cast(nextOffset) >= candidate.size()) + { + if (current == static_cast('.')) + { + isIllegal = true; + } + } + + if (isIllegal) + { + result.append(1, replaceChar); + } + else + { + size_t count = (nextOffset == UBRK_DONE ? std::string::npos : static_cast(nextOffset) - static_cast(offset)); + result.append(candidate.substr(static_cast(offset), count)); + } + + ++resultBreakCount; + } + + // If there are too many characters for a single path; switch to a hash. + // This should basically never happen, but if it does it will prevent collisions better. + if (resultBreakCount > pathLengthLimit) + { + return SHA256::ConvertToString(SHA256::ComputeHash(candidate)); + } + + // Second, look for any newly formed illegal names. + // For now just error on these cases; they should not happen often. + for (const auto& illegalName : { + "."sv, "CON"sv, "PRN"sv, "AUX"sv, "NUL"sv, "COM1"sv, "COM2"sv, "COM3"sv, "COM4"sv, "COM5"sv, "COM6"sv, "COM7"sv, "COM8"sv, "COM9"sv, + "LPT1"sv, "LPT2"sv, "LPT3"sv, "LPT4"sv, "LPT5"sv, "LPT6"sv, "LPT7"sv, "LPT8"sv, "LPT9"sv }) + { + // Either equals the illegal name (starts with and same length) or starts with and the first character after is a . + if (CaseInsensitiveStartsWith(result, illegalName) && (result.size() == illegalName.size() || result[illegalName.size()] == '.')) + { + THROW_HR(E_INVALIDARG); + } + } + + return result; + } + + std::pair SplitFileNameFromURI(std::string_view uri) + { + std::filesystem::path filename = GetFileNameFromURI(uri); + return { std::string{ uri.substr(0, uri.size() - filename.u8string().size()) }, filename }; + } + + std::filesystem::path GetFileNameFromURI(std::string_view uri) + { + winrt::Windows::Foundation::Uri winrtUri{ winrt::hstring{ ConvertToUTF16(uri) } }; + std::filesystem::path path{ static_cast(winrtUri.Path()) }; + + return path.filename(); + } + + std::vector SplitIntoWords(std::string_view input) + { + ICUBreakIterator itr{ input, UBRK_WORD }; + std::size_t currentOffset = 0; + + std::vector result; + while (itr.Next() != UBRK_DONE) + { + std::size_t nextOffset = itr.CurrentOffset(); + + // Ignore spaces and punctuation, accept words and numbers + if (itr.CurrentRuleStatus() != UBRK_WORD_NONE) + { + auto wordSize = nextOffset - currentOffset; + result.emplace_back(input, currentOffset, wordSize); + } + + currentOffset = nextOffset; + } + + return result; + } + + std::vector SplitIntoLines(std::string_view input, size_t maximum) + { + std::size_t currentOffset = 0; + std::vector result; + + while (currentOffset < input.size() && (!maximum || result.size() < maximum)) + { + std::size_t nextOffset = input.find_first_of("\r\n", currentOffset); + if (nextOffset == std::string_view::npos) + { + nextOffset = input.size(); + } + + if (nextOffset - currentOffset > 1) + { + result.emplace_back(input.substr(currentOffset, nextOffset - currentOffset)); + } + + currentOffset = nextOffset + 1; + } + + return result; + } + + bool LimitOutputLines(std::vector& lines, size_t lineWidth, size_t maximum) + { + size_t totalLines = 0; + size_t currentLine = 0; + bool result = false; + + for (; currentLine < lines.size() && totalLines < maximum; ++currentLine) + { + size_t currentLineWidth = UTF8ColumnWidth(lines[currentLine]); + // If current line is empty, the cost is 1 line (0 + 1). + // If not, round up to the next line count (by rounding down through integer division after subtracting 1 + 1). + size_t currentLineActualLineCount = (currentLineWidth ? (currentLineWidth - 1) / lineWidth : 0) + 1; + + // The current line may be too big to be the last line, or it may be just the right size but we will end up trimming + // additional lines. In either case, append an ellipsis to indicate that we trimmed the value. + size_t availableLines = maximum - totalLines; + if (currentLineActualLineCount > availableLines || + (currentLineActualLineCount == availableLines && currentLine != lines.size() - 1)) + { + size_t actualWidth = 0; + std::string trimmedLine = UTF8TrimRightToColumnWidth(lines[currentLine], (availableLines * lineWidth) - 1, actualWidth); + trimmedLine += "\xE2\x80\xA6"; // UTF8 encoding of ellipsis (�) character + lines[currentLine] = trimmedLine; + + currentLineActualLineCount = availableLines; + result = true; + } + + totalLines += currentLineActualLineCount; + } + + // Drop any unprocessed lines + if (currentLine != lines.size()) + { + lines.resize(currentLine); + result = true; + } + + return result; + } + + std::string ConvertToHexString(const std::vector& buffer, size_t byteCount) + { + if (byteCount && buffer.size() != byteCount) + { + THROW_HR_MSG(E_INVALIDARG, "ConvertToHexString: Invalid buffer size"); + } + + std::string result(2 * buffer.size(), '\0'); + static constexpr std::array hexChars = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; + + for (size_t i = 0; i < buffer.size(); ++i) + { + result[2 * i] = hexChars[(buffer[i] >> 4) & 0xF]; + result[2 * i + 1] = hexChars[buffer[i] & 0xF]; + } + + return result; + } + + std::vector ParseFromHexString(const std::string& value, size_t byteCount) + { + if ((byteCount && value.size() != (2 * byteCount)) || + (value.size() % 2)) + { + THROW_HR_MSG(E_INVALIDARG, "ParseFromHexString: Invalid value size"); + } + + const char* valuePtr = value.c_str(); + std::vector result; + result.resize(value.size() / 2); + + for (size_t i = 0; i < result.size(); i++) + { + sscanf_s(valuePtr + 2 * i, "%02hhx", &result[i]); + } + + return result; + } + + template + static std::string JoinInternal(std::string_view separator, const std::vector& vector) + { + auto vectorSize = vector.size(); + if (vectorSize == 0) + { + return {}; + } + + std::ostringstream ssJoin; + ssJoin << vector[0]; + for (size_t i = 1; i < vectorSize; ++i) + { + ssJoin << separator << vector[i]; + } + return ssJoin.str(); + } + + LocIndString Join(LocIndView separator, const std::vector& vector) + { + return LocIndString{ JoinInternal(separator, vector) }; + } + + std::string Join(std::string_view separator, const std::vector& vector) + { + return JoinInternal(separator, vector); + } + + std::vector Split(const std::string& input, char separator, bool trim) + { + return SplitTemplate(input, separator, trim); + } + + std::vector SplitView(const std::string& input, char separator, bool trim) + { + return SplitTemplate(static_cast(input), separator, trim); + } + + std::vector Split(std::string_view input, char separator, bool trim) + { + return SplitTemplate(input, separator, trim); + } + + std::vector Split(const std::wstring& input, wchar_t separator, bool trim) + { + return SplitTemplate(input, separator, trim); + } + + std::vector SplitView(const std::wstring& input, wchar_t separator, bool trim) + { + return SplitTemplate(static_cast(input), separator, trim); + } + + std::vector Split(std::wstring_view input, wchar_t separator, bool trim) + { + return SplitTemplate(input, separator, trim); + } + + std::string_view ConvertBoolToString(bool value) + { + return value ? "true"sv : "false"sv; + } + + std::optional TryConvertStringToBool(const std::string_view& input) + { + try + { + if (CaseInsensitiveEquals(input, "false"sv)) + { + return { false }; + } + + if (CaseInsensitiveEquals(input, "true"sv)) + { + return { true }; + } + + return {}; + } + catch (...) + { + return {}; + } + } + + std::optional TryConvertStringToInt32(const std::string_view& input) + { + int32_t result = 0; + auto parseResult = std::from_chars(input.data(), input.data() + input.length(), result); + + std::optional optionalResult; + if (parseResult.ec == std::errc{}) + { + optionalResult = result; + } + + return optionalResult; + } + + std::string ConvertGuidToString(const GUID& value) + { + wchar_t buffer[40]; + THROW_HR_IF(E_UNEXPECTED, !StringFromGUID2(value, buffer, ARRAYSIZE(buffer))); + return ConvertToUTF8(buffer); + } + + std::wstring CreateNewGuidNameWString() + { + GUID guid; + THROW_IF_FAILED(CoCreateGuid(&guid)); + + wchar_t buffer[40]; + THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(guid, buffer, ARRAYSIZE(buffer)) != 39); + + return std::wstring{ &buffer[1], 36 }; + } + + bool IsDwordFlagSet(const std::string& value) + { + if (std::empty(value)) + { + return false; + } + + try + { + DWORD dwordValue = std::stoul(value); + + // If the value is 0, then it is not set. + return dwordValue != 0; + } + catch (...) + { + return false; + } + } + + size_t FindControlCodeToConvert(std::string_view input, size_t offset) + { + size_t nextControl = offset; + while (nextControl < input.size()) + { + char currentChar = input[nextControl]; + + // Convert all low controls except tab, line feed and carriage return + if (currentChar >= 0 && currentChar < 0x20 && + currentChar != '\t' && + currentChar != '\n' && + currentChar != '\r') + { + break; + } + + // Convert the Delete control + if (currentChar == 0x7F) + { + break; + } + + ++nextControl; + } + + return nextControl < input.size() ? nextControl : std::string::npos; + } + + std::string ConvertControlCodesToPictures(std::string_view input) + { + std::string result; + size_t pos = 0; + + while (pos < input.size()) + { + size_t nextControl = FindControlCodeToConvert(input, pos); + + if (nextControl == std::string::npos) + { + // No more control codes found + result += input.substr(pos); + break; + } + else + { + result += input.substr(pos, nextControl - pos); + + char currentChar = input[nextControl]; + + if (currentChar >= 0 && currentChar < 0x20) + { + // ASCII 0x00 - 0x1F => UTF-8 0x2400 - 0x241F + // Then manually converted to UTF-8 since only the last character is affected + result += '\xE2'; + result += '\x90'; + result += ('\x80' + currentChar); + } + else if (currentChar == 0x7F) + { + // UTF-8 for control picture of DELETE + result += "\xE2\x90\xA1"; + } + + pos = nextControl + 1; + } + } + + return result; + } + + std::string GetRandomString(size_t size) + { + static constexpr char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + static std::default_random_engine randomEngine(std::random_device{}()); + static std::uniform_int_distribution distribution(0, 35); + + std::string result; + result.resize(size); + + for (size_t i = 0; i < size; i++) + { + result[i] = chars[distribution(randomEngine)]; + } + + return result; + } + + bool IsValidWindowsFeaturePattern(std::string_view value) + { + if (value.empty()) + { + return false; + } + + for (char c : value) + { + if (!std::isalnum(static_cast(c)) && c != '-' && c != '_') + { + return false; + } + } + + return true; + } +} diff --git a/src/AppInstallerSharedLib/COMStaticStorage.cpp b/src/AppInstallerSharedLib/COMStaticStorage.cpp index f271ab1823..35002def45 100644 --- a/src/AppInstallerSharedLib/COMStaticStorage.cpp +++ b/src/AppInstallerSharedLib/COMStaticStorage.cpp @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/COMStaticStorage.h" - -namespace AppInstaller::WinRT -{ - COMStaticStorageStatics& COMStaticStorageStatics::Instance() - { - static COMStaticStorageStatics s_instance; - return s_instance; - } - - void COMStaticStorageStatics::AddStaticStorageItem(const winrt::hstring& name, const winrt::Windows::Foundation::IInspectable& item) - { - COMStaticStorageStatics& instance = Instance(); - const winrt::slim_lock_guard lock{ instance.m_lock }; - winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().Insert(name, item); - instance.m_items.emplace(std::wstring{ name }); - } - - void COMStaticStorageStatics::ResetAll() try - { - COMStaticStorageStatics& instance = Instance(); - std::set localItems; - - { - const winrt::slim_lock_guard lock{ instance.m_lock }; - instance.m_items.swap(localItems); - } - - for (const auto& item : localItems) - { - winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().TryRemove(item); - } - } - CATCH_LOG(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/COMStaticStorage.h" + +namespace AppInstaller::WinRT +{ + COMStaticStorageStatics& COMStaticStorageStatics::Instance() + { + static COMStaticStorageStatics s_instance; + return s_instance; + } + + void COMStaticStorageStatics::AddStaticStorageItem(const winrt::hstring& name, const winrt::Windows::Foundation::IInspectable& item) + { + COMStaticStorageStatics& instance = Instance(); + const winrt::slim_lock_guard lock{ instance.m_lock }; + winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().Insert(name, item); + instance.m_items.emplace(std::wstring{ name }); + } + + void COMStaticStorageStatics::ResetAll() try + { + COMStaticStorageStatics& instance = Instance(); + std::set localItems; + + { + const winrt::slim_lock_guard lock{ instance.m_lock }; + instance.m_items.swap(localItems); + } + + for (const auto& item : localItems) + { + winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().TryRemove(item); + } + } + CATCH_LOG(); +} diff --git a/src/AppInstallerSharedLib/Certificates.cpp b/src/AppInstallerSharedLib/Certificates.cpp index 9f25e3534c..afeab5a340 100644 --- a/src/AppInstallerSharedLib/Certificates.cpp +++ b/src/AppInstallerSharedLib/Certificates.cpp @@ -1,819 +1,819 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Certificates.h" -#include "AppInstallerDateTime.h" -#include "AppInstallerLogging.h" -#include "AppInstallerStrings.h" -#include "winget/JsonUtil.h" -#include "winget/Resources.h" - -namespace AppInstaller::Certificates -{ - namespace - { - // WTHelperProvDataFromStateData and WTHelperGetProvSignerFromChain are not in the wintrust import lib; - // resolve them at runtime via GetProcAddress. - using WTHelperProvDataFromStateDataPtr = decltype(&WTHelperProvDataFromStateData); - using WTHelperGetProvSignerFromChainPtr = decltype(&WTHelperGetProvSignerFromChain); - - struct WinTrustHelpers - { - WinTrustHelpers() - { - m_module.reset(LoadLibraryExW(L"wintrust.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); - if (!m_module) - { - AICLI_LOG(Core, Warning, << "Could not load wintrust.dll"); - return; - } - - m_provDataFromStateData = reinterpret_cast( - GetProcAddress(m_module.get(), "WTHelperProvDataFromStateData")); - if (!m_provDataFromStateData) - { - AICLI_LOG(Core, Warning, << "Could not get proc address of WTHelperProvDataFromStateData"); - } - - m_provSignerFromChain = reinterpret_cast( - GetProcAddress(m_module.get(), "WTHelperGetProvSignerFromChain")); - if (!m_provSignerFromChain) - { - AICLI_LOG(Core, Warning, << "Could not get proc address of WTHelperGetProvSignerFromChain"); - } - } - - CRYPT_PROVIDER_DATA* ProvDataFromStateData(HANDLE stateData) const - { - return m_provDataFromStateData ? m_provDataFromStateData(stateData) : nullptr; - } - - CRYPT_PROVIDER_SGNR* ProvSignerFromChain(CRYPT_PROVIDER_DATA* provData, DWORD signerIdx, BOOL counterSigner, DWORD counterSignerIdx) const - { - return m_provSignerFromChain ? m_provSignerFromChain(provData, signerIdx, counterSigner, counterSignerIdx) : nullptr; - } - - private: - wil::unique_hmodule m_module; - WTHelperProvDataFromStateDataPtr m_provDataFromStateData = nullptr; - WTHelperGetProvSignerFromChainPtr m_provSignerFromChain = nullptr; - }; - - const WinTrustHelpers& GetWinTrustHelpers() - { - static WinTrustHelpers s_helpers; - return s_helpers; - } - - std::string GetNameString(PCCERT_CONTEXT certContext, DWORD nameType, bool forIssuer, void* typeParam = nullptr) - { - if (!certContext) - { - return ""; - } - - DWORD flags = forIssuer ? CERT_NAME_ISSUER_FLAG : 0; - - DWORD characterCount = CertGetNameStringW(certContext, nameType, flags, typeParam, nullptr, 0); - std::wstring result(characterCount, L'\0'); - characterCount = CertGetNameStringW(certContext, nameType, flags, typeParam, &result[0], characterCount); - - if (static_cast(characterCount) == result.size()) - { - return Utility::ConvertToUTF8(static_cast(result).substr(0, result.size() - 1)); - } - else - { - return ""; - } - } - - std::string GetSimpleDisplayName(PCCERT_CONTEXT certContext, bool forIssuer = false) - { - return GetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, forIssuer); - } - - std::string GetX500Name(PCCERT_CONTEXT certContext, bool forIssuer = false) - { - DWORD stringType = CERT_X500_NAME_STR; - return GetNameString(certContext, CERT_NAME_RDN_TYPE, forIssuer, &stringType); - } - - std::string GetCommonName(PCCERT_CONTEXT certContext, bool forIssuer = false) - { - std::string commonName = szOID_COMMON_NAME; - return GetNameString(certContext, CERT_NAME_ATTR_TYPE, forIssuer, &commonName[0]); - } - - std::string GetDescriptionOfCertChain(PCCERT_CHAIN_CONTEXT chainContext) - { - PCCERT_SIMPLE_CHAIN chain = chainContext->rgpChain[0]; - std::ostringstream stream; - std::string indent; - - for (DWORD i = 0; i < chain->cElement; ++i) - { - PCCERT_CHAIN_ELEMENT element = chain->rgpElement[(chain->cElement - 1) - i]; - - if (!indent.empty()) - { - stream << std::endl; - } - - stream << indent; - - stream << GetSimpleDisplayName(element->pCertContext); - - indent.append(" "); - } - - return std::move(stream).str(); - } - - std::optional GetTypeFromString(std::string_view value) - { - std::string lowerValue = Utility::ToLower(value); - - if (lowerValue == "none") - { - return PinningVerificationType::None; - } - else if (lowerValue == "publickey") - { - return PinningVerificationType::PublicKey; - } - else if (lowerValue == "subject") - { - return PinningVerificationType::Subject; - } - else if (lowerValue == "issuer") - { - return PinningVerificationType::Issuer; - } - else if (lowerValue == "anyissuer") - { - return PinningVerificationType::AnyIssuer; - } - else if (lowerValue == "requirenonleaf") - { - return PinningVerificationType::RequireNonLeaf; - } - - return {}; - } - - CertificateChainPosition GetCertificateChainPosition(DWORD index, DWORD count) - { - THROW_HR_IF(E_INVALIDARG, count == 0); - - CertificateChainPosition position = CertificateChainPosition::Unknown; - - if (index == 0) - { - position |= CertificateChainPosition::Root; - } - - if (index > 0 && index < (count - 1)) - { - position |= CertificateChainPosition::Intermediate; - } - - if (index == (count - 1)) - { - position |= CertificateChainPosition::Leaf; - } - - return position; - } - } - - std::ostream& operator<<(std::ostream& out, PinningVerificationType value) - { - if (value == PinningVerificationType::None) - { - out << "None"; - } - else - { - bool prepend = false; - - for (const auto& flag : std::initializer_list>{ - { PinningVerificationType::PublicKey, "PublicKey" }, - { PinningVerificationType::Subject, "Subject" }, - { PinningVerificationType::Issuer, "Issuer" }, - { PinningVerificationType::AnyIssuer, "AnyIssuer" }, - { PinningVerificationType::RequireNonLeaf, "RequireNonLeaf" }, - }) - { - if (WI_IsAnyFlagSet(value, flag.first)) - { - if (prepend) - { - out << " | "; - } - out << flag.second; - prepend = true; - } - } - } - - return out; - } - - std::ostream& operator<<(std::ostream& out, CertificateChainPosition value) - { - if (value == CertificateChainPosition::Unknown) - { - out << "Unknown"; - } - else - { - bool prepend = false; - - for (const auto& flag : std::initializer_list>{ - { CertificateChainPosition::Root, "Root" }, - { CertificateChainPosition::Intermediate, "Intermediate" }, - { CertificateChainPosition::Leaf, "Leaf" }, - }) - { - if (WI_IsAnyFlagSet(value, flag.first)) - { - if (prepend) - { - out << " | "; - } - out << flag.second; - prepend = true; - } - } - } - - return out; - } - - PinningDetails& PinningDetails::LoadCertificate(int resource, int resourceType) - { - return LoadCertificate(Resource::GetResourceAsBytes(resource, resourceType)); - } - - PinningDetails& PinningDetails::LoadCertificate(const std::vector& certificateBytes) - { - return LoadCertificate(std::make_pair(&certificateBytes[0], certificateBytes.size())); - } - - PinningDetails& PinningDetails::LoadCertificate(const std::pair certificateBytes) - { - m_certificateContext.reset(CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, certificateBytes.first, static_cast(certificateBytes.second))); - THROW_LAST_ERROR_IF(!m_certificateContext); - return *this; - } - - PinningDetails& PinningDetails::SetPinning(PinningVerificationType type) - { - m_pinning = type; - return *this; - } - - // The JSON is expected to look like: - // { - // "Validation":["publickey"], - // "EmbeddedCertificate":"" - // } - bool PinningDetails::LoadFrom(const Json::Value& configuration) - { - const std::string validationName = "Validation"; - - if (!configuration.isMember(validationName)) - { - AICLI_LOG(Core, Warning, << "Details JSON item has no member " << validationName); - return false; - } - - auto validationValue = JSON::GetValue>(configuration[validationName]); - if (!validationValue) - { - AICLI_LOG(Core, Warning, << "Details JSON item member " << validationName << " was not an array of strings"); - return false; - } - - for (const std::string& singleValidation : validationValue.value()) - { - auto validationType = GetTypeFromString(singleValidation); - - if (!validationType) - { - AICLI_LOG(Core, Warning, << "Details JSON validation is unknown: " << singleValidation); - return false; - } - - m_pinning |= validationType.value(); - } - - if (m_pinning == PinningVerificationType::None) - { - // No need to load a certificate if not doing any pinning - return true; - } - - const std::string embeddedCertificateName = "EmbeddedCertificate"; - - if (!configuration.isMember(embeddedCertificateName)) - { - AICLI_LOG(Core, Warning, << "Details JSON item has no member " << embeddedCertificateName); - return false; - } - - auto embeddedCertificateValue = JSON::GetValue(configuration[embeddedCertificateName]); - if (!validationValue) - { - AICLI_LOG(Core, Warning, << "Details JSON item member " << embeddedCertificateName << " was not a string"); - return false; - } - - auto embeddedCertificateBytes = Utility::ParseFromHexString(embeddedCertificateValue.value()); - LoadCertificate(embeddedCertificateBytes); - - return true; - - } - - CertificatePinningValidationResult PinningDetails::Validate(PCCERT_CONTEXT certContext, CertificateChainPosition position) const - { - CertificatePinningValidationResult failResult = WI_IsFlagSet(m_pinning, PinningVerificationType::AnyIssuer) ? CertificatePinningValidationResult::Skipped : CertificatePinningValidationResult::Rejected; - - if (WI_IsFlagSet(m_pinning, PinningVerificationType::RequireNonLeaf) && - WI_IsFlagSet(position, CertificateChainPosition::Leaf)) - { - AICLI_LOG(Core, Verbose, << "Required non-leaf mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "] was " << position); - return CertificatePinningValidationResult::Rejected; - } - - if (WI_IsFlagSet(m_pinning, PinningVerificationType::PublicKey)) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); - - if (!CertComparePublicKeyInfo( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - &m_certificateContext.get()->pCertInfo->SubjectPublicKeyInfo, - &certContext->pCertInfo->SubjectPublicKeyInfo)) - { - AICLI_LOG(Core, Verbose, << "Public key mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); - return failResult; - } - } - - if (WI_IsFlagSet(m_pinning, PinningVerificationType::Subject)) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); - - if (!CertCompareCertificateName( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - &m_certificateContext.get()->pCertInfo->Subject, - &certContext->pCertInfo->Subject)) - { - AICLI_LOG(Core, Verbose, << "Subject mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); - return failResult; - } - } - - if (WI_IsFlagSet(m_pinning, PinningVerificationType::Issuer)) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); - - if (!CertCompareCertificateName( - X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, - &m_certificateContext.get()->pCertInfo->Issuer, - &certContext->pCertInfo->Issuer)) - { - AICLI_LOG(Core, Verbose, << "Issuer mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); - return failResult; - } - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - if (m_customValidation) - { - if (!m_customValidation(*this, certContext, position)) - { - AICLI_LOG(Core, Verbose, << "Custom validation returned false: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); - return failResult; - } - } -#endif - - return CertificatePinningValidationResult::Accepted; - } - - void PinningDetails::OutputDescription(std::ostream& stream, std::string_view indent) const - { - stream << indent << GetSimpleDisplayName(m_certificateContext.get()) << " : " << m_pinning; - } - - double PinningDetails::GetRemainingLifetimePercentage() const - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); - - auto notBefore = Utility::ConvertFiletimeToSystemClock(m_certificateContext.get()->pCertInfo->NotBefore); - auto notAfter = Utility::ConvertFiletimeToSystemClock(m_certificateContext.get()->pCertInfo->NotAfter); - THROW_HR_IF(E_NOT_VALID_STATE, notBefore > notAfter); - - auto now = std::chrono::system_clock::now(); - - if (now < notBefore) - { - return 1.0; - } - else if (now > notAfter) - { - return 0.0; - } - - auto totalTime = notAfter - notBefore; - auto remainingTime = notAfter - now; - - return static_cast(remainingTime.count()) / static_cast(totalTime.count()); - } - - PinningChain::Node PinningChain::Node::Next() - { - if (!HasNext()) - { - m_chain.get().emplace_back(); - } - - return { m_chain, m_index + 1 }; - } - - const PinningChain::Node PinningChain::Node::Next() const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !HasNext()); - return { m_chain, m_index + 1 }; - } - - void PinningChain::Node::RemoveNext() - { - m_chain.get().erase(m_chain.get().begin() + m_index + 1, m_chain.get().end()); - } - - bool PinningChain::Node::HasNext() const - { - return (m_index + 1 < m_chain.get().size()); - } - - PinningChain::Node::Node(std::vector& chain, size_t index) : - m_chain(chain), m_index(index) {} - - PinningChain::Node PinningChain::Root() - { - if (m_chain.empty()) - { - m_chain.emplace_back(); - } - - return { m_chain, 0 }; - } - - const PinningChain::Node PinningChain::Root() const - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_chain.empty()); - return { const_cast&>(m_chain), 0 }; - } - - PinningChain& PinningChain::PartialChain(bool isPartial) - { - m_partial = isPartial; - return *this; - } - - bool PinningChain::Validate(PCCERT_CHAIN_CONTEXT chainContext) const - { - if (m_chain.empty()) - { - // An empty chain rejects all inputs. - AICLI_LOG(Core, Warning, << "Empty pinning chain blindly rejecting chain context"); - return false; - } - - THROW_HR_IF(E_INVALIDARG, chainContext->cChain == 0); - - // Currently don't support chains bridged with CTLs; there must be only one simple chain that terminates in a trusted root. - if (chainContext->cChain > 1) - { - AICLI_LOG(Core, Verbose, << "Rejecting chain context with multiple chains"); - return false; - } - - PCCERT_SIMPLE_CHAIN chain = chainContext->rgpChain[0]; - - if (chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) - { - AICLI_LOG(Core, Verbose, << "Rejecting simple chain context with bad TrustStatus: " << chain->TrustStatus.dwErrorStatus << " [" << chain->TrustStatus.dwInfoStatus << "]"); - return false; - } - - if (chain->pTrustListInfo) - { - // This should not happen as the only reason for pTrustListInfo to be set is when `chainContext->cChain > 1`, which is rejected above - AICLI_LOG(Core, Verbose, << "Rejecting simple chain context with CTL info"); - return false; - } - - if (!m_partial && static_cast(chain->cElement) != m_chain.size()) - { - AICLI_LOG(Core, Verbose, << "Rejecting simple chain context based on size: expected " << m_chain.size() << ", got " << chain->cElement); - return false; - } - - size_t currentDetailsIndex = 0; - - for (DWORD i = 0; i < chain->cElement; ++i) - { - PCCERT_CHAIN_ELEMENT element = chain->rgpElement[(chain->cElement - 1) - i]; - - if (element->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) - { - AICLI_LOG(Core, Verbose, << "Rejecting chain element with bad TrustStatus: " << element->TrustStatus.dwErrorStatus << " [" << element->TrustStatus.dwInfoStatus << "]"); - return false; - } - - CertificatePinningValidationResult result = m_chain[currentDetailsIndex].Validate(element->pCertContext, GetCertificateChainPosition(i, chain->cElement)); - - if (result == CertificatePinningValidationResult::Rejected) - { - return false; - } - else if (result == CertificatePinningValidationResult::Accepted) - { - ++currentDetailsIndex; - } - else - { - THROW_HR_IF(E_UNEXPECTED, !m_partial || result != CertificatePinningValidationResult::Skipped); - AICLI_LOG(Core, Verbose, << "Skipping [" << GetSimpleDisplayName(element->pCertContext) << "] in partial chain validation."); - } - - if (m_partial && m_chain.size() == currentDetailsIndex) - { - break; - } - } - - // Ensure that all chain elements have been accepted - return m_chain.size() == currentDetailsIndex; - } - - std::string PinningChain::GetDescription() const - { - if (m_chain.empty()) - { - return ""; - } - - std::ostringstream stream; - std::string indent; - - for (const PinningDetails& details : m_chain) - { - if (!indent.empty()) - { - stream << std::endl; - } - else if (m_partial) - { - stream << "[Partial Chain Validation]" << std::endl; - } - - details.OutputDescription(stream, indent); - indent.append(" "); - } - - return std::move(stream).str(); - } - - // The JSON is expected to look like: - // { - // "Chain":[ - // { - // "Validation":["publickey"], - // "EmbeddedCertificate":"" - // }, - // { - // "Validation":["subject","issuer"], - // "EmbeddedCertificate":"" - // }, - // ... - // ] - // } - bool PinningChain::LoadFrom(const Json::Value& configuration) - { - const std::string chainName = "Chain"; - if (!configuration.isMember(chainName)) - { - AICLI_LOG(Core, Warning, << "Chains JSON item has no member " << chainName); - return false; - } - - const auto& chain = configuration[chainName]; - if (!chain.isArray()) - { - AICLI_LOG(Core, Warning, << "Chain JSON input is not an array"); - return false; - } - - for (const auto& configItem : chain) - { - PinningDetails details; - if (!details.LoadFrom(configItem)) - { - return false; - } - - m_chain.emplace_back(std::move(details)); - } - - return true; - } - - double PinningChain::GetRemainingLifetimePercentage() const - { - double result = 1.0; - - for (const auto& details : m_chain) - { - result = std::min(result, details.GetRemainingLifetimePercentage()); - } - - return result; - } - - CallbackPinningChainValidation::CallbackPinningChainValidation(std::function callback) - : m_callback(std::move(callback)) - { - } - - bool CallbackPinningChainValidation::Validate(PCCERT_CHAIN_CONTEXT chainContext) const - { - THROW_HR_IF(E_INVALIDARG, !chainContext || chainContext->cChain == 0); - PCCERT_SIMPLE_CHAIN simpleChain = chainContext->rgpChain[0]; - THROW_HR_IF(E_INVALIDARG, !simpleChain || simpleChain->cElement == 0); - PCCERT_CHAIN_ELEMENT leafElement = simpleChain->rgpElement[0]; - THROW_HR_IF(E_INVALIDARG, !leafElement || !leafElement->pCertContext); - return m_callback(leafElement->pCertContext); - } - - std::string CallbackPinningChainValidation::GetDescription() const - { - return ""; - } - - PinningConfiguration::PinningConfiguration(std::string identifier) : m_identifier(identifier) - { - if (m_identifier.empty()) - { - GUID guid; - LOG_IF_FAILED(CoCreateGuid(&guid)); - wchar_t identifierBuffer[256] = {}; - (void)StringFromGUID2(guid, identifierBuffer, ARRAYSIZE(identifierBuffer)); - m_identifier = Utility::ConvertToUTF8(identifierBuffer); - } - } - - void PinningConfiguration::AddChain(PinningChain chain) - { - AddChain(std::make_shared(std::move(chain))); - } - - void PinningConfiguration::AddChain(std::shared_ptr chain) - { - AICLI_LOG(Core, Verbose, << "Adding chain to pinning configuration [" << m_identifier << "]:\n" << chain->GetDescription()); - m_configuration.emplace_back(std::move(chain)); - } - - bool PinningConfiguration::Validate(PCCERT_CONTEXT certContext) const - { - if (m_configuration.empty()) - { - // No pinning configured - return true; - } - - const BYTE* encodedBegin = certContext->pbCertEncoded; - const BYTE* encodedEnd = encodedBegin + certContext->cbCertEncoded; - if (certContext->cbCertEncoded == m_cachedCertificate.size() && - std::equal(encodedBegin, encodedEnd, m_cachedCertificate.begin())) - { - // We have seen this certificate and deemed it valid already. - return true; - } - - // Get the chain for the given leaf certificate - wil::unique_cert_chain_context chainContext; - - char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH; - std::array chainUses = { - oidPkixKpServerAuth, - }; - - CERT_CHAIN_PARA chainParameters = {}; - chainParameters.cbSize = sizeof(chainParameters); - chainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; - chainParameters.RequestedUsage.Usage.cUsageIdentifier = static_cast(chainUses.size()); - chainParameters.RequestedUsage.Usage.rgpszUsageIdentifier = chainUses.data(); - - THROW_IF_WIN32_BOOL_FALSE(CertGetCertificateChain(nullptr, certContext, nullptr, certContext->hCertStore, &chainParameters, CERT_CHAIN_REVOCATION_CHECK_CHAIN, nullptr, &chainContext)); - - bool result = false; - - for (const auto& chain : m_configuration) - { - if (chain->Validate(chainContext.get())) - { - AICLI_LOG(Core, Verbose, << "Certificate `" << GetSimpleDisplayName(certContext) << "` accepted by pinning configuration:\n" << chain->GetDescription()); - result = true; - break; - } - } - - if (result) - { - // Only cache a successful validation. - m_cachedCertificate.assign(encodedBegin, encodedEnd); - } - else - { - AICLI_LOG(Core, Error, << "Rejecting certificate [" << GetSimpleDisplayName(certContext) << "] as it did not match anything in pinning configuration [" << m_identifier << "]:\n" << GetDescriptionOfCertChain(chainContext.get())); - } - - return result; - } - - // The JSON is expected to look like: - // { - // "Chains":[ - // { - // "Chain":[ - // { - // "Validation":["publickey"], - // "EmbeddedCertificate":"" - // }, - // { - // "Validation":["subject","issuer"], - // "EmbeddedCertificate":"" - // }, - // ... - // ] - // } - // ] - // } - bool PinningConfiguration::LoadFrom(const Json::Value& configuration) - { - const std::string chainsName = "Chains"; - if (!configuration.isMember(chainsName)) - { - AICLI_LOG(Core, Warning, << "PinningConfiguration JSON item has no member " << chainsName); - return false; - } - const auto& chains = configuration[chainsName]; - - if (!chains.isArray()) - { - AICLI_LOG(Core, Warning, << "PinningConfiguration.Chains is not an array"); - return false; - } - - std::vector resultCache; - - for (const auto& configItem : chains) - { - PinningChain chain; - if (!chain.LoadFrom(configItem)) - { - return false; - } - - resultCache.emplace_back(std::move(chain)); - } - - // Move all chains into the config now that we have succeeded - for (auto& result : resultCache) - { - AddChain(std::move(result)); - } - - return true; - } - - double PinningConfiguration::GetRemainingLifetimePercentage() const - { - double result = 0.0; - - for (const auto& chain : m_configuration) - { - result = std::max(result, chain->GetRemainingLifetimePercentage()); - } - - return result; - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Certificates.h" +#include "AppInstallerDateTime.h" +#include "AppInstallerLogging.h" +#include "AppInstallerStrings.h" +#include "winget/JsonUtil.h" +#include "winget/Resources.h" + +namespace AppInstaller::Certificates +{ + namespace + { + // WTHelperProvDataFromStateData and WTHelperGetProvSignerFromChain are not in the wintrust import lib; + // resolve them at runtime via GetProcAddress. + using WTHelperProvDataFromStateDataPtr = decltype(&WTHelperProvDataFromStateData); + using WTHelperGetProvSignerFromChainPtr = decltype(&WTHelperGetProvSignerFromChain); + + struct WinTrustHelpers + { + WinTrustHelpers() + { + m_module.reset(LoadLibraryExW(L"wintrust.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); + if (!m_module) + { + AICLI_LOG(Core, Warning, << "Could not load wintrust.dll"); + return; + } + + m_provDataFromStateData = reinterpret_cast( + GetProcAddress(m_module.get(), "WTHelperProvDataFromStateData")); + if (!m_provDataFromStateData) + { + AICLI_LOG(Core, Warning, << "Could not get proc address of WTHelperProvDataFromStateData"); + } + + m_provSignerFromChain = reinterpret_cast( + GetProcAddress(m_module.get(), "WTHelperGetProvSignerFromChain")); + if (!m_provSignerFromChain) + { + AICLI_LOG(Core, Warning, << "Could not get proc address of WTHelperGetProvSignerFromChain"); + } + } + + CRYPT_PROVIDER_DATA* ProvDataFromStateData(HANDLE stateData) const + { + return m_provDataFromStateData ? m_provDataFromStateData(stateData) : nullptr; + } + + CRYPT_PROVIDER_SGNR* ProvSignerFromChain(CRYPT_PROVIDER_DATA* provData, DWORD signerIdx, BOOL counterSigner, DWORD counterSignerIdx) const + { + return m_provSignerFromChain ? m_provSignerFromChain(provData, signerIdx, counterSigner, counterSignerIdx) : nullptr; + } + + private: + wil::unique_hmodule m_module; + WTHelperProvDataFromStateDataPtr m_provDataFromStateData = nullptr; + WTHelperGetProvSignerFromChainPtr m_provSignerFromChain = nullptr; + }; + + const WinTrustHelpers& GetWinTrustHelpers() + { + static WinTrustHelpers s_helpers; + return s_helpers; + } + + std::string GetNameString(PCCERT_CONTEXT certContext, DWORD nameType, bool forIssuer, void* typeParam = nullptr) + { + if (!certContext) + { + return ""; + } + + DWORD flags = forIssuer ? CERT_NAME_ISSUER_FLAG : 0; + + DWORD characterCount = CertGetNameStringW(certContext, nameType, flags, typeParam, nullptr, 0); + std::wstring result(characterCount, L'\0'); + characterCount = CertGetNameStringW(certContext, nameType, flags, typeParam, &result[0], characterCount); + + if (static_cast(characterCount) == result.size()) + { + return Utility::ConvertToUTF8(static_cast(result).substr(0, result.size() - 1)); + } + else + { + return ""; + } + } + + std::string GetSimpleDisplayName(PCCERT_CONTEXT certContext, bool forIssuer = false) + { + return GetNameString(certContext, CERT_NAME_SIMPLE_DISPLAY_TYPE, forIssuer); + } + + std::string GetX500Name(PCCERT_CONTEXT certContext, bool forIssuer = false) + { + DWORD stringType = CERT_X500_NAME_STR; + return GetNameString(certContext, CERT_NAME_RDN_TYPE, forIssuer, &stringType); + } + + std::string GetCommonName(PCCERT_CONTEXT certContext, bool forIssuer = false) + { + std::string commonName = szOID_COMMON_NAME; + return GetNameString(certContext, CERT_NAME_ATTR_TYPE, forIssuer, &commonName[0]); + } + + std::string GetDescriptionOfCertChain(PCCERT_CHAIN_CONTEXT chainContext) + { + PCCERT_SIMPLE_CHAIN chain = chainContext->rgpChain[0]; + std::ostringstream stream; + std::string indent; + + for (DWORD i = 0; i < chain->cElement; ++i) + { + PCCERT_CHAIN_ELEMENT element = chain->rgpElement[(chain->cElement - 1) - i]; + + if (!indent.empty()) + { + stream << std::endl; + } + + stream << indent; + + stream << GetSimpleDisplayName(element->pCertContext); + + indent.append(" "); + } + + return std::move(stream).str(); + } + + std::optional GetTypeFromString(std::string_view value) + { + std::string lowerValue = Utility::ToLower(value); + + if (lowerValue == "none") + { + return PinningVerificationType::None; + } + else if (lowerValue == "publickey") + { + return PinningVerificationType::PublicKey; + } + else if (lowerValue == "subject") + { + return PinningVerificationType::Subject; + } + else if (lowerValue == "issuer") + { + return PinningVerificationType::Issuer; + } + else if (lowerValue == "anyissuer") + { + return PinningVerificationType::AnyIssuer; + } + else if (lowerValue == "requirenonleaf") + { + return PinningVerificationType::RequireNonLeaf; + } + + return {}; + } + + CertificateChainPosition GetCertificateChainPosition(DWORD index, DWORD count) + { + THROW_HR_IF(E_INVALIDARG, count == 0); + + CertificateChainPosition position = CertificateChainPosition::Unknown; + + if (index == 0) + { + position |= CertificateChainPosition::Root; + } + + if (index > 0 && index < (count - 1)) + { + position |= CertificateChainPosition::Intermediate; + } + + if (index == (count - 1)) + { + position |= CertificateChainPosition::Leaf; + } + + return position; + } + } + + std::ostream& operator<<(std::ostream& out, PinningVerificationType value) + { + if (value == PinningVerificationType::None) + { + out << "None"; + } + else + { + bool prepend = false; + + for (const auto& flag : std::initializer_list>{ + { PinningVerificationType::PublicKey, "PublicKey" }, + { PinningVerificationType::Subject, "Subject" }, + { PinningVerificationType::Issuer, "Issuer" }, + { PinningVerificationType::AnyIssuer, "AnyIssuer" }, + { PinningVerificationType::RequireNonLeaf, "RequireNonLeaf" }, + }) + { + if (WI_IsAnyFlagSet(value, flag.first)) + { + if (prepend) + { + out << " | "; + } + out << flag.second; + prepend = true; + } + } + } + + return out; + } + + std::ostream& operator<<(std::ostream& out, CertificateChainPosition value) + { + if (value == CertificateChainPosition::Unknown) + { + out << "Unknown"; + } + else + { + bool prepend = false; + + for (const auto& flag : std::initializer_list>{ + { CertificateChainPosition::Root, "Root" }, + { CertificateChainPosition::Intermediate, "Intermediate" }, + { CertificateChainPosition::Leaf, "Leaf" }, + }) + { + if (WI_IsAnyFlagSet(value, flag.first)) + { + if (prepend) + { + out << " | "; + } + out << flag.second; + prepend = true; + } + } + } + + return out; + } + + PinningDetails& PinningDetails::LoadCertificate(int resource, int resourceType) + { + return LoadCertificate(Resource::GetResourceAsBytes(resource, resourceType)); + } + + PinningDetails& PinningDetails::LoadCertificate(const std::vector& certificateBytes) + { + return LoadCertificate(std::make_pair(&certificateBytes[0], certificateBytes.size())); + } + + PinningDetails& PinningDetails::LoadCertificate(const std::pair certificateBytes) + { + m_certificateContext.reset(CertCreateCertificateContext(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, certificateBytes.first, static_cast(certificateBytes.second))); + THROW_LAST_ERROR_IF(!m_certificateContext); + return *this; + } + + PinningDetails& PinningDetails::SetPinning(PinningVerificationType type) + { + m_pinning = type; + return *this; + } + + // The JSON is expected to look like: + // { + // "Validation":["publickey"], + // "EmbeddedCertificate":"" + // } + bool PinningDetails::LoadFrom(const Json::Value& configuration) + { + const std::string validationName = "Validation"; + + if (!configuration.isMember(validationName)) + { + AICLI_LOG(Core, Warning, << "Details JSON item has no member " << validationName); + return false; + } + + auto validationValue = JSON::GetValue>(configuration[validationName]); + if (!validationValue) + { + AICLI_LOG(Core, Warning, << "Details JSON item member " << validationName << " was not an array of strings"); + return false; + } + + for (const std::string& singleValidation : validationValue.value()) + { + auto validationType = GetTypeFromString(singleValidation); + + if (!validationType) + { + AICLI_LOG(Core, Warning, << "Details JSON validation is unknown: " << singleValidation); + return false; + } + + m_pinning |= validationType.value(); + } + + if (m_pinning == PinningVerificationType::None) + { + // No need to load a certificate if not doing any pinning + return true; + } + + const std::string embeddedCertificateName = "EmbeddedCertificate"; + + if (!configuration.isMember(embeddedCertificateName)) + { + AICLI_LOG(Core, Warning, << "Details JSON item has no member " << embeddedCertificateName); + return false; + } + + auto embeddedCertificateValue = JSON::GetValue(configuration[embeddedCertificateName]); + if (!validationValue) + { + AICLI_LOG(Core, Warning, << "Details JSON item member " << embeddedCertificateName << " was not a string"); + return false; + } + + auto embeddedCertificateBytes = Utility::ParseFromHexString(embeddedCertificateValue.value()); + LoadCertificate(embeddedCertificateBytes); + + return true; + + } + + CertificatePinningValidationResult PinningDetails::Validate(PCCERT_CONTEXT certContext, CertificateChainPosition position) const + { + CertificatePinningValidationResult failResult = WI_IsFlagSet(m_pinning, PinningVerificationType::AnyIssuer) ? CertificatePinningValidationResult::Skipped : CertificatePinningValidationResult::Rejected; + + if (WI_IsFlagSet(m_pinning, PinningVerificationType::RequireNonLeaf) && + WI_IsFlagSet(position, CertificateChainPosition::Leaf)) + { + AICLI_LOG(Core, Verbose, << "Required non-leaf mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "] was " << position); + return CertificatePinningValidationResult::Rejected; + } + + if (WI_IsFlagSet(m_pinning, PinningVerificationType::PublicKey)) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); + + if (!CertComparePublicKeyInfo( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + &m_certificateContext.get()->pCertInfo->SubjectPublicKeyInfo, + &certContext->pCertInfo->SubjectPublicKeyInfo)) + { + AICLI_LOG(Core, Verbose, << "Public key mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); + return failResult; + } + } + + if (WI_IsFlagSet(m_pinning, PinningVerificationType::Subject)) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); + + if (!CertCompareCertificateName( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + &m_certificateContext.get()->pCertInfo->Subject, + &certContext->pCertInfo->Subject)) + { + AICLI_LOG(Core, Verbose, << "Subject mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); + return failResult; + } + } + + if (WI_IsFlagSet(m_pinning, PinningVerificationType::Issuer)) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); + + if (!CertCompareCertificateName( + X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, + &m_certificateContext.get()->pCertInfo->Issuer, + &certContext->pCertInfo->Issuer)) + { + AICLI_LOG(Core, Verbose, << "Issuer mismatch: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); + return failResult; + } + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + if (m_customValidation) + { + if (!m_customValidation(*this, certContext, position)) + { + AICLI_LOG(Core, Verbose, << "Custom validation returned false: Expected certificate [" << GetSimpleDisplayName(m_certificateContext.get()) << "], Actual certificate [" << GetSimpleDisplayName(certContext) << "]"); + return failResult; + } + } +#endif + + return CertificatePinningValidationResult::Accepted; + } + + void PinningDetails::OutputDescription(std::ostream& stream, std::string_view indent) const + { + stream << indent << GetSimpleDisplayName(m_certificateContext.get()) << " : " << m_pinning; + } + + double PinningDetails::GetRemainingLifetimePercentage() const + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_certificateContext); + + auto notBefore = Utility::ConvertFiletimeToSystemClock(m_certificateContext.get()->pCertInfo->NotBefore); + auto notAfter = Utility::ConvertFiletimeToSystemClock(m_certificateContext.get()->pCertInfo->NotAfter); + THROW_HR_IF(E_NOT_VALID_STATE, notBefore > notAfter); + + auto now = std::chrono::system_clock::now(); + + if (now < notBefore) + { + return 1.0; + } + else if (now > notAfter) + { + return 0.0; + } + + auto totalTime = notAfter - notBefore; + auto remainingTime = notAfter - now; + + return static_cast(remainingTime.count()) / static_cast(totalTime.count()); + } + + PinningChain::Node PinningChain::Node::Next() + { + if (!HasNext()) + { + m_chain.get().emplace_back(); + } + + return { m_chain, m_index + 1 }; + } + + const PinningChain::Node PinningChain::Node::Next() const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), !HasNext()); + return { m_chain, m_index + 1 }; + } + + void PinningChain::Node::RemoveNext() + { + m_chain.get().erase(m_chain.get().begin() + m_index + 1, m_chain.get().end()); + } + + bool PinningChain::Node::HasNext() const + { + return (m_index + 1 < m_chain.get().size()); + } + + PinningChain::Node::Node(std::vector& chain, size_t index) : + m_chain(chain), m_index(index) {} + + PinningChain::Node PinningChain::Root() + { + if (m_chain.empty()) + { + m_chain.emplace_back(); + } + + return { m_chain, 0 }; + } + + const PinningChain::Node PinningChain::Root() const + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INVALID_STATE), m_chain.empty()); + return { const_cast&>(m_chain), 0 }; + } + + PinningChain& PinningChain::PartialChain(bool isPartial) + { + m_partial = isPartial; + return *this; + } + + bool PinningChain::Validate(PCCERT_CHAIN_CONTEXT chainContext) const + { + if (m_chain.empty()) + { + // An empty chain rejects all inputs. + AICLI_LOG(Core, Warning, << "Empty pinning chain blindly rejecting chain context"); + return false; + } + + THROW_HR_IF(E_INVALIDARG, chainContext->cChain == 0); + + // Currently don't support chains bridged with CTLs; there must be only one simple chain that terminates in a trusted root. + if (chainContext->cChain > 1) + { + AICLI_LOG(Core, Verbose, << "Rejecting chain context with multiple chains"); + return false; + } + + PCCERT_SIMPLE_CHAIN chain = chainContext->rgpChain[0]; + + if (chain->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) + { + AICLI_LOG(Core, Verbose, << "Rejecting simple chain context with bad TrustStatus: " << chain->TrustStatus.dwErrorStatus << " [" << chain->TrustStatus.dwInfoStatus << "]"); + return false; + } + + if (chain->pTrustListInfo) + { + // This should not happen as the only reason for pTrustListInfo to be set is when `chainContext->cChain > 1`, which is rejected above + AICLI_LOG(Core, Verbose, << "Rejecting simple chain context with CTL info"); + return false; + } + + if (!m_partial && static_cast(chain->cElement) != m_chain.size()) + { + AICLI_LOG(Core, Verbose, << "Rejecting simple chain context based on size: expected " << m_chain.size() << ", got " << chain->cElement); + return false; + } + + size_t currentDetailsIndex = 0; + + for (DWORD i = 0; i < chain->cElement; ++i) + { + PCCERT_CHAIN_ELEMENT element = chain->rgpElement[(chain->cElement - 1) - i]; + + if (element->TrustStatus.dwErrorStatus != CERT_TRUST_NO_ERROR) + { + AICLI_LOG(Core, Verbose, << "Rejecting chain element with bad TrustStatus: " << element->TrustStatus.dwErrorStatus << " [" << element->TrustStatus.dwInfoStatus << "]"); + return false; + } + + CertificatePinningValidationResult result = m_chain[currentDetailsIndex].Validate(element->pCertContext, GetCertificateChainPosition(i, chain->cElement)); + + if (result == CertificatePinningValidationResult::Rejected) + { + return false; + } + else if (result == CertificatePinningValidationResult::Accepted) + { + ++currentDetailsIndex; + } + else + { + THROW_HR_IF(E_UNEXPECTED, !m_partial || result != CertificatePinningValidationResult::Skipped); + AICLI_LOG(Core, Verbose, << "Skipping [" << GetSimpleDisplayName(element->pCertContext) << "] in partial chain validation."); + } + + if (m_partial && m_chain.size() == currentDetailsIndex) + { + break; + } + } + + // Ensure that all chain elements have been accepted + return m_chain.size() == currentDetailsIndex; + } + + std::string PinningChain::GetDescription() const + { + if (m_chain.empty()) + { + return ""; + } + + std::ostringstream stream; + std::string indent; + + for (const PinningDetails& details : m_chain) + { + if (!indent.empty()) + { + stream << std::endl; + } + else if (m_partial) + { + stream << "[Partial Chain Validation]" << std::endl; + } + + details.OutputDescription(stream, indent); + indent.append(" "); + } + + return std::move(stream).str(); + } + + // The JSON is expected to look like: + // { + // "Chain":[ + // { + // "Validation":["publickey"], + // "EmbeddedCertificate":"" + // }, + // { + // "Validation":["subject","issuer"], + // "EmbeddedCertificate":"" + // }, + // ... + // ] + // } + bool PinningChain::LoadFrom(const Json::Value& configuration) + { + const std::string chainName = "Chain"; + if (!configuration.isMember(chainName)) + { + AICLI_LOG(Core, Warning, << "Chains JSON item has no member " << chainName); + return false; + } + + const auto& chain = configuration[chainName]; + if (!chain.isArray()) + { + AICLI_LOG(Core, Warning, << "Chain JSON input is not an array"); + return false; + } + + for (const auto& configItem : chain) + { + PinningDetails details; + if (!details.LoadFrom(configItem)) + { + return false; + } + + m_chain.emplace_back(std::move(details)); + } + + return true; + } + + double PinningChain::GetRemainingLifetimePercentage() const + { + double result = 1.0; + + for (const auto& details : m_chain) + { + result = std::min(result, details.GetRemainingLifetimePercentage()); + } + + return result; + } + + CallbackPinningChainValidation::CallbackPinningChainValidation(std::function callback) + : m_callback(std::move(callback)) + { + } + + bool CallbackPinningChainValidation::Validate(PCCERT_CHAIN_CONTEXT chainContext) const + { + THROW_HR_IF(E_INVALIDARG, !chainContext || chainContext->cChain == 0); + PCCERT_SIMPLE_CHAIN simpleChain = chainContext->rgpChain[0]; + THROW_HR_IF(E_INVALIDARG, !simpleChain || simpleChain->cElement == 0); + PCCERT_CHAIN_ELEMENT leafElement = simpleChain->rgpElement[0]; + THROW_HR_IF(E_INVALIDARG, !leafElement || !leafElement->pCertContext); + return m_callback(leafElement->pCertContext); + } + + std::string CallbackPinningChainValidation::GetDescription() const + { + return ""; + } + + PinningConfiguration::PinningConfiguration(std::string identifier) : m_identifier(identifier) + { + if (m_identifier.empty()) + { + GUID guid; + LOG_IF_FAILED(CoCreateGuid(&guid)); + wchar_t identifierBuffer[256] = {}; + (void)StringFromGUID2(guid, identifierBuffer, ARRAYSIZE(identifierBuffer)); + m_identifier = Utility::ConvertToUTF8(identifierBuffer); + } + } + + void PinningConfiguration::AddChain(PinningChain chain) + { + AddChain(std::make_shared(std::move(chain))); + } + + void PinningConfiguration::AddChain(std::shared_ptr chain) + { + AICLI_LOG(Core, Verbose, << "Adding chain to pinning configuration [" << m_identifier << "]:\n" << chain->GetDescription()); + m_configuration.emplace_back(std::move(chain)); + } + + bool PinningConfiguration::Validate(PCCERT_CONTEXT certContext) const + { + if (m_configuration.empty()) + { + // No pinning configured + return true; + } + + const BYTE* encodedBegin = certContext->pbCertEncoded; + const BYTE* encodedEnd = encodedBegin + certContext->cbCertEncoded; + if (certContext->cbCertEncoded == m_cachedCertificate.size() && + std::equal(encodedBegin, encodedEnd, m_cachedCertificate.begin())) + { + // We have seen this certificate and deemed it valid already. + return true; + } + + // Get the chain for the given leaf certificate + wil::unique_cert_chain_context chainContext; + + char oidPkixKpServerAuth[] = szOID_PKIX_KP_SERVER_AUTH; + std::array chainUses = { + oidPkixKpServerAuth, + }; + + CERT_CHAIN_PARA chainParameters = {}; + chainParameters.cbSize = sizeof(chainParameters); + chainParameters.RequestedUsage.dwType = USAGE_MATCH_TYPE_OR; + chainParameters.RequestedUsage.Usage.cUsageIdentifier = static_cast(chainUses.size()); + chainParameters.RequestedUsage.Usage.rgpszUsageIdentifier = chainUses.data(); + + THROW_IF_WIN32_BOOL_FALSE(CertGetCertificateChain(nullptr, certContext, nullptr, certContext->hCertStore, &chainParameters, CERT_CHAIN_REVOCATION_CHECK_CHAIN, nullptr, &chainContext)); + + bool result = false; + + for (const auto& chain : m_configuration) + { + if (chain->Validate(chainContext.get())) + { + AICLI_LOG(Core, Verbose, << "Certificate `" << GetSimpleDisplayName(certContext) << "` accepted by pinning configuration:\n" << chain->GetDescription()); + result = true; + break; + } + } + + if (result) + { + // Only cache a successful validation. + m_cachedCertificate.assign(encodedBegin, encodedEnd); + } + else + { + AICLI_LOG(Core, Error, << "Rejecting certificate [" << GetSimpleDisplayName(certContext) << "] as it did not match anything in pinning configuration [" << m_identifier << "]:\n" << GetDescriptionOfCertChain(chainContext.get())); + } + + return result; + } + + // The JSON is expected to look like: + // { + // "Chains":[ + // { + // "Chain":[ + // { + // "Validation":["publickey"], + // "EmbeddedCertificate":"" + // }, + // { + // "Validation":["subject","issuer"], + // "EmbeddedCertificate":"" + // }, + // ... + // ] + // } + // ] + // } + bool PinningConfiguration::LoadFrom(const Json::Value& configuration) + { + const std::string chainsName = "Chains"; + if (!configuration.isMember(chainsName)) + { + AICLI_LOG(Core, Warning, << "PinningConfiguration JSON item has no member " << chainsName); + return false; + } + const auto& chains = configuration[chainsName]; + + if (!chains.isArray()) + { + AICLI_LOG(Core, Warning, << "PinningConfiguration.Chains is not an array"); + return false; + } + + std::vector resultCache; + + for (const auto& configItem : chains) + { + PinningChain chain; + if (!chain.LoadFrom(configItem)) + { + return false; + } + + resultCache.emplace_back(std::move(chain)); + } + + // Move all chains into the config now that we have succeeded + for (auto& result : resultCache) + { + AddChain(std::move(result)); + } + + return true; + } + + double PinningConfiguration::GetRemainingLifetimePercentage() const + { + double result = 0.0; + + for (const auto& chain : m_configuration) + { + result = std::max(result, chain->GetRemainingLifetimePercentage()); + } + + return result; + } std::string GetAuthenticodeSubject(const std::filesystem::path& filePath) { @@ -845,13 +845,13 @@ namespace AppInstaller::Certificates { return {}; } - - CRYPT_PROVIDER_DATA* provData = GetWinTrustHelpers().ProvDataFromStateData(trustData.hWVTStateData); - if (!provData) - { - return {}; - } - + + CRYPT_PROVIDER_DATA* provData = GetWinTrustHelpers().ProvDataFromStateData(trustData.hWVTStateData); + if (!provData) + { + return {}; + } + CRYPT_PROVIDER_SGNR* signer = GetWinTrustHelpers().ProvSignerFromChain(provData, 0, FALSE, 0); if (!signer || signer->csCertChain == 0 || !signer->pasCertChain) { @@ -863,8 +863,8 @@ namespace AppInstaller::Certificates { return {}; } - + DWORD strType = CERT_X500_NAME_STR | CERT_NAME_STR_REVERSE_FLAG; return GetNameString(certContext, CERT_NAME_RDN_TYPE, false, &strType); } -} +} diff --git a/src/AppInstallerSharedLib/Compression.cpp b/src/AppInstallerSharedLib/Compression.cpp index d66fda1e91..ab143aaa40 100644 --- a/src/AppInstallerSharedLib/Compression.cpp +++ b/src/AppInstallerSharedLib/Compression.cpp @@ -1,93 +1,93 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/Compression.h" - -namespace AppInstaller::Compression -{ - Compressor::Compressor(DWORD algorithm) - { - THROW_IF_WIN32_BOOL_FALSE(CreateCompressor(algorithm, nullptr, &m_compressor)); - } - - std::vector Compressor::Compress(std::string_view data) - { - std::vector result; - - if (!data.empty()) - { - SIZE_T compressedBufferSize = 0; - THROW_HR_IF(E_UNEXPECTED, ::Compress(m_compressor.get(), data.data(), data.size(), nullptr, 0, &compressedBufferSize)); - THROW_LAST_ERROR_IF(GetLastError() != ERROR_INSUFFICIENT_BUFFER); - - result.resize(compressedBufferSize); - - SIZE_T compressedDataSize = 0; - THROW_IF_WIN32_BOOL_FALSE(::Compress(m_compressor.get(), data.data(), data.size(), &result[0], result.size(), &compressedDataSize)); - - result.resize(compressedDataSize); - } - - return result; - } - - void Compressor::Reset() - { - THROW_IF_WIN32_BOOL_FALSE(ResetCompressor(m_compressor.get())); - } - - void Compressor::SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value) - { - THROW_IF_WIN32_BOOL_FALSE(SetCompressorInformation(m_compressor.get(), information, &value, sizeof(value))); - } - - DWORD Compressor::GetInformation(COMPRESS_INFORMATION_CLASS information) - { - DWORD result = 0; - THROW_IF_WIN32_BOOL_FALSE(QueryCompressorInformation(m_compressor.get(), information, &result, sizeof(result))); - return result; - } - - Decompressor::Decompressor(DWORD algorithm) - { - THROW_IF_WIN32_BOOL_FALSE(CreateDecompressor(algorithm, nullptr, &m_decompressor)); - } - - std::vector Decompressor::Decompress(const std::vector& data) - { - std::vector result; - - if (!data.empty()) - { - SIZE_T decompressedBufferSize = 0; - THROW_HR_IF(E_UNEXPECTED, ::Decompress(m_decompressor.get(), data.data(), data.size(), nullptr, 0, &decompressedBufferSize)); - THROW_LAST_ERROR_IF(GetLastError() != ERROR_INSUFFICIENT_BUFFER); - - result.resize(decompressedBufferSize); - - SIZE_T decompressedDataSize = 0; - THROW_IF_WIN32_BOOL_FALSE(::Decompress(m_decompressor.get(), data.data(), data.size(), &result[0], result.size(), &decompressedDataSize)); - - result.resize(decompressedDataSize); - } - - return result; - } - - void Decompressor::Reset() - { - THROW_IF_WIN32_BOOL_FALSE(ResetDecompressor(m_decompressor.get())); - } - - void Decompressor::SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value) - { - THROW_IF_WIN32_BOOL_FALSE(SetDecompressorInformation(m_decompressor.get(), information, &value, sizeof(value))); - } - - DWORD Decompressor::GetInformation(COMPRESS_INFORMATION_CLASS information) - { - DWORD result = 0; - THROW_IF_WIN32_BOOL_FALSE(QueryDecompressorInformation(m_decompressor.get(), information, &result, sizeof(result))); - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Compression.h" + +namespace AppInstaller::Compression +{ + Compressor::Compressor(DWORD algorithm) + { + THROW_IF_WIN32_BOOL_FALSE(CreateCompressor(algorithm, nullptr, &m_compressor)); + } + + std::vector Compressor::Compress(std::string_view data) + { + std::vector result; + + if (!data.empty()) + { + SIZE_T compressedBufferSize = 0; + THROW_HR_IF(E_UNEXPECTED, ::Compress(m_compressor.get(), data.data(), data.size(), nullptr, 0, &compressedBufferSize)); + THROW_LAST_ERROR_IF(GetLastError() != ERROR_INSUFFICIENT_BUFFER); + + result.resize(compressedBufferSize); + + SIZE_T compressedDataSize = 0; + THROW_IF_WIN32_BOOL_FALSE(::Compress(m_compressor.get(), data.data(), data.size(), &result[0], result.size(), &compressedDataSize)); + + result.resize(compressedDataSize); + } + + return result; + } + + void Compressor::Reset() + { + THROW_IF_WIN32_BOOL_FALSE(ResetCompressor(m_compressor.get())); + } + + void Compressor::SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value) + { + THROW_IF_WIN32_BOOL_FALSE(SetCompressorInformation(m_compressor.get(), information, &value, sizeof(value))); + } + + DWORD Compressor::GetInformation(COMPRESS_INFORMATION_CLASS information) + { + DWORD result = 0; + THROW_IF_WIN32_BOOL_FALSE(QueryCompressorInformation(m_compressor.get(), information, &result, sizeof(result))); + return result; + } + + Decompressor::Decompressor(DWORD algorithm) + { + THROW_IF_WIN32_BOOL_FALSE(CreateDecompressor(algorithm, nullptr, &m_decompressor)); + } + + std::vector Decompressor::Decompress(const std::vector& data) + { + std::vector result; + + if (!data.empty()) + { + SIZE_T decompressedBufferSize = 0; + THROW_HR_IF(E_UNEXPECTED, ::Decompress(m_decompressor.get(), data.data(), data.size(), nullptr, 0, &decompressedBufferSize)); + THROW_LAST_ERROR_IF(GetLastError() != ERROR_INSUFFICIENT_BUFFER); + + result.resize(decompressedBufferSize); + + SIZE_T decompressedDataSize = 0; + THROW_IF_WIN32_BOOL_FALSE(::Decompress(m_decompressor.get(), data.data(), data.size(), &result[0], result.size(), &decompressedDataSize)); + + result.resize(decompressedDataSize); + } + + return result; + } + + void Decompressor::Reset() + { + THROW_IF_WIN32_BOOL_FALSE(ResetDecompressor(m_decompressor.get())); + } + + void Decompressor::SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value) + { + THROW_IF_WIN32_BOOL_FALSE(SetDecompressorInformation(m_decompressor.get(), information, &value, sizeof(value))); + } + + DWORD Decompressor::GetInformation(COMPRESS_INFORMATION_CLASS information) + { + DWORD result = 0; + THROW_IF_WIN32_BOOL_FALSE(QueryDecompressorInformation(m_decompressor.get(), information, &result, sizeof(result))); + return result; + } +} diff --git a/src/AppInstallerSharedLib/DateTime.cpp b/src/AppInstallerSharedLib/DateTime.cpp index b5c26bae66..40fecd73b2 100644 --- a/src/AppInstallerSharedLib/DateTime.cpp +++ b/src/AppInstallerSharedLib/DateTime.cpp @@ -1,197 +1,197 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerDateTime.h" - -using namespace std::chrono; - -namespace AppInstaller::Utility -{ - namespace - { - struct OutputTimePointContext - { - OutputTimePointContext(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet) : - Stream(stream), Time(time), Facet(facet) - { - auto tt = system_clock::to_time_t(time); - _localtime64_s(&LocalTime, &tt); - } - - std::ostream& Stream; - const std::chrono::system_clock::time_point& Time; - tm LocalTime{}; - TimeFacet Facet; - }; - - struct OutputTimePointFacetInfo - { - TimeFacet Facet; - char FollowingSeparator; - void (*Action)(const OutputTimePointContext&); - }; - } - - void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, bool useRFC3339) - { - OutputTimePoint(stream, time, TimeFacet::Default | (useRFC3339 ? TimeFacet::RFC3339 : TimeFacet::None)); - } - - // If moved to C++20, this can be replaced with standard library implementations. - void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet) - { - OutputTimePointContext context{ stream, time, facet }; - using Ctx = const OutputTimePointContext&; - - bool useRFC3339 = WI_IsFlagSet(facet, TimeFacet::RFC3339); - bool filename = WI_IsFlagSet(facet, TimeFacet::Filename); - char day_time_separator = useRFC3339 ? 'T' : (filename ? '-' : ' '); - char time_field_separator = filename ? '-' : ':'; - - bool needsSeparator = false; - char currentSeparator = '-'; - - for (const auto& info : { - OutputTimePointFacetInfo{ TimeFacet::ShortYear, '-', [](Ctx ctx) { ctx.Stream << (ctx.LocalTime.tm_year - 100); }}, - OutputTimePointFacetInfo{ TimeFacet::Year, '-', [](Ctx ctx) { ctx.Stream << (1900 + ctx.LocalTime.tm_year); }}, - OutputTimePointFacetInfo{ TimeFacet::Month, '-', [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << (1 + ctx.LocalTime.tm_mon); }}, - OutputTimePointFacetInfo{ TimeFacet::Day, day_time_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_mday; }}, - OutputTimePointFacetInfo{ TimeFacet::Hour, time_field_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_hour; }}, - OutputTimePointFacetInfo{ TimeFacet::Minute, time_field_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_min; }}, - OutputTimePointFacetInfo{ TimeFacet::Second, '.', [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_sec; }}, - OutputTimePointFacetInfo{ TimeFacet::Millisecond, '-', [](Ctx ctx) - { - // Get partial seconds - auto sinceEpoch = ctx.Time.time_since_epoch(); - auto leftoverMillis = duration_cast(sinceEpoch) - duration_cast(sinceEpoch); - - ctx.Stream << std::setw(3) << std::setfill('0') << leftoverMillis.count(); - }}, - OutputTimePointFacetInfo{ TimeFacet::RFC3339, '\0', [](Ctx ctx) - { - // RFC 3339 requires adding time zone info. - // No need to bother getting the actual time zone as we don't need it. - // -00:00 represents an unspecified time zone, not UTC. - ctx.Stream << "00:00"; - }}, - }) - { - if (WI_AreAllFlagsSet(facet, info.Facet)) - { - if (needsSeparator) - { - stream << currentSeparator; - } - - info.Action(context); - needsSeparator = true; - } - - // Getting this right for every mix of facets is probably not possible. - // Future needs can dictate changes here. - currentSeparator = info.FollowingSeparator; - } - } - - std::string TimePointToString(const std::chrono::system_clock::time_point& time, bool useRFC3339) - { - std::ostringstream stream; - OutputTimePoint(stream, time, useRFC3339); - return std::move(stream).str(); - } - - std::string TimePointToString(const std::chrono::system_clock::time_point& time, TimeFacet facet) - { - std::ostringstream stream; - OutputTimePoint(stream, time, facet); - return std::move(stream).str(); - } - - std::string GetCurrentTimeForFilename(bool shortTime) - { - return TimePointToString(std::chrono::system_clock::now(), (shortTime ? TimeFacet::ShortYearSecondPrecision : TimeFacet::Default) | TimeFacet::Filename); - } - - std::string GetCurrentDateForARP() - { - auto now = std::chrono::system_clock::now(); - std::time_t tt = std::chrono::system_clock::to_time_t(now); - - struct tm newTime; - localtime_s(&newTime, &tt); - - std::stringstream ss; - ss << std::put_time(&newTime, "%Y%m%d"); - return ss.str(); - } - - int64_t GetCurrentUnixEpoch() - { - static_assert(std::is_same_v, "time returns a 64-bit integer"); - time_t now = time(nullptr); - return static_cast(now); - } - - int64_t ConvertSystemClockToUnixEpoch(const std::chrono::system_clock::time_point& time) - { - static_assert(std::is_same_v, "to_time_t returns a 64-bit integer"); - time_t timeAsTimeT = std::chrono::system_clock::to_time_t(time); - return static_cast(timeAsTimeT); - } - - std::chrono::system_clock::time_point ConvertUnixEpochToSystemClock(int64_t epoch) - { - return std::chrono::system_clock::from_time_t(static_cast(epoch)); - } - - std::chrono::system_clock::time_point ConvertFiletimeToSystemClock(const FILETIME& fileTime) - { - return winrt::clock::to_sys(winrt::clock::from_FILETIME(fileTime)); - } - - FILETIME ConvertSystemClockToFileTime(const std::chrono::system_clock::time_point& time) - { - return winrt::clock::to_FILETIME(winrt::clock::from_sys(time)); - } - - std::chrono::system_clock::time_point GetTimePointFromVersion(const UInt64Version& version) - { - // Our custom format for converting UTC into a version is: - // Major :: `Year` [1, 9999] - // Minor :: `Month * 100 + Day` where Month [1, 12] and Day [1, 31] - // Build :: `Hour * 100 + Minute` where Hour [1, 24] and Minute [0, 59] - // Revision :: Milliseconds, but since no seconds are available we will disregard this - - tm versionTime{}; - - // Limit to the range supported by _mkgmtime64, which is 1970 to 3000 (hello to Y3K maintainers from 2023!) - UINT64 majorVersion = version.Major(); - if (majorVersion < 1970 || majorVersion > 3000) - { - return std::chrono::system_clock::time_point::min(); - } - versionTime.tm_year = static_cast(majorVersion) - 1900; - - UINT64 minorVersion = version.Minor(); - UINT64 monthValue = minorVersion / 100; - UINT64 dayValue = minorVersion % 100; - if (monthValue < 1 || monthValue > 12 || dayValue < 1 || dayValue > 31) - { - return std::chrono::system_clock::time_point::min(); - } - versionTime.tm_mon = static_cast(monthValue) - 1; - versionTime.tm_mday = static_cast(dayValue); - - UINT64 buildVersion = version.Build(); - UINT64 hourValue = buildVersion / 100; - UINT64 minuteValue = buildVersion % 100; - if (hourValue < 1 || hourValue > 24 || minuteValue > 59) - { - return std::chrono::system_clock::time_point::min(); - } - versionTime.tm_hour = static_cast(hourValue) - 1; - versionTime.tm_min = static_cast(minuteValue); - - return std::chrono::system_clock::from_time_t(_mkgmtime64(&versionTime)); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerDateTime.h" + +using namespace std::chrono; + +namespace AppInstaller::Utility +{ + namespace + { + struct OutputTimePointContext + { + OutputTimePointContext(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet) : + Stream(stream), Time(time), Facet(facet) + { + auto tt = system_clock::to_time_t(time); + _localtime64_s(&LocalTime, &tt); + } + + std::ostream& Stream; + const std::chrono::system_clock::time_point& Time; + tm LocalTime{}; + TimeFacet Facet; + }; + + struct OutputTimePointFacetInfo + { + TimeFacet Facet; + char FollowingSeparator; + void (*Action)(const OutputTimePointContext&); + }; + } + + void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, bool useRFC3339) + { + OutputTimePoint(stream, time, TimeFacet::Default | (useRFC3339 ? TimeFacet::RFC3339 : TimeFacet::None)); + } + + // If moved to C++20, this can be replaced with standard library implementations. + void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet) + { + OutputTimePointContext context{ stream, time, facet }; + using Ctx = const OutputTimePointContext&; + + bool useRFC3339 = WI_IsFlagSet(facet, TimeFacet::RFC3339); + bool filename = WI_IsFlagSet(facet, TimeFacet::Filename); + char day_time_separator = useRFC3339 ? 'T' : (filename ? '-' : ' '); + char time_field_separator = filename ? '-' : ':'; + + bool needsSeparator = false; + char currentSeparator = '-'; + + for (const auto& info : { + OutputTimePointFacetInfo{ TimeFacet::ShortYear, '-', [](Ctx ctx) { ctx.Stream << (ctx.LocalTime.tm_year - 100); }}, + OutputTimePointFacetInfo{ TimeFacet::Year, '-', [](Ctx ctx) { ctx.Stream << (1900 + ctx.LocalTime.tm_year); }}, + OutputTimePointFacetInfo{ TimeFacet::Month, '-', [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << (1 + ctx.LocalTime.tm_mon); }}, + OutputTimePointFacetInfo{ TimeFacet::Day, day_time_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_mday; }}, + OutputTimePointFacetInfo{ TimeFacet::Hour, time_field_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_hour; }}, + OutputTimePointFacetInfo{ TimeFacet::Minute, time_field_separator, [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_min; }}, + OutputTimePointFacetInfo{ TimeFacet::Second, '.', [](Ctx ctx) { ctx.Stream << std::setw(2) << std::setfill('0') << ctx.LocalTime.tm_sec; }}, + OutputTimePointFacetInfo{ TimeFacet::Millisecond, '-', [](Ctx ctx) + { + // Get partial seconds + auto sinceEpoch = ctx.Time.time_since_epoch(); + auto leftoverMillis = duration_cast(sinceEpoch) - duration_cast(sinceEpoch); + + ctx.Stream << std::setw(3) << std::setfill('0') << leftoverMillis.count(); + }}, + OutputTimePointFacetInfo{ TimeFacet::RFC3339, '\0', [](Ctx ctx) + { + // RFC 3339 requires adding time zone info. + // No need to bother getting the actual time zone as we don't need it. + // -00:00 represents an unspecified time zone, not UTC. + ctx.Stream << "00:00"; + }}, + }) + { + if (WI_AreAllFlagsSet(facet, info.Facet)) + { + if (needsSeparator) + { + stream << currentSeparator; + } + + info.Action(context); + needsSeparator = true; + } + + // Getting this right for every mix of facets is probably not possible. + // Future needs can dictate changes here. + currentSeparator = info.FollowingSeparator; + } + } + + std::string TimePointToString(const std::chrono::system_clock::time_point& time, bool useRFC3339) + { + std::ostringstream stream; + OutputTimePoint(stream, time, useRFC3339); + return std::move(stream).str(); + } + + std::string TimePointToString(const std::chrono::system_clock::time_point& time, TimeFacet facet) + { + std::ostringstream stream; + OutputTimePoint(stream, time, facet); + return std::move(stream).str(); + } + + std::string GetCurrentTimeForFilename(bool shortTime) + { + return TimePointToString(std::chrono::system_clock::now(), (shortTime ? TimeFacet::ShortYearSecondPrecision : TimeFacet::Default) | TimeFacet::Filename); + } + + std::string GetCurrentDateForARP() + { + auto now = std::chrono::system_clock::now(); + std::time_t tt = std::chrono::system_clock::to_time_t(now); + + struct tm newTime; + localtime_s(&newTime, &tt); + + std::stringstream ss; + ss << std::put_time(&newTime, "%Y%m%d"); + return ss.str(); + } + + int64_t GetCurrentUnixEpoch() + { + static_assert(std::is_same_v, "time returns a 64-bit integer"); + time_t now = time(nullptr); + return static_cast(now); + } + + int64_t ConvertSystemClockToUnixEpoch(const std::chrono::system_clock::time_point& time) + { + static_assert(std::is_same_v, "to_time_t returns a 64-bit integer"); + time_t timeAsTimeT = std::chrono::system_clock::to_time_t(time); + return static_cast(timeAsTimeT); + } + + std::chrono::system_clock::time_point ConvertUnixEpochToSystemClock(int64_t epoch) + { + return std::chrono::system_clock::from_time_t(static_cast(epoch)); + } + + std::chrono::system_clock::time_point ConvertFiletimeToSystemClock(const FILETIME& fileTime) + { + return winrt::clock::to_sys(winrt::clock::from_FILETIME(fileTime)); + } + + FILETIME ConvertSystemClockToFileTime(const std::chrono::system_clock::time_point& time) + { + return winrt::clock::to_FILETIME(winrt::clock::from_sys(time)); + } + + std::chrono::system_clock::time_point GetTimePointFromVersion(const UInt64Version& version) + { + // Our custom format for converting UTC into a version is: + // Major :: `Year` [1, 9999] + // Minor :: `Month * 100 + Day` where Month [1, 12] and Day [1, 31] + // Build :: `Hour * 100 + Minute` where Hour [1, 24] and Minute [0, 59] + // Revision :: Milliseconds, but since no seconds are available we will disregard this + + tm versionTime{}; + + // Limit to the range supported by _mkgmtime64, which is 1970 to 3000 (hello to Y3K maintainers from 2023!) + UINT64 majorVersion = version.Major(); + if (majorVersion < 1970 || majorVersion > 3000) + { + return std::chrono::system_clock::time_point::min(); + } + versionTime.tm_year = static_cast(majorVersion) - 1900; + + UINT64 minorVersion = version.Minor(); + UINT64 monthValue = minorVersion / 100; + UINT64 dayValue = minorVersion % 100; + if (monthValue < 1 || monthValue > 12 || dayValue < 1 || dayValue > 31) + { + return std::chrono::system_clock::time_point::min(); + } + versionTime.tm_mon = static_cast(monthValue) - 1; + versionTime.tm_mday = static_cast(dayValue); + + UINT64 buildVersion = version.Build(); + UINT64 hourValue = buildVersion / 100; + UINT64 minuteValue = buildVersion % 100; + if (hourValue < 1 || hourValue > 24 || minuteValue > 59) + { + return std::chrono::system_clock::time_point::min(); + } + versionTime.tm_hour = static_cast(hourValue) - 1; + versionTime.tm_min = static_cast(minuteValue); + + return std::chrono::system_clock::from_time_t(_mkgmtime64(&versionTime)); + } +} diff --git a/src/AppInstallerSharedLib/Errors.cpp b/src/AppInstallerSharedLib/Errors.cpp index 0e9b811e87..f91e2fc621 100644 --- a/src/AppInstallerSharedLib/Errors.cpp +++ b/src/AppInstallerSharedLib/Errors.cpp @@ -1,501 +1,501 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerErrors.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerStrings.h" -#include "Public/winget/Resources.h" - - -namespace AppInstaller -{ - namespace - { - // A simple struct to hold the data - struct HResultData - { - HRESULT Value; - std::string_view Symbol; - std::string_view Description; - - bool operator<(const HResultData& other) const - { - return Value < other.Value; - } - }; - - // HRESULT information for our errors - struct WinGetHResultInformation : public Errors::HResultInformation - { - constexpr WinGetHResultInformation(HRESULT value, std::string_view symbol, std::string_view unlocalizedDescription) : - Errors::HResultInformation(value, symbol), m_unlocalizedDescription(unlocalizedDescription) - {} - - constexpr WinGetHResultInformation(const HResultData& data) : - Errors::HResultInformation(data.Value, data.Symbol), m_unlocalizedDescription(data.Description) - {} - - Utility::LocIndString GetDescription() const override - { - auto localizedDescription = StringResource::TryResolveString(Utility::ConvertToUTF16(Symbol())); - return localizedDescription ? - std::move(localizedDescription).value() : - Utility::LocIndString{ m_unlocalizedDescription }; - } - - private: - std::string_view m_unlocalizedDescription; - }; - - // HRESULT information for our errors, with the unlocalized description only. - struct WinGetHResultInformationUnlocalized : public Errors::HResultInformation - { - constexpr WinGetHResultInformationUnlocalized(HRESULT value, std::string_view symbol, std::string_view unlocalizedDescription) : - Errors::HResultInformation(value, symbol), m_unlocalizedDescription(unlocalizedDescription) - {} - - constexpr WinGetHResultInformationUnlocalized(const HResultData& data) : - Errors::HResultInformation(data.Value, data.Symbol), m_unlocalizedDescription(data.Description) - {} - - Utility::LocIndString GetDescription() const override - { - return Utility::LocIndString{ m_unlocalizedDescription }; - } - - private: - std::string_view m_unlocalizedDescription; - }; - - // The information entry for an HRESULT not in the list (someone probably forgot to add an entry) - struct UnknownHResultInformation : public Errors::HResultInformation - { - constexpr UnknownHResultInformation(HRESULT value) : - Errors::HResultInformation(value) - {} - - Utility::LocIndString GetDescription() const override - { - auto localizedDescription = StringResource::TryResolveString(StringResource::String::UnknownErrorCode); - return localizedDescription ? - std::move(localizedDescription).value() : - Utility::LocIndString{ "Unknown error code"sv }; - } - }; - -#define WINGET_HRESULT_INFO(_name_,_description_) HResultData{ _name_, #_name_, _description_ } - - constexpr const HResultData s_wingetHResultData[] = - { - // Changes to any of these errors require the corresponding resource string in winget.resw to be updated. - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, "Internal Error"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, "Invalid command line arguments"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_COMMAND_FAILED, "Executing command failed"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MANIFEST_FAILED, "Opening manifest failed"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED, "Cancellation signal received"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED, "Running ShellExecute failed"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION, "Cannot process manifest. The manifest version is higher than supported. Please update the client."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED, "Downloading installer failed"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, "Cannot write to index; it is a higher schema version"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED, "The index is corrupt"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCES_INVALID, "The configured source information is corrupt"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, "The source name is already configured"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE, "The source type is invalid"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, "The MSIX file is a bundle, not a package"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING, "Data required by the source is missing"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER, "None of the installers are applicable for the current system"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH, "The installer file's hash does not match the manifest"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, "The source name does not exist"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS, "The source location is already configured under another name"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND, "No packages found"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED, "No sources are configured"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND, "Multiple packages found matching the criteria"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND, "No manifest found matching the criteria"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED, "Failed to get Public folder from source package"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, "Command requires administrator privileges to run"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, "The source location is not secure"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY, "The Microsoft Store client is blocked by policy"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY, "The Microsoft Store app is blocked by policy"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, "The feature is currently under development. It can be enabled using winget settings."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED, "Failed to install the Microsoft Store app"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, "Failed to perform auto complete"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, "Failed to initialize YAML parser"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY, "Encountered an invalid YAML key"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, "Encountered a duplicate YAML key"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION, "Invalid YAML operation"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, "Failed to build YAML doc"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE, "Invalid YAML emitter state"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA, "Invalid YAML data"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR, "LibYAML error"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING, "Manifest validation succeeded with warning"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE, "Manifest validation failed"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, "Manifest is invalid"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE, "No applicable update found"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE, "winget upgrade --all completed with failures"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED, "Installer failed security check"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH, "Download size does not match expected content length"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND, "Uninstall command not found"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED, "Running uninstall command failed"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR, "ICU break iterator error"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR, "ICU casemap error"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR, "ICU regex error"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED, "Failed to install one or more imported packages"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND, "Could not find one or more requested packages"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, "Json file is invalid"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE, "The source location is not remote"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, "The configured rest source is not supported"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA, "Invalid data returned by rest source"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, "Operation is blocked by Group Policy"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR, "Rest API internal error"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, "Invalid rest source url"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE, "Unsupported MIME type returned by rest API"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION, "Invalid rest source contract version"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, "The source data is corrupted or tampered"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE, "Error reading from the stream"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED, "Package agreements were not agreed to"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR, "Error reading input in prompt"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST, "The search request is not supported by one or more sources"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND, "The rest API endpoint is not found."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED, "Failed to open the source."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED, "Source agreements were not agreed to"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH, "Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE, "Missing resource file"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED, "Running MSI install failed"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT, "Arguments for msiexec are invalid"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, "Failed to open one or more sources"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED, "Failed to validate dependencies"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE, "One or more package is missing"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN, "Invalid table column"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER, "The upgrade version is not newer than the installed version"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN, "Upgrade version is unknown and override is not specified"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR, "ICU conversion error"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED, "Failed to install portable package"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED, "Volume does not support reparse points"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS, "Portable package from a different source already exists."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY, "Unable to create symlink, path points to a directory."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION, "The installer cannot be run from an administrator context."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED, "Failed to uninstall portable package"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED, "Failed to validate DisplayVersion values against index."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT, "One or more arguments are not supported."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL, "Embedded null characters are disallowed for SQLite"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND, "Failed to find the nested installer in the archive."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED, "Failed to extract archive."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH, "Invalid relative file path to nested installer provided."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH, "The server certificate did not match any of the expected values."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED, "Install location must be provided."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED, "Archive malware scan failed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED, "Found at least one version of the package installed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS, "A pin already exists for the package."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST, "There is no pin for the package."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX, "Unable to open the pin database."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED, "One or more applications failed to install"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED, "One or more applications failed to uninstall"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE, "One or more queries did not return exactly one match"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED, "The package has a pin that prevents upgrade."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB, "The package currently installed is the stub package"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED, "Application shutdown signal received"), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES, "Failed to download package dependencies."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED, "Failed to download package. Download for offline installation is prohibited."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE, "A required service is busy or unavailable. Try again later."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND, "The guid provided does not correspond to a valid resume state."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH, "The current client version did not match the client version of the saved state."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE, "The resume state data is invalid."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX, "Unable to open the checkpoint database."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED, "Exceeded max resume limit."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, "Invalid authentication info."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED, "Authentication method not supported."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED, "Authentication failed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED, "Authentication failed. Interactive authentication required."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER, "Authentication failed. User cancelled."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT, "Authentication failed. Authenticated account is not the desired account."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND, "Repair command not found."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE, "Repair operation is not applicable."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED, "Repair operation failed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED, "The installer technology in use doesn't support repair."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, "Repair operations involving administrator privileges are not permitted on packages installed within the user scope."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED, "The SQLite connection was terminated to prevent corruption."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED, "Failed to get Microsoft Store package catalog."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE, "No applicable Microsoft Store package found from Microsoft Store package catalog."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED, "Failed to get Microsoft Store package download information."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE, "No applicable Microsoft Store package download information found."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED, "Failed to retrieve Microsoft Store package license."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED, "The Microsoft Store package does not support download."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN, "Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have the required privilege."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE, "Downloaded zero byte installer; ensure that your network connection is working properly."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, "Failed installing one or more fonts."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED, "Font file is not supported and cannot be installed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED, "Font package is already installed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND, "Font file not found."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED, "Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED, "Font validation failed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED, "Font rollback failed. The font may not be in a good state. Try uninstalling after a restart."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH, "An upgrade is available but uses a different install technology than the current installation"), - - // Install errors. - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, "Application is currently running. Exit the application then try again."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS, "Another installation is already in progress. Try again later."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE, "One or more file is being used. Exit the application then try again."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY, "This package has a dependency missing from your system."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL, "There's no more space on your PC. Make space, then try again."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY, "There's not enough memory available to install. Close other applications then try again."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK, "This application requires internet connectivity. Connect to a network then try again."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT, "This application encountered an error during installation. Contact support."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH, "Restart your PC to finish installation."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, "Installation failed. Restart your PC then try again."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED, "Your PC will restart to finish installation."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER, "You cancelled the installation."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED, "Another version of this application is already installed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE, "A higher version of this application is already installed."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY, "Organization policies are preventing installation. Contact your admin."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, "Failed to install package dependencies."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION, "Application is currently in use by another application."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER, "Invalid parameter."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, "Package not supported by the system."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED, "The installer does not support upgrading an existing package."), - WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR, "Installation failed with a custom installer error."), - - // Status values for check package installed status results. - // Partial success has the success bit(first bit) set to 0. - WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND, "The Apps and Features Entry for the package could not be found."), - WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND, "The install location could not be found."), - WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH, "The hash of the existing file did not match."), - WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_NOT_FOUND, "File not found."), - WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR, "The file could not be accessed."), - - // Configuration Errors - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE, "The configuration file is invalid."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_YAML, "The YAML syntax is invalid."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, "A configuration field has an invalid type."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, "The configuration has an unknown version."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_SET_APPLY_FAILED, "An error occurred while applying the configuration."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, "The configuration contains a duplicate identifier."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, "The configuration is missing a dependency."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, "The configuration has an unsatisfied dependency."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_ASSERTION_FAILED, "An assertion for the configuration unit failed."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_MANUALLY_SKIPPED, "The configuration was manually skipped."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED, "The user declined to continue execution."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE, "The dependency graph contains a cycle which cannot be resolved."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, "The configuration has an invalid field value."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_MISSING_FIELD, "The configuration is missing a field."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_TEST_FAILED, "Some of the configuration units failed while testing their state."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_TEST_NOT_RUN, "Configuration state was not tested."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_GET_FAILED, "The configuration unit failed getting its properties."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND, "The specified configuration could not be found."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY, "Parameter cannot be passed across integrity boundary."), - - // Configuration Processor Errors - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED, "The configuration unit was not installed."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY, "The configuration unit could not be found."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES, "Multiple matches were found for the configuration unit; specify the module to select the correct one."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_GET, "The configuration unit failed while attempting to get the current system state."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST, "The configuration unit failed while attempting to test the current system state."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_SET, "The configuration unit failed while attempting to apply the desired state."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT, "The module for the configuration unit is available in multiple locations with the same version."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE, "Loading the module for the configuration unit failed."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT, "The configuration unit returned an unexpected result during execution."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT, "A unit contains a setting that requires the config root."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN, "Loading the module for the configuration unit failed because it requires administrator privileges to run."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, "Operation is not supported by the configuration processor."), - WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, "The DSC processor hash provided does not match hash of the target file."), - - // Errors without the error bit set - WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE, "The install location is not applicable."), - WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, "The file was found but the hash was not checked."), - }; - - // Map externally defined HRESULTs to error messages we want to use here - constexpr const HResultData s_externalHResultData[] = - { - // Changes to any of these errors require the corresponding resource string in winget.resw to be updated. - HResultData{ static_cast(0x803FB103), "StoreInstall_PackageNotAvailableForCurrentSystem", "The package is not compatible with the current Windows version or platform." }, - HResultData{ static_cast(0x803FB104), "StoreInstall_PackageNotAvailableForCurrentSystem", "The package is not compatible with the current Windows version or platform." }, - HResultData{ static_cast(0x803FB106), "StoreInstall_PackageNotAvailableForCurrentSystem", "The package is not compatible with the current Windows version or platform." }, - }; - - template - const HResultData* FindHResultData(HRESULT value, const HResultData (&dataArray)[ArraySize]) - { - auto itr = std::lower_bound(std::cbegin(dataArray), std::cend(dataArray), HResultData{ value }); - - if (itr != std::cend(dataArray) && itr->Value == value) - { - return itr; - } - - return nullptr; - } - - const HResultData* FindWinGetHResultData(HRESULT value) - { - return FindHResultData(value, s_wingetHResultData); - } - - const HResultData* FindExternalHResultData(HRESULT value) - { - return FindHResultData(value, s_externalHResultData); - } - - Utility::LocIndString GetMessageForAppInstallerHR(HRESULT hr) - { - const HResultData* data = FindWinGetHResultData(hr); - return data ? - WinGetHResultInformation(*data).GetDescription() : - UnknownHResultInformation(hr).GetDescription(); - } - - void GetUserPresentableMessageForHR(std::ostringstream& strstr, HRESULT hr) - { - strstr << "0x" << Logging::SetHRFormat << hr << " : "; - - if (HRESULT_FACILITY(hr) == APPINSTALLER_CLI_ERROR_FACILITY) - { - strstr << GetMessageForAppInstallerHR(hr); - } - else - { - const HResultData* data = FindExternalHResultData(hr); - - if (data) - { - strstr << WinGetHResultInformation(*data).GetDescription(); - } - else - { - strstr << std::system_category().message(hr); - } - } - } - } - - std::string GetUserPresentableMessage(const wil::ResultException& re) - { - const auto& info = re.GetFailureInfo(); - - std::ostringstream strstr; - - // We assume that if the exception has a message, that message is relevant to show to the user. - if (info.pszMessage) - { - strstr << Utility::ConvertToUTF8(info.pszMessage) << std::endl; - } - - GetUserPresentableMessageForHR(strstr, re.GetErrorCode()); - - return strstr.str(); - } - - std::string GetUserPresentableMessage(const std::exception& e) - { - return e.what(); - } - - std::string GetUserPresentableMessage(HRESULT hr) - { - std::ostringstream strstr; - GetUserPresentableMessageForHR(strstr, hr); - return strstr.str(); - } - -#ifndef WINGET_DISABLE_FOR_FUZZING - std::string GetUserPresentableMessage(const winrt::hresult_error& hre) - { - std::ostringstream strstr; - GetUserPresentableMessageForHR(strstr, hre.code()); - return strstr.str(); - } -#endif - - namespace Errors - { - constexpr HResultInformation::HResultInformation(HRESULT value) : - m_value(value) {} - - constexpr HResultInformation::HResultInformation(HRESULT value, std::string_view symbol) : - m_value(value), m_symbol(symbol) {} - - HRESULT HResultInformation::Value() const - { - return m_value; - } - - bool HResultInformation::operator<(const HResultInformation& other) const - { - return m_value < other.m_value; - } - - Utility::LocIndView HResultInformation::Symbol() const - { - return Utility::LocIndView{ m_symbol }; - } - - Utility::LocIndString HResultInformation::GetDescription() const - { - if (HRESULT_FACILITY(m_value) == APPINSTALLER_CLI_ERROR_FACILITY) - { - // In case external code attempts to construct HResultInformation directly - return GetMessageForAppInstallerHR(m_value); - } - else - { - return Utility::LocIndString{ std::system_category().message(m_value) }; - } - } - - std::unique_ptr HResultInformation::Find(HRESULT value) - { - if (HRESULT_FACILITY(value) == APPINSTALLER_CLI_ERROR_FACILITY) - { - const HResultData* data = FindWinGetHResultData(value); - - if (data) - { - return std::make_unique(*data); - } - else - { - return std::make_unique(value); - } - } - else - { - return std::make_unique(value); - } - } - - std::vector> HResultInformation::Find(std::string_view value) - { - std::vector> result; - - auto addToResultIf = [&](auto predicate) - { - for (const HResultData& data : s_wingetHResultData) - { - if (predicate(data) && - std::none_of(result.begin(), result.end(), [&](const std::unique_ptr& info) { return info->Value() == data.Value; })) - { - result.emplace_back(std::make_unique(data)); - } - } - }; - - addToResultIf([&](const HResultData& data) { return Utility::CaseInsensitiveEquals(data.Symbol, value); }); - addToResultIf([&](const HResultData& data) { return Utility::CaseInsensitiveContainsSubstring(data.Symbol, value); }); - addToResultIf([&](const HResultData& data) { return Utility::CaseInsensitiveContainsSubstring(data.Description, value); }); - - return result; - } - - std::vector> GetWinGetErrors() - { - std::vector> result; - result.reserve(ARRAYSIZE(s_wingetHResultData)); - - for (const HResultData& data : s_wingetHResultData) - { - result.emplace_back(std::make_unique(data)); - } - - return result; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerErrors.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerStrings.h" +#include "Public/winget/Resources.h" + + +namespace AppInstaller +{ + namespace + { + // A simple struct to hold the data + struct HResultData + { + HRESULT Value; + std::string_view Symbol; + std::string_view Description; + + bool operator<(const HResultData& other) const + { + return Value < other.Value; + } + }; + + // HRESULT information for our errors + struct WinGetHResultInformation : public Errors::HResultInformation + { + constexpr WinGetHResultInformation(HRESULT value, std::string_view symbol, std::string_view unlocalizedDescription) : + Errors::HResultInformation(value, symbol), m_unlocalizedDescription(unlocalizedDescription) + {} + + constexpr WinGetHResultInformation(const HResultData& data) : + Errors::HResultInformation(data.Value, data.Symbol), m_unlocalizedDescription(data.Description) + {} + + Utility::LocIndString GetDescription() const override + { + auto localizedDescription = StringResource::TryResolveString(Utility::ConvertToUTF16(Symbol())); + return localizedDescription ? + std::move(localizedDescription).value() : + Utility::LocIndString{ m_unlocalizedDescription }; + } + + private: + std::string_view m_unlocalizedDescription; + }; + + // HRESULT information for our errors, with the unlocalized description only. + struct WinGetHResultInformationUnlocalized : public Errors::HResultInformation + { + constexpr WinGetHResultInformationUnlocalized(HRESULT value, std::string_view symbol, std::string_view unlocalizedDescription) : + Errors::HResultInformation(value, symbol), m_unlocalizedDescription(unlocalizedDescription) + {} + + constexpr WinGetHResultInformationUnlocalized(const HResultData& data) : + Errors::HResultInformation(data.Value, data.Symbol), m_unlocalizedDescription(data.Description) + {} + + Utility::LocIndString GetDescription() const override + { + return Utility::LocIndString{ m_unlocalizedDescription }; + } + + private: + std::string_view m_unlocalizedDescription; + }; + + // The information entry for an HRESULT not in the list (someone probably forgot to add an entry) + struct UnknownHResultInformation : public Errors::HResultInformation + { + constexpr UnknownHResultInformation(HRESULT value) : + Errors::HResultInformation(value) + {} + + Utility::LocIndString GetDescription() const override + { + auto localizedDescription = StringResource::TryResolveString(StringResource::String::UnknownErrorCode); + return localizedDescription ? + std::move(localizedDescription).value() : + Utility::LocIndString{ "Unknown error code"sv }; + } + }; + +#define WINGET_HRESULT_INFO(_name_,_description_) HResultData{ _name_, #_name_, _description_ } + + constexpr const HResultData s_wingetHResultData[] = + { + // Changes to any of these errors require the corresponding resource string in winget.resw to be updated. + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, "Internal Error"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, "Invalid command line arguments"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_COMMAND_FAILED, "Executing command failed"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MANIFEST_FAILED, "Opening manifest failed"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED, "Cancellation signal received"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED, "Running ShellExecute failed"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION, "Cannot process manifest. The manifest version is higher than supported. Please update the client."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED, "Downloading installer failed"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX, "Cannot write to index; it is a higher schema version"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED, "The index is corrupt"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCES_INVALID, "The configured source information is corrupt"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, "The source name is already configured"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE, "The source type is invalid"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE, "The MSIX file is a bundle, not a package"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING, "Data required by the source is missing"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER, "None of the installers are applicable for the current system"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH, "The installer file's hash does not match the manifest"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, "The source name does not exist"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS, "The source location is already configured under another name"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND, "No packages found"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED, "No sources are configured"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND, "Multiple packages found matching the criteria"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND, "No manifest found matching the criteria"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED, "Failed to get Public folder from source package"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, "Command requires administrator privileges to run"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE, "The source location is not secure"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY, "The Microsoft Store client is blocked by policy"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY, "The Microsoft Store app is blocked by policy"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED, "The feature is currently under development. It can be enabled using winget settings."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED, "Failed to install the Microsoft Store app"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD, "Failed to perform auto complete"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, "Failed to initialize YAML parser"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY, "Encountered an invalid YAML key"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, "Encountered a duplicate YAML key"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION, "Invalid YAML operation"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, "Failed to build YAML doc"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE, "Invalid YAML emitter state"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA, "Invalid YAML data"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR, "LibYAML error"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING, "Manifest validation succeeded with warning"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE, "Manifest validation failed"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_MANIFEST, "Manifest is invalid"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE, "No applicable update found"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE, "winget upgrade --all completed with failures"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED, "Installer failed security check"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH, "Download size does not match expected content length"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND, "Uninstall command not found"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED, "Running uninstall command failed"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR, "ICU break iterator error"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR, "ICU casemap error"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR, "ICU regex error"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED, "Failed to install one or more imported packages"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND, "Could not find one or more requested packages"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE, "Json file is invalid"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE, "The source location is not remote"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE, "The configured rest source is not supported"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA, "Invalid data returned by rest source"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, "Operation is blocked by Group Policy"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR, "Rest API internal error"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL, "Invalid rest source url"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE, "Unsupported MIME type returned by rest API"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION, "Invalid rest source contract version"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE, "The source data is corrupted or tampered"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE, "Error reading from the stream"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED, "Package agreements were not agreed to"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR, "Error reading input in prompt"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST, "The search request is not supported by one or more sources"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND, "The rest API endpoint is not found."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED, "Failed to open the source."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED, "Source agreements were not agreed to"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH, "Header size exceeds the allowable limit of 1024 characters. Please reduce the size and try again."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE, "Missing resource file"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED, "Running MSI install failed"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT, "Arguments for msiexec are invalid"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES, "Failed to open one or more sources"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED, "Failed to validate dependencies"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE, "One or more package is missing"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN, "Invalid table column"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER, "The upgrade version is not newer than the installed version"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN, "Upgrade version is unknown and override is not specified"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR, "ICU conversion error"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED, "Failed to install portable package"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED, "Volume does not support reparse points"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS, "Portable package from a different source already exists."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY, "Unable to create symlink, path points to a directory."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION, "The installer cannot be run from an administrator context."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED, "Failed to uninstall portable package"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED, "Failed to validate DisplayVersion values against index."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT, "One or more arguments are not supported."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL, "Embedded null characters are disallowed for SQLite"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND, "Failed to find the nested installer in the archive."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED, "Failed to extract archive."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH, "Invalid relative file path to nested installer provided."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH, "The server certificate did not match any of the expected values."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED, "Install location must be provided."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED, "Archive malware scan failed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED, "Found at least one version of the package installed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS, "A pin already exists for the package."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST, "There is no pin for the package."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX, "Unable to open the pin database."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED, "One or more applications failed to install"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED, "One or more applications failed to uninstall"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE, "One or more queries did not return exactly one match"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED, "The package has a pin that prevents upgrade."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB, "The package currently installed is the stub package"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED, "Application shutdown signal received"), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES, "Failed to download package dependencies."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED, "Failed to download package. Download for offline installation is prohibited."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE, "A required service is busy or unavailable. Try again later."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND, "The guid provided does not correspond to a valid resume state."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH, "The current client version did not match the client version of the saved state."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE, "The resume state data is invalid."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX, "Unable to open the checkpoint database."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED, "Exceeded max resume limit."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO, "Invalid authentication info."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED, "Authentication method not supported."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED, "Authentication failed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED, "Authentication failed. Interactive authentication required."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER, "Authentication failed. User cancelled."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT, "Authentication failed. Authenticated account is not the desired account."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND, "Repair command not found."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE, "Repair operation is not applicable."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED, "Repair operation failed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED, "The installer technology in use doesn't support repair."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED, "Repair operations involving administrator privileges are not permitted on packages installed within the user scope."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED, "The SQLite connection was terminated to prevent corruption."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED, "Failed to get Microsoft Store package catalog."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE, "No applicable Microsoft Store package found from Microsoft Store package catalog."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED, "Failed to get Microsoft Store package download information."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE, "No applicable Microsoft Store package download information found."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED, "Failed to retrieve Microsoft Store package license."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED, "The Microsoft Store package does not support download."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN, "Failed to retrieve Microsoft Store package license. The Microsoft Entra Id account does not have the required privilege."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE, "Downloaded zero byte installer; ensure that your network connection is working properly."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED, "Failed installing one or more fonts."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED, "Font file is not supported and cannot be installed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED, "Font package is already installed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND, "Font file not found."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED, "Font uninstall failed. The font may not be in a good state. Try uninstalling after a restart."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED, "Font validation failed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED, "Font rollback failed. The font may not be in a good state. Try uninstalling after a restart."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH, "An upgrade is available but uses a different install technology than the current installation"), + + // Install errors. + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE, "Application is currently running. Exit the application then try again."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS, "Another installation is already in progress. Try again later."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE, "One or more file is being used. Exit the application then try again."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY, "This package has a dependency missing from your system."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL, "There's no more space on your PC. Make space, then try again."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY, "There's not enough memory available to install. Close other applications then try again."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK, "This application requires internet connectivity. Connect to a network then try again."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT, "This application encountered an error during installation. Contact support."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH, "Restart your PC to finish installation."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL, "Installation failed. Restart your PC then try again."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED, "Your PC will restart to finish installation."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER, "You cancelled the installation."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED, "Another version of this application is already installed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE, "A higher version of this application is already installed."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY, "Organization policies are preventing installation. Contact your admin."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES, "Failed to install package dependencies."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION, "Application is currently in use by another application."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER, "Invalid parameter."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED, "Package not supported by the system."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED, "The installer does not support upgrading an existing package."), + WINGET_HRESULT_INFO(APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR, "Installation failed with a custom installer error."), + + // Status values for check package installed status results. + // Partial success has the success bit(first bit) set to 0. + WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND, "The Apps and Features Entry for the package could not be found."), + WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND, "The install location could not be found."), + WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH, "The hash of the existing file did not match."), + WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_NOT_FOUND, "File not found."), + WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR, "The file could not be accessed."), + + // Configuration Errors + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE, "The configuration file is invalid."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_YAML, "The YAML syntax is invalid."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, "A configuration field has an invalid type."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, "The configuration has an unknown version."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_SET_APPLY_FAILED, "An error occurred while applying the configuration."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, "The configuration contains a duplicate identifier."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, "The configuration is missing a dependency."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, "The configuration has an unsatisfied dependency."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_ASSERTION_FAILED, "An assertion for the configuration unit failed."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_MANUALLY_SKIPPED, "The configuration was manually skipped."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED, "The user declined to continue execution."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE, "The dependency graph contains a cycle which cannot be resolved."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, "The configuration has an invalid field value."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_MISSING_FIELD, "The configuration is missing a field."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_TEST_FAILED, "Some of the configuration units failed while testing their state."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_TEST_NOT_RUN, "Configuration state was not tested."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_GET_FAILED, "The configuration unit failed getting its properties."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND, "The specified configuration could not be found."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY, "Parameter cannot be passed across integrity boundary."), + + // Configuration Processor Errors + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED, "The configuration unit was not installed."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY, "The configuration unit could not be found."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES, "Multiple matches were found for the configuration unit; specify the module to select the correct one."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_GET, "The configuration unit failed while attempting to get the current system state."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST, "The configuration unit failed while attempting to test the current system state."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_SET, "The configuration unit failed while attempting to apply the desired state."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT, "The module for the configuration unit is available in multiple locations with the same version."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE, "Loading the module for the configuration unit failed."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT, "The configuration unit returned an unexpected result during execution."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT, "A unit contains a setting that requires the config root."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN, "Loading the module for the configuration unit failed because it requires administrator privileges to run."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, "Operation is not supported by the configuration processor."), + WINGET_HRESULT_INFO(WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, "The DSC processor hash provided does not match hash of the target file."), + + // Errors without the error bit set + WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE, "The install location is not applicable."), + WINGET_HRESULT_INFO(WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK, "The file was found but the hash was not checked."), + }; + + // Map externally defined HRESULTs to error messages we want to use here + constexpr const HResultData s_externalHResultData[] = + { + // Changes to any of these errors require the corresponding resource string in winget.resw to be updated. + HResultData{ static_cast(0x803FB103), "StoreInstall_PackageNotAvailableForCurrentSystem", "The package is not compatible with the current Windows version or platform." }, + HResultData{ static_cast(0x803FB104), "StoreInstall_PackageNotAvailableForCurrentSystem", "The package is not compatible with the current Windows version or platform." }, + HResultData{ static_cast(0x803FB106), "StoreInstall_PackageNotAvailableForCurrentSystem", "The package is not compatible with the current Windows version or platform." }, + }; + + template + const HResultData* FindHResultData(HRESULT value, const HResultData (&dataArray)[ArraySize]) + { + auto itr = std::lower_bound(std::cbegin(dataArray), std::cend(dataArray), HResultData{ value }); + + if (itr != std::cend(dataArray) && itr->Value == value) + { + return itr; + } + + return nullptr; + } + + const HResultData* FindWinGetHResultData(HRESULT value) + { + return FindHResultData(value, s_wingetHResultData); + } + + const HResultData* FindExternalHResultData(HRESULT value) + { + return FindHResultData(value, s_externalHResultData); + } + + Utility::LocIndString GetMessageForAppInstallerHR(HRESULT hr) + { + const HResultData* data = FindWinGetHResultData(hr); + return data ? + WinGetHResultInformation(*data).GetDescription() : + UnknownHResultInformation(hr).GetDescription(); + } + + void GetUserPresentableMessageForHR(std::ostringstream& strstr, HRESULT hr) + { + strstr << "0x" << Logging::SetHRFormat << hr << " : "; + + if (HRESULT_FACILITY(hr) == APPINSTALLER_CLI_ERROR_FACILITY) + { + strstr << GetMessageForAppInstallerHR(hr); + } + else + { + const HResultData* data = FindExternalHResultData(hr); + + if (data) + { + strstr << WinGetHResultInformation(*data).GetDescription(); + } + else + { + strstr << std::system_category().message(hr); + } + } + } + } + + std::string GetUserPresentableMessage(const wil::ResultException& re) + { + const auto& info = re.GetFailureInfo(); + + std::ostringstream strstr; + + // We assume that if the exception has a message, that message is relevant to show to the user. + if (info.pszMessage) + { + strstr << Utility::ConvertToUTF8(info.pszMessage) << std::endl; + } + + GetUserPresentableMessageForHR(strstr, re.GetErrorCode()); + + return strstr.str(); + } + + std::string GetUserPresentableMessage(const std::exception& e) + { + return e.what(); + } + + std::string GetUserPresentableMessage(HRESULT hr) + { + std::ostringstream strstr; + GetUserPresentableMessageForHR(strstr, hr); + return strstr.str(); + } + +#ifndef WINGET_DISABLE_FOR_FUZZING + std::string GetUserPresentableMessage(const winrt::hresult_error& hre) + { + std::ostringstream strstr; + GetUserPresentableMessageForHR(strstr, hre.code()); + return strstr.str(); + } +#endif + + namespace Errors + { + constexpr HResultInformation::HResultInformation(HRESULT value) : + m_value(value) {} + + constexpr HResultInformation::HResultInformation(HRESULT value, std::string_view symbol) : + m_value(value), m_symbol(symbol) {} + + HRESULT HResultInformation::Value() const + { + return m_value; + } + + bool HResultInformation::operator<(const HResultInformation& other) const + { + return m_value < other.m_value; + } + + Utility::LocIndView HResultInformation::Symbol() const + { + return Utility::LocIndView{ m_symbol }; + } + + Utility::LocIndString HResultInformation::GetDescription() const + { + if (HRESULT_FACILITY(m_value) == APPINSTALLER_CLI_ERROR_FACILITY) + { + // In case external code attempts to construct HResultInformation directly + return GetMessageForAppInstallerHR(m_value); + } + else + { + return Utility::LocIndString{ std::system_category().message(m_value) }; + } + } + + std::unique_ptr HResultInformation::Find(HRESULT value) + { + if (HRESULT_FACILITY(value) == APPINSTALLER_CLI_ERROR_FACILITY) + { + const HResultData* data = FindWinGetHResultData(value); + + if (data) + { + return std::make_unique(*data); + } + else + { + return std::make_unique(value); + } + } + else + { + return std::make_unique(value); + } + } + + std::vector> HResultInformation::Find(std::string_view value) + { + std::vector> result; + + auto addToResultIf = [&](auto predicate) + { + for (const HResultData& data : s_wingetHResultData) + { + if (predicate(data) && + std::none_of(result.begin(), result.end(), [&](const std::unique_ptr& info) { return info->Value() == data.Value; })) + { + result.emplace_back(std::make_unique(data)); + } + } + }; + + addToResultIf([&](const HResultData& data) { return Utility::CaseInsensitiveEquals(data.Symbol, value); }); + addToResultIf([&](const HResultData& data) { return Utility::CaseInsensitiveContainsSubstring(data.Symbol, value); }); + addToResultIf([&](const HResultData& data) { return Utility::CaseInsensitiveContainsSubstring(data.Description, value); }); + + return result; + } + + std::vector> GetWinGetErrors() + { + std::vector> result; + result.reserve(ARRAYSIZE(s_wingetHResultData)); + + for (const HResultData& data : s_wingetHResultData) + { + result.emplace_back(std::make_unique(data)); + } + + return result; + } + } +} diff --git a/src/AppInstallerSharedLib/Filesystem.cpp b/src/AppInstallerSharedLib/Filesystem.cpp index 2e0e283359..85acbf09a3 100644 --- a/src/AppInstallerSharedLib/Filesystem.cpp +++ b/src/AppInstallerSharedLib/Filesystem.cpp @@ -1,828 +1,828 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/Filesystem.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerLogging.h" -#include "Public/winget/Runtime.h" - -using namespace std::chrono_literals; -using namespace std::string_view_literals; -using namespace AppInstaller::Runtime; - -namespace AppInstaller::Filesystem -{ - namespace anon - { - constexpr std::string_view s_AppDataDir_Settings = "Settings"sv; - constexpr std::string_view s_AppDataDir_State = "State"sv; - - constexpr std::string_view s_LocalAppDataEnvironmentVariable = "%LOCALAPPDATA%"; - - // Contains the information about an ACE entry for a given principal. - struct ACEDetails - { - ACEPrincipal Principal; - PSID SID; - TRUSTEE_TYPE TrusteeType; - }; - - constexpr BYTE s_InheritableAceFlags = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; - - struct PrincipalPermissions - { - DWORD DirectAccessMask = 0; - DWORD ObjectChildAccessMask = 0; - DWORD ContainerChildAccessMask = 0; - - bool operator==(const PrincipalPermissions& other) const - { - return DirectAccessMask == other.DirectAccessMask && - ObjectChildAccessMask == other.ObjectChildAccessMask && - ContainerChildAccessMask == other.ContainerChildAccessMask; - } - }; - - struct ExpectedACE - { - ACEPrincipal Principal; - PSID SID; - }; - - DWORD AccessPermissionsFrom(ACEPermissions permissions) - { - DWORD result = 0; - - if (permissions == ACEPermissions::All) - { - result |= GENERIC_ALL; - } - else - { - if (WI_IsFlagSet(permissions, ACEPermissions::Read)) - { - result |= GENERIC_READ; - } - - if (WI_IsFlagSet(permissions, ACEPermissions::Write)) - { - result |= GENERIC_WRITE | FILE_DELETE_CHILD; - } - - if (WI_IsFlagSet(permissions, ACEPermissions::Execute)) - { - result |= GENERIC_EXECUTE; - } - } - - return result; - } - - DWORD NormalizeAccessMask(DWORD accessMask) - { - GENERIC_MAPPING genericMapping - { - FILE_GENERIC_READ, - FILE_GENERIC_WRITE, - FILE_GENERIC_EXECUTE, - FILE_ALL_ACCESS, - }; - - MapGenericMask(&accessMask, &genericMapping); - return accessMask; - } - - std::map GetExpectedPermissions( - const PathDetails& details, - const ACEDetails(&aceDetails)[3], - const std::optional& principalToIgnore, - PSID& expectedOwnerSID) - { - std::map result; - - for (const auto& ace : aceDetails) - { - if (principalToIgnore && principalToIgnore.value() == ace.Principal) - { - continue; - } - - if (details.Owner && details.Owner.value() == ace.Principal) - { - expectedOwnerSID = ace.SID; - } - - auto itr = details.ACL.find(ace.Principal); - if (itr != details.ACL.end()) - { - DWORD normalizedAccessMask = NormalizeAccessMask(AccessPermissionsFrom(itr->second)); - result.emplace(ace.Principal, PrincipalPermissions - { - normalizedAccessMask, - normalizedAccessMask, - normalizedAccessMask, - }); - } - } - - return result; - } - - std::optional> GetActualPermissions( - PACL acl, - const std::vector& expectedAces) - { - constexpr BYTE s_AllowedAceFlags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE; - - ACL_SIZE_INFORMATION sizeInformation{}; - THROW_IF_WIN32_BOOL_FALSE(GetAclInformation(acl, &sizeInformation, sizeof(sizeInformation), AclSizeInformation)); - - std::map result; - - for (DWORD i = 0; i < sizeInformation.AceCount; ++i) - { - void* ace = nullptr; - THROW_IF_WIN32_BOOL_FALSE(GetAce(acl, i, &ace)); - - const ACE_HEADER* aceHeader = static_cast(ace); - if (aceHeader->AceType != ACCESS_ALLOWED_ACE_TYPE || (aceHeader->AceFlags & ~s_AllowedAceFlags) != 0) - { - return std::nullopt; - } - - const ACCESS_ALLOWED_ACE* accessAllowedAce = static_cast(ace); - PSID sid = reinterpret_cast(const_cast(&accessAllowedAce->SidStart)); - auto expectedAceItr = std::find_if(expectedAces.begin(), expectedAces.end(), - [&](const auto& expectedAce) - { - return EqualSid(expectedAce.SID, sid); - }); - if (expectedAceItr == expectedAces.end()) - { - return std::nullopt; - } - - if (WI_IsFlagSet(aceHeader->AceFlags, INHERIT_ONLY_ACE) && - (aceHeader->AceFlags & (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE)) == 0) - { - return std::nullopt; - } - - PrincipalPermissions& principalPermissions = result[expectedAceItr->Principal]; - DWORD normalizedAccessMask = NormalizeAccessMask(accessAllowedAce->Mask); - - if (!WI_IsFlagSet(aceHeader->AceFlags, INHERIT_ONLY_ACE)) - { - principalPermissions.DirectAccessMask |= normalizedAccessMask; - } - - if (WI_IsFlagSet(aceHeader->AceFlags, OBJECT_INHERIT_ACE)) - { - principalPermissions.ObjectChildAccessMask |= normalizedAccessMask; - } - - if (WI_IsFlagSet(aceHeader->AceFlags, CONTAINER_INHERIT_ACE)) - { - principalPermissions.ContainerChildAccessMask |= normalizedAccessMask; - } - } - - return result; - } - - std::optional GetPrincipalToIgnore(const PathDetails& details, const TOKEN_USER* userToken, PSID systemSID) - { - bool hasCurrentUser = details.ACL.count(ACEPrincipal::CurrentUser) != 0; - bool hasSystem = details.ACL.count(ACEPrincipal::System) != 0; - - if ((hasCurrentUser && hasSystem) && - IsRunningAsSystem() && - (!details.Owner || (details.Owner.value() != ACEPrincipal::CurrentUser && details.Owner.value() != ACEPrincipal::System))) - { - THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - - if (hasCurrentUser && hasSystem && EqualSid(userToken->User.Sid, systemSID)) - { - return (details.Owner.value() == ACEPrincipal::CurrentUser ? ACEPrincipal::System : ACEPrincipal::CurrentUser); - } - - return std::nullopt; - } - - bool PathHasExpectedOwnerAndACLs(const PathDetails& details) - { - auto userToken = wil::get_token_information(); - auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); - auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); - auto principalToIgnore = GetPrincipalToIgnore(details, userToken.get(), systemSID.get()); - - ACEDetails aceDetails[] = - { - { ACEPrincipal::CurrentUser, userToken->User.Sid, TRUSTEE_IS_USER }, - { ACEPrincipal::Admins, adminSID.get(), TRUSTEE_IS_WELL_KNOWN_GROUP }, - { ACEPrincipal::System, systemSID.get(), TRUSTEE_IS_USER }, - }; - - PSID expectedOwnerSID = nullptr; - std::map expectedPermissions = GetExpectedPermissions(details, aceDetails, principalToIgnore, expectedOwnerSID); - - SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION; - PSID ownerSID = nullptr; - - if (details.Owner) - { - securityInformation |= OWNER_SECURITY_INFORMATION; - ownerSID = expectedOwnerSID; - } - - std::wstring path = details.Path.wstring(); - wil::unique_hlocal_security_descriptor securityDescriptor; - PSID actualOwnerSID = nullptr; - DWORD result = GetNamedSecurityInfoW( - &path[0], - SE_FILE_OBJECT, - securityInformation, - details.Owner ? &actualOwnerSID : nullptr, - nullptr, - nullptr, - nullptr, - &securityDescriptor); - - if (result != ERROR_SUCCESS) - { - return false; - } - - SECURITY_DESCRIPTOR_CONTROL control = 0; - DWORD revision = 0; - if (!GetSecurityDescriptorControl(securityDescriptor.get(), &control, &revision) || !WI_IsFlagSet(control, SE_DACL_PROTECTED)) - { - return false; - } - - BOOL daclPresent = FALSE; - BOOL daclDefaulted = FALSE; - PACL currentDacl = nullptr; - if (!GetSecurityDescriptorDacl(securityDescriptor.get(), &daclPresent, ¤tDacl, &daclDefaulted) || !daclPresent || !currentDacl) - { - return false; - } - - if (ownerSID && (!actualOwnerSID || !EqualSid(actualOwnerSID, ownerSID))) - { - return false; - } - - std::vector expectedAces; - for (const auto& ace : aceDetails) - { - if (principalToIgnore && principalToIgnore.value() == ace.Principal) - { - continue; - } - - if (details.ACL.count(ace.Principal) != 0) - { - expectedAces.push_back({ ace.Principal, ace.SID }); - } - } - - auto actualPermissions = GetActualPermissions(currentDacl, expectedAces); - return actualPermissions && actualPermissions.value() == expectedPermissions; - } - - // Gets the path to the appdata root. - // *Only used by non packaged version!* - std::filesystem::path GetPathToAppDataRoot(bool anonymize) - { - std::filesystem::path result = anonymize ? s_LocalAppDataEnvironmentVariable : GetKnownFolderPath(FOLDERID_LocalAppData); - result /= "Microsoft/WinGet"; - - return result; - } - - // Gets the path to the app data relative directory. - std::filesystem::path GetPathToAppDataDir(const std::filesystem::path& relative, bool anonymize) - { - THROW_HR_IF(E_INVALIDARG, !relative.has_relative_path()); - THROW_HR_IF(E_INVALIDARG, relative.has_root_path()); - THROW_HR_IF(E_INVALIDARG, !relative.has_filename()); - - std::filesystem::path result = GetPathToAppDataRoot(anonymize); - result /= relative; - - return result; - } - } - - DWORD GetVolumeInformationFlagsByHandle(HANDLE anyFileHandle) - { - DWORD flags = 0; - wchar_t fileSystemName[MAX_PATH]; - THROW_LAST_ERROR_IF(!GetVolumeInformationByHandleW( - anyFileHandle, /*hFile*/ - NULL, /*lpVolumeNameBuffer*/ - 0, /*nVolumeNameSize*/ - NULL, /*lpVolumeSerialNumber*/ - NULL, /*lpMaximumComponentLength*/ - &flags, /*lpFileSystemFlags*/ - fileSystemName, /*lpFileSystemNameBuffer*/ - MAX_PATH /*nFileSystemNameSize*/)); - - // Vista and older does not report all flags, fix them up here - if (!(flags & FILE_SUPPORTS_HARD_LINKS) && !_wcsicmp(fileSystemName, L"NTFS")) - { - flags |= FILE_SUPPORTS_HARD_LINKS | FILE_SUPPORTS_EXTENDED_ATTRIBUTES | FILE_SUPPORTS_OPEN_BY_FILE_ID | FILE_SUPPORTS_USN_JOURNAL; - } - - return flags; - } - - DWORD GetVolumeInformationFlags(const std::filesystem::path& anyPath) - { - wil::unique_hfile fileHandle{ CreateFileW( - anyPath.c_str(), /*lpFileName*/ - 0, /*dwDesiredAccess*/ - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /*dwShareMode*/ - NULL, /*lpSecurityAttributes*/ - OPEN_EXISTING, /*dwCreationDisposition*/ - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, /*dwFlagsAndAttributes*/ - NULL /*hTemplateFile*/) }; - - THROW_LAST_ERROR_IF(fileHandle.get() == INVALID_HANDLE_VALUE); - - return GetVolumeInformationFlagsByHandle(fileHandle.get()); - } - - bool SupportsNamedStreams(const std::filesystem::path& path) - { - return (GetVolumeInformationFlags(path) & FILE_NAMED_STREAMS) != 0; - } - - bool SupportsHardLinks(const std::filesystem::path& path) - { - return (GetVolumeInformationFlags(path) & FILE_SUPPORTS_HARD_LINKS) != 0; - } - - bool SupportsReparsePoints(const std::filesystem::path& path) - { - return (GetVolumeInformationFlags(path) & FILE_SUPPORTS_REPARSE_POINTS) != 0; - } - - bool PathEscapesBaseDirectory(std::string_view relativePath) - { - // Normalize the path, then check if the first part is ".." - auto resolvedPath = std::filesystem::path{ relativePath }.lexically_normal(); - return !resolvedPath.empty() && *resolvedPath.begin() == ".."; - } - - // Complicated rename algorithm due to somewhat arbitrary failures. - // 1. First, try to rename. - // 2. Then, create an empty file for the target, and attempt to rename. - // 3. Then, try repeatedly for 500ms in case it is a timing thing. - // 4. Attempt to use a hard link if available. - // 5. Copy the file if nothing else has worked so far. - void RenameFile(const std::filesystem::path& from, const std::filesystem::path& to) - { - // 1. First, try to rename. - try - { - // std::filesystem::rename() handles motw correctly if applicable. - std::filesystem::rename(from, to); - return; - } - CATCH_LOG(); - - // 2. Then, create an empty file for the target, and attempt to rename. - // This seems to fix things in certain cases, so we do it. - try - { - { - std::ofstream targetFile{ to }; - } - std::filesystem::rename(from, to); - return; - } - CATCH_LOG(); - - // 3. Then, try repeatedly for 500ms in case it is a timing thing. - for (int i = 0; i < 5; ++i) - { - try - { - std::this_thread::sleep_for(100ms); - std::filesystem::rename(from, to); - return; - } - CATCH_LOG(); - } - - // 4. Attempt to use a hard link if available. - if (SupportsHardLinks(from)) - { - try - { - // Create a hard link to the file; the installer will be left in the temp directory afterward - // but it is better to succeed the operation and leave a file around than to fail. - // First we have to remove the target file as the function will not overwrite. - std::filesystem::remove(to); - std::filesystem::create_hard_link(from, to); - return; - } - CATCH_LOG(); - } - - // 5. Copy the file if nothing else has worked so far. - // Create a copy of the file; the installer will be left in the temp directory afterward - // but it is better to succeed the operation and leave a file around than to fail. - std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - static bool* s_CreateSymlinkResult_TestHook_Override = nullptr; - - void TestHook_SetCreateSymlinkResult_Override(bool* status) - { - s_CreateSymlinkResult_TestHook_Override = status; - } -#endif - - bool CreateSymlink(const std::filesystem::path& target, const std::filesystem::path& link) - { -#ifndef AICLI_DISABLE_TEST_HOOKS - if (s_CreateSymlinkResult_TestHook_Override) - { - return *s_CreateSymlinkResult_TestHook_Override; - } -#endif - try - { - std::filesystem::create_symlink(target, link); - return true; - } - catch (std::filesystem::filesystem_error& error) - { - if (error.code().value() == ERROR_PRIVILEGE_NOT_HELD) - { - return false; - } - else - { - throw; - } - } - } - - bool VerifySymlink(const std::filesystem::path& symlink, const std::filesystem::path& target) - { - // Use read_symlink to get the symlink's recorded target without traversing the filesystem - // chain. weakly_canonical would follow the symlink, which is blocked by - // ProcessRedirectionTrustPolicy (inherited from the MSIX packaged process context on - // newer Windows builds) when the symlink was created by a non-elevated process. - const std::filesystem::path symlinkTarget = std::filesystem::read_symlink(symlink); - - // If the recorded target is relative, resolve it against the symlink's parent directory. - const std::filesystem::path resolvedTarget = symlinkTarget.is_absolute() ? - symlinkTarget : (symlink.parent_path() / symlinkTarget); - - // Windows paths are case-insensitive. Use lexically_normal to resolve . and .. without - // filesystem access, then compare case-insensitively. - return Utility::ICUCaseInsensitiveEquals( - resolvedTarget.lexically_normal().u8string(), - target.lexically_normal().u8string()); - } - - void AppendExtension(std::filesystem::path& target, const std::string& value) - { - if (target.extension() != value) - { - target += value; - } - } - - bool SymlinkExists(const std::filesystem::path& symlinkPath) - { - return std::filesystem::is_symlink(std::filesystem::symlink_status(symlinkPath)); - } - - std::filesystem::path GetExpandedPath(const std::string& path) - { - std::string trimPath = path; - Utility::Trim(trimPath); - - try - { - return std::filesystem::weakly_canonical(Utility::ExpandEnvironmentVariables(Utility::ConvertToUTF16(trimPath))); - } - catch (...) - { - return Utility::ConvertToUTF16(path); - } - } - - bool ReplaceCommonPathPrefix(std::filesystem::path& source, const std::filesystem::path& prefix, std::string_view replacement) - { - auto prefixItr = prefix.begin(); - auto sourceItr = source.begin(); - - while (prefixItr != prefix.end() && sourceItr != source.end()) - { - if (!Utility::ICUCaseInsensitiveEquals(prefixItr->u8string(), sourceItr->u8string())) - { - break; - } - - ++prefixItr; - ++sourceItr; - } - - // Only replace source if we found all of prefix - if (prefixItr == prefix.end()) - { - std::filesystem::path temp{ replacement }; - - for (; sourceItr != source.end(); ++sourceItr) - { - temp /= *sourceItr; - } - - source = std::move(temp); - - return true; - } - - return false; - } - - std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) - { - wil::unique_cotaskmem_string knownFolder = nullptr; - THROW_IF_FAILED(SHGetKnownFolderPath(id, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_PACKAGE_REDIRECTION, NULL, &knownFolder)); - return knownFolder.get(); - } - - bool IsSameVolume(const std::filesystem::path& path1, const std::filesystem::path& path2) - { - WCHAR volumeName1[MAX_PATH]; - WCHAR volumeName2[MAX_PATH]; - - // Note: GetVolumePathNameW will return false if the volume drive does not exist. - if (!GetVolumePathNameW(path1.c_str(), volumeName1, MAX_PATH) || !GetVolumePathNameW(path2.c_str(), volumeName2, MAX_PATH)) - { - return false; - } - return Utility::ICUCaseInsensitiveEquals(Utility::ConvertToUTF8(volumeName1), Utility::ConvertToUTF8(volumeName2)); - } - - bool IsParentPath(const std::filesystem::path& path, const std::filesystem::path& parentPath) - { - return std::filesystem::weakly_canonical(path.parent_path()) == std::filesystem::weakly_canonical(parentPath); - } - - void PathDetails::SetOwner(ACEPrincipal owner) - { - Owner = owner; - ACL[owner] = ACEPermissions::All; - } - - bool PathDetails::ShouldApplyACL() const - { - if (ACL.empty()) - { - return false; - } - - try - { - return !anon::PathHasExpectedOwnerAndACLs(*this); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION_MSG("Failed to inspect ACL state; reapplying ACLs to preserve security"); - return true; - } - } - - void PathDetails::ApplyACL() const - { - auto userToken = wil::get_token_information(); - auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); - auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); - PSID ownerSID = nullptr; - - anon::ACEDetails aceDetails[] = - { - { ACEPrincipal::CurrentUser, userToken->User.Sid, TRUSTEE_IS_USER }, - { ACEPrincipal::Admins, adminSID.get(), TRUSTEE_IS_WELL_KNOWN_GROUP}, - { ACEPrincipal::System, systemSID.get(), TRUSTEE_IS_USER}, - }; - - ULONG entriesCount = 0; - std::array explicitAccess; - - // If the current user is SYSTEM, we want to take either the owner or the only configured set of permissions. - // The check above should prevent us from getting into situations outside of the ones below. - std::optional principalToIgnore = anon::GetPrincipalToIgnore(*this, userToken.get(), systemSID.get()); - - for (const auto& ace : aceDetails) - { - if (principalToIgnore && principalToIgnore.value() == ace.Principal) - { - continue; - } - - if (Owner && Owner.value() == ace.Principal) - { - ownerSID = ace.SID; - } - - auto itr = ACL.find(ace.Principal); - if (itr != ACL.end()) - { - EXPLICIT_ACCESS_W& entry = explicitAccess[entriesCount++]; - entry = {}; - - entry.grfAccessPermissions = anon::AccessPermissionsFrom(itr->second); - entry.grfAccessMode = SET_ACCESS; - entry.grfInheritance = anon::s_InheritableAceFlags; - - entry.Trustee.pMultipleTrustee = nullptr; - entry.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; - entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; - entry.Trustee.TrusteeType = ace.TrusteeType; - entry.Trustee.ptstrName = reinterpret_cast(ace.SID); - } - } - - wil::unique_any acl; - THROW_IF_WIN32_ERROR(SetEntriesInAclW(entriesCount, explicitAccess.data(), nullptr, &acl)); - - std::wstring path = Path.wstring(); - SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION; - - if (ownerSID) - { - securityInformation |= OWNER_SECURITY_INFORMATION; - } - - DWORD result = SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation, ownerSID, nullptr, acl.get(), nullptr); - - // We can be denied access attempting to set the owner when the owner is already correct. - // Determine if the owner is correct; if so, try again without attempting to set the owner. - if (result == ERROR_ACCESS_DENIED && ownerSID) - { - wil::unique_hlocal_security_descriptor securityDescriptor; - PSID currentOwnerSID = nullptr; - DWORD getResult = GetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, ¤tOwnerSID, nullptr, nullptr, nullptr, &securityDescriptor); - - if (SUCCEEDED_WIN32_LOG(getResult) && currentOwnerSID && EqualSid(currentOwnerSID, ownerSID)) - { - result = SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation & ~OWNER_SECURITY_INFORMATION, nullptr, nullptr, acl.get(), nullptr); - } - } - - THROW_IF_WIN32_ERROR(result); - } - - std::filesystem::path InitializeAndGetPathTo(PathDetails&& details) - { - if (details.Create) - { - if (details.Path.is_absolute()) - { - if (std::filesystem::exists(details.Path) && !std::filesystem::is_directory(details.Path)) - { - std::filesystem::remove(details.Path); - } - - std::filesystem::create_directories(details.Path); - - // Set the ACLs on the directory if needed. We do this after creating the directory because an attacker could - // have created the directory beforehand so we must be able to place the correct ACL on any directory or fail - // to operate. - if (details.ShouldApplyACL()) - { - details.ApplyACL(); - } - } - else - { - AICLI_LOG(Core, Warning, << "InitializeAndGetPathTo directory creation requested for path that was not absolute: " << details.Path); - } - } - - return std::move(details.Path); - } - - PathDetails GetPathDetailsFor(PathName path, bool forDisplay) - { - PathDetails result; - // We should not create directories by default when they are retrieved for display purposes. - result.Create = !forDisplay; - - switch (path) - { - case PathName::UnpackagedLocalStateRoot: - result.Path = anon::GetPathToAppDataDir(anon::s_AppDataDir_State, forDisplay); - result.SetOwner(ACEPrincipal::CurrentUser); - result.ACL[ACEPrincipal::System] = ACEPermissions::All; - result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - break; - case PathName::UnpackagedSettingsRoot: - result.Path = anon::GetPathToAppDataDir(anon::s_AppDataDir_Settings, forDisplay); - result.SetOwner(ACEPrincipal::CurrentUser); - result.ACL[ACEPrincipal::System] = ACEPermissions::All; - result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return result; - } - - std::filesystem::path GetExecutablePathForProcess(HANDLE process) - { - wil::unique_cotaskmem_string imageName = nullptr; - if (SUCCEEDED(wil::QueryFullProcessImageNameW(process, 0, imageName)) && - (imageName.get() != nullptr)) - { - return imageName.get(); - } - - return {}; - } - - std::vector GetFileInfoFor(const std::filesystem::path& directory) - { - std::vector result; - - for (const auto& file : std::filesystem::directory_iterator{ directory }) - { - if (file.is_regular_file()) - { - result.emplace_back(FileInfo{ file.path(), file.last_write_time(), file.file_size() }); - } - } - - return result; - } - - void FilterToFilesExceedingLimits(std::vector& files, const FileLimits& limits) - { - auto now = std::filesystem::file_time_type::clock::now(); - std::chrono::hours ageLimit = limits.Age; - static_assert(sizeof(uintmax_t) >= 8); - uintmax_t totalSizeLimit = static_cast(limits.TotalSizeInMB) << 20; - size_t countLimit = limits.Count; - - // Sort with oldest first so that we can work backward to find the cutoff - std::sort(files.begin(), files.end(), [](const FileInfo& a, const FileInfo& b) { return a.LastWriteTime < b.LastWriteTime; }); - - // Walk the list backward until we find the first entry that goes over one of the limits - size_t i = files.size(); - uintmax_t totalSize = 0; - for (; i > 0; --i) - { - const FileInfo& current = files[i - 1]; - - if (totalSizeLimit != 0) - { - totalSize += current.Size; - if (totalSize > totalSizeLimit) - { - break; - } - } - - if (countLimit != 0 && (files.size() - i + 1) > countLimit) - { - break; - } - - if (ageLimit != 0h && now - current.LastWriteTime > ageLimit) - { - break; - } - } - - files.resize(i); - } - - void WriteStringToFile(HANDLE fileHandle, std::string_view content) - { - size_t totalBytesWritten = 0; - while (totalBytesWritten < content.size()) - { - DWORD bytesWritten = 0; - THROW_LAST_ERROR_IF(!WriteFile( - fileHandle, - content.data() + totalBytesWritten, - static_cast(content.size() - totalBytesWritten), - &bytesWritten, - nullptr)); - totalBytesWritten += bytesWritten; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Filesystem.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerLogging.h" +#include "Public/winget/Runtime.h" + +using namespace std::chrono_literals; +using namespace std::string_view_literals; +using namespace AppInstaller::Runtime; + +namespace AppInstaller::Filesystem +{ + namespace anon + { + constexpr std::string_view s_AppDataDir_Settings = "Settings"sv; + constexpr std::string_view s_AppDataDir_State = "State"sv; + + constexpr std::string_view s_LocalAppDataEnvironmentVariable = "%LOCALAPPDATA%"; + + // Contains the information about an ACE entry for a given principal. + struct ACEDetails + { + ACEPrincipal Principal; + PSID SID; + TRUSTEE_TYPE TrusteeType; + }; + + constexpr BYTE s_InheritableAceFlags = CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE; + + struct PrincipalPermissions + { + DWORD DirectAccessMask = 0; + DWORD ObjectChildAccessMask = 0; + DWORD ContainerChildAccessMask = 0; + + bool operator==(const PrincipalPermissions& other) const + { + return DirectAccessMask == other.DirectAccessMask && + ObjectChildAccessMask == other.ObjectChildAccessMask && + ContainerChildAccessMask == other.ContainerChildAccessMask; + } + }; + + struct ExpectedACE + { + ACEPrincipal Principal; + PSID SID; + }; + + DWORD AccessPermissionsFrom(ACEPermissions permissions) + { + DWORD result = 0; + + if (permissions == ACEPermissions::All) + { + result |= GENERIC_ALL; + } + else + { + if (WI_IsFlagSet(permissions, ACEPermissions::Read)) + { + result |= GENERIC_READ; + } + + if (WI_IsFlagSet(permissions, ACEPermissions::Write)) + { + result |= GENERIC_WRITE | FILE_DELETE_CHILD; + } + + if (WI_IsFlagSet(permissions, ACEPermissions::Execute)) + { + result |= GENERIC_EXECUTE; + } + } + + return result; + } + + DWORD NormalizeAccessMask(DWORD accessMask) + { + GENERIC_MAPPING genericMapping + { + FILE_GENERIC_READ, + FILE_GENERIC_WRITE, + FILE_GENERIC_EXECUTE, + FILE_ALL_ACCESS, + }; + + MapGenericMask(&accessMask, &genericMapping); + return accessMask; + } + + std::map GetExpectedPermissions( + const PathDetails& details, + const ACEDetails(&aceDetails)[3], + const std::optional& principalToIgnore, + PSID& expectedOwnerSID) + { + std::map result; + + for (const auto& ace : aceDetails) + { + if (principalToIgnore && principalToIgnore.value() == ace.Principal) + { + continue; + } + + if (details.Owner && details.Owner.value() == ace.Principal) + { + expectedOwnerSID = ace.SID; + } + + auto itr = details.ACL.find(ace.Principal); + if (itr != details.ACL.end()) + { + DWORD normalizedAccessMask = NormalizeAccessMask(AccessPermissionsFrom(itr->second)); + result.emplace(ace.Principal, PrincipalPermissions + { + normalizedAccessMask, + normalizedAccessMask, + normalizedAccessMask, + }); + } + } + + return result; + } + + std::optional> GetActualPermissions( + PACL acl, + const std::vector& expectedAces) + { + constexpr BYTE s_AllowedAceFlags = OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE; + + ACL_SIZE_INFORMATION sizeInformation{}; + THROW_IF_WIN32_BOOL_FALSE(GetAclInformation(acl, &sizeInformation, sizeof(sizeInformation), AclSizeInformation)); + + std::map result; + + for (DWORD i = 0; i < sizeInformation.AceCount; ++i) + { + void* ace = nullptr; + THROW_IF_WIN32_BOOL_FALSE(GetAce(acl, i, &ace)); + + const ACE_HEADER* aceHeader = static_cast(ace); + if (aceHeader->AceType != ACCESS_ALLOWED_ACE_TYPE || (aceHeader->AceFlags & ~s_AllowedAceFlags) != 0) + { + return std::nullopt; + } + + const ACCESS_ALLOWED_ACE* accessAllowedAce = static_cast(ace); + PSID sid = reinterpret_cast(const_cast(&accessAllowedAce->SidStart)); + auto expectedAceItr = std::find_if(expectedAces.begin(), expectedAces.end(), + [&](const auto& expectedAce) + { + return EqualSid(expectedAce.SID, sid); + }); + if (expectedAceItr == expectedAces.end()) + { + return std::nullopt; + } + + if (WI_IsFlagSet(aceHeader->AceFlags, INHERIT_ONLY_ACE) && + (aceHeader->AceFlags & (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE)) == 0) + { + return std::nullopt; + } + + PrincipalPermissions& principalPermissions = result[expectedAceItr->Principal]; + DWORD normalizedAccessMask = NormalizeAccessMask(accessAllowedAce->Mask); + + if (!WI_IsFlagSet(aceHeader->AceFlags, INHERIT_ONLY_ACE)) + { + principalPermissions.DirectAccessMask |= normalizedAccessMask; + } + + if (WI_IsFlagSet(aceHeader->AceFlags, OBJECT_INHERIT_ACE)) + { + principalPermissions.ObjectChildAccessMask |= normalizedAccessMask; + } + + if (WI_IsFlagSet(aceHeader->AceFlags, CONTAINER_INHERIT_ACE)) + { + principalPermissions.ContainerChildAccessMask |= normalizedAccessMask; + } + } + + return result; + } + + std::optional GetPrincipalToIgnore(const PathDetails& details, const TOKEN_USER* userToken, PSID systemSID) + { + bool hasCurrentUser = details.ACL.count(ACEPrincipal::CurrentUser) != 0; + bool hasSystem = details.ACL.count(ACEPrincipal::System) != 0; + + if ((hasCurrentUser && hasSystem) && + IsRunningAsSystem() && + (!details.Owner || (details.Owner.value() != ACEPrincipal::CurrentUser && details.Owner.value() != ACEPrincipal::System))) + { + THROW_HR(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + if (hasCurrentUser && hasSystem && EqualSid(userToken->User.Sid, systemSID)) + { + return (details.Owner.value() == ACEPrincipal::CurrentUser ? ACEPrincipal::System : ACEPrincipal::CurrentUser); + } + + return std::nullopt; + } + + bool PathHasExpectedOwnerAndACLs(const PathDetails& details) + { + auto userToken = wil::get_token_information(); + auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); + auto principalToIgnore = GetPrincipalToIgnore(details, userToken.get(), systemSID.get()); + + ACEDetails aceDetails[] = + { + { ACEPrincipal::CurrentUser, userToken->User.Sid, TRUSTEE_IS_USER }, + { ACEPrincipal::Admins, adminSID.get(), TRUSTEE_IS_WELL_KNOWN_GROUP }, + { ACEPrincipal::System, systemSID.get(), TRUSTEE_IS_USER }, + }; + + PSID expectedOwnerSID = nullptr; + std::map expectedPermissions = GetExpectedPermissions(details, aceDetails, principalToIgnore, expectedOwnerSID); + + SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION; + PSID ownerSID = nullptr; + + if (details.Owner) + { + securityInformation |= OWNER_SECURITY_INFORMATION; + ownerSID = expectedOwnerSID; + } + + std::wstring path = details.Path.wstring(); + wil::unique_hlocal_security_descriptor securityDescriptor; + PSID actualOwnerSID = nullptr; + DWORD result = GetNamedSecurityInfoW( + &path[0], + SE_FILE_OBJECT, + securityInformation, + details.Owner ? &actualOwnerSID : nullptr, + nullptr, + nullptr, + nullptr, + &securityDescriptor); + + if (result != ERROR_SUCCESS) + { + return false; + } + + SECURITY_DESCRIPTOR_CONTROL control = 0; + DWORD revision = 0; + if (!GetSecurityDescriptorControl(securityDescriptor.get(), &control, &revision) || !WI_IsFlagSet(control, SE_DACL_PROTECTED)) + { + return false; + } + + BOOL daclPresent = FALSE; + BOOL daclDefaulted = FALSE; + PACL currentDacl = nullptr; + if (!GetSecurityDescriptorDacl(securityDescriptor.get(), &daclPresent, ¤tDacl, &daclDefaulted) || !daclPresent || !currentDacl) + { + return false; + } + + if (ownerSID && (!actualOwnerSID || !EqualSid(actualOwnerSID, ownerSID))) + { + return false; + } + + std::vector expectedAces; + for (const auto& ace : aceDetails) + { + if (principalToIgnore && principalToIgnore.value() == ace.Principal) + { + continue; + } + + if (details.ACL.count(ace.Principal) != 0) + { + expectedAces.push_back({ ace.Principal, ace.SID }); + } + } + + auto actualPermissions = GetActualPermissions(currentDacl, expectedAces); + return actualPermissions && actualPermissions.value() == expectedPermissions; + } + + // Gets the path to the appdata root. + // *Only used by non packaged version!* + std::filesystem::path GetPathToAppDataRoot(bool anonymize) + { + std::filesystem::path result = anonymize ? s_LocalAppDataEnvironmentVariable : GetKnownFolderPath(FOLDERID_LocalAppData); + result /= "Microsoft/WinGet"; + + return result; + } + + // Gets the path to the app data relative directory. + std::filesystem::path GetPathToAppDataDir(const std::filesystem::path& relative, bool anonymize) + { + THROW_HR_IF(E_INVALIDARG, !relative.has_relative_path()); + THROW_HR_IF(E_INVALIDARG, relative.has_root_path()); + THROW_HR_IF(E_INVALIDARG, !relative.has_filename()); + + std::filesystem::path result = GetPathToAppDataRoot(anonymize); + result /= relative; + + return result; + } + } + + DWORD GetVolumeInformationFlagsByHandle(HANDLE anyFileHandle) + { + DWORD flags = 0; + wchar_t fileSystemName[MAX_PATH]; + THROW_LAST_ERROR_IF(!GetVolumeInformationByHandleW( + anyFileHandle, /*hFile*/ + NULL, /*lpVolumeNameBuffer*/ + 0, /*nVolumeNameSize*/ + NULL, /*lpVolumeSerialNumber*/ + NULL, /*lpMaximumComponentLength*/ + &flags, /*lpFileSystemFlags*/ + fileSystemName, /*lpFileSystemNameBuffer*/ + MAX_PATH /*nFileSystemNameSize*/)); + + // Vista and older does not report all flags, fix them up here + if (!(flags & FILE_SUPPORTS_HARD_LINKS) && !_wcsicmp(fileSystemName, L"NTFS")) + { + flags |= FILE_SUPPORTS_HARD_LINKS | FILE_SUPPORTS_EXTENDED_ATTRIBUTES | FILE_SUPPORTS_OPEN_BY_FILE_ID | FILE_SUPPORTS_USN_JOURNAL; + } + + return flags; + } + + DWORD GetVolumeInformationFlags(const std::filesystem::path& anyPath) + { + wil::unique_hfile fileHandle{ CreateFileW( + anyPath.c_str(), /*lpFileName*/ + 0, /*dwDesiredAccess*/ + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, /*dwShareMode*/ + NULL, /*lpSecurityAttributes*/ + OPEN_EXISTING, /*dwCreationDisposition*/ + FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, /*dwFlagsAndAttributes*/ + NULL /*hTemplateFile*/) }; + + THROW_LAST_ERROR_IF(fileHandle.get() == INVALID_HANDLE_VALUE); + + return GetVolumeInformationFlagsByHandle(fileHandle.get()); + } + + bool SupportsNamedStreams(const std::filesystem::path& path) + { + return (GetVolumeInformationFlags(path) & FILE_NAMED_STREAMS) != 0; + } + + bool SupportsHardLinks(const std::filesystem::path& path) + { + return (GetVolumeInformationFlags(path) & FILE_SUPPORTS_HARD_LINKS) != 0; + } + + bool SupportsReparsePoints(const std::filesystem::path& path) + { + return (GetVolumeInformationFlags(path) & FILE_SUPPORTS_REPARSE_POINTS) != 0; + } + + bool PathEscapesBaseDirectory(std::string_view relativePath) + { + // Normalize the path, then check if the first part is ".." + auto resolvedPath = std::filesystem::path{ relativePath }.lexically_normal(); + return !resolvedPath.empty() && *resolvedPath.begin() == ".."; + } + + // Complicated rename algorithm due to somewhat arbitrary failures. + // 1. First, try to rename. + // 2. Then, create an empty file for the target, and attempt to rename. + // 3. Then, try repeatedly for 500ms in case it is a timing thing. + // 4. Attempt to use a hard link if available. + // 5. Copy the file if nothing else has worked so far. + void RenameFile(const std::filesystem::path& from, const std::filesystem::path& to) + { + // 1. First, try to rename. + try + { + // std::filesystem::rename() handles motw correctly if applicable. + std::filesystem::rename(from, to); + return; + } + CATCH_LOG(); + + // 2. Then, create an empty file for the target, and attempt to rename. + // This seems to fix things in certain cases, so we do it. + try + { + { + std::ofstream targetFile{ to }; + } + std::filesystem::rename(from, to); + return; + } + CATCH_LOG(); + + // 3. Then, try repeatedly for 500ms in case it is a timing thing. + for (int i = 0; i < 5; ++i) + { + try + { + std::this_thread::sleep_for(100ms); + std::filesystem::rename(from, to); + return; + } + CATCH_LOG(); + } + + // 4. Attempt to use a hard link if available. + if (SupportsHardLinks(from)) + { + try + { + // Create a hard link to the file; the installer will be left in the temp directory afterward + // but it is better to succeed the operation and leave a file around than to fail. + // First we have to remove the target file as the function will not overwrite. + std::filesystem::remove(to); + std::filesystem::create_hard_link(from, to); + return; + } + CATCH_LOG(); + } + + // 5. Copy the file if nothing else has worked so far. + // Create a copy of the file; the installer will be left in the temp directory afterward + // but it is better to succeed the operation and leave a file around than to fail. + std::filesystem::copy_file(from, to, std::filesystem::copy_options::overwrite_existing); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + static bool* s_CreateSymlinkResult_TestHook_Override = nullptr; + + void TestHook_SetCreateSymlinkResult_Override(bool* status) + { + s_CreateSymlinkResult_TestHook_Override = status; + } +#endif + + bool CreateSymlink(const std::filesystem::path& target, const std::filesystem::path& link) + { +#ifndef AICLI_DISABLE_TEST_HOOKS + if (s_CreateSymlinkResult_TestHook_Override) + { + return *s_CreateSymlinkResult_TestHook_Override; + } +#endif + try + { + std::filesystem::create_symlink(target, link); + return true; + } + catch (std::filesystem::filesystem_error& error) + { + if (error.code().value() == ERROR_PRIVILEGE_NOT_HELD) + { + return false; + } + else + { + throw; + } + } + } + + bool VerifySymlink(const std::filesystem::path& symlink, const std::filesystem::path& target) + { + // Use read_symlink to get the symlink's recorded target without traversing the filesystem + // chain. weakly_canonical would follow the symlink, which is blocked by + // ProcessRedirectionTrustPolicy (inherited from the MSIX packaged process context on + // newer Windows builds) when the symlink was created by a non-elevated process. + const std::filesystem::path symlinkTarget = std::filesystem::read_symlink(symlink); + + // If the recorded target is relative, resolve it against the symlink's parent directory. + const std::filesystem::path resolvedTarget = symlinkTarget.is_absolute() ? + symlinkTarget : (symlink.parent_path() / symlinkTarget); + + // Windows paths are case-insensitive. Use lexically_normal to resolve . and .. without + // filesystem access, then compare case-insensitively. + return Utility::ICUCaseInsensitiveEquals( + resolvedTarget.lexically_normal().u8string(), + target.lexically_normal().u8string()); + } + + void AppendExtension(std::filesystem::path& target, const std::string& value) + { + if (target.extension() != value) + { + target += value; + } + } + + bool SymlinkExists(const std::filesystem::path& symlinkPath) + { + return std::filesystem::is_symlink(std::filesystem::symlink_status(symlinkPath)); + } + + std::filesystem::path GetExpandedPath(const std::string& path) + { + std::string trimPath = path; + Utility::Trim(trimPath); + + try + { + return std::filesystem::weakly_canonical(Utility::ExpandEnvironmentVariables(Utility::ConvertToUTF16(trimPath))); + } + catch (...) + { + return Utility::ConvertToUTF16(path); + } + } + + bool ReplaceCommonPathPrefix(std::filesystem::path& source, const std::filesystem::path& prefix, std::string_view replacement) + { + auto prefixItr = prefix.begin(); + auto sourceItr = source.begin(); + + while (prefixItr != prefix.end() && sourceItr != source.end()) + { + if (!Utility::ICUCaseInsensitiveEquals(prefixItr->u8string(), sourceItr->u8string())) + { + break; + } + + ++prefixItr; + ++sourceItr; + } + + // Only replace source if we found all of prefix + if (prefixItr == prefix.end()) + { + std::filesystem::path temp{ replacement }; + + for (; sourceItr != source.end(); ++sourceItr) + { + temp /= *sourceItr; + } + + source = std::move(temp); + + return true; + } + + return false; + } + + std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) + { + wil::unique_cotaskmem_string knownFolder = nullptr; + THROW_IF_FAILED(SHGetKnownFolderPath(id, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_PACKAGE_REDIRECTION, NULL, &knownFolder)); + return knownFolder.get(); + } + + bool IsSameVolume(const std::filesystem::path& path1, const std::filesystem::path& path2) + { + WCHAR volumeName1[MAX_PATH]; + WCHAR volumeName2[MAX_PATH]; + + // Note: GetVolumePathNameW will return false if the volume drive does not exist. + if (!GetVolumePathNameW(path1.c_str(), volumeName1, MAX_PATH) || !GetVolumePathNameW(path2.c_str(), volumeName2, MAX_PATH)) + { + return false; + } + return Utility::ICUCaseInsensitiveEquals(Utility::ConvertToUTF8(volumeName1), Utility::ConvertToUTF8(volumeName2)); + } + + bool IsParentPath(const std::filesystem::path& path, const std::filesystem::path& parentPath) + { + return std::filesystem::weakly_canonical(path.parent_path()) == std::filesystem::weakly_canonical(parentPath); + } + + void PathDetails::SetOwner(ACEPrincipal owner) + { + Owner = owner; + ACL[owner] = ACEPermissions::All; + } + + bool PathDetails::ShouldApplyACL() const + { + if (ACL.empty()) + { + return false; + } + + try + { + return !anon::PathHasExpectedOwnerAndACLs(*this); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION_MSG("Failed to inspect ACL state; reapplying ACLs to preserve security"); + return true; + } + } + + void PathDetails::ApplyACL() const + { + auto userToken = wil::get_token_information(); + auto adminSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + auto systemSID = wil::make_static_sid(SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); + PSID ownerSID = nullptr; + + anon::ACEDetails aceDetails[] = + { + { ACEPrincipal::CurrentUser, userToken->User.Sid, TRUSTEE_IS_USER }, + { ACEPrincipal::Admins, adminSID.get(), TRUSTEE_IS_WELL_KNOWN_GROUP}, + { ACEPrincipal::System, systemSID.get(), TRUSTEE_IS_USER}, + }; + + ULONG entriesCount = 0; + std::array explicitAccess; + + // If the current user is SYSTEM, we want to take either the owner or the only configured set of permissions. + // The check above should prevent us from getting into situations outside of the ones below. + std::optional principalToIgnore = anon::GetPrincipalToIgnore(*this, userToken.get(), systemSID.get()); + + for (const auto& ace : aceDetails) + { + if (principalToIgnore && principalToIgnore.value() == ace.Principal) + { + continue; + } + + if (Owner && Owner.value() == ace.Principal) + { + ownerSID = ace.SID; + } + + auto itr = ACL.find(ace.Principal); + if (itr != ACL.end()) + { + EXPLICIT_ACCESS_W& entry = explicitAccess[entriesCount++]; + entry = {}; + + entry.grfAccessPermissions = anon::AccessPermissionsFrom(itr->second); + entry.grfAccessMode = SET_ACCESS; + entry.grfInheritance = anon::s_InheritableAceFlags; + + entry.Trustee.pMultipleTrustee = nullptr; + entry.Trustee.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; + entry.Trustee.TrusteeForm = TRUSTEE_IS_SID; + entry.Trustee.TrusteeType = ace.TrusteeType; + entry.Trustee.ptstrName = reinterpret_cast(ace.SID); + } + } + + wil::unique_any acl; + THROW_IF_WIN32_ERROR(SetEntriesInAclW(entriesCount, explicitAccess.data(), nullptr, &acl)); + + std::wstring path = Path.wstring(); + SECURITY_INFORMATION securityInformation = DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION; + + if (ownerSID) + { + securityInformation |= OWNER_SECURITY_INFORMATION; + } + + DWORD result = SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation, ownerSID, nullptr, acl.get(), nullptr); + + // We can be denied access attempting to set the owner when the owner is already correct. + // Determine if the owner is correct; if so, try again without attempting to set the owner. + if (result == ERROR_ACCESS_DENIED && ownerSID) + { + wil::unique_hlocal_security_descriptor securityDescriptor; + PSID currentOwnerSID = nullptr; + DWORD getResult = GetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, ¤tOwnerSID, nullptr, nullptr, nullptr, &securityDescriptor); + + if (SUCCEEDED_WIN32_LOG(getResult) && currentOwnerSID && EqualSid(currentOwnerSID, ownerSID)) + { + result = SetNamedSecurityInfoW(&path[0], SE_FILE_OBJECT, securityInformation & ~OWNER_SECURITY_INFORMATION, nullptr, nullptr, acl.get(), nullptr); + } + } + + THROW_IF_WIN32_ERROR(result); + } + + std::filesystem::path InitializeAndGetPathTo(PathDetails&& details) + { + if (details.Create) + { + if (details.Path.is_absolute()) + { + if (std::filesystem::exists(details.Path) && !std::filesystem::is_directory(details.Path)) + { + std::filesystem::remove(details.Path); + } + + std::filesystem::create_directories(details.Path); + + // Set the ACLs on the directory if needed. We do this after creating the directory because an attacker could + // have created the directory beforehand so we must be able to place the correct ACL on any directory or fail + // to operate. + if (details.ShouldApplyACL()) + { + details.ApplyACL(); + } + } + else + { + AICLI_LOG(Core, Warning, << "InitializeAndGetPathTo directory creation requested for path that was not absolute: " << details.Path); + } + } + + return std::move(details.Path); + } + + PathDetails GetPathDetailsFor(PathName path, bool forDisplay) + { + PathDetails result; + // We should not create directories by default when they are retrieved for display purposes. + result.Create = !forDisplay; + + switch (path) + { + case PathName::UnpackagedLocalStateRoot: + result.Path = anon::GetPathToAppDataDir(anon::s_AppDataDir_State, forDisplay); + result.SetOwner(ACEPrincipal::CurrentUser); + result.ACL[ACEPrincipal::System] = ACEPermissions::All; + result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + break; + case PathName::UnpackagedSettingsRoot: + result.Path = anon::GetPathToAppDataDir(anon::s_AppDataDir_Settings, forDisplay); + result.SetOwner(ACEPrincipal::CurrentUser); + result.ACL[ACEPrincipal::System] = ACEPermissions::All; + result.ACL[ACEPrincipal::Admins] = ACEPermissions::All; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + std::filesystem::path GetExecutablePathForProcess(HANDLE process) + { + wil::unique_cotaskmem_string imageName = nullptr; + if (SUCCEEDED(wil::QueryFullProcessImageNameW(process, 0, imageName)) && + (imageName.get() != nullptr)) + { + return imageName.get(); + } + + return {}; + } + + std::vector GetFileInfoFor(const std::filesystem::path& directory) + { + std::vector result; + + for (const auto& file : std::filesystem::directory_iterator{ directory }) + { + if (file.is_regular_file()) + { + result.emplace_back(FileInfo{ file.path(), file.last_write_time(), file.file_size() }); + } + } + + return result; + } + + void FilterToFilesExceedingLimits(std::vector& files, const FileLimits& limits) + { + auto now = std::filesystem::file_time_type::clock::now(); + std::chrono::hours ageLimit = limits.Age; + static_assert(sizeof(uintmax_t) >= 8); + uintmax_t totalSizeLimit = static_cast(limits.TotalSizeInMB) << 20; + size_t countLimit = limits.Count; + + // Sort with oldest first so that we can work backward to find the cutoff + std::sort(files.begin(), files.end(), [](const FileInfo& a, const FileInfo& b) { return a.LastWriteTime < b.LastWriteTime; }); + + // Walk the list backward until we find the first entry that goes over one of the limits + size_t i = files.size(); + uintmax_t totalSize = 0; + for (; i > 0; --i) + { + const FileInfo& current = files[i - 1]; + + if (totalSizeLimit != 0) + { + totalSize += current.Size; + if (totalSize > totalSizeLimit) + { + break; + } + } + + if (countLimit != 0 && (files.size() - i + 1) > countLimit) + { + break; + } + + if (ageLimit != 0h && now - current.LastWriteTime > ageLimit) + { + break; + } + } + + files.resize(i); + } + + void WriteStringToFile(HANDLE fileHandle, std::string_view content) + { + size_t totalBytesWritten = 0; + while (totalBytesWritten < content.size()) + { + DWORD bytesWritten = 0; + THROW_LAST_ERROR_IF(!WriteFile( + fileHandle, + content.data() + totalBytesWritten, + static_cast(content.size() - totalBytesWritten), + &bytesWritten, + nullptr)); + totalBytesWritten += bytesWritten; + } + } +} diff --git a/src/AppInstallerSharedLib/GroupPolicy.cpp b/src/AppInstallerSharedLib/GroupPolicy.cpp index 9bf7c63231..19cf9321e4 100644 --- a/src/AppInstallerSharedLib/GroupPolicy.cpp +++ b/src/AppInstallerSharedLib/GroupPolicy.cpp @@ -1,462 +1,462 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/GroupPolicy.h" -#include "AppInstallerLogging.h" - -using namespace AppInstaller::StringResource; - -namespace AppInstaller::Settings -{ - namespace - { - GroupPolicy& InstanceInternal(std::optional overridePolicy = {}) - { - static GroupPolicy s_groupPolicy{ Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, "Software\\Policies\\Microsoft\\Windows\\AppInstaller") }; - static GroupPolicy* s_override = nullptr; - - if (overridePolicy.has_value()) - { - s_override = overridePolicy.value(); - } - - return (s_override ? *s_override : s_groupPolicy); - } - - std::optional GetRegistryValueObject(const Registry::Key& key, const std::string_view valueName) - { - if (!key) - { - // Key does not exist; there's nothing to return - return std::nullopt; - } - - return key[valueName]; - } - - template - std::optional().GetValue())> GetRegistryValueData(const Registry::Value& regValue, const std::string_view valueName) - { - auto value = regValue.TryGetValue(); - if (!value.has_value()) - { - AICLI_LOG(Core, Warning, << "Value for policy '" << valueName << "' does not have expected type"); - return std::nullopt; - } - - return std::move(value.value()); - } - - template - std::optional().GetValue())> GetRegistryValueData(const Registry::Key& key, const std::string_view valueName) - { - auto regValue = GetRegistryValueObject(key, valueName); - if (!regValue.has_value()) - { - // Value does not exist; there's nothing to return - return std::nullopt; - } - - return GetRegistryValueData(regValue.value(), valueName); - } - - std::optional RegistryValueIsTrue(const Registry::Key& key, std::string_view valueName) - { - auto intValue = GetRegistryValueData(key, valueName); - if (!intValue.has_value()) - { - return std::nullopt; - } - - AICLI_LOG(Core, Verbose, << "Found policy '" << valueName << "', Value: " << *intValue); - return (bool)*intValue; - } - - PolicyState GetStateInternal(const Registry::Key& key, TogglePolicy::Policy policy) - { - // Default to not configured if there is no policy for this - if (policy == TogglePolicy::Policy::None) - { - return PolicyState::NotConfigured; - } - - auto togglePolicy = TogglePolicy::GetPolicy(policy); - - // Policies are not configured if there is no registry value. - auto setting = RegistryValueIsTrue(key, togglePolicy.RegValueName()); - if (!setting.has_value()) - { - return PolicyState::NotConfigured; - } - - // Return flag as-is or invert depending on the policy - return *setting ? PolicyState::Enabled : PolicyState::Disabled; - } - - template - void Validate(const Registry::Key& policiesKey, GroupPolicy::ValuePoliciesMap& policies) - { - auto value = details::ValuePolicyMapping

::ReadAndValidate(policiesKey); - if (value.has_value()) - { - policies.Add

(std::move(*value)); - } - } - - template <> - void Validate(const Registry::Key&, GroupPolicy::ValuePoliciesMap&) {}; - - template - void ValidateAllValuePolicies( - const Registry::Key& policiesKey, - GroupPolicy::ValuePoliciesMap& policies, - std::index_sequence) - { - // Use folding to call each policy validate function. - (FoldHelper{}, ..., Validate(P)>(policiesKey, policies)); - } - - // Reads a list from a Group Policy. - // The list is stored in a sub-key of the policies key, and each value in that key is a list item. - // Cases not considered by this function because we don't use them: - // - When the list is in an arbitrary key, not a sub key. - // - When the list values are mixed with other values and are identified by a prefix in their names. - // - When the value names are relevant. - template - std::optional::value_t> ReadList(const Registry::Key& policiesKey) - { - using Mapping = details::ValuePolicyMapping

; - - auto listKey = policiesKey.SubKey(Mapping::KeyName); - if (!listKey.has_value()) - { - return std::nullopt; - } - - typename Mapping::value_t items; - for (const auto& value : listKey->Values()) - { - std::optional potentialValue = value.Value(); - - if (potentialValue) - { - auto item = Mapping::ReadAndValidateItem(potentialValue.value()); - if (item.has_value()) - { - items.emplace_back(std::move(item.value())); - } - else - { - AICLI_LOG(Core, Warning, << "Failed to read Group Policy list value. Policy [" << Mapping::KeyName << "], Value [" << value.Name() << ']'); - } - } - else - { - AICLI_LOG(Core, Verbose, << "Group Policy list value not found. Policy [" << Mapping::KeyName << "], Value [" << value.Name() << ']'); - } - } - - return items; - } - - std::optional ReadSourceFromRegistryValue(const Registry::Value& item) - { - auto jsonString = item.TryGetValue(); - if (!jsonString.has_value()) - { - AICLI_LOG(Core, Warning, << "Registry value is not a string"); - return std::nullopt; - } - - int stringLength = static_cast(jsonString->length()); - Json::Value sourceJson; - Json::CharReaderBuilder charReaderBuilder; - const std::unique_ptr jsonReader(charReaderBuilder.newCharReader()); - Json::String jsonErrors; - if (!jsonReader->parse(jsonString->c_str(), jsonString->c_str() + stringLength, &sourceJson, &jsonErrors)) - { - AICLI_LOG(Core, Warning, << "Registry value does not contain a valid JSON: " << jsonErrors); - return std::nullopt; - } - - SourceFromPolicy source; - - auto readSourceAttribute = [&](const std::string& name, std::string SourceFromPolicy::* member) - { - if (sourceJson.isMember(name) && sourceJson[name].isString()) - { - source.*member = sourceJson[name].asString(); - return true; - } - else - { - AICLI_LOG(Core, Warning, << "Source JSON does not contain a string value for " << name); - return false; - } - }; - - // All required fields should be read here. - bool allRead = readSourceAttribute("Name", &SourceFromPolicy::Name) - && readSourceAttribute("Arg", &SourceFromPolicy::Arg) - && readSourceAttribute("Type", &SourceFromPolicy::Type) - && readSourceAttribute("Data", &SourceFromPolicy::Data) - && readSourceAttribute("Identifier", &SourceFromPolicy::Identifier); - - if (!allRead) - { - return std::nullopt; - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - // Enable certificate pinning configuration through GP sources for testing - const std::string pinningConfigurationName = "CertificatePinning"; - if (sourceJson.isMember(pinningConfigurationName)) - { - source.PinningConfiguration = Certificates::PinningConfiguration(source.Name); - if (!source.PinningConfiguration.LoadFrom(sourceJson[pinningConfigurationName])) - { - return std::nullopt; - } - } -#endif - // TrustLevel, Explicit, and Priority are optional policy fields with default values. - const std::string trustLevelName = "TrustLevel"; - if (sourceJson.isMember(trustLevelName) && sourceJson[trustLevelName].isArray()) - { - const Json::Value in = sourceJson[trustLevelName]; - std::vector result; - result.reserve(in.size()); - std::transform(in.begin(), in.end(), std::back_inserter(result), [](const auto& e) { return e.asString(); }); - source.TrustLevel = result; - } - - const std::string explicitName = "Explicit"; - if (sourceJson.isMember(explicitName) && sourceJson[explicitName].isBool()) - { - source.Explicit = sourceJson[explicitName].asBool(); - } - - const std::string priorityName = "Priority"; - if (sourceJson.isMember(priorityName) && sourceJson[priorityName].isInt()) - { - source.Priority = sourceJson[priorityName].asInt(); - } - - return source; - } - } - - namespace details - { -#define POLICY_MAPPING_DEFAULT_READ(_policy_) \ - std::optional::value_t> ValuePolicyMapping<_policy_>::ReadAndValidate(const Registry::Key& policiesKey) \ - { \ - using Mapping = ValuePolicyMapping<_policy_>; \ - return GetRegistryValueData(policiesKey, Mapping::ValueName); \ - } - -#define POLICY_MAPPING_DEFAULT_LIST_READ(_policy_) \ - std::optional::value_t> ValuePolicyMapping<_policy_>::ReadAndValidate(const Registry::Key& policiesKey) \ - { \ - return ReadList<_policy_>(policiesKey); \ - } - - POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AdditionalSources); - POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AllowedSources); - POLICY_MAPPING_DEFAULT_READ(ValuePolicy::DefaultProxy); - - std::nullopt_t ValuePolicyMapping::ReadAndValidate(const Registry::Key&) - { - return std::nullopt; - } - - std::optional ValuePolicyMapping::ReadAndValidate(const Registry::Key& policiesKey) - { - // This policy used to have another name in the registry. - // Try to read first with the current name, and if it's not present - // check if the old name is present. - using Mapping = ValuePolicyMapping; - - auto regValueWithCurrentName = GetRegistryValueObject(policiesKey, Mapping::ValueName); - if (regValueWithCurrentName.has_value()) - { - // We use the current name even if it doesn't have valid data. - return GetRegistryValueData(regValueWithCurrentName.value(), Mapping::ValueName); - } - else - { - return GetRegistryValueData(policiesKey, "SourceAutoUpdateIntervalInMinutes"sv); - } - } - - std::optional ValuePolicyMapping::ReadAndValidateItem(const Registry::Value& item) - { - return ReadSourceFromRegistryValue(item); - } - - std::optional ValuePolicyMapping::ReadAndValidateItem(const Registry::Value& item) - { - return ReadSourceFromRegistryValue(item); - } - } - - TogglePolicy TogglePolicy::GetPolicy(TogglePolicy::Policy policy) - { - switch (policy) - { - case TogglePolicy::Policy::WinGet: - return TogglePolicy(policy, "EnableAppInstaller"sv, String::PolicyEnableWinGet); - case TogglePolicy::Policy::Settings: - return TogglePolicy(policy, "EnableSettings"sv, String::PolicyEnableWingetSettings); - case TogglePolicy::Policy::ExperimentalFeatures: - return TogglePolicy(policy, "EnableExperimentalFeatures"sv, String::PolicyEnableExperimentalFeatures); - case TogglePolicy::Policy::LocalManifestFiles: - return TogglePolicy(policy, "EnableLocalManifestFiles"sv, String::PolicyEnableLocalManifests); - case TogglePolicy::Policy::HashOverride: - return TogglePolicy(policy, "EnableHashOverride"sv, String::PolicyEnableHashOverride); - case TogglePolicy::Policy::LocalArchiveMalwareScanOverride: - return TogglePolicy(policy, "EnableLocalArchiveMalwareScanOverride"sv, String::PolicyEnableLocalArchiveMalwareScanOverride); - case TogglePolicy::Policy::DefaultSource: - return TogglePolicy(policy, "EnableDefaultSource"sv, String::PolicyEnableDefaultSource); - case TogglePolicy::Policy::MSStoreSource: - return TogglePolicy(policy, "EnableMicrosoftStoreSource"sv, String::PolicyEnableMSStoreSource); - case TogglePolicy::Policy::FontSource: - return TogglePolicy(policy, "EnableFontSource"sv, String::PolicyEnableFontSource); - case TogglePolicy::Policy::AdditionalSources: - return TogglePolicy(policy, "EnableAdditionalSources"sv, String::PolicyAdditionalSources); - case TogglePolicy::Policy::AllowedSources: - return TogglePolicy(policy, "EnableAllowedSources"sv, String::PolicyAllowedSources); - case TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore: - return TogglePolicy(policy, "EnableBypassCertificatePinningForMicrosoftStore"sv, String::PolicyEnableBypassCertificatePinningForMicrosoftStore); - case TogglePolicy::Policy::WinGetCommandLineInterfaces: - return TogglePolicy(policy, "EnableWindowsPackageManagerCommandLineInterfaces"sv, String::PolicyEnableWindowsPackageManagerCommandLineInterfaces); - case TogglePolicy::Policy::Configuration: - return TogglePolicy(policy, "EnableWindowsPackageManagerConfiguration"sv, String::PolicyEnableWinGetConfiguration); - case TogglePolicy::Policy::ProxyCommandLineOptions: - return TogglePolicy(policy, "EnableWindowsPackageManagerProxyCommandLineOptions"sv, String::PolicyEnableProxyCommandLineOptions); - case TogglePolicy::Policy::McpServer: - return TogglePolicy(policy, "EnableWindowsPackageManagerMcpServer"sv, String::PolicyEnableMcpServer); - case TogglePolicy::Policy::ConfigurationProcessorPath: - return TogglePolicy(policy, "EnableWindowsPackageManagerConfigurationProcessorPath"sv, String::PolicyEnableConfigurationProcessorPath); - default: - THROW_HR(E_UNEXPECTED); - } - } - - std::vector TogglePolicy::GetAllPolicies() - { - using Toggle_t = std::underlying_type_t; - - std::vector result; - - // Skip "None" - for (Toggle_t i = 1 + static_cast(TogglePolicy::Policy::None); i < static_cast(TogglePolicy::Policy::Max); ++i) - { - result.emplace_back(GetPolicy(static_cast(i))); - } - - return result; - } - - std::string SourceFromPolicy::ToJsonString() const - { - Json::Value json{ Json::ValueType::objectValue }; - json["Name"] = Name; - json["Type"] = Type; - json["Arg"] = Arg; - json["Data"] = Data; - json["Identifier"] = Identifier; - json["Explicit"] = Explicit; - - if (Priority) - { - json["Priority"] = Priority.value(); - } - - // Trust level is represented as an array of trust level strings since there can be multiple flags set. - int trustLevelLength = static_cast(TrustLevel.size()); - for (int i = 0; i < trustLevelLength; ++i) - { - json["TrustLevel"][i] = TrustLevel[i]; - } - - Json::StreamWriterBuilder writerBuilder; - writerBuilder.settings_["indentation"] = ""; - return Json::writeString(writerBuilder, json); - } - - GroupPolicy::GroupPolicy(Registry::Key key) : m_key(std::move(key)) - { - Reload(); - } - - void GroupPolicy::Reload() - { - std::map toggles; - ValuePoliciesMap values; - - ValidateAllValuePolicies(m_key, values, std::make_index_sequence(ValuePolicy::Max)>()); - - using Toggle_t = std::underlying_type_t; - for (Toggle_t i = static_cast(TogglePolicy::Policy::None); i < static_cast(TogglePolicy::Policy::Max); ++i) - { - auto policy = static_cast(i); - toggles[policy] = GetStateInternal(m_key, policy); - } - - auto lock = m_lock.lock_exclusive(); - m_toggles = std::move(toggles); - m_values = std::move(values); - } - - PolicyState GroupPolicy::GetStateNoLock(TogglePolicy::Policy policy) const - { - auto itr = m_toggles.find(policy); - if (itr == m_toggles.end()) - { - return PolicyState::NotConfigured; - } - - return itr->second; - } - - PolicyState GroupPolicy::GetState(TogglePolicy::Policy policy) const - { - auto lock = m_lock.lock_shared(); - return GetStateNoLock(policy); - } - - bool GroupPolicy::IsEnabled(TogglePolicy::Policy policy) const - { - if (policy == TogglePolicy::Policy::None) - { - return true; - } - - auto lock = m_lock.lock_shared(); - PolicyState state = GetStateNoLock(policy); - if (state == PolicyState::NotConfigured) - { - return TogglePolicy::GetPolicy(policy).DefaultIsEnabled(); - } - - return state == PolicyState::Enabled; - } - - GroupPolicy& GroupPolicy::Instance() - { - return InstanceInternal(); - } - -#ifndef AICLI_DISABLE_TEST_HOOKS - void GroupPolicy::OverrideInstance(GroupPolicy* overridePolicy) - { - InstanceInternal(overridePolicy); - } - - void GroupPolicy::ResetInstance() - { - InstanceInternal(nullptr); - } -#endif -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/GroupPolicy.h" +#include "AppInstallerLogging.h" + +using namespace AppInstaller::StringResource; + +namespace AppInstaller::Settings +{ + namespace + { + GroupPolicy& InstanceInternal(std::optional overridePolicy = {}) + { + static GroupPolicy s_groupPolicy{ Registry::Key::OpenIfExists(HKEY_LOCAL_MACHINE, "Software\\Policies\\Microsoft\\Windows\\AppInstaller") }; + static GroupPolicy* s_override = nullptr; + + if (overridePolicy.has_value()) + { + s_override = overridePolicy.value(); + } + + return (s_override ? *s_override : s_groupPolicy); + } + + std::optional GetRegistryValueObject(const Registry::Key& key, const std::string_view valueName) + { + if (!key) + { + // Key does not exist; there's nothing to return + return std::nullopt; + } + + return key[valueName]; + } + + template + std::optional().GetValue())> GetRegistryValueData(const Registry::Value& regValue, const std::string_view valueName) + { + auto value = regValue.TryGetValue(); + if (!value.has_value()) + { + AICLI_LOG(Core, Warning, << "Value for policy '" << valueName << "' does not have expected type"); + return std::nullopt; + } + + return std::move(value.value()); + } + + template + std::optional().GetValue())> GetRegistryValueData(const Registry::Key& key, const std::string_view valueName) + { + auto regValue = GetRegistryValueObject(key, valueName); + if (!regValue.has_value()) + { + // Value does not exist; there's nothing to return + return std::nullopt; + } + + return GetRegistryValueData(regValue.value(), valueName); + } + + std::optional RegistryValueIsTrue(const Registry::Key& key, std::string_view valueName) + { + auto intValue = GetRegistryValueData(key, valueName); + if (!intValue.has_value()) + { + return std::nullopt; + } + + AICLI_LOG(Core, Verbose, << "Found policy '" << valueName << "', Value: " << *intValue); + return (bool)*intValue; + } + + PolicyState GetStateInternal(const Registry::Key& key, TogglePolicy::Policy policy) + { + // Default to not configured if there is no policy for this + if (policy == TogglePolicy::Policy::None) + { + return PolicyState::NotConfigured; + } + + auto togglePolicy = TogglePolicy::GetPolicy(policy); + + // Policies are not configured if there is no registry value. + auto setting = RegistryValueIsTrue(key, togglePolicy.RegValueName()); + if (!setting.has_value()) + { + return PolicyState::NotConfigured; + } + + // Return flag as-is or invert depending on the policy + return *setting ? PolicyState::Enabled : PolicyState::Disabled; + } + + template + void Validate(const Registry::Key& policiesKey, GroupPolicy::ValuePoliciesMap& policies) + { + auto value = details::ValuePolicyMapping

::ReadAndValidate(policiesKey); + if (value.has_value()) + { + policies.Add

(std::move(*value)); + } + } + + template <> + void Validate(const Registry::Key&, GroupPolicy::ValuePoliciesMap&) {}; + + template + void ValidateAllValuePolicies( + const Registry::Key& policiesKey, + GroupPolicy::ValuePoliciesMap& policies, + std::index_sequence) + { + // Use folding to call each policy validate function. + (FoldHelper{}, ..., Validate(P)>(policiesKey, policies)); + } + + // Reads a list from a Group Policy. + // The list is stored in a sub-key of the policies key, and each value in that key is a list item. + // Cases not considered by this function because we don't use them: + // - When the list is in an arbitrary key, not a sub key. + // - When the list values are mixed with other values and are identified by a prefix in their names. + // - When the value names are relevant. + template + std::optional::value_t> ReadList(const Registry::Key& policiesKey) + { + using Mapping = details::ValuePolicyMapping

; + + auto listKey = policiesKey.SubKey(Mapping::KeyName); + if (!listKey.has_value()) + { + return std::nullopt; + } + + typename Mapping::value_t items; + for (const auto& value : listKey->Values()) + { + std::optional potentialValue = value.Value(); + + if (potentialValue) + { + auto item = Mapping::ReadAndValidateItem(potentialValue.value()); + if (item.has_value()) + { + items.emplace_back(std::move(item.value())); + } + else + { + AICLI_LOG(Core, Warning, << "Failed to read Group Policy list value. Policy [" << Mapping::KeyName << "], Value [" << value.Name() << ']'); + } + } + else + { + AICLI_LOG(Core, Verbose, << "Group Policy list value not found. Policy [" << Mapping::KeyName << "], Value [" << value.Name() << ']'); + } + } + + return items; + } + + std::optional ReadSourceFromRegistryValue(const Registry::Value& item) + { + auto jsonString = item.TryGetValue(); + if (!jsonString.has_value()) + { + AICLI_LOG(Core, Warning, << "Registry value is not a string"); + return std::nullopt; + } + + int stringLength = static_cast(jsonString->length()); + Json::Value sourceJson; + Json::CharReaderBuilder charReaderBuilder; + const std::unique_ptr jsonReader(charReaderBuilder.newCharReader()); + Json::String jsonErrors; + if (!jsonReader->parse(jsonString->c_str(), jsonString->c_str() + stringLength, &sourceJson, &jsonErrors)) + { + AICLI_LOG(Core, Warning, << "Registry value does not contain a valid JSON: " << jsonErrors); + return std::nullopt; + } + + SourceFromPolicy source; + + auto readSourceAttribute = [&](const std::string& name, std::string SourceFromPolicy::* member) + { + if (sourceJson.isMember(name) && sourceJson[name].isString()) + { + source.*member = sourceJson[name].asString(); + return true; + } + else + { + AICLI_LOG(Core, Warning, << "Source JSON does not contain a string value for " << name); + return false; + } + }; + + // All required fields should be read here. + bool allRead = readSourceAttribute("Name", &SourceFromPolicy::Name) + && readSourceAttribute("Arg", &SourceFromPolicy::Arg) + && readSourceAttribute("Type", &SourceFromPolicy::Type) + && readSourceAttribute("Data", &SourceFromPolicy::Data) + && readSourceAttribute("Identifier", &SourceFromPolicy::Identifier); + + if (!allRead) + { + return std::nullopt; + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + // Enable certificate pinning configuration through GP sources for testing + const std::string pinningConfigurationName = "CertificatePinning"; + if (sourceJson.isMember(pinningConfigurationName)) + { + source.PinningConfiguration = Certificates::PinningConfiguration(source.Name); + if (!source.PinningConfiguration.LoadFrom(sourceJson[pinningConfigurationName])) + { + return std::nullopt; + } + } +#endif + // TrustLevel, Explicit, and Priority are optional policy fields with default values. + const std::string trustLevelName = "TrustLevel"; + if (sourceJson.isMember(trustLevelName) && sourceJson[trustLevelName].isArray()) + { + const Json::Value in = sourceJson[trustLevelName]; + std::vector result; + result.reserve(in.size()); + std::transform(in.begin(), in.end(), std::back_inserter(result), [](const auto& e) { return e.asString(); }); + source.TrustLevel = result; + } + + const std::string explicitName = "Explicit"; + if (sourceJson.isMember(explicitName) && sourceJson[explicitName].isBool()) + { + source.Explicit = sourceJson[explicitName].asBool(); + } + + const std::string priorityName = "Priority"; + if (sourceJson.isMember(priorityName) && sourceJson[priorityName].isInt()) + { + source.Priority = sourceJson[priorityName].asInt(); + } + + return source; + } + } + + namespace details + { +#define POLICY_MAPPING_DEFAULT_READ(_policy_) \ + std::optional::value_t> ValuePolicyMapping<_policy_>::ReadAndValidate(const Registry::Key& policiesKey) \ + { \ + using Mapping = ValuePolicyMapping<_policy_>; \ + return GetRegistryValueData(policiesKey, Mapping::ValueName); \ + } + +#define POLICY_MAPPING_DEFAULT_LIST_READ(_policy_) \ + std::optional::value_t> ValuePolicyMapping<_policy_>::ReadAndValidate(const Registry::Key& policiesKey) \ + { \ + return ReadList<_policy_>(policiesKey); \ + } + + POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AdditionalSources); + POLICY_MAPPING_DEFAULT_LIST_READ(ValuePolicy::AllowedSources); + POLICY_MAPPING_DEFAULT_READ(ValuePolicy::DefaultProxy); + + std::nullopt_t ValuePolicyMapping::ReadAndValidate(const Registry::Key&) + { + return std::nullopt; + } + + std::optional ValuePolicyMapping::ReadAndValidate(const Registry::Key& policiesKey) + { + // This policy used to have another name in the registry. + // Try to read first with the current name, and if it's not present + // check if the old name is present. + using Mapping = ValuePolicyMapping; + + auto regValueWithCurrentName = GetRegistryValueObject(policiesKey, Mapping::ValueName); + if (regValueWithCurrentName.has_value()) + { + // We use the current name even if it doesn't have valid data. + return GetRegistryValueData(regValueWithCurrentName.value(), Mapping::ValueName); + } + else + { + return GetRegistryValueData(policiesKey, "SourceAutoUpdateIntervalInMinutes"sv); + } + } + + std::optional ValuePolicyMapping::ReadAndValidateItem(const Registry::Value& item) + { + return ReadSourceFromRegistryValue(item); + } + + std::optional ValuePolicyMapping::ReadAndValidateItem(const Registry::Value& item) + { + return ReadSourceFromRegistryValue(item); + } + } + + TogglePolicy TogglePolicy::GetPolicy(TogglePolicy::Policy policy) + { + switch (policy) + { + case TogglePolicy::Policy::WinGet: + return TogglePolicy(policy, "EnableAppInstaller"sv, String::PolicyEnableWinGet); + case TogglePolicy::Policy::Settings: + return TogglePolicy(policy, "EnableSettings"sv, String::PolicyEnableWingetSettings); + case TogglePolicy::Policy::ExperimentalFeatures: + return TogglePolicy(policy, "EnableExperimentalFeatures"sv, String::PolicyEnableExperimentalFeatures); + case TogglePolicy::Policy::LocalManifestFiles: + return TogglePolicy(policy, "EnableLocalManifestFiles"sv, String::PolicyEnableLocalManifests); + case TogglePolicy::Policy::HashOverride: + return TogglePolicy(policy, "EnableHashOverride"sv, String::PolicyEnableHashOverride); + case TogglePolicy::Policy::LocalArchiveMalwareScanOverride: + return TogglePolicy(policy, "EnableLocalArchiveMalwareScanOverride"sv, String::PolicyEnableLocalArchiveMalwareScanOverride); + case TogglePolicy::Policy::DefaultSource: + return TogglePolicy(policy, "EnableDefaultSource"sv, String::PolicyEnableDefaultSource); + case TogglePolicy::Policy::MSStoreSource: + return TogglePolicy(policy, "EnableMicrosoftStoreSource"sv, String::PolicyEnableMSStoreSource); + case TogglePolicy::Policy::FontSource: + return TogglePolicy(policy, "EnableFontSource"sv, String::PolicyEnableFontSource); + case TogglePolicy::Policy::AdditionalSources: + return TogglePolicy(policy, "EnableAdditionalSources"sv, String::PolicyAdditionalSources); + case TogglePolicy::Policy::AllowedSources: + return TogglePolicy(policy, "EnableAllowedSources"sv, String::PolicyAllowedSources); + case TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore: + return TogglePolicy(policy, "EnableBypassCertificatePinningForMicrosoftStore"sv, String::PolicyEnableBypassCertificatePinningForMicrosoftStore); + case TogglePolicy::Policy::WinGetCommandLineInterfaces: + return TogglePolicy(policy, "EnableWindowsPackageManagerCommandLineInterfaces"sv, String::PolicyEnableWindowsPackageManagerCommandLineInterfaces); + case TogglePolicy::Policy::Configuration: + return TogglePolicy(policy, "EnableWindowsPackageManagerConfiguration"sv, String::PolicyEnableWinGetConfiguration); + case TogglePolicy::Policy::ProxyCommandLineOptions: + return TogglePolicy(policy, "EnableWindowsPackageManagerProxyCommandLineOptions"sv, String::PolicyEnableProxyCommandLineOptions); + case TogglePolicy::Policy::McpServer: + return TogglePolicy(policy, "EnableWindowsPackageManagerMcpServer"sv, String::PolicyEnableMcpServer); + case TogglePolicy::Policy::ConfigurationProcessorPath: + return TogglePolicy(policy, "EnableWindowsPackageManagerConfigurationProcessorPath"sv, String::PolicyEnableConfigurationProcessorPath); + default: + THROW_HR(E_UNEXPECTED); + } + } + + std::vector TogglePolicy::GetAllPolicies() + { + using Toggle_t = std::underlying_type_t; + + std::vector result; + + // Skip "None" + for (Toggle_t i = 1 + static_cast(TogglePolicy::Policy::None); i < static_cast(TogglePolicy::Policy::Max); ++i) + { + result.emplace_back(GetPolicy(static_cast(i))); + } + + return result; + } + + std::string SourceFromPolicy::ToJsonString() const + { + Json::Value json{ Json::ValueType::objectValue }; + json["Name"] = Name; + json["Type"] = Type; + json["Arg"] = Arg; + json["Data"] = Data; + json["Identifier"] = Identifier; + json["Explicit"] = Explicit; + + if (Priority) + { + json["Priority"] = Priority.value(); + } + + // Trust level is represented as an array of trust level strings since there can be multiple flags set. + int trustLevelLength = static_cast(TrustLevel.size()); + for (int i = 0; i < trustLevelLength; ++i) + { + json["TrustLevel"][i] = TrustLevel[i]; + } + + Json::StreamWriterBuilder writerBuilder; + writerBuilder.settings_["indentation"] = ""; + return Json::writeString(writerBuilder, json); + } + + GroupPolicy::GroupPolicy(Registry::Key key) : m_key(std::move(key)) + { + Reload(); + } + + void GroupPolicy::Reload() + { + std::map toggles; + ValuePoliciesMap values; + + ValidateAllValuePolicies(m_key, values, std::make_index_sequence(ValuePolicy::Max)>()); + + using Toggle_t = std::underlying_type_t; + for (Toggle_t i = static_cast(TogglePolicy::Policy::None); i < static_cast(TogglePolicy::Policy::Max); ++i) + { + auto policy = static_cast(i); + toggles[policy] = GetStateInternal(m_key, policy); + } + + auto lock = m_lock.lock_exclusive(); + m_toggles = std::move(toggles); + m_values = std::move(values); + } + + PolicyState GroupPolicy::GetStateNoLock(TogglePolicy::Policy policy) const + { + auto itr = m_toggles.find(policy); + if (itr == m_toggles.end()) + { + return PolicyState::NotConfigured; + } + + return itr->second; + } + + PolicyState GroupPolicy::GetState(TogglePolicy::Policy policy) const + { + auto lock = m_lock.lock_shared(); + return GetStateNoLock(policy); + } + + bool GroupPolicy::IsEnabled(TogglePolicy::Policy policy) const + { + if (policy == TogglePolicy::Policy::None) + { + return true; + } + + auto lock = m_lock.lock_shared(); + PolicyState state = GetStateNoLock(policy); + if (state == PolicyState::NotConfigured) + { + return TogglePolicy::GetPolicy(policy).DefaultIsEnabled(); + } + + return state == PolicyState::Enabled; + } + + GroupPolicy& GroupPolicy::Instance() + { + return InstanceInternal(); + } + +#ifndef AICLI_DISABLE_TEST_HOOKS + void GroupPolicy::OverrideInstance(GroupPolicy* overridePolicy) + { + InstanceInternal(overridePolicy); + } + + void GroupPolicy::ResetInstance() + { + InstanceInternal(nullptr); + } +#endif +} diff --git a/src/AppInstallerSharedLib/ICU/SQLiteICU.h b/src/AppInstallerSharedLib/ICU/SQLiteICU.h index d073e87ee8..34ebc3e55b 100644 --- a/src/AppInstallerSharedLib/ICU/SQLiteICU.h +++ b/src/AppInstallerSharedLib/ICU/SQLiteICU.h @@ -1,11 +1,11 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -// Adapted from the file sqliteicu.h to use the built-in Windows SQLite and ICU binaries. - -extern "C" -{ - int sqlite3IcuInit(sqlite3* db); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +// Adapted from the file sqliteicu.h to use the built-in Windows SQLite and ICU binaries. + +extern "C" +{ + int sqlite3IcuInit(sqlite3* db); +} diff --git a/src/AppInstallerSharedLib/JsonSchemaValidation.cpp b/src/AppInstallerSharedLib/JsonSchemaValidation.cpp index 6718113566..6cd43450ff 100644 --- a/src/AppInstallerSharedLib/JsonSchemaValidation.cpp +++ b/src/AppInstallerSharedLib/JsonSchemaValidation.cpp @@ -1,62 +1,62 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/JsonSchemaValidation.h" -#include "winget/Resources.h" - -namespace AppInstaller::JsonSchema -{ - Json::Value LoadSchemaDoc(std::string_view schemaStr) - { - Json::Value schemaJson; - int schemaLength = static_cast(schemaStr.length()); - Json::CharReaderBuilder charReaderBuilder; - const std::unique_ptr jsonReader(charReaderBuilder.newCharReader()); - std::string errorMsg; - if (!jsonReader->parse(schemaStr.data(), schemaStr.data() + schemaLength, &schemaJson, &errorMsg)) - { - THROW_HR_MSG(E_UNEXPECTED, "Jsoncpp parser failed to parse the schema doc. Reason: %hs", errorMsg.c_str()); - } - - return schemaJson; - } - - Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType) - { - return LoadSchemaDoc(Resource::GetResourceAsString(resourceName, resourceType)); - } - - void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema) - { - valijson::SchemaParser schemaParser; - valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); - schemaParser.populateSchema(jsonSchemaAdapter, schema); - } - - bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results) - { - valijson::Validator schemaValidator; - valijson::adapters::JsonCppAdapter jsonAdapter(json); - return schemaValidator.validate(schema, jsonAdapter, &results); - } - - std::string GetErrorStringFromResults(valijson::ValidationResults& results) - { - valijson::ValidationResults::Error error; - std::stringstream ss; - - ss << "Schema validation failed." << std::endl; - while (results.popError(error)) - { - std::string context; - for (auto itr = error.context.begin(); itr != error.context.end(); itr++) - { - context += *itr; - } - - ss << "Error context: " << context << " Description: " << error.description << std::endl; - } - - return ss.str(); - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/JsonSchemaValidation.h" +#include "winget/Resources.h" + +namespace AppInstaller::JsonSchema +{ + Json::Value LoadSchemaDoc(std::string_view schemaStr) + { + Json::Value schemaJson; + int schemaLength = static_cast(schemaStr.length()); + Json::CharReaderBuilder charReaderBuilder; + const std::unique_ptr jsonReader(charReaderBuilder.newCharReader()); + std::string errorMsg; + if (!jsonReader->parse(schemaStr.data(), schemaStr.data() + schemaLength, &schemaJson, &errorMsg)) + { + THROW_HR_MSG(E_UNEXPECTED, "Jsoncpp parser failed to parse the schema doc. Reason: %hs", errorMsg.c_str()); + } + + return schemaJson; + } + + Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType) + { + return LoadSchemaDoc(Resource::GetResourceAsString(resourceName, resourceType)); + } + + void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema) + { + valijson::SchemaParser schemaParser; + valijson::adapters::JsonCppAdapter jsonSchemaAdapter(schemaJson); + schemaParser.populateSchema(jsonSchemaAdapter, schema); + } + + bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results) + { + valijson::Validator schemaValidator; + valijson::adapters::JsonCppAdapter jsonAdapter(json); + return schemaValidator.validate(schema, jsonAdapter, &results); + } + + std::string GetErrorStringFromResults(valijson::ValidationResults& results) + { + valijson::ValidationResults::Error error; + std::stringstream ss; + + ss << "Schema validation failed." << std::endl; + while (results.popError(error)) + { + std::string context; + for (auto itr = error.context.begin(); itr != error.context.end(); itr++) + { + context += *itr; + } + + ss << "Error context: " << context << " Description: " << error.description << std::endl; + } + + return ss.str(); + } } \ No newline at end of file diff --git a/src/AppInstallerSharedLib/JsonUtil.cpp b/src/AppInstallerSharedLib/JsonUtil.cpp index dbe33dc301..a89caae455 100644 --- a/src/AppInstallerSharedLib/JsonUtil.cpp +++ b/src/AppInstallerSharedLib/JsonUtil.cpp @@ -141,21 +141,21 @@ namespace AppInstaller::JSON } return value.as_integer(); - } + } - std::optional GetRawUInt64ValueFromJsonValue(const web::json::value& value) + std::optional GetRawUInt64ValueFromJsonValue(const web::json::value& value) { if (value.is_null() || !value.is_number()) { return {}; } - - const web::json::number& valueNumber = value.as_number(); - - if (!valueNumber.is_uint64()) - { - return {}; - } + + const web::json::number& valueNumber = value.as_number(); + + if (!valueNumber.is_uint64()) + { + return {}; + } return valueNumber.to_uint64(); } @@ -170,9 +170,9 @@ namespace AppInstaller::JSON } return {}; - } + } - std::optional GetRawUInt64ValueFromJsonNode(const web::json::value& node, const utility::string_t& keyName) + std::optional GetRawUInt64ValueFromJsonNode(const web::json::value& node, const utility::string_t& keyName) { std::optional> jsonValue = GetJsonValueFromNode(node, keyName); diff --git a/src/AppInstallerSharedLib/ManagedFile.cpp b/src/AppInstallerSharedLib/ManagedFile.cpp index 9d0ad500c8..60453312ce 100644 --- a/src/AppInstallerSharedLib/ManagedFile.cpp +++ b/src/AppInstallerSharedLib/ManagedFile.cpp @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/ManagedFile.h" -#include "AppInstallerLogging.h" - -namespace AppInstaller::Utility -{ - ManagedFile ManagedFile::CreateWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess, bool deleteOnExit) - { - ManagedFile file; - file.m_fileHandle.reset(CreateFileW(path.c_str(), desiredAccess, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); - THROW_LAST_ERROR_IF(!file.m_fileHandle); - file.m_filePath = path; - file.m_deleteFileOnExit = deleteOnExit; - - return file; - } - - ManagedFile ManagedFile::OpenWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess) - { - ManagedFile file; - file.m_fileHandle.reset(CreateFileW(path.c_str(), desiredAccess, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); - THROW_LAST_ERROR_IF(!file.m_fileHandle); - file.m_filePath = path; - - return file; - } - - ManagedFile::~ManagedFile() - { - if (m_deleteFileOnExit) - { - if (m_fileHandle) - { - m_fileHandle.reset(); - } - - try - { - std::filesystem::remove(m_filePath); - } - catch (...) - { - AICLI_LOG(Core, Info, << "Failed to remove managed file at: " << m_filePath); - } - } - } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/ManagedFile.h" +#include "AppInstallerLogging.h" + +namespace AppInstaller::Utility +{ + ManagedFile ManagedFile::CreateWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess, bool deleteOnExit) + { + ManagedFile file; + file.m_fileHandle.reset(CreateFileW(path.c_str(), desiredAccess, FILE_SHARE_READ, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!file.m_fileHandle); + file.m_filePath = path; + file.m_deleteFileOnExit = deleteOnExit; + + return file; + } + + ManagedFile ManagedFile::OpenWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess) + { + ManagedFile file; + file.m_fileHandle.reset(CreateFileW(path.c_str(), desiredAccess, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr)); + THROW_LAST_ERROR_IF(!file.m_fileHandle); + file.m_filePath = path; + + return file; + } + + ManagedFile::~ManagedFile() + { + if (m_deleteFileOnExit) + { + if (m_fileHandle) + { + m_fileHandle.reset(); + } + + try + { + std::filesystem::remove(m_filePath); + } + catch (...) + { + AICLI_LOG(Core, Info, << "Failed to remove managed file at: " << m_filePath); + } + } + } } \ No newline at end of file diff --git a/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h b/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h index 283e55959a..089a7bca24 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerDateTime.h @@ -1,70 +1,70 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Utility -{ - // The individual aspects of a time point. - enum class TimeFacet - { - None = 0x000, - Millisecond = 0x001, - Second = 0x002, - Minute = 0x004, - Hour = 0x008, - Day = 0x010, - Month = 0x020, - Year = 0x040, - // `Year - 2000` [2 digits for 75 more years] - ShortYear = 0x080, - // Includes unspecified time zone - RFC3339 = 0x100, - // Limits special character use - Filename = 0x200, - - Default = Year | Month | Day | Hour | Minute | Second | Millisecond, - ShortYearSecondPrecision = ShortYear | Month | Day | Hour | Minute | Second, - }; - - DEFINE_ENUM_FLAG_OPERATORS(TimeFacet); - - // Writes the given time to the given stream. - // Assumes that system_clock uses Linux epoch (as required by C++20 standard). - // Time is also assumed to be after the epoch. - void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, bool useRFC3339 = false); - void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet); - - // Converts the time point to a string using OutputTimePoint. - std::string TimePointToString(const std::chrono::system_clock::time_point& time, bool useRFC3339 = false); - std::string TimePointToString(const std::chrono::system_clock::time_point& time, TimeFacet facet); - - // Gets the current time as a string. Can be used as a file name. - // Tries to make things a little bit shorter when shortTime == true. - std::string GetCurrentTimeForFilename(bool shortTime = false); - - // Gets the current date as a string to be used in the ARP registry. - std::string GetCurrentDateForARP(); - - // Gets the current time as a unix epoch value. - int64_t GetCurrentUnixEpoch(); - - // Converts the given unix epoch time to a system_clock::time_point. - int64_t ConvertSystemClockToUnixEpoch(const std::chrono::system_clock::time_point& time); - - // Converts the given unix epoch time to a system_clock::time_point. - std::chrono::system_clock::time_point ConvertUnixEpochToSystemClock(int64_t epoch); - - // Converts the given file time to a system_clock::time_point. - std::chrono::system_clock::time_point ConvertFiletimeToSystemClock(const FILETIME& fileTime); - - // Converts the given system_clock::time_point to a FILETIME. - FILETIME ConvertSystemClockToFileTime(const std::chrono::system_clock::time_point& time); - - // Converts the given package version into a time_point using our custom format. - // Ensure that the package is expected to use this format, or you may get strange times. - // If the version is not convertable, the minimum time is returned. - std::chrono::system_clock::time_point GetTimePointFromVersion(const UInt64Version& version); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Utility +{ + // The individual aspects of a time point. + enum class TimeFacet + { + None = 0x000, + Millisecond = 0x001, + Second = 0x002, + Minute = 0x004, + Hour = 0x008, + Day = 0x010, + Month = 0x020, + Year = 0x040, + // `Year - 2000` [2 digits for 75 more years] + ShortYear = 0x080, + // Includes unspecified time zone + RFC3339 = 0x100, + // Limits special character use + Filename = 0x200, + + Default = Year | Month | Day | Hour | Minute | Second | Millisecond, + ShortYearSecondPrecision = ShortYear | Month | Day | Hour | Minute | Second, + }; + + DEFINE_ENUM_FLAG_OPERATORS(TimeFacet); + + // Writes the given time to the given stream. + // Assumes that system_clock uses Linux epoch (as required by C++20 standard). + // Time is also assumed to be after the epoch. + void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, bool useRFC3339 = false); + void OutputTimePoint(std::ostream& stream, const std::chrono::system_clock::time_point& time, TimeFacet facet); + + // Converts the time point to a string using OutputTimePoint. + std::string TimePointToString(const std::chrono::system_clock::time_point& time, bool useRFC3339 = false); + std::string TimePointToString(const std::chrono::system_clock::time_point& time, TimeFacet facet); + + // Gets the current time as a string. Can be used as a file name. + // Tries to make things a little bit shorter when shortTime == true. + std::string GetCurrentTimeForFilename(bool shortTime = false); + + // Gets the current date as a string to be used in the ARP registry. + std::string GetCurrentDateForARP(); + + // Gets the current time as a unix epoch value. + int64_t GetCurrentUnixEpoch(); + + // Converts the given unix epoch time to a system_clock::time_point. + int64_t ConvertSystemClockToUnixEpoch(const std::chrono::system_clock::time_point& time); + + // Converts the given unix epoch time to a system_clock::time_point. + std::chrono::system_clock::time_point ConvertUnixEpochToSystemClock(int64_t epoch); + + // Converts the given file time to a system_clock::time_point. + std::chrono::system_clock::time_point ConvertFiletimeToSystemClock(const FILETIME& fileTime); + + // Converts the given system_clock::time_point to a FILETIME. + FILETIME ConvertSystemClockToFileTime(const std::chrono::system_clock::time_point& time); + + // Converts the given package version into a time_point using our custom format. + // Ensure that the package is expected to use this format, or you may get strange times. + // If the version is not convertable, the minimum time is returned. + std::chrono::system_clock::time_point GetTimePointFromVersion(const UInt64Version& version); +} diff --git a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h index 6510e363ad..6cbd652bc6 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerErrors.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerErrors.h @@ -1,285 +1,285 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -// Errors is the most ubiquitous header; including the mismatch detection in it should reach everywhere. -#include - -#ifndef WINGET_DISABLE_FOR_FUZZING -#include -#endif - -#include -#include -#include -#include -#include - - -#define APPINSTALLER_CLI_ERROR_FACILITY 0xA15 - -// Changes to any of these errors require the corresponding resource string in winget.resw to be updated. -#define APPINSTALLER_CLI_ERROR_INTERNAL_ERROR ((HRESULT)0x8A150001) -#define APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS ((HRESULT)0x8A150002) -#define APPINSTALLER_CLI_ERROR_COMMAND_FAILED ((HRESULT)0x8A150003) -#define APPINSTALLER_CLI_ERROR_MANIFEST_FAILED ((HRESULT)0x8A150004) -#define APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED ((HRESULT)0x8A150005) -#define APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED ((HRESULT)0x8A150006) -#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION ((HRESULT)0x8A150007) -#define APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED ((HRESULT)0x8A150008) -#define APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX ((HRESULT)0x8A150009) -#define APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED ((HRESULT)0x8A15000A) -#define APPINSTALLER_CLI_ERROR_SOURCES_INVALID ((HRESULT)0x8A15000B) -#define APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS ((HRESULT)0x8A15000C) -#define APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE ((HRESULT)0x8A15000D) -#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE ((HRESULT)0x8A15000E) -#define APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING ((HRESULT)0x8A15000F) -#define APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER ((HRESULT)0x8A150010) -#define APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH ((HRESULT)0x8A150011) -#define APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST ((HRESULT)0x8A150012) -#define APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS ((HRESULT)0x8A150013) -#define APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND ((HRESULT)0x8A150014) -#define APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED ((HRESULT)0x8A150015) -#define APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND ((HRESULT)0x8A150016) -#define APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND ((HRESULT)0x8A150017) -#define APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED ((HRESULT)0x8A150018) -#define APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN ((HRESULT)0x8A150019) -#define APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE ((HRESULT)0x8A15001A) -#define APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY ((HRESULT)0x8A15001B) -#define APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY ((HRESULT)0x8A15001C) -#define APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED ((HRESULT)0x8A15001D) -#define APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED ((HRESULT)0x8A15001E) -#define APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD ((HRESULT)0x8A15001F) -#define APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED ((HRESULT)0x8A150020) -#define APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY ((HRESULT)0x8A150021) -#define APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY ((HRESULT)0x8A150022) -#define APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION ((HRESULT)0x8A150023) -#define APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED ((HRESULT)0x8A150024) -#define APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE ((HRESULT)0x8A150025) -#define APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA ((HRESULT)0x8A150026) -#define APPINSTALLER_CLI_ERROR_LIBYAML_ERROR ((HRESULT)0x8A150027) -#define APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING ((HRESULT)0x8A150028) -#define APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE ((HRESULT)0x8A150029) -#define APPINSTALLER_CLI_ERROR_INVALID_MANIFEST ((HRESULT)0x8A15002A) -#define APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE ((HRESULT)0x8A15002B) -#define APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE ((HRESULT)0x8A15002C) -#define APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED ((HRESULT)0x8A15002D) -#define APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH ((HRESULT)0x8A15002E) -#define APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND ((HRESULT)0x8A15002F) -#define APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED ((HRESULT)0x8A150030) -#define APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR ((HRESULT)0x8A150031) -#define APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR ((HRESULT)0x8A150032) -#define APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR ((HRESULT)0x8A150033) -#define APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED ((HRESULT)0x8A150034) -#define APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND ((HRESULT)0x8A150035) -#define APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE ((HRESULT)0x8A150036) -#define APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE ((HRESULT)0x8A150037) -#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE ((HRESULT)0x8A150038) -#define APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA ((HRESULT)0x8A150039) -#define APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY ((HRESULT)0x8A15003A) -#define APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR ((HRESULT)0x8A15003B) -#define APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL ((HRESULT)0x8A15003C) -#define APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE ((HRESULT)0x8A15003D) -#define APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION ((HRESULT)0x8A15003E) -#define APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE ((HRESULT)0x8A15003F) -#define APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE ((HRESULT)0x8A150040) -#define APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED ((HRESULT)0x8A150041) -#define APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR ((HRESULT)0x8A150042) -#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST ((HRESULT)0x8A150043) -#define APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND ((HRESULT)0x8A150044) -#define APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED ((HRESULT)0x8A150045) -#define APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED ((HRESULT)0x8A150046) -#define APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH ((HRESULT)0x8A150047) -#define APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE ((HRESULT)0x8A150048) -#define APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED ((HRESULT)0x8A150049) -#define APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT ((HRESULT)0x8A15004A) -#define APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES ((HRESULT)0x8A15004B) -#define APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED ((HRESULT)0x8A15004C) -#define APPINSTALLER_CLI_ERROR_MISSING_PACKAGE ((HRESULT)0x8A15004D) -#define APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN ((HRESULT)0x8A15004E) -#define APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER ((HRESULT)0x8A15004F) -#define APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN ((HRESULT)0x8A150050) -#define APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR ((HRESULT)0x8A150051) -#define APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED ((HRESULT)0x8A150052) -#define APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED ((HRESULT)0x8A150053) -#define APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS ((HRESULT)0x8A150054) -#define APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY ((HRESULT)0x8A150055) -#define APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION ((HRESULT)0x8A150056) -#define APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED ((HRESULT)0x8A150057) -#define APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED ((HRESULT)0x8A150058) -#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT ((HRESULT)0x8A150059) -#define APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL ((HRESULT)0x8A15005A) -#define APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND ((HRESULT)0x8A15005B) -#define APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED ((HRESULT)0x8A15005C) -#define APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH ((HRESULT)0x8A15005D) -#define APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH ((HRESULT)0x8A15005E) -#define APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED ((HRESULT)0x8A15005F) -#define APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED ((HRESULT)0x8A150060) -#define APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED ((HRESULT)0x8A150061) -#define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) -#define APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST ((HRESULT)0x8A150063) -#define APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX ((HRESULT)0x8A150064) -#define APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED ((HRESULT)0x8A150065) -#define APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED ((HRESULT)0x8A150066) -#define APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE ((HRESULT)0x8A150067) -#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED ((HRESULT)0x8A150068) -#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB ((HRESULT)0x8A150069) -#define APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED ((HRESULT)0x8A15006A) -#define APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES ((HRESULT)0x8A15006B) -#define APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED ((HRESULT)0x8A15006C) -#define APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE ((HRESULT)0x8A15006D) -#define APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND ((HRESULT)0x8A15006E) -#define APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH ((HRESULT)0x8A15006F) -#define APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE ((HRESULT)0x8A150070) -#define APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX ((HRESULT)0x8A150071) -#define APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED ((HRESULT)0x8A150072) -#define APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO ((HRESULT)0x8A150073) -#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED ((HRESULT)0x8A150074) -#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED ((HRESULT)0x8A150075) -#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED ((HRESULT)0x8A150076) -#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER ((HRESULT)0x8A150077) -#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT ((HRESULT)0x8A150078) -#define APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND ((HRESULT)0x8A150079) -#define APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE ((HRESULT)0x8A15007A) -#define APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED ((HRESULT)0x8A15007B) -#define APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED ((HRESULT)0x8A15007C) -#define APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED ((HRESULT)0x8A15007D) -#define APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED ((HRESULT)0x8A15007E) -#define APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED ((HRESULT)0x8A15007F) -#define APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE ((HRESULT)0x8A150080) -#define APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED ((HRESULT)0x8A150081) -#define APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE ((HRESULT)0x8A150082) -#define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED ((HRESULT)0x8A150083) -#define APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED ((HRESULT)0x8A150084) -#define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN ((HRESULT)0x8A150085) -#define APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE ((HRESULT)0x8A150086) -#define APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED ((HRESULT)0x8A150087) -#define APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED ((HRESULT)0x8A150088) -#define APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED ((HRESULT)0x8A150089) -#define APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND ((HRESULT)0x8A15008A) -#define APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED ((HRESULT)0x8A15008B) -#define APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED ((HRESULT)0x8A15008C) -#define APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED ((HRESULT)0x8A15008D) -#define APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH ((HRESULT)0x8A15008E) - -// Install errors. -#define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) -#define APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS ((HRESULT)0x8A150102) -#define APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE ((HRESULT)0x8A150103) -#define APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY ((HRESULT)0x8A150104) -#define APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL ((HRESULT)0x8A150105) -#define APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY ((HRESULT)0x8A150106) -#define APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK ((HRESULT)0x8A150107) -#define APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT ((HRESULT)0x8A150108) -#define APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH ((HRESULT)0x8A150109) -#define APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL ((HRESULT)0x8A15010A) -#define APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED ((HRESULT)0x8A15010B) -#define APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER ((HRESULT)0x8A15010C) -#define APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED ((HRESULT)0x8A15010D) -#define APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE ((HRESULT)0x8A15010E) -#define APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY ((HRESULT)0x8A15010F) -#define APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES ((HRESULT)0x8A150110) -#define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION ((HRESULT)0x8A150111) -#define APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER ((HRESULT)0x8A150112) -#define APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED ((HRESULT)0x8A150113) -#define APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED ((HRESULT)0x8A150114) -#define APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR ((HRESULT)0x8A150115) - -// Status values for check package installed status results. -// Partial success has the success bit(first bit) set to 0. -#define WINGET_INSTALLED_STATUS_ARP_ENTRY_FOUND S_OK -#define WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND ((HRESULT)0x8A150201) -#define WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND S_OK -#define WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE ((HRESULT)0x0A150202) -#define WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND ((HRESULT)0x8A150203) -#define WINGET_INSTALLED_STATUS_FILE_HASH_MATCH S_OK -#define WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH ((HRESULT)0x8A150204) -#define WINGET_INSTALLED_STATUS_FILE_NOT_FOUND ((HRESULT)0x8A150205) -#define WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK ((HRESULT)0x0A150206) -#define WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR ((HRESULT)0x8A150207) - -// Configuration Errors -#define WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE ((HRESULT)0x8A15C001) -#define WINGET_CONFIG_ERROR_INVALID_YAML ((HRESULT)0x8A15C002) -#define WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE ((HRESULT)0x8A15C003) -#define WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION ((HRESULT)0x8A15C004) -#define WINGET_CONFIG_ERROR_SET_APPLY_FAILED ((HRESULT)0x8A15C005) -#define WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER ((HRESULT)0x8A15C006) -#define WINGET_CONFIG_ERROR_MISSING_DEPENDENCY ((HRESULT)0x8A15C007) -#define WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED ((HRESULT)0x8A15C008) -#define WINGET_CONFIG_ERROR_ASSERTION_FAILED ((HRESULT)0x8A15C009) -#define WINGET_CONFIG_ERROR_MANUALLY_SKIPPED ((HRESULT)0x8A15C00A) -#define WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED ((HRESULT)0x8A15C00B) -#define WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE ((HRESULT)0x8A15C00C) -#define WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE ((HRESULT)0x8A15C00D) -#define WINGET_CONFIG_ERROR_MISSING_FIELD ((HRESULT)0x8A15C00E) -#define WINGET_CONFIG_ERROR_TEST_FAILED ((HRESULT)0x8A15C00F) -#define WINGET_CONFIG_ERROR_TEST_NOT_RUN ((HRESULT)0x8A15C010) -#define WINGET_CONFIG_ERROR_GET_FAILED ((HRESULT)0x8A15C011) -#define WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND ((HRESULT)0x8A15C012) -#define WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY ((HRESULT)0x8A15C013) - -// Configuration Processor Errors -#define WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED ((HRESULT)0x8A15C101) -#define WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY ((HRESULT)0x8A15C102) -#define WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES ((HRESULT)0x8A15C103) -#define WINGET_CONFIG_ERROR_UNIT_INVOKE_GET ((HRESULT)0x8A15C104) -#define WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST ((HRESULT)0x8A15C105) -#define WINGET_CONFIG_ERROR_UNIT_INVOKE_SET ((HRESULT)0x8A15C106) -#define WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT ((HRESULT)0x8A15C107) -#define WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE ((HRESULT)0x8A15C108) -#define WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT ((HRESULT)0x8A15C109) -#define WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT ((HRESULT)0x8A15C110) -#define WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN ((HRESULT)0x8A15C111) -#define WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR ((HRESULT)0x8A15C112) -#define WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH ((HRESULT)0x8A15C113) - -namespace AppInstaller -{ - // Gets error messages that are presentable to the user. - std::string GetUserPresentableMessage(const wil::ResultException& re); - std::string GetUserPresentableMessage(const std::exception& e); - std::string GetUserPresentableMessage(HRESULT hr); - -#ifndef WINGET_DISABLE_FOR_FUZZING - std::string GetUserPresentableMessage(const winrt::hresult_error& hre); -#endif - - namespace Errors - { - // Details about an HRESULT - struct HResultInformation - { - constexpr HResultInformation(HRESULT value); - constexpr HResultInformation(HRESULT value, std::string_view symbol); - - virtual ~HResultInformation() = default; - - HRESULT Value() const; - - bool operator<(const HResultInformation& other) const; - - // The symbol will be an empty view if not known. - Utility::LocIndView Symbol() const; - - // Looks up the description of the HRESULT - virtual Utility::LocIndString GetDescription() const; - - // Find information by HRESULT; this lookup is optimized. - static std::unique_ptr Find(HRESULT value); - - // Find information by substring match with the given value. - // This lookup is not optimized, as it should only be needed for the error command. - static std::vector> Find(std::string_view value); - - private: - HRESULT m_value; - std::string_view m_symbol; - }; - - // Gets all of the custom error information for our errors. - std::vector> GetWinGetErrors(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +// Errors is the most ubiquitous header; including the mismatch detection in it should reach everywhere. +#include + +#ifndef WINGET_DISABLE_FOR_FUZZING +#include +#endif + +#include +#include +#include +#include +#include + + +#define APPINSTALLER_CLI_ERROR_FACILITY 0xA15 + +// Changes to any of these errors require the corresponding resource string in winget.resw to be updated. +#define APPINSTALLER_CLI_ERROR_INTERNAL_ERROR ((HRESULT)0x8A150001) +#define APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS ((HRESULT)0x8A150002) +#define APPINSTALLER_CLI_ERROR_COMMAND_FAILED ((HRESULT)0x8A150003) +#define APPINSTALLER_CLI_ERROR_MANIFEST_FAILED ((HRESULT)0x8A150004) +#define APPINSTALLER_CLI_ERROR_CTRL_SIGNAL_RECEIVED ((HRESULT)0x8A150005) +#define APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED ((HRESULT)0x8A150006) +#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_MANIFESTVERSION ((HRESULT)0x8A150007) +#define APPINSTALLER_CLI_ERROR_DOWNLOAD_FAILED ((HRESULT)0x8A150008) +#define APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX ((HRESULT)0x8A150009) +#define APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED ((HRESULT)0x8A15000A) +#define APPINSTALLER_CLI_ERROR_SOURCES_INVALID ((HRESULT)0x8A15000B) +#define APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS ((HRESULT)0x8A15000C) +#define APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE ((HRESULT)0x8A15000D) +#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_BUNDLE ((HRESULT)0x8A15000E) +#define APPINSTALLER_CLI_ERROR_SOURCE_DATA_MISSING ((HRESULT)0x8A15000F) +#define APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER ((HRESULT)0x8A150010) +#define APPINSTALLER_CLI_ERROR_INSTALLER_HASH_MISMATCH ((HRESULT)0x8A150011) +#define APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST ((HRESULT)0x8A150012) +#define APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS ((HRESULT)0x8A150013) +#define APPINSTALLER_CLI_ERROR_NO_APPLICATIONS_FOUND ((HRESULT)0x8A150014) +#define APPINSTALLER_CLI_ERROR_NO_SOURCES_DEFINED ((HRESULT)0x8A150015) +#define APPINSTALLER_CLI_ERROR_MULTIPLE_APPLICATIONS_FOUND ((HRESULT)0x8A150016) +#define APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND ((HRESULT)0x8A150017) +#define APPINSTALLER_CLI_ERROR_EXTENSION_PUBLIC_FAILED ((HRESULT)0x8A150018) +#define APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN ((HRESULT)0x8A150019) +#define APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE ((HRESULT)0x8A15001A) +#define APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY ((HRESULT)0x8A15001B) +#define APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY ((HRESULT)0x8A15001C) +#define APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED ((HRESULT)0x8A15001D) +#define APPINSTALLER_CLI_ERROR_MSSTORE_INSTALL_FAILED ((HRESULT)0x8A15001E) +#define APPINSTALLER_CLI_ERROR_COMPLETE_INPUT_BAD ((HRESULT)0x8A15001F) +#define APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED ((HRESULT)0x8A150020) +#define APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY ((HRESULT)0x8A150021) +#define APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY ((HRESULT)0x8A150022) +#define APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION ((HRESULT)0x8A150023) +#define APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED ((HRESULT)0x8A150024) +#define APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE ((HRESULT)0x8A150025) +#define APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA ((HRESULT)0x8A150026) +#define APPINSTALLER_CLI_ERROR_LIBYAML_ERROR ((HRESULT)0x8A150027) +#define APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_WARNING ((HRESULT)0x8A150028) +#define APPINSTALLER_CLI_ERROR_MANIFEST_VALIDATION_FAILURE ((HRESULT)0x8A150029) +#define APPINSTALLER_CLI_ERROR_INVALID_MANIFEST ((HRESULT)0x8A15002A) +#define APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE ((HRESULT)0x8A15002B) +#define APPINSTALLER_CLI_ERROR_UPDATE_ALL_HAS_FAILURE ((HRESULT)0x8A15002C) +#define APPINSTALLER_CLI_ERROR_INSTALLER_SECURITY_CHECK_FAILED ((HRESULT)0x8A15002D) +#define APPINSTALLER_CLI_ERROR_DOWNLOAD_SIZE_MISMATCH ((HRESULT)0x8A15002E) +#define APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND ((HRESULT)0x8A15002F) +#define APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED ((HRESULT)0x8A150030) +#define APPINSTALLER_CLI_ERROR_ICU_BREAK_ITERATOR_ERROR ((HRESULT)0x8A150031) +#define APPINSTALLER_CLI_ERROR_ICU_CASEMAP_ERROR ((HRESULT)0x8A150032) +#define APPINSTALLER_CLI_ERROR_ICU_REGEX_ERROR ((HRESULT)0x8A150033) +#define APPINSTALLER_CLI_ERROR_IMPORT_INSTALL_FAILED ((HRESULT)0x8A150034) +#define APPINSTALLER_CLI_ERROR_NOT_ALL_PACKAGES_FOUND ((HRESULT)0x8A150035) +#define APPINSTALLER_CLI_ERROR_JSON_INVALID_FILE ((HRESULT)0x8A150036) +#define APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE ((HRESULT)0x8A150037) +#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE ((HRESULT)0x8A150038) +#define APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA ((HRESULT)0x8A150039) +#define APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY ((HRESULT)0x8A15003A) +#define APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR ((HRESULT)0x8A15003B) +#define APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_URL ((HRESULT)0x8A15003C) +#define APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE ((HRESULT)0x8A15003D) +#define APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION ((HRESULT)0x8A15003E) +#define APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE ((HRESULT)0x8A15003F) +#define APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE ((HRESULT)0x8A150040) +#define APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED ((HRESULT)0x8A150041) +#define APPINSTALLER_CLI_ERROR_PROMPT_INPUT_ERROR ((HRESULT)0x8A150042) +#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_SOURCE_REQUEST ((HRESULT)0x8A150043) +#define APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND ((HRESULT)0x8A150044) +#define APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED ((HRESULT)0x8A150045) +#define APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED ((HRESULT)0x8A150046) +#define APPINSTALLER_CLI_ERROR_CUSTOMHEADER_EXCEEDS_MAXLENGTH ((HRESULT)0x8A150047) +#define APPINSTALLER_CLI_ERROR_MISSING_RESOURCE_FILE ((HRESULT)0x8A150048) +#define APPINSTALLER_CLI_ERROR_MSI_INSTALL_FAILED ((HRESULT)0x8A150049) +#define APPINSTALLER_CLI_ERROR_INVALID_MSIEXEC_ARGUMENT ((HRESULT)0x8A15004A) +#define APPINSTALLER_CLI_ERROR_FAILED_TO_OPEN_ALL_SOURCES ((HRESULT)0x8A15004B) +#define APPINSTALLER_CLI_ERROR_DEPENDENCIES_VALIDATION_FAILED ((HRESULT)0x8A15004C) +#define APPINSTALLER_CLI_ERROR_MISSING_PACKAGE ((HRESULT)0x8A15004D) +#define APPINSTALLER_CLI_ERROR_INVALID_TABLE_COLUMN ((HRESULT)0x8A15004E) +#define APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER ((HRESULT)0x8A15004F) +#define APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN ((HRESULT)0x8A150050) +#define APPINSTALLER_CLI_ERROR_ICU_CONVERSION_ERROR ((HRESULT)0x8A150051) +#define APPINSTALLER_CLI_ERROR_PORTABLE_INSTALL_FAILED ((HRESULT)0x8A150052) +#define APPINSTALLER_CLI_ERROR_PORTABLE_REPARSE_POINT_NOT_SUPPORTED ((HRESULT)0x8A150053) +#define APPINSTALLER_CLI_ERROR_PORTABLE_PACKAGE_ALREADY_EXISTS ((HRESULT)0x8A150054) +#define APPINSTALLER_CLI_ERROR_PORTABLE_SYMLINK_PATH_IS_DIRECTORY ((HRESULT)0x8A150055) +#define APPINSTALLER_CLI_ERROR_INSTALLER_PROHIBITS_ELEVATION ((HRESULT)0x8A150056) +#define APPINSTALLER_CLI_ERROR_PORTABLE_UNINSTALL_FAILED ((HRESULT)0x8A150057) +#define APPINSTALLER_CLI_ERROR_ARP_VERSION_VALIDATION_FAILED ((HRESULT)0x8A150058) +#define APPINSTALLER_CLI_ERROR_UNSUPPORTED_ARGUMENT ((HRESULT)0x8A150059) +#define APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL ((HRESULT)0x8A15005A) +#define APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_NOT_FOUND ((HRESULT)0x8A15005B) +#define APPINSTALLER_CLI_ERROR_EXTRACT_ARCHIVE_FAILED ((HRESULT)0x8A15005C) +#define APPINSTALLER_CLI_ERROR_NESTEDINSTALLER_INVALID_PATH ((HRESULT)0x8A15005D) +#define APPINSTALLER_CLI_ERROR_PINNED_CERTIFICATE_MISMATCH ((HRESULT)0x8A15005E) +#define APPINSTALLER_CLI_ERROR_INSTALL_LOCATION_REQUIRED ((HRESULT)0x8A15005F) +#define APPINSTALLER_CLI_ERROR_ARCHIVE_SCAN_FAILED ((HRESULT)0x8A150060) +#define APPINSTALLER_CLI_ERROR_PACKAGE_ALREADY_INSTALLED ((HRESULT)0x8A150061) +#define APPINSTALLER_CLI_ERROR_PIN_ALREADY_EXISTS ((HRESULT)0x8A150062) +#define APPINSTALLER_CLI_ERROR_PIN_DOES_NOT_EXIST ((HRESULT)0x8A150063) +#define APPINSTALLER_CLI_ERROR_CANNOT_OPEN_PINNING_INDEX ((HRESULT)0x8A150064) +#define APPINSTALLER_CLI_ERROR_MULTIPLE_INSTALL_FAILED ((HRESULT)0x8A150065) +#define APPINSTALLER_CLI_ERROR_MULTIPLE_UNINSTALL_FAILED ((HRESULT)0x8A150066) +#define APPINSTALLER_CLI_ERROR_NOT_ALL_QUERIES_FOUND_SINGLE ((HRESULT)0x8A150067) +#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_PINNED ((HRESULT)0x8A150068) +#define APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB ((HRESULT)0x8A150069) +#define APPINSTALLER_CLI_ERROR_APPTERMINATION_RECEIVED ((HRESULT)0x8A15006A) +#define APPINSTALLER_CLI_ERROR_DOWNLOAD_DEPENDENCIES ((HRESULT)0x8A15006B) +#define APPINSTALLER_CLI_ERROR_DOWNLOAD_COMMAND_PROHIBITED ((HRESULT)0x8A15006C) +#define APPINSTALLER_CLI_ERROR_SERVICE_UNAVAILABLE ((HRESULT)0x8A15006D) +#define APPINSTALLER_CLI_ERROR_RESUME_ID_NOT_FOUND ((HRESULT)0x8A15006E) +#define APPINSTALLER_CLI_ERROR_CLIENT_VERSION_MISMATCH ((HRESULT)0x8A15006F) +#define APPINSTALLER_CLI_ERROR_INVALID_RESUME_STATE ((HRESULT)0x8A150070) +#define APPINSTALLER_CLI_ERROR_CANNOT_OPEN_CHECKPOINT_INDEX ((HRESULT)0x8A150071) +#define APPINSTALLER_CLI_ERROR_RESUME_LIMIT_EXCEEDED ((HRESULT)0x8A150072) +#define APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO ((HRESULT)0x8A150073) +#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED ((HRESULT)0x8A150074) +#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED ((HRESULT)0x8A150075) +#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED ((HRESULT)0x8A150076) +#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER ((HRESULT)0x8A150077) +#define APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT ((HRESULT)0x8A150078) +#define APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND ((HRESULT)0x8A150079) +#define APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE ((HRESULT)0x8A15007A) +#define APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED ((HRESULT)0x8A15007B) +#define APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED ((HRESULT)0x8A15007C) +#define APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED ((HRESULT)0x8A15007D) +#define APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED ((HRESULT)0x8A15007E) +#define APPINSTALLER_CLI_ERROR_DISPLAYCATALOG_API_FAILED ((HRESULT)0x8A15007F) +#define APPINSTALLER_CLI_ERROR_NO_APPLICABLE_DISPLAYCATALOG_PACKAGE ((HRESULT)0x8A150080) +#define APPINSTALLER_CLI_ERROR_SFSCLIENT_API_FAILED ((HRESULT)0x8A150081) +#define APPINSTALLER_CLI_ERROR_NO_APPLICABLE_SFSCLIENT_PACKAGE ((HRESULT)0x8A150082) +#define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED ((HRESULT)0x8A150083) +#define APPINSTALLER_CLI_ERROR_SFSCLIENT_PACKAGE_NOT_SUPPORTED ((HRESULT)0x8A150084) +#define APPINSTALLER_CLI_ERROR_LICENSING_API_FAILED_FORBIDDEN ((HRESULT)0x8A150085) +#define APPINSTALLER_CLI_ERROR_INSTALLER_ZERO_BYTE_FILE ((HRESULT)0x8A150086) +#define APPINSTALLER_CLI_ERROR_FONT_INSTALL_FAILED ((HRESULT)0x8A150087) +#define APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_SUPPORTED ((HRESULT)0x8A150088) +#define APPINSTALLER_CLI_ERROR_FONT_ALREADY_INSTALLED ((HRESULT)0x8A150089) +#define APPINSTALLER_CLI_ERROR_FONT_FILE_NOT_FOUND ((HRESULT)0x8A15008A) +#define APPINSTALLER_CLI_ERROR_FONT_UNINSTALL_FAILED ((HRESULT)0x8A15008B) +#define APPINSTALLER_CLI_ERROR_FONT_VALIDATION_FAILED ((HRESULT)0x8A15008C) +#define APPINSTALLER_CLI_ERROR_FONT_ROLLBACK_FAILED ((HRESULT)0x8A15008D) +#define APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH ((HRESULT)0x8A15008E) + +// Install errors. +#define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE ((HRESULT)0x8A150101) +#define APPINSTALLER_CLI_ERROR_INSTALL_INSTALL_IN_PROGRESS ((HRESULT)0x8A150102) +#define APPINSTALLER_CLI_ERROR_INSTALL_FILE_IN_USE ((HRESULT)0x8A150103) +#define APPINSTALLER_CLI_ERROR_INSTALL_MISSING_DEPENDENCY ((HRESULT)0x8A150104) +#define APPINSTALLER_CLI_ERROR_INSTALL_DISK_FULL ((HRESULT)0x8A150105) +#define APPINSTALLER_CLI_ERROR_INSTALL_INSUFFICIENT_MEMORY ((HRESULT)0x8A150106) +#define APPINSTALLER_CLI_ERROR_INSTALL_NO_NETWORK ((HRESULT)0x8A150107) +#define APPINSTALLER_CLI_ERROR_INSTALL_CONTACT_SUPPORT ((HRESULT)0x8A150108) +#define APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_TO_FINISH ((HRESULT)0x8A150109) +#define APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_REQUIRED_FOR_INSTALL ((HRESULT)0x8A15010A) +#define APPINSTALLER_CLI_ERROR_INSTALL_REBOOT_INITIATED ((HRESULT)0x8A15010B) +#define APPINSTALLER_CLI_ERROR_INSTALL_CANCELLED_BY_USER ((HRESULT)0x8A15010C) +#define APPINSTALLER_CLI_ERROR_INSTALL_ALREADY_INSTALLED ((HRESULT)0x8A15010D) +#define APPINSTALLER_CLI_ERROR_INSTALL_DOWNGRADE ((HRESULT)0x8A15010E) +#define APPINSTALLER_CLI_ERROR_INSTALL_BLOCKED_BY_POLICY ((HRESULT)0x8A15010F) +#define APPINSTALLER_CLI_ERROR_INSTALL_DEPENDENCIES ((HRESULT)0x8A150110) +#define APPINSTALLER_CLI_ERROR_INSTALL_PACKAGE_IN_USE_BY_APPLICATION ((HRESULT)0x8A150111) +#define APPINSTALLER_CLI_ERROR_INSTALL_INVALID_PARAMETER ((HRESULT)0x8A150112) +#define APPINSTALLER_CLI_ERROR_INSTALL_SYSTEM_NOT_SUPPORTED ((HRESULT)0x8A150113) +#define APPINSTALLER_CLI_ERROR_INSTALL_UPGRADE_NOT_SUPPORTED ((HRESULT)0x8A150114) +#define APPINSTALLER_CLI_ERROR_INSTALL_CUSTOM_ERROR ((HRESULT)0x8A150115) + +// Status values for check package installed status results. +// Partial success has the success bit(first bit) set to 0. +#define WINGET_INSTALLED_STATUS_ARP_ENTRY_FOUND S_OK +#define WINGET_INSTALLED_STATUS_ARP_ENTRY_NOT_FOUND ((HRESULT)0x8A150201) +#define WINGET_INSTALLED_STATUS_INSTALL_LOCATION_FOUND S_OK +#define WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_APPLICABLE ((HRESULT)0x0A150202) +#define WINGET_INSTALLED_STATUS_INSTALL_LOCATION_NOT_FOUND ((HRESULT)0x8A150203) +#define WINGET_INSTALLED_STATUS_FILE_HASH_MATCH S_OK +#define WINGET_INSTALLED_STATUS_FILE_HASH_MISMATCH ((HRESULT)0x8A150204) +#define WINGET_INSTALLED_STATUS_FILE_NOT_FOUND ((HRESULT)0x8A150205) +#define WINGET_INSTALLED_STATUS_FILE_FOUND_WITHOUT_HASH_CHECK ((HRESULT)0x0A150206) +#define WINGET_INSTALLED_STATUS_FILE_ACCESS_ERROR ((HRESULT)0x8A150207) + +// Configuration Errors +#define WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE ((HRESULT)0x8A15C001) +#define WINGET_CONFIG_ERROR_INVALID_YAML ((HRESULT)0x8A15C002) +#define WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE ((HRESULT)0x8A15C003) +#define WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION ((HRESULT)0x8A15C004) +#define WINGET_CONFIG_ERROR_SET_APPLY_FAILED ((HRESULT)0x8A15C005) +#define WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER ((HRESULT)0x8A15C006) +#define WINGET_CONFIG_ERROR_MISSING_DEPENDENCY ((HRESULT)0x8A15C007) +#define WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED ((HRESULT)0x8A15C008) +#define WINGET_CONFIG_ERROR_ASSERTION_FAILED ((HRESULT)0x8A15C009) +#define WINGET_CONFIG_ERROR_MANUALLY_SKIPPED ((HRESULT)0x8A15C00A) +#define WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED ((HRESULT)0x8A15C00B) +#define WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE ((HRESULT)0x8A15C00C) +#define WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE ((HRESULT)0x8A15C00D) +#define WINGET_CONFIG_ERROR_MISSING_FIELD ((HRESULT)0x8A15C00E) +#define WINGET_CONFIG_ERROR_TEST_FAILED ((HRESULT)0x8A15C00F) +#define WINGET_CONFIG_ERROR_TEST_NOT_RUN ((HRESULT)0x8A15C010) +#define WINGET_CONFIG_ERROR_GET_FAILED ((HRESULT)0x8A15C011) +#define WINGET_CONFIG_ERROR_HISTORY_ITEM_NOT_FOUND ((HRESULT)0x8A15C012) +#define WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY ((HRESULT)0x8A15C013) + +// Configuration Processor Errors +#define WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED ((HRESULT)0x8A15C101) +#define WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY ((HRESULT)0x8A15C102) +#define WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES ((HRESULT)0x8A15C103) +#define WINGET_CONFIG_ERROR_UNIT_INVOKE_GET ((HRESULT)0x8A15C104) +#define WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST ((HRESULT)0x8A15C105) +#define WINGET_CONFIG_ERROR_UNIT_INVOKE_SET ((HRESULT)0x8A15C106) +#define WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT ((HRESULT)0x8A15C107) +#define WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE ((HRESULT)0x8A15C108) +#define WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT ((HRESULT)0x8A15C109) +#define WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT ((HRESULT)0x8A15C110) +#define WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN ((HRESULT)0x8A15C111) +#define WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR ((HRESULT)0x8A15C112) +#define WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH ((HRESULT)0x8A15C113) + +namespace AppInstaller +{ + // Gets error messages that are presentable to the user. + std::string GetUserPresentableMessage(const wil::ResultException& re); + std::string GetUserPresentableMessage(const std::exception& e); + std::string GetUserPresentableMessage(HRESULT hr); + +#ifndef WINGET_DISABLE_FOR_FUZZING + std::string GetUserPresentableMessage(const winrt::hresult_error& hre); +#endif + + namespace Errors + { + // Details about an HRESULT + struct HResultInformation + { + constexpr HResultInformation(HRESULT value); + constexpr HResultInformation(HRESULT value, std::string_view symbol); + + virtual ~HResultInformation() = default; + + HRESULT Value() const; + + bool operator<(const HResultInformation& other) const; + + // The symbol will be an empty view if not known. + Utility::LocIndView Symbol() const; + + // Looks up the description of the HRESULT + virtual Utility::LocIndString GetDescription() const; + + // Find information by HRESULT; this lookup is optimized. + static std::unique_ptr Find(HRESULT value); + + // Find information by substring match with the given value. + // This lookup is not optimized, as it should only be needed for the error command. + static std::vector> Find(std::string_view value); + + private: + HRESULT m_value; + std::string_view m_symbol; + }; + + // Gets all of the custom error information for our errors. + std::vector> GetWinGetErrors(); + } +} diff --git a/src/AppInstallerSharedLib/Public/AppInstallerLanguageUtilities.h b/src/AppInstallerSharedLib/Public/AppInstallerLanguageUtilities.h index 7338886a17..505a4eb00d 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerLanguageUtilities.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerLanguageUtilities.h @@ -1,245 +1,245 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller -{ - // A helper type that resets itself when it is moved from. - template - struct ResetWhenMovedFrom - { - ResetWhenMovedFrom() : m_var{} {} - - ResetWhenMovedFrom(T t) : m_var{ t } {} - - // Not copyable - ResetWhenMovedFrom(const ResetWhenMovedFrom&) = delete; - ResetWhenMovedFrom& operator=(const ResetWhenMovedFrom&) = delete; - - ResetWhenMovedFrom(ResetWhenMovedFrom&& other) noexcept : - m_var(std::move(other.m_var)) - { - other.m_var = T{}; - } - - ResetWhenMovedFrom& operator=(ResetWhenMovedFrom&& other) noexcept - { - m_var = std::move(other.m_var); - other.m_var = T{}; - return *this; - } - - operator T& () { return m_var; } - operator const T& () const { return m_var; } - - private: - T m_var; - }; - - // Enables a bool to be used as a destruction indicator. - // Default construction *sets the value to false!* - using DestructionToken = ResetWhenMovedFrom; - - // Enable use of folding to execute functions across parameter packs. - struct FoldHelper - { - template - FoldHelper& operator,(T&&) { return *this; } - }; - - // Get the integral value for an enum. - template - constexpr inline std::enable_if_t, std::underlying_type_t> ToIntegral(E e) - { - return static_cast>(e); - } - - // Get the enum value for an integral. - template - constexpr inline std::enable_if_t, E> ToEnum(std::underlying_type_t ut) - { - return static_cast(ut); - } - - // Enum based variant helper. - // Enum must be an enum whose first member has the value 0, each subsequent member increases by 1, and the final member is named Max. - // Mapping is a template type that takes one template parameter of type Enum, and whose members define value_t as the type for that enum value. - template typename Mapping> - struct EnumBasedVariant - { - private: - // Used to deduce the variant type; making a variant that includes std::monostate and all Mapping types. - template - static inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } - - public: - // Holds data of any type listed in Mapping. - using variant_t = decltype(Deduce(std::make_index_sequence(Enum::Max)>())); - - // Gets the index into the variant for the given Data. - static constexpr inline size_t Index(Enum e) { return static_cast(e) + 1; } - }; - - // An action that can be taken on an EnumBasedVariantMap. - enum class EnumBasedVariantMapAction - { - Add, - Contains, - Get, - }; - - // A callback function that can be used for logging map actions. - template - using EnumBasedVariantMapActionCallback = void (*)(const void* map, Enum value, EnumBasedVariantMapAction action); - - // Provides a map of the Enum to the mapped types. - template typename Mapping, EnumBasedVariantMapActionCallback Callback = nullptr> - struct EnumBasedVariantMap - { - using Variant = EnumBasedVariant; - - template - using mapping_t = typename Mapping::value_t; - - // Adds a value to the map, or overwrites an existing entry. - // This must be used to create the initial data entry, but Get can be used to modify. - template - void Add(mapping_t&& v) - { - if constexpr (Callback) - { - Callback(this, E, EnumBasedVariantMapAction::Add); - } - m_data[E].emplace(std::move(std::forward>(v))); - } - - template - void Add(const mapping_t& v) - { - if constexpr (Callback) - { - Callback(this, E, EnumBasedVariantMapAction::Add); - } - m_data[E].emplace(v); - } - - // Return a value indicating whether the given enum is stored in the map. - bool Contains(Enum e) const - { - if constexpr (Callback) - { - Callback(this, e, EnumBasedVariantMapAction::Contains); - } - return (m_data.find(e) != m_data.end()); - } - - // Gets the value. - template - mapping_t& Get() - { - if constexpr (Callback) - { - Callback(this, E, EnumBasedVariantMapAction::Get); - } - return std::get(GetVariant(E)); - } - - template - const mapping_t& Get() const - { - if constexpr (Callback) - { - Callback(this, E, EnumBasedVariantMapAction::Get); - } - return std::get(GetVariant(E)); - } - - private: - typename Variant::variant_t& GetVariant(Enum e) - { - auto itr = m_data.find(e); - THROW_HR_IF_MSG(E_NOT_SET, itr == m_data.end(), "GetVariant(%d)", static_cast(e)); - return itr->second; - } - - const typename Variant::variant_t& GetVariant(Enum e) const - { - auto itr = m_data.find(e); - THROW_HR_IF_MSG(E_NOT_SET, itr == m_data.cend(), "GetVariant(%d)", static_cast(e)); - return itr->second; - } - - std::map m_data; - }; - - template - std::vector GetAllSequentialEnumValues(E initialToSkip) - { - std::vector result; - using underlying_t = std::underlying_type_t; - - for (underlying_t i = 1 + static_cast(initialToSkip); i < static_cast(E::Max); ++i) - { - result.emplace_back(static_cast(i)); - } - - return result; - } - - template - std::vector GetAllExponentialEnumValues(E initialToSkip) - { - std::vector result; - using underlying_t = std::underlying_type_t; - - for (underlying_t i = 1 + static_cast(initialToSkip); i < static_cast(E::Max); i <<= 1) - { - result.emplace_back(static_cast(i)); - } - - return result; - } - - // Returns the number of flag-bit enum values between initialToSkip and Max, - // computed at compile time. - template - constexpr size_t GetExponentialEnumValuesCount(E initialToSkip) - { - using underlying_t = std::underlying_type_t; - size_t count = 0; - for (underlying_t i = 1 + static_cast(initialToSkip); i < static_cast(E::Max); i <<= 1) - { - ++count; - } - return count; - } -} - -// Enable enums to be output generically (as their integral value). -template -std::enable_if_t, std::ostream&> operator<<(std::ostream& out, E e) -{ - return out << AppInstaller::ToIntegral(e); -} - -template -struct CopyConstructibleAtomic : public std::atomic -{ - using std::atomic::atomic; - using std::atomic::operator=; - - CopyConstructibleAtomic(const CopyConstructibleAtomic& other) : - std::atomic(other.load()) {} -}; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller +{ + // A helper type that resets itself when it is moved from. + template + struct ResetWhenMovedFrom + { + ResetWhenMovedFrom() : m_var{} {} + + ResetWhenMovedFrom(T t) : m_var{ t } {} + + // Not copyable + ResetWhenMovedFrom(const ResetWhenMovedFrom&) = delete; + ResetWhenMovedFrom& operator=(const ResetWhenMovedFrom&) = delete; + + ResetWhenMovedFrom(ResetWhenMovedFrom&& other) noexcept : + m_var(std::move(other.m_var)) + { + other.m_var = T{}; + } + + ResetWhenMovedFrom& operator=(ResetWhenMovedFrom&& other) noexcept + { + m_var = std::move(other.m_var); + other.m_var = T{}; + return *this; + } + + operator T& () { return m_var; } + operator const T& () const { return m_var; } + + private: + T m_var; + }; + + // Enables a bool to be used as a destruction indicator. + // Default construction *sets the value to false!* + using DestructionToken = ResetWhenMovedFrom; + + // Enable use of folding to execute functions across parameter packs. + struct FoldHelper + { + template + FoldHelper& operator,(T&&) { return *this; } + }; + + // Get the integral value for an enum. + template + constexpr inline std::enable_if_t, std::underlying_type_t> ToIntegral(E e) + { + return static_cast>(e); + } + + // Get the enum value for an integral. + template + constexpr inline std::enable_if_t, E> ToEnum(std::underlying_type_t ut) + { + return static_cast(ut); + } + + // Enum based variant helper. + // Enum must be an enum whose first member has the value 0, each subsequent member increases by 1, and the final member is named Max. + // Mapping is a template type that takes one template parameter of type Enum, and whose members define value_t as the type for that enum value. + template typename Mapping> + struct EnumBasedVariant + { + private: + // Used to deduce the variant type; making a variant that includes std::monostate and all Mapping types. + template + static inline auto Deduce(std::index_sequence) { return std::variant(I)>::value_t...>{}; } + + public: + // Holds data of any type listed in Mapping. + using variant_t = decltype(Deduce(std::make_index_sequence(Enum::Max)>())); + + // Gets the index into the variant for the given Data. + static constexpr inline size_t Index(Enum e) { return static_cast(e) + 1; } + }; + + // An action that can be taken on an EnumBasedVariantMap. + enum class EnumBasedVariantMapAction + { + Add, + Contains, + Get, + }; + + // A callback function that can be used for logging map actions. + template + using EnumBasedVariantMapActionCallback = void (*)(const void* map, Enum value, EnumBasedVariantMapAction action); + + // Provides a map of the Enum to the mapped types. + template typename Mapping, EnumBasedVariantMapActionCallback Callback = nullptr> + struct EnumBasedVariantMap + { + using Variant = EnumBasedVariant; + + template + using mapping_t = typename Mapping::value_t; + + // Adds a value to the map, or overwrites an existing entry. + // This must be used to create the initial data entry, but Get can be used to modify. + template + void Add(mapping_t&& v) + { + if constexpr (Callback) + { + Callback(this, E, EnumBasedVariantMapAction::Add); + } + m_data[E].emplace(std::move(std::forward>(v))); + } + + template + void Add(const mapping_t& v) + { + if constexpr (Callback) + { + Callback(this, E, EnumBasedVariantMapAction::Add); + } + m_data[E].emplace(v); + } + + // Return a value indicating whether the given enum is stored in the map. + bool Contains(Enum e) const + { + if constexpr (Callback) + { + Callback(this, e, EnumBasedVariantMapAction::Contains); + } + return (m_data.find(e) != m_data.end()); + } + + // Gets the value. + template + mapping_t& Get() + { + if constexpr (Callback) + { + Callback(this, E, EnumBasedVariantMapAction::Get); + } + return std::get(GetVariant(E)); + } + + template + const mapping_t& Get() const + { + if constexpr (Callback) + { + Callback(this, E, EnumBasedVariantMapAction::Get); + } + return std::get(GetVariant(E)); + } + + private: + typename Variant::variant_t& GetVariant(Enum e) + { + auto itr = m_data.find(e); + THROW_HR_IF_MSG(E_NOT_SET, itr == m_data.end(), "GetVariant(%d)", static_cast(e)); + return itr->second; + } + + const typename Variant::variant_t& GetVariant(Enum e) const + { + auto itr = m_data.find(e); + THROW_HR_IF_MSG(E_NOT_SET, itr == m_data.cend(), "GetVariant(%d)", static_cast(e)); + return itr->second; + } + + std::map m_data; + }; + + template + std::vector GetAllSequentialEnumValues(E initialToSkip) + { + std::vector result; + using underlying_t = std::underlying_type_t; + + for (underlying_t i = 1 + static_cast(initialToSkip); i < static_cast(E::Max); ++i) + { + result.emplace_back(static_cast(i)); + } + + return result; + } + + template + std::vector GetAllExponentialEnumValues(E initialToSkip) + { + std::vector result; + using underlying_t = std::underlying_type_t; + + for (underlying_t i = 1 + static_cast(initialToSkip); i < static_cast(E::Max); i <<= 1) + { + result.emplace_back(static_cast(i)); + } + + return result; + } + + // Returns the number of flag-bit enum values between initialToSkip and Max, + // computed at compile time. + template + constexpr size_t GetExponentialEnumValuesCount(E initialToSkip) + { + using underlying_t = std::underlying_type_t; + size_t count = 0; + for (underlying_t i = 1 + static_cast(initialToSkip); i < static_cast(E::Max); i <<= 1) + { + ++count; + } + return count; + } +} + +// Enable enums to be output generically (as their integral value). +template +std::enable_if_t, std::ostream&> operator<<(std::ostream& out, E e) +{ + return out << AppInstaller::ToIntegral(e); +} + +template +struct CopyConstructibleAtomic : public std::atomic +{ + using std::atomic::atomic; + using std::atomic::operator=; + + CopyConstructibleAtomic(const CopyConstructibleAtomic& other) : + std::atomic(other.load()) {} +}; diff --git a/src/AppInstallerSharedLib/Public/AppInstallerLogging.h b/src/AppInstallerSharedLib/Public/AppInstallerLogging.h index 39dbed0b6f..6b95a002e2 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerLogging.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerLogging.h @@ -1,244 +1,244 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define AICLI_LOG_DIRECT(_logger_,_channel_,_level_,_outstream_) \ - do { \ - auto _aicli_log_channel = AppInstaller::Logging::Channel:: _channel_; \ - auto _aicli_log_level = AppInstaller::Logging::Level:: _level_; \ - auto& _aicli_log_log = _logger_; \ - if (_aicli_log_log.IsEnabled(_aicli_log_channel, _aicli_log_level)) \ - { \ - AppInstaller::Logging::LoggingStream _aicli_log_strstr; \ - _aicli_log_strstr _outstream_; \ - _aicli_log_log.Write(_aicli_log_channel, _aicli_log_level, _aicli_log_strstr.str()); \ - } \ - } while (0, 0) - -#define AICLI_LOG(_channel_,_level_,_outstream_) AICLI_LOG_DIRECT(AppInstaller::Logging::Log(),_channel_,_level_,_outstream_) - -// Consider using this macro when the string might be larger than 4K. -// The normal macro has some buffering that occurs; it can cut off larger strings and is slower. -#define AICLI_LOG_LARGE_STRING(_channel_,_level_,_headerStream_,_largeString_) \ - do { \ - auto _aicli_log_channel = AppInstaller::Logging::Channel:: _channel_; \ - auto _aicli_log_level = AppInstaller::Logging::Level:: _level_; \ - auto& _aicli_log_log = AppInstaller::Logging::Log(); \ - if (_aicli_log_log.IsEnabled(_aicli_log_channel, _aicli_log_level)) \ - { \ - AppInstaller::Logging::LoggingStream _aicli_log_strstr; \ - _aicli_log_strstr _headerStream_; \ - _aicli_log_log.Write(_aicli_log_channel, _aicli_log_level, _aicli_log_strstr.str()); \ - _aicli_log_log.WriteDirect(_aicli_log_channel, _aicli_log_level, _largeString_); \ - } \ - } while (0, 0) - -namespace AppInstaller::Logging -{ - // The channel that the log is from. - // Channels enable large groups of logs to be enabled or disabled together. - enum class Channel : uint32_t - { - Fail = 0x1, - CLI = 0x2, - SQL = 0x4, - Repo = 0x8, - YAML = 0x10, - Core = 0x20, - Test = 0x40, - Config = 0x80, - Workflow = 0x100, - None = 0, - All = 0xFFFFFFFF, - Defaults = All & ~(SQL | Workflow), - }; - - DEFINE_ENUM_FLAG_OPERATORS(Channel); - - // Gets the channel's name as a string. - std::string_view GetChannelName(Channel channel); - - // Gets the channel from it's name. - Channel GetChannelFromName(std::string_view channel); - - // Gets the maximum channel name length in characters. - size_t GetMaxChannelNameLength(); - - // The level of the log. - enum class Level - { - Verbose, - Info, - Warning, - Error, - Crit, - }; - - // Gets the single-character level marker written to log files: V/I/W/E/C. - char GetLevelChar(Level level); - - enum class LogNameStrategy - { - // The log name is the name of the manifest with a timestamp - Manifest, - // The log name is just a timestamp - Timestamp, - // The log name is a GUID - Guid, - // The log name is the first 8 characters of a GUID - ShortGuid, - }; - - // Indicates a location of significance in the logging stream. - enum class Tag - { - // The initial set of logging has been completed. - HeadersComplete, - }; - - // The interface that a log target must implement. - struct ILogger - { - virtual ~ILogger() = default; - - // Gets the name of the logger for internal use. - virtual std::string GetName() const = 0; - - // Informs the logger of the given log. - virtual void Write(Channel channel, Level level, std::string_view message) noexcept = 0; - - // Informs the logger of the given log with the intention that no buffering occurs (in winget code). - virtual void WriteDirect(Channel channel, Level level, std::string_view message) noexcept = 0; - - // Indicates that the given tag location has occurred. - virtual void SetTag(Tag) noexcept {} - }; - - // This type contains the set of loggers that diagnostic logging will be sent to. - // Each binary that leverages it must configure any loggers and filters to their - // desired level, as nothing is enabled by default. - struct DiagnosticLogger - { - DiagnosticLogger() = default; - - ~DiagnosticLogger() = default; - - DiagnosticLogger(const DiagnosticLogger&) = delete; - DiagnosticLogger& operator=(const DiagnosticLogger&) = delete; - - DiagnosticLogger(DiagnosticLogger&&) = delete; - DiagnosticLogger& operator=(DiagnosticLogger&&) = delete; - - // Gets the singleton instance of this type. - static DiagnosticLogger& GetInstance(); - - // NOTE: The logger management functionality is *SINGLE THREAD SAFE*. - // This includes with logging itself. - // As it is not expected that adding/removing loggers is an - // extremely frequent operation, no care has been made to protect - // it from modifying loggers while logging may be occurring. - - // Adds a logger to the active set. - void AddLogger(std::unique_ptr&& logger); - - // Determines if a logger with the given name is present. - bool ContainsLogger(const std::string& name); - - // Removes a logger from the active set, returning it. - std::unique_ptr RemoveLogger(const std::string& name); - - // Removes all loggers. - void RemoveAllLoggers(); - - // Enables the given channel(s), in addition to the currently enabled channels. - void EnableChannel(Channel channel); - - // The given channel mask will become the only enabled channels. - void SetEnabledChannels(Channel channel); - - // Disables the given channel. - void DisableChannel(Channel channel); - - // Sets the enabled level. - // All levels above this level will be enabled. - // For example; SetLevel(Verbose) will enable all logs. - void SetLevel(Level level); - - // Gets the enabled level. - Level GetLevel() const; - - // Checks whether a given channel and level are enabled. - bool IsEnabled(Channel channel, Level level) const; - - // Writes a log line, if the given channel and level are enabled. - void Write(Channel channel, Level level, std::string_view message); - - // Writes a log line, if the given channel and level are enabled. - // Use to make large logs more efficient by writing directly to the output streams. - void WriteDirect(Channel channel, Level level, std::string_view message); - - // Indicates that the given tag location has occurred. - void SetTag(Tag tag); - - private: - - std::vector> m_loggers; - Channel m_enabledChannels = Channel::None; - Level m_enabledLevel = Level::Info; - }; - - DiagnosticLogger& Log(); - - // Calls the various stream format functions to produce an 8 character hexadecimal output. - std::ostream& SetHRFormat(std::ostream& out); - - // This type allows us to override the default behavior of output operators for logging. - struct LoggingStream - { - // Force use of the UTF-8 string from a file path. - // This should not be necessary when we move to C++20 and convert to using u8string. - friend AppInstaller::Logging::LoggingStream& operator<<(AppInstaller::Logging::LoggingStream& out, const std::filesystem::path& path) - { - out.m_out << path.u8string(); - return out; - } - - // Enums - template - friend std::enable_if_t>, AppInstaller::Logging::LoggingStream&> - operator<<(AppInstaller::Logging::LoggingStream& out, T t) - { - out.m_out << ToIntegral(t); - return out; - } - - // Everything else. - template - friend std::enable_if_t, std::filesystem::path>, std::is_enum>>, AppInstaller::Logging::LoggingStream&> - operator<<(AppInstaller::Logging::LoggingStream& out, T&& t) - { - out.m_out << std::forward(t); - return out; - } - - std::string str() const { return m_out.str(); } - - private: - std::stringstream m_out; - }; -} - -namespace std -{ - std::ostream& operator<<(std::ostream& out, const std::chrono::system_clock::time_point& time); - std::ostream& operator<<(std::ostream& out, const GUID& guid); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define AICLI_LOG_DIRECT(_logger_,_channel_,_level_,_outstream_) \ + do { \ + auto _aicli_log_channel = AppInstaller::Logging::Channel:: _channel_; \ + auto _aicli_log_level = AppInstaller::Logging::Level:: _level_; \ + auto& _aicli_log_log = _logger_; \ + if (_aicli_log_log.IsEnabled(_aicli_log_channel, _aicli_log_level)) \ + { \ + AppInstaller::Logging::LoggingStream _aicli_log_strstr; \ + _aicli_log_strstr _outstream_; \ + _aicli_log_log.Write(_aicli_log_channel, _aicli_log_level, _aicli_log_strstr.str()); \ + } \ + } while (0, 0) + +#define AICLI_LOG(_channel_,_level_,_outstream_) AICLI_LOG_DIRECT(AppInstaller::Logging::Log(),_channel_,_level_,_outstream_) + +// Consider using this macro when the string might be larger than 4K. +// The normal macro has some buffering that occurs; it can cut off larger strings and is slower. +#define AICLI_LOG_LARGE_STRING(_channel_,_level_,_headerStream_,_largeString_) \ + do { \ + auto _aicli_log_channel = AppInstaller::Logging::Channel:: _channel_; \ + auto _aicli_log_level = AppInstaller::Logging::Level:: _level_; \ + auto& _aicli_log_log = AppInstaller::Logging::Log(); \ + if (_aicli_log_log.IsEnabled(_aicli_log_channel, _aicli_log_level)) \ + { \ + AppInstaller::Logging::LoggingStream _aicli_log_strstr; \ + _aicli_log_strstr _headerStream_; \ + _aicli_log_log.Write(_aicli_log_channel, _aicli_log_level, _aicli_log_strstr.str()); \ + _aicli_log_log.WriteDirect(_aicli_log_channel, _aicli_log_level, _largeString_); \ + } \ + } while (0, 0) + +namespace AppInstaller::Logging +{ + // The channel that the log is from. + // Channels enable large groups of logs to be enabled or disabled together. + enum class Channel : uint32_t + { + Fail = 0x1, + CLI = 0x2, + SQL = 0x4, + Repo = 0x8, + YAML = 0x10, + Core = 0x20, + Test = 0x40, + Config = 0x80, + Workflow = 0x100, + None = 0, + All = 0xFFFFFFFF, + Defaults = All & ~(SQL | Workflow), + }; + + DEFINE_ENUM_FLAG_OPERATORS(Channel); + + // Gets the channel's name as a string. + std::string_view GetChannelName(Channel channel); + + // Gets the channel from it's name. + Channel GetChannelFromName(std::string_view channel); + + // Gets the maximum channel name length in characters. + size_t GetMaxChannelNameLength(); + + // The level of the log. + enum class Level + { + Verbose, + Info, + Warning, + Error, + Crit, + }; + + // Gets the single-character level marker written to log files: V/I/W/E/C. + char GetLevelChar(Level level); + + enum class LogNameStrategy + { + // The log name is the name of the manifest with a timestamp + Manifest, + // The log name is just a timestamp + Timestamp, + // The log name is a GUID + Guid, + // The log name is the first 8 characters of a GUID + ShortGuid, + }; + + // Indicates a location of significance in the logging stream. + enum class Tag + { + // The initial set of logging has been completed. + HeadersComplete, + }; + + // The interface that a log target must implement. + struct ILogger + { + virtual ~ILogger() = default; + + // Gets the name of the logger for internal use. + virtual std::string GetName() const = 0; + + // Informs the logger of the given log. + virtual void Write(Channel channel, Level level, std::string_view message) noexcept = 0; + + // Informs the logger of the given log with the intention that no buffering occurs (in winget code). + virtual void WriteDirect(Channel channel, Level level, std::string_view message) noexcept = 0; + + // Indicates that the given tag location has occurred. + virtual void SetTag(Tag) noexcept {} + }; + + // This type contains the set of loggers that diagnostic logging will be sent to. + // Each binary that leverages it must configure any loggers and filters to their + // desired level, as nothing is enabled by default. + struct DiagnosticLogger + { + DiagnosticLogger() = default; + + ~DiagnosticLogger() = default; + + DiagnosticLogger(const DiagnosticLogger&) = delete; + DiagnosticLogger& operator=(const DiagnosticLogger&) = delete; + + DiagnosticLogger(DiagnosticLogger&&) = delete; + DiagnosticLogger& operator=(DiagnosticLogger&&) = delete; + + // Gets the singleton instance of this type. + static DiagnosticLogger& GetInstance(); + + // NOTE: The logger management functionality is *SINGLE THREAD SAFE*. + // This includes with logging itself. + // As it is not expected that adding/removing loggers is an + // extremely frequent operation, no care has been made to protect + // it from modifying loggers while logging may be occurring. + + // Adds a logger to the active set. + void AddLogger(std::unique_ptr&& logger); + + // Determines if a logger with the given name is present. + bool ContainsLogger(const std::string& name); + + // Removes a logger from the active set, returning it. + std::unique_ptr RemoveLogger(const std::string& name); + + // Removes all loggers. + void RemoveAllLoggers(); + + // Enables the given channel(s), in addition to the currently enabled channels. + void EnableChannel(Channel channel); + + // The given channel mask will become the only enabled channels. + void SetEnabledChannels(Channel channel); + + // Disables the given channel. + void DisableChannel(Channel channel); + + // Sets the enabled level. + // All levels above this level will be enabled. + // For example; SetLevel(Verbose) will enable all logs. + void SetLevel(Level level); + + // Gets the enabled level. + Level GetLevel() const; + + // Checks whether a given channel and level are enabled. + bool IsEnabled(Channel channel, Level level) const; + + // Writes a log line, if the given channel and level are enabled. + void Write(Channel channel, Level level, std::string_view message); + + // Writes a log line, if the given channel and level are enabled. + // Use to make large logs more efficient by writing directly to the output streams. + void WriteDirect(Channel channel, Level level, std::string_view message); + + // Indicates that the given tag location has occurred. + void SetTag(Tag tag); + + private: + + std::vector> m_loggers; + Channel m_enabledChannels = Channel::None; + Level m_enabledLevel = Level::Info; + }; + + DiagnosticLogger& Log(); + + // Calls the various stream format functions to produce an 8 character hexadecimal output. + std::ostream& SetHRFormat(std::ostream& out); + + // This type allows us to override the default behavior of output operators for logging. + struct LoggingStream + { + // Force use of the UTF-8 string from a file path. + // This should not be necessary when we move to C++20 and convert to using u8string. + friend AppInstaller::Logging::LoggingStream& operator<<(AppInstaller::Logging::LoggingStream& out, const std::filesystem::path& path) + { + out.m_out << path.u8string(); + return out; + } + + // Enums + template + friend std::enable_if_t>, AppInstaller::Logging::LoggingStream&> + operator<<(AppInstaller::Logging::LoggingStream& out, T t) + { + out.m_out << ToIntegral(t); + return out; + } + + // Everything else. + template + friend std::enable_if_t, std::filesystem::path>, std::is_enum>>, AppInstaller::Logging::LoggingStream&> + operator<<(AppInstaller::Logging::LoggingStream& out, T&& t) + { + out.m_out << std::forward(t); + return out; + } + + std::string str() const { return m_out.str(); } + + private: + std::stringstream m_out; + }; +} + +namespace std +{ + std::ostream& operator<<(std::ostream& out, const std::chrono::system_clock::time_point& time); + std::ostream& operator<<(std::ostream& out, const GUID& guid); +} diff --git a/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h b/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h index 2272683b8b..ce794d0374 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerSHA256.h @@ -1,95 +1,95 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Utility { - - // Forward declaration of type defined within PAL - struct SHA256Context; - - // Class used to compute SHA256 hashes over various sets of data. - // Create one and Add data to it if the data is not all available, - // or simply call ComputeHash if the data is all in memory. - class SHA256 - { - public: - using HashBuffer = std::vector; - constexpr static size_t HashBufferSizeInBytes = 32; - constexpr static size_t HashStringSizeInChars = 64; - - struct HashDetails - { - HashBuffer Hash; - uint64_t SizeInBytes = 0; - }; - - SHA256(); - - // Adds the next chunk of data to the hash. - void Add(const uint8_t* buffer, size_t cbBuffer); - - inline void Add(const std::vector& buffer) - { - Add(buffer.data(), buffer.size()); - } - - // Gets the hash of the data. This is a destructive action; the accumulated hash - // value will be returned and the object can no longer be used. - void Get(HashBuffer& hash); - - inline HashBuffer Get() - { - HashBuffer result{}; - Get(result); - return result; - } - - // Computes the hash of the given buffer immediately. - static HashBuffer ComputeHash(const uint8_t* buffer, std::uint32_t cbBuffer); - - // Computes the hash of the given buffer immediately. - static HashBuffer ComputeHash(const std::vector& buffer); - - // Computes the hash of the given string immediately. - static HashBuffer ComputeHash(std::string_view buffer); - - // Computes the hash from a given stream. - static HashBuffer ComputeHash(std::istream& in); - - // Computes the hash from a given stream. - static HashDetails ComputeHashDetails(std::istream& in); - - // Computes the hash from a given file path. - static HashBuffer ComputeHashFromFile(const std::filesystem::path& path); - - // Computes the hash from an open file HANDLE by reading sequentially from the current position. - // The caller retains ownership of the handle. - static HashBuffer ComputeHashFromHandle(HANDLE fileHandle); - - static std::string ConvertToString(const HashBuffer& hashBuffer); - - static std::wstring ConvertToWideString(const HashBuffer& hashBuffer); - - static HashBuffer ConvertToBytes(const std::string& hashStr); - static HashBuffer ConvertToBytes(const std::wstring& hashStr); - - // Returns a value indicating whether the two hashes are equal. - static bool AreEqual(const HashBuffer& first, const HashBuffer& second); - - private: - void EnsureNotFinished() const; - - struct SHA256ContextDeleter - { - void operator()(SHA256Context* context); - }; - - std::unique_ptr context; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Utility { + + // Forward declaration of type defined within PAL + struct SHA256Context; + + // Class used to compute SHA256 hashes over various sets of data. + // Create one and Add data to it if the data is not all available, + // or simply call ComputeHash if the data is all in memory. + class SHA256 + { + public: + using HashBuffer = std::vector; + constexpr static size_t HashBufferSizeInBytes = 32; + constexpr static size_t HashStringSizeInChars = 64; + + struct HashDetails + { + HashBuffer Hash; + uint64_t SizeInBytes = 0; + }; + + SHA256(); + + // Adds the next chunk of data to the hash. + void Add(const uint8_t* buffer, size_t cbBuffer); + + inline void Add(const std::vector& buffer) + { + Add(buffer.data(), buffer.size()); + } + + // Gets the hash of the data. This is a destructive action; the accumulated hash + // value will be returned and the object can no longer be used. + void Get(HashBuffer& hash); + + inline HashBuffer Get() + { + HashBuffer result{}; + Get(result); + return result; + } + + // Computes the hash of the given buffer immediately. + static HashBuffer ComputeHash(const uint8_t* buffer, std::uint32_t cbBuffer); + + // Computes the hash of the given buffer immediately. + static HashBuffer ComputeHash(const std::vector& buffer); + + // Computes the hash of the given string immediately. + static HashBuffer ComputeHash(std::string_view buffer); + + // Computes the hash from a given stream. + static HashBuffer ComputeHash(std::istream& in); + + // Computes the hash from a given stream. + static HashDetails ComputeHashDetails(std::istream& in); + + // Computes the hash from a given file path. + static HashBuffer ComputeHashFromFile(const std::filesystem::path& path); + + // Computes the hash from an open file HANDLE by reading sequentially from the current position. + // The caller retains ownership of the handle. + static HashBuffer ComputeHashFromHandle(HANDLE fileHandle); + + static std::string ConvertToString(const HashBuffer& hashBuffer); + + static std::wstring ConvertToWideString(const HashBuffer& hashBuffer); + + static HashBuffer ConvertToBytes(const std::string& hashStr); + static HashBuffer ConvertToBytes(const std::wstring& hashStr); + + // Returns a value indicating whether the two hashes are equal. + static bool AreEqual(const HashBuffer& first, const HashBuffer& second); + + private: + void EnsureNotFinished() const; + + struct SHA256ContextDeleter + { + void operator()(SHA256Context* context); + }; + + std::unique_ptr context; + }; +} diff --git a/src/AppInstallerSharedLib/Public/AppInstallerStrings.h b/src/AppInstallerSharedLib/Public/AppInstallerStrings.h index 34ca3238b9..0f84ff90b6 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerStrings.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerStrings.h @@ -1,332 +1,332 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::Utility -{ - // Converts the given UTF16 string to UTF8 - std::string ConvertToUTF8(std::wstring_view input); - - // Converts the given UTF8 string to UTF16 - std::wstring ConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8); - - // Tries to convert the given UTF8 string to UTF16 - std::optional TryConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8); - - // Converts the given UTF8 string to UTF32 - std::u32string ConvertToUTF32(std::string_view input); - - // Normalizes a UTF8 string to the given form. - std::string Normalize(std::string_view input, NORM_FORM form = NORM_FORM::NormalizationKC); - - // Normalizes a UTF16 string to the given form. - std::wstring Normalize(std::wstring_view input, NORM_FORM form = NORM_FORM::NormalizationKC); - - // Replaces any embedded null character found within the string. - void ReplaceEmbeddedNullCharacters(std::string& s, char c = ' '); - - // Type to hold and force a normalized UTF8 string. - // Also enables further normalization by replacing embedded null characters with spaces. - template - struct NormalizedUTF8 : public std::string - { - NormalizedUTF8() = default; - - template - NormalizedUTF8(const char(&s)[Size]) { AssignValue(std::string_view{ s, (s[Size - 1] == '\0' ? Size - 1 : Size) }); } - - NormalizedUTF8(std::string_view sv) { AssignValue(sv); } - - NormalizedUTF8(std::string& s) { AssignValue(s); } - NormalizedUTF8(const std::string& s) { AssignValue(s); } - NormalizedUTF8(std::string&& s) { AssignValue(s); } - - NormalizedUTF8(std::wstring_view sv) { AssignValue(sv); } - - NormalizedUTF8(const NormalizedUTF8& other) = default; - NormalizedUTF8& operator=(const NormalizedUTF8& other) = default; - - NormalizedUTF8(NormalizedUTF8&& other) = default; - NormalizedUTF8& operator=(NormalizedUTF8&& other) = default; - - template - NormalizedUTF8& operator=(const char(&s)[Size]) - { - AssignValue(std::string_view{ s, (s[Size - 1] == '\0' ? Size - 1 : Size) }); - return *this; - } - - NormalizedUTF8& operator=(std::string_view sv) - { - AssignValue(sv); - return *this; - } - - NormalizedUTF8& operator=(const std::string& s) - { - AssignValue(s); - return *this; - } - - NormalizedUTF8& operator=(std::string&& s) - { - AssignValue(s); - return *this; - } - - private: - void AssignValue(std::string_view sv) - { - assign(Normalize(sv, Form)); - - if constexpr (ConvertEmbeddedNullToSpace) - { - ReplaceEmbeddedNullCharacters(*this); - } - } - - void AssignValue(std::wstring_view sv) - { - assign(ConvertToUTF8(Normalize(sv, Form))); - - if constexpr (ConvertEmbeddedNullToSpace) - { - ReplaceEmbeddedNullCharacters(*this); - } - } - }; - - using NormalizedString = NormalizedUTF8; - - // Compares the two UTF8 strings in a case-insensitive manner. - // Use this if one of the values is a known value, and thus ToLower is sufficient. - bool CaseInsensitiveEquals(std::string_view a, std::string_view b); - - // Compares the two UTF16 strings in a case-insensitive manner. - // Use this if one of the values is a known value, and thus ToLower is sufficient. - bool CaseInsensitiveEquals(std::wstring_view a, std::wstring_view b); - - // Returns if a UTF8 string is contained within a vector in a case-insensitive manner. - bool CaseInsensitiveContains(const std::vector& a, std::string_view b); - - // Determines if string a starts with string b. UTF16. - bool StartsWith(std::wstring_view a, std::wstring_view b); - - // Determines if string a starts with string b. UTF8. - // Use this if one of the values is a known value, and thus ToLower is sufficient. - bool CaseInsensitiveStartsWith(std::string_view a, std::string_view b); - - // Determines if string a starts with string b. UTF16. - // Use this if one of the values is a known value, and thus ToLower is sufficient. - bool CaseInsensitiveStartsWith(std::wstring_view a, std::wstring_view b); - - // Determines if string a contains string b. - // Use this if one of the values is a known value, and thus ToLower is sufficient. - bool CaseInsensitiveContainsSubstring(std::string_view a, std::string_view b); - - // Determines if string a contains string b. - bool ContainsSubstring(std::string_view a, std::string_view b); - - // Compares the two UTF8 strings in a case-insensitive manner, using ICU for case folding. - bool ICUCaseInsensitiveEquals(std::string_view a, std::string_view b); - - // Determines if string a starts with string b, using ICU for case folding. - bool ICUCaseInsensitiveStartsWith(std::string_view a, std::string_view b); - - // Returns the number of grapheme clusters (characters) in an UTF8-encoded string. - size_t UTF8Length(std::string_view input); - - // Returns the number of units the UTF8-encoded string will take in terminal output. Some characters take 2 units in terminal output. - size_t UTF8ColumnWidth(const NormalizedUTF8& input); - - // Returns a substring view in an UTF8-encoded string. Offset and count are measured in grapheme clusters (characters). - std::string_view UTF8Substring(std::string_view input, size_t offset, size_t count); - - // Returns a substring view in an UTF8-encoded string trimmed to be at most expected length. Length is measured as units taken in terminal output. - // Note the returned substring view might be less than specified length as some characters might take 2 units in terminal output. - std::string UTF8TrimRightToColumnWidth(const NormalizedUTF8&, size_t expectedWidth, size_t& actualWidth); - - // Get the lower case version of the given std::string - std::string ToLower(std::string_view in); - - // Get the lower case version of the given std::wstring - std::wstring ToLower(std::wstring_view in); - - // Folds the case of the given std::string - // See https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#case-folding - std::string FoldCase(std::string_view input); - - // Folds the case of the given NormalizedString, returning it as also Normalized - // See https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#case-folding - NormalizedString FoldCase(const NormalizedString& input); - - // Checks if the input string is empty or whitespace - bool IsEmptyOrWhitespace(std::string_view str); - bool IsEmptyOrWhitespace(std::wstring_view str); - - // Find token in the input string and replace with value. - // Returns a value indicating whether a replacement occurred. - bool FindAndReplace(std::string& inputStr, std::string_view token, std::string_view value); - - // Replaces the token in the input string with value while copying to a new result. - std::wstring ReplaceWhileCopying(std::wstring_view input, std::wstring_view token, std::wstring_view value); - - // Removes whitespace from the beginning and end of the string. - std::string& Trim(std::string& str); - std::string Trim(const std::string& str); - std::string Trim(std::string&& str); - std::string_view& Trim(std::string_view& str); - std::string_view Trim(std::string_view&& str); - - // Removes whitespace from the beginning and end of the string. - std::wstring& Trim(std::wstring& str); - std::wstring Trim(const std::wstring& str); - std::wstring Trim(std::wstring&& str); - std::wstring_view& Trim(std::wstring_view& str); - std::wstring_view Trim(std::wstring_view&& str); - - // Reads the entire stream into a string. - std::string ReadEntireStream(std::istream& stream); - - // Reads the entire stream into a byte array. - std::vector ReadEntireStreamAsByteArray(std::istream& stream); - - // Expands environment variables within the input. - std::wstring ExpandEnvironmentVariables(const std::wstring& input); - - // Converts the candidate path part into one suitable for the actual file system - std::string MakeSuitablePathPart(std::string_view candidate); - - // Splits the file name part off of the given URI. - std::pair SplitFileNameFromURI(std::string_view uri); - - // Gets the file name part of the given URI. - std::filesystem::path GetFileNameFromURI(std::string_view uri); - - // Splits the string into words. - std::vector SplitIntoWords(std::string_view input); - - // Splits the string into lines. - // Drops empty lines. - std::vector SplitIntoLines(std::string_view input, size_t maximum = 0); - - // Removes lines from the vector (and/or characters from the last line) so that it contains the maximum number of lines. - // Returns true if changes were made, false if not. - bool LimitOutputLines(std::vector& lines, size_t lineWidth, size_t maximum); - - // Converts a container to a string representation of it. - template - std::string ConvertContainerToString(const T& container, U toString) - { - std::ostringstream strstr; - strstr << '['; - - bool firstItem = true; - for (const auto& item : container) - { - if (firstItem) - { - firstItem = false; - } - else - { - strstr << ", "; - } - - strstr << toString(item); - } - - strstr << ']'; - return strstr.str(); // We need C++20 to get std::move(strstr).str() to extract the string from inside the stream - } - - template - std::string ConvertContainerToString(const T& container) - { - return ConvertContainerToString(container, [](const auto& item) { return item; }); - } - - template - std::basic_string StringOrEmptyIfNull(const CharType* string) - { - if (string) - { - return { string }; - } - else - { - return {}; - } - } - - // Converts the given bytes into a hexadecimal string. - std::string ConvertToHexString(const std::vector& buffer, size_t byteCount = 0); - - // Converts the given hexadecimal string into bytes. - std::vector ParseFromHexString(const std::string& value, size_t byteCount = 0); - - // Join a string vector using the provided separator. - LocIndString Join(LocIndView separator, const std::vector& vector); - - // Join a string vector using the provided separator. - std::string Join(std::string_view separator, const std::vector& vector); - - // Splits the string using the provided separator. Entries can also be trimmed. - std::vector Split(const std::string& input, char separator, bool trim = false); - std::vector SplitView(const std::string& input, char separator, bool trim = false); - std::vector Split(std::string_view input, char separator, bool trim = false); - - // Splits the string using the provided separator. Entries can also be trimmed. - std::vector Split(const std::wstring& input, wchar_t separator, bool trim = false); - std::vector SplitView(const std::wstring& input, wchar_t separator, bool trim = false); - std::vector Split(std::wstring_view input, wchar_t separator, bool trim = false); - - // Format an input string by replacing placeholders {index} with provided values at corresponding indices. - // Note: After upgrading to C++20, this function should be deprecated in favor of std::format. - template - std::string Format(std::string inputStr, T ... args) - { - int index = 0; - (FindAndReplace(inputStr, "{" + std::to_string(index++) + "}", (std::ostringstream() << args).str()),...); - return inputStr; - } - - // Converts the given boolean value to a string. - std::string_view ConvertBoolToString(bool value); - - // Converts the given string view into a bool. - std::optional TryConvertStringToBool(const std::string_view& value); - - // Converts the given string view into an int32. - std::optional TryConvertStringToInt32(const std::string_view& value); - - // Converts the given GUID value to a string. - std::string ConvertGuidToString(const GUID& value); - - // Creates a new GUID and returns the string value. - std::wstring CreateNewGuidNameWString(); - - // Converts the input string to a DWORD value using std::stoul and returns a boolean value based on the resulting DWORD value. - bool IsDwordFlagSet(const std::string& value); - - // Finds the next control code index that would be replaced. - // Returns std::string::npos if not found. - size_t FindControlCodeToConvert(std::string_view input, size_t offset = 0); - - // Converts most control codes in the input to their corresponding control picture in the output. - // Exempts tab, line feed, and carriage return from being replaced. - std::string ConvertControlCodesToPictures(std::string_view input); - - // Generates a random alpha numeric string. - std::string GetRandomString(size_t size = 8); - - // Checks whether a given string is a valid potential Windows feature name. - bool IsValidWindowsFeaturePattern(std::string_view value); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::Utility +{ + // Converts the given UTF16 string to UTF8 + std::string ConvertToUTF8(std::wstring_view input); + + // Converts the given UTF8 string to UTF16 + std::wstring ConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8); + + // Tries to convert the given UTF8 string to UTF16 + std::optional TryConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8); + + // Converts the given UTF8 string to UTF32 + std::u32string ConvertToUTF32(std::string_view input); + + // Normalizes a UTF8 string to the given form. + std::string Normalize(std::string_view input, NORM_FORM form = NORM_FORM::NormalizationKC); + + // Normalizes a UTF16 string to the given form. + std::wstring Normalize(std::wstring_view input, NORM_FORM form = NORM_FORM::NormalizationKC); + + // Replaces any embedded null character found within the string. + void ReplaceEmbeddedNullCharacters(std::string& s, char c = ' '); + + // Type to hold and force a normalized UTF8 string. + // Also enables further normalization by replacing embedded null characters with spaces. + template + struct NormalizedUTF8 : public std::string + { + NormalizedUTF8() = default; + + template + NormalizedUTF8(const char(&s)[Size]) { AssignValue(std::string_view{ s, (s[Size - 1] == '\0' ? Size - 1 : Size) }); } + + NormalizedUTF8(std::string_view sv) { AssignValue(sv); } + + NormalizedUTF8(std::string& s) { AssignValue(s); } + NormalizedUTF8(const std::string& s) { AssignValue(s); } + NormalizedUTF8(std::string&& s) { AssignValue(s); } + + NormalizedUTF8(std::wstring_view sv) { AssignValue(sv); } + + NormalizedUTF8(const NormalizedUTF8& other) = default; + NormalizedUTF8& operator=(const NormalizedUTF8& other) = default; + + NormalizedUTF8(NormalizedUTF8&& other) = default; + NormalizedUTF8& operator=(NormalizedUTF8&& other) = default; + + template + NormalizedUTF8& operator=(const char(&s)[Size]) + { + AssignValue(std::string_view{ s, (s[Size - 1] == '\0' ? Size - 1 : Size) }); + return *this; + } + + NormalizedUTF8& operator=(std::string_view sv) + { + AssignValue(sv); + return *this; + } + + NormalizedUTF8& operator=(const std::string& s) + { + AssignValue(s); + return *this; + } + + NormalizedUTF8& operator=(std::string&& s) + { + AssignValue(s); + return *this; + } + + private: + void AssignValue(std::string_view sv) + { + assign(Normalize(sv, Form)); + + if constexpr (ConvertEmbeddedNullToSpace) + { + ReplaceEmbeddedNullCharacters(*this); + } + } + + void AssignValue(std::wstring_view sv) + { + assign(ConvertToUTF8(Normalize(sv, Form))); + + if constexpr (ConvertEmbeddedNullToSpace) + { + ReplaceEmbeddedNullCharacters(*this); + } + } + }; + + using NormalizedString = NormalizedUTF8; + + // Compares the two UTF8 strings in a case-insensitive manner. + // Use this if one of the values is a known value, and thus ToLower is sufficient. + bool CaseInsensitiveEquals(std::string_view a, std::string_view b); + + // Compares the two UTF16 strings in a case-insensitive manner. + // Use this if one of the values is a known value, and thus ToLower is sufficient. + bool CaseInsensitiveEquals(std::wstring_view a, std::wstring_view b); + + // Returns if a UTF8 string is contained within a vector in a case-insensitive manner. + bool CaseInsensitiveContains(const std::vector& a, std::string_view b); + + // Determines if string a starts with string b. UTF16. + bool StartsWith(std::wstring_view a, std::wstring_view b); + + // Determines if string a starts with string b. UTF8. + // Use this if one of the values is a known value, and thus ToLower is sufficient. + bool CaseInsensitiveStartsWith(std::string_view a, std::string_view b); + + // Determines if string a starts with string b. UTF16. + // Use this if one of the values is a known value, and thus ToLower is sufficient. + bool CaseInsensitiveStartsWith(std::wstring_view a, std::wstring_view b); + + // Determines if string a contains string b. + // Use this if one of the values is a known value, and thus ToLower is sufficient. + bool CaseInsensitiveContainsSubstring(std::string_view a, std::string_view b); + + // Determines if string a contains string b. + bool ContainsSubstring(std::string_view a, std::string_view b); + + // Compares the two UTF8 strings in a case-insensitive manner, using ICU for case folding. + bool ICUCaseInsensitiveEquals(std::string_view a, std::string_view b); + + // Determines if string a starts with string b, using ICU for case folding. + bool ICUCaseInsensitiveStartsWith(std::string_view a, std::string_view b); + + // Returns the number of grapheme clusters (characters) in an UTF8-encoded string. + size_t UTF8Length(std::string_view input); + + // Returns the number of units the UTF8-encoded string will take in terminal output. Some characters take 2 units in terminal output. + size_t UTF8ColumnWidth(const NormalizedUTF8& input); + + // Returns a substring view in an UTF8-encoded string. Offset and count are measured in grapheme clusters (characters). + std::string_view UTF8Substring(std::string_view input, size_t offset, size_t count); + + // Returns a substring view in an UTF8-encoded string trimmed to be at most expected length. Length is measured as units taken in terminal output. + // Note the returned substring view might be less than specified length as some characters might take 2 units in terminal output. + std::string UTF8TrimRightToColumnWidth(const NormalizedUTF8&, size_t expectedWidth, size_t& actualWidth); + + // Get the lower case version of the given std::string + std::string ToLower(std::string_view in); + + // Get the lower case version of the given std::wstring + std::wstring ToLower(std::wstring_view in); + + // Folds the case of the given std::string + // See https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#case-folding + std::string FoldCase(std::string_view input); + + // Folds the case of the given NormalizedString, returning it as also Normalized + // See https://unicode-org.github.io/icu/userguide/transforms/casemappings.html#case-folding + NormalizedString FoldCase(const NormalizedString& input); + + // Checks if the input string is empty or whitespace + bool IsEmptyOrWhitespace(std::string_view str); + bool IsEmptyOrWhitespace(std::wstring_view str); + + // Find token in the input string and replace with value. + // Returns a value indicating whether a replacement occurred. + bool FindAndReplace(std::string& inputStr, std::string_view token, std::string_view value); + + // Replaces the token in the input string with value while copying to a new result. + std::wstring ReplaceWhileCopying(std::wstring_view input, std::wstring_view token, std::wstring_view value); + + // Removes whitespace from the beginning and end of the string. + std::string& Trim(std::string& str); + std::string Trim(const std::string& str); + std::string Trim(std::string&& str); + std::string_view& Trim(std::string_view& str); + std::string_view Trim(std::string_view&& str); + + // Removes whitespace from the beginning and end of the string. + std::wstring& Trim(std::wstring& str); + std::wstring Trim(const std::wstring& str); + std::wstring Trim(std::wstring&& str); + std::wstring_view& Trim(std::wstring_view& str); + std::wstring_view Trim(std::wstring_view&& str); + + // Reads the entire stream into a string. + std::string ReadEntireStream(std::istream& stream); + + // Reads the entire stream into a byte array. + std::vector ReadEntireStreamAsByteArray(std::istream& stream); + + // Expands environment variables within the input. + std::wstring ExpandEnvironmentVariables(const std::wstring& input); + + // Converts the candidate path part into one suitable for the actual file system + std::string MakeSuitablePathPart(std::string_view candidate); + + // Splits the file name part off of the given URI. + std::pair SplitFileNameFromURI(std::string_view uri); + + // Gets the file name part of the given URI. + std::filesystem::path GetFileNameFromURI(std::string_view uri); + + // Splits the string into words. + std::vector SplitIntoWords(std::string_view input); + + // Splits the string into lines. + // Drops empty lines. + std::vector SplitIntoLines(std::string_view input, size_t maximum = 0); + + // Removes lines from the vector (and/or characters from the last line) so that it contains the maximum number of lines. + // Returns true if changes were made, false if not. + bool LimitOutputLines(std::vector& lines, size_t lineWidth, size_t maximum); + + // Converts a container to a string representation of it. + template + std::string ConvertContainerToString(const T& container, U toString) + { + std::ostringstream strstr; + strstr << '['; + + bool firstItem = true; + for (const auto& item : container) + { + if (firstItem) + { + firstItem = false; + } + else + { + strstr << ", "; + } + + strstr << toString(item); + } + + strstr << ']'; + return strstr.str(); // We need C++20 to get std::move(strstr).str() to extract the string from inside the stream + } + + template + std::string ConvertContainerToString(const T& container) + { + return ConvertContainerToString(container, [](const auto& item) { return item; }); + } + + template + std::basic_string StringOrEmptyIfNull(const CharType* string) + { + if (string) + { + return { string }; + } + else + { + return {}; + } + } + + // Converts the given bytes into a hexadecimal string. + std::string ConvertToHexString(const std::vector& buffer, size_t byteCount = 0); + + // Converts the given hexadecimal string into bytes. + std::vector ParseFromHexString(const std::string& value, size_t byteCount = 0); + + // Join a string vector using the provided separator. + LocIndString Join(LocIndView separator, const std::vector& vector); + + // Join a string vector using the provided separator. + std::string Join(std::string_view separator, const std::vector& vector); + + // Splits the string using the provided separator. Entries can also be trimmed. + std::vector Split(const std::string& input, char separator, bool trim = false); + std::vector SplitView(const std::string& input, char separator, bool trim = false); + std::vector Split(std::string_view input, char separator, bool trim = false); + + // Splits the string using the provided separator. Entries can also be trimmed. + std::vector Split(const std::wstring& input, wchar_t separator, bool trim = false); + std::vector SplitView(const std::wstring& input, wchar_t separator, bool trim = false); + std::vector Split(std::wstring_view input, wchar_t separator, bool trim = false); + + // Format an input string by replacing placeholders {index} with provided values at corresponding indices. + // Note: After upgrading to C++20, this function should be deprecated in favor of std::format. + template + std::string Format(std::string inputStr, T ... args) + { + int index = 0; + (FindAndReplace(inputStr, "{" + std::to_string(index++) + "}", (std::ostringstream() << args).str()),...); + return inputStr; + } + + // Converts the given boolean value to a string. + std::string_view ConvertBoolToString(bool value); + + // Converts the given string view into a bool. + std::optional TryConvertStringToBool(const std::string_view& value); + + // Converts the given string view into an int32. + std::optional TryConvertStringToInt32(const std::string_view& value); + + // Converts the given GUID value to a string. + std::string ConvertGuidToString(const GUID& value); + + // Creates a new GUID and returns the string value. + std::wstring CreateNewGuidNameWString(); + + // Converts the input string to a DWORD value using std::stoul and returns a boolean value based on the resulting DWORD value. + bool IsDwordFlagSet(const std::string& value); + + // Finds the next control code index that would be replaced. + // Returns std::string::npos if not found. + size_t FindControlCodeToConvert(std::string_view input, size_t offset = 0); + + // Converts most control codes in the input to their corresponding control picture in the output. + // Exempts tab, line feed, and carriage return from being replaced. + std::string ConvertControlCodesToPictures(std::string_view input); + + // Generates a random alpha numeric string. + std::string GetRandomString(size_t size = 8); + + // Checks whether a given string is a valid potential Windows feature name. + bool IsValidWindowsFeaturePattern(std::string_view value); +} diff --git a/src/AppInstallerSharedLib/Public/AppInstallerVersions.h b/src/AppInstallerSharedLib/Public/AppInstallerVersions.h index 49dd4f1e99..6e31f96ef8 100644 --- a/src/AppInstallerSharedLib/Public/AppInstallerVersions.h +++ b/src/AppInstallerSharedLib/Public/AppInstallerVersions.h @@ -1,299 +1,299 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Utility -{ - using namespace std::string_view_literals; - - // Creates a comparable version object from a string. - // Versions are parsed by: - // 1. Parse approximate comparator sign if applicable - // 2. Splitting the string based on the given splitChars (or DefaultSplitChars) - // 3. Parsing a leading, positive integer from each split part - // 4. Saving any remaining, non-digits as a supplemental value - // - // Versions are compared by: - // for each part in each version - // if one side has no more parts, perform the remaining comparisons against an empty part - // if integers not equal, return comparison of integers - // else if only one side has a non-empty string part, it is less - // else if string parts not equal, return comparison of strings - // if each part has been compared, use approximate comparator if applicable - // - // Note: approximate to another approximate version is invalid. - // approximate to Unknown is invalid. - struct Version - { - // Used in approximate version to indicate the relation to the base version. - enum class ApproximateComparator - { - None, - LessThan, - GreaterThan, - }; - - // The default characters to split a version string on. - constexpr static std::string_view DefaultSplitChars = "."sv; - - Version() = default; - - Version(const std::string& version, std::string_view splitChars = DefaultSplitChars) : - Version(std::string(version), splitChars) {} - Version(std::string&& version, std::string_view splitChars = DefaultSplitChars); - - // Constructing an approximate version from a base version. - Version(Version baseVersion, ApproximateComparator approximateComparator); - - // Resets the version's value to the input. - virtual void Assign(std::string version, std::string_view splitChars = DefaultSplitChars); - - // Gets the full version string used to construct the Version. - const std::string& ToString() const { return m_version; } - - bool operator<(const Version& other) const; - bool operator>(const Version& other) const; - bool operator<=(const Version& other) const; - bool operator>=(const Version& other) const; - bool operator==(const Version& other) const; - bool operator!=(const Version& other) const; - - // Determines if this version is the sentinel value defining the 'Latest' version - bool IsLatest() const; - - // Returns a Version that will return true for IsLatest - static Version CreateLatest(); - - // Determines if this version is the sentinel value defining an 'Unknown' version - bool IsUnknown() const; - - // Returns a Version that will return true for IsUnknown - static Version CreateUnknown(); - - // Gets a bool indicating whether the full version string is empty. - // Does not indicate that Parts is empty; for instance when "0.0" is given, - // this will be false while GetParts().empty() would be true. - bool IsEmpty() const { return m_version.empty(); } - - // An individual version part in between split characters. - struct Part - { - Part() = default; - Part(uint64_t integer) : Integer(integer) {} - Part(const std::string& part); - Part(uint64_t integer, std::string other); - - bool operator<(const Part& other) const; - bool operator==(const Part& other) const; - bool operator!=(const Part& other) const; - - uint64_t Integer = 0; - std::string Other; - - private: - std::string m_foldedOther; - }; - - // Gets the part breakdown for a given version. - const std::vector& GetParts() const { return m_parts; } - - // Gets the part at the given index; or the implied zero part if past the end. - const Part& PartAt(size_t index) const; - - // Returns if the version is an approximate version. - bool IsApproximate() const { return m_approximateComparator != ApproximateComparator::None; } - - // Get the base version from approximate version, or return a copy if the version is not approximate. - Version GetBaseVersion() const; - - protected: - - bool IsBaseVersionLatest() const; - bool IsBaseVersionUnknown() const; - // Called by overloaded less than operator implementation when base version already compared and equal, less than determined by approximate comparator. - bool ApproximateCompareLessThan(const Version& other) const; - - std::string m_version; - std::vector m_parts; - bool m_trimPrefix = true; - ApproximateComparator m_approximateComparator = ApproximateComparator::None; - - // Remove trailing empty parts (0 or empty) - void Trim(); - }; - - // Version that does not have leading non-digit characters trimmed - struct RawVersion : protected Version - { - RawVersion() { m_trimPrefix = false; } - RawVersion(std::string version, std::string_view splitChars = DefaultSplitChars); - - using Version::GetParts; - }; - - // Four parts version number: 16-bits.16-bits.16-bits.16-bits - struct UInt64Version : public Version - { - UInt64Version() = default; - UInt64Version(UINT64 version); - UInt64Version(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision); - UInt64Version(std::string&& version, std::string_view splitChars = DefaultSplitChars); - UInt64Version(const std::string& version, std::string_view splitChars = DefaultSplitChars) : - UInt64Version(std::string(version), splitChars) {} - - void Assign(std::string version, std::string_view splitChars = DefaultSplitChars) override; - void Assign(UINT64 version); - void Assign(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision); - - UINT64 Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } - UINT64 Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } - UINT64 Build() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } - UINT64 Revision() const { return m_parts.size() > 3 ? m_parts[3].Integer : 0; } - }; - - // A semantic version as defined by semver.org - // This is currently fairly loose in its restrictions on a version, and is largely to separate out the prelease and build metadata portions. - struct SemanticVersion : public Version - { - SemanticVersion() = default; - SemanticVersion(std::string&& version); - SemanticVersion(const std::string& version) : - SemanticVersion(std::string(version)) {} - - void Assign(std::string version, std::string_view splitChars = DefaultSplitChars) override; - - // Indicates that the version is pre-release - bool IsPrerelease() const; - // The pre-release version - const Version& PrereleaseVersion() const; - - // Indicates that the version has build metadata - bool HasBuildMetadata() const; - // The build metadata version - const Version& BuildMetadata() const; - - private: - Version m_prerelease; - Version m_buildMetadata; - }; - - // Version range represented by a min version and max version, both inclusive. - struct VersionRange - { - VersionRange() { m_isEmpty = true; }; - VersionRange(Version minVersion, Version maxVersion); - - bool IsEmpty() const { return m_isEmpty; } - - // Checks if version ranges overlap. Empty version range does not overlap with any version range. - bool Overlaps(const VersionRange& other) const; - - // Checks if the version range is effectively the same as a single version. - bool IsSameAsSingleVersion(const Version& version) const; - - // Checks if a version is within the version range - bool ContainsVersion(const Version& version) const; - - // < operator will thow if compared with an empty range or an overlapped range - bool operator<(const VersionRange& other) const; - - const Version& GetMinVersion() const; - const Version& GetMaxVersion() const; - - private: - Version m_minVersion; - Version m_maxVersion; - bool m_isEmpty = false; - }; - - // A range of versions indicated by a version and optionally a wildcard at the end. - struct GatedVersion - { - GatedVersion() {} - GatedVersion(Version&& version) : m_version(std::move(version)) {} - GatedVersion(const Version& version) : m_version(version) {} - GatedVersion(std::string_view versionString) : m_version(std::string{ versionString }) {} - GatedVersion(const std::string& versionString) : m_version(versionString) {} - - // Determines whether a given version falls within this Gated version. - // I.e., whether it matches up to the wildcard - bool IsValidVersion(Version version) const; - - bool operator==(const GatedVersion& other) const { return m_version == other.m_version; } - const std::string& ToString() const { return m_version.ToString(); } - - private: - // Hold the version string as a Version object that makes it easy to access each of - // the version's parts. The real magic is in IsValidVersion() - Version m_version; - }; - - // A channel string; existing solely to give a type. - // - // Compared lexicographically. - struct Channel - { - Channel() = default; - Channel(const std::string& channel) : m_channel(channel) {} - Channel(std::string&& channel) : m_channel(std::move(channel)) {} - - const std::string& ToString() const { return m_channel; } - - bool operator<(const Channel& other) const; - - private: - std::string m_channel; - }; - - // Contains a version and channel. - // These are compared by: - // if channel not equal, return compare channel - // else return !compare version - // - // The implication of this is that the default less sort will be: - // 2.0, "" - // 1.0, "" - // 3.0, "alpha" - // 2.0, "alpha" - struct VersionAndChannel - { - VersionAndChannel() = default; - VersionAndChannel(Version&& version, Channel&& channel); - - const Version& GetVersion() const { return m_version; } - const Channel& GetChannel() const { return m_channel; } - - std::string ToString() const; - - bool operator<(const VersionAndChannel& other) const; - - // A convenience function to make more semantic sense at call sites over the somewhat awkward less than ordering. - bool IsUpdatedBy(const VersionAndChannel& other) const; - - private: - Version m_version; - Channel m_channel; - }; - - // Checks if there are overlaps within the list of version ranges - bool HasOverlapInVersionRanges(const std::vector& ranges); - - // The OpenType font version. - // The format of this version type is 'Version 1.234 ;567' - // The only part that is of importance is the 'Major.Minor' parts. - // The 'Version' string is typically found at the beginning of the version string. - // Any value after a digit that is not a '.' represents some other meaning. - struct OpenTypeFontVersion : Version - { - OpenTypeFontVersion() = default; - - OpenTypeFontVersion(std::string&& version); - OpenTypeFontVersion(const std::string& version) : - OpenTypeFontVersion(std::string(version)) {} - - void Assign(std::string version, std::string_view splitChars = DefaultSplitChars) override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Utility +{ + using namespace std::string_view_literals; + + // Creates a comparable version object from a string. + // Versions are parsed by: + // 1. Parse approximate comparator sign if applicable + // 2. Splitting the string based on the given splitChars (or DefaultSplitChars) + // 3. Parsing a leading, positive integer from each split part + // 4. Saving any remaining, non-digits as a supplemental value + // + // Versions are compared by: + // for each part in each version + // if one side has no more parts, perform the remaining comparisons against an empty part + // if integers not equal, return comparison of integers + // else if only one side has a non-empty string part, it is less + // else if string parts not equal, return comparison of strings + // if each part has been compared, use approximate comparator if applicable + // + // Note: approximate to another approximate version is invalid. + // approximate to Unknown is invalid. + struct Version + { + // Used in approximate version to indicate the relation to the base version. + enum class ApproximateComparator + { + None, + LessThan, + GreaterThan, + }; + + // The default characters to split a version string on. + constexpr static std::string_view DefaultSplitChars = "."sv; + + Version() = default; + + Version(const std::string& version, std::string_view splitChars = DefaultSplitChars) : + Version(std::string(version), splitChars) {} + Version(std::string&& version, std::string_view splitChars = DefaultSplitChars); + + // Constructing an approximate version from a base version. + Version(Version baseVersion, ApproximateComparator approximateComparator); + + // Resets the version's value to the input. + virtual void Assign(std::string version, std::string_view splitChars = DefaultSplitChars); + + // Gets the full version string used to construct the Version. + const std::string& ToString() const { return m_version; } + + bool operator<(const Version& other) const; + bool operator>(const Version& other) const; + bool operator<=(const Version& other) const; + bool operator>=(const Version& other) const; + bool operator==(const Version& other) const; + bool operator!=(const Version& other) const; + + // Determines if this version is the sentinel value defining the 'Latest' version + bool IsLatest() const; + + // Returns a Version that will return true for IsLatest + static Version CreateLatest(); + + // Determines if this version is the sentinel value defining an 'Unknown' version + bool IsUnknown() const; + + // Returns a Version that will return true for IsUnknown + static Version CreateUnknown(); + + // Gets a bool indicating whether the full version string is empty. + // Does not indicate that Parts is empty; for instance when "0.0" is given, + // this will be false while GetParts().empty() would be true. + bool IsEmpty() const { return m_version.empty(); } + + // An individual version part in between split characters. + struct Part + { + Part() = default; + Part(uint64_t integer) : Integer(integer) {} + Part(const std::string& part); + Part(uint64_t integer, std::string other); + + bool operator<(const Part& other) const; + bool operator==(const Part& other) const; + bool operator!=(const Part& other) const; + + uint64_t Integer = 0; + std::string Other; + + private: + std::string m_foldedOther; + }; + + // Gets the part breakdown for a given version. + const std::vector& GetParts() const { return m_parts; } + + // Gets the part at the given index; or the implied zero part if past the end. + const Part& PartAt(size_t index) const; + + // Returns if the version is an approximate version. + bool IsApproximate() const { return m_approximateComparator != ApproximateComparator::None; } + + // Get the base version from approximate version, or return a copy if the version is not approximate. + Version GetBaseVersion() const; + + protected: + + bool IsBaseVersionLatest() const; + bool IsBaseVersionUnknown() const; + // Called by overloaded less than operator implementation when base version already compared and equal, less than determined by approximate comparator. + bool ApproximateCompareLessThan(const Version& other) const; + + std::string m_version; + std::vector m_parts; + bool m_trimPrefix = true; + ApproximateComparator m_approximateComparator = ApproximateComparator::None; + + // Remove trailing empty parts (0 or empty) + void Trim(); + }; + + // Version that does not have leading non-digit characters trimmed + struct RawVersion : protected Version + { + RawVersion() { m_trimPrefix = false; } + RawVersion(std::string version, std::string_view splitChars = DefaultSplitChars); + + using Version::GetParts; + }; + + // Four parts version number: 16-bits.16-bits.16-bits.16-bits + struct UInt64Version : public Version + { + UInt64Version() = default; + UInt64Version(UINT64 version); + UInt64Version(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision); + UInt64Version(std::string&& version, std::string_view splitChars = DefaultSplitChars); + UInt64Version(const std::string& version, std::string_view splitChars = DefaultSplitChars) : + UInt64Version(std::string(version), splitChars) {} + + void Assign(std::string version, std::string_view splitChars = DefaultSplitChars) override; + void Assign(UINT64 version); + void Assign(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision); + + UINT64 Major() const { return m_parts.size() > 0 ? m_parts[0].Integer : 0; } + UINT64 Minor() const { return m_parts.size() > 1 ? m_parts[1].Integer : 0; } + UINT64 Build() const { return m_parts.size() > 2 ? m_parts[2].Integer : 0; } + UINT64 Revision() const { return m_parts.size() > 3 ? m_parts[3].Integer : 0; } + }; + + // A semantic version as defined by semver.org + // This is currently fairly loose in its restrictions on a version, and is largely to separate out the prelease and build metadata portions. + struct SemanticVersion : public Version + { + SemanticVersion() = default; + SemanticVersion(std::string&& version); + SemanticVersion(const std::string& version) : + SemanticVersion(std::string(version)) {} + + void Assign(std::string version, std::string_view splitChars = DefaultSplitChars) override; + + // Indicates that the version is pre-release + bool IsPrerelease() const; + // The pre-release version + const Version& PrereleaseVersion() const; + + // Indicates that the version has build metadata + bool HasBuildMetadata() const; + // The build metadata version + const Version& BuildMetadata() const; + + private: + Version m_prerelease; + Version m_buildMetadata; + }; + + // Version range represented by a min version and max version, both inclusive. + struct VersionRange + { + VersionRange() { m_isEmpty = true; }; + VersionRange(Version minVersion, Version maxVersion); + + bool IsEmpty() const { return m_isEmpty; } + + // Checks if version ranges overlap. Empty version range does not overlap with any version range. + bool Overlaps(const VersionRange& other) const; + + // Checks if the version range is effectively the same as a single version. + bool IsSameAsSingleVersion(const Version& version) const; + + // Checks if a version is within the version range + bool ContainsVersion(const Version& version) const; + + // < operator will thow if compared with an empty range or an overlapped range + bool operator<(const VersionRange& other) const; + + const Version& GetMinVersion() const; + const Version& GetMaxVersion() const; + + private: + Version m_minVersion; + Version m_maxVersion; + bool m_isEmpty = false; + }; + + // A range of versions indicated by a version and optionally a wildcard at the end. + struct GatedVersion + { + GatedVersion() {} + GatedVersion(Version&& version) : m_version(std::move(version)) {} + GatedVersion(const Version& version) : m_version(version) {} + GatedVersion(std::string_view versionString) : m_version(std::string{ versionString }) {} + GatedVersion(const std::string& versionString) : m_version(versionString) {} + + // Determines whether a given version falls within this Gated version. + // I.e., whether it matches up to the wildcard + bool IsValidVersion(Version version) const; + + bool operator==(const GatedVersion& other) const { return m_version == other.m_version; } + const std::string& ToString() const { return m_version.ToString(); } + + private: + // Hold the version string as a Version object that makes it easy to access each of + // the version's parts. The real magic is in IsValidVersion() + Version m_version; + }; + + // A channel string; existing solely to give a type. + // + // Compared lexicographically. + struct Channel + { + Channel() = default; + Channel(const std::string& channel) : m_channel(channel) {} + Channel(std::string&& channel) : m_channel(std::move(channel)) {} + + const std::string& ToString() const { return m_channel; } + + bool operator<(const Channel& other) const; + + private: + std::string m_channel; + }; + + // Contains a version and channel. + // These are compared by: + // if channel not equal, return compare channel + // else return !compare version + // + // The implication of this is that the default less sort will be: + // 2.0, "" + // 1.0, "" + // 3.0, "alpha" + // 2.0, "alpha" + struct VersionAndChannel + { + VersionAndChannel() = default; + VersionAndChannel(Version&& version, Channel&& channel); + + const Version& GetVersion() const { return m_version; } + const Channel& GetChannel() const { return m_channel; } + + std::string ToString() const; + + bool operator<(const VersionAndChannel& other) const; + + // A convenience function to make more semantic sense at call sites over the somewhat awkward less than ordering. + bool IsUpdatedBy(const VersionAndChannel& other) const; + + private: + Version m_version; + Channel m_channel; + }; + + // Checks if there are overlaps within the list of version ranges + bool HasOverlapInVersionRanges(const std::vector& ranges); + + // The OpenType font version. + // The format of this version type is 'Version 1.234 ;567' + // The only part that is of importance is the 'Major.Minor' parts. + // The 'Version' string is typically found at the beginning of the version string. + // Any value after a digit that is not a '.' represents some other meaning. + struct OpenTypeFontVersion : Version + { + OpenTypeFontVersion() = default; + + OpenTypeFontVersion(std::string&& version); + OpenTypeFontVersion(const std::string& version) : + OpenTypeFontVersion(std::string(version)) {} + + void Assign(std::string version, std::string_view splitChars = DefaultSplitChars) override; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/AsyncTokens.h b/src/AppInstallerSharedLib/Public/winget/AsyncTokens.h index 1f1658023f..21260dc885 100644 --- a/src/AppInstallerSharedLib/Public/winget/AsyncTokens.h +++ b/src/AppInstallerSharedLib/Public/winget/AsyncTokens.h @@ -1,211 +1,211 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::WinRT -{ - namespace details - { - // Type erasing interface for winrt cancellation token. - struct AsyncCancellationTypeErasure - { - virtual ~AsyncCancellationTypeErasure() = default; - - virtual bool IsCancelled() const noexcept = 0; - virtual void Callback(winrt::delegate<>&& callback) const noexcept = 0; - virtual void Cancel() noexcept = 0; - }; - - // Type containing winrt cancellation token wrapper. - template - struct AsyncCancellationT : public AsyncCancellationTypeErasure - { - using Token = winrt::impl::cancellation_token; - - AsyncCancellationT(Token&& token) : m_token(std::move(token)) {} - - bool IsCancelled() const noexcept override - { - return m_token(); - } - - void Callback(winrt::delegate<>&& callback) const noexcept override - { - m_token.callback(std::move(callback)); - } - - void Cancel() noexcept override - { - // This is a bit of a hack, but the cancellation_token provides no access to the underlying promise for the purpose of cancellation. - static_assert(sizeof(Token) == sizeof(Promise*), "We expect that the cancellation_token has only 1 member and it is a Promise*"); - (*reinterpret_cast(&m_token))->Cancel(); - } - - private: - Token m_token; - }; - } - - // May hold a cancellation token and provide the ability to check its status. - // If empty, it will act as if it is never cancelled. - struct AsyncCancellation - { - // Create an empty cancellation object, which will never be cancelled. - AsyncCancellation() = default; - - // Create a cancellation object from the winrt token. - template - AsyncCancellation(winrt::impl::cancellation_token&& token) - { - m_token = std::make_shared>(std::move(token)); - } - - // Returns true if the operation has been cancelled, false if not. - bool IsCancelled() const noexcept - { - return m_token ? m_token->IsCancelled() : false; - } - - // Throws the appropriate exception if the operation has been cancelled. - void ThrowIfCancelled() const - { - if (IsCancelled()) - { - AICLI_LOG(Core, Warning, << "Operation cancelled"); - throw winrt::hresult_canceled(); - } - } - - // Sets a callback that will be invoked on cancellation. - void Callback(winrt::delegate<>&& callback) const noexcept - { - if (m_token) - { - m_token->Callback(std::move(callback)); - } - } - - std::weak_ptr GetWeak() - { - return m_token; - } - - private: - std::shared_ptr m_token; - }; - - namespace details - { - // Type erasing interface for winrt progress token. - template - struct AsyncProgressTypeErasure - { - virtual ~AsyncProgressTypeErasure() = default; - - virtual void Progress(ProgressT const& progress) const = 0; - - virtual void Result(ResultT const& result) const = 0; - }; - - // Type containing winrt progress token wrapper. - template - struct AsyncProgressT : public AsyncProgressTypeErasure - { - using Token = winrt::impl::progress_token; - - AsyncProgressT(Token&& token) : m_token(std::move(token)) {} - - void Progress(ProgressT const& progress) const override - { - m_token(progress); - } - - void Result(ResultT const& result) const override - { - m_token.set_result(result); - } - - private: - Token m_token; - }; - - // Type containing winrt EventHandler. - template - struct AsyncProgressEventHandlerT : public AsyncProgressTypeErasure - { - using Token = winrt::Windows::Foundation::EventHandler; - - AsyncProgressEventHandlerT(Token&& token) : m_token(std::move(token)) {} - - void Progress(ProgressT const& progress) const override - { - m_token(m_result, progress); - } - - void Result(ResultT const& result) const override - { - m_result = result; - } - - private: - mutable ResultT m_result = nullptr; - Token m_token; - }; - } - - // May hold a progress token and provide the ability to send progress updates. - // If empty, progress will be dropped on calls here. - template - struct AsyncProgress : public AsyncCancellation - { - // Create an empty progress object. - AsyncProgress() = default; - - // Create a progress object from the winrt token. - template - AsyncProgress(winrt::impl::progress_token&& progress, winrt::impl::cancellation_token&& cancellation) : - AsyncCancellation(std::move(cancellation)) - { - m_token = std::make_unique>(std::move(progress)); - } - - // Create a progress object from an EventHandler. - template - AsyncProgress(winrt::Windows::Foundation::EventHandler&& progress, winrt::impl::cancellation_token&& cancellation) : - AsyncCancellation(std::move(cancellation)) - { - m_token = std::make_unique>(std::move(progress)); - } - - // Create a cancellation only object. - template - AsyncProgress(winrt::impl::cancellation_token&& cancellation) : - AsyncCancellation(std::move(cancellation)) - { - } - - // Sends progress if this object is not empty. - void Progress(ProgressT const& progress) const - { - if (m_token) - { - m_token->Progress(progress); - } - } - - // Sets the result onto the progress object if it is not empty. - void Result(ResultT const& result) const - { - if (m_token) - { - m_token->Result(result); - } - } - - private: - std::unique_ptr> m_token; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::WinRT +{ + namespace details + { + // Type erasing interface for winrt cancellation token. + struct AsyncCancellationTypeErasure + { + virtual ~AsyncCancellationTypeErasure() = default; + + virtual bool IsCancelled() const noexcept = 0; + virtual void Callback(winrt::delegate<>&& callback) const noexcept = 0; + virtual void Cancel() noexcept = 0; + }; + + // Type containing winrt cancellation token wrapper. + template + struct AsyncCancellationT : public AsyncCancellationTypeErasure + { + using Token = winrt::impl::cancellation_token; + + AsyncCancellationT(Token&& token) : m_token(std::move(token)) {} + + bool IsCancelled() const noexcept override + { + return m_token(); + } + + void Callback(winrt::delegate<>&& callback) const noexcept override + { + m_token.callback(std::move(callback)); + } + + void Cancel() noexcept override + { + // This is a bit of a hack, but the cancellation_token provides no access to the underlying promise for the purpose of cancellation. + static_assert(sizeof(Token) == sizeof(Promise*), "We expect that the cancellation_token has only 1 member and it is a Promise*"); + (*reinterpret_cast(&m_token))->Cancel(); + } + + private: + Token m_token; + }; + } + + // May hold a cancellation token and provide the ability to check its status. + // If empty, it will act as if it is never cancelled. + struct AsyncCancellation + { + // Create an empty cancellation object, which will never be cancelled. + AsyncCancellation() = default; + + // Create a cancellation object from the winrt token. + template + AsyncCancellation(winrt::impl::cancellation_token&& token) + { + m_token = std::make_shared>(std::move(token)); + } + + // Returns true if the operation has been cancelled, false if not. + bool IsCancelled() const noexcept + { + return m_token ? m_token->IsCancelled() : false; + } + + // Throws the appropriate exception if the operation has been cancelled. + void ThrowIfCancelled() const + { + if (IsCancelled()) + { + AICLI_LOG(Core, Warning, << "Operation cancelled"); + throw winrt::hresult_canceled(); + } + } + + // Sets a callback that will be invoked on cancellation. + void Callback(winrt::delegate<>&& callback) const noexcept + { + if (m_token) + { + m_token->Callback(std::move(callback)); + } + } + + std::weak_ptr GetWeak() + { + return m_token; + } + + private: + std::shared_ptr m_token; + }; + + namespace details + { + // Type erasing interface for winrt progress token. + template + struct AsyncProgressTypeErasure + { + virtual ~AsyncProgressTypeErasure() = default; + + virtual void Progress(ProgressT const& progress) const = 0; + + virtual void Result(ResultT const& result) const = 0; + }; + + // Type containing winrt progress token wrapper. + template + struct AsyncProgressT : public AsyncProgressTypeErasure + { + using Token = winrt::impl::progress_token; + + AsyncProgressT(Token&& token) : m_token(std::move(token)) {} + + void Progress(ProgressT const& progress) const override + { + m_token(progress); + } + + void Result(ResultT const& result) const override + { + m_token.set_result(result); + } + + private: + Token m_token; + }; + + // Type containing winrt EventHandler. + template + struct AsyncProgressEventHandlerT : public AsyncProgressTypeErasure + { + using Token = winrt::Windows::Foundation::EventHandler; + + AsyncProgressEventHandlerT(Token&& token) : m_token(std::move(token)) {} + + void Progress(ProgressT const& progress) const override + { + m_token(m_result, progress); + } + + void Result(ResultT const& result) const override + { + m_result = result; + } + + private: + mutable ResultT m_result = nullptr; + Token m_token; + }; + } + + // May hold a progress token and provide the ability to send progress updates. + // If empty, progress will be dropped on calls here. + template + struct AsyncProgress : public AsyncCancellation + { + // Create an empty progress object. + AsyncProgress() = default; + + // Create a progress object from the winrt token. + template + AsyncProgress(winrt::impl::progress_token&& progress, winrt::impl::cancellation_token&& cancellation) : + AsyncCancellation(std::move(cancellation)) + { + m_token = std::make_unique>(std::move(progress)); + } + + // Create a progress object from an EventHandler. + template + AsyncProgress(winrt::Windows::Foundation::EventHandler&& progress, winrt::impl::cancellation_token&& cancellation) : + AsyncCancellation(std::move(cancellation)) + { + m_token = std::make_unique>(std::move(progress)); + } + + // Create a cancellation only object. + template + AsyncProgress(winrt::impl::cancellation_token&& cancellation) : + AsyncCancellation(std::move(cancellation)) + { + } + + // Sends progress if this object is not empty. + void Progress(ProgressT const& progress) const + { + if (m_token) + { + m_token->Progress(progress); + } + } + + // Sets the result onto the progress object if it is not empty. + void Result(ResultT const& result) const + { + if (m_token) + { + m_token->Result(result); + } + } + + private: + std::unique_ptr> m_token; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/COMStaticStorage.h b/src/AppInstallerSharedLib/Public/winget/COMStaticStorage.h index a7ab1d03a7..930421da90 100644 --- a/src/AppInstallerSharedLib/Public/winget/COMStaticStorage.h +++ b/src/AppInstallerSharedLib/Public/winget/COMStaticStorage.h @@ -1,83 +1,83 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -namespace AppInstaller::WinRT -{ - // Contains registration for static storage so that they can be cleared. - struct COMStaticStorageStatics - { - // Adds a static storage key to the set of known items. - static void AddStaticStorageItem(const winrt::hstring& name, const winrt::Windows::Foundation::IInspectable& item); - - // Removes all known static storage items. - static void ResetAll(); - - private: - COMStaticStorageStatics() = default; - - static COMStaticStorageStatics& Instance(); - - winrt::slim_mutex m_lock; - std::set m_items; - }; - - // https://devblogs.microsoft.com/oldnewthing/20210215-00/?p=104865 - // Base class for an object that needs to live in the COM static store. - // An object needs to use this if it has: - // - static lifetime - // - references to externally implemented COM objects - // - // Additionally, it should *not* contain references to WRL counted objects implemented by this module. - // If it does, it will prevent the module from being unloaded until COM is uninitialized, which is often never. - template - struct COMStaticStorageBase - { - private: - struct DataHolder : public winrt::implements - { - std::shared_ptr m_shared{ std::make_shared() }; - }; - - std::weak_ptr m_weak; - winrt::slim_mutex m_lock; - winrt::hstring m_name; - - public: - COMStaticStorageBase(std::wstring_view name) : m_name(name) {} - - std::shared_ptr Get() - { - { - const std::shared_lock lock{ m_lock }; - if (auto cached = m_weak.lock()) - { - return cached; - } - } - - auto value = winrt::make_self(); - - const winrt::slim_lock_guard lock{ m_lock }; - if (auto cached = m_weak.lock()) - { - return cached; - } - - COMStaticStorageStatics::AddStaticStorageItem(m_name, value.as()); - m_weak = value->m_shared; - return value->m_shared; - } - - void Reset() - { - winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().TryRemove(m_name); - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +namespace AppInstaller::WinRT +{ + // Contains registration for static storage so that they can be cleared. + struct COMStaticStorageStatics + { + // Adds a static storage key to the set of known items. + static void AddStaticStorageItem(const winrt::hstring& name, const winrt::Windows::Foundation::IInspectable& item); + + // Removes all known static storage items. + static void ResetAll(); + + private: + COMStaticStorageStatics() = default; + + static COMStaticStorageStatics& Instance(); + + winrt::slim_mutex m_lock; + std::set m_items; + }; + + // https://devblogs.microsoft.com/oldnewthing/20210215-00/?p=104865 + // Base class for an object that needs to live in the COM static store. + // An object needs to use this if it has: + // - static lifetime + // - references to externally implemented COM objects + // + // Additionally, it should *not* contain references to WRL counted objects implemented by this module. + // If it does, it will prevent the module from being unloaded until COM is uninitialized, which is often never. + template + struct COMStaticStorageBase + { + private: + struct DataHolder : public winrt::implements + { + std::shared_ptr m_shared{ std::make_shared() }; + }; + + std::weak_ptr m_weak; + winrt::slim_mutex m_lock; + winrt::hstring m_name; + + public: + COMStaticStorageBase(std::wstring_view name) : m_name(name) {} + + std::shared_ptr Get() + { + { + const std::shared_lock lock{ m_lock }; + if (auto cached = m_weak.lock()) + { + return cached; + } + } + + auto value = winrt::make_self(); + + const winrt::slim_lock_guard lock{ m_lock }; + if (auto cached = m_weak.lock()) + { + return cached; + } + + COMStaticStorageStatics::AddStaticStorageItem(m_name, value.as()); + m_weak = value->m_shared; + return value->m_shared; + } + + void Reset() + { + winrt::Windows::ApplicationModel::Core::CoreApplication::Properties().TryRemove(m_name); + } + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/Certificates.h b/src/AppInstallerSharedLib/Public/winget/Certificates.h index c2034fa468..0acc6b840c 100644 --- a/src/AppInstallerSharedLib/Public/winget/Certificates.h +++ b/src/AppInstallerSharedLib/Public/winget/Certificates.h @@ -1,256 +1,256 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::Certificates -{ - // Returns the Authenticode signing subject name (e.g., "Microsoft Corporation") for the - // given file. Only returns a value when WinVerifyTrust confirms the signature is valid and - // trusted; extracts the subject from the embedded PE Authenticode signature via crypt32. - // Returns an empty string if the file is unsigned, untrusted, or on any error. - std::string GetAuthenticodeSubject(const std::filesystem::path& filePath); - - // Defines the types of certificate pinning to perform. - enum class PinningVerificationType : uint32_t - { - // No validation; accepts anything. - None = 0x0, - // Validates that the public keys match; requires a certificate to be loaded. - PublicKey = 0x1, - // Validates that the full subjects match; requires a certificate to be loaded. - Subject = 0x2, - // Validates that the full issuers match; requires a certificate to be loaded. - Issuer = 0x4, - // Allows for unknown certificates in the chain; will continue to consume certificates from the chain until it matches. - // Requires partial chain. - AnyIssuer = 0x8, - // Requires that the certificate is not a leaf. - RequireNonLeaf = 0x10, - }; - - DEFINE_ENUM_FLAG_OPERATORS(PinningVerificationType); - - std::ostream& operator<<(std::ostream& out, PinningVerificationType value); - - // The position within a chain that a certificate is located. - enum class CertificateChainPosition - { - Unknown = 0x0, - // The start of the chain. - Root = 0x1, - // An indeterminate intermediate along the chain. - Intermediate = 0x2, - // The final certificate of the chain. - Leaf = 0x4, - }; - - DEFINE_ENUM_FLAG_OPERATORS(CertificateChainPosition); - - std::ostream& operator<<(std::ostream& out, CertificateChainPosition value); - - // The result of validating a single certificate. - enum class CertificatePinningValidationResult - { - // The certificate was accepted as valid. - Accepted, - // The certificate was rejected as invalid. - Rejected, - // The next certificate in the chain should be validated against the current details. - // For use by partial chain validation that does not require exacting chain configurations. - Skipped, - }; - - // Contains the specific information about a certificate to pin. - struct PinningDetails - { - PinningDetails() = default; - - PinningDetails(const PinningDetails&) = default; - PinningDetails& operator=(const PinningDetails&) = default; - - PinningDetails(PinningDetails&&) = default; - PinningDetails& operator=(PinningDetails&&) = default; - - // Loads the certificate context. - PinningDetails& LoadCertificate(int resource, int resourceType); - PinningDetails& LoadCertificate(const std::vector& certificateBytes); - PinningDetails& LoadCertificate(const std::pair certificateBytes); - PCCERT_CONTEXT GetCertificate() const { return m_certificateContext.get(); } - - PinningDetails& SetPinning(PinningVerificationType type); - PinningVerificationType GetPinning() const { return m_pinning; } - - // Validates the given certificate against the pinning information. - CertificatePinningValidationResult Validate(PCCERT_CONTEXT certContext, CertificateChainPosition position) const; - - // Outputs a description of the pinning details. - void OutputDescription(std::ostream& stream, std::string_view indent) const; - - // Loads the pinning details from the given JSON. - [[nodiscard]] bool LoadFrom(const Json::Value& configuration); - - // Determines how far the certificate is through its lifespan. - double GetRemainingLifetimePercentage() const; - -#ifndef AICLI_DISABLE_TEST_HOOKS - using CustomValidationFunction = std::function; - void SetCustomValidationFunction(CustomValidationFunction function) { m_customValidation = std::move(function); } - private: - CustomValidationFunction m_customValidation; -#endif - - private: - wil::shared_cert_context m_certificateContext; - PinningVerificationType m_pinning = PinningVerificationType::None; - }; - - // Interface for a single chain validation strategy within a PinningConfiguration. - // For a certificate to be accepted by PinningConfiguration, it must be accepted by at least one chain. - struct IPinningChainValidation - { - virtual ~IPinningChainValidation() = default; - - // Returns true if the certificate chain is accepted. - virtual bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const = 0; - - // Returns a human-readable description of this validation for logging. - virtual std::string GetDescription() const = 0; - - // Returns the remaining lifetime percentage of the pinned material (0.0 = expired, 1.0 = full life remaining). - // Implementations without a fixed lifetime should return 1.0. - virtual double GetRemainingLifetimePercentage() const = 0; - }; - - // Contains the full chain of pinning details. - struct PinningChain : public IPinningChainValidation - { - PinningChain() = default; - - PinningChain(const PinningChain&) = default; - PinningChain& operator=(const PinningChain&) = default; - - PinningChain(PinningChain&&) = default; - PinningChain& operator=(PinningChain&&) = default; - - // A single entry in the chain. - struct Node - { - friend PinningChain; - - // Access the value - PinningDetails* operator->() { return &m_chain.get()[m_index]; } - - // Create/access the next node in the chain. - Node Next(); - const Node Next() const; - - // Drops the next node (and all subsequent nodes) from the chain. - void RemoveNext(); - - // Indicates if there is already an existing next node. - bool HasNext() const; - - private: - Node(std::vector& chain, size_t index); - - std::reference_wrapper> m_chain; - size_t m_index = 0; - }; - - // Gets the root certificate pinning details in the chain. - // These will correspond to the root of the certificate chain being verified. - Node Root(); - const Node Root() const; - - // A partial chain will validate success if all of its components are successful. - PinningChain& PartialChain(bool isPartial = true); - bool IsPartialChain() const { return m_partial; } - - // Validates the given certificate chain against the configuration. - // Returns true to indicate that the chain meets the pinning configuration criteria. - // Returns false to indicate the it does not. - bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const override; - - // Gets a description of the pinning chain. - std::string GetDescription() const override; - - // Loads the pinning chain from the given JSON. - [[nodiscard]] bool LoadFrom(const Json::Value& configuration); - - // Determines how far the certificate chain is through its lifespan (the minimum of all of its certificates). - double GetRemainingLifetimePercentage() const override; - - private: - std::vector m_chain; - bool m_partial = false; - }; - - // A chain validation that delegates to a caller-supplied callback. - struct CallbackPinningChainValidation : public IPinningChainValidation - { - // callback receives the leaf (end-entity) certificate and returns true to accept, false to reject. - explicit CallbackPinningChainValidation(std::function callback); - - bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const override; - std::string GetDescription() const override; - double GetRemainingLifetimePercentage() const override { return 1.0; } - - private: - std::function m_callback; - }; - - // Holds the details about how a certificate chain is to be validated (aka "pinned"). - struct PinningConfiguration - { - PinningConfiguration(std::string identifier = {}); - - PinningConfiguration(const PinningConfiguration&) = default; - PinningConfiguration& operator=(const PinningConfiguration&) = default; - - PinningConfiguration(PinningConfiguration&&) = default; - PinningConfiguration& operator=(PinningConfiguration&&) = default; - - // Adds a possible chain to the configuration. - // For a certificate to be valid, it must match only one of the configured chains. - void AddChain(PinningChain chain); - void AddChain(std::shared_ptr chain); - - // Validates the given leaf certificate against the configuration. - // Returns true to indicate that the certificate meets the pinning configuration criteria. - // Returns false to indicate the it does not. - bool Validate(PCCERT_CONTEXT certContext) const; - - // True if no pinning is configured. - bool IsEmpty() const { return m_configuration.empty(); } - - // Loads the pinning configuration from the given JSON. - [[nodiscard]] bool LoadFrom(const Json::Value& configuration); - - // Determines how far the configuration is through its lifespan (the maximum of all of its chains). - double GetRemainingLifetimePercentage() const; - - private: - // The identifier used when logging. - std::string m_identifier; - - // The configured chains. - std::vector> m_configuration; - - // We store the last certificate that was successfully validated to speed up subsequent checks. - // Only cache a single certificate under the assumption that most of the time there will - // only be a single server certificate in use. - mutable std::vector m_cachedCertificate; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::Certificates +{ + // Returns the Authenticode signing subject name (e.g., "Microsoft Corporation") for the + // given file. Only returns a value when WinVerifyTrust confirms the signature is valid and + // trusted; extracts the subject from the embedded PE Authenticode signature via crypt32. + // Returns an empty string if the file is unsigned, untrusted, or on any error. + std::string GetAuthenticodeSubject(const std::filesystem::path& filePath); + + // Defines the types of certificate pinning to perform. + enum class PinningVerificationType : uint32_t + { + // No validation; accepts anything. + None = 0x0, + // Validates that the public keys match; requires a certificate to be loaded. + PublicKey = 0x1, + // Validates that the full subjects match; requires a certificate to be loaded. + Subject = 0x2, + // Validates that the full issuers match; requires a certificate to be loaded. + Issuer = 0x4, + // Allows for unknown certificates in the chain; will continue to consume certificates from the chain until it matches. + // Requires partial chain. + AnyIssuer = 0x8, + // Requires that the certificate is not a leaf. + RequireNonLeaf = 0x10, + }; + + DEFINE_ENUM_FLAG_OPERATORS(PinningVerificationType); + + std::ostream& operator<<(std::ostream& out, PinningVerificationType value); + + // The position within a chain that a certificate is located. + enum class CertificateChainPosition + { + Unknown = 0x0, + // The start of the chain. + Root = 0x1, + // An indeterminate intermediate along the chain. + Intermediate = 0x2, + // The final certificate of the chain. + Leaf = 0x4, + }; + + DEFINE_ENUM_FLAG_OPERATORS(CertificateChainPosition); + + std::ostream& operator<<(std::ostream& out, CertificateChainPosition value); + + // The result of validating a single certificate. + enum class CertificatePinningValidationResult + { + // The certificate was accepted as valid. + Accepted, + // The certificate was rejected as invalid. + Rejected, + // The next certificate in the chain should be validated against the current details. + // For use by partial chain validation that does not require exacting chain configurations. + Skipped, + }; + + // Contains the specific information about a certificate to pin. + struct PinningDetails + { + PinningDetails() = default; + + PinningDetails(const PinningDetails&) = default; + PinningDetails& operator=(const PinningDetails&) = default; + + PinningDetails(PinningDetails&&) = default; + PinningDetails& operator=(PinningDetails&&) = default; + + // Loads the certificate context. + PinningDetails& LoadCertificate(int resource, int resourceType); + PinningDetails& LoadCertificate(const std::vector& certificateBytes); + PinningDetails& LoadCertificate(const std::pair certificateBytes); + PCCERT_CONTEXT GetCertificate() const { return m_certificateContext.get(); } + + PinningDetails& SetPinning(PinningVerificationType type); + PinningVerificationType GetPinning() const { return m_pinning; } + + // Validates the given certificate against the pinning information. + CertificatePinningValidationResult Validate(PCCERT_CONTEXT certContext, CertificateChainPosition position) const; + + // Outputs a description of the pinning details. + void OutputDescription(std::ostream& stream, std::string_view indent) const; + + // Loads the pinning details from the given JSON. + [[nodiscard]] bool LoadFrom(const Json::Value& configuration); + + // Determines how far the certificate is through its lifespan. + double GetRemainingLifetimePercentage() const; + +#ifndef AICLI_DISABLE_TEST_HOOKS + using CustomValidationFunction = std::function; + void SetCustomValidationFunction(CustomValidationFunction function) { m_customValidation = std::move(function); } + private: + CustomValidationFunction m_customValidation; +#endif + + private: + wil::shared_cert_context m_certificateContext; + PinningVerificationType m_pinning = PinningVerificationType::None; + }; + + // Interface for a single chain validation strategy within a PinningConfiguration. + // For a certificate to be accepted by PinningConfiguration, it must be accepted by at least one chain. + struct IPinningChainValidation + { + virtual ~IPinningChainValidation() = default; + + // Returns true if the certificate chain is accepted. + virtual bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const = 0; + + // Returns a human-readable description of this validation for logging. + virtual std::string GetDescription() const = 0; + + // Returns the remaining lifetime percentage of the pinned material (0.0 = expired, 1.0 = full life remaining). + // Implementations without a fixed lifetime should return 1.0. + virtual double GetRemainingLifetimePercentage() const = 0; + }; + + // Contains the full chain of pinning details. + struct PinningChain : public IPinningChainValidation + { + PinningChain() = default; + + PinningChain(const PinningChain&) = default; + PinningChain& operator=(const PinningChain&) = default; + + PinningChain(PinningChain&&) = default; + PinningChain& operator=(PinningChain&&) = default; + + // A single entry in the chain. + struct Node + { + friend PinningChain; + + // Access the value + PinningDetails* operator->() { return &m_chain.get()[m_index]; } + + // Create/access the next node in the chain. + Node Next(); + const Node Next() const; + + // Drops the next node (and all subsequent nodes) from the chain. + void RemoveNext(); + + // Indicates if there is already an existing next node. + bool HasNext() const; + + private: + Node(std::vector& chain, size_t index); + + std::reference_wrapper> m_chain; + size_t m_index = 0; + }; + + // Gets the root certificate pinning details in the chain. + // These will correspond to the root of the certificate chain being verified. + Node Root(); + const Node Root() const; + + // A partial chain will validate success if all of its components are successful. + PinningChain& PartialChain(bool isPartial = true); + bool IsPartialChain() const { return m_partial; } + + // Validates the given certificate chain against the configuration. + // Returns true to indicate that the chain meets the pinning configuration criteria. + // Returns false to indicate the it does not. + bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const override; + + // Gets a description of the pinning chain. + std::string GetDescription() const override; + + // Loads the pinning chain from the given JSON. + [[nodiscard]] bool LoadFrom(const Json::Value& configuration); + + // Determines how far the certificate chain is through its lifespan (the minimum of all of its certificates). + double GetRemainingLifetimePercentage() const override; + + private: + std::vector m_chain; + bool m_partial = false; + }; + + // A chain validation that delegates to a caller-supplied callback. + struct CallbackPinningChainValidation : public IPinningChainValidation + { + // callback receives the leaf (end-entity) certificate and returns true to accept, false to reject. + explicit CallbackPinningChainValidation(std::function callback); + + bool Validate(PCCERT_CHAIN_CONTEXT chainContext) const override; + std::string GetDescription() const override; + double GetRemainingLifetimePercentage() const override { return 1.0; } + + private: + std::function m_callback; + }; + + // Holds the details about how a certificate chain is to be validated (aka "pinned"). + struct PinningConfiguration + { + PinningConfiguration(std::string identifier = {}); + + PinningConfiguration(const PinningConfiguration&) = default; + PinningConfiguration& operator=(const PinningConfiguration&) = default; + + PinningConfiguration(PinningConfiguration&&) = default; + PinningConfiguration& operator=(PinningConfiguration&&) = default; + + // Adds a possible chain to the configuration. + // For a certificate to be valid, it must match only one of the configured chains. + void AddChain(PinningChain chain); + void AddChain(std::shared_ptr chain); + + // Validates the given leaf certificate against the configuration. + // Returns true to indicate that the certificate meets the pinning configuration criteria. + // Returns false to indicate the it does not. + bool Validate(PCCERT_CONTEXT certContext) const; + + // True if no pinning is configured. + bool IsEmpty() const { return m_configuration.empty(); } + + // Loads the pinning configuration from the given JSON. + [[nodiscard]] bool LoadFrom(const Json::Value& configuration); + + // Determines how far the configuration is through its lifespan (the maximum of all of its chains). + double GetRemainingLifetimePercentage() const; + + private: + // The identifier used when logging. + std::string m_identifier; + + // The configured chains. + std::vector> m_configuration; + + // We store the last certificate that was successfully validated to speed up subsequent checks. + // Only cache a single certificate under the assumption that most of the time there will + // only be a single server certificate in use. + mutable std::vector m_cachedCertificate; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/Compression.h b/src/AppInstallerSharedLib/Public/winget/Compression.h index 8a39669aaf..0fa330141b 100644 --- a/src/AppInstallerSharedLib/Public/winget/Compression.h +++ b/src/AppInstallerSharedLib/Public/winget/Compression.h @@ -1,54 +1,54 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller::Compression -{ - // Contains a compressor from the Windows Compression API. - struct Compressor - { - // Create a compressor using the given algorithm (see COMPRESS_ALGORITHM_*) - Compressor(DWORD algorithm); - - // Compresses the given data. - std::vector Compress(std::string_view data); - - // Resets the compressor. - void Reset(); - - // Sets compressor information values. - void SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value); - - // Gets compressor information values. - DWORD GetInformation(COMPRESS_INFORMATION_CLASS information); - - private: - wil::unique_any m_compressor; - }; - - // Contains a decompressor from the Windows Compression API. - struct Decompressor - { - // Create a decompressor using the given algorithm (see COMPRESS_ALGORITHM_*) - Decompressor(DWORD algorithm); - - // Decompresses the given data. - std::vector Decompress(const std::vector& data); - - // Resets the decompressor. - void Reset(); - - // Sets decompressor information values. - void SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value); - - // Gets decompressor information values. - DWORD GetInformation(COMPRESS_INFORMATION_CLASS information); - - private: - wil::unique_any m_decompressor; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller::Compression +{ + // Contains a compressor from the Windows Compression API. + struct Compressor + { + // Create a compressor using the given algorithm (see COMPRESS_ALGORITHM_*) + Compressor(DWORD algorithm); + + // Compresses the given data. + std::vector Compress(std::string_view data); + + // Resets the compressor. + void Reset(); + + // Sets compressor information values. + void SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value); + + // Gets compressor information values. + DWORD GetInformation(COMPRESS_INFORMATION_CLASS information); + + private: + wil::unique_any m_compressor; + }; + + // Contains a decompressor from the Windows Compression API. + struct Decompressor + { + // Create a decompressor using the given algorithm (see COMPRESS_ALGORITHM_*) + Decompressor(DWORD algorithm); + + // Decompresses the given data. + std::vector Decompress(const std::vector& data); + + // Resets the decompressor. + void Reset(); + + // Sets decompressor information values. + void SetInformation(COMPRESS_INFORMATION_CLASS information, DWORD value); + + // Gets decompressor information values. + DWORD GetInformation(COMPRESS_INFORMATION_CLASS information); + + private: + wil::unique_any m_decompressor; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/ConfigurationSetProcessorHandlers.h b/src/AppInstallerSharedLib/Public/winget/ConfigurationSetProcessorHandlers.h index 31e97b58b1..3d5fd1b806 100644 --- a/src/AppInstallerSharedLib/Public/winget/ConfigurationSetProcessorHandlers.h +++ b/src/AppInstallerSharedLib/Public/winget/ConfigurationSetProcessorHandlers.h @@ -1,14 +1,14 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Configuration -{ - constexpr std::wstring_view PowerShellHandlerIdentifier = L"pwsh"; - constexpr std::wstring_view DynamicRuntimeHandlerIdentifier = L"{73fea39f-6f4a-41c9-ba94-6fd14d633e40}"; - constexpr std::wstring_view DSCv3HandlerIdentifier = L"{dbb2ac6d-1b58-4b05-9c50-b463cc434771}"; - constexpr std::wstring_view DSCv3DynamicRuntimeHandlerIdentifier = L"{5f83e564-ca26-41ca-89db-36f5f0517ffd}"; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Configuration +{ + constexpr std::wstring_view PowerShellHandlerIdentifier = L"pwsh"; + constexpr std::wstring_view DynamicRuntimeHandlerIdentifier = L"{73fea39f-6f4a-41c9-ba94-6fd14d633e40}"; + constexpr std::wstring_view DSCv3HandlerIdentifier = L"{dbb2ac6d-1b58-4b05-9c50-b463cc434771}"; + constexpr std::wstring_view DSCv3DynamicRuntimeHandlerIdentifier = L"{5f83e564-ca26-41ca-89db-36f5f0517ffd}"; +} diff --git a/src/AppInstallerSharedLib/Public/winget/Filesystem.h b/src/AppInstallerSharedLib/Public/winget/Filesystem.h index 1a7df6aa28..f450444234 100644 --- a/src/AppInstallerSharedLib/Public/winget/Filesystem.h +++ b/src/AppInstallerSharedLib/Public/winget/Filesystem.h @@ -1,155 +1,155 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include - -using namespace std::chrono_literals; - -namespace AppInstaller::Filesystem -{ - // Checks if the file system at path supports named streams/ADS - bool SupportsNamedStreams(const std::filesystem::path& path); - - // Checks if the file system at path supports hard links - bool SupportsHardLinks(const std::filesystem::path& path); - - // Checks if the file system at path support reparse points - bool SupportsReparsePoints(const std::filesystem::path& path); - - // Checks if a relative paths points to a location outside of the base path. - bool PathEscapesBaseDirectory(std::string_view relativePath); - - // Renames the file to a new path. - void RenameFile(const std::filesystem::path& from, const std::filesystem::path& to); - - // Creates a symlink that points to the target path. - bool CreateSymlink(const std::filesystem::path& target, const std::filesystem::path& link); - - // Verifies that a symlink points to the target path. - bool VerifySymlink(const std::filesystem::path& symlink, const std::filesystem::path& target); - - // Appends the .exe extension to the path if not present. - void AppendExtension(std::filesystem::path& value, const std::string& extension); - - // Checks if the path is a symlink and exists. - bool SymlinkExists(const std::filesystem::path& symlinkPath); - bool CreateSymlink(const std::filesystem::path& path, const std::filesystem::path& target); - - // Get expanded file system path. - std::filesystem::path GetExpandedPath(const std::string& path); - - // If `source` begins with all of `prefix`, replace that with `replacement`. - // Returns true if replacement happened, false otherwise. - bool ReplaceCommonPathPrefix(std::filesystem::path& source, const std::filesystem::path& prefix, std::string_view replacement); - - // Gets the path of a known folder. - std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id); - - // Verifies that the paths are on the same volume. - bool IsSameVolume(const std::filesystem::path& path1, const std::filesystem::path& path2); - - // Verifies if 'path' has parent equal to 'parentPath' - bool IsParentPath(const std::filesystem::path& path, const std::filesystem::path& parentPath); - - // The principal that an ACE applies to. - enum class ACEPrincipal : uint32_t - { - CurrentUser, - Admins, - System, - }; - - // The permissions granted to a specific ACE. - enum class ACEPermissions : uint32_t - { - // This is not "Deny All", but rather, "Not mentioned" - None = 0x0, - Read = 0x1, - Write = 0x2, - Execute = 0x4, - ReadWrite = Read | Write, - ReadExecute = Read | Execute, - ReadWriteExecute = Read | Write | Execute, - // All means that full control will be granted - All = 0xFFFFFFFF - }; - - DEFINE_ENUM_FLAG_OPERATORS(ACEPermissions); - - // Information about a path that we use and how to set it up. - struct PathDetails - { - std::filesystem::path Path; - // Default to creating the directory with inherited ownership and permissions - bool Create = true; - std::optional Owner; - std::map ACL; - - // Shorthand for setting Owner and giving them ACEPermissions::All - void SetOwner(ACEPrincipal owner); - - // Determines if the ACL needs to be applied. - bool ShouldApplyACL() const; - - // Applies the ACL unconditionally. - void ApplyACL() const; - }; - - // Initializes from the given details and returns the path to it. - // The path is moved out of the details. - std::filesystem::path InitializeAndGetPathTo(PathDetails&& details); - - // Gets the path to the requested location. - template - std::filesystem::path GetPathTo(PathEnum path, bool forDisplay = false) - { - return InitializeAndGetPathTo(GetPathDetailsFor(path, forDisplay)); - } - - // A shared path. - enum class PathName - { - // Local state root that is specifically unpackaged (even if used from a packaged process). - UnpackagedLocalStateRoot, - // Local settings root that is specifically unpackaged (even if used from a packaged process). - UnpackagedSettingsRoot, - }; - - // Gets the PathDetails used for the given path. - // This is exposed primarily to allow for testing, GetPathTo should be preferred. - PathDetails GetPathDetailsFor(PathName path, bool forDisplay = false); - - // Gets the path to the executable for the given process. - std::filesystem::path GetExecutablePathForProcess(HANDLE process); - - // Information about a specific file. - struct FileInfo - { - std::filesystem::path Path; - std::filesystem::file_time_type LastWriteTime{}; - uintmax_t Size = 0; - }; - - // Gets the FileInfo for each regular file directly under the given directory. - std::vector GetFileInfoFor(const std::filesystem::path& directory); - - // Limitations on a set of files. - // Any value that is 0 is treated as no limit. - struct FileLimits - { - std::chrono::hours Age = 0h; - uint32_t TotalSizeInMB = 0; - size_t Count = 0; - }; - - // Modifies the given files to only include those that exceed the limits that are provided. - void FilterToFilesExceedingLimits(std::vector& files, const FileLimits& limits); - - // Writes the given string to the file handle, handling partial writes. - void WriteStringToFile(HANDLE fileHandle, std::string_view content); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace AppInstaller::Filesystem +{ + // Checks if the file system at path supports named streams/ADS + bool SupportsNamedStreams(const std::filesystem::path& path); + + // Checks if the file system at path supports hard links + bool SupportsHardLinks(const std::filesystem::path& path); + + // Checks if the file system at path support reparse points + bool SupportsReparsePoints(const std::filesystem::path& path); + + // Checks if a relative paths points to a location outside of the base path. + bool PathEscapesBaseDirectory(std::string_view relativePath); + + // Renames the file to a new path. + void RenameFile(const std::filesystem::path& from, const std::filesystem::path& to); + + // Creates a symlink that points to the target path. + bool CreateSymlink(const std::filesystem::path& target, const std::filesystem::path& link); + + // Verifies that a symlink points to the target path. + bool VerifySymlink(const std::filesystem::path& symlink, const std::filesystem::path& target); + + // Appends the .exe extension to the path if not present. + void AppendExtension(std::filesystem::path& value, const std::string& extension); + + // Checks if the path is a symlink and exists. + bool SymlinkExists(const std::filesystem::path& symlinkPath); + bool CreateSymlink(const std::filesystem::path& path, const std::filesystem::path& target); + + // Get expanded file system path. + std::filesystem::path GetExpandedPath(const std::string& path); + + // If `source` begins with all of `prefix`, replace that with `replacement`. + // Returns true if replacement happened, false otherwise. + bool ReplaceCommonPathPrefix(std::filesystem::path& source, const std::filesystem::path& prefix, std::string_view replacement); + + // Gets the path of a known folder. + std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id); + + // Verifies that the paths are on the same volume. + bool IsSameVolume(const std::filesystem::path& path1, const std::filesystem::path& path2); + + // Verifies if 'path' has parent equal to 'parentPath' + bool IsParentPath(const std::filesystem::path& path, const std::filesystem::path& parentPath); + + // The principal that an ACE applies to. + enum class ACEPrincipal : uint32_t + { + CurrentUser, + Admins, + System, + }; + + // The permissions granted to a specific ACE. + enum class ACEPermissions : uint32_t + { + // This is not "Deny All", but rather, "Not mentioned" + None = 0x0, + Read = 0x1, + Write = 0x2, + Execute = 0x4, + ReadWrite = Read | Write, + ReadExecute = Read | Execute, + ReadWriteExecute = Read | Write | Execute, + // All means that full control will be granted + All = 0xFFFFFFFF + }; + + DEFINE_ENUM_FLAG_OPERATORS(ACEPermissions); + + // Information about a path that we use and how to set it up. + struct PathDetails + { + std::filesystem::path Path; + // Default to creating the directory with inherited ownership and permissions + bool Create = true; + std::optional Owner; + std::map ACL; + + // Shorthand for setting Owner and giving them ACEPermissions::All + void SetOwner(ACEPrincipal owner); + + // Determines if the ACL needs to be applied. + bool ShouldApplyACL() const; + + // Applies the ACL unconditionally. + void ApplyACL() const; + }; + + // Initializes from the given details and returns the path to it. + // The path is moved out of the details. + std::filesystem::path InitializeAndGetPathTo(PathDetails&& details); + + // Gets the path to the requested location. + template + std::filesystem::path GetPathTo(PathEnum path, bool forDisplay = false) + { + return InitializeAndGetPathTo(GetPathDetailsFor(path, forDisplay)); + } + + // A shared path. + enum class PathName + { + // Local state root that is specifically unpackaged (even if used from a packaged process). + UnpackagedLocalStateRoot, + // Local settings root that is specifically unpackaged (even if used from a packaged process). + UnpackagedSettingsRoot, + }; + + // Gets the PathDetails used for the given path. + // This is exposed primarily to allow for testing, GetPathTo should be preferred. + PathDetails GetPathDetailsFor(PathName path, bool forDisplay = false); + + // Gets the path to the executable for the given process. + std::filesystem::path GetExecutablePathForProcess(HANDLE process); + + // Information about a specific file. + struct FileInfo + { + std::filesystem::path Path; + std::filesystem::file_time_type LastWriteTime{}; + uintmax_t Size = 0; + }; + + // Gets the FileInfo for each regular file directly under the given directory. + std::vector GetFileInfoFor(const std::filesystem::path& directory); + + // Limitations on a set of files. + // Any value that is 0 is treated as no limit. + struct FileLimits + { + std::chrono::hours Age = 0h; + uint32_t TotalSizeInMB = 0; + size_t Count = 0; + }; + + // Modifies the given files to only include those that exceed the limits that are provided. + void FilterToFilesExceedingLimits(std::vector& files, const FileLimits& limits); + + // Writes the given string to the file handle, handling partial writes. + void WriteStringToFile(HANDLE fileHandle, std::string_view content); +} diff --git a/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h b/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h index f4521a6d41..57d2315b9d 100644 --- a/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h +++ b/src/AppInstallerSharedLib/Public/winget/GroupPolicy.h @@ -1,244 +1,244 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include - -#include - -using namespace std::string_view_literals; - -namespace AppInstaller::Settings -{ - - // A policy that sets a value for some setting. - // The value of the policy is a value in the registry key, or is - // made up of sub-keys for settings that are lists. - enum class ValuePolicy - { - None, - SourceAutoUpdateIntervalInMinutes, - AdditionalSources, - AllowedSources, - DefaultProxy, - Max, - }; - - // A policy that acts as a toggle to enable or disable a feature. - // They are backed by a DWORD value with values 0 and 1. - struct TogglePolicy - { - enum class Policy - { - None = 0, - WinGet, - Settings, - ExperimentalFeatures, - LocalManifestFiles, - HashOverride, - LocalArchiveMalwareScanOverride, - DefaultSource, - MSStoreSource, - FontSource, - AdditionalSources, - AllowedSources, - BypassCertificatePinningForMicrosoftStore, - WinGetCommandLineInterfaces, - Configuration, - ProxyCommandLineOptions, - McpServer, - ConfigurationProcessorPath, - Max, - }; - - TogglePolicy(Policy policy, std::string_view regValueName, StringResource::StringId policyName, bool defaultIsEnabled = true) : - m_policy(policy), m_regValueName(regValueName), m_policyName(policyName), m_defaultIsEnabled(defaultIsEnabled) {} - - static TogglePolicy GetPolicy(Policy policy); - static std::vector GetAllPolicies(); - - Policy GetPolicy() const { return m_policy; } - std::string_view RegValueName() const { return m_regValueName; } - StringResource::StringId PolicyName() const { return m_policyName; } - bool DefaultIsEnabled() const { return m_defaultIsEnabled; } - - private: - Policy m_policy; - std::string_view m_regValueName; - StringResource::StringId m_policyName; - bool m_defaultIsEnabled; - }; - - // Possible configuration states for a policy. - enum class PolicyState - { - NotConfigured, - Disabled, - Enabled, - }; - - // A source defined by Group Policy to be added or allowed - struct SourceFromPolicy - { - std::string Name; - std::string Arg; - std::string Type; - std::string Data; - std::string Identifier; - std::vector TrustLevel; - bool Explicit = false; - std::optional Priority; - -#ifndef AICLI_DISABLE_TEST_HOOKS - Certificates::PinningConfiguration PinningConfiguration; -#endif - - std::string ToJsonString() const; - }; - - - namespace details - { - - template - struct ValuePolicyMapping - { - // value_t - type of the policy - // ReadAndValidate() - Function that reads the value and does semantic validation. - - // For simple values: - // ValueName - Name of the registry value - // ValueType - Type of the registry value - // reg_value_t - Type returned by the registry when reading the value - - // For lists: - // item_t - Type of each item - // KeyName -- Name of the sub-key containing the list - // ReadAndValidateItem() - Function that reads a single item from a subkey - }; - - template<> - struct ValuePolicyMapping - { - using value_t = std::monostate; - using opt_value_t = std::nullopt_t; - static std::nullopt_t ReadAndValidate(const Registry::Key& policiesKey); - }; - -#define POLICY_MAPPING_SPECIALIZATION(_policy_, _type_, _extra_) \ - template <> \ - struct ValuePolicyMapping<_policy_> \ - { \ - using value_t = _type_; \ - using opt_value_t = std::optional; \ - static std::optional ReadAndValidate(const Registry::Key& policiesKey); \ - _extra_ \ - } - -#define POLICY_MAPPING_VALUE_SPECIALIZATION(_policy_, _type_, _valueName_, _valueType_) \ - POLICY_MAPPING_SPECIALIZATION(_policy_, _type_, \ - static constexpr std::string_view ValueName = _valueName_; \ - static constexpr Registry::Value::Type ValueType = _valueType_; \ - using reg_value_t = decltype(std::declval().GetValue()); \ - ) - -#define POLICY_MAPPING_LIST_SPECIALIZATION(_policy_, _type_, _keyName_) \ - POLICY_MAPPING_SPECIALIZATION(_policy_, std::vector<_type_>, \ - static constexpr std::string_view KeyName = _keyName_; \ - using item_t = _type_; \ - static std::optional ReadAndValidateItem(const Registry::Value& item); \ - ) - - POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::SourceAutoUpdateIntervalInMinutes, uint32_t, "SourceAutoUpdateInterval"sv, Registry::Value::Type::DWord); - POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::DefaultProxy, std::string, "DefaultProxy"sv, Registry::Value::Type::String); - - POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AdditionalSources, SourceFromPolicy, "AdditionalSources"sv); - POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AllowedSources, SourceFromPolicy, "AllowedSources"sv); - } - - // Representation of the policies read from the registry. - struct GroupPolicy - { - using ValuePoliciesMap = EnumBasedVariantMap; - - static GroupPolicy& Instance(); - - GroupPolicy(Registry::Key key); - ~GroupPolicy() = default; - - GroupPolicy() = delete; - - GroupPolicy(const GroupPolicy&) = delete; - GroupPolicy& operator=(const GroupPolicy&) = delete; - - GroupPolicy(GroupPolicy&&) = delete; - GroupPolicy& operator=(GroupPolicy&&) = delete; - - template - using ValueType = typename details::ValuePolicyMapping

::value_t; - - // Gets the policy value if it is present - template - typename details::ValuePolicyMapping

::opt_value_t GetValue() const - { - auto lock = m_lock.lock_shared(); - if (m_values.Contains(P)) - { - return m_values.Get

(); - } - else - { - return std::nullopt; - } - } - - template<> - std::nullopt_t GetValue() const - { - return std::nullopt; - } - - PolicyState GetState(TogglePolicy::Policy policy) const; - - // Checks whether a policy is enabled, using an appropriate default when not configured. - // Should not be used when not configured means something different than enabled/disabled. - bool IsEnabled(TogglePolicy::Policy policy) const; - - // Re-reads all policy values from the registry. - void Reload(); - -#ifndef AICLI_DISABLE_TEST_HOOKS - protected: - static void OverrideInstance(GroupPolicy* gp); - static void ResetInstance(); -#else - private: -#endif - Registry::Key m_key; - mutable wil::srwlock m_lock; - std::map m_toggles; - ValuePoliciesMap m_values; - - private: - PolicyState GetStateNoLock(TogglePolicy::Policy policy) const; - }; - - inline const GroupPolicy& GroupPolicies() - { - return GroupPolicy::Instance(); - } - - struct GroupPolicyException - { - GroupPolicyException(TogglePolicy::Policy policy) : m_policy(policy) {} - - const TogglePolicy::Policy& Policy() const { return m_policy; } - - private: - TogglePolicy::Policy m_policy; - }; - -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include + +#include + +using namespace std::string_view_literals; + +namespace AppInstaller::Settings +{ + + // A policy that sets a value for some setting. + // The value of the policy is a value in the registry key, or is + // made up of sub-keys for settings that are lists. + enum class ValuePolicy + { + None, + SourceAutoUpdateIntervalInMinutes, + AdditionalSources, + AllowedSources, + DefaultProxy, + Max, + }; + + // A policy that acts as a toggle to enable or disable a feature. + // They are backed by a DWORD value with values 0 and 1. + struct TogglePolicy + { + enum class Policy + { + None = 0, + WinGet, + Settings, + ExperimentalFeatures, + LocalManifestFiles, + HashOverride, + LocalArchiveMalwareScanOverride, + DefaultSource, + MSStoreSource, + FontSource, + AdditionalSources, + AllowedSources, + BypassCertificatePinningForMicrosoftStore, + WinGetCommandLineInterfaces, + Configuration, + ProxyCommandLineOptions, + McpServer, + ConfigurationProcessorPath, + Max, + }; + + TogglePolicy(Policy policy, std::string_view regValueName, StringResource::StringId policyName, bool defaultIsEnabled = true) : + m_policy(policy), m_regValueName(regValueName), m_policyName(policyName), m_defaultIsEnabled(defaultIsEnabled) {} + + static TogglePolicy GetPolicy(Policy policy); + static std::vector GetAllPolicies(); + + Policy GetPolicy() const { return m_policy; } + std::string_view RegValueName() const { return m_regValueName; } + StringResource::StringId PolicyName() const { return m_policyName; } + bool DefaultIsEnabled() const { return m_defaultIsEnabled; } + + private: + Policy m_policy; + std::string_view m_regValueName; + StringResource::StringId m_policyName; + bool m_defaultIsEnabled; + }; + + // Possible configuration states for a policy. + enum class PolicyState + { + NotConfigured, + Disabled, + Enabled, + }; + + // A source defined by Group Policy to be added or allowed + struct SourceFromPolicy + { + std::string Name; + std::string Arg; + std::string Type; + std::string Data; + std::string Identifier; + std::vector TrustLevel; + bool Explicit = false; + std::optional Priority; + +#ifndef AICLI_DISABLE_TEST_HOOKS + Certificates::PinningConfiguration PinningConfiguration; +#endif + + std::string ToJsonString() const; + }; + + + namespace details + { + + template + struct ValuePolicyMapping + { + // value_t - type of the policy + // ReadAndValidate() - Function that reads the value and does semantic validation. + + // For simple values: + // ValueName - Name of the registry value + // ValueType - Type of the registry value + // reg_value_t - Type returned by the registry when reading the value + + // For lists: + // item_t - Type of each item + // KeyName -- Name of the sub-key containing the list + // ReadAndValidateItem() - Function that reads a single item from a subkey + }; + + template<> + struct ValuePolicyMapping + { + using value_t = std::monostate; + using opt_value_t = std::nullopt_t; + static std::nullopt_t ReadAndValidate(const Registry::Key& policiesKey); + }; + +#define POLICY_MAPPING_SPECIALIZATION(_policy_, _type_, _extra_) \ + template <> \ + struct ValuePolicyMapping<_policy_> \ + { \ + using value_t = _type_; \ + using opt_value_t = std::optional; \ + static std::optional ReadAndValidate(const Registry::Key& policiesKey); \ + _extra_ \ + } + +#define POLICY_MAPPING_VALUE_SPECIALIZATION(_policy_, _type_, _valueName_, _valueType_) \ + POLICY_MAPPING_SPECIALIZATION(_policy_, _type_, \ + static constexpr std::string_view ValueName = _valueName_; \ + static constexpr Registry::Value::Type ValueType = _valueType_; \ + using reg_value_t = decltype(std::declval().GetValue()); \ + ) + +#define POLICY_MAPPING_LIST_SPECIALIZATION(_policy_, _type_, _keyName_) \ + POLICY_MAPPING_SPECIALIZATION(_policy_, std::vector<_type_>, \ + static constexpr std::string_view KeyName = _keyName_; \ + using item_t = _type_; \ + static std::optional ReadAndValidateItem(const Registry::Value& item); \ + ) + + POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::SourceAutoUpdateIntervalInMinutes, uint32_t, "SourceAutoUpdateInterval"sv, Registry::Value::Type::DWord); + POLICY_MAPPING_VALUE_SPECIALIZATION(ValuePolicy::DefaultProxy, std::string, "DefaultProxy"sv, Registry::Value::Type::String); + + POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AdditionalSources, SourceFromPolicy, "AdditionalSources"sv); + POLICY_MAPPING_LIST_SPECIALIZATION(ValuePolicy::AllowedSources, SourceFromPolicy, "AllowedSources"sv); + } + + // Representation of the policies read from the registry. + struct GroupPolicy + { + using ValuePoliciesMap = EnumBasedVariantMap; + + static GroupPolicy& Instance(); + + GroupPolicy(Registry::Key key); + ~GroupPolicy() = default; + + GroupPolicy() = delete; + + GroupPolicy(const GroupPolicy&) = delete; + GroupPolicy& operator=(const GroupPolicy&) = delete; + + GroupPolicy(GroupPolicy&&) = delete; + GroupPolicy& operator=(GroupPolicy&&) = delete; + + template + using ValueType = typename details::ValuePolicyMapping

::value_t; + + // Gets the policy value if it is present + template + typename details::ValuePolicyMapping

::opt_value_t GetValue() const + { + auto lock = m_lock.lock_shared(); + if (m_values.Contains(P)) + { + return m_values.Get

(); + } + else + { + return std::nullopt; + } + } + + template<> + std::nullopt_t GetValue() const + { + return std::nullopt; + } + + PolicyState GetState(TogglePolicy::Policy policy) const; + + // Checks whether a policy is enabled, using an appropriate default when not configured. + // Should not be used when not configured means something different than enabled/disabled. + bool IsEnabled(TogglePolicy::Policy policy) const; + + // Re-reads all policy values from the registry. + void Reload(); + +#ifndef AICLI_DISABLE_TEST_HOOKS + protected: + static void OverrideInstance(GroupPolicy* gp); + static void ResetInstance(); +#else + private: +#endif + Registry::Key m_key; + mutable wil::srwlock m_lock; + std::map m_toggles; + ValuePoliciesMap m_values; + + private: + PolicyState GetStateNoLock(TogglePolicy::Policy policy) const; + }; + + inline const GroupPolicy& GroupPolicies() + { + return GroupPolicy::Instance(); + } + + struct GroupPolicyException + { + GroupPolicyException(TogglePolicy::Policy policy) : m_policy(policy) {} + + const TogglePolicy::Policy& Policy() const { return m_policy; } + + private: + TogglePolicy::Policy m_policy; + }; + +} diff --git a/src/AppInstallerSharedLib/Public/winget/IConfigurationStaticsInternals.h b/src/AppInstallerSharedLib/Public/winget/IConfigurationStaticsInternals.h index 95b0528204..70bddf0105 100644 --- a/src/AppInstallerSharedLib/Public/winget/IConfigurationStaticsInternals.h +++ b/src/AppInstallerSharedLib/Public/winget/IConfigurationStaticsInternals.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::WinRT -{ - // Flag values for SetExperimentalState. - enum class ConfigurationStaticsInternalsStateFlags : UINT32 - { - None = 0, - All = None - }; - - DEFINE_ENUM_FLAG_OPERATORS(ConfigurationStaticsInternalsStateFlags); - - MIDL_INTERFACE("C3886148-148A-4A3D-8018-9CDACDFC0B8D") - IConfigurationStaticsInternals : public IUnknown - { - public: - virtual /* [local] */ HRESULT STDMETHODCALLTYPE SetExperimentalState( - UINT32 state) = 0; - - virtual /* [local] */ HRESULT STDMETHODCALLTYPE BlockNewWorkForShutdown() = 0; - - virtual /* [local] */ HRESULT STDMETHODCALLTYPE BeginShutdown() = 0; - - virtual /* [local] */ HRESULT STDMETHODCALLTYPE WaitForShutdown() = 0; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::WinRT +{ + // Flag values for SetExperimentalState. + enum class ConfigurationStaticsInternalsStateFlags : UINT32 + { + None = 0, + All = None + }; + + DEFINE_ENUM_FLAG_OPERATORS(ConfigurationStaticsInternalsStateFlags); + + MIDL_INTERFACE("C3886148-148A-4A3D-8018-9CDACDFC0B8D") + IConfigurationStaticsInternals : public IUnknown + { + public: + virtual /* [local] */ HRESULT STDMETHODCALLTYPE SetExperimentalState( + UINT32 state) = 0; + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE BlockNewWorkForShutdown() = 0; + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE BeginShutdown() = 0; + + virtual /* [local] */ HRESULT STDMETHODCALLTYPE WaitForShutdown() = 0; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/ILifetimeWatcher.h b/src/AppInstallerSharedLib/Public/winget/ILifetimeWatcher.h index 3083ed0001..858f04765d 100644 --- a/src/AppInstallerSharedLib/Public/winget/ILifetimeWatcher.h +++ b/src/AppInstallerSharedLib/Public/winget/ILifetimeWatcher.h @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::WinRT -{ - MIDL_INTERFACE("59b5623f-d03e-41f8-b400-89ee04ea02d7") - ILifetimeWatcher : public IUnknown - { - public: - // Due to the way the winrt types are set up, this watcher will not have AddRef called. - virtual /* [local] */ HRESULT STDMETHODCALLTYPE SetLifetimeWatcher( - IUnknown* watcher) = 0; - }; - - // Implements ILifetimeWatcher functionality. - struct LifetimeWatcherBase - { - HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher) - { - m_lifetimeWatcher = winrt::Windows::Foundation::IUnknown(watcher, winrt::take_ownership_from_abi); - return S_OK; - } - - void PropagateLifetimeWatcher(const winrt::Windows::Foundation::IUnknown& child) - { - if (m_lifetimeWatcher && child) - { - // Require that any call to this function is for an object that implements watching to prevent - // accidental assumptions about the child object and lifetime management. - auto watcher = child.as(); - - // Create a copy of the lifetime watcher (to add_ref), then detach and pass it to the child to own. - watcher->SetLifetimeWatcher(static_cast(winrt::detach_abi(winrt::Windows::Foundation::IUnknown{ m_lifetimeWatcher }))); - } - } - - private: - winrt::Windows::Foundation::IUnknown m_lifetimeWatcher; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::WinRT +{ + MIDL_INTERFACE("59b5623f-d03e-41f8-b400-89ee04ea02d7") + ILifetimeWatcher : public IUnknown + { + public: + // Due to the way the winrt types are set up, this watcher will not have AddRef called. + virtual /* [local] */ HRESULT STDMETHODCALLTYPE SetLifetimeWatcher( + IUnknown* watcher) = 0; + }; + + // Implements ILifetimeWatcher functionality. + struct LifetimeWatcherBase + { + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher) + { + m_lifetimeWatcher = winrt::Windows::Foundation::IUnknown(watcher, winrt::take_ownership_from_abi); + return S_OK; + } + + void PropagateLifetimeWatcher(const winrt::Windows::Foundation::IUnknown& child) + { + if (m_lifetimeWatcher && child) + { + // Require that any call to this function is for an object that implements watching to prevent + // accidental assumptions about the child object and lifetime management. + auto watcher = child.as(); + + // Create a copy of the lifetime watcher (to add_ref), then detach and pass it to the child to own. + watcher->SetLifetimeWatcher(static_cast(winrt::detach_abi(winrt::Windows::Foundation::IUnknown{ m_lifetimeWatcher }))); + } + } + + private: + winrt::Windows::Foundation::IUnknown m_lifetimeWatcher; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/JsonSchemaValidation.h b/src/AppInstallerSharedLib/Public/winget/JsonSchemaValidation.h index fac68f2b99..179e587362 100644 --- a/src/AppInstallerSharedLib/Public/winget/JsonSchemaValidation.h +++ b/src/AppInstallerSharedLib/Public/winget/JsonSchemaValidation.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::JsonSchema -{ - // Load schema as parsed json doc - Json::Value LoadSchemaDoc(std::string_view schemaStr); - - // Load an embedded resource from binary and return as Json::Value - Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType); - - // Populate a valijson Schema object from a json value - void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema); - - // Validate a json doc with a schema - // Returns whether it was successful and fills the results object - bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results); - - // Extracts the error messages from a result into a single non-localized string - std::string GetErrorStringFromResults(valijson::ValidationResults& results); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::JsonSchema +{ + // Load schema as parsed json doc + Json::Value LoadSchemaDoc(std::string_view schemaStr); + + // Load an embedded resource from binary and return as Json::Value + Json::Value LoadResourceAsSchemaDoc(PCWSTR resourceName, PCWSTR resourceType); + + // Populate a valijson Schema object from a json value + void PopulateSchema(const Json::Value& schemaJson, valijson::Schema& schema); + + // Validate a json doc with a schema + // Returns whether it was successful and fills the results object + bool Validate(const valijson::Schema& schema, const Json::Value& json, valijson::ValidationResults& results); + + // Extracts the error messages from a result into a single non-localized string + std::string GetErrorStringFromResults(valijson::ValidationResults& results); } \ No newline at end of file diff --git a/src/AppInstallerSharedLib/Public/winget/JsonUtil.h b/src/AppInstallerSharedLib/Public/winget/JsonUtil.h index 7c80536cb0..ad377df606 100644 --- a/src/AppInstallerSharedLib/Public/winget/JsonUtil.h +++ b/src/AppInstallerSharedLib/Public/winget/JsonUtil.h @@ -53,11 +53,11 @@ namespace AppInstaller::JSON std::vector GetRawStringArrayFromJsonNode(const web::json::value& node, const utility::string_t& keyName); std::set GetRawStringSetFromJsonNode(const web::json::value& node, const utility::string_t& keyName); - std::optional GetRawIntValueFromJsonValue(const web::json::value& value); + std::optional GetRawIntValueFromJsonValue(const web::json::value& value); std::optional GetRawUInt64ValueFromJsonValue(const web::json::value& value); - std::optional GetRawIntValueFromJsonNode(const web::json::value& node, const utility::string_t& keyName); + std::optional GetRawIntValueFromJsonNode(const web::json::value& node, const utility::string_t& keyName); std::optional GetRawUInt64ValueFromJsonNode(const web::json::value& node, const utility::string_t& keyName); diff --git a/src/AppInstallerSharedLib/Public/winget/ManagedFile.h b/src/AppInstallerSharedLib/Public/winget/ManagedFile.h index 7288c828a7..ef6b75f4fd 100644 --- a/src/AppInstallerSharedLib/Public/winget/ManagedFile.h +++ b/src/AppInstallerSharedLib/Public/winget/ManagedFile.h @@ -1,36 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::Utility -{ - // Struct that holds a file handle and may perform additional operations on exit - struct ManagedFile - { - ManagedFile() = default; - - ManagedFile(const ManagedFile&) = delete; - ManagedFile& operator=(const ManagedFile&) = delete; - - ManagedFile(ManagedFile&&) = default; - ManagedFile& operator=(ManagedFile&&) = default; - - HANDLE GetFileHandle() const { return m_fileHandle.get(); } - const std::filesystem::path& GetFilePath() const { return m_filePath; } - - // Always creates a new write locked file at the path given. desiredAccess is passed to CreateFile call. - static ManagedFile CreateWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess, bool deleteOnExit); - - // Always opens an existing file at the path given with write locked. desiredAccess is passed to CreateFile call. - static ManagedFile OpenWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess); - - ~ManagedFile(); - - private: - std::filesystem::path m_filePath; - wil::unique_handle m_fileHandle; - bool m_deleteFileOnExit = false; - }; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::Utility +{ + // Struct that holds a file handle and may perform additional operations on exit + struct ManagedFile + { + ManagedFile() = default; + + ManagedFile(const ManagedFile&) = delete; + ManagedFile& operator=(const ManagedFile&) = delete; + + ManagedFile(ManagedFile&&) = default; + ManagedFile& operator=(ManagedFile&&) = default; + + HANDLE GetFileHandle() const { return m_fileHandle.get(); } + const std::filesystem::path& GetFilePath() const { return m_filePath; } + + // Always creates a new write locked file at the path given. desiredAccess is passed to CreateFile call. + static ManagedFile CreateWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess, bool deleteOnExit); + + // Always opens an existing file at the path given with write locked. desiredAccess is passed to CreateFile call. + static ManagedFile OpenWriteLockedFile(const std::filesystem::path& path, DWORD desiredAccess); + + ~ManagedFile(); + + private: + std::filesystem::path m_filePath; + wil::unique_handle m_fileHandle; + bool m_deleteFileOnExit = false; + }; } \ No newline at end of file diff --git a/src/AppInstallerSharedLib/Public/winget/ModuleCountBase.h b/src/AppInstallerSharedLib/Public/winget/ModuleCountBase.h index f4aa1eda68..c27ac35e04 100644 --- a/src/AppInstallerSharedLib/Public/winget/ModuleCountBase.h +++ b/src/AppInstallerSharedLib/Public/winget/ModuleCountBase.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace AppInstaller::WinRT -{ - // Implements module count interactions. - struct ModuleCountBase - { - ModuleCountBase() - { - if (auto modulePtr = ::Microsoft::WRL::GetModuleBase()) - { - modulePtr->IncrementObjectCount(); - } - } - - ~ModuleCountBase() - { - if (auto modulePtr = ::Microsoft::WRL::GetModuleBase()) - { - modulePtr->DecrementObjectCount(); - } - } - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace AppInstaller::WinRT +{ + // Implements module count interactions. + struct ModuleCountBase + { + ModuleCountBase() + { + if (auto modulePtr = ::Microsoft::WRL::GetModuleBase()) + { + modulePtr->IncrementObjectCount(); + } + } + + ~ModuleCountBase() + { + if (auto modulePtr = ::Microsoft::WRL::GetModuleBase()) + { + modulePtr->DecrementObjectCount(); + } + } + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/PathTree.h b/src/AppInstallerSharedLib/Public/winget/PathTree.h index 202d9cd5d8..25df46321f 100644 --- a/src/AppInstallerSharedLib/Public/winget/PathTree.h +++ b/src/AppInstallerSharedLib/Public/winget/PathTree.h @@ -1,129 +1,129 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller::Filesystem -{ - // Container that holds a map of items addressable by path. - template - struct PathTree - { - using value_t = Value; - - PathTree() = default; - - private: - struct Node - { - value_t Value{}; - std::map Children; - }; - - public: - // Returns the value for the given path, inserting it if necessary. - value_t& FindOrInsert(const std::filesystem::path& path) - { - return FindNode(path, true)->Value; - } - - // Finds the value for the given path; returns null if not found. - value_t* Find(const std::filesystem::path& path) - { - Node* node = FindNode(path, false); - return node ? &node->Value : nullptr; - } - - // Finds the value for the given path; returns null if not found. - const value_t* Find(const std::filesystem::path& path) const - { - const Node* node = FindNode(path); - return node ? &node->Value : nullptr; - } - - // Invokes the `visit` function for each value in the tree starting at `initialPath` (unconditionally) - // and recursively continuing on to children for whom the predicate returns true. - void VisitIf(const std::filesystem::path& initialPath, std::function visit, std::function predicate) const - { - const Node* node = FindNode(initialPath); - if (node) - { - std::queue nodes; - nodes.push(node); - - while (!nodes.empty()) - { - const Node* currentNode = nodes.front(); - nodes.pop(); - - visit(currentNode->Value); - - for (const auto& child : currentNode->Children) - { - if (predicate(child.second.Value)) - { - nodes.push(&child.second); - } - } - } - } - } - - private: - // Finds the node for the given path, creating as needed if requested. - Node* FindNode(const std::filesystem::path& path, bool createIfNeeded) - { - if (path.empty()) - { - if (createIfNeeded) - { - THROW_HR(E_INVALIDARG); - } - else - { - return nullptr; - } - } - - const auto& nodePath = std::filesystem::weakly_canonical(path); - Node* currentNode = &m_rootNode; - - for (const auto& pathPart : nodePath) - { - auto& children = currentNode->Children; - - if (createIfNeeded) - { - currentNode = &children[pathPart]; - } - else - { - auto itr = children.find(pathPart); - - if (itr != children.end()) - { - currentNode = &itr->second; - } - else - { - // Not found and should not create - return nullptr; - } - } - } - - return currentNode; - } - - // Finds the node for the given path; returns null if not found. - const Node* FindNode(const std::filesystem::path& path) const - { - return const_cast(this)->FindNode(path, false); - } - - Node m_rootNode; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller::Filesystem +{ + // Container that holds a map of items addressable by path. + template + struct PathTree + { + using value_t = Value; + + PathTree() = default; + + private: + struct Node + { + value_t Value{}; + std::map Children; + }; + + public: + // Returns the value for the given path, inserting it if necessary. + value_t& FindOrInsert(const std::filesystem::path& path) + { + return FindNode(path, true)->Value; + } + + // Finds the value for the given path; returns null if not found. + value_t* Find(const std::filesystem::path& path) + { + Node* node = FindNode(path, false); + return node ? &node->Value : nullptr; + } + + // Finds the value for the given path; returns null if not found. + const value_t* Find(const std::filesystem::path& path) const + { + const Node* node = FindNode(path); + return node ? &node->Value : nullptr; + } + + // Invokes the `visit` function for each value in the tree starting at `initialPath` (unconditionally) + // and recursively continuing on to children for whom the predicate returns true. + void VisitIf(const std::filesystem::path& initialPath, std::function visit, std::function predicate) const + { + const Node* node = FindNode(initialPath); + if (node) + { + std::queue nodes; + nodes.push(node); + + while (!nodes.empty()) + { + const Node* currentNode = nodes.front(); + nodes.pop(); + + visit(currentNode->Value); + + for (const auto& child : currentNode->Children) + { + if (predicate(child.second.Value)) + { + nodes.push(&child.second); + } + } + } + } + } + + private: + // Finds the node for the given path, creating as needed if requested. + Node* FindNode(const std::filesystem::path& path, bool createIfNeeded) + { + if (path.empty()) + { + if (createIfNeeded) + { + THROW_HR(E_INVALIDARG); + } + else + { + return nullptr; + } + } + + const auto& nodePath = std::filesystem::weakly_canonical(path); + Node* currentNode = &m_rootNode; + + for (const auto& pathPart : nodePath) + { + auto& children = currentNode->Children; + + if (createIfNeeded) + { + currentNode = &children[pathPart]; + } + else + { + auto itr = children.find(pathPart); + + if (itr != children.end()) + { + currentNode = &itr->second; + } + else + { + // Not found and should not create + return nullptr; + } + } + } + + return currentNode; + } + + // Finds the node for the given path; returns null if not found. + const Node* FindNode(const std::filesystem::path& path) const + { + return const_cast(this)->FindNode(path, false); + } + + Node m_rootNode; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/Registry.h b/src/AppInstallerSharedLib/Public/winget/Registry.h index 7483d5144b..39d0e95266 100644 --- a/src/AppInstallerSharedLib/Public/winget/Registry.h +++ b/src/AppInstallerSharedLib/Public/winget/Registry.h @@ -1,313 +1,313 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include -#include -#include - -#define AICLI_REGISTRY_UTF16_FLAG 0x08000000 - -namespace AppInstaller::Registry -{ - namespace details - { - template - constexpr bool dependent_false = false; - - template - struct ValueTypeSpecifics - { - using value_t = void; - - static value_t Convert(const std::vector& data) - { - static_assert(dependent_false, "No Type specific override has been supplied"); - } - }; - - template <> - struct ValueTypeSpecifics - { - using value_t = std::vector; - static value_t Convert(const std::vector& data); - }; - - template <> - struct ValueTypeSpecifics - { - using value_t = std::string; - static value_t Convert(const std::vector& data); - }; - - template <> - struct ValueTypeSpecifics - { - using value_t = std::wstring; - static value_t Convert(const std::vector& data); - }; - - template <> - struct ValueTypeSpecifics - { - using value_t = std::string; - static value_t Convert(const std::vector& data); - }; - - template <> - struct ValueTypeSpecifics - { - using value_t = std::wstring; - static value_t Convert(const std::vector& data); - }; - - template <> - struct ValueTypeSpecifics - { - using value_t = std::vector; - static value_t Convert(const std::vector& data); - }; - - template <> - struct ValueTypeSpecifics - { - using value_t = uint32_t; - static value_t Convert(const std::vector& data); - }; - } - - struct Key; - struct ValueList; - - // A registry value. - struct Value - { - friend Key; - friend ValueList; - - // The type of data stored in the Value. - enum class Type : DWORD - { - None = REG_NONE, - String = REG_SZ, - UTF16Flag = AICLI_REGISTRY_UTF16_FLAG, - UTF16String = REG_SZ | UTF16Flag, - ExpandString = REG_EXPAND_SZ, - UTF16ExpandString = REG_EXPAND_SZ | UTF16Flag, - Binary = REG_BINARY, - DWord = REG_DWORD, - DWordLittleEndian = REG_DWORD_LITTLE_ENDIAN, - DWordBigEndian = REG_DWORD_BIG_ENDIAN, - MultiString = REG_MULTI_SZ, - QWord = REG_QWORD, - QWordLittleEndian = REG_QWORD_LITTLE_ENDIAN, - }; - - Type GetType() const { return m_type; } - - template - typename details::ValueTypeSpecifics(T)>::value_t GetValue() const - { - auto value = TryGetValue(); - if (!value.has_value()) - { - THROW_HR(E_INVALIDARG); - } - - return std::move(value.value()); - } - - template - typename std::optional(T)>::value_t> TryGetValue() const - { - if (HasCompatibleType(T)) - { - return details::ValueTypeSpecifics(T)>::Convert(m_data); - } - else - { - return std::nullopt; - } - } - - private: - Value(DWORD type, std::vector&& data); - - bool HasCompatibleType(Type type) const; - - Type m_type; - std::vector m_data; - }; - - // Value iteration - struct ValueList - { - friend Key; - - struct const_iterator; - - struct ValueRef - { - friend const_iterator; - - // Gets the name of the value. - std::string Name() const; - - // Gets the actual value of the value. - // The optional allows for the potential race with the value being removed. - std::optional Value() const; - - private: - ValueRef(wil::shared_hkey key, std::wstring&& valueName); - - wil::shared_hkey m_key; - std::wstring m_valueName; - }; - - struct const_iterator - { - friend ValueList; - - const_iterator& operator++(); - const_iterator operator++(int); - - bool operator==(const const_iterator& other) const; - bool operator!=(const const_iterator& other) const; - - const ValueRef& operator*() const; - const ValueRef* operator->() const; - - private: - // Create an iterator - const_iterator(const wil::shared_hkey& key, DWORD index = 0); - - // Create an iterator for end - const_iterator() = default; - - void GetValue(); - - // An empty handle represents the end iterator. - wil::shared_hkey m_key; - DWORD m_index = 0; - std::optional m_value; - }; - - const_iterator begin() const; - const_iterator end() const; - - private: - ValueList(wil::shared_hkey key); - - wil::shared_hkey m_key; - }; - - // A registry key. - struct Key - { - Key() = default; - Key(HKEY key); - Key(HKEY key, std::string_view subKey, DWORD options = 0, REGSAM access = KEY_READ); - Key(HKEY key, const std::wstring& subKey, DWORD options = 0, REGSAM access = KEY_READ); - - // --== Sub-Key iteration ==-- - struct const_iterator; - - struct SubKeyRef - { - friend const_iterator; - - // Gets the name of the subkey. - std::string Name() const; - - // Opens the subkey. - Key Open() const; - - operator bool() const { return m_parentKey.operator bool(); } - - private: - // For a valid iterator - SubKeyRef(const wil::shared_hkey& key, REGSAM access); - - // For the end iterator - SubKeyRef() = default; - - // Enumerates the subkey of m_parentKey at the given index. - void Enum(DWORD index); - - wil::shared_hkey m_parentKey; - REGSAM m_access = KEY_READ; - std::wstring m_subKeyName; - }; - - struct const_iterator - { - friend Key; - - const_iterator& operator++(); - const_iterator operator++(int); - - bool operator==(const const_iterator& other) const; - bool operator!=(const const_iterator& other) const; - - const SubKeyRef& operator*() const; - const SubKeyRef* operator->() const; - - private: - // Create an iterator for begin - const_iterator(const wil::shared_hkey& key, REGSAM access); - - // Create an iterator for end - const_iterator() = default; - - DWORD m_index = 0; - SubKeyRef m_subkey; - }; - - const_iterator begin() const; - const_iterator end() const; - - std::optional operator[](std::string_view name) const; - std::optional operator[](const std::wstring& name) const; - - void DeleteValue(std::string_view name) const; - void DeleteValue(const std::wstring& name) const; - - std::optional SubKey(std::string_view name, DWORD options = 0) const; - std::optional SubKey(const std::wstring& name, DWORD options = 0) const; - - // Set registry values. - void SetValue(const std::wstring& name, const std::wstring& value, DWORD type = REG_SZ) const; - void SetValue(const std::wstring& name, const std::vector& value, DWORD type = REG_BINARY) const; - void SetValue(const std::wstring& name, DWORD value) const; - - ValueList Values() const; - - operator bool() const { return m_key.operator bool(); } - operator HKEY() const { return m_key.get(); } - - // Open a Key; will return an empty Key if the subkey does not exist. - static Key OpenIfExists(HKEY key, std::string_view subKey = {}, DWORD options = 0, REGSAM access = KEY_READ); - static Key OpenIfExists(HKEY key, const std::wstring& subKey = {}, DWORD options = 0, REGSAM access = KEY_READ); - - // Creates a new Key or returns one if it already existed. - static Key Create(HKEY key, std::string_view subkey = {}, DWORD options = REG_OPTION_NON_VOLATILE, REGSAM access = KEY_ALL_ACCESS); - static Key Create(HKEY key, const std::wstring& subKey = {}, DWORD options = REG_OPTION_NON_VOLATILE, REGSAM access = KEY_ALL_ACCESS); - - // Delete a key - static bool Delete(HKEY key, std::string_view subkey, DWORD samDesired); - static bool Delete(HKEY key, const std::wstring& subKey, DWORD samDesired); - static bool DeleteTree(HKEY key, const std::wstring& subKey); - - private: - // When ignoring error, returns whether the key existed - bool Initialize(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access, bool ignoreErrorIfDoesNotExist); - - // Returns whether the key was created successfully. - bool CreateAndOpen(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access); - - wil::shared_hkey m_key; - REGSAM m_access = KEY_READ; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include +#include +#include + +#define AICLI_REGISTRY_UTF16_FLAG 0x08000000 + +namespace AppInstaller::Registry +{ + namespace details + { + template + constexpr bool dependent_false = false; + + template + struct ValueTypeSpecifics + { + using value_t = void; + + static value_t Convert(const std::vector& data) + { + static_assert(dependent_false, "No Type specific override has been supplied"); + } + }; + + template <> + struct ValueTypeSpecifics + { + using value_t = std::vector; + static value_t Convert(const std::vector& data); + }; + + template <> + struct ValueTypeSpecifics + { + using value_t = std::string; + static value_t Convert(const std::vector& data); + }; + + template <> + struct ValueTypeSpecifics + { + using value_t = std::wstring; + static value_t Convert(const std::vector& data); + }; + + template <> + struct ValueTypeSpecifics + { + using value_t = std::string; + static value_t Convert(const std::vector& data); + }; + + template <> + struct ValueTypeSpecifics + { + using value_t = std::wstring; + static value_t Convert(const std::vector& data); + }; + + template <> + struct ValueTypeSpecifics + { + using value_t = std::vector; + static value_t Convert(const std::vector& data); + }; + + template <> + struct ValueTypeSpecifics + { + using value_t = uint32_t; + static value_t Convert(const std::vector& data); + }; + } + + struct Key; + struct ValueList; + + // A registry value. + struct Value + { + friend Key; + friend ValueList; + + // The type of data stored in the Value. + enum class Type : DWORD + { + None = REG_NONE, + String = REG_SZ, + UTF16Flag = AICLI_REGISTRY_UTF16_FLAG, + UTF16String = REG_SZ | UTF16Flag, + ExpandString = REG_EXPAND_SZ, + UTF16ExpandString = REG_EXPAND_SZ | UTF16Flag, + Binary = REG_BINARY, + DWord = REG_DWORD, + DWordLittleEndian = REG_DWORD_LITTLE_ENDIAN, + DWordBigEndian = REG_DWORD_BIG_ENDIAN, + MultiString = REG_MULTI_SZ, + QWord = REG_QWORD, + QWordLittleEndian = REG_QWORD_LITTLE_ENDIAN, + }; + + Type GetType() const { return m_type; } + + template + typename details::ValueTypeSpecifics(T)>::value_t GetValue() const + { + auto value = TryGetValue(); + if (!value.has_value()) + { + THROW_HR(E_INVALIDARG); + } + + return std::move(value.value()); + } + + template + typename std::optional(T)>::value_t> TryGetValue() const + { + if (HasCompatibleType(T)) + { + return details::ValueTypeSpecifics(T)>::Convert(m_data); + } + else + { + return std::nullopt; + } + } + + private: + Value(DWORD type, std::vector&& data); + + bool HasCompatibleType(Type type) const; + + Type m_type; + std::vector m_data; + }; + + // Value iteration + struct ValueList + { + friend Key; + + struct const_iterator; + + struct ValueRef + { + friend const_iterator; + + // Gets the name of the value. + std::string Name() const; + + // Gets the actual value of the value. + // The optional allows for the potential race with the value being removed. + std::optional Value() const; + + private: + ValueRef(wil::shared_hkey key, std::wstring&& valueName); + + wil::shared_hkey m_key; + std::wstring m_valueName; + }; + + struct const_iterator + { + friend ValueList; + + const_iterator& operator++(); + const_iterator operator++(int); + + bool operator==(const const_iterator& other) const; + bool operator!=(const const_iterator& other) const; + + const ValueRef& operator*() const; + const ValueRef* operator->() const; + + private: + // Create an iterator + const_iterator(const wil::shared_hkey& key, DWORD index = 0); + + // Create an iterator for end + const_iterator() = default; + + void GetValue(); + + // An empty handle represents the end iterator. + wil::shared_hkey m_key; + DWORD m_index = 0; + std::optional m_value; + }; + + const_iterator begin() const; + const_iterator end() const; + + private: + ValueList(wil::shared_hkey key); + + wil::shared_hkey m_key; + }; + + // A registry key. + struct Key + { + Key() = default; + Key(HKEY key); + Key(HKEY key, std::string_view subKey, DWORD options = 0, REGSAM access = KEY_READ); + Key(HKEY key, const std::wstring& subKey, DWORD options = 0, REGSAM access = KEY_READ); + + // --== Sub-Key iteration ==-- + struct const_iterator; + + struct SubKeyRef + { + friend const_iterator; + + // Gets the name of the subkey. + std::string Name() const; + + // Opens the subkey. + Key Open() const; + + operator bool() const { return m_parentKey.operator bool(); } + + private: + // For a valid iterator + SubKeyRef(const wil::shared_hkey& key, REGSAM access); + + // For the end iterator + SubKeyRef() = default; + + // Enumerates the subkey of m_parentKey at the given index. + void Enum(DWORD index); + + wil::shared_hkey m_parentKey; + REGSAM m_access = KEY_READ; + std::wstring m_subKeyName; + }; + + struct const_iterator + { + friend Key; + + const_iterator& operator++(); + const_iterator operator++(int); + + bool operator==(const const_iterator& other) const; + bool operator!=(const const_iterator& other) const; + + const SubKeyRef& operator*() const; + const SubKeyRef* operator->() const; + + private: + // Create an iterator for begin + const_iterator(const wil::shared_hkey& key, REGSAM access); + + // Create an iterator for end + const_iterator() = default; + + DWORD m_index = 0; + SubKeyRef m_subkey; + }; + + const_iterator begin() const; + const_iterator end() const; + + std::optional operator[](std::string_view name) const; + std::optional operator[](const std::wstring& name) const; + + void DeleteValue(std::string_view name) const; + void DeleteValue(const std::wstring& name) const; + + std::optional SubKey(std::string_view name, DWORD options = 0) const; + std::optional SubKey(const std::wstring& name, DWORD options = 0) const; + + // Set registry values. + void SetValue(const std::wstring& name, const std::wstring& value, DWORD type = REG_SZ) const; + void SetValue(const std::wstring& name, const std::vector& value, DWORD type = REG_BINARY) const; + void SetValue(const std::wstring& name, DWORD value) const; + + ValueList Values() const; + + operator bool() const { return m_key.operator bool(); } + operator HKEY() const { return m_key.get(); } + + // Open a Key; will return an empty Key if the subkey does not exist. + static Key OpenIfExists(HKEY key, std::string_view subKey = {}, DWORD options = 0, REGSAM access = KEY_READ); + static Key OpenIfExists(HKEY key, const std::wstring& subKey = {}, DWORD options = 0, REGSAM access = KEY_READ); + + // Creates a new Key or returns one if it already existed. + static Key Create(HKEY key, std::string_view subkey = {}, DWORD options = REG_OPTION_NON_VOLATILE, REGSAM access = KEY_ALL_ACCESS); + static Key Create(HKEY key, const std::wstring& subKey = {}, DWORD options = REG_OPTION_NON_VOLATILE, REGSAM access = KEY_ALL_ACCESS); + + // Delete a key + static bool Delete(HKEY key, std::string_view subkey, DWORD samDesired); + static bool Delete(HKEY key, const std::wstring& subKey, DWORD samDesired); + static bool DeleteTree(HKEY key, const std::wstring& subKey); + + private: + // When ignoring error, returns whether the key existed + bool Initialize(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access, bool ignoreErrorIfDoesNotExist); + + // Returns whether the key was created successfully. + bool CreateAndOpen(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access); + + wil::shared_hkey m_key; + REGSAM m_access = KEY_READ; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/Resources.h b/src/AppInstallerSharedLib/Public/winget/Resources.h index 3f39e4bc80..8c5da1175f 100644 --- a/src/AppInstallerSharedLib/Public/winget/Resources.h +++ b/src/AppInstallerSharedLib/Public/winget/Resources.h @@ -1,149 +1,149 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include -#include -#include "AppInstallerStrings.h" - -using namespace std::string_view_literals; - -namespace AppInstaller -{ - namespace StringResource - { -#define WINGET_WIDE_STRINGIFY_HELP(_id_) L ## _id_ -#define WINGET_WIDE_STRINGIFY(_id_) WINGET_WIDE_STRINGIFY_HELP(_id_) -#define WINGET_DEFINE_RESOURCE_STRINGID(_id_) static constexpr AppInstaller::StringResource::StringId _id_ { WINGET_WIDE_STRINGIFY(#_id_) ## sv } - - // A resource identifier - struct StringId : public std::wstring_view - { - explicit constexpr StringId(std::wstring_view id) : std::wstring_view(id) {} - - // Sets the placeholder values in the resolved string id. - // Example: out << myStringId(placeholderVal1, placeholderVal2, ...) - template - Utility::LocIndString operator()(T ... args) const; - - // Creates a StringId that represents an empty resource string - static StringId Empty(); - - private: - // Resolve the string ID to its corresponding localized string - // without replacing placeholders. - std::string Resolve() const; - }; - - inline StringId StringId::Empty() { return StringId{ {} }; } - - // Output resource identifier as localized string. - std::ostream& operator<<(std::ostream& out, StringId si); - - // Resource string identifiers. - struct String - { - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWinGet); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWingetSettings); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableExperimentalFeatures); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableLocalManifests); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableHashOverride); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableLocalArchiveMalwareScanOverride); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableDefaultSource); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableMSStoreSource); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableFontSource); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyAdditionalSources); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyAllowedSources); - WINGET_DEFINE_RESOURCE_STRINGID(PolicySourceAutoUpdateInterval); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableBypassCertificatePinningForMicrosoftStore); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWindowsPackageManagerCommandLineInterfaces); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWinGetConfiguration); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableProxyCommandLineOptions); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableMcpServer); - WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableConfigurationProcessorPath); - - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldFormat); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldValue); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidValueFromPolicy); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningLoadedBackupSettings); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningParseError); - WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningUsingDefault); - - WINGET_DEFINE_RESOURCE_STRINGID(UnknownErrorCode); - }; - } - - namespace Resource - { - // Get an embedded resource from the binary and return as std::string_view. - // Resource data is valid as long as the binary is loaded. - std::string_view GetResourceAsString(int resourceName, int resourceType); - std::string_view GetResourceAsString(PCWSTR resourceName, PCWSTR resourceType); - - // Get an embedded resource from the binary and return as std::pair. - // Resource data is valid as long as the binary is loaded. - std::pair GetResourceAsBytes(int resourceName, int resourceType); - std::pair GetResourceAsBytes(PCWSTR resourceName, PCWSTR resourceType); - - // A localized string - struct LocString : public Utility::LocIndString - { - LocString() = default; - - LocString(StringResource::StringId id) : Utility::LocIndString(id()) {} - LocString(Utility::LocIndString locIndString) : Utility::LocIndString(std::move(locIndString)) {} - - LocString(const LocString&) = default; - LocString& operator=(const LocString&) = default; - - LocString(LocString&&) = default; - LocString& operator=(LocString&&) = default; - }; - } - - namespace StringResource - { - // Tries to resolve a string, returning a nullopt if it cannot. - std::optional TryResolveString(std::wstring_view resKey); - } - - namespace details - { - // List of approved types for output, others are potentially not localized. - template - struct IsApprovedForOutput - { - static constexpr bool value = std::is_arithmetic::value; - }; - -#define WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(_t_) \ - template <> \ - struct IsApprovedForOutput<_t_> \ - { \ - static constexpr bool value = true; \ - } - - // It is assumed that single char values need not be localized, as they are matched - // ordinally or they are punctuation / other. - WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(char); - // Localized strings (and from an Id for one for convenience). - WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(StringResource::StringId); - WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Resource::LocString); - // Strings explicitly declared as localization independent. - WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::LocIndView); - WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::LocIndString); - // Normalized strings come from user data and should therefore already by localized - // by how they are chosen (or there is no localized version). - WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::NormalizedString); - } - - template - Utility::LocIndString StringResource::StringId::operator()(T ... args) const - { - static_assert((details::IsApprovedForOutput>::value && ...), "This type may not be localized, see comment for more information"); - return Utility::LocIndString{ Utility::Format(Resolve(), std::forward(args)...) }; - } -} - +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include +#include +#include "AppInstallerStrings.h" + +using namespace std::string_view_literals; + +namespace AppInstaller +{ + namespace StringResource + { +#define WINGET_WIDE_STRINGIFY_HELP(_id_) L ## _id_ +#define WINGET_WIDE_STRINGIFY(_id_) WINGET_WIDE_STRINGIFY_HELP(_id_) +#define WINGET_DEFINE_RESOURCE_STRINGID(_id_) static constexpr AppInstaller::StringResource::StringId _id_ { WINGET_WIDE_STRINGIFY(#_id_) ## sv } + + // A resource identifier + struct StringId : public std::wstring_view + { + explicit constexpr StringId(std::wstring_view id) : std::wstring_view(id) {} + + // Sets the placeholder values in the resolved string id. + // Example: out << myStringId(placeholderVal1, placeholderVal2, ...) + template + Utility::LocIndString operator()(T ... args) const; + + // Creates a StringId that represents an empty resource string + static StringId Empty(); + + private: + // Resolve the string ID to its corresponding localized string + // without replacing placeholders. + std::string Resolve() const; + }; + + inline StringId StringId::Empty() { return StringId{ {} }; } + + // Output resource identifier as localized string. + std::ostream& operator<<(std::ostream& out, StringId si); + + // Resource string identifiers. + struct String + { + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWinGet); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWingetSettings); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableExperimentalFeatures); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableLocalManifests); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableHashOverride); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableLocalArchiveMalwareScanOverride); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableDefaultSource); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableMSStoreSource); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableFontSource); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyAdditionalSources); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyAllowedSources); + WINGET_DEFINE_RESOURCE_STRINGID(PolicySourceAutoUpdateInterval); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableBypassCertificatePinningForMicrosoftStore); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWindowsPackageManagerCommandLineInterfaces); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableWinGetConfiguration); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableProxyCommandLineOptions); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableMcpServer); + WINGET_DEFINE_RESOURCE_STRINGID(PolicyEnableConfigurationProcessorPath); + + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldFormat); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidFieldValue); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningInvalidValueFromPolicy); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningLoadedBackupSettings); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningParseError); + WINGET_DEFINE_RESOURCE_STRINGID(SettingsWarningUsingDefault); + + WINGET_DEFINE_RESOURCE_STRINGID(UnknownErrorCode); + }; + } + + namespace Resource + { + // Get an embedded resource from the binary and return as std::string_view. + // Resource data is valid as long as the binary is loaded. + std::string_view GetResourceAsString(int resourceName, int resourceType); + std::string_view GetResourceAsString(PCWSTR resourceName, PCWSTR resourceType); + + // Get an embedded resource from the binary and return as std::pair. + // Resource data is valid as long as the binary is loaded. + std::pair GetResourceAsBytes(int resourceName, int resourceType); + std::pair GetResourceAsBytes(PCWSTR resourceName, PCWSTR resourceType); + + // A localized string + struct LocString : public Utility::LocIndString + { + LocString() = default; + + LocString(StringResource::StringId id) : Utility::LocIndString(id()) {} + LocString(Utility::LocIndString locIndString) : Utility::LocIndString(std::move(locIndString)) {} + + LocString(const LocString&) = default; + LocString& operator=(const LocString&) = default; + + LocString(LocString&&) = default; + LocString& operator=(LocString&&) = default; + }; + } + + namespace StringResource + { + // Tries to resolve a string, returning a nullopt if it cannot. + std::optional TryResolveString(std::wstring_view resKey); + } + + namespace details + { + // List of approved types for output, others are potentially not localized. + template + struct IsApprovedForOutput + { + static constexpr bool value = std::is_arithmetic::value; + }; + +#define WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(_t_) \ + template <> \ + struct IsApprovedForOutput<_t_> \ + { \ + static constexpr bool value = true; \ + } + + // It is assumed that single char values need not be localized, as they are matched + // ordinally or they are punctuation / other. + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(char); + // Localized strings (and from an Id for one for convenience). + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(StringResource::StringId); + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Resource::LocString); + // Strings explicitly declared as localization independent. + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::LocIndView); + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::LocIndString); + // Normalized strings come from user data and should therefore already by localized + // by how they are chosen (or there is no localized version). + WINGET_CREATE_ISAPPROVEDFOROUTPUT_SPECIALIZATION(Utility::NormalizedString); + } + + template + Utility::LocIndString StringResource::StringId::operator()(T ... args) const + { + static_assert((details::IsApprovedForOutput>::value && ...), "This type may not be localized, see comment for more information"); + return Utility::LocIndString{ Utility::Format(Resolve(), std::forward(args)...) }; + } +} + diff --git a/src/AppInstallerSharedLib/Public/winget/Runtime.h b/src/AppInstallerSharedLib/Public/winget/Runtime.h index 9f29760ce2..930793a772 100644 --- a/src/AppInstallerSharedLib/Public/winget/Runtime.h +++ b/src/AppInstallerSharedLib/Public/winget/Runtime.h @@ -1,67 +1,67 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include -#include -#include -#include - -namespace AppInstaller::Runtime -{ - // Determines whether the process is running in a packaged context or not. - bool IsRunningInPackagedContext(); - - // Determines the current version of the client and returns it. - Utility::LocIndString GetClientVersion(); - - // Gets the package family name of the current package (or empty string if not packaged). - std::wstring GetPackageFamilyName(); - - // Determines the current version of the package if running in a packaged context. - Utility::LocIndString GetPackageVersion(); - - // Gets a string representation of the OS version for debugging purposes. - Utility::LocIndString GetOSVersion(); - - // Gets the OS region. - // This can be used as the current market. - std::string GetOSRegion(); - - // Determines whether the current OS version is >= the given one. - // We treat the given Version struct as a standard 4 part Windows OS version. - bool IsCurrentOSVersionGreaterThanOrEqual(const Utility::Version& version); - - // Determines whether the process is running with administrator privileges. - bool IsRunningAsAdmin(); - - // Determines whether the process is running with local system context. - bool IsRunningAsSystem(); - - // Determines whether the process is running with administrator or system privileges. - bool IsRunningAsAdminOrSystem(); - - // Determines whether the current token can be elevated. - // This only returns true for tokens that are TokenElevationTypeLimited. - // Thus, it will only be true if: - // 1. UAC is enabled - // 2. the user is in the Administrators group - // 3. the token is not already elevated - bool IsRunningWithLimitedToken(); - - // Determines if the given amount of stack bytes are available. - // If the answer cannot be determined properly, the return value will be `false`. - DECLSPEC_NOINLINE bool IsStackAvailable(size_t bytes); - - // Returns true if this is a release build; false if not. - inline constexpr bool IsReleaseBuild() - { -#ifdef WINGET_ENABLE_RELEASE_BUILD - return true; -#else - return false; -#endif - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include +#include +#include +#include + +namespace AppInstaller::Runtime +{ + // Determines whether the process is running in a packaged context or not. + bool IsRunningInPackagedContext(); + + // Determines the current version of the client and returns it. + Utility::LocIndString GetClientVersion(); + + // Gets the package family name of the current package (or empty string if not packaged). + std::wstring GetPackageFamilyName(); + + // Determines the current version of the package if running in a packaged context. + Utility::LocIndString GetPackageVersion(); + + // Gets a string representation of the OS version for debugging purposes. + Utility::LocIndString GetOSVersion(); + + // Gets the OS region. + // This can be used as the current market. + std::string GetOSRegion(); + + // Determines whether the current OS version is >= the given one. + // We treat the given Version struct as a standard 4 part Windows OS version. + bool IsCurrentOSVersionGreaterThanOrEqual(const Utility::Version& version); + + // Determines whether the process is running with administrator privileges. + bool IsRunningAsAdmin(); + + // Determines whether the process is running with local system context. + bool IsRunningAsSystem(); + + // Determines whether the process is running with administrator or system privileges. + bool IsRunningAsAdminOrSystem(); + + // Determines whether the current token can be elevated. + // This only returns true for tokens that are TokenElevationTypeLimited. + // Thus, it will only be true if: + // 1. UAC is enabled + // 2. the user is in the Administrators group + // 3. the token is not already elevated + bool IsRunningWithLimitedToken(); + + // Determines if the given amount of stack bytes are available. + // If the answer cannot be determined properly, the return value will be `false`. + DECLSPEC_NOINLINE bool IsStackAvailable(size_t bytes); + + // Returns true if this is a release build; false if not. + inline constexpr bool IsReleaseBuild() + { +#ifdef WINGET_ENABLE_RELEASE_BUILD + return true; +#else + return false; +#endif + } +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteDynamicStorage.h b/src/AppInstallerSharedLib/Public/winget/SQLiteDynamicStorage.h index 14f11b74ac..3a03f351ca 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteDynamicStorage.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteDynamicStorage.h @@ -1,57 +1,57 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace AppInstaller::SQLite -{ - // Type the allows for the schema version of the underlying storage to be changed dynamically. - struct SQLiteDynamicStorage : public SQLiteStorageBase - { - // Creates a new database with the given schema version. - SQLiteDynamicStorage(const std::string& target, const Version& version); - SQLiteDynamicStorage(const std::filesystem::path& target, const Version& version); - - // Opens an existing database with the given disposition. - SQLiteDynamicStorage(const std::string& filePath, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& file = {}); - SQLiteDynamicStorage(const std::filesystem::path& filePath, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& file = {}); - - // Implicit conversion to a connection object for convenience. - operator Connection& (); - operator const Connection& () const; - Connection& GetConnection(); - const Connection& GetConnection() const; - - using SQLiteStorageBase::SetLastWriteTime; - - // Must be kept alive to ensure consistent schema view and exclusive use of the owned connection. - struct TransactionLock - { - _Acquires_lock_(mutex) - TransactionLock(std::mutex& mutex); - - _Acquires_lock_(mutex) - TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name, bool immediateWrite); - - // Abandons the transaction and any changes; releases the connection lock. - void Rollback(bool throwOnError = true); - - // Commits the transaction and releases the connection lock. - void Commit(); - - private: - std::lock_guard m_lock; - Transaction m_transaction; - }; - - // Acquires the connection lock and begins a transaction on the database. - // If the returned result is empty, the schema version has changed and the caller must handle this. - std::unique_ptr TryBeginTransaction(std::string_view name, bool immediateWrite); - - // Locks the connection for use during the schema upgrade. - std::unique_ptr LockConnection(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace AppInstaller::SQLite +{ + // Type the allows for the schema version of the underlying storage to be changed dynamically. + struct SQLiteDynamicStorage : public SQLiteStorageBase + { + // Creates a new database with the given schema version. + SQLiteDynamicStorage(const std::string& target, const Version& version); + SQLiteDynamicStorage(const std::filesystem::path& target, const Version& version); + + // Opens an existing database with the given disposition. + SQLiteDynamicStorage(const std::string& filePath, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& file = {}); + SQLiteDynamicStorage(const std::filesystem::path& filePath, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& file = {}); + + // Implicit conversion to a connection object for convenience. + operator Connection& (); + operator const Connection& () const; + Connection& GetConnection(); + const Connection& GetConnection() const; + + using SQLiteStorageBase::SetLastWriteTime; + + // Must be kept alive to ensure consistent schema view and exclusive use of the owned connection. + struct TransactionLock + { + _Acquires_lock_(mutex) + TransactionLock(std::mutex& mutex); + + _Acquires_lock_(mutex) + TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name, bool immediateWrite); + + // Abandons the transaction and any changes; releases the connection lock. + void Rollback(bool throwOnError = true); + + // Commits the transaction and releases the connection lock. + void Commit(); + + private: + std::lock_guard m_lock; + Transaction m_transaction; + }; + + // Acquires the connection lock and begins a transaction on the database. + // If the returned result is empty, the schema version has changed and the caller must handle this. + std::unique_ptr TryBeginTransaction(std::string_view name, bool immediateWrite); + + // Locks the connection for use during the schema upgrade. + std::unique_ptr LockConnection(); + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteMetadataTable.h b/src/AppInstallerSharedLib/Public/winget/SQLiteMetadataTable.h index 036a2bafc4..5e723e44cc 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteMetadataTable.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteMetadataTable.h @@ -1,67 +1,67 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include - -namespace AppInstaller::SQLite -{ - using namespace std::string_view_literals; - - static constexpr std::string_view s_MetadataValueName_DatabaseIdentifier = "databaseIdentifier"sv; - static constexpr std::string_view s_MetadataValueName_MajorVersion = "majorVersion"sv; - static constexpr std::string_view s_MetadataValueName_MinorVersion = "minorVersion"sv; - static constexpr std::string_view s_MetadataValueName_LastWriteTime = "lastwritetime"sv; - - // The metadata table for the database. - // Contains a fixed-schema set of named values that can be used to determine how to read the rest of the database. - struct MetadataTable - { - static void Create(Connection& connection); - - // Gets the named value from the metadata table, interpreting it as the given type. - template - static Value GetNamedValue(const Connection& connection, std::string_view name) - { - Statement statement = GetNamedValueStatement(connection, name); - return statement.GetColumn(0); - } - - // Gets the named value from the metadata table, interpreting it as the given type. - // Returns nullopt if the value is not present. - template - static std::optional TryGetNamedValue(const Connection& connection, std::string_view name) - { - std::optional statement = TryGetNamedValueStatement(connection, name); - if (statement) - { - return statement->GetColumn(0); - } - else - { - return std::nullopt; - } - } - - // Sets the named value into the metadata table. - template - static void SetNamedValue(const Connection& connection, std::string_view name, Value&& v) - { - Statement statement = SetNamedValueStatement(connection, name); - statement.Bind(2, std::forward(v)); - statement.Execute(); - } - - private: - // Internal function that gets the named value. - static Statement GetNamedValueStatement(const Connection& connection, std::string_view name); - - // Internal function that gets the named value, or nullopt if it is not present. - static std::optional TryGetNamedValueStatement(const Connection& connection, std::string_view name); - - // Internal function that sets the named value. - static Statement SetNamedValueStatement(const Connection& connection, std::string_view name); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include + +namespace AppInstaller::SQLite +{ + using namespace std::string_view_literals; + + static constexpr std::string_view s_MetadataValueName_DatabaseIdentifier = "databaseIdentifier"sv; + static constexpr std::string_view s_MetadataValueName_MajorVersion = "majorVersion"sv; + static constexpr std::string_view s_MetadataValueName_MinorVersion = "minorVersion"sv; + static constexpr std::string_view s_MetadataValueName_LastWriteTime = "lastwritetime"sv; + + // The metadata table for the database. + // Contains a fixed-schema set of named values that can be used to determine how to read the rest of the database. + struct MetadataTable + { + static void Create(Connection& connection); + + // Gets the named value from the metadata table, interpreting it as the given type. + template + static Value GetNamedValue(const Connection& connection, std::string_view name) + { + Statement statement = GetNamedValueStatement(connection, name); + return statement.GetColumn(0); + } + + // Gets the named value from the metadata table, interpreting it as the given type. + // Returns nullopt if the value is not present. + template + static std::optional TryGetNamedValue(const Connection& connection, std::string_view name) + { + std::optional statement = TryGetNamedValueStatement(connection, name); + if (statement) + { + return statement->GetColumn(0); + } + else + { + return std::nullopt; + } + } + + // Sets the named value into the metadata table. + template + static void SetNamedValue(const Connection& connection, std::string_view name, Value&& v) + { + Statement statement = SetNamedValueStatement(connection, name); + statement.Bind(2, std::forward(v)); + statement.Execute(); + } + + private: + // Internal function that gets the named value. + static Statement GetNamedValueStatement(const Connection& connection, std::string_view name); + + // Internal function that gets the named value, or nullopt if it is not present. + static std::optional TryGetNamedValueStatement(const Connection& connection, std::string_view name); + + // Internal function that sets the named value. + static Statement SetNamedValueStatement(const Connection& connection, std::string_view name); + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h index 7de19998d7..0ef543a87a 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteStatementBuilder.h @@ -1,570 +1,570 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace std::string_view_literals; - -namespace AppInstaller::SQLite::Builder -{ - namespace details - { - // Sentinel types to indicate special cases to the builder. - struct unbound_t {}; - struct rowcount_t {}; - - // Class for intake from external functions. - struct SubBuilder - { - SubBuilder(std::string&& s) : m_string(std::move(s)) {} - - SubBuilder(const SubBuilder&) = default; - SubBuilder& operator=(const SubBuilder&) = default; - - SubBuilder(SubBuilder&&) noexcept = default; - SubBuilder& operator=(SubBuilder&&) noexcept = default; - - const std::string& GetString() const { return m_string; } - - protected: - std::string m_string; - }; - - // Base class for all sub-builders. - struct SubBuilderBase - { - SubBuilderBase() = default; - - SubBuilderBase(const SubBuilderBase&) = default; - SubBuilderBase& operator=(const SubBuilderBase&) = default; - - SubBuilderBase(SubBuilderBase&&) noexcept = default; - SubBuilderBase& operator=(SubBuilderBase&&) noexcept = default; - - virtual operator SubBuilder() { return { m_stream.str() }; } - - protected: - std::ostringstream m_stream; - }; - } - - // Pass this value to indicate that the caller will bind the value later. - __declspec_selectany_ details::unbound_t Unbound; - - // Pass this value to indicate that the number of rows is to be selected. - __declspec_selectany_ details::rowcount_t RowCount; - - // A qualified table reference. - struct QualifiedTable - { - std::string_view Schema; - std::string_view Table; - - explicit constexpr QualifiedTable(std::string_view table) : Table(table) {} - explicit constexpr QualifiedTable(std::string_view schema, std::string_view table) : Schema(schema), Table(table) {} - }; - - namespace Schema - { - // The main database's schema table. - // More info can be found at: https://www.sqlite.org/schematab.html - constexpr QualifiedTable MainTable{ "main"sv, "sqlite_master"sv }; - - // The sqlite_schema column name for the type of the object. - constexpr std::string_view TypeColumn = "type"sv; - - // The sqlite_schema type value for a table. - constexpr std::string_view Type_Table = "table"sv; - - // The sqlite_schema type value for an index. - constexpr std::string_view Type_Index = "index"sv; - - // The sqlite_schema column name for the name of the object. - constexpr std::string_view NameColumn = "name"sv; - } - - // A qualified column reference. - struct QualifiedColumn - { - std::string_view Table; - std::string_view Column; - - explicit QualifiedColumn(std::string_view column) : Column(column) {} - explicit QualifiedColumn(std::string_view table, std::string_view column) : Table(table), Column(column) {} - }; - - // SQLite types as an enum. - enum class Type - { - Int, - Bool = Int, - Int64, - RowId = Int64, - Text, - Blob, - Integer, // Type for specifying a primary key column as a row id alias. - None, // Does not declare a type - }; - - template - struct TypeInfo - { - }; - - template <> - struct TypeInfo - { - using value_t = std::string; - }; - - template <> - struct TypeInfo - { - using value_t = std::optional; - }; - - template <> - struct TypeInfo - { - using value_t = SQLite::blob_t; - }; - - template <> - struct TypeInfo - { - using value_t = std::optional; - }; - - // Aggregate functions. - enum class Aggregate - { - Min, - Max, - }; - - // Helper to mark create an integer primary key for rowid, making it stable across vacuum. - struct IntegerPrimaryKey : public details::SubBuilderBase - { - IntegerPrimaryKey(); - - IntegerPrimaryKey(const IntegerPrimaryKey&) = default; - IntegerPrimaryKey& operator=(const IntegerPrimaryKey&) = default; - - IntegerPrimaryKey(IntegerPrimaryKey&&) noexcept = default; - IntegerPrimaryKey& operator=(IntegerPrimaryKey&&) noexcept = default; - - // Set the column to autoincrement. SQLite recommends against using this value unless - // you need to ensure that rowids are not ever reused. - IntegerPrimaryKey& AutoIncrement(bool isTrue = true); - }; - - // Helper used when creating a table. - struct ColumnBuilder : public details::SubBuilderBase - { - // Specify the column name and type when creating the builder. - ColumnBuilder(std::string_view column, Type type); - - ColumnBuilder(const ColumnBuilder&) = default; - ColumnBuilder& operator=(const ColumnBuilder&) = default; - - ColumnBuilder(ColumnBuilder&&) noexcept = default; - ColumnBuilder& operator=(ColumnBuilder&&) noexcept = default; - - // Indicate that the column is not able to be null. - // Allow for data driven construction with input value. - ColumnBuilder& NotNull(bool isTrue = true); - - // Indicate that the column is case-insensitive. - // Allow for data driven construction with input value. - ColumnBuilder& CollateNoCase(bool isTrue = true); - - // Indicate the default value for the column. - // Note that a default value is not considered constant if it is bound, - // so this function directly places the incoming value into the SQL statement. - ColumnBuilder& Default(int64_t value); - - // Indicate that the column is unique. - // Allow for data driven construction with input value. - ColumnBuilder& Unique(bool isTrue = true); - - // Indicate that the column is the primary key. - // Allow for data driven construction with input value. - ColumnBuilder& PrimaryKey(bool isTrue = true); - }; - - // Helper used to specify a primary key with multiple columns. - struct PrimaryKeyBuilder : public details::SubBuilderBase - { - PrimaryKeyBuilder(); - PrimaryKeyBuilder(std::initializer_list columns); - - PrimaryKeyBuilder(const PrimaryKeyBuilder&) = default; - PrimaryKeyBuilder& operator=(const PrimaryKeyBuilder&) = default; - - PrimaryKeyBuilder(PrimaryKeyBuilder&&) noexcept = default; - PrimaryKeyBuilder& operator=(PrimaryKeyBuilder&&) noexcept = default; - - virtual operator details::SubBuilder() override; - - // Add a column to the primary key. - PrimaryKeyBuilder& Column(std::string_view column); - - private: - bool m_isFirst = true; - bool m_needsClosing = true; - }; - - // A class that aids in building SQL statements in a more expressive manner than simple strings. - struct StatementBuilder - { - StatementBuilder() = default; - - StatementBuilder(const StatementBuilder&) = default; - StatementBuilder& operator=(const StatementBuilder&) = default; - - StatementBuilder(StatementBuilder&&) = default; - StatementBuilder& operator=(StatementBuilder&&) = default; - - // Begin a select statement for the given columns. - StatementBuilder& Select(); - StatementBuilder& Select(std::string_view column); - StatementBuilder& Select(std::initializer_list columns); - StatementBuilder& Select(const QualifiedColumn& column); - StatementBuilder& Select(std::initializer_list columns); - StatementBuilder& Select(details::rowcount_t); - - // Indicate the table that the statement will be operating on. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& From(); - StatementBuilder& From(std::string_view table); - StatementBuilder& From(QualifiedTable table); - StatementBuilder& From(std::initializer_list table); - - // Begin a filter clause on the given column. - StatementBuilder& Where(std::string_view column); - StatementBuilder& Where(const QualifiedColumn& column); - - // A full filter clause looking for an embedded null character. - // Is extremely specific to consistency checks, and so a more detailed construct is not required. - StatementBuilder& WhereValueContainsEmbeddedNullCharacter(std::string_view column); - StatementBuilder& WhereValueContainsEmbeddedNullCharacter(const QualifiedColumn& column); - - // Indicate the operation of the filter clause. - template - StatementBuilder& Equals(const ValueType& value) - { - AddBindFunctor(AppendOpAndBinder(Op::Equals), value); - return *this; - } - template - StatementBuilder& Equals(const std::optional& value) - { - if (value) - { - AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value()); - return *this; - } - else - { - return IsNull(); - } - } - // The optional index value can be used to specify the parameter index. - StatementBuilder& Equals(details::unbound_t, std::optional index = {}); - StatementBuilder& Equals(std::nullptr_t); - StatementBuilder& Equals(); - StatementBuilder& Equals(const QualifiedColumn& column); - - template - StatementBuilder& IsGreaterThan(const ValueType& value) - { - AddBindFunctor(AppendOpAndBinder(Op::GreaterThan), value); - return *this; - } - StatementBuilder& IsGreaterThan(details::unbound_t, std::optional index = {}); - - template - StatementBuilder& IsGreaterThanOrEqualTo(const ValueType& value) - { - AddBindFunctor(AppendOpAndBinder(Op::GreaterThanOrEqualTo), value); - return *this; - } - StatementBuilder& IsGreaterThanOrEqualTo(details::unbound_t, std::optional index = {}); - - StatementBuilder& LikeWithEscape(std::string_view value); - StatementBuilder& Like(details::unbound_t); - - StatementBuilder& Escape(std::string_view escapeChar); - - StatementBuilder& Not(); - StatementBuilder& In(); - - // Appends a set of value binders for the In clause. - StatementBuilder& In(size_t count); - - // IsNull(true) means the value is null; IsNull(false) means the value is not null. - StatementBuilder& IsNull(bool isNull = true); - StatementBuilder& IsNotNull() { return IsNull(false); } - - // Operators for combining filter clauses. - StatementBuilder& And(std::string_view column); - StatementBuilder& And(const QualifiedColumn& column); - StatementBuilder& Or(const QualifiedColumn& column); - - // Begin a join clause. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& Join(std::string_view table); - StatementBuilder& Join(QualifiedTable table); - StatementBuilder& Join(std::initializer_list table); - - // Begin a left outer join clause. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& LeftOuterJoin(std::string_view table); - StatementBuilder& LeftOuterJoin(QualifiedTable table); - StatementBuilder& LeftOuterJoin(std::initializer_list table); - - // Set the join constraint. - StatementBuilder& On(const QualifiedColumn& column1, const QualifiedColumn& column2); - - // Specify the grouping to use. - StatementBuilder& GroupBy(std::string_view column); - StatementBuilder& GroupBy(const QualifiedColumn& column); - - // Specify the ordering to use. - StatementBuilder& OrderBy(std::string_view column); - StatementBuilder& OrderBy(const QualifiedColumn& column); - StatementBuilder& OrderBy(std::initializer_list columns); - - // Specify the ordering behavior. - StatementBuilder& Ascending(); - StatementBuilder& Descending(); - - // Limits the result set to the given number of rows. - StatementBuilder& Limit(size_t rowCount); - - // Begin an insert statement for the given table. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& InsertInto(std::string_view table); - StatementBuilder& InsertInto(QualifiedTable table); - StatementBuilder& InsertInto(std::initializer_list table); - - // Begin an insert or ignore statement for the given table. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& InsertOrIgnore(std::string_view table); - StatementBuilder& InsertOrIgnore(QualifiedTable table); - StatementBuilder& InsertOrIgnore(std::initializer_list table); - - // Set the columns for a statement (typically insert). - StatementBuilder& Columns(std::string_view column); - StatementBuilder& Columns(std::initializer_list columns); - StatementBuilder& Columns(const QualifiedColumn& column); - StatementBuilder& Columns(std::initializer_list columns); - - // Set the columns for a select or create table statement. - StatementBuilder& Columns(std::initializer_list columns); - StatementBuilder& BeginColumns(); - StatementBuilder& Column(std::string_view column); - StatementBuilder& Column(const QualifiedColumn& column); - StatementBuilder& Column(Aggregate aggOp, std::string_view column); - StatementBuilder& Column(Aggregate aggOp, const QualifiedColumn& column); - StatementBuilder& Column(const details::SubBuilder& column); - StatementBuilder& EndColumns(); - - // Set the columns null constraint. - StatementBuilder& NotNull(bool isTrue = true); - - // Set the column's default value. - template - StatementBuilder& Default(const ValueType& value) - { - m_stream << " DEFAULT (" << value << ")"; - return *this; - } - - // Add the values clause for an insert statement. - template - StatementBuilder& Values(const ValueTypes&... values) - { - int bindIndexBegin = AppendValuesAndBinders(sizeof...(ValueTypes)); - // Use folding to add a binder for every value, specifically in the order they were given. - // Do not change this expression without understanding the implications to the bind order. - // See: https://en.cppreference.com/w/cpp/language/fold for more details. - (FoldHelper{}, ..., InsertValuesValueBinder(bindIndexBegin++, values)); - return *this; - } - StatementBuilder& BeginValues(); - template - StatementBuilder& Value(const ValueType& value) - { - InsertValuesValueBinder(AppendValueAndBinder(), value); - return *this; - } - StatementBuilder& EndValues(); - - // Begin a table creation statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& CreateTable(std::string_view table); - StatementBuilder& CreateTable(QualifiedTable table); - StatementBuilder& CreateTable(std::initializer_list table); - - // Begin an alter table statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& AlterTable(std::string_view table); - StatementBuilder& AlterTable(QualifiedTable table); - StatementBuilder& AlterTable(std::initializer_list table); - - // Complete an alter table statement by adding a column. - StatementBuilder& Add(std::string_view column, Type type); - - // Begin a table deletion statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DropTable(std::string_view table); - StatementBuilder& DropTable(QualifiedTable table); - StatementBuilder& DropTable(std::initializer_list table); - - // Begin a table deletion statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DropTableIfExists(std::string_view table); - StatementBuilder& DropTableIfExists(QualifiedTable table); - StatementBuilder& DropTableIfExists(std::initializer_list table); - - // Begin an index creation statement. - // The initializer_list form enables the index name to be constructed from multiple parts. - StatementBuilder& CreateIndex(std::string_view table); - StatementBuilder& CreateIndex(QualifiedTable table); - StatementBuilder& CreateIndex(std::initializer_list table); - - // Begin an unique index creation statement. - // The initializer_list form enables the index name to be constructed from multiple parts. - StatementBuilder& CreateUniqueIndex(std::string_view table); - StatementBuilder& CreateUniqueIndex(QualifiedTable table); - StatementBuilder& CreateUniqueIndex(std::initializer_list table); - - // Begin an index deletion statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DropIndex(std::string_view index); - StatementBuilder& DropIndex(QualifiedTable index); - StatementBuilder& DropIndex(std::initializer_list index); - - // Set index target table. - StatementBuilder& On(std::string_view table); - StatementBuilder& On(std::initializer_list table); - - // Begin a delete statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& DeleteFrom(std::string_view table); - StatementBuilder& DeleteFrom(QualifiedTable table); - StatementBuilder& DeleteFrom(std::initializer_list table); - - // Begin an update statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& Update(std::string_view table); - StatementBuilder& Update(QualifiedTable table); - StatementBuilder& Update(std::initializer_list table); - - // Begin an `update or replace` statement. - // The initializer_list form enables the table name to be constructed from multiple parts. - StatementBuilder& UpdateOrReplace(std::string_view table); - StatementBuilder& UpdateOrReplace(QualifiedTable table); - StatementBuilder& UpdateOrReplace(std::initializer_list table); - - // Output the set portion of an update statement. - StatementBuilder& Set(); - - // Output the set portion of an update statement. - StatementBuilder& Vacuum(); - - // General purpose functions to begin and end a parenthetical expression. - StatementBuilder& BeginParenthetical(); - StatementBuilder& EndParenthetical(); - - // Adds the `without rowid` clause. - StatementBuilder& WithoutRowID(); - - // Assign an alias to the previous item. - StatementBuilder& As(std::string_view alias); - - // Gets the last bound index. - // A value of zero indicates that nothing has been bound. - int GetLastBindIndex() const { return m_bindIndex - 1; } - - // Prepares and returns the statement, applying any bindings that were requested. - Statement Prepare(const Connection& connection); - - // A convenience function that prepares, binds, and then executes a statement that does not return rows. - void Execute(const Connection& connection); - - private: - enum class Op - { - Equals, - Like, - Escape, - Literal, - GreaterThan, - GreaterThanOrEqualTo, - }; - - // Appends given the operation. - // The optional index value can be used to specify the parameter index. - int AppendOpAndBinder(Op op, std::optional index = {}); - - // Appends a set of binders for the values clause of an insert. - int AppendValuesAndBinders(size_t count); - - // Appends a binder for the values clause of an insert. - int AppendValueAndBinder(); - - // Adds a functor to our list that will bind the given value. - template - void AddBindFunctor(int binderIndex, const ValueType& value) - { - m_binders.emplace_back([binderIndex, value](Statement& s) { s.Bind(binderIndex, value); }); - } - - // Helper template for binding incoming values for an insert. - template - StatementBuilder& InsertValuesValueBinder(int bindIndex, const ValueType& value) - { - AddBindFunctor(bindIndex, value); - return *this; - } - template - StatementBuilder& InsertValuesValueBinder(int bindIndex, const std::optional& value) - { - if (value) - { - AddBindFunctor(bindIndex, value.value()); - } - else - { - AddBindFunctor(bindIndex, nullptr); - } - return *this; - } - StatementBuilder& InsertValuesValueBinder(int, details::unbound_t) - { - return *this; - } - StatementBuilder& InsertValuesValueBinder(int bindIndex, std::nullptr_t) - { - AddBindFunctor(bindIndex, nullptr); - return *this; - } - - std::ostringstream m_stream; - // Because binding values starts at 1 - int m_bindIndex = 1; - std::vector> m_binders; - bool m_needsComma = false; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_view_literals; + +namespace AppInstaller::SQLite::Builder +{ + namespace details + { + // Sentinel types to indicate special cases to the builder. + struct unbound_t {}; + struct rowcount_t {}; + + // Class for intake from external functions. + struct SubBuilder + { + SubBuilder(std::string&& s) : m_string(std::move(s)) {} + + SubBuilder(const SubBuilder&) = default; + SubBuilder& operator=(const SubBuilder&) = default; + + SubBuilder(SubBuilder&&) noexcept = default; + SubBuilder& operator=(SubBuilder&&) noexcept = default; + + const std::string& GetString() const { return m_string; } + + protected: + std::string m_string; + }; + + // Base class for all sub-builders. + struct SubBuilderBase + { + SubBuilderBase() = default; + + SubBuilderBase(const SubBuilderBase&) = default; + SubBuilderBase& operator=(const SubBuilderBase&) = default; + + SubBuilderBase(SubBuilderBase&&) noexcept = default; + SubBuilderBase& operator=(SubBuilderBase&&) noexcept = default; + + virtual operator SubBuilder() { return { m_stream.str() }; } + + protected: + std::ostringstream m_stream; + }; + } + + // Pass this value to indicate that the caller will bind the value later. + __declspec_selectany_ details::unbound_t Unbound; + + // Pass this value to indicate that the number of rows is to be selected. + __declspec_selectany_ details::rowcount_t RowCount; + + // A qualified table reference. + struct QualifiedTable + { + std::string_view Schema; + std::string_view Table; + + explicit constexpr QualifiedTable(std::string_view table) : Table(table) {} + explicit constexpr QualifiedTable(std::string_view schema, std::string_view table) : Schema(schema), Table(table) {} + }; + + namespace Schema + { + // The main database's schema table. + // More info can be found at: https://www.sqlite.org/schematab.html + constexpr QualifiedTable MainTable{ "main"sv, "sqlite_master"sv }; + + // The sqlite_schema column name for the type of the object. + constexpr std::string_view TypeColumn = "type"sv; + + // The sqlite_schema type value for a table. + constexpr std::string_view Type_Table = "table"sv; + + // The sqlite_schema type value for an index. + constexpr std::string_view Type_Index = "index"sv; + + // The sqlite_schema column name for the name of the object. + constexpr std::string_view NameColumn = "name"sv; + } + + // A qualified column reference. + struct QualifiedColumn + { + std::string_view Table; + std::string_view Column; + + explicit QualifiedColumn(std::string_view column) : Column(column) {} + explicit QualifiedColumn(std::string_view table, std::string_view column) : Table(table), Column(column) {} + }; + + // SQLite types as an enum. + enum class Type + { + Int, + Bool = Int, + Int64, + RowId = Int64, + Text, + Blob, + Integer, // Type for specifying a primary key column as a row id alias. + None, // Does not declare a type + }; + + template + struct TypeInfo + { + }; + + template <> + struct TypeInfo + { + using value_t = std::string; + }; + + template <> + struct TypeInfo + { + using value_t = std::optional; + }; + + template <> + struct TypeInfo + { + using value_t = SQLite::blob_t; + }; + + template <> + struct TypeInfo + { + using value_t = std::optional; + }; + + // Aggregate functions. + enum class Aggregate + { + Min, + Max, + }; + + // Helper to mark create an integer primary key for rowid, making it stable across vacuum. + struct IntegerPrimaryKey : public details::SubBuilderBase + { + IntegerPrimaryKey(); + + IntegerPrimaryKey(const IntegerPrimaryKey&) = default; + IntegerPrimaryKey& operator=(const IntegerPrimaryKey&) = default; + + IntegerPrimaryKey(IntegerPrimaryKey&&) noexcept = default; + IntegerPrimaryKey& operator=(IntegerPrimaryKey&&) noexcept = default; + + // Set the column to autoincrement. SQLite recommends against using this value unless + // you need to ensure that rowids are not ever reused. + IntegerPrimaryKey& AutoIncrement(bool isTrue = true); + }; + + // Helper used when creating a table. + struct ColumnBuilder : public details::SubBuilderBase + { + // Specify the column name and type when creating the builder. + ColumnBuilder(std::string_view column, Type type); + + ColumnBuilder(const ColumnBuilder&) = default; + ColumnBuilder& operator=(const ColumnBuilder&) = default; + + ColumnBuilder(ColumnBuilder&&) noexcept = default; + ColumnBuilder& operator=(ColumnBuilder&&) noexcept = default; + + // Indicate that the column is not able to be null. + // Allow for data driven construction with input value. + ColumnBuilder& NotNull(bool isTrue = true); + + // Indicate that the column is case-insensitive. + // Allow for data driven construction with input value. + ColumnBuilder& CollateNoCase(bool isTrue = true); + + // Indicate the default value for the column. + // Note that a default value is not considered constant if it is bound, + // so this function directly places the incoming value into the SQL statement. + ColumnBuilder& Default(int64_t value); + + // Indicate that the column is unique. + // Allow for data driven construction with input value. + ColumnBuilder& Unique(bool isTrue = true); + + // Indicate that the column is the primary key. + // Allow for data driven construction with input value. + ColumnBuilder& PrimaryKey(bool isTrue = true); + }; + + // Helper used to specify a primary key with multiple columns. + struct PrimaryKeyBuilder : public details::SubBuilderBase + { + PrimaryKeyBuilder(); + PrimaryKeyBuilder(std::initializer_list columns); + + PrimaryKeyBuilder(const PrimaryKeyBuilder&) = default; + PrimaryKeyBuilder& operator=(const PrimaryKeyBuilder&) = default; + + PrimaryKeyBuilder(PrimaryKeyBuilder&&) noexcept = default; + PrimaryKeyBuilder& operator=(PrimaryKeyBuilder&&) noexcept = default; + + virtual operator details::SubBuilder() override; + + // Add a column to the primary key. + PrimaryKeyBuilder& Column(std::string_view column); + + private: + bool m_isFirst = true; + bool m_needsClosing = true; + }; + + // A class that aids in building SQL statements in a more expressive manner than simple strings. + struct StatementBuilder + { + StatementBuilder() = default; + + StatementBuilder(const StatementBuilder&) = default; + StatementBuilder& operator=(const StatementBuilder&) = default; + + StatementBuilder(StatementBuilder&&) = default; + StatementBuilder& operator=(StatementBuilder&&) = default; + + // Begin a select statement for the given columns. + StatementBuilder& Select(); + StatementBuilder& Select(std::string_view column); + StatementBuilder& Select(std::initializer_list columns); + StatementBuilder& Select(const QualifiedColumn& column); + StatementBuilder& Select(std::initializer_list columns); + StatementBuilder& Select(details::rowcount_t); + + // Indicate the table that the statement will be operating on. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& From(); + StatementBuilder& From(std::string_view table); + StatementBuilder& From(QualifiedTable table); + StatementBuilder& From(std::initializer_list table); + + // Begin a filter clause on the given column. + StatementBuilder& Where(std::string_view column); + StatementBuilder& Where(const QualifiedColumn& column); + + // A full filter clause looking for an embedded null character. + // Is extremely specific to consistency checks, and so a more detailed construct is not required. + StatementBuilder& WhereValueContainsEmbeddedNullCharacter(std::string_view column); + StatementBuilder& WhereValueContainsEmbeddedNullCharacter(const QualifiedColumn& column); + + // Indicate the operation of the filter clause. + template + StatementBuilder& Equals(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value); + return *this; + } + template + StatementBuilder& Equals(const std::optional& value) + { + if (value) + { + AddBindFunctor(AppendOpAndBinder(Op::Equals), value.value()); + return *this; + } + else + { + return IsNull(); + } + } + // The optional index value can be used to specify the parameter index. + StatementBuilder& Equals(details::unbound_t, std::optional index = {}); + StatementBuilder& Equals(std::nullptr_t); + StatementBuilder& Equals(); + StatementBuilder& Equals(const QualifiedColumn& column); + + template + StatementBuilder& IsGreaterThan(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::GreaterThan), value); + return *this; + } + StatementBuilder& IsGreaterThan(details::unbound_t, std::optional index = {}); + + template + StatementBuilder& IsGreaterThanOrEqualTo(const ValueType& value) + { + AddBindFunctor(AppendOpAndBinder(Op::GreaterThanOrEqualTo), value); + return *this; + } + StatementBuilder& IsGreaterThanOrEqualTo(details::unbound_t, std::optional index = {}); + + StatementBuilder& LikeWithEscape(std::string_view value); + StatementBuilder& Like(details::unbound_t); + + StatementBuilder& Escape(std::string_view escapeChar); + + StatementBuilder& Not(); + StatementBuilder& In(); + + // Appends a set of value binders for the In clause. + StatementBuilder& In(size_t count); + + // IsNull(true) means the value is null; IsNull(false) means the value is not null. + StatementBuilder& IsNull(bool isNull = true); + StatementBuilder& IsNotNull() { return IsNull(false); } + + // Operators for combining filter clauses. + StatementBuilder& And(std::string_view column); + StatementBuilder& And(const QualifiedColumn& column); + StatementBuilder& Or(const QualifiedColumn& column); + + // Begin a join clause. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& Join(std::string_view table); + StatementBuilder& Join(QualifiedTable table); + StatementBuilder& Join(std::initializer_list table); + + // Begin a left outer join clause. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& LeftOuterJoin(std::string_view table); + StatementBuilder& LeftOuterJoin(QualifiedTable table); + StatementBuilder& LeftOuterJoin(std::initializer_list table); + + // Set the join constraint. + StatementBuilder& On(const QualifiedColumn& column1, const QualifiedColumn& column2); + + // Specify the grouping to use. + StatementBuilder& GroupBy(std::string_view column); + StatementBuilder& GroupBy(const QualifiedColumn& column); + + // Specify the ordering to use. + StatementBuilder& OrderBy(std::string_view column); + StatementBuilder& OrderBy(const QualifiedColumn& column); + StatementBuilder& OrderBy(std::initializer_list columns); + + // Specify the ordering behavior. + StatementBuilder& Ascending(); + StatementBuilder& Descending(); + + // Limits the result set to the given number of rows. + StatementBuilder& Limit(size_t rowCount); + + // Begin an insert statement for the given table. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& InsertInto(std::string_view table); + StatementBuilder& InsertInto(QualifiedTable table); + StatementBuilder& InsertInto(std::initializer_list table); + + // Begin an insert or ignore statement for the given table. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& InsertOrIgnore(std::string_view table); + StatementBuilder& InsertOrIgnore(QualifiedTable table); + StatementBuilder& InsertOrIgnore(std::initializer_list table); + + // Set the columns for a statement (typically insert). + StatementBuilder& Columns(std::string_view column); + StatementBuilder& Columns(std::initializer_list columns); + StatementBuilder& Columns(const QualifiedColumn& column); + StatementBuilder& Columns(std::initializer_list columns); + + // Set the columns for a select or create table statement. + StatementBuilder& Columns(std::initializer_list columns); + StatementBuilder& BeginColumns(); + StatementBuilder& Column(std::string_view column); + StatementBuilder& Column(const QualifiedColumn& column); + StatementBuilder& Column(Aggregate aggOp, std::string_view column); + StatementBuilder& Column(Aggregate aggOp, const QualifiedColumn& column); + StatementBuilder& Column(const details::SubBuilder& column); + StatementBuilder& EndColumns(); + + // Set the columns null constraint. + StatementBuilder& NotNull(bool isTrue = true); + + // Set the column's default value. + template + StatementBuilder& Default(const ValueType& value) + { + m_stream << " DEFAULT (" << value << ")"; + return *this; + } + + // Add the values clause for an insert statement. + template + StatementBuilder& Values(const ValueTypes&... values) + { + int bindIndexBegin = AppendValuesAndBinders(sizeof...(ValueTypes)); + // Use folding to add a binder for every value, specifically in the order they were given. + // Do not change this expression without understanding the implications to the bind order. + // See: https://en.cppreference.com/w/cpp/language/fold for more details. + (FoldHelper{}, ..., InsertValuesValueBinder(bindIndexBegin++, values)); + return *this; + } + StatementBuilder& BeginValues(); + template + StatementBuilder& Value(const ValueType& value) + { + InsertValuesValueBinder(AppendValueAndBinder(), value); + return *this; + } + StatementBuilder& EndValues(); + + // Begin a table creation statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& CreateTable(std::string_view table); + StatementBuilder& CreateTable(QualifiedTable table); + StatementBuilder& CreateTable(std::initializer_list table); + + // Begin an alter table statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& AlterTable(std::string_view table); + StatementBuilder& AlterTable(QualifiedTable table); + StatementBuilder& AlterTable(std::initializer_list table); + + // Complete an alter table statement by adding a column. + StatementBuilder& Add(std::string_view column, Type type); + + // Begin a table deletion statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DropTable(std::string_view table); + StatementBuilder& DropTable(QualifiedTable table); + StatementBuilder& DropTable(std::initializer_list table); + + // Begin a table deletion statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DropTableIfExists(std::string_view table); + StatementBuilder& DropTableIfExists(QualifiedTable table); + StatementBuilder& DropTableIfExists(std::initializer_list table); + + // Begin an index creation statement. + // The initializer_list form enables the index name to be constructed from multiple parts. + StatementBuilder& CreateIndex(std::string_view table); + StatementBuilder& CreateIndex(QualifiedTable table); + StatementBuilder& CreateIndex(std::initializer_list table); + + // Begin an unique index creation statement. + // The initializer_list form enables the index name to be constructed from multiple parts. + StatementBuilder& CreateUniqueIndex(std::string_view table); + StatementBuilder& CreateUniqueIndex(QualifiedTable table); + StatementBuilder& CreateUniqueIndex(std::initializer_list table); + + // Begin an index deletion statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DropIndex(std::string_view index); + StatementBuilder& DropIndex(QualifiedTable index); + StatementBuilder& DropIndex(std::initializer_list index); + + // Set index target table. + StatementBuilder& On(std::string_view table); + StatementBuilder& On(std::initializer_list table); + + // Begin a delete statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& DeleteFrom(std::string_view table); + StatementBuilder& DeleteFrom(QualifiedTable table); + StatementBuilder& DeleteFrom(std::initializer_list table); + + // Begin an update statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& Update(std::string_view table); + StatementBuilder& Update(QualifiedTable table); + StatementBuilder& Update(std::initializer_list table); + + // Begin an `update or replace` statement. + // The initializer_list form enables the table name to be constructed from multiple parts. + StatementBuilder& UpdateOrReplace(std::string_view table); + StatementBuilder& UpdateOrReplace(QualifiedTable table); + StatementBuilder& UpdateOrReplace(std::initializer_list table); + + // Output the set portion of an update statement. + StatementBuilder& Set(); + + // Output the set portion of an update statement. + StatementBuilder& Vacuum(); + + // General purpose functions to begin and end a parenthetical expression. + StatementBuilder& BeginParenthetical(); + StatementBuilder& EndParenthetical(); + + // Adds the `without rowid` clause. + StatementBuilder& WithoutRowID(); + + // Assign an alias to the previous item. + StatementBuilder& As(std::string_view alias); + + // Gets the last bound index. + // A value of zero indicates that nothing has been bound. + int GetLastBindIndex() const { return m_bindIndex - 1; } + + // Prepares and returns the statement, applying any bindings that were requested. + Statement Prepare(const Connection& connection); + + // A convenience function that prepares, binds, and then executes a statement that does not return rows. + void Execute(const Connection& connection); + + private: + enum class Op + { + Equals, + Like, + Escape, + Literal, + GreaterThan, + GreaterThanOrEqualTo, + }; + + // Appends given the operation. + // The optional index value can be used to specify the parameter index. + int AppendOpAndBinder(Op op, std::optional index = {}); + + // Appends a set of binders for the values clause of an insert. + int AppendValuesAndBinders(size_t count); + + // Appends a binder for the values clause of an insert. + int AppendValueAndBinder(); + + // Adds a functor to our list that will bind the given value. + template + void AddBindFunctor(int binderIndex, const ValueType& value) + { + m_binders.emplace_back([binderIndex, value](Statement& s) { s.Bind(binderIndex, value); }); + } + + // Helper template for binding incoming values for an insert. + template + StatementBuilder& InsertValuesValueBinder(int bindIndex, const ValueType& value) + { + AddBindFunctor(bindIndex, value); + return *this; + } + template + StatementBuilder& InsertValuesValueBinder(int bindIndex, const std::optional& value) + { + if (value) + { + AddBindFunctor(bindIndex, value.value()); + } + else + { + AddBindFunctor(bindIndex, nullptr); + } + return *this; + } + StatementBuilder& InsertValuesValueBinder(int, details::unbound_t) + { + return *this; + } + StatementBuilder& InsertValuesValueBinder(int bindIndex, std::nullptr_t) + { + AddBindFunctor(bindIndex, nullptr); + return *this; + } + + std::ostringstream m_stream; + // Because binding values starts at 1 + int m_bindIndex = 1; + std::vector> m_binders; + bool m_needsComma = false; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteStorageBase.h b/src/AppInstallerSharedLib/Public/winget/SQLiteStorageBase.h index 56e9c495b5..8b4958343d 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteStorageBase.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteStorageBase.h @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include - -namespace AppInstaller::SQLite -{ - // Type that wraps the basic SQLite storage functionality; the connection and metadata like schema version. - struct SQLiteStorageBase - { - // The disposition for opening the database. - enum class OpenDisposition - { - // Open for read only. - Read, - // Open for read and write. - ReadWrite, - // The database will not change while in use; open for immutable read. - Immutable, - }; - - // Gets the last write time for the database. - std::chrono::system_clock::time_point GetLastWriteTime() const; - - // Gets the identifier written to the database when it was created. - std::string GetDatabaseIdentifier() const; - - // Gets the schema version of the database. - const Version& GetVersion() const { return m_version; } - - // Renames the database file and any auxiliary files given the inputs. - // Should only be used on an inactive database. - // If overwrite is given, existing destination files will be removed first. - static void RenameSQLiteDatabase(const std::filesystem::path& source, const std::filesystem::path& destination, bool overwrite = false); - - protected: - SQLiteStorageBase(const std::string& target, const Version& version, size_t pageSize = 0); - - SQLiteStorageBase(const std::string& filePath, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); - - SQLiteStorageBase(const std::string& target, SQLiteStorageBase& source); - - // Sets the last write time metadata value in the database. - void SetLastWriteTime(); - - Utility::ManagedFile m_indexFile; - SQLite::Connection m_dbconn; - Version m_version; - std::unique_ptr m_interfaceLock = std::make_unique(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include + +namespace AppInstaller::SQLite +{ + // Type that wraps the basic SQLite storage functionality; the connection and metadata like schema version. + struct SQLiteStorageBase + { + // The disposition for opening the database. + enum class OpenDisposition + { + // Open for read only. + Read, + // Open for read and write. + ReadWrite, + // The database will not change while in use; open for immutable read. + Immutable, + }; + + // Gets the last write time for the database. + std::chrono::system_clock::time_point GetLastWriteTime() const; + + // Gets the identifier written to the database when it was created. + std::string GetDatabaseIdentifier() const; + + // Gets the schema version of the database. + const Version& GetVersion() const { return m_version; } + + // Renames the database file and any auxiliary files given the inputs. + // Should only be used on an inactive database. + // If overwrite is given, existing destination files will be removed first. + static void RenameSQLiteDatabase(const std::filesystem::path& source, const std::filesystem::path& destination, bool overwrite = false); + + protected: + SQLiteStorageBase(const std::string& target, const Version& version, size_t pageSize = 0); + + SQLiteStorageBase(const std::string& filePath, SQLiteStorageBase::OpenDisposition disposition, Utility::ManagedFile&& indexFile); + + SQLiteStorageBase(const std::string& target, SQLiteStorageBase& source); + + // Sets the last write time metadata value in the database. + void SetLastWriteTime(); + + Utility::ManagedFile m_indexFile; + SQLite::Connection m_dbconn; + Version m_version; + std::unique_ptr m_interfaceLock = std::make_unique(); + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteTempTable.h b/src/AppInstallerSharedLib/Public/winget/SQLiteTempTable.h index b164813836..ae2c2fdabe 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteTempTable.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteTempTable.h @@ -1,35 +1,35 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - - -namespace AppInstaller::SQLite -{ - // The base for a class that represents a temp table. - struct TempTable - { - TempTable(); - - ~TempTable(); - - TempTable(const TempTable&) = delete; - TempTable& operator=(const TempTable&) = delete; - - TempTable(TempTable&&) = default; - TempTable& operator=(TempTable&&) = default; - - protected: - // Gets the qualified name of the temp table. - Builder::QualifiedTable GetQualifiedName() const; - - // Prepares the drop table statement for use in destructor. - // It needs to be run by the derived class after the table is actually created. - void InitDropStatement(const Connection& connection); - - private: - std::string m_name; - Statement m_dropTableStatement; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + + +namespace AppInstaller::SQLite +{ + // The base for a class that represents a temp table. + struct TempTable + { + TempTable(); + + ~TempTable(); + + TempTable(const TempTable&) = delete; + TempTable& operator=(const TempTable&) = delete; + + TempTable(TempTable&&) = default; + TempTable& operator=(TempTable&&) = default; + + protected: + // Gets the qualified name of the temp table. + Builder::QualifiedTable GetQualifiedName() const; + + // Prepares the drop table statement for use in destructor. + // It needs to be run by the derived class after the table is actually created. + void InitDropStatement(const Connection& connection); + + private: + std::string m_name; + Statement m_dropTableStatement; + }; +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteVersion.h b/src/AppInstallerSharedLib/Public/winget/SQLiteVersion.h index eef4256833..706c451157 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteVersion.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteVersion.h @@ -1,64 +1,64 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace AppInstaller::SQLite -{ - // Represents the schema version of the database. - struct Version - { - // The major version of the schema. - // All minor changes to this major version must be backward compatible. - uint32_t MajorVersion{}; - // The minor version of the schema. - // All changes to the schema warrant a change to the minor version. - uint32_t MinorVersion{}; - - bool operator==(const Version& other) const - { - return (MajorVersion == other.MajorVersion && MinorVersion == other.MinorVersion); - } - - bool operator!=(const Version& other) const - { - return !operator==(other); - } - - bool operator>=(const Version& other) const - { - if (MajorVersion > other.MajorVersion) return true; - if (MajorVersion < other.MajorVersion) return false; - return MinorVersion >= other.MinorVersion; - } - - bool operator<(const Version& other) const - { - if (MajorVersion < other.MajorVersion) return true; - if (MajorVersion > other.MajorVersion) return false; - return MinorVersion < other.MinorVersion; - } - - // Gets a version that represents the latest schema known to the implementation. - static Version Latest(); - - // Gets a version that represents the latest schema known to the implementation for the given major version. - static Version LatestForMajor(uint32_t majorVersion); - - // Determines if this version represents the latest schema. - bool IsLatest() const; - - // Determines if this version represents the latest schema of the given major version. - bool IsLatestForMajor(uint32_t majorVersion) const; - - // Determines the schema version of the opened database. - static Version GetSchemaVersion(Connection& connection); - - // Writes the current version to the given database. - void SetSchemaVersion(Connection& connection) const; - }; - - // Output the version - std::ostream& operator<<(std::ostream& out, const Version& version); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace AppInstaller::SQLite +{ + // Represents the schema version of the database. + struct Version + { + // The major version of the schema. + // All minor changes to this major version must be backward compatible. + uint32_t MajorVersion{}; + // The minor version of the schema. + // All changes to the schema warrant a change to the minor version. + uint32_t MinorVersion{}; + + bool operator==(const Version& other) const + { + return (MajorVersion == other.MajorVersion && MinorVersion == other.MinorVersion); + } + + bool operator!=(const Version& other) const + { + return !operator==(other); + } + + bool operator>=(const Version& other) const + { + if (MajorVersion > other.MajorVersion) return true; + if (MajorVersion < other.MajorVersion) return false; + return MinorVersion >= other.MinorVersion; + } + + bool operator<(const Version& other) const + { + if (MajorVersion < other.MajorVersion) return true; + if (MajorVersion > other.MajorVersion) return false; + return MinorVersion < other.MinorVersion; + } + + // Gets a version that represents the latest schema known to the implementation. + static Version Latest(); + + // Gets a version that represents the latest schema known to the implementation for the given major version. + static Version LatestForMajor(uint32_t majorVersion); + + // Determines if this version represents the latest schema. + bool IsLatest() const; + + // Determines if this version represents the latest schema of the given major version. + bool IsLatestForMajor(uint32_t majorVersion) const; + + // Determines the schema version of the opened database. + static Version GetSchemaVersion(Connection& connection); + + // Writes the current version to the given database. + void SetSchemaVersion(Connection& connection) const; + }; + + // Output the version + std::ostream& operator<<(std::ostream& out, const Version& version); +} diff --git a/src/AppInstallerSharedLib/Public/winget/SQLiteWrapper.h b/src/AppInstallerSharedLib/Public/winget/SQLiteWrapper.h index 369c515fb5..0f100b4394 100644 --- a/src/AppInstallerSharedLib/Public/winget/SQLiteWrapper.h +++ b/src/AppInstallerSharedLib/Public/winget/SQLiteWrapper.h @@ -1,476 +1,476 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SQLITE_MEMORY_DB_CONNECTION_TARGET ":memory:" - -using namespace std::string_view_literals; - -namespace AppInstaller::SQLite -{ - // The name of the rowid column in SQLite. - extern std::string_view RowIDName; - - // The type of a rowid column in code. - using rowid_t = int64_t; - - // The type to use for blob data. - using blob_t = std::vector; - - namespace details - { - template - constexpr bool dependent_false = false; - - template - struct ParameterSpecificsImpl - { - static T& ToLog(T&&) - { - static_assert(dependent_false, "No type specific override has been supplied"); - } - static void Bind(sqlite3_stmt*, int, T&&) - { - static_assert(dependent_false, "No type specific override has been supplied"); - } - static T GetColumn(sqlite3_stmt*, int) - { - static_assert(dependent_false, "No type specific override has been supplied"); - } - }; - - template <> - struct ParameterSpecificsImpl - { - inline static std::string_view ToLog(nullptr_t) { return "null"sv; } - static void Bind(sqlite3_stmt* stmt, int index, nullptr_t); - }; - - template <> - struct ParameterSpecificsImpl - { - inline static const std::string& ToLog(const std::string& v) { return v; } - static void Bind(sqlite3_stmt* stmt, int index, const std::string& v); - static std::string GetColumn(sqlite3_stmt* stmt, int column); - }; - - template <> - struct ParameterSpecificsImpl - { - inline static const std::string_view& ToLog(const std::string_view& v) { return v; } - static void Bind(sqlite3_stmt* stmt, int index, std::string_view v); - }; - - template <> - struct ParameterSpecificsImpl - { - inline static int ToLog(int v) { return v; } - static void Bind(sqlite3_stmt* stmt, int index, int v); - static int GetColumn(sqlite3_stmt* stmt, int column); - }; - - template <> - struct ParameterSpecificsImpl - { - inline static int64_t ToLog(int64_t v) { return v; } - static void Bind(sqlite3_stmt* stmt, int index, int64_t v); - static int64_t GetColumn(sqlite3_stmt* stmt, int column); - }; - - template <> - struct ParameterSpecificsImpl - { - inline static bool ToLog(bool v) { return v; } - static void Bind(sqlite3_stmt* stmt, int index, bool v); - static bool GetColumn(sqlite3_stmt* stmt, int column); - }; - - template <> - struct ParameterSpecificsImpl - { - static std::string ToLog(const blob_t& v); - static void Bind(sqlite3_stmt* stmt, int index, const blob_t& v); - static blob_t GetColumn(sqlite3_stmt* stmt, int column); - }; - - template <> - struct ParameterSpecificsImpl - { - static std::string ToLog(const GUID& v); - static void Bind(sqlite3_stmt* stmt, int index, const GUID& v); - static GUID GetColumn(sqlite3_stmt* stmt, int column); - }; - - template - struct ParameterSpecificsImpl>> - { - static auto ToLog(E v) - { - return ToIntegral(v); - } - static void Bind(sqlite3_stmt* stmt, int index, E v) - { - ParameterSpecificsImpl>::Bind(stmt, index, ToIntegral(v)); - } - static E GetColumn(sqlite3_stmt* stmt, int column) - { - return ToEnum(ParameterSpecificsImpl>::GetColumn(stmt, column)); - } - }; - - template - struct ParameterSpecificsImpl> - { - using Optional = std::optional; - - static auto ToLog(const Optional& v) - { - std::ostringstream result; - if (v) - { - result << ParameterSpecificsImpl::ToLog(v.value()); - } - else - { - result << "{null}"; - } - return std::move(result).str(); - } - - static void Bind(sqlite3_stmt* stmt, int index, const Optional& v) - { - if (v) - { - ParameterSpecificsImpl::Bind(stmt, index, v.value()); - } - else - { - ParameterSpecificsImpl::Bind(stmt, index, nullptr); - } - } - - static Optional GetColumn(sqlite3_stmt* stmt, int column) - { - if (sqlite3_column_type(stmt, column) == SQLITE_NULL) - { - return std::nullopt; - } - else - { - return ParameterSpecificsImpl::GetColumn(stmt, column); - } - } - }; - - template - using ParameterSpecifics = ParameterSpecificsImpl>; - - // Allows the connection to be shared so that it can be closed in some circumstances. - struct SharedConnection - { - // Disables the connection, causing an exception to be thrown by `get`. - void Disable(); - - // Gets the connection object if active. - sqlite3* Get() const; - - // Gets the connection object for creation. - sqlite3** GetPtr(); - - private: - std::atomic_bool m_active = true; - wil::unique_any m_dbconn; - }; - } - - // A SQLite exception. - struct SQLiteException : public wil::ResultException - { - SQLiteException(int error) : wil::ResultException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, error)) {} - }; - - struct Statement; - - // The connection to a database. - struct Connection - { - friend Statement; - - // The disposition for opening a database connection. - enum class OpenDisposition : int - { - // Open existing database for reading. - ReadOnly = SQLITE_OPEN_READONLY, - // Open existing database for reading and writing. - ReadWrite = SQLITE_OPEN_READWRITE, - // Create new database for reading and writing. - Create = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, - }; - - // Flags for opening a database connection. - enum class OpenFlags : int - { - // No flags specified. - None = 0, - // Indicate that the target can be a URI. - Uri = SQLITE_OPEN_URI, - }; - - static Connection Create(const std::string& target, OpenDisposition disposition, OpenFlags flags = OpenFlags::None); - - Connection() = default; - - Connection(const Connection&) = delete; - Connection& operator=(const Connection&) = delete; - - Connection(Connection&& other) = default; - Connection& operator=(Connection&& other) = default; - - ~Connection() = default; - - // Enables the ICU integrations on this connection. - void EnableICU(); - - // Gets the last inserted rowid to the database. - rowid_t GetLastInsertRowID(); - - // Gets the count of changed rows for the last executed statement. - int GetChanges() const; - - //. Gets the (fixed but arbitrary) identifier for this connection. - size_t GetID() const; - - // Sets the busy timeout for the connection. - void SetBusyTimeout(std::chrono::milliseconds timeout); - - // Sets the journal mode. - // Returns true if successful, false if not. - // Must be performed outside of a transaction. - bool SetJournalMode(std::string_view mode); - - // Sets the page size for a new, empty database. - // Must be called before the first write to take effect. - // Must be a power of two between 512 and 65536 (inclusive), but we let SQLite enforce that. - void SetPageSize(size_t pageSize); - - operator sqlite3* () const { return m_dbconn->Get(); } - - protected: - // Gets the shared connection. - std::shared_ptr GetSharedConnection() const; - - private: - Connection(const std::string& target, OpenDisposition disposition, OpenFlags flags); - - size_t m_id = 0; - std::shared_ptr m_dbconn; - }; - - // A SQL statement. - struct Statement - { - static Statement Create(const Connection& connection, const std::string& sql); - static Statement Create(const Connection& connection, std::string_view sql); - static Statement Create(const Connection& connection, char const* const sql); - - Statement() = default; - - Statement(const Statement&) = delete; - Statement& operator=(const Statement&) = delete; - - Statement(Statement&& other) = default; - Statement& operator=(Statement&& other) = default; - - operator sqlite3_stmt* () const { return m_stmt.get(); } - - // The state of the statement. - enum class State - { - // The statement has been prepared, but not evaluated. - Prepared = 0, - // The statement has a row available for reading. - HasRow = 1, - // The statement has been completed. - Completed = 2, - // The statement has resulted in an error. - Error = 3, - }; - - // Gets the current state of the statement. - State GetState() const { return m_state; } - - // Bind parameters to the statement. - // The index is 1 based. - template - void Bind(int index, Value&& v) - { - AICLI_LOG(SQL, Verbose, << "Binding statement #" << m_connectionId << '-' << m_id << ": " << index << " => " << details::ParameterSpecifics::ToLog(std::forward(v))); - details::ParameterSpecifics::Bind(m_stmt.get(), index, std::forward(v)); - } - - // Evaluate the statement; either retrieving the next row or executing some action. - // Returns true if there is a row of data, or false if there is none. - // This return value is the equivalent of 'GetState() == State::HasRow' after calling Step. - bool Step(bool closeConnectionOnError = false); - - // Equivalent to Step, but does not ever expect a result, throwing if one is retrieved. - void Execute(bool closeConnectionOnError = false); - - // Gets a boolean value that indicates whether the specified column value is null in the current row. - // The index is 0 based. - bool GetColumnIsNull(int column); - - // Gets the value of the specified column from the current row. - // The index is 0 based. - template - Value GetColumn(int column) - { - THROW_HR_IF(E_BOUNDS, m_state != State::HasRow); - return details::ParameterSpecifics::GetColumn(m_stmt.get(), column); - } - - // Gets the entire row of values from the current row. - // The values requested *must* be those available starting from the first column, but trailing columns can be omitted. - template - std::tuple GetRow() - { - return GetRowImpl(std::make_integer_sequence{}); - } - - // Resets the statement state, allowing it to be evaluated again. - // Note that this does not clear data bindings. - void Reset(); - - // Determines if the statement owns an underlying object. - operator bool() const { return static_cast(m_stmt); } - - private: - Statement(const Connection& connection, std::string_view sql); - - // Helper to receive the integer sequence from the public function. - // This is equivalent to calling: - // for (i = 0 .. count of Values types) - // GetColumn(i) - // Then putting them all into a tuple. - template - std::tuple GetRowImpl(std::integer_sequence) - { - THROW_HR_IF(E_BOUNDS, m_state != State::HasRow); - return std::make_tuple(details::ParameterSpecifics::GetColumn(m_stmt.get(), I)...); - } - - std::shared_ptr m_dbconn; - size_t m_connectionId = 0; - size_t m_id = 0; - wil::unique_any m_stmt; - State m_state = State::Prepared; - }; - - // A SQLite transaction. - // Use as the beginning of a transaction stack, specifically when the transaction will write - // and the database is in WAL mode. - struct Transaction - { - // Creates a transaction, beginning it. - static Transaction Create(Connection& connection, std::string name, bool immediateWrite); - - Transaction(); - - Transaction(const Transaction&) = delete; - Transaction& operator=(const Transaction&) = delete; - - Transaction(Transaction&&) = default; - Transaction& operator=(Transaction&&) = default; - - ~Transaction(); - - // Rolls back the Transaction. - void Rollback(bool throwOnError = true); - - // Commits the Transaction. - void Commit(); - - private: - Transaction(Connection& connection, std::string&& name, bool immediateWrite); - - std::string m_name; - DestructionToken m_inProgress = true; - Statement m_rollback; - Statement m_commit; - }; - - // A SQLite savepoint. - struct Savepoint - { - // Creates a savepoint, beginning it. - static Savepoint Create(Connection& connection, std::string name); - - Savepoint(); - - Savepoint(const Savepoint&) = delete; - Savepoint& operator=(const Savepoint&) = delete; - - Savepoint(Savepoint&&) = default; - Savepoint& operator=(Savepoint&&) = default; - - ~Savepoint(); - - // Rolls back the Savepoint. - void Rollback(bool throwOnError = true); - - // Commits the Savepoint. - void Commit(); - - private: - Savepoint(Connection& connection, std::string&& name); - - std::string m_name; - DestructionToken m_inProgress = true; - Statement m_rollbackTo; - Statement m_release; - }; - - // A SQLite backup operation. - struct Backup - { - // Creates a backup. - static Backup Create(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName); - - Backup(const Backup&) = delete; - Backup& operator=(const Backup&) = delete; - - Backup(Backup&&) = default; - Backup& operator=(Backup&&) = default; - - // Performs some or all of the backup. - // Returns true if the backup is completed, false if not. - bool Step(int pages = -1); - - private: - Backup(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName); - - wil::unique_any m_backup; - }; - - // The escape character used in the EscapeStringForLike function. - extern std::string_view EscapeCharForLike; - - // Escapes the given input string for passing to a like operation. - std::string EscapeStringForLike(std::string_view value); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SQLITE_MEMORY_DB_CONNECTION_TARGET ":memory:" + +using namespace std::string_view_literals; + +namespace AppInstaller::SQLite +{ + // The name of the rowid column in SQLite. + extern std::string_view RowIDName; + + // The type of a rowid column in code. + using rowid_t = int64_t; + + // The type to use for blob data. + using blob_t = std::vector; + + namespace details + { + template + constexpr bool dependent_false = false; + + template + struct ParameterSpecificsImpl + { + static T& ToLog(T&&) + { + static_assert(dependent_false, "No type specific override has been supplied"); + } + static void Bind(sqlite3_stmt*, int, T&&) + { + static_assert(dependent_false, "No type specific override has been supplied"); + } + static T GetColumn(sqlite3_stmt*, int) + { + static_assert(dependent_false, "No type specific override has been supplied"); + } + }; + + template <> + struct ParameterSpecificsImpl + { + inline static std::string_view ToLog(nullptr_t) { return "null"sv; } + static void Bind(sqlite3_stmt* stmt, int index, nullptr_t); + }; + + template <> + struct ParameterSpecificsImpl + { + inline static const std::string& ToLog(const std::string& v) { return v; } + static void Bind(sqlite3_stmt* stmt, int index, const std::string& v); + static std::string GetColumn(sqlite3_stmt* stmt, int column); + }; + + template <> + struct ParameterSpecificsImpl + { + inline static const std::string_view& ToLog(const std::string_view& v) { return v; } + static void Bind(sqlite3_stmt* stmt, int index, std::string_view v); + }; + + template <> + struct ParameterSpecificsImpl + { + inline static int ToLog(int v) { return v; } + static void Bind(sqlite3_stmt* stmt, int index, int v); + static int GetColumn(sqlite3_stmt* stmt, int column); + }; + + template <> + struct ParameterSpecificsImpl + { + inline static int64_t ToLog(int64_t v) { return v; } + static void Bind(sqlite3_stmt* stmt, int index, int64_t v); + static int64_t GetColumn(sqlite3_stmt* stmt, int column); + }; + + template <> + struct ParameterSpecificsImpl + { + inline static bool ToLog(bool v) { return v; } + static void Bind(sqlite3_stmt* stmt, int index, bool v); + static bool GetColumn(sqlite3_stmt* stmt, int column); + }; + + template <> + struct ParameterSpecificsImpl + { + static std::string ToLog(const blob_t& v); + static void Bind(sqlite3_stmt* stmt, int index, const blob_t& v); + static blob_t GetColumn(sqlite3_stmt* stmt, int column); + }; + + template <> + struct ParameterSpecificsImpl + { + static std::string ToLog(const GUID& v); + static void Bind(sqlite3_stmt* stmt, int index, const GUID& v); + static GUID GetColumn(sqlite3_stmt* stmt, int column); + }; + + template + struct ParameterSpecificsImpl>> + { + static auto ToLog(E v) + { + return ToIntegral(v); + } + static void Bind(sqlite3_stmt* stmt, int index, E v) + { + ParameterSpecificsImpl>::Bind(stmt, index, ToIntegral(v)); + } + static E GetColumn(sqlite3_stmt* stmt, int column) + { + return ToEnum(ParameterSpecificsImpl>::GetColumn(stmt, column)); + } + }; + + template + struct ParameterSpecificsImpl> + { + using Optional = std::optional; + + static auto ToLog(const Optional& v) + { + std::ostringstream result; + if (v) + { + result << ParameterSpecificsImpl::ToLog(v.value()); + } + else + { + result << "{null}"; + } + return std::move(result).str(); + } + + static void Bind(sqlite3_stmt* stmt, int index, const Optional& v) + { + if (v) + { + ParameterSpecificsImpl::Bind(stmt, index, v.value()); + } + else + { + ParameterSpecificsImpl::Bind(stmt, index, nullptr); + } + } + + static Optional GetColumn(sqlite3_stmt* stmt, int column) + { + if (sqlite3_column_type(stmt, column) == SQLITE_NULL) + { + return std::nullopt; + } + else + { + return ParameterSpecificsImpl::GetColumn(stmt, column); + } + } + }; + + template + using ParameterSpecifics = ParameterSpecificsImpl>; + + // Allows the connection to be shared so that it can be closed in some circumstances. + struct SharedConnection + { + // Disables the connection, causing an exception to be thrown by `get`. + void Disable(); + + // Gets the connection object if active. + sqlite3* Get() const; + + // Gets the connection object for creation. + sqlite3** GetPtr(); + + private: + std::atomic_bool m_active = true; + wil::unique_any m_dbconn; + }; + } + + // A SQLite exception. + struct SQLiteException : public wil::ResultException + { + SQLiteException(int error) : wil::ResultException(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_SQLITE, error)) {} + }; + + struct Statement; + + // The connection to a database. + struct Connection + { + friend Statement; + + // The disposition for opening a database connection. + enum class OpenDisposition : int + { + // Open existing database for reading. + ReadOnly = SQLITE_OPEN_READONLY, + // Open existing database for reading and writing. + ReadWrite = SQLITE_OPEN_READWRITE, + // Create new database for reading and writing. + Create = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, + }; + + // Flags for opening a database connection. + enum class OpenFlags : int + { + // No flags specified. + None = 0, + // Indicate that the target can be a URI. + Uri = SQLITE_OPEN_URI, + }; + + static Connection Create(const std::string& target, OpenDisposition disposition, OpenFlags flags = OpenFlags::None); + + Connection() = default; + + Connection(const Connection&) = delete; + Connection& operator=(const Connection&) = delete; + + Connection(Connection&& other) = default; + Connection& operator=(Connection&& other) = default; + + ~Connection() = default; + + // Enables the ICU integrations on this connection. + void EnableICU(); + + // Gets the last inserted rowid to the database. + rowid_t GetLastInsertRowID(); + + // Gets the count of changed rows for the last executed statement. + int GetChanges() const; + + //. Gets the (fixed but arbitrary) identifier for this connection. + size_t GetID() const; + + // Sets the busy timeout for the connection. + void SetBusyTimeout(std::chrono::milliseconds timeout); + + // Sets the journal mode. + // Returns true if successful, false if not. + // Must be performed outside of a transaction. + bool SetJournalMode(std::string_view mode); + + // Sets the page size for a new, empty database. + // Must be called before the first write to take effect. + // Must be a power of two between 512 and 65536 (inclusive), but we let SQLite enforce that. + void SetPageSize(size_t pageSize); + + operator sqlite3* () const { return m_dbconn->Get(); } + + protected: + // Gets the shared connection. + std::shared_ptr GetSharedConnection() const; + + private: + Connection(const std::string& target, OpenDisposition disposition, OpenFlags flags); + + size_t m_id = 0; + std::shared_ptr m_dbconn; + }; + + // A SQL statement. + struct Statement + { + static Statement Create(const Connection& connection, const std::string& sql); + static Statement Create(const Connection& connection, std::string_view sql); + static Statement Create(const Connection& connection, char const* const sql); + + Statement() = default; + + Statement(const Statement&) = delete; + Statement& operator=(const Statement&) = delete; + + Statement(Statement&& other) = default; + Statement& operator=(Statement&& other) = default; + + operator sqlite3_stmt* () const { return m_stmt.get(); } + + // The state of the statement. + enum class State + { + // The statement has been prepared, but not evaluated. + Prepared = 0, + // The statement has a row available for reading. + HasRow = 1, + // The statement has been completed. + Completed = 2, + // The statement has resulted in an error. + Error = 3, + }; + + // Gets the current state of the statement. + State GetState() const { return m_state; } + + // Bind parameters to the statement. + // The index is 1 based. + template + void Bind(int index, Value&& v) + { + AICLI_LOG(SQL, Verbose, << "Binding statement #" << m_connectionId << '-' << m_id << ": " << index << " => " << details::ParameterSpecifics::ToLog(std::forward(v))); + details::ParameterSpecifics::Bind(m_stmt.get(), index, std::forward(v)); + } + + // Evaluate the statement; either retrieving the next row or executing some action. + // Returns true if there is a row of data, or false if there is none. + // This return value is the equivalent of 'GetState() == State::HasRow' after calling Step. + bool Step(bool closeConnectionOnError = false); + + // Equivalent to Step, but does not ever expect a result, throwing if one is retrieved. + void Execute(bool closeConnectionOnError = false); + + // Gets a boolean value that indicates whether the specified column value is null in the current row. + // The index is 0 based. + bool GetColumnIsNull(int column); + + // Gets the value of the specified column from the current row. + // The index is 0 based. + template + Value GetColumn(int column) + { + THROW_HR_IF(E_BOUNDS, m_state != State::HasRow); + return details::ParameterSpecifics::GetColumn(m_stmt.get(), column); + } + + // Gets the entire row of values from the current row. + // The values requested *must* be those available starting from the first column, but trailing columns can be omitted. + template + std::tuple GetRow() + { + return GetRowImpl(std::make_integer_sequence{}); + } + + // Resets the statement state, allowing it to be evaluated again. + // Note that this does not clear data bindings. + void Reset(); + + // Determines if the statement owns an underlying object. + operator bool() const { return static_cast(m_stmt); } + + private: + Statement(const Connection& connection, std::string_view sql); + + // Helper to receive the integer sequence from the public function. + // This is equivalent to calling: + // for (i = 0 .. count of Values types) + // GetColumn(i) + // Then putting them all into a tuple. + template + std::tuple GetRowImpl(std::integer_sequence) + { + THROW_HR_IF(E_BOUNDS, m_state != State::HasRow); + return std::make_tuple(details::ParameterSpecifics::GetColumn(m_stmt.get(), I)...); + } + + std::shared_ptr m_dbconn; + size_t m_connectionId = 0; + size_t m_id = 0; + wil::unique_any m_stmt; + State m_state = State::Prepared; + }; + + // A SQLite transaction. + // Use as the beginning of a transaction stack, specifically when the transaction will write + // and the database is in WAL mode. + struct Transaction + { + // Creates a transaction, beginning it. + static Transaction Create(Connection& connection, std::string name, bool immediateWrite); + + Transaction(); + + Transaction(const Transaction&) = delete; + Transaction& operator=(const Transaction&) = delete; + + Transaction(Transaction&&) = default; + Transaction& operator=(Transaction&&) = default; + + ~Transaction(); + + // Rolls back the Transaction. + void Rollback(bool throwOnError = true); + + // Commits the Transaction. + void Commit(); + + private: + Transaction(Connection& connection, std::string&& name, bool immediateWrite); + + std::string m_name; + DestructionToken m_inProgress = true; + Statement m_rollback; + Statement m_commit; + }; + + // A SQLite savepoint. + struct Savepoint + { + // Creates a savepoint, beginning it. + static Savepoint Create(Connection& connection, std::string name); + + Savepoint(); + + Savepoint(const Savepoint&) = delete; + Savepoint& operator=(const Savepoint&) = delete; + + Savepoint(Savepoint&&) = default; + Savepoint& operator=(Savepoint&&) = default; + + ~Savepoint(); + + // Rolls back the Savepoint. + void Rollback(bool throwOnError = true); + + // Commits the Savepoint. + void Commit(); + + private: + Savepoint(Connection& connection, std::string&& name); + + std::string m_name; + DestructionToken m_inProgress = true; + Statement m_rollbackTo; + Statement m_release; + }; + + // A SQLite backup operation. + struct Backup + { + // Creates a backup. + static Backup Create(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName); + + Backup(const Backup&) = delete; + Backup& operator=(const Backup&) = delete; + + Backup(Backup&&) = default; + Backup& operator=(Backup&&) = default; + + // Performs some or all of the backup. + // Returns true if the backup is completed, false if not. + bool Step(int pages = -1); + + private: + Backup(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName); + + wil::unique_any m_backup; + }; + + // The escape character used in the EscapeStringForLike function. + extern std::string_view EscapeCharForLike; + + // Escapes the given input string for passing to a like operation. + std::string EscapeStringForLike(std::string_view value); +} diff --git a/src/AppInstallerSharedLib/Public/winget/Security.h b/src/AppInstallerSharedLib/Public/winget/Security.h index 89378cb9cf..fa83b95fd2 100644 --- a/src/AppInstallerSharedLib/Public/winget/Security.h +++ b/src/AppInstallerSharedLib/Public/winget/Security.h @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace AppInstaller::Security -{ - // A Windows integrity level. - enum class IntegrityLevel - { - Untrusted, - Low, - Medium, - MediumPlus, - High, - System, - ProtectedProcess, - }; - - // Gets the integrity level for the current effective token. - // Does not know how to determine MediumPlus, if that ever matters... - IntegrityLevel GetEffectiveIntegrityLevel(); - - // Determines if the current COM caller is the same user as the current process - // and is at least equal integrity level (higher will also be allowed). - bool IsCOMCallerSameUserAndIntegrityLevel(); - - // Determines if the current COM caller is at least the minimum integrity level provided. - bool IsCOMCallerIntegrityLevelAtLeast(IntegrityLevel minimumLevel); - - // Determines if the current integrity level is at least the minimum integrity level provided. - bool IsCurrentIntegrityLevelAtLeast(IntegrityLevel minimumLevel); - - // Gets the string representation of the given SID. - std::string ToString(PSID sid); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace AppInstaller::Security +{ + // A Windows integrity level. + enum class IntegrityLevel + { + Untrusted, + Low, + Medium, + MediumPlus, + High, + System, + ProtectedProcess, + }; + + // Gets the integrity level for the current effective token. + // Does not know how to determine MediumPlus, if that ever matters... + IntegrityLevel GetEffectiveIntegrityLevel(); + + // Determines if the current COM caller is the same user as the current process + // and is at least equal integrity level (higher will also be allowed). + bool IsCOMCallerSameUserAndIntegrityLevel(); + + // Determines if the current COM caller is at least the minimum integrity level provided. + bool IsCOMCallerIntegrityLevelAtLeast(IntegrityLevel minimumLevel); + + // Determines if the current integrity level is at least the minimum integrity level provided. + bool IsCurrentIntegrityLevelAtLeast(IntegrityLevel minimumLevel); + + // Gets the string representation of the given SID. + std::string ToString(PSID sid); +} diff --git a/src/AppInstallerSharedLib/Public/winget/Yaml.h b/src/AppInstallerSharedLib/Public/winget/Yaml.h index 2e4521430b..4efc3713ae 100644 --- a/src/AppInstallerSharedLib/Public/winget/Yaml.h +++ b/src/AppInstallerSharedLib/Public/winget/Yaml.h @@ -1,364 +1,364 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -#include -#include -#include -#include -#include -#include -#include -#include - - -namespace AppInstaller::YAML -{ - // A location within the stream. - struct Mark - { - Mark() = default; - Mark(size_t l, size_t c) : line(l), column(c) {} - - size_t line = 0; - size_t column = 0; - }; - - // An exception from YAML. - struct Exception : public wil::ResultException - { - // The type of error that occurred. - enum class Type - { - None, - Memory, - Reader, - Scanner, - Parser, - Composer, - Writer, - Emitter, - Policy, - }; - - // Should only be used for Memory. - Exception(Type type); - - // Should only be used for Reader. - Exception(Type type, const char* problem, size_t offset, int value); - - // Used for Scanner, Parser, and Composer. - Exception(Type type, const char* problem, const Mark& problemMark, const char* context = {}, const Mark& contextMark = {}); - - // Used for Writer and Emitter. - Exception(Type type, const char* problem); - - const char* what() const noexcept override; - - const Mark& GetMark() const; - - private: - std::string m_what; - YAML::Mark m_mark; - }; - - // A YAML node. - struct Node - { - // The node's type. - enum class Type - { - Invalid, - None, - Scalar, - Sequence, - Mapping - }; - - // The node's tag - enum class TagType - { - Unknown, - Null, - Bool, - Str, - Int, - Float, - Timestamp, - Seq, - Map, - }; - - Node() : m_type(Type::Invalid), m_tagType(TagType::Unknown) {} - Node(Type type, std::string tag, const Mark& mark); - - // Sets the scalar value of the node. - void SetScalar(std::string value); - void SetScalar(std::string value, bool isQuoted); - - // Adds a child node to the sequence. - template - Node& AddSequenceNode(Args&&... args) - { - Require(Type::Sequence); - return m_sequence->emplace_back(std::forward(args)...); - } - - // Merges sequence nodes. If both sequence have the specified key with the same value - // they will get merged together. All elements in sequence must have the key. - void MergeSequenceNode(Node other, std::string_view key, bool caseInsensitive = false); - - // Adds a child node to the mapping. - template - Node& AddMappingNode(Node&& key, Args&&... args) - { - Require(Type::Mapping); - return m_mapping->emplace(std::move(key), Node(std::forward(args)...))->second; - } - - // Merge mapping node. If both contain a node with the same key preserve this. - void MergeMappingNode(Node other, bool caseInsensitive = false); - - bool IsDefined() const { return m_type != Type::Invalid; } - bool IsNull() const { return m_type == Type::Invalid || m_type == Type::None || (m_type == Type::Scalar && m_scalar.empty()); } - bool IsScalar() const { return m_type == Type::Scalar; } - bool IsSequence() const { return m_type == Type::Sequence; } - bool IsMap() const { return m_type == Type::Mapping; } - Type GetType() const { return m_type; } - TagType GetTagType() const { return m_tagType; } - - explicit operator bool() const { return IsDefined(); } - - // Gets the scalar value as the requested type. - template - T as() const - { - Require(Type::Scalar); - T* t = nullptr; - return as_dispatch(t); - } - - template - std::optional try_as() const - { - if (m_type != Type::Scalar) - { - return {}; - } - - T* t = nullptr; - return try_as_dispatch(t); - } - - bool operator<(const Node& other) const; - - // Gets a child node from the mapping by its name. - Node& operator[](std::string_view key); - const Node& operator[](std::string_view key) const; - - // Gets a child node from the mapping by its name case-insensitive. - Node& GetChildNode(std::string_view key); - const Node& GetChildNode(std::string_view key) const; - - // Gets a child node from the sequence by its index. - Node& operator[](size_t index); - const Node& operator[](size_t index) const; - - // Gets the number of child nodes. - size_t size() const; - - // Gets the mark for this node. - const Mark& Mark() const { return m_mark; } - - // Gets the nodes in the sequence. - const std::vector& Sequence() const; - - // Gets the nodes in the mapping. - const std::multimap& Mapping() const; - - private: - Node(std::string_view key) : m_type(Type::Scalar), m_scalar(key), m_tagType(TagType::Str) {} - - // Require certain node types to; throwing if the requirement is not met. - void Require(Type type) const; - - // The workers for the as function. - std::string as_dispatch(std::string*) const; - std::optional try_as_dispatch(std::string*) const; - - std::wstring as_dispatch(std::wstring*) const; - std::optional try_as_dispatch(std::wstring*) const; - - int64_t as_dispatch(int64_t*) const; - std::optional try_as_dispatch(int64_t*) const; - - int as_dispatch(int*) const; - std::optional try_as_dispatch(int*) const; - - bool as_dispatch(bool*) const; - std::optional try_as_dispatch(bool*) const; - - Type m_type; - std::string m_tag; - TagType m_tagType; - YAML::Mark m_mark; - std::string m_scalar; - std::optional> m_sequence; - std::optional> m_mapping; - }; - - // Loads from the input; returns the root node of the first document. - Node Load(std::string_view input); - Node Load(const std::string& input); - Node Load(const std::filesystem::path& input); - Node Load(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut); - - // Any emitter event. - // Not using enum class to enable existing code to function. - enum EmitterEvent - { - BeginSeq, - EndSeq, - BeginMap, - EndMap, - Key, - Value, - }; - - // Sets the scalar style to use for the next scalar output. - enum class ScalarStyle - { - Any, - Plain, - SingleQuoted, - DoubleQuoted, - Literal, - Folded, - }; - - // A schema header for a document. - struct DocumentSchemaHeader - { - DocumentSchemaHeader() = default; - DocumentSchemaHeader(std::string schemaHeaderString, const Mark& mark) : SchemaHeader(std::move(schemaHeaderString)), Mark(mark) {} - - std::string SchemaHeader; - Mark Mark; - static constexpr std::string_view YamlLanguageServerKey = "yaml-language-server"; - }; - - struct Document - { - Document() = default; - Document(Node root, DocumentSchemaHeader schemaHeader) : m_root(std::move(root)), m_schemaHeader(std::move(schemaHeader)) {} - - const DocumentSchemaHeader& GetSchemaHeader() const { return m_schemaHeader; } - - // Return r-values for move semantics - Node&& GetRoot() && { return std::move(m_root); } - - private: - Node m_root; - DocumentSchemaHeader m_schemaHeader; - }; - - // Forward declaration to allow pImpl in this Emitter. - namespace Wrapper - { - struct Document; - } - - // Loads from the input; returns the root node of the first document. - Document LoadDocument(std::string_view input); - Document LoadDocument(const std::string& input); - Document LoadDocument(const std::filesystem::path& input); - Document LoadDocument(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut); - - // A YAML emitter. - struct Emitter - { - Emitter(); - - Emitter(const Emitter&) = delete; - Emitter& operator=(const Emitter&) = delete; - - Emitter(Emitter&&) noexcept; - Emitter& operator=(Emitter&&) noexcept; - - ~Emitter(); - - // Emit events and values. - Emitter& operator<<(EmitterEvent event); - Emitter& operator<<(std::string_view value); - Emitter& operator<<(int64_t value); - Emitter& operator<<(int value); - Emitter& operator<<(bool value); - - Emitter& operator<<(ScalarStyle style); - - // Gets the result of the emitter; can only be retrieved once. - std::string str(); - - // Gets the result of the emitter to out stream; can only be retrieved once. - void Emit(std::ostream& out); - - private: - // Appends the given node to the current container if applicable. - void AppendNode(int id); - - std::unique_ptr m_document; - - // If set, stores the last Key that was set. - std::optional m_keyId; - - struct ContainerInfo - { - ContainerInfo(int id, bool map) : Id(id), IsMapping(map) {} - - int Id; - bool IsMapping; - }; - - // The stack of containers being emitted. - std::stack m_containers; - - // *** State Machine *** - - // The type of input coming into the emitter. - enum class InputType - { - Scalar, - BeginSeq, - EndSeq, - BeginMap, - EndMap, - Key, - Value, - }; - - // If set, defines the type of the next scalar (Key or Value). - std::optional m_scalarType; - - // If set, defines the style of the next scalar. - std::optional m_scalarStyle; - - // Converts the input type to a bitmask value. - size_t GetInputBitmask(InputType type); - - // Checks the state of the emitter to ensure that the incoming value is acceptable. - void CheckInput(InputType type); - - // The currently allowed input types. - size_t m_allowedInputs = 0; - - template - void SetAllowedInputs() - { - m_allowedInputs = (GetInputBitmask(types) | ...); - } - - // Sets the allowed inputs for the container on the top of the stack. - void SetAllowedInputsForContainer(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace AppInstaller::YAML +{ + // A location within the stream. + struct Mark + { + Mark() = default; + Mark(size_t l, size_t c) : line(l), column(c) {} + + size_t line = 0; + size_t column = 0; + }; + + // An exception from YAML. + struct Exception : public wil::ResultException + { + // The type of error that occurred. + enum class Type + { + None, + Memory, + Reader, + Scanner, + Parser, + Composer, + Writer, + Emitter, + Policy, + }; + + // Should only be used for Memory. + Exception(Type type); + + // Should only be used for Reader. + Exception(Type type, const char* problem, size_t offset, int value); + + // Used for Scanner, Parser, and Composer. + Exception(Type type, const char* problem, const Mark& problemMark, const char* context = {}, const Mark& contextMark = {}); + + // Used for Writer and Emitter. + Exception(Type type, const char* problem); + + const char* what() const noexcept override; + + const Mark& GetMark() const; + + private: + std::string m_what; + YAML::Mark m_mark; + }; + + // A YAML node. + struct Node + { + // The node's type. + enum class Type + { + Invalid, + None, + Scalar, + Sequence, + Mapping + }; + + // The node's tag + enum class TagType + { + Unknown, + Null, + Bool, + Str, + Int, + Float, + Timestamp, + Seq, + Map, + }; + + Node() : m_type(Type::Invalid), m_tagType(TagType::Unknown) {} + Node(Type type, std::string tag, const Mark& mark); + + // Sets the scalar value of the node. + void SetScalar(std::string value); + void SetScalar(std::string value, bool isQuoted); + + // Adds a child node to the sequence. + template + Node& AddSequenceNode(Args&&... args) + { + Require(Type::Sequence); + return m_sequence->emplace_back(std::forward(args)...); + } + + // Merges sequence nodes. If both sequence have the specified key with the same value + // they will get merged together. All elements in sequence must have the key. + void MergeSequenceNode(Node other, std::string_view key, bool caseInsensitive = false); + + // Adds a child node to the mapping. + template + Node& AddMappingNode(Node&& key, Args&&... args) + { + Require(Type::Mapping); + return m_mapping->emplace(std::move(key), Node(std::forward(args)...))->second; + } + + // Merge mapping node. If both contain a node with the same key preserve this. + void MergeMappingNode(Node other, bool caseInsensitive = false); + + bool IsDefined() const { return m_type != Type::Invalid; } + bool IsNull() const { return m_type == Type::Invalid || m_type == Type::None || (m_type == Type::Scalar && m_scalar.empty()); } + bool IsScalar() const { return m_type == Type::Scalar; } + bool IsSequence() const { return m_type == Type::Sequence; } + bool IsMap() const { return m_type == Type::Mapping; } + Type GetType() const { return m_type; } + TagType GetTagType() const { return m_tagType; } + + explicit operator bool() const { return IsDefined(); } + + // Gets the scalar value as the requested type. + template + T as() const + { + Require(Type::Scalar); + T* t = nullptr; + return as_dispatch(t); + } + + template + std::optional try_as() const + { + if (m_type != Type::Scalar) + { + return {}; + } + + T* t = nullptr; + return try_as_dispatch(t); + } + + bool operator<(const Node& other) const; + + // Gets a child node from the mapping by its name. + Node& operator[](std::string_view key); + const Node& operator[](std::string_view key) const; + + // Gets a child node from the mapping by its name case-insensitive. + Node& GetChildNode(std::string_view key); + const Node& GetChildNode(std::string_view key) const; + + // Gets a child node from the sequence by its index. + Node& operator[](size_t index); + const Node& operator[](size_t index) const; + + // Gets the number of child nodes. + size_t size() const; + + // Gets the mark for this node. + const Mark& Mark() const { return m_mark; } + + // Gets the nodes in the sequence. + const std::vector& Sequence() const; + + // Gets the nodes in the mapping. + const std::multimap& Mapping() const; + + private: + Node(std::string_view key) : m_type(Type::Scalar), m_scalar(key), m_tagType(TagType::Str) {} + + // Require certain node types to; throwing if the requirement is not met. + void Require(Type type) const; + + // The workers for the as function. + std::string as_dispatch(std::string*) const; + std::optional try_as_dispatch(std::string*) const; + + std::wstring as_dispatch(std::wstring*) const; + std::optional try_as_dispatch(std::wstring*) const; + + int64_t as_dispatch(int64_t*) const; + std::optional try_as_dispatch(int64_t*) const; + + int as_dispatch(int*) const; + std::optional try_as_dispatch(int*) const; + + bool as_dispatch(bool*) const; + std::optional try_as_dispatch(bool*) const; + + Type m_type; + std::string m_tag; + TagType m_tagType; + YAML::Mark m_mark; + std::string m_scalar; + std::optional> m_sequence; + std::optional> m_mapping; + }; + + // Loads from the input; returns the root node of the first document. + Node Load(std::string_view input); + Node Load(const std::string& input); + Node Load(const std::filesystem::path& input); + Node Load(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut); + + // Any emitter event. + // Not using enum class to enable existing code to function. + enum EmitterEvent + { + BeginSeq, + EndSeq, + BeginMap, + EndMap, + Key, + Value, + }; + + // Sets the scalar style to use for the next scalar output. + enum class ScalarStyle + { + Any, + Plain, + SingleQuoted, + DoubleQuoted, + Literal, + Folded, + }; + + // A schema header for a document. + struct DocumentSchemaHeader + { + DocumentSchemaHeader() = default; + DocumentSchemaHeader(std::string schemaHeaderString, const Mark& mark) : SchemaHeader(std::move(schemaHeaderString)), Mark(mark) {} + + std::string SchemaHeader; + Mark Mark; + static constexpr std::string_view YamlLanguageServerKey = "yaml-language-server"; + }; + + struct Document + { + Document() = default; + Document(Node root, DocumentSchemaHeader schemaHeader) : m_root(std::move(root)), m_schemaHeader(std::move(schemaHeader)) {} + + const DocumentSchemaHeader& GetSchemaHeader() const { return m_schemaHeader; } + + // Return r-values for move semantics + Node&& GetRoot() && { return std::move(m_root); } + + private: + Node m_root; + DocumentSchemaHeader m_schemaHeader; + }; + + // Forward declaration to allow pImpl in this Emitter. + namespace Wrapper + { + struct Document; + } + + // Loads from the input; returns the root node of the first document. + Document LoadDocument(std::string_view input); + Document LoadDocument(const std::string& input); + Document LoadDocument(const std::filesystem::path& input); + Document LoadDocument(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut); + + // A YAML emitter. + struct Emitter + { + Emitter(); + + Emitter(const Emitter&) = delete; + Emitter& operator=(const Emitter&) = delete; + + Emitter(Emitter&&) noexcept; + Emitter& operator=(Emitter&&) noexcept; + + ~Emitter(); + + // Emit events and values. + Emitter& operator<<(EmitterEvent event); + Emitter& operator<<(std::string_view value); + Emitter& operator<<(int64_t value); + Emitter& operator<<(int value); + Emitter& operator<<(bool value); + + Emitter& operator<<(ScalarStyle style); + + // Gets the result of the emitter; can only be retrieved once. + std::string str(); + + // Gets the result of the emitter to out stream; can only be retrieved once. + void Emit(std::ostream& out); + + private: + // Appends the given node to the current container if applicable. + void AppendNode(int id); + + std::unique_ptr m_document; + + // If set, stores the last Key that was set. + std::optional m_keyId; + + struct ContainerInfo + { + ContainerInfo(int id, bool map) : Id(id), IsMapping(map) {} + + int Id; + bool IsMapping; + }; + + // The stack of containers being emitted. + std::stack m_containers; + + // *** State Machine *** + + // The type of input coming into the emitter. + enum class InputType + { + Scalar, + BeginSeq, + EndSeq, + BeginMap, + EndMap, + Key, + Value, + }; + + // If set, defines the type of the next scalar (Key or Value). + std::optional m_scalarType; + + // If set, defines the style of the next scalar. + std::optional m_scalarStyle; + + // Converts the input type to a bitmask value. + size_t GetInputBitmask(InputType type); + + // Checks the state of the emitter to ensure that the incoming value is acceptable. + void CheckInput(InputType type); + + // The currently allowed input types. + size_t m_allowedInputs = 0; + + template + void SetAllowedInputs() + { + m_allowedInputs = (GetInputBitmask(types) | ...); + } + + // Sets the allowed inputs for the container on the top of the stack. + void SetAllowedInputsForContainer(); + }; +} diff --git a/src/AppInstallerSharedLib/Registry.cpp b/src/AppInstallerSharedLib/Registry.cpp index 0e4266e1a2..40d667c569 100644 --- a/src/AppInstallerSharedLib/Registry.cpp +++ b/src/AppInstallerSharedLib/Registry.cpp @@ -1,599 +1,599 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/Registry.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerLogging.h" - - -namespace AppInstaller::Registry -{ - namespace - { - std::wstring_view ConvertBytesToWideStringView(const std::vector& data) - { - // Remove any extra bytes because the data could just be dirty; better to not have a bad character than outright fail. - std::wstring_view result{ reinterpret_cast(data.data()), data.size() / sizeof(wchar_t) }; - - // Registry values may or may not be null terminated; we will remove any trailing nulls - while (!result.empty() && result.back() == L'\0') - { - result = result.substr(0, result.size() - 1); - } - - return result; - } - - std::wstring ConvertBytesToWideString(const std::vector& data) - { - return std::wstring{ ConvertBytesToWideStringView(data) }; - } - - std::string ConvertBytesToString(const std::vector& data) - { - return Utility::ConvertToUTF8(ConvertBytesToWideStringView(data)); - } - - uint32_t ConvertBytesToUInt32LE(const std::vector& data) - { - THROW_HR_IF(E_NOT_VALID_STATE, data.size() != sizeof(uint32_t)); - uint32_t result = 0; - uint32_t shift = 0; - - for (const BYTE datum : data) - { - result |= ((static_cast(datum) & 0xFF) << shift); - shift += 8; - } - - return result; - } - - bool TryGetRegistryValueNameFromIndex(const wil::shared_hkey& key, DWORD index, std::wstring& valueName) - { - constexpr DWORD MaxNameLength = 32767; - LSTATUS status = ERROR_SUCCESS; - DWORD charCount = 0; - valueName = L'\0'; - - while (valueName.size() <= MaxNameLength) - { - charCount = wil::safe_cast(valueName.size()); - - // We could also get the type and data here, but we read only the name instead - // to prevent duplication with the code that gets the data from the name. - status = RegEnumValueW(key.get(), index, &valueName[0], &charCount, nullptr, nullptr, nullptr, nullptr); - - if (status == ERROR_MORE_DATA) - { - // See if we can get away with the current capacity - if (valueName.size() < valueName.capacity()) - { - valueName.resize(valueName.capacity()); - } - else - { - valueName.resize(valueName.capacity() * 2); - } - } - else - { - break; - } - } - - if (status == ERROR_SUCCESS) - { - valueName.resize(wil::safe_cast(charCount)); - return true; - } - else if (status == ERROR_NO_MORE_ITEMS) - { - return false; - } - else - { - THROW_IF_WIN32_ERROR(status); - return false; - } - } - - bool TryGetRegistryValueData(const wil::shared_hkey& key, const std::wstring& valueName, DWORD& type, std::vector& data) - { - data.resize(64); - - LSTATUS status = ERROR_SUCCESS; - DWORD byteCount = 0; - - while (data.size() < (64 << 20)) - { - byteCount = wil::safe_cast(data.size()); - status = RegGetValueW(key.get(), nullptr, valueName.c_str(), RRF_RT_ANY | RRF_NOEXPAND, &type, data.data(), &byteCount); - - if (status == ERROR_MORE_DATA && byteCount > data.size()) - { - data.resize(byteCount); - } - else - { - break; - } - } - - if (status == ERROR_FILE_NOT_FOUND) - { - return false; - } - - THROW_IF_WIN32_ERROR(status); - - // Resize to actual data size - data.resize(byteCount); - - return true; - } - - bool DeleteRegistryValueData(const wil::shared_hkey& key, const std::wstring& valueName) - { - LSTATUS status = RegDeleteValueW(key.get(), valueName.c_str()); - - if (status == ERROR_FILE_NOT_FOUND) - { - return false; - } - - THROW_IF_WIN32_ERROR(status); - - return true; - } - } - - namespace details - { - ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) - { - return data; - } - - ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) - { - return ConvertBytesToString(data); - } - - ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) - { - return ConvertBytesToWideString(data); - } - - ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) - { - return Utility::ConvertToUTF8(Utility::ExpandEnvironmentVariables(ConvertBytesToWideString(data))); - } - - ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) - { - return ConvertBytesToWideString(data); - } - - ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) - { - return data; - } - - ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) - { - return ConvertBytesToUInt32LE(data); - } - } - - Value::Value(DWORD type, std::vector&& data) : m_type(static_cast(type)), m_data(std::move(data)) - { - } - - bool Value::HasCompatibleType(Type type) const - { - // Allow interop between String and ExpandString - if ((m_type == Type::String || m_type == Type::ExpandString || m_type == Type::UTF16String || m_type == Type::UTF16ExpandString) && - (type == Type::String || type == Type::ExpandString || type == Type::UTF16String || type == Type::UTF16ExpandString)) - { - return true; - } - - return m_type == type; - } - - ValueList::ValueRef::ValueRef(wil::shared_hkey key, std::wstring&& valueName) : m_key(std::move(key)), m_valueName(std::move(valueName)) {} - - std::string ValueList::ValueRef::Name() const - { - return Utility::ConvertToUTF8(m_valueName); - } - - std::optional ValueList::ValueRef::Value() const - { - DWORD type; - std::vector data; - if (!TryGetRegistryValueData(m_key, m_valueName, type, data)) - { - return std::nullopt; - } - - return Registry::Value{ type, std::move(data) }; - } - - ValueList::const_iterator& ValueList::const_iterator::operator++() - { - ++m_index; - GetValue(); - return *this; - } - - ValueList::const_iterator ValueList::const_iterator::operator++(int) - { - const_iterator result; - result.m_key = m_key; - result.m_index = m_index++; - result.m_value = std::nullopt; - std::swap(m_value, result.m_value); - GetValue(); - return result; - } - - bool ValueList::const_iterator::operator==(const const_iterator& other) const - { - return (!m_key && !other.m_key) || (m_key.get() == other.m_key.get() && m_index == other.m_index); - } - - bool ValueList::const_iterator::operator!=(const const_iterator& other) const - { - return !operator==(other); - } - - void ValueList::const_iterator::GetValue() - { - std::wstring valueName; - if (!TryGetRegistryValueNameFromIndex(m_key, m_index, valueName)) - { - m_key.reset(); - return; - } - - m_value = ValueRef{ m_key, std::move(valueName) }; - } - - const ValueList::ValueRef& ValueList::const_iterator::operator*() const - { - return m_value.value(); - } - - const ValueList::ValueRef* ValueList::const_iterator::operator->() const - { - return &m_value.value(); - } - - ValueList::const_iterator::const_iterator(const wil::shared_hkey& key, DWORD index) : m_key(key), m_index(index) - { - GetValue(); - } - - ValueList::const_iterator ValueList::begin() const - { - return { m_key }; - } - - ValueList::const_iterator ValueList::end() const - { - return {}; - } - - ValueList::ValueList(wil::shared_hkey key) : m_key(key) {} - - Key::Key(HKEY key) - { - Initialize(key, {}, 0, KEY_READ, false); - } - - Key::Key(HKEY key, std::string_view subKey, DWORD options, REGSAM access) - { - Initialize(key, Utility::ConvertToUTF16(subKey), options, access, false); - } - - Key::Key(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) - { - Initialize(key, subKey, options, access, false); - } - - std::string Key::SubKeyRef::Name() const - { - return Utility::ConvertToUTF8(m_subKeyName); - } - - Key Key::SubKeyRef::Open() const - { - return { m_parentKey.get(), m_subKeyName, 0, m_access }; - } - - Key::SubKeyRef::SubKeyRef(const wil::shared_hkey& key, REGSAM access) : - m_parentKey(key), m_access(access), m_subKeyName(64, L'\0') - { - Enum(0); - } - - void Key::SubKeyRef::Enum(DWORD index) - { - LSTATUS status = ERROR_SUCCESS; - DWORD charCount = 0; - - while (m_subKeyName.size() < 4096) - { - charCount = wil::safe_cast(m_subKeyName.size()); - status = RegEnumKeyExW(m_parentKey.get(), index, &m_subKeyName[0], &charCount, nullptr, nullptr, nullptr, nullptr); - - if (status == ERROR_MORE_DATA) - { - // See if we can get away with the current capacity - if (m_subKeyName.size() < m_subKeyName.capacity()) - { - m_subKeyName.resize(m_subKeyName.capacity()); - } - else - { - m_subKeyName.resize(m_subKeyName.capacity() * 2); - } - } - else - { - break; - } - } - - if (status == ERROR_SUCCESS) - { - m_subKeyName.resize(wil::safe_cast(charCount)); - } - else if (status == ERROR_NO_MORE_ITEMS) - { - m_parentKey.reset(); - } - else - { - THROW_IF_WIN32_ERROR(status); - } - } - - Key::const_iterator& Key::const_iterator::operator++() - { - m_subkey.Enum(++m_index); - return *this; - } - - Key::const_iterator Key::const_iterator::operator++(int) - { - const_iterator result = *this; - m_subkey.Enum(++m_index); - return result; - } - - bool Key::const_iterator::operator==(const const_iterator& other) const - { - return (!m_subkey.m_parentKey && !other.m_subkey.m_parentKey) || (m_subkey.m_parentKey.get() == other.m_subkey.m_parentKey.get() && m_index == other.m_index); - } - - bool Key::const_iterator::operator!=(const const_iterator& other) const - { - return !operator==(other); - } - - const Key::SubKeyRef& Key::const_iterator::operator*() const - { - return m_subkey; - } - - const Key::SubKeyRef* Key::const_iterator::operator->() const - { - return &m_subkey; - } - - Key::const_iterator::const_iterator(const wil::shared_hkey& key, REGSAM access) : - m_subkey(key, access) - { - } - - Key::const_iterator Key::begin() const - { - return { m_key, m_access }; - } - - Key::const_iterator Key::end() const - { - return {}; - } - - std::optional Key::operator[](std::string_view name) const - { - return operator[](Utility::ConvertToUTF16(name)); - } - - std::optional Key::operator[](const std::wstring& name) const - { - DWORD type; - std::vector data; - - if (TryGetRegistryValueData(m_key, name, type, data)) - { - return Value{ type, std::move(data) }; - } - else - { - return {}; - } - } - - void Key::DeleteValue(std::string_view name) const - { - DeleteValue(Utility::ConvertToUTF16(name)); - } - - void Key::DeleteValue(const std::wstring& name) const - { - if (DeleteRegistryValueData(m_key, name)) - { - AICLI_LOG(Core, Verbose, << "Registry value '" << Utility::ConvertToUTF8(name) << "' deleted successfully."); - } - else - { - AICLI_LOG(Core, Verbose, << "Registry value '" << Utility::ConvertToUTF8(name) << "' does not exist."); - } - } - - std::optional Key::SubKey(std::string_view subKey, DWORD options) const - { - return SubKey(Utility::ConvertToUTF16(subKey), options); - } - - std::optional Key::SubKey(const std::wstring& subKey, DWORD options) const - { - if (!m_key) - { - return std::nullopt; - } - - Key result; - if (result.Initialize(m_key.get(), subKey, options, m_access, true)) - { - return result; - } - else - { - return std::nullopt; - } - } - - void Key::SetValue(const std::wstring& name, const std::wstring& value, DWORD type) const - { - THROW_IF_WIN32_ERROR(RegSetValueExW(m_key.get(), name.c_str(), 0, type, reinterpret_cast(value.c_str()), static_cast(sizeof(wchar_t) * (value.size() + 1)))); - AICLI_LOG(Core, Verbose, << "Setting '" << Utility::ConvertToUTF8(name) << "' with the value: " << Utility::ConvertToUTF8(value)); - } - - void Key::SetValue(const std::wstring& name, const std::vector& value, DWORD type) const - { - THROW_IF_WIN32_ERROR(RegSetValueExW(m_key.get(), name.c_str(), 0, type, reinterpret_cast(value.data()), static_cast(value.size()))); - AICLI_LOG(Core, Verbose, << "Setting '" << Utility::ConvertToUTF8(name) << "' with the value: " << ConvertBytesToString(value)); - - } - - void Key::SetValue(const std::wstring& name, DWORD value) const - { - THROW_IF_WIN32_ERROR(RegSetValueExW(m_key.get(), name.c_str(), 0, REG_DWORD, reinterpret_cast(&value), sizeof(DWORD))); - AICLI_LOG(Core, Verbose, << "Setting '" << Utility::ConvertToUTF8(name) << "' with the value: " << value); - - } - - ValueList Key::Values() const - { - return { m_key }; - } - - Key Key::OpenIfExists(HKEY key, std::string_view subKey, DWORD options, REGSAM access) - { - return OpenIfExists(key, Utility::ConvertToUTF16(subKey), options, access); - } - - Key Key::OpenIfExists(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) - { - Key result; - result.Initialize(key, subKey, options, access, true); - return result; - } - - Key Key::Create(HKEY key, std::string_view subKey, DWORD options, REGSAM access) - { - return Create(key, Utility::ConvertToUTF16(subKey), options, access); - } - - Key Key::Create(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) - { - Key result; - result.CreateAndOpen(key, subKey, options, access); - return result; - } - - bool Key::Delete(HKEY key, std::string_view subKey, DWORD samDesired) - { - return Delete(key, Utility::ConvertToUTF16(subKey), samDesired); - } - - bool Key::Delete(HKEY key, const std::wstring& subKey, DWORD samDesired) - { - LSTATUS status = RegDeleteKeyExW(key, subKey.c_str(), samDesired, 0); - if (status == ERROR_SUCCESS) - { - AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was deleted successfully."); - return true; - } - else if (status == ERROR_FILE_NOT_FOUND) - { - AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was not found."); - } - else - { - THROW_IF_WIN32_ERROR(status); - } - - return false; - } - - bool Key::DeleteTree(HKEY key, const std::wstring& subKey) - { - LSTATUS status = RegDeleteTree(key, subKey.c_str()); - if (status == ERROR_SUCCESS) - { - AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was deleted successfully."); - return true; - } - else if (status == ERROR_FILE_NOT_FOUND) - { - AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was not found."); - } - else - { - THROW_IF_WIN32_ERROR(status); - } - - return false; - } - - bool Key::CreateAndOpen(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) - { - m_access = access; - LPDWORD disposition = {}; - LSTATUS status = RegCreateKeyExW(key, subKey.c_str(), 0, nullptr, options, access, NULL, &m_key, disposition); - - if (disposition == (LPDWORD)REG_CREATED_NEW_KEY) - { - AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was created."); - } - else if (disposition == (LPDWORD)REG_OPENED_EXISTING_KEY) - { - AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' already existed and was opened."); - } - - THROW_IF_WIN32_ERROR(status); - return true; - } - - bool Key::Initialize(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access, bool ignoreErrorIfDoesNotExist) - { - m_access = access; - LSTATUS status = RegOpenKeyExW(key, subKey.c_str(), options, access, &m_key); - - if (ignoreErrorIfDoesNotExist && status == ERROR_FILE_NOT_FOUND) - { - AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was not found"); - return false; - } - - THROW_IF_WIN32_ERROR(status); - return true; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/Registry.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerLogging.h" + + +namespace AppInstaller::Registry +{ + namespace + { + std::wstring_view ConvertBytesToWideStringView(const std::vector& data) + { + // Remove any extra bytes because the data could just be dirty; better to not have a bad character than outright fail. + std::wstring_view result{ reinterpret_cast(data.data()), data.size() / sizeof(wchar_t) }; + + // Registry values may or may not be null terminated; we will remove any trailing nulls + while (!result.empty() && result.back() == L'\0') + { + result = result.substr(0, result.size() - 1); + } + + return result; + } + + std::wstring ConvertBytesToWideString(const std::vector& data) + { + return std::wstring{ ConvertBytesToWideStringView(data) }; + } + + std::string ConvertBytesToString(const std::vector& data) + { + return Utility::ConvertToUTF8(ConvertBytesToWideStringView(data)); + } + + uint32_t ConvertBytesToUInt32LE(const std::vector& data) + { + THROW_HR_IF(E_NOT_VALID_STATE, data.size() != sizeof(uint32_t)); + uint32_t result = 0; + uint32_t shift = 0; + + for (const BYTE datum : data) + { + result |= ((static_cast(datum) & 0xFF) << shift); + shift += 8; + } + + return result; + } + + bool TryGetRegistryValueNameFromIndex(const wil::shared_hkey& key, DWORD index, std::wstring& valueName) + { + constexpr DWORD MaxNameLength = 32767; + LSTATUS status = ERROR_SUCCESS; + DWORD charCount = 0; + valueName = L'\0'; + + while (valueName.size() <= MaxNameLength) + { + charCount = wil::safe_cast(valueName.size()); + + // We could also get the type and data here, but we read only the name instead + // to prevent duplication with the code that gets the data from the name. + status = RegEnumValueW(key.get(), index, &valueName[0], &charCount, nullptr, nullptr, nullptr, nullptr); + + if (status == ERROR_MORE_DATA) + { + // See if we can get away with the current capacity + if (valueName.size() < valueName.capacity()) + { + valueName.resize(valueName.capacity()); + } + else + { + valueName.resize(valueName.capacity() * 2); + } + } + else + { + break; + } + } + + if (status == ERROR_SUCCESS) + { + valueName.resize(wil::safe_cast(charCount)); + return true; + } + else if (status == ERROR_NO_MORE_ITEMS) + { + return false; + } + else + { + THROW_IF_WIN32_ERROR(status); + return false; + } + } + + bool TryGetRegistryValueData(const wil::shared_hkey& key, const std::wstring& valueName, DWORD& type, std::vector& data) + { + data.resize(64); + + LSTATUS status = ERROR_SUCCESS; + DWORD byteCount = 0; + + while (data.size() < (64 << 20)) + { + byteCount = wil::safe_cast(data.size()); + status = RegGetValueW(key.get(), nullptr, valueName.c_str(), RRF_RT_ANY | RRF_NOEXPAND, &type, data.data(), &byteCount); + + if (status == ERROR_MORE_DATA && byteCount > data.size()) + { + data.resize(byteCount); + } + else + { + break; + } + } + + if (status == ERROR_FILE_NOT_FOUND) + { + return false; + } + + THROW_IF_WIN32_ERROR(status); + + // Resize to actual data size + data.resize(byteCount); + + return true; + } + + bool DeleteRegistryValueData(const wil::shared_hkey& key, const std::wstring& valueName) + { + LSTATUS status = RegDeleteValueW(key.get(), valueName.c_str()); + + if (status == ERROR_FILE_NOT_FOUND) + { + return false; + } + + THROW_IF_WIN32_ERROR(status); + + return true; + } + } + + namespace details + { + ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) + { + return data; + } + + ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) + { + return ConvertBytesToString(data); + } + + ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) + { + return ConvertBytesToWideString(data); + } + + ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) + { + return Utility::ConvertToUTF8(Utility::ExpandEnvironmentVariables(ConvertBytesToWideString(data))); + } + + ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) + { + return ConvertBytesToWideString(data); + } + + ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) + { + return data; + } + + ValueTypeSpecifics::value_t ValueTypeSpecifics::Convert(const std::vector& data) + { + return ConvertBytesToUInt32LE(data); + } + } + + Value::Value(DWORD type, std::vector&& data) : m_type(static_cast(type)), m_data(std::move(data)) + { + } + + bool Value::HasCompatibleType(Type type) const + { + // Allow interop between String and ExpandString + if ((m_type == Type::String || m_type == Type::ExpandString || m_type == Type::UTF16String || m_type == Type::UTF16ExpandString) && + (type == Type::String || type == Type::ExpandString || type == Type::UTF16String || type == Type::UTF16ExpandString)) + { + return true; + } + + return m_type == type; + } + + ValueList::ValueRef::ValueRef(wil::shared_hkey key, std::wstring&& valueName) : m_key(std::move(key)), m_valueName(std::move(valueName)) {} + + std::string ValueList::ValueRef::Name() const + { + return Utility::ConvertToUTF8(m_valueName); + } + + std::optional ValueList::ValueRef::Value() const + { + DWORD type; + std::vector data; + if (!TryGetRegistryValueData(m_key, m_valueName, type, data)) + { + return std::nullopt; + } + + return Registry::Value{ type, std::move(data) }; + } + + ValueList::const_iterator& ValueList::const_iterator::operator++() + { + ++m_index; + GetValue(); + return *this; + } + + ValueList::const_iterator ValueList::const_iterator::operator++(int) + { + const_iterator result; + result.m_key = m_key; + result.m_index = m_index++; + result.m_value = std::nullopt; + std::swap(m_value, result.m_value); + GetValue(); + return result; + } + + bool ValueList::const_iterator::operator==(const const_iterator& other) const + { + return (!m_key && !other.m_key) || (m_key.get() == other.m_key.get() && m_index == other.m_index); + } + + bool ValueList::const_iterator::operator!=(const const_iterator& other) const + { + return !operator==(other); + } + + void ValueList::const_iterator::GetValue() + { + std::wstring valueName; + if (!TryGetRegistryValueNameFromIndex(m_key, m_index, valueName)) + { + m_key.reset(); + return; + } + + m_value = ValueRef{ m_key, std::move(valueName) }; + } + + const ValueList::ValueRef& ValueList::const_iterator::operator*() const + { + return m_value.value(); + } + + const ValueList::ValueRef* ValueList::const_iterator::operator->() const + { + return &m_value.value(); + } + + ValueList::const_iterator::const_iterator(const wil::shared_hkey& key, DWORD index) : m_key(key), m_index(index) + { + GetValue(); + } + + ValueList::const_iterator ValueList::begin() const + { + return { m_key }; + } + + ValueList::const_iterator ValueList::end() const + { + return {}; + } + + ValueList::ValueList(wil::shared_hkey key) : m_key(key) {} + + Key::Key(HKEY key) + { + Initialize(key, {}, 0, KEY_READ, false); + } + + Key::Key(HKEY key, std::string_view subKey, DWORD options, REGSAM access) + { + Initialize(key, Utility::ConvertToUTF16(subKey), options, access, false); + } + + Key::Key(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) + { + Initialize(key, subKey, options, access, false); + } + + std::string Key::SubKeyRef::Name() const + { + return Utility::ConvertToUTF8(m_subKeyName); + } + + Key Key::SubKeyRef::Open() const + { + return { m_parentKey.get(), m_subKeyName, 0, m_access }; + } + + Key::SubKeyRef::SubKeyRef(const wil::shared_hkey& key, REGSAM access) : + m_parentKey(key), m_access(access), m_subKeyName(64, L'\0') + { + Enum(0); + } + + void Key::SubKeyRef::Enum(DWORD index) + { + LSTATUS status = ERROR_SUCCESS; + DWORD charCount = 0; + + while (m_subKeyName.size() < 4096) + { + charCount = wil::safe_cast(m_subKeyName.size()); + status = RegEnumKeyExW(m_parentKey.get(), index, &m_subKeyName[0], &charCount, nullptr, nullptr, nullptr, nullptr); + + if (status == ERROR_MORE_DATA) + { + // See if we can get away with the current capacity + if (m_subKeyName.size() < m_subKeyName.capacity()) + { + m_subKeyName.resize(m_subKeyName.capacity()); + } + else + { + m_subKeyName.resize(m_subKeyName.capacity() * 2); + } + } + else + { + break; + } + } + + if (status == ERROR_SUCCESS) + { + m_subKeyName.resize(wil::safe_cast(charCount)); + } + else if (status == ERROR_NO_MORE_ITEMS) + { + m_parentKey.reset(); + } + else + { + THROW_IF_WIN32_ERROR(status); + } + } + + Key::const_iterator& Key::const_iterator::operator++() + { + m_subkey.Enum(++m_index); + return *this; + } + + Key::const_iterator Key::const_iterator::operator++(int) + { + const_iterator result = *this; + m_subkey.Enum(++m_index); + return result; + } + + bool Key::const_iterator::operator==(const const_iterator& other) const + { + return (!m_subkey.m_parentKey && !other.m_subkey.m_parentKey) || (m_subkey.m_parentKey.get() == other.m_subkey.m_parentKey.get() && m_index == other.m_index); + } + + bool Key::const_iterator::operator!=(const const_iterator& other) const + { + return !operator==(other); + } + + const Key::SubKeyRef& Key::const_iterator::operator*() const + { + return m_subkey; + } + + const Key::SubKeyRef* Key::const_iterator::operator->() const + { + return &m_subkey; + } + + Key::const_iterator::const_iterator(const wil::shared_hkey& key, REGSAM access) : + m_subkey(key, access) + { + } + + Key::const_iterator Key::begin() const + { + return { m_key, m_access }; + } + + Key::const_iterator Key::end() const + { + return {}; + } + + std::optional Key::operator[](std::string_view name) const + { + return operator[](Utility::ConvertToUTF16(name)); + } + + std::optional Key::operator[](const std::wstring& name) const + { + DWORD type; + std::vector data; + + if (TryGetRegistryValueData(m_key, name, type, data)) + { + return Value{ type, std::move(data) }; + } + else + { + return {}; + } + } + + void Key::DeleteValue(std::string_view name) const + { + DeleteValue(Utility::ConvertToUTF16(name)); + } + + void Key::DeleteValue(const std::wstring& name) const + { + if (DeleteRegistryValueData(m_key, name)) + { + AICLI_LOG(Core, Verbose, << "Registry value '" << Utility::ConvertToUTF8(name) << "' deleted successfully."); + } + else + { + AICLI_LOG(Core, Verbose, << "Registry value '" << Utility::ConvertToUTF8(name) << "' does not exist."); + } + } + + std::optional Key::SubKey(std::string_view subKey, DWORD options) const + { + return SubKey(Utility::ConvertToUTF16(subKey), options); + } + + std::optional Key::SubKey(const std::wstring& subKey, DWORD options) const + { + if (!m_key) + { + return std::nullopt; + } + + Key result; + if (result.Initialize(m_key.get(), subKey, options, m_access, true)) + { + return result; + } + else + { + return std::nullopt; + } + } + + void Key::SetValue(const std::wstring& name, const std::wstring& value, DWORD type) const + { + THROW_IF_WIN32_ERROR(RegSetValueExW(m_key.get(), name.c_str(), 0, type, reinterpret_cast(value.c_str()), static_cast(sizeof(wchar_t) * (value.size() + 1)))); + AICLI_LOG(Core, Verbose, << "Setting '" << Utility::ConvertToUTF8(name) << "' with the value: " << Utility::ConvertToUTF8(value)); + } + + void Key::SetValue(const std::wstring& name, const std::vector& value, DWORD type) const + { + THROW_IF_WIN32_ERROR(RegSetValueExW(m_key.get(), name.c_str(), 0, type, reinterpret_cast(value.data()), static_cast(value.size()))); + AICLI_LOG(Core, Verbose, << "Setting '" << Utility::ConvertToUTF8(name) << "' with the value: " << ConvertBytesToString(value)); + + } + + void Key::SetValue(const std::wstring& name, DWORD value) const + { + THROW_IF_WIN32_ERROR(RegSetValueExW(m_key.get(), name.c_str(), 0, REG_DWORD, reinterpret_cast(&value), sizeof(DWORD))); + AICLI_LOG(Core, Verbose, << "Setting '" << Utility::ConvertToUTF8(name) << "' with the value: " << value); + + } + + ValueList Key::Values() const + { + return { m_key }; + } + + Key Key::OpenIfExists(HKEY key, std::string_view subKey, DWORD options, REGSAM access) + { + return OpenIfExists(key, Utility::ConvertToUTF16(subKey), options, access); + } + + Key Key::OpenIfExists(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) + { + Key result; + result.Initialize(key, subKey, options, access, true); + return result; + } + + Key Key::Create(HKEY key, std::string_view subKey, DWORD options, REGSAM access) + { + return Create(key, Utility::ConvertToUTF16(subKey), options, access); + } + + Key Key::Create(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) + { + Key result; + result.CreateAndOpen(key, subKey, options, access); + return result; + } + + bool Key::Delete(HKEY key, std::string_view subKey, DWORD samDesired) + { + return Delete(key, Utility::ConvertToUTF16(subKey), samDesired); + } + + bool Key::Delete(HKEY key, const std::wstring& subKey, DWORD samDesired) + { + LSTATUS status = RegDeleteKeyExW(key, subKey.c_str(), samDesired, 0); + if (status == ERROR_SUCCESS) + { + AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was deleted successfully."); + return true; + } + else if (status == ERROR_FILE_NOT_FOUND) + { + AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was not found."); + } + else + { + THROW_IF_WIN32_ERROR(status); + } + + return false; + } + + bool Key::DeleteTree(HKEY key, const std::wstring& subKey) + { + LSTATUS status = RegDeleteTree(key, subKey.c_str()); + if (status == ERROR_SUCCESS) + { + AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was deleted successfully."); + return true; + } + else if (status == ERROR_FILE_NOT_FOUND) + { + AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was not found."); + } + else + { + THROW_IF_WIN32_ERROR(status); + } + + return false; + } + + bool Key::CreateAndOpen(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access) + { + m_access = access; + LPDWORD disposition = {}; + LSTATUS status = RegCreateKeyExW(key, subKey.c_str(), 0, nullptr, options, access, NULL, &m_key, disposition); + + if (disposition == (LPDWORD)REG_CREATED_NEW_KEY) + { + AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was created."); + } + else if (disposition == (LPDWORD)REG_OPENED_EXISTING_KEY) + { + AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' already existed and was opened."); + } + + THROW_IF_WIN32_ERROR(status); + return true; + } + + bool Key::Initialize(HKEY key, const std::wstring& subKey, DWORD options, REGSAM access, bool ignoreErrorIfDoesNotExist) + { + m_access = access; + LSTATUS status = RegOpenKeyExW(key, subKey.c_str(), options, access, &m_key); + + if (ignoreErrorIfDoesNotExist && status == ERROR_FILE_NOT_FOUND) + { + AICLI_LOG(Core, Verbose, << "Subkey '" << Utility::ConvertToUTF8(subKey) << "' was not found"); + return false; + } + + THROW_IF_WIN32_ERROR(status); + return true; + } +} diff --git a/src/AppInstallerSharedLib/Resources.cpp b/src/AppInstallerSharedLib/Resources.cpp index 8924ae69d8..fa33d99cc4 100644 --- a/src/AppInstallerSharedLib/Resources.cpp +++ b/src/AppInstallerSharedLib/Resources.cpp @@ -1,152 +1,152 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Resources.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerStrings.h" -#include "Public/AppInstallerErrors.h" - -namespace AppInstaller -{ - namespace Resource - { - namespace - { - std::pair GetResourceData(PCWSTR resourceName, PCWSTR resourceType) - { - HMODULE resourceModule = nullptr; - GetModuleHandleExW( - GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, - reinterpret_cast(GetResourceData), - &resourceModule); - THROW_LAST_ERROR_IF_NULL(resourceModule); - - HRSRC resourceInfoHandle = FindResourceW(resourceModule, resourceName, resourceType); - THROW_LAST_ERROR_IF_NULL(resourceInfoHandle); - - HGLOBAL resourceMemoryHandle = LoadResource(resourceModule, resourceInfoHandle); - THROW_LAST_ERROR_IF_NULL(resourceMemoryHandle); - - DWORD resourceSize = SizeofResource(resourceModule, resourceInfoHandle); - THROW_LAST_ERROR_IF(resourceSize == 0); - - void* resourceContent = LockResource(resourceMemoryHandle); - THROW_HR_IF_NULL(E_UNEXPECTED, resourceContent); - - return std::make_pair(resourceContent, static_cast(resourceSize)); - } - } - - std::string_view GetResourceAsString(int resourceName, int resourceType) - { - return GetResourceAsString(MAKEINTRESOURCE(resourceName), MAKEINTRESOURCE(resourceType)); - } - - std::string_view GetResourceAsString(PCWSTR resourceName, PCWSTR resourceType) - { - auto resourceData = GetResourceData(resourceName, resourceType); - return { reinterpret_cast(resourceData.first), resourceData.second }; - } - - std::pair GetResourceAsBytes(int resourceName, int resourceType) - { - return GetResourceAsBytes(MAKEINTRESOURCE(resourceName), MAKEINTRESOURCE(resourceType)); - } - - std::pair GetResourceAsBytes(PCWSTR resourceName, PCWSTR resourceType) - { - auto resourceData = GetResourceData(resourceName, resourceType); - return std::make_pair(reinterpret_cast(resourceData.first), resourceData.second); - } - - // Utility class to load resources - struct Loader - { - // Gets the singleton instance of the resource loader. - static const Loader& Instance() - { - static Loader instance; - return instance; - } - - // Gets the string resource value. - std::string ResolveString(std::wstring_view resKey) const - { - if (resKey.empty()) - { - return {}; - } - - if (m_wingetLoader) - { - return Utility::ConvertToUTF8(m_wingetLoader.GetString(resKey)); - } - - // Loader failed to load resource file, print the resource key instead. - return Utility::ConvertToUTF8(resKey); - } - - // Gets the string resource value or nothing if not present. - std::optional TryResolveString(std::wstring_view resKey) const - { - if (!resKey.empty() && m_wingetLoader) - { - try - { - winrt::hstring result = m_wingetLoader.GetString(resKey); - - if (!result.empty()) - { - return Resource::LocString{ Utility::LocIndString{ Utility::ConvertToUTF8(result) } }; - } - } - CATCH_LOG() - } - - return {}; - } - - private: - winrt::Windows::ApplicationModel::Resources::ResourceLoader m_wingetLoader; - - Loader() : m_wingetLoader(nullptr) - { - try - { - // The default constructor of ResourceLoader throws a winrt::hresult_error exception - // when resource.pri is not found. ResourceLoader::GetForViewIndependentUse also throws - // a winrt::hresult_error but for reasons unknown it only gets caught when running on the - // debugger. Running without a debugger will result in a crash that not even adding a - // catch all will fix. To provide a good error message we call the default constructor - // before calling GetForViewIndependentUse. - m_wingetLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader(); - m_wingetLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader::GetForViewIndependentUse(L"winget"); - } - catch (const winrt::hresult_error& hre) - { - // This message cannot be localized. - AICLI_LOG(CLI, Error, << "Failure loading resource file with error: " << hre.code()); - m_wingetLoader = nullptr; - } - } - }; - } - - namespace StringResource - { - std::string StringId::Resolve() const - { - return Resource::Loader::Instance().ResolveString(*this); - } - - std::ostream& operator<<(std::ostream& out, StringId si) - { - return (out << Resource::LocString{ si }); - } - - std::optional TryResolveString(std::wstring_view resKey) - { - return Resource::Loader::Instance().TryResolveString(resKey); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Resources.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerStrings.h" +#include "Public/AppInstallerErrors.h" + +namespace AppInstaller +{ + namespace Resource + { + namespace + { + std::pair GetResourceData(PCWSTR resourceName, PCWSTR resourceType) + { + HMODULE resourceModule = nullptr; + GetModuleHandleExW( + GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast(GetResourceData), + &resourceModule); + THROW_LAST_ERROR_IF_NULL(resourceModule); + + HRSRC resourceInfoHandle = FindResourceW(resourceModule, resourceName, resourceType); + THROW_LAST_ERROR_IF_NULL(resourceInfoHandle); + + HGLOBAL resourceMemoryHandle = LoadResource(resourceModule, resourceInfoHandle); + THROW_LAST_ERROR_IF_NULL(resourceMemoryHandle); + + DWORD resourceSize = SizeofResource(resourceModule, resourceInfoHandle); + THROW_LAST_ERROR_IF(resourceSize == 0); + + void* resourceContent = LockResource(resourceMemoryHandle); + THROW_HR_IF_NULL(E_UNEXPECTED, resourceContent); + + return std::make_pair(resourceContent, static_cast(resourceSize)); + } + } + + std::string_view GetResourceAsString(int resourceName, int resourceType) + { + return GetResourceAsString(MAKEINTRESOURCE(resourceName), MAKEINTRESOURCE(resourceType)); + } + + std::string_view GetResourceAsString(PCWSTR resourceName, PCWSTR resourceType) + { + auto resourceData = GetResourceData(resourceName, resourceType); + return { reinterpret_cast(resourceData.first), resourceData.second }; + } + + std::pair GetResourceAsBytes(int resourceName, int resourceType) + { + return GetResourceAsBytes(MAKEINTRESOURCE(resourceName), MAKEINTRESOURCE(resourceType)); + } + + std::pair GetResourceAsBytes(PCWSTR resourceName, PCWSTR resourceType) + { + auto resourceData = GetResourceData(resourceName, resourceType); + return std::make_pair(reinterpret_cast(resourceData.first), resourceData.second); + } + + // Utility class to load resources + struct Loader + { + // Gets the singleton instance of the resource loader. + static const Loader& Instance() + { + static Loader instance; + return instance; + } + + // Gets the string resource value. + std::string ResolveString(std::wstring_view resKey) const + { + if (resKey.empty()) + { + return {}; + } + + if (m_wingetLoader) + { + return Utility::ConvertToUTF8(m_wingetLoader.GetString(resKey)); + } + + // Loader failed to load resource file, print the resource key instead. + return Utility::ConvertToUTF8(resKey); + } + + // Gets the string resource value or nothing if not present. + std::optional TryResolveString(std::wstring_view resKey) const + { + if (!resKey.empty() && m_wingetLoader) + { + try + { + winrt::hstring result = m_wingetLoader.GetString(resKey); + + if (!result.empty()) + { + return Resource::LocString{ Utility::LocIndString{ Utility::ConvertToUTF8(result) } }; + } + } + CATCH_LOG() + } + + return {}; + } + + private: + winrt::Windows::ApplicationModel::Resources::ResourceLoader m_wingetLoader; + + Loader() : m_wingetLoader(nullptr) + { + try + { + // The default constructor of ResourceLoader throws a winrt::hresult_error exception + // when resource.pri is not found. ResourceLoader::GetForViewIndependentUse also throws + // a winrt::hresult_error but for reasons unknown it only gets caught when running on the + // debugger. Running without a debugger will result in a crash that not even adding a + // catch all will fix. To provide a good error message we call the default constructor + // before calling GetForViewIndependentUse. + m_wingetLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader(); + m_wingetLoader = winrt::Windows::ApplicationModel::Resources::ResourceLoader::GetForViewIndependentUse(L"winget"); + } + catch (const winrt::hresult_error& hre) + { + // This message cannot be localized. + AICLI_LOG(CLI, Error, << "Failure loading resource file with error: " << hre.code()); + m_wingetLoader = nullptr; + } + } + }; + } + + namespace StringResource + { + std::string StringId::Resolve() const + { + return Resource::Loader::Instance().ResolveString(*this); + } + + std::ostream& operator<<(std::ostream& out, StringId si) + { + return (out << Resource::LocString{ si }); + } + + std::optional TryResolveString(std::wstring_view resKey) + { + return Resource::Loader::Instance().TryResolveString(resKey); + } + } +} diff --git a/src/AppInstallerSharedLib/Runtime.cpp b/src/AppInstallerSharedLib/Runtime.cpp index c9f8665486..39096981fd 100644 --- a/src/AppInstallerSharedLib/Runtime.cpp +++ b/src/AppInstallerSharedLib/Runtime.cpp @@ -1,238 +1,238 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include "Public/winget/Runtime.h" -#include "Public/AppInstallerLogging.h" -#include "Public/AppInstallerStrings.h" - - -namespace AppInstaller::Runtime -{ - using namespace Utility; - - namespace - { - using namespace std::string_view_literals; - constexpr std::string_view s_PreviewBuildSuffix = "-preview"sv; - - // Gets a boolean indicating whether the current process has identity. - bool DoesCurrentProcessHaveIdentity() - { - UINT32 length = 0; - LONG result = ::GetPackageFamilyName(GetCurrentProcess(), &length, nullptr); - return (result != APPMODEL_ERROR_NO_PACKAGE); - } - - std::unique_ptr GetPACKAGE_ID() - { - UINT32 bufferLength = 0; - LONG gcpiResult = GetCurrentPackageId(&bufferLength, nullptr); - THROW_HR_IF(E_UNEXPECTED, gcpiResult != ERROR_INSUFFICIENT_BUFFER); - - std::unique_ptr buffer = std::make_unique(bufferLength); - - gcpiResult = GetCurrentPackageId(&bufferLength, buffer.get()); - if (FAILED_WIN32_LOG(gcpiResult)) - { - return {}; - } - - return buffer; - } - - // Gets the package name; only succeeds if running in a packaged context. - std::string GetPackageName() - { - std::unique_ptr buffer = GetPACKAGE_ID(); - if (!buffer) - { - return {}; - } - - PACKAGE_ID* packageId = reinterpret_cast(buffer.get()); - return Utility::ConvertToUTF8(packageId->name); - } - - // Gets the package version; only succeeds if running in a packaged context. - std::optional GetPACKAGE_VERSION() - { - std::unique_ptr buffer = GetPACKAGE_ID(); - if (!buffer) - { - return {}; - } - - PACKAGE_ID* packageId = reinterpret_cast(buffer.get()); - return packageId->version; - } - } - - bool IsRunningInPackagedContext() - { - static bool result = DoesCurrentProcessHaveIdentity(); - return result; - } - - LocIndString GetClientVersion() - { - std::ostringstream strstr; - strstr << VERSION_MAJOR << '.' << VERSION_MINOR << '.' << VERSION_BUILD; - - if (!IsReleaseBuild()) - { - strstr << s_PreviewBuildSuffix; - } - - return LocIndString{ strstr.str() }; - } - - std::wstring GetPackageFamilyName() - { - UINT32 length = 0; - LONG returnValue = ::GetPackageFamilyName(GetCurrentProcess(), &length, nullptr); - - if (returnValue == APPMODEL_ERROR_NO_PACKAGE) - { - return {}; - } - - if (returnValue != ERROR_INSUFFICIENT_BUFFER) - { - THROW_IF_WIN32_ERROR(returnValue); - } - - std::wstring result(length, '\0'); - returnValue = ::GetPackageFamilyName(GetCurrentProcess(), &length, &result[0]); - THROW_IF_WIN32_ERROR(returnValue); - THROW_HR_IF(E_UNEXPECTED, length == 0); - - result.resize(length - 1); - return result; - } - - LocIndString GetPackageVersion() - { - using namespace std::string_literals; - - if (IsRunningInPackagedContext()) - { - auto version = GetPACKAGE_VERSION(); - - if (!version) - { - // In the extremely unlikely event of a failure, this is merely a sentinel value - // to indicated such. The only other option is to completely prevent execution, - // which seems unnecessary. - return LocIndString{ "error"sv }; - } - - std::ostringstream strstr; - strstr << GetPackageName() << " v" << version->Major << '.' << version->Minor << '.' << version->Build << '.' << version->Revision; - - return LocIndString{ strstr.str() }; - } - else - { - // Calling code should avoid calling in when this is the case. - return LocIndString{ "none"sv }; - } - } - - LocIndString GetOSVersion() - { - winrt::Windows::System::Profile::AnalyticsInfo analyticsInfo{}; - auto versionInfo = analyticsInfo.VersionInfo(); - - uint64_t version = std::stoull(Utility::ConvertToUTF8(versionInfo.DeviceFamilyVersion())); - uint16_t parts[4]; - - for (size_t i = 0; i < ARRAYSIZE(parts); ++i) - { - parts[i] = version & 0xFFFF; - version = version >> 16; - } - - std::ostringstream strstr; - strstr << Utility::ConvertToUTF8(versionInfo.DeviceFamily()) << " v" << parts[3] << '.' << parts[2] << '.' << parts[1] << '.' << parts[0]; - - return LocIndString{ strstr.str() }; - } - - std::string GetOSRegion() - { - winrt::Windows::Globalization::GeographicRegion region; - return Utility::ConvertToUTF8(region.CodeTwoLetter()); - } - - bool IsCurrentOSVersionGreaterThanOrEqual(const Utility::Version& version) - { - DWORD versionParts[3] = {}; - - for (size_t i = 0; i < ARRAYSIZE(versionParts) && i < version.GetParts().size(); ++i) - { - versionParts[i] = static_cast(std::min(static_cast(std::numeric_limits::max()), version.GetParts()[i].Integer)); - } - - OSVERSIONINFOEXW osVersionInfo{}; - osVersionInfo.dwOSVersionInfoSize = sizeof(osVersionInfo); - osVersionInfo.dwMajorVersion = versionParts[0]; - osVersionInfo.dwMinorVersion = versionParts[1]; - osVersionInfo.dwBuildNumber = versionParts[2]; - osVersionInfo.wServicePackMajor = 0; - osVersionInfo.wServicePackMinor = 0; - - DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR; - - DWORDLONG conditions = 0; - VER_SET_CONDITION(conditions, VER_MAJORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(conditions, VER_MINORVERSION, VER_GREATER_EQUAL); - VER_SET_CONDITION(conditions, VER_BUILDNUMBER, VER_GREATER_EQUAL); - VER_SET_CONDITION(conditions, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); - VER_SET_CONDITION(conditions, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); - - BOOL result = VerifyVersionInfoW(&osVersionInfo, mask, conditions); - if (!result) - { - THROW_LAST_ERROR_IF(GetLastError() != ERROR_OLD_WIN_VERSION); - } - return !!result; - } - - bool IsRunningAsAdmin() - { - return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); - } - - bool IsRunningAsSystem() - { - return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); - } - - bool IsRunningAsAdminOrSystem() - { - return IsRunningAsAdmin() || IsRunningAsSystem(); - } - - bool IsRunningWithLimitedToken() - { - return wil::get_token_information() == TokenElevationTypeLimited; - } - - DECLSPEC_NOINLINE bool IsStackAvailable(size_t bytes) - { - // https://devblogs.microsoft.com/oldnewthing/20200610-00/?p=103855 - ULONG_PTR low, high; - GetCurrentThreadStackLimits(&low, &high); - auto remaining = reinterpret_cast(&low) - low; - if (remaining > high - low) - { - // Choosing to return false instead of failing - return false; - } - - ULONG guarantee = 0; - SetThreadStackGuarantee(&guarantee); - return remaining >= bytes + guarantee; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "Public/winget/Runtime.h" +#include "Public/AppInstallerLogging.h" +#include "Public/AppInstallerStrings.h" + + +namespace AppInstaller::Runtime +{ + using namespace Utility; + + namespace + { + using namespace std::string_view_literals; + constexpr std::string_view s_PreviewBuildSuffix = "-preview"sv; + + // Gets a boolean indicating whether the current process has identity. + bool DoesCurrentProcessHaveIdentity() + { + UINT32 length = 0; + LONG result = ::GetPackageFamilyName(GetCurrentProcess(), &length, nullptr); + return (result != APPMODEL_ERROR_NO_PACKAGE); + } + + std::unique_ptr GetPACKAGE_ID() + { + UINT32 bufferLength = 0; + LONG gcpiResult = GetCurrentPackageId(&bufferLength, nullptr); + THROW_HR_IF(E_UNEXPECTED, gcpiResult != ERROR_INSUFFICIENT_BUFFER); + + std::unique_ptr buffer = std::make_unique(bufferLength); + + gcpiResult = GetCurrentPackageId(&bufferLength, buffer.get()); + if (FAILED_WIN32_LOG(gcpiResult)) + { + return {}; + } + + return buffer; + } + + // Gets the package name; only succeeds if running in a packaged context. + std::string GetPackageName() + { + std::unique_ptr buffer = GetPACKAGE_ID(); + if (!buffer) + { + return {}; + } + + PACKAGE_ID* packageId = reinterpret_cast(buffer.get()); + return Utility::ConvertToUTF8(packageId->name); + } + + // Gets the package version; only succeeds if running in a packaged context. + std::optional GetPACKAGE_VERSION() + { + std::unique_ptr buffer = GetPACKAGE_ID(); + if (!buffer) + { + return {}; + } + + PACKAGE_ID* packageId = reinterpret_cast(buffer.get()); + return packageId->version; + } + } + + bool IsRunningInPackagedContext() + { + static bool result = DoesCurrentProcessHaveIdentity(); + return result; + } + + LocIndString GetClientVersion() + { + std::ostringstream strstr; + strstr << VERSION_MAJOR << '.' << VERSION_MINOR << '.' << VERSION_BUILD; + + if (!IsReleaseBuild()) + { + strstr << s_PreviewBuildSuffix; + } + + return LocIndString{ strstr.str() }; + } + + std::wstring GetPackageFamilyName() + { + UINT32 length = 0; + LONG returnValue = ::GetPackageFamilyName(GetCurrentProcess(), &length, nullptr); + + if (returnValue == APPMODEL_ERROR_NO_PACKAGE) + { + return {}; + } + + if (returnValue != ERROR_INSUFFICIENT_BUFFER) + { + THROW_IF_WIN32_ERROR(returnValue); + } + + std::wstring result(length, '\0'); + returnValue = ::GetPackageFamilyName(GetCurrentProcess(), &length, &result[0]); + THROW_IF_WIN32_ERROR(returnValue); + THROW_HR_IF(E_UNEXPECTED, length == 0); + + result.resize(length - 1); + return result; + } + + LocIndString GetPackageVersion() + { + using namespace std::string_literals; + + if (IsRunningInPackagedContext()) + { + auto version = GetPACKAGE_VERSION(); + + if (!version) + { + // In the extremely unlikely event of a failure, this is merely a sentinel value + // to indicated such. The only other option is to completely prevent execution, + // which seems unnecessary. + return LocIndString{ "error"sv }; + } + + std::ostringstream strstr; + strstr << GetPackageName() << " v" << version->Major << '.' << version->Minor << '.' << version->Build << '.' << version->Revision; + + return LocIndString{ strstr.str() }; + } + else + { + // Calling code should avoid calling in when this is the case. + return LocIndString{ "none"sv }; + } + } + + LocIndString GetOSVersion() + { + winrt::Windows::System::Profile::AnalyticsInfo analyticsInfo{}; + auto versionInfo = analyticsInfo.VersionInfo(); + + uint64_t version = std::stoull(Utility::ConvertToUTF8(versionInfo.DeviceFamilyVersion())); + uint16_t parts[4]; + + for (size_t i = 0; i < ARRAYSIZE(parts); ++i) + { + parts[i] = version & 0xFFFF; + version = version >> 16; + } + + std::ostringstream strstr; + strstr << Utility::ConvertToUTF8(versionInfo.DeviceFamily()) << " v" << parts[3] << '.' << parts[2] << '.' << parts[1] << '.' << parts[0]; + + return LocIndString{ strstr.str() }; + } + + std::string GetOSRegion() + { + winrt::Windows::Globalization::GeographicRegion region; + return Utility::ConvertToUTF8(region.CodeTwoLetter()); + } + + bool IsCurrentOSVersionGreaterThanOrEqual(const Utility::Version& version) + { + DWORD versionParts[3] = {}; + + for (size_t i = 0; i < ARRAYSIZE(versionParts) && i < version.GetParts().size(); ++i) + { + versionParts[i] = static_cast(std::min(static_cast(std::numeric_limits::max()), version.GetParts()[i].Integer)); + } + + OSVERSIONINFOEXW osVersionInfo{}; + osVersionInfo.dwOSVersionInfoSize = sizeof(osVersionInfo); + osVersionInfo.dwMajorVersion = versionParts[0]; + osVersionInfo.dwMinorVersion = versionParts[1]; + osVersionInfo.dwBuildNumber = versionParts[2]; + osVersionInfo.wServicePackMajor = 0; + osVersionInfo.wServicePackMinor = 0; + + DWORD mask = VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER | VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR; + + DWORDLONG conditions = 0; + VER_SET_CONDITION(conditions, VER_MAJORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(conditions, VER_MINORVERSION, VER_GREATER_EQUAL); + VER_SET_CONDITION(conditions, VER_BUILDNUMBER, VER_GREATER_EQUAL); + VER_SET_CONDITION(conditions, VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + VER_SET_CONDITION(conditions, VER_SERVICEPACKMINOR, VER_GREATER_EQUAL); + + BOOL result = VerifyVersionInfoW(&osVersionInfo, mask, conditions); + if (!result) + { + THROW_LAST_ERROR_IF(GetLastError() != ERROR_OLD_WIN_VERSION); + } + return !!result; + } + + bool IsRunningAsAdmin() + { + return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_BUILTIN_DOMAIN_RID, DOMAIN_ALIAS_RID_ADMINS); + } + + bool IsRunningAsSystem() + { + return wil::test_token_membership(nullptr, SECURITY_NT_AUTHORITY, SECURITY_LOCAL_SYSTEM_RID); + } + + bool IsRunningAsAdminOrSystem() + { + return IsRunningAsAdmin() || IsRunningAsSystem(); + } + + bool IsRunningWithLimitedToken() + { + return wil::get_token_information() == TokenElevationTypeLimited; + } + + DECLSPEC_NOINLINE bool IsStackAvailable(size_t bytes) + { + // https://devblogs.microsoft.com/oldnewthing/20200610-00/?p=103855 + ULONG_PTR low, high; + GetCurrentThreadStackLimits(&low, &high); + auto remaining = reinterpret_cast(&low) - low; + if (remaining > high - low) + { + // Choosing to return false instead of failing + return false; + } + + ULONG guarantee = 0; + SetThreadStackGuarantee(&guarantee); + return remaining >= bytes + guarantee; + } +} diff --git a/src/AppInstallerSharedLib/SHA256.cpp b/src/AppInstallerSharedLib/SHA256.cpp index ce71e0f77b..2c81c08199 100644 --- a/src/AppInstallerSharedLib/SHA256.cpp +++ b/src/AppInstallerSharedLib/SHA256.cpp @@ -1,210 +1,210 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include -#define WIN32_NO_STATUS -#include -#include "Public/AppInstallerSHA256.h" -#include "Public/AppInstallerErrors.h" -#include "Public/AppInstallerStrings.h" - -namespace AppInstaller::Utility { - - struct SHA256Context - { - wil::unique_bcrypt_algorithm algHandle; - wil::unique_bcrypt_hash hashHandle; - DWORD hashLength = 0; - }; - - SHA256::SHA256() : context(new SHA256Context{}) - { - BCRYPT_ALG_HANDLE algHandleT{}; - BCRYPT_HASH_HANDLE hashHandleT; - DWORD resultLength = 0; - - // Open an algorithm handle - THROW_IF_NTSTATUS_FAILED_MSG(BCryptOpenAlgorithmProvider( - &algHandleT, // Alg Handle pointer - BCRYPT_SHA256_ALGORITHM, // Cryptographic Algorithm name (null terminated unicode string) - nullptr, // Provider name; if null, the default provider is loaded - 0), // Flags - "failed opening SHA256 algorithm provider"); - context->algHandle.reset(algHandleT); - - // Obtain the length of the hash - THROW_IF_NTSTATUS_FAILED_MSG(BCryptGetProperty( - context->algHandle.get(), // Handle to a CNG object - BCRYPT_HASH_LENGTH, // Property name (null terminated unicode string) - (PBYTE) & (context->hashLength), // Address of the output buffer which receives the property value - sizeof(context->hashLength), // Size of the buffer in bytes - &resultLength, // Number of bytes that were copied into the buffer - 0), // Flags - "failed getting SHA256 hash length"); - - if (resultLength != sizeof(context->hashLength)) - { - THROW_HR_MSG(E_UNEXPECTED, "failed getting SHA256 hash length"); - } - - // Create a hash handle - THROW_IF_NTSTATUS_FAILED_MSG(BCryptCreateHash( - context->algHandle.get(), // Handle to an algorithm provider - &hashHandleT, // A pointer to a hash handle - can be a hash or hmac object - nullptr, // Pointer to the buffer that receives the hash/hmac object - 0, // Size of the buffer in bytes - nullptr, // A pointer to a key to use for the hash or MAC - 0, // Size of the key in bytes - 0), // Flags - "failed creating SHA256 hash object"); - context->hashHandle.reset(hashHandleT); - } - - void SHA256::Add(const uint8_t* buffer, size_t cbBuffer) - { - EnsureNotFinished(); - - // Add the data - THROW_IF_NTSTATUS_FAILED_MSG( - BCryptHashData(context->hashHandle.get(), const_cast(buffer), static_cast(cbBuffer), 0), - "failed adding SHA256 data"); - } - - void SHA256::Get(HashBuffer& hash) - { - EnsureNotFinished(); - - // Size the hash buffer appropriately - hash.resize(context->hashLength); - - // Obtain the hash of the message(s) into the hash buffer - THROW_IF_NTSTATUS_FAILED_MSG(BCryptFinishHash( - context->hashHandle.get(), // Handle to the hash or MAC object - hash.data(), // A pointer to a buffer that receives the hash or MAC value - context->hashLength, // Size of the buffer in bytes - 0), // Flags - "failed getting SHA256 hash"); - - context.reset(); - } - - std::string SHA256::ConvertToString(const HashBuffer& hashBuffer) - { - return Utility::ConvertToHexString(hashBuffer, HashBufferSizeInBytes); - } - - std::wstring SHA256::ConvertToWideString(const HashBuffer& hashBuffer) - { - return ConvertToUTF16(SHA256::ConvertToString(hashBuffer)); - } - - SHA256::HashBuffer SHA256::ConvertToBytes(const std::string& hashStr) - { - return Utility::ParseFromHexString(hashStr, HashBufferSizeInBytes); - } - - SHA256::HashBuffer SHA256::ConvertToBytes(const std::wstring& hashStr) - { - return Utility::ParseFromHexString(Utility::ConvertToUTF8(hashStr), HashBufferSizeInBytes); - } - - SHA256::HashBuffer SHA256::ComputeHash(const std::uint8_t* buffer, std::uint32_t cbBuffer) - { - SHA256 hasher; - hasher.Add(buffer, cbBuffer); - return hasher.Get(); - } - - SHA256::HashBuffer SHA256::ComputeHash(const std::vector& buffer) - { - THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), buffer.size() > std::numeric_limits::max()); - return ComputeHash(buffer.data(), static_cast(buffer.size())); - } - - SHA256::HashBuffer SHA256::ComputeHash(std::string_view buffer) - { - return ComputeHash(reinterpret_cast(buffer.data()), static_cast(buffer.size())); - } - - SHA256::HashBuffer SHA256::ComputeHash(std::istream& in) - { - return ComputeHashDetails(in).Hash; - } - - SHA256::HashDetails SHA256::ComputeHashDetails(std::istream& in) - { - // Throw exceptions on badbit - auto excState = in.exceptions(); - auto revertExcState = wil::scope_exit([excState, &in]() { in.exceptions(excState); }); - in.exceptions(std::ios_base::badbit); - - const int bufferSize = 1024 * 1024; // 1MB - auto buffer = std::make_unique(bufferSize); - - SHA256 hasher; - uint64_t totalSize = 0; - - while (in.good()) - { - in.read((char*)(buffer.get()), bufferSize); - std::streamsize bytesRead = in.gcount(); - if (bytesRead) - { - hasher.Add(buffer.get(), static_cast(bytesRead)); - totalSize += static_cast(bytesRead); - } - } - - if (in.eof()) - { - HashDetails result; - result.Hash = hasher.Get(); - result.SizeInBytes = totalSize; - return result; - } - else - { - THROW_HR(APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE); - } - } - - SHA256::HashBuffer SHA256::ComputeHashFromFile(const std::filesystem::path& path) - { - std::ifstream inStream{ path, std::ifstream::binary }; - const Utility::SHA256::HashBuffer& targetFileHash = Utility::SHA256::ComputeHash(inStream); - inStream.close(); - return targetFileHash; - } - - SHA256::HashBuffer SHA256::ComputeHashFromHandle(HANDLE fileHandle) - { - constexpr DWORD bufferSize = 1024 * 1024; - auto buffer = std::make_unique(bufferSize); - SHA256 hasher; - DWORD bytesRead = 0; - - while (ReadFile(fileHandle, buffer.get(), bufferSize, &bytesRead, nullptr) && bytesRead > 0) - { - hasher.Add(buffer.get(), bytesRead); - } - - return hasher.Get(); - } - - void SHA256::SHA256ContextDeleter::operator()(SHA256Context* context) - { - delete context; - } - - bool SHA256::AreEqual(const HashBuffer& first, const HashBuffer& second) - { - return (first.size() == second.size() && std::equal(first.begin(), first.end(), second.begin())); - } - - void SHA256::EnsureNotFinished() const - { - if (!context) - { - THROW_HR_MSG(E_UNEXPECTED, "The hash is already finished"); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include +#define WIN32_NO_STATUS +#include +#include "Public/AppInstallerSHA256.h" +#include "Public/AppInstallerErrors.h" +#include "Public/AppInstallerStrings.h" + +namespace AppInstaller::Utility { + + struct SHA256Context + { + wil::unique_bcrypt_algorithm algHandle; + wil::unique_bcrypt_hash hashHandle; + DWORD hashLength = 0; + }; + + SHA256::SHA256() : context(new SHA256Context{}) + { + BCRYPT_ALG_HANDLE algHandleT{}; + BCRYPT_HASH_HANDLE hashHandleT; + DWORD resultLength = 0; + + // Open an algorithm handle + THROW_IF_NTSTATUS_FAILED_MSG(BCryptOpenAlgorithmProvider( + &algHandleT, // Alg Handle pointer + BCRYPT_SHA256_ALGORITHM, // Cryptographic Algorithm name (null terminated unicode string) + nullptr, // Provider name; if null, the default provider is loaded + 0), // Flags + "failed opening SHA256 algorithm provider"); + context->algHandle.reset(algHandleT); + + // Obtain the length of the hash + THROW_IF_NTSTATUS_FAILED_MSG(BCryptGetProperty( + context->algHandle.get(), // Handle to a CNG object + BCRYPT_HASH_LENGTH, // Property name (null terminated unicode string) + (PBYTE) & (context->hashLength), // Address of the output buffer which receives the property value + sizeof(context->hashLength), // Size of the buffer in bytes + &resultLength, // Number of bytes that were copied into the buffer + 0), // Flags + "failed getting SHA256 hash length"); + + if (resultLength != sizeof(context->hashLength)) + { + THROW_HR_MSG(E_UNEXPECTED, "failed getting SHA256 hash length"); + } + + // Create a hash handle + THROW_IF_NTSTATUS_FAILED_MSG(BCryptCreateHash( + context->algHandle.get(), // Handle to an algorithm provider + &hashHandleT, // A pointer to a hash handle - can be a hash or hmac object + nullptr, // Pointer to the buffer that receives the hash/hmac object + 0, // Size of the buffer in bytes + nullptr, // A pointer to a key to use for the hash or MAC + 0, // Size of the key in bytes + 0), // Flags + "failed creating SHA256 hash object"); + context->hashHandle.reset(hashHandleT); + } + + void SHA256::Add(const uint8_t* buffer, size_t cbBuffer) + { + EnsureNotFinished(); + + // Add the data + THROW_IF_NTSTATUS_FAILED_MSG( + BCryptHashData(context->hashHandle.get(), const_cast(buffer), static_cast(cbBuffer), 0), + "failed adding SHA256 data"); + } + + void SHA256::Get(HashBuffer& hash) + { + EnsureNotFinished(); + + // Size the hash buffer appropriately + hash.resize(context->hashLength); + + // Obtain the hash of the message(s) into the hash buffer + THROW_IF_NTSTATUS_FAILED_MSG(BCryptFinishHash( + context->hashHandle.get(), // Handle to the hash or MAC object + hash.data(), // A pointer to a buffer that receives the hash or MAC value + context->hashLength, // Size of the buffer in bytes + 0), // Flags + "failed getting SHA256 hash"); + + context.reset(); + } + + std::string SHA256::ConvertToString(const HashBuffer& hashBuffer) + { + return Utility::ConvertToHexString(hashBuffer, HashBufferSizeInBytes); + } + + std::wstring SHA256::ConvertToWideString(const HashBuffer& hashBuffer) + { + return ConvertToUTF16(SHA256::ConvertToString(hashBuffer)); + } + + SHA256::HashBuffer SHA256::ConvertToBytes(const std::string& hashStr) + { + return Utility::ParseFromHexString(hashStr, HashBufferSizeInBytes); + } + + SHA256::HashBuffer SHA256::ConvertToBytes(const std::wstring& hashStr) + { + return Utility::ParseFromHexString(Utility::ConvertToUTF8(hashStr), HashBufferSizeInBytes); + } + + SHA256::HashBuffer SHA256::ComputeHash(const std::uint8_t* buffer, std::uint32_t cbBuffer) + { + SHA256 hasher; + hasher.Add(buffer, cbBuffer); + return hasher.Get(); + } + + SHA256::HashBuffer SHA256::ComputeHash(const std::vector& buffer) + { + THROW_HR_IF(HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER), buffer.size() > std::numeric_limits::max()); + return ComputeHash(buffer.data(), static_cast(buffer.size())); + } + + SHA256::HashBuffer SHA256::ComputeHash(std::string_view buffer) + { + return ComputeHash(reinterpret_cast(buffer.data()), static_cast(buffer.size())); + } + + SHA256::HashBuffer SHA256::ComputeHash(std::istream& in) + { + return ComputeHashDetails(in).Hash; + } + + SHA256::HashDetails SHA256::ComputeHashDetails(std::istream& in) + { + // Throw exceptions on badbit + auto excState = in.exceptions(); + auto revertExcState = wil::scope_exit([excState, &in]() { in.exceptions(excState); }); + in.exceptions(std::ios_base::badbit); + + const int bufferSize = 1024 * 1024; // 1MB + auto buffer = std::make_unique(bufferSize); + + SHA256 hasher; + uint64_t totalSize = 0; + + while (in.good()) + { + in.read((char*)(buffer.get()), bufferSize); + std::streamsize bytesRead = in.gcount(); + if (bytesRead) + { + hasher.Add(buffer.get(), static_cast(bytesRead)); + totalSize += static_cast(bytesRead); + } + } + + if (in.eof()) + { + HashDetails result; + result.Hash = hasher.Get(); + result.SizeInBytes = totalSize; + return result; + } + else + { + THROW_HR(APPINSTALLER_CLI_ERROR_STREAM_READ_FAILURE); + } + } + + SHA256::HashBuffer SHA256::ComputeHashFromFile(const std::filesystem::path& path) + { + std::ifstream inStream{ path, std::ifstream::binary }; + const Utility::SHA256::HashBuffer& targetFileHash = Utility::SHA256::ComputeHash(inStream); + inStream.close(); + return targetFileHash; + } + + SHA256::HashBuffer SHA256::ComputeHashFromHandle(HANDLE fileHandle) + { + constexpr DWORD bufferSize = 1024 * 1024; + auto buffer = std::make_unique(bufferSize); + SHA256 hasher; + DWORD bytesRead = 0; + + while (ReadFile(fileHandle, buffer.get(), bufferSize, &bytesRead, nullptr) && bytesRead > 0) + { + hasher.Add(buffer.get(), bytesRead); + } + + return hasher.Get(); + } + + void SHA256::SHA256ContextDeleter::operator()(SHA256Context* context) + { + delete context; + } + + bool SHA256::AreEqual(const HashBuffer& first, const HashBuffer& second) + { + return (first.size() == second.size() && std::equal(first.begin(), first.end(), second.begin())); + } + + void SHA256::EnsureNotFinished() const + { + if (!context) + { + THROW_HR_MSG(E_UNEXPECTED, "The hash is already finished"); + } + } +} diff --git a/src/AppInstallerSharedLib/SQLiteDynamicStorage.cpp b/src/AppInstallerSharedLib/SQLiteDynamicStorage.cpp index 42c0d97510..4616baab82 100644 --- a/src/AppInstallerSharedLib/SQLiteDynamicStorage.cpp +++ b/src/AppInstallerSharedLib/SQLiteDynamicStorage.cpp @@ -1,91 +1,91 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SQLiteDynamicStorage.h" - -namespace AppInstaller::SQLite -{ - SQLiteDynamicStorage::SQLiteDynamicStorage(const std::string& target, const Version& version) : SQLiteStorageBase(target, version) - { - version.SetSchemaVersion(m_dbconn); - } - - SQLiteDynamicStorage::SQLiteDynamicStorage(const std::filesystem::path& target, const Version& version) : SQLiteDynamicStorage(target.u8string(), version) - {} - - SQLiteDynamicStorage::SQLiteDynamicStorage( - const std::string& filePath, - SQLiteStorageBase::OpenDisposition disposition, - Utility::ManagedFile&& file) - : SQLiteStorageBase(filePath, disposition, std::move(file)) - {} - - SQLiteDynamicStorage::SQLiteDynamicStorage( - const std::filesystem::path& filePath, - SQLiteStorageBase::OpenDisposition disposition, - Utility::ManagedFile&& file) - : SQLiteDynamicStorage(filePath.u8string(), disposition, std::move(file)) - {} - - SQLiteDynamicStorage::operator Connection& () - { - return m_dbconn; - } - - SQLiteDynamicStorage::operator const Connection& () const - { - return m_dbconn; - } - - Connection& SQLiteDynamicStorage::GetConnection() - { - return m_dbconn; - } - - const Connection& SQLiteDynamicStorage::GetConnection() const - { - return m_dbconn; - } - - _Acquires_lock_(mutex) - SQLiteDynamicStorage::TransactionLock::TransactionLock(std::mutex& mutex) : - m_lock(mutex) - { - } - - _Acquires_lock_(mutex) - SQLiteDynamicStorage::TransactionLock::TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name, bool immediateWrite) : - m_lock(mutex) - { - m_transaction = Transaction::Create(connection, std::string{ name }, immediateWrite); - } - - void SQLiteDynamicStorage::TransactionLock::Rollback(bool throwOnError) - { - m_transaction.Rollback(throwOnError); - } - - void SQLiteDynamicStorage::TransactionLock::Commit() - { - m_transaction.Commit(); - } - - std::unique_ptr SQLiteDynamicStorage::TryBeginTransaction(std::string_view name, bool immediateWrite) - { - auto result = std::make_unique(*m_interfaceLock, m_dbconn, name, immediateWrite); - - Version currentVersion = Version::GetSchemaVersion(m_dbconn); - if (currentVersion != m_version) - { - m_version = currentVersion; - result.reset(); - } - - return result; - } - - std::unique_ptr SQLiteDynamicStorage::LockConnection() - { - return std::make_unique(*m_interfaceLock); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SQLiteDynamicStorage.h" + +namespace AppInstaller::SQLite +{ + SQLiteDynamicStorage::SQLiteDynamicStorage(const std::string& target, const Version& version) : SQLiteStorageBase(target, version) + { + version.SetSchemaVersion(m_dbconn); + } + + SQLiteDynamicStorage::SQLiteDynamicStorage(const std::filesystem::path& target, const Version& version) : SQLiteDynamicStorage(target.u8string(), version) + {} + + SQLiteDynamicStorage::SQLiteDynamicStorage( + const std::string& filePath, + SQLiteStorageBase::OpenDisposition disposition, + Utility::ManagedFile&& file) + : SQLiteStorageBase(filePath, disposition, std::move(file)) + {} + + SQLiteDynamicStorage::SQLiteDynamicStorage( + const std::filesystem::path& filePath, + SQLiteStorageBase::OpenDisposition disposition, + Utility::ManagedFile&& file) + : SQLiteDynamicStorage(filePath.u8string(), disposition, std::move(file)) + {} + + SQLiteDynamicStorage::operator Connection& () + { + return m_dbconn; + } + + SQLiteDynamicStorage::operator const Connection& () const + { + return m_dbconn; + } + + Connection& SQLiteDynamicStorage::GetConnection() + { + return m_dbconn; + } + + const Connection& SQLiteDynamicStorage::GetConnection() const + { + return m_dbconn; + } + + _Acquires_lock_(mutex) + SQLiteDynamicStorage::TransactionLock::TransactionLock(std::mutex& mutex) : + m_lock(mutex) + { + } + + _Acquires_lock_(mutex) + SQLiteDynamicStorage::TransactionLock::TransactionLock(std::mutex& mutex, Connection& connection, std::string_view name, bool immediateWrite) : + m_lock(mutex) + { + m_transaction = Transaction::Create(connection, std::string{ name }, immediateWrite); + } + + void SQLiteDynamicStorage::TransactionLock::Rollback(bool throwOnError) + { + m_transaction.Rollback(throwOnError); + } + + void SQLiteDynamicStorage::TransactionLock::Commit() + { + m_transaction.Commit(); + } + + std::unique_ptr SQLiteDynamicStorage::TryBeginTransaction(std::string_view name, bool immediateWrite) + { + auto result = std::make_unique(*m_interfaceLock, m_dbconn, name, immediateWrite); + + Version currentVersion = Version::GetSchemaVersion(m_dbconn); + if (currentVersion != m_version) + { + m_version = currentVersion; + result.reset(); + } + + return result; + } + + std::unique_ptr SQLiteDynamicStorage::LockConnection() + { + return std::make_unique(*m_interfaceLock); + } +} diff --git a/src/AppInstallerSharedLib/SQLiteMetadataTable.cpp b/src/AppInstallerSharedLib/SQLiteMetadataTable.cpp index 42a0300b9a..80accd310b 100644 --- a/src/AppInstallerSharedLib/SQLiteMetadataTable.cpp +++ b/src/AppInstallerSharedLib/SQLiteMetadataTable.cpp @@ -1,63 +1,63 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SQLiteMetadataTable.h" - - -using namespace std::literals; - -namespace AppInstaller::SQLite -{ - // Table data [note that this table is not versioned, and thus *cannot change*] - static constexpr std::string_view s_MetadataTable_Table_Name = "metadata"sv; - static constexpr std::string_view s_MetadataTable_Column_Name = "name"sv; - static constexpr std::string_view s_MetadataTable_Column_Value = "value"sv; - - static constexpr std::string_view s_MetadataTable_Table_Create = R"( -CREATE TABLE [metadata]( - [name] TEXT PRIMARY KEY NOT NULL, - [value] TEXT NOT NULL) WITHOUT ROWID -)"sv; - - // Statements - static constexpr std::string_view s_MetadataTableStmt_GetNamedValue = "select [value] from [metadata] where [name] = ?"sv; - static constexpr std::string_view s_MetadataTableStmt_SetNamedValue = "insert or replace into [metadata] ([name], [value]) values (?, ?)"sv; - - void MetadataTable::Create(Connection& connection) - { - Statement create = Statement::Create(connection, s_MetadataTable_Table_Create); - create.Execute(); - } - - Statement MetadataTable::GetNamedValueStatement(const Connection& connection, std::string_view name) - { - std::optional result = TryGetNamedValueStatement(connection, name); - THROW_HR_IF(E_NOT_SET, !result); - return std::move(result).value(); - } - - std::optional MetadataTable::TryGetNamedValueStatement(const Connection& connection, std::string_view name) - { - THROW_HR_IF(E_INVALIDARG, name.empty()); - - Statement result = Statement::Create(connection, s_MetadataTableStmt_GetNamedValue); - result.Bind(1, name); - - if (result.Step()) - { - return result; - } - else - { - return std::nullopt; - } - } - - Statement MetadataTable::SetNamedValueStatement(const Connection& connection, std::string_view name) - { - THROW_HR_IF(E_INVALIDARG, name.empty()); - Statement result = Statement::Create(connection, s_MetadataTableStmt_SetNamedValue); - result.Bind(1, name); - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SQLiteMetadataTable.h" + + +using namespace std::literals; + +namespace AppInstaller::SQLite +{ + // Table data [note that this table is not versioned, and thus *cannot change*] + static constexpr std::string_view s_MetadataTable_Table_Name = "metadata"sv; + static constexpr std::string_view s_MetadataTable_Column_Name = "name"sv; + static constexpr std::string_view s_MetadataTable_Column_Value = "value"sv; + + static constexpr std::string_view s_MetadataTable_Table_Create = R"( +CREATE TABLE [metadata]( + [name] TEXT PRIMARY KEY NOT NULL, + [value] TEXT NOT NULL) WITHOUT ROWID +)"sv; + + // Statements + static constexpr std::string_view s_MetadataTableStmt_GetNamedValue = "select [value] from [metadata] where [name] = ?"sv; + static constexpr std::string_view s_MetadataTableStmt_SetNamedValue = "insert or replace into [metadata] ([name], [value]) values (?, ?)"sv; + + void MetadataTable::Create(Connection& connection) + { + Statement create = Statement::Create(connection, s_MetadataTable_Table_Create); + create.Execute(); + } + + Statement MetadataTable::GetNamedValueStatement(const Connection& connection, std::string_view name) + { + std::optional result = TryGetNamedValueStatement(connection, name); + THROW_HR_IF(E_NOT_SET, !result); + return std::move(result).value(); + } + + std::optional MetadataTable::TryGetNamedValueStatement(const Connection& connection, std::string_view name) + { + THROW_HR_IF(E_INVALIDARG, name.empty()); + + Statement result = Statement::Create(connection, s_MetadataTableStmt_GetNamedValue); + result.Bind(1, name); + + if (result.Step()) + { + return result; + } + else + { + return std::nullopt; + } + } + + Statement MetadataTable::SetNamedValueStatement(const Connection& connection, std::string_view name) + { + THROW_HR_IF(E_INVALIDARG, name.empty()); + Statement result = Statement::Create(connection, s_MetadataTableStmt_SetNamedValue); + result.Bind(1, name); + return result; + } +} diff --git a/src/AppInstallerSharedLib/SQLiteStatementBuilder.cpp b/src/AppInstallerSharedLib/SQLiteStatementBuilder.cpp index a28644025b..4277ebf1b5 100644 --- a/src/AppInstallerSharedLib/SQLiteStatementBuilder.cpp +++ b/src/AppInstallerSharedLib/SQLiteStatementBuilder.cpp @@ -1,1010 +1,1010 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SQLiteStatementBuilder.h" - -namespace AppInstaller::SQLite::Builder -{ - std::ostream& operator<<(std::ostream& out, const QualifiedColumn& column) - { - if (!column.Table.empty()) - { - out << '[' << column.Table << "]."; - } - out << '[' << column.Column << ']'; - return out; - } - - std::ostream& operator<<(std::ostream& out, const QualifiedTable& table) - { - if (!table.Schema.empty()) - { - out << '[' << table.Schema << "]."; - } - out << '[' << table.Table << ']'; - return out; - } - - std::ostream& operator<<(std::ostream& out, const details::SubBuilder& column) - { - out << column.GetString(); - return out; - } - - namespace - { - void OutputColumns(std::ostream& out, std::string_view op, std::string_view column) - { - out << op << '[' << column << ']'; - } - - void OutputColumns(std::ostream& out, std::string_view op, std::initializer_list columns) - { - out << op; - bool isFirst = true; - for (const auto& c : columns) - { - out << (isFirst ? "[" : ", [") << c << ']'; - isFirst = false; - } - } - - void OutputColumns(std::ostream& out, std::string_view op, const QualifiedColumn& column) - { - out << op << column; - } - - void OutputColumns(std::ostream& out, std::string_view op, std::initializer_list columns) - { - out << op; - bool isFirst = true; - for (const auto& c : columns) - { - out << (isFirst ? "" : ", ") << c; - isFirst = false; - } - } - - void OutputColumns(std::ostream& out, std::string_view op, std::initializer_list columns) - { - out << op; - bool isFirst = true; - for (const auto& c : columns) - { - out << (isFirst ? "" : ", ") << c; - isFirst = false; - } - } - - void OutputAggregate(std::ostream& out, Aggregate op) - { - out << ' '; - switch (op) - { - case Aggregate::Min: - out << "MIN"; - break; - case Aggregate::Max: - out << "MAX"; - break; - default: - THROW_HR(E_UNEXPECTED); - } - } - - void OutputColumns(std::ostream& out, Aggregate op, std::string_view column) - { - OutputAggregate(out, op); - out << "([" << column << "])"; - } - - void OutputColumns(std::ostream& out, Aggregate op, const QualifiedColumn& column) - { - OutputAggregate(out, op); - out << '(' << column << ')'; - } - - // Use to output operation and table name, such as " FROM [table]" - void OutputOperationAndTable(std::ostream& out, std::string_view op, std::string_view table) - { - out << op << " [" << table << ']'; - } - - void OutputOperationAndTable(std::ostream& out, std::string_view op, QualifiedTable table) - { - out << op << table; - } - - void OutputOperationAndTable(std::ostream& out, std::string_view op, std::initializer_list table) - { - out << op << " ["; - for (std::string_view t : table) - { - out << t; - } - out << ']'; - } - - void OutputType(std::ostream& out, Type type) - { - out << ' '; - switch (type) - { - case Type::Int: - out << "INT"; - break; - case Type::Int64: - out << "INT64"; - break; - case Type::Text: - out << "TEXT"; - break; - case Type::Blob: - out << "BLOB"; - break; - case Type::Integer: - out << "INTEGER"; - break; - case Type::None: - break; - default: - THROW_HR(E_UNEXPECTED); - } - } - } - - IntegerPrimaryKey::IntegerPrimaryKey() - { - m_stream << SQLite::RowIDName << " INTEGER PRIMARY KEY"; - } - - IntegerPrimaryKey& IntegerPrimaryKey::AutoIncrement(bool isTrue) - { - if (isTrue) - { - m_stream << " AUTOINCREMENT"; - } - return *this; - } - - ColumnBuilder::ColumnBuilder(std::string_view column, Type type) - { - OutputColumns(m_stream, "", column); - OutputType(m_stream, type); - } - - ColumnBuilder& ColumnBuilder::NotNull(bool isTrue) - { - if (isTrue) - { - m_stream << " NOT NULL"; - } - return *this; - } - - ColumnBuilder& ColumnBuilder::CollateNoCase(bool isTrue) - { - if (isTrue) - { - m_stream << " COLLATE NOCASE"; - } - return *this; - } - - ColumnBuilder& ColumnBuilder::Default(int64_t value) - { - m_stream << " DEFAULT " << value; - return *this; - } - - ColumnBuilder& ColumnBuilder::Unique(bool isTrue) - { - if (isTrue) - { - m_stream << " UNIQUE"; - } - return *this; - } - - ColumnBuilder& ColumnBuilder::PrimaryKey(bool isTrue) - { - if (isTrue) - { - m_stream << " PRIMARY KEY"; - } - return *this; - } - - PrimaryKeyBuilder::PrimaryKeyBuilder(std::initializer_list columns) - { - OutputColumns(m_stream, "PRIMARY KEY(", columns); - m_stream << ')'; - m_needsClosing = false; - } - - PrimaryKeyBuilder::PrimaryKeyBuilder() - { - m_stream << "PRIMARY KEY("; - } - - PrimaryKeyBuilder& PrimaryKeyBuilder::Column(std::string_view column) - { - if (m_isFirst) - { - m_isFirst = false; - } - else - { - m_stream << ", "; - } - OutputColumns(m_stream, "", column); - return *this; - } - - PrimaryKeyBuilder::operator details::SubBuilder() - { - if (m_needsClosing) - { - m_stream << ')'; - m_needsClosing = false; - } - return { m_stream.str() }; - } - - StatementBuilder& StatementBuilder::Select() - { - m_stream << "SELECT "; - m_needsComma = false; - return *this; - } - - StatementBuilder& StatementBuilder::Select(std::string_view column) - { - OutputColumns(m_stream, "SELECT ", column); - return *this; - } - - StatementBuilder& StatementBuilder::Select(std::initializer_list columns) - { - OutputColumns(m_stream, "SELECT ", columns); - return *this; - } - - StatementBuilder& StatementBuilder::Select(const QualifiedColumn& column) - { - OutputColumns(m_stream, "SELECT ", column); - return *this; - } - - StatementBuilder& StatementBuilder::Select(std::initializer_list columns) - { - OutputColumns(m_stream, "SELECT ", columns); - return *this; - } - - StatementBuilder& StatementBuilder::Select(details::rowcount_t) - { - m_stream << "SELECT COUNT(*)"; - return *this; - } - - StatementBuilder& StatementBuilder::From() - { - m_stream << " FROM "; - return *this; - } - - StatementBuilder& StatementBuilder::From(std::string_view table) - { - OutputOperationAndTable(m_stream, " FROM", table); - return *this; - } - - StatementBuilder& StatementBuilder::From(QualifiedTable table) - { - OutputOperationAndTable(m_stream, " FROM", table); - return *this; - } - - StatementBuilder& StatementBuilder::From(std::initializer_list table) - { - OutputOperationAndTable(m_stream, " FROM", table); - return *this; - } - - StatementBuilder& StatementBuilder::Where(std::string_view column) - { - OutputColumns(m_stream, " WHERE ", column); - return *this; - } - - StatementBuilder& StatementBuilder::Where(const QualifiedColumn& column) - { - OutputColumns(m_stream, " WHERE ", column); - return *this; - } - - StatementBuilder& StatementBuilder::WhereValueContainsEmbeddedNullCharacter(std::string_view column) - { - OutputColumns(m_stream, " WHERE instr(", column); - m_stream << ",char(0))>0"; - return *this; - } - - StatementBuilder& StatementBuilder::WhereValueContainsEmbeddedNullCharacter(const QualifiedColumn& column) - { - OutputColumns(m_stream, " WHERE instr(", column); - m_stream << ",char(0))>0"; - return *this; - } - - StatementBuilder& StatementBuilder::Equals(details::unbound_t, std::optional index) - { - AppendOpAndBinder(Op::Equals, index); - return *this; - } - - StatementBuilder& StatementBuilder::Equals(std::nullptr_t) - { - // This is almost certainly not what you want. - // In SQL, value = NULL is always false. - // Use StatementBuilder::IsNull instead. - THROW_HR(E_NOTIMPL); - } - - StatementBuilder& StatementBuilder::Equals() - { - m_stream << " ="; - return *this; - } - - StatementBuilder& StatementBuilder::Equals(const QualifiedColumn& column) - { - OutputColumns(m_stream, " = ", column); - return *this; - } - - StatementBuilder& StatementBuilder::IsGreaterThan(details::unbound_t, std::optional index) - { - AppendOpAndBinder(Op::GreaterThan, index); - return *this; - } - - StatementBuilder& StatementBuilder::IsGreaterThanOrEqualTo(details::unbound_t, std::optional index) - { - AppendOpAndBinder(Op::GreaterThanOrEqualTo, index); - return *this; - } - - StatementBuilder& StatementBuilder::LikeWithEscape(std::string_view value) - { - AddBindFunctor(AppendOpAndBinder(Op::Like), EscapeStringForLike(value)); - return Escape(EscapeCharForLike); - } - - StatementBuilder& StatementBuilder::Like(details::unbound_t) - { - AppendOpAndBinder(Op::Like); - return *this; - } - - StatementBuilder& StatementBuilder::Escape(std::string_view escapeChar) - { - THROW_HR_IF(E_INVALIDARG, escapeChar.length() != 1); - AddBindFunctor(AppendOpAndBinder(Op::Escape), escapeChar); - return *this; - } - - StatementBuilder& StatementBuilder::Not() - { - m_stream << " NOT"; - return *this; - } - - StatementBuilder& StatementBuilder::In() - { - m_stream << " IN"; - return *this; - } - - StatementBuilder& StatementBuilder::In(size_t count) - { - m_stream << " IN ("; - for (size_t i = 0; i < count; ++i) - { - m_stream << (i == 0 ? "?" : ", ?"); - } - m_stream << ')'; - - m_bindIndex += static_cast(count); - return *this; - } - - StatementBuilder& StatementBuilder::IsNull(bool isNull) - { - m_stream << " IS " << (isNull ? "" : "NOT ") << "NULL"; - return *this; - } - - StatementBuilder& StatementBuilder::And(std::string_view column) - { - OutputColumns(m_stream, " AND ", column); - return *this; - } - - StatementBuilder& StatementBuilder::And(const QualifiedColumn& column) - { - OutputColumns(m_stream, " AND ", column); - return *this; - } - - StatementBuilder& StatementBuilder::Or(const QualifiedColumn& column) - { - OutputColumns(m_stream, " OR ", column); - return *this; - } - - StatementBuilder& StatementBuilder::Join(std::string_view table) - { - OutputOperationAndTable(m_stream, " JOIN", table); - return *this; - } - - StatementBuilder& StatementBuilder::Join(QualifiedTable table) - { - OutputOperationAndTable(m_stream, " JOIN", table); - return *this; - } - - StatementBuilder& StatementBuilder::Join(std::initializer_list table) - { - OutputOperationAndTable(m_stream, " JOIN", table); - return *this; - } - - StatementBuilder& StatementBuilder::LeftOuterJoin(std::string_view table) - { - OutputOperationAndTable(m_stream, " LEFT OUTER JOIN", table); - return *this; - } - - StatementBuilder& StatementBuilder::LeftOuterJoin(QualifiedTable table) - { - OutputOperationAndTable(m_stream, " LEFT OUTER JOIN", table); - return *this; - } - - StatementBuilder& StatementBuilder::LeftOuterJoin(std::initializer_list table) - { - OutputOperationAndTable(m_stream, " LEFT OUTER JOIN", table); - return *this; - } - - StatementBuilder& StatementBuilder::On(const QualifiedColumn& column1, const QualifiedColumn& column2) - { - m_stream << " ON " << column1 << " = " << column2; - return *this; - } - - StatementBuilder& StatementBuilder::Limit(size_t rowCount) - { - m_stream << " LIMIT " << rowCount; - return *this; - } - - StatementBuilder& StatementBuilder::GroupBy(std::string_view column) - { - OutputColumns(m_stream, " GROUP BY ", column); - return *this; - } - - StatementBuilder& StatementBuilder::GroupBy(const QualifiedColumn& column) - { - OutputColumns(m_stream, " GROUP BY ", column); - return *this; - } - - StatementBuilder& StatementBuilder::OrderBy(std::string_view column) - { - OutputColumns(m_stream, " ORDER BY ", column); - return *this; - } - - StatementBuilder& StatementBuilder::OrderBy(const QualifiedColumn& column) - { - OutputColumns(m_stream, " ORDER BY ", column); - return *this; - } - - StatementBuilder& StatementBuilder::OrderBy(std::initializer_list columns) - { - OutputColumns(m_stream, " ORDER BY ", columns); - return *this; - } - - StatementBuilder& StatementBuilder::Ascending() - { - m_stream << " ASC"; - return *this; - } - - StatementBuilder& StatementBuilder::Descending() - { - m_stream << " DESC"; - return *this; - } - - StatementBuilder& StatementBuilder::InsertInto(std::string_view table) - { - OutputOperationAndTable(m_stream, "INSERT INTO", table); - return *this; - } - - StatementBuilder& StatementBuilder::InsertInto(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "INSERT INTO", table); - return *this; - } - - StatementBuilder& StatementBuilder::InsertInto(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "INSERT INTO", table); - return *this; - } - - StatementBuilder& StatementBuilder::InsertOrIgnore(std::string_view table) - { - OutputOperationAndTable(m_stream, "INSERT OR IGNORE INTO", table); - return *this; - } - - StatementBuilder& StatementBuilder::InsertOrIgnore(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "INSERT OR IGNORE INTO", table); - return *this; - } - - StatementBuilder& StatementBuilder::InsertOrIgnore(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "INSERT OR IGNORE INTO", table); - return *this; - } - - StatementBuilder& StatementBuilder::Columns(std::string_view column) - { - OutputColumns(m_stream, "(", column); - m_stream << ')'; - return *this; - } - - StatementBuilder& StatementBuilder::Columns(std::initializer_list columns) - { - OutputColumns(m_stream, "(", columns); - m_stream << ')'; - return *this; - } - - StatementBuilder& StatementBuilder::Columns(const QualifiedColumn& column) - { - OutputColumns(m_stream, "(", column); - m_stream << ')'; - return *this; - } - - StatementBuilder& StatementBuilder::Columns(std::initializer_list columns) - { - OutputColumns(m_stream, "(", columns); - m_stream << ')'; - return *this; - } - - StatementBuilder& StatementBuilder::Columns(std::initializer_list columns) - { - OutputColumns(m_stream, "(", columns); - m_stream << ')'; - return *this; - } - - StatementBuilder& StatementBuilder::BeginColumns() - { - m_stream << '('; - m_needsComma = false; - return *this; - } - - StatementBuilder& StatementBuilder::Column(std::string_view column) - { - if (m_needsComma) - { - m_stream << ", "; - } - OutputColumns(m_stream, "", column); - m_needsComma = true; - return *this; - } - - StatementBuilder& StatementBuilder::Column(const QualifiedColumn& column) - { - if (m_needsComma) - { - m_stream << ", "; - } - OutputColumns(m_stream, "", column); - m_needsComma = true; - return *this; - } - - StatementBuilder& StatementBuilder::Column(Aggregate aggOp, std::string_view column) - { - if (m_needsComma) - { - m_stream << ", "; - } - OutputColumns(m_stream, aggOp, column); - m_needsComma = true; - return *this; - } - - StatementBuilder& StatementBuilder::Column(Aggregate aggOp, const QualifiedColumn& column) - { - if (m_needsComma) - { - m_stream << ", "; - } - OutputColumns(m_stream, aggOp, column); - m_needsComma = true; - return *this; - } - - StatementBuilder& StatementBuilder::Column(const details::SubBuilder& column) - { - if (m_needsComma) - { - m_stream << ", "; - } - m_stream << column; - m_needsComma = true; - return *this; - } - - StatementBuilder& StatementBuilder::EndColumns() - { - m_stream << ')'; - m_needsComma = false; - return *this; - } - - StatementBuilder& StatementBuilder::NotNull(bool isTrue) - { - if (isTrue) - { - m_stream << " NOT NULL"; - } - return *this; - } - - StatementBuilder& StatementBuilder::BeginValues() - { - m_stream << " VALUES ("; - m_needsComma = false; - return *this; - } - - StatementBuilder& StatementBuilder::EndValues() - { - m_stream << ')'; - m_needsComma = false; - return *this; - } - - StatementBuilder& StatementBuilder::CreateTable(std::string_view table) - { - OutputOperationAndTable(m_stream, "CREATE TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateTable(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "CREATE TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateTable(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "CREATE TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::AlterTable(std::string_view table) - { - OutputOperationAndTable(m_stream, "ALTER TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::AlterTable(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "ALTER TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::AlterTable(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "ALTER TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::Add(std::string_view column, Type type) - { - m_stream << " ADD " << column; - OutputType(m_stream, type); - return *this; - } - - StatementBuilder& StatementBuilder::DropTable(std::string_view table) - { - OutputOperationAndTable(m_stream, "DROP TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::DropTable(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "DROP TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::DropTable(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "DROP TABLE", table); - return *this; - } - - StatementBuilder& StatementBuilder::DropTableIfExists(std::string_view table) - { - OutputOperationAndTable(m_stream, "DROP TABLE IF EXISTS", table); - return *this; - } - - StatementBuilder& StatementBuilder::DropTableIfExists(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "DROP TABLE IF EXISTS", table); - return *this; - } - - StatementBuilder& StatementBuilder::DropTableIfExists(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "DROP TABLE IF EXISTS", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateIndex(std::string_view table) - { - OutputOperationAndTable(m_stream, "CREATE INDEX", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateIndex(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "CREATE INDEX", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateIndex(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "CREATE INDEX", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateUniqueIndex(std::string_view table) - { - OutputOperationAndTable(m_stream, "CREATE UNIQUE INDEX", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateUniqueIndex(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "CREATE UNIQUE INDEX", table); - return *this; - } - - StatementBuilder& StatementBuilder::CreateUniqueIndex(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "CREATE UNIQUE INDEX", table); - return *this; - } - - StatementBuilder& StatementBuilder::DropIndex(std::string_view index) - { - OutputOperationAndTable(m_stream, "DROP INDEX", index); - return *this; - } - - StatementBuilder& StatementBuilder::DropIndex(QualifiedTable index) - { - OutputOperationAndTable(m_stream, "DROP INDEX", index); - return *this; - } - - StatementBuilder& StatementBuilder::DropIndex(std::initializer_list index) - { - OutputOperationAndTable(m_stream, "DROP INDEX", index); - return *this; - } - - StatementBuilder& StatementBuilder::On(std::string_view table) - { - OutputOperationAndTable(m_stream, " ON", table); - return *this; - } - - StatementBuilder& StatementBuilder::On(std::initializer_list table) - { - OutputOperationAndTable(m_stream, " ON", table); - return *this; - } - - StatementBuilder& StatementBuilder::DeleteFrom(std::string_view table) - { - OutputOperationAndTable(m_stream, "DELETE FROM", table); - return *this; - } - - StatementBuilder& StatementBuilder::DeleteFrom(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "DELETE FROM", table); - return *this; - } - - StatementBuilder& StatementBuilder::DeleteFrom(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "DELETE FROM", table); - return *this; - } - - StatementBuilder& StatementBuilder::Update(std::string_view table) - { - OutputOperationAndTable(m_stream, "UPDATE", table); - return *this; - } - - StatementBuilder& StatementBuilder::Update(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "UPDATE", table); - return *this; - } - - StatementBuilder& StatementBuilder::Update(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "UPDATE", table); - return *this; - } - - StatementBuilder& StatementBuilder::UpdateOrReplace(std::string_view table) - { - OutputOperationAndTable(m_stream, "UPDATE OR REPLACE", table); - return *this; - } - - StatementBuilder& StatementBuilder::UpdateOrReplace(QualifiedTable table) - { - OutputOperationAndTable(m_stream, "UPDATE OR REPLACE", table); - return *this; - } - - StatementBuilder& StatementBuilder::UpdateOrReplace(std::initializer_list table) - { - OutputOperationAndTable(m_stream, "UPDATE OR REPLACE", table); - return *this; - } - - StatementBuilder& StatementBuilder::Set() - { - m_stream << " SET "; - m_needsComma = false; - return *this; - } - - StatementBuilder& StatementBuilder::Vacuum() - { - m_stream << "VACUUM"; - return *this; - } - - StatementBuilder& StatementBuilder::BeginParenthetical() - { - m_stream << '('; - return *this; - } - - StatementBuilder& StatementBuilder::EndParenthetical() - { - m_stream << ')'; - return *this; - } - - StatementBuilder& StatementBuilder::WithoutRowID() - { - m_stream << " WITHOUT ROWID"; - return *this; - } - - - StatementBuilder& StatementBuilder::As(std::string_view alias) - { - OutputOperationAndTable(m_stream, " AS", alias); - return *this; - } - - Statement StatementBuilder::Prepare(const Connection& connection) - { - Statement result = Statement::Create(connection, m_stream.str()); - for (const auto& f : m_binders) - { - f(result); - } - return result; - } - - void StatementBuilder::Execute(const Connection& connection) - { - Prepare(connection).Execute(); - } - - int StatementBuilder::AppendOpAndBinder(Op op, std::optional index) - { - switch (op) - { - case Op::Equals: - m_stream << " = ?"; - break; - case Op::Like: - m_stream << " LIKE ?"; - break; - case Op::Escape: - m_stream << " ESCAPE ?"; - break; - case Op::Literal: - m_stream << " ?"; - break; - case Op::GreaterThan: - m_stream << " > ?"; - break; - case Op::GreaterThanOrEqualTo: - m_stream << " >= ?"; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - if (index) - { - m_stream << index.value(); - } - - return m_bindIndex++; - } - - int StatementBuilder::AppendValuesAndBinders(size_t count) - { - m_stream << " VALUES ("; - for (size_t i = 0; i < count; ++i) - { - m_stream << (i == 0 ? "?" : ", ?"); - } - m_stream << ')'; - - int result = m_bindIndex; - m_bindIndex += static_cast(count); - return result; - } - - int StatementBuilder::AppendValueAndBinder() - { - if (m_needsComma) - { - m_stream << ", "; - } - m_stream << '?'; - m_needsComma = true; - return m_bindIndex++; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SQLiteStatementBuilder.h" + +namespace AppInstaller::SQLite::Builder +{ + std::ostream& operator<<(std::ostream& out, const QualifiedColumn& column) + { + if (!column.Table.empty()) + { + out << '[' << column.Table << "]."; + } + out << '[' << column.Column << ']'; + return out; + } + + std::ostream& operator<<(std::ostream& out, const QualifiedTable& table) + { + if (!table.Schema.empty()) + { + out << '[' << table.Schema << "]."; + } + out << '[' << table.Table << ']'; + return out; + } + + std::ostream& operator<<(std::ostream& out, const details::SubBuilder& column) + { + out << column.GetString(); + return out; + } + + namespace + { + void OutputColumns(std::ostream& out, std::string_view op, std::string_view column) + { + out << op << '[' << column << ']'; + } + + void OutputColumns(std::ostream& out, std::string_view op, std::initializer_list columns) + { + out << op; + bool isFirst = true; + for (const auto& c : columns) + { + out << (isFirst ? "[" : ", [") << c << ']'; + isFirst = false; + } + } + + void OutputColumns(std::ostream& out, std::string_view op, const QualifiedColumn& column) + { + out << op << column; + } + + void OutputColumns(std::ostream& out, std::string_view op, std::initializer_list columns) + { + out << op; + bool isFirst = true; + for (const auto& c : columns) + { + out << (isFirst ? "" : ", ") << c; + isFirst = false; + } + } + + void OutputColumns(std::ostream& out, std::string_view op, std::initializer_list columns) + { + out << op; + bool isFirst = true; + for (const auto& c : columns) + { + out << (isFirst ? "" : ", ") << c; + isFirst = false; + } + } + + void OutputAggregate(std::ostream& out, Aggregate op) + { + out << ' '; + switch (op) + { + case Aggregate::Min: + out << "MIN"; + break; + case Aggregate::Max: + out << "MAX"; + break; + default: + THROW_HR(E_UNEXPECTED); + } + } + + void OutputColumns(std::ostream& out, Aggregate op, std::string_view column) + { + OutputAggregate(out, op); + out << "([" << column << "])"; + } + + void OutputColumns(std::ostream& out, Aggregate op, const QualifiedColumn& column) + { + OutputAggregate(out, op); + out << '(' << column << ')'; + } + + // Use to output operation and table name, such as " FROM [table]" + void OutputOperationAndTable(std::ostream& out, std::string_view op, std::string_view table) + { + out << op << " [" << table << ']'; + } + + void OutputOperationAndTable(std::ostream& out, std::string_view op, QualifiedTable table) + { + out << op << table; + } + + void OutputOperationAndTable(std::ostream& out, std::string_view op, std::initializer_list table) + { + out << op << " ["; + for (std::string_view t : table) + { + out << t; + } + out << ']'; + } + + void OutputType(std::ostream& out, Type type) + { + out << ' '; + switch (type) + { + case Type::Int: + out << "INT"; + break; + case Type::Int64: + out << "INT64"; + break; + case Type::Text: + out << "TEXT"; + break; + case Type::Blob: + out << "BLOB"; + break; + case Type::Integer: + out << "INTEGER"; + break; + case Type::None: + break; + default: + THROW_HR(E_UNEXPECTED); + } + } + } + + IntegerPrimaryKey::IntegerPrimaryKey() + { + m_stream << SQLite::RowIDName << " INTEGER PRIMARY KEY"; + } + + IntegerPrimaryKey& IntegerPrimaryKey::AutoIncrement(bool isTrue) + { + if (isTrue) + { + m_stream << " AUTOINCREMENT"; + } + return *this; + } + + ColumnBuilder::ColumnBuilder(std::string_view column, Type type) + { + OutputColumns(m_stream, "", column); + OutputType(m_stream, type); + } + + ColumnBuilder& ColumnBuilder::NotNull(bool isTrue) + { + if (isTrue) + { + m_stream << " NOT NULL"; + } + return *this; + } + + ColumnBuilder& ColumnBuilder::CollateNoCase(bool isTrue) + { + if (isTrue) + { + m_stream << " COLLATE NOCASE"; + } + return *this; + } + + ColumnBuilder& ColumnBuilder::Default(int64_t value) + { + m_stream << " DEFAULT " << value; + return *this; + } + + ColumnBuilder& ColumnBuilder::Unique(bool isTrue) + { + if (isTrue) + { + m_stream << " UNIQUE"; + } + return *this; + } + + ColumnBuilder& ColumnBuilder::PrimaryKey(bool isTrue) + { + if (isTrue) + { + m_stream << " PRIMARY KEY"; + } + return *this; + } + + PrimaryKeyBuilder::PrimaryKeyBuilder(std::initializer_list columns) + { + OutputColumns(m_stream, "PRIMARY KEY(", columns); + m_stream << ')'; + m_needsClosing = false; + } + + PrimaryKeyBuilder::PrimaryKeyBuilder() + { + m_stream << "PRIMARY KEY("; + } + + PrimaryKeyBuilder& PrimaryKeyBuilder::Column(std::string_view column) + { + if (m_isFirst) + { + m_isFirst = false; + } + else + { + m_stream << ", "; + } + OutputColumns(m_stream, "", column); + return *this; + } + + PrimaryKeyBuilder::operator details::SubBuilder() + { + if (m_needsClosing) + { + m_stream << ')'; + m_needsClosing = false; + } + return { m_stream.str() }; + } + + StatementBuilder& StatementBuilder::Select() + { + m_stream << "SELECT "; + m_needsComma = false; + return *this; + } + + StatementBuilder& StatementBuilder::Select(std::string_view column) + { + OutputColumns(m_stream, "SELECT ", column); + return *this; + } + + StatementBuilder& StatementBuilder::Select(std::initializer_list columns) + { + OutputColumns(m_stream, "SELECT ", columns); + return *this; + } + + StatementBuilder& StatementBuilder::Select(const QualifiedColumn& column) + { + OutputColumns(m_stream, "SELECT ", column); + return *this; + } + + StatementBuilder& StatementBuilder::Select(std::initializer_list columns) + { + OutputColumns(m_stream, "SELECT ", columns); + return *this; + } + + StatementBuilder& StatementBuilder::Select(details::rowcount_t) + { + m_stream << "SELECT COUNT(*)"; + return *this; + } + + StatementBuilder& StatementBuilder::From() + { + m_stream << " FROM "; + return *this; + } + + StatementBuilder& StatementBuilder::From(std::string_view table) + { + OutputOperationAndTable(m_stream, " FROM", table); + return *this; + } + + StatementBuilder& StatementBuilder::From(QualifiedTable table) + { + OutputOperationAndTable(m_stream, " FROM", table); + return *this; + } + + StatementBuilder& StatementBuilder::From(std::initializer_list table) + { + OutputOperationAndTable(m_stream, " FROM", table); + return *this; + } + + StatementBuilder& StatementBuilder::Where(std::string_view column) + { + OutputColumns(m_stream, " WHERE ", column); + return *this; + } + + StatementBuilder& StatementBuilder::Where(const QualifiedColumn& column) + { + OutputColumns(m_stream, " WHERE ", column); + return *this; + } + + StatementBuilder& StatementBuilder::WhereValueContainsEmbeddedNullCharacter(std::string_view column) + { + OutputColumns(m_stream, " WHERE instr(", column); + m_stream << ",char(0))>0"; + return *this; + } + + StatementBuilder& StatementBuilder::WhereValueContainsEmbeddedNullCharacter(const QualifiedColumn& column) + { + OutputColumns(m_stream, " WHERE instr(", column); + m_stream << ",char(0))>0"; + return *this; + } + + StatementBuilder& StatementBuilder::Equals(details::unbound_t, std::optional index) + { + AppendOpAndBinder(Op::Equals, index); + return *this; + } + + StatementBuilder& StatementBuilder::Equals(std::nullptr_t) + { + // This is almost certainly not what you want. + // In SQL, value = NULL is always false. + // Use StatementBuilder::IsNull instead. + THROW_HR(E_NOTIMPL); + } + + StatementBuilder& StatementBuilder::Equals() + { + m_stream << " ="; + return *this; + } + + StatementBuilder& StatementBuilder::Equals(const QualifiedColumn& column) + { + OutputColumns(m_stream, " = ", column); + return *this; + } + + StatementBuilder& StatementBuilder::IsGreaterThan(details::unbound_t, std::optional index) + { + AppendOpAndBinder(Op::GreaterThan, index); + return *this; + } + + StatementBuilder& StatementBuilder::IsGreaterThanOrEqualTo(details::unbound_t, std::optional index) + { + AppendOpAndBinder(Op::GreaterThanOrEqualTo, index); + return *this; + } + + StatementBuilder& StatementBuilder::LikeWithEscape(std::string_view value) + { + AddBindFunctor(AppendOpAndBinder(Op::Like), EscapeStringForLike(value)); + return Escape(EscapeCharForLike); + } + + StatementBuilder& StatementBuilder::Like(details::unbound_t) + { + AppendOpAndBinder(Op::Like); + return *this; + } + + StatementBuilder& StatementBuilder::Escape(std::string_view escapeChar) + { + THROW_HR_IF(E_INVALIDARG, escapeChar.length() != 1); + AddBindFunctor(AppendOpAndBinder(Op::Escape), escapeChar); + return *this; + } + + StatementBuilder& StatementBuilder::Not() + { + m_stream << " NOT"; + return *this; + } + + StatementBuilder& StatementBuilder::In() + { + m_stream << " IN"; + return *this; + } + + StatementBuilder& StatementBuilder::In(size_t count) + { + m_stream << " IN ("; + for (size_t i = 0; i < count; ++i) + { + m_stream << (i == 0 ? "?" : ", ?"); + } + m_stream << ')'; + + m_bindIndex += static_cast(count); + return *this; + } + + StatementBuilder& StatementBuilder::IsNull(bool isNull) + { + m_stream << " IS " << (isNull ? "" : "NOT ") << "NULL"; + return *this; + } + + StatementBuilder& StatementBuilder::And(std::string_view column) + { + OutputColumns(m_stream, " AND ", column); + return *this; + } + + StatementBuilder& StatementBuilder::And(const QualifiedColumn& column) + { + OutputColumns(m_stream, " AND ", column); + return *this; + } + + StatementBuilder& StatementBuilder::Or(const QualifiedColumn& column) + { + OutputColumns(m_stream, " OR ", column); + return *this; + } + + StatementBuilder& StatementBuilder::Join(std::string_view table) + { + OutputOperationAndTable(m_stream, " JOIN", table); + return *this; + } + + StatementBuilder& StatementBuilder::Join(QualifiedTable table) + { + OutputOperationAndTable(m_stream, " JOIN", table); + return *this; + } + + StatementBuilder& StatementBuilder::Join(std::initializer_list table) + { + OutputOperationAndTable(m_stream, " JOIN", table); + return *this; + } + + StatementBuilder& StatementBuilder::LeftOuterJoin(std::string_view table) + { + OutputOperationAndTable(m_stream, " LEFT OUTER JOIN", table); + return *this; + } + + StatementBuilder& StatementBuilder::LeftOuterJoin(QualifiedTable table) + { + OutputOperationAndTable(m_stream, " LEFT OUTER JOIN", table); + return *this; + } + + StatementBuilder& StatementBuilder::LeftOuterJoin(std::initializer_list table) + { + OutputOperationAndTable(m_stream, " LEFT OUTER JOIN", table); + return *this; + } + + StatementBuilder& StatementBuilder::On(const QualifiedColumn& column1, const QualifiedColumn& column2) + { + m_stream << " ON " << column1 << " = " << column2; + return *this; + } + + StatementBuilder& StatementBuilder::Limit(size_t rowCount) + { + m_stream << " LIMIT " << rowCount; + return *this; + } + + StatementBuilder& StatementBuilder::GroupBy(std::string_view column) + { + OutputColumns(m_stream, " GROUP BY ", column); + return *this; + } + + StatementBuilder& StatementBuilder::GroupBy(const QualifiedColumn& column) + { + OutputColumns(m_stream, " GROUP BY ", column); + return *this; + } + + StatementBuilder& StatementBuilder::OrderBy(std::string_view column) + { + OutputColumns(m_stream, " ORDER BY ", column); + return *this; + } + + StatementBuilder& StatementBuilder::OrderBy(const QualifiedColumn& column) + { + OutputColumns(m_stream, " ORDER BY ", column); + return *this; + } + + StatementBuilder& StatementBuilder::OrderBy(std::initializer_list columns) + { + OutputColumns(m_stream, " ORDER BY ", columns); + return *this; + } + + StatementBuilder& StatementBuilder::Ascending() + { + m_stream << " ASC"; + return *this; + } + + StatementBuilder& StatementBuilder::Descending() + { + m_stream << " DESC"; + return *this; + } + + StatementBuilder& StatementBuilder::InsertInto(std::string_view table) + { + OutputOperationAndTable(m_stream, "INSERT INTO", table); + return *this; + } + + StatementBuilder& StatementBuilder::InsertInto(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "INSERT INTO", table); + return *this; + } + + StatementBuilder& StatementBuilder::InsertInto(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "INSERT INTO", table); + return *this; + } + + StatementBuilder& StatementBuilder::InsertOrIgnore(std::string_view table) + { + OutputOperationAndTable(m_stream, "INSERT OR IGNORE INTO", table); + return *this; + } + + StatementBuilder& StatementBuilder::InsertOrIgnore(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "INSERT OR IGNORE INTO", table); + return *this; + } + + StatementBuilder& StatementBuilder::InsertOrIgnore(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "INSERT OR IGNORE INTO", table); + return *this; + } + + StatementBuilder& StatementBuilder::Columns(std::string_view column) + { + OutputColumns(m_stream, "(", column); + m_stream << ')'; + return *this; + } + + StatementBuilder& StatementBuilder::Columns(std::initializer_list columns) + { + OutputColumns(m_stream, "(", columns); + m_stream << ')'; + return *this; + } + + StatementBuilder& StatementBuilder::Columns(const QualifiedColumn& column) + { + OutputColumns(m_stream, "(", column); + m_stream << ')'; + return *this; + } + + StatementBuilder& StatementBuilder::Columns(std::initializer_list columns) + { + OutputColumns(m_stream, "(", columns); + m_stream << ')'; + return *this; + } + + StatementBuilder& StatementBuilder::Columns(std::initializer_list columns) + { + OutputColumns(m_stream, "(", columns); + m_stream << ')'; + return *this; + } + + StatementBuilder& StatementBuilder::BeginColumns() + { + m_stream << '('; + m_needsComma = false; + return *this; + } + + StatementBuilder& StatementBuilder::Column(std::string_view column) + { + if (m_needsComma) + { + m_stream << ", "; + } + OutputColumns(m_stream, "", column); + m_needsComma = true; + return *this; + } + + StatementBuilder& StatementBuilder::Column(const QualifiedColumn& column) + { + if (m_needsComma) + { + m_stream << ", "; + } + OutputColumns(m_stream, "", column); + m_needsComma = true; + return *this; + } + + StatementBuilder& StatementBuilder::Column(Aggregate aggOp, std::string_view column) + { + if (m_needsComma) + { + m_stream << ", "; + } + OutputColumns(m_stream, aggOp, column); + m_needsComma = true; + return *this; + } + + StatementBuilder& StatementBuilder::Column(Aggregate aggOp, const QualifiedColumn& column) + { + if (m_needsComma) + { + m_stream << ", "; + } + OutputColumns(m_stream, aggOp, column); + m_needsComma = true; + return *this; + } + + StatementBuilder& StatementBuilder::Column(const details::SubBuilder& column) + { + if (m_needsComma) + { + m_stream << ", "; + } + m_stream << column; + m_needsComma = true; + return *this; + } + + StatementBuilder& StatementBuilder::EndColumns() + { + m_stream << ')'; + m_needsComma = false; + return *this; + } + + StatementBuilder& StatementBuilder::NotNull(bool isTrue) + { + if (isTrue) + { + m_stream << " NOT NULL"; + } + return *this; + } + + StatementBuilder& StatementBuilder::BeginValues() + { + m_stream << " VALUES ("; + m_needsComma = false; + return *this; + } + + StatementBuilder& StatementBuilder::EndValues() + { + m_stream << ')'; + m_needsComma = false; + return *this; + } + + StatementBuilder& StatementBuilder::CreateTable(std::string_view table) + { + OutputOperationAndTable(m_stream, "CREATE TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateTable(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "CREATE TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateTable(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "CREATE TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::AlterTable(std::string_view table) + { + OutputOperationAndTable(m_stream, "ALTER TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::AlterTable(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "ALTER TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::AlterTable(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "ALTER TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::Add(std::string_view column, Type type) + { + m_stream << " ADD " << column; + OutputType(m_stream, type); + return *this; + } + + StatementBuilder& StatementBuilder::DropTable(std::string_view table) + { + OutputOperationAndTable(m_stream, "DROP TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::DropTable(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "DROP TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::DropTable(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "DROP TABLE", table); + return *this; + } + + StatementBuilder& StatementBuilder::DropTableIfExists(std::string_view table) + { + OutputOperationAndTable(m_stream, "DROP TABLE IF EXISTS", table); + return *this; + } + + StatementBuilder& StatementBuilder::DropTableIfExists(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "DROP TABLE IF EXISTS", table); + return *this; + } + + StatementBuilder& StatementBuilder::DropTableIfExists(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "DROP TABLE IF EXISTS", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateIndex(std::string_view table) + { + OutputOperationAndTable(m_stream, "CREATE INDEX", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateIndex(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "CREATE INDEX", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateIndex(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "CREATE INDEX", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateUniqueIndex(std::string_view table) + { + OutputOperationAndTable(m_stream, "CREATE UNIQUE INDEX", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateUniqueIndex(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "CREATE UNIQUE INDEX", table); + return *this; + } + + StatementBuilder& StatementBuilder::CreateUniqueIndex(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "CREATE UNIQUE INDEX", table); + return *this; + } + + StatementBuilder& StatementBuilder::DropIndex(std::string_view index) + { + OutputOperationAndTable(m_stream, "DROP INDEX", index); + return *this; + } + + StatementBuilder& StatementBuilder::DropIndex(QualifiedTable index) + { + OutputOperationAndTable(m_stream, "DROP INDEX", index); + return *this; + } + + StatementBuilder& StatementBuilder::DropIndex(std::initializer_list index) + { + OutputOperationAndTable(m_stream, "DROP INDEX", index); + return *this; + } + + StatementBuilder& StatementBuilder::On(std::string_view table) + { + OutputOperationAndTable(m_stream, " ON", table); + return *this; + } + + StatementBuilder& StatementBuilder::On(std::initializer_list table) + { + OutputOperationAndTable(m_stream, " ON", table); + return *this; + } + + StatementBuilder& StatementBuilder::DeleteFrom(std::string_view table) + { + OutputOperationAndTable(m_stream, "DELETE FROM", table); + return *this; + } + + StatementBuilder& StatementBuilder::DeleteFrom(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "DELETE FROM", table); + return *this; + } + + StatementBuilder& StatementBuilder::DeleteFrom(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "DELETE FROM", table); + return *this; + } + + StatementBuilder& StatementBuilder::Update(std::string_view table) + { + OutputOperationAndTable(m_stream, "UPDATE", table); + return *this; + } + + StatementBuilder& StatementBuilder::Update(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "UPDATE", table); + return *this; + } + + StatementBuilder& StatementBuilder::Update(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "UPDATE", table); + return *this; + } + + StatementBuilder& StatementBuilder::UpdateOrReplace(std::string_view table) + { + OutputOperationAndTable(m_stream, "UPDATE OR REPLACE", table); + return *this; + } + + StatementBuilder& StatementBuilder::UpdateOrReplace(QualifiedTable table) + { + OutputOperationAndTable(m_stream, "UPDATE OR REPLACE", table); + return *this; + } + + StatementBuilder& StatementBuilder::UpdateOrReplace(std::initializer_list table) + { + OutputOperationAndTable(m_stream, "UPDATE OR REPLACE", table); + return *this; + } + + StatementBuilder& StatementBuilder::Set() + { + m_stream << " SET "; + m_needsComma = false; + return *this; + } + + StatementBuilder& StatementBuilder::Vacuum() + { + m_stream << "VACUUM"; + return *this; + } + + StatementBuilder& StatementBuilder::BeginParenthetical() + { + m_stream << '('; + return *this; + } + + StatementBuilder& StatementBuilder::EndParenthetical() + { + m_stream << ')'; + return *this; + } + + StatementBuilder& StatementBuilder::WithoutRowID() + { + m_stream << " WITHOUT ROWID"; + return *this; + } + + + StatementBuilder& StatementBuilder::As(std::string_view alias) + { + OutputOperationAndTable(m_stream, " AS", alias); + return *this; + } + + Statement StatementBuilder::Prepare(const Connection& connection) + { + Statement result = Statement::Create(connection, m_stream.str()); + for (const auto& f : m_binders) + { + f(result); + } + return result; + } + + void StatementBuilder::Execute(const Connection& connection) + { + Prepare(connection).Execute(); + } + + int StatementBuilder::AppendOpAndBinder(Op op, std::optional index) + { + switch (op) + { + case Op::Equals: + m_stream << " = ?"; + break; + case Op::Like: + m_stream << " LIKE ?"; + break; + case Op::Escape: + m_stream << " ESCAPE ?"; + break; + case Op::Literal: + m_stream << " ?"; + break; + case Op::GreaterThan: + m_stream << " > ?"; + break; + case Op::GreaterThanOrEqualTo: + m_stream << " >= ?"; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + if (index) + { + m_stream << index.value(); + } + + return m_bindIndex++; + } + + int StatementBuilder::AppendValuesAndBinders(size_t count) + { + m_stream << " VALUES ("; + for (size_t i = 0; i < count; ++i) + { + m_stream << (i == 0 ? "?" : ", ?"); + } + m_stream << ')'; + + int result = m_bindIndex; + m_bindIndex += static_cast(count); + return result; + } + + int StatementBuilder::AppendValueAndBinder() + { + if (m_needsComma) + { + m_stream << ", "; + } + m_stream << '?'; + m_needsComma = true; + return m_bindIndex++; + } +} diff --git a/src/AppInstallerSharedLib/SQLiteStorageBase.cpp b/src/AppInstallerSharedLib/SQLiteStorageBase.cpp index bdb5fdb680..61d50dfb78 100644 --- a/src/AppInstallerSharedLib/SQLiteStorageBase.cpp +++ b/src/AppInstallerSharedLib/SQLiteStorageBase.cpp @@ -1,185 +1,185 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SQLiteStorageBase.h" -#include "Public/winget/SQLiteMetadataTable.h" -#include "AppInstallerDateTime.h" - -namespace AppInstaller::SQLite -{ - namespace - { - static char const* const GetOpenDispositionString(SQLiteStorageBase::OpenDisposition disposition) - { - switch (disposition) - { - case SQLiteStorageBase::OpenDisposition::Read: - return "Read"; - case SQLiteStorageBase::OpenDisposition::ReadWrite: - return "ReadWrite"; - case SQLiteStorageBase::OpenDisposition::Immutable: - return "ImmutableRead"; - default: - return "Unknown"; - } - } - - std::filesystem::path AddSuffix(const std::filesystem::path& source, std::wstring_view suffix) - { - std::filesystem::path result{ source }; - - if (!suffix.empty()) - { - std::wstring filename = result.filename().wstring(); - filename += suffix; - result.replace_filename(std::move(filename)); - } - - return result; - } - } - - // One method for converting open disposition to proper open disposition - // another method for obtaining the right flags - void SQLiteStorageBase::SetLastWriteTime() - { - MetadataTable::SetNamedValue(m_dbconn, s_MetadataValueName_LastWriteTime, Utility::GetCurrentUnixEpoch()); - } - - // Recording last write time based on MSDN documentation stating that time returns a POSIX epoch time and thus - // should be consistent across systems. - std::chrono::system_clock::time_point SQLiteStorageBase::GetLastWriteTime() const - { - int64_t lastWriteTime = MetadataTable::GetNamedValue(m_dbconn, s_MetadataValueName_LastWriteTime); - return Utility::ConvertUnixEpochToSystemClock(lastWriteTime); - } - - std::string SQLiteStorageBase::GetDatabaseIdentifier() const - { - return MetadataTable::TryGetNamedValue(m_dbconn, s_MetadataValueName_DatabaseIdentifier).value_or(std::string{}); - } - - void SQLiteStorageBase::RenameSQLiteDatabase(const std::filesystem::path& source, const std::filesystem::path& destination, bool overwrite) - { - auto fileSuffixes = { L"", L"-journal", L"-wal" }; - - THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, !std::filesystem::exists(source)); - THROW_WIN32_IF(ERROR_DIRECTORY, std::filesystem::is_directory(source)); - - if (overwrite) - { - for (const auto& suffix : fileSuffixes) - { - std::filesystem::path target = AddSuffix(destination, suffix); - - if (std::filesystem::exists(target)) - { - std::filesystem::remove_all(target); - } - } - } - - for (const auto& suffix : fileSuffixes) - { - std::filesystem::path target = AddSuffix(source, suffix); - - if (std::filesystem::exists(target)) - { - std::filesystem::rename(target, AddSuffix(destination, suffix)); - } - } - } - - SQLiteStorageBase::SQLiteStorageBase(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& file) : - m_indexFile(std::move(file)) - { - AICLI_LOG(Repo, Info, << "Opening database for " << GetOpenDispositionString(disposition) << " at '" << filePath << "'"); - switch (disposition) - { - case OpenDisposition::Read: - m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::None); - break; - case OpenDisposition::ReadWrite: - m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadWrite, SQLite::Connection::OpenFlags::None); - break; - case OpenDisposition::Immutable: - { - // Following the algorithm set forth at https://sqlite.org/uri.html [3.1] to convert to a URI path - // The execution order builds out the string so that it shouldn't require any moves (other than growing) - std::string target; - // Add an 'arbitrary' growth size to prevent the majority of needing to grow (adding 'file:/' and '?immutable=1') - target.reserve(filePath.size() + 20); - - target += "file:"; - - bool wasLastCharSlash = false; - - if (filePath.size() >= 2 && filePath[1] == ':' && - ((filePath[0] >= 'a' && filePath[0] <= 'z') || - (filePath[0] >= 'A' && filePath[0] <= 'Z'))) - { - target += '/'; - wasLastCharSlash = true; - } - - for (char c : filePath) - { - bool wasThisCharSlash = false; - switch (c) - { - case '?': target += "%3f"; break; - case '#': target += "%23"; break; - case '\\': - case '/': - { - wasThisCharSlash = true; - if (!wasLastCharSlash) - { - target += '/'; - } - break; - } - default: target += c; break; - } - - wasLastCharSlash = wasThisCharSlash; - } - - target += "?immutable=1"; - m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::Uri); - break; - } - default: - THROW_HR(E_UNEXPECTED); - } - - m_version = Version::GetSchemaVersion(m_dbconn); - } - - SQLiteStorageBase::SQLiteStorageBase(const std::string& target, const Version& version, size_t pageSize) : - m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)) - { - m_version = version; - if (pageSize > 0) - { - m_dbconn.SetPageSize(pageSize); - } - MetadataTable::Create(m_dbconn); - - // Write a new identifier for this database - GUID databaseIdentifier; - THROW_IF_FAILED(CoCreateGuid(&databaseIdentifier)); - std::ostringstream stream; - stream << databaseIdentifier; - MetadataTable::SetNamedValue(m_dbconn, s_MetadataValueName_DatabaseIdentifier, stream.str()); - } - - SQLiteStorageBase::SQLiteStorageBase(const std::string& target, SQLiteStorageBase& source) : - m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)), - m_version(source.m_version) - { - std::string mainDatabase = "main"; - Backup backup = Backup::Create(m_dbconn, mainDatabase, source.m_dbconn, mainDatabase); - backup.Step(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SQLiteStorageBase.h" +#include "Public/winget/SQLiteMetadataTable.h" +#include "AppInstallerDateTime.h" + +namespace AppInstaller::SQLite +{ + namespace + { + static char const* const GetOpenDispositionString(SQLiteStorageBase::OpenDisposition disposition) + { + switch (disposition) + { + case SQLiteStorageBase::OpenDisposition::Read: + return "Read"; + case SQLiteStorageBase::OpenDisposition::ReadWrite: + return "ReadWrite"; + case SQLiteStorageBase::OpenDisposition::Immutable: + return "ImmutableRead"; + default: + return "Unknown"; + } + } + + std::filesystem::path AddSuffix(const std::filesystem::path& source, std::wstring_view suffix) + { + std::filesystem::path result{ source }; + + if (!suffix.empty()) + { + std::wstring filename = result.filename().wstring(); + filename += suffix; + result.replace_filename(std::move(filename)); + } + + return result; + } + } + + // One method for converting open disposition to proper open disposition + // another method for obtaining the right flags + void SQLiteStorageBase::SetLastWriteTime() + { + MetadataTable::SetNamedValue(m_dbconn, s_MetadataValueName_LastWriteTime, Utility::GetCurrentUnixEpoch()); + } + + // Recording last write time based on MSDN documentation stating that time returns a POSIX epoch time and thus + // should be consistent across systems. + std::chrono::system_clock::time_point SQLiteStorageBase::GetLastWriteTime() const + { + int64_t lastWriteTime = MetadataTable::GetNamedValue(m_dbconn, s_MetadataValueName_LastWriteTime); + return Utility::ConvertUnixEpochToSystemClock(lastWriteTime); + } + + std::string SQLiteStorageBase::GetDatabaseIdentifier() const + { + return MetadataTable::TryGetNamedValue(m_dbconn, s_MetadataValueName_DatabaseIdentifier).value_or(std::string{}); + } + + void SQLiteStorageBase::RenameSQLiteDatabase(const std::filesystem::path& source, const std::filesystem::path& destination, bool overwrite) + { + auto fileSuffixes = { L"", L"-journal", L"-wal" }; + + THROW_WIN32_IF(ERROR_FILE_NOT_FOUND, !std::filesystem::exists(source)); + THROW_WIN32_IF(ERROR_DIRECTORY, std::filesystem::is_directory(source)); + + if (overwrite) + { + for (const auto& suffix : fileSuffixes) + { + std::filesystem::path target = AddSuffix(destination, suffix); + + if (std::filesystem::exists(target)) + { + std::filesystem::remove_all(target); + } + } + } + + for (const auto& suffix : fileSuffixes) + { + std::filesystem::path target = AddSuffix(source, suffix); + + if (std::filesystem::exists(target)) + { + std::filesystem::rename(target, AddSuffix(destination, suffix)); + } + } + } + + SQLiteStorageBase::SQLiteStorageBase(const std::string& filePath, OpenDisposition disposition, Utility::ManagedFile&& file) : + m_indexFile(std::move(file)) + { + AICLI_LOG(Repo, Info, << "Opening database for " << GetOpenDispositionString(disposition) << " at '" << filePath << "'"); + switch (disposition) + { + case OpenDisposition::Read: + m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::None); + break; + case OpenDisposition::ReadWrite: + m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadWrite, SQLite::Connection::OpenFlags::None); + break; + case OpenDisposition::Immutable: + { + // Following the algorithm set forth at https://sqlite.org/uri.html [3.1] to convert to a URI path + // The execution order builds out the string so that it shouldn't require any moves (other than growing) + std::string target; + // Add an 'arbitrary' growth size to prevent the majority of needing to grow (adding 'file:/' and '?immutable=1') + target.reserve(filePath.size() + 20); + + target += "file:"; + + bool wasLastCharSlash = false; + + if (filePath.size() >= 2 && filePath[1] == ':' && + ((filePath[0] >= 'a' && filePath[0] <= 'z') || + (filePath[0] >= 'A' && filePath[0] <= 'Z'))) + { + target += '/'; + wasLastCharSlash = true; + } + + for (char c : filePath) + { + bool wasThisCharSlash = false; + switch (c) + { + case '?': target += "%3f"; break; + case '#': target += "%23"; break; + case '\\': + case '/': + { + wasThisCharSlash = true; + if (!wasLastCharSlash) + { + target += '/'; + } + break; + } + default: target += c; break; + } + + wasLastCharSlash = wasThisCharSlash; + } + + target += "?immutable=1"; + m_dbconn = SQLite::Connection::Create(filePath, SQLite::Connection::OpenDisposition::ReadOnly, SQLite::Connection::OpenFlags::Uri); + break; + } + default: + THROW_HR(E_UNEXPECTED); + } + + m_version = Version::GetSchemaVersion(m_dbconn); + } + + SQLiteStorageBase::SQLiteStorageBase(const std::string& target, const Version& version, size_t pageSize) : + m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)) + { + m_version = version; + if (pageSize > 0) + { + m_dbconn.SetPageSize(pageSize); + } + MetadataTable::Create(m_dbconn); + + // Write a new identifier for this database + GUID databaseIdentifier; + THROW_IF_FAILED(CoCreateGuid(&databaseIdentifier)); + std::ostringstream stream; + stream << databaseIdentifier; + MetadataTable::SetNamedValue(m_dbconn, s_MetadataValueName_DatabaseIdentifier, stream.str()); + } + + SQLiteStorageBase::SQLiteStorageBase(const std::string& target, SQLiteStorageBase& source) : + m_dbconn(SQLite::Connection::Create(target, SQLite::Connection::OpenDisposition::Create)), + m_version(source.m_version) + { + std::string mainDatabase = "main"; + Backup backup = Backup::Create(m_dbconn, mainDatabase, source.m_dbconn, mainDatabase); + backup.Step(); + } +} diff --git a/src/AppInstallerSharedLib/SQLiteTempTable.cpp b/src/AppInstallerSharedLib/SQLiteTempTable.cpp index a86cac0173..5fc40eceb7 100644 --- a/src/AppInstallerSharedLib/SQLiteTempTable.cpp +++ b/src/AppInstallerSharedLib/SQLiteTempTable.cpp @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SQLiteTempTable.h" -#include "AppInstallerStrings.h" - - -namespace AppInstaller::SQLite -{ - using namespace std::string_view_literals; - - TempTable::TempTable() - { - GUID tempName; - THROW_IF_FAILED(CoCreateGuid(&tempName)); - - wchar_t guidAsString[MAX_PATH]; - THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(tempName, guidAsString, MAX_PATH) == 0); - - m_name = Utility::ConvertToUTF8(guidAsString); - } - - TempTable::~TempTable() - { - if (m_dropTableStatement) - { - m_dropTableStatement.Execute(); - } - } - - Builder::QualifiedTable TempTable::GetQualifiedName() const - { - return Builder::QualifiedTable("temp"sv, m_name); - } - - void TempTable::InitDropStatement(const Connection& connection) - { - Builder::StatementBuilder builder; - builder.DropTable(m_name); - - m_dropTableStatement = builder.Prepare(connection); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SQLiteTempTable.h" +#include "AppInstallerStrings.h" + + +namespace AppInstaller::SQLite +{ + using namespace std::string_view_literals; + + TempTable::TempTable() + { + GUID tempName; + THROW_IF_FAILED(CoCreateGuid(&tempName)); + + wchar_t guidAsString[MAX_PATH]; + THROW_HR_IF(E_UNEXPECTED, StringFromGUID2(tempName, guidAsString, MAX_PATH) == 0); + + m_name = Utility::ConvertToUTF8(guidAsString); + } + + TempTable::~TempTable() + { + if (m_dropTableStatement) + { + m_dropTableStatement.Execute(); + } + } + + Builder::QualifiedTable TempTable::GetQualifiedName() const + { + return Builder::QualifiedTable("temp"sv, m_name); + } + + void TempTable::InitDropStatement(const Connection& connection) + { + Builder::StatementBuilder builder; + builder.DropTable(m_name); + + m_dropTableStatement = builder.Prepare(connection); + } +} diff --git a/src/AppInstallerSharedLib/SQLiteVersion.cpp b/src/AppInstallerSharedLib/SQLiteVersion.cpp index 367ecc3dae..92f09a1b17 100644 --- a/src/AppInstallerSharedLib/SQLiteVersion.cpp +++ b/src/AppInstallerSharedLib/SQLiteVersion.cpp @@ -1,64 +1,64 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SQLiteVersion.h" -#include "Public/winget/SQLiteMetadataTable.h" - -#include - -namespace AppInstaller::SQLite -{ - Version Version::GetSchemaVersion(Connection& connection) - { - int major = MetadataTable::GetNamedValue(connection, s_MetadataValueName_MajorVersion); - int minor = MetadataTable::GetNamedValue(connection, s_MetadataValueName_MinorVersion); - - return { static_cast(major), static_cast(minor) }; - } - - void Version::SetSchemaVersion(Connection& connection) const - { - Savepoint savepoint = Savepoint::Create(connection, "version_setschemaversion"); - - MetadataTable::SetNamedValue(connection, s_MetadataValueName_MajorVersion, static_cast(MajorVersion)); - MetadataTable::SetNamedValue(connection, s_MetadataValueName_MinorVersion, static_cast(MinorVersion)); - - savepoint.Commit(); - } - - std::ostream& operator<<(std::ostream& out, const Version& version) - { - if (version.IsLatest()) - { - return out << "Latest"; - } - else if (version.IsLatestForMajor(version.MajorVersion)) - { - return out << version.MajorVersion << ".Latest"; - } - else - { - return out << version.MajorVersion << '.' << version.MinorVersion; - } - } - - Version Version::Latest() - { - return { std::numeric_limits::max(), std::numeric_limits::max() }; - } - - Version Version::LatestForMajor(uint32_t majorVersion) - { - return { majorVersion, std::numeric_limits::max() }; - } - - bool Version::IsLatest() const - { - return (MajorVersion == std::numeric_limits::max() && MinorVersion == std::numeric_limits::max()); - } - - bool Version::IsLatestForMajor(uint32_t majorVersion) const - { - return (MajorVersion == majorVersion && MinorVersion == std::numeric_limits::max()); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SQLiteVersion.h" +#include "Public/winget/SQLiteMetadataTable.h" + +#include + +namespace AppInstaller::SQLite +{ + Version Version::GetSchemaVersion(Connection& connection) + { + int major = MetadataTable::GetNamedValue(connection, s_MetadataValueName_MajorVersion); + int minor = MetadataTable::GetNamedValue(connection, s_MetadataValueName_MinorVersion); + + return { static_cast(major), static_cast(minor) }; + } + + void Version::SetSchemaVersion(Connection& connection) const + { + Savepoint savepoint = Savepoint::Create(connection, "version_setschemaversion"); + + MetadataTable::SetNamedValue(connection, s_MetadataValueName_MajorVersion, static_cast(MajorVersion)); + MetadataTable::SetNamedValue(connection, s_MetadataValueName_MinorVersion, static_cast(MinorVersion)); + + savepoint.Commit(); + } + + std::ostream& operator<<(std::ostream& out, const Version& version) + { + if (version.IsLatest()) + { + return out << "Latest"; + } + else if (version.IsLatestForMajor(version.MajorVersion)) + { + return out << version.MajorVersion << ".Latest"; + } + else + { + return out << version.MajorVersion << '.' << version.MinorVersion; + } + } + + Version Version::Latest() + { + return { std::numeric_limits::max(), std::numeric_limits::max() }; + } + + Version Version::LatestForMajor(uint32_t majorVersion) + { + return { majorVersion, std::numeric_limits::max() }; + } + + bool Version::IsLatest() const + { + return (MajorVersion == std::numeric_limits::max() && MinorVersion == std::numeric_limits::max()); + } + + bool Version::IsLatestForMajor(uint32_t majorVersion) const + { + return (MajorVersion == majorVersion && MinorVersion == std::numeric_limits::max()); + } +} diff --git a/src/AppInstallerSharedLib/SQLiteWrapper.cpp b/src/AppInstallerSharedLib/SQLiteWrapper.cpp index d0a79870b8..78df7a5b9c 100644 --- a/src/AppInstallerSharedLib/SQLiteWrapper.cpp +++ b/src/AppInstallerSharedLib/SQLiteWrapper.cpp @@ -1,574 +1,574 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SQLiteWrapper.h" -#include "Public/AppInstallerErrors.h" -#include "Public/AppInstallerStrings.h" -#include "ICU/SQLiteICU.h" - -#include - -using namespace std::chrono_literals; -using namespace std::string_view_literals; - -// Enable this to have all Statement constructions output the associated query plan. -#define WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED 0 - -#if WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED -#include -#endif - -// Connection is used twice -#define SQLITE_ERROR_MSG(_error_,_connection_) (_connection_ ? sqlite3_errmsg(_connection_) : sqlite3_errstr(_error_)) - -#define THROW_SQLITE(_error_,_connection_) \ - do { \ - int _ts_sqliteReturnValue = (_error_); \ - sqlite3* _ts_sqliteConnection = (_connection_); \ - THROW_EXCEPTION_MSG(SQLiteException(_ts_sqliteReturnValue), "%hs", SQLITE_ERROR_MSG(_ts_sqliteReturnValue, _ts_sqliteConnection)); \ - } while (0,0) - -#define THROW_IF_SQLITE_FAILED(_statement_,_connection_) \ - do { \ - int _tisf_sqliteReturnValue = (_statement_); \ - if (_tisf_sqliteReturnValue != SQLITE_OK) \ - { \ - THROW_SQLITE(_tisf_sqliteReturnValue,_connection_); \ - } \ - } while (0,0) - -namespace AppInstaller::SQLite -{ - std::string_view RowIDName = "rowid"sv; - - namespace - { - size_t GetNextConnectionId() - { - static std::atomic_size_t connectionId(0); - return ++connectionId; - } - - size_t GetNextStatementId() - { - static std::atomic_size_t statementId(0); - return ++statementId; - } - } - - namespace details - { - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, nullptr_t) - { - THROW_IF_SQLITE_FAILED(sqlite3_bind_null(stmt, index), sqlite3_db_handle(stmt)); - } - - void ThrowIfContainsEmbeddedNullCharacter(std::string_view v) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL, v.find('\0') != std::string_view::npos); - } - - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, const std::string& v) - { - ThrowIfContainsEmbeddedNullCharacter(v); - THROW_IF_SQLITE_FAILED(sqlite3_bind_text64(stmt, index, v.c_str(), v.size(), SQLITE_TRANSIENT, SQLITE_UTF8), sqlite3_db_handle(stmt)); - } - - std::string ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) - { - return reinterpret_cast(sqlite3_column_text(stmt, column)); - } - - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, std::string_view v) - { - if (v.empty()) - { - // An empty string_view can have it's data member return nullptr, which effectively binds a null value. - // We don't want that, so instead bind an empty string, which will have a non-null data pointer. - ParameterSpecificsImpl::Bind(stmt, index, {}); - } - else - { - ThrowIfContainsEmbeddedNullCharacter(v); - THROW_IF_SQLITE_FAILED(sqlite3_bind_text64(stmt, index, v.data(), v.size(), SQLITE_TRANSIENT, SQLITE_UTF8), sqlite3_db_handle(stmt)); - } - } - - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, int v) - { - THROW_IF_SQLITE_FAILED(sqlite3_bind_int(stmt, index, v), sqlite3_db_handle(stmt)); - } - - int ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) - { - return sqlite3_column_int(stmt, column); - } - - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, int64_t v) - { - THROW_IF_SQLITE_FAILED(sqlite3_bind_int64(stmt, index, v), sqlite3_db_handle(stmt)); - } - - int64_t ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) - { - return sqlite3_column_int64(stmt, column); - } - - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, bool v) - { - THROW_IF_SQLITE_FAILED(sqlite3_bind_int(stmt, index, (v ? 1 : 0)), sqlite3_db_handle(stmt)); - } - - bool ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) - { - return (sqlite3_column_int(stmt, column) != 0); - } - - std::string ParameterSpecificsImpl::ToLog(const blob_t& v) - { - std::ostringstream strstr; - strstr << "blob[" << v.size() << "]"; - return strstr.str(); - } - - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, const blob_t& v) - { - THROW_IF_SQLITE_FAILED(sqlite3_bind_blob64(stmt, index, v.data(), v.size(), SQLITE_TRANSIENT), sqlite3_db_handle(stmt)); - } - - blob_t ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) - { - const blob_t::value_type* blobPtr = reinterpret_cast(sqlite3_column_blob(stmt, column)); - if (blobPtr) - { - int blobBytes = sqlite3_column_bytes(stmt, column); - return blob_t{ blobPtr, blobPtr + blobBytes }; - } - else - { - return {}; - } - } - - std::string ParameterSpecificsImpl::ToLog(const GUID& v) - { - std::ostringstream strstr; - strstr << v; - return strstr.str(); - } - - void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, const GUID& v) - { - static_assert(sizeof(v) == 16); - THROW_IF_SQLITE_FAILED(sqlite3_bind_blob64(stmt, index, &v, sizeof(v), SQLITE_TRANSIENT), sqlite3_db_handle(stmt)); - } - - GUID ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) - { - GUID result{}; - - const void* blobPtr = sqlite3_column_blob(stmt, column); - if (blobPtr) - { - result = *reinterpret_cast(blobPtr); - } - - return result; - } - - void SharedConnection::Disable() - { - m_active = false; - } - - sqlite3* SharedConnection::Get() const - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED, !m_active.load()); - return m_dbconn.get(); - } - - sqlite3** SharedConnection::GetPtr() - { - return &m_dbconn; - } - } - - Connection::Connection(const std::string& target, OpenDisposition disposition, OpenFlags flags) - { - m_dbconn = std::make_shared(); - m_id = GetNextConnectionId(); - AICLI_LOG(SQL, Info, << "Opening SQLite connection #" << m_id << ": '" << target << "' [" << std::hex << static_cast(disposition) << ", " << std::hex << static_cast(flags) << "]"); - // Always force connection serialization until we determine that there are situations where it is not needed - int resultingFlags = static_cast(disposition) | static_cast(flags) | SQLITE_OPEN_FULLMUTEX; - THROW_IF_SQLITE_FAILED(sqlite3_open_v2(target.c_str(), m_dbconn->GetPtr(), resultingFlags, nullptr), nullptr); - } - - Connection Connection::Create(const std::string& target, OpenDisposition disposition, OpenFlags flags) - { - Connection result{ target, disposition, flags }; - - THROW_IF_SQLITE_FAILED(sqlite3_extended_result_codes(result.m_dbconn->Get(), 1), result.m_dbconn->Get()); - result.SetBusyTimeout(250ms); - - return result; - } - - void Connection::EnableICU() - { - AICLI_LOG(SQL, Verbose, << "Enabling ICU"); - THROW_IF_SQLITE_FAILED(sqlite3IcuInit(m_dbconn->Get()), m_dbconn->Get()); - } - - rowid_t Connection::GetLastInsertRowID() - { - return sqlite3_last_insert_rowid(m_dbconn->Get()); - } - - int Connection::GetChanges() const - { - return sqlite3_changes(m_dbconn->Get()); - } - - size_t Connection::GetID() const - { - return m_id; - } - - void Connection::SetBusyTimeout(std::chrono::milliseconds timeout) - { - THROW_IF_SQLITE_FAILED(sqlite3_busy_timeout(m_dbconn->Get(), static_cast(timeout.count())), m_dbconn->Get()); - } - - bool Connection::SetJournalMode(std::string_view mode) - { - using namespace AppInstaller::Utility; - - std::ostringstream stream; - stream << "PRAGMA journal_mode=" << mode; - - Statement setJournalMode = Statement::Create(*this, stream.str()); - THROW_HR_IF(E_UNEXPECTED, !setJournalMode.Step()); - return ToLower(setJournalMode.GetColumn(0)) == ToLower(mode); - } - - void Connection::SetPageSize(size_t pageSize) - { - std::ostringstream stream; - stream << "PRAGMA page_size=" << pageSize; - - Statement setPageSize = Statement::Create(*this, stream.str()); - setPageSize.Step(); - } - - std::shared_ptr Connection::GetSharedConnection() const - { - return m_dbconn; - } - - Statement::Statement(const Connection& connection, std::string_view sql) - { - m_dbconn = connection.GetSharedConnection(); - m_connectionId = connection.GetID(); - m_id = GetNextStatementId(); - AICLI_LOG(SQL, Verbose, << "Preparing statement #" << m_connectionId << '-' << m_id << ": " << sql); - // SQL string size should include the null terminator (https://www.sqlite.org/c3ref/prepare.html) - assert(sql.data()[sql.size()] == '\0'); - THROW_IF_SQLITE_FAILED(sqlite3_prepare_v2(connection, sql.data(), static_cast(sql.size() + 1), &m_stmt, nullptr), connection); - } - -#if WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED -#define WINGET_SQLITE_EXPLAIN_QUERY_PLAN(_connection_,_sql_) \ - std::string _explainStatementSQL_ = "EXPLAIN QUERY PLAN "; \ - _explainStatementSQL_.append(_sql_); \ - try { \ - Statement _explainStatement_(_connection_,_explainStatementSQL_); \ - LogExplainQueryPlanResult(_sql_, _explainStatement_); \ - } catch(...) {} - - void LogExplainQueryPlanResult(std::string_view sql, Statement& plan) - { - bool outputHeader = true; - std::stack parents; - - while (plan.Step()) - { - if (outputHeader) - { - AICLI_LOG(SQL, Info, << "Query plan for: " << sql); - outputHeader = false; - } - - int id = plan.GetColumn(0); - int parent = plan.GetColumn(1); - - while (!parents.empty() && parents.top() != parent) - { - parents.pop(); - } - - AICLI_LOG(SQL, Info, << "|-" << std::string(parents.size() * 2, '-') << ' ' << plan.GetColumn(3)); - - parents.push(id); - } - } -#else -#define WINGET_SQLITE_EXPLAIN_QUERY_PLAN(_connection_,_sql_) -#endif - - Statement Statement::Create(const Connection& connection, const std::string& sql) - { - WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); - return { connection, { sql.c_str(), sql.size() } }; - } - - Statement Statement::Create(const Connection& connection, std::string_view sql) - { - WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); - // We need the statement to be null terminated, and the only way to guarantee that with a string_view is to construct a string copy. - return Create(connection, std::string(sql)); - } - - Statement Statement::Create(const Connection& connection, char const* const sql) - { - WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); - return { connection, sql }; - } - - bool Statement::Step(bool closeConnectionOnError) - { - AICLI_LOG(SQL, Verbose, << "Stepping statement #" << m_connectionId << '-' << m_id); - int result = sqlite3_step(m_stmt.get()); - - if (result == SQLITE_ROW) - { - AICLI_LOG(SQL, Verbose, << "Statement #" << m_connectionId << '-' << m_id << " has data"); - m_state = State::HasRow; - return true; - } - else if (result == SQLITE_DONE) - { - AICLI_LOG(SQL, Verbose, << "Statement #" << m_connectionId << '-' << m_id << " has completed"); - m_state = State::Completed; - return false; - } - else - { - m_state = State::Error; - - if (closeConnectionOnError) - { - m_dbconn->Disable(); - } - - THROW_SQLITE(result, sqlite3_db_handle(m_stmt.get())); - } - } - - void Statement::Execute(bool closeConnectionOnError) - { - THROW_HR_IF(E_UNEXPECTED, Step(closeConnectionOnError)); - } - - bool Statement::GetColumnIsNull(int column) - { - int type = sqlite3_column_type(m_stmt.get(), column); - return type == SQLITE_NULL; - } - - void Statement::Reset() - { - AICLI_LOG(SQL, Verbose, << "Reset statement #" << m_connectionId << '-' << m_id); - // Ignore return value from reset, as if it is an error, it was the error from the last call to step. - sqlite3_reset(m_stmt.get()); - m_state = State::Prepared; - } - - Transaction::Transaction() : m_inProgress(false) - {} - - Transaction::Transaction(Connection& connection, std::string&& name, bool immediateWrite) : - m_name(std::move(name)) - { - using namespace std::string_literals; - - Statement begin = Statement::Create(connection, "BEGIN "s + (immediateWrite ? "IMMEDIATE" : "DEFERRED")); - m_rollback = Statement::Create(connection, "ROLLBACK"); - m_commit = Statement::Create(connection, "COMMIT"); - - AICLI_LOG(SQL, Verbose, << "Begin transaction: " << m_name); - begin.Step(); - } - - Transaction Transaction::Create(Connection& connection, std::string name, bool immediateWrite) - { - return { connection, std::move(name), immediateWrite }; - } - - Transaction::~Transaction() - { - // Prevent a termination by not throwing on errors here - Rollback(false); - } - - void Transaction::Rollback(bool throwOnError) - { - if (m_inProgress) - { - // Only try rollback once - m_inProgress = false; - - try - { - AICLI_LOG(SQL, Verbose, << "Roll back transaction: " << m_name); - m_rollback.Step(true); - } - catch (...) - { - if (throwOnError) - { - throw; - } - - LOG_CAUGHT_EXCEPTION(); - } - } - } - - void Transaction::Commit() - { - if (m_inProgress) - { - AICLI_LOG(SQL, Verbose, << "Commit transaction: " << m_name); - m_commit.Step(); - m_inProgress = false; - } - } - - Savepoint::Savepoint() : m_inProgress(false) - {} - - Savepoint::Savepoint(Connection& connection, std::string&& name) : - m_name(std::move(name)) - { - using namespace std::string_literals; - - Statement begin = Statement::Create(connection, "SAVEPOINT ["s + m_name + "]"); - m_rollbackTo = Statement::Create(connection, "ROLLBACK TO ["s + m_name + "]"); - m_release = Statement::Create(connection, "RELEASE ["s + m_name + "]"); - - AICLI_LOG(SQL, Verbose, << "Begin savepoint: " << m_name); - begin.Step(); - } - - Savepoint Savepoint::Create(Connection& connection, std::string name) - { - return { connection, std::move(name) }; - } - - Savepoint::~Savepoint() - { - // Prevent a termination by not throwing on errors here - Rollback(false); - } - - void Savepoint::Rollback(bool throwOnError) - { - if (m_inProgress) - { - // Only try rollback once - m_inProgress = false; - - try - { - AICLI_LOG(SQL, Verbose, << "Roll back savepoint: " << m_name); - m_rollbackTo.Step(true); - // 'ROLLBACK TO' *DOES NOT* remove the savepoint from the transaction stack. - // In order to remove it, we must RELEASE. Since we just invoked a ROLLBACK TO - // this should have the effect of 'committing' nothing. - m_release.Step(true); - } - catch (...) - { - if (throwOnError) - { - throw; - } - - LOG_CAUGHT_EXCEPTION(); - } - } - } - - void Savepoint::Commit() - { - if (m_inProgress) - { - AICLI_LOG(SQL, Verbose, << "Commit savepoint: " << m_name); - m_release.Step(); - m_inProgress = false; - } - } - - Backup::Backup(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName) - { - m_backup.reset(sqlite3_backup_init(destination, destinationName.c_str(), source, sourceName.c_str())); - - if (!m_backup) - { - THROW_SQLITE(sqlite3_errcode(destination), destination); - } - } - - Backup Backup::Create(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName) - { - return { destination, destinationName, source, sourceName }; - } - - bool Backup::Step(int pages) - { - int stepResult = sqlite3_backup_step(m_backup.get(), pages); - - if (stepResult == SQLITE_OK) - { - // A negative number of pages should finish the operation - if (pages < 0) - { - THROW_HR(E_UNEXPECTED); - } - - // Success but not done - return false; - } - else if (stepResult == SQLITE_DONE) - { - return true; - } - else - { - THROW_SQLITE(stepResult, nullptr); - } - } - - std::string_view EscapeCharForLike = "'"sv; - - std::string EscapeStringForLike(std::string_view value) - { - constexpr char singleChar = '_'; - constexpr char multiChar = '%'; - char escapeChar = EscapeCharForLike[0]; - - std::string result; - result.reserve(value.length()); - - for (char c : value) - { - if (c == singleChar || c == multiChar || c == escapeChar) - { - result.append(1, escapeChar); - } - result.append(1, c); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SQLiteWrapper.h" +#include "Public/AppInstallerErrors.h" +#include "Public/AppInstallerStrings.h" +#include "ICU/SQLiteICU.h" + +#include + +using namespace std::chrono_literals; +using namespace std::string_view_literals; + +// Enable this to have all Statement constructions output the associated query plan. +#define WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED 0 + +#if WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED +#include +#endif + +// Connection is used twice +#define SQLITE_ERROR_MSG(_error_,_connection_) (_connection_ ? sqlite3_errmsg(_connection_) : sqlite3_errstr(_error_)) + +#define THROW_SQLITE(_error_,_connection_) \ + do { \ + int _ts_sqliteReturnValue = (_error_); \ + sqlite3* _ts_sqliteConnection = (_connection_); \ + THROW_EXCEPTION_MSG(SQLiteException(_ts_sqliteReturnValue), "%hs", SQLITE_ERROR_MSG(_ts_sqliteReturnValue, _ts_sqliteConnection)); \ + } while (0,0) + +#define THROW_IF_SQLITE_FAILED(_statement_,_connection_) \ + do { \ + int _tisf_sqliteReturnValue = (_statement_); \ + if (_tisf_sqliteReturnValue != SQLITE_OK) \ + { \ + THROW_SQLITE(_tisf_sqliteReturnValue,_connection_); \ + } \ + } while (0,0) + +namespace AppInstaller::SQLite +{ + std::string_view RowIDName = "rowid"sv; + + namespace + { + size_t GetNextConnectionId() + { + static std::atomic_size_t connectionId(0); + return ++connectionId; + } + + size_t GetNextStatementId() + { + static std::atomic_size_t statementId(0); + return ++statementId; + } + } + + namespace details + { + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, nullptr_t) + { + THROW_IF_SQLITE_FAILED(sqlite3_bind_null(stmt, index), sqlite3_db_handle(stmt)); + } + + void ThrowIfContainsEmbeddedNullCharacter(std::string_view v) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_BIND_WITH_EMBEDDED_NULL, v.find('\0') != std::string_view::npos); + } + + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, const std::string& v) + { + ThrowIfContainsEmbeddedNullCharacter(v); + THROW_IF_SQLITE_FAILED(sqlite3_bind_text64(stmt, index, v.c_str(), v.size(), SQLITE_TRANSIENT, SQLITE_UTF8), sqlite3_db_handle(stmt)); + } + + std::string ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) + { + return reinterpret_cast(sqlite3_column_text(stmt, column)); + } + + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, std::string_view v) + { + if (v.empty()) + { + // An empty string_view can have it's data member return nullptr, which effectively binds a null value. + // We don't want that, so instead bind an empty string, which will have a non-null data pointer. + ParameterSpecificsImpl::Bind(stmt, index, {}); + } + else + { + ThrowIfContainsEmbeddedNullCharacter(v); + THROW_IF_SQLITE_FAILED(sqlite3_bind_text64(stmt, index, v.data(), v.size(), SQLITE_TRANSIENT, SQLITE_UTF8), sqlite3_db_handle(stmt)); + } + } + + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, int v) + { + THROW_IF_SQLITE_FAILED(sqlite3_bind_int(stmt, index, v), sqlite3_db_handle(stmt)); + } + + int ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) + { + return sqlite3_column_int(stmt, column); + } + + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, int64_t v) + { + THROW_IF_SQLITE_FAILED(sqlite3_bind_int64(stmt, index, v), sqlite3_db_handle(stmt)); + } + + int64_t ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) + { + return sqlite3_column_int64(stmt, column); + } + + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, bool v) + { + THROW_IF_SQLITE_FAILED(sqlite3_bind_int(stmt, index, (v ? 1 : 0)), sqlite3_db_handle(stmt)); + } + + bool ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) + { + return (sqlite3_column_int(stmt, column) != 0); + } + + std::string ParameterSpecificsImpl::ToLog(const blob_t& v) + { + std::ostringstream strstr; + strstr << "blob[" << v.size() << "]"; + return strstr.str(); + } + + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, const blob_t& v) + { + THROW_IF_SQLITE_FAILED(sqlite3_bind_blob64(stmt, index, v.data(), v.size(), SQLITE_TRANSIENT), sqlite3_db_handle(stmt)); + } + + blob_t ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) + { + const blob_t::value_type* blobPtr = reinterpret_cast(sqlite3_column_blob(stmt, column)); + if (blobPtr) + { + int blobBytes = sqlite3_column_bytes(stmt, column); + return blob_t{ blobPtr, blobPtr + blobBytes }; + } + else + { + return {}; + } + } + + std::string ParameterSpecificsImpl::ToLog(const GUID& v) + { + std::ostringstream strstr; + strstr << v; + return strstr.str(); + } + + void ParameterSpecificsImpl::Bind(sqlite3_stmt* stmt, int index, const GUID& v) + { + static_assert(sizeof(v) == 16); + THROW_IF_SQLITE_FAILED(sqlite3_bind_blob64(stmt, index, &v, sizeof(v), SQLITE_TRANSIENT), sqlite3_db_handle(stmt)); + } + + GUID ParameterSpecificsImpl::GetColumn(sqlite3_stmt* stmt, int column) + { + GUID result{}; + + const void* blobPtr = sqlite3_column_blob(stmt, column); + if (blobPtr) + { + result = *reinterpret_cast(blobPtr); + } + + return result; + } + + void SharedConnection::Disable() + { + m_active = false; + } + + sqlite3* SharedConnection::Get() const + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SQLITE_CONNECTION_TERMINATED, !m_active.load()); + return m_dbconn.get(); + } + + sqlite3** SharedConnection::GetPtr() + { + return &m_dbconn; + } + } + + Connection::Connection(const std::string& target, OpenDisposition disposition, OpenFlags flags) + { + m_dbconn = std::make_shared(); + m_id = GetNextConnectionId(); + AICLI_LOG(SQL, Info, << "Opening SQLite connection #" << m_id << ": '" << target << "' [" << std::hex << static_cast(disposition) << ", " << std::hex << static_cast(flags) << "]"); + // Always force connection serialization until we determine that there are situations where it is not needed + int resultingFlags = static_cast(disposition) | static_cast(flags) | SQLITE_OPEN_FULLMUTEX; + THROW_IF_SQLITE_FAILED(sqlite3_open_v2(target.c_str(), m_dbconn->GetPtr(), resultingFlags, nullptr), nullptr); + } + + Connection Connection::Create(const std::string& target, OpenDisposition disposition, OpenFlags flags) + { + Connection result{ target, disposition, flags }; + + THROW_IF_SQLITE_FAILED(sqlite3_extended_result_codes(result.m_dbconn->Get(), 1), result.m_dbconn->Get()); + result.SetBusyTimeout(250ms); + + return result; + } + + void Connection::EnableICU() + { + AICLI_LOG(SQL, Verbose, << "Enabling ICU"); + THROW_IF_SQLITE_FAILED(sqlite3IcuInit(m_dbconn->Get()), m_dbconn->Get()); + } + + rowid_t Connection::GetLastInsertRowID() + { + return sqlite3_last_insert_rowid(m_dbconn->Get()); + } + + int Connection::GetChanges() const + { + return sqlite3_changes(m_dbconn->Get()); + } + + size_t Connection::GetID() const + { + return m_id; + } + + void Connection::SetBusyTimeout(std::chrono::milliseconds timeout) + { + THROW_IF_SQLITE_FAILED(sqlite3_busy_timeout(m_dbconn->Get(), static_cast(timeout.count())), m_dbconn->Get()); + } + + bool Connection::SetJournalMode(std::string_view mode) + { + using namespace AppInstaller::Utility; + + std::ostringstream stream; + stream << "PRAGMA journal_mode=" << mode; + + Statement setJournalMode = Statement::Create(*this, stream.str()); + THROW_HR_IF(E_UNEXPECTED, !setJournalMode.Step()); + return ToLower(setJournalMode.GetColumn(0)) == ToLower(mode); + } + + void Connection::SetPageSize(size_t pageSize) + { + std::ostringstream stream; + stream << "PRAGMA page_size=" << pageSize; + + Statement setPageSize = Statement::Create(*this, stream.str()); + setPageSize.Step(); + } + + std::shared_ptr Connection::GetSharedConnection() const + { + return m_dbconn; + } + + Statement::Statement(const Connection& connection, std::string_view sql) + { + m_dbconn = connection.GetSharedConnection(); + m_connectionId = connection.GetID(); + m_id = GetNextStatementId(); + AICLI_LOG(SQL, Verbose, << "Preparing statement #" << m_connectionId << '-' << m_id << ": " << sql); + // SQL string size should include the null terminator (https://www.sqlite.org/c3ref/prepare.html) + assert(sql.data()[sql.size()] == '\0'); + THROW_IF_SQLITE_FAILED(sqlite3_prepare_v2(connection, sql.data(), static_cast(sql.size() + 1), &m_stmt, nullptr), connection); + } + +#if WINGET_SQLITE_EXPLAIN_QUERY_PLAN_ENABLED +#define WINGET_SQLITE_EXPLAIN_QUERY_PLAN(_connection_,_sql_) \ + std::string _explainStatementSQL_ = "EXPLAIN QUERY PLAN "; \ + _explainStatementSQL_.append(_sql_); \ + try { \ + Statement _explainStatement_(_connection_,_explainStatementSQL_); \ + LogExplainQueryPlanResult(_sql_, _explainStatement_); \ + } catch(...) {} + + void LogExplainQueryPlanResult(std::string_view sql, Statement& plan) + { + bool outputHeader = true; + std::stack parents; + + while (plan.Step()) + { + if (outputHeader) + { + AICLI_LOG(SQL, Info, << "Query plan for: " << sql); + outputHeader = false; + } + + int id = plan.GetColumn(0); + int parent = plan.GetColumn(1); + + while (!parents.empty() && parents.top() != parent) + { + parents.pop(); + } + + AICLI_LOG(SQL, Info, << "|-" << std::string(parents.size() * 2, '-') << ' ' << plan.GetColumn(3)); + + parents.push(id); + } + } +#else +#define WINGET_SQLITE_EXPLAIN_QUERY_PLAN(_connection_,_sql_) +#endif + + Statement Statement::Create(const Connection& connection, const std::string& sql) + { + WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); + return { connection, { sql.c_str(), sql.size() } }; + } + + Statement Statement::Create(const Connection& connection, std::string_view sql) + { + WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); + // We need the statement to be null terminated, and the only way to guarantee that with a string_view is to construct a string copy. + return Create(connection, std::string(sql)); + } + + Statement Statement::Create(const Connection& connection, char const* const sql) + { + WINGET_SQLITE_EXPLAIN_QUERY_PLAN(connection, sql); + return { connection, sql }; + } + + bool Statement::Step(bool closeConnectionOnError) + { + AICLI_LOG(SQL, Verbose, << "Stepping statement #" << m_connectionId << '-' << m_id); + int result = sqlite3_step(m_stmt.get()); + + if (result == SQLITE_ROW) + { + AICLI_LOG(SQL, Verbose, << "Statement #" << m_connectionId << '-' << m_id << " has data"); + m_state = State::HasRow; + return true; + } + else if (result == SQLITE_DONE) + { + AICLI_LOG(SQL, Verbose, << "Statement #" << m_connectionId << '-' << m_id << " has completed"); + m_state = State::Completed; + return false; + } + else + { + m_state = State::Error; + + if (closeConnectionOnError) + { + m_dbconn->Disable(); + } + + THROW_SQLITE(result, sqlite3_db_handle(m_stmt.get())); + } + } + + void Statement::Execute(bool closeConnectionOnError) + { + THROW_HR_IF(E_UNEXPECTED, Step(closeConnectionOnError)); + } + + bool Statement::GetColumnIsNull(int column) + { + int type = sqlite3_column_type(m_stmt.get(), column); + return type == SQLITE_NULL; + } + + void Statement::Reset() + { + AICLI_LOG(SQL, Verbose, << "Reset statement #" << m_connectionId << '-' << m_id); + // Ignore return value from reset, as if it is an error, it was the error from the last call to step. + sqlite3_reset(m_stmt.get()); + m_state = State::Prepared; + } + + Transaction::Transaction() : m_inProgress(false) + {} + + Transaction::Transaction(Connection& connection, std::string&& name, bool immediateWrite) : + m_name(std::move(name)) + { + using namespace std::string_literals; + + Statement begin = Statement::Create(connection, "BEGIN "s + (immediateWrite ? "IMMEDIATE" : "DEFERRED")); + m_rollback = Statement::Create(connection, "ROLLBACK"); + m_commit = Statement::Create(connection, "COMMIT"); + + AICLI_LOG(SQL, Verbose, << "Begin transaction: " << m_name); + begin.Step(); + } + + Transaction Transaction::Create(Connection& connection, std::string name, bool immediateWrite) + { + return { connection, std::move(name), immediateWrite }; + } + + Transaction::~Transaction() + { + // Prevent a termination by not throwing on errors here + Rollback(false); + } + + void Transaction::Rollback(bool throwOnError) + { + if (m_inProgress) + { + // Only try rollback once + m_inProgress = false; + + try + { + AICLI_LOG(SQL, Verbose, << "Roll back transaction: " << m_name); + m_rollback.Step(true); + } + catch (...) + { + if (throwOnError) + { + throw; + } + + LOG_CAUGHT_EXCEPTION(); + } + } + } + + void Transaction::Commit() + { + if (m_inProgress) + { + AICLI_LOG(SQL, Verbose, << "Commit transaction: " << m_name); + m_commit.Step(); + m_inProgress = false; + } + } + + Savepoint::Savepoint() : m_inProgress(false) + {} + + Savepoint::Savepoint(Connection& connection, std::string&& name) : + m_name(std::move(name)) + { + using namespace std::string_literals; + + Statement begin = Statement::Create(connection, "SAVEPOINT ["s + m_name + "]"); + m_rollbackTo = Statement::Create(connection, "ROLLBACK TO ["s + m_name + "]"); + m_release = Statement::Create(connection, "RELEASE ["s + m_name + "]"); + + AICLI_LOG(SQL, Verbose, << "Begin savepoint: " << m_name); + begin.Step(); + } + + Savepoint Savepoint::Create(Connection& connection, std::string name) + { + return { connection, std::move(name) }; + } + + Savepoint::~Savepoint() + { + // Prevent a termination by not throwing on errors here + Rollback(false); + } + + void Savepoint::Rollback(bool throwOnError) + { + if (m_inProgress) + { + // Only try rollback once + m_inProgress = false; + + try + { + AICLI_LOG(SQL, Verbose, << "Roll back savepoint: " << m_name); + m_rollbackTo.Step(true); + // 'ROLLBACK TO' *DOES NOT* remove the savepoint from the transaction stack. + // In order to remove it, we must RELEASE. Since we just invoked a ROLLBACK TO + // this should have the effect of 'committing' nothing. + m_release.Step(true); + } + catch (...) + { + if (throwOnError) + { + throw; + } + + LOG_CAUGHT_EXCEPTION(); + } + } + } + + void Savepoint::Commit() + { + if (m_inProgress) + { + AICLI_LOG(SQL, Verbose, << "Commit savepoint: " << m_name); + m_release.Step(); + m_inProgress = false; + } + } + + Backup::Backup(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName) + { + m_backup.reset(sqlite3_backup_init(destination, destinationName.c_str(), source, sourceName.c_str())); + + if (!m_backup) + { + THROW_SQLITE(sqlite3_errcode(destination), destination); + } + } + + Backup Backup::Create(Connection& destination, const std::string& destinationName, Connection& source, const std::string& sourceName) + { + return { destination, destinationName, source, sourceName }; + } + + bool Backup::Step(int pages) + { + int stepResult = sqlite3_backup_step(m_backup.get(), pages); + + if (stepResult == SQLITE_OK) + { + // A negative number of pages should finish the operation + if (pages < 0) + { + THROW_HR(E_UNEXPECTED); + } + + // Success but not done + return false; + } + else if (stepResult == SQLITE_DONE) + { + return true; + } + else + { + THROW_SQLITE(stepResult, nullptr); + } + } + + std::string_view EscapeCharForLike = "'"sv; + + std::string EscapeStringForLike(std::string_view value) + { + constexpr char singleChar = '_'; + constexpr char multiChar = '%'; + char escapeChar = EscapeCharForLike[0]; + + std::string result; + result.reserve(value.length()); + + for (char c : value) + { + if (c == singleChar || c == multiChar || c == escapeChar) + { + result.append(1, escapeChar); + } + result.append(1, c); + } + + return result; + } +} diff --git a/src/AppInstallerSharedLib/Security.cpp b/src/AppInstallerSharedLib/Security.cpp index 8915f8de9c..7abff9949e 100644 --- a/src/AppInstallerSharedLib/Security.cpp +++ b/src/AppInstallerSharedLib/Security.cpp @@ -1,140 +1,140 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "winget/Security.h" -#include "AppInstallerLogging.h" -#include "AppInstallerLanguageUtilities.h" - -namespace AppInstaller::Security -{ - namespace - { - bool IsSameAuthority(const SID_IDENTIFIER_AUTHORITY& a, const SID_IDENTIFIER_AUTHORITY& b) - { - for (size_t i = 0; i < ARRAYSIZE(a.Value); ++i) - { - if (a.Value[i] != b.Value[i]) - { - return false; - } - } - - return true; - } - - // Helper to impersonate the COM or RPC caller. - struct ImpersonateCOMorRPCCaller - { - static ImpersonateCOMorRPCCaller BeginImpersonation() - { - return {}; - } - - ~ImpersonateCOMorRPCCaller() - { - if (m_serverSecurity) - { - FAIL_FAST_IF_FAILED(m_serverSecurity->RevertToSelf()); - } - else - { - FAIL_FAST_IF(RpcRevertToSelf() != RPC_S_OK); - } - } - - private: - ImpersonateCOMorRPCCaller() - { - if (SUCCEEDED_LOG(CoGetCallContext(IID_IServerSecurity, m_serverSecurity.put_void()))) - { - THROW_IF_FAILED(m_serverSecurity->ImpersonateClient()); - } - else - { - RPC_STATUS status = RpcImpersonateClient(nullptr); - THROW_HR_IF(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_RPC, status), status != RPC_S_OK); - } - } - - wil::com_ptr m_serverSecurity; - }; - } - - IntegrityLevel GetEffectiveIntegrityLevel() - { - auto currentIntegrityLevel = wil::get_token_information(); - PSID sid = currentIntegrityLevel->Label.Sid; - THROW_HR_IF(CO_E_INVALIDSID, !IsValidSid(sid)); - - auto identifierAuthority = GetSidIdentifierAuthority(sid); - THROW_HR_IF(E_UNEXPECTED, !IsSameAuthority(*identifierAuthority, SECURITY_MANDATORY_LABEL_AUTHORITY)); - - PUCHAR subAuthorityCount = GetSidSubAuthorityCount(sid); - THROW_HR_IF(E_UNEXPECTED, *subAuthorityCount != 1); - - PDWORD subAuthority = GetSidSubAuthority(sid, 0); - - switch (*subAuthority) - { - case SECURITY_MANDATORY_UNTRUSTED_RID: return IntegrityLevel::Untrusted; - case SECURITY_MANDATORY_LOW_RID: return IntegrityLevel::Low; - case SECURITY_MANDATORY_MEDIUM_RID: return IntegrityLevel::Medium; - case SECURITY_MANDATORY_HIGH_RID: return IntegrityLevel::High; - case SECURITY_MANDATORY_SYSTEM_RID: return IntegrityLevel::System; - case SECURITY_MANDATORY_PROTECTED_PROCESS_RID: return IntegrityLevel::ProtectedProcess; - } - - THROW_HR(E_UNEXPECTED); - } - - bool IsCOMCallerSameUserAndIntegrityLevel() - { - auto serverUser = wil::get_token_information(); - IntegrityLevel serverIntegrityLevel = GetEffectiveIntegrityLevel(); - - auto impersonation = ImpersonateCOMorRPCCaller::BeginImpersonation(); - - auto callingUser = wil::get_token_information(); - IntegrityLevel callingIntegrityLevel = GetEffectiveIntegrityLevel(); - - if (!EqualSid(serverUser->User.Sid, callingUser->User.Sid)) - { - AICLI_LOG(Core, Crit, << "Attempt to access by another user: " << ToString(callingUser->User.Sid)); - return false; - } - - if (ToIntegral(callingIntegrityLevel) < ToIntegral(serverIntegrityLevel)) - { - AICLI_LOG(Core, Crit, << "Attempt to access by a lower integrity process: " << callingIntegrityLevel << " < " << serverIntegrityLevel); - return false; - } - - return true; - } - - bool IsCOMCallerIntegrityLevelAtLeast(IntegrityLevel minimumLevel) - { - auto impersonation = ImpersonateCOMorRPCCaller::BeginImpersonation(); - return IsCurrentIntegrityLevelAtLeast(minimumLevel); - } - - bool IsCurrentIntegrityLevelAtLeast(IntegrityLevel minimumLevel) - { - IntegrityLevel callingIntegrityLevel = GetEffectiveIntegrityLevel(); - - if (ToIntegral(callingIntegrityLevel) < ToIntegral(minimumLevel)) - { - AICLI_LOG(Core, Crit, << "Attempt to access by a lower integrity process than required: " << callingIntegrityLevel << " < " << minimumLevel); - return false; - } - - return true; - } - - std::string ToString(PSID sid) - { - wil::unique_hlocal_ansistring result; - THROW_IF_WIN32_BOOL_FALSE(ConvertSidToStringSidA(sid, &result)); - return result.get(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "winget/Security.h" +#include "AppInstallerLogging.h" +#include "AppInstallerLanguageUtilities.h" + +namespace AppInstaller::Security +{ + namespace + { + bool IsSameAuthority(const SID_IDENTIFIER_AUTHORITY& a, const SID_IDENTIFIER_AUTHORITY& b) + { + for (size_t i = 0; i < ARRAYSIZE(a.Value); ++i) + { + if (a.Value[i] != b.Value[i]) + { + return false; + } + } + + return true; + } + + // Helper to impersonate the COM or RPC caller. + struct ImpersonateCOMorRPCCaller + { + static ImpersonateCOMorRPCCaller BeginImpersonation() + { + return {}; + } + + ~ImpersonateCOMorRPCCaller() + { + if (m_serverSecurity) + { + FAIL_FAST_IF_FAILED(m_serverSecurity->RevertToSelf()); + } + else + { + FAIL_FAST_IF(RpcRevertToSelf() != RPC_S_OK); + } + } + + private: + ImpersonateCOMorRPCCaller() + { + if (SUCCEEDED_LOG(CoGetCallContext(IID_IServerSecurity, m_serverSecurity.put_void()))) + { + THROW_IF_FAILED(m_serverSecurity->ImpersonateClient()); + } + else + { + RPC_STATUS status = RpcImpersonateClient(nullptr); + THROW_HR_IF(MAKE_HRESULT(SEVERITY_ERROR, FACILITY_RPC, status), status != RPC_S_OK); + } + } + + wil::com_ptr m_serverSecurity; + }; + } + + IntegrityLevel GetEffectiveIntegrityLevel() + { + auto currentIntegrityLevel = wil::get_token_information(); + PSID sid = currentIntegrityLevel->Label.Sid; + THROW_HR_IF(CO_E_INVALIDSID, !IsValidSid(sid)); + + auto identifierAuthority = GetSidIdentifierAuthority(sid); + THROW_HR_IF(E_UNEXPECTED, !IsSameAuthority(*identifierAuthority, SECURITY_MANDATORY_LABEL_AUTHORITY)); + + PUCHAR subAuthorityCount = GetSidSubAuthorityCount(sid); + THROW_HR_IF(E_UNEXPECTED, *subAuthorityCount != 1); + + PDWORD subAuthority = GetSidSubAuthority(sid, 0); + + switch (*subAuthority) + { + case SECURITY_MANDATORY_UNTRUSTED_RID: return IntegrityLevel::Untrusted; + case SECURITY_MANDATORY_LOW_RID: return IntegrityLevel::Low; + case SECURITY_MANDATORY_MEDIUM_RID: return IntegrityLevel::Medium; + case SECURITY_MANDATORY_HIGH_RID: return IntegrityLevel::High; + case SECURITY_MANDATORY_SYSTEM_RID: return IntegrityLevel::System; + case SECURITY_MANDATORY_PROTECTED_PROCESS_RID: return IntegrityLevel::ProtectedProcess; + } + + THROW_HR(E_UNEXPECTED); + } + + bool IsCOMCallerSameUserAndIntegrityLevel() + { + auto serverUser = wil::get_token_information(); + IntegrityLevel serverIntegrityLevel = GetEffectiveIntegrityLevel(); + + auto impersonation = ImpersonateCOMorRPCCaller::BeginImpersonation(); + + auto callingUser = wil::get_token_information(); + IntegrityLevel callingIntegrityLevel = GetEffectiveIntegrityLevel(); + + if (!EqualSid(serverUser->User.Sid, callingUser->User.Sid)) + { + AICLI_LOG(Core, Crit, << "Attempt to access by another user: " << ToString(callingUser->User.Sid)); + return false; + } + + if (ToIntegral(callingIntegrityLevel) < ToIntegral(serverIntegrityLevel)) + { + AICLI_LOG(Core, Crit, << "Attempt to access by a lower integrity process: " << callingIntegrityLevel << " < " << serverIntegrityLevel); + return false; + } + + return true; + } + + bool IsCOMCallerIntegrityLevelAtLeast(IntegrityLevel minimumLevel) + { + auto impersonation = ImpersonateCOMorRPCCaller::BeginImpersonation(); + return IsCurrentIntegrityLevelAtLeast(minimumLevel); + } + + bool IsCurrentIntegrityLevelAtLeast(IntegrityLevel minimumLevel) + { + IntegrityLevel callingIntegrityLevel = GetEffectiveIntegrityLevel(); + + if (ToIntegral(callingIntegrityLevel) < ToIntegral(minimumLevel)) + { + AICLI_LOG(Core, Crit, << "Attempt to access by a lower integrity process than required: " << callingIntegrityLevel << " < " << minimumLevel); + return false; + } + + return true; + } + + std::string ToString(PSID sid) + { + wil::unique_hlocal_ansistring result; + THROW_IF_WIN32_BOOL_FALSE(ConvertSidToStringSidA(sid, &result)); + return result.get(); + } +} diff --git a/src/AppInstallerSharedLib/SharedThreadGlobals.cpp b/src/AppInstallerSharedLib/SharedThreadGlobals.cpp index 18bede0b0c..529557f672 100644 --- a/src/AppInstallerSharedLib/SharedThreadGlobals.cpp +++ b/src/AppInstallerSharedLib/SharedThreadGlobals.cpp @@ -1,45 +1,45 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/winget/SharedThreadGlobals.h" - -namespace AppInstaller::ThreadLocalStorage -{ - using namespace AppInstaller::Logging; - - static ThreadGlobals* SetOrGetThreadGlobals(bool setThreadGlobals, ThreadGlobals* pThreadGlobals = nullptr) - { - thread_local AppInstaller::ThreadLocalStorage::ThreadGlobals* t_pThreadGlobals = nullptr; - - if (setThreadGlobals) - { - AppInstaller::ThreadLocalStorage::ThreadGlobals* previous_pThreadGlobals = t_pThreadGlobals; - t_pThreadGlobals = pThreadGlobals; - return previous_pThreadGlobals; - } - - return t_pThreadGlobals; - } - - std::unique_ptr ThreadGlobals::SetForCurrentThread() - { - return std::make_unique(SetOrGetThreadGlobals(true, this)); - } - - ThreadGlobals* ThreadGlobals::GetForCurrentThread() - { - return SetOrGetThreadGlobals(false); - } - - PreviousThreadGlobals::PreviousThreadGlobals(ThreadGlobals* previous) : m_previous(previous) - { - m_threadId = GetCurrentThreadId(); - } - - PreviousThreadGlobals::~PreviousThreadGlobals() - { - // Not remaining on the same thread is a serious issue that must be resolved - FAIL_FAST_IF(GetCurrentThreadId() != m_threadId); - std::ignore = SetOrGetThreadGlobals(true, m_previous); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/winget/SharedThreadGlobals.h" + +namespace AppInstaller::ThreadLocalStorage +{ + using namespace AppInstaller::Logging; + + static ThreadGlobals* SetOrGetThreadGlobals(bool setThreadGlobals, ThreadGlobals* pThreadGlobals = nullptr) + { + thread_local AppInstaller::ThreadLocalStorage::ThreadGlobals* t_pThreadGlobals = nullptr; + + if (setThreadGlobals) + { + AppInstaller::ThreadLocalStorage::ThreadGlobals* previous_pThreadGlobals = t_pThreadGlobals; + t_pThreadGlobals = pThreadGlobals; + return previous_pThreadGlobals; + } + + return t_pThreadGlobals; + } + + std::unique_ptr ThreadGlobals::SetForCurrentThread() + { + return std::make_unique(SetOrGetThreadGlobals(true, this)); + } + + ThreadGlobals* ThreadGlobals::GetForCurrentThread() + { + return SetOrGetThreadGlobals(false); + } + + PreviousThreadGlobals::PreviousThreadGlobals(ThreadGlobals* previous) : m_previous(previous) + { + m_threadId = GetCurrentThreadId(); + } + + PreviousThreadGlobals::~PreviousThreadGlobals() + { + // Not remaining on the same thread is a serious issue that must be resolved + FAIL_FAST_IF(GetCurrentThreadId() != m_threadId); + std::ignore = SetOrGetThreadGlobals(true, m_previous); + } +} diff --git a/src/AppInstallerSharedLib/Versions.cpp b/src/AppInstallerSharedLib/Versions.cpp index 0d9d5d5732..783d1d8e84 100644 --- a/src/AppInstallerSharedLib/Versions.cpp +++ b/src/AppInstallerSharedLib/Versions.cpp @@ -1,718 +1,718 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerVersions.h" -#include "Public/AppInstallerStrings.h" - -namespace AppInstaller::Utility -{ - using namespace std::string_view_literals; - - static constexpr std::string_view s_Digit_Characters = "0123456789"sv; - static constexpr std::string_view s_Version_Part_Latest = "Latest"sv; - static constexpr std::string_view s_Version_Part_Unknown = "Unknown"sv; - - static constexpr std::string_view s_Approximate_Less_Than = "< "sv; - static constexpr std::string_view s_Approximate_Greater_Than = "> "sv; - - Version::Version(std::string&& version, std::string_view splitChars) - { - Assign(std::move(version), splitChars); - } - - RawVersion::RawVersion(std::string version, std::string_view splitChars) - { - m_trimPrefix = false; - Assign(std::move(version), splitChars); - } - - Version::Version(Version baseVersion, ApproximateComparator approximateComparator) : Version(std::move(baseVersion)) - { - if (approximateComparator == ApproximateComparator::None) - { - return; - } - - THROW_HR_IF(E_INVALIDARG, this->IsApproximate() || this->IsUnknown()); - - m_approximateComparator = approximateComparator; - if (approximateComparator == ApproximateComparator::LessThan) - { - m_version = std::string{ s_Approximate_Less_Than } + m_version; - } - else if (approximateComparator == ApproximateComparator::GreaterThan) - { - m_version = std::string{ s_Approximate_Greater_Than } + m_version; - } - } - - void Version::Assign(std::string version, std::string_view splitChars) - { - m_version = std::move(Utility::Trim(version)); - - // Process approximate comparator if applicable - std::string baseVersion = m_version; - if (CaseInsensitiveStartsWith(m_version, s_Approximate_Less_Than)) - { - m_approximateComparator = ApproximateComparator::LessThan; - baseVersion = m_version.substr(s_Approximate_Less_Than.length(), m_version.length() - s_Approximate_Less_Than.length()); - } - else if (CaseInsensitiveStartsWith(m_version, s_Approximate_Greater_Than)) - { - m_approximateComparator = ApproximateComparator::GreaterThan; - baseVersion = m_version.substr(s_Approximate_Greater_Than.length(), m_version.length() - s_Approximate_Greater_Than.length()); - } - - // If there is a digit before the split character, or no split characters exist, trim off all leading non-digit characters - size_t digitPos = baseVersion.find_first_of(s_Digit_Characters); - size_t splitPos = baseVersion.find_first_of(splitChars); - if (m_trimPrefix && digitPos != std::string::npos && (splitPos == std::string::npos || digitPos < splitPos)) - { - baseVersion.erase(0, digitPos); - } - - // Then parse the base version - size_t pos = 0; - - while (pos < baseVersion.length()) - { - size_t newPos = baseVersion.find_first_of(splitChars, pos); - - size_t length = (newPos == std::string::npos ? baseVersion.length() : newPos) - pos; - m_parts.emplace_back(baseVersion.substr(pos, length)); - - pos += length + 1; - } - - // Trim version parts - Trim(); - - THROW_HR_IF(E_INVALIDARG, m_approximateComparator != ApproximateComparator::None && IsBaseVersionUnknown()); - } - - void Version::Trim() - { - while (!m_parts.empty()) - { - const Part& part = m_parts.back(); - if (part.Integer == 0 && part.Other.empty()) - { - m_parts.pop_back(); - } - else - { - return; - } - } - } - - bool Version::operator<(const Version& other) const - { - // Sort Latest higher than any other values - bool thisIsLatest = IsBaseVersionLatest(); - bool otherIsLatest = other.IsBaseVersionLatest(); - - if (thisIsLatest && otherIsLatest) - { - return ApproximateCompareLessThan(other); - } - else if (thisIsLatest || otherIsLatest) - { - // If only one is latest, this can only be less than if the other is and this is not. - return (otherIsLatest && !thisIsLatest); - } - - // Sort Unknown lower than any known values - bool thisIsUnknown = IsBaseVersionUnknown(); - bool otherIsUnknown = other.IsBaseVersionUnknown(); - - if (thisIsUnknown && otherIsUnknown) - { - // This code path should always return false as we disable approximate version for Unknown for now - return ApproximateCompareLessThan(other); - } - else if (thisIsUnknown || otherIsUnknown) - { - // If at least one is unknown, this can only be less than if it is and the other is not. - return (thisIsUnknown && !otherIsUnknown); - } - - const Part emptyPart{}; - for (size_t i = 0; i < std::max(m_parts.size(), other.m_parts.size()); ++i) - { - // Whichever version is shorter, we need to pad it with empty parts - const Part& partA = (i >= m_parts.size()) ? emptyPart : m_parts[i]; - const Part& partB = (i >= other.m_parts.size()) ? emptyPart : other.m_parts[i]; - - if (partA < partB) - { - return true; - } - else if (partB < partA) - { - return false; - } - // else parts are equal, so continue to next part - } - - // All parts were compared and found to be equal - return ApproximateCompareLessThan(other); - } - - bool Version::operator>(const Version& other) const - { - return other < *this; - } - - bool Version::operator<=(const Version& other) const - { - return !(*this > other); - } - - bool Version::operator>=(const Version& other) const - { - return !(*this < other); - } - - bool Version::operator==(const Version& other) const - { - if (m_approximateComparator != other.m_approximateComparator) - { - return false; - } - - if ((IsBaseVersionLatest() && other.IsBaseVersionLatest()) || - (IsBaseVersionUnknown() && other.IsBaseVersionUnknown())) - { - return true; - } - - if (m_parts.size() != other.m_parts.size()) - { - return false; - } - - for (size_t i = 0; i < m_parts.size(); ++i) - { - if (m_parts[i] != other.m_parts[i]) - { - return false; - } - } - - return true; - } - - bool Version::operator!=(const Version& other) const - { - return !(*this == other); - } - - bool Version::IsLatest() const - { - return (m_approximateComparator != ApproximateComparator::LessThan && IsBaseVersionLatest()); - } - - Version Version::CreateLatest() - { - Version result; - result.m_version = s_Version_Part_Latest; - result.m_parts.emplace_back(0, std::string{ s_Version_Part_Latest }); - return result; - } - - bool Version::IsUnknown() const - { - return IsBaseVersionUnknown(); - } - - Version Version::CreateUnknown() - { - Version result; - result.m_version = s_Version_Part_Unknown; - result.m_parts.emplace_back(0, std::string{ s_Version_Part_Unknown }); - return result; - } - - const Version::Part& Version::PartAt(size_t index) const - { - static Part s_zero{}; - - if (index < m_parts.size()) - { - return m_parts[index]; - } - else - { - return s_zero; - } - } - - Version Version::GetBaseVersion() const - { - Version baseVersion = *this; - baseVersion.m_approximateComparator = ApproximateComparator::None; - if (m_approximateComparator == ApproximateComparator::LessThan) - { - baseVersion.m_version = m_version.substr(s_Approximate_Less_Than.size()); - } - else if (m_approximateComparator == ApproximateComparator::GreaterThan) - { - baseVersion.m_version = m_version.substr(s_Approximate_Greater_Than.size()); - } - - return baseVersion; - } - - bool Version::IsBaseVersionLatest() const - { - return (m_parts.size() == 1 && m_parts[0].Integer == 0 && Utility::CaseInsensitiveEquals(m_parts[0].Other, s_Version_Part_Latest)); - } - - bool Version::IsBaseVersionUnknown() const - { - return (m_parts.size() == 1 && m_parts[0].Integer == 0 && Utility::CaseInsensitiveEquals(m_parts[0].Other, s_Version_Part_Unknown)); - } - - bool Version::ApproximateCompareLessThan(const Version& other) const - { - // Only true if this is less than, other is not, OR this is none, other is greater than - return (m_approximateComparator == ApproximateComparator::LessThan && other.m_approximateComparator != ApproximateComparator::LessThan) || - (m_approximateComparator == ApproximateComparator::None && other.m_approximateComparator == ApproximateComparator::GreaterThan); - } - - Version::Part::Part(const std::string& part) - { - std::string interimPart = Utility::Trim(part); - const char* begin = interimPart.c_str(); - char* end = nullptr; - errno = 0; - Integer = strtoull(begin, &end, 10); - - if (errno == ERANGE) - { - Integer = 0; - Other = interimPart; - } - else if (static_cast(end - begin) != interimPart.length()) - { - Other = end; - } - - m_foldedOther = Utility::FoldCase(static_cast(Other)); - } - - Version::Part::Part(uint64_t integer, std::string other) : - Integer(integer), Other(std::move(Utility::Trim(other))) - { - m_foldedOther = Utility::FoldCase(static_cast(Other)); - } - - bool Version::Part::operator<(const Part& other) const - { - if (Integer < other.Integer) - { - return true; - } - else if (Integer > other.Integer) - { - return false; - } - else if (Other.empty()) - { - // If this Other is empty, it is at least >= - return false; - } - else if (!Other.empty() && other.Other.empty()) - { - // If the other Other is empty and this is not, this is less. - return true; - } - else if (m_foldedOther < other.m_foldedOther) - { - // Compare the folded versions - return true; - } - - // else Other >= other.Other - return false; - } - - bool Version::Part::operator==(const Part& other) const - { - return Integer == other.Integer && m_foldedOther == other.m_foldedOther; - } - - bool Version::Part::operator!=(const Part& other) const - { - return !(*this == other); - } - - bool Channel::operator<(const Channel& other) const - { - return m_channel < other.m_channel; - } - - VersionAndChannel::VersionAndChannel(Version&& version, Channel&& channel) : - m_version(std::move(version)), m_channel(std::move(channel)) {} - - std::string VersionAndChannel::ToString() const - { - std::string result; - result = m_version.ToString(); - if (!m_channel.ToString().empty()) - { - result += '['; - result += m_channel.ToString(); - result += ']'; - } - return result; - } - - bool VersionAndChannel::operator<(const VersionAndChannel& other) const - { - if (m_channel < other.m_channel) - { - return true; - } - else if (other.m_channel < m_channel) - { - return false; - } - // We intentionally invert the order for version here. - else if (other.m_version < m_version) - { - return true; - } - - // else m_version >= other.m_version - return false; - } - - bool VersionAndChannel::IsUpdatedBy(const VersionAndChannel& other) const - { - // Channel crossing should not happen here. - if (!Utility::ICUCaseInsensitiveEquals(m_channel.ToString(), other.m_channel.ToString())) - { - return false; - } - - return m_version < other.m_version; - } - - UInt64Version::UInt64Version(UINT64 version) - { - Assign(version); - } - - UInt64Version::UInt64Version(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision) - { - Assign(major, minor, build, revision); - } - - void UInt64Version::Assign(UINT64 version) - { - constexpr UINT64 mask16 = (1 << 16) - 1; - uint16_t revision = version & mask16; - uint16_t build = (version >> 0x10) & mask16; - uint16_t minor = (version >> 0x20) & mask16; - uint16_t major = (version >> 0x30) & mask16; - - Assign(major, minor, build, revision); - } - - void UInt64Version::Assign(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision) - { - // Construct a string representation of the provided version - std::stringstream ssVersion; - ssVersion << major - << Version::DefaultSplitChars << minor - << Version::DefaultSplitChars << build - << Version::DefaultSplitChars << revision; - m_version = ssVersion.str(); - - // Construct the 4 parts - m_parts = { major, minor, build, revision }; - - // Trim version parts - Trim(); - } - - UInt64Version::UInt64Version(std::string&& version, std::string_view splitChars) - { - Assign(std::move(version), splitChars); - } - - void UInt64Version::Assign(std::string version, std::string_view splitChars) - { - Version::Assign(std::move(version), splitChars); - - // After trimming trailing parts (0 or empty), - // at most 4 parts must be present - THROW_HR_IF(E_INVALIDARG, m_parts.size() > 4); - for (const auto& part : m_parts) - { - // Check for non-empty Other part - THROW_HR_IF(E_INVALIDARG, !part.Other.empty()); - - // Check for overflow Integer part - THROW_HR_IF(E_INVALIDARG, part.Integer >> 16 != 0); - } - } - - SemanticVersion::SemanticVersion(std::string&& version) - { - Assign(std::move(version), DefaultSplitChars); - } - - void SemanticVersion::Assign(std::string version, std::string_view splitChars) - { - // Semantic versions require using the default split character - THROW_HR_IF(E_INVALIDARG, splitChars != DefaultSplitChars); - - // First split off any trailing build metadata - std::string interimVersion = Utility::Trim(version); - size_t buildMetadataPos = interimVersion.find('+', 0); - - if (buildMetadataPos != std::string::npos) - { - m_buildMetadata.Assign(interimVersion.substr(buildMetadataPos + 1)); - interimVersion.resize(buildMetadataPos); - } - - // Now split off the prerelease data - size_t prereleasePos = interimVersion.find('-', 0); - - if (prereleasePos != std::string::npos) - { - m_prerelease.Assign(interimVersion.substr(prereleasePos + 1)); - interimVersion.resize(prereleasePos); - } - - // Parse main version - Version::Assign(std::move(interimVersion), splitChars); - THROW_HR_IF(E_INVALIDARG, IsApproximate()); - THROW_HR_IF(E_INVALIDARG, m_parts.size() > 3); - for (size_t i = 0; i < 3; ++i) - { - THROW_HR_IF(E_INVALIDARG, !PartAt(i).Other.empty()); - } - - // Put rest of version back onto Other of last part - size_t otherSplit = (prereleasePos != std::string::npos ? prereleasePos : buildMetadataPos); - if (otherSplit != std::string::npos) - { - while (m_parts.size() < 3) - { - m_parts.emplace_back(); - } - m_parts[2].Other = version.substr(otherSplit); - } - - // Overwrite the whole version string with our whole version string - m_version = std::move(version); - } - - bool SemanticVersion::IsPrerelease() const - { - return !m_prerelease.IsEmpty(); - } - - const Version& SemanticVersion::PrereleaseVersion() const - { - return m_prerelease; - } - - bool SemanticVersion::HasBuildMetadata() const - { - return !m_buildMetadata.IsEmpty(); - } - - const Version& SemanticVersion::BuildMetadata() const - { - return m_buildMetadata; - } - - VersionRange::VersionRange(Version first, Version second) - { - if (first < second) - { - m_minVersion = std::move(first); - m_maxVersion = std::move(second); - } - else - { - m_minVersion = std::move(second); - m_maxVersion = std::move(first); - } - } - - bool VersionRange::Overlaps(const VersionRange& other) const - { - // No overlap if either is an empty range. - if (IsEmpty() || other.IsEmpty()) - { - return false; - } - - return m_minVersion <= other.m_maxVersion && m_maxVersion >= other.m_minVersion; - } - - bool VersionRange::IsSameAsSingleVersion(const Version& version) const - { - if (IsEmpty()) - { - return false; - } - - return m_minVersion == version && m_maxVersion == version; - } - - bool VersionRange::ContainsVersion(const Version& version) const - { - if (IsEmpty()) - { - return false; - } - - return version >= m_minVersion && version <= m_maxVersion; - } - - bool VersionRange::operator<(const VersionRange& other) const - { - THROW_HR_IF(E_INVALIDARG, IsEmpty() || other.IsEmpty() || Overlaps(other)); - - return m_minVersion < other.m_minVersion; - } - - const Version& VersionRange::GetMinVersion() const - { - THROW_HR_IF(E_NOT_VALID_STATE, IsEmpty()); - return m_minVersion; - } - - const Version& VersionRange::GetMaxVersion() const - { - THROW_HR_IF(E_NOT_VALID_STATE, IsEmpty()); - return m_maxVersion; - } - - bool GatedVersion::IsValidVersion(Version version) const - { - auto gateParts = m_version.GetParts(); - if (gateParts.empty()) - { - return false; - } - - if (gateParts.back() != Version::Part("*")) - { - // Without wildcards, revert to direct comparison - return m_version == version; - } - - auto versionParts = version.GetParts(); - for (size_t i = 0; i < gateParts.size() - 1; ++i) - { - if (versionParts.size() > i) - { - if (gateParts[i] == versionParts[i]) - { - continue; - } - else - { - // Mismatch with the gated version - return false; - } - } - else - { - // Assume trailing 0s on the version - if (gateParts[i] != Version::Part(0)) - { - return false; - } - } - } - - // All version parts matched - return true; - } - - bool HasOverlapInVersionRanges(const std::vector& ranges) - { - for (size_t i = 0; i < ranges.size(); i++) - { - for (size_t j = i + 1; j < ranges.size(); j++) - { - if (ranges[i].Overlaps(ranges[j])) - { - return true; - } - } - } - - return false; - } - - OpenTypeFontVersion::OpenTypeFontVersion(std::string&& version) - { - Assign(std::move(version), DefaultSplitChars); - } - - void OpenTypeFontVersion::Assign(std::string version, std::string_view splitChars) - { - // Open type version requires using the default split character - THROW_HR_IF(E_INVALIDARG, splitChars != DefaultSplitChars); - - // Split on default split character. - std::vector parts = Split(version, '.', true); - - std::string majorString; - std::string minorString; - - // Font version must have a "major.minor" part. - if (parts.size() >= 2) - { - // Find first digit and trim all preceding characters. - std::string firstPart = parts[0]; - size_t majorStartIndex = firstPart.find_first_of(s_Digit_Characters); - - if (majorStartIndex != std::string::npos) - { - firstPart.erase(0, majorStartIndex); - } - - size_t majorEndIndex = firstPart.find_last_of(s_Digit_Characters); - majorString = firstPart.substr(0, majorEndIndex + 1); - - // Parse and verify minor part. - std::string secondPart = parts[1]; - size_t endPos = secondPart.find_first_not_of(s_Digit_Characters); - - // If a non-digit character exists, trim off the remainder. - if (endPos != std::string::npos) - { - secondPart.erase(endPos, secondPart.length()); - } - - minorString = secondPart; - } - - // Verify results. - if (!majorString.empty() && !minorString.empty()) - { - m_parts.emplace_back(majorString); - m_parts.emplace_back(minorString); - m_version = Utility::Join(DefaultSplitChars, { majorString, minorString }); - - Trim(); - } - else - { - m_version = s_Version_Part_Unknown; - m_parts.emplace_back(0, std::string{ s_Version_Part_Unknown }); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerVersions.h" +#include "Public/AppInstallerStrings.h" + +namespace AppInstaller::Utility +{ + using namespace std::string_view_literals; + + static constexpr std::string_view s_Digit_Characters = "0123456789"sv; + static constexpr std::string_view s_Version_Part_Latest = "Latest"sv; + static constexpr std::string_view s_Version_Part_Unknown = "Unknown"sv; + + static constexpr std::string_view s_Approximate_Less_Than = "< "sv; + static constexpr std::string_view s_Approximate_Greater_Than = "> "sv; + + Version::Version(std::string&& version, std::string_view splitChars) + { + Assign(std::move(version), splitChars); + } + + RawVersion::RawVersion(std::string version, std::string_view splitChars) + { + m_trimPrefix = false; + Assign(std::move(version), splitChars); + } + + Version::Version(Version baseVersion, ApproximateComparator approximateComparator) : Version(std::move(baseVersion)) + { + if (approximateComparator == ApproximateComparator::None) + { + return; + } + + THROW_HR_IF(E_INVALIDARG, this->IsApproximate() || this->IsUnknown()); + + m_approximateComparator = approximateComparator; + if (approximateComparator == ApproximateComparator::LessThan) + { + m_version = std::string{ s_Approximate_Less_Than } + m_version; + } + else if (approximateComparator == ApproximateComparator::GreaterThan) + { + m_version = std::string{ s_Approximate_Greater_Than } + m_version; + } + } + + void Version::Assign(std::string version, std::string_view splitChars) + { + m_version = std::move(Utility::Trim(version)); + + // Process approximate comparator if applicable + std::string baseVersion = m_version; + if (CaseInsensitiveStartsWith(m_version, s_Approximate_Less_Than)) + { + m_approximateComparator = ApproximateComparator::LessThan; + baseVersion = m_version.substr(s_Approximate_Less_Than.length(), m_version.length() - s_Approximate_Less_Than.length()); + } + else if (CaseInsensitiveStartsWith(m_version, s_Approximate_Greater_Than)) + { + m_approximateComparator = ApproximateComparator::GreaterThan; + baseVersion = m_version.substr(s_Approximate_Greater_Than.length(), m_version.length() - s_Approximate_Greater_Than.length()); + } + + // If there is a digit before the split character, or no split characters exist, trim off all leading non-digit characters + size_t digitPos = baseVersion.find_first_of(s_Digit_Characters); + size_t splitPos = baseVersion.find_first_of(splitChars); + if (m_trimPrefix && digitPos != std::string::npos && (splitPos == std::string::npos || digitPos < splitPos)) + { + baseVersion.erase(0, digitPos); + } + + // Then parse the base version + size_t pos = 0; + + while (pos < baseVersion.length()) + { + size_t newPos = baseVersion.find_first_of(splitChars, pos); + + size_t length = (newPos == std::string::npos ? baseVersion.length() : newPos) - pos; + m_parts.emplace_back(baseVersion.substr(pos, length)); + + pos += length + 1; + } + + // Trim version parts + Trim(); + + THROW_HR_IF(E_INVALIDARG, m_approximateComparator != ApproximateComparator::None && IsBaseVersionUnknown()); + } + + void Version::Trim() + { + while (!m_parts.empty()) + { + const Part& part = m_parts.back(); + if (part.Integer == 0 && part.Other.empty()) + { + m_parts.pop_back(); + } + else + { + return; + } + } + } + + bool Version::operator<(const Version& other) const + { + // Sort Latest higher than any other values + bool thisIsLatest = IsBaseVersionLatest(); + bool otherIsLatest = other.IsBaseVersionLatest(); + + if (thisIsLatest && otherIsLatest) + { + return ApproximateCompareLessThan(other); + } + else if (thisIsLatest || otherIsLatest) + { + // If only one is latest, this can only be less than if the other is and this is not. + return (otherIsLatest && !thisIsLatest); + } + + // Sort Unknown lower than any known values + bool thisIsUnknown = IsBaseVersionUnknown(); + bool otherIsUnknown = other.IsBaseVersionUnknown(); + + if (thisIsUnknown && otherIsUnknown) + { + // This code path should always return false as we disable approximate version for Unknown for now + return ApproximateCompareLessThan(other); + } + else if (thisIsUnknown || otherIsUnknown) + { + // If at least one is unknown, this can only be less than if it is and the other is not. + return (thisIsUnknown && !otherIsUnknown); + } + + const Part emptyPart{}; + for (size_t i = 0; i < std::max(m_parts.size(), other.m_parts.size()); ++i) + { + // Whichever version is shorter, we need to pad it with empty parts + const Part& partA = (i >= m_parts.size()) ? emptyPart : m_parts[i]; + const Part& partB = (i >= other.m_parts.size()) ? emptyPart : other.m_parts[i]; + + if (partA < partB) + { + return true; + } + else if (partB < partA) + { + return false; + } + // else parts are equal, so continue to next part + } + + // All parts were compared and found to be equal + return ApproximateCompareLessThan(other); + } + + bool Version::operator>(const Version& other) const + { + return other < *this; + } + + bool Version::operator<=(const Version& other) const + { + return !(*this > other); + } + + bool Version::operator>=(const Version& other) const + { + return !(*this < other); + } + + bool Version::operator==(const Version& other) const + { + if (m_approximateComparator != other.m_approximateComparator) + { + return false; + } + + if ((IsBaseVersionLatest() && other.IsBaseVersionLatest()) || + (IsBaseVersionUnknown() && other.IsBaseVersionUnknown())) + { + return true; + } + + if (m_parts.size() != other.m_parts.size()) + { + return false; + } + + for (size_t i = 0; i < m_parts.size(); ++i) + { + if (m_parts[i] != other.m_parts[i]) + { + return false; + } + } + + return true; + } + + bool Version::operator!=(const Version& other) const + { + return !(*this == other); + } + + bool Version::IsLatest() const + { + return (m_approximateComparator != ApproximateComparator::LessThan && IsBaseVersionLatest()); + } + + Version Version::CreateLatest() + { + Version result; + result.m_version = s_Version_Part_Latest; + result.m_parts.emplace_back(0, std::string{ s_Version_Part_Latest }); + return result; + } + + bool Version::IsUnknown() const + { + return IsBaseVersionUnknown(); + } + + Version Version::CreateUnknown() + { + Version result; + result.m_version = s_Version_Part_Unknown; + result.m_parts.emplace_back(0, std::string{ s_Version_Part_Unknown }); + return result; + } + + const Version::Part& Version::PartAt(size_t index) const + { + static Part s_zero{}; + + if (index < m_parts.size()) + { + return m_parts[index]; + } + else + { + return s_zero; + } + } + + Version Version::GetBaseVersion() const + { + Version baseVersion = *this; + baseVersion.m_approximateComparator = ApproximateComparator::None; + if (m_approximateComparator == ApproximateComparator::LessThan) + { + baseVersion.m_version = m_version.substr(s_Approximate_Less_Than.size()); + } + else if (m_approximateComparator == ApproximateComparator::GreaterThan) + { + baseVersion.m_version = m_version.substr(s_Approximate_Greater_Than.size()); + } + + return baseVersion; + } + + bool Version::IsBaseVersionLatest() const + { + return (m_parts.size() == 1 && m_parts[0].Integer == 0 && Utility::CaseInsensitiveEquals(m_parts[0].Other, s_Version_Part_Latest)); + } + + bool Version::IsBaseVersionUnknown() const + { + return (m_parts.size() == 1 && m_parts[0].Integer == 0 && Utility::CaseInsensitiveEquals(m_parts[0].Other, s_Version_Part_Unknown)); + } + + bool Version::ApproximateCompareLessThan(const Version& other) const + { + // Only true if this is less than, other is not, OR this is none, other is greater than + return (m_approximateComparator == ApproximateComparator::LessThan && other.m_approximateComparator != ApproximateComparator::LessThan) || + (m_approximateComparator == ApproximateComparator::None && other.m_approximateComparator == ApproximateComparator::GreaterThan); + } + + Version::Part::Part(const std::string& part) + { + std::string interimPart = Utility::Trim(part); + const char* begin = interimPart.c_str(); + char* end = nullptr; + errno = 0; + Integer = strtoull(begin, &end, 10); + + if (errno == ERANGE) + { + Integer = 0; + Other = interimPart; + } + else if (static_cast(end - begin) != interimPart.length()) + { + Other = end; + } + + m_foldedOther = Utility::FoldCase(static_cast(Other)); + } + + Version::Part::Part(uint64_t integer, std::string other) : + Integer(integer), Other(std::move(Utility::Trim(other))) + { + m_foldedOther = Utility::FoldCase(static_cast(Other)); + } + + bool Version::Part::operator<(const Part& other) const + { + if (Integer < other.Integer) + { + return true; + } + else if (Integer > other.Integer) + { + return false; + } + else if (Other.empty()) + { + // If this Other is empty, it is at least >= + return false; + } + else if (!Other.empty() && other.Other.empty()) + { + // If the other Other is empty and this is not, this is less. + return true; + } + else if (m_foldedOther < other.m_foldedOther) + { + // Compare the folded versions + return true; + } + + // else Other >= other.Other + return false; + } + + bool Version::Part::operator==(const Part& other) const + { + return Integer == other.Integer && m_foldedOther == other.m_foldedOther; + } + + bool Version::Part::operator!=(const Part& other) const + { + return !(*this == other); + } + + bool Channel::operator<(const Channel& other) const + { + return m_channel < other.m_channel; + } + + VersionAndChannel::VersionAndChannel(Version&& version, Channel&& channel) : + m_version(std::move(version)), m_channel(std::move(channel)) {} + + std::string VersionAndChannel::ToString() const + { + std::string result; + result = m_version.ToString(); + if (!m_channel.ToString().empty()) + { + result += '['; + result += m_channel.ToString(); + result += ']'; + } + return result; + } + + bool VersionAndChannel::operator<(const VersionAndChannel& other) const + { + if (m_channel < other.m_channel) + { + return true; + } + else if (other.m_channel < m_channel) + { + return false; + } + // We intentionally invert the order for version here. + else if (other.m_version < m_version) + { + return true; + } + + // else m_version >= other.m_version + return false; + } + + bool VersionAndChannel::IsUpdatedBy(const VersionAndChannel& other) const + { + // Channel crossing should not happen here. + if (!Utility::ICUCaseInsensitiveEquals(m_channel.ToString(), other.m_channel.ToString())) + { + return false; + } + + return m_version < other.m_version; + } + + UInt64Version::UInt64Version(UINT64 version) + { + Assign(version); + } + + UInt64Version::UInt64Version(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision) + { + Assign(major, minor, build, revision); + } + + void UInt64Version::Assign(UINT64 version) + { + constexpr UINT64 mask16 = (1 << 16) - 1; + uint16_t revision = version & mask16; + uint16_t build = (version >> 0x10) & mask16; + uint16_t minor = (version >> 0x20) & mask16; + uint16_t major = (version >> 0x30) & mask16; + + Assign(major, minor, build, revision); + } + + void UInt64Version::Assign(uint16_t major, uint16_t minor, uint16_t build, uint16_t revision) + { + // Construct a string representation of the provided version + std::stringstream ssVersion; + ssVersion << major + << Version::DefaultSplitChars << minor + << Version::DefaultSplitChars << build + << Version::DefaultSplitChars << revision; + m_version = ssVersion.str(); + + // Construct the 4 parts + m_parts = { major, minor, build, revision }; + + // Trim version parts + Trim(); + } + + UInt64Version::UInt64Version(std::string&& version, std::string_view splitChars) + { + Assign(std::move(version), splitChars); + } + + void UInt64Version::Assign(std::string version, std::string_view splitChars) + { + Version::Assign(std::move(version), splitChars); + + // After trimming trailing parts (0 or empty), + // at most 4 parts must be present + THROW_HR_IF(E_INVALIDARG, m_parts.size() > 4); + for (const auto& part : m_parts) + { + // Check for non-empty Other part + THROW_HR_IF(E_INVALIDARG, !part.Other.empty()); + + // Check for overflow Integer part + THROW_HR_IF(E_INVALIDARG, part.Integer >> 16 != 0); + } + } + + SemanticVersion::SemanticVersion(std::string&& version) + { + Assign(std::move(version), DefaultSplitChars); + } + + void SemanticVersion::Assign(std::string version, std::string_view splitChars) + { + // Semantic versions require using the default split character + THROW_HR_IF(E_INVALIDARG, splitChars != DefaultSplitChars); + + // First split off any trailing build metadata + std::string interimVersion = Utility::Trim(version); + size_t buildMetadataPos = interimVersion.find('+', 0); + + if (buildMetadataPos != std::string::npos) + { + m_buildMetadata.Assign(interimVersion.substr(buildMetadataPos + 1)); + interimVersion.resize(buildMetadataPos); + } + + // Now split off the prerelease data + size_t prereleasePos = interimVersion.find('-', 0); + + if (prereleasePos != std::string::npos) + { + m_prerelease.Assign(interimVersion.substr(prereleasePos + 1)); + interimVersion.resize(prereleasePos); + } + + // Parse main version + Version::Assign(std::move(interimVersion), splitChars); + THROW_HR_IF(E_INVALIDARG, IsApproximate()); + THROW_HR_IF(E_INVALIDARG, m_parts.size() > 3); + for (size_t i = 0; i < 3; ++i) + { + THROW_HR_IF(E_INVALIDARG, !PartAt(i).Other.empty()); + } + + // Put rest of version back onto Other of last part + size_t otherSplit = (prereleasePos != std::string::npos ? prereleasePos : buildMetadataPos); + if (otherSplit != std::string::npos) + { + while (m_parts.size() < 3) + { + m_parts.emplace_back(); + } + m_parts[2].Other = version.substr(otherSplit); + } + + // Overwrite the whole version string with our whole version string + m_version = std::move(version); + } + + bool SemanticVersion::IsPrerelease() const + { + return !m_prerelease.IsEmpty(); + } + + const Version& SemanticVersion::PrereleaseVersion() const + { + return m_prerelease; + } + + bool SemanticVersion::HasBuildMetadata() const + { + return !m_buildMetadata.IsEmpty(); + } + + const Version& SemanticVersion::BuildMetadata() const + { + return m_buildMetadata; + } + + VersionRange::VersionRange(Version first, Version second) + { + if (first < second) + { + m_minVersion = std::move(first); + m_maxVersion = std::move(second); + } + else + { + m_minVersion = std::move(second); + m_maxVersion = std::move(first); + } + } + + bool VersionRange::Overlaps(const VersionRange& other) const + { + // No overlap if either is an empty range. + if (IsEmpty() || other.IsEmpty()) + { + return false; + } + + return m_minVersion <= other.m_maxVersion && m_maxVersion >= other.m_minVersion; + } + + bool VersionRange::IsSameAsSingleVersion(const Version& version) const + { + if (IsEmpty()) + { + return false; + } + + return m_minVersion == version && m_maxVersion == version; + } + + bool VersionRange::ContainsVersion(const Version& version) const + { + if (IsEmpty()) + { + return false; + } + + return version >= m_minVersion && version <= m_maxVersion; + } + + bool VersionRange::operator<(const VersionRange& other) const + { + THROW_HR_IF(E_INVALIDARG, IsEmpty() || other.IsEmpty() || Overlaps(other)); + + return m_minVersion < other.m_minVersion; + } + + const Version& VersionRange::GetMinVersion() const + { + THROW_HR_IF(E_NOT_VALID_STATE, IsEmpty()); + return m_minVersion; + } + + const Version& VersionRange::GetMaxVersion() const + { + THROW_HR_IF(E_NOT_VALID_STATE, IsEmpty()); + return m_maxVersion; + } + + bool GatedVersion::IsValidVersion(Version version) const + { + auto gateParts = m_version.GetParts(); + if (gateParts.empty()) + { + return false; + } + + if (gateParts.back() != Version::Part("*")) + { + // Without wildcards, revert to direct comparison + return m_version == version; + } + + auto versionParts = version.GetParts(); + for (size_t i = 0; i < gateParts.size() - 1; ++i) + { + if (versionParts.size() > i) + { + if (gateParts[i] == versionParts[i]) + { + continue; + } + else + { + // Mismatch with the gated version + return false; + } + } + else + { + // Assume trailing 0s on the version + if (gateParts[i] != Version::Part(0)) + { + return false; + } + } + } + + // All version parts matched + return true; + } + + bool HasOverlapInVersionRanges(const std::vector& ranges) + { + for (size_t i = 0; i < ranges.size(); i++) + { + for (size_t j = i + 1; j < ranges.size(); j++) + { + if (ranges[i].Overlaps(ranges[j])) + { + return true; + } + } + } + + return false; + } + + OpenTypeFontVersion::OpenTypeFontVersion(std::string&& version) + { + Assign(std::move(version), DefaultSplitChars); + } + + void OpenTypeFontVersion::Assign(std::string version, std::string_view splitChars) + { + // Open type version requires using the default split character + THROW_HR_IF(E_INVALIDARG, splitChars != DefaultSplitChars); + + // Split on default split character. + std::vector parts = Split(version, '.', true); + + std::string majorString; + std::string minorString; + + // Font version must have a "major.minor" part. + if (parts.size() >= 2) + { + // Find first digit and trim all preceding characters. + std::string firstPart = parts[0]; + size_t majorStartIndex = firstPart.find_first_of(s_Digit_Characters); + + if (majorStartIndex != std::string::npos) + { + firstPart.erase(0, majorStartIndex); + } + + size_t majorEndIndex = firstPart.find_last_of(s_Digit_Characters); + majorString = firstPart.substr(0, majorEndIndex + 1); + + // Parse and verify minor part. + std::string secondPart = parts[1]; + size_t endPos = secondPart.find_first_not_of(s_Digit_Characters); + + // If a non-digit character exists, trim off the remainder. + if (endPos != std::string::npos) + { + secondPart.erase(endPos, secondPart.length()); + } + + minorString = secondPart; + } + + // Verify results. + if (!majorString.empty() && !minorString.empty()) + { + m_parts.emplace_back(majorString); + m_parts.emplace_back(minorString); + m_version = Utility::Join(DefaultSplitChars, { majorString, minorString }); + + Trim(); + } + else + { + m_version = s_Version_Part_Unknown; + m_parts.emplace_back(0, std::string{ s_Version_Part_Unknown }); + } + } +} diff --git a/src/AppInstallerSharedLib/Yaml.cpp b/src/AppInstallerSharedLib/Yaml.cpp index 1326256ac9..ec8b6822c5 100644 --- a/src/AppInstallerSharedLib/Yaml.cpp +++ b/src/AppInstallerSharedLib/Yaml.cpp @@ -1,886 +1,886 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include -#include "winget/Yaml.h" -#include "YamlWrapper.h" -#include "AppInstallerErrors.h" -#include "AppInstallerLogging.h" -#include "AppInstallerStrings.h" - - -namespace AppInstaller::YAML -{ - using namespace std::string_view_literals; - - namespace - { - Node s_globalInvalidNode; - - static constexpr std::string_view s_nullTag = "tag:yaml.org,2002:null"sv; - static constexpr std::string_view s_boolTag = "tag:yaml.org,2002:bool"sv; - static constexpr std::string_view s_strTag = "tag:yaml.org,2002:str"sv; - static constexpr std::string_view s_intTag = "tag:yaml.org,2002:int"sv; - static constexpr std::string_view s_floatTag = "tag:yaml.org,2002:float"sv; - static constexpr std::string_view s_timestampTag = "tag:yaml.org,2002:timestamp"sv; - static constexpr std::string_view s_seqTag = "tag:yaml.org,2002:seq"sv; - static constexpr std::string_view s_mapTag = "tag:yaml.org,2002:map"sv; - - std::string_view GetExceptionTypeStringView(Exception::Type type) - { - switch (type) - { - case Exception::Type::None: - return "None"sv; - case Exception::Type::Memory: - return "Memory"sv; - case Exception::Type::Reader: - return "Reader"sv; - case Exception::Type::Scanner: - return "Scanner"sv; - case Exception::Type::Parser: - return "Parser"sv; - case Exception::Type::Composer: - return "Composer"sv; - case Exception::Type::Writer: - return "Writer"sv; - case Exception::Type::Emitter: - return "Emitter"sv; - case Exception::Type::Policy: - return "Policy"sv; - } - - return "Unknown"sv; - } - - void OutputExceptionHeader(std::ostringstream& out, Exception::Type type) - { - out << "[YAML:" << GetExceptionTypeStringView(type) << "] "; - } - - void OutputMark(std::ostringstream& out, const Mark& mark) - { - out << "[line " << mark.line << "; col " << mark.column << ']'; - } - - Node::TagType ConvertToTagType(const std::string& tag) - { - if (tag == s_strTag) - { - return Node::TagType::Str; - } - else if (tag == s_seqTag) - { - return Node::TagType::Seq; - } - else if (tag == s_mapTag) - { - return Node::TagType::Map; - } - else if (tag == s_boolTag) - { - return Node::TagType::Bool; - } - else if (tag == s_intTag) - { - return Node::TagType::Int; - } - else if (tag == s_floatTag) - { - return Node::TagType::Float; - } - else if (tag == s_timestampTag) - { - return Node::TagType::Timestamp; - } - else if (tag == s_nullTag) - { - return Node::TagType::Null; - } - - return Node::TagType::Unknown; - } - - DocumentSchemaHeader ExtractSchemaHeaderFromYaml( const std::string& yamlDocument, size_t rootNodeLine) - { - std::istringstream input(yamlDocument); - std::string line; - size_t currentLine = 1; - - // Search for the schema header string in the comments before the root node. - while (currentLine < rootNodeLine && std::getline(input, line)) - { - std::string comment = Utility::Trim(line); - - // Check if the line is a comment - if (!comment.empty() && comment[0] == '#') - { - size_t pos = line.find(DocumentSchemaHeader::YamlLanguageServerKey); - - // Check if the comment contains the schema header string - if (pos != std::string::npos) - { - return DocumentSchemaHeader(std::move(comment), YAML::Mark{ currentLine, pos}); - } - } - - currentLine++; - } - - return {}; - } - } - - Exception::Exception(Type type) : - wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR) - { - std::ostringstream out; - OutputExceptionHeader(out, type); - - if (type == Type::Memory) - { - out << "Unable to (re)allocate memory"; - } - else - { - out << "An unknown error occurred"; - } - - m_what = out.str(); - } - - Exception::Exception(Type type, const char* problem, size_t offset, int value) : - wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR) - { - std::ostringstream out; - OutputExceptionHeader(out, type); - - out << (problem ? problem : "Unexplained error"); - - if (value != -1) - { - out << " [" << value << ']'; - } - - out << " at " << offset; - - m_what = out.str(); - } - - Exception::Exception(Type type, const char* problem, const Mark& problemMark, const char* context, const Mark& contextMark) : - wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR), m_mark(problemMark) - { - std::ostringstream out; - OutputExceptionHeader(out, type); - - if (context) - { - out << context << ' '; - OutputMark(out, contextMark); - out << ' ' << (problem ? problem : "unexplained error"); - } - else - { - out << (problem ? problem : "Unexplained error"); - } - - out << ' '; - OutputMark(out, problemMark); - - m_what = out.str(); - } - - Exception::Exception(Type type, const char* problem) : - wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR) - { - std::ostringstream out; - OutputExceptionHeader(out, type); - - out << (problem ? problem : "Unexplained error"); - - m_what = out.str(); - } - - const char* Exception::what() const noexcept - { - return m_what.c_str(); - } - - const Mark& Exception::GetMark() const - { - return m_mark; - } - - Node::Node(Type type, std::string tag, const YAML::Mark& mark) : - m_type(type), m_tag(std::move(tag)), m_mark(mark) - { - if (m_type == Type::Sequence) - { - m_sequence = decltype(m_sequence)::value_type{}; - } - else if (m_type == Type::Mapping) - { - m_mapping = decltype(m_mapping)::value_type{}; - } - - m_tagType = ConvertToTagType(m_tag); - } - - void Node::SetScalar(std::string value) - { - Require(Type::Scalar); - m_scalar = std::move(value); - } - - void Node::SetScalar(std::string value, bool isQuoted) - { - this->SetScalar(value); - - // For untagged scalar nodes, libyaml always assigns the generic string - // tag. Here we just try our best and assume that if the value is unquoted - // then is not necessarily a string. - // TODO: handle float and timestamps - if (!isQuoted && this->GetTagType() == TagType::Str) - { - // Integer - // 0 | -? [1-9] [0-9]* - auto tryInt = this->try_as(); - if (tryInt.has_value()) - { - m_tagType = TagType::Int; - return; - } - - // Boolean. Either 'true' or 'false' - auto tryBool = this->try_as(); - if (tryBool.has_value()) - { - m_tagType = TagType::Bool; - } - } - } - - bool Node::operator<(const Node& other) const - { - Require(Type::Scalar); - other.Require(Type::Scalar); - return this->m_scalar < other.m_scalar; - } - - Node& Node::operator[](std::string_view key) - { - Require(Type::Mapping); - auto itrs = m_mapping->equal_range(key); - - if (itrs.first == itrs.second) - { - return s_globalInvalidNode; - } - - Node& result = itrs.first->second; - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, ++itrs.first != itrs.second); - - return result; - } - - const Node& Node::operator[](std::string_view key) const - { - Require(Type::Mapping); - auto itrs = m_mapping->equal_range(key); - - if (itrs.first == itrs.second) - { - return s_globalInvalidNode; - } - - const Node& result = itrs.first->second; - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, ++itrs.first != itrs.second); - - return result; - } - - // Gets a child node from the mapping by its name. - Node& Node::GetChildNode(std::string_view key) - { - Require(Type::Mapping); - - auto itr = m_mapping->begin(); - for (; itr != m_mapping->end(); itr++) - { - if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) - { - break; - } - } - - if (itr == m_mapping->end()) - { - return s_globalInvalidNode; - } - - auto firstFound = itr; - for (++itr; itr != m_mapping->end(); itr++) - { - if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) - { - break; - } - } - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, itr != m_mapping->end()); - Node& result = firstFound->second; - return result; - } - - const Node& Node::GetChildNode(std::string_view key) const - { - Require(Type::Mapping); - - auto itr = m_mapping->begin(); - for (; itr != m_mapping->end(); itr++) - { - if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) - { - break; - } - } - - if (itr == m_mapping->end()) - { - return s_globalInvalidNode; - } - - auto firstFound = itr; - for (++itr; itr != m_mapping->end(); itr++) - { - if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) - { - break; - } - } - - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, itr != m_mapping->end()); - const Node& result = firstFound->second; - return result; - } - - Node& Node::operator[](size_t index) - { - Require(Type::Sequence); - return m_sequence.value()[index]; - } - - const Node& Node::operator[](size_t index) const - { - Require(Type::Sequence); - return m_sequence.value()[index]; - } - - size_t Node::size() const - { - switch (m_type) - { - case Type::Invalid: - case Type::None: - case Type::Scalar: - return 0; - case Type::Sequence: - return m_sequence->size(); - case Type::Mapping: - return m_mapping->size(); - } - - THROW_HR(E_UNEXPECTED); - } - - const std::vector& Node::Sequence() const - { - Require(Type::Sequence); - return m_sequence.value(); - } - - const std::multimap& Node::Mapping() const - { - Require(Type::Mapping); - return m_mapping.value(); - } - - void Node::Require(Type type) const - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION, m_type != type); - } - - std::string Node::as_dispatch(std::string*) const - { - return m_scalar; - } - - std::optional Node::try_as_dispatch(std::string*) const - { - return std::optional{ m_scalar }; - } - - std::wstring Node::as_dispatch(std::wstring*) const - { - return Utility::ConvertToUTF16(m_scalar); - } - - std::optional Node::try_as_dispatch(std::wstring*) const - { - return Utility::TryConvertToUTF16(m_scalar); - } - - int64_t Node::as_dispatch(int64_t*) const - { - return std::stoll(m_scalar); - } - - std::optional Node::try_as_dispatch(int64_t*) const - { - if (m_scalar.empty()) - { - return {}; - } - - const char* begin = m_scalar.c_str(); - char* end = nullptr; - errno = 0; - int64_t result = static_cast(strtoll(begin, &end, 0)); - - if (errno == ERANGE || static_cast(end - begin) != m_scalar.length()) - { - return {}; - } - - return result; - } - - int Node::as_dispatch(int*) const - { - // To allow HResult representation - return static_cast(std::stoll(m_scalar, 0, 0)); - } - - std::optional Node::try_as_dispatch(int*) const - { - try - { - return std::optional{ static_cast(std::stoll(m_scalar, 0, 0)) }; - } - catch (...) - { - return {}; - } - } - - bool Node::as_dispatch(bool*) const - { - bool* t = nullptr; - auto tryToBool = this->try_as_dispatch(t); - if (tryToBool.has_value()) - { - return tryToBool.value(); - } - else - { - THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA); - } - } - - std::optional Node::try_as_dispatch(bool*) const - { - if (Utility::CaseInsensitiveEquals(m_scalar, "true")) - { - return std::optional{ true }; - } - else if (Utility::CaseInsensitiveEquals(m_scalar, "false")) - { - return std::optional{ false }; - } - - return {}; - } - - void Node::MergeSequenceNode(Node other, std::string_view key, bool caseInsensitive) - { - Require(Type::Sequence); - other.Require(Type::Sequence); - - auto getKeyValue = [&](const YAML::Node& node) { - auto keyNode = caseInsensitive ? node.GetChildNode(key) : node[key]; - if (keyNode.IsNull()) - { - THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA); - } - - auto keyValue = keyNode.as(); - return caseInsensitive ? std::string{ Utility::FoldCase(std::string_view{keyValue}) } : keyValue; - }; - - std::map newSequenceMap; - for (Node& node : m_sequence.value()) - { - node.Require(Type::Mapping); - auto keyValue = getKeyValue(node); - newSequenceMap.emplace(std::move(keyValue), std::move(node)); - } - - for (Node& node : other.m_sequence.value()) - { - node.Require(Type::Mapping); - auto keyValue = getKeyValue(node); - if (newSequenceMap.find(keyValue) == newSequenceMap.end()) - { - newSequenceMap.emplace(std::move(keyValue), std::move(node)); - } - else - { - newSequenceMap[keyValue].MergeMappingNode(node, caseInsensitive); - } - } - - m_sequence.reset(); - std::vector newSequence; - for (const auto& keyValuePair : newSequenceMap) - { - newSequence.push_back(keyValuePair.second); - } - - m_sequence = std::move(newSequence); - } - - void Node::MergeMappingNode(Node other, bool caseInsensitive) - { - Require(Type::Mapping); - other.Require(Type::Mapping); - - std::multimap uniques; - for (auto& keyValuePair : other.m_mapping.value()) - { - if (caseInsensitive) - { - auto node = GetChildNode(keyValuePair.first.as()); - if (node.IsNull()) - { - uniques.emplace(std::move(keyValuePair)); - } - } - else - { - if (m_mapping->count(keyValuePair.first) == 0) - { - uniques.emplace(std::move(keyValuePair)); - } - } - } - - m_mapping->merge(uniques); - } - - Node Load(std::string_view input) - { - Wrapper::Parser parser(input); - Wrapper::Document document = parser.Load(); - - if (document.HasRoot()) - { - return document.GetRoot(); - } - else - { - return {}; - } - } - - Node Load(const std::string& input) - { - return Load(static_cast(input)); - } - - Node Load(std::istream& input, Utility::SHA256::HashBuffer* hashOut) - { - Wrapper::Parser parser(input, hashOut); - Wrapper::Document document = parser.Load(); - - if (document.HasRoot()) - { - return document.GetRoot(); - } - else - { - return {}; - } - } - - Node Load(const std::filesystem::path& input, Utility::SHA256::HashBuffer* hashOut) - { - std::ifstream stream(input, std::ios_base::in | std::ios_base::binary); - THROW_LAST_ERROR_IF(stream.fail()); - return Load(stream, hashOut); - } - - Node Load(const std::filesystem::path& input) - { - return Load(input, nullptr); - } - - Node Load(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut) - { - return Load(input, &hashOut); - } - - Document LoadDocument(std::string_view input) - { - Wrapper::Parser parser(input); - Wrapper::Document document = parser.Load(); - - if (document.HasRoot()) - { - const Node root = document.GetRoot(); - const DocumentSchemaHeader schemaHeader = ExtractSchemaHeaderFromYaml(parser.GetEncodedInput(), root.Mark().line); - - return { root, schemaHeader }; - } - else - { - // Return an empty root and schema header. - return {}; - } - } - - Document LoadDocument(const std::string& input) - { - return LoadDocument(static_cast(input)); - } - - Document LoadDocument(std::istream& input, Utility::SHA256::HashBuffer* hashOut) - { - Wrapper::Parser parser(input, hashOut); - Wrapper::Document document = parser.Load(); - - if (document.HasRoot()) - { - const Node root = document.GetRoot(); - const DocumentSchemaHeader schemaHeader = ExtractSchemaHeaderFromYaml(parser.GetEncodedInput(), root.Mark().line); - - return { root, schemaHeader }; - } - else - { - // Return an empty root and schema header. - return {}; - } - } - - Document LoadDocument(const std::filesystem::path& input, Utility::SHA256::HashBuffer* hashOut) - { - std::ifstream stream(input, std::ios_base::in | std::ios_base::binary); - THROW_LAST_ERROR_IF(stream.fail()); - return LoadDocument(stream, hashOut); - } - - Document LoadDocument(const std::filesystem::path& input) - { - return LoadDocument(input, nullptr); - } - - Document LoadDocument(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut) - { - return LoadDocument(input, &hashOut); - } - - Emitter::Emitter() : - m_document(std::make_unique(true)) - { - SetAllowedInputs(); - } - - Emitter::Emitter(Emitter&&) noexcept = default; - Emitter& Emitter::operator=(Emitter&&) noexcept = default; - - Emitter::~Emitter() = default; - - Emitter& Emitter::operator<<(EmitterEvent event) - { - switch (event) - { - case AppInstaller::YAML::BeginSeq: - { - CheckInput(InputType::BeginSeq); - int id = m_document->AddSequence(); - AppendNode(id); - m_containers.emplace(id, false); - SetAllowedInputsForContainer(); - break; - } - case AppInstaller::YAML::EndSeq: - CheckInput(InputType::EndSeq); - m_containers.pop(); - SetAllowedInputsForContainer(); - break; - case AppInstaller::YAML::BeginMap: - { - CheckInput(InputType::BeginMap); - int id = m_document->AddMapping(); - AppendNode(id); - m_containers.emplace(id, true); - SetAllowedInputsForContainer(); - break; - } - case AppInstaller::YAML::EndMap: - CheckInput(InputType::EndMap); - m_containers.pop(); - SetAllowedInputsForContainer(); - break; - case AppInstaller::YAML::Key: - CheckInput(InputType::Key); - m_scalarType = InputType::Key; - SetAllowedInputs(); - break; - case AppInstaller::YAML::Value: - CheckInput(InputType::Value); - m_scalarType = InputType::Value; - SetAllowedInputs(); - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return *this; - } - - Emitter& Emitter::operator<<(std::string_view value) - { - CheckInput(InputType::Scalar); - - int id = m_document->AddScalar(value, m_scalarStyle.value_or(ScalarStyle::Any)); - m_scalarStyle = std::nullopt; - - if (!m_scalarType) - { - // Part of a sequence - AppendNode(id); - // No change to allowed inputs - } - else if (m_scalarType.value() == InputType::Key) - { - m_keyId = id; - m_scalarType = std::nullopt; - SetAllowedInputs(); - } - else if (m_scalarType.value() == InputType::Value) - { - // Mapping pair complete - AppendNode(id); - m_scalarType = std::nullopt; - SetAllowedInputsForContainer(); - } - else - { - THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE); - } - - return *this; - } - - Emitter& Emitter::operator<<(int64_t value) - { - std::ostringstream stream; - stream << value; - return operator<<(stream.str()); - } - - Emitter& Emitter::operator<<(int value) - { - std::ostringstream stream; - stream << value; - return operator<<(stream.str()); - } - - Emitter& Emitter::operator<<(bool value) - { - return operator<<(value ? "true"sv : "false"sv); - } - - Emitter& Emitter::operator<<(ScalarStyle style) - { - m_scalarStyle = style; - // Because without this you get a C26815... - (void)0; - return *this; - } - - std::string Emitter::str() - { - std::ostringstream stream; - Wrapper::Emitter emitter(stream); - - emitter.Dump(*m_document); - emitter.Flush(); - - return stream.str(); - } - - void Emitter::Emit(std::ostream& out) - { - Wrapper::Emitter emitter(out); - - emitter.Dump(*m_document); - emitter.Flush(); - } - - void Emitter::AppendNode(int id) - { - if (!m_containers.empty()) - { - ContainerInfo& ci = m_containers.top(); - - if (ci.IsMapping) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE, !m_keyId); - m_document->AppendMappingPair(ci.Id, m_keyId.value(), id); - m_keyId = std::nullopt; - } - else - { - m_document->AppendSequenceItem(ci.Id, id); - } - } - } - - size_t Emitter::GetInputBitmask(InputType type) - { - return static_cast(1) << static_cast(type); - } - - void Emitter::CheckInput(InputType type) - { - if ((m_allowedInputs & GetInputBitmask(type)) == 0) - { - AICLI_LOG(YAML, Error, << "Invalid emitter input [0x" << - std::hex << std::setw(2) << std::setfill('0') << GetInputBitmask(type) << "], expected one of [0x" << - std::hex << std::setw(2) << std::setfill('0') << m_allowedInputs << "]"); - THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE); - } - } - - void Emitter::SetAllowedInputsForContainer() - { - if (m_containers.empty()) - { - m_allowedInputs = 0; - } - else - { - if (m_containers.top().IsMapping) - { - SetAllowedInputs(); - } - else - { - SetAllowedInputs(); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include +#include "winget/Yaml.h" +#include "YamlWrapper.h" +#include "AppInstallerErrors.h" +#include "AppInstallerLogging.h" +#include "AppInstallerStrings.h" + + +namespace AppInstaller::YAML +{ + using namespace std::string_view_literals; + + namespace + { + Node s_globalInvalidNode; + + static constexpr std::string_view s_nullTag = "tag:yaml.org,2002:null"sv; + static constexpr std::string_view s_boolTag = "tag:yaml.org,2002:bool"sv; + static constexpr std::string_view s_strTag = "tag:yaml.org,2002:str"sv; + static constexpr std::string_view s_intTag = "tag:yaml.org,2002:int"sv; + static constexpr std::string_view s_floatTag = "tag:yaml.org,2002:float"sv; + static constexpr std::string_view s_timestampTag = "tag:yaml.org,2002:timestamp"sv; + static constexpr std::string_view s_seqTag = "tag:yaml.org,2002:seq"sv; + static constexpr std::string_view s_mapTag = "tag:yaml.org,2002:map"sv; + + std::string_view GetExceptionTypeStringView(Exception::Type type) + { + switch (type) + { + case Exception::Type::None: + return "None"sv; + case Exception::Type::Memory: + return "Memory"sv; + case Exception::Type::Reader: + return "Reader"sv; + case Exception::Type::Scanner: + return "Scanner"sv; + case Exception::Type::Parser: + return "Parser"sv; + case Exception::Type::Composer: + return "Composer"sv; + case Exception::Type::Writer: + return "Writer"sv; + case Exception::Type::Emitter: + return "Emitter"sv; + case Exception::Type::Policy: + return "Policy"sv; + } + + return "Unknown"sv; + } + + void OutputExceptionHeader(std::ostringstream& out, Exception::Type type) + { + out << "[YAML:" << GetExceptionTypeStringView(type) << "] "; + } + + void OutputMark(std::ostringstream& out, const Mark& mark) + { + out << "[line " << mark.line << "; col " << mark.column << ']'; + } + + Node::TagType ConvertToTagType(const std::string& tag) + { + if (tag == s_strTag) + { + return Node::TagType::Str; + } + else if (tag == s_seqTag) + { + return Node::TagType::Seq; + } + else if (tag == s_mapTag) + { + return Node::TagType::Map; + } + else if (tag == s_boolTag) + { + return Node::TagType::Bool; + } + else if (tag == s_intTag) + { + return Node::TagType::Int; + } + else if (tag == s_floatTag) + { + return Node::TagType::Float; + } + else if (tag == s_timestampTag) + { + return Node::TagType::Timestamp; + } + else if (tag == s_nullTag) + { + return Node::TagType::Null; + } + + return Node::TagType::Unknown; + } + + DocumentSchemaHeader ExtractSchemaHeaderFromYaml( const std::string& yamlDocument, size_t rootNodeLine) + { + std::istringstream input(yamlDocument); + std::string line; + size_t currentLine = 1; + + // Search for the schema header string in the comments before the root node. + while (currentLine < rootNodeLine && std::getline(input, line)) + { + std::string comment = Utility::Trim(line); + + // Check if the line is a comment + if (!comment.empty() && comment[0] == '#') + { + size_t pos = line.find(DocumentSchemaHeader::YamlLanguageServerKey); + + // Check if the comment contains the schema header string + if (pos != std::string::npos) + { + return DocumentSchemaHeader(std::move(comment), YAML::Mark{ currentLine, pos}); + } + } + + currentLine++; + } + + return {}; + } + } + + Exception::Exception(Type type) : + wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR) + { + std::ostringstream out; + OutputExceptionHeader(out, type); + + if (type == Type::Memory) + { + out << "Unable to (re)allocate memory"; + } + else + { + out << "An unknown error occurred"; + } + + m_what = out.str(); + } + + Exception::Exception(Type type, const char* problem, size_t offset, int value) : + wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR) + { + std::ostringstream out; + OutputExceptionHeader(out, type); + + out << (problem ? problem : "Unexplained error"); + + if (value != -1) + { + out << " [" << value << ']'; + } + + out << " at " << offset; + + m_what = out.str(); + } + + Exception::Exception(Type type, const char* problem, const Mark& problemMark, const char* context, const Mark& contextMark) : + wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR), m_mark(problemMark) + { + std::ostringstream out; + OutputExceptionHeader(out, type); + + if (context) + { + out << context << ' '; + OutputMark(out, contextMark); + out << ' ' << (problem ? problem : "unexplained error"); + } + else + { + out << (problem ? problem : "Unexplained error"); + } + + out << ' '; + OutputMark(out, problemMark); + + m_what = out.str(); + } + + Exception::Exception(Type type, const char* problem) : + wil::ResultException(APPINSTALLER_CLI_ERROR_LIBYAML_ERROR) + { + std::ostringstream out; + OutputExceptionHeader(out, type); + + out << (problem ? problem : "Unexplained error"); + + m_what = out.str(); + } + + const char* Exception::what() const noexcept + { + return m_what.c_str(); + } + + const Mark& Exception::GetMark() const + { + return m_mark; + } + + Node::Node(Type type, std::string tag, const YAML::Mark& mark) : + m_type(type), m_tag(std::move(tag)), m_mark(mark) + { + if (m_type == Type::Sequence) + { + m_sequence = decltype(m_sequence)::value_type{}; + } + else if (m_type == Type::Mapping) + { + m_mapping = decltype(m_mapping)::value_type{}; + } + + m_tagType = ConvertToTagType(m_tag); + } + + void Node::SetScalar(std::string value) + { + Require(Type::Scalar); + m_scalar = std::move(value); + } + + void Node::SetScalar(std::string value, bool isQuoted) + { + this->SetScalar(value); + + // For untagged scalar nodes, libyaml always assigns the generic string + // tag. Here we just try our best and assume that if the value is unquoted + // then is not necessarily a string. + // TODO: handle float and timestamps + if (!isQuoted && this->GetTagType() == TagType::Str) + { + // Integer + // 0 | -? [1-9] [0-9]* + auto tryInt = this->try_as(); + if (tryInt.has_value()) + { + m_tagType = TagType::Int; + return; + } + + // Boolean. Either 'true' or 'false' + auto tryBool = this->try_as(); + if (tryBool.has_value()) + { + m_tagType = TagType::Bool; + } + } + } + + bool Node::operator<(const Node& other) const + { + Require(Type::Scalar); + other.Require(Type::Scalar); + return this->m_scalar < other.m_scalar; + } + + Node& Node::operator[](std::string_view key) + { + Require(Type::Mapping); + auto itrs = m_mapping->equal_range(key); + + if (itrs.first == itrs.second) + { + return s_globalInvalidNode; + } + + Node& result = itrs.first->second; + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, ++itrs.first != itrs.second); + + return result; + } + + const Node& Node::operator[](std::string_view key) const + { + Require(Type::Mapping); + auto itrs = m_mapping->equal_range(key); + + if (itrs.first == itrs.second) + { + return s_globalInvalidNode; + } + + const Node& result = itrs.first->second; + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, ++itrs.first != itrs.second); + + return result; + } + + // Gets a child node from the mapping by its name. + Node& Node::GetChildNode(std::string_view key) + { + Require(Type::Mapping); + + auto itr = m_mapping->begin(); + for (; itr != m_mapping->end(); itr++) + { + if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) + { + break; + } + } + + if (itr == m_mapping->end()) + { + return s_globalInvalidNode; + } + + auto firstFound = itr; + for (++itr; itr != m_mapping->end(); itr++) + { + if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) + { + break; + } + } + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, itr != m_mapping->end()); + Node& result = firstFound->second; + return result; + } + + const Node& Node::GetChildNode(std::string_view key) const + { + Require(Type::Mapping); + + auto itr = m_mapping->begin(); + for (; itr != m_mapping->end(); itr++) + { + if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) + { + break; + } + } + + if (itr == m_mapping->end()) + { + return s_globalInvalidNode; + } + + auto firstFound = itr; + for (++itr; itr != m_mapping->end(); itr++) + { + if (Utility::CaseInsensitiveEquals(itr->first.m_scalar, key)) + { + break; + } + } + + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY, itr != m_mapping->end()); + const Node& result = firstFound->second; + return result; + } + + Node& Node::operator[](size_t index) + { + Require(Type::Sequence); + return m_sequence.value()[index]; + } + + const Node& Node::operator[](size_t index) const + { + Require(Type::Sequence); + return m_sequence.value()[index]; + } + + size_t Node::size() const + { + switch (m_type) + { + case Type::Invalid: + case Type::None: + case Type::Scalar: + return 0; + case Type::Sequence: + return m_sequence->size(); + case Type::Mapping: + return m_mapping->size(); + } + + THROW_HR(E_UNEXPECTED); + } + + const std::vector& Node::Sequence() const + { + Require(Type::Sequence); + return m_sequence.value(); + } + + const std::multimap& Node::Mapping() const + { + Require(Type::Mapping); + return m_mapping.value(); + } + + void Node::Require(Type type) const + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION, m_type != type); + } + + std::string Node::as_dispatch(std::string*) const + { + return m_scalar; + } + + std::optional Node::try_as_dispatch(std::string*) const + { + return std::optional{ m_scalar }; + } + + std::wstring Node::as_dispatch(std::wstring*) const + { + return Utility::ConvertToUTF16(m_scalar); + } + + std::optional Node::try_as_dispatch(std::wstring*) const + { + return Utility::TryConvertToUTF16(m_scalar); + } + + int64_t Node::as_dispatch(int64_t*) const + { + return std::stoll(m_scalar); + } + + std::optional Node::try_as_dispatch(int64_t*) const + { + if (m_scalar.empty()) + { + return {}; + } + + const char* begin = m_scalar.c_str(); + char* end = nullptr; + errno = 0; + int64_t result = static_cast(strtoll(begin, &end, 0)); + + if (errno == ERANGE || static_cast(end - begin) != m_scalar.length()) + { + return {}; + } + + return result; + } + + int Node::as_dispatch(int*) const + { + // To allow HResult representation + return static_cast(std::stoll(m_scalar, 0, 0)); + } + + std::optional Node::try_as_dispatch(int*) const + { + try + { + return std::optional{ static_cast(std::stoll(m_scalar, 0, 0)) }; + } + catch (...) + { + return {}; + } + } + + bool Node::as_dispatch(bool*) const + { + bool* t = nullptr; + auto tryToBool = this->try_as_dispatch(t); + if (tryToBool.has_value()) + { + return tryToBool.value(); + } + else + { + THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA); + } + } + + std::optional Node::try_as_dispatch(bool*) const + { + if (Utility::CaseInsensitiveEquals(m_scalar, "true")) + { + return std::optional{ true }; + } + else if (Utility::CaseInsensitiveEquals(m_scalar, "false")) + { + return std::optional{ false }; + } + + return {}; + } + + void Node::MergeSequenceNode(Node other, std::string_view key, bool caseInsensitive) + { + Require(Type::Sequence); + other.Require(Type::Sequence); + + auto getKeyValue = [&](const YAML::Node& node) { + auto keyNode = caseInsensitive ? node.GetChildNode(key) : node[key]; + if (keyNode.IsNull()) + { + THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA); + } + + auto keyValue = keyNode.as(); + return caseInsensitive ? std::string{ Utility::FoldCase(std::string_view{keyValue}) } : keyValue; + }; + + std::map newSequenceMap; + for (Node& node : m_sequence.value()) + { + node.Require(Type::Mapping); + auto keyValue = getKeyValue(node); + newSequenceMap.emplace(std::move(keyValue), std::move(node)); + } + + for (Node& node : other.m_sequence.value()) + { + node.Require(Type::Mapping); + auto keyValue = getKeyValue(node); + if (newSequenceMap.find(keyValue) == newSequenceMap.end()) + { + newSequenceMap.emplace(std::move(keyValue), std::move(node)); + } + else + { + newSequenceMap[keyValue].MergeMappingNode(node, caseInsensitive); + } + } + + m_sequence.reset(); + std::vector newSequence; + for (const auto& keyValuePair : newSequenceMap) + { + newSequence.push_back(keyValuePair.second); + } + + m_sequence = std::move(newSequence); + } + + void Node::MergeMappingNode(Node other, bool caseInsensitive) + { + Require(Type::Mapping); + other.Require(Type::Mapping); + + std::multimap uniques; + for (auto& keyValuePair : other.m_mapping.value()) + { + if (caseInsensitive) + { + auto node = GetChildNode(keyValuePair.first.as()); + if (node.IsNull()) + { + uniques.emplace(std::move(keyValuePair)); + } + } + else + { + if (m_mapping->count(keyValuePair.first) == 0) + { + uniques.emplace(std::move(keyValuePair)); + } + } + } + + m_mapping->merge(uniques); + } + + Node Load(std::string_view input) + { + Wrapper::Parser parser(input); + Wrapper::Document document = parser.Load(); + + if (document.HasRoot()) + { + return document.GetRoot(); + } + else + { + return {}; + } + } + + Node Load(const std::string& input) + { + return Load(static_cast(input)); + } + + Node Load(std::istream& input, Utility::SHA256::HashBuffer* hashOut) + { + Wrapper::Parser parser(input, hashOut); + Wrapper::Document document = parser.Load(); + + if (document.HasRoot()) + { + return document.GetRoot(); + } + else + { + return {}; + } + } + + Node Load(const std::filesystem::path& input, Utility::SHA256::HashBuffer* hashOut) + { + std::ifstream stream(input, std::ios_base::in | std::ios_base::binary); + THROW_LAST_ERROR_IF(stream.fail()); + return Load(stream, hashOut); + } + + Node Load(const std::filesystem::path& input) + { + return Load(input, nullptr); + } + + Node Load(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut) + { + return Load(input, &hashOut); + } + + Document LoadDocument(std::string_view input) + { + Wrapper::Parser parser(input); + Wrapper::Document document = parser.Load(); + + if (document.HasRoot()) + { + const Node root = document.GetRoot(); + const DocumentSchemaHeader schemaHeader = ExtractSchemaHeaderFromYaml(parser.GetEncodedInput(), root.Mark().line); + + return { root, schemaHeader }; + } + else + { + // Return an empty root and schema header. + return {}; + } + } + + Document LoadDocument(const std::string& input) + { + return LoadDocument(static_cast(input)); + } + + Document LoadDocument(std::istream& input, Utility::SHA256::HashBuffer* hashOut) + { + Wrapper::Parser parser(input, hashOut); + Wrapper::Document document = parser.Load(); + + if (document.HasRoot()) + { + const Node root = document.GetRoot(); + const DocumentSchemaHeader schemaHeader = ExtractSchemaHeaderFromYaml(parser.GetEncodedInput(), root.Mark().line); + + return { root, schemaHeader }; + } + else + { + // Return an empty root and schema header. + return {}; + } + } + + Document LoadDocument(const std::filesystem::path& input, Utility::SHA256::HashBuffer* hashOut) + { + std::ifstream stream(input, std::ios_base::in | std::ios_base::binary); + THROW_LAST_ERROR_IF(stream.fail()); + return LoadDocument(stream, hashOut); + } + + Document LoadDocument(const std::filesystem::path& input) + { + return LoadDocument(input, nullptr); + } + + Document LoadDocument(const std::filesystem::path& input, Utility::SHA256::HashBuffer& hashOut) + { + return LoadDocument(input, &hashOut); + } + + Emitter::Emitter() : + m_document(std::make_unique(true)) + { + SetAllowedInputs(); + } + + Emitter::Emitter(Emitter&&) noexcept = default; + Emitter& Emitter::operator=(Emitter&&) noexcept = default; + + Emitter::~Emitter() = default; + + Emitter& Emitter::operator<<(EmitterEvent event) + { + switch (event) + { + case AppInstaller::YAML::BeginSeq: + { + CheckInput(InputType::BeginSeq); + int id = m_document->AddSequence(); + AppendNode(id); + m_containers.emplace(id, false); + SetAllowedInputsForContainer(); + break; + } + case AppInstaller::YAML::EndSeq: + CheckInput(InputType::EndSeq); + m_containers.pop(); + SetAllowedInputsForContainer(); + break; + case AppInstaller::YAML::BeginMap: + { + CheckInput(InputType::BeginMap); + int id = m_document->AddMapping(); + AppendNode(id); + m_containers.emplace(id, true); + SetAllowedInputsForContainer(); + break; + } + case AppInstaller::YAML::EndMap: + CheckInput(InputType::EndMap); + m_containers.pop(); + SetAllowedInputsForContainer(); + break; + case AppInstaller::YAML::Key: + CheckInput(InputType::Key); + m_scalarType = InputType::Key; + SetAllowedInputs(); + break; + case AppInstaller::YAML::Value: + CheckInput(InputType::Value); + m_scalarType = InputType::Value; + SetAllowedInputs(); + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return *this; + } + + Emitter& Emitter::operator<<(std::string_view value) + { + CheckInput(InputType::Scalar); + + int id = m_document->AddScalar(value, m_scalarStyle.value_or(ScalarStyle::Any)); + m_scalarStyle = std::nullopt; + + if (!m_scalarType) + { + // Part of a sequence + AppendNode(id); + // No change to allowed inputs + } + else if (m_scalarType.value() == InputType::Key) + { + m_keyId = id; + m_scalarType = std::nullopt; + SetAllowedInputs(); + } + else if (m_scalarType.value() == InputType::Value) + { + // Mapping pair complete + AppendNode(id); + m_scalarType = std::nullopt; + SetAllowedInputsForContainer(); + } + else + { + THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE); + } + + return *this; + } + + Emitter& Emitter::operator<<(int64_t value) + { + std::ostringstream stream; + stream << value; + return operator<<(stream.str()); + } + + Emitter& Emitter::operator<<(int value) + { + std::ostringstream stream; + stream << value; + return operator<<(stream.str()); + } + + Emitter& Emitter::operator<<(bool value) + { + return operator<<(value ? "true"sv : "false"sv); + } + + Emitter& Emitter::operator<<(ScalarStyle style) + { + m_scalarStyle = style; + // Because without this you get a C26815... + (void)0; + return *this; + } + + std::string Emitter::str() + { + std::ostringstream stream; + Wrapper::Emitter emitter(stream); + + emitter.Dump(*m_document); + emitter.Flush(); + + return stream.str(); + } + + void Emitter::Emit(std::ostream& out) + { + Wrapper::Emitter emitter(out); + + emitter.Dump(*m_document); + emitter.Flush(); + } + + void Emitter::AppendNode(int id) + { + if (!m_containers.empty()) + { + ContainerInfo& ci = m_containers.top(); + + if (ci.IsMapping) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE, !m_keyId); + m_document->AppendMappingPair(ci.Id, m_keyId.value(), id); + m_keyId = std::nullopt; + } + else + { + m_document->AppendSequenceItem(ci.Id, id); + } + } + } + + size_t Emitter::GetInputBitmask(InputType type) + { + return static_cast(1) << static_cast(type); + } + + void Emitter::CheckInput(InputType type) + { + if ((m_allowedInputs & GetInputBitmask(type)) == 0) + { + AICLI_LOG(YAML, Error, << "Invalid emitter input [0x" << + std::hex << std::setw(2) << std::setfill('0') << GetInputBitmask(type) << "], expected one of [0x" << + std::hex << std::setw(2) << std::setfill('0') << m_allowedInputs << "]"); + THROW_HR(APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE); + } + } + + void Emitter::SetAllowedInputsForContainer() + { + if (m_containers.empty()) + { + m_allowedInputs = 0; + } + else + { + if (m_containers.top().IsMapping) + { + SetAllowedInputs(); + } + else + { + SetAllowedInputs(); + } + } + } +} diff --git a/src/AppInstallerSharedLib/YamlWrapper.cpp b/src/AppInstallerSharedLib/YamlWrapper.cpp index 1f08f98a9f..554af9ca66 100644 --- a/src/AppInstallerSharedLib/YamlWrapper.cpp +++ b/src/AppInstallerSharedLib/YamlWrapper.cpp @@ -1,579 +1,579 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include -#include "YamlWrapper.h" -#include "AppInstallerErrors.h" -#include "AppInstallerLogging.h" -#include "AppInstallerStrings.h" - - -namespace AppInstaller::YAML::Wrapper -{ - namespace - { - Node::Type ConvertNodeType(yaml_node_type_t type) - { - switch (type) - { - case YAML_NO_NODE: - return Node::Type::None; - case YAML_SCALAR_NODE: - return Node::Type::Scalar; - case YAML_SEQUENCE_NODE: - return Node::Type::Sequence; - case YAML_MAPPING_NODE: - return Node::Type::Mapping; - } - - THROW_HR(E_UNEXPECTED); - } - - Exception::Type ConvertErrorType(yaml_error_type_t type) - { - switch (type) - { - case YAML_NO_ERROR: - return Exception::Type::None; - case YAML_MEMORY_ERROR: - return Exception::Type::Memory; - case YAML_READER_ERROR: - return Exception::Type::Reader; - case YAML_SCANNER_ERROR: - return Exception::Type::Scanner; - case YAML_PARSER_ERROR: - return Exception::Type::Parser; - case YAML_COMPOSER_ERROR: - return Exception::Type::Composer; - case YAML_WRITER_ERROR: - return Exception::Type::Writer; - case YAML_EMITTER_ERROR: - return Exception::Type::Emitter; - } - - THROW_HR(E_UNEXPECTED); - } - - Mark ConvertMark(const yaml_mark_t& mark) - { - return { mark.line + 1, mark.column + 1 }; - } - - std::string ConvertYamlString(yaml_char_t* string, const yaml_mark_t& mark, size_t length = std::string::npos) - { - std::string_view resultView; - - if (length == std::string::npos) - { - resultView = { reinterpret_cast(string) }; - } - else - { - resultView = { reinterpret_cast(string), length }; - } - - size_t invalidCharacter = Utility::FindControlCodeToConvert(resultView); - if (invalidCharacter != std::string::npos) - { - THROW_EXCEPTION(Exception(Exception::Type::Policy, "unsupported control character", ConvertMark(mark))); - } - - return std::string{ resultView }; - } - - std::string ConvertScalarToString(yaml_node_t* node, const yaml_mark_t& mark) - { - return ConvertYamlString(node->data.scalar.value, mark, node->data.scalar.length); - } - - yaml_scalar_style_t ConvertStyle(ScalarStyle style) - { - switch (style) - { - case ScalarStyle::Any: return yaml_scalar_style_t::YAML_ANY_SCALAR_STYLE; - case ScalarStyle::Plain: return yaml_scalar_style_t::YAML_PLAIN_SCALAR_STYLE; - case ScalarStyle::SingleQuoted: return yaml_scalar_style_t::YAML_SINGLE_QUOTED_SCALAR_STYLE; - case ScalarStyle::DoubleQuoted: return yaml_scalar_style_t::YAML_DOUBLE_QUOTED_SCALAR_STYLE; - case ScalarStyle::Literal: return yaml_scalar_style_t::YAML_LITERAL_SCALAR_STYLE; - case ScalarStyle::Folded: return yaml_scalar_style_t::YAML_FOLDED_SCALAR_STYLE; - default: THROW_HR(E_UNEXPECTED); - } - } - } - - Document::Document(bool init) : - m_token(true) - { - if (init) - { - // Initialize with no version directive or tags, and implicit start and end. - if (!yaml_document_initialize(&m_document, NULL, NULL, NULL, 1, 1)) - { - THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); - } - } - else - { - memset(&m_document, 0, sizeof(m_document)); - } - } - - Document::~Document() - { - if (m_token) - { - yaml_document_delete(&m_document); - } - } - - bool Document::HasRoot() - { - return yaml_document_get_root_node(&m_document) != nullptr; - } - - Node Document::GetRoot() - { - yaml_node_t* root = yaml_document_get_root_node(&m_document); - - if (!root) - { - return {}; - } - - Node result(ConvertNodeType(root->type), ConvertYamlString(root->tag, root->start_mark), ConvertMark(root->start_mark)); - - struct StackItem - { - StackItem(yaml_node_t* yn, Node* n) : - yamlNode(yn), node(n) {} - - yaml_node_t* yamlNode = nullptr; - Node* node = nullptr; - size_t childOffset = 0; - }; - - static int YAML_DOCUMENT_NEST_LEVEL_LIMIT = 100; - int nestLevel = 0; - - std::stack resultStack; - resultStack.emplace(root, &result); - - while (!resultStack.empty()) - { - StackItem& stackItem = resultStack.top(); - bool pop = false; - - switch (stackItem.yamlNode->type) - { - case YAML_NO_NODE: - pop = true; - break; - case YAML_SCALAR_NODE: - stackItem.node->SetScalar( - ConvertScalarToString(stackItem.yamlNode, stackItem.yamlNode->start_mark), - stackItem.yamlNode->data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE || - stackItem.yamlNode->data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE); - pop = true; - break; - case YAML_SEQUENCE_NODE: - { - if (stackItem.childOffset == 0) - { - // We've entered the sequence. - nestLevel++; - } - - yaml_node_item_t* child = stackItem.yamlNode->data.sequence.items.start + stackItem.childOffset++; - if (child < stackItem.yamlNode->data.sequence.items.top) - { - yaml_node_t* childYamlNode = GetNode(*child); - Node& childNode = stackItem.node->AddSequenceNode(ConvertNodeType(childYamlNode->type), ConvertYamlString(childYamlNode->tag, childYamlNode->start_mark), ConvertMark(childYamlNode->start_mark)); - resultStack.emplace(childYamlNode, &childNode); - } - else - { - // We've reached the end of the sequence - pop = true; - nestLevel--; - } - break; - } - case YAML_MAPPING_NODE: - { - if (stackItem.childOffset == 0) - { - // We've entered the mapping. - nestLevel++; - } - - yaml_node_pair_t* child = stackItem.yamlNode->data.mapping.pairs.start + stackItem.childOffset++; - if (child < stackItem.yamlNode->data.mapping.pairs.top) - { - yaml_node_t* keyYamlNode = GetNode(child->key); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY, keyYamlNode->type != YAML_SCALAR_NODE); - - Node keyNode(ConvertNodeType(keyYamlNode->type), ConvertYamlString(keyYamlNode->tag, keyYamlNode->start_mark), ConvertMark(keyYamlNode->start_mark)); - keyNode.SetScalar(ConvertScalarToString(keyYamlNode, keyYamlNode->start_mark)); - - yaml_node_t* valueYamlNode = GetNode(child->value); - - Node& childNode = stackItem.node->AddMappingNode(std::move(keyNode), ConvertNodeType(valueYamlNode->type), ConvertYamlString(valueYamlNode->tag, valueYamlNode->start_mark), ConvertMark(valueYamlNode->start_mark)); - resultStack.emplace(valueYamlNode, &childNode); - } - else - { - // We've reached the end of the mapping - pop = true; - nestLevel--; - } - break; - } - } - - if (pop) - { - resultStack.pop(); - } - - THROW_HR_IF_MSG(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, nestLevel > YAML_DOCUMENT_NEST_LEVEL_LIMIT, "Too many layers of nested nodes."); - } - - return result; - } - - int Document::AddScalar(std::string_view value, ScalarStyle style) - { - int result = yaml_document_add_scalar(&m_document, NULL, reinterpret_cast(value.data()), static_cast(value.size()), ConvertStyle(style)); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0); - return result; - } - - int Document::AddSequence() - { - int result = yaml_document_add_sequence(&m_document, NULL, YAML_ANY_SEQUENCE_STYLE); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0); - return result; - } - - int Document::AddMapping() - { - int result = yaml_document_add_mapping(&m_document, NULL, YAML_ANY_MAPPING_STYLE); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0); - return result; - } - - void Document::AppendSequenceItem(int sequence, int item) - { - if (!yaml_document_append_sequence_item(&m_document, sequence, item)) - { - THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); - } - } - - void Document::AppendMappingPair(int mapping, int key, int value) - { - if (!yaml_document_append_mapping_pair(&m_document, mapping, key, value)) - { - THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); - } - } - - yaml_node_t* Document::GetNode(yaml_node_item_t index) - { - yaml_node_t* result = yaml_document_get_node(&m_document, index); - THROW_HR_IF(E_BOUNDS, !result); - return result; - } - - Parser::Parser(std::string_view input) : m_token(true), m_input(input) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_parser_initialize(&m_parser)); - - PrepareInput(); - yaml_parser_set_input_string(&m_parser, reinterpret_cast(m_input.c_str()), m_input.size()); - } - - Parser::Parser(std::istream& input, Utility::SHA256::HashBuffer* hashOut) : m_token(true) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_parser_initialize(&m_parser)); - - m_input = Utility::ReadEntireStream(input); - - if (hashOut) - { - *hashOut = Utility::SHA256::ComputeHash(reinterpret_cast(m_input.data()), static_cast(m_input.size())); - } - - PrepareInput(); - yaml_parser_set_input_string(&m_parser, reinterpret_cast(m_input.c_str()), m_input.size()); - } - - Parser::~Parser() - { - if (m_token) - { - yaml_parser_delete(&m_parser); - } - } - - Document Parser::Load() - { - Document result; - - if (!yaml_parser_load(&m_parser, &result)) - { - Exception::Type type = ConvertErrorType(m_parser.error); - - switch (type) - { - case Exception::Type::Memory: - THROW_EXCEPTION(Exception(type)); - case Exception::Type::Reader: - THROW_EXCEPTION(Exception(type, m_parser.problem, m_parser.problem_offset, m_parser.problem_value)); - case Exception::Type::Scanner: - case Exception::Type::Parser: - case Exception::Type::Composer: - THROW_EXCEPTION(Exception(type, m_parser.problem, ConvertMark(m_parser.problem_mark), m_parser.context, ConvertMark(m_parser.context_mark))); - default: - THROW_EXCEPTION(Exception(type, "An unexpected error type occurred in Parser::Load")); - } - } - - return result; - } - - void Parser::PrepareInput() - { - constexpr char c_utf16LEBOM[2] = { static_cast(0xFF), static_cast(0xFE) }; - constexpr char c_utf16BEBOM[2] = { static_cast(0xFE), static_cast(0xFF) }; - constexpr char c_utf8BOM[3] = { static_cast(0xEF), static_cast(0xBB), static_cast(0xBF) }; - - // If input has a BOM, we want to remove it to prevent errors with checking for comments within the input document. - - // Check for UTF-16 BOMs - if (m_input.size() >= sizeof(c_utf16LEBOM) && std::memcmp(m_input.data(), c_utf16LEBOM, sizeof(c_utf16LEBOM)) == 0) - { - AICLI_LOG(YAML, Verbose, << "Found UTF-16 LE BOM"); - yaml_parser_set_encoding(&m_parser, YAML_UTF16LE_ENCODING); // Without the BOM, the encoding must be explicitly set - m_input.erase(0, sizeof(c_utf16LEBOM)); // Remove the BOM from the input - return; - } - - if (m_input.size() >= sizeof(c_utf16BEBOM) && std::memcmp(m_input.data(), c_utf16BEBOM, sizeof(c_utf16BEBOM)) == 0) - { - AICLI_LOG(YAML, Verbose, << "Found UTF-16 BE BOM"); - yaml_parser_set_encoding(&m_parser, YAML_UTF16BE_ENCODING); // Without the BOM, the encoding must be explicitly set - m_input.erase(0, sizeof(c_utf16BEBOM)); // Remove the BOM from the input - return; - } - - // Check for UTF-8 BOM - if (m_input.size() >= sizeof(c_utf8BOM) && std::memcmp(m_input.data(), c_utf8BOM, sizeof(c_utf8BOM)) == 0) - { - AICLI_LOG(YAML, Verbose, << "Found UTF-8 BOM"); - yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING); // Without the BOM, the encoding must be explicitly set - m_input.erase(0, sizeof(c_utf8BOM)); // Remove the BOM from the input - return; - } - - // Check for BOM-less UTF-16 LE - INT expectedTests = IS_TEXT_UNICODE_ASCII16 | IS_TEXT_UNICODE_STATISTICS | IS_TEXT_UNICODE_CONTROLS; - INT testResults = expectedTests; - if (IsTextUnicode(m_input.data(), wil::safe_cast(m_input.size()), &testResults) || testResults == expectedTests) - { - AICLI_LOG(YAML, Verbose, << "Detected UTF-16 LE"); - yaml_parser_set_encoding(&m_parser, YAML_UTF16LE_ENCODING); - return; - } - - // Check for BOM-less UTF-16 BE - expectedTests = IS_TEXT_UNICODE_REVERSE_ASCII16 | IS_TEXT_UNICODE_REVERSE_STATISTICS | IS_TEXT_UNICODE_REVERSE_CONTROLS; - testResults = expectedTests; - if (IsTextUnicode(m_input.data(), wil::safe_cast(m_input.size()), &testResults) || testResults == expectedTests) - { - AICLI_LOG(YAML, Verbose, << "Detected UTF-16 BE"); - yaml_parser_set_encoding(&m_parser, YAML_UTF16BE_ENCODING); - return; - } - - // Check for BOM-less UTF-8 - UINT nChars = MultiByteToWideChar( - CP_UTF8, - MB_ERR_INVALID_CHARS, - m_input.data(), - wil::safe_cast(m_input.size()), - NULL, - 0); - - if (nChars > 0 || GetLastError() != ERROR_NO_UNICODE_TRANSLATION) - { - AICLI_LOG(YAML, Verbose, << "Detected UTF-8"); - yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING); - return; - } - - // Must be ANSI (Windows-1252 assumed), convert to UTF-8 - AICLI_LOG(YAML, Verbose, << "Assuming ANSI Windows-1252"); - std::wstring utf16 = Utility::ConvertToUTF16(m_input, 1252); - m_input = Utility::ConvertToUTF8(utf16); - yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING); - } - - Event::~Event() - { - if (m_token) - { - yaml_event_delete(&m_event); - } - } - - Event Event::StreamStart() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_stream_start_event_initialize(&result, YAML_UTF8_ENCODING)); - result.m_token = true; - return result; - } - - Event Event::StreamEnd() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_stream_end_event_initialize(&result)); - result.m_token = true; - return result; - } - - Event Event::DocumentStart() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_document_start_event_initialize(&result, NULL, NULL, NULL, 1)); - result.m_token = true; - return result; - } - - Event Event::DocumentEnd() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_document_end_event_initialize(&result, 1)); - result.m_token = true; - return result; - } - - Event Event::SequenceStart() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_sequence_start_event_initialize(&result, NULL, NULL, 1, YAML_ANY_SEQUENCE_STYLE)); - result.m_token = true; - return result; - } - - Event Event::SequenceEnd() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_sequence_end_event_initialize(&result)); - result.m_token = true; - return result; - } - - Event Event::MappingStart() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_mapping_start_event_initialize(&result, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE)); - result.m_token = true; - return result; - } - - Event Event::MappingEnd() - { - Event result; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_mapping_end_event_initialize(&result)); - result.m_token = true; - return result; - } - - Emitter::Emitter(std::ostream& output) : - m_token(true), m_outputStream(&output) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_emitter_initialize(&m_emitter)); - yaml_emitter_set_output(&m_emitter, StreamWriteHandler, this); - yaml_emitter_set_encoding(&m_emitter, YAML_UTF8_ENCODING); - } - - Emitter::~Emitter() - { - if (m_token) - { - yaml_emitter_delete(&m_emitter); - } - } - - void Emitter::Emit(Event& event) - { - event.Detach(); - if (!yaml_emitter_emit(&m_emitter, &event)) - { - ThrowError(); - } - } - - void Emitter::Emit(Event&& event) - { - event.Detach(); - if (!yaml_emitter_emit(&m_emitter, &event)) - { - ThrowError(); - } - } - - void Emitter::Dump(Document& document) - { - document.Detach(); - if (!yaml_emitter_dump(&m_emitter, &document)) - { - ThrowError(); - } - } - - void Emitter::Flush() - { - if (!yaml_emitter_flush(&m_emitter)) - { - ThrowError(); - } - } - - int Emitter::StreamWriteHandler( - void* data, - unsigned char* buffer, - size_t size) - { - Emitter& emitter = *reinterpret_cast(data); - - try - { - emitter.m_outputStream->write(reinterpret_cast(buffer), size); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - return 0; - } - - return 1; - } - - void Emitter::ThrowError() - { - Exception::Type type = ConvertErrorType(m_emitter.error); - - switch (type) - { - case Exception::Type::Memory: - THROW_EXCEPTION(Exception(type)); - case Exception::Type::Emitter: - case Exception::Type::Writer: - THROW_EXCEPTION(Exception(type, m_emitter.problem)); - default: - THROW_EXCEPTION(Exception(type, "An unexpected error type occurred in Emitter")); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include +#include "YamlWrapper.h" +#include "AppInstallerErrors.h" +#include "AppInstallerLogging.h" +#include "AppInstallerStrings.h" + + +namespace AppInstaller::YAML::Wrapper +{ + namespace + { + Node::Type ConvertNodeType(yaml_node_type_t type) + { + switch (type) + { + case YAML_NO_NODE: + return Node::Type::None; + case YAML_SCALAR_NODE: + return Node::Type::Scalar; + case YAML_SEQUENCE_NODE: + return Node::Type::Sequence; + case YAML_MAPPING_NODE: + return Node::Type::Mapping; + } + + THROW_HR(E_UNEXPECTED); + } + + Exception::Type ConvertErrorType(yaml_error_type_t type) + { + switch (type) + { + case YAML_NO_ERROR: + return Exception::Type::None; + case YAML_MEMORY_ERROR: + return Exception::Type::Memory; + case YAML_READER_ERROR: + return Exception::Type::Reader; + case YAML_SCANNER_ERROR: + return Exception::Type::Scanner; + case YAML_PARSER_ERROR: + return Exception::Type::Parser; + case YAML_COMPOSER_ERROR: + return Exception::Type::Composer; + case YAML_WRITER_ERROR: + return Exception::Type::Writer; + case YAML_EMITTER_ERROR: + return Exception::Type::Emitter; + } + + THROW_HR(E_UNEXPECTED); + } + + Mark ConvertMark(const yaml_mark_t& mark) + { + return { mark.line + 1, mark.column + 1 }; + } + + std::string ConvertYamlString(yaml_char_t* string, const yaml_mark_t& mark, size_t length = std::string::npos) + { + std::string_view resultView; + + if (length == std::string::npos) + { + resultView = { reinterpret_cast(string) }; + } + else + { + resultView = { reinterpret_cast(string), length }; + } + + size_t invalidCharacter = Utility::FindControlCodeToConvert(resultView); + if (invalidCharacter != std::string::npos) + { + THROW_EXCEPTION(Exception(Exception::Type::Policy, "unsupported control character", ConvertMark(mark))); + } + + return std::string{ resultView }; + } + + std::string ConvertScalarToString(yaml_node_t* node, const yaml_mark_t& mark) + { + return ConvertYamlString(node->data.scalar.value, mark, node->data.scalar.length); + } + + yaml_scalar_style_t ConvertStyle(ScalarStyle style) + { + switch (style) + { + case ScalarStyle::Any: return yaml_scalar_style_t::YAML_ANY_SCALAR_STYLE; + case ScalarStyle::Plain: return yaml_scalar_style_t::YAML_PLAIN_SCALAR_STYLE; + case ScalarStyle::SingleQuoted: return yaml_scalar_style_t::YAML_SINGLE_QUOTED_SCALAR_STYLE; + case ScalarStyle::DoubleQuoted: return yaml_scalar_style_t::YAML_DOUBLE_QUOTED_SCALAR_STYLE; + case ScalarStyle::Literal: return yaml_scalar_style_t::YAML_LITERAL_SCALAR_STYLE; + case ScalarStyle::Folded: return yaml_scalar_style_t::YAML_FOLDED_SCALAR_STYLE; + default: THROW_HR(E_UNEXPECTED); + } + } + } + + Document::Document(bool init) : + m_token(true) + { + if (init) + { + // Initialize with no version directive or tags, and implicit start and end. + if (!yaml_document_initialize(&m_document, NULL, NULL, NULL, 1, 1)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); + } + } + else + { + memset(&m_document, 0, sizeof(m_document)); + } + } + + Document::~Document() + { + if (m_token) + { + yaml_document_delete(&m_document); + } + } + + bool Document::HasRoot() + { + return yaml_document_get_root_node(&m_document) != nullptr; + } + + Node Document::GetRoot() + { + yaml_node_t* root = yaml_document_get_root_node(&m_document); + + if (!root) + { + return {}; + } + + Node result(ConvertNodeType(root->type), ConvertYamlString(root->tag, root->start_mark), ConvertMark(root->start_mark)); + + struct StackItem + { + StackItem(yaml_node_t* yn, Node* n) : + yamlNode(yn), node(n) {} + + yaml_node_t* yamlNode = nullptr; + Node* node = nullptr; + size_t childOffset = 0; + }; + + static int YAML_DOCUMENT_NEST_LEVEL_LIMIT = 100; + int nestLevel = 0; + + std::stack resultStack; + resultStack.emplace(root, &result); + + while (!resultStack.empty()) + { + StackItem& stackItem = resultStack.top(); + bool pop = false; + + switch (stackItem.yamlNode->type) + { + case YAML_NO_NODE: + pop = true; + break; + case YAML_SCALAR_NODE: + stackItem.node->SetScalar( + ConvertScalarToString(stackItem.yamlNode, stackItem.yamlNode->start_mark), + stackItem.yamlNode->data.scalar.style == YAML_SINGLE_QUOTED_SCALAR_STYLE || + stackItem.yamlNode->data.scalar.style == YAML_DOUBLE_QUOTED_SCALAR_STYLE); + pop = true; + break; + case YAML_SEQUENCE_NODE: + { + if (stackItem.childOffset == 0) + { + // We've entered the sequence. + nestLevel++; + } + + yaml_node_item_t* child = stackItem.yamlNode->data.sequence.items.start + stackItem.childOffset++; + if (child < stackItem.yamlNode->data.sequence.items.top) + { + yaml_node_t* childYamlNode = GetNode(*child); + Node& childNode = stackItem.node->AddSequenceNode(ConvertNodeType(childYamlNode->type), ConvertYamlString(childYamlNode->tag, childYamlNode->start_mark), ConvertMark(childYamlNode->start_mark)); + resultStack.emplace(childYamlNode, &childNode); + } + else + { + // We've reached the end of the sequence + pop = true; + nestLevel--; + } + break; + } + case YAML_MAPPING_NODE: + { + if (stackItem.childOffset == 0) + { + // We've entered the mapping. + nestLevel++; + } + + yaml_node_pair_t* child = stackItem.yamlNode->data.mapping.pairs.start + stackItem.childOffset++; + if (child < stackItem.yamlNode->data.mapping.pairs.top) + { + yaml_node_t* keyYamlNode = GetNode(child->key); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY, keyYamlNode->type != YAML_SCALAR_NODE); + + Node keyNode(ConvertNodeType(keyYamlNode->type), ConvertYamlString(keyYamlNode->tag, keyYamlNode->start_mark), ConvertMark(keyYamlNode->start_mark)); + keyNode.SetScalar(ConvertScalarToString(keyYamlNode, keyYamlNode->start_mark)); + + yaml_node_t* valueYamlNode = GetNode(child->value); + + Node& childNode = stackItem.node->AddMappingNode(std::move(keyNode), ConvertNodeType(valueYamlNode->type), ConvertYamlString(valueYamlNode->tag, valueYamlNode->start_mark), ConvertMark(valueYamlNode->start_mark)); + resultStack.emplace(valueYamlNode, &childNode); + } + else + { + // We've reached the end of the mapping + pop = true; + nestLevel--; + } + break; + } + } + + if (pop) + { + resultStack.pop(); + } + + THROW_HR_IF_MSG(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, nestLevel > YAML_DOCUMENT_NEST_LEVEL_LIMIT, "Too many layers of nested nodes."); + } + + return result; + } + + int Document::AddScalar(std::string_view value, ScalarStyle style) + { + int result = yaml_document_add_scalar(&m_document, NULL, reinterpret_cast(value.data()), static_cast(value.size()), ConvertStyle(style)); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0); + return result; + } + + int Document::AddSequence() + { + int result = yaml_document_add_sequence(&m_document, NULL, YAML_ANY_SEQUENCE_STYLE); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0); + return result; + } + + int Document::AddMapping() + { + int result = yaml_document_add_mapping(&m_document, NULL, YAML_ANY_MAPPING_STYLE); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED, result == 0); + return result; + } + + void Document::AppendSequenceItem(int sequence, int item) + { + if (!yaml_document_append_sequence_item(&m_document, sequence, item)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); + } + } + + void Document::AppendMappingPair(int mapping, int key, int value) + { + if (!yaml_document_append_mapping_pair(&m_document, mapping, key, value)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED); + } + } + + yaml_node_t* Document::GetNode(yaml_node_item_t index) + { + yaml_node_t* result = yaml_document_get_node(&m_document, index); + THROW_HR_IF(E_BOUNDS, !result); + return result; + } + + Parser::Parser(std::string_view input) : m_token(true), m_input(input) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_parser_initialize(&m_parser)); + + PrepareInput(); + yaml_parser_set_input_string(&m_parser, reinterpret_cast(m_input.c_str()), m_input.size()); + } + + Parser::Parser(std::istream& input, Utility::SHA256::HashBuffer* hashOut) : m_token(true) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_parser_initialize(&m_parser)); + + m_input = Utility::ReadEntireStream(input); + + if (hashOut) + { + *hashOut = Utility::SHA256::ComputeHash(reinterpret_cast(m_input.data()), static_cast(m_input.size())); + } + + PrepareInput(); + yaml_parser_set_input_string(&m_parser, reinterpret_cast(m_input.c_str()), m_input.size()); + } + + Parser::~Parser() + { + if (m_token) + { + yaml_parser_delete(&m_parser); + } + } + + Document Parser::Load() + { + Document result; + + if (!yaml_parser_load(&m_parser, &result)) + { + Exception::Type type = ConvertErrorType(m_parser.error); + + switch (type) + { + case Exception::Type::Memory: + THROW_EXCEPTION(Exception(type)); + case Exception::Type::Reader: + THROW_EXCEPTION(Exception(type, m_parser.problem, m_parser.problem_offset, m_parser.problem_value)); + case Exception::Type::Scanner: + case Exception::Type::Parser: + case Exception::Type::Composer: + THROW_EXCEPTION(Exception(type, m_parser.problem, ConvertMark(m_parser.problem_mark), m_parser.context, ConvertMark(m_parser.context_mark))); + default: + THROW_EXCEPTION(Exception(type, "An unexpected error type occurred in Parser::Load")); + } + } + + return result; + } + + void Parser::PrepareInput() + { + constexpr char c_utf16LEBOM[2] = { static_cast(0xFF), static_cast(0xFE) }; + constexpr char c_utf16BEBOM[2] = { static_cast(0xFE), static_cast(0xFF) }; + constexpr char c_utf8BOM[3] = { static_cast(0xEF), static_cast(0xBB), static_cast(0xBF) }; + + // If input has a BOM, we want to remove it to prevent errors with checking for comments within the input document. + + // Check for UTF-16 BOMs + if (m_input.size() >= sizeof(c_utf16LEBOM) && std::memcmp(m_input.data(), c_utf16LEBOM, sizeof(c_utf16LEBOM)) == 0) + { + AICLI_LOG(YAML, Verbose, << "Found UTF-16 LE BOM"); + yaml_parser_set_encoding(&m_parser, YAML_UTF16LE_ENCODING); // Without the BOM, the encoding must be explicitly set + m_input.erase(0, sizeof(c_utf16LEBOM)); // Remove the BOM from the input + return; + } + + if (m_input.size() >= sizeof(c_utf16BEBOM) && std::memcmp(m_input.data(), c_utf16BEBOM, sizeof(c_utf16BEBOM)) == 0) + { + AICLI_LOG(YAML, Verbose, << "Found UTF-16 BE BOM"); + yaml_parser_set_encoding(&m_parser, YAML_UTF16BE_ENCODING); // Without the BOM, the encoding must be explicitly set + m_input.erase(0, sizeof(c_utf16BEBOM)); // Remove the BOM from the input + return; + } + + // Check for UTF-8 BOM + if (m_input.size() >= sizeof(c_utf8BOM) && std::memcmp(m_input.data(), c_utf8BOM, sizeof(c_utf8BOM)) == 0) + { + AICLI_LOG(YAML, Verbose, << "Found UTF-8 BOM"); + yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING); // Without the BOM, the encoding must be explicitly set + m_input.erase(0, sizeof(c_utf8BOM)); // Remove the BOM from the input + return; + } + + // Check for BOM-less UTF-16 LE + INT expectedTests = IS_TEXT_UNICODE_ASCII16 | IS_TEXT_UNICODE_STATISTICS | IS_TEXT_UNICODE_CONTROLS; + INT testResults = expectedTests; + if (IsTextUnicode(m_input.data(), wil::safe_cast(m_input.size()), &testResults) || testResults == expectedTests) + { + AICLI_LOG(YAML, Verbose, << "Detected UTF-16 LE"); + yaml_parser_set_encoding(&m_parser, YAML_UTF16LE_ENCODING); + return; + } + + // Check for BOM-less UTF-16 BE + expectedTests = IS_TEXT_UNICODE_REVERSE_ASCII16 | IS_TEXT_UNICODE_REVERSE_STATISTICS | IS_TEXT_UNICODE_REVERSE_CONTROLS; + testResults = expectedTests; + if (IsTextUnicode(m_input.data(), wil::safe_cast(m_input.size()), &testResults) || testResults == expectedTests) + { + AICLI_LOG(YAML, Verbose, << "Detected UTF-16 BE"); + yaml_parser_set_encoding(&m_parser, YAML_UTF16BE_ENCODING); + return; + } + + // Check for BOM-less UTF-8 + UINT nChars = MultiByteToWideChar( + CP_UTF8, + MB_ERR_INVALID_CHARS, + m_input.data(), + wil::safe_cast(m_input.size()), + NULL, + 0); + + if (nChars > 0 || GetLastError() != ERROR_NO_UNICODE_TRANSLATION) + { + AICLI_LOG(YAML, Verbose, << "Detected UTF-8"); + yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING); + return; + } + + // Must be ANSI (Windows-1252 assumed), convert to UTF-8 + AICLI_LOG(YAML, Verbose, << "Assuming ANSI Windows-1252"); + std::wstring utf16 = Utility::ConvertToUTF16(m_input, 1252); + m_input = Utility::ConvertToUTF8(utf16); + yaml_parser_set_encoding(&m_parser, YAML_UTF8_ENCODING); + } + + Event::~Event() + { + if (m_token) + { + yaml_event_delete(&m_event); + } + } + + Event Event::StreamStart() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_stream_start_event_initialize(&result, YAML_UTF8_ENCODING)); + result.m_token = true; + return result; + } + + Event Event::StreamEnd() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_stream_end_event_initialize(&result)); + result.m_token = true; + return result; + } + + Event Event::DocumentStart() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_document_start_event_initialize(&result, NULL, NULL, NULL, 1)); + result.m_token = true; + return result; + } + + Event Event::DocumentEnd() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_document_end_event_initialize(&result, 1)); + result.m_token = true; + return result; + } + + Event Event::SequenceStart() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_sequence_start_event_initialize(&result, NULL, NULL, 1, YAML_ANY_SEQUENCE_STYLE)); + result.m_token = true; + return result; + } + + Event Event::SequenceEnd() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_sequence_end_event_initialize(&result)); + result.m_token = true; + return result; + } + + Event Event::MappingStart() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_mapping_start_event_initialize(&result, NULL, NULL, 1, YAML_ANY_MAPPING_STYLE)); + result.m_token = true; + return result; + } + + Event Event::MappingEnd() + { + Event result; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_mapping_end_event_initialize(&result)); + result.m_token = true; + return result; + } + + Emitter::Emitter(std::ostream& output) : + m_token(true), m_outputStream(&output) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED, !yaml_emitter_initialize(&m_emitter)); + yaml_emitter_set_output(&m_emitter, StreamWriteHandler, this); + yaml_emitter_set_encoding(&m_emitter, YAML_UTF8_ENCODING); + } + + Emitter::~Emitter() + { + if (m_token) + { + yaml_emitter_delete(&m_emitter); + } + } + + void Emitter::Emit(Event& event) + { + event.Detach(); + if (!yaml_emitter_emit(&m_emitter, &event)) + { + ThrowError(); + } + } + + void Emitter::Emit(Event&& event) + { + event.Detach(); + if (!yaml_emitter_emit(&m_emitter, &event)) + { + ThrowError(); + } + } + + void Emitter::Dump(Document& document) + { + document.Detach(); + if (!yaml_emitter_dump(&m_emitter, &document)) + { + ThrowError(); + } + } + + void Emitter::Flush() + { + if (!yaml_emitter_flush(&m_emitter)) + { + ThrowError(); + } + } + + int Emitter::StreamWriteHandler( + void* data, + unsigned char* buffer, + size_t size) + { + Emitter& emitter = *reinterpret_cast(data); + + try + { + emitter.m_outputStream->write(reinterpret_cast(buffer), size); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + return 0; + } + + return 1; + } + + void Emitter::ThrowError() + { + Exception::Type type = ConvertErrorType(m_emitter.error); + + switch (type) + { + case Exception::Type::Memory: + THROW_EXCEPTION(Exception(type)); + case Exception::Type::Emitter: + case Exception::Type::Writer: + THROW_EXCEPTION(Exception(type, m_emitter.problem)); + default: + THROW_EXCEPTION(Exception(type, "An unexpected error type occurred in Emitter")); + } + } +} diff --git a/src/AppInstallerSharedLib/YamlWrapper.h b/src/AppInstallerSharedLib/YamlWrapper.h index 6fdc5f9f7d..7accb35179 100644 --- a/src/AppInstallerSharedLib/YamlWrapper.h +++ b/src/AppInstallerSharedLib/YamlWrapper.h @@ -1,171 +1,171 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "winget/Yaml.h" -#include "AppInstallerLanguageUtilities.h" -#include "AppInstallerSHA256.h" - -#include -#include -#include - - -namespace AppInstaller::YAML::Wrapper -{ - // A libyaml yaml_document_t. - // A parsed document, created by the Parser. - struct Document - { - // Initializes the document. - Document(bool init = false); - - Document(const Document&) = delete; - Document& operator=(const Document&) = delete; - - Document(Document&&) noexcept = default; - Document& operator=(Document&&) noexcept = delete; - - ~Document(); - - yaml_document_t* operator&() { return &m_document; } - - // Indicates that the document should not be deleted, as - // it has been handed off to the emitter. - void Detach() { m_token = false; } - - // Determines whether the document has a root node. - bool HasRoot(); - - // Gets the root node of the document, if it has one. - Node GetRoot(); - - // Adds a scalar node to the document. - int AddScalar(std::string_view value, ScalarStyle style = ScalarStyle::Any); - - // Adds a sequence node to the document. - int AddSequence(); - - // Adds a mapping node to the document. - int AddMapping(); - - // Appends a node to the end of the sequence. - void AppendSequenceItem(int sequence, int item); - - // Adds a pair to the mapping. - void AppendMappingPair(int mapping, int key, int value); - - private: - // Gets the node referenced by the index. - yaml_node_t* GetNode(yaml_node_item_t index); - - DestructionToken m_token; - yaml_document_t m_document; - }; - - // A libyaml yaml_parser_t. - // The core parser construct for reading bytes directly. - struct Parser - { - Parser(std::string_view input); - Parser(std::istream& input, Utility::SHA256::HashBuffer* hashOut = nullptr); - - Parser(const Parser&) = delete; - Parser& operator=(const Parser&) = delete; - - Parser(Parser&&) noexcept = default; - Parser& operator=(Parser&&) noexcept = delete; - - ~Parser(); - - yaml_parser_t* operator&() { return &m_parser; } - - // Loads the next document from the input, if one exists. - Document Load(); - - // Retrieves the input that was used to create the parser with the correct encoding scheme. - const std::string& GetEncodedInput() const { return m_input; } - - private: - // Determines the type of encoding in use, transforming the input as necessary. - void PrepareInput(); - - DestructionToken m_token; - yaml_parser_t m_parser; - std::string m_input; - }; - - // A libyaml yaml_event_t. - // The generic event type for. - struct Event - { - Event(const Event&) = delete; - Event& operator=(const Event&) = delete; - - Event(Event&&) noexcept = default; - Event& operator=(Event&&) noexcept = delete; - - ~Event(); - - yaml_event_t* operator&() { return &m_event; } - - // Indicates that the event should not be deleted, as - // it has been handed off to the emitter. - void Detach() { m_token = false; } - - // Event creation functions. - static Event StreamStart(); - static Event StreamEnd(); - static Event DocumentStart(); - static Event DocumentEnd(); - static Event SequenceStart(); - static Event SequenceEnd(); - static Event MappingStart(); - static Event MappingEnd(); - - private: - Event() = default; - - DestructionToken m_token; - yaml_event_t m_event = {}; - }; - - // A libyaml yaml_emitter_t. - // Allows YAML to be written out. - struct Emitter - { - Emitter(std::ostream& output); - - Emitter(const Emitter&) = delete; - Emitter& operator=(const Emitter&) = delete; - - Emitter(Emitter&&) noexcept = default; - Emitter& operator=(Emitter&&) noexcept = delete; - - ~Emitter(); - - yaml_emitter_t* operator&() { return &m_emitter; } - - // Emits and event. - void Emit(Event& event); - void Emit(Event&& event); - - // Dumps a document to the emitter. - void Dump(Document& document); - - // Flushes the emitter. - void Flush(); - - private: - static int StreamWriteHandler( - void* data, - unsigned char* buffer, - size_t size); - - void ThrowError(); - - DestructionToken m_token; - yaml_emitter_t m_emitter; - std::ostream* m_outputStream = nullptr; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "winget/Yaml.h" +#include "AppInstallerLanguageUtilities.h" +#include "AppInstallerSHA256.h" + +#include +#include +#include + + +namespace AppInstaller::YAML::Wrapper +{ + // A libyaml yaml_document_t. + // A parsed document, created by the Parser. + struct Document + { + // Initializes the document. + Document(bool init = false); + + Document(const Document&) = delete; + Document& operator=(const Document&) = delete; + + Document(Document&&) noexcept = default; + Document& operator=(Document&&) noexcept = delete; + + ~Document(); + + yaml_document_t* operator&() { return &m_document; } + + // Indicates that the document should not be deleted, as + // it has been handed off to the emitter. + void Detach() { m_token = false; } + + // Determines whether the document has a root node. + bool HasRoot(); + + // Gets the root node of the document, if it has one. + Node GetRoot(); + + // Adds a scalar node to the document. + int AddScalar(std::string_view value, ScalarStyle style = ScalarStyle::Any); + + // Adds a sequence node to the document. + int AddSequence(); + + // Adds a mapping node to the document. + int AddMapping(); + + // Appends a node to the end of the sequence. + void AppendSequenceItem(int sequence, int item); + + // Adds a pair to the mapping. + void AppendMappingPair(int mapping, int key, int value); + + private: + // Gets the node referenced by the index. + yaml_node_t* GetNode(yaml_node_item_t index); + + DestructionToken m_token; + yaml_document_t m_document; + }; + + // A libyaml yaml_parser_t. + // The core parser construct for reading bytes directly. + struct Parser + { + Parser(std::string_view input); + Parser(std::istream& input, Utility::SHA256::HashBuffer* hashOut = nullptr); + + Parser(const Parser&) = delete; + Parser& operator=(const Parser&) = delete; + + Parser(Parser&&) noexcept = default; + Parser& operator=(Parser&&) noexcept = delete; + + ~Parser(); + + yaml_parser_t* operator&() { return &m_parser; } + + // Loads the next document from the input, if one exists. + Document Load(); + + // Retrieves the input that was used to create the parser with the correct encoding scheme. + const std::string& GetEncodedInput() const { return m_input; } + + private: + // Determines the type of encoding in use, transforming the input as necessary. + void PrepareInput(); + + DestructionToken m_token; + yaml_parser_t m_parser; + std::string m_input; + }; + + // A libyaml yaml_event_t. + // The generic event type for. + struct Event + { + Event(const Event&) = delete; + Event& operator=(const Event&) = delete; + + Event(Event&&) noexcept = default; + Event& operator=(Event&&) noexcept = delete; + + ~Event(); + + yaml_event_t* operator&() { return &m_event; } + + // Indicates that the event should not be deleted, as + // it has been handed off to the emitter. + void Detach() { m_token = false; } + + // Event creation functions. + static Event StreamStart(); + static Event StreamEnd(); + static Event DocumentStart(); + static Event DocumentEnd(); + static Event SequenceStart(); + static Event SequenceEnd(); + static Event MappingStart(); + static Event MappingEnd(); + + private: + Event() = default; + + DestructionToken m_token; + yaml_event_t m_event = {}; + }; + + // A libyaml yaml_emitter_t. + // Allows YAML to be written out. + struct Emitter + { + Emitter(std::ostream& output); + + Emitter(const Emitter&) = delete; + Emitter& operator=(const Emitter&) = delete; + + Emitter(Emitter&&) noexcept = default; + Emitter& operator=(Emitter&&) noexcept = delete; + + ~Emitter(); + + yaml_emitter_t* operator&() { return &m_emitter; } + + // Emits and event. + void Emit(Event& event); + void Emit(Event&& event); + + // Dumps a document to the emitter. + void Dump(Document& document); + + // Flushes the emitter. + void Flush(); + + private: + static int StreamWriteHandler( + void* data, + unsigned char* buffer, + size_t size); + + void ThrowError(); + + DestructionToken m_token; + yaml_emitter_t m_emitter; + std::ostream* m_outputStream = nullptr; + }; +} diff --git a/src/AppInstallerSharedLib/packages.config b/src/AppInstallerSharedLib/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/AppInstallerSharedLib/packages.config +++ b/src/AppInstallerSharedLib/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/AppInstallerSharedLib/pch.h b/src/AppInstallerSharedLib/pch.h index 2a7366d8aa..6a5d304986 100644 --- a/src/AppInstallerSharedLib/pch.h +++ b/src/AppInstallerSharedLib/pch.h @@ -10,8 +10,8 @@ #include #include #include -#include -#include +#include +#include #include #define YAML_DECLARE_STATIC @@ -40,7 +40,7 @@ #include #include #include -#include +#include #include #include #include @@ -60,7 +60,7 @@ #include #pragma warning( pop ) -#include +#include #include #include #include diff --git a/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj b/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj index 2070e1dc45..72fad85337 100644 --- a/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj +++ b/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj @@ -1,137 +1,137 @@ - - - - 15.0 - {6CB84692-5994-407D-B9BD-9216AF77FE83} - Win32Proj - AppInstallerTestExeInstaller - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - - Application - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - - - - - - - - - - - - NotUsing - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /Zi - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - true - true - stdcpp17 - stdcpp17 - MultiThreadedDebugDLL - - - Console - false - - - - - WIN32;%(PreprocessorDefinitions) - true - stdcpp17 - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - true - true - true - stdcpp17 - stdcpp17 - stdcpp17 - - - Console - true - true - false - - - - - - - - - - - + + + + 15.0 + {6CB84692-5994-407D-B9BD-9216AF77FE83} + Win32Proj + AppInstallerTestExeInstaller + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + Application + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + + + + + + + + + + + + NotUsing + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /Zi + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + true + true + stdcpp17 + stdcpp17 + MultiThreadedDebugDLL + + + Console + false + + + + + WIN32;%(PreprocessorDefinitions) + true + stdcpp17 + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + true + true + true + stdcpp17 + stdcpp17 + stdcpp17 + + + Console + true + true + false + + + + + + + + + + + \ No newline at end of file diff --git a/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj.filters b/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj.filters index 128a386645..a91ad0ed99 100644 --- a/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj.filters +++ b/src/AppInstallerTestExeInstaller/AppInstallerTestExeInstaller.vcxproj.filters @@ -1,22 +1,22 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + \ No newline at end of file diff --git a/src/AppInstallerTestExeInstaller/main.cpp b/src/AppInstallerTestExeInstaller/main.cpp index b5b7bc9a11..a016d02c63 100644 --- a/src/AppInstallerTestExeInstaller/main.cpp +++ b/src/AppInstallerTestExeInstaller/main.cpp @@ -1,687 +1,687 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::filesystem; - -std::wstring_view RegistrySubkey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"; -std::wstring_view DefaultProductID = L"{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}"; -std::wstring_view DefaultDisplayName = L"AppInstallerTestExeInstaller"; -std::wstring_view DefaultDisplayVersion = L"1.0.0.0"; -std::wstring_view DscSubDirectoryName = L"SubDirectory"; -auto InstallLocationEnvironmentVariableName = "WINGET_TEST_EXE_INSTALL_LOCATION"; - -void WriteModifyRepairScript(std::wofstream& script, const path& repairCompletedTextFilePath, bool isModifyScript) { - std::wstring scriptName = isModifyScript ? L"Modify" : L"Uninstaller"; - script << L" if /I \"%%A\"==\"/repair\" (\n" - << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully > \"" << repairCompletedTextFilePath.wstring() << "\"\n" - << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully\n" - << L" EXIT /B 0\n" - << L" ) else if /I \"%%A\"==\"/r\" (\n" - << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully > \"" << repairCompletedTextFilePath.wstring() << "\"\n" - << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully\n" - << L" EXIT /B 0\n" - << L" )"; -} - -void WriteModifyUninstallScript(std::wofstream& script) { - script << L" else if /I \"%%A\"==\"/uninstall\" (\n" - << L" call UninstallTestExe.bat\n" - << L" EXIT /B 0\n" - << L" ) else if /I \"%%A\"==\"/X\" (\n" - << L" call UninstallTestExe.bat\n" - << L" EXIT /B 0\n" - << L" )\n"; -} - -void WriteModifyInvalidOperationScript(std::wofstream& script) { - script << L"echo Invalid operation\n" - << L"EXIT /B 1\n"; -} - -void WriteUninstallerScript( - std::wofstream& uninstallerScript, - const path& uninstallerOutputTextFilePath, - const std::wstring& registryKey, - std::initializer_list paths) { - uninstallerScript << "ECHO. >" << uninstallerOutputTextFilePath << "\n"; - uninstallerScript << "ECHO AppInstallerTestExeInstaller.exe uninstalled successfully.\n"; - uninstallerScript << "REG DELETE " << registryKey << " /f\n"; - - for (const auto& path : paths) - { - std::wstring pathString = path.wstring(); - uninstallerScript << "if exist \"" << pathString << "\" del \"" << pathString << "\"\n"; - } -} - -path GenerateUninstaller(std::wostream& out, const path& installDirectory, const std::wstring& productID, bool useHKLM) -{ - path uninstallerPath = installDirectory; - uninstallerPath /= "UninstallTestExe.bat"; - - out << "Uninstaller located at path: " << uninstallerPath << std::endl; - - path uninstallerOutputTextFilePath = installDirectory; - uninstallerOutputTextFilePath /= "TestExeUninstalled.txt"; - - path repairCompletedTextFilePath = installDirectory; - repairCompletedTextFilePath /= "TestExeRepairCompleted.txt"; - - std::wstring registryKey{ useHKLM ? L"HKEY_LOCAL_MACHINE\\" : L"HKEY_CURRENT_USER\\" }; - registryKey += RegistrySubkey; - if (!productID.empty()) - { - registryKey += productID; - } - else - { - registryKey += DefaultProductID; - } - - std::wofstream uninstallerScript(uninstallerPath); - uninstallerScript << "@echo off\n"; - uninstallerScript << L"for %%A in (%*) do (\n"; - WriteModifyRepairScript(uninstallerScript, repairCompletedTextFilePath, false /*isModifyScript*/); - uninstallerScript << ")\n"; - WriteUninstallerScript(uninstallerScript, uninstallerOutputTextFilePath, registryKey, - { - installDirectory / "ModifyTestExe.bat", - repairCompletedTextFilePath, - installDirectory / "AppInstallerTestResource.exe", - installDirectory / "AppInstallerTest.dsc.resource.json", - installDirectory / DscSubDirectoryName / "AppInstallerTestResource.exe", - installDirectory / DscSubDirectoryName / "AppInstallerTest.dsc.resource.json", - }); - - uninstallerScript.close(); - - return uninstallerPath; -} - -path GenerateModifyPath(const path& installDirectory) -{ - path modifyScriptPath = installDirectory; - modifyScriptPath /= "ModifyTestExe.bat"; - - path repairCompletedTextFilePath = installDirectory; - repairCompletedTextFilePath /= "TestExeRepairCompleted.txt"; - - std::wofstream modifyScript(modifyScriptPath); - - modifyScript << L"@echo off\n"; - modifyScript << L"for %%A in (%*) do (\n"; - WriteModifyRepairScript(modifyScript, repairCompletedTextFilePath, true /*isModifyScript*/); - WriteModifyUninstallScript(modifyScript); - modifyScript << L")\n"; - WriteModifyInvalidOperationScript(modifyScript); - - modifyScript.close(); - - return modifyScriptPath; -} - -void GenerateDSCv3ProviderFiles(const path& installDirectory, const std::wstring_view subDirectory) -{ - path dscResourceExecutablePath = installDirectory; - if (!subDirectory.empty()) - { - dscResourceExecutablePath /= subDirectory; - std::filesystem::create_directories(dscResourceExecutablePath); - } - dscResourceExecutablePath /= "AppInstallerTestResource.exe"; - - WCHAR currentExecutable[MAX_PATH]; - GetModuleFileName(nullptr, currentExecutable, MAX_PATH); - path currentExecutablePath{ currentExecutable }; - copy_file(currentExecutablePath, dscResourceExecutablePath); - - path dscResourceManifestPath = installDirectory; - if (!subDirectory.empty()) - { - dscResourceManifestPath /= subDirectory; - } - dscResourceManifestPath /= "AppInstallerTest.dsc.resource.json"; - - std::wstring DscResourceJsonContent = - LR"( - { - "$schema" : "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", - "description" : "AppInstallerTest dsc Resource.", - "export" : - { - "args" : - [ - "/DscExport" - ], - "executable" : "AppInstallerTestResource.exe" - }, - "get" : - { - "args" : - [ - "/DscGet" - ], - "executable" : "AppInstallerTestResource.exe", - "input" : "stdin" - }, - "set" : - { - "args" : - [ - "/DscSet" - ] , - "executable" : "AppInstallerTestResource.exe", - "handlesExist" : true, - "implementsPretest" : true, - "input" : "stdin", - "return" : "state" - }, - "test" : - { - "args" : - [ - "/DscTest" - ] , - "executable" : "AppInstallerTestResource.exe", - "input" : "stdin", - "return" : "state" - }, - "schema": { - "embedded": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AppInstallerTestResource", - "description": "App Installer Test Resource", - "type": "object", - "required": [], - "additionalProperties": false, - "properties": { - "_inDesiredState": { - "description": "Indicates whether an instance is in the desired state.", - "type": "boolean" - }, - "data": { - "type": "string", - "description": "Test data." - } - } - } - }, - "type" : "AppInstallerTest/TestResource)"; - - if (!subDirectory.empty()) - { - DscResourceJsonContent += '_'; - DscResourceJsonContent += subDirectory; - } - - DscResourceJsonContent += LR"(", - "version" : "1.0.0" - } - )"; - - - std::wofstream dscResourceJson(dscResourceManifestPath); - dscResourceJson << DscResourceJsonContent; - dscResourceJson.close(); -} - -void WriteToUninstallRegistry( - std::wostream& out, - const std::wstring& productID, - const path& uninstallerPath, - const path& modifyPath, - const std::wstring& displayName, - const std::wstring& displayVersion, - const std::wstring& installLocation, - bool useHKLM, - bool noRepair, - bool noModify) -{ - HKEY hkey; - LONG lReg; - - // String inputs to registry must be of wide char type - const wchar_t* publisher = L"Microsoft Corporation"; - std::wstring uninstallString = uninstallerPath.wstring(); - std::wstring modifyPathString = modifyPath.wstring(); - - DWORD version = 1; - - std::wstring registryKey{ RegistrySubkey }; - - if (!productID.empty()) - { - registryKey += productID; - out << "Product Code overridden to: " << registryKey << std::endl; - } - else - { - registryKey += DefaultProductID; - out << "Default Product Code used: " << registryKey << std::endl; - } - - lReg = RegCreateKeyEx( - useHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, - registryKey.c_str(), - 0, - NULL, - REG_OPTION_NON_VOLATILE, - KEY_ALL_ACCESS, - NULL, - &hkey, - NULL); - - if (lReg == ERROR_SUCCESS) - { - out << "Successfully opened registry key" << std::endl; - - // Set Display Name Property Value - if (LONG res = RegSetValueEx(hkey, L"DisplayName", NULL, REG_SZ, (LPBYTE)displayName.c_str(), (DWORD)(displayName.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) - { - out << "Failed to write DisplayName value. Error Code: " << res << std::endl; - } - - // Set Display Version Property Value - if (LONG res = RegSetValueEx(hkey, L"DisplayVersion", NULL, REG_SZ, (LPBYTE)displayVersion.c_str(), (DWORD)(displayVersion.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) - { - out << "Failed to write DisplayVersion value. Error Code: " << res << std::endl; - } - - // Set Publisher Property Value - if (LONG res = RegSetValueEx(hkey, L"Publisher", NULL, REG_SZ, (LPBYTE)publisher, (DWORD)(wcslen(publisher) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) - { - out << "Failed to write Publisher value. Error Code: " << res << std::endl; - } - - // Set UninstallString Property Value - if (LONG res = RegSetValueEx(hkey, L"UninstallString", NULL, REG_EXPAND_SZ, (LPBYTE)uninstallString.c_str(), (DWORD)(uninstallString.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) - { - out << "Failed to write UninstallString value. Error Code: " << res << std::endl; - } - - // Set Version Property Value - if (LONG res = RegSetValueEx(hkey, L"Version", NULL, REG_DWORD, (LPBYTE)&version, sizeof(version)) != ERROR_SUCCESS) - { - out << "Failed to write Version value. Error Code: " << res << std::endl; - } - - // Set InstallLocation Property Value - if (LONG res = RegSetValueEx(hkey, L"InstallLocation", NULL, REG_SZ, (LPBYTE)installLocation.c_str(), (DWORD)(installLocation.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) - { - out << "Failed to write InstallLocation value. Error Code: " << res << std::endl; - } - - // Set ModifyPath Property Value - if (LONG res = RegSetValueEx(hkey, L"ModifyPath", NULL, REG_EXPAND_SZ, (LPBYTE)modifyPathString.c_str(), (DWORD)(modifyPathString.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) - { - out << "Failed to write ModifyPath value. Error Code: " << res << std::endl; - } - - if(noRepair) - { - // Set NoRepair Property Value - DWORD noRepairValue = 1; - if (LONG res = RegSetValueEx(hkey, L"NoRepair", NULL, REG_DWORD, (LPBYTE)&noRepairValue, sizeof(noRepairValue)) != ERROR_SUCCESS) - { - out << "Failed to write NoRepair value. Error Code: " << res << std::endl; - } - } - - if(noModify) - { - // Set NoModify Property Value - DWORD noModifyValue = 1; - if (LONG res = RegSetValueEx(hkey, L"NoModify", NULL, REG_DWORD, (LPBYTE)&noModifyValue, sizeof(noModifyValue)) != ERROR_SUCCESS) - { - out << "Failed to write NoModify value. Error Code: " << res << std::endl; - } - } - - out << "Write to registry key completed" << std::endl; - } - else { - out << "Key Creation Failed" << std::endl; - } - - RegCloseKey(hkey); -} - -void WriteToFile(const path& filePath, const std::wstringstream& content) -{ - std::wofstream file(filePath, std::ofstream::out); - file << content.str(); - file.close(); -} - -void HandleRepairOperation(const std::wstring& productID, const std::wstringstream& outContent, bool useHKLM) -{ - path installDirectory; - - // Open the registry key - HKEY hKey; - std::wstring registryPath = std::wstring(RegistrySubkey); - - if (!productID.empty()) - { - registryPath += productID; - } - else - { - registryPath += DefaultProductID; - } - - LONG lReg = RegOpenKeyEx(useHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, registryPath.c_str(), 0, KEY_READ, &hKey); - - if (lReg == ERROR_SUCCESS) - { - // Query the value of the InstallLocation - wchar_t regInstallLocation[MAX_PATH]; - DWORD bufferSize = sizeof(regInstallLocation); - lReg = RegQueryValueEx(hKey, L"InstallLocation", NULL, NULL, (LPBYTE)regInstallLocation, &bufferSize); - - if (lReg == ERROR_SUCCESS) - { - // Convert the InstallLocation to a path - installDirectory = std::wstring(regInstallLocation); - } - - // Close the registry key - RegCloseKey(hKey); - - if(installDirectory.empty()) - { - // We could not find the install location, so we cannot repair - return; - } - } - else - { - // We could not find the uninstall APR registry key, so we cannot repair - return; - } - - path outFilePath = installDirectory; - outFilePath /= "TestExeRepairCompleted.txt"; - WriteToFile(outFilePath, outContent); -} - -void HandleInstallationOperation( - std::wostream& out, - const path& installDirectory, - const std::wstringstream& outContent, - const std::wstring& productCode, - bool useHKLM, - const std::wstring& displayName, - const std::wstring& displayVersion, - bool noRepair, - bool noModify, - bool generateDscResourceFiles) -{ - path outFilePath = installDirectory; - outFilePath /= "TestExeInstalled.txt"; - - std::wofstream file(outFilePath, std::ofstream::out); - file << outContent.str(); - file.close(); - - if (generateDscResourceFiles) - { - GenerateDSCv3ProviderFiles(installDirectory, {}); - GenerateDSCv3ProviderFiles(installDirectory, DscSubDirectoryName); - } - - path uninstallerPath = GenerateUninstaller(out, installDirectory, productCode, useHKLM); - path modifyPath = GenerateModifyPath(installDirectory); - - WriteToUninstallRegistry(out, productCode, uninstallerPath, modifyPath, displayName, displayVersion, installDirectory.wstring(), useHKLM, noRepair, noModify); -} - -// The installer prints all args to an output file and writes to the Uninstall registry key -int wmain(int argc, const wchar_t** argv) -{ - std::optional installDirectory; - std::wstringstream outContent; - std::wstring productCode; - std::wstring displayName; - std::wstring displayVersion; - std::wstring aliasToExecute; - std::wstring aliasArguments; - bool useHKLM = false; - bool noOperation = false; - int exitCode = 0; - bool isRepair = false; - bool noRepair = false; - bool noModify = false; - bool generateDscResourceFiles = false; - - // Output to cout by default, but swap to a file if requested - std::wostream* out = &std::wcout; - std::wofstream logFile; - - for (int i = 1; i < argc; i++) - { - outContent << argv[i] << ' '; - - // Supports custom install path. - if (_wcsicmp(argv[i], L"/InstallDir") == 0) - { - if (++i < argc) - { - installDirectory = argv[i]; - outContent << argv[i] << ' '; - } - } - - // Supports custom exit code - else if (_wcsicmp(argv[i], L"/ExitCode") == 0) - { - if (++i < argc) - { - exitCode = static_cast(std::stoll(argv[i], 0, 0)); - outContent << argv[i] << ' '; - } - } - - // Supports custom product code ID - else if (_wcsicmp(argv[i], L"/ProductID") == 0) - { - if (++i < argc) - { - productCode = argv[i]; - outContent << argv[i] << ' '; - } - } - - // Supports custom DisplayName - else if (_wcsicmp(argv[i], L"/DisplayName") == 0) - { - if (++i < argc) - { - displayName = argv[i]; - outContent << argv[i] << ' '; - } - } - - // Supports custom version - else if (_wcsicmp(argv[i], L"/Version") == 0) - { - if (++i < argc) - { - displayVersion = argv[i]; - outContent << argv[i] << ' '; - } - } - - // Supports log file - else if (_wcsicmp(argv[i], L"/LogFile") == 0) - { - if (++i < argc) - { - logFile = std::wofstream(argv[i], std::wofstream::out | std::wofstream::trunc); - out = &logFile; - outContent << argv[i] << ' '; - } - } - - // Writes to HKLM - else if (_wcsicmp(argv[i], L"/UseHKLM") == 0) - { - useHKLM = true; - } - - // Executes a command alias during installation - else if (_wcsicmp(argv[i], L"/AliasToExecute") == 0) - { - if (++i < argc) - { - aliasToExecute = argv[i]; - outContent << argv[i] << ' '; - } - } - - // Additional arguments to include when executing the command alias during installation - else if (_wcsicmp(argv[i], L"/AliasArguments") == 0) - { - if (++i < argc) - { - aliasArguments = argv[i]; - outContent << argv[i] << ' '; - } - } - - // Supports /repair and /r to emulate repair operation using installer. - else if (_wcsicmp(argv[i], L"/repair") == 0 - || _wcsicmp(argv[i], L"/r") == 0) - { - isRepair = true; - } - - else if (_wcsicmp(argv[i], L"/NoRepair") == 0) - { - noRepair = true; - } - - else if (_wcsicmp(argv[i], L"/NoModify") == 0) - { - noModify = true; - } - - // Returns the success exit code to emulate being invoked by another caller. - else if (_wcsicmp(argv[i], L"/NoOperation") == 0) - { - noOperation = true; - } - - // Also output dsc resource files - else if (_wcsicmp(argv[i], L"/GenerateDscResourceFiles") == 0) - { - generateDscResourceFiles = true; - } - - // Dsc resource get - else if (_wcsicmp(argv[i], L"/DscGet") == 0) - { - std::cout << R"({"data":"TestData"})" << std::endl; - return 0; - } - - // Dsc resource set - else if (_wcsicmp(argv[i], L"/DscSet") == 0) - { - std::cout << R"({"_inDesiredState":true})" << std::endl; - return 0; - } - - // Dsc resource test - else if (_wcsicmp(argv[i], L"/DscTest") == 0) - { - std::cout << R"({"_inDesiredState":true})" << std::endl; - return 0; - } - - // Dsc resource export - else if (_wcsicmp(argv[i], L"/DscExport") == 0) - { - std::cout << R"({"data":"TestData"})" << std::endl; - return 0; - } - } - - if (!installDirectory) - { - char* value = nullptr; - size_t valueLength = 0; - errno_t result = _dupenv_s(&value, &valueLength, InstallLocationEnvironmentVariableName); - if (result == 0 && value) - { - installDirectory = value; - } - else - { - installDirectory = temp_directory_path(); - } - - if (value) - { - free(value); - } - } - - std::filesystem::create_directories(installDirectory.value()); - - if (noOperation) - { - return exitCode; - } - - if (!aliasToExecute.empty()) - { - SHELLEXECUTEINFOW execInfo = { 0 }; - execInfo.cbSize = sizeof(execInfo); - execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - execInfo.lpFile = aliasToExecute.c_str(); - - if (!aliasArguments.empty()) - { - execInfo.lpParameters = aliasArguments.c_str(); - } - execInfo.nShow = SW_SHOW; - - if (!ShellExecuteExW(&execInfo) || !execInfo.hProcess) - { - return -1; - } - } - - if (displayName.empty()) - { - displayName = DefaultDisplayName; - } - - if (displayVersion.empty()) - { - displayVersion = DefaultDisplayVersion; - } - - if (isRepair) - { - outContent << L"\nInstaller Repair operation for AppInstallerTestExeInstaller.exe completed successfully."; - HandleRepairOperation(productCode, outContent, useHKLM); - } - else - { - HandleInstallationOperation(*out, installDirectory.value(), outContent, productCode, useHKLM, displayName, displayVersion, noRepair, noModify, generateDscResourceFiles); - } - - return exitCode; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::filesystem; + +std::wstring_view RegistrySubkey = L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\"; +std::wstring_view DefaultProductID = L"{A499DD5E-8DC5-4AD2-911A-BCD0263295E9}"; +std::wstring_view DefaultDisplayName = L"AppInstallerTestExeInstaller"; +std::wstring_view DefaultDisplayVersion = L"1.0.0.0"; +std::wstring_view DscSubDirectoryName = L"SubDirectory"; +auto InstallLocationEnvironmentVariableName = "WINGET_TEST_EXE_INSTALL_LOCATION"; + +void WriteModifyRepairScript(std::wofstream& script, const path& repairCompletedTextFilePath, bool isModifyScript) { + std::wstring scriptName = isModifyScript ? L"Modify" : L"Uninstaller"; + script << L" if /I \"%%A\"==\"/repair\" (\n" + << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully > \"" << repairCompletedTextFilePath.wstring() << "\"\n" + << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully\n" + << L" EXIT /B 0\n" + << L" ) else if /I \"%%A\"==\"/r\" (\n" + << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully > \"" << repairCompletedTextFilePath.wstring() << "\"\n" + << L" ECHO " << scriptName << L" Repair operation for AppInstallerTestExeInstaller.exe completed successfully\n" + << L" EXIT /B 0\n" + << L" )"; +} + +void WriteModifyUninstallScript(std::wofstream& script) { + script << L" else if /I \"%%A\"==\"/uninstall\" (\n" + << L" call UninstallTestExe.bat\n" + << L" EXIT /B 0\n" + << L" ) else if /I \"%%A\"==\"/X\" (\n" + << L" call UninstallTestExe.bat\n" + << L" EXIT /B 0\n" + << L" )\n"; +} + +void WriteModifyInvalidOperationScript(std::wofstream& script) { + script << L"echo Invalid operation\n" + << L"EXIT /B 1\n"; +} + +void WriteUninstallerScript( + std::wofstream& uninstallerScript, + const path& uninstallerOutputTextFilePath, + const std::wstring& registryKey, + std::initializer_list paths) { + uninstallerScript << "ECHO. >" << uninstallerOutputTextFilePath << "\n"; + uninstallerScript << "ECHO AppInstallerTestExeInstaller.exe uninstalled successfully.\n"; + uninstallerScript << "REG DELETE " << registryKey << " /f\n"; + + for (const auto& path : paths) + { + std::wstring pathString = path.wstring(); + uninstallerScript << "if exist \"" << pathString << "\" del \"" << pathString << "\"\n"; + } +} + +path GenerateUninstaller(std::wostream& out, const path& installDirectory, const std::wstring& productID, bool useHKLM) +{ + path uninstallerPath = installDirectory; + uninstallerPath /= "UninstallTestExe.bat"; + + out << "Uninstaller located at path: " << uninstallerPath << std::endl; + + path uninstallerOutputTextFilePath = installDirectory; + uninstallerOutputTextFilePath /= "TestExeUninstalled.txt"; + + path repairCompletedTextFilePath = installDirectory; + repairCompletedTextFilePath /= "TestExeRepairCompleted.txt"; + + std::wstring registryKey{ useHKLM ? L"HKEY_LOCAL_MACHINE\\" : L"HKEY_CURRENT_USER\\" }; + registryKey += RegistrySubkey; + if (!productID.empty()) + { + registryKey += productID; + } + else + { + registryKey += DefaultProductID; + } + + std::wofstream uninstallerScript(uninstallerPath); + uninstallerScript << "@echo off\n"; + uninstallerScript << L"for %%A in (%*) do (\n"; + WriteModifyRepairScript(uninstallerScript, repairCompletedTextFilePath, false /*isModifyScript*/); + uninstallerScript << ")\n"; + WriteUninstallerScript(uninstallerScript, uninstallerOutputTextFilePath, registryKey, + { + installDirectory / "ModifyTestExe.bat", + repairCompletedTextFilePath, + installDirectory / "AppInstallerTestResource.exe", + installDirectory / "AppInstallerTest.dsc.resource.json", + installDirectory / DscSubDirectoryName / "AppInstallerTestResource.exe", + installDirectory / DscSubDirectoryName / "AppInstallerTest.dsc.resource.json", + }); + + uninstallerScript.close(); + + return uninstallerPath; +} + +path GenerateModifyPath(const path& installDirectory) +{ + path modifyScriptPath = installDirectory; + modifyScriptPath /= "ModifyTestExe.bat"; + + path repairCompletedTextFilePath = installDirectory; + repairCompletedTextFilePath /= "TestExeRepairCompleted.txt"; + + std::wofstream modifyScript(modifyScriptPath); + + modifyScript << L"@echo off\n"; + modifyScript << L"for %%A in (%*) do (\n"; + WriteModifyRepairScript(modifyScript, repairCompletedTextFilePath, true /*isModifyScript*/); + WriteModifyUninstallScript(modifyScript); + modifyScript << L")\n"; + WriteModifyInvalidOperationScript(modifyScript); + + modifyScript.close(); + + return modifyScriptPath; +} + +void GenerateDSCv3ProviderFiles(const path& installDirectory, const std::wstring_view subDirectory) +{ + path dscResourceExecutablePath = installDirectory; + if (!subDirectory.empty()) + { + dscResourceExecutablePath /= subDirectory; + std::filesystem::create_directories(dscResourceExecutablePath); + } + dscResourceExecutablePath /= "AppInstallerTestResource.exe"; + + WCHAR currentExecutable[MAX_PATH]; + GetModuleFileName(nullptr, currentExecutable, MAX_PATH); + path currentExecutablePath{ currentExecutable }; + copy_file(currentExecutablePath, dscResourceExecutablePath); + + path dscResourceManifestPath = installDirectory; + if (!subDirectory.empty()) + { + dscResourceManifestPath /= subDirectory; + } + dscResourceManifestPath /= "AppInstallerTest.dsc.resource.json"; + + std::wstring DscResourceJsonContent = + LR"( + { + "$schema" : "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2024/04/bundled/resource/manifest.json", + "description" : "AppInstallerTest dsc Resource.", + "export" : + { + "args" : + [ + "/DscExport" + ], + "executable" : "AppInstallerTestResource.exe" + }, + "get" : + { + "args" : + [ + "/DscGet" + ], + "executable" : "AppInstallerTestResource.exe", + "input" : "stdin" + }, + "set" : + { + "args" : + [ + "/DscSet" + ] , + "executable" : "AppInstallerTestResource.exe", + "handlesExist" : true, + "implementsPretest" : true, + "input" : "stdin", + "return" : "state" + }, + "test" : + { + "args" : + [ + "/DscTest" + ] , + "executable" : "AppInstallerTestResource.exe", + "input" : "stdin", + "return" : "state" + }, + "schema": { + "embedded": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "AppInstallerTestResource", + "description": "App Installer Test Resource", + "type": "object", + "required": [], + "additionalProperties": false, + "properties": { + "_inDesiredState": { + "description": "Indicates whether an instance is in the desired state.", + "type": "boolean" + }, + "data": { + "type": "string", + "description": "Test data." + } + } + } + }, + "type" : "AppInstallerTest/TestResource)"; + + if (!subDirectory.empty()) + { + DscResourceJsonContent += '_'; + DscResourceJsonContent += subDirectory; + } + + DscResourceJsonContent += LR"(", + "version" : "1.0.0" + } + )"; + + + std::wofstream dscResourceJson(dscResourceManifestPath); + dscResourceJson << DscResourceJsonContent; + dscResourceJson.close(); +} + +void WriteToUninstallRegistry( + std::wostream& out, + const std::wstring& productID, + const path& uninstallerPath, + const path& modifyPath, + const std::wstring& displayName, + const std::wstring& displayVersion, + const std::wstring& installLocation, + bool useHKLM, + bool noRepair, + bool noModify) +{ + HKEY hkey; + LONG lReg; + + // String inputs to registry must be of wide char type + const wchar_t* publisher = L"Microsoft Corporation"; + std::wstring uninstallString = uninstallerPath.wstring(); + std::wstring modifyPathString = modifyPath.wstring(); + + DWORD version = 1; + + std::wstring registryKey{ RegistrySubkey }; + + if (!productID.empty()) + { + registryKey += productID; + out << "Product Code overridden to: " << registryKey << std::endl; + } + else + { + registryKey += DefaultProductID; + out << "Default Product Code used: " << registryKey << std::endl; + } + + lReg = RegCreateKeyEx( + useHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, + registryKey.c_str(), + 0, + NULL, + REG_OPTION_NON_VOLATILE, + KEY_ALL_ACCESS, + NULL, + &hkey, + NULL); + + if (lReg == ERROR_SUCCESS) + { + out << "Successfully opened registry key" << std::endl; + + // Set Display Name Property Value + if (LONG res = RegSetValueEx(hkey, L"DisplayName", NULL, REG_SZ, (LPBYTE)displayName.c_str(), (DWORD)(displayName.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) + { + out << "Failed to write DisplayName value. Error Code: " << res << std::endl; + } + + // Set Display Version Property Value + if (LONG res = RegSetValueEx(hkey, L"DisplayVersion", NULL, REG_SZ, (LPBYTE)displayVersion.c_str(), (DWORD)(displayVersion.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) + { + out << "Failed to write DisplayVersion value. Error Code: " << res << std::endl; + } + + // Set Publisher Property Value + if (LONG res = RegSetValueEx(hkey, L"Publisher", NULL, REG_SZ, (LPBYTE)publisher, (DWORD)(wcslen(publisher) + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) + { + out << "Failed to write Publisher value. Error Code: " << res << std::endl; + } + + // Set UninstallString Property Value + if (LONG res = RegSetValueEx(hkey, L"UninstallString", NULL, REG_EXPAND_SZ, (LPBYTE)uninstallString.c_str(), (DWORD)(uninstallString.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) + { + out << "Failed to write UninstallString value. Error Code: " << res << std::endl; + } + + // Set Version Property Value + if (LONG res = RegSetValueEx(hkey, L"Version", NULL, REG_DWORD, (LPBYTE)&version, sizeof(version)) != ERROR_SUCCESS) + { + out << "Failed to write Version value. Error Code: " << res << std::endl; + } + + // Set InstallLocation Property Value + if (LONG res = RegSetValueEx(hkey, L"InstallLocation", NULL, REG_SZ, (LPBYTE)installLocation.c_str(), (DWORD)(installLocation.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) + { + out << "Failed to write InstallLocation value. Error Code: " << res << std::endl; + } + + // Set ModifyPath Property Value + if (LONG res = RegSetValueEx(hkey, L"ModifyPath", NULL, REG_EXPAND_SZ, (LPBYTE)modifyPathString.c_str(), (DWORD)(modifyPathString.length() + 1) * sizeof(wchar_t)) != ERROR_SUCCESS) + { + out << "Failed to write ModifyPath value. Error Code: " << res << std::endl; + } + + if(noRepair) + { + // Set NoRepair Property Value + DWORD noRepairValue = 1; + if (LONG res = RegSetValueEx(hkey, L"NoRepair", NULL, REG_DWORD, (LPBYTE)&noRepairValue, sizeof(noRepairValue)) != ERROR_SUCCESS) + { + out << "Failed to write NoRepair value. Error Code: " << res << std::endl; + } + } + + if(noModify) + { + // Set NoModify Property Value + DWORD noModifyValue = 1; + if (LONG res = RegSetValueEx(hkey, L"NoModify", NULL, REG_DWORD, (LPBYTE)&noModifyValue, sizeof(noModifyValue)) != ERROR_SUCCESS) + { + out << "Failed to write NoModify value. Error Code: " << res << std::endl; + } + } + + out << "Write to registry key completed" << std::endl; + } + else { + out << "Key Creation Failed" << std::endl; + } + + RegCloseKey(hkey); +} + +void WriteToFile(const path& filePath, const std::wstringstream& content) +{ + std::wofstream file(filePath, std::ofstream::out); + file << content.str(); + file.close(); +} + +void HandleRepairOperation(const std::wstring& productID, const std::wstringstream& outContent, bool useHKLM) +{ + path installDirectory; + + // Open the registry key + HKEY hKey; + std::wstring registryPath = std::wstring(RegistrySubkey); + + if (!productID.empty()) + { + registryPath += productID; + } + else + { + registryPath += DefaultProductID; + } + + LONG lReg = RegOpenKeyEx(useHKLM ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER, registryPath.c_str(), 0, KEY_READ, &hKey); + + if (lReg == ERROR_SUCCESS) + { + // Query the value of the InstallLocation + wchar_t regInstallLocation[MAX_PATH]; + DWORD bufferSize = sizeof(regInstallLocation); + lReg = RegQueryValueEx(hKey, L"InstallLocation", NULL, NULL, (LPBYTE)regInstallLocation, &bufferSize); + + if (lReg == ERROR_SUCCESS) + { + // Convert the InstallLocation to a path + installDirectory = std::wstring(regInstallLocation); + } + + // Close the registry key + RegCloseKey(hKey); + + if(installDirectory.empty()) + { + // We could not find the install location, so we cannot repair + return; + } + } + else + { + // We could not find the uninstall APR registry key, so we cannot repair + return; + } + + path outFilePath = installDirectory; + outFilePath /= "TestExeRepairCompleted.txt"; + WriteToFile(outFilePath, outContent); +} + +void HandleInstallationOperation( + std::wostream& out, + const path& installDirectory, + const std::wstringstream& outContent, + const std::wstring& productCode, + bool useHKLM, + const std::wstring& displayName, + const std::wstring& displayVersion, + bool noRepair, + bool noModify, + bool generateDscResourceFiles) +{ + path outFilePath = installDirectory; + outFilePath /= "TestExeInstalled.txt"; + + std::wofstream file(outFilePath, std::ofstream::out); + file << outContent.str(); + file.close(); + + if (generateDscResourceFiles) + { + GenerateDSCv3ProviderFiles(installDirectory, {}); + GenerateDSCv3ProviderFiles(installDirectory, DscSubDirectoryName); + } + + path uninstallerPath = GenerateUninstaller(out, installDirectory, productCode, useHKLM); + path modifyPath = GenerateModifyPath(installDirectory); + + WriteToUninstallRegistry(out, productCode, uninstallerPath, modifyPath, displayName, displayVersion, installDirectory.wstring(), useHKLM, noRepair, noModify); +} + +// The installer prints all args to an output file and writes to the Uninstall registry key +int wmain(int argc, const wchar_t** argv) +{ + std::optional installDirectory; + std::wstringstream outContent; + std::wstring productCode; + std::wstring displayName; + std::wstring displayVersion; + std::wstring aliasToExecute; + std::wstring aliasArguments; + bool useHKLM = false; + bool noOperation = false; + int exitCode = 0; + bool isRepair = false; + bool noRepair = false; + bool noModify = false; + bool generateDscResourceFiles = false; + + // Output to cout by default, but swap to a file if requested + std::wostream* out = &std::wcout; + std::wofstream logFile; + + for (int i = 1; i < argc; i++) + { + outContent << argv[i] << ' '; + + // Supports custom install path. + if (_wcsicmp(argv[i], L"/InstallDir") == 0) + { + if (++i < argc) + { + installDirectory = argv[i]; + outContent << argv[i] << ' '; + } + } + + // Supports custom exit code + else if (_wcsicmp(argv[i], L"/ExitCode") == 0) + { + if (++i < argc) + { + exitCode = static_cast(std::stoll(argv[i], 0, 0)); + outContent << argv[i] << ' '; + } + } + + // Supports custom product code ID + else if (_wcsicmp(argv[i], L"/ProductID") == 0) + { + if (++i < argc) + { + productCode = argv[i]; + outContent << argv[i] << ' '; + } + } + + // Supports custom DisplayName + else if (_wcsicmp(argv[i], L"/DisplayName") == 0) + { + if (++i < argc) + { + displayName = argv[i]; + outContent << argv[i] << ' '; + } + } + + // Supports custom version + else if (_wcsicmp(argv[i], L"/Version") == 0) + { + if (++i < argc) + { + displayVersion = argv[i]; + outContent << argv[i] << ' '; + } + } + + // Supports log file + else if (_wcsicmp(argv[i], L"/LogFile") == 0) + { + if (++i < argc) + { + logFile = std::wofstream(argv[i], std::wofstream::out | std::wofstream::trunc); + out = &logFile; + outContent << argv[i] << ' '; + } + } + + // Writes to HKLM + else if (_wcsicmp(argv[i], L"/UseHKLM") == 0) + { + useHKLM = true; + } + + // Executes a command alias during installation + else if (_wcsicmp(argv[i], L"/AliasToExecute") == 0) + { + if (++i < argc) + { + aliasToExecute = argv[i]; + outContent << argv[i] << ' '; + } + } + + // Additional arguments to include when executing the command alias during installation + else if (_wcsicmp(argv[i], L"/AliasArguments") == 0) + { + if (++i < argc) + { + aliasArguments = argv[i]; + outContent << argv[i] << ' '; + } + } + + // Supports /repair and /r to emulate repair operation using installer. + else if (_wcsicmp(argv[i], L"/repair") == 0 + || _wcsicmp(argv[i], L"/r") == 0) + { + isRepair = true; + } + + else if (_wcsicmp(argv[i], L"/NoRepair") == 0) + { + noRepair = true; + } + + else if (_wcsicmp(argv[i], L"/NoModify") == 0) + { + noModify = true; + } + + // Returns the success exit code to emulate being invoked by another caller. + else if (_wcsicmp(argv[i], L"/NoOperation") == 0) + { + noOperation = true; + } + + // Also output dsc resource files + else if (_wcsicmp(argv[i], L"/GenerateDscResourceFiles") == 0) + { + generateDscResourceFiles = true; + } + + // Dsc resource get + else if (_wcsicmp(argv[i], L"/DscGet") == 0) + { + std::cout << R"({"data":"TestData"})" << std::endl; + return 0; + } + + // Dsc resource set + else if (_wcsicmp(argv[i], L"/DscSet") == 0) + { + std::cout << R"({"_inDesiredState":true})" << std::endl; + return 0; + } + + // Dsc resource test + else if (_wcsicmp(argv[i], L"/DscTest") == 0) + { + std::cout << R"({"_inDesiredState":true})" << std::endl; + return 0; + } + + // Dsc resource export + else if (_wcsicmp(argv[i], L"/DscExport") == 0) + { + std::cout << R"({"data":"TestData"})" << std::endl; + return 0; + } + } + + if (!installDirectory) + { + char* value = nullptr; + size_t valueLength = 0; + errno_t result = _dupenv_s(&value, &valueLength, InstallLocationEnvironmentVariableName); + if (result == 0 && value) + { + installDirectory = value; + } + else + { + installDirectory = temp_directory_path(); + } + + if (value) + { + free(value); + } + } + + std::filesystem::create_directories(installDirectory.value()); + + if (noOperation) + { + return exitCode; + } + + if (!aliasToExecute.empty()) + { + SHELLEXECUTEINFOW execInfo = { 0 }; + execInfo.cbSize = sizeof(execInfo); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + execInfo.lpFile = aliasToExecute.c_str(); + + if (!aliasArguments.empty()) + { + execInfo.lpParameters = aliasArguments.c_str(); + } + execInfo.nShow = SW_SHOW; + + if (!ShellExecuteExW(&execInfo) || !execInfo.hProcess) + { + return -1; + } + } + + if (displayName.empty()) + { + displayName = DefaultDisplayName; + } + + if (displayVersion.empty()) + { + displayVersion = DefaultDisplayVersion; + } + + if (isRepair) + { + outContent << L"\nInstaller Repair operation for AppInstallerTestExeInstaller.exe completed successfully."; + HandleRepairOperation(productCode, outContent, useHKLM); + } + else + { + HandleInstallationOperation(*out, installDirectory.value(), outContent, productCode, useHKLM, displayName, displayVersion, noRepair, noModify, generateDscResourceFiles); + } + + return exitCode; +} diff --git a/src/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.vdproj b/src/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.vdproj index 79d046860e..8c3f9b46d9 100644 --- a/src/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.vdproj +++ b/src/AppInstallerTestMsiInstaller/AppInstallerTestMsiInstaller.vdproj @@ -1,709 +1,709 @@ -"DeployProject" -{ -"VSVersion" = "3:800" -"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}" -"IsWebType" = "8:FALSE" -"ProjectName" = "8:AppInstallerTestMsiInstaller" -"LanguageId" = "3:1033" -"CodePage" = "3:1252" -"UILanguageId" = "3:1033" -"SccProjectName" = "8:" -"SccLocalPath" = "8:" -"SccAuxPath" = "8:" -"SccProvider" = "8:" - "Hierarchy" - { - } - "Configurations" - { - "Debug" - { - "DisplayName" = "8:Debug" - "IsDebugOnly" = "11:TRUE" - "IsReleaseOnly" = "11:FALSE" - "OutputFilename" = "8:Debug\\AppInstallerTestMsiInstaller.msi" - "PackageFilesAs" = "3:2" - "PackageFileSize" = "3:-2147483648" - "CabType" = "3:1" - "Compression" = "3:2" - "SignOutput" = "11:FALSE" - "CertificateFile" = "8:" - "PrivateKeyFile" = "8:" - "TimeStampServer" = "8:" - "InstallerBootstrapper" = "3:2" - "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" - { - "Enabled" = "11:TRUE" - "PromptEnabled" = "11:TRUE" - "PrerequisitesLocation" = "2:1" - "Url" = "8:" - "ComponentsUrl" = "8:" - "Items" - { - "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2" - { - "Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)" - "ProductCode" = "8:.NETFramework,Version=v4.7.2" - } - } - } - } - "Release" - { - "DisplayName" = "8:Release" - "IsDebugOnly" = "11:FALSE" - "IsReleaseOnly" = "11:TRUE" - "OutputFilename" = "8:Release\\AppInstallerTestMsiInstaller.msi" - "PackageFilesAs" = "3:2" - "PackageFileSize" = "3:-2147483648" - "CabType" = "3:1" - "Compression" = "3:2" - "SignOutput" = "11:FALSE" - "CertificateFile" = "8:" - "PrivateKeyFile" = "8:" - "TimeStampServer" = "8:" - "InstallerBootstrapper" = "3:2" - "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" - { - "Enabled" = "11:TRUE" - "PromptEnabled" = "11:TRUE" - "PrerequisitesLocation" = "2:1" - "Url" = "8:" - "ComponentsUrl" = "8:" - "Items" - { - "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2" - { - "Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)" - "ProductCode" = "8:.NETFramework,Version=v4.7.2" - } - } - } - } - } - "Deployable" - { - "CustomAction" - { - } - "DefaultFeature" - { - "Name" = "8:DefaultFeature" - "Title" = "8:" - "Description" = "8:" - } - "ExternalPersistence" - { - "LaunchCondition" - { - } - } - "File" - { - } - "FileType" - { - } - "Folder" - { - "{3C67513D-01DD-4637-8A68-80971EB9504F}:_3FD954FA64934D1C9D4F66157C4603AF" - { - "DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]" - "Name" = "8:#1925" - "AlwaysCreate" = "11:FALSE" - "Condition" = "8:" - "Transitive" = "11:FALSE" - "Property" = "8:TARGETDIR" - "Folders" - { - } - } - "{1525181F-901A-416C-8A58-119130FE478E}:_7CE3351BC05D486393C4720064B75896" - { - "Name" = "8:#1916" - "AlwaysCreate" = "11:FALSE" - "Condition" = "8:" - "Transitive" = "11:FALSE" - "Property" = "8:DesktopFolder" - "Folders" - { - } - } - "{1525181F-901A-416C-8A58-119130FE478E}:_9537533CAD3E4467B320694E22A39C4C" - { - "Name" = "8:#1919" - "AlwaysCreate" = "11:FALSE" - "Condition" = "8:" - "Transitive" = "11:FALSE" - "Property" = "8:ProgramMenuFolder" - "Folders" - { - } - } - } - "LaunchCondition" - { - } - "Locator" - { - } - "MsiBootstrapper" - { - "LangId" = "3:1033" - "RequiresElevation" = "11:FALSE" - } - "Product" - { - "Name" = "8:Microsoft Visual Studio" - "ProductName" = "8:AppInstallerTestMsiInstaller" - "ProductCode" = "8:{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}" - "PackageCode" = "8:{B21B22D8-5E23-401C-84FA-EE074DB23F8F}" - "UpgradeCode" = "8:{B9CF9DD5-D46F-4CE0-BFC9-633BF9D3A6F4}" - "AspNetVersion" = "8:4.0.30319.0" - "RestartWWWService" = "11:FALSE" - "RemovePreviousVersions" = "11:FALSE" - "DetectNewerInstalledVersion" = "11:TRUE" - "InstallAllUsers" = "11:FALSE" - "ProductVersion" = "8:1.0.0" - "Manufacturer" = "8:AppInstallerCLI" - "ARPHELPTELEPHONE" = "8:" - "ARPHELPLINK" = "8:" - "Title" = "8:AppInstallerTestMsiInstaller" - "Subject" = "8:" - "ARPCONTACT" = "8:AppInstallerCLI" - "Keywords" = "8:" - "ARPCOMMENTS" = "8:" - "ARPURLINFOABOUT" = "8:" - "ARPPRODUCTICON" = "8:" - "ARPIconIndex" = "3:0" - "SearchPath" = "8:" - "UseSystemSearchPath" = "11:TRUE" - "TargetPlatform" = "3:0" - "PreBuildEvent" = "8:" - "PostBuildEvent" = "8:" - "RunPostBuildEvent" = "3:0" - } - "Registry" - { - "HKLM" - { - "Keys" - { - "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_951AD5F7C8424CCAB911C2469F382BA0" - { - "Name" = "8:Software" - "Condition" = "8:" - "AlwaysCreate" = "11:FALSE" - "DeleteAtUninstall" = "11:FALSE" - "Transitive" = "11:FALSE" - "Keys" - { - "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_1245A15ED08E4D31AC11FE6C6AF43AA6" - { - "Name" = "8:[Manufacturer]" - "Condition" = "8:" - "AlwaysCreate" = "11:FALSE" - "DeleteAtUninstall" = "11:FALSE" - "Transitive" = "11:FALSE" - "Keys" - { - } - "Values" - { - } - } - } - "Values" - { - } - } - } - } - "HKCU" - { - "Keys" - { - "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_A0E8E016B459429AB33F8A8041B409CF" - { - "Name" = "8:Software" - "Condition" = "8:" - "AlwaysCreate" = "11:FALSE" - "DeleteAtUninstall" = "11:FALSE" - "Transitive" = "11:FALSE" - "Keys" - { - "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_6F792619739B49D49348459164023C26" - { - "Name" = "8:[Manufacturer]" - "Condition" = "8:" - "AlwaysCreate" = "11:FALSE" - "DeleteAtUninstall" = "11:FALSE" - "Transitive" = "11:FALSE" - "Keys" - { - } - "Values" - { - } - } - } - "Values" - { - } - } - } - } - "HKCR" - { - "Keys" - { - } - } - "HKU" - { - "Keys" - { - } - } - "HKPU" - { - "Keys" - { - } - } - } - "Sequences" - { - } - "Shortcut" - { - } - "UserInterface" - { - "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_07F5BD380D034D2788D0E26EC943AF9E" - { - "Name" = "8:#1900" - "Sequence" = "3:2" - "Attributes" = "3:1" - "Dialogs" - { - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_0F2C662EB54446EEBB450AB41783B804" - { - "Sequence" = "3:300" - "DisplayName" = "8:Confirm Installation" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdAdminConfirmDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - } - } - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_5B669F9D2835440A97450ED9A579EBB0" - { - "Sequence" = "3:100" - "DisplayName" = "8:Welcome" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdAdminWelcomeDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - "CopyrightWarning" - { - "Name" = "8:CopyrightWarning" - "DisplayName" = "8:#1002" - "Description" = "8:#1102" - "Type" = "3:3" - "ContextData" = "8:" - "Attributes" = "3:0" - "Setting" = "3:1" - "Value" = "8:#1202" - "DefaultValue" = "8:#1202" - "UsePlugInResources" = "11:TRUE" - } - "Welcome" - { - "Name" = "8:Welcome" - "DisplayName" = "8:#1003" - "Description" = "8:#1103" - "Type" = "3:3" - "ContextData" = "8:" - "Attributes" = "3:0" - "Setting" = "3:1" - "Value" = "8:#1203" - "DefaultValue" = "8:#1203" - "UsePlugInResources" = "11:TRUE" - } - } - } - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_A1B6CF1DE99744E9BA298D9516F4FBBB" - { - "Sequence" = "3:200" - "DisplayName" = "8:Installation Folder" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdAdminFolderDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - } - } - } - } - "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_31DFC87DB7EB49BBBE91080D4E17EBC3" - { - "Name" = "8:#1901" - "Sequence" = "3:2" - "Attributes" = "3:2" - "Dialogs" - { - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_B0FC86DC72004463B28043005E93933D" - { - "Sequence" = "3:100" - "DisplayName" = "8:Progress" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdAdminProgressDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - "ShowProgress" - { - "Name" = "8:ShowProgress" - "DisplayName" = "8:#1009" - "Description" = "8:#1109" - "Type" = "3:5" - "ContextData" = "8:1;True=1;False=0" - "Attributes" = "3:0" - "Setting" = "3:0" - "Value" = "3:1" - "DefaultValue" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - } - } - } - } - "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_49F7C7303D464C18981485552AA18870" - { - "Name" = "8:#1901" - "Sequence" = "3:1" - "Attributes" = "3:2" - "Dialogs" - { - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_78EF5C34DBDB4BC88F2789CBE1F17043" - { - "Sequence" = "3:100" - "DisplayName" = "8:Progress" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdProgressDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - "ShowProgress" - { - "Name" = "8:ShowProgress" - "DisplayName" = "8:#1009" - "Description" = "8:#1109" - "Type" = "3:5" - "ContextData" = "8:1;True=1;False=0" - "Attributes" = "3:0" - "Setting" = "3:0" - "Value" = "3:1" - "DefaultValue" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - } - } - } - } - "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_68474A3513F54E56B4D451594D7639DC" - { - "Name" = "8:#1902" - "Sequence" = "3:2" - "Attributes" = "3:3" - "Dialogs" - { - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_3EC2CCC643CA4EEB9DAFF092520273EE" - { - "Sequence" = "3:100" - "DisplayName" = "8:Finished" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdAdminFinishedDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - } - } - } - } - "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_81A3A282324C416DB8EA6A5FABEAD776" - { - "UseDynamicProperties" = "11:FALSE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdUserInterface.wim" - } - "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_869386ADC36F4DAE92F694770A481917" - { - "UseDynamicProperties" = "11:FALSE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdBasicDialogs.wim" - } - "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_9E39D7A2A2BB4185AE8290AF159CBDE8" - { - "Name" = "8:#1902" - "Sequence" = "3:1" - "Attributes" = "3:3" - "Dialogs" - { - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_18EEDC13BA92450EA46E5CFAD31164FE" - { - "Sequence" = "3:100" - "DisplayName" = "8:Finished" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdFinishedDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - "UpdateText" - { - "Name" = "8:UpdateText" - "DisplayName" = "8:#1058" - "Description" = "8:#1158" - "Type" = "3:15" - "ContextData" = "8:" - "Attributes" = "3:0" - "Setting" = "3:1" - "Value" = "8:#1258" - "DefaultValue" = "8:#1258" - "UsePlugInResources" = "11:TRUE" - } - } - } - } - } - "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_A1D7AFF21DC048769B063F0ABB6BAD93" - { - "Name" = "8:#1900" - "Sequence" = "3:1" - "Attributes" = "3:1" - "Dialogs" - { - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_6AFCB2A470844F02AC97138D2C4255B5" - { - "Sequence" = "3:300" - "DisplayName" = "8:Confirm Installation" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdConfirmDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - } - } - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_73B000DF31AF44D789DC52D2EB3AC63B" - { - "Sequence" = "3:200" - "DisplayName" = "8:Installation Folder" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdFolderDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - "InstallAllUsersVisible" - { - "Name" = "8:InstallAllUsersVisible" - "DisplayName" = "8:#1059" - "Description" = "8:#1159" - "Type" = "3:5" - "ContextData" = "8:1;True=1;False=0" - "Attributes" = "3:0" - "Setting" = "3:0" - "Value" = "3:1" - "DefaultValue" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - } - } - "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_E411887483814552AF4BF33BBB4F1B1D" - { - "Sequence" = "3:100" - "DisplayName" = "8:Welcome" - "UseDynamicProperties" = "11:TRUE" - "IsDependency" = "11:FALSE" - "SourcePath" = "8:\\VsdWelcomeDlg.wid" - "Properties" - { - "BannerBitmap" - { - "Name" = "8:BannerBitmap" - "DisplayName" = "8:#1001" - "Description" = "8:#1101" - "Type" = "3:8" - "ContextData" = "8:Bitmap" - "Attributes" = "3:4" - "Setting" = "3:1" - "UsePlugInResources" = "11:TRUE" - } - "CopyrightWarning" - { - "Name" = "8:CopyrightWarning" - "DisplayName" = "8:#1002" - "Description" = "8:#1102" - "Type" = "3:3" - "ContextData" = "8:" - "Attributes" = "3:0" - "Setting" = "3:1" - "Value" = "8:#1202" - "DefaultValue" = "8:#1202" - "UsePlugInResources" = "11:TRUE" - } - "Welcome" - { - "Name" = "8:Welcome" - "DisplayName" = "8:#1003" - "Description" = "8:#1103" - "Type" = "3:3" - "ContextData" = "8:" - "Attributes" = "3:0" - "Setting" = "3:1" - "Value" = "8:#1203" - "DefaultValue" = "8:#1203" - "UsePlugInResources" = "11:TRUE" - } - } - } - } - } - } - "MergeModule" - { - } - "ProjectOutput" - { - "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_9892A03006D742BB8C58FDF406605C8B" - { - "SourcePath" = "8:..\\x86\\Release\\AppInstallerTestExeInstaller\\AppInstallerTestExeInstaller.exe" - "TargetName" = "8:" - "Tag" = "8:" - "Folder" = "8:_3FD954FA64934D1C9D4F66157C4603AF" - "Condition" = "8:" - "Transitive" = "11:FALSE" - "Vital" = "11:TRUE" - "ReadOnly" = "11:FALSE" - "Hidden" = "11:FALSE" - "System" = "11:FALSE" - "Permanent" = "11:FALSE" - "SharedLegacy" = "11:FALSE" - "PackageAs" = "3:1" - "Register" = "3:1" - "Exclude" = "11:FALSE" - "IsDependency" = "11:FALSE" - "IsolateTo" = "8:" - "ProjectOutputGroupRegister" = "3:1" - "OutputConfiguration" = "8:Release|Win32" - "OutputGroupCanonicalName" = "8:Built" - "OutputProjectGuid" = "8:{6CB84692-5994-407D-B9BD-9216AF77FE83}" - "ShowKeyOutput" = "11:TRUE" - "ExcludeFilters" - { - } - } - } - } -} +"DeployProject" +{ +"VSVersion" = "3:800" +"ProjectType" = "8:{978C614F-708E-4E1A-B201-565925725DBA}" +"IsWebType" = "8:FALSE" +"ProjectName" = "8:AppInstallerTestMsiInstaller" +"LanguageId" = "3:1033" +"CodePage" = "3:1252" +"UILanguageId" = "3:1033" +"SccProjectName" = "8:" +"SccLocalPath" = "8:" +"SccAuxPath" = "8:" +"SccProvider" = "8:" + "Hierarchy" + { + } + "Configurations" + { + "Debug" + { + "DisplayName" = "8:Debug" + "IsDebugOnly" = "11:TRUE" + "IsReleaseOnly" = "11:FALSE" + "OutputFilename" = "8:Debug\\AppInstallerTestMsiInstaller.msi" + "PackageFilesAs" = "3:2" + "PackageFileSize" = "3:-2147483648" + "CabType" = "3:1" + "Compression" = "3:2" + "SignOutput" = "11:FALSE" + "CertificateFile" = "8:" + "PrivateKeyFile" = "8:" + "TimeStampServer" = "8:" + "InstallerBootstrapper" = "3:2" + "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" + { + "Enabled" = "11:TRUE" + "PromptEnabled" = "11:TRUE" + "PrerequisitesLocation" = "2:1" + "Url" = "8:" + "ComponentsUrl" = "8:" + "Items" + { + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2" + { + "Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)" + "ProductCode" = "8:.NETFramework,Version=v4.7.2" + } + } + } + } + "Release" + { + "DisplayName" = "8:Release" + "IsDebugOnly" = "11:FALSE" + "IsReleaseOnly" = "11:TRUE" + "OutputFilename" = "8:Release\\AppInstallerTestMsiInstaller.msi" + "PackageFilesAs" = "3:2" + "PackageFileSize" = "3:-2147483648" + "CabType" = "3:1" + "Compression" = "3:2" + "SignOutput" = "11:FALSE" + "CertificateFile" = "8:" + "PrivateKeyFile" = "8:" + "TimeStampServer" = "8:" + "InstallerBootstrapper" = "3:2" + "BootstrapperCfg:{63ACBE69-63AA-4F98-B2B6-99F9E24495F2}" + { + "Enabled" = "11:TRUE" + "PromptEnabled" = "11:TRUE" + "PrerequisitesLocation" = "2:1" + "Url" = "8:" + "ComponentsUrl" = "8:" + "Items" + { + "{EDC2488A-8267-493A-A98E-7D9C3B36CDF3}:.NETFramework,Version=v4.7.2" + { + "Name" = "8:Microsoft .NET Framework 4.7.2 (x86 and x64)" + "ProductCode" = "8:.NETFramework,Version=v4.7.2" + } + } + } + } + } + "Deployable" + { + "CustomAction" + { + } + "DefaultFeature" + { + "Name" = "8:DefaultFeature" + "Title" = "8:" + "Description" = "8:" + } + "ExternalPersistence" + { + "LaunchCondition" + { + } + } + "File" + { + } + "FileType" + { + } + "Folder" + { + "{3C67513D-01DD-4637-8A68-80971EB9504F}:_3FD954FA64934D1C9D4F66157C4603AF" + { + "DefaultLocation" = "8:[ProgramFilesFolder][Manufacturer]\\[ProductName]" + "Name" = "8:#1925" + "AlwaysCreate" = "11:FALSE" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Property" = "8:TARGETDIR" + "Folders" + { + } + } + "{1525181F-901A-416C-8A58-119130FE478E}:_7CE3351BC05D486393C4720064B75896" + { + "Name" = "8:#1916" + "AlwaysCreate" = "11:FALSE" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Property" = "8:DesktopFolder" + "Folders" + { + } + } + "{1525181F-901A-416C-8A58-119130FE478E}:_9537533CAD3E4467B320694E22A39C4C" + { + "Name" = "8:#1919" + "AlwaysCreate" = "11:FALSE" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Property" = "8:ProgramMenuFolder" + "Folders" + { + } + } + } + "LaunchCondition" + { + } + "Locator" + { + } + "MsiBootstrapper" + { + "LangId" = "3:1033" + "RequiresElevation" = "11:FALSE" + } + "Product" + { + "Name" = "8:Microsoft Visual Studio" + "ProductName" = "8:AppInstallerTestMsiInstaller" + "ProductCode" = "8:{A5D36CF1-1993-4F63-BFB4-3ACD910D36A1}" + "PackageCode" = "8:{B21B22D8-5E23-401C-84FA-EE074DB23F8F}" + "UpgradeCode" = "8:{B9CF9DD5-D46F-4CE0-BFC9-633BF9D3A6F4}" + "AspNetVersion" = "8:4.0.30319.0" + "RestartWWWService" = "11:FALSE" + "RemovePreviousVersions" = "11:FALSE" + "DetectNewerInstalledVersion" = "11:TRUE" + "InstallAllUsers" = "11:FALSE" + "ProductVersion" = "8:1.0.0" + "Manufacturer" = "8:AppInstallerCLI" + "ARPHELPTELEPHONE" = "8:" + "ARPHELPLINK" = "8:" + "Title" = "8:AppInstallerTestMsiInstaller" + "Subject" = "8:" + "ARPCONTACT" = "8:AppInstallerCLI" + "Keywords" = "8:" + "ARPCOMMENTS" = "8:" + "ARPURLINFOABOUT" = "8:" + "ARPPRODUCTICON" = "8:" + "ARPIconIndex" = "3:0" + "SearchPath" = "8:" + "UseSystemSearchPath" = "11:TRUE" + "TargetPlatform" = "3:0" + "PreBuildEvent" = "8:" + "PostBuildEvent" = "8:" + "RunPostBuildEvent" = "3:0" + } + "Registry" + { + "HKLM" + { + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_951AD5F7C8424CCAB911C2469F382BA0" + { + "Name" = "8:Software" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_1245A15ED08E4D31AC11FE6C6AF43AA6" + { + "Name" = "8:[Manufacturer]" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + } + "Values" + { + } + } + } + "Values" + { + } + } + } + } + "HKCU" + { + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_A0E8E016B459429AB33F8A8041B409CF" + { + "Name" = "8:Software" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + "{60EA8692-D2D5-43EB-80DC-7906BF13D6EF}:_6F792619739B49D49348459164023C26" + { + "Name" = "8:[Manufacturer]" + "Condition" = "8:" + "AlwaysCreate" = "11:FALSE" + "DeleteAtUninstall" = "11:FALSE" + "Transitive" = "11:FALSE" + "Keys" + { + } + "Values" + { + } + } + } + "Values" + { + } + } + } + } + "HKCR" + { + "Keys" + { + } + } + "HKU" + { + "Keys" + { + } + } + "HKPU" + { + "Keys" + { + } + } + } + "Sequences" + { + } + "Shortcut" + { + } + "UserInterface" + { + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_07F5BD380D034D2788D0E26EC943AF9E" + { + "Name" = "8:#1900" + "Sequence" = "3:2" + "Attributes" = "3:1" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_0F2C662EB54446EEBB450AB41783B804" + { + "Sequence" = "3:300" + "DisplayName" = "8:Confirm Installation" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdAdminConfirmDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_5B669F9D2835440A97450ED9A579EBB0" + { + "Sequence" = "3:100" + "DisplayName" = "8:Welcome" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdAdminWelcomeDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "CopyrightWarning" + { + "Name" = "8:CopyrightWarning" + "DisplayName" = "8:#1002" + "Description" = "8:#1102" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1202" + "DefaultValue" = "8:#1202" + "UsePlugInResources" = "11:TRUE" + } + "Welcome" + { + "Name" = "8:Welcome" + "DisplayName" = "8:#1003" + "Description" = "8:#1103" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1203" + "DefaultValue" = "8:#1203" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_A1B6CF1DE99744E9BA298D9516F4FBBB" + { + "Sequence" = "3:200" + "DisplayName" = "8:Installation Folder" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdAdminFolderDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_31DFC87DB7EB49BBBE91080D4E17EBC3" + { + "Name" = "8:#1901" + "Sequence" = "3:2" + "Attributes" = "3:2" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_B0FC86DC72004463B28043005E93933D" + { + "Sequence" = "3:100" + "DisplayName" = "8:Progress" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdAdminProgressDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "ShowProgress" + { + "Name" = "8:ShowProgress" + "DisplayName" = "8:#1009" + "Description" = "8:#1109" + "Type" = "3:5" + "ContextData" = "8:1;True=1;False=0" + "Attributes" = "3:0" + "Setting" = "3:0" + "Value" = "3:1" + "DefaultValue" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_49F7C7303D464C18981485552AA18870" + { + "Name" = "8:#1901" + "Sequence" = "3:1" + "Attributes" = "3:2" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_78EF5C34DBDB4BC88F2789CBE1F17043" + { + "Sequence" = "3:100" + "DisplayName" = "8:Progress" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdProgressDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "ShowProgress" + { + "Name" = "8:ShowProgress" + "DisplayName" = "8:#1009" + "Description" = "8:#1109" + "Type" = "3:5" + "ContextData" = "8:1;True=1;False=0" + "Attributes" = "3:0" + "Setting" = "3:0" + "Value" = "3:1" + "DefaultValue" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_68474A3513F54E56B4D451594D7639DC" + { + "Name" = "8:#1902" + "Sequence" = "3:2" + "Attributes" = "3:3" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_3EC2CCC643CA4EEB9DAFF092520273EE" + { + "Sequence" = "3:100" + "DisplayName" = "8:Finished" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdAdminFinishedDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_81A3A282324C416DB8EA6A5FABEAD776" + { + "UseDynamicProperties" = "11:FALSE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdUserInterface.wim" + } + "{2479F3F5-0309-486D-8047-8187E2CE5BA0}:_869386ADC36F4DAE92F694770A481917" + { + "UseDynamicProperties" = "11:FALSE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdBasicDialogs.wim" + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_9E39D7A2A2BB4185AE8290AF159CBDE8" + { + "Name" = "8:#1902" + "Sequence" = "3:1" + "Attributes" = "3:3" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_18EEDC13BA92450EA46E5CFAD31164FE" + { + "Sequence" = "3:100" + "DisplayName" = "8:Finished" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdFinishedDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "UpdateText" + { + "Name" = "8:UpdateText" + "DisplayName" = "8:#1058" + "Description" = "8:#1158" + "Type" = "3:15" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1258" + "DefaultValue" = "8:#1258" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + "{DF760B10-853B-4699-99F2-AFF7185B4A62}:_A1D7AFF21DC048769B063F0ABB6BAD93" + { + "Name" = "8:#1900" + "Sequence" = "3:1" + "Attributes" = "3:1" + "Dialogs" + { + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_6AFCB2A470844F02AC97138D2C4255B5" + { + "Sequence" = "3:300" + "DisplayName" = "8:Confirm Installation" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdConfirmDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_73B000DF31AF44D789DC52D2EB3AC63B" + { + "Sequence" = "3:200" + "DisplayName" = "8:Installation Folder" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdFolderDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "InstallAllUsersVisible" + { + "Name" = "8:InstallAllUsersVisible" + "DisplayName" = "8:#1059" + "Description" = "8:#1159" + "Type" = "3:5" + "ContextData" = "8:1;True=1;False=0" + "Attributes" = "3:0" + "Setting" = "3:0" + "Value" = "3:1" + "DefaultValue" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + } + } + "{688940B3-5CA9-4162-8DEE-2993FA9D8CBC}:_E411887483814552AF4BF33BBB4F1B1D" + { + "Sequence" = "3:100" + "DisplayName" = "8:Welcome" + "UseDynamicProperties" = "11:TRUE" + "IsDependency" = "11:FALSE" + "SourcePath" = "8:\\VsdWelcomeDlg.wid" + "Properties" + { + "BannerBitmap" + { + "Name" = "8:BannerBitmap" + "DisplayName" = "8:#1001" + "Description" = "8:#1101" + "Type" = "3:8" + "ContextData" = "8:Bitmap" + "Attributes" = "3:4" + "Setting" = "3:1" + "UsePlugInResources" = "11:TRUE" + } + "CopyrightWarning" + { + "Name" = "8:CopyrightWarning" + "DisplayName" = "8:#1002" + "Description" = "8:#1102" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1202" + "DefaultValue" = "8:#1202" + "UsePlugInResources" = "11:TRUE" + } + "Welcome" + { + "Name" = "8:Welcome" + "DisplayName" = "8:#1003" + "Description" = "8:#1103" + "Type" = "3:3" + "ContextData" = "8:" + "Attributes" = "3:0" + "Setting" = "3:1" + "Value" = "8:#1203" + "DefaultValue" = "8:#1203" + "UsePlugInResources" = "11:TRUE" + } + } + } + } + } + } + "MergeModule" + { + } + "ProjectOutput" + { + "{5259A561-127C-4D43-A0A1-72F10C7B3BF8}:_9892A03006D742BB8C58FDF406605C8B" + { + "SourcePath" = "8:..\\x86\\Release\\AppInstallerTestExeInstaller\\AppInstallerTestExeInstaller.exe" + "TargetName" = "8:" + "Tag" = "8:" + "Folder" = "8:_3FD954FA64934D1C9D4F66157C4603AF" + "Condition" = "8:" + "Transitive" = "11:FALSE" + "Vital" = "11:TRUE" + "ReadOnly" = "11:FALSE" + "Hidden" = "11:FALSE" + "System" = "11:FALSE" + "Permanent" = "11:FALSE" + "SharedLegacy" = "11:FALSE" + "PackageAs" = "3:1" + "Register" = "3:1" + "Exclude" = "11:FALSE" + "IsDependency" = "11:FALSE" + "IsolateTo" = "8:" + "ProjectOutputGroupRegister" = "3:1" + "OutputConfiguration" = "8:Release|Win32" + "OutputGroupCanonicalName" = "8:Built" + "OutputProjectGuid" = "8:{6CB84692-5994-407D-B9BD-9216AF77FE83}" + "ShowKeyOutput" = "11:TRUE" + "ExcludeFilters" + { + } + } + } + } +} diff --git a/src/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.wapproj b/src/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.wapproj index a1ef9bdd74..67a292df90 100644 --- a/src/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.wapproj +++ b/src/AppInstallerTestMsixInstaller/AppInstallerTestMsixInstaller.wapproj @@ -1,94 +1,94 @@ - - - - 15.0 - - - - Debug - x86 - - - Release - x86 - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - Debug - AnyCPU - - - Release - AnyCPU - - - - $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ - - - - 3e2cba31-ceba-4d63-bf52-49c0718e19ea - 10.0.26100.0 - 10.0.17763.0 - en-US - false - ..\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.vcxproj - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - - Designer - - - - - - - - - - - - - - - + + + + 15.0 + + + + Debug + x86 + + + Release + x86 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + Debug + AnyCPU + + + Release + AnyCPU + + + + $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\ + + + + 3e2cba31-ceba-4d63-bf52-49c0718e19ea + 10.0.26100.0 + 10.0.17763.0 + en-US + false + ..\AppInstallerTestExeInstaller\AppInstallerTestExeInstaller.vcxproj + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + + Designer + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AppInstallerTestMsixInstaller/Package.appxmanifest b/src/AppInstallerTestMsixInstaller/Package.appxmanifest index 02dc19f64d..27f1866c2f 100644 --- a/src/AppInstallerTestMsixInstaller/Package.appxmanifest +++ b/src/AppInstallerTestMsixInstaller/Package.appxmanifest @@ -1,49 +1,49 @@ - - - - - - - - AppInstallerTestMsixInstaller - AppInstallerTest - Images\StoreLogo.png - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + AppInstallerTestMsixInstaller + AppInstallerTest + Images\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/COMServer/COMServer.vcxitems b/src/COMServer/COMServer.vcxitems index b5bfddd89b..f213c65913 100644 --- a/src/COMServer/COMServer.vcxitems +++ b/src/COMServer/COMServer.vcxitems @@ -1,13 +1,13 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - {409cd681-22a4-469d-88ae-cb5e4836e07a} - - - - __WRL_DISABLE_STATIC_INITIALIZE__;%(PreprocessorDefinitions) - - + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {409cd681-22a4-469d-88ae-cb5e4836e07a} + + + + __WRL_DISABLE_STATIC_INITIALIZE__;%(PreprocessorDefinitions) + + \ No newline at end of file diff --git a/src/CertificateResources/CertificateResources.h b/src/CertificateResources/CertificateResources.h index 67ef17722f..e5df648aec 100644 --- a/src/CertificateResources/CertificateResources.h +++ b/src/CertificateResources/CertificateResources.h @@ -1,14 +1,14 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#define CERTIFICATE_RESOURCE_TYPE 400 - -#define IDX_CERTIFICATE_STORE_ROOT_1 401 -#define IDX_CERTIFICATE_STORE_INTERMEDIATE_1 402 -#define IDX_CERTIFICATE_STORE_LEAF_1 403 -#define IDX_CERTIFICATE_STORE_ROOT_2 404 -#define IDX_CERTIFICATE_STORE_INTERMEDIATE_2 405 -#define IDX_CERTIFICATE_STORE_LEAF_2 406 -#define IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2 407 -#define IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2 408 +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define CERTIFICATE_RESOURCE_TYPE 400 + +#define IDX_CERTIFICATE_STORE_ROOT_1 401 +#define IDX_CERTIFICATE_STORE_INTERMEDIATE_1 402 +#define IDX_CERTIFICATE_STORE_LEAF_1 403 +#define IDX_CERTIFICATE_STORE_ROOT_2 404 +#define IDX_CERTIFICATE_STORE_INTERMEDIATE_2 405 +#define IDX_CERTIFICATE_STORE_LEAF_2 406 +#define IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2 407 +#define IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2 408 diff --git a/src/CertificateResources/CertificateResources.rc b/src/CertificateResources/CertificateResources.rc index 8c290de704..e65221b81d 100644 --- a/src/CertificateResources/CertificateResources.rc +++ b/src/CertificateResources/CertificateResources.rc @@ -1,76 +1,76 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" -#include "CertificateResources.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE 9, 1 - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - -///////////////////////////////////////////////////////////////////////////// -// -// Packages schema -// - -IDX_CERTIFICATE_STORE_ROOT_1 CERTIFICATE_RESOURCE_TYPE "StoreRoot1.cer" -IDX_CERTIFICATE_STORE_INTERMEDIATE_1 CERTIFICATE_RESOURCE_TYPE "StoreIntermediate1.cer" -IDX_CERTIFICATE_STORE_LEAF_1 CERTIFICATE_RESOURCE_TYPE "StoreLeaf1.cer" - -IDX_CERTIFICATE_STORE_ROOT_2 CERTIFICATE_RESOURCE_TYPE "StoreRoot2.cer" -IDX_CERTIFICATE_STORE_INTERMEDIATE_2 CERTIFICATE_RESOURCE_TYPE "StoreIntermediate2.cer" -IDX_CERTIFICATE_STORE_LEAF_2 CERTIFICATE_RESOURCE_TYPE "StoreLeaf2.cer" - -IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2 CERTIFICATE_RESOURCE_TYPE "Microsoft_TLS_ECC_Root_G2.crt" -IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2 CERTIFICATE_RESOURCE_TYPE "Microsoft_TLS_RSA_Root_G2.crt" +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "CertificateResources.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Packages schema +// + +IDX_CERTIFICATE_STORE_ROOT_1 CERTIFICATE_RESOURCE_TYPE "StoreRoot1.cer" +IDX_CERTIFICATE_STORE_INTERMEDIATE_1 CERTIFICATE_RESOURCE_TYPE "StoreIntermediate1.cer" +IDX_CERTIFICATE_STORE_LEAF_1 CERTIFICATE_RESOURCE_TYPE "StoreLeaf1.cer" + +IDX_CERTIFICATE_STORE_ROOT_2 CERTIFICATE_RESOURCE_TYPE "StoreRoot2.cer" +IDX_CERTIFICATE_STORE_INTERMEDIATE_2 CERTIFICATE_RESOURCE_TYPE "StoreIntermediate2.cer" +IDX_CERTIFICATE_STORE_LEAF_2 CERTIFICATE_RESOURCE_TYPE "StoreLeaf2.cer" + +IDX_CERTIFICATE_MS_TLS_ECC_ROOT_G2 CERTIFICATE_RESOURCE_TYPE "Microsoft_TLS_ECC_Root_G2.crt" +IDX_CERTIFICATE_MS_TLS_RSA_ROOT_G2 CERTIFICATE_RESOURCE_TYPE "Microsoft_TLS_RSA_Root_G2.crt" diff --git a/src/CertificateResources/CertificateResources.vcxitems b/src/CertificateResources/CertificateResources.vcxitems index 2a532157ee..cb31ca0c4a 100644 --- a/src/CertificateResources/CertificateResources.vcxitems +++ b/src/CertificateResources/CertificateResources.vcxitems @@ -1,33 +1,33 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - {b0bbbd92-943b-408f-b2b2-dbbab4a22d23} - - - - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) - - - - - - - - - - - - - - - - - - - - - - + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {b0bbbd92-943b-408f-b2b2-dbbab4a22d23} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/CertificateResources/CertificateResources.vcxitems.filters b/src/CertificateResources/CertificateResources.vcxitems.filters index 0352aedb4f..552cdd4e04 100644 --- a/src/CertificateResources/CertificateResources.vcxitems.filters +++ b/src/CertificateResources/CertificateResources.vcxitems.filters @@ -1,41 +1,41 @@ - - - - - {ad622b6a-16bb-4e32-921c-f1d6505fc0ed} - - - - - Certificates - - - Certificates - - - Certificates - - - Certificates - - - Certificates - - - Certificates - - - Certificates - - - Certificates - - - - - - - - - + + + + + {ad622b6a-16bb-4e32-921c-f1d6505fc0ed} + + + + + Certificates + + + Certificates + + + Certificates + + + Certificates + + + Certificates + + + Certificates + + + Certificates + + + Certificates + + + + + + + + + \ No newline at end of file diff --git a/src/CertificateResources/resource.h b/src/CertificateResources/resource.h index e7bf1ee70f..d2ad2adb85 100644 --- a/src/CertificateResources/resource.h +++ b/src/CertificateResources/resource.h @@ -1,14 +1,14 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by CertificateResources.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by CertificateResources.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/CodeAnalysis.ruleset b/src/CodeAnalysis.ruleset index 0d2c177f9b..b252f82def 100644 --- a/src/CodeAnalysis.ruleset +++ b/src/CodeAnalysis.ruleset @@ -1,251 +1,251 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ComInprocTestbed/ComInprocTestbed.manifest b/src/ComInprocTestbed/ComInprocTestbed.manifest index fbc69addfe..c88ebba43d 100644 --- a/src/ComInprocTestbed/ComInprocTestbed.manifest +++ b/src/ComInprocTestbed/ComInprocTestbed.manifest @@ -1,36 +1,36 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ComInprocTestbed/ComInprocTestbed.vcxproj b/src/ComInprocTestbed/ComInprocTestbed.vcxproj index 116906cf7b..b95fad10ff 100644 --- a/src/ComInprocTestbed/ComInprocTestbed.vcxproj +++ b/src/ComInprocTestbed/ComInprocTestbed.vcxproj @@ -1,223 +1,223 @@ - - - - - 15.0 - {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94} - Win32Proj - ComInprocTestbed - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - - Application - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - false - true - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - false - true - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - false - true - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - false - true - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - false - true - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - false - true - - - - - - - - - - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /Zi - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - true - true - stdcpp17 - stdcpp17 - MultiThreadedDebugDLL - - - Console - false - - - ComInprocTestbed.manifest - - - ComInprocTestbed.manifest - - - - - WIN32;%(PreprocessorDefinitions) - true - stdcpp17 - - - ComInprocTestbed.manifest - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - true - true - true - stdcpp17 - stdcpp17 - stdcpp17 - - - Console - true - true - false - - - ComInprocTestbed.manifest - - - ComInprocTestbed.manifest - - - ComInprocTestbed.manifest - - - - - - - Create - Create - Create - Create - Create - Create - - - - - - - - - {9ac3c6a4-1875-4d3e-bf9c-c31e81eff6b4} - false - false - true - - - {1cc41a9a-ae66-459d-9210-1e572dd7be69} - - - {2046b5af-666d-4ce8-8d3e-c32c57908a56} - false - false - true - - - - - true - - - - - - - true - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + 15.0 + {E5BCFF58-7D0C-4770-ABB9-AECE1027CD94} + Win32Proj + ComInprocTestbed + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + Application + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + false + true + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + false + true + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + false + true + + + + + + + + + + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /Zi + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + true + true + stdcpp17 + stdcpp17 + MultiThreadedDebugDLL + + + Console + false + + + ComInprocTestbed.manifest + + + ComInprocTestbed.manifest + + + + + WIN32;%(PreprocessorDefinitions) + true + stdcpp17 + + + ComInprocTestbed.manifest + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + true + true + true + stdcpp17 + stdcpp17 + stdcpp17 + + + Console + true + true + false + + + ComInprocTestbed.manifest + + + ComInprocTestbed.manifest + + + ComInprocTestbed.manifest + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + + + + {9ac3c6a4-1875-4d3e-bf9c-c31e81eff6b4} + false + false + true + + + {1cc41a9a-ae66-459d-9210-1e572dd7be69} + + + {2046b5af-666d-4ce8-8d3e-c32c57908a56} + false + false + true + + + + + true + + + + + + + true + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/src/ComInprocTestbed/ComInprocTestbed.vcxproj.filters b/src/ComInprocTestbed/ComInprocTestbed.vcxproj.filters index f7d573db19..fab38937e9 100644 --- a/src/ComInprocTestbed/ComInprocTestbed.vcxproj.filters +++ b/src/ComInprocTestbed/ComInprocTestbed.vcxproj.filters @@ -1,51 +1,51 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - - - - - - - - - - Header Files - - - Header Files - - - Header Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + + + + + + + + Header Files + + + Header Files + + + Header Files + + \ No newline at end of file diff --git a/src/ComInprocTestbed/PackageManager.cpp b/src/ComInprocTestbed/PackageManager.cpp index 574ae4b1ed..10e82b08f3 100644 --- a/src/ComInprocTestbed/PackageManager.cpp +++ b/src/ComInprocTestbed/PackageManager.cpp @@ -1,201 +1,201 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackageManager.h" - -using namespace winrt::Microsoft::Management::Deployment; - -// Work around the assertions about waiting on an STA thread from C++/WinRT -template -auto WaitForResult(Operation&& operation) -{ - std::promise promise; - auto future = promise.get_future(); - operation.Completed([&](const auto&, const auto&) { promise.set_value(); }); - future.wait(); - return operation.GetResults(); -} - -PackageCatalog Connect(const PackageCatalogReference& reference, std::string_view name) -{ - auto connectResult = reference.Connect(); - - if (connectResult.Status() != ConnectResultStatus::Ok) - { - std::cout << "Connecting to " << name << " got: " << static_cast(connectResult.Status()) << " [" << connectResult.ExtendedErrorCode() << "]\n"; - return nullptr; - } - - return connectResult.PackageCatalog(); -} - -PackageCatalog ConnectComposite(const PackageManager& packageManager, const TestParameters& testParameters, CompositeSearchBehavior searchBehavior, PackageInstallScope scope = PackageInstallScope::Any) -{ - CreateCompositePackageCatalogOptions options = testParameters.CreateCreateCompositePackageCatalogOptions(); - options.InstalledScope(scope); - options.CompositeSearchBehavior(searchBehavior); - - auto sourceName = winrt::to_hstring(testParameters.SourceName); - - for (PackageCatalogReference catalogRef : packageManager.GetPackageCatalogs()) - { - if (sourceName.empty() || catalogRef.Info().Name() == sourceName) - { - options.Catalogs().Append(catalogRef); - } - } - - // Inputs are provided for a source that we did not find; add it. - if (!sourceName.empty() && !testParameters.SourceURL.empty() && options.Catalogs().Size() == 0) - { - AddPackageCatalogOptions addPackageCatalogOptions = testParameters.CreateAddPackageCatalogOptions(); - addPackageCatalogOptions.Name(sourceName); - addPackageCatalogOptions.SourceUri(winrt::to_hstring(testParameters.SourceURL)); - addPackageCatalogOptions.TrustLevel(PackageCatalogTrustLevel::Trusted); - - auto addCatalogResult = WaitForResult(packageManager.AddPackageCatalogAsync(addPackageCatalogOptions)); - - if (addCatalogResult.Status() != AddPackageCatalogStatus::Ok) - { - std::cout << "Adding catalog `" << testParameters.SourceName << "` [`" << testParameters.SourceURL << "`] got: " << static_cast(addCatalogResult.Status()) << " [" << addCatalogResult.ExtendedErrorCode() << "]\n"; - return nullptr; - } - - // Get the new catalog - options.Catalogs().Append(packageManager.GetPackageCatalogByName(sourceName)); - } - - return Connect(packageManager.CreateCompositePackageCatalog(options), "Composite Catalog"); -} - -CatalogPackage FindPackage(const PackageCatalog& compositeCatalog, const TestParameters& testParameters) -{ - PackageMatchFilter filter = testParameters.CreatePackageMatchFilter(); - filter.Field(PackageMatchField::Id); - filter.Option(PackageFieldMatchOption::EqualsCaseInsensitive); - filter.Value(winrt::to_hstring(testParameters.PackageName)); - - FindPackagesOptions findOptions = testParameters.CreateFindPackagesOptions(); - findOptions.Filters().Append(filter); - - auto findResult = compositeCatalog.FindPackages(findOptions); - if (findResult.Status() != FindPackagesResultStatus::Ok) - { - std::cout << "Finding package " << testParameters.PackageName << " got: " << static_cast(findResult.Status()) << " [" << findResult.ExtendedErrorCode() << "]\n"; - return nullptr; - } - - if (findResult.Matches().Size() != 1) - { - std::cout << "Finding package " << testParameters.PackageName << " got " << findResult.Matches().Size() << " results.\n"; - return nullptr; - } - - return findResult.Matches().GetAt(0).CatalogPackage(); -} - -bool UsePackageManager(const TestParameters& testParameters) -{ - PackageManager packageManager = testParameters.CreatePackageManager(); - - // Force installed cache to be created - auto installedCatalogRef = packageManager.GetLocalPackageCatalog(LocalPackageCatalog::InstalledPackages); - auto installedCatalog = Connect(installedCatalogRef, "Installed Catalog"); - if (!installedCatalog) - { - return false; - } - - // Force TerminationSignalHandler to be created - auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs); - if (!compositeCatalog) - { - return false; - } - - auto package = FindPackage(compositeCatalog, testParameters); - if (!package) - { - return false; - } - - DownloadOptions downloadOptions = testParameters.CreateDownloadOptions(); - auto downloadResult = WaitForResult(packageManager.DownloadPackageAsync(package, downloadOptions)); - - if (downloadResult.Status() != DownloadResultStatus::Ok) - { - std::cout << "Downloading package " << testParameters.PackageName << " got: " << static_cast(downloadResult.Status()) << "\n"; - return false; - } - - return true; -} - -void InitializePackageManagerGlobals() -{ - PackageManagerSettings settings; - settings.SetCallerIdentifier(L"ComInprocTestbed"); - settings.SetStateIdentifier(L"ComInprocTestbed"); -} - -void SetUnloadPreference(bool value) -{ - PackageManagerSettings settings; - settings.CanUnloadPreference(value); -} -void SetDisableTerminationSignals(bool value) -{ - PackageManagerSettings settings; - settings.TerminationSignalMonitoring(!value); -} - -bool DetectForSystem(const TestParameters& testParameters) -{ - PackageManager packageManager = testParameters.CreatePackageManager(); - - auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromAllCatalogs, PackageInstallScope::SystemOrUnknown); - if (!compositeCatalog) - { - winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - - auto package = FindPackage(compositeCatalog, testParameters); - if (!package) - { - winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - - return package.DefaultInstallVersion() && package.InstalledVersion(); -} - -bool InstallForSystem(const TestParameters& testParameters) -{ - PackageManager packageManager = testParameters.CreatePackageManager(); - - auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromAllCatalogs, PackageInstallScope::SystemOrUnknown); - if (!compositeCatalog) - { - winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - - auto package = FindPackage(compositeCatalog, testParameters); - if (!package) - { - winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); - } - - InstallOptions options = testParameters.CreateInstallOptions(); - options.AcceptPackageAgreements(true); - options.BypassIsStoreClientBlockedPolicyCheck(true); - options.Force(true); - options.PackageInstallScope(PackageInstallScope::SystemOrUnknown); - auto installResult = WaitForResult(packageManager.InstallPackageAsync(package, options)); - - if (installResult.Status() != InstallResultStatus::Ok) - { - std::cout << "Installing package " << testParameters.PackageName << " got: " << static_cast(installResult.Status()) << " [" << installResult.ExtendedErrorCode() << "] [" << installResult.InstallerErrorCode() << "]\n"; - return false; - } - - return true; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageManager.h" + +using namespace winrt::Microsoft::Management::Deployment; + +// Work around the assertions about waiting on an STA thread from C++/WinRT +template +auto WaitForResult(Operation&& operation) +{ + std::promise promise; + auto future = promise.get_future(); + operation.Completed([&](const auto&, const auto&) { promise.set_value(); }); + future.wait(); + return operation.GetResults(); +} + +PackageCatalog Connect(const PackageCatalogReference& reference, std::string_view name) +{ + auto connectResult = reference.Connect(); + + if (connectResult.Status() != ConnectResultStatus::Ok) + { + std::cout << "Connecting to " << name << " got: " << static_cast(connectResult.Status()) << " [" << connectResult.ExtendedErrorCode() << "]\n"; + return nullptr; + } + + return connectResult.PackageCatalog(); +} + +PackageCatalog ConnectComposite(const PackageManager& packageManager, const TestParameters& testParameters, CompositeSearchBehavior searchBehavior, PackageInstallScope scope = PackageInstallScope::Any) +{ + CreateCompositePackageCatalogOptions options = testParameters.CreateCreateCompositePackageCatalogOptions(); + options.InstalledScope(scope); + options.CompositeSearchBehavior(searchBehavior); + + auto sourceName = winrt::to_hstring(testParameters.SourceName); + + for (PackageCatalogReference catalogRef : packageManager.GetPackageCatalogs()) + { + if (sourceName.empty() || catalogRef.Info().Name() == sourceName) + { + options.Catalogs().Append(catalogRef); + } + } + + // Inputs are provided for a source that we did not find; add it. + if (!sourceName.empty() && !testParameters.SourceURL.empty() && options.Catalogs().Size() == 0) + { + AddPackageCatalogOptions addPackageCatalogOptions = testParameters.CreateAddPackageCatalogOptions(); + addPackageCatalogOptions.Name(sourceName); + addPackageCatalogOptions.SourceUri(winrt::to_hstring(testParameters.SourceURL)); + addPackageCatalogOptions.TrustLevel(PackageCatalogTrustLevel::Trusted); + + auto addCatalogResult = WaitForResult(packageManager.AddPackageCatalogAsync(addPackageCatalogOptions)); + + if (addCatalogResult.Status() != AddPackageCatalogStatus::Ok) + { + std::cout << "Adding catalog `" << testParameters.SourceName << "` [`" << testParameters.SourceURL << "`] got: " << static_cast(addCatalogResult.Status()) << " [" << addCatalogResult.ExtendedErrorCode() << "]\n"; + return nullptr; + } + + // Get the new catalog + options.Catalogs().Append(packageManager.GetPackageCatalogByName(sourceName)); + } + + return Connect(packageManager.CreateCompositePackageCatalog(options), "Composite Catalog"); +} + +CatalogPackage FindPackage(const PackageCatalog& compositeCatalog, const TestParameters& testParameters) +{ + PackageMatchFilter filter = testParameters.CreatePackageMatchFilter(); + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::EqualsCaseInsensitive); + filter.Value(winrt::to_hstring(testParameters.PackageName)); + + FindPackagesOptions findOptions = testParameters.CreateFindPackagesOptions(); + findOptions.Filters().Append(filter); + + auto findResult = compositeCatalog.FindPackages(findOptions); + if (findResult.Status() != FindPackagesResultStatus::Ok) + { + std::cout << "Finding package " << testParameters.PackageName << " got: " << static_cast(findResult.Status()) << " [" << findResult.ExtendedErrorCode() << "]\n"; + return nullptr; + } + + if (findResult.Matches().Size() != 1) + { + std::cout << "Finding package " << testParameters.PackageName << " got " << findResult.Matches().Size() << " results.\n"; + return nullptr; + } + + return findResult.Matches().GetAt(0).CatalogPackage(); +} + +bool UsePackageManager(const TestParameters& testParameters) +{ + PackageManager packageManager = testParameters.CreatePackageManager(); + + // Force installed cache to be created + auto installedCatalogRef = packageManager.GetLocalPackageCatalog(LocalPackageCatalog::InstalledPackages); + auto installedCatalog = Connect(installedCatalogRef, "Installed Catalog"); + if (!installedCatalog) + { + return false; + } + + // Force TerminationSignalHandler to be created + auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs); + if (!compositeCatalog) + { + return false; + } + + auto package = FindPackage(compositeCatalog, testParameters); + if (!package) + { + return false; + } + + DownloadOptions downloadOptions = testParameters.CreateDownloadOptions(); + auto downloadResult = WaitForResult(packageManager.DownloadPackageAsync(package, downloadOptions)); + + if (downloadResult.Status() != DownloadResultStatus::Ok) + { + std::cout << "Downloading package " << testParameters.PackageName << " got: " << static_cast(downloadResult.Status()) << "\n"; + return false; + } + + return true; +} + +void InitializePackageManagerGlobals() +{ + PackageManagerSettings settings; + settings.SetCallerIdentifier(L"ComInprocTestbed"); + settings.SetStateIdentifier(L"ComInprocTestbed"); +} + +void SetUnloadPreference(bool value) +{ + PackageManagerSettings settings; + settings.CanUnloadPreference(value); +} +void SetDisableTerminationSignals(bool value) +{ + PackageManagerSettings settings; + settings.TerminationSignalMonitoring(!value); +} + +bool DetectForSystem(const TestParameters& testParameters) +{ + PackageManager packageManager = testParameters.CreatePackageManager(); + + auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromAllCatalogs, PackageInstallScope::SystemOrUnknown); + if (!compositeCatalog) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + auto package = FindPackage(compositeCatalog, testParameters); + if (!package) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + return package.DefaultInstallVersion() && package.InstalledVersion(); +} + +bool InstallForSystem(const TestParameters& testParameters) +{ + PackageManager packageManager = testParameters.CreatePackageManager(); + + auto compositeCatalog = ConnectComposite(packageManager, testParameters, CompositeSearchBehavior::RemotePackagesFromAllCatalogs, PackageInstallScope::SystemOrUnknown); + if (!compositeCatalog) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + auto package = FindPackage(compositeCatalog, testParameters); + if (!package) + { + winrt::throw_hresult(HRESULT_FROM_WIN32(ERROR_INVALID_STATE)); + } + + InstallOptions options = testParameters.CreateInstallOptions(); + options.AcceptPackageAgreements(true); + options.BypassIsStoreClientBlockedPolicyCheck(true); + options.Force(true); + options.PackageInstallScope(PackageInstallScope::SystemOrUnknown); + auto installResult = WaitForResult(packageManager.InstallPackageAsync(package, options)); + + if (installResult.Status() != InstallResultStatus::Ok) + { + std::cout << "Installing package " << testParameters.PackageName << " got: " << static_cast(installResult.Status()) << " [" << installResult.ExtendedErrorCode() << "] [" << installResult.InstallerErrorCode() << "]\n"; + return false; + } + + return true; +} diff --git a/src/ComInprocTestbed/PackageManager.h b/src/ComInprocTestbed/PackageManager.h index 0ad82e64dd..c08f06de5b 100644 --- a/src/ComInprocTestbed/PackageManager.h +++ b/src/ComInprocTestbed/PackageManager.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include "Tests.h" - -// Attempts to instantiate all static objects -bool UsePackageManager(const TestParameters& testParameters); - -// Sets up the globals for the test caller. -void InitializePackageManagerGlobals(); - -// Sets the module to prevent it from unloading. -void SetUnloadPreference(bool value); - -// Sets the module to prevent it from listening to termination signals. -void SetDisableTerminationSignals(bool value); - -// Attempts to detect the target package as installed for the system. -bool DetectForSystem(const TestParameters& testParameters); - -// Installs the target package for the system. -bool InstallForSystem(const TestParameters& testParameters); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include "Tests.h" + +// Attempts to instantiate all static objects +bool UsePackageManager(const TestParameters& testParameters); + +// Sets up the globals for the test caller. +void InitializePackageManagerGlobals(); + +// Sets the module to prevent it from unloading. +void SetUnloadPreference(bool value); + +// Sets the module to prevent it from listening to termination signals. +void SetDisableTerminationSignals(bool value); + +// Attempts to detect the target package as installed for the system. +bool DetectForSystem(const TestParameters& testParameters); + +// Installs the target package for the system. +bool InstallForSystem(const TestParameters& testParameters); diff --git a/src/ComInprocTestbed/Tests.cpp b/src/ComInprocTestbed/Tests.cpp index 95244506af..7f9ecf6726 100644 --- a/src/ComInprocTestbed/Tests.cpp +++ b/src/ComInprocTestbed/Tests.cpp @@ -1,633 +1,633 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Tests.h" -#include "PackageManager.h" - -using namespace std::string_view_literals; -using namespace winrt::Microsoft::Management::Deployment; - -namespace -{ -#define ADVANCE_ARG_PARAMETER if (++i >= argc) { winrt::throw_hresult(E_INVALIDARG); } - - std::string ToLower(std::string_view in) - { - std::string result(in); - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return static_cast(std::tolower(c)); }); - return result; - } - -#define BEGIN_ENUM_PARSE_FUNC(_type_) \ - _type_ Parse ## _type_(const char* input) \ - { \ - auto lower = ToLower(input); \ - if (lower.empty()) {} - -#define ITEM_ENUM_PARSE_FUNC(_type_, _value_) \ - else if (ToLower(#_value_) == lower) \ - { \ - return _type_ ## :: ## _value_; \ - } - -#define END_ENUM_PARSE_FUNC \ - winrt::throw_hresult(E_INVALIDARG); \ - } - -#define BEGIN_ENUM_NAME_FUNC(_type_) \ - std::string_view ToString(_type_); \ - std::ostream& operator<<(std::ostream& o, _type_ input) { return (o << ToString(input)); } \ - std::string_view ToString(_type_ input) \ - { \ - switch (input) { - -#define ITEM_ENUM_NAME_FUNC(_type_, _value_) \ - case _type_ ## :: ## _value_: return #_value_; - -#define END_ENUM_NAME_FUNC \ - } \ - return "Unknown"; \ - } - - BEGIN_ENUM_PARSE_FUNC(ComInitializationType) - ITEM_ENUM_PARSE_FUNC(ComInitializationType, STA) - ITEM_ENUM_PARSE_FUNC(ComInitializationType, MTA) - END_ENUM_PARSE_FUNC - - BEGIN_ENUM_NAME_FUNC(ComInitializationType) - ITEM_ENUM_NAME_FUNC(ComInitializationType, STA) - ITEM_ENUM_NAME_FUNC(ComInitializationType, MTA) - END_ENUM_NAME_FUNC - - BEGIN_ENUM_PARSE_FUNC(UnloadBehavior) - ITEM_ENUM_PARSE_FUNC(UnloadBehavior, Allow) - ITEM_ENUM_PARSE_FUNC(UnloadBehavior, AtUninitialize) - ITEM_ENUM_PARSE_FUNC(UnloadBehavior, Never) - END_ENUM_PARSE_FUNC - - BEGIN_ENUM_NAME_FUNC(UnloadBehavior) - ITEM_ENUM_NAME_FUNC(UnloadBehavior, Allow) - ITEM_ENUM_NAME_FUNC(UnloadBehavior, AtUninitialize) - ITEM_ENUM_NAME_FUNC(UnloadBehavior, Never) - END_ENUM_NAME_FUNC - - BEGIN_ENUM_PARSE_FUNC(ActivationType) - ITEM_ENUM_PARSE_FUNC(ActivationType, ClassName) - ITEM_ENUM_PARSE_FUNC(ActivationType, CoCreateInstance) - END_ENUM_PARSE_FUNC - - BEGIN_ENUM_NAME_FUNC(ActivationType) - ITEM_ENUM_NAME_FUNC(ActivationType, ClassName) - ITEM_ENUM_NAME_FUNC(ActivationType, CoCreateInstance) - END_ENUM_NAME_FUNC - - BOOL CALLBACK CheckForWinGetWindow(HWND hwnd, LPARAM param) - { - bool* result = reinterpret_cast(param); - - DWORD windowProcessId = 0; - GetWindowThreadProcessId(hwnd, &windowProcessId); - - if (GetCurrentProcessId() == windowProcessId) - { - int textLength = GetWindowTextLengthW(hwnd) + 1; - std::wstring windowText(textLength, '\0'); - textLength = GetWindowTextW(hwnd, &windowText[0], textLength); - windowText.resize(textLength); - - if (L"WingetMessageOnlyWindow"sv == windowText) - { - *result = true; - return FALSE; - } - } - - return TRUE; - } - - // Look for the set of well known objects that should be present after we have spun everything up. - // Returns true if all well known objects are found in the expected state. - bool SearchForWellKnownObjects(bool expectExist, const Snapshot& snapshot) - { - bool result = true; - - // Known modules in snapshot - bool knownModulesLoaded = snapshot.MicrosoftManagementDeploymentInProcLoaded && snapshot.WindowsPackageManagerLoaded; - if (knownModulesLoaded != expectExist) - { - std::cout << "Known modules were not in expected state [" << (expectExist ? "loaded" : "unloaded") << "]\n"; - result = false; - } - - auto coreApplicationProperties = winrt::Windows::ApplicationModel::Core::CoreApplication::Properties(); - - // COM statics - for (std::wstring_view item : { - L"WindowsPackageManager.CachedInstalledIndex"sv, - L"WindowsPackageManager.TerminationSignalHandler"sv, - }) - { - bool present = coreApplicationProperties.HasKey(item); - - if (present != expectExist) - { - std::cout << "CoreApplication property `" << winrt::to_string(item) << "` was not in expected state [" << (expectExist ? "should exist" : "should not exist") << "]\n"; - result = false; - } - } - - return result; - } - - // Look for the set of termination signal monitoring objects that should be present after we have spun everything up. - // Returns true if all objects are found in the expected state. - bool SearchForTerminationSignalObjects(bool expectExist) - { - bool result = true; - - // Shutdown monitoring window - bool foundWindow = false; - EnumWindows(CheckForWinGetWindow, reinterpret_cast(&foundWindow)); - - if (foundWindow != expectExist) - { - std::cout << "WinGet Window `WingetMessageOnlyWindow` was not in expected state [" << (expectExist ? "should exist" : "should not exist") << "]\n"; - result = false; - } - - return result; - } - - std::string GetBytesString(SIZE_T bytes) - { - constexpr SIZE_T s_kilo = 1024; - constexpr std::string_view s_sizes = "BKMG"sv; - size_t i = 0; - - while ((i + 1) < s_sizes.size() && bytes > s_kilo) - { - bytes /= s_kilo; - ++i; - } - - return std::to_string(bytes) + s_sizes[i]; - } - - const CLSID CLSID_PackageManager = { 0x2DDE4456, 0x64D9, 0x4673, 0x8F, 0x7E, 0xA4, 0xF1, 0x9A, 0x2E, 0x6C, 0xC3 }; // 2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3 - const CLSID CLSID_FindPackagesOptions = { 0x96B9A53A, 0x9228, 0x4DA0, 0xB0, 0x13, 0xBB, 0x1B, 0x20, 0x31, 0xAB, 0x3D }; // 96B9A53A-9228-4DA0-B013-BB1B2031AB3D - const CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x768318A6, 0x2EB5, 0x400D, 0x84, 0xD0, 0xDF, 0x35, 0x34, 0xC3, 0x0F, 0x5D }; // 768318A6-2EB5-400D-84D0-DF3534C30F5D - const CLSID CLSID_InstallOptions = { 0xE2AF3BA8, 0x8A88, 0x4766, 0x9D, 0xDA, 0xAE, 0x40, 0x13, 0xAD, 0xE2, 0x86 }; // E2AF3BA8-8A88-4766-9DDA-AE4013ADE286 - const CLSID CLSID_UninstallOptions = { 0x869CB959, 0xEB54, 0x425C, 0xA1, 0xE4, 0x1A, 0x1C, 0x29, 0x1C, 0x64, 0xE9 }; // 869CB959-EB54-425C-A1E4-1A1C291C64E9 - const CLSID CLSID_PackageMatchFilter = { 0x57DC8962, 0x7343, 0x42CD, 0xB9, 0x1C, 0x04, 0xF6, 0xA2, 0x5D, 0xB1, 0xD0 }; // 57DC8962-7343-42CD-B91C-04F6A25DB1D0 - const CLSID CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB - const CLSID CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 - const CLSID CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 - const CLSID CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} - const CLSID CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} - const CLSID CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} - - template - T CreatePackageManagerObject(ActivationType activationType, const CLSID& clsid) - { - if (ActivationType::ClassName == activationType) - { - return T{}; - } - else if (ActivationType::CoCreateInstance == activationType) - { - return winrt::create_instance(clsid); - } - - winrt::throw_hresult(E_UNEXPECTED); - } -} - -TestParameters::TestParameters(int argc, const char** argv) -{ - for (int i = 0; i < argc; ++i) - { - if ("-test"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - TestToRun = ToLower(argv[i]); - } - else if ("-com"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - ComInit = ParseComInitializationType(argv[i]); - } - else if ("-leak-com"sv == argv[i]) - { - LeakCOM = true; - } - else if ("-itr"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - Iterations = atoi(argv[i]); - } - else if ("-pkg"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - PackageName = argv[i]; - } - else if ("-src"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - SourceName = argv[i]; - } - else if ("-url"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - SourceURL = argv[i]; - } - else if ("-unload"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - UnloadBehavior = ParseUnloadBehavior(argv[i]); - } - else if ("-activation"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - ActivationType = ParseActivationType(argv[i]); - } - else if ("-keep-factories"sv == argv[i]) - { - SkipClearFactories = true; - } - else if ("-work-test-sleep"sv == argv[i]) - { - ADVANCE_ARG_PARAMETER - WorkTestSleepInterval = atoi(argv[i]); - } - else if ("-no-term"sv == argv[i]) - { - DisableTerminationSignals = true; - } - } -} - -void TestParameters::OutputDetails() const -{ - std::cout << "Running inproc testbed with:\n" - " COM Init : " << ComInit << "\n" - " Activate : " << ActivationType << "\n" - " Clear : " << std::boolalpha << !SkipClearFactories << "\n" - " Leak COM : " << std::boolalpha << LeakCOM << "\n" - " Unload : " << UnloadBehavior << "\n" - " Expect : " << std::boolalpha << UnloadExpected() << "\n" - " Test : " << TestToRun << "\n" - " Sleep : " << WorkTestSleepInterval << "\n" - " Package : " << PackageName << "\n" - " Source : " << SourceName << "\n" - " URL : " << SourceURL << "\n" - " Passes : " << Iterations << std::endl; -} - -bool TestParameters::InitializeTestState() const -{ - HRESULT hr = S_OK; - - if (ComInitializationType::STA == ComInit) - { - hr = RoInitialize(RO_INIT_SINGLETHREADED); - } - else if (ComInitializationType::MTA == ComInit) - { - hr = RoInitialize(RO_INIT_MULTITHREADED); - } - - if (FAILED(hr)) - { - std::cout << "RoInitialize returned " << hr << std::endl; - return false; - } - - if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior) - { - SetUnloadPreference(false); - } - - return true; -} - -bool TestParameters::InitializeIterationState() const -{ - InitializePackageManagerGlobals(); - - if (DisableTerminationSignals) - { - SetDisableTerminationSignals(true); - } - - return true; -} - -std::unique_ptr TestParameters::CreateTest() const -{ - if ("unload_check"sv == TestToRun) - { - return std::make_unique(*this); - } - else if ("install_detect"sv == TestToRun) - { - return std::make_unique(*this); - } - - return {}; -} - -void TestParameters::UninitializeTestState() const -{ - if (UnloadBehavior::AtUninitialize == UnloadBehavior) - { - SetUnloadPreference(true); - } - - if (!LeakCOM) - { - RoUninitialize(); - } -} - -bool TestParameters::UnloadExpected() const -{ - bool shouldUnload = true; - if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior || - (ActivationType::ClassName == ActivationType && SkipClearFactories)) - { - shouldUnload = false; - } - return shouldUnload; -} - -PackageManager TestParameters::CreatePackageManager() const -{ - return CreatePackageManagerObject(ActivationType, CLSID_PackageManager); -} - -CreateCompositePackageCatalogOptions TestParameters::CreateCreateCompositePackageCatalogOptions() const -{ - return CreatePackageManagerObject(ActivationType, CLSID_CreateCompositePackageCatalogOptions); -} - -PackageMatchFilter TestParameters::CreatePackageMatchFilter() const -{ - return CreatePackageManagerObject(ActivationType, CLSID_PackageMatchFilter); -} - -FindPackagesOptions TestParameters::CreateFindPackagesOptions() const -{ - return CreatePackageManagerObject(ActivationType, CLSID_FindPackagesOptions); -} - -DownloadOptions TestParameters::CreateDownloadOptions() const -{ - return CreatePackageManagerObject(ActivationType, CLSID_DownloadOptions); -} - -AddPackageCatalogOptions TestParameters::CreateAddPackageCatalogOptions() const -{ - return CreatePackageManagerObject(ActivationType, CLSID_AddPackageCatalogOptions); -} - -InstallOptions TestParameters::CreateInstallOptions() const -{ - return CreatePackageManagerObject(ActivationType, CLSID_InstallOptions); -} - -Snapshot::Snapshot() -{ - const DWORD processId = GetCurrentProcessId(); - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE, processId); - - // Count threads in this process - THREADENTRY32 threadEntry{}; - threadEntry.dwSize = sizeof(threadEntry); - - if (Thread32First(snapshot, &threadEntry)) - { - do - { - if (processId == threadEntry.th32OwnerProcessID) - { - ++ThreadCount; - } - } while (Thread32Next(snapshot, &threadEntry)); - } - - // Count modules - MODULEENTRY32 moduleEntry{}; - moduleEntry.dwSize = sizeof(moduleEntry); - - if (Module32First(snapshot, &moduleEntry)) - { - do - { - if (moduleEntry.szModule == L"Microsoft.Management.Deployment.InProc.dll"sv) - { - MicrosoftManagementDeploymentInProcLoaded = true; - } - else if (moduleEntry.szModule == L"WindowsPackageManager.dll"sv) - { - WindowsPackageManagerLoaded = true; - } - - ++ModuleCount; - } while (Module32Next(snapshot, &moduleEntry)); - } - - // Get memory stats - GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&Memory), sizeof(Memory)); - - CloseHandle(snapshot); -} - -UnloadAndCheckForLeaks::UnloadAndCheckForLeaks(const TestParameters& parameters) : m_parameters(parameters) -{ -} - -bool UnloadAndCheckForLeaks::RunIterationWork() -{ - std::cout << "UnloadAndCheckForLeaks::RunIterationWork\n"; - return UsePackageManager(m_parameters); -} - -bool UnloadAndCheckForLeaks::RunIterationTest() -{ - std::cout << "UnloadAndCheckForLeaks::RunIterationTest\n"; - - Snapshot beforeUnload; - if (!SearchForWellKnownObjects(true, beforeUnload) || - !SearchForTerminationSignalObjects(!m_parameters.DisableTerminationSignals)) - { - return false; - } - - CoFreeUnusedLibrariesEx(0, 0); - - Snapshot afterUnload; - m_iterationSnapshots.emplace_back(beforeUnload, afterUnload); - - if (!SearchForWellKnownObjects(!m_parameters.UnloadExpected(), afterUnload) || - !SearchForTerminationSignalObjects(!m_parameters.UnloadExpected() && !m_parameters.DisableTerminationSignals)) - { - return false; - } - - return true; -} - -bool UnloadAndCheckForLeaks::RunFinal() -{ - constexpr std::streamsize s_columnWidth = 5; - - bool result = true; - - std::cout << "--- UnloadAndCheckForLeaks results ---\n"; - std::cout << std::setfill(' '); - - // --- Threads --- - std::cout << "Thread Count [Initial: " << m_initialSnapshot.ThreadCount << "]\n"; - - std::cout << "Iteration "; - for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) - { - std::cout << std::setw(s_columnWidth) << (i + 1); - } - std::cout << '\n'; - - std::cout << "Pre Unload "; - for (const auto& snapshot : m_iterationSnapshots) - { - std::cout << std::setw(s_columnWidth) << snapshot.first.ThreadCount; - } - std::cout << '\n'; - - std::cout << "Post Unload"; - for (const auto& snapshot : m_iterationSnapshots) - { - std::cout << std::setw(s_columnWidth) << snapshot.second.ThreadCount; - } - std::cout << '\n'; - - // Look for consistent increase in measured values - if (m_iterationSnapshots.size() > 1) - { - size_t previousValue = m_iterationSnapshots[0].second.ThreadCount; - bool consistentIncrease = true; - - for (size_t i = 1; i < m_iterationSnapshots.size(); ++i) - { - size_t currentValue = m_iterationSnapshots[i].second.ThreadCount; - if (currentValue > previousValue) - { - previousValue = currentValue; - } - else - { - consistentIncrease = false; - break; - } - } - - if (consistentIncrease) - { - std::cout << "Post unload thread count shows consistent increase; failing test.\n"; - result = false; - } - } - - // --- Modules --- - std::cout << "Module Count [Initial: " << m_initialSnapshot.ModuleCount << "]\n"; - - std::cout << "Iteration "; - for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) - { - std::cout << std::setw(s_columnWidth) << (i + 1); - } - std::cout << '\n'; - - std::cout << "Pre Unload "; - for (const auto& snapshot : m_iterationSnapshots) - { - std::cout << std::setw(s_columnWidth) << snapshot.first.ModuleCount; - } - std::cout << '\n'; - - std::cout << "Post Unload"; - for (const auto& snapshot : m_iterationSnapshots) - { - std::cout << std::setw(s_columnWidth) << snapshot.second.ModuleCount; - } - std::cout << '\n'; - - // Look for modules not unloading - if (m_parameters.UnloadExpected() && m_iterationSnapshots.size() > 1) - { - bool noUnloadFound = false; - - for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) - { - if (m_iterationSnapshots[i].first.ModuleCount == m_iterationSnapshots[i].second.ModuleCount) - { - noUnloadFound = true; - } - } - - if (noUnloadFound) - { - std::cout << "Module count did not decrease during at least one iteration; failing test.\n"; - result = false; - } - } - - // --- Memory --- - std::cout << "Private Usage [Initial: " << GetBytesString(m_initialSnapshot.Memory.PrivateUsage) << "]\n"; - - std::cout << "Iteration "; - for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) - { - std::cout << std::setw(s_columnWidth) << (i + 1); - } - std::cout << '\n'; - - std::cout << "Pre Unload "; - for (const auto& snapshot : m_iterationSnapshots) - { - std::cout << std::setw(s_columnWidth) << GetBytesString(snapshot.first.Memory.PrivateUsage); - } - std::cout << '\n'; - - std::cout << "Post Unload"; - for (const auto& snapshot : m_iterationSnapshots) - { - std::cout << std::setw(s_columnWidth) << GetBytesString(snapshot.second.Memory.PrivateUsage); - } - std::cout << '\n'; - - return result; -} - -InstallForSystem_DetectPresence::InstallForSystem_DetectPresence(const TestParameters& parameters) : m_parameters(parameters) -{ -} - -bool InstallForSystem_DetectPresence::RunIterationWork() -{ - std::cout << "Before installing, the detection state was: " << std::boolalpha << DetectForSystem(m_parameters) << '\n'; - - return InstallForSystem(m_parameters); -} - -bool InstallForSystem_DetectPresence::RunIterationTest() -{ - bool result = DetectForSystem(m_parameters); - std::cout << "After installing, the detection state was: " << std::boolalpha << result << '\n'; - return result; -} - -bool InstallForSystem_DetectPresence::RunFinal() -{ - return true; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Tests.h" +#include "PackageManager.h" + +using namespace std::string_view_literals; +using namespace winrt::Microsoft::Management::Deployment; + +namespace +{ +#define ADVANCE_ARG_PARAMETER if (++i >= argc) { winrt::throw_hresult(E_INVALIDARG); } + + std::string ToLower(std::string_view in) + { + std::string result(in); + std::transform(result.begin(), result.end(), result.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return result; + } + +#define BEGIN_ENUM_PARSE_FUNC(_type_) \ + _type_ Parse ## _type_(const char* input) \ + { \ + auto lower = ToLower(input); \ + if (lower.empty()) {} + +#define ITEM_ENUM_PARSE_FUNC(_type_, _value_) \ + else if (ToLower(#_value_) == lower) \ + { \ + return _type_ ## :: ## _value_; \ + } + +#define END_ENUM_PARSE_FUNC \ + winrt::throw_hresult(E_INVALIDARG); \ + } + +#define BEGIN_ENUM_NAME_FUNC(_type_) \ + std::string_view ToString(_type_); \ + std::ostream& operator<<(std::ostream& o, _type_ input) { return (o << ToString(input)); } \ + std::string_view ToString(_type_ input) \ + { \ + switch (input) { + +#define ITEM_ENUM_NAME_FUNC(_type_, _value_) \ + case _type_ ## :: ## _value_: return #_value_; + +#define END_ENUM_NAME_FUNC \ + } \ + return "Unknown"; \ + } + + BEGIN_ENUM_PARSE_FUNC(ComInitializationType) + ITEM_ENUM_PARSE_FUNC(ComInitializationType, STA) + ITEM_ENUM_PARSE_FUNC(ComInitializationType, MTA) + END_ENUM_PARSE_FUNC + + BEGIN_ENUM_NAME_FUNC(ComInitializationType) + ITEM_ENUM_NAME_FUNC(ComInitializationType, STA) + ITEM_ENUM_NAME_FUNC(ComInitializationType, MTA) + END_ENUM_NAME_FUNC + + BEGIN_ENUM_PARSE_FUNC(UnloadBehavior) + ITEM_ENUM_PARSE_FUNC(UnloadBehavior, Allow) + ITEM_ENUM_PARSE_FUNC(UnloadBehavior, AtUninitialize) + ITEM_ENUM_PARSE_FUNC(UnloadBehavior, Never) + END_ENUM_PARSE_FUNC + + BEGIN_ENUM_NAME_FUNC(UnloadBehavior) + ITEM_ENUM_NAME_FUNC(UnloadBehavior, Allow) + ITEM_ENUM_NAME_FUNC(UnloadBehavior, AtUninitialize) + ITEM_ENUM_NAME_FUNC(UnloadBehavior, Never) + END_ENUM_NAME_FUNC + + BEGIN_ENUM_PARSE_FUNC(ActivationType) + ITEM_ENUM_PARSE_FUNC(ActivationType, ClassName) + ITEM_ENUM_PARSE_FUNC(ActivationType, CoCreateInstance) + END_ENUM_PARSE_FUNC + + BEGIN_ENUM_NAME_FUNC(ActivationType) + ITEM_ENUM_NAME_FUNC(ActivationType, ClassName) + ITEM_ENUM_NAME_FUNC(ActivationType, CoCreateInstance) + END_ENUM_NAME_FUNC + + BOOL CALLBACK CheckForWinGetWindow(HWND hwnd, LPARAM param) + { + bool* result = reinterpret_cast(param); + + DWORD windowProcessId = 0; + GetWindowThreadProcessId(hwnd, &windowProcessId); + + if (GetCurrentProcessId() == windowProcessId) + { + int textLength = GetWindowTextLengthW(hwnd) + 1; + std::wstring windowText(textLength, '\0'); + textLength = GetWindowTextW(hwnd, &windowText[0], textLength); + windowText.resize(textLength); + + if (L"WingetMessageOnlyWindow"sv == windowText) + { + *result = true; + return FALSE; + } + } + + return TRUE; + } + + // Look for the set of well known objects that should be present after we have spun everything up. + // Returns true if all well known objects are found in the expected state. + bool SearchForWellKnownObjects(bool expectExist, const Snapshot& snapshot) + { + bool result = true; + + // Known modules in snapshot + bool knownModulesLoaded = snapshot.MicrosoftManagementDeploymentInProcLoaded && snapshot.WindowsPackageManagerLoaded; + if (knownModulesLoaded != expectExist) + { + std::cout << "Known modules were not in expected state [" << (expectExist ? "loaded" : "unloaded") << "]\n"; + result = false; + } + + auto coreApplicationProperties = winrt::Windows::ApplicationModel::Core::CoreApplication::Properties(); + + // COM statics + for (std::wstring_view item : { + L"WindowsPackageManager.CachedInstalledIndex"sv, + L"WindowsPackageManager.TerminationSignalHandler"sv, + }) + { + bool present = coreApplicationProperties.HasKey(item); + + if (present != expectExist) + { + std::cout << "CoreApplication property `" << winrt::to_string(item) << "` was not in expected state [" << (expectExist ? "should exist" : "should not exist") << "]\n"; + result = false; + } + } + + return result; + } + + // Look for the set of termination signal monitoring objects that should be present after we have spun everything up. + // Returns true if all objects are found in the expected state. + bool SearchForTerminationSignalObjects(bool expectExist) + { + bool result = true; + + // Shutdown monitoring window + bool foundWindow = false; + EnumWindows(CheckForWinGetWindow, reinterpret_cast(&foundWindow)); + + if (foundWindow != expectExist) + { + std::cout << "WinGet Window `WingetMessageOnlyWindow` was not in expected state [" << (expectExist ? "should exist" : "should not exist") << "]\n"; + result = false; + } + + return result; + } + + std::string GetBytesString(SIZE_T bytes) + { + constexpr SIZE_T s_kilo = 1024; + constexpr std::string_view s_sizes = "BKMG"sv; + size_t i = 0; + + while ((i + 1) < s_sizes.size() && bytes > s_kilo) + { + bytes /= s_kilo; + ++i; + } + + return std::to_string(bytes) + s_sizes[i]; + } + + const CLSID CLSID_PackageManager = { 0x2DDE4456, 0x64D9, 0x4673, 0x8F, 0x7E, 0xA4, 0xF1, 0x9A, 0x2E, 0x6C, 0xC3 }; // 2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3 + const CLSID CLSID_FindPackagesOptions = { 0x96B9A53A, 0x9228, 0x4DA0, 0xB0, 0x13, 0xBB, 0x1B, 0x20, 0x31, 0xAB, 0x3D }; // 96B9A53A-9228-4DA0-B013-BB1B2031AB3D + const CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x768318A6, 0x2EB5, 0x400D, 0x84, 0xD0, 0xDF, 0x35, 0x34, 0xC3, 0x0F, 0x5D }; // 768318A6-2EB5-400D-84D0-DF3534C30F5D + const CLSID CLSID_InstallOptions = { 0xE2AF3BA8, 0x8A88, 0x4766, 0x9D, 0xDA, 0xAE, 0x40, 0x13, 0xAD, 0xE2, 0x86 }; // E2AF3BA8-8A88-4766-9DDA-AE4013ADE286 + const CLSID CLSID_UninstallOptions = { 0x869CB959, 0xEB54, 0x425C, 0xA1, 0xE4, 0x1A, 0x1C, 0x29, 0x1C, 0x64, 0xE9 }; // 869CB959-EB54-425C-A1E4-1A1C291C64E9 + const CLSID CLSID_PackageMatchFilter = { 0x57DC8962, 0x7343, 0x42CD, 0xB9, 0x1C, 0x04, 0xF6, 0xA2, 0x5D, 0xB1, 0xD0 }; // 57DC8962-7343-42CD-B91C-04F6A25DB1D0 + const CLSID CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB + const CLSID CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 + const CLSID CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 + const CLSID CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} + const CLSID CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} + const CLSID CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} + + template + T CreatePackageManagerObject(ActivationType activationType, const CLSID& clsid) + { + if (ActivationType::ClassName == activationType) + { + return T{}; + } + else if (ActivationType::CoCreateInstance == activationType) + { + return winrt::create_instance(clsid); + } + + winrt::throw_hresult(E_UNEXPECTED); + } +} + +TestParameters::TestParameters(int argc, const char** argv) +{ + for (int i = 0; i < argc; ++i) + { + if ("-test"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + TestToRun = ToLower(argv[i]); + } + else if ("-com"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + ComInit = ParseComInitializationType(argv[i]); + } + else if ("-leak-com"sv == argv[i]) + { + LeakCOM = true; + } + else if ("-itr"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + Iterations = atoi(argv[i]); + } + else if ("-pkg"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + PackageName = argv[i]; + } + else if ("-src"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + SourceName = argv[i]; + } + else if ("-url"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + SourceURL = argv[i]; + } + else if ("-unload"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + UnloadBehavior = ParseUnloadBehavior(argv[i]); + } + else if ("-activation"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + ActivationType = ParseActivationType(argv[i]); + } + else if ("-keep-factories"sv == argv[i]) + { + SkipClearFactories = true; + } + else if ("-work-test-sleep"sv == argv[i]) + { + ADVANCE_ARG_PARAMETER + WorkTestSleepInterval = atoi(argv[i]); + } + else if ("-no-term"sv == argv[i]) + { + DisableTerminationSignals = true; + } + } +} + +void TestParameters::OutputDetails() const +{ + std::cout << "Running inproc testbed with:\n" + " COM Init : " << ComInit << "\n" + " Activate : " << ActivationType << "\n" + " Clear : " << std::boolalpha << !SkipClearFactories << "\n" + " Leak COM : " << std::boolalpha << LeakCOM << "\n" + " Unload : " << UnloadBehavior << "\n" + " Expect : " << std::boolalpha << UnloadExpected() << "\n" + " Test : " << TestToRun << "\n" + " Sleep : " << WorkTestSleepInterval << "\n" + " Package : " << PackageName << "\n" + " Source : " << SourceName << "\n" + " URL : " << SourceURL << "\n" + " Passes : " << Iterations << std::endl; +} + +bool TestParameters::InitializeTestState() const +{ + HRESULT hr = S_OK; + + if (ComInitializationType::STA == ComInit) + { + hr = RoInitialize(RO_INIT_SINGLETHREADED); + } + else if (ComInitializationType::MTA == ComInit) + { + hr = RoInitialize(RO_INIT_MULTITHREADED); + } + + if (FAILED(hr)) + { + std::cout << "RoInitialize returned " << hr << std::endl; + return false; + } + + if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior) + { + SetUnloadPreference(false); + } + + return true; +} + +bool TestParameters::InitializeIterationState() const +{ + InitializePackageManagerGlobals(); + + if (DisableTerminationSignals) + { + SetDisableTerminationSignals(true); + } + + return true; +} + +std::unique_ptr TestParameters::CreateTest() const +{ + if ("unload_check"sv == TestToRun) + { + return std::make_unique(*this); + } + else if ("install_detect"sv == TestToRun) + { + return std::make_unique(*this); + } + + return {}; +} + +void TestParameters::UninitializeTestState() const +{ + if (UnloadBehavior::AtUninitialize == UnloadBehavior) + { + SetUnloadPreference(true); + } + + if (!LeakCOM) + { + RoUninitialize(); + } +} + +bool TestParameters::UnloadExpected() const +{ + bool shouldUnload = true; + if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior || + (ActivationType::ClassName == ActivationType && SkipClearFactories)) + { + shouldUnload = false; + } + return shouldUnload; +} + +PackageManager TestParameters::CreatePackageManager() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_PackageManager); +} + +CreateCompositePackageCatalogOptions TestParameters::CreateCreateCompositePackageCatalogOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_CreateCompositePackageCatalogOptions); +} + +PackageMatchFilter TestParameters::CreatePackageMatchFilter() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_PackageMatchFilter); +} + +FindPackagesOptions TestParameters::CreateFindPackagesOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_FindPackagesOptions); +} + +DownloadOptions TestParameters::CreateDownloadOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_DownloadOptions); +} + +AddPackageCatalogOptions TestParameters::CreateAddPackageCatalogOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_AddPackageCatalogOptions); +} + +InstallOptions TestParameters::CreateInstallOptions() const +{ + return CreatePackageManagerObject(ActivationType, CLSID_InstallOptions); +} + +Snapshot::Snapshot() +{ + const DWORD processId = GetCurrentProcessId(); + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE, processId); + + // Count threads in this process + THREADENTRY32 threadEntry{}; + threadEntry.dwSize = sizeof(threadEntry); + + if (Thread32First(snapshot, &threadEntry)) + { + do + { + if (processId == threadEntry.th32OwnerProcessID) + { + ++ThreadCount; + } + } while (Thread32Next(snapshot, &threadEntry)); + } + + // Count modules + MODULEENTRY32 moduleEntry{}; + moduleEntry.dwSize = sizeof(moduleEntry); + + if (Module32First(snapshot, &moduleEntry)) + { + do + { + if (moduleEntry.szModule == L"Microsoft.Management.Deployment.InProc.dll"sv) + { + MicrosoftManagementDeploymentInProcLoaded = true; + } + else if (moduleEntry.szModule == L"WindowsPackageManager.dll"sv) + { + WindowsPackageManagerLoaded = true; + } + + ++ModuleCount; + } while (Module32Next(snapshot, &moduleEntry)); + } + + // Get memory stats + GetProcessMemoryInfo(GetCurrentProcess(), reinterpret_cast(&Memory), sizeof(Memory)); + + CloseHandle(snapshot); +} + +UnloadAndCheckForLeaks::UnloadAndCheckForLeaks(const TestParameters& parameters) : m_parameters(parameters) +{ +} + +bool UnloadAndCheckForLeaks::RunIterationWork() +{ + std::cout << "UnloadAndCheckForLeaks::RunIterationWork\n"; + return UsePackageManager(m_parameters); +} + +bool UnloadAndCheckForLeaks::RunIterationTest() +{ + std::cout << "UnloadAndCheckForLeaks::RunIterationTest\n"; + + Snapshot beforeUnload; + if (!SearchForWellKnownObjects(true, beforeUnload) || + !SearchForTerminationSignalObjects(!m_parameters.DisableTerminationSignals)) + { + return false; + } + + CoFreeUnusedLibrariesEx(0, 0); + + Snapshot afterUnload; + m_iterationSnapshots.emplace_back(beforeUnload, afterUnload); + + if (!SearchForWellKnownObjects(!m_parameters.UnloadExpected(), afterUnload) || + !SearchForTerminationSignalObjects(!m_parameters.UnloadExpected() && !m_parameters.DisableTerminationSignals)) + { + return false; + } + + return true; +} + +bool UnloadAndCheckForLeaks::RunFinal() +{ + constexpr std::streamsize s_columnWidth = 5; + + bool result = true; + + std::cout << "--- UnloadAndCheckForLeaks results ---\n"; + std::cout << std::setfill(' '); + + // --- Threads --- + std::cout << "Thread Count [Initial: " << m_initialSnapshot.ThreadCount << "]\n"; + + std::cout << "Iteration "; + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + std::cout << std::setw(s_columnWidth) << (i + 1); + } + std::cout << '\n'; + + std::cout << "Pre Unload "; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.first.ThreadCount; + } + std::cout << '\n'; + + std::cout << "Post Unload"; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.second.ThreadCount; + } + std::cout << '\n'; + + // Look for consistent increase in measured values + if (m_iterationSnapshots.size() > 1) + { + size_t previousValue = m_iterationSnapshots[0].second.ThreadCount; + bool consistentIncrease = true; + + for (size_t i = 1; i < m_iterationSnapshots.size(); ++i) + { + size_t currentValue = m_iterationSnapshots[i].second.ThreadCount; + if (currentValue > previousValue) + { + previousValue = currentValue; + } + else + { + consistentIncrease = false; + break; + } + } + + if (consistentIncrease) + { + std::cout << "Post unload thread count shows consistent increase; failing test.\n"; + result = false; + } + } + + // --- Modules --- + std::cout << "Module Count [Initial: " << m_initialSnapshot.ModuleCount << "]\n"; + + std::cout << "Iteration "; + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + std::cout << std::setw(s_columnWidth) << (i + 1); + } + std::cout << '\n'; + + std::cout << "Pre Unload "; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.first.ModuleCount; + } + std::cout << '\n'; + + std::cout << "Post Unload"; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << snapshot.second.ModuleCount; + } + std::cout << '\n'; + + // Look for modules not unloading + if (m_parameters.UnloadExpected() && m_iterationSnapshots.size() > 1) + { + bool noUnloadFound = false; + + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + if (m_iterationSnapshots[i].first.ModuleCount == m_iterationSnapshots[i].second.ModuleCount) + { + noUnloadFound = true; + } + } + + if (noUnloadFound) + { + std::cout << "Module count did not decrease during at least one iteration; failing test.\n"; + result = false; + } + } + + // --- Memory --- + std::cout << "Private Usage [Initial: " << GetBytesString(m_initialSnapshot.Memory.PrivateUsage) << "]\n"; + + std::cout << "Iteration "; + for (size_t i = 0; i < m_iterationSnapshots.size(); ++i) + { + std::cout << std::setw(s_columnWidth) << (i + 1); + } + std::cout << '\n'; + + std::cout << "Pre Unload "; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << GetBytesString(snapshot.first.Memory.PrivateUsage); + } + std::cout << '\n'; + + std::cout << "Post Unload"; + for (const auto& snapshot : m_iterationSnapshots) + { + std::cout << std::setw(s_columnWidth) << GetBytesString(snapshot.second.Memory.PrivateUsage); + } + std::cout << '\n'; + + return result; +} + +InstallForSystem_DetectPresence::InstallForSystem_DetectPresence(const TestParameters& parameters) : m_parameters(parameters) +{ +} + +bool InstallForSystem_DetectPresence::RunIterationWork() +{ + std::cout << "Before installing, the detection state was: " << std::boolalpha << DetectForSystem(m_parameters) << '\n'; + + return InstallForSystem(m_parameters); +} + +bool InstallForSystem_DetectPresence::RunIterationTest() +{ + bool result = DetectForSystem(m_parameters); + std::cout << "After installing, the detection state was: " << std::boolalpha << result << '\n'; + return result; +} + +bool InstallForSystem_DetectPresence::RunFinal() +{ + return true; +} diff --git a/src/ComInprocTestbed/Tests.h b/src/ComInprocTestbed/Tests.h index 7de44c4af1..2b57db0d19 100644 --- a/src/ComInprocTestbed/Tests.h +++ b/src/ComInprocTestbed/Tests.h @@ -1,125 +1,125 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -// Represents a test that will be performed. -struct ITest -{ - virtual ~ITest() = default; - - // Runs an iteration of the work. Performed at the beginning of the iteration. - virtual bool RunIterationWork() = 0; - - // Runs an iteration of the test. Performed at the end of the iteration. - virtual bool RunIterationTest() = 0; - - // Performs the final test validation. - virtual bool RunFinal() = 0; -}; - -enum class ComInitializationType -{ - STA, - MTA, -}; - -enum class UnloadBehavior -{ - Allow, - AtUninitialize, - Never, -}; - -enum class ActivationType -{ - ClassName, - CoCreateInstance, -}; - -// Test parameters from command line -struct TestParameters -{ - TestParameters(int argc, const char** argv); - - void OutputDetails() const; - - bool InitializeTestState() const; - - bool InitializeIterationState() const; - - std::unique_ptr CreateTest() const; - - void UninitializeTestState() const; - - // Determines if we expect COM to unload the module based on inputs. - bool UnloadExpected() const; - - winrt::Microsoft::Management::Deployment::PackageManager CreatePackageManager() const; - winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() const; - winrt::Microsoft::Management::Deployment::PackageMatchFilter CreatePackageMatchFilter() const; - winrt::Microsoft::Management::Deployment::FindPackagesOptions CreateFindPackagesOptions() const; - winrt::Microsoft::Management::Deployment::DownloadOptions CreateDownloadOptions() const; - winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions CreateAddPackageCatalogOptions() const; - winrt::Microsoft::Management::Deployment::InstallOptions CreateInstallOptions() const; - - std::string TestToRun; - ComInitializationType ComInit = ComInitializationType::MTA; - bool LeakCOM = false; - int Iterations = 1; - std::string PackageName = "Microsoft.Edit"; - std::string SourceName = "winget"; - std::string SourceURL; - UnloadBehavior UnloadBehavior = UnloadBehavior::Allow; - ActivationType ActivationType = ActivationType::ClassName; - bool SkipClearFactories = false; - DWORD WorkTestSleepInterval = 0; - bool DisableTerminationSignals = false; -}; - -// Captures a snapshot of current resource usage. -struct Snapshot -{ - Snapshot(); - - size_t ThreadCount = 0; - size_t ModuleCount = 0; - bool MicrosoftManagementDeploymentInProcLoaded = false; - bool WindowsPackageManagerLoaded = false; - PROCESS_MEMORY_COUNTERS_EX2 Memory{}; -}; - -// A test that unloads the COM module and looks for resources that were not released. -struct UnloadAndCheckForLeaks : public ITest -{ - UnloadAndCheckForLeaks(const TestParameters& parameters); - - bool RunIterationWork() override; - - bool RunIterationTest() override; - - bool RunFinal() override; - -private: - const TestParameters& m_parameters; - Snapshot m_initialSnapshot; - std::vector> m_iterationSnapshots; -}; - -// A test that installs the package machine wide and then attempts to detect that it is installed. -struct InstallForSystem_DetectPresence : public ITest -{ - InstallForSystem_DetectPresence(const TestParameters& parameters); - - bool RunIterationWork() override; - - bool RunIterationTest() override; - - bool RunFinal() override; - -private: - const TestParameters& m_parameters; -}; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +// Represents a test that will be performed. +struct ITest +{ + virtual ~ITest() = default; + + // Runs an iteration of the work. Performed at the beginning of the iteration. + virtual bool RunIterationWork() = 0; + + // Runs an iteration of the test. Performed at the end of the iteration. + virtual bool RunIterationTest() = 0; + + // Performs the final test validation. + virtual bool RunFinal() = 0; +}; + +enum class ComInitializationType +{ + STA, + MTA, +}; + +enum class UnloadBehavior +{ + Allow, + AtUninitialize, + Never, +}; + +enum class ActivationType +{ + ClassName, + CoCreateInstance, +}; + +// Test parameters from command line +struct TestParameters +{ + TestParameters(int argc, const char** argv); + + void OutputDetails() const; + + bool InitializeTestState() const; + + bool InitializeIterationState() const; + + std::unique_ptr CreateTest() const; + + void UninitializeTestState() const; + + // Determines if we expect COM to unload the module based on inputs. + bool UnloadExpected() const; + + winrt::Microsoft::Management::Deployment::PackageManager CreatePackageManager() const; + winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() const; + winrt::Microsoft::Management::Deployment::PackageMatchFilter CreatePackageMatchFilter() const; + winrt::Microsoft::Management::Deployment::FindPackagesOptions CreateFindPackagesOptions() const; + winrt::Microsoft::Management::Deployment::DownloadOptions CreateDownloadOptions() const; + winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions CreateAddPackageCatalogOptions() const; + winrt::Microsoft::Management::Deployment::InstallOptions CreateInstallOptions() const; + + std::string TestToRun; + ComInitializationType ComInit = ComInitializationType::MTA; + bool LeakCOM = false; + int Iterations = 1; + std::string PackageName = "Microsoft.Edit"; + std::string SourceName = "winget"; + std::string SourceURL; + UnloadBehavior UnloadBehavior = UnloadBehavior::Allow; + ActivationType ActivationType = ActivationType::ClassName; + bool SkipClearFactories = false; + DWORD WorkTestSleepInterval = 0; + bool DisableTerminationSignals = false; +}; + +// Captures a snapshot of current resource usage. +struct Snapshot +{ + Snapshot(); + + size_t ThreadCount = 0; + size_t ModuleCount = 0; + bool MicrosoftManagementDeploymentInProcLoaded = false; + bool WindowsPackageManagerLoaded = false; + PROCESS_MEMORY_COUNTERS_EX2 Memory{}; +}; + +// A test that unloads the COM module and looks for resources that were not released. +struct UnloadAndCheckForLeaks : public ITest +{ + UnloadAndCheckForLeaks(const TestParameters& parameters); + + bool RunIterationWork() override; + + bool RunIterationTest() override; + + bool RunFinal() override; + +private: + const TestParameters& m_parameters; + Snapshot m_initialSnapshot; + std::vector> m_iterationSnapshots; +}; + +// A test that installs the package machine wide and then attempts to detect that it is installed. +struct InstallForSystem_DetectPresence : public ITest +{ + InstallForSystem_DetectPresence(const TestParameters& parameters); + + bool RunIterationWork() override; + + bool RunIterationTest() override; + + bool RunFinal() override; + +private: + const TestParameters& m_parameters; +}; diff --git a/src/ComInprocTestbed/main.cpp b/src/ComInprocTestbed/main.cpp index 1210ba0ae7..3c00d14e35 100644 --- a/src/ComInprocTestbed/main.cpp +++ b/src/ComInprocTestbed/main.cpp @@ -1,76 +1,76 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackageManager.h" -#include "Tests.h" - -using namespace std::string_view_literals; - -int main(int argc, const char** argv) try -{ - const TestParameters testParameters(argc, argv); - testParameters.OutputDetails(); - if (!testParameters.InitializeTestState()) - { - return 2; - } - - auto test = testParameters.CreateTest(); - - for (int i = 0; i < testParameters.Iterations; ++i) - { - std::cout << "Begin iteration " << (i + 1) << std::endl; - - if (!testParameters.InitializeIterationState()) - { - return 2; - } - - if (test && !test->RunIterationWork()) - { - return 3; - } - - if (!testParameters.SkipClearFactories) - { - winrt::clear_factory_cache(); - } - - if (testParameters.WorkTestSleepInterval) - { - Sleep(testParameters.WorkTestSleepInterval); - } - - if (test && !test->RunIterationTest()) - { - return 4; - } - - std::cout << "Iteration " << (i + 1) << " completed" << std::endl; - } - - if (test && !test->RunFinal()) - { - return 5; - } - - testParameters.UninitializeTestState(); - - std::cout << "Tests completed" << std::endl; - return 0; -} -catch (const std::exception& e) -{ - std::cout << "Caught std exception: " << e.what() << std::endl; - return 1; -} -catch (const winrt::hresult_error& hre) -{ - std::cout << "Caught winrt exception: " << winrt::to_string(hre.message()) << std::endl; - return 1; -} -catch (...) -{ - std::cout << "Caught unknown exception" << std::endl; - return 1; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageManager.h" +#include "Tests.h" + +using namespace std::string_view_literals; + +int main(int argc, const char** argv) try +{ + const TestParameters testParameters(argc, argv); + testParameters.OutputDetails(); + if (!testParameters.InitializeTestState()) + { + return 2; + } + + auto test = testParameters.CreateTest(); + + for (int i = 0; i < testParameters.Iterations; ++i) + { + std::cout << "Begin iteration " << (i + 1) << std::endl; + + if (!testParameters.InitializeIterationState()) + { + return 2; + } + + if (test && !test->RunIterationWork()) + { + return 3; + } + + if (!testParameters.SkipClearFactories) + { + winrt::clear_factory_cache(); + } + + if (testParameters.WorkTestSleepInterval) + { + Sleep(testParameters.WorkTestSleepInterval); + } + + if (test && !test->RunIterationTest()) + { + return 4; + } + + std::cout << "Iteration " << (i + 1) << " completed" << std::endl; + } + + if (test && !test->RunFinal()) + { + return 5; + } + + testParameters.UninitializeTestState(); + + std::cout << "Tests completed" << std::endl; + return 0; +} +catch (const std::exception& e) +{ + std::cout << "Caught std exception: " << e.what() << std::endl; + return 1; +} +catch (const winrt::hresult_error& hre) +{ + std::cout << "Caught winrt exception: " << winrt::to_string(hre.message()) << std::endl; + return 1; +} +catch (...) +{ + std::cout << "Caught unknown exception" << std::endl; + return 1; +} diff --git a/src/ComInprocTestbed/packages.config b/src/ComInprocTestbed/packages.config index f229371b33..f32f48b009 100644 --- a/src/ComInprocTestbed/packages.config +++ b/src/ComInprocTestbed/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/ComInprocTestbed/pch.h b/src/ComInprocTestbed/pch.h index e6b4a93660..f5a1bad9ee 100644 --- a/src/ComInprocTestbed/pch.h +++ b/src/ComInprocTestbed/pch.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#define NOMINMAX -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define NOMINMAX +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj b/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj index 07732954e1..35c1060c2f 100644 --- a/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj +++ b/src/ConfigurationRemotingServer/ConfigurationRemotingServer.csproj @@ -1,52 +1,52 @@ - - - - Exe - net8.0-windows10.0.26100.0 - enable - enable - 10.0.17763.0 - x64;x86;arm64 - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - true - win-x64;win-x86;win-arm64 - - - - win-x64 - - - - win-x86 - - - - win-arm64 - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Exe + net8.0-windows10.0.26100.0 + enable + enable + 10.0.17763.0 + x64;x86;arm64 + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + true + win-x64;win-x86;win-arm64 + + + + win-x64 + + + + win-x86 + + + + win-arm64 + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ConfigurationRemotingServer/EnvironmentChangeListener.cs b/src/ConfigurationRemotingServer/EnvironmentChangeListener.cs index 7c7bb3c287..03394d2520 100644 --- a/src/ConfigurationRemotingServer/EnvironmentChangeListener.cs +++ b/src/ConfigurationRemotingServer/EnvironmentChangeListener.cs @@ -1,180 +1,180 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -using System.Runtime.InteropServices; -using System.Threading; - -namespace ConfigurationRemotingServer -{ - internal class EnvironmentChangeListener - { - // Window message constants - private const int WM_SETTINGCHANGE = 0x001A; - private const int WM_QUIT = 0x0012; - - // User32.dll imports - [DllImport("user32.dll")] - private static extern IntPtr CreateWindowEx( - uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, - int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, - IntPtr hInstance, IntPtr lpParam); - - [DllImport("user32.dll")] - private static extern bool DestroyWindow(IntPtr hWnd); - - [DllImport("user32.dll")] - private static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - - [DllImport("user32.dll")] - private static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); - - [DllImport("user32.dll")] - private static extern bool PostMessageW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - - [DllImport("user32.dll")] - private static extern bool TranslateMessage(ref MSG lpMsg); - - [DllImport("user32.dll")] - private static extern IntPtr DispatchMessage(ref MSG lpMsg); - - [DllImport("user32.dll")] - private static extern ushort RegisterClass(ref WNDCLASS lpWndClass); - - [DllImport("kernel32.dll")] - private static extern IntPtr GetModuleHandle(string? lpModuleName); - - // Windows message structure - [StructLayout(LayoutKind.Sequential)] - public struct MSG - { - public IntPtr hwnd; - public uint message; - public IntPtr wParam; - public IntPtr lParam; - public uint time; - public POINT pt; - public uint lPrivate; - } - - [StructLayout(LayoutKind.Sequential)] - public struct POINT - { - public int x; - public int y; - } - - // Window class structure - [StructLayout(LayoutKind.Sequential)] - public struct WNDCLASS - { - public uint style; - public IntPtr lpfnWndProc; - public int cbClsExtra; - public int cbWndExtra; - public IntPtr hInstance; - public IntPtr hIcon; - public IntPtr hCursor; - public IntPtr hBackground; - [MarshalAs(UnmanagedType.LPStr)] - public string? lpszMenuName; - [MarshalAs(UnmanagedType.LPStr)] - public string lpszClassName; - } - - // Window procedure delegate - private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - // Keep a reference to the delegate to prevent it from being garbage collected - private static WndProcDelegate? _wndProcDelegate; - - private IntPtr _hwnd; - private Thread _thread; - - public delegate void EnvironmentChangedEventHandler(); - public static event EnvironmentChangedEventHandler? EnvironmentChanged; - - public EnvironmentChangeListener() - { - _thread = new Thread(MessageLoop); - _thread.Start(); - } - - public void Stop() - { - if (PostMessageW(_hwnd, WM_QUIT, IntPtr.Zero, IntPtr.Zero)) - { - _thread.Join(); - } - } - - private void MessageLoop() - { - // Register window class - string className = "EnvironmentMonitorClass"; - _wndProcDelegate = WndProc; - - WNDCLASS wndClass = new WNDCLASS - { - style = 0, - lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), - cbClsExtra = 0, - cbWndExtra = 0, - hInstance = GetModuleHandle(null), - hIcon = IntPtr.Zero, - hCursor = IntPtr.Zero, - hBackground = IntPtr.Zero, - lpszMenuName = "", - lpszClassName = className - }; - - RegisterClass(ref wndClass); - - // Create hidden window - const uint WS_OVERLAPPED = 0x00000000; - const uint WS_EX_TOOL_WINDOW = 0x00000080; - - _hwnd = CreateWindowEx( - WS_EX_TOOL_WINDOW, - className, - "Environment Monitor", - WS_OVERLAPPED, - 0, 0, 0, 0, - IntPtr.Zero, IntPtr.Zero, - GetModuleHandle(null), IntPtr.Zero); - - if (_hwnd == IntPtr.Zero) - { - return; - } - - MSG msg; - while (GetMessage(out msg, IntPtr.Zero, 0, 0)) - { - TranslateMessage(ref msg); - DispatchMessage(ref msg); - } - - // Clean up - DestroyWindow(_hwnd); - } - - private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) - { - switch (msg) - { - case WM_SETTINGCHANGE: - // Check if this is an environment variable change notification - string? msgInfo = Marshal.PtrToStringAnsi(lParam); - if (msgInfo == "Environment") - { - if (EnvironmentChanged != null) - { - EnvironmentChanged.Invoke(); - } - } - break; - } - - return DefWindowProc(hWnd, msg, wParam, lParam); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using System.Runtime.InteropServices; +using System.Threading; + +namespace ConfigurationRemotingServer +{ + internal class EnvironmentChangeListener + { + // Window message constants + private const int WM_SETTINGCHANGE = 0x001A; + private const int WM_QUIT = 0x0012; + + // User32.dll imports + [DllImport("user32.dll")] + private static extern IntPtr CreateWindowEx( + uint dwExStyle, string lpClassName, string lpWindowName, uint dwStyle, + int x, int y, int nWidth, int nHeight, IntPtr hWndParent, IntPtr hMenu, + IntPtr hInstance, IntPtr lpParam); + + [DllImport("user32.dll")] + private static extern bool DestroyWindow(IntPtr hWnd); + + [DllImport("user32.dll")] + private static extern IntPtr DefWindowProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern bool GetMessage(out MSG lpMsg, IntPtr hWnd, uint wMsgFilterMin, uint wMsgFilterMax); + + [DllImport("user32.dll")] + private static extern bool PostMessageW(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + + [DllImport("user32.dll")] + private static extern bool TranslateMessage(ref MSG lpMsg); + + [DllImport("user32.dll")] + private static extern IntPtr DispatchMessage(ref MSG lpMsg); + + [DllImport("user32.dll")] + private static extern ushort RegisterClass(ref WNDCLASS lpWndClass); + + [DllImport("kernel32.dll")] + private static extern IntPtr GetModuleHandle(string? lpModuleName); + + // Windows message structure + [StructLayout(LayoutKind.Sequential)] + public struct MSG + { + public IntPtr hwnd; + public uint message; + public IntPtr wParam; + public IntPtr lParam; + public uint time; + public POINT pt; + public uint lPrivate; + } + + [StructLayout(LayoutKind.Sequential)] + public struct POINT + { + public int x; + public int y; + } + + // Window class structure + [StructLayout(LayoutKind.Sequential)] + public struct WNDCLASS + { + public uint style; + public IntPtr lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hBackground; + [MarshalAs(UnmanagedType.LPStr)] + public string? lpszMenuName; + [MarshalAs(UnmanagedType.LPStr)] + public string lpszClassName; + } + + // Window procedure delegate + private delegate IntPtr WndProcDelegate(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); + // Keep a reference to the delegate to prevent it from being garbage collected + private static WndProcDelegate? _wndProcDelegate; + + private IntPtr _hwnd; + private Thread _thread; + + public delegate void EnvironmentChangedEventHandler(); + public static event EnvironmentChangedEventHandler? EnvironmentChanged; + + public EnvironmentChangeListener() + { + _thread = new Thread(MessageLoop); + _thread.Start(); + } + + public void Stop() + { + if (PostMessageW(_hwnd, WM_QUIT, IntPtr.Zero, IntPtr.Zero)) + { + _thread.Join(); + } + } + + private void MessageLoop() + { + // Register window class + string className = "EnvironmentMonitorClass"; + _wndProcDelegate = WndProc; + + WNDCLASS wndClass = new WNDCLASS + { + style = 0, + lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_wndProcDelegate), + cbClsExtra = 0, + cbWndExtra = 0, + hInstance = GetModuleHandle(null), + hIcon = IntPtr.Zero, + hCursor = IntPtr.Zero, + hBackground = IntPtr.Zero, + lpszMenuName = "", + lpszClassName = className + }; + + RegisterClass(ref wndClass); + + // Create hidden window + const uint WS_OVERLAPPED = 0x00000000; + const uint WS_EX_TOOL_WINDOW = 0x00000080; + + _hwnd = CreateWindowEx( + WS_EX_TOOL_WINDOW, + className, + "Environment Monitor", + WS_OVERLAPPED, + 0, 0, 0, 0, + IntPtr.Zero, IntPtr.Zero, + GetModuleHandle(null), IntPtr.Zero); + + if (_hwnd == IntPtr.Zero) + { + return; + } + + MSG msg; + while (GetMessage(out msg, IntPtr.Zero, 0, 0)) + { + TranslateMessage(ref msg); + DispatchMessage(ref msg); + } + + // Clean up + DestroyWindow(_hwnd); + } + + private static IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + switch (msg) + { + case WM_SETTINGCHANGE: + // Check if this is an environment variable change notification + string? msgInfo = Marshal.PtrToStringAnsi(lParam); + if (msgInfo == "Environment") + { + if (EnvironmentChanged != null) + { + EnvironmentChanged.Invoke(); + } + } + break; + } + + return DefWindowProc(hWnd, msg, wParam, lParam); + } + } +} diff --git a/src/ConfigurationRemotingServer/Program.cs b/src/ConfigurationRemotingServer/Program.cs index 19c65c8e49..00774b9412 100644 --- a/src/ConfigurationRemotingServer/Program.cs +++ b/src/ConfigurationRemotingServer/Program.cs @@ -1,312 +1,312 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -using System.Reflection; -using System.Runtime.InteropServices; -using System.Runtime.Loader; -using System.Text; -using System.Text.Json; -using System.Text.Json.Serialization; -using Microsoft.Management.Configuration; -using Microsoft.Management.Configuration.Processor; -using Microsoft.Management.Configuration.Processor.Helpers; -using WinRT; -using IConfigurationSetProcessorFactory = global::Microsoft.Management.Configuration.IConfigurationSetProcessorFactory; - -namespace ConfigurationRemotingServer -{ - ///

- /// Custom assembly load context. - /// - internal class NativeAssemblyLoadContext : AssemblyLoadContext - { - private static readonly string PackageRootPath; - - private static readonly NativeAssemblyLoadContext NativeALC = new(); - - static NativeAssemblyLoadContext() - { - var self = typeof(NativeAssemblyLoadContext).Assembly; - PackageRootPath = Path.Combine( - Path.GetDirectoryName(self.Location)!, - ".."); - } - - private NativeAssemblyLoadContext() - : base("NativeAssemblyLoadContext", isCollectible: false) - { - } - - /// - /// Handler to resolve unmanaged assemblies. - /// - /// Assembly load context. - /// Assembly name. - /// The assembly, null if not in our assembly location. - internal static IntPtr ResolvingUnmanagedHandler(Assembly context, string name) - { - if (name.Equals("WindowsPackageManager.dll", StringComparison.OrdinalIgnoreCase)) - { - return NativeALC.LoadUnmanagedDll(name); - } - - return IntPtr.Zero; - } - - /// - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string path = Path.Combine(PackageRootPath, unmanagedDllName); - if (File.Exists(path)) - { - return this.LoadUnmanagedDllFromPath(path); - } - - return IntPtr.Zero; - } - } - - internal class Program - { - private const string CommandLineSectionSeparator = "~~~~~~"; - private const string ExternalModulesName = "ExternalModules"; - - static int Main(string[] args) - { - // Remove any attached console to prevent modules (or their actions) from writing to our console. - FreeConsole(); - - // Help find WindowsPackageManager.dll - AssemblyLoadContext.Default.ResolvingUnmanagedDll += NativeAssemblyLoadContext.ResolvingUnmanagedHandler; - - string staticsCallback = args[1]; - - // Listen for setting change message and update PATH if needed. - EnvironmentChangeListener.EnvironmentChanged += OnEnvironmentChanged; - EnvironmentChangeListener environmentChangeListener = new EnvironmentChangeListener(); - - try - { - string completionEventName = args[2]; - uint parentProcessId = uint.Parse(args[3]); - string processorEngine = args[4]; - - ConfigurationSet? limitationSet = null; - LimitationSetMetadata? limitationSetMetadata = null; - - // Parse limitation set if applicable. - // The format will be: - // ~~~~~~ ~~~~~~ - // Metadata json format: - // { - // "path": "C:\full\file\path.yaml" - // } - // If a limitation set is provided, the processor will be limited - // to only work on units defined inside the limitation set. - var commandPtr = GetCommandLineW(); - var commandStr = Marshal.PtrToStringUni(commandPtr) ?? string.Empty; - - // In case the limitation set content contains the separator, we'll not use Split method. - var firstSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator); - if (firstSeparatorIndex > 0) - { - var secondSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator, firstSeparatorIndex + CommandLineSectionSeparator.Length); - if (secondSeparatorIndex <= 0) - { - throw new ArgumentException("The input command contains only one separator string."); - } - - // Parse limitation set. - byte[] limitationSetBytes = Encoding.UTF8.GetBytes(commandStr.Substring(secondSeparatorIndex + CommandLineSectionSeparator.Length)); - MemoryStream memoryStream = new MemoryStream(); - memoryStream.Write(limitationSetBytes); - memoryStream.Flush(); - memoryStream.Seek(0, SeekOrigin.Begin); - ConfigurationProcessor processor = new ConfigurationProcessor((IConfigurationSetProcessorFactory?)null); - var limitationSetResult = processor.OpenConfigurationSet(memoryStream.AsInputStream()); - memoryStream.Close(); - - if (limitationSetResult.ResultCode != null) - { - throw limitationSetResult.ResultCode; - } - - limitationSet = limitationSetResult.Set; - if (limitationSet == null) - { - throw new ArgumentException("The limitation set cannot be parsed."); - } - - // Now parse metadata json and update the limitation set - limitationSetMetadata = JsonSerializer.Deserialize(commandStr.Substring( - firstSeparatorIndex + CommandLineSectionSeparator.Length, - secondSeparatorIndex - firstSeparatorIndex - CommandLineSectionSeparator.Length)); - - if (limitationSetMetadata != null) - { - limitationSet.Path = limitationSetMetadata.Path; - } - } - - IConfigurationSetProcessorFactory factory = CreateFactory(processorEngine, limitationSet, limitationSetMetadata); - IObjectReference factoryInterface = MarshalInterface.CreateMarshaler(factory); - - return WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(0, factoryInterface.ThisPtr, staticsCallback, completionEventName, parentProcessId); - } - catch (Exception ex) - { - WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(ex.HResult, IntPtr.Zero, staticsCallback, null, 0); - return ex.HResult; - } - finally - { - environmentChangeListener.Stop(); - } - } - - private static void OnEnvironmentChanged() - { - PathEnvironmentVariableHandler.UpdatePath(); - } - - private class LimitationSetMetadata - { - [JsonPropertyName("path")] - public string Path { get; set; } = string.Empty; - - [JsonPropertyName("modulePath")] - public string? ModulePath { get; set; } = null; - - [JsonPropertyName("processorPath")] - public string? ProcessorPath { get; set; } = null; - - [JsonPropertyName("processorPathHash")] - public string? ProcessorPathHash { get; set; } = null; - - [JsonPropertyName("processorPathIsAlias")] - public bool? ProcessorPathIsAlias { get; set; } = null; - } - - private static IConfigurationSetProcessorFactory CreateFactory(string processorEngine, ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) - { - switch (processorEngine) - { - case "pwsh": - return CreatePowerShellFactory(limitationSet, limitationSetMetadata); - case "dscv3": - return CreateDSCv3Factory(limitationSet, limitationSetMetadata); - } - - throw new NotImplementedException($"Processor engine unknown: {processorEngine}"); - } - - private static IConfigurationSetProcessorFactory CreatePowerShellFactory(ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) - { - PowerShellConfigurationSetProcessorFactory factory = new PowerShellConfigurationSetProcessorFactory(); - - // Set default properties. - var externalModulesPath = GetExternalModulesPath(); - if (string.IsNullOrWhiteSpace(externalModulesPath)) - { - throw new DirectoryNotFoundException("Failed to get ExternalModules."); - } - - // Set as implicit module paths so it will be always included in AdditionalModulePaths - factory.ImplicitModulePaths = new List() { externalModulesPath }; - factory.ProcessorType = PowerShellConfigurationProcessorType.Hosted; - - if (limitationSetMetadata != null) - { - if (limitationSetMetadata.ModulePath != null) - { - PowerShellConfigurationProcessorLocation parsedLocation = PowerShellConfigurationProcessorLocation.Default; - if (Enum.TryParse(limitationSetMetadata.ModulePath, out parsedLocation)) - { - factory.Location = parsedLocation; - } - else - { - factory.Location = PowerShellConfigurationProcessorLocation.Custom; - factory.CustomLocation = limitationSetMetadata.ModulePath; - } - } - } - - // Apply limitation set and thereby disable changing properties. - if (limitationSet != null) - { - factory.LimitationSet = limitationSet; - } - - return factory; - } - - private static IConfigurationSetProcessorFactory CreateDSCv3Factory(ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) - { - DSCv3ConfigurationSetProcessorFactory factory = new DSCv3ConfigurationSetProcessorFactory(); - - if (limitationSetMetadata != null) - { - if (limitationSetMetadata.ProcessorPath != null) - { - if (limitationSetMetadata.ProcessorPathHash == null) - { - throw new InvalidOperationException("A custom processor path was provided without a hash for integrity verification."); - } - - // Set hash and alias before path so they are available when the path is verified on first use. - factory.DscExecutablePathHash = limitationSetMetadata.ProcessorPathHash; - factory.DscExecutablePathIsAlias = limitationSetMetadata.ProcessorPathIsAlias ?? false; - factory.DscExecutablePath = limitationSetMetadata.ProcessorPath; - } - else - { - // Require that the path to the DSC executable be presented to the user in limitation mode. - // This helps prevent path attacks against an elevated process (as long as the user checks the value). - throw new ArgumentNullException("The path to the DSC executable must be supplied in limitation mode."); - } - } - - // Apply limitation set and thereby disable changing properties. - if (limitationSet != null) - { - factory.LimitationSet = limitationSet; - } - - return factory; - } - - private static string GetExternalModulesPath() - { - var currentAssemblyDirectoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - if (currentAssemblyDirectoryPath != null) - { - var packageRootPath = Directory.GetParent(currentAssemblyDirectoryPath)?.FullName; - if (packageRootPath != null) - { - var externalModulesPath = Path.Combine(packageRootPath, ExternalModulesName); - if (Directory.Exists(externalModulesPath)) - { - return externalModulesPath; - } - } - } - - return string.Empty; - } - - [DllImport("WindowsPackageManager.dll")] - private static extern int WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization( - int result, - IntPtr factory, - [MarshalAs(UnmanagedType.LPWStr)]string staticsCallback, - [MarshalAs(UnmanagedType.LPWStr)]string? completionEventName, - uint parentProcessId); - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] - private static extern IntPtr GetCommandLineW(); - - [DllImport("kernel32.dll")] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool FreeConsole(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +using System.Reflection; +using System.Runtime.InteropServices; +using System.Runtime.Loader; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using Microsoft.Management.Configuration; +using Microsoft.Management.Configuration.Processor; +using Microsoft.Management.Configuration.Processor.Helpers; +using WinRT; +using IConfigurationSetProcessorFactory = global::Microsoft.Management.Configuration.IConfigurationSetProcessorFactory; + +namespace ConfigurationRemotingServer +{ + /// + /// Custom assembly load context. + /// + internal class NativeAssemblyLoadContext : AssemblyLoadContext + { + private static readonly string PackageRootPath; + + private static readonly NativeAssemblyLoadContext NativeALC = new(); + + static NativeAssemblyLoadContext() + { + var self = typeof(NativeAssemblyLoadContext).Assembly; + PackageRootPath = Path.Combine( + Path.GetDirectoryName(self.Location)!, + ".."); + } + + private NativeAssemblyLoadContext() + : base("NativeAssemblyLoadContext", isCollectible: false) + { + } + + /// + /// Handler to resolve unmanaged assemblies. + /// + /// Assembly load context. + /// Assembly name. + /// The assembly, null if not in our assembly location. + internal static IntPtr ResolvingUnmanagedHandler(Assembly context, string name) + { + if (name.Equals("WindowsPackageManager.dll", StringComparison.OrdinalIgnoreCase)) + { + return NativeALC.LoadUnmanagedDll(name); + } + + return IntPtr.Zero; + } + + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string path = Path.Combine(PackageRootPath, unmanagedDllName); + if (File.Exists(path)) + { + return this.LoadUnmanagedDllFromPath(path); + } + + return IntPtr.Zero; + } + } + + internal class Program + { + private const string CommandLineSectionSeparator = "~~~~~~"; + private const string ExternalModulesName = "ExternalModules"; + + static int Main(string[] args) + { + // Remove any attached console to prevent modules (or their actions) from writing to our console. + FreeConsole(); + + // Help find WindowsPackageManager.dll + AssemblyLoadContext.Default.ResolvingUnmanagedDll += NativeAssemblyLoadContext.ResolvingUnmanagedHandler; + + string staticsCallback = args[1]; + + // Listen for setting change message and update PATH if needed. + EnvironmentChangeListener.EnvironmentChanged += OnEnvironmentChanged; + EnvironmentChangeListener environmentChangeListener = new EnvironmentChangeListener(); + + try + { + string completionEventName = args[2]; + uint parentProcessId = uint.Parse(args[3]); + string processorEngine = args[4]; + + ConfigurationSet? limitationSet = null; + LimitationSetMetadata? limitationSetMetadata = null; + + // Parse limitation set if applicable. + // The format will be: + // ~~~~~~ ~~~~~~ + // Metadata json format: + // { + // "path": "C:\full\file\path.yaml" + // } + // If a limitation set is provided, the processor will be limited + // to only work on units defined inside the limitation set. + var commandPtr = GetCommandLineW(); + var commandStr = Marshal.PtrToStringUni(commandPtr) ?? string.Empty; + + // In case the limitation set content contains the separator, we'll not use Split method. + var firstSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator); + if (firstSeparatorIndex > 0) + { + var secondSeparatorIndex = commandStr.IndexOf(CommandLineSectionSeparator, firstSeparatorIndex + CommandLineSectionSeparator.Length); + if (secondSeparatorIndex <= 0) + { + throw new ArgumentException("The input command contains only one separator string."); + } + + // Parse limitation set. + byte[] limitationSetBytes = Encoding.UTF8.GetBytes(commandStr.Substring(secondSeparatorIndex + CommandLineSectionSeparator.Length)); + MemoryStream memoryStream = new MemoryStream(); + memoryStream.Write(limitationSetBytes); + memoryStream.Flush(); + memoryStream.Seek(0, SeekOrigin.Begin); + ConfigurationProcessor processor = new ConfigurationProcessor((IConfigurationSetProcessorFactory?)null); + var limitationSetResult = processor.OpenConfigurationSet(memoryStream.AsInputStream()); + memoryStream.Close(); + + if (limitationSetResult.ResultCode != null) + { + throw limitationSetResult.ResultCode; + } + + limitationSet = limitationSetResult.Set; + if (limitationSet == null) + { + throw new ArgumentException("The limitation set cannot be parsed."); + } + + // Now parse metadata json and update the limitation set + limitationSetMetadata = JsonSerializer.Deserialize(commandStr.Substring( + firstSeparatorIndex + CommandLineSectionSeparator.Length, + secondSeparatorIndex - firstSeparatorIndex - CommandLineSectionSeparator.Length)); + + if (limitationSetMetadata != null) + { + limitationSet.Path = limitationSetMetadata.Path; + } + } + + IConfigurationSetProcessorFactory factory = CreateFactory(processorEngine, limitationSet, limitationSetMetadata); + IObjectReference factoryInterface = MarshalInterface.CreateMarshaler(factory); + + return WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(0, factoryInterface.ThisPtr, staticsCallback, completionEventName, parentProcessId); + } + catch (Exception ex) + { + WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization(ex.HResult, IntPtr.Zero, staticsCallback, null, 0); + return ex.HResult; + } + finally + { + environmentChangeListener.Stop(); + } + } + + private static void OnEnvironmentChanged() + { + PathEnvironmentVariableHandler.UpdatePath(); + } + + private class LimitationSetMetadata + { + [JsonPropertyName("path")] + public string Path { get; set; } = string.Empty; + + [JsonPropertyName("modulePath")] + public string? ModulePath { get; set; } = null; + + [JsonPropertyName("processorPath")] + public string? ProcessorPath { get; set; } = null; + + [JsonPropertyName("processorPathHash")] + public string? ProcessorPathHash { get; set; } = null; + + [JsonPropertyName("processorPathIsAlias")] + public bool? ProcessorPathIsAlias { get; set; } = null; + } + + private static IConfigurationSetProcessorFactory CreateFactory(string processorEngine, ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) + { + switch (processorEngine) + { + case "pwsh": + return CreatePowerShellFactory(limitationSet, limitationSetMetadata); + case "dscv3": + return CreateDSCv3Factory(limitationSet, limitationSetMetadata); + } + + throw new NotImplementedException($"Processor engine unknown: {processorEngine}"); + } + + private static IConfigurationSetProcessorFactory CreatePowerShellFactory(ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) + { + PowerShellConfigurationSetProcessorFactory factory = new PowerShellConfigurationSetProcessorFactory(); + + // Set default properties. + var externalModulesPath = GetExternalModulesPath(); + if (string.IsNullOrWhiteSpace(externalModulesPath)) + { + throw new DirectoryNotFoundException("Failed to get ExternalModules."); + } + + // Set as implicit module paths so it will be always included in AdditionalModulePaths + factory.ImplicitModulePaths = new List() { externalModulesPath }; + factory.ProcessorType = PowerShellConfigurationProcessorType.Hosted; + + if (limitationSetMetadata != null) + { + if (limitationSetMetadata.ModulePath != null) + { + PowerShellConfigurationProcessorLocation parsedLocation = PowerShellConfigurationProcessorLocation.Default; + if (Enum.TryParse(limitationSetMetadata.ModulePath, out parsedLocation)) + { + factory.Location = parsedLocation; + } + else + { + factory.Location = PowerShellConfigurationProcessorLocation.Custom; + factory.CustomLocation = limitationSetMetadata.ModulePath; + } + } + } + + // Apply limitation set and thereby disable changing properties. + if (limitationSet != null) + { + factory.LimitationSet = limitationSet; + } + + return factory; + } + + private static IConfigurationSetProcessorFactory CreateDSCv3Factory(ConfigurationSet? limitationSet, LimitationSetMetadata? limitationSetMetadata) + { + DSCv3ConfigurationSetProcessorFactory factory = new DSCv3ConfigurationSetProcessorFactory(); + + if (limitationSetMetadata != null) + { + if (limitationSetMetadata.ProcessorPath != null) + { + if (limitationSetMetadata.ProcessorPathHash == null) + { + throw new InvalidOperationException("A custom processor path was provided without a hash for integrity verification."); + } + + // Set hash and alias before path so they are available when the path is verified on first use. + factory.DscExecutablePathHash = limitationSetMetadata.ProcessorPathHash; + factory.DscExecutablePathIsAlias = limitationSetMetadata.ProcessorPathIsAlias ?? false; + factory.DscExecutablePath = limitationSetMetadata.ProcessorPath; + } + else + { + // Require that the path to the DSC executable be presented to the user in limitation mode. + // This helps prevent path attacks against an elevated process (as long as the user checks the value). + throw new ArgumentNullException("The path to the DSC executable must be supplied in limitation mode."); + } + } + + // Apply limitation set and thereby disable changing properties. + if (limitationSet != null) + { + factory.LimitationSet = limitationSet; + } + + return factory; + } + + private static string GetExternalModulesPath() + { + var currentAssemblyDirectoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + if (currentAssemblyDirectoryPath != null) + { + var packageRootPath = Directory.GetParent(currentAssemblyDirectoryPath)?.FullName; + if (packageRootPath != null) + { + var externalModulesPath = Path.Combine(packageRootPath, ExternalModulesName); + if (Directory.Exists(externalModulesPath)) + { + return externalModulesPath; + } + } + } + + return string.Empty; + } + + [DllImport("WindowsPackageManager.dll")] + private static extern int WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization( + int result, + IntPtr factory, + [MarshalAs(UnmanagedType.LPWStr)]string staticsCallback, + [MarshalAs(UnmanagedType.LPWStr)]string? completionEventName, + uint parentProcessId); + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] + private static extern IntPtr GetCommandLineW(); + + [DllImport("kernel32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool FreeConsole(); + } +} diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 6a42f20e99..7d9a37b900 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,42 +1,42 @@ - - - true - true - - 10.0.26100.53 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + true + true + + 10.0.26100.53 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Get-VcxprojNugetPackageVersions.ps1 b/src/Get-VcxprojNugetPackageVersions.ps1 index 04c4958650..be407db342 100644 --- a/src/Get-VcxprojNugetPackageVersions.ps1 +++ b/src/Get-VcxprojNugetPackageVersions.ps1 @@ -1,339 +1,339 @@ -#Requires -Version 5.1 - -<# -.SYNOPSIS - Recursively searches for .vcxproj files and extracts NuGet package versions from associated packages.config files. - -.DESCRIPTION - This script searches directories below the script location for Visual Studio C++ project files (.vcxproj) - and their corresponding packages.config files. It extracts all NuGet package references with their versions - and generates a comprehensive report. - -.PARAMETER OutputFormat - Specifies the output format: Table, CSV, JSON, or Object. Default is Table. Object format returns PackageInfo objects without printing anything. - -.PARAMETER ExportPath - Optional path to export the results to a file. Not applicable for Object format. - -.PARAMETER PackageFilter - Optional filter to search for specific package names. Supports wildcards. If specified, only packages matching this filter will be included in the results. - -.EXAMPLE - .\Get-VcxprojNugetPackageVersions.ps1 - -.EXAMPLE - .\Get-VcxprojNugetPackageVersions.ps1 -OutputFormat CSV -ExportPath "packages-report.csv" - -.EXAMPLE - .\Get-VcxprojNugetPackageVersions.ps1 -PackageFilter "Microsoft*" - -.EXAMPLE - .\Get-VcxprojNugetPackageVersions.ps1 -PackageFilter "Newtonsoft.Json" -OutputFormat JSON - -.EXAMPLE - $packages = .\Get-VcxprojNugetPackageVersions.ps1 -OutputFormat Object -#> - -[CmdletBinding()] -param( - [ValidateSet("Table", "CSV", "JSON", "Object")] - [string]$OutputFormat = "Table", - - [string]$ExportPath, - - [string]$PackageFilter -) - -# Custom class to hold package information -class PackageInfo { - [string]$ProjectFile - [string]$ProjectName - [string]$PackageId - [string]$Version - [string]$TargetFramework - [string]$PackagesConfigPath - [bool]$PackagesConfigExists -} - -function Get-VcxprojFiles { - param( - [string]$SearchPath, - [bool]$SuppressOutput = $false - ) - - Write-Verbose "Searching for .vcxproj files in: $SearchPath" - - try { - $vcxprojFiles = Get-ChildItem -Path $SearchPath -Filter "*.vcxproj" -Recurse -ErrorAction SilentlyContinue - if (-not $SuppressOutput) { - Write-Host "Found $($vcxprojFiles.Count) .vcxproj files" -ForegroundColor Green - } - return $vcxprojFiles - } - catch { - Write-Warning "Error searching for .vcxproj files: $($_.Exception.Message)" - return @() - } -} - -function Get-PackagesFromConfig { - param( - [string]$PackagesConfigPath, - [string]$ProjectFile, - [string]$ProjectName, - [string]$PackageFilter - ) - - $packages = @() - - if (-not (Test-Path $PackagesConfigPath)) { - Write-Verbose "No packages.config found for project: $ProjectName" - - # If a package filter is specified and there's no packages.config, don't include this project - if ($PackageFilter) { - Write-Verbose "Package filter specified but no packages.config exists for project: $ProjectName. Excluding from results." - return @() - } - - # Return empty package info to indicate no packages.config - $packageInfo = [PackageInfo]::new() - $packageInfo.ProjectFile = $ProjectFile - $packageInfo.ProjectName = $ProjectName - $packageInfo.PackageId = "N/A" - $packageInfo.Version = "N/A" - $packageInfo.TargetFramework = "N/A" - $packageInfo.PackagesConfigPath = $PackagesConfigPath - $packageInfo.PackagesConfigExists = $false - return @($packageInfo) - } - - try { - Write-Verbose "Processing packages.config: $PackagesConfigPath" - [xml]$packagesXml = Get-Content $PackagesConfigPath -ErrorAction Stop - - $packageNodes = $packagesXml.packages.package - - if ($null -eq $packageNodes) { - Write-Verbose "No package nodes found in packages.config for project: $ProjectName" - return @() - } - - # Handle both single package and multiple packages scenarios - if ($packageNodes -is [System.Xml.XmlElement]) { - # Single package - $packageNodes = @($packageNodes) - } - - foreach ($package in $packageNodes) { - # Apply package filter if specified - if ($PackageFilter -and $package.id -notlike $PackageFilter) { - Write-Verbose "Package '$($package.id)' does not match filter '$PackageFilter', skipping" - continue - } - - $packageInfo = [PackageInfo]::new() - $packageInfo.ProjectFile = $ProjectFile - $packageInfo.ProjectName = $ProjectName - $packageInfo.PackageId = $package.id - $packageInfo.Version = $package.version - $packageInfo.TargetFramework = $package.targetFramework - $packageInfo.PackagesConfigPath = $PackagesConfigPath - $packageInfo.PackagesConfigExists = $true - - $packages += $packageInfo - } - - Write-Verbose "Found $($packages.Count) packages in $ProjectName" - } - catch { - Write-Warning "Error parsing packages.config for project '$ProjectName': $($_.Exception.Message)" - } - - return $packages -} - -function Get-ProjectName { - param([string]$VcxprojPath) - - try { - [xml]$projectXml = Get-Content $VcxprojPath -ErrorAction Stop - - # Try to get project name from PropertyGroup - $projectNameNode = $projectXml.Project.PropertyGroup.ProjectName | Select-Object -First 1 - if ($projectNameNode) { - return $projectNameNode - } - - # Fallback to filename without extension - return [System.IO.Path]::GetFileNameWithoutExtension($VcxprojPath) - } - catch { - Write-Verbose "Could not parse project file for name, using filename: $($_.Exception.Message)" - return [System.IO.Path]::GetFileNameWithoutExtension($VcxprojPath) - } -} - -function Export-Results { - param( - [array]$Results, - [string]$Format, - [string]$Path - ) - - if (-not $Path) { - return - } - - try { - switch ($Format) { - "CSV" { - $Results | Export-Csv -Path $Path -NoTypeInformation - Write-Host "Results exported to CSV: $Path" -ForegroundColor Green - } - "JSON" { - $Results | ConvertTo-Json -Depth 3 | Out-File -FilePath $Path -Encoding utf8 - Write-Host "Results exported to JSON: $Path" -ForegroundColor Green - } - default { - $Results | Format-Table -AutoSize | Out-File -FilePath $Path -Encoding utf8 - Write-Host "Results exported to text file: $Path" -ForegroundColor Green - } - } - } - catch { - Write-Warning "Failed to export results to '$Path': $($_.Exception.Message)" - } -} - -function Show-Summary { - param([array]$AllPackages) - - $projectsWithPackages = $AllPackages | Where-Object { $_.PackagesConfigExists } | Group-Object ProjectFile - $projectsWithoutPackages = $AllPackages | Where-Object { -not $_.PackagesConfigExists } | Group-Object ProjectFile - - Write-Host "`n=== SUMMARY ===" -ForegroundColor Cyan - Write-Host "Total .vcxproj files found: $($projectsWithPackages.Count + $projectsWithoutPackages.Count)" - Write-Host "Projects with packages.config: $($projectsWithPackages.Count)" -ForegroundColor Green - Write-Host "Projects without packages.config: $($projectsWithoutPackages.Count)" -ForegroundColor Yellow - Write-Host "Total package references: $(($AllPackages | Where-Object { $_.PackagesConfigExists }).Count)" -ForegroundColor Green -} - -# Main execution -try { - $scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path - - # Suppress output messages for Object format - if ($OutputFormat -ne "Object") { - Write-Host "Starting search from: $scriptPath" -ForegroundColor Cyan - Write-Host "Output format: $OutputFormat" -ForegroundColor Cyan - - if ($PackageFilter) { - Write-Host "Package filter: $PackageFilter" -ForegroundColor Cyan - } - - if ($ExportPath) { - Write-Host "Export path: $ExportPath" -ForegroundColor Cyan - } - } - - # Find all .vcxproj files - $vcxprojFiles = Get-VcxprojFiles -SearchPath $scriptPath -SuppressOutput ($OutputFormat -eq "Object") - - if ($vcxprojFiles.Count -eq 0) { - if ($OutputFormat -ne "Object") { - Write-Warning "No .vcxproj files found in the search directory." - } - return @() # Return empty array for Object format - } - - # Process each .vcxproj file - $allPackages = @() - $processedCount = 0 - - foreach ($vcxproj in $vcxprojFiles) { - $processedCount++ - Write-Progress -Activity "Processing .vcxproj files" -Status "$processedCount of $($vcxprojFiles.Count)" -PercentComplete (($processedCount / $vcxprojFiles.Count) * 100) - - $projectName = Get-ProjectName -VcxprojPath $vcxproj.FullName - $packagesConfigPath = Join-Path $vcxproj.DirectoryName "packages.config" - - $packages = Get-PackagesFromConfig -PackagesConfigPath $packagesConfigPath -ProjectFile $vcxproj.FullName -ProjectName $projectName -PackageFilter $PackageFilter - $allPackages += $packages - } - - Write-Progress -Activity "Processing .vcxproj files" -Completed - - # Display results - if ($allPackages.Count -gt 0) { - switch ($OutputFormat) { - "CSV" { - if ($ExportPath) { - Export-Results -Results $allPackages -Format "CSV" -Path $ExportPath - } else { - $allPackages | ConvertTo-Csv -NoTypeInformation | Write-Output - } - } - "JSON" { - if ($ExportPath) { - Export-Results -Results $allPackages -Format "JSON" -Path $ExportPath - } else { - $allPackages | ConvertTo-Json -Depth 3 | Write-Output - } - } - "Object" { - # Return only packages where PackagesConfigExists is true - $packagesWithConfig = $allPackages | Where-Object { $_.PackagesConfigExists } - return $packagesWithConfig - } - default { - # Table format - Write-Host "`n=== PACKAGE DETAILS ===" -ForegroundColor Cyan - - # Show the set of packages and versions found across all projects - $uniquePackages = $allPackages | Where-Object { $_.PackagesConfigExists } | Group-Object PackageId, Version - if ($uniquePackages.Count -gt 0) { - $uniquePackages | Format-Table -Property Name, Count -AutoSize - } - - # Output the URL of each package on nuget.org - Write-Host "`n=== PACKAGE URLS ===" -ForegroundColor Cyan - - $uniquePackages = $allPackages | Where-Object { $_.PackagesConfigExists } | Group-Object PackageId - foreach ($packageGroup in $uniquePackages) { - $packageId = $packageGroup.Name - $url = "https://www.nuget.org/packages/$packageId#versions-body-tab" - Write-Host " $packageId : $url" -ForegroundColor Yellow - } - - Write-Host "`n=== PROJECT DETAILS ===" -ForegroundColor Cyan - - # Show projects with packages - $packagesWithConfig = $allPackages | Where-Object { $_.PackagesConfigExists } - if ($packagesWithConfig.Count -gt 0) { - $packagesWithConfig | Format-Table -Property ProjectName, PackageId, Version, TargetFramework -AutoSize - } - - if ($ExportPath) { - Export-Results -Results $allPackages -Format $OutputFormat -Path $ExportPath - } - } - } - - # Show summary (but not for Object format) - if ($OutputFormat -ne "Object") { - Show-Summary -AllPackages $allPackages - } - } else { - if ($OutputFormat -ne "Object") { - Write-Host "No packages found in any .vcxproj files." -ForegroundColor Yellow - } - } -} -catch { - Write-Error "An error occurred during execution: $($_.Exception.Message)" - Write-Error $_.ScriptStackTrace -} - -if ($OutputFormat -ne "Object") { - Write-Host "`nScript completed." -ForegroundColor Green +#Requires -Version 5.1 + +<# +.SYNOPSIS + Recursively searches for .vcxproj files and extracts NuGet package versions from associated packages.config files. + +.DESCRIPTION + This script searches directories below the script location for Visual Studio C++ project files (.vcxproj) + and their corresponding packages.config files. It extracts all NuGet package references with their versions + and generates a comprehensive report. + +.PARAMETER OutputFormat + Specifies the output format: Table, CSV, JSON, or Object. Default is Table. Object format returns PackageInfo objects without printing anything. + +.PARAMETER ExportPath + Optional path to export the results to a file. Not applicable for Object format. + +.PARAMETER PackageFilter + Optional filter to search for specific package names. Supports wildcards. If specified, only packages matching this filter will be included in the results. + +.EXAMPLE + .\Get-VcxprojNugetPackageVersions.ps1 + +.EXAMPLE + .\Get-VcxprojNugetPackageVersions.ps1 -OutputFormat CSV -ExportPath "packages-report.csv" + +.EXAMPLE + .\Get-VcxprojNugetPackageVersions.ps1 -PackageFilter "Microsoft*" + +.EXAMPLE + .\Get-VcxprojNugetPackageVersions.ps1 -PackageFilter "Newtonsoft.Json" -OutputFormat JSON + +.EXAMPLE + $packages = .\Get-VcxprojNugetPackageVersions.ps1 -OutputFormat Object +#> + +[CmdletBinding()] +param( + [ValidateSet("Table", "CSV", "JSON", "Object")] + [string]$OutputFormat = "Table", + + [string]$ExportPath, + + [string]$PackageFilter +) + +# Custom class to hold package information +class PackageInfo { + [string]$ProjectFile + [string]$ProjectName + [string]$PackageId + [string]$Version + [string]$TargetFramework + [string]$PackagesConfigPath + [bool]$PackagesConfigExists +} + +function Get-VcxprojFiles { + param( + [string]$SearchPath, + [bool]$SuppressOutput = $false + ) + + Write-Verbose "Searching for .vcxproj files in: $SearchPath" + + try { + $vcxprojFiles = Get-ChildItem -Path $SearchPath -Filter "*.vcxproj" -Recurse -ErrorAction SilentlyContinue + if (-not $SuppressOutput) { + Write-Host "Found $($vcxprojFiles.Count) .vcxproj files" -ForegroundColor Green + } + return $vcxprojFiles + } + catch { + Write-Warning "Error searching for .vcxproj files: $($_.Exception.Message)" + return @() + } +} + +function Get-PackagesFromConfig { + param( + [string]$PackagesConfigPath, + [string]$ProjectFile, + [string]$ProjectName, + [string]$PackageFilter + ) + + $packages = @() + + if (-not (Test-Path $PackagesConfigPath)) { + Write-Verbose "No packages.config found for project: $ProjectName" + + # If a package filter is specified and there's no packages.config, don't include this project + if ($PackageFilter) { + Write-Verbose "Package filter specified but no packages.config exists for project: $ProjectName. Excluding from results." + return @() + } + + # Return empty package info to indicate no packages.config + $packageInfo = [PackageInfo]::new() + $packageInfo.ProjectFile = $ProjectFile + $packageInfo.ProjectName = $ProjectName + $packageInfo.PackageId = "N/A" + $packageInfo.Version = "N/A" + $packageInfo.TargetFramework = "N/A" + $packageInfo.PackagesConfigPath = $PackagesConfigPath + $packageInfo.PackagesConfigExists = $false + return @($packageInfo) + } + + try { + Write-Verbose "Processing packages.config: $PackagesConfigPath" + [xml]$packagesXml = Get-Content $PackagesConfigPath -ErrorAction Stop + + $packageNodes = $packagesXml.packages.package + + if ($null -eq $packageNodes) { + Write-Verbose "No package nodes found in packages.config for project: $ProjectName" + return @() + } + + # Handle both single package and multiple packages scenarios + if ($packageNodes -is [System.Xml.XmlElement]) { + # Single package + $packageNodes = @($packageNodes) + } + + foreach ($package in $packageNodes) { + # Apply package filter if specified + if ($PackageFilter -and $package.id -notlike $PackageFilter) { + Write-Verbose "Package '$($package.id)' does not match filter '$PackageFilter', skipping" + continue + } + + $packageInfo = [PackageInfo]::new() + $packageInfo.ProjectFile = $ProjectFile + $packageInfo.ProjectName = $ProjectName + $packageInfo.PackageId = $package.id + $packageInfo.Version = $package.version + $packageInfo.TargetFramework = $package.targetFramework + $packageInfo.PackagesConfigPath = $PackagesConfigPath + $packageInfo.PackagesConfigExists = $true + + $packages += $packageInfo + } + + Write-Verbose "Found $($packages.Count) packages in $ProjectName" + } + catch { + Write-Warning "Error parsing packages.config for project '$ProjectName': $($_.Exception.Message)" + } + + return $packages +} + +function Get-ProjectName { + param([string]$VcxprojPath) + + try { + [xml]$projectXml = Get-Content $VcxprojPath -ErrorAction Stop + + # Try to get project name from PropertyGroup + $projectNameNode = $projectXml.Project.PropertyGroup.ProjectName | Select-Object -First 1 + if ($projectNameNode) { + return $projectNameNode + } + + # Fallback to filename without extension + return [System.IO.Path]::GetFileNameWithoutExtension($VcxprojPath) + } + catch { + Write-Verbose "Could not parse project file for name, using filename: $($_.Exception.Message)" + return [System.IO.Path]::GetFileNameWithoutExtension($VcxprojPath) + } +} + +function Export-Results { + param( + [array]$Results, + [string]$Format, + [string]$Path + ) + + if (-not $Path) { + return + } + + try { + switch ($Format) { + "CSV" { + $Results | Export-Csv -Path $Path -NoTypeInformation + Write-Host "Results exported to CSV: $Path" -ForegroundColor Green + } + "JSON" { + $Results | ConvertTo-Json -Depth 3 | Out-File -FilePath $Path -Encoding utf8 + Write-Host "Results exported to JSON: $Path" -ForegroundColor Green + } + default { + $Results | Format-Table -AutoSize | Out-File -FilePath $Path -Encoding utf8 + Write-Host "Results exported to text file: $Path" -ForegroundColor Green + } + } + } + catch { + Write-Warning "Failed to export results to '$Path': $($_.Exception.Message)" + } +} + +function Show-Summary { + param([array]$AllPackages) + + $projectsWithPackages = $AllPackages | Where-Object { $_.PackagesConfigExists } | Group-Object ProjectFile + $projectsWithoutPackages = $AllPackages | Where-Object { -not $_.PackagesConfigExists } | Group-Object ProjectFile + + Write-Host "`n=== SUMMARY ===" -ForegroundColor Cyan + Write-Host "Total .vcxproj files found: $($projectsWithPackages.Count + $projectsWithoutPackages.Count)" + Write-Host "Projects with packages.config: $($projectsWithPackages.Count)" -ForegroundColor Green + Write-Host "Projects without packages.config: $($projectsWithoutPackages.Count)" -ForegroundColor Yellow + Write-Host "Total package references: $(($AllPackages | Where-Object { $_.PackagesConfigExists }).Count)" -ForegroundColor Green +} + +# Main execution +try { + $scriptPath = Split-Path -Parent $MyInvocation.MyCommand.Path + + # Suppress output messages for Object format + if ($OutputFormat -ne "Object") { + Write-Host "Starting search from: $scriptPath" -ForegroundColor Cyan + Write-Host "Output format: $OutputFormat" -ForegroundColor Cyan + + if ($PackageFilter) { + Write-Host "Package filter: $PackageFilter" -ForegroundColor Cyan + } + + if ($ExportPath) { + Write-Host "Export path: $ExportPath" -ForegroundColor Cyan + } + } + + # Find all .vcxproj files + $vcxprojFiles = Get-VcxprojFiles -SearchPath $scriptPath -SuppressOutput ($OutputFormat -eq "Object") + + if ($vcxprojFiles.Count -eq 0) { + if ($OutputFormat -ne "Object") { + Write-Warning "No .vcxproj files found in the search directory." + } + return @() # Return empty array for Object format + } + + # Process each .vcxproj file + $allPackages = @() + $processedCount = 0 + + foreach ($vcxproj in $vcxprojFiles) { + $processedCount++ + Write-Progress -Activity "Processing .vcxproj files" -Status "$processedCount of $($vcxprojFiles.Count)" -PercentComplete (($processedCount / $vcxprojFiles.Count) * 100) + + $projectName = Get-ProjectName -VcxprojPath $vcxproj.FullName + $packagesConfigPath = Join-Path $vcxproj.DirectoryName "packages.config" + + $packages = Get-PackagesFromConfig -PackagesConfigPath $packagesConfigPath -ProjectFile $vcxproj.FullName -ProjectName $projectName -PackageFilter $PackageFilter + $allPackages += $packages + } + + Write-Progress -Activity "Processing .vcxproj files" -Completed + + # Display results + if ($allPackages.Count -gt 0) { + switch ($OutputFormat) { + "CSV" { + if ($ExportPath) { + Export-Results -Results $allPackages -Format "CSV" -Path $ExportPath + } else { + $allPackages | ConvertTo-Csv -NoTypeInformation | Write-Output + } + } + "JSON" { + if ($ExportPath) { + Export-Results -Results $allPackages -Format "JSON" -Path $ExportPath + } else { + $allPackages | ConvertTo-Json -Depth 3 | Write-Output + } + } + "Object" { + # Return only packages where PackagesConfigExists is true + $packagesWithConfig = $allPackages | Where-Object { $_.PackagesConfigExists } + return $packagesWithConfig + } + default { + # Table format + Write-Host "`n=== PACKAGE DETAILS ===" -ForegroundColor Cyan + + # Show the set of packages and versions found across all projects + $uniquePackages = $allPackages | Where-Object { $_.PackagesConfigExists } | Group-Object PackageId, Version + if ($uniquePackages.Count -gt 0) { + $uniquePackages | Format-Table -Property Name, Count -AutoSize + } + + # Output the URL of each package on nuget.org + Write-Host "`n=== PACKAGE URLS ===" -ForegroundColor Cyan + + $uniquePackages = $allPackages | Where-Object { $_.PackagesConfigExists } | Group-Object PackageId + foreach ($packageGroup in $uniquePackages) { + $packageId = $packageGroup.Name + $url = "https://www.nuget.org/packages/$packageId#versions-body-tab" + Write-Host " $packageId : $url" -ForegroundColor Yellow + } + + Write-Host "`n=== PROJECT DETAILS ===" -ForegroundColor Cyan + + # Show projects with packages + $packagesWithConfig = $allPackages | Where-Object { $_.PackagesConfigExists } + if ($packagesWithConfig.Count -gt 0) { + $packagesWithConfig | Format-Table -Property ProjectName, PackageId, Version, TargetFramework -AutoSize + } + + if ($ExportPath) { + Export-Results -Results $allPackages -Format $OutputFormat -Path $ExportPath + } + } + } + + # Show summary (but not for Object format) + if ($OutputFormat -ne "Object") { + Show-Summary -AllPackages $allPackages + } + } else { + if ($OutputFormat -ne "Object") { + Write-Host "No packages found in any .vcxproj files." -ForegroundColor Yellow + } + } +} +catch { + Write-Error "An error occurred during execution: $($_.Exception.Message)" + Write-Error $_.ScriptStackTrace +} + +if ($OutputFormat -ne "Object") { + Write-Host "`nScript completed." -ForegroundColor Green } \ No newline at end of file diff --git a/src/IndexCreationTool/IndexCreationTool.csproj b/src/IndexCreationTool/IndexCreationTool.csproj index 4025eeb1f4..67af2a7683 100644 --- a/src/IndexCreationTool/IndexCreationTool.csproj +++ b/src/IndexCreationTool/IndexCreationTool.csproj @@ -1,24 +1,24 @@ - - - - Exe - net8.0 - $(SolutionDir)$(Platform)\$(Configuration)\IndexCreationTool\ - x64;x86 - - - - - - - Content - PreserveNewest - True - - - - - - - - + + + + Exe + net8.0 + $(SolutionDir)$(Platform)\$(Configuration)\IndexCreationTool\ + x64;x86 + + + + + + + Content + PreserveNewest + True + + + + + + + + diff --git a/src/IndexCreationTool/Program.cs b/src/IndexCreationTool/Program.cs index f07ba51612..c59cd92b3f 100644 --- a/src/IndexCreationTool/Program.cs +++ b/src/IndexCreationTool/Program.cs @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace IndexCreationTool -{ - using Microsoft.WinGetSourceCreator; - using System; - using System.IO; - using System.Text.Json; - using System.Text.Json.Serialization; - using WinGetSourceCreator.Model; - - class Program - { - private static void PrintUsage() - { - Console.WriteLine("Usage: IndexCreationTool.exe -f "); - } - - static int Main(string[] args) - { - try - { - string inputFile = string.Empty; - for (int i = 0; i < args.Length; i++) - { - if (args[i] == "-f" && ++i < args.Length) - { - inputFile = args[i]; - } - } - - if (string.IsNullOrEmpty(inputFile) || !File.Exists(inputFile)) - { - PrintUsage(); - throw new ArgumentException("Missing input file"); - } - - WinGetLocalSource.CreateFromLocalSourceFile(inputFile); - } - catch (Exception e) - { - PrintUsage(); - Console.WriteLine(e.Message); - return -1; - } - - return 0; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace IndexCreationTool +{ + using Microsoft.WinGetSourceCreator; + using System; + using System.IO; + using System.Text.Json; + using System.Text.Json.Serialization; + using WinGetSourceCreator.Model; + + class Program + { + private static void PrintUsage() + { + Console.WriteLine("Usage: IndexCreationTool.exe -f "); + } + + static int Main(string[] args) + { + try + { + string inputFile = string.Empty; + for (int i = 0; i < args.Length; i++) + { + if (args[i] == "-f" && ++i < args.Length) + { + inputFile = args[i]; + } + } + + if (string.IsNullOrEmpty(inputFile) || !File.Exists(inputFile)) + { + PrintUsage(); + throw new ArgumentException("Missing input file"); + } + + WinGetLocalSource.CreateFromLocalSourceFile(inputFile); + } + catch (Exception e) + { + PrintUsage(); + Console.WriteLine(e.Message); + return -1; + } + + return 0; + } + } +} diff --git a/src/LocalhostWebServer/LocalhostWebServer.csproj b/src/LocalhostWebServer/LocalhostWebServer.csproj index e963674a20..e630d6c9d6 100644 --- a/src/LocalhostWebServer/LocalhostWebServer.csproj +++ b/src/LocalhostWebServer/LocalhostWebServer.csproj @@ -1,23 +1,23 @@ - - - - net8.0 - $(SolutionDir)$(Platform)\$(Configuration)\LocalhostWebServer\ - x64;x86 - - - - - - - Content - PreserveNewest - True - - - - - - - - + + + + net8.0 + $(SolutionDir)$(Platform)\$(Configuration)\LocalhostWebServer\ + x64;x86 + + + + + + + Content + PreserveNewest + True + + + + + + + + diff --git a/src/LocalhostWebServer/Program.cs b/src/LocalhostWebServer/Program.cs index 11e06a0b58..cd4bb1b6e4 100644 --- a/src/LocalhostWebServer/Program.cs +++ b/src/LocalhostWebServer/Program.cs @@ -1,178 +1,178 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace LocalhostWebServer -{ - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Hosting; - using System; - using System.IO; - using Microsoft.Extensions.Configuration; - using System.Security.Cryptography.X509Certificates; - using System.Text.Json.Serialization; - using System.Text.Json; - using WinGetSourceCreator.Model; - using Microsoft.WinGetSourceCreator; - using System.Runtime.InteropServices; - - public class Program - { - const string CertificateProviderString = "Microsoft.PowerShell.Security\\Certificate::"; - const string StoreLocationCurrentUser = "CurrentUser"; - const string StoreLocationLocalMachine = "LocalMachine"; - - static void Main(string[] args) - { - IConfiguration config = new ConfigurationBuilder() - .AddCommandLine(args) - .Build(); - - Startup.StaticFileRoot = config.GetValue("StaticFileRoot"); - Startup.CertPath = config.GetValue("CertPath"); - Startup.CertPassword = config.GetValue("CertPassword"); - Startup.Port = config.GetValue("Port", 5001); - Startup.OutCertFile = config.GetValue("OutCertFile"); - Startup.LocalSourceJson = config.GetValue("LocalSourceJson"); - Startup.TestDataPath = config.GetValue("TestDataPath"); - Startup.ExitBeforeRun = config.GetValue("ExitBeforeRun"); - - if (string.IsNullOrEmpty(Startup.StaticFileRoot) || - string.IsNullOrEmpty(Startup.CertPath)) - { - Console.WriteLine("Usage: LocalhostWebServer.exe StaticFileRoot= " + - "CertPath= CertPassword= "); - return; - } - - Directory.CreateDirectory(Startup.StaticFileRoot); - - if (Startup.CertPath.StartsWith(CertificateProviderString)) - { - string certPath = Startup.CertPath.Substring(CertificateProviderString.Length); - string[] pathParts = certPath.Split('\\'); - - if (pathParts.Length != 3) - { - throw new InvalidDataException($"Don't know how to handle: {Startup.CertPath}"); - } - - StoreLocation storeLocation = StoreLocation.CurrentUser; - if (pathParts[0] == StoreLocationCurrentUser) - { - // The default - } - else if (pathParts[0] == StoreLocationLocalMachine) - { - storeLocation = StoreLocation.LocalMachine; - } - else - { - throw new InvalidDataException($"Unknown store scope: {Startup.CertPath}"); - } - - X509Store x509Store = new X509Store(pathParts[1], storeLocation); - x509Store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); - X509Certificate2Collection collection = x509Store.Certificates; - - if (collection.Count == 0) - { - throw new InvalidDataException($"Found {collection.Count} certificates in store '{pathParts[0]}' [{storeLocation}] \\ '{pathParts[1]}': {Startup.CertPath}"); - } - - X509Certificate2Collection results = collection.Find(X509FindType.FindByThumbprint, pathParts[2], true); - - if (results.Count != 1) - { - throw new InvalidDataException($"Found {results.Count} matches for '{pathParts[2]}': {Startup.CertPath}"); - } - - ServerCertificate = results[0]; - } - else - { - ServerCertificate = new X509Certificate2(Startup.CertPath, Startup.CertPassword); - } - - if (!string.IsNullOrEmpty(Startup.OutCertFile)) - { - string parent = Path.GetDirectoryName(Startup.OutCertFile); - if (!string.IsNullOrEmpty(parent)) - { - Directory.CreateDirectory(parent); - } - - File.WriteAllBytes(Startup.OutCertFile, ServerCertificate.Export(X509ContentType.Cert)); - } - - if (!string.IsNullOrEmpty(Startup.LocalSourceJson)) - { - if (!File.Exists(Startup.LocalSourceJson)) - { - throw new FileNotFoundException(Startup.LocalSourceJson); - } - - WinGetLocalSource.CreateFromLocalSourceFile(Startup.LocalSourceJson); - } - - if (!string.IsNullOrEmpty(Startup.TestDataPath)) - { - if (!Directory.Exists(Startup.TestDataPath)) - { - throw new DirectoryNotFoundException(Startup.TestDataPath); - } - - var testDataDirectory = Path.Combine(Startup.StaticFileRoot, "TestData"); - Directory.CreateDirectory(testDataDirectory); - - CopyDirectoryRecursive(Startup.TestDataPath, testDataDirectory); - } - - if (Startup.ExitBeforeRun) - { - return; - } - - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseKestrel(opt => - { - opt.ListenAnyIP(Startup.Port, listOpt => - { - listOpt.UseHttps(ServerCertificate); - }); - }); - webBuilder.UseContentRoot(Startup.StaticFileRoot); - webBuilder.UseStartup(); - }); - - public static X509Certificate2 ServerCertificate { get; private set; } - - private static void CopyDirectoryRecursive(string sourceDir, string destDir) - { - if (!Directory.Exists(destDir)) - { - Directory.CreateDirectory(destDir); - } - - string[] files = Directory.GetFiles(sourceDir); - foreach (string file in files) - { - string dest = Path.Combine(destDir, Path.GetFileName(file)); - File.Copy(file, dest, overwrite: true); - } - - string[] directories = Directory.GetDirectories(sourceDir); - foreach (string dir in directories) - { - string dest = Path.Combine(destDir, Path.GetFileName(dir)); - CopyDirectoryRecursive(dir, dest); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace LocalhostWebServer +{ + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.Extensions.Hosting; + using System; + using System.IO; + using Microsoft.Extensions.Configuration; + using System.Security.Cryptography.X509Certificates; + using System.Text.Json.Serialization; + using System.Text.Json; + using WinGetSourceCreator.Model; + using Microsoft.WinGetSourceCreator; + using System.Runtime.InteropServices; + + public class Program + { + const string CertificateProviderString = "Microsoft.PowerShell.Security\\Certificate::"; + const string StoreLocationCurrentUser = "CurrentUser"; + const string StoreLocationLocalMachine = "LocalMachine"; + + static void Main(string[] args) + { + IConfiguration config = new ConfigurationBuilder() + .AddCommandLine(args) + .Build(); + + Startup.StaticFileRoot = config.GetValue("StaticFileRoot"); + Startup.CertPath = config.GetValue("CertPath"); + Startup.CertPassword = config.GetValue("CertPassword"); + Startup.Port = config.GetValue("Port", 5001); + Startup.OutCertFile = config.GetValue("OutCertFile"); + Startup.LocalSourceJson = config.GetValue("LocalSourceJson"); + Startup.TestDataPath = config.GetValue("TestDataPath"); + Startup.ExitBeforeRun = config.GetValue("ExitBeforeRun"); + + if (string.IsNullOrEmpty(Startup.StaticFileRoot) || + string.IsNullOrEmpty(Startup.CertPath)) + { + Console.WriteLine("Usage: LocalhostWebServer.exe StaticFileRoot= " + + "CertPath= CertPassword= "); + return; + } + + Directory.CreateDirectory(Startup.StaticFileRoot); + + if (Startup.CertPath.StartsWith(CertificateProviderString)) + { + string certPath = Startup.CertPath.Substring(CertificateProviderString.Length); + string[] pathParts = certPath.Split('\\'); + + if (pathParts.Length != 3) + { + throw new InvalidDataException($"Don't know how to handle: {Startup.CertPath}"); + } + + StoreLocation storeLocation = StoreLocation.CurrentUser; + if (pathParts[0] == StoreLocationCurrentUser) + { + // The default + } + else if (pathParts[0] == StoreLocationLocalMachine) + { + storeLocation = StoreLocation.LocalMachine; + } + else + { + throw new InvalidDataException($"Unknown store scope: {Startup.CertPath}"); + } + + X509Store x509Store = new X509Store(pathParts[1], storeLocation); + x509Store.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly); + X509Certificate2Collection collection = x509Store.Certificates; + + if (collection.Count == 0) + { + throw new InvalidDataException($"Found {collection.Count} certificates in store '{pathParts[0]}' [{storeLocation}] \\ '{pathParts[1]}': {Startup.CertPath}"); + } + + X509Certificate2Collection results = collection.Find(X509FindType.FindByThumbprint, pathParts[2], true); + + if (results.Count != 1) + { + throw new InvalidDataException($"Found {results.Count} matches for '{pathParts[2]}': {Startup.CertPath}"); + } + + ServerCertificate = results[0]; + } + else + { + ServerCertificate = new X509Certificate2(Startup.CertPath, Startup.CertPassword); + } + + if (!string.IsNullOrEmpty(Startup.OutCertFile)) + { + string parent = Path.GetDirectoryName(Startup.OutCertFile); + if (!string.IsNullOrEmpty(parent)) + { + Directory.CreateDirectory(parent); + } + + File.WriteAllBytes(Startup.OutCertFile, ServerCertificate.Export(X509ContentType.Cert)); + } + + if (!string.IsNullOrEmpty(Startup.LocalSourceJson)) + { + if (!File.Exists(Startup.LocalSourceJson)) + { + throw new FileNotFoundException(Startup.LocalSourceJson); + } + + WinGetLocalSource.CreateFromLocalSourceFile(Startup.LocalSourceJson); + } + + if (!string.IsNullOrEmpty(Startup.TestDataPath)) + { + if (!Directory.Exists(Startup.TestDataPath)) + { + throw new DirectoryNotFoundException(Startup.TestDataPath); + } + + var testDataDirectory = Path.Combine(Startup.StaticFileRoot, "TestData"); + Directory.CreateDirectory(testDataDirectory); + + CopyDirectoryRecursive(Startup.TestDataPath, testDataDirectory); + } + + if (Startup.ExitBeforeRun) + { + return; + } + + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseKestrel(opt => + { + opt.ListenAnyIP(Startup.Port, listOpt => + { + listOpt.UseHttps(ServerCertificate); + }); + }); + webBuilder.UseContentRoot(Startup.StaticFileRoot); + webBuilder.UseStartup(); + }); + + public static X509Certificate2 ServerCertificate { get; private set; } + + private static void CopyDirectoryRecursive(string sourceDir, string destDir) + { + if (!Directory.Exists(destDir)) + { + Directory.CreateDirectory(destDir); + } + + string[] files = Directory.GetFiles(sourceDir); + foreach (string file in files) + { + string dest = Path.Combine(destDir, Path.GetFileName(file)); + File.Copy(file, dest, overwrite: true); + } + + string[] directories = Directory.GetDirectories(sourceDir); + foreach (string dir in directories) + { + string dest = Path.Combine(destDir, Path.GetFileName(dir)); + CopyDirectoryRecursive(dir, dest); + } + } + } +} diff --git a/src/LocalhostWebServer/Run-LocalhostWebServer.ps1 b/src/LocalhostWebServer/Run-LocalhostWebServer.ps1 index d3d0352038..36e4b2cd4a 100644 --- a/src/LocalhostWebServer/Run-LocalhostWebServer.ps1 +++ b/src/LocalhostWebServer/Run-LocalhostWebServer.ps1 @@ -1,76 +1,76 @@ -<# -.SYNOPSIS - Runs the LocalhostWebServer. -.PARAMETER BuildRoot - The root of the build output directory for LocalhostWebServer -.PARAMETER StaticFileRoot - The root of the static files to be served through Localhost -.PARAMETER CertPath - Path to HTTPS Development Certificate File (pfx) -.PARAMETER CertPassword - Secure Password for HTTPS Certificate -.PARAMETER OutCertFile - Export cert location. -.PARAMETER LocalSourceJson - Local source json definition -.PARAMETER SourceCert - The certificate of the source package. -#> - -param( - [Parameter(Mandatory=$true)] - [string]$BuildRoot, - - [Parameter(Mandatory=$true)] - [string]$StaticFileRoot, - - [Parameter(Mandatory=$true)] - [string]$CertPath, - - [Parameter()] - [string]$CertPassword, - - [Parameter()] - [string]$OutCertFile, - - [Parameter()] - [string]$LocalSourceJson, - - [Parameter()] - [string]$SourceCert, - - [Parameter()] - [string]$TestDataPath, - - [Parameter()] - [switch]$ExitBeforeRun -) - -if (-not [System.String]::IsNullOrEmpty($sourceCert)) -{ - # Requires admin - & certutil.exe -addstore -f "TRUSTEDPEOPLE" $sourceCert -} - -Push-Location $BuildRoot - -$startProcessArguments = @{ - FilePath = Join-Path $BuildRoot "LocalhostWebServer.exe" - ArgumentList = "StaticFileRoot=$StaticFileRoot CertPath=$CertPath CertPassword=$CertPassword OutCertFile=$OutCertFile LocalSourceJson=$LocalSourceJson TestDataPath=$TestDataPath ExitBeforeRun=$ExitBeforeRun" - PassThru = $true -} - -if (-not [System.string]::IsNullOrEmpty($env:artifactsDir)) -{ - $startProcessArguments.RedirectStandardOutput = Join-Path $env:artifactsDir "LocalhostWebServer.out" - $startProcessArguments.RedirectStandardError = Join-Path $env:artifactsDir "LocalhostWebServer.err" -} - -$Local:process = Start-Process @startProcessArguments - -if ($ExitBeforeRun) -{ - Wait-Process -InputObject $Local:process -} - -Pop-Location +<# +.SYNOPSIS + Runs the LocalhostWebServer. +.PARAMETER BuildRoot + The root of the build output directory for LocalhostWebServer +.PARAMETER StaticFileRoot + The root of the static files to be served through Localhost +.PARAMETER CertPath + Path to HTTPS Development Certificate File (pfx) +.PARAMETER CertPassword + Secure Password for HTTPS Certificate +.PARAMETER OutCertFile + Export cert location. +.PARAMETER LocalSourceJson + Local source json definition +.PARAMETER SourceCert + The certificate of the source package. +#> + +param( + [Parameter(Mandatory=$true)] + [string]$BuildRoot, + + [Parameter(Mandatory=$true)] + [string]$StaticFileRoot, + + [Parameter(Mandatory=$true)] + [string]$CertPath, + + [Parameter()] + [string]$CertPassword, + + [Parameter()] + [string]$OutCertFile, + + [Parameter()] + [string]$LocalSourceJson, + + [Parameter()] + [string]$SourceCert, + + [Parameter()] + [string]$TestDataPath, + + [Parameter()] + [switch]$ExitBeforeRun +) + +if (-not [System.String]::IsNullOrEmpty($sourceCert)) +{ + # Requires admin + & certutil.exe -addstore -f "TRUSTEDPEOPLE" $sourceCert +} + +Push-Location $BuildRoot + +$startProcessArguments = @{ + FilePath = Join-Path $BuildRoot "LocalhostWebServer.exe" + ArgumentList = "StaticFileRoot=$StaticFileRoot CertPath=$CertPath CertPassword=$CertPassword OutCertFile=$OutCertFile LocalSourceJson=$LocalSourceJson TestDataPath=$TestDataPath ExitBeforeRun=$ExitBeforeRun" + PassThru = $true +} + +if (-not [System.string]::IsNullOrEmpty($env:artifactsDir)) +{ + $startProcessArguments.RedirectStandardOutput = Join-Path $env:artifactsDir "LocalhostWebServer.out" + $startProcessArguments.RedirectStandardError = Join-Path $env:artifactsDir "LocalhostWebServer.err" +} + +$Local:process = Start-Process @startProcessArguments + +if ($ExitBeforeRun) +{ + Wait-Process -InputObject $Local:process +} + +Pop-Location diff --git a/src/LocalhostWebServer/Startup.cs b/src/LocalhostWebServer/Startup.cs index d71dc1f992..95d7fa6eef 100644 --- a/src/LocalhostWebServer/Startup.cs +++ b/src/LocalhostWebServer/Startup.cs @@ -1,104 +1,104 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace LocalhostWebServer -{ - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.AspNetCore.StaticFiles; - using Microsoft.Extensions.Configuration; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.FileProviders; - using Microsoft.Extensions.Hosting; - - public class Startup - { - public const string StaticFileRequestPath = "/TestKit"; - - public static string StaticFileRoot { get; set; } - - public static string CertPath { get; set; } - - public static string CertPassword { get; set; } - - public static string OutCertFile { get; set; } - - public static int Port { get; set; } - - public static string LocalSourceJson { get; set; } - - public static string TestDataPath { get; set; } - - public static bool ExitBeforeRun { get; set; } - - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddControllersWithViews(); - } - - public IConfiguration Configuration { get; } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - - app.UseHttpsRedirection(); - - //Add .yaml and .msix mappings - var provider = new FileExtensionContentTypeProvider(); - provider.Mappings[".yml"] = "application/x-yaml"; - provider.Mappings[".yaml"] = "application/x-yaml"; - provider.Mappings[".msix"] = "application/msix"; - provider.Mappings[".exe"] = "application/x-msdownload"; - provider.Mappings[".msi"] = "application/msi"; - provider.Mappings[".appx"] = "application/vns.ms-appx"; - provider.Mappings[".appxbundle"] = "application/vns.ms-appx"; - provider.Mappings[".mszyml"] = "application/x-ms-zip-yaml"; - provider.Mappings[".ttf"] = "application/x-font-ttf"; - - //Enable static file serving - app.UseStaticFiles(new StaticFileOptions - { - FileProvider = new PhysicalFileProvider(StaticFileRoot), - RequestPath = StaticFileRequestPath, - ContentTypeProvider = provider, - ServeUnknownFileTypes = true, - DefaultContentType = "application/octet-stream", - OnPrepareResponse = ctx => - { - // REST source information files are extensionless JSON; set the correct content type - // and prevent caching so that each Connect() call gets a fresh response and - // triggers certificate validation. - if (ctx.File.Name == "information") - { - ctx.Context.Response.ContentType = "application/json"; - ctx.Context.Response.Headers.CacheControl = "no-store"; - } - }, - }); - - app.UseDirectoryBrowser(new DirectoryBrowserOptions - { - FileProvider = new PhysicalFileProvider(StaticFileRoot), - RequestPath = StaticFileRequestPath - }); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapDefaultControllerRoute(); - }); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace LocalhostWebServer +{ + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Hosting; + using Microsoft.AspNetCore.StaticFiles; + using Microsoft.Extensions.Configuration; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Hosting; + + public class Startup + { + public const string StaticFileRequestPath = "/TestKit"; + + public static string StaticFileRoot { get; set; } + + public static string CertPath { get; set; } + + public static string CertPassword { get; set; } + + public static string OutCertFile { get; set; } + + public static int Port { get; set; } + + public static string LocalSourceJson { get; set; } + + public static string TestDataPath { get; set; } + + public static bool ExitBeforeRun { get; set; } + + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddControllersWithViews(); + } + + public IConfiguration Configuration { get; } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + //Add .yaml and .msix mappings + var provider = new FileExtensionContentTypeProvider(); + provider.Mappings[".yml"] = "application/x-yaml"; + provider.Mappings[".yaml"] = "application/x-yaml"; + provider.Mappings[".msix"] = "application/msix"; + provider.Mappings[".exe"] = "application/x-msdownload"; + provider.Mappings[".msi"] = "application/msi"; + provider.Mappings[".appx"] = "application/vns.ms-appx"; + provider.Mappings[".appxbundle"] = "application/vns.ms-appx"; + provider.Mappings[".mszyml"] = "application/x-ms-zip-yaml"; + provider.Mappings[".ttf"] = "application/x-font-ttf"; + + //Enable static file serving + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(StaticFileRoot), + RequestPath = StaticFileRequestPath, + ContentTypeProvider = provider, + ServeUnknownFileTypes = true, + DefaultContentType = "application/octet-stream", + OnPrepareResponse = ctx => + { + // REST source information files are extensionless JSON; set the correct content type + // and prevent caching so that each Connect() call gets a fresh response and + // triggers certificate validation. + if (ctx.File.Name == "information") + { + ctx.Context.Response.ContentType = "application/json"; + ctx.Context.Response.Headers.CacheControl = "no-store"; + } + }, + }); + + app.UseDirectoryBrowser(new DirectoryBrowserOptions + { + FileProvider = new PhysicalFileProvider(StaticFileRoot), + RequestPath = StaticFileRequestPath + }); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapDefaultControllerRoute(); + }); + } + } +} diff --git a/src/ManifestSchema/ManifestSchema.h b/src/ManifestSchema/ManifestSchema.h index 25a2fa9af8..5949ba6413 100644 --- a/src/ManifestSchema/ManifestSchema.h +++ b/src/ManifestSchema/ManifestSchema.h @@ -1,80 +1,80 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -// 0 is reserved to represent when there is no resource. -#define MANIFESTSCHEMA_NO_RESOURCE 0 - -#define MANIFESTSCHEMA_RESOURCE_TYPE 200 - -#define IDX_MANIFEST_SCHEMA_PREVIEW 201 - -#define IDX_MANIFEST_SCHEMA_V1_SINGLETON 202 -#define IDX_MANIFEST_SCHEMA_V1_VERSION 203 -#define IDX_MANIFEST_SCHEMA_V1_INSTALLER 204 -#define IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE 205 -#define IDX_MANIFEST_SCHEMA_V1_LOCALE 206 - -#define IDX_MANIFEST_SCHEMA_V1_1_SINGLETON 207 -#define IDX_MANIFEST_SCHEMA_V1_1_VERSION 208 -#define IDX_MANIFEST_SCHEMA_V1_1_INSTALLER 209 -#define IDX_MANIFEST_SCHEMA_V1_1_DEFAULTLOCALE 210 -#define IDX_MANIFEST_SCHEMA_V1_1_LOCALE 211 - -#define IDX_MANIFEST_SCHEMA_V1_2_SINGLETON 212 -#define IDX_MANIFEST_SCHEMA_V1_2_VERSION 213 -#define IDX_MANIFEST_SCHEMA_V1_2_INSTALLER 214 -#define IDX_MANIFEST_SCHEMA_V1_2_DEFAULTLOCALE 215 -#define IDX_MANIFEST_SCHEMA_V1_2_LOCALE 216 - -#define IDX_MANIFEST_SCHEMA_V1_4_SINGLETON 217 -#define IDX_MANIFEST_SCHEMA_V1_4_VERSION 218 -#define IDX_MANIFEST_SCHEMA_V1_4_INSTALLER 219 -#define IDX_MANIFEST_SCHEMA_V1_4_DEFAULTLOCALE 220 -#define IDX_MANIFEST_SCHEMA_V1_4_LOCALE 221 - -#define IDX_MANIFEST_SCHEMA_V1_5_SINGLETON 222 -#define IDX_MANIFEST_SCHEMA_V1_5_VERSION 223 -#define IDX_MANIFEST_SCHEMA_V1_5_INSTALLER 224 -#define IDX_MANIFEST_SCHEMA_V1_5_DEFAULTLOCALE 225 -#define IDX_MANIFEST_SCHEMA_V1_5_LOCALE 226 - -#define IDX_MANIFEST_SCHEMA_V1_6_SINGLETON 227 -#define IDX_MANIFEST_SCHEMA_V1_6_VERSION 228 -#define IDX_MANIFEST_SCHEMA_V1_6_INSTALLER 229 -#define IDX_MANIFEST_SCHEMA_V1_6_DEFAULTLOCALE 230 -#define IDX_MANIFEST_SCHEMA_V1_6_LOCALE 231 - -#define IDX_MANIFEST_SCHEMA_V1_7_SINGLETON 232 -#define IDX_MANIFEST_SCHEMA_V1_7_VERSION 233 -#define IDX_MANIFEST_SCHEMA_V1_7_INSTALLER 234 -#define IDX_MANIFEST_SCHEMA_V1_7_DEFAULTLOCALE 235 -#define IDX_MANIFEST_SCHEMA_V1_7_LOCALE 236 - -#define IDX_MANIFEST_SCHEMA_V1_9_SINGLETON 237 -#define IDX_MANIFEST_SCHEMA_V1_9_VERSION 238 -#define IDX_MANIFEST_SCHEMA_V1_9_INSTALLER 239 -#define IDX_MANIFEST_SCHEMA_V1_9_DEFAULTLOCALE 240 -#define IDX_MANIFEST_SCHEMA_V1_9_LOCALE 241 - -#define IDX_MANIFEST_SCHEMA_V1_10_SINGLETON 242 -#define IDX_MANIFEST_SCHEMA_V1_10_VERSION 243 -#define IDX_MANIFEST_SCHEMA_V1_10_INSTALLER 244 -#define IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE 245 -#define IDX_MANIFEST_SCHEMA_V1_10_LOCALE 246 - -#define IDX_MANIFEST_SCHEMA_V1_12_SINGLETON 247 -#define IDX_MANIFEST_SCHEMA_V1_12_VERSION 248 -#define IDX_MANIFEST_SCHEMA_V1_12_INSTALLER 249 -#define IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE 250 -#define IDX_MANIFEST_SCHEMA_V1_12_LOCALE 251 - -#define IDX_MANIFEST_SCHEMA_V1_28_SINGLETON 252 -#define IDX_MANIFEST_SCHEMA_V1_28_VERSION 253 -#define IDX_MANIFEST_SCHEMA_V1_28_INSTALLER 254 -#define IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE 255 -#define IDX_MANIFEST_SCHEMA_V1_28_LOCALE 256 - -// Packages schema starts at 300 -// Certificates start at 400 -// If we get to 300, either move the others or skip over them +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +// 0 is reserved to represent when there is no resource. +#define MANIFESTSCHEMA_NO_RESOURCE 0 + +#define MANIFESTSCHEMA_RESOURCE_TYPE 200 + +#define IDX_MANIFEST_SCHEMA_PREVIEW 201 + +#define IDX_MANIFEST_SCHEMA_V1_SINGLETON 202 +#define IDX_MANIFEST_SCHEMA_V1_VERSION 203 +#define IDX_MANIFEST_SCHEMA_V1_INSTALLER 204 +#define IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE 205 +#define IDX_MANIFEST_SCHEMA_V1_LOCALE 206 + +#define IDX_MANIFEST_SCHEMA_V1_1_SINGLETON 207 +#define IDX_MANIFEST_SCHEMA_V1_1_VERSION 208 +#define IDX_MANIFEST_SCHEMA_V1_1_INSTALLER 209 +#define IDX_MANIFEST_SCHEMA_V1_1_DEFAULTLOCALE 210 +#define IDX_MANIFEST_SCHEMA_V1_1_LOCALE 211 + +#define IDX_MANIFEST_SCHEMA_V1_2_SINGLETON 212 +#define IDX_MANIFEST_SCHEMA_V1_2_VERSION 213 +#define IDX_MANIFEST_SCHEMA_V1_2_INSTALLER 214 +#define IDX_MANIFEST_SCHEMA_V1_2_DEFAULTLOCALE 215 +#define IDX_MANIFEST_SCHEMA_V1_2_LOCALE 216 + +#define IDX_MANIFEST_SCHEMA_V1_4_SINGLETON 217 +#define IDX_MANIFEST_SCHEMA_V1_4_VERSION 218 +#define IDX_MANIFEST_SCHEMA_V1_4_INSTALLER 219 +#define IDX_MANIFEST_SCHEMA_V1_4_DEFAULTLOCALE 220 +#define IDX_MANIFEST_SCHEMA_V1_4_LOCALE 221 + +#define IDX_MANIFEST_SCHEMA_V1_5_SINGLETON 222 +#define IDX_MANIFEST_SCHEMA_V1_5_VERSION 223 +#define IDX_MANIFEST_SCHEMA_V1_5_INSTALLER 224 +#define IDX_MANIFEST_SCHEMA_V1_5_DEFAULTLOCALE 225 +#define IDX_MANIFEST_SCHEMA_V1_5_LOCALE 226 + +#define IDX_MANIFEST_SCHEMA_V1_6_SINGLETON 227 +#define IDX_MANIFEST_SCHEMA_V1_6_VERSION 228 +#define IDX_MANIFEST_SCHEMA_V1_6_INSTALLER 229 +#define IDX_MANIFEST_SCHEMA_V1_6_DEFAULTLOCALE 230 +#define IDX_MANIFEST_SCHEMA_V1_6_LOCALE 231 + +#define IDX_MANIFEST_SCHEMA_V1_7_SINGLETON 232 +#define IDX_MANIFEST_SCHEMA_V1_7_VERSION 233 +#define IDX_MANIFEST_SCHEMA_V1_7_INSTALLER 234 +#define IDX_MANIFEST_SCHEMA_V1_7_DEFAULTLOCALE 235 +#define IDX_MANIFEST_SCHEMA_V1_7_LOCALE 236 + +#define IDX_MANIFEST_SCHEMA_V1_9_SINGLETON 237 +#define IDX_MANIFEST_SCHEMA_V1_9_VERSION 238 +#define IDX_MANIFEST_SCHEMA_V1_9_INSTALLER 239 +#define IDX_MANIFEST_SCHEMA_V1_9_DEFAULTLOCALE 240 +#define IDX_MANIFEST_SCHEMA_V1_9_LOCALE 241 + +#define IDX_MANIFEST_SCHEMA_V1_10_SINGLETON 242 +#define IDX_MANIFEST_SCHEMA_V1_10_VERSION 243 +#define IDX_MANIFEST_SCHEMA_V1_10_INSTALLER 244 +#define IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE 245 +#define IDX_MANIFEST_SCHEMA_V1_10_LOCALE 246 + +#define IDX_MANIFEST_SCHEMA_V1_12_SINGLETON 247 +#define IDX_MANIFEST_SCHEMA_V1_12_VERSION 248 +#define IDX_MANIFEST_SCHEMA_V1_12_INSTALLER 249 +#define IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE 250 +#define IDX_MANIFEST_SCHEMA_V1_12_LOCALE 251 + +#define IDX_MANIFEST_SCHEMA_V1_28_SINGLETON 252 +#define IDX_MANIFEST_SCHEMA_V1_28_VERSION 253 +#define IDX_MANIFEST_SCHEMA_V1_28_INSTALLER 254 +#define IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE 255 +#define IDX_MANIFEST_SCHEMA_V1_28_LOCALE 256 + +// Packages schema starts at 300 +// Certificates start at 400 +// If we get to 300, either move the others or skip over them diff --git a/src/ManifestSchema/ManifestSchema.rc b/src/ManifestSchema/ManifestSchema.rc index ca98f61e7f..5973c89dd3 100644 --- a/src/ManifestSchema/ManifestSchema.rc +++ b/src/ManifestSchema/ManifestSchema.rc @@ -1,132 +1,132 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" -#include "ManifestSchema.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE 9, 1 - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - -///////////////////////////////////////////////////////////////////////////// -// -// Manifest schema -// -IDX_MANIFEST_SCHEMA_PREVIEW MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\preview\\manifest.0.1.0.json" - -IDX_MANIFEST_SCHEMA_V1_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.singleton.1.0.0.json" -IDX_MANIFEST_SCHEMA_V1_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.version.1.0.0.json" -IDX_MANIFEST_SCHEMA_V1_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.installer.1.0.0.json" -IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.defaultLocale.1.0.0.json" -IDX_MANIFEST_SCHEMA_V1_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.locale.1.0.0.json" - -IDX_MANIFEST_SCHEMA_V1_1_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.singleton.1.1.0.json" -IDX_MANIFEST_SCHEMA_V1_1_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.version.1.1.0.json" -IDX_MANIFEST_SCHEMA_V1_1_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.installer.1.1.0.json" -IDX_MANIFEST_SCHEMA_V1_1_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.defaultLocale.1.1.0.json" -IDX_MANIFEST_SCHEMA_V1_1_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.locale.1.1.0.json" - -IDX_MANIFEST_SCHEMA_V1_2_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.singleton.1.2.0.json" -IDX_MANIFEST_SCHEMA_V1_2_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.version.1.2.0.json" -IDX_MANIFEST_SCHEMA_V1_2_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.installer.1.2.0.json" -IDX_MANIFEST_SCHEMA_V1_2_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.defaultLocale.1.2.0.json" -IDX_MANIFEST_SCHEMA_V1_2_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.locale.1.2.0.json" - -IDX_MANIFEST_SCHEMA_V1_4_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.singleton.1.4.0.json" -IDX_MANIFEST_SCHEMA_V1_4_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.version.1.4.0.json" -IDX_MANIFEST_SCHEMA_V1_4_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.installer.1.4.0.json" -IDX_MANIFEST_SCHEMA_V1_4_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.defaultLocale.1.4.0.json" -IDX_MANIFEST_SCHEMA_V1_4_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.locale.1.4.0.json" - -IDX_MANIFEST_SCHEMA_V1_5_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.singleton.1.5.0.json" -IDX_MANIFEST_SCHEMA_V1_5_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.version.1.5.0.json" -IDX_MANIFEST_SCHEMA_V1_5_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.installer.1.5.0.json" -IDX_MANIFEST_SCHEMA_V1_5_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.defaultLocale.1.5.0.json" -IDX_MANIFEST_SCHEMA_V1_5_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.locale.1.5.0.json" - -IDX_MANIFEST_SCHEMA_V1_6_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.singleton.1.6.0.json" -IDX_MANIFEST_SCHEMA_V1_6_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.version.1.6.0.json" -IDX_MANIFEST_SCHEMA_V1_6_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.installer.1.6.0.json" -IDX_MANIFEST_SCHEMA_V1_6_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.defaultLocale.1.6.0.json" -IDX_MANIFEST_SCHEMA_V1_6_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.locale.1.6.0.json" - -IDX_MANIFEST_SCHEMA_V1_7_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.singleton.1.7.0.json" -IDX_MANIFEST_SCHEMA_V1_7_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.version.1.7.0.json" -IDX_MANIFEST_SCHEMA_V1_7_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.installer.1.7.0.json" -IDX_MANIFEST_SCHEMA_V1_7_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.defaultLocale.1.7.0.json" -IDX_MANIFEST_SCHEMA_V1_7_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.locale.1.7.0.json" - -IDX_MANIFEST_SCHEMA_V1_9_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.singleton.1.9.0.json" -IDX_MANIFEST_SCHEMA_V1_9_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.version.1.9.0.json" -IDX_MANIFEST_SCHEMA_V1_9_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.installer.1.9.0.json" -IDX_MANIFEST_SCHEMA_V1_9_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.defaultLocale.1.9.0.json" -IDX_MANIFEST_SCHEMA_V1_9_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.locale.1.9.0.json" - -IDX_MANIFEST_SCHEMA_V1_10_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.singleton.1.10.0.json" -IDX_MANIFEST_SCHEMA_V1_10_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.version.1.10.0.json" -IDX_MANIFEST_SCHEMA_V1_10_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.installer.1.10.0.json" -IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.defaultLocale.1.10.0.json" -IDX_MANIFEST_SCHEMA_V1_10_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.locale.1.10.0.json" - -IDX_MANIFEST_SCHEMA_V1_12_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.singleton.1.12.0.json" -IDX_MANIFEST_SCHEMA_V1_12_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.version.1.12.0.json" -IDX_MANIFEST_SCHEMA_V1_12_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.installer.1.12.0.json" -IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.defaultLocale.1.12.0.json" -IDX_MANIFEST_SCHEMA_V1_12_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.locale.1.12.0.json" - -IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" -IDX_MANIFEST_SCHEMA_V1_28_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "ManifestSchema.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Manifest schema +// +IDX_MANIFEST_SCHEMA_PREVIEW MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\preview\\manifest.0.1.0.json" + +IDX_MANIFEST_SCHEMA_V1_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.singleton.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.version.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.installer.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.defaultLocale.1.0.0.json" +IDX_MANIFEST_SCHEMA_V1_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.0.0\\manifest.locale.1.0.0.json" + +IDX_MANIFEST_SCHEMA_V1_1_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.singleton.1.1.0.json" +IDX_MANIFEST_SCHEMA_V1_1_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.version.1.1.0.json" +IDX_MANIFEST_SCHEMA_V1_1_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.installer.1.1.0.json" +IDX_MANIFEST_SCHEMA_V1_1_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.defaultLocale.1.1.0.json" +IDX_MANIFEST_SCHEMA_V1_1_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.1.0\\manifest.locale.1.1.0.json" + +IDX_MANIFEST_SCHEMA_V1_2_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.singleton.1.2.0.json" +IDX_MANIFEST_SCHEMA_V1_2_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.version.1.2.0.json" +IDX_MANIFEST_SCHEMA_V1_2_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.installer.1.2.0.json" +IDX_MANIFEST_SCHEMA_V1_2_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.defaultLocale.1.2.0.json" +IDX_MANIFEST_SCHEMA_V1_2_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.2.0\\manifest.locale.1.2.0.json" + +IDX_MANIFEST_SCHEMA_V1_4_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.singleton.1.4.0.json" +IDX_MANIFEST_SCHEMA_V1_4_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.version.1.4.0.json" +IDX_MANIFEST_SCHEMA_V1_4_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.installer.1.4.0.json" +IDX_MANIFEST_SCHEMA_V1_4_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.defaultLocale.1.4.0.json" +IDX_MANIFEST_SCHEMA_V1_4_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.4.0\\manifest.locale.1.4.0.json" + +IDX_MANIFEST_SCHEMA_V1_5_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.singleton.1.5.0.json" +IDX_MANIFEST_SCHEMA_V1_5_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.version.1.5.0.json" +IDX_MANIFEST_SCHEMA_V1_5_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.installer.1.5.0.json" +IDX_MANIFEST_SCHEMA_V1_5_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.defaultLocale.1.5.0.json" +IDX_MANIFEST_SCHEMA_V1_5_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.5.0\\manifest.locale.1.5.0.json" + +IDX_MANIFEST_SCHEMA_V1_6_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.singleton.1.6.0.json" +IDX_MANIFEST_SCHEMA_V1_6_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.version.1.6.0.json" +IDX_MANIFEST_SCHEMA_V1_6_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.installer.1.6.0.json" +IDX_MANIFEST_SCHEMA_V1_6_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.defaultLocale.1.6.0.json" +IDX_MANIFEST_SCHEMA_V1_6_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.6.0\\manifest.locale.1.6.0.json" + +IDX_MANIFEST_SCHEMA_V1_7_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.singleton.1.7.0.json" +IDX_MANIFEST_SCHEMA_V1_7_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.version.1.7.0.json" +IDX_MANIFEST_SCHEMA_V1_7_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.installer.1.7.0.json" +IDX_MANIFEST_SCHEMA_V1_7_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.defaultLocale.1.7.0.json" +IDX_MANIFEST_SCHEMA_V1_7_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.7.0\\manifest.locale.1.7.0.json" + +IDX_MANIFEST_SCHEMA_V1_9_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.singleton.1.9.0.json" +IDX_MANIFEST_SCHEMA_V1_9_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.version.1.9.0.json" +IDX_MANIFEST_SCHEMA_V1_9_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.installer.1.9.0.json" +IDX_MANIFEST_SCHEMA_V1_9_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.defaultLocale.1.9.0.json" +IDX_MANIFEST_SCHEMA_V1_9_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.9.0\\manifest.locale.1.9.0.json" + +IDX_MANIFEST_SCHEMA_V1_10_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.singleton.1.10.0.json" +IDX_MANIFEST_SCHEMA_V1_10_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.version.1.10.0.json" +IDX_MANIFEST_SCHEMA_V1_10_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.installer.1.10.0.json" +IDX_MANIFEST_SCHEMA_V1_10_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.defaultLocale.1.10.0.json" +IDX_MANIFEST_SCHEMA_V1_10_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.10.0\\manifest.locale.1.10.0.json" + +IDX_MANIFEST_SCHEMA_V1_12_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.singleton.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.version.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.installer.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.defaultLocale.1.12.0.json" +IDX_MANIFEST_SCHEMA_V1_12_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\v1.12.0\\manifest.locale.1.12.0.json" + +IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json" +IDX_MANIFEST_SCHEMA_V1_28_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json" diff --git a/src/ManifestSchema/ManifestSchema.vcxitems b/src/ManifestSchema/ManifestSchema.vcxitems index a45d704ce5..31c42f299f 100644 --- a/src/ManifestSchema/ManifestSchema.vcxitems +++ b/src/ManifestSchema/ManifestSchema.vcxitems @@ -1,81 +1,81 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - {7d05f64d-ce5a-42aa-a2c1-e91458f061cf} - - - - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {7d05f64d-ce5a-42aa-a2c1-e91458f061cf} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ManifestSchema/ManifestSchema.vcxitems.filters b/src/ManifestSchema/ManifestSchema.vcxitems.filters index 6bc5c732d7..8760fd710d 100644 --- a/src/ManifestSchema/ManifestSchema.vcxitems.filters +++ b/src/ManifestSchema/ManifestSchema.vcxitems.filters @@ -1,221 +1,221 @@ - - - - - {d6998b42-8ce1-440a-ad12-8b505a284d7b} - - - {f392b73a-3389-4d29-86d9-539a98e2bf3b} - - - {fe494b58-5e5c-415a-922f-c6e10725e06a} - - - {74616d1e-e987-4c28-bd85-7bbd205ee813} - - - {36190723-3948-462f-97c2-47fa1b770826} - - - {fb3524f4-5541-468d-a33c-ddcdad90484d} - - - {422a18e9-7735-412d-b16b-98d9d53a1632} - - - {27ca448e-4d6f-4039-bc55-46a7df7c69e2} - - - {d14a3093-eaa9-49d0-9fc4-fbc596afa673} - - - {040a7f05-fd31-466c-bb71-0d59e4cdf1c4} - - - {f7069050-9235-44e0-ab07-72e4addfd765} - - - {9c132cfd-cf4c-48d0-a32b-c52213f742fe} - - - {99217106-7710-40c7-a2df-e5b69c758e5a} - - - - - - - - - - - - schema\latest - - - schema\latest - - - schema\latest - - - schema\latest - - - schema\latest - - - schema\preview - - - schema\v1.0.0 - - - schema\v1.0.0 - - - schema\v1.0.0 - - - schema\v1.0.0 - - - schema\v1.0.0 - - - schema\v1.1.0 - - - schema\v1.1.0 - - - schema\v1.1.0 - - - schema\v1.1.0 - - - schema\v1.1.0 - - - schema\v1.2.0 - - - schema\v1.2.0 - - - schema\v1.2.0 - - - schema\v1.2.0 - - - schema\v1.2.0 - - - schema\v1.4.0 - - - schema\v1.4.0 - - - schema\v1.4.0 - - - schema\v1.4.0 - - - schema\v1.4.0 - - - schema\v1.5.0 - - - schema\v1.5.0 - - - schema\v1.5.0 - - - schema\v1.5.0 - - - schema\v1.5.0 - - - schema\v1.6.0 - - - schema\v1.6.0 - - - schema\v1.6.0 - - - schema\v1.6.0 - - - schema\v1.6.0 - - - schema\v1.7.0 - - - schema\v1.7.0 - - - schema\v1.7.0 - - - schema\v1.7.0 - - - schema\v1.7.0 - - - schema\v1.9.0 - - - schema\v1.9.0 - - - schema\v1.9.0 - - - schema\v1.9.0 - - - schema\v1.9.0 - - - schema\v1.10.0 - - - schema\v1.10.0 - - - schema\v1.10.0 - - - schema\v1.10.0 - - - schema\v1.10.0 - - - schema\v1.12.0 - - - schema\v1.12.0 - - - schema\v1.12.0 - - - schema\v1.12.0 - - - schema\v1.12.0 - - + + + + + {d6998b42-8ce1-440a-ad12-8b505a284d7b} + + + {f392b73a-3389-4d29-86d9-539a98e2bf3b} + + + {fe494b58-5e5c-415a-922f-c6e10725e06a} + + + {74616d1e-e987-4c28-bd85-7bbd205ee813} + + + {36190723-3948-462f-97c2-47fa1b770826} + + + {fb3524f4-5541-468d-a33c-ddcdad90484d} + + + {422a18e9-7735-412d-b16b-98d9d53a1632} + + + {27ca448e-4d6f-4039-bc55-46a7df7c69e2} + + + {d14a3093-eaa9-49d0-9fc4-fbc596afa673} + + + {040a7f05-fd31-466c-bb71-0d59e4cdf1c4} + + + {f7069050-9235-44e0-ab07-72e4addfd765} + + + {9c132cfd-cf4c-48d0-a32b-c52213f742fe} + + + {99217106-7710-40c7-a2df-e5b69c758e5a} + + + + + + + + + + + + schema\latest + + + schema\latest + + + schema\latest + + + schema\latest + + + schema\latest + + + schema\preview + + + schema\v1.0.0 + + + schema\v1.0.0 + + + schema\v1.0.0 + + + schema\v1.0.0 + + + schema\v1.0.0 + + + schema\v1.1.0 + + + schema\v1.1.0 + + + schema\v1.1.0 + + + schema\v1.1.0 + + + schema\v1.1.0 + + + schema\v1.2.0 + + + schema\v1.2.0 + + + schema\v1.2.0 + + + schema\v1.2.0 + + + schema\v1.2.0 + + + schema\v1.4.0 + + + schema\v1.4.0 + + + schema\v1.4.0 + + + schema\v1.4.0 + + + schema\v1.4.0 + + + schema\v1.5.0 + + + schema\v1.5.0 + + + schema\v1.5.0 + + + schema\v1.5.0 + + + schema\v1.5.0 + + + schema\v1.6.0 + + + schema\v1.6.0 + + + schema\v1.6.0 + + + schema\v1.6.0 + + + schema\v1.6.0 + + + schema\v1.7.0 + + + schema\v1.7.0 + + + schema\v1.7.0 + + + schema\v1.7.0 + + + schema\v1.7.0 + + + schema\v1.9.0 + + + schema\v1.9.0 + + + schema\v1.9.0 + + + schema\v1.9.0 + + + schema\v1.9.0 + + + schema\v1.10.0 + + + schema\v1.10.0 + + + schema\v1.10.0 + + + schema\v1.10.0 + + + schema\v1.10.0 + + + schema\v1.12.0 + + + schema\v1.12.0 + + + schema\v1.12.0 + + + schema\v1.12.0 + + + schema\v1.12.0 + + \ No newline at end of file diff --git a/src/ManifestSchema/resource.h b/src/ManifestSchema/resource.h index 1f8b4abe6d..b966896e9b 100644 --- a/src/ManifestSchema/resource.h +++ b/src/ManifestSchema/resource.h @@ -1,14 +1,14 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by ManifestSchema.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by ManifestSchema.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Collect-ConfigurationOOPTests.ps1 b/src/Microsoft.Management.Configuration.OutOfProc/Collect-ConfigurationOOPTests.ps1 index f072588c27..fd805fd9ec 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Collect-ConfigurationOOPTests.ps1 +++ b/src/Microsoft.Management.Configuration.OutOfProc/Collect-ConfigurationOOPTests.ps1 @@ -1,13 +1,13 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[CmdletBinding()] -param( - [string]$TargetLocation -) - -$Local:settingsExport = ConvertFrom-Json (wingetdev.exe settings export) -$Local:logsFilePath = Join-Path (Split-Path $Local:settingsExport.userSettingsFile -Parent) "DiagOutputDir" - -Get-AppxPackage WinGetDevCLI | Remove-AppxPackage - -Copy-Item $Local:logsFilePath $TargetLocation -Recurse -Force -ErrorAction Ignore +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + [string]$TargetLocation +) + +$Local:settingsExport = ConvertFrom-Json (wingetdev.exe settings export) +$Local:logsFilePath = Join-Path (Split-Path $Local:settingsExport.userSettingsFile -Parent) "DiagOutputDir" + +Get-AppxPackage WinGetDevCLI | Remove-AppxPackage + +Copy-Item $Local:logsFilePath $TargetLocation -Recurse -Force -ErrorAction Ignore diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Factory.cpp b/src/Microsoft.Management.Configuration.OutOfProc/Factory.cpp index 6b58e05f91..25f12182ec 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Factory.cpp +++ b/src/Microsoft.Management.Configuration.OutOfProc/Factory.cpp @@ -1,132 +1,132 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Factory.h" -#include -#include -#include - -namespace Microsoft::Management::Configuration::OutOfProc -{ - namespace - { - const CLSID& GetConfigurationStaticsCLSID() - { -#if USE_PROD_CLSIDS - static const CLSID CLSID_ConfigurationStatics = { 0x73d763b7,0x2937,0x432f,{0xa9,0x7a,0xd9,0x8a,0x4a,0x59,0x61,0x26} }; // 73D763B7-2937-432F-A97A-D98A4A596126 -#else - static const CLSID CLSID_ConfigurationStatics = { 0xc9ed7917,0x66ab,0x4e31,{0xa9,0x2a,0xf6,0x5f,0x18,0xef,0x79,0x33} }; // C9ED7917-66AB-4E31-A92A-F65F18EF7933 -#endif - - return CLSID_ConfigurationStatics; - } - - winrt::Microsoft::Management::Configuration::IConfigurationStatics CreateOOPStaticsObject() - { - bool isAdmin = AppInstaller::Runtime::IsRunningAsAdmin(); - - try - { - return winrt::create_instance(GetConfigurationStaticsCLSID(), CLSCTX_LOCAL_SERVER | CLSCTX_NO_CODE_DOWNLOAD); - } - catch (const winrt::hresult_error& hre) - { - // We only want to fall through to trying the manual activation if we are running as admin and couldn't find the registration. - if (!(isAdmin && hre.code() == REGDB_E_CLASSNOTREG)) - { - throw; - } - } - - winrt::com_ptr<::IUnknown> result; - THROW_IF_FAILED(WinGetServerManualActivation_CreateInstance(GetConfigurationStaticsCLSID(), winrt::guid_of(), 0, result.put_void())); - return result.as(); - } - } - - Factory::Factory() - { - IncrementRefCount(); - } - - Factory::~Factory() - { - DecrementRefCount(); - } - - bool Factory::HasReferences() - { - return s_referenceCount.load() != 0; - } - - void Factory::Terminate() - { - WinGetServerManualActivation_Terminate(); - } - - bool Factory::IsCLSID(const GUID& clsid) - { - if (clsid == GetConfigurationStaticsCLSID()) - { - return true; - } - - return false; - } - - bool Factory::IsCLSID(HSTRING clsid) - { - constexpr std::wstring_view s_ClassName = L"Microsoft.Management.Configuration.ConfigurationStaticFunctions"; - - UINT32 length = 0; - PCWSTR buffer = WindowsGetStringRawBuffer(clsid, &length); - - if (std::wstring_view{ buffer, length } == s_ClassName) - { - return true; - } - - return false; - } - - winrt::Windows::Foundation::IInspectable Factory::ActivateInstance() - { - return CreateOOPStaticsObject().as(); - } - - HRESULT STDMETHODCALLTYPE Factory::CreateInstance(::IUnknown* pUnkOuter, REFIID riid, void** ppvObject) try - { - RETURN_HR_IF(E_POINTER, !ppvObject); - *ppvObject = nullptr; - RETURN_HR_IF(CLASS_E_NOAGGREGATION, pUnkOuter != nullptr); - - return CreateOOPStaticsObject().as(riid, ppvObject); - } - CATCH_RETURN(); - - HRESULT STDMETHODCALLTYPE Factory::LockServer(BOOL fLock) - { - if (fLock) - { - IncrementRefCount(); - } - else - { - DecrementRefCount(); - } - - return S_OK; - } - - void Factory::IncrementRefCount() - { - ++s_referenceCount; - } - - void Factory::DecrementRefCount() - { - --s_referenceCount; - } - - std::atomic Factory::s_referenceCount = ATOMIC_VAR_INIT(0); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Factory.h" +#include +#include +#include + +namespace Microsoft::Management::Configuration::OutOfProc +{ + namespace + { + const CLSID& GetConfigurationStaticsCLSID() + { +#if USE_PROD_CLSIDS + static const CLSID CLSID_ConfigurationStatics = { 0x73d763b7,0x2937,0x432f,{0xa9,0x7a,0xd9,0x8a,0x4a,0x59,0x61,0x26} }; // 73D763B7-2937-432F-A97A-D98A4A596126 +#else + static const CLSID CLSID_ConfigurationStatics = { 0xc9ed7917,0x66ab,0x4e31,{0xa9,0x2a,0xf6,0x5f,0x18,0xef,0x79,0x33} }; // C9ED7917-66AB-4E31-A92A-F65F18EF7933 +#endif + + return CLSID_ConfigurationStatics; + } + + winrt::Microsoft::Management::Configuration::IConfigurationStatics CreateOOPStaticsObject() + { + bool isAdmin = AppInstaller::Runtime::IsRunningAsAdmin(); + + try + { + return winrt::create_instance(GetConfigurationStaticsCLSID(), CLSCTX_LOCAL_SERVER | CLSCTX_NO_CODE_DOWNLOAD); + } + catch (const winrt::hresult_error& hre) + { + // We only want to fall through to trying the manual activation if we are running as admin and couldn't find the registration. + if (!(isAdmin && hre.code() == REGDB_E_CLASSNOTREG)) + { + throw; + } + } + + winrt::com_ptr<::IUnknown> result; + THROW_IF_FAILED(WinGetServerManualActivation_CreateInstance(GetConfigurationStaticsCLSID(), winrt::guid_of(), 0, result.put_void())); + return result.as(); + } + } + + Factory::Factory() + { + IncrementRefCount(); + } + + Factory::~Factory() + { + DecrementRefCount(); + } + + bool Factory::HasReferences() + { + return s_referenceCount.load() != 0; + } + + void Factory::Terminate() + { + WinGetServerManualActivation_Terminate(); + } + + bool Factory::IsCLSID(const GUID& clsid) + { + if (clsid == GetConfigurationStaticsCLSID()) + { + return true; + } + + return false; + } + + bool Factory::IsCLSID(HSTRING clsid) + { + constexpr std::wstring_view s_ClassName = L"Microsoft.Management.Configuration.ConfigurationStaticFunctions"; + + UINT32 length = 0; + PCWSTR buffer = WindowsGetStringRawBuffer(clsid, &length); + + if (std::wstring_view{ buffer, length } == s_ClassName) + { + return true; + } + + return false; + } + + winrt::Windows::Foundation::IInspectable Factory::ActivateInstance() + { + return CreateOOPStaticsObject().as(); + } + + HRESULT STDMETHODCALLTYPE Factory::CreateInstance(::IUnknown* pUnkOuter, REFIID riid, void** ppvObject) try + { + RETURN_HR_IF(E_POINTER, !ppvObject); + *ppvObject = nullptr; + RETURN_HR_IF(CLASS_E_NOAGGREGATION, pUnkOuter != nullptr); + + return CreateOOPStaticsObject().as(riid, ppvObject); + } + CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE Factory::LockServer(BOOL fLock) + { + if (fLock) + { + IncrementRefCount(); + } + else + { + DecrementRefCount(); + } + + return S_OK; + } + + void Factory::IncrementRefCount() + { + ++s_referenceCount; + } + + void Factory::DecrementRefCount() + { + --s_referenceCount; + } + + std::atomic Factory::s_referenceCount = ATOMIC_VAR_INIT(0); +} diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Factory.h b/src/Microsoft.Management.Configuration.OutOfProc/Factory.h index 33e2ba2b77..301105583b 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Factory.h +++ b/src/Microsoft.Management.Configuration.OutOfProc/Factory.h @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace Microsoft::Management::Configuration::OutOfProc -{ - struct Factory : winrt::implements - { - Factory(); - ~Factory(); - - // Returns true if the reference count is not 0; false if it is. - static bool HasReferences(); - - // Forcibly destroys any static objects. - static void Terminate(); - - // Determines if the given CLSID is the CLSID for the factory. - static bool IsCLSID(const GUID& clsid); - - // Determines if the given CLSID is the CLSID for the factory. - static bool IsCLSID(HSTRING clsid); - - // IActivationFactory - winrt::Windows::Foundation::IInspectable ActivateInstance(); - - // IClassFactory - HRESULT STDMETHODCALLTYPE CreateInstance(::IUnknown *pUnkOuter, REFIID riid, void **ppvObject); - HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock); - - private: - static void IncrementRefCount(); - static void DecrementRefCount(); - - static std::atomic s_referenceCount; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace Microsoft::Management::Configuration::OutOfProc +{ + struct Factory : winrt::implements + { + Factory(); + ~Factory(); + + // Returns true if the reference count is not 0; false if it is. + static bool HasReferences(); + + // Forcibly destroys any static objects. + static void Terminate(); + + // Determines if the given CLSID is the CLSID for the factory. + static bool IsCLSID(const GUID& clsid); + + // Determines if the given CLSID is the CLSID for the factory. + static bool IsCLSID(HSTRING clsid); + + // IActivationFactory + winrt::Windows::Foundation::IInspectable ActivateInstance(); + + // IClassFactory + HRESULT STDMETHODCALLTYPE CreateInstance(::IUnknown *pUnkOuter, REFIID riid, void **ppvObject); + HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock); + + private: + static void IncrementRefCount(); + static void DecrementRefCount(); + + static std::atomic s_referenceCount; + }; +} diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj b/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj index e6e62e5c0c..d52da7bcf5 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj +++ b/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj @@ -1,427 +1,427 @@ - - - - - true - true - true - 15.0 - Win32Proj - MicrosoftManagementConfigurationOutOfProc - 10.0.26100.0 - 10.0.17763.0 - true - false - {2268D5AD-7F2A-485A-8C4B-C574497514C9} - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - DynamicLibrary - - - true - true - - - false - true - false - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - true - false - false - stdcpp17 - stdcpp17 - true - true - true - true - 6001 - 6001 - false - false - - - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - WIN32;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - false - stdcpp17 - true - true - 6001 - false - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - true - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - 6001 - 6001 - 6001 - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - 6001 - 6001 - 6001 - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - Create - - - - - - - - - - - - {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} - - - {ca460806-5e41-4e97-9a3d-1d74b433b663} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - + + + + + true + true + true + 15.0 + Win32Proj + MicrosoftManagementConfigurationOutOfProc + 10.0.26100.0 + 10.0.17763.0 + true + false + {2268D5AD-7F2A-485A-8C4B-C574497514C9} + + + + + Debug + ARM64 + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + DynamicLibrary + + + true + true + + + false + true + false + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + true + false + false + stdcpp17 + stdcpp17 + true + true + true + true + 6001 + 6001 + false + false + + + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + WIN32;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + true + 6001 + false + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + true + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + 6001 + 6001 + 6001 + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + 6001 + 6001 + 6001 + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + + + Create + + + + + + + + + + + + {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} + + + {ca460806-5e41-4e97-9a3d-1d74b433b663} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj.filters b/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj.filters index 3ed03d3635..15c52c8a46 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj.filters +++ b/src/Microsoft.Management.Configuration.OutOfProc/Microsoft.Management.Configuration.OutOfProc.vcxproj.filters @@ -1,60 +1,60 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {6017fd94-3eb1-40bc-964f-5dd571077d3c} - - - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - WinGetServerManualActivation - - - WinGetServerManualActivation - - - WinGetServerManualActivation - - - - - - - Source Files - - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {6017fd94-3eb1-40bc-964f-5dd571077d3c} + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + WinGetServerManualActivation + + + WinGetServerManualActivation + + + WinGetServerManualActivation + + + + + + + Source Files + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 b/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 index 96c925e58c..9dd8a0c5d8 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 +++ b/src/Microsoft.Management.Configuration.OutOfProc/Prepare-ConfigurationOOPTests.ps1 @@ -1,35 +1,35 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[CmdletBinding()] -param( - [string]$BuildOutputPath, - - [string]$PackageLayoutPath -) - -# 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\net8.0-windows10.0.26100.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\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.dll" - -Copy-Item $Local:dllSourcePath $Local:dllTargetPath -Force - -# Register the package -if (-not [System.String]::IsNullOrEmpty($PackageLayoutPath)) -{ - $Local:packageManifestPath = Join-Path $PackageLayoutPath "AppxManifest.xml" - - Add-AppxPackage -ForceApplicationShutdown -Register $Local:packageManifestPath - - # Configure crash dump and log file settings - $Local:settingsExport = ConvertFrom-Json (wingetdev.exe settings export) - $Local:settingsFilePath = $Local:settingsExport.userSettingsFile - $Local:settingsFileContent = ConvertTo-Json @{ debugging= @{ enableSelfInitiatedMinidump=$true ; keepAllLogFiles=$true } ; experimentalFeatures= @{ configuration03=$true } } - - Set-Content -Path $Local:settingsFilePath -Value $Local:settingsFileContent -} +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + [string]$BuildOutputPath, + + [string]$PackageLayoutPath +) + +# 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\net8.0-windows10.0.26100.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\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.dll" + +Copy-Item $Local:dllSourcePath $Local:dllTargetPath -Force + +# Register the package +if (-not [System.String]::IsNullOrEmpty($PackageLayoutPath)) +{ + $Local:packageManifestPath = Join-Path $PackageLayoutPath "AppxManifest.xml" + + Add-AppxPackage -ForceApplicationShutdown -Register $Local:packageManifestPath + + # Configure crash dump and log file settings + $Local:settingsExport = ConvertFrom-Json (wingetdev.exe settings export) + $Local:settingsFilePath = $Local:settingsExport.userSettingsFile + $Local:settingsFileContent = ConvertTo-Json @{ debugging= @{ enableSelfInitiatedMinidump=$true ; keepAllLogFiles=$true } ; experimentalFeatures= @{ configuration03=$true } } + + Set-Content -Path $Local:settingsFilePath -Value $Local:settingsFileContent +} diff --git a/src/Microsoft.Management.Configuration.OutOfProc/Run-ConfigurationOOPTests.ps1 b/src/Microsoft.Management.Configuration.OutOfProc/Run-ConfigurationOOPTests.ps1 index 12f8ec642f..5a9247eb28 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/Run-ConfigurationOOPTests.ps1 +++ b/src/Microsoft.Management.Configuration.OutOfProc/Run-ConfigurationOOPTests.ps1 @@ -1,69 +1,69 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[CmdletBinding()] -param( - [string]$BuildOutputPath, - - [string]$PackageLayoutPath, - - [string]$TestCaseFilter, - - [string]$Platform = "x64", - - [string]$Configuration = "Debug" -) - -# Derive BuildOutputPath from the repo root if not specified -if ([System.String]::IsNullOrEmpty($BuildOutputPath)) -{ - $Local:repoRoot = git -C $PSScriptRoot rev-parse --show-toplevel - $BuildOutputPath = Join-Path $Local:repoRoot "src" $Platform $Configuration -} - -# Step 1: Prepare the test output directory -$Local:prepareScript = Join-Path $PSScriptRoot "Prepare-ConfigurationOOPTests.ps1" -& $Local:prepareScript -BuildOutputPath $BuildOutputPath -PackageLayoutPath $PackageLayoutPath - -if (-not $?) -{ - Write-Error "Preparation script failed" - exit 1 -} - -# Step 2: Find vstest.console.exe via vswhere -$Local:vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" -if (-not (Test-Path $Local:vswhere)) -{ - Write-Error "vswhere.exe not found at '$Local:vswhere'. Is Visual Studio installed?" - exit 1 -} - -$Local:vsInstallPath = & $Local:vswhere -latest -products * -requires Microsoft.VisualStudio.PackageGroup.TestTools.Core -property installationPath -if ([System.String]::IsNullOrEmpty($Local:vsInstallPath)) -{ - Write-Error "Could not find a Visual Studio installation with test tools via vswhere." - exit 1 -} - -$Local:vstestPath = Join-Path $Local:vsInstallPath "Common7\IDE\Extensions\TestPlatform\vstest.console.exe" -if (-not (Test-Path $Local:vstestPath)) -{ - Write-Error "vstest.console.exe not found at '$Local:vstestPath'." - exit 1 -} - -# Step 3: Run the tests with vstest -$Local:testDll = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.UnitTests.dll" - -$Local:vstestArgs = @( - $Local:testDll, - "--logger:console;verbosity=detailed" -) - -if (-not [System.String]::IsNullOrEmpty($TestCaseFilter)) -{ - $Local:vstestArgs += "--TestCaseFilter:$TestCaseFilter" -} - -& $Local:vstestPath @Local:vstestArgs -exit $LASTEXITCODE +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + [string]$BuildOutputPath, + + [string]$PackageLayoutPath, + + [string]$TestCaseFilter, + + [string]$Platform = "x64", + + [string]$Configuration = "Debug" +) + +# Derive BuildOutputPath from the repo root if not specified +if ([System.String]::IsNullOrEmpty($BuildOutputPath)) +{ + $Local:repoRoot = git -C $PSScriptRoot rev-parse --show-toplevel + $BuildOutputPath = Join-Path $Local:repoRoot "src" $Platform $Configuration +} + +# Step 1: Prepare the test output directory +$Local:prepareScript = Join-Path $PSScriptRoot "Prepare-ConfigurationOOPTests.ps1" +& $Local:prepareScript -BuildOutputPath $BuildOutputPath -PackageLayoutPath $PackageLayoutPath + +if (-not $?) +{ + Write-Error "Preparation script failed" + exit 1 +} + +# Step 2: Find vstest.console.exe via vswhere +$Local:vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" +if (-not (Test-Path $Local:vswhere)) +{ + Write-Error "vswhere.exe not found at '$Local:vswhere'. Is Visual Studio installed?" + exit 1 +} + +$Local:vsInstallPath = & $Local:vswhere -latest -products * -requires Microsoft.VisualStudio.PackageGroup.TestTools.Core -property installationPath +if ([System.String]::IsNullOrEmpty($Local:vsInstallPath)) +{ + Write-Error "Could not find a Visual Studio installation with test tools via vswhere." + exit 1 +} + +$Local:vstestPath = Join-Path $Local:vsInstallPath "Common7\IDE\Extensions\TestPlatform\vstest.console.exe" +if (-not (Test-Path $Local:vstestPath)) +{ + Write-Error "vstest.console.exe not found at '$Local:vstestPath'." + exit 1 +} + +# Step 3: Run the tests with vstest +$Local:testDll = Join-Path $BuildOutputPath "Microsoft.Management.Configuration.UnitTests\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.UnitTests.dll" + +$Local:vstestArgs = @( + $Local:testDll, + "--logger:console;verbosity=detailed" +) + +if (-not [System.String]::IsNullOrEmpty($TestCaseFilter)) +{ + $Local:vstestArgs += "--TestCaseFilter:$TestCaseFilter" +} + +& $Local:vstestPath @Local:vstestArgs +exit $LASTEXITCODE diff --git a/src/Microsoft.Management.Configuration.OutOfProc/dllmain.cpp b/src/Microsoft.Management.Configuration.OutOfProc/dllmain.cpp index a4a90c62e3..8b9d924ba8 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/dllmain.cpp +++ b/src/Microsoft.Management.Configuration.OutOfProc/dllmain.cpp @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Factory.h" -#include - -using namespace Microsoft::Management::Configuration::OutOfProc; - -EXTERN_C BOOL WINAPI DllMain( - HMODULE /* hModule */, - DWORD reason, - LPVOID /* lpReserved */) -{ - switch (reason) - { - case DLL_PROCESS_DETACH: - Factory::Terminate(); - break; - } - - return TRUE; -} - -_Check_return_ -STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) try -{ - RETURN_HR_IF(E_POINTER, !ppv); - *ppv = nullptr; - - winrt::Windows::Foundation::IUnknown result; - - if (Factory::IsCLSID(rclsid)) - { - result = winrt::make().as(); - } - - if (result) - { - return result.as(riid, ppv); - } - - return REGDB_E_CLASSNOTREG; -} -CATCH_RETURN(); - -__control_entrypoint(DllExport) -STDAPI DllCanUnloadNow() -{ - return Factory::HasReferences() ? S_FALSE : S_OK; -} - -STDAPI DllGetActivationFactory(HSTRING classId, void** factory) try -{ - RETURN_HR_IF(E_POINTER, !factory); - *factory = nullptr; - - winrt::Windows::Foundation::IUnknown result; - - if (Factory::IsCLSID(classId)) - { - result = winrt::make().as(); - } - - if (result) - { - return result.as(winrt::guid_of(), factory); - } - - return REGDB_E_CLASSNOTREG; -} -CATCH_RETURN(); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Factory.h" +#include + +using namespace Microsoft::Management::Configuration::OutOfProc; + +EXTERN_C BOOL WINAPI DllMain( + HMODULE /* hModule */, + DWORD reason, + LPVOID /* lpReserved */) +{ + switch (reason) + { + case DLL_PROCESS_DETACH: + Factory::Terminate(); + break; + } + + return TRUE; +} + +_Check_return_ +STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) try +{ + RETURN_HR_IF(E_POINTER, !ppv); + *ppv = nullptr; + + winrt::Windows::Foundation::IUnknown result; + + if (Factory::IsCLSID(rclsid)) + { + result = winrt::make().as(); + } + + if (result) + { + return result.as(riid, ppv); + } + + return REGDB_E_CLASSNOTREG; +} +CATCH_RETURN(); + +__control_entrypoint(DllExport) +STDAPI DllCanUnloadNow() +{ + return Factory::HasReferences() ? S_FALSE : S_OK; +} + +STDAPI DllGetActivationFactory(HSTRING classId, void** factory) try +{ + RETURN_HR_IF(E_POINTER, !factory); + *factory = nullptr; + + winrt::Windows::Foundation::IUnknown result; + + if (Factory::IsCLSID(classId)) + { + result = winrt::make().as(); + } + + if (result) + { + return result.as(winrt::guid_of(), factory); + } + + return REGDB_E_CLASSNOTREG; +} +CATCH_RETURN(); diff --git a/src/Microsoft.Management.Configuration.OutOfProc/packages.config b/src/Microsoft.Management.Configuration.OutOfProc/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/packages.config +++ b/src/Microsoft.Management.Configuration.OutOfProc/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration.OutOfProc/pch.cpp b/src/Microsoft.Management.Configuration.OutOfProc/pch.cpp index 855ee69a1e..c084a84a60 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/pch.cpp +++ b/src/Microsoft.Management.Configuration.OutOfProc/pch.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" diff --git a/src/Microsoft.Management.Configuration.OutOfProc/pch.h b/src/Microsoft.Management.Configuration.OutOfProc/pch.h index 4d676fbb5e..7da5c77b03 100644 --- a/src/Microsoft.Management.Configuration.OutOfProc/pch.h +++ b/src/Microsoft.Management.Configuration.OutOfProc/pch.h @@ -1,13 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -#include -#include - -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#include +#include + +#include +#include diff --git a/src/Microsoft.Management.Configuration.Processor/Constants/DirectiveConstants.cs b/src/Microsoft.Management.Configuration.Processor/Constants/DirectiveConstants.cs index 7568f11c36..5d5a0ac1e5 100644 --- a/src/Microsoft.Management.Configuration.Processor/Constants/DirectiveConstants.cs +++ b/src/Microsoft.Management.Configuration.Processor/Constants/DirectiveConstants.cs @@ -1,24 +1,24 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Constants -{ - /// - /// Directives. - /// - internal static class DirectiveConstants - { -#pragma warning disable SA1600 // ElementsMustBeDocumented - public const string MaxVersion = "maxVersion"; - public const string MinVersion = "minVersion"; - public const string Module = "module"; - public const string ModuleGuid = "moduleGuid"; - public const string Repository = "repository"; - public const string Version = "version"; - public const string AllowPrerelease = "allowPrerelease"; -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Constants +{ + /// + /// Directives. + /// + internal static class DirectiveConstants + { +#pragma warning disable SA1600 // ElementsMustBeDocumented + public const string MaxVersion = "maxVersion"; + public const string MinVersion = "minVersion"; + public const string Module = "module"; + public const string ModuleGuid = "moduleGuid"; + public const string Repository = "repository"; + public const string Version = "version"; + public const string AllowPrerelease = "allowPrerelease"; +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs index 1c34608db5..67af276bb5 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/FindDscPackageStateMachine.cs @@ -1,186 +1,186 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System; - - /// - /// Provides the state machine that decides which DSC package to use. - /// - internal class FindDscPackageStateMachine - { - private const string StableDscPackageFamilyName = "Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe"; - private const string PreviewDscPackageFamilyName = "Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe"; - - private readonly Version minimumStableVersion = new Version(3, 1); - private readonly Version minimumPreviewVersion = new Version(3, 1, 7); - - private State currentState = State.Initial; - private string? dscExecutablePath; - - /// - /// A state of the state machine. - /// - public enum State - { - /// - /// The initial state. - /// - Initial, - - /// - /// A stable installation attempt has been made. - /// - StableInstallAttempted, - - /// - /// A preview installation attempt has been made. - /// - PreviewInstallAttempted, - - /// - /// The state machine is terminated. - /// - Terminated, - } - - /// - /// A transition of the state machine. - /// - public enum Transition - { - /// - /// Transition to a terminated state with DSC being found. - /// - Found, - - /// - /// Attempt to install the stable version of DSC. - /// - InstallStable, - - /// - /// Attempt to install the preview version of DSC. - /// - InstallPreview, - - /// - /// Transition to a terminated state with DSC *not* being found. - /// - NotFound, - } - - /// - /// Gets the file path of the DSC (Desired State Configuration) executable. - /// - public string? DscExecutablePath - { - get - { - if (this.currentState == State.Terminated) - { - return this.dscExecutablePath; - } - else - { - PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); - if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) - { - return stableInformation.AliasPath; - } - else - { - PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); - if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) - { - return previewInformation.AliasPath; - } - else - { - return null; - } - } - } - } - } - - /// - /// Determines the next state transition based on the current context or conditions. - /// - /// - /// A string representing the name of the next transition. - /// - public Transition DetermineNextTransition() - { - switch (this.currentState) - { - case State.Initial: - { - PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); - if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) - { - return this.Found(stableInformation); - } - else - { - this.currentState = State.StableInstallAttempted; - return Transition.InstallStable; - } - } - - case State.StableInstallAttempted: - { - PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); - if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) - { - return this.Found(stableInformation); - } - else - { - PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); - if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) - { - return this.Found(previewInformation); - } - else - { - this.currentState = State.PreviewInstallAttempted; - return Transition.InstallPreview; - } - } - } - - case State.PreviewInstallAttempted: - { - PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); - if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) - { - return this.Found(previewInformation); - } - else - { - this.currentState = State.Terminated; - return Transition.NotFound; - } - } - - case State.Terminated: - return this.DscExecutablePath == null ? Transition.NotFound : Transition.Found; - - default: - throw new InvalidOperationException($"Unexpected state: {this.currentState}"); - } - } - - private Transition Found(PackageInformation packageInformation) - { - this.dscExecutablePath = packageInformation.AliasPath; - this.currentState = State.Terminated; - return Transition.Found; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + + /// + /// Provides the state machine that decides which DSC package to use. + /// + internal class FindDscPackageStateMachine + { + private const string StableDscPackageFamilyName = "Microsoft.DesiredStateConfiguration_8wekyb3d8bbwe"; + private const string PreviewDscPackageFamilyName = "Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe"; + + private readonly Version minimumStableVersion = new Version(3, 1); + private readonly Version minimumPreviewVersion = new Version(3, 1, 7); + + private State currentState = State.Initial; + private string? dscExecutablePath; + + /// + /// A state of the state machine. + /// + public enum State + { + /// + /// The initial state. + /// + Initial, + + /// + /// A stable installation attempt has been made. + /// + StableInstallAttempted, + + /// + /// A preview installation attempt has been made. + /// + PreviewInstallAttempted, + + /// + /// The state machine is terminated. + /// + Terminated, + } + + /// + /// A transition of the state machine. + /// + public enum Transition + { + /// + /// Transition to a terminated state with DSC being found. + /// + Found, + + /// + /// Attempt to install the stable version of DSC. + /// + InstallStable, + + /// + /// Attempt to install the preview version of DSC. + /// + InstallPreview, + + /// + /// Transition to a terminated state with DSC *not* being found. + /// + NotFound, + } + + /// + /// Gets the file path of the DSC (Desired State Configuration) executable. + /// + public string? DscExecutablePath + { + get + { + if (this.currentState == State.Terminated) + { + return this.dscExecutablePath; + } + else + { + PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); + if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) + { + return stableInformation.AliasPath; + } + else + { + PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); + if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) + { + return previewInformation.AliasPath; + } + else + { + return null; + } + } + } + } + } + + /// + /// Determines the next state transition based on the current context or conditions. + /// + /// + /// A string representing the name of the next transition. + /// + public Transition DetermineNextTransition() + { + switch (this.currentState) + { + case State.Initial: + { + PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); + if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) + { + return this.Found(stableInformation); + } + else + { + this.currentState = State.StableInstallAttempted; + return Transition.InstallStable; + } + } + + case State.StableInstallAttempted: + { + PackageInformation stableInformation = new PackageInformation(StableDscPackageFamilyName); + if (stableInformation.IsInstalled && stableInformation.Version >= this.minimumStableVersion) + { + return this.Found(stableInformation); + } + else + { + PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); + if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) + { + return this.Found(previewInformation); + } + else + { + this.currentState = State.PreviewInstallAttempted; + return Transition.InstallPreview; + } + } + } + + case State.PreviewInstallAttempted: + { + PackageInformation previewInformation = new PackageInformation(PreviewDscPackageFamilyName); + if (previewInformation.IsInstalled && previewInformation.Version >= this.minimumPreviewVersion) + { + return this.Found(previewInformation); + } + else + { + this.currentState = State.Terminated; + return Transition.NotFound; + } + } + + case State.Terminated: + return this.DscExecutablePath == null ? Transition.NotFound : Transition.Found; + + default: + throw new InvalidOperationException($"Unexpected state: {this.currentState}"); + } + } + + private Transition Found(PackageInformation packageInformation) + { + this.dscExecutablePath = packageInformation.AliasPath; + this.currentState = State.Terminated; + return Transition.Found; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/IDiagnosticsSink.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/IDiagnosticsSink.cs index 7f150a5ff1..1ce3ba369d 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/IDiagnosticsSink.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/IDiagnosticsSink.cs @@ -1,21 +1,21 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - /// - /// Defines the interface for a diagnostics sink. - /// - internal interface IDiagnosticsSink - { - /// - /// Sends a diagnostic message. - /// - /// The level of the message. - /// The message. - public void OnDiagnostics(DiagnosticLevel level, string message); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + /// + /// Defines the interface for a diagnostics sink. + /// + internal interface IDiagnosticsSink + { + /// + /// Sends a diagnostic message. + /// + /// The level of the message. + /// The message. + public void OnDiagnostics(DiagnosticLevel level, string message); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs index b291427fac..1810eb50bc 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/PackageInformation.cs @@ -1,73 +1,73 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System; - using System.IO; - using Windows.Management.Deployment; - - /// - /// Contains information about a package. - /// - internal class PackageInformation - { - private const string DscExecutableFileName = "dsc.exe"; - - /// - /// Initializes a new instance of the class. - /// - /// The package family name. - public PackageInformation(string familyName) - { - PackageManager packageManager = new PackageManager(); - - var packages = packageManager.FindPackagesForUserWithPackageTypes(null, familyName, PackageTypes.Main); - - if (packages != null) - { - foreach (var package in packages) - { - var packageVersion = package.Id.Version; - Version version = new Version(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision); - - if (this.Version == null || version > this.Version) - { - this.Version = version; - } - } - } - - string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); - string aliasPath = Path.Combine(localAppData, "Microsoft\\WindowsApps", familyName, DscExecutableFileName); - - if (Path.Exists(aliasPath)) - { - this.AliasPath = aliasPath; - } - - if (this.AliasPath != null && this.Version != null) - { - this.IsInstalled = true; - } - } - - /// - /// Gets a value indicating whether the package is installed or not. - /// - public bool IsInstalled { get; private set; } - - /// - /// Gets the path to the dsc.exe alias. - /// - public string? AliasPath { get; private set; } - - /// - /// Gets the version of the package. - /// - public Version? Version { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.IO; + using Windows.Management.Deployment; + + /// + /// Contains information about a package. + /// + internal class PackageInformation + { + private const string DscExecutableFileName = "dsc.exe"; + + /// + /// Initializes a new instance of the class. + /// + /// The package family name. + public PackageInformation(string familyName) + { + PackageManager packageManager = new PackageManager(); + + var packages = packageManager.FindPackagesForUserWithPackageTypes(null, familyName, PackageTypes.Main); + + if (packages != null) + { + foreach (var package in packages) + { + var packageVersion = package.Id.Version; + Version version = new Version(packageVersion.Major, packageVersion.Minor, packageVersion.Build, packageVersion.Revision); + + if (this.Version == null || version > this.Version) + { + this.Version = version; + } + } + } + + string localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string aliasPath = Path.Combine(localAppData, "Microsoft\\WindowsApps", familyName, DscExecutableFileName); + + if (Path.Exists(aliasPath)) + { + this.AliasPath = aliasPath; + } + + if (this.AliasPath != null && this.Version != null) + { + this.IsInstalled = true; + } + } + + /// + /// Gets a value indicating whether the package is installed or not. + /// + public bool IsInstalled { get; private set; } + + /// + /// Gets the path to the dsc.exe alias. + /// + public string? AliasPath { get; private set; } + + /// + /// Gets the version of the package. + /// + public Version? Version { get; private set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecution.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecution.cs index e3762281a9..686f1ea8fd 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecution.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecution.cs @@ -1,304 +1,304 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Text; - using System.Threading; - using Microsoft.Management.Configuration.Processor.Helpers; - - /// - /// Wrapper for a single process execution and its output. - /// - internal class ProcessExecution - { - private static int nextExecutionNumber = 0; - - private List outputLines = new List(); - private List errorLines = new List(); - - /// - /// Initializes a new instance of the class. - /// - public ProcessExecution() - { - this.ExecutionNumber = Interlocked.Increment(ref nextExecutionNumber); - } - - /// - /// An event that receives the output lines as they are delivered. - /// - public event EventHandler? OutputLineReceived; - - /// - /// An event that receives the error lines as they are delivered. - /// - public event EventHandler? ErrorLineReceived; - - /// - /// Gets the monotonically increasing number assigned to this process execution. - /// - public int ExecutionNumber { get; } - - /// - /// Gets the executable path. - /// - required public string ExecutablePath { get; init; } - - /// - /// Gets the arguments to use for the process. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public IEnumerable Arguments { get; init; } = []; - - /// - /// Gets the data to write to standard input of the process. - /// - public string? Input { get; init; } = string.Empty; - - /// - /// Gets the list of custom environment variables to use for the process. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public IEnumerable EnvironmentVariables { get; init; } = []; - - /// - /// Gets the argument string passed to the process. - /// - public string SerializedArguments - { - get - { - StringBuilder processArguments = new StringBuilder(); - - foreach (string arg in this.Arguments) - { - if (processArguments.Length != 0) - { - processArguments.Append(' '); - } - - processArguments.Append(arg); - } - - return processArguments.ToString(); - } - } - - /// - /// Gets the full command line that the process should see. - /// - public string CommandLine - { - get - { - return $"{this.ExecutablePath} {this.SerializedArguments}"; - } - } - - /// - /// Gets the current set of output lines. - /// Not thread safe, use OutputLineReceived for async flows. - /// - public IReadOnlyCollection Output - { - get { return this.outputLines; } - } - - /// - /// Gets the current set of error lines. - /// Not thread safe, use ErrorLineReceived for async flows. - /// - public IReadOnlyCollection Error - { - get { return this.errorLines; } - } - - /// - /// Gets the exit code of the process. - /// Will be null until the process exits. - /// - public int? ExitCode { get; private set; } = null; - - /// - /// Gets or sets the process object; null until Start called. - /// - private Process? Process { get; set; } - - /// - /// Starts the process. - /// - /// This object. - /// Thrown if Start has already been called. - public ProcessExecution Start() - { - if (this.Process != null) - { - throw new InvalidOperationException("Process has already been started."); - } - - ProcessStartInfo startInfo; - - lock (PathEnvironmentVariableHandler.Lock) - { - startInfo = new ProcessStartInfo(this.ExecutablePath, this.SerializedArguments); - } - - this.Process = new Process() { StartInfo = startInfo }; - - startInfo.UseShellExecute = false; - startInfo.WindowStyle = ProcessWindowStyle.Hidden; - - startInfo.StandardOutputEncoding = Encoding.UTF8; - startInfo.RedirectStandardOutput = true; - this.Process.OutputDataReceived += (sender, args) => - { - string? output = args.Data; - - if (output != null) - { - this.outputLines.Add(output); - - this.OutputLineReceived?.Invoke(this, output); - } - }; - - startInfo.StandardErrorEncoding = Encoding.UTF8; - startInfo.RedirectStandardError = true; - this.Process.ErrorDataReceived += (sender, args) => - { - string? error = args.Data; - - if (error != null) - { - this.errorLines.Add(error); - - this.ErrorLineReceived?.Invoke(this, error); - } - }; - - if (this.Input != null) - { - startInfo.StandardInputEncoding = Encoding.UTF8; - startInfo.RedirectStandardInput = true; - } - - foreach (var env in this.EnvironmentVariables) - { - switch (env.ValueType) - { - case ProcessExecutionEnvironmentVariableValueType.Override: - startInfo.EnvironmentVariables[env.Name] = env.Value; - break; - - case ProcessExecutionEnvironmentVariableValueType.Prepend: - startInfo.EnvironmentVariables[env.Name] = MergeStringsWithSeparator(env.Value, startInfo.EnvironmentVariables[env.Name] ?? string.Empty, env.Separator); - break; - - case ProcessExecutionEnvironmentVariableValueType.Append: - startInfo.EnvironmentVariables[env.Name] = MergeStringsWithSeparator(startInfo.EnvironmentVariables[env.Name] ?? string.Empty, env.Value, env.Separator); - break; - } - } - - this.Process.Start(); - this.Process.BeginOutputReadLine(); - this.Process.BeginErrorReadLine(); - - if (this.Input != null) - { - this.Process.StandardInput.Write(this.Input); - this.Process.StandardInput.Close(); - } - - return this; - } - - /// - /// Waits for the process to exit. - /// - /// The minimum amount of time to wait for the process to exit, in milliseconds. - /// True if the process exited; false if not. - /// Thrown if Start has not been called. - public bool WaitForExit(int milliseconds = Timeout.Infinite) - { - if (this.Process == null) - { - throw new InvalidOperationException("Process has not been started."); - } - - if (this.Process.WaitForExit(milliseconds)) - { - // According to documentation, this extra call will ensure that the redirected streams have finished reading all of the data. - this.Process.WaitForExit(); - - this.ExitCode = this.Process.ExitCode; - - return true; - } - else - { - return false; - } - } - - /// - /// Gets all of the output lines as a single string. - /// - /// The output lines as a string. - public string GetAllOutputLines() - { - return GetAllLines(this.outputLines); - } - - /// - /// Gets all of the error lines as a single string. - /// - /// The error lines as a string. - public string GetAllErrorLines() - { - return GetAllLines(this.errorLines); - } - - private static string GetAllLines(List lines) - { - StringBuilder stringBuilder = new StringBuilder(); - - foreach (string line in lines) - { - stringBuilder.Append(line).Append('\n'); - } - - return stringBuilder.ToString(); - } - - private static string MergeStringsWithSeparator(string first, string second, string separator) - { - if (string.IsNullOrEmpty(separator)) - { - return first + second; - } - else - { - if (first.EndsWith(separator) && second.StartsWith(separator)) - { - return first + second.Substring(separator.Length); - } - else if (first.EndsWith(separator) || second.StartsWith(separator)) - { - return first + second; - } - else - { - return first + separator + second; - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Text; + using System.Threading; + using Microsoft.Management.Configuration.Processor.Helpers; + + /// + /// Wrapper for a single process execution and its output. + /// + internal class ProcessExecution + { + private static int nextExecutionNumber = 0; + + private List outputLines = new List(); + private List errorLines = new List(); + + /// + /// Initializes a new instance of the class. + /// + public ProcessExecution() + { + this.ExecutionNumber = Interlocked.Increment(ref nextExecutionNumber); + } + + /// + /// An event that receives the output lines as they are delivered. + /// + public event EventHandler? OutputLineReceived; + + /// + /// An event that receives the error lines as they are delivered. + /// + public event EventHandler? ErrorLineReceived; + + /// + /// Gets the monotonically increasing number assigned to this process execution. + /// + public int ExecutionNumber { get; } + + /// + /// Gets the executable path. + /// + required public string ExecutablePath { get; init; } + + /// + /// Gets the arguments to use for the process. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public IEnumerable Arguments { get; init; } = []; + + /// + /// Gets the data to write to standard input of the process. + /// + public string? Input { get; init; } = string.Empty; + + /// + /// Gets the list of custom environment variables to use for the process. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public IEnumerable EnvironmentVariables { get; init; } = []; + + /// + /// Gets the argument string passed to the process. + /// + public string SerializedArguments + { + get + { + StringBuilder processArguments = new StringBuilder(); + + foreach (string arg in this.Arguments) + { + if (processArguments.Length != 0) + { + processArguments.Append(' '); + } + + processArguments.Append(arg); + } + + return processArguments.ToString(); + } + } + + /// + /// Gets the full command line that the process should see. + /// + public string CommandLine + { + get + { + return $"{this.ExecutablePath} {this.SerializedArguments}"; + } + } + + /// + /// Gets the current set of output lines. + /// Not thread safe, use OutputLineReceived for async flows. + /// + public IReadOnlyCollection Output + { + get { return this.outputLines; } + } + + /// + /// Gets the current set of error lines. + /// Not thread safe, use ErrorLineReceived for async flows. + /// + public IReadOnlyCollection Error + { + get { return this.errorLines; } + } + + /// + /// Gets the exit code of the process. + /// Will be null until the process exits. + /// + public int? ExitCode { get; private set; } = null; + + /// + /// Gets or sets the process object; null until Start called. + /// + private Process? Process { get; set; } + + /// + /// Starts the process. + /// + /// This object. + /// Thrown if Start has already been called. + public ProcessExecution Start() + { + if (this.Process != null) + { + throw new InvalidOperationException("Process has already been started."); + } + + ProcessStartInfo startInfo; + + lock (PathEnvironmentVariableHandler.Lock) + { + startInfo = new ProcessStartInfo(this.ExecutablePath, this.SerializedArguments); + } + + this.Process = new Process() { StartInfo = startInfo }; + + startInfo.UseShellExecute = false; + startInfo.WindowStyle = ProcessWindowStyle.Hidden; + + startInfo.StandardOutputEncoding = Encoding.UTF8; + startInfo.RedirectStandardOutput = true; + this.Process.OutputDataReceived += (sender, args) => + { + string? output = args.Data; + + if (output != null) + { + this.outputLines.Add(output); + + this.OutputLineReceived?.Invoke(this, output); + } + }; + + startInfo.StandardErrorEncoding = Encoding.UTF8; + startInfo.RedirectStandardError = true; + this.Process.ErrorDataReceived += (sender, args) => + { + string? error = args.Data; + + if (error != null) + { + this.errorLines.Add(error); + + this.ErrorLineReceived?.Invoke(this, error); + } + }; + + if (this.Input != null) + { + startInfo.StandardInputEncoding = Encoding.UTF8; + startInfo.RedirectStandardInput = true; + } + + foreach (var env in this.EnvironmentVariables) + { + switch (env.ValueType) + { + case ProcessExecutionEnvironmentVariableValueType.Override: + startInfo.EnvironmentVariables[env.Name] = env.Value; + break; + + case ProcessExecutionEnvironmentVariableValueType.Prepend: + startInfo.EnvironmentVariables[env.Name] = MergeStringsWithSeparator(env.Value, startInfo.EnvironmentVariables[env.Name] ?? string.Empty, env.Separator); + break; + + case ProcessExecutionEnvironmentVariableValueType.Append: + startInfo.EnvironmentVariables[env.Name] = MergeStringsWithSeparator(startInfo.EnvironmentVariables[env.Name] ?? string.Empty, env.Value, env.Separator); + break; + } + } + + this.Process.Start(); + this.Process.BeginOutputReadLine(); + this.Process.BeginErrorReadLine(); + + if (this.Input != null) + { + this.Process.StandardInput.Write(this.Input); + this.Process.StandardInput.Close(); + } + + return this; + } + + /// + /// Waits for the process to exit. + /// + /// The minimum amount of time to wait for the process to exit, in milliseconds. + /// True if the process exited; false if not. + /// Thrown if Start has not been called. + public bool WaitForExit(int milliseconds = Timeout.Infinite) + { + if (this.Process == null) + { + throw new InvalidOperationException("Process has not been started."); + } + + if (this.Process.WaitForExit(milliseconds)) + { + // According to documentation, this extra call will ensure that the redirected streams have finished reading all of the data. + this.Process.WaitForExit(); + + this.ExitCode = this.Process.ExitCode; + + return true; + } + else + { + return false; + } + } + + /// + /// Gets all of the output lines as a single string. + /// + /// The output lines as a string. + public string GetAllOutputLines() + { + return GetAllLines(this.outputLines); + } + + /// + /// Gets all of the error lines as a single string. + /// + /// The error lines as a string. + public string GetAllErrorLines() + { + return GetAllLines(this.errorLines); + } + + private static string GetAllLines(List lines) + { + StringBuilder stringBuilder = new StringBuilder(); + + foreach (string line in lines) + { + stringBuilder.Append(line).Append('\n'); + } + + return stringBuilder.ToString(); + } + + private static string MergeStringsWithSeparator(string first, string second, string separator) + { + if (string.IsNullOrEmpty(separator)) + { + return first + second; + } + else + { + if (first.EndsWith(separator) && second.StartsWith(separator)) + { + return first + second.Substring(separator.Length); + } + else if (first.EndsWith(separator) || second.StartsWith(separator)) + { + return first + second; + } + else + { + return first + separator + second; + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariable.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariable.cs index 5ea987693d..9d8e74f75c 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariable.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariable.cs @@ -1,34 +1,34 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - /// - /// Contains custom environment variable info for ProcessExecution. - /// - internal class ProcessExecutionEnvironmentVariable - { - /// - /// Gets the name of the environment variable. - /// - required public string Name { get; init; } - - /// - /// Gets the value of the environment variable. - /// - required public string Value { get; init; } - - /// - /// Gets the value type of the environment variable. - /// - public ProcessExecutionEnvironmentVariableValueType ValueType { get; init; } = ProcessExecutionEnvironmentVariableValueType.Override; - - /// - /// Gets the separator of the environment variable if value type is prepend or append. - /// - public string Separator { get; init; } = ";"; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + /// + /// Contains custom environment variable info for ProcessExecution. + /// + internal class ProcessExecutionEnvironmentVariable + { + /// + /// Gets the name of the environment variable. + /// + required public string Name { get; init; } + + /// + /// Gets the value of the environment variable. + /// + required public string Value { get; init; } + + /// + /// Gets the value type of the environment variable. + /// + public ProcessExecutionEnvironmentVariableValueType ValueType { get; init; } = ProcessExecutionEnvironmentVariableValueType.Override; + + /// + /// Gets the separator of the environment variable if value type is prepend or append. + /// + public string Separator { get; init; } = ";"; + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariableValueType.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariableValueType.cs index 8fa24601d9..ad5072af75 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariableValueType.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessExecutionEnvironmentVariableValueType.cs @@ -1,29 +1,29 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - /// - /// The environment variable value type. - /// - internal enum ProcessExecutionEnvironmentVariableValueType - { - /// - /// Prepend. - /// - Prepend, - - /// - /// Append. - /// - Append, - - /// - /// Override. - /// - Override, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + /// + /// The environment variable value type. + /// + internal enum ProcessExecutionEnvironmentVariableValueType + { + /// + /// Prepend. + /// + Prepend, + + /// + /// Append. + /// + Append, + + /// + /// Override. + /// + Override, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessOutputBatcher.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessOutputBatcher.cs index ecefc47f98..9822a82b54 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessOutputBatcher.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessOutputBatcher.cs @@ -1,120 +1,120 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System; - using System.Text; - using System.Threading; - - /// - /// Subscribes to a 's output and error events, - /// accumulates lines in a thread-safe buffer, and forwards them to an - /// as a single batched message on a fixed interval. - /// This avoids one cross-process IPC call per output line while still delivering - /// output promptly even if the process never exits. - /// - internal sealed class ProcessOutputBatcher : IDisposable - { - private readonly IDiagnosticsSink? sink; - private readonly Timer flushTimer; - private readonly object bufferLock = new object(); - private StringBuilder buffer = new StringBuilder(); - private string batchHeader = "--- Process Output ---"; - private string outputPrefix = "[out] "; - private string errorPrefix = "[err] "; - private bool disposed = false; - - /// - /// Initializes a new instance of the class. - /// - /// The diagnostics sink to forward batched output to. May be null. - /// How often to flush accumulated output to the sink. - public ProcessOutputBatcher(IDiagnosticsSink? sink, TimeSpan flushInterval) - { - this.sink = sink; - this.flushTimer = new Timer(this.OnTimerTick, null, flushInterval, flushInterval); - } - - /// - /// Subscribes to the given 's output and error events. - /// Must be called before . - /// - /// The process execution to monitor. - public void Subscribe(ProcessExecution processExecution) - { - int number = processExecution.ExecutionNumber; - this.batchHeader = $"--- [{number}] Process Output ---"; - this.outputPrefix = $"[{number}:out] "; - this.errorPrefix = $"[{number}:err] "; - processExecution.OutputLineReceived += this.OnOutputLineReceived; - processExecution.ErrorLineReceived += this.OnErrorLineReceived; - } - - /// - /// Stops the timer, flushes any remaining buffered lines to the sink, and disposes resources. - /// Call this after returns to ensure all output is delivered. - /// - public void Flush() - { - this.flushTimer.Change(Timeout.Infinite, Timeout.Infinite); - this.EmitBuffer(); - } - - /// - public void Dispose() - { - if (!this.disposed) - { - this.disposed = true; - this.flushTimer.Dispose(); - } - } - - private void OnOutputLineReceived(object? sender, string line) - { - lock (this.bufferLock) - { - this.buffer.Append('\n').Append(this.outputPrefix).Append(line); - } - } - - private void OnErrorLineReceived(object? sender, string line) - { - lock (this.bufferLock) - { - this.buffer.Append('\n').Append(this.errorPrefix).Append(line); - } - } - - private void OnTimerTick(object? state) - { - this.EmitBuffer(); - } - - private void EmitBuffer() - { - if (this.sink == null) - { - return; - } - - StringBuilder toEmit; - lock (this.bufferLock) - { - if (this.buffer.Length == 0) - { - return; - } - - toEmit = this.buffer; - this.buffer = new StringBuilder(); - } - - this.sink.OnDiagnostics(DiagnosticLevel.Verbose, this.batchHeader + toEmit); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.Text; + using System.Threading; + + /// + /// Subscribes to a 's output and error events, + /// accumulates lines in a thread-safe buffer, and forwards them to an + /// as a single batched message on a fixed interval. + /// This avoids one cross-process IPC call per output line while still delivering + /// output promptly even if the process never exits. + /// + internal sealed class ProcessOutputBatcher : IDisposable + { + private readonly IDiagnosticsSink? sink; + private readonly Timer flushTimer; + private readonly object bufferLock = new object(); + private StringBuilder buffer = new StringBuilder(); + private string batchHeader = "--- Process Output ---"; + private string outputPrefix = "[out] "; + private string errorPrefix = "[err] "; + private bool disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// The diagnostics sink to forward batched output to. May be null. + /// How often to flush accumulated output to the sink. + public ProcessOutputBatcher(IDiagnosticsSink? sink, TimeSpan flushInterval) + { + this.sink = sink; + this.flushTimer = new Timer(this.OnTimerTick, null, flushInterval, flushInterval); + } + + /// + /// Subscribes to the given 's output and error events. + /// Must be called before . + /// + /// The process execution to monitor. + public void Subscribe(ProcessExecution processExecution) + { + int number = processExecution.ExecutionNumber; + this.batchHeader = $"--- [{number}] Process Output ---"; + this.outputPrefix = $"[{number}:out] "; + this.errorPrefix = $"[{number}:err] "; + processExecution.OutputLineReceived += this.OnOutputLineReceived; + processExecution.ErrorLineReceived += this.OnErrorLineReceived; + } + + /// + /// Stops the timer, flushes any remaining buffered lines to the sink, and disposes resources. + /// Call this after returns to ensure all output is delivered. + /// + public void Flush() + { + this.flushTimer.Change(Timeout.Infinite, Timeout.Infinite); + this.EmitBuffer(); + } + + /// + public void Dispose() + { + if (!this.disposed) + { + this.disposed = true; + this.flushTimer.Dispose(); + } + } + + private void OnOutputLineReceived(object? sender, string line) + { + lock (this.bufferLock) + { + this.buffer.Append('\n').Append(this.outputPrefix).Append(line); + } + } + + private void OnErrorLineReceived(object? sender, string line) + { + lock (this.bufferLock) + { + this.buffer.Append('\n').Append(this.errorPrefix).Append(line); + } + } + + private void OnTimerTick(object? state) + { + this.EmitBuffer(); + } + + private void EmitBuffer() + { + if (this.sink == null) + { + return; + } + + StringBuilder toEmit; + lock (this.bufferLock) + { + if (this.buffer.Length == 0) + { + return; + } + + toEmit = this.buffer; + this.buffer = new StringBuilder(); + } + + this.sink.OnDiagnostics(DiagnosticLevel.Verbose, this.batchHeader + toEmit); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorPathIntegrity.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorPathIntegrity.cs index a0411a394b..9af3752d78 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorPathIntegrity.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorPathIntegrity.cs @@ -1,202 +1,202 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System; - using System.Runtime.InteropServices; - using System.Security.Cryptography; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Win32.SafeHandles; - - /// - /// Provides integrity verification for the DSC processor executable path. - /// Handles both regular files and app execution alias reparse points. - /// - internal static class ProcessorPathIntegrity - { - private const uint GenericRead = 0x80000000; - private const uint FileShareRead = 0x00000001; - private const uint FileShareExecute = 0x00000004; - private const uint OpenExisting = 3; - private const uint FileAttributeNormal = 0x80; - private const uint FileFlagOpenReparsePoint = 0x00200000; - private const uint FileFlagBackupSemantics = 0x02000000; - private const uint FsctlGetReparsePoint = 0x000900A8; - private const int MaximumReparseDataBufferSize = 16 * 1024; - - /// - /// Opens the processor path, verifies its hash matches the expected value, and returns - /// an open handle for TOCTOU protection. - /// - /// The path to the DSC executable or app execution alias. - /// The expected SHA256 hash (hex string, case-insensitive). - /// Whether the path is an app execution alias reparse point. - /// An open SafeFileHandle to the file; caller should hold this for the process lifetime. - public static SafeFileHandle VerifyAndOpen(string path, string expectedHash, bool isAlias) - { - SafeFileHandle handle; - byte[] hashBytes; - - if (isAlias) - { - handle = CreateFile( - path, - 0, - FileShareRead | FileShareExecute, - IntPtr.Zero, - OpenExisting, - FileFlagOpenReparsePoint | FileFlagBackupSemantics, - IntPtr.Zero); - - if (handle.IsInvalid) - { - throw new InvalidOperationException($"Failed to open processor path alias '{path}': Win32 error {Marshal.GetLastWin32Error()}"); - } - - byte[] reparseBuffer = new byte[MaximumReparseDataBufferSize]; - if (!DeviceIoControl(handle, FsctlGetReparsePoint, IntPtr.Zero, 0, reparseBuffer, (uint)reparseBuffer.Length, out uint bytesReturned, IntPtr.Zero)) - { - handle.Dispose(); - throw new InvalidOperationException($"Failed to read reparse data for '{path}': Win32 error {Marshal.GetLastWin32Error()}"); - } - - hashBytes = SHA256.HashData(reparseBuffer.AsSpan(0, (int)bytesReturned)); - } - else - { - handle = CreateFile( - path, - GenericRead, - FileShareRead | FileShareExecute, - IntPtr.Zero, - OpenExisting, - FileAttributeNormal, - IntPtr.Zero); - - if (handle.IsInvalid) - { - throw new InvalidOperationException($"Failed to open processor path '{path}': Win32 error {Marshal.GetLastWin32Error()}"); - } - - hashBytes = ComputeSHA256FromHandle(handle); - } - - string computedHash = Convert.ToHexString(hashBytes).ToLowerInvariant(); - if (!string.Equals(computedHash, expectedHash, StringComparison.OrdinalIgnoreCase)) - { - handle.Dispose(); - throw new DscProcessorHashMismatchException(); - } - - return handle; - } - - /// - /// Computes the SHA256 hash of a path, auto-detecting whether it is an app execution alias. - /// - /// The path to hash. - /// Receives true if the path is an app execution alias reparse point. - /// The SHA256 hash as a lowercase hex string. - public static string ComputeHash(string path, out bool isAlias) - { - // Attempt to open as a regular file first. - SafeFileHandle regularHandle = CreateFile( - path, - GenericRead, - FileShareRead | FileShareExecute, - IntPtr.Zero, - OpenExisting, - FileAttributeNormal, - IntPtr.Zero); - - if (!regularHandle.IsInvalid) - { - isAlias = false; - using (regularHandle) - { - return Convert.ToHexString(ComputeSHA256FromHandle(regularHandle)).ToLowerInvariant(); - } - } - - // If the regular open fails, try as an app execution alias reparse point. - SafeFileHandle aliasHandle = CreateFile( - path, - 0, - FileShareRead | FileShareExecute, - IntPtr.Zero, - OpenExisting, - FileFlagOpenReparsePoint | FileFlagBackupSemantics, - IntPtr.Zero); - - if (aliasHandle.IsInvalid) - { - throw new InvalidOperationException($"Failed to open path '{path}': Win32 error {Marshal.GetLastWin32Error()}"); - } - - using (aliasHandle) - { - byte[] reparseBuffer = new byte[MaximumReparseDataBufferSize]; - if (!DeviceIoControl(aliasHandle, FsctlGetReparsePoint, IntPtr.Zero, 0, reparseBuffer, (uint)reparseBuffer.Length, out uint bytesReturned, IntPtr.Zero)) - { - throw new InvalidOperationException($"Failed to read reparse data for '{path}': Win32 error {Marshal.GetLastWin32Error()}"); - } - - isAlias = true; - return Convert.ToHexString(SHA256.HashData(reparseBuffer.AsSpan(0, (int)bytesReturned))).ToLowerInvariant(); - } - } - - private static byte[] ComputeSHA256FromHandle(SafeFileHandle handle) - { - using var incrHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); - - byte[] buffer = new byte[1024 * 1024]; - while (true) - { - if (!ReadFile(handle, buffer, (uint)buffer.Length, out uint bytesRead, IntPtr.Zero) || bytesRead == 0) - { - break; - } - - incrHash.AppendData(buffer, 0, (int)bytesRead); - } - - return incrHash.GetHashAndReset(); - } - - [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] - private static extern SafeFileHandle CreateFile( - string lpFileName, - uint dwDesiredAccess, - uint dwShareMode, - IntPtr lpSecurityAttributes, - uint dwCreationDisposition, - uint dwFlagsAndAttributes, - IntPtr hTemplateFile); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool DeviceIoControl( - SafeFileHandle hDevice, - uint dwIoControlCode, - IntPtr lpInBuffer, - uint nInBufferSize, - byte[] lpOutBuffer, - uint nOutBufferSize, - out uint lpBytesReturned, - IntPtr lpOverlapped); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool ReadFile( - SafeFileHandle hFile, - byte[] lpBuffer, - uint nNumberOfBytesToRead, - out uint lpNumberOfBytesRead, - IntPtr lpOverlapped); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.Runtime.InteropServices; + using System.Security.Cryptography; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Win32.SafeHandles; + + /// + /// Provides integrity verification for the DSC processor executable path. + /// Handles both regular files and app execution alias reparse points. + /// + internal static class ProcessorPathIntegrity + { + private const uint GenericRead = 0x80000000; + private const uint FileShareRead = 0x00000001; + private const uint FileShareExecute = 0x00000004; + private const uint OpenExisting = 3; + private const uint FileAttributeNormal = 0x80; + private const uint FileFlagOpenReparsePoint = 0x00200000; + private const uint FileFlagBackupSemantics = 0x02000000; + private const uint FsctlGetReparsePoint = 0x000900A8; + private const int MaximumReparseDataBufferSize = 16 * 1024; + + /// + /// Opens the processor path, verifies its hash matches the expected value, and returns + /// an open handle for TOCTOU protection. + /// + /// The path to the DSC executable or app execution alias. + /// The expected SHA256 hash (hex string, case-insensitive). + /// Whether the path is an app execution alias reparse point. + /// An open SafeFileHandle to the file; caller should hold this for the process lifetime. + public static SafeFileHandle VerifyAndOpen(string path, string expectedHash, bool isAlias) + { + SafeFileHandle handle; + byte[] hashBytes; + + if (isAlias) + { + handle = CreateFile( + path, + 0, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileFlagOpenReparsePoint | FileFlagBackupSemantics, + IntPtr.Zero); + + if (handle.IsInvalid) + { + throw new InvalidOperationException($"Failed to open processor path alias '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + byte[] reparseBuffer = new byte[MaximumReparseDataBufferSize]; + if (!DeviceIoControl(handle, FsctlGetReparsePoint, IntPtr.Zero, 0, reparseBuffer, (uint)reparseBuffer.Length, out uint bytesReturned, IntPtr.Zero)) + { + handle.Dispose(); + throw new InvalidOperationException($"Failed to read reparse data for '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + hashBytes = SHA256.HashData(reparseBuffer.AsSpan(0, (int)bytesReturned)); + } + else + { + handle = CreateFile( + path, + GenericRead, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileAttributeNormal, + IntPtr.Zero); + + if (handle.IsInvalid) + { + throw new InvalidOperationException($"Failed to open processor path '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + hashBytes = ComputeSHA256FromHandle(handle); + } + + string computedHash = Convert.ToHexString(hashBytes).ToLowerInvariant(); + if (!string.Equals(computedHash, expectedHash, StringComparison.OrdinalIgnoreCase)) + { + handle.Dispose(); + throw new DscProcessorHashMismatchException(); + } + + return handle; + } + + /// + /// Computes the SHA256 hash of a path, auto-detecting whether it is an app execution alias. + /// + /// The path to hash. + /// Receives true if the path is an app execution alias reparse point. + /// The SHA256 hash as a lowercase hex string. + public static string ComputeHash(string path, out bool isAlias) + { + // Attempt to open as a regular file first. + SafeFileHandle regularHandle = CreateFile( + path, + GenericRead, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileAttributeNormal, + IntPtr.Zero); + + if (!regularHandle.IsInvalid) + { + isAlias = false; + using (regularHandle) + { + return Convert.ToHexString(ComputeSHA256FromHandle(regularHandle)).ToLowerInvariant(); + } + } + + // If the regular open fails, try as an app execution alias reparse point. + SafeFileHandle aliasHandle = CreateFile( + path, + 0, + FileShareRead | FileShareExecute, + IntPtr.Zero, + OpenExisting, + FileFlagOpenReparsePoint | FileFlagBackupSemantics, + IntPtr.Zero); + + if (aliasHandle.IsInvalid) + { + throw new InvalidOperationException($"Failed to open path '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + using (aliasHandle) + { + byte[] reparseBuffer = new byte[MaximumReparseDataBufferSize]; + if (!DeviceIoControl(aliasHandle, FsctlGetReparsePoint, IntPtr.Zero, 0, reparseBuffer, (uint)reparseBuffer.Length, out uint bytesReturned, IntPtr.Zero)) + { + throw new InvalidOperationException($"Failed to read reparse data for '{path}': Win32 error {Marshal.GetLastWin32Error()}"); + } + + isAlias = true; + return Convert.ToHexString(SHA256.HashData(reparseBuffer.AsSpan(0, (int)bytesReturned))).ToLowerInvariant(); + } + } + + private static byte[] ComputeSHA256FromHandle(SafeFileHandle handle) + { + using var incrHash = IncrementalHash.CreateHash(HashAlgorithmName.SHA256); + + byte[] buffer = new byte[1024 * 1024]; + while (true) + { + if (!ReadFile(handle, buffer, (uint)buffer.Length, out uint bytesRead, IntPtr.Zero) || bytesRead == 0) + { + break; + } + + incrHash.AppendData(buffer, 0, (int)bytesRead); + } + + return incrHash.GetHashAndReset(); + } + + [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern SafeFileHandle CreateFile( + string lpFileName, + uint dwDesiredAccess, + uint dwShareMode, + IntPtr lpSecurityAttributes, + uint dwCreationDisposition, + uint dwFlagsAndAttributes, + IntPtr hTemplateFile); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool DeviceIoControl( + SafeFileHandle hDevice, + uint dwIoControlCode, + IntPtr lpInBuffer, + uint nInBufferSize, + byte[] lpOutBuffer, + uint nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool ReadFile( + SafeFileHandle hFile, + byte[] lpBuffer, + uint nNumberOfBytesToRead, + out uint lpNumberOfBytesRead, + IntPtr lpOverlapped); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorRunSettings.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorRunSettings.cs index ba1bdf3ebe..2c45dca28d 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorRunSettings.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorRunSettings.cs @@ -1,59 +1,59 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.Helpers; - - /// - /// Contains settings for the DSC v3 processor components to share. - /// - internal class ProcessorRunSettings - { - /// - /// Gets the paths for finding DSC resources and executables. - /// - public string ResourceSearchPaths { get; private set; } = string.Empty; - - /// - /// Gets a value indicating whether the resource search paths are exclusive. - /// - public bool ResourceSearchPathsExclusive { get; private set; } = false; - - /// - /// Creates ProcessorRunSettings from FindUnitProcessorsOptions. - /// - /// The find unit processors options. - /// A ProcessorRunSettings. - public static ProcessorRunSettings CreateFromFindUnitProcessorsOptions(FindUnitProcessorsOptions findOptions) - { - return new ProcessorRunSettings - { - ResourceSearchPaths = findOptions.SearchPaths, - ResourceSearchPathsExclusive = findOptions.SearchPathsExclusive, - }; - } - - /// - /// Creates ProcessorRunSettings from a ResourceDetails. - /// - /// The resource details to be used. - /// A ProcessorRunSettings. - public static ProcessorRunSettings CreateFromResourceDetails(ResourceDetails? resourceDetails) - { - return new ProcessorRunSettings - { - ResourceSearchPaths = Path.GetDirectoryName(resourceDetails?.Path) ?? string.Empty, - ResourceSearchPathsExclusive = false, - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Helpers; + + /// + /// Contains settings for the DSC v3 processor components to share. + /// + internal class ProcessorRunSettings + { + /// + /// Gets the paths for finding DSC resources and executables. + /// + public string ResourceSearchPaths { get; private set; } = string.Empty; + + /// + /// Gets a value indicating whether the resource search paths are exclusive. + /// + public bool ResourceSearchPathsExclusive { get; private set; } = false; + + /// + /// Creates ProcessorRunSettings from FindUnitProcessorsOptions. + /// + /// The find unit processors options. + /// A ProcessorRunSettings. + public static ProcessorRunSettings CreateFromFindUnitProcessorsOptions(FindUnitProcessorsOptions findOptions) + { + return new ProcessorRunSettings + { + ResourceSearchPaths = findOptions.SearchPaths, + ResourceSearchPathsExclusive = findOptions.SearchPathsExclusive, + }; + } + + /// + /// Creates ProcessorRunSettings from a ResourceDetails. + /// + /// The resource details to be used. + /// A ProcessorRunSettings. + public static ProcessorRunSettings CreateFromResourceDetails(ResourceDetails? resourceDetails) + { + return new ProcessorRunSettings + { + ResourceSearchPaths = Path.GetDirectoryName(resourceDetails?.Path) ?? string.Empty, + ResourceSearchPathsExclusive = false, + }; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs index 79f4cff589..a80d50ac42 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ProcessorSettings.cs @@ -1,376 +1,376 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Win32.SafeHandles; - - /// - /// Contains settings for the DSC v3 processor components to share. - /// - internal class ProcessorSettings : IDisposable - { - private readonly object dscV3Lock = new (); - private readonly object defaultPathLock = new (); - private readonly object processorPathLock = new (); - - private FindDscPackageStateMachine dscPackageStateMachine = new (); - private IDSCv3? dscV3 = null; - private string? defaultPath = null; - private string? defaultPathHash = null; - private bool? defaultPathIsAlias = null; - private SafeFileHandle? processorPathHandle = null; - private bool processorPathVerified = false; - private bool disposed = false; - - private Dictionary resourceDetailsDictionary = new (); - - /// - /// Gets or sets the path to the DSC v3 executable. - /// - public string? DscExecutablePath { get; set; } - - /// - /// Gets or sets the expected SHA256 hash of the DSC v3 executable (hex string). - /// Must be set before is accessed when a custom path is used. - /// - public string? DscExecutablePathHash { get; set; } - - /// - /// Gets or sets a value indicating whether is an app execution alias. - /// - public bool? DscExecutablePathIsAlias { get; set; } - - /// - /// Gets the path to the DSC v3 executable. - /// - public string EffectiveDscExecutablePath - { - get - { - if (this.DscExecutablePath != null) - { - this.EnsureProcessorPathVerified(); - return this.DscExecutablePath; - } - - lock (this.defaultPathLock) - { - if (this.defaultPath != null) - { - return this.defaultPath; - } - } - - string? localDefaultPath = this.GetFoundDscExecutablePath(); - - if (localDefaultPath == null) - { - throw new FileNotFoundException("Could not find DSC v3 executable path."); - } - - lock (this.defaultPathLock) - { - if (this.defaultPath == null) - { - this.defaultPath = localDefaultPath; - } - - return this.defaultPath; - } - } - } - - /// - /// Gets an object for interacting with the DSC executable at EffectiveDscExecutablePath. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Set is only provided for tests.")] - public IDSCv3 DSCv3 - { - get - { - lock (this.dscV3Lock) - { - if (this.dscV3 == null) - { - this.dscV3 = IDSCv3.Create(this); - } - - return this.dscV3; - } - } - -#if !AICLI_DISABLE_TEST_HOOKS - set - { - lock (this.dscV3Lock) - { - this.dscV3 = value; - } - } -#endif - } - - /// - /// Gets or sets the diagnostics sink to use. - /// - public IDiagnosticsSink? DiagnosticsSink { get; set; } = null; - - /// - /// Gets or sets a value indicating whether the processor should produce more verbose output. - /// - public bool DiagnosticTraceEnabled { get; set; } = false; - - /// - /// Find the DSC v3 executable. - /// - /// The full path to the dsc.exe executable, or null if not found. - public string? GetFoundDscExecutablePath() - { - string? result = this.dscPackageStateMachine.DscExecutablePath; - - if (result != null) - { - // Ensure hash and alias are computed and cached alongside the path. - this.EnsureFoundPathHashCached(result); - } - - return result; - } - - /// - /// Gets the SHA256 hash of the auto-discovered DSC executable path, or null if not yet discovered. - /// - /// Lowercase hex hash string, or null. - public string? GetFoundDscExecutablePathHash() - { - lock (this.defaultPathLock) - { - return this.defaultPathHash; - } - } - - /// - /// Gets whether the auto-discovered DSC executable path is an app execution alias, or null if not yet discovered. - /// - /// True if alias, false if regular file, or null if not yet discovered. - public bool? GetFoundDscExecutablePathIsAlias() - { - lock (this.defaultPathLock) - { - return this.defaultPathIsAlias; - } - } - - /// - /// Invokes a step in the DSC search state machine. - /// - /// The transition to take in the state machine. - public FindDscPackageStateMachine.Transition PumpFindDscStateMachine() - { - return this.dscPackageStateMachine.DetermineNextTransition(); - } - - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Create a deep copy of this settings object. - /// - /// A deep copy of this object. - public ProcessorSettings Clone() - { - ProcessorSettings result = new ProcessorSettings(); - - result.resourceDetailsDictionary = this.resourceDetailsDictionary; - result.DiagnosticsSink = this.DiagnosticsSink; - result.DscExecutablePath = this.DscExecutablePath; - result.DscExecutablePathHash = this.DscExecutablePathHash; - result.DscExecutablePathIsAlias = this.DscExecutablePathIsAlias; - result.DiagnosticTraceEnabled = this.DiagnosticTraceEnabled; - lock (this.defaultPathLock) - { - result.defaultPath = this.defaultPath; - result.defaultPathHash = this.defaultPathHash; - result.defaultPathIsAlias = this.defaultPathIsAlias; - } - -#if !AICLI_DISABLE_TEST_HOOKS - result.dscV3 = this.DSCv3; -#endif - - return result; - } - - /// - /// Gets a string representation of this object. - /// - /// A string representation of this object. - public override string ToString() - { - StringBuilder sb = new StringBuilder(); - - sb.Append("EffectiveDscExecutablePath: "); - sb.AppendLine(this.EffectiveDscExecutablePath); - - sb.Append("DiagnosticTraceLevel: "); - sb.Append(this.DiagnosticTraceEnabled); - - return sb.ToString(); - } - - /// - /// Gets the ResourceDetails for a configuration unit. - /// - /// The configuration unit to find details for. - /// The level of detail to get. - /// The ResourceDetails for the unit, or null if not found. - public ResourceDetails? GetResourceDetails(ConfigurationUnitInternal configurationUnitInternal, ConfigurationUnitDetailFlags detailFlags) - { - ResourceDetails? result = null; - bool inDictionary = false; - - lock (this.resourceDetailsDictionary) - { - inDictionary = this.resourceDetailsDictionary.TryGetValue(configurationUnitInternal.QualifiedName, out result); - } - - if (result == null) - { - result = new ResourceDetails(configurationUnitInternal.QualifiedName); - } - - result.EnsureDetails(this, detailFlags); - - if (result.Exists) - { - if (!inDictionary) - { - lock (this.resourceDetailsDictionary) - { - this.resourceDetailsDictionary.Add(configurationUnitInternal.QualifiedName, result); - } - } - - return result; - } - else - { - return null; - } - } - - /// - /// Gets all ResourceDetails matching find options. - /// - /// The find options. - /// A list of ResourceDetails. - public List FindAllResourceDetails(FindUnitProcessorsOptions findOptions) - { - List result = new List(); - - var resourceItemList = this.DSCv3.GetAllResources(ProcessorRunSettings.CreateFromFindUnitProcessorsOptions(findOptions)); - - foreach (var item in resourceItemList) - { - ResourceDetails? details = null; - bool inDictionary = false; - lock (this.resourceDetailsDictionary) - { - inDictionary = this.resourceDetailsDictionary.TryGetValue(item.Type, out details); - } - - if (details == null) - { - details = new ResourceDetails(item.Type); - } - - if (!details.Exists) - { - details.SetResourceListItem(item); - } - - details.EnsureDetails(this, findOptions.UnitDetailFlags); - - if (!inDictionary) - { - lock (this.resourceDetailsDictionary) - { - this.resourceDetailsDictionary.Add(item.Type, details); - } - } - - result.Add(details); - } - - return result; - } - - /// - /// Releases resources held by this instance, including the open handle used for TOCTOU protection. - /// - /// True if called from Dispose(); false if called from a finalizer. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (disposing) - { - lock (this.processorPathLock) - { - this.processorPathHandle?.Dispose(); - this.processorPathHandle = null; - } - } - - this.disposed = true; - } - } - - private void EnsureFoundPathHashCached(string path) - { - lock (this.defaultPathLock) - { - if (this.defaultPathHash == null) - { - this.defaultPathHash = ProcessorPathIntegrity.ComputeHash(path, out bool isAlias); - this.defaultPathIsAlias = isAlias; - } - } - } - - private void EnsureProcessorPathVerified() - { - lock (this.processorPathLock) - { - if (!this.processorPathVerified) - { - if (this.DscExecutablePathHash == null) - { - throw new InvalidOperationException("A custom processor path was provided without a hash for integrity verification."); - } - - bool isAlias = this.DscExecutablePathIsAlias ?? false; - this.processorPathHandle = ProcessorPathIntegrity.VerifyAndOpen( - this.DscExecutablePath!, - this.DscExecutablePathHash, - isAlias); - this.processorPathVerified = true; - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Win32.SafeHandles; + + /// + /// Contains settings for the DSC v3 processor components to share. + /// + internal class ProcessorSettings : IDisposable + { + private readonly object dscV3Lock = new (); + private readonly object defaultPathLock = new (); + private readonly object processorPathLock = new (); + + private FindDscPackageStateMachine dscPackageStateMachine = new (); + private IDSCv3? dscV3 = null; + private string? defaultPath = null; + private string? defaultPathHash = null; + private bool? defaultPathIsAlias = null; + private SafeFileHandle? processorPathHandle = null; + private bool processorPathVerified = false; + private bool disposed = false; + + private Dictionary resourceDetailsDictionary = new (); + + /// + /// Gets or sets the path to the DSC v3 executable. + /// + public string? DscExecutablePath { get; set; } + + /// + /// Gets or sets the expected SHA256 hash of the DSC v3 executable (hex string). + /// Must be set before is accessed when a custom path is used. + /// + public string? DscExecutablePathHash { get; set; } + + /// + /// Gets or sets a value indicating whether is an app execution alias. + /// + public bool? DscExecutablePathIsAlias { get; set; } + + /// + /// Gets the path to the DSC v3 executable. + /// + public string EffectiveDscExecutablePath + { + get + { + if (this.DscExecutablePath != null) + { + this.EnsureProcessorPathVerified(); + return this.DscExecutablePath; + } + + lock (this.defaultPathLock) + { + if (this.defaultPath != null) + { + return this.defaultPath; + } + } + + string? localDefaultPath = this.GetFoundDscExecutablePath(); + + if (localDefaultPath == null) + { + throw new FileNotFoundException("Could not find DSC v3 executable path."); + } + + lock (this.defaultPathLock) + { + if (this.defaultPath == null) + { + this.defaultPath = localDefaultPath; + } + + return this.defaultPath; + } + } + } + + /// + /// Gets an object for interacting with the DSC executable at EffectiveDscExecutablePath. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1623:Property summary documentation should match accessors", Justification = "Set is only provided for tests.")] + public IDSCv3 DSCv3 + { + get + { + lock (this.dscV3Lock) + { + if (this.dscV3 == null) + { + this.dscV3 = IDSCv3.Create(this); + } + + return this.dscV3; + } + } + +#if !AICLI_DISABLE_TEST_HOOKS + set + { + lock (this.dscV3Lock) + { + this.dscV3 = value; + } + } +#endif + } + + /// + /// Gets or sets the diagnostics sink to use. + /// + public IDiagnosticsSink? DiagnosticsSink { get; set; } = null; + + /// + /// Gets or sets a value indicating whether the processor should produce more verbose output. + /// + public bool DiagnosticTraceEnabled { get; set; } = false; + + /// + /// Find the DSC v3 executable. + /// + /// The full path to the dsc.exe executable, or null if not found. + public string? GetFoundDscExecutablePath() + { + string? result = this.dscPackageStateMachine.DscExecutablePath; + + if (result != null) + { + // Ensure hash and alias are computed and cached alongside the path. + this.EnsureFoundPathHashCached(result); + } + + return result; + } + + /// + /// Gets the SHA256 hash of the auto-discovered DSC executable path, or null if not yet discovered. + /// + /// Lowercase hex hash string, or null. + public string? GetFoundDscExecutablePathHash() + { + lock (this.defaultPathLock) + { + return this.defaultPathHash; + } + } + + /// + /// Gets whether the auto-discovered DSC executable path is an app execution alias, or null if not yet discovered. + /// + /// True if alias, false if regular file, or null if not yet discovered. + public bool? GetFoundDscExecutablePathIsAlias() + { + lock (this.defaultPathLock) + { + return this.defaultPathIsAlias; + } + } + + /// + /// Invokes a step in the DSC search state machine. + /// + /// The transition to take in the state machine. + public FindDscPackageStateMachine.Transition PumpFindDscStateMachine() + { + return this.dscPackageStateMachine.DetermineNextTransition(); + } + + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Create a deep copy of this settings object. + /// + /// A deep copy of this object. + public ProcessorSettings Clone() + { + ProcessorSettings result = new ProcessorSettings(); + + result.resourceDetailsDictionary = this.resourceDetailsDictionary; + result.DiagnosticsSink = this.DiagnosticsSink; + result.DscExecutablePath = this.DscExecutablePath; + result.DscExecutablePathHash = this.DscExecutablePathHash; + result.DscExecutablePathIsAlias = this.DscExecutablePathIsAlias; + result.DiagnosticTraceEnabled = this.DiagnosticTraceEnabled; + lock (this.defaultPathLock) + { + result.defaultPath = this.defaultPath; + result.defaultPathHash = this.defaultPathHash; + result.defaultPathIsAlias = this.defaultPathIsAlias; + } + +#if !AICLI_DISABLE_TEST_HOOKS + result.dscV3 = this.DSCv3; +#endif + + return result; + } + + /// + /// Gets a string representation of this object. + /// + /// A string representation of this object. + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + + sb.Append("EffectiveDscExecutablePath: "); + sb.AppendLine(this.EffectiveDscExecutablePath); + + sb.Append("DiagnosticTraceLevel: "); + sb.Append(this.DiagnosticTraceEnabled); + + return sb.ToString(); + } + + /// + /// Gets the ResourceDetails for a configuration unit. + /// + /// The configuration unit to find details for. + /// The level of detail to get. + /// The ResourceDetails for the unit, or null if not found. + public ResourceDetails? GetResourceDetails(ConfigurationUnitInternal configurationUnitInternal, ConfigurationUnitDetailFlags detailFlags) + { + ResourceDetails? result = null; + bool inDictionary = false; + + lock (this.resourceDetailsDictionary) + { + inDictionary = this.resourceDetailsDictionary.TryGetValue(configurationUnitInternal.QualifiedName, out result); + } + + if (result == null) + { + result = new ResourceDetails(configurationUnitInternal.QualifiedName); + } + + result.EnsureDetails(this, detailFlags); + + if (result.Exists) + { + if (!inDictionary) + { + lock (this.resourceDetailsDictionary) + { + this.resourceDetailsDictionary.Add(configurationUnitInternal.QualifiedName, result); + } + } + + return result; + } + else + { + return null; + } + } + + /// + /// Gets all ResourceDetails matching find options. + /// + /// The find options. + /// A list of ResourceDetails. + public List FindAllResourceDetails(FindUnitProcessorsOptions findOptions) + { + List result = new List(); + + var resourceItemList = this.DSCv3.GetAllResources(ProcessorRunSettings.CreateFromFindUnitProcessorsOptions(findOptions)); + + foreach (var item in resourceItemList) + { + ResourceDetails? details = null; + bool inDictionary = false; + lock (this.resourceDetailsDictionary) + { + inDictionary = this.resourceDetailsDictionary.TryGetValue(item.Type, out details); + } + + if (details == null) + { + details = new ResourceDetails(item.Type); + } + + if (!details.Exists) + { + details.SetResourceListItem(item); + } + + details.EnsureDetails(this, findOptions.UnitDetailFlags); + + if (!inDictionary) + { + lock (this.resourceDetailsDictionary) + { + this.resourceDetailsDictionary.Add(item.Type, details); + } + } + + result.Add(details); + } + + return result; + } + + /// + /// Releases resources held by this instance, including the open handle used for TOCTOU protection. + /// + /// True if called from Dispose(); false if called from a finalizer. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (disposing) + { + lock (this.processorPathLock) + { + this.processorPathHandle?.Dispose(); + this.processorPathHandle = null; + } + } + + this.disposed = true; + } + } + + private void EnsureFoundPathHashCached(string path) + { + lock (this.defaultPathLock) + { + if (this.defaultPathHash == null) + { + this.defaultPathHash = ProcessorPathIntegrity.ComputeHash(path, out bool isAlias); + this.defaultPathIsAlias = isAlias; + } + } + } + + private void EnsureProcessorPathVerified() + { + lock (this.processorPathLock) + { + if (!this.processorPathVerified) + { + if (this.DscExecutablePathHash == null) + { + throw new InvalidOperationException("A custom processor path was provided without a hash for integrity verification."); + } + + bool isAlias = this.DscExecutablePathIsAlias ?? false; + this.processorPathHandle = ProcessorPathIntegrity.VerifyAndOpen( + this.DscExecutablePath!, + this.DscExecutablePathHash, + isAlias); + this.processorPathVerified = true; + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ResourceDetails.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ResourceDetails.cs index f24c7b4173..ed097a9b10 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ResourceDetails.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Helpers/ResourceDetails.cs @@ -1,196 +1,196 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers -{ - using System; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.Unit; - - /// - /// Cached data about a resource. - /// - internal class ResourceDetails - { - private readonly string resourceTypeName; - - private object detailsUpdateLock = new object(); - private IResourceListItem? resourceListItem = null; - - /// - /// The current level of detail stored by this object. - /// - /// The method of discovery for each of the levels in ConfigurationUnitDetailFlags: - /// None: No details, either because the resource was not found or EnsureDetails has not been called. - /// Local: `resource list` is used to determine the details. An embedded schema may enable "Load" details level. - /// Property information may not be available at this level. - /// Catalog: Same as local; there is currently no catalog to query against. - /// Download: Same as local; there is currently no catalog to find anything to download. - /// Load: `resource schema` is used to get the full schema for the resource. - /// This ensures that property information is available. - /// - private ConfigurationUnitDetailFlags currentDetailLevel = ConfigurationUnitDetailFlags.None; - - /// - /// Initializes a new instance of the class. - /// - /// The resource type name. - public ResourceDetails(string resourceTypeName) - { - this.resourceTypeName = resourceTypeName; - } - - /// - /// Gets a value indicating whether this resource exists. - /// Will be false until EnsureDetails is called and the resource is found. - /// - public bool Exists - { - get - { - lock (this.detailsUpdateLock) - { - return this.currentDetailLevel != ConfigurationUnitDetailFlags.None; - } - } - } - - /// - /// Gets the path of the resource. - /// - public string? Path - { - get - { - lock (this.detailsUpdateLock) - { - return this.resourceListItem?.Path; - } - } - } - - /// - /// Sets the resource list item directly to avoid duplicate "dsc resource list" calls. - /// - /// The resource list item. - public void SetResourceListItem(IResourceListItem item) - { - lock (this.detailsUpdateLock) - { - if (this.resourceListItem != null) - { - throw new InvalidOperationException("Resource list item is already set"); - } - - this.resourceListItem = item; - this.currentDetailLevel |= ConfigurationUnitDetailFlags.Local; - } - } - - /// - /// Ensures that the given detail level is present. - /// - /// The processor settings to use when getting details. - /// The detail level flags. - public void EnsureDetails(ProcessorSettings processorSettings, ConfigurationUnitDetailFlags detailFlags) - { - if (this.DetailsNeededFor(detailFlags, ConfigurationUnitDetailFlags.Local)) - { - // If we can't get local details, then exit until we have more options. - if (!this.GetLocalDetails(processorSettings)) - { - return; - } - } - - if (this.DetailsNeededFor(detailFlags, ConfigurationUnitDetailFlags.Load)) - { - this.GetLoadDetails(processorSettings); - } - } - - /// - /// Gets a ConfigurationUnitProcessorDetails populated with all available data. - /// - /// A ConfigurationUnitProcessorDetails populated with all available data. - public ConfigurationUnitProcessorDetails? GetConfigurationUnitProcessorDetails() - { - if (!this.Exists) - { - return null; - } - - ConfigurationUnitProcessorDetails result = new ConfigurationUnitProcessorDetails() { UnitType = this.resourceTypeName }; - - lock (this.detailsUpdateLock) - { - if (this.resourceListItem != null) - { - result.UnitType = this.resourceListItem.Type; - result.IsGroup = IsGroup(this.resourceListItem.Kind); - result.Version = this.resourceListItem.Version; - result.UnitDescription = this.resourceListItem.Description; - result.Author = this.resourceListItem.Author; - result.Path = this.resourceListItem.Path; - - result.IsLocal = true; - } - } - - return result; - } - - private static bool IsGroup(ResourceKind kind) => kind switch - { - ResourceKind.Adapter => true, - ResourceKind.Group => true, - _ => false, - }; - - private bool DetailsNeededFor(ConfigurationUnitDetailFlags detailFlags, ConfigurationUnitDetailFlags targetLevel) - { - if (!detailFlags.HasFlag(targetLevel)) - { - return false; - } - - lock (this.detailsUpdateLock) - { - return !this.currentDetailLevel.HasFlag(targetLevel); - } - } - - private bool GetLocalDetails(ProcessorSettings processorSettings) - { - IResourceListItem? resourceListItem = processorSettings.DSCv3.GetResourceByType(this.resourceTypeName, null); - - if (resourceListItem != null) - { - // TODO: Attempt to extract embedded schema to avoid the need for Load. - lock (this.detailsUpdateLock) - { - if (!this.currentDetailLevel.HasFlag(ConfigurationUnitDetailFlags.Local)) - { - this.resourceListItem = resourceListItem; - this.currentDetailLevel |= ConfigurationUnitDetailFlags.Local; - } - } - - return true; - } - else - { - return false; - } - } - - private void GetLoadDetails(ProcessorSettings processorSettings) - { - throw new NotImplementedException(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Helpers +{ + using System; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.Unit; + + /// + /// Cached data about a resource. + /// + internal class ResourceDetails + { + private readonly string resourceTypeName; + + private object detailsUpdateLock = new object(); + private IResourceListItem? resourceListItem = null; + + /// + /// The current level of detail stored by this object. + /// + /// The method of discovery for each of the levels in ConfigurationUnitDetailFlags: + /// None: No details, either because the resource was not found or EnsureDetails has not been called. + /// Local: `resource list` is used to determine the details. An embedded schema may enable "Load" details level. + /// Property information may not be available at this level. + /// Catalog: Same as local; there is currently no catalog to query against. + /// Download: Same as local; there is currently no catalog to find anything to download. + /// Load: `resource schema` is used to get the full schema for the resource. + /// This ensures that property information is available. + /// + private ConfigurationUnitDetailFlags currentDetailLevel = ConfigurationUnitDetailFlags.None; + + /// + /// Initializes a new instance of the class. + /// + /// The resource type name. + public ResourceDetails(string resourceTypeName) + { + this.resourceTypeName = resourceTypeName; + } + + /// + /// Gets a value indicating whether this resource exists. + /// Will be false until EnsureDetails is called and the resource is found. + /// + public bool Exists + { + get + { + lock (this.detailsUpdateLock) + { + return this.currentDetailLevel != ConfigurationUnitDetailFlags.None; + } + } + } + + /// + /// Gets the path of the resource. + /// + public string? Path + { + get + { + lock (this.detailsUpdateLock) + { + return this.resourceListItem?.Path; + } + } + } + + /// + /// Sets the resource list item directly to avoid duplicate "dsc resource list" calls. + /// + /// The resource list item. + public void SetResourceListItem(IResourceListItem item) + { + lock (this.detailsUpdateLock) + { + if (this.resourceListItem != null) + { + throw new InvalidOperationException("Resource list item is already set"); + } + + this.resourceListItem = item; + this.currentDetailLevel |= ConfigurationUnitDetailFlags.Local; + } + } + + /// + /// Ensures that the given detail level is present. + /// + /// The processor settings to use when getting details. + /// The detail level flags. + public void EnsureDetails(ProcessorSettings processorSettings, ConfigurationUnitDetailFlags detailFlags) + { + if (this.DetailsNeededFor(detailFlags, ConfigurationUnitDetailFlags.Local)) + { + // If we can't get local details, then exit until we have more options. + if (!this.GetLocalDetails(processorSettings)) + { + return; + } + } + + if (this.DetailsNeededFor(detailFlags, ConfigurationUnitDetailFlags.Load)) + { + this.GetLoadDetails(processorSettings); + } + } + + /// + /// Gets a ConfigurationUnitProcessorDetails populated with all available data. + /// + /// A ConfigurationUnitProcessorDetails populated with all available data. + public ConfigurationUnitProcessorDetails? GetConfigurationUnitProcessorDetails() + { + if (!this.Exists) + { + return null; + } + + ConfigurationUnitProcessorDetails result = new ConfigurationUnitProcessorDetails() { UnitType = this.resourceTypeName }; + + lock (this.detailsUpdateLock) + { + if (this.resourceListItem != null) + { + result.UnitType = this.resourceListItem.Type; + result.IsGroup = IsGroup(this.resourceListItem.Kind); + result.Version = this.resourceListItem.Version; + result.UnitDescription = this.resourceListItem.Description; + result.Author = this.resourceListItem.Author; + result.Path = this.resourceListItem.Path; + + result.IsLocal = true; + } + } + + return result; + } + + private static bool IsGroup(ResourceKind kind) => kind switch + { + ResourceKind.Adapter => true, + ResourceKind.Group => true, + _ => false, + }; + + private bool DetailsNeededFor(ConfigurationUnitDetailFlags detailFlags, ConfigurationUnitDetailFlags targetLevel) + { + if (!detailFlags.HasFlag(targetLevel)) + { + return false; + } + + lock (this.detailsUpdateLock) + { + return !this.currentDetailLevel.HasFlag(targetLevel); + } + } + + private bool GetLocalDetails(ProcessorSettings processorSettings) + { + IResourceListItem? resourceListItem = processorSettings.DSCv3.GetResourceByType(this.resourceTypeName, null); + + if (resourceListItem != null) + { + // TODO: Attempt to extract embedded schema to avoid the need for Load. + lock (this.detailsUpdateLock) + { + if (!this.currentDetailLevel.HasFlag(ConfigurationUnitDetailFlags.Local)) + { + this.resourceListItem = resourceListItem; + this.currentDetailLevel |= ConfigurationUnitDetailFlags.Local; + } + } + + return true; + } + else + { + return false; + } + } + + private void GetLoadDetails(ProcessorSettings processorSettings) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IDSCv3.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IDSCv3.cs index 38633fb736..bad5e2b86d 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IDSCv3.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IDSCv3.cs @@ -1,76 +1,76 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Model -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.Processor.Helpers; - - /// - /// Interface for interacting with DSC v3. - /// - internal interface IDSCv3 - { - /// - /// Creates the appropriate instance of the DSCv3 interface for the given executable. - /// - /// The processor settings. - /// An object that properly interacts with the specific version of DSC v3. - public static IDSCv3 Create(ProcessorSettings processorSettings) - { - // Expand as needed to detect the version of dsc.exe and/or its schemas in use. - return new Schema_2024_04.DSCv3(processorSettings); - } - - /// - /// Gets a single resource by its type name. - /// - /// The type name of the resource. - /// The processor run settings. - /// A single resource item. - public IResourceListItem? GetResourceByType(string resourceType, ProcessorRunSettings? runSettings); - - /// - /// Gets all resource items. - /// - /// The processor run settings. - /// A list of resource items. - public List GetAllResources(ProcessorRunSettings? runSettings); - - /// - /// Tests a configuration unit. - /// - /// The unit to test. - /// The processor run settings. - /// A test result. - public IResourceTestItem TestResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); - - /// - /// Gets a configuration unit settings. - /// - /// The unit to get. - /// The processor run settings. - /// A get result. - public IResourceGetItem GetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); - - /// - /// Sets a configuration unit settings. - /// - /// The unit to set. - /// The processor run settings. - /// A set result. - public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); - - /// - /// Exports configuration unit. - /// - /// The unit to export. - /// The processor run settings. - /// A list of export results. - public IList ExportResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.Helpers; + + /// + /// Interface for interacting with DSC v3. + /// + internal interface IDSCv3 + { + /// + /// Creates the appropriate instance of the DSCv3 interface for the given executable. + /// + /// The processor settings. + /// An object that properly interacts with the specific version of DSC v3. + public static IDSCv3 Create(ProcessorSettings processorSettings) + { + // Expand as needed to detect the version of dsc.exe and/or its schemas in use. + return new Schema_2024_04.DSCv3(processorSettings); + } + + /// + /// Gets a single resource by its type name. + /// + /// The type name of the resource. + /// The processor run settings. + /// A single resource item. + public IResourceListItem? GetResourceByType(string resourceType, ProcessorRunSettings? runSettings); + + /// + /// Gets all resource items. + /// + /// The processor run settings. + /// A list of resource items. + public List GetAllResources(ProcessorRunSettings? runSettings); + + /// + /// Tests a configuration unit. + /// + /// The unit to test. + /// The processor run settings. + /// A test result. + public IResourceTestItem TestResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); + + /// + /// Gets a configuration unit settings. + /// + /// The unit to get. + /// The processor run settings. + /// A get result. + public IResourceGetItem GetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); + + /// + /// Sets a configuration unit settings. + /// + /// The unit to set. + /// The processor run settings. + /// A set result. + public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); + + /// + /// Exports configuration unit. + /// + /// The unit to export. + /// The processor run settings. + /// A list of export results. + public IList ExportResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceExportItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceExportItem.cs index ee2a243c31..1c535f1355 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceExportItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceExportItem.cs @@ -1,42 +1,42 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Model -{ - using System.Collections.Generic; - using Windows.Foundation.Collections; - - /// - /// The interface to a `resource export` command result. - /// - internal interface IResourceExportItem - { - /// - /// Gets the type of the resource. - /// - public string Type { get; } - - /// - /// Gets the name of the resource instance. - /// - public string Name { get; } - - /// - /// Gets the settings for this item. - /// - public ValueSet Settings { get; } - - /// - /// Gets the metadata for this item. - /// - public ValueSet Metadata { get; } - - /// - /// Gets the dependencies for this item. - /// - public IList Dependencies { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + using System.Collections.Generic; + using Windows.Foundation.Collections; + + /// + /// The interface to a `resource export` command result. + /// + internal interface IResourceExportItem + { + /// + /// Gets the type of the resource. + /// + public string Type { get; } + + /// + /// Gets the name of the resource instance. + /// + public string Name { get; } + + /// + /// Gets the settings for this item. + /// + public ValueSet Settings { get; } + + /// + /// Gets the metadata for this item. + /// + public ValueSet Metadata { get; } + + /// + /// Gets the dependencies for this item. + /// + public IList Dependencies { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceGetItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceGetItem.cs index d05dcb35dd..12904fc02a 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceGetItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceGetItem.cs @@ -1,21 +1,21 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Model -{ - using Windows.Foundation.Collections; - - /// - /// The interface to a `resource get` command result. - /// - internal interface IResourceGetItem - { - /// - /// Gets the settings for this item. - /// - public ValueSet Settings { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + using Windows.Foundation.Collections; + + /// + /// The interface to a `resource get` command result. + /// + internal interface IResourceGetItem + { + /// + /// Gets the settings for this item. + /// + public ValueSet Settings { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceListItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceListItem.cs index 14dcc290a3..f1132f6f5e 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceListItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceListItem.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Model -{ - /// - /// The interface to a single JSON line output by the `resource list` command. - /// - internal interface IResourceListItem - { - /// - /// Gets the type of the resource. - /// Should match the regex "^\\w+(\\.\\w+){0,2}\\/\\w+$". - /// - public string Type { get; } - - /// - /// Gets the kind of the resource. - /// - public ResourceKind Kind { get; } - - /// - /// Gets the version of the resource. - /// This is a semver version. - /// - public string? Version { get; } - - /// - /// Gets the description of the resource. - /// - public string? Description { get; } - - /// - /// Gets the path to the directory containing the resource. - /// - public string? Directory { get; } - - /// - /// Gets the author of the resource. - /// - public string? Author { get; } - - /// - /// Gets the path of the resource. - /// - public string? Path { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + /// + /// The interface to a single JSON line output by the `resource list` command. + /// + internal interface IResourceListItem + { + /// + /// Gets the type of the resource. + /// Should match the regex "^\\w+(\\.\\w+){0,2}\\/\\w+$". + /// + public string Type { get; } + + /// + /// Gets the kind of the resource. + /// + public ResourceKind Kind { get; } + + /// + /// Gets the version of the resource. + /// This is a semver version. + /// + public string? Version { get; } + + /// + /// Gets the description of the resource. + /// + public string? Description { get; } + + /// + /// Gets the path to the directory containing the resource. + /// + public string? Directory { get; } + + /// + /// Gets the author of the resource. + /// + public string? Author { get; } + + /// + /// Gets the path of the resource. + /// + public string? Path { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceSetItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceSetItem.cs index f8cc39ec4e..bae4495235 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceSetItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceSetItem.cs @@ -1,21 +1,21 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Model -{ - using System.Collections.Generic; - - /// - /// The interface to a `resource set` command result. - /// - internal interface IResourceSetItem - { - /// - /// Gets a value indicating whether a reboot is required. - /// - public bool RebootRequired { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + using System.Collections.Generic; + + /// + /// The interface to a `resource set` command result. + /// + internal interface IResourceSetItem + { + /// + /// Gets a value indicating whether a reboot is required. + /// + public bool RebootRequired { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceTestItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceTestItem.cs index 5aa9ace755..26606f1c8b 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceTestItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/IResourceTestItem.cs @@ -1,19 +1,19 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Model -{ - /// - /// The interface to a `resource test` command result. - /// - internal interface IResourceTestItem - { - /// - /// Gets a value indicating whether the resource is in the desired state. - /// - public bool InDesiredState { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + /// + /// The interface to a `resource test` command result. + /// + internal interface IResourceTestItem + { + /// + /// Gets a value indicating whether the resource is in the desired state. + /// + public bool InDesiredState { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/ResourceKind.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/ResourceKind.cs index 5799441c3a..1294e5d5e5 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/ResourceKind.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Model/ResourceKind.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Model -{ - /// - /// https://learn.microsoft.com/en-us/powershell/dsc/reference/schemas/definitions/resourcekind?view=dsc-3.0 - /// The kind of resource. - /// - internal enum ResourceKind - { - /// - /// The kind is unknown. - /// - Unknown, - - /// - /// A standard resource. - /// - Resource, - - /// - /// An adapter resource. - /// - Adapter, - - /// - /// A group resource. - /// - Group, - - /// - /// An importer resource. - /// - Importer, - - /// - /// An exporter resource. - /// - Exporter, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Model +{ + /// + /// https://learn.microsoft.com/en-us/powershell/dsc/reference/schemas/definitions/resourcekind?view=dsc-3.0 + /// The kind of resource. + /// + internal enum ResourceKind + { + /// + /// The kind is unknown. + /// + Unknown, + + /// + /// A standard resource. + /// + Resource, + + /// + /// An adapter resource. + /// + Adapter, + + /// + /// A group resource. + /// + Group, + + /// + /// An importer resource. + /// + Importer, + + /// + /// An exporter resource. + /// + Exporter, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/DSCv3.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/DSCv3.cs index cf546b9072..57f549cc00 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/DSCv3.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/DSCv3.cs @@ -1,337 +1,337 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04 -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Text.Json; - using System.Text.Json.Serialization; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.Processor.Helpers; - using Windows.Foundation.Collections; - - /// - /// An instance of IDSCv3 for interacting with 1.0. - /// - internal class DSCv3 : IDSCv3 - { - private const string PlainTextTraces = "-t plaintext"; - private const string DiagnosticTraceLevelArguments = "-l trace"; - private const string ResourceCommand = "resource"; - private const string ListCommand = "list"; - private const string TestCommand = "test"; - private const string GetCommand = "get"; - private const string SetCommand = "set"; - private const string ExportCommand = "export"; - private const string ResourceParameter = "-r"; - private const string FileParameter = "-f"; - private const string StdInputIdentifier = "-"; - - private const string PathEnvironmentVariable = "PATH"; - private const string DSCResourcePathEnvironmentVariable = "DSC_RESOURCE_PATH"; - - private readonly ProcessorSettings processorSettings; - - /// - /// Initializes a new instance of the class. - /// - /// The processor settings. - public DSCv3(ProcessorSettings processorSettings) - { - this.processorSettings = processorSettings; - } - - private string DiagnosticTraceLevel - { - get - { - return this.processorSettings.DiagnosticTraceEnabled ? DiagnosticTraceLevelArguments : string.Empty; - } - } - - /// - public IResourceListItem? GetResourceByType(string resourceType, ProcessorRunSettings? runSettings) - { - ResourceListItem? result = this.GetResourceByTypeInternal(resourceType, null, runSettings); - if (result != null) - { - return result; - } - - // Check for this resource within adapters - List results = new List(); - - foreach (ResourceListItem resource in this.GetAllResources(runSettings)) - { - if (resource.Kind == Definitions.ResourceKind.Adapter) - { - result = this.GetResourceByTypeInternal(resourceType, resource.Type, runSettings); - if (result != null) - { - results.Add(result); - } - } - } - - return this.GetResourceByLatestVersion(results); - } - - /// - public IResourceTestItem TestResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - ProcessExecution processExecution = new ProcessExecution() - { - ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, - Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, TestCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier }, - Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()), - EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), - }; - - if (this.RunSynchronously(processExecution)) - { - throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Test, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); - } - - return TestFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Test, unitInternal.QualifiedName), GetDefaultJsonOptions()); - } - - /// - public IResourceGetItem GetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - ProcessExecution processExecution = new ProcessExecution() - { - ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, - Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, GetCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier }, - Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()), - EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), - }; - - if (this.RunSynchronously(processExecution)) - { - throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Get, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); - } - - return GetFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Get, unitInternal.QualifiedName), GetDefaultJsonOptions()); - } - - /// - public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - ProcessExecution processExecution = new ProcessExecution() - { - ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, - Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, SetCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier }, - Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()), - EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), - }; - - if (this.RunSynchronously(processExecution)) - { - throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); - } - - return SetFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions()); - } - - /// - public IList ExportResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - // 3.0 can't handle input to export; 3.1 will fix that. - ValueSet expandedSettings = unitInternal.GetExpandedSettings(); - if (expandedSettings.Count != 0) - { - throw new NotImplementedException("Must use DSC v3.1.* to provide input to export."); - } - - ProcessExecution processExecution = new ProcessExecution() - { - ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, - Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ExportCommand, ResourceParameter, unitInternal.QualifiedName }, - EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), - }; - - if (this.RunSynchronously(processExecution)) - { - throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Export, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); - } - - return ConfigurationDocument.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions()).InterfaceResources; - } - - /// - public List GetAllResources(ProcessorRunSettings? runSettings) - { - ProcessExecution processExecution = new ProcessExecution() - { - ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, - Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ListCommand }, - EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), - }; - - this.RunSynchronously(processExecution); - - return GetOutputLinesAs(processExecution).ToList(); - } - - private static void ThrowOnMultipleOutputLines(ProcessExecution processExecution, string method, string resourceName) - { - if (processExecution.Output.Count > 1) - { - throw new Exceptions.InvokeDscResourceException(method, resourceName, processExecution.GetAllOutputLines()); - } - } - - private static void ThrowOnZeroOutputLines(ProcessExecution processExecution, string method, string resourceName) - { - if (processExecution.Output.Count == 0) - { - throw new Exceptions.InvokeDscResourceException(method, resourceName); - } - } - - private static T? GetOptionalSingleOutputLineAs(ProcessExecution processExecution) - { - if (processExecution.Output.Count == 0) - { - return default; - } - - return JsonSerializer.Deserialize(processExecution.Output.First(), GetDefaultJsonOptions()); - } - - private static List GetOutputLinesAs(ProcessExecution processExecution) - { - List result = new List(); - var options = GetDefaultJsonOptions(); - - foreach (string line in processExecution.Output) - { - T? lineObject = JsonSerializer.Deserialize(line, options); - if (lineObject != null) - { - result.Add(lineObject); - } - } - - return result; - } - - private static JsonDocument GetRequiredSingleOutputLineAsJSON(ProcessExecution processExecution, string method, string resourceName) - { - ThrowOnMultipleOutputLines(processExecution, method, resourceName); - ThrowOnZeroOutputLines(processExecution, method, resourceName); - - return JsonDocument.Parse(processExecution.Output.First()); - } - - private static JsonSerializerOptions GetDefaultJsonOptions() - { - return new JsonSerializerOptions() - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter(), - }, - }; - } - - private static string ConvertValueSetToJSON(ValueSet valueSet) - { - return JsonSerializer.Serialize(valueSet.ToHashtable()); - } - - private static List CreateEnvironmentVariablesFromProcessorRunSettings(ProcessorRunSettings? runSettings) - { - List result = new List(); - if (runSettings is not null && !string.IsNullOrEmpty(runSettings.ResourceSearchPaths)) - { - // For exclusive search paths, adding to PATH is still needed as anything referenced in the manifest is still searched from PATH. - result.Add(new ProcessExecutionEnvironmentVariable { Name = PathEnvironmentVariable, Value = runSettings.ResourceSearchPaths, ValueType = ProcessExecutionEnvironmentVariableValueType.Prepend }); - - if (runSettings.ResourceSearchPathsExclusive) - { - result.Add(new ProcessExecutionEnvironmentVariable { Name = DSCResourcePathEnvironmentVariable, Value = runSettings.ResourceSearchPaths, ValueType = ProcessExecutionEnvironmentVariableValueType.Override }); - } - } - - return result; - } - - /// - /// Runs the process, waiting until it completes. - /// - /// The process to run. - /// True if the exit code was not 0. - private bool RunSynchronously(ProcessExecution processExecution) - { - this.processorSettings.DiagnosticsSink?.OnDiagnostics(DiagnosticLevel.Verbose, $"[{processExecution.ExecutionNumber}] Starting process: {processExecution.CommandLine}{(processExecution.Input == null ? string.Empty : $"\n--- Input Stream ---\n{processExecution.Input}")}"); - - using (var batcher = new ProcessOutputBatcher(this.processorSettings.DiagnosticsSink, TimeSpan.FromMilliseconds(500))) - { - try - { - batcher.Subscribe(processExecution); - processExecution.Start().WaitForExit(); - } - finally - { - batcher.Flush(); - } - } - - this.processorSettings.DiagnosticsSink?.OnDiagnostics(DiagnosticLevel.Verbose, $"Process exited with code: {processExecution.ExitCode}"); - - return processExecution.ExitCode != 0; - } - - private ResourceListItem? GetResourceByTypeInternal(string resourceType, string? adapter, ProcessorRunSettings? runSettings) - { - ProcessExecution processExecution = new ProcessExecution() - { - ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, - Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ListCommand, adapter != null ? $"-a {adapter}" : string.Empty, resourceType }, - EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), - }; - - this.RunSynchronously(processExecution); - - List results = GetOutputLinesAs(processExecution); - - return this.GetResourceByLatestVersion(results); - } - - private ResourceListItem? GetResourceByLatestVersion(List resources) - { - // There may be different versions of same resource on the system. We check if all - // resource types match, we return the first one. - // TODO: May want to pick the latest one from the list. But since we are not using - // the version in our commands, picking any one is good for now. - ResourceListItem? candidate = null; - string candidateType = string.Empty; - - foreach (ResourceListItem resource in resources) - { - if (candidate == null) - { - candidate = resource; - candidateType = resource.Type; - } - else if (!candidateType.Equals(resource.Type, StringComparison.OrdinalIgnoreCase)) - { - throw new Exceptions.GetDscResourceMultipleMatches(candidateType, null); - } - } - - return candidate; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04 +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.Processor.Helpers; + using Windows.Foundation.Collections; + + /// + /// An instance of IDSCv3 for interacting with 1.0. + /// + internal class DSCv3 : IDSCv3 + { + private const string PlainTextTraces = "-t plaintext"; + private const string DiagnosticTraceLevelArguments = "-l trace"; + private const string ResourceCommand = "resource"; + private const string ListCommand = "list"; + private const string TestCommand = "test"; + private const string GetCommand = "get"; + private const string SetCommand = "set"; + private const string ExportCommand = "export"; + private const string ResourceParameter = "-r"; + private const string FileParameter = "-f"; + private const string StdInputIdentifier = "-"; + + private const string PathEnvironmentVariable = "PATH"; + private const string DSCResourcePathEnvironmentVariable = "DSC_RESOURCE_PATH"; + + private readonly ProcessorSettings processorSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The processor settings. + public DSCv3(ProcessorSettings processorSettings) + { + this.processorSettings = processorSettings; + } + + private string DiagnosticTraceLevel + { + get + { + return this.processorSettings.DiagnosticTraceEnabled ? DiagnosticTraceLevelArguments : string.Empty; + } + } + + /// + public IResourceListItem? GetResourceByType(string resourceType, ProcessorRunSettings? runSettings) + { + ResourceListItem? result = this.GetResourceByTypeInternal(resourceType, null, runSettings); + if (result != null) + { + return result; + } + + // Check for this resource within adapters + List results = new List(); + + foreach (ResourceListItem resource in this.GetAllResources(runSettings)) + { + if (resource.Kind == Definitions.ResourceKind.Adapter) + { + result = this.GetResourceByTypeInternal(resourceType, resource.Type, runSettings); + if (result != null) + { + results.Add(result); + } + } + } + + return this.GetResourceByLatestVersion(results); + } + + /// + public IResourceTestItem TestResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + ProcessExecution processExecution = new ProcessExecution() + { + ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, + Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, TestCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier }, + Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()), + EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), + }; + + if (this.RunSynchronously(processExecution)) + { + throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Test, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); + } + + return TestFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Test, unitInternal.QualifiedName), GetDefaultJsonOptions()); + } + + /// + public IResourceGetItem GetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + ProcessExecution processExecution = new ProcessExecution() + { + ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, + Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, GetCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier }, + Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()), + EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), + }; + + if (this.RunSynchronously(processExecution)) + { + throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Get, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); + } + + return GetFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Get, unitInternal.QualifiedName), GetDefaultJsonOptions()); + } + + /// + public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + ProcessExecution processExecution = new ProcessExecution() + { + ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, + Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, SetCommand, ResourceParameter, unitInternal.QualifiedName, FileParameter, StdInputIdentifier }, + Input = ConvertValueSetToJSON(unitInternal.GetExpandedSettings()), + EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), + }; + + if (this.RunSynchronously(processExecution)) + { + throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); + } + + return SetFullItem.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions()); + } + + /// + public IList ExportResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + // 3.0 can't handle input to export; 3.1 will fix that. + ValueSet expandedSettings = unitInternal.GetExpandedSettings(); + if (expandedSettings.Count != 0) + { + throw new NotImplementedException("Must use DSC v3.1.* to provide input to export."); + } + + ProcessExecution processExecution = new ProcessExecution() + { + ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, + Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ExportCommand, ResourceParameter, unitInternal.QualifiedName }, + EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), + }; + + if (this.RunSynchronously(processExecution)) + { + throw new Exceptions.InvokeDscResourceException(Exceptions.InvokeDscResourceException.Export, unitInternal.QualifiedName, null, processExecution.GetAllErrorLines()); + } + + return ConfigurationDocument.CreateFrom(GetRequiredSingleOutputLineAsJSON(processExecution, Exceptions.InvokeDscResourceException.Set, unitInternal.QualifiedName), GetDefaultJsonOptions()).InterfaceResources; + } + + /// + public List GetAllResources(ProcessorRunSettings? runSettings) + { + ProcessExecution processExecution = new ProcessExecution() + { + ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, + Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ListCommand }, + EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), + }; + + this.RunSynchronously(processExecution); + + return GetOutputLinesAs(processExecution).ToList(); + } + + private static void ThrowOnMultipleOutputLines(ProcessExecution processExecution, string method, string resourceName) + { + if (processExecution.Output.Count > 1) + { + throw new Exceptions.InvokeDscResourceException(method, resourceName, processExecution.GetAllOutputLines()); + } + } + + private static void ThrowOnZeroOutputLines(ProcessExecution processExecution, string method, string resourceName) + { + if (processExecution.Output.Count == 0) + { + throw new Exceptions.InvokeDscResourceException(method, resourceName); + } + } + + private static T? GetOptionalSingleOutputLineAs(ProcessExecution processExecution) + { + if (processExecution.Output.Count == 0) + { + return default; + } + + return JsonSerializer.Deserialize(processExecution.Output.First(), GetDefaultJsonOptions()); + } + + private static List GetOutputLinesAs(ProcessExecution processExecution) + { + List result = new List(); + var options = GetDefaultJsonOptions(); + + foreach (string line in processExecution.Output) + { + T? lineObject = JsonSerializer.Deserialize(line, options); + if (lineObject != null) + { + result.Add(lineObject); + } + } + + return result; + } + + private static JsonDocument GetRequiredSingleOutputLineAsJSON(ProcessExecution processExecution, string method, string resourceName) + { + ThrowOnMultipleOutputLines(processExecution, method, resourceName); + ThrowOnZeroOutputLines(processExecution, method, resourceName); + + return JsonDocument.Parse(processExecution.Output.First()); + } + + private static JsonSerializerOptions GetDefaultJsonOptions() + { + return new JsonSerializerOptions() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(), + }, + }; + } + + private static string ConvertValueSetToJSON(ValueSet valueSet) + { + return JsonSerializer.Serialize(valueSet.ToHashtable()); + } + + private static List CreateEnvironmentVariablesFromProcessorRunSettings(ProcessorRunSettings? runSettings) + { + List result = new List(); + if (runSettings is not null && !string.IsNullOrEmpty(runSettings.ResourceSearchPaths)) + { + // For exclusive search paths, adding to PATH is still needed as anything referenced in the manifest is still searched from PATH. + result.Add(new ProcessExecutionEnvironmentVariable { Name = PathEnvironmentVariable, Value = runSettings.ResourceSearchPaths, ValueType = ProcessExecutionEnvironmentVariableValueType.Prepend }); + + if (runSettings.ResourceSearchPathsExclusive) + { + result.Add(new ProcessExecutionEnvironmentVariable { Name = DSCResourcePathEnvironmentVariable, Value = runSettings.ResourceSearchPaths, ValueType = ProcessExecutionEnvironmentVariableValueType.Override }); + } + } + + return result; + } + + /// + /// Runs the process, waiting until it completes. + /// + /// The process to run. + /// True if the exit code was not 0. + private bool RunSynchronously(ProcessExecution processExecution) + { + this.processorSettings.DiagnosticsSink?.OnDiagnostics(DiagnosticLevel.Verbose, $"[{processExecution.ExecutionNumber}] Starting process: {processExecution.CommandLine}{(processExecution.Input == null ? string.Empty : $"\n--- Input Stream ---\n{processExecution.Input}")}"); + + using (var batcher = new ProcessOutputBatcher(this.processorSettings.DiagnosticsSink, TimeSpan.FromMilliseconds(500))) + { + try + { + batcher.Subscribe(processExecution); + processExecution.Start().WaitForExit(); + } + finally + { + batcher.Flush(); + } + } + + this.processorSettings.DiagnosticsSink?.OnDiagnostics(DiagnosticLevel.Verbose, $"Process exited with code: {processExecution.ExitCode}"); + + return processExecution.ExitCode != 0; + } + + private ResourceListItem? GetResourceByTypeInternal(string resourceType, string? adapter, ProcessorRunSettings? runSettings) + { + ProcessExecution processExecution = new ProcessExecution() + { + ExecutablePath = this.processorSettings.EffectiveDscExecutablePath, + Arguments = new[] { PlainTextTraces, this.DiagnosticTraceLevel, ResourceCommand, ListCommand, adapter != null ? $"-a {adapter}" : string.Empty, resourceType }, + EnvironmentVariables = CreateEnvironmentVariablesFromProcessorRunSettings(runSettings), + }; + + this.RunSynchronously(processExecution); + + List results = GetOutputLinesAs(processExecution); + + return this.GetResourceByLatestVersion(results); + } + + private ResourceListItem? GetResourceByLatestVersion(List resources) + { + // There may be different versions of same resource on the system. We check if all + // resource types match, we return the first one. + // TODO: May want to pick the latest one from the list. But since we are not using + // the version in our commands, picking any one is good for now. + ResourceListItem? candidate = null; + string candidateType = string.Empty; + + foreach (ResourceListItem resource in resources) + { + if (candidate == null) + { + candidate = resource; + candidateType = resource.Type; + } + else if (!candidateType.Equals(resource.Type, StringComparison.OrdinalIgnoreCase)) + { + throw new Exceptions.GetDscResourceMultipleMatches(candidateType, null); + } + } + + return candidate; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Definitions/ResourceKind.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Definitions/ResourceKind.cs index a845264751..75e29e16b9 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Definitions/ResourceKind.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Definitions/ResourceKind.cs @@ -1,52 +1,52 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Definitions -{ - /// - /// https://learn.microsoft.com/en-us/powershell/dsc/reference/schemas/definitions/resourcekind?view=dsc-3.0 - /// The kind of resource. - /// - internal enum ResourceKind - { - /// - /// The kind is unknown. - /// - Unknown, - - /// - /// A standard resource. - /// - Resource, - - /// - /// An adapter resource. - /// - Adapter, - - /// - /// A group resource. - /// - Group, - - /// - /// An import(er) resource. - /// The name listed in the DSC schema. - /// - Import, - - /// - /// An importer resource. - /// The name used by the code. - /// - Importer = Import, - - /// - /// An exporter resource. - /// - Exporter, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Definitions +{ + /// + /// https://learn.microsoft.com/en-us/powershell/dsc/reference/schemas/definitions/resourcekind?view=dsc-3.0 + /// The kind of resource. + /// + internal enum ResourceKind + { + /// + /// The kind is unknown. + /// + Unknown, + + /// + /// A standard resource. + /// + Resource, + + /// + /// An adapter resource. + /// + Adapter, + + /// + /// A group resource. + /// + Group, + + /// + /// An import(er) resource. + /// The name listed in the DSC schema. + /// + Import, + + /// + /// An importer resource. + /// The name used by the code. + /// + Importer = Import, + + /// + /// An exporter resource. + /// + Exporter, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Metadata/ResourceInstanceResult.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Metadata/ResourceInstanceResult.cs index 0fedf0f14e..35bc795c20 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Metadata/ResourceInstanceResult.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Metadata/ResourceInstanceResult.cs @@ -1,36 +1,36 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Metadata -{ - using System; - using System.Text.Json.Serialization; - - /// - /// Defines metadata DSC returns for a DSC configuration operation against a resource instance. - /// - internal class ResourceInstanceResult - { - /// - /// Gets or sets the context metadata for this instance result. - /// - [JsonRequired] - [JsonPropertyName("Microsoft.DSC")] - public ContextMetadata? MicrosoftDSC { get; set; } - - /// - /// Contains properties generated by the DSC v3 platform. - /// - public class ContextMetadata - { - /// - /// Gets or sets the duration of a resource instance execution. - /// - [JsonRequired] - public TimeSpan Duration { get; set; } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Metadata +{ + using System; + using System.Text.Json.Serialization; + + /// + /// Defines metadata DSC returns for a DSC configuration operation against a resource instance. + /// + internal class ResourceInstanceResult + { + /// + /// Gets or sets the context metadata for this instance result. + /// + [JsonRequired] + [JsonPropertyName("Microsoft.DSC")] + public ContextMetadata? MicrosoftDSC { get; set; } + + /// + /// Contains properties generated by the DSC v3 platform. + /// + public class ContextMetadata + { + /// + /// Gets or sets the duration of a resource instance execution. + /// + [JsonRequired] + public TimeSpan Duration { get; set; } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ConfigurationDocument.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ConfigurationDocument.cs index 1f1a4bad8b..cc2ff997bb 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ConfigurationDocument.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ConfigurationDocument.cs @@ -1,56 +1,56 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Text.Json; - using System.Text.Json.Serialization; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - - /// - /// A configuration document. - /// - internal class ConfigurationDocument - { - /// - /// Gets or sets the list of resources in the document. - /// - public List Resources { get; set; } = new List(); - - /// - /// Gets the list of resources as the interface version. - /// - [JsonIgnore] - public IList InterfaceResources - { - get - { - return new List(this.Resources.AsEnumerable()); - } - } - - /// - /// Initializes a new instance of the ConfigurationDocument class. - /// - /// The document to construct from. - /// The options to use. - /// The item created. - public static ConfigurationDocument CreateFrom(JsonDocument document, JsonSerializerOptions options) - { - ConfigurationDocument? result = JsonSerializer.Deserialize(document, options); - - if (result == null) - { - throw new InvalidDataException("Unable to deserialize ConfigurationDocument."); - } - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// + /// A configuration document. + /// + internal class ConfigurationDocument + { + /// + /// Gets or sets the list of resources in the document. + /// + public List Resources { get; set; } = new List(); + + /// + /// Gets the list of resources as the interface version. + /// + [JsonIgnore] + public IList InterfaceResources + { + get + { + return new List(this.Resources.AsEnumerable()); + } + } + + /// + /// Initializes a new instance of the ConfigurationDocument class. + /// + /// The document to construct from. + /// The options to use. + /// The item created. + public static ConfigurationDocument CreateFrom(JsonDocument document, JsonSerializerOptions options) + { + ConfigurationDocument? result = JsonSerializer.Deserialize(document, options); + + if (result == null) + { + throw new InvalidDataException("Unable to deserialize ConfigurationDocument."); + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/FullItemBase.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/FullItemBase.cs index bf7cabdea9..ab134f1d99 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/FullItemBase.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/FullItemBase.cs @@ -1,127 +1,127 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using System.IO; - using System.Text.Json; - using System.Text.Json.Nodes; - using System.Text.Json.Serialization; - using Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Metadata; - - /// - /// The base implementation of the full form output. - /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full result, which also includes the resource type and instance name. - /// - /// The simple item type. - /// The full item type. - internal class FullItemBase - where TFull : FullItemBase, new() - { - private const string NameProperty = "name"; - - /// - /// Initializes a new instance of the class. - /// - public FullItemBase() - { - } - - /// - /// Gets or sets the metadata for this test result. - /// - [JsonRequired] - public ResourceInstanceResult? Metadata { get; set; } - - /// - /// Gets or sets the name for this test result. - /// - [JsonRequired] - public string? Name { get; set; } - - /// - /// Gets or sets the type for the resource. - /// - [JsonRequired] - public string? Type { get; set; } - - /// - /// Gets or sets the result of the test. - /// - [JsonRequired] - public JsonNode? Result { get; set; } - - /// - /// Gets or sets the simple result. - /// - [JsonIgnore] - public TSimple? SimpleResult { get; set; } - - /// - /// Gets or sets the full results. - /// - [JsonIgnore] - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Opening square brackets should be spaced correctly", Justification = "Pending SC 1.2 release")] - protected TFull[]? FullResults { get; set; } - - /// - /// Initializes a new instance of the TFull class. - /// - /// The document to construct from. - /// The options to use. - /// The item created. - public static TFull CreateFrom(JsonDocument document, JsonSerializerOptions options) - { - if (!document.RootElement.TryGetProperty(NameProperty, out JsonElement jsonElement)) - { - return new () { SimpleResult = JsonSerializer.Deserialize(document, options) }; - } - else - { - TFull? result = JsonSerializer.Deserialize(document, options); - - if (result == null) - { - throw new InvalidDataException("Unable to deserialize full result."); - } - - result.ProcessResult(options); - return result; - } - } - - /// - /// Converts the Result property into the appropriate simple or full results. - /// - /// The options to use. - public void ProcessResult(JsonSerializerOptions options) - { - if (this.Result == null) - { - throw new System.InvalidOperationException("JSON result has not been initialized."); - } - - if (this.Result is JsonObject jsonObject) - { - this.SimpleResult = JsonSerializer.Deserialize(this.Result, options); - } - else - { - this.FullResults = JsonSerializer.Deserialize(this.Result, options); - - if (this.FullResults == null) - { - throw new InvalidDataException("Unable to deserialize full results."); - } - - foreach (TFull result in this.FullResults) - { - result.ProcessResult(options); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.IO; + using System.Text.Json; + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + using Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Metadata; + + /// + /// The base implementation of the full form output. + /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full result, which also includes the resource type and instance name. + /// + /// The simple item type. + /// The full item type. + internal class FullItemBase + where TFull : FullItemBase, new() + { + private const string NameProperty = "name"; + + /// + /// Initializes a new instance of the class. + /// + public FullItemBase() + { + } + + /// + /// Gets or sets the metadata for this test result. + /// + [JsonRequired] + public ResourceInstanceResult? Metadata { get; set; } + + /// + /// Gets or sets the name for this test result. + /// + [JsonRequired] + public string? Name { get; set; } + + /// + /// Gets or sets the type for the resource. + /// + [JsonRequired] + public string? Type { get; set; } + + /// + /// Gets or sets the result of the test. + /// + [JsonRequired] + public JsonNode? Result { get; set; } + + /// + /// Gets or sets the simple result. + /// + [JsonIgnore] + public TSimple? SimpleResult { get; set; } + + /// + /// Gets or sets the full results. + /// + [JsonIgnore] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Opening square brackets should be spaced correctly", Justification = "Pending SC 1.2 release")] + protected TFull[]? FullResults { get; set; } + + /// + /// Initializes a new instance of the TFull class. + /// + /// The document to construct from. + /// The options to use. + /// The item created. + public static TFull CreateFrom(JsonDocument document, JsonSerializerOptions options) + { + if (!document.RootElement.TryGetProperty(NameProperty, out JsonElement jsonElement)) + { + return new () { SimpleResult = JsonSerializer.Deserialize(document, options) }; + } + else + { + TFull? result = JsonSerializer.Deserialize(document, options); + + if (result == null) + { + throw new InvalidDataException("Unable to deserialize full result."); + } + + result.ProcessResult(options); + return result; + } + } + + /// + /// Converts the Result property into the appropriate simple or full results. + /// + /// The options to use. + public void ProcessResult(JsonSerializerOptions options) + { + if (this.Result == null) + { + throw new System.InvalidOperationException("JSON result has not been initialized."); + } + + if (this.Result is JsonObject jsonObject) + { + this.SimpleResult = JsonSerializer.Deserialize(this.Result, options); + } + else + { + this.FullResults = JsonSerializer.Deserialize(this.Result, options); + + if (this.FullResults == null) + { + throw new InvalidDataException("Unable to deserialize full results."); + } + + foreach (TFull result in this.FullResults) + { + result.ProcessResult(options); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetFullItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetFullItem.cs index 804476e3e1..f010a57f21 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetFullItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetFullItem.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.Extensions; - using Windows.Foundation.Collections; - - /// - /// The full form of the get output. - /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full get result, which also includes the resource type and instance name. - /// - internal class GetFullItem : FullItemBase, IResourceGetItem - { - /// - /// Initializes a new instance of the class. - /// - public GetFullItem() - { - } - - /// - public ValueSet Settings - { - get - { - if (this.SimpleResult != null) - { - return this.SimpleResult.ActualState?.ToValueSet() ?? throw new System.InvalidOperationException("Get result has not been initialized."); - } - else if (this.FullResults != null) - { - throw new System.NotImplementedException("Requires constructing the entire group as the settings."); - } - else - { - throw new System.InvalidOperationException("Get result has not been initialized."); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Extensions; + using Windows.Foundation.Collections; + + /// + /// The full form of the get output. + /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full get result, which also includes the resource type and instance name. + /// + internal class GetFullItem : FullItemBase, IResourceGetItem + { + /// + /// Initializes a new instance of the class. + /// + public GetFullItem() + { + } + + /// + public ValueSet Settings + { + get + { + if (this.SimpleResult != null) + { + return this.SimpleResult.ActualState?.ToValueSet() ?? throw new System.InvalidOperationException("Get result has not been initialized."); + } + else if (this.FullResults != null) + { + throw new System.NotImplementedException("Requires constructing the entire group as the settings."); + } + else + { + throw new System.InvalidOperationException("Get result has not been initialized."); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetSimpleItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetSimpleItem.cs index bc45975a2c..ee3d5fb58a 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetSimpleItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/GetSimpleItem.cs @@ -1,24 +1,24 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using System.Text.Json.Nodes; - using System.Text.Json.Serialization; - - /// - /// The simple form of the get output. - /// DSC returns a simple get response when the instance isn't a group resource, adapter resource, or nested inside a group or adapter resource. - /// - internal class GetSimpleItem - { - /// - /// Gets or sets the state of the resource properties. - /// - [JsonRequired] - public JsonObject? ActualState { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + + /// + /// The simple form of the get output. + /// DSC returns a simple get response when the instance isn't a group resource, adapter resource, or nested inside a group or adapter resource. + /// + internal class GetSimpleItem + { + /// + /// Gets or sets the state of the resource properties. + /// + [JsonRequired] + public JsonObject? ActualState { get; set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceCapability.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceCapability.cs index 37a652fa52..f54b41aab0 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceCapability.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceCapability.cs @@ -1,60 +1,60 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - /// - /// https://learn.microsoft.com/en-us/powershell/dsc/reference/schemas/outputs/resource/list?view=dsc-3.0#capabilities - /// The capabilities that a resource can have. - /// - internal enum ResourceCapability - { - /// - /// Can call get on the resource. - /// Required. - /// - Get, - - /// - /// Can call set on the resource. - /// - Set, - - /// - /// The resource operates properly in the presence of the `_exist` property. - /// If not present, DSC will use `delete` when `_exist == false`. - /// - SetHandlesExist, - - /// - /// The resource can handle a "what if" query directly. - /// Otherwise, DSC will handle it synthetically. - /// - WhatIf, - - /// - /// The resource can handle a "test" query directly. - /// Otherwise, DSC will handle it synthetically. - /// - Test, - - /// - /// Can call delete on the resource. - /// - Delete, - - /// - /// Can call export on the resource. - /// - Export, - - /// - /// Can call resolve on the resource. - /// This can produce new resources, such as importing another configuration document. - /// - Resolve, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + /// + /// https://learn.microsoft.com/en-us/powershell/dsc/reference/schemas/outputs/resource/list?view=dsc-3.0#capabilities + /// The capabilities that a resource can have. + /// + internal enum ResourceCapability + { + /// + /// Can call get on the resource. + /// Required. + /// + Get, + + /// + /// Can call set on the resource. + /// + Set, + + /// + /// The resource operates properly in the presence of the `_exist` property. + /// If not present, DSC will use `delete` when `_exist == false`. + /// + SetHandlesExist, + + /// + /// The resource can handle a "what if" query directly. + /// Otherwise, DSC will handle it synthetically. + /// + WhatIf, + + /// + /// The resource can handle a "test" query directly. + /// Otherwise, DSC will handle it synthetically. + /// + Test, + + /// + /// Can call delete on the resource. + /// + Delete, + + /// + /// Can call export on the resource. + /// + Export, + + /// + /// Can call resolve on the resource. + /// This can produce new resources, such as importing another configuration document. + /// + Resolve, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceItem.cs index 2461a434d7..111b62a11c 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceItem.cs @@ -1,81 +1,81 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using System.Collections.Generic; - using System.Text.Json.Nodes; - using System.Text.Json.Serialization; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.Extensions; - using Windows.Foundation.Collections; - - /// - /// The object type from a single resource item. - /// - internal class ResourceItem : IResourceExportItem - { - /// - /// Gets or sets the type of the resource. - /// Should match the regex "^\\w+(\\.\\w+){0,2}\\/\\w+$". - /// - [JsonRequired] - required public string Type { get; set; } - - /// - /// Gets or sets the name of the resource instance. - /// - [JsonRequired] - required public string Name { get; set; } - - /// - /// Gets or sets the properties object. - /// - public JsonObject? Properties { get; set; } - - /// - /// Gets or sets the metadata object. - /// - [JsonPropertyName("metadata")] - public JsonObject? MetadataObject { get; set; } - - /// - /// Gets or sets the dependencies. - /// - [JsonPropertyName("dependencies")] - public List DependenciesList { get; set; } = new List(); - - /// - [JsonIgnore] - public ValueSet Settings - { - get - { - return this.Properties.ToValueSet(); - } - } - - /// - [JsonIgnore] - public ValueSet Metadata - { - get - { - return this.MetadataObject.ToValueSet(); - } - } - - /// - [JsonIgnore] - public IList Dependencies - { - get - { - return this.DependenciesList; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Collections.Generic; + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Extensions; + using Windows.Foundation.Collections; + + /// + /// The object type from a single resource item. + /// + internal class ResourceItem : IResourceExportItem + { + /// + /// Gets or sets the type of the resource. + /// Should match the regex "^\\w+(\\.\\w+){0,2}\\/\\w+$". + /// + [JsonRequired] + required public string Type { get; set; } + + /// + /// Gets or sets the name of the resource instance. + /// + [JsonRequired] + required public string Name { get; set; } + + /// + /// Gets or sets the properties object. + /// + public JsonObject? Properties { get; set; } + + /// + /// Gets or sets the metadata object. + /// + [JsonPropertyName("metadata")] + public JsonObject? MetadataObject { get; set; } + + /// + /// Gets or sets the dependencies. + /// + [JsonPropertyName("dependencies")] + public List DependenciesList { get; set; } = new List(); + + /// + [JsonIgnore] + public ValueSet Settings + { + get + { + return this.Properties.ToValueSet(); + } + } + + /// + [JsonIgnore] + public ValueSet Metadata + { + get + { + return this.MetadataObject.ToValueSet(); + } + } + + /// + [JsonIgnore] + public IList Dependencies + { + get + { + return this.DependenciesList; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceListItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceListItem.cs index 03eee84c3f..8abbd29cc3 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceListItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/ResourceListItem.cs @@ -1,96 +1,96 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using System.Text.Json.Nodes; - using System.Text.Json.Serialization; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - - /// - /// The object type from a single JSON line output by the `resource list` command. - /// - internal class ResourceListItem : IResourceListItem - { - /// - /// Gets or sets the type of the resource. - /// Should match the regex "^\\w+(\\.\\w+){0,2}\\/\\w+$". - /// - [JsonRequired] - required public string Type { get; set; } - - /// - /// Gets or sets the kind of the resource. - /// - public Definitions.ResourceKind Kind { get; set; } = Definitions.ResourceKind.Unknown; - - /// - [JsonIgnore] - Model.ResourceKind IResourceListItem.Kind => this.Kind switch - { - Definitions.ResourceKind.Unknown => Model.ResourceKind.Unknown, - Definitions.ResourceKind.Resource => Model.ResourceKind.Resource, - Definitions.ResourceKind.Adapter => Model.ResourceKind.Adapter, - Definitions.ResourceKind.Group => Model.ResourceKind.Group, - Definitions.ResourceKind.Import => Model.ResourceKind.Importer, - Definitions.ResourceKind.Exporter => Model.ResourceKind.Exporter, - _ => throw new System.IO.InvalidDataException($"Unknown ResourceKind: {this.Kind}") - }; - - /// - /// Gets or sets the version of the resource. - /// This is a semver version. - /// - public string? Version { get; set; } - - /// - /// Gets or sets the capabilities of the resource. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public ResourceCapability[] Capabilities { get; set; } = []; - - /// - /// Gets or sets the description of the resource. - /// - public string? Description { get; set; } - - /// - /// Gets or sets the path to the resource definition file. - /// - public string? Path { get; set; } - - /// - /// Gets or sets the path to the directory containing the resource. - /// - public string? Directory { get; set; } - - /// - /// Gets or sets a value that indicates implementation details of the resource. - /// - public JsonNode? ImplementedAs { get; set; } - - /// - /// Gets or sets the author of the resource. - /// - public string? Author { get; set; } - - /// - /// Gets or sets the names of the properties of the resource. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public string[] Properties { get; set; } = []; - - /// - /// Gets or sets the adapter required by the resource. - /// - public string? RequireAdapter { get; set; } - - /// - /// Gets or sets the resource definition manifest. - /// - public JsonObject? Manifest { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// + /// The object type from a single JSON line output by the `resource list` command. + /// + internal class ResourceListItem : IResourceListItem + { + /// + /// Gets or sets the type of the resource. + /// Should match the regex "^\\w+(\\.\\w+){0,2}\\/\\w+$". + /// + [JsonRequired] + required public string Type { get; set; } + + /// + /// Gets or sets the kind of the resource. + /// + public Definitions.ResourceKind Kind { get; set; } = Definitions.ResourceKind.Unknown; + + /// + [JsonIgnore] + Model.ResourceKind IResourceListItem.Kind => this.Kind switch + { + Definitions.ResourceKind.Unknown => Model.ResourceKind.Unknown, + Definitions.ResourceKind.Resource => Model.ResourceKind.Resource, + Definitions.ResourceKind.Adapter => Model.ResourceKind.Adapter, + Definitions.ResourceKind.Group => Model.ResourceKind.Group, + Definitions.ResourceKind.Import => Model.ResourceKind.Importer, + Definitions.ResourceKind.Exporter => Model.ResourceKind.Exporter, + _ => throw new System.IO.InvalidDataException($"Unknown ResourceKind: {this.Kind}") + }; + + /// + /// Gets or sets the version of the resource. + /// This is a semver version. + /// + public string? Version { get; set; } + + /// + /// Gets or sets the capabilities of the resource. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public ResourceCapability[] Capabilities { get; set; } = []; + + /// + /// Gets or sets the description of the resource. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the path to the resource definition file. + /// + public string? Path { get; set; } + + /// + /// Gets or sets the path to the directory containing the resource. + /// + public string? Directory { get; set; } + + /// + /// Gets or sets a value that indicates implementation details of the resource. + /// + public JsonNode? ImplementedAs { get; set; } + + /// + /// Gets or sets the author of the resource. + /// + public string? Author { get; set; } + + /// + /// Gets or sets the names of the properties of the resource. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public string[] Properties { get; set; } = []; + + /// + /// Gets or sets the adapter required by the resource. + /// + public string? RequireAdapter { get; set; } + + /// + /// Gets or sets the resource definition manifest. + /// + public JsonObject? Manifest { get; set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetFullItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetFullItem.cs index 7ceaf96b07..7b9b1e018d 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetFullItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetFullItem.cs @@ -1,27 +1,27 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - - /// - /// The full form of the set output. - /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full set result, which also includes the resource type and instance name. - /// - internal class SetFullItem : FullItemBase, IResourceSetItem - { - /// - /// Initializes a new instance of the class. - /// - public SetFullItem() - { - } - - /// - public bool RebootRequired => false; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// + /// The full form of the set output. + /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full set result, which also includes the resource type and instance name. + /// + internal class SetFullItem : FullItemBase, IResourceSetItem + { + /// + /// Initializes a new instance of the class. + /// + public SetFullItem() + { + } + + /// + public bool RebootRequired => false; + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetSimpleItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetSimpleItem.cs index 626a5753e1..0d054b5314 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetSimpleItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/SetSimpleItem.cs @@ -1,37 +1,37 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using System.Text.Json.Nodes; - using System.Text.Json.Serialization; - - /// - /// The simple form of the set output. - /// DSC returns a simple set response when the instance isn't a group resource, adapter resource, or nested inside a group or adapter resource. - /// - internal class SetSimpleItem - { - /// - /// Gets or sets the state of the resource properties before the attempt to set them. - /// - [JsonRequired] - public JsonObject? BeforeState { get; set; } - - /// - /// Gets or sets the state of the resource properties after the attempt to set them. - /// - [JsonRequired] - public JsonObject? AfterState { get; set; } - - /// - /// Gets or sets the list of properties that changed. - /// - [JsonRequired] - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public string[] ChangedProperties { get; set; } = []; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + + /// + /// The simple form of the set output. + /// DSC returns a simple set response when the instance isn't a group resource, adapter resource, or nested inside a group or adapter resource. + /// + internal class SetSimpleItem + { + /// + /// Gets or sets the state of the resource properties before the attempt to set them. + /// + [JsonRequired] + public JsonObject? BeforeState { get; set; } + + /// + /// Gets or sets the state of the resource properties after the attempt to set them. + /// + [JsonRequired] + public JsonObject? AfterState { get; set; } + + /// + /// Gets or sets the list of properties that changed. + /// + [JsonRequired] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public string[] ChangedProperties { get; set; } = []; + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestFullItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestFullItem.cs index c976ef1350..1e4b9d9b91 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestFullItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestFullItem.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - - /// - /// The full form of the test output. - /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full test result, which also includes the resource type and instance name. - /// - internal class TestFullItem : FullItemBase, IResourceTestItem - { - /// - /// Initializes a new instance of the class. - /// - public TestFullItem() - { - } - - /// - public bool InDesiredState - { - get - { - if (this.SimpleResult != null) - { - return this.SimpleResult.InDesiredState; - } - else if (this.FullResults != null) - { - bool result = true; - - foreach (var item in this.FullResults) - { - result = result && item.InDesiredState; - } - - return result; - } - else - { - throw new System.InvalidOperationException("Test result has not been initialized."); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// + /// The full form of the test output. + /// When the retrieved instance is for group resource, adapter resource, or nested inside a group or adapter resource, DSC returns a full test result, which also includes the resource type and instance name. + /// + internal class TestFullItem : FullItemBase, IResourceTestItem + { + /// + /// Initializes a new instance of the class. + /// + public TestFullItem() + { + } + + /// + public bool InDesiredState + { + get + { + if (this.SimpleResult != null) + { + return this.SimpleResult.InDesiredState; + } + else if (this.FullResults != null) + { + bool result = true; + + foreach (var item in this.FullResults) + { + result = result && item.InDesiredState; + } + + return result; + } + else + { + throw new System.InvalidOperationException("Test result has not been initialized."); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestSimpleItem.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestSimpleItem.cs index aea72383b7..5139acb1d2 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestSimpleItem.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Schema_2024_04/Outputs/TestSimpleItem.cs @@ -1,43 +1,43 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs -{ - using System.Text.Json.Nodes; - using System.Text.Json.Serialization; - - /// - /// The simple form of the test output. - /// DSC returns a simple test response when the instance isn't a group resource, adapter resource, or nested inside a group or adapter resource. - /// - internal class TestSimpleItem - { - /// - /// Gets or sets the desired state of the resource properties. - /// - [JsonRequired] - public JsonObject? DesiredState { get; set; } - - /// - /// Gets or sets the actual state of the resource properties. - /// - [JsonRequired] - public JsonObject? ActualState { get; set; } - - /// - /// Gets or sets a value indicating whether the resource is in the desired state. - /// - [JsonRequired] - public bool InDesiredState { get; set; } - - /// - /// Gets or sets the list of properties that are not in the desired state. - /// - [JsonRequired] - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] - public string[] DifferingProperties { get; set; } = []; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Schema_2024_04.Outputs +{ + using System.Text.Json.Nodes; + using System.Text.Json.Serialization; + + /// + /// The simple form of the test output. + /// DSC returns a simple test response when the instance isn't a group resource, adapter resource, or nested inside a group or adapter resource. + /// + internal class TestSimpleItem + { + /// + /// Gets or sets the desired state of the resource properties. + /// + [JsonRequired] + public JsonObject? DesiredState { get; set; } + + /// + /// Gets or sets the actual state of the resource properties. + /// + [JsonRequired] + public JsonObject? ActualState { get; set; } + + /// + /// Gets or sets a value indicating whether the resource is in the desired state. + /// + [JsonRequired] + public bool InDesiredState { get; set; } + + /// + /// Gets or sets the list of properties that are not in the desired state. + /// + [JsonRequired] + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1010:Opening square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/3687 pending SC 1.2 release")] + public string[] DifferingProperties { get; set; } = []; + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Set/DSCv3ConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Set/DSCv3ConfigurationSetProcessor.cs index a678a73e91..0bf1522cf7 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Set/DSCv3ConfigurationSetProcessor.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Set/DSCv3ConfigurationSetProcessor.cs @@ -1,83 +1,83 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Set -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.Processor.DSCv3.Unit; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.Set; - - /// - /// Configuration set processor. - /// - internal sealed partial class DSCv3ConfigurationSetProcessor : ConfigurationSetProcessorBase, IConfigurationSetProcessor, IFindUnitProcessorsSetProcessor - { - private readonly ProcessorSettings processorSettings; - - /// - /// Initializes a new instance of the class. - /// - /// The processor settings to use. - /// Configuration set. - /// Whether the set processor should work in limitation mode. - public DSCv3ConfigurationSetProcessor(ProcessorSettings processorSettings, ConfigurationSet? configurationSet, bool isLimitMode = false) - : base(configurationSet, isLimitMode) - { - this.processorSettings = processorSettings; - } - - /// - protected override IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit) - { - ConfigurationUnitInternal configurationUnitInternal = new ConfigurationUnitInternal(unit, this.ConfigurationSet?.Path); - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}..."); - - ResourceDetails? resourceDetails = this.processorSettings.GetResourceDetails(configurationUnitInternal, ConfigurationUnitDetailFlags.Local); - if (resourceDetails == null) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Resource not found: {configurationUnitInternal.QualifiedName}"); - - // Don't throw when the resource is not found until https://github.com/PowerShell/DSC/issues/786 is resolved - // throw new Exceptions.FindDscResourceNotFoundException(configurationUnitInternal.QualifiedName, null); - } - - return new DSCv3ConfigurationUnitProcessor(this.processorSettings, resourceDetails, configurationUnitInternal, this.IsLimitMode) { SetProcessorFactory = this.SetProcessorFactory }; - } - - /// - protected override IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) - { - ConfigurationUnitInternal configurationUnitInternal = new ConfigurationUnitInternal(unit, this.ConfigurationSet?.Path); - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting resource details [{detailFlags}] for: {configurationUnitInternal.QualifiedName}..."); - - ResourceDetails? resourceDetails = this.processorSettings.GetResourceDetails(configurationUnitInternal, detailFlags); - if (resourceDetails == null) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Resource not found: {configurationUnitInternal.QualifiedName}"); - return null; - } - - return resourceDetails.GetConfigurationUnitProcessorDetails(); - } - - /// - protected override IList FindUnitProcessorsInternal(FindUnitProcessorsOptions findOptions) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Finding unit processors with following options. SearchPaths: {findOptions.SearchPaths}, SearchPathsExclusive: {findOptions.SearchPathsExclusive}, DetailFlags: [{findOptions.UnitDetailFlags}]"); - List result = new List(); - - var resourceDetailsList = this.processorSettings.FindAllResourceDetails(findOptions); - foreach (var resourceDetails in resourceDetailsList) - { - result.Add(resourceDetails.GetConfigurationUnitProcessorDetails() !); - } - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Set +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.DSCv3.Unit; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.Set; + + /// + /// Configuration set processor. + /// + internal sealed partial class DSCv3ConfigurationSetProcessor : ConfigurationSetProcessorBase, IConfigurationSetProcessor, IFindUnitProcessorsSetProcessor + { + private readonly ProcessorSettings processorSettings; + + /// + /// Initializes a new instance of the class. + /// + /// The processor settings to use. + /// Configuration set. + /// Whether the set processor should work in limitation mode. + public DSCv3ConfigurationSetProcessor(ProcessorSettings processorSettings, ConfigurationSet? configurationSet, bool isLimitMode = false) + : base(configurationSet, isLimitMode) + { + this.processorSettings = processorSettings; + } + + /// + protected override IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit) + { + ConfigurationUnitInternal configurationUnitInternal = new ConfigurationUnitInternal(unit, this.ConfigurationSet?.Path); + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}..."); + + ResourceDetails? resourceDetails = this.processorSettings.GetResourceDetails(configurationUnitInternal, ConfigurationUnitDetailFlags.Local); + if (resourceDetails == null) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Resource not found: {configurationUnitInternal.QualifiedName}"); + + // Don't throw when the resource is not found until https://github.com/PowerShell/DSC/issues/786 is resolved + // throw new Exceptions.FindDscResourceNotFoundException(configurationUnitInternal.QualifiedName, null); + } + + return new DSCv3ConfigurationUnitProcessor(this.processorSettings, resourceDetails, configurationUnitInternal, this.IsLimitMode) { SetProcessorFactory = this.SetProcessorFactory }; + } + + /// + protected override IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) + { + ConfigurationUnitInternal configurationUnitInternal = new ConfigurationUnitInternal(unit, this.ConfigurationSet?.Path); + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting resource details [{detailFlags}] for: {configurationUnitInternal.QualifiedName}..."); + + ResourceDetails? resourceDetails = this.processorSettings.GetResourceDetails(configurationUnitInternal, detailFlags); + if (resourceDetails == null) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Resource not found: {configurationUnitInternal.QualifiedName}"); + return null; + } + + return resourceDetails.GetConfigurationUnitProcessorDetails(); + } + + /// + protected override IList FindUnitProcessorsInternal(FindUnitProcessorsOptions findOptions) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Finding unit processors with following options. SearchPaths: {findOptions.SearchPaths}, SearchPathsExclusive: {findOptions.SearchPathsExclusive}, DetailFlags: [{findOptions.UnitDetailFlags}]"); + List result = new List(); + + var resourceDetailsList = this.processorSettings.FindAllResourceDetails(findOptions); + foreach (var resourceDetails in resourceDetailsList) + { + result.Add(resourceDetails.GetConfigurationUnitProcessorDetails() !); + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/DSCv3/Unit/DSCv3ConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.Processor/DSCv3/Unit/DSCv3ConfigurationUnitProcessor.cs index ff0a11c4b5..6acbb6e388 100644 --- a/src/Microsoft.Management.Configuration.Processor/DSCv3/Unit/DSCv3ConfigurationUnitProcessor.cs +++ b/src/Microsoft.Management.Configuration.Processor/DSCv3/Unit/DSCv3ConfigurationUnitProcessor.cs @@ -1,108 +1,108 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.DSCv3.Unit -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.Unit; - using Windows.Foundation.Collections; - - /// - /// Provides access to a specific configuration unit within the runtime. - /// - internal sealed partial class DSCv3ConfigurationUnitProcessor : ConfigurationUnitProcessorBase, IConfigurationUnitProcessor, IGetAllSettingsConfigurationUnitProcessor, IGetAllUnitsConfigurationUnitProcessor, IDiagnosticsSink - { - private readonly ProcessorSettings processorSettings; - private readonly ResourceDetails? resourceDetails; - - /// - /// Initializes a new instance of the class. - /// - /// The processor settings to use. - /// The resource to use. - /// Internal unit. - /// Whether it is under limit mode. - internal DSCv3ConfigurationUnitProcessor(ProcessorSettings processorSettings, ResourceDetails? resourceDetails, ConfigurationUnitInternal unitInternal, bool isLimitMode = false) - : base(unitInternal, isLimitMode) - { - this.processorSettings = processorSettings; - this.resourceDetails = resourceDetails; - } - - /// - void IDiagnosticsSink.OnDiagnostics(DiagnosticLevel level, string message) - { - this.OnDiagnostics(level, message); - } - - /// - protected override ValueSet GetSettingsInternal() - { - return this.processorSettings.DSCv3.GetResourceSettings(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)).Settings; - } - - /// - protected override bool TestSettingsInternal() - { - return this.processorSettings.DSCv3.TestResource(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)).InDesiredState; - } - - /// - protected override bool ApplySettingsInternal() - { - return this.processorSettings.DSCv3.SetResourceSettings(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)).RebootRequired; - } - - /// - protected override IList? GetAllSettingsInternal() - { - var exportResult = this.processorSettings.DSCv3.ExportResource(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)); - - string expectedType = this.UnitInternal.QualifiedName.ToLowerInvariant(); - List result = new List(); - - foreach (var exportItem in exportResult) - { - if (exportItem.Type.ToLowerInvariant() != expectedType) - { - throw new UnitPropertyUnsupportedException(typeof(IGetAllSettingsConfigurationUnitProcessor)); - } - - result.Add(exportItem.Settings); - } - - return result; - } - - /// - protected override IList? GetAllUnitsInternal() - { - var exportResult = this.processorSettings.DSCv3.ExportResource(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)); - - List result = new List(); - - foreach (var exportItem in exportResult) - { - ConfigurationUnit unit = new ConfigurationUnit(); - - unit.Type = exportItem.Type; - unit.Identifier = exportItem.Name; - unit.Settings = exportItem.Settings; - unit.Metadata = exportItem.Metadata; - unit.Dependencies = exportItem.Dependencies; - - result.Add(unit); - } - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.DSCv3.Unit +{ + using System; + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.Unit; + using Windows.Foundation.Collections; + + /// + /// Provides access to a specific configuration unit within the runtime. + /// + internal sealed partial class DSCv3ConfigurationUnitProcessor : ConfigurationUnitProcessorBase, IConfigurationUnitProcessor, IGetAllSettingsConfigurationUnitProcessor, IGetAllUnitsConfigurationUnitProcessor, IDiagnosticsSink + { + private readonly ProcessorSettings processorSettings; + private readonly ResourceDetails? resourceDetails; + + /// + /// Initializes a new instance of the class. + /// + /// The processor settings to use. + /// The resource to use. + /// Internal unit. + /// Whether it is under limit mode. + internal DSCv3ConfigurationUnitProcessor(ProcessorSettings processorSettings, ResourceDetails? resourceDetails, ConfigurationUnitInternal unitInternal, bool isLimitMode = false) + : base(unitInternal, isLimitMode) + { + this.processorSettings = processorSettings; + this.resourceDetails = resourceDetails; + } + + /// + void IDiagnosticsSink.OnDiagnostics(DiagnosticLevel level, string message) + { + this.OnDiagnostics(level, message); + } + + /// + protected override ValueSet GetSettingsInternal() + { + return this.processorSettings.DSCv3.GetResourceSettings(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)).Settings; + } + + /// + protected override bool TestSettingsInternal() + { + return this.processorSettings.DSCv3.TestResource(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)).InDesiredState; + } + + /// + protected override bool ApplySettingsInternal() + { + return this.processorSettings.DSCv3.SetResourceSettings(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)).RebootRequired; + } + + /// + protected override IList? GetAllSettingsInternal() + { + var exportResult = this.processorSettings.DSCv3.ExportResource(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)); + + string expectedType = this.UnitInternal.QualifiedName.ToLowerInvariant(); + List result = new List(); + + foreach (var exportItem in exportResult) + { + if (exportItem.Type.ToLowerInvariant() != expectedType) + { + throw new UnitPropertyUnsupportedException(typeof(IGetAllSettingsConfigurationUnitProcessor)); + } + + result.Add(exportItem.Settings); + } + + return result; + } + + /// + protected override IList? GetAllUnitsInternal() + { + var exportResult = this.processorSettings.DSCv3.ExportResource(this.UnitInternal, ProcessorRunSettings.CreateFromResourceDetails(this.resourceDetails)); + + List result = new List(); + + foreach (var exportItem in exportResult) + { + ConfigurationUnit unit = new ConfigurationUnit(); + + unit.Type = exportItem.Type; + unit.Identifier = exportItem.Name; + unit.Settings = exportItem.Settings; + unit.Metadata = exportItem.Metadata; + unit.Dependencies = exportItem.Dependencies; + + result.Add(unit); + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/DscProcessorHashMismatchException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/DscProcessorHashMismatchException.cs index 70790ff8ed..d115530f87 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/DscProcessorHashMismatchException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/DscProcessorHashMismatchException.cs @@ -1,26 +1,26 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using Microsoft.PowerShell.Commands; - - /// - /// DSC processor does not match provided hash. - /// - internal class DscProcessorHashMismatchException : Exception - { - /// - /// Initializes a new instance of the class. - /// - public DscProcessorHashMismatchException() - : base("The DSC processor hash provided does not match hash of the target file.") - { - this.HResult = ErrorCodes.WinGetConfigProcessorHashMismatch; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using Microsoft.PowerShell.Commands; + + /// + /// DSC processor does not match provided hash. + /// + internal class DscProcessorHashMismatchException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public DscProcessorHashMismatchException() + : base("The DSC processor hash provided does not match hash of the target file.") + { + this.HResult = ErrorCodes.WinGetConfigProcessorHashMismatch; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs index 94653913de..bf620ae3af 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/ErrorCodes.cs @@ -1,84 +1,84 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - /// - /// This should match the ones in AppInstallerErrors.h. - /// - internal static class ErrorCodes - { - /// - /// Corresponds to E_UNEXPECTED; this code path was reached without the developer realizing it was possible. - /// - internal const int Unexpected = unchecked((int)0x8000ffff); - - /// - /// The module of the unit was installed, but the unit was not found. - /// - internal const int WinGetConfigUnitNotFound = unchecked((int)0x8A15C101); - - /// - /// The unit couldn't be found in the repository. - /// - internal const int WinGetConfigUnitNotFoundRepository = unchecked((int)0x8A15C102); - - /// - /// Multiple units found with the same criteria. - /// - internal const int WinGetConfigUnitMultipleMatches = unchecked((int)0x8A15C103); - - /// - /// Unit error calling Invoke-DscResource Get. - /// - internal const int WinGetConfigUnitInvokeGet = unchecked((int)0x8A15C104); - - /// - /// Unit error calling Invoke-DscResource Test. - /// - internal const int WinGetConfigUnitInvokeTest = unchecked((int)0x8A15C105); - - /// - /// Unit error calling Invoke-DscResource Set. - /// - internal const int WinGetConfigUnitInvokeSet = unchecked((int)0x8A15C106); - - /// - /// Internal error calling Get-DscResource. More than one module found with the same version. - /// - internal const int WinGetConfigUnitModuleConflict = unchecked((int)0x8A15C107); - - /// - /// The module where the DSC resource is implemented cannot be imported. - /// - internal const int WinGetConfigUnitImportModule = unchecked((int)0x8A15C108); - - /// - /// The unit returned an invalid result. - /// - internal const int WinGetConfigUnitInvokeInvalidResult = unchecked((int)0x8A15C109); - - /// - /// The unit contains a setting that requires config root. - /// - internal const int WinGetConfigUnitSettingConfigRoot = unchecked((int)0x8A15C110); - - /// - /// The module where the DSC resource is implemented requires admin. - /// - internal const int WinGetConfigUnitImportModuleAdmin = unchecked((int)0x8A15C111); - - /// - /// The property type of a unit is not supported. - /// - internal const int WinGetConfigUnitUnsupportedType = unchecked((int)0x8A15C112); - - /// - /// The DSC processor hash provided does not match hash of the target file. - /// - internal const int WinGetConfigProcessorHashMismatch = unchecked((int)0x8A15C113); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + /// + /// This should match the ones in AppInstallerErrors.h. + /// + internal static class ErrorCodes + { + /// + /// Corresponds to E_UNEXPECTED; this code path was reached without the developer realizing it was possible. + /// + internal const int Unexpected = unchecked((int)0x8000ffff); + + /// + /// The module of the unit was installed, but the unit was not found. + /// + internal const int WinGetConfigUnitNotFound = unchecked((int)0x8A15C101); + + /// + /// The unit couldn't be found in the repository. + /// + internal const int WinGetConfigUnitNotFoundRepository = unchecked((int)0x8A15C102); + + /// + /// Multiple units found with the same criteria. + /// + internal const int WinGetConfigUnitMultipleMatches = unchecked((int)0x8A15C103); + + /// + /// Unit error calling Invoke-DscResource Get. + /// + internal const int WinGetConfigUnitInvokeGet = unchecked((int)0x8A15C104); + + /// + /// Unit error calling Invoke-DscResource Test. + /// + internal const int WinGetConfigUnitInvokeTest = unchecked((int)0x8A15C105); + + /// + /// Unit error calling Invoke-DscResource Set. + /// + internal const int WinGetConfigUnitInvokeSet = unchecked((int)0x8A15C106); + + /// + /// Internal error calling Get-DscResource. More than one module found with the same version. + /// + internal const int WinGetConfigUnitModuleConflict = unchecked((int)0x8A15C107); + + /// + /// The module where the DSC resource is implemented cannot be imported. + /// + internal const int WinGetConfigUnitImportModule = unchecked((int)0x8A15C108); + + /// + /// The unit returned an invalid result. + /// + internal const int WinGetConfigUnitInvokeInvalidResult = unchecked((int)0x8A15C109); + + /// + /// The unit contains a setting that requires config root. + /// + internal const int WinGetConfigUnitSettingConfigRoot = unchecked((int)0x8A15C110); + + /// + /// The module where the DSC resource is implemented requires admin. + /// + internal const int WinGetConfigUnitImportModuleAdmin = unchecked((int)0x8A15C111); + + /// + /// The property type of a unit is not supported. + /// + internal const int WinGetConfigUnitUnsupportedType = unchecked((int)0x8A15C112); + + /// + /// The DSC processor hash provided does not match hash of the target file. + /// + internal const int WinGetConfigProcessorHashMismatch = unchecked((int)0x8A15C113); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/FindDscResourceNotFoundException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/FindDscResourceNotFoundException.cs index afc53e196e..60d2332c90 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/FindDscResourceNotFoundException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/FindDscResourceNotFoundException.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using Microsoft.PowerShell.Commands; - - /// - /// Resource not found by Find-DscResource. - /// - internal class FindDscResourceNotFoundException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Resource name. - /// Optional module. - public FindDscResourceNotFoundException(string resourceName, ModuleSpecification? module) - : base($"Could not find resource: {resourceName} [{module?.ToString() ?? ""}]") - { - this.HResult = ErrorCodes.WinGetConfigUnitNotFoundRepository; - this.ResourceName = resourceName; - this.Module = module; - } - - /// - /// Gets the resource name. - /// - public string ResourceName { get; } - - /// - /// Gets the module, if any. - /// - public ModuleSpecification? Module { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using Microsoft.PowerShell.Commands; + + /// + /// Resource not found by Find-DscResource. + /// + internal class FindDscResourceNotFoundException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Resource name. + /// Optional module. + public FindDscResourceNotFoundException(string resourceName, ModuleSpecification? module) + : base($"Could not find resource: {resourceName} [{module?.ToString() ?? ""}]") + { + this.HResult = ErrorCodes.WinGetConfigUnitNotFoundRepository; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Gets the resource name. + /// + public string ResourceName { get; } + + /// + /// Gets the module, if any. + /// + public ModuleSpecification? Module { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceModuleConflict.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceModuleConflict.cs index dee4ba9a52..8c73e7faf4 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceModuleConflict.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceModuleConflict.cs @@ -1,43 +1,43 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.PowerShell.Commands; - - /// - /// A call to Get-DscResource failed because at least two modules with the same version where found in the module path. - /// If you are getting this verify the module path. - /// - internal class GetDscResourceModuleConflict : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Resource name. - /// Optional module. - /// The original runtime exception thrown. - public GetDscResourceModuleConflict(string? resourceName, ModuleSpecification? module, RuntimeException inner) - : base($"Multiple modules with same version in module path: {resourceName?.ToString() ?? ""} [{module?.ToString() ?? ""}]", inner) - { - this.HResult = ErrorCodes.WinGetConfigUnitModuleConflict; - this.ResourceName = resourceName; - this.Module = module; - } - - /// - /// Gets the resource name. - /// - public string? ResourceName { get; } - - /// - /// Gets the module, if any. - /// - public ModuleSpecification? Module { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.PowerShell.Commands; + + /// + /// A call to Get-DscResource failed because at least two modules with the same version where found in the module path. + /// If you are getting this verify the module path. + /// + internal class GetDscResourceModuleConflict : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Resource name. + /// Optional module. + /// The original runtime exception thrown. + public GetDscResourceModuleConflict(string? resourceName, ModuleSpecification? module, RuntimeException inner) + : base($"Multiple modules with same version in module path: {resourceName?.ToString() ?? ""} [{module?.ToString() ?? ""}]", inner) + { + this.HResult = ErrorCodes.WinGetConfigUnitModuleConflict; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Gets the resource name. + /// + public string? ResourceName { get; } + + /// + /// Gets the module, if any. + /// + public ModuleSpecification? Module { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceMultipleMatches.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceMultipleMatches.cs index 15094ac1bf..ce66cbf6df 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceMultipleMatches.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/GetDscResourceMultipleMatches.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using Microsoft.PowerShell.Commands; - - /// - /// A call to Get-DscResource return multiple results for a specific resource. - /// - internal class GetDscResourceMultipleMatches : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Resource name. - /// Optional module. - public GetDscResourceMultipleMatches(string resourceName, ModuleSpecification? module) - : base($"Multiple matches found for resource: {resourceName} [{module?.ToString() ?? ""}]") - { - this.HResult = ErrorCodes.WinGetConfigUnitMultipleMatches; - this.ResourceName = resourceName; - this.Module = module; - } - - /// - /// Gets the resource name. - /// - public string ResourceName { get; } - - /// - /// Gets the module, if any. - /// - public ModuleSpecification? Module { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using Microsoft.PowerShell.Commands; + + /// + /// A call to Get-DscResource return multiple results for a specific resource. + /// + internal class GetDscResourceMultipleMatches : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Resource name. + /// Optional module. + public GetDscResourceMultipleMatches(string resourceName, ModuleSpecification? module) + : base($"Multiple matches found for resource: {resourceName} [{module?.ToString() ?? ""}]") + { + this.HResult = ErrorCodes.WinGetConfigUnitMultipleMatches; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Gets the resource name. + /// + public string ResourceName { get; } + + /// + /// Gets the module, if any. + /// + public ModuleSpecification? Module { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/IConfigurationUnitResultException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/IConfigurationUnitResultException.cs index d2f15e5bae..c54b67cf6d 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/IConfigurationUnitResultException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/IConfigurationUnitResultException.cs @@ -1,32 +1,32 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using Microsoft.PowerShell.Commands; - - /// - /// An interface that enables an exception to expose information appropriate for a unit result. - /// - internal interface IConfigurationUnitResultException - { - /// - /// Gets a value indicating the source of the result. - /// - public ConfigurationUnitResultSource ResultSource { get; } - - /// - /// Gets the description of the result. - /// - public string Description { get; } - - /// - /// Gets the details for the result. - /// - public string Details { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using Microsoft.PowerShell.Commands; + + /// + /// An interface that enables an exception to expose information appropriate for a unit result. + /// + internal interface IConfigurationUnitResultException + { + /// + /// Gets a value indicating the source of the result. + /// + public ConfigurationUnitResultSource ResultSource { get; } + + /// + /// Gets the description of the result. + /// + public string Description { get; } + + /// + /// Gets the details for the result. + /// + public string Details { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs index 22e771555d..4bb8f02378 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/ImportModuleException.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using System.Management.Automation; - - /// - /// Import-Module threw an exception. - /// - internal class ImportModuleException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Module name. - /// Inner exception. - public ImportModuleException(string? moduleName, Exception pwshEx) - : base($"Could not import module: {moduleName?.ToString() ?? ""}", pwshEx) - { - this.HResult = this.GetHResult(pwshEx); - this.ModuleName = moduleName; - } - - /// - /// Gets the module name. - /// - public string? ModuleName { get; } - - private int GetHResult(Exception pwshEx) - { - if (pwshEx.InnerException is not null) - { - var scriptEx = pwshEx.InnerException as ScriptRequiresException; - if (scriptEx is not null) - { - if (scriptEx.ErrorRecord.CategoryInfo.Category == ErrorCategory.PermissionDenied) - { - return ErrorCodes.WinGetConfigUnitImportModuleAdmin; - } - } - } - - return ErrorCodes.WinGetConfigUnitImportModule; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using System.Management.Automation; + + /// + /// Import-Module threw an exception. + /// + internal class ImportModuleException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Module name. + /// Inner exception. + public ImportModuleException(string? moduleName, Exception pwshEx) + : base($"Could not import module: {moduleName?.ToString() ?? ""}", pwshEx) + { + this.HResult = this.GetHResult(pwshEx); + this.ModuleName = moduleName; + } + + /// + /// Gets the module name. + /// + public string? ModuleName { get; } + + private int GetHResult(Exception pwshEx) + { + if (pwshEx.InnerException is not null) + { + var scriptEx = pwshEx.InnerException as ScriptRequiresException; + if (scriptEx is not null) + { + if (scriptEx.ErrorRecord.CategoryInfo.Category == ErrorCategory.PermissionDenied) + { + return ErrorCodes.WinGetConfigUnitImportModuleAdmin; + } + } + } + + return ErrorCodes.WinGetConfigUnitImportModule; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/InstallDscResourceException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/InstallDscResourceException.cs index db8fe8638a..5d5abe6f23 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/InstallDscResourceException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/InstallDscResourceException.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using Microsoft.PowerShell.Commands; - - /// - /// Installing a DSC resource failed unexpectedly. - /// - internal class InstallDscResourceException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Resource name. - /// Module. - public InstallDscResourceException(string resourceName, ModuleSpecification? module) - : base($"Unable to find resource after install: {resourceName} [{module?.ToString() ?? ""}]") - { - this.HResult = ErrorCodes.WinGetConfigUnitNotFound; - this.ResourceName = resourceName; - this.Module = module; - } - - /// - /// Gets the resource name. - /// - public string ResourceName { get; } - - /// - /// Gets the module, if any. - /// - public ModuleSpecification? Module { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using Microsoft.PowerShell.Commands; + + /// + /// Installing a DSC resource failed unexpectedly. + /// + internal class InstallDscResourceException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Resource name. + /// Module. + public InstallDscResourceException(string resourceName, ModuleSpecification? module) + : base($"Unable to find resource after install: {resourceName} [{module?.ToString() ?? ""}]") + { + this.HResult = ErrorCodes.WinGetConfigUnitNotFound; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Gets the resource name. + /// + public string ResourceName { get; } + + /// + /// Gets the module, if any. + /// + public ModuleSpecification? Module { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceException.cs index 80a7dc6195..a1fcba660a 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/InvokeDscResourceException.cs @@ -1,184 +1,184 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.Management.Configuration; - using Microsoft.PowerShell.Commands; - - /// - /// A call to Invoke-DscResource failed unexpectedly. - /// - internal class InvokeDscResourceException : Exception, IConfigurationUnitResultException - { - /// - /// The string for the Get method. - /// - public const string Get = "Get"; - - /// - /// The string for the Set method. - /// - public const string Set = "Set"; - - /// - /// The string for the Test method. - /// - public const string Test = "Test"; - - /// - /// The string for the Export method. - /// - public const string Export = "Export"; - - /// - /// Initializes a new instance of the class. - /// Use this constructor when no error is generated by the invoke and the result is not a valid value. - /// - /// Method. - /// Resource name. - /// Optional module. - public InvokeDscResourceException(string method, string resourceName, ModuleSpecification? module = null) - : base(CreateMessage(method, resourceName, module, null)) - { - // No message means that the invoke returned an invalid result. - this.HResult = ErrorCodes.WinGetConfigUnitInvokeInvalidResult; - this.Method = method; - this.ResourceName = resourceName; - this.Module = module; - } - - /// - /// Initializes a new instance of the class. - /// Use this constructor when there is a message and the result is not valid. - /// - /// Method. - /// Resource name. - /// Message. - public InvokeDscResourceException(string method, string resourceName, string message) - : base(CreateMessage(method, resourceName, null, message)) - { - this.HResult = ErrorCodes.WinGetConfigUnitInvokeInvalidResult; - this.Method = method; - this.ResourceName = resourceName; - } - - /// - /// Initializes a new instance of the class. - /// Use this constructor when the invoke fails with an error message. - /// - /// Method. - /// Resource name. - /// Optional module. - /// Message. - /// If true, the source of this error is set to be the configuration. - public InvokeDscResourceException(string method, string resourceName, ModuleSpecification? module, string message, bool configurationSetSource = false) - : base(CreateMessage(method, resourceName, module, message)) - { - this.HResult = GetHRForMethod(method); - this.Method = method; - this.ResourceName = resourceName; - this.Module = module; - this.Description = message; - - if (configurationSetSource) - { - this.ResultSource = ConfigurationUnitResultSource.ConfigurationSet; - } - } - - /// - /// Initializes a new instance of the class. - /// Use this constructor when the invoke fails with an exception. - /// - /// Method. - /// Resource name. - /// Optional module. - /// The invoke exception. - public InvokeDscResourceException(string method, string resourceName, ModuleSpecification? module, Exception inner) - : base(CreateMessage(method, resourceName, module, inner.Message), inner) - { - this.HResult = GetHRForMethod(method); - this.Method = method; - this.ResourceName = resourceName; - this.Module = module; - this.Description = (inner as RuntimeException)?.ErrorRecord.ToString() ?? inner.Message; - } - - /// - /// Gets the invoke method. - /// - public string Method { get; } - - /// - /// Gets the resource name. - /// - public string ResourceName { get; } - - /// - /// Gets the module, if any. - /// - public ModuleSpecification? Module { get; } - - /// - /// Gets a value indicating the source of the result. - /// - public ConfigurationUnitResultSource ResultSource { get; } = ConfigurationUnitResultSource.UnitProcessing; - - /// - /// Gets the description of the result. - /// - public string Description { get; } = string.Empty; - - /// - /// Gets the details for the result. - /// - public string Details - { - get - { - RuntimeException? re = this.InnerException as RuntimeException; - if (re != null) - { - return re.ErrorRecord.ScriptStackTrace; - } - - return this.ToString(); - } - } - - /// - /// Gets the HRESULT value for the given method. - /// - /// The method. - /// The HRESULT for the method. - private static int GetHRForMethod(string method) - { - switch (method) - { - case Get: return ErrorCodes.WinGetConfigUnitInvokeGet; - case Set: return ErrorCodes.WinGetConfigUnitInvokeSet; - case Test: return ErrorCodes.WinGetConfigUnitInvokeTest; - case Export: return ErrorCodes.WinGetConfigUnitInvokeGet; - } - - return ErrorCodes.Unexpected; - } - - private static string CreateMessage(string method, string resourceName, ModuleSpecification? module, string? message) - { - string result = $"Failed when calling `{method}` for resource: {resourceName} [{module?.ToString() ?? ""}]"; - if (message != null) - { - result += $" Message: '{message}'"; - } - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.Management.Configuration; + using Microsoft.PowerShell.Commands; + + /// + /// A call to Invoke-DscResource failed unexpectedly. + /// + internal class InvokeDscResourceException : Exception, IConfigurationUnitResultException + { + /// + /// The string for the Get method. + /// + public const string Get = "Get"; + + /// + /// The string for the Set method. + /// + public const string Set = "Set"; + + /// + /// The string for the Test method. + /// + public const string Test = "Test"; + + /// + /// The string for the Export method. + /// + public const string Export = "Export"; + + /// + /// Initializes a new instance of the class. + /// Use this constructor when no error is generated by the invoke and the result is not a valid value. + /// + /// Method. + /// Resource name. + /// Optional module. + public InvokeDscResourceException(string method, string resourceName, ModuleSpecification? module = null) + : base(CreateMessage(method, resourceName, module, null)) + { + // No message means that the invoke returned an invalid result. + this.HResult = ErrorCodes.WinGetConfigUnitInvokeInvalidResult; + this.Method = method; + this.ResourceName = resourceName; + this.Module = module; + } + + /// + /// Initializes a new instance of the class. + /// Use this constructor when there is a message and the result is not valid. + /// + /// Method. + /// Resource name. + /// Message. + public InvokeDscResourceException(string method, string resourceName, string message) + : base(CreateMessage(method, resourceName, null, message)) + { + this.HResult = ErrorCodes.WinGetConfigUnitInvokeInvalidResult; + this.Method = method; + this.ResourceName = resourceName; + } + + /// + /// Initializes a new instance of the class. + /// Use this constructor when the invoke fails with an error message. + /// + /// Method. + /// Resource name. + /// Optional module. + /// Message. + /// If true, the source of this error is set to be the configuration. + public InvokeDscResourceException(string method, string resourceName, ModuleSpecification? module, string message, bool configurationSetSource = false) + : base(CreateMessage(method, resourceName, module, message)) + { + this.HResult = GetHRForMethod(method); + this.Method = method; + this.ResourceName = resourceName; + this.Module = module; + this.Description = message; + + if (configurationSetSource) + { + this.ResultSource = ConfigurationUnitResultSource.ConfigurationSet; + } + } + + /// + /// Initializes a new instance of the class. + /// Use this constructor when the invoke fails with an exception. + /// + /// Method. + /// Resource name. + /// Optional module. + /// The invoke exception. + public InvokeDscResourceException(string method, string resourceName, ModuleSpecification? module, Exception inner) + : base(CreateMessage(method, resourceName, module, inner.Message), inner) + { + this.HResult = GetHRForMethod(method); + this.Method = method; + this.ResourceName = resourceName; + this.Module = module; + this.Description = (inner as RuntimeException)?.ErrorRecord.ToString() ?? inner.Message; + } + + /// + /// Gets the invoke method. + /// + public string Method { get; } + + /// + /// Gets the resource name. + /// + public string ResourceName { get; } + + /// + /// Gets the module, if any. + /// + public ModuleSpecification? Module { get; } + + /// + /// Gets a value indicating the source of the result. + /// + public ConfigurationUnitResultSource ResultSource { get; } = ConfigurationUnitResultSource.UnitProcessing; + + /// + /// Gets the description of the result. + /// + public string Description { get; } = string.Empty; + + /// + /// Gets the details for the result. + /// + public string Details + { + get + { + RuntimeException? re = this.InnerException as RuntimeException; + if (re != null) + { + return re.ErrorRecord.ScriptStackTrace; + } + + return this.ToString(); + } + } + + /// + /// Gets the HRESULT value for the given method. + /// + /// The method. + /// The HRESULT for the method. + private static int GetHRForMethod(string method) + { + switch (method) + { + case Get: return ErrorCodes.WinGetConfigUnitInvokeGet; + case Set: return ErrorCodes.WinGetConfigUnitInvokeSet; + case Test: return ErrorCodes.WinGetConfigUnitInvokeTest; + case Export: return ErrorCodes.WinGetConfigUnitInvokeGet; + } + + return ErrorCodes.Unexpected; + } + + private static string CreateMessage(string method, string resourceName, ModuleSpecification? module, string? message) + { + string result = $"Failed when calling `{method}` for resource: {resourceName} [{module?.ToString() ?? ""}]"; + if (message != null) + { + result += $" Message: '{message}'"; + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitPropertyUnsupportedException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitPropertyUnsupportedException.cs index 205f3e010a..1223728c9a 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitPropertyUnsupportedException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitPropertyUnsupportedException.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - - /// - /// The property type of a unit is not supported. - /// - internal class UnitPropertyUnsupportedException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Name. - /// Type. - /// Inner exception. - public UnitPropertyUnsupportedException(string name, Type type, Exception inner) - : base($"Property {name} of type {type.FullName} is not supported.", inner) - { - this.HResult = ErrorCodes.WinGetConfigUnitUnsupportedType; - this.Name = name; - this.Type = type; - } - - /// - /// Initializes a new instance of the class. - /// - /// Type. - public UnitPropertyUnsupportedException(Type type) - : base($"Type {type.FullName} is not supported.") - { - this.HResult = ErrorCodes.WinGetConfigUnitUnsupportedType; - this.Type = type; - } - - /// - /// Gets the name. - /// - public string? Name { get; } - - /// - /// Gets the type. - /// - public Type Type { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + + /// + /// The property type of a unit is not supported. + /// + internal class UnitPropertyUnsupportedException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Name. + /// Type. + /// Inner exception. + public UnitPropertyUnsupportedException(string name, Type type, Exception inner) + : base($"Property {name} of type {type.FullName} is not supported.", inner) + { + this.HResult = ErrorCodes.WinGetConfigUnitUnsupportedType; + this.Name = name; + this.Type = type; + } + + /// + /// Initializes a new instance of the class. + /// + /// Type. + public UnitPropertyUnsupportedException(Type type) + : base($"Type {type.FullName} is not supported.") + { + this.HResult = ErrorCodes.WinGetConfigUnitUnsupportedType; + this.Type = type; + } + + /// + /// Gets the name. + /// + public string? Name { get; } + + /// + /// Gets the type. + /// + public Type Type { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitSettingConfigRootException.cs b/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitSettingConfigRootException.cs index 54aa4c051f..aaab74f274 100644 --- a/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitSettingConfigRootException.cs +++ b/src/Microsoft.Management.Configuration.Processor/Exceptions/UnitSettingConfigRootException.cs @@ -1,39 +1,39 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Exceptions -{ - using System; - - /// - /// A setting uses the config root variable and the Path was not set in the ConfigurationSet. - /// - internal class UnitSettingConfigRootException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Unit name. - /// Setting. - public UnitSettingConfigRootException(string unitName, string setting) - : base($"Unit: {unitName} Setting {setting} requires the ConfigurationSet Path") - { - this.HResult = ErrorCodes.WinGetConfigUnitSettingConfigRoot; - this.UnitName = unitName; - this.Setting = setting; - } - - /// - /// Gets the resource name. - /// - public string UnitName { get; } - - /// - /// Gets the setting that reference the config root variable. - /// - public string Setting { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Exceptions +{ + using System; + + /// + /// A setting uses the config root variable and the Path was not set in the ConfigurationSet. + /// + internal class UnitSettingConfigRootException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Unit name. + /// Setting. + public UnitSettingConfigRootException(string unitName, string setting) + : base($"Unit: {unitName} Setting {setting} requires the ConfigurationSet Path") + { + this.HResult = ErrorCodes.WinGetConfigUnitSettingConfigRoot; + this.UnitName = unitName; + this.Setting = setting; + } + + /// + /// Gets the resource name. + /// + public string UnitName { get; } + + /// + /// Gets the setting that reference the config root variable. + /// + public string Setting { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/DictionaryExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/DictionaryExtensions.cs index b6a1f20bc9..ac8ecf0637 100644 --- a/src/Microsoft.Management.Configuration.Processor/Extensions/DictionaryExtensions.cs +++ b/src/Microsoft.Management.Configuration.Processor/Extensions/DictionaryExtensions.cs @@ -1,62 +1,62 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Extensions -{ - using System; - using System.Collections; - using System.Collections.Generic; - using Windows.Foundation.Collections; - - /// - /// Extensions for dictionaries. - /// - internal static class DictionaryExtensions - { - /// - /// Performs a deep compare of the dictionaries. - /// - /// First dictionary. - /// Second dictionary. - /// Whether the two dictionaries equal. - internal static bool ContentEquals(this IDictionary first, IDictionary second) - { - if (first.Count != second.Count) - { - return false; - } - - foreach (var keyValuePair in first) - { - string key = keyValuePair.Key; - if (!second.ContainsKey(key)) - { - return false; - } - - var firstValue = keyValuePair.Value; - var secondValue = second[key]; - - // Empty value check. - if (firstValue == null && secondValue == null) - { - continue; - } - else if (firstValue == null || secondValue == null) - { - return false; - } - - if (firstValue != secondValue) - { - return false; - } - } - - return true; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Extensions +{ + using System; + using System.Collections; + using System.Collections.Generic; + using Windows.Foundation.Collections; + + /// + /// Extensions for dictionaries. + /// + internal static class DictionaryExtensions + { + /// + /// Performs a deep compare of the dictionaries. + /// + /// First dictionary. + /// Second dictionary. + /// Whether the two dictionaries equal. + internal static bool ContentEquals(this IDictionary first, IDictionary second) + { + if (first.Count != second.Count) + { + return false; + } + + foreach (var keyValuePair in first) + { + string key = keyValuePair.Key; + if (!second.ContainsKey(key)) + { + return false; + } + + var firstValue = keyValuePair.Value; + var secondValue = second[key]; + + // Empty value check. + if (firstValue == null && secondValue == null) + { + continue; + } + else if (firstValue == null || secondValue == null) + { + return false; + } + + if (firstValue != secondValue) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/ExceptionExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/ExceptionExtensions.cs index 5301904a67..f6a36104cb 100644 --- a/src/Microsoft.Management.Configuration.Processor/Extensions/ExceptionExtensions.cs +++ b/src/Microsoft.Management.Configuration.Processor/Extensions/ExceptionExtensions.cs @@ -1,32 +1,32 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Extensions -{ - using System; - - /// - /// Extension method for Exception. - /// - internal static class ExceptionExtensions - { - /// - /// Gets the most inner exception. - /// - /// Exception. - /// Most inner exception. - public static Exception GetMostInnerException(this Exception e) - { - Exception ex = e; - while (ex.InnerException != null) - { - ex = ex.InnerException; - } - - return ex; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Extensions +{ + using System; + + /// + /// Extension method for Exception. + /// + internal static class ExceptionExtensions + { + /// + /// Gets the most inner exception. + /// + /// Exception. + /// Most inner exception. + public static Exception GetMostInnerException(this Exception e) + { + Exception ex = e; + while (ex.InnerException != null) + { + ex = ex.InnerException; + } + + return ex; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/HashtableExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/HashtableExtensions.cs index b11b4e2005..2b4a3a86a3 100644 --- a/src/Microsoft.Management.Configuration.Processor/Extensions/HashtableExtensions.cs +++ b/src/Microsoft.Management.Configuration.Processor/Extensions/HashtableExtensions.cs @@ -1,54 +1,54 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Extensions -{ - using System.Collections; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Helpers; - using Windows.Foundation.Collections; - - /// - /// Extensions for Hashtable. - /// - internal static class HashtableExtensions - { - /// - /// Convert a hashtable to a value set. - /// - /// hashtable. - /// Value set. - public static ValueSet ToValueSet(this Hashtable hashtable) - { - var valueSet = new ValueSet(); - - foreach (DictionaryEntry entry in hashtable) - { - if (entry.Key is string key) - { - if (entry.Value is null) - { - valueSet.Add(key, null); - } - else - { - var value = TypeHelpers.GetCompatibleValueSetValueOfProperty(entry.Value.GetType(), entry.Value); - if (value != null) - { - valueSet.Add(key, value); - } - } - } - else - { - throw new UnitPropertyUnsupportedException(entry.Key.GetType()); - } - } - - return valueSet; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Extensions +{ + using System.Collections; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Helpers; + using Windows.Foundation.Collections; + + /// + /// Extensions for Hashtable. + /// + internal static class HashtableExtensions + { + /// + /// Convert a hashtable to a value set. + /// + /// hashtable. + /// Value set. + public static ValueSet ToValueSet(this Hashtable hashtable) + { + var valueSet = new ValueSet(); + + foreach (DictionaryEntry entry in hashtable) + { + if (entry.Key is string key) + { + if (entry.Value is null) + { + valueSet.Add(key, null); + } + else + { + var value = TypeHelpers.GetCompatibleValueSetValueOfProperty(entry.Value.GetType(), entry.Value); + if (value != null) + { + valueSet.Add(key, value); + } + } + } + else + { + throw new UnitPropertyUnsupportedException(entry.Key.GetType()); + } + } + + return valueSet; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/JsonObjectExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/JsonObjectExtensions.cs index 11868bfa0d..826f5fdeb7 100644 --- a/src/Microsoft.Management.Configuration.Processor/Extensions/JsonObjectExtensions.cs +++ b/src/Microsoft.Management.Configuration.Processor/Extensions/JsonObjectExtensions.cs @@ -1,72 +1,72 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Extensions -{ - using System.Text.Json; - using System.Text.Json.Nodes; - using Windows.Foundation.Collections; - - /// - /// Extensions for JsonObject. - /// - internal static class JsonObjectExtensions - { - /// - /// Converts the JSON object to a ValueSet. - /// - /// The object to convert. - /// The ValueSet. - public static ValueSet ToValueSet(this JsonObject? jsonObject) - { - ValueSet result = new ValueSet(); - - if (jsonObject != null) - { - foreach (var item in jsonObject) - { - result.Add(item.Key, ToValue(item.Value)); - } - } - - return result; - } - - private static object? ToValue(JsonNode? node) => node switch - { - JsonObject obj => obj.ToValueSet(), - JsonArray array => ToValueSet(array), - JsonValue value => ToValue(value), - _ => null, - }; - - private static ValueSet ToValueSet(JsonArray array) - { - ValueSet result = new ValueSet(); - result.Add(ValueSetExtensions.TreatAsArray, true); - - int index = 0; - foreach (var item in array) - { - result.Add(index.ToString(), ToValue(item)); - ++index; - } - - return result; - } - - private static object? ToValue(JsonValue value) => value.GetValueKind() switch - { - JsonValueKind.Null => null, - JsonValueKind.Undefined => null, - JsonValueKind.String => value.GetValue(), - JsonValueKind.Number => value.GetValue(), - JsonValueKind.True => true, - JsonValueKind.False => false, - _ => throw new System.NotImplementedException("Unexpected default case") - }; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Extensions +{ + using System.Text.Json; + using System.Text.Json.Nodes; + using Windows.Foundation.Collections; + + /// + /// Extensions for JsonObject. + /// + internal static class JsonObjectExtensions + { + /// + /// Converts the JSON object to a ValueSet. + /// + /// The object to convert. + /// The ValueSet. + public static ValueSet ToValueSet(this JsonObject? jsonObject) + { + ValueSet result = new ValueSet(); + + if (jsonObject != null) + { + foreach (var item in jsonObject) + { + result.Add(item.Key, ToValue(item.Value)); + } + } + + return result; + } + + private static object? ToValue(JsonNode? node) => node switch + { + JsonObject obj => obj.ToValueSet(), + JsonArray array => ToValueSet(array), + JsonValue value => ToValue(value), + _ => null, + }; + + private static ValueSet ToValueSet(JsonArray array) + { + ValueSet result = new ValueSet(); + result.Add(ValueSetExtensions.TreatAsArray, true); + + int index = 0; + foreach (var item in array) + { + result.Add(index.ToString(), ToValue(item)); + ++index; + } + + return result; + } + + private static object? ToValue(JsonValue value) => value.GetValueKind() switch + { + JsonValueKind.Null => null, + JsonValueKind.Undefined => null, + JsonValueKind.String => value.GetValue(), + JsonValueKind.Number => value.GetValue(), + JsonValueKind.True => true, + JsonValueKind.False => false, + _ => throw new System.NotImplementedException("Unexpected default case") + }; + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs b/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs index 57795c8b3c..6fcefd4c49 100644 --- a/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs +++ b/src/Microsoft.Management.Configuration.Processor/Extensions/ValueSetExtensions.cs @@ -1,182 +1,182 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Extensions -{ - using System; - using System.Collections; - using System.Collections.Generic; - using Windows.Foundation.Collections; - - /// - /// Extensions for ValueSet. - /// - internal static class ValueSetExtensions - { - /// - /// The value in a ValueSet that indicates that it is an array of items. - /// - internal const string TreatAsArray = "treatAsArray"; - - /// - /// Extension method to transform a ValueSet to a Hashtable. - /// - /// Value set. - /// A hashtable. - public static Hashtable ToHashtable(this ValueSet valueSet) - { - var hashtable = new Hashtable(); - - foreach (var keyValuePair in valueSet) - { - if (keyValuePair.Value is ValueSet innerValueSet) - { - if (innerValueSet.ContainsKey(TreatAsArray)) - { - hashtable.Add(keyValuePair.Key, innerValueSet.ToArray()); - } - else - { - hashtable.Add(keyValuePair.Key, innerValueSet.ToHashtable()); - } - } - else - { - hashtable.Add(keyValuePair.Key, keyValuePair.Value); - } - } - - return hashtable; - } - - /// - /// Gets ordered list from a ValueSet that is threated as an array. - /// - /// ValueSet. - /// Ordered list. - public static IList ToArray(this ValueSet valueSet) - { - if (!valueSet.ContainsKey(TreatAsArray)) - { - throw new InvalidOperationException(); - } - - var sortedList = new SortedList(); - - foreach (var keyValuePair in valueSet) - { - if (keyValuePair.Key == TreatAsArray) - { - continue; - } - - if (int.TryParse(keyValuePair.Key, out int key)) - { - if (keyValuePair.Value is ValueSet innerValueSet) - { - sortedList.Add(key, innerValueSet.ToHashtable()); - } - else - { - sortedList.Add(key, keyValuePair.Value); - } - } - else - { - throw new InvalidOperationException($"Invalid key for ValueSet to array {keyValuePair.Key}"); - } - } - - return sortedList.Values; - } - - /// - /// Performs a deep compare of the ValueSets. - /// - /// First ValueSet. - /// Second ValueSet. - /// Whether the two ValueSets equal. - public static bool ContentEquals(this ValueSet first, ValueSet second) - { - if (first.Count != second.Count) - { - return false; - } - - foreach (var keyValuePair in first) - { - string key = keyValuePair.Key; - if (!second.ContainsKey(key)) - { - return false; - } - - var firstValue = keyValuePair.Value; - var secondValue = second[key]; - - // Empty value check. - if (firstValue == null && secondValue == null) - { - continue; - } - else if (firstValue == null || secondValue == null) - { - return false; - } - - // Try as ValueSet. - var firstValueSet = firstValue as ValueSet; - var secondValueSet = secondValue as ValueSet; - - if (firstValueSet != null && secondValueSet != null) - { - if (!firstValueSet.ContentEquals(secondValueSet)) - { - return false; - } - else - { - continue; - } - } - else if (firstValueSet != null || secondValueSet != null) - { - return false; - } - - // Try as scalar. - if (firstValue is string firstString && secondValue is string secondString) - { - if (firstString != secondString) - { - return false; - } - } - else if (firstValue is long firstLong && secondValue is long secondLong) - { - if (firstLong != secondLong) - { - return false; - } - } - else if (firstValue is bool firstBool && secondValue is bool secondBool) - { - if (firstBool != secondBool) - { - return false; - } - } - else - { - // Note: DateTime and float are not supported in parser yet. - return false; - } - } - - return true; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Extensions +{ + using System; + using System.Collections; + using System.Collections.Generic; + using Windows.Foundation.Collections; + + /// + /// Extensions for ValueSet. + /// + internal static class ValueSetExtensions + { + /// + /// The value in a ValueSet that indicates that it is an array of items. + /// + internal const string TreatAsArray = "treatAsArray"; + + /// + /// Extension method to transform a ValueSet to a Hashtable. + /// + /// Value set. + /// A hashtable. + public static Hashtable ToHashtable(this ValueSet valueSet) + { + var hashtable = new Hashtable(); + + foreach (var keyValuePair in valueSet) + { + if (keyValuePair.Value is ValueSet innerValueSet) + { + if (innerValueSet.ContainsKey(TreatAsArray)) + { + hashtable.Add(keyValuePair.Key, innerValueSet.ToArray()); + } + else + { + hashtable.Add(keyValuePair.Key, innerValueSet.ToHashtable()); + } + } + else + { + hashtable.Add(keyValuePair.Key, keyValuePair.Value); + } + } + + return hashtable; + } + + /// + /// Gets ordered list from a ValueSet that is threated as an array. + /// + /// ValueSet. + /// Ordered list. + public static IList ToArray(this ValueSet valueSet) + { + if (!valueSet.ContainsKey(TreatAsArray)) + { + throw new InvalidOperationException(); + } + + var sortedList = new SortedList(); + + foreach (var keyValuePair in valueSet) + { + if (keyValuePair.Key == TreatAsArray) + { + continue; + } + + if (int.TryParse(keyValuePair.Key, out int key)) + { + if (keyValuePair.Value is ValueSet innerValueSet) + { + sortedList.Add(key, innerValueSet.ToHashtable()); + } + else + { + sortedList.Add(key, keyValuePair.Value); + } + } + else + { + throw new InvalidOperationException($"Invalid key for ValueSet to array {keyValuePair.Key}"); + } + } + + return sortedList.Values; + } + + /// + /// Performs a deep compare of the ValueSets. + /// + /// First ValueSet. + /// Second ValueSet. + /// Whether the two ValueSets equal. + public static bool ContentEquals(this ValueSet first, ValueSet second) + { + if (first.Count != second.Count) + { + return false; + } + + foreach (var keyValuePair in first) + { + string key = keyValuePair.Key; + if (!second.ContainsKey(key)) + { + return false; + } + + var firstValue = keyValuePair.Value; + var secondValue = second[key]; + + // Empty value check. + if (firstValue == null && secondValue == null) + { + continue; + } + else if (firstValue == null || secondValue == null) + { + return false; + } + + // Try as ValueSet. + var firstValueSet = firstValue as ValueSet; + var secondValueSet = secondValue as ValueSet; + + if (firstValueSet != null && secondValueSet != null) + { + if (!firstValueSet.ContentEquals(secondValueSet)) + { + return false; + } + else + { + continue; + } + } + else if (firstValueSet != null || secondValueSet != null) + { + return false; + } + + // Try as scalar. + if (firstValue is string firstString && secondValue is string secondString) + { + if (firstString != secondString) + { + return false; + } + } + else if (firstValue is long firstLong && secondValue is long secondLong) + { + if (firstLong != secondLong) + { + return false; + } + } + else if (firstValue is bool firstBool && secondValue is bool secondBool) + { + if (firstBool != secondBool) + { + return false; + } + } + else + { + // Note: DateTime and float are not supported in parser yet. + return false; + } + } + + return true; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Factory/ConfigurationSetProcessorFactoryBase.cs b/src/Microsoft.Management.Configuration.Processor/Factory/ConfigurationSetProcessorFactoryBase.cs index 91dacaeeb2..dbe098f888 100644 --- a/src/Microsoft.Management.Configuration.Processor/Factory/ConfigurationSetProcessorFactoryBase.cs +++ b/src/Microsoft.Management.Configuration.Processor/Factory/ConfigurationSetProcessorFactoryBase.cs @@ -1,182 +1,182 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Factory -{ - using System; - using System.Runtime.CompilerServices; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.Processor.Set; - - /// - /// IConfigurationSetProcessorFactory base implementation. - /// - internal abstract partial class ConfigurationSetProcessorFactoryBase : IDiagnosticsSink - { - private bool isCreateProcessorInvoked = false; - - // Backing variables for properties that are restricted in limit mode. - private ConfigurationSet? limitationSet; - - /// - /// Initializes a new instance of the class. - /// - public ConfigurationSetProcessorFactoryBase() - { - } - - /// - /// Diagnostics event; useful for logging and/or verbose output. - /// - public event EventHandler? Diagnostics; - - /// - /// Gets or sets the minimum diagnostic level to send. - /// - public DiagnosticLevel MinimumLevel { get; set; } = DiagnosticLevel.Informational; - - /// - /// Gets or sets the limitation set. Limitation set can only be set once. - /// - public ConfigurationSet? LimitationSet - { - get - { - return this.limitationSet; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting LimitationSet in limit mode is invalid."); - } - - this.limitationSet = value; - } - } - - /// - /// Gets the configuration unit processor details for the given unit. - /// - /// Configuration Set. - /// Configuration set processor. - public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet? incomingSet) - { - try - { - bool isLimitMode = this.IsLimitMode(); - this.OnDiagnostics(DiagnosticLevel.Informational, $"The set processor factory is running in limit mode: {isLimitMode}."); - - this.CheckLimitMode(); - - ConfigurationSet? set = isLimitMode ? this.limitationSet : incomingSet; - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating set processor for `{set?.Name ?? ""}`..."); - - if (set != null && (set.Parameters.Count > 0 || set.Variables.Count > 0)) - { - this.OnDiagnostics(DiagnosticLevel.Error, $" Parameters/variables are not yet supported."); - throw new NotImplementedException(); - } - - IConfigurationSetProcessor result = this.CreateSetProcessorInternal(set, isLimitMode); - - this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating set processor."); - - return result; - } - catch (Exception ex) - { - this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); - throw; - } - } - - /// - void IDiagnosticsSink.OnDiagnostics(DiagnosticLevel level, string message) - { - this.OnDiagnostics(level, message); - } - - /// - /// Sends diagnostics if appropriate. - /// - /// The level of this diagnostic message. - /// The diagnostic message. - internal void OnDiagnostics(DiagnosticLevel level, string message) - { - EventHandler? diagnostics = this.Diagnostics; - if (diagnostics != null && level >= this.MinimumLevel) - { - this.InvokeDiagnostics(diagnostics, level, message); - } - } - - /// - /// Determines if diagnostics are enabled. - /// This allows optimizing out string construction for some cases. - /// - /// True if diagnostics are enabled; false if not. - protected bool AreDiagnosticsEnabled() - { - return this.Diagnostics != null; - } - - /// - /// Gets the configuration unit processor details for the given unit. - /// - /// Configuration Set. - /// Whether the processor should be in limit mode. - /// Configuration set processor. - protected abstract IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode); - - /// - /// Gets a value indicating whether the factory is operation in limit mode. - /// - /// True if the factory is in limit mode, false otherwise. - protected bool IsLimitMode() - { - return this.limitationSet != null; - } - - /// - /// Sends diagnostics to the given handler. - /// - /// The handler to invoke. - /// The level of this diagnostic message. - /// The diagnostic message. - private void InvokeDiagnostics(EventHandler diagnostics, DiagnosticLevel level, string message) - { - Helpers.DiagnosticInformation information = new () - { - Level = level, - Message = message, - }; - diagnostics.Invoke(this, information); - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private void CheckLimitMode() - { - if (!this.IsLimitMode()) - { - return; - } - - if (this.isCreateProcessorInvoked) - { - this.OnDiagnostics(DiagnosticLevel.Error, "CreateSetProcessor is already invoked in limit mode."); - throw new InvalidOperationException("CreateSetProcessor is already invoked in limit mode."); - } - else - { - this.isCreateProcessorInvoked = true; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Factory +{ + using System; + using System.Runtime.CompilerServices; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.Set; + + /// + /// IConfigurationSetProcessorFactory base implementation. + /// + internal abstract partial class ConfigurationSetProcessorFactoryBase : IDiagnosticsSink + { + private bool isCreateProcessorInvoked = false; + + // Backing variables for properties that are restricted in limit mode. + private ConfigurationSet? limitationSet; + + /// + /// Initializes a new instance of the class. + /// + public ConfigurationSetProcessorFactoryBase() + { + } + + /// + /// Diagnostics event; useful for logging and/or verbose output. + /// + public event EventHandler? Diagnostics; + + /// + /// Gets or sets the minimum diagnostic level to send. + /// + public DiagnosticLevel MinimumLevel { get; set; } = DiagnosticLevel.Informational; + + /// + /// Gets or sets the limitation set. Limitation set can only be set once. + /// + public ConfigurationSet? LimitationSet + { + get + { + return this.limitationSet; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting LimitationSet in limit mode is invalid."); + } + + this.limitationSet = value; + } + } + + /// + /// Gets the configuration unit processor details for the given unit. + /// + /// Configuration Set. + /// Configuration set processor. + public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet? incomingSet) + { + try + { + bool isLimitMode = this.IsLimitMode(); + this.OnDiagnostics(DiagnosticLevel.Informational, $"The set processor factory is running in limit mode: {isLimitMode}."); + + this.CheckLimitMode(); + + ConfigurationSet? set = isLimitMode ? this.limitationSet : incomingSet; + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating set processor for `{set?.Name ?? ""}`..."); + + if (set != null && (set.Parameters.Count > 0 || set.Variables.Count > 0)) + { + this.OnDiagnostics(DiagnosticLevel.Error, $" Parameters/variables are not yet supported."); + throw new NotImplementedException(); + } + + IConfigurationSetProcessor result = this.CreateSetProcessorInternal(set, isLimitMode); + + this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating set processor."); + + return result; + } + catch (Exception ex) + { + this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); + throw; + } + } + + /// + void IDiagnosticsSink.OnDiagnostics(DiagnosticLevel level, string message) + { + this.OnDiagnostics(level, message); + } + + /// + /// Sends diagnostics if appropriate. + /// + /// The level of this diagnostic message. + /// The diagnostic message. + internal void OnDiagnostics(DiagnosticLevel level, string message) + { + EventHandler? diagnostics = this.Diagnostics; + if (diagnostics != null && level >= this.MinimumLevel) + { + this.InvokeDiagnostics(diagnostics, level, message); + } + } + + /// + /// Determines if diagnostics are enabled. + /// This allows optimizing out string construction for some cases. + /// + /// True if diagnostics are enabled; false if not. + protected bool AreDiagnosticsEnabled() + { + return this.Diagnostics != null; + } + + /// + /// Gets the configuration unit processor details for the given unit. + /// + /// Configuration Set. + /// Whether the processor should be in limit mode. + /// Configuration set processor. + protected abstract IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode); + + /// + /// Gets a value indicating whether the factory is operation in limit mode. + /// + /// True if the factory is in limit mode, false otherwise. + protected bool IsLimitMode() + { + return this.limitationSet != null; + } + + /// + /// Sends diagnostics to the given handler. + /// + /// The handler to invoke. + /// The level of this diagnostic message. + /// The diagnostic message. + private void InvokeDiagnostics(EventHandler diagnostics, DiagnosticLevel level, string message) + { + Helpers.DiagnosticInformation information = new () + { + Level = level, + Message = message, + }; + diagnostics.Invoke(this, information); + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private void CheckLimitMode() + { + if (!this.IsLimitMode()) + { + return; + } + + if (this.isCreateProcessorInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "CreateSetProcessor is already invoked in limit mode."); + throw new InvalidOperationException("CreateSetProcessor is already invoked in limit mode."); + } + else + { + this.isCreateProcessorInvoked = true; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs index a1a7221771..5845cff200 100644 --- a/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs +++ b/src/Microsoft.Management.Configuration.Processor/Helpers/ConfigurationUnitInternal.cs @@ -1,263 +1,263 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using Microsoft.Management.Configuration.Processor.Constants; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Windows.Foundation.Collections; - - /// - /// Wrapper around Configuration units and its directives. - /// Creates a normalized directives map for consumption. - /// - internal class ConfigurationUnitInternal - { - private const string ConfigRootVar = "${WinGetConfigRoot}"; - - private readonly string? configurationFileRootPath = null; - private readonly Dictionary normalizedDirectives = new (); - - /// - /// Initializes a new instance of the class. - /// - /// Configuration unit. - /// The configuration file path. - public ConfigurationUnitInternal( - ConfigurationUnit unit, - string? configurationFilePath) - { - this.Unit = unit; - this.InitializeDirectives(); - this.InitializeNames(); - - if (!string.IsNullOrEmpty(configurationFilePath)) - { - if (!File.Exists(configurationFilePath)) - { - throw new FileNotFoundException(configurationFilePath); - } - - this.configurationFileRootPath = Path.GetDirectoryName(configurationFilePath); - } - } - - /// - /// Gets the configuration unit. - /// - public ConfigurationUnit Unit { get; } - - /// - /// Gets a value indicating whether the unit type should be treated as the resource name. - /// - public bool UnitTypeIsResourceName { get; init; } = false; - - /// - /// Gets the resource name *only*. For example, "Resource". - /// - public string ResourceName { get; private set; } - - /// - /// Gets the qualified name, which includes the module. For example, "Module/Resource". - /// - public string QualifiedName { get; private set; } - - /// - /// Gets the directive value from the unit taking into account the directives overlay. - /// - /// Directive name. - /// Value of directive, null if not found. - /// Directive type value. - public TType? GetDirective(string directiveName) - where TType : class - { - var normalizedDirectiveName = StringHelpers.Normalize(directiveName); - if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) - { - return value as TType; - } - - return null; - } - - /// - /// Gets the bool value of a directive from the unit taking into account the directives overlay. - /// - /// Directive name. - /// Value of directive, false if not found. - public bool? GetDirective(string directiveName) - { - var normalizedDirectiveName = StringHelpers.Normalize(directiveName); - if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) - { - return value as bool?; - } - - return null; - } - - /// - /// Gets the semantic version, if any. - /// - /// SemanticVersion, null if not specified. - public SemanticVersion? GetSemanticVersion() - { - string? semanticVersion = this.GetDirective(DirectiveConstants.Version); - if (!string.IsNullOrWhiteSpace(semanticVersion)) - { - return new SemanticVersion(semanticVersion); - } - - return null; - } - - /// - /// Gets the semantic min version, if any. - /// - /// SemanticVersion, null if not specified. - public SemanticVersion? GetSemanticMinVersion() - { - string? semanticVersion = this.GetDirective(DirectiveConstants.MinVersion); - if (!string.IsNullOrWhiteSpace(semanticVersion)) - { - return new SemanticVersion(semanticVersion); - } - - return null; - } - - /// - /// Gets the semantic max version, if any. - /// - /// SemanticVersion, null if not specified. - public SemanticVersion? GetSemanticMaxVersion() - { - string? semanticVersion = this.GetDirective(DirectiveConstants.MaxVersion); - if (!string.IsNullOrWhiteSpace(semanticVersion)) - { - return new SemanticVersion(semanticVersion); - } - - return null; - } - - /// - /// TODO: Implement for more variables. - /// I am so sad because rs.SessionStateProxy.InvokeCommand.ExpandString doesn't work as I wanted. - /// PowerShell assumes all code passed to ExpandString is trusted and we cannot assume that. - /// - /// ValueSet with settings. - public ValueSet GetExpandedSettings() - { - var valueSet = new ValueSet(); - foreach (var value in this.Unit.Settings) - { - if (value.Value is string) - { - // For now, we just expand config root. - valueSet.Add(value.Key, this.ExpandConfigRoot(value.Value as string, value.Key)); - } - else - { - valueSet.Add(value); - } - } - - return valueSet; - } - - private string? ExpandConfigRoot(string? value, string settingName) - { - if (!string.IsNullOrEmpty(value)) - { - // TODO: since we only support one variable, this only finds and replace - // ${WingetConfigRoot} if found in the string when the work of expanding - // string is done it should take into account other operators like the subexpression operator $() - if (value.Contains(ConfigRootVar, StringComparison.OrdinalIgnoreCase)) - { - if (string.IsNullOrEmpty(this.configurationFileRootPath)) - { - throw new UnitSettingConfigRootException(this.QualifiedName, settingName); - } - - return value.Replace(ConfigRootVar, this.configurationFileRootPath, StringComparison.OrdinalIgnoreCase); - } - } - - return value; - } - - private void InitializeDirectives() - { - foreach (var directive in this.Unit.Metadata) - { - var normalizedKey = StringHelpers.Normalize(directive.Key); - this.normalizedDirectives.Add(normalizedKey, directive.Value); - } - } - - private string ConstructQualifiedName(string? moduleName) - { - return $"{(moduleName == null ? string.Empty : $"{moduleName}/")}{this.ResourceName}"; - } - - [MemberNotNull(nameof(ResourceName), nameof(QualifiedName))] - private void InitializeNames() - { - // Determine ResourceName, QualifiedName, and the module directive - string unitType = this.Unit.Type; - string? moduleDirective = this.GetDirective(DirectiveConstants.Module); - - if (this.UnitTypeIsResourceName) - { - this.ResourceName = unitType; - this.QualifiedName = this.ConstructQualifiedName(moduleDirective); - return; - } - - int unitTypeDividerPosition = unitType.IndexOf('/'); - - if (unitTypeDividerPosition == unitType.Length - 1) - { - throw new ArgumentException($"Invalid unit Type: {unitType}"); - } - - string? moduleName; - - if (unitTypeDividerPosition == -1) - { - moduleName = moduleDirective; - this.ResourceName = unitType; - this.QualifiedName = this.ConstructQualifiedName(moduleDirective); - } - else - { - moduleName = unitType.Substring(0, unitTypeDividerPosition); - this.ResourceName = unitType.Substring(unitTypeDividerPosition + 1); - this.QualifiedName = unitType; - } - - if (moduleName != null) - { - if (moduleDirective != null) - { - if (moduleName != moduleDirective) - { - throw new ArgumentException($"Mismatched module specifiers: {moduleName} != {moduleDirective}"); - } - } - else - { - this.normalizedDirectives.Add(DirectiveConstants.Module, moduleName); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Helpers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using Microsoft.Management.Configuration.Processor.Constants; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Windows.Foundation.Collections; + + /// + /// Wrapper around Configuration units and its directives. + /// Creates a normalized directives map for consumption. + /// + internal class ConfigurationUnitInternal + { + private const string ConfigRootVar = "${WinGetConfigRoot}"; + + private readonly string? configurationFileRootPath = null; + private readonly Dictionary normalizedDirectives = new (); + + /// + /// Initializes a new instance of the class. + /// + /// Configuration unit. + /// The configuration file path. + public ConfigurationUnitInternal( + ConfigurationUnit unit, + string? configurationFilePath) + { + this.Unit = unit; + this.InitializeDirectives(); + this.InitializeNames(); + + if (!string.IsNullOrEmpty(configurationFilePath)) + { + if (!File.Exists(configurationFilePath)) + { + throw new FileNotFoundException(configurationFilePath); + } + + this.configurationFileRootPath = Path.GetDirectoryName(configurationFilePath); + } + } + + /// + /// Gets the configuration unit. + /// + public ConfigurationUnit Unit { get; } + + /// + /// Gets a value indicating whether the unit type should be treated as the resource name. + /// + public bool UnitTypeIsResourceName { get; init; } = false; + + /// + /// Gets the resource name *only*. For example, "Resource". + /// + public string ResourceName { get; private set; } + + /// + /// Gets the qualified name, which includes the module. For example, "Module/Resource". + /// + public string QualifiedName { get; private set; } + + /// + /// Gets the directive value from the unit taking into account the directives overlay. + /// + /// Directive name. + /// Value of directive, null if not found. + /// Directive type value. + public TType? GetDirective(string directiveName) + where TType : class + { + var normalizedDirectiveName = StringHelpers.Normalize(directiveName); + if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) + { + return value as TType; + } + + return null; + } + + /// + /// Gets the bool value of a directive from the unit taking into account the directives overlay. + /// + /// Directive name. + /// Value of directive, false if not found. + public bool? GetDirective(string directiveName) + { + var normalizedDirectiveName = StringHelpers.Normalize(directiveName); + if (this.normalizedDirectives.TryGetValue(normalizedDirectiveName, out object? value)) + { + return value as bool?; + } + + return null; + } + + /// + /// Gets the semantic version, if any. + /// + /// SemanticVersion, null if not specified. + public SemanticVersion? GetSemanticVersion() + { + string? semanticVersion = this.GetDirective(DirectiveConstants.Version); + if (!string.IsNullOrWhiteSpace(semanticVersion)) + { + return new SemanticVersion(semanticVersion); + } + + return null; + } + + /// + /// Gets the semantic min version, if any. + /// + /// SemanticVersion, null if not specified. + public SemanticVersion? GetSemanticMinVersion() + { + string? semanticVersion = this.GetDirective(DirectiveConstants.MinVersion); + if (!string.IsNullOrWhiteSpace(semanticVersion)) + { + return new SemanticVersion(semanticVersion); + } + + return null; + } + + /// + /// Gets the semantic max version, if any. + /// + /// SemanticVersion, null if not specified. + public SemanticVersion? GetSemanticMaxVersion() + { + string? semanticVersion = this.GetDirective(DirectiveConstants.MaxVersion); + if (!string.IsNullOrWhiteSpace(semanticVersion)) + { + return new SemanticVersion(semanticVersion); + } + + return null; + } + + /// + /// TODO: Implement for more variables. + /// I am so sad because rs.SessionStateProxy.InvokeCommand.ExpandString doesn't work as I wanted. + /// PowerShell assumes all code passed to ExpandString is trusted and we cannot assume that. + /// + /// ValueSet with settings. + public ValueSet GetExpandedSettings() + { + var valueSet = new ValueSet(); + foreach (var value in this.Unit.Settings) + { + if (value.Value is string) + { + // For now, we just expand config root. + valueSet.Add(value.Key, this.ExpandConfigRoot(value.Value as string, value.Key)); + } + else + { + valueSet.Add(value); + } + } + + return valueSet; + } + + private string? ExpandConfigRoot(string? value, string settingName) + { + if (!string.IsNullOrEmpty(value)) + { + // TODO: since we only support one variable, this only finds and replace + // ${WingetConfigRoot} if found in the string when the work of expanding + // string is done it should take into account other operators like the subexpression operator $() + if (value.Contains(ConfigRootVar, StringComparison.OrdinalIgnoreCase)) + { + if (string.IsNullOrEmpty(this.configurationFileRootPath)) + { + throw new UnitSettingConfigRootException(this.QualifiedName, settingName); + } + + return value.Replace(ConfigRootVar, this.configurationFileRootPath, StringComparison.OrdinalIgnoreCase); + } + } + + return value; + } + + private void InitializeDirectives() + { + foreach (var directive in this.Unit.Metadata) + { + var normalizedKey = StringHelpers.Normalize(directive.Key); + this.normalizedDirectives.Add(normalizedKey, directive.Value); + } + } + + private string ConstructQualifiedName(string? moduleName) + { + return $"{(moduleName == null ? string.Empty : $"{moduleName}/")}{this.ResourceName}"; + } + + [MemberNotNull(nameof(ResourceName), nameof(QualifiedName))] + private void InitializeNames() + { + // Determine ResourceName, QualifiedName, and the module directive + string unitType = this.Unit.Type; + string? moduleDirective = this.GetDirective(DirectiveConstants.Module); + + if (this.UnitTypeIsResourceName) + { + this.ResourceName = unitType; + this.QualifiedName = this.ConstructQualifiedName(moduleDirective); + return; + } + + int unitTypeDividerPosition = unitType.IndexOf('/'); + + if (unitTypeDividerPosition == unitType.Length - 1) + { + throw new ArgumentException($"Invalid unit Type: {unitType}"); + } + + string? moduleName; + + if (unitTypeDividerPosition == -1) + { + moduleName = moduleDirective; + this.ResourceName = unitType; + this.QualifiedName = this.ConstructQualifiedName(moduleDirective); + } + else + { + moduleName = unitType.Substring(0, unitTypeDividerPosition); + this.ResourceName = unitType.Substring(unitTypeDividerPosition + 1); + this.QualifiedName = unitType; + } + + if (moduleName != null) + { + if (moduleDirective != null) + { + if (moduleName != moduleDirective) + { + throw new ArgumentException($"Mismatched module specifiers: {moduleName} != {moduleDirective}"); + } + } + else + { + this.normalizedDirectives.Add(DirectiveConstants.Module, moduleName); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/DiagnosticInformation.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/DiagnosticInformation.cs index 82586b1521..b9e5edecea 100644 --- a/src/Microsoft.Management.Configuration.Processor/Helpers/DiagnosticInformation.cs +++ b/src/Microsoft.Management.Configuration.Processor/Helpers/DiagnosticInformation.cs @@ -1,23 +1,23 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System; - using Microsoft.Management.Configuration; - - /// - /// Implements IDiagnosticInformation. - /// - internal sealed partial class DiagnosticInformation : IDiagnosticInformation - { - /// - public DiagnosticLevel Level { get; internal set; } - - /// - public string? Message { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Helpers +{ + using System; + using Microsoft.Management.Configuration; + + /// + /// Implements IDiagnosticInformation. + /// + internal sealed partial class DiagnosticInformation : IDiagnosticInformation + { + /// + public DiagnosticLevel Level { get; internal set; } + + /// + public string? Message { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/SemanticVersion.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/SemanticVersion.cs index 03b69904fc..4c1edf223e 100644 --- a/src/Microsoft.Management.Configuration.Processor/Helpers/SemanticVersion.cs +++ b/src/Microsoft.Management.Configuration.Processor/Helpers/SemanticVersion.cs @@ -1,73 +1,73 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System; - - /// - /// A semantic version. - /// - internal class SemanticVersion - { - private const string MaxRange = "999999999"; - - private readonly string semanticVersion; - - /// - /// Initializes a new instance of the class. - /// - /// Version. - public SemanticVersion(string version) - { - this.semanticVersion = GetMaximumVersion(version); - - // Prerelease versions append the prerelease tag after a - - // PowerShell doesn't handle semantic versions. - if (this.semanticVersion.Contains("-")) - { - var indexOf = this.semanticVersion.IndexOf("-"); - this.Version = new Version(this.semanticVersion[..indexOf]); - this.PrereleaseTag = this.semanticVersion[(indexOf + 1) ..]; - } - else - { - this.Version = new Version(this.semanticVersion); - } - } - - /// - /// Gets the version without a prerelease tag. - /// - public Version Version { get; } - - /// - /// Gets prerelease tag if any. - /// - public string? PrereleaseTag { get; } = null; - - /// - /// Gets a value indicating whether if the semantic version is prerelease. - /// - public bool IsPrerelease => !string.IsNullOrEmpty(this.PrereleaseTag); - - /// - public override string ToString() - { - return this.semanticVersion; - } - - /// - /// Max out a version by replacing * if needed. - /// - /// Version. - /// Maxed version. - private static string GetMaximumVersion(string version) - { - return version.Replace("*", MaxRange); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Helpers +{ + using System; + + /// + /// A semantic version. + /// + internal class SemanticVersion + { + private const string MaxRange = "999999999"; + + private readonly string semanticVersion; + + /// + /// Initializes a new instance of the class. + /// + /// Version. + public SemanticVersion(string version) + { + this.semanticVersion = GetMaximumVersion(version); + + // Prerelease versions append the prerelease tag after a - + // PowerShell doesn't handle semantic versions. + if (this.semanticVersion.Contains("-")) + { + var indexOf = this.semanticVersion.IndexOf("-"); + this.Version = new Version(this.semanticVersion[..indexOf]); + this.PrereleaseTag = this.semanticVersion[(indexOf + 1) ..]; + } + else + { + this.Version = new Version(this.semanticVersion); + } + } + + /// + /// Gets the version without a prerelease tag. + /// + public Version Version { get; } + + /// + /// Gets prerelease tag if any. + /// + public string? PrereleaseTag { get; } = null; + + /// + /// Gets a value indicating whether if the semantic version is prerelease. + /// + public bool IsPrerelease => !string.IsNullOrEmpty(this.PrereleaseTag); + + /// + public override string ToString() + { + return this.semanticVersion; + } + + /// + /// Max out a version by replacing * if needed. + /// + /// Version. + /// Maxed version. + private static string GetMaximumVersion(string version) + { + return version.Replace("*", MaxRange); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/StringHelpers.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/StringHelpers.cs index 4aac948182..fe7aba610c 100644 --- a/src/Microsoft.Management.Configuration.Processor/Helpers/StringHelpers.cs +++ b/src/Microsoft.Management.Configuration.Processor/Helpers/StringHelpers.cs @@ -1,24 +1,24 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - /// - /// String helpers. - /// - internal static class StringHelpers - { - /// - /// Normalize string. - /// - /// Value. - /// Normalized string. - public static string Normalize(string value) - { - return value.ToLowerInvariant(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Helpers +{ + /// + /// String helpers. + /// + internal static class StringHelpers + { + /// + /// Normalize string. + /// + /// Value. + /// Normalized string. + public static string Normalize(string value) + { + return value.ToLowerInvariant(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Helpers/TypeHelpers.cs b/src/Microsoft.Management.Configuration.Processor/Helpers/TypeHelpers.cs index dfe10f7761..03c88dc64a 100644 --- a/src/Microsoft.Management.Configuration.Processor/Helpers/TypeHelpers.cs +++ b/src/Microsoft.Management.Configuration.Processor/Helpers/TypeHelpers.cs @@ -1,228 +1,228 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Reflection; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.SetProcessorFactory; - using Windows.Foundation.Collections; - - /// - /// Type helpers. - /// - internal static class TypeHelpers - { - /// - /// Verifies a property exists. - /// - /// Dynamic object. - /// Name of property. - /// True if property exists. - public static bool PropertyExists(dynamic obj, string name) - { - return obj.GetType().GetProperty(name) is not null; - } - - /// - /// Verifies a property exists with the specified type. - /// - /// Expected type. - /// Dynamic object. - /// Name of property. - /// True if property and is of the specified type. - public static bool PropertyWithTypeExists(dynamic obj, string name) - { - return PropertyExists(obj, name) && - obj.GetType().GetProperty(name).PropertyType == typeof(TType); - } - - /// - /// Verifies the property exist and is an enum. - /// - /// Dynamic object. - /// Name of property. - /// True if property exists and is an enum. - public static bool PropertyExistsAndIsEnum(dynamic obj, string name) - { - return PropertyExists(obj, name) && - obj.GetType().GetProperty(name).PropertyType.IsEnum; - } - - /// - /// Verifies the property exists and is a list. Use this when you don't know the type of the List. - /// - /// Dynamic object. - /// Name of property. - /// True if property exists and is a list. - public static bool PropertyExistsAndIsList(dynamic obj, string name) - { - return PropertyExists(obj, name) && - obj.GetType().GetProperty(name).PropertyType.IsGenericType && - obj.GetType().GetProperty(name).PropertyType.GetGenericTypeDefinition() == typeof(List<>); - } - - /// - /// Gets all the properties and values from an object. - /// - /// Object. - /// ValueSet with properties names and values. - public static ValueSet GetAllPropertiesValues(object obj) - { - var result = new ValueSet(); - foreach (PropertyInfo property in obj.GetType().GetProperties()) - { - var key = property.Name; - var value = GetCompatibleValueSetValueOfProperty(property.PropertyType, property.GetValue(obj)); - result.Add(key, value); - } - - return result; - } - - /// - /// Gets a compatible type for a ValueSet value. - /// - /// Type. - /// Value. - /// Value converted to a compatible type. - public static object? GetCompatibleValueSetValueOfProperty(Type type, object? value) - { - if (value == null) - { - return null; - } - - // Specialize here. - if (type.IsEnum) - { - return value.ToString(); - } - else if (type == typeof(Hashtable)) - { - Hashtable hashtable = (Hashtable)value; - return hashtable.ToValueSet(); - } - else if (type.IsArray) - { - var valueSetArray = new ValueSet(); - int index = 0; - foreach (object arrayObj in (Array)value) - { - var arrayValue = GetCompatibleValueSetValueOfProperty(arrayObj.GetType(), arrayObj); - if (arrayValue != null) - { - valueSetArray.Add(index.ToString(), arrayValue); - index++; - } - } - - if (valueSetArray.Count > 0) - { - valueSetArray.Add("treatAsArray", true); - } - - return valueSetArray; - } - else if (type == typeof(string)) - { - // Ignore empty strings. - string propertyString = (string)value; - if (!string.IsNullOrEmpty(propertyString)) - { - return propertyString; - } - else - { - return null; - } - } - else if (type.IsValueType) - { - return value; - } - - // This might be too restrictive but anything else is going to be some object that we don't support anyway. - throw new UnitPropertyUnsupportedException(value.GetType()); - } - - /// - /// Converts PowerShellConfigurationProcessorPolicy string value to PwshConfigurationProcessorPolicy. - /// - /// PowerShellConfigurationProcessorPolicy value. - /// PwshConfigurationProcessorPolicy. - public static PwshConfigurationProcessorPolicy ToPwshConfigurationProcessorPolicy(PowerShellConfigurationProcessorPolicy value) - { - return value switch - { - PowerShellConfigurationProcessorPolicy.Unrestricted => PwshConfigurationProcessorPolicy.Unrestricted, - PowerShellConfigurationProcessorPolicy.RemoteSigned => PwshConfigurationProcessorPolicy.RemoteSigned, - PowerShellConfigurationProcessorPolicy.AllSigned => PwshConfigurationProcessorPolicy.AllSigned, - PowerShellConfigurationProcessorPolicy.Restricted => PwshConfigurationProcessorPolicy.Restricted, - PowerShellConfigurationProcessorPolicy.Bypass => PwshConfigurationProcessorPolicy.Bypass, - PowerShellConfigurationProcessorPolicy.Undefined => PwshConfigurationProcessorPolicy.Undefined, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PwshConfigurationProcessorPolicy string value to PowerShellConfigurationProcessorPolicy. - /// - /// PwshConfigurationProcessorPolicy value. - /// PowerShellConfigurationProcessorPolicy. - public static PowerShellConfigurationProcessorPolicy ToPowerShellConfigurationProcessorPolicy(PwshConfigurationProcessorPolicy value) - { - return value switch - { - PwshConfigurationProcessorPolicy.Unrestricted => PowerShellConfigurationProcessorPolicy.Unrestricted, - PwshConfigurationProcessorPolicy.RemoteSigned => PowerShellConfigurationProcessorPolicy.RemoteSigned, - PwshConfigurationProcessorPolicy.AllSigned => PowerShellConfigurationProcessorPolicy.AllSigned, - PwshConfigurationProcessorPolicy.Restricted => PowerShellConfigurationProcessorPolicy.Restricted, - PwshConfigurationProcessorPolicy.Bypass => PowerShellConfigurationProcessorPolicy.Bypass, - PwshConfigurationProcessorPolicy.Undefined => PowerShellConfigurationProcessorPolicy.Undefined, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PowerShellConfigurationProcessorLocation string value to PwshConfigurationProcessorLocation. - /// - /// PowerShellConfigurationProcessorLocation value. - /// PwshConfigurationProcessorLocation. - public static PwshConfigurationProcessorLocation ToPwshConfigurationProcessorLocation(PowerShellConfigurationProcessorLocation value) - { - return value switch - { - PowerShellConfigurationProcessorLocation.CurrentUser => PwshConfigurationProcessorLocation.CurrentUser, - PowerShellConfigurationProcessorLocation.AllUsers => PwshConfigurationProcessorLocation.AllUsers, - PowerShellConfigurationProcessorLocation.WinGetModulePath => PwshConfigurationProcessorLocation.WinGetModulePath, - PowerShellConfigurationProcessorLocation.Custom => PwshConfigurationProcessorLocation.Custom, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PwshConfigurationProcessorLocation string value to PowerShellConfigurationProcessorLocation. - /// - /// PwshConfigurationProcessorLocation value. - /// PowerShellConfigurationProcessorLocation. - public static PowerShellConfigurationProcessorLocation ToPowerShellConfigurationProcessorLocation(PwshConfigurationProcessorLocation value) - { - return value switch - { - PwshConfigurationProcessorLocation.CurrentUser => PowerShellConfigurationProcessorLocation.CurrentUser, - PwshConfigurationProcessorLocation.AllUsers => PowerShellConfigurationProcessorLocation.AllUsers, - PwshConfigurationProcessorLocation.WinGetModulePath => PowerShellConfigurationProcessorLocation.WinGetModulePath, - PwshConfigurationProcessorLocation.Custom => PowerShellConfigurationProcessorLocation.Custom, - _ => throw new InvalidOperationException(), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Helpers +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Reflection; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.SetProcessorFactory; + using Windows.Foundation.Collections; + + /// + /// Type helpers. + /// + internal static class TypeHelpers + { + /// + /// Verifies a property exists. + /// + /// Dynamic object. + /// Name of property. + /// True if property exists. + public static bool PropertyExists(dynamic obj, string name) + { + return obj.GetType().GetProperty(name) is not null; + } + + /// + /// Verifies a property exists with the specified type. + /// + /// Expected type. + /// Dynamic object. + /// Name of property. + /// True if property and is of the specified type. + public static bool PropertyWithTypeExists(dynamic obj, string name) + { + return PropertyExists(obj, name) && + obj.GetType().GetProperty(name).PropertyType == typeof(TType); + } + + /// + /// Verifies the property exist and is an enum. + /// + /// Dynamic object. + /// Name of property. + /// True if property exists and is an enum. + public static bool PropertyExistsAndIsEnum(dynamic obj, string name) + { + return PropertyExists(obj, name) && + obj.GetType().GetProperty(name).PropertyType.IsEnum; + } + + /// + /// Verifies the property exists and is a list. Use this when you don't know the type of the List. + /// + /// Dynamic object. + /// Name of property. + /// True if property exists and is a list. + public static bool PropertyExistsAndIsList(dynamic obj, string name) + { + return PropertyExists(obj, name) && + obj.GetType().GetProperty(name).PropertyType.IsGenericType && + obj.GetType().GetProperty(name).PropertyType.GetGenericTypeDefinition() == typeof(List<>); + } + + /// + /// Gets all the properties and values from an object. + /// + /// Object. + /// ValueSet with properties names and values. + public static ValueSet GetAllPropertiesValues(object obj) + { + var result = new ValueSet(); + foreach (PropertyInfo property in obj.GetType().GetProperties()) + { + var key = property.Name; + var value = GetCompatibleValueSetValueOfProperty(property.PropertyType, property.GetValue(obj)); + result.Add(key, value); + } + + return result; + } + + /// + /// Gets a compatible type for a ValueSet value. + /// + /// Type. + /// Value. + /// Value converted to a compatible type. + public static object? GetCompatibleValueSetValueOfProperty(Type type, object? value) + { + if (value == null) + { + return null; + } + + // Specialize here. + if (type.IsEnum) + { + return value.ToString(); + } + else if (type == typeof(Hashtable)) + { + Hashtable hashtable = (Hashtable)value; + return hashtable.ToValueSet(); + } + else if (type.IsArray) + { + var valueSetArray = new ValueSet(); + int index = 0; + foreach (object arrayObj in (Array)value) + { + var arrayValue = GetCompatibleValueSetValueOfProperty(arrayObj.GetType(), arrayObj); + if (arrayValue != null) + { + valueSetArray.Add(index.ToString(), arrayValue); + index++; + } + } + + if (valueSetArray.Count > 0) + { + valueSetArray.Add("treatAsArray", true); + } + + return valueSetArray; + } + else if (type == typeof(string)) + { + // Ignore empty strings. + string propertyString = (string)value; + if (!string.IsNullOrEmpty(propertyString)) + { + return propertyString; + } + else + { + return null; + } + } + else if (type.IsValueType) + { + return value; + } + + // This might be too restrictive but anything else is going to be some object that we don't support anyway. + throw new UnitPropertyUnsupportedException(value.GetType()); + } + + /// + /// Converts PowerShellConfigurationProcessorPolicy string value to PwshConfigurationProcessorPolicy. + /// + /// PowerShellConfigurationProcessorPolicy value. + /// PwshConfigurationProcessorPolicy. + public static PwshConfigurationProcessorPolicy ToPwshConfigurationProcessorPolicy(PowerShellConfigurationProcessorPolicy value) + { + return value switch + { + PowerShellConfigurationProcessorPolicy.Unrestricted => PwshConfigurationProcessorPolicy.Unrestricted, + PowerShellConfigurationProcessorPolicy.RemoteSigned => PwshConfigurationProcessorPolicy.RemoteSigned, + PowerShellConfigurationProcessorPolicy.AllSigned => PwshConfigurationProcessorPolicy.AllSigned, + PowerShellConfigurationProcessorPolicy.Restricted => PwshConfigurationProcessorPolicy.Restricted, + PowerShellConfigurationProcessorPolicy.Bypass => PwshConfigurationProcessorPolicy.Bypass, + PowerShellConfigurationProcessorPolicy.Undefined => PwshConfigurationProcessorPolicy.Undefined, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PwshConfigurationProcessorPolicy string value to PowerShellConfigurationProcessorPolicy. + /// + /// PwshConfigurationProcessorPolicy value. + /// PowerShellConfigurationProcessorPolicy. + public static PowerShellConfigurationProcessorPolicy ToPowerShellConfigurationProcessorPolicy(PwshConfigurationProcessorPolicy value) + { + return value switch + { + PwshConfigurationProcessorPolicy.Unrestricted => PowerShellConfigurationProcessorPolicy.Unrestricted, + PwshConfigurationProcessorPolicy.RemoteSigned => PowerShellConfigurationProcessorPolicy.RemoteSigned, + PwshConfigurationProcessorPolicy.AllSigned => PowerShellConfigurationProcessorPolicy.AllSigned, + PwshConfigurationProcessorPolicy.Restricted => PowerShellConfigurationProcessorPolicy.Restricted, + PwshConfigurationProcessorPolicy.Bypass => PowerShellConfigurationProcessorPolicy.Bypass, + PwshConfigurationProcessorPolicy.Undefined => PowerShellConfigurationProcessorPolicy.Undefined, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PowerShellConfigurationProcessorLocation string value to PwshConfigurationProcessorLocation. + /// + /// PowerShellConfigurationProcessorLocation value. + /// PwshConfigurationProcessorLocation. + public static PwshConfigurationProcessorLocation ToPwshConfigurationProcessorLocation(PowerShellConfigurationProcessorLocation value) + { + return value switch + { + PowerShellConfigurationProcessorLocation.CurrentUser => PwshConfigurationProcessorLocation.CurrentUser, + PowerShellConfigurationProcessorLocation.AllUsers => PwshConfigurationProcessorLocation.AllUsers, + PowerShellConfigurationProcessorLocation.WinGetModulePath => PwshConfigurationProcessorLocation.WinGetModulePath, + PowerShellConfigurationProcessorLocation.Custom => PwshConfigurationProcessorLocation.Custom, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PwshConfigurationProcessorLocation string value to PowerShellConfigurationProcessorLocation. + /// + /// PwshConfigurationProcessorLocation value. + /// PowerShellConfigurationProcessorLocation. + public static PowerShellConfigurationProcessorLocation ToPowerShellConfigurationProcessorLocation(PwshConfigurationProcessorLocation value) + { + return value switch + { + PwshConfigurationProcessorLocation.CurrentUser => PowerShellConfigurationProcessorLocation.CurrentUser, + PwshConfigurationProcessorLocation.AllUsers => PowerShellConfigurationProcessorLocation.AllUsers, + PwshConfigurationProcessorLocation.WinGetModulePath => PowerShellConfigurationProcessorLocation.WinGetModulePath, + PwshConfigurationProcessorLocation.Custom => PowerShellConfigurationProcessorLocation.Custom, + _ => throw new InvalidOperationException(), + }; + } + } +} 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 a1c25865f2..047632421a 100644 --- a/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj +++ b/src/Microsoft.Management.Configuration.Processor/Microsoft.Management.Configuration.Processor.csproj @@ -1,140 +1,140 @@ - - - - net8.0 - - $(DotNetVersion)-windows10.0.26100.0 - enable - - 10.0.17763.0 - false - AnyCpu - win - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - true - - None - - 1591,8785 - Debug;Release;ReleaseStatic - true - true - - - - true - - - - true - - - - true - 10.0.26100.0 - - - - - - - - - - contentFiles - - - all - - - - - - Content - PreserveNewest - True - - - - - - Content - Always - - - - - $(DefineConstants);WinGetCsWinRTEmbedded - false - - Microsoft.Management.Configuration; - Windows.ApplicationModel.AppDisplayInf; - Windows.ApplicationModel.IAppDisplayInf; - Windows.ApplicationModel.AppExecutionContex; - Windows.ApplicationModel.IAppExecutionContex; - Windows.ApplicationModel.AppInf; - Windows.ApplicationModel.IAppInf; - Windows.ApplicationModel.AppInstallerInf; - Windows.ApplicationModel.IAppInstallerInf; - Windows.ApplicationModel.AppInstallerPolicySourc; - Windows.ApplicationModel.AddResourcePackageOption; - Windows.ApplicationModel.IAddResourcePackageOption; - Windows.ApplicationModel.FindRelatedPackagesOption; - Windows.ApplicationModel.IFindRelatedPackagesOption; - Windows.ApplicationModel.Packag; - Windows.ApplicationModel.IPackag; - Windows.ApplicationModel.Core.AppListEntr; - Windows.ApplicationModel.Core.IAppListEntr; - Windows.Data.Text.TextSegmen; - Windows.Management.Deployment; - Windows.Devices.Geolocation; - Windows.Foundation; - Windows.Globalization.DayOfWee; - Windows.Networking.Connectivity; - Windows.Networking.DomainNameTyp; - Windows.Networking.EndpointPai; - Windows.Networking.IEndpointPai; - Windows.Networking.HostNam; - Windows.Networking.IHostNam; - Windows.Security.Cryptography.Certificates; - Windows.Storage; - Windows.Storage.Provider.FileUpdateStatu; - Windows.System.ProcessorArchitectur; - Windows.System.Use; - Windows.System.IUse; - - - Windows.Foundation.PropertyType; - Windows.Storage.Provider; - - - 10.0.17763.0 - - - - - - - $(OutputPath)..\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd - $(SolutionDir)x64\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd - $(SolutionDir)x86\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd - $(SolutionDir)arm64\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd - - - - - - - - - - - - - - - - - - + + + + net8.0 + + $(DotNetVersion)-windows10.0.26100.0 + enable + + 10.0.17763.0 + false + AnyCpu + win + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + true + + None + + 1591,8785 + Debug;Release;ReleaseStatic + true + true + + + + true + + + + true + + + + true + 10.0.26100.0 + + + + + + + + + + contentFiles + + + all + + + + + + Content + PreserveNewest + True + + + + + + Content + Always + + + + + $(DefineConstants);WinGetCsWinRTEmbedded + false + + Microsoft.Management.Configuration; + Windows.ApplicationModel.AppDisplayInf; + Windows.ApplicationModel.IAppDisplayInf; + Windows.ApplicationModel.AppExecutionContex; + Windows.ApplicationModel.IAppExecutionContex; + Windows.ApplicationModel.AppInf; + Windows.ApplicationModel.IAppInf; + Windows.ApplicationModel.AppInstallerInf; + Windows.ApplicationModel.IAppInstallerInf; + Windows.ApplicationModel.AppInstallerPolicySourc; + Windows.ApplicationModel.AddResourcePackageOption; + Windows.ApplicationModel.IAddResourcePackageOption; + Windows.ApplicationModel.FindRelatedPackagesOption; + Windows.ApplicationModel.IFindRelatedPackagesOption; + Windows.ApplicationModel.Packag; + Windows.ApplicationModel.IPackag; + Windows.ApplicationModel.Core.AppListEntr; + Windows.ApplicationModel.Core.IAppListEntr; + Windows.Data.Text.TextSegmen; + Windows.Management.Deployment; + Windows.Devices.Geolocation; + Windows.Foundation; + Windows.Globalization.DayOfWee; + Windows.Networking.Connectivity; + Windows.Networking.DomainNameTyp; + Windows.Networking.EndpointPai; + Windows.Networking.IEndpointPai; + Windows.Networking.HostNam; + Windows.Networking.IHostNam; + Windows.Security.Cryptography.Certificates; + Windows.Storage; + Windows.Storage.Provider.FileUpdateStatu; + Windows.System.ProcessorArchitectur; + Windows.System.Use; + Windows.System.IUse; + + + Windows.Foundation.PropertyType; + Windows.Storage.Provider; + + + 10.0.17763.0 + + + + + + + $(OutputPath)..\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd + $(SolutionDir)x64\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd + $(SolutionDir)x86\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd + $(SolutionDir)arm64\$(Configuration)\Microsoft.Management.Configuration\Microsoft.Management.Configuration.winmd + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Constants/PowerShellConstants.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Constants/PowerShellConstants.cs index efdadcab9f..b7de3d8368 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Constants/PowerShellConstants.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Constants/PowerShellConstants.cs @@ -1,81 +1,81 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Constants -{ - /// - /// Constants related to PowerShell. - /// - internal static class PowerShellConstants - { -#pragma warning disable SA1600 // ElementsMustBeDocumented - public const string Core = "Core"; - - internal static class Variables - { - public const string PSEdition = "PSEdition"; - public const string Error = "Error"; - public const string PSModulePath = "env:PSModulePath"; - } - - internal static class Modules - { - public const string PSDesiredStateConfiguration = "PSDesiredStateConfiguration"; - public const string PSDesiredStateConfigurationMinVersion = "2.0.7"; - public const string PowerShellGet = "PowerShellGet"; - public const string PowerShellGetMinVersion = "2.2.5"; - public const string PSDesiredStateConfigurationMaxVersion = "2.*"; - } - - internal static class Commands - { - public const string FindDscResource = "Find-DscResource"; - public const string GetAuthenticodeSignature = "Get-AuthenticodeSignature"; - public const string GetChildItem = "Get-ChildItem"; - public const string GetCommand = "Get-Command"; - public const string GetDscResource = "Get-DscResource"; - public const string GetInstalledModule = "Get-InstalledModule"; - public const string GetModule = "Get-Module"; - public const string ImportModule = "Import-Module"; - public const string InstallModule = "Install-Module"; - public const string InvokeDscResource = "Invoke-DscResource"; - public const string SaveModule = "Save-Module"; - public const string FindModule = "Find-Module"; - public const string ImportCliXml = "Import-CliXml"; - } - - internal static class Parameters - { - public const string AllowPrerelease = "AllowPrerelease"; - public const string Force = "Force"; - public const string FullyQualifiedName = "FullyQualifiedName"; - public const string Guid = "GUID"; - public const string InputObject = "InputObject"; - public const string ListAvailable = "ListAvailable"; - public const string MaximumVersion = "MaximumVersion"; - public const string Method = "Method"; - public const string MinimumVersion = "MinimumVersion"; - public const string Module = "Module"; - public const string ModuleName = "ModuleName"; - public const string ModuleVersion = "ModuleVersion"; - public const string Name = "Name"; - public const string Path = "Path"; - public const string Property = "Property"; - public const string Recurse = "Recurse"; - public const string Repository = "Repository"; - public const string RequiredVersion = "RequiredVersion"; - public const string Scope = "Scope"; - } - - internal static class DscMethods - { - public const string Get = "Get"; - public const string Set = "Set"; - public const string Test = "Test"; - } -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Constants +{ + /// + /// Constants related to PowerShell. + /// + internal static class PowerShellConstants + { +#pragma warning disable SA1600 // ElementsMustBeDocumented + public const string Core = "Core"; + + internal static class Variables + { + public const string PSEdition = "PSEdition"; + public const string Error = "Error"; + public const string PSModulePath = "env:PSModulePath"; + } + + internal static class Modules + { + public const string PSDesiredStateConfiguration = "PSDesiredStateConfiguration"; + public const string PSDesiredStateConfigurationMinVersion = "2.0.7"; + public const string PowerShellGet = "PowerShellGet"; + public const string PowerShellGetMinVersion = "2.2.5"; + public const string PSDesiredStateConfigurationMaxVersion = "2.*"; + } + + internal static class Commands + { + public const string FindDscResource = "Find-DscResource"; + public const string GetAuthenticodeSignature = "Get-AuthenticodeSignature"; + public const string GetChildItem = "Get-ChildItem"; + public const string GetCommand = "Get-Command"; + public const string GetDscResource = "Get-DscResource"; + public const string GetInstalledModule = "Get-InstalledModule"; + public const string GetModule = "Get-Module"; + public const string ImportModule = "Import-Module"; + public const string InstallModule = "Install-Module"; + public const string InvokeDscResource = "Invoke-DscResource"; + public const string SaveModule = "Save-Module"; + public const string FindModule = "Find-Module"; + public const string ImportCliXml = "Import-CliXml"; + } + + internal static class Parameters + { + public const string AllowPrerelease = "AllowPrerelease"; + public const string Force = "Force"; + public const string FullyQualifiedName = "FullyQualifiedName"; + public const string Guid = "GUID"; + public const string InputObject = "InputObject"; + public const string ListAvailable = "ListAvailable"; + public const string MaximumVersion = "MaximumVersion"; + public const string Method = "Method"; + public const string MinimumVersion = "MinimumVersion"; + public const string Module = "Module"; + public const string ModuleName = "ModuleName"; + public const string ModuleVersion = "ModuleVersion"; + public const string Name = "Name"; + public const string Path = "Path"; + public const string Property = "Property"; + public const string Recurse = "Recurse"; + public const string Repository = "Repository"; + public const string RequiredVersion = "RequiredVersion"; + public const string Scope = "Scope"; + } + + internal static class DscMethods + { + public const string Get = "Get"; + public const string Set = "Set"; + public const string Test = "Test"; + } +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/DscModuleV2.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/DscModuleV2.cs index 3aadebb1ea..23bada1a92 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/DscModuleV2.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/DscModuleV2.cs @@ -1,297 +1,297 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.DscModules -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.Linq; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; - - /// - /// PSDesiredStateConfiguration v2. - /// - internal class DscModuleV2 : IDscModule - { - private const string InDesiredState = "InDesiredState"; - private const string RebootRequired = "RebootRequired"; - - private static readonly IEnumerable ExclusionResourcesParentPath = new string[] - { - @"C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources", - @"C:\Program Files\WindowsPowerShell\Modules\PackageManagement\1.0.0.1", - }; - - /// - /// Initializes a new instance of the class. - /// - public DscModuleV2() - { - this.ModuleSpecification = PowerShellHelpers.CreateModuleSpecification( - Modules.PSDesiredStateConfiguration, - minVersion: Modules.PSDesiredStateConfigurationMinVersion, - maxVersion: Modules.PSDesiredStateConfigurationMaxVersion); - } - - /// - public ModuleSpecification ModuleSpecification { get; private init; } - - /// - public string GetDscResourceCmd { get; } = Commands.GetDscResource; - - /// - public string InvokeDscResourceCmd { get; } = Commands.InvokeDscResource; - - /// - public IReadOnlyList GetAllDscResources(PowerShell pwsh) - { - return this.GetDscResources(pwsh, null, null); - } - - /// - public IReadOnlyList GetDscResourcesInModule( - PowerShell pwsh, - ModuleSpecification moduleSpecification) - { - return this.GetDscResources(pwsh, null, moduleSpecification); - } - - /// - public DscResourceInfoInternal? GetDscResource( - PowerShell pwsh, - string name, - ModuleSpecification? moduleSpecification) - { - var dscResourceInfos = this.GetDscResources(pwsh, name, moduleSpecification); - - if (dscResourceInfos.Count == 0) - { - return null; - } - else if (dscResourceInfos.Count > 1) - { - throw new GetDscResourceMultipleMatches(name, moduleSpecification); - } - - return dscResourceInfos[0]; - } - - /// - public ValueSet InvokeGetResource( - PowerShell pwsh, - ValueSet settings, - string name, - ModuleSpecification? moduleSpecification) - { - PSObject? getResult = null; - - try - { - getResult = pwsh.AddCommand(this.InvokeDscResourceCmd) - .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) - .AddParameter(Parameters.Method, DscMethods.Get) - .InvokeAndStopOnError() - .FirstOrDefault(); - } - catch (Exception ex) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, ex); - } - - string? errorMessage = pwsh.GetErrorMessage(); - if (errorMessage is not null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, errorMessage); - } - - if (getResult is null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification); - } - - // Script based resource. - if (getResult.BaseObject is Hashtable) - { - Hashtable hashTable = (Hashtable)getResult.BaseObject; - var resultSettings = new ValueSet(); - foreach (DictionaryEntry entry in hashTable) - { - resultSettings.Add(entry.Key as string, entry.Value); - } - - return resultSettings; - } - - // Class-based resource. - return TypeHelpers.GetAllPropertiesValues(getResult.BaseObject); - } - - /// - public bool InvokeTestResource( - PowerShell pwsh, - ValueSet settings, - string name, - ModuleSpecification? moduleSpecification) - { - // Returned type is InvokeDscResourceTestResult which is a PowerShell classed defined - // in PSDesiredStateConfiguration.psm1. - dynamic? testResult = null; - - try - { - testResult = pwsh.AddCommand(this.InvokeDscResourceCmd) - .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) - .AddParameter(Parameters.Method, DscMethods.Test) - .InvokeAndStopOnError() - .FirstOrDefault(); - } - catch (Exception ex) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, ex); - } - - string? errorMessage = pwsh.GetErrorMessage(); - if (errorMessage is not null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, errorMessage); - } - - if (testResult is null || - !TypeHelpers.PropertyWithTypeExists(testResult, InDesiredState)) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification); - } - - return testResult?.InDesiredState; - } - - /// - public bool InvokeSetResource( - PowerShell pwsh, - ValueSet settings, - string name, - ModuleSpecification? moduleSpecification) - { - // Returned type is InvokeDscResourceSetResult which is a PowerShell classed defined - // in PSDesiredStateConfiguration.psm1. - dynamic? setResult = null; - - try - { - setResult = pwsh.AddCommand(this.InvokeDscResourceCmd) - .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) - .AddParameter(Parameters.Method, DscMethods.Set) - .InvokeAndStopOnError() - .FirstOrDefault(); - } - catch (Exception ex) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, ex); - } - - string? errorMessage = pwsh.GetErrorMessage(); - if (errorMessage is not null) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, errorMessage, pwsh.ContainsPropertyError()); - } - - if (setResult is null || - !TypeHelpers.PropertyWithTypeExists(setResult, RebootRequired)) - { - throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification); - } - - return setResult?.RebootRequired; - } - - private static Dictionary PrepareInvokeParameters( - string name, - ValueSet settings, - ModuleSpecification? moduleSpecification) - { - Hashtable properties = settings.ToHashtable(); - - var parameters = new Dictionary() - { - { Parameters.Name, name }, - { Parameters.Property, properties }, - }; - - if (moduleSpecification is not null) - { - parameters.Add(Parameters.ModuleName, moduleSpecification); - } - - return parameters; - } - - private IReadOnlyList GetDscResources( - PowerShell pwsh, - string? name, - ModuleSpecification? moduleSpecification) - { - pwsh.AddCommand(this.GetDscResourceCmd); - - if (name is not null) - { - pwsh.AddParameter(Parameters.Name, name); - } - - if (moduleSpecification is not null) - { - pwsh.AddParameter(Parameters.Module, moduleSpecification); - } - - try - { - var resources = pwsh.Invoke(); - return this.ConvertToDscResourceInfoInternal(resources); - } - catch (RuntimeException e) - { - // Detect easily this. - if (e.ErrorRecord.FullyQualifiedErrorId == "ExceptionWhenSetting,GetResourceFromKeyword") - { - throw new GetDscResourceModuleConflict(name, moduleSpecification, e); - } - - throw; - } - } - - private List ConvertToDscResourceInfoInternal(Collection psObjects) - { - var result = new List(); - foreach (dynamic psObject in psObjects) - { - var dscResourceInfo = new DscResourceInfoInternal(psObject); - - // Explicitly don't support old DSC resources from v1 PSDesiredStateConfiguration. - // Even if the Windows System32 Windows PowerShell module path is removed they - // will show up. - if (ExclusionResourcesParentPath.Any(e => dscResourceInfo.ParentPath!.StartsWith(e))) - { - continue; - } - - result.Add(dscResourceInfo); - } - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscModules +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Linq; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// + /// PSDesiredStateConfiguration v2. + /// + internal class DscModuleV2 : IDscModule + { + private const string InDesiredState = "InDesiredState"; + private const string RebootRequired = "RebootRequired"; + + private static readonly IEnumerable ExclusionResourcesParentPath = new string[] + { + @"C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources", + @"C:\Program Files\WindowsPowerShell\Modules\PackageManagement\1.0.0.1", + }; + + /// + /// Initializes a new instance of the class. + /// + public DscModuleV2() + { + this.ModuleSpecification = PowerShellHelpers.CreateModuleSpecification( + Modules.PSDesiredStateConfiguration, + minVersion: Modules.PSDesiredStateConfigurationMinVersion, + maxVersion: Modules.PSDesiredStateConfigurationMaxVersion); + } + + /// + public ModuleSpecification ModuleSpecification { get; private init; } + + /// + public string GetDscResourceCmd { get; } = Commands.GetDscResource; + + /// + public string InvokeDscResourceCmd { get; } = Commands.InvokeDscResource; + + /// + public IReadOnlyList GetAllDscResources(PowerShell pwsh) + { + return this.GetDscResources(pwsh, null, null); + } + + /// + public IReadOnlyList GetDscResourcesInModule( + PowerShell pwsh, + ModuleSpecification moduleSpecification) + { + return this.GetDscResources(pwsh, null, moduleSpecification); + } + + /// + public DscResourceInfoInternal? GetDscResource( + PowerShell pwsh, + string name, + ModuleSpecification? moduleSpecification) + { + var dscResourceInfos = this.GetDscResources(pwsh, name, moduleSpecification); + + if (dscResourceInfos.Count == 0) + { + return null; + } + else if (dscResourceInfos.Count > 1) + { + throw new GetDscResourceMultipleMatches(name, moduleSpecification); + } + + return dscResourceInfos[0]; + } + + /// + public ValueSet InvokeGetResource( + PowerShell pwsh, + ValueSet settings, + string name, + ModuleSpecification? moduleSpecification) + { + PSObject? getResult = null; + + try + { + getResult = pwsh.AddCommand(this.InvokeDscResourceCmd) + .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) + .AddParameter(Parameters.Method, DscMethods.Get) + .InvokeAndStopOnError() + .FirstOrDefault(); + } + catch (Exception ex) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, ex); + } + + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification, errorMessage); + } + + if (getResult is null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Get, name, moduleSpecification); + } + + // Script based resource. + if (getResult.BaseObject is Hashtable) + { + Hashtable hashTable = (Hashtable)getResult.BaseObject; + var resultSettings = new ValueSet(); + foreach (DictionaryEntry entry in hashTable) + { + resultSettings.Add(entry.Key as string, entry.Value); + } + + return resultSettings; + } + + // Class-based resource. + return TypeHelpers.GetAllPropertiesValues(getResult.BaseObject); + } + + /// + public bool InvokeTestResource( + PowerShell pwsh, + ValueSet settings, + string name, + ModuleSpecification? moduleSpecification) + { + // Returned type is InvokeDscResourceTestResult which is a PowerShell classed defined + // in PSDesiredStateConfiguration.psm1. + dynamic? testResult = null; + + try + { + testResult = pwsh.AddCommand(this.InvokeDscResourceCmd) + .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) + .AddParameter(Parameters.Method, DscMethods.Test) + .InvokeAndStopOnError() + .FirstOrDefault(); + } + catch (Exception ex) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, ex); + } + + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification, errorMessage); + } + + if (testResult is null || + !TypeHelpers.PropertyWithTypeExists(testResult, InDesiredState)) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Test, name, moduleSpecification); + } + + return testResult?.InDesiredState; + } + + /// + public bool InvokeSetResource( + PowerShell pwsh, + ValueSet settings, + string name, + ModuleSpecification? moduleSpecification) + { + // Returned type is InvokeDscResourceSetResult which is a PowerShell classed defined + // in PSDesiredStateConfiguration.psm1. + dynamic? setResult = null; + + try + { + setResult = pwsh.AddCommand(this.InvokeDscResourceCmd) + .AddParameters(PrepareInvokeParameters(name, settings, moduleSpecification)) + .AddParameter(Parameters.Method, DscMethods.Set) + .InvokeAndStopOnError() + .FirstOrDefault(); + } + catch (Exception ex) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, ex); + } + + string? errorMessage = pwsh.GetErrorMessage(); + if (errorMessage is not null) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification, errorMessage, pwsh.ContainsPropertyError()); + } + + if (setResult is null || + !TypeHelpers.PropertyWithTypeExists(setResult, RebootRequired)) + { + throw new InvokeDscResourceException(InvokeDscResourceException.Set, name, moduleSpecification); + } + + return setResult?.RebootRequired; + } + + private static Dictionary PrepareInvokeParameters( + string name, + ValueSet settings, + ModuleSpecification? moduleSpecification) + { + Hashtable properties = settings.ToHashtable(); + + var parameters = new Dictionary() + { + { Parameters.Name, name }, + { Parameters.Property, properties }, + }; + + if (moduleSpecification is not null) + { + parameters.Add(Parameters.ModuleName, moduleSpecification); + } + + return parameters; + } + + private IReadOnlyList GetDscResources( + PowerShell pwsh, + string? name, + ModuleSpecification? moduleSpecification) + { + pwsh.AddCommand(this.GetDscResourceCmd); + + if (name is not null) + { + pwsh.AddParameter(Parameters.Name, name); + } + + if (moduleSpecification is not null) + { + pwsh.AddParameter(Parameters.Module, moduleSpecification); + } + + try + { + var resources = pwsh.Invoke(); + return this.ConvertToDscResourceInfoInternal(resources); + } + catch (RuntimeException e) + { + // Detect easily this. + if (e.ErrorRecord.FullyQualifiedErrorId == "ExceptionWhenSetting,GetResourceFromKeyword") + { + throw new GetDscResourceModuleConflict(name, moduleSpecification, e); + } + + throw; + } + } + + private List ConvertToDscResourceInfoInternal(Collection psObjects) + { + var result = new List(); + foreach (dynamic psObject in psObjects) + { + var dscResourceInfo = new DscResourceInfoInternal(psObject); + + // Explicitly don't support old DSC resources from v1 PSDesiredStateConfiguration. + // Even if the Windows System32 Windows PowerShell module path is removed they + // will show up. + if (ExclusionResourcesParentPath.Any(e => dscResourceInfo.ParentPath!.StartsWith(e))) + { + continue; + } + + result.Add(dscResourceInfo); + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/IDscModule.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/IDscModule.cs index 8b686ea91c..3b7c0a7375 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/IDscModule.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscModules/IDscModule.cs @@ -1,89 +1,89 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.DscModules -{ - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - - /// - /// Interface for defining a PSDesiredStateConfiguration module. - /// - internal interface IDscModule - { - /// - /// Gets the module specification. - /// - ModuleSpecification ModuleSpecification { get; } - - /// - /// Gets the name of the Get-DscResource Cmdlet. - /// - string GetDscResourceCmd { get; } - - /// - /// Gets the name of the Invoke-DscResource Cmdlet. - /// - string InvokeDscResourceCmd { get; } - - /// - /// Gets all DSC resource. - /// - /// PowerShell. - /// A list with the DSC resource. - IReadOnlyList GetAllDscResources(PowerShell pwsh); - - /// - /// Gets all resources in a module. - /// - /// PowerShell. - /// Module specification. - /// List of resources of that module and version. - IReadOnlyList GetDscResourcesInModule(PowerShell pwsh, ModuleSpecification moduleSpecification); - - /// - /// Gets a DSC Resource. - /// - /// PowerShell. - /// Name. - /// Module specification. - /// DSC Resource from that module and version. - DscResourceInfoInternal? GetDscResource(PowerShell pwsh, string name, ModuleSpecification? moduleSpecification); - - /// - /// Calls Invoke-DscResource -Method Get from this module. - /// - /// PowerShell. - /// Settings. - /// Name. - /// Module specification. - /// Properties of resource. - ValueSet InvokeGetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// - /// Calls Invoke-DscResource -Method Test from this module. - /// - /// PowerShell. - /// Settings. - /// Name. - /// Module specification. - /// Is in desired state. - bool InvokeTestResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// - /// Calls Invoke-DscResource -Method Set from this module. - /// - /// PowerShell. - /// Settings. - /// Name. - /// Module specification. - /// If a reboot is required. - bool InvokeSetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscModules +{ + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + + /// + /// Interface for defining a PSDesiredStateConfiguration module. + /// + internal interface IDscModule + { + /// + /// Gets the module specification. + /// + ModuleSpecification ModuleSpecification { get; } + + /// + /// Gets the name of the Get-DscResource Cmdlet. + /// + string GetDscResourceCmd { get; } + + /// + /// Gets the name of the Invoke-DscResource Cmdlet. + /// + string InvokeDscResourceCmd { get; } + + /// + /// Gets all DSC resource. + /// + /// PowerShell. + /// A list with the DSC resource. + IReadOnlyList GetAllDscResources(PowerShell pwsh); + + /// + /// Gets all resources in a module. + /// + /// PowerShell. + /// Module specification. + /// List of resources of that module and version. + IReadOnlyList GetDscResourcesInModule(PowerShell pwsh, ModuleSpecification moduleSpecification); + + /// + /// Gets a DSC Resource. + /// + /// PowerShell. + /// Name. + /// Module specification. + /// DSC Resource from that module and version. + DscResourceInfoInternal? GetDscResource(PowerShell pwsh, string name, ModuleSpecification? moduleSpecification); + + /// + /// Calls Invoke-DscResource -Method Get from this module. + /// + /// PowerShell. + /// Settings. + /// Name. + /// Module specification. + /// Properties of resource. + ValueSet InvokeGetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// + /// Calls Invoke-DscResource -Method Test from this module. + /// + /// PowerShell. + /// Settings. + /// Name. + /// Module specification. + /// Is in desired state. + bool InvokeTestResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// + /// Calls Invoke-DscResource -Method Set from this module. + /// + /// PowerShell. + /// Settings. + /// Name. + /// Module specification. + /// If a reboot is required. + bool InvokeSetResource(PowerShell pwsh, ValueSet settings, string name, ModuleSpecification? moduleSpecification); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourceInfoInternal.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourceInfoInternal.cs index 52f37256bd..7d0780a0f6 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourceInfoInternal.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourceInfoInternal.cs @@ -1,157 +1,157 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Helpers; - - /// - /// Contains a DSC resource information. - /// This object should be initialized with a Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo. - /// That object is the result of Get-DscResource and is not the same as System.Management.Automation.DscResourceInfo. - /// The type is defined in a psd1 in the PSDesiredStateConfiguration. This file is based on that. - /// If Invoke-DscResource gets support for passing the DscResourceInfo, we could keep the original object as member - /// to then pass it in code. - /// - internal class DscResourceInfoInternal - { - private const string DscResourceInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo"; - - /// - /// Initializes a new instance of the class. - /// - /// Dynamic object. Expected DscResourceInfo. - public DscResourceInfoInternal(dynamic info) - { - if (info.GetType().FullName != DscResourceInfoFullName) - { - throw new ArgumentException(); - } - - this.ResourceType = info.ResourceType; - this.Name = info.Name; - this.FriendlyName = info.FriendlyName; - this.Module = info.Module; - this.Path = info.Path; - this.ParentPath = info.ParentPath; - this.ImplementedAs = Enum.Parse(info.ImplementedAs.ToString()); - this.CompanyName = info.CompanyName; - - if (this.Module is not null) - { - this.ModuleName = this.Module.Name; - this.Version = this.Module.Version; - } - - foreach (object property in info.Properties) - { - this.Properties.Add(new DscResourcePropertyInfoInternal(property)); - } - } - - /// - /// Initializes a new instance of the class. - /// Used for unit tests. - /// - /// Resource name. - /// Module name. - /// Version. - internal DscResourceInfoInternal(string name, string? moduleName, Version? version) - { - this.Name = name; - this.ModuleName = moduleName; - this.Version = version; - } - - /// - /// Gets or sets resource type name. - /// - public string? ResourceType { get; set; } - - /// - /// Gets or sets Name of the resource. This name is used to access the resource. - /// - public string Name { get; set; } - - /// - /// Gets the normalized resource name. - /// - public string NormalizedName - { - get { return StringHelpers.Normalize(this.Name); } - } - - /// - /// Gets or sets friendly name defined for the resource. - /// - public string? FriendlyName { get; set; } - - /// - /// Gets or sets module which implements the resource. This could point to parent module, if the DSC resource is implemented. - /// by one of nested modules. - /// - public PSModuleInfo? Module { get; set; } - - /// - /// Gets or sets name of the module which implements the resource. - /// - public string? ModuleName { get; set; } - - /// - /// Gets the normalized module name. - /// - public string? NormalizedModuleName - { - get { return this.ModuleName is not null ? StringHelpers.Normalize(this.ModuleName) : null; } - } - - /// - /// Gets or sets version of the module which implements the resource. - /// - public Version? Version { get; set; } - - /// - /// Gets or sets of the file which implements the resource. For the resources which are defined using - /// MOF file, this will be path to a module which resides in the same folder where schema.mof file is present. - /// For composite resources, this will be the module which implements the resource. - /// - public string? Path { get; set; } - - /// - /// Gets or sets parent folder, where the resource is defined - /// It is the folder containing either the implementing module(=Path) or folder containing ".schema.mof". - /// For native providers, Path will be null and only ParentPath will be present. - /// - public string? ParentPath { get; set; } - - /// - /// Gets or sets a value which indicate how DSC resource is implemented. - /// - public ImplementedAsTypeInternal? ImplementedAs { get; set; } - - /// - /// Gets or sets company which owns this resource. - /// - public string? CompanyName { get; set; } - - /// - /// Gets properties of the resource. - /// - public List Properties { get; private set; } = new List(); - - /// - /// Updates properties of the resource. - /// - /// Updated properties. - public void UpdateProperties(List properties) - { - this.Properties = properties; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Helpers; + + /// + /// Contains a DSC resource information. + /// This object should be initialized with a Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo. + /// That object is the result of Get-DscResource and is not the same as System.Management.Automation.DscResourceInfo. + /// The type is defined in a psd1 in the PSDesiredStateConfiguration. This file is based on that. + /// If Invoke-DscResource gets support for passing the DscResourceInfo, we could keep the original object as member + /// to then pass it in code. + /// + internal class DscResourceInfoInternal + { + private const string DscResourceInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourceInfo"; + + /// + /// Initializes a new instance of the class. + /// + /// Dynamic object. Expected DscResourceInfo. + public DscResourceInfoInternal(dynamic info) + { + if (info.GetType().FullName != DscResourceInfoFullName) + { + throw new ArgumentException(); + } + + this.ResourceType = info.ResourceType; + this.Name = info.Name; + this.FriendlyName = info.FriendlyName; + this.Module = info.Module; + this.Path = info.Path; + this.ParentPath = info.ParentPath; + this.ImplementedAs = Enum.Parse(info.ImplementedAs.ToString()); + this.CompanyName = info.CompanyName; + + if (this.Module is not null) + { + this.ModuleName = this.Module.Name; + this.Version = this.Module.Version; + } + + foreach (object property in info.Properties) + { + this.Properties.Add(new DscResourcePropertyInfoInternal(property)); + } + } + + /// + /// Initializes a new instance of the class. + /// Used for unit tests. + /// + /// Resource name. + /// Module name. + /// Version. + internal DscResourceInfoInternal(string name, string? moduleName, Version? version) + { + this.Name = name; + this.ModuleName = moduleName; + this.Version = version; + } + + /// + /// Gets or sets resource type name. + /// + public string? ResourceType { get; set; } + + /// + /// Gets or sets Name of the resource. This name is used to access the resource. + /// + public string Name { get; set; } + + /// + /// Gets the normalized resource name. + /// + public string NormalizedName + { + get { return StringHelpers.Normalize(this.Name); } + } + + /// + /// Gets or sets friendly name defined for the resource. + /// + public string? FriendlyName { get; set; } + + /// + /// Gets or sets module which implements the resource. This could point to parent module, if the DSC resource is implemented. + /// by one of nested modules. + /// + public PSModuleInfo? Module { get; set; } + + /// + /// Gets or sets name of the module which implements the resource. + /// + public string? ModuleName { get; set; } + + /// + /// Gets the normalized module name. + /// + public string? NormalizedModuleName + { + get { return this.ModuleName is not null ? StringHelpers.Normalize(this.ModuleName) : null; } + } + + /// + /// Gets or sets version of the module which implements the resource. + /// + public Version? Version { get; set; } + + /// + /// Gets or sets of the file which implements the resource. For the resources which are defined using + /// MOF file, this will be path to a module which resides in the same folder where schema.mof file is present. + /// For composite resources, this will be the module which implements the resource. + /// + public string? Path { get; set; } + + /// + /// Gets or sets parent folder, where the resource is defined + /// It is the folder containing either the implementing module(=Path) or folder containing ".schema.mof". + /// For native providers, Path will be null and only ParentPath will be present. + /// + public string? ParentPath { get; set; } + + /// + /// Gets or sets a value which indicate how DSC resource is implemented. + /// + public ImplementedAsTypeInternal? ImplementedAs { get; set; } + + /// + /// Gets or sets company which owns this resource. + /// + public string? CompanyName { get; set; } + + /// + /// Gets properties of the resource. + /// + public List Properties { get; private set; } = new List(); + + /// + /// Updates properties of the resource. + /// + /// Updated properties. + public void UpdateProperties(List properties) + { + this.Properties = properties; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourcePropertyInfoInternal.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourcePropertyInfoInternal.cs index c470cf2677..8daba2eeb4 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourcePropertyInfoInternal.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/DscResourcePropertyInfoInternal.cs @@ -1,56 +1,56 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo -{ - using System; - using System.Collections.Generic; - - /// - /// Contains a DSC resource property information. - /// - internal sealed class DscResourcePropertyInfoInternal - { - private const string DscResourcePropertyInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourcePropertyInfo"; - - /// - /// Initializes a new instance of the class. - /// - /// Dynamic DSC property info. - public DscResourcePropertyInfoInternal(dynamic dscPropertyInfo) - { - if (dscPropertyInfo.GetType().FullName != DscResourcePropertyInfoFullName) - { - throw new ArgumentException(); - } - - this.Name = dscPropertyInfo.Name; - this.PropertyType = dscPropertyInfo.PropertyType; - this.IsMandatory = dscPropertyInfo.IsMandatory; - this.Values = dscPropertyInfo.Values; - } - - /// - /// Gets or sets name of the property. - /// - public string Name { get; set; } - - /// - /// Gets or sets type of the property. - /// - public string PropertyType { get; set; } - - /// - /// Gets or sets a value indicating whether the property is mandatory or not. - /// - public bool IsMandatory { get; set; } - - /// - /// Gets Values for a resource property. - /// - public List Values { get; private set; } = new List(); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo +{ + using System; + using System.Collections.Generic; + + /// + /// Contains a DSC resource property information. + /// + internal sealed class DscResourcePropertyInfoInternal + { + private const string DscResourcePropertyInfoFullName = "Microsoft.PowerShell.DesiredStateConfiguration.DscResourcePropertyInfo"; + + /// + /// Initializes a new instance of the class. + /// + /// Dynamic DSC property info. + public DscResourcePropertyInfoInternal(dynamic dscPropertyInfo) + { + if (dscPropertyInfo.GetType().FullName != DscResourcePropertyInfoFullName) + { + throw new ArgumentException(); + } + + this.Name = dscPropertyInfo.Name; + this.PropertyType = dscPropertyInfo.PropertyType; + this.IsMandatory = dscPropertyInfo.IsMandatory; + this.Values = dscPropertyInfo.Values; + } + + /// + /// Gets or sets name of the property. + /// + public string Name { get; set; } + + /// + /// Gets or sets type of the property. + /// + public string PropertyType { get; set; } + + /// + /// Gets or sets a value indicating whether the property is mandatory or not. + /// + public bool IsMandatory { get; set; } + + /// + /// Gets Values for a resource property. + /// + public List Values { get; private set; } = new List(); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/ImplementedAsTypeInternal.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/ImplementedAsTypeInternal.cs index 7119dc28b1..3621be19e0 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/ImplementedAsTypeInternal.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/DscResourcesInfo/ImplementedAsTypeInternal.cs @@ -1,34 +1,34 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo -{ - /// - /// Enumerated values for DSC resource implementation type. - /// - internal enum ImplementedAsTypeInternal - { - /// - /// DSC resource implementation type not known. - /// - None = 0, - - /// - /// DSC resource is implemented using PowerShell module. - /// - PowerShell = 1, - - /// - /// DSC resource is implemented using a CIM provider. - /// - Binary = 2, - - /// - /// DSC resource is a composite and implemented using configuration keyword. - /// - Composite = 3, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo +{ + /// + /// Enumerated values for DSC resource implementation type. + /// + internal enum ImplementedAsTypeInternal + { + /// + /// DSC resource implementation type not known. + /// + None = 0, + + /// + /// DSC resource is implemented using PowerShell module. + /// + PowerShell = 1, + + /// + /// DSC resource is implemented using a CIM provider. + /// + Binary = 2, + + /// + /// DSC resource is a composite and implemented using configuration keyword. + /// + Composite = 3, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Extensions/PowerShellExtensions.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Extensions/PowerShellExtensions.cs index 96a49be536..d6073d7cf6 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Extensions/PowerShellExtensions.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Extensions/PowerShellExtensions.cs @@ -1,96 +1,96 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Extensions -{ - using System.Collections.ObjectModel; - using System.Management.Automation; - using System.Text; - - /// - /// Extensions methods for class. - /// - internal static class PowerShellExtensions - { - /// - /// Calls Invoke with to stop execution of commands. - /// - /// PowerShell. - /// Collection of PSObjects representing output. - public static Collection InvokeAndStopOnError(this PowerShell pwsh) - { - var settings = new PSInvocationSettings() - { - ErrorActionPreference = ActionPreference.Stop, - }; - - return pwsh.Invoke(null, settings); - } - - /// - /// Calls Invoke with to stop execution of commands. - /// - /// Type of output object(s) expected from the command invocation. - /// PowerShell. - /// Collection of the type output representing output. - public static Collection InvokeAndStopOnError(this PowerShell pwsh) - { - var settings = new PSInvocationSettings() - { - ErrorActionPreference = ActionPreference.Stop, - }; - - return pwsh.Invoke(null, settings); - } - - /// - /// Gets the error stream message if any. - /// - /// PowerShell. - /// Error message. Null if none. - public static string? GetErrorMessage(this PowerShell pwsh) - { - if (pwsh.HadErrors) - { - var psStreamBuilder = new StringBuilder(); - foreach (var line in pwsh.Streams.Error) - { - psStreamBuilder.AppendLine(line.ToString()); - } - - return psStreamBuilder.ToString(); - } - - return null; - } - - /// - /// Determines if the given shell contains a property error, meaning that the source of this error is the - /// configuration values and not the configuration unit itself. - /// - /// The shell to inspect. - /// True if it only contains property errors; false otherwise. - public static bool ContainsPropertyError(this PowerShell pwsh) - { - if (!pwsh.HadErrors) - { - return false; - } - - bool result = true; - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Extensions +{ + using System.Collections.ObjectModel; + using System.Management.Automation; + using System.Text; + + /// + /// Extensions methods for class. + /// + internal static class PowerShellExtensions + { + /// + /// Calls Invoke with to stop execution of commands. + /// + /// PowerShell. + /// Collection of PSObjects representing output. + public static Collection InvokeAndStopOnError(this PowerShell pwsh) + { + var settings = new PSInvocationSettings() + { + ErrorActionPreference = ActionPreference.Stop, + }; + + return pwsh.Invoke(null, settings); + } + + /// + /// Calls Invoke with to stop execution of commands. + /// + /// Type of output object(s) expected from the command invocation. + /// PowerShell. + /// Collection of the type output representing output. + public static Collection InvokeAndStopOnError(this PowerShell pwsh) + { + var settings = new PSInvocationSettings() + { + ErrorActionPreference = ActionPreference.Stop, + }; + + return pwsh.Invoke(null, settings); + } + + /// + /// Gets the error stream message if any. + /// + /// PowerShell. + /// Error message. Null if none. + public static string? GetErrorMessage(this PowerShell pwsh) + { + if (pwsh.HadErrors) + { + var psStreamBuilder = new StringBuilder(); + foreach (var line in pwsh.Streams.Error) + { + psStreamBuilder.AppendLine(line.ToString()); + } + + return psStreamBuilder.ToString(); + } + + return null; + } + + /// + /// Determines if the given shell contains a property error, meaning that the source of this error is the + /// configuration values and not the configuration unit itself. + /// + /// The shell to inspect. + /// True if it only contains property errors; false otherwise. + public static bool ContainsPropertyError(this PowerShell pwsh) + { + if (!pwsh.HadErrors) + { + return false; + } + + bool result = true; + foreach (ErrorRecord? error in pwsh.Streams.Error) { - if (error?.FullyQualifiedErrorId == "PropertyAssignmentException") - { - result = result && true; + if (error?.FullyQualifiedErrorId == "PropertyAssignmentException") + { + result = result && true; } - } - - return result; - } - } -} + } + + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndModule.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndModule.cs index 88fcf81ba4..608a10293f 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndModule.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndModule.cs @@ -1,49 +1,49 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers -{ - using System; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Constants; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.PowerShell.Commands; - - /// - /// Contains information about the unit and the DSC resource that applies to it. - /// - internal class ConfigurationUnitAndModule : ConfigurationUnitInternal - { - /// - /// Initializes a new instance of the class. - /// - /// Configuration unit. - /// The configuration file path. - public ConfigurationUnitAndModule(ConfigurationUnit unit, string? configurationFilePath) - : base(unit, configurationFilePath) - { - string? moduleName = this.GetDirective(DirectiveConstants.Module); - if (string.IsNullOrEmpty(moduleName)) - { - this.Module = null; - } - else - { - this.Module = PowerShellHelpers.CreateModuleSpecification( - moduleName, - this.GetDirective(DirectiveConstants.Version), - this.GetDirective(DirectiveConstants.MinVersion), - this.GetDirective(DirectiveConstants.MaxVersion), - this.GetDirective(DirectiveConstants.ModuleGuid)); - } - } - - /// - /// Gets the module specification. - /// - public ModuleSpecification? Module { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Constants; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.PowerShell.Commands; + + /// + /// Contains information about the unit and the DSC resource that applies to it. + /// + internal class ConfigurationUnitAndModule : ConfigurationUnitInternal + { + /// + /// Initializes a new instance of the class. + /// + /// Configuration unit. + /// The configuration file path. + public ConfigurationUnitAndModule(ConfigurationUnit unit, string? configurationFilePath) + : base(unit, configurationFilePath) + { + string? moduleName = this.GetDirective(DirectiveConstants.Module); + if (string.IsNullOrEmpty(moduleName)) + { + this.Module = null; + } + else + { + this.Module = PowerShellHelpers.CreateModuleSpecification( + moduleName, + this.GetDirective(DirectiveConstants.Version), + this.GetDirective(DirectiveConstants.MinVersion), + this.GetDirective(DirectiveConstants.MaxVersion), + this.GetDirective(DirectiveConstants.ModuleGuid)); + } + } + + /// + /// Gets the module specification. + /// + public ModuleSpecification? Module { get; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndResource.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndResource.cs index 656e2baec6..24f9df7ab0 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndResource.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/ConfigurationUnitAndResource.cs @@ -1,102 +1,102 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers -{ - using System; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - - /// - /// Contains information about the unit and the DSC resource that applies to it. - /// - internal class ConfigurationUnitAndResource - { - private readonly DscResourceInfoInternal dscResourceInfoInternal; - - /// - /// Initializes a new instance of the class. - /// - /// Configuration unit internal. - /// DscResourceInfoInternal. - public ConfigurationUnitAndResource( - ConfigurationUnitAndModule configurationUnitInternal, - DscResourceInfoInternal dscResourceInfoInternal) - { - if (!configurationUnitInternal.ResourceName.Equals(dscResourceInfoInternal.Name, StringComparison.OrdinalIgnoreCase)) - { - throw new ArgumentException(); - } - - this.UnitInternal = configurationUnitInternal; - this.dscResourceInfoInternal = dscResourceInfoInternal; - } - - /// - /// Gets or initializes the internal unit. - /// - public ConfigurationUnitAndModule UnitInternal { get; private init; } - - /// - /// Gets the configuration unit. - /// - public ConfigurationUnit Unit - { - get { return this.UnitInternal.Unit; } - } - - /// - /// Gets the DSC resource name. - /// - /// DSC resource name. - public string ResourceName - { - get { return this.dscResourceInfoInternal.Name; } - } - - /// - /// Gets the module specification. - /// - public ModuleSpecification? Module - { - get { return this.UnitInternal.Module; } - } - - /// - /// Gets a directive if exits. - /// - /// Name of directive. - /// The value of the directive. Null if doesn't exist. - /// Directive type value. - public TType? GetDirective(string directiveName) - where TType : class - { - return this.UnitInternal.GetDirective(directiveName); - } - - /// - /// Gets a directive bool value. - /// - /// Name of directive. - /// The value of the directive. False if not set. - public bool? GetDirective(string directiveName) - { - return this.UnitInternal.GetDirective(directiveName); - } - - /// - /// Gets the settings of the unit. - /// - /// ValueSet with settings. - public ValueSet GetSettings() - { - return this.UnitInternal.GetExpandedSettings(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + + /// + /// Contains information about the unit and the DSC resource that applies to it. + /// + internal class ConfigurationUnitAndResource + { + private readonly DscResourceInfoInternal dscResourceInfoInternal; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration unit internal. + /// DscResourceInfoInternal. + public ConfigurationUnitAndResource( + ConfigurationUnitAndModule configurationUnitInternal, + DscResourceInfoInternal dscResourceInfoInternal) + { + if (!configurationUnitInternal.ResourceName.Equals(dscResourceInfoInternal.Name, StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException(); + } + + this.UnitInternal = configurationUnitInternal; + this.dscResourceInfoInternal = dscResourceInfoInternal; + } + + /// + /// Gets or initializes the internal unit. + /// + public ConfigurationUnitAndModule UnitInternal { get; private init; } + + /// + /// Gets the configuration unit. + /// + public ConfigurationUnit Unit + { + get { return this.UnitInternal.Unit; } + } + + /// + /// Gets the DSC resource name. + /// + /// DSC resource name. + public string ResourceName + { + get { return this.dscResourceInfoInternal.Name; } + } + + /// + /// Gets the module specification. + /// + public ModuleSpecification? Module + { + get { return this.UnitInternal.Module; } + } + + /// + /// Gets a directive if exits. + /// + /// Name of directive. + /// The value of the directive. Null if doesn't exist. + /// Directive type value. + public TType? GetDirective(string directiveName) + where TType : class + { + return this.UnitInternal.GetDirective(directiveName); + } + + /// + /// Gets a directive bool value. + /// + /// Name of directive. + /// The value of the directive. False if not set. + public bool? GetDirective(string directiveName) + { + return this.UnitInternal.GetDirective(directiveName); + } + + /// + /// Gets the settings of the unit. + /// + /// ValueSet with settings. + public ValueSet GetSettings() + { + return this.UnitInternal.GetExpandedSettings(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/DscResourcesMap.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/DscResourcesMap.cs index 1b534224a7..47e2f670df 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/DscResourcesMap.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/DscResourcesMap.cs @@ -1,224 +1,224 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - - /// - /// DscResources in the system. Get-DscResource looks the entire PSModulePath to find resources every time, so - /// calling it multiples times is expensive. This class will store them in memory per configuration set. - /// The resources are stored in a dictionary in the following hierarchy. - /// - Module name - /// - Resource name - /// - Module Version - /// - DscResourceInfo obj. - /// There are some resources that don't have a module name. These are modules that live under - /// C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources. - /// This is not currently used. For now, we let PowerShell handle all the versioning. In order to avoid - /// calls to Get-DscResource every time a unit is set, this class needs to be updated to handle min and max - /// version. As it is, it just does RequiredVersion. - /// - internal sealed class DscResourcesMap - { - private readonly Dictionary resources = new Dictionary(); - - /// - /// Initializes a new instance of the class. - /// - /// DSC Resources. - public DscResourcesMap(IReadOnlyList resources) - { - foreach (var resource in resources) - { - if (string.IsNullOrEmpty(resource.Name)) - { - throw new ArgumentException(nameof(resource.Name)); - } - - if (resource.Version is null) - { - throw new ArgumentException(nameof(resource.Version)); - } - - this.Insert(resource); - } - } - - /// - /// Looks for a DSC resource in the specified module. - /// - /// DSC resource name. - /// Optional module name. - /// Optional module version. - /// DscResourceInfoInternal. Null if not found. - public DscResourceInfoInternal? GetResource(string dscResourceName, string? moduleName, Version? version) - { - string normalizedResourceName = StringHelpers.Normalize(dscResourceName); - string? normalizedModuleName = null; - if (moduleName is not null) - { - normalizedModuleName = StringHelpers.Normalize(moduleName); - } - - if (normalizedModuleName is null) - { - foreach (var module in this.resources.Values) - { - var resourceInfo = module.GetDscResourceInfo(normalizedResourceName, version); - if (resourceInfo is not null) - { - return resourceInfo; - } - } - } - else - { - if (this.resources.TryGetValue(normalizedModuleName, out ModuleDscResources? module)) - { - return module.GetDscResourceInfo(normalizedResourceName, version); - } - } - - return null; - } - - /// - /// Does a DSC resource exists. - /// - /// DSC resource name. - /// Optional module name. - /// Optional module version. - /// True if resource exists. - public bool Exists(string dscResourceName, string? moduleName, Version? version) - { - return this.GetResource(dscResourceName, moduleName, version) != null; - } - - private void Insert(DscResourceInfoInternal dscResourceInfo) - { - string? normalizedModuleName = dscResourceInfo.NormalizedModuleName; - - // There are some resources that doesn't have module, use empty as key. - string moduleKey = string.Empty; - if (normalizedModuleName is not null) - { - moduleKey = normalizedModuleName; - } - - if (!this.resources.ContainsKey(moduleKey)) - { - this.resources.Add(moduleKey, new ModuleDscResources(moduleKey, dscResourceInfo)); - } - else - { - this.resources[moduleKey].AddResource(moduleKey, dscResourceInfo); - } - } - - /// - /// Represents a map versions for a DscResource. - /// - private class DscResourceVersions : Dictionary - { - private readonly string resourceName; - - public DscResourceVersions(DscResourceInfoInternal dscResourceInfo) - { - this.resourceName = dscResourceInfo.NormalizedName; - this.AddVersion(dscResourceInfo); - } - - /// - /// Adds a resource version. - /// - /// DscResourceInfo. - public void AddVersion(DscResourceInfoInternal dscResourceInfo) - { - if (this.resourceName != dscResourceInfo.NormalizedName) - { - throw new ArgumentException(dscResourceInfo.NormalizedName); - } - - // There are some system resources without version or module name. - if (dscResourceInfo.Version is null) - { - this.Add(new Version(), dscResourceInfo); - } - else - { - // Get-DscResource will fail if the same module with same - // resources and same version exists in the module path - // so this shouldn't happen. Either way, we should throw there or here. - this.Add(dscResourceInfo.Version, dscResourceInfo); - } - } - - public DscResourceInfoInternal? GetDscResourceInfo(Version? version) - { - if (version is null) - { - // Sort keys, get the latest. - var newestVersion = this.Keys.OrderByDescending(v => v).First(); - return this[newestVersion]; - } - - if (this.TryGetValue(version, out DscResourceInfoInternal? resourceInfo)) - { - return resourceInfo; - } - - return null; - } - } - - /// - /// Represent a map of resources for a module. - /// - private class ModuleDscResources : Dictionary - { - private readonly string normalizedModuleName; - - public ModuleDscResources(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) - { - this.normalizedModuleName = normalizedModuleName; - this.AddResource(this.normalizedModuleName, dscResourceInfo); - } - - public void AddResource(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) - { - if (this.normalizedModuleName != normalizedModuleName) - { - throw new ArgumentException(nameof(normalizedModuleName)); - } - - var normalizedResourceName = dscResourceInfo.NormalizedName; - if (!this.ContainsKey(normalizedResourceName)) - { - this.Add(normalizedResourceName, new DscResourceVersions(dscResourceInfo)); - } - else - { - this[normalizedResourceName].AddVersion(dscResourceInfo); - } - } - - public DscResourceInfoInternal? GetDscResourceInfo(string dscResourceName, Version? version) - { - if (this.TryGetValue(dscResourceName, out DscResourceVersions? versions)) - { - return versions.GetDscResourceInfo(version); - } - - return null; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + + /// + /// DscResources in the system. Get-DscResource looks the entire PSModulePath to find resources every time, so + /// calling it multiples times is expensive. This class will store them in memory per configuration set. + /// The resources are stored in a dictionary in the following hierarchy. + /// - Module name + /// - Resource name + /// - Module Version + /// - DscResourceInfo obj. + /// There are some resources that don't have a module name. These are modules that live under + /// C:\WINDOWS\system32\WindowsPowershell\v1.0\Modules\PsDesiredStateConfiguration\DscResources. + /// This is not currently used. For now, we let PowerShell handle all the versioning. In order to avoid + /// calls to Get-DscResource every time a unit is set, this class needs to be updated to handle min and max + /// version. As it is, it just does RequiredVersion. + /// + internal sealed class DscResourcesMap + { + private readonly Dictionary resources = new Dictionary(); + + /// + /// Initializes a new instance of the class. + /// + /// DSC Resources. + public DscResourcesMap(IReadOnlyList resources) + { + foreach (var resource in resources) + { + if (string.IsNullOrEmpty(resource.Name)) + { + throw new ArgumentException(nameof(resource.Name)); + } + + if (resource.Version is null) + { + throw new ArgumentException(nameof(resource.Version)); + } + + this.Insert(resource); + } + } + + /// + /// Looks for a DSC resource in the specified module. + /// + /// DSC resource name. + /// Optional module name. + /// Optional module version. + /// DscResourceInfoInternal. Null if not found. + public DscResourceInfoInternal? GetResource(string dscResourceName, string? moduleName, Version? version) + { + string normalizedResourceName = StringHelpers.Normalize(dscResourceName); + string? normalizedModuleName = null; + if (moduleName is not null) + { + normalizedModuleName = StringHelpers.Normalize(moduleName); + } + + if (normalizedModuleName is null) + { + foreach (var module in this.resources.Values) + { + var resourceInfo = module.GetDscResourceInfo(normalizedResourceName, version); + if (resourceInfo is not null) + { + return resourceInfo; + } + } + } + else + { + if (this.resources.TryGetValue(normalizedModuleName, out ModuleDscResources? module)) + { + return module.GetDscResourceInfo(normalizedResourceName, version); + } + } + + return null; + } + + /// + /// Does a DSC resource exists. + /// + /// DSC resource name. + /// Optional module name. + /// Optional module version. + /// True if resource exists. + public bool Exists(string dscResourceName, string? moduleName, Version? version) + { + return this.GetResource(dscResourceName, moduleName, version) != null; + } + + private void Insert(DscResourceInfoInternal dscResourceInfo) + { + string? normalizedModuleName = dscResourceInfo.NormalizedModuleName; + + // There are some resources that doesn't have module, use empty as key. + string moduleKey = string.Empty; + if (normalizedModuleName is not null) + { + moduleKey = normalizedModuleName; + } + + if (!this.resources.ContainsKey(moduleKey)) + { + this.resources.Add(moduleKey, new ModuleDscResources(moduleKey, dscResourceInfo)); + } + else + { + this.resources[moduleKey].AddResource(moduleKey, dscResourceInfo); + } + } + + /// + /// Represents a map versions for a DscResource. + /// + private class DscResourceVersions : Dictionary + { + private readonly string resourceName; + + public DscResourceVersions(DscResourceInfoInternal dscResourceInfo) + { + this.resourceName = dscResourceInfo.NormalizedName; + this.AddVersion(dscResourceInfo); + } + + /// + /// Adds a resource version. + /// + /// DscResourceInfo. + public void AddVersion(DscResourceInfoInternal dscResourceInfo) + { + if (this.resourceName != dscResourceInfo.NormalizedName) + { + throw new ArgumentException(dscResourceInfo.NormalizedName); + } + + // There are some system resources without version or module name. + if (dscResourceInfo.Version is null) + { + this.Add(new Version(), dscResourceInfo); + } + else + { + // Get-DscResource will fail if the same module with same + // resources and same version exists in the module path + // so this shouldn't happen. Either way, we should throw there or here. + this.Add(dscResourceInfo.Version, dscResourceInfo); + } + } + + public DscResourceInfoInternal? GetDscResourceInfo(Version? version) + { + if (version is null) + { + // Sort keys, get the latest. + var newestVersion = this.Keys.OrderByDescending(v => v).First(); + return this[newestVersion]; + } + + if (this.TryGetValue(version, out DscResourceInfoInternal? resourceInfo)) + { + return resourceInfo; + } + + return null; + } + } + + /// + /// Represent a map of resources for a module. + /// + private class ModuleDscResources : Dictionary + { + private readonly string normalizedModuleName; + + public ModuleDscResources(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) + { + this.normalizedModuleName = normalizedModuleName; + this.AddResource(this.normalizedModuleName, dscResourceInfo); + } + + public void AddResource(string normalizedModuleName, DscResourceInfoInternal dscResourceInfo) + { + if (this.normalizedModuleName != normalizedModuleName) + { + throw new ArgumentException(nameof(normalizedModuleName)); + } + + var normalizedResourceName = dscResourceInfo.NormalizedName; + if (!this.ContainsKey(normalizedResourceName)) + { + this.Add(normalizedResourceName, new DscResourceVersions(dscResourceInfo)); + } + else + { + this[normalizedResourceName].AddVersion(dscResourceInfo); + } + } + + public DscResourceInfoInternal? GetDscResourceInfo(string dscResourceName, Version? version) + { + if (this.TryGetValue(dscResourceName, out DscResourceVersions? versions)) + { + return versions.GetDscResourceInfo(version); + } + + return null; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/Factory.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/Factory.cs index 4d4bcf23e1..870fb15a2a 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/Factory.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/Factory.cs @@ -1,223 +1,223 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.Unit; - using Windows.Security.Cryptography.Certificates; - - /// - /// Enables creation of PowerShell specific instances of configuration interfaces. - /// - internal static class Factory - { - private static readonly IEnumerable PublicRepositories = new string[] - { - "https://www.powershellgallery.com/api/v2", - }; - - /// - /// Initializes a new instance of the class. - /// - /// Unit name. - /// DSC Resource Info. - /// PSModuleInfo. - /// GetModuleInfo. - /// List of certificates. - /// An initialized instance of . - public static ConfigurationUnitProcessorDetails CreateUnitProcessorDetails( - string unitName, - DscResourceInfoInternal? dscResourceInfo, - PSModuleInfo? psModuleInfo, - PSObject? getModuleInfo, - List? certs) - { - if (dscResourceInfo is null && - psModuleInfo is null && - getModuleInfo is null) - { - throw new ArgumentException(); - } - - ConfigurationUnitProcessorDetails result = new ConfigurationUnitProcessorDetails - { - UnitType = unitName, - }; - - if (dscResourceInfo is not null) - { - result.IsLocal = true; - - var settings = new List(); - foreach (var properties in dscResourceInfo.Properties) - { - settings.Add(CreateUnitSettingDetails(properties)); - } - - result.Settings = settings; - - if (psModuleInfo is null) - { - result.ModuleName = dscResourceInfo.ModuleName; - result.Version = dscResourceInfo.Version is not null ? dscResourceInfo.Version.ToString() : null; - } - } - - if (psModuleInfo is not null) - { - result.ModuleDocumentationUri = psModuleInfo.HelpInfoUri is not null ? new Uri(psModuleInfo.HelpInfoUri) : null; - result.UnitIconUri = psModuleInfo.IconUri; - result.ModuleName = psModuleInfo.Name; - result.ModuleType = psModuleInfo.ModuleType.ToString(); - result.ModuleDescription = psModuleInfo.Description; - result.PublishedModuleUri = psModuleInfo.ProjectUri; - result.Version = psModuleInfo.Version.ToString(); - result.Author = psModuleInfo.Author; - result.Publisher = psModuleInfo.CompanyName; - } - - if (getModuleInfo is not null) - { - result.ModuleSource = TryGetPropertyAsString(getModuleInfo, "Repository"); - result.PublishedDate = TryGetPropertyFromDateTimeToDateTimeOffset(getModuleInfo, "PublishedDate"); - - var repoSourceLocation = getModuleInfo.Properties["RepositorySourceLocation"]; - if (repoSourceLocation is not null) - { - string? repoSourceLocationValue = repoSourceLocation.Value as string; - if (repoSourceLocationValue is not null) - { - result.IsPublic = PublicRepositories.Any(r => r == repoSourceLocationValue); - } - } - - if (psModuleInfo is null) - { - // Type is not the same as this PSModuleType. - result.UnitIconUri = TryGetPropertyAsUri(getModuleInfo, "IconUri"); - result.ModuleName = TryGetPropertyAsString(getModuleInfo, "Name"); - result.ModuleDescription = TryGetPropertyAsString(getModuleInfo, "Description"); - result.Version = TryGetPropertyAsString(getModuleInfo, "Version"); - result.Author = TryGetPropertyAsString(getModuleInfo, "Author"); - result.Publisher = TryGetPropertyAsString(getModuleInfo, "CompanyName"); - } - } - - result.SigningInformation = certs; - - return result; - } - - /// - /// Initializes a new instance of the class. - /// - /// DSC Resource info. - /// An initialized instance of . - public static ConfigurationUnitSettingDetails CreateUnitSettingDetails(DscResourcePropertyInfoInternal dscResourceInfo) - { - return new ConfigurationUnitSettingDetails - { - Identifier = dscResourceInfo.Name, - IsRequired = dscResourceInfo.IsMandatory, - Type = GetPropertyType(dscResourceInfo.PropertyType), - }; - } - - private static string? TryGetPropertyAsString(PSObject getModuleInfo, string getModuleInfoProperty) - { - var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; - if (moduleProperty is not null) - { - return moduleProperty.Value as string; - } - - return null; - } - - private static Uri? TryGetPropertyAsUri(PSObject getModuleInfo, string getModuleInfoProperty) - { - var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; - if (moduleProperty is not null) - { - string? modulePropertyString = moduleProperty.Value as string; - if (modulePropertyString is not null) - { - return new Uri(modulePropertyString); - } - } - - return null; - } - - private static DateTimeOffset TryGetPropertyFromDateTimeToDateTimeOffset(PSObject getModuleInfo, string getModuleInfoProperty) - { - var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; - if (moduleProperty is not null) - { - DateTime propertyAsDateTime; - - try - { - propertyAsDateTime = (DateTime)moduleProperty.Value; - return new DateTimeOffset(propertyAsDateTime); - } - catch - { - } - } - - return default; - } - - private static Windows.Foundation.PropertyType GetPropertyType(string propertyType) - { - switch (propertyType.ToLowerInvariant()) - { - case "[byte]": return Windows.Foundation.PropertyType.UInt8; - case "[int16]": return Windows.Foundation.PropertyType.Int16; - case "[uint16]": return Windows.Foundation.PropertyType.UInt16; - case "[int32]": return Windows.Foundation.PropertyType.Int32; - case "[uint32]": return Windows.Foundation.PropertyType.UInt32; - case "[int64]": return Windows.Foundation.PropertyType.Int64; - case "[uint64]": return Windows.Foundation.PropertyType.UInt64; - case "[single]": return Windows.Foundation.PropertyType.Single; - case "[double]": return Windows.Foundation.PropertyType.Double; - case "[char]": return Windows.Foundation.PropertyType.Char16; - case "[bool]": return Windows.Foundation.PropertyType.Boolean; - case "[string]": return Windows.Foundation.PropertyType.String; - case "[datetime]": return Windows.Foundation.PropertyType.DateTime; - case "[datetimeoffset]": return Windows.Foundation.PropertyType.DateTime; - case "[timespan]": return Windows.Foundation.PropertyType.TimeSpan; - case "[guid]": return Windows.Foundation.PropertyType.Guid; - case "[byte[]]": return Windows.Foundation.PropertyType.UInt8Array; - case "[int16[]]": return Windows.Foundation.PropertyType.Int16Array; - case "[uint16[]]": return Windows.Foundation.PropertyType.UInt16Array; - case "[int32[]]": return Windows.Foundation.PropertyType.Int32Array; - case "[uint32[]]": return Windows.Foundation.PropertyType.UInt32Array; - case "[int64[]]": return Windows.Foundation.PropertyType.Int64Array; - case "[uint64[]]": return Windows.Foundation.PropertyType.UInt64Array; - case "[single[]]": return Windows.Foundation.PropertyType.SingleArray; - case "[double[]]": return Windows.Foundation.PropertyType.DoubleArray; - case "[char[]]": return Windows.Foundation.PropertyType.Char16Array; - case "[bool[]]": return Windows.Foundation.PropertyType.BooleanArray; - case "[string[]]": return Windows.Foundation.PropertyType.StringArray; - case "[object[]]": return Windows.Foundation.PropertyType.InspectableArray; - case "[datetime[]]": return Windows.Foundation.PropertyType.DateTimeArray; - case "[datetimeoffset[]]": return Windows.Foundation.PropertyType.DateTimeArray; - case "[timespan[]]": return Windows.Foundation.PropertyType.TimeSpanArray; - case "[guid[]]": return Windows.Foundation.PropertyType.GuidArray; - - // Everything else will just be an object... - default: return Windows.Foundation.PropertyType.Inspectable; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.Unit; + using Windows.Security.Cryptography.Certificates; + + /// + /// Enables creation of PowerShell specific instances of configuration interfaces. + /// + internal static class Factory + { + private static readonly IEnumerable PublicRepositories = new string[] + { + "https://www.powershellgallery.com/api/v2", + }; + + /// + /// Initializes a new instance of the class. + /// + /// Unit name. + /// DSC Resource Info. + /// PSModuleInfo. + /// GetModuleInfo. + /// List of certificates. + /// An initialized instance of . + public static ConfigurationUnitProcessorDetails CreateUnitProcessorDetails( + string unitName, + DscResourceInfoInternal? dscResourceInfo, + PSModuleInfo? psModuleInfo, + PSObject? getModuleInfo, + List? certs) + { + if (dscResourceInfo is null && + psModuleInfo is null && + getModuleInfo is null) + { + throw new ArgumentException(); + } + + ConfigurationUnitProcessorDetails result = new ConfigurationUnitProcessorDetails + { + UnitType = unitName, + }; + + if (dscResourceInfo is not null) + { + result.IsLocal = true; + + var settings = new List(); + foreach (var properties in dscResourceInfo.Properties) + { + settings.Add(CreateUnitSettingDetails(properties)); + } + + result.Settings = settings; + + if (psModuleInfo is null) + { + result.ModuleName = dscResourceInfo.ModuleName; + result.Version = dscResourceInfo.Version is not null ? dscResourceInfo.Version.ToString() : null; + } + } + + if (psModuleInfo is not null) + { + result.ModuleDocumentationUri = psModuleInfo.HelpInfoUri is not null ? new Uri(psModuleInfo.HelpInfoUri) : null; + result.UnitIconUri = psModuleInfo.IconUri; + result.ModuleName = psModuleInfo.Name; + result.ModuleType = psModuleInfo.ModuleType.ToString(); + result.ModuleDescription = psModuleInfo.Description; + result.PublishedModuleUri = psModuleInfo.ProjectUri; + result.Version = psModuleInfo.Version.ToString(); + result.Author = psModuleInfo.Author; + result.Publisher = psModuleInfo.CompanyName; + } + + if (getModuleInfo is not null) + { + result.ModuleSource = TryGetPropertyAsString(getModuleInfo, "Repository"); + result.PublishedDate = TryGetPropertyFromDateTimeToDateTimeOffset(getModuleInfo, "PublishedDate"); + + var repoSourceLocation = getModuleInfo.Properties["RepositorySourceLocation"]; + if (repoSourceLocation is not null) + { + string? repoSourceLocationValue = repoSourceLocation.Value as string; + if (repoSourceLocationValue is not null) + { + result.IsPublic = PublicRepositories.Any(r => r == repoSourceLocationValue); + } + } + + if (psModuleInfo is null) + { + // Type is not the same as this PSModuleType. + result.UnitIconUri = TryGetPropertyAsUri(getModuleInfo, "IconUri"); + result.ModuleName = TryGetPropertyAsString(getModuleInfo, "Name"); + result.ModuleDescription = TryGetPropertyAsString(getModuleInfo, "Description"); + result.Version = TryGetPropertyAsString(getModuleInfo, "Version"); + result.Author = TryGetPropertyAsString(getModuleInfo, "Author"); + result.Publisher = TryGetPropertyAsString(getModuleInfo, "CompanyName"); + } + } + + result.SigningInformation = certs; + + return result; + } + + /// + /// Initializes a new instance of the class. + /// + /// DSC Resource info. + /// An initialized instance of . + public static ConfigurationUnitSettingDetails CreateUnitSettingDetails(DscResourcePropertyInfoInternal dscResourceInfo) + { + return new ConfigurationUnitSettingDetails + { + Identifier = dscResourceInfo.Name, + IsRequired = dscResourceInfo.IsMandatory, + Type = GetPropertyType(dscResourceInfo.PropertyType), + }; + } + + private static string? TryGetPropertyAsString(PSObject getModuleInfo, string getModuleInfoProperty) + { + var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; + if (moduleProperty is not null) + { + return moduleProperty.Value as string; + } + + return null; + } + + private static Uri? TryGetPropertyAsUri(PSObject getModuleInfo, string getModuleInfoProperty) + { + var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; + if (moduleProperty is not null) + { + string? modulePropertyString = moduleProperty.Value as string; + if (modulePropertyString is not null) + { + return new Uri(modulePropertyString); + } + } + + return null; + } + + private static DateTimeOffset TryGetPropertyFromDateTimeToDateTimeOffset(PSObject getModuleInfo, string getModuleInfoProperty) + { + var moduleProperty = getModuleInfo.Properties[getModuleInfoProperty]; + if (moduleProperty is not null) + { + DateTime propertyAsDateTime; + + try + { + propertyAsDateTime = (DateTime)moduleProperty.Value; + return new DateTimeOffset(propertyAsDateTime); + } + catch + { + } + } + + return default; + } + + private static Windows.Foundation.PropertyType GetPropertyType(string propertyType) + { + switch (propertyType.ToLowerInvariant()) + { + case "[byte]": return Windows.Foundation.PropertyType.UInt8; + case "[int16]": return Windows.Foundation.PropertyType.Int16; + case "[uint16]": return Windows.Foundation.PropertyType.UInt16; + case "[int32]": return Windows.Foundation.PropertyType.Int32; + case "[uint32]": return Windows.Foundation.PropertyType.UInt32; + case "[int64]": return Windows.Foundation.PropertyType.Int64; + case "[uint64]": return Windows.Foundation.PropertyType.UInt64; + case "[single]": return Windows.Foundation.PropertyType.Single; + case "[double]": return Windows.Foundation.PropertyType.Double; + case "[char]": return Windows.Foundation.PropertyType.Char16; + case "[bool]": return Windows.Foundation.PropertyType.Boolean; + case "[string]": return Windows.Foundation.PropertyType.String; + case "[datetime]": return Windows.Foundation.PropertyType.DateTime; + case "[datetimeoffset]": return Windows.Foundation.PropertyType.DateTime; + case "[timespan]": return Windows.Foundation.PropertyType.TimeSpan; + case "[guid]": return Windows.Foundation.PropertyType.Guid; + case "[byte[]]": return Windows.Foundation.PropertyType.UInt8Array; + case "[int16[]]": return Windows.Foundation.PropertyType.Int16Array; + case "[uint16[]]": return Windows.Foundation.PropertyType.UInt16Array; + case "[int32[]]": return Windows.Foundation.PropertyType.Int32Array; + case "[uint32[]]": return Windows.Foundation.PropertyType.UInt32Array; + case "[int64[]]": return Windows.Foundation.PropertyType.Int64Array; + case "[uint64[]]": return Windows.Foundation.PropertyType.UInt64Array; + case "[single[]]": return Windows.Foundation.PropertyType.SingleArray; + case "[double[]]": return Windows.Foundation.PropertyType.DoubleArray; + case "[char[]]": return Windows.Foundation.PropertyType.Char16Array; + case "[bool[]]": return Windows.Foundation.PropertyType.BooleanArray; + case "[string[]]": return Windows.Foundation.PropertyType.StringArray; + case "[object[]]": return Windows.Foundation.PropertyType.InspectableArray; + case "[datetime[]]": return Windows.Foundation.PropertyType.DateTimeArray; + case "[datetimeoffset[]]": return Windows.Foundation.PropertyType.DateTimeArray; + case "[timespan[]]": return Windows.Foundation.PropertyType.TimeSpanArray; + case "[guid[]]": return Windows.Foundation.PropertyType.GuidArray; + + // Everything else will just be an object... + default: return Windows.Foundation.PropertyType.Inspectable; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/IPowerShellGet.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/IPowerShellGet.cs index b0e7ca9d49..7a8084a38d 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/IPowerShellGet.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/IPowerShellGet.cs @@ -1,94 +1,94 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers -{ - using System.Management.Automation; - using Microsoft.PowerShell.Commands; - using SemanticVersion = Microsoft.Management.Configuration.Processor.Helpers.SemanticVersion; - - /// - /// Interface for PowerShellGet cmdlets. - /// - internal interface IPowerShellGet - { - /// - /// Calls Find-Module. - /// - /// PowerShell instance. - /// Module name. - /// Optional version. - /// Optional min version. - /// Optional max version. - /// Optional repository. - /// Optional allow prerelease module. - /// Module info, null if not found. - PSObject? FindModule( - PowerShell pwsh, - string moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease); - - /// - /// Calls Find-DscResource. - /// - /// PowerShell instance. - /// resource name. - /// Optional module name. - /// Optional version. - /// Optional min version. - /// Optional max version. - /// Optional repository. - /// Optional allow prerelease module. - /// Dsc Resource info, null if not found. - PSObject? FindDscResource( - PowerShell pwsh, - string resourceName, - string? moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease); - - /// - /// Calls Save-Module with module specification. - /// - /// PowerShell instance. - /// Module specification. - /// Location to save module. - void SaveModule(PowerShell pwsh, ModuleSpecification moduleSpecification, string location); - - /// - /// Calls Save-Module -InputObject object -Path location. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// - /// PowerShell instance. - /// Input object. - /// Location to save module. - void SaveModule(PowerShell pwsh, PSObject inputObject, string location); - - /// - /// Calls Install-Module -InputObject object. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// - /// PowerShell instance. - /// Input object. - /// If to install to all users. - void InstallModule(PowerShell pwsh, PSObject inputObject, bool allUsers); - - /// - /// Calls Install-Module with a module specification. - /// - /// PowerShell instance. - /// Module specification. - /// If to install to all users. - void InstallModule(PowerShell pwsh, ModuleSpecification moduleSpecification, bool allUsers); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System.Management.Automation; + using Microsoft.PowerShell.Commands; + using SemanticVersion = Microsoft.Management.Configuration.Processor.Helpers.SemanticVersion; + + /// + /// Interface for PowerShellGet cmdlets. + /// + internal interface IPowerShellGet + { + /// + /// Calls Find-Module. + /// + /// PowerShell instance. + /// Module name. + /// Optional version. + /// Optional min version. + /// Optional max version. + /// Optional repository. + /// Optional allow prerelease module. + /// Module info, null if not found. + PSObject? FindModule( + PowerShell pwsh, + string moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease); + + /// + /// Calls Find-DscResource. + /// + /// PowerShell instance. + /// resource name. + /// Optional module name. + /// Optional version. + /// Optional min version. + /// Optional max version. + /// Optional repository. + /// Optional allow prerelease module. + /// Dsc Resource info, null if not found. + PSObject? FindDscResource( + PowerShell pwsh, + string resourceName, + string? moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease); + + /// + /// Calls Save-Module with module specification. + /// + /// PowerShell instance. + /// Module specification. + /// Location to save module. + void SaveModule(PowerShell pwsh, ModuleSpecification moduleSpecification, string location); + + /// + /// Calls Save-Module -InputObject object -Path location. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// + /// PowerShell instance. + /// Input object. + /// Location to save module. + void SaveModule(PowerShell pwsh, PSObject inputObject, string location); + + /// + /// Calls Install-Module -InputObject object. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// + /// PowerShell instance. + /// Input object. + /// If to install to all users. + void InstallModule(PowerShell pwsh, PSObject inputObject, bool allUsers); + + /// + /// Calls Install-Module with a module specification. + /// + /// PowerShell instance. + /// Module specification. + /// If to install to all users. + void InstallModule(PowerShell pwsh, ModuleSpecification moduleSpecification, bool allUsers); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellGetV2.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellGetV2.cs index 16beac9dd2..822f0bc534 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellGetV2.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellGetV2.cs @@ -1,250 +1,250 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers -{ - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using Microsoft.PowerShell.Commands; - using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; - using SemanticVersion = Microsoft.Management.Configuration.Processor.Helpers.SemanticVersion; - - /// - /// PowerShellGet implementation for 2.2.5 . - /// - internal class PowerShellGetV2 : IPowerShellGet - { - private const string AllUsers = "AllUsers"; - - /// - public PSObject? FindModule( - PowerShell pwsh, - string moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease) - { - bool implicitAllowPrerelease = false; - - var parameters = new Dictionary() - { - { Parameters.Name, moduleName }, - }; - - if (semanticVersion != null) - { - implicitAllowPrerelease |= semanticVersion.IsPrerelease; - parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); - } - - if (semanticMinVersion != null) - { - implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; - parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); - } - - if (semanticMaxVersion != null) - { - implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; - parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); - } - - if (!string.IsNullOrEmpty(repository)) - { - parameters.Add(Parameters.Repository, repository); - } - - if (allowPrerelease.HasValue || implicitAllowPrerelease) - { - // If explicit allowPrerelease = false don't use implicit. - bool allow = allowPrerelease ?? implicitAllowPrerelease; - parameters.Add(Parameters.AllowPrerelease, allow); - } - - pwsh.AddCommand(Commands.FindModule) - .AddParameters(parameters); - - return pwsh.Invoke().FirstOrDefault(); - } - - /// - public PSObject? FindDscResource( - PowerShell pwsh, - string resourceName, - string? moduleName, - SemanticVersion? semanticVersion, - SemanticVersion? semanticMinVersion, - SemanticVersion? semanticMaxVersion, - string? repository, - bool? allowPrerelease) - { - var parameters = new Dictionary() - { - { Parameters.Name, resourceName }, - }; - - bool implicitAllowPrerelease = false; - - if (!string.IsNullOrEmpty(moduleName)) - { - parameters.Add(Parameters.ModuleName, moduleName); - } - - if (semanticVersion != null) - { - implicitAllowPrerelease |= semanticVersion.IsPrerelease; - parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); - } - - if (semanticMinVersion != null) - { - implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; - parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); - } - - if (semanticMaxVersion != null) - { - implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; - parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); - } - - if (!string.IsNullOrEmpty(repository)) - { - parameters.Add(Parameters.Repository, repository); - } - - if (allowPrerelease.HasValue || implicitAllowPrerelease) - { - // If explicit allowPrerelease = false don't use implicit. - bool allow = allowPrerelease ?? implicitAllowPrerelease; - parameters.Add(Parameters.AllowPrerelease, allow); - } - - pwsh.AddCommand(Commands.FindDscResource) - .AddParameters(parameters); - - // The result is just a PSCustomObject with a type name of Microsoft.PowerShell.Commands.PSGetDscResourceInfo. - // When no module is passed and a resource is not found, this will return an empty list. If a module - // is specified and no resource is found then it will fail earlier because of a Write-Error. - return pwsh.Invoke().FirstOrDefault(); - } - - /// - public void SaveModule( - PowerShell pwsh, - ModuleSpecification moduleSpecification, - string location) - { - var parameters = new Dictionary() - { - { Parameters.Name, moduleSpecification.Name }, - { Parameters.Path, location }, - }; - - if (moduleSpecification.Version is not null) - { - parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); - } - - if (moduleSpecification.MaximumVersion is not null) - { - parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); - } - - if (moduleSpecification.RequiredVersion is not null) - { - parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); - } - - _ = pwsh.AddCommand(Commands.SaveModule) - .AddParameters(parameters) - .AddParameter(Parameters.Force) - .Invoke(); - } - - /// - public void SaveModule( - PowerShell pwsh, - PSObject inputObject, - string location) - { - _ = pwsh.AddCommand(Commands.SaveModule) - .AddParameter(Parameters.Path, location) - .AddParameter(Parameters.InputObject, inputObject) - .AddParameter(Parameters.Force) - .Invoke(); - } - - /// - public void InstallModule( - PowerShell pwsh, - PSObject inputObject, - bool allUsers) - { - var parameters = new Dictionary() - { - { Parameters.InputObject, inputObject }, - }; - - if (allUsers) - { - parameters.Add(Parameters.Scope, AllUsers); - } - - // If the repository is untrusted, it will fail with: - // Microsoft.PowerShell.Commands.WriteErrorException : Exception calling "ShouldContinue" with "5" - // argument(s): "A command that prompts the user failed because the host program or the command type - // does not support user interaction. - // If its trusted, PowerShellGets adds the Force parameter to the call to PackageManager\Install-Package. - // TODO: Once we have policies, we should remove Force. For hosted environments and depending - // on the policy we will trust PSGallery when we create the Runspace or add Force here. - _ = pwsh.AddCommand(Commands.InstallModule) - .AddParameters(parameters) - .AddParameter(Parameters.Force) - .Invoke(); - } - - /// - public void InstallModule( - PowerShell pwsh, - ModuleSpecification moduleSpecification, - bool allUsers) - { - var parameters = new Dictionary() - { - { Parameters.Name, moduleSpecification.Name }, - }; - - if (moduleSpecification.Version is not null) - { - parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); - } - - if (moduleSpecification.MaximumVersion is not null) - { - parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); - } - - if (moduleSpecification.RequiredVersion is not null) - { - parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); - } - - if (allUsers) - { - parameters.Add(Parameters.Scope, AllUsers); - } - - _ = pwsh.AddCommand(Commands.InstallModule) - .AddParameters(parameters) - .AddParameter(Parameters.Force) - .Invoke(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using Microsoft.PowerShell.Commands; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + using SemanticVersion = Microsoft.Management.Configuration.Processor.Helpers.SemanticVersion; + + /// + /// PowerShellGet implementation for 2.2.5 . + /// + internal class PowerShellGetV2 : IPowerShellGet + { + private const string AllUsers = "AllUsers"; + + /// + public PSObject? FindModule( + PowerShell pwsh, + string moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease) + { + bool implicitAllowPrerelease = false; + + var parameters = new Dictionary() + { + { Parameters.Name, moduleName }, + }; + + if (semanticVersion != null) + { + implicitAllowPrerelease |= semanticVersion.IsPrerelease; + parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); + } + + if (semanticMinVersion != null) + { + implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; + parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); + } + + if (semanticMaxVersion != null) + { + implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; + parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); + } + + if (!string.IsNullOrEmpty(repository)) + { + parameters.Add(Parameters.Repository, repository); + } + + if (allowPrerelease.HasValue || implicitAllowPrerelease) + { + // If explicit allowPrerelease = false don't use implicit. + bool allow = allowPrerelease ?? implicitAllowPrerelease; + parameters.Add(Parameters.AllowPrerelease, allow); + } + + pwsh.AddCommand(Commands.FindModule) + .AddParameters(parameters); + + return pwsh.Invoke().FirstOrDefault(); + } + + /// + public PSObject? FindDscResource( + PowerShell pwsh, + string resourceName, + string? moduleName, + SemanticVersion? semanticVersion, + SemanticVersion? semanticMinVersion, + SemanticVersion? semanticMaxVersion, + string? repository, + bool? allowPrerelease) + { + var parameters = new Dictionary() + { + { Parameters.Name, resourceName }, + }; + + bool implicitAllowPrerelease = false; + + if (!string.IsNullOrEmpty(moduleName)) + { + parameters.Add(Parameters.ModuleName, moduleName); + } + + if (semanticVersion != null) + { + implicitAllowPrerelease |= semanticVersion.IsPrerelease; + parameters.Add(Parameters.RequiredVersion, semanticVersion.ToString()); + } + + if (semanticMinVersion != null) + { + implicitAllowPrerelease |= semanticMinVersion.IsPrerelease; + parameters.Add(Parameters.MinimumVersion, semanticMinVersion.ToString()); + } + + if (semanticMaxVersion != null) + { + implicitAllowPrerelease |= semanticMaxVersion.IsPrerelease; + parameters.Add(Parameters.MaximumVersion, semanticMaxVersion.ToString()); + } + + if (!string.IsNullOrEmpty(repository)) + { + parameters.Add(Parameters.Repository, repository); + } + + if (allowPrerelease.HasValue || implicitAllowPrerelease) + { + // If explicit allowPrerelease = false don't use implicit. + bool allow = allowPrerelease ?? implicitAllowPrerelease; + parameters.Add(Parameters.AllowPrerelease, allow); + } + + pwsh.AddCommand(Commands.FindDscResource) + .AddParameters(parameters); + + // The result is just a PSCustomObject with a type name of Microsoft.PowerShell.Commands.PSGetDscResourceInfo. + // When no module is passed and a resource is not found, this will return an empty list. If a module + // is specified and no resource is found then it will fail earlier because of a Write-Error. + return pwsh.Invoke().FirstOrDefault(); + } + + /// + public void SaveModule( + PowerShell pwsh, + ModuleSpecification moduleSpecification, + string location) + { + var parameters = new Dictionary() + { + { Parameters.Name, moduleSpecification.Name }, + { Parameters.Path, location }, + }; + + if (moduleSpecification.Version is not null) + { + parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); + } + + if (moduleSpecification.MaximumVersion is not null) + { + parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); + } + + if (moduleSpecification.RequiredVersion is not null) + { + parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); + } + + _ = pwsh.AddCommand(Commands.SaveModule) + .AddParameters(parameters) + .AddParameter(Parameters.Force) + .Invoke(); + } + + /// + public void SaveModule( + PowerShell pwsh, + PSObject inputObject, + string location) + { + _ = pwsh.AddCommand(Commands.SaveModule) + .AddParameter(Parameters.Path, location) + .AddParameter(Parameters.InputObject, inputObject) + .AddParameter(Parameters.Force) + .Invoke(); + } + + /// + public void InstallModule( + PowerShell pwsh, + PSObject inputObject, + bool allUsers) + { + var parameters = new Dictionary() + { + { Parameters.InputObject, inputObject }, + }; + + if (allUsers) + { + parameters.Add(Parameters.Scope, AllUsers); + } + + // If the repository is untrusted, it will fail with: + // Microsoft.PowerShell.Commands.WriteErrorException : Exception calling "ShouldContinue" with "5" + // argument(s): "A command that prompts the user failed because the host program or the command type + // does not support user interaction. + // If its trusted, PowerShellGets adds the Force parameter to the call to PackageManager\Install-Package. + // TODO: Once we have policies, we should remove Force. For hosted environments and depending + // on the policy we will trust PSGallery when we create the Runspace or add Force here. + _ = pwsh.AddCommand(Commands.InstallModule) + .AddParameters(parameters) + .AddParameter(Parameters.Force) + .Invoke(); + } + + /// + public void InstallModule( + PowerShell pwsh, + ModuleSpecification moduleSpecification, + bool allUsers) + { + var parameters = new Dictionary() + { + { Parameters.Name, moduleSpecification.Name }, + }; + + if (moduleSpecification.Version is not null) + { + parameters.Add(Parameters.MinimumVersion, moduleSpecification.Version); + } + + if (moduleSpecification.MaximumVersion is not null) + { + parameters.Add(Parameters.MaximumVersion, moduleSpecification.MaximumVersion); + } + + if (moduleSpecification.RequiredVersion is not null) + { + parameters.Add(Parameters.RequiredVersion, moduleSpecification.RequiredVersion); + } + + if (allUsers) + { + parameters.Add(Parameters.Scope, AllUsers); + } + + _ = pwsh.AddCommand(Commands.InstallModule) + .AddParameters(parameters) + .AddParameter(Parameters.Force) + .Invoke(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellHelpers.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellHelpers.cs index 9a24c55ce7..2aaab4ea47 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellHelpers.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Helpers/PowerShellHelpers.cs @@ -1,83 +1,83 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers -{ - using System.Collections; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.PowerShell.Commands; - using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; - - /// - /// General PowerShell helpers. - /// - internal static class PowerShellHelpers - { - /// - /// Creates a module specification object. - /// - /// Module name. - /// Optional version. - /// Optional min version. - /// Optional max version. - /// Optional guid. - /// ModuleSpecification. - public static ModuleSpecification CreateModuleSpecification( - string moduleName, - string? version = null, - string? minVersion = null, - string? maxVersion = null, - string? guid = null) - { - if (version is null && - minVersion is null && - maxVersion is null) - { - // Otherwise will fail with MissingMemberException... - return new ModuleSpecification(moduleName); - } - - var moduleInfo = new Hashtable - { - { Parameters.ModuleName, moduleName }, - }; - - if (!string.IsNullOrEmpty(version)) - { - var semanticVersion = new SemanticVersion(version); - moduleInfo.Add(Parameters.RequiredVersion, semanticVersion.Version); - } - - if (!string.IsNullOrEmpty(minVersion)) - { - var semanticVersion = new SemanticVersion(minVersion); - moduleInfo.Add(Parameters.ModuleVersion, semanticVersion.Version); - } - - if (!string.IsNullOrEmpty(maxVersion)) - { - var semanticVersion = new SemanticVersion(maxVersion); - - // For some reason, the constructor of ModuleSpecification that takes - // a hashtable calls ModuleCmdletBase.GetMaximumVersion. This method will - // validate the max version and replace * for 999999999 only if its the last - // char in the string. But then the returned value is not assigned to the - // ModuleSpecification's MaximumVersion property. If we want to set a - // MaximumVersion with a wildcard and pass this to Install-Module it will - // fail with "Cannot convert value 'x.*' to type 'System.Version'." - moduleInfo.Add(Parameters.MaximumVersion, semanticVersion.Version.ToString()); - } - - if (!string.IsNullOrEmpty(guid)) - { - moduleInfo.Add(Parameters.Guid, guid); - } - - // Using the Hashtable constructor will verify that RequiredVersion is used properly. - return new ModuleSpecification(moduleInfo); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Helpers +{ + using System.Collections; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.PowerShell.Commands; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// + /// General PowerShell helpers. + /// + internal static class PowerShellHelpers + { + /// + /// Creates a module specification object. + /// + /// Module name. + /// Optional version. + /// Optional min version. + /// Optional max version. + /// Optional guid. + /// ModuleSpecification. + public static ModuleSpecification CreateModuleSpecification( + string moduleName, + string? version = null, + string? minVersion = null, + string? maxVersion = null, + string? guid = null) + { + if (version is null && + minVersion is null && + maxVersion is null) + { + // Otherwise will fail with MissingMemberException... + return new ModuleSpecification(moduleName); + } + + var moduleInfo = new Hashtable + { + { Parameters.ModuleName, moduleName }, + }; + + if (!string.IsNullOrEmpty(version)) + { + var semanticVersion = new SemanticVersion(version); + moduleInfo.Add(Parameters.RequiredVersion, semanticVersion.Version); + } + + if (!string.IsNullOrEmpty(minVersion)) + { + var semanticVersion = new SemanticVersion(minVersion); + moduleInfo.Add(Parameters.ModuleVersion, semanticVersion.Version); + } + + if (!string.IsNullOrEmpty(maxVersion)) + { + var semanticVersion = new SemanticVersion(maxVersion); + + // For some reason, the constructor of ModuleSpecification that takes + // a hashtable calls ModuleCmdletBase.GetMaximumVersion. This method will + // validate the max version and replace * for 999999999 only if its the last + // char in the string. But then the returned value is not assigned to the + // ModuleSpecification's MaximumVersion property. If we want to set a + // MaximumVersion with a wildcard and pass this to Install-Module it will + // fail with "Cannot convert value 'x.*' to type 'System.Version'." + moduleInfo.Add(Parameters.MaximumVersion, semanticVersion.Version.ToString()); + } + + if (!string.IsNullOrEmpty(guid)) + { + moduleInfo.Add(Parameters.Guid, guid); + } + + // Using the Hashtable constructor will verify that RequiredVersion is used properly. + return new ModuleSpecification(moduleInfo); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/HostedEnvironment.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/HostedEnvironment.cs index 93181645d0..c56b6242d0 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/HostedEnvironment.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/HostedEnvironment.cs @@ -1,555 +1,555 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Runspaces -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using System.Runtime.InteropServices.WindowsRuntime; - using Microsoft.Management.Configuration.Processor.Constants; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - using Windows.Security.Cryptography.Certificates; - using Windows.Storage.Streams; - using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; - - /// - /// Process environment. Provides interaction with PowerShell for a hosted environment. - /// - internal class HostedEnvironment : IProcessorEnvironment - { - private readonly PowerShellConfigurationProcessorType type; - private readonly IPowerShellGet powerShellGet; - - private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.CurrentUser; - private string? customLocation; - - /// - /// Initializes a new instance of the class. - /// - /// PowerShell Runspace. - /// Configuration processor type. - /// IDscModule. - public HostedEnvironment( - Runspace runspace, - PowerShellConfigurationProcessorType type, - IDscModule dscModule) - { - this.Runspace = runspace; - this.type = type; - this.DscModule = dscModule; - - // TODO: once v3 is release implement v3 version. - this.powerShellGet = new PowerShellGetV2(); - } - - /// - public Runspace Runspace { get; } - - /// - /// Gets the DscModule. - /// - internal IDscModule DscModule { get; } - - /// - /// Gets or initializes the set processor factory. - /// - internal PowerShellConfigurationSetProcessorFactory? SetProcessorFactory { get; init; } - - /// - public void ValidateRunspace() - { - // Only support PowerShell Core. - if (this.GetVariable(Variables.PSEdition) != Core) - { - throw new NotSupportedException("Only PowerShell Core is supported."); - } - - // If opening a runspace has failures, like one of the modules in ImportPSModule is not found, it won't throw but - // write to the error output. This is not a fatal error, since we install PSDesiredStateConfiguration - // module if not found, so unless there's a real reason keep it in verbose. - var errors = this.GetVariable(Variables.Error); - if (errors.Count > 0) - { - this.OnDiagnostics( - DiagnosticLevel.Verbose, - $"Error creating runspace '{string.Join("\n", errors.Cast().ToArray())}'"); - } - - var powerShellGet = PowerShellHelpers.CreateModuleSpecification( - Modules.PowerShellGet, - minVersion: Modules.PowerShellGetMinVersion); - if (!this.ValidateModule(powerShellGet)) - { - var previousVersion = this.GetAvailableModule( - PowerShellHelpers.CreateModuleSpecification( - Modules.PowerShellGet)); - string message = $"Required '{powerShellGet}'"; - if (previousVersion is not null) - { - message += $" Found '{previousVersion.Name} {previousVersion.Version}'"; - } - - throw new NotSupportedException(message); - } - - // Make sure PSDesiredConfiguration is present. - this.InstallModule(this.DscModule.ModuleSpecification); - } - - /// - public IReadOnlyList GetAllDscResources() - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var results = this.DscModule.GetAllDscResources(pwsh); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return results; - } - - /// - public IReadOnlyList GetDscResourcesInModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var results = this.DscModule.GetDscResourcesInModule(pwsh, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return results; - } - - /// - public DscResourceInfoInternal? GetDscResource(ConfigurationUnitAndModule unitInternal) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.GetDscResource(pwsh, unitInternal.ResourceName, unitInternal.Module); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// - public ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.InvokeGetResource(pwsh, settings, name, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// - public bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.InvokeTestResource(pwsh, settings, name, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// - public bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var result = this.DscModule.InvokeSetResource(pwsh, settings, name, moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// - public PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var moduleInfo = pwsh.AddCommand(Commands.GetModule) - .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) - .Invoke() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return moduleInfo; - } - - /// - public PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var moduleInfo = pwsh.AddCommand(Commands.GetModule) - .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) - .AddParameter(Parameters.ListAvailable) - .Invoke() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return moduleInfo; - } - - /// - public PSModuleInfo? GetAvailableModule(string path) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var moduleInfo = pwsh.AddCommand(Commands.GetModule) - .AddParameter(Parameters.Name, path) - .AddParameter(Parameters.ListAvailable) - .Invoke() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return moduleInfo; - } - - /// - public void ImportModule(ModuleSpecification moduleSpecification) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - _ = pwsh.AddCommand(Commands.ImportModule) - .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) - .Invoke(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// - public void ImportModule(string path) - { - if (!File.Exists(path)) - { - throw new FileNotFoundException(path); - } - - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - _ = pwsh.AddCommand(Commands.ImportModule) - .AddParameter(Parameters.Name, path) - .Invoke(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// - public PSObject? GetInstalledModule(ModuleSpecification moduleSpecification) - { - // Instead of Get-InstalledModule, we look for PSGetModuleInfo.xml and serialize it - // if found. This allow us to get the information from Install-Module and Save-Module. - var module = this.GetAvailableModule(moduleSpecification); - if (module is null) - { - return null; - } - - var getModuleInfoFile = Path.Combine(module.ModuleBase, "PSGetModuleInfo.xml"); - if (!File.Exists(getModuleInfoFile)) - { - // Keep Get-InstalledModule behaviour. - return null; - } - - using PowerShell pwsh = PowerShell.Create(this.Runspace); - var installedModule = pwsh.AddCommand(Commands.ImportCliXml) - .AddParameter(Parameters.Path, getModuleInfoFile) - .Invoke() - .FirstOrDefault(); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return installedModule; - } - - /// - public PSObject? FindModule(ConfigurationUnitInternal unitInternal) - { - // Don't use ModuleSpecification here. Each parameter is independent and - // we need version even if a module was not specified. - string? moduleName = unitInternal.GetDirective(DirectiveConstants.Module); - - if (string.IsNullOrEmpty(moduleName)) - { - return null; - } - - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var result = this.powerShellGet.FindModule( - pwsh, - moduleName, - unitInternal.GetSemanticVersion(), - unitInternal.GetSemanticMinVersion(), - unitInternal.GetSemanticMaxVersion(), - unitInternal.GetDirective(DirectiveConstants.Repository), - unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// - public PSObject? FindDscResource(ConfigurationUnitInternal unitInternal) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var result = this.powerShellGet.FindDscResource( - pwsh, - unitInternal.ResourceName, - unitInternal.GetDirective(DirectiveConstants.Module), - unitInternal.GetSemanticVersion(), - unitInternal.GetSemanticMinVersion(), - unitInternal.GetSemanticMaxVersion(), - unitInternal.GetDirective(DirectiveConstants.Repository), - unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return result; - } - - /// - public void SaveModule(PSObject inputObject, string location) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.SaveModule(pwsh, inputObject, location); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// - public void SaveModule(ModuleSpecification moduleSpecification, string location) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.SaveModule(pwsh, moduleSpecification, location); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - /// - public void InstallModule(PSObject inputObject) - { - if (this.location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(this.customLocation)) - { - throw new ArgumentNullException(nameof(this.customLocation)); - } - - this.SaveModule(inputObject, this.customLocation); - } - else - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.InstallModule(pwsh, inputObject, this.location == PowerShellConfigurationProcessorLocation.AllUsers); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - } - - /// - public void InstallModule(ModuleSpecification moduleSpecification) - { - // Maybe is already there. - if (!this.ValidateModule(moduleSpecification)) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Installing module: {moduleSpecification.Name} ..."); - - // Ok, we have to get it. - if (this.location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(this.customLocation)) - { - throw new ArgumentNullException(nameof(this.customLocation)); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling save module ..."); - this.SaveModule(moduleSpecification, this.customLocation); - } - else - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling install module ..."); - using PowerShell pwsh = PowerShell.Create(this.Runspace); - this.powerShellGet.InstallModule(pwsh, moduleSpecification, this.location == PowerShellConfigurationProcessorLocation.AllUsers); - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module installed."); - } - } - - /// - public List GetCertsOfValidSignedFiles(string[] paths) - { - using PowerShell pwsh = PowerShell.Create(this.Runspace); - - var signatures = pwsh.AddCommand(Commands.GetChildItem) - .AddParameter(Parameters.Path, paths) - .AddCommand(Commands.GetAuthenticodeSignature) - .Invoke(); - - var thumbprint = new HashSet(); - var certificates = new List(); - foreach (var signature in signatures) - { - if (signature.Status == SignatureStatus.Valid) - { - if (thumbprint.Add(signature.SignerCertificate.Thumbprint)) - { - IBuffer buffer = signature.SignerCertificate.GetRawCertData().AsBuffer(); - certificates.Add(new Certificate(buffer)); - } - } - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); - return certificates; - } - - /// - public TType GetVariable(string name) - { - return (TType)this.Runspace.SessionStateProxy.PSVariable.GetValue(name); - } - - /// - public void SetVariable(string name, object value) - { - this.Runspace.SessionStateProxy.PSVariable.Set(name, value); - } - - /// - public void SetPSModulePath(string path) - { - this.SetVariable(Variables.PSModulePath, path); - } - - /// - public void SetPSModulePaths(IReadOnlyList paths) - { - this.SetVariable(Variables.PSModulePath, string.Join(";", paths)); - } - - /// - public void PrependPSModulePath(string path) - { - var oldModulePath = this.GetModulePaths(); - if (!oldModulePath.Contains(path)) - { - this.SetPSModulePath($"{path};{string.Join(";", oldModulePath)}"); - } - } - - /// - public void PrependPSModulePaths(IReadOnlyList paths) - { - var newPaths = paths.ToList(); - var oldModulePath = this.GetModulePaths(); - foreach (var newPath in paths) - { - if (oldModulePath.Contains(newPath)) - { - newPaths.Remove(newPath); - } - } - - if (newPaths.Any()) - { - this.SetPSModulePath($"{string.Join(";", newPaths)};{string.Join(";", oldModulePath)}"); - } - } - - /// - public void AppendPSModulePath(string path) - { - var oldModulePath = this.GetModulePaths(); - if (!oldModulePath.Contains(path)) - { - this.SetPSModulePath($"{string.Join(";", oldModulePath)};{path}"); - } - } - - /// - public void AppendPSModulePaths(IReadOnlyList paths) - { - var newPaths = paths.ToList(); - var oldModulePath = this.GetModulePaths(); - foreach (var newPath in paths) - { - if (oldModulePath.Contains(newPath)) - { - newPaths.Remove(newPath); - } - } - - if (newPaths.Any()) - { - this.SetPSModulePath($"{string.Join(";", oldModulePath)};{string.Join(";", newPaths)}"); - } - } - - /// - public void CleanupPSModulePath(string path) - { - string newModulePath = this.GetVariable(Variables.PSModulePath) - .Replace($"{path};", null) - .Replace($";{path}", null); - - this.SetPSModulePath(newModulePath); - } - - /// - public void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation) - { - this.location = location; - if (this.location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(customLocation)) - { - throw new ArgumentNullException(nameof(customLocation)); - } - - this.customLocation = customLocation; - } - } - - private bool ValidateModule(ModuleSpecification moduleSpecification) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Validating module: {moduleSpecification.Name} ..."); - - var loadedModule = this.GetImportedModule(moduleSpecification); - if (loadedModule is not null) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is already imported."); - return true; - } - - var availableModule = this.GetAvailableModule(moduleSpecification); - if (availableModule is not null) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is available, importing ..."); - this.ImportModule(moduleSpecification); - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module imported."); - return true; - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module not found."); - return false; - } - - private void OnDiagnostics(DiagnosticLevel level, PowerShell pwsh) - { - this.SetProcessorFactory?.OnDiagnostics(level, pwsh); - } - - private void OnDiagnostics(DiagnosticLevel level, string message) - { - this.SetProcessorFactory?.OnDiagnostics(level, message); - } - - private HashSet GetModulePaths() - { - return this.GetVariable(Variables.PSModulePath).Split(";").ToHashSet(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Runspaces +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Runtime.InteropServices.WindowsRuntime; + using Microsoft.Management.Configuration.Processor.Constants; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + using Windows.Security.Cryptography.Certificates; + using Windows.Storage.Streams; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// + /// Process environment. Provides interaction with PowerShell for a hosted environment. + /// + internal class HostedEnvironment : IProcessorEnvironment + { + private readonly PowerShellConfigurationProcessorType type; + private readonly IPowerShellGet powerShellGet; + + private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.CurrentUser; + private string? customLocation; + + /// + /// Initializes a new instance of the class. + /// + /// PowerShell Runspace. + /// Configuration processor type. + /// IDscModule. + public HostedEnvironment( + Runspace runspace, + PowerShellConfigurationProcessorType type, + IDscModule dscModule) + { + this.Runspace = runspace; + this.type = type; + this.DscModule = dscModule; + + // TODO: once v3 is release implement v3 version. + this.powerShellGet = new PowerShellGetV2(); + } + + /// + public Runspace Runspace { get; } + + /// + /// Gets the DscModule. + /// + internal IDscModule DscModule { get; } + + /// + /// Gets or initializes the set processor factory. + /// + internal PowerShellConfigurationSetProcessorFactory? SetProcessorFactory { get; init; } + + /// + public void ValidateRunspace() + { + // Only support PowerShell Core. + if (this.GetVariable(Variables.PSEdition) != Core) + { + throw new NotSupportedException("Only PowerShell Core is supported."); + } + + // If opening a runspace has failures, like one of the modules in ImportPSModule is not found, it won't throw but + // write to the error output. This is not a fatal error, since we install PSDesiredStateConfiguration + // module if not found, so unless there's a real reason keep it in verbose. + var errors = this.GetVariable(Variables.Error); + if (errors.Count > 0) + { + this.OnDiagnostics( + DiagnosticLevel.Verbose, + $"Error creating runspace '{string.Join("\n", errors.Cast().ToArray())}'"); + } + + var powerShellGet = PowerShellHelpers.CreateModuleSpecification( + Modules.PowerShellGet, + minVersion: Modules.PowerShellGetMinVersion); + if (!this.ValidateModule(powerShellGet)) + { + var previousVersion = this.GetAvailableModule( + PowerShellHelpers.CreateModuleSpecification( + Modules.PowerShellGet)); + string message = $"Required '{powerShellGet}'"; + if (previousVersion is not null) + { + message += $" Found '{previousVersion.Name} {previousVersion.Version}'"; + } + + throw new NotSupportedException(message); + } + + // Make sure PSDesiredConfiguration is present. + this.InstallModule(this.DscModule.ModuleSpecification); + } + + /// + public IReadOnlyList GetAllDscResources() + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var results = this.DscModule.GetAllDscResources(pwsh); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return results; + } + + /// + public IReadOnlyList GetDscResourcesInModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var results = this.DscModule.GetDscResourcesInModule(pwsh, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return results; + } + + /// + public DscResourceInfoInternal? GetDscResource(ConfigurationUnitAndModule unitInternal) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.GetDscResource(pwsh, unitInternal.ResourceName, unitInternal.Module); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// + public ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.InvokeGetResource(pwsh, settings, name, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// + public bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.InvokeTestResource(pwsh, settings, name, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// + public bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var result = this.DscModule.InvokeSetResource(pwsh, settings, name, moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// + public PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var moduleInfo = pwsh.AddCommand(Commands.GetModule) + .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) + .Invoke() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return moduleInfo; + } + + /// + public PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var moduleInfo = pwsh.AddCommand(Commands.GetModule) + .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) + .AddParameter(Parameters.ListAvailable) + .Invoke() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return moduleInfo; + } + + /// + public PSModuleInfo? GetAvailableModule(string path) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var moduleInfo = pwsh.AddCommand(Commands.GetModule) + .AddParameter(Parameters.Name, path) + .AddParameter(Parameters.ListAvailable) + .Invoke() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return moduleInfo; + } + + /// + public void ImportModule(ModuleSpecification moduleSpecification) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + _ = pwsh.AddCommand(Commands.ImportModule) + .AddParameter(Parameters.FullyQualifiedName, moduleSpecification) + .Invoke(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// + public void ImportModule(string path) + { + if (!File.Exists(path)) + { + throw new FileNotFoundException(path); + } + + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + _ = pwsh.AddCommand(Commands.ImportModule) + .AddParameter(Parameters.Name, path) + .Invoke(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// + public PSObject? GetInstalledModule(ModuleSpecification moduleSpecification) + { + // Instead of Get-InstalledModule, we look for PSGetModuleInfo.xml and serialize it + // if found. This allow us to get the information from Install-Module and Save-Module. + var module = this.GetAvailableModule(moduleSpecification); + if (module is null) + { + return null; + } + + var getModuleInfoFile = Path.Combine(module.ModuleBase, "PSGetModuleInfo.xml"); + if (!File.Exists(getModuleInfoFile)) + { + // Keep Get-InstalledModule behaviour. + return null; + } + + using PowerShell pwsh = PowerShell.Create(this.Runspace); + var installedModule = pwsh.AddCommand(Commands.ImportCliXml) + .AddParameter(Parameters.Path, getModuleInfoFile) + .Invoke() + .FirstOrDefault(); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return installedModule; + } + + /// + public PSObject? FindModule(ConfigurationUnitInternal unitInternal) + { + // Don't use ModuleSpecification here. Each parameter is independent and + // we need version even if a module was not specified. + string? moduleName = unitInternal.GetDirective(DirectiveConstants.Module); + + if (string.IsNullOrEmpty(moduleName)) + { + return null; + } + + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var result = this.powerShellGet.FindModule( + pwsh, + moduleName, + unitInternal.GetSemanticVersion(), + unitInternal.GetSemanticMinVersion(), + unitInternal.GetSemanticMaxVersion(), + unitInternal.GetDirective(DirectiveConstants.Repository), + unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// + public PSObject? FindDscResource(ConfigurationUnitInternal unitInternal) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var result = this.powerShellGet.FindDscResource( + pwsh, + unitInternal.ResourceName, + unitInternal.GetDirective(DirectiveConstants.Module), + unitInternal.GetSemanticVersion(), + unitInternal.GetSemanticMinVersion(), + unitInternal.GetSemanticMaxVersion(), + unitInternal.GetDirective(DirectiveConstants.Repository), + unitInternal.GetDirective(DirectiveConstants.AllowPrerelease)); + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return result; + } + + /// + public void SaveModule(PSObject inputObject, string location) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.SaveModule(pwsh, inputObject, location); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// + public void SaveModule(ModuleSpecification moduleSpecification, string location) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.SaveModule(pwsh, moduleSpecification, location); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + /// + public void InstallModule(PSObject inputObject) + { + if (this.location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(this.customLocation)) + { + throw new ArgumentNullException(nameof(this.customLocation)); + } + + this.SaveModule(inputObject, this.customLocation); + } + else + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.InstallModule(pwsh, inputObject, this.location == PowerShellConfigurationProcessorLocation.AllUsers); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + } + + /// + public void InstallModule(ModuleSpecification moduleSpecification) + { + // Maybe is already there. + if (!this.ValidateModule(moduleSpecification)) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Installing module: {moduleSpecification.Name} ..."); + + // Ok, we have to get it. + if (this.location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(this.customLocation)) + { + throw new ArgumentNullException(nameof(this.customLocation)); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling save module ..."); + this.SaveModule(moduleSpecification, this.customLocation); + } + else + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... calling install module ..."); + using PowerShell pwsh = PowerShell.Create(this.Runspace); + this.powerShellGet.InstallModule(pwsh, moduleSpecification, this.location == PowerShellConfigurationProcessorLocation.AllUsers); + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module installed."); + } + } + + /// + public List GetCertsOfValidSignedFiles(string[] paths) + { + using PowerShell pwsh = PowerShell.Create(this.Runspace); + + var signatures = pwsh.AddCommand(Commands.GetChildItem) + .AddParameter(Parameters.Path, paths) + .AddCommand(Commands.GetAuthenticodeSignature) + .Invoke(); + + var thumbprint = new HashSet(); + var certificates = new List(); + foreach (var signature in signatures) + { + if (signature.Status == SignatureStatus.Valid) + { + if (thumbprint.Add(signature.SignerCertificate.Thumbprint)) + { + IBuffer buffer = signature.SignerCertificate.GetRawCertData().AsBuffer(); + certificates.Add(new Certificate(buffer)); + } + } + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, pwsh); + return certificates; + } + + /// + public TType GetVariable(string name) + { + return (TType)this.Runspace.SessionStateProxy.PSVariable.GetValue(name); + } + + /// + public void SetVariable(string name, object value) + { + this.Runspace.SessionStateProxy.PSVariable.Set(name, value); + } + + /// + public void SetPSModulePath(string path) + { + this.SetVariable(Variables.PSModulePath, path); + } + + /// + public void SetPSModulePaths(IReadOnlyList paths) + { + this.SetVariable(Variables.PSModulePath, string.Join(";", paths)); + } + + /// + public void PrependPSModulePath(string path) + { + var oldModulePath = this.GetModulePaths(); + if (!oldModulePath.Contains(path)) + { + this.SetPSModulePath($"{path};{string.Join(";", oldModulePath)}"); + } + } + + /// + public void PrependPSModulePaths(IReadOnlyList paths) + { + var newPaths = paths.ToList(); + var oldModulePath = this.GetModulePaths(); + foreach (var newPath in paths) + { + if (oldModulePath.Contains(newPath)) + { + newPaths.Remove(newPath); + } + } + + if (newPaths.Any()) + { + this.SetPSModulePath($"{string.Join(";", newPaths)};{string.Join(";", oldModulePath)}"); + } + } + + /// + public void AppendPSModulePath(string path) + { + var oldModulePath = this.GetModulePaths(); + if (!oldModulePath.Contains(path)) + { + this.SetPSModulePath($"{string.Join(";", oldModulePath)};{path}"); + } + } + + /// + public void AppendPSModulePaths(IReadOnlyList paths) + { + var newPaths = paths.ToList(); + var oldModulePath = this.GetModulePaths(); + foreach (var newPath in paths) + { + if (oldModulePath.Contains(newPath)) + { + newPaths.Remove(newPath); + } + } + + if (newPaths.Any()) + { + this.SetPSModulePath($"{string.Join(";", oldModulePath)};{string.Join(";", newPaths)}"); + } + } + + /// + public void CleanupPSModulePath(string path) + { + string newModulePath = this.GetVariable(Variables.PSModulePath) + .Replace($"{path};", null) + .Replace($";{path}", null); + + this.SetPSModulePath(newModulePath); + } + + /// + public void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation) + { + this.location = location; + if (this.location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(customLocation)) + { + throw new ArgumentNullException(nameof(customLocation)); + } + + this.customLocation = customLocation; + } + } + + private bool ValidateModule(ModuleSpecification moduleSpecification) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Validating module: {moduleSpecification.Name} ..."); + + var loadedModule = this.GetImportedModule(moduleSpecification); + if (loadedModule is not null) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is already imported."); + return true; + } + + var availableModule = this.GetAvailableModule(moduleSpecification); + if (availableModule is not null) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module is available, importing ..."); + this.ImportModule(moduleSpecification); + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module imported."); + return true; + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $" ... module not found."); + return false; + } + + private void OnDiagnostics(DiagnosticLevel level, PowerShell pwsh) + { + this.SetProcessorFactory?.OnDiagnostics(level, pwsh); + } + + private void OnDiagnostics(DiagnosticLevel level, string message) + { + this.SetProcessorFactory?.OnDiagnostics(level, message); + } + + private HashSet GetModulePaths() + { + return this.GetVariable(Variables.PSModulePath).Split(";").ToHashSet(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/IProcessorEnvironment.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/IProcessorEnvironment.cs index 7c27b5731f..708e2a493f 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/IProcessorEnvironment.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/IProcessorEnvironment.cs @@ -1,233 +1,233 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments -{ - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - using Windows.Security.Cryptography.Certificates; - - /// - /// IProcessorEnvironment. Provides interaction with PowerShell. - /// - internal interface IProcessorEnvironment - { - /// - /// Gets the runspace. - /// - System.Management.Automation.Runspaces.Runspace Runspace { get; } - - /// - /// Validates the runspace. - /// - void ValidateRunspace(); - - /// - /// Gets all DSC resource. - /// - /// A list with the DSC resource. - IReadOnlyList GetAllDscResources(); - - /// - /// Gets all resources in a module. - /// - /// Module specification. - /// List of resources. - IReadOnlyList GetDscResourcesInModule(ModuleSpecification moduleSpecification); - - /// - /// Gets a DSC Resource. - /// - /// Configuration unit internal. - /// DSC Resource. - DscResourceInfoInternal? GetDscResource(ConfigurationUnitAndModule unitInternal); - - /// - /// Calls Invoke-DscResource -Method Get from this module. - /// - /// Settings. - /// Name. - /// Module specification. - /// Properties of resource. - ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// - /// Calls Invoke-DscResource -Method Test from this module. - /// - /// Settings. - /// Name. - /// Module specification. - /// Is in desired state. - bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// - /// Calls Invoke-DscResource -Method Set from this module. - /// - /// Settings. - /// Name. - /// Module specification. - /// If a reboot is required. - bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); - - /// - /// Calls Get-Module with fully qualified name. - /// - /// Module name. - /// PSModuleInfo, null if not imported. - PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification); - - /// - /// Calls Get-Module with the fully qualified name and using ListAvailable. - /// - /// Module specification. - /// PSModuleInfo, null if not found. - PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification); - - /// - /// Calls Get-Module from a path using ListAvailable. - /// - /// Path. - /// The first module returned, null if none. - PSModuleInfo? GetAvailableModule(string path); - - /// - /// Calls Import-Module with the fully qualified name. - /// - /// Module specification. - void ImportModule(ModuleSpecification moduleSpecification); - - /// - /// Calls Import-Module with a file path. - /// - /// Module file path. - void ImportModule(string path); - - /// - /// Calls Get-InstalledModule. - /// - /// Module specification. - /// Module info, null if not installed. - PSObject? GetInstalledModule(ModuleSpecification moduleSpecification); - - /// - /// Calls Find-Module. - /// - /// Configuration unit internal. - /// Module info, null if not found. - PSObject? FindModule(ConfigurationUnitInternal unitInternal); - - /// - /// Calls Find-DscResource. - /// - /// Configuration unit internal. - /// Dsc Resource info, null if not found. - PSObject? FindDscResource(ConfigurationUnitInternal unitInternal); - - /// - /// Calls Save-Module -InputObject object -Path location. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// - /// Input object. - /// Location to save module. - void SaveModule(PSObject inputObject, string location); - - /// - /// Calls Save-Module. - /// - /// Module specification. - /// Location to save module. - void SaveModule(ModuleSpecification moduleSpecification, string location); - - /// - /// Calls Install-Module -InputObject object. - /// Input object must be the result of Find cmdlets of PowerShellGet. - /// - /// Input object. - void InstallModule(PSObject inputObject); - - /// - /// Calls Install-Module with a module specification. - /// - /// Module specification. - void InstallModule(ModuleSpecification moduleSpecification); - - /// - /// Get unique certificates of valid signed files from the specified paths. - /// - /// Path. - /// List with valid signatures. - List GetCertsOfValidSignedFiles(string[] paths); - - /// - /// Gets the value of a variable. - /// - /// Type of the variable. - /// Name of variable. - /// The value of a variable, null if doesn't exist. - TType GetVariable(string name); - - /// - /// Sets a variable with its value. - /// - /// Name of variable. - /// Value of variable. - void SetVariable(string name, object value); - - /// - /// Overwrites PSModulePath with the specified path. - /// - /// Path. - void SetPSModulePath(string path); - - /// - /// Overwrites PSModulePath with the specified paths. - /// - /// Paths. - void SetPSModulePaths(IReadOnlyList paths); - - /// - /// Prepends path to the PSModulePath. - /// - /// Path. - void PrependPSModulePath(string path); - - /// - /// Prepends paths to the PSModulePath. - /// - /// Paths. - void PrependPSModulePaths(IReadOnlyList paths); - - /// - /// Append path to the PSModulePath. - /// - /// Path. - void AppendPSModulePath(string path); - - /// - /// Append paths to the PSModulePath. - /// - /// Path. - void AppendPSModulePaths(IReadOnlyList paths); - - /// - /// Removes a path from the module path. - /// - /// Path. - void CleanupPSModulePath(string path); - - /// - /// Sets the location for installing modules. - /// - /// Location. - /// Path for custom location. - void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments +{ + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + using Windows.Security.Cryptography.Certificates; + + /// + /// IProcessorEnvironment. Provides interaction with PowerShell. + /// + internal interface IProcessorEnvironment + { + /// + /// Gets the runspace. + /// + System.Management.Automation.Runspaces.Runspace Runspace { get; } + + /// + /// Validates the runspace. + /// + void ValidateRunspace(); + + /// + /// Gets all DSC resource. + /// + /// A list with the DSC resource. + IReadOnlyList GetAllDscResources(); + + /// + /// Gets all resources in a module. + /// + /// Module specification. + /// List of resources. + IReadOnlyList GetDscResourcesInModule(ModuleSpecification moduleSpecification); + + /// + /// Gets a DSC Resource. + /// + /// Configuration unit internal. + /// DSC Resource. + DscResourceInfoInternal? GetDscResource(ConfigurationUnitAndModule unitInternal); + + /// + /// Calls Invoke-DscResource -Method Get from this module. + /// + /// Settings. + /// Name. + /// Module specification. + /// Properties of resource. + ValueSet InvokeGetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// + /// Calls Invoke-DscResource -Method Test from this module. + /// + /// Settings. + /// Name. + /// Module specification. + /// Is in desired state. + bool InvokeTestResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// + /// Calls Invoke-DscResource -Method Set from this module. + /// + /// Settings. + /// Name. + /// Module specification. + /// If a reboot is required. + bool InvokeSetResource(ValueSet settings, string name, ModuleSpecification? moduleSpecification); + + /// + /// Calls Get-Module with fully qualified name. + /// + /// Module name. + /// PSModuleInfo, null if not imported. + PSModuleInfo? GetImportedModule(ModuleSpecification moduleSpecification); + + /// + /// Calls Get-Module with the fully qualified name and using ListAvailable. + /// + /// Module specification. + /// PSModuleInfo, null if not found. + PSModuleInfo? GetAvailableModule(ModuleSpecification moduleSpecification); + + /// + /// Calls Get-Module from a path using ListAvailable. + /// + /// Path. + /// The first module returned, null if none. + PSModuleInfo? GetAvailableModule(string path); + + /// + /// Calls Import-Module with the fully qualified name. + /// + /// Module specification. + void ImportModule(ModuleSpecification moduleSpecification); + + /// + /// Calls Import-Module with a file path. + /// + /// Module file path. + void ImportModule(string path); + + /// + /// Calls Get-InstalledModule. + /// + /// Module specification. + /// Module info, null if not installed. + PSObject? GetInstalledModule(ModuleSpecification moduleSpecification); + + /// + /// Calls Find-Module. + /// + /// Configuration unit internal. + /// Module info, null if not found. + PSObject? FindModule(ConfigurationUnitInternal unitInternal); + + /// + /// Calls Find-DscResource. + /// + /// Configuration unit internal. + /// Dsc Resource info, null if not found. + PSObject? FindDscResource(ConfigurationUnitInternal unitInternal); + + /// + /// Calls Save-Module -InputObject object -Path location. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// + /// Input object. + /// Location to save module. + void SaveModule(PSObject inputObject, string location); + + /// + /// Calls Save-Module. + /// + /// Module specification. + /// Location to save module. + void SaveModule(ModuleSpecification moduleSpecification, string location); + + /// + /// Calls Install-Module -InputObject object. + /// Input object must be the result of Find cmdlets of PowerShellGet. + /// + /// Input object. + void InstallModule(PSObject inputObject); + + /// + /// Calls Install-Module with a module specification. + /// + /// Module specification. + void InstallModule(ModuleSpecification moduleSpecification); + + /// + /// Get unique certificates of valid signed files from the specified paths. + /// + /// Path. + /// List with valid signatures. + List GetCertsOfValidSignedFiles(string[] paths); + + /// + /// Gets the value of a variable. + /// + /// Type of the variable. + /// Name of variable. + /// The value of a variable, null if doesn't exist. + TType GetVariable(string name); + + /// + /// Sets a variable with its value. + /// + /// Name of variable. + /// Value of variable. + void SetVariable(string name, object value); + + /// + /// Overwrites PSModulePath with the specified path. + /// + /// Path. + void SetPSModulePath(string path); + + /// + /// Overwrites PSModulePath with the specified paths. + /// + /// Paths. + void SetPSModulePaths(IReadOnlyList paths); + + /// + /// Prepends path to the PSModulePath. + /// + /// Path. + void PrependPSModulePath(string path); + + /// + /// Prepends paths to the PSModulePath. + /// + /// Paths. + void PrependPSModulePaths(IReadOnlyList paths); + + /// + /// Append path to the PSModulePath. + /// + /// Path. + void AppendPSModulePath(string path); + + /// + /// Append paths to the PSModulePath. + /// + /// Path. + void AppendPSModulePaths(IReadOnlyList paths); + + /// + /// Removes a path from the module path. + /// + /// Path. + void CleanupPSModulePath(string path); + + /// + /// Sets the location for installing modules. + /// + /// Location. + /// Path for custom location. + void SetLocation(PowerShellConfigurationProcessorLocation location, string? customLocation); + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/ProcessorEnvironmentFactory.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/ProcessorEnvironmentFactory.cs index 2970e05744..af7b80b48d 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/ProcessorEnvironmentFactory.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/ProcessorEnvironments/ProcessorEnvironmentFactory.cs @@ -1,109 +1,109 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments -{ - using System; - using System.Collections.Generic; - using System.Management.Automation.Runspaces; - using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; - using Microsoft.Management.Configuration.Processor.PowerShell.Runspaces; - using Microsoft.PowerShell; - using Microsoft.PowerShell.Commands; - - /// - /// Factory class to create a processor environment. - /// - internal class ProcessorEnvironmentFactory - { - private readonly PowerShellConfigurationProcessorType type; - - /// - /// Initializes a new instance of the class. - /// - /// Configuration processor type. - public ProcessorEnvironmentFactory(PowerShellConfigurationProcessorType type) - { - this.type = type; - } - - /// - /// Create process environment. - /// - /// Optional processor factory. - /// Configuration processor policy. - /// IProcessorEnvironment. - public IProcessorEnvironment CreateEnvironment( - PowerShellConfigurationSetProcessorFactory? setProcessorFactory, - PowerShellConfigurationProcessorPolicy policy) - { - IDscModule dscModule = new DscModuleV2(); - ExecutionPolicy executionPolicy = this.GetExecutionPolicy(policy); - - // The for ConfigurationProcessorType.Default the idea was that since is already running in PowerShell we will - // have access to the variables in the current runspace, but we can't use that runspace and AFAIK - // there's not a simple way to simply clone a runspace. If we want to do it, we will need to get the - // variables from the current runspace and add them here, but maybe some of them are objects that can't - // handle being used in different runspace. It will also be time consuming and we can't block for creating - // the create set processor. Even if we could clone it, at this point we are running in a different thread, - // so there's no default runspace to clone here (aka. PowerShell.Create(RunspaceMode.CurrentRunspace) throws) - // - // If we want to somehow support, it might be easier to explicitly ask for the variables that need to be - // ported. We can add a new property to IConfigurationProcessorFactoryProperties with the variable names - // and set them here, but if they change they won't get reflected in our runspace (which might be a good thing). - // The problem with that is that they will need to be defined when the configuration set is opened and it really - // just makes sense before the ConfigurationSetProcessor gets created. We could add a new IConfigurationSetProcessorProperties - // Then in PowerShell it can be something like - // Get-WinGetConfiguration | Add-WinGetConfigurationVariable -Name foo | Start-WinGetConfiguration - if (this.type == PowerShellConfigurationProcessorType.Hosted || - this.type == PowerShellConfigurationProcessorType.Default) - { - var initialSessionState = this.CreateInitialSessionState( - executionPolicy, - new List - { - dscModule.ModuleSpecification, - }); - - var runspace = RunspaceFactory.CreateRunspace(initialSessionState); - runspace.Open(); - - return new HostedEnvironment(runspace, this.type, dscModule) - { - SetProcessorFactory = setProcessorFactory, - }; - } - - throw new ArgumentException(this.type.ToString()); - } - - private InitialSessionState CreateInitialSessionState(ExecutionPolicy policy, IReadOnlyList modules) - { - InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); - - // If this call fails importing the module, it won't throw but write to the error output. DSCModule is - // in charge of verifying that it got loaded correctly and if not, to install it. - initialSessionState.ImportPSModule(modules); - - initialSessionState.ExecutionPolicy = policy; - - return initialSessionState; - } - - private ExecutionPolicy GetExecutionPolicy(PowerShellConfigurationProcessorPolicy policy) - { - return policy switch - { - PowerShellConfigurationProcessorPolicy.Unrestricted => ExecutionPolicy.Unrestricted, - PowerShellConfigurationProcessorPolicy.RemoteSigned => ExecutionPolicy.RemoteSigned, - PowerShellConfigurationProcessorPolicy.AllSigned => ExecutionPolicy.AllSigned, - PowerShellConfigurationProcessorPolicy.Restricted => ExecutionPolicy.Restricted, - PowerShellConfigurationProcessorPolicy.Bypass => ExecutionPolicy.Bypass, - _ => throw new InvalidOperationException(), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments +{ + using System; + using System.Collections.Generic; + using System.Management.Automation.Runspaces; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.Runspaces; + using Microsoft.PowerShell; + using Microsoft.PowerShell.Commands; + + /// + /// Factory class to create a processor environment. + /// + internal class ProcessorEnvironmentFactory + { + private readonly PowerShellConfigurationProcessorType type; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration processor type. + public ProcessorEnvironmentFactory(PowerShellConfigurationProcessorType type) + { + this.type = type; + } + + /// + /// Create process environment. + /// + /// Optional processor factory. + /// Configuration processor policy. + /// IProcessorEnvironment. + public IProcessorEnvironment CreateEnvironment( + PowerShellConfigurationSetProcessorFactory? setProcessorFactory, + PowerShellConfigurationProcessorPolicy policy) + { + IDscModule dscModule = new DscModuleV2(); + ExecutionPolicy executionPolicy = this.GetExecutionPolicy(policy); + + // The for ConfigurationProcessorType.Default the idea was that since is already running in PowerShell we will + // have access to the variables in the current runspace, but we can't use that runspace and AFAIK + // there's not a simple way to simply clone a runspace. If we want to do it, we will need to get the + // variables from the current runspace and add them here, but maybe some of them are objects that can't + // handle being used in different runspace. It will also be time consuming and we can't block for creating + // the create set processor. Even if we could clone it, at this point we are running in a different thread, + // so there's no default runspace to clone here (aka. PowerShell.Create(RunspaceMode.CurrentRunspace) throws) + // + // If we want to somehow support, it might be easier to explicitly ask for the variables that need to be + // ported. We can add a new property to IConfigurationProcessorFactoryProperties with the variable names + // and set them here, but if they change they won't get reflected in our runspace (which might be a good thing). + // The problem with that is that they will need to be defined when the configuration set is opened and it really + // just makes sense before the ConfigurationSetProcessor gets created. We could add a new IConfigurationSetProcessorProperties + // Then in PowerShell it can be something like + // Get-WinGetConfiguration | Add-WinGetConfigurationVariable -Name foo | Start-WinGetConfiguration + if (this.type == PowerShellConfigurationProcessorType.Hosted || + this.type == PowerShellConfigurationProcessorType.Default) + { + var initialSessionState = this.CreateInitialSessionState( + executionPolicy, + new List + { + dscModule.ModuleSpecification, + }); + + var runspace = RunspaceFactory.CreateRunspace(initialSessionState); + runspace.Open(); + + return new HostedEnvironment(runspace, this.type, dscModule) + { + SetProcessorFactory = setProcessorFactory, + }; + } + + throw new ArgumentException(this.type.ToString()); + } + + private InitialSessionState CreateInitialSessionState(ExecutionPolicy policy, IReadOnlyList modules) + { + InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); + + // If this call fails importing the module, it won't throw but write to the error output. DSCModule is + // in charge of verifying that it got loaded correctly and if not, to install it. + initialSessionState.ImportPSModule(modules); + + initialSessionState.ExecutionPolicy = policy; + + return initialSessionState; + } + + private ExecutionPolicy GetExecutionPolicy(PowerShellConfigurationProcessorPolicy policy) + { + return policy switch + { + PowerShellConfigurationProcessorPolicy.Unrestricted => ExecutionPolicy.Unrestricted, + PowerShellConfigurationProcessorPolicy.RemoteSigned => ExecutionPolicy.RemoteSigned, + PowerShellConfigurationProcessorPolicy.AllSigned => ExecutionPolicy.AllSigned, + PowerShellConfigurationProcessorPolicy.Restricted => ExecutionPolicy.Restricted, + PowerShellConfigurationProcessorPolicy.Bypass => ExecutionPolicy.Bypass, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Set/PowerShellConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Set/PowerShellConfigurationSetProcessor.cs index 1fa26e436c..9090600fc8 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Set/PowerShellConfigurationSetProcessor.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Set/PowerShellConfigurationSetProcessor.cs @@ -1,309 +1,309 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Set -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.PowerShell.Unit; - using Microsoft.Management.Configuration.Processor.Set; - using Microsoft.Management.Configuration.Processor.Unit; - using Windows.Security.Cryptography.Certificates; - - /// - /// IConfigurationSetProcessor implementation using PowerShell DSC v2. - /// - internal sealed partial class PowerShellConfigurationSetProcessor : ConfigurationSetProcessorBase, IConfigurationSetProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The processor environment. - /// Configuration set. - /// Whether the set processor should work in limitation mode. - public PowerShellConfigurationSetProcessor(IProcessorEnvironment processorEnvironment, ConfigurationSet? configurationSet, bool isLimitMode = false) - : base(configurationSet, isLimitMode) - { - this.ProcessorEnvironment = processorEnvironment; - } - - /// - /// Gets the processor environment. - /// - internal IProcessorEnvironment ProcessorEnvironment { get; } - - /// - protected override IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit) - { - var configurationUnitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path) { UnitTypeIsResourceName = IsUnitTypeResourceName(this.ConfigurationSet?.SchemaVersion) }; - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}..."); - - var dscResourceInfo = this.PrepareUnitForProcessing(configurationUnitInternal); - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Using unit from location: {dscResourceInfo.Path}"); - return new PowerShellConfigurationUnitProcessor( - this.ProcessorEnvironment, - new ConfigurationUnitAndResource(configurationUnitInternal, dscResourceInfo), - this.IsLimitMode) - { SetProcessorFactory = this.SetProcessorFactory }; - } - - /// - protected override IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) - { - var unitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path); - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailFlags}] for: {unitInternal.QualifiedName}"); - - // (Local | Download | Load) will all work off of local files, so if any one is an option just use the local module info if found. - DscResourceInfoInternal? dscResourceInfo = null; - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Local) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) - { - dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - } - - if (dscResourceInfo is not null) - { - return this.GetUnitProcessorDetailsLocal( - dscResourceInfo.Name, - dscResourceInfo, - detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)); - } - - if (!(detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load))) - { - // Not found locally. - return null; - } - - var unitModuleInfo = this.FindUnitModule(unitInternal); - if (unitModuleInfo is null) - { - // Not found in catalog. - return null; - } - - PSObject foundModule = unitModuleInfo.Value.Module; - string resourceName = unitModuleInfo.Value.ResourceName; - - dynamic foundModuleInfo = foundModule; - - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog)) - { - return Factory.CreateUnitProcessorDetails( - resourceName, - null, - null, - foundModule, - null); - } - - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download)) - { - var tempSavePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(tempSavePath); - this.ProcessorEnvironment.SaveModule(foundModule, tempSavePath); - - var moduleInfo = this.ProcessorEnvironment.GetAvailableModule( - Path.Combine(tempSavePath, foundModuleInfo.Name)); - - return Factory.CreateUnitProcessorDetails( - resourceName, - null, - moduleInfo, - foundModule, - this.GetCertificates(moduleInfo)); - } - - if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) - { - this.ProcessorEnvironment.InstallModule(foundModule); - - dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - - if (dscResourceInfo is null) - { - // Well, this is awkward. - throw new InstallDscResourceException( - unitInternal.ResourceName, - PowerShellHelpers.CreateModuleSpecification(foundModuleInfo.Name, foundModuleInfo.Version)); - } - - return this.GetUnitProcessorDetailsLocal(dscResourceInfo.Name, dscResourceInfo, true); - } - - return null; - } - - private static bool IsUnitTypeResourceName(string? schemaVersion) - { - return schemaVersion != null && schemaVersion == "0.1"; - } - - /// - /// Finds the module and preferred resource name for processing the configuration unit. - /// - /// The internal configuration unit. - /// A tuple containing the module info and preferred resource name, or null if not found. - private (PSObject Module, string ResourceName)? FindUnitModule(ConfigurationUnitAndModule unitInternal) - { - PSObject? foundModule = null; - string resourceName = string.Empty; - - // If module has been specified, find it and assume that the resource will be within it. - // Do this first as we do not currently gain much from FindDscResource; if that changes then it can be the primary. - if (unitInternal.Module != null) - { - foundModule = this.ProcessorEnvironment.FindModule(unitInternal); - if (foundModule != null) - { - resourceName = unitInternal.ResourceName; - } - } - else - { - dynamic? foundResource = this.ProcessorEnvironment.FindDscResource(unitInternal); - if (foundResource != null) - { - foundModule = foundResource.PSGetModuleInfo; - - // Hopefully they will never change the properties name. If someone can explain to me - // why assign it Name to $_ in Find-DscResource turns into a string in PowerShell but - // into a PSObject here that would be nice... - resourceName = foundResource.Name.ToString(); - } - } - - if (foundModule != null) - { - return (foundModule, resourceName); - } - - return null; - } - - private DscResourceInfoInternal PrepareUnitForProcessing(ConfigurationUnitAndModule unitInternal) - { - // Invoke-DscResource makes a call to Get-DscResource which looks at the entire PSModulePath - // to see if a resource exists. DscResourcesMap is an attempt to try to optimize Get-DscResource - // by making just one call and get all of them, but it doesn't support minVersion and maxVersion. - // For now, lets make PowerShell fully figure out which module to use and try to optimize it later. - // This class will have a private member Lazy which will be initialized by calling - // this.ProcessorEnvironment.GetAllDscResources() - // To improve the performance even more, we will still need Invoke-DscResource to be update to - // get a DSC resource info object instead of calling Get-DscResource every time. - var dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - - if (dscResourceInfo is null) - { - var findUnitModuleResult = this.FindUnitModule(unitInternal); - - if (findUnitModuleResult is null) - { - throw new FindDscResourceNotFoundException(unitInternal.ResourceName, unitInternal.Module); - } - - this.ProcessorEnvironment.InstallModule(findUnitModuleResult.Value.Module); - - // Now we should find it. - dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); - if (dscResourceInfo is null) - { - throw new InstallDscResourceException(unitInternal.ResourceName, unitInternal.Module); - } - } - - // PowerShell will prompt the user when a module that is downloaded from the internet is imported. - // For a hosted environment, this will throw an exception because it doesn't support user interaction. - // In the case we don't import the module here, eventually Invoke-DscResource will fail for class - // resources because they will call a method on a null obj. It is easier to just fail here. - // The exception being thrown will have the correct details (user needs to call Unblock-File) - // instead of the cryptic Invoke with 0 arguments. - if (!string.IsNullOrEmpty(dscResourceInfo.Path)) - { - try - { - this.ProcessorEnvironment.ImportModule(dscResourceInfo.Path); - } - catch (Exception e) - { - throw new ImportModuleException(dscResourceInfo.ModuleName, e); - } - } - - return dscResourceInfo; - } - - private ConfigurationUnitProcessorDetails GetUnitProcessorDetailsLocal( - string unitName, - DscResourceInfoInternal dscResourceInfo, - bool importModule) - { - // I'm looking at you resources under C:\WINDOWS\system32\WindowsPowershell - if (dscResourceInfo.ModuleName is null || - dscResourceInfo.Version is null) - { - return Factory.CreateUnitProcessorDetails( - dscResourceInfo.Name, - dscResourceInfo, - null, - null, - null); - } - - var module = PowerShellHelpers.CreateModuleSpecification( - dscResourceInfo.ModuleName, - dscResourceInfo.Version.ToString()); - - // Get-InstalledModule only works for modules installed via PowerShell-Get. - // There are some properties that can only be obtain by that it so is better to take both. - var moduleInfo = this.ProcessorEnvironment.GetAvailableModule(module); - var installedModule = this.ProcessorEnvironment.GetInstalledModule(module); - - if (importModule) - { - this.ProcessorEnvironment.ImportModule(module); - } - - return Factory.CreateUnitProcessorDetails( - dscResourceInfo.Name, - dscResourceInfo, - moduleInfo, - installedModule, - this.GetCertificates(moduleInfo)); - } - - private List? GetCertificates(PSModuleInfo? moduleInfo) - { - if (moduleInfo is null) - { - return null; - } - - // TODO: we still need to investigate more here, but lets start with something. - var paths = new List(); - - var psdPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psd1"); - if (File.Exists(psdPath)) - { - paths.Add(psdPath); - } - - var psmPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psm1"); - if (File.Exists(psmPath)) - { - paths.Add(psmPath); - } - - return this.ProcessorEnvironment.GetCertsOfValidSignedFiles(paths.ToArray()); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Set +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Unit; + using Microsoft.Management.Configuration.Processor.Set; + using Microsoft.Management.Configuration.Processor.Unit; + using Windows.Security.Cryptography.Certificates; + + /// + /// IConfigurationSetProcessor implementation using PowerShell DSC v2. + /// + internal sealed partial class PowerShellConfigurationSetProcessor : ConfigurationSetProcessorBase, IConfigurationSetProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The processor environment. + /// Configuration set. + /// Whether the set processor should work in limitation mode. + public PowerShellConfigurationSetProcessor(IProcessorEnvironment processorEnvironment, ConfigurationSet? configurationSet, bool isLimitMode = false) + : base(configurationSet, isLimitMode) + { + this.ProcessorEnvironment = processorEnvironment; + } + + /// + /// Gets the processor environment. + /// + internal IProcessorEnvironment ProcessorEnvironment { get; } + + /// + protected override IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit) + { + var configurationUnitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path) { UnitTypeIsResourceName = IsUnitTypeResourceName(this.ConfigurationSet?.SchemaVersion) }; + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Creating unit processor for: {configurationUnitInternal.QualifiedName}..."); + + var dscResourceInfo = this.PrepareUnitForProcessing(configurationUnitInternal); + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Using unit from location: {dscResourceInfo.Path}"); + return new PowerShellConfigurationUnitProcessor( + this.ProcessorEnvironment, + new ConfigurationUnitAndResource(configurationUnitInternal, dscResourceInfo), + this.IsLimitMode) + { SetProcessorFactory = this.SetProcessorFactory }; + } + + /// + protected override IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) + { + var unitInternal = new ConfigurationUnitAndModule(unit, this.ConfigurationSet?.Path); + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Getting unit details [{detailFlags}] for: {unitInternal.QualifiedName}"); + + // (Local | Download | Load) will all work off of local files, so if any one is an option just use the local module info if found. + DscResourceInfoInternal? dscResourceInfo = null; + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Local) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) + { + dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + } + + if (dscResourceInfo is not null) + { + return this.GetUnitProcessorDetailsLocal( + dscResourceInfo.Name, + dscResourceInfo, + detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)); + } + + if (!(detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download) || detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load))) + { + // Not found locally. + return null; + } + + var unitModuleInfo = this.FindUnitModule(unitInternal); + if (unitModuleInfo is null) + { + // Not found in catalog. + return null; + } + + PSObject foundModule = unitModuleInfo.Value.Module; + string resourceName = unitModuleInfo.Value.ResourceName; + + dynamic foundModuleInfo = foundModule; + + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Catalog)) + { + return Factory.CreateUnitProcessorDetails( + resourceName, + null, + null, + foundModule, + null); + } + + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Download)) + { + var tempSavePath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempSavePath); + this.ProcessorEnvironment.SaveModule(foundModule, tempSavePath); + + var moduleInfo = this.ProcessorEnvironment.GetAvailableModule( + Path.Combine(tempSavePath, foundModuleInfo.Name)); + + return Factory.CreateUnitProcessorDetails( + resourceName, + null, + moduleInfo, + foundModule, + this.GetCertificates(moduleInfo)); + } + + if (detailFlags.HasFlag(ConfigurationUnitDetailFlags.Load)) + { + this.ProcessorEnvironment.InstallModule(foundModule); + + dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + + if (dscResourceInfo is null) + { + // Well, this is awkward. + throw new InstallDscResourceException( + unitInternal.ResourceName, + PowerShellHelpers.CreateModuleSpecification(foundModuleInfo.Name, foundModuleInfo.Version)); + } + + return this.GetUnitProcessorDetailsLocal(dscResourceInfo.Name, dscResourceInfo, true); + } + + return null; + } + + private static bool IsUnitTypeResourceName(string? schemaVersion) + { + return schemaVersion != null && schemaVersion == "0.1"; + } + + /// + /// Finds the module and preferred resource name for processing the configuration unit. + /// + /// The internal configuration unit. + /// A tuple containing the module info and preferred resource name, or null if not found. + private (PSObject Module, string ResourceName)? FindUnitModule(ConfigurationUnitAndModule unitInternal) + { + PSObject? foundModule = null; + string resourceName = string.Empty; + + // If module has been specified, find it and assume that the resource will be within it. + // Do this first as we do not currently gain much from FindDscResource; if that changes then it can be the primary. + if (unitInternal.Module != null) + { + foundModule = this.ProcessorEnvironment.FindModule(unitInternal); + if (foundModule != null) + { + resourceName = unitInternal.ResourceName; + } + } + else + { + dynamic? foundResource = this.ProcessorEnvironment.FindDscResource(unitInternal); + if (foundResource != null) + { + foundModule = foundResource.PSGetModuleInfo; + + // Hopefully they will never change the properties name. If someone can explain to me + // why assign it Name to $_ in Find-DscResource turns into a string in PowerShell but + // into a PSObject here that would be nice... + resourceName = foundResource.Name.ToString(); + } + } + + if (foundModule != null) + { + return (foundModule, resourceName); + } + + return null; + } + + private DscResourceInfoInternal PrepareUnitForProcessing(ConfigurationUnitAndModule unitInternal) + { + // Invoke-DscResource makes a call to Get-DscResource which looks at the entire PSModulePath + // to see if a resource exists. DscResourcesMap is an attempt to try to optimize Get-DscResource + // by making just one call and get all of them, but it doesn't support minVersion and maxVersion. + // For now, lets make PowerShell fully figure out which module to use and try to optimize it later. + // This class will have a private member Lazy which will be initialized by calling + // this.ProcessorEnvironment.GetAllDscResources() + // To improve the performance even more, we will still need Invoke-DscResource to be update to + // get a DSC resource info object instead of calling Get-DscResource every time. + var dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + + if (dscResourceInfo is null) + { + var findUnitModuleResult = this.FindUnitModule(unitInternal); + + if (findUnitModuleResult is null) + { + throw new FindDscResourceNotFoundException(unitInternal.ResourceName, unitInternal.Module); + } + + this.ProcessorEnvironment.InstallModule(findUnitModuleResult.Value.Module); + + // Now we should find it. + dscResourceInfo = this.ProcessorEnvironment.GetDscResource(unitInternal); + if (dscResourceInfo is null) + { + throw new InstallDscResourceException(unitInternal.ResourceName, unitInternal.Module); + } + } + + // PowerShell will prompt the user when a module that is downloaded from the internet is imported. + // For a hosted environment, this will throw an exception because it doesn't support user interaction. + // In the case we don't import the module here, eventually Invoke-DscResource will fail for class + // resources because they will call a method on a null obj. It is easier to just fail here. + // The exception being thrown will have the correct details (user needs to call Unblock-File) + // instead of the cryptic Invoke with 0 arguments. + if (!string.IsNullOrEmpty(dscResourceInfo.Path)) + { + try + { + this.ProcessorEnvironment.ImportModule(dscResourceInfo.Path); + } + catch (Exception e) + { + throw new ImportModuleException(dscResourceInfo.ModuleName, e); + } + } + + return dscResourceInfo; + } + + private ConfigurationUnitProcessorDetails GetUnitProcessorDetailsLocal( + string unitName, + DscResourceInfoInternal dscResourceInfo, + bool importModule) + { + // I'm looking at you resources under C:\WINDOWS\system32\WindowsPowershell + if (dscResourceInfo.ModuleName is null || + dscResourceInfo.Version is null) + { + return Factory.CreateUnitProcessorDetails( + dscResourceInfo.Name, + dscResourceInfo, + null, + null, + null); + } + + var module = PowerShellHelpers.CreateModuleSpecification( + dscResourceInfo.ModuleName, + dscResourceInfo.Version.ToString()); + + // Get-InstalledModule only works for modules installed via PowerShell-Get. + // There are some properties that can only be obtain by that it so is better to take both. + var moduleInfo = this.ProcessorEnvironment.GetAvailableModule(module); + var installedModule = this.ProcessorEnvironment.GetInstalledModule(module); + + if (importModule) + { + this.ProcessorEnvironment.ImportModule(module); + } + + return Factory.CreateUnitProcessorDetails( + dscResourceInfo.Name, + dscResourceInfo, + moduleInfo, + installedModule, + this.GetCertificates(moduleInfo)); + } + + private List? GetCertificates(PSModuleInfo? moduleInfo) + { + if (moduleInfo is null) + { + return null; + } + + // TODO: we still need to investigate more here, but lets start with something. + var paths = new List(); + + var psdPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psd1"); + if (File.Exists(psdPath)) + { + paths.Add(psdPath); + } + + var psmPath = Path.Combine(moduleInfo.ModuleBase, $"{moduleInfo.Name}.psm1"); + if (File.Exists(psmPath)) + { + paths.Add(psmPath); + } + + return this.ProcessorEnvironment.GetCertsOfValidSignedFiles(paths.ToArray()); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/PowerShell/Unit/PowerShellConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.Processor/PowerShell/Unit/PowerShellConfigurationUnitProcessor.cs index 4a070287d6..1b2d8dfa46 100644 --- a/src/Microsoft.Management.Configuration.Processor/PowerShell/Unit/PowerShellConfigurationUnitProcessor.cs +++ b/src/Microsoft.Management.Configuration.Processor/PowerShell/Unit/PowerShellConfigurationUnitProcessor.cs @@ -1,63 +1,63 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.PowerShell.Unit -{ - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.Unit; - using Windows.Foundation.Collections; - - /// - /// Provides access to a specific configuration unit within the runtime. - /// - internal sealed partial class PowerShellConfigurationUnitProcessor : ConfigurationUnitProcessorBase, IConfigurationUnitProcessor - { - private readonly IProcessorEnvironment processorEnvironment; - private readonly ConfigurationUnitAndResource unitResource; - - /// - /// Initializes a new instance of the class. - /// - /// Processor environment. - /// UnitResource. - /// Whether it is under limit mode. - internal PowerShellConfigurationUnitProcessor(IProcessorEnvironment processorEnvironment, ConfigurationUnitAndResource unitResource, bool isLimitMode = false) - : base(unitResource.UnitInternal, isLimitMode) - { - this.processorEnvironment = processorEnvironment; - this.unitResource = unitResource; - } - - /// - protected override ValueSet GetSettingsInternal() - { - return this.processorEnvironment.InvokeGetResource( - this.unitResource.GetSettings(), - this.unitResource.ResourceName, - this.unitResource.Module); - } - - /// - protected override bool TestSettingsInternal() - { - return this.processorEnvironment.InvokeTestResource( - this.unitResource.GetSettings(), - this.unitResource.ResourceName, - this.unitResource.Module); - } - - /// - protected override bool ApplySettingsInternal() - { - return this.processorEnvironment.InvokeSetResource( - this.unitResource.GetSettings(), - this.unitResource.ResourceName, - this.unitResource.Module); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.PowerShell.Unit +{ + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.Unit; + using Windows.Foundation.Collections; + + /// + /// Provides access to a specific configuration unit within the runtime. + /// + internal sealed partial class PowerShellConfigurationUnitProcessor : ConfigurationUnitProcessorBase, IConfigurationUnitProcessor + { + private readonly IProcessorEnvironment processorEnvironment; + private readonly ConfigurationUnitAndResource unitResource; + + /// + /// Initializes a new instance of the class. + /// + /// Processor environment. + /// UnitResource. + /// Whether it is under limit mode. + internal PowerShellConfigurationUnitProcessor(IProcessorEnvironment processorEnvironment, ConfigurationUnitAndResource unitResource, bool isLimitMode = false) + : base(unitResource.UnitInternal, isLimitMode) + { + this.processorEnvironment = processorEnvironment; + this.unitResource = unitResource; + } + + /// + protected override ValueSet GetSettingsInternal() + { + return this.processorEnvironment.InvokeGetResource( + this.unitResource.GetSettings(), + this.unitResource.ResourceName, + this.unitResource.Module); + } + + /// + protected override bool TestSettingsInternal() + { + return this.processorEnvironment.InvokeTestResource( + this.unitResource.GetSettings(), + this.unitResource.ResourceName, + this.unitResource.Module); + } + + /// + protected override bool ApplySettingsInternal() + { + return this.processorEnvironment.InvokeSetResource( + this.unitResource.GetSettings(), + this.unitResource.ResourceName, + this.unitResource.Module); + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Properties/AssemblyInfo.cs b/src/Microsoft.Management.Configuration.Processor/Properties/AssemblyInfo.cs index 4ebc6b5a22..3b2c01eb5d 100644 --- a/src/Microsoft.Management.Configuration.Processor/Properties/AssemblyInfo.cs +++ b/src/Microsoft.Management.Configuration.Processor/Properties/AssemblyInfo.cs @@ -1,27 +1,27 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -using System.Runtime.CompilerServices; -using System.Runtime.Versioning; - -// InternalsVisibleTo specifies that types that are ordinarily visible only within the current -// assembly are visible to a specified assembly. This is only for types and members with internal -// or private protected scope, NOT private. Add any test dll that requires access to internal members. -[assembly: InternalsVisibleTo("Microsoft.Management.Configuration.UnitTests")] - -// Needed to allow us mock internal interfaces. -[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] - -#if WinGetCsWinRTEmbedded -// Allow our consuming assemblies access when built embedded. -[assembly: InternalsVisibleTo("Microsoft.WinGet.Configuration.Engine")] -[assembly: InternalsVisibleTo("ConfigurationRemotingServer")] -#endif - -// Forcibly set the target and supported platforms due to the internal build setup. -// Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.26100.0")] -[assembly: SupportedOSPlatform("Windows10.0.17763.0")] +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +// InternalsVisibleTo specifies that types that are ordinarily visible only within the current +// assembly are visible to a specified assembly. This is only for types and members with internal +// or private protected scope, NOT private. Add any test dll that requires access to internal members. +[assembly: InternalsVisibleTo("Microsoft.Management.Configuration.UnitTests")] + +// Needed to allow us mock internal interfaces. +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")] + +#if WinGetCsWinRTEmbedded +// Allow our consuming assemblies access when built embedded. +[assembly: InternalsVisibleTo("Microsoft.WinGet.Configuration.Engine")] +[assembly: InternalsVisibleTo("ConfigurationRemotingServer")] +#endif + +// Forcibly set the target and supported platforms due to the internal build setup. +// Keep in sync with project versions. +[assembly: TargetPlatform("Windows10.0.26100.0")] +[assembly: SupportedOSPlatform("Windows10.0.17763.0")] diff --git a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs index 55a88fece3..cdc3d0cff9 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/DSCv3ConfigurationSetProcessorFactory.cs @@ -1,305 +1,305 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.Diagnostics.CodeAnalysis; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.Processor.DSCv3.Set; - using Microsoft.Management.Configuration.Processor.Factory; - - /// - /// IConfigurationSetProcessorFactory implementation using DSC v3. - /// - internal sealed partial class DSCv3ConfigurationSetProcessorFactory : ConfigurationSetProcessorFactoryBase, IConfigurationSetProcessorFactory, IDictionary - { - private const string DscExecutablePathPropertyName = "DscExecutablePath"; - private const string FoundDscExecutablePathPropertyName = "FoundDscExecutablePath"; - private const string DiagnosticTraceEnabledPropertyName = "DiagnosticTraceEnabled"; - private const string FindDscStateMachinePropertyName = "FindDscStateMachine"; - private const string DscExecutablePathHashPropertyName = "DscExecutablePathHash"; - private const string DscExecutablePathIsAliasPropertyName = "DscExecutablePathIsAlias"; - private const string FoundDscExecutablePathHashPropertyName = "FoundDscExecutablePathHash"; - private const string FoundDscExecutablePathIsAliasPropertyName = "FoundDscExecutablePathIsAlias"; - - private ProcessorSettings processorSettings = new (); - - /// - /// Initializes a new instance of the class. - /// - public DSCv3ConfigurationSetProcessorFactory() - { - this.processorSettings.DiagnosticsSink = this; - } - - /// - /// Gets or sets the path to the DSC v3 executable. - /// - public string? DscExecutablePath - { - get - { - return this.processorSettings.DscExecutablePath; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting DscExecutablePath in limit mode is invalid."); - } - - this.processorSettings.DscExecutablePath = value; - } - } - - /// - /// Gets or sets the expected SHA256 hash of (hex string). - /// Required when a custom processor path is provided. - /// - public string? DscExecutablePathHash - { - get - { - return this.processorSettings.DscExecutablePathHash; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting DscExecutablePathHash in limit mode is invalid."); - } - - this.processorSettings.DscExecutablePathHash = value; - } - } - - /// - /// Gets or sets a value indicating whether is an app execution alias. - /// - public bool? DscExecutablePathIsAlias - { - get - { - return this.processorSettings.DscExecutablePathIsAlias; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting DscExecutablePathIsAlias in limit mode is invalid."); - } - - this.processorSettings.DscExecutablePathIsAlias = value; - } - } - -#if !AICLI_DISABLE_TEST_HOOKS - /// - /// Gets the processor settings; for tests only. - /// - public ProcessorSettings Settings - { - get - { - return this.processorSettings; - } - } -#endif - - /// - public ICollection Keys => throw new NotImplementedException(); - - /// - public ICollection Values => throw new NotImplementedException(); - - /// - public int Count => throw new NotImplementedException(); - - /// - public bool IsReadOnly => this.IsLimitMode(); - - /// - public string this[string key] { get => this.GetValue(key); set => this.SetValue(key, value); } - - /// - public void Add(string key, string value) - { - this.SetValue(key, value); - } - - /// - public void Add(KeyValuePair item) - { - this.SetValue(item.Key, item.Value); - } - - /// - public void Clear() - { - throw new NotImplementedException(); - } - - /// - public bool Contains(KeyValuePair item) - { - throw new NotImplementedException(); - } - - /// - public bool ContainsKey(string key) - { - switch (key) - { - case DscExecutablePathPropertyName: - return this.DscExecutablePath != null; - case DscExecutablePathHashPropertyName: - return this.DscExecutablePathHash != null; - case DscExecutablePathIsAliasPropertyName: - return this.DscExecutablePathIsAlias != null; - case FoundDscExecutablePathHashPropertyName: - return this.processorSettings.GetFoundDscExecutablePathHash() != null; - case FoundDscExecutablePathIsAliasPropertyName: - return this.processorSettings.GetFoundDscExecutablePathIsAlias() != null; - } - - return false; - } - - /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } - - /// - public IEnumerator> GetEnumerator() - { - throw new NotImplementedException(); - } - - /// - public bool Remove(string key) - { - throw new NotImplementedException(); - } - - /// - public bool Remove(KeyValuePair item) - { - throw new NotImplementedException(); - } - - /// - public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) - { - value = null; - - switch (key) - { - case DscExecutablePathPropertyName: - value = this.DscExecutablePath!; - return true; - case FoundDscExecutablePathPropertyName: - value = this.processorSettings.GetFoundDscExecutablePath() !; - return true; - case DiagnosticTraceEnabledPropertyName: - value = this.processorSettings.DiagnosticTraceEnabled.ToString(); - return true; - case FindDscStateMachinePropertyName: - value = this.processorSettings.PumpFindDscStateMachine().ToString(); - return true; - case DscExecutablePathHashPropertyName: - if (this.DscExecutablePathHash != null) - { - value = this.DscExecutablePathHash; - return true; - } - - return false; - case DscExecutablePathIsAliasPropertyName: - if (this.DscExecutablePathIsAlias != null) - { - value = this.DscExecutablePathIsAlias.Value.ToString(); - return true; - } - - return false; - case FoundDscExecutablePathHashPropertyName: - string? foundHash = this.processorSettings.GetFoundDscExecutablePathHash(); - if (foundHash != null) - { - value = foundHash; - return true; - } - - return false; - case FoundDscExecutablePathIsAliasPropertyName: - bool? foundIsAlias = this.processorSettings.GetFoundDscExecutablePathIsAlias(); - if (foundIsAlias != null) - { - value = foundIsAlias.Value.ToString(); - return true; - } - - return false; - } - - return false; - } - - /// - IEnumerator IEnumerable.GetEnumerator() - { - return this.GetEnumerator(); - } - - /// - protected override IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode) - { - ProcessorSettings processorSettingsCopy = this.processorSettings.Clone(); - this.OnDiagnostics(DiagnosticLevel.Verbose, "Creating set processor with settings:\n" + processorSettingsCopy.ToString()); - return new DSCv3ConfigurationSetProcessor(processorSettingsCopy, set, isLimitMode) { SetProcessorFactory = this }; - } - - private string GetValue(string name) - { - if (this.TryGetValue(name, out string? result)) - { - return result; - } - - throw new ArgumentOutOfRangeException($"Invalid property name: {name}"); - } - - private void SetValue(string name, string value) - { - switch (name) - { - case DscExecutablePathPropertyName: - this.DscExecutablePath = value; - break; - case DiagnosticTraceEnabledPropertyName: - this.processorSettings.DiagnosticTraceEnabled = bool.Parse(value); - break; - case DscExecutablePathHashPropertyName: - this.DscExecutablePathHash = value; - break; - case DscExecutablePathIsAliasPropertyName: - this.DscExecutablePathIsAlias = bool.Parse(value); - break; - default: - throw new ArgumentOutOfRangeException($"Invalid property name: {name}"); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.DSCv3.Set; + using Microsoft.Management.Configuration.Processor.Factory; + + /// + /// IConfigurationSetProcessorFactory implementation using DSC v3. + /// + internal sealed partial class DSCv3ConfigurationSetProcessorFactory : ConfigurationSetProcessorFactoryBase, IConfigurationSetProcessorFactory, IDictionary + { + private const string DscExecutablePathPropertyName = "DscExecutablePath"; + private const string FoundDscExecutablePathPropertyName = "FoundDscExecutablePath"; + private const string DiagnosticTraceEnabledPropertyName = "DiagnosticTraceEnabled"; + private const string FindDscStateMachinePropertyName = "FindDscStateMachine"; + private const string DscExecutablePathHashPropertyName = "DscExecutablePathHash"; + private const string DscExecutablePathIsAliasPropertyName = "DscExecutablePathIsAlias"; + private const string FoundDscExecutablePathHashPropertyName = "FoundDscExecutablePathHash"; + private const string FoundDscExecutablePathIsAliasPropertyName = "FoundDscExecutablePathIsAlias"; + + private ProcessorSettings processorSettings = new (); + + /// + /// Initializes a new instance of the class. + /// + public DSCv3ConfigurationSetProcessorFactory() + { + this.processorSettings.DiagnosticsSink = this; + } + + /// + /// Gets or sets the path to the DSC v3 executable. + /// + public string? DscExecutablePath + { + get + { + return this.processorSettings.DscExecutablePath; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting DscExecutablePath in limit mode is invalid."); + } + + this.processorSettings.DscExecutablePath = value; + } + } + + /// + /// Gets or sets the expected SHA256 hash of (hex string). + /// Required when a custom processor path is provided. + /// + public string? DscExecutablePathHash + { + get + { + return this.processorSettings.DscExecutablePathHash; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting DscExecutablePathHash in limit mode is invalid."); + } + + this.processorSettings.DscExecutablePathHash = value; + } + } + + /// + /// Gets or sets a value indicating whether is an app execution alias. + /// + public bool? DscExecutablePathIsAlias + { + get + { + return this.processorSettings.DscExecutablePathIsAlias; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting DscExecutablePathIsAlias in limit mode is invalid."); + } + + this.processorSettings.DscExecutablePathIsAlias = value; + } + } + +#if !AICLI_DISABLE_TEST_HOOKS + /// + /// Gets the processor settings; for tests only. + /// + public ProcessorSettings Settings + { + get + { + return this.processorSettings; + } + } +#endif + + /// + public ICollection Keys => throw new NotImplementedException(); + + /// + public ICollection Values => throw new NotImplementedException(); + + /// + public int Count => throw new NotImplementedException(); + + /// + public bool IsReadOnly => this.IsLimitMode(); + + /// + public string this[string key] { get => this.GetValue(key); set => this.SetValue(key, value); } + + /// + public void Add(string key, string value) + { + this.SetValue(key, value); + } + + /// + public void Add(KeyValuePair item) + { + this.SetValue(item.Key, item.Value); + } + + /// + public void Clear() + { + throw new NotImplementedException(); + } + + /// + public bool Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } + + /// + public bool ContainsKey(string key) + { + switch (key) + { + case DscExecutablePathPropertyName: + return this.DscExecutablePath != null; + case DscExecutablePathHashPropertyName: + return this.DscExecutablePathHash != null; + case DscExecutablePathIsAliasPropertyName: + return this.DscExecutablePathIsAlias != null; + case FoundDscExecutablePathHashPropertyName: + return this.processorSettings.GetFoundDscExecutablePathHash() != null; + case FoundDscExecutablePathIsAliasPropertyName: + return this.processorSettings.GetFoundDscExecutablePathIsAlias() != null; + } + + return false; + } + + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } + + /// + public IEnumerator> GetEnumerator() + { + throw new NotImplementedException(); + } + + /// + public bool Remove(string key) + { + throw new NotImplementedException(); + } + + /// + public bool Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } + + /// + public bool TryGetValue(string key, [MaybeNullWhen(false)] out string value) + { + value = null; + + switch (key) + { + case DscExecutablePathPropertyName: + value = this.DscExecutablePath!; + return true; + case FoundDscExecutablePathPropertyName: + value = this.processorSettings.GetFoundDscExecutablePath() !; + return true; + case DiagnosticTraceEnabledPropertyName: + value = this.processorSettings.DiagnosticTraceEnabled.ToString(); + return true; + case FindDscStateMachinePropertyName: + value = this.processorSettings.PumpFindDscStateMachine().ToString(); + return true; + case DscExecutablePathHashPropertyName: + if (this.DscExecutablePathHash != null) + { + value = this.DscExecutablePathHash; + return true; + } + + return false; + case DscExecutablePathIsAliasPropertyName: + if (this.DscExecutablePathIsAlias != null) + { + value = this.DscExecutablePathIsAlias.Value.ToString(); + return true; + } + + return false; + case FoundDscExecutablePathHashPropertyName: + string? foundHash = this.processorSettings.GetFoundDscExecutablePathHash(); + if (foundHash != null) + { + value = foundHash; + return true; + } + + return false; + case FoundDscExecutablePathIsAliasPropertyName: + bool? foundIsAlias = this.processorSettings.GetFoundDscExecutablePathIsAlias(); + if (foundIsAlias != null) + { + value = foundIsAlias.Value.ToString(); + return true; + } + + return false; + } + + return false; + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + protected override IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode) + { + ProcessorSettings processorSettingsCopy = this.processorSettings.Clone(); + this.OnDiagnostics(DiagnosticLevel.Verbose, "Creating set processor with settings:\n" + processorSettingsCopy.ToString()); + return new DSCv3ConfigurationSetProcessor(processorSettingsCopy, set, isLimitMode) { SetProcessorFactory = this }; + } + + private string GetValue(string name) + { + if (this.TryGetValue(name, out string? result)) + { + return result; + } + + throw new ArgumentOutOfRangeException($"Invalid property name: {name}"); + } + + private void SetValue(string name, string value) + { + switch (name) + { + case DscExecutablePathPropertyName: + this.DscExecutablePath = value; + break; + case DiagnosticTraceEnabledPropertyName: + this.processorSettings.DiagnosticTraceEnabled = bool.Parse(value); + break; + case DscExecutablePathHashPropertyName: + this.DscExecutablePathHash = value; + break; + case DscExecutablePathIsAliasPropertyName: + this.DscExecutablePathIsAlias = bool.Parse(value); + break; + default: + throw new ArgumentOutOfRangeException($"Invalid property name: {name}"); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Public/IPowerShellConfigurationProcessorFactoryProperties.cs b/src/Microsoft.Management.Configuration.Processor/Public/IPowerShellConfigurationProcessorFactoryProperties.cs index a080e5c7f0..210f8e3fd2 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/IPowerShellConfigurationProcessorFactoryProperties.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/IPowerShellConfigurationProcessorFactoryProperties.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor -{ - using System.Collections.Generic; - - /// - /// Properties for the configuration processor factory. - /// - public interface IPowerShellConfigurationProcessorFactoryProperties - { - /// - /// Gets or sets the processor type. - /// - PowerShellConfigurationProcessorType ProcessorType { get; set; } - - /// - /// Gets or sets the additional module paths. - /// - IReadOnlyList? AdditionalModulePaths { get; set; } - - /// - /// Gets or sets the configuration policy. - /// - PowerShellConfigurationProcessorPolicy Policy { get; set; } - - /// - /// Gets or sets the module location. - /// - PowerShellConfigurationProcessorLocation Location { get; set; } - - /// - /// Gets or sets the install module path. Only used for Scope.Custom. - /// - string? CustomLocation { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor +{ + using System.Collections.Generic; + + /// + /// Properties for the configuration processor factory. + /// + public interface IPowerShellConfigurationProcessorFactoryProperties + { + /// + /// Gets or sets the processor type. + /// + PowerShellConfigurationProcessorType ProcessorType { get; set; } + + /// + /// Gets or sets the additional module paths. + /// + IReadOnlyList? AdditionalModulePaths { get; set; } + + /// + /// Gets or sets the configuration policy. + /// + PowerShellConfigurationProcessorPolicy Policy { get; set; } + + /// + /// Gets or sets the module location. + /// + PowerShellConfigurationProcessorLocation Location { get; set; } + + /// + /// Gets or sets the install module path. Only used for Scope.Custom. + /// + string? CustomLocation { get; set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Public/PathEnvironmentVariableHandler.cs b/src/Microsoft.Management.Configuration.Processor/Public/PathEnvironmentVariableHandler.cs index 604c6b8a7e..246b8a7deb 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/PathEnvironmentVariableHandler.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/PathEnvironmentVariableHandler.cs @@ -1,69 +1,69 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Helpers -{ - using System; - using System.Collections.Generic; - - /// - /// Class for handling PATH environment variable. - /// - public static class PathEnvironmentVariableHandler - { - private const string PathEnvironmentVariable = "PATH"; - - private static readonly object EnvironmentVariableLock = new object(); - - /// - /// Gets the lock to read or write PATH environment variable. - /// - public static object Lock - { - get { return EnvironmentVariableLock; } - } - - /// - /// Updates the process's PATH environment variable if new paths added. - /// Only adds new paths since we add to PATH in other code which may not be in the registry. - /// - public static void UpdatePath() - { - HashSet paths = new HashSet(Environment.GetEnvironmentVariable(PathEnvironmentVariable)?.Split(';') ?? Array.Empty()); - var originalPathsSize = paths.Count; - - AddPathsIfNotExist(paths, Environment.GetEnvironmentVariable(PathEnvironmentVariable, EnvironmentVariableTarget.Machine)?.Split(';')); - AddPathsIfNotExist(paths, Environment.GetEnvironmentVariable(PathEnvironmentVariable, EnvironmentVariableTarget.User)?.Split(';')); - - if (paths.Count > originalPathsSize) - { - lock (Lock) - { - Environment.SetEnvironmentVariable(PathEnvironmentVariable, string.Join(';', paths)); - } - } - } - - // TODO: Currently it always adds new paths to the end. The "proper" thing to do would probably be to calculate - // the full new list of paths (what one would expect to get from a new process launch) and use a line merge algorithm - // with a strategy that puts the ephemeral entries before the new permanent ones. -#pragma warning disable SA1011 // Closing square brackets should be spaced correctly - private static void AddPathsIfNotExist(HashSet currentPaths, string[]? paths) -#pragma warning restore SA1011 // Closing square brackets should be spaced correctly - { - if (paths is not null) - { - foreach (var path in paths) - { - if (!currentPaths.Contains(path)) - { - currentPaths.Add(path); - } - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Helpers +{ + using System; + using System.Collections.Generic; + + /// + /// Class for handling PATH environment variable. + /// + public static class PathEnvironmentVariableHandler + { + private const string PathEnvironmentVariable = "PATH"; + + private static readonly object EnvironmentVariableLock = new object(); + + /// + /// Gets the lock to read or write PATH environment variable. + /// + public static object Lock + { + get { return EnvironmentVariableLock; } + } + + /// + /// Updates the process's PATH environment variable if new paths added. + /// Only adds new paths since we add to PATH in other code which may not be in the registry. + /// + public static void UpdatePath() + { + HashSet paths = new HashSet(Environment.GetEnvironmentVariable(PathEnvironmentVariable)?.Split(';') ?? Array.Empty()); + var originalPathsSize = paths.Count; + + AddPathsIfNotExist(paths, Environment.GetEnvironmentVariable(PathEnvironmentVariable, EnvironmentVariableTarget.Machine)?.Split(';')); + AddPathsIfNotExist(paths, Environment.GetEnvironmentVariable(PathEnvironmentVariable, EnvironmentVariableTarget.User)?.Split(';')); + + if (paths.Count > originalPathsSize) + { + lock (Lock) + { + Environment.SetEnvironmentVariable(PathEnvironmentVariable, string.Join(';', paths)); + } + } + } + + // TODO: Currently it always adds new paths to the end. The "proper" thing to do would probably be to calculate + // the full new list of paths (what one would expect to get from a new process launch) and use a line merge algorithm + // with a strategy that puts the ephemeral entries before the new permanent ones. +#pragma warning disable SA1011 // Closing square brackets should be spaced correctly + private static void AddPathsIfNotExist(HashSet currentPaths, string[]? paths) +#pragma warning restore SA1011 // Closing square brackets should be spaced correctly + { + if (paths is not null) + { + foreach (var path in paths) + { + if (!currentPaths.Contains(path)) + { + currentPaths.Add(path); + } + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorLocation.cs b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorLocation.cs index c909217cd0..c8bb310e8b 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorLocation.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorLocation.cs @@ -1,39 +1,39 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor -{ - /// - /// The location where modules are going to be installed. - /// - public enum PowerShellConfigurationProcessorLocation - { - /// - /// Current user path. - /// - CurrentUser = 0, - - /// - /// AllUsers path. Requires admin. - /// - AllUsers = 1, - - /// - /// The winget location %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules. - /// - WinGetModulePath = 2, - - /// - /// Custom path. - /// - Custom = 3, - - /// - /// Default. - /// - Default = WinGetModulePath, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor +{ + /// + /// The location where modules are going to be installed. + /// + public enum PowerShellConfigurationProcessorLocation + { + /// + /// Current user path. + /// + CurrentUser = 0, + + /// + /// AllUsers path. Requires admin. + /// + AllUsers = 1, + + /// + /// The winget location %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules. + /// + WinGetModulePath = 2, + + /// + /// Custom path. + /// + Custom = 3, + + /// + /// Default. + /// + Default = WinGetModulePath, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorPolicy.cs b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorPolicy.cs index 3f5a280003..d3f36744fc 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorPolicy.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorPolicy.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor -{ - /// - /// Processor policy. - /// For Processor type Default and Hosted they mean the same as PowerShell ExecutionPolicy. - /// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies. - /// - public enum PowerShellConfigurationProcessorPolicy - { - /// - /// Unrestricted. - /// - Unrestricted = 0, - - /// - /// RemoteSigned. - /// - RemoteSigned = 1, - - /// - /// AllSigned. - /// - AllSigned = 2, - - /// - /// Restricted. - /// - Restricted = 3, - - /// - /// Bypass. - /// - Bypass = 4, - - /// - /// Undefined. - /// - Undefined = 5, - - /// - /// Default. - /// - Default = RemoteSigned, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor +{ + /// + /// Processor policy. + /// For Processor type Default and Hosted they mean the same as PowerShell ExecutionPolicy. + /// https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies. + /// + public enum PowerShellConfigurationProcessorPolicy + { + /// + /// Unrestricted. + /// + Unrestricted = 0, + + /// + /// RemoteSigned. + /// + RemoteSigned = 1, + + /// + /// AllSigned. + /// + AllSigned = 2, + + /// + /// Restricted. + /// + Restricted = 3, + + /// + /// Bypass. + /// + Bypass = 4, + + /// + /// Undefined. + /// + Undefined = 5, + + /// + /// Default. + /// + Default = RemoteSigned, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorType.cs b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorType.cs index 195ec5baba..fa13dfc21d 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorType.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationProcessorType.cs @@ -1,24 +1,24 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor -{ - /// - /// Configuration processor runspace type. - /// - public enum PowerShellConfigurationProcessorType - { - /// - /// Uses default runspace. Requires to be running in PowerShell. Uses current runspace. - /// - Default, - - /// - /// Creates a new runspace in a hosted environment. - /// - Hosted, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor +{ + /// + /// Configuration processor runspace type. + /// + public enum PowerShellConfigurationProcessorType + { + /// + /// Uses default runspace. Requires to be running in PowerShell. Uses current runspace. + /// + Default, + + /// + /// Creates a new runspace in a hosted environment. + /// + Hosted, + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs index 257052a6a6..c76d7f03bb 100644 --- a/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs +++ b/src/Microsoft.Management.Configuration.Processor/Public/PowerShellConfigurationSetProcessorFactory.cs @@ -1,327 +1,327 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Text; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Factory; - using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.PowerShell.Set; - using Microsoft.Management.Configuration.SetProcessorFactory; - using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; - - /// - /// IConfigurationSetProcessorFactory implementation using PowerShell DSC v2. - /// -#if WinGetCsWinRTEmbedded - internal -#else - public -#endif - sealed partial class PowerShellConfigurationSetProcessorFactory : ConfigurationSetProcessorFactoryBase, IConfigurationSetProcessorFactory, IPowerShellConfigurationProcessorFactoryProperties, IPwshConfigurationSetProcessorFactoryProperties - { - // Backing variables for properties that are restricted in limit mode. - private PowerShellConfigurationProcessorType processorType = PowerShellConfigurationProcessorType.Default; - private IReadOnlyList? additionalModulePaths; - private IReadOnlyList? implicitModulePaths; - private PowerShellConfigurationProcessorPolicy policy = PowerShellConfigurationProcessorPolicy.Default; - private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.Default; - private string? customLocation; - - /// - /// Initializes a new instance of the class. - /// - public PowerShellConfigurationSetProcessorFactory() - { - } - - /// - /// Gets or sets the processor type. - /// - public PowerShellConfigurationProcessorType ProcessorType - { - get - { - return this.processorType; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting ProcessorType in limit mode is invalid."); - } - - this.processorType = value; - } - } - - /// - /// Gets or sets the additional module paths. - /// - public IReadOnlyList? AdditionalModulePaths - { - get - { - return this.additionalModulePaths; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting AdditionalModulePaths in limit mode is invalid."); - } - - // Create a copy of incoming value - List newModulePaths = new List(); - if (value != null) - { - foreach (string path in value) - { - newModulePaths.Add(path); - } - } - - // Add implicit module paths if applicable - if (this.implicitModulePaths != null) - { - foreach (string path in this.implicitModulePaths) - { - if (!newModulePaths.Contains(path)) - { - newModulePaths.Add(path); - } - } - } - - this.additionalModulePaths = newModulePaths; - } - } - - /// - /// Gets or sets the implicit module paths. These paths are always included in AdditionalModulePaths. - /// - public IReadOnlyList? ImplicitModulePaths - { - get - { - return this.implicitModulePaths; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting ImplicitModulePaths in limit mode is invalid."); - } - - this.implicitModulePaths = value; - - // Apply to additional module paths if applicable. - if (this.implicitModulePaths != null) - { - List newModulePaths = new List(); - if (this.additionalModulePaths != null) - { - foreach (string path in this.additionalModulePaths) - { - newModulePaths.Add(path); - } - } - - foreach (string path in this.implicitModulePaths) - { - if (!newModulePaths.Contains(path)) - { - newModulePaths.Add(path); - } - } - - this.additionalModulePaths = newModulePaths; - } - } - } - - /// - /// Gets or sets the configuration policy. - /// - public PowerShellConfigurationProcessorPolicy Policy - { - get - { - return this.policy; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting Policy in limit mode is invalid."); - } - - this.policy = value; - } - } - - /// - /// Gets or sets the configuration policy. - /// - PwshConfigurationProcessorPolicy IPwshConfigurationSetProcessorFactoryProperties.Policy - { - get { return Helpers.TypeHelpers.ToPwshConfigurationProcessorPolicy(this.Policy); } - set { this.Policy = Helpers.TypeHelpers.ToPowerShellConfigurationProcessorPolicy(value); } - } - - /// - /// Gets or sets the module location. - /// - public PowerShellConfigurationProcessorLocation Location - { - get - { - return this.location; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting Location in limit mode is invalid."); - } - - this.location = value; - } - } - - /// - /// Gets or sets the module location. - /// - PwshConfigurationProcessorLocation IPwshConfigurationSetProcessorFactoryProperties.Location - { - get { return Helpers.TypeHelpers.ToPwshConfigurationProcessorLocation(this.Location); } - set { this.Location = Helpers.TypeHelpers.ToPowerShellConfigurationProcessorLocation(value); } - } - - /// - /// Gets or sets the install module path. Only used for Scope = Custom. - /// - public string? CustomLocation - { - get - { - return this.customLocation; - } - - set - { - if (this.IsLimitMode()) - { - throw new InvalidOperationException("Setting CustomLocation in limit mode is invalid."); - } - - this.customLocation = value; - } - } - - /// - /// Gets the winget module path. - /// - /// The winget module path. - internal static string GetWinGetModulePath() - { - return Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), - @"Microsoft\WinGet\Configuration\Modules"); - } - - /// - /// Sends diagnostic if appropriate for PowerShell streams. - /// - /// The level of this diagnostic message. - /// The PowerShell object. - internal void OnDiagnostics(DiagnosticLevel level, System.Management.Automation.PowerShell pwsh) - { - if (this.AreDiagnosticsEnabled() && level >= this.MinimumLevel && pwsh.HadErrors) - { - var builder = new StringBuilder(); - - // There are the last commands ran by that PowerShell obj, not all in our session. - builder.Append("PowerShellCommands: "); - foreach (var c in pwsh.Commands.Commands) - { - builder.Append($"['{c.CommandText}'"); - if (c.Parameters.Count > 0) - { - builder.Append(" Parameters: "); - foreach (var p in c.Parameters) - { - builder.Append($"{p.Name} = '{p.Value}' "); - } - - builder.Append("]"); - } - - builder.AppendLine(); - } - - foreach (var error in pwsh.Streams.Error) - { - builder.AppendLine($"[WriteError] {error}"); - } - - this.OnDiagnostics(level, builder.ToString()); - } - } - - /// - protected override IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode) - { - var envFactory = new ProcessorEnvironmentFactory(this.ProcessorType); - var processorEnvironment = envFactory.CreateEnvironment( - this, - this.Policy); - - if (this.AdditionalModulePaths is not null) - { - processorEnvironment.PrependPSModulePaths(this.AdditionalModulePaths); - } - - // Always add the winget path. - var wingetModulePath = GetWinGetModulePath(); - processorEnvironment.PrependPSModulePath(wingetModulePath); - if (this.Location == PowerShellConfigurationProcessorLocation.WinGetModulePath) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, "Using winget module path"); - processorEnvironment.SetLocation(PowerShellConfigurationProcessorLocation.Custom, wingetModulePath); - } - else if (this.Location == PowerShellConfigurationProcessorLocation.Custom) - { - if (string.IsNullOrEmpty(this.CustomLocation)) - { - throw new ArgumentNullException(nameof(this.CustomLocation)); - } - - processorEnvironment.SetLocation(this.Location, this.CustomLocation); - processorEnvironment.PrependPSModulePath(this.CustomLocation); - } - else - { - processorEnvironment.SetLocation(this.Location, null); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $" Effective module path:\n{processorEnvironment.GetVariable(Variables.PSModulePath)}"); - - processorEnvironment.ValidateRunspace(); - - return new PowerShellConfigurationSetProcessor(processorEnvironment, set, isLimitMode) { SetProcessorFactory = this }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Text; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Factory; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Set; + using Microsoft.Management.Configuration.SetProcessorFactory; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// + /// IConfigurationSetProcessorFactory implementation using PowerShell DSC v2. + /// +#if WinGetCsWinRTEmbedded + internal +#else + public +#endif + sealed partial class PowerShellConfigurationSetProcessorFactory : ConfigurationSetProcessorFactoryBase, IConfigurationSetProcessorFactory, IPowerShellConfigurationProcessorFactoryProperties, IPwshConfigurationSetProcessorFactoryProperties + { + // Backing variables for properties that are restricted in limit mode. + private PowerShellConfigurationProcessorType processorType = PowerShellConfigurationProcessorType.Default; + private IReadOnlyList? additionalModulePaths; + private IReadOnlyList? implicitModulePaths; + private PowerShellConfigurationProcessorPolicy policy = PowerShellConfigurationProcessorPolicy.Default; + private PowerShellConfigurationProcessorLocation location = PowerShellConfigurationProcessorLocation.Default; + private string? customLocation; + + /// + /// Initializes a new instance of the class. + /// + public PowerShellConfigurationSetProcessorFactory() + { + } + + /// + /// Gets or sets the processor type. + /// + public PowerShellConfigurationProcessorType ProcessorType + { + get + { + return this.processorType; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting ProcessorType in limit mode is invalid."); + } + + this.processorType = value; + } + } + + /// + /// Gets or sets the additional module paths. + /// + public IReadOnlyList? AdditionalModulePaths + { + get + { + return this.additionalModulePaths; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting AdditionalModulePaths in limit mode is invalid."); + } + + // Create a copy of incoming value + List newModulePaths = new List(); + if (value != null) + { + foreach (string path in value) + { + newModulePaths.Add(path); + } + } + + // Add implicit module paths if applicable + if (this.implicitModulePaths != null) + { + foreach (string path in this.implicitModulePaths) + { + if (!newModulePaths.Contains(path)) + { + newModulePaths.Add(path); + } + } + } + + this.additionalModulePaths = newModulePaths; + } + } + + /// + /// Gets or sets the implicit module paths. These paths are always included in AdditionalModulePaths. + /// + public IReadOnlyList? ImplicitModulePaths + { + get + { + return this.implicitModulePaths; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting ImplicitModulePaths in limit mode is invalid."); + } + + this.implicitModulePaths = value; + + // Apply to additional module paths if applicable. + if (this.implicitModulePaths != null) + { + List newModulePaths = new List(); + if (this.additionalModulePaths != null) + { + foreach (string path in this.additionalModulePaths) + { + newModulePaths.Add(path); + } + } + + foreach (string path in this.implicitModulePaths) + { + if (!newModulePaths.Contains(path)) + { + newModulePaths.Add(path); + } + } + + this.additionalModulePaths = newModulePaths; + } + } + } + + /// + /// Gets or sets the configuration policy. + /// + public PowerShellConfigurationProcessorPolicy Policy + { + get + { + return this.policy; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting Policy in limit mode is invalid."); + } + + this.policy = value; + } + } + + /// + /// Gets or sets the configuration policy. + /// + PwshConfigurationProcessorPolicy IPwshConfigurationSetProcessorFactoryProperties.Policy + { + get { return Helpers.TypeHelpers.ToPwshConfigurationProcessorPolicy(this.Policy); } + set { this.Policy = Helpers.TypeHelpers.ToPowerShellConfigurationProcessorPolicy(value); } + } + + /// + /// Gets or sets the module location. + /// + public PowerShellConfigurationProcessorLocation Location + { + get + { + return this.location; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting Location in limit mode is invalid."); + } + + this.location = value; + } + } + + /// + /// Gets or sets the module location. + /// + PwshConfigurationProcessorLocation IPwshConfigurationSetProcessorFactoryProperties.Location + { + get { return Helpers.TypeHelpers.ToPwshConfigurationProcessorLocation(this.Location); } + set { this.Location = Helpers.TypeHelpers.ToPowerShellConfigurationProcessorLocation(value); } + } + + /// + /// Gets or sets the install module path. Only used for Scope = Custom. + /// + public string? CustomLocation + { + get + { + return this.customLocation; + } + + set + { + if (this.IsLimitMode()) + { + throw new InvalidOperationException("Setting CustomLocation in limit mode is invalid."); + } + + this.customLocation = value; + } + } + + /// + /// Gets the winget module path. + /// + /// The winget module path. + internal static string GetWinGetModulePath() + { + return Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + @"Microsoft\WinGet\Configuration\Modules"); + } + + /// + /// Sends diagnostic if appropriate for PowerShell streams. + /// + /// The level of this diagnostic message. + /// The PowerShell object. + internal void OnDiagnostics(DiagnosticLevel level, System.Management.Automation.PowerShell pwsh) + { + if (this.AreDiagnosticsEnabled() && level >= this.MinimumLevel && pwsh.HadErrors) + { + var builder = new StringBuilder(); + + // There are the last commands ran by that PowerShell obj, not all in our session. + builder.Append("PowerShellCommands: "); + foreach (var c in pwsh.Commands.Commands) + { + builder.Append($"['{c.CommandText}'"); + if (c.Parameters.Count > 0) + { + builder.Append(" Parameters: "); + foreach (var p in c.Parameters) + { + builder.Append($"{p.Name} = '{p.Value}' "); + } + + builder.Append("]"); + } + + builder.AppendLine(); + } + + foreach (var error in pwsh.Streams.Error) + { + builder.AppendLine($"[WriteError] {error}"); + } + + this.OnDiagnostics(level, builder.ToString()); + } + } + + /// + protected override IConfigurationSetProcessor CreateSetProcessorInternal(ConfigurationSet? set, bool isLimitMode) + { + var envFactory = new ProcessorEnvironmentFactory(this.ProcessorType); + var processorEnvironment = envFactory.CreateEnvironment( + this, + this.Policy); + + if (this.AdditionalModulePaths is not null) + { + processorEnvironment.PrependPSModulePaths(this.AdditionalModulePaths); + } + + // Always add the winget path. + var wingetModulePath = GetWinGetModulePath(); + processorEnvironment.PrependPSModulePath(wingetModulePath); + if (this.Location == PowerShellConfigurationProcessorLocation.WinGetModulePath) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, "Using winget module path"); + processorEnvironment.SetLocation(PowerShellConfigurationProcessorLocation.Custom, wingetModulePath); + } + else if (this.Location == PowerShellConfigurationProcessorLocation.Custom) + { + if (string.IsNullOrEmpty(this.CustomLocation)) + { + throw new ArgumentNullException(nameof(this.CustomLocation)); + } + + processorEnvironment.SetLocation(this.Location, this.CustomLocation); + processorEnvironment.PrependPSModulePath(this.CustomLocation); + } + else + { + processorEnvironment.SetLocation(this.Location, null); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $" Effective module path:\n{processorEnvironment.GetVariable(Variables.PSModulePath)}"); + + processorEnvironment.ValidateRunspace(); + + return new PowerShellConfigurationSetProcessor(processorEnvironment, set, isLimitMode) { SetProcessorFactory = this }; + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs index e1d70a47d5..5607888087 100644 --- a/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs +++ b/src/Microsoft.Management.Configuration.Processor/Set/ConfigurationSetProcessorBase.cs @@ -1,259 +1,259 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Set -{ - using System; - using System.Collections.Generic; - using System.Runtime.CompilerServices; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.Processor.Factory; - using Microsoft.Management.Configuration.Processor.Unit; - - /// - /// IConfigurationSetProcessor base implementation. - /// - internal abstract partial class ConfigurationSetProcessorBase - { - private readonly ConfigurationSet? configurationSet; - private List limitUnitList = new List(); - - /// - /// Initializes a new instance of the class. - /// - /// Configuration set. - /// Whether the set processor should work in limitation mode. - public ConfigurationSetProcessorBase(ConfigurationSet? configurationSet, bool isLimitMode) - { - this.configurationSet = configurationSet; - this.IsLimitMode = isLimitMode; - - // In limit mode, configurationSet is the limitation set to be used. It cannot be null. - if (this.IsLimitMode) - { - if (this.configurationSet == null) - { - throw new ArgumentNullException(nameof(configurationSet), "configurationSet is required in limit mode."); - } - - foreach (var unit in this.configurationSet.Units) - { - this.limitUnitList.Add(unit); - } - } - } - - /// - /// Gets or initializes the set processor factory. - /// - internal ConfigurationSetProcessorFactoryBase? SetProcessorFactory { get; init; } - - /// - /// Gets a value indicating whether the set processor is running in limit mode. - /// - internal bool IsLimitMode { get; private set; } - - /// - /// Gets the configuration set for this processor. - /// - protected ConfigurationSet? ConfigurationSet - { - get { return this.configurationSet; } - } - - /// - /// Creates a configuration unit processor for the given unit. - /// - /// Configuration unit. - /// A configuration unit processor. - public IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit incomingUnit) - { - try - { - this.OnDiagnostics(DiagnosticLevel.Informational, $"CreateUnitProcessor is running in limit mode: {this.IsLimitMode}."); - - // CreateUnitProcessor can only be called once on each configuration unit in limit mode. - var unit = this.GetConfigurationUnit(incomingUnit, true); - - IConfigurationUnitProcessor result = this.CreateUnitProcessorInternal(unit); - - this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating unit processor."); - - return result; - } - catch (Exception ex) - { - this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); - throw; - } - } - - /// - /// Gets the configuration unit processor details for the given unit. - /// - /// Configuration unit. - /// Detail flags. - /// Configuration unit processor details. - public IConfigurationUnitProcessorDetails? GetUnitProcessorDetails(ConfigurationUnit incomingUnit, ConfigurationUnitDetailFlags detailFlags) - { - try - { - this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); - - // GetUnitProcessorDetails can be invoked multiple times on each configuration unit in limit mode. - var unit = this.GetConfigurationUnit(incomingUnit); - - return this.GetUnitProcessorDetailsInternal(unit, detailFlags); - } - catch (Exception ex) - { - this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); - throw; - } - } - - /// - /// Gets all configuration units for the given unit type. - /// Returned units may be of types other than the one passed in. - /// - /// Find unit processors options. - /// A list of unit processor details. - public IList FindUnitProcessors(FindUnitProcessorsOptions findOptions) - { - try - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `FindUnitProcessors` ..."); - - return this.FindUnitProcessorsInternal(findOptions); - } - catch (Exception ex) - { - this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); - throw; - } - } - - /// - /// Creates a configuration unit processor for the given unit. - /// - /// Configuration unit. - /// A configuration unit processor. - protected abstract IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit); - - /// - /// Gets the configuration unit processor details for the given unit. - /// - /// Configuration unit. - /// Detail flags. - /// Configuration unit processor details. - protected abstract IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); - - /// - /// Finds unit processors based on the input FindUnitProcessorsOptions. - /// Derive from IFindUnitProcessorsSetProcessor and implement an override to support this. - /// - /// Find unit processors options. - /// A list of unit processor details. - protected virtual IList FindUnitProcessorsInternal(FindUnitProcessorsOptions findOptions) - { - throw new NotImplementedException("Configuration set processor did not implement FindUnitProcessorsInternal."); - } - - /// - /// Sends diagnostics to factory. - /// - /// The level of this diagnostic message. - /// The diagnostic message. - protected void OnDiagnostics(DiagnosticLevel level, string message) - { - this.SetProcessorFactory?.OnDiagnostics(level, message); - } - - private bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) - { - var firstIdentifier = first.Identifier; - var firstIntent = first.Intent; - var firstType = first.Type; - var secondIdentifier = second.Identifier; - var secondType = second.Type; - var secondIntent = second.Intent; - - if (firstIdentifier != secondIdentifier) - { - return false; - } - - if (firstType != secondType || - firstIntent != secondIntent) - { - this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of type or intent."); - return false; - } - - var firstEnvironment = first.Environment; - var secondEnvironment = second.Environment; - if (firstEnvironment.Context != secondEnvironment.Context || - firstEnvironment.ProcessorIdentifier != secondEnvironment.ProcessorIdentifier || - !firstEnvironment.ProcessorProperties.ContentEquals(secondEnvironment.ProcessorProperties)) - { - this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of environment."); - return false; - } - - if (!first.Settings.ContentEquals(second.Settings)) - { - this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of settings."); - return false; - } - - if (!first.Metadata.ContentEquals(second.Metadata)) - { - this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of metadata."); - return false; - } - - // Note: Consider group units logic when group units are supported. - return true; - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private ConfigurationUnit GetConfigurationUnit(ConfigurationUnit incomingUnit, bool useLimitList = false) - { - if (this.IsLimitMode) - { - if (this.configurationSet == null) - { - throw new InvalidOperationException("Configuration set should not be null in limit mode."); - } - - var unitList = useLimitList ? this.limitUnitList : this.configurationSet.Units; - - for (int i = 0; i < unitList.Count; i++) - { - var unit = unitList[i]; - if (this.ConfigurationUnitEquals(incomingUnit, unit)) - { - if (useLimitList) - { - this.limitUnitList.RemoveAt(i); - } - - return unit; - } - - // Note: Consider group units logic when group units are supported. - } - - this.OnDiagnostics(DiagnosticLevel.Error, "Configuration unit match not found in limit mode."); - throw new InvalidOperationException("Configuration unit match not found in limit mode."); - } - else - { - return incomingUnit; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Set +{ + using System; + using System.Collections.Generic; + using System.Runtime.CompilerServices; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.Processor.Factory; + using Microsoft.Management.Configuration.Processor.Unit; + + /// + /// IConfigurationSetProcessor base implementation. + /// + internal abstract partial class ConfigurationSetProcessorBase + { + private readonly ConfigurationSet? configurationSet; + private List limitUnitList = new List(); + + /// + /// Initializes a new instance of the class. + /// + /// Configuration set. + /// Whether the set processor should work in limitation mode. + public ConfigurationSetProcessorBase(ConfigurationSet? configurationSet, bool isLimitMode) + { + this.configurationSet = configurationSet; + this.IsLimitMode = isLimitMode; + + // In limit mode, configurationSet is the limitation set to be used. It cannot be null. + if (this.IsLimitMode) + { + if (this.configurationSet == null) + { + throw new ArgumentNullException(nameof(configurationSet), "configurationSet is required in limit mode."); + } + + foreach (var unit in this.configurationSet.Units) + { + this.limitUnitList.Add(unit); + } + } + } + + /// + /// Gets or initializes the set processor factory. + /// + internal ConfigurationSetProcessorFactoryBase? SetProcessorFactory { get; init; } + + /// + /// Gets a value indicating whether the set processor is running in limit mode. + /// + internal bool IsLimitMode { get; private set; } + + /// + /// Gets the configuration set for this processor. + /// + protected ConfigurationSet? ConfigurationSet + { + get { return this.configurationSet; } + } + + /// + /// Creates a configuration unit processor for the given unit. + /// + /// Configuration unit. + /// A configuration unit processor. + public IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit incomingUnit) + { + try + { + this.OnDiagnostics(DiagnosticLevel.Informational, $"CreateUnitProcessor is running in limit mode: {this.IsLimitMode}."); + + // CreateUnitProcessor can only be called once on each configuration unit in limit mode. + var unit = this.GetConfigurationUnit(incomingUnit, true); + + IConfigurationUnitProcessor result = this.CreateUnitProcessorInternal(unit); + + this.OnDiagnostics(DiagnosticLevel.Verbose, "... done creating unit processor."); + + return result; + } + catch (Exception ex) + { + this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); + throw; + } + } + + /// + /// Gets the configuration unit processor details for the given unit. + /// + /// Configuration unit. + /// Detail flags. + /// Configuration unit processor details. + public IConfigurationUnitProcessorDetails? GetUnitProcessorDetails(ConfigurationUnit incomingUnit, ConfigurationUnitDetailFlags detailFlags) + { + try + { + this.OnDiagnostics(DiagnosticLevel.Informational, $"GetUnitProcessorDetails is running in limit mode: {this.IsLimitMode}."); + + // GetUnitProcessorDetails can be invoked multiple times on each configuration unit in limit mode. + var unit = this.GetConfigurationUnit(incomingUnit); + + return this.GetUnitProcessorDetailsInternal(unit, detailFlags); + } + catch (Exception ex) + { + this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); + throw; + } + } + + /// + /// Gets all configuration units for the given unit type. + /// Returned units may be of types other than the one passed in. + /// + /// Find unit processors options. + /// A list of unit processor details. + public IList FindUnitProcessors(FindUnitProcessorsOptions findOptions) + { + try + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `FindUnitProcessors` ..."); + + return this.FindUnitProcessorsInternal(findOptions); + } + catch (Exception ex) + { + this.OnDiagnostics(DiagnosticLevel.Error, ex.ToString()); + throw; + } + } + + /// + /// Creates a configuration unit processor for the given unit. + /// + /// Configuration unit. + /// A configuration unit processor. + protected abstract IConfigurationUnitProcessor CreateUnitProcessorInternal(ConfigurationUnit unit); + + /// + /// Gets the configuration unit processor details for the given unit. + /// + /// Configuration unit. + /// Detail flags. + /// Configuration unit processor details. + protected abstract IConfigurationUnitProcessorDetails? GetUnitProcessorDetailsInternal(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); + + /// + /// Finds unit processors based on the input FindUnitProcessorsOptions. + /// Derive from IFindUnitProcessorsSetProcessor and implement an override to support this. + /// + /// Find unit processors options. + /// A list of unit processor details. + protected virtual IList FindUnitProcessorsInternal(FindUnitProcessorsOptions findOptions) + { + throw new NotImplementedException("Configuration set processor did not implement FindUnitProcessorsInternal."); + } + + /// + /// Sends diagnostics to factory. + /// + /// The level of this diagnostic message. + /// The diagnostic message. + protected void OnDiagnostics(DiagnosticLevel level, string message) + { + this.SetProcessorFactory?.OnDiagnostics(level, message); + } + + private bool ConfigurationUnitEquals(ConfigurationUnit first, ConfigurationUnit second) + { + var firstIdentifier = first.Identifier; + var firstIntent = first.Intent; + var firstType = first.Type; + var secondIdentifier = second.Identifier; + var secondType = second.Type; + var secondIntent = second.Intent; + + if (firstIdentifier != secondIdentifier) + { + return false; + } + + if (firstType != secondType || + firstIntent != secondIntent) + { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of type or intent."); + return false; + } + + var firstEnvironment = first.Environment; + var secondEnvironment = second.Environment; + if (firstEnvironment.Context != secondEnvironment.Context || + firstEnvironment.ProcessorIdentifier != secondEnvironment.ProcessorIdentifier || + !firstEnvironment.ProcessorProperties.ContentEquals(secondEnvironment.ProcessorProperties)) + { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of environment."); + return false; + } + + if (!first.Settings.ContentEquals(second.Settings)) + { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of settings."); + return false; + } + + if (!first.Metadata.ContentEquals(second.Metadata)) + { + this.OnDiagnostics(DiagnosticLevel.Error, $"Configuration unit `{firstIdentifier}` mismatch of metadata."); + return false; + } + + // Note: Consider group units logic when group units are supported. + return true; + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private ConfigurationUnit GetConfigurationUnit(ConfigurationUnit incomingUnit, bool useLimitList = false) + { + if (this.IsLimitMode) + { + if (this.configurationSet == null) + { + throw new InvalidOperationException("Configuration set should not be null in limit mode."); + } + + var unitList = useLimitList ? this.limitUnitList : this.configurationSet.Units; + + for (int i = 0; i < unitList.Count; i++) + { + var unit = unitList[i]; + if (this.ConfigurationUnitEquals(incomingUnit, unit)) + { + if (useLimitList) + { + this.limitUnitList.RemoveAt(i); + } + + return unit; + } + + // Note: Consider group units logic when group units are supported. + } + + this.OnDiagnostics(DiagnosticLevel.Error, "Configuration unit match not found in limit mode."); + throw new InvalidOperationException("Configuration unit match not found in limit mode."); + } + else + { + return incomingUnit; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ApplySettingsResult.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ApplySettingsResult.cs index a342ae59df..61c8dba051 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/ApplySettingsResult.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/ApplySettingsResult.cs @@ -1,44 +1,44 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using Microsoft.Management.Configuration; - - /// - /// Implements IApplySettingsResult. - /// - internal sealed partial class ApplySettingsResult : IApplySettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public ApplySettingsResult(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); - - /// - public bool RebootRequired { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using Microsoft.Management.Configuration; + + /// + /// Implements IApplySettingsResult. + /// + internal sealed partial class ApplySettingsResult : IApplySettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public ApplySettingsResult(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); + + /// + public bool RebootRequired { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs index 2956a6feb0..6c1697d86b 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorBase.cs @@ -1,308 +1,308 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using System; - using System.Collections.Generic; - using System.ComponentModel; - using System.Runtime.CompilerServices; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.Processor.Factory; - using Microsoft.Management.Configuration.Processor.Helpers; - using Windows.Foundation.Collections; - - /// - /// IConfigurationUnitProcessor base implementation. - /// - internal abstract partial class ConfigurationUnitProcessorBase - { - private readonly ConfigurationUnitInternal unitInternal; - private readonly bool isLimitMode; - - private bool isTestInvoked = false; - private bool isApplyInvoked = false; - - /// - /// Initializes a new instance of the class. - /// - /// UnitInternal. - /// Whether it is under limit mode. - internal ConfigurationUnitProcessorBase(ConfigurationUnitInternal unitInternal, bool isLimitMode) - { - this.unitInternal = unitInternal; - this.isLimitMode = isLimitMode; - } - - /// - /// Gets the configuration unit that the processor was created for. - /// - public ConfigurationUnit Unit => this.unitInternal.Unit; - - /// - /// Gets or initializes the set processor factory. - /// - internal ConfigurationSetProcessorFactoryBase? SetProcessorFactory { get; init; } - - /// - /// Gets the internal configuration unit. - /// - protected ConfigurationUnitInternal UnitInternal => this.unitInternal; - - /// - /// Gets the current system state for the configuration unit. - /// Calls Get on the DSC resource. - /// - /// A . - public IGetSettingsResult GetSettings() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Get` for resource: {this.unitInternal.QualifiedName}..."); - - this.CheckLimitMode(ConfigurationUnitIntent.Inform); - var result = new GetSettingsResult(this.Unit); - - try - { - result.Settings = this.GetSettingsInternal(); - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Get`."); - return result; - } - - /// - /// Determines if the system is already in the state described by the configuration unit. - /// Calls Test on the DSC resource. - /// - /// A . - public ITestSettingsResult TestSettings() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Test` for resource: {this.unitInternal.QualifiedName}..."); - - if (this.Unit.Intent == ConfigurationUnitIntent.Inform) - { - this.OnDiagnostics(DiagnosticLevel.Error, "`Test` should not be called on a unit with intent of `Inform`"); - throw new NotSupportedException(); - } - - this.CheckLimitMode(ConfigurationUnitIntent.Assert); - var result = new TestSettingsResult(this.Unit); - result.TestResult = ConfigurationTestResult.Failed; - try - { - bool testResult = this.TestSettingsInternal(); - - result.TestResult = testResult ? ConfigurationTestResult.Positive : ConfigurationTestResult.Negative; - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Test`."); - return result; - } - - /// - /// Applies the state described in the configuration unit. - /// Calls Set in the DSC resource. - /// - /// A . - public IApplySettingsResult ApplySettings() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Apply` for resource: {this.unitInternal.QualifiedName}..."); - - if (this.Unit.Intent == ConfigurationUnitIntent.Inform || - this.Unit.Intent == ConfigurationUnitIntent.Assert) - { - this.OnDiagnostics(DiagnosticLevel.Error, $"`Apply` should not be called on a unit with intent of `{this.Unit.Intent}`"); - throw new NotSupportedException(); - } - - this.CheckLimitMode(ConfigurationUnitIntent.Apply); - var result = new ApplySettingsResult(this.Unit); - try - { - result.RebootRequired = this.ApplySettingsInternal(); - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Apply`."); - return result; - } - - /// - /// Gets the current settings for all the instances of a configuration unit. - /// - /// A . - public IGetAllSettingsResult GetAllSettings() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `GetAllSettings` for resource: {this.unitInternal.QualifiedName}..."); - - this.CheckLimitMode(ConfigurationUnitIntent.Inform); - var result = new GetAllSettingsResult(this.Unit); - - try - { - result.Settings = this.GetAllSettingsInternal(); - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `GetAllSettings`."); - return result; - } - - /// - /// Gets all configuration units for the given unit type. - /// Returned units may be of types other than the one passed in. - /// - /// A . - public IGetAllUnitsResult GetAllUnits() - { - this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `GetAllUnits` for resource: {this.unitInternal.QualifiedName}..."); - - this.CheckLimitMode(ConfigurationUnitIntent.Inform); - var result = new GetAllUnitsResult(this.Unit); - - try - { - result.Units = this.GetAllUnitsInternal(); - } - catch (Exception e) - { - this.ExtractExceptionInformation(e, result.InternalResult); - } - - this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `GetAllUnits`."); - return result; - } - - /// - /// Gets the current settings. - /// - /// The current settings. - protected abstract ValueSet GetSettingsInternal(); - - /// - /// Tests the current settings. - /// - /// A boolean indicating whether the settings are in the desired state. - protected abstract bool TestSettingsInternal(); - - /// - /// Applies the desired settings. - /// - /// A boolean indicating whether a reboot is required. - protected abstract bool ApplySettingsInternal(); - - /// - /// Gets the current settings for all the instances of a configuration unit. - /// Derive from IGetAllSettingsConfigurationUnitProcessor and implement an override to support this. - /// - /// The settings as ValueSets. - protected virtual IList? GetAllSettingsInternal() - { - throw new NotImplementedException("Configuration unit processor did not implement GetAllSettingsInternal."); - } - - /// - /// Gets all configuration units for the given unit type. - /// Returned units may be of types other than the one passed in. - /// Derive from IGetAllUnitsConfigurationUnitProcessor and implement an override to support this. - /// - /// The configuration units. - protected virtual IList? GetAllUnitsInternal() - { - throw new NotImplementedException("Configuration unit processor did not implement GetAllUnitsInternal."); - } - - /// - /// Sends diagnostics if appropriate. - /// - /// The level of this diagnostic message. - /// The diagnostic message. - protected void OnDiagnostics(DiagnosticLevel level, string message) - { - this.SetProcessorFactory?.OnDiagnostics(level, message); - } - - private void ExtractExceptionInformation(Exception e, ConfigurationUnitResultInformation resultInformation) - { - this.OnDiagnostics(DiagnosticLevel.Verbose, e.ToString()); - - IConfigurationUnitResultException? configurationUnitResultException = e as IConfigurationUnitResultException; - if (configurationUnitResultException != null) - { - resultInformation.ResultCode = e; - resultInformation.Description = configurationUnitResultException.Description; - resultInformation.Details = configurationUnitResultException.Details; - resultInformation.ResultSource = configurationUnitResultException.ResultSource; - } - else - { - var inner = e.GetMostInnerException(); - resultInformation.ResultCode = inner; - resultInformation.Description = e.Message; - resultInformation.Details = e.ToString(); - resultInformation.ResultSource = ConfigurationUnitResultSource.Internal; - } - } - - [MethodImpl(MethodImplOptions.Synchronized)] - private void CheckLimitMode(ConfigurationUnitIntent intent) - { - if (!this.isLimitMode) - { - return; - } - - if (intent == ConfigurationUnitIntent.Unknown) - { - throw new InvalidEnumArgumentException(nameof(ConfigurationUnitIntent.Unknown)); - } - - if (intent == ConfigurationUnitIntent.Assert) - { - if (this.isTestInvoked) - { - this.OnDiagnostics(DiagnosticLevel.Error, "TestSettings is already invoked in limit mode."); - throw new InvalidOperationException("TestSettings is already invoked in limit mode."); - } - else - { - this.isTestInvoked = true; - } - } - - if (intent == ConfigurationUnitIntent.Apply) - { - if (this.isApplyInvoked) - { - this.OnDiagnostics(DiagnosticLevel.Error, "ApplySettings is already invoked in limit mode."); - throw new InvalidOperationException("ApplySettings is already invoked in limit mode."); - } - else - { - this.isApplyInvoked = true; - } - } - - // Get is always allowed now. - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using System; + using System.Collections.Generic; + using System.ComponentModel; + using System.Runtime.CompilerServices; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.Processor.Factory; + using Microsoft.Management.Configuration.Processor.Helpers; + using Windows.Foundation.Collections; + + /// + /// IConfigurationUnitProcessor base implementation. + /// + internal abstract partial class ConfigurationUnitProcessorBase + { + private readonly ConfigurationUnitInternal unitInternal; + private readonly bool isLimitMode; + + private bool isTestInvoked = false; + private bool isApplyInvoked = false; + + /// + /// Initializes a new instance of the class. + /// + /// UnitInternal. + /// Whether it is under limit mode. + internal ConfigurationUnitProcessorBase(ConfigurationUnitInternal unitInternal, bool isLimitMode) + { + this.unitInternal = unitInternal; + this.isLimitMode = isLimitMode; + } + + /// + /// Gets the configuration unit that the processor was created for. + /// + public ConfigurationUnit Unit => this.unitInternal.Unit; + + /// + /// Gets or initializes the set processor factory. + /// + internal ConfigurationSetProcessorFactoryBase? SetProcessorFactory { get; init; } + + /// + /// Gets the internal configuration unit. + /// + protected ConfigurationUnitInternal UnitInternal => this.unitInternal; + + /// + /// Gets the current system state for the configuration unit. + /// Calls Get on the DSC resource. + /// + /// A . + public IGetSettingsResult GetSettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Get` for resource: {this.unitInternal.QualifiedName}..."); + + this.CheckLimitMode(ConfigurationUnitIntent.Inform); + var result = new GetSettingsResult(this.Unit); + + try + { + result.Settings = this.GetSettingsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Get`."); + return result; + } + + /// + /// Determines if the system is already in the state described by the configuration unit. + /// Calls Test on the DSC resource. + /// + /// A . + public ITestSettingsResult TestSettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Test` for resource: {this.unitInternal.QualifiedName}..."); + + if (this.Unit.Intent == ConfigurationUnitIntent.Inform) + { + this.OnDiagnostics(DiagnosticLevel.Error, "`Test` should not be called on a unit with intent of `Inform`"); + throw new NotSupportedException(); + } + + this.CheckLimitMode(ConfigurationUnitIntent.Assert); + var result = new TestSettingsResult(this.Unit); + result.TestResult = ConfigurationTestResult.Failed; + try + { + bool testResult = this.TestSettingsInternal(); + + result.TestResult = testResult ? ConfigurationTestResult.Positive : ConfigurationTestResult.Negative; + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Test`."); + return result; + } + + /// + /// Applies the state described in the configuration unit. + /// Calls Set in the DSC resource. + /// + /// A . + public IApplySettingsResult ApplySettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `Apply` for resource: {this.unitInternal.QualifiedName}..."); + + if (this.Unit.Intent == ConfigurationUnitIntent.Inform || + this.Unit.Intent == ConfigurationUnitIntent.Assert) + { + this.OnDiagnostics(DiagnosticLevel.Error, $"`Apply` should not be called on a unit with intent of `{this.Unit.Intent}`"); + throw new NotSupportedException(); + } + + this.CheckLimitMode(ConfigurationUnitIntent.Apply); + var result = new ApplySettingsResult(this.Unit); + try + { + result.RebootRequired = this.ApplySettingsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `Apply`."); + return result; + } + + /// + /// Gets the current settings for all the instances of a configuration unit. + /// + /// A . + public IGetAllSettingsResult GetAllSettings() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `GetAllSettings` for resource: {this.unitInternal.QualifiedName}..."); + + this.CheckLimitMode(ConfigurationUnitIntent.Inform); + var result = new GetAllSettingsResult(this.Unit); + + try + { + result.Settings = this.GetAllSettingsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `GetAllSettings`."); + return result; + } + + /// + /// Gets all configuration units for the given unit type. + /// Returned units may be of types other than the one passed in. + /// + /// A . + public IGetAllUnitsResult GetAllUnits() + { + this.OnDiagnostics(DiagnosticLevel.Verbose, $"Invoking `GetAllUnits` for resource: {this.unitInternal.QualifiedName}..."); + + this.CheckLimitMode(ConfigurationUnitIntent.Inform); + var result = new GetAllUnitsResult(this.Unit); + + try + { + result.Units = this.GetAllUnitsInternal(); + } + catch (Exception e) + { + this.ExtractExceptionInformation(e, result.InternalResult); + } + + this.OnDiagnostics(DiagnosticLevel.Verbose, $"... done invoking `GetAllUnits`."); + return result; + } + + /// + /// Gets the current settings. + /// + /// The current settings. + protected abstract ValueSet GetSettingsInternal(); + + /// + /// Tests the current settings. + /// + /// A boolean indicating whether the settings are in the desired state. + protected abstract bool TestSettingsInternal(); + + /// + /// Applies the desired settings. + /// + /// A boolean indicating whether a reboot is required. + protected abstract bool ApplySettingsInternal(); + + /// + /// Gets the current settings for all the instances of a configuration unit. + /// Derive from IGetAllSettingsConfigurationUnitProcessor and implement an override to support this. + /// + /// The settings as ValueSets. + protected virtual IList? GetAllSettingsInternal() + { + throw new NotImplementedException("Configuration unit processor did not implement GetAllSettingsInternal."); + } + + /// + /// Gets all configuration units for the given unit type. + /// Returned units may be of types other than the one passed in. + /// Derive from IGetAllUnitsConfigurationUnitProcessor and implement an override to support this. + /// + /// The configuration units. + protected virtual IList? GetAllUnitsInternal() + { + throw new NotImplementedException("Configuration unit processor did not implement GetAllUnitsInternal."); + } + + /// + /// Sends diagnostics if appropriate. + /// + /// The level of this diagnostic message. + /// The diagnostic message. + protected void OnDiagnostics(DiagnosticLevel level, string message) + { + this.SetProcessorFactory?.OnDiagnostics(level, message); + } + + private void ExtractExceptionInformation(Exception e, ConfigurationUnitResultInformation resultInformation) + { + this.OnDiagnostics(DiagnosticLevel.Verbose, e.ToString()); + + IConfigurationUnitResultException? configurationUnitResultException = e as IConfigurationUnitResultException; + if (configurationUnitResultException != null) + { + resultInformation.ResultCode = e; + resultInformation.Description = configurationUnitResultException.Description; + resultInformation.Details = configurationUnitResultException.Details; + resultInformation.ResultSource = configurationUnitResultException.ResultSource; + } + else + { + var inner = e.GetMostInnerException(); + resultInformation.ResultCode = inner; + resultInformation.Description = e.Message; + resultInformation.Details = e.ToString(); + resultInformation.ResultSource = ConfigurationUnitResultSource.Internal; + } + } + + [MethodImpl(MethodImplOptions.Synchronized)] + private void CheckLimitMode(ConfigurationUnitIntent intent) + { + if (!this.isLimitMode) + { + return; + } + + if (intent == ConfigurationUnitIntent.Unknown) + { + throw new InvalidEnumArgumentException(nameof(ConfigurationUnitIntent.Unknown)); + } + + if (intent == ConfigurationUnitIntent.Assert) + { + if (this.isTestInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "TestSettings is already invoked in limit mode."); + throw new InvalidOperationException("TestSettings is already invoked in limit mode."); + } + else + { + this.isTestInvoked = true; + } + } + + if (intent == ConfigurationUnitIntent.Apply) + { + if (this.isApplyInvoked) + { + this.OnDiagnostics(DiagnosticLevel.Error, "ApplySettings is already invoked in limit mode."); + throw new InvalidOperationException("ApplySettings is already invoked in limit mode."); + } + else + { + this.isApplyInvoked = true; + } + } + + // Get is always allowed now. + } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorDetails.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorDetails.cs index f349ab8baf..c7f7302fe5 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorDetails.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitProcessorDetails.cs @@ -1,125 +1,125 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration; - - /// - /// Provides information for a specific configuration unit within the runtime. - /// - internal sealed partial class ConfigurationUnitProcessorDetails : IConfigurationUnitProcessorDetails, IConfigurationUnitProcessorDetails2, IConfigurationUnitProcessorDetails3 - { - /// - /// Initializes a new instance of the class. - /// - public ConfigurationUnitProcessorDetails() - { - } - - /// - /// Gets or sets the name of the unit of configuration. - /// - required public string UnitType { get; internal set; } - - /// - /// Gets or sets the description of the unit of configuration. - /// - public string? UnitDescription { get; internal set; } - - /// - /// Gets or sets the URI of the documentation for the unit of configuration. - /// - public Uri? UnitDocumentationUri { get; internal set; } - - /// - /// Gets or sets the URI of the icon for the unit of configuration. - /// - public Uri? UnitIconUri { get; internal set; } - - /// - /// Gets or sets the name of the module containing the unit of configuration. - /// - public string? ModuleName { get; internal set; } - - /// - /// Gets or sets the type of the module containing the unit of configuration. - /// - public string? ModuleType { get; internal set; } - - /// - /// Gets or sets the source of the module containing the unit of configuration. - /// - public string? ModuleSource { get; internal set; } - - /// - /// Gets or sets the description of the module containing the unit of configuration. - /// - public string? ModuleDescription { get; internal set; } - - /// - /// Gets or sets the URI of the documentation for the module containing the unit of configuration. - /// - public Uri? ModuleDocumentationUri { get; internal set; } - - /// - /// Gets or sets the URI for the published module containing the unit of configuration. - /// - public Uri? PublishedModuleUri { get; internal set; } - - /// - /// Gets or sets the version of the module containing the unit of configuration. - /// - public string? Version { get; internal set; } - - /// - /// Gets or sets the publishing date of the module containing the unit of configuration. - /// - public DateTimeOffset PublishedDate { get; internal set; } - - /// - /// Gets or sets a value indicating whether the module is already present on the system. - /// - public bool IsLocal { get; internal set; } - - /// - /// Gets or sets the author of the module containing the unit of configuration. - /// - public string? Author { get; internal set; } - - /// - /// Gets or sets the publisher of the module containing the unit of configuration. - /// - public string? Publisher { get; internal set; } - - /// - /// Gets or sets the signing certificate of the module files containing the unit of configuration. - /// - public IReadOnlyList? SigningInformation { get; internal set; } - - /// - /// Gets or sets the settings information for the unit of configuration. - /// - public IReadOnlyList? Settings { get; internal set; } - - /// - /// Gets or sets a value indicating whether the module comes from a public repository. - /// - public bool IsPublic { get; internal set; } - - /// - /// Gets or sets a value indicating whether this resource is a group. - /// - public bool IsGroup { get; internal set; } - - /// - /// Gets or sets the path of the resource. - /// - public string? Path { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using System; + using System.Collections.Generic; + using Microsoft.Management.Configuration; + + /// + /// Provides information for a specific configuration unit within the runtime. + /// + internal sealed partial class ConfigurationUnitProcessorDetails : IConfigurationUnitProcessorDetails, IConfigurationUnitProcessorDetails2, IConfigurationUnitProcessorDetails3 + { + /// + /// Initializes a new instance of the class. + /// + public ConfigurationUnitProcessorDetails() + { + } + + /// + /// Gets or sets the name of the unit of configuration. + /// + required public string UnitType { get; internal set; } + + /// + /// Gets or sets the description of the unit of configuration. + /// + public string? UnitDescription { get; internal set; } + + /// + /// Gets or sets the URI of the documentation for the unit of configuration. + /// + public Uri? UnitDocumentationUri { get; internal set; } + + /// + /// Gets or sets the URI of the icon for the unit of configuration. + /// + public Uri? UnitIconUri { get; internal set; } + + /// + /// Gets or sets the name of the module containing the unit of configuration. + /// + public string? ModuleName { get; internal set; } + + /// + /// Gets or sets the type of the module containing the unit of configuration. + /// + public string? ModuleType { get; internal set; } + + /// + /// Gets or sets the source of the module containing the unit of configuration. + /// + public string? ModuleSource { get; internal set; } + + /// + /// Gets or sets the description of the module containing the unit of configuration. + /// + public string? ModuleDescription { get; internal set; } + + /// + /// Gets or sets the URI of the documentation for the module containing the unit of configuration. + /// + public Uri? ModuleDocumentationUri { get; internal set; } + + /// + /// Gets or sets the URI for the published module containing the unit of configuration. + /// + public Uri? PublishedModuleUri { get; internal set; } + + /// + /// Gets or sets the version of the module containing the unit of configuration. + /// + public string? Version { get; internal set; } + + /// + /// Gets or sets the publishing date of the module containing the unit of configuration. + /// + public DateTimeOffset PublishedDate { get; internal set; } + + /// + /// Gets or sets a value indicating whether the module is already present on the system. + /// + public bool IsLocal { get; internal set; } + + /// + /// Gets or sets the author of the module containing the unit of configuration. + /// + public string? Author { get; internal set; } + + /// + /// Gets or sets the publisher of the module containing the unit of configuration. + /// + public string? Publisher { get; internal set; } + + /// + /// Gets or sets the signing certificate of the module files containing the unit of configuration. + /// + public IReadOnlyList? SigningInformation { get; internal set; } + + /// + /// Gets or sets the settings information for the unit of configuration. + /// + public IReadOnlyList? Settings { get; internal set; } + + /// + /// Gets or sets a value indicating whether the module comes from a public repository. + /// + public bool IsPublic { get; internal set; } + + /// + /// Gets or sets a value indicating whether this resource is a group. + /// + public bool IsGroup { get; internal set; } + + /// + /// Gets or sets the path of the resource. + /// + public string? Path { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitResultInformation.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitResultInformation.cs index f4863d137e..3103dafe2e 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitResultInformation.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitResultInformation.cs @@ -1,29 +1,29 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using System; - using Microsoft.Management.Configuration; - - /// - /// Implements IConfigurationUnitResultInformation. - /// - internal sealed partial class ConfigurationUnitResultInformation : IConfigurationUnitResultInformation - { - /// - public string? Description { get; internal set; } - - /// - public string? Details { get; internal set; } - - /// - public Exception? ResultCode { get; internal set; } - - /// - public ConfigurationUnitResultSource ResultSource { get; internal set; } = ConfigurationUnitResultSource.None; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using System; + using Microsoft.Management.Configuration; + + /// + /// Implements IConfigurationUnitResultInformation. + /// + internal sealed partial class ConfigurationUnitResultInformation : IConfigurationUnitResultInformation + { + /// + public string? Description { get; internal set; } + + /// + public string? Details { get; internal set; } + + /// + public Exception? ResultCode { get; internal set; } + + /// + public ConfigurationUnitResultSource ResultSource { get; internal set; } = ConfigurationUnitResultSource.None; + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitSettingDetails.cs b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitSettingDetails.cs index 1bdc160c63..14f1337385 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitSettingDetails.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/ConfigurationUnitSettingDetails.cs @@ -1,64 +1,64 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using Microsoft.Management.Configuration; - - /// - /// Provides information for a specific configuration unit setting. - /// - internal sealed partial class ConfigurationUnitSettingDetails : IConfigurationUnitSettingDetails - { - /// - /// Initializes a new instance of the class. - /// - public ConfigurationUnitSettingDetails() - { - } - - /// - /// Gets the name of the setting. - /// - required public string Identifier { get; init; } - - /// - /// Gets the title of the setting. - /// - public string Title { get; init; } = string.Empty; - - /// - /// Gets the description of the setting. - /// - public string Description { get; init; } = string.Empty; - - /// - /// Gets a value indicating whether the setting is a key. This is used to determine if different settings are in conflict. - /// - public bool IsKey { get; init; } = false; - - /// - /// Gets a value indicating whether a non-empty value for the setting is required. - /// - public bool IsRequired { get; init; } = false; - - /// - /// Gets a value indicating whether the setting should be serialized in order to be applied on another system. - /// - public bool IsInformational { get; init; } = false; - - /// - /// Gets the data type for the value of this setting. - /// - public Windows.Foundation.PropertyType Type { get; init; } = Windows.Foundation.PropertyType.Inspectable; - - /// - /// Gets the semantics to be used for this setting. The goal is to enable richer conflict detection and authoring - /// scenarios by having a deeper understanding of this value than "String". - /// - public string Schema { get; init; } = string.Empty; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using Microsoft.Management.Configuration; + + /// + /// Provides information for a specific configuration unit setting. + /// + internal sealed partial class ConfigurationUnitSettingDetails : IConfigurationUnitSettingDetails + { + /// + /// Initializes a new instance of the class. + /// + public ConfigurationUnitSettingDetails() + { + } + + /// + /// Gets the name of the setting. + /// + required public string Identifier { get; init; } + + /// + /// Gets the title of the setting. + /// + public string Title { get; init; } = string.Empty; + + /// + /// Gets the description of the setting. + /// + public string Description { get; init; } = string.Empty; + + /// + /// Gets a value indicating whether the setting is a key. This is used to determine if different settings are in conflict. + /// + public bool IsKey { get; init; } = false; + + /// + /// Gets a value indicating whether a non-empty value for the setting is required. + /// + public bool IsRequired { get; init; } = false; + + /// + /// Gets a value indicating whether the setting should be serialized in order to be applied on another system. + /// + public bool IsInformational { get; init; } = false; + + /// + /// Gets the data type for the value of this setting. + /// + public Windows.Foundation.PropertyType Type { get; init; } = Windows.Foundation.PropertyType.Inspectable; + + /// + /// Gets the semantics to be used for this setting. The goal is to enable richer conflict detection and authoring + /// scenarios by having a deeper understanding of this value than "String". + /// + public string Schema { get; init; } = string.Empty; + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/GetAllSettingsResult.cs b/src/Microsoft.Management.Configuration.Processor/Unit/GetAllSettingsResult.cs index 324b6f1d4b..b5fd04f64f 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/GetAllSettingsResult.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/GetAllSettingsResult.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Windows.Foundation.Collections; - - /// - /// Implements IGetAllSettingsResult. - /// - internal partial class GetAllSettingsResult : IGetAllSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public GetAllSettingsResult(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); - - /// - public IList? Settings { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Windows.Foundation.Collections; + + /// + /// Implements IGetAllSettingsResult. + /// + internal partial class GetAllSettingsResult : IGetAllSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public GetAllSettingsResult(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); + + /// + public IList? Settings { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/GetAllUnitsResult.cs b/src/Microsoft.Management.Configuration.Processor/Unit/GetAllUnitsResult.cs index f99f1edd99..a35b90e0fe 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/GetAllUnitsResult.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/GetAllUnitsResult.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Windows.Foundation.Collections; - - /// - /// Implements IGetAllUnitsResult. - /// - internal partial class GetAllUnitsResult : IGetAllUnitsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public GetAllUnitsResult(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); - - /// - public IList? Units { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Windows.Foundation.Collections; + + /// + /// Implements IGetAllUnitsResult. + /// + internal partial class GetAllUnitsResult : IGetAllUnitsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public GetAllUnitsResult(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); + + /// + public IList? Units { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/GetSettingsResult.cs b/src/Microsoft.Management.Configuration.Processor/Unit/GetSettingsResult.cs index 1f6a60db7f..a4f77f39e4 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/GetSettingsResult.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/GetSettingsResult.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using Microsoft.Management.Configuration; - using Windows.Foundation.Collections; - - /// - /// Implements IGetSettingsResult. - /// - internal sealed partial class GetSettingsResult : IGetSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public GetSettingsResult(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); - - /// - public ValueSet? Settings { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using Microsoft.Management.Configuration; + using Windows.Foundation.Collections; + + /// + /// Implements IGetSettingsResult. + /// + internal sealed partial class GetSettingsResult : IGetSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public GetSettingsResult(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); + + /// + public ValueSet? Settings { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.Processor/Unit/TestSettingsResult.cs b/src/Microsoft.Management.Configuration.Processor/Unit/TestSettingsResult.cs index 69ba10a9ba..69c4853a3f 100644 --- a/src/Microsoft.Management.Configuration.Processor/Unit/TestSettingsResult.cs +++ b/src/Microsoft.Management.Configuration.Processor/Unit/TestSettingsResult.cs @@ -1,44 +1,44 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.Processor.Unit -{ - using Microsoft.Management.Configuration; - - /// - /// Implements ITestSettingsResult. - /// - internal sealed partial class TestSettingsResult : ITestSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public TestSettingsResult(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); - - /// - public ConfigurationTestResult TestResult { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.Processor.Unit +{ + using Microsoft.Management.Configuration; + + /// + /// Implements ITestSettingsResult. + /// + internal sealed partial class TestSettingsResult : ITestSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public TestSettingsResult(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public ConfigurationUnitResultInformation InternalResult { get; } = new ConfigurationUnitResultInformation(); + + /// + public ConfigurationTestResult TestResult { get; internal set; } + } +} 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 208bd44f5c..33801cfe17 100644 --- a/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj +++ b/src/Microsoft.Management.Configuration.Projection/Microsoft.Management.Configuration.Projection.csproj @@ -1,44 +1,44 @@ - - - - net8.0-windows10.0.26100.0 - AnyCpu - enable - enable - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - Debug;Release;ReleaseStatic - true - - - - Microsoft.Management.Configuration - $(OutDir) - 10.0.26100.0 - - 10.0.17763.0 - - True - - - - - None - - - - - - - - - Content - PreserveNewest - True - - - - + + + + net8.0-windows10.0.26100.0 + AnyCpu + enable + enable + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + Debug;Release;ReleaseStatic + true + + + + Microsoft.Management.Configuration + $(OutDir) + 10.0.26100.0 + + 10.0.17763.0 + + True + + + + + None + + + + + + + + + Content + PreserveNewest + True + + + + diff --git a/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestCollection.cs b/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestCollection.cs index 69f74968e6..0dbcb5e515 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestCollection.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestCollection.cs @@ -1,21 +1,21 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Fixtures -{ - using Xunit; - - /// - /// Unit test collection. - /// - [CollectionDefinition("UnitTestCollection")] - public class UnitTestCollection : ICollectionFixture - { - // This class has no code, and is never created. Its purpose is simply - // to be the place to apply [CollectionDefinition] and all the - // ICollectionFixture<> interfaces. - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Fixtures +{ + using Xunit; + + /// + /// Unit test collection. + /// + [CollectionDefinition("UnitTestCollection")] + public class UnitTestCollection : ICollectionFixture + { + // This class has no code, and is never created. Its purpose is simply + // to be the place to apply [CollectionDefinition] and all the + // ICollectionFixture<> interfaces. + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestFixture.cs b/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestFixture.cs index c08fd256ad..3f9c923361 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestFixture.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Fixtures/UnitTestFixture.cs @@ -1,122 +1,122 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Fixtures -{ - using System; - using System.Diagnostics.CodeAnalysis; - using System.IO; - using System.Reflection; - using Microsoft.Management.Configuration.Processor; - using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; - using WinRT; - using Xunit.Abstractions; - - /// - /// Unit test fixture. - /// - public class UnitTestFixture - { - /// - /// Initializes a new instance of the class. - /// - /// The message sink for the fixture. - public UnitTestFixture(IMessageSink messageSink) - { - this.MessageSink = messageSink; - - string assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) - ?? throw new ArgumentException(); - - this.TestModulesPath = Path.Combine(assemblyPath, "TestCollateral", "PowerShellModules"); - if (!Directory.Exists(this.TestModulesPath)) - { - throw new DirectoryNotFoundException(this.TestModulesPath); - } - - // Use the environment variable if present, which is how ADO pipelines will find it. - string? gitSearchPath = Environment.GetEnvironmentVariable("BUILD_SOURCESDIRECTORY"); - - if (string.IsNullOrWhiteSpace(gitSearchPath)) - { - gitSearchPath = Path.GetDirectoryName(assemblyPath); - - while (!string.IsNullOrEmpty(gitSearchPath)) - { - if (Directory.Exists(Path.Combine(gitSearchPath, ".git"))) - { - break; - } - - gitSearchPath = Path.GetDirectoryName(gitSearchPath); - } - } - - this.GitRootPath = gitSearchPath ?? throw new DirectoryNotFoundException("git root path"); - - this.ExternalModulesPath = Path.Combine(this.GitRootPath, "src", "PowerShell", "ExternalModules"); - if (!Directory.Exists(this.ExternalModulesPath)) - { - throw new DirectoryNotFoundException(this.ExternalModulesPath); - } - - this.RecreateStatics(); - } - - /// - /// Gets the message sink for the fixture. - /// - public IMessageSink MessageSink { get; private init; } - - /// - /// Gets the test module path. - /// - public string TestModulesPath { get; } - - /// - /// Gets the git root path. - /// - public string GitRootPath { get; } - - /// - /// Gets the external module path. - /// - public string ExternalModulesPath { get; } - - /// - /// Gets the configuration statics object to use. - /// - internal IConfigurationStatics2 ConfigurationStatics { get; private set; } - - /// - /// Creates a new statics object for use by the tests. - /// - [MemberNotNull("ConfigurationStatics")] - public void RecreateStatics() - { - this.ConfigurationStatics = new ConfigurationStaticFunctions().As(); - } - - /// - /// Creates a runspace adding the test module path. - /// - /// Validate runspace. - /// PowerShellRunspace. - internal IProcessorEnvironment PrepareTestProcessorEnvironment(bool validate = false) - { - var processorEnv = new ProcessorEnvironmentFactory(PowerShellConfigurationProcessorType.Hosted).CreateEnvironment(null, PowerShellConfigurationProcessorPolicy.Unrestricted); - processorEnv.PrependPSModulePath(this.ExternalModulesPath); - processorEnv.PrependPSModulePath(this.TestModulesPath); - - if (validate) - { - processorEnv.ValidateRunspace(); - } - - return processorEnv; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Fixtures +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.IO; + using System.Reflection; + using Microsoft.Management.Configuration.Processor; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using WinRT; + using Xunit.Abstractions; + + /// + /// Unit test fixture. + /// + public class UnitTestFixture + { + /// + /// Initializes a new instance of the class. + /// + /// The message sink for the fixture. + public UnitTestFixture(IMessageSink messageSink) + { + this.MessageSink = messageSink; + + string assemblyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + ?? throw new ArgumentException(); + + this.TestModulesPath = Path.Combine(assemblyPath, "TestCollateral", "PowerShellModules"); + if (!Directory.Exists(this.TestModulesPath)) + { + throw new DirectoryNotFoundException(this.TestModulesPath); + } + + // Use the environment variable if present, which is how ADO pipelines will find it. + string? gitSearchPath = Environment.GetEnvironmentVariable("BUILD_SOURCESDIRECTORY"); + + if (string.IsNullOrWhiteSpace(gitSearchPath)) + { + gitSearchPath = Path.GetDirectoryName(assemblyPath); + + while (!string.IsNullOrEmpty(gitSearchPath)) + { + if (Directory.Exists(Path.Combine(gitSearchPath, ".git"))) + { + break; + } + + gitSearchPath = Path.GetDirectoryName(gitSearchPath); + } + } + + this.GitRootPath = gitSearchPath ?? throw new DirectoryNotFoundException("git root path"); + + this.ExternalModulesPath = Path.Combine(this.GitRootPath, "src", "PowerShell", "ExternalModules"); + if (!Directory.Exists(this.ExternalModulesPath)) + { + throw new DirectoryNotFoundException(this.ExternalModulesPath); + } + + this.RecreateStatics(); + } + + /// + /// Gets the message sink for the fixture. + /// + public IMessageSink MessageSink { get; private init; } + + /// + /// Gets the test module path. + /// + public string TestModulesPath { get; } + + /// + /// Gets the git root path. + /// + public string GitRootPath { get; } + + /// + /// Gets the external module path. + /// + public string ExternalModulesPath { get; } + + /// + /// Gets the configuration statics object to use. + /// + internal IConfigurationStatics2 ConfigurationStatics { get; private set; } + + /// + /// Creates a new statics object for use by the tests. + /// + [MemberNotNull("ConfigurationStatics")] + public void RecreateStatics() + { + this.ConfigurationStatics = new ConfigurationStaticFunctions().As(); + } + + /// + /// Creates a runspace adding the test module path. + /// + /// Validate runspace. + /// PowerShellRunspace. + internal IProcessorEnvironment PrepareTestProcessorEnvironment(bool validate = false) + { + var processorEnv = new ProcessorEnvironmentFactory(PowerShellConfigurationProcessorType.Hosted).CreateEnvironment(null, PowerShellConfigurationProcessorPolicy.Unrestricted); + processorEnv.PrependPSModulePath(this.ExternalModulesPath); + processorEnv.PrependPSModulePath(this.TestModulesPath); + + if (validate) + { + processorEnv.ValidateRunspace(); + } + + return processorEnv; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupMemberSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupMemberSettingsResultInstance.cs index bc4c3ca585..5995c798c6 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupMemberSettingsResultInstance.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupMemberSettingsResultInstance.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - /// - /// Implements IApplyGroupMemberSettingsResult. - /// - internal sealed partial class ApplyGroupMemberSettingsResultInstance : IApplyGroupMemberSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The unit for this result. - internal ApplyGroupMemberSettingsResultInstance(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - public bool PreviouslyInDesiredState { get; internal set; } - - /// - public bool RebootRequired { get; internal set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); - - /// - public ConfigurationUnitState State { get; internal set; } - - /// - public ConfigurationUnit Unit { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + /// + /// Implements IApplyGroupMemberSettingsResult. + /// + internal sealed partial class ApplyGroupMemberSettingsResultInstance : IApplyGroupMemberSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The unit for this result. + internal ApplyGroupMemberSettingsResultInstance(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + public bool PreviouslyInDesiredState { get; internal set; } + + /// + public bool RebootRequired { get; internal set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// + public ConfigurationUnitState State { get; internal set; } + + /// + public ConfigurationUnit Unit { get; private init; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupSettingsResultInstance.cs index a45c311ef8..f981269ae6 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupSettingsResultInstance.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplyGroupSettingsResultInstance.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - - /// - /// Implements IApplyGroupSettingsResult. - /// - internal sealed partial class ApplyGroupSettingsResultInstance : IApplyGroupSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The group for this result. - internal ApplyGroupSettingsResultInstance(object? group) - { - this.Group = group; - } - - /// - public object? Group { get; private init; } - - /// - public bool RebootRequired { get; internal set; } - - /// - public IConfigurationUnitResultInformation? ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); - - /// - public IList? UnitResults { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + + /// + /// Implements IApplyGroupSettingsResult. + /// + internal sealed partial class ApplyGroupSettingsResultInstance : IApplyGroupSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The group for this result. + internal ApplyGroupSettingsResultInstance(object? group) + { + this.Group = group; + } + + /// + public object? Group { get; private init; } + + /// + public bool RebootRequired { get; internal set; } + + /// + public IConfigurationUnitResultInformation? ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// + public IList? UnitResults { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplySettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplySettingsResultInstance.cs index 6e6b943378..77c527a133 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplySettingsResultInstance.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ApplySettingsResultInstance.cs @@ -1,44 +1,44 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using Microsoft.Management.Configuration; - - /// - /// Implements IApplySettingsResult. - /// - internal sealed partial class ApplySettingsResultInstance : IApplySettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public ApplySettingsResultInstance(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); - - /// - public bool RebootRequired { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using Microsoft.Management.Configuration; + + /// + /// Implements IApplySettingsResult. + /// + internal sealed partial class ApplySettingsResultInstance : IApplySettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public ApplySettingsResultInstance(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// + public bool RebootRequired { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs index 8ae8df8b78..6b3c5d66f3 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationEnvironmentData.cs @@ -1,88 +1,88 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - - /// - /// Contains the data defining a configuration environment. - /// - internal class ConfigurationEnvironmentData - { - /// - /// Initializes a new instance of the class. - /// - internal ConfigurationEnvironmentData() - { - } - - /// - /// Gets or sets the security context. - /// - internal SecurityContext Context { get; set; } = SecurityContext.Current; - - /// - /// Gets or sets the processor identifier. - /// - internal string ProcessorIdentifier { get; set; } = string.Empty; - - /// - /// Gets or sets the processor properties. - /// - internal Dictionary ProcessorProperties { get; set; } = new (); - - /// - /// Applies this environment to the given unit. - /// - /// The unit to apply to. - /// The given unit. - internal ConfigurationUnit ApplyToUnit(ConfigurationUnit unit) - { - var environment = unit.Environment; - - environment.Context = this.Context; - environment.ProcessorIdentifier = this.ProcessorIdentifier; - environment.ProcessorProperties.Clear(); - foreach (var property in this.ProcessorProperties) - { - environment.ProcessorProperties.Add(property.Key, property.Value); - } - - return unit; - } - - /// - /// Tests whether the given properties match this object's properties. - /// - /// The properties to test. - /// True if the properties match; false if not. - internal bool PropertiesEqual(IDictionary properties) - { - if (properties.Count != this.ProcessorProperties.Count) - { - return false; - } - - foreach (var property in properties) - { - string? value = null; - if (!this.ProcessorProperties.TryGetValue(property.Key, out value)) - { - return false; - } - - if (property.Value != value) - { - return false; - } - } - - return true; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + + /// + /// Contains the data defining a configuration environment. + /// + internal class ConfigurationEnvironmentData + { + /// + /// Initializes a new instance of the class. + /// + internal ConfigurationEnvironmentData() + { + } + + /// + /// Gets or sets the security context. + /// + internal SecurityContext Context { get; set; } = SecurityContext.Current; + + /// + /// Gets or sets the processor identifier. + /// + internal string ProcessorIdentifier { get; set; } = string.Empty; + + /// + /// Gets or sets the processor properties. + /// + internal Dictionary ProcessorProperties { get; set; } = new (); + + /// + /// Applies this environment to the given unit. + /// + /// The unit to apply to. + /// The given unit. + internal ConfigurationUnit ApplyToUnit(ConfigurationUnit unit) + { + var environment = unit.Environment; + + environment.Context = this.Context; + environment.ProcessorIdentifier = this.ProcessorIdentifier; + environment.ProcessorProperties.Clear(); + foreach (var property in this.ProcessorProperties) + { + environment.ProcessorProperties.Add(property.Key, property.Value); + } + + return unit; + } + + /// + /// Tests whether the given properties match this object's properties. + /// + /// The properties to test. + /// True if the properties match; false if not. + internal bool PropertiesEqual(IDictionary properties) + { + if (properties.Count != this.ProcessorProperties.Count) + { + return false; + } + + foreach (var property in properties) + { + string? value = null; + if (!this.ProcessorProperties.TryGetValue(property.Key, out value)) + { + return false; + } + + if (property.Value != value) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationExtensions.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationExtensions.cs index e6409d5090..58885c23fa 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationExtensions.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationExtensions.cs @@ -1,36 +1,36 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Linq; - using System.Reflection; - - /// - /// Contains extension methods for configuration objects. - /// - internal static class ConfigurationExtensions - { - /// - /// Assigns the given properties to the configuration unit. - /// - /// The unit to assign the properties of. - /// The properties to assign. - /// The given ConfigurationUnit. - internal static ConfigurationUnit Assign(this ConfigurationUnit unit, object properties) - { - PropertyInfo[] unitProperties = typeof(ConfigurationUnit).GetProperties(); - - foreach (PropertyInfo property in properties.GetType().GetProperties()) - { - PropertyInfo matchingProperty = unitProperties.First(pi => pi.Name == property.Name); - matchingProperty.SetValue(unit, property.GetValue(properties)); - } - - return unit; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Linq; + using System.Reflection; + + /// + /// Contains extension methods for configuration objects. + /// + internal static class ConfigurationExtensions + { + /// + /// Assigns the given properties to the configuration unit. + /// + /// The unit to assign the properties of. + /// The properties to assign. + /// The given ConfigurationUnit. + internal static ConfigurationUnit Assign(this ConfigurationUnit unit, object properties) + { + PropertyInfo[] unitProperties = typeof(ConfigurationUnit).GetProperties(); + + foreach (PropertyInfo property in properties.GetType().GetProperties()) + { + PropertyInfo matchingProperty = unitProperties.First(pi => pi.Name == property.Name); + matchingProperty.SetValue(unit, property.GetValue(properties)); + } + + return unit; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs index c1dd3a9daa..d3708e68cc 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/ConfigurationProcessorTestBase.cs @@ -1,240 +1,240 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Windows.Storage.Streams; - using Xunit; - using Xunit.Abstractions; - - /// - /// Test base that provides helpers for dealing with . - /// - public class ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - protected ConfigurationProcessorTestBase(UnitTestFixture fixture, ITestOutputHelper log) - { - this.Fixture = fixture; - this.Log = log; - this.EventSink = new DiagnosticsEventSink(fixture, log); - } - - /// - /// Gets the event sink for this test base. - /// - protected DiagnosticsEventSink EventSink { get; private set; } - - /// - /// Gets the test fixture. - /// - protected UnitTestFixture Fixture { get; private init; } - - /// - /// Gets the output helper. - /// - protected ITestOutputHelper Log { get; private init; } - - /// - /// Create a new with the diagnostics event hooked up. - /// - /// The factory to use. - /// The new object. - internal ConfigurationProcessor CreateConfigurationProcessorWithDiagnostics(IConfigurationSetProcessorFactory? factory = null) - { - ConfigurationProcessor result = this.Fixture.ConfigurationStatics.CreateConfigurationProcessor(factory); - result.Diagnostics += this.EventSink.DiagnosticsHandler; - result.MinimumLevel = DiagnosticLevel.Verbose; - return result; - } - - /// - /// Creates an input stream from the given string. - /// - /// The contents that the stream should contain. - /// The created stream. - internal IInputStream CreateStream(string contents) - { - InMemoryRandomAccessStream result = new InMemoryRandomAccessStream(); - - using (DataWriter writer = new DataWriter(result)) - { - writer.UnicodeEncoding = UnicodeEncoding.Utf8; - writer.WriteString(contents); - writer.StoreAsync().AsTask().Wait(); - writer.DetachStream(); - } - - result.Seek(0); - return result; - } - - /// - /// Creates an string from the given output stream. - /// - /// The output stream. - /// The created string. - internal string ReadStream(InMemoryRandomAccessStream stream) - { - string result = string.Empty; - using (DataReader reader = new DataReader(stream.GetInputStreamAt(0))) - { - reader.UnicodeEncoding = UnicodeEncoding.Utf8; - reader.LoadAsync((uint)stream.Size).AsTask().Wait(); - uint bytesToRead = reader.UnconsumedBufferLength; - - if (bytesToRead > 0) - { - result = reader.ReadString(bytesToRead); - } - } - - return result; - } - - /// - /// Creates a configuration unit via the configuration statics object. - /// - /// A new configuration unit. - internal ConfigurationUnit ConfigurationUnit() - { - return this.Fixture.ConfigurationStatics.CreateConfigurationUnit(); - } - - /// - /// Creates a configuration parameter via the configuration statics object. - /// - /// A new configuration parameter. - internal ConfigurationParameter ConfigurationParameter() - { - return this.Fixture.ConfigurationStatics.CreateConfigurationParameter(); - } - - /// - /// Creates a configuration set via the configuration statics object. - /// - /// A new configuration set. - internal ConfigurationSet ConfigurationSet() - { - return this.Fixture.ConfigurationStatics.CreateConfigurationSet(); - } - - /// - /// Verifies the summary event generated by a processing run. - /// - /// The configuration set. - /// The set result. - /// The result source. - internal void VerifySummaryEvent(ConfigurationSet configurationSet, ApplyConfigurationSetResult setResult, ConfigurationUnitResultSource resultSource) - { - TelemetryEvent summary = this.VerifySummaryEventShared(configurationSet, ConfigurationUnitIntent.Apply, resultSource == ConfigurationUnitResultSource.None ? 0 : setResult.ResultCode.HResult, resultSource); - - int[] counts = new int[3]; - int[] runs = new int[3]; - int[] failures = new int[3]; - - var unitResults = setResult.UnitResults; - for (int i = 0; i < unitResults.Count; ++i) - { - ApplyConfigurationUnitResult unitResult = unitResults[i]; - SummaryCountByIntent(counts, runs, failures, unitResult.Unit.Intent, unitResult.ResultInformation); - } - - VerifySummaryCounts(summary, counts, runs, failures); - } - - /// - /// Verifies the summary event generated by a processing run. - /// - /// The configuration set. - /// The set result. - /// The result code. - /// The result source. - internal void VerifySummaryEvent(ConfigurationSet configurationSet, TestConfigurationSetResult setResult, int resultCode, ConfigurationUnitResultSource resultSource) - { - TelemetryEvent summary = this.VerifySummaryEventShared(configurationSet, ConfigurationUnitIntent.Assert, resultCode, resultSource); - - int[] counts = new int[3]; - int[] runs = new int[3]; - int[] failures = new int[3]; - - foreach (TestConfigurationUnitResult unitResult in setResult.UnitResults) - { - SummaryCountByIntent(counts, runs, failures, unitResult.Unit.Intent, unitResult.ResultInformation); - } - - VerifySummaryCounts(summary, counts, runs, failures); - } - - private static void SummaryCountByIntent(int[] counts, int[] runs, int[] failures, ConfigurationUnitIntent intent, IConfigurationUnitResultInformation resultInformation) - { - if (intent == ConfigurationUnitIntent.Unknown) - { - intent = ConfigurationUnitIntent.Apply; - } - - int index = (int)intent; - - counts[index]++; - - if (resultInformation.ResultSource != ConfigurationUnitResultSource.ConfigurationSet && resultInformation.ResultSource != ConfigurationUnitResultSource.Precondition) - { - runs[index]++; - } - - if (resultInformation.ResultCode != null) - { - failures[index]++; - } - } - - private static void VerifySummaryCounts(TelemetryEvent summary, int[] counts, int[] runs, int[] failures) - { - Assert.Equal(counts[(int)ConfigurationUnitIntent.Assert].ToString(), summary.Properties[TelemetryEvent.AssertCount]); - Assert.Equal(runs[(int)ConfigurationUnitIntent.Assert].ToString(), summary.Properties[TelemetryEvent.AssertsRun]); - Assert.Equal(failures[(int)ConfigurationUnitIntent.Assert].ToString(), summary.Properties[TelemetryEvent.AssertsFailed]); - - Assert.Equal(counts[(int)ConfigurationUnitIntent.Inform].ToString(), summary.Properties[TelemetryEvent.InformCount]); - Assert.Equal(runs[(int)ConfigurationUnitIntent.Inform].ToString(), summary.Properties[TelemetryEvent.InformsRun]); - Assert.Equal(failures[(int)ConfigurationUnitIntent.Inform].ToString(), summary.Properties[TelemetryEvent.InformsFailed]); - - Assert.Equal(counts[(int)ConfigurationUnitIntent.Apply].ToString(), summary.Properties[TelemetryEvent.ApplyCount]); - Assert.Equal(runs[(int)ConfigurationUnitIntent.Apply].ToString(), summary.Properties[TelemetryEvent.AppliesRun]); - Assert.Equal(failures[(int)ConfigurationUnitIntent.Apply].ToString(), summary.Properties[TelemetryEvent.AppliesFailed]); - } - - /// - /// Verifies the summary event generated by a processing run. - /// - /// The configuration set. - /// The run intent. - /// The result code. - /// The result source. - private TelemetryEvent VerifySummaryEventShared(ConfigurationSet configurationSet, ConfigurationUnitIntent runIntent, int resultCode, ConfigurationUnitResultSource resultSource) - { - Assert.Single(this.EventSink.Events); - TelemetryEvent summary = this.EventSink.Events[0]; - - Assert.Equal(TelemetryEvent.ConfigProcessingSummaryName, summary.Name); - Assert.NotEqual(string.Empty, summary.CodeVersion); - Assert.NotEqual(Guid.Empty, summary.ActivityID); - Assert.Equal(string.Empty, summary.Caller); - Assert.Equal(configurationSet.InstanceIdentifier, Guid.Parse(summary.Properties[TelemetryEvent.SetID])); - Assert.False(int.Parse(summary.Properties[TelemetryEvent.FromHistory]) != 0); - Assert.Equal(((int)runIntent).ToString(), summary.Properties[TelemetryEvent.RunIntent]); - Assert.Equal(resultCode.ToString(), summary.Properties[TelemetryEvent.Result]); - Assert.Equal(((int)resultSource).ToString(), summary.Properties[TelemetryEvent.FailurePoint]); - - return summary; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Windows.Storage.Streams; + using Xunit; + using Xunit.Abstractions; + + /// + /// Test base that provides helpers for dealing with . + /// + public class ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + protected ConfigurationProcessorTestBase(UnitTestFixture fixture, ITestOutputHelper log) + { + this.Fixture = fixture; + this.Log = log; + this.EventSink = new DiagnosticsEventSink(fixture, log); + } + + /// + /// Gets the event sink for this test base. + /// + protected DiagnosticsEventSink EventSink { get; private set; } + + /// + /// Gets the test fixture. + /// + protected UnitTestFixture Fixture { get; private init; } + + /// + /// Gets the output helper. + /// + protected ITestOutputHelper Log { get; private init; } + + /// + /// Create a new with the diagnostics event hooked up. + /// + /// The factory to use. + /// The new object. + internal ConfigurationProcessor CreateConfigurationProcessorWithDiagnostics(IConfigurationSetProcessorFactory? factory = null) + { + ConfigurationProcessor result = this.Fixture.ConfigurationStatics.CreateConfigurationProcessor(factory); + result.Diagnostics += this.EventSink.DiagnosticsHandler; + result.MinimumLevel = DiagnosticLevel.Verbose; + return result; + } + + /// + /// Creates an input stream from the given string. + /// + /// The contents that the stream should contain. + /// The created stream. + internal IInputStream CreateStream(string contents) + { + InMemoryRandomAccessStream result = new InMemoryRandomAccessStream(); + + using (DataWriter writer = new DataWriter(result)) + { + writer.UnicodeEncoding = UnicodeEncoding.Utf8; + writer.WriteString(contents); + writer.StoreAsync().AsTask().Wait(); + writer.DetachStream(); + } + + result.Seek(0); + return result; + } + + /// + /// Creates an string from the given output stream. + /// + /// The output stream. + /// The created string. + internal string ReadStream(InMemoryRandomAccessStream stream) + { + string result = string.Empty; + using (DataReader reader = new DataReader(stream.GetInputStreamAt(0))) + { + reader.UnicodeEncoding = UnicodeEncoding.Utf8; + reader.LoadAsync((uint)stream.Size).AsTask().Wait(); + uint bytesToRead = reader.UnconsumedBufferLength; + + if (bytesToRead > 0) + { + result = reader.ReadString(bytesToRead); + } + } + + return result; + } + + /// + /// Creates a configuration unit via the configuration statics object. + /// + /// A new configuration unit. + internal ConfigurationUnit ConfigurationUnit() + { + return this.Fixture.ConfigurationStatics.CreateConfigurationUnit(); + } + + /// + /// Creates a configuration parameter via the configuration statics object. + /// + /// A new configuration parameter. + internal ConfigurationParameter ConfigurationParameter() + { + return this.Fixture.ConfigurationStatics.CreateConfigurationParameter(); + } + + /// + /// Creates a configuration set via the configuration statics object. + /// + /// A new configuration set. + internal ConfigurationSet ConfigurationSet() + { + return this.Fixture.ConfigurationStatics.CreateConfigurationSet(); + } + + /// + /// Verifies the summary event generated by a processing run. + /// + /// The configuration set. + /// The set result. + /// The result source. + internal void VerifySummaryEvent(ConfigurationSet configurationSet, ApplyConfigurationSetResult setResult, ConfigurationUnitResultSource resultSource) + { + TelemetryEvent summary = this.VerifySummaryEventShared(configurationSet, ConfigurationUnitIntent.Apply, resultSource == ConfigurationUnitResultSource.None ? 0 : setResult.ResultCode.HResult, resultSource); + + int[] counts = new int[3]; + int[] runs = new int[3]; + int[] failures = new int[3]; + + var unitResults = setResult.UnitResults; + for (int i = 0; i < unitResults.Count; ++i) + { + ApplyConfigurationUnitResult unitResult = unitResults[i]; + SummaryCountByIntent(counts, runs, failures, unitResult.Unit.Intent, unitResult.ResultInformation); + } + + VerifySummaryCounts(summary, counts, runs, failures); + } + + /// + /// Verifies the summary event generated by a processing run. + /// + /// The configuration set. + /// The set result. + /// The result code. + /// The result source. + internal void VerifySummaryEvent(ConfigurationSet configurationSet, TestConfigurationSetResult setResult, int resultCode, ConfigurationUnitResultSource resultSource) + { + TelemetryEvent summary = this.VerifySummaryEventShared(configurationSet, ConfigurationUnitIntent.Assert, resultCode, resultSource); + + int[] counts = new int[3]; + int[] runs = new int[3]; + int[] failures = new int[3]; + + foreach (TestConfigurationUnitResult unitResult in setResult.UnitResults) + { + SummaryCountByIntent(counts, runs, failures, unitResult.Unit.Intent, unitResult.ResultInformation); + } + + VerifySummaryCounts(summary, counts, runs, failures); + } + + private static void SummaryCountByIntent(int[] counts, int[] runs, int[] failures, ConfigurationUnitIntent intent, IConfigurationUnitResultInformation resultInformation) + { + if (intent == ConfigurationUnitIntent.Unknown) + { + intent = ConfigurationUnitIntent.Apply; + } + + int index = (int)intent; + + counts[index]++; + + if (resultInformation.ResultSource != ConfigurationUnitResultSource.ConfigurationSet && resultInformation.ResultSource != ConfigurationUnitResultSource.Precondition) + { + runs[index]++; + } + + if (resultInformation.ResultCode != null) + { + failures[index]++; + } + } + + private static void VerifySummaryCounts(TelemetryEvent summary, int[] counts, int[] runs, int[] failures) + { + Assert.Equal(counts[(int)ConfigurationUnitIntent.Assert].ToString(), summary.Properties[TelemetryEvent.AssertCount]); + Assert.Equal(runs[(int)ConfigurationUnitIntent.Assert].ToString(), summary.Properties[TelemetryEvent.AssertsRun]); + Assert.Equal(failures[(int)ConfigurationUnitIntent.Assert].ToString(), summary.Properties[TelemetryEvent.AssertsFailed]); + + Assert.Equal(counts[(int)ConfigurationUnitIntent.Inform].ToString(), summary.Properties[TelemetryEvent.InformCount]); + Assert.Equal(runs[(int)ConfigurationUnitIntent.Inform].ToString(), summary.Properties[TelemetryEvent.InformsRun]); + Assert.Equal(failures[(int)ConfigurationUnitIntent.Inform].ToString(), summary.Properties[TelemetryEvent.InformsFailed]); + + Assert.Equal(counts[(int)ConfigurationUnitIntent.Apply].ToString(), summary.Properties[TelemetryEvent.ApplyCount]); + Assert.Equal(runs[(int)ConfigurationUnitIntent.Apply].ToString(), summary.Properties[TelemetryEvent.AppliesRun]); + Assert.Equal(failures[(int)ConfigurationUnitIntent.Apply].ToString(), summary.Properties[TelemetryEvent.AppliesFailed]); + } + + /// + /// Verifies the summary event generated by a processing run. + /// + /// The configuration set. + /// The run intent. + /// The result code. + /// The result source. + private TelemetryEvent VerifySummaryEventShared(ConfigurationSet configurationSet, ConfigurationUnitIntent runIntent, int resultCode, ConfigurationUnitResultSource resultSource) + { + Assert.Single(this.EventSink.Events); + TelemetryEvent summary = this.EventSink.Events[0]; + + Assert.Equal(TelemetryEvent.ConfigProcessingSummaryName, summary.Name); + Assert.NotEqual(string.Empty, summary.CodeVersion); + Assert.NotEqual(Guid.Empty, summary.ActivityID); + Assert.Equal(string.Empty, summary.Caller); + Assert.Equal(configurationSet.InstanceIdentifier, Guid.Parse(summary.Properties[TelemetryEvent.SetID])); + Assert.False(int.Parse(summary.Properties[TelemetryEvent.FromHistory]) != 0); + Assert.Equal(((int)runIntent).ToString(), summary.Properties[TelemetryEvent.RunIntent]); + Assert.Equal(resultCode.ToString(), summary.Properties[TelemetryEvent.Result]); + Assert.Equal(((int)resultSource).ToString(), summary.Properties[TelemetryEvent.FailurePoint]); + + return summary; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs index 506ead01f1..85301c69e4 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Constants.cs @@ -1,50 +1,50 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - /// - /// Constants used by the tests. - /// - public class Constants - { - /// - /// The assembly name value used by xUnit traits. - /// - public const string AssemblyNameForTraits = "Microsoft.Management.Configuration.UnitTests"; - - /// - /// The namespace where xUnit traits will be defined. - /// - public const string NamespaceNameForTraits = "Microsoft.Management.Configuration.UnitTests.Helpers"; - - /// - /// The dynamic runtime factory handler identifier. - /// - public const string DynamicRuntimeHandlerIdentifier = "{73fea39f-6f4a-41c9-ba94-6fd14d633e40}"; - - /// - /// The DSCv3-specific dynamic runtime factory handler identifier. - /// Unlike DynamicRuntimeHandlerIdentifier, this pre-selects the DSCv3 processor engine. - /// - public const string DSCv3DynamicRuntimeHandlerIdentifier = "{5f83e564-ca26-41ca-89db-36f5f0517ffd}"; - - /// - /// Test guid for enabling test mode for the dynamic runtime factory. Forces factory to exclude 'runas' verb and sets current IL to medium. - /// - public const string EnableDynamicFactoryTestMode = "1e62d683-2999-44e7-81f7-6f8f35e8d731"; - - /// - /// Test guid for allowing the restricted integrity level to be supported. - /// - public const string EnableRestrictedIntegrityLevelTestGuid = "5cae3226-185f-4289-815c-3c089d238dc6"; - - /// - /// Test guid for forcing units to have a high integrity level during the final routing of unit processor creation. - /// - public const string ForceHighIntegrityLevelUnitsTestGuid = "f698d20f-3584-4f28-bc75-28037e08e651"; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + /// + /// Constants used by the tests. + /// + public class Constants + { + /// + /// The assembly name value used by xUnit traits. + /// + public const string AssemblyNameForTraits = "Microsoft.Management.Configuration.UnitTests"; + + /// + /// The namespace where xUnit traits will be defined. + /// + public const string NamespaceNameForTraits = "Microsoft.Management.Configuration.UnitTests.Helpers"; + + /// + /// The dynamic runtime factory handler identifier. + /// + public const string DynamicRuntimeHandlerIdentifier = "{73fea39f-6f4a-41c9-ba94-6fd14d633e40}"; + + /// + /// The DSCv3-specific dynamic runtime factory handler identifier. + /// Unlike DynamicRuntimeHandlerIdentifier, this pre-selects the DSCv3 processor engine. + /// + public const string DSCv3DynamicRuntimeHandlerIdentifier = "{5f83e564-ca26-41ca-89db-36f5f0517ffd}"; + + /// + /// Test guid for enabling test mode for the dynamic runtime factory. Forces factory to exclude 'runas' verb and sets current IL to medium. + /// + public const string EnableDynamicFactoryTestMode = "1e62d683-2999-44e7-81f7-6f8f35e8d731"; + + /// + /// Test guid for allowing the restricted integrity level to be supported. + /// + public const string EnableRestrictedIntegrityLevelTestGuid = "5cae3226-185f-4289-815c-3c089d238dc6"; + + /// + /// Test guid for forcing units to have a high integrity level during the final routing of unit processor creation. + /// + public const string ForceHighIntegrityLevelUnitsTestGuid = "f698d20f-3584-4f28-bc75-28037e08e651"; + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/DiagnosticsEventSink.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/DiagnosticsEventSink.cs index 8ef30705a0..9dd45ea752 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/DiagnosticsEventSink.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/DiagnosticsEventSink.cs @@ -1,60 +1,60 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Xunit.Abstractions; - using Xunit.Sdk; - - /// - /// This class aids in getting diagnostics data from the out to the xUnit infrastructure. - /// - public class DiagnosticsEventSink - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public DiagnosticsEventSink(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Gets the telemetry events that have been seen. - /// - public List Events { get; private set; } = new List(); - - /// - /// Handles diagnostic information from a . - /// - /// The object sending the information. - /// The diagnostic information. - internal void DiagnosticsHandler(object? sender, IDiagnosticInformation e) - { - if (e.Message.Contains(TelemetryEvent.Preamble)) - { - this.Events.Add(new TelemetryEvent(e.Message)); - } - - if (e.Level == DiagnosticLevel.Verbose) - { - this.fixture.MessageSink.OnMessage(new DiagnosticMessage(e.Message)); - } - else - { - this.log.WriteLine(e.Message); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Xunit.Abstractions; + using Xunit.Sdk; + + /// + /// This class aids in getting diagnostics data from the out to the xUnit infrastructure. + /// + public class DiagnosticsEventSink + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public DiagnosticsEventSink(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Gets the telemetry events that have been seen. + /// + public List Events { get; private set; } = new List(); + + /// + /// Handles diagnostic information from a . + /// + /// The object sending the information. + /// The diagnostic information. + internal void DiagnosticsHandler(object? sender, IDiagnosticInformation e) + { + if (e.Message.Contains(TelemetryEvent.Preamble)) + { + this.Events.Add(new TelemetryEvent(e.Message)); + } + + if (e.Level == DiagnosticLevel.Verbose) + { + this.fixture.MessageSink.OnMessage(new DiagnosticMessage(e.Message)); + } + else + { + this.log.WriteLine(e.Message); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs index 85a8f148b4..a0801db17d 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/Errors.cs @@ -1,59 +1,59 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - /// - /// Contains the error codes used by Microsoft.Management.Configuration. - /// - internal static class Errors - { -#pragma warning disable SA1310 // Field names should not contain underscore -#pragma warning disable SA1600 // Elements should be documented -#pragma warning disable SA1025 // Code should not contain multiple whitespace in a row - - public static readonly int WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE = unchecked((int)0x8A15C001); - public static readonly int WINGET_CONFIG_ERROR_INVALID_YAML = unchecked((int)0x8A15C002); - public static readonly int WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE = unchecked((int)0x8A15C003); - public static readonly int WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION = unchecked((int)0x8A15C004); - public static readonly int WINGET_CONFIG_ERROR_SET_APPLY_FAILED = unchecked((int)0x8A15C005); - public static readonly int WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER = unchecked((int)0x8A15C006); - public static readonly int WINGET_CONFIG_ERROR_MISSING_DEPENDENCY = unchecked((int)0x8A15C007); - public static readonly int WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED = unchecked((int)0x8A15C008); - public static readonly int WINGET_CONFIG_ERROR_ASSERTION_FAILED = unchecked((int)0x8A15C009); - public static readonly int WINGET_CONFIG_ERROR_MANUALLY_SKIPPED = unchecked((int)0x8A15C00A); - public static readonly int WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED = unchecked((int)0x8A15C00B); - public static readonly int WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE = unchecked((int)0x8A15C00C); - public static readonly int WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE = unchecked((int)0x8A15C00D); - public static readonly int WINGET_CONFIG_ERROR_MISSING_FIELD = unchecked((int)0x8A15C00E); - public static readonly int WINGET_CONFIG_ERROR_TEST_FAILED = unchecked((int)0x8A15C00F); - public static readonly int WINGET_CONFIG_ERROR_TEST_NOT_RUN = unchecked((int)0x8A15C010); - public static readonly int WINGET_CONFIG_ERROR_GET_FAILED = unchecked((int)0x8A15C011); - public static readonly int WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY = unchecked((int)0x8A15C013); - - // Configuration Processor Errors - public static readonly int WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED = unchecked((int)0x8A15C101); - public static readonly int WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY = unchecked((int)0x8A15C102); - public static readonly int WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES = unchecked((int)0x8A15C103); - public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_GET = unchecked((int)0x8A15C104); - public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST = unchecked((int)0x8A15C105); - public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_SET = unchecked((int)0x8A15C106); - public static readonly int WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT = unchecked((int)0x8A15C107); - public static readonly int WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE = unchecked((int)0x8A15C108); - public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT = unchecked((int)0x8A15C109); - public static readonly int WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT = unchecked((int)0x8A15C110); - public static readonly int WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN = unchecked((int)0x8A15C111); - public static readonly int WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR = unchecked((int)0x8A15C112); - public static readonly int WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH = unchecked((int)0x8A15C113); - - // Limitation Set Errors - public static readonly int CORE_INVALID_OPERATION = unchecked((int)0x80131509); - -#pragma warning restore SA1025 // Code should not contain multiple whitespace in a row -#pragma warning restore SA1600 // Elements should be documented -#pragma warning restore SA1310 // Field names should not contain underscore - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + /// + /// Contains the error codes used by Microsoft.Management.Configuration. + /// + internal static class Errors + { +#pragma warning disable SA1310 // Field names should not contain underscore +#pragma warning disable SA1600 // Elements should be documented +#pragma warning disable SA1025 // Code should not contain multiple whitespace in a row + + public static readonly int WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE = unchecked((int)0x8A15C001); + public static readonly int WINGET_CONFIG_ERROR_INVALID_YAML = unchecked((int)0x8A15C002); + public static readonly int WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE = unchecked((int)0x8A15C003); + public static readonly int WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION = unchecked((int)0x8A15C004); + public static readonly int WINGET_CONFIG_ERROR_SET_APPLY_FAILED = unchecked((int)0x8A15C005); + public static readonly int WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER = unchecked((int)0x8A15C006); + public static readonly int WINGET_CONFIG_ERROR_MISSING_DEPENDENCY = unchecked((int)0x8A15C007); + public static readonly int WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED = unchecked((int)0x8A15C008); + public static readonly int WINGET_CONFIG_ERROR_ASSERTION_FAILED = unchecked((int)0x8A15C009); + public static readonly int WINGET_CONFIG_ERROR_MANUALLY_SKIPPED = unchecked((int)0x8A15C00A); + public static readonly int WINGET_CONFIG_ERROR_WARNING_NOT_ACCEPTED = unchecked((int)0x8A15C00B); + public static readonly int WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE = unchecked((int)0x8A15C00C); + public static readonly int WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE = unchecked((int)0x8A15C00D); + public static readonly int WINGET_CONFIG_ERROR_MISSING_FIELD = unchecked((int)0x8A15C00E); + public static readonly int WINGET_CONFIG_ERROR_TEST_FAILED = unchecked((int)0x8A15C00F); + public static readonly int WINGET_CONFIG_ERROR_TEST_NOT_RUN = unchecked((int)0x8A15C010); + public static readonly int WINGET_CONFIG_ERROR_GET_FAILED = unchecked((int)0x8A15C011); + public static readonly int WINGET_CONFIG_ERROR_PARAMETER_INTEGRITY_BOUNDARY = unchecked((int)0x8A15C013); + + // Configuration Processor Errors + public static readonly int WINGET_CONFIG_ERROR_UNIT_NOT_INSTALLED = unchecked((int)0x8A15C101); + public static readonly int WINGET_CONFIG_ERROR_UNIT_NOT_FOUND_REPOSITORY = unchecked((int)0x8A15C102); + public static readonly int WINGET_CONFIG_ERROR_UNIT_MULTIPLE_MATCHES = unchecked((int)0x8A15C103); + public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_GET = unchecked((int)0x8A15C104); + public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_TEST = unchecked((int)0x8A15C105); + public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_SET = unchecked((int)0x8A15C106); + public static readonly int WINGET_CONFIG_ERROR_UNIT_MODULE_CONFLICT = unchecked((int)0x8A15C107); + public static readonly int WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE = unchecked((int)0x8A15C108); + public static readonly int WINGET_CONFIG_ERROR_UNIT_INVOKE_INVALID_RESULT = unchecked((int)0x8A15C109); + public static readonly int WINGET_CONFIG_ERROR_UNIT_SETTING_CONFIG_ROOT = unchecked((int)0x8A15C110); + public static readonly int WINGET_CONFIG_ERROR_UNIT_IMPORT_MODULE_ADMIN = unchecked((int)0x8A15C111); + public static readonly int WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR = unchecked((int)0x8A15C112); + public static readonly int WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH = unchecked((int)0x8A15C113); + + // Limitation Set Errors + public static readonly int CORE_INVALID_OPERATION = unchecked((int)0x80131509); + +#pragma warning restore SA1025 // Code should not contain multiple whitespace in a row +#pragma warning restore SA1600 // Elements should be documented +#pragma warning restore SA1310 // Field names should not contain underscore + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/FactSkipIfCI.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/FactSkipIfCI.cs index f24829f624..5caf6b2d2a 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/FactSkipIfCI.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/FactSkipIfCI.cs @@ -1,28 +1,28 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using Xunit; - - /// - /// Skip fact tests if running in CI builds. - /// - public class FactSkipIfCI : FactAttribute - { - /// - /// Initializes a new instance of the class. - /// - public FactSkipIfCI() - { - if (Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") is not null) - { - this.Skip = "Skip test for CI builds"; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using Xunit; + + /// + /// Skip fact tests if running in CI builds. + /// + public class FactSkipIfCI : FactAttribute + { + /// + /// Initializes a new instance of the class. + /// + public FactSkipIfCI() + { + if (Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") is not null) + { + this.Skip = "Skip test for CI builds"; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetAllSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetAllSettingsResultInstance.cs index d201422f32..ef0782f5d0 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetAllSettingsResultInstance.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetAllSettingsResultInstance.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Windows.Foundation.Collections; - - /// - /// Implements IGetAllSettingsResult. - /// - internal sealed partial class GetAllSettingsResultInstance : IGetAllSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public GetAllSettingsResultInstance(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); - - /// - public IList? Settings { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Windows.Foundation.Collections; + + /// + /// Implements IGetAllSettingsResult. + /// + internal sealed partial class GetAllSettingsResultInstance : IGetAllSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public GetAllSettingsResultInstance(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// + public IList? Settings { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetSettingsResultInstance.cs index 314c4c2d59..9fa995d825 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetSettingsResultInstance.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/GetSettingsResultInstance.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using Microsoft.Management.Configuration; - using Windows.Foundation.Collections; - - /// - /// Implements IGetSettingsResult. - /// - internal sealed partial class GetSettingsResultInstance : IGetSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public GetSettingsResultInstance(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); - - /// - public ValueSet? Settings { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using Microsoft.Management.Configuration; + using Windows.Foundation.Collections; + + /// + /// Implements IGetSettingsResult. + /// + internal sealed partial class GetSettingsResultInstance : IGetSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public GetSettingsResultInstance(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// + public ValueSet? Settings { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcAttribute.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcAttribute.cs index b5d9da91eb..43b0019f84 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcAttribute.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcAttribute.cs @@ -1,26 +1,26 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using Xunit.Sdk; - - /// - /// Trait used to mark a test as only for the in proc scenario. - /// - [TraitDiscoverer(InProcDiscoverer.TypeName, Constants.AssemblyNameForTraits)] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] - public class InProcAttribute : Attribute, ITraitAttribute - { - /// - /// Initializes a new instance of the class. - /// - public InProcAttribute() - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using Xunit.Sdk; + + /// + /// Trait used to mark a test as only for the in proc scenario. + /// + [TraitDiscoverer(InProcDiscoverer.TypeName, Constants.AssemblyNameForTraits)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] + public class InProcAttribute : Attribute, ITraitAttribute + { + /// + /// Initializes a new instance of the class. + /// + public InProcAttribute() + { + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcDiscoverer.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcDiscoverer.cs index 7f7afb6a46..1984985af2 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcDiscoverer.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/InProcDiscoverer.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - using Xunit.Abstractions; - using Xunit.Sdk; - - /// - /// Enables integration with xUnit trait system. - /// - public class InProcDiscoverer : ITraitDiscoverer - { - /// - /// The type name for this discoverer. - /// - public const string TypeName = Constants.NamespaceNameForTraits + ".InProcDiscoverer"; - - /// - /// Initializes a new instance of the class. - /// - public InProcDiscoverer() - { - } - - /// - /// Gets the trait information for the InProcAttribute. - /// - /// The trait information. - /// Trait name/value pairs. - public IEnumerable> GetTraits(IAttributeInfo traitAttribute) - { - yield return new KeyValuePair("Category", "InProc"); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + using Xunit.Abstractions; + using Xunit.Sdk; + + /// + /// Enables integration with xUnit trait system. + /// + public class InProcDiscoverer : ITraitDiscoverer + { + /// + /// The type name for this discoverer. + /// + public const string TypeName = Constants.NamespaceNameForTraits + ".InProcDiscoverer"; + + /// + /// Initializes a new instance of the class. + /// + public InProcDiscoverer() + { + } + + /// + /// Gets the trait information for the InProcAttribute. + /// + /// The trait information. + /// Trait name/value pairs. + public IEnumerable> GetTraits(IAttributeInfo traitAttribute) + { + yield return new KeyValuePair("Category", "InProc"); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs index 43894f0327..776458c701 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcAttribute.cs @@ -1,37 +1,37 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using Xunit.Sdk; - - /// - /// Trait used to mark a test as being able to run against the out of proc server. - /// - [TraitDiscoverer(OutOfProcDiscoverer.TypeName, Constants.AssemblyNameForTraits)] - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] - public class OutOfProcAttribute : Attribute, ITraitAttribute - { - /// - /// Initializes a new instance of the class. - /// - public OutOfProcAttribute() - { - // To run the tests OOP, you need to replace Microsoft.Management.Configuration.dll with Microsoft.Management.Configuration.OutOfProc.dll (renamed to remove the OutOfProc). - // You will also need to copy over Microsoft.Management.Configuration.winmd as it is needed by COM. - // - // You can use the script to do this: - // \src\Microsoft.Management.Configuration.OutOfProc\Prepare-ConfigurationOOPTests.ps1 -BuildOutputPath \src\x64\Debug - // - // It can be easier to run the tests on the command line because any changes needing a recompile will overwrite the DLL update above. - // 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\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.UnitTests.dll" --TestCaseFilter:Category=OutOfProc - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using Xunit.Sdk; + + /// + /// Trait used to mark a test as being able to run against the out of proc server. + /// + [TraitDiscoverer(OutOfProcDiscoverer.TypeName, Constants.AssemblyNameForTraits)] + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)] + public class OutOfProcAttribute : Attribute, ITraitAttribute + { + /// + /// Initializes a new instance of the class. + /// + public OutOfProcAttribute() + { + // To run the tests OOP, you need to replace Microsoft.Management.Configuration.dll with Microsoft.Management.Configuration.OutOfProc.dll (renamed to remove the OutOfProc). + // You will also need to copy over Microsoft.Management.Configuration.winmd as it is needed by COM. + // + // You can use the script to do this: + // \src\Microsoft.Management.Configuration.OutOfProc\Prepare-ConfigurationOOPTests.ps1 -BuildOutputPath \src\x64\Debug + // + // It can be easier to run the tests on the command line because any changes needing a recompile will overwrite the DLL update above. + // 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\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.UnitTests.dll" --TestCaseFilter:Category=OutOfProc + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcDiscoverer.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcDiscoverer.cs index fed7070c6f..9d08d16570 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcDiscoverer.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/OutOfProcDiscoverer.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - using Xunit.Abstractions; - using Xunit.Sdk; - - /// - /// Enables integration with xUnit trait system. - /// - public class OutOfProcDiscoverer : ITraitDiscoverer - { - /// - /// The type name for this discoverer. - /// - public const string TypeName = Constants.NamespaceNameForTraits + ".OutOfProcDiscoverer"; - - /// - /// Initializes a new instance of the class. - /// - public OutOfProcDiscoverer() - { - } - - /// - /// Gets the trait information for the OutOfProcAttribute. - /// - /// The trait information. - /// Trait name/value pairs. - public IEnumerable> GetTraits(IAttributeInfo traitAttribute) - { - yield return new KeyValuePair("Category", "OutOfProc"); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + using Xunit.Abstractions; + using Xunit.Sdk; + + /// + /// Enables integration with xUnit trait system. + /// + public class OutOfProcDiscoverer : ITraitDiscoverer + { + /// + /// The type name for this discoverer. + /// + public const string TypeName = Constants.NamespaceNameForTraits + ".OutOfProcDiscoverer"; + + /// + /// Initializes a new instance of the class. + /// + public OutOfProcDiscoverer() + { + } + + /// + /// Gets the trait information for the OutOfProcAttribute. + /// + /// The trait information. + /// Trait name/value pairs. + public IEnumerable> GetTraits(IAttributeInfo traitAttribute) + { + yield return new KeyValuePair("Category", "OutOfProc"); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/PowerShellTestsConstants.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/PowerShellTestsConstants.cs index 969e2f21f1..78645c39b4 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/PowerShellTestsConstants.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/PowerShellTestsConstants.cs @@ -1,27 +1,27 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - /// - /// PowerShell related constants. - /// - internal static class PowerShellTestsConstants - { -#pragma warning disable SA1600 // ElementsMustBeDocumented - public static class TestModule - { - public const string SimpleTestResourceModuleName = "xSimpleTestResource"; - public const string SimpleFileResourceName = "SimpleFileResource"; - public const string SimpleTestResourceName = "SimpleTestResource"; - public const string SimpleTestResourceThrowsName = "SimpleTestResourceThrows"; - public const string SimpleTestResourceErrorName = "SimpleTestResourceError"; - public const string SimpleTestResourceManifestFileName = "xSimpleTestResource.psd1"; - public const string SimpleTestResourceVersion = "0.0.0.1"; - } -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + /// + /// PowerShell related constants. + /// + internal static class PowerShellTestsConstants + { +#pragma warning disable SA1600 // ElementsMustBeDocumented + public static class TestModule + { + public const string SimpleTestResourceModuleName = "xSimpleTestResource"; + public const string SimpleFileResourceName = "SimpleFileResource"; + public const string SimpleTestResourceName = "SimpleTestResource"; + public const string SimpleTestResourceThrowsName = "SimpleTestResourceThrows"; + public const string SimpleTestResourceErrorName = "SimpleTestResourceError"; + public const string SimpleTestResourceManifestFileName = "xSimpleTestResource.psd1"; + public const string SimpleTestResourceVersion = "0.0.0.1"; + } +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TelemetryEvent.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TelemetryEvent.cs index f4163e3489..0596bfe828 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TelemetryEvent.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TelemetryEvent.cs @@ -1,140 +1,140 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - - /// - /// This class holds the data about a telemetry event detected via the diagnostics side channel. - /// - public class TelemetryEvent - { - /// - /// The initial indicator that the diagnostics message contains the contents of a telemetry event. - /// - public const string Preamble = "#DebugEventStream"; - - /// - /// The name of the ConfigUnitRun event. - /// - public const string ConfigUnitRunName = "ConfigUnitRun"; - - /// - /// The name of the ConfigProcessingSummary event. - /// - public const string ConfigProcessingSummaryName = "ConfigProcessingSummary"; - -#pragma warning disable SA1600 // Elements should be documented - - // Shared fields - public const string SetID = "SetID"; - public const string RunIntent = "RunIntent"; - public const string Result = "Result"; - public const string FailurePoint = "FailurePoint"; - - // ConfigUnitRun fields - public const string UnitID = "UnitID"; - public const string UnitName = "UnitName"; - public const string ModuleName = "ModuleName"; - public const string UnitIntent = "UnitIntent"; - public const string Action = "Action"; - public const string SettingsProvided = "SettingsProvided"; - - // ConfigProcessingSummary fields - public const string FromHistory = "FromHistory"; - public const string AssertCount = "AssertCount"; - public const string AssertsRun = "AssertsRun"; - public const string AssertsFailed = "AssertsFailed"; - public const string InformCount = "InformCount"; - public const string InformsRun = "InformsRun"; - public const string InformsFailed = "InformsFailed"; - public const string ApplyCount = "ApplyCount"; - public const string AppliesRun = "AppliesRun"; - public const string AppliesFailed = "AppliesFailed"; -#pragma warning restore SA1600 // Elements should be documented - - /// - /// Initializes a new instance of the class. - /// - /// The message containing the event data. - public TelemetryEvent(string eventMessage) - { - bool preambleSeen = false; - - foreach (string line in eventMessage.Split('\n')) - { - if (line == Preamble) - { - preambleSeen = true; - continue; - } - - if (!preambleSeen) - { - // Skip all lines until the preamble is seen - continue; - } - - int splitIndex = line.IndexOf(": "); - if (splitIndex != -1) - { - this.Properties.Add(line.Substring(0, splitIndex), line.Substring(splitIndex + 2)); - } - } - } - - /// - /// Gets the properties for this event. - /// - public Dictionary Properties { get; private set; } = new Dictionary(); - - /// - /// Gets the name of the event. - /// - public string Name - { - get - { - return this.Properties["Event"]; - } - } - - /// - /// Gets the activity id. - /// - public Guid ActivityID - { - get - { - return Guid.Parse(this.Properties["ActivityID"]); - } - } - - /// - /// Gets the version of the code. - /// - public string CodeVersion - { - get - { - return this.Properties["CodeVersion"]; - } - } - - /// - /// Gets the caller. - /// - public string Caller - { - get - { - return this.Properties["Caller"]; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + + /// + /// This class holds the data about a telemetry event detected via the diagnostics side channel. + /// + public class TelemetryEvent + { + /// + /// The initial indicator that the diagnostics message contains the contents of a telemetry event. + /// + public const string Preamble = "#DebugEventStream"; + + /// + /// The name of the ConfigUnitRun event. + /// + public const string ConfigUnitRunName = "ConfigUnitRun"; + + /// + /// The name of the ConfigProcessingSummary event. + /// + public const string ConfigProcessingSummaryName = "ConfigProcessingSummary"; + +#pragma warning disable SA1600 // Elements should be documented + + // Shared fields + public const string SetID = "SetID"; + public const string RunIntent = "RunIntent"; + public const string Result = "Result"; + public const string FailurePoint = "FailurePoint"; + + // ConfigUnitRun fields + public const string UnitID = "UnitID"; + public const string UnitName = "UnitName"; + public const string ModuleName = "ModuleName"; + public const string UnitIntent = "UnitIntent"; + public const string Action = "Action"; + public const string SettingsProvided = "SettingsProvided"; + + // ConfigProcessingSummary fields + public const string FromHistory = "FromHistory"; + public const string AssertCount = "AssertCount"; + public const string AssertsRun = "AssertsRun"; + public const string AssertsFailed = "AssertsFailed"; + public const string InformCount = "InformCount"; + public const string InformsRun = "InformsRun"; + public const string InformsFailed = "InformsFailed"; + public const string ApplyCount = "ApplyCount"; + public const string AppliesRun = "AppliesRun"; + public const string AppliesFailed = "AppliesFailed"; +#pragma warning restore SA1600 // Elements should be documented + + /// + /// Initializes a new instance of the class. + /// + /// The message containing the event data. + public TelemetryEvent(string eventMessage) + { + bool preambleSeen = false; + + foreach (string line in eventMessage.Split('\n')) + { + if (line == Preamble) + { + preambleSeen = true; + continue; + } + + if (!preambleSeen) + { + // Skip all lines until the preamble is seen + continue; + } + + int splitIndex = line.IndexOf(": "); + if (splitIndex != -1) + { + this.Properties.Add(line.Substring(0, splitIndex), line.Substring(splitIndex + 2)); + } + } + } + + /// + /// Gets the properties for this event. + /// + public Dictionary Properties { get; private set; } = new Dictionary(); + + /// + /// Gets the name of the event. + /// + public string Name + { + get + { + return this.Properties["Event"]; + } + } + + /// + /// Gets the activity id. + /// + public Guid ActivityID + { + get + { + return Guid.Parse(this.Properties["ActivityID"]); + } + } + + /// + /// Gets the version of the code. + /// + public string CodeVersion + { + get + { + return this.Properties["CodeVersion"]; + } + } + + /// + /// Gets the caller. + /// + public string Caller + { + get + { + return this.Properties["Caller"]; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempDirectory.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempDirectory.cs index 961153585d..74b870bbf8 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempDirectory.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempDirectory.cs @@ -1,120 +1,120 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.IO; - - /// - /// Creates a temporary directory in the user's temporary directory. - /// - internal class TempDirectory : IDisposable - { - private bool disposed = false; - private bool cleanup; - - /// - /// Initializes a new instance of the class. - /// - /// Optional directory name. If null, creates a random directory name. - /// Delete directory if already exists. Default true. - /// Deletes directory at disposing time. Default true. - public TempDirectory( - string? directoryName = null, - bool deleteIfExists = true, - bool cleanup = true) - { - var path = Path.GetTempPath(); - - if (directoryName is null) - { - this.DirectoryName = Path.GetRandomFileName(); - } - else - { - this.DirectoryName = directoryName; - } - - this.FullDirectoryPath = Path.Combine(Path.GetTempPath(), this.DirectoryName); - - if (deleteIfExists && Directory.Exists(this.FullDirectoryPath)) - { - Directory.Delete(this.FullDirectoryPath, true); - } - - Directory.CreateDirectory(this.FullDirectoryPath); - this.cleanup = cleanup; - } - - /// - /// Gets the directory name. - /// - public string DirectoryName { get; } - - /// - /// Gets the full directory name. - /// - public string FullDirectoryPath { get; } - - /// - /// IDisposable.Dispose . - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Copies all contents of a directory into this directory. - /// - /// Source directory. - public void CopyDirectory(string sourceDir) - { - this.CopyDirectory(sourceDir, this.FullDirectoryPath); - } - - /// - /// Protected disposed. - /// - /// Disposing. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (this.cleanup && Directory.Exists(this.FullDirectoryPath)) - { - Directory.Delete(this.FullDirectoryPath, true); - } - - this.disposed = true; - } - } - - private void CopyDirectory(string sourceDir, string destinationDir) - { - var dir = new DirectoryInfo(sourceDir); - - if (!dir.Exists) - { - throw new DirectoryNotFoundException(dir.FullName); - } - - Directory.CreateDirectory(destinationDir); - - foreach (FileInfo file in dir.GetFiles()) - { - file.CopyTo(Path.Combine(destinationDir, file.Name)); - } - - foreach (DirectoryInfo subDir in dir.GetDirectories()) - { - this.CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name)); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.IO; + + /// + /// Creates a temporary directory in the user's temporary directory. + /// + internal class TempDirectory : IDisposable + { + private bool disposed = false; + private bool cleanup; + + /// + /// Initializes a new instance of the class. + /// + /// Optional directory name. If null, creates a random directory name. + /// Delete directory if already exists. Default true. + /// Deletes directory at disposing time. Default true. + public TempDirectory( + string? directoryName = null, + bool deleteIfExists = true, + bool cleanup = true) + { + var path = Path.GetTempPath(); + + if (directoryName is null) + { + this.DirectoryName = Path.GetRandomFileName(); + } + else + { + this.DirectoryName = directoryName; + } + + this.FullDirectoryPath = Path.Combine(Path.GetTempPath(), this.DirectoryName); + + if (deleteIfExists && Directory.Exists(this.FullDirectoryPath)) + { + Directory.Delete(this.FullDirectoryPath, true); + } + + Directory.CreateDirectory(this.FullDirectoryPath); + this.cleanup = cleanup; + } + + /// + /// Gets the directory name. + /// + public string DirectoryName { get; } + + /// + /// Gets the full directory name. + /// + public string FullDirectoryPath { get; } + + /// + /// IDisposable.Dispose . + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Copies all contents of a directory into this directory. + /// + /// Source directory. + public void CopyDirectory(string sourceDir) + { + this.CopyDirectory(sourceDir, this.FullDirectoryPath); + } + + /// + /// Protected disposed. + /// + /// Disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (this.cleanup && Directory.Exists(this.FullDirectoryPath)) + { + Directory.Delete(this.FullDirectoryPath, true); + } + + this.disposed = true; + } + } + + private void CopyDirectory(string sourceDir, string destinationDir) + { + var dir = new DirectoryInfo(sourceDir); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException(dir.FullName); + } + + Directory.CreateDirectory(destinationDir); + + foreach (FileInfo file in dir.GetFiles()) + { + file.CopyTo(Path.Combine(destinationDir, file.Name)); + } + + foreach (DirectoryInfo subDir in dir.GetDirectories()) + { + this.CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name)); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempFile.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempFile.cs index 5306472683..07c3494ef6 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempFile.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TempFile.cs @@ -1,109 +1,109 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.IO; - - /// - /// Creates a temporary file in the user's temporary directory. - /// - internal class TempFile : IDisposable - { - private bool disposed = false; - private bool cleanup; - - /// - /// Initializes a new instance of the class. - /// - /// Optional file name. If null, creates a random file name. - /// Delete file if already exists. Default true. - /// Optional content. If not null or empty, creates file and writes to it. - /// Deletes file at disposing time. Default true. - public TempFile( - string? fileName = null, - bool deleteIfExists = true, - string? content = null, - bool cleanup = true) - { - if (fileName is null) - { - this.FileName = Path.GetRandomFileName(); - } - else - { - this.FileName = fileName; - } - - this.FullFileName = Path.Combine(Path.GetTempPath(), this.FileName); - - if (deleteIfExists && File.Exists(this.FullFileName)) - { - File.Delete(this.FullFileName); - } - - if (!string.IsNullOrWhiteSpace(content)) - { - this.CreateFile(content); - } - - this.cleanup = cleanup; - } - - /// - /// Gets the file name. - /// - public string FileName { get; } - - /// - /// Gets the full file name. - /// - public string FullFileName { get; } - - /// - /// IDisposable.Dispose . - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Creates the file. - /// - /// Content. - public void CreateFile(string? content = null) - { - if (content is null) - { - using var fs = File.Create(this.FullFileName); - } - else - { - File.WriteAllText(this.FullFileName, content); - } - } - - /// - /// Protected disposed. - /// - /// Disposing. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (this.cleanup && File.Exists(this.FullFileName)) - { - File.Delete(this.FullFileName); - } - - this.disposed = true; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.IO; + + /// + /// Creates a temporary file in the user's temporary directory. + /// + internal class TempFile : IDisposable + { + private bool disposed = false; + private bool cleanup; + + /// + /// Initializes a new instance of the class. + /// + /// Optional file name. If null, creates a random file name. + /// Delete file if already exists. Default true. + /// Optional content. If not null or empty, creates file and writes to it. + /// Deletes file at disposing time. Default true. + public TempFile( + string? fileName = null, + bool deleteIfExists = true, + string? content = null, + bool cleanup = true) + { + if (fileName is null) + { + this.FileName = Path.GetRandomFileName(); + } + else + { + this.FileName = fileName; + } + + this.FullFileName = Path.Combine(Path.GetTempPath(), this.FileName); + + if (deleteIfExists && File.Exists(this.FullFileName)) + { + File.Delete(this.FullFileName); + } + + if (!string.IsNullOrWhiteSpace(content)) + { + this.CreateFile(content); + } + + this.cleanup = cleanup; + } + + /// + /// Gets the file name. + /// + public string FileName { get; } + + /// + /// Gets the full file name. + /// + public string FullFileName { get; } + + /// + /// IDisposable.Dispose . + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Creates the file. + /// + /// Content. + public void CreateFile(string? content = null) + { + if (content is null) + { + using var fs = File.Create(this.FullFileName); + } + else + { + File.WriteAllText(this.FullFileName, content); + } + } + + /// + /// Protected disposed. + /// + /// Disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (this.cleanup && File.Exists(this.FullFileName)) + { + File.Delete(this.FullFileName); + } + + this.disposed = true; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs index f3eb43251a..86b710855e 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationProcessorFactory.cs @@ -1,137 +1,137 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - - /// - /// A test implementation of IConfigurationSetProcessorFactory. - /// - internal partial class TestConfigurationProcessorFactory : IConfigurationSetProcessorFactory - { - /// - /// Delegate type for CreateSetProcessor. - /// - /// The TestConfigurationProcessorFactory that is calling this function. - /// The set. - /// A new TestConfigurationSetProcessor for the set. - internal delegate IConfigurationSetProcessor CreateSetProcessorDelegateType(TestConfigurationProcessorFactory factory, ConfigurationSet configurationSet); - - /// - /// Diagnostics event; useful for logging and/or verbose output. - /// -#pragma warning disable CS0067 // The event is never used - public event EventHandler? Diagnostics; -#pragma warning restore CS0067 // The event is never used - - /// - /// Gets or sets the minimum diagnostic level to send. - /// - public DiagnosticLevel MinimumLevel { get; set; } = DiagnosticLevel.Informational; - - /// - /// Gets or sets the processor used when the incoming configuration set is null. - /// - internal TestConfigurationSetProcessor? NullProcessor { get; set; } - - /// - /// Gets or sets the processors to be used by this factory. - /// - internal Dictionary Processors { get; set; } = - new Dictionary(); - - /// - /// Gets or sets the exception used when the incoming configuration set is null. - /// - internal Exception? NullException { get; set; } - - /// - /// Gets or sets the exceptions to be used by this factory. - /// - internal Dictionary Exceptions { get; set; } = - new Dictionary(); - - /// - /// Gets or sets the delegate use to replace the default CreateSetProcessor functionality. - /// - internal CreateSetProcessorDelegateType? CreateSetProcessorDelegate { get; set; } - - /// - /// Creates a new TestConfigurationSetProcessor for the set. - /// - /// The set. - /// A new TestConfigurationSetProcessor for the set. - public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet configurationSet) - { - if (this.CreateSetProcessorDelegate != null) - { - return this.CreateSetProcessorDelegate(this, configurationSet); - } - - return this.DefaultCreateSetProcessor(configurationSet); - } - - /// - /// The default test implementation that creates a new TestConfigurationSetProcessor for the set. - /// - /// The set. - /// A new TestConfigurationSetProcessor for the set. - internal IConfigurationSetProcessor DefaultCreateSetProcessor(ConfigurationSet configurationSet) - { - if (configurationSet == null) - { - if (this.NullException != null) - { - throw this.NullException; - } - - if (this.NullProcessor == null) - { - this.NullProcessor = new TestConfigurationSetProcessor(null); - } - - return this.NullProcessor; - } - - if (this.Exceptions.ContainsKey(configurationSet)) - { - throw this.Exceptions[configurationSet]; - } - - if (!this.Processors.ContainsKey(configurationSet)) - { - this.Processors.Add(configurationSet, new TestConfigurationSetProcessor(configurationSet)); - } - - return this.Processors[configurationSet]; - } - - /// - /// A convenience function to create a new processor for the given set and store it in the dictionary for use in the test. - /// - /// The set. - /// A new TestConfigurationSetProcessor for the set. - internal TestConfigurationSetProcessor CreateTestProcessor(ConfigurationSet configurationSet) - { - this.Processors[configurationSet] = new TestConfigurationSetProcessor(configurationSet); - return this.Processors[configurationSet]; - } - - /// - /// A convenience function to create a new group processor for the given set and store it in the dictionary for use in the test. - /// - /// The set. - /// A new TestConfigurationSetGroupProcessor for the set. - internal TestConfigurationSetGroupProcessor CreateTestGroupProcessor(ConfigurationSet configurationSet) - { - TestConfigurationSetGroupProcessor result = new (configurationSet); - this.Processors[configurationSet] = result; - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + + /// + /// A test implementation of IConfigurationSetProcessorFactory. + /// + internal partial class TestConfigurationProcessorFactory : IConfigurationSetProcessorFactory + { + /// + /// Delegate type for CreateSetProcessor. + /// + /// The TestConfigurationProcessorFactory that is calling this function. + /// The set. + /// A new TestConfigurationSetProcessor for the set. + internal delegate IConfigurationSetProcessor CreateSetProcessorDelegateType(TestConfigurationProcessorFactory factory, ConfigurationSet configurationSet); + + /// + /// Diagnostics event; useful for logging and/or verbose output. + /// +#pragma warning disable CS0067 // The event is never used + public event EventHandler? Diagnostics; +#pragma warning restore CS0067 // The event is never used + + /// + /// Gets or sets the minimum diagnostic level to send. + /// + public DiagnosticLevel MinimumLevel { get; set; } = DiagnosticLevel.Informational; + + /// + /// Gets or sets the processor used when the incoming configuration set is null. + /// + internal TestConfigurationSetProcessor? NullProcessor { get; set; } + + /// + /// Gets or sets the processors to be used by this factory. + /// + internal Dictionary Processors { get; set; } = + new Dictionary(); + + /// + /// Gets or sets the exception used when the incoming configuration set is null. + /// + internal Exception? NullException { get; set; } + + /// + /// Gets or sets the exceptions to be used by this factory. + /// + internal Dictionary Exceptions { get; set; } = + new Dictionary(); + + /// + /// Gets or sets the delegate use to replace the default CreateSetProcessor functionality. + /// + internal CreateSetProcessorDelegateType? CreateSetProcessorDelegate { get; set; } + + /// + /// Creates a new TestConfigurationSetProcessor for the set. + /// + /// The set. + /// A new TestConfigurationSetProcessor for the set. + public IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet configurationSet) + { + if (this.CreateSetProcessorDelegate != null) + { + return this.CreateSetProcessorDelegate(this, configurationSet); + } + + return this.DefaultCreateSetProcessor(configurationSet); + } + + /// + /// The default test implementation that creates a new TestConfigurationSetProcessor for the set. + /// + /// The set. + /// A new TestConfigurationSetProcessor for the set. + internal IConfigurationSetProcessor DefaultCreateSetProcessor(ConfigurationSet configurationSet) + { + if (configurationSet == null) + { + if (this.NullException != null) + { + throw this.NullException; + } + + if (this.NullProcessor == null) + { + this.NullProcessor = new TestConfigurationSetProcessor(null); + } + + return this.NullProcessor; + } + + if (this.Exceptions.ContainsKey(configurationSet)) + { + throw this.Exceptions[configurationSet]; + } + + if (!this.Processors.ContainsKey(configurationSet)) + { + this.Processors.Add(configurationSet, new TestConfigurationSetProcessor(configurationSet)); + } + + return this.Processors[configurationSet]; + } + + /// + /// A convenience function to create a new processor for the given set and store it in the dictionary for use in the test. + /// + /// The set. + /// A new TestConfigurationSetProcessor for the set. + internal TestConfigurationSetProcessor CreateTestProcessor(ConfigurationSet configurationSet) + { + this.Processors[configurationSet] = new TestConfigurationSetProcessor(configurationSet); + return this.Processors[configurationSet]; + } + + /// + /// A convenience function to create a new group processor for the given set and store it in the dictionary for use in the test. + /// + /// The set. + /// A new TestConfigurationSetGroupProcessor for the set. + internal TestConfigurationSetGroupProcessor CreateTestGroupProcessor(ConfigurationSet configurationSet) + { + TestConfigurationSetGroupProcessor result = new (configurationSet); + this.Processors[configurationSet] = result; + return result; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetGroupProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetGroupProcessor.cs index b5b4f17c10..d87e2980da 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetGroupProcessor.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetGroupProcessor.cs @@ -1,120 +1,120 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Runtime.InteropServices.WindowsRuntime; - using System.Threading; - using System.Threading.Tasks; - using Windows.Foundation; - using Windows.Foundation.Collections; - - /// - /// A test implementation of IConfigurationGroupProcessor. - /// - internal partial class TestConfigurationSetGroupProcessor : TestConfigurationSetProcessor, IConfigurationGroupProcessor - { - /// - /// The event that is waited on before actually processing the async operations. - /// - private AutoResetEvent asyncWaitEvent = new AutoResetEvent(false); - - /// - /// Initializes a new instance of the class. - /// - /// The set that this processor is for. - internal TestConfigurationSetGroupProcessor(ConfigurationSet? set) - : base(set) - { - } - - /// - /// Gets the group that this processor targets. - /// - public object? Group - { - get { return this.Set; } - } - - /// - /// Gets or sets a value indicating whether the async methods should wait on an event before processing. - /// - internal bool ShouldWaitOnAsyncEvent { get; set; } = false; - - /// - /// Apply settings for the group. - /// - /// The progress handler. - /// The operation to apply settings. - public IAsyncOperation ApplyGroupSettingsAsync(EventHandler progressHandler) - { - return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => - { - this.WaitOnAsyncEvent(cancellationToken); - - ApplyGroupSettingsResultInstance result = new (this.Group); - result.UnitResults = new List(); - - if (this.Set != null) - { - TestConfigurationUnitGroupProcessor.ApplyGroupSettings(this.Set.Units, progressHandler, result); - } - - return result; - })); - } - - /// - /// Test settings for the group. - /// - /// The progress handler. - /// The operation to test settings. - public IAsyncOperation TestGroupSettingsAsync(EventHandler progressHandler) - { - return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => - { - this.WaitOnAsyncEvent(cancellationToken); - - TestGroupSettingsResultInstance result = new (this.Group); - result.UnitResults = new List(); - - if (this.Set != null) - { - result.TestResult = TestConfigurationUnitGroupProcessor.GetTestResult(this.Set.Metadata); - TestConfigurationUnitGroupProcessor.TestGroupSettings(this.Set.Units, progressHandler, result); - } - - return result; - })); - } - - /// - /// Signals the async event. - /// - internal void SignalAsyncEvent() - { - this.asyncWaitEvent.Set(); - } - - /// - /// Waits on the async event. - /// - private void WaitOnAsyncEvent(CancellationToken cancellationToken) - { - if (this.ShouldWaitOnAsyncEvent) - { - cancellationToken.Register(() => this.asyncWaitEvent.Set()); - if (!this.asyncWaitEvent.WaitOne(10000)) - { - throw new TimeoutException(); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.InteropServices.WindowsRuntime; + using System.Threading; + using System.Threading.Tasks; + using Windows.Foundation; + using Windows.Foundation.Collections; + + /// + /// A test implementation of IConfigurationGroupProcessor. + /// + internal partial class TestConfigurationSetGroupProcessor : TestConfigurationSetProcessor, IConfigurationGroupProcessor + { + /// + /// The event that is waited on before actually processing the async operations. + /// + private AutoResetEvent asyncWaitEvent = new AutoResetEvent(false); + + /// + /// Initializes a new instance of the class. + /// + /// The set that this processor is for. + internal TestConfigurationSetGroupProcessor(ConfigurationSet? set) + : base(set) + { + } + + /// + /// Gets the group that this processor targets. + /// + public object? Group + { + get { return this.Set; } + } + + /// + /// Gets or sets a value indicating whether the async methods should wait on an event before processing. + /// + internal bool ShouldWaitOnAsyncEvent { get; set; } = false; + + /// + /// Apply settings for the group. + /// + /// The progress handler. + /// The operation to apply settings. + public IAsyncOperation ApplyGroupSettingsAsync(EventHandler progressHandler) + { + return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + ApplyGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List(); + + if (this.Set != null) + { + TestConfigurationUnitGroupProcessor.ApplyGroupSettings(this.Set.Units, progressHandler, result); + } + + return result; + })); + } + + /// + /// Test settings for the group. + /// + /// The progress handler. + /// The operation to test settings. + public IAsyncOperation TestGroupSettingsAsync(EventHandler progressHandler) + { + return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + TestGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List(); + + if (this.Set != null) + { + result.TestResult = TestConfigurationUnitGroupProcessor.GetTestResult(this.Set.Metadata); + TestConfigurationUnitGroupProcessor.TestGroupSettings(this.Set.Units, progressHandler, result); + } + + return result; + })); + } + + /// + /// Signals the async event. + /// + internal void SignalAsyncEvent() + { + this.asyncWaitEvent.Set(); + } + + /// + /// Waits on the async event. + /// + private void WaitOnAsyncEvent(CancellationToken cancellationToken) + { + if (this.ShouldWaitOnAsyncEvent) + { + cancellationToken.Register(() => this.asyncWaitEvent.Set()); + if (!this.asyncWaitEvent.WaitOne(10000)) + { + throw new TimeoutException(); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs index 3b231d1b87..35c5b7fe82 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationSetProcessor.cs @@ -1,149 +1,149 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - - /// - /// A test implementation of IConfigurationSetProcessor. - /// - internal partial class TestConfigurationSetProcessor : IConfigurationSetProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The set that this processor is for. - internal TestConfigurationSetProcessor(ConfigurationSet? set) - { - this.Set = set; - } - - /// - /// Gets or sets the processors to be used by this factory. - /// - internal Dictionary Processors { get; set; } = - new Dictionary(); - - /// - /// Gets or sets the details to be used by this factory. - /// - internal Dictionary Details { get; set; } = - new Dictionary(); - - /// - /// Gets or sets the exceptions to be used by this factory. - /// - internal Dictionary Exceptions { get; set; } = - new Dictionary(); - - /// - /// Gets or sets a value indicating whether the default unit processors for groups will enable group processing. - /// - internal bool EnableDefaultGroupProcessorCreation { get; set; } = false; - - /// - /// Gets the ConfigurationSet that this processor targets. - /// - protected ConfigurationSet? Set { get; private set; } - - /// - /// Creates a new unit processor for the given unit. - /// - /// The unit. - /// The configuration unit processor. - public IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit unit) - { - if (this.Exceptions.ContainsKey(unit)) - { - throw this.Exceptions[unit]; - } - - if (!this.Processors.ContainsKey(unit)) - { - if (this.EnableDefaultGroupProcessorCreation && unit.IsGroup) - { - this.Processors.Add(unit, new TestConfigurationUnitGroupProcessor(unit)); - } - else - { - this.Processors.Add(unit, new TestConfigurationUnitProcessor(unit)); - } - } - - return this.Processors[unit]; - } - - /// - /// Gets the unit processor details for the given unit. - /// - /// The unit. - /// The detail flags. - /// The details requested. - public IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) - { - if (this.Exceptions.ContainsKey(unit)) - { - throw this.Exceptions[unit]; - } - - if (!this.Details.ContainsKey(unit)) - { - this.Details.Add(unit, new TestConfigurationUnitProcessorDetails(unit, detailFlags)); - } - - return this.Details[unit]; - } - - /// - /// Creates a new test processor for the given unit. - /// - /// The unit. - /// The configuration unit processor. - internal TestConfigurationUnitProcessor CreateTestProcessor(ConfigurationUnit unit) - { - this.Processors[unit] = new TestConfigurationUnitProcessor(unit); - return this.Processors[unit]; - } - - /// - /// Creates a new test group processor for the given unit. - /// - /// The unit. - /// A new TestConfigurationUnitGroupProcessor for the unit. - internal TestConfigurationUnitGroupProcessor CreateTestGroupProcessor(ConfigurationUnit unit) - { - TestConfigurationUnitGroupProcessor result = new (unit); - this.Processors[unit] = result; - return result; - } - - /// - /// Creates a new test processor that supports GetAllSettings for the given unit. - /// - /// The unit. - /// The configuration unit processor. - internal TestGetAllSettingsConfigurationUnitProcessor CreateGetAllSettingsTestProcessor(ConfigurationUnit unit) - { - var processor = new TestGetAllSettingsConfigurationUnitProcessor(unit); - this.Processors[unit] = processor; - return processor; - } - - /// - /// Creates a new unit processor details for the given unit. - /// - /// The unit. - /// The detail flags. - /// The details requested. - internal TestConfigurationUnitProcessorDetails CreateUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) - { - this.Details[unit] = new TestConfigurationUnitProcessorDetails(unit, detailFlags); - return this.Details[unit]; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + + /// + /// A test implementation of IConfigurationSetProcessor. + /// + internal partial class TestConfigurationSetProcessor : IConfigurationSetProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The set that this processor is for. + internal TestConfigurationSetProcessor(ConfigurationSet? set) + { + this.Set = set; + } + + /// + /// Gets or sets the processors to be used by this factory. + /// + internal Dictionary Processors { get; set; } = + new Dictionary(); + + /// + /// Gets or sets the details to be used by this factory. + /// + internal Dictionary Details { get; set; } = + new Dictionary(); + + /// + /// Gets or sets the exceptions to be used by this factory. + /// + internal Dictionary Exceptions { get; set; } = + new Dictionary(); + + /// + /// Gets or sets a value indicating whether the default unit processors for groups will enable group processing. + /// + internal bool EnableDefaultGroupProcessorCreation { get; set; } = false; + + /// + /// Gets the ConfigurationSet that this processor targets. + /// + protected ConfigurationSet? Set { get; private set; } + + /// + /// Creates a new unit processor for the given unit. + /// + /// The unit. + /// The configuration unit processor. + public IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit unit) + { + if (this.Exceptions.ContainsKey(unit)) + { + throw this.Exceptions[unit]; + } + + if (!this.Processors.ContainsKey(unit)) + { + if (this.EnableDefaultGroupProcessorCreation && unit.IsGroup) + { + this.Processors.Add(unit, new TestConfigurationUnitGroupProcessor(unit)); + } + else + { + this.Processors.Add(unit, new TestConfigurationUnitProcessor(unit)); + } + } + + return this.Processors[unit]; + } + + /// + /// Gets the unit processor details for the given unit. + /// + /// The unit. + /// The detail flags. + /// The details requested. + public IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) + { + if (this.Exceptions.ContainsKey(unit)) + { + throw this.Exceptions[unit]; + } + + if (!this.Details.ContainsKey(unit)) + { + this.Details.Add(unit, new TestConfigurationUnitProcessorDetails(unit, detailFlags)); + } + + return this.Details[unit]; + } + + /// + /// Creates a new test processor for the given unit. + /// + /// The unit. + /// The configuration unit processor. + internal TestConfigurationUnitProcessor CreateTestProcessor(ConfigurationUnit unit) + { + this.Processors[unit] = new TestConfigurationUnitProcessor(unit); + return this.Processors[unit]; + } + + /// + /// Creates a new test group processor for the given unit. + /// + /// The unit. + /// A new TestConfigurationUnitGroupProcessor for the unit. + internal TestConfigurationUnitGroupProcessor CreateTestGroupProcessor(ConfigurationUnit unit) + { + TestConfigurationUnitGroupProcessor result = new (unit); + this.Processors[unit] = result; + return result; + } + + /// + /// Creates a new test processor that supports GetAllSettings for the given unit. + /// + /// The unit. + /// The configuration unit processor. + internal TestGetAllSettingsConfigurationUnitProcessor CreateGetAllSettingsTestProcessor(ConfigurationUnit unit) + { + var processor = new TestGetAllSettingsConfigurationUnitProcessor(unit); + this.Processors[unit] = processor; + return processor; + } + + /// + /// Creates a new unit processor details for the given unit. + /// + /// The unit. + /// The detail flags. + /// The details requested. + internal TestConfigurationUnitProcessorDetails CreateUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) + { + this.Details[unit] = new TestConfigurationUnitProcessorDetails(unit, detailFlags); + return this.Details[unit]; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitGroupProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitGroupProcessor.cs index d190b2320b..54a2e542a7 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitGroupProcessor.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitGroupProcessor.cs @@ -1,205 +1,205 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Runtime.InteropServices.WindowsRuntime; - using System.Threading; - using System.Threading.Tasks; - using Windows.Foundation; - using Windows.Foundation.Collections; - - /// - /// A test implementation of IConfigurationGroupProcessor. - /// - internal partial class TestConfigurationUnitGroupProcessor : TestConfigurationUnitProcessor, IConfigurationGroupProcessor - { - /// - /// The Setting key that will be used to set the TestResult of the unit. - /// - internal const string TestResultSetting = "TestResult"; - - /// - /// The event that is waited on before actually processing the async operations. - /// - private AutoResetEvent asyncWaitEvent = new AutoResetEvent(false); - - /// - /// Initializes a new instance of the class. - /// - /// The unit that this processor is for. - internal TestConfigurationUnitGroupProcessor(ConfigurationUnit unit) - : base(unit) - { - } - - /// - /// Gets the group that this processor targets. - /// - public object Group - { - get { return this.Unit; } - } - - /// - /// Gets or sets a value indicating whether the async methods should wait on an event before processing. - /// - internal bool ShouldWaitOnAsyncEvent { get; set; } = false; - - /// - /// Apply settings for the group. - /// - /// The progress handler. - /// The operation to apply settings. - public IAsyncOperation ApplyGroupSettingsAsync(EventHandler progressHandler) - { - return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => - { - this.WaitOnAsyncEvent(cancellationToken); - - ApplyGroupSettingsResultInstance result = new (this.Group); - result.UnitResults = new List(); - - ApplyGroupSettings(this.Unit.Units, progressHandler, result); - - return result; - })); - } - - /// - /// Test settings for the group. - /// - /// The progress handler. - /// The operation to test settings. - public IAsyncOperation TestGroupSettingsAsync(EventHandler progressHandler) - { - return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => - { - this.WaitOnAsyncEvent(cancellationToken); - - TestGroupSettingsResultInstance result = new (this.Group); - result.UnitResults = new List(); - - result.TestResult = GetTestResult(this.Unit.Metadata); - TestGroupSettings(this.Unit.Units, progressHandler, result); - - return result; - })); - } - - /// - /// Gets the tests result for the given unit. - /// - /// The unit. - /// The test result for the unit. - internal static ConfigurationTestResult GetTestResult(ConfigurationUnit unit) - { - return GetTestResult(unit.Settings); - } - - /// - /// Gets the tests result for the given values. - /// - /// The values. - /// The test result for the values. - internal static ConfigurationTestResult GetTestResult(ValueSet values) - { - if (values.ContainsKey(TestResultSetting)) - { - string? valueString = values[TestResultSetting]?.ToString(); - if (valueString != null) - { - return Enum.Parse(valueString); - } - } - - return ConfigurationTestResult.Positive; - } - - /// - /// Applies group settings for the given group members. - /// - /// The group members. - /// The progress reporting object. - /// The result object. - internal static void ApplyGroupSettings(IList? groupMembers, EventHandler progress, ApplyGroupSettingsResultInstance result) - { - if (groupMembers != null) - { - foreach (ConfigurationUnit unit in groupMembers) - { - ApplyGroupMemberSettingsResultInstance unitResult = new (unit); - result.UnitResults!.Add(unitResult); - - unitResult.State = ConfigurationUnitState.InProgress; - progress.Invoke(null, unitResult); - - unitResult.PreviouslyInDesiredState = GetTestResult(unit) == ConfigurationTestResult.Positive; - - if (unit.IsGroup) - { - ApplyGroupSettings(unit.Units, progress, result); - } - - unitResult.State = ConfigurationUnitState.Completed; - progress.Invoke(null, unitResult); - } - } - } - - /// - /// Tests group settings for the given group members. - /// - /// The group members. - /// The progress reporting object. - /// The result object. - internal static void TestGroupSettings(IList? groupMembers, EventHandler progress, TestGroupSettingsResultInstance result) - { - if (groupMembers != null) - { - foreach (ConfigurationUnit unit in groupMembers) - { - TestSettingsResultInstance unitResult = new (unit); - - if (unit.IsGroup) - { - TestGroupSettings(unit.Units, progress, result); - } - - unitResult.TestResult = GetTestResult(unit); - result.UnitResults!.Add(unitResult); - progress.Invoke(null, unitResult); - } - } - } - - /// - /// Signals the async event. - /// - internal void SignalAsyncEvent() - { - this.asyncWaitEvent.Set(); - } - - /// - /// Waits on the async event. - /// - private void WaitOnAsyncEvent(CancellationToken cancellationToken) - { - if (this.ShouldWaitOnAsyncEvent) - { - cancellationToken.Register(() => this.asyncWaitEvent.Set()); - if (!this.asyncWaitEvent.WaitOne(10000)) - { - throw new TimeoutException(); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.InteropServices.WindowsRuntime; + using System.Threading; + using System.Threading.Tasks; + using Windows.Foundation; + using Windows.Foundation.Collections; + + /// + /// A test implementation of IConfigurationGroupProcessor. + /// + internal partial class TestConfigurationUnitGroupProcessor : TestConfigurationUnitProcessor, IConfigurationGroupProcessor + { + /// + /// The Setting key that will be used to set the TestResult of the unit. + /// + internal const string TestResultSetting = "TestResult"; + + /// + /// The event that is waited on before actually processing the async operations. + /// + private AutoResetEvent asyncWaitEvent = new AutoResetEvent(false); + + /// + /// Initializes a new instance of the class. + /// + /// The unit that this processor is for. + internal TestConfigurationUnitGroupProcessor(ConfigurationUnit unit) + : base(unit) + { + } + + /// + /// Gets the group that this processor targets. + /// + public object Group + { + get { return this.Unit; } + } + + /// + /// Gets or sets a value indicating whether the async methods should wait on an event before processing. + /// + internal bool ShouldWaitOnAsyncEvent { get; set; } = false; + + /// + /// Apply settings for the group. + /// + /// The progress handler. + /// The operation to apply settings. + public IAsyncOperation ApplyGroupSettingsAsync(EventHandler progressHandler) + { + return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + ApplyGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List(); + + ApplyGroupSettings(this.Unit.Units, progressHandler, result); + + return result; + })); + } + + /// + /// Test settings for the group. + /// + /// The progress handler. + /// The operation to test settings. + public IAsyncOperation TestGroupSettingsAsync(EventHandler progressHandler) + { + return AsyncInfo.Run((CancellationToken cancellationToken) => Task.Run(() => + { + this.WaitOnAsyncEvent(cancellationToken); + + TestGroupSettingsResultInstance result = new (this.Group); + result.UnitResults = new List(); + + result.TestResult = GetTestResult(this.Unit.Metadata); + TestGroupSettings(this.Unit.Units, progressHandler, result); + + return result; + })); + } + + /// + /// Gets the tests result for the given unit. + /// + /// The unit. + /// The test result for the unit. + internal static ConfigurationTestResult GetTestResult(ConfigurationUnit unit) + { + return GetTestResult(unit.Settings); + } + + /// + /// Gets the tests result for the given values. + /// + /// The values. + /// The test result for the values. + internal static ConfigurationTestResult GetTestResult(ValueSet values) + { + if (values.ContainsKey(TestResultSetting)) + { + string? valueString = values[TestResultSetting]?.ToString(); + if (valueString != null) + { + return Enum.Parse(valueString); + } + } + + return ConfigurationTestResult.Positive; + } + + /// + /// Applies group settings for the given group members. + /// + /// The group members. + /// The progress reporting object. + /// The result object. + internal static void ApplyGroupSettings(IList? groupMembers, EventHandler progress, ApplyGroupSettingsResultInstance result) + { + if (groupMembers != null) + { + foreach (ConfigurationUnit unit in groupMembers) + { + ApplyGroupMemberSettingsResultInstance unitResult = new (unit); + result.UnitResults!.Add(unitResult); + + unitResult.State = ConfigurationUnitState.InProgress; + progress.Invoke(null, unitResult); + + unitResult.PreviouslyInDesiredState = GetTestResult(unit) == ConfigurationTestResult.Positive; + + if (unit.IsGroup) + { + ApplyGroupSettings(unit.Units, progress, result); + } + + unitResult.State = ConfigurationUnitState.Completed; + progress.Invoke(null, unitResult); + } + } + } + + /// + /// Tests group settings for the given group members. + /// + /// The group members. + /// The progress reporting object. + /// The result object. + internal static void TestGroupSettings(IList? groupMembers, EventHandler progress, TestGroupSettingsResultInstance result) + { + if (groupMembers != null) + { + foreach (ConfigurationUnit unit in groupMembers) + { + TestSettingsResultInstance unitResult = new (unit); + + if (unit.IsGroup) + { + TestGroupSettings(unit.Units, progress, result); + } + + unitResult.TestResult = GetTestResult(unit); + result.UnitResults!.Add(unitResult); + progress.Invoke(null, unitResult); + } + } + } + + /// + /// Signals the async event. + /// + internal void SignalAsyncEvent() + { + this.asyncWaitEvent.Set(); + } + + /// + /// Waits on the async event. + /// + private void WaitOnAsyncEvent(CancellationToken cancellationToken) + { + if (this.ShouldWaitOnAsyncEvent) + { + cancellationToken.Register(() => this.asyncWaitEvent.Set()); + if (!this.asyncWaitEvent.WaitOne(10000)) + { + throw new TimeoutException(); + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessor.cs index a74af090cb..6150a6b8a0 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessor.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessor.cs @@ -1,150 +1,150 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - - /// - /// A test implementation of IConfigurationProcessorFactory. - /// - internal partial class TestConfigurationUnitProcessor : IConfigurationUnitProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The unit. - internal TestConfigurationUnitProcessor(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// The delegate for ApplySettings. - /// - /// The result. - internal delegate IApplySettingsResult ApplySettingsDelegateType(); - - /// - /// The delegate for GetSettings. - /// - /// The result. - internal delegate IGetSettingsResult GetSettingsDelegateType(); - - /// - /// The delegate for TestSettings. - /// - /// The result. - internal delegate ITestSettingsResult TestSettingsDelegateType(); - - /// - /// The delegate for TestSettings that passes the unit in. - /// - /// The unit. - /// The result. - internal delegate ITestSettingsResult TestSettingsDelegateWithUnitType(ConfigurationUnit unit); - - /// - /// Gets or sets the directives overlay. - /// - public IReadOnlyDictionary? DirectivesOverlay { get; set; } - - /// - /// Gets the configuration unit. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - /// Gets or sets the delegate object for ApplySettings. - /// - internal ApplySettingsDelegateType? ApplySettingsDelegate { get; set; } - - /// - /// Gets the number of times ApplySettings is called. - /// - internal int ApplySettingsCalls { get; private set; } = 0; - - /// - /// Gets or sets the delegate object for GetSettings. - /// - internal GetSettingsDelegateType? GetSettingsDelegate { get; set; } - - /// - /// Gets the number of times GetSettings is called. - /// - internal int GetSettingsCalls { get; private set; } = 0; - - /// - /// Gets or sets the delegate object for TestSettings. - /// - internal TestSettingsDelegateType? TestSettingsDelegate { get; set; } - - /// - /// Gets or sets the delegate object for TestSettings that takes in the unit. - /// - internal TestSettingsDelegateWithUnitType? TestSettingsDelegateWithUnit { get; set; } - - /// - /// Gets the number of times TestSettings is called. - /// - internal int TestSettingsCalls { get; private set; } = 0; - - /// - /// Calls the ApplySettingsDelegate if one is provided; returns success if not. - /// - /// The result. - public IApplySettingsResult ApplySettings() - { - ++this.ApplySettingsCalls; - if (this.ApplySettingsDelegate != null) - { - return this.ApplySettingsDelegate(); - } - else - { - return new ApplySettingsResultInstance(this.Unit); - } - } - - /// - /// Calls the GetSettingsDelegate if one is provided; returns success if not (with no settings values). - /// - /// The result. - public IGetSettingsResult GetSettings() - { - ++this.GetSettingsCalls; - if (this.GetSettingsDelegate != null) - { - return this.GetSettingsDelegate(); - } - else - { - return new GetSettingsResultInstance(this.Unit); - } - } - - /// - /// Calls the TestSettingsDelegate if one is provided; returns success if not (with a positive test result). - /// - /// The result. - public ITestSettingsResult TestSettings() - { - ++this.TestSettingsCalls; - if (this.TestSettingsDelegateWithUnit != null) - { - return this.TestSettingsDelegateWithUnit(this.Unit); - } - else if (this.TestSettingsDelegate != null) - { - return this.TestSettingsDelegate(); - } - else - { - return new TestSettingsResultInstance(this.Unit) { TestResult = ConfigurationTestResult.Positive }; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + + /// + /// A test implementation of IConfigurationProcessorFactory. + /// + internal partial class TestConfigurationUnitProcessor : IConfigurationUnitProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The unit. + internal TestConfigurationUnitProcessor(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// The delegate for ApplySettings. + /// + /// The result. + internal delegate IApplySettingsResult ApplySettingsDelegateType(); + + /// + /// The delegate for GetSettings. + /// + /// The result. + internal delegate IGetSettingsResult GetSettingsDelegateType(); + + /// + /// The delegate for TestSettings. + /// + /// The result. + internal delegate ITestSettingsResult TestSettingsDelegateType(); + + /// + /// The delegate for TestSettings that passes the unit in. + /// + /// The unit. + /// The result. + internal delegate ITestSettingsResult TestSettingsDelegateWithUnitType(ConfigurationUnit unit); + + /// + /// Gets or sets the directives overlay. + /// + public IReadOnlyDictionary? DirectivesOverlay { get; set; } + + /// + /// Gets the configuration unit. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + /// Gets or sets the delegate object for ApplySettings. + /// + internal ApplySettingsDelegateType? ApplySettingsDelegate { get; set; } + + /// + /// Gets the number of times ApplySettings is called. + /// + internal int ApplySettingsCalls { get; private set; } = 0; + + /// + /// Gets or sets the delegate object for GetSettings. + /// + internal GetSettingsDelegateType? GetSettingsDelegate { get; set; } + + /// + /// Gets the number of times GetSettings is called. + /// + internal int GetSettingsCalls { get; private set; } = 0; + + /// + /// Gets or sets the delegate object for TestSettings. + /// + internal TestSettingsDelegateType? TestSettingsDelegate { get; set; } + + /// + /// Gets or sets the delegate object for TestSettings that takes in the unit. + /// + internal TestSettingsDelegateWithUnitType? TestSettingsDelegateWithUnit { get; set; } + + /// + /// Gets the number of times TestSettings is called. + /// + internal int TestSettingsCalls { get; private set; } = 0; + + /// + /// Calls the ApplySettingsDelegate if one is provided; returns success if not. + /// + /// The result. + public IApplySettingsResult ApplySettings() + { + ++this.ApplySettingsCalls; + if (this.ApplySettingsDelegate != null) + { + return this.ApplySettingsDelegate(); + } + else + { + return new ApplySettingsResultInstance(this.Unit); + } + } + + /// + /// Calls the GetSettingsDelegate if one is provided; returns success if not (with no settings values). + /// + /// The result. + public IGetSettingsResult GetSettings() + { + ++this.GetSettingsCalls; + if (this.GetSettingsDelegate != null) + { + return this.GetSettingsDelegate(); + } + else + { + return new GetSettingsResultInstance(this.Unit); + } + } + + /// + /// Calls the TestSettingsDelegate if one is provided; returns success if not (with a positive test result). + /// + /// The result. + public ITestSettingsResult TestSettings() + { + ++this.TestSettingsCalls; + if (this.TestSettingsDelegateWithUnit != null) + { + return this.TestSettingsDelegateWithUnit(this.Unit); + } + else if (this.TestSettingsDelegate != null) + { + return this.TestSettingsDelegate(); + } + else + { + return new TestSettingsResultInstance(this.Unit) { TestResult = ConfigurationTestResult.Positive }; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessorDetails.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessorDetails.cs index 5be02fdb8f..195624bf98 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessorDetails.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitProcessorDetails.cs @@ -1,70 +1,70 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - using Windows.Security.Cryptography.Certificates; - - /// - /// A test implementation of IConfigurationProcessorFactory. - /// - internal partial class TestConfigurationUnitProcessorDetails : IConfigurationUnitProcessorDetails - { - private ConfigurationUnit unit; - private ConfigurationUnitDetailFlags detailFlags; - - /// - /// Initializes a new instance of the class. - /// - /// The unit. - /// The flags of the details. - internal TestConfigurationUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) - { - this.unit = unit; - this.detailFlags = detailFlags; - } - -#pragma warning disable SA1600 // Elements should be documented - public string? Author { get; internal set; } - - public bool IsLocal { get; internal set; } - - public string? ModuleDescription { get; internal set; } - - public Uri? ModuleDocumentationUri { get; internal set; } - - public string? ModuleName { get; internal set; } - - public string? ModuleSource { get; internal set; } - - public string? ModuleType { get; internal set; } - - public DateTimeOffset PublishedDate { get; internal set; } - - public Uri? PublishedModuleUri { get; internal set; } - - public string? Publisher { get; internal set; } - - public IReadOnlyList? Settings { get; internal set; } - - public IReadOnlyList? SigningInformation { get; internal set; } - - public string? UnitDescription { get; internal set; } - - public Uri? UnitDocumentationUri { get; internal set; } - - public Uri? UnitIconUri { get; internal set; } - - public string? UnitType { get; internal set; } - - public string? Version { get; internal set; } - - public bool IsPublic { get; internal set; } -#pragma warning restore SA1600 // Elements should be documented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + using Windows.Security.Cryptography.Certificates; + + /// + /// A test implementation of IConfigurationProcessorFactory. + /// + internal partial class TestConfigurationUnitProcessorDetails : IConfigurationUnitProcessorDetails + { + private ConfigurationUnit unit; + private ConfigurationUnitDetailFlags detailFlags; + + /// + /// Initializes a new instance of the class. + /// + /// The unit. + /// The flags of the details. + internal TestConfigurationUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags) + { + this.unit = unit; + this.detailFlags = detailFlags; + } + +#pragma warning disable SA1600 // Elements should be documented + public string? Author { get; internal set; } + + public bool IsLocal { get; internal set; } + + public string? ModuleDescription { get; internal set; } + + public Uri? ModuleDocumentationUri { get; internal set; } + + public string? ModuleName { get; internal set; } + + public string? ModuleSource { get; internal set; } + + public string? ModuleType { get; internal set; } + + public DateTimeOffset PublishedDate { get; internal set; } + + public Uri? PublishedModuleUri { get; internal set; } + + public string? Publisher { get; internal set; } + + public IReadOnlyList? Settings { get; internal set; } + + public IReadOnlyList? SigningInformation { get; internal set; } + + public string? UnitDescription { get; internal set; } + + public Uri? UnitDocumentationUri { get; internal set; } + + public Uri? UnitIconUri { get; internal set; } + + public string? UnitType { get; internal set; } + + public string? Version { get; internal set; } + + public bool IsPublic { get; internal set; } +#pragma warning restore SA1600 // Elements should be documented + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitResultInformation.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitResultInformation.cs index 622f91792b..fc015216b2 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitResultInformation.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestConfigurationUnitResultInformation.cs @@ -1,37 +1,37 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using System.Collections.Generic; - - /// - /// A test implementation of IConfigurationSetProcessorFactory. - /// - internal partial class TestConfigurationUnitResultInformation : IConfigurationUnitResultInformation - { - /// - /// Gets or sets the description. - /// - public string Description { get; set; } = string.Empty; - - /// - /// Gets or sets the details. - /// - public string Details { get; set; } = string.Empty; - - /// - /// Gets or sets the result code. - /// - public Exception? ResultCode { get; set; } - - /// - /// Gets or sets the result source. - /// - public ConfigurationUnitResultSource ResultSource { get; set; } = ConfigurationUnitResultSource.None; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using System.Collections.Generic; + + /// + /// A test implementation of IConfigurationSetProcessorFactory. + /// + internal partial class TestConfigurationUnitResultInformation : IConfigurationUnitResultInformation + { + /// + /// Gets or sets the description. + /// + public string Description { get; set; } = string.Empty; + + /// + /// Gets or sets the details. + /// + public string Details { get; set; } = string.Empty; + + /// + /// Gets or sets the result code. + /// + public Exception? ResultCode { get; set; } + + /// + /// Gets or sets the result source. + /// + public ConfigurationUnitResultSource ResultSource { get; set; } = ConfigurationUnitResultSource.None; + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestDSCv3.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestDSCv3.cs index e24bdae3e6..29a91df981 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestDSCv3.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestDSCv3.cs @@ -1,145 +1,145 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.Helpers; - - /// - /// Implements IDSCv3 for tests. - /// - internal class TestDSCv3 : IDSCv3 - { - /// - /// The delegate type for GetResourceByType. - /// - /// The type name of the resource. - /// A single resource item. - internal delegate IResourceListItem? GetResourceByTypeDelegateType(string resourceType); - - /// - /// The delegate type for GetResourceSettings. - /// - /// The unit to get. - /// A get result. - internal delegate IResourceGetItem GetResourceSettingsDelegateType(ConfigurationUnitInternal unitInternal); - - /// - /// The delegate type for SetResourceSettings. - /// - /// The unit to set. - /// A set result. - internal delegate IResourceSetItem SetResourceSettingsDelegateType(ConfigurationUnitInternal unitInternal); - - /// - /// The delegate type for TestResource. - /// - /// The unit to test. - /// A test result. - internal delegate IResourceTestItem TestResourceDelegateType(ConfigurationUnitInternal unitInternal); - - /// - /// The delegate type for TestResource. - /// - /// The unit to test. - /// A test result. - internal delegate IList ExportResourceDelegateType(ConfigurationUnitInternal unitInternal); - - /// - /// Gets or sets the GetResourceByType result. - /// - public IResourceListItem? GetResourceByTypeResult { get; set; } - - /// - /// Gets or sets the GetResourceByType delegate. - /// - public GetResourceByTypeDelegateType? GetResourceByTypeDelegate { get; set; } - - /// - /// Gets or sets the GetResourceSettings result. - /// - public IResourceGetItem? GetResourceSettingsResult { get; set; } - - /// - /// Gets or sets the GetResourceSettings delegate. - /// - public GetResourceSettingsDelegateType? GetResourceSettingsDelegate { get; set; } - - /// - /// Gets or sets the SetResourceSettings result. - /// - public IResourceSetItem? SetResourceSettingsResult { get; set; } - - /// - /// Gets or sets the SetResourceSettings delegate. - /// - public SetResourceSettingsDelegateType? SetResourceSettingsDelegate { get; set; } - - /// - /// Gets or sets the TestResource result. - /// - public IResourceTestItem? TestResourceResult { get; set; } - - /// - /// Gets or sets the TestResource delegate. - /// - public TestResourceDelegateType? TestResourceDelegate { get; set; } - - /// - /// Gets or sets the ExportResource result. - /// - public IList? ExportResourceResult { get; set; } - - /// - /// Gets or sets the ExportResource delegate. - /// - public ExportResourceDelegateType? ExportResourceDelegate { get; set; } - - /// - /// Gets or sets the GetAllResources result. - /// - public List? GetAllResourcesResult { get; set; } - - /// - public IResourceListItem? GetResourceByType(string resourceType, ProcessorRunSettings? runSettings) - { - return this.GetResourceByTypeResult ?? this.GetResourceByTypeDelegate?.Invoke(resourceType); - } - - /// - public IResourceGetItem GetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - return this.GetResourceSettingsResult ?? this.GetResourceSettingsDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); - } - - /// - public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - return this.SetResourceSettingsResult ?? this.SetResourceSettingsDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); - } - - /// - public IResourceTestItem TestResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - return this.TestResourceResult ?? this.TestResourceDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); - } - - /// - public IList ExportResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) - { - return this.ExportResourceResult ?? this.ExportResourceDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); - } - - /// - public List GetAllResources(ProcessorRunSettings? runSettings) - { - return this.GetAllResourcesResult ?? throw new System.NotImplementedException(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Helpers; + + /// + /// Implements IDSCv3 for tests. + /// + internal class TestDSCv3 : IDSCv3 + { + /// + /// The delegate type for GetResourceByType. + /// + /// The type name of the resource. + /// A single resource item. + internal delegate IResourceListItem? GetResourceByTypeDelegateType(string resourceType); + + /// + /// The delegate type for GetResourceSettings. + /// + /// The unit to get. + /// A get result. + internal delegate IResourceGetItem GetResourceSettingsDelegateType(ConfigurationUnitInternal unitInternal); + + /// + /// The delegate type for SetResourceSettings. + /// + /// The unit to set. + /// A set result. + internal delegate IResourceSetItem SetResourceSettingsDelegateType(ConfigurationUnitInternal unitInternal); + + /// + /// The delegate type for TestResource. + /// + /// The unit to test. + /// A test result. + internal delegate IResourceTestItem TestResourceDelegateType(ConfigurationUnitInternal unitInternal); + + /// + /// The delegate type for TestResource. + /// + /// The unit to test. + /// A test result. + internal delegate IList ExportResourceDelegateType(ConfigurationUnitInternal unitInternal); + + /// + /// Gets or sets the GetResourceByType result. + /// + public IResourceListItem? GetResourceByTypeResult { get; set; } + + /// + /// Gets or sets the GetResourceByType delegate. + /// + public GetResourceByTypeDelegateType? GetResourceByTypeDelegate { get; set; } + + /// + /// Gets or sets the GetResourceSettings result. + /// + public IResourceGetItem? GetResourceSettingsResult { get; set; } + + /// + /// Gets or sets the GetResourceSettings delegate. + /// + public GetResourceSettingsDelegateType? GetResourceSettingsDelegate { get; set; } + + /// + /// Gets or sets the SetResourceSettings result. + /// + public IResourceSetItem? SetResourceSettingsResult { get; set; } + + /// + /// Gets or sets the SetResourceSettings delegate. + /// + public SetResourceSettingsDelegateType? SetResourceSettingsDelegate { get; set; } + + /// + /// Gets or sets the TestResource result. + /// + public IResourceTestItem? TestResourceResult { get; set; } + + /// + /// Gets or sets the TestResource delegate. + /// + public TestResourceDelegateType? TestResourceDelegate { get; set; } + + /// + /// Gets or sets the ExportResource result. + /// + public IList? ExportResourceResult { get; set; } + + /// + /// Gets or sets the ExportResource delegate. + /// + public ExportResourceDelegateType? ExportResourceDelegate { get; set; } + + /// + /// Gets or sets the GetAllResources result. + /// + public List? GetAllResourcesResult { get; set; } + + /// + public IResourceListItem? GetResourceByType(string resourceType, ProcessorRunSettings? runSettings) + { + return this.GetResourceByTypeResult ?? this.GetResourceByTypeDelegate?.Invoke(resourceType); + } + + /// + public IResourceGetItem GetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + return this.GetResourceSettingsResult ?? this.GetResourceSettingsDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); + } + + /// + public IResourceSetItem SetResourceSettings(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + return this.SetResourceSettingsResult ?? this.SetResourceSettingsDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); + } + + /// + public IResourceTestItem TestResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + return this.TestResourceResult ?? this.TestResourceDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); + } + + /// + public IList ExportResource(ConfigurationUnitInternal unitInternal, ProcessorRunSettings? runSettings) + { + return this.ExportResourceResult ?? this.ExportResourceDelegate?.Invoke(unitInternal) ?? throw new System.NotImplementedException(); + } + + /// + public List GetAllResources(ProcessorRunSettings? runSettings) + { + return this.GetAllResourcesResult ?? throw new System.NotImplementedException(); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGetAllSettingsConfigurationUnitProcessor.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGetAllSettingsConfigurationUnitProcessor.cs index 95d7e9cb9a..91ac790d4a 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGetAllSettingsConfigurationUnitProcessor.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGetAllSettingsConfigurationUnitProcessor.cs @@ -1,56 +1,56 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - /// - /// A test implementation of IConfigurationProcessorFactory. - /// - internal partial class TestGetAllSettingsConfigurationUnitProcessor : TestConfigurationUnitProcessor, IGetAllSettingsConfigurationUnitProcessor - { - /// - /// Initializes a new instance of the class. - /// - /// The unit. - internal TestGetAllSettingsConfigurationUnitProcessor(ConfigurationUnit unit) - : base(unit) - { - } - - /// - /// The delegate for GetAllSettings. - /// - /// The result. - internal delegate IGetAllSettingsResult GetAllSettingsDelegateType(); - - /// - /// Gets or sets the delegate object for GetAllSettings. - /// - internal GetAllSettingsDelegateType? GetAllSettingsDelegate { get; set; } - - /// - /// Gets the number of times GetAllSettings is called. - /// - internal int GetAllSettingsCalls { get; private set; } = 0; - - /// - /// Calls the GetAllSettingsDelegate if one is provided; returns success if not (with no settings values). - /// - /// The result. - public IGetAllSettingsResult GetAllSettings() - { - ++this.GetAllSettingsCalls; - if (this.GetAllSettingsDelegate != null) - { - return this.GetAllSettingsDelegate(); - } - else - { - return new GetAllSettingsResultInstance(this.Unit); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + /// + /// A test implementation of IConfigurationProcessorFactory. + /// + internal partial class TestGetAllSettingsConfigurationUnitProcessor : TestConfigurationUnitProcessor, IGetAllSettingsConfigurationUnitProcessor + { + /// + /// Initializes a new instance of the class. + /// + /// The unit. + internal TestGetAllSettingsConfigurationUnitProcessor(ConfigurationUnit unit) + : base(unit) + { + } + + /// + /// The delegate for GetAllSettings. + /// + /// The result. + internal delegate IGetAllSettingsResult GetAllSettingsDelegateType(); + + /// + /// Gets or sets the delegate object for GetAllSettings. + /// + internal GetAllSettingsDelegateType? GetAllSettingsDelegate { get; set; } + + /// + /// Gets the number of times GetAllSettings is called. + /// + internal int GetAllSettingsCalls { get; private set; } = 0; + + /// + /// Calls the GetAllSettingsDelegate if one is provided; returns success if not (with no settings values). + /// + /// The result. + public IGetAllSettingsResult GetAllSettings() + { + ++this.GetAllSettingsCalls; + if (this.GetAllSettingsDelegate != null) + { + return this.GetAllSettingsDelegate(); + } + else + { + return new GetAllSettingsResultInstance(this.Unit); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGroupSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGroupSettingsResultInstance.cs index 318cdff4bf..03c8292e60 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGroupSettingsResultInstance.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestGroupSettingsResultInstance.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - - /// - /// Implements ITestGroupSettingsResult. - /// - internal partial class TestGroupSettingsResultInstance : ITestGroupSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The group for this result. - internal TestGroupSettingsResultInstance(object? group) - { - this.Group = group; - } - - /// - public object? Group { get; private init; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); - - /// - public ConfigurationTestResult TestResult { get; internal set; } - - /// - public IList? UnitResults { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + + /// + /// Implements ITestGroupSettingsResult. + /// + internal partial class TestGroupSettingsResultInstance : ITestGroupSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The group for this result. + internal TestGroupSettingsResultInstance(object? group) + { + this.Group = group; + } + + /// + public object? Group { get; private init; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// + public ConfigurationTestResult TestResult { get; internal set; } + + /// + public IList? UnitResults { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceExportItem.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceExportItem.cs index ea08693cce..8187f389a1 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceExportItem.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceExportItem.cs @@ -1,43 +1,43 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Windows.Foundation.Collections; - - /// - /// Implements IResourceExportItem for tests. - /// - internal class TestResourceExportItem : IResourceExportItem - { - /// - /// Gets or sets the type. - /// - required public string Type { get; set; } - - /// - /// Gets or sets the name. - /// - required public string Name { get; set; } - - /// - /// Gets or sets the settings. - /// - public ValueSet Settings { get; set; } = new ValueSet(); - - /// - /// Gets or sets the metadata. - /// - public ValueSet Metadata { get; set; } = new ValueSet(); - - /// - /// Gets or sets the dependencies. - /// - public IList Dependencies { get; set; } = new List(); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Windows.Foundation.Collections; + + /// + /// Implements IResourceExportItem for tests. + /// + internal class TestResourceExportItem : IResourceExportItem + { + /// + /// Gets or sets the type. + /// + required public string Type { get; set; } + + /// + /// Gets or sets the name. + /// + required public string Name { get; set; } + + /// + /// Gets or sets the settings. + /// + public ValueSet Settings { get; set; } = new ValueSet(); + + /// + /// Gets or sets the metadata. + /// + public ValueSet Metadata { get; set; } = new ValueSet(); + + /// + /// Gets or sets the dependencies. + /// + public IList Dependencies { get; set; } = new List(); + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceGetItem.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceGetItem.cs index 9d376f5841..5990a9fb14 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceGetItem.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceGetItem.cs @@ -1,22 +1,22 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Windows.Foundation.Collections; - - /// - /// Implements IResourceGetItem for tests. - /// - internal class TestResourceGetItem : IResourceGetItem - { - /// - /// Gets or sets the settings. - /// - public ValueSet Settings { get; set; } = new ValueSet(); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Windows.Foundation.Collections; + + /// + /// Implements IResourceGetItem for tests. + /// + internal class TestResourceGetItem : IResourceGetItem + { + /// + /// Gets or sets the settings. + /// + public ValueSet Settings { get; set; } = new ValueSet(); + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceListItem.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceListItem.cs index eca0881467..bbb5253077 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceListItem.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceListItem.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - - /// - /// Implements IResourceListItem for tests. - /// - internal class TestResourceListItem : IResourceListItem - { - /// - /// Gets or sets the type. - /// - required public string Type { get; set; } - - /// - /// Gets or sets the kind. - /// - public ResourceKind Kind { get; set; } - - /// - /// Gets or sets the version. - /// - public string? Version { get; set; } - - /// - /// Gets or sets the description. - /// - public string? Description { get; set; } - - /// - /// Gets or sets the directory. - /// - public string? Directory { get; set; } - - /// - /// Gets or sets the author. - /// - public string? Author { get; set; } - - /// - /// Gets or sets the path. - /// - public string? Path { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// + /// Implements IResourceListItem for tests. + /// + internal class TestResourceListItem : IResourceListItem + { + /// + /// Gets or sets the type. + /// + required public string Type { get; set; } + + /// + /// Gets or sets the kind. + /// + public ResourceKind Kind { get; set; } + + /// + /// Gets or sets the version. + /// + public string? Version { get; set; } + + /// + /// Gets or sets the description. + /// + public string? Description { get; set; } + + /// + /// Gets or sets the directory. + /// + public string? Directory { get; set; } + + /// + /// Gets or sets the author. + /// + public string? Author { get; set; } + + /// + /// Gets or sets the path. + /// + public string? Path { get; set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceSetItem.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceSetItem.cs index fb35872846..b9bb172f31 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceSetItem.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceSetItem.cs @@ -1,21 +1,21 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - - /// - /// Implements IResourceSetItem for tests. - /// - internal class TestResourceSetItem : IResourceSetItem - { - /// - /// Gets or sets a value indicating whether a reboot is required. - /// - public bool RebootRequired { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// + /// Implements IResourceSetItem for tests. + /// + internal class TestResourceSetItem : IResourceSetItem + { + /// + /// Gets or sets a value indicating whether a reboot is required. + /// + public bool RebootRequired { get; set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceTestItem.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceTestItem.cs index c015743665..4529b71f41 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceTestItem.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestResourceTestItem.cs @@ -1,21 +1,21 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - - /// - /// Implements IResourceTestItem for tests. - /// - internal class TestResourceTestItem : IResourceTestItem - { - /// - /// Gets or sets a value indicating whether the system is in the desired state. - /// - public bool InDesiredState { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + + /// + /// Implements IResourceTestItem for tests. + /// + internal class TestResourceTestItem : IResourceTestItem + { + /// + /// Gets or sets a value indicating whether the system is in the desired state. + /// + public bool InDesiredState { get; set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestSettingsResultInstance.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestSettingsResultInstance.cs index e287b9f951..2844db90f7 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestSettingsResultInstance.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TestSettingsResultInstance.cs @@ -1,44 +1,44 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using Microsoft.Management.Configuration; - - /// - /// Implements ITestSettingsResult. - /// - internal sealed partial class TestSettingsResultInstance : ITestSettingsResult - { - /// - /// Initializes a new instance of the class. - /// - /// The configuration unit that the result is for. - public TestSettingsResultInstance(ConfigurationUnit unit) - { - this.Unit = unit; - } - - /// - /// Gets the configuration unit that the result is for. - /// - public ConfigurationUnit Unit { get; private set; } - - /// - public IConfigurationUnitResultInformation ResultInformation - { - get { return this.InternalResult; } - } - - /// - /// Gets the implementation object for ResultInformation. - /// - public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); - - /// - public ConfigurationTestResult TestResult { get; internal set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using Microsoft.Management.Configuration; + + /// + /// Implements ITestSettingsResult. + /// + internal sealed partial class TestSettingsResultInstance : ITestSettingsResult + { + /// + /// Initializes a new instance of the class. + /// + /// The configuration unit that the result is for. + public TestSettingsResultInstance(ConfigurationUnit unit) + { + this.Unit = unit; + } + + /// + /// Gets the configuration unit that the result is for. + /// + public ConfigurationUnit Unit { get; private set; } + + /// + public IConfigurationUnitResultInformation ResultInformation + { + get { return this.InternalResult; } + } + + /// + /// Gets the implementation object for ResultInformation. + /// + public TestConfigurationUnitResultInformation InternalResult { get; } = new TestConfigurationUnitResultInformation(); + + /// + public ConfigurationTestResult TestResult { get; internal set; } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TheorySkipIfCI.cs b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TheorySkipIfCI.cs index 670fc5f813..867800f171 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Helpers/TheorySkipIfCI.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Helpers/TheorySkipIfCI.cs @@ -1,28 +1,28 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Helpers -{ - using System; - using Xunit; - - /// - /// Skip theory test if running in CI builds. - /// - public sealed class TheorySkipIfCI : TheoryAttribute - { - /// - /// Initializes a new instance of the class. - /// - public TheorySkipIfCI() - { - if (Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") is not null) - { - this.Skip = "Skip test for CI builds"; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Helpers +{ + using System; + using Xunit; + + /// + /// Skip theory test if running in CI builds. + /// + public sealed class TheorySkipIfCI : TheoryAttribute + { + /// + /// Initializes a new instance of the class. + /// + public TheorySkipIfCI() + { + if (Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") is not null) + { + this.Skip = "Skip test for CI builds"; + } + } + } +} 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 38f1617bf4..fcd1754491 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj +++ b/src/Microsoft.Management.Configuration.UnitTests/Microsoft.Management.Configuration.UnitTests.csproj @@ -1,80 +1,80 @@ - - - - net8.0-windows10.0.26100.0 - enable - 10.0.17763.0 - x64;x86;arm64 - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - win-x64;win-x86;win-arm64 - - - - - - - - - - - - - - - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - True - - - True - PreserveNewest - - - True - - - - - - - - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - + + + + net8.0-windows10.0.26100.0 + enable + 10.0.17763.0 + x64;x86;arm64 + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + win-x64;win-x86;win-arm64 + + + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + True + + + True + PreserveNewest + + + True + + + + + + + + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psd1 b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psd1 index 014cc57c30..2b8ca9975a 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psd1 +++ b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psd1 @@ -1,37 +1,37 @@ -# -# Module manifest for module 'xAdminTestResource' -# -# Generated by: Luffytaro -# -# Generated on: 10/11/2023 -# - -@{ - -RootModule = 'xAdminTestResource.psm1' -ModuleVersion = '0.0.0.1' -GUID = 'a0be43e8-ac22-4244-8efc-7263dfa58b8c' -CompatiblePSEditions = 'Core' -Author = 'Luffytaro' -CompanyName = 'Microsoft Corporation' -Copyright = '(c) Microsoft Corporation. All rights reserved.' -Description = 'PowerShell module with DSC resources for unit tests that requires admin' -PowerShellVersion = '7.2' -FunctionsToExport = @() -CmdletsToExport = @() -DscResourcesToExport = @( - 'AdminResource' -) -HelpInfoURI = 'https://www.contoso.com/help' - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - ProjectUri = 'https://github.com/microsoft/winget-cli' - IconUri = 'https://www.contoso.com/icons/icon.png' - } - -} - -} +# +# Module manifest for module 'xAdminTestResource' +# +# Generated by: Luffytaro +# +# Generated on: 10/11/2023 +# + +@{ + +RootModule = 'xAdminTestResource.psm1' +ModuleVersion = '0.0.0.1' +GUID = 'a0be43e8-ac22-4244-8efc-7263dfa58b8c' +CompatiblePSEditions = 'Core' +Author = 'Luffytaro' +CompanyName = 'Microsoft Corporation' +Copyright = '(c) Microsoft Corporation. All rights reserved.' +Description = 'PowerShell module with DSC resources for unit tests that requires admin' +PowerShellVersion = '7.2' +FunctionsToExport = @() +CmdletsToExport = @() +DscResourcesToExport = @( + 'AdminResource' +) +HelpInfoURI = 'https://www.contoso.com/help' + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + ProjectUri = 'https://github.com/microsoft/winget-cli' + IconUri = 'https://www.contoso.com/icons/icon.png' + } + +} + +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psm1 b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psm1 index 7e8b9b2aca..2dd778833d 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psm1 +++ b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xAdminTestResource/xAdminTestResource.psm1 @@ -1,29 +1,29 @@ -# Simple module that requires admin. -#Requires -RunAsAdministrator -enum Ensure -{ - Absent - Present -} - -[DscResource()] -class AdminResource -{ - [DscProperty(Key)] - [string] $key - - [AdminResource] Get() - { - return $this - } - - [bool] Test() - { - return $false - } - - [void] Set() - { - } -} - +# Simple module that requires admin. +#Requires -RunAsAdministrator +enum Ensure +{ + Absent + Present +} + +[DscResource()] +class AdminResource +{ + [DscProperty(Key)] + [string] $key + + [AdminResource] Get() + { + return $this + } + + [bool] Test() + { + return $false + } + + [void] Set() + { + } +} + diff --git a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 index e4d2652725..4201fad827 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 +++ b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psd1 @@ -1,41 +1,41 @@ -# -# Module manifest for module 'xSimpleTestResource' -# -# Generated by: Luffytaro -# -# Generated on: 1/24/2023 -# - -@{ - -RootModule = 'xSimpleTestResource.psm1' -ModuleVersion = '0.0.0.1' -GUID = 'a0be43e8-ac22-4244-8efc-7263dfa50b8c' -CompatiblePSEditions = 'Core' -Author = 'Luffytaro' -CompanyName = 'Microsoft Corporation' -Copyright = '(c) Microsoft Corporation. All rights reserved.' -Description = 'PowerShell module with DSC resources for unit tests' -PowerShellVersion = '7.2' -FunctionsToExport = @() -CmdletsToExport = @() -DscResourcesToExport = @( - 'SimpleFileResource' - 'SimpleTestResource' - 'SimpleTestResourceThrows' - 'SimpleTestResourceError' - 'SimpleTestResourceTypes' -) -HelpInfoURI = 'https://www.contoso.com/help' - -# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. -PrivateData = @{ - - PSData = @{ - ProjectUri = 'https://github.com/microsoft/winget-cli' - IconUri = 'https://www.contoso.com/icons/icon.png' - } - -} - -} +# +# Module manifest for module 'xSimpleTestResource' +# +# Generated by: Luffytaro +# +# Generated on: 1/24/2023 +# + +@{ + +RootModule = 'xSimpleTestResource.psm1' +ModuleVersion = '0.0.0.1' +GUID = 'a0be43e8-ac22-4244-8efc-7263dfa50b8c' +CompatiblePSEditions = 'Core' +Author = 'Luffytaro' +CompanyName = 'Microsoft Corporation' +Copyright = '(c) Microsoft Corporation. All rights reserved.' +Description = 'PowerShell module with DSC resources for unit tests' +PowerShellVersion = '7.2' +FunctionsToExport = @() +CmdletsToExport = @() +DscResourcesToExport = @( + 'SimpleFileResource' + 'SimpleTestResource' + 'SimpleTestResourceThrows' + 'SimpleTestResourceError' + 'SimpleTestResourceTypes' +) +HelpInfoURI = 'https://www.contoso.com/help' + +# Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. +PrivateData = @{ + + PSData = @{ + ProjectUri = 'https://github.com/microsoft/winget-cli' + IconUri = 'https://www.contoso.com/icons/icon.png' + } + +} + +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 index 893609a4a3..2f07cf70c9 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 +++ b/src/Microsoft.Management.Configuration.UnitTests/TestCollateral/PowerShellModules/xSimpleTestResource/xSimpleTestResource.psm1 @@ -1,262 +1,262 @@ -# Simple module with resources. - -enum Ensure -{ - Absent - Present -} - -# This resource just checks if a file is there or not with and if its with the specified content. -[DscResource()] -class SimpleFileResource -{ - [DscProperty(Key)] - [string] $Path - - [DscProperty()] - [Ensure] $Ensure = [Ensure]::Present - - [DscProperty()] - [string] $Content = $null - - [SimpleFileResource] Get() - { - if ([string]::IsNullOrEmpty($this.Path)) - { - throw - } - - $fileContent = $null - if (Test-Path -Path $this.Path -PathType Leaf) - { - $fileContent = Get-Content $this.Path -Raw - } - - $result = @{ - Path = $this.Path - Content = $fileContent - } - - return $result - } - - [bool] Test() - { - $get = $this.Get() - - if (Test-Path -Path $this.Path -PathType Leaf) - { - if ($this.Ensure -eq [Ensure]::Present) - { - return $this.Content -eq $get.Content - } - } - elseif ($this.Ensure -eq [Ensure]::Absent) - { - return $true - } - - return $false - } - - [void] Set() - { - if (-not $this.Test()) - { - if (Test-Path -Path $this.Path -PathType Leaf) - { - if ($this.Ensure -eq [Ensure]::Present) - { - Set-Content $this.Path $this.Content -NoNewline - } - else - { - Remove-Item $this.Path - } - } - else - { - if ($this.Ensure -eq [Ensure]::Present) - { - Set-Content $this.Path $this.Content -NoNewline - } - } - } - } -} - -[DscResource()] -class SimpleTestResource -{ - [DscProperty(Key)] - [string] $key - - [DscProperty(Mandatory)] - [string] $secretCode - - [SimpleTestResource] Get() - { - $result = @{ - key = "SimpleTestResourceKey" - } - return $result - } - - [bool] Test() - { - return $this.secretCode -eq "4815162342" - } - - [void] Set() - { - if (-not $this.Test()) - { - $global:DSCMachineStatus = 1 - } - } -} - -[DscResource()] -class SimpleTestResourceThrows -{ - [DscProperty(Key)] - [string] $key - - [SimpleTestResourceThrows] Get() - { - $result = @{ - key = "SimpleTestResourceThrowsKey" - } - throw "throws in Get" - return $result - } - - [bool] Test() - { - throw "throws in Test" - return $false - } - - [void] Set() - { - throw "throws in Set" - } -} - -[DscResource()] -class SimpleTestResourceError -{ - [DscProperty(Key)] - [string] $key - - [SimpleTestResourceError] Get() - { - $result = @{ - key = "SimpleTestResourceErrorKey" - } - Write-Error "Error in Get" - return $result - } - - [bool] Test() - { - Write-Error "Error in Test" - return $true - } - - [void] Set() - { - Write-Error "Error in Set" - } -} - -[DscResource()] -class SimpleTestResourceTypes -{ - [DscProperty(Key)] - [string] $key - - [DscProperty()] - [boolean] $boolProperty - - [DscProperty()] - [int] $intProperty; - - [DscProperty()] - [double] $doubleProperty; - - [DscProperty()] - [char] $charProperty; - - [DscProperty()] - [Hashtable] $hashtableProperty; - - [SimpleTestResourceTypes] Get() - { - $result = @{ - key = "SimpleTestResourceTypesKey" - boolProperty = $false - intProperty = 0 - doubleProperty = 0.0 - charProperty = 'z' - hashtableProperty = @{} - } - return $result - } - - [bool] Test() - { - # Because we can't get the error stream from a class based resource, I throw so is easier to know if - # there's something wrong. - if ($this.boolProperty -ne $true) - { - throw "Failed boolProperty" - } - - if ($this.intProperty -ne 3) - { - throw "Failed intProperty. Got $($this.intProperty)" - } - - if ($this.doubleProperty -ne -9.876) - { - throw "Failed doubleProperty Got $($this.doubleProperty)" - } - - if ($this.charProperty -ne 'f') - { - throw "Failed charProperty Got $($this.charProperty)" - } - - if ($this.hashtableProperty.ContainsKey("secretStringKey")) - { - if ($this.hashtableProperty["secretStringKey"] -ne "secretCode") - { - throw "Failed comparing value of `$hashtableProperty.secretStringKey Got $($this.hashtableProperty["secretStringKey"])" - } - } - else - { - throw "Failed finding secretStringKey in hashtableProperty" - } - - if ($this.hashtableProperty.ContainsKey("secretIntKey")) - { - if ($this.hashtableProperty["secretIntKey"] -ne 123456) - { - throw "Failed comparing value of `$hashtableProperty.secretIntKey Got $($this.hashtableProperty["secretIntKey"])" - } - } - else - { - throw "Failed finding secretIntKey in hashtableProperty" - } - - return $true - } - - [void] Set() - { - # no-op - } -} +# Simple module with resources. + +enum Ensure +{ + Absent + Present +} + +# This resource just checks if a file is there or not with and if its with the specified content. +[DscResource()] +class SimpleFileResource +{ + [DscProperty(Key)] + [string] $Path + + [DscProperty()] + [Ensure] $Ensure = [Ensure]::Present + + [DscProperty()] + [string] $Content = $null + + [SimpleFileResource] Get() + { + if ([string]::IsNullOrEmpty($this.Path)) + { + throw + } + + $fileContent = $null + if (Test-Path -Path $this.Path -PathType Leaf) + { + $fileContent = Get-Content $this.Path -Raw + } + + $result = @{ + Path = $this.Path + Content = $fileContent + } + + return $result + } + + [bool] Test() + { + $get = $this.Get() + + if (Test-Path -Path $this.Path -PathType Leaf) + { + if ($this.Ensure -eq [Ensure]::Present) + { + return $this.Content -eq $get.Content + } + } + elseif ($this.Ensure -eq [Ensure]::Absent) + { + return $true + } + + return $false + } + + [void] Set() + { + if (-not $this.Test()) + { + if (Test-Path -Path $this.Path -PathType Leaf) + { + if ($this.Ensure -eq [Ensure]::Present) + { + Set-Content $this.Path $this.Content -NoNewline + } + else + { + Remove-Item $this.Path + } + } + else + { + if ($this.Ensure -eq [Ensure]::Present) + { + Set-Content $this.Path $this.Content -NoNewline + } + } + } + } +} + +[DscResource()] +class SimpleTestResource +{ + [DscProperty(Key)] + [string] $key + + [DscProperty(Mandatory)] + [string] $secretCode + + [SimpleTestResource] Get() + { + $result = @{ + key = "SimpleTestResourceKey" + } + return $result + } + + [bool] Test() + { + return $this.secretCode -eq "4815162342" + } + + [void] Set() + { + if (-not $this.Test()) + { + $global:DSCMachineStatus = 1 + } + } +} + +[DscResource()] +class SimpleTestResourceThrows +{ + [DscProperty(Key)] + [string] $key + + [SimpleTestResourceThrows] Get() + { + $result = @{ + key = "SimpleTestResourceThrowsKey" + } + throw "throws in Get" + return $result + } + + [bool] Test() + { + throw "throws in Test" + return $false + } + + [void] Set() + { + throw "throws in Set" + } +} + +[DscResource()] +class SimpleTestResourceError +{ + [DscProperty(Key)] + [string] $key + + [SimpleTestResourceError] Get() + { + $result = @{ + key = "SimpleTestResourceErrorKey" + } + Write-Error "Error in Get" + return $result + } + + [bool] Test() + { + Write-Error "Error in Test" + return $true + } + + [void] Set() + { + Write-Error "Error in Set" + } +} + +[DscResource()] +class SimpleTestResourceTypes +{ + [DscProperty(Key)] + [string] $key + + [DscProperty()] + [boolean] $boolProperty + + [DscProperty()] + [int] $intProperty; + + [DscProperty()] + [double] $doubleProperty; + + [DscProperty()] + [char] $charProperty; + + [DscProperty()] + [Hashtable] $hashtableProperty; + + [SimpleTestResourceTypes] Get() + { + $result = @{ + key = "SimpleTestResourceTypesKey" + boolProperty = $false + intProperty = 0 + doubleProperty = 0.0 + charProperty = 'z' + hashtableProperty = @{} + } + return $result + } + + [bool] Test() + { + # Because we can't get the error stream from a class based resource, I throw so is easier to know if + # there's something wrong. + if ($this.boolProperty -ne $true) + { + throw "Failed boolProperty" + } + + if ($this.intProperty -ne 3) + { + throw "Failed intProperty. Got $($this.intProperty)" + } + + if ($this.doubleProperty -ne -9.876) + { + throw "Failed doubleProperty Got $($this.doubleProperty)" + } + + if ($this.charProperty -ne 'f') + { + throw "Failed charProperty Got $($this.charProperty)" + } + + if ($this.hashtableProperty.ContainsKey("secretStringKey")) + { + if ($this.hashtableProperty["secretStringKey"] -ne "secretCode") + { + throw "Failed comparing value of `$hashtableProperty.secretStringKey Got $($this.hashtableProperty["secretStringKey"])" + } + } + else + { + throw "Failed finding secretStringKey in hashtableProperty" + } + + if ($this.hashtableProperty.ContainsKey("secretIntKey")) + { + if ($this.hashtableProperty["secretIntKey"] -ne 123456) + { + throw "Failed comparing value of `$hashtableProperty.secretIntKey Got $($this.hashtableProperty["secretIntKey"])" + } + } + else + { + throw "Failed finding secretIntKey in hashtableProperty" + } + + return $true + } + + [void] Set() + { + # no-op + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationDetailsTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationDetailsTests.cs index 05b77c5746..dd8f42f15d 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationDetailsTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationDetailsTests.cs @@ -1,222 +1,222 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.Processor.Unit; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Windows.Security.Cryptography.Certificates; - using Xunit; - using Xunit.Abstractions; - - /// - /// Tests for ConfigurationUnitProcessorDetails and ConfigurationUnitSettingDetails. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationDetailsTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Fixture. - /// log. - public ConfigurationDetailsTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Tests creating ConfigurationUnitProcessorDetails and ConfigurationUnitSettingDetails with different inputs. - /// - /// Has dsc info. - /// Has ps module info. - /// Has get module info. - /// Has certs. - [Theory] - [InlineData(false, false, false, false)] - [InlineData(false, false, false, true)] - [InlineData(false, false, true, false)] - [InlineData(false, false, true, true)] - [InlineData(false, true, false, false)] - [InlineData(false, true, false, true)] - [InlineData(false, true, true, false)] - [InlineData(false, true, true, true)] - [InlineData(true, false, false, false)] - [InlineData(true, false, false, true)] - [InlineData(true, false, true, false)] - [InlineData(true, false, true, true)] - [InlineData(true, true, false, false)] - [InlineData(true, true, false, true)] - [InlineData(true, true, true, false)] - [InlineData(true, true, true, true)] - public void ConfigurationUnitProcessorDetails_CreationTest(bool hasDscInfo, bool hasPSModuleInfo, bool hasGetModuleInfo, bool hasCerts) - { - List? certsInput = null; - if (hasCerts) - { - certsInput = new List(); - } - - if (!hasDscInfo && !hasPSModuleInfo && !hasGetModuleInfo) - { - Assert.Throws( - () => Factory.CreateUnitProcessorDetails("unitName", null, null, null, certsInput)); - } - else - { - var unit = this.CreateConfigurationUnit(); - var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); - - DscResourceInfoInternal? dscResourceInfoInput = null; - if (hasDscInfo) - { - dscResourceInfoInput = dscResourceInfo; - } - - PSModuleInfo? psModuleInfoInput = null; - if (hasPSModuleInfo) - { - psModuleInfoInput = psModuleInfo; - } - - PSObject? getModuleInfo = null; - if (hasGetModuleInfo) - { - getModuleInfo = this.CreateGetModuleInfo(); - } - - var details = Factory.CreateUnitProcessorDetails(unit.Type, dscResourceInfoInput, psModuleInfoInput, getModuleInfo, certsInput); - - Assert.Equal(unit.Type, details.UnitType); - - if (hasDscInfo) - { - Assert.True(details.IsLocal); - Assert.Equal("xSimpleTestResource", details.ModuleName); - Assert.Equal("0.0.0.1", details.Version); - Assert.NotNull(details.Settings); - Assert.True(details.Settings.Count == 5); - - var pathSetting = details.Settings.Where(s => s.Identifier == "Path").FirstOrDefault(); - Assert.NotNull(pathSetting); - Assert.Equal(Windows.Foundation.PropertyType.String, pathSetting.Type); - Assert.True(pathSetting.IsRequired); - Assert.Equal(string.Empty, pathSetting.Schema); - - var contentSetting = details.Settings.Where(s => s.Identifier == "Content").FirstOrDefault(); - Assert.NotNull(contentSetting); - Assert.Equal(Windows.Foundation.PropertyType.String, contentSetting.Type); - Assert.False(contentSetting.IsRequired); - Assert.Equal(string.Empty, contentSetting.Schema); - - var dependsOnSetting = details.Settings.Where(s => s.Identifier == "DependsOn").FirstOrDefault(); - Assert.NotNull(dependsOnSetting); - Assert.Equal(Windows.Foundation.PropertyType.StringArray, dependsOnSetting.Type); - Assert.False(dependsOnSetting.IsRequired); - Assert.Equal(string.Empty, dependsOnSetting.Schema); - - var ensureSetting = details.Settings.Where(s => s.Identifier == "Ensure").FirstOrDefault(); - Assert.NotNull(ensureSetting); - Assert.Equal(Windows.Foundation.PropertyType.String, ensureSetting.Type); - Assert.False(ensureSetting.IsRequired); - Assert.Equal(string.Empty, ensureSetting.Schema); - - var psDscRunAsCredentialSetting = details.Settings.Where(s => s.Identifier == "PsDscRunAsCredential").FirstOrDefault(); - Assert.NotNull(psDscRunAsCredentialSetting); - Assert.Equal(Windows.Foundation.PropertyType.Inspectable, psDscRunAsCredentialSetting.Type); - Assert.False(psDscRunAsCredentialSetting.IsRequired); - Assert.Equal(string.Empty, psDscRunAsCredentialSetting.Schema); - } - - if (hasPSModuleInfo) - { - Assert.Equal(new Uri("https://www.contoso.com/help"), details.ModuleDocumentationUri); - Assert.Equal(new Uri("https://www.contoso.com/icons/icon.png"), details.UnitIconUri); - Assert.Equal("xSimpleTestResource", details.ModuleName); - Assert.Equal(ModuleType.Script.ToString(), details.ModuleType); - Assert.Equal("PowerShell module with DSC resources for unit tests", details.ModuleDescription); - Assert.Equal(new Uri("https://github.com/microsoft/winget-cli"), details.PublishedModuleUri); - Assert.Equal("0.0.0.1", details.Version); - Assert.Equal("Luffytaro", details.Author); - Assert.Equal("Microsoft Corporation", details.Publisher); - } - - if (hasGetModuleInfo) - { - Assert.Equal("PSGallery", details.ModuleSource); - Assert.Equal(new DateTimeOffset(new DateTime(2017, 12, 10)), details.PublishedDate); - Assert.Equal(new Uri("https://www.contoso.com/icons/icon.png"), details.UnitIconUri); - Assert.Equal("xSimpleTestResource", details.ModuleName); - Assert.Equal("PowerShell module with DSC resources for unit tests", details.ModuleDescription); - Assert.Equal("0.0.0.1", details.Version); - Assert.Equal("Luffytaro", details.Author); - Assert.Equal("Microsoft Corporation", details.Publisher); - Assert.True(details.IsPublic); - } - - if (hasCerts) - { - Assert.NotNull(details.SigningInformation); - } - } - } - - private ConfigurationUnit CreateConfigurationUnit() - { - var unit = new ConfigurationUnit(); - unit.Type = "SimpleFileResource"; - unit.Metadata.Add("module", "xSimpleTestResource"); - unit.Metadata.Add("version", "0.0.0.1"); - - return unit; - } - - private (DscResourceInfoInternal dscResourceInfo, PSModuleInfo psModuleInfo) GetResourceAndModuleInfo(ConfigurationUnit unit) - { - // This is easier than trying to mock sealed class from external code... - var testEnv = this.fixture.PrepareTestProcessorEnvironment(true); - - var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitAndModule(unit, string.Empty)); - var psModuleInfo = testEnv.GetAvailableModule(PowerShellHelpers.CreateModuleSpecification("xSimpleTestResource", "0.0.0.1")); - - if (dscResourceInfo is null || psModuleInfo is null) - { - throw new ArgumentNullException("Test processor environment not set correctly"); - } - - return (dscResourceInfo, psModuleInfo); - } - - private PSObject CreateGetModuleInfo() - { - return new PSObject(new - { - Repository = "PSGallery", - PublishedDate = new DateTime(2017, 12, 10), - IconUri = "https://www.contoso.com/icons/icon.png", - Name = "xSimpleTestResource", - Description = "PowerShell module with DSC resources for unit tests", - RepositorySourceLocation = "https://www.powershellgallery.com/api/v2", - Version = "0.0.0.1", - Author = "Luffytaro", - CompanyName = "Microsoft Corporation", - }); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.Unit; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Windows.Security.Cryptography.Certificates; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for ConfigurationUnitProcessorDetails and ConfigurationUnitSettingDetails. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationDetailsTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Fixture. + /// log. + public ConfigurationDetailsTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Tests creating ConfigurationUnitProcessorDetails and ConfigurationUnitSettingDetails with different inputs. + /// + /// Has dsc info. + /// Has ps module info. + /// Has get module info. + /// Has certs. + [Theory] + [InlineData(false, false, false, false)] + [InlineData(false, false, false, true)] + [InlineData(false, false, true, false)] + [InlineData(false, false, true, true)] + [InlineData(false, true, false, false)] + [InlineData(false, true, false, true)] + [InlineData(false, true, true, false)] + [InlineData(false, true, true, true)] + [InlineData(true, false, false, false)] + [InlineData(true, false, false, true)] + [InlineData(true, false, true, false)] + [InlineData(true, false, true, true)] + [InlineData(true, true, false, false)] + [InlineData(true, true, false, true)] + [InlineData(true, true, true, false)] + [InlineData(true, true, true, true)] + public void ConfigurationUnitProcessorDetails_CreationTest(bool hasDscInfo, bool hasPSModuleInfo, bool hasGetModuleInfo, bool hasCerts) + { + List? certsInput = null; + if (hasCerts) + { + certsInput = new List(); + } + + if (!hasDscInfo && !hasPSModuleInfo && !hasGetModuleInfo) + { + Assert.Throws( + () => Factory.CreateUnitProcessorDetails("unitName", null, null, null, certsInput)); + } + else + { + var unit = this.CreateConfigurationUnit(); + var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); + + DscResourceInfoInternal? dscResourceInfoInput = null; + if (hasDscInfo) + { + dscResourceInfoInput = dscResourceInfo; + } + + PSModuleInfo? psModuleInfoInput = null; + if (hasPSModuleInfo) + { + psModuleInfoInput = psModuleInfo; + } + + PSObject? getModuleInfo = null; + if (hasGetModuleInfo) + { + getModuleInfo = this.CreateGetModuleInfo(); + } + + var details = Factory.CreateUnitProcessorDetails(unit.Type, dscResourceInfoInput, psModuleInfoInput, getModuleInfo, certsInput); + + Assert.Equal(unit.Type, details.UnitType); + + if (hasDscInfo) + { + Assert.True(details.IsLocal); + Assert.Equal("xSimpleTestResource", details.ModuleName); + Assert.Equal("0.0.0.1", details.Version); + Assert.NotNull(details.Settings); + Assert.True(details.Settings.Count == 5); + + var pathSetting = details.Settings.Where(s => s.Identifier == "Path").FirstOrDefault(); + Assert.NotNull(pathSetting); + Assert.Equal(Windows.Foundation.PropertyType.String, pathSetting.Type); + Assert.True(pathSetting.IsRequired); + Assert.Equal(string.Empty, pathSetting.Schema); + + var contentSetting = details.Settings.Where(s => s.Identifier == "Content").FirstOrDefault(); + Assert.NotNull(contentSetting); + Assert.Equal(Windows.Foundation.PropertyType.String, contentSetting.Type); + Assert.False(contentSetting.IsRequired); + Assert.Equal(string.Empty, contentSetting.Schema); + + var dependsOnSetting = details.Settings.Where(s => s.Identifier == "DependsOn").FirstOrDefault(); + Assert.NotNull(dependsOnSetting); + Assert.Equal(Windows.Foundation.PropertyType.StringArray, dependsOnSetting.Type); + Assert.False(dependsOnSetting.IsRequired); + Assert.Equal(string.Empty, dependsOnSetting.Schema); + + var ensureSetting = details.Settings.Where(s => s.Identifier == "Ensure").FirstOrDefault(); + Assert.NotNull(ensureSetting); + Assert.Equal(Windows.Foundation.PropertyType.String, ensureSetting.Type); + Assert.False(ensureSetting.IsRequired); + Assert.Equal(string.Empty, ensureSetting.Schema); + + var psDscRunAsCredentialSetting = details.Settings.Where(s => s.Identifier == "PsDscRunAsCredential").FirstOrDefault(); + Assert.NotNull(psDscRunAsCredentialSetting); + Assert.Equal(Windows.Foundation.PropertyType.Inspectable, psDscRunAsCredentialSetting.Type); + Assert.False(psDscRunAsCredentialSetting.IsRequired); + Assert.Equal(string.Empty, psDscRunAsCredentialSetting.Schema); + } + + if (hasPSModuleInfo) + { + Assert.Equal(new Uri("https://www.contoso.com/help"), details.ModuleDocumentationUri); + Assert.Equal(new Uri("https://www.contoso.com/icons/icon.png"), details.UnitIconUri); + Assert.Equal("xSimpleTestResource", details.ModuleName); + Assert.Equal(ModuleType.Script.ToString(), details.ModuleType); + Assert.Equal("PowerShell module with DSC resources for unit tests", details.ModuleDescription); + Assert.Equal(new Uri("https://github.com/microsoft/winget-cli"), details.PublishedModuleUri); + Assert.Equal("0.0.0.1", details.Version); + Assert.Equal("Luffytaro", details.Author); + Assert.Equal("Microsoft Corporation", details.Publisher); + } + + if (hasGetModuleInfo) + { + Assert.Equal("PSGallery", details.ModuleSource); + Assert.Equal(new DateTimeOffset(new DateTime(2017, 12, 10)), details.PublishedDate); + Assert.Equal(new Uri("https://www.contoso.com/icons/icon.png"), details.UnitIconUri); + Assert.Equal("xSimpleTestResource", details.ModuleName); + Assert.Equal("PowerShell module with DSC resources for unit tests", details.ModuleDescription); + Assert.Equal("0.0.0.1", details.Version); + Assert.Equal("Luffytaro", details.Author); + Assert.Equal("Microsoft Corporation", details.Publisher); + Assert.True(details.IsPublic); + } + + if (hasCerts) + { + Assert.NotNull(details.SigningInformation); + } + } + } + + private ConfigurationUnit CreateConfigurationUnit() + { + var unit = new ConfigurationUnit(); + unit.Type = "SimpleFileResource"; + unit.Metadata.Add("module", "xSimpleTestResource"); + unit.Metadata.Add("version", "0.0.0.1"); + + return unit; + } + + private (DscResourceInfoInternal dscResourceInfo, PSModuleInfo psModuleInfo) GetResourceAndModuleInfo(ConfigurationUnit unit) + { + // This is easier than trying to mock sealed class from external code... + var testEnv = this.fixture.PrepareTestProcessorEnvironment(true); + + var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitAndModule(unit, string.Empty)); + var psModuleInfo = testEnv.GetAvailableModule(PowerShellHelpers.CreateModuleSpecification("xSimpleTestResource", "0.0.0.1")); + + if (dscResourceInfo is null || psModuleInfo is null) + { + throw new ArgumentNullException("Test processor environment not set correctly"); + } + + return (dscResourceInfo, psModuleInfo); + } + + private PSObject CreateGetModuleInfo() + { + return new PSObject(new + { + Repository = "PSGallery", + PublishedDate = new DateTime(2017, 12, 10), + IconUri = "https://www.contoso.com/icons/icon.png", + Name = "xSimpleTestResource", + Description = "PowerShell module with DSC resources for unit tests", + RepositorySourceLocation = "https://www.powershellgallery.com/api/v2", + Version = "0.0.0.1", + Author = "Luffytaro", + CompanyName = "Microsoft Corporation", + }); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationHistoryTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationHistoryTests.cs index 5d1b706d52..f16d335be7 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationHistoryTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationHistoryTests.cs @@ -1,473 +1,473 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Diagnostics.CodeAnalysis; - using System.Xml.Linq; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for configuration history. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationHistoryTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationHistoryTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// Checks that the history matches the applied set. - /// - [Fact] - [OutOfProc] - public void ApplySet_HistoryMatches_0_1() - { - this.RunApplyHistoryMatchTest( - @" -properties: - configurationVersion: 0.1 - assertions: - - resource: Assert - id: AssertIdentifier1 - directives: - module: Module - settings: - Setting1: '1' - Setting2: 2 - - resource: Assert - id: AssertIdentifier2 - dependsOn: - - AssertIdentifier1 - directives: - module: Module - settings: - Setting1: - Setting2: 2 - parameters: - - resource: Inform - id: InformIdentifier1 - directives: - module: Module2 - settings: - Setting1: - Setting2: - Setting3: 3 - resources: - - resource: Apply -", new string[] { "AssertIdentifier2" }); - } - - /// - /// Checks that the history matches the applied set. - /// - [Fact] - [OutOfProc] - public void ApplySet_HistoryMatches_0_2() - { - this.RunApplyHistoryMatchTest( - @" -properties: - configurationVersion: 0.2 - assertions: - - resource: Module/Assert - id: AssertIdentifier1 - settings: - Setting1: '1' - Setting2: 2 - - resource: Module/Assert - id: AssertIdentifier2 - dependsOn: - - AssertIdentifier1 - directives: - description: Describe! - settings: - Setting1: - Setting2: 2 - parameters: - - resource: Module2/Inform - id: InformIdentifier1 - settings: - Setting1: - Setting2: - Setting3: 3 - resources: - - resource: Apply -", new string[] { "AssertIdentifier2" }); - } - - /// - /// Checks that the history matches the applied set. - /// - [Fact] - public void ApplySet_HistoryMatches_0_3() - { - this.RunApplyHistoryMatchTest( - @" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - a: 1 - b: '2' -variables: - v1: var1 - v2: 42 -resources: - - name: Name - type: Module/Resource - metadata: - e: '5' - f: 6 - properties: - c: 3 - d: '4' - - name: Name2 - type: Module/Resource2 - dependsOn: - - Name - properties: - l: '10' - metadata: - i: '7' - j: 8 - q: 42 - - name: Group - type: Module2/Resource - metadata: - isGroup: true - properties: - resources: - - name: Child1 - type: Module3/Resource - metadata: - e: '5' - f: 6 - properties: - c: 3 - d: '4' - - name: Child2 - type: Module4/Resource2 - properties: - l: '10' - metadata: - i: '7' - j: 8 - q: 42 -"); - } - - /// - /// Applies a set, reads the history, changes the read set and reapplies it. - /// - [Fact] - [OutOfProc] - public void ApplySet_ChangeHistory() - { - string disabledIdentifier = "AssertIdentifier2"; - - ConfigurationSet returnedSet = this.RunApplyHistoryMatchTest( - @" -properties: - configurationVersion: 0.2 - assertions: - - resource: Module/Assert - id: AssertIdentifier1 - settings: - Setting1: '1' - Setting2: 2 - - resource: Module/Assert - id: AssertIdentifier2 - dependsOn: - - AssertIdentifier1 - directives: - description: Describe! - settings: - Setting1: - Setting2: 2 - parameters: - - resource: Module2/Inform - id: InformIdentifier1 - settings: - Setting1: - Setting2: - Setting3: 3 - resources: - - resource: Apply -", new string[] { disabledIdentifier }); - - foreach (ConfigurationUnit unit in returnedSet.Units) - { - if (unit.Identifier == disabledIdentifier) - { - unit.IsActive = true; - } - } - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(returnedSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.Null(result.ResultCode); - - ConfigurationSet? historySet = null; - - foreach (ConfigurationSet set in processor.GetConfigurationHistory()) - { - if (set.InstanceIdentifier == returnedSet.InstanceIdentifier) - { - historySet = set; - } - } - - this.AssertSetsEqual(returnedSet, historySet); - } - - /// - /// Applies a set, reads the history and removes it. - /// - [Fact] - [OutOfProc] - public void ApplySet_RemoveHistory() - { - ConfigurationSet returnedSet = this.RunApplyHistoryMatchTest( - @" -properties: - configurationVersion: 0.2 - assertions: - - resource: Module/Assert - id: AssertIdentifier1 - settings: - Setting1: '1' - Setting2: 2 - - resource: Module/Assert - id: AssertIdentifier2 - dependsOn: - - AssertIdentifier1 - directives: - description: Describe! - settings: - Setting1: - Setting2: 2 - parameters: - - resource: Module2/Inform - id: InformIdentifier1 - settings: - Setting1: - Setting2: - Setting3: 3 - resources: - - resource: Apply -"); - - returnedSet.Remove(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ConfigurationSet? historySet = null; - - foreach (ConfigurationSet set in processor.GetConfigurationHistory()) - { - if (set.InstanceIdentifier == returnedSet.InstanceIdentifier) - { - historySet = set; - } - } - - Assert.Null(historySet); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2927")] - private ConfigurationSet RunApplyHistoryMatchTest(string contents, string[]? inactiveIdentifiers = null) - { - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - OpenConfigurationSetResult configurationSetResult = processor.OpenConfigurationSet(this.CreateStream(contents)); - ConfigurationSet configurationSet = configurationSetResult.Set; - Assert.NotNull(configurationSet); - - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - setProcessor.EnableDefaultGroupProcessorCreation = true; - - configurationSet.Name = "Test Name"; - configurationSet.Origin = "Test Origin"; - configurationSet.Path = "Test Path"; - - if (inactiveIdentifiers != null) - { - foreach (string identifier in inactiveIdentifiers) - { - foreach (ConfigurationUnit unit in configurationSet.Units) - { - if (unit.Identifier == identifier) - { - unit.IsActive = false; - } - } - } - } - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.Null(result.ResultCode); - - ConfigurationSet? historySet = null; - - foreach (ConfigurationSet set in processor.GetConfigurationHistory()) - { - if (set.InstanceIdentifier == configurationSet.InstanceIdentifier) - { - historySet = set; - } - } - - this.AssertSetsEqual(configurationSet, historySet); - this.AssertResultsEqual(result, historySet); - return historySet; - } - - private void AssertSetsEqual(ConfigurationSet expectedSet, [NotNull] ConfigurationSet? actualSet) - { - Assert.NotNull(actualSet); - Assert.Equal(expectedSet.Name, actualSet.Name); - Assert.Equal(expectedSet.Origin, actualSet.Origin); - Assert.Equal(expectedSet.Path, actualSet.Path); - - Assert.Equal(ConfigurationSetState.Completed, actualSet.State); - - this.AssertTimeNotZero(actualSet.FirstApply); - this.AssertTimeNotZero(actualSet.ApplyBegun); - this.AssertTimeNotZero(actualSet.ApplyEnded); - Assert.True(actualSet.FirstApply <= actualSet.ApplyBegun); - Assert.True(actualSet.ApplyBegun <= actualSet.ApplyEnded); - - Assert.Equal(expectedSet.SchemaVersion, actualSet.SchemaVersion); - Assert.Equal(expectedSet.SchemaUri, actualSet.SchemaUri); - Assert.True(expectedSet.Metadata.ContentEquals(actualSet.Metadata)); - - this.AssertUnitsListEqual(expectedSet.Units, actualSet.Units); - } - - private void AssertTimeNotZero(DateTimeOffset actualTime) - { - Assert.NotEqual(DateTimeOffset.UnixEpoch, actualTime); - Assert.NotEqual(DateTimeOffset.MinValue, actualTime); - } - - private void AssertUnitsListEqual(IList expectedUnits, IList actualUnits) - { - Assert.Equal(expectedUnits.Count, actualUnits.Count); - - foreach (ConfigurationUnit expectedUnit in expectedUnits) - { - ConfigurationUnit? actualUnit = null; - foreach (ConfigurationUnit historyUnit in actualUnits) - { - if (historyUnit.InstanceIdentifier == expectedUnit.InstanceIdentifier) - { - actualUnit = historyUnit; - } - } - - this.AssertUnitsEqual(expectedUnit, actualUnit); - } - } - - private void AssertUnitsEqual(ConfigurationUnit expectedUnit, ConfigurationUnit? actualUnit) - { - Assert.NotNull(actualUnit); - Assert.Equal(expectedUnit.Type, actualUnit.Type); - Assert.Equal(expectedUnit.Identifier, actualUnit.Identifier); - Assert.Equal(expectedUnit.Intent, actualUnit.Intent); - Assert.Equal(expectedUnit.Dependencies, actualUnit.Dependencies); - Assert.True(expectedUnit.Metadata.ContentEquals(actualUnit.Metadata), $"Metadata not equal: {expectedUnit.Identifier}\n---expected---:\n{expectedUnit.Metadata.ToYaml()}\n---actual---:\n{actualUnit.Metadata.ToYaml()}"); - Assert.True(expectedUnit.Settings.ContentEquals(actualUnit.Settings)); - Assert.Equal(expectedUnit.IsActive, actualUnit.IsActive); - Assert.Equal(expectedUnit.IsGroup, actualUnit.IsGroup); - - if (expectedUnit.IsGroup) - { - this.AssertUnitsListEqual(expectedUnit.Units, actualUnit.Units); - } - } - - private void AssertResultsEqual(ApplyConfigurationSetResult expected, ConfigurationSet actualSet) - { - List actualUnitList = new List(); - - foreach (ConfigurationUnit unit in actualSet.Units) - { - this.AccumulateUnits(actualUnitList, unit); - } - - foreach (ApplyConfigurationUnitResult expectedUnitResult in expected.UnitResults) - { - ConfigurationUnit? actualUnit = null; - foreach (ConfigurationUnit historyUnit in actualUnitList) - { - if (historyUnit.InstanceIdentifier == expectedUnitResult.Unit.InstanceIdentifier) - { - actualUnit = historyUnit; - } - } - - this.AssertUnitResultsEqual(expectedUnitResult, actualUnit); - } - } - - private void AccumulateUnits(List unitList, ConfigurationUnit unit) - { - unitList.Add(unit); - if (unit.IsGroup) - { - foreach (ConfigurationUnit child in unit.Units) - { - this.AccumulateUnits(unitList, child); - } - } - } - - private void AssertUnitResultsEqual(ApplyConfigurationUnitResult expectedResult, ConfigurationUnit? actualUnit) - { - Assert.NotNull(actualUnit); - Assert.Equal(expectedResult.State, actualUnit.State); - - var expectedResultInformation = expectedResult.ResultInformation; - if (expectedResultInformation != null) - { - var actualResultInformation = actualUnit.ResultInformation; - Assert.NotNull(actualResultInformation); - - Assert.Equal(expectedResultInformation.ResultCode == null, actualResultInformation.ResultCode == null); - if (expectedResultInformation.ResultCode != null) - { - Assert.Equal(expectedResultInformation.ResultCode.HResult, actualResultInformation.ResultCode!.HResult); - } - - Assert.Equal(expectedResultInformation.Description, actualResultInformation.Description); - Assert.Equal(expectedResultInformation.Details, actualResultInformation.Details); - Assert.Equal(expectedResultInformation.ResultSource, actualResultInformation.ResultSource); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Xml.Linq; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for configuration history. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationHistoryTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationHistoryTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// Checks that the history matches the applied set. + /// + [Fact] + [OutOfProc] + public void ApplySet_HistoryMatches_0_1() + { + this.RunApplyHistoryMatchTest( + @" +properties: + configurationVersion: 0.1 + assertions: + - resource: Assert + id: AssertIdentifier1 + directives: + module: Module + settings: + Setting1: '1' + Setting2: 2 + - resource: Assert + id: AssertIdentifier2 + dependsOn: + - AssertIdentifier1 + directives: + module: Module + settings: + Setting1: + Setting2: 2 + parameters: + - resource: Inform + id: InformIdentifier1 + directives: + module: Module2 + settings: + Setting1: + Setting2: + Setting3: 3 + resources: + - resource: Apply +", new string[] { "AssertIdentifier2" }); + } + + /// + /// Checks that the history matches the applied set. + /// + [Fact] + [OutOfProc] + public void ApplySet_HistoryMatches_0_2() + { + this.RunApplyHistoryMatchTest( + @" +properties: + configurationVersion: 0.2 + assertions: + - resource: Module/Assert + id: AssertIdentifier1 + settings: + Setting1: '1' + Setting2: 2 + - resource: Module/Assert + id: AssertIdentifier2 + dependsOn: + - AssertIdentifier1 + directives: + description: Describe! + settings: + Setting1: + Setting2: 2 + parameters: + - resource: Module2/Inform + id: InformIdentifier1 + settings: + Setting1: + Setting2: + Setting3: 3 + resources: + - resource: Apply +", new string[] { "AssertIdentifier2" }); + } + + /// + /// Checks that the history matches the applied set. + /// + [Fact] + public void ApplySet_HistoryMatches_0_3() + { + this.RunApplyHistoryMatchTest( + @" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + a: 1 + b: '2' +variables: + v1: var1 + v2: 42 +resources: + - name: Name + type: Module/Resource + metadata: + e: '5' + f: 6 + properties: + c: 3 + d: '4' + - name: Name2 + type: Module/Resource2 + dependsOn: + - Name + properties: + l: '10' + metadata: + i: '7' + j: 8 + q: 42 + - name: Group + type: Module2/Resource + metadata: + isGroup: true + properties: + resources: + - name: Child1 + type: Module3/Resource + metadata: + e: '5' + f: 6 + properties: + c: 3 + d: '4' + - name: Child2 + type: Module4/Resource2 + properties: + l: '10' + metadata: + i: '7' + j: 8 + q: 42 +"); + } + + /// + /// Applies a set, reads the history, changes the read set and reapplies it. + /// + [Fact] + [OutOfProc] + public void ApplySet_ChangeHistory() + { + string disabledIdentifier = "AssertIdentifier2"; + + ConfigurationSet returnedSet = this.RunApplyHistoryMatchTest( + @" +properties: + configurationVersion: 0.2 + assertions: + - resource: Module/Assert + id: AssertIdentifier1 + settings: + Setting1: '1' + Setting2: 2 + - resource: Module/Assert + id: AssertIdentifier2 + dependsOn: + - AssertIdentifier1 + directives: + description: Describe! + settings: + Setting1: + Setting2: 2 + parameters: + - resource: Module2/Inform + id: InformIdentifier1 + settings: + Setting1: + Setting2: + Setting3: 3 + resources: + - resource: Apply +", new string[] { disabledIdentifier }); + + foreach (ConfigurationUnit unit in returnedSet.Units) + { + if (unit.Identifier == disabledIdentifier) + { + unit.IsActive = true; + } + } + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(returnedSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.Null(result.ResultCode); + + ConfigurationSet? historySet = null; + + foreach (ConfigurationSet set in processor.GetConfigurationHistory()) + { + if (set.InstanceIdentifier == returnedSet.InstanceIdentifier) + { + historySet = set; + } + } + + this.AssertSetsEqual(returnedSet, historySet); + } + + /// + /// Applies a set, reads the history and removes it. + /// + [Fact] + [OutOfProc] + public void ApplySet_RemoveHistory() + { + ConfigurationSet returnedSet = this.RunApplyHistoryMatchTest( + @" +properties: + configurationVersion: 0.2 + assertions: + - resource: Module/Assert + id: AssertIdentifier1 + settings: + Setting1: '1' + Setting2: 2 + - resource: Module/Assert + id: AssertIdentifier2 + dependsOn: + - AssertIdentifier1 + directives: + description: Describe! + settings: + Setting1: + Setting2: 2 + parameters: + - resource: Module2/Inform + id: InformIdentifier1 + settings: + Setting1: + Setting2: + Setting3: 3 + resources: + - resource: Apply +"); + + returnedSet.Remove(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ConfigurationSet? historySet = null; + + foreach (ConfigurationSet set in processor.GetConfigurationHistory()) + { + if (set.InstanceIdentifier == returnedSet.InstanceIdentifier) + { + historySet = set; + } + } + + Assert.Null(historySet); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.SpacingRules", "SA1011:Closing square brackets should be spaced correctly", Justification = "https://github.com/DotNetAnalyzers/StyleCopAnalyzers/issues/2927")] + private ConfigurationSet RunApplyHistoryMatchTest(string contents, string[]? inactiveIdentifiers = null) + { + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + OpenConfigurationSetResult configurationSetResult = processor.OpenConfigurationSet(this.CreateStream(contents)); + ConfigurationSet configurationSet = configurationSetResult.Set; + Assert.NotNull(configurationSet); + + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.EnableDefaultGroupProcessorCreation = true; + + configurationSet.Name = "Test Name"; + configurationSet.Origin = "Test Origin"; + configurationSet.Path = "Test Path"; + + if (inactiveIdentifiers != null) + { + foreach (string identifier in inactiveIdentifiers) + { + foreach (ConfigurationUnit unit in configurationSet.Units) + { + if (unit.Identifier == identifier) + { + unit.IsActive = false; + } + } + } + } + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.Null(result.ResultCode); + + ConfigurationSet? historySet = null; + + foreach (ConfigurationSet set in processor.GetConfigurationHistory()) + { + if (set.InstanceIdentifier == configurationSet.InstanceIdentifier) + { + historySet = set; + } + } + + this.AssertSetsEqual(configurationSet, historySet); + this.AssertResultsEqual(result, historySet); + return historySet; + } + + private void AssertSetsEqual(ConfigurationSet expectedSet, [NotNull] ConfigurationSet? actualSet) + { + Assert.NotNull(actualSet); + Assert.Equal(expectedSet.Name, actualSet.Name); + Assert.Equal(expectedSet.Origin, actualSet.Origin); + Assert.Equal(expectedSet.Path, actualSet.Path); + + Assert.Equal(ConfigurationSetState.Completed, actualSet.State); + + this.AssertTimeNotZero(actualSet.FirstApply); + this.AssertTimeNotZero(actualSet.ApplyBegun); + this.AssertTimeNotZero(actualSet.ApplyEnded); + Assert.True(actualSet.FirstApply <= actualSet.ApplyBegun); + Assert.True(actualSet.ApplyBegun <= actualSet.ApplyEnded); + + Assert.Equal(expectedSet.SchemaVersion, actualSet.SchemaVersion); + Assert.Equal(expectedSet.SchemaUri, actualSet.SchemaUri); + Assert.True(expectedSet.Metadata.ContentEquals(actualSet.Metadata)); + + this.AssertUnitsListEqual(expectedSet.Units, actualSet.Units); + } + + private void AssertTimeNotZero(DateTimeOffset actualTime) + { + Assert.NotEqual(DateTimeOffset.UnixEpoch, actualTime); + Assert.NotEqual(DateTimeOffset.MinValue, actualTime); + } + + private void AssertUnitsListEqual(IList expectedUnits, IList actualUnits) + { + Assert.Equal(expectedUnits.Count, actualUnits.Count); + + foreach (ConfigurationUnit expectedUnit in expectedUnits) + { + ConfigurationUnit? actualUnit = null; + foreach (ConfigurationUnit historyUnit in actualUnits) + { + if (historyUnit.InstanceIdentifier == expectedUnit.InstanceIdentifier) + { + actualUnit = historyUnit; + } + } + + this.AssertUnitsEqual(expectedUnit, actualUnit); + } + } + + private void AssertUnitsEqual(ConfigurationUnit expectedUnit, ConfigurationUnit? actualUnit) + { + Assert.NotNull(actualUnit); + Assert.Equal(expectedUnit.Type, actualUnit.Type); + Assert.Equal(expectedUnit.Identifier, actualUnit.Identifier); + Assert.Equal(expectedUnit.Intent, actualUnit.Intent); + Assert.Equal(expectedUnit.Dependencies, actualUnit.Dependencies); + Assert.True(expectedUnit.Metadata.ContentEquals(actualUnit.Metadata), $"Metadata not equal: {expectedUnit.Identifier}\n---expected---:\n{expectedUnit.Metadata.ToYaml()}\n---actual---:\n{actualUnit.Metadata.ToYaml()}"); + Assert.True(expectedUnit.Settings.ContentEquals(actualUnit.Settings)); + Assert.Equal(expectedUnit.IsActive, actualUnit.IsActive); + Assert.Equal(expectedUnit.IsGroup, actualUnit.IsGroup); + + if (expectedUnit.IsGroup) + { + this.AssertUnitsListEqual(expectedUnit.Units, actualUnit.Units); + } + } + + private void AssertResultsEqual(ApplyConfigurationSetResult expected, ConfigurationSet actualSet) + { + List actualUnitList = new List(); + + foreach (ConfigurationUnit unit in actualSet.Units) + { + this.AccumulateUnits(actualUnitList, unit); + } + + foreach (ApplyConfigurationUnitResult expectedUnitResult in expected.UnitResults) + { + ConfigurationUnit? actualUnit = null; + foreach (ConfigurationUnit historyUnit in actualUnitList) + { + if (historyUnit.InstanceIdentifier == expectedUnitResult.Unit.InstanceIdentifier) + { + actualUnit = historyUnit; + } + } + + this.AssertUnitResultsEqual(expectedUnitResult, actualUnit); + } + } + + private void AccumulateUnits(List unitList, ConfigurationUnit unit) + { + unitList.Add(unit); + if (unit.IsGroup) + { + foreach (ConfigurationUnit child in unit.Units) + { + this.AccumulateUnits(unitList, child); + } + } + } + + private void AssertUnitResultsEqual(ApplyConfigurationUnitResult expectedResult, ConfigurationUnit? actualUnit) + { + Assert.NotNull(actualUnit); + Assert.Equal(expectedResult.State, actualUnit.State); + + var expectedResultInformation = expectedResult.ResultInformation; + if (expectedResultInformation != null) + { + var actualResultInformation = actualUnit.ResultInformation; + Assert.NotNull(actualResultInformation); + + Assert.Equal(expectedResultInformation.ResultCode == null, actualResultInformation.ResultCode == null); + if (expectedResultInformation.ResultCode != null) + { + Assert.Equal(expectedResultInformation.ResultCode.HResult, actualResultInformation.ResultCode!.HResult); + } + + Assert.Equal(expectedResultInformation.Description, actualResultInformation.Description); + Assert.Equal(expectedResultInformation.Details, actualResultInformation.Details); + Assert.Equal(expectedResultInformation.ResultSource, actualResultInformation.ResultSource); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationMixedElevationTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationMixedElevationTests.cs index 56b03be88a..8fa530d661 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationMixedElevationTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationMixedElevationTests.cs @@ -1,303 +1,303 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.IO; - using System.Threading.Tasks; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for verifying the processor behavior for handling mixed elevation scenarios. - /// - [Collection("UnitTestCollection")] - [OutOfProc] - public class ConfigurationMixedElevationTests : ConfigurationProcessorTestBase - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationMixedElevationTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Verifies that applying units of mixed elevation is successful. Also verifies that the elevated processor has a different process id. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task ApplyMixedElevationUnits() - { - string resourceName = "E2ETestResourcePID"; - string moduleName = "xE2ETestResource"; - Version version = new Version("0.0.0.1"); - - string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(tempDirectory); - - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.SchemaVersion = "0.2"; - configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); - - ConfigurationUnit unit = this.ConfigurationUnit(); - unit.Metadata.Add("version", version.ToString()); - unit.Metadata.Add("module", moduleName); - unit.Settings.Add("directoryPath", tempDirectory); - unit.Type = resourceName; - unit.Intent = ConfigurationUnitIntent.Apply; - - ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); - elevatedUnit.Metadata.Add("version", version.ToString()); - elevatedUnit.Metadata.Add("module", moduleName); - elevatedUnit.Environment.Context = SecurityContext.Elevated; - elevatedUnit.Settings.Add("directoryPath", tempDirectory); - elevatedUnit.Type = resourceName; - elevatedUnit.Intent = ConfigurationUnitIntent.Apply; - - configurationSet.Units = new ConfigurationUnit[] { unit, elevatedUnit }; - - IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - - // Get the number of unique PIDs from temp directory. - int pidCount = Directory.GetFiles(tempDirectory).Length; - - // Clean up temp directory folder. - Directory.Delete(tempDirectory, true); - - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.Equal(2, result.UnitResults.Count); - - foreach (var unitResult in result.UnitResults) - { - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - // There should be exactly 2 unique PIDs, one for each integrity level. - Assert.Equal(2, pidCount); - } - - /// - /// Verifies that applying units of mixed elevation is successful. Also verifies that the elevated processor has a different process id. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task ApplyMixedElevationUnits_Schema_0_3() - { - string resourceName = "xE2ETestResource/E2ETestResourcePID"; - Version version = new Version("0.0.0.1"); - - string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(tempDirectory); - - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.SchemaVersion = "0.3"; - configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); - - ConfigurationUnit unit = this.ConfigurationUnit(); - unit.Metadata.Add("version", version.ToString()); - unit.Settings.Add("directoryPath", tempDirectory); - unit.Type = resourceName; - unit.Identifier = "current"; - - ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); - elevatedUnit.Intent = ConfigurationUnitIntent.Unknown; - elevatedUnit.Metadata.Add("version", version.ToString()); - elevatedUnit.Environment.Context = SecurityContext.Elevated; - elevatedUnit.Settings.Add("directoryPath", tempDirectory); - elevatedUnit.Type = resourceName; - elevatedUnit.Identifier = "elevated"; - - configurationSet.Units = new ConfigurationUnit[] { unit, elevatedUnit }; - - IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - - // Get the number of unique PIDs from temp directory. - int pidCount = Directory.GetFiles(tempDirectory).Length; - - // Clean up temp directory folder. - Directory.Delete(tempDirectory, true); - - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.Equal(2, result.UnitResults.Count); - - foreach (var unitResult in result.UnitResults) - { - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - // There should be exactly 2 unique PIDs, one for each integrity level. - Assert.Equal(2, pidCount); - } - - /// - /// Verifies that creating a high integrity unit processor for a non elevated unit should return an invalid operation result. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task ApplyUnitNotInLimitationSet() - { - string resourceName = "E2ETestResource"; - string moduleName = "xE2ETestResource"; - Version version = new Version("0.0.0.1"); - - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.SchemaVersion = "0.2"; - configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); - configurationSet.Metadata.Add(Helpers.Constants.ForceHighIntegrityLevelUnitsTestGuid, true); - configurationSet.Metadata.Add(Helpers.Constants.EnableRestrictedIntegrityLevelTestGuid, true); - - ConfigurationUnit unit = this.ConfigurationUnit(); - unit.Metadata.Add("version", version.ToString()); - unit.Metadata.Add("module", moduleName); - unit.Metadata.Add("unique", "value"); - unit.Type = resourceName; - unit.Intent = ConfigurationUnitIntent.Apply; - - ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); - elevatedUnit.Metadata.Add("version", version.ToString()); - elevatedUnit.Metadata.Add("module", moduleName); - elevatedUnit.Environment.Context = SecurityContext.Elevated; - elevatedUnit.Type = resourceName; - elevatedUnit.Intent = ConfigurationUnitIntent.Apply; - - configurationSet.Units = new ConfigurationUnit[] { unit, elevatedUnit }; - - IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_SET_APPLY_FAILED, result.ResultCode.HResult); - Assert.Equal(2, result.UnitResults.Count); - - ApplyConfigurationUnitResult unitResult = result.UnitResults[0]; - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.Equal(Errors.CORE_INVALID_OPERATION, unitResult.ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.Internal, unitResult.ResultInformation.ResultSource); - - // Elevated unit should still succeed when applied. - ApplyConfigurationUnitResult elevatedUnitResult = result.UnitResults[1]; - Assert.NotNull(elevatedUnitResult); - Assert.False(elevatedUnitResult.PreviouslyInDesiredState); - Assert.False(elevatedUnitResult.RebootRequired); - Assert.NotNull(elevatedUnitResult.ResultInformation); - Assert.Null(elevatedUnitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, elevatedUnitResult.ResultInformation.ResultSource); - } - - /// - /// Verifies that attempting to pass a secure parameter across the integrity boundary fails. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task SecureParameterAcrossIntegrityBoundaryFails() - { - string resourceName = "E2ETestResourcePID"; - string moduleName = "xE2ETestResource"; - Version version = new Version("0.0.0.1"); - - string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(tempDirectory); - - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); - - ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); - elevatedUnit.Metadata.Add("version", version.ToString()); - elevatedUnit.Metadata.Add("module", moduleName); - elevatedUnit.Environment.Context = SecurityContext.Elevated; - elevatedUnit.Settings.Add("directoryPath", tempDirectory); - elevatedUnit.Type = resourceName; - elevatedUnit.Intent = ConfigurationUnitIntent.Apply; - - configurationSet.Units = new ConfigurationUnit[] { elevatedUnit }; - - ConfigurationParameter parameter = this.ConfigurationParameter(); - parameter.Name = "param"; - parameter.Type = Windows.Foundation.PropertyType.String; - parameter.IsSecure = true; - parameter.ProvidedValue = "secrets"; - - configurationSet.Parameters = new ConfigurationParameter[] { parameter }; - - IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); - - // While parameters are not supported, we expect to get a not implemented exception. - // Once they are implemented, swap to the appropriate error mechanism for the parameter integrity boundary. - Assert.Throws(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); - } - - /// - /// Verifies that DynamicFactoryProcessors do not break on empty set when working with only units. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task CreateDynamicFactoryProcessorsWithEmptyConfigurationSet() - { - string resourceName = "E2ETestResourcePID"; - string moduleName = "xE2ETestResource"; - Version version = new Version("0.0.0.1"); - - string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - Directory.CreateDirectory(tempDirectory); - - ConfigurationUnit unit = this.ConfigurationUnit(); - unit.Metadata.Add("version", version.ToString()); - unit.Metadata.Add("module", moduleName); - unit.Settings.Add("directoryPath", tempDirectory); - unit.Type = resourceName; - unit.Intent = ConfigurationUnitIntent.Inform; - - IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); - - var result = await processor.GetUnitSettingsAsync(unit); - - Assert.NotNull(result); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.IO; + using System.Threading.Tasks; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for verifying the processor behavior for handling mixed elevation scenarios. + /// + [Collection("UnitTestCollection")] + [OutOfProc] + public class ConfigurationMixedElevationTests : ConfigurationProcessorTestBase + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationMixedElevationTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Verifies that applying units of mixed elevation is successful. Also verifies that the elevated processor has a different process id. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ApplyMixedElevationUnits() + { + string resourceName = "E2ETestResourcePID"; + string moduleName = "xE2ETestResource"; + Version version = new Version("0.0.0.1"); + + string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.2"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Metadata.Add("version", version.ToString()); + unit.Metadata.Add("module", moduleName); + unit.Settings.Add("directoryPath", tempDirectory); + unit.Type = resourceName; + unit.Intent = ConfigurationUnitIntent.Apply; + + ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); + elevatedUnit.Metadata.Add("version", version.ToString()); + elevatedUnit.Metadata.Add("module", moduleName); + elevatedUnit.Environment.Context = SecurityContext.Elevated; + elevatedUnit.Settings.Add("directoryPath", tempDirectory); + elevatedUnit.Type = resourceName; + elevatedUnit.Intent = ConfigurationUnitIntent.Apply; + + configurationSet.Units = new ConfigurationUnit[] { unit, elevatedUnit }; + + IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + + // Get the number of unique PIDs from temp directory. + int pidCount = Directory.GetFiles(tempDirectory).Length; + + // Clean up temp directory folder. + Directory.Delete(tempDirectory, true); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.Equal(2, result.UnitResults.Count); + + foreach (var unitResult in result.UnitResults) + { + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + // There should be exactly 2 unique PIDs, one for each integrity level. + Assert.Equal(2, pidCount); + } + + /// + /// Verifies that applying units of mixed elevation is successful. Also verifies that the elevated processor has a different process id. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ApplyMixedElevationUnits_Schema_0_3() + { + string resourceName = "xE2ETestResource/E2ETestResourcePID"; + Version version = new Version("0.0.0.1"); + + string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.3"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Metadata.Add("version", version.ToString()); + unit.Settings.Add("directoryPath", tempDirectory); + unit.Type = resourceName; + unit.Identifier = "current"; + + ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); + elevatedUnit.Intent = ConfigurationUnitIntent.Unknown; + elevatedUnit.Metadata.Add("version", version.ToString()); + elevatedUnit.Environment.Context = SecurityContext.Elevated; + elevatedUnit.Settings.Add("directoryPath", tempDirectory); + elevatedUnit.Type = resourceName; + elevatedUnit.Identifier = "elevated"; + + configurationSet.Units = new ConfigurationUnit[] { unit, elevatedUnit }; + + IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + + // Get the number of unique PIDs from temp directory. + int pidCount = Directory.GetFiles(tempDirectory).Length; + + // Clean up temp directory folder. + Directory.Delete(tempDirectory, true); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.Equal(2, result.UnitResults.Count); + + foreach (var unitResult in result.UnitResults) + { + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + // There should be exactly 2 unique PIDs, one for each integrity level. + Assert.Equal(2, pidCount); + } + + /// + /// Verifies that creating a high integrity unit processor for a non elevated unit should return an invalid operation result. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task ApplyUnitNotInLimitationSet() + { + string resourceName = "E2ETestResource"; + string moduleName = "xE2ETestResource"; + Version version = new Version("0.0.0.1"); + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.2"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + configurationSet.Metadata.Add(Helpers.Constants.ForceHighIntegrityLevelUnitsTestGuid, true); + configurationSet.Metadata.Add(Helpers.Constants.EnableRestrictedIntegrityLevelTestGuid, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Metadata.Add("version", version.ToString()); + unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("unique", "value"); + unit.Type = resourceName; + unit.Intent = ConfigurationUnitIntent.Apply; + + ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); + elevatedUnit.Metadata.Add("version", version.ToString()); + elevatedUnit.Metadata.Add("module", moduleName); + elevatedUnit.Environment.Context = SecurityContext.Elevated; + elevatedUnit.Type = resourceName; + elevatedUnit.Intent = ConfigurationUnitIntent.Apply; + + configurationSet.Units = new ConfigurationUnit[] { unit, elevatedUnit }; + + IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_SET_APPLY_FAILED, result.ResultCode.HResult); + Assert.Equal(2, result.UnitResults.Count); + + ApplyConfigurationUnitResult unitResult = result.UnitResults[0]; + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.Equal(Errors.CORE_INVALID_OPERATION, unitResult.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.Internal, unitResult.ResultInformation.ResultSource); + + // Elevated unit should still succeed when applied. + ApplyConfigurationUnitResult elevatedUnitResult = result.UnitResults[1]; + Assert.NotNull(elevatedUnitResult); + Assert.False(elevatedUnitResult.PreviouslyInDesiredState); + Assert.False(elevatedUnitResult.RebootRequired); + Assert.NotNull(elevatedUnitResult.ResultInformation); + Assert.Null(elevatedUnitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, elevatedUnitResult.ResultInformation.ResultSource); + } + + /// + /// Verifies that attempting to pass a secure parameter across the integrity boundary fails. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task SecureParameterAcrossIntegrityBoundaryFails() + { + string resourceName = "E2ETestResourcePID"; + string moduleName = "xE2ETestResource"; + Version version = new Version("0.0.0.1"); + + string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit elevatedUnit = this.ConfigurationUnit(); + elevatedUnit.Metadata.Add("version", version.ToString()); + elevatedUnit.Metadata.Add("module", moduleName); + elevatedUnit.Environment.Context = SecurityContext.Elevated; + elevatedUnit.Settings.Add("directoryPath", tempDirectory); + elevatedUnit.Type = resourceName; + elevatedUnit.Intent = ConfigurationUnitIntent.Apply; + + configurationSet.Units = new ConfigurationUnit[] { elevatedUnit }; + + ConfigurationParameter parameter = this.ConfigurationParameter(); + parameter.Name = "param"; + parameter.Type = Windows.Foundation.PropertyType.String; + parameter.IsSecure = true; + parameter.ProvidedValue = "secrets"; + + configurationSet.Parameters = new ConfigurationParameter[] { parameter }; + + IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + + // While parameters are not supported, we expect to get a not implemented exception. + // Once they are implemented, swap to the appropriate error mechanism for the parameter integrity boundary. + Assert.Throws(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); + } + + /// + /// Verifies that DynamicFactoryProcessors do not break on empty set when working with only units. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task CreateDynamicFactoryProcessorsWithEmptyConfigurationSet() + { + string resourceName = "E2ETestResourcePID"; + string moduleName = "xE2ETestResource"; + Version version = new Version("0.0.0.1"); + + string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + Directory.CreateDirectory(tempDirectory); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Metadata.Add("version", version.ToString()); + unit.Metadata.Add("module", moduleName); + unit.Settings.Add("directoryPath", tempDirectory); + unit.Type = resourceName; + unit.Intent = ConfigurationUnitIntent.Inform; + + IConfigurationSetProcessorFactory dynamicFactory = await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync(Helpers.Constants.DynamicRuntimeHandlerIdentifier); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + + var result = await processor.GetUnitSettingsAsync(unit); + + Assert.NotNull(result); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorApplyTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorApplyTests.cs index 0a93758625..6a0d516c0e 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorApplyTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorApplyTests.cs @@ -1,619 +1,619 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Threading; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for running apply on the processor. - /// - [Collection("UnitTestCollection")] - [InProc] - [OutOfProc] - public class ConfigurationProcessorApplyTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationProcessorApplyTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// An error creating the set processor results in an error for the function. - /// - [Fact] - public void ApplySet_SetProcessorError() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.Exceptions.Add(configurationSet, new FileNotFoundException()); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - Assert.Throws(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); - - Assert.Empty(this.EventSink.Events); - } - - /// - /// Multiple configuration units with the same identifier. - /// - [Fact] - public void ApplySet_DuplicateIdentifiers() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnit1 = this.ConfigurationUnit(); - ConfigurationUnit configurationUnit2 = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitDifferentIdentifier = this.ConfigurationUnit(); - string sharedIdentifier = "SameIdentifier"; - configurationUnit1.Identifier = sharedIdentifier; - configurationUnit2.Identifier = sharedIdentifier; - configurationSet.Units = new ConfigurationUnit[] { configurationUnit1, configurationUnit2, configurationUnitDifferentIdentifier }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, result.ResultCode.HResult); - Assert.Equal(3, result.UnitResults.Count); - - foreach (var configurationUnit in new ConfigurationUnit[] { configurationUnit1, configurationUnit2 }) - { - ApplyConfigurationUnitResult? unitResult = result.UnitResults.First(x => x.Unit == configurationUnit); - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, unitResult.ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.ConfigurationSet, unitResult.ResultInformation.ResultSource); - } - - ApplyConfigurationUnitResult unitResultDifferentIdentifier = result.UnitResults.First(x => x.Unit == configurationUnitDifferentIdentifier); - Assert.NotNull(unitResultDifferentIdentifier); - Assert.False(unitResultDifferentIdentifier.PreviouslyInDesiredState); - Assert.False(unitResultDifferentIdentifier.RebootRequired); - Assert.NotNull(unitResultDifferentIdentifier.ResultInformation); - Assert.Null(unitResultDifferentIdentifier.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResultDifferentIdentifier.ResultInformation.ResultSource); - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.ConfigurationSet); - } - - /// - /// A configuration unit has a dependency that is not in the set. - /// - [Fact] - public void ApplySet_MissingDependency() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitMissingDependency = this.ConfigurationUnit(); - configurationUnit.Identifier = "Identifier"; - configurationUnitMissingDependency.Dependencies = new string[] { "Dependency" }; - configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitMissingDependency }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, result.ResultCode.HResult); - Assert.Equal(2, result.UnitResults.Count); - - ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == configurationUnit); - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - - unitResult = result.UnitResults.First(x => x.Unit == configurationUnitMissingDependency); - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, unitResult.ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.ConfigurationSet, unitResult.ResultInformation.ResultSource); - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.ConfigurationSet); - } - - /// - /// The configuration set has a dependency cycle. - /// - [Fact] - public void ApplySet_DependencyCycle() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnit1 = this.ConfigurationUnit(); - ConfigurationUnit configurationUnit2 = this.ConfigurationUnit(); - ConfigurationUnit configurationUnit3 = this.ConfigurationUnit(); - configurationUnit1.Identifier = "Identifier1"; - configurationUnit2.Identifier = "Identifier2"; - configurationUnit3.Identifier = "Identifier3"; - configurationUnit1.Dependencies = new string[] { "Identifier3" }; - configurationUnit2.Dependencies = new string[] { "Identifier1" }; - configurationUnit3.Dependencies = new string[] { "Identifier2" }; - configurationSet.Units = new ConfigurationUnit[] { configurationUnit1, configurationUnit2, configurationUnit3 }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE, result.ResultCode.HResult); - Assert.Equal(3, result.UnitResults.Count); - - foreach (var unitResult in result.UnitResults) - { - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, unitResult.ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.Precondition, unitResult.ResultInformation.ResultSource); - } - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Precondition); - } - - /// - /// Checks that the intent for configuration units is handled properly. - /// - [Fact] - public void ApplySet_IntentRespected() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitAssert = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert }); - ConfigurationUnit configurationUnitInform = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Inform }); - ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitInform, configurationUnitApply, configurationUnitAssert }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessorAssert = setProcessor.CreateTestProcessor(configurationUnitAssert); - TestConfigurationUnitProcessor unitProcessorInform = setProcessor.CreateTestProcessor(configurationUnitInform); - TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); - unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.Equal(3, result.UnitResults.Count); - - foreach (var unitResult in result.UnitResults) - { - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - Assert.Equal(1, unitProcessorAssert.TestSettingsCalls); - Assert.Equal(0, unitProcessorAssert.GetSettingsCalls); - Assert.Equal(0, unitProcessorAssert.ApplySettingsCalls); - - Assert.Equal(0, unitProcessorInform.TestSettingsCalls); - Assert.Equal(1, unitProcessorInform.GetSettingsCalls); - Assert.Equal(0, unitProcessorInform.ApplySettingsCalls); - - Assert.Equal(1, unitProcessorApply.TestSettingsCalls); - Assert.Equal(0, unitProcessorApply.GetSettingsCalls); - Assert.Equal(1, unitProcessorApply.ApplySettingsCalls); - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); - } - - /// - /// An assertion fails to run. - /// - [Fact] - public void ApplySet_AssertionFailure() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitAssert = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert }); - ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply, configurationUnitAssert }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessorAssert = setProcessor.CreateTestProcessor(configurationUnitAssert); - unitProcessorAssert.TestSettingsDelegate = () => throw new NullReferenceException(); - TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); - unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, result.ResultCode.HResult); - Assert.Equal(2, result.UnitResults.Count); - - ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == configurationUnitAssert); - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.IsType(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.Internal, unitResult.ResultInformation.ResultSource); - - unitResult = result.UnitResults.First(x => x.Unit == configurationUnitApply); - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, unitResult.ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.Precondition, unitResult.ResultInformation.ResultSource); - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Internal); - } - - /// - /// An assertion is found to be false. - /// - [Fact] - public void ApplySet_AssertionNegative() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitAssert = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert }); - ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply, configurationUnitAssert }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessorAssert = setProcessor.CreateTestProcessor(configurationUnitAssert); - unitProcessorAssert.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitAssert) { TestResult = ConfigurationTestResult.Negative }; - TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); - unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, result.ResultCode.HResult); - Assert.Equal(2, result.UnitResults.Count); - - foreach (var unitResult in result.UnitResults) - { - Assert.NotNull(unitResult); - Assert.False(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, unitResult.ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.Precondition, unitResult.ResultInformation.ResultSource); - } - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Precondition); - } - - /// - /// A unit in the correct state is not applied again. - /// - [Fact] - public void ApplySet_UnitAlreadyInCorrectState() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnit = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSet.Units = new ConfigurationUnit[] { configurationUnit }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnit); - unitProcessor.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnit) { TestResult = ConfigurationTestResult.Positive }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.Equal(1, result.UnitResults.Count); - - ApplyConfigurationUnitResult unitResult = result.UnitResults.First(); - Assert.NotNull(unitResult); - Assert.True(unitResult.PreviouslyInDesiredState); - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); - } - - /// - /// Checks the progress reporting. - /// - [Fact] - public void ApplySet_Progress() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit assert1 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert, Identifier = "Assert1" }); - ConfigurationUnit assert2 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert, Identifier = "Assert2", Dependencies = new string[] { assert1.Identifier } }); - ConfigurationUnit inform1 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Inform, Identifier = "Inform1" }); - ConfigurationUnit apply1 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply1" }); - ConfigurationUnit apply2 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply2" }); - ConfigurationUnit apply3 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply3", Dependencies = new string[] { apply1.Identifier, apply2.Identifier } }); - ConfigurationUnit apply4 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply4", IsActive = false }); - ConfigurationUnit apply5 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply5", Dependencies = new string[] { apply4.Identifier } }); - configurationSet.Units = new ConfigurationUnit[] { assert2, assert1, inform1, apply1, apply3, apply4, apply2, apply5 }; - - ManualResetEvent startProcessing = new ManualResetEvent(false); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.CreateSetProcessorDelegate = (f, c) => - { - startProcessing.WaitOne(); - return f.DefaultCreateSetProcessor(c); - }; - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - List progressEvents = new List(); - - var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); - operation.Progress = (asyncInfo, progress) => progressEvents.Add(progress); - startProcessing.Set(); - operation.AsTask().Wait(); - ApplyConfigurationSetResult result = operation.GetResults(); - - Assert.NotNull(result); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, result.ResultCode.HResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(configurationSet.Units.Count, result.UnitResults.Count); - - // Verify that progress events match the expected - ExpectedConfigurationChangeData[] expectedProgress = new ExpectedConfigurationChangeData[] - { - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.SetStateChanged, SetState = ConfigurationSetState.InProgress }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = assert1 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = assert1 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = assert2 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = assert2 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = inform1 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = inform1 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = apply1 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = apply1 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Skipped, Unit = apply4, HResult = Errors.WINGET_CONFIG_ERROR_MANUALLY_SKIPPED }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = apply2 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = apply2 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = apply3 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = apply3 }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Skipped, Unit = apply5, HResult = Errors.WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED }, - new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.SetStateChanged, SetState = ConfigurationSetState.Completed }, - }; - - // Drop the pending event if it happens to be present - if (progressEvents.Count > 0 && progressEvents[0].Change == ConfigurationSetChangeEventType.SetStateChanged && progressEvents[0].SetState == ConfigurationSetState.Pending) - { - progressEvents.RemoveAt(0); - } - - Assert.Equal(expectedProgress.Count(), progressEvents.Count); - - for (int i = 0; i < progressEvents.Count; ++i) - { - this.Log.WriteLine($"Comparing event {i}"); - Assert.Equal(expectedProgress[i].Change, progressEvents[i].Change); - switch (expectedProgress[i].Change) - { - case ConfigurationSetChangeEventType.SetStateChanged: - Assert.Equal(expectedProgress[i].SetState, progressEvents[i].SetState); - break; - case ConfigurationSetChangeEventType.UnitStateChanged: - Assert.Equal(expectedProgress[i].UnitState, progressEvents[i].UnitState); - Assert.Same(expectedProgress[i].Unit, progressEvents[i].Unit); - - Assert.NotNull(progressEvents[i].ResultInformation); - if (expectedProgress[i].HResult == 0) - { - Assert.Null(progressEvents[i].ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, progressEvents[i].ResultInformation.ResultSource); - } - else - { - Assert.NotNull(progressEvents[i].ResultInformation.ResultCode); - Assert.Equal(expectedProgress[i].HResult, progressEvents[i].ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.Precondition, progressEvents[i].ResultInformation.ResultSource); - } - - break; - default: - Assert.Fail("Unexpected ConfigurationSetChangeEventType value"); - break; - } - } - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Precondition); - } - - /// - /// Ensures that multiple apply operations are sequenced. - /// - [Fact] - public void ApplySet_Sequenced() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply }; - - ManualResetEvent startProcessing = new ManualResetEvent(true); - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.CreateSetProcessorDelegate = (f, c) => - { - WaitOn(startProcessing); - return f.DefaultCreateSetProcessor(c); - }; - - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); - unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; - - ManualResetEvent applyEventWaiting = new ManualResetEvent(false); - ManualResetEvent completeApplyEvent = new ManualResetEvent(false); - unitProcessorApply.ApplySettingsDelegate = () => - { - applyEventWaiting.Set(); - WaitOn(completeApplyEvent); - return new ApplySettingsResultInstance(configurationUnitApply); - }; - - ConfigurationSet configurationSetThatWaits = this.ConfigurationSet(); - ConfigurationUnit configurationUnitThatWaits = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSetThatWaits.Units = new ConfigurationUnit[] { configurationUnitThatWaits }; - - TestConfigurationSetProcessor setThatWaitsProcessor = factory.CreateTestProcessor(configurationSetThatWaits); - TestConfigurationUnitProcessor unitThatWaitsProcessor = setProcessor.CreateTestProcessor(configurationUnitThatWaits); - unitThatWaitsProcessor.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; - - ManualResetEvent waitingUnitApply = new ManualResetEvent(false); - unitThatWaitsProcessor.ApplySettingsDelegate = () => - { - WaitOn(waitingUnitApply); - return new ApplySettingsResultInstance(configurationUnitThatWaits); - }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - var applySetOperation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); - WaitOn(applyEventWaiting); - - startProcessing.Reset(); - var waitingSetOperation = processor.ApplySetAsync(configurationSetThatWaits, ApplyConfigurationSetFlags.None); - AutoResetEvent waitingProgress = new AutoResetEvent(false); - ConfigurationSetState progressState = ConfigurationSetState.Unknown; - waitingSetOperation.Progress += (result, changeData) => - { - if (changeData.Change == ConfigurationSetChangeEventType.SetStateChanged) - { - progressState = changeData.SetState; - waitingProgress.Set(); - } - }; - - startProcessing.Set(); - WaitOn(waitingProgress); - Assert.Equal(ConfigurationSetState.Pending, progressState); - - completeApplyEvent.Set(); - WaitOn(waitingProgress); - Assert.Equal(ConfigurationSetState.InProgress, progressState); - - waitingUnitApply.Set(); - WaitOn(waitingProgress); - Assert.Equal(ConfigurationSetState.Completed, progressState); - - waitingSetOperation.AsTask().Wait(); - } - - /// - /// Ensures that a consistency check apply is not blocked. - /// - [Fact] - public void ApplySet_ConsistencyCheckNotSequenced() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply }; - - ManualResetEvent startProcessing = new ManualResetEvent(true); - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.CreateSetProcessorDelegate = (f, c) => - { - WaitOn(startProcessing); - return f.DefaultCreateSetProcessor(c); - }; - - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); - unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; - - ManualResetEvent applyEventWaiting = new ManualResetEvent(false); - ManualResetEvent completeApplyEvent = new ManualResetEvent(false); - unitProcessorApply.ApplySettingsDelegate = () => - { - applyEventWaiting.Set(); - WaitOn(completeApplyEvent); - return new ApplySettingsResultInstance(configurationUnitApply); - }; - - ConfigurationSet configurationSetThatWaits = this.ConfigurationSet(); - ConfigurationUnit configurationUnitThatWaits = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); - configurationSetThatWaits.Units = new ConfigurationUnit[] { configurationUnitThatWaits }; - - TestConfigurationSetProcessor setThatWaitsProcessor = factory.CreateTestProcessor(configurationSetThatWaits); - TestConfigurationUnitProcessor unitThatWaitsProcessor = setProcessor.CreateTestProcessor(configurationUnitThatWaits); - unitThatWaitsProcessor.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; - - ManualResetEvent waitingUnitApply = new ManualResetEvent(false); - unitThatWaitsProcessor.ApplySettingsDelegate = () => - { - WaitOn(waitingUnitApply); - return new ApplySettingsResultInstance(configurationUnitThatWaits); - }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - var applySetOperation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); - WaitOn(applyEventWaiting); - - startProcessing.Reset(); - var waitingSetOperation = processor.ApplySetAsync(configurationSetThatWaits, ApplyConfigurationSetFlags.PerformConsistencyCheckOnly); - Assert.True(waitingSetOperation.AsTask().Wait(10000)); - - completeApplyEvent.Set(); - } - - private static void WaitOn(WaitHandle waitable) - { - if (!waitable.WaitOne(10000)) - { - throw new TimeoutException(); - } - } - - private struct ExpectedConfigurationChangeData - { - public ConfigurationSetChangeEventType Change; - public ConfigurationSetState SetState; - public ConfigurationUnitState UnitState; - public int HResult; - public ConfigurationUnit Unit; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for running apply on the processor. + /// + [Collection("UnitTestCollection")] + [InProc] + [OutOfProc] + public class ConfigurationProcessorApplyTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationProcessorApplyTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// An error creating the set processor results in an error for the function. + /// + [Fact] + public void ApplySet_SetProcessorError() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.Exceptions.Add(configurationSet, new FileNotFoundException()); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + Assert.Throws(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); + + Assert.Empty(this.EventSink.Events); + } + + /// + /// Multiple configuration units with the same identifier. + /// + [Fact] + public void ApplySet_DuplicateIdentifiers() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnit1 = this.ConfigurationUnit(); + ConfigurationUnit configurationUnit2 = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitDifferentIdentifier = this.ConfigurationUnit(); + string sharedIdentifier = "SameIdentifier"; + configurationUnit1.Identifier = sharedIdentifier; + configurationUnit2.Identifier = sharedIdentifier; + configurationSet.Units = new ConfigurationUnit[] { configurationUnit1, configurationUnit2, configurationUnitDifferentIdentifier }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, result.ResultCode.HResult); + Assert.Equal(3, result.UnitResults.Count); + + foreach (var configurationUnit in new ConfigurationUnit[] { configurationUnit1, configurationUnit2 }) + { + ApplyConfigurationUnitResult? unitResult = result.UnitResults.First(x => x.Unit == configurationUnit); + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, unitResult.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.ConfigurationSet, unitResult.ResultInformation.ResultSource); + } + + ApplyConfigurationUnitResult unitResultDifferentIdentifier = result.UnitResults.First(x => x.Unit == configurationUnitDifferentIdentifier); + Assert.NotNull(unitResultDifferentIdentifier); + Assert.False(unitResultDifferentIdentifier.PreviouslyInDesiredState); + Assert.False(unitResultDifferentIdentifier.RebootRequired); + Assert.NotNull(unitResultDifferentIdentifier.ResultInformation); + Assert.Null(unitResultDifferentIdentifier.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResultDifferentIdentifier.ResultInformation.ResultSource); + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.ConfigurationSet); + } + + /// + /// A configuration unit has a dependency that is not in the set. + /// + [Fact] + public void ApplySet_MissingDependency() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitMissingDependency = this.ConfigurationUnit(); + configurationUnit.Identifier = "Identifier"; + configurationUnitMissingDependency.Dependencies = new string[] { "Dependency" }; + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitMissingDependency }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, result.ResultCode.HResult); + Assert.Equal(2, result.UnitResults.Count); + + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == configurationUnit); + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + + unitResult = result.UnitResults.First(x => x.Unit == configurationUnitMissingDependency); + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, unitResult.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.ConfigurationSet, unitResult.ResultInformation.ResultSource); + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.ConfigurationSet); + } + + /// + /// The configuration set has a dependency cycle. + /// + [Fact] + public void ApplySet_DependencyCycle() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnit1 = this.ConfigurationUnit(); + ConfigurationUnit configurationUnit2 = this.ConfigurationUnit(); + ConfigurationUnit configurationUnit3 = this.ConfigurationUnit(); + configurationUnit1.Identifier = "Identifier1"; + configurationUnit2.Identifier = "Identifier2"; + configurationUnit3.Identifier = "Identifier3"; + configurationUnit1.Dependencies = new string[] { "Identifier3" }; + configurationUnit2.Dependencies = new string[] { "Identifier1" }; + configurationUnit3.Dependencies = new string[] { "Identifier2" }; + configurationSet.Units = new ConfigurationUnit[] { configurationUnit1, configurationUnit2, configurationUnit3 }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE, result.ResultCode.HResult); + Assert.Equal(3, result.UnitResults.Count); + + foreach (var unitResult in result.UnitResults) + { + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, unitResult.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.Precondition, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Precondition); + } + + /// + /// Checks that the intent for configuration units is handled properly. + /// + [Fact] + public void ApplySet_IntentRespected() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitAssert = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert }); + ConfigurationUnit configurationUnitInform = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Inform }); + ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitInform, configurationUnitApply, configurationUnitAssert }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessorAssert = setProcessor.CreateTestProcessor(configurationUnitAssert); + TestConfigurationUnitProcessor unitProcessorInform = setProcessor.CreateTestProcessor(configurationUnitInform); + TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); + unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.Equal(3, result.UnitResults.Count); + + foreach (var unitResult in result.UnitResults) + { + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + Assert.Equal(1, unitProcessorAssert.TestSettingsCalls); + Assert.Equal(0, unitProcessorAssert.GetSettingsCalls); + Assert.Equal(0, unitProcessorAssert.ApplySettingsCalls); + + Assert.Equal(0, unitProcessorInform.TestSettingsCalls); + Assert.Equal(1, unitProcessorInform.GetSettingsCalls); + Assert.Equal(0, unitProcessorInform.ApplySettingsCalls); + + Assert.Equal(1, unitProcessorApply.TestSettingsCalls); + Assert.Equal(0, unitProcessorApply.GetSettingsCalls); + Assert.Equal(1, unitProcessorApply.ApplySettingsCalls); + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// + /// An assertion fails to run. + /// + [Fact] + public void ApplySet_AssertionFailure() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitAssert = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert }); + ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply, configurationUnitAssert }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessorAssert = setProcessor.CreateTestProcessor(configurationUnitAssert); + unitProcessorAssert.TestSettingsDelegate = () => throw new NullReferenceException(); + TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); + unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, result.ResultCode.HResult); + Assert.Equal(2, result.UnitResults.Count); + + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == configurationUnitAssert); + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.IsType(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.Internal, unitResult.ResultInformation.ResultSource); + + unitResult = result.UnitResults.First(x => x.Unit == configurationUnitApply); + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, unitResult.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.Precondition, unitResult.ResultInformation.ResultSource); + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Internal); + } + + /// + /// An assertion is found to be false. + /// + [Fact] + public void ApplySet_AssertionNegative() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitAssert = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert }); + ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply, configurationUnitAssert }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessorAssert = setProcessor.CreateTestProcessor(configurationUnitAssert); + unitProcessorAssert.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitAssert) { TestResult = ConfigurationTestResult.Negative }; + TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); + unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, result.ResultCode.HResult); + Assert.Equal(2, result.UnitResults.Count); + + foreach (var unitResult in result.UnitResults) + { + Assert.NotNull(unitResult); + Assert.False(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_ASSERTION_FAILED, unitResult.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.Precondition, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Precondition); + } + + /// + /// A unit in the correct state is not applied again. + /// + [Fact] + public void ApplySet_UnitAlreadyInCorrectState() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnit = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSet.Units = new ConfigurationUnit[] { configurationUnit }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnit); + unitProcessor.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnit) { TestResult = ConfigurationTestResult.Positive }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.Equal(1, result.UnitResults.Count); + + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(); + Assert.NotNull(unitResult); + Assert.True(unitResult.PreviouslyInDesiredState); + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// + /// Checks the progress reporting. + /// + [Fact] + public void ApplySet_Progress() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit assert1 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert, Identifier = "Assert1" }); + ConfigurationUnit assert2 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Assert, Identifier = "Assert2", Dependencies = new string[] { assert1.Identifier } }); + ConfigurationUnit inform1 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Inform, Identifier = "Inform1" }); + ConfigurationUnit apply1 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply1" }); + ConfigurationUnit apply2 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply2" }); + ConfigurationUnit apply3 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply3", Dependencies = new string[] { apply1.Identifier, apply2.Identifier } }); + ConfigurationUnit apply4 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply4", IsActive = false }); + ConfigurationUnit apply5 = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply, Identifier = "Apply5", Dependencies = new string[] { apply4.Identifier } }); + configurationSet.Units = new ConfigurationUnit[] { assert2, assert1, inform1, apply1, apply3, apply4, apply2, apply5 }; + + ManualResetEvent startProcessing = new ManualResetEvent(false); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.CreateSetProcessorDelegate = (f, c) => + { + startProcessing.WaitOne(); + return f.DefaultCreateSetProcessor(c); + }; + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + List progressEvents = new List(); + + var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); + operation.Progress = (asyncInfo, progress) => progressEvents.Add(progress); + startProcessing.Set(); + operation.AsTask().Wait(); + ApplyConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, result.ResultCode.HResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(configurationSet.Units.Count, result.UnitResults.Count); + + // Verify that progress events match the expected + ExpectedConfigurationChangeData[] expectedProgress = new ExpectedConfigurationChangeData[] + { + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.SetStateChanged, SetState = ConfigurationSetState.InProgress }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = assert1 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = assert1 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = assert2 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = assert2 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = inform1 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = inform1 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = apply1 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = apply1 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Skipped, Unit = apply4, HResult = Errors.WINGET_CONFIG_ERROR_MANUALLY_SKIPPED }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = apply2 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = apply2 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.InProgress, Unit = apply3 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Completed, Unit = apply3 }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.UnitStateChanged, UnitState = ConfigurationUnitState.Skipped, Unit = apply5, HResult = Errors.WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED }, + new ExpectedConfigurationChangeData() { Change = ConfigurationSetChangeEventType.SetStateChanged, SetState = ConfigurationSetState.Completed }, + }; + + // Drop the pending event if it happens to be present + if (progressEvents.Count > 0 && progressEvents[0].Change == ConfigurationSetChangeEventType.SetStateChanged && progressEvents[0].SetState == ConfigurationSetState.Pending) + { + progressEvents.RemoveAt(0); + } + + Assert.Equal(expectedProgress.Count(), progressEvents.Count); + + for (int i = 0; i < progressEvents.Count; ++i) + { + this.Log.WriteLine($"Comparing event {i}"); + Assert.Equal(expectedProgress[i].Change, progressEvents[i].Change); + switch (expectedProgress[i].Change) + { + case ConfigurationSetChangeEventType.SetStateChanged: + Assert.Equal(expectedProgress[i].SetState, progressEvents[i].SetState); + break; + case ConfigurationSetChangeEventType.UnitStateChanged: + Assert.Equal(expectedProgress[i].UnitState, progressEvents[i].UnitState); + Assert.Same(expectedProgress[i].Unit, progressEvents[i].Unit); + + Assert.NotNull(progressEvents[i].ResultInformation); + if (expectedProgress[i].HResult == 0) + { + Assert.Null(progressEvents[i].ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, progressEvents[i].ResultInformation.ResultSource); + } + else + { + Assert.NotNull(progressEvents[i].ResultInformation.ResultCode); + Assert.Equal(expectedProgress[i].HResult, progressEvents[i].ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.Precondition, progressEvents[i].ResultInformation.ResultSource); + } + + break; + default: + Assert.Fail("Unexpected ConfigurationSetChangeEventType value"); + break; + } + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.Precondition); + } + + /// + /// Ensures that multiple apply operations are sequenced. + /// + [Fact] + public void ApplySet_Sequenced() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply }; + + ManualResetEvent startProcessing = new ManualResetEvent(true); + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.CreateSetProcessorDelegate = (f, c) => + { + WaitOn(startProcessing); + return f.DefaultCreateSetProcessor(c); + }; + + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); + unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; + + ManualResetEvent applyEventWaiting = new ManualResetEvent(false); + ManualResetEvent completeApplyEvent = new ManualResetEvent(false); + unitProcessorApply.ApplySettingsDelegate = () => + { + applyEventWaiting.Set(); + WaitOn(completeApplyEvent); + return new ApplySettingsResultInstance(configurationUnitApply); + }; + + ConfigurationSet configurationSetThatWaits = this.ConfigurationSet(); + ConfigurationUnit configurationUnitThatWaits = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSetThatWaits.Units = new ConfigurationUnit[] { configurationUnitThatWaits }; + + TestConfigurationSetProcessor setThatWaitsProcessor = factory.CreateTestProcessor(configurationSetThatWaits); + TestConfigurationUnitProcessor unitThatWaitsProcessor = setProcessor.CreateTestProcessor(configurationUnitThatWaits); + unitThatWaitsProcessor.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; + + ManualResetEvent waitingUnitApply = new ManualResetEvent(false); + unitThatWaitsProcessor.ApplySettingsDelegate = () => + { + WaitOn(waitingUnitApply); + return new ApplySettingsResultInstance(configurationUnitThatWaits); + }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + var applySetOperation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); + WaitOn(applyEventWaiting); + + startProcessing.Reset(); + var waitingSetOperation = processor.ApplySetAsync(configurationSetThatWaits, ApplyConfigurationSetFlags.None); + AutoResetEvent waitingProgress = new AutoResetEvent(false); + ConfigurationSetState progressState = ConfigurationSetState.Unknown; + waitingSetOperation.Progress += (result, changeData) => + { + if (changeData.Change == ConfigurationSetChangeEventType.SetStateChanged) + { + progressState = changeData.SetState; + waitingProgress.Set(); + } + }; + + startProcessing.Set(); + WaitOn(waitingProgress); + Assert.Equal(ConfigurationSetState.Pending, progressState); + + completeApplyEvent.Set(); + WaitOn(waitingProgress); + Assert.Equal(ConfigurationSetState.InProgress, progressState); + + waitingUnitApply.Set(); + WaitOn(waitingProgress); + Assert.Equal(ConfigurationSetState.Completed, progressState); + + waitingSetOperation.AsTask().Wait(); + } + + /// + /// Ensures that a consistency check apply is not blocked. + /// + [Fact] + public void ApplySet_ConsistencyCheckNotSequenced() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitApply = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitApply }; + + ManualResetEvent startProcessing = new ManualResetEvent(true); + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.CreateSetProcessorDelegate = (f, c) => + { + WaitOn(startProcessing); + return f.DefaultCreateSetProcessor(c); + }; + + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessorApply = setProcessor.CreateTestProcessor(configurationUnitApply); + unitProcessorApply.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; + + ManualResetEvent applyEventWaiting = new ManualResetEvent(false); + ManualResetEvent completeApplyEvent = new ManualResetEvent(false); + unitProcessorApply.ApplySettingsDelegate = () => + { + applyEventWaiting.Set(); + WaitOn(completeApplyEvent); + return new ApplySettingsResultInstance(configurationUnitApply); + }; + + ConfigurationSet configurationSetThatWaits = this.ConfigurationSet(); + ConfigurationUnit configurationUnitThatWaits = this.ConfigurationUnit().Assign(new { Intent = ConfigurationUnitIntent.Apply }); + configurationSetThatWaits.Units = new ConfigurationUnit[] { configurationUnitThatWaits }; + + TestConfigurationSetProcessor setThatWaitsProcessor = factory.CreateTestProcessor(configurationSetThatWaits); + TestConfigurationUnitProcessor unitThatWaitsProcessor = setProcessor.CreateTestProcessor(configurationUnitThatWaits); + unitThatWaitsProcessor.TestSettingsDelegate = () => new TestSettingsResultInstance(configurationUnitApply) { TestResult = ConfigurationTestResult.Negative }; + + ManualResetEvent waitingUnitApply = new ManualResetEvent(false); + unitThatWaitsProcessor.ApplySettingsDelegate = () => + { + WaitOn(waitingUnitApply); + return new ApplySettingsResultInstance(configurationUnitThatWaits); + }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + var applySetOperation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); + WaitOn(applyEventWaiting); + + startProcessing.Reset(); + var waitingSetOperation = processor.ApplySetAsync(configurationSetThatWaits, ApplyConfigurationSetFlags.PerformConsistencyCheckOnly); + Assert.True(waitingSetOperation.AsTask().Wait(10000)); + + completeApplyEvent.Set(); + } + + private static void WaitOn(WaitHandle waitable) + { + if (!waitable.WaitOne(10000)) + { + throw new TimeoutException(); + } + } + + private struct ExpectedConfigurationChangeData + { + public ConfigurationSetChangeEventType Change; + public ConfigurationSetState SetState; + public ConfigurationUnitState UnitState; + public int HResult; + public ConfigurationUnit Unit; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs index 571e3aeae7..c09384f770 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorFactoryTests.cs @@ -1,187 +1,187 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor; - using Microsoft.Management.Configuration.Processor.PowerShell.Set; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using WinRT; - using Xunit; - using Xunit.Abstractions; - using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; - - /// - /// Tests ConfigurationProcessorFactory. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationProcessorFactoryTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationProcessorFactoryTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// CreateSetProcessor test. - /// - [Fact] - public void CreateSetProcessor_Test() - { - var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); - - var properties = configurationProcessorFactory.As(); - properties.ProcessorType = PowerShellConfigurationProcessorType.Hosted; - - var configurationSet = new ConfigurationSet(); - - var configurationProcessorSet = configurationProcessorFactory.CreateSetProcessor(configurationSet); - - Assert.NotNull(configurationProcessorSet); - Assert.IsType(configurationProcessorSet); - var processorSet = configurationProcessorSet as PowerShellConfigurationSetProcessor; - Assert.NotNull(processorSet); - } - - /// - /// AdditionalModulePaths test. - /// - [Fact] - public void CreateSetProcessor_Properties_PsModulePath() - { - var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); - - var properties = configurationProcessorFactory.As(); - properties.ProcessorType = PowerShellConfigurationProcessorType.Hosted; - properties.AdditionalModulePaths = new List - { - "ThisIsOnePath", - "ThisIsAnotherPath", - }; - - var configurationSet = new ConfigurationSet(); - - var configurationProcessorSet = configurationProcessorFactory.CreateSetProcessor(configurationSet); - - Assert.IsType(configurationProcessorSet); - var processorSet = configurationProcessorSet as PowerShellConfigurationSetProcessor; - Assert.NotNull(processorSet); - - var modulePath = processorSet.ProcessorEnvironment.GetVariable(Variables.PSModulePath); - Assert.Contains("ThisIsOnePath;ThisIsAnotherPath", modulePath); - } - - /// - /// Make sure the winget path is always added to PSModulePath. - /// - /// Location. - [Theory] - [InlineData(PowerShellConfigurationProcessorLocation.CurrentUser)] - [InlineData(PowerShellConfigurationProcessorLocation.AllUsers)] - [InlineData(PowerShellConfigurationProcessorLocation.WinGetModulePath)] - [InlineData(PowerShellConfigurationProcessorLocation.Custom)] - public void CreateSetProcessor_WinGetPath(PowerShellConfigurationProcessorLocation location) - { - var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); - - var properties = configurationProcessorFactory.As(); - properties.Location = location; - - if (properties.Location == PowerShellConfigurationProcessorLocation.Custom) - { - properties.CustomLocation = @"c:\this\is\a\module\path"; - } - - var configurationSet = new ConfigurationSet(); - - var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; - Assert.NotNull(setProcessor); - - var modulePath = setProcessor.ProcessorEnvironment.GetVariable(Variables.PSModulePath); - Assert.Contains($"{PowerShellConfigurationSetProcessorFactory.GetWinGetModulePath()};", modulePath); - } - - /// - /// Tests the custom location is added successfully. - /// - [Fact] - public void CreateSetProcessor_CustomLocation() - { - var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); - - var properties = configurationProcessorFactory.As(); - properties.Location = PowerShellConfigurationProcessorLocation.Custom; - properties.CustomLocation = @"c:\this\is\a\module\path"; - - var configurationSet = new ConfigurationSet(); - - var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; - Assert.NotNull(setProcessor); - - var modulePath = setProcessor.ProcessorEnvironment.GetVariable(Variables.PSModulePath); - Assert.Contains($"{properties.CustomLocation};", modulePath); - } - - /// - /// Tests the configuration set processor in limitation mode. - /// - [Fact] - public void CreateSetProcessor_LimitMode() - { - var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); - var configurationSet = new ConfigurationSet(); - configurationProcessorFactory.LimitationSet = configurationSet; - - Assert.Throws(() => configurationProcessorFactory.LimitationSet = configurationSet); - Assert.Throws(() => configurationProcessorFactory.ProcessorType = PowerShellConfigurationProcessorType.Default); - Assert.Throws(() => configurationProcessorFactory.AdditionalModulePaths = new List()); - Assert.Throws(() => configurationProcessorFactory.ImplicitModulePaths = new List()); - Assert.Throws(() => configurationProcessorFactory.Policy = PowerShellConfigurationProcessorPolicy.Unrestricted); - Assert.Throws(() => configurationProcessorFactory.Location = PowerShellConfigurationProcessorLocation.Custom); - Assert.Throws(() => configurationProcessorFactory.CustomLocation = @"c:\this\is\a\module\path"); - - var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; - Assert.NotNull(setProcessor); - Assert.True(setProcessor.IsLimitMode); - - // Create processor again in limit mode should fail - Assert.Throws(() => configurationProcessorFactory.CreateSetProcessor(configurationSet)); - } - - /// - /// Tests the configuration set processor factory with ImplicitModulePaths. - /// - [Fact] - public void ImplicitModulePaths() - { - var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); - - // When ImplicitModulePaths module paths are not set - configurationProcessorFactory.AdditionalModulePaths = new List { @"c:\this\is\additional" }; - Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List { @"c:\this\is\additional" }); - - // Implicit ModulePaths are set, it automatically populates AdditionalModulePaths - configurationProcessorFactory.ImplicitModulePaths = new List { @"c:\this\is\implicit" }; - Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List { @"c:\this\is\additional", @"c:\this\is\implicit" }); - - // Set AdditionalModulePaths when ImplicitModulePaths module paths are set - configurationProcessorFactory.AdditionalModulePaths = new List { @"c:\this\is\additional\2" }; - Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List { @"c:\this\is\additional\2", @"c:\this\is\implicit" }); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor; + using Microsoft.Management.Configuration.Processor.PowerShell.Set; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using WinRT; + using Xunit; + using Xunit.Abstractions; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// + /// Tests ConfigurationProcessorFactory. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationProcessorFactoryTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationProcessorFactoryTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// CreateSetProcessor test. + /// + [Fact] + public void CreateSetProcessor_Test() + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + + var properties = configurationProcessorFactory.As(); + properties.ProcessorType = PowerShellConfigurationProcessorType.Hosted; + + var configurationSet = new ConfigurationSet(); + + var configurationProcessorSet = configurationProcessorFactory.CreateSetProcessor(configurationSet); + + Assert.NotNull(configurationProcessorSet); + Assert.IsType(configurationProcessorSet); + var processorSet = configurationProcessorSet as PowerShellConfigurationSetProcessor; + Assert.NotNull(processorSet); + } + + /// + /// AdditionalModulePaths test. + /// + [Fact] + public void CreateSetProcessor_Properties_PsModulePath() + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + + var properties = configurationProcessorFactory.As(); + properties.ProcessorType = PowerShellConfigurationProcessorType.Hosted; + properties.AdditionalModulePaths = new List + { + "ThisIsOnePath", + "ThisIsAnotherPath", + }; + + var configurationSet = new ConfigurationSet(); + + var configurationProcessorSet = configurationProcessorFactory.CreateSetProcessor(configurationSet); + + Assert.IsType(configurationProcessorSet); + var processorSet = configurationProcessorSet as PowerShellConfigurationSetProcessor; + Assert.NotNull(processorSet); + + var modulePath = processorSet.ProcessorEnvironment.GetVariable(Variables.PSModulePath); + Assert.Contains("ThisIsOnePath;ThisIsAnotherPath", modulePath); + } + + /// + /// Make sure the winget path is always added to PSModulePath. + /// + /// Location. + [Theory] + [InlineData(PowerShellConfigurationProcessorLocation.CurrentUser)] + [InlineData(PowerShellConfigurationProcessorLocation.AllUsers)] + [InlineData(PowerShellConfigurationProcessorLocation.WinGetModulePath)] + [InlineData(PowerShellConfigurationProcessorLocation.Custom)] + public void CreateSetProcessor_WinGetPath(PowerShellConfigurationProcessorLocation location) + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + + var properties = configurationProcessorFactory.As(); + properties.Location = location; + + if (properties.Location == PowerShellConfigurationProcessorLocation.Custom) + { + properties.CustomLocation = @"c:\this\is\a\module\path"; + } + + var configurationSet = new ConfigurationSet(); + + var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; + Assert.NotNull(setProcessor); + + var modulePath = setProcessor.ProcessorEnvironment.GetVariable(Variables.PSModulePath); + Assert.Contains($"{PowerShellConfigurationSetProcessorFactory.GetWinGetModulePath()};", modulePath); + } + + /// + /// Tests the custom location is added successfully. + /// + [Fact] + public void CreateSetProcessor_CustomLocation() + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + + var properties = configurationProcessorFactory.As(); + properties.Location = PowerShellConfigurationProcessorLocation.Custom; + properties.CustomLocation = @"c:\this\is\a\module\path"; + + var configurationSet = new ConfigurationSet(); + + var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; + Assert.NotNull(setProcessor); + + var modulePath = setProcessor.ProcessorEnvironment.GetVariable(Variables.PSModulePath); + Assert.Contains($"{properties.CustomLocation};", modulePath); + } + + /// + /// Tests the configuration set processor in limitation mode. + /// + [Fact] + public void CreateSetProcessor_LimitMode() + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + var configurationSet = new ConfigurationSet(); + configurationProcessorFactory.LimitationSet = configurationSet; + + Assert.Throws(() => configurationProcessorFactory.LimitationSet = configurationSet); + Assert.Throws(() => configurationProcessorFactory.ProcessorType = PowerShellConfigurationProcessorType.Default); + Assert.Throws(() => configurationProcessorFactory.AdditionalModulePaths = new List()); + Assert.Throws(() => configurationProcessorFactory.ImplicitModulePaths = new List()); + Assert.Throws(() => configurationProcessorFactory.Policy = PowerShellConfigurationProcessorPolicy.Unrestricted); + Assert.Throws(() => configurationProcessorFactory.Location = PowerShellConfigurationProcessorLocation.Custom); + Assert.Throws(() => configurationProcessorFactory.CustomLocation = @"c:\this\is\a\module\path"); + + var setProcessor = configurationProcessorFactory.CreateSetProcessor(configurationSet) as PowerShellConfigurationSetProcessor; + Assert.NotNull(setProcessor); + Assert.True(setProcessor.IsLimitMode); + + // Create processor again in limit mode should fail + Assert.Throws(() => configurationProcessorFactory.CreateSetProcessor(configurationSet)); + } + + /// + /// Tests the configuration set processor factory with ImplicitModulePaths. + /// + [Fact] + public void ImplicitModulePaths() + { + var configurationProcessorFactory = new PowerShellConfigurationSetProcessorFactory(); + + // When ImplicitModulePaths module paths are not set + configurationProcessorFactory.AdditionalModulePaths = new List { @"c:\this\is\additional" }; + Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List { @"c:\this\is\additional" }); + + // Implicit ModulePaths are set, it automatically populates AdditionalModulePaths + configurationProcessorFactory.ImplicitModulePaths = new List { @"c:\this\is\implicit" }; + Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List { @"c:\this\is\additional", @"c:\this\is\implicit" }); + + // Set AdditionalModulePaths when ImplicitModulePaths module paths are set + configurationProcessorFactory.AdditionalModulePaths = new List { @"c:\this\is\additional\2" }; + Assert.Equal(configurationProcessorFactory.AdditionalModulePaths, new List { @"c:\this\is\additional\2", @"c:\this\is\implicit" }); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetAllTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetAllTests.cs index a552c0df6b..a841994b00 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetAllTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetAllTests.cs @@ -1,147 +1,147 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System.Collections.Generic; - using System.IO; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Windows.Foundation.Collections; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for getting details on processors. - /// - [Collection("UnitTestCollection")] - [InProc] - [OutOfProc] - public class ConfigurationProcessorGetAllTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationProcessorGetAllTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// The unit settings processor returns an error HRESULT. - /// - [Fact] - public void GetAllSettings_ProcessorSettingsError() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - var unitProcessor = factory.NullProcessor.CreateGetAllSettingsTestProcessor(configurationUnit); - unitProcessor.GetAllSettingsDelegate = () => throw new FileNotFoundException(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); - - Assert.NotNull(result); - Assert.Null(result.Settings); - Assert.NotNull(result.ResultInformation); - Assert.NotNull(result.ResultInformation.ResultCode); - Assert.IsType(result.ResultInformation.ResultCode); - } - - /// - /// The unit settings processor returns an error in its result. - /// - [Fact] - public void GetAllSettings_ProcessorSettingsFailedResult() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - var unitProcessor = factory.NullProcessor.CreateGetAllSettingsTestProcessor(configurationUnit); - - GetAllSettingsResultInstance getAllSettingsResult = new GetAllSettingsResultInstance(configurationUnit); - getAllSettingsResult.InternalResult.ResultCode = new InvalidDataException(); - getAllSettingsResult.InternalResult.Description = "We fail because we must"; - unitProcessor.GetAllSettingsDelegate = () => getAllSettingsResult; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); - - Assert.NotNull(result); - Assert.Null(result.Settings); - Assert.NotNull(result.ResultInformation); - Assert.Equal(getAllSettingsResult.ResultInformation.ResultCode.HResult, result.ResultInformation.ResultCode.HResult); - Assert.Equal(getAllSettingsResult.ResultInformation.Description, result.ResultInformation.Description); - } - - /// - /// The unit settings processor returns good settings. - /// - [Fact] - public void GetAllSettings_ProcessorSettingsSuccess() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - var unitProcessor = factory.NullProcessor.CreateGetAllSettingsTestProcessor(configurationUnit); - - GetAllSettingsResultInstance getAllSettingsResult = new GetAllSettingsResultInstance(configurationUnit); - getAllSettingsResult.Settings = new List() - { - new ValueSet - { - { "key", "value" }, - }, - }; - unitProcessor.GetAllSettingsDelegate = () => getAllSettingsResult; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); - - Assert.NotNull(result); - Assert.NotNull(result.ResultInformation); - Assert.Null(result.ResultInformation.ResultCode); - Assert.Empty(result.ResultInformation.Description); - Assert.NotNull(result.Settings); - Assert.Equal(1, result.Settings.Count); - Assert.Contains("key", result.Settings[0]); - Assert.IsType(result.Settings[0]["key"]); - Assert.Equal("value", (string)result.Settings[0]["key"]); - } - - /// - /// The unit settings processor does not support GetAllSettings. - /// - [Fact] - public void GetAllSettings_UnsupportedByProcessor() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - var unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); - - Assert.NotNull(result); - Assert.NotNull(result.ResultInformation); - Assert.NotNull(result.ResultInformation.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, result.ResultInformation.ResultCode.HResult); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System.Collections.Generic; + using System.IO; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Windows.Foundation.Collections; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for getting details on processors. + /// + [Collection("UnitTestCollection")] + [InProc] + [OutOfProc] + public class ConfigurationProcessorGetAllTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationProcessorGetAllTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// The unit settings processor returns an error HRESULT. + /// + [Fact] + public void GetAllSettings_ProcessorSettingsError() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + var unitProcessor = factory.NullProcessor.CreateGetAllSettingsTestProcessor(configurationUnit); + unitProcessor.GetAllSettingsDelegate = () => throw new FileNotFoundException(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); + + Assert.NotNull(result); + Assert.Null(result.Settings); + Assert.NotNull(result.ResultInformation); + Assert.NotNull(result.ResultInformation.ResultCode); + Assert.IsType(result.ResultInformation.ResultCode); + } + + /// + /// The unit settings processor returns an error in its result. + /// + [Fact] + public void GetAllSettings_ProcessorSettingsFailedResult() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + var unitProcessor = factory.NullProcessor.CreateGetAllSettingsTestProcessor(configurationUnit); + + GetAllSettingsResultInstance getAllSettingsResult = new GetAllSettingsResultInstance(configurationUnit); + getAllSettingsResult.InternalResult.ResultCode = new InvalidDataException(); + getAllSettingsResult.InternalResult.Description = "We fail because we must"; + unitProcessor.GetAllSettingsDelegate = () => getAllSettingsResult; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); + + Assert.NotNull(result); + Assert.Null(result.Settings); + Assert.NotNull(result.ResultInformation); + Assert.Equal(getAllSettingsResult.ResultInformation.ResultCode.HResult, result.ResultInformation.ResultCode.HResult); + Assert.Equal(getAllSettingsResult.ResultInformation.Description, result.ResultInformation.Description); + } + + /// + /// The unit settings processor returns good settings. + /// + [Fact] + public void GetAllSettings_ProcessorSettingsSuccess() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + var unitProcessor = factory.NullProcessor.CreateGetAllSettingsTestProcessor(configurationUnit); + + GetAllSettingsResultInstance getAllSettingsResult = new GetAllSettingsResultInstance(configurationUnit); + getAllSettingsResult.Settings = new List() + { + new ValueSet + { + { "key", "value" }, + }, + }; + unitProcessor.GetAllSettingsDelegate = () => getAllSettingsResult; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + Assert.Empty(result.ResultInformation.Description); + Assert.NotNull(result.Settings); + Assert.Equal(1, result.Settings.Count); + Assert.Contains("key", result.Settings[0]); + Assert.IsType(result.Settings[0]["key"]); + Assert.Equal("value", (string)result.Settings[0]["key"]); + } + + /// + /// The unit settings processor does not support GetAllSettings. + /// + [Fact] + public void GetAllSettings_UnsupportedByProcessor() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + var unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetAllConfigurationUnitSettingsResult result = processor.GetAllUnitSettings(configurationUnit); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.NotNull(result.ResultInformation.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, result.ResultInformation.ResultCode.HResult); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetTests.cs index e628ab8516..adaeb9ad84 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGetTests.cs @@ -1,207 +1,207 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System.IO; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for getting details on processors. - /// - [Collection("UnitTestCollection")] - [InProc] - [OutOfProc] - public class ConfigurationProcessorGetTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationProcessorGetTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// Getting unit details throws an error. - /// - [Fact] - public void GetUnitDetailsError() - { - ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - var thrownException = new FileNotFoundException(); - factory.NullProcessor.Exceptions.Add(configurationUnitThrows, thrownException); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetConfigurationUnitDetailsResult result = processor.GetUnitDetails(configurationUnitThrows, ConfigurationUnitDetailFlags.Local); - - Assert.Null(result.Details); - Assert.Equal(configurationUnitThrows, result.Unit); - Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); - Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); - } - - /// - /// Getting unit details retrieves a value. - /// - [Fact] - public void GetUnitDetailsSuccess() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - Assert.Null(configurationUnit.Details); - processor.GetUnitDetails(configurationUnit, ConfigurationUnitDetailFlags.Local); - Assert.NotNull(configurationUnit.Details); - } - - /// - /// Getting set details throws an error. - /// - [Fact] - public void GetSetDetailsError() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitWorks, configurationUnitThrows }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - var thrownException = new InvalidDataException(); - setProcessor.Exceptions.Add(configurationUnitThrows, thrownException); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetConfigurationSetDetailsResult result = processor.GetSetDetails(configurationSet, ConfigurationUnitDetailFlags.Local); - var unitResults = result.UnitResults; - Assert.Equal(2, unitResults.Count); - - Assert.Equal(configurationUnitWorks, unitResults[0].Unit); - Assert.Null(unitResults[0].ResultInformation.ResultCode); - Assert.NotNull(configurationUnitWorks.Details); - - Assert.Equal(configurationUnitThrows, unitResults[1].Unit); - Assert.NotNull(unitResults[1].ResultInformation.ResultCode); - Assert.Equal(thrownException.HResult, unitResults[1].ResultInformation.ResultCode.HResult); - Assert.Null(configurationUnitThrows.Details); - } - - /// - /// Getting set details retrieves all values. - /// - [Fact] - public void GetSetDetailsSuccess() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnit1 = this.ConfigurationUnit(); - ConfigurationUnit configurationUnit2 = this.ConfigurationUnit(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnit1, configurationUnit2 }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - processor.GetSetDetails(configurationSet, ConfigurationUnitDetailFlags.Local); - Assert.NotNull(configurationUnit1.Details); - Assert.NotNull(configurationUnit2.Details); - } - - /// - /// The unit settings processor returns an error HRESULT. - /// - [Fact] - public void GetSettings_ProcessorSettingsError() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - TestConfigurationUnitProcessor unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); - unitProcessor.GetSettingsDelegate = () => throw new FileNotFoundException(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetConfigurationUnitSettingsResult result = processor.GetUnitSettings(configurationUnit); - - Assert.NotNull(result); - Assert.NotNull(result.Settings); - Assert.NotNull(result.ResultInformation); - Assert.NotNull(result.ResultInformation.ResultCode); - Assert.IsType(result.ResultInformation.ResultCode); - } - - /// - /// The unit settings processor returns an error in it's result. - /// - [Fact] - public void GetSettings_ProcessorSettingsFailedResult() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - TestConfigurationUnitProcessor unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); - GetSettingsResultInstance getSettingsResult = new GetSettingsResultInstance(configurationUnit); - getSettingsResult.InternalResult.ResultCode = new InvalidDataException(); - getSettingsResult.InternalResult.Description = "We fail because we must"; - unitProcessor.GetSettingsDelegate = () => getSettingsResult; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetConfigurationUnitSettingsResult result = processor.GetUnitSettings(configurationUnit); - - Assert.NotNull(result); - Assert.Null(result.Settings); - Assert.NotNull(result.ResultInformation); - Assert.Equal(getSettingsResult.ResultInformation.ResultCode.HResult, result.ResultInformation.ResultCode.HResult); - Assert.Equal(getSettingsResult.ResultInformation.Description, result.ResultInformation.Description); - } - - /// - /// The unit settings processor returns good settings. - /// - [Fact] - public void GetSettings_ProcessorSettingsSuccess() - { - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.NullProcessor = new TestConfigurationSetProcessor(null); - TestConfigurationUnitProcessor unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); - GetSettingsResultInstance getSettingsResult = new GetSettingsResultInstance(configurationUnit); - getSettingsResult.Settings = new Windows.Foundation.Collections.ValueSet(); - getSettingsResult.Settings.Add("key", "value"); - unitProcessor.GetSettingsDelegate = () => getSettingsResult; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - GetConfigurationUnitSettingsResult result = processor.GetUnitSettings(configurationUnit); - - Assert.NotNull(result); - Assert.NotNull(result.ResultInformation); - Assert.Null(result.ResultInformation.ResultCode); - Assert.Empty(result.ResultInformation.Description); - Assert.NotNull(result.Settings); - Assert.NotEmpty(result.Settings); - Assert.Contains("key", result.Settings); - Assert.IsType(result.Settings["key"]); - Assert.Equal("value", (string)result.Settings["key"]); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System.IO; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for getting details on processors. + /// + [Collection("UnitTestCollection")] + [InProc] + [OutOfProc] + public class ConfigurationProcessorGetTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationProcessorGetTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// Getting unit details throws an error. + /// + [Fact] + public void GetUnitDetailsError() + { + ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + var thrownException = new FileNotFoundException(); + factory.NullProcessor.Exceptions.Add(configurationUnitThrows, thrownException); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetConfigurationUnitDetailsResult result = processor.GetUnitDetails(configurationUnitThrows, ConfigurationUnitDetailFlags.Local); + + Assert.Null(result.Details); + Assert.Equal(configurationUnitThrows, result.Unit); + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); + } + + /// + /// Getting unit details retrieves a value. + /// + [Fact] + public void GetUnitDetailsSuccess() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + Assert.Null(configurationUnit.Details); + processor.GetUnitDetails(configurationUnit, ConfigurationUnitDetailFlags.Local); + Assert.NotNull(configurationUnit.Details); + } + + /// + /// Getting set details throws an error. + /// + [Fact] + public void GetSetDetailsError() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitWorks, configurationUnitThrows }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + var thrownException = new InvalidDataException(); + setProcessor.Exceptions.Add(configurationUnitThrows, thrownException); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetConfigurationSetDetailsResult result = processor.GetSetDetails(configurationSet, ConfigurationUnitDetailFlags.Local); + var unitResults = result.UnitResults; + Assert.Equal(2, unitResults.Count); + + Assert.Equal(configurationUnitWorks, unitResults[0].Unit); + Assert.Null(unitResults[0].ResultInformation.ResultCode); + Assert.NotNull(configurationUnitWorks.Details); + + Assert.Equal(configurationUnitThrows, unitResults[1].Unit); + Assert.NotNull(unitResults[1].ResultInformation.ResultCode); + Assert.Equal(thrownException.HResult, unitResults[1].ResultInformation.ResultCode.HResult); + Assert.Null(configurationUnitThrows.Details); + } + + /// + /// Getting set details retrieves all values. + /// + [Fact] + public void GetSetDetailsSuccess() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnit1 = this.ConfigurationUnit(); + ConfigurationUnit configurationUnit2 = this.ConfigurationUnit(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnit1, configurationUnit2 }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + processor.GetSetDetails(configurationSet, ConfigurationUnitDetailFlags.Local); + Assert.NotNull(configurationUnit1.Details); + Assert.NotNull(configurationUnit2.Details); + } + + /// + /// The unit settings processor returns an error HRESULT. + /// + [Fact] + public void GetSettings_ProcessorSettingsError() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + TestConfigurationUnitProcessor unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); + unitProcessor.GetSettingsDelegate = () => throw new FileNotFoundException(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetConfigurationUnitSettingsResult result = processor.GetUnitSettings(configurationUnit); + + Assert.NotNull(result); + Assert.NotNull(result.Settings); + Assert.NotNull(result.ResultInformation); + Assert.NotNull(result.ResultInformation.ResultCode); + Assert.IsType(result.ResultInformation.ResultCode); + } + + /// + /// The unit settings processor returns an error in it's result. + /// + [Fact] + public void GetSettings_ProcessorSettingsFailedResult() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + TestConfigurationUnitProcessor unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); + GetSettingsResultInstance getSettingsResult = new GetSettingsResultInstance(configurationUnit); + getSettingsResult.InternalResult.ResultCode = new InvalidDataException(); + getSettingsResult.InternalResult.Description = "We fail because we must"; + unitProcessor.GetSettingsDelegate = () => getSettingsResult; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetConfigurationUnitSettingsResult result = processor.GetUnitSettings(configurationUnit); + + Assert.NotNull(result); + Assert.Null(result.Settings); + Assert.NotNull(result.ResultInformation); + Assert.Equal(getSettingsResult.ResultInformation.ResultCode.HResult, result.ResultInformation.ResultCode.HResult); + Assert.Equal(getSettingsResult.ResultInformation.Description, result.ResultInformation.Description); + } + + /// + /// The unit settings processor returns good settings. + /// + [Fact] + public void GetSettings_ProcessorSettingsSuccess() + { + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.NullProcessor = new TestConfigurationSetProcessor(null); + TestConfigurationUnitProcessor unitProcessor = factory.NullProcessor.CreateTestProcessor(configurationUnit); + GetSettingsResultInstance getSettingsResult = new GetSettingsResultInstance(configurationUnit); + getSettingsResult.Settings = new Windows.Foundation.Collections.ValueSet(); + getSettingsResult.Settings.Add("key", "value"); + unitProcessor.GetSettingsDelegate = () => getSettingsResult; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + GetConfigurationUnitSettingsResult result = processor.GetUnitSettings(configurationUnit); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + Assert.Empty(result.ResultInformation.Description); + Assert.NotNull(result.Settings); + Assert.NotEmpty(result.Settings); + Assert.Contains("key", result.Settings); + Assert.IsType(result.Settings["key"]); + Assert.Equal("value", (string)result.Settings["key"]); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs index 440c0df320..1e44dcfd5b 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorGroupTests.cs @@ -1,655 +1,655 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for running group processing. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationProcessorGroupTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationProcessorGroupTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// Test a set that was parsed with schema 0.3. - /// - [Fact] - public void TestSet_Parsed_0_3() - { - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -resources: - - name: Name - type: Module/Resource - properties: - c: 3 - d: '4' - - name: Name2 - type: Module/Resource2 - properties: - l: '10' -")); - - Assert.Null(openResult.ResultCode); - Assert.NotNull(openResult.Set); - Assert.Equal(string.Empty, openResult.Field); - Assert.Equal(string.Empty, openResult.Value); - Assert.Equal(0U, openResult.Line); - Assert.Equal(0U, openResult.Column); - - ConfigurationSet configurationSet = openResult.Set; - int unitCount = configurationSet.Units.Count; - - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - - TestConfigurationUnitProcessor[] unitProcessors = new TestConfigurationUnitProcessor[unitCount]; - for (int i = 0; i < unitCount; ++i) - { - unitProcessors[i] = setProcessor.CreateTestProcessor(configurationSet.Units[i]); - if (i == 0) - { - unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Negative }; - } - else - { - unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Positive }; - } - } - - TestConfigurationSetResult result = processor.TestSet(configurationSet); - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Negative, result.TestResult); - Assert.Equal(unitCount, result.UnitResults.Count); - - for (int i = 0; i < unitCount; ++i) - { - var unitResult = result.UnitResults[i]; - Assert.NotNull(unitResult); - - if (i == 0) - { - Assert.Equal(ConfigurationTestResult.Negative, unitResult.TestResult); - } - else - { - Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); - } - - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the set processor is a group processor. - /// - [Fact] - public void TestSet_SetGroupProcessor() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - ConfigurationUnit configurationUnitNegative = this.ConfigurationUnit(); - configurationUnitNegative.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - ConfigurationUnit configurationUnitPositive = this.ConfigurationUnit(); - configurationUnitPositive.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Positive.ToString(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitNegative, configurationUnitPositive }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); - setProcessor.ShouldWaitOnAsyncEvent = true; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - List progressValues = new List(); - - var operation = processor.TestSetAsync(configurationSet); - operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, TestConfigurationUnitResult unitResult) => { progressValues.Add(unitResult); }; - setProcessor.SignalAsyncEvent(); - - operation.AsTask().Wait(); - TestConfigurationSetResult result = operation.GetResults(); - - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Negative, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(2, result.UnitResults.Count); - Assert.Equal(2, progressValues.Count); - - TestConfigurationUnitResult negativeResult = result.UnitResults.First(x => x.Unit == configurationUnitNegative); - TestConfigurationUnitResult negativeProgress = progressValues.First(x => x.Unit == configurationUnitNegative); - - foreach (TestConfigurationUnitResult unitResult in new TestConfigurationUnitResult[] { negativeResult, negativeProgress }) - { - Assert.NotNull(unitResult); - Assert.Equal(ConfigurationTestResult.Negative, unitResult.TestResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - TestConfigurationUnitResult positiveResult = result.UnitResults.First(x => x.Unit == configurationUnitPositive); - TestConfigurationUnitResult positiveProgress = progressValues.First(x => x.Unit == configurationUnitPositive); - - foreach (TestConfigurationUnitResult unitResult in new TestConfigurationUnitResult[] { positiveResult, positiveProgress }) - { - Assert.NotNull(unitResult); - Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the set processor is a group processor and contains a unit that is also a group. - /// - [Fact] - public void TestSet_SetGroupProcessor_WithGroupUnit() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); - - configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; - - ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); - configurationUnitGroup.IsGroup = true; - configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); - setProcessor.ShouldWaitOnAsyncEvent = true; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - List progressValues = new List(); - - var operation = processor.TestSetAsync(configurationSet); - operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, TestConfigurationUnitResult unitResult) => { progressValues.Add(unitResult); }; - setProcessor.SignalAsyncEvent(); - - operation.AsTask().Wait(); - TestConfigurationSetResult result = operation.GetResults(); - - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(3, result.UnitResults.Count); - Assert.Equal(3, progressValues.Count); - - foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) - { - foreach (IReadOnlyList unitResults in new IReadOnlyList[] { result.UnitResults, progressValues }) - { - TestConfigurationUnitResult unitResult = unitResults.First(x => x.Unit == unit); - - Assert.NotNull(unitResult); - Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - } - - this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the standard set processor is used and there is a group unit. - /// - [Fact] - public void TestSet_UnitGroupProcessor_WithGroupUnit() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); - - configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; - - ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); - configurationUnitGroup.IsGroup = true; - configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - setProcessor.CreateTestProcessor(configurationUnit); - setProcessor.CreateTestGroupProcessor(configurationUnitGroup); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - TestConfigurationSetResult result = processor.TestSet(configurationSet); - - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(3, result.UnitResults.Count); - - foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) - { - TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); - - Assert.NotNull(unitResult); - Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the standard set processor is used and there is a non-group unit that still exposes a group processor. - /// - [Fact] - public void TestSet_UnitGroupProcessor_WithNonGroupUnit() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); - - configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - setProcessor.CreateTestProcessor(configurationUnit); - setProcessor.CreateTestGroupProcessor(configurationUnitGroup); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - TestConfigurationSetResult result = processor.TestSet(configurationSet); - - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(2, result.UnitResults.Count); - - foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }) - { - TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); - - Assert.NotNull(unitResult); - Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); - } - - /// - /// Apply a set that was parsed with schema 0.3. - /// - [Fact] - public void ApplySet_Parsed_0_3() - { - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -resources: - - name: Name - type: Module/Resource - properties: - c: 3 - d: '4' - - name: Name2 - type: Module/Resource2 - properties: - l: '10' -")); - - Assert.Null(openResult.ResultCode); - Assert.NotNull(openResult.Set); - Assert.Equal(string.Empty, openResult.Field); - Assert.Equal(string.Empty, openResult.Value); - Assert.Equal(0U, openResult.Line); - Assert.Equal(0U, openResult.Column); - - ConfigurationSet configurationSet = openResult.Set; - int unitCount = configurationSet.Units.Count; - - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - - TestConfigurationUnitProcessor[] unitProcessors = new TestConfigurationUnitProcessor[unitCount]; - for (int i = 0; i < unitCount; ++i) - { - unitProcessors[i] = setProcessor.CreateTestProcessor(configurationSet.Units[i]); - if (i == 0) - { - unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Negative }; - } - else - { - unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Positive }; - } - } - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.Equal(unitCount, result.UnitResults.Count); - - for (int i = 0; i < unitCount; ++i) - { - var unitResult = result.UnitResults[i]; - Assert.NotNull(unitResult); - - if (i == 0) - { - Assert.False(unitResult.PreviouslyInDesiredState); - } - else - { - Assert.True(unitResult.PreviouslyInDesiredState); - } - - Assert.False(unitResult.RebootRequired); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - } - - for (int i = 0; i < unitCount; ++i) - { - Assert.Equal(1, unitProcessors[i].TestSettingsCalls); - Assert.Equal(0, unitProcessors[i].GetSettingsCalls); - if (i == 0) - { - Assert.Equal(1, unitProcessors[i].ApplySettingsCalls); - } - else - { - Assert.Equal(0, unitProcessors[i].ApplySettingsCalls); - } - } - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the set processor is a group processor. - /// - [Fact] - public void ApplySet_SetGroupProcessor() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - - ConfigurationUnit configurationUnitNegative = this.ConfigurationUnit(); - configurationUnitNegative.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - ConfigurationUnit configurationUnitPositive = this.ConfigurationUnit(); - configurationUnitPositive.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Positive.ToString(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitNegative, configurationUnitPositive }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - ManualResetEvent startProcessing = new ManualResetEvent(false); - factory.CreateSetProcessorDelegate = (f, c) => - { - startProcessing.WaitOne(); - return f.CreateTestGroupProcessor(c); - }; - - var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); - - List progressValues = new List(); - operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, ConfigurationSetChangeData unitResult) => { progressValues.Add(unitResult); }; - startProcessing.Set(); - operation.AsTask().Wait(); - ApplyConfigurationSetResult result = operation.GetResults(); - - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.NotNull(result.UnitResults); - Assert.Equal(configurationSet.Units.Count, result.UnitResults.Count); - Assert.Equal((configurationSet.Units.Count * 2) + 2, progressValues.Count); - - ApplyConfigurationUnitResult negativeResult = result.UnitResults.First(x => x.Unit == configurationUnitNegative); - - Assert.NotNull(negativeResult); - Assert.NotNull(negativeResult.ResultInformation); - Assert.Null(negativeResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, negativeResult.ResultInformation.ResultSource); - Assert.Equal(ConfigurationUnitState.Completed, negativeResult.State); - Assert.False(negativeResult.PreviouslyInDesiredState); - - IEnumerable negativeProgress = progressValues.Where(x => x.Unit == configurationUnitNegative); - Assert.Equal(2, negativeProgress.Count()); - - foreach (ConfigurationSetChangeData change in negativeProgress) - { - Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); - Assert.Equal(ConfigurationSetState.InProgress, change.SetState); - Assert.NotNull(change.ResultInformation); - Assert.Null(change.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); - } - - Assert.Single(negativeProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress)); - Assert.Single(negativeProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed)); - - ApplyConfigurationUnitResult positiveResult = result.UnitResults.First(x => x.Unit == configurationUnitPositive); - - Assert.NotNull(positiveResult); - Assert.NotNull(positiveResult.ResultInformation); - Assert.Null(positiveResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, positiveResult.ResultInformation.ResultSource); - Assert.Equal(ConfigurationUnitState.Completed, positiveResult.State); - Assert.True(positiveResult.PreviouslyInDesiredState); - - IEnumerable positiveProgress = progressValues.Where(x => x.Unit == configurationUnitPositive); - Assert.Equal(2, positiveProgress.Count()); - - foreach (ConfigurationSetChangeData change in positiveProgress) - { - Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); - Assert.Equal(ConfigurationSetState.InProgress, change.SetState); - Assert.NotNull(change.ResultInformation); - Assert.Null(change.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); - } - - Assert.Single(positiveProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress)); - Assert.Single(positiveProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed)); - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the set processor is a group processor and contains a unit that is also a group. - /// - [Fact] - public void ApplySet_SetGroupProcessor_WithGroupUnit() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); - - configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; - - ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); - configurationUnitGroup.IsGroup = true; - configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - ManualResetEvent startProcessing = new ManualResetEvent(false); - factory.CreateSetProcessorDelegate = (f, c) => - { - startProcessing.WaitOne(); - return f.CreateTestGroupProcessor(c); - }; - - var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); - - List progressValues = new List(); - operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, ConfigurationSetChangeData unitResult) => { progressValues.Add(unitResult); }; - startProcessing.Set(); - operation.AsTask().Wait(); - ApplyConfigurationSetResult result = operation.GetResults(); - - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.NotNull(result.UnitResults); - Assert.Equal(3, result.UnitResults.Count); - Assert.Equal(2 + (3 * 2), progressValues.Count); - - foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) - { - ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); - - Assert.NotNull(unitResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); - Assert.True(unitResult.PreviouslyInDesiredState); - - IEnumerable unitProgress = progressValues.Where(x => x.Unit == unit); - Assert.Equal(2, unitProgress.Count()); - - foreach (ConfigurationSetChangeData change in unitProgress) - { - Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); - Assert.Equal(ConfigurationSetState.InProgress, change.SetState); - Assert.NotNull(change.ResultInformation); - Assert.Null(change.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); - } - - Assert.True(unitProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress).Count() <= 1); - Assert.True(unitProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed).Count() <= 1); - } - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the standard set processor is used and there is a group unit. - /// - [Fact] - public void ApplySet_UnitGroupProcessor_WithGroupUnit() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); - - configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; - - ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); - configurationUnitGroup.IsGroup = true; - configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - setProcessor.CreateTestProcessor(configurationUnit); - setProcessor.CreateTestGroupProcessor(configurationUnitGroup); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.NotNull(result.UnitResults); - Assert.Equal(3, result.UnitResults.Count); - - foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) - { - ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); - - Assert.NotNull(unitResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); - Assert.True(unitResult.PreviouslyInDesiredState); - } - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); - } - - /// - /// Test when the standard set processor is used and there is a non-group unit that still exposes a group processor. - /// - [Fact] - public void ApplySet_UnitGroupProcessor_WithNonGroupUnit() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); - - ConfigurationUnit configurationUnit = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); - - configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - setProcessor.CreateTestProcessor(configurationUnit); - setProcessor.CreateTestGroupProcessor(configurationUnitGroup); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); - - Assert.NotNull(result); - Assert.Null(result.ResultCode); - Assert.NotNull(result.UnitResults); - Assert.Equal(2, result.UnitResults.Count); - - foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }) - { - ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); - - Assert.NotNull(unitResult); - Assert.NotNull(unitResult.ResultInformation); - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); - Assert.True(unitResult.PreviouslyInDesiredState); - } - - this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for running group processing. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationProcessorGroupTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationProcessorGroupTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// Test a set that was parsed with schema 0.3. + /// + [Fact] + public void TestSet_Parsed_0_3() + { + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: Name + type: Module/Resource + properties: + c: 3 + d: '4' + - name: Name2 + type: Module/Resource2 + properties: + l: '10' +")); + + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + Assert.Equal(string.Empty, openResult.Field); + Assert.Equal(string.Empty, openResult.Value); + Assert.Equal(0U, openResult.Line); + Assert.Equal(0U, openResult.Column); + + ConfigurationSet configurationSet = openResult.Set; + int unitCount = configurationSet.Units.Count; + + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + + TestConfigurationUnitProcessor[] unitProcessors = new TestConfigurationUnitProcessor[unitCount]; + for (int i = 0; i < unitCount; ++i) + { + unitProcessors[i] = setProcessor.CreateTestProcessor(configurationSet.Units[i]); + if (i == 0) + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Negative }; + } + else + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Positive }; + } + } + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Negative, result.TestResult); + Assert.Equal(unitCount, result.UnitResults.Count); + + for (int i = 0; i < unitCount; ++i) + { + var unitResult = result.UnitResults[i]; + Assert.NotNull(unitResult); + + if (i == 0) + { + Assert.Equal(ConfigurationTestResult.Negative, unitResult.TestResult); + } + else + { + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + } + + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the set processor is a group processor. + /// + [Fact] + public void TestSet_SetGroupProcessor() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + ConfigurationUnit configurationUnitNegative = this.ConfigurationUnit(); + configurationUnitNegative.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + ConfigurationUnit configurationUnitPositive = this.ConfigurationUnit(); + configurationUnitPositive.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Positive.ToString(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitNegative, configurationUnitPositive }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); + setProcessor.ShouldWaitOnAsyncEvent = true; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + List progressValues = new List(); + + var operation = processor.TestSetAsync(configurationSet); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, TestConfigurationUnitResult unitResult) => { progressValues.Add(unitResult); }; + setProcessor.SignalAsyncEvent(); + + operation.AsTask().Wait(); + TestConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Negative, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + Assert.Equal(2, progressValues.Count); + + TestConfigurationUnitResult negativeResult = result.UnitResults.First(x => x.Unit == configurationUnitNegative); + TestConfigurationUnitResult negativeProgress = progressValues.First(x => x.Unit == configurationUnitNegative); + + foreach (TestConfigurationUnitResult unitResult in new TestConfigurationUnitResult[] { negativeResult, negativeProgress }) + { + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Negative, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + TestConfigurationUnitResult positiveResult = result.UnitResults.First(x => x.Unit == configurationUnitPositive); + TestConfigurationUnitResult positiveProgress = progressValues.First(x => x.Unit == configurationUnitPositive); + + foreach (TestConfigurationUnitResult unitResult in new TestConfigurationUnitResult[] { positiveResult, positiveProgress }) + { + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the set processor is a group processor and contains a unit that is also a group. + /// + [Fact] + public void TestSet_SetGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetGroupProcessor setProcessor = factory.CreateTestGroupProcessor(configurationSet); + setProcessor.ShouldWaitOnAsyncEvent = true; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + List progressValues = new List(); + + var operation = processor.TestSetAsync(configurationSet); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, TestConfigurationUnitResult unitResult) => { progressValues.Add(unitResult); }; + setProcessor.SignalAsyncEvent(); + + operation.AsTask().Wait(); + TestConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + Assert.Equal(3, progressValues.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + foreach (IReadOnlyList unitResults in new IReadOnlyList[] { result.UnitResults, progressValues }) + { + TestConfigurationUnitResult unitResult = unitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the standard set processor is used and there is a group unit. + /// + [Fact] + public void TestSet_UnitGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the standard set processor is used and there is a non-group unit that still exposes a group processor. + /// + [Fact] + public void TestSet_UnitGroupProcessor_WithNonGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Positive, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }) + { + TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.Equal(ConfigurationTestResult.Positive, unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + this.VerifySummaryEvent(configurationSet, result, 0, ConfigurationUnitResultSource.None); + } + + /// + /// Apply a set that was parsed with schema 0.3. + /// + [Fact] + public void ApplySet_Parsed_0_3() + { + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: Name + type: Module/Resource + properties: + c: 3 + d: '4' + - name: Name2 + type: Module/Resource2 + properties: + l: '10' +")); + + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + Assert.Equal(string.Empty, openResult.Field); + Assert.Equal(string.Empty, openResult.Value); + Assert.Equal(0U, openResult.Line); + Assert.Equal(0U, openResult.Column); + + ConfigurationSet configurationSet = openResult.Set; + int unitCount = configurationSet.Units.Count; + + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + + TestConfigurationUnitProcessor[] unitProcessors = new TestConfigurationUnitProcessor[unitCount]; + for (int i = 0; i < unitCount; ++i) + { + unitProcessors[i] = setProcessor.CreateTestProcessor(configurationSet.Units[i]); + if (i == 0) + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Negative }; + } + else + { + unitProcessors[i].TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => new TestSettingsResultInstance(unit) { TestResult = ConfigurationTestResult.Positive }; + } + } + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.Equal(unitCount, result.UnitResults.Count); + + for (int i = 0; i < unitCount; ++i) + { + var unitResult = result.UnitResults[i]; + Assert.NotNull(unitResult); + + if (i == 0) + { + Assert.False(unitResult.PreviouslyInDesiredState); + } + else + { + Assert.True(unitResult.PreviouslyInDesiredState); + } + + Assert.False(unitResult.RebootRequired); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + } + + for (int i = 0; i < unitCount; ++i) + { + Assert.Equal(1, unitProcessors[i].TestSettingsCalls); + Assert.Equal(0, unitProcessors[i].GetSettingsCalls); + if (i == 0) + { + Assert.Equal(1, unitProcessors[i].ApplySettingsCalls); + } + else + { + Assert.Equal(0, unitProcessors[i].ApplySettingsCalls); + } + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the set processor is a group processor. + /// + [Fact] + public void ApplySet_SetGroupProcessor() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + + ConfigurationUnit configurationUnitNegative = this.ConfigurationUnit(); + configurationUnitNegative.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + ConfigurationUnit configurationUnitPositive = this.ConfigurationUnit(); + configurationUnitPositive.Settings[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Positive.ToString(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitNegative, configurationUnitPositive }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + ManualResetEvent startProcessing = new ManualResetEvent(false); + factory.CreateSetProcessorDelegate = (f, c) => + { + startProcessing.WaitOne(); + return f.CreateTestGroupProcessor(c); + }; + + var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); + + List progressValues = new List(); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, ConfigurationSetChangeData unitResult) => { progressValues.Add(unitResult); }; + startProcessing.Set(); + operation.AsTask().Wait(); + ApplyConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(configurationSet.Units.Count, result.UnitResults.Count); + Assert.Equal((configurationSet.Units.Count * 2) + 2, progressValues.Count); + + ApplyConfigurationUnitResult negativeResult = result.UnitResults.First(x => x.Unit == configurationUnitNegative); + + Assert.NotNull(negativeResult); + Assert.NotNull(negativeResult.ResultInformation); + Assert.Null(negativeResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, negativeResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, negativeResult.State); + Assert.False(negativeResult.PreviouslyInDesiredState); + + IEnumerable negativeProgress = progressValues.Where(x => x.Unit == configurationUnitNegative); + Assert.Equal(2, negativeProgress.Count()); + + foreach (ConfigurationSetChangeData change in negativeProgress) + { + Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); + Assert.Equal(ConfigurationSetState.InProgress, change.SetState); + Assert.NotNull(change.ResultInformation); + Assert.Null(change.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); + } + + Assert.Single(negativeProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress)); + Assert.Single(negativeProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed)); + + ApplyConfigurationUnitResult positiveResult = result.UnitResults.First(x => x.Unit == configurationUnitPositive); + + Assert.NotNull(positiveResult); + Assert.NotNull(positiveResult.ResultInformation); + Assert.Null(positiveResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, positiveResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, positiveResult.State); + Assert.True(positiveResult.PreviouslyInDesiredState); + + IEnumerable positiveProgress = progressValues.Where(x => x.Unit == configurationUnitPositive); + Assert.Equal(2, positiveProgress.Count()); + + foreach (ConfigurationSetChangeData change in positiveProgress) + { + Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); + Assert.Equal(ConfigurationSetState.InProgress, change.SetState); + Assert.NotNull(change.ResultInformation); + Assert.Null(change.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); + } + + Assert.Single(positiveProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress)); + Assert.Single(positiveProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed)); + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the set processor is a group processor and contains a unit that is also a group. + /// + [Fact] + public void ApplySet_SetGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + ManualResetEvent startProcessing = new ManualResetEvent(false); + factory.CreateSetProcessorDelegate = (f, c) => + { + startProcessing.WaitOne(); + return f.CreateTestGroupProcessor(c); + }; + + var operation = processor.ApplySetAsync(configurationSet, ApplyConfigurationSetFlags.None); + + List progressValues = new List(); + operation.Progress = (Windows.Foundation.IAsyncOperationWithProgress op, ConfigurationSetChangeData unitResult) => { progressValues.Add(unitResult); }; + startProcessing.Set(); + operation.AsTask().Wait(); + ApplyConfigurationSetResult result = operation.GetResults(); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + Assert.Equal(2 + (3 * 2), progressValues.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); + Assert.True(unitResult.PreviouslyInDesiredState); + + IEnumerable unitProgress = progressValues.Where(x => x.Unit == unit); + Assert.Equal(2, unitProgress.Count()); + + foreach (ConfigurationSetChangeData change in unitProgress) + { + Assert.Equal(ConfigurationSetChangeEventType.UnitStateChanged, change.Change); + Assert.Equal(ConfigurationSetState.InProgress, change.SetState); + Assert.NotNull(change.ResultInformation); + Assert.Null(change.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, change.ResultInformation.ResultSource); + } + + Assert.True(unitProgress.Where(x => x.UnitState == ConfigurationUnitState.InProgress).Count() <= 1); + Assert.True(unitProgress.Where(x => x.UnitState == ConfigurationUnitState.Completed).Count() <= 1); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the standard set processor is used and there is a group unit. + /// + [Fact] + public void ApplySet_UnitGroupProcessor_WithGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + ConfigurationUnit configurationUnitGroupMember = this.ConfigurationUnit(); + configurationUnitGroup.IsGroup = true; + configurationUnitGroup.Units = new ConfigurationUnit[] { configurationUnitGroupMember }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(3, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup, configurationUnitGroupMember }) + { + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); + Assert.True(unitResult.PreviouslyInDesiredState); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + + /// + /// Test when the standard set processor is used and there is a non-group unit that still exposes a group processor. + /// + [Fact] + public void ApplySet_UnitGroupProcessor_WithNonGroupUnit() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.Metadata[TestConfigurationUnitGroupProcessor.TestResultSetting] = ConfigurationTestResult.Negative.ToString(); + + ConfigurationUnit configurationUnit = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitGroup = this.ConfigurationUnit(); + + configurationSet.Units = new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.CreateTestProcessor(configurationUnit); + setProcessor.CreateTestGroupProcessor(configurationUnitGroup); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + ApplyConfigurationSetResult result = processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None); + + Assert.NotNull(result); + Assert.Null(result.ResultCode); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + + foreach (ConfigurationUnit unit in new ConfigurationUnit[] { configurationUnit, configurationUnitGroup }) + { + ApplyConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == unit); + + Assert.NotNull(unitResult); + Assert.NotNull(unitResult.ResultInformation); + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + Assert.Equal(ConfigurationUnitState.Completed, unitResult.State); + Assert.True(unitResult.PreviouslyInDesiredState); + } + + this.VerifySummaryEvent(configurationSet, result, ConfigurationUnitResultSource.None); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTelemetryTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTelemetryTests.cs index 3910de7ee0..79f7d75c6e 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTelemetryTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTelemetryTests.cs @@ -1,239 +1,239 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using System.Threading; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Microsoft.VisualStudio.TestPlatform.ObjectModel; - using Xunit; - using Xunit.Abstractions; - using static System.Collections.Specialized.BitVector32; - - /// - /// Unit tests for running test on the processor. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationProcessorTelemetryTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationProcessorTelemetryTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// No event is generated if the unit succeeds. - /// - [Fact] - public void Telemetry_NoUnitEventOnSuccess() - { - TelemetryTestObjects testObjects = new TelemetryTestObjects(getFails: false); - testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); - testObjects.CreateDetails(); - - GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); - - Assert.Single(this.EventSink.Events); - Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); - } - - /// - /// No event is generated if the details have not been retrieved. - /// - [Fact] - public void Telemetry_NoUnitEventIfNoDetails() - { - TelemetryTestObjects testObjects = new TelemetryTestObjects(); - testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); - - GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); - - Assert.Empty(this.EventSink.Events); - } - - /// - /// No event is generated if the module is not public. - /// - [Fact] - public void Telemetry_NoUnitEventIfNotPublic() - { - TelemetryTestObjects testObjects = new TelemetryTestObjects(); - testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); - testObjects.CreateDetails(isPublic: false); - - GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); - - Assert.Empty(this.EventSink.Events); - } - - /// - /// The activity set by the caller is the value in the event. - /// - [Fact] - public void Telemetry_ActivityID() - { - TelemetryTestObjects testObjects = new TelemetryTestObjects(); - testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); - testObjects.CreateDetails(); - - Guid activity = Guid.NewGuid(); - testObjects.Processor.ActivityIdentifier = activity; - - GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); - - Assert.Single(this.EventSink.Events); - Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); - Assert.Equal(activity, this.EventSink.Events[0].ActivityID); - } - - /// - /// Disabling telemetry causes no event to be produced. - /// - /// The state of telemetry. - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Telemetry_EnableState(bool state) - { - TelemetryTestObjects testObjects = new TelemetryTestObjects(); - testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); - testObjects.CreateDetails(); - - testObjects.Processor.GenerateTelemetryEvents = state; - - GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); - - if (state) - { - Assert.Single(this.EventSink.Events); - Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); - } - else - { - Assert.Empty(this.EventSink.Events); - } - } - - /// - /// The caller set by the caller is the value in the event. - /// - [Fact] - public void Telemetry_Caller() - { - TelemetryTestObjects testObjects = new TelemetryTestObjects(); - testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); - testObjects.CreateDetails(); - - string caller = "TheTests"; - testObjects.Processor.Caller = caller; - - GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); - - Assert.Single(this.EventSink.Events); - Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); - Assert.Equal(caller, this.EventSink.Events[0].Caller); - } - - /// - /// Verifies all of the telemetry fields that come from executing a specific unit. - /// - [Fact] - public void Telemetry_UnitFields() - { -#pragma warning disable CS8602 // Dereference of a possibly null reference. - TelemetryTestObjects testObjects = new TelemetryTestObjects(); - testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); - testObjects.CreateDetails(); - - testObjects.Unit.Type = "TestUnitName"; - testObjects.UnitDetails.ModuleName = "TestModuleName"; - - string setting1 = "setting1"; - string setting2 = "setting2"; - - testObjects.Unit.Settings.Add(setting1, 0); - testObjects.Unit.Settings.Add(setting2, 0); - - GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); - - Assert.Single(this.EventSink.Events); - TelemetryEvent runEvent = this.EventSink.Events[0]; - Assert.Equal(TelemetryEvent.ConfigUnitRunName, runEvent.Name); - Assert.NotEqual(string.Empty, runEvent.CodeVersion); - Assert.NotEqual(Guid.Empty, runEvent.ActivityID); - Assert.Equal(string.Empty, runEvent.Caller); - Assert.Equal(Guid.Empty, Guid.Parse(runEvent.Properties[TelemetryEvent.SetID])); - Assert.NotEqual(Guid.Empty, Guid.Parse(runEvent.Properties[TelemetryEvent.UnitID])); - Assert.Equal(testObjects.Unit.Type, runEvent.Properties[TelemetryEvent.UnitName]); - Assert.Equal(testObjects.UnitDetails.ModuleName, runEvent.Properties[TelemetryEvent.ModuleName]); - Assert.Equal(((int)testObjects.Unit.Intent).ToString(), runEvent.Properties[TelemetryEvent.UnitIntent]); - Assert.Equal(((int)ConfigurationUnitIntent.Inform).ToString(), runEvent.Properties[TelemetryEvent.RunIntent]); - Assert.NotEqual(string.Empty, runEvent.Properties[TelemetryEvent.Action]); - Assert.Equal(testObjects.GetResult.ResultInformation.ResultCode.HResult.ToString(), runEvent.Properties[TelemetryEvent.Result]); - Assert.Equal(((int)testObjects.GetResult.ResultInformation.ResultSource).ToString(), runEvent.Properties[TelemetryEvent.FailurePoint]); - Assert.Equal(setting1 + "|" + setting2, runEvent.Properties[TelemetryEvent.SettingsProvided]); -#pragma warning restore CS8602 // Dereference of a possibly null reference. - } - - private class TelemetryTestObjects - { - public TelemetryTestObjects(bool getFails = true) - { - this.Unit = new ConfigurationUnit { Intent = ConfigurationUnitIntent.Apply }; - - this.Factory = new TestConfigurationProcessorFactory(); - this.Factory.NullProcessor = new TestConfigurationSetProcessor(null); - - this.UnitProcessor = this.Factory.NullProcessor.CreateTestProcessor(this.Unit); - - if (getFails) - { - var getResult = new GetSettingsResultInstance(this.Unit); - getResult.InternalResult.ResultCode = new NullReferenceException(); - getResult.InternalResult.ResultSource = ConfigurationUnitResultSource.UnitProcessing; - this.GetResult = getResult; - this.UnitProcessor.GetSettingsDelegate = () => this.GetResult; - } - } - - public ConfigurationUnit Unit { get; set; } - - public TestConfigurationProcessorFactory Factory { get; set; } - - public TestConfigurationUnitProcessor UnitProcessor { get; set; } - - public IGetSettingsResult? GetResult { get; set; } - - public TestConfigurationUnitProcessorDetails? UnitDetails { get; set; } - - public ConfigurationProcessor? Processor { get; set; } - - public void CreateDetails(bool isPublic = true) - { -#pragma warning disable CS8602 // Dereference of a possibly null reference. - this.UnitDetails = this.Factory.NullProcessor.CreateUnitDetails(this.Unit, ConfigurationUnitDetailFlags.ReadOnly); -#pragma warning restore CS8602 // Dereference of a possibly null reference. - this.UnitDetails.IsPublic = isPublic; - - this.Processor?.GetUnitDetails(this.Unit, ConfigurationUnitDetailFlags.ReadOnly); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Runtime.InteropServices; + using System.Threading; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Microsoft.VisualStudio.TestPlatform.ObjectModel; + using Xunit; + using Xunit.Abstractions; + using static System.Collections.Specialized.BitVector32; + + /// + /// Unit tests for running test on the processor. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationProcessorTelemetryTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationProcessorTelemetryTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// No event is generated if the unit succeeds. + /// + [Fact] + public void Telemetry_NoUnitEventOnSuccess() + { + TelemetryTestObjects testObjects = new TelemetryTestObjects(getFails: false); + testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); + testObjects.CreateDetails(); + + GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); + + Assert.Single(this.EventSink.Events); + Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); + } + + /// + /// No event is generated if the details have not been retrieved. + /// + [Fact] + public void Telemetry_NoUnitEventIfNoDetails() + { + TelemetryTestObjects testObjects = new TelemetryTestObjects(); + testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); + + GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); + + Assert.Empty(this.EventSink.Events); + } + + /// + /// No event is generated if the module is not public. + /// + [Fact] + public void Telemetry_NoUnitEventIfNotPublic() + { + TelemetryTestObjects testObjects = new TelemetryTestObjects(); + testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); + testObjects.CreateDetails(isPublic: false); + + GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); + + Assert.Empty(this.EventSink.Events); + } + + /// + /// The activity set by the caller is the value in the event. + /// + [Fact] + public void Telemetry_ActivityID() + { + TelemetryTestObjects testObjects = new TelemetryTestObjects(); + testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); + testObjects.CreateDetails(); + + Guid activity = Guid.NewGuid(); + testObjects.Processor.ActivityIdentifier = activity; + + GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); + + Assert.Single(this.EventSink.Events); + Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); + Assert.Equal(activity, this.EventSink.Events[0].ActivityID); + } + + /// + /// Disabling telemetry causes no event to be produced. + /// + /// The state of telemetry. + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Telemetry_EnableState(bool state) + { + TelemetryTestObjects testObjects = new TelemetryTestObjects(); + testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); + testObjects.CreateDetails(); + + testObjects.Processor.GenerateTelemetryEvents = state; + + GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); + + if (state) + { + Assert.Single(this.EventSink.Events); + Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); + } + else + { + Assert.Empty(this.EventSink.Events); + } + } + + /// + /// The caller set by the caller is the value in the event. + /// + [Fact] + public void Telemetry_Caller() + { + TelemetryTestObjects testObjects = new TelemetryTestObjects(); + testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); + testObjects.CreateDetails(); + + string caller = "TheTests"; + testObjects.Processor.Caller = caller; + + GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); + + Assert.Single(this.EventSink.Events); + Assert.Equal(TelemetryEvent.ConfigUnitRunName, this.EventSink.Events[0].Name); + Assert.Equal(caller, this.EventSink.Events[0].Caller); + } + + /// + /// Verifies all of the telemetry fields that come from executing a specific unit. + /// + [Fact] + public void Telemetry_UnitFields() + { +#pragma warning disable CS8602 // Dereference of a possibly null reference. + TelemetryTestObjects testObjects = new TelemetryTestObjects(); + testObjects.Processor = this.CreateConfigurationProcessorWithDiagnostics(testObjects.Factory); + testObjects.CreateDetails(); + + testObjects.Unit.Type = "TestUnitName"; + testObjects.UnitDetails.ModuleName = "TestModuleName"; + + string setting1 = "setting1"; + string setting2 = "setting2"; + + testObjects.Unit.Settings.Add(setting1, 0); + testObjects.Unit.Settings.Add(setting2, 0); + + GetConfigurationUnitSettingsResult result = testObjects.Processor.GetUnitSettings(testObjects.Unit); + + Assert.Single(this.EventSink.Events); + TelemetryEvent runEvent = this.EventSink.Events[0]; + Assert.Equal(TelemetryEvent.ConfigUnitRunName, runEvent.Name); + Assert.NotEqual(string.Empty, runEvent.CodeVersion); + Assert.NotEqual(Guid.Empty, runEvent.ActivityID); + Assert.Equal(string.Empty, runEvent.Caller); + Assert.Equal(Guid.Empty, Guid.Parse(runEvent.Properties[TelemetryEvent.SetID])); + Assert.NotEqual(Guid.Empty, Guid.Parse(runEvent.Properties[TelemetryEvent.UnitID])); + Assert.Equal(testObjects.Unit.Type, runEvent.Properties[TelemetryEvent.UnitName]); + Assert.Equal(testObjects.UnitDetails.ModuleName, runEvent.Properties[TelemetryEvent.ModuleName]); + Assert.Equal(((int)testObjects.Unit.Intent).ToString(), runEvent.Properties[TelemetryEvent.UnitIntent]); + Assert.Equal(((int)ConfigurationUnitIntent.Inform).ToString(), runEvent.Properties[TelemetryEvent.RunIntent]); + Assert.NotEqual(string.Empty, runEvent.Properties[TelemetryEvent.Action]); + Assert.Equal(testObjects.GetResult.ResultInformation.ResultCode.HResult.ToString(), runEvent.Properties[TelemetryEvent.Result]); + Assert.Equal(((int)testObjects.GetResult.ResultInformation.ResultSource).ToString(), runEvent.Properties[TelemetryEvent.FailurePoint]); + Assert.Equal(setting1 + "|" + setting2, runEvent.Properties[TelemetryEvent.SettingsProvided]); +#pragma warning restore CS8602 // Dereference of a possibly null reference. + } + + private class TelemetryTestObjects + { + public TelemetryTestObjects(bool getFails = true) + { + this.Unit = new ConfigurationUnit { Intent = ConfigurationUnitIntent.Apply }; + + this.Factory = new TestConfigurationProcessorFactory(); + this.Factory.NullProcessor = new TestConfigurationSetProcessor(null); + + this.UnitProcessor = this.Factory.NullProcessor.CreateTestProcessor(this.Unit); + + if (getFails) + { + var getResult = new GetSettingsResultInstance(this.Unit); + getResult.InternalResult.ResultCode = new NullReferenceException(); + getResult.InternalResult.ResultSource = ConfigurationUnitResultSource.UnitProcessing; + this.GetResult = getResult; + this.UnitProcessor.GetSettingsDelegate = () => this.GetResult; + } + } + + public ConfigurationUnit Unit { get; set; } + + public TestConfigurationProcessorFactory Factory { get; set; } + + public TestConfigurationUnitProcessor UnitProcessor { get; set; } + + public IGetSettingsResult? GetResult { get; set; } + + public TestConfigurationUnitProcessorDetails? UnitDetails { get; set; } + + public ConfigurationProcessor? Processor { get; set; } + + public void CreateDetails(bool isPublic = true) + { +#pragma warning disable CS8602 // Dereference of a possibly null reference. + this.UnitDetails = this.Factory.NullProcessor.CreateUnitDetails(this.Unit, ConfigurationUnitDetailFlags.ReadOnly); +#pragma warning restore CS8602 // Dereference of a possibly null reference. + this.UnitDetails.IsPublic = isPublic; + + this.Processor?.GetUnitDetails(this.Unit, ConfigurationUnitDetailFlags.ReadOnly); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTestTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTestTests.cs index 530eee8a92..6e19082704 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTestTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationProcessorTestTests.cs @@ -1,323 +1,323 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.IO; - using System.Linq; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for running test on the processor. - /// - [Collection("UnitTestCollection")] - [InProc] - [OutOfProc] - public class ConfigurationProcessorTestTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationProcessorTestTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// An error creating the set processor results in an error for the function. - /// - [Fact] - public void TestSet_SetProcessorError() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - factory.Exceptions.Add(configurationSet, new FileNotFoundException()); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - Assert.Throws(() => processor.TestSet(configurationSet)); - - Assert.Empty(this.EventSink.Events); - } - - /// - /// An error creating a unit processor results in an error for that unit. - /// - [Fact] - public void TestSet_UnitProcessorCreationError() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitThrows, configurationUnitWorks }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - setProcessor.Exceptions.Add(configurationUnitThrows, new NullReferenceException()); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - TestConfigurationSetResult result = processor.TestSet(configurationSet); - - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(2, result.UnitResults.Count); - - TestConfigurationUnitResult throwsResult = result.UnitResults.First(x => x.Unit == configurationUnitThrows); - Assert.NotNull(throwsResult); - Assert.Equal(ConfigurationTestResult.Failed, throwsResult.TestResult); - Assert.NotNull(throwsResult.ResultInformation); - Assert.NotNull(throwsResult.ResultInformation.ResultCode); - Assert.IsType(throwsResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.Internal, throwsResult.ResultInformation.ResultSource); - - TestConfigurationUnitResult worksResult = result.UnitResults.First(x => x.Unit == configurationUnitWorks); - Assert.NotNull(worksResult); - Assert.Equal(ConfigurationTestResult.Positive, worksResult.TestResult); - Assert.NotNull(worksResult.ResultInformation); - Assert.Null(worksResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, worksResult.ResultInformation.ResultSource); - - this.VerifySummaryEvent(configurationSet, result, throwsResult.ResultInformation.ResultCode.HResult, ConfigurationUnitResultSource.Internal); - } - - /// - /// An error running a unit processor results in an error for that unit. - /// - [Fact] - public void TestSet_UnitProcessorExecutionError() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitWorks, configurationUnitThrows }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitThrows); - unitProcessor.TestSettingsDelegate = () => throw new NullReferenceException(); - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - TestConfigurationSetResult result = processor.TestSet(configurationSet); - - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(2, result.UnitResults.Count); - - TestConfigurationUnitResult throwsResult = result.UnitResults.First(x => x.Unit == configurationUnitThrows); - Assert.NotNull(throwsResult); - Assert.Equal(ConfigurationTestResult.Failed, throwsResult.TestResult); - Assert.NotNull(throwsResult.ResultInformation); - Assert.NotNull(throwsResult.ResultInformation.ResultCode); - Assert.IsType(throwsResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.Internal, throwsResult.ResultInformation.ResultSource); - - TestConfigurationUnitResult worksResult = result.UnitResults.First(x => x.Unit == configurationUnitWorks); - Assert.NotNull(worksResult); - Assert.Equal(ConfigurationTestResult.Positive, worksResult.TestResult); - Assert.NotNull(worksResult.ResultInformation); - Assert.Null(worksResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, worksResult.ResultInformation.ResultSource); - - this.VerifySummaryEvent(configurationSet, result, throwsResult.ResultInformation.ResultCode.HResult, ConfigurationUnitResultSource.Internal); - } - - /// - /// An error running a unit processor results in an error for that unit. - /// - [Fact] - public void TestSet_UnitProcessorResultError() - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitWorks, configurationUnitThrows }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitThrows); - TestSettingsResultInstance testResult = new TestSettingsResultInstance(configurationUnitThrows); - testResult.TestResult = ConfigurationTestResult.Failed; - testResult.InternalResult.ResultCode = new NullReferenceException(); - testResult.InternalResult.Description = "Failed again"; - testResult.InternalResult.ResultSource = ConfigurationUnitResultSource.UnitProcessing; - unitProcessor.TestSettingsDelegate = () => testResult; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - TestConfigurationSetResult result = processor.TestSet(configurationSet); - - Assert.NotNull(result); - Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(2, result.UnitResults.Count); - - TestConfigurationUnitResult throwsResult = result.UnitResults.First(x => x.Unit == configurationUnitThrows); - Assert.NotNull(throwsResult); - Assert.Equal(ConfigurationTestResult.Failed, throwsResult.TestResult); - Assert.NotNull(throwsResult.ResultInformation); - Assert.NotNull(throwsResult.ResultInformation.ResultCode); - Assert.IsType(throwsResult.ResultInformation.ResultCode); - Assert.Equal(testResult.ResultInformation.Description, throwsResult.ResultInformation.Description); - Assert.Equal(testResult.ResultInformation.ResultSource, throwsResult.ResultInformation.ResultSource); - - TestConfigurationUnitResult worksResult = result.UnitResults.First(x => x.Unit == configurationUnitWorks); - Assert.NotNull(worksResult); - Assert.Equal(ConfigurationTestResult.Positive, worksResult.TestResult); - Assert.NotNull(worksResult.ResultInformation); - Assert.Null(worksResult.ResultInformation.ResultCode); - Assert.Equal(ConfigurationUnitResultSource.None, worksResult.ResultInformation.ResultSource); - - this.VerifySummaryEvent(configurationSet, result, testResult.ResultInformation.ResultCode.HResult, testResult.ResultInformation.ResultSource); - } - - /// - /// Ensures that the expected TestResult comes back for all types. - /// - [Fact] - public void TestSet_ResultTypes() - { - this.RunTestSetTestForResultTypes(new ConfigurationTestResult[] { ConfigurationTestResult.Positive, ConfigurationTestResult.Negative, ConfigurationTestResult.Failed, ConfigurationTestResult.NotRun }, ConfigurationTestResult.Failed); - } - - /// - /// Ensures that a single negative makes the overall result negative. - /// - [Fact] - public void TestSet_Negative() - { - this.RunTestSetTestForResultTypes(new ConfigurationTestResult[] { ConfigurationTestResult.Positive, ConfigurationTestResult.Negative, ConfigurationTestResult.Positive, ConfigurationTestResult.NotRun }, ConfigurationTestResult.Negative); - } - - /// - /// Ensures that a single not run test does not impact the positive result. - /// - [Fact] - public void TestSet_Positive() - { - this.RunTestSetTestForResultTypes(new ConfigurationTestResult[] { ConfigurationTestResult.Positive, ConfigurationTestResult.Positive, ConfigurationTestResult.Positive, ConfigurationTestResult.NotRun }, ConfigurationTestResult.Positive); - } - - private TestSettingsResultInstance PositiveResult(ConfigurationUnit unit) - { - TestSettingsResultInstance positiveResult = new TestSettingsResultInstance(unit); - positiveResult.TestResult = ConfigurationTestResult.Positive; - return positiveResult; - } - - private TestSettingsResultInstance NegativeResult(ConfigurationUnit unit) - { - TestSettingsResultInstance negativeResult = new TestSettingsResultInstance(unit); - negativeResult.TestResult = ConfigurationTestResult.Negative; - return negativeResult; - } - - private TestSettingsResultInstance FailedResult(ConfigurationUnit unit, string description, ConfigurationUnitResultSource resultSource) - { - TestSettingsResultInstance failedResult = new TestSettingsResultInstance(unit); - failedResult.TestResult = ConfigurationTestResult.Failed; - failedResult.InternalResult.ResultCode = new NullReferenceException(); - failedResult.InternalResult.Description = description; - failedResult.InternalResult.ResultSource = resultSource; - return failedResult; - } - - /// - /// Creates a test scenario where the units produce the given test results. - /// - /// The result types for each unit. - /// The expected overall test result. - private void RunTestSetTestForResultTypes(ConfigurationTestResult[] resultTypes, ConfigurationTestResult overallResult) - { - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit[] configurationUnits = new ConfigurationUnit[resultTypes.Length]; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - - string failedDescription = "Failed again"; - ConfigurationUnitResultSource failedResultSource = ConfigurationUnitResultSource.UnitProcessing; - - for (int i = 0; i < resultTypes.Length; ++i) - { - configurationUnits[i] = this.ConfigurationUnit(); - configurationUnits[i].Type = $"Unit {i}"; - TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnits[i]); - - switch (resultTypes[i]) - { - case ConfigurationTestResult.Positive: - unitProcessor.TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => this.PositiveResult(unit); - break; - case ConfigurationTestResult.Negative: - unitProcessor.TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => this.NegativeResult(unit); - break; - case ConfigurationTestResult.NotRun: - configurationUnits[i].Intent = ConfigurationUnitIntent.Inform; - break; - case ConfigurationTestResult.Failed: - unitProcessor.TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => this.FailedResult(unit, failedDescription, failedResultSource); - break; - } - } - - configurationSet.Units = configurationUnits; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - TestConfigurationSetResult result = processor.TestSet(configurationSet); - - Assert.NotNull(result); - Assert.Equal(overallResult, result.TestResult); - Assert.NotNull(result.UnitResults); - Assert.Equal(resultTypes.Length, result.UnitResults.Count); - - int summaryEventResult = 0; - ConfigurationUnitResultSource resultSource = ConfigurationUnitResultSource.None; - - for (int i = 0; i < resultTypes.Length; ++i) - { - TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == configurationUnits[i]); - - Assert.NotNull(unitResult); - Assert.Equal(resultTypes[i], unitResult.TestResult); - Assert.NotNull(unitResult.ResultInformation); - - switch (resultTypes[i]) - { - case ConfigurationTestResult.Positive: - case ConfigurationTestResult.Negative: - case ConfigurationTestResult.NotRun: - Assert.Null(unitResult.ResultInformation.ResultCode); - Assert.Empty(unitResult.ResultInformation.Description); - Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); - break; - case ConfigurationTestResult.Failed: - Assert.NotNull(unitResult.ResultInformation.ResultCode); - Assert.IsType(unitResult.ResultInformation.ResultCode); - Assert.Equal(failedDescription, unitResult.ResultInformation.Description); - Assert.Equal(failedResultSource, unitResult.ResultInformation.ResultSource); - summaryEventResult = unitResult.ResultInformation.ResultCode.HResult; - resultSource = unitResult.ResultInformation.ResultSource; - break; - } - } - - this.VerifySummaryEvent(configurationSet, result, summaryEventResult, resultSource); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.IO; + using System.Linq; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for running test on the processor. + /// + [Collection("UnitTestCollection")] + [InProc] + [OutOfProc] + public class ConfigurationProcessorTestTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationProcessorTestTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// An error creating the set processor results in an error for the function. + /// + [Fact] + public void TestSet_SetProcessorError() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + factory.Exceptions.Add(configurationSet, new FileNotFoundException()); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + Assert.Throws(() => processor.TestSet(configurationSet)); + + Assert.Empty(this.EventSink.Events); + } + + /// + /// An error creating a unit processor results in an error for that unit. + /// + [Fact] + public void TestSet_UnitProcessorCreationError() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitThrows, configurationUnitWorks }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + setProcessor.Exceptions.Add(configurationUnitThrows, new NullReferenceException()); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + + TestConfigurationUnitResult throwsResult = result.UnitResults.First(x => x.Unit == configurationUnitThrows); + Assert.NotNull(throwsResult); + Assert.Equal(ConfigurationTestResult.Failed, throwsResult.TestResult); + Assert.NotNull(throwsResult.ResultInformation); + Assert.NotNull(throwsResult.ResultInformation.ResultCode); + Assert.IsType(throwsResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.Internal, throwsResult.ResultInformation.ResultSource); + + TestConfigurationUnitResult worksResult = result.UnitResults.First(x => x.Unit == configurationUnitWorks); + Assert.NotNull(worksResult); + Assert.Equal(ConfigurationTestResult.Positive, worksResult.TestResult); + Assert.NotNull(worksResult.ResultInformation); + Assert.Null(worksResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, worksResult.ResultInformation.ResultSource); + + this.VerifySummaryEvent(configurationSet, result, throwsResult.ResultInformation.ResultCode.HResult, ConfigurationUnitResultSource.Internal); + } + + /// + /// An error running a unit processor results in an error for that unit. + /// + [Fact] + public void TestSet_UnitProcessorExecutionError() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitWorks, configurationUnitThrows }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitThrows); + unitProcessor.TestSettingsDelegate = () => throw new NullReferenceException(); + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + + TestConfigurationUnitResult throwsResult = result.UnitResults.First(x => x.Unit == configurationUnitThrows); + Assert.NotNull(throwsResult); + Assert.Equal(ConfigurationTestResult.Failed, throwsResult.TestResult); + Assert.NotNull(throwsResult.ResultInformation); + Assert.NotNull(throwsResult.ResultInformation.ResultCode); + Assert.IsType(throwsResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.Internal, throwsResult.ResultInformation.ResultSource); + + TestConfigurationUnitResult worksResult = result.UnitResults.First(x => x.Unit == configurationUnitWorks); + Assert.NotNull(worksResult); + Assert.Equal(ConfigurationTestResult.Positive, worksResult.TestResult); + Assert.NotNull(worksResult.ResultInformation); + Assert.Null(worksResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, worksResult.ResultInformation.ResultSource); + + this.VerifySummaryEvent(configurationSet, result, throwsResult.ResultInformation.ResultCode.HResult, ConfigurationUnitResultSource.Internal); + } + + /// + /// An error running a unit processor results in an error for that unit. + /// + [Fact] + public void TestSet_UnitProcessorResultError() + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitThrows = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitWorks, configurationUnitThrows }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitThrows); + TestSettingsResultInstance testResult = new TestSettingsResultInstance(configurationUnitThrows); + testResult.TestResult = ConfigurationTestResult.Failed; + testResult.InternalResult.ResultCode = new NullReferenceException(); + testResult.InternalResult.Description = "Failed again"; + testResult.InternalResult.ResultSource = ConfigurationUnitResultSource.UnitProcessing; + unitProcessor.TestSettingsDelegate = () => testResult; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(2, result.UnitResults.Count); + + TestConfigurationUnitResult throwsResult = result.UnitResults.First(x => x.Unit == configurationUnitThrows); + Assert.NotNull(throwsResult); + Assert.Equal(ConfigurationTestResult.Failed, throwsResult.TestResult); + Assert.NotNull(throwsResult.ResultInformation); + Assert.NotNull(throwsResult.ResultInformation.ResultCode); + Assert.IsType(throwsResult.ResultInformation.ResultCode); + Assert.Equal(testResult.ResultInformation.Description, throwsResult.ResultInformation.Description); + Assert.Equal(testResult.ResultInformation.ResultSource, throwsResult.ResultInformation.ResultSource); + + TestConfigurationUnitResult worksResult = result.UnitResults.First(x => x.Unit == configurationUnitWorks); + Assert.NotNull(worksResult); + Assert.Equal(ConfigurationTestResult.Positive, worksResult.TestResult); + Assert.NotNull(worksResult.ResultInformation); + Assert.Null(worksResult.ResultInformation.ResultCode); + Assert.Equal(ConfigurationUnitResultSource.None, worksResult.ResultInformation.ResultSource); + + this.VerifySummaryEvent(configurationSet, result, testResult.ResultInformation.ResultCode.HResult, testResult.ResultInformation.ResultSource); + } + + /// + /// Ensures that the expected TestResult comes back for all types. + /// + [Fact] + public void TestSet_ResultTypes() + { + this.RunTestSetTestForResultTypes(new ConfigurationTestResult[] { ConfigurationTestResult.Positive, ConfigurationTestResult.Negative, ConfigurationTestResult.Failed, ConfigurationTestResult.NotRun }, ConfigurationTestResult.Failed); + } + + /// + /// Ensures that a single negative makes the overall result negative. + /// + [Fact] + public void TestSet_Negative() + { + this.RunTestSetTestForResultTypes(new ConfigurationTestResult[] { ConfigurationTestResult.Positive, ConfigurationTestResult.Negative, ConfigurationTestResult.Positive, ConfigurationTestResult.NotRun }, ConfigurationTestResult.Negative); + } + + /// + /// Ensures that a single not run test does not impact the positive result. + /// + [Fact] + public void TestSet_Positive() + { + this.RunTestSetTestForResultTypes(new ConfigurationTestResult[] { ConfigurationTestResult.Positive, ConfigurationTestResult.Positive, ConfigurationTestResult.Positive, ConfigurationTestResult.NotRun }, ConfigurationTestResult.Positive); + } + + private TestSettingsResultInstance PositiveResult(ConfigurationUnit unit) + { + TestSettingsResultInstance positiveResult = new TestSettingsResultInstance(unit); + positiveResult.TestResult = ConfigurationTestResult.Positive; + return positiveResult; + } + + private TestSettingsResultInstance NegativeResult(ConfigurationUnit unit) + { + TestSettingsResultInstance negativeResult = new TestSettingsResultInstance(unit); + negativeResult.TestResult = ConfigurationTestResult.Negative; + return negativeResult; + } + + private TestSettingsResultInstance FailedResult(ConfigurationUnit unit, string description, ConfigurationUnitResultSource resultSource) + { + TestSettingsResultInstance failedResult = new TestSettingsResultInstance(unit); + failedResult.TestResult = ConfigurationTestResult.Failed; + failedResult.InternalResult.ResultCode = new NullReferenceException(); + failedResult.InternalResult.Description = description; + failedResult.InternalResult.ResultSource = resultSource; + return failedResult; + } + + /// + /// Creates a test scenario where the units produce the given test results. + /// + /// The result types for each unit. + /// The expected overall test result. + private void RunTestSetTestForResultTypes(ConfigurationTestResult[] resultTypes, ConfigurationTestResult overallResult) + { + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit[] configurationUnits = new ConfigurationUnit[resultTypes.Length]; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + + string failedDescription = "Failed again"; + ConfigurationUnitResultSource failedResultSource = ConfigurationUnitResultSource.UnitProcessing; + + for (int i = 0; i < resultTypes.Length; ++i) + { + configurationUnits[i] = this.ConfigurationUnit(); + configurationUnits[i].Type = $"Unit {i}"; + TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnits[i]); + + switch (resultTypes[i]) + { + case ConfigurationTestResult.Positive: + unitProcessor.TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => this.PositiveResult(unit); + break; + case ConfigurationTestResult.Negative: + unitProcessor.TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => this.NegativeResult(unit); + break; + case ConfigurationTestResult.NotRun: + configurationUnits[i].Intent = ConfigurationUnitIntent.Inform; + break; + case ConfigurationTestResult.Failed: + unitProcessor.TestSettingsDelegateWithUnit = (ConfigurationUnit unit) => this.FailedResult(unit, failedDescription, failedResultSource); + break; + } + } + + configurationSet.Units = configurationUnits; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult result = processor.TestSet(configurationSet); + + Assert.NotNull(result); + Assert.Equal(overallResult, result.TestResult); + Assert.NotNull(result.UnitResults); + Assert.Equal(resultTypes.Length, result.UnitResults.Count); + + int summaryEventResult = 0; + ConfigurationUnitResultSource resultSource = ConfigurationUnitResultSource.None; + + for (int i = 0; i < resultTypes.Length; ++i) + { + TestConfigurationUnitResult unitResult = result.UnitResults.First(x => x.Unit == configurationUnits[i]); + + Assert.NotNull(unitResult); + Assert.Equal(resultTypes[i], unitResult.TestResult); + Assert.NotNull(unitResult.ResultInformation); + + switch (resultTypes[i]) + { + case ConfigurationTestResult.Positive: + case ConfigurationTestResult.Negative: + case ConfigurationTestResult.NotRun: + Assert.Null(unitResult.ResultInformation.ResultCode); + Assert.Empty(unitResult.ResultInformation.Description); + Assert.Equal(ConfigurationUnitResultSource.None, unitResult.ResultInformation.ResultSource); + break; + case ConfigurationTestResult.Failed: + Assert.NotNull(unitResult.ResultInformation.ResultCode); + Assert.IsType(unitResult.ResultInformation.ResultCode); + Assert.Equal(failedDescription, unitResult.ResultInformation.Description); + Assert.Equal(failedResultSource, unitResult.ResultInformation.ResultSource); + summaryEventResult = unitResult.ResultInformation.ResultCode.HResult; + resultSource = unitResult.ResultInformation.ResultSource; + break; + } + } + + this.VerifySummaryEvent(configurationSet, result, summaryEventResult, resultSource); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs index f37a738c1c..b37404fd38 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetAuthoringTests.cs @@ -1,247 +1,247 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; - using Windows.Foundation.Collections; - using Windows.Storage.Streams; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for configuration set authoring (creating objects). - /// - [Collection("UnitTestCollection")] - [InProc] - [OutOfProc] - public class ConfigurationSetAuthoringTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationSetAuthoringTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// Creates a configuration set and sets all available properties. - /// - [Fact] - public void ConfigurationSetAndProperties() - { - string testName = "Test Name"; - string testOrigin = "Test Origin"; - string testPath = "TestPath.ext"; - - ConfigurationSet testSet = this.ConfigurationSet(); - - testSet.Name = testName; - Assert.Equal(testName, testSet.Name); - testSet.Origin = testOrigin; - Assert.Equal(testOrigin, testSet.Origin); - testSet.Path = testPath; - Assert.Equal(testPath, testSet.Path); - - Assert.NotEqual(Guid.Empty, testSet.InstanceIdentifier); - Assert.Equal(ConfigurationSetState.Unknown, testSet.State); - - Assert.Empty(testSet.Units); - testSet.Units = new ConfigurationUnit[] { this.ConfigurationUnit() }; - Assert.Equal(1, testSet.Units.Count); - - Assert.NotEqual(string.Empty, testSet.SchemaVersion); - } - - /// - /// Creates a configuration unit and sets all available properties. - /// - [Fact] - public void ConfigurationUnitAndProperties() - { - string testName = "Test Name"; - string testIdentifier = "Test Identifier"; - ConfigurationUnitIntent testIntent = ConfigurationUnitIntent.Assert; - - ConfigurationUnit testUnit = this.ConfigurationUnit(); - testUnit.Type = testName; - Assert.Equal(testName, testUnit.Type); - testUnit.Identifier = testIdentifier; - Assert.Equal(testIdentifier, testUnit.Identifier); - - Assert.NotEqual(Guid.Empty, testUnit.InstanceIdentifier); - - Assert.Equal(ConfigurationUnitIntent.Apply, testUnit.Intent); - testUnit.Intent = testIntent; - Assert.Equal(testIntent, testUnit.Intent); - - Assert.Empty(testUnit.Dependencies); - testUnit.Dependencies = new string[] { "dependency1", "dependency2" }; - Assert.Equal(2, testUnit.Dependencies.Count); - - Assert.Empty(testUnit.Metadata); - Assert.Empty(testUnit.Settings); - Assert.Null(testUnit.Details); - - Assert.Equal(ConfigurationUnitState.Unknown, testUnit.State); - - Assert.Null(testUnit.ResultInformation); - - Assert.True(testUnit.IsActive); - testUnit.IsActive = false; - Assert.False(testUnit.IsActive); - } - - /// - /// Basic sanity check to verify that nested value sets can be serialized successfully. - /// - [Fact] - public void ConfigurationSetSerializeNestedValueSets() - { - ConfigurationSet testSet = this.ConfigurationSet(); - - testSet.SchemaVersion = "0.2"; - ConfigurationUnit testUnit = this.ConfigurationUnit(); - string testName = "Test Name"; - string testIdentifier = "Test Identifier"; - testUnit.Type = testName; - testUnit.Identifier = testIdentifier; - - ValueSet innerValueSet = new ValueSet(); - innerValueSet.Add("innerKey", "innerValue"); - - ValueSet outerValueSet = new ValueSet(); - outerValueSet.Add("outerKey", innerValueSet); - testUnit.Metadata = outerValueSet; - testSet.Units.Add(testUnit); - - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - testSet.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - Assert.NotNull(yamlOutput); - } - - /// - /// Test for unique unit environment calculation. - /// - [Fact] - public void ConfigurationSet_UnitEnvironments() - { - ConfigurationSet testSet = this.ConfigurationSet(); - - Dictionary firstProperty = new Dictionary(); - firstProperty.Add("property", "value1"); - - Dictionary secondProperty = new Dictionary(); - secondProperty.Add("property", "value2"); - - Helpers.ConfigurationEnvironmentData[] environments = new Helpers.ConfigurationEnvironmentData[] - { - new () { ProcessorIdentifier = "dscv3" }, - new () { ProcessorIdentifier = "pwsh" }, - new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Elevated }, - new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Restricted }, - new () { ProcessorIdentifier = "dscv3", ProcessorProperties = firstProperty }, - new () { ProcessorIdentifier = "pwsh", ProcessorProperties = firstProperty }, - new () { ProcessorIdentifier = "pwsh", ProcessorProperties = secondProperty }, - new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Restricted, ProcessorProperties = firstProperty }, - new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Elevated, ProcessorProperties = firstProperty }, - }; - - foreach (int index in new int[] { 0, 1, 1, 2, 3, 5, 4, 6, 7, 8, 2, 7, 7, 7 }) - { - Assert.True(index < environments.Length); - testSet.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); - } - - var uniqueEnvironments = testSet.GetUnitEnvironments(); - this.EnsureEnvironmentEquivalence(environments, uniqueEnvironments); - } - - /// - /// Test for unique unit environment calculation with group units. - /// - [Fact] - public void ConfigurationSet_GroupUnitEnvironments() - { - ConfigurationSet testSet = this.ConfigurationSet(); - - Dictionary firstProperty = new Dictionary(); - firstProperty.Add("property", "value1"); - - Dictionary secondProperty = new Dictionary(); - secondProperty.Add("property", "value2"); - - Helpers.ConfigurationEnvironmentData[] environments = new Helpers.ConfigurationEnvironmentData[] - { - new () { ProcessorIdentifier = "dscv3" }, - new () { ProcessorIdentifier = "pwsh" }, - new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Elevated }, - new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Restricted }, - new () { ProcessorIdentifier = "dscv3", ProcessorProperties = firstProperty }, - new () { ProcessorIdentifier = "pwsh", ProcessorProperties = firstProperty }, - new () { ProcessorIdentifier = "pwsh", ProcessorProperties = secondProperty }, - new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Restricted, ProcessorProperties = firstProperty }, - new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Elevated, ProcessorProperties = firstProperty }, - new (), // The default environment for the group unit - }; - - foreach (int index in new int[] { 0, 1, 1, 3, 5, 4, 6, 8 }) - { - Assert.True(index < environments.Length); - testSet.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); - } - - var groupUnit = this.ConfigurationUnit(); - groupUnit.IsGroup = true; - - foreach (int index in new int[] { 7, 5, 2 }) - { - Assert.True(index < environments.Length); - groupUnit.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); - } - - testSet.Units.Add(groupUnit); - - var uniqueEnvironments = testSet.GetUnitEnvironments(); - this.EnsureEnvironmentEquivalence(environments, uniqueEnvironments); - } - - private void EnsureEnvironmentEquivalence(Helpers.ConfigurationEnvironmentData[] expectedEnvironments, IList? actualEnvironments) - { - Assert.NotNull(actualEnvironments); - Assert.Equal(expectedEnvironments.Length, actualEnvironments.Count); - - bool[] foundEnvironments = new bool[expectedEnvironments.Length]; - foreach (var actual in actualEnvironments) - { - for (int i = 0; i < expectedEnvironments.Length; i++) - { - var expected = expectedEnvironments[i]; - if (actual.Context == expected.Context && actual.ProcessorIdentifier == expected.ProcessorIdentifier && expected.PropertiesEqual(actual.ProcessorProperties)) - { - foundEnvironments[i] = true; - break; - } - } - } - - for (int i = 0; i < foundEnvironments.Length; i++) - { - Assert.True(foundEnvironments[i], $"Found expected environment: {i}"); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Microsoft.VisualStudio.TestPlatform.PlatformAbstractions.Interfaces; + using Windows.Foundation.Collections; + using Windows.Storage.Streams; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for configuration set authoring (creating objects). + /// + [Collection("UnitTestCollection")] + [InProc] + [OutOfProc] + public class ConfigurationSetAuthoringTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationSetAuthoringTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// Creates a configuration set and sets all available properties. + /// + [Fact] + public void ConfigurationSetAndProperties() + { + string testName = "Test Name"; + string testOrigin = "Test Origin"; + string testPath = "TestPath.ext"; + + ConfigurationSet testSet = this.ConfigurationSet(); + + testSet.Name = testName; + Assert.Equal(testName, testSet.Name); + testSet.Origin = testOrigin; + Assert.Equal(testOrigin, testSet.Origin); + testSet.Path = testPath; + Assert.Equal(testPath, testSet.Path); + + Assert.NotEqual(Guid.Empty, testSet.InstanceIdentifier); + Assert.Equal(ConfigurationSetState.Unknown, testSet.State); + + Assert.Empty(testSet.Units); + testSet.Units = new ConfigurationUnit[] { this.ConfigurationUnit() }; + Assert.Equal(1, testSet.Units.Count); + + Assert.NotEqual(string.Empty, testSet.SchemaVersion); + } + + /// + /// Creates a configuration unit and sets all available properties. + /// + [Fact] + public void ConfigurationUnitAndProperties() + { + string testName = "Test Name"; + string testIdentifier = "Test Identifier"; + ConfigurationUnitIntent testIntent = ConfigurationUnitIntent.Assert; + + ConfigurationUnit testUnit = this.ConfigurationUnit(); + testUnit.Type = testName; + Assert.Equal(testName, testUnit.Type); + testUnit.Identifier = testIdentifier; + Assert.Equal(testIdentifier, testUnit.Identifier); + + Assert.NotEqual(Guid.Empty, testUnit.InstanceIdentifier); + + Assert.Equal(ConfigurationUnitIntent.Apply, testUnit.Intent); + testUnit.Intent = testIntent; + Assert.Equal(testIntent, testUnit.Intent); + + Assert.Empty(testUnit.Dependencies); + testUnit.Dependencies = new string[] { "dependency1", "dependency2" }; + Assert.Equal(2, testUnit.Dependencies.Count); + + Assert.Empty(testUnit.Metadata); + Assert.Empty(testUnit.Settings); + Assert.Null(testUnit.Details); + + Assert.Equal(ConfigurationUnitState.Unknown, testUnit.State); + + Assert.Null(testUnit.ResultInformation); + + Assert.True(testUnit.IsActive); + testUnit.IsActive = false; + Assert.False(testUnit.IsActive); + } + + /// + /// Basic sanity check to verify that nested value sets can be serialized successfully. + /// + [Fact] + public void ConfigurationSetSerializeNestedValueSets() + { + ConfigurationSet testSet = this.ConfigurationSet(); + + testSet.SchemaVersion = "0.2"; + ConfigurationUnit testUnit = this.ConfigurationUnit(); + string testName = "Test Name"; + string testIdentifier = "Test Identifier"; + testUnit.Type = testName; + testUnit.Identifier = testIdentifier; + + ValueSet innerValueSet = new ValueSet(); + innerValueSet.Add("innerKey", "innerValue"); + + ValueSet outerValueSet = new ValueSet(); + outerValueSet.Add("outerKey", innerValueSet); + testUnit.Metadata = outerValueSet; + testSet.Units.Add(testUnit); + + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + testSet.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + Assert.NotNull(yamlOutput); + } + + /// + /// Test for unique unit environment calculation. + /// + [Fact] + public void ConfigurationSet_UnitEnvironments() + { + ConfigurationSet testSet = this.ConfigurationSet(); + + Dictionary firstProperty = new Dictionary(); + firstProperty.Add("property", "value1"); + + Dictionary secondProperty = new Dictionary(); + secondProperty.Add("property", "value2"); + + Helpers.ConfigurationEnvironmentData[] environments = new Helpers.ConfigurationEnvironmentData[] + { + new () { ProcessorIdentifier = "dscv3" }, + new () { ProcessorIdentifier = "pwsh" }, + new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Elevated }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Restricted }, + new () { ProcessorIdentifier = "dscv3", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = secondProperty }, + new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Restricted, ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Elevated, ProcessorProperties = firstProperty }, + }; + + foreach (int index in new int[] { 0, 1, 1, 2, 3, 5, 4, 6, 7, 8, 2, 7, 7, 7 }) + { + Assert.True(index < environments.Length); + testSet.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); + } + + var uniqueEnvironments = testSet.GetUnitEnvironments(); + this.EnsureEnvironmentEquivalence(environments, uniqueEnvironments); + } + + /// + /// Test for unique unit environment calculation with group units. + /// + [Fact] + public void ConfigurationSet_GroupUnitEnvironments() + { + ConfigurationSet testSet = this.ConfigurationSet(); + + Dictionary firstProperty = new Dictionary(); + firstProperty.Add("property", "value1"); + + Dictionary secondProperty = new Dictionary(); + secondProperty.Add("property", "value2"); + + Helpers.ConfigurationEnvironmentData[] environments = new Helpers.ConfigurationEnvironmentData[] + { + new () { ProcessorIdentifier = "dscv3" }, + new () { ProcessorIdentifier = "pwsh" }, + new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Elevated }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Restricted }, + new () { ProcessorIdentifier = "dscv3", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", ProcessorProperties = secondProperty }, + new () { ProcessorIdentifier = "dscv3", Context = SecurityContext.Restricted, ProcessorProperties = firstProperty }, + new () { ProcessorIdentifier = "pwsh", Context = SecurityContext.Elevated, ProcessorProperties = firstProperty }, + new (), // The default environment for the group unit + }; + + foreach (int index in new int[] { 0, 1, 1, 3, 5, 4, 6, 8 }) + { + Assert.True(index < environments.Length); + testSet.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); + } + + var groupUnit = this.ConfigurationUnit(); + groupUnit.IsGroup = true; + + foreach (int index in new int[] { 7, 5, 2 }) + { + Assert.True(index < environments.Length); + groupUnit.Units.Add(environments[index].ApplyToUnit(this.ConfigurationUnit())); + } + + testSet.Units.Add(groupUnit); + + var uniqueEnvironments = testSet.GetUnitEnvironments(); + this.EnsureEnvironmentEquivalence(environments, uniqueEnvironments); + } + + private void EnsureEnvironmentEquivalence(Helpers.ConfigurationEnvironmentData[] expectedEnvironments, IList? actualEnvironments) + { + Assert.NotNull(actualEnvironments); + Assert.Equal(expectedEnvironments.Length, actualEnvironments.Count); + + bool[] foundEnvironments = new bool[expectedEnvironments.Length]; + foreach (var actual in actualEnvironments) + { + for (int i = 0; i < expectedEnvironments.Length; i++) + { + var expected = expectedEnvironments[i]; + if (actual.Context == expected.Context && actual.ProcessorIdentifier == expected.ProcessorIdentifier && expected.PropertiesEqual(actual.ProcessorProperties)) + { + foundEnvironments[i] = true; + break; + } + } + } + + for (int i = 0; i < foundEnvironments.Length; i++) + { + Assert.True(foundEnvironments[i], $"Found expected environment: {i}"); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs index ed12aacfd5..95f0105907 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationSetProcessorTests.cs @@ -1,983 +1,983 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.PowerShell.Set; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.PowerShell.Commands; - using Moq; - using Windows.Foundation.Collections; - using Windows.Security.Cryptography.Certificates; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for configuration processor tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationSetProcessorTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationSetProcessorTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Test CreateUnitProcessor. Happy path. - /// - [Fact] - public void CreateUnitProcessor_ResourceExists() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("module", moduleName); - unit.Metadata.Add("version", version.ToString()); - - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); - Assert.NotNull(unitProcessor); - Assert.Equal(unit.Type, unitProcessor.Unit.Type); - - processorEnvMock.Verify(); - } - - /// - /// Test CreateUnitProcessor case-insensitive. - /// - [Fact] - public void CreateUnitProcessor_CaseInsensitive() - { - string resourceName = "name"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type.Equals("Name", StringComparison.OrdinalIgnoreCase)))) - .Returns(new DscResourceInfoInternal("Name", moduleName, version)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("module", moduleName); - unit.Metadata.Add("version", version.ToString()); - - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); - Assert.NotNull(unitProcessor); - Assert.Equal(unit.Type, unitProcessor.Unit.Type); - - processorEnvMock.Verify(); - } - - /// - /// Test CreateUnitProcessor case-insensitive. - /// - [Fact] - public void CreateUnitProcessor_ResourceNameMismatch() - { - string resourceName = "name"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(new DscResourceInfoInternal("OtherName", moduleName, version)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("module", moduleName); - unit.Metadata.Add("version", version.ToString()); - - Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(unit)); - - processorEnvMock.Verify(); - } - - /// - /// Test CreateUnitProcessor with no version directive. - /// - [Fact] - public void CreateUnitProcessor_ResourceExists_NoVersionDirective() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0.0.0"); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("module", moduleName); - - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); - Assert.NotNull(unitProcessor); - Assert.Equal(unit.Type, unitProcessor.Unit.Type); - - processorEnvMock.Verify(); - } - - /// - /// Test CreateUnitProcessor with no module directive. - /// - [Fact] - public void CreateUnitProcessor_ResourceExists_NoModuleDirective() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); - Assert.NotNull(unitProcessor); - Assert.Equal(unit.Type, unitProcessor.Unit.Type); - - processorEnvMock.Verify(); - } - - /// - /// Tests Creating a unit processor by downloading the resource. - /// - [Fact] - public void CreateUnitProcessor_InstallResource() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - DscResourceInfoInternal? nullResource = null; - DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); - var processorEnvMock = new Mock(); - processorEnvMock.SetupSequence( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(nullResource) - .Returns(dscResourceInfo); - - PSObject findDscResourceResult = new PSObject(processorEnvMock); - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => c.Unit.Type == resourceName))) - .Returns(findDscResourceResult) - .Verifiable(); - - processorEnvMock.Setup( - m => m.InstallModule(findDscResourceResult)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("module", moduleName); - unit.Metadata.Add("version", version.ToString()); - - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); - Assert.NotNull(unitProcessor); - Assert.Equal(unit.Type, unitProcessor.Unit.Type); - - processorEnvMock.Verify(); - } - - /// - /// Tests Creating a unit processor by downloading the resource. - /// - [Fact] - public void CreateUnitProcessor_InstallResource_WithoutModule() - { - string resourceName = "SimpleFileResource"; - Version version = new Version("0.0.0.1"); - - DscResourceInfoInternal? nullResource = null; - DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, null, version); - var processorEnvMock = new Mock(); - processorEnvMock.SetupSequence( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(nullResource) - .Returns(dscResourceInfo); - - PSObject findDscResourceResult = this.CreateFindResourceInfo(); - processorEnvMock.Setup( - m => m.FindDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(findDscResourceResult) - .Verifiable(); - - PSObject moduleInfo = ((dynamic)findDscResourceResult).PSGetModuleInfo; - processorEnvMock.Setup( - m => m.InstallModule(moduleInfo)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("version", version.ToString()); - - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); - Assert.NotNull(unitProcessor); - Assert.Equal(unit.Type, unitProcessor.Unit.Type); - - processorEnvMock.Verify(); - } - - /// - /// Tests Creating a unit processor by downloading the resource. - /// - [Fact] - public void CreateUnitProcessor_InstallResource_NotFoundAfterInstall() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - DscResourceInfoInternal? nullResource = null; - DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(nullResource); - - PSObject findDscResourceResult = new PSObject(processorEnvMock); - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => c.Unit.Type == resourceName))) - .Returns(findDscResourceResult) - .Verifiable(); - - processorEnvMock.Setup( - m => m.InstallModule(findDscResourceResult)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("module", moduleName); - unit.Metadata.Add("version", version.ToString()); - - Assert.Throws( - () => configurationSetProcessor.CreateUnitProcessor(unit)); - - processorEnvMock.Verify(); - } - - /// - /// Tests Creating a unit processor by downloading the resource. - /// - [Fact] - public void CreateUnitProcessor_InstallResource_NotFound() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - DscResourceInfoInternal? nullResource = null; - DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(nullResource); - - PSObject? findDscResourceResult = null; - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => c.Unit.Type == resourceName))) - .Returns(findDscResourceResult) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = resourceName, - }; - unit.Metadata.Add("module", moduleName); - unit.Metadata.Add("version", version.ToString()); - - Assert.Throws( - () => configurationSetProcessor.CreateUnitProcessor(unit)); - - processorEnvMock.Verify(); - } - - /// - /// Test GetUnitProcessorDetails Local Resource not found. - /// - [Fact] - public void GetUnitProcessorDetails_Local_NoFound() - { - string resourceName = "xResource"; - DscResourceInfoInternal? nullDscInfoInternal = null; - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == resourceName))) - .Returns(nullDscInfoInternal) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var unit = new ConfigurationUnit() - { - Type = resourceName, - }; - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.Local); - - Assert.Null(configurationUnitProcessorDetails); - - processorEnvMock.Verify(); - } - - /// - /// Test GetUnitProcessorDetails Local Found. Module not installed by PowerShellGet. - /// - [Fact] - public void GetUnitProcessorDetails_Local_NotInstalledByPowerShellGet() - { - var unit = this.CreateConfigurationUnit(); - var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); - PSObject? nullPsModuleInfo = null; - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == dscResourceInfo.Name))) - .Returns(dscResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetAvailableModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Returns(psModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Returns(nullPsModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetCertsOfValidSignedFiles(It.IsAny())) - .Returns(new List()) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.Local); - - Assert.NotNull(configurationUnitProcessorDetails); - Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); - - processorEnvMock.Verify(); - } - - /// - /// Test GetUnitProcessorDetails locally found. Do not include Load. - /// - /// Detail flags. - [Theory] - [InlineData(ConfigurationUnitDetailFlags.Local)] - [InlineData(ConfigurationUnitDetailFlags.ReadOnly)] - [InlineData(ConfigurationUnitDetailFlags.Download)] - public void GetUnitProcessorDetails_Local(object detailFlags) - { - var unit = this.CreateConfigurationUnit(); - var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); - var getModuleInfo = this.CreateGetModuleInfo(); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == dscResourceInfo.Name))) - .Returns(dscResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetAvailableModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Returns(psModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Returns(getModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetCertsOfValidSignedFiles(It.IsAny())) - .Returns(new List()) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - Assert.IsType(detailFlags)); - - Assert.NotNull(configurationUnitProcessorDetails); - Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); - - processorEnvMock.Verify(); - processorEnvMock.Verify(m => m.FindDscResource(It.IsAny()), Times.Never()); - processorEnvMock.Verify(m => m.ImportModule(It.IsAny()), Times.Never()); - } - - /// - /// Test GetUnitProcessorDetails locally found and load. - /// - [Fact] - public void GetUnitProcessorDetails_Local_Load() - { - var unit = this.CreateConfigurationUnit(); - var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); - var getModuleInfo = this.CreateGetModuleInfo(); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == dscResourceInfo.Name))) - .Returns(dscResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetAvailableModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Returns(psModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Returns(getModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.ImportModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetCertsOfValidSignedFiles(It.IsAny())) - .Returns(new List()) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.Load); - - Assert.NotNull(configurationUnitProcessorDetails); - Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); - - processorEnvMock.Verify(); - processorEnvMock.Verify(m => m.FindDscResource(It.IsAny()), Times.Never()); - } - - /// - /// Test GetUnitProcessorDetails Catalog Not Found. - /// - [Fact] - public void GetUnitProcessorDetails_Catalog_NotFound() - { - var unit = this.CreateConfigurationUnit(); - DscResourceInfoInternal? nullDscResourceInfo = null; - PSObject? nullPsModuleInfo = null; - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) - .Returns(nullDscResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => unit.Type == unit.Type))) - .Returns(nullPsModuleInfo) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.ReadOnly); - - Assert.Null(configurationUnitProcessorDetails); - - processorEnvMock.Verify(); - } - - /// - /// Test GetUnitProcessorDetails Catalog Found. - /// - [Fact] - public void GetUnitProcessorDetails_Catalog() - { - var unit = this.CreateConfigurationUnit(); - DscResourceInfoInternal? nullDscResourceInfo = null; - var getFindResourceInfo = this.CreateFindResourceInfo(); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) - .Returns(nullDscResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => unit.Type == unit.Type))) - .Returns(getFindResourceInfo) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.ReadOnly); - - Assert.NotNull(configurationUnitProcessorDetails); - Assert.Equal("SimpleFileResource", configurationUnitProcessorDetails.UnitType); - - processorEnvMock.Verify(); - } - - /// - /// Test GetUnitProcessorDetails downloading module. - /// - [Fact] - public void GetUnitProcessorDetails_Download() - { - var unit = this.CreateConfigurationUnit(); - DscResourceInfoInternal? nullDscResourceInfo = null; - var (_, psModuleInfo) = this.GetResourceAndModuleInfo(unit); - var getFindModuleInfo = this.CreateGetModuleInfo(); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) - .Returns(nullDscResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => unit.Type == unit.Type))) - .Returns(getFindModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.SaveModule(getFindModuleInfo, It.IsAny())) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetAvailableModule(It.Is(s => s.EndsWith("xSimpleTestResource")))) - .Returns(psModuleInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetCertsOfValidSignedFiles(It.IsAny())) - .Returns(new List()) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.Download); - - Assert.NotNull(configurationUnitProcessorDetails); - Assert.Equal("SimpleFileResource", configurationUnitProcessorDetails.UnitType); - - processorEnvMock.Verify(); - - processorEnvMock.Verify(m => m.InstallModule(It.IsAny()), Times.Never()); - } - - /// - /// Tests GetUnitProcessorDetails install module, but resource not found anyway. - /// - [Fact] - public void GetUnitProcessorDetails_Load_NotFoundAfterInstall() - { - var unit = this.CreateConfigurationUnit(); - DscResourceInfoInternal? nullDscResourceInfo = null; - var (_, psModuleInfo) = this.GetResourceAndModuleInfo(unit); - var getFindResourceInfo = this.CreateFindResourceInfo(); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) - .Returns(nullDscResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => unit.Type == unit.Type))) - .Returns(getFindResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.InstallModule(getFindResourceInfo)) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - Assert.Throws(() => configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.Load)); - - processorEnvMock.Verify(); - processorEnvMock.Verify( - m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type)), - Times.Exactly(2)); - } - - /// - /// Tests GetUnitProcessorDetails install module. - /// - [Fact] - public void GetUnitProcessorDetails_Load() - { - var unit = this.CreateConfigurationUnit(); - DscResourceInfoInternal? nullDscResourceInfo = null; - var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); - var getFindResourceInfo = this.CreateFindResourceInfo(); - - var processorEnvMock = new Mock(); - processorEnvMock.SetupSequence( - m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) - .Returns(nullDscResourceInfo) - .Returns(dscResourceInfo); - processorEnvMock.Setup( - m => m.FindModule(It.Is(c => unit.Type == unit.Type))) - .Returns(getFindResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.InstallModule(getFindResourceInfo)) - .Verifiable(); - processorEnvMock.Setup( - m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Returns(getFindResourceInfo) - .Verifiable(); - processorEnvMock.Setup( - m => m.ImportModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) - .Verifiable(); - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - new ConfigurationSet()); - - var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( - unit, - ConfigurationUnitDetailFlags.Load); - - Assert.NotNull(configurationUnitProcessorDetails); - Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); - - processorEnvMock.Verify(); - processorEnvMock.Verify( - m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type)), - Times.Exactly(2)); - } - - /// - /// This tests uses SimpleTestResourceTypes Test to validate the resource got the correct types - /// from the processor. - /// - [Fact] - public void CreateUnitProcessor_TestTypes() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - var setProcessor = new PowerShellConfigurationSetProcessor(processorEnv, new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = "SimpleTestResourceTypes", - Intent = ConfigurationUnitIntent.Assert, - }; - - unit.Metadata.Add("module", "xSimpleTestResource"); - unit.Metadata.Add("version", "0.0.0.1"); - - var hashtableProperty = new ValueSet - { - { "secretStringKey", "secretCode" }, - { "secretIntKey", "123456" }, - }; - - unit.Settings.Add("boolProperty", true); - unit.Settings.Add("intProperty", 3); - unit.Settings.Add("doubleProperty", -9.876); - unit.Settings.Add("charProperty", 'f'); - unit.Settings.Add("hashtableProperty", hashtableProperty); - - var unitProcessor = setProcessor.CreateUnitProcessor(unit); - - unitProcessor.TestSettings(); - } - - /// - /// Tests a module that requires admin is loaded from non admin. - /// - [FactSkipIfCI] - public void CreateUnitProcessor_ModuleRequiresAdmin() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - var setProcessor = new PowerShellConfigurationSetProcessor(processorEnv, new ConfigurationSet()); - - var unit = new ConfigurationUnit - { - Type = "AdminResource", - Intent = ConfigurationUnitIntent.Assert, - }; - unit.Metadata.Add("module", "xAdminTestResource"); - unit.Metadata.Add("version", "0.0.0.1"); - - unit.Settings.Add("key", "key"); - - var importModuleException = Assert.Throws(() => setProcessor.CreateUnitProcessor(unit)); - Assert.Equal(ErrorCodes.WinGetConfigUnitImportModuleAdmin, importModuleException.HResult); - } - - /// - /// Test CreateUnitProcessor. Limit mode. - /// - [Fact] - public void CreateUnitProcessor_LimitMode() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) - .Verifiable(); - - var limitSet = new ConfigurationSet(); - var limitUnit = new ConfigurationUnit - { - Type = resourceName, - Intent = ConfigurationUnitIntent.Apply, - }; - limitUnit.Metadata.Add("module", moduleName); - limitUnit.Metadata.Add("version", version.ToString()); - limitSet.Units = new List { limitUnit }; - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - limitSet, - true); - - // Calling with unit different from limit set should throw. - var unitDifferentContent = new ConfigurationUnit - { - Type = "differentResourceName", - Intent = ConfigurationUnitIntent.Apply, - }; - - Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(unitDifferentContent)); - Assert.Throws(() => configurationSetProcessor.GetUnitProcessorDetails(unitDifferentContent, ConfigurationUnitDetailFlags.Load)); - - // Calling with unit matching limit set. - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(limitUnit); - Assert.NotNull(unitProcessor); - Assert.Equal(limitUnit.Type, unitProcessor.Unit.Type); - var processorDetails = configurationSetProcessor.GetUnitProcessorDetails(limitUnit, ConfigurationUnitDetailFlags.Load); - Assert.NotNull(processorDetails); - Assert.Equal(moduleName, processorDetails.ModuleName); - - // Calling CreateProcessor again should thow. Calling GetProcessorDetails multiple times is ok. - Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(limitUnit)); - var processorDetails2 = configurationSetProcessor.GetUnitProcessorDetails(limitUnit, ConfigurationUnitDetailFlags.Load); - Assert.NotNull(processorDetails2); - Assert.Equal(moduleName, processorDetails2.ModuleName); - } - - /// - /// Test CreateUnitProcessor. Limit mode. Duplicate units in limit set. - /// - [Fact] - public void CreateUnitProcessor_LimitMode_DuplicateUnits() - { - string resourceName = "xResourceName"; - string moduleName = "xModuleName"; - Version version = new Version("1.0"); - - var processorEnvMock = new Mock(); - processorEnvMock.Setup( - m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) - .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) - .Verifiable(); - - var limitSet = new ConfigurationSet(); - var limitUnit = new ConfigurationUnit - { - Type = resourceName, - Intent = ConfigurationUnitIntent.Apply, - }; - limitUnit.Metadata.Add("module", moduleName); - limitUnit.Metadata.Add("version", version.ToString()); - limitSet.Units = new List { limitUnit, limitUnit }; - - var configurationSetProcessor = new PowerShellConfigurationSetProcessor( - processorEnvMock.Object, - limitSet, - true); - - // Calling with unit different from limit set should throw. - var unitDifferentContent = new ConfigurationUnit - { - Type = "differentResourceName", - Intent = ConfigurationUnitIntent.Apply, - }; - - Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(unitDifferentContent)); - - // Calling with unit matching limit set. - var unitProcessor = configurationSetProcessor.CreateUnitProcessor(limitUnit); - Assert.NotNull(unitProcessor); - Assert.Equal(limitUnit.Type, unitProcessor.Unit.Type); - - // Calling again should also not thow. - var unitProcessor2 = configurationSetProcessor.CreateUnitProcessor(limitUnit); - Assert.NotNull(unitProcessor2); - Assert.Equal(limitUnit.Type, unitProcessor2.Unit.Type); - - // Calling third time should throw. - Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(limitUnit)); - } - - private ConfigurationUnit CreateConfigurationUnit() - { - var unit = new ConfigurationUnit(); - unit.Type = "SimpleFileResource"; - unit.Metadata.Add("module", "xSimpleTestResource"); - unit.Metadata.Add("version", "0.0.0.1"); - - return unit; - } - - private (DscResourceInfoInternal dscResourceInfo, PSModuleInfo psModuleInfo) GetResourceAndModuleInfo(ConfigurationUnit unit) - { - // This is easier than trying to mock sealed class from external code... - var testEnv = this.fixture.PrepareTestProcessorEnvironment(true); - var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitAndModule(unit, string.Empty)); - var psModuleInfo = testEnv.GetAvailableModule(PowerShellHelpers.CreateModuleSpecification("xSimpleTestResource", "0.0.0.1")); - - if (dscResourceInfo is null || psModuleInfo is null) - { - throw new ArgumentNullException("Test processor environment not set correctly"); - } - - return (dscResourceInfo, psModuleInfo); - } - - private PSObject CreateGetModuleInfo() - { - return new PSObject(new - { - Repository = "PSGallery", - PublishedDate = new DateTime(2017, 12, 10), - IconUri = "https://github.com/microsoft/winget-cli", - Name = "xSimpleTestResource", - Description = "PowerShell module with DSC resources for unit tests", - RepositorySourceLocation = "https://github.com/microsoft/winget-cli", - Version = "0.0.0.1", - Author = "Luffytaro", - CompanyName = "Microsoft Corporation", - }); - } - - private PSObject CreateFindResourceInfo() - { - var getModuleInfo = this.CreateGetModuleInfo(); - return new PSObject(new - { - Name = "SimpleFileResource", - ModuleName = "xSimpleTestResource", - Version = "0.0.0.1", - PSGetModuleInfo = getModuleInfo, - }); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Set; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.PowerShell.Commands; + using Moq; + using Windows.Foundation.Collections; + using Windows.Security.Cryptography.Certificates; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for configuration processor tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationSetProcessorTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationSetProcessorTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Test CreateUnitProcessor. Happy path. + /// + [Fact] + public void CreateUnitProcessor_ResourceExists() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("version", version.ToString()); + + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); + Assert.NotNull(unitProcessor); + Assert.Equal(unit.Type, unitProcessor.Unit.Type); + + processorEnvMock.Verify(); + } + + /// + /// Test CreateUnitProcessor case-insensitive. + /// + [Fact] + public void CreateUnitProcessor_CaseInsensitive() + { + string resourceName = "name"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type.Equals("Name", StringComparison.OrdinalIgnoreCase)))) + .Returns(new DscResourceInfoInternal("Name", moduleName, version)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("version", version.ToString()); + + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); + Assert.NotNull(unitProcessor); + Assert.Equal(unit.Type, unitProcessor.Unit.Type); + + processorEnvMock.Verify(); + } + + /// + /// Test CreateUnitProcessor case-insensitive. + /// + [Fact] + public void CreateUnitProcessor_ResourceNameMismatch() + { + string resourceName = "name"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal("OtherName", moduleName, version)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("version", version.ToString()); + + Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(unit)); + + processorEnvMock.Verify(); + } + + /// + /// Test CreateUnitProcessor with no version directive. + /// + [Fact] + public void CreateUnitProcessor_ResourceExists_NoVersionDirective() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0.0.0"); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("module", moduleName); + + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); + Assert.NotNull(unitProcessor); + Assert.Equal(unit.Type, unitProcessor.Unit.Type); + + processorEnvMock.Verify(); + } + + /// + /// Test CreateUnitProcessor with no module directive. + /// + [Fact] + public void CreateUnitProcessor_ResourceExists_NoModuleDirective() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); + Assert.NotNull(unitProcessor); + Assert.Equal(unit.Type, unitProcessor.Unit.Type); + + processorEnvMock.Verify(); + } + + /// + /// Tests Creating a unit processor by downloading the resource. + /// + [Fact] + public void CreateUnitProcessor_InstallResource() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + DscResourceInfoInternal? nullResource = null; + DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); + var processorEnvMock = new Mock(); + processorEnvMock.SetupSequence( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(nullResource) + .Returns(dscResourceInfo); + + PSObject findDscResourceResult = new PSObject(processorEnvMock); + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => c.Unit.Type == resourceName))) + .Returns(findDscResourceResult) + .Verifiable(); + + processorEnvMock.Setup( + m => m.InstallModule(findDscResourceResult)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("version", version.ToString()); + + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); + Assert.NotNull(unitProcessor); + Assert.Equal(unit.Type, unitProcessor.Unit.Type); + + processorEnvMock.Verify(); + } + + /// + /// Tests Creating a unit processor by downloading the resource. + /// + [Fact] + public void CreateUnitProcessor_InstallResource_WithoutModule() + { + string resourceName = "SimpleFileResource"; + Version version = new Version("0.0.0.1"); + + DscResourceInfoInternal? nullResource = null; + DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, null, version); + var processorEnvMock = new Mock(); + processorEnvMock.SetupSequence( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(nullResource) + .Returns(dscResourceInfo); + + PSObject findDscResourceResult = this.CreateFindResourceInfo(); + processorEnvMock.Setup( + m => m.FindDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(findDscResourceResult) + .Verifiable(); + + PSObject moduleInfo = ((dynamic)findDscResourceResult).PSGetModuleInfo; + processorEnvMock.Setup( + m => m.InstallModule(moduleInfo)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("version", version.ToString()); + + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(unit); + Assert.NotNull(unitProcessor); + Assert.Equal(unit.Type, unitProcessor.Unit.Type); + + processorEnvMock.Verify(); + } + + /// + /// Tests Creating a unit processor by downloading the resource. + /// + [Fact] + public void CreateUnitProcessor_InstallResource_NotFoundAfterInstall() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + DscResourceInfoInternal? nullResource = null; + DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(nullResource); + + PSObject findDscResourceResult = new PSObject(processorEnvMock); + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => c.Unit.Type == resourceName))) + .Returns(findDscResourceResult) + .Verifiable(); + + processorEnvMock.Setup( + m => m.InstallModule(findDscResourceResult)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("version", version.ToString()); + + Assert.Throws( + () => configurationSetProcessor.CreateUnitProcessor(unit)); + + processorEnvMock.Verify(); + } + + /// + /// Tests Creating a unit processor by downloading the resource. + /// + [Fact] + public void CreateUnitProcessor_InstallResource_NotFound() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + DscResourceInfoInternal? nullResource = null; + DscResourceInfoInternal dscResourceInfo = new DscResourceInfoInternal(resourceName, moduleName, version); + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(nullResource); + + PSObject? findDscResourceResult = null; + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => c.Unit.Type == resourceName))) + .Returns(findDscResourceResult) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = resourceName, + }; + unit.Metadata.Add("module", moduleName); + unit.Metadata.Add("version", version.ToString()); + + Assert.Throws( + () => configurationSetProcessor.CreateUnitProcessor(unit)); + + processorEnvMock.Verify(); + } + + /// + /// Test GetUnitProcessorDetails Local Resource not found. + /// + [Fact] + public void GetUnitProcessorDetails_Local_NoFound() + { + string resourceName = "xResource"; + DscResourceInfoInternal? nullDscInfoInternal = null; + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == resourceName))) + .Returns(nullDscInfoInternal) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var unit = new ConfigurationUnit() + { + Type = resourceName, + }; + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.Local); + + Assert.Null(configurationUnitProcessorDetails); + + processorEnvMock.Verify(); + } + + /// + /// Test GetUnitProcessorDetails Local Found. Module not installed by PowerShellGet. + /// + [Fact] + public void GetUnitProcessorDetails_Local_NotInstalledByPowerShellGet() + { + var unit = this.CreateConfigurationUnit(); + var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); + PSObject? nullPsModuleInfo = null; + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == dscResourceInfo.Name))) + .Returns(dscResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetAvailableModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Returns(psModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Returns(nullPsModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetCertsOfValidSignedFiles(It.IsAny())) + .Returns(new List()) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.Local); + + Assert.NotNull(configurationUnitProcessorDetails); + Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); + + processorEnvMock.Verify(); + } + + /// + /// Test GetUnitProcessorDetails locally found. Do not include Load. + /// + /// Detail flags. + [Theory] + [InlineData(ConfigurationUnitDetailFlags.Local)] + [InlineData(ConfigurationUnitDetailFlags.ReadOnly)] + [InlineData(ConfigurationUnitDetailFlags.Download)] + public void GetUnitProcessorDetails_Local(object detailFlags) + { + var unit = this.CreateConfigurationUnit(); + var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); + var getModuleInfo = this.CreateGetModuleInfo(); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == dscResourceInfo.Name))) + .Returns(dscResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetAvailableModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Returns(psModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Returns(getModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetCertsOfValidSignedFiles(It.IsAny())) + .Returns(new List()) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + Assert.IsType(detailFlags)); + + Assert.NotNull(configurationUnitProcessorDetails); + Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); + + processorEnvMock.Verify(); + processorEnvMock.Verify(m => m.FindDscResource(It.IsAny()), Times.Never()); + processorEnvMock.Verify(m => m.ImportModule(It.IsAny()), Times.Never()); + } + + /// + /// Test GetUnitProcessorDetails locally found and load. + /// + [Fact] + public void GetUnitProcessorDetails_Local_Load() + { + var unit = this.CreateConfigurationUnit(); + var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); + var getModuleInfo = this.CreateGetModuleInfo(); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == dscResourceInfo.Name))) + .Returns(dscResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetAvailableModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Returns(psModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Returns(getModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.ImportModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetCertsOfValidSignedFiles(It.IsAny())) + .Returns(new List()) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.Load); + + Assert.NotNull(configurationUnitProcessorDetails); + Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); + + processorEnvMock.Verify(); + processorEnvMock.Verify(m => m.FindDscResource(It.IsAny()), Times.Never()); + } + + /// + /// Test GetUnitProcessorDetails Catalog Not Found. + /// + [Fact] + public void GetUnitProcessorDetails_Catalog_NotFound() + { + var unit = this.CreateConfigurationUnit(); + DscResourceInfoInternal? nullDscResourceInfo = null; + PSObject? nullPsModuleInfo = null; + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) + .Returns(nullDscResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => unit.Type == unit.Type))) + .Returns(nullPsModuleInfo) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.ReadOnly); + + Assert.Null(configurationUnitProcessorDetails); + + processorEnvMock.Verify(); + } + + /// + /// Test GetUnitProcessorDetails Catalog Found. + /// + [Fact] + public void GetUnitProcessorDetails_Catalog() + { + var unit = this.CreateConfigurationUnit(); + DscResourceInfoInternal? nullDscResourceInfo = null; + var getFindResourceInfo = this.CreateFindResourceInfo(); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) + .Returns(nullDscResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => unit.Type == unit.Type))) + .Returns(getFindResourceInfo) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.ReadOnly); + + Assert.NotNull(configurationUnitProcessorDetails); + Assert.Equal("SimpleFileResource", configurationUnitProcessorDetails.UnitType); + + processorEnvMock.Verify(); + } + + /// + /// Test GetUnitProcessorDetails downloading module. + /// + [Fact] + public void GetUnitProcessorDetails_Download() + { + var unit = this.CreateConfigurationUnit(); + DscResourceInfoInternal? nullDscResourceInfo = null; + var (_, psModuleInfo) = this.GetResourceAndModuleInfo(unit); + var getFindModuleInfo = this.CreateGetModuleInfo(); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) + .Returns(nullDscResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => unit.Type == unit.Type))) + .Returns(getFindModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.SaveModule(getFindModuleInfo, It.IsAny())) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetAvailableModule(It.Is(s => s.EndsWith("xSimpleTestResource")))) + .Returns(psModuleInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetCertsOfValidSignedFiles(It.IsAny())) + .Returns(new List()) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.Download); + + Assert.NotNull(configurationUnitProcessorDetails); + Assert.Equal("SimpleFileResource", configurationUnitProcessorDetails.UnitType); + + processorEnvMock.Verify(); + + processorEnvMock.Verify(m => m.InstallModule(It.IsAny()), Times.Never()); + } + + /// + /// Tests GetUnitProcessorDetails install module, but resource not found anyway. + /// + [Fact] + public void GetUnitProcessorDetails_Load_NotFoundAfterInstall() + { + var unit = this.CreateConfigurationUnit(); + DscResourceInfoInternal? nullDscResourceInfo = null; + var (_, psModuleInfo) = this.GetResourceAndModuleInfo(unit); + var getFindResourceInfo = this.CreateFindResourceInfo(); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) + .Returns(nullDscResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => unit.Type == unit.Type))) + .Returns(getFindResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.InstallModule(getFindResourceInfo)) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + Assert.Throws(() => configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.Load)); + + processorEnvMock.Verify(); + processorEnvMock.Verify( + m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type)), + Times.Exactly(2)); + } + + /// + /// Tests GetUnitProcessorDetails install module. + /// + [Fact] + public void GetUnitProcessorDetails_Load() + { + var unit = this.CreateConfigurationUnit(); + DscResourceInfoInternal? nullDscResourceInfo = null; + var (dscResourceInfo, psModuleInfo) = this.GetResourceAndModuleInfo(unit); + var getFindResourceInfo = this.CreateFindResourceInfo(); + + var processorEnvMock = new Mock(); + processorEnvMock.SetupSequence( + m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type))) + .Returns(nullDscResourceInfo) + .Returns(dscResourceInfo); + processorEnvMock.Setup( + m => m.FindModule(It.Is(c => unit.Type == unit.Type))) + .Returns(getFindResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.InstallModule(getFindResourceInfo)) + .Verifiable(); + processorEnvMock.Setup( + m => m.GetInstalledModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Returns(getFindResourceInfo) + .Verifiable(); + processorEnvMock.Setup( + m => m.ImportModule(It.Is(s => s.Name == dscResourceInfo.ModuleName))) + .Verifiable(); + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + new ConfigurationSet()); + + var configurationUnitProcessorDetails = configurationSetProcessor.GetUnitProcessorDetails( + unit, + ConfigurationUnitDetailFlags.Load); + + Assert.NotNull(configurationUnitProcessorDetails); + Assert.Equal(dscResourceInfo.Name, configurationUnitProcessorDetails.UnitType); + + processorEnvMock.Verify(); + processorEnvMock.Verify( + m => m.GetDscResource(It.Is(u => u.Unit.Type == unit.Type)), + Times.Exactly(2)); + } + + /// + /// This tests uses SimpleTestResourceTypes Test to validate the resource got the correct types + /// from the processor. + /// + [Fact] + public void CreateUnitProcessor_TestTypes() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + var setProcessor = new PowerShellConfigurationSetProcessor(processorEnv, new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = "SimpleTestResourceTypes", + Intent = ConfigurationUnitIntent.Assert, + }; + + unit.Metadata.Add("module", "xSimpleTestResource"); + unit.Metadata.Add("version", "0.0.0.1"); + + var hashtableProperty = new ValueSet + { + { "secretStringKey", "secretCode" }, + { "secretIntKey", "123456" }, + }; + + unit.Settings.Add("boolProperty", true); + unit.Settings.Add("intProperty", 3); + unit.Settings.Add("doubleProperty", -9.876); + unit.Settings.Add("charProperty", 'f'); + unit.Settings.Add("hashtableProperty", hashtableProperty); + + var unitProcessor = setProcessor.CreateUnitProcessor(unit); + + unitProcessor.TestSettings(); + } + + /// + /// Tests a module that requires admin is loaded from non admin. + /// + [FactSkipIfCI] + public void CreateUnitProcessor_ModuleRequiresAdmin() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + var setProcessor = new PowerShellConfigurationSetProcessor(processorEnv, new ConfigurationSet()); + + var unit = new ConfigurationUnit + { + Type = "AdminResource", + Intent = ConfigurationUnitIntent.Assert, + }; + unit.Metadata.Add("module", "xAdminTestResource"); + unit.Metadata.Add("version", "0.0.0.1"); + + unit.Settings.Add("key", "key"); + + var importModuleException = Assert.Throws(() => setProcessor.CreateUnitProcessor(unit)); + Assert.Equal(ErrorCodes.WinGetConfigUnitImportModuleAdmin, importModuleException.HResult); + } + + /// + /// Test CreateUnitProcessor. Limit mode. + /// + [Fact] + public void CreateUnitProcessor_LimitMode() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) + .Verifiable(); + + var limitSet = new ConfigurationSet(); + var limitUnit = new ConfigurationUnit + { + Type = resourceName, + Intent = ConfigurationUnitIntent.Apply, + }; + limitUnit.Metadata.Add("module", moduleName); + limitUnit.Metadata.Add("version", version.ToString()); + limitSet.Units = new List { limitUnit }; + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + limitSet, + true); + + // Calling with unit different from limit set should throw. + var unitDifferentContent = new ConfigurationUnit + { + Type = "differentResourceName", + Intent = ConfigurationUnitIntent.Apply, + }; + + Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(unitDifferentContent)); + Assert.Throws(() => configurationSetProcessor.GetUnitProcessorDetails(unitDifferentContent, ConfigurationUnitDetailFlags.Load)); + + // Calling with unit matching limit set. + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(limitUnit); + Assert.NotNull(unitProcessor); + Assert.Equal(limitUnit.Type, unitProcessor.Unit.Type); + var processorDetails = configurationSetProcessor.GetUnitProcessorDetails(limitUnit, ConfigurationUnitDetailFlags.Load); + Assert.NotNull(processorDetails); + Assert.Equal(moduleName, processorDetails.ModuleName); + + // Calling CreateProcessor again should thow. Calling GetProcessorDetails multiple times is ok. + Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(limitUnit)); + var processorDetails2 = configurationSetProcessor.GetUnitProcessorDetails(limitUnit, ConfigurationUnitDetailFlags.Load); + Assert.NotNull(processorDetails2); + Assert.Equal(moduleName, processorDetails2.ModuleName); + } + + /// + /// Test CreateUnitProcessor. Limit mode. Duplicate units in limit set. + /// + [Fact] + public void CreateUnitProcessor_LimitMode_DuplicateUnits() + { + string resourceName = "xResourceName"; + string moduleName = "xModuleName"; + Version version = new Version("1.0"); + + var processorEnvMock = new Mock(); + processorEnvMock.Setup( + m => m.GetDscResource(It.Is(c => c.Unit.Type == resourceName))) + .Returns(new DscResourceInfoInternal(resourceName, moduleName, version)) + .Verifiable(); + + var limitSet = new ConfigurationSet(); + var limitUnit = new ConfigurationUnit + { + Type = resourceName, + Intent = ConfigurationUnitIntent.Apply, + }; + limitUnit.Metadata.Add("module", moduleName); + limitUnit.Metadata.Add("version", version.ToString()); + limitSet.Units = new List { limitUnit, limitUnit }; + + var configurationSetProcessor = new PowerShellConfigurationSetProcessor( + processorEnvMock.Object, + limitSet, + true); + + // Calling with unit different from limit set should throw. + var unitDifferentContent = new ConfigurationUnit + { + Type = "differentResourceName", + Intent = ConfigurationUnitIntent.Apply, + }; + + Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(unitDifferentContent)); + + // Calling with unit matching limit set. + var unitProcessor = configurationSetProcessor.CreateUnitProcessor(limitUnit); + Assert.NotNull(unitProcessor); + Assert.Equal(limitUnit.Type, unitProcessor.Unit.Type); + + // Calling again should also not thow. + var unitProcessor2 = configurationSetProcessor.CreateUnitProcessor(limitUnit); + Assert.NotNull(unitProcessor2); + Assert.Equal(limitUnit.Type, unitProcessor2.Unit.Type); + + // Calling third time should throw. + Assert.Throws(() => configurationSetProcessor.CreateUnitProcessor(limitUnit)); + } + + private ConfigurationUnit CreateConfigurationUnit() + { + var unit = new ConfigurationUnit(); + unit.Type = "SimpleFileResource"; + unit.Metadata.Add("module", "xSimpleTestResource"); + unit.Metadata.Add("version", "0.0.0.1"); + + return unit; + } + + private (DscResourceInfoInternal dscResourceInfo, PSModuleInfo psModuleInfo) GetResourceAndModuleInfo(ConfigurationUnit unit) + { + // This is easier than trying to mock sealed class from external code... + var testEnv = this.fixture.PrepareTestProcessorEnvironment(true); + var dscResourceInfo = testEnv.GetDscResource(new ConfigurationUnitAndModule(unit, string.Empty)); + var psModuleInfo = testEnv.GetAvailableModule(PowerShellHelpers.CreateModuleSpecification("xSimpleTestResource", "0.0.0.1")); + + if (dscResourceInfo is null || psModuleInfo is null) + { + throw new ArgumentNullException("Test processor environment not set correctly"); + } + + return (dscResourceInfo, psModuleInfo); + } + + private PSObject CreateGetModuleInfo() + { + return new PSObject(new + { + Repository = "PSGallery", + PublishedDate = new DateTime(2017, 12, 10), + IconUri = "https://github.com/microsoft/winget-cli", + Name = "xSimpleTestResource", + Description = "PowerShell module with DSC resources for unit tests", + RepositorySourceLocation = "https://github.com/microsoft/winget-cli", + Version = "0.0.0.1", + Author = "Luffytaro", + CompanyName = "Microsoft Corporation", + }); + } + + private PSObject CreateFindResourceInfo() + { + var getModuleInfo = this.CreateGetModuleInfo(); + return new PSObject(new + { + Name = "SimpleFileResource", + ModuleName = "xSimpleTestResource", + Version = "0.0.0.1", + PSGetModuleInfo = getModuleInfo, + }); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs index fc5f18e5b5..d2243dc0a1 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitInternalTests.cs @@ -1,159 +1,159 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Management.Automation; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// Tests ConfigurationUnitExtensionsTests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationUnitInternalTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationUnitInternalTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Test GetDirectives. - /// - [Fact] - public void GetDirectivesTest() - { - string moduleDirective = "module"; - string unitModule = "xModule"; - - string versionDirective = "version"; - string unitVersion = "1.0.0.0"; - - string descriptionDirective = "description"; - string unitDescription = "beep beep boop i am a text"; - - string boolDirective = "boolDirective"; - bool boolDirectiveValue = true; - - string boolDirective2 = "boolDirective2"; - bool boolDirective2Value = false; - - var unit = new ConfigurationUnit().Assign(new { Type = $"{unitModule}/unitResource" }); - unit.Metadata.Add(moduleDirective, unitModule); - unit.Metadata.Add(versionDirective, unitVersion); - unit.Metadata.Add(descriptionDirective, unitDescription); - unit.Metadata.Add(boolDirective, boolDirectiveValue); - unit.Metadata.Add(boolDirective2, boolDirective2Value); - - var unitInternal = new ConfigurationUnitAndModule(unit, string.Empty); - - var description = unitInternal.GetDirective(descriptionDirective); - Assert.Equal(description, unitDescription); - - var fake = unitInternal.GetDirective("fake"); - Assert.Null(fake); - - var description2 = unitInternal.GetDirective("DESCRIPTION"); - Assert.Equal(description2, unitDescription); - - Assert.Equal(unitModule, unitInternal.Module!.Name); - - Assert.Equal(Version.Parse(unitVersion), unitInternal.Module!.RequiredVersion); - - Assert.Equal(boolDirectiveValue, unitInternal.GetDirective(boolDirective)); - Assert.Equal(boolDirective2Value, unitInternal.GetDirective(boolDirective2)); - Assert.Null(unitInternal.GetDirective("fakeBool")); - } - - /// - /// Tests GetVersion with a bad version. - /// - [Fact] - public void GetVersion_BadVersion() - { - var unit = new ConfigurationUnit(); - unit.Metadata.Add("module", "module"); - unit.Metadata.Add("version", "not a version"); - - Assert.Throws( - () => new ConfigurationUnitInternal(unit, string.Empty)); - } - - /// - /// Verifies expansion of ConfigRoot. - /// - [Fact] - public void GetExpandedSettings_ConfigRoot() - { - using var tmpFile = new TempFile("fakeConfigFile.yml", content: "content"); - - var unit = new ConfigurationUnit().Assign(new { Type = "unitModule/unitResource" }); - unit.Settings.Add("var1", @"$WinGetConfigRoot\this\is\a\path.txt"); - unit.Settings.Add("var2", @"${WinGetConfigRoot}\this\is\a\path.txt"); - unit.Settings.Add("var3", @"this\is\a\$WINGETCONFIGROOT\path.txt"); - unit.Settings.Add("var4", @"this\is\a\${WINGETCONFIGROOT}\path.txt"); - unit.Settings.Add("var5", @"this\is\a\path\$wingetconfigroot"); - unit.Settings.Add("var6", @"this\is\a\path\${wingetconfigroot}"); - - string configPath = tmpFile.FullFileName; - string? expectedPath = Path.GetDirectoryName(configPath); - var unitInternal = new ConfigurationUnitInternal(unit, configPath); - - var expandedSettings = unitInternal.GetExpandedSettings(); - - var var1 = expandedSettings["var1"]; - Assert.Equal(@"$WinGetConfigRoot\this\is\a\path.txt", var1 as string); - - var var2 = expandedSettings["var2"]; - Assert.Equal($@"{expectedPath}\this\is\a\path.txt", var2 as string); - - var var3 = expandedSettings["var3"]; - Assert.Equal(@"this\is\a\$WINGETCONFIGROOT\path.txt", var3 as string); - - var var4 = expandedSettings["var4"]; - Assert.Equal($@"this\is\a\{expectedPath}\path.txt", var4 as string); - - var var5 = expandedSettings["var5"]; - Assert.Equal(@"this\is\a\path\$wingetconfigroot", var5 as string); - - var var6 = expandedSettings["var6"]; - Assert.Equal($@"this\is\a\path\{expectedPath}", var6 as string); - } - - /// - /// Verifies throws when config root is not set. - /// - [Fact] - public void GetExpandedSetting_ConfigRoot_Throw() - { - var unit = new ConfigurationUnit().Assign(new { Type = "unitModule/unitResource" }); - unit.Settings.Add("var2", @"${WinGetConfigRoot}\this\is\a\path.txt"); - - var unitInternal = new ConfigurationUnitInternal(unit, null!); - Assert.Throws(() => unitInternal.GetExpandedSettings()); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Management.Automation; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests ConfigurationUnitExtensionsTests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationUnitInternalTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationUnitInternalTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Test GetDirectives. + /// + [Fact] + public void GetDirectivesTest() + { + string moduleDirective = "module"; + string unitModule = "xModule"; + + string versionDirective = "version"; + string unitVersion = "1.0.0.0"; + + string descriptionDirective = "description"; + string unitDescription = "beep beep boop i am a text"; + + string boolDirective = "boolDirective"; + bool boolDirectiveValue = true; + + string boolDirective2 = "boolDirective2"; + bool boolDirective2Value = false; + + var unit = new ConfigurationUnit().Assign(new { Type = $"{unitModule}/unitResource" }); + unit.Metadata.Add(moduleDirective, unitModule); + unit.Metadata.Add(versionDirective, unitVersion); + unit.Metadata.Add(descriptionDirective, unitDescription); + unit.Metadata.Add(boolDirective, boolDirectiveValue); + unit.Metadata.Add(boolDirective2, boolDirective2Value); + + var unitInternal = new ConfigurationUnitAndModule(unit, string.Empty); + + var description = unitInternal.GetDirective(descriptionDirective); + Assert.Equal(description, unitDescription); + + var fake = unitInternal.GetDirective("fake"); + Assert.Null(fake); + + var description2 = unitInternal.GetDirective("DESCRIPTION"); + Assert.Equal(description2, unitDescription); + + Assert.Equal(unitModule, unitInternal.Module!.Name); + + Assert.Equal(Version.Parse(unitVersion), unitInternal.Module!.RequiredVersion); + + Assert.Equal(boolDirectiveValue, unitInternal.GetDirective(boolDirective)); + Assert.Equal(boolDirective2Value, unitInternal.GetDirective(boolDirective2)); + Assert.Null(unitInternal.GetDirective("fakeBool")); + } + + /// + /// Tests GetVersion with a bad version. + /// + [Fact] + public void GetVersion_BadVersion() + { + var unit = new ConfigurationUnit(); + unit.Metadata.Add("module", "module"); + unit.Metadata.Add("version", "not a version"); + + Assert.Throws( + () => new ConfigurationUnitInternal(unit, string.Empty)); + } + + /// + /// Verifies expansion of ConfigRoot. + /// + [Fact] + public void GetExpandedSettings_ConfigRoot() + { + using var tmpFile = new TempFile("fakeConfigFile.yml", content: "content"); + + var unit = new ConfigurationUnit().Assign(new { Type = "unitModule/unitResource" }); + unit.Settings.Add("var1", @"$WinGetConfigRoot\this\is\a\path.txt"); + unit.Settings.Add("var2", @"${WinGetConfigRoot}\this\is\a\path.txt"); + unit.Settings.Add("var3", @"this\is\a\$WINGETCONFIGROOT\path.txt"); + unit.Settings.Add("var4", @"this\is\a\${WINGETCONFIGROOT}\path.txt"); + unit.Settings.Add("var5", @"this\is\a\path\$wingetconfigroot"); + unit.Settings.Add("var6", @"this\is\a\path\${wingetconfigroot}"); + + string configPath = tmpFile.FullFileName; + string? expectedPath = Path.GetDirectoryName(configPath); + var unitInternal = new ConfigurationUnitInternal(unit, configPath); + + var expandedSettings = unitInternal.GetExpandedSettings(); + + var var1 = expandedSettings["var1"]; + Assert.Equal(@"$WinGetConfigRoot\this\is\a\path.txt", var1 as string); + + var var2 = expandedSettings["var2"]; + Assert.Equal($@"{expectedPath}\this\is\a\path.txt", var2 as string); + + var var3 = expandedSettings["var3"]; + Assert.Equal(@"this\is\a\$WINGETCONFIGROOT\path.txt", var3 as string); + + var var4 = expandedSettings["var4"]; + Assert.Equal($@"this\is\a\{expectedPath}\path.txt", var4 as string); + + var var5 = expandedSettings["var5"]; + Assert.Equal(@"this\is\a\path\$wingetconfigroot", var5 as string); + + var var6 = expandedSettings["var6"]; + Assert.Equal($@"this\is\a\path\{expectedPath}", var6 as string); + } + + /// + /// Verifies throws when config root is not set. + /// + [Fact] + public void GetExpandedSetting_ConfigRoot_Throw() + { + var unit = new ConfigurationUnit().Assign(new { Type = "unitModule/unitResource" }); + unit.Settings.Add("var2", @"${WinGetConfigRoot}\this\is\a\path.txt"); + + var unitInternal = new ConfigurationUnitInternal(unit, null!); + Assert.Throws(() => unitInternal.GetExpandedSettings()); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs index d9e6d0b7fe..1763b0e55a 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ConfigurationUnitProcessorTests.cs @@ -1,409 +1,409 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Management.Automation; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; - using Microsoft.Management.Configuration.Processor.PowerShell.Unit; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.PowerShell.Commands; - using Moq; - using Windows.Foundation.Collections; - using Xunit; - using Xunit.Abstractions; - - /// - /// Configuration unit processor tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ConfigurationUnitProcessorTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ConfigurationUnitProcessorTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Call GetSettings with all intents should succeed. - /// - /// Intent. - [Theory] - [InlineData(ConfigurationUnitIntent.Inform)] - [InlineData(ConfigurationUnitIntent.Assert)] - [InlineData(ConfigurationUnitIntent.Apply)] - public void GetSettings_Test(object intent) - { - string theKey = "key"; - string theValue = "value"; - var valueGetResult = new ValueSet - { - { theKey, theValue }, - }; - - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeGetResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(valueGetResult) - .Verifiable(); - - var unitResource = this.CreateUnitResource(Assert.IsType(intent)); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.GetSettings(); - - processorEnvMock.Verify(); - - Assert.True(result.Settings.Count == 1); - Assert.True(result.Settings.ContainsKey(theKey)); - Assert.True(result.Settings.TryGetValue(theKey, out object keyValue)); - Assert.Equal(theValue, keyValue as string); - } - - /// - /// Tests GetSettings when a System.Management.Automation.RuntimeException is thrown. - /// - [Fact] - public void GetSettings_Throws_Pwsh_RuntimeException() - { - var thrownException = new RuntimeException("a message"); - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeGetResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Throws(() => thrownException) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.GetSettings(); - - processorEnvMock.Verify(); - - // Do not check for the type. - Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); - Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); - Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); - } - - /// - /// Tests GetSettings when a Microsoft.PowerShell.Commands.WriteErrorException is thrown. - /// - [Fact] - public void GetSettings_Throws_Pwsh_WriteErrorException() - { - var thrownException = new WriteErrorException("a message"); - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeGetResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Throws(() => thrownException) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.GetSettings(); - - processorEnvMock.Verify(); - - // Do not check for the type. - Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); - Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); - Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); - } - - /// - /// Call TestSettings with Inform intent is not allowed. - /// - [Fact] - public void TestSettings_InformIntent() - { - var processorEnvMock = new Mock(); - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - Assert.Throws(() => unitProcessor.TestSettings()); - } - - /// - /// Call TestSettings with Assert and Apply should work. - /// - /// Intent. - /// Invoke test result. - [Theory] - [InlineData(ConfigurationUnitIntent.Assert, false)] - [InlineData(ConfigurationUnitIntent.Apply, false)] - [InlineData(ConfigurationUnitIntent.Assert, true)] - [InlineData(ConfigurationUnitIntent.Apply, true)] - public void TestSettings_TestSucceeded(object intent, bool invokeTestResult) - { - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeTestResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(invokeTestResult) - .Verifiable(); - - var unitResource = this.CreateUnitResource(Assert.IsType(intent)); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var testResult = unitProcessor.TestSettings(); - - processorEnvMock.Verify(); - - var expectedConfigTestResult = ConfigurationTestResult.Negative; - if (invokeTestResult) - { - expectedConfigTestResult = ConfigurationTestResult.Positive; - } - - Assert.Equal(expectedConfigTestResult, testResult.TestResult); - } - - /// - /// Tests TestSettings when a System.Management.Automation.RuntimeException is thrown. - /// - [Fact] - public void TestSettings_Throws_Pwsh_RuntimeException() - { - var thrownException = new RuntimeException("a message"); - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeTestResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Throws(() => thrownException) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Assert); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.TestSettings(); - - processorEnvMock.Verify(); - - Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); - - // Do not check for the type. - Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); - Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); - Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); - } - - /// - /// Tests TestSettings when a Microsoft.PowerShell.Commands.WriteErrorException is thrown. - /// - [Fact] - public void TestSettings_Throws_Pwsh_WriteErrorException() - { - var thrownException = new WriteErrorException("a message"); - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeTestResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Throws(() => thrownException) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Assert); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.TestSettings(); - - processorEnvMock.Verify(); - - Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); - - // Do not check for the type. - Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); - Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); - Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); - } - - /// - /// Call ApplySettings with invalid intents. - /// - /// Intent. - [Theory] - [InlineData(ConfigurationUnitIntent.Inform)] - [InlineData(ConfigurationUnitIntent.Assert)] - public void ApplySettings_InvalidIntent(object intent) - { - var processorEnvMock = new Mock(); - var unitResource = this.CreateUnitResource(Assert.IsType(intent)); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - Assert.Throws(() => unitProcessor.ApplySettings()); - } - - /// - /// Call ApplySettings. - /// - /// Reboot required. - [Theory] - [InlineData(true)] - [InlineData(false)] - public void ApplySettings_Test(bool rebootRequired) - { - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeSetResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(rebootRequired) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.ApplySettings(); - - Assert.Equal(rebootRequired, result.RebootRequired); - } - - /// - /// Tests ApplySettings when a System.Management.Automation.RuntimeException is thrown. - /// - [Fact] - public void ApplySettings_Throws_Pwsh_RuntimeException() - { - var thrownException = new RuntimeException("a message"); - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeSetResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Throws(() => thrownException) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.ApplySettings(); - - processorEnvMock.Verify(); - - // Do not check for the type. - Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); - Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); - Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); - } - - /// - /// Tests ApplySettings when a Microsoft.PowerShell.Commands.WriteErrorException is thrown. - /// - [Fact] - public void ApplySettings_Throws_Pwsh_WriteErrorException() - { - var thrownException = new RuntimeException("a message"); - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeSetResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Throws(() => thrownException) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); - - var result = unitProcessor.ApplySettings(); - - processorEnvMock.Verify(); - - // Do not check for the type. - Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); - Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); - Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); - } - - /// - /// Tests ApplySettings in limit mode. - /// - [Fact] - public void ApplySettings_Test_LimitMode() - { - string theKey = "key"; - string theValue = "value"; - var valueGetResult = new ValueSet - { - { theKey, theValue }, - }; - - var processorEnvMock = new Mock(); - processorEnvMock.Setup(m => m.InvokeGetResource( - It.IsAny(), - It.IsAny(), - It.IsAny())) - .Returns(valueGetResult) - .Verifiable(); - - var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); - - var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource, true); - - // GetSettings can be called multiple times. - var getResult = unitProcessor.GetSettings(); - getResult = unitProcessor.GetSettings(); - - // TestSettings can be called only once. - var testResult = unitProcessor.TestSettings(); - Assert.Throws(() => unitProcessor.TestSettings()); - - // ApplySettings can be called only once. - var applyResult = unitProcessor.ApplySettings(); - Assert.Throws(() => unitProcessor.ApplySettings()); - } - - private ConfigurationUnitAndResource CreateUnitResource(ConfigurationUnitIntent intent) - { - string resourceName = "xResourceName"; - return new ConfigurationUnitAndResource( - new ConfigurationUnitAndModule( - new ConfigurationUnit - { - Type = resourceName, - Intent = intent, - }, - string.Empty), - new DscResourceInfoInternal(resourceName, null, null)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Management.Automation; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.Processor.PowerShell.ProcessorEnvironments; + using Microsoft.Management.Configuration.Processor.PowerShell.Unit; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.PowerShell.Commands; + using Moq; + using Windows.Foundation.Collections; + using Xunit; + using Xunit.Abstractions; + + /// + /// Configuration unit processor tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ConfigurationUnitProcessorTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ConfigurationUnitProcessorTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Call GetSettings with all intents should succeed. + /// + /// Intent. + [Theory] + [InlineData(ConfigurationUnitIntent.Inform)] + [InlineData(ConfigurationUnitIntent.Assert)] + [InlineData(ConfigurationUnitIntent.Apply)] + public void GetSettings_Test(object intent) + { + string theKey = "key"; + string theValue = "value"; + var valueGetResult = new ValueSet + { + { theKey, theValue }, + }; + + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeGetResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(valueGetResult) + .Verifiable(); + + var unitResource = this.CreateUnitResource(Assert.IsType(intent)); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.GetSettings(); + + processorEnvMock.Verify(); + + Assert.True(result.Settings.Count == 1); + Assert.True(result.Settings.ContainsKey(theKey)); + Assert.True(result.Settings.TryGetValue(theKey, out object keyValue)); + Assert.Equal(theValue, keyValue as string); + } + + /// + /// Tests GetSettings when a System.Management.Automation.RuntimeException is thrown. + /// + [Fact] + public void GetSettings_Throws_Pwsh_RuntimeException() + { + var thrownException = new RuntimeException("a message"); + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeGetResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(() => thrownException) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.GetSettings(); + + processorEnvMock.Verify(); + + // Do not check for the type. + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); + } + + /// + /// Tests GetSettings when a Microsoft.PowerShell.Commands.WriteErrorException is thrown. + /// + [Fact] + public void GetSettings_Throws_Pwsh_WriteErrorException() + { + var thrownException = new WriteErrorException("a message"); + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeGetResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(() => thrownException) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.GetSettings(); + + processorEnvMock.Verify(); + + // Do not check for the type. + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); + } + + /// + /// Call TestSettings with Inform intent is not allowed. + /// + [Fact] + public void TestSettings_InformIntent() + { + var processorEnvMock = new Mock(); + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Inform); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + Assert.Throws(() => unitProcessor.TestSettings()); + } + + /// + /// Call TestSettings with Assert and Apply should work. + /// + /// Intent. + /// Invoke test result. + [Theory] + [InlineData(ConfigurationUnitIntent.Assert, false)] + [InlineData(ConfigurationUnitIntent.Apply, false)] + [InlineData(ConfigurationUnitIntent.Assert, true)] + [InlineData(ConfigurationUnitIntent.Apply, true)] + public void TestSettings_TestSucceeded(object intent, bool invokeTestResult) + { + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeTestResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(invokeTestResult) + .Verifiable(); + + var unitResource = this.CreateUnitResource(Assert.IsType(intent)); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var testResult = unitProcessor.TestSettings(); + + processorEnvMock.Verify(); + + var expectedConfigTestResult = ConfigurationTestResult.Negative; + if (invokeTestResult) + { + expectedConfigTestResult = ConfigurationTestResult.Positive; + } + + Assert.Equal(expectedConfigTestResult, testResult.TestResult); + } + + /// + /// Tests TestSettings when a System.Management.Automation.RuntimeException is thrown. + /// + [Fact] + public void TestSettings_Throws_Pwsh_RuntimeException() + { + var thrownException = new RuntimeException("a message"); + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeTestResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(() => thrownException) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Assert); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.TestSettings(); + + processorEnvMock.Verify(); + + Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); + + // Do not check for the type. + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); + } + + /// + /// Tests TestSettings when a Microsoft.PowerShell.Commands.WriteErrorException is thrown. + /// + [Fact] + public void TestSettings_Throws_Pwsh_WriteErrorException() + { + var thrownException = new WriteErrorException("a message"); + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeTestResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(() => thrownException) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Assert); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.TestSettings(); + + processorEnvMock.Verify(); + + Assert.Equal(ConfigurationTestResult.Failed, result.TestResult); + + // Do not check for the type. + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); + } + + /// + /// Call ApplySettings with invalid intents. + /// + /// Intent. + [Theory] + [InlineData(ConfigurationUnitIntent.Inform)] + [InlineData(ConfigurationUnitIntent.Assert)] + public void ApplySettings_InvalidIntent(object intent) + { + var processorEnvMock = new Mock(); + var unitResource = this.CreateUnitResource(Assert.IsType(intent)); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + Assert.Throws(() => unitProcessor.ApplySettings()); + } + + /// + /// Call ApplySettings. + /// + /// Reboot required. + [Theory] + [InlineData(true)] + [InlineData(false)] + public void ApplySettings_Test(bool rebootRequired) + { + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeSetResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(rebootRequired) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.ApplySettings(); + + Assert.Equal(rebootRequired, result.RebootRequired); + } + + /// + /// Tests ApplySettings when a System.Management.Automation.RuntimeException is thrown. + /// + [Fact] + public void ApplySettings_Throws_Pwsh_RuntimeException() + { + var thrownException = new RuntimeException("a message"); + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeSetResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(() => thrownException) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.ApplySettings(); + + processorEnvMock.Verify(); + + // Do not check for the type. + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); + } + + /// + /// Tests ApplySettings when a Microsoft.PowerShell.Commands.WriteErrorException is thrown. + /// + [Fact] + public void ApplySettings_Throws_Pwsh_WriteErrorException() + { + var thrownException = new RuntimeException("a message"); + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeSetResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Throws(() => thrownException) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource); + + var result = unitProcessor.ApplySettings(); + + processorEnvMock.Verify(); + + // Do not check for the type. + Assert.Equal(thrownException.HResult, result.ResultInformation.ResultCode.HResult); + Assert.True(!string.IsNullOrWhiteSpace(result.ResultInformation.Description)); + Assert.Equal(ConfigurationUnitResultSource.Internal, result.ResultInformation.ResultSource); + } + + /// + /// Tests ApplySettings in limit mode. + /// + [Fact] + public void ApplySettings_Test_LimitMode() + { + string theKey = "key"; + string theValue = "value"; + var valueGetResult = new ValueSet + { + { theKey, theValue }, + }; + + var processorEnvMock = new Mock(); + processorEnvMock.Setup(m => m.InvokeGetResource( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(valueGetResult) + .Verifiable(); + + var unitResource = this.CreateUnitResource(ConfigurationUnitIntent.Apply); + + var unitProcessor = new PowerShellConfigurationUnitProcessor(processorEnvMock.Object, unitResource, true); + + // GetSettings can be called multiple times. + var getResult = unitProcessor.GetSettings(); + getResult = unitProcessor.GetSettings(); + + // TestSettings can be called only once. + var testResult = unitProcessor.TestSettings(); + Assert.Throws(() => unitProcessor.TestSettings()); + + // ApplySettings can be called only once. + var applyResult = unitProcessor.ApplySettings(); + Assert.Throws(() => unitProcessor.ApplySettings()); + } + + private ConfigurationUnitAndResource CreateUnitResource(ConfigurationUnitIntent intent) + { + string resourceName = "xResourceName"; + return new ConfigurationUnitAndResource( + new ConfigurationUnitAndModule( + new ConfigurationUnit + { + Type = resourceName, + Intent = intent, + }, + string.Empty), + new DscResourceInfoInternal(resourceName, null, null)); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityIntegrationTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityIntegrationTests.cs index f8c12c9e57..537f6f0fdb 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityIntegrationTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityIntegrationTests.cs @@ -1,108 +1,108 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.Threading.Tasks; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// Integration tests for DSCv3 processor path integrity checks. - /// These tests verify that hash verification catches mismatches when a custom DSC executable - /// path is provided. Units run in-process (no elevation split) so limit mode is not involved. - /// - [Collection("UnitTestCollection")] - [OutOfProc] - public class DSCv3ProcessorPathIntegrityIntegrationTests : ConfigurationProcessorTestBase - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public DSCv3ProcessorPathIntegrityIntegrationTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Verifies that providing a wrong hash for a custom processor path causes the - /// apply to fail with a hash mismatch error. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task Apply_ProcessorPath_WrongHash_FailsWithHashMismatch() - { - using var tempFile = new TempFile(content: "test content for hash test"); - - IConfigurationSetProcessorFactory dynamicFactory = - await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync( - Helpers.Constants.DSCv3DynamicRuntimeHandlerIdentifier); - - var factoryMap = (IDictionary)dynamicFactory; - factoryMap["DscExecutablePath"] = tempFile.FullFileName; - factoryMap["DscExecutablePathHash"] = new string('0', 64); // Wrong hash. - factoryMap["DscExecutablePathIsAlias"] = "false"; - - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.SchemaVersion = "0.3"; - configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); - - ConfigurationUnit unit = this.ConfigurationUnit(); - unit.Identifier = "testUnit"; - unit.Type = "TestResource/TestUnit"; - unit.Intent = ConfigurationUnitIntent.Apply; - configurationSet.Units = new[] { unit }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); - var ex = Assert.ThrowsAny(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); - } - - /// - /// Verifies that omitting the hash for a custom processor path causes the apply - /// to fail. The server requires a hash whenever a custom path is provided. - /// - /// A representing the asynchronous unit test. - [Fact] - public async Task Apply_ProcessorPath_MissingHash_ApplyFails() - { - using var tempFile = new TempFile(content: "test content"); - - IConfigurationSetProcessorFactory dynamicFactory = - await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync( - Helpers.Constants.DSCv3DynamicRuntimeHandlerIdentifier); - - var factoryMap = (IDictionary)dynamicFactory; - - // DscExecutablePathHash intentionally omitted. - factoryMap["DscExecutablePath"] = tempFile.FullFileName; - - ConfigurationSet configurationSet = this.ConfigurationSet(); - configurationSet.SchemaVersion = "0.3"; - configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); - - ConfigurationUnit unit = this.ConfigurationUnit(); - unit.Identifier = "testUnit"; - unit.Type = "TestResource/TestUnit"; - unit.Intent = ConfigurationUnitIntent.Apply; - configurationSet.Units = new[] { unit }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); - Assert.ThrowsAny(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// Integration tests for DSCv3 processor path integrity checks. + /// These tests verify that hash verification catches mismatches when a custom DSC executable + /// path is provided. Units run in-process (no elevation split) so limit mode is not involved. + /// + [Collection("UnitTestCollection")] + [OutOfProc] + public class DSCv3ProcessorPathIntegrityIntegrationTests : ConfigurationProcessorTestBase + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public DSCv3ProcessorPathIntegrityIntegrationTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Verifies that providing a wrong hash for a custom processor path causes the + /// apply to fail with a hash mismatch error. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Apply_ProcessorPath_WrongHash_FailsWithHashMismatch() + { + using var tempFile = new TempFile(content: "test content for hash test"); + + IConfigurationSetProcessorFactory dynamicFactory = + await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync( + Helpers.Constants.DSCv3DynamicRuntimeHandlerIdentifier); + + var factoryMap = (IDictionary)dynamicFactory; + factoryMap["DscExecutablePath"] = tempFile.FullFileName; + factoryMap["DscExecutablePathHash"] = new string('0', 64); // Wrong hash. + factoryMap["DscExecutablePathIsAlias"] = "false"; + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.3"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Identifier = "testUnit"; + unit.Type = "TestResource/TestUnit"; + unit.Intent = ConfigurationUnitIntent.Apply; + configurationSet.Units = new[] { unit }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + var ex = Assert.ThrowsAny(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); + } + + /// + /// Verifies that omitting the hash for a custom processor path causes the apply + /// to fail. The server requires a hash whenever a custom path is provided. + /// + /// A representing the asynchronous unit test. + [Fact] + public async Task Apply_ProcessorPath_MissingHash_ApplyFails() + { + using var tempFile = new TempFile(content: "test content"); + + IConfigurationSetProcessorFactory dynamicFactory = + await this.fixture.ConfigurationStatics.CreateConfigurationSetProcessorFactoryAsync( + Helpers.Constants.DSCv3DynamicRuntimeHandlerIdentifier); + + var factoryMap = (IDictionary)dynamicFactory; + + // DscExecutablePathHash intentionally omitted. + factoryMap["DscExecutablePath"] = tempFile.FullFileName; + + ConfigurationSet configurationSet = this.ConfigurationSet(); + configurationSet.SchemaVersion = "0.3"; + configurationSet.Metadata.Add(Helpers.Constants.EnableDynamicFactoryTestMode, true); + + ConfigurationUnit unit = this.ConfigurationUnit(); + unit.Identifier = "testUnit"; + unit.Type = "TestResource/TestUnit"; + unit.Intent = ConfigurationUnitIntent.Apply; + configurationSet.Units = new[] { unit }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(dynamicFactory); + Assert.ThrowsAny(() => processor.ApplySet(configurationSet, ApplyConfigurationSetFlags.None)); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityUnitTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityUnitTests.cs index 2358eb7ee1..44c24fb98d 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityUnitTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorPathIntegrityUnitTests.cs @@ -1,160 +1,160 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// In-process unit tests for DSCv3 processor path integrity helper methods. - /// These exercise the helpers directly without going through the elevation split. - /// - [Collection("UnitTestCollection")] - [InProc] - public class DSCv3ProcessorPathIntegrityUnitTests : ConfigurationProcessorTestBase - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public DSCv3ProcessorPathIntegrityUnitTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Verifies that returns a 64-char - /// lowercase hex SHA256 hash for a regular file. - /// - [Fact] - public void ComputeHash_RegularFile_ReturnsLowercaseHex64() - { - using var tempFile = new TempFile(content: "test content for hashing"); - - string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); - - Assert.False(isAlias); - Assert.Equal(64, hash.Length); - Assert.Equal(hash, hash.ToLowerInvariant()); - } - - /// - /// Verifies that two calls to on the same - /// file return the same hash. - /// - [Fact] - public void ComputeHash_SameFile_ReturnsSameHash() - { - using var tempFile = new TempFile(content: "deterministic content"); - - string hash1 = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out _); - string hash2 = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out _); - - Assert.Equal(hash1, hash2); - } - - /// - /// Verifies that succeeds and returns a - /// valid handle when the correct hash is supplied. - /// - [Fact] - public void VerifyAndOpen_CorrectHash_ReturnsValidHandle() - { - using var tempFile = new TempFile(content: "test content"); - - string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); - using var handle = ProcessorPathIntegrity.VerifyAndOpen(tempFile.FullFileName, hash, isAlias); - - Assert.False(handle.IsInvalid); - } - - /// - /// Verifies that throws with the - /// hash mismatch HRESULT when the wrong hash is supplied. - /// - [Fact] - public void VerifyAndOpen_WrongHash_ThrowsHashMismatch() - { - using var tempFile = new TempFile(content: "test content"); - - Exception? ex = Record.Exception(() => - { - using var handle = ProcessorPathIntegrity.VerifyAndOpen(tempFile.FullFileName, new string('0', 64), isAlias: false); - }); - - Assert.NotNull(ex); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); - } - - /// - /// Verifies that throws - /// when a custom path is set without a hash. - /// - [Fact] - public void ProcessorSettings_CustomPath_NoHash_Throws() - { - using var tempFile = new TempFile(content: "test content"); - - var settings = new ProcessorSettings(); - settings.DscExecutablePath = tempFile.FullFileName; - - Exception? ex = Record.Exception(() => _ = settings.EffectiveDscExecutablePath); - - Assert.NotNull(ex); - Assert.IsType(ex); - } - - /// - /// Verifies that returns the - /// path when the correct hash is provided. - /// - [Fact] - public void ProcessorSettings_CustomPath_CorrectHash_ReturnsPath() - { - using var tempFile = new TempFile(content: "test content"); - - string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); - - using var settings = new ProcessorSettings(); - settings.DscExecutablePath = tempFile.FullFileName; - settings.DscExecutablePathHash = hash; - settings.DscExecutablePathIsAlias = isAlias; - - Assert.Equal(tempFile.FullFileName, settings.EffectiveDscExecutablePath); - } - - /// - /// Verifies that throws with the - /// hash mismatch HRESULT when a wrong hash is set for a custom path. - /// - [Fact] - public void ProcessorSettings_CustomPath_WrongHash_ThrowsHashMismatch() - { - using var tempFile = new TempFile(content: "test content"); - - var settings = new ProcessorSettings(); - settings.DscExecutablePath = tempFile.FullFileName; - settings.DscExecutablePathHash = new string('0', 64); - settings.DscExecutablePathIsAlias = false; - - Exception? ex = Record.Exception(() => _ = settings.EffectiveDscExecutablePath); - - Assert.NotNull(ex); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using Microsoft.Management.Configuration.Processor.DSCv3.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// In-process unit tests for DSCv3 processor path integrity helper methods. + /// These exercise the helpers directly without going through the elevation split. + /// + [Collection("UnitTestCollection")] + [InProc] + public class DSCv3ProcessorPathIntegrityUnitTests : ConfigurationProcessorTestBase + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public DSCv3ProcessorPathIntegrityUnitTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Verifies that returns a 64-char + /// lowercase hex SHA256 hash for a regular file. + /// + [Fact] + public void ComputeHash_RegularFile_ReturnsLowercaseHex64() + { + using var tempFile = new TempFile(content: "test content for hashing"); + + string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); + + Assert.False(isAlias); + Assert.Equal(64, hash.Length); + Assert.Equal(hash, hash.ToLowerInvariant()); + } + + /// + /// Verifies that two calls to on the same + /// file return the same hash. + /// + [Fact] + public void ComputeHash_SameFile_ReturnsSameHash() + { + using var tempFile = new TempFile(content: "deterministic content"); + + string hash1 = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out _); + string hash2 = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out _); + + Assert.Equal(hash1, hash2); + } + + /// + /// Verifies that succeeds and returns a + /// valid handle when the correct hash is supplied. + /// + [Fact] + public void VerifyAndOpen_CorrectHash_ReturnsValidHandle() + { + using var tempFile = new TempFile(content: "test content"); + + string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); + using var handle = ProcessorPathIntegrity.VerifyAndOpen(tempFile.FullFileName, hash, isAlias); + + Assert.False(handle.IsInvalid); + } + + /// + /// Verifies that throws with the + /// hash mismatch HRESULT when the wrong hash is supplied. + /// + [Fact] + public void VerifyAndOpen_WrongHash_ThrowsHashMismatch() + { + using var tempFile = new TempFile(content: "test content"); + + Exception? ex = Record.Exception(() => + { + using var handle = ProcessorPathIntegrity.VerifyAndOpen(tempFile.FullFileName, new string('0', 64), isAlias: false); + }); + + Assert.NotNull(ex); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); + } + + /// + /// Verifies that throws + /// when a custom path is set without a hash. + /// + [Fact] + public void ProcessorSettings_CustomPath_NoHash_Throws() + { + using var tempFile = new TempFile(content: "test content"); + + var settings = new ProcessorSettings(); + settings.DscExecutablePath = tempFile.FullFileName; + + Exception? ex = Record.Exception(() => _ = settings.EffectiveDscExecutablePath); + + Assert.NotNull(ex); + Assert.IsType(ex); + } + + /// + /// Verifies that returns the + /// path when the correct hash is provided. + /// + [Fact] + public void ProcessorSettings_CustomPath_CorrectHash_ReturnsPath() + { + using var tempFile = new TempFile(content: "test content"); + + string hash = ProcessorPathIntegrity.ComputeHash(tempFile.FullFileName, out bool isAlias); + + using var settings = new ProcessorSettings(); + settings.DscExecutablePath = tempFile.FullFileName; + settings.DscExecutablePathHash = hash; + settings.DscExecutablePathIsAlias = isAlias; + + Assert.Equal(tempFile.FullFileName, settings.EffectiveDscExecutablePath); + } + + /// + /// Verifies that throws with the + /// hash mismatch HRESULT when a wrong hash is set for a custom path. + /// + [Fact] + public void ProcessorSettings_CustomPath_WrongHash_ThrowsHashMismatch() + { + using var tempFile = new TempFile(content: "test content"); + + var settings = new ProcessorSettings(); + settings.DscExecutablePath = tempFile.FullFileName; + settings.DscExecutablePathHash = new string('0', 64); + settings.DscExecutablePathIsAlias = false; + + Exception? ex = Record.Exception(() => _ = settings.EffectiveDscExecutablePath); + + Assert.NotNull(ex); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_PROCESSOR_HASH_MISMATCH, ex.HResult); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs index 4e56765212..59cbc74492 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DSCv3ProcessorTests.cs @@ -1,328 +1,328 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Security.Cryptography; - using Microsoft.Management.Configuration.Processor; - using Microsoft.Management.Configuration.Processor.DSCv3.Model; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Windows.Foundation.Collections; - using Xunit; - using Xunit.Abstractions; - - /// - /// Tests for the DSCv3 processor. - /// - [Collection("UnitTestCollection")] - [InProc] - public class DSCv3ProcessorTests : ConfigurationProcessorTestBase - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public DSCv3ProcessorTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Tests for the unit details caching. - /// - [Fact] - public void Set_UnitPropertyDetailsCached() - { - var (factory, dsc) = CreateTestFactory(); - var set = this.ConfigurationSet(); - string type1 = "Type1"; - string type2 = "Type2"; - var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); - var unit2 = this.ConfigurationUnit().Assign(new { Type = type2 }); - - var setProcessor = factory.CreateSetProcessor(set); - - // Initially, no details - var details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); - Assert.Null(details); - - // Null result not cached - dsc.GetResourceByTypeResult = new TestResourceListItem() { Type = type1 }; - details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); - Assert.NotNull(details); - Assert.Equal(type1, details.UnitType); - - // Not-null result cached - dsc.GetResourceByTypeResult = null; - dsc.GetResourceByTypeDelegate = s => throw new System.Exception("Shouldn't be called"); - details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); - Assert.NotNull(details); - Assert.Equal(type1, details.UnitType); - - // Different type, no details - dsc.GetResourceByTypeDelegate = null; - details = setProcessor.GetUnitProcessorDetails(unit2, ConfigurationUnitDetailFlags.Local); - Assert.Null(details); - - // Null result not cached - dsc.GetResourceByTypeResult = new TestResourceListItem() { Type = type2 }; - details = setProcessor.GetUnitProcessorDetails(unit2, ConfigurationUnitDetailFlags.Local); - Assert.NotNull(details); - Assert.Equal(type2, details.UnitType); - - // First type is still first type - dsc.GetResourceByTypeResult = null; - dsc.GetResourceByTypeDelegate = s => throw new System.Exception("Shouldn't be called"); - details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); - Assert.NotNull(details); - Assert.Equal(type1, details.UnitType); - } - - /// - /// Test for unit processor creation requiring resource to be found. - /// - [Fact(Skip = "Disable this test while we have the bypass in place")] - public void Set_ResourceNotFoundIsError() - { - var (factory, dsc) = CreateTestFactory(); - var set = this.ConfigurationSet(); - string type1 = "Type1"; - var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); - - var setProcessor = factory.CreateSetProcessor(set); - - // Not found is error - Assert.Throws(() => setProcessor.CreateUnitProcessor(unit1)); - - // Found is not error - dsc.GetResourceByTypeResult = new TestResourceListItem() { Type = type1 }; - var unitProcessor = setProcessor.CreateUnitProcessor(unit1); - Assert.NotNull(unitProcessor); - Assert.Equal(type1, unitProcessor.Unit.Type); - } - - /// - /// Test for settings export. - /// - [Fact] - public void GetAllSettings_Expected() - { - var (factory, dsc) = CreateTestFactory(); - var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - string type1 = "Type1"; - var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); - - dsc.GetResourceByTypeDelegate = (type) => - { - Assert.Equal(type1, type); - return new TestResourceListItem() { Type = type1 }; - }; - - ValueSet set1 = new ValueSet(); - set1.Add("key1", "val1"); - - ValueSet set2 = new ValueSet(); - set2.Add("key2", "val2"); - - dsc.ExportResourceResult = new List() - { - new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, - new TestResourceExportItem() { Type = type1, Name = "2", Settings = set2 }, - }; - - var result = processor.GetAllUnitSettings(unit1); - - Assert.NotNull(result); - Assert.NotNull(result.ResultInformation); - Assert.Null(result.ResultInformation.ResultCode); - - Assert.Equal(2, result.Settings.Count); - Assert.NotNull(result.Settings.Single(set => set.Contains(set1.First()))); - Assert.NotNull(result.Settings.Single(set => set.Contains(set2.First()))); - } - - /// - /// Test for settings export with differing types. - /// - [Fact] - public void GetAllSettings_DifferentType() - { - var (factory, dsc) = CreateTestFactory(); - var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - string type1 = "Type1"; - string type2 = "Type2"; - var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); - - dsc.GetResourceByTypeDelegate = (type) => - { - Assert.Equal(type1, type); - return new TestResourceListItem() { Type = type1 }; - }; - - ValueSet set1 = new ValueSet(); - set1.Add("key1", "val1"); - - ValueSet set2 = new ValueSet(); - set2.Add("key2", "val2"); - - dsc.ExportResourceResult = new List() - { - new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, - new TestResourceExportItem() { Type = type2, Name = "2", Settings = set2 }, - }; - - var result = processor.GetAllUnitSettings(unit1); - - Assert.NotNull(result); - Assert.NotNull(result.ResultInformation); - Assert.NotNull(result.ResultInformation.ResultCode); - Assert.Equal(ErrorCodes.WinGetConfigUnitUnsupportedType, result.ResultInformation.ResultCode.HResult); - } - - /// - /// Test for unit export. - /// - [Fact] - public void GetAllUnits_Simple() - { - var (factory, dsc) = CreateTestFactory(); - var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - string type1 = "Type1"; - var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); - - dsc.GetResourceByTypeDelegate = (type) => - { - Assert.Equal(type1, type); - return new TestResourceListItem() { Type = type1 }; - }; - - ValueSet set1 = new ValueSet(); - set1.Add("key1", "val1"); - - ValueSet set2 = new ValueSet(); - set2.Add("key2", "val2"); - - dsc.ExportResourceResult = new List() - { - new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, - new TestResourceExportItem() { Type = type1, Name = "2", Settings = set2 }, - }; - - var result = processor.GetAllUnits(unit1); - - Assert.NotNull(result); - Assert.NotNull(result.ResultInformation); - Assert.Null(result.ResultInformation.ResultCode); - - Assert.Equal(2, result.Units.Count); - - foreach (var unit in result.Units) - { - Assert.Equal(type1, unit.Type); - Assert.NotEmpty(unit.Identifier); - } - - Assert.NotEqual(result.Units[0].Identifier, result.Units[1].Identifier); - - Assert.NotNull(result.Units.Single(unit => unit.Settings.Contains(set1.First()))); - Assert.NotNull(result.Units.Single(unit => unit.Settings.Contains(set2.First()))); - } - - /// - /// Test for unit export with complex data. - /// - [Fact] - public void GetAllUnits_Complex() - { - var (factory, dsc) = CreateTestFactory(); - var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - string type1 = "Type1"; - string type2 = "Type2"; - - string name1 = "1"; - string name2 = "2"; - - var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); - - dsc.GetResourceByTypeDelegate = (type) => - { - Assert.Equal(type1, type); - return new TestResourceListItem() { Type = type1 }; - }; - - ValueSet set1 = new ValueSet(); - set1.Add("key1", "val1"); - - ValueSet metadata1 = new ValueSet(); - metadata1.Add("met1", "val11"); - - ValueSet set2 = new ValueSet(); - set2.Add("key2", "val2"); - - List dependencies2 = new List(); - dependencies2.Add(name1); - - dsc.ExportResourceResult = new List() - { - new TestResourceExportItem() { Type = type1, Name = name1, Settings = set1, Metadata = metadata1 }, - new TestResourceExportItem() { Type = type2, Name = name2, Settings = set2, Dependencies = dependencies2 }, - }; - - var result = processor.GetAllUnits(unit1); - - Assert.NotNull(result); - Assert.NotNull(result.ResultInformation); - Assert.Null(result.ResultInformation.ResultCode); - - Assert.Equal(2, result.Units.Count); - - var result1 = result.Units.Single(unit => unit.Identifier == name1); - var result2 = result.Units.Single(unit => unit.Identifier == name2); - - Assert.Equal(type1, result1.Type); - Assert.Equal(type2, result2.Type); - - Assert.Contains(set1.First(), result1.Settings); - Assert.Contains(set2.First(), result2.Settings); - - Assert.Contains(metadata1.First(), result1.Metadata); - Assert.Empty(result2.Metadata); - - Assert.Empty(result1.Dependencies); - Assert.Contains(dependencies2.First(), result2.Dependencies); - } - - private static (DSCv3ConfigurationSetProcessorFactory, TestDSCv3) CreateTestFactory() - { - DSCv3ConfigurationSetProcessorFactory factory = new DSCv3ConfigurationSetProcessorFactory(); - TestDSCv3 dsc = new TestDSCv3(); - factory.Settings.DSCv3 = dsc; - TempFile tempFile = new TempFile(content: "Contents", cleanup: false); - factory.Settings.DscExecutablePath = tempFile.FullFileName; - using var fileStream = File.Open(factory.Settings.DscExecutablePath, FileMode.Open); - factory.Settings.DscExecutablePathHash = Convert.ToHexString(SHA256.HashData(fileStream)).ToLowerInvariant(); - - return (factory, dsc); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Security.Cryptography; + using Microsoft.Management.Configuration.Processor; + using Microsoft.Management.Configuration.Processor.DSCv3.Model; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Windows.Foundation.Collections; + using Xunit; + using Xunit.Abstractions; + + /// + /// Tests for the DSCv3 processor. + /// + [Collection("UnitTestCollection")] + [InProc] + public class DSCv3ProcessorTests : ConfigurationProcessorTestBase + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public DSCv3ProcessorTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Tests for the unit details caching. + /// + [Fact] + public void Set_UnitPropertyDetailsCached() + { + var (factory, dsc) = CreateTestFactory(); + var set = this.ConfigurationSet(); + string type1 = "Type1"; + string type2 = "Type2"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + var unit2 = this.ConfigurationUnit().Assign(new { Type = type2 }); + + var setProcessor = factory.CreateSetProcessor(set); + + // Initially, no details + var details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); + Assert.Null(details); + + // Null result not cached + dsc.GetResourceByTypeResult = new TestResourceListItem() { Type = type1 }; + details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); + Assert.NotNull(details); + Assert.Equal(type1, details.UnitType); + + // Not-null result cached + dsc.GetResourceByTypeResult = null; + dsc.GetResourceByTypeDelegate = s => throw new System.Exception("Shouldn't be called"); + details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); + Assert.NotNull(details); + Assert.Equal(type1, details.UnitType); + + // Different type, no details + dsc.GetResourceByTypeDelegate = null; + details = setProcessor.GetUnitProcessorDetails(unit2, ConfigurationUnitDetailFlags.Local); + Assert.Null(details); + + // Null result not cached + dsc.GetResourceByTypeResult = new TestResourceListItem() { Type = type2 }; + details = setProcessor.GetUnitProcessorDetails(unit2, ConfigurationUnitDetailFlags.Local); + Assert.NotNull(details); + Assert.Equal(type2, details.UnitType); + + // First type is still first type + dsc.GetResourceByTypeResult = null; + dsc.GetResourceByTypeDelegate = s => throw new System.Exception("Shouldn't be called"); + details = setProcessor.GetUnitProcessorDetails(unit1, ConfigurationUnitDetailFlags.Local); + Assert.NotNull(details); + Assert.Equal(type1, details.UnitType); + } + + /// + /// Test for unit processor creation requiring resource to be found. + /// + [Fact(Skip = "Disable this test while we have the bypass in place")] + public void Set_ResourceNotFoundIsError() + { + var (factory, dsc) = CreateTestFactory(); + var set = this.ConfigurationSet(); + string type1 = "Type1"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + var setProcessor = factory.CreateSetProcessor(set); + + // Not found is error + Assert.Throws(() => setProcessor.CreateUnitProcessor(unit1)); + + // Found is not error + dsc.GetResourceByTypeResult = new TestResourceListItem() { Type = type1 }; + var unitProcessor = setProcessor.CreateUnitProcessor(unit1); + Assert.NotNull(unitProcessor); + Assert.Equal(type1, unitProcessor.Unit.Type); + } + + /// + /// Test for settings export. + /// + [Fact] + public void GetAllSettings_Expected() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + dsc.ExportResourceResult = new List() + { + new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, + new TestResourceExportItem() { Type = type1, Name = "2", Settings = set2 }, + }; + + var result = processor.GetAllUnitSettings(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + + Assert.Equal(2, result.Settings.Count); + Assert.NotNull(result.Settings.Single(set => set.Contains(set1.First()))); + Assert.NotNull(result.Settings.Single(set => set.Contains(set2.First()))); + } + + /// + /// Test for settings export with differing types. + /// + [Fact] + public void GetAllSettings_DifferentType() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + string type2 = "Type2"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + dsc.ExportResourceResult = new List() + { + new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, + new TestResourceExportItem() { Type = type2, Name = "2", Settings = set2 }, + }; + + var result = processor.GetAllUnitSettings(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.NotNull(result.ResultInformation.ResultCode); + Assert.Equal(ErrorCodes.WinGetConfigUnitUnsupportedType, result.ResultInformation.ResultCode.HResult); + } + + /// + /// Test for unit export. + /// + [Fact] + public void GetAllUnits_Simple() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + dsc.ExportResourceResult = new List() + { + new TestResourceExportItem() { Type = type1, Name = "1", Settings = set1 }, + new TestResourceExportItem() { Type = type1, Name = "2", Settings = set2 }, + }; + + var result = processor.GetAllUnits(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + + Assert.Equal(2, result.Units.Count); + + foreach (var unit in result.Units) + { + Assert.Equal(type1, unit.Type); + Assert.NotEmpty(unit.Identifier); + } + + Assert.NotEqual(result.Units[0].Identifier, result.Units[1].Identifier); + + Assert.NotNull(result.Units.Single(unit => unit.Settings.Contains(set1.First()))); + Assert.NotNull(result.Units.Single(unit => unit.Settings.Contains(set2.First()))); + } + + /// + /// Test for unit export with complex data. + /// + [Fact] + public void GetAllUnits_Complex() + { + var (factory, dsc) = CreateTestFactory(); + var processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + string type1 = "Type1"; + string type2 = "Type2"; + + string name1 = "1"; + string name2 = "2"; + + var unit1 = this.ConfigurationUnit().Assign(new { Type = type1 }); + + dsc.GetResourceByTypeDelegate = (type) => + { + Assert.Equal(type1, type); + return new TestResourceListItem() { Type = type1 }; + }; + + ValueSet set1 = new ValueSet(); + set1.Add("key1", "val1"); + + ValueSet metadata1 = new ValueSet(); + metadata1.Add("met1", "val11"); + + ValueSet set2 = new ValueSet(); + set2.Add("key2", "val2"); + + List dependencies2 = new List(); + dependencies2.Add(name1); + + dsc.ExportResourceResult = new List() + { + new TestResourceExportItem() { Type = type1, Name = name1, Settings = set1, Metadata = metadata1 }, + new TestResourceExportItem() { Type = type2, Name = name2, Settings = set2, Dependencies = dependencies2 }, + }; + + var result = processor.GetAllUnits(unit1); + + Assert.NotNull(result); + Assert.NotNull(result.ResultInformation); + Assert.Null(result.ResultInformation.ResultCode); + + Assert.Equal(2, result.Units.Count); + + var result1 = result.Units.Single(unit => unit.Identifier == name1); + var result2 = result.Units.Single(unit => unit.Identifier == name2); + + Assert.Equal(type1, result1.Type); + Assert.Equal(type2, result2.Type); + + Assert.Contains(set1.First(), result1.Settings); + Assert.Contains(set2.First(), result2.Settings); + + Assert.Contains(metadata1.First(), result1.Metadata); + Assert.Empty(result2.Metadata); + + Assert.Empty(result1.Dependencies); + Assert.Contains(dependencies2.First(), result2.Dependencies); + } + + private static (DSCv3ConfigurationSetProcessorFactory, TestDSCv3) CreateTestFactory() + { + DSCv3ConfigurationSetProcessorFactory factory = new DSCv3ConfigurationSetProcessorFactory(); + TestDSCv3 dsc = new TestDSCv3(); + factory.Settings.DSCv3 = dsc; + TempFile tempFile = new TempFile(content: "Contents", cleanup: false); + factory.Settings.DscExecutablePath = tempFile.FullFileName; + using var fileStream = File.Open(factory.Settings.DscExecutablePath, FileMode.Open); + factory.Settings.DscExecutablePathHash = Convert.ToHexString(SHA256.HashData(fileStream)).ToLowerInvariant(); + + return (factory, dsc); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2SimpleFileResourceTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2SimpleFileResourceTests.cs index a0a488e2a8..8b7dfd2441 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2SimpleFileResourceTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2SimpleFileResourceTests.cs @@ -1,284 +1,284 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System.IO; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Windows.Foundation.Collections; - using Xunit; - using Xunit.Abstractions; - using static Microsoft.Management.Configuration.UnitTests.Helpers.PowerShellTestsConstants; - - /// - /// Class that tests a little not that complex resource. - /// - [Collection("UnitTestCollection")] - [InProc] - public class DscModuleV2SimpleFileResourceTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Fixture. - /// log. - public DscModuleV2SimpleFileResourceTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Test SimpleFileResource Ensure Present and Absent when file doesn't exist. - /// - /// Ensure value. - /// Expected result. - [Theory] - [InlineData("Absent", true)] - [InlineData("Present", false)] - public void SimpleFileResource_FileAbsent(string ensureValue, bool expectedResult) - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - // Doesn't create a file. - using var tmpFile = new TempFile(); - - var settings = new ValueSet - { - { "Path", tmpFile.FullFileName }, - { "Ensure", ensureValue }, - }; - - var dscModule = new DscModuleV2(); - - using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); - Assert.Equal( - expectedResult, - dscModule.InvokeTestResource( - pwsh, - settings, - TestModule.SimpleFileResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Test SimpleFileResource when the file exists. - /// - [Fact] - public void SimpleFileResource_FilePresent() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - using var tmpFile = new TempFile(); - tmpFile.CreateFile(); - - var settings = new ValueSet - { - { "Path", tmpFile.FullFileName }, - { "Ensure", "Present" }, - }; - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); - Assert.True(dscModule.InvokeTestResource( - pwsh, - settings, - TestModule.SimpleFileResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Test SimpleFileResource with different content. - /// - [Fact] - public void SimpleFileResource_DifferentContent() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - using var tmpFile = new TempFile(content: "All work and no play makes Ruben a dull boy"); - - var settings = new ValueSet - { - { "Path", tmpFile.FullFileName }, - { "Ensure", "Present" }, - { "Content", "Is that a from somewhere?" }, - }; - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); - Assert.False(dscModule.InvokeTestResource( - pwsh, - settings, - TestModule.SimpleFileResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Test SimpleFileResource with same context. - /// - [Fact] - public void SimpleFileResource_SameContent() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - string content = "This is literally the same, dont fail"; - using var tmpFile = new TempFile(content: content); - - var settings = new ValueSet - { - { "Path", tmpFile.FullFileName }, - { "Ensure", "Present" }, - { "Content", content }, - }; - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); - Assert.True(dscModule.InvokeTestResource( - pwsh, - settings, - TestModule.SimpleFileResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Test SimpleFileResource Set creates the file. - /// - /// Ensure value. - /// If file exists before calling set. - /// If file exists after calling set. - [Theory] - [InlineData("Absent", false, false)] - [InlineData("Present", false, true)] - [InlineData("Absent", true, false)] - [InlineData("Present", true, true)] - public void SimpleFileResource_Set_Ensure(string ensureValue, bool preCondition, bool postCondition) - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - // Doesn't create a file. - using var tmpFile = new TempFile(); - - var settings = new ValueSet - { - { "Path", tmpFile.FullFileName }, - { "Ensure", ensureValue }, - }; - - if (preCondition) - { - tmpFile.CreateFile(); - } - - Assert.Equal( - preCondition, - File.Exists(tmpFile.FullFileName)); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); - dscModule.InvokeSetResource( - pwsh, - settings, - TestModule.SimpleFileResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - - Assert.Equal( - postCondition, - File.Exists(tmpFile.FullFileName)); - } - - /// - /// Tests SimpleFileResource Set writes to the file. - /// - /// The text in the file before calling Set. - /// The test in the file after calling Set. - [Theory] - [InlineData(null, "after content")] - [InlineData("i am a content", "and im another")] - [InlineData("copy paste", "copy paste")] - public void SimpleFileResource_Set_Content(string? preSetContent, string postSetContent) - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - // Doesn't create a file. - using var tmpFile = new TempFile(); - if (preSetContent is not null) - { - tmpFile.CreateFile(preSetContent); - } - - var settings = new ValueSet - { - { "Path", tmpFile.FullFileName }, - { "Ensure", "Present" }, - { "Content", postSetContent }, - }; - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); - dscModule.InvokeSetResource( - pwsh, - settings, - TestModule.SimpleFileResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - - Assert.Equal( - postSetContent, - File.ReadAllText(tmpFile.FullFileName)); - } - - /// - /// Test SimpleFileResource Get. - /// - [Fact] - public void SimpleFileResource_Get() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - string content = "I'm out of ideas"; - using var tmpFile = new TempFile(content: content); - - var settings = new ValueSet - { - { "Path", tmpFile.FullFileName }, - }; - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); - var properties = dscModule.InvokeGetResource( - pwsh, - settings, - TestModule.SimpleFileResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - - Assert.True(properties.ContainsKey("Path")); - Assert.True(properties.TryGetValue("Path", out object pathResult)); - Assert.Equal(tmpFile.FullFileName, pathResult as string); - - // Present is just the default value. - Assert.True(properties.ContainsKey("Ensure")); - Assert.True(properties.TryGetValue("Ensure", out object ensureResult)); - Assert.Equal("Present", ensureResult as string); - - Assert.True(properties.ContainsKey("Content")); - Assert.True(properties.TryGetValue("Content", out object contentResult)); - Assert.Equal(content, contentResult); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System.IO; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Windows.Foundation.Collections; + using Xunit; + using Xunit.Abstractions; + using static Microsoft.Management.Configuration.UnitTests.Helpers.PowerShellTestsConstants; + + /// + /// Class that tests a little not that complex resource. + /// + [Collection("UnitTestCollection")] + [InProc] + public class DscModuleV2SimpleFileResourceTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Fixture. + /// log. + public DscModuleV2SimpleFileResourceTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Test SimpleFileResource Ensure Present and Absent when file doesn't exist. + /// + /// Ensure value. + /// Expected result. + [Theory] + [InlineData("Absent", true)] + [InlineData("Present", false)] + public void SimpleFileResource_FileAbsent(string ensureValue, bool expectedResult) + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + // Doesn't create a file. + using var tmpFile = new TempFile(); + + var settings = new ValueSet + { + { "Path", tmpFile.FullFileName }, + { "Ensure", ensureValue }, + }; + + var dscModule = new DscModuleV2(); + + using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); + Assert.Equal( + expectedResult, + dscModule.InvokeTestResource( + pwsh, + settings, + TestModule.SimpleFileResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Test SimpleFileResource when the file exists. + /// + [Fact] + public void SimpleFileResource_FilePresent() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + using var tmpFile = new TempFile(); + tmpFile.CreateFile(); + + var settings = new ValueSet + { + { "Path", tmpFile.FullFileName }, + { "Ensure", "Present" }, + }; + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); + Assert.True(dscModule.InvokeTestResource( + pwsh, + settings, + TestModule.SimpleFileResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Test SimpleFileResource with different content. + /// + [Fact] + public void SimpleFileResource_DifferentContent() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + using var tmpFile = new TempFile(content: "All work and no play makes Ruben a dull boy"); + + var settings = new ValueSet + { + { "Path", tmpFile.FullFileName }, + { "Ensure", "Present" }, + { "Content", "Is that a from somewhere?" }, + }; + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); + Assert.False(dscModule.InvokeTestResource( + pwsh, + settings, + TestModule.SimpleFileResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Test SimpleFileResource with same context. + /// + [Fact] + public void SimpleFileResource_SameContent() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + string content = "This is literally the same, dont fail"; + using var tmpFile = new TempFile(content: content); + + var settings = new ValueSet + { + { "Path", tmpFile.FullFileName }, + { "Ensure", "Present" }, + { "Content", content }, + }; + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); + Assert.True(dscModule.InvokeTestResource( + pwsh, + settings, + TestModule.SimpleFileResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Test SimpleFileResource Set creates the file. + /// + /// Ensure value. + /// If file exists before calling set. + /// If file exists after calling set. + [Theory] + [InlineData("Absent", false, false)] + [InlineData("Present", false, true)] + [InlineData("Absent", true, false)] + [InlineData("Present", true, true)] + public void SimpleFileResource_Set_Ensure(string ensureValue, bool preCondition, bool postCondition) + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + // Doesn't create a file. + using var tmpFile = new TempFile(); + + var settings = new ValueSet + { + { "Path", tmpFile.FullFileName }, + { "Ensure", ensureValue }, + }; + + if (preCondition) + { + tmpFile.CreateFile(); + } + + Assert.Equal( + preCondition, + File.Exists(tmpFile.FullFileName)); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); + dscModule.InvokeSetResource( + pwsh, + settings, + TestModule.SimpleFileResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + + Assert.Equal( + postCondition, + File.Exists(tmpFile.FullFileName)); + } + + /// + /// Tests SimpleFileResource Set writes to the file. + /// + /// The text in the file before calling Set. + /// The test in the file after calling Set. + [Theory] + [InlineData(null, "after content")] + [InlineData("i am a content", "and im another")] + [InlineData("copy paste", "copy paste")] + public void SimpleFileResource_Set_Content(string? preSetContent, string postSetContent) + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + // Doesn't create a file. + using var tmpFile = new TempFile(); + if (preSetContent is not null) + { + tmpFile.CreateFile(preSetContent); + } + + var settings = new ValueSet + { + { "Path", tmpFile.FullFileName }, + { "Ensure", "Present" }, + { "Content", postSetContent }, + }; + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); + dscModule.InvokeSetResource( + pwsh, + settings, + TestModule.SimpleFileResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + + Assert.Equal( + postSetContent, + File.ReadAllText(tmpFile.FullFileName)); + } + + /// + /// Test SimpleFileResource Get. + /// + [Fact] + public void SimpleFileResource_Get() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + string content = "I'm out of ideas"; + using var tmpFile = new TempFile(content: content); + + var settings = new ValueSet + { + { "Path", tmpFile.FullFileName }, + }; + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(processorEnv.Runspace); + var properties = dscModule.InvokeGetResource( + pwsh, + settings, + TestModule.SimpleFileResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + + Assert.True(properties.ContainsKey("Path")); + Assert.True(properties.TryGetValue("Path", out object pathResult)); + Assert.Equal(tmpFile.FullFileName, pathResult as string); + + // Present is just the default value. + Assert.True(properties.ContainsKey("Ensure")); + Assert.True(properties.TryGetValue("Ensure", out object ensureResult)); + Assert.Equal("Present", ensureResult as string); + + Assert.True(properties.ContainsKey("Content")); + Assert.True(properties.TryGetValue("Content", out object contentResult)); + Assert.Equal(content, contentResult); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs index 06a0bf968b..a2d40a6871 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscModuleV2Tests.cs @@ -1,623 +1,623 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.IO; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor.Exceptions; - using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.PowerShell.Commands; - using Windows.Foundation.Collections; - using Xunit; - using Xunit.Abstractions; - using static Microsoft.Management.Configuration.UnitTests.Helpers.PowerShellTestsConstants; - - /// - /// Tests DscModuleV2 with really simple resources. - /// - [Collection("UnitTestCollection")] - [InProc] - public class DscModuleV2Tests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Fixture. - /// log. - public DscModuleV2Tests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Tests GetAllDscResources. - /// - [Fact] - public void GetAllDscResources_Test() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var resources = dscModule.GetAllDscResources(pwsh); - - Assert.True(resources.Count > 0); - Assert.Contains(resources, r => r.Name == TestModule.SimpleFileResourceName); - } - - /// - /// Tests GetDscResourcesInModule. - /// - /// Module. - /// Expected DSC resources. - [Theory] - [InlineData(TestModule.SimpleTestResourceModuleName, 5)] - [InlineData("MyReallyFakeModule", 0)] - public void GetDscResourcesInModule_Test(string module, int expectedResources) - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var resources = dscModule.GetDscResourcesInModule( - pwsh, - PowerShellHelpers.CreateModuleSpecification(module)); - Assert.Equal(expectedResources, resources.Count); - } - - /// - /// Tests GetDscResourcesInModule with versions. - /// - [Fact] - public void GetDscResourcesInModule_VersionTest() - { - string newVersion = "1.0.0.0"; - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - // Get duplicated resources by creating a new directory and copy our modules. - // Change version and add them to the PSModulePath. - using var tmpDir = new TempDirectory(); - tmpDir.CopyDirectory(this.fixture.TestModulesPath); - var manifestFile = Path.Combine( - tmpDir.FullDirectoryPath, - TestModule.SimpleTestResourceModuleName, - TestModule.SimpleTestResourceManifestFileName); - File.WriteAllText( - manifestFile, - File.ReadAllText(manifestFile).Replace("0.0.0.1", newVersion)); - testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); - - var dscModule = new DscModuleV2(); - - // This doesn't work on v2 - ////var allResources = dscModule.GetDscResourcesInModule( - //// testEnvironment.Runspace, - //// PowerShellHelpers.CreateModuleSpecification(TestModule.SimpleTestResourceModuleName)); - ////Assert.Equal(8, allResources.Count); - { - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var ogResources = dscModule.GetDscResourcesInModule( - pwsh, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName, - version: TestModule.SimpleTestResourceVersion)); - Assert.Equal(5, ogResources.Count); - } - - { - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var newVersionResources = dscModule.GetDscResourcesInModule( - pwsh, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName, - version: newVersion)); - Assert.Equal(5, newVersionResources.Count); - } - - { - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var badVersionResources = dscModule.GetDscResourcesInModule( - pwsh, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName, - version: "1.2.3.4")); - Assert.Equal(0, badVersionResources.Count); - } - } - - /// - /// Tests GetDscResource. Should return a resource. - /// - [Fact] - public void GetDscResource_ResourceExists() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var resource = dscModule.GetDscResource( - pwsh, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification(TestModule.SimpleTestResourceModuleName)); - - Assert.NotNull(resource); - } - - /// - /// Test GetDscResource for a resource that doesn't exist. - /// - [Fact] - public void GetDscResource_ResourceDoesntExist() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var resource = dscModule.GetDscResource( - pwsh, - "FakeResourceName", - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - Assert.Null(resource); - } - - /// - /// Test GetDscResource when the same module is in different paths. - /// - [Fact] - public void GetDscResource_Conflict() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - // Get duplicated resources by creating a new directory and copy our modules. - // Then add it to the PSModulePath. - using var tmpDir = new TempDirectory(); - tmpDir.CopyDirectory(this.fixture.TestModulesPath); - testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - Assert.Throws( - () => dscModule.GetDscResource( - pwsh, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Tests GetDscResource when there are multiple versions of a resource. - /// - [Fact] - public void GetDscResource_DiffVersions() - { - string newVersion = "2.0.0.0"; - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - // Get duplicated resources by creating a new directory and copy our modules. - // Change version and add them to the PSModulePath. - using var tmpDir = new TempDirectory(); - tmpDir.CopyDirectory(this.fixture.TestModulesPath); - var manifestFile = Path.Combine( - tmpDir.FullDirectoryPath, - TestModule.SimpleTestResourceModuleName, - TestModule.SimpleTestResourceManifestFileName); - File.WriteAllText( - manifestFile, - File.ReadAllText(manifestFile).Replace("0.0.0.1", newVersion)); - testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); - - var dscModule = new DscModuleV2(); - - // specific version. - { - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var resource = dscModule.GetDscResource( - pwsh, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName, - TestModule.SimpleTestResourceVersion)); - Assert.NotNull(resource); - } - - { - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var dsc = dscModule.GetDscResource( - pwsh, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName, - version: newVersion)); - - Assert.NotNull(dsc); - Assert.NotNull(dsc.Version); - Assert.Equal(newVersion, dsc.Version.ToString()); - } - } - - /// - /// Calls Invoke-DscResource Get. - /// - [Fact] - public void InvokeGetResource_Test() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var getResult = dscModule.InvokeGetResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - - Assert.True(getResult.ContainsKey("key")); - Assert.True(getResult.TryGetValue("key", out object keyValue)); - Assert.Equal("SimpleTestResourceKey", keyValue as string); - } - - /// - /// Calls Invoke-DscResource Get. Resource Get throws. - /// - [Fact] - public void InvokeGetResource_ResourceThrows() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var exception = Assert.Throws(() => - dscModule.InvokeGetResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceThrowsName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - - Assert.IsType(exception.InnerException); - } - - /// - /// Calls Invoke-DscResource Get. Resource writes error. - /// - [Fact(Skip = "Not supported in PSDesiredStateConfiguration 2.0.7")] - public void InvokeGetResource_ResourceError() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - Assert.Throws(() => - dscModule.InvokeGetResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceErrorName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Calls Invoke-DscResource Get. Resource does not exist. - /// - [Fact] - public void InvokeGetResource_ResourceDoesntExist() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var exception = Assert.Throws( - () => dscModule.InvokeGetResource( - pwsh, - new ValueSet(), - "FakeResourceName", - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - - Assert.IsType(exception.InnerException); - } - - /// - /// Calls Invoke-DscResource Test. - /// - /// Setting value. - /// Expected result. - [Theory] - [InlineData("4815162342", true)] - [InlineData("notalostreference", false)] - public void InvokeTestResource_Test(string value, bool expectedResult) - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - - var settings = new ValueSet() - { - { "secretCode", value }, - }; - - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var testResult = dscModule.InvokeTestResource( - pwsh, - settings, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - - Assert.Equal(expectedResult, testResult); - } - - /// - /// Calls Invoke-DscResource Test. Resource throws. - /// - [Fact] - public void InvokeTestResource_Throws() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var exception = Assert.Throws(() => - dscModule.InvokeTestResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceThrowsName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - - Assert.IsType(exception.InnerException); - } - - /// - /// Calls Invoke-DscResource Test. Resource writes error. - /// - [Fact(Skip = "Not supported in PSDesiredStateConfiguration 2.0.7")] - public void InvokeTestResource_ResourceError() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - Assert.Throws(() => - dscModule.InvokeTestResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceErrorName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Calls Invoke-DscResource Test. Resource does not exist. - /// - [Fact] - public void InvokeTestResource_ResourceDoesntExist() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var exception = Assert.Throws(() => - _ = dscModule.InvokeTestResource( - pwsh, - new ValueSet(), - "FakeResourceName", - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - - Assert.IsType(exception.InnerException); - } - - /// - /// Calls Invoke-DscResource Set. - /// - /// Setting value. - /// Expected reboot required. - [Fact] - public void InvokeSetResource_Test() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - - var settings = new ValueSet() - { - { "secretCode", "4815162342" }, - }; - - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var testResult = dscModule.InvokeSetResource( - pwsh, - settings, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - - // TODO: Verify reboot required when is supported for class resources. - ////Assert.Equal(rebootRequired, testResult); - } - - /// - /// Calls Invoke-DscResource Set. Resource throws. - /// - [Fact] - public void InvokeSetResource_Throws() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var exception = Assert.Throws(() => - dscModule.InvokeSetResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceThrowsName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - - Assert.IsType(exception.InnerException); - } - - /// - /// Calls Invoke-DscResource Set. Resource writes error. - /// - [Fact(Skip = "Not supported in PSDesiredStateConfiguration 2.0.7")] - public void InvokeSetResource_ResourceError() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - Assert.Throws(() => - dscModule.InvokeSetResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceErrorName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - } - - /// - /// Calls Invoke-DscResource Set. Resource does not exist. - /// - [Fact] - public void InvokeSetResource_ResourceDoesntExist() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var exception = Assert.Throws(() => - dscModule.InvokeSetResource( - pwsh, - new ValueSet(), - "FakeResourceName", - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - - Assert.IsType(exception.InnerException); - } - - /// - /// Test calling Invoke-DscResource when a resource has multiple versions. - /// - [Fact] - public void InvokeResource_MultipleVersions() - { - string newVersion = "0.0.2.0"; - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - // Get duplicated resources by creating a new directory and copy our modules. - // Change version and add them to the PSModulePath. - using var tmpDir = new TempDirectory(); - tmpDir.CopyDirectory(this.fixture.TestModulesPath); - var manifestFile = Path.Combine( - tmpDir.FullDirectoryPath, - TestModule.SimpleTestResourceModuleName, - TestModule.SimpleTestResourceManifestFileName); - File.WriteAllText( - manifestFile, - File.ReadAllText(manifestFile).Replace("0.0.0.1", newVersion)); - testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); - - var dscModule = new DscModuleV2(); - { - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - dscModule.InvokeSetResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName, - TestModule.SimpleTestResourceVersion)); - } - - { - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - dscModule.InvokeSetResource( - pwsh, - new ValueSet(), - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName, - version: newVersion)); - } - } - - /// - /// Calls Invoke-DscResource invalid arguments. - /// - [Fact] - public void InvokeSetResource_InvalidArguments() - { - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - - var dscModule = new DscModuleV2(); - - var settings = new ValueSet() - { - { "Fake", "please dont add it" }, - }; - - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var e = Assert.Throws(() => dscModule.InvokeSetResource( - pwsh, - settings, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName))); - - Assert.Contains("The property 'Fake' cannot be found on this object.", e.Message); - Assert.Equal(ConfigurationUnitResultSource.ConfigurationSet, e.ResultSource); - } - - /// - /// Tests GetDscResourcesInModule with versions. - /// - [Fact] - public void InvokeSetResource_ModulePathSpaces() - { - // Copy test module to a directory with spaces. - using var tmpDir = new TempDirectory(directoryName: Path.Combine(Guid.NewGuid().ToString(), "Path With Spaces")); - tmpDir.CopyDirectory(this.fixture.TestModulesPath); - var manifestFile = Path.Combine( - tmpDir.FullDirectoryPath, - TestModule.SimpleTestResourceModuleName, - TestModule.SimpleTestResourceManifestFileName); - - var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); - testEnvironment.CleanupPSModulePath(this.fixture.TestModulesPath); - testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); - - var dscModule = new DscModuleV2(); - - var settings = new ValueSet() - { - { "secretCode", "4815162342" }, - }; - - using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); - var testResult = dscModule.InvokeSetResource( - pwsh, - settings, - TestModule.SimpleTestResourceName, - PowerShellHelpers.CreateModuleSpecification( - TestModule.SimpleTestResourceModuleName)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.IO; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor.Exceptions; + using Microsoft.Management.Configuration.Processor.PowerShell.DscModules; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.PowerShell.Commands; + using Windows.Foundation.Collections; + using Xunit; + using Xunit.Abstractions; + using static Microsoft.Management.Configuration.UnitTests.Helpers.PowerShellTestsConstants; + + /// + /// Tests DscModuleV2 with really simple resources. + /// + [Collection("UnitTestCollection")] + [InProc] + public class DscModuleV2Tests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Fixture. + /// log. + public DscModuleV2Tests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Tests GetAllDscResources. + /// + [Fact] + public void GetAllDscResources_Test() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var resources = dscModule.GetAllDscResources(pwsh); + + Assert.True(resources.Count > 0); + Assert.Contains(resources, r => r.Name == TestModule.SimpleFileResourceName); + } + + /// + /// Tests GetDscResourcesInModule. + /// + /// Module. + /// Expected DSC resources. + [Theory] + [InlineData(TestModule.SimpleTestResourceModuleName, 5)] + [InlineData("MyReallyFakeModule", 0)] + public void GetDscResourcesInModule_Test(string module, int expectedResources) + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var resources = dscModule.GetDscResourcesInModule( + pwsh, + PowerShellHelpers.CreateModuleSpecification(module)); + Assert.Equal(expectedResources, resources.Count); + } + + /// + /// Tests GetDscResourcesInModule with versions. + /// + [Fact] + public void GetDscResourcesInModule_VersionTest() + { + string newVersion = "1.0.0.0"; + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + // Get duplicated resources by creating a new directory and copy our modules. + // Change version and add them to the PSModulePath. + using var tmpDir = new TempDirectory(); + tmpDir.CopyDirectory(this.fixture.TestModulesPath); + var manifestFile = Path.Combine( + tmpDir.FullDirectoryPath, + TestModule.SimpleTestResourceModuleName, + TestModule.SimpleTestResourceManifestFileName); + File.WriteAllText( + manifestFile, + File.ReadAllText(manifestFile).Replace("0.0.0.1", newVersion)); + testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); + + var dscModule = new DscModuleV2(); + + // This doesn't work on v2 + ////var allResources = dscModule.GetDscResourcesInModule( + //// testEnvironment.Runspace, + //// PowerShellHelpers.CreateModuleSpecification(TestModule.SimpleTestResourceModuleName)); + ////Assert.Equal(8, allResources.Count); + { + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var ogResources = dscModule.GetDscResourcesInModule( + pwsh, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName, + version: TestModule.SimpleTestResourceVersion)); + Assert.Equal(5, ogResources.Count); + } + + { + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var newVersionResources = dscModule.GetDscResourcesInModule( + pwsh, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName, + version: newVersion)); + Assert.Equal(5, newVersionResources.Count); + } + + { + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var badVersionResources = dscModule.GetDscResourcesInModule( + pwsh, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName, + version: "1.2.3.4")); + Assert.Equal(0, badVersionResources.Count); + } + } + + /// + /// Tests GetDscResource. Should return a resource. + /// + [Fact] + public void GetDscResource_ResourceExists() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var resource = dscModule.GetDscResource( + pwsh, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification(TestModule.SimpleTestResourceModuleName)); + + Assert.NotNull(resource); + } + + /// + /// Test GetDscResource for a resource that doesn't exist. + /// + [Fact] + public void GetDscResource_ResourceDoesntExist() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var resource = dscModule.GetDscResource( + pwsh, + "FakeResourceName", + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + Assert.Null(resource); + } + + /// + /// Test GetDscResource when the same module is in different paths. + /// + [Fact] + public void GetDscResource_Conflict() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + // Get duplicated resources by creating a new directory and copy our modules. + // Then add it to the PSModulePath. + using var tmpDir = new TempDirectory(); + tmpDir.CopyDirectory(this.fixture.TestModulesPath); + testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + Assert.Throws( + () => dscModule.GetDscResource( + pwsh, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Tests GetDscResource when there are multiple versions of a resource. + /// + [Fact] + public void GetDscResource_DiffVersions() + { + string newVersion = "2.0.0.0"; + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + // Get duplicated resources by creating a new directory and copy our modules. + // Change version and add them to the PSModulePath. + using var tmpDir = new TempDirectory(); + tmpDir.CopyDirectory(this.fixture.TestModulesPath); + var manifestFile = Path.Combine( + tmpDir.FullDirectoryPath, + TestModule.SimpleTestResourceModuleName, + TestModule.SimpleTestResourceManifestFileName); + File.WriteAllText( + manifestFile, + File.ReadAllText(manifestFile).Replace("0.0.0.1", newVersion)); + testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); + + var dscModule = new DscModuleV2(); + + // specific version. + { + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var resource = dscModule.GetDscResource( + pwsh, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName, + TestModule.SimpleTestResourceVersion)); + Assert.NotNull(resource); + } + + { + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var dsc = dscModule.GetDscResource( + pwsh, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName, + version: newVersion)); + + Assert.NotNull(dsc); + Assert.NotNull(dsc.Version); + Assert.Equal(newVersion, dsc.Version.ToString()); + } + } + + /// + /// Calls Invoke-DscResource Get. + /// + [Fact] + public void InvokeGetResource_Test() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var getResult = dscModule.InvokeGetResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + + Assert.True(getResult.ContainsKey("key")); + Assert.True(getResult.TryGetValue("key", out object keyValue)); + Assert.Equal("SimpleTestResourceKey", keyValue as string); + } + + /// + /// Calls Invoke-DscResource Get. Resource Get throws. + /// + [Fact] + public void InvokeGetResource_ResourceThrows() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var exception = Assert.Throws(() => + dscModule.InvokeGetResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceThrowsName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.IsType(exception.InnerException); + } + + /// + /// Calls Invoke-DscResource Get. Resource writes error. + /// + [Fact(Skip = "Not supported in PSDesiredStateConfiguration 2.0.7")] + public void InvokeGetResource_ResourceError() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + Assert.Throws(() => + dscModule.InvokeGetResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceErrorName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Calls Invoke-DscResource Get. Resource does not exist. + /// + [Fact] + public void InvokeGetResource_ResourceDoesntExist() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var exception = Assert.Throws( + () => dscModule.InvokeGetResource( + pwsh, + new ValueSet(), + "FakeResourceName", + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.IsType(exception.InnerException); + } + + /// + /// Calls Invoke-DscResource Test. + /// + /// Setting value. + /// Expected result. + [Theory] + [InlineData("4815162342", true)] + [InlineData("notalostreference", false)] + public void InvokeTestResource_Test(string value, bool expectedResult) + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + + var settings = new ValueSet() + { + { "secretCode", value }, + }; + + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var testResult = dscModule.InvokeTestResource( + pwsh, + settings, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + + Assert.Equal(expectedResult, testResult); + } + + /// + /// Calls Invoke-DscResource Test. Resource throws. + /// + [Fact] + public void InvokeTestResource_Throws() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var exception = Assert.Throws(() => + dscModule.InvokeTestResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceThrowsName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.IsType(exception.InnerException); + } + + /// + /// Calls Invoke-DscResource Test. Resource writes error. + /// + [Fact(Skip = "Not supported in PSDesiredStateConfiguration 2.0.7")] + public void InvokeTestResource_ResourceError() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + Assert.Throws(() => + dscModule.InvokeTestResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceErrorName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Calls Invoke-DscResource Test. Resource does not exist. + /// + [Fact] + public void InvokeTestResource_ResourceDoesntExist() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var exception = Assert.Throws(() => + _ = dscModule.InvokeTestResource( + pwsh, + new ValueSet(), + "FakeResourceName", + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.IsType(exception.InnerException); + } + + /// + /// Calls Invoke-DscResource Set. + /// + /// Setting value. + /// Expected reboot required. + [Fact] + public void InvokeSetResource_Test() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + + var settings = new ValueSet() + { + { "secretCode", "4815162342" }, + }; + + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var testResult = dscModule.InvokeSetResource( + pwsh, + settings, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + + // TODO: Verify reboot required when is supported for class resources. + ////Assert.Equal(rebootRequired, testResult); + } + + /// + /// Calls Invoke-DscResource Set. Resource throws. + /// + [Fact] + public void InvokeSetResource_Throws() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var exception = Assert.Throws(() => + dscModule.InvokeSetResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceThrowsName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.IsType(exception.InnerException); + } + + /// + /// Calls Invoke-DscResource Set. Resource writes error. + /// + [Fact(Skip = "Not supported in PSDesiredStateConfiguration 2.0.7")] + public void InvokeSetResource_ResourceError() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + Assert.Throws(() => + dscModule.InvokeSetResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceErrorName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + } + + /// + /// Calls Invoke-DscResource Set. Resource does not exist. + /// + [Fact] + public void InvokeSetResource_ResourceDoesntExist() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var exception = Assert.Throws(() => + dscModule.InvokeSetResource( + pwsh, + new ValueSet(), + "FakeResourceName", + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.IsType(exception.InnerException); + } + + /// + /// Test calling Invoke-DscResource when a resource has multiple versions. + /// + [Fact] + public void InvokeResource_MultipleVersions() + { + string newVersion = "0.0.2.0"; + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + // Get duplicated resources by creating a new directory and copy our modules. + // Change version and add them to the PSModulePath. + using var tmpDir = new TempDirectory(); + tmpDir.CopyDirectory(this.fixture.TestModulesPath); + var manifestFile = Path.Combine( + tmpDir.FullDirectoryPath, + TestModule.SimpleTestResourceModuleName, + TestModule.SimpleTestResourceManifestFileName); + File.WriteAllText( + manifestFile, + File.ReadAllText(manifestFile).Replace("0.0.0.1", newVersion)); + testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); + + var dscModule = new DscModuleV2(); + { + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + dscModule.InvokeSetResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName, + TestModule.SimpleTestResourceVersion)); + } + + { + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + dscModule.InvokeSetResource( + pwsh, + new ValueSet(), + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName, + version: newVersion)); + } + } + + /// + /// Calls Invoke-DscResource invalid arguments. + /// + [Fact] + public void InvokeSetResource_InvalidArguments() + { + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + + var dscModule = new DscModuleV2(); + + var settings = new ValueSet() + { + { "Fake", "please dont add it" }, + }; + + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var e = Assert.Throws(() => dscModule.InvokeSetResource( + pwsh, + settings, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName))); + + Assert.Contains("The property 'Fake' cannot be found on this object.", e.Message); + Assert.Equal(ConfigurationUnitResultSource.ConfigurationSet, e.ResultSource); + } + + /// + /// Tests GetDscResourcesInModule with versions. + /// + [Fact] + public void InvokeSetResource_ModulePathSpaces() + { + // Copy test module to a directory with spaces. + using var tmpDir = new TempDirectory(directoryName: Path.Combine(Guid.NewGuid().ToString(), "Path With Spaces")); + tmpDir.CopyDirectory(this.fixture.TestModulesPath); + var manifestFile = Path.Combine( + tmpDir.FullDirectoryPath, + TestModule.SimpleTestResourceModuleName, + TestModule.SimpleTestResourceManifestFileName); + + var testEnvironment = this.fixture.PrepareTestProcessorEnvironment(); + testEnvironment.CleanupPSModulePath(this.fixture.TestModulesPath); + testEnvironment.AppendPSModulePath(tmpDir.FullDirectoryPath); + + var dscModule = new DscModuleV2(); + + var settings = new ValueSet() + { + { "secretCode", "4815162342" }, + }; + + using PowerShell pwsh = PowerShell.Create(testEnvironment.Runspace); + var testResult = dscModule.InvokeSetResource( + pwsh, + settings, + TestModule.SimpleTestResourceName, + PowerShellHelpers.CreateModuleSpecification( + TestModule.SimpleTestResourceModuleName)); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscResourceMapTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscResourceMapTests.cs index ca403bcd7c..39712e0328 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/DscResourceMapTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/DscResourceMapTests.cs @@ -1,178 +1,178 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// DscResourceMap tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class DscResourceMapTests - { - private const string ResourceZoro = "xResourceZoro"; - - private const string ModuleMugiwara = "xMugiwaraModule"; - private const string ModuleMugiwaraResourceLuffy = "xResourceLuffy"; - - private const string ModuleOni = "xModuleOni"; - private const string ModuleOniResourceKaido = "xResourceKaido"; - private const string ModuleOniResourceYamato = "xResourceYamato"; - - private static readonly Version VersionZoro = new Version("1.0.0.0"); - - private static readonly Version VersionLuffyGear4 = new Version("4.0.0.1"); - private static readonly Version VersionLuffyGear5 = new Version("5.0.0.0"); - - private static readonly Version VersionKaido = new Version("0.0.0.4"); - private static readonly Version VersionYamato = new Version("0.0.0.11"); - - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log. - public DscResourceMapTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Test GetResource for resources in the map. - /// - [Fact] - public void DscResourcesMap_GetResource() - { - var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); - - // Get just by name. - var zoroResult = dscResourceMap.GetResource(ResourceZoro, null, null); - Assert.NotNull(zoroResult); - Assert.Equal(ResourceZoro, zoroResult.Name); - Assert.Equal(VersionZoro, zoroResult.Version); - - // Name not normalized. - var zoroResultNormalize = dscResourceMap.GetResource("XRESOURCEZORO", null, null); - Assert.NotNull(zoroResultNormalize); - Assert.Equal(ResourceZoro, zoroResultNormalize.Name); - Assert.Equal(VersionZoro, zoroResultNormalize.Version); - - var yamatoResult = dscResourceMap.GetResource(ModuleOniResourceYamato, null, null); - Assert.NotNull(yamatoResult); - Assert.Equal(ModuleOniResourceYamato, yamatoResult.Name); - Assert.Equal(ModuleOni, yamatoResult.ModuleName); - Assert.Equal(VersionYamato, yamatoResult.Version); - - // Just by name and module get latest. - var luffyResult5 = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, ModuleMugiwara, null); - Assert.NotNull(luffyResult5); - Assert.Equal(ModuleMugiwaraResourceLuffy, luffyResult5.Name); - Assert.Equal(ModuleMugiwara, luffyResult5.ModuleName); - Assert.Equal(VersionLuffyGear5, luffyResult5.Version); - - // Specific version. - var luffyResult4 = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear4); - Assert.NotNull(luffyResult4); - Assert.Equal(ModuleMugiwaraResourceLuffy, luffyResult4.Name); - Assert.Equal(ModuleMugiwara, luffyResult4.ModuleName); - Assert.Equal(VersionLuffyGear4, luffyResult4.Version); - - // Module name not normalized - var luffyResult4Normalized = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, "XMUGIWARAMODULE", VersionLuffyGear4); - Assert.NotNull(luffyResult4Normalized); - Assert.Equal(ModuleMugiwaraResourceLuffy, luffyResult4Normalized.Name); - Assert.Equal(ModuleMugiwara, luffyResult4Normalized.ModuleName); - Assert.Equal(VersionLuffyGear4, luffyResult4Normalized.Version); - } - - /// - /// Tests GetResource when resources are not in the maps. - /// - [Fact] - public void DscResourcesMap_GetResource_NoResource() - { - var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); - - // Zoro is lost. - var zoroResult = dscResourceMap.GetResource(ResourceZoro, ModuleMugiwara, null); - Assert.Null(zoroResult); - - // Yamato didn't join (spoilers) - var yamatoResult = dscResourceMap.GetResource(ModuleOniResourceYamato, ModuleMugiwara, VersionYamato); - Assert.Null(yamatoResult); - - // Gear 6 is not a thing (?) - var luffyResult = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, ModuleMugiwara, Version.Parse("6.0.0.0")); - Assert.Null(luffyResult); - - // Wrong universe. - var gokuResult = dscResourceMap.GetResource("xResourceGoku", null, null); - Assert.Null(gokuResult); - } - - /// - /// Test Exists for resources in the map. - /// - [Fact] - public void DscResourcesMap_Exists() - { - var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); - - Assert.True(dscResourceMap.Exists(ResourceZoro, null, null)); - Assert.True(dscResourceMap.Exists(ModuleOniResourceYamato, null, null)); - Assert.True(dscResourceMap.Exists(ModuleMugiwaraResourceLuffy, ModuleMugiwara, null)); - Assert.True(dscResourceMap.Exists(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear4)); - } - - /// - /// Tests Exists when resources are not in the maps. - /// - [Fact] - public void DscResourcesMap_Exists_NoResource() - { - var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); - - Assert.False(dscResourceMap.Exists(ResourceZoro, ModuleMugiwara, null)); - Assert.False(dscResourceMap.Exists(ModuleOniResourceYamato, ModuleMugiwara, VersionYamato)); - Assert.False(dscResourceMap.Exists(ModuleMugiwaraResourceLuffy, ModuleMugiwara, Version.Parse("6.0.0.0"))); - Assert.False(dscResourceMap.Exists("xResourceGoku", null, null)); - } - - /// - /// Creates 5 fake resources. - /// 1 resource without module name. - /// 2 resources module A same resource v1 v2. - /// 2 resources module B different resources. - /// - /// Fake resources. - private IReadOnlyList CreateDscResourceInfo() - { - return new List() - { - new DscResourceInfoInternal(ResourceZoro, null, VersionZoro), - - new DscResourceInfoInternal(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear4), - new DscResourceInfoInternal(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear5), - - new DscResourceInfoInternal(ModuleOniResourceKaido, ModuleOni, VersionKaido), - new DscResourceInfoInternal(ModuleOniResourceYamato, ModuleOni, VersionYamato), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.PowerShell.DscResourcesInfo; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// DscResourceMap tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class DscResourceMapTests + { + private const string ResourceZoro = "xResourceZoro"; + + private const string ModuleMugiwara = "xMugiwaraModule"; + private const string ModuleMugiwaraResourceLuffy = "xResourceLuffy"; + + private const string ModuleOni = "xModuleOni"; + private const string ModuleOniResourceKaido = "xResourceKaido"; + private const string ModuleOniResourceYamato = "xResourceYamato"; + + private static readonly Version VersionZoro = new Version("1.0.0.0"); + + private static readonly Version VersionLuffyGear4 = new Version("4.0.0.1"); + private static readonly Version VersionLuffyGear5 = new Version("5.0.0.0"); + + private static readonly Version VersionKaido = new Version("0.0.0.4"); + private static readonly Version VersionYamato = new Version("0.0.0.11"); + + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log. + public DscResourceMapTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Test GetResource for resources in the map. + /// + [Fact] + public void DscResourcesMap_GetResource() + { + var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); + + // Get just by name. + var zoroResult = dscResourceMap.GetResource(ResourceZoro, null, null); + Assert.NotNull(zoroResult); + Assert.Equal(ResourceZoro, zoroResult.Name); + Assert.Equal(VersionZoro, zoroResult.Version); + + // Name not normalized. + var zoroResultNormalize = dscResourceMap.GetResource("XRESOURCEZORO", null, null); + Assert.NotNull(zoroResultNormalize); + Assert.Equal(ResourceZoro, zoroResultNormalize.Name); + Assert.Equal(VersionZoro, zoroResultNormalize.Version); + + var yamatoResult = dscResourceMap.GetResource(ModuleOniResourceYamato, null, null); + Assert.NotNull(yamatoResult); + Assert.Equal(ModuleOniResourceYamato, yamatoResult.Name); + Assert.Equal(ModuleOni, yamatoResult.ModuleName); + Assert.Equal(VersionYamato, yamatoResult.Version); + + // Just by name and module get latest. + var luffyResult5 = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, ModuleMugiwara, null); + Assert.NotNull(luffyResult5); + Assert.Equal(ModuleMugiwaraResourceLuffy, luffyResult5.Name); + Assert.Equal(ModuleMugiwara, luffyResult5.ModuleName); + Assert.Equal(VersionLuffyGear5, luffyResult5.Version); + + // Specific version. + var luffyResult4 = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear4); + Assert.NotNull(luffyResult4); + Assert.Equal(ModuleMugiwaraResourceLuffy, luffyResult4.Name); + Assert.Equal(ModuleMugiwara, luffyResult4.ModuleName); + Assert.Equal(VersionLuffyGear4, luffyResult4.Version); + + // Module name not normalized + var luffyResult4Normalized = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, "XMUGIWARAMODULE", VersionLuffyGear4); + Assert.NotNull(luffyResult4Normalized); + Assert.Equal(ModuleMugiwaraResourceLuffy, luffyResult4Normalized.Name); + Assert.Equal(ModuleMugiwara, luffyResult4Normalized.ModuleName); + Assert.Equal(VersionLuffyGear4, luffyResult4Normalized.Version); + } + + /// + /// Tests GetResource when resources are not in the maps. + /// + [Fact] + public void DscResourcesMap_GetResource_NoResource() + { + var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); + + // Zoro is lost. + var zoroResult = dscResourceMap.GetResource(ResourceZoro, ModuleMugiwara, null); + Assert.Null(zoroResult); + + // Yamato didn't join (spoilers) + var yamatoResult = dscResourceMap.GetResource(ModuleOniResourceYamato, ModuleMugiwara, VersionYamato); + Assert.Null(yamatoResult); + + // Gear 6 is not a thing (?) + var luffyResult = dscResourceMap.GetResource(ModuleMugiwaraResourceLuffy, ModuleMugiwara, Version.Parse("6.0.0.0")); + Assert.Null(luffyResult); + + // Wrong universe. + var gokuResult = dscResourceMap.GetResource("xResourceGoku", null, null); + Assert.Null(gokuResult); + } + + /// + /// Test Exists for resources in the map. + /// + [Fact] + public void DscResourcesMap_Exists() + { + var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); + + Assert.True(dscResourceMap.Exists(ResourceZoro, null, null)); + Assert.True(dscResourceMap.Exists(ModuleOniResourceYamato, null, null)); + Assert.True(dscResourceMap.Exists(ModuleMugiwaraResourceLuffy, ModuleMugiwara, null)); + Assert.True(dscResourceMap.Exists(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear4)); + } + + /// + /// Tests Exists when resources are not in the maps. + /// + [Fact] + public void DscResourcesMap_Exists_NoResource() + { + var dscResourceMap = new DscResourcesMap(this.CreateDscResourceInfo()); + + Assert.False(dscResourceMap.Exists(ResourceZoro, ModuleMugiwara, null)); + Assert.False(dscResourceMap.Exists(ModuleOniResourceYamato, ModuleMugiwara, VersionYamato)); + Assert.False(dscResourceMap.Exists(ModuleMugiwaraResourceLuffy, ModuleMugiwara, Version.Parse("6.0.0.0"))); + Assert.False(dscResourceMap.Exists("xResourceGoku", null, null)); + } + + /// + /// Creates 5 fake resources. + /// 1 resource without module name. + /// 2 resources module A same resource v1 v2. + /// 2 resources module B different resources. + /// + /// Fake resources. + private IReadOnlyList CreateDscResourceInfo() + { + return new List() + { + new DscResourceInfoInternal(ResourceZoro, null, VersionZoro), + + new DscResourceInfoInternal(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear4), + new DscResourceInfoInternal(ModuleMugiwaraResourceLuffy, ModuleMugiwara, VersionLuffyGear5), + + new DscResourceInfoInternal(ModuleOniResourceKaido, ModuleOni, VersionKaido), + new DscResourceInfoInternal(ModuleOniResourceYamato, ModuleOni, VersionYamato), + }; + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ExceptionExtensionsTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ExceptionExtensionsTests.cs index 02c29ae37b..8e10b4ae38 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ExceptionExtensionsTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ExceptionExtensionsTests.cs @@ -1,66 +1,66 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.PowerShell.Commands; - using Xunit; - using Xunit.Abstractions; - - /// - /// Exception extension tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ExceptionExtensionsTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ExceptionExtensionsTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Tests GetMostInnerException. - /// - [Fact] - public void GetMostInnerException_Test() - { - var exception = new Exception( - "message1", - new WriteErrorException( - "WriteException", - new ArgumentNullException())); - - var mostInner = exception.GetMostInnerException(); - Assert.IsType(mostInner); - - var exception2 = new Exception( - "message2", - new WriteErrorException( - "WriteException2")); - - mostInner = exception2.GetMostInnerException(); - Assert.IsType(mostInner); - - var exception3 = new ArgumentOutOfRangeException("message2"); - mostInner = exception3.GetMostInnerException(); - Assert.IsType(mostInner); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.PowerShell.Commands; + using Xunit; + using Xunit.Abstractions; + + /// + /// Exception extension tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ExceptionExtensionsTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ExceptionExtensionsTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Tests GetMostInnerException. + /// + [Fact] + public void GetMostInnerException_Test() + { + var exception = new Exception( + "message1", + new WriteErrorException( + "WriteException", + new ArgumentNullException())); + + var mostInner = exception.GetMostInnerException(); + Assert.IsType(mostInner); + + var exception2 = new Exception( + "message2", + new WriteErrorException( + "WriteException2")); + + mostInner = exception2.GetMostInnerException(); + Assert.IsType(mostInner); + + var exception3 = new ArgumentOutOfRangeException("message2"); + mostInner = exception3.GetMostInnerException(); + Assert.IsType(mostInner); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/HashtableExtensionsTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/HashtableExtensionsTests.cs index db2e37ebed..9d7adf045b 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/HashtableExtensionsTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/HashtableExtensionsTests.cs @@ -9,8 +9,8 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests using System.Collections; using Microsoft.Management.Configuration.Processor.Exceptions; using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; using Windows.Foundation.Collections; using Xunit; using Xunit.Abstractions; @@ -18,7 +18,7 @@ namespace Microsoft.Management.Configuration.UnitTests.Tests /// /// Hashtable extension tests. /// - [Collection("UnitTestCollection")] + [Collection("UnitTestCollection")] [InProc] public class HashtableExtensionsTests { @@ -69,7 +69,7 @@ public void ToValueSet_InnerHashtable() { var ht = new Hashtable() { - { + { "hashtableKey", new Hashtable() { { "key1", "value1" }, diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs index 9b9c661d65..e4487827f8 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/OpenConfigurationSetTests.cs @@ -1,1385 +1,1385 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections.Generic; - using System.DirectoryServices; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Microsoft.VisualBasic; - using Windows.Foundation.Collections; - using Windows.Storage.Streams; - using WinRT; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for parsing configuration sets from streams. - /// - [Collection("UnitTestCollection")] - [InProc] - [OutOfProc] - public class OpenConfigurationSetTests : ConfigurationProcessorTestBase - { - /// - /// The directives key for the module property. - /// - internal const string ModuleDirective = "module"; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public OpenConfigurationSetTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// Passes a null stream as input. - /// - [Fact] - public void NullStream() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(null); - Assert.Null(result.Set); - Assert.IsType(result.ResultCode); - Assert.Equal(string.Empty, result.Field); - } - - /// - /// Passes an empty stream as input. - /// - [Fact] - public void EmptyStream() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(string.Empty)); - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_YAML, result.ResultCode.HResult); - Assert.Equal(string.Empty, result.Field); - Assert.Equal(0U, result.Line); - Assert.Equal(0U, result.Column); - } - - /// - /// Passes a stream with a single null byte in it. - /// - [Fact] - public void NullByteStream() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream("\0")); - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_YAML, result.ResultCode.HResult); - Assert.NotEqual(string.Empty, result.Field); - Assert.Equal(0U, result.Line); - Assert.Equal(0U, result.Column); - } - - /// - /// Passes YAML, but it isn't anything like a configuration file. - /// - [Fact] - public void NotConfigYAML() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream("yaml: yep")); - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_FIELD, result.ResultCode.HResult); - Assert.Equal("$schema", result.Field); - Assert.Equal(0U, result.Line); - Assert.Equal(0U, result.Column); - } - - /// - /// Passes YAML without a schema version. - /// - [Fact] - public void NoConfigVersion() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - thing: 1 -")); - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_FIELD, result.ResultCode.HResult); - Assert.Equal("configurationVersion", result.Field); - Assert.Equal(0U, result.Line); - Assert.Equal(0U, result.Column); - } - - /// - /// Passes YAML that appears to be from the distant future. - /// - [Fact] - public void UnknownConfigVersion() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 99999999 -")); - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, result.ResultCode.HResult); - Assert.Equal("configurationVersion", result.Field); - Assert.Equal("99999999", result.Value); - Assert.Equal(0U, result.Line); - Assert.Equal(0U, result.Column); - } - - /// - /// Has one of each type of intent to ensure that it is set properly. - /// - [Fact] - public void EnsureIntent() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.1 - assertions: - - resource: Assert - parameters: - - resource: Inform - resources: - - resource: Apply -")); - - Assert.NotNull(result.Set); - Assert.Null(result.ResultCode); - Assert.Equal(string.Empty, result.Field); - - var units = result.Set.Units; - Assert.Equal(3, units.Count); - bool sawAssert = false; - bool sawInform = false; - bool sawApply = false; - - foreach (var unit in units) - { - Assert.Equal(unit.Type, unit.Intent.ToString()); - switch (unit.Intent) - { - case ConfigurationUnitIntent.Assert: sawAssert = true; break; - case ConfigurationUnitIntent.Inform: sawInform = true; break; - case ConfigurationUnitIntent.Apply: sawApply = true; break; - default: Assert.Fail("Unknown intent"); break; - } - } - - Assert.True(sawAssert); - Assert.True(sawInform); - Assert.True(sawApply); - } - - /// - /// Passes YAML with resources being something other than a sequence. - /// - [Fact] - public void NonSequenceUnits() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.1 - resources: 1 -")); - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, result.ResultCode.HResult); - Assert.Equal("resources", result.Field); - Assert.Equal(4U, result.Line); - Assert.NotEqual(0U, result.Column); - } - - /// - /// Passes YAML with a resource being something other than a map. - /// - [Fact] - public void NonMapSequenceUnits() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.1 - resources: - - string -")); - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, result.ResultCode.HResult); - Assert.Equal("resources[0]", result.Field); - Assert.Equal(5U, result.Line); - Assert.NotEqual(0U, result.Column); - } - - /// - /// Passes YAML with all values present. - /// - [Fact] - public void CheckAllUnitProperties() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.1 - resources: - - resource: Resource - id: Identifier - dependsOn: - - Dependency1 - - Dependency2 - directives: - Directive1: A - Directive2: B - settings: - Setting1: '1' - Setting2: 2 -")); - Assert.NotNull(result.Set); - Assert.Null(result.ResultCode); - Assert.Equal(string.Empty, result.Field); - - Assert.NotEqual(Guid.Empty, result.Set.InstanceIdentifier); - - var units = result.Set.Units; - Assert.NotNull(units); - Assert.Equal(1, units.Count); - - ConfigurationUnit unit = units[0]; - Assert.NotNull(unit); - Assert.Equal("Resource", unit.Type); - Assert.NotEqual(Guid.Empty, unit.InstanceIdentifier); - Assert.Equal("Identifier", unit.Identifier); - Assert.Equal(ConfigurationUnitIntent.Apply, unit.Intent); - - var dependencies = unit.Dependencies; - Assert.NotNull(dependencies); - Assert.Equal(2, dependencies.Count); - Assert.Contains("Dependency1", dependencies); - Assert.Contains("Dependency2", dependencies); - - var directives = unit.Metadata; - Assert.NotNull(directives); - Assert.Equal(2, directives.Count); - Assert.Contains("Directive1", directives); - Assert.Equal("A", directives["Directive1"]); - Assert.Contains("Directive2", directives); - Assert.Equal("B", directives["Directive2"]); - - var settings = unit.Settings; - Assert.NotNull(settings); - Assert.Equal(2, settings.Count); - Assert.Contains("Setting1", settings); - Assert.Equal("1", settings["Setting1"]); - Assert.Contains("Setting2", settings); - Assert.Equal(2L, settings["Setting2"]); - - Assert.Null(unit.Details); - Assert.Equal(ConfigurationUnitState.Unknown, unit.State); - Assert.Null(unit.ResultInformation); - Assert.True(unit.IsActive); - } - - /// - /// Test type of scalar nodes. - /// - [Fact] - public void CheckUnitScalarTypes() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.1 - resources: - - resource: Resource - id: Identifier - settings: - SettingInt: 1 - SettingString: '1' - SettingBool: false - SettingStringBool: 'false' -")); - Assert.NotNull(result.Set); - Assert.Null(result.ResultCode); - - var units = result.Set.Units; - Assert.NotNull(units); - Assert.Equal(1, units.Count); - - ConfigurationUnit unit = units[0]; - Assert.NotNull(unit); - - var settings = unit.Settings; - Assert.NotNull(settings); - Assert.Equal(4, settings.Count); - Assert.Contains("SettingInt", settings); - Assert.Equal(1L, settings["SettingInt"]); - Assert.Contains("SettingString", settings); - Assert.Equal("1", settings["SettingString"]); - Assert.Contains("SettingBool", settings); - Assert.Equal(false, settings["SettingBool"]); - Assert.Contains("SettingStringBool", settings); - Assert.Equal("false", settings["SettingStringBool"]); - } - - /// - /// Test that module gets left in resource name in 0.1. - /// - [Fact] - public void ModuleInResourceName_NotFor0_1() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.1 - resources: - - resource: Module/Resource - id: Identifier - settings: - SettingInt: 1 -")); - - Assert.NotNull(result.Set); - Assert.Null(result.ResultCode); - - Assert.Equal("0.1", result.Set.SchemaVersion); - Assert.Single(result.Set.Units); - - var unit = result.Set.Units[0]; - Assert.NotNull(unit); - Assert.Equal("Module/Resource", unit.Type); - Assert.Empty(unit.Metadata); - } - - /// - /// Test that module gets parsed out of resource name in 0.2. - /// - [Fact] - public void ModuleInResourceName() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.2 - resources: - - resource: Module/Resource - id: Identifier - directives: - module: Module - settings: - SettingInt: 1 -")); - - Assert.NotNull(result.Set); - Assert.Null(result.ResultCode); - - Assert.Equal("0.2", result.Set.SchemaVersion); - Assert.Single(result.Set.Units); - - var unit = result.Set.Units[0]; - Assert.NotNull(unit); - Assert.Equal("Resource", unit.Type); - Assert.Single(unit.Metadata); - Assert.True(unit.Metadata.ContainsKey(ModuleDirective)); - Assert.Equal("Module", unit.Metadata[ModuleDirective]); - } - - /// - /// Test that module is in the resource name and the directives and are different. - /// - [Fact] - public void ModuleInResourceName_DirectiveDifferent() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.2 - resources: - - resource: Module/Resource - id: Identifier - directives: - module: DifferentModule - settings: - SettingInt: 1 -")); - - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); - Assert.Equal(ModuleDirective, result.Field); - Assert.Equal("DifferentModule", result.Value); - Assert.Equal(5U, result.Line); - Assert.NotEqual(0U, result.Column); - } - - /// - /// Test that providing only the module in the qualified name is an error. - /// - [Fact] - public void EmptyResourceWithModule() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.2 - resources: - - resource: Module/ - id: Identifier - settings: - SettingInt: 1 -")); - - Assert.Null(result.Set); - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); - Assert.Equal("resource", result.Field); - Assert.Equal("Module/", result.Value); - Assert.Equal(5U, result.Line); - Assert.NotEqual(0U, result.Column); - } - - /// - /// Verifies that the configuration set (0.2) can be serialized and reopened correctly. - /// - [Fact] - public void TestSet_Serialize_0_2() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.2 - assertions: - - resource: FakeModule/FakeResource - id: TestId - directives: - description: FakeDescription - allowPrerelease: true - securityContext: elevated - settings: - TestString: Hello - TestBool: false - TestInt: 1234 - resources: - - resource: FakeModule2/FakeResource2 - id: TestId2 - dependsOn: - - TestId - - dependency2 - - dependency3 - directives: - description: FakeDescription2 - securityContext: elevated - settings: - TestString: Bye - TestBool: true - TestInt: 4321 - Mapping: - Key: TestValue -")); - - // Serialize set. - ConfigurationSet configurationSet = openResult.Set; - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - configurationSet.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - Assert.Null(serializedSetResult.ResultCode); - ConfigurationSet set = serializedSetResult.Set; - Assert.NotNull(set); - - Assert.Equal("0.2", set.SchemaVersion); - Assert.Equal(2, set.Units.Count); - - Assert.Equal("FakeResource", set.Units[0].Type); - Assert.Equal(ConfigurationUnitIntent.Assert, set.Units[0].Intent); - Assert.Equal("TestId", set.Units[0].Identifier); - this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("module", "FakeModule")); - this.VerifyValueSet(set.Units[0].Settings, new ("TestString", "Hello"), new ("TestBool", false), new ("TestInt", 1234)); - - Assert.Equal("FakeResource2", set.Units[1].Type); - Assert.Equal(ConfigurationUnitIntent.Apply, set.Units[1].Intent); - 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 ("module", "FakeModule2")); - - 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)); - } - - /// - /// Verifies that the configuration set (0.3) can be serialized and reopened correctly. - /// - [Fact] - public void TestSet_Serialize_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - description: FakeSetDescription -variables: - var1: Test1 - var2: 42 -parameters: - param1: - type: securestring - param2: - type: int - defaultValue: 89 -resources: - - type: FakeModule/FakeResource - name: TestId - metadata: - description: FakeDescription - allowPrerelease: true - myVal: mine - properties: - TestString: Hello - TestBool: false - TestInt: 1234 - - type: FakeModule2/FakeResource2 - name: TestId2 - dependsOn: - - TestId - - dependency2 - - dependency3 - metadata: - description: FakeDescription2 - myVal: yours - properties: - TestString: Bye - TestBool: true - TestInt: 4321 - Mapping: - Key: TestValue - - type: FakeModule/FakeResource3 - name: TestId3 - metadata: - isGroup: true - properties: - other: value - resources: - - type: Grouped/Resource - name: Child - properties: - b: c -")); - - // Serialize set. - ConfigurationSet configurationSet = openResult.Set; - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - configurationSet.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - Assert.Null(serializedSetResult.ResultCode); - ConfigurationSet set = serializedSetResult.Set; - Assert.NotNull(set); - - Assert.Equal("0.3", set.SchemaVersion); - Assert.Equal(3, set.Units.Count); - - this.VerifyValueSet(set.Metadata, new KeyValuePair("description", "FakeSetDescription")); - 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); - this.VerifyParameter(set.Parameters[1], "param2", Windows.Foundation.PropertyType.Int64, false, 89); - - 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 ("myVal", "mine")); - 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 ("myVal", "yours")); - - 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)); - - Assert.Equal("FakeModule/FakeResource3", set.Units[2].Type); - Assert.Equal("TestId3", set.Units[2].Identifier); - Assert.True(set.Units[2].IsGroup); - - ValueSet childResource = new ValueSet(); - childResource.Add("type", "Grouped/Resource"); - childResource.Add("name", "Child"); - ValueSet childResourceProperties = new ValueSet(); - childResourceProperties.Add("b", "c"); - childResource.Add("properties", childResourceProperties); - - ValueSet resourcesArray = new ValueSet(); - resourcesArray.Add("treatAsArray", true); - resourcesArray.Add("0", childResource); - - this.VerifyValueSet(set.Units[2].Settings, new ("other", "value"), new ("resources", resourcesArray)); - - var groupChildren = set.Units[2].Units; - Assert.Single(groupChildren); - - Assert.Equal("Grouped/Resource", groupChildren[0].Type); - Assert.Equal("Child", groupChildren[0].Identifier); - this.VerifyValueSet(groupChildren[0].Settings, new KeyValuePair("b", "c")); - } - - /// - /// Test for using version 0.3 schema. - /// - [Fact] - public void BasicVersion_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - a: 1 - b: '2' -variables: - v1: var1 - v2: 42 -resources: - - name: Name - type: Module/Resource - metadata: - e: '5' - f: 6 - properties: - c: 3 - d: '4' - dependsOn: - - g - - h - - name: Name2 - type: Module/Resource2 - dependsOn: - - m - properties: - l: '10' - metadata: - i: '7' - j: 8 - q: 42 -")); - - Assert.Null(result.ResultCode); - Assert.NotNull(result.Set); - Assert.Equal(string.Empty, result.Field); - Assert.Equal(string.Empty, result.Value); - Assert.Equal(0U, result.Line); - Assert.Equal(0U, result.Column); - - ConfigurationSet set = result.Set; - - Assert.Equal("0.3", set.SchemaVersion); - Assert.NotNull(set.SchemaUri); - Assert.Equal("https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json", set.SchemaUri.ToString()); - - this.VerifyValueSet(set.Metadata, new ("a", 1), new ("b", "2")); - this.VerifyValueSet(set.Variables, new ("v1", "var1"), new ("v2", 42)); - - Assert.Empty(set.Parameters); - - Assert.Equal(2, set.Units.Count); - - this.VerifyUnitProperties(set.Units[0], "Name", "Module/Resource"); - this.VerifyValueSet(set.Units[0].Metadata, new ("e", "5"), new ("f", 6)); - this.VerifyValueSet(set.Units[0].Settings, new ("c", 3), new ("d", "4")); - this.VerifyStringArray(set.Units[0].Dependencies, "g", "h"); - - this.VerifyUnitProperties(set.Units[1], "Name2", "Module/Resource2"); - this.VerifyValueSet(set.Units[1].Metadata, new ("i", "7"), new ("j", 8), new ("q", 42)); - this.VerifyValueSet(set.Units[1].Settings, new KeyValuePair("l", "10")); - this.VerifyStringArray(set.Units[1].Dependencies, "m"); - } - - /// - /// Test for the successful parsing of default value of a parameter. - /// - /// The type. - /// The default value. - /// The expected value. - /// The expected type. - /// The secure state. - [Theory] - [InlineData("string", "abc", "abc", Windows.Foundation.PropertyType.String)] - [InlineData("string", "'42'", "42", Windows.Foundation.PropertyType.String)] - [InlineData("securestring", "abcdef", "abcdef", Windows.Foundation.PropertyType.String, true)] - [InlineData("int", "42", 42, Windows.Foundation.PropertyType.Int64)] - [InlineData("bool", "true", true, Windows.Foundation.PropertyType.Boolean)] - [InlineData("object", "string", "string", Windows.Foundation.PropertyType.Inspectable)] - [InlineData("object", "42", 42, Windows.Foundation.PropertyType.Inspectable)] - [InlineData("secureobject", "string", "string", Windows.Foundation.PropertyType.Inspectable, true)] - [InlineData("secureobject", "42", 42, Windows.Foundation.PropertyType.Inspectable, true)] - public void Parameters_DefaultValue_Success(string type, string defaultValue, object expectedValue, object expectedType, bool secure = false) - { - this.TestParameterDefaultValue(type, defaultValue, expectedValue, Assert.IsType(expectedType), secure); - } - - /// - /// Test for the failed parsing of default value of a parameter. - /// - /// The type. - /// The default value. - /// The expected value. - [Theory] - [InlineData("string", "42")] - [InlineData("int", "abc")] - [InlineData("int", "'42'", "42")] - [InlineData("bool", "'true'", "true")] - public void Parameters_DefaultValue_Failure(string type, string defaultValue, object? expectedValue = null) - { - this.TestParameterDefaultValue(type, defaultValue, expectedValue); - } - - /// - /// Test to ensure that schema version and uri is working as expected. - /// - /// The version. - /// The uri. - [Theory] - [InlineData("0.1", null)] - [InlineData("0.2", null)] - [InlineData("0.3", "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json")] - public void Schema_Version_Uri(string version, string? uri) - { - ConfigurationSet set = this.ConfigurationSet(); - - set.SchemaVersion = version; - if (uri != null) - { - Assert.Equal(uri, set.SchemaUri.AbsoluteUri); - } - else - { - Assert.Null(set.SchemaUri); - } - - if (!string.IsNullOrEmpty(uri)) - { - set.SchemaUri = new Uri(uri); - Assert.Equal(version, set.SchemaVersion); - } - } - - /// - /// Verifies that the configuration set (0.2) with environments parses and serializes. - /// - [Fact] - public void Environment_0_2() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -properties: - configurationVersion: 0.2 - resources: - - resource: FakeModule/FakeResource - id: elevated - directives: - description: FakeDescription - allowPrerelease: true - securityContext: elevated - settings: - TestString: Hello - - resource: FakeModule2/FakeResource2 - id: restricted - directives: - description: FakeDescription2 - securityContext: restricted - settings: - TestString: Bye - - resource: FakeModule2/FakeResource2 - id: current - directives: - securityContext: current - settings: - TestString: Bye - - resource: FakeModule2/FakeResource2 - id: default - settings: - TestString: Bye -")); - - Dictionary expectedEnvironments = new Dictionary(); - expectedEnvironments.Add("elevated", SecurityContext.Elevated); - expectedEnvironments.Add("restricted", SecurityContext.Restricted); - expectedEnvironments.Add("current", SecurityContext.Current); - expectedEnvironments.Add("default", SecurityContext.Current); - - this.ValidateSecurityContexts(openResult, expectedEnvironments); - - // Shuffle security contexts, serialize, parse and validate again - expectedEnvironments["elevated"] = SecurityContext.Restricted; - expectedEnvironments["restricted"] = SecurityContext.Current; - expectedEnvironments["current"] = SecurityContext.Restricted; - expectedEnvironments["default"] = SecurityContext.Elevated; - - var units = openResult.Set.Units; - foreach (var unit in units) - { - SecurityContext newContext = SecurityContext.Current; - Assert.True(expectedEnvironments.TryGetValue(unit.Identifier, out newContext)); - unit.Environment.Context = newContext; - } - - // Serialize set. - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - openResult.Set.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - - this.ValidateSecurityContexts(serializedSetResult, expectedEnvironments); - } - - /// - /// Verifies that the configuration set (0.3) inherits set environment. - /// - [Fact] - public void SetMetadataEnvironmentInheritance_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - securityContext: elevated - processor: - identifier: pwsh - properties: - a: b -resources: - - name: first - type: Module/Resource - properties: - c: 3 - - name: second - type: Module/Resource2 - properties: - l: '10' -")); - - Dictionary environmentProperties = new Dictionary(); - environmentProperties.Add("a", "b"); - ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; - - Dictionary expectedEnvironments = new Dictionary(); - expectedEnvironments.Add("first", new ConfigurationEnvironmentData()); - expectedEnvironments.Add("second", new ConfigurationEnvironmentData()); - - this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); - - // Serialize set. - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - openResult.Set.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - - this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); - } - - /// - /// Verifies that the configuration set (0.3) inherits set environment. - /// - [Fact] - public void SetMetadataEnvironmentInheritance_ProcessorOverridden_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - securityContext: elevated - processor: - identifier: pwsh - properties: - a: b -resources: - - name: first - type: Module/Resource - properties: - c: 3 - - name: second - type: Module/Resource2 - properties: - l: '10' - metadata: - winget: - processor: not-pwsh -")); - - Dictionary environmentProperties = new Dictionary(); - environmentProperties.Add("a", "b"); - ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; - - Dictionary expectedEnvironments = new Dictionary(); - expectedEnvironments.Add("first", new ConfigurationEnvironmentData()); - expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { ProcessorIdentifier = "not-pwsh" }); - - this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); - - // Serialize set. - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - openResult.Set.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - - this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); - } - - /// - /// Verifies that the configuration set (0.3) inherits set environment. - /// - [Fact] - public void SetMetadataEnvironmentInheritance_ContextOverridden_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - securityContext: elevated - processor: - identifier: pwsh - properties: - a: b -resources: - - name: first - type: Module/Resource - properties: - c: 3 - - name: second - type: Module/Resource2 - properties: - l: '10' - metadata: - winget: - securityContext: restricted -")); - - Dictionary environmentProperties = new Dictionary(); - environmentProperties.Add("a", "b"); - ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; - - Dictionary expectedEnvironments = new Dictionary(); - expectedEnvironments.Add("first", new ConfigurationEnvironmentData()); - expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted }); - - this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); - - // Serialize set. - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - openResult.Set.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - - this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); - } - - /// - /// Verifies that the configuration set (0.3) serializes common environment to the set metadata. - /// - [Fact] - public void CommonEnvironmentElevatedToSetMetadata_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -resources: - - name: first - type: Module/Resource - metadata: - winget: - securityContext: elevated - processor: pwsh - properties: - c: 3 - - name: second - type: Module/Resource2 - properties: - l: '10' - metadata: - winget: - securityContext: elevated - processor: - identifier: pwsh -")); - - ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData(); - - Dictionary expectedEnvironments = new Dictionary(); - expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); - expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); - - this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); - - // Serialize set. - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - openResult.Set.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - - this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); - } - - /// - /// Verifies that the configuration set (0.3) serializes common environment to the set metadata. - /// - [Fact] - public void CommonProcessorElevatedToSetMetadata_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -resources: - - name: first - type: Module/Resource - metadata: - winget: - securityContext: elevated - processor: pwsh - properties: - c: 3 - - name: second - type: Module/Resource2 - properties: - l: '10' - metadata: - winget: - securityContext: restricted - processor: - identifier: pwsh -")); - - ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData(); - - Dictionary expectedEnvironments = new Dictionary(); - expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); - expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted, ProcessorIdentifier = "pwsh" }); - - this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); - - // Serialize set. - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - openResult.Set.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - - this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); - } - - /// - /// Verifies that the configuration set (0.3) environments work with group units. - /// - [Fact] - public void EnvironmentsWithGroups_0_3() - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -metadata: - winget: - securityContext: elevated - processor: - identifier: pwsh - properties: - a: b -resources: - - name: non-group - type: Module/Resource2 - properties: - l: '10' - - name: group - type: Module/Resource - metadata: - isGroup: true - winget: - securityContext: restricted - properties: - resources: - - name: inherit - type: Module/Resource - properties: - a: b - - name: override - type: Module/Resource - properties: - c: d - metadata: - winget: - processor: not-pwsh -")); - - Dictionary environmentProperties = new Dictionary(); - environmentProperties.Add("a", "b"); - ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; - - Dictionary expectedEnvironments = new Dictionary(); - expectedEnvironments.Add("non-group", new ConfigurationEnvironmentData()); - expectedEnvironments.Add("group", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted }); - - Dictionary groupExpectedEnvironments = new Dictionary(); - groupExpectedEnvironments.Add("inherit", new ConfigurationEnvironmentData()); - groupExpectedEnvironments.Add("override", new ConfigurationEnvironmentData() { ProcessorIdentifier = "not-pwsh" }); - - this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments, "group", groupExpectedEnvironments); - - // Serialize set. - InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); - openResult.Set.Serialize(stream); - - string yamlOutput = this.ReadStream(stream); - - // Reopen configuration set from serialized string and verify values. - OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); - - this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments, "group", groupExpectedEnvironments); - } - - private void ValidateEnvironments(OpenConfigurationSetResult openResult, ConfigurationEnvironmentData setEnvironment, Dictionary expectedEnvironments, string? groupToCheck = null, Dictionary? groupExpectedEnvironments = null) - { - Assert.Null(openResult.ResultCode); - Assert.NotNull(openResult.Set); - ConfigurationSet configurationSet = openResult.Set; - - this.ValidateEnvironment(setEnvironment, configurationSet.Environment); - - var units = configurationSet.Units; - this.ValidateEnvironments(units, expectedEnvironments, groupToCheck, groupExpectedEnvironments); - } - - private void ValidateEnvironments(IList units, Dictionary expectedEnvironments, string? groupToCheck = null, Dictionary? groupExpectedEnvironments = null) - { - Assert.Equal(expectedEnvironments.Count, units.Count); - foreach (var unit in units) - { - ConfigurationEnvironmentData? expectedEnvironment = null; - Assert.True(expectedEnvironments.TryGetValue(unit.Identifier, out expectedEnvironment)); - this.ValidateEnvironment(expectedEnvironment, unit.Environment); - - if (unit.Identifier == groupToCheck) - { - Assert.True(unit.IsGroup); - Assert.NotNull(groupExpectedEnvironments); - var groupUnits = unit.Units; - this.ValidateEnvironments(groupUnits, groupExpectedEnvironments); - } - } - } - - private void ValidateEnvironment(ConfigurationEnvironmentData? expectedEnvironment, ConfigurationEnvironment? actualEnvironment) - { - Assert.NotNull(expectedEnvironment); - Assert.NotNull(actualEnvironment); - Assert.Equal(expectedEnvironment.Context, actualEnvironment.Context); - Assert.Equal(expectedEnvironment.ProcessorIdentifier, actualEnvironment.ProcessorIdentifier); - Assert.True(expectedEnvironment.PropertiesEqual(actualEnvironment.ProcessorProperties)); - } - - private void ValidateSecurityContexts(OpenConfigurationSetResult openResult, Dictionary expectedContexts) - { - Assert.Null(openResult.ResultCode); - Assert.NotNull(openResult.Set); - ConfigurationSet configurationSet = openResult.Set; - - var units = configurationSet.Units; - Assert.Equal(expectedContexts.Count, units.Count); - foreach (var unit in units) - { - SecurityContext expectedContext = SecurityContext.Current; - Assert.True(expectedContexts.TryGetValue(unit.Identifier, out expectedContext)); - Assert.Equal(expectedContext, unit.Environment.Context); - } - } - - private void TestParameterDefaultValue(string type, string defaultValue, object? expectedValue = null, Windows.Foundation.PropertyType? expectedType = null, bool secure = false) - { - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); - - OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(string.Format( - @" -$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json -parameters: - {0}: - type: {0} - defaultValue: {1} -", - type, - defaultValue))); - - if (expectedType != null) - { - Assert.Null(result.ResultCode); - Assert.NotNull(result.Set); - Assert.Equal(string.Empty, result.Field); - Assert.Equal(string.Empty, result.Value); - Assert.Equal(0U, result.Line); - Assert.Equal(0U, result.Column); - - var parameters = result.Set.Parameters; - Assert.NotNull(parameters); - Assert.Single(parameters); - - Assert.Equal(type, parameters[0].Name); - Assert.Equal(expectedType, parameters[0].Type); - Assert.Equal(secure, parameters[0].IsSecure); - - Assert.NotNull(expectedValue); - this.VerifyObject(type, expectedValue, parameters[0].DefaultValue); - } - else - { - Assert.NotNull(result.ResultCode); - Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); - Assert.Null(result.Set); - Assert.Equal("defaultValue", result.Field); - Assert.Equal(expectedValue?.ToString() ?? defaultValue, result.Value); - Assert.NotEqual(0U, result.Line); - Assert.NotEqual(0U, result.Column); - } - } - - private void VerifyUnitProperties(ConfigurationUnit unit, string identifier, string type) - { - Assert.NotNull(unit); - Assert.Equal(identifier, unit.Identifier); - Assert.Equal(type, unit.Type); - } - - private void VerifyValueSet(ValueSet values, params KeyValuePair[] expected) - { - Assert.NotNull(values); - Assert.Equal(expected.Length, values.Count); - - foreach (var expectation in expected) - { - Assert.True(values.ContainsKey(expectation.Key), $"Not Found {expectation.Key}"); - object value = values[expectation.Key]; - - this.VerifyObject(expectation.Key, expectation.Value, value); - } - } - - private void VerifyStringArray(IList strings, params string[] expected) - { - Assert.NotNull(strings); - Assert.Equal(expected.Length, strings.Count); - - foreach (var expectation in expected) - { - bool found = false; - foreach (var value in strings) - { - if (!found) - { - found = expectation == value; - } - } - - Assert.True(found, $"Did not find {expectation} in string array"); - } - } - - private void VerifyParameter(ConfigurationParameter parameter, string name, Windows.Foundation.PropertyType type, bool secure, object? defaultValue = null) - { - Assert.Equal(name, parameter.Name); - Assert.Equal(type, parameter.Type); - Assert.Equal(secure, parameter.IsSecure); - this.VerifyObject(name, defaultValue, parameter.DefaultValue); - } - - private void VerifyObject(string name, object? expectedValue, object? actualValue) - { - if (expectedValue != null) - { - Assert.NotNull(actualValue); - - switch (expectedValue) - { - case int i: - Assert.True(i == (int)(long)actualValue, $"{name}: expected[{i}], actual[{(int)(long)actualValue}]"); - break; - case string s: - Assert.True(s == (string)actualValue, $"{name}: expected[{s}], actual[{(string)actualValue}]"); - break; - case bool b: - Assert.True(b == (bool)actualValue, $"{name}: expected[{b}], actual[{(bool)actualValue}]"); - break; - case ValueSet v: - var actualValueSet = actualValue.As(); - Assert.True(v.ContentEquals(actualValueSet), $"ValueSets not equal: {name}\n---expected---:\n{v.ToYaml()}\n---actual---:\n{actualValueSet.ToYaml()}"); - break; - default: - Assert.Fail($"Add expected type `{expectedValue.GetType().Name}` to switch statement for {name}."); - break; - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections.Generic; + using System.DirectoryServices; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Microsoft.VisualBasic; + using Windows.Foundation.Collections; + using Windows.Storage.Streams; + using WinRT; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for parsing configuration sets from streams. + /// + [Collection("UnitTestCollection")] + [InProc] + [OutOfProc] + public class OpenConfigurationSetTests : ConfigurationProcessorTestBase + { + /// + /// The directives key for the module property. + /// + internal const string ModuleDirective = "module"; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public OpenConfigurationSetTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// Passes a null stream as input. + /// + [Fact] + public void NullStream() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(null); + Assert.Null(result.Set); + Assert.IsType(result.ResultCode); + Assert.Equal(string.Empty, result.Field); + } + + /// + /// Passes an empty stream as input. + /// + [Fact] + public void EmptyStream() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(string.Empty)); + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_YAML, result.ResultCode.HResult); + Assert.Equal(string.Empty, result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); + } + + /// + /// Passes a stream with a single null byte in it. + /// + [Fact] + public void NullByteStream() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream("\0")); + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_YAML, result.ResultCode.HResult); + Assert.NotEqual(string.Empty, result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); + } + + /// + /// Passes YAML, but it isn't anything like a configuration file. + /// + [Fact] + public void NotConfigYAML() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream("yaml: yep")); + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_FIELD, result.ResultCode.HResult); + Assert.Equal("$schema", result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); + } + + /// + /// Passes YAML without a schema version. + /// + [Fact] + public void NoConfigVersion() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + thing: 1 +")); + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_MISSING_FIELD, result.ResultCode.HResult); + Assert.Equal("configurationVersion", result.Field); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); + } + + /// + /// Passes YAML that appears to be from the distant future. + /// + [Fact] + public void UnknownConfigVersion() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 99999999 +")); + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, result.ResultCode.HResult); + Assert.Equal("configurationVersion", result.Field); + Assert.Equal("99999999", result.Value); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); + } + + /// + /// Has one of each type of intent to ensure that it is set properly. + /// + [Fact] + public void EnsureIntent() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.1 + assertions: + - resource: Assert + parameters: + - resource: Inform + resources: + - resource: Apply +")); + + Assert.NotNull(result.Set); + Assert.Null(result.ResultCode); + Assert.Equal(string.Empty, result.Field); + + var units = result.Set.Units; + Assert.Equal(3, units.Count); + bool sawAssert = false; + bool sawInform = false; + bool sawApply = false; + + foreach (var unit in units) + { + Assert.Equal(unit.Type, unit.Intent.ToString()); + switch (unit.Intent) + { + case ConfigurationUnitIntent.Assert: sawAssert = true; break; + case ConfigurationUnitIntent.Inform: sawInform = true; break; + case ConfigurationUnitIntent.Apply: sawApply = true; break; + default: Assert.Fail("Unknown intent"); break; + } + } + + Assert.True(sawAssert); + Assert.True(sawInform); + Assert.True(sawApply); + } + + /// + /// Passes YAML with resources being something other than a sequence. + /// + [Fact] + public void NonSequenceUnits() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.1 + resources: 1 +")); + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, result.ResultCode.HResult); + Assert.Equal("resources", result.Field); + Assert.Equal(4U, result.Line); + Assert.NotEqual(0U, result.Column); + } + + /// + /// Passes YAML with a resource being something other than a map. + /// + [Fact] + public void NonMapSequenceUnits() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.1 + resources: + - string +")); + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, result.ResultCode.HResult); + Assert.Equal("resources[0]", result.Field); + Assert.Equal(5U, result.Line); + Assert.NotEqual(0U, result.Column); + } + + /// + /// Passes YAML with all values present. + /// + [Fact] + public void CheckAllUnitProperties() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.1 + resources: + - resource: Resource + id: Identifier + dependsOn: + - Dependency1 + - Dependency2 + directives: + Directive1: A + Directive2: B + settings: + Setting1: '1' + Setting2: 2 +")); + Assert.NotNull(result.Set); + Assert.Null(result.ResultCode); + Assert.Equal(string.Empty, result.Field); + + Assert.NotEqual(Guid.Empty, result.Set.InstanceIdentifier); + + var units = result.Set.Units; + Assert.NotNull(units); + Assert.Equal(1, units.Count); + + ConfigurationUnit unit = units[0]; + Assert.NotNull(unit); + Assert.Equal("Resource", unit.Type); + Assert.NotEqual(Guid.Empty, unit.InstanceIdentifier); + Assert.Equal("Identifier", unit.Identifier); + Assert.Equal(ConfigurationUnitIntent.Apply, unit.Intent); + + var dependencies = unit.Dependencies; + Assert.NotNull(dependencies); + Assert.Equal(2, dependencies.Count); + Assert.Contains("Dependency1", dependencies); + Assert.Contains("Dependency2", dependencies); + + var directives = unit.Metadata; + Assert.NotNull(directives); + Assert.Equal(2, directives.Count); + Assert.Contains("Directive1", directives); + Assert.Equal("A", directives["Directive1"]); + Assert.Contains("Directive2", directives); + Assert.Equal("B", directives["Directive2"]); + + var settings = unit.Settings; + Assert.NotNull(settings); + Assert.Equal(2, settings.Count); + Assert.Contains("Setting1", settings); + Assert.Equal("1", settings["Setting1"]); + Assert.Contains("Setting2", settings); + Assert.Equal(2L, settings["Setting2"]); + + Assert.Null(unit.Details); + Assert.Equal(ConfigurationUnitState.Unknown, unit.State); + Assert.Null(unit.ResultInformation); + Assert.True(unit.IsActive); + } + + /// + /// Test type of scalar nodes. + /// + [Fact] + public void CheckUnitScalarTypes() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.1 + resources: + - resource: Resource + id: Identifier + settings: + SettingInt: 1 + SettingString: '1' + SettingBool: false + SettingStringBool: 'false' +")); + Assert.NotNull(result.Set); + Assert.Null(result.ResultCode); + + var units = result.Set.Units; + Assert.NotNull(units); + Assert.Equal(1, units.Count); + + ConfigurationUnit unit = units[0]; + Assert.NotNull(unit); + + var settings = unit.Settings; + Assert.NotNull(settings); + Assert.Equal(4, settings.Count); + Assert.Contains("SettingInt", settings); + Assert.Equal(1L, settings["SettingInt"]); + Assert.Contains("SettingString", settings); + Assert.Equal("1", settings["SettingString"]); + Assert.Contains("SettingBool", settings); + Assert.Equal(false, settings["SettingBool"]); + Assert.Contains("SettingStringBool", settings); + Assert.Equal("false", settings["SettingStringBool"]); + } + + /// + /// Test that module gets left in resource name in 0.1. + /// + [Fact] + public void ModuleInResourceName_NotFor0_1() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.1 + resources: + - resource: Module/Resource + id: Identifier + settings: + SettingInt: 1 +")); + + Assert.NotNull(result.Set); + Assert.Null(result.ResultCode); + + Assert.Equal("0.1", result.Set.SchemaVersion); + Assert.Single(result.Set.Units); + + var unit = result.Set.Units[0]; + Assert.NotNull(unit); + Assert.Equal("Module/Resource", unit.Type); + Assert.Empty(unit.Metadata); + } + + /// + /// Test that module gets parsed out of resource name in 0.2. + /// + [Fact] + public void ModuleInResourceName() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: Module/Resource + id: Identifier + directives: + module: Module + settings: + SettingInt: 1 +")); + + Assert.NotNull(result.Set); + Assert.Null(result.ResultCode); + + Assert.Equal("0.2", result.Set.SchemaVersion); + Assert.Single(result.Set.Units); + + var unit = result.Set.Units[0]; + Assert.NotNull(unit); + Assert.Equal("Resource", unit.Type); + Assert.Single(unit.Metadata); + Assert.True(unit.Metadata.ContainsKey(ModuleDirective)); + Assert.Equal("Module", unit.Metadata[ModuleDirective]); + } + + /// + /// Test that module is in the resource name and the directives and are different. + /// + [Fact] + public void ModuleInResourceName_DirectiveDifferent() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: Module/Resource + id: Identifier + directives: + module: DifferentModule + settings: + SettingInt: 1 +")); + + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); + Assert.Equal(ModuleDirective, result.Field); + Assert.Equal("DifferentModule", result.Value); + Assert.Equal(5U, result.Line); + Assert.NotEqual(0U, result.Column); + } + + /// + /// Test that providing only the module in the qualified name is an error. + /// + [Fact] + public void EmptyResourceWithModule() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: Module/ + id: Identifier + settings: + SettingInt: 1 +")); + + Assert.Null(result.Set); + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); + Assert.Equal("resource", result.Field); + Assert.Equal("Module/", result.Value); + Assert.Equal(5U, result.Line); + Assert.NotEqual(0U, result.Column); + } + + /// + /// Verifies that the configuration set (0.2) can be serialized and reopened correctly. + /// + [Fact] + public void TestSet_Serialize_0_2() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + assertions: + - resource: FakeModule/FakeResource + id: TestId + directives: + description: FakeDescription + allowPrerelease: true + securityContext: elevated + settings: + TestString: Hello + TestBool: false + TestInt: 1234 + resources: + - resource: FakeModule2/FakeResource2 + id: TestId2 + dependsOn: + - TestId + - dependency2 + - dependency3 + directives: + description: FakeDescription2 + securityContext: elevated + settings: + TestString: Bye + TestBool: true + TestInt: 4321 + Mapping: + Key: TestValue +")); + + // Serialize set. + ConfigurationSet configurationSet = openResult.Set; + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + configurationSet.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + Assert.Null(serializedSetResult.ResultCode); + ConfigurationSet set = serializedSetResult.Set; + Assert.NotNull(set); + + Assert.Equal("0.2", set.SchemaVersion); + Assert.Equal(2, set.Units.Count); + + Assert.Equal("FakeResource", set.Units[0].Type); + Assert.Equal(ConfigurationUnitIntent.Assert, set.Units[0].Intent); + Assert.Equal("TestId", set.Units[0].Identifier); + this.VerifyValueSet(set.Units[0].Metadata, new ("description", "FakeDescription"), new ("allowPrerelease", true), new ("module", "FakeModule")); + this.VerifyValueSet(set.Units[0].Settings, new ("TestString", "Hello"), new ("TestBool", false), new ("TestInt", 1234)); + + Assert.Equal("FakeResource2", set.Units[1].Type); + Assert.Equal(ConfigurationUnitIntent.Apply, set.Units[1].Intent); + 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 ("module", "FakeModule2")); + + 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)); + } + + /// + /// Verifies that the configuration set (0.3) can be serialized and reopened correctly. + /// + [Fact] + public void TestSet_Serialize_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + description: FakeSetDescription +variables: + var1: Test1 + var2: 42 +parameters: + param1: + type: securestring + param2: + type: int + defaultValue: 89 +resources: + - type: FakeModule/FakeResource + name: TestId + metadata: + description: FakeDescription + allowPrerelease: true + myVal: mine + properties: + TestString: Hello + TestBool: false + TestInt: 1234 + - type: FakeModule2/FakeResource2 + name: TestId2 + dependsOn: + - TestId + - dependency2 + - dependency3 + metadata: + description: FakeDescription2 + myVal: yours + properties: + TestString: Bye + TestBool: true + TestInt: 4321 + Mapping: + Key: TestValue + - type: FakeModule/FakeResource3 + name: TestId3 + metadata: + isGroup: true + properties: + other: value + resources: + - type: Grouped/Resource + name: Child + properties: + b: c +")); + + // Serialize set. + ConfigurationSet configurationSet = openResult.Set; + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + configurationSet.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + Assert.Null(serializedSetResult.ResultCode); + ConfigurationSet set = serializedSetResult.Set; + Assert.NotNull(set); + + Assert.Equal("0.3", set.SchemaVersion); + Assert.Equal(3, set.Units.Count); + + this.VerifyValueSet(set.Metadata, new KeyValuePair("description", "FakeSetDescription")); + 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); + this.VerifyParameter(set.Parameters[1], "param2", Windows.Foundation.PropertyType.Int64, false, 89); + + 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 ("myVal", "mine")); + 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 ("myVal", "yours")); + + 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)); + + Assert.Equal("FakeModule/FakeResource3", set.Units[2].Type); + Assert.Equal("TestId3", set.Units[2].Identifier); + Assert.True(set.Units[2].IsGroup); + + ValueSet childResource = new ValueSet(); + childResource.Add("type", "Grouped/Resource"); + childResource.Add("name", "Child"); + ValueSet childResourceProperties = new ValueSet(); + childResourceProperties.Add("b", "c"); + childResource.Add("properties", childResourceProperties); + + ValueSet resourcesArray = new ValueSet(); + resourcesArray.Add("treatAsArray", true); + resourcesArray.Add("0", childResource); + + this.VerifyValueSet(set.Units[2].Settings, new ("other", "value"), new ("resources", resourcesArray)); + + var groupChildren = set.Units[2].Units; + Assert.Single(groupChildren); + + Assert.Equal("Grouped/Resource", groupChildren[0].Type); + Assert.Equal("Child", groupChildren[0].Identifier); + this.VerifyValueSet(groupChildren[0].Settings, new KeyValuePair("b", "c")); + } + + /// + /// Test for using version 0.3 schema. + /// + [Fact] + public void BasicVersion_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + a: 1 + b: '2' +variables: + v1: var1 + v2: 42 +resources: + - name: Name + type: Module/Resource + metadata: + e: '5' + f: 6 + properties: + c: 3 + d: '4' + dependsOn: + - g + - h + - name: Name2 + type: Module/Resource2 + dependsOn: + - m + properties: + l: '10' + metadata: + i: '7' + j: 8 + q: 42 +")); + + Assert.Null(result.ResultCode); + Assert.NotNull(result.Set); + Assert.Equal(string.Empty, result.Field); + Assert.Equal(string.Empty, result.Value); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); + + ConfigurationSet set = result.Set; + + Assert.Equal("0.3", set.SchemaVersion); + Assert.NotNull(set.SchemaUri); + Assert.Equal("https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json", set.SchemaUri.ToString()); + + this.VerifyValueSet(set.Metadata, new ("a", 1), new ("b", "2")); + this.VerifyValueSet(set.Variables, new ("v1", "var1"), new ("v2", 42)); + + Assert.Empty(set.Parameters); + + Assert.Equal(2, set.Units.Count); + + this.VerifyUnitProperties(set.Units[0], "Name", "Module/Resource"); + this.VerifyValueSet(set.Units[0].Metadata, new ("e", "5"), new ("f", 6)); + this.VerifyValueSet(set.Units[0].Settings, new ("c", 3), new ("d", "4")); + this.VerifyStringArray(set.Units[0].Dependencies, "g", "h"); + + this.VerifyUnitProperties(set.Units[1], "Name2", "Module/Resource2"); + this.VerifyValueSet(set.Units[1].Metadata, new ("i", "7"), new ("j", 8), new ("q", 42)); + this.VerifyValueSet(set.Units[1].Settings, new KeyValuePair("l", "10")); + this.VerifyStringArray(set.Units[1].Dependencies, "m"); + } + + /// + /// Test for the successful parsing of default value of a parameter. + /// + /// The type. + /// The default value. + /// The expected value. + /// The expected type. + /// The secure state. + [Theory] + [InlineData("string", "abc", "abc", Windows.Foundation.PropertyType.String)] + [InlineData("string", "'42'", "42", Windows.Foundation.PropertyType.String)] + [InlineData("securestring", "abcdef", "abcdef", Windows.Foundation.PropertyType.String, true)] + [InlineData("int", "42", 42, Windows.Foundation.PropertyType.Int64)] + [InlineData("bool", "true", true, Windows.Foundation.PropertyType.Boolean)] + [InlineData("object", "string", "string", Windows.Foundation.PropertyType.Inspectable)] + [InlineData("object", "42", 42, Windows.Foundation.PropertyType.Inspectable)] + [InlineData("secureobject", "string", "string", Windows.Foundation.PropertyType.Inspectable, true)] + [InlineData("secureobject", "42", 42, Windows.Foundation.PropertyType.Inspectable, true)] + public void Parameters_DefaultValue_Success(string type, string defaultValue, object expectedValue, object expectedType, bool secure = false) + { + this.TestParameterDefaultValue(type, defaultValue, expectedValue, Assert.IsType(expectedType), secure); + } + + /// + /// Test for the failed parsing of default value of a parameter. + /// + /// The type. + /// The default value. + /// The expected value. + [Theory] + [InlineData("string", "42")] + [InlineData("int", "abc")] + [InlineData("int", "'42'", "42")] + [InlineData("bool", "'true'", "true")] + public void Parameters_DefaultValue_Failure(string type, string defaultValue, object? expectedValue = null) + { + this.TestParameterDefaultValue(type, defaultValue, expectedValue); + } + + /// + /// Test to ensure that schema version and uri is working as expected. + /// + /// The version. + /// The uri. + [Theory] + [InlineData("0.1", null)] + [InlineData("0.2", null)] + [InlineData("0.3", "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json")] + public void Schema_Version_Uri(string version, string? uri) + { + ConfigurationSet set = this.ConfigurationSet(); + + set.SchemaVersion = version; + if (uri != null) + { + Assert.Equal(uri, set.SchemaUri.AbsoluteUri); + } + else + { + Assert.Null(set.SchemaUri); + } + + if (!string.IsNullOrEmpty(uri)) + { + set.SchemaUri = new Uri(uri); + Assert.Equal(version, set.SchemaVersion); + } + } + + /// + /// Verifies that the configuration set (0.2) with environments parses and serializes. + /// + [Fact] + public void Environment_0_2() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +properties: + configurationVersion: 0.2 + resources: + - resource: FakeModule/FakeResource + id: elevated + directives: + description: FakeDescription + allowPrerelease: true + securityContext: elevated + settings: + TestString: Hello + - resource: FakeModule2/FakeResource2 + id: restricted + directives: + description: FakeDescription2 + securityContext: restricted + settings: + TestString: Bye + - resource: FakeModule2/FakeResource2 + id: current + directives: + securityContext: current + settings: + TestString: Bye + - resource: FakeModule2/FakeResource2 + id: default + settings: + TestString: Bye +")); + + Dictionary expectedEnvironments = new Dictionary(); + expectedEnvironments.Add("elevated", SecurityContext.Elevated); + expectedEnvironments.Add("restricted", SecurityContext.Restricted); + expectedEnvironments.Add("current", SecurityContext.Current); + expectedEnvironments.Add("default", SecurityContext.Current); + + this.ValidateSecurityContexts(openResult, expectedEnvironments); + + // Shuffle security contexts, serialize, parse and validate again + expectedEnvironments["elevated"] = SecurityContext.Restricted; + expectedEnvironments["restricted"] = SecurityContext.Current; + expectedEnvironments["current"] = SecurityContext.Restricted; + expectedEnvironments["default"] = SecurityContext.Elevated; + + var units = openResult.Set.Units; + foreach (var unit in units) + { + SecurityContext newContext = SecurityContext.Current; + Assert.True(expectedEnvironments.TryGetValue(unit.Identifier, out newContext)); + unit.Environment.Context = newContext; + } + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateSecurityContexts(serializedSetResult, expectedEnvironments); + } + + /// + /// Verifies that the configuration set (0.3) inherits set environment. + /// + [Fact] + public void SetMetadataEnvironmentInheritance_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: first + type: Module/Resource + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' +")); + + Dictionary environmentProperties = new Dictionary(); + environmentProperties.Add("a", "b"); + ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; + + Dictionary expectedEnvironments = new Dictionary(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData()); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData()); + + this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); + } + + /// + /// Verifies that the configuration set (0.3) inherits set environment. + /// + [Fact] + public void SetMetadataEnvironmentInheritance_ProcessorOverridden_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: first + type: Module/Resource + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + processor: not-pwsh +")); + + Dictionary environmentProperties = new Dictionary(); + environmentProperties.Add("a", "b"); + ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; + + Dictionary expectedEnvironments = new Dictionary(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData()); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { ProcessorIdentifier = "not-pwsh" }); + + this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); + } + + /// + /// Verifies that the configuration set (0.3) inherits set environment. + /// + [Fact] + public void SetMetadataEnvironmentInheritance_ContextOverridden_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: first + type: Module/Resource + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + securityContext: restricted +")); + + Dictionary environmentProperties = new Dictionary(); + environmentProperties.Add("a", "b"); + ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; + + Dictionary expectedEnvironments = new Dictionary(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData()); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted }); + + this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); + } + + /// + /// Verifies that the configuration set (0.3) serializes common environment to the set metadata. + /// + [Fact] + public void CommonEnvironmentElevatedToSetMetadata_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: first + type: Module/Resource + metadata: + winget: + securityContext: elevated + processor: pwsh + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh +")); + + ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData(); + + Dictionary expectedEnvironments = new Dictionary(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); + + this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); + } + + /// + /// Verifies that the configuration set (0.3) serializes common environment to the set metadata. + /// + [Fact] + public void CommonProcessorElevatedToSetMetadata_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +resources: + - name: first + type: Module/Resource + metadata: + winget: + securityContext: elevated + processor: pwsh + properties: + c: 3 + - name: second + type: Module/Resource2 + properties: + l: '10' + metadata: + winget: + securityContext: restricted + processor: + identifier: pwsh +")); + + ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData(); + + Dictionary expectedEnvironments = new Dictionary(); + expectedEnvironments.Add("first", new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh" }); + expectedEnvironments.Add("second", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted, ProcessorIdentifier = "pwsh" }); + + this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(serializedSetResult, setEnvironment, expectedEnvironments); + } + + /// + /// Verifies that the configuration set (0.3) environments work with group units. + /// + [Fact] + public void EnvironmentsWithGroups_0_3() + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult openResult = processor.OpenConfigurationSet(this.CreateStream(@" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +metadata: + winget: + securityContext: elevated + processor: + identifier: pwsh + properties: + a: b +resources: + - name: non-group + type: Module/Resource2 + properties: + l: '10' + - name: group + type: Module/Resource + metadata: + isGroup: true + winget: + securityContext: restricted + properties: + resources: + - name: inherit + type: Module/Resource + properties: + a: b + - name: override + type: Module/Resource + properties: + c: d + metadata: + winget: + processor: not-pwsh +")); + + Dictionary environmentProperties = new Dictionary(); + environmentProperties.Add("a", "b"); + ConfigurationEnvironmentData setEnvironment = new ConfigurationEnvironmentData() { Context = SecurityContext.Elevated, ProcessorIdentifier = "pwsh", ProcessorProperties = environmentProperties }; + + Dictionary expectedEnvironments = new Dictionary(); + expectedEnvironments.Add("non-group", new ConfigurationEnvironmentData()); + expectedEnvironments.Add("group", new ConfigurationEnvironmentData() { Context = SecurityContext.Restricted }); + + Dictionary groupExpectedEnvironments = new Dictionary(); + groupExpectedEnvironments.Add("inherit", new ConfigurationEnvironmentData()); + groupExpectedEnvironments.Add("override", new ConfigurationEnvironmentData() { ProcessorIdentifier = "not-pwsh" }); + + this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments, "group", groupExpectedEnvironments); + + // Serialize set. + InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream(); + openResult.Set.Serialize(stream); + + string yamlOutput = this.ReadStream(stream); + + // Reopen configuration set from serialized string and verify values. + OpenConfigurationSetResult serializedSetResult = processor.OpenConfigurationSet(this.CreateStream(yamlOutput)); + + this.ValidateEnvironments(openResult, setEnvironment, expectedEnvironments, "group", groupExpectedEnvironments); + } + + private void ValidateEnvironments(OpenConfigurationSetResult openResult, ConfigurationEnvironmentData setEnvironment, Dictionary expectedEnvironments, string? groupToCheck = null, Dictionary? groupExpectedEnvironments = null) + { + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + ConfigurationSet configurationSet = openResult.Set; + + this.ValidateEnvironment(setEnvironment, configurationSet.Environment); + + var units = configurationSet.Units; + this.ValidateEnvironments(units, expectedEnvironments, groupToCheck, groupExpectedEnvironments); + } + + private void ValidateEnvironments(IList units, Dictionary expectedEnvironments, string? groupToCheck = null, Dictionary? groupExpectedEnvironments = null) + { + Assert.Equal(expectedEnvironments.Count, units.Count); + foreach (var unit in units) + { + ConfigurationEnvironmentData? expectedEnvironment = null; + Assert.True(expectedEnvironments.TryGetValue(unit.Identifier, out expectedEnvironment)); + this.ValidateEnvironment(expectedEnvironment, unit.Environment); + + if (unit.Identifier == groupToCheck) + { + Assert.True(unit.IsGroup); + Assert.NotNull(groupExpectedEnvironments); + var groupUnits = unit.Units; + this.ValidateEnvironments(groupUnits, groupExpectedEnvironments); + } + } + } + + private void ValidateEnvironment(ConfigurationEnvironmentData? expectedEnvironment, ConfigurationEnvironment? actualEnvironment) + { + Assert.NotNull(expectedEnvironment); + Assert.NotNull(actualEnvironment); + Assert.Equal(expectedEnvironment.Context, actualEnvironment.Context); + Assert.Equal(expectedEnvironment.ProcessorIdentifier, actualEnvironment.ProcessorIdentifier); + Assert.True(expectedEnvironment.PropertiesEqual(actualEnvironment.ProcessorProperties)); + } + + private void ValidateSecurityContexts(OpenConfigurationSetResult openResult, Dictionary expectedContexts) + { + Assert.Null(openResult.ResultCode); + Assert.NotNull(openResult.Set); + ConfigurationSet configurationSet = openResult.Set; + + var units = configurationSet.Units; + Assert.Equal(expectedContexts.Count, units.Count); + foreach (var unit in units) + { + SecurityContext expectedContext = SecurityContext.Current; + Assert.True(expectedContexts.TryGetValue(unit.Identifier, out expectedContext)); + Assert.Equal(expectedContext, unit.Environment.Context); + } + } + + private void TestParameterDefaultValue(string type, string defaultValue, object? expectedValue = null, Windows.Foundation.PropertyType? expectedType = null, bool secure = false) + { + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(); + + OpenConfigurationSetResult result = processor.OpenConfigurationSet(this.CreateStream(string.Format( + @" +$schema: https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json +parameters: + {0}: + type: {0} + defaultValue: {1} +", + type, + defaultValue))); + + if (expectedType != null) + { + Assert.Null(result.ResultCode); + Assert.NotNull(result.Set); + Assert.Equal(string.Empty, result.Field); + Assert.Equal(string.Empty, result.Value); + Assert.Equal(0U, result.Line); + Assert.Equal(0U, result.Column); + + var parameters = result.Set.Parameters; + Assert.NotNull(parameters); + Assert.Single(parameters); + + Assert.Equal(type, parameters[0].Name); + Assert.Equal(expectedType, parameters[0].Type); + Assert.Equal(secure, parameters[0].IsSecure); + + Assert.NotNull(expectedValue); + this.VerifyObject(type, expectedValue, parameters[0].DefaultValue); + } + else + { + Assert.NotNull(result.ResultCode); + Assert.Equal(Errors.WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, result.ResultCode.HResult); + Assert.Null(result.Set); + Assert.Equal("defaultValue", result.Field); + Assert.Equal(expectedValue?.ToString() ?? defaultValue, result.Value); + Assert.NotEqual(0U, result.Line); + Assert.NotEqual(0U, result.Column); + } + } + + private void VerifyUnitProperties(ConfigurationUnit unit, string identifier, string type) + { + Assert.NotNull(unit); + Assert.Equal(identifier, unit.Identifier); + Assert.Equal(type, unit.Type); + } + + private void VerifyValueSet(ValueSet values, params KeyValuePair[] expected) + { + Assert.NotNull(values); + Assert.Equal(expected.Length, values.Count); + + foreach (var expectation in expected) + { + Assert.True(values.ContainsKey(expectation.Key), $"Not Found {expectation.Key}"); + object value = values[expectation.Key]; + + this.VerifyObject(expectation.Key, expectation.Value, value); + } + } + + private void VerifyStringArray(IList strings, params string[] expected) + { + Assert.NotNull(strings); + Assert.Equal(expected.Length, strings.Count); + + foreach (var expectation in expected) + { + bool found = false; + foreach (var value in strings) + { + if (!found) + { + found = expectation == value; + } + } + + Assert.True(found, $"Did not find {expectation} in string array"); + } + } + + private void VerifyParameter(ConfigurationParameter parameter, string name, Windows.Foundation.PropertyType type, bool secure, object? defaultValue = null) + { + Assert.Equal(name, parameter.Name); + Assert.Equal(type, parameter.Type); + Assert.Equal(secure, parameter.IsSecure); + this.VerifyObject(name, defaultValue, parameter.DefaultValue); + } + + private void VerifyObject(string name, object? expectedValue, object? actualValue) + { + if (expectedValue != null) + { + Assert.NotNull(actualValue); + + switch (expectedValue) + { + case int i: + Assert.True(i == (int)(long)actualValue, $"{name}: expected[{i}], actual[{(int)(long)actualValue}]"); + break; + case string s: + Assert.True(s == (string)actualValue, $"{name}: expected[{s}], actual[{(string)actualValue}]"); + break; + case bool b: + Assert.True(b == (bool)actualValue, $"{name}: expected[{b}], actual[{(bool)actualValue}]"); + break; + case ValueSet v: + var actualValueSet = actualValue.As(); + Assert.True(v.ContentEquals(actualValueSet), $"ValueSets not equal: {name}\n---expected---:\n{v.ToYaml()}\n---actual---:\n{actualValueSet.ToYaml()}"); + break; + default: + Assert.Fail($"Add expected type `{expectedValue.GetType().Name}` to switch statement for {name}."); + break; + } + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs index f737312bd0..92e9ae5f50 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/PowerShellHelperTests.cs @@ -1,178 +1,178 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// PowerShell helper tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class PowerShellHelperTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public PowerShellHelperTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Tests CreateModuleSpecification. - /// - [Fact] - public void CreateModuleSpecification_Module_Test() - { - string moduleName = "MyModule"; - - var moduleSpecification = PowerShellHelpers.CreateModuleSpecification(moduleName); - Assert.Equal(moduleName, moduleSpecification.Name); - Assert.Null(moduleSpecification.RequiredVersion); - Assert.Null(moduleSpecification.Version); - Assert.Null(moduleSpecification.MaximumVersion); - Assert.Null(moduleSpecification.Guid); - } - - /// - /// Tests CreateModuleSpecification with arguments. use version. - /// - [Fact] - public void CreateModuleSpecification_Required_Test() - { - string moduleName = "MyModule"; - string version = "0.1.0"; - string guid = Guid.NewGuid().ToString(); - - var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( - moduleName, - version, - null, - null, - guid); - Assert.Equal(moduleName, moduleSpecification.Name); - Assert.NotNull(moduleSpecification.RequiredVersion); - Assert.Equal(version, moduleSpecification.RequiredVersion.ToString()); - Assert.Null(moduleSpecification.Version); - Assert.Null(moduleSpecification.MaximumVersion); - Assert.NotNull(moduleSpecification.Guid); - Assert.Equal(guid, moduleSpecification.Guid.ToString()); - } - - /// - /// Tests CreateModuleSpecification with arguments. Use min version. - /// - [Fact] - public void CreateModuleSpecification_MinMax_Test() - { - string moduleName = "MyModule"; - string minVersion = "0.0.1"; - string maxVersion = "1.0.0"; - string guid = Guid.NewGuid().ToString(); - - var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( - moduleName, - null, - minVersion, - maxVersion, - guid); - Assert.Equal(moduleName, moduleSpecification.Name); - Assert.Null(moduleSpecification.RequiredVersion); - Assert.NotNull(moduleSpecification.Version); - Assert.Equal(minVersion, moduleSpecification.Version.ToString()); - Assert.NotNull(moduleSpecification.MaximumVersion); - Assert.Equal(maxVersion, moduleSpecification.MaximumVersion.ToString()); - Assert.NotNull(moduleSpecification.Guid); - Assert.Equal(guid, moduleSpecification.Guid.ToString()); - } - - /// - /// Tests CreateModuleSpecification with prerelease versions. - /// - [Fact] - public void CreateModuleSpecification_PrereleaseVersions_Required_Test() - { - string moduleName = "MyModule"; - string preVersion = "0.1.0-pre"; - string version = "0.1.0"; - - var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( - moduleName, - preVersion); - Assert.Equal(moduleName, moduleSpecification.Name); - Assert.NotNull(moduleSpecification.RequiredVersion); - Assert.Equal(version, moduleSpecification.RequiredVersion.ToString()); - Assert.Null(moduleSpecification.Version); - Assert.Null(moduleSpecification.MaximumVersion); - Assert.Null(moduleSpecification.Guid); - } - - /// - /// Tests CreateModuleSpecification with prerelease versions. - /// - [Fact] - public void CreateModuleSpecification_PrereleaseVersions_Test() - { - string moduleName = "MyModule"; - string preMinVersion = "0.0.1-pre"; - string minVersion = "0.0.1"; - string preMaxVersion = "1.0.0-pre"; - string maxVersion = "1.0.0"; - - var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( - moduleName, - null, - preMinVersion, - preMaxVersion); - Assert.Equal(moduleName, moduleSpecification.Name); - Assert.Null(moduleSpecification.RequiredVersion); - Assert.NotNull(moduleSpecification.Version); - Assert.Equal(minVersion, moduleSpecification.Version.ToString()); - Assert.NotNull(moduleSpecification.MaximumVersion); - Assert.Equal(maxVersion, moduleSpecification.MaximumVersion.ToString()); - Assert.Null(moduleSpecification.Guid); - } - - /// - /// Tests CreateModuleSpecification with arguments. Don't use minVersion and version. - /// - [Fact] - public void CreateModuleSpecification_Args_Throws_Test() - { - string moduleName = "MyModule"; - string version = "0.1.0"; - string minVersion = "0.0.1"; - string maxVersion = "1.0.0"; - - Assert.Throws(() => PowerShellHelpers.CreateModuleSpecification( - moduleName, - version, - minVersion, - null, - null)); - - Assert.Throws(() => PowerShellHelpers.CreateModuleSpecification( - moduleName, - version, - null, - maxVersion, - null)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using Microsoft.Management.Configuration.Processor.PowerShell.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// PowerShell helper tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class PowerShellHelperTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public PowerShellHelperTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Tests CreateModuleSpecification. + /// + [Fact] + public void CreateModuleSpecification_Module_Test() + { + string moduleName = "MyModule"; + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification(moduleName); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.Null(moduleSpecification.RequiredVersion); + Assert.Null(moduleSpecification.Version); + Assert.Null(moduleSpecification.MaximumVersion); + Assert.Null(moduleSpecification.Guid); + } + + /// + /// Tests CreateModuleSpecification with arguments. use version. + /// + [Fact] + public void CreateModuleSpecification_Required_Test() + { + string moduleName = "MyModule"; + string version = "0.1.0"; + string guid = Guid.NewGuid().ToString(); + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + version, + null, + null, + guid); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.NotNull(moduleSpecification.RequiredVersion); + Assert.Equal(version, moduleSpecification.RequiredVersion.ToString()); + Assert.Null(moduleSpecification.Version); + Assert.Null(moduleSpecification.MaximumVersion); + Assert.NotNull(moduleSpecification.Guid); + Assert.Equal(guid, moduleSpecification.Guid.ToString()); + } + + /// + /// Tests CreateModuleSpecification with arguments. Use min version. + /// + [Fact] + public void CreateModuleSpecification_MinMax_Test() + { + string moduleName = "MyModule"; + string minVersion = "0.0.1"; + string maxVersion = "1.0.0"; + string guid = Guid.NewGuid().ToString(); + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + null, + minVersion, + maxVersion, + guid); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.Null(moduleSpecification.RequiredVersion); + Assert.NotNull(moduleSpecification.Version); + Assert.Equal(minVersion, moduleSpecification.Version.ToString()); + Assert.NotNull(moduleSpecification.MaximumVersion); + Assert.Equal(maxVersion, moduleSpecification.MaximumVersion.ToString()); + Assert.NotNull(moduleSpecification.Guid); + Assert.Equal(guid, moduleSpecification.Guid.ToString()); + } + + /// + /// Tests CreateModuleSpecification with prerelease versions. + /// + [Fact] + public void CreateModuleSpecification_PrereleaseVersions_Required_Test() + { + string moduleName = "MyModule"; + string preVersion = "0.1.0-pre"; + string version = "0.1.0"; + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + preVersion); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.NotNull(moduleSpecification.RequiredVersion); + Assert.Equal(version, moduleSpecification.RequiredVersion.ToString()); + Assert.Null(moduleSpecification.Version); + Assert.Null(moduleSpecification.MaximumVersion); + Assert.Null(moduleSpecification.Guid); + } + + /// + /// Tests CreateModuleSpecification with prerelease versions. + /// + [Fact] + public void CreateModuleSpecification_PrereleaseVersions_Test() + { + string moduleName = "MyModule"; + string preMinVersion = "0.0.1-pre"; + string minVersion = "0.0.1"; + string preMaxVersion = "1.0.0-pre"; + string maxVersion = "1.0.0"; + + var moduleSpecification = PowerShellHelpers.CreateModuleSpecification( + moduleName, + null, + preMinVersion, + preMaxVersion); + Assert.Equal(moduleName, moduleSpecification.Name); + Assert.Null(moduleSpecification.RequiredVersion); + Assert.NotNull(moduleSpecification.Version); + Assert.Equal(minVersion, moduleSpecification.Version.ToString()); + Assert.NotNull(moduleSpecification.MaximumVersion); + Assert.Equal(maxVersion, moduleSpecification.MaximumVersion.ToString()); + Assert.Null(moduleSpecification.Guid); + } + + /// + /// Tests CreateModuleSpecification with arguments. Don't use minVersion and version. + /// + [Fact] + public void CreateModuleSpecification_Args_Throws_Test() + { + string moduleName = "MyModule"; + string version = "0.1.0"; + string minVersion = "0.0.1"; + string maxVersion = "1.0.0"; + + Assert.Throws(() => PowerShellHelpers.CreateModuleSpecification( + moduleName, + version, + minVersion, + null, + null)); + + Assert.Throws(() => PowerShellHelpers.CreateModuleSpecification( + moduleName, + version, + null, + maxVersion, + null)); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs index bc061c46b1..0c6ab1e8c3 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ProcessorEnvironmentTests.cs @@ -1,251 +1,251 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Moq; - using Xunit; - using Xunit.Abstractions; - using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; - - /// - /// HostedEnvironment tests, that is more ProcessorEnvironmentBase tests for non forwarding functions. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ProcessorEnvironmentTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log. - public ProcessorEnvironmentTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Test SetVariable and SetVariable methods. - /// - [Fact] - public void HostedEnvironment_Variables() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - // As string. - string var1Name = "var1"; - string var1 = "This is a string"; - processorEnv.SetVariable(var1Name, var1); - string var1Result = processorEnv.GetVariable(var1Name); - Assert.Equal(var1, var1Result); - - // As int. - string var2Name = "var2"; - int var2 = 42; - processorEnv.SetVariable(var2Name, var2); - int var2Result = processorEnv.GetVariable(var2Name); - - // Wrong type. - Assert.Throws(() => processorEnv.GetVariable(var1Name)); - } - - /// - /// Tests SetPsModulePath. - /// - [Fact] - public void HostedEnvironment_SetPSModulePath() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - string psModulePathInput = "SetPSModulePathModulePath"; - string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.NotEqual(psModulePathInput, psModulePath); - - processorEnv.SetPSModulePath(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.Equal(psModulePathInput, psModulePath); - } - - /// - /// Tests SetPsModulePaths. - /// - [Fact] - public void HostedEnvironment_SetPSModulePaths() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - var psModulePathInput = new List() - { - "Path1", - "Path2", - "Path3", - "Path4", - }; - string psModulePathExpected = "Path1;Path2;Path3;Path4"; - - string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.NotEqual(psModulePathExpected, psModulePath); - - processorEnv.SetPSModulePaths(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.Equal(psModulePathExpected, psModulePath); - } - - /// - /// Tests AppendPSModulePath. - /// - [Fact] - public void HostedEnvironment_AppendPSModulePath() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - string psModulePathInput = "AppendPSModulePathModulePath"; - string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.False(psModulePath.EndsWith($";{psModulePathInput}")); - - processorEnv.AppendPSModulePath(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.EndsWith($";{psModulePathInput}", psModulePath); - - // No duplicates - processorEnv.AppendPSModulePath(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.False(psModulePath.EndsWith($";{psModulePathInput};{psModulePathInput}")); - Assert.EndsWith($";{psModulePathInput}", psModulePath); - } - - /// - /// Tests AppendPSModulePaths. - /// - [Fact] - public void HostedEnvironment_AppendPSModulePaths() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - var psModulePathInput = new List() - { - "AppendPSModulePathsPath1", - "AppendPSModulePathsPath2", - "AppendPSModulePathsPath3", - "AppendPSModulePathsPath4", - }; - string psModulePathExpected = "AppendPSModulePathsPath1;AppendPSModulePathsPath2;AppendPSModulePathsPath3;AppendPSModulePathsPath4"; - - string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.False(psModulePath.EndsWith($";{psModulePathExpected}")); - - processorEnv.AppendPSModulePaths(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.EndsWith($";{psModulePathExpected}", psModulePath); - - // No duplicates - psModulePathInput = new List() - { - "AppendPSModulePathsPath1", - "AppendPSModulePathsPath5", - "AppendPSModulePathsPath2", - }; - processorEnv.AppendPSModulePaths(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.EndsWith($";{psModulePathExpected};AppendPSModulePathsPath5", psModulePath); - } - - /// - /// Tests PrependPSModulePath. - /// - [Fact] - public void HostedEnvironment_PrependPSModulePath() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - string psModulePathInput = "PrependPSModulePathModulePath"; - string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.False(psModulePath.StartsWith($";{psModulePathInput}")); - - processorEnv.PrependPSModulePath(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.StartsWith($"{psModulePathInput};", psModulePath); - - // No duplicates - processorEnv.PrependPSModulePath(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.False(psModulePath.StartsWith($"{psModulePathInput};{psModulePathInput};")); - Assert.StartsWith($"{psModulePathInput};", psModulePath); - } - - /// - /// Tests PrependPSModulePaths. - /// - [Fact] - public void HostedEnvironment_PrependPSModulePaths() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - - var psModulePathInput = new List() - { - "PrependPSModulePathsPath1", - "PrependPSModulePathsPath2", - "PrependPSModulePathsPath3", - "PrependPSModulePathsPath4", - }; - string psModulePathExpected = "PrependPSModulePathsPath1;PrependPSModulePathsPath2;PrependPSModulePathsPath3;PrependPSModulePathsPath4"; - - string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.False(psModulePath.StartsWith($";{psModulePathExpected}")); - - processorEnv.PrependPSModulePaths(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.StartsWith($"{psModulePathExpected};", psModulePath); - - // No duplicates - psModulePathInput = new List() - { - "PrependPSModulePathsPath1", - "PrependPSModulePathsPath5", - "PrependPSModulePathsPath2", - }; - processorEnv.PrependPSModulePaths(psModulePathInput); - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.StartsWith($"PrependPSModulePathsPath5;{psModulePathExpected};", psModulePath); - } - - /// - /// Test CleanupPSModulePath. - /// - [Fact] - public void HostedEnvironment_CleanupPSModulePath() - { - var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); - var psModulePathInput = new List() - { - "CleanupPSModulePathPath1", - "CleanupPSModulePathPath2", - "CleanupPSModulePathPath3", - "CleanupPSModulePathPath1", - "CleanupPSModulePathPath1", - }; - - string psModulePathExpected = "CleanupPSModulePathPath1;CleanupPSModulePathPath2;CleanupPSModulePathPath3;CleanupPSModulePathPath1;CleanupPSModulePathPath1"; - processorEnv.SetPSModulePaths(psModulePathInput); - string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.Equal(psModulePathExpected, psModulePath); - - processorEnv.CleanupPSModulePath("CleanupPSModulePathPath1"); - - psModulePathExpected = "CleanupPSModulePathPath2;CleanupPSModulePathPath3"; - psModulePath = processorEnv.GetVariable(Variables.PSModulePath); - Assert.Equal(psModulePathExpected, psModulePath); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Moq; + using Xunit; + using Xunit.Abstractions; + using static Microsoft.Management.Configuration.Processor.PowerShell.Constants.PowerShellConstants; + + /// + /// HostedEnvironment tests, that is more ProcessorEnvironmentBase tests for non forwarding functions. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ProcessorEnvironmentTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log. + public ProcessorEnvironmentTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Test SetVariable and SetVariable methods. + /// + [Fact] + public void HostedEnvironment_Variables() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + // As string. + string var1Name = "var1"; + string var1 = "This is a string"; + processorEnv.SetVariable(var1Name, var1); + string var1Result = processorEnv.GetVariable(var1Name); + Assert.Equal(var1, var1Result); + + // As int. + string var2Name = "var2"; + int var2 = 42; + processorEnv.SetVariable(var2Name, var2); + int var2Result = processorEnv.GetVariable(var2Name); + + // Wrong type. + Assert.Throws(() => processorEnv.GetVariable(var1Name)); + } + + /// + /// Tests SetPsModulePath. + /// + [Fact] + public void HostedEnvironment_SetPSModulePath() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + string psModulePathInput = "SetPSModulePathModulePath"; + string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.NotEqual(psModulePathInput, psModulePath); + + processorEnv.SetPSModulePath(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.Equal(psModulePathInput, psModulePath); + } + + /// + /// Tests SetPsModulePaths. + /// + [Fact] + public void HostedEnvironment_SetPSModulePaths() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + var psModulePathInput = new List() + { + "Path1", + "Path2", + "Path3", + "Path4", + }; + string psModulePathExpected = "Path1;Path2;Path3;Path4"; + + string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.NotEqual(psModulePathExpected, psModulePath); + + processorEnv.SetPSModulePaths(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.Equal(psModulePathExpected, psModulePath); + } + + /// + /// Tests AppendPSModulePath. + /// + [Fact] + public void HostedEnvironment_AppendPSModulePath() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + string psModulePathInput = "AppendPSModulePathModulePath"; + string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.False(psModulePath.EndsWith($";{psModulePathInput}")); + + processorEnv.AppendPSModulePath(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.EndsWith($";{psModulePathInput}", psModulePath); + + // No duplicates + processorEnv.AppendPSModulePath(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.False(psModulePath.EndsWith($";{psModulePathInput};{psModulePathInput}")); + Assert.EndsWith($";{psModulePathInput}", psModulePath); + } + + /// + /// Tests AppendPSModulePaths. + /// + [Fact] + public void HostedEnvironment_AppendPSModulePaths() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + var psModulePathInput = new List() + { + "AppendPSModulePathsPath1", + "AppendPSModulePathsPath2", + "AppendPSModulePathsPath3", + "AppendPSModulePathsPath4", + }; + string psModulePathExpected = "AppendPSModulePathsPath1;AppendPSModulePathsPath2;AppendPSModulePathsPath3;AppendPSModulePathsPath4"; + + string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.False(psModulePath.EndsWith($";{psModulePathExpected}")); + + processorEnv.AppendPSModulePaths(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.EndsWith($";{psModulePathExpected}", psModulePath); + + // No duplicates + psModulePathInput = new List() + { + "AppendPSModulePathsPath1", + "AppendPSModulePathsPath5", + "AppendPSModulePathsPath2", + }; + processorEnv.AppendPSModulePaths(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.EndsWith($";{psModulePathExpected};AppendPSModulePathsPath5", psModulePath); + } + + /// + /// Tests PrependPSModulePath. + /// + [Fact] + public void HostedEnvironment_PrependPSModulePath() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + string psModulePathInput = "PrependPSModulePathModulePath"; + string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.False(psModulePath.StartsWith($";{psModulePathInput}")); + + processorEnv.PrependPSModulePath(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.StartsWith($"{psModulePathInput};", psModulePath); + + // No duplicates + processorEnv.PrependPSModulePath(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.False(psModulePath.StartsWith($"{psModulePathInput};{psModulePathInput};")); + Assert.StartsWith($"{psModulePathInput};", psModulePath); + } + + /// + /// Tests PrependPSModulePaths. + /// + [Fact] + public void HostedEnvironment_PrependPSModulePaths() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + + var psModulePathInput = new List() + { + "PrependPSModulePathsPath1", + "PrependPSModulePathsPath2", + "PrependPSModulePathsPath3", + "PrependPSModulePathsPath4", + }; + string psModulePathExpected = "PrependPSModulePathsPath1;PrependPSModulePathsPath2;PrependPSModulePathsPath3;PrependPSModulePathsPath4"; + + string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.False(psModulePath.StartsWith($";{psModulePathExpected}")); + + processorEnv.PrependPSModulePaths(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.StartsWith($"{psModulePathExpected};", psModulePath); + + // No duplicates + psModulePathInput = new List() + { + "PrependPSModulePathsPath1", + "PrependPSModulePathsPath5", + "PrependPSModulePathsPath2", + }; + processorEnv.PrependPSModulePaths(psModulePathInput); + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.StartsWith($"PrependPSModulePathsPath5;{psModulePathExpected};", psModulePath); + } + + /// + /// Test CleanupPSModulePath. + /// + [Fact] + public void HostedEnvironment_CleanupPSModulePath() + { + var processorEnv = this.fixture.PrepareTestProcessorEnvironment(); + var psModulePathInput = new List() + { + "CleanupPSModulePathPath1", + "CleanupPSModulePathPath2", + "CleanupPSModulePathPath3", + "CleanupPSModulePathPath1", + "CleanupPSModulePathPath1", + }; + + string psModulePathExpected = "CleanupPSModulePathPath1;CleanupPSModulePathPath2;CleanupPSModulePathPath3;CleanupPSModulePathPath1;CleanupPSModulePathPath1"; + processorEnv.SetPSModulePaths(psModulePathInput); + string psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.Equal(psModulePathExpected, psModulePath); + + processorEnv.CleanupPSModulePath("CleanupPSModulePathPath1"); + + psModulePathExpected = "CleanupPSModulePathPath2;CleanupPSModulePathPath3"; + psModulePath = processorEnv.GetVariable(Variables.PSModulePath); + Assert.Equal(psModulePathExpected, psModulePath); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/SemanticVersionTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/SemanticVersionTests.cs index 52502d8d64..b3fac6c01d 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/SemanticVersionTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/SemanticVersionTests.cs @@ -1,78 +1,78 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Xunit; - using Xunit.Abstractions; - - /// - /// Semantic version tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class SemanticVersionTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public SemanticVersionTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Test version. - /// - [Fact] - public void SemanticVersion_Test() - { - var semanticVersion = new SemanticVersion("1.0.1"); - Assert.Equal("1.0.1", semanticVersion.ToString()); - Assert.Equal(new Version("1.0.1"), semanticVersion.Version); - Assert.False(semanticVersion.IsPrerelease); - Assert.Null(semanticVersion.PrereleaseTag); - } - - /// - /// Tests prerelease version. - /// - [Fact] - public void PrereleaseVersion_Test() - { - var semanticVersion = new SemanticVersion("1.0.1-pre"); - Assert.Equal("1.0.1-pre", semanticVersion.ToString()); - Assert.Equal(new Version("1.0.1"), semanticVersion.Version); - Assert.True(semanticVersion.IsPrerelease); - Assert.Equal("pre", semanticVersion.PrereleaseTag); - } - - /// - /// Tests GetMaximumVersion. - /// - /// Input. - /// Expected. - [Theory] - [InlineData("1.0.1", "1.0.1")] - [InlineData("1.0.*", "1.0.999999999")] - [InlineData("*.*.1", "999999999.999999999.1")] - public void MaximumVersion_Test(string input, string expected) - { - var semanticVersion = new SemanticVersion(input); - Assert.Equal(expected, semanticVersion.ToString()); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Xunit; + using Xunit.Abstractions; + + /// + /// Semantic version tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class SemanticVersionTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public SemanticVersionTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Test version. + /// + [Fact] + public void SemanticVersion_Test() + { + var semanticVersion = new SemanticVersion("1.0.1"); + Assert.Equal("1.0.1", semanticVersion.ToString()); + Assert.Equal(new Version("1.0.1"), semanticVersion.Version); + Assert.False(semanticVersion.IsPrerelease); + Assert.Null(semanticVersion.PrereleaseTag); + } + + /// + /// Tests prerelease version. + /// + [Fact] + public void PrereleaseVersion_Test() + { + var semanticVersion = new SemanticVersion("1.0.1-pre"); + Assert.Equal("1.0.1-pre", semanticVersion.ToString()); + Assert.Equal(new Version("1.0.1"), semanticVersion.Version); + Assert.True(semanticVersion.IsPrerelease); + Assert.Equal("pre", semanticVersion.PrereleaseTag); + } + + /// + /// Tests GetMaximumVersion. + /// + /// Input. + /// Expected. + [Theory] + [InlineData("1.0.1", "1.0.1")] + [InlineData("1.0.*", "1.0.999999999")] + [InlineData("*.*.1", "999999999.999999999.1")] + public void MaximumVersion_Test(string input, string expected) + { + var semanticVersion = new SemanticVersion(input); + Assert.Equal(expected, semanticVersion.ToString()); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ShutdownTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ShutdownTests.cs index 2c242775de..07e3ff4a2f 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ShutdownTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ShutdownTests.cs @@ -1,185 +1,185 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using WinGetTestCommon; - using Xunit; - using Xunit.Abstractions; - - /// - /// Unit tests for running test on the processor. - /// - [Collection("UnitTestCollection")] - [OutOfProc] - public class ShutdownTests : ConfigurationProcessorTestBase - { - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ShutdownTests(UnitTestFixture fixture, ITestOutputHelper log) - : base(fixture, log) - { - } - - /// - /// Initiates a shutdown on the process when it is running a synchronous operation. - /// - [Fact] - public void ShutdownSynchronization_SyncCall() - { - this.Fixture.RecreateStatics(); - - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitWaits = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitWaits, configurationUnitWorks }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitWaits); - - ManualResetEvent isWaiting = new ManualResetEvent(false); - ManualResetEvent waitingOn = new ManualResetEvent(false); - unitProcessor.TestSettingsDelegate = () => - { - isWaiting.Set(); - waitingOn.WaitOne(); - return new TestSettingsResultInstance(configurationUnitWaits) { TestResult = ConfigurationTestResult.Positive }; - }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - TestConfigurationSetResult? result = null; - Exception? exception = null; - - ManualResetEvent syncCallDone = new ManualResetEvent(false); - Thread thread = new Thread(() => - { - try - { - result = processor.TestSet(configurationSet); - } - catch (Exception ex) - { - exception = ex; - } - - syncCallDone.Set(); - }); - thread.Start(); - - Assert.True(isWaiting.WaitOne(5000)); - - var servers = WinGetServerInstance.GetInstances(); - Assert.Single(servers); - - var server = servers[0]; - Assert.True(server.HasWindow); - - // This is the call pattern from Windows - this.SendMessageAndLog(server, WindowMessage.QueryEndSession); - - Thread thread2 = new Thread(() => - { - // Release the wait after initiating the shutdown, but before waiting on it - waitingOn.Set(); - }); - thread2.Start(); - - this.SendMessageAndLog(server, WindowMessage.EndSession); - this.SendMessageAndLog(server, WindowMessage.Close); - - Assert.True(syncCallDone.WaitOne(5000)); - - Assert.NotNull(exception); - - Assert.True(server.Process.WaitForExit(5000)); - } - - /// - /// Initiates a shutdown on the process when it is running an asynchronous operation. - /// - [Fact] - public void ShutdownSynchronization_AsyncCall() - { - this.Fixture.RecreateStatics(); - - ConfigurationSet configurationSet = this.ConfigurationSet(); - ConfigurationUnit configurationUnitWaits = this.ConfigurationUnit(); - ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); - configurationSet.Units = new ConfigurationUnit[] { configurationUnitWaits, configurationUnitWorks }; - - TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); - TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); - TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitWaits); - - ManualResetEvent isWaiting = new ManualResetEvent(false); - ManualResetEvent waitingOn = new ManualResetEvent(false); - unitProcessor.TestSettingsDelegate = () => - { - isWaiting.Set(); - waitingOn.WaitOne(); - return new TestSettingsResultInstance(configurationUnitWaits) { TestResult = ConfigurationTestResult.Positive }; - }; - - ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); - - var operation = processor.TestSetAsync(configurationSet); - Assert.True(isWaiting.WaitOne(5000)); - - var servers = WinGetServerInstance.GetInstances(); - Assert.Single(servers); - - var server = servers[0]; - Assert.True(server.HasWindow); - - // This is the call pattern from Windows - this.SendMessageAndLog(server, WindowMessage.QueryEndSession); - - Thread thread2 = new Thread(() => - { - // Release the wait after initiating the shutdown, but before waiting on it - waitingOn.Set(); - }); - thread2.Start(); - - this.SendMessageAndLog(server, WindowMessage.EndSession); - this.SendMessageAndLog(server, WindowMessage.Close); - - Assert.ThrowsAny(() => operation.GetAwaiter().GetResult()); - - Assert.True(server.Process.WaitForExit(5000)); - } - - private void SendMessageAndLog(WinGetServerInstance server, WindowMessage message) - { - this.Log.WriteLine($"Sending message {message} to process {server.Process.Id}..."); - try - { - if (server.SendMessage(message)) - { - this.Log.WriteLine("... succeeded."); - } - else - { - this.Log.WriteLine("... failed."); - } - } - catch (Exception e) - { - this.Log.WriteLine($"... had exception: {e.Message}"); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using WinGetTestCommon; + using Xunit; + using Xunit.Abstractions; + + /// + /// Unit tests for running test on the processor. + /// + [Collection("UnitTestCollection")] + [OutOfProc] + public class ShutdownTests : ConfigurationProcessorTestBase + { + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ShutdownTests(UnitTestFixture fixture, ITestOutputHelper log) + : base(fixture, log) + { + } + + /// + /// Initiates a shutdown on the process when it is running a synchronous operation. + /// + [Fact] + public void ShutdownSynchronization_SyncCall() + { + this.Fixture.RecreateStatics(); + + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitWaits = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitWaits, configurationUnitWorks }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitWaits); + + ManualResetEvent isWaiting = new ManualResetEvent(false); + ManualResetEvent waitingOn = new ManualResetEvent(false); + unitProcessor.TestSettingsDelegate = () => + { + isWaiting.Set(); + waitingOn.WaitOne(); + return new TestSettingsResultInstance(configurationUnitWaits) { TestResult = ConfigurationTestResult.Positive }; + }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + TestConfigurationSetResult? result = null; + Exception? exception = null; + + ManualResetEvent syncCallDone = new ManualResetEvent(false); + Thread thread = new Thread(() => + { + try + { + result = processor.TestSet(configurationSet); + } + catch (Exception ex) + { + exception = ex; + } + + syncCallDone.Set(); + }); + thread.Start(); + + Assert.True(isWaiting.WaitOne(5000)); + + var servers = WinGetServerInstance.GetInstances(); + Assert.Single(servers); + + var server = servers[0]; + Assert.True(server.HasWindow); + + // This is the call pattern from Windows + this.SendMessageAndLog(server, WindowMessage.QueryEndSession); + + Thread thread2 = new Thread(() => + { + // Release the wait after initiating the shutdown, but before waiting on it + waitingOn.Set(); + }); + thread2.Start(); + + this.SendMessageAndLog(server, WindowMessage.EndSession); + this.SendMessageAndLog(server, WindowMessage.Close); + + Assert.True(syncCallDone.WaitOne(5000)); + + Assert.NotNull(exception); + + Assert.True(server.Process.WaitForExit(5000)); + } + + /// + /// Initiates a shutdown on the process when it is running an asynchronous operation. + /// + [Fact] + public void ShutdownSynchronization_AsyncCall() + { + this.Fixture.RecreateStatics(); + + ConfigurationSet configurationSet = this.ConfigurationSet(); + ConfigurationUnit configurationUnitWaits = this.ConfigurationUnit(); + ConfigurationUnit configurationUnitWorks = this.ConfigurationUnit(); + configurationSet.Units = new ConfigurationUnit[] { configurationUnitWaits, configurationUnitWorks }; + + TestConfigurationProcessorFactory factory = new TestConfigurationProcessorFactory(); + TestConfigurationSetProcessor setProcessor = factory.CreateTestProcessor(configurationSet); + TestConfigurationUnitProcessor unitProcessor = setProcessor.CreateTestProcessor(configurationUnitWaits); + + ManualResetEvent isWaiting = new ManualResetEvent(false); + ManualResetEvent waitingOn = new ManualResetEvent(false); + unitProcessor.TestSettingsDelegate = () => + { + isWaiting.Set(); + waitingOn.WaitOne(); + return new TestSettingsResultInstance(configurationUnitWaits) { TestResult = ConfigurationTestResult.Positive }; + }; + + ConfigurationProcessor processor = this.CreateConfigurationProcessorWithDiagnostics(factory); + + var operation = processor.TestSetAsync(configurationSet); + Assert.True(isWaiting.WaitOne(5000)); + + var servers = WinGetServerInstance.GetInstances(); + Assert.Single(servers); + + var server = servers[0]; + Assert.True(server.HasWindow); + + // This is the call pattern from Windows + this.SendMessageAndLog(server, WindowMessage.QueryEndSession); + + Thread thread2 = new Thread(() => + { + // Release the wait after initiating the shutdown, but before waiting on it + waitingOn.Set(); + }); + thread2.Start(); + + this.SendMessageAndLog(server, WindowMessage.EndSession); + this.SendMessageAndLog(server, WindowMessage.Close); + + Assert.ThrowsAny(() => operation.GetAwaiter().GetResult()); + + Assert.True(server.Process.WaitForExit(5000)); + } + + private void SendMessageAndLog(WinGetServerInstance server, WindowMessage message) + { + this.Log.WriteLine($"Sending message {message} to process {server.Process.Id}..."); + try + { + if (server.SendMessage(message)) + { + this.Log.WriteLine("... succeeded."); + } + else + { + this.Log.WriteLine("... failed."); + } + } + catch (Exception e) + { + this.Log.WriteLine($"... had exception: {e.Message}"); + } + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/TypeHelpersTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/TypeHelpersTests.cs index b072e369a5..e02916202f 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/TypeHelpersTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/TypeHelpersTests.cs @@ -1,212 +1,212 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System.Collections; - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.Helpers; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Windows.Foundation.Collections; - using Xunit; - using Xunit.Abstractions; - - /// - /// TypeHelpers tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class TypeHelpersTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public TypeHelpersTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - private enum TestEnum - { - Value, - } - - /// - /// Tests PropertyExists. - /// - [Fact] - public void PropertyExistsTests() - { - dynamic obj = new - { - Property1 = "value", - }; - - Assert.True(TypeHelpers.PropertyExists(obj, "Property1")); - Assert.False(TypeHelpers.PropertyExists(obj, "Property2")); - } - - /// - /// Tests PropertyWithTypeExists. - /// - [Fact] - public void PropertyWithTypeExistsTests() - { - dynamic obj = new - { - Property1 = "value", - Property2 = 42, - }; - - Assert.True(TypeHelpers.PropertyWithTypeExists(obj, "Property1")); - Assert.True(TypeHelpers.PropertyWithTypeExists(obj, "Property2")); - - Assert.False(TypeHelpers.PropertyWithTypeExists(obj, "Property1")); - Assert.False(TypeHelpers.PropertyWithTypeExists(obj, "Property2")); - } - - /// - /// Test PropertyExistsAndIsEnum. - /// - [Fact] - public void PropertyExistsAndIsEnumTests() - { - dynamic obj = new - { - Property1 = TestEnum.Value, - Property2 = "string", - }; - - Assert.True(TypeHelpers.PropertyExistsAndIsEnum(obj, "Property1")); - Assert.False(TypeHelpers.PropertyExistsAndIsEnum(obj, "Property2")); - } - - /// - /// Tests PropertyExistsAndIsList. - /// - [Fact] - public void PropertyExistsAndIsListTests() - { - dynamic obj = new - { - Property1 = new List() { "value" }, - Property2 = "string", - }; - - Assert.True(TypeHelpers.PropertyExistsAndIsList(obj, "Property1")); - Assert.False(TypeHelpers.PropertyExistsAndIsList(obj, "Property2")); - } - - /// - /// Tests GetAllPropertiesValues. - /// - [Fact] - public void GetAllPropertiesValuesTests() - { - string s = "value"; - int i = 42; - TestEnum e = TestEnum.Value; - dynamic obj = new - { - Property1 = s, - Property2 = i, - Property3 = e, - }; - - ValueSet set = TypeHelpers.GetAllPropertiesValues(obj); - Assert.Equal(3, set.Count); - - Assert.True(set.ContainsKey("Property1")); - Assert.True(set.TryGetValue("Property1", out object v1)); - Assert.Equal(s, v1 as string); - - Assert.True(set.ContainsKey("Property2")); - Assert.True(set.TryGetValue("Property2", out object v2)); - Assert.Equal(i, (int)v2); - - Assert.True(set.ContainsKey("Property3")); - Assert.True(set.TryGetValue("Property3", out object v3)); - Assert.Equal(e.ToString(), v3); - } - - /// - /// Verifies when a property is a Hashtable. It must be converted to a ValueSet. - /// - [Fact] - public void GetAllPropertiesValuesTest_Hashtable() - { - string k1 = "key1"; - string k2 = "key2"; - int v1 = 7; - string v2 = "value2"; - dynamic obj = new - { - Property1 = new Hashtable - { - { k1, v1 }, - { k2, v2 }, - }, - }; - - ValueSet set = TypeHelpers.GetAllPropertiesValues(obj); - Assert.Single(set); - - Assert.True(set.ContainsKey("Property1")); - Assert.True(set.TryGetValue("Property1", out object valueSetResultObj)); - - ValueSet? valueSetResult = valueSetResultObj as ValueSet; - Assert.NotNull(valueSetResult); - Assert.Equal(2, valueSetResult.Count); - Assert.True(valueSetResult.ContainsKey(k1)); - Assert.Equal(v1, (int)valueSetResult[k1]); - Assert.True(valueSetResult.ContainsKey(k2)); - Assert.Equal(v2, (string)valueSetResult[k2]); - } - - /// - /// Verifies when a property is an array. It must generate a ValueSet - /// where the keys are the index and a key treatAsArray means the value - /// must be treated an array. - /// - [Fact] - public void GetAllPropertiesValuesTest_Array() - { - dynamic obj = new - { - Property1 = new int[] - { - 1, - 2, - 3, - 4, - }, - }; - - ValueSet set = TypeHelpers.GetAllPropertiesValues(obj); - Assert.Single(set); - - Assert.True(set.ContainsKey("Property1")); - Assert.True(set.TryGetValue("Property1", out object valueSetResultObj)); - - ValueSet? valueSetResult = valueSetResultObj as ValueSet; - Assert.NotNull(valueSetResult); - Assert.Equal(5, valueSetResult.Count); - - Assert.True(valueSetResult.ContainsKey("treatAsArray")); - Assert.True(valueSetResult.ContainsKey("0")); - Assert.True(valueSetResult.ContainsKey("1")); - Assert.True(valueSetResult.ContainsKey("2")); - Assert.True(valueSetResult.ContainsKey("3")); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System.Collections; + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.Helpers; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Windows.Foundation.Collections; + using Xunit; + using Xunit.Abstractions; + + /// + /// TypeHelpers tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class TypeHelpersTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public TypeHelpersTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + private enum TestEnum + { + Value, + } + + /// + /// Tests PropertyExists. + /// + [Fact] + public void PropertyExistsTests() + { + dynamic obj = new + { + Property1 = "value", + }; + + Assert.True(TypeHelpers.PropertyExists(obj, "Property1")); + Assert.False(TypeHelpers.PropertyExists(obj, "Property2")); + } + + /// + /// Tests PropertyWithTypeExists. + /// + [Fact] + public void PropertyWithTypeExistsTests() + { + dynamic obj = new + { + Property1 = "value", + Property2 = 42, + }; + + Assert.True(TypeHelpers.PropertyWithTypeExists(obj, "Property1")); + Assert.True(TypeHelpers.PropertyWithTypeExists(obj, "Property2")); + + Assert.False(TypeHelpers.PropertyWithTypeExists(obj, "Property1")); + Assert.False(TypeHelpers.PropertyWithTypeExists(obj, "Property2")); + } + + /// + /// Test PropertyExistsAndIsEnum. + /// + [Fact] + public void PropertyExistsAndIsEnumTests() + { + dynamic obj = new + { + Property1 = TestEnum.Value, + Property2 = "string", + }; + + Assert.True(TypeHelpers.PropertyExistsAndIsEnum(obj, "Property1")); + Assert.False(TypeHelpers.PropertyExistsAndIsEnum(obj, "Property2")); + } + + /// + /// Tests PropertyExistsAndIsList. + /// + [Fact] + public void PropertyExistsAndIsListTests() + { + dynamic obj = new + { + Property1 = new List() { "value" }, + Property2 = "string", + }; + + Assert.True(TypeHelpers.PropertyExistsAndIsList(obj, "Property1")); + Assert.False(TypeHelpers.PropertyExistsAndIsList(obj, "Property2")); + } + + /// + /// Tests GetAllPropertiesValues. + /// + [Fact] + public void GetAllPropertiesValuesTests() + { + string s = "value"; + int i = 42; + TestEnum e = TestEnum.Value; + dynamic obj = new + { + Property1 = s, + Property2 = i, + Property3 = e, + }; + + ValueSet set = TypeHelpers.GetAllPropertiesValues(obj); + Assert.Equal(3, set.Count); + + Assert.True(set.ContainsKey("Property1")); + Assert.True(set.TryGetValue("Property1", out object v1)); + Assert.Equal(s, v1 as string); + + Assert.True(set.ContainsKey("Property2")); + Assert.True(set.TryGetValue("Property2", out object v2)); + Assert.Equal(i, (int)v2); + + Assert.True(set.ContainsKey("Property3")); + Assert.True(set.TryGetValue("Property3", out object v3)); + Assert.Equal(e.ToString(), v3); + } + + /// + /// Verifies when a property is a Hashtable. It must be converted to a ValueSet. + /// + [Fact] + public void GetAllPropertiesValuesTest_Hashtable() + { + string k1 = "key1"; + string k2 = "key2"; + int v1 = 7; + string v2 = "value2"; + dynamic obj = new + { + Property1 = new Hashtable + { + { k1, v1 }, + { k2, v2 }, + }, + }; + + ValueSet set = TypeHelpers.GetAllPropertiesValues(obj); + Assert.Single(set); + + Assert.True(set.ContainsKey("Property1")); + Assert.True(set.TryGetValue("Property1", out object valueSetResultObj)); + + ValueSet? valueSetResult = valueSetResultObj as ValueSet; + Assert.NotNull(valueSetResult); + Assert.Equal(2, valueSetResult.Count); + Assert.True(valueSetResult.ContainsKey(k1)); + Assert.Equal(v1, (int)valueSetResult[k1]); + Assert.True(valueSetResult.ContainsKey(k2)); + Assert.Equal(v2, (string)valueSetResult[k2]); + } + + /// + /// Verifies when a property is an array. It must generate a ValueSet + /// where the keys are the index and a key treatAsArray means the value + /// must be treated an array. + /// + [Fact] + public void GetAllPropertiesValuesTest_Array() + { + dynamic obj = new + { + Property1 = new int[] + { + 1, + 2, + 3, + 4, + }, + }; + + ValueSet set = TypeHelpers.GetAllPropertiesValues(obj); + Assert.Single(set); + + Assert.True(set.ContainsKey("Property1")); + Assert.True(set.TryGetValue("Property1", out object valueSetResultObj)); + + ValueSet? valueSetResult = valueSetResultObj as ValueSet; + Assert.NotNull(valueSetResult); + Assert.Equal(5, valueSetResult.Count); + + Assert.True(valueSetResult.ContainsKey("treatAsArray")); + Assert.True(valueSetResult.ContainsKey("0")); + Assert.True(valueSetResult.ContainsKey("1")); + Assert.True(valueSetResult.ContainsKey("2")); + Assert.True(valueSetResult.ContainsKey("3")); + } + } +} diff --git a/src/Microsoft.Management.Configuration.UnitTests/Tests/ValueSetExtensionsTests.cs b/src/Microsoft.Management.Configuration.UnitTests/Tests/ValueSetExtensionsTests.cs index cef5cbbc2c..caca649ee0 100644 --- a/src/Microsoft.Management.Configuration.UnitTests/Tests/ValueSetExtensionsTests.cs +++ b/src/Microsoft.Management.Configuration.UnitTests/Tests/ValueSetExtensionsTests.cs @@ -1,461 +1,461 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.Management.Configuration.UnitTests.Tests -{ - using System; - using System.Collections; - using System.Collections.Generic; - using Microsoft.Management.Configuration.Processor.Extensions; - using Microsoft.Management.Configuration.UnitTests.Fixtures; - using Microsoft.Management.Configuration.UnitTests.Helpers; - using Windows.Foundation.Collections; - using Xunit; - using Xunit.Abstractions; - - /// - /// ValueSet extension tests. - /// - [Collection("UnitTestCollection")] - [InProc] - public class ValueSetExtensionsTests - { - private readonly UnitTestFixture fixture; - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Unit test fixture. - /// Log helper. - public ValueSetExtensionsTests(UnitTestFixture fixture, ITestOutputHelper log) - { - this.fixture = fixture; - this.log = log; - } - - /// - /// Tests PropertyExists. - /// - [Fact] - public void ValueSet_SimpleTypes() - { - string stringProperty = "stringProperty"; - string stringPropertyValue = "string"; - - string intProperty = "intProperty"; - long intPropertyValue = 64; - - string boolProperty = "boolProperty"; - bool boolPropertyValue = true; - - var valueSet = new ValueSet - { - { stringProperty, stringPropertyValue }, - { intProperty, intPropertyValue }, - { boolProperty, boolPropertyValue }, - }; - - var resultHashtable = valueSet.ToHashtable(); - - Assert.NotNull(resultHashtable); - - Assert.True(resultHashtable.ContainsKey(stringProperty)); - Assert.Equal(stringPropertyValue, resultHashtable[stringProperty]); - - Assert.True(resultHashtable.ContainsKey(intProperty)); - Assert.Equal(intPropertyValue, resultHashtable[intProperty]); - - Assert.True(resultHashtable.ContainsKey(boolProperty)); - Assert.Equal(boolPropertyValue, resultHashtable[boolProperty]); - } - - /// - /// Tests when a ValueSet has inner value sets. - /// - [Fact] - public void ValueSet_NestedValueSets() - { - string boolPropertyInner = "boolPropertyInner"; - bool boolPropertyValueInner = true; - var valueSetInner = new ValueSet() - { - { boolPropertyInner, boolPropertyValueInner }, - }; - - string stringPropertyInnerInner = "stringPropertyInnerInner"; - string stringPropertyValueInnerInner = "stringInnerInner"; - var valueSetInnerInner = new ValueSet() - { - { stringPropertyInnerInner, stringPropertyValueInnerInner }, - }; - - string inner2Key = "InnerKey2"; - var valueSetInner2 = new ValueSet() - { - { inner2Key, valueSetInnerInner }, - }; - - string key1 = "key1"; - string key2 = "key2"; - var valueSet = new ValueSet() - { - { key1, valueSetInner }, - { key2, valueSetInner2 }, - }; - - var hashtable = valueSet.ToHashtable(); - Assert.NotNull(hashtable); - Assert.Equal(2, hashtable.Count); - Assert.True(hashtable.ContainsKey(key1)); - Assert.True(hashtable.ContainsKey(key2)); - - var key1Result = hashtable[key1] as Hashtable; - Assert.NotNull(key1Result); - Assert.Single(key1Result); - Assert.True(key1Result.ContainsKey(boolPropertyInner)); - Assert.Equal(boolPropertyValueInner, key1Result[boolPropertyInner]); - - var key2Result = hashtable[key2] as Hashtable; - Assert.NotNull(key2Result); - Assert.Single(key2Result); - Assert.True(key2Result.ContainsKey(inner2Key)); - var key2ResultInner = key2Result[inner2Key] as Hashtable; - Assert.NotNull(key2ResultInner); - Assert.True(key2ResultInner.ContainsKey(stringPropertyInnerInner)); - Assert.Equal(stringPropertyValueInnerInner, key2ResultInner[stringPropertyInnerInner]); - } - - /// - /// Test when ValueSet contains a ValueSet that is threated as an. - /// - [Fact] - public void ValueSet_ArraySimpleTypes() - { - var valueSetArray = new ValueSet() - { - { "treatAsArray", true }, - { "0", "value1" }, - { "1", "value1" }, - { "2", "value1" }, - { "3", "value1" }, - }; - - string arrayKey = "arrayKey"; - var valueSet = new ValueSet() - { - { arrayKey, valueSetArray }, - }; - - var hashtable = valueSet.ToHashtable(); - Assert.NotNull(hashtable); - Assert.True(hashtable.ContainsKey(arrayKey)); - - var expectedList = hashtable[arrayKey] as IList; - Assert.NotNull(expectedList); - Assert.Equal(4, expectedList.Count); - foreach (var element in expectedList) - { - Assert.IsType(element); - } - } - - /// - /// Test ValueSet with an array of value sets. - /// - [Fact] - public void ValueSet_ArrayHashtable() - { - var arrayValue1 = new ValueSet() - { - { "key11", "value11" }, - { "key12", "value12" }, - }; - - var arrayValue2 = new ValueSet() - { - { "key21", "value21" }, - }; - - var valueSetArray = new ValueSet() - { - { "treatAsArray", true }, - { "0", arrayValue1 }, - { "1", arrayValue2 }, - }; - - string arrayKey = "arrayKey"; - var valueSet = new ValueSet() - { - { arrayKey, valueSetArray }, - }; - - var hashtable = valueSet.ToHashtable(); - Assert.NotNull(hashtable); - Assert.True(hashtable.ContainsKey(arrayKey)); - - var expectedList = hashtable[arrayKey] as IList; - Assert.NotNull(expectedList); - Assert.Equal(2, expectedList.Count); - - var resultValue1 = expectedList[0] as Hashtable; - Assert.NotNull(resultValue1); - Assert.Equal(2, resultValue1.Count); - - var resultValue2 = expectedList[1] as Hashtable; - Assert.NotNull(resultValue2); - Assert.Single(resultValue2); - } - - /// - /// Tests ConvertValueSetToArray. - /// - [Fact] - public void ValueSet_ArrayOrder() - { - string arrayValue0 = "arrayValue0"; - string arrayValue1 = "arrayValue1"; - string arrayValue2 = "arrayValue2"; - string arrayValue3 = "arrayValue3"; - string arrayValue4 = "arrayValue4"; - string arrayValue5 = "arrayValue5"; - string arrayValue6 = "arrayValue6"; - string arrayValue7 = "arrayValue7"; - string arrayValue8 = "arrayValue8"; - string arrayValue9 = "arrayValue9"; - string arrayValue10 = "arrayValue10"; - string arrayValue11 = "arrayValue11"; - string arrayValue12 = "arrayValue12"; - var valueSetArray = new ValueSet() - { - { "10", arrayValue10 }, - { "7", arrayValue7 }, - { "2", arrayValue2 }, - { "12", arrayValue12 }, - { "6", arrayValue6 }, - { "treatAsArray", true }, - { "3", arrayValue3 }, - { "1", arrayValue1 }, - { "9", arrayValue9 }, - { "0", arrayValue0 }, - { "4", arrayValue4 }, - { "11", arrayValue11 }, - { "8", arrayValue8 }, - { "5", arrayValue5 }, - }; - - var result = valueSetArray.ToArray(); - Assert.NotNull(result); - Assert.Equal(valueSetArray.Count - 1, result.Count); - Assert.Equal(arrayValue0, result[0]); - Assert.Equal(arrayValue1, result[1]); - Assert.Equal(arrayValue2, result[2]); - Assert.Equal(arrayValue3, result[3]); - Assert.Equal(arrayValue4, result[4]); - Assert.Equal(arrayValue5, result[5]); - Assert.Equal(arrayValue6, result[6]); - Assert.Equal(arrayValue7, result[7]); - Assert.Equal(arrayValue8, result[8]); - Assert.Equal(arrayValue9, result[9]); - Assert.Equal(arrayValue10, result[10]); - Assert.Equal(arrayValue11, result[11]); - Assert.Equal(arrayValue12, result[12]); - } - - /// - /// Tests ConvertValueSetToArray. - /// - [Fact] - public void ValueSet_InvalidArray() - { - string arrayValue0 = "arrayValue0"; - string arrayValue1 = "arrayValue1"; - string arrayValue2 = "arrayValue2"; - string arrayValue3 = "arrayValue3"; - string arrayValue4 = "arrayValue4"; - string arrayValue5 = "arrayValue5"; - string arrayValue6 = "arrayValue6"; - string arrayValue7 = "arrayValue7"; - string arrayValue8 = "arrayValue8"; - string arrayValue9 = "arrayValue9"; - string arrayValue10 = "arrayValue10"; - string arrayValue11 = "arrayValue11"; - string arrayValue12 = "arrayValue12"; - var valueSetArray = new ValueSet() - { - { "10", arrayValue10 }, - { "7", arrayValue7 }, - { "2", arrayValue2 }, - { "12", arrayValue12 }, - { "6", arrayValue6 }, - { "3", arrayValue3 }, - { "1", arrayValue1 }, - { "9", arrayValue9 }, - { "0", arrayValue0 }, - { "4", arrayValue4 }, - { "11", arrayValue11 }, - { "8", arrayValue8 }, - { "5", arrayValue5 }, - }; - - Assert.Throws(() => valueSetArray.ToArray()); - } - - /// - /// Tests ConvertValueSetToArray. - /// - [Fact] - public void ValueSet_InvalidArrayKey() - { - string arrayValue0 = "arrayValue0"; - string arrayValue1 = "arrayValue1"; - string arrayValue2 = "arrayValue2"; - string arrayValue3 = "arrayValue3"; - string arrayValue4 = "arrayValue4"; - string arrayValue5 = "arrayValue5"; - string arrayValue6 = "arrayValue6"; - string arrayValue7 = "arrayValue7"; - string arrayValue8 = "arrayValue8"; - string arrayValue9 = "arrayValue9"; - string arrayValue10 = "arrayValue10"; - string arrayValue11 = "arrayValue11"; - string arrayValue12 = "arrayValue12"; - var valueSetArray = new ValueSet() - { - { "10", arrayValue10 }, - { "7", arrayValue7 }, - { "2", arrayValue2 }, - { "a", arrayValue12 }, - { "6", arrayValue6 }, - { "3", arrayValue3 }, - { "1", arrayValue1 }, - { "9", arrayValue9 }, - { "0", arrayValue0 }, - { "4", arrayValue4 }, - { "11", arrayValue11 }, - { "8", arrayValue8 }, - { "5", arrayValue5 }, - }; - - Assert.Throws(() => valueSetArray.ToArray()); - } - - /// - /// Tests ValueSet simple types content equals. - /// - [Fact] - public void ValueSet_SimpleTypes_ContentEquals() - { - string stringProperty = "stringProperty"; - string stringPropertyValue = "string"; - - string intProperty = "intProperty"; - long intPropertyValue = 64; - - string boolProperty = "boolProperty"; - bool boolPropertyValue = true; - - var valueSet = new ValueSet - { - { stringProperty, stringPropertyValue }, - { intProperty, intPropertyValue }, - { boolProperty, boolPropertyValue }, - }; - - // Same content different order - var valueSetDifferentOrder = new ValueSet - { - { boolProperty, boolPropertyValue }, - { stringProperty, stringPropertyValue }, - { intProperty, intPropertyValue }, - }; - - Assert.True(valueSet.ContentEquals(valueSetDifferentOrder)); - - // Entry missing - var valueSetEntryMissing = new ValueSet - { - { stringProperty, stringPropertyValue }, - { intProperty, intPropertyValue }, - }; - - Assert.False(valueSet.ContentEquals(valueSetEntryMissing)); - - // Different entry - var valueSetEntryDifferent = new ValueSet - { - { stringProperty, stringPropertyValue }, - { intProperty, intPropertyValue }, - { "Another", "AnotherValue" }, - }; - - Assert.False(valueSet.ContentEquals(valueSetEntryDifferent)); - - // Different value - var valueSetDifferentValue = new ValueSet - { - { boolProperty, boolPropertyValue }, - { stringProperty, stringPropertyValue }, - { intProperty, 0 }, - }; - - Assert.False(valueSet.ContentEquals(valueSetDifferentValue)); - } - - /// - /// Tests when a ValueSet has inner value sets. - /// - [Fact] - public void ValueSet_NestedValueSets_ContentEquals() - { - string boolPropertyInner = "boolPropertyInner"; - bool boolPropertyValueInner = true; - var valueSetInner = new ValueSet() - { - { boolPropertyInner, boolPropertyValueInner }, - }; - - string stringPropertyInnerInner = "stringPropertyInnerInner"; - string stringPropertyValueInnerInner = "stringInnerInner"; - var valueSetInnerInner = new ValueSet() - { - { stringPropertyInnerInner, stringPropertyValueInnerInner }, - }; - - string inner2Key = "InnerKey2"; - var valueSetInner2 = new ValueSet() - { - { inner2Key, valueSetInnerInner }, - }; - - string key1 = "key1"; - string key2 = "key2"; - var valueSet = new ValueSet() - { - { key1, valueSetInner }, - { key2, valueSetInner2 }, - }; - - // Same content different order - var valueSetDifferentOrder = new ValueSet() - { - { key2, valueSetInner2 }, - { key1, valueSetInner }, - }; - - Assert.True(valueSet.ContentEquals(valueSetDifferentOrder)); - - // Different nested content - var valueSetDifferentContent = new ValueSet() - { - { key2, valueSetInner }, - { key1, valueSetInner2 }, - }; - - Assert.False(valueSet.ContentEquals(valueSetDifferentContent)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.Management.Configuration.UnitTests.Tests +{ + using System; + using System.Collections; + using System.Collections.Generic; + using Microsoft.Management.Configuration.Processor.Extensions; + using Microsoft.Management.Configuration.UnitTests.Fixtures; + using Microsoft.Management.Configuration.UnitTests.Helpers; + using Windows.Foundation.Collections; + using Xunit; + using Xunit.Abstractions; + + /// + /// ValueSet extension tests. + /// + [Collection("UnitTestCollection")] + [InProc] + public class ValueSetExtensionsTests + { + private readonly UnitTestFixture fixture; + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Unit test fixture. + /// Log helper. + public ValueSetExtensionsTests(UnitTestFixture fixture, ITestOutputHelper log) + { + this.fixture = fixture; + this.log = log; + } + + /// + /// Tests PropertyExists. + /// + [Fact] + public void ValueSet_SimpleTypes() + { + string stringProperty = "stringProperty"; + string stringPropertyValue = "string"; + + string intProperty = "intProperty"; + long intPropertyValue = 64; + + string boolProperty = "boolProperty"; + bool boolPropertyValue = true; + + var valueSet = new ValueSet + { + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + { boolProperty, boolPropertyValue }, + }; + + var resultHashtable = valueSet.ToHashtable(); + + Assert.NotNull(resultHashtable); + + Assert.True(resultHashtable.ContainsKey(stringProperty)); + Assert.Equal(stringPropertyValue, resultHashtable[stringProperty]); + + Assert.True(resultHashtable.ContainsKey(intProperty)); + Assert.Equal(intPropertyValue, resultHashtable[intProperty]); + + Assert.True(resultHashtable.ContainsKey(boolProperty)); + Assert.Equal(boolPropertyValue, resultHashtable[boolProperty]); + } + + /// + /// Tests when a ValueSet has inner value sets. + /// + [Fact] + public void ValueSet_NestedValueSets() + { + string boolPropertyInner = "boolPropertyInner"; + bool boolPropertyValueInner = true; + var valueSetInner = new ValueSet() + { + { boolPropertyInner, boolPropertyValueInner }, + }; + + string stringPropertyInnerInner = "stringPropertyInnerInner"; + string stringPropertyValueInnerInner = "stringInnerInner"; + var valueSetInnerInner = new ValueSet() + { + { stringPropertyInnerInner, stringPropertyValueInnerInner }, + }; + + string inner2Key = "InnerKey2"; + var valueSetInner2 = new ValueSet() + { + { inner2Key, valueSetInnerInner }, + }; + + string key1 = "key1"; + string key2 = "key2"; + var valueSet = new ValueSet() + { + { key1, valueSetInner }, + { key2, valueSetInner2 }, + }; + + var hashtable = valueSet.ToHashtable(); + Assert.NotNull(hashtable); + Assert.Equal(2, hashtable.Count); + Assert.True(hashtable.ContainsKey(key1)); + Assert.True(hashtable.ContainsKey(key2)); + + var key1Result = hashtable[key1] as Hashtable; + Assert.NotNull(key1Result); + Assert.Single(key1Result); + Assert.True(key1Result.ContainsKey(boolPropertyInner)); + Assert.Equal(boolPropertyValueInner, key1Result[boolPropertyInner]); + + var key2Result = hashtable[key2] as Hashtable; + Assert.NotNull(key2Result); + Assert.Single(key2Result); + Assert.True(key2Result.ContainsKey(inner2Key)); + var key2ResultInner = key2Result[inner2Key] as Hashtable; + Assert.NotNull(key2ResultInner); + Assert.True(key2ResultInner.ContainsKey(stringPropertyInnerInner)); + Assert.Equal(stringPropertyValueInnerInner, key2ResultInner[stringPropertyInnerInner]); + } + + /// + /// Test when ValueSet contains a ValueSet that is threated as an. + /// + [Fact] + public void ValueSet_ArraySimpleTypes() + { + var valueSetArray = new ValueSet() + { + { "treatAsArray", true }, + { "0", "value1" }, + { "1", "value1" }, + { "2", "value1" }, + { "3", "value1" }, + }; + + string arrayKey = "arrayKey"; + var valueSet = new ValueSet() + { + { arrayKey, valueSetArray }, + }; + + var hashtable = valueSet.ToHashtable(); + Assert.NotNull(hashtable); + Assert.True(hashtable.ContainsKey(arrayKey)); + + var expectedList = hashtable[arrayKey] as IList; + Assert.NotNull(expectedList); + Assert.Equal(4, expectedList.Count); + foreach (var element in expectedList) + { + Assert.IsType(element); + } + } + + /// + /// Test ValueSet with an array of value sets. + /// + [Fact] + public void ValueSet_ArrayHashtable() + { + var arrayValue1 = new ValueSet() + { + { "key11", "value11" }, + { "key12", "value12" }, + }; + + var arrayValue2 = new ValueSet() + { + { "key21", "value21" }, + }; + + var valueSetArray = new ValueSet() + { + { "treatAsArray", true }, + { "0", arrayValue1 }, + { "1", arrayValue2 }, + }; + + string arrayKey = "arrayKey"; + var valueSet = new ValueSet() + { + { arrayKey, valueSetArray }, + }; + + var hashtable = valueSet.ToHashtable(); + Assert.NotNull(hashtable); + Assert.True(hashtable.ContainsKey(arrayKey)); + + var expectedList = hashtable[arrayKey] as IList; + Assert.NotNull(expectedList); + Assert.Equal(2, expectedList.Count); + + var resultValue1 = expectedList[0] as Hashtable; + Assert.NotNull(resultValue1); + Assert.Equal(2, resultValue1.Count); + + var resultValue2 = expectedList[1] as Hashtable; + Assert.NotNull(resultValue2); + Assert.Single(resultValue2); + } + + /// + /// Tests ConvertValueSetToArray. + /// + [Fact] + public void ValueSet_ArrayOrder() + { + string arrayValue0 = "arrayValue0"; + string arrayValue1 = "arrayValue1"; + string arrayValue2 = "arrayValue2"; + string arrayValue3 = "arrayValue3"; + string arrayValue4 = "arrayValue4"; + string arrayValue5 = "arrayValue5"; + string arrayValue6 = "arrayValue6"; + string arrayValue7 = "arrayValue7"; + string arrayValue8 = "arrayValue8"; + string arrayValue9 = "arrayValue9"; + string arrayValue10 = "arrayValue10"; + string arrayValue11 = "arrayValue11"; + string arrayValue12 = "arrayValue12"; + var valueSetArray = new ValueSet() + { + { "10", arrayValue10 }, + { "7", arrayValue7 }, + { "2", arrayValue2 }, + { "12", arrayValue12 }, + { "6", arrayValue6 }, + { "treatAsArray", true }, + { "3", arrayValue3 }, + { "1", arrayValue1 }, + { "9", arrayValue9 }, + { "0", arrayValue0 }, + { "4", arrayValue4 }, + { "11", arrayValue11 }, + { "8", arrayValue8 }, + { "5", arrayValue5 }, + }; + + var result = valueSetArray.ToArray(); + Assert.NotNull(result); + Assert.Equal(valueSetArray.Count - 1, result.Count); + Assert.Equal(arrayValue0, result[0]); + Assert.Equal(arrayValue1, result[1]); + Assert.Equal(arrayValue2, result[2]); + Assert.Equal(arrayValue3, result[3]); + Assert.Equal(arrayValue4, result[4]); + Assert.Equal(arrayValue5, result[5]); + Assert.Equal(arrayValue6, result[6]); + Assert.Equal(arrayValue7, result[7]); + Assert.Equal(arrayValue8, result[8]); + Assert.Equal(arrayValue9, result[9]); + Assert.Equal(arrayValue10, result[10]); + Assert.Equal(arrayValue11, result[11]); + Assert.Equal(arrayValue12, result[12]); + } + + /// + /// Tests ConvertValueSetToArray. + /// + [Fact] + public void ValueSet_InvalidArray() + { + string arrayValue0 = "arrayValue0"; + string arrayValue1 = "arrayValue1"; + string arrayValue2 = "arrayValue2"; + string arrayValue3 = "arrayValue3"; + string arrayValue4 = "arrayValue4"; + string arrayValue5 = "arrayValue5"; + string arrayValue6 = "arrayValue6"; + string arrayValue7 = "arrayValue7"; + string arrayValue8 = "arrayValue8"; + string arrayValue9 = "arrayValue9"; + string arrayValue10 = "arrayValue10"; + string arrayValue11 = "arrayValue11"; + string arrayValue12 = "arrayValue12"; + var valueSetArray = new ValueSet() + { + { "10", arrayValue10 }, + { "7", arrayValue7 }, + { "2", arrayValue2 }, + { "12", arrayValue12 }, + { "6", arrayValue6 }, + { "3", arrayValue3 }, + { "1", arrayValue1 }, + { "9", arrayValue9 }, + { "0", arrayValue0 }, + { "4", arrayValue4 }, + { "11", arrayValue11 }, + { "8", arrayValue8 }, + { "5", arrayValue5 }, + }; + + Assert.Throws(() => valueSetArray.ToArray()); + } + + /// + /// Tests ConvertValueSetToArray. + /// + [Fact] + public void ValueSet_InvalidArrayKey() + { + string arrayValue0 = "arrayValue0"; + string arrayValue1 = "arrayValue1"; + string arrayValue2 = "arrayValue2"; + string arrayValue3 = "arrayValue3"; + string arrayValue4 = "arrayValue4"; + string arrayValue5 = "arrayValue5"; + string arrayValue6 = "arrayValue6"; + string arrayValue7 = "arrayValue7"; + string arrayValue8 = "arrayValue8"; + string arrayValue9 = "arrayValue9"; + string arrayValue10 = "arrayValue10"; + string arrayValue11 = "arrayValue11"; + string arrayValue12 = "arrayValue12"; + var valueSetArray = new ValueSet() + { + { "10", arrayValue10 }, + { "7", arrayValue7 }, + { "2", arrayValue2 }, + { "a", arrayValue12 }, + { "6", arrayValue6 }, + { "3", arrayValue3 }, + { "1", arrayValue1 }, + { "9", arrayValue9 }, + { "0", arrayValue0 }, + { "4", arrayValue4 }, + { "11", arrayValue11 }, + { "8", arrayValue8 }, + { "5", arrayValue5 }, + }; + + Assert.Throws(() => valueSetArray.ToArray()); + } + + /// + /// Tests ValueSet simple types content equals. + /// + [Fact] + public void ValueSet_SimpleTypes_ContentEquals() + { + string stringProperty = "stringProperty"; + string stringPropertyValue = "string"; + + string intProperty = "intProperty"; + long intPropertyValue = 64; + + string boolProperty = "boolProperty"; + bool boolPropertyValue = true; + + var valueSet = new ValueSet + { + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + { boolProperty, boolPropertyValue }, + }; + + // Same content different order + var valueSetDifferentOrder = new ValueSet + { + { boolProperty, boolPropertyValue }, + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + }; + + Assert.True(valueSet.ContentEquals(valueSetDifferentOrder)); + + // Entry missing + var valueSetEntryMissing = new ValueSet + { + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + }; + + Assert.False(valueSet.ContentEquals(valueSetEntryMissing)); + + // Different entry + var valueSetEntryDifferent = new ValueSet + { + { stringProperty, stringPropertyValue }, + { intProperty, intPropertyValue }, + { "Another", "AnotherValue" }, + }; + + Assert.False(valueSet.ContentEquals(valueSetEntryDifferent)); + + // Different value + var valueSetDifferentValue = new ValueSet + { + { boolProperty, boolPropertyValue }, + { stringProperty, stringPropertyValue }, + { intProperty, 0 }, + }; + + Assert.False(valueSet.ContentEquals(valueSetDifferentValue)); + } + + /// + /// Tests when a ValueSet has inner value sets. + /// + [Fact] + public void ValueSet_NestedValueSets_ContentEquals() + { + string boolPropertyInner = "boolPropertyInner"; + bool boolPropertyValueInner = true; + var valueSetInner = new ValueSet() + { + { boolPropertyInner, boolPropertyValueInner }, + }; + + string stringPropertyInnerInner = "stringPropertyInnerInner"; + string stringPropertyValueInnerInner = "stringInnerInner"; + var valueSetInnerInner = new ValueSet() + { + { stringPropertyInnerInner, stringPropertyValueInnerInner }, + }; + + string inner2Key = "InnerKey2"; + var valueSetInner2 = new ValueSet() + { + { inner2Key, valueSetInnerInner }, + }; + + string key1 = "key1"; + string key2 = "key2"; + var valueSet = new ValueSet() + { + { key1, valueSetInner }, + { key2, valueSetInner2 }, + }; + + // Same content different order + var valueSetDifferentOrder = new ValueSet() + { + { key2, valueSetInner2 }, + { key1, valueSetInner }, + }; + + Assert.True(valueSet.ContentEquals(valueSetDifferentOrder)); + + // Different nested content + var valueSetDifferentContent = new ValueSet() + { + { key2, valueSetInner }, + { key1, valueSetInner2 }, + }; + + Assert.False(valueSet.ContentEquals(valueSetDifferentContent)); + } + } +} diff --git a/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.cpp b/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.cpp index 5d2f4e3f00..a7f80cf92e 100644 --- a/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.cpp +++ b/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.cpp @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ApplyConfigurationSetResult.h" -#include "ApplyConfigurationSetResult.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - ApplyConfigurationSetResult::ApplyConfigurationSetResult() : - m_unitResults(multi_threaded_vector()) - {} - - Windows::Foundation::Collections::IVectorView ApplyConfigurationSetResult::UnitResults() const - { - return m_unitResults.GetView(); - } - - const Windows::Foundation::Collections::IVector& ApplyConfigurationSetResult::UnitResultsVector() - { - return m_unitResults; - } - - hresult ApplyConfigurationSetResult::ResultCode() const - { - return m_resultCode; - } - - void ApplyConfigurationSetResult::ResultCode(hresult value) - { - m_resultCode = value; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ApplyConfigurationSetResult.h" +#include "ApplyConfigurationSetResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + ApplyConfigurationSetResult::ApplyConfigurationSetResult() : + m_unitResults(multi_threaded_vector()) + {} + + Windows::Foundation::Collections::IVectorView ApplyConfigurationSetResult::UnitResults() const + { + return m_unitResults.GetView(); + } + + const Windows::Foundation::Collections::IVector& ApplyConfigurationSetResult::UnitResultsVector() + { + return m_unitResults; + } + + hresult ApplyConfigurationSetResult::ResultCode() const + { + return m_resultCode; + } + + void ApplyConfigurationSetResult::ResultCode(hresult value) + { + m_resultCode = value; + } +} diff --git a/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.h b/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.h index ed411452a1..d0d5493bab 100644 --- a/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.h +++ b/src/Microsoft.Management.Configuration/ApplyConfigurationSetResult.h @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ApplyConfigurationSetResult.g.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ApplyConfigurationSetResult : ApplyConfigurationSetResultT - { - using ApplyConfigurationUnitResult = Configuration::ApplyConfigurationUnitResult; - - ApplyConfigurationSetResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - const Windows::Foundation::Collections::IVector& UnitResultsVector(); - void ResultCode(hresult value); -#endif - - Windows::Foundation::Collections::IVectorView UnitResults() const; - hresult ResultCode() const; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - Windows::Foundation::Collections::IVector m_unitResults; - hresult m_resultCode; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ApplyConfigurationSetResult.g.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ApplyConfigurationSetResult : ApplyConfigurationSetResultT + { + using ApplyConfigurationUnitResult = Configuration::ApplyConfigurationUnitResult; + + ApplyConfigurationSetResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + const Windows::Foundation::Collections::IVector& UnitResultsVector(); + void ResultCode(hresult value); +#endif + + Windows::Foundation::Collections::IVectorView UnitResults() const; + hresult ResultCode() const; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::Collections::IVector m_unitResults; + hresult m_resultCode; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.cpp b/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.cpp index c179a47c5c..08b51c0cd6 100644 --- a/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.cpp +++ b/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.cpp @@ -1,85 +1,85 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ApplyConfigurationUnitResult.h" -#include "ApplyConfigurationUnitResult.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - ApplyConfigurationUnitResult::ApplyConfigurationUnitResult() : - m_resultInformation(make_self>()) - { - } - - void ApplyConfigurationUnitResult::Initialize(const IApplySettingsResult& result) - { - m_unit = result.Unit(); - THROW_HR_IF(E_POINTER, !m_unit); - m_resultInformation->Initialize(result.ResultInformation()); - } - - void ApplyConfigurationUnitResult::Initialize(const IApplyGroupMemberSettingsResult& unitResult) - { - m_unit = unitResult.Unit(); - THROW_HR_IF(E_POINTER, !m_unit); - m_resultInformation->Initialize(unitResult.ResultInformation()); - m_state = unitResult.State(); - m_previouslyInDesiredState = unitResult.PreviouslyInDesiredState(); - m_rebootRequired = unitResult.RebootRequired(); - } - - ConfigurationUnit ApplyConfigurationUnitResult::Unit() - { - return m_unit; - } - - void ApplyConfigurationUnitResult::Unit(ConfigurationUnit value) - { - m_unit = std::move(value); - } - - ConfigurationUnitState ApplyConfigurationUnitResult::State() const - { - return m_state.load(); - } - - void ApplyConfigurationUnitResult::State(ConfigurationUnitState value) - { - m_state.store(value); - } - - bool ApplyConfigurationUnitResult::PreviouslyInDesiredState() const - { - return m_previouslyInDesiredState; - } - - void ApplyConfigurationUnitResult::PreviouslyInDesiredState(bool value) - { - m_previouslyInDesiredState = value; - } - - bool ApplyConfigurationUnitResult::RebootRequired() const - { - return m_rebootRequired; - } - - void ApplyConfigurationUnitResult::RebootRequired(bool value) - { - m_rebootRequired = value; - } - - IConfigurationUnitResultInformation ApplyConfigurationUnitResult::ResultInformation() - { - return *m_resultInformation; - } - - void ApplyConfigurationUnitResult::ResultInformation(const Configuration::IConfigurationUnitResultInformation& value) - { - m_resultInformation->Initialize(value); - } - - ApplyConfigurationUnitResult::ResultInformationT ApplyConfigurationUnitResult::ResultInformationInternal() - { - return m_resultInformation; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ApplyConfigurationUnitResult.h" +#include "ApplyConfigurationUnitResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + ApplyConfigurationUnitResult::ApplyConfigurationUnitResult() : + m_resultInformation(make_self>()) + { + } + + void ApplyConfigurationUnitResult::Initialize(const IApplySettingsResult& result) + { + m_unit = result.Unit(); + THROW_HR_IF(E_POINTER, !m_unit); + m_resultInformation->Initialize(result.ResultInformation()); + } + + void ApplyConfigurationUnitResult::Initialize(const IApplyGroupMemberSettingsResult& unitResult) + { + m_unit = unitResult.Unit(); + THROW_HR_IF(E_POINTER, !m_unit); + m_resultInformation->Initialize(unitResult.ResultInformation()); + m_state = unitResult.State(); + m_previouslyInDesiredState = unitResult.PreviouslyInDesiredState(); + m_rebootRequired = unitResult.RebootRequired(); + } + + ConfigurationUnit ApplyConfigurationUnitResult::Unit() + { + return m_unit; + } + + void ApplyConfigurationUnitResult::Unit(ConfigurationUnit value) + { + m_unit = std::move(value); + } + + ConfigurationUnitState ApplyConfigurationUnitResult::State() const + { + return m_state.load(); + } + + void ApplyConfigurationUnitResult::State(ConfigurationUnitState value) + { + m_state.store(value); + } + + bool ApplyConfigurationUnitResult::PreviouslyInDesiredState() const + { + return m_previouslyInDesiredState; + } + + void ApplyConfigurationUnitResult::PreviouslyInDesiredState(bool value) + { + m_previouslyInDesiredState = value; + } + + bool ApplyConfigurationUnitResult::RebootRequired() const + { + return m_rebootRequired; + } + + void ApplyConfigurationUnitResult::RebootRequired(bool value) + { + m_rebootRequired = value; + } + + IConfigurationUnitResultInformation ApplyConfigurationUnitResult::ResultInformation() + { + return *m_resultInformation; + } + + void ApplyConfigurationUnitResult::ResultInformation(const Configuration::IConfigurationUnitResultInformation& value) + { + m_resultInformation->Initialize(value); + } + + ApplyConfigurationUnitResult::ResultInformationT ApplyConfigurationUnitResult::ResultInformationInternal() + { + return m_resultInformation; + } +} diff --git a/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.h b/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.h index 4b49180cfe..5e35fef5cd 100644 --- a/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.h +++ b/src/Microsoft.Management.Configuration/ApplyConfigurationUnitResult.h @@ -1,44 +1,44 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ApplyConfigurationUnitResult.g.h" -#include "ConfigurationUnitResultInformation.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ApplyConfigurationUnitResult : ApplyConfigurationUnitResultT - { - using ConfigurationUnit = Configuration::ConfigurationUnit; - using ResultInformationT = decltype(make_self>()); - - ApplyConfigurationUnitResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(const IApplySettingsResult& result); - void Initialize(const IApplyGroupMemberSettingsResult& unitResult); - - void Unit(ConfigurationUnit value); - void State(ConfigurationUnitState value); - void PreviouslyInDesiredState(bool value); - void RebootRequired(bool value); - void ResultInformation(const Configuration::IConfigurationUnitResultInformation& value); - ResultInformationT ResultInformationInternal(); -#endif - - ConfigurationUnit Unit(); - ConfigurationUnitState State() const; - bool PreviouslyInDesiredState() const; - bool RebootRequired() const; - IConfigurationUnitResultInformation ResultInformation(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ConfigurationUnit m_unit = nullptr; - std::atomic m_state = ConfigurationUnitState::Pending; - bool m_previouslyInDesiredState = false; - bool m_rebootRequired = false; - ResultInformationT m_resultInformation; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ApplyConfigurationUnitResult.g.h" +#include "ConfigurationUnitResultInformation.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ApplyConfigurationUnitResult : ApplyConfigurationUnitResultT + { + using ConfigurationUnit = Configuration::ConfigurationUnit; + using ResultInformationT = decltype(make_self>()); + + ApplyConfigurationUnitResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(const IApplySettingsResult& result); + void Initialize(const IApplyGroupMemberSettingsResult& unitResult); + + void Unit(ConfigurationUnit value); + void State(ConfigurationUnitState value); + void PreviouslyInDesiredState(bool value); + void RebootRequired(bool value); + void ResultInformation(const Configuration::IConfigurationUnitResultInformation& value); + ResultInformationT ResultInformationInternal(); +#endif + + ConfigurationUnit Unit(); + ConfigurationUnitState State() const; + bool PreviouslyInDesiredState() const; + bool RebootRequired() const; + IConfigurationUnitResultInformation ResultInformation(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationUnit m_unit = nullptr; + std::atomic m_state = ConfigurationUnitState::Pending; + bool m_previouslyInDesiredState = false; + bool m_rebootRequired = false; + ResultInformationT m_resultInformation; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.cpp b/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.cpp index 1b628ea39b..844cf1e553 100644 --- a/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.cpp +++ b/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.cpp @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ApplyGroupSettingsResult.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - ApplyGroupSettingsResult::ApplyGroupSettingsResult() : - m_resultInformation(make_self>()), - m_unitResults(winrt::multi_threaded_vector()) - {} - - void ApplyGroupSettingsResult::Group(const Windows::Foundation::IInspectable& value) - { - m_group = value; - } - - void ApplyGroupSettingsResult::RebootRequired(bool value) - { - m_rebootRequired = value; - } - - ApplyGroupSettingsResult::ResultInformationPtr ApplyGroupSettingsResult::ResultInformationInternal() - { - return m_resultInformation; - } - - Windows::Foundation::IInspectable ApplyGroupSettingsResult::Group() - { - return m_group; - } - - bool ApplyGroupSettingsResult::RebootRequired() - { - return m_rebootRequired; - } - - IConfigurationUnitResultInformation ApplyGroupSettingsResult::ResultInformation() - { - return *m_resultInformation; - } - - Windows::Foundation::Collections::IVector ApplyGroupSettingsResult::UnitResults() - { - return m_unitResults; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ApplyGroupSettingsResult.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + ApplyGroupSettingsResult::ApplyGroupSettingsResult() : + m_resultInformation(make_self>()), + m_unitResults(winrt::multi_threaded_vector()) + {} + + void ApplyGroupSettingsResult::Group(const Windows::Foundation::IInspectable& value) + { + m_group = value; + } + + void ApplyGroupSettingsResult::RebootRequired(bool value) + { + m_rebootRequired = value; + } + + ApplyGroupSettingsResult::ResultInformationPtr ApplyGroupSettingsResult::ResultInformationInternal() + { + return m_resultInformation; + } + + Windows::Foundation::IInspectable ApplyGroupSettingsResult::Group() + { + return m_group; + } + + bool ApplyGroupSettingsResult::RebootRequired() + { + return m_rebootRequired; + } + + IConfigurationUnitResultInformation ApplyGroupSettingsResult::ResultInformation() + { + return *m_resultInformation; + } + + Windows::Foundation::Collections::IVector ApplyGroupSettingsResult::UnitResults() + { + return m_unitResults; + } +} diff --git a/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.h b/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.h index 71257c15eb..af564b4703 100644 --- a/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.h +++ b/src/Microsoft.Management.Configuration/ApplyGroupSettingsResult.h @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" -#include "ConfigurationUnitResultInformation.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ApplyGroupSettingsResult : winrt::implements - { - ApplyGroupSettingsResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - using ResultInformationPtr = decltype(make_self>()); - - void Group(const Windows::Foundation::IInspectable& value); - void RebootRequired(bool value); - ResultInformationPtr ResultInformationInternal(); -#endif - - Windows::Foundation::IInspectable Group(); - bool RebootRequired(); - IConfigurationUnitResultInformation ResultInformation(); - Windows::Foundation::Collections::IVector UnitResults(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - Windows::Foundation::IInspectable m_group; - bool m_rebootRequired = false; - ResultInformationPtr m_resultInformation; - Windows::Foundation::Collections::IVector m_unitResults; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ApplyGroupSettingsResult : winrt::implements + { + ApplyGroupSettingsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + using ResultInformationPtr = decltype(make_self>()); + + void Group(const Windows::Foundation::IInspectable& value); + void RebootRequired(bool value); + ResultInformationPtr ResultInformationInternal(); +#endif + + Windows::Foundation::IInspectable Group(); + bool RebootRequired(); + IConfigurationUnitResultInformation ResultInformation(); + Windows::Foundation::Collections::IVector UnitResults(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::IInspectable m_group; + bool m_rebootRequired = false; + ResultInformationPtr m_resultInformation; + Windows::Foundation::Collections::IVector m_unitResults; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ArgumentValidation.cpp b/src/Microsoft.Management.Configuration/ArgumentValidation.cpp index 1328315190..581d1bf3b4 100644 --- a/src/Microsoft.Management.Configuration/ArgumentValidation.cpp +++ b/src/Microsoft.Management.Configuration/ArgumentValidation.cpp @@ -1,210 +1,210 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include -#include "ArgumentValidation.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - void EnsureSupportedType(Windows::Foundation::PropertyType type) - { - switch (type) - { - case Windows::Foundation::PropertyType::UInt8: - case Windows::Foundation::PropertyType::Int16: - case Windows::Foundation::PropertyType::UInt16: - case Windows::Foundation::PropertyType::Int32: - case Windows::Foundation::PropertyType::UInt32: - case Windows::Foundation::PropertyType::Int64: - case Windows::Foundation::PropertyType::UInt64: - case Windows::Foundation::PropertyType::Single: - case Windows::Foundation::PropertyType::Double: - case Windows::Foundation::PropertyType::Char16: - case Windows::Foundation::PropertyType::Boolean: - case Windows::Foundation::PropertyType::String: - case Windows::Foundation::PropertyType::Inspectable: - case Windows::Foundation::PropertyType::DateTime: - case Windows::Foundation::PropertyType::TimeSpan: - case Windows::Foundation::PropertyType::Guid: - case Windows::Foundation::PropertyType::UInt8Array: - case Windows::Foundation::PropertyType::Int16Array: - case Windows::Foundation::PropertyType::UInt16Array: - case Windows::Foundation::PropertyType::Int32Array: - case Windows::Foundation::PropertyType::UInt32Array: - case Windows::Foundation::PropertyType::Int64Array: - case Windows::Foundation::PropertyType::UInt64Array: - case Windows::Foundation::PropertyType::SingleArray: - case Windows::Foundation::PropertyType::DoubleArray: - case Windows::Foundation::PropertyType::Char16Array: - case Windows::Foundation::PropertyType::BooleanArray: - case Windows::Foundation::PropertyType::StringArray: - case Windows::Foundation::PropertyType::InspectableArray: - case Windows::Foundation::PropertyType::DateTimeArray: - case Windows::Foundation::PropertyType::TimeSpanArray: - case Windows::Foundation::PropertyType::GuidArray: - return; - } - - THROW_HR(E_INVALIDARG); - } - - bool IsValidObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type) - { - auto propertyValue = value.try_as(); - - // If the type is an object, it is acceptable for the value to be a ValueSet directly - if (type == Windows::Foundation::PropertyType::Inspectable && - (propertyValue || value.try_as())) - { - return true; - } - - // If it wasn't an object type and a ValueSet, it must be an IPropertyValue - if (!propertyValue) - { - return false; - } - - // If it is an IPropertyValue, it must have the required type - return (propertyValue.Type() == type); - } - - void EnsureObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type) - { - THROW_HR_IF(E_INVALIDARG, !IsValidObjectType(value, type)); - } - - bool IsComparableType(Windows::Foundation::PropertyType type) - { - switch (type) - { - case Windows::Foundation::PropertyType::UInt8: - case Windows::Foundation::PropertyType::Int16: - case Windows::Foundation::PropertyType::UInt16: - case Windows::Foundation::PropertyType::Int32: - case Windows::Foundation::PropertyType::UInt32: - case Windows::Foundation::PropertyType::Int64: - case Windows::Foundation::PropertyType::UInt64: - case Windows::Foundation::PropertyType::Single: - case Windows::Foundation::PropertyType::Double: - case Windows::Foundation::PropertyType::Char16: - case Windows::Foundation::PropertyType::DateTime: - case Windows::Foundation::PropertyType::TimeSpan: - return true; - } - - return false; - } - - void EnsureComparableType(Windows::Foundation::PropertyType type) - { - THROW_HR_IF(E_INVALIDARG, !IsComparableType(type)); - } - - bool IsLengthType(Windows::Foundation::PropertyType type) - { - switch (type) - { - case Windows::Foundation::PropertyType::String: - case Windows::Foundation::PropertyType::UInt8Array: - case Windows::Foundation::PropertyType::Int16Array: - case Windows::Foundation::PropertyType::UInt16Array: - case Windows::Foundation::PropertyType::Int32Array: - case Windows::Foundation::PropertyType::UInt32Array: - case Windows::Foundation::PropertyType::Int64Array: - case Windows::Foundation::PropertyType::UInt64Array: - case Windows::Foundation::PropertyType::SingleArray: - case Windows::Foundation::PropertyType::DoubleArray: - case Windows::Foundation::PropertyType::Char16Array: - case Windows::Foundation::PropertyType::BooleanArray: - case Windows::Foundation::PropertyType::StringArray: - case Windows::Foundation::PropertyType::InspectableArray: - case Windows::Foundation::PropertyType::DateTimeArray: - case Windows::Foundation::PropertyType::TimeSpanArray: - case Windows::Foundation::PropertyType::GuidArray: - case Windows::Foundation::PropertyType::PointArray: - case Windows::Foundation::PropertyType::SizeArray: - case Windows::Foundation::PropertyType::RectArray: - case Windows::Foundation::PropertyType::OtherTypeArray: - return true; - } - - return false; - } - - void EnsureLengthType(Windows::Foundation::PropertyType type) - { - THROW_HR_IF(E_INVALIDARG, !IsLengthType(type)); - } - - bool IsStringableType(Windows::Foundation::PropertyType type) - { - switch (type) - { - case Windows::Foundation::PropertyType::UInt8: - case Windows::Foundation::PropertyType::Int16: - case Windows::Foundation::PropertyType::UInt16: - case Windows::Foundation::PropertyType::Int32: - case Windows::Foundation::PropertyType::UInt32: - case Windows::Foundation::PropertyType::Int64: - case Windows::Foundation::PropertyType::UInt64: - case Windows::Foundation::PropertyType::Single: - case Windows::Foundation::PropertyType::Double: - case Windows::Foundation::PropertyType::Char16: - case Windows::Foundation::PropertyType::Boolean: - case Windows::Foundation::PropertyType::String: - return true; - } - - return false; - } - - hstring ToString(Windows::Foundation::IPropertyValue value) - { - Windows::Foundation::PropertyType type = value.Type(); - if (type == Windows::Foundation::PropertyType::String) - { - return value.GetString(); - } - - std::wostringstream stream; - - switch (value.Type()) - { - case Windows::Foundation::PropertyType::UInt8: - stream << value.GetUInt8(); - break; - case Windows::Foundation::PropertyType::Int16: - stream << value.GetInt16(); - break; - case Windows::Foundation::PropertyType::UInt16: - stream << value.GetUInt16(); - break; - case Windows::Foundation::PropertyType::Int32: - stream << value.GetInt32(); - break; - case Windows::Foundation::PropertyType::UInt32: - stream << value.GetUInt32(); - break; - case Windows::Foundation::PropertyType::Int64: - stream << value.GetInt64(); - break; - case Windows::Foundation::PropertyType::UInt64: - stream << value.GetUInt64(); - break; - case Windows::Foundation::PropertyType::Single: - stream << value.GetSingle(); - break; - case Windows::Foundation::PropertyType::Double: - stream << value.GetDouble(); - break; - case Windows::Foundation::PropertyType::Char16: - stream << value.GetChar16(); - break; - case Windows::Foundation::PropertyType::Boolean: - stream << value.GetBoolean() ? L"true" : L"false"; - break; - } - - return hstring{ stream.str() }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include +#include "ArgumentValidation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void EnsureSupportedType(Windows::Foundation::PropertyType type) + { + switch (type) + { + case Windows::Foundation::PropertyType::UInt8: + case Windows::Foundation::PropertyType::Int16: + case Windows::Foundation::PropertyType::UInt16: + case Windows::Foundation::PropertyType::Int32: + case Windows::Foundation::PropertyType::UInt32: + case Windows::Foundation::PropertyType::Int64: + case Windows::Foundation::PropertyType::UInt64: + case Windows::Foundation::PropertyType::Single: + case Windows::Foundation::PropertyType::Double: + case Windows::Foundation::PropertyType::Char16: + case Windows::Foundation::PropertyType::Boolean: + case Windows::Foundation::PropertyType::String: + case Windows::Foundation::PropertyType::Inspectable: + case Windows::Foundation::PropertyType::DateTime: + case Windows::Foundation::PropertyType::TimeSpan: + case Windows::Foundation::PropertyType::Guid: + case Windows::Foundation::PropertyType::UInt8Array: + case Windows::Foundation::PropertyType::Int16Array: + case Windows::Foundation::PropertyType::UInt16Array: + case Windows::Foundation::PropertyType::Int32Array: + case Windows::Foundation::PropertyType::UInt32Array: + case Windows::Foundation::PropertyType::Int64Array: + case Windows::Foundation::PropertyType::UInt64Array: + case Windows::Foundation::PropertyType::SingleArray: + case Windows::Foundation::PropertyType::DoubleArray: + case Windows::Foundation::PropertyType::Char16Array: + case Windows::Foundation::PropertyType::BooleanArray: + case Windows::Foundation::PropertyType::StringArray: + case Windows::Foundation::PropertyType::InspectableArray: + case Windows::Foundation::PropertyType::DateTimeArray: + case Windows::Foundation::PropertyType::TimeSpanArray: + case Windows::Foundation::PropertyType::GuidArray: + return; + } + + THROW_HR(E_INVALIDARG); + } + + bool IsValidObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type) + { + auto propertyValue = value.try_as(); + + // If the type is an object, it is acceptable for the value to be a ValueSet directly + if (type == Windows::Foundation::PropertyType::Inspectable && + (propertyValue || value.try_as())) + { + return true; + } + + // If it wasn't an object type and a ValueSet, it must be an IPropertyValue + if (!propertyValue) + { + return false; + } + + // If it is an IPropertyValue, it must have the required type + return (propertyValue.Type() == type); + } + + void EnsureObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type) + { + THROW_HR_IF(E_INVALIDARG, !IsValidObjectType(value, type)); + } + + bool IsComparableType(Windows::Foundation::PropertyType type) + { + switch (type) + { + case Windows::Foundation::PropertyType::UInt8: + case Windows::Foundation::PropertyType::Int16: + case Windows::Foundation::PropertyType::UInt16: + case Windows::Foundation::PropertyType::Int32: + case Windows::Foundation::PropertyType::UInt32: + case Windows::Foundation::PropertyType::Int64: + case Windows::Foundation::PropertyType::UInt64: + case Windows::Foundation::PropertyType::Single: + case Windows::Foundation::PropertyType::Double: + case Windows::Foundation::PropertyType::Char16: + case Windows::Foundation::PropertyType::DateTime: + case Windows::Foundation::PropertyType::TimeSpan: + return true; + } + + return false; + } + + void EnsureComparableType(Windows::Foundation::PropertyType type) + { + THROW_HR_IF(E_INVALIDARG, !IsComparableType(type)); + } + + bool IsLengthType(Windows::Foundation::PropertyType type) + { + switch (type) + { + case Windows::Foundation::PropertyType::String: + case Windows::Foundation::PropertyType::UInt8Array: + case Windows::Foundation::PropertyType::Int16Array: + case Windows::Foundation::PropertyType::UInt16Array: + case Windows::Foundation::PropertyType::Int32Array: + case Windows::Foundation::PropertyType::UInt32Array: + case Windows::Foundation::PropertyType::Int64Array: + case Windows::Foundation::PropertyType::UInt64Array: + case Windows::Foundation::PropertyType::SingleArray: + case Windows::Foundation::PropertyType::DoubleArray: + case Windows::Foundation::PropertyType::Char16Array: + case Windows::Foundation::PropertyType::BooleanArray: + case Windows::Foundation::PropertyType::StringArray: + case Windows::Foundation::PropertyType::InspectableArray: + case Windows::Foundation::PropertyType::DateTimeArray: + case Windows::Foundation::PropertyType::TimeSpanArray: + case Windows::Foundation::PropertyType::GuidArray: + case Windows::Foundation::PropertyType::PointArray: + case Windows::Foundation::PropertyType::SizeArray: + case Windows::Foundation::PropertyType::RectArray: + case Windows::Foundation::PropertyType::OtherTypeArray: + return true; + } + + return false; + } + + void EnsureLengthType(Windows::Foundation::PropertyType type) + { + THROW_HR_IF(E_INVALIDARG, !IsLengthType(type)); + } + + bool IsStringableType(Windows::Foundation::PropertyType type) + { + switch (type) + { + case Windows::Foundation::PropertyType::UInt8: + case Windows::Foundation::PropertyType::Int16: + case Windows::Foundation::PropertyType::UInt16: + case Windows::Foundation::PropertyType::Int32: + case Windows::Foundation::PropertyType::UInt32: + case Windows::Foundation::PropertyType::Int64: + case Windows::Foundation::PropertyType::UInt64: + case Windows::Foundation::PropertyType::Single: + case Windows::Foundation::PropertyType::Double: + case Windows::Foundation::PropertyType::Char16: + case Windows::Foundation::PropertyType::Boolean: + case Windows::Foundation::PropertyType::String: + return true; + } + + return false; + } + + hstring ToString(Windows::Foundation::IPropertyValue value) + { + Windows::Foundation::PropertyType type = value.Type(); + if (type == Windows::Foundation::PropertyType::String) + { + return value.GetString(); + } + + std::wostringstream stream; + + switch (value.Type()) + { + case Windows::Foundation::PropertyType::UInt8: + stream << value.GetUInt8(); + break; + case Windows::Foundation::PropertyType::Int16: + stream << value.GetInt16(); + break; + case Windows::Foundation::PropertyType::UInt16: + stream << value.GetUInt16(); + break; + case Windows::Foundation::PropertyType::Int32: + stream << value.GetInt32(); + break; + case Windows::Foundation::PropertyType::UInt32: + stream << value.GetUInt32(); + break; + case Windows::Foundation::PropertyType::Int64: + stream << value.GetInt64(); + break; + case Windows::Foundation::PropertyType::UInt64: + stream << value.GetUInt64(); + break; + case Windows::Foundation::PropertyType::Single: + stream << value.GetSingle(); + break; + case Windows::Foundation::PropertyType::Double: + stream << value.GetDouble(); + break; + case Windows::Foundation::PropertyType::Char16: + stream << value.GetChar16(); + break; + case Windows::Foundation::PropertyType::Boolean: + stream << value.GetBoolean() ? L"true" : L"false"; + break; + } + + return hstring{ stream.str() }; + } +} diff --git a/src/Microsoft.Management.Configuration/ArgumentValidation.h b/src/Microsoft.Management.Configuration/ArgumentValidation.h index 83cddfff72..91383dd37e 100644 --- a/src/Microsoft.Management.Configuration/ArgumentValidation.h +++ b/src/Microsoft.Management.Configuration/ArgumentValidation.h @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Ensures that the given type is supported. - void EnsureSupportedType(Windows::Foundation::PropertyType type); - - // Ensures that the value object matches the expected property type. - bool IsValidObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type); - - // Ensures that the value object matches the expected property type. - void EnsureObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type); - - // Determines if the given type supports comparison. - bool IsComparableType(Windows::Foundation::PropertyType type); - - // Ensures that the given type supports comparison. - void EnsureComparableType(Windows::Foundation::PropertyType type); - - // Determines if the given type supports length restrictions. - bool IsLengthType(Windows::Foundation::PropertyType type); - - // Ensures that the given type supports length restrictions. - void EnsureLengthType(Windows::Foundation::PropertyType type); - - // Determines if the given type is a scalar that can be converted to a reasonable string representation. - bool IsStringableType(Windows::Foundation::PropertyType type); - - // Gets the string version of the given property, if it is stringable. - hstring ToString(Windows::Foundation::IPropertyValue value); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Ensures that the given type is supported. + void EnsureSupportedType(Windows::Foundation::PropertyType type); + + // Ensures that the value object matches the expected property type. + bool IsValidObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type); + + // Ensures that the value object matches the expected property type. + void EnsureObjectType(Windows::Foundation::IInspectable const& value, Windows::Foundation::PropertyType type); + + // Determines if the given type supports comparison. + bool IsComparableType(Windows::Foundation::PropertyType type); + + // Ensures that the given type supports comparison. + void EnsureComparableType(Windows::Foundation::PropertyType type); + + // Determines if the given type supports length restrictions. + bool IsLengthType(Windows::Foundation::PropertyType type); + + // Ensures that the given type supports length restrictions. + void EnsureLengthType(Windows::Foundation::PropertyType type); + + // Determines if the given type is a scalar that can be converted to a reasonable string representation. + bool IsStringableType(Windows::Foundation::PropertyType type); + + // Gets the string version of the given property, if it is stringable. + hstring ToString(Windows::Foundation::IPropertyValue value); +} diff --git a/src/Microsoft.Management.Configuration/ConfigThreadGlobals.cpp b/src/Microsoft.Management.Configuration/ConfigThreadGlobals.cpp index 71bd973117..f8bd51e0a7 100644 --- a/src/Microsoft.Management.Configuration/ConfigThreadGlobals.cpp +++ b/src/Microsoft.Management.Configuration/ConfigThreadGlobals.cpp @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigThreadGlobals.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - AppInstaller::Logging::DiagnosticLogger& ConfigThreadGlobals::GetDiagnosticLogger() - { - return m_logger; - } - - void* ConfigThreadGlobals::GetTelemetryObject() - { - return &m_telemetry; - } - - TelemetryTraceLogger& ConfigThreadGlobals::GetTelemetryLogger() - { - return m_telemetry; - } - - const TelemetryTraceLogger& ConfigThreadGlobals::GetTelemetryLogger() const - { - return m_telemetry; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigThreadGlobals.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + AppInstaller::Logging::DiagnosticLogger& ConfigThreadGlobals::GetDiagnosticLogger() + { + return m_logger; + } + + void* ConfigThreadGlobals::GetTelemetryObject() + { + return &m_telemetry; + } + + TelemetryTraceLogger& ConfigThreadGlobals::GetTelemetryLogger() + { + return m_telemetry; + } + + const TelemetryTraceLogger& ConfigThreadGlobals::GetTelemetryLogger() const + { + return m_telemetry; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigThreadGlobals.h b/src/Microsoft.Management.Configuration/ConfigThreadGlobals.h index 66f51458df..add0d6c15e 100644 --- a/src/Microsoft.Management.Configuration/ConfigThreadGlobals.h +++ b/src/Microsoft.Management.Configuration/ConfigThreadGlobals.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Interface for access to values that are stored on a per-thread object. - struct ConfigThreadGlobals : public AppInstaller::ThreadLocalStorage::ThreadGlobals - { - ConfigThreadGlobals() = default; - virtual ~ConfigThreadGlobals() = default; - - AppInstaller::Logging::DiagnosticLogger& GetDiagnosticLogger() override; - - void* GetTelemetryObject() override; - - TelemetryTraceLogger& GetTelemetryLogger(); - const TelemetryTraceLogger& GetTelemetryLogger() const; - - protected: - AppInstaller::Logging::DiagnosticLogger m_logger; - TelemetryTraceLogger m_telemetry; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Interface for access to values that are stored on a per-thread object. + struct ConfigThreadGlobals : public AppInstaller::ThreadLocalStorage::ThreadGlobals + { + ConfigThreadGlobals() = default; + virtual ~ConfigThreadGlobals() = default; + + AppInstaller::Logging::DiagnosticLogger& GetDiagnosticLogger() override; + + void* GetTelemetryObject() override; + + TelemetryTraceLogger& GetTelemetryLogger(); + const TelemetryTraceLogger& GetTelemetryLogger() const; + + protected: + AppInstaller::Logging::DiagnosticLogger m_logger; + TelemetryTraceLogger m_telemetry; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationChangeData.cpp b/src/Microsoft.Management.Configuration/ConfigurationChangeData.cpp index b4b020e693..001b0a3568 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationChangeData.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationChangeData.cpp @@ -1,30 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationChangeData.h" -#include "ConfigurationChangeData.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationChangeData.h" +#include "ConfigurationChangeData.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ void ConfigurationChangeData::Initialize(ConfigurationChangeEventType change, guid instanceIdentifier, ConfigurationSetState state) { m_change = change; m_instanceIdentifier = instanceIdentifier; m_state = state; } - + ConfigurationChangeEventType ConfigurationChangeData::Change() { return m_change; } - + guid ConfigurationChangeData::InstanceIdentifier() { return m_instanceIdentifier; } - + ConfigurationSetState ConfigurationChangeData::State() { return m_state; - } -} + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationChangeData.h b/src/Microsoft.Management.Configuration/ConfigurationChangeData.h index 5bae6952ff..811bbfb78b 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationChangeData.h +++ b/src/Microsoft.Management.Configuration/ConfigurationChangeData.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationChangeData.g.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationChangeData : ConfigurationChangeDataT - { - ConfigurationChangeData() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(ConfigurationChangeEventType change, guid instanceIdentifier, ConfigurationSetState state); -#endif - - ConfigurationChangeEventType Change(); - guid InstanceIdentifier(); - ConfigurationSetState State(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ConfigurationChangeEventType m_change{}; - guid m_instanceIdentifier{}; - ConfigurationSetState m_state{}; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationChangeData.g.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationChangeData : ConfigurationChangeDataT + { + ConfigurationChangeData() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(ConfigurationChangeEventType change, guid instanceIdentifier, ConfigurationSetState state); +#endif + + ConfigurationChangeEventType Change(); + guid InstanceIdentifier(); + ConfigurationSetState State(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationChangeEventType m_change{}; + guid m_instanceIdentifier{}; + ConfigurationSetState m_state{}; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationConflict.cpp b/src/Microsoft.Management.Configuration/ConfigurationConflict.cpp index 6d945ba068..07a1d30c9e 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationConflict.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationConflict.cpp @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationConflict.h" -#include "ConfigurationConflict.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationConflict.h" +#include "ConfigurationConflict.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ void ConfigurationConflict::Initialize(ConfigurationConflictType conflict) { m_conflict = conflict; } - + ConfigurationConflictType ConfigurationConflict::Conflict() { return m_conflict; } - + ConfigurationSet ConfigurationConflict::FirstSet() { return m_firstSet; } - + ConfigurationSet ConfigurationConflict::SecondSet() { return m_secondSet; } - + ConfigurationUnit ConfigurationConflict::FirstUnit() { return m_firstUnit; } - + ConfigurationUnit ConfigurationConflict::SecondUnit() { return m_secondUnit; } - + Windows::Foundation::Collections::IVectorView ConfigurationConflict::Settings() { if (m_settings) @@ -46,5 +46,5 @@ namespace winrt::Microsoft::Management::Configuration::implementation { return nullptr; } - } -} + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationConflict.h b/src/Microsoft.Management.Configuration/ConfigurationConflict.h index 6956916df5..8016211f00 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationConflict.h +++ b/src/Microsoft.Management.Configuration/ConfigurationConflict.h @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationConflict.g.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationConflict : ConfigurationConflictT - { - ConfigurationConflict() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(ConfigurationConflictType conflict); -#endif - - ConfigurationConflictType Conflict(); - ConfigurationSet FirstSet(); - ConfigurationSet SecondSet(); - ConfigurationUnit FirstUnit(); - ConfigurationUnit SecondUnit(); - Windows::Foundation::Collections::IVectorView Settings(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ConfigurationConflictType m_conflict{}; - ConfigurationSet m_firstSet = nullptr; - ConfigurationSet m_secondSet = nullptr; - ConfigurationUnit m_firstUnit = nullptr; - ConfigurationUnit m_secondUnit = nullptr; - Windows::Foundation::Collections::IVector m_settings = nullptr; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationConflict.g.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationConflict : ConfigurationConflictT + { + ConfigurationConflict() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(ConfigurationConflictType conflict); +#endif + + ConfigurationConflictType Conflict(); + ConfigurationSet FirstSet(); + ConfigurationSet SecondSet(); + ConfigurationUnit FirstUnit(); + ConfigurationUnit SecondUnit(); + Windows::Foundation::Collections::IVectorView Settings(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationConflictType m_conflict{}; + ConfigurationSet m_firstSet = nullptr; + ConfigurationSet m_secondSet = nullptr; + ConfigurationUnit m_firstUnit = nullptr; + ConfigurationUnit m_secondUnit = nullptr; + Windows::Foundation::Collections::IVector m_settings = nullptr; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.cpp b/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.cpp index a68546d625..dbaa7cba1a 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.cpp @@ -1,30 +1,30 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationConflictSetting.h" -#include "ConfigurationConflictSetting.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - void ConfigurationConflictSetting::Initialize(std::wstring_view name, const IInspectable& firstValue, const IInspectable& secondValue) - { - m_name = name; - m_firstValue = firstValue; - m_secondValue = secondValue; - } - - hstring ConfigurationConflictSetting::Name() - { - return m_name; - } - - ConfigurationConflictSetting::IInspectable ConfigurationConflictSetting::FirstValue() - { - return m_firstValue; - } - - ConfigurationConflictSetting::IInspectable ConfigurationConflictSetting::SecondValue() - { - return m_secondValue; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationConflictSetting.h" +#include "ConfigurationConflictSetting.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void ConfigurationConflictSetting::Initialize(std::wstring_view name, const IInspectable& firstValue, const IInspectable& secondValue) + { + m_name = name; + m_firstValue = firstValue; + m_secondValue = secondValue; + } + + hstring ConfigurationConflictSetting::Name() + { + return m_name; + } + + ConfigurationConflictSetting::IInspectable ConfigurationConflictSetting::FirstValue() + { + return m_firstValue; + } + + ConfigurationConflictSetting::IInspectable ConfigurationConflictSetting::SecondValue() + { + return m_secondValue; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.h b/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.h index bf790906fc..1b49e69f3a 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.h +++ b/src/Microsoft.Management.Configuration/ConfigurationConflictSetting.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationConflictSetting.g.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationConflictSetting : ConfigurationConflictSettingT - { - ConfigurationConflictSetting() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(std::wstring_view name, const IInspectable& firstValue, const IInspectable& secondValue); -#endif - - hstring Name(); - IInspectable FirstValue(); - IInspectable SecondValue(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - hstring m_name; - IInspectable m_firstValue = nullptr; - IInspectable m_secondValue = nullptr; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationConflictSetting.g.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationConflictSetting : ConfigurationConflictSettingT + { + ConfigurationConflictSetting() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(std::wstring_view name, const IInspectable& firstValue, const IInspectable& secondValue); +#endif + + hstring Name(); + IInspectable FirstValue(); + IInspectable SecondValue(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + hstring m_name; + IInspectable m_firstValue = nullptr; + IInspectable m_secondValue = nullptr; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationEnvironment.cpp b/src/Microsoft.Management.Configuration/ConfigurationEnvironment.cpp index 19beebe3d4..7a0017cb54 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationEnvironment.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationEnvironment.cpp @@ -1,167 +1,167 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationEnvironment.h" -#include "ConfigurationEnvironment.g.cpp" -#include "ArgumentValidation.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - template - void DeepCopyEnvironmentFrom(implementation::ConfigurationEnvironment& self, const T& toDeepCopy) - { - self.Context(toDeepCopy.Context()); - self.ProcessorIdentifier(toDeepCopy.ProcessorIdentifier()); - self.ProcessorProperties(toDeepCopy.ProcessorProperties()); - } - } - - ConfigurationEnvironment::ConfigurationEnvironment() : - m_processorProperties(multi_threaded_map()) - {} - - ConfigurationEnvironment::ConfigurationEnvironment(SecurityContext context, const std::wstring& processorIdentifier, std::map&& processorProperties) : - m_context(context), m_processorIdentifier(processorIdentifier), m_processorProperties(multi_threaded_map(std::move(processorProperties))) - {} - - ConfigurationEnvironment::ConfigurationEnvironment(const implementation::ConfigurationEnvironment& toDeepCopy) - { - DeepCopy(toDeepCopy); - } - - ConfigurationEnvironment::ConfigurationEnvironment(const Configuration::ConfigurationEnvironment& toDeepCopy) - { - DeepCopyEnvironmentFrom(*this, toDeepCopy); - } - - SecurityContext ConfigurationEnvironment::Context() const - { - return m_context; - } - - void ConfigurationEnvironment::Context(SecurityContext value) - { - m_context = value; - } - - hstring ConfigurationEnvironment::ProcessorIdentifier() const - { - return m_processorIdentifier; - } - - void ConfigurationEnvironment::ProcessorIdentifier(const hstring& value) - { - m_processorIdentifier = value; - } - - Windows::Foundation::Collections::IMap ConfigurationEnvironment::ProcessorProperties() const - { - return m_processorProperties; - } - - void ConfigurationEnvironment::DeepCopy(const implementation::ConfigurationEnvironment& toDeepCopy) - { - DeepCopyEnvironmentFrom(*this, toDeepCopy); - } - - void ConfigurationEnvironment::ProcessorProperties(const Windows::Foundation::Collections::IMap& values) - { - std::map properties; - for (const auto& property : values) - { - properties.emplace(property.Key(), property.Value()); - } - m_processorProperties = multi_threaded_map(std::move(properties)); - } - - void ConfigurationEnvironment::ProcessorProperties(const Windows::Foundation::Collections::ValueSet& values) - { - std::map properties; - - for (const auto& value : values) - { - Windows::Foundation::IPropertyValue property = value.Value().try_as(); - if (property && IsStringableType(property.Type())) - { - properties.emplace(value.Key(), ToString(property)); - } - } - - m_processorProperties = multi_threaded_map(std::move(properties)); - } - - bool ConfigurationEnvironment::IsDefault() const - { - return m_context == SecurityContext::Current && m_processorIdentifier.empty() && m_processorProperties.Size() == 0; - } - - com_ptr ConfigurationEnvironment::CalculateCommonEnvironment(const std::vector& environments) - { - com_ptr result; - - if (environments.empty()) - { - result = make_self(); - } - else - { - result = make_self(environments.front()); - - for (size_t i = 1; i < environments.size(); ++i) - { - const Configuration::ConfigurationEnvironment& environment = environments[i]; - - if (result->m_context != environment.Context()) - { - result->m_context = SecurityContext::Current; - } - - if (result->m_processorIdentifier != environment.ProcessorIdentifier()) - { - result->m_processorIdentifier.clear(); - } - - if (!AreEqual(result->m_processorProperties, environment.ProcessorProperties())) - { - result->m_processorProperties = single_threaded_map(); - } - - // Check if we have already found everything to be different - if (result->IsDefault()) - { - break; - } - } - } - - return result; - } - - bool ConfigurationEnvironment::AreEqual(const Windows::Foundation::Collections::IMap& a, const Windows::Foundation::Collections::IMap& b) - { - uint32_t a_size = a ? a.Size() : 0; - uint32_t b_size = b ? b.Size() : 0; - - if (a_size == 0 && b_size == 0) - { - return true; - } - else if (a_size != b_size) - { - return false; - } - - for (const auto& entry : a) - { - hstring key = entry.Key(); - if (!b.HasKey(key) || entry.Value() != b.Lookup(key)) - { - return false; - } - } - - return true; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationEnvironment.h" +#include "ConfigurationEnvironment.g.cpp" +#include "ArgumentValidation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + template + void DeepCopyEnvironmentFrom(implementation::ConfigurationEnvironment& self, const T& toDeepCopy) + { + self.Context(toDeepCopy.Context()); + self.ProcessorIdentifier(toDeepCopy.ProcessorIdentifier()); + self.ProcessorProperties(toDeepCopy.ProcessorProperties()); + } + } + + ConfigurationEnvironment::ConfigurationEnvironment() : + m_processorProperties(multi_threaded_map()) + {} + + ConfigurationEnvironment::ConfigurationEnvironment(SecurityContext context, const std::wstring& processorIdentifier, std::map&& processorProperties) : + m_context(context), m_processorIdentifier(processorIdentifier), m_processorProperties(multi_threaded_map(std::move(processorProperties))) + {} + + ConfigurationEnvironment::ConfigurationEnvironment(const implementation::ConfigurationEnvironment& toDeepCopy) + { + DeepCopy(toDeepCopy); + } + + ConfigurationEnvironment::ConfigurationEnvironment(const Configuration::ConfigurationEnvironment& toDeepCopy) + { + DeepCopyEnvironmentFrom(*this, toDeepCopy); + } + + SecurityContext ConfigurationEnvironment::Context() const + { + return m_context; + } + + void ConfigurationEnvironment::Context(SecurityContext value) + { + m_context = value; + } + + hstring ConfigurationEnvironment::ProcessorIdentifier() const + { + return m_processorIdentifier; + } + + void ConfigurationEnvironment::ProcessorIdentifier(const hstring& value) + { + m_processorIdentifier = value; + } + + Windows::Foundation::Collections::IMap ConfigurationEnvironment::ProcessorProperties() const + { + return m_processorProperties; + } + + void ConfigurationEnvironment::DeepCopy(const implementation::ConfigurationEnvironment& toDeepCopy) + { + DeepCopyEnvironmentFrom(*this, toDeepCopy); + } + + void ConfigurationEnvironment::ProcessorProperties(const Windows::Foundation::Collections::IMap& values) + { + std::map properties; + for (const auto& property : values) + { + properties.emplace(property.Key(), property.Value()); + } + m_processorProperties = multi_threaded_map(std::move(properties)); + } + + void ConfigurationEnvironment::ProcessorProperties(const Windows::Foundation::Collections::ValueSet& values) + { + std::map properties; + + for (const auto& value : values) + { + Windows::Foundation::IPropertyValue property = value.Value().try_as(); + if (property && IsStringableType(property.Type())) + { + properties.emplace(value.Key(), ToString(property)); + } + } + + m_processorProperties = multi_threaded_map(std::move(properties)); + } + + bool ConfigurationEnvironment::IsDefault() const + { + return m_context == SecurityContext::Current && m_processorIdentifier.empty() && m_processorProperties.Size() == 0; + } + + com_ptr ConfigurationEnvironment::CalculateCommonEnvironment(const std::vector& environments) + { + com_ptr result; + + if (environments.empty()) + { + result = make_self(); + } + else + { + result = make_self(environments.front()); + + for (size_t i = 1; i < environments.size(); ++i) + { + const Configuration::ConfigurationEnvironment& environment = environments[i]; + + if (result->m_context != environment.Context()) + { + result->m_context = SecurityContext::Current; + } + + if (result->m_processorIdentifier != environment.ProcessorIdentifier()) + { + result->m_processorIdentifier.clear(); + } + + if (!AreEqual(result->m_processorProperties, environment.ProcessorProperties())) + { + result->m_processorProperties = single_threaded_map(); + } + + // Check if we have already found everything to be different + if (result->IsDefault()) + { + break; + } + } + } + + return result; + } + + bool ConfigurationEnvironment::AreEqual(const Windows::Foundation::Collections::IMap& a, const Windows::Foundation::Collections::IMap& b) + { + uint32_t a_size = a ? a.Size() : 0; + uint32_t b_size = b ? b.Size() : 0; + + if (a_size == 0 && b_size == 0) + { + return true; + } + else if (a_size != b_size) + { + return false; + } + + for (const auto& entry : a) + { + hstring key = entry.Key(); + if (!b.HasKey(key) || entry.Value() != b.Lookup(key)) + { + return false; + } + } + + return true; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationEnvironment.h b/src/Microsoft.Management.Configuration/ConfigurationEnvironment.h index 9c93ca91b1..a70120754f 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationEnvironment.h +++ b/src/Microsoft.Management.Configuration/ConfigurationEnvironment.h @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationEnvironment.g.h" -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationEnvironment : ConfigurationEnvironmentT, AppInstaller::WinRT::ModuleCountBase - { - ConfigurationEnvironment(); - ConfigurationEnvironment(SecurityContext context, const std::wstring& processorIdentifier, std::map&& processorProperties); - ConfigurationEnvironment(const implementation::ConfigurationEnvironment& toDeepCopy); - ConfigurationEnvironment(const Configuration::ConfigurationEnvironment& toDeepCopy); - - SecurityContext Context() const; - void Context(SecurityContext value); - - hstring ProcessorIdentifier() const; - void ProcessorIdentifier(const hstring& value); - - Windows::Foundation::Collections::IMap ProcessorProperties() const; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - // Copies the values from the given environment, including making a new map of the properties. - void DeepCopy(const implementation::ConfigurationEnvironment& toDeepCopy); - - // Copies the properties from the given map. - void ProcessorProperties(const Windows::Foundation::Collections::IMap& values); - - // Copies the scalar properties from the given ValueSet. - // Ignores all values that cannot be converted to a string. - void ProcessorProperties(const Windows::Foundation::Collections::ValueSet& values); - - // Determines if this environment represents the default environment. - bool IsDefault() const; - - // Creates an environment, setting only fields that are identical between all given environments. - static com_ptr CalculateCommonEnvironment(const std::vector& environments); - - // Checks if the two given properties maps are equal. - static bool AreEqual(const Windows::Foundation::Collections::IMap& a, const Windows::Foundation::Collections::IMap& b); - - private: - SecurityContext m_context = SecurityContext::Current; - hstring m_processorIdentifier; - Windows::Foundation::Collections::IMap m_processorProperties; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationEnvironment.g.h" +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationEnvironment : ConfigurationEnvironmentT, AppInstaller::WinRT::ModuleCountBase + { + ConfigurationEnvironment(); + ConfigurationEnvironment(SecurityContext context, const std::wstring& processorIdentifier, std::map&& processorProperties); + ConfigurationEnvironment(const implementation::ConfigurationEnvironment& toDeepCopy); + ConfigurationEnvironment(const Configuration::ConfigurationEnvironment& toDeepCopy); + + SecurityContext Context() const; + void Context(SecurityContext value); + + hstring ProcessorIdentifier() const; + void ProcessorIdentifier(const hstring& value); + + Windows::Foundation::Collections::IMap ProcessorProperties() const; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + // Copies the values from the given environment, including making a new map of the properties. + void DeepCopy(const implementation::ConfigurationEnvironment& toDeepCopy); + + // Copies the properties from the given map. + void ProcessorProperties(const Windows::Foundation::Collections::IMap& values); + + // Copies the scalar properties from the given ValueSet. + // Ignores all values that cannot be converted to a string. + void ProcessorProperties(const Windows::Foundation::Collections::ValueSet& values); + + // Determines if this environment represents the default environment. + bool IsDefault() const; + + // Creates an environment, setting only fields that are identical between all given environments. + static com_ptr CalculateCommonEnvironment(const std::vector& environments); + + // Checks if the two given properties maps are equal. + static bool AreEqual(const Windows::Foundation::Collections::IMap& a, const Windows::Foundation::Collections::IMap& b); + + private: + SecurityContext m_context = SecurityContext::Current; + hstring m_processorIdentifier; + Windows::Foundation::Collections::IMap m_processorProperties; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationParameter.cpp b/src/Microsoft.Management.Configuration/ConfigurationParameter.cpp index c1c5f64e44..629e01102f 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationParameter.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationParameter.cpp @@ -1,176 +1,176 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationParameter.h" -#include "ConfigurationParameter.g.cpp" -#include "ArgumentValidation.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - hstring ConfigurationParameter::Name() const - { - return m_name; - } - - void ConfigurationParameter::Name(hstring const& value) - { - m_name = value; - } - - hstring ConfigurationParameter::Description() const - { - return m_description; - } - - void ConfigurationParameter::Description(hstring const& value) - { - m_description = value; - } - - Windows::Foundation::Collections::ValueSet ConfigurationParameter::Metadata() const - { - return m_metadata; - } - - void ConfigurationParameter::Metadata(const Windows::Foundation::Collections::ValueSet& value) - { - THROW_HR_IF(E_POINTER, !value); - m_metadata = value; - } - - bool ConfigurationParameter::IsSecure() const - { - return m_isSecure; - } - - void ConfigurationParameter::IsSecure(bool value) - { - m_isSecure = value; - } - - Windows::Foundation::PropertyType ConfigurationParameter::Type() const - { - return m_type; - } - - void ConfigurationParameter::Type(Windows::Foundation::PropertyType value) - { - EnsureSupportedType(value); - m_type = value; - } - - Windows::Foundation::IInspectable ConfigurationParameter::DefaultValue() const - { - return m_defaultValue; - } - - void ConfigurationParameter::DefaultValue(Windows::Foundation::IInspectable const& value) - { - if (value) - { - EnsureObjectType(value, m_type); - } - - m_defaultValue = value; - } - - Windows::Foundation::Collections::IVector ConfigurationParameter::AllowedValues() const - { - return m_allowedValues; - } - - void ConfigurationParameter::AllowedValues(Windows::Foundation::Collections::IVector const& value) - { - if (value) - { - for (const auto& item : value) - { - if (item) - { - EnsureObjectType(item, m_type); - } - } - } - - m_allowedValues = value; - } - - void ConfigurationParameter::AllowedValues(std::vector&& value) - { - m_allowedValues = winrt::multi_threaded_vector(std::move(value)); - } - - uint32_t ConfigurationParameter::MinimumLength() const - { - return m_minimumLength; - } - - void ConfigurationParameter::MinimumLength(uint32_t value) - { - EnsureLengthType(m_type); - m_minimumLength = value; - } - - uint32_t ConfigurationParameter::MaximumLength() const - { - return m_maximumLength; - } - - void ConfigurationParameter::MaximumLength(uint32_t value) - { - EnsureLengthType(m_type); - m_maximumLength = value; - } - - Windows::Foundation::IInspectable ConfigurationParameter::MinimumValue() const - { - return m_minimumValue; - } - - void ConfigurationParameter::MinimumValue(Windows::Foundation::IInspectable const& value) - { - if (value) - { - EnsureObjectType(value, m_type); - EnsureComparableType(m_type); - } - - m_minimumValue = value; - } - - Windows::Foundation::IInspectable ConfigurationParameter::MaximumValue() const - { - return m_maximumValue; - } - - void ConfigurationParameter::MaximumValue(Windows::Foundation::IInspectable const& value) - { - if (value) - { - EnsureObjectType(value, m_type); - EnsureComparableType(m_type); - } - - m_maximumValue = value; - } - - Windows::Foundation::IInspectable ConfigurationParameter::ProvidedValue() const - { - return m_providedValue; - } - - void ConfigurationParameter::ProvidedValue(Windows::Foundation::IInspectable const& value) - { - if (value) - { - EnsureObjectType(value, m_type); - } - - m_providedValue = value; - } - - HRESULT STDMETHODCALLTYPE ConfigurationParameter::SetLifetimeWatcher(IUnknown* watcher) - { - return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationParameter.h" +#include "ConfigurationParameter.g.cpp" +#include "ArgumentValidation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + hstring ConfigurationParameter::Name() const + { + return m_name; + } + + void ConfigurationParameter::Name(hstring const& value) + { + m_name = value; + } + + hstring ConfigurationParameter::Description() const + { + return m_description; + } + + void ConfigurationParameter::Description(hstring const& value) + { + m_description = value; + } + + Windows::Foundation::Collections::ValueSet ConfigurationParameter::Metadata() const + { + return m_metadata; + } + + void ConfigurationParameter::Metadata(const Windows::Foundation::Collections::ValueSet& value) + { + THROW_HR_IF(E_POINTER, !value); + m_metadata = value; + } + + bool ConfigurationParameter::IsSecure() const + { + return m_isSecure; + } + + void ConfigurationParameter::IsSecure(bool value) + { + m_isSecure = value; + } + + Windows::Foundation::PropertyType ConfigurationParameter::Type() const + { + return m_type; + } + + void ConfigurationParameter::Type(Windows::Foundation::PropertyType value) + { + EnsureSupportedType(value); + m_type = value; + } + + Windows::Foundation::IInspectable ConfigurationParameter::DefaultValue() const + { + return m_defaultValue; + } + + void ConfigurationParameter::DefaultValue(Windows::Foundation::IInspectable const& value) + { + if (value) + { + EnsureObjectType(value, m_type); + } + + m_defaultValue = value; + } + + Windows::Foundation::Collections::IVector ConfigurationParameter::AllowedValues() const + { + return m_allowedValues; + } + + void ConfigurationParameter::AllowedValues(Windows::Foundation::Collections::IVector const& value) + { + if (value) + { + for (const auto& item : value) + { + if (item) + { + EnsureObjectType(item, m_type); + } + } + } + + m_allowedValues = value; + } + + void ConfigurationParameter::AllowedValues(std::vector&& value) + { + m_allowedValues = winrt::multi_threaded_vector(std::move(value)); + } + + uint32_t ConfigurationParameter::MinimumLength() const + { + return m_minimumLength; + } + + void ConfigurationParameter::MinimumLength(uint32_t value) + { + EnsureLengthType(m_type); + m_minimumLength = value; + } + + uint32_t ConfigurationParameter::MaximumLength() const + { + return m_maximumLength; + } + + void ConfigurationParameter::MaximumLength(uint32_t value) + { + EnsureLengthType(m_type); + m_maximumLength = value; + } + + Windows::Foundation::IInspectable ConfigurationParameter::MinimumValue() const + { + return m_minimumValue; + } + + void ConfigurationParameter::MinimumValue(Windows::Foundation::IInspectable const& value) + { + if (value) + { + EnsureObjectType(value, m_type); + EnsureComparableType(m_type); + } + + m_minimumValue = value; + } + + Windows::Foundation::IInspectable ConfigurationParameter::MaximumValue() const + { + return m_maximumValue; + } + + void ConfigurationParameter::MaximumValue(Windows::Foundation::IInspectable const& value) + { + if (value) + { + EnsureObjectType(value, m_type); + EnsureComparableType(m_type); + } + + m_maximumValue = value; + } + + Windows::Foundation::IInspectable ConfigurationParameter::ProvidedValue() const + { + return m_providedValue; + } + + void ConfigurationParameter::ProvidedValue(Windows::Foundation::IInspectable const& value) + { + if (value) + { + EnsureObjectType(value, m_type); + } + + m_providedValue = value; + } + + HRESULT STDMETHODCALLTYPE ConfigurationParameter::SetLifetimeWatcher(IUnknown* watcher) + { + return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp index 7ecdbc5ce3..add32e6b9a 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationProcessor.cpp @@ -1,1210 +1,1210 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationProcessor.h" -#include "ConfigurationProcessor.g.cpp" -#include "ConfigurationSet.h" -#include "OpenConfigurationSetResult.h" -#include "ConfigurationSetParser.h" -#include "DiagnosticInformationInstance.h" -#include "ApplyConfigurationSetResult.h" -#include "ApplyConfigurationUnitResult.h" -#include "TestConfigurationSetResult.h" -#include "TestConfigurationUnitResult.h" -#include "ConfigurationUnitResultInformation.h" -#include "GetConfigurationUnitSettingsResult.h" -#include "GetAllConfigurationUnitSettingsResult.h" -#include "GetAllConfigurationUnitsResult.h" -#include "ExceptionResultHelpers.h" -#include "ConfigurationSetChangeData.h" -#include "GetConfigurationUnitDetailsResult.h" -#include "GetConfigurationSetDetailsResult.h" -#include "DefaultSetGroupProcessor.h" -#include "ConfigurationSequencer.h" -#include "ConfigurationStatus.h" - -#include -#include -#include -#include - -using namespace std::chrono_literals; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - AppInstaller::Logging::Level ConvertLevel(DiagnosticLevel level) - { - switch (level) - { - case DiagnosticLevel::Verbose: return AppInstaller::Logging::Level::Verbose; - case DiagnosticLevel::Informational: return AppInstaller::Logging::Level::Info; - case DiagnosticLevel::Warning: return AppInstaller::Logging::Level::Warning; - case DiagnosticLevel::Error: return AppInstaller::Logging::Level::Error; - case DiagnosticLevel::Critical: return AppInstaller::Logging::Level::Crit; - default: return AppInstaller::Logging::Level::Warning; - } - } - - DiagnosticLevel ConvertLevel(AppInstaller::Logging::Level level) - { - switch (level) - { - case AppInstaller::Logging::Level::Verbose: return DiagnosticLevel::Verbose; - case AppInstaller::Logging::Level::Info: return DiagnosticLevel::Informational; - case AppInstaller::Logging::Level::Warning: return DiagnosticLevel::Warning; - case AppInstaller::Logging::Level::Error: return DiagnosticLevel::Error; - case AppInstaller::Logging::Level::Crit: return DiagnosticLevel::Critical; - default: return DiagnosticLevel::Warning; - } - } - - // ILogger that sends data back to the Diagnostics event of the ConfigurationProcessor. - struct ConfigurationProcessorDiagnosticsLogger : public AppInstaller::Logging::ILogger - { - ConfigurationProcessorDiagnosticsLogger(ConfigurationProcessor& processor) : m_processor(processor) {} - - std::string GetName() const override - { - return "ConfigurationProcessorDiagnosticsLogger"; - } - - void Write(AppInstaller::Logging::Channel channel, AppInstaller::Logging::Level level, std::string_view message) noexcept override try - { - std::ostringstream strstr; - strstr << '[' << AppInstaller::Logging::GetChannelName(channel) << "] " << message; - m_processor.SendDiagnostics(ConvertLevel(level), strstr.str()); - } - catch (...) {} - - void WriteDirect(AppInstaller::Logging::Channel, AppInstaller::Logging::Level level, std::string_view message) noexcept override try - { - m_processor.SendDiagnostics(ConvertLevel(level), message); - } - catch (...) {} - - private: - ConfigurationProcessor& m_processor; - }; - - // Helper to ensure a one-time callback attach - struct AttachWilFailureCallback - { - AttachWilFailureCallback() - { - wil::SetResultLoggingCallback(wilResultLoggingCallback); - } - - ~AttachWilFailureCallback() = default; - - static void __stdcall wilResultLoggingCallback(const wil::FailureInfo& info) noexcept - { - AICLI_LOG(Fail, Error, << [&]() { - wchar_t message[2048]; - GetFailureLogString(message, ARRAYSIZE(message), info); - return AppInstaller::Utility::ConvertToUTF8(message); - }()); - } - - static void Ensure() - { - static AttachWilFailureCallback s_callbackAttach; - } - }; - } - - ConfigurationProcessor::ConfigurationProcessor() - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::Configuration)); - - AppInstaller::Logging::DiagnosticLogger& logger = m_threadGlobals.GetDiagnosticLogger(); - logger.SetEnabledChannels(AppInstaller::Logging::Channel::All); - logger.SetLevel(AppInstaller::Logging::Level::Verbose); - logger.AddLogger(std::make_unique(*this)); - } - - ConfigurationProcessor::ConfigurationProcessor(const IConfigurationSetProcessorFactory& factory) : ConfigurationProcessor() - { - ConfigurationSetProcessorFactory(factory); - } - - event_token ConfigurationProcessor::Diagnostics(const Windows::Foundation::EventHandler& handler) - { - AttachWilFailureCallback::Ensure(); - return m_diagnostics.add(handler); - } - - void ConfigurationProcessor::Diagnostics(const event_token& token) noexcept - { - m_diagnostics.remove(token); - } - - DiagnosticLevel ConfigurationProcessor::MinimumLevel() - { - return m_minimumLevel; - } - - void ConfigurationProcessor::MinimumLevel(DiagnosticLevel value) - { - m_minimumLevel = value; - m_threadGlobals.GetDiagnosticLogger().SetLevel(ConvertLevel(value)); - if (m_factory) - { - m_factory.MinimumLevel(value); - } - } - - hstring ConfigurationProcessor::Caller() const - { - return hstring{ AppInstaller::Utility::ConvertToUTF16(m_threadGlobals.GetTelemetryLogger().GetCaller()) }; - } - - void ConfigurationProcessor::Caller(hstring value) - { - m_threadGlobals.GetTelemetryLogger().SetCaller(AppInstaller::Utility::ConvertToUTF8(value)); - } - - guid ConfigurationProcessor::ActivityIdentifier() - { - return *m_threadGlobals.GetTelemetryLogger().GetActivityId(); - } - - void ConfigurationProcessor::ActivityIdentifier(const guid& value) - { - m_threadGlobals.GetTelemetryLogger().SetActivityId(value); - } - - bool ConfigurationProcessor::GenerateTelemetryEvents() - { - return m_threadGlobals.GetTelemetryLogger().IsEnabled(); - } - - void ConfigurationProcessor::GenerateTelemetryEvents(bool value) - { - std::ignore = m_threadGlobals.GetTelemetryLogger().EnableRuntime(value); - } - - event_token ConfigurationProcessor::ConfigurationChange(const Windows::Foundation::TypedEventHandler& handler) - { - if (!m_configurationChange) - { - auto status = ConfigurationStatus::Instance(); - std::atomic_store(&m_changeRegistration, status->RegisterForChange(*this)); - } - - return m_configurationChange.add(handler); - } - - void ConfigurationProcessor::ConfigurationChange(const event_token& token) noexcept - { - m_configurationChange.remove(token); - - if (!m_configurationChange) - { - std::atomic_store(&m_changeRegistration, {}); - } - } - - void ConfigurationProcessor::ConfigurationChange(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data) try - { - m_configurationChange(set, data); - } - CATCH_LOG(); - - Windows::Foundation::Collections::IVector ConfigurationProcessor::GetConfigurationHistory() - { - return GetConfigurationHistoryImpl(); - } - - Windows::Foundation::IAsyncOperation> ConfigurationProcessor::GetConfigurationHistoryAsync() - { - auto strong_this{ get_strong() }; - co_await winrt::resume_background(); - co_return GetConfigurationHistoryImpl({ co_await winrt::get_cancellation_token() }); - } - - Configuration::OpenConfigurationSetResult ConfigurationProcessor::OpenConfigurationSet(const Windows::Storage::Streams::IInputStream& stream) - { - return OpenConfigurationSetAsync(stream).get(); - } - - Windows::Foundation::IAsyncOperation ConfigurationProcessor::OpenConfigurationSetAsync(const Windows::Storage::Streams::IInputStream& stream) - { - auto strong_this{ get_strong() }; - Windows::Storage::Streams::IInputStream localStream = stream; - - co_await winrt::resume_background(); - auto cancellation = co_await winrt::get_cancellation_token(); - - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - auto result = make_self>(); - - if (!localStream) - { - result->Initialize(E_POINTER, {}); - co_return *result; - } - - try - { - // Read the entire file into memory as we expect them to be small and - // our YAML parser doesn't support streaming at this time. - // This is done here to enable easy cancellation propagation to the stream reads. - uint32_t bufferSize = 1 << 20; - Windows::Storage::Streams::Buffer buffer(bufferSize); - - // Memory stream in mixed elevation does not support InputStreamOptions as flags. - Windows::Storage::Streams::InputStreamOptions readOptions = Windows::Storage::Streams::InputStreamOptions::Partial; - std::string inputString; - - for (;;) - { - auto asyncOperation = localStream.ReadAsync(buffer, bufferSize, readOptions); - - // Manually poll status and propagate cancellation to stay on this thread for thread globals - while (asyncOperation.Status() == Windows::Foundation::AsyncStatus::Started) - { - if (cancellation()) - { - asyncOperation.Cancel(); - } - - std::this_thread::sleep_for(100ms); - } - - Windows::Storage::Streams::IBuffer readBuffer = asyncOperation.GetResults(); - - size_t readSize = static_cast(readBuffer.Length()); - if (readSize) - { - static_assert(sizeof(char) == sizeof(*readBuffer.data())); - inputString.append(reinterpret_cast(readBuffer.data()), readSize); - } - else - { - break; - } - } - - std::unique_ptr parser = ConfigurationSetParser::Create(inputString); - - if (FAILED(parser->Result())) - { - result->Initialize(parser->Result(), parser->Field(), parser->Value(), parser->Line(), parser->Column()); - co_return *result; - } - - parser->Parse(); - if (FAILED(parser->Result())) - { - result->Initialize(parser->Result(), parser->Field(), parser->Value(), parser->Line(), parser->Column()); - co_return *result; - } - - auto configurationSet = parser->GetConfigurationSet(); - PropagateLifetimeWatcher(configurationSet.as()); - configurationSet->SetInputHash(AppInstaller::Utility::SHA256::ConvertToString(AppInstaller::Utility::SHA256::ComputeHash(inputString))); - - result->Initialize(*configurationSet); - } - catch (const wil::ResultException& resultException) - { - result->Initialize(resultException.GetErrorCode()); - } - catch (...) - { - result->Initialize(WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE); - LOG_CAUGHT_EXCEPTION(); - } - - co_return *result; - } - - Windows::Foundation::Collections::IVector ConfigurationProcessor::CheckForConflicts( - const Windows::Foundation::Collections::IVectorView& configurationSets, - bool includeConfigurationHistory) - { - UNREFERENCED_PARAMETER(configurationSets); - UNREFERENCED_PARAMETER(includeConfigurationHistory); - THROW_HR(E_NOTIMPL); - } - - Windows::Foundation::IAsyncOperation> ConfigurationProcessor::CheckForConflictsAsync( - const Windows::Foundation::Collections::IVectorView& configurationSets, - bool includeConfigurationHistory) - { - co_return CheckForConflicts(configurationSets, includeConfigurationHistory); - } - - Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetails(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return GetSetDetailsImpl(configurationSet, detailFlags); - } - - Windows::Foundation::IAsyncOperationWithProgress ConfigurationProcessor::GetSetDetailsAsync( - const Configuration::ConfigurationSet& configurationSet, - ConfigurationUnitDetailFlags detailFlags) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - Configuration::ConfigurationSet localSet = configurationSet; - - co_await winrt::resume_background(); - - co_return GetSetDetailsImpl(localSet, detailFlags, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token()}); - } - - Windows::Foundation::Collections::IVector ConfigurationProcessor::GetConfigurationHistoryImpl(ShutdownAwareAsyncCancellation cancellation) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - m_database.EnsureOpened(false); - cancellation.ThrowIfCancelled(); - - std::vector result; - for (const auto& set : m_database.GetSetHistory()) - { - PropagateLifetimeWatcher(*set); - result.emplace_back(*set); - } - - return multi_threaded_vector(std::move(result)); - } - - Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetailsImpl( - const Configuration::ConfigurationSet& configurationSet, - ConfigurationUnitDetailFlags detailFlags, - ShutdownAwareAsyncProgress progress) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); - - auto result = make_self>(); - progress.Result(*result); - - for (const auto& unit : configurationSet.Units()) - { - progress.ThrowIfCancelled(); - - auto unitResult = make_self>(); - auto unitResultInformation = make_self>(); - unitResult->Unit(unit); - unitResult->ResultInformation(*unitResultInformation); - - try - { - IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailFlags); - unitResult->Details(details); - get_self(unit)->Details(std::move(details)); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResultInformation); - } - - result->UnitResultsVector().Append(*unitResult); - progress.Progress(*unitResult); - } - - return *result; - } - - Configuration::GetConfigurationUnitDetailsResult ConfigurationProcessor::GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return GetUnitDetailsImpl(unit, detailFlags); - } - - Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - ConfigurationUnit localUnit = unit; - - co_await winrt::resume_background(); - - co_return GetUnitDetailsImpl(localUnit, detailFlags); - } - - Configuration::GetConfigurationUnitDetailsResult ConfigurationProcessor::GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - - auto unitResult = make_self>(); - auto unitResultInformation = make_self>(); - unitResult->Unit(unit); - unitResult->ResultInformation(*unitResultInformation); - - try - { - IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailFlags); - unitResult->Details(details); - get_self(unit)->Details(std::move(details)); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResultInformation); - } - - return *unitResult; - } - - Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySet(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return ApplySetImpl(configurationSet, flags); - } - - Windows::Foundation::IAsyncOperationWithProgress ConfigurationProcessor::ApplySetAsync( - const Configuration::ConfigurationSet& configurationSet, - ApplyConfigurationSetFlags flags) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - Configuration::ConfigurationSet localSet = configurationSet; - - co_await winrt::resume_background(); - - co_return ApplySetImpl(localSet, flags, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token() }); - } - - Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySetImpl( - const Configuration::ConfigurationSet& configurationSet, - ApplyConfigurationSetFlags flags, - ShutdownAwareAsyncProgress progress) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationGroupProcessor groupProcessor; - bool recordHistoryAndStatus = false; - - if (WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) - { - // If performing a consistency check, always use the default processor and let it know as well - auto defaultGroupProcessor = make_self>(); - defaultGroupProcessor->Initialize(configurationSet, nullptr, m_threadGlobals, true); - groupProcessor = *defaultGroupProcessor; - } - else - { - groupProcessor = GetSetGroupProcessor(configurationSet); - - // Write this set to the database history - // This is a somewhat arbitrary time to write it, but it should not be done if PerformConsistencyCheckOnly is passed, so this is convenient. - recordHistoryAndStatus = true; - m_database.EnsureOpened(); - progress.ThrowIfCancelled(); - m_database.WriteSetHistory(configurationSet, WI_IsFlagSet(flags, ApplyConfigurationSetFlags::DoNotOverwriteMatchingOriginSet)); - } - - auto result = make_self>(); - - // Build out the unit results and a map to find them quickly - using UnitResultType = decltype(make_self>()); - std::map unitResultMap; - - std::function&)> createUnitResults = - [&](const winrt::Windows::Foundation::Collections::IVector& units) - { - for (const Configuration::ConfigurationUnit& unit : units) - { - // Add to result - UnitResultType applyUnitResult = make_self>(); - applyUnitResult->Unit(unit); - result->UnitResultsVector().Append(*applyUnitResult); - - // Add to map - unitResultMap.emplace(unit.InstanceIdentifier(), applyUnitResult); - - // Handle members if present - if (unit.IsGroup()) - { - createUnitResults(unit.Units()); - } - } - }; - - createUnitResults(configurationSet.Units()); - - progress.Result(*result); - - try - { - ConfigurationSequencer sequencer{ m_database }; - auto status = ConfigurationStatus::Instance(); - guid setInstanceIdentifier = configurationSet.InstanceIdentifier(); - auto updateState = [&](ConfigurationSetState state) - { - try - { - progress.Progress(implementation::ConfigurationSetChangeData::Create(state)); - } - CATCH_LOG(); - - if (recordHistoryAndStatus) - { - status->UpdateSetState(setInstanceIdentifier, state); - } - }; - - if (!WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) - { - if (sequencer.Enqueue(configurationSet)) - { - updateState(ConfigurationSetState::Pending); - sequencer.Wait(progress.GetCancellation()); - } - } - - progress.ThrowIfCancelled(); - - updateState(ConfigurationSetState::InProgress); - - // Forward unit result progress to caller - auto applyOperation = groupProcessor.ApplyGroupSettingsAsync([&](const auto&, const IApplyGroupMemberSettingsResult& unitResult) - { - auto itr = unitResultMap.find(unitResult.Unit().InstanceIdentifier()); - if (itr != unitResultMap.end()) - { - itr->second->Initialize(unitResult); - } - - // Create progress object - auto applyResult = make_self(); - applyResult->Initialize(unitResult); - progress.Progress(*applyResult); - - if (recordHistoryAndStatus) - { - status->UpdateUnitState(setInstanceIdentifier, applyResult); - } - }); - - // Cancel the inner operation if we are cancelled - progress.Callback([applyOperation]() { applyOperation.Cancel(); }); - - IApplyGroupSettingsResult applyResult = applyOperation.get(); - - // Place all results from the processor into our result - if (applyResult.ResultInformation()) - { - result->ResultCode(applyResult.ResultInformation().ResultCode()); - } - - for (const IApplyGroupMemberSettingsResult& unitResult : applyResult.UnitResults()) - { - // Update overall result - auto itr = unitResultMap.find(unitResult.Unit().InstanceIdentifier()); - if (itr == unitResultMap.end()) - { - continue; - } - - itr->second->Initialize(unitResult); - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate( - configurationSet.InstanceIdentifier(), - itr->second->Unit(), - ConfigurationUnitIntent::Apply, - TelemetryTraceLogger::ApplyAction, - itr->second->ResultInformation()); - } - - updateState(ConfigurationSetState::Completed); - - m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForApply(*winrt::get_self(configurationSet), *result); - return *result; - } - catch (...) - { - m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForApplyException( - *winrt::get_self(configurationSet), - LOG_CAUGHT_EXCEPTION(), - *result); - throw; - } - } - - Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSet(const Configuration::ConfigurationSet& configurationSet) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return TestSetImpl(configurationSet); - } - - Windows::Foundation::IAsyncOperationWithProgress ConfigurationProcessor::TestSetAsync(const Configuration::ConfigurationSet& configurationSet) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - Configuration::ConfigurationSet localSet = configurationSet; - - co_await winrt::resume_background(); - - co_return TestSetImpl(localSet, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token() }); - } - - Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSetImpl( - const Configuration::ConfigurationSet& configurationSet, - ShutdownAwareAsyncProgress progress) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationGroupProcessor groupProcessor = GetSetGroupProcessor(configurationSet); - auto result = make_self>(); - result->TestResult(ConfigurationTestResult::NotRun); - progress.Result(*result); - - try - { - // Forward unit result progress to caller - auto testOperation = groupProcessor.TestGroupSettingsAsync([&](const auto&, const ITestSettingsResult& unitResult) - { - auto testResult = make_self>(); - testResult->Initialize(unitResult); - - result->AppendUnitResult(*testResult); - progress.Progress(*testResult); - }); - - // Cancel the inner operation if we are cancelled - progress.Callback([testOperation]() { testOperation.Cancel(); }); - - ITestGroupSettingsResult testResult = testOperation.get(); - - // Send telemetry for all results - for (const ITestSettingsResult& unitResult : testResult.UnitResults()) - { - auto testUnitResult = make_self>(); - testUnitResult->Initialize(unitResult); - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate( - configurationSet.InstanceIdentifier(), - testUnitResult->Unit(), - ConfigurationUnitIntent::Assert, - TelemetryTraceLogger::TestAction, - testUnitResult->ResultInformation()); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForTest(*winrt::get_self(configurationSet), *result); - return *result; - } - catch (...) - { - m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForTestException( - *winrt::get_self(configurationSet), - LOG_CAUGHT_EXCEPTION(), - *result); - throw; - } - } - - Configuration::GetConfigurationUnitSettingsResult ConfigurationProcessor::GetUnitSettings(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return GetUnitSettingsImpl(unit); - } - - Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetUnitSettingsAsync(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - ConfigurationUnit localUnit = unit; - - co_await winrt::resume_background(); - - co_return GetUnitSettingsImpl(localUnit, { co_await winrt::get_cancellation_token() }); - } - - Configuration::GetConfigurationUnitSettingsResult ConfigurationProcessor::GetUnitSettingsImpl( - const ConfigurationUnit& unit, - ShutdownAwareAsyncCancellation cancellation) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - auto result = make_self>(); - auto unitResult = make_self>(); - result->ResultInformation(*unitResult); - - cancellation.ThrowIfCancelled(); - - IConfigurationUnitProcessor unitProcessor; - - try - { - unitProcessor = setProcessor.CreateUnitProcessor(unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - cancellation.ThrowIfCancelled(); - - if (unitProcessor) - { - try - { - IGetSettingsResult settingsResult = unitProcessor.GetSettings(); - result->Settings(settingsResult.Settings()); - result->ResultInformation(settingsResult.ResultInformation()); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::GetAction, result->ResultInformation()); - } - - return *result; - } - - Configuration::GetAllConfigurationUnitSettingsResult ConfigurationProcessor::GetAllUnitSettings(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return GetAllUnitSettingsImpl(unit); - } - - Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetAllUnitSettingsAsync(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - ConfigurationUnit localUnit = unit; - - co_await winrt::resume_background(); - - co_return GetAllUnitSettingsImpl(localUnit, { co_await winrt::get_cancellation_token() }); - } - - Configuration::GetAllConfigurationUnitSettingsResult ConfigurationProcessor::GetAllUnitSettingsImpl( - const ConfigurationUnit& unit, - ShutdownAwareAsyncCancellation cancellation) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - auto result = make_self>(); - auto unitResult = make_self>(); - result->ResultInformation(*unitResult); - - cancellation.ThrowIfCancelled(); - - IConfigurationUnitProcessor unitProcessor; - - try - { - unitProcessor = setProcessor.CreateUnitProcessor(unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - cancellation.ThrowIfCancelled(); - - IGetAllSettingsConfigurationUnitProcessor getAllSettingsUnitProcessor; - if (unitProcessor.try_as(getAllSettingsUnitProcessor)) - { - cancellation.ThrowIfCancelled(); - - try - { - IGetAllSettingsResult allSettingsResult = getAllSettingsUnitProcessor.GetAllSettings(); - result->Settings(allSettingsResult.Settings()); - result->ResultInformation(allSettingsResult.ResultInformation()); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); - } - else - { - AICLI_LOG(Config, Error, << "Unit Processor does not support GetAllSettings operation"); - unitResult->Initialize(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, hstring{}); - } - - return *result; - } - - Configuration::GetAllConfigurationUnitsResult ConfigurationProcessor::GetAllUnits(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return GetAllUnitsImpl(unit); - } - - Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetAllUnitsAsync(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - ConfigurationUnit localUnit = unit; - - co_await winrt::resume_background(); - - co_return GetAllUnitsImpl(localUnit, { co_await winrt::get_cancellation_token() }); - } - - Configuration::GetAllConfigurationUnitsResult ConfigurationProcessor::GetAllUnitsImpl( - const ConfigurationUnit& unit, - ShutdownAwareAsyncCancellation cancellation) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - auto result = make_self>(); - auto unitResult = make_self>(); - result->ResultInformation(*unitResult); - - cancellation.ThrowIfCancelled(); - - IConfigurationUnitProcessor unitProcessor; - - try - { - unitProcessor = setProcessor.CreateUnitProcessor(unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - cancellation.ThrowIfCancelled(); - - IGetAllUnitsConfigurationUnitProcessor getAllUnitsUnitProcessor; - IGetAllSettingsConfigurationUnitProcessor getAllSettingsUnitProcessor; - - if (unitProcessor.try_as(getAllUnitsUnitProcessor)) - { - cancellation.ThrowIfCancelled(); - - try - { - IGetAllUnitsResult allUnitsResult = getAllUnitsUnitProcessor.GetAllUnits(); - result->Units(allUnitsResult.Units()); - result->ResultInformation(allUnitsResult.ResultInformation()); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); - } - else if (unitProcessor.try_as(getAllSettingsUnitProcessor)) - { - cancellation.ThrowIfCancelled(); - - try - { - IGetAllSettingsResult allSettingsResult = getAllSettingsUnitProcessor.GetAllSettings(); - - auto allSettings = allSettingsResult.Settings(); - if (allSettings) - { - std::vector units; - - size_t index = 0; - auto currentType = unit.Type(); - auto currentDetails = unit.Details(); - - for (const auto& settings : allSettings) - { - auto newUnit = make_self(); - - newUnit->Type(currentType); - newUnit->Settings(settings); - newUnit->Details(currentDetails); - - std::wostringstream identifierStream; - identifierStream << static_cast(currentType) << L'-' << index++; - newUnit->Identifier(hstring{ identifierStream.str() }); - - units.push_back(*newUnit); - } - - result->Units(single_threaded_vector(std::move(units))); - } - - result->ResultInformation(allSettingsResult.ResultInformation()); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); - } - else - { - AICLI_LOG(Config, Error, << "Unit Processor does not support GetAllUnits or GetAllSettings operation"); - unitResult->Initialize(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, hstring{}); - } - - return *result; - } - - Windows::Foundation::Collections::IVector ConfigurationProcessor::FindUnitProcessors(const FindUnitProcessorsOptions& findOptions) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return FindUnitProcessorsImpl(findOptions); - } - - Windows::Foundation::IAsyncOperation> ConfigurationProcessor::FindUnitProcessorsAsync(const FindUnitProcessorsOptions& findOptions) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - FindUnitProcessorsOptions localOptions = findOptions; - - co_await winrt::resume_background(); - - co_return FindUnitProcessorsImpl(localOptions, { co_await winrt::get_cancellation_token() }); - } - - Windows::Foundation::Collections::IVector ConfigurationProcessor::FindUnitProcessorsImpl( - const FindUnitProcessorsOptions& findOptions, - ShutdownAwareAsyncCancellation cancellation) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - - cancellation.ThrowIfCancelled(); - - IFindUnitProcessorsSetProcessor findUnitProcessorsSetProcessor; - - if (setProcessor.try_as(findUnitProcessorsSetProcessor)) - { - return findUnitProcessorsSetProcessor.FindUnitProcessors(findOptions); - } - else - { - AICLI_LOG(Config, Error, << "Set Processor does not support FindUnitProcessors operation"); - THROW_HR(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR); - } - } - - Configuration::ApplyConfigurationUnitResult ConfigurationProcessor::ApplyUnit(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return ApplyUnitImpl(unit); - } - - Windows::Foundation::IAsyncOperation ConfigurationProcessor::ApplyUnitAsync(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - ConfigurationUnit localUnit = unit; - - co_await winrt::resume_background(); - - co_return ApplyUnitImpl(localUnit, { co_await winrt::get_cancellation_token() }); - } - - Configuration::ApplyConfigurationUnitResult ConfigurationProcessor::ApplyUnitImpl( - const ConfigurationUnit& unit, - ShutdownAwareAsyncCancellation cancellation) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - auto result = make_self>(); - auto unitResult = make_self>(); - result->Unit(unit); - result->ResultInformation(*unitResult); - - cancellation.ThrowIfCancelled(); - - IConfigurationUnitProcessor unitProcessor; - - try - { - unitProcessor = setProcessor.CreateUnitProcessor(unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - cancellation.ThrowIfCancelled(); - - if (unitProcessor) - { - try - { - auto applyResult = unitProcessor.ApplySettings(); - result->Unit(applyResult.Unit()); - result->State(Configuration::ConfigurationUnitState::Completed); - result->ResultInformation(applyResult.ResultInformation()); - result->RebootRequired(applyResult.RebootRequired()); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Apply, TelemetryTraceLogger::ApplyAction, result->ResultInformation()); - } - - return *result; - } - - Configuration::TestConfigurationUnitResult ConfigurationProcessor::TestUnit(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - return TestUnitImpl(unit); - } - - Windows::Foundation::IAsyncOperation ConfigurationProcessor::TestUnitAsync(const ConfigurationUnit& unit) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); - - auto strong_this{ get_strong() }; - ConfigurationUnit localUnit = unit; - - co_await winrt::resume_background(); - - co_return TestUnitImpl(localUnit, { co_await winrt::get_cancellation_token() }); - } - - Configuration::TestConfigurationUnitResult ConfigurationProcessor::TestUnitImpl( - const ConfigurationUnit& unit, - ShutdownAwareAsyncCancellation cancellation) - { - auto threadGlobals = m_threadGlobals.SetForCurrentThread(); - - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); - auto result = make_self>(); - auto unitResult = make_self>(); - result->Unit(unit); - result->ResultInformation(*unitResult); - - cancellation.ThrowIfCancelled(); - - IConfigurationUnitProcessor unitProcessor; - - try - { - unitProcessor = setProcessor.CreateUnitProcessor(unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - cancellation.ThrowIfCancelled(); - - if (unitProcessor) - { - try - { - auto testResult = unitProcessor.TestSettings(); - result->Unit(testResult.Unit()); - result->TestResult(testResult.TestResult()); - result->ResultInformation(testResult.ResultInformation()); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Assert, TelemetryTraceLogger::TestAction, result->ResultInformation()); - } - - return *result; - } - - IConfigurationGroupProcessor ConfigurationProcessor::GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet) - { - IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); - - IConfigurationGroupProcessor result = setProcessor.try_as(); - if (!result) - { - auto groupProcessor = make_self>(); - groupProcessor->Initialize(configurationSet, setProcessor, m_threadGlobals); - result = *groupProcessor; - } - - return result; - } - - HRESULT STDMETHODCALLTYPE ConfigurationProcessor::SetLifetimeWatcher(IUnknown* watcher) - { - return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); - } - - void ConfigurationProcessor::ConfigurationSetProcessorFactory(const IConfigurationSetProcessorFactory& value) - { - m_factory = value; - - if (m_factory) - { - m_factoryDiagnosticsEventRevoker = m_factory.Diagnostics(winrt::auto_revoke, - [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) - { - if (auto strong_this{ weak_this.get() }) - { - strong_this->SendDiagnostics(information); - } - }); - } - } - - void ConfigurationProcessor::SendDiagnostics(DiagnosticLevel level, std::string_view message) try - { - if (level >= m_minimumLevel) - { - auto diagnostics = make_self>(); - diagnostics->Initialize(level, AppInstaller::Utility::ConvertToUTF16(message)); - SendDiagnosticsImpl(*diagnostics); - } - } - // While diagnostics can be important, a failure to send them should not cause additional issues. - catch (...) {} - - void ConfigurationProcessor::SendDiagnostics(const IDiagnosticInformation& information) try - { - if (information.Level() >= m_minimumLevel) - { - SendDiagnosticsImpl(information); - } - } - // While diagnostics can be important, a failure to send them should not cause additional issues. - catch (...) {} - - void ConfigurationProcessor::SendDiagnosticsImpl(const IDiagnosticInformation& information) - { - std::lock_guard lock{ m_diagnosticsMutex }; - - // Prevent a winrt/wil error recursion here by detecting that this thread failed to send a previous message. - if (m_isHandlingDiagnostics) - { - std::wstring debugMessage = L"An error occurred while trying to send a previous diagnostics message:\n"; - debugMessage.append(information.Message()); - OutputDebugStringW(debugMessage.c_str()); - return; - } - - m_isHandlingDiagnostics = true; - auto notHandling = wil::scope_exit([&] { m_isHandlingDiagnostics = false; }); - - m_diagnostics(*this, information); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationProcessor.h" +#include "ConfigurationProcessor.g.cpp" +#include "ConfigurationSet.h" +#include "OpenConfigurationSetResult.h" +#include "ConfigurationSetParser.h" +#include "DiagnosticInformationInstance.h" +#include "ApplyConfigurationSetResult.h" +#include "ApplyConfigurationUnitResult.h" +#include "TestConfigurationSetResult.h" +#include "TestConfigurationUnitResult.h" +#include "ConfigurationUnitResultInformation.h" +#include "GetConfigurationUnitSettingsResult.h" +#include "GetAllConfigurationUnitSettingsResult.h" +#include "GetAllConfigurationUnitsResult.h" +#include "ExceptionResultHelpers.h" +#include "ConfigurationSetChangeData.h" +#include "GetConfigurationUnitDetailsResult.h" +#include "GetConfigurationSetDetailsResult.h" +#include "DefaultSetGroupProcessor.h" +#include "ConfigurationSequencer.h" +#include "ConfigurationStatus.h" + +#include +#include +#include +#include + +using namespace std::chrono_literals; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + AppInstaller::Logging::Level ConvertLevel(DiagnosticLevel level) + { + switch (level) + { + case DiagnosticLevel::Verbose: return AppInstaller::Logging::Level::Verbose; + case DiagnosticLevel::Informational: return AppInstaller::Logging::Level::Info; + case DiagnosticLevel::Warning: return AppInstaller::Logging::Level::Warning; + case DiagnosticLevel::Error: return AppInstaller::Logging::Level::Error; + case DiagnosticLevel::Critical: return AppInstaller::Logging::Level::Crit; + default: return AppInstaller::Logging::Level::Warning; + } + } + + DiagnosticLevel ConvertLevel(AppInstaller::Logging::Level level) + { + switch (level) + { + case AppInstaller::Logging::Level::Verbose: return DiagnosticLevel::Verbose; + case AppInstaller::Logging::Level::Info: return DiagnosticLevel::Informational; + case AppInstaller::Logging::Level::Warning: return DiagnosticLevel::Warning; + case AppInstaller::Logging::Level::Error: return DiagnosticLevel::Error; + case AppInstaller::Logging::Level::Crit: return DiagnosticLevel::Critical; + default: return DiagnosticLevel::Warning; + } + } + + // ILogger that sends data back to the Diagnostics event of the ConfigurationProcessor. + struct ConfigurationProcessorDiagnosticsLogger : public AppInstaller::Logging::ILogger + { + ConfigurationProcessorDiagnosticsLogger(ConfigurationProcessor& processor) : m_processor(processor) {} + + std::string GetName() const override + { + return "ConfigurationProcessorDiagnosticsLogger"; + } + + void Write(AppInstaller::Logging::Channel channel, AppInstaller::Logging::Level level, std::string_view message) noexcept override try + { + std::ostringstream strstr; + strstr << '[' << AppInstaller::Logging::GetChannelName(channel) << "] " << message; + m_processor.SendDiagnostics(ConvertLevel(level), strstr.str()); + } + catch (...) {} + + void WriteDirect(AppInstaller::Logging::Channel, AppInstaller::Logging::Level level, std::string_view message) noexcept override try + { + m_processor.SendDiagnostics(ConvertLevel(level), message); + } + catch (...) {} + + private: + ConfigurationProcessor& m_processor; + }; + + // Helper to ensure a one-time callback attach + struct AttachWilFailureCallback + { + AttachWilFailureCallback() + { + wil::SetResultLoggingCallback(wilResultLoggingCallback); + } + + ~AttachWilFailureCallback() = default; + + static void __stdcall wilResultLoggingCallback(const wil::FailureInfo& info) noexcept + { + AICLI_LOG(Fail, Error, << [&]() { + wchar_t message[2048]; + GetFailureLogString(message, ARRAYSIZE(message), info); + return AppInstaller::Utility::ConvertToUTF8(message); + }()); + } + + static void Ensure() + { + static AttachWilFailureCallback s_callbackAttach; + } + }; + } + + ConfigurationProcessor::ConfigurationProcessor() + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::Configuration)); + + AppInstaller::Logging::DiagnosticLogger& logger = m_threadGlobals.GetDiagnosticLogger(); + logger.SetEnabledChannels(AppInstaller::Logging::Channel::All); + logger.SetLevel(AppInstaller::Logging::Level::Verbose); + logger.AddLogger(std::make_unique(*this)); + } + + ConfigurationProcessor::ConfigurationProcessor(const IConfigurationSetProcessorFactory& factory) : ConfigurationProcessor() + { + ConfigurationSetProcessorFactory(factory); + } + + event_token ConfigurationProcessor::Diagnostics(const Windows::Foundation::EventHandler& handler) + { + AttachWilFailureCallback::Ensure(); + return m_diagnostics.add(handler); + } + + void ConfigurationProcessor::Diagnostics(const event_token& token) noexcept + { + m_diagnostics.remove(token); + } + + DiagnosticLevel ConfigurationProcessor::MinimumLevel() + { + return m_minimumLevel; + } + + void ConfigurationProcessor::MinimumLevel(DiagnosticLevel value) + { + m_minimumLevel = value; + m_threadGlobals.GetDiagnosticLogger().SetLevel(ConvertLevel(value)); + if (m_factory) + { + m_factory.MinimumLevel(value); + } + } + + hstring ConfigurationProcessor::Caller() const + { + return hstring{ AppInstaller::Utility::ConvertToUTF16(m_threadGlobals.GetTelemetryLogger().GetCaller()) }; + } + + void ConfigurationProcessor::Caller(hstring value) + { + m_threadGlobals.GetTelemetryLogger().SetCaller(AppInstaller::Utility::ConvertToUTF8(value)); + } + + guid ConfigurationProcessor::ActivityIdentifier() + { + return *m_threadGlobals.GetTelemetryLogger().GetActivityId(); + } + + void ConfigurationProcessor::ActivityIdentifier(const guid& value) + { + m_threadGlobals.GetTelemetryLogger().SetActivityId(value); + } + + bool ConfigurationProcessor::GenerateTelemetryEvents() + { + return m_threadGlobals.GetTelemetryLogger().IsEnabled(); + } + + void ConfigurationProcessor::GenerateTelemetryEvents(bool value) + { + std::ignore = m_threadGlobals.GetTelemetryLogger().EnableRuntime(value); + } + + event_token ConfigurationProcessor::ConfigurationChange(const Windows::Foundation::TypedEventHandler& handler) + { + if (!m_configurationChange) + { + auto status = ConfigurationStatus::Instance(); + std::atomic_store(&m_changeRegistration, status->RegisterForChange(*this)); + } + + return m_configurationChange.add(handler); + } + + void ConfigurationProcessor::ConfigurationChange(const event_token& token) noexcept + { + m_configurationChange.remove(token); + + if (!m_configurationChange) + { + std::atomic_store(&m_changeRegistration, {}); + } + } + + void ConfigurationProcessor::ConfigurationChange(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data) try + { + m_configurationChange(set, data); + } + CATCH_LOG(); + + Windows::Foundation::Collections::IVector ConfigurationProcessor::GetConfigurationHistory() + { + return GetConfigurationHistoryImpl(); + } + + Windows::Foundation::IAsyncOperation> ConfigurationProcessor::GetConfigurationHistoryAsync() + { + auto strong_this{ get_strong() }; + co_await winrt::resume_background(); + co_return GetConfigurationHistoryImpl({ co_await winrt::get_cancellation_token() }); + } + + Configuration::OpenConfigurationSetResult ConfigurationProcessor::OpenConfigurationSet(const Windows::Storage::Streams::IInputStream& stream) + { + return OpenConfigurationSetAsync(stream).get(); + } + + Windows::Foundation::IAsyncOperation ConfigurationProcessor::OpenConfigurationSetAsync(const Windows::Storage::Streams::IInputStream& stream) + { + auto strong_this{ get_strong() }; + Windows::Storage::Streams::IInputStream localStream = stream; + + co_await winrt::resume_background(); + auto cancellation = co_await winrt::get_cancellation_token(); + + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + auto result = make_self>(); + + if (!localStream) + { + result->Initialize(E_POINTER, {}); + co_return *result; + } + + try + { + // Read the entire file into memory as we expect them to be small and + // our YAML parser doesn't support streaming at this time. + // This is done here to enable easy cancellation propagation to the stream reads. + uint32_t bufferSize = 1 << 20; + Windows::Storage::Streams::Buffer buffer(bufferSize); + + // Memory stream in mixed elevation does not support InputStreamOptions as flags. + Windows::Storage::Streams::InputStreamOptions readOptions = Windows::Storage::Streams::InputStreamOptions::Partial; + std::string inputString; + + for (;;) + { + auto asyncOperation = localStream.ReadAsync(buffer, bufferSize, readOptions); + + // Manually poll status and propagate cancellation to stay on this thread for thread globals + while (asyncOperation.Status() == Windows::Foundation::AsyncStatus::Started) + { + if (cancellation()) + { + asyncOperation.Cancel(); + } + + std::this_thread::sleep_for(100ms); + } + + Windows::Storage::Streams::IBuffer readBuffer = asyncOperation.GetResults(); + + size_t readSize = static_cast(readBuffer.Length()); + if (readSize) + { + static_assert(sizeof(char) == sizeof(*readBuffer.data())); + inputString.append(reinterpret_cast(readBuffer.data()), readSize); + } + else + { + break; + } + } + + std::unique_ptr parser = ConfigurationSetParser::Create(inputString); + + if (FAILED(parser->Result())) + { + result->Initialize(parser->Result(), parser->Field(), parser->Value(), parser->Line(), parser->Column()); + co_return *result; + } + + parser->Parse(); + if (FAILED(parser->Result())) + { + result->Initialize(parser->Result(), parser->Field(), parser->Value(), parser->Line(), parser->Column()); + co_return *result; + } + + auto configurationSet = parser->GetConfigurationSet(); + PropagateLifetimeWatcher(configurationSet.as()); + configurationSet->SetInputHash(AppInstaller::Utility::SHA256::ConvertToString(AppInstaller::Utility::SHA256::ComputeHash(inputString))); + + result->Initialize(*configurationSet); + } + catch (const wil::ResultException& resultException) + { + result->Initialize(resultException.GetErrorCode()); + } + catch (...) + { + result->Initialize(WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE); + LOG_CAUGHT_EXCEPTION(); + } + + co_return *result; + } + + Windows::Foundation::Collections::IVector ConfigurationProcessor::CheckForConflicts( + const Windows::Foundation::Collections::IVectorView& configurationSets, + bool includeConfigurationHistory) + { + UNREFERENCED_PARAMETER(configurationSets); + UNREFERENCED_PARAMETER(includeConfigurationHistory); + THROW_HR(E_NOTIMPL); + } + + Windows::Foundation::IAsyncOperation> ConfigurationProcessor::CheckForConflictsAsync( + const Windows::Foundation::Collections::IVectorView& configurationSets, + bool includeConfigurationHistory) + { + co_return CheckForConflicts(configurationSets, includeConfigurationHistory); + } + + Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetails(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return GetSetDetailsImpl(configurationSet, detailFlags); + } + + Windows::Foundation::IAsyncOperationWithProgress ConfigurationProcessor::GetSetDetailsAsync( + const Configuration::ConfigurationSet& configurationSet, + ConfigurationUnitDetailFlags detailFlags) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + Configuration::ConfigurationSet localSet = configurationSet; + + co_await winrt::resume_background(); + + co_return GetSetDetailsImpl(localSet, detailFlags, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token()}); + } + + Windows::Foundation::Collections::IVector ConfigurationProcessor::GetConfigurationHistoryImpl(ShutdownAwareAsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + m_database.EnsureOpened(false); + cancellation.ThrowIfCancelled(); + + std::vector result; + for (const auto& set : m_database.GetSetHistory()) + { + PropagateLifetimeWatcher(*set); + result.emplace_back(*set); + } + + return multi_threaded_vector(std::move(result)); + } + + Configuration::GetConfigurationSetDetailsResult ConfigurationProcessor::GetSetDetailsImpl( + const Configuration::ConfigurationSet& configurationSet, + ConfigurationUnitDetailFlags detailFlags, + ShutdownAwareAsyncProgress progress) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); + + auto result = make_self>(); + progress.Result(*result); + + for (const auto& unit : configurationSet.Units()) + { + progress.ThrowIfCancelled(); + + auto unitResult = make_self>(); + auto unitResultInformation = make_self>(); + unitResult->Unit(unit); + unitResult->ResultInformation(*unitResultInformation); + + try + { + IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailFlags); + unitResult->Details(details); + get_self(unit)->Details(std::move(details)); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResultInformation); + } + + result->UnitResultsVector().Append(*unitResult); + progress.Progress(*unitResult); + } + + return *result; + } + + Configuration::GetConfigurationUnitDetailsResult ConfigurationProcessor::GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return GetUnitDetailsImpl(unit, detailFlags); + } + + Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + ConfigurationUnit localUnit = unit; + + co_await winrt::resume_background(); + + co_return GetUnitDetailsImpl(localUnit, detailFlags); + } + + Configuration::GetConfigurationUnitDetailsResult ConfigurationProcessor::GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + + auto unitResult = make_self>(); + auto unitResultInformation = make_self>(); + unitResult->Unit(unit); + unitResult->ResultInformation(*unitResultInformation); + + try + { + IConfigurationUnitProcessorDetails details = setProcessor.GetUnitProcessorDetails(unit, detailFlags); + unitResult->Details(details); + get_self(unit)->Details(std::move(details)); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResultInformation); + } + + return *unitResult; + } + + Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySet(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return ApplySetImpl(configurationSet, flags); + } + + Windows::Foundation::IAsyncOperationWithProgress ConfigurationProcessor::ApplySetAsync( + const Configuration::ConfigurationSet& configurationSet, + ApplyConfigurationSetFlags flags) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + Configuration::ConfigurationSet localSet = configurationSet; + + co_await winrt::resume_background(); + + co_return ApplySetImpl(localSet, flags, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token() }); + } + + Configuration::ApplyConfigurationSetResult ConfigurationProcessor::ApplySetImpl( + const Configuration::ConfigurationSet& configurationSet, + ApplyConfigurationSetFlags flags, + ShutdownAwareAsyncProgress progress) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationGroupProcessor groupProcessor; + bool recordHistoryAndStatus = false; + + if (WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) + { + // If performing a consistency check, always use the default processor and let it know as well + auto defaultGroupProcessor = make_self>(); + defaultGroupProcessor->Initialize(configurationSet, nullptr, m_threadGlobals, true); + groupProcessor = *defaultGroupProcessor; + } + else + { + groupProcessor = GetSetGroupProcessor(configurationSet); + + // Write this set to the database history + // This is a somewhat arbitrary time to write it, but it should not be done if PerformConsistencyCheckOnly is passed, so this is convenient. + recordHistoryAndStatus = true; + m_database.EnsureOpened(); + progress.ThrowIfCancelled(); + m_database.WriteSetHistory(configurationSet, WI_IsFlagSet(flags, ApplyConfigurationSetFlags::DoNotOverwriteMatchingOriginSet)); + } + + auto result = make_self>(); + + // Build out the unit results and a map to find them quickly + using UnitResultType = decltype(make_self>()); + std::map unitResultMap; + + std::function&)> createUnitResults = + [&](const winrt::Windows::Foundation::Collections::IVector& units) + { + for (const Configuration::ConfigurationUnit& unit : units) + { + // Add to result + UnitResultType applyUnitResult = make_self>(); + applyUnitResult->Unit(unit); + result->UnitResultsVector().Append(*applyUnitResult); + + // Add to map + unitResultMap.emplace(unit.InstanceIdentifier(), applyUnitResult); + + // Handle members if present + if (unit.IsGroup()) + { + createUnitResults(unit.Units()); + } + } + }; + + createUnitResults(configurationSet.Units()); + + progress.Result(*result); + + try + { + ConfigurationSequencer sequencer{ m_database }; + auto status = ConfigurationStatus::Instance(); + guid setInstanceIdentifier = configurationSet.InstanceIdentifier(); + auto updateState = [&](ConfigurationSetState state) + { + try + { + progress.Progress(implementation::ConfigurationSetChangeData::Create(state)); + } + CATCH_LOG(); + + if (recordHistoryAndStatus) + { + status->UpdateSetState(setInstanceIdentifier, state); + } + }; + + if (!WI_IsFlagSet(flags, ApplyConfigurationSetFlags::PerformConsistencyCheckOnly)) + { + if (sequencer.Enqueue(configurationSet)) + { + updateState(ConfigurationSetState::Pending); + sequencer.Wait(progress.GetCancellation()); + } + } + + progress.ThrowIfCancelled(); + + updateState(ConfigurationSetState::InProgress); + + // Forward unit result progress to caller + auto applyOperation = groupProcessor.ApplyGroupSettingsAsync([&](const auto&, const IApplyGroupMemberSettingsResult& unitResult) + { + auto itr = unitResultMap.find(unitResult.Unit().InstanceIdentifier()); + if (itr != unitResultMap.end()) + { + itr->second->Initialize(unitResult); + } + + // Create progress object + auto applyResult = make_self(); + applyResult->Initialize(unitResult); + progress.Progress(*applyResult); + + if (recordHistoryAndStatus) + { + status->UpdateUnitState(setInstanceIdentifier, applyResult); + } + }); + + // Cancel the inner operation if we are cancelled + progress.Callback([applyOperation]() { applyOperation.Cancel(); }); + + IApplyGroupSettingsResult applyResult = applyOperation.get(); + + // Place all results from the processor into our result + if (applyResult.ResultInformation()) + { + result->ResultCode(applyResult.ResultInformation().ResultCode()); + } + + for (const IApplyGroupMemberSettingsResult& unitResult : applyResult.UnitResults()) + { + // Update overall result + auto itr = unitResultMap.find(unitResult.Unit().InstanceIdentifier()); + if (itr == unitResultMap.end()) + { + continue; + } + + itr->second->Initialize(unitResult); + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate( + configurationSet.InstanceIdentifier(), + itr->second->Unit(), + ConfigurationUnitIntent::Apply, + TelemetryTraceLogger::ApplyAction, + itr->second->ResultInformation()); + } + + updateState(ConfigurationSetState::Completed); + + m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForApply(*winrt::get_self(configurationSet), *result); + return *result; + } + catch (...) + { + m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForApplyException( + *winrt::get_self(configurationSet), + LOG_CAUGHT_EXCEPTION(), + *result); + throw; + } + } + + Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSet(const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return TestSetImpl(configurationSet); + } + + Windows::Foundation::IAsyncOperationWithProgress ConfigurationProcessor::TestSetAsync(const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + Configuration::ConfigurationSet localSet = configurationSet; + + co_await winrt::resume_background(); + + co_return TestSetImpl(localSet, { co_await winrt::get_progress_token(), co_await winrt::get_cancellation_token() }); + } + + Configuration::TestConfigurationSetResult ConfigurationProcessor::TestSetImpl( + const Configuration::ConfigurationSet& configurationSet, + ShutdownAwareAsyncProgress progress) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationGroupProcessor groupProcessor = GetSetGroupProcessor(configurationSet); + auto result = make_self>(); + result->TestResult(ConfigurationTestResult::NotRun); + progress.Result(*result); + + try + { + // Forward unit result progress to caller + auto testOperation = groupProcessor.TestGroupSettingsAsync([&](const auto&, const ITestSettingsResult& unitResult) + { + auto testResult = make_self>(); + testResult->Initialize(unitResult); + + result->AppendUnitResult(*testResult); + progress.Progress(*testResult); + }); + + // Cancel the inner operation if we are cancelled + progress.Callback([testOperation]() { testOperation.Cancel(); }); + + ITestGroupSettingsResult testResult = testOperation.get(); + + // Send telemetry for all results + for (const ITestSettingsResult& unitResult : testResult.UnitResults()) + { + auto testUnitResult = make_self>(); + testUnitResult->Initialize(unitResult); + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate( + configurationSet.InstanceIdentifier(), + testUnitResult->Unit(), + ConfigurationUnitIntent::Assert, + TelemetryTraceLogger::TestAction, + testUnitResult->ResultInformation()); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForTest(*winrt::get_self(configurationSet), *result); + return *result; + } + catch (...) + { + m_threadGlobals.GetTelemetryLogger().LogConfigProcessingSummaryForTestException( + *winrt::get_self(configurationSet), + LOG_CAUGHT_EXCEPTION(), + *result); + throw; + } + } + + Configuration::GetConfigurationUnitSettingsResult ConfigurationProcessor::GetUnitSettings(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return GetUnitSettingsImpl(unit); + } + + Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetUnitSettingsAsync(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + ConfigurationUnit localUnit = unit; + + co_await winrt::resume_background(); + + co_return GetUnitSettingsImpl(localUnit, { co_await winrt::get_cancellation_token() }); + } + + Configuration::GetConfigurationUnitSettingsResult ConfigurationProcessor::GetUnitSettingsImpl( + const ConfigurationUnit& unit, + ShutdownAwareAsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + auto result = make_self>(); + auto unitResult = make_self>(); + result->ResultInformation(*unitResult); + + cancellation.ThrowIfCancelled(); + + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + cancellation.ThrowIfCancelled(); + + if (unitProcessor) + { + try + { + IGetSettingsResult settingsResult = unitProcessor.GetSettings(); + result->Settings(settingsResult.Settings()); + result->ResultInformation(settingsResult.ResultInformation()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::GetAction, result->ResultInformation()); + } + + return *result; + } + + Configuration::GetAllConfigurationUnitSettingsResult ConfigurationProcessor::GetAllUnitSettings(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return GetAllUnitSettingsImpl(unit); + } + + Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetAllUnitSettingsAsync(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + ConfigurationUnit localUnit = unit; + + co_await winrt::resume_background(); + + co_return GetAllUnitSettingsImpl(localUnit, { co_await winrt::get_cancellation_token() }); + } + + Configuration::GetAllConfigurationUnitSettingsResult ConfigurationProcessor::GetAllUnitSettingsImpl( + const ConfigurationUnit& unit, + ShutdownAwareAsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + auto result = make_self>(); + auto unitResult = make_self>(); + result->ResultInformation(*unitResult); + + cancellation.ThrowIfCancelled(); + + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + cancellation.ThrowIfCancelled(); + + IGetAllSettingsConfigurationUnitProcessor getAllSettingsUnitProcessor; + if (unitProcessor.try_as(getAllSettingsUnitProcessor)) + { + cancellation.ThrowIfCancelled(); + + try + { + IGetAllSettingsResult allSettingsResult = getAllSettingsUnitProcessor.GetAllSettings(); + result->Settings(allSettingsResult.Settings()); + result->ResultInformation(allSettingsResult.ResultInformation()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); + } + else + { + AICLI_LOG(Config, Error, << "Unit Processor does not support GetAllSettings operation"); + unitResult->Initialize(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, hstring{}); + } + + return *result; + } + + Configuration::GetAllConfigurationUnitsResult ConfigurationProcessor::GetAllUnits(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return GetAllUnitsImpl(unit); + } + + Windows::Foundation::IAsyncOperation ConfigurationProcessor::GetAllUnitsAsync(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + ConfigurationUnit localUnit = unit; + + co_await winrt::resume_background(); + + co_return GetAllUnitsImpl(localUnit, { co_await winrt::get_cancellation_token() }); + } + + Configuration::GetAllConfigurationUnitsResult ConfigurationProcessor::GetAllUnitsImpl( + const ConfigurationUnit& unit, + ShutdownAwareAsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + auto result = make_self>(); + auto unitResult = make_self>(); + result->ResultInformation(*unitResult); + + cancellation.ThrowIfCancelled(); + + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + cancellation.ThrowIfCancelled(); + + IGetAllUnitsConfigurationUnitProcessor getAllUnitsUnitProcessor; + IGetAllSettingsConfigurationUnitProcessor getAllSettingsUnitProcessor; + + if (unitProcessor.try_as(getAllUnitsUnitProcessor)) + { + cancellation.ThrowIfCancelled(); + + try + { + IGetAllUnitsResult allUnitsResult = getAllUnitsUnitProcessor.GetAllUnits(); + result->Units(allUnitsResult.Units()); + result->ResultInformation(allUnitsResult.ResultInformation()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); + } + else if (unitProcessor.try_as(getAllSettingsUnitProcessor)) + { + cancellation.ThrowIfCancelled(); + + try + { + IGetAllSettingsResult allSettingsResult = getAllSettingsUnitProcessor.GetAllSettings(); + + auto allSettings = allSettingsResult.Settings(); + if (allSettings) + { + std::vector units; + + size_t index = 0; + auto currentType = unit.Type(); + auto currentDetails = unit.Details(); + + for (const auto& settings : allSettings) + { + auto newUnit = make_self(); + + newUnit->Type(currentType); + newUnit->Settings(settings); + newUnit->Details(currentDetails); + + std::wostringstream identifierStream; + identifierStream << static_cast(currentType) << L'-' << index++; + newUnit->Identifier(hstring{ identifierStream.str() }); + + units.push_back(*newUnit); + } + + result->Units(single_threaded_vector(std::move(units))); + } + + result->ResultInformation(allSettingsResult.ResultInformation()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Inform, TelemetryTraceLogger::ExportAction, result->ResultInformation()); + } + else + { + AICLI_LOG(Config, Error, << "Unit Processor does not support GetAllUnits or GetAllSettings operation"); + unitResult->Initialize(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR, hstring{}); + } + + return *result; + } + + Windows::Foundation::Collections::IVector ConfigurationProcessor::FindUnitProcessors(const FindUnitProcessorsOptions& findOptions) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return FindUnitProcessorsImpl(findOptions); + } + + Windows::Foundation::IAsyncOperation> ConfigurationProcessor::FindUnitProcessorsAsync(const FindUnitProcessorsOptions& findOptions) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + FindUnitProcessorsOptions localOptions = findOptions; + + co_await winrt::resume_background(); + + co_return FindUnitProcessorsImpl(localOptions, { co_await winrt::get_cancellation_token() }); + } + + Windows::Foundation::Collections::IVector ConfigurationProcessor::FindUnitProcessorsImpl( + const FindUnitProcessorsOptions& findOptions, + ShutdownAwareAsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + + cancellation.ThrowIfCancelled(); + + IFindUnitProcessorsSetProcessor findUnitProcessorsSetProcessor; + + if (setProcessor.try_as(findUnitProcessorsSetProcessor)) + { + return findUnitProcessorsSetProcessor.FindUnitProcessors(findOptions); + } + else + { + AICLI_LOG(Config, Error, << "Set Processor does not support FindUnitProcessors operation"); + THROW_HR(WINGET_CONFIG_ERROR_NOT_SUPPORTED_BY_PROCESSOR); + } + } + + Configuration::ApplyConfigurationUnitResult ConfigurationProcessor::ApplyUnit(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return ApplyUnitImpl(unit); + } + + Windows::Foundation::IAsyncOperation ConfigurationProcessor::ApplyUnitAsync(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + ConfigurationUnit localUnit = unit; + + co_await winrt::resume_background(); + + co_return ApplyUnitImpl(localUnit, { co_await winrt::get_cancellation_token() }); + } + + Configuration::ApplyConfigurationUnitResult ConfigurationProcessor::ApplyUnitImpl( + const ConfigurationUnit& unit, + ShutdownAwareAsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + auto result = make_self>(); + auto unitResult = make_self>(); + result->Unit(unit); + result->ResultInformation(*unitResult); + + cancellation.ThrowIfCancelled(); + + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + cancellation.ThrowIfCancelled(); + + if (unitProcessor) + { + try + { + auto applyResult = unitProcessor.ApplySettings(); + result->Unit(applyResult.Unit()); + result->State(Configuration::ConfigurationUnitState::Completed); + result->ResultInformation(applyResult.ResultInformation()); + result->RebootRequired(applyResult.RebootRequired()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Apply, TelemetryTraceLogger::ApplyAction, result->ResultInformation()); + } + + return *result; + } + + Configuration::TestConfigurationUnitResult ConfigurationProcessor::TestUnit(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + return TestUnitImpl(unit); + } + + Windows::Foundation::IAsyncOperation ConfigurationProcessor::TestUnitAsync(const ConfigurationUnit& unit) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_factory); + + auto strong_this{ get_strong() }; + ConfigurationUnit localUnit = unit; + + co_await winrt::resume_background(); + + co_return TestUnitImpl(localUnit, { co_await winrt::get_cancellation_token() }); + } + + Configuration::TestConfigurationUnitResult ConfigurationProcessor::TestUnitImpl( + const ConfigurationUnit& unit, + ShutdownAwareAsyncCancellation cancellation) + { + auto threadGlobals = m_threadGlobals.SetForCurrentThread(); + + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(nullptr); + auto result = make_self>(); + auto unitResult = make_self>(); + result->Unit(unit); + result->ResultInformation(*unitResult); + + cancellation.ThrowIfCancelled(); + + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + cancellation.ThrowIfCancelled(); + + if (unitProcessor) + { + try + { + auto testResult = unitProcessor.TestSettings(); + result->Unit(testResult.Unit()); + result->TestResult(testResult.TestResult()); + result->ResultInformation(testResult.ResultInformation()); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + m_threadGlobals.GetTelemetryLogger().LogConfigUnitRunIfAppropriate(GUID_NULL, unit, ConfigurationUnitIntent::Assert, TelemetryTraceLogger::TestAction, result->ResultInformation()); + } + + return *result; + } + + IConfigurationGroupProcessor ConfigurationProcessor::GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet) + { + IConfigurationSetProcessor setProcessor = m_factory.CreateSetProcessor(configurationSet); + + IConfigurationGroupProcessor result = setProcessor.try_as(); + if (!result) + { + auto groupProcessor = make_self>(); + groupProcessor->Initialize(configurationSet, setProcessor, m_threadGlobals); + result = *groupProcessor; + } + + return result; + } + + HRESULT STDMETHODCALLTYPE ConfigurationProcessor::SetLifetimeWatcher(IUnknown* watcher) + { + return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } + + void ConfigurationProcessor::ConfigurationSetProcessorFactory(const IConfigurationSetProcessorFactory& value) + { + m_factory = value; + + if (m_factory) + { + m_factoryDiagnosticsEventRevoker = m_factory.Diagnostics(winrt::auto_revoke, + [weak_this{ get_weak() }](const IInspectable&, const IDiagnosticInformation& information) + { + if (auto strong_this{ weak_this.get() }) + { + strong_this->SendDiagnostics(information); + } + }); + } + } + + void ConfigurationProcessor::SendDiagnostics(DiagnosticLevel level, std::string_view message) try + { + if (level >= m_minimumLevel) + { + auto diagnostics = make_self>(); + diagnostics->Initialize(level, AppInstaller::Utility::ConvertToUTF16(message)); + SendDiagnosticsImpl(*diagnostics); + } + } + // While diagnostics can be important, a failure to send them should not cause additional issues. + catch (...) {} + + void ConfigurationProcessor::SendDiagnostics(const IDiagnosticInformation& information) try + { + if (information.Level() >= m_minimumLevel) + { + SendDiagnosticsImpl(information); + } + } + // While diagnostics can be important, a failure to send them should not cause additional issues. + catch (...) {} + + void ConfigurationProcessor::SendDiagnosticsImpl(const IDiagnosticInformation& information) + { + std::lock_guard lock{ m_diagnosticsMutex }; + + // Prevent a winrt/wil error recursion here by detecting that this thread failed to send a previous message. + if (m_isHandlingDiagnostics) + { + std::wstring debugMessage = L"An error occurred while trying to send a previous diagnostics message:\n"; + debugMessage.append(information.Message()); + OutputDebugStringW(debugMessage.c_str()); + return; + } + + m_isHandlingDiagnostics = true; + auto notHandling = wil::scope_exit([&] { m_isHandlingDiagnostics = false; }); + + m_diagnostics(*this, information); + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationProcessor.h index 51c3f801b7..aa07eb8f9a 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationProcessor.h +++ b/src/Microsoft.Management.Configuration/ConfigurationProcessor.h @@ -1,170 +1,170 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationProcessor.g.h" -#include -#include -#include -#include "ConfigThreadGlobals.h" -#include "ConfigurationStatus.h" -#include "Database/ConfigurationDatabase.h" -#include -#include "ShutdownSynchronization.h" -#include - -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationProcessor : ConfigurationProcessorT>, AppInstaller::WinRT::LifetimeWatcherBase - { - using ConfigurationSetChangeData = Configuration::ConfigurationSetChangeData; - using ConfigurationUnit = Configuration::ConfigurationUnit; - using ApplyConfigurationSetResult = Configuration::ApplyConfigurationSetResult; - using TestConfigurationSetResult = Configuration::TestConfigurationSetResult; - using TestConfigurationUnitResult = Configuration::TestConfigurationUnitResult; - using GetConfigurationUnitSettingsResult = Configuration::GetConfigurationUnitSettingsResult; - using GetAllConfigurationUnitSettingsResult = Configuration::GetAllConfigurationUnitSettingsResult; - using GetConfigurationSetDetailsResult = Configuration::GetConfigurationSetDetailsResult; - using GetConfigurationUnitDetailsResult = Configuration::GetConfigurationUnitDetailsResult; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - ConfigurationProcessor(); -#endif - - ConfigurationProcessor(const IConfigurationSetProcessorFactory& factory); - - event_token Diagnostics(const Windows::Foundation::EventHandler& handler); - void Diagnostics(const event_token& token) noexcept; - - DiagnosticLevel MinimumLevel(); - void MinimumLevel(DiagnosticLevel value); - - hstring Caller() const; - void Caller(hstring value); - - guid ActivityIdentifier(); - void ActivityIdentifier(const guid& value); - - bool GenerateTelemetryEvents(); - void GenerateTelemetryEvents(bool value); - - event_token ConfigurationChange(const Windows::Foundation::TypedEventHandler& handler); - void ConfigurationChange(const event_token& token) noexcept; - - Windows::Foundation::Collections::IVector GetConfigurationHistory(); - Windows::Foundation::IAsyncOperation> GetConfigurationHistoryAsync(); - - Configuration::OpenConfigurationSetResult OpenConfigurationSet(const Windows::Storage::Streams::IInputStream& stream); - Windows::Foundation::IAsyncOperation OpenConfigurationSetAsync(const Windows::Storage::Streams::IInputStream& stream); - - Windows::Foundation::Collections::IVector CheckForConflicts( - const Windows::Foundation::Collections::IVectorView& configurationSets, - bool includeConfigurationHistory); - Windows::Foundation::IAsyncOperation> CheckForConflictsAsync( - const Windows::Foundation::Collections::IVectorView& configurationSets, - bool includeConfigurationHistory); - - GetConfigurationSetDetailsResult GetSetDetails(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); - Windows::Foundation::IAsyncOperationWithProgress GetSetDetailsAsync(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); - - GetConfigurationUnitDetailsResult GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); - Windows::Foundation::IAsyncOperation GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); - - ApplyConfigurationSetResult ApplySet(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); - Windows::Foundation::IAsyncOperationWithProgress ApplySetAsync(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); - - TestConfigurationSetResult TestSet(const Configuration::ConfigurationSet& configurationSet); - Windows::Foundation::IAsyncOperationWithProgress TestSetAsync(const Configuration::ConfigurationSet& configurationSet); - - GetConfigurationUnitSettingsResult GetUnitSettings(const ConfigurationUnit& unit); - Windows::Foundation::IAsyncOperation GetUnitSettingsAsync(const ConfigurationUnit& unit); - - GetAllConfigurationUnitSettingsResult GetAllUnitSettings(const ConfigurationUnit& unit); - Windows::Foundation::IAsyncOperation GetAllUnitSettingsAsync(const ConfigurationUnit& unit); - - Configuration::GetAllConfigurationUnitsResult GetAllUnits(const ConfigurationUnit& unit); - Windows::Foundation::IAsyncOperation GetAllUnitsAsync(const ConfigurationUnit& unit); - - Windows::Foundation::Collections::IVector FindUnitProcessors(const Configuration::FindUnitProcessorsOptions& findOptions); - Windows::Foundation::IAsyncOperation> FindUnitProcessorsAsync(const Configuration::FindUnitProcessorsOptions& findOptions); - - Configuration::ApplyConfigurationUnitResult ApplyUnit(const ConfigurationUnit& unit); - Windows::Foundation::IAsyncOperation ApplyUnitAsync(const ConfigurationUnit& unit); - - Configuration::TestConfigurationUnitResult TestUnit(const ConfigurationUnit& unit); - Windows::Foundation::IAsyncOperation TestUnitAsync(const ConfigurationUnit& unit); - - HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void ConfigurationSetProcessorFactory(const IConfigurationSetProcessorFactory& value); - - // Sends diagnostics objects to the event. - void SendDiagnostics(DiagnosticLevel level, std::string_view message); - - // Sends diagnostics objects to the event. - void SendDiagnostics(const IDiagnosticInformation& information); - - // Indicate a configuration change occurred. - void ConfigurationChange(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data); - - private: - Windows::Foundation::Collections::IVector GetConfigurationHistoryImpl(ShutdownAwareAsyncCancellation cancellation = {}); - - GetConfigurationSetDetailsResult GetSetDetailsImpl( - const Configuration::ConfigurationSet& configurationSet, - ConfigurationUnitDetailFlags detailFlags, - ShutdownAwareAsyncProgress progress = {}); - - GetConfigurationUnitDetailsResult GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); - - ApplyConfigurationSetResult ApplySetImpl( - const Configuration::ConfigurationSet& configurationSet, - ApplyConfigurationSetFlags flags, - ShutdownAwareAsyncProgress progress = {}); - - TestConfigurationSetResult TestSetImpl( - const Configuration::ConfigurationSet& configurationSet, - ShutdownAwareAsyncProgress progress = {}); - - GetConfigurationUnitSettingsResult GetUnitSettingsImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); - - GetAllConfigurationUnitSettingsResult GetAllUnitSettingsImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); - - Configuration::GetAllConfigurationUnitsResult GetAllUnitsImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); - - Windows::Foundation::Collections::IVector FindUnitProcessorsImpl(const Configuration::FindUnitProcessorsOptions& findOptions, ShutdownAwareAsyncCancellation cancellation = {}); - - Configuration::ApplyConfigurationUnitResult ApplyUnitImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); - - Configuration::TestConfigurationUnitResult TestUnitImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); - - IConfigurationGroupProcessor GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet); - - void SendDiagnosticsImpl(const IDiagnosticInformation& information); - - IConfigurationSetProcessorFactory m_factory = nullptr; - event> m_diagnostics; - event> m_configurationChange; - ConfigThreadGlobals m_threadGlobals; - IConfigurationSetProcessorFactory::Diagnostics_revoker m_factoryDiagnosticsEventRevoker; - DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational; - std::recursive_mutex m_diagnosticsMutex; - ConfigurationDatabase m_database; - bool m_isHandlingDiagnostics = false; - std::shared_ptr m_changeRegistration; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Configuration::factory_implementation -{ - struct ConfigurationProcessor : ConfigurationProcessorT - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationProcessor.g.h" +#include +#include +#include +#include "ConfigThreadGlobals.h" +#include "ConfigurationStatus.h" +#include "Database/ConfigurationDatabase.h" +#include +#include "ShutdownSynchronization.h" +#include + +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationProcessor : ConfigurationProcessorT>, AppInstaller::WinRT::LifetimeWatcherBase + { + using ConfigurationSetChangeData = Configuration::ConfigurationSetChangeData; + using ConfigurationUnit = Configuration::ConfigurationUnit; + using ApplyConfigurationSetResult = Configuration::ApplyConfigurationSetResult; + using TestConfigurationSetResult = Configuration::TestConfigurationSetResult; + using TestConfigurationUnitResult = Configuration::TestConfigurationUnitResult; + using GetConfigurationUnitSettingsResult = Configuration::GetConfigurationUnitSettingsResult; + using GetAllConfigurationUnitSettingsResult = Configuration::GetAllConfigurationUnitSettingsResult; + using GetConfigurationSetDetailsResult = Configuration::GetConfigurationSetDetailsResult; + using GetConfigurationUnitDetailsResult = Configuration::GetConfigurationUnitDetailsResult; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + ConfigurationProcessor(); +#endif + + ConfigurationProcessor(const IConfigurationSetProcessorFactory& factory); + + event_token Diagnostics(const Windows::Foundation::EventHandler& handler); + void Diagnostics(const event_token& token) noexcept; + + DiagnosticLevel MinimumLevel(); + void MinimumLevel(DiagnosticLevel value); + + hstring Caller() const; + void Caller(hstring value); + + guid ActivityIdentifier(); + void ActivityIdentifier(const guid& value); + + bool GenerateTelemetryEvents(); + void GenerateTelemetryEvents(bool value); + + event_token ConfigurationChange(const Windows::Foundation::TypedEventHandler& handler); + void ConfigurationChange(const event_token& token) noexcept; + + Windows::Foundation::Collections::IVector GetConfigurationHistory(); + Windows::Foundation::IAsyncOperation> GetConfigurationHistoryAsync(); + + Configuration::OpenConfigurationSetResult OpenConfigurationSet(const Windows::Storage::Streams::IInputStream& stream); + Windows::Foundation::IAsyncOperation OpenConfigurationSetAsync(const Windows::Storage::Streams::IInputStream& stream); + + Windows::Foundation::Collections::IVector CheckForConflicts( + const Windows::Foundation::Collections::IVectorView& configurationSets, + bool includeConfigurationHistory); + Windows::Foundation::IAsyncOperation> CheckForConflictsAsync( + const Windows::Foundation::Collections::IVectorView& configurationSets, + bool includeConfigurationHistory); + + GetConfigurationSetDetailsResult GetSetDetails(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); + Windows::Foundation::IAsyncOperationWithProgress GetSetDetailsAsync(const Configuration::ConfigurationSet& configurationSet, ConfigurationUnitDetailFlags detailFlags); + + GetConfigurationUnitDetailsResult GetUnitDetails(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); + Windows::Foundation::IAsyncOperation GetUnitDetailsAsync(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); + + ApplyConfigurationSetResult ApplySet(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); + Windows::Foundation::IAsyncOperationWithProgress ApplySetAsync(const Configuration::ConfigurationSet& configurationSet, ApplyConfigurationSetFlags flags); + + TestConfigurationSetResult TestSet(const Configuration::ConfigurationSet& configurationSet); + Windows::Foundation::IAsyncOperationWithProgress TestSetAsync(const Configuration::ConfigurationSet& configurationSet); + + GetConfigurationUnitSettingsResult GetUnitSettings(const ConfigurationUnit& unit); + Windows::Foundation::IAsyncOperation GetUnitSettingsAsync(const ConfigurationUnit& unit); + + GetAllConfigurationUnitSettingsResult GetAllUnitSettings(const ConfigurationUnit& unit); + Windows::Foundation::IAsyncOperation GetAllUnitSettingsAsync(const ConfigurationUnit& unit); + + Configuration::GetAllConfigurationUnitsResult GetAllUnits(const ConfigurationUnit& unit); + Windows::Foundation::IAsyncOperation GetAllUnitsAsync(const ConfigurationUnit& unit); + + Windows::Foundation::Collections::IVector FindUnitProcessors(const Configuration::FindUnitProcessorsOptions& findOptions); + Windows::Foundation::IAsyncOperation> FindUnitProcessorsAsync(const Configuration::FindUnitProcessorsOptions& findOptions); + + Configuration::ApplyConfigurationUnitResult ApplyUnit(const ConfigurationUnit& unit); + Windows::Foundation::IAsyncOperation ApplyUnitAsync(const ConfigurationUnit& unit); + + Configuration::TestConfigurationUnitResult TestUnit(const ConfigurationUnit& unit); + Windows::Foundation::IAsyncOperation TestUnitAsync(const ConfigurationUnit& unit); + + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void ConfigurationSetProcessorFactory(const IConfigurationSetProcessorFactory& value); + + // Sends diagnostics objects to the event. + void SendDiagnostics(DiagnosticLevel level, std::string_view message); + + // Sends diagnostics objects to the event. + void SendDiagnostics(const IDiagnosticInformation& information); + + // Indicate a configuration change occurred. + void ConfigurationChange(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data); + + private: + Windows::Foundation::Collections::IVector GetConfigurationHistoryImpl(ShutdownAwareAsyncCancellation cancellation = {}); + + GetConfigurationSetDetailsResult GetSetDetailsImpl( + const Configuration::ConfigurationSet& configurationSet, + ConfigurationUnitDetailFlags detailFlags, + ShutdownAwareAsyncProgress progress = {}); + + GetConfigurationUnitDetailsResult GetUnitDetailsImpl(const ConfigurationUnit& unit, ConfigurationUnitDetailFlags detailFlags); + + ApplyConfigurationSetResult ApplySetImpl( + const Configuration::ConfigurationSet& configurationSet, + ApplyConfigurationSetFlags flags, + ShutdownAwareAsyncProgress progress = {}); + + TestConfigurationSetResult TestSetImpl( + const Configuration::ConfigurationSet& configurationSet, + ShutdownAwareAsyncProgress progress = {}); + + GetConfigurationUnitSettingsResult GetUnitSettingsImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); + + GetAllConfigurationUnitSettingsResult GetAllUnitSettingsImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); + + Configuration::GetAllConfigurationUnitsResult GetAllUnitsImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); + + Windows::Foundation::Collections::IVector FindUnitProcessorsImpl(const Configuration::FindUnitProcessorsOptions& findOptions, ShutdownAwareAsyncCancellation cancellation = {}); + + Configuration::ApplyConfigurationUnitResult ApplyUnitImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); + + Configuration::TestConfigurationUnitResult TestUnitImpl(const ConfigurationUnit& unit, ShutdownAwareAsyncCancellation cancellation = {}); + + IConfigurationGroupProcessor GetSetGroupProcessor(const Configuration::ConfigurationSet& configurationSet); + + void SendDiagnosticsImpl(const IDiagnosticInformation& information); + + IConfigurationSetProcessorFactory m_factory = nullptr; + event> m_diagnostics; + event> m_configurationChange; + ConfigThreadGlobals m_threadGlobals; + IConfigurationSetProcessorFactory::Diagnostics_revoker m_factoryDiagnosticsEventRevoker; + DiagnosticLevel m_minimumLevel = DiagnosticLevel::Informational; + std::recursive_mutex m_diagnosticsMutex; + ConfigurationDatabase m_database; + bool m_isHandlingDiagnostics = false; + std::shared_ptr m_changeRegistration; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Configuration::factory_implementation +{ + struct ConfigurationProcessor : ConfigurationProcessorT + { + }; +} +#endif diff --git a/src/Microsoft.Management.Configuration/ConfigurationSequencer.cpp b/src/Microsoft.Management.Configuration/ConfigurationSequencer.cpp index cdbc061c48..d0536004d3 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSequencer.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSequencer.cpp @@ -1,173 +1,173 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSequencer.h" -#include "ConfigurationStatus.h" -#include - -using namespace std::chrono_literals; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - ConfigurationSequencer::ConfigurationSequencer(ConfigurationDatabase& database) : m_database(database) {} - - ConfigurationSequencer::~ConfigurationSequencer() - { - // Best effort attempt to remove our queue row - try - { - m_database.RemoveQueueItem(m_queueItemObjectName); - - auto status = ConfigurationStatus::Instance(); - status->UpdateSetState(m_setInstanceIdentifier, false); - } - CATCH_LOG(); - } - - // This function creates necessary objects and records this operation into the table. - // It then performs the equivalent of `Wait` with a timeout of 0. - bool ConfigurationSequencer::Enqueue(const Configuration::ConfigurationSet& configurationSet) - { - m_setInstanceIdentifier = configurationSet.InstanceIdentifier(); - - // Create an arbitrarily named object - std::wstring objectName = L"WinGetConfigQueue_" + AppInstaller::Utility::CreateNewGuidNameWString(); - m_queueItemObjectName = AppInstaller::Utility::ConvertToUTF8(objectName); - m_queueItemObject.create(wil::EventOptions::None, objectName.c_str()); - - m_database.AddQueueItem(configurationSet, m_queueItemObjectName); - - auto statusInstance = ConfigurationStatus::Instance(); - statusInstance->UpdateSetState(m_setInstanceIdentifier, true); - - // Create shared mutex - constexpr PCWSTR applyMutexName = L"WinGetConfigQueueApplyMutex"; - - for (int i = 0; !m_applyMutex && i < 2; ++i) - { - if (!m_applyMutex.try_create(applyMutexName, 0, SYNCHRONIZE)) - { - m_applyMutex.try_open(applyMutexName, SYNCHRONIZE); - } - } - - THROW_LAST_ERROR_IF(!m_applyMutex); - - // Probe for an empty queue - DWORD status = 0; - m_applyMutexScope = m_applyMutex.acquire(&status, 0); - THROW_LAST_ERROR_IF(status == WAIT_FAILED); - - if (status == WAIT_TIMEOUT) - { - return true; - } - - if (GetQueuePosition() == 0) - { - m_database.SetActiveQueueItem(m_queueItemObjectName); - return false; - } - else - { - m_applyMutexScope.reset(); - return true; - } - } - - // The configuration queue consists of a table in the shared database and cooperative handling of said table. - // At any moment, the active processor must be holding a common named mutex. - // Each active queue entry also holds their own arbitrarily named object, recorded in the table. - // - // The general mechanism to wait is: - // 1. Wait on common named mutex - // 2. Check if first in queue, including probing arbitrary named objects of entries ahead of us - // 3. If not first, wait for X * queue position, where X is sufficiently high to prevent contention on main mutex - void ConfigurationSequencer::Wait(AppInstaller::WinRT::AsyncCancellation& cancellation) - { - THROW_HR_IF(E_NOT_VALID_STATE, !m_applyMutex); - - wil::unique_event cancellationEvent; - cancellationEvent.create(); - - HANDLE waitHandles[2]; - waitHandles[0] = cancellationEvent.get(); - waitHandles[1] = m_applyMutex.get(); - - cancellation.Callback([&]() { cancellationEvent.SetEvent(); }); - auto clearCancelCallback = wil::scope_exit([&cancellation]() { cancellation.Callback([]() {}); }); - - for (;;) - { - DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); - THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); - - if (waitResult == WAIT_OBJECT_0) - { - // Cancellation - break; - } - else if (waitResult == WAIT_OBJECT_0 + 1 || waitResult == WAIT_ABANDONED_0 + 1) - { - // We now hold the apply mutex - wil::mutex_release_scope_exit applyMutexScope{ m_applyMutex.get() }; - - size_t queuePosition = GetQueuePosition(); - if (queuePosition == 0) - { - m_applyMutexScope = std::move(applyMutexScope); - m_database.SetActiveQueueItem(m_queueItemObjectName); - break; - } - else - { - applyMutexScope.reset(); - std::this_thread::sleep_for(queuePosition * 100ms); - } - } - } - } - - size_t ConfigurationSequencer::GetQueuePosition() - { - auto queueItems = m_database.GetQueueItems(); - - // If we get no queue items at all, we assume that the database doesn't support queueing. - if (queueItems.empty()) - { - return 0; - } - - size_t result = 0; - bool found = false; - - for (const auto& item : queueItems) - { - if (item.ObjectName == m_queueItemObjectName) - { - found = true; - break; - } - - std::wstring objectName = AppInstaller::Utility::ConvertToUTF16(item.ObjectName); - QueueObjectType itemObject; - if (itemObject.try_open(objectName.c_str(), SYNCHRONIZE)) - { - ++result; - } - else - { - // Best effort attempt to remove the dead queue row - try - { - m_database.RemoveQueueItem(item.ObjectName); - } - CATCH_LOG(); - } - } - - THROW_HR_IF(E_NOT_SET, !found); - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSequencer.h" +#include "ConfigurationStatus.h" +#include + +using namespace std::chrono_literals; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + ConfigurationSequencer::ConfigurationSequencer(ConfigurationDatabase& database) : m_database(database) {} + + ConfigurationSequencer::~ConfigurationSequencer() + { + // Best effort attempt to remove our queue row + try + { + m_database.RemoveQueueItem(m_queueItemObjectName); + + auto status = ConfigurationStatus::Instance(); + status->UpdateSetState(m_setInstanceIdentifier, false); + } + CATCH_LOG(); + } + + // This function creates necessary objects and records this operation into the table. + // It then performs the equivalent of `Wait` with a timeout of 0. + bool ConfigurationSequencer::Enqueue(const Configuration::ConfigurationSet& configurationSet) + { + m_setInstanceIdentifier = configurationSet.InstanceIdentifier(); + + // Create an arbitrarily named object + std::wstring objectName = L"WinGetConfigQueue_" + AppInstaller::Utility::CreateNewGuidNameWString(); + m_queueItemObjectName = AppInstaller::Utility::ConvertToUTF8(objectName); + m_queueItemObject.create(wil::EventOptions::None, objectName.c_str()); + + m_database.AddQueueItem(configurationSet, m_queueItemObjectName); + + auto statusInstance = ConfigurationStatus::Instance(); + statusInstance->UpdateSetState(m_setInstanceIdentifier, true); + + // Create shared mutex + constexpr PCWSTR applyMutexName = L"WinGetConfigQueueApplyMutex"; + + for (int i = 0; !m_applyMutex && i < 2; ++i) + { + if (!m_applyMutex.try_create(applyMutexName, 0, SYNCHRONIZE)) + { + m_applyMutex.try_open(applyMutexName, SYNCHRONIZE); + } + } + + THROW_LAST_ERROR_IF(!m_applyMutex); + + // Probe for an empty queue + DWORD status = 0; + m_applyMutexScope = m_applyMutex.acquire(&status, 0); + THROW_LAST_ERROR_IF(status == WAIT_FAILED); + + if (status == WAIT_TIMEOUT) + { + return true; + } + + if (GetQueuePosition() == 0) + { + m_database.SetActiveQueueItem(m_queueItemObjectName); + return false; + } + else + { + m_applyMutexScope.reset(); + return true; + } + } + + // The configuration queue consists of a table in the shared database and cooperative handling of said table. + // At any moment, the active processor must be holding a common named mutex. + // Each active queue entry also holds their own arbitrarily named object, recorded in the table. + // + // The general mechanism to wait is: + // 1. Wait on common named mutex + // 2. Check if first in queue, including probing arbitrary named objects of entries ahead of us + // 3. If not first, wait for X * queue position, where X is sufficiently high to prevent contention on main mutex + void ConfigurationSequencer::Wait(AppInstaller::WinRT::AsyncCancellation& cancellation) + { + THROW_HR_IF(E_NOT_VALID_STATE, !m_applyMutex); + + wil::unique_event cancellationEvent; + cancellationEvent.create(); + + HANDLE waitHandles[2]; + waitHandles[0] = cancellationEvent.get(); + waitHandles[1] = m_applyMutex.get(); + + cancellation.Callback([&]() { cancellationEvent.SetEvent(); }); + auto clearCancelCallback = wil::scope_exit([&cancellation]() { cancellation.Callback([]() {}); }); + + for (;;) + { + DWORD waitResult = WaitForMultipleObjects(ARRAYSIZE(waitHandles), waitHandles, FALSE, INFINITE); + THROW_LAST_ERROR_IF(waitResult == WAIT_FAILED); + + if (waitResult == WAIT_OBJECT_0) + { + // Cancellation + break; + } + else if (waitResult == WAIT_OBJECT_0 + 1 || waitResult == WAIT_ABANDONED_0 + 1) + { + // We now hold the apply mutex + wil::mutex_release_scope_exit applyMutexScope{ m_applyMutex.get() }; + + size_t queuePosition = GetQueuePosition(); + if (queuePosition == 0) + { + m_applyMutexScope = std::move(applyMutexScope); + m_database.SetActiveQueueItem(m_queueItemObjectName); + break; + } + else + { + applyMutexScope.reset(); + std::this_thread::sleep_for(queuePosition * 100ms); + } + } + } + } + + size_t ConfigurationSequencer::GetQueuePosition() + { + auto queueItems = m_database.GetQueueItems(); + + // If we get no queue items at all, we assume that the database doesn't support queueing. + if (queueItems.empty()) + { + return 0; + } + + size_t result = 0; + bool found = false; + + for (const auto& item : queueItems) + { + if (item.ObjectName == m_queueItemObjectName) + { + found = true; + break; + } + + std::wstring objectName = AppInstaller::Utility::ConvertToUTF16(item.ObjectName); + QueueObjectType itemObject; + if (itemObject.try_open(objectName.c_str(), SYNCHRONIZE)) + { + ++result; + } + else + { + // Best effort attempt to remove the dead queue row + try + { + m_database.RemoveQueueItem(item.ObjectName); + } + CATCH_LOG(); + } + } + + THROW_HR_IF(E_NOT_SET, !found); + + return result; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSequencer.h b/src/Microsoft.Management.Configuration/ConfigurationSequencer.h index 189b07a2f0..646b8e4dbe 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSequencer.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSequencer.h @@ -1,48 +1,48 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Database/ConfigurationDatabase.h" -#include -#include -#include - - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Allows for sequencing of configuration set applications. - struct ConfigurationSequencer - { - ConfigurationSequencer(ConfigurationDatabase& database); - - ConfigurationSequencer(const ConfigurationSequencer&) = delete; - ConfigurationSequencer& operator=(const ConfigurationSequencer&) = delete; - - ConfigurationSequencer(ConfigurationSequencer&&) = delete; - ConfigurationSequencer& operator=(ConfigurationSequencer&&) = delete; - - ~ConfigurationSequencer(); - - // Enters the current sequencer into the queue of operations. - // Returns true to indicate that this operation has been queued and must wait. - // Returns false to indicate that this operation is able to proceed (queued directly to the front). - bool Enqueue(const Configuration::ConfigurationSet& configurationSet); - - // Waits for this operation to reach the front of the queue. - // Registers a cancellation callback so that we can halt our waiting. - void Wait(AppInstaller::WinRT::AsyncCancellation& cancellation); - - private: - // Determines the effective queue position of this operation; removing queue entries that are not longer active. - // 0 is the front of the queue. - size_t GetQueuePosition(); - - using QueueObjectType = wil::unique_event; - - ConfigurationDatabase& m_database; - guid m_setInstanceIdentifier{}; - std::string m_queueItemObjectName; - QueueObjectType m_queueItemObject; - wil::unique_mutex m_applyMutex; - wil::mutex_release_scope_exit m_applyMutexScope; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/ConfigurationDatabase.h" +#include +#include +#include + + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Allows for sequencing of configuration set applications. + struct ConfigurationSequencer + { + ConfigurationSequencer(ConfigurationDatabase& database); + + ConfigurationSequencer(const ConfigurationSequencer&) = delete; + ConfigurationSequencer& operator=(const ConfigurationSequencer&) = delete; + + ConfigurationSequencer(ConfigurationSequencer&&) = delete; + ConfigurationSequencer& operator=(ConfigurationSequencer&&) = delete; + + ~ConfigurationSequencer(); + + // Enters the current sequencer into the queue of operations. + // Returns true to indicate that this operation has been queued and must wait. + // Returns false to indicate that this operation is able to proceed (queued directly to the front). + bool Enqueue(const Configuration::ConfigurationSet& configurationSet); + + // Waits for this operation to reach the front of the queue. + // Registers a cancellation callback so that we can halt our waiting. + void Wait(AppInstaller::WinRT::AsyncCancellation& cancellation); + + private: + // Determines the effective queue position of this operation; removing queue entries that are not longer active. + // 0 is the front of the queue. + size_t GetQueuePosition(); + + using QueueObjectType = wil::unique_event; + + ConfigurationDatabase& m_database; + guid m_setInstanceIdentifier{}; + std::string m_queueItemObjectName; + QueueObjectType m_queueItemObject; + wil::unique_mutex m_applyMutex; + wil::mutex_release_scope_exit m_applyMutexScope; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.cpp b/src/Microsoft.Management.Configuration/ConfigurationSet.cpp index 5c37540760..5bfa558f46 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSet.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSet.cpp @@ -1,328 +1,328 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSet.h" -#include "ConfigurationSet.g.cpp" -#include "ConfigurationSetParser.h" -#include "ConfigurationSetSerializer.h" -#include "Database/ConfigurationDatabase.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace impl - { - struct EnvironmentData - { - EnvironmentData(Configuration::ConfigurationEnvironment environment) : - Environment(environment), Context(environment.Context()), Identifier(environment.ProcessorIdentifier()), Properties(environment.ProcessorProperties()) - { - } - - EnvironmentData(EnvironmentData&&) = default; - - bool operator==(const EnvironmentData& other) const - { - return - Context == other.Context && - Identifier == other.Identifier && - ConfigurationEnvironment::AreEqual(Properties, other.Properties); - } - - Configuration::ConfigurationEnvironment Environment; - SecurityContext Context; - hstring Identifier; - Windows::Foundation::Collections::IMap Properties; - }; - - bool ContainsEnvironment(const std::vector& uniqueEnvironments, const EnvironmentData& data) - { - for (const EnvironmentData& item : uniqueEnvironments) - { - if (item == data) - { - return true; - } - } - - return false; - } - - void ComputeUniqueEnvironments(std::vector& uniqueEnvironments, const Windows::Foundation::Collections::IVector& units) - { - for (const Configuration::ConfigurationUnit& unit : units) - { - if (unit.IsActive()) - { - EnvironmentData data{ unit.Environment() }; - if (!ContainsEnvironment(uniqueEnvironments, data)) - { - uniqueEnvironments.emplace_back(std::move(data)); - } - - if (unit.IsGroup()) - { - ComputeUniqueEnvironments(uniqueEnvironments, unit.Units()); - } - } - } - } - } - - ConfigurationSet::ConfigurationSet() - { - GUID instanceIdentifier; - THROW_IF_FAILED(CoCreateGuid(&instanceIdentifier)); - m_instanceIdentifier = instanceIdentifier; - std::tie(m_schemaVersion, m_schemaUri) = ConfigurationSetParser::LatestVersion(); - } - - ConfigurationSet::ConfigurationSet(const guid& instanceIdentifier) : - m_instanceIdentifier(instanceIdentifier) - { - } - - void ConfigurationSet::Units(std::vector&& units) - { - m_units = winrt::multi_threaded_vector(std::move(units)); - } - - void ConfigurationSet::Parameters(std::vector&& value) - { - m_parameters = winrt::multi_threaded_vector(std::move(value)); - } - - hstring ConfigurationSet::Name() - { - return m_name; - } - - void ConfigurationSet::Name(const hstring& value) - { - m_name = value; - } - - hstring ConfigurationSet::Origin() - { - return m_origin; - } - - void ConfigurationSet::Origin(const hstring& value) - { - m_origin = value; - } - - hstring ConfigurationSet::Path() - { - return m_path; - } - - void ConfigurationSet::Path(const hstring& value) - { - m_path = value; - } - - guid ConfigurationSet::InstanceIdentifier() const - { - return m_instanceIdentifier; - } - - ConfigurationSetState ConfigurationSet::State() - { - auto status = ConfigurationStatus::Instance(); - return status->GetSetState(m_instanceIdentifier); - } - - clock::time_point ConfigurationSet::FirstApply() - { - auto status = ConfigurationStatus::Instance(); - return status->GetSetFirstApply(m_instanceIdentifier); - } - - clock::time_point ConfigurationSet::ApplyBegun() - { - auto status = ConfigurationStatus::Instance(); - return status->GetSetApplyBegun(m_instanceIdentifier); - } - - clock::time_point ConfigurationSet::ApplyEnded() - { - auto status = ConfigurationStatus::Instance(); - return status->GetSetApplyEnded(m_instanceIdentifier); - } - - Windows::Foundation::Collections::IVector ConfigurationSet::Units() - { - return m_units; - } - - void ConfigurationSet::Units(const Windows::Foundation::Collections::IVector& value) - { - THROW_HR_IF(E_POINTER, !value); - m_units = value; - } - - hstring ConfigurationSet::SchemaVersion() - { - return m_schemaVersion; - } - - void ConfigurationSet::SchemaVersion(const hstring& value) - { - THROW_HR_IF(E_INVALIDARG, !ConfigurationSetParser::IsRecognizedSchemaVersion(value)); - m_schemaUri = ConfigurationSetParser::GetSchemaUriForVersion(value); - m_schemaVersion = value; - } - - void ConfigurationSet::ConfigurationSetChange(com_ptr& data, const std::optional& unitInstanceIdentifier) try - { - if (unitInstanceIdentifier) - { - Windows::Foundation::Collections::IVector comUnits = m_units; - - std::vector units{ comUnits.Size() }; - units.resize(comUnits.GetMany(0, units)); - - for (const ConfigurationUnit& unit : units) - { - if (unit.InstanceIdentifier() == unitInstanceIdentifier.value()) - { - data->Unit(unit); - break; - } - } - } - - m_configurationSetChange(*get_strong(), *data); - } - CATCH_LOG(); - - event_token ConfigurationSet::ConfigurationSetChange(const Windows::Foundation::TypedEventHandler& handler) - { - if (!m_configurationSetChange) - { - auto status = ConfigurationStatus::Instance(); - std::atomic_store(&m_setChangeRegistration, status->RegisterForSetChange(*this)); - } - - return m_configurationSetChange.add(handler); - } - - void ConfigurationSet::ConfigurationSetChange(const event_token& token) noexcept - { - m_configurationSetChange.remove(token); - - if (!m_configurationSetChange) - { - std::atomic_store(&m_setChangeRegistration, {}); - } - } - - void ConfigurationSet::Serialize(const Windows::Storage::Streams::IOutputStream& stream) - { - std::unique_ptr serializer = ConfigurationSetSerializer::CreateSerializer(m_schemaVersion); - hstring result = serializer->Serialize(this); - auto resultUtf8 = winrt::to_string(result); - std::vector bytes(resultUtf8.begin(), resultUtf8.end()); - - Windows::Storage::Streams::DataWriter dataWriter{ stream }; - dataWriter.WriteBytes(bytes); - dataWriter.StoreAsync().get(); - dataWriter.DetachStream(); - } - - void ConfigurationSet::Remove() - { - ConfigurationDatabase database; - database.EnsureOpened(false); - database.RemoveSetHistory(*get_strong()); - } - - Windows::Foundation::Collections::ValueSet ConfigurationSet::Metadata() - { - return m_metadata; - } - - void ConfigurationSet::Metadata(const Windows::Foundation::Collections::ValueSet& value) - { - THROW_HR_IF(E_POINTER, !value); - m_metadata = value; - } - - Windows::Foundation::Collections::IVector ConfigurationSet::Parameters() - { - return m_parameters; - } - - void ConfigurationSet::Parameters(const Windows::Foundation::Collections::IVector& value) - { - THROW_HR_IF(E_POINTER, !value); - m_parameters = value; - } - - Windows::Foundation::Collections::ValueSet ConfigurationSet::Variables() - { - return m_variables; - } - - void ConfigurationSet::Variables(const Windows::Foundation::Collections::ValueSet& value) - { - THROW_HR_IF(E_POINTER, !value); - m_variables = value; - } - - Windows::Foundation::Uri ConfigurationSet::SchemaUri() - { - return m_schemaUri; - } - - void ConfigurationSet::SchemaUri(const Windows::Foundation::Uri& value) - { - THROW_HR_IF(E_INVALIDARG, !ConfigurationSetParser::IsRecognizedSchemaUri(value)); - m_schemaVersion = ConfigurationSetParser::GetSchemaVersionForUri(value); - m_schemaUri = value; - } - - Configuration::ConfigurationEnvironment ConfigurationSet::Environment() - { - return *m_environment; - } - - implementation::ConfigurationEnvironment& ConfigurationSet::EnvironmentInternal() - { - return *m_environment; - } - - std::vector ConfigurationSet::GetUnitEnvironmentsInternal() - { - std::vector uniqueEnvironments; - ComputeUniqueEnvironments(uniqueEnvironments, m_units); - - std::vector result; - for (const impl::EnvironmentData& data : uniqueEnvironments) - { - result.emplace_back(*make_self(data.Environment)); - } - return result; - } - - Windows::Foundation::Collections::IVector ConfigurationSet::GetUnitEnvironments() - { - return single_threaded_vector(GetUnitEnvironmentsInternal()); - } - - HRESULT STDMETHODCALLTYPE ConfigurationSet::SetLifetimeWatcher(IUnknown* watcher) - { - return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); - } - - void ConfigurationSet::SetInputHash(std::string inputHash) - { - m_inputHash = std::move(inputHash); - } - - const std::string& ConfigurationSet::GetInputHash() const - { - return m_inputHash; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSet.h" +#include "ConfigurationSet.g.cpp" +#include "ConfigurationSetParser.h" +#include "ConfigurationSetSerializer.h" +#include "Database/ConfigurationDatabase.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace impl + { + struct EnvironmentData + { + EnvironmentData(Configuration::ConfigurationEnvironment environment) : + Environment(environment), Context(environment.Context()), Identifier(environment.ProcessorIdentifier()), Properties(environment.ProcessorProperties()) + { + } + + EnvironmentData(EnvironmentData&&) = default; + + bool operator==(const EnvironmentData& other) const + { + return + Context == other.Context && + Identifier == other.Identifier && + ConfigurationEnvironment::AreEqual(Properties, other.Properties); + } + + Configuration::ConfigurationEnvironment Environment; + SecurityContext Context; + hstring Identifier; + Windows::Foundation::Collections::IMap Properties; + }; + + bool ContainsEnvironment(const std::vector& uniqueEnvironments, const EnvironmentData& data) + { + for (const EnvironmentData& item : uniqueEnvironments) + { + if (item == data) + { + return true; + } + } + + return false; + } + + void ComputeUniqueEnvironments(std::vector& uniqueEnvironments, const Windows::Foundation::Collections::IVector& units) + { + for (const Configuration::ConfigurationUnit& unit : units) + { + if (unit.IsActive()) + { + EnvironmentData data{ unit.Environment() }; + if (!ContainsEnvironment(uniqueEnvironments, data)) + { + uniqueEnvironments.emplace_back(std::move(data)); + } + + if (unit.IsGroup()) + { + ComputeUniqueEnvironments(uniqueEnvironments, unit.Units()); + } + } + } + } + } + + ConfigurationSet::ConfigurationSet() + { + GUID instanceIdentifier; + THROW_IF_FAILED(CoCreateGuid(&instanceIdentifier)); + m_instanceIdentifier = instanceIdentifier; + std::tie(m_schemaVersion, m_schemaUri) = ConfigurationSetParser::LatestVersion(); + } + + ConfigurationSet::ConfigurationSet(const guid& instanceIdentifier) : + m_instanceIdentifier(instanceIdentifier) + { + } + + void ConfigurationSet::Units(std::vector&& units) + { + m_units = winrt::multi_threaded_vector(std::move(units)); + } + + void ConfigurationSet::Parameters(std::vector&& value) + { + m_parameters = winrt::multi_threaded_vector(std::move(value)); + } + + hstring ConfigurationSet::Name() + { + return m_name; + } + + void ConfigurationSet::Name(const hstring& value) + { + m_name = value; + } + + hstring ConfigurationSet::Origin() + { + return m_origin; + } + + void ConfigurationSet::Origin(const hstring& value) + { + m_origin = value; + } + + hstring ConfigurationSet::Path() + { + return m_path; + } + + void ConfigurationSet::Path(const hstring& value) + { + m_path = value; + } + + guid ConfigurationSet::InstanceIdentifier() const + { + return m_instanceIdentifier; + } + + ConfigurationSetState ConfigurationSet::State() + { + auto status = ConfigurationStatus::Instance(); + return status->GetSetState(m_instanceIdentifier); + } + + clock::time_point ConfigurationSet::FirstApply() + { + auto status = ConfigurationStatus::Instance(); + return status->GetSetFirstApply(m_instanceIdentifier); + } + + clock::time_point ConfigurationSet::ApplyBegun() + { + auto status = ConfigurationStatus::Instance(); + return status->GetSetApplyBegun(m_instanceIdentifier); + } + + clock::time_point ConfigurationSet::ApplyEnded() + { + auto status = ConfigurationStatus::Instance(); + return status->GetSetApplyEnded(m_instanceIdentifier); + } + + Windows::Foundation::Collections::IVector ConfigurationSet::Units() + { + return m_units; + } + + void ConfigurationSet::Units(const Windows::Foundation::Collections::IVector& value) + { + THROW_HR_IF(E_POINTER, !value); + m_units = value; + } + + hstring ConfigurationSet::SchemaVersion() + { + return m_schemaVersion; + } + + void ConfigurationSet::SchemaVersion(const hstring& value) + { + THROW_HR_IF(E_INVALIDARG, !ConfigurationSetParser::IsRecognizedSchemaVersion(value)); + m_schemaUri = ConfigurationSetParser::GetSchemaUriForVersion(value); + m_schemaVersion = value; + } + + void ConfigurationSet::ConfigurationSetChange(com_ptr& data, const std::optional& unitInstanceIdentifier) try + { + if (unitInstanceIdentifier) + { + Windows::Foundation::Collections::IVector comUnits = m_units; + + std::vector units{ comUnits.Size() }; + units.resize(comUnits.GetMany(0, units)); + + for (const ConfigurationUnit& unit : units) + { + if (unit.InstanceIdentifier() == unitInstanceIdentifier.value()) + { + data->Unit(unit); + break; + } + } + } + + m_configurationSetChange(*get_strong(), *data); + } + CATCH_LOG(); + + event_token ConfigurationSet::ConfigurationSetChange(const Windows::Foundation::TypedEventHandler& handler) + { + if (!m_configurationSetChange) + { + auto status = ConfigurationStatus::Instance(); + std::atomic_store(&m_setChangeRegistration, status->RegisterForSetChange(*this)); + } + + return m_configurationSetChange.add(handler); + } + + void ConfigurationSet::ConfigurationSetChange(const event_token& token) noexcept + { + m_configurationSetChange.remove(token); + + if (!m_configurationSetChange) + { + std::atomic_store(&m_setChangeRegistration, {}); + } + } + + void ConfigurationSet::Serialize(const Windows::Storage::Streams::IOutputStream& stream) + { + std::unique_ptr serializer = ConfigurationSetSerializer::CreateSerializer(m_schemaVersion); + hstring result = serializer->Serialize(this); + auto resultUtf8 = winrt::to_string(result); + std::vector bytes(resultUtf8.begin(), resultUtf8.end()); + + Windows::Storage::Streams::DataWriter dataWriter{ stream }; + dataWriter.WriteBytes(bytes); + dataWriter.StoreAsync().get(); + dataWriter.DetachStream(); + } + + void ConfigurationSet::Remove() + { + ConfigurationDatabase database; + database.EnsureOpened(false); + database.RemoveSetHistory(*get_strong()); + } + + Windows::Foundation::Collections::ValueSet ConfigurationSet::Metadata() + { + return m_metadata; + } + + void ConfigurationSet::Metadata(const Windows::Foundation::Collections::ValueSet& value) + { + THROW_HR_IF(E_POINTER, !value); + m_metadata = value; + } + + Windows::Foundation::Collections::IVector ConfigurationSet::Parameters() + { + return m_parameters; + } + + void ConfigurationSet::Parameters(const Windows::Foundation::Collections::IVector& value) + { + THROW_HR_IF(E_POINTER, !value); + m_parameters = value; + } + + Windows::Foundation::Collections::ValueSet ConfigurationSet::Variables() + { + return m_variables; + } + + void ConfigurationSet::Variables(const Windows::Foundation::Collections::ValueSet& value) + { + THROW_HR_IF(E_POINTER, !value); + m_variables = value; + } + + Windows::Foundation::Uri ConfigurationSet::SchemaUri() + { + return m_schemaUri; + } + + void ConfigurationSet::SchemaUri(const Windows::Foundation::Uri& value) + { + THROW_HR_IF(E_INVALIDARG, !ConfigurationSetParser::IsRecognizedSchemaUri(value)); + m_schemaVersion = ConfigurationSetParser::GetSchemaVersionForUri(value); + m_schemaUri = value; + } + + Configuration::ConfigurationEnvironment ConfigurationSet::Environment() + { + return *m_environment; + } + + implementation::ConfigurationEnvironment& ConfigurationSet::EnvironmentInternal() + { + return *m_environment; + } + + std::vector ConfigurationSet::GetUnitEnvironmentsInternal() + { + std::vector uniqueEnvironments; + ComputeUniqueEnvironments(uniqueEnvironments, m_units); + + std::vector result; + for (const impl::EnvironmentData& data : uniqueEnvironments) + { + result.emplace_back(*make_self(data.Environment)); + } + return result; + } + + Windows::Foundation::Collections::IVector ConfigurationSet::GetUnitEnvironments() + { + return single_threaded_vector(GetUnitEnvironmentsInternal()); + } + + HRESULT STDMETHODCALLTYPE ConfigurationSet::SetLifetimeWatcher(IUnknown* watcher) + { + return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } + + void ConfigurationSet::SetInputHash(std::string inputHash) + { + m_inputHash = std::move(inputHash); + } + + const std::string& ConfigurationSet::GetInputHash() const + { + return m_inputHash; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSet.h b/src/Microsoft.Management.Configuration/ConfigurationSet.h index 8005f9a6d4..a3badb366c 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSet.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSet.h @@ -1,110 +1,110 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSet.g.h" -#include "ConfigurationEnvironment.h" -#include "ConfigurationSetChangeData.h" -#include "ConfigurationStatus.h" -#include -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationSet : ConfigurationSetT>, AppInstaller::WinRT::LifetimeWatcherBase, AppInstaller::WinRT::ModuleCountBase - { - using WinRT_Self = ::winrt::Microsoft::Management::Configuration::ConfigurationSet; - using ConfigurationUnit = ::winrt::Microsoft::Management::Configuration::ConfigurationUnit; - using ConfigurationParameter = ::winrt::Microsoft::Management::Configuration::ConfigurationParameter; - - ConfigurationSet(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - ConfigurationSet(const guid& instanceIdentifier); - void Units(std::vector&& units); - void Parameters(std::vector&& value); - void ConfigurationSetChange(com_ptr& data, const std::optional& unitInstanceIdentifier); - implementation::ConfigurationEnvironment& EnvironmentInternal(); - std::vector GetUnitEnvironmentsInternal(); -#endif - - hstring Name(); - void Name(const hstring& value); - - hstring Origin(); - void Origin(const hstring& value); - - hstring Path(); - void Path(const hstring& value); - - guid InstanceIdentifier() const; - ConfigurationSetState State(); - clock::time_point FirstApply(); - clock::time_point ApplyBegun(); - clock::time_point ApplyEnded(); - - Windows::Foundation::Collections::IVector Units(); - void Units(const Windows::Foundation::Collections::IVector& value); - - hstring SchemaVersion(); - void SchemaVersion(const hstring& value); - - event_token ConfigurationSetChange(const Windows::Foundation::TypedEventHandler& handler); - void ConfigurationSetChange(const event_token& token) noexcept; - - void Serialize(const Windows::Storage::Streams::IOutputStream& stream); - - void Remove(); - - Windows::Foundation::Collections::ValueSet Metadata(); - void Metadata(const Windows::Foundation::Collections::ValueSet& value); - - Windows::Foundation::Collections::IVector Parameters(); - void Parameters(const Windows::Foundation::Collections::IVector& value); - - Windows::Foundation::Collections::ValueSet Variables(); - void Variables(const Windows::Foundation::Collections::ValueSet& value); - - Windows::Foundation::Uri SchemaUri(); - void SchemaUri(const Windows::Foundation::Uri& value); - - Configuration::ConfigurationEnvironment Environment(); - - Windows::Foundation::Collections::IVector GetUnitEnvironments(); - - HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void SetInputHash(std::string inputHash); - const std::string& GetInputHash() const; - - private: - hstring m_name; - hstring m_origin; - hstring m_path; - guid m_instanceIdentifier; - clock::time_point m_firstApply{}; - Windows::Foundation::Collections::IVector m_units{ winrt::multi_threaded_vector() }; - hstring m_schemaVersion; - winrt::event> m_configurationSetChange; - Windows::Foundation::Collections::ValueSet m_metadata; - Windows::Foundation::Collections::IVector m_parameters{ winrt::multi_threaded_vector() }; - Windows::Foundation::Collections::ValueSet m_variables; - Windows::Foundation::Uri m_schemaUri = nullptr; - std::string m_inputHash; - std::shared_ptr m_setChangeRegistration; - com_ptr m_environment{ make_self() }; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Configuration::factory_implementation -{ - struct ConfigurationSet : ConfigurationSetT - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSet.g.h" +#include "ConfigurationEnvironment.h" +#include "ConfigurationSetChangeData.h" +#include "ConfigurationStatus.h" +#include +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationSet : ConfigurationSetT>, AppInstaller::WinRT::LifetimeWatcherBase, AppInstaller::WinRT::ModuleCountBase + { + using WinRT_Self = ::winrt::Microsoft::Management::Configuration::ConfigurationSet; + using ConfigurationUnit = ::winrt::Microsoft::Management::Configuration::ConfigurationUnit; + using ConfigurationParameter = ::winrt::Microsoft::Management::Configuration::ConfigurationParameter; + + ConfigurationSet(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + ConfigurationSet(const guid& instanceIdentifier); + void Units(std::vector&& units); + void Parameters(std::vector&& value); + void ConfigurationSetChange(com_ptr& data, const std::optional& unitInstanceIdentifier); + implementation::ConfigurationEnvironment& EnvironmentInternal(); + std::vector GetUnitEnvironmentsInternal(); +#endif + + hstring Name(); + void Name(const hstring& value); + + hstring Origin(); + void Origin(const hstring& value); + + hstring Path(); + void Path(const hstring& value); + + guid InstanceIdentifier() const; + ConfigurationSetState State(); + clock::time_point FirstApply(); + clock::time_point ApplyBegun(); + clock::time_point ApplyEnded(); + + Windows::Foundation::Collections::IVector Units(); + void Units(const Windows::Foundation::Collections::IVector& value); + + hstring SchemaVersion(); + void SchemaVersion(const hstring& value); + + event_token ConfigurationSetChange(const Windows::Foundation::TypedEventHandler& handler); + void ConfigurationSetChange(const event_token& token) noexcept; + + void Serialize(const Windows::Storage::Streams::IOutputStream& stream); + + void Remove(); + + Windows::Foundation::Collections::ValueSet Metadata(); + void Metadata(const Windows::Foundation::Collections::ValueSet& value); + + Windows::Foundation::Collections::IVector Parameters(); + void Parameters(const Windows::Foundation::Collections::IVector& value); + + Windows::Foundation::Collections::ValueSet Variables(); + void Variables(const Windows::Foundation::Collections::ValueSet& value); + + Windows::Foundation::Uri SchemaUri(); + void SchemaUri(const Windows::Foundation::Uri& value); + + Configuration::ConfigurationEnvironment Environment(); + + Windows::Foundation::Collections::IVector GetUnitEnvironments(); + + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void SetInputHash(std::string inputHash); + const std::string& GetInputHash() const; + + private: + hstring m_name; + hstring m_origin; + hstring m_path; + guid m_instanceIdentifier; + clock::time_point m_firstApply{}; + Windows::Foundation::Collections::IVector m_units{ winrt::multi_threaded_vector() }; + hstring m_schemaVersion; + winrt::event> m_configurationSetChange; + Windows::Foundation::Collections::ValueSet m_metadata; + Windows::Foundation::Collections::IVector m_parameters{ winrt::multi_threaded_vector() }; + Windows::Foundation::Collections::ValueSet m_variables; + Windows::Foundation::Uri m_schemaUri = nullptr; + std::string m_inputHash; + std::shared_ptr m_setChangeRegistration; + com_ptr m_environment{ make_self() }; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Configuration::factory_implementation +{ + struct ConfigurationSet : ConfigurationSetT + { + }; +} +#endif diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp index e79512ef8a..049d646af7 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.cpp @@ -1,542 +1,542 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetApplyProcessor.h" -#include "ConfigurationSetChangeData.h" -#include "ExceptionResultHelpers.h" - -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - constexpr std::wstring_view s_ResourceType_RunCommandOnSet = L"Microsoft.DSC.Transitional/RunCommandOnSet"; - - std::string GetNormalizedIdentifier(hstring identifier) - { - using namespace AppInstaller::Utility; - return FoldCase(NormalizedString{ identifier }); - } - - bool AssertFilter(ConfigurationUnitIntent intent) - { - return intent == ConfigurationUnitIntent::Assert; - } - - bool InformFilter(ConfigurationUnitIntent intent) - { - return intent == ConfigurationUnitIntent::Inform; - } - - bool ApplyFilter(ConfigurationUnitIntent intent) - { - return intent == ConfigurationUnitIntent::Apply || intent == ConfigurationUnitIntent::Unknown; - } - - // Check if a unit should always be applied. No TestSettings is needed. - bool ShouldApplyAlways(const Configuration::ConfigurationUnit& unit) - { - if (AppInstaller::Utility::CaseInsensitiveEquals(s_ResourceType_RunCommandOnSet, unit.Type())) - { - return true; - } - - return false; - } - } - - ConfigurationSetApplyProcessor::ConfigurationSetApplyProcessor( - const Configuration::ConfigurationSet& configurationSet, - IConfigurationSetProcessor setProcessor, - progress_type&& progress) : - m_configurationSet(configurationSet), - m_setProcessor(std::move(setProcessor)), - m_result(make_self>()), - m_progress(std::move(progress)) - { - // Create a copy of the set of configuration units - auto unitsView = configurationSet.Units(); - std::vector unitsToProcess{ unitsView.Size() }; - unitsView.GetMany(0, unitsToProcess); - - // Create the unit info vector from these units - for (const auto& unit : unitsToProcess) - { - m_unitInfo.emplace_back(unit); - m_result->UnitResults().Append(*m_unitInfo.back().Result); - } - - m_progress.Result(*m_result); - } - - void ConfigurationSetApplyProcessor::Process(bool preProcessOnly) - { - if (PreProcess() && !preProcessOnly) - { - ProcessInternal(HasProcessedSuccessfully, &ConfigurationSetApplyProcessor::ProcessUnit, true); - } - } - - IApplyGroupSettingsResult ConfigurationSetApplyProcessor::Result() const - { - return *m_result; - } - - ConfigurationSetApplyProcessor::UnitInfo::UnitInfo(const Configuration::ConfigurationUnit& unit) : - Unit(unit), Result(make_self>()) - { - Result->Unit(unit); - ResultInformation = Result->ResultInformationInternal(); - } - - bool ConfigurationSetApplyProcessor::PreProcess() - { - bool result = true; - - for (size_t i = 0; i < m_unitInfo.size(); ++i) - { - if (!AddUnitToMap(m_unitInfo[i], i)) - { - result = false; - } - } - - if (!result) - { - // This is the only error that adding to the map can produce - m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER); - return false; - } - - for (UnitInfo& unitInfo : m_unitInfo) - { - for (hstring dependencyHstring : unitInfo.Unit.Dependencies()) - { - // Throw out empty dependency strings - if (dependencyHstring.empty()) - { - continue; - } - - std::string dependency = GetNormalizedIdentifier(dependencyHstring); - auto itr = m_idToUnitInfoIndex.find(dependency); - if (itr == m_idToUnitInfoIndex.end()) - { - AICLI_LOG(Config, Error, << "Found missing dependency: " << dependency); - unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, ConfigurationUnitResultSource::ConfigurationSet); - unitInfo.ResultInformation->Details(dependencyHstring); - SendProgress(ConfigurationUnitState::Completed, unitInfo); - result = false; - // TODO: Consider collecting all missing dependencies, for now just the first - break; - } - else - { - unitInfo.DependencyIndices.emplace_back(itr->second); - } - } - } - - if (!result) - { - // This is the only error that adding to the map can produce - m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY); - return false; - } - - if (!ProcessInternal(HasPreprocessed, &ConfigurationSetApplyProcessor::MarkPreprocessed)) - { - // The preprocessing simulates processing as if every unit run was successful. - // If it fails, this means that there are unit definitions whose dependencies cannot be satisfied. - // The only reason for that is a cycle in the dependency graph somewhere. - m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE); - return false; - } - - return true; - } - - bool ConfigurationSetApplyProcessor::AddUnitToMap(UnitInfo& unitInfo, size_t unitInfoIndex) - { - hstring originalIdentifier = unitInfo.Unit.Identifier(); - if (originalIdentifier.empty()) - { - return true; - } - - std::string identifier = GetNormalizedIdentifier(originalIdentifier); - - auto itr = m_idToUnitInfoIndex.find(identifier); - if (itr != m_idToUnitInfoIndex.end()) - { - AICLI_LOG(Config, Error, << "Found duplicate identifier: " << identifier); - // Found a duplicate identifier, mark both as such - m_unitInfo[itr->second].ResultInformation->Initialize(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, ConfigurationUnitResultSource::ConfigurationSet); - SendProgressIfNotComplete(ConfigurationUnitState::Completed, m_unitInfo[itr->second]); - unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, ConfigurationUnitResultSource::ConfigurationSet); - SendProgress(ConfigurationUnitState::Completed, unitInfo); - return false; - } - else - { - m_idToUnitInfoIndex.emplace(std::move(identifier), unitInfoIndex); - return true; - } - } - - bool ConfigurationSetApplyProcessor::ProcessInternal(CheckDependencyPtr checkDependencyFunction, ProcessUnitPtr processUnitFunction, bool sendProgress) - { - // Create the set of units that need to be processed - std::vector unitsToProcess; - for (size_t i = 0, size = m_unitInfo.size(); i < size; ++i) - { - unitsToProcess.emplace_back(i); - } - - // Always process all ConfigurationUnitIntent::Assert first - if (!ProcessIntentInternal( - unitsToProcess, - checkDependencyFunction, - processUnitFunction, - AssertFilter, - WINGET_CONFIG_ERROR_ASSERTION_FAILED, - WINGET_CONFIG_ERROR_ASSERTION_FAILED, - sendProgress)) - { - return false; - } - - // Then all ConfigurationUnitIntent::Inform - if (!ProcessIntentInternal( - unitsToProcess, - checkDependencyFunction, - processUnitFunction, - InformFilter, - WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, - WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, - sendProgress)) - { - return false; - } - - // Then all ConfigurationUnitIntent::Apply - return ProcessIntentInternal( - unitsToProcess, - checkDependencyFunction, - processUnitFunction, - ApplyFilter, - E_FAIL, // This should not happen as there are no other intents left - WINGET_CONFIG_ERROR_SET_APPLY_FAILED, - sendProgress); - } - - bool ConfigurationSetApplyProcessor::ProcessIntentInternal( - std::vector& unitsToProcess, - CheckDependencyPtr checkDependencyFunction, - ProcessUnitPtr processUnitFunction, - IntentFilterPtr intentFilter, - hresult errorForOtherIntents, - hresult errorForFailures, - bool sendProgress) - { - // Always process the first item in the list that is available to be processed - bool hasProcessed = true; - bool hasFailure = false; - while (hasProcessed) - { - hasProcessed = false; - for (auto itr = unitsToProcess.begin(), end = unitsToProcess.end(); itr != end; ++itr) - { - UnitInfo& unitInfo = m_unitInfo[*itr]; - if (HasIntentAndSatisfiedDependencies(unitInfo, intentFilter, checkDependencyFunction)) - { - if (!(this->*processUnitFunction)(unitInfo)) - { - hasFailure = true; - } - unitsToProcess.erase(itr); - hasProcessed = true; - break; - } - } - } - - // Mark all remaining items with intent as failed due to dependency - bool hasRemainingDependencies = false; - for (size_t index : unitsToProcess) - { - UnitInfo& unitInfo = m_unitInfo[index]; - if (intentFilter(unitInfo.Unit.Intent())) - { - hasRemainingDependencies = true; - unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, ConfigurationUnitResultSource::Precondition); - if (sendProgress) - { - SendProgress(ConfigurationUnitState::Skipped, unitInfo); - } - } - } - - // Any failures are fatal, mark all other units as failed due to that - if (hasFailure || hasRemainingDependencies) - { - for (size_t index : unitsToProcess) - { - UnitInfo& unitInfo = m_unitInfo[index]; - if (!intentFilter(unitInfo.Unit.Intent())) - { - unitInfo.ResultInformation->Initialize(errorForOtherIntents, ConfigurationUnitResultSource::Precondition); - if (sendProgress) - { - SendProgress(ConfigurationUnitState::Skipped, unitInfo); - } - } - } - - if (hasFailure) - { - m_result->ResultInformationInternal()->ResultCode(errorForFailures); - } - else // hasRemainingDependencies - { - m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED); - } - return false; - } - - return true; - } - - bool ConfigurationSetApplyProcessor::HasIntentAndSatisfiedDependencies( - const UnitInfo& unitInfo, - IntentFilterPtr intentFilter, - CheckDependencyPtr checkDependencyFunction) const - { - bool result = false; - - if (intentFilter(unitInfo.Unit.Intent())) - { - result = true; - for (size_t dependencyIndex : unitInfo.DependencyIndices) - { - if (!checkDependencyFunction(m_unitInfo[dependencyIndex])) - { - result = false; - break; - } - } - } - - return result; - } - - bool ConfigurationSetApplyProcessor::HasPreprocessed(const UnitInfo& unitInfo) - { - return unitInfo.PreProcessed; - } - - bool ConfigurationSetApplyProcessor::MarkPreprocessed(UnitInfo& unitInfo) - { - unitInfo.PreProcessed = true; - return true; - } - - bool ConfigurationSetApplyProcessor::HasProcessedSuccessfully(const UnitInfo& unitInfo) - { - return unitInfo.Processed && SUCCEEDED(unitInfo.ResultInformation->ResultCode()); - } - - bool ConfigurationSetApplyProcessor::ProcessUnit(UnitInfo& unitInfo) - { - m_progress.ThrowIfCancelled(); - - IConfigurationUnitProcessor unitProcessor; - - // Once we get this far, consider the unit processed even if we fail to create the actual processor. - unitInfo.Processed = true; - - if (!unitInfo.Unit.IsActive()) - { - // If the unit is requested to be skipped, we mark it with a failure to prevent any dependency from running. - // But we return true from this function to indicate a successful "processing". - unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_MANUALLY_SKIPPED, ConfigurationUnitResultSource::Precondition); - SendProgress(ConfigurationUnitState::Skipped, unitInfo); - return true; - } - - // Send a progress event that we are starting, and prepare one for completion when we exit the function - SendProgress(ConfigurationUnitState::InProgress, unitInfo); - auto sendCompletedProgress = wil::scope_exit([this, &unitInfo]() { SendProgress(ConfigurationUnitState::Completed, unitInfo); }); - - try - { - unitProcessor = m_setProcessor.CreateUnitProcessor(unitInfo.Unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitInfo.ResultInformation); - return false; - } - - // As the process of creating the unit processor could take a while, check for cancellation again - m_progress.ThrowIfCancelled(); - - bool result = false; - - try - { - switch (unitInfo.Unit.Intent()) - { - case ConfigurationUnitIntent::Assert: - { - ITestSettingsResult settingsResult = unitProcessor.TestSettings(); - - if (settingsResult.TestResult() == ConfigurationTestResult::Positive) - { - result = true; - } - else if (settingsResult.TestResult() == ConfigurationTestResult::Negative) - { - unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_ASSERTION_FAILED, ConfigurationUnitResultSource::Precondition); - } - else if (settingsResult.TestResult() == ConfigurationTestResult::Failed) - { - unitInfo.ResultInformation->Initialize(settingsResult.ResultInformation()); - } - else - { - unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); - } - } - break; - - case ConfigurationUnitIntent::Inform: - { - // Force the processor to retrieve the settings - IGetSettingsResult settingsResult = unitProcessor.GetSettings(); - if (SUCCEEDED(settingsResult.ResultInformation().ResultCode())) - { - result = true; - } - else - { - unitInfo.ResultInformation->Initialize(settingsResult.ResultInformation()); - } - } - break; - - case ConfigurationUnitIntent::Apply: - case ConfigurationUnitIntent::Unknown: - { - // Check for a group processor and let it do the work if present - IConfigurationGroupProcessor groupProcessor = unitProcessor.try_as(); - - if (groupProcessor) - { - auto applyOperation = groupProcessor.ApplyGroupSettingsAsync([&](const auto&, const IApplyGroupMemberSettingsResult& unitResult) - { - m_progress.Progress(unitResult); - }); - - // Cancel the inner operation if we are cancelled - m_progress.Callback([applyOperation]() { applyOperation.Cancel(); }); - - IApplyGroupSettingsResult groupResult = applyOperation.get(); - - // Put all of the group's unit results in our unit results - bool groupPreviouslyInDesiredState = true; - - for (const auto& groupUnitResult : groupResult.UnitResults()) - { - m_result->UnitResults().Append(groupUnitResult); - groupPreviouslyInDesiredState = groupPreviouslyInDesiredState && groupUnitResult.PreviouslyInDesiredState(); - } - - // Copy the group result into the existing unit result for the group - unitInfo.Result->PreviouslyInDesiredState(groupPreviouslyInDesiredState); - unitInfo.ResultInformation->Initialize(groupResult.ResultInformation()); - - if (SUCCEEDED(unitInfo.ResultInformation->ResultCode())) - { - unitInfo.Result->RebootRequired(groupResult.RebootRequired()); - result = true; - } - } - else - { - ITestSettingsResult testSettingsResult = nullptr; - bool applyAlways = ShouldApplyAlways(unitProcessor.Unit()); - - if (!applyAlways) - { - testSettingsResult = unitProcessor.TestSettings(); - } - - if (applyAlways || testSettingsResult.TestResult() == ConfigurationTestResult::Negative) - { - // Just in case testing took a while, check for cancellation before moving on to applying - m_progress.ThrowIfCancelled(); - - IApplySettingsResult applySettingsResult = unitProcessor.ApplySettings(); - if (SUCCEEDED(applySettingsResult.ResultInformation().ResultCode())) - { - unitInfo.Result->RebootRequired(applySettingsResult.RebootRequired()); - result = true; - } - else - { - unitInfo.ResultInformation->Initialize(applySettingsResult.ResultInformation()); - } - } - else if (testSettingsResult.TestResult() == ConfigurationTestResult::Positive) - { - unitInfo.Result->PreviouslyInDesiredState(true); - result = true; - } - else if (testSettingsResult.TestResult() == ConfigurationTestResult::Failed) - { - unitInfo.ResultInformation->Initialize(testSettingsResult.ResultInformation()); - } - else - { - unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); - } - } - } - break; - - default: - unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); - break; - } - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitInfo.ResultInformation); - } - - return result; - } - - void ConfigurationSetApplyProcessor::SendProgress(ConfigurationUnitState state, const UnitInfo& unitInfo) - { - unitInfo.Result->State(state); - - try - { - m_progress.Progress(*unitInfo.Result); - } - CATCH_LOG(); - } - - void ConfigurationSetApplyProcessor::SendProgressIfNotComplete(ConfigurationUnitState state, const UnitInfo& unitInfo) - { - if (unitInfo.Result->State() != ConfigurationUnitState::Completed) - { - SendProgress(state, unitInfo); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetApplyProcessor.h" +#include "ConfigurationSetChangeData.h" +#include "ExceptionResultHelpers.h" + +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + constexpr std::wstring_view s_ResourceType_RunCommandOnSet = L"Microsoft.DSC.Transitional/RunCommandOnSet"; + + std::string GetNormalizedIdentifier(hstring identifier) + { + using namespace AppInstaller::Utility; + return FoldCase(NormalizedString{ identifier }); + } + + bool AssertFilter(ConfigurationUnitIntent intent) + { + return intent == ConfigurationUnitIntent::Assert; + } + + bool InformFilter(ConfigurationUnitIntent intent) + { + return intent == ConfigurationUnitIntent::Inform; + } + + bool ApplyFilter(ConfigurationUnitIntent intent) + { + return intent == ConfigurationUnitIntent::Apply || intent == ConfigurationUnitIntent::Unknown; + } + + // Check if a unit should always be applied. No TestSettings is needed. + bool ShouldApplyAlways(const Configuration::ConfigurationUnit& unit) + { + if (AppInstaller::Utility::CaseInsensitiveEquals(s_ResourceType_RunCommandOnSet, unit.Type())) + { + return true; + } + + return false; + } + } + + ConfigurationSetApplyProcessor::ConfigurationSetApplyProcessor( + const Configuration::ConfigurationSet& configurationSet, + IConfigurationSetProcessor setProcessor, + progress_type&& progress) : + m_configurationSet(configurationSet), + m_setProcessor(std::move(setProcessor)), + m_result(make_self>()), + m_progress(std::move(progress)) + { + // Create a copy of the set of configuration units + auto unitsView = configurationSet.Units(); + std::vector unitsToProcess{ unitsView.Size() }; + unitsView.GetMany(0, unitsToProcess); + + // Create the unit info vector from these units + for (const auto& unit : unitsToProcess) + { + m_unitInfo.emplace_back(unit); + m_result->UnitResults().Append(*m_unitInfo.back().Result); + } + + m_progress.Result(*m_result); + } + + void ConfigurationSetApplyProcessor::Process(bool preProcessOnly) + { + if (PreProcess() && !preProcessOnly) + { + ProcessInternal(HasProcessedSuccessfully, &ConfigurationSetApplyProcessor::ProcessUnit, true); + } + } + + IApplyGroupSettingsResult ConfigurationSetApplyProcessor::Result() const + { + return *m_result; + } + + ConfigurationSetApplyProcessor::UnitInfo::UnitInfo(const Configuration::ConfigurationUnit& unit) : + Unit(unit), Result(make_self>()) + { + Result->Unit(unit); + ResultInformation = Result->ResultInformationInternal(); + } + + bool ConfigurationSetApplyProcessor::PreProcess() + { + bool result = true; + + for (size_t i = 0; i < m_unitInfo.size(); ++i) + { + if (!AddUnitToMap(m_unitInfo[i], i)) + { + result = false; + } + } + + if (!result) + { + // This is the only error that adding to the map can produce + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER); + return false; + } + + for (UnitInfo& unitInfo : m_unitInfo) + { + for (hstring dependencyHstring : unitInfo.Unit.Dependencies()) + { + // Throw out empty dependency strings + if (dependencyHstring.empty()) + { + continue; + } + + std::string dependency = GetNormalizedIdentifier(dependencyHstring); + auto itr = m_idToUnitInfoIndex.find(dependency); + if (itr == m_idToUnitInfoIndex.end()) + { + AICLI_LOG(Config, Error, << "Found missing dependency: " << dependency); + unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY, ConfigurationUnitResultSource::ConfigurationSet); + unitInfo.ResultInformation->Details(dependencyHstring); + SendProgress(ConfigurationUnitState::Completed, unitInfo); + result = false; + // TODO: Consider collecting all missing dependencies, for now just the first + break; + } + else + { + unitInfo.DependencyIndices.emplace_back(itr->second); + } + } + } + + if (!result) + { + // This is the only error that adding to the map can produce + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_MISSING_DEPENDENCY); + return false; + } + + if (!ProcessInternal(HasPreprocessed, &ConfigurationSetApplyProcessor::MarkPreprocessed)) + { + // The preprocessing simulates processing as if every unit run was successful. + // If it fails, this means that there are unit definitions whose dependencies cannot be satisfied. + // The only reason for that is a cycle in the dependency graph somewhere. + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_SET_DEPENDENCY_CYCLE); + return false; + } + + return true; + } + + bool ConfigurationSetApplyProcessor::AddUnitToMap(UnitInfo& unitInfo, size_t unitInfoIndex) + { + hstring originalIdentifier = unitInfo.Unit.Identifier(); + if (originalIdentifier.empty()) + { + return true; + } + + std::string identifier = GetNormalizedIdentifier(originalIdentifier); + + auto itr = m_idToUnitInfoIndex.find(identifier); + if (itr != m_idToUnitInfoIndex.end()) + { + AICLI_LOG(Config, Error, << "Found duplicate identifier: " << identifier); + // Found a duplicate identifier, mark both as such + m_unitInfo[itr->second].ResultInformation->Initialize(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, ConfigurationUnitResultSource::ConfigurationSet); + SendProgressIfNotComplete(ConfigurationUnitState::Completed, m_unitInfo[itr->second]); + unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_DUPLICATE_IDENTIFIER, ConfigurationUnitResultSource::ConfigurationSet); + SendProgress(ConfigurationUnitState::Completed, unitInfo); + return false; + } + else + { + m_idToUnitInfoIndex.emplace(std::move(identifier), unitInfoIndex); + return true; + } + } + + bool ConfigurationSetApplyProcessor::ProcessInternal(CheckDependencyPtr checkDependencyFunction, ProcessUnitPtr processUnitFunction, bool sendProgress) + { + // Create the set of units that need to be processed + std::vector unitsToProcess; + for (size_t i = 0, size = m_unitInfo.size(); i < size; ++i) + { + unitsToProcess.emplace_back(i); + } + + // Always process all ConfigurationUnitIntent::Assert first + if (!ProcessIntentInternal( + unitsToProcess, + checkDependencyFunction, + processUnitFunction, + AssertFilter, + WINGET_CONFIG_ERROR_ASSERTION_FAILED, + WINGET_CONFIG_ERROR_ASSERTION_FAILED, + sendProgress)) + { + return false; + } + + // Then all ConfigurationUnitIntent::Inform + if (!ProcessIntentInternal( + unitsToProcess, + checkDependencyFunction, + processUnitFunction, + InformFilter, + WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, + WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, + sendProgress)) + { + return false; + } + + // Then all ConfigurationUnitIntent::Apply + return ProcessIntentInternal( + unitsToProcess, + checkDependencyFunction, + processUnitFunction, + ApplyFilter, + E_FAIL, // This should not happen as there are no other intents left + WINGET_CONFIG_ERROR_SET_APPLY_FAILED, + sendProgress); + } + + bool ConfigurationSetApplyProcessor::ProcessIntentInternal( + std::vector& unitsToProcess, + CheckDependencyPtr checkDependencyFunction, + ProcessUnitPtr processUnitFunction, + IntentFilterPtr intentFilter, + hresult errorForOtherIntents, + hresult errorForFailures, + bool sendProgress) + { + // Always process the first item in the list that is available to be processed + bool hasProcessed = true; + bool hasFailure = false; + while (hasProcessed) + { + hasProcessed = false; + for (auto itr = unitsToProcess.begin(), end = unitsToProcess.end(); itr != end; ++itr) + { + UnitInfo& unitInfo = m_unitInfo[*itr]; + if (HasIntentAndSatisfiedDependencies(unitInfo, intentFilter, checkDependencyFunction)) + { + if (!(this->*processUnitFunction)(unitInfo)) + { + hasFailure = true; + } + unitsToProcess.erase(itr); + hasProcessed = true; + break; + } + } + } + + // Mark all remaining items with intent as failed due to dependency + bool hasRemainingDependencies = false; + for (size_t index : unitsToProcess) + { + UnitInfo& unitInfo = m_unitInfo[index]; + if (intentFilter(unitInfo.Unit.Intent())) + { + hasRemainingDependencies = true; + unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED, ConfigurationUnitResultSource::Precondition); + if (sendProgress) + { + SendProgress(ConfigurationUnitState::Skipped, unitInfo); + } + } + } + + // Any failures are fatal, mark all other units as failed due to that + if (hasFailure || hasRemainingDependencies) + { + for (size_t index : unitsToProcess) + { + UnitInfo& unitInfo = m_unitInfo[index]; + if (!intentFilter(unitInfo.Unit.Intent())) + { + unitInfo.ResultInformation->Initialize(errorForOtherIntents, ConfigurationUnitResultSource::Precondition); + if (sendProgress) + { + SendProgress(ConfigurationUnitState::Skipped, unitInfo); + } + } + } + + if (hasFailure) + { + m_result->ResultInformationInternal()->ResultCode(errorForFailures); + } + else // hasRemainingDependencies + { + m_result->ResultInformationInternal()->ResultCode(WINGET_CONFIG_ERROR_DEPENDENCY_UNSATISFIED); + } + return false; + } + + return true; + } + + bool ConfigurationSetApplyProcessor::HasIntentAndSatisfiedDependencies( + const UnitInfo& unitInfo, + IntentFilterPtr intentFilter, + CheckDependencyPtr checkDependencyFunction) const + { + bool result = false; + + if (intentFilter(unitInfo.Unit.Intent())) + { + result = true; + for (size_t dependencyIndex : unitInfo.DependencyIndices) + { + if (!checkDependencyFunction(m_unitInfo[dependencyIndex])) + { + result = false; + break; + } + } + } + + return result; + } + + bool ConfigurationSetApplyProcessor::HasPreprocessed(const UnitInfo& unitInfo) + { + return unitInfo.PreProcessed; + } + + bool ConfigurationSetApplyProcessor::MarkPreprocessed(UnitInfo& unitInfo) + { + unitInfo.PreProcessed = true; + return true; + } + + bool ConfigurationSetApplyProcessor::HasProcessedSuccessfully(const UnitInfo& unitInfo) + { + return unitInfo.Processed && SUCCEEDED(unitInfo.ResultInformation->ResultCode()); + } + + bool ConfigurationSetApplyProcessor::ProcessUnit(UnitInfo& unitInfo) + { + m_progress.ThrowIfCancelled(); + + IConfigurationUnitProcessor unitProcessor; + + // Once we get this far, consider the unit processed even if we fail to create the actual processor. + unitInfo.Processed = true; + + if (!unitInfo.Unit.IsActive()) + { + // If the unit is requested to be skipped, we mark it with a failure to prevent any dependency from running. + // But we return true from this function to indicate a successful "processing". + unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_MANUALLY_SKIPPED, ConfigurationUnitResultSource::Precondition); + SendProgress(ConfigurationUnitState::Skipped, unitInfo); + return true; + } + + // Send a progress event that we are starting, and prepare one for completion when we exit the function + SendProgress(ConfigurationUnitState::InProgress, unitInfo); + auto sendCompletedProgress = wil::scope_exit([this, &unitInfo]() { SendProgress(ConfigurationUnitState::Completed, unitInfo); }); + + try + { + unitProcessor = m_setProcessor.CreateUnitProcessor(unitInfo.Unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitInfo.ResultInformation); + return false; + } + + // As the process of creating the unit processor could take a while, check for cancellation again + m_progress.ThrowIfCancelled(); + + bool result = false; + + try + { + switch (unitInfo.Unit.Intent()) + { + case ConfigurationUnitIntent::Assert: + { + ITestSettingsResult settingsResult = unitProcessor.TestSettings(); + + if (settingsResult.TestResult() == ConfigurationTestResult::Positive) + { + result = true; + } + else if (settingsResult.TestResult() == ConfigurationTestResult::Negative) + { + unitInfo.ResultInformation->Initialize(WINGET_CONFIG_ERROR_ASSERTION_FAILED, ConfigurationUnitResultSource::Precondition); + } + else if (settingsResult.TestResult() == ConfigurationTestResult::Failed) + { + unitInfo.ResultInformation->Initialize(settingsResult.ResultInformation()); + } + else + { + unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); + } + } + break; + + case ConfigurationUnitIntent::Inform: + { + // Force the processor to retrieve the settings + IGetSettingsResult settingsResult = unitProcessor.GetSettings(); + if (SUCCEEDED(settingsResult.ResultInformation().ResultCode())) + { + result = true; + } + else + { + unitInfo.ResultInformation->Initialize(settingsResult.ResultInformation()); + } + } + break; + + case ConfigurationUnitIntent::Apply: + case ConfigurationUnitIntent::Unknown: + { + // Check for a group processor and let it do the work if present + IConfigurationGroupProcessor groupProcessor = unitProcessor.try_as(); + + if (groupProcessor) + { + auto applyOperation = groupProcessor.ApplyGroupSettingsAsync([&](const auto&, const IApplyGroupMemberSettingsResult& unitResult) + { + m_progress.Progress(unitResult); + }); + + // Cancel the inner operation if we are cancelled + m_progress.Callback([applyOperation]() { applyOperation.Cancel(); }); + + IApplyGroupSettingsResult groupResult = applyOperation.get(); + + // Put all of the group's unit results in our unit results + bool groupPreviouslyInDesiredState = true; + + for (const auto& groupUnitResult : groupResult.UnitResults()) + { + m_result->UnitResults().Append(groupUnitResult); + groupPreviouslyInDesiredState = groupPreviouslyInDesiredState && groupUnitResult.PreviouslyInDesiredState(); + } + + // Copy the group result into the existing unit result for the group + unitInfo.Result->PreviouslyInDesiredState(groupPreviouslyInDesiredState); + unitInfo.ResultInformation->Initialize(groupResult.ResultInformation()); + + if (SUCCEEDED(unitInfo.ResultInformation->ResultCode())) + { + unitInfo.Result->RebootRequired(groupResult.RebootRequired()); + result = true; + } + } + else + { + ITestSettingsResult testSettingsResult = nullptr; + bool applyAlways = ShouldApplyAlways(unitProcessor.Unit()); + + if (!applyAlways) + { + testSettingsResult = unitProcessor.TestSettings(); + } + + if (applyAlways || testSettingsResult.TestResult() == ConfigurationTestResult::Negative) + { + // Just in case testing took a while, check for cancellation before moving on to applying + m_progress.ThrowIfCancelled(); + + IApplySettingsResult applySettingsResult = unitProcessor.ApplySettings(); + if (SUCCEEDED(applySettingsResult.ResultInformation().ResultCode())) + { + unitInfo.Result->RebootRequired(applySettingsResult.RebootRequired()); + result = true; + } + else + { + unitInfo.ResultInformation->Initialize(applySettingsResult.ResultInformation()); + } + } + else if (testSettingsResult.TestResult() == ConfigurationTestResult::Positive) + { + unitInfo.Result->PreviouslyInDesiredState(true); + result = true; + } + else if (testSettingsResult.TestResult() == ConfigurationTestResult::Failed) + { + unitInfo.ResultInformation->Initialize(testSettingsResult.ResultInformation()); + } + else + { + unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); + } + } + } + break; + + default: + unitInfo.ResultInformation->Initialize(E_UNEXPECTED, ConfigurationUnitResultSource::Internal); + break; + } + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitInfo.ResultInformation); + } + + return result; + } + + void ConfigurationSetApplyProcessor::SendProgress(ConfigurationUnitState state, const UnitInfo& unitInfo) + { + unitInfo.Result->State(state); + + try + { + m_progress.Progress(*unitInfo.Result); + } + CATCH_LOG(); + } + + void ConfigurationSetApplyProcessor::SendProgressIfNotComplete(ConfigurationUnitState state, const UnitInfo& unitInfo) + { + if (unitInfo.Result->State() != ConfigurationUnitState::Completed) + { + SendProgress(state, unitInfo); + } + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h index f0b4342f77..506baa8e9d 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetApplyProcessor.h @@ -1,108 +1,108 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSet.h" -#include "ConfigurationUnit.h" -#include "ApplyGroupSettingsResult.h" -#include "ApplyConfigurationUnitResult.h" -#include "ConfigurationUnitResultInformation.h" -#include "ShutdownSynchronization.h" - -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // A helper to better organize the configuration set Apply. - struct ConfigurationSetApplyProcessor - { - using ConfigurationSet = Configuration::ConfigurationSet; - using ConfigurationUnit = Configuration::ConfigurationUnit; - using ConfigurationSetChangeData = Configuration::ConfigurationSetChangeData; - - using result_type = decltype(make_self>()); - using progress_type = ShutdownAwareAsyncProgress; - - ConfigurationSetApplyProcessor(const ConfigurationSet& configurationSet, IConfigurationSetProcessor setProcessor, progress_type&& progress); - - // Processes the apply for the configuration set. - void Process(bool preProcessOnly = false); - - // Gets the result object. - IApplyGroupSettingsResult Result() const; - - private: - // Contains all of the relevant data for a configuration unit. - struct UnitInfo - { - UnitInfo(const ConfigurationUnit& unit); - - ConfigurationUnit Unit; - std::vector DependencyIndices; - decltype(make_self>()) Result; - decltype(make_self>()) ResultInformation; - bool PreProcessed = false; - bool Processed = false; - }; - - // Builds out some data used during processing and validates the set along the way. - bool PreProcess(); - - // Adds the given unit to the identifier to unit info index map. - bool AddUnitToMap(UnitInfo& unitInfo, size_t unitInfoIndex); - - // Checks the dependency; returns true to indicate that the dependency is satisfied, false if not. - using CheckDependencyPtr = bool (*)(const UnitInfo&); - - // Processes the unit; returns true if successful, false if not. - using ProcessUnitPtr = bool (ConfigurationSetApplyProcessor::*)(UnitInfo&); - - // Return true to process these intents, false to skip them. - using IntentFilterPtr = bool (*)(ConfigurationUnitIntent); - - // Runs the processing using the given functions. - bool ProcessInternal(CheckDependencyPtr checkDependencyFunction, ProcessUnitPtr processUnitFunction, bool sendProgress = false); - - // Processes one of the non-writing intent types, which are fatal if not all successful - bool ProcessIntentInternal( - std::vector& unitsToProcess, - CheckDependencyPtr checkDependencyFunction, - ProcessUnitPtr processUnitFunction, - IntentFilterPtr intentFilter, - hresult errorForOtherIntents, - hresult errorForFailures, - bool sendProgress); - - // Determines if the given unit has the given intent and all of its dependencies are satisfied - bool HasIntentAndSatisfiedDependencies( - const UnitInfo& unitInfo, - IntentFilterPtr intentFilter, - CheckDependencyPtr checkDependencyFunction) const; - - // Checks a dependency for preprocessing. - static bool HasPreprocessed(const UnitInfo& unitInfo); - - // Marks a unit as preprocessed. - bool MarkPreprocessed(UnitInfo& unitInfo); - - // Checks a dependency for having processed successfully. - static bool HasProcessedSuccessfully(const UnitInfo& unitInfo); - - // Processes a configuration unit per its intent. - bool ProcessUnit(UnitInfo& unitInfo); - - // Sends progress - // TODO: Eventually these functions/call sites will be used for history - void SendProgress(ConfigurationUnitState state, const UnitInfo& unitInfo); - void SendProgressIfNotComplete(ConfigurationUnitState state, const UnitInfo& unitInfo); - - ConfigurationSet m_configurationSet; - IConfigurationSetProcessor m_setProcessor; - result_type m_result; - progress_type m_progress; - std::vector m_unitInfo; - std::map m_idToUnitInfoIndex; - hresult m_resultCode; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSet.h" +#include "ConfigurationUnit.h" +#include "ApplyGroupSettingsResult.h" +#include "ApplyConfigurationUnitResult.h" +#include "ConfigurationUnitResultInformation.h" +#include "ShutdownSynchronization.h" + +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // A helper to better organize the configuration set Apply. + struct ConfigurationSetApplyProcessor + { + using ConfigurationSet = Configuration::ConfigurationSet; + using ConfigurationUnit = Configuration::ConfigurationUnit; + using ConfigurationSetChangeData = Configuration::ConfigurationSetChangeData; + + using result_type = decltype(make_self>()); + using progress_type = ShutdownAwareAsyncProgress; + + ConfigurationSetApplyProcessor(const ConfigurationSet& configurationSet, IConfigurationSetProcessor setProcessor, progress_type&& progress); + + // Processes the apply for the configuration set. + void Process(bool preProcessOnly = false); + + // Gets the result object. + IApplyGroupSettingsResult Result() const; + + private: + // Contains all of the relevant data for a configuration unit. + struct UnitInfo + { + UnitInfo(const ConfigurationUnit& unit); + + ConfigurationUnit Unit; + std::vector DependencyIndices; + decltype(make_self>()) Result; + decltype(make_self>()) ResultInformation; + bool PreProcessed = false; + bool Processed = false; + }; + + // Builds out some data used during processing and validates the set along the way. + bool PreProcess(); + + // Adds the given unit to the identifier to unit info index map. + bool AddUnitToMap(UnitInfo& unitInfo, size_t unitInfoIndex); + + // Checks the dependency; returns true to indicate that the dependency is satisfied, false if not. + using CheckDependencyPtr = bool (*)(const UnitInfo&); + + // Processes the unit; returns true if successful, false if not. + using ProcessUnitPtr = bool (ConfigurationSetApplyProcessor::*)(UnitInfo&); + + // Return true to process these intents, false to skip them. + using IntentFilterPtr = bool (*)(ConfigurationUnitIntent); + + // Runs the processing using the given functions. + bool ProcessInternal(CheckDependencyPtr checkDependencyFunction, ProcessUnitPtr processUnitFunction, bool sendProgress = false); + + // Processes one of the non-writing intent types, which are fatal if not all successful + bool ProcessIntentInternal( + std::vector& unitsToProcess, + CheckDependencyPtr checkDependencyFunction, + ProcessUnitPtr processUnitFunction, + IntentFilterPtr intentFilter, + hresult errorForOtherIntents, + hresult errorForFailures, + bool sendProgress); + + // Determines if the given unit has the given intent and all of its dependencies are satisfied + bool HasIntentAndSatisfiedDependencies( + const UnitInfo& unitInfo, + IntentFilterPtr intentFilter, + CheckDependencyPtr checkDependencyFunction) const; + + // Checks a dependency for preprocessing. + static bool HasPreprocessed(const UnitInfo& unitInfo); + + // Marks a unit as preprocessed. + bool MarkPreprocessed(UnitInfo& unitInfo); + + // Checks a dependency for having processed successfully. + static bool HasProcessedSuccessfully(const UnitInfo& unitInfo); + + // Processes a configuration unit per its intent. + bool ProcessUnit(UnitInfo& unitInfo); + + // Sends progress + // TODO: Eventually these functions/call sites will be used for history + void SendProgress(ConfigurationUnitState state, const UnitInfo& unitInfo); + void SendProgressIfNotComplete(ConfigurationUnitState state, const UnitInfo& unitInfo); + + ConfigurationSet m_configurationSet; + IConfigurationSetProcessor m_setProcessor; + result_type m_result; + progress_type m_progress; + std::vector m_unitInfo; + std::map m_idToUnitInfoIndex; + hresult m_resultCode; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp index de1ae0ee1a..249e5b5d78 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.cpp @@ -1,76 +1,76 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetChangeData.h" -#include "ConfigurationSetChangeData.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - Configuration::ConfigurationSetChangeData ConfigurationSetChangeData::Create(ConfigurationSetState state) - { - auto result = make_self(); - result->Initialize(state); - return *result; - } - - Configuration::ConfigurationSetChangeData ConfigurationSetChangeData::Create(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit) - { - auto result = make_self(); - result->Initialize(state, resultInformation, unit); - return *result; - } - - void ConfigurationSetChangeData::Initialize(ConfigurationSetState state) - { - m_change = ConfigurationSetChangeEventType::SetStateChanged; - m_setState = state; - } - - void ConfigurationSetChangeData::Initialize(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit) - { - m_change = ConfigurationSetChangeEventType::UnitStateChanged; - m_setState = ConfigurationSetState::InProgress; - m_unitState = state; - m_resultInformation = resultInformation; - m_unit = unit; - } - - void ConfigurationSetChangeData::Initialize(const IApplyGroupMemberSettingsResult& unitResult) - { - m_change = ConfigurationSetChangeEventType::UnitStateChanged; - m_setState = ConfigurationSetState::InProgress; - m_unitState = unitResult.State(); - m_resultInformation = unitResult.ResultInformation(); - m_unit = unitResult.Unit(); - } - - ConfigurationSetChangeEventType ConfigurationSetChangeData::Change() - { - return m_change; - } - - ConfigurationSetState ConfigurationSetChangeData::SetState() - { - return m_setState; - } - - ConfigurationUnitState ConfigurationSetChangeData::UnitState() - { - return m_unitState; - } - - IConfigurationUnitResultInformation ConfigurationSetChangeData::ResultInformation() - { - return m_resultInformation; - } - - ConfigurationUnit ConfigurationSetChangeData::Unit() - { - return m_unit; - } - - void ConfigurationSetChangeData::Unit(const ConfigurationUnit& unit) - { - m_unit = unit; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetChangeData.h" +#include "ConfigurationSetChangeData.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + Configuration::ConfigurationSetChangeData ConfigurationSetChangeData::Create(ConfigurationSetState state) + { + auto result = make_self(); + result->Initialize(state); + return *result; + } + + Configuration::ConfigurationSetChangeData ConfigurationSetChangeData::Create(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit) + { + auto result = make_self(); + result->Initialize(state, resultInformation, unit); + return *result; + } + + void ConfigurationSetChangeData::Initialize(ConfigurationSetState state) + { + m_change = ConfigurationSetChangeEventType::SetStateChanged; + m_setState = state; + } + + void ConfigurationSetChangeData::Initialize(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit) + { + m_change = ConfigurationSetChangeEventType::UnitStateChanged; + m_setState = ConfigurationSetState::InProgress; + m_unitState = state; + m_resultInformation = resultInformation; + m_unit = unit; + } + + void ConfigurationSetChangeData::Initialize(const IApplyGroupMemberSettingsResult& unitResult) + { + m_change = ConfigurationSetChangeEventType::UnitStateChanged; + m_setState = ConfigurationSetState::InProgress; + m_unitState = unitResult.State(); + m_resultInformation = unitResult.ResultInformation(); + m_unit = unitResult.Unit(); + } + + ConfigurationSetChangeEventType ConfigurationSetChangeData::Change() + { + return m_change; + } + + ConfigurationSetState ConfigurationSetChangeData::SetState() + { + return m_setState; + } + + ConfigurationUnitState ConfigurationSetChangeData::UnitState() + { + return m_unitState; + } + + IConfigurationUnitResultInformation ConfigurationSetChangeData::ResultInformation() + { + return m_resultInformation; + } + + ConfigurationUnit ConfigurationSetChangeData::Unit() + { + return m_unit; + } + + void ConfigurationSetChangeData::Unit(const ConfigurationUnit& unit) + { + m_unit = unit; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h index 54cbcb8e0e..e2ad93a709 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetChangeData.h @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetChangeData.g.h" -#include "ConfigurationUnitResultInformation.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationSetChangeData : ConfigurationSetChangeDataT, AppInstaller::WinRT::ModuleCountBase - { - using ConfigurationUnit = Configuration::ConfigurationUnit; - - ConfigurationSetChangeData() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - static Configuration::ConfigurationSetChangeData Create(ConfigurationSetState state); - static Configuration::ConfigurationSetChangeData Create(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit); - - void Initialize(ConfigurationSetState state); - void Initialize(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit); - void Initialize(const IApplyGroupMemberSettingsResult& unitResult); - - void Unit(const ConfigurationUnit& unit); -#endif - - ConfigurationSetChangeEventType Change(); - ConfigurationSetState SetState(); - ConfigurationUnitState UnitState(); - IConfigurationUnitResultInformation ResultInformation(); - ConfigurationUnit Unit(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ConfigurationSetChangeEventType m_change = ConfigurationSetChangeEventType::Unknown; - ConfigurationSetState m_setState = ConfigurationSetState::Unknown; - ConfigurationUnitState m_unitState = ConfigurationUnitState::Unknown; - IConfigurationUnitResultInformation m_resultInformation; - ConfigurationUnit m_unit = nullptr; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetChangeData.g.h" +#include "ConfigurationUnitResultInformation.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationSetChangeData : ConfigurationSetChangeDataT, AppInstaller::WinRT::ModuleCountBase + { + using ConfigurationUnit = Configuration::ConfigurationUnit; + + ConfigurationSetChangeData() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + static Configuration::ConfigurationSetChangeData Create(ConfigurationSetState state); + static Configuration::ConfigurationSetChangeData Create(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit); + + void Initialize(ConfigurationSetState state); + void Initialize(ConfigurationUnitState state, IConfigurationUnitResultInformation resultInformation, ConfigurationUnit unit); + void Initialize(const IApplyGroupMemberSettingsResult& unitResult); + + void Unit(const ConfigurationUnit& unit); +#endif + + ConfigurationSetChangeEventType Change(); + ConfigurationSetState SetState(); + ConfigurationUnitState UnitState(); + IConfigurationUnitResultInformation ResultInformation(); + ConfigurationUnit Unit(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationSetChangeEventType m_change = ConfigurationSetChangeEventType::Unknown; + ConfigurationSetState m_setState = ConfigurationSetState::Unknown; + ConfigurationUnitState m_unitState = ConfigurationUnitState::Unknown; + IConfigurationUnitResultInformation m_resultInformation; + ConfigurationUnit m_unit = nullptr; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp index 0b5721c6ab..a0f012348e 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser.cpp @@ -1,585 +1,585 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetParser.h" -#include "ParsingMacros.h" -#include "ArgumentValidation.h" - -#include -#include -#include -#include - -#include "ConfigurationSetUtilities.h" -#include "ConfigurationSetParserError.h" -#include "ConfigurationSetParser_0_1.h" -#include "ConfigurationSetParser_0_2.h" -#include "ConfigurationSetParser_0_3.h" - -using namespace AppInstaller::Utility; -using namespace AppInstaller::YAML; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - struct SchemaVersionAndUri - { - std::string_view Version; - std::wstring_view VersionWide; - std::string_view Uri; - std::wstring_view UriWide; - }; - -#define SCHEMA_VERSION_MAP_ITEM(_version_,_uri_) _version_, TEXT(_version_), _uri_, TEXT(_uri_) - - // Please keep in sorted order with the highest version last. - // Duplicate URIs are supported, but duplicate versions are not. The highest version for a URI will be the one mapped to, the lower versions will be aliases. - SchemaVersionAndUri SchemaVersionAndUriMap[] = - { - { SCHEMA_VERSION_MAP_ITEM("0.1", "") }, - { SCHEMA_VERSION_MAP_ITEM("0.2", "") }, - { SCHEMA_VERSION_MAP_ITEM("0.3", "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json") }, - }; - - Windows::Foundation::IInspectable GetIInspectableFromNode(const Node& node); - - // Fills the ValueSet from the given node, which is assumed to be a map. - void FillValueSetFromMap(const Node& mapNode, const Windows::Foundation::Collections::ValueSet& valueSet) - { - for (const auto& mapItem : mapNode.Mapping()) - { - // Insert returns true if it replaces an existing key, and that indicates an invalid map. - THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE, valueSet.Insert(mapItem.first.as(), GetIInspectableFromNode(mapItem.second))); - } - } - - // Returns the appropriate IPropertyValue for the given node, which is assumed to be a scalar. - Windows::Foundation::IInspectable GetPropertyValueFromScalar(const Node& node) - { - ::winrt::Windows::Foundation::IInspectable result; - - switch (node.GetTagType()) - { - case Node::TagType::Null: - return Windows::Foundation::PropertyValue::CreateEmpty(); - case Node::TagType::Bool: - return Windows::Foundation::PropertyValue::CreateBoolean(node.as()); - case Node::TagType::Str: - return Windows::Foundation::PropertyValue::CreateString(node.as()); - case Node::TagType::Int: - return Windows::Foundation::PropertyValue::CreateInt64(node.as()); - case Node::TagType::Float: - THROW_HR(E_NOTIMPL); - case Node::TagType::Timestamp: - THROW_HR(E_NOTIMPL); - default: - THROW_HR(E_UNEXPECTED); - } - } - - // Returns the appropriate IPropertyValue for the given node, which is assumed to be a scalar. - Windows::Foundation::IInspectable GetPropertyValueFromSequence(const Node& sequenceNode) - { - Windows::Foundation::Collections::ValueSet result; - size_t index = 0; - - for (const Node& sequenceItem : sequenceNode.Sequence()) - { - std::wostringstream strstr; - strstr << index++; - result.Insert(strstr.str(), GetIInspectableFromNode(sequenceItem)); - } - - result.Insert(L"treatAsArray", Windows::Foundation::PropertyValue::CreateBoolean(true)); - return result; - } - - // Returns the appropriate IInspectable for the given node. - Windows::Foundation::IInspectable GetIInspectableFromNode(const Node& node) - { - ::winrt::Windows::Foundation::IInspectable result; - - switch (node.GetType()) - { - case Node::Type::Invalid: - case Node::Type::None: - // Leave value as null - break; - case Node::Type::Scalar: - result = GetPropertyValueFromScalar(node); - break; - case Node::Type::Sequence: - result = GetPropertyValueFromSequence(node); - break; - case Node::Type::Mapping: - { - Windows::Foundation::Collections::ValueSet subset; - FillValueSetFromMap(node, subset); - result = std::move(subset); - } - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return result; - } - - // Contains the qualified resource name information. - struct QualifiedResourceName - { - QualifiedResourceName(hstring input) - { - std::wstring_view inputView = input; - size_t pos = inputView.find('/'); - - if (pos != std::wstring_view::npos) - { - Module = inputView.substr(0, pos); - Resource = inputView.substr(pos + 1); - } - else - { - Resource = input; - } - } - - hstring Module; - hstring Resource; - }; - } - - std::unique_ptr ConfigurationSetParser::Create(std::string_view input) - { - AICLI_LOG_LARGE_STRING(Config, Verbose, << "Parsing configuration set:", input); - - Node document; - std::string documentError; - Mark documentErrorMark; - - try - { - document = Load(input); - } - catch (const Exception& exc) - { - documentError = exc.what(); - documentErrorMark = exc.GetMark(); - } - CATCH_LOG(); - - if (!document.IsMap()) - { - AICLI_LOG(Config, Error, << "Invalid YAML: " << documentError << " at [line " << documentErrorMark.line << ", col " << documentErrorMark.column << "]"); - return std::make_unique(WINGET_CONFIG_ERROR_INVALID_YAML, documentError, documentErrorMark); - } - - // The schema version for parsing the rest of the document - std::string schemaUriString; - std::string schemaVersionString; - - Node& schemaNode = document[GetConfigurationFieldName(ConfigurationField::Schema)]; - if (schemaNode.IsScalar()) - { - schemaUriString = schemaNode.as(); - schemaVersionString = GetSchemaVersionForUri(schemaUriString); - AICLI_LOG(Config, Verbose, << "Configuration schema `" << schemaNode.as() << "` mapped to version `" << schemaVersionString << "`."); - } - - // If we recognize the schema, use that version. - // If we didn't recognize it, try using the older format. - if (schemaVersionString.empty()) - { - std::unique_ptr oldFormatError = GetSchemaVersionFromOldFormat(document, schemaVersionString); - - // We have no schema version at all... - if (oldFormatError) - { - // If the schema was provided and we didn't recognize it, make that the error. - if (schemaNode.IsScalar()) - { - AICLI_LOG(Config, Error, << "Unknown configuration schema: " << schemaUriString); - return std::make_unique(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetConfigurationFieldName(ConfigurationField::Schema), schemaUriString); - } - else - { - // Otherwise, this is an older format file (or neither). The proper error came back from that function. - return oldFormatError; - } - } - } - - // Create the parser based on the version selected - auto result = CreateForSchemaVersion(std::move(schemaVersionString)); - result->SetDocument(std::move(document)); - return result; - } - - std::unique_ptr ConfigurationSetParser::CreateForSchemaVersion(std::string input) - { - SemanticVersion schemaVersion(std::move(input)); - - // TODO: Consider having the version/uri/type information all together in the future - if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 1) - { - return std::make_unique(); - } - else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 2) - { - return std::make_unique(); - } - else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 3) - { - return std::make_unique(); - } - - AICLI_LOG(Config, Error, << "Unknown configuration version: " << schemaVersion.ToString()); - return std::make_unique(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion), schemaVersion.ToString()); - } - - bool ConfigurationSetParser::IsRecognizedSchemaVersion(hstring value) try - { - SemanticVersion schemaVersion(ConvertToUTF8(value)); - - for (const auto& item : SchemaVersionAndUriMap) - { - if (schemaVersion == SemanticVersion{ std::string{ item.Version } }) - { - return true; - } - } - - return false; - } - catch (...) { LOG_CAUGHT_EXCEPTION(); return false; } - - bool ConfigurationSetParser::IsRecognizedSchemaUri(const Windows::Foundation::Uri& value) - { - return !GetSchemaVersionForUri(value).empty(); - } - - Windows::Foundation::Uri ConfigurationSetParser::GetSchemaUriForVersion(hstring value) - { - for (const auto& item : SchemaVersionAndUriMap) - { - if (value == item.VersionWide) - { - return item.Uri.empty() ? nullptr : Windows::Foundation::Uri{ item.UriWide }; - } - } - - return nullptr; - } - - hstring ConfigurationSetParser::GetSchemaVersionForUri(Windows::Foundation::Uri value) - { - // Do a reverse search in order to give the highest version back for a given URI. - auto itr = std::rbegin(SchemaVersionAndUriMap); - auto end = std::rend(SchemaVersionAndUriMap); - for (; itr != end; ++itr) - { - const auto& item = *itr; - if (!item.Uri.empty()) - { - Windows::Foundation::Uri uri{ item.UriWide }; - if (value.Equals(uri)) - { - return hstring{ item.VersionWide }; - } - } - } - - return {}; - } - - std::string ConfigurationSetParser::GetSchemaVersionForUri(std::string_view value) - { - // Do a reverse search in order to give the highest version back for a given URI. - auto itr = std::rbegin(SchemaVersionAndUriMap); - auto end = std::rend(SchemaVersionAndUriMap); - for (; itr != end; ++itr) - { - const auto& item = *itr; - if (!item.Uri.empty()) - { - if (item.Uri == value) - { - return std::string{ item.Version }; - } - } - } - - return {}; - } - - std::pair ConfigurationSetParser::LatestVersion() - { - auto latest = std::rbegin(SchemaVersionAndUriMap); - return { hstring{ latest->VersionWide }, Windows::Foundation::Uri{ latest->UriWide } }; - } - - Windows::Foundation::Collections::ValueSet ConfigurationSetParser::ParseValueSet(std::string_view input) - { - Windows::Foundation::Collections::ValueSet result; - FillValueSetFromMap(Load(input), result); - return result; - } - - std::vector ConfigurationSetParser::ParseStringArray(std::string_view input) - { - std::vector result; - ParseSequence(Load(input), "string_array", Node::Type::Scalar, [&](const AppInstaller::YAML::Node& item) - { - result.emplace_back(item.as()); - }); - return result; - } - - void ConfigurationSetParser::SetError(hresult result, std::string_view field, std::string_view value, uint32_t line, uint32_t column) - { - AICLI_LOG(Config, Error, << "ConfigurationSetParser error: " << AppInstaller::Logging::SetHRFormat << result << " for " << field << " with value `" << value << "` at [line " << line << ", col " << column << "]"); - m_result = result; - m_field = ConvertToUTF16(field); - m_value = ConvertToUTF16(value); - m_line = line; - m_column = column; - } - - void ConfigurationSetParser::SetError(hresult result, std::string_view field, const Mark& mark, std::string_view value) - { - SetError(result, field, value, static_cast(mark.line), static_cast(mark.column)); - } - - const Node& ConfigurationSetParser::GetAndEnsureField(const Node& parent, ConfigurationField field, bool required, std::optional type) - { - const Node& fieldNode = parent[GetConfigurationFieldName(field)]; - - if (fieldNode) - { - if (type && fieldNode.GetType() != type.value()) - { - SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(field), fieldNode.Mark()); - } - } - else if (required) - { - SetError(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(field)); - } - - return fieldNode; - } - - void ConfigurationSetParser::EnsureFieldAbsent(const Node& parent, ConfigurationField field) - { - const Node& fieldNode = parent[GetConfigurationFieldName(field)]; - - if (fieldNode) - { - SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, GetConfigurationFieldName(field), fieldNode.Mark(), fieldNode.as()); - } - } - - void ConfigurationSetParser::ParseValueSet(const Node& node, ConfigurationField field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet) - { - const Node& mapNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Mapping)); - - if (mapNode) - { - FillValueSetFromMap(mapNode, valueSet); - } - } - - void ConfigurationSetParser::ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function operation) - { - const Node& mapNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Mapping)); - if (!mapNode) - { - return; - } - - std::ostringstream strstr; - strstr << GetConfigurationFieldName(field); - size_t index = 0; - - for (const auto& mapItem : mapNode.Mapping()) - { - std::string name = mapItem.first.as(); - if (name.empty()) - { - strstr << '[' << index << ']'; - FIELD_VALUE_ERROR(strstr.str(), name, mapItem.first.Mark()); - } - - if (mapItem.second.GetType() != elementType) - { - strstr << '[' << index << ']'; - FIELD_TYPE_ERROR(strstr.str(), mapItem.second.Mark()); - } - index++; - - CHECK_ERROR(operation(std::move(name), mapItem.second)); - } - } - - void ConfigurationSetParser::ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional elementType, std::function operation) - { - const Node& sequenceNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Sequence)); - if (!sequenceNode) - { - return; - } - - ParseSequence(sequenceNode, GetConfigurationFieldName(field), elementType, operation); - } - - void ConfigurationSetParser::ParseSequence(const AppInstaller::YAML::Node& node, std::string_view nameForErrors, std::optional elementType, std::function operation) - { - std::ostringstream strstr; - strstr << nameForErrors; - size_t index = 0; - - for (const Node& item : node.Sequence()) - { - if (elementType && item.GetType() != elementType.value()) - { - strstr << '[' << index << ']'; - FIELD_TYPE_ERROR(strstr.str(), item.Mark()); - } - index++; - - CHECK_ERROR(operation(item)); - } - } - - std::unique_ptr ConfigurationSetParser::GetSchemaVersionFromOldFormat(AppInstaller::YAML::Node& document, std::string& schemaVersionString) - { - Node& propertiesNode = document[GetConfigurationFieldName(ConfigurationField::Properties)]; - if (!propertiesNode) - { - AICLI_LOG(Config, Error, << "No properties"); - // Even though this is for the "older" format, if there is no properties entry then give an error for the newer format since this is probably neither. - return std::make_unique(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::Schema)); - } - else if (!propertiesNode.IsMap()) - { - AICLI_LOG(Config, Error, << "Invalid properties type"); - return std::make_unique(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(ConfigurationField::Properties), propertiesNode.Mark()); - } - - Node& versionNode = propertiesNode[GetConfigurationFieldName(ConfigurationField::ConfigurationVersion)]; - if (!versionNode) - { - AICLI_LOG(Config, Error, << "No configuration version"); - return std::make_unique(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion)); - } - else if (!versionNode.IsScalar()) - { - AICLI_LOG(Config, Error, << "Invalid configuration version type"); - return std::make_unique(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion), versionNode.Mark()); - } - - schemaVersionString = versionNode.as(); - return {}; - } - - void ConfigurationSetParser::GetStringValueForUnit(const Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)) - { - const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Scalar)); - - if (valueNode) - { - hstring value{ valueNode.as() }; - FIELD_MISSING_ERROR_IF(value.empty() && required, GetConfigurationFieldName(field)); - - (unit->*propertyFunction)(std::move(value)); - } - } - - void ConfigurationSetParser::GetStringArrayForUnit(const Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector&& value)) - { - std::vector arrayValue; - CHECK_ERROR(ParseSequence(node, field, required, Node::Type::Scalar, [&](const AppInstaller::YAML::Node& item) - { - arrayValue.emplace_back(item.as()); - })); - - if (!arrayValue.empty()) - { - (unit->*propertyFunction)(std::move(arrayValue)); - } - } - - void ConfigurationSetParser::ValidateType(ConfigurationUnit* unit, const Node& unitNode, ConfigurationField typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType) - { - QualifiedResourceName qualifiedName{ unit->Type() }; - - const Node& typeNode = CHECK_ERROR(GetAndEnsureField(unitNode, typeField, true, Node::Type::Scalar)); - FIELD_VALUE_ERROR_IF(qualifiedName.Resource.empty(), GetConfigurationFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); - - if (!qualifiedName.Module.empty()) - { - // If the module is provided in both the resource name and the directives, ensure that it matches - hstring moduleDirectiveFieldName = GetConfigurationFieldNameHString(ConfigurationField::ModuleDirective); - auto moduleDirective = unit->Metadata().TryLookup(moduleDirectiveFieldName); - if (moduleDirective) - { - auto moduleProperty = moduleDirective.try_as(); - FIELD_TYPE_ERROR_IF(!moduleProperty, GetConfigurationFieldName(ConfigurationField::ModuleDirective), unitNode.Mark()); - FIELD_TYPE_ERROR_IF(moduleProperty.Type() != Windows::Foundation::PropertyType::String, GetConfigurationFieldName(ConfigurationField::ModuleDirective), unitNode.Mark()); - hstring moduleValue = moduleProperty.GetString(); - FIELD_VALUE_ERROR_IF(qualifiedName.Module != moduleValue, GetConfigurationFieldName(ConfigurationField::ModuleDirective), ConvertToUTF8(moduleValue), unitNode.Mark()); - } - else if (moveModuleNameToMetadata) - { - unit->Metadata().Insert(moduleDirectiveFieldName, Windows::Foundation::PropertyValue::CreateString(qualifiedName.Module)); - } - - if (moveModuleNameToMetadata) - { - // Set the unit name to be just the resource portion - unit->Type(qualifiedName.Resource); - } - } - else if (moduleNameRequiredInType) - { - FIELD_VALUE_ERROR(GetConfigurationFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); - } - } - - void ConfigurationSetParser::ParseObject(const Node& node, ConfigurationField fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result) - { - try - { - Windows::Foundation::IInspectable object = GetIInspectableFromNode(node); - FIELD_VALUE_ERROR_IF(!IsValidObjectType(object, type), GetConfigurationFieldName(fieldForErrors), node.as(), node.Mark()); - result = std::move(object); - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - FIELD_VALUE_ERROR(GetConfigurationFieldName(fieldForErrors), node.as(), node.Mark()); - } - } - - void ConfigurationSetParser::ExtractSecurityContext(implementation::ConfigurationUnit* unit, SecurityContext defaultContext) - { - THROW_HR_IF_NULL(E_POINTER, unit); - - ExtractSecurityContext(unit->Metadata(), unit->EnvironmentInternal(), defaultContext); - } - - void ConfigurationSetParser::ExtractSecurityContext(Windows::Foundation::Collections::ValueSet metadata, implementation::ConfigurationEnvironment& environment, SecurityContext defaultContext) - { - SecurityContext computedContext = defaultContext; - - auto securityContext = TryLookupProperty(metadata, ConfigurationField::SecurityContextMetadata, Windows::Foundation::PropertyType::String); - if (securityContext) - { - TryParseSecurityContext(securityContext.GetString(), computedContext); - metadata.Remove(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata)); - } - - environment.Context(computedContext); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetParser.h" +#include "ParsingMacros.h" +#include "ArgumentValidation.h" + +#include +#include +#include +#include + +#include "ConfigurationSetUtilities.h" +#include "ConfigurationSetParserError.h" +#include "ConfigurationSetParser_0_1.h" +#include "ConfigurationSetParser_0_2.h" +#include "ConfigurationSetParser_0_3.h" + +using namespace AppInstaller::Utility; +using namespace AppInstaller::YAML; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + struct SchemaVersionAndUri + { + std::string_view Version; + std::wstring_view VersionWide; + std::string_view Uri; + std::wstring_view UriWide; + }; + +#define SCHEMA_VERSION_MAP_ITEM(_version_,_uri_) _version_, TEXT(_version_), _uri_, TEXT(_uri_) + + // Please keep in sorted order with the highest version last. + // Duplicate URIs are supported, but duplicate versions are not. The highest version for a URI will be the one mapped to, the lower versions will be aliases. + SchemaVersionAndUri SchemaVersionAndUriMap[] = + { + { SCHEMA_VERSION_MAP_ITEM("0.1", "") }, + { SCHEMA_VERSION_MAP_ITEM("0.2", "") }, + { SCHEMA_VERSION_MAP_ITEM("0.3", "https://raw.githubusercontent.com/PowerShell/DSC/main/schemas/2023/08/config/document.json") }, + }; + + Windows::Foundation::IInspectable GetIInspectableFromNode(const Node& node); + + // Fills the ValueSet from the given node, which is assumed to be a map. + void FillValueSetFromMap(const Node& mapNode, const Windows::Foundation::Collections::ValueSet& valueSet) + { + for (const auto& mapItem : mapNode.Mapping()) + { + // Insert returns true if it replaces an existing key, and that indicates an invalid map. + THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_CONFIGURATION_FILE, valueSet.Insert(mapItem.first.as(), GetIInspectableFromNode(mapItem.second))); + } + } + + // Returns the appropriate IPropertyValue for the given node, which is assumed to be a scalar. + Windows::Foundation::IInspectable GetPropertyValueFromScalar(const Node& node) + { + ::winrt::Windows::Foundation::IInspectable result; + + switch (node.GetTagType()) + { + case Node::TagType::Null: + return Windows::Foundation::PropertyValue::CreateEmpty(); + case Node::TagType::Bool: + return Windows::Foundation::PropertyValue::CreateBoolean(node.as()); + case Node::TagType::Str: + return Windows::Foundation::PropertyValue::CreateString(node.as()); + case Node::TagType::Int: + return Windows::Foundation::PropertyValue::CreateInt64(node.as()); + case Node::TagType::Float: + THROW_HR(E_NOTIMPL); + case Node::TagType::Timestamp: + THROW_HR(E_NOTIMPL); + default: + THROW_HR(E_UNEXPECTED); + } + } + + // Returns the appropriate IPropertyValue for the given node, which is assumed to be a scalar. + Windows::Foundation::IInspectable GetPropertyValueFromSequence(const Node& sequenceNode) + { + Windows::Foundation::Collections::ValueSet result; + size_t index = 0; + + for (const Node& sequenceItem : sequenceNode.Sequence()) + { + std::wostringstream strstr; + strstr << index++; + result.Insert(strstr.str(), GetIInspectableFromNode(sequenceItem)); + } + + result.Insert(L"treatAsArray", Windows::Foundation::PropertyValue::CreateBoolean(true)); + return result; + } + + // Returns the appropriate IInspectable for the given node. + Windows::Foundation::IInspectable GetIInspectableFromNode(const Node& node) + { + ::winrt::Windows::Foundation::IInspectable result; + + switch (node.GetType()) + { + case Node::Type::Invalid: + case Node::Type::None: + // Leave value as null + break; + case Node::Type::Scalar: + result = GetPropertyValueFromScalar(node); + break; + case Node::Type::Sequence: + result = GetPropertyValueFromSequence(node); + break; + case Node::Type::Mapping: + { + Windows::Foundation::Collections::ValueSet subset; + FillValueSetFromMap(node, subset); + result = std::move(subset); + } + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } + + // Contains the qualified resource name information. + struct QualifiedResourceName + { + QualifiedResourceName(hstring input) + { + std::wstring_view inputView = input; + size_t pos = inputView.find('/'); + + if (pos != std::wstring_view::npos) + { + Module = inputView.substr(0, pos); + Resource = inputView.substr(pos + 1); + } + else + { + Resource = input; + } + } + + hstring Module; + hstring Resource; + }; + } + + std::unique_ptr ConfigurationSetParser::Create(std::string_view input) + { + AICLI_LOG_LARGE_STRING(Config, Verbose, << "Parsing configuration set:", input); + + Node document; + std::string documentError; + Mark documentErrorMark; + + try + { + document = Load(input); + } + catch (const Exception& exc) + { + documentError = exc.what(); + documentErrorMark = exc.GetMark(); + } + CATCH_LOG(); + + if (!document.IsMap()) + { + AICLI_LOG(Config, Error, << "Invalid YAML: " << documentError << " at [line " << documentErrorMark.line << ", col " << documentErrorMark.column << "]"); + return std::make_unique(WINGET_CONFIG_ERROR_INVALID_YAML, documentError, documentErrorMark); + } + + // The schema version for parsing the rest of the document + std::string schemaUriString; + std::string schemaVersionString; + + Node& schemaNode = document[GetConfigurationFieldName(ConfigurationField::Schema)]; + if (schemaNode.IsScalar()) + { + schemaUriString = schemaNode.as(); + schemaVersionString = GetSchemaVersionForUri(schemaUriString); + AICLI_LOG(Config, Verbose, << "Configuration schema `" << schemaNode.as() << "` mapped to version `" << schemaVersionString << "`."); + } + + // If we recognize the schema, use that version. + // If we didn't recognize it, try using the older format. + if (schemaVersionString.empty()) + { + std::unique_ptr oldFormatError = GetSchemaVersionFromOldFormat(document, schemaVersionString); + + // We have no schema version at all... + if (oldFormatError) + { + // If the schema was provided and we didn't recognize it, make that the error. + if (schemaNode.IsScalar()) + { + AICLI_LOG(Config, Error, << "Unknown configuration schema: " << schemaUriString); + return std::make_unique(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetConfigurationFieldName(ConfigurationField::Schema), schemaUriString); + } + else + { + // Otherwise, this is an older format file (or neither). The proper error came back from that function. + return oldFormatError; + } + } + } + + // Create the parser based on the version selected + auto result = CreateForSchemaVersion(std::move(schemaVersionString)); + result->SetDocument(std::move(document)); + return result; + } + + std::unique_ptr ConfigurationSetParser::CreateForSchemaVersion(std::string input) + { + SemanticVersion schemaVersion(std::move(input)); + + // TODO: Consider having the version/uri/type information all together in the future + if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 1) + { + return std::make_unique(); + } + else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 2) + { + return std::make_unique(); + } + else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 3) + { + return std::make_unique(); + } + + AICLI_LOG(Config, Error, << "Unknown configuration version: " << schemaVersion.ToString()); + return std::make_unique(WINGET_CONFIG_ERROR_UNKNOWN_CONFIGURATION_FILE_VERSION, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion), schemaVersion.ToString()); + } + + bool ConfigurationSetParser::IsRecognizedSchemaVersion(hstring value) try + { + SemanticVersion schemaVersion(ConvertToUTF8(value)); + + for (const auto& item : SchemaVersionAndUriMap) + { + if (schemaVersion == SemanticVersion{ std::string{ item.Version } }) + { + return true; + } + } + + return false; + } + catch (...) { LOG_CAUGHT_EXCEPTION(); return false; } + + bool ConfigurationSetParser::IsRecognizedSchemaUri(const Windows::Foundation::Uri& value) + { + return !GetSchemaVersionForUri(value).empty(); + } + + Windows::Foundation::Uri ConfigurationSetParser::GetSchemaUriForVersion(hstring value) + { + for (const auto& item : SchemaVersionAndUriMap) + { + if (value == item.VersionWide) + { + return item.Uri.empty() ? nullptr : Windows::Foundation::Uri{ item.UriWide }; + } + } + + return nullptr; + } + + hstring ConfigurationSetParser::GetSchemaVersionForUri(Windows::Foundation::Uri value) + { + // Do a reverse search in order to give the highest version back for a given URI. + auto itr = std::rbegin(SchemaVersionAndUriMap); + auto end = std::rend(SchemaVersionAndUriMap); + for (; itr != end; ++itr) + { + const auto& item = *itr; + if (!item.Uri.empty()) + { + Windows::Foundation::Uri uri{ item.UriWide }; + if (value.Equals(uri)) + { + return hstring{ item.VersionWide }; + } + } + } + + return {}; + } + + std::string ConfigurationSetParser::GetSchemaVersionForUri(std::string_view value) + { + // Do a reverse search in order to give the highest version back for a given URI. + auto itr = std::rbegin(SchemaVersionAndUriMap); + auto end = std::rend(SchemaVersionAndUriMap); + for (; itr != end; ++itr) + { + const auto& item = *itr; + if (!item.Uri.empty()) + { + if (item.Uri == value) + { + return std::string{ item.Version }; + } + } + } + + return {}; + } + + std::pair ConfigurationSetParser::LatestVersion() + { + auto latest = std::rbegin(SchemaVersionAndUriMap); + return { hstring{ latest->VersionWide }, Windows::Foundation::Uri{ latest->UriWide } }; + } + + Windows::Foundation::Collections::ValueSet ConfigurationSetParser::ParseValueSet(std::string_view input) + { + Windows::Foundation::Collections::ValueSet result; + FillValueSetFromMap(Load(input), result); + return result; + } + + std::vector ConfigurationSetParser::ParseStringArray(std::string_view input) + { + std::vector result; + ParseSequence(Load(input), "string_array", Node::Type::Scalar, [&](const AppInstaller::YAML::Node& item) + { + result.emplace_back(item.as()); + }); + return result; + } + + void ConfigurationSetParser::SetError(hresult result, std::string_view field, std::string_view value, uint32_t line, uint32_t column) + { + AICLI_LOG(Config, Error, << "ConfigurationSetParser error: " << AppInstaller::Logging::SetHRFormat << result << " for " << field << " with value `" << value << "` at [line " << line << ", col " << column << "]"); + m_result = result; + m_field = ConvertToUTF16(field); + m_value = ConvertToUTF16(value); + m_line = line; + m_column = column; + } + + void ConfigurationSetParser::SetError(hresult result, std::string_view field, const Mark& mark, std::string_view value) + { + SetError(result, field, value, static_cast(mark.line), static_cast(mark.column)); + } + + const Node& ConfigurationSetParser::GetAndEnsureField(const Node& parent, ConfigurationField field, bool required, std::optional type) + { + const Node& fieldNode = parent[GetConfigurationFieldName(field)]; + + if (fieldNode) + { + if (type && fieldNode.GetType() != type.value()) + { + SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(field), fieldNode.Mark()); + } + } + else if (required) + { + SetError(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(field)); + } + + return fieldNode; + } + + void ConfigurationSetParser::EnsureFieldAbsent(const Node& parent, ConfigurationField field) + { + const Node& fieldNode = parent[GetConfigurationFieldName(field)]; + + if (fieldNode) + { + SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, GetConfigurationFieldName(field), fieldNode.Mark(), fieldNode.as()); + } + } + + void ConfigurationSetParser::ParseValueSet(const Node& node, ConfigurationField field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet) + { + const Node& mapNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Mapping)); + + if (mapNode) + { + FillValueSetFromMap(mapNode, valueSet); + } + } + + void ConfigurationSetParser::ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function operation) + { + const Node& mapNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Mapping)); + if (!mapNode) + { + return; + } + + std::ostringstream strstr; + strstr << GetConfigurationFieldName(field); + size_t index = 0; + + for (const auto& mapItem : mapNode.Mapping()) + { + std::string name = mapItem.first.as(); + if (name.empty()) + { + strstr << '[' << index << ']'; + FIELD_VALUE_ERROR(strstr.str(), name, mapItem.first.Mark()); + } + + if (mapItem.second.GetType() != elementType) + { + strstr << '[' << index << ']'; + FIELD_TYPE_ERROR(strstr.str(), mapItem.second.Mark()); + } + index++; + + CHECK_ERROR(operation(std::move(name), mapItem.second)); + } + } + + void ConfigurationSetParser::ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional elementType, std::function operation) + { + const Node& sequenceNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Sequence)); + if (!sequenceNode) + { + return; + } + + ParseSequence(sequenceNode, GetConfigurationFieldName(field), elementType, operation); + } + + void ConfigurationSetParser::ParseSequence(const AppInstaller::YAML::Node& node, std::string_view nameForErrors, std::optional elementType, std::function operation) + { + std::ostringstream strstr; + strstr << nameForErrors; + size_t index = 0; + + for (const Node& item : node.Sequence()) + { + if (elementType && item.GetType() != elementType.value()) + { + strstr << '[' << index << ']'; + FIELD_TYPE_ERROR(strstr.str(), item.Mark()); + } + index++; + + CHECK_ERROR(operation(item)); + } + } + + std::unique_ptr ConfigurationSetParser::GetSchemaVersionFromOldFormat(AppInstaller::YAML::Node& document, std::string& schemaVersionString) + { + Node& propertiesNode = document[GetConfigurationFieldName(ConfigurationField::Properties)]; + if (!propertiesNode) + { + AICLI_LOG(Config, Error, << "No properties"); + // Even though this is for the "older" format, if there is no properties entry then give an error for the newer format since this is probably neither. + return std::make_unique(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::Schema)); + } + else if (!propertiesNode.IsMap()) + { + AICLI_LOG(Config, Error, << "Invalid properties type"); + return std::make_unique(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(ConfigurationField::Properties), propertiesNode.Mark()); + } + + Node& versionNode = propertiesNode[GetConfigurationFieldName(ConfigurationField::ConfigurationVersion)]; + if (!versionNode) + { + AICLI_LOG(Config, Error, << "No configuration version"); + return std::make_unique(WINGET_CONFIG_ERROR_MISSING_FIELD, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion)); + } + else if (!versionNode.IsScalar()) + { + AICLI_LOG(Config, Error, << "Invalid configuration version type"); + return std::make_unique(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, GetConfigurationFieldName(ConfigurationField::ConfigurationVersion), versionNode.Mark()); + } + + schemaVersionString = versionNode.as(); + return {}; + } + + void ConfigurationSetParser::GetStringValueForUnit(const Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)) + { + const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, required, Node::Type::Scalar)); + + if (valueNode) + { + hstring value{ valueNode.as() }; + FIELD_MISSING_ERROR_IF(value.empty() && required, GetConfigurationFieldName(field)); + + (unit->*propertyFunction)(std::move(value)); + } + } + + void ConfigurationSetParser::GetStringArrayForUnit(const Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector&& value)) + { + std::vector arrayValue; + CHECK_ERROR(ParseSequence(node, field, required, Node::Type::Scalar, [&](const AppInstaller::YAML::Node& item) + { + arrayValue.emplace_back(item.as()); + })); + + if (!arrayValue.empty()) + { + (unit->*propertyFunction)(std::move(arrayValue)); + } + } + + void ConfigurationSetParser::ValidateType(ConfigurationUnit* unit, const Node& unitNode, ConfigurationField typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType) + { + QualifiedResourceName qualifiedName{ unit->Type() }; + + const Node& typeNode = CHECK_ERROR(GetAndEnsureField(unitNode, typeField, true, Node::Type::Scalar)); + FIELD_VALUE_ERROR_IF(qualifiedName.Resource.empty(), GetConfigurationFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); + + if (!qualifiedName.Module.empty()) + { + // If the module is provided in both the resource name and the directives, ensure that it matches + hstring moduleDirectiveFieldName = GetConfigurationFieldNameHString(ConfigurationField::ModuleDirective); + auto moduleDirective = unit->Metadata().TryLookup(moduleDirectiveFieldName); + if (moduleDirective) + { + auto moduleProperty = moduleDirective.try_as(); + FIELD_TYPE_ERROR_IF(!moduleProperty, GetConfigurationFieldName(ConfigurationField::ModuleDirective), unitNode.Mark()); + FIELD_TYPE_ERROR_IF(moduleProperty.Type() != Windows::Foundation::PropertyType::String, GetConfigurationFieldName(ConfigurationField::ModuleDirective), unitNode.Mark()); + hstring moduleValue = moduleProperty.GetString(); + FIELD_VALUE_ERROR_IF(qualifiedName.Module != moduleValue, GetConfigurationFieldName(ConfigurationField::ModuleDirective), ConvertToUTF8(moduleValue), unitNode.Mark()); + } + else if (moveModuleNameToMetadata) + { + unit->Metadata().Insert(moduleDirectiveFieldName, Windows::Foundation::PropertyValue::CreateString(qualifiedName.Module)); + } + + if (moveModuleNameToMetadata) + { + // Set the unit name to be just the resource portion + unit->Type(qualifiedName.Resource); + } + } + else if (moduleNameRequiredInType) + { + FIELD_VALUE_ERROR(GetConfigurationFieldName(typeField), ConvertToUTF8(unit->Type()), typeNode.Mark()); + } + } + + void ConfigurationSetParser::ParseObject(const Node& node, ConfigurationField fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result) + { + try + { + Windows::Foundation::IInspectable object = GetIInspectableFromNode(node); + FIELD_VALUE_ERROR_IF(!IsValidObjectType(object, type), GetConfigurationFieldName(fieldForErrors), node.as(), node.Mark()); + result = std::move(object); + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + FIELD_VALUE_ERROR(GetConfigurationFieldName(fieldForErrors), node.as(), node.Mark()); + } + } + + void ConfigurationSetParser::ExtractSecurityContext(implementation::ConfigurationUnit* unit, SecurityContext defaultContext) + { + THROW_HR_IF_NULL(E_POINTER, unit); + + ExtractSecurityContext(unit->Metadata(), unit->EnvironmentInternal(), defaultContext); + } + + void ConfigurationSetParser::ExtractSecurityContext(Windows::Foundation::Collections::ValueSet metadata, implementation::ConfigurationEnvironment& environment, SecurityContext defaultContext) + { + SecurityContext computedContext = defaultContext; + + auto securityContext = TryLookupProperty(metadata, ConfigurationField::SecurityContextMetadata, Windows::Foundation::PropertyType::String); + if (securityContext) + { + TryParseSecurityContext(securityContext.GetString(), computedContext); + metadata.Remove(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata)); + } + + environment.Context(computedContext); + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser.h index df7f9ca0bc..e34cc1ae49 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser.h @@ -1,143 +1,143 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Interface for parsing a configuration set stream. - struct ConfigurationSetParser - { - // Create a parser from the given bytes (the encoding is detected). - static std::unique_ptr Create(std::string_view input); - - // Create a parser for the given schema version. - static std::unique_ptr CreateForSchemaVersion(std::string schemaVersion); - - // Determines if the given value is a recognized schema version. - // This will only return true for a version that we fully recognize. - static bool IsRecognizedSchemaVersion(hstring value); - - // Determines if the given value is a recognized schema URI. - // This will only return true for a URI that we fully recognize. - static bool IsRecognizedSchemaUri(const Windows::Foundation::Uri& value); - - // Gets the schema URI associated with the given version, or null if there is not one. - static Windows::Foundation::Uri GetSchemaUriForVersion(hstring value); - - // Gets the schema version associated with the given URI, or null if there is not one. - static hstring GetSchemaVersionForUri(Windows::Foundation::Uri value); - - // Gets the schema version associated with the given URI, or null if there is not one. - static std::string GetSchemaVersionForUri(std::string_view value); - - // Gets the latest schema version. - static std::pair LatestVersion(); - - virtual ~ConfigurationSetParser() noexcept = default; - - ConfigurationSetParser(const ConfigurationSetParser&) = delete; - ConfigurationSetParser& operator=(const ConfigurationSetParser&) = delete; - ConfigurationSetParser(ConfigurationSetParser&&) = default; - ConfigurationSetParser& operator=(ConfigurationSetParser&&) = default; - - // Parse the full document. - virtual void Parse() = 0; - - // Retrieves the schema version of the parser. - virtual hstring GetSchemaVersion() = 0; - - // Extracts (and removes) the environment information from the given metadata. - virtual void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) = 0; - - using ConfigurationSetPtr = winrt::com_ptr; - - // Retrieve the configuration set from the parser. - ConfigurationSetPtr GetConfigurationSet() const { return m_configurationSet; } - - // The latest result code from the parser. - hresult Result() const { return m_result; } - - // The field related to the result code. - hstring Field() const { return m_field; } - - // The value of the field. - hstring Value() const { return m_value; } - - // The line related to the result code. - uint32_t Line() const { return m_line; } - - // The column related to the result code. - uint32_t Column() const { return m_column; } - - // Parse a ValueSet from the given input. - Windows::Foundation::Collections::ValueSet ParseValueSet(std::string_view input); - - // Parse a string array from the given input. - std::vector ParseStringArray(std::string_view input); - - protected: - ConfigurationSetParser() = default; - - // Sets (or resets) the document to parse. - virtual void SetDocument(AppInstaller::YAML::Node&& document) = 0; - - // Set the error state - void SetError(hresult result, std::string_view field = {}, std::string_view value = {}, uint32_t line = 0, uint32_t column = 0); - void SetError(hresult result, std::string_view field, const AppInstaller::YAML::Mark& mark, std::string_view value = {}); - - ConfigurationSetPtr m_configurationSet; - hresult m_result; - hstring m_field; - hstring m_value; - uint32_t m_line = 0; - uint32_t m_column = 0; - - // Gets the given `field` from the `parent` node, checking against the requirement and type. - const AppInstaller::YAML::Node& GetAndEnsureField(const AppInstaller::YAML::Node& parent, ConfigurationField field, bool required, std::optional type); - - // Errors if the given `field` is present. - void EnsureFieldAbsent(const AppInstaller::YAML::Node& parent, ConfigurationField field); - - // Parse the ValueSet named `field` from the given `node`. - void ParseValueSet(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet); - - // Parse the mapping named `field` from the given `node`. - void ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function operation); - - // Parse the sequence named `field` from the given `node`. - void ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional elementType, std::function operation); - - // Parse the sequence from the given `node`. - void ParseSequence(const AppInstaller::YAML::Node& node, std::string_view nameForErrors, std::optional elementType, std::function operation); - - // Gets the string value in `field` from the given `node`, setting this value on `unit` using the `propertyFunction`. - void GetStringValueForUnit(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)); - - // Gets the string array in `field` from the given `node`, setting this value on `unit` using the `propertyFunction`. - void GetStringArrayForUnit(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector&& value)); - - // Validates the unit's Type property for correctness and consistency with the metadata. Should be called after parsing the Metadata value. - void ValidateType(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationField typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType); - - // Parses an object from the given node, attempting to treat it as the requested type if possible. - void ParseObject(const AppInstaller::YAML::Node& node, ConfigurationField fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result); - - // Extracts the security context from the metadata in the given unit; if not present use `defaultContext`. - void ExtractSecurityContext(implementation::ConfigurationUnit* unit, SecurityContext defaultContext = SecurityContext::Current); - void ExtractSecurityContext(Windows::Foundation::Collections::ValueSet metadata, implementation::ConfigurationEnvironment& environment, SecurityContext defaultContext = SecurityContext::Current); - - private: - // Support older schema parsing. - static std::unique_ptr GetSchemaVersionFromOldFormat(AppInstaller::YAML::Node& document, std::string& schemaVersionString); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Interface for parsing a configuration set stream. + struct ConfigurationSetParser + { + // Create a parser from the given bytes (the encoding is detected). + static std::unique_ptr Create(std::string_view input); + + // Create a parser for the given schema version. + static std::unique_ptr CreateForSchemaVersion(std::string schemaVersion); + + // Determines if the given value is a recognized schema version. + // This will only return true for a version that we fully recognize. + static bool IsRecognizedSchemaVersion(hstring value); + + // Determines if the given value is a recognized schema URI. + // This will only return true for a URI that we fully recognize. + static bool IsRecognizedSchemaUri(const Windows::Foundation::Uri& value); + + // Gets the schema URI associated with the given version, or null if there is not one. + static Windows::Foundation::Uri GetSchemaUriForVersion(hstring value); + + // Gets the schema version associated with the given URI, or null if there is not one. + static hstring GetSchemaVersionForUri(Windows::Foundation::Uri value); + + // Gets the schema version associated with the given URI, or null if there is not one. + static std::string GetSchemaVersionForUri(std::string_view value); + + // Gets the latest schema version. + static std::pair LatestVersion(); + + virtual ~ConfigurationSetParser() noexcept = default; + + ConfigurationSetParser(const ConfigurationSetParser&) = delete; + ConfigurationSetParser& operator=(const ConfigurationSetParser&) = delete; + ConfigurationSetParser(ConfigurationSetParser&&) = default; + ConfigurationSetParser& operator=(ConfigurationSetParser&&) = default; + + // Parse the full document. + virtual void Parse() = 0; + + // Retrieves the schema version of the parser. + virtual hstring GetSchemaVersion() = 0; + + // Extracts (and removes) the environment information from the given metadata. + virtual void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) = 0; + + using ConfigurationSetPtr = winrt::com_ptr; + + // Retrieve the configuration set from the parser. + ConfigurationSetPtr GetConfigurationSet() const { return m_configurationSet; } + + // The latest result code from the parser. + hresult Result() const { return m_result; } + + // The field related to the result code. + hstring Field() const { return m_field; } + + // The value of the field. + hstring Value() const { return m_value; } + + // The line related to the result code. + uint32_t Line() const { return m_line; } + + // The column related to the result code. + uint32_t Column() const { return m_column; } + + // Parse a ValueSet from the given input. + Windows::Foundation::Collections::ValueSet ParseValueSet(std::string_view input); + + // Parse a string array from the given input. + std::vector ParseStringArray(std::string_view input); + + protected: + ConfigurationSetParser() = default; + + // Sets (or resets) the document to parse. + virtual void SetDocument(AppInstaller::YAML::Node&& document) = 0; + + // Set the error state + void SetError(hresult result, std::string_view field = {}, std::string_view value = {}, uint32_t line = 0, uint32_t column = 0); + void SetError(hresult result, std::string_view field, const AppInstaller::YAML::Mark& mark, std::string_view value = {}); + + ConfigurationSetPtr m_configurationSet; + hresult m_result; + hstring m_field; + hstring m_value; + uint32_t m_line = 0; + uint32_t m_column = 0; + + // Gets the given `field` from the `parent` node, checking against the requirement and type. + const AppInstaller::YAML::Node& GetAndEnsureField(const AppInstaller::YAML::Node& parent, ConfigurationField field, bool required, std::optional type); + + // Errors if the given `field` is present. + void EnsureFieldAbsent(const AppInstaller::YAML::Node& parent, ConfigurationField field); + + // Parse the ValueSet named `field` from the given `node`. + void ParseValueSet(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, const Windows::Foundation::Collections::ValueSet& valueSet); + + // Parse the mapping named `field` from the given `node`. + void ParseMapping(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, AppInstaller::YAML::Node::Type elementType, std::function operation); + + // Parse the sequence named `field` from the given `node`. + void ParseSequence(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, std::optional elementType, std::function operation); + + // Parse the sequence from the given `node`. + void ParseSequence(const AppInstaller::YAML::Node& node, std::string_view nameForErrors, std::optional elementType, std::function operation); + + // Gets the string value in `field` from the given `node`, setting this value on `unit` using the `propertyFunction`. + void GetStringValueForUnit(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(const hstring& value)); + + // Gets the string array in `field` from the given `node`, setting this value on `unit` using the `propertyFunction`. + void GetStringArrayForUnit(const AppInstaller::YAML::Node& node, ConfigurationField field, bool required, ConfigurationUnit* unit, void(ConfigurationUnit::* propertyFunction)(std::vector&& value)); + + // Validates the unit's Type property for correctness and consistency with the metadata. Should be called after parsing the Metadata value. + void ValidateType(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationField typeField, bool moveModuleNameToMetadata, bool moduleNameRequiredInType); + + // Parses an object from the given node, attempting to treat it as the requested type if possible. + void ParseObject(const AppInstaller::YAML::Node& node, ConfigurationField fieldForErrors, Windows::Foundation::PropertyType type, Windows::Foundation::IInspectable& result); + + // Extracts the security context from the metadata in the given unit; if not present use `defaultContext`. + void ExtractSecurityContext(implementation::ConfigurationUnit* unit, SecurityContext defaultContext = SecurityContext::Current); + void ExtractSecurityContext(Windows::Foundation::Collections::ValueSet metadata, implementation::ConfigurationEnvironment& environment, SecurityContext defaultContext = SecurityContext::Current); + + private: + // Support older schema parsing. + static std::unique_ptr GetSchemaVersionFromOldFormat(AppInstaller::YAML::Node& document, std::string& schemaVersionString); + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParserError.h b/src/Microsoft.Management.Configuration/ConfigurationSetParserError.h index d5dfee3c25..07dee5e076 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParserError.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParserError.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetParser.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Parser object that only indicates an error occurred. - struct ConfigurationSetParserError : public ConfigurationSetParser - { - ConfigurationSetParserError(hresult result, std::string_view field = {}, std::string_view value = {}) - { - SetError(result, field, value); - } - - ConfigurationSetParserError(hresult result, std::string_view field, const AppInstaller::YAML::Mark& mark) - { - SetError(result, field, mark); - } - - void Parse() override {} - - hstring GetSchemaVersion() override { return {}; } - - void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet, implementation::ConfigurationEnvironment&) override {} - - protected: - void SetDocument(AppInstaller::YAML::Node&&) override {} - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetParser.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Parser object that only indicates an error occurred. + struct ConfigurationSetParserError : public ConfigurationSetParser + { + ConfigurationSetParserError(hresult result, std::string_view field = {}, std::string_view value = {}) + { + SetError(result, field, value); + } + + ConfigurationSetParserError(hresult result, std::string_view field, const AppInstaller::YAML::Mark& mark) + { + SetError(result, field, mark); + } + + void Parse() override {} + + hstring GetSchemaVersion() override { return {}; } + + void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet, implementation::ConfigurationEnvironment&) override {} + + protected: + void SetDocument(AppInstaller::YAML::Node&&) override {} + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp index 17b109fe4d..675032dea1 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.cpp @@ -1,63 +1,63 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetParser_0_1.h" -#include "ParsingMacros.h" - -#include -#include - -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - using namespace AppInstaller::YAML; - - void ConfigurationSetParser_0_1::Parse() - { - std::vector units; - const Node& properties = m_document[GetConfigurationFieldName(ConfigurationField::Properties)]; - ParseConfigurationUnitsFromField(properties, ConfigurationField::Assertions, ConfigurationUnitIntent::Assert, units); - ParseConfigurationUnitsFromField(properties, ConfigurationField::Parameters, ConfigurationUnitIntent::Inform, units); - ParseConfigurationUnitsFromField(properties, ConfigurationField::Resources, ConfigurationUnitIntent::Apply, units); - - m_configurationSet = make_self(); - m_configurationSet->Units(std::move(units)); - m_configurationSet->SchemaVersion(GetSchemaVersion()); - } - - hstring ConfigurationSetParser_0_1::GetSchemaVersion() - { - static hstring s_schemaVersion{ L"0.1" }; - return s_schemaVersion; - } - - void ConfigurationSetParser_0_1::ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet, implementation::ConfigurationEnvironment&) - { - } - - void ConfigurationSetParser_0_1::SetDocument(AppInstaller::YAML::Node&& document) - { - m_document = std::move(document); - } - - void ConfigurationSetParser_0_1::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector& result) - { - ParseSequence(document, field, false, Node::Type::Mapping, [&](const Node& item) - { - auto configurationUnit = make_self(); - ParseConfigurationUnit(configurationUnit.get(), item, intent); - result.emplace_back(*configurationUnit); - }); - } - - void ConfigurationSetParser_0_1::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) - { - CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Resource, true, unit, &ConfigurationUnit::Type)); - CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Id, false, unit, &ConfigurationUnit::Identifier)); - unit->Intent(intent); - CHECK_ERROR(GetStringArrayForUnit(unitNode, ConfigurationField::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); - CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Directives, false, unit->Metadata())); - CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Settings, false, unit->Settings())); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetParser_0_1.h" +#include "ParsingMacros.h" + +#include +#include + +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + using namespace AppInstaller::YAML; + + void ConfigurationSetParser_0_1::Parse() + { + std::vector units; + const Node& properties = m_document[GetConfigurationFieldName(ConfigurationField::Properties)]; + ParseConfigurationUnitsFromField(properties, ConfigurationField::Assertions, ConfigurationUnitIntent::Assert, units); + ParseConfigurationUnitsFromField(properties, ConfigurationField::Parameters, ConfigurationUnitIntent::Inform, units); + ParseConfigurationUnitsFromField(properties, ConfigurationField::Resources, ConfigurationUnitIntent::Apply, units); + + m_configurationSet = make_self(); + m_configurationSet->Units(std::move(units)); + m_configurationSet->SchemaVersion(GetSchemaVersion()); + } + + hstring ConfigurationSetParser_0_1::GetSchemaVersion() + { + static hstring s_schemaVersion{ L"0.1" }; + return s_schemaVersion; + } + + void ConfigurationSetParser_0_1::ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet, implementation::ConfigurationEnvironment&) + { + } + + void ConfigurationSetParser_0_1::SetDocument(AppInstaller::YAML::Node&& document) + { + m_document = std::move(document); + } + + void ConfigurationSetParser_0_1::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector& result) + { + ParseSequence(document, field, false, Node::Type::Mapping, [&](const Node& item) + { + auto configurationUnit = make_self(); + ParseConfigurationUnit(configurationUnit.get(), item, intent); + result.emplace_back(*configurationUnit); + }); + } + + void ConfigurationSetParser_0_1::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) + { + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Resource, true, unit, &ConfigurationUnit::Type)); + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Id, false, unit, &ConfigurationUnit::Identifier)); + unit->Intent(intent); + CHECK_ERROR(GetStringArrayForUnit(unitNode, ConfigurationField::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Directives, false, unit->Metadata())); + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Settings, false, unit->Settings())); + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h index 57cf2333e1..98aed182bd 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_1.h @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetParser.h" - -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Parser for schema version 0.1 - struct ConfigurationSetParser_0_1 : public ConfigurationSetParser - { - ConfigurationSetParser_0_1() = default; - - virtual ~ConfigurationSetParser_0_1() noexcept = default; - - ConfigurationSetParser_0_1(const ConfigurationSetParser_0_1&) = delete; - ConfigurationSetParser_0_1& operator=(const ConfigurationSetParser_0_1&) = delete; - ConfigurationSetParser_0_1(ConfigurationSetParser_0_1&&) = default; - ConfigurationSetParser_0_1& operator=(ConfigurationSetParser_0_1&&) = default; - - void Parse() override; - - // Retrieves the schema version of the parser. - hstring GetSchemaVersion() override; - - void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) override; - - protected: - // Sets (or resets) the document to parse. - void SetDocument(AppInstaller::YAML::Node&& document) override; - - void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector& result); - virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationUnitIntent intent); - - AppInstaller::YAML::Node m_document; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetParser.h" + +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Parser for schema version 0.1 + struct ConfigurationSetParser_0_1 : public ConfigurationSetParser + { + ConfigurationSetParser_0_1() = default; + + virtual ~ConfigurationSetParser_0_1() noexcept = default; + + ConfigurationSetParser_0_1(const ConfigurationSetParser_0_1&) = delete; + ConfigurationSetParser_0_1& operator=(const ConfigurationSetParser_0_1&) = delete; + ConfigurationSetParser_0_1(ConfigurationSetParser_0_1&&) = default; + ConfigurationSetParser_0_1& operator=(ConfigurationSetParser_0_1&&) = default; + + void Parse() override; + + // Retrieves the schema version of the parser. + hstring GetSchemaVersion() override; + + void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) override; + + protected: + // Sets (or resets) the document to parse. + void SetDocument(AppInstaller::YAML::Node&& document) override; + + void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, ConfigurationUnitIntent intent, std::vector& result); + virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationUnitIntent intent); + + AppInstaller::YAML::Node m_document; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp index 6e24aace31..cc44a26f31 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.cpp @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetParser_0_2.h" -#include "ParsingMacros.h" - -#include -#include - -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - using namespace AppInstaller::YAML; - - hstring ConfigurationSetParser_0_2::GetSchemaVersion() - { - static hstring s_schemaVersion{ L"0.2" }; - return s_schemaVersion; - } - - void ConfigurationSetParser_0_2::ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) - { - ExtractSecurityContext(valueSet, environment); - } - - void ConfigurationSetParser_0_2::SetDocument(AppInstaller::YAML::Node&& document) - { - m_document = std::move(document); - } - - void ConfigurationSetParser_0_2::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) - { - CHECK_ERROR(ConfigurationSetParser_0_1::ParseConfigurationUnit(unit, unitNode, intent)); - ValidateType(unit, unitNode, ConfigurationField::Resource, true, false); - ExtractSecurityContext(unit); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetParser_0_2.h" +#include "ParsingMacros.h" + +#include +#include + +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + using namespace AppInstaller::YAML; + + hstring ConfigurationSetParser_0_2::GetSchemaVersion() + { + static hstring s_schemaVersion{ L"0.2" }; + return s_schemaVersion; + } + + void ConfigurationSetParser_0_2::ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) + { + ExtractSecurityContext(valueSet, environment); + } + + void ConfigurationSetParser_0_2::SetDocument(AppInstaller::YAML::Node&& document) + { + m_document = std::move(document); + } + + void ConfigurationSetParser_0_2::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode, ConfigurationUnitIntent intent) + { + CHECK_ERROR(ConfigurationSetParser_0_1::ParseConfigurationUnit(unit, unitNode, intent)); + ValidateType(unit, unitNode, ConfigurationField::Resource, true, false); + ExtractSecurityContext(unit); + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.h index 04d75dfe9d..1fb9d958ba 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_2.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetParser_0_1.h" - -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Parser for schema version 0.2 - struct ConfigurationSetParser_0_2 : public ConfigurationSetParser_0_1 - { - ConfigurationSetParser_0_2() = default; - - virtual ~ConfigurationSetParser_0_2() noexcept = default; - - ConfigurationSetParser_0_2(const ConfigurationSetParser_0_2&) = delete; - ConfigurationSetParser_0_2& operator=(const ConfigurationSetParser_0_2&) = delete; - ConfigurationSetParser_0_2(ConfigurationSetParser_0_2&&) = default; - ConfigurationSetParser_0_2& operator=(ConfigurationSetParser_0_2&&) = default; - - // Retrieves the schema version of the parser. - hstring GetSchemaVersion() override; - - void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) override; - - protected: - // Sets (or resets) the document to parse. - void SetDocument(AppInstaller::YAML::Node&& document) override; - - void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationUnitIntent intent) override; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetParser_0_1.h" + +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Parser for schema version 0.2 + struct ConfigurationSetParser_0_2 : public ConfigurationSetParser_0_1 + { + ConfigurationSetParser_0_2() = default; + + virtual ~ConfigurationSetParser_0_2() noexcept = default; + + ConfigurationSetParser_0_2(const ConfigurationSetParser_0_2&) = delete; + ConfigurationSetParser_0_2& operator=(const ConfigurationSetParser_0_2&) = delete; + ConfigurationSetParser_0_2(ConfigurationSetParser_0_2&&) = default; + ConfigurationSetParser_0_2& operator=(ConfigurationSetParser_0_2&&) = default; + + // Retrieves the schema version of the parser. + hstring GetSchemaVersion() override; + + void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) override; + + protected: + // Sets (or resets) the document to parse. + void SetDocument(AppInstaller::YAML::Node&& document) override; + + void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode, ConfigurationUnitIntent intent) override; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp index 7c67af20b4..e8f6e64ff4 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.cpp @@ -1,360 +1,360 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetParser_0_3.h" -#include "ParsingMacros.h" -#include "ArgumentValidation.h" - -#include -#include - -#include - -using namespace AppInstaller::YAML; -using namespace winrt::Windows::Foundation; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - using IInspectable = winrt::Windows::Foundation::IInspectable; - - void ConfigurationSetParser_0_3::Parse() - { - auto result = make_self(); - - CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Metadata, false, result->Metadata())); - - CHECK_ERROR(ExtractEnvironmentFromMetadata(result->Metadata(), result->EnvironmentInternal())); - - CHECK_ERROR(ParseParameters(result)); - CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Variables, false, result->Variables())); - - std::vector units; - CHECK_ERROR(ParseConfigurationUnitsFromField(m_document, ConfigurationField::Resources, units)); - result->Units(std::move(units)); - - result->SchemaVersion(GetSchemaVersion()); - m_configurationSet = std::move(result); - } - - hstring ConfigurationSetParser_0_3::GetSchemaVersion() - { - static hstring s_schemaVersion{ L"0.3" }; - return s_schemaVersion; - } - - void ConfigurationSetParser_0_3::SetDocument(AppInstaller::YAML::Node&& document) - { - m_document = std::move(document); - } - - void ConfigurationSetParser_0_3::ParseParameters(ConfigurationSetParser::ConfigurationSetPtr& set) - { - std::vector parameters; - - ParseMapping(m_document, ConfigurationField::Parameters, false, Node::Type::Mapping, [&](std::string name, const Node& item) - { - auto parameter = make_self>(); - CHECK_ERROR(ParseParameter(parameter.get(), item)); - parameter->Name(hstring{ AppInstaller::Utility::ConvertToUTF16(name) }); - parameters.emplace_back(*parameter); - }); - - set->Parameters(std::move(parameters)); - } - - void ConfigurationSetParser_0_3::ParseParameter(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node) - { - CHECK_ERROR(ParseParameterType(parameter, node)); - CHECK_ERROR(ParseValueSet(node, ConfigurationField::Metadata, false, parameter->Metadata())); - CHECK_ERROR(GetStringValueForParameter(node, ConfigurationField::Description, parameter, &ConfigurationParameter::Description)); - - PropertyType parameterType = parameter->Type(); - CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::DefaultValue, parameterType, parameter, &ConfigurationParameter::DefaultValue)); - - std::vector allowedValues; - - CHECK_ERROR(ParseSequence(node, ConfigurationField::AllowedValues, false, std::nullopt, [&](const Node& item) - { - IInspectable object; - CHECK_ERROR(ParseObject(item, ConfigurationField::AllowedValues, parameterType, object)); - allowedValues.emplace_back(std::move(object)); - })); - - if (!allowedValues.empty()) - { - parameter->AllowedValues(std::move(allowedValues)); - } - - if (IsLengthType(parameterType)) - { - CHECK_ERROR(GetUInt32ValueForParameter(node, ConfigurationField::MinimumLength, parameter, &ConfigurationParameter::MinimumLength)); - CHECK_ERROR(GetUInt32ValueForParameter(node, ConfigurationField::MaximumLength, parameter, &ConfigurationParameter::MaximumLength)); - } - else - { - CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MinimumLength)); - CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MaximumLength)); - } - - if (IsComparableType(parameterType)) - { - CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::MinimumValue, parameterType, parameter, &ConfigurationParameter::MinimumValue)); - CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::MaximumValue, parameterType, parameter, &ConfigurationParameter::MaximumValue)); - } - else - { - CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MinimumValue)); - CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MaximumValue)); - } - } - - void ConfigurationSetParser_0_3::ParseParameterType(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node) - { - const Node& typeNode = CHECK_ERROR(GetAndEnsureField(node, ConfigurationField::Type, true, Node::Type::Scalar)); - std::string typeValue = typeNode.as(); - auto parsedType = ParseWindowsFoundationPropertyType(typeValue); - - if (parsedType) - { - parameter->Type(parsedType->first); - parameter->IsSecure(parsedType->second); - } - else - { - FIELD_VALUE_ERROR(GetConfigurationFieldName(ConfigurationField::Type), typeValue, typeNode.Mark()); - } - } - - void ConfigurationSetParser_0_3::GetStringValueForParameter( - const Node& node, - ConfigurationField field, - ConfigurationParameter* parameter, - void(ConfigurationParameter::* propertyFunction)(const hstring& value)) - { - const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, false, Node::Type::Scalar)); - - if (valueNode) - { - (parameter->*propertyFunction)(hstring{ valueNode.as() }); - } - } - - void ConfigurationSetParser_0_3::GetUInt32ValueForParameter( - const AppInstaller::YAML::Node& node, - ConfigurationField field, - ConfigurationParameter* parameter, - void(ConfigurationParameter::* propertyFunction)(uint32_t value)) - { - const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, false, Node::Type::Scalar)); - - if (valueNode) - { - int64_t value = valueNode.as(); - if (value < 0 || value > static_cast(std::numeric_limits::max())) - { - FIELD_VALUE_ERROR(GetConfigurationFieldName(field), valueNode.as(), valueNode.Mark()); - } - (parameter->*propertyFunction)(static_cast(value)); - } - } - - void ConfigurationSetParser_0_3::ParseObjectValueForParameter( - const AppInstaller::YAML::Node& node, - ConfigurationField field, - PropertyType type, - ConfigurationParameter* parameter, - void(ConfigurationParameter::* propertyFunction)(const IInspectable& value)) - { - const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, false, std::nullopt)); - - if (valueNode) - { - IInspectable valueObject; - CHECK_ERROR(ParseObject(valueNode, field, type, valueObject)); - - (parameter->*propertyFunction)(valueObject); - } - } - - void ConfigurationSetParser_0_3::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, std::vector& result) - { - ParseSequence(document, field, false, Node::Type::Mapping, [&](const Node& item) - { - auto configurationUnit = make_self(); - ParseConfigurationUnit(configurationUnit.get(), item); - result.emplace_back(*configurationUnit); - }); - } - - void ConfigurationSetParser_0_3::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode) - { - // Set unknown intent as the new schema doesn't express it directly - unit->Intent(ConfigurationUnitIntent::Unknown); - - CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Name, true, unit, &ConfigurationUnit::Identifier)); - CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Type, true, unit, &ConfigurationUnit::Type)); - CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Metadata, false, unit->Metadata())); - CHECK_ERROR(ExtractEnvironmentForUnit(unit)); - CHECK_ERROR(ValidateType(unit, unitNode, ConfigurationField::Type, false, true)); - CHECK_ERROR(GetStringArrayForUnit(unitNode, ConfigurationField::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); - - // Regardless of being a group or not, parse the settings. - CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Properties, false, unit->Settings())); - - if (ShouldConvertToGroup(unit)) - { - unit->IsGroup(true); - - // TODO: The PS DSC v3 POR looks like it supports each group defining a new schema to be used for its group items. - // Consider supporting that in the future; but for now just use the same schema for everything. - const Node& propertiesNode = GetAndEnsureField(unitNode, ConfigurationField::Properties, false, Node::Type::Mapping); - if (propertiesNode) - { - std::vector units; - CHECK_ERROR(ParseConfigurationUnitsFromField(propertiesNode, ConfigurationField::Resources, units)); - unit->Units(std::move(units)); - } - } - } - - bool ConfigurationSetParser_0_3::ShouldConvertToGroup(ConfigurationUnit* unit) - { - // Allow the metadata to inform us that we should treat it as a group, including preventing a known type from being treated as one. - auto isGroupObject = unit->Metadata().TryLookup(GetConfigurationFieldNameHString(ConfigurationField::IsGroupMetadata)); - if (isGroupObject) - { - auto isGroupProperty = isGroupObject.try_as(); - if (isGroupProperty && isGroupProperty.Type() == PropertyType::Boolean) - { - return isGroupProperty.GetBoolean(); - } - } - - // TODO: Check for known types - - return false; - } - - void ConfigurationSetParser_0_3::ExtractEnvironmentFromMetadata(Collections::ValueSet metadata, ConfigurationEnvironment& targetEnvironment) - { - auto root = TryLookupValueSet(metadata, ConfigurationField::WingetMetadataRoot); - if (root) - { - // Get security context - auto securityContext = TryLookupProperty(root, ConfigurationField::SecurityContextMetadata, PropertyType::String); - if (securityContext) - { - SecurityContext computedContext = SecurityContext::Current; - if (TryParseSecurityContext(securityContext.GetString(), computedContext)) - { - targetEnvironment.Context(computedContext); - } - root.Remove(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata)); - } - - // Get processor - hstring processorFieldName = GetConfigurationFieldNameHString(ConfigurationField::ProcessorMetadata); - IInspectable processor = root.TryLookup(processorFieldName); - Collections::ValueSet processorValueSet = processor.try_as(); - if (processorValueSet) - { - targetEnvironment.ProcessorIdentifier({}); - targetEnvironment.ProcessorProperties().Clear(); - - IPropertyValue identifier = TryLookupProperty(processorValueSet, ConfigurationField::ProcessorIdentifierMetadata, PropertyType::String); - if (identifier) - { - targetEnvironment.ProcessorIdentifier(identifier.GetString()); - - Collections::ValueSet processorSettings = TryLookupValueSet(processorValueSet, ConfigurationField::ProcessorPropertiesMetadata); - if (processorSettings) - { - targetEnvironment.ProcessorProperties(processorSettings); - } - } - - root.Remove(processorFieldName); - } - else - { - IPropertyValue processorProperty = processor.try_as(); - if (processorProperty) - { - targetEnvironment.ProcessorIdentifier(processorProperty.GetString()); - targetEnvironment.ProcessorProperties().Clear(); - root.Remove(processorFieldName); - } - } - - if (root.Size() == 0) - { - metadata.Remove(GetConfigurationFieldNameHString(ConfigurationField::WingetMetadataRoot)); - } - } - } - - void ConfigurationSetParser_0_3::ExtractEnvironmentForUnit(ConfigurationUnit* unit) - { - // Get unnested security context - ExtractSecurityContext(unit); - - // Get nested environment - ExtractEnvironmentFromMetadata(unit->Metadata(), unit->EnvironmentInternal()); - } - - std::optional> ParseWindowsFoundationPropertyType(std::string_view value) - { - if (value == "string") - { - return std::make_pair(PropertyType::String, false); - } - else if (value == "securestring") - { - return std::make_pair(PropertyType::String, true); - } - else if (value == "int") - { - return std::make_pair(PropertyType::Int64, false); - } - else if (value == "bool") - { - return std::make_pair(PropertyType::Boolean, false); - } - else if (value == "object") - { - return std::make_pair(PropertyType::Inspectable, false); - } - else if (value == "secureobject") - { - return std::make_pair(PropertyType::Inspectable, true); - } - else if (value == "array") - { - return std::make_pair(PropertyType::InspectableArray, false); - } - - // TODO: Consider supporting an expanded set of type strings - return std::nullopt; - } - - std::string_view ToString(PropertyType value, bool isSecure) - { - switch (value) - { - case PropertyType::Int16: - case PropertyType::Int32: - case PropertyType::Int64: - return "int"sv; - case PropertyType::Boolean: - return "bool"sv; - case PropertyType::String: - return isSecure ? "securestring"sv : "string"sv; - case PropertyType::Inspectable: - return isSecure ? "secureobject"sv : "object"sv; - case PropertyType::InspectableArray: - return "array"sv; - default: - return {}; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetParser_0_3.h" +#include "ParsingMacros.h" +#include "ArgumentValidation.h" + +#include +#include + +#include + +using namespace AppInstaller::YAML; +using namespace winrt::Windows::Foundation; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + using IInspectable = winrt::Windows::Foundation::IInspectable; + + void ConfigurationSetParser_0_3::Parse() + { + auto result = make_self(); + + CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Metadata, false, result->Metadata())); + + CHECK_ERROR(ExtractEnvironmentFromMetadata(result->Metadata(), result->EnvironmentInternal())); + + CHECK_ERROR(ParseParameters(result)); + CHECK_ERROR(ParseValueSet(m_document, ConfigurationField::Variables, false, result->Variables())); + + std::vector units; + CHECK_ERROR(ParseConfigurationUnitsFromField(m_document, ConfigurationField::Resources, units)); + result->Units(std::move(units)); + + result->SchemaVersion(GetSchemaVersion()); + m_configurationSet = std::move(result); + } + + hstring ConfigurationSetParser_0_3::GetSchemaVersion() + { + static hstring s_schemaVersion{ L"0.3" }; + return s_schemaVersion; + } + + void ConfigurationSetParser_0_3::SetDocument(AppInstaller::YAML::Node&& document) + { + m_document = std::move(document); + } + + void ConfigurationSetParser_0_3::ParseParameters(ConfigurationSetParser::ConfigurationSetPtr& set) + { + std::vector parameters; + + ParseMapping(m_document, ConfigurationField::Parameters, false, Node::Type::Mapping, [&](std::string name, const Node& item) + { + auto parameter = make_self>(); + CHECK_ERROR(ParseParameter(parameter.get(), item)); + parameter->Name(hstring{ AppInstaller::Utility::ConvertToUTF16(name) }); + parameters.emplace_back(*parameter); + }); + + set->Parameters(std::move(parameters)); + } + + void ConfigurationSetParser_0_3::ParseParameter(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node) + { + CHECK_ERROR(ParseParameterType(parameter, node)); + CHECK_ERROR(ParseValueSet(node, ConfigurationField::Metadata, false, parameter->Metadata())); + CHECK_ERROR(GetStringValueForParameter(node, ConfigurationField::Description, parameter, &ConfigurationParameter::Description)); + + PropertyType parameterType = parameter->Type(); + CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::DefaultValue, parameterType, parameter, &ConfigurationParameter::DefaultValue)); + + std::vector allowedValues; + + CHECK_ERROR(ParseSequence(node, ConfigurationField::AllowedValues, false, std::nullopt, [&](const Node& item) + { + IInspectable object; + CHECK_ERROR(ParseObject(item, ConfigurationField::AllowedValues, parameterType, object)); + allowedValues.emplace_back(std::move(object)); + })); + + if (!allowedValues.empty()) + { + parameter->AllowedValues(std::move(allowedValues)); + } + + if (IsLengthType(parameterType)) + { + CHECK_ERROR(GetUInt32ValueForParameter(node, ConfigurationField::MinimumLength, parameter, &ConfigurationParameter::MinimumLength)); + CHECK_ERROR(GetUInt32ValueForParameter(node, ConfigurationField::MaximumLength, parameter, &ConfigurationParameter::MaximumLength)); + } + else + { + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MinimumLength)); + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MaximumLength)); + } + + if (IsComparableType(parameterType)) + { + CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::MinimumValue, parameterType, parameter, &ConfigurationParameter::MinimumValue)); + CHECK_ERROR(ParseObjectValueForParameter(node, ConfigurationField::MaximumValue, parameterType, parameter, &ConfigurationParameter::MaximumValue)); + } + else + { + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MinimumValue)); + CHECK_ERROR(EnsureFieldAbsent(node, ConfigurationField::MaximumValue)); + } + } + + void ConfigurationSetParser_0_3::ParseParameterType(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node) + { + const Node& typeNode = CHECK_ERROR(GetAndEnsureField(node, ConfigurationField::Type, true, Node::Type::Scalar)); + std::string typeValue = typeNode.as(); + auto parsedType = ParseWindowsFoundationPropertyType(typeValue); + + if (parsedType) + { + parameter->Type(parsedType->first); + parameter->IsSecure(parsedType->second); + } + else + { + FIELD_VALUE_ERROR(GetConfigurationFieldName(ConfigurationField::Type), typeValue, typeNode.Mark()); + } + } + + void ConfigurationSetParser_0_3::GetStringValueForParameter( + const Node& node, + ConfigurationField field, + ConfigurationParameter* parameter, + void(ConfigurationParameter::* propertyFunction)(const hstring& value)) + { + const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, false, Node::Type::Scalar)); + + if (valueNode) + { + (parameter->*propertyFunction)(hstring{ valueNode.as() }); + } + } + + void ConfigurationSetParser_0_3::GetUInt32ValueForParameter( + const AppInstaller::YAML::Node& node, + ConfigurationField field, + ConfigurationParameter* parameter, + void(ConfigurationParameter::* propertyFunction)(uint32_t value)) + { + const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, false, Node::Type::Scalar)); + + if (valueNode) + { + int64_t value = valueNode.as(); + if (value < 0 || value > static_cast(std::numeric_limits::max())) + { + FIELD_VALUE_ERROR(GetConfigurationFieldName(field), valueNode.as(), valueNode.Mark()); + } + (parameter->*propertyFunction)(static_cast(value)); + } + } + + void ConfigurationSetParser_0_3::ParseObjectValueForParameter( + const AppInstaller::YAML::Node& node, + ConfigurationField field, + PropertyType type, + ConfigurationParameter* parameter, + void(ConfigurationParameter::* propertyFunction)(const IInspectable& value)) + { + const Node& valueNode = CHECK_ERROR(GetAndEnsureField(node, field, false, std::nullopt)); + + if (valueNode) + { + IInspectable valueObject; + CHECK_ERROR(ParseObject(valueNode, field, type, valueObject)); + + (parameter->*propertyFunction)(valueObject); + } + } + + void ConfigurationSetParser_0_3::ParseConfigurationUnitsFromField(const Node& document, ConfigurationField field, std::vector& result) + { + ParseSequence(document, field, false, Node::Type::Mapping, [&](const Node& item) + { + auto configurationUnit = make_self(); + ParseConfigurationUnit(configurationUnit.get(), item); + result.emplace_back(*configurationUnit); + }); + } + + void ConfigurationSetParser_0_3::ParseConfigurationUnit(ConfigurationUnit* unit, const Node& unitNode) + { + // Set unknown intent as the new schema doesn't express it directly + unit->Intent(ConfigurationUnitIntent::Unknown); + + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Name, true, unit, &ConfigurationUnit::Identifier)); + CHECK_ERROR(GetStringValueForUnit(unitNode, ConfigurationField::Type, true, unit, &ConfigurationUnit::Type)); + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Metadata, false, unit->Metadata())); + CHECK_ERROR(ExtractEnvironmentForUnit(unit)); + CHECK_ERROR(ValidateType(unit, unitNode, ConfigurationField::Type, false, true)); + CHECK_ERROR(GetStringArrayForUnit(unitNode, ConfigurationField::DependsOn, false, unit, &ConfigurationUnit::Dependencies)); + + // Regardless of being a group or not, parse the settings. + CHECK_ERROR(ParseValueSet(unitNode, ConfigurationField::Properties, false, unit->Settings())); + + if (ShouldConvertToGroup(unit)) + { + unit->IsGroup(true); + + // TODO: The PS DSC v3 POR looks like it supports each group defining a new schema to be used for its group items. + // Consider supporting that in the future; but for now just use the same schema for everything. + const Node& propertiesNode = GetAndEnsureField(unitNode, ConfigurationField::Properties, false, Node::Type::Mapping); + if (propertiesNode) + { + std::vector units; + CHECK_ERROR(ParseConfigurationUnitsFromField(propertiesNode, ConfigurationField::Resources, units)); + unit->Units(std::move(units)); + } + } + } + + bool ConfigurationSetParser_0_3::ShouldConvertToGroup(ConfigurationUnit* unit) + { + // Allow the metadata to inform us that we should treat it as a group, including preventing a known type from being treated as one. + auto isGroupObject = unit->Metadata().TryLookup(GetConfigurationFieldNameHString(ConfigurationField::IsGroupMetadata)); + if (isGroupObject) + { + auto isGroupProperty = isGroupObject.try_as(); + if (isGroupProperty && isGroupProperty.Type() == PropertyType::Boolean) + { + return isGroupProperty.GetBoolean(); + } + } + + // TODO: Check for known types + + return false; + } + + void ConfigurationSetParser_0_3::ExtractEnvironmentFromMetadata(Collections::ValueSet metadata, ConfigurationEnvironment& targetEnvironment) + { + auto root = TryLookupValueSet(metadata, ConfigurationField::WingetMetadataRoot); + if (root) + { + // Get security context + auto securityContext = TryLookupProperty(root, ConfigurationField::SecurityContextMetadata, PropertyType::String); + if (securityContext) + { + SecurityContext computedContext = SecurityContext::Current; + if (TryParseSecurityContext(securityContext.GetString(), computedContext)) + { + targetEnvironment.Context(computedContext); + } + root.Remove(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata)); + } + + // Get processor + hstring processorFieldName = GetConfigurationFieldNameHString(ConfigurationField::ProcessorMetadata); + IInspectable processor = root.TryLookup(processorFieldName); + Collections::ValueSet processorValueSet = processor.try_as(); + if (processorValueSet) + { + targetEnvironment.ProcessorIdentifier({}); + targetEnvironment.ProcessorProperties().Clear(); + + IPropertyValue identifier = TryLookupProperty(processorValueSet, ConfigurationField::ProcessorIdentifierMetadata, PropertyType::String); + if (identifier) + { + targetEnvironment.ProcessorIdentifier(identifier.GetString()); + + Collections::ValueSet processorSettings = TryLookupValueSet(processorValueSet, ConfigurationField::ProcessorPropertiesMetadata); + if (processorSettings) + { + targetEnvironment.ProcessorProperties(processorSettings); + } + } + + root.Remove(processorFieldName); + } + else + { + IPropertyValue processorProperty = processor.try_as(); + if (processorProperty) + { + targetEnvironment.ProcessorIdentifier(processorProperty.GetString()); + targetEnvironment.ProcessorProperties().Clear(); + root.Remove(processorFieldName); + } + } + + if (root.Size() == 0) + { + metadata.Remove(GetConfigurationFieldNameHString(ConfigurationField::WingetMetadataRoot)); + } + } + } + + void ConfigurationSetParser_0_3::ExtractEnvironmentForUnit(ConfigurationUnit* unit) + { + // Get unnested security context + ExtractSecurityContext(unit); + + // Get nested environment + ExtractEnvironmentFromMetadata(unit->Metadata(), unit->EnvironmentInternal()); + } + + std::optional> ParseWindowsFoundationPropertyType(std::string_view value) + { + if (value == "string") + { + return std::make_pair(PropertyType::String, false); + } + else if (value == "securestring") + { + return std::make_pair(PropertyType::String, true); + } + else if (value == "int") + { + return std::make_pair(PropertyType::Int64, false); + } + else if (value == "bool") + { + return std::make_pair(PropertyType::Boolean, false); + } + else if (value == "object") + { + return std::make_pair(PropertyType::Inspectable, false); + } + else if (value == "secureobject") + { + return std::make_pair(PropertyType::Inspectable, true); + } + else if (value == "array") + { + return std::make_pair(PropertyType::InspectableArray, false); + } + + // TODO: Consider supporting an expanded set of type strings + return std::nullopt; + } + + std::string_view ToString(PropertyType value, bool isSecure) + { + switch (value) + { + case PropertyType::Int16: + case PropertyType::Int32: + case PropertyType::Int64: + return "int"sv; + case PropertyType::Boolean: + return "bool"sv; + case PropertyType::String: + return isSecure ? "securestring"sv : "string"sv; + case PropertyType::Inspectable: + return isSecure ? "secureobject"sv : "object"sv; + case PropertyType::InspectableArray: + return "array"sv; + default: + return {}; + } + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h index 2db3ab18bc..97c3e2354e 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetParser_0_3.h @@ -1,73 +1,73 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetParser.h" -#include "ConfigurationParameter.h" -#include - -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Parser for schema version 0.3 - struct ConfigurationSetParser_0_3 : public ConfigurationSetParser - { - ConfigurationSetParser_0_3() = default; - - virtual ~ConfigurationSetParser_0_3() noexcept = default; - - ConfigurationSetParser_0_3(const ConfigurationSetParser_0_3&) = delete; - ConfigurationSetParser_0_3& operator=(const ConfigurationSetParser_0_3&) = delete; - ConfigurationSetParser_0_3(ConfigurationSetParser_0_3&&) = default; - ConfigurationSetParser_0_3& operator=(ConfigurationSetParser_0_3&&) = default; - - // Retrieve the configuration units from the parser. - void Parse() override; - - // Retrieves the schema version of the parser. - hstring GetSchemaVersion() override; - - // Extracts the environment configuration from the given metadata. - // This only examines the winget subnode. - void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) override; - - protected: - // Sets (or resets) the document to parse. - void SetDocument(AppInstaller::YAML::Node&& document) override; - - void ParseParameters(ConfigurationSetParser::ConfigurationSetPtr& set); - void ParseParameter(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node); - void ParseParameterType(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node); - void GetStringValueForParameter( - const AppInstaller::YAML::Node& node, - ConfigurationField field, - ConfigurationParameter* parameter, - void(ConfigurationParameter::* propertyFunction)(const hstring& value)); - void GetUInt32ValueForParameter( - const AppInstaller::YAML::Node& node, - ConfigurationField field, - ConfigurationParameter* parameter, - void(ConfigurationParameter::* propertyFunction)(uint32_t value)); - void ParseObjectValueForParameter( - const AppInstaller::YAML::Node& node, - ConfigurationField field, - Windows::Foundation::PropertyType type, - ConfigurationParameter* parameter, - void(ConfigurationParameter::* propertyFunction)(const Windows::Foundation::IInspectable& value)); - - void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, std::vector& result); - virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode); - // Determines if the given unit should be converted to a group. - bool ShouldConvertToGroup(ConfigurationUnit* unit); - - // Extracts the environment for a unit. - void ExtractEnvironmentForUnit(ConfigurationUnit* unit); - - AppInstaller::YAML::Node m_document; - }; - - std::optional> ParseWindowsFoundationPropertyType(std::string_view value); - std::string_view ToString(Windows::Foundation::PropertyType value, bool isSecure); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetParser.h" +#include "ConfigurationParameter.h" +#include + +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Parser for schema version 0.3 + struct ConfigurationSetParser_0_3 : public ConfigurationSetParser + { + ConfigurationSetParser_0_3() = default; + + virtual ~ConfigurationSetParser_0_3() noexcept = default; + + ConfigurationSetParser_0_3(const ConfigurationSetParser_0_3&) = delete; + ConfigurationSetParser_0_3& operator=(const ConfigurationSetParser_0_3&) = delete; + ConfigurationSetParser_0_3(ConfigurationSetParser_0_3&&) = default; + ConfigurationSetParser_0_3& operator=(ConfigurationSetParser_0_3&&) = default; + + // Retrieve the configuration units from the parser. + void Parse() override; + + // Retrieves the schema version of the parser. + hstring GetSchemaVersion() override; + + // Extracts the environment configuration from the given metadata. + // This only examines the winget subnode. + void ExtractEnvironmentFromMetadata(Windows::Foundation::Collections::ValueSet valueSet, implementation::ConfigurationEnvironment& environment) override; + + protected: + // Sets (or resets) the document to parse. + void SetDocument(AppInstaller::YAML::Node&& document) override; + + void ParseParameters(ConfigurationSetParser::ConfigurationSetPtr& set); + void ParseParameter(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node); + void ParseParameterType(ConfigurationParameter* parameter, const AppInstaller::YAML::Node& node); + void GetStringValueForParameter( + const AppInstaller::YAML::Node& node, + ConfigurationField field, + ConfigurationParameter* parameter, + void(ConfigurationParameter::* propertyFunction)(const hstring& value)); + void GetUInt32ValueForParameter( + const AppInstaller::YAML::Node& node, + ConfigurationField field, + ConfigurationParameter* parameter, + void(ConfigurationParameter::* propertyFunction)(uint32_t value)); + void ParseObjectValueForParameter( + const AppInstaller::YAML::Node& node, + ConfigurationField field, + Windows::Foundation::PropertyType type, + ConfigurationParameter* parameter, + void(ConfigurationParameter::* propertyFunction)(const Windows::Foundation::IInspectable& value)); + + void ParseConfigurationUnitsFromField(const AppInstaller::YAML::Node& document, ConfigurationField field, std::vector& result); + virtual void ParseConfigurationUnit(ConfigurationUnit* unit, const AppInstaller::YAML::Node& unitNode); + // Determines if the given unit should be converted to a group. + bool ShouldConvertToGroup(ConfigurationUnit* unit); + + // Extracts the environment for a unit. + void ExtractEnvironmentForUnit(ConfigurationUnit* unit); + + AppInstaller::YAML::Node m_document; + }; + + std::optional> ParseWindowsFoundationPropertyType(std::string_view value); + std::string_view ToString(Windows::Foundation::PropertyType value, bool isSecure); +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp index 2d66c9540f..c109c06a33 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.cpp @@ -1,292 +1,292 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" - -#include -#include -#include - -#include "ConfigurationSetSerializer.h" -#include "ConfigurationSetSerializer_0_2.h" -#include "ConfigurationSetSerializer_0_3.h" -#include "ConfigurationSetUtilities.h" - -using namespace AppInstaller::Utility; -using namespace AppInstaller::YAML; -using namespace winrt::Windows::Foundation; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace anon - { - static constexpr std::string_view s_nullValue = "null"; - - struct ValueSetWriter - { - ValueSetWriter(const Windows::Foundation::Collections::ValueSet& valueSet, const std::vector>& overrides) : - m_valueSet(valueSet), m_overrides(overrides) - { - // Create a sorted list of the field names to exclude - for (const auto & override : m_overrides) - { - m_exclusionStrings.push_back(GetConfigurationFieldNameHString(override.first)); - } - std::sort(m_exclusionStrings.begin(), m_exclusionStrings.end()); - } - - bool IsResultEmpty() - { - size_t nullOverrides = 0; - - for (const auto& override : m_overrides) - { - // A non-null override will always be output - if (override.second) - { - return false; - } - else - { - ++nullOverrides; - } - } - - if (m_valueSet) - { - // If there are more values than null overrides, something will be output - if (static_cast(m_valueSet.Size()) > nullOverrides) - { - return false; - } - - // Check for a value that we would output - for (const auto& [key, value] : m_valueSet) - { - if (value != nullptr && - !std::binary_search(m_exclusionStrings.begin(), m_exclusionStrings.end(), key)) - { - return false; - } - } - } - - return true; - } - - void Write(AppInstaller::YAML::Emitter& emitter, void(* WriteYamlValue)(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value)) - { - emitter << BeginMap; - - WriteValues(emitter, WriteYamlValue); - - emitter << EndMap; - } - - void WriteValues(AppInstaller::YAML::Emitter& emitter, void(*WriteYamlValue)(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value)) - { - if (m_valueSet) - { - for (const auto& [key, value] : m_valueSet) - { - if (value != nullptr && - !std::binary_search(m_exclusionStrings.begin(), m_exclusionStrings.end(), key)) - { - std::string keyName = winrt::to_string(key); - emitter << Key << keyName << Value; - WriteYamlValue(emitter, value); - } - } - } - - for (const auto & override : m_overrides) - { - if (override.second != nullptr) - { - std::string_view keyName = GetConfigurationFieldName(override.first); - emitter << Key << keyName << Value; - WriteYamlValue(emitter, override.second); - } - } - } - - private: - const Windows::Foundation::Collections::ValueSet& m_valueSet; - const std::vector>& m_overrides; - std::vector m_exclusionStrings; - }; - } - - std::unique_ptr ConfigurationSetSerializer::CreateSerializer(hstring version, bool strictVersionMatching) - { - // Create the parser based on the version selected - SemanticVersion schemaVersion(std::move(winrt::to_string(version))); - - // TODO: Consider having the version/uri/type information all together in the future - if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 1) - { - // Remove this once the 0.1 serializer is implemented. - THROW_HR_IF(E_NOTIMPL, strictVersionMatching); - - return std::make_unique(); - - } - else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 2) - { - return std::make_unique(); - } - else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 3) - { - return std::make_unique(); - } - else - { - AICLI_LOG(Config, Error, << "Unknown configuration version: " << schemaVersion.ToString()); - THROW_HR(E_UNEXPECTED); - } - } - - std::string ConfigurationSetSerializer::SerializeValueSet(const Windows::Foundation::Collections::ValueSet& valueSet) - { - Emitter emitter; - WriteYamlValueSet(emitter, valueSet); - return emitter.str(); - } - - std::string ConfigurationSetSerializer::SerializeStringArray(const Windows::Foundation::Collections::IVector& stringArray) - { - Emitter emitter; - WriteYamlStringArray(emitter, stringArray); - return emitter.str(); - } - - void ConfigurationSetSerializer::WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides) - { - anon::ValueSetWriter writer{ valueSet, overrides }; - - if (!writer.IsResultEmpty()) - { - emitter << Key << GetConfigurationFieldName(key); - writer.Write(emitter, WriteYamlValue); - } - } - - void ConfigurationSetSerializer::WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides) - { - anon::ValueSetWriter writer{ valueSet, overrides }; - writer.Write(emitter, WriteYamlValue); - } - - void ConfigurationSetSerializer::WriteYamlValueSetValues(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides) - { - anon::ValueSetWriter writer{ valueSet, overrides }; - writer.WriteValues(emitter, WriteYamlValue); - } - - void ConfigurationSetSerializer::WriteYamlStringArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values) - { - emitter << BeginSeq; - - for (const auto& value : values) - { - emitter << ConvertToUTF8(value); - } - - emitter << EndSeq; - } - - void ConfigurationSetSerializer::WriteYamlValue(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value) - { - if (value == nullptr) - { - emitter << anon::s_nullValue; - } - else - { - const auto& currentValueSet = value.try_as(); - if (currentValueSet) - { - if (currentValueSet.HasKey(L"treatAsArray")) - { - WriteYamlValueSetAsArray(emitter, currentValueSet); - } - else - { - WriteYamlValueSet(emitter, currentValueSet); - } - } - else - { - IPropertyValue property = value.as(); - auto type = property.Type(); - - if (type == PropertyType::Boolean) - { - emitter << property.GetBoolean(); - } - else if (type == PropertyType::String) - { - emitter << ScalarStyle::DoubleQuoted << ConvertToUTF8(property.GetString()); - } - else if (type == PropertyType::Int64) - { - emitter << property.GetInt64(); - } - else - { - THROW_HR(E_NOTIMPL); - } - } - } - } - - void ConfigurationSetSerializer::WriteYamlValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const winrt::Windows::Foundation::IInspectable& value) - { - if (value != nullptr) - { - emitter << Key << GetConfigurationFieldName(key) << Value; - WriteYamlValue(emitter, value); - } - } - - void ConfigurationSetSerializer::WriteYamlStringValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, hstring value) - { - if (!value.empty()) - { - emitter << Key << GetConfigurationFieldName(key) << Value << ConvertToUTF8(value); - } - } - - void ConfigurationSetSerializer::WriteYamlValueSetAsArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSetArray) - { - std::vector> arrayValues; - for (const auto& arrayValue : valueSetArray) - { - if (arrayValue.Key() != L"treatAsArray") - { - arrayValues.emplace_back(std::make_pair(std::stoi(arrayValue.Key().c_str()), arrayValue.Value())); - } - } - - std::sort( - arrayValues.begin(), - arrayValues.end(), - [](const std::pair& a, const std::pair& b) - { - return a.first < b.first; - }); - - emitter << BeginSeq; - - for (const auto& arrayValue : arrayValues) - { - WriteYamlValue(emitter, arrayValue.second); - } - - emitter << EndSeq; - } - - std::wstring_view ConfigurationSetSerializer::GetSchemaVersionCommentPrefix() - { - return L"# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/"sv; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +#include +#include +#include + +#include "ConfigurationSetSerializer.h" +#include "ConfigurationSetSerializer_0_2.h" +#include "ConfigurationSetSerializer_0_3.h" +#include "ConfigurationSetUtilities.h" + +using namespace AppInstaller::Utility; +using namespace AppInstaller::YAML; +using namespace winrt::Windows::Foundation; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace anon + { + static constexpr std::string_view s_nullValue = "null"; + + struct ValueSetWriter + { + ValueSetWriter(const Windows::Foundation::Collections::ValueSet& valueSet, const std::vector>& overrides) : + m_valueSet(valueSet), m_overrides(overrides) + { + // Create a sorted list of the field names to exclude + for (const auto & override : m_overrides) + { + m_exclusionStrings.push_back(GetConfigurationFieldNameHString(override.first)); + } + std::sort(m_exclusionStrings.begin(), m_exclusionStrings.end()); + } + + bool IsResultEmpty() + { + size_t nullOverrides = 0; + + for (const auto& override : m_overrides) + { + // A non-null override will always be output + if (override.second) + { + return false; + } + else + { + ++nullOverrides; + } + } + + if (m_valueSet) + { + // If there are more values than null overrides, something will be output + if (static_cast(m_valueSet.Size()) > nullOverrides) + { + return false; + } + + // Check for a value that we would output + for (const auto& [key, value] : m_valueSet) + { + if (value != nullptr && + !std::binary_search(m_exclusionStrings.begin(), m_exclusionStrings.end(), key)) + { + return false; + } + } + } + + return true; + } + + void Write(AppInstaller::YAML::Emitter& emitter, void(* WriteYamlValue)(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value)) + { + emitter << BeginMap; + + WriteValues(emitter, WriteYamlValue); + + emitter << EndMap; + } + + void WriteValues(AppInstaller::YAML::Emitter& emitter, void(*WriteYamlValue)(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value)) + { + if (m_valueSet) + { + for (const auto& [key, value] : m_valueSet) + { + if (value != nullptr && + !std::binary_search(m_exclusionStrings.begin(), m_exclusionStrings.end(), key)) + { + std::string keyName = winrt::to_string(key); + emitter << Key << keyName << Value; + WriteYamlValue(emitter, value); + } + } + } + + for (const auto & override : m_overrides) + { + if (override.second != nullptr) + { + std::string_view keyName = GetConfigurationFieldName(override.first); + emitter << Key << keyName << Value; + WriteYamlValue(emitter, override.second); + } + } + } + + private: + const Windows::Foundation::Collections::ValueSet& m_valueSet; + const std::vector>& m_overrides; + std::vector m_exclusionStrings; + }; + } + + std::unique_ptr ConfigurationSetSerializer::CreateSerializer(hstring version, bool strictVersionMatching) + { + // Create the parser based on the version selected + SemanticVersion schemaVersion(std::move(winrt::to_string(version))); + + // TODO: Consider having the version/uri/type information all together in the future + if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 1) + { + // Remove this once the 0.1 serializer is implemented. + THROW_HR_IF(E_NOTIMPL, strictVersionMatching); + + return std::make_unique(); + + } + else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 2) + { + return std::make_unique(); + } + else if (schemaVersion.PartAt(0).Integer == 0 && schemaVersion.PartAt(1).Integer == 3) + { + return std::make_unique(); + } + else + { + AICLI_LOG(Config, Error, << "Unknown configuration version: " << schemaVersion.ToString()); + THROW_HR(E_UNEXPECTED); + } + } + + std::string ConfigurationSetSerializer::SerializeValueSet(const Windows::Foundation::Collections::ValueSet& valueSet) + { + Emitter emitter; + WriteYamlValueSet(emitter, valueSet); + return emitter.str(); + } + + std::string ConfigurationSetSerializer::SerializeStringArray(const Windows::Foundation::Collections::IVector& stringArray) + { + Emitter emitter; + WriteYamlStringArray(emitter, stringArray); + return emitter.str(); + } + + void ConfigurationSetSerializer::WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides) + { + anon::ValueSetWriter writer{ valueSet, overrides }; + + if (!writer.IsResultEmpty()) + { + emitter << Key << GetConfigurationFieldName(key); + writer.Write(emitter, WriteYamlValue); + } + } + + void ConfigurationSetSerializer::WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides) + { + anon::ValueSetWriter writer{ valueSet, overrides }; + writer.Write(emitter, WriteYamlValue); + } + + void ConfigurationSetSerializer::WriteYamlValueSetValues(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides) + { + anon::ValueSetWriter writer{ valueSet, overrides }; + writer.WriteValues(emitter, WriteYamlValue); + } + + void ConfigurationSetSerializer::WriteYamlStringArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values) + { + emitter << BeginSeq; + + for (const auto& value : values) + { + emitter << ConvertToUTF8(value); + } + + emitter << EndSeq; + } + + void ConfigurationSetSerializer::WriteYamlValue(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value) + { + if (value == nullptr) + { + emitter << anon::s_nullValue; + } + else + { + const auto& currentValueSet = value.try_as(); + if (currentValueSet) + { + if (currentValueSet.HasKey(L"treatAsArray")) + { + WriteYamlValueSetAsArray(emitter, currentValueSet); + } + else + { + WriteYamlValueSet(emitter, currentValueSet); + } + } + else + { + IPropertyValue property = value.as(); + auto type = property.Type(); + + if (type == PropertyType::Boolean) + { + emitter << property.GetBoolean(); + } + else if (type == PropertyType::String) + { + emitter << ScalarStyle::DoubleQuoted << ConvertToUTF8(property.GetString()); + } + else if (type == PropertyType::Int64) + { + emitter << property.GetInt64(); + } + else + { + THROW_HR(E_NOTIMPL); + } + } + } + } + + void ConfigurationSetSerializer::WriteYamlValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const winrt::Windows::Foundation::IInspectable& value) + { + if (value != nullptr) + { + emitter << Key << GetConfigurationFieldName(key) << Value; + WriteYamlValue(emitter, value); + } + } + + void ConfigurationSetSerializer::WriteYamlStringValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, hstring value) + { + if (!value.empty()) + { + emitter << Key << GetConfigurationFieldName(key) << Value << ConvertToUTF8(value); + } + } + + void ConfigurationSetSerializer::WriteYamlValueSetAsArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSetArray) + { + std::vector> arrayValues; + for (const auto& arrayValue : valueSetArray) + { + if (arrayValue.Key() != L"treatAsArray") + { + arrayValues.emplace_back(std::make_pair(std::stoi(arrayValue.Key().c_str()), arrayValue.Value())); + } + } + + std::sort( + arrayValues.begin(), + arrayValues.end(), + [](const std::pair& a, const std::pair& b) + { + return a.first < b.first; + }); + + emitter << BeginSeq; + + for (const auto& arrayValue : arrayValues) + { + WriteYamlValue(emitter, arrayValue.second); + } + + emitter << EndSeq; + } + + std::wstring_view ConfigurationSetSerializer::GetSchemaVersionCommentPrefix() + { + return L"# yaml-language-server: $schema=https://aka.ms/configuration-dsc-schema/"sv; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h index dacb2c12b9..a531e27a86 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer.h @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetSerializer.h" -#include "ConfigurationSetUtilities.h" -#include "ConfigurationSet.h" - -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationSetSerializer - { - using OverrideMap = std::vector>; - - static std::unique_ptr CreateSerializer(hstring version, bool strictVersionMatching = false); - - virtual ~ConfigurationSetSerializer() noexcept = default; - - ConfigurationSetSerializer(const ConfigurationSetSerializer&) = delete; - ConfigurationSetSerializer& operator=(const ConfigurationSetSerializer&) = delete; - ConfigurationSetSerializer(ConfigurationSetSerializer&&) = default; - ConfigurationSetSerializer& operator=(ConfigurationSetSerializer&&) = default; - - // Serializes a configuration set to the original yaml string. - virtual hstring Serialize(ConfigurationSet*) = 0; - - // Serialize the metadata with the given environment. - virtual std::string SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) = 0; - - // Serializes a value set only. - std::string SerializeValueSet(const Windows::Foundation::Collections::ValueSet& valueSet); - - // Serializes a value set only. - std::string SerializeStringArray(const Windows::Foundation::Collections::IVector& stringArray); - - protected: - ConfigurationSetSerializer() = default; - - static void WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides = {}); - static void WriteYamlValueSetValues(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides = {}); - static void WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides = {}); - static void WriteYamlValueSetAsArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSetArray); - - static void WriteYamlStringArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values); - - static void WriteYamlValue(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value); - static void WriteYamlValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const winrt::Windows::Foundation::IInspectable& value); - static void WriteYamlStringValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, hstring value); - - std::wstring_view GetSchemaVersionCommentPrefix(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetSerializer.h" +#include "ConfigurationSetUtilities.h" +#include "ConfigurationSet.h" + +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationSetSerializer + { + using OverrideMap = std::vector>; + + static std::unique_ptr CreateSerializer(hstring version, bool strictVersionMatching = false); + + virtual ~ConfigurationSetSerializer() noexcept = default; + + ConfigurationSetSerializer(const ConfigurationSetSerializer&) = delete; + ConfigurationSetSerializer& operator=(const ConfigurationSetSerializer&) = delete; + ConfigurationSetSerializer(ConfigurationSetSerializer&&) = default; + ConfigurationSetSerializer& operator=(ConfigurationSetSerializer&&) = default; + + // Serializes a configuration set to the original yaml string. + virtual hstring Serialize(ConfigurationSet*) = 0; + + // Serialize the metadata with the given environment. + virtual std::string SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) = 0; + + // Serializes a value set only. + std::string SerializeValueSet(const Windows::Foundation::Collections::ValueSet& valueSet); + + // Serializes a value set only. + std::string SerializeStringArray(const Windows::Foundation::Collections::IVector& stringArray); + + protected: + ConfigurationSetSerializer() = default; + + static void WriteYamlValueSet(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides = {}); + static void WriteYamlValueSetValues(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides = {}); + static void WriteYamlValueSetIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const Windows::Foundation::Collections::ValueSet& valueSet, const OverrideMap& overrides = {}); + static void WriteYamlValueSetAsArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::ValueSet& valueSetArray); + + static void WriteYamlStringArray(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values); + + static void WriteYamlValue(AppInstaller::YAML::Emitter& emitter, const winrt::Windows::Foundation::IInspectable& value); + static void WriteYamlValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, const winrt::Windows::Foundation::IInspectable& value); + static void WriteYamlStringValueIfNotEmpty(AppInstaller::YAML::Emitter& emitter, ConfigurationField key, hstring value); + + std::wstring_view GetSchemaVersionCommentPrefix(); + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp index 754c28055f..14d01ddb3e 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.cpp @@ -1,148 +1,148 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetSerializer_0_2.h" -#include "ConfigurationSetUtilities.h" - -#include - -using namespace AppInstaller::Utility; -using namespace AppInstaller::YAML; -using namespace winrt::Windows::Foundation; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - hstring ConfigurationSetSerializer_0_2::Serialize(ConfigurationSet* configurationSet) - { - std::vector assertions; - std::vector resources; - - for (auto unit : configurationSet->Units()) - { - ConfigurationUnitIntent unitIntent = unit.Intent(); - - if (unitIntent == ConfigurationUnitIntent::Assert) - { - assertions.emplace_back(unit); - } - else if (unitIntent == ConfigurationUnitIntent::Apply) - { - resources.emplace_back(unit); - } - } - - Emitter emitter; - - emitter << BeginMap; - emitter << Key << GetConfigurationFieldName(ConfigurationField::Properties); - - emitter << BeginMap; - emitter << Key << GetConfigurationFieldName(ConfigurationField::ConfigurationVersion) << Value << ConvertToUTF8(configurationSet->SchemaVersion()); - - if (!assertions.empty()) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::Assertions); - WriteYamlConfigurationUnits(emitter, assertions); - } - - if (!resources.empty()) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::Resources); - WriteYamlConfigurationUnits(emitter, resources); - } - - emitter << EndMap; - emitter << EndMap; - - std::wostringstream result; - result << GetSchemaVersionCommentPrefix() << static_cast(configurationSet->SchemaVersion()) << L"\n" << ConvertToUTF16(emitter.str()); - return hstring{ std::move(result).str() }; - } - - std::string ConfigurationSetSerializer_0_2::SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) - { - Emitter emitter; - WriteYamlValueSet(emitter, metadata, GetMetadataWithEnvironmentOverrides(false, environment.Context())); - return emitter.str(); - } - - void ConfigurationSetSerializer_0_2::WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const std::vector& units) - { - emitter << BeginSeq; - - for (const auto& unit : units) - { - // Resource - emitter << BeginMap; - emitter << Key << GetConfigurationFieldName(ConfigurationField::Resource) << Value << AppInstaller::Utility::ConvertToUTF8(GetResourceName(unit)); - - // Id - if (!unit.Identifier().empty()) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::Id) << Value << AppInstaller::Utility::ConvertToUTF8(unit.Identifier()); - } - - // Dependencies - if (unit.Dependencies().Size() > 0) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::DependsOn); - emitter << BeginSeq; - - for (const auto& dependency : unit.Dependencies()) - { - emitter << AppInstaller::Utility::ConvertToUTF8(dependency); - } - - emitter << EndSeq; - } - - // Directives - WriteResourceDirectives(emitter, unit); - - // Settings - const auto& settings = unit.Settings(); - emitter << Key << GetConfigurationFieldName(ConfigurationField::Settings); - WriteYamlValueSet(emitter, settings); - - emitter << EndMap; - } - - emitter << EndSeq; - } - - winrt::hstring ConfigurationSetSerializer_0_2::GetResourceName(const ConfigurationUnit& unit) - { - const auto& metadata = unit.Metadata(); - const auto moduleKey = GetConfigurationFieldNameHString(ConfigurationField::ModuleDirective); - if (metadata.HasKey(moduleKey)) - { - auto object = metadata.Lookup(moduleKey); - auto property = object.try_as(); - if (property && property.Type() == PropertyType::String) - { - return property.GetString() + '/' + unit.Type(); - } - } - - return unit.Type(); - } - - void ConfigurationSetSerializer_0_2::WriteResourceDirectives(AppInstaller::YAML::Emitter& emitter, const ConfigurationUnit& unit) - { - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Directives, unit.Metadata(), GetMetadataWithEnvironmentOverrides(true, unit.Environment().Context())); - } - - ConfigurationSetSerializer::OverrideMap ConfigurationSetSerializer_0_2::GetMetadataWithEnvironmentOverrides(bool includeModuleOverride, SecurityContext securityContext) - { - ConfigurationSetSerializer::OverrideMap result { - { ConfigurationField::SecurityContextMetadata, (securityContext != SecurityContext::Current ? PropertyValue::CreateString(ToWString(securityContext)) : nullptr)} - }; - - if (includeModuleOverride) - { - result.emplace_back(ConfigurationField::ModuleDirective, nullptr); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetSerializer_0_2.h" +#include "ConfigurationSetUtilities.h" + +#include + +using namespace AppInstaller::Utility; +using namespace AppInstaller::YAML; +using namespace winrt::Windows::Foundation; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + hstring ConfigurationSetSerializer_0_2::Serialize(ConfigurationSet* configurationSet) + { + std::vector assertions; + std::vector resources; + + for (auto unit : configurationSet->Units()) + { + ConfigurationUnitIntent unitIntent = unit.Intent(); + + if (unitIntent == ConfigurationUnitIntent::Assert) + { + assertions.emplace_back(unit); + } + else if (unitIntent == ConfigurationUnitIntent::Apply) + { + resources.emplace_back(unit); + } + } + + Emitter emitter; + + emitter << BeginMap; + emitter << Key << GetConfigurationFieldName(ConfigurationField::Properties); + + emitter << BeginMap; + emitter << Key << GetConfigurationFieldName(ConfigurationField::ConfigurationVersion) << Value << ConvertToUTF8(configurationSet->SchemaVersion()); + + if (!assertions.empty()) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Assertions); + WriteYamlConfigurationUnits(emitter, assertions); + } + + if (!resources.empty()) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Resources); + WriteYamlConfigurationUnits(emitter, resources); + } + + emitter << EndMap; + emitter << EndMap; + + std::wostringstream result; + result << GetSchemaVersionCommentPrefix() << static_cast(configurationSet->SchemaVersion()) << L"\n" << ConvertToUTF16(emitter.str()); + return hstring{ std::move(result).str() }; + } + + std::string ConfigurationSetSerializer_0_2::SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) + { + Emitter emitter; + WriteYamlValueSet(emitter, metadata, GetMetadataWithEnvironmentOverrides(false, environment.Context())); + return emitter.str(); + } + + void ConfigurationSetSerializer_0_2::WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const std::vector& units) + { + emitter << BeginSeq; + + for (const auto& unit : units) + { + // Resource + emitter << BeginMap; + emitter << Key << GetConfigurationFieldName(ConfigurationField::Resource) << Value << AppInstaller::Utility::ConvertToUTF8(GetResourceName(unit)); + + // Id + if (!unit.Identifier().empty()) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Id) << Value << AppInstaller::Utility::ConvertToUTF8(unit.Identifier()); + } + + // Dependencies + if (unit.Dependencies().Size() > 0) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::DependsOn); + emitter << BeginSeq; + + for (const auto& dependency : unit.Dependencies()) + { + emitter << AppInstaller::Utility::ConvertToUTF8(dependency); + } + + emitter << EndSeq; + } + + // Directives + WriteResourceDirectives(emitter, unit); + + // Settings + const auto& settings = unit.Settings(); + emitter << Key << GetConfigurationFieldName(ConfigurationField::Settings); + WriteYamlValueSet(emitter, settings); + + emitter << EndMap; + } + + emitter << EndSeq; + } + + winrt::hstring ConfigurationSetSerializer_0_2::GetResourceName(const ConfigurationUnit& unit) + { + const auto& metadata = unit.Metadata(); + const auto moduleKey = GetConfigurationFieldNameHString(ConfigurationField::ModuleDirective); + if (metadata.HasKey(moduleKey)) + { + auto object = metadata.Lookup(moduleKey); + auto property = object.try_as(); + if (property && property.Type() == PropertyType::String) + { + return property.GetString() + '/' + unit.Type(); + } + } + + return unit.Type(); + } + + void ConfigurationSetSerializer_0_2::WriteResourceDirectives(AppInstaller::YAML::Emitter& emitter, const ConfigurationUnit& unit) + { + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Directives, unit.Metadata(), GetMetadataWithEnvironmentOverrides(true, unit.Environment().Context())); + } + + ConfigurationSetSerializer::OverrideMap ConfigurationSetSerializer_0_2::GetMetadataWithEnvironmentOverrides(bool includeModuleOverride, SecurityContext securityContext) + { + ConfigurationSetSerializer::OverrideMap result { + { ConfigurationField::SecurityContextMetadata, (securityContext != SecurityContext::Current ? PropertyValue::CreateString(ToWString(securityContext)) : nullptr)} + }; + + if (includeModuleOverride) + { + result.emplace_back(ConfigurationField::ModuleDirective, nullptr); + } + + return result; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.h b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.h index 0f0346c970..653f6e3146 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_2.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetSerializer.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Serializer for schema version 0.2 - struct ConfigurationSetSerializer_0_2 : public ConfigurationSetSerializer - { - ConfigurationSetSerializer_0_2() {} - - virtual ~ConfigurationSetSerializer_0_2() noexcept = default; - - ConfigurationSetSerializer_0_2(const ConfigurationSetSerializer_0_2&) = delete; - ConfigurationSetSerializer_0_2& operator=(const ConfigurationSetSerializer_0_2&) = delete; - ConfigurationSetSerializer_0_2(ConfigurationSetSerializer_0_2&&) = default; - ConfigurationSetSerializer_0_2& operator=(ConfigurationSetSerializer_0_2&&) = default; - - hstring Serialize(ConfigurationSet* configurationSet) override; - - std::string SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) override; - - protected: - void WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const std::vector& units); - - virtual winrt::hstring GetResourceName(const ConfigurationUnit& unit); - virtual void WriteResourceDirectives(AppInstaller::YAML::Emitter& emitter, const ConfigurationUnit& unit); - static ConfigurationSetSerializer::OverrideMap GetMetadataWithEnvironmentOverrides(bool includeModuleOverride, SecurityContext securityContext); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetSerializer.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Serializer for schema version 0.2 + struct ConfigurationSetSerializer_0_2 : public ConfigurationSetSerializer + { + ConfigurationSetSerializer_0_2() {} + + virtual ~ConfigurationSetSerializer_0_2() noexcept = default; + + ConfigurationSetSerializer_0_2(const ConfigurationSetSerializer_0_2&) = delete; + ConfigurationSetSerializer_0_2& operator=(const ConfigurationSetSerializer_0_2&) = delete; + ConfigurationSetSerializer_0_2(ConfigurationSetSerializer_0_2&&) = default; + ConfigurationSetSerializer_0_2& operator=(ConfigurationSetSerializer_0_2&&) = default; + + hstring Serialize(ConfigurationSet* configurationSet) override; + + std::string SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) override; + + protected: + void WriteYamlConfigurationUnits(AppInstaller::YAML::Emitter& emitter, const std::vector& units); + + virtual winrt::hstring GetResourceName(const ConfigurationUnit& unit); + virtual void WriteResourceDirectives(AppInstaller::YAML::Emitter& emitter, const ConfigurationUnit& unit); + static ConfigurationSetSerializer::OverrideMap GetMetadataWithEnvironmentOverrides(bool includeModuleOverride, SecurityContext securityContext); + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.cpp index 18ce84f131..6052a19e1b 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.cpp @@ -1,307 +1,307 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetSerializer_0_3.h" -#include "ArgumentValidation.h" -#include "ConfigurationSetParser_0_3.h" -#include "ConfigurationSetUtilities.h" -#include "ConfigurationEnvironment.h" -#include -#include - -using namespace AppInstaller::Utility; -using namespace AppInstaller::YAML; -using namespace winrt::Windows::Foundation; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - Windows::Foundation::Collections::ValueSet GetWingetProcessorMetadataValueSet(Windows::Foundation::Collections::ValueSet& metadata) - { - Windows::Foundation::Collections::ValueSet result = nullptr; - hstring processorMetadataKey = GetConfigurationFieldNameHString(ConfigurationField::ProcessorMetadata); - - if (metadata) - { - Windows::Foundation::IInspectable processorMetadataObject = metadata.TryLookup(processorMetadataKey); - if (processorMetadataObject) - { - result = processorMetadataObject.try_as(); - THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, !result); - } - } - else - { - metadata = Collections::ValueSet{}; - } - - if (!result) - { - result = Collections::ValueSet{}; - metadata.Insert(processorMetadataKey, result); - } - - return result; - } - - Windows::Foundation::Collections::ValueSet CreateValueSetFromStringMap(const Windows::Foundation::Collections::IMap& map) - { - Windows::Foundation::Collections::ValueSet result; - if (map) - { - for (const auto& item : map) - { - result.Insert(item.Key(), PropertyValue::CreateString(item.Value())); - } - } - return result; - } - - void AddEnvironmentToMetadata( - Windows::Foundation::Collections::ValueSet& metadata, - SecurityContext context, - hstring processor, - Windows::Foundation::Collections::IMap properties, - SecurityContext defaultContext = SecurityContext::Current, - hstring defaultProcessor = {}, - Windows::Foundation::Collections::IMap defaultProperties = nullptr) - { - if (context != defaultContext) - { - if (!metadata) - { - metadata = Collections::ValueSet{}; - } - - metadata.Insert(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata), PropertyValue::CreateString(ToWString(context))); - } - - Windows::Foundation::Collections::ValueSet processorValueSet{ nullptr }; - - if (processor != defaultProcessor) - { - if (!processorValueSet) - { - processorValueSet = GetWingetProcessorMetadataValueSet(metadata); - } - - processorValueSet.Insert(GetConfigurationFieldNameHString(ConfigurationField::ProcessorIdentifierMetadata), PropertyValue::CreateString(processor)); - } - - if (!ConfigurationEnvironment::AreEqual(properties, defaultProperties)) - { - if (!processorValueSet) - { - processorValueSet = GetWingetProcessorMetadataValueSet(metadata); - } - - processorValueSet.Insert(GetConfigurationFieldNameHString(ConfigurationField::ProcessorPropertiesMetadata), CreateValueSetFromStringMap(properties)); - } - } - - void AddEnvironmentToMetadata( - Windows::Foundation::Collections::ValueSet& metadata, - const com_ptr& environment) - { - if (environment) - { - AddEnvironmentToMetadata(metadata, environment->Context(), environment->ProcessorIdentifier(), environment->ProcessorProperties()); - } - } - - void AddEnvironmentToMetadata( - Windows::Foundation::Collections::ValueSet& metadata, - const Configuration::ConfigurationEnvironment& environment) - { - AddEnvironmentToMetadata(metadata, - environment.Context(), environment.ProcessorIdentifier(), environment.ProcessorProperties()); - } - } - - hstring ConfigurationSetSerializer_0_3::Serialize(ConfigurationSet* configurationSet) - { - Emitter emitter; - - emitter << BeginMap; - - emitter << Key << GetConfigurationFieldName(ConfigurationField::Schema) << Value << ConvertToUTF8(configurationSet->SchemaUri().ToString()); - - // Prepare an override if necessary - Collections::ValueSet wingetMetadataOverride = nullptr; - AddEnvironmentToMetadata(wingetMetadataOverride, configurationSet->EnvironmentInternal()); - - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, configurationSet->Metadata(), - { - { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride }, - { ConfigurationField::SecurityContextMetadata, nullptr }, - }); - - WriteYamlParameters(emitter, configurationSet->Parameters()); - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Variables, configurationSet->Variables()); - WriteYamlConfigurationUnits(emitter, configurationSet->Units()); - - emitter << EndMap; - - std::wostringstream result; - result << GetSchemaVersionCommentPrefix() << static_cast(configurationSet->SchemaVersion()) << L"\n" << ConvertToUTF16(emitter.str()); - return hstring{ std::move(result).str() }; - } - - std::string ConfigurationSetSerializer_0_3::SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) - { - Emitter emitter; - - Collections::ValueSet wingetMetadataOverride = nullptr; - AddEnvironmentToMetadata(wingetMetadataOverride, environment); - - WriteYamlValueSet(emitter, metadata, - { - { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride }, - { ConfigurationField::SecurityContextMetadata, nullptr }, - }); - - return emitter.str(); - } - - void ConfigurationSetSerializer_0_3::WriteYamlParameters(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values) - { - if (!values || values.Size() == 0) - { - return; - } - - emitter << Key << GetConfigurationFieldName(ConfigurationField::Parameters); - - emitter << BeginMap; - - for (const Configuration::ConfigurationParameter& parameter : values) - { - emitter << Key << ConvertToUTF8(parameter.Name()); - - emitter << BeginMap; - - auto type = parameter.Type(); - - emitter << Key << GetConfigurationFieldName(ConfigurationField::Type) << Value << ToString(type, parameter.IsSecure()); - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, parameter.Metadata()); - WriteYamlStringValueIfNotEmpty(emitter, ConfigurationField::Description, parameter.Description()); - WriteYamlValueIfNotEmpty(emitter, ConfigurationField::DefaultValue, parameter.DefaultValue()); - - auto allowedValues = parameter.AllowedValues(); - if (allowedValues && allowedValues.Size() != 0) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::AllowedValues); - - emitter << BeginSeq; - - for (const auto& value : allowedValues) - { - emitter << Value; - WriteYamlValue(emitter, value); - } - - emitter << EndSeq; - } - - if (IsLengthType(type)) - { - uint32_t minimumLength = parameter.MinimumLength(); - if (minimumLength != 0) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::MinimumLength) << Value << static_cast(minimumLength); - } - - uint32_t maximumLength = parameter.MaximumLength(); - if (maximumLength != std::numeric_limits::max()) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::MaximumLength) << Value << static_cast(maximumLength); - } - } - - if (IsComparableType(type)) - { - WriteYamlValueIfNotEmpty(emitter, ConfigurationField::MinimumValue, parameter.MinimumValue()); - WriteYamlValueIfNotEmpty(emitter, ConfigurationField::MaximumValue, parameter.MaximumValue()); - } - - emitter << EndMap; - } - - emitter << EndMap; - } - - void ConfigurationSetSerializer_0_3::WriteYamlConfigurationUnits( - AppInstaller::YAML::Emitter& emitter, - const Windows::Foundation::Collections::IVector& values) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::Resources); - - emitter << BeginSeq; - - for (const Configuration::ConfigurationUnit& unit : values) - { - emitter << BeginMap; - - hstring identifier = unit.Identifier(); - THROW_HR_IF(WINGET_CONFIG_ERROR_MISSING_FIELD, identifier.empty()); - emitter << Key << GetConfigurationFieldName(ConfigurationField::Name) << Value << ConvertToUTF8(identifier); - - hstring type = unit.Type(); - THROW_HR_IF(WINGET_CONFIG_ERROR_MISSING_FIELD, type.empty()); - emitter << Key << GetConfigurationFieldName(ConfigurationField::Type) << Value << ConvertToUTF8(type); - - // Prepare an override if necessary - Collections::ValueSet wingetMetadataOverride = nullptr; - Configuration::ConfigurationEnvironment unitEnvironment = unit.Environment(); - AddEnvironmentToMetadata(wingetMetadataOverride, unitEnvironment); - - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, unit.Metadata(), - { { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride } }); - - auto dependencies = unit.Dependencies(); - if (dependencies && dependencies.Size() != 0) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::DependsOn); - - emitter << BeginSeq; - - for (const auto& value : dependencies) - { - emitter << ConvertToUTF8(value); - } - - emitter << EndSeq; - } - - // If this unit is a group, write the units directly - if (unit.IsGroup()) - { - auto groupUnits = unit.Units(); - - if (groupUnits.Size() != 0) - { - emitter << Key << GetConfigurationFieldName(ConfigurationField::Properties); - emitter << BeginMap; - - // Write everything but the resources - WriteYamlValueSetValues(emitter, unit.Settings(), - { { ConfigurationField::Resources, nullptr } }); - - // Write the resources from the individual units - WriteYamlConfigurationUnits(emitter, groupUnits); - - emitter << EndMap; - } - } - else - { - WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Properties, unit.Settings()); - } - - emitter << EndMap; - } - - emitter << EndSeq; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetSerializer_0_3.h" +#include "ArgumentValidation.h" +#include "ConfigurationSetParser_0_3.h" +#include "ConfigurationSetUtilities.h" +#include "ConfigurationEnvironment.h" +#include +#include + +using namespace AppInstaller::Utility; +using namespace AppInstaller::YAML; +using namespace winrt::Windows::Foundation; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + Windows::Foundation::Collections::ValueSet GetWingetProcessorMetadataValueSet(Windows::Foundation::Collections::ValueSet& metadata) + { + Windows::Foundation::Collections::ValueSet result = nullptr; + hstring processorMetadataKey = GetConfigurationFieldNameHString(ConfigurationField::ProcessorMetadata); + + if (metadata) + { + Windows::Foundation::IInspectable processorMetadataObject = metadata.TryLookup(processorMetadataKey); + if (processorMetadataObject) + { + result = processorMetadataObject.try_as(); + THROW_HR_IF(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, !result); + } + } + else + { + metadata = Collections::ValueSet{}; + } + + if (!result) + { + result = Collections::ValueSet{}; + metadata.Insert(processorMetadataKey, result); + } + + return result; + } + + Windows::Foundation::Collections::ValueSet CreateValueSetFromStringMap(const Windows::Foundation::Collections::IMap& map) + { + Windows::Foundation::Collections::ValueSet result; + if (map) + { + for (const auto& item : map) + { + result.Insert(item.Key(), PropertyValue::CreateString(item.Value())); + } + } + return result; + } + + void AddEnvironmentToMetadata( + Windows::Foundation::Collections::ValueSet& metadata, + SecurityContext context, + hstring processor, + Windows::Foundation::Collections::IMap properties, + SecurityContext defaultContext = SecurityContext::Current, + hstring defaultProcessor = {}, + Windows::Foundation::Collections::IMap defaultProperties = nullptr) + { + if (context != defaultContext) + { + if (!metadata) + { + metadata = Collections::ValueSet{}; + } + + metadata.Insert(GetConfigurationFieldNameHString(ConfigurationField::SecurityContextMetadata), PropertyValue::CreateString(ToWString(context))); + } + + Windows::Foundation::Collections::ValueSet processorValueSet{ nullptr }; + + if (processor != defaultProcessor) + { + if (!processorValueSet) + { + processorValueSet = GetWingetProcessorMetadataValueSet(metadata); + } + + processorValueSet.Insert(GetConfigurationFieldNameHString(ConfigurationField::ProcessorIdentifierMetadata), PropertyValue::CreateString(processor)); + } + + if (!ConfigurationEnvironment::AreEqual(properties, defaultProperties)) + { + if (!processorValueSet) + { + processorValueSet = GetWingetProcessorMetadataValueSet(metadata); + } + + processorValueSet.Insert(GetConfigurationFieldNameHString(ConfigurationField::ProcessorPropertiesMetadata), CreateValueSetFromStringMap(properties)); + } + } + + void AddEnvironmentToMetadata( + Windows::Foundation::Collections::ValueSet& metadata, + const com_ptr& environment) + { + if (environment) + { + AddEnvironmentToMetadata(metadata, environment->Context(), environment->ProcessorIdentifier(), environment->ProcessorProperties()); + } + } + + void AddEnvironmentToMetadata( + Windows::Foundation::Collections::ValueSet& metadata, + const Configuration::ConfigurationEnvironment& environment) + { + AddEnvironmentToMetadata(metadata, + environment.Context(), environment.ProcessorIdentifier(), environment.ProcessorProperties()); + } + } + + hstring ConfigurationSetSerializer_0_3::Serialize(ConfigurationSet* configurationSet) + { + Emitter emitter; + + emitter << BeginMap; + + emitter << Key << GetConfigurationFieldName(ConfigurationField::Schema) << Value << ConvertToUTF8(configurationSet->SchemaUri().ToString()); + + // Prepare an override if necessary + Collections::ValueSet wingetMetadataOverride = nullptr; + AddEnvironmentToMetadata(wingetMetadataOverride, configurationSet->EnvironmentInternal()); + + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, configurationSet->Metadata(), + { + { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride }, + { ConfigurationField::SecurityContextMetadata, nullptr }, + }); + + WriteYamlParameters(emitter, configurationSet->Parameters()); + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Variables, configurationSet->Variables()); + WriteYamlConfigurationUnits(emitter, configurationSet->Units()); + + emitter << EndMap; + + std::wostringstream result; + result << GetSchemaVersionCommentPrefix() << static_cast(configurationSet->SchemaVersion()) << L"\n" << ConvertToUTF16(emitter.str()); + return hstring{ std::move(result).str() }; + } + + std::string ConfigurationSetSerializer_0_3::SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) + { + Emitter emitter; + + Collections::ValueSet wingetMetadataOverride = nullptr; + AddEnvironmentToMetadata(wingetMetadataOverride, environment); + + WriteYamlValueSet(emitter, metadata, + { + { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride }, + { ConfigurationField::SecurityContextMetadata, nullptr }, + }); + + return emitter.str(); + } + + void ConfigurationSetSerializer_0_3::WriteYamlParameters(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values) + { + if (!values || values.Size() == 0) + { + return; + } + + emitter << Key << GetConfigurationFieldName(ConfigurationField::Parameters); + + emitter << BeginMap; + + for (const Configuration::ConfigurationParameter& parameter : values) + { + emitter << Key << ConvertToUTF8(parameter.Name()); + + emitter << BeginMap; + + auto type = parameter.Type(); + + emitter << Key << GetConfigurationFieldName(ConfigurationField::Type) << Value << ToString(type, parameter.IsSecure()); + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, parameter.Metadata()); + WriteYamlStringValueIfNotEmpty(emitter, ConfigurationField::Description, parameter.Description()); + WriteYamlValueIfNotEmpty(emitter, ConfigurationField::DefaultValue, parameter.DefaultValue()); + + auto allowedValues = parameter.AllowedValues(); + if (allowedValues && allowedValues.Size() != 0) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::AllowedValues); + + emitter << BeginSeq; + + for (const auto& value : allowedValues) + { + emitter << Value; + WriteYamlValue(emitter, value); + } + + emitter << EndSeq; + } + + if (IsLengthType(type)) + { + uint32_t minimumLength = parameter.MinimumLength(); + if (minimumLength != 0) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::MinimumLength) << Value << static_cast(minimumLength); + } + + uint32_t maximumLength = parameter.MaximumLength(); + if (maximumLength != std::numeric_limits::max()) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::MaximumLength) << Value << static_cast(maximumLength); + } + } + + if (IsComparableType(type)) + { + WriteYamlValueIfNotEmpty(emitter, ConfigurationField::MinimumValue, parameter.MinimumValue()); + WriteYamlValueIfNotEmpty(emitter, ConfigurationField::MaximumValue, parameter.MaximumValue()); + } + + emitter << EndMap; + } + + emitter << EndMap; + } + + void ConfigurationSetSerializer_0_3::WriteYamlConfigurationUnits( + AppInstaller::YAML::Emitter& emitter, + const Windows::Foundation::Collections::IVector& values) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Resources); + + emitter << BeginSeq; + + for (const Configuration::ConfigurationUnit& unit : values) + { + emitter << BeginMap; + + hstring identifier = unit.Identifier(); + THROW_HR_IF(WINGET_CONFIG_ERROR_MISSING_FIELD, identifier.empty()); + emitter << Key << GetConfigurationFieldName(ConfigurationField::Name) << Value << ConvertToUTF8(identifier); + + hstring type = unit.Type(); + THROW_HR_IF(WINGET_CONFIG_ERROR_MISSING_FIELD, type.empty()); + emitter << Key << GetConfigurationFieldName(ConfigurationField::Type) << Value << ConvertToUTF8(type); + + // Prepare an override if necessary + Collections::ValueSet wingetMetadataOverride = nullptr; + Configuration::ConfigurationEnvironment unitEnvironment = unit.Environment(); + AddEnvironmentToMetadata(wingetMetadataOverride, unitEnvironment); + + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Metadata, unit.Metadata(), + { { ConfigurationField::WingetMetadataRoot, wingetMetadataOverride } }); + + auto dependencies = unit.Dependencies(); + if (dependencies && dependencies.Size() != 0) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::DependsOn); + + emitter << BeginSeq; + + for (const auto& value : dependencies) + { + emitter << ConvertToUTF8(value); + } + + emitter << EndSeq; + } + + // If this unit is a group, write the units directly + if (unit.IsGroup()) + { + auto groupUnits = unit.Units(); + + if (groupUnits.Size() != 0) + { + emitter << Key << GetConfigurationFieldName(ConfigurationField::Properties); + emitter << BeginMap; + + // Write everything but the resources + WriteYamlValueSetValues(emitter, unit.Settings(), + { { ConfigurationField::Resources, nullptr } }); + + // Write the resources from the individual units + WriteYamlConfigurationUnits(emitter, groupUnits); + + emitter << EndMap; + } + } + else + { + WriteYamlValueSetIfNotEmpty(emitter, ConfigurationField::Properties, unit.Settings()); + } + + emitter << EndMap; + } + + emitter << EndSeq; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.h b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.h index 2f407ffc97..815b1a662a 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetSerializer_0_3.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetSerializer.h" -#include "ConfigurationEnvironment.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Serializer for schema version 0.3 - struct ConfigurationSetSerializer_0_3 : public ConfigurationSetSerializer - { - ConfigurationSetSerializer_0_3() {} - - virtual ~ConfigurationSetSerializer_0_3() noexcept = default; - - ConfigurationSetSerializer_0_3(const ConfigurationSetSerializer_0_3&) = delete; - ConfigurationSetSerializer_0_3& operator=(const ConfigurationSetSerializer_0_3&) = delete; - ConfigurationSetSerializer_0_3(ConfigurationSetSerializer_0_3&&) = default; - ConfigurationSetSerializer_0_3& operator=(ConfigurationSetSerializer_0_3&&) = default; - - hstring Serialize(ConfigurationSet* configurationSet) override; - - std::string SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) override; - - protected: - void WriteYamlParameters(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values); - void WriteYamlConfigurationUnits( - AppInstaller::YAML::Emitter& emitter, - const Windows::Foundation::Collections::IVector& values); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetSerializer.h" +#include "ConfigurationEnvironment.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Serializer for schema version 0.3 + struct ConfigurationSetSerializer_0_3 : public ConfigurationSetSerializer + { + ConfigurationSetSerializer_0_3() {} + + virtual ~ConfigurationSetSerializer_0_3() noexcept = default; + + ConfigurationSetSerializer_0_3(const ConfigurationSetSerializer_0_3&) = delete; + ConfigurationSetSerializer_0_3& operator=(const ConfigurationSetSerializer_0_3&) = delete; + ConfigurationSetSerializer_0_3(ConfigurationSetSerializer_0_3&&) = default; + ConfigurationSetSerializer_0_3& operator=(ConfigurationSetSerializer_0_3&&) = default; + + hstring Serialize(ConfigurationSet* configurationSet) override; + + std::string SerializeMetadataWithEnvironment(const Windows::Foundation::Collections::ValueSet& metadata, const Configuration::ConfigurationEnvironment& environment) override; + + protected: + void WriteYamlParameters(AppInstaller::YAML::Emitter& emitter, const Windows::Foundation::Collections::IVector& values); + void WriteYamlConfigurationUnits( + AppInstaller::YAML::Emitter& emitter, + const Windows::Foundation::Collections::IVector& values); + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp index 216ad1e329..62249d1cc2 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.cpp @@ -1,138 +1,138 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationSetUtilities.h" -#include - -using namespace std::string_view_literals; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - std::string_view GetConfigurationFieldName(ConfigurationField fieldName) - { - switch (fieldName) - { - case ConfigurationField::ConfigurationVersion: return "configurationVersion"sv; - case ConfigurationField::Properties: return "properties"sv; - case ConfigurationField::Resource: return "resource"sv; - case ConfigurationField::Directives: return "directives"sv; - case ConfigurationField::Settings: return "settings"sv; - case ConfigurationField::Assertions: return "assertions"sv; - case ConfigurationField::Id: return "id"sv; - case ConfigurationField::DependsOn: return "dependsOn"sv; - - case ConfigurationField::Resources: return "resources"sv; - case ConfigurationField::ModuleDirective: return "module"sv; - case ConfigurationField::SecurityContextMetadata: return "securityContext"sv; - - case ConfigurationField::Schema: return "$schema"sv; - case ConfigurationField::Metadata: return "metadata"sv; - case ConfigurationField::Parameters: return "parameters"sv; - case ConfigurationField::Variables: return "variables"sv; - case ConfigurationField::Type: return "type"sv; - case ConfigurationField::Description: return "description"sv; - case ConfigurationField::Name: return "name"sv; - case ConfigurationField::IsGroupMetadata: return "isGroup"sv; - case ConfigurationField::DefaultValue: return "defaultValue"sv; - case ConfigurationField::AllowedValues: return "allowedValues"sv; - case ConfigurationField::MinimumLength: return "minLength"sv; - case ConfigurationField::MaximumLength: return "maxLength"sv; - case ConfigurationField::MinimumValue: return "minValue"sv; - case ConfigurationField::MaximumValue: return "maxValue"sv; - case ConfigurationField::WingetMetadataRoot: return "winget"sv; - case ConfigurationField::ProcessorMetadata: return "processor"sv; - case ConfigurationField::ProcessorIdentifierMetadata: return "identifier"sv; - case ConfigurationField::ProcessorPropertiesMetadata: return "properties"sv; - } - - THROW_HR(E_UNEXPECTED); - } - - hstring GetConfigurationFieldNameHString(ConfigurationField fieldName) - { - return hstring{ AppInstaller::Utility::ConvertToUTF16(GetConfigurationFieldName(fieldName)) }; - } - - bool TryParseSecurityContext(const hstring& value, SecurityContext& result) - { - std::wstring securityContextLower = AppInstaller::Utility::ToLower(value); - - if (securityContextLower == L"elevated") - { - result = SecurityContext::Elevated; - } - else if (securityContextLower == L"restricted") - { - result = SecurityContext::Restricted; - } - else if (securityContextLower == L"current") - { - result = SecurityContext::Current; - } - else - { - return false; - } - - return true; - } - - SecurityContext ParseSecurityContext(const hstring& value) - { - SecurityContext result = SecurityContext::Current; - THROW_HR_IF(E_INVALIDARG, !TryParseSecurityContext(value, result)); - return result; - } - - std::string_view ToString(SecurityContext value) - { - switch (value) - { - case SecurityContext::Current: return "current"; - case SecurityContext::Restricted: return "restricted"; - case SecurityContext::Elevated: return "elevated"; - } - - THROW_HR(E_INVALIDARG); - } - - std::wstring_view ToWString(SecurityContext value) - { - switch (value) - { - case SecurityContext::Current: return L"current"; - case SecurityContext::Restricted: return L"restricted"; - case SecurityContext::Elevated: return L"elevated"; - } - - THROW_HR(E_INVALIDARG); - } - - Windows::Foundation::Collections::ValueSet TryLookupValueSet(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field) - { - Windows::Foundation::IInspectable value = valueSet.TryLookup(GetConfigurationFieldNameHString(field)); - - if (value) - { - return value.try_as(); - } - - return nullptr; - } - - Windows::Foundation::IPropertyValue TryLookupProperty(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field, Windows::Foundation::PropertyType type) - { - Windows::Foundation::IInspectable value = valueSet.TryLookup(GetConfigurationFieldNameHString(field)); - - if (value) - { - Windows::Foundation::IPropertyValue property = value.try_as(); - if (property && (type == Windows::Foundation::PropertyType::Empty || property.Type() == type)) - { - return property; - } - } - - return nullptr; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationSetUtilities.h" +#include + +using namespace std::string_view_literals; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + std::string_view GetConfigurationFieldName(ConfigurationField fieldName) + { + switch (fieldName) + { + case ConfigurationField::ConfigurationVersion: return "configurationVersion"sv; + case ConfigurationField::Properties: return "properties"sv; + case ConfigurationField::Resource: return "resource"sv; + case ConfigurationField::Directives: return "directives"sv; + case ConfigurationField::Settings: return "settings"sv; + case ConfigurationField::Assertions: return "assertions"sv; + case ConfigurationField::Id: return "id"sv; + case ConfigurationField::DependsOn: return "dependsOn"sv; + + case ConfigurationField::Resources: return "resources"sv; + case ConfigurationField::ModuleDirective: return "module"sv; + case ConfigurationField::SecurityContextMetadata: return "securityContext"sv; + + case ConfigurationField::Schema: return "$schema"sv; + case ConfigurationField::Metadata: return "metadata"sv; + case ConfigurationField::Parameters: return "parameters"sv; + case ConfigurationField::Variables: return "variables"sv; + case ConfigurationField::Type: return "type"sv; + case ConfigurationField::Description: return "description"sv; + case ConfigurationField::Name: return "name"sv; + case ConfigurationField::IsGroupMetadata: return "isGroup"sv; + case ConfigurationField::DefaultValue: return "defaultValue"sv; + case ConfigurationField::AllowedValues: return "allowedValues"sv; + case ConfigurationField::MinimumLength: return "minLength"sv; + case ConfigurationField::MaximumLength: return "maxLength"sv; + case ConfigurationField::MinimumValue: return "minValue"sv; + case ConfigurationField::MaximumValue: return "maxValue"sv; + case ConfigurationField::WingetMetadataRoot: return "winget"sv; + case ConfigurationField::ProcessorMetadata: return "processor"sv; + case ConfigurationField::ProcessorIdentifierMetadata: return "identifier"sv; + case ConfigurationField::ProcessorPropertiesMetadata: return "properties"sv; + } + + THROW_HR(E_UNEXPECTED); + } + + hstring GetConfigurationFieldNameHString(ConfigurationField fieldName) + { + return hstring{ AppInstaller::Utility::ConvertToUTF16(GetConfigurationFieldName(fieldName)) }; + } + + bool TryParseSecurityContext(const hstring& value, SecurityContext& result) + { + std::wstring securityContextLower = AppInstaller::Utility::ToLower(value); + + if (securityContextLower == L"elevated") + { + result = SecurityContext::Elevated; + } + else if (securityContextLower == L"restricted") + { + result = SecurityContext::Restricted; + } + else if (securityContextLower == L"current") + { + result = SecurityContext::Current; + } + else + { + return false; + } + + return true; + } + + SecurityContext ParseSecurityContext(const hstring& value) + { + SecurityContext result = SecurityContext::Current; + THROW_HR_IF(E_INVALIDARG, !TryParseSecurityContext(value, result)); + return result; + } + + std::string_view ToString(SecurityContext value) + { + switch (value) + { + case SecurityContext::Current: return "current"; + case SecurityContext::Restricted: return "restricted"; + case SecurityContext::Elevated: return "elevated"; + } + + THROW_HR(E_INVALIDARG); + } + + std::wstring_view ToWString(SecurityContext value) + { + switch (value) + { + case SecurityContext::Current: return L"current"; + case SecurityContext::Restricted: return L"restricted"; + case SecurityContext::Elevated: return L"elevated"; + } + + THROW_HR(E_INVALIDARG); + } + + Windows::Foundation::Collections::ValueSet TryLookupValueSet(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field) + { + Windows::Foundation::IInspectable value = valueSet.TryLookup(GetConfigurationFieldNameHString(field)); + + if (value) + { + return value.try_as(); + } + + return nullptr; + } + + Windows::Foundation::IPropertyValue TryLookupProperty(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field, Windows::Foundation::PropertyType type) + { + Windows::Foundation::IInspectable value = valueSet.TryLookup(GetConfigurationFieldNameHString(field)); + + if (value) + { + Windows::Foundation::IPropertyValue property = value.try_as(); + if (property && (type == Windows::Foundation::PropertyType::Empty || property.Type() == type)) + { + return property; + } + } + + return nullptr; + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h index a9c7d43014..d561ffc1ad 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h +++ b/src/Microsoft.Management.Configuration/ConfigurationSetUtilities.h @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // The various configuration fields that are used in parsing/serialization. - enum class ConfigurationField - { - // v0.1 and v0.2 - ConfigurationVersion, - Properties, - Resource, - Directives, - Settings, - Assertions, - Id, - DependsOn, - - // Universal - Resources, - ModuleDirective, - SecurityContextMetadata, - - // v0.3 - Schema, - Metadata, - Parameters, - Variables, - Type, - Description, - Name, - IsGroupMetadata, - DefaultValue, - AllowedValues, - MinimumLength, - MaximumLength, - MinimumValue, - MaximumValue, - WingetMetadataRoot, - ProcessorMetadata, - ProcessorIdentifierMetadata, - ProcessorPropertiesMetadata, - }; - - // Gets the name value of the configuration field. - std::string_view GetConfigurationFieldName(ConfigurationField fieldName); - - winrt::hstring GetConfigurationFieldNameHString(ConfigurationField fieldName); - - // Attempts to parse a security context from a string. - // Returns true if successful; false otherwise. - bool TryParseSecurityContext(const hstring& value, SecurityContext& result); - - // Parses a security context from a string. - SecurityContext ParseSecurityContext(const hstring& value); - - // Gets the string representation of a security context. - std::string_view ToString(SecurityContext value); - - // Gets the string representation of a security context. - std::wstring_view ToWString(SecurityContext value); - - // Tries to get the field value from the given value set; only if it is a value set. - Windows::Foundation::Collections::ValueSet TryLookupValueSet(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field); - - // Tries to get the field value from the given value set; only if it is a value set. - Windows::Foundation::IPropertyValue TryLookupProperty(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field, Windows::Foundation::PropertyType type = Windows::Foundation::PropertyType::Empty); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // The various configuration fields that are used in parsing/serialization. + enum class ConfigurationField + { + // v0.1 and v0.2 + ConfigurationVersion, + Properties, + Resource, + Directives, + Settings, + Assertions, + Id, + DependsOn, + + // Universal + Resources, + ModuleDirective, + SecurityContextMetadata, + + // v0.3 + Schema, + Metadata, + Parameters, + Variables, + Type, + Description, + Name, + IsGroupMetadata, + DefaultValue, + AllowedValues, + MinimumLength, + MaximumLength, + MinimumValue, + MaximumValue, + WingetMetadataRoot, + ProcessorMetadata, + ProcessorIdentifierMetadata, + ProcessorPropertiesMetadata, + }; + + // Gets the name value of the configuration field. + std::string_view GetConfigurationFieldName(ConfigurationField fieldName); + + winrt::hstring GetConfigurationFieldNameHString(ConfigurationField fieldName); + + // Attempts to parse a security context from a string. + // Returns true if successful; false otherwise. + bool TryParseSecurityContext(const hstring& value, SecurityContext& result); + + // Parses a security context from a string. + SecurityContext ParseSecurityContext(const hstring& value); + + // Gets the string representation of a security context. + std::string_view ToString(SecurityContext value); + + // Gets the string representation of a security context. + std::wstring_view ToWString(SecurityContext value); + + // Tries to get the field value from the given value set; only if it is a value set. + Windows::Foundation::Collections::ValueSet TryLookupValueSet(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field); + + // Tries to get the field value from the given value set; only if it is a value set. + Windows::Foundation::IPropertyValue TryLookupProperty(const Windows::Foundation::Collections::ValueSet& valueSet, ConfigurationField field, Windows::Foundation::PropertyType type = Windows::Foundation::PropertyType::Empty); +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.cpp b/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.cpp index efca7f6b20..b0e85c8bd4 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.cpp @@ -6,8 +6,8 @@ #include "ConfigurationUnit.h" #include "ConfigurationSet.h" #include "ConfigurationProcessor.h" -#include "ConfigurationParameter.h" -#include "FindUnitProcessorsOptions.h" +#include "ConfigurationParameter.h" +#include "FindUnitProcessorsOptions.h" #include "ShutdownSynchronization.h" #include #include @@ -63,22 +63,22 @@ namespace winrt::Microsoft::Management::Configuration::implementation { m_state = static_cast(state); return S_OK; - } - - HRESULT STDMETHODCALLTYPE ConfigurationStaticFunctions::BlockNewWorkForShutdown() - { - ShutdownSynchronization::Instance().BlockNewWork(); + } + + HRESULT STDMETHODCALLTYPE ConfigurationStaticFunctions::BlockNewWorkForShutdown() + { + ShutdownSynchronization::Instance().BlockNewWork(); return S_OK; - } - - HRESULT STDMETHODCALLTYPE ConfigurationStaticFunctions::BeginShutdown() - { - ShutdownSynchronization::Instance().CancelAllWork(); + } + + HRESULT STDMETHODCALLTYPE ConfigurationStaticFunctions::BeginShutdown() + { + ShutdownSynchronization::Instance().CancelAllWork(); return S_OK; - } - - HRESULT STDMETHODCALLTYPE ConfigurationStaticFunctions::WaitForShutdown() - { + } + + HRESULT STDMETHODCALLTYPE ConfigurationStaticFunctions::WaitForShutdown() + { ShutdownSynchronization::Instance().Wait(); return S_OK; } diff --git a/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.h b/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.h index 9a85818a11..1bf782a575 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.h +++ b/src/Microsoft.Management.Configuration/ConfigurationStaticFunctions.h @@ -16,13 +16,13 @@ namespace winrt::Microsoft::Management::Configuration::implementation Configuration::ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory const& factory); bool IsConfigurationAvailable() { return true; } Windows::Foundation::IAsyncActionWithProgress EnsureConfigurationAvailableAsync(); - Configuration::ConfigurationParameter CreateConfigurationParameter(); + Configuration::ConfigurationParameter CreateConfigurationParameter(); Configuration::FindUnitProcessorsOptions CreateFindUnitProcessorsOptions(); // IConfigurationStaticsInternals - HRESULT STDMETHODCALLTYPE SetExperimentalState(UINT32 state); - HRESULT STDMETHODCALLTYPE BlockNewWorkForShutdown(); - HRESULT STDMETHODCALLTYPE BeginShutdown(); + HRESULT STDMETHODCALLTYPE SetExperimentalState(UINT32 state); + HRESULT STDMETHODCALLTYPE BlockNewWorkForShutdown(); + HRESULT STDMETHODCALLTYPE BeginShutdown(); HRESULT STDMETHODCALLTYPE WaitForShutdown(); private: diff --git a/src/Microsoft.Management.Configuration/ConfigurationStatus.cpp b/src/Microsoft.Management.Configuration/ConfigurationStatus.cpp index 0ac3130ca6..5335da3b0e 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationStatus.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationStatus.cpp @@ -1,410 +1,410 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationStatus.h" -#include "ConfigurationChangeData.h" -#include "ConfigurationProcessor.h" -#include "ConfigurationSet.h" -#include "ConfigurationUnitResultInformation.h" -#include -#include - - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace details - { - // Implements the consuming side of the status signaling. - struct ChangeListener - { - struct SetStatusItem - { - ConfigurationDatabase::StatusItem Status; - com_ptr Set; - }; - - ChangeListener(ConfigurationStatus& status) : m_status(status) - { - ConfigurationDatabase::StatusBaseline baseline = m_status.Database().GetStatusBaseline(); - m_changeIdentifier = baseline.ChangeIdentifier; - - for (const auto& item : baseline.SetStatus) - { - m_lastSetStatus.emplace(item.SetInstanceIdentifier, SetStatusItem{ item }); - } - - std::wstring objectName = L"WinGetConfigListener_" + AppInstaller::Utility::CreateNewGuidNameWString(); - m_listenerEventName = AppInstaller::Utility::ConvertToUTF8(objectName); - m_listenerEvent.create(wil::EventOptions::None, objectName.c_str()); - - m_status.Database().AddListener(m_listenerEventName); - - m_threadPoolWait.reset(CreateThreadpoolWait(StaticWaitCallback, this, nullptr)); - THROW_LAST_ERROR_IF(!m_threadPoolWait); - - SetThreadpoolWait(m_threadPoolWait.get(), m_listenerEvent.get(), NULL); - } - - ~ChangeListener() - { - try - { - m_status.Database().RemoveListener(m_listenerEventName); - } - CATCH_LOG(); - } - - private: - static void NTAPI StaticWaitCallback(PTP_CALLBACK_INSTANCE, void* context, TP_WAIT*, TP_WAIT_RESULT) - { - reinterpret_cast(context)->WaitCallback(); - } - - void WaitCallback() try - { - std::vector changes = m_status.Database().GetStatusSince(m_changeIdentifier); - - // Convert status items to relevant change information - for (const auto& change : changes) - { - if (change.UnitInstanceIdentifier) - { - if (m_status.HasSetChangeRegistration(change.SetInstanceIdentifier)) - { - // A unit status change - ConfigurationUnitState state = AppInstaller::ToEnum(change.State); - - decltype(make_self>()) resultInformation; - - if (change.ResultCode) - { - resultInformation = make_self>(); - resultInformation->ResultCode(change.ResultCode.value()); - resultInformation->Description(hstring{ AppInstaller::Utility::ConvertToUTF16(change.ResultDescription) }); - resultInformation->Details(hstring{ AppInstaller::Utility::ConvertToUTF16(change.ResultDetails) }); - resultInformation->ResultSource(change.ResultSource); - } - - auto changeData = make_self(); - changeData->Initialize(state, *resultInformation, nullptr); - - m_status.SetChangeDetected(change.SetInstanceIdentifier, changeData, change.UnitInstanceIdentifier); - } - } - else - { - // A set status change - ConfigurationSetState state = AppInstaller::ToEnum(change.State); - ConfigurationChangeEventType changeType = ConfigurationChangeEventType::Unknown; - - SetStatusItem* setStatusItem = nullptr; - auto itr = m_lastSetStatus.find(change.SetInstanceIdentifier); - if (itr != m_lastSetStatus.end()) - { - setStatusItem = &itr->second; - } - - if (!setStatusItem) - { - changeType = ConfigurationChangeEventType::SetAdded; - - std::tie(itr, std::ignore) = m_lastSetStatus.emplace(change.SetInstanceIdentifier, SetStatusItem{ change }); - setStatusItem = &itr->second; - } - else - { - changeType = (change.InQueue ? ConfigurationChangeEventType::SetStateChanged : ConfigurationChangeEventType::SetRemoved); - } - - if (m_status.HasChangeRegistrations()) - { - if (!setStatusItem->Set) - { - setStatusItem->Set = m_status.Database().GetSet(change.SetInstanceIdentifier); - } - - auto changeData = make_self>(); - changeData->Initialize(changeType, change.SetInstanceIdentifier, state); - - m_status.ChangeDetected(*setStatusItem->Set, *changeData); - } - - auto setChangeData = make_self(); - setChangeData->Initialize(state); - - m_status.SetChangeDetected(change.SetInstanceIdentifier, setChangeData, std::nullopt); - } - - m_changeIdentifier = change.ChangeIdentifier; - } - - SetThreadpoolWait(m_threadPoolWait.get(), m_listenerEvent.get(), NULL); - } - CATCH_LOG_MSG("ChangeListener::WaitCallback exception"); - - ConfigurationStatus& m_status; - int64_t m_changeIdentifier; - std::map m_lastSetStatus; - wil::unique_event m_listenerEvent; - std::string m_listenerEventName; - - // Keep last to destroy first - wil::unique_threadpool_wait m_threadPoolWait; - }; - } - - ConfigurationStatus::ConfigurationStatus(private_construction) {} - - ConfigurationStatus::~ConfigurationStatus() = default; - - std::shared_ptr ConfigurationStatus::Instance() - { - static std::shared_ptr s_instance; - - std::shared_ptr result = std::atomic_load(&s_instance); - if (!result) - { - result = std::make_shared(private_construction{}); - std::shared_ptr empty; - - if (!std::atomic_compare_exchange_strong(&s_instance, &empty, result)) - { - result = empty; - } - } - - return result; - } - - ConfigurationSetState ConfigurationStatus::GetSetState(const winrt::guid& instanceIdentifier) - { - m_database.EnsureOpened(false); - return m_database.GetSetState(instanceIdentifier); - } - - clock::time_point ConfigurationStatus::GetSetFirstApply(const winrt::guid& instanceIdentifier) - { - m_database.EnsureOpened(false); - return clock::from_sys(m_database.GetSetFirstApply(instanceIdentifier)); - } - - clock::time_point ConfigurationStatus::GetSetApplyBegun(const winrt::guid& instanceIdentifier) - { - using system_clock = std::chrono::system_clock; - - m_database.EnsureOpened(false); - system_clock::time_point result = m_database.GetSetApplyBegun(instanceIdentifier); - return (result == system_clock::time_point{} ? clock::time_point{} : clock::from_sys(result)); - } - - clock::time_point ConfigurationStatus::GetSetApplyEnded(const winrt::guid& instanceIdentifier) - { - using system_clock = std::chrono::system_clock; - - m_database.EnsureOpened(false); - system_clock::time_point result = m_database.GetSetApplyEnded(instanceIdentifier); - return (result == system_clock::time_point{} ? clock::time_point{} : clock::from_sys(result)); - } - - ConfigurationUnitState ConfigurationStatus::GetUnitState(const winrt::guid& instanceIdentifier) - { - m_database.EnsureOpened(false); - return m_database.GetUnitState(instanceIdentifier); - } - - IConfigurationUnitResultInformation ConfigurationStatus::GetUnitResultInformation(const winrt::guid& instanceIdentifier) - { - m_database.EnsureOpened(false); - return m_database.GetUnitResultInformation(instanceIdentifier); - } - - void ConfigurationStatus::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) - { - m_database.EnsureOpened(); - m_database.UpdateSetState(setInstanceIdentifier, state); - SignalChangeListeners(); - } - - void ConfigurationStatus::UpdateSetState(const guid& setInstanceIdentifier, bool inQueue) - { - m_database.EnsureOpened(); - m_database.UpdateSetInQueue(setInstanceIdentifier, inQueue); - SignalChangeListeners(); - } - - void ConfigurationStatus::UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData) - { - m_database.EnsureOpened(); - m_database.UpdateUnitState(setInstanceIdentifier, changeData); - SignalChangeListeners(); - } - - ConfigurationStatus::SetChangeRegistration::SetChangeRegistration(const winrt::guid& instanceIdentifier, ConfigurationSet* configurationSet) : - m_status(Instance()), m_instanceIdentifier(instanceIdentifier), m_configurationSet(configurationSet) {} - - ConfigurationStatus::SetChangeRegistration::~SetChangeRegistration() - { - m_status->RemoveSetChangeRegistration(m_instanceIdentifier, m_configurationSet); - } - - std::shared_ptr ConfigurationStatus::RegisterForSetChange(ConfigurationSet& set) - { - m_database.EnsureOpened(); - - winrt::guid instanceIdentifier = set.InstanceIdentifier(); - - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - m_setChangeRegistrations.emplace(instanceIdentifier, &set); - EnableChangeListeningIfNeeded(); - } - - return std::make_shared(instanceIdentifier, &set); - } - - void ConfigurationStatus::RemoveSetChangeRegistration(const winrt::guid& instanceIdentifier, ConfigurationSet* configurationSet) noexcept - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - - auto [begin, end] = m_setChangeRegistrations.equal_range(instanceIdentifier); - - for (; begin != end; ++begin) - { - if (begin->second == configurationSet) - { - m_setChangeRegistrations.erase(begin); - break; - } - } - - DisableChangeListeningIfNeeded(); - } - - ConfigurationStatus::ChangeRegistration::ChangeRegistration(const winrt::guid& instanceIdentifier) : - m_status(Instance()), m_instanceIdentifier(instanceIdentifier) {} - - ConfigurationStatus::ChangeRegistration::~ChangeRegistration() - { - m_status->RemoveChangeRegistration(m_instanceIdentifier); - } - - std::shared_ptr ConfigurationStatus::RegisterForChange(ConfigurationProcessor& processor) - { - m_database.EnsureOpened(); - - GUID instanceIdentifier; - std::ignore = CoCreateGuid(&instanceIdentifier); - - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - m_changeRegistrations.emplace_back(instanceIdentifier, &processor); - EnableChangeListeningIfNeeded(); - } - - return std::make_shared(instanceIdentifier); - } - - void ConfigurationStatus::RemoveChangeRegistration(const winrt::guid& instanceIdentifier) noexcept - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - - for (auto itr = m_changeRegistrations.begin(); itr != m_changeRegistrations.end(); ++itr) - { - if (itr->first == instanceIdentifier) - { - m_changeRegistrations.erase(itr); - DisableChangeListeningIfNeeded(); - return; - } - } - } - - void ConfigurationStatus::EnableChangeListeningIfNeeded() - { - if (!m_changeListener) - { - m_changeListener = std::make_unique(*this); - } - } - - void ConfigurationStatus::DisableChangeListeningIfNeeded() - { - if (m_changeListener && m_setChangeRegistrations.empty() && m_changeRegistrations.empty()) - { - m_changeListener.reset(); - } - } - - void ConfigurationStatus::SignalChangeListeners() - { - std::vector changeListeners = m_database.GetChangeListeners(); - - for (const auto& listener : changeListeners) - { - std::wstring objectName = AppInstaller::Utility::ConvertToUTF16(listener.ObjectName); - wil::unique_event listenerEvent; - if (listenerEvent.try_open(objectName.c_str(), EVENT_MODIFY_STATE)) - { - listenerEvent.SetEvent(); - } - else - { - m_database.RemoveListener(listener.ObjectName); - } - } - } - - ConfigurationDatabase& ConfigurationStatus::Database() - { - return m_database; - } - - bool ConfigurationStatus::HasSetChangeRegistration(const guid& setInstanceIdentifier) - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - auto [begin, end] = m_setChangeRegistrations.equal_range(setInstanceIdentifier); - return begin != end; - } - - bool ConfigurationStatus::HasChangeRegistrations() - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - return !m_changeRegistrations.empty(); - } - - void ConfigurationStatus::SetChangeDetected(const winrt::guid& setInstanceIdentifier, com_ptr& data, const std::optional& unitInstanceIdentifier) - { - std::vector setChangeRegistrations; - - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - - auto [begin, end] = m_setChangeRegistrations.equal_range(setInstanceIdentifier); - - for (; begin != end; ++begin) - { - setChangeRegistrations.emplace_back(begin->second); - } - } - - for (ConfigurationSet* set : setChangeRegistrations) - { - set->ConfigurationSetChange(data, unitInstanceIdentifier); - } - } - - void ConfigurationStatus::ChangeDetected(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data) - { - std::vector> changeRegistrations; - - { - std::lock_guard lock{ m_changeRegistrationsMutex }; - changeRegistrations = m_changeRegistrations; - } - - for (const auto& registration : changeRegistrations) - { - registration.second->ConfigurationChange(set, data); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationStatus.h" +#include "ConfigurationChangeData.h" +#include "ConfigurationProcessor.h" +#include "ConfigurationSet.h" +#include "ConfigurationUnitResultInformation.h" +#include +#include + + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace details + { + // Implements the consuming side of the status signaling. + struct ChangeListener + { + struct SetStatusItem + { + ConfigurationDatabase::StatusItem Status; + com_ptr Set; + }; + + ChangeListener(ConfigurationStatus& status) : m_status(status) + { + ConfigurationDatabase::StatusBaseline baseline = m_status.Database().GetStatusBaseline(); + m_changeIdentifier = baseline.ChangeIdentifier; + + for (const auto& item : baseline.SetStatus) + { + m_lastSetStatus.emplace(item.SetInstanceIdentifier, SetStatusItem{ item }); + } + + std::wstring objectName = L"WinGetConfigListener_" + AppInstaller::Utility::CreateNewGuidNameWString(); + m_listenerEventName = AppInstaller::Utility::ConvertToUTF8(objectName); + m_listenerEvent.create(wil::EventOptions::None, objectName.c_str()); + + m_status.Database().AddListener(m_listenerEventName); + + m_threadPoolWait.reset(CreateThreadpoolWait(StaticWaitCallback, this, nullptr)); + THROW_LAST_ERROR_IF(!m_threadPoolWait); + + SetThreadpoolWait(m_threadPoolWait.get(), m_listenerEvent.get(), NULL); + } + + ~ChangeListener() + { + try + { + m_status.Database().RemoveListener(m_listenerEventName); + } + CATCH_LOG(); + } + + private: + static void NTAPI StaticWaitCallback(PTP_CALLBACK_INSTANCE, void* context, TP_WAIT*, TP_WAIT_RESULT) + { + reinterpret_cast(context)->WaitCallback(); + } + + void WaitCallback() try + { + std::vector changes = m_status.Database().GetStatusSince(m_changeIdentifier); + + // Convert status items to relevant change information + for (const auto& change : changes) + { + if (change.UnitInstanceIdentifier) + { + if (m_status.HasSetChangeRegistration(change.SetInstanceIdentifier)) + { + // A unit status change + ConfigurationUnitState state = AppInstaller::ToEnum(change.State); + + decltype(make_self>()) resultInformation; + + if (change.ResultCode) + { + resultInformation = make_self>(); + resultInformation->ResultCode(change.ResultCode.value()); + resultInformation->Description(hstring{ AppInstaller::Utility::ConvertToUTF16(change.ResultDescription) }); + resultInformation->Details(hstring{ AppInstaller::Utility::ConvertToUTF16(change.ResultDetails) }); + resultInformation->ResultSource(change.ResultSource); + } + + auto changeData = make_self(); + changeData->Initialize(state, *resultInformation, nullptr); + + m_status.SetChangeDetected(change.SetInstanceIdentifier, changeData, change.UnitInstanceIdentifier); + } + } + else + { + // A set status change + ConfigurationSetState state = AppInstaller::ToEnum(change.State); + ConfigurationChangeEventType changeType = ConfigurationChangeEventType::Unknown; + + SetStatusItem* setStatusItem = nullptr; + auto itr = m_lastSetStatus.find(change.SetInstanceIdentifier); + if (itr != m_lastSetStatus.end()) + { + setStatusItem = &itr->second; + } + + if (!setStatusItem) + { + changeType = ConfigurationChangeEventType::SetAdded; + + std::tie(itr, std::ignore) = m_lastSetStatus.emplace(change.SetInstanceIdentifier, SetStatusItem{ change }); + setStatusItem = &itr->second; + } + else + { + changeType = (change.InQueue ? ConfigurationChangeEventType::SetStateChanged : ConfigurationChangeEventType::SetRemoved); + } + + if (m_status.HasChangeRegistrations()) + { + if (!setStatusItem->Set) + { + setStatusItem->Set = m_status.Database().GetSet(change.SetInstanceIdentifier); + } + + auto changeData = make_self>(); + changeData->Initialize(changeType, change.SetInstanceIdentifier, state); + + m_status.ChangeDetected(*setStatusItem->Set, *changeData); + } + + auto setChangeData = make_self(); + setChangeData->Initialize(state); + + m_status.SetChangeDetected(change.SetInstanceIdentifier, setChangeData, std::nullopt); + } + + m_changeIdentifier = change.ChangeIdentifier; + } + + SetThreadpoolWait(m_threadPoolWait.get(), m_listenerEvent.get(), NULL); + } + CATCH_LOG_MSG("ChangeListener::WaitCallback exception"); + + ConfigurationStatus& m_status; + int64_t m_changeIdentifier; + std::map m_lastSetStatus; + wil::unique_event m_listenerEvent; + std::string m_listenerEventName; + + // Keep last to destroy first + wil::unique_threadpool_wait m_threadPoolWait; + }; + } + + ConfigurationStatus::ConfigurationStatus(private_construction) {} + + ConfigurationStatus::~ConfigurationStatus() = default; + + std::shared_ptr ConfigurationStatus::Instance() + { + static std::shared_ptr s_instance; + + std::shared_ptr result = std::atomic_load(&s_instance); + if (!result) + { + result = std::make_shared(private_construction{}); + std::shared_ptr empty; + + if (!std::atomic_compare_exchange_strong(&s_instance, &empty, result)) + { + result = empty; + } + } + + return result; + } + + ConfigurationSetState ConfigurationStatus::GetSetState(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return m_database.GetSetState(instanceIdentifier); + } + + clock::time_point ConfigurationStatus::GetSetFirstApply(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return clock::from_sys(m_database.GetSetFirstApply(instanceIdentifier)); + } + + clock::time_point ConfigurationStatus::GetSetApplyBegun(const winrt::guid& instanceIdentifier) + { + using system_clock = std::chrono::system_clock; + + m_database.EnsureOpened(false); + system_clock::time_point result = m_database.GetSetApplyBegun(instanceIdentifier); + return (result == system_clock::time_point{} ? clock::time_point{} : clock::from_sys(result)); + } + + clock::time_point ConfigurationStatus::GetSetApplyEnded(const winrt::guid& instanceIdentifier) + { + using system_clock = std::chrono::system_clock; + + m_database.EnsureOpened(false); + system_clock::time_point result = m_database.GetSetApplyEnded(instanceIdentifier); + return (result == system_clock::time_point{} ? clock::time_point{} : clock::from_sys(result)); + } + + ConfigurationUnitState ConfigurationStatus::GetUnitState(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return m_database.GetUnitState(instanceIdentifier); + } + + IConfigurationUnitResultInformation ConfigurationStatus::GetUnitResultInformation(const winrt::guid& instanceIdentifier) + { + m_database.EnsureOpened(false); + return m_database.GetUnitResultInformation(instanceIdentifier); + } + + void ConfigurationStatus::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + m_database.EnsureOpened(); + m_database.UpdateSetState(setInstanceIdentifier, state); + SignalChangeListeners(); + } + + void ConfigurationStatus::UpdateSetState(const guid& setInstanceIdentifier, bool inQueue) + { + m_database.EnsureOpened(); + m_database.UpdateSetInQueue(setInstanceIdentifier, inQueue); + SignalChangeListeners(); + } + + void ConfigurationStatus::UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData) + { + m_database.EnsureOpened(); + m_database.UpdateUnitState(setInstanceIdentifier, changeData); + SignalChangeListeners(); + } + + ConfigurationStatus::SetChangeRegistration::SetChangeRegistration(const winrt::guid& instanceIdentifier, ConfigurationSet* configurationSet) : + m_status(Instance()), m_instanceIdentifier(instanceIdentifier), m_configurationSet(configurationSet) {} + + ConfigurationStatus::SetChangeRegistration::~SetChangeRegistration() + { + m_status->RemoveSetChangeRegistration(m_instanceIdentifier, m_configurationSet); + } + + std::shared_ptr ConfigurationStatus::RegisterForSetChange(ConfigurationSet& set) + { + m_database.EnsureOpened(); + + winrt::guid instanceIdentifier = set.InstanceIdentifier(); + + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + m_setChangeRegistrations.emplace(instanceIdentifier, &set); + EnableChangeListeningIfNeeded(); + } + + return std::make_shared(instanceIdentifier, &set); + } + + void ConfigurationStatus::RemoveSetChangeRegistration(const winrt::guid& instanceIdentifier, ConfigurationSet* configurationSet) noexcept + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + + auto [begin, end] = m_setChangeRegistrations.equal_range(instanceIdentifier); + + for (; begin != end; ++begin) + { + if (begin->second == configurationSet) + { + m_setChangeRegistrations.erase(begin); + break; + } + } + + DisableChangeListeningIfNeeded(); + } + + ConfigurationStatus::ChangeRegistration::ChangeRegistration(const winrt::guid& instanceIdentifier) : + m_status(Instance()), m_instanceIdentifier(instanceIdentifier) {} + + ConfigurationStatus::ChangeRegistration::~ChangeRegistration() + { + m_status->RemoveChangeRegistration(m_instanceIdentifier); + } + + std::shared_ptr ConfigurationStatus::RegisterForChange(ConfigurationProcessor& processor) + { + m_database.EnsureOpened(); + + GUID instanceIdentifier; + std::ignore = CoCreateGuid(&instanceIdentifier); + + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + m_changeRegistrations.emplace_back(instanceIdentifier, &processor); + EnableChangeListeningIfNeeded(); + } + + return std::make_shared(instanceIdentifier); + } + + void ConfigurationStatus::RemoveChangeRegistration(const winrt::guid& instanceIdentifier) noexcept + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + + for (auto itr = m_changeRegistrations.begin(); itr != m_changeRegistrations.end(); ++itr) + { + if (itr->first == instanceIdentifier) + { + m_changeRegistrations.erase(itr); + DisableChangeListeningIfNeeded(); + return; + } + } + } + + void ConfigurationStatus::EnableChangeListeningIfNeeded() + { + if (!m_changeListener) + { + m_changeListener = std::make_unique(*this); + } + } + + void ConfigurationStatus::DisableChangeListeningIfNeeded() + { + if (m_changeListener && m_setChangeRegistrations.empty() && m_changeRegistrations.empty()) + { + m_changeListener.reset(); + } + } + + void ConfigurationStatus::SignalChangeListeners() + { + std::vector changeListeners = m_database.GetChangeListeners(); + + for (const auto& listener : changeListeners) + { + std::wstring objectName = AppInstaller::Utility::ConvertToUTF16(listener.ObjectName); + wil::unique_event listenerEvent; + if (listenerEvent.try_open(objectName.c_str(), EVENT_MODIFY_STATE)) + { + listenerEvent.SetEvent(); + } + else + { + m_database.RemoveListener(listener.ObjectName); + } + } + } + + ConfigurationDatabase& ConfigurationStatus::Database() + { + return m_database; + } + + bool ConfigurationStatus::HasSetChangeRegistration(const guid& setInstanceIdentifier) + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + auto [begin, end] = m_setChangeRegistrations.equal_range(setInstanceIdentifier); + return begin != end; + } + + bool ConfigurationStatus::HasChangeRegistrations() + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + return !m_changeRegistrations.empty(); + } + + void ConfigurationStatus::SetChangeDetected(const winrt::guid& setInstanceIdentifier, com_ptr& data, const std::optional& unitInstanceIdentifier) + { + std::vector setChangeRegistrations; + + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + + auto [begin, end] = m_setChangeRegistrations.equal_range(setInstanceIdentifier); + + for (; begin != end; ++begin) + { + setChangeRegistrations.emplace_back(begin->second); + } + } + + for (ConfigurationSet* set : setChangeRegistrations) + { + set->ConfigurationSetChange(data, unitInstanceIdentifier); + } + } + + void ConfigurationStatus::ChangeDetected(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data) + { + std::vector> changeRegistrations; + + { + std::lock_guard lock{ m_changeRegistrationsMutex }; + changeRegistrations = m_changeRegistrations; + } + + for (const auto& registration : changeRegistrations) + { + registration.second->ConfigurationChange(set, data); + } + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationStatus.h b/src/Microsoft.Management.Configuration/ConfigurationStatus.h index af30c59f55..fb2973d9fc 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationStatus.h +++ b/src/Microsoft.Management.Configuration/ConfigurationStatus.h @@ -1,127 +1,127 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Database/ConfigurationDatabase.h" -#include "ConfigurationSetChangeData.h" -#include -#include -#include -#include -#include -#include - - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Forward declarations - struct ConfigurationProcessor; - struct ConfigurationSet; - struct ConfigurationSetChangeData; - - namespace details - { - struct ChangeListener; - } - - // Provides access to overall configuration status information. - struct ConfigurationStatus - { - private: - struct private_construction {}; - - public: - friend details::ChangeListener; - - ConfigurationStatus(private_construction); - - ConfigurationStatus(const ConfigurationStatus&) = delete; - ConfigurationStatus& operator=(const ConfigurationStatus&) = delete; - - ConfigurationStatus(ConfigurationStatus&&) = delete; - ConfigurationStatus& operator=(ConfigurationStatus&&) = delete; - - ~ConfigurationStatus(); - - // Gets the singleton instance. - static std::shared_ptr Instance(); - - // Get various set state information - ConfigurationSetState GetSetState(const guid& instanceIdentifier); - clock::time_point GetSetFirstApply(const guid& instanceIdentifier); - clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); - clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); - ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); - IConfigurationUnitResultInformation GetUnitResultInformation(const guid& instanceIdentifier); - - // Record state changes - void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); - void UpdateSetState(const guid& setInstanceIdentifier, bool inQueue); - void UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData); - - // Keeps data for a set change listener. - struct SetChangeRegistration - { - SetChangeRegistration(const guid& instanceIdentifier, ConfigurationSet* configurationSet); - - SetChangeRegistration(const SetChangeRegistration&) = delete; - SetChangeRegistration& operator=(const SetChangeRegistration&) = delete; - - SetChangeRegistration(SetChangeRegistration&&) = delete; - SetChangeRegistration& operator=(SetChangeRegistration&&) = delete; - - ~SetChangeRegistration(); - - private: - std::shared_ptr m_status; - guid m_instanceIdentifier; - ConfigurationSet* m_configurationSet; - }; - - std::shared_ptr RegisterForSetChange(ConfigurationSet& set); - void RemoveSetChangeRegistration(const guid& instanceIdentifier, ConfigurationSet* configurationSet) noexcept; - - // Keeps data for a change listener. - struct ChangeRegistration - { - ChangeRegistration(const guid& instanceIdentifier); - - ChangeRegistration(const ChangeRegistration&) = delete; - ChangeRegistration& operator=(const ChangeRegistration&) = delete; - - ChangeRegistration(ChangeRegistration&&) = delete; - ChangeRegistration& operator=(ChangeRegistration&&) = delete; - - ~ChangeRegistration(); - - private: - std::shared_ptr m_status; - guid m_instanceIdentifier; - }; - - std::shared_ptr RegisterForChange(ConfigurationProcessor& processor); - void RemoveChangeRegistration(const guid& instanceIdentifier) noexcept; - - private: - void EnableChangeListeningIfNeeded(); - void DisableChangeListeningIfNeeded(); - - void SignalChangeListeners(); - - ConfigurationDatabase& Database(); - - bool HasSetChangeRegistration(const guid& setInstanceIdentifier); - bool HasChangeRegistrations(); - - void SetChangeDetected(const guid& setInstanceIdentifier, com_ptr& data, const std::optional& unitInstanceIdentifier); - void ChangeDetected(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data); - - ConfigurationDatabase m_database; - - std::mutex m_changeRegistrationsMutex; - std::multimap m_setChangeRegistrations; - std::vector> m_changeRegistrations; - - // Keep this last to ensure it is destroyed first - std::unique_ptr m_changeListener; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/ConfigurationDatabase.h" +#include "ConfigurationSetChangeData.h" +#include +#include +#include +#include +#include +#include + + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Forward declarations + struct ConfigurationProcessor; + struct ConfigurationSet; + struct ConfigurationSetChangeData; + + namespace details + { + struct ChangeListener; + } + + // Provides access to overall configuration status information. + struct ConfigurationStatus + { + private: + struct private_construction {}; + + public: + friend details::ChangeListener; + + ConfigurationStatus(private_construction); + + ConfigurationStatus(const ConfigurationStatus&) = delete; + ConfigurationStatus& operator=(const ConfigurationStatus&) = delete; + + ConfigurationStatus(ConfigurationStatus&&) = delete; + ConfigurationStatus& operator=(ConfigurationStatus&&) = delete; + + ~ConfigurationStatus(); + + // Gets the singleton instance. + static std::shared_ptr Instance(); + + // Get various set state information + ConfigurationSetState GetSetState(const guid& instanceIdentifier); + clock::time_point GetSetFirstApply(const guid& instanceIdentifier); + clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); + clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + IConfigurationUnitResultInformation GetUnitResultInformation(const guid& instanceIdentifier); + + // Record state changes + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + void UpdateSetState(const guid& setInstanceIdentifier, bool inQueue); + void UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData); + + // Keeps data for a set change listener. + struct SetChangeRegistration + { + SetChangeRegistration(const guid& instanceIdentifier, ConfigurationSet* configurationSet); + + SetChangeRegistration(const SetChangeRegistration&) = delete; + SetChangeRegistration& operator=(const SetChangeRegistration&) = delete; + + SetChangeRegistration(SetChangeRegistration&&) = delete; + SetChangeRegistration& operator=(SetChangeRegistration&&) = delete; + + ~SetChangeRegistration(); + + private: + std::shared_ptr m_status; + guid m_instanceIdentifier; + ConfigurationSet* m_configurationSet; + }; + + std::shared_ptr RegisterForSetChange(ConfigurationSet& set); + void RemoveSetChangeRegistration(const guid& instanceIdentifier, ConfigurationSet* configurationSet) noexcept; + + // Keeps data for a change listener. + struct ChangeRegistration + { + ChangeRegistration(const guid& instanceIdentifier); + + ChangeRegistration(const ChangeRegistration&) = delete; + ChangeRegistration& operator=(const ChangeRegistration&) = delete; + + ChangeRegistration(ChangeRegistration&&) = delete; + ChangeRegistration& operator=(ChangeRegistration&&) = delete; + + ~ChangeRegistration(); + + private: + std::shared_ptr m_status; + guid m_instanceIdentifier; + }; + + std::shared_ptr RegisterForChange(ConfigurationProcessor& processor); + void RemoveChangeRegistration(const guid& instanceIdentifier) noexcept; + + private: + void EnableChangeListeningIfNeeded(); + void DisableChangeListeningIfNeeded(); + + void SignalChangeListeners(); + + ConfigurationDatabase& Database(); + + bool HasSetChangeRegistration(const guid& setInstanceIdentifier); + bool HasChangeRegistrations(); + + void SetChangeDetected(const guid& setInstanceIdentifier, com_ptr& data, const std::optional& unitInstanceIdentifier); + void ChangeDetected(const Configuration::ConfigurationSet& set, const Configuration::ConfigurationChangeData& data); + + ConfigurationDatabase m_database; + + std::mutex m_changeRegistrationsMutex; + std::multimap m_setChangeRegistrations; + std::vector> m_changeRegistrations; + + // Keep this last to ensure it is destroyed first + std::unique_ptr m_changeListener; + }; +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp index 413ed40a93..743660f509 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationUnit.cpp @@ -1,232 +1,232 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ConfigurationUnit.h" -#include "ConfigurationUnit.g.cpp" -#include "ConfigurationSetParser.h" -#include "ConfigurationStatus.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - using ValueSet = Windows::Foundation::Collections::ValueSet; - - ValueSet Clone(const ValueSet& source) - { - ValueSet result; - - for (const auto& entry : source) - { - ValueSet child = entry.Value().try_as(); - - if (child) - { - result.Insert(entry.Key(), Clone(child)); - } - else - { - result.Insert(entry.Key(), entry.Value()); - } - } - - return result; - } - - Windows::Foundation::Collections::IVector Clone(const Windows::Foundation::Collections::IVector& value) - { - std::vector temp{ value.Size() }; - value.GetMany(0, temp); - return winrt::multi_threaded_vector(std::move(temp)); - } - } - - ConfigurationUnit::ConfigurationUnit() - { - GUID instanceIdentifier; - THROW_IF_FAILED(CoCreateGuid(&instanceIdentifier)); - m_instanceIdentifier = instanceIdentifier; - } - - ConfigurationUnit::ConfigurationUnit(const guid& instanceIdentifier) : - m_instanceIdentifier(instanceIdentifier) - { - } - - hstring ConfigurationUnit::Type() - { - return m_type; - } - - void ConfigurationUnit::Type(const hstring& value) - { - m_type = value; - } - - guid ConfigurationUnit::InstanceIdentifier() - { - return m_instanceIdentifier; - } - - hstring ConfigurationUnit::Identifier() - { - return m_identifier; - } - - void ConfigurationUnit::Identifier(const hstring& value) - { - m_identifier = value; - } - - ConfigurationUnitIntent ConfigurationUnit::Intent() - { - return m_intent; - } - - void ConfigurationUnit::Intent(ConfigurationUnitIntent value) - { - m_intent = value; - } - - Windows::Foundation::Collections::IVector ConfigurationUnit::Dependencies() - { - return m_dependencies; - } - - void ConfigurationUnit::Dependencies(const Windows::Foundation::Collections::IVector& value) - { - THROW_HR_IF(E_POINTER, !value); - m_dependencies = value; - } - - void ConfigurationUnit::Dependencies(std::vector&& value) - { - m_dependencies = winrt::multi_threaded_vector(std::move(value)); - } - - Windows::Foundation::Collections::ValueSet ConfigurationUnit::Metadata() - { - return m_metadata; - } - - void ConfigurationUnit::Metadata(const Windows::Foundation::Collections::ValueSet& value) - { - THROW_HR_IF(E_POINTER, !value); - m_metadata = value; - } - - Windows::Foundation::Collections::ValueSet ConfigurationUnit::Settings() - { - return m_settings; - } - - void ConfigurationUnit::Settings(const Windows::Foundation::Collections::ValueSet& value) - { - THROW_HR_IF(E_POINTER, !value); - m_settings = value; - } - - IConfigurationUnitProcessorDetails ConfigurationUnit::Details() - { - return m_details; - } - - void ConfigurationUnit::Details(IConfigurationUnitProcessorDetails details) - { - m_details = std::move(details); - } - - ConfigurationUnitState ConfigurationUnit::State() - { - auto status = ConfigurationStatus::Instance(); - return status->GetUnitState(m_instanceIdentifier); - } - - IConfigurationUnitResultInformation ConfigurationUnit::ResultInformation() - { - auto status = ConfigurationStatus::Instance(); - return status->GetUnitResultInformation(m_instanceIdentifier); - } - - bool ConfigurationUnit::IsActive() - { - return m_isActive; - } - - void ConfigurationUnit::IsActive(bool value) - { - m_isActive = value; - } - - Configuration::ConfigurationUnit ConfigurationUnit::Copy() - { - auto result = make_self(); - - result->m_type = m_type; - result->m_intent = m_intent; - result->m_dependencies = Clone(m_dependencies); - result->m_metadata = Clone(m_metadata); - result->m_settings = Clone(m_settings); - result->m_details = m_details; - result->m_environment = make_self(*m_environment); - - return *result; - } - - bool ConfigurationUnit::IsGroup() - { - return m_isGroup; - } - - void ConfigurationUnit::IsGroup(bool value) - { - m_isGroup = value; - - if (value) - { - if (!m_units) - { - m_units = winrt::multi_threaded_vector(); - } - } - } - - Windows::Foundation::Collections::IVector ConfigurationUnit::Units() - { - return m_units; - } - - void ConfigurationUnit::Units(const Windows::Foundation::Collections::IVector& value) - { - if (m_isGroup) - { - THROW_HR_IF(E_POINTER, !value); - } - else if (value) - { - m_isGroup = true; - } - - m_units = value; - } - - void ConfigurationUnit::Units(std::vector&& value) - { - m_units = winrt::multi_threaded_vector(std::move(value)); - } - - Configuration::ConfigurationEnvironment ConfigurationUnit::Environment() - { - return *m_environment; - } - - implementation::ConfigurationEnvironment& ConfigurationUnit::EnvironmentInternal() - { - return *m_environment; - } - - HRESULT STDMETHODCALLTYPE ConfigurationUnit::SetLifetimeWatcher(IUnknown* watcher) - { - return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ConfigurationUnit.h" +#include "ConfigurationUnit.g.cpp" +#include "ConfigurationSetParser.h" +#include "ConfigurationStatus.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + using ValueSet = Windows::Foundation::Collections::ValueSet; + + ValueSet Clone(const ValueSet& source) + { + ValueSet result; + + for (const auto& entry : source) + { + ValueSet child = entry.Value().try_as(); + + if (child) + { + result.Insert(entry.Key(), Clone(child)); + } + else + { + result.Insert(entry.Key(), entry.Value()); + } + } + + return result; + } + + Windows::Foundation::Collections::IVector Clone(const Windows::Foundation::Collections::IVector& value) + { + std::vector temp{ value.Size() }; + value.GetMany(0, temp); + return winrt::multi_threaded_vector(std::move(temp)); + } + } + + ConfigurationUnit::ConfigurationUnit() + { + GUID instanceIdentifier; + THROW_IF_FAILED(CoCreateGuid(&instanceIdentifier)); + m_instanceIdentifier = instanceIdentifier; + } + + ConfigurationUnit::ConfigurationUnit(const guid& instanceIdentifier) : + m_instanceIdentifier(instanceIdentifier) + { + } + + hstring ConfigurationUnit::Type() + { + return m_type; + } + + void ConfigurationUnit::Type(const hstring& value) + { + m_type = value; + } + + guid ConfigurationUnit::InstanceIdentifier() + { + return m_instanceIdentifier; + } + + hstring ConfigurationUnit::Identifier() + { + return m_identifier; + } + + void ConfigurationUnit::Identifier(const hstring& value) + { + m_identifier = value; + } + + ConfigurationUnitIntent ConfigurationUnit::Intent() + { + return m_intent; + } + + void ConfigurationUnit::Intent(ConfigurationUnitIntent value) + { + m_intent = value; + } + + Windows::Foundation::Collections::IVector ConfigurationUnit::Dependencies() + { + return m_dependencies; + } + + void ConfigurationUnit::Dependencies(const Windows::Foundation::Collections::IVector& value) + { + THROW_HR_IF(E_POINTER, !value); + m_dependencies = value; + } + + void ConfigurationUnit::Dependencies(std::vector&& value) + { + m_dependencies = winrt::multi_threaded_vector(std::move(value)); + } + + Windows::Foundation::Collections::ValueSet ConfigurationUnit::Metadata() + { + return m_metadata; + } + + void ConfigurationUnit::Metadata(const Windows::Foundation::Collections::ValueSet& value) + { + THROW_HR_IF(E_POINTER, !value); + m_metadata = value; + } + + Windows::Foundation::Collections::ValueSet ConfigurationUnit::Settings() + { + return m_settings; + } + + void ConfigurationUnit::Settings(const Windows::Foundation::Collections::ValueSet& value) + { + THROW_HR_IF(E_POINTER, !value); + m_settings = value; + } + + IConfigurationUnitProcessorDetails ConfigurationUnit::Details() + { + return m_details; + } + + void ConfigurationUnit::Details(IConfigurationUnitProcessorDetails details) + { + m_details = std::move(details); + } + + ConfigurationUnitState ConfigurationUnit::State() + { + auto status = ConfigurationStatus::Instance(); + return status->GetUnitState(m_instanceIdentifier); + } + + IConfigurationUnitResultInformation ConfigurationUnit::ResultInformation() + { + auto status = ConfigurationStatus::Instance(); + return status->GetUnitResultInformation(m_instanceIdentifier); + } + + bool ConfigurationUnit::IsActive() + { + return m_isActive; + } + + void ConfigurationUnit::IsActive(bool value) + { + m_isActive = value; + } + + Configuration::ConfigurationUnit ConfigurationUnit::Copy() + { + auto result = make_self(); + + result->m_type = m_type; + result->m_intent = m_intent; + result->m_dependencies = Clone(m_dependencies); + result->m_metadata = Clone(m_metadata); + result->m_settings = Clone(m_settings); + result->m_details = m_details; + result->m_environment = make_self(*m_environment); + + return *result; + } + + bool ConfigurationUnit::IsGroup() + { + return m_isGroup; + } + + void ConfigurationUnit::IsGroup(bool value) + { + m_isGroup = value; + + if (value) + { + if (!m_units) + { + m_units = winrt::multi_threaded_vector(); + } + } + } + + Windows::Foundation::Collections::IVector ConfigurationUnit::Units() + { + return m_units; + } + + void ConfigurationUnit::Units(const Windows::Foundation::Collections::IVector& value) + { + if (m_isGroup) + { + THROW_HR_IF(E_POINTER, !value); + } + else if (value) + { + m_isGroup = true; + } + + m_units = value; + } + + void ConfigurationUnit::Units(std::vector&& value) + { + m_units = winrt::multi_threaded_vector(std::move(value)); + } + + Configuration::ConfigurationEnvironment ConfigurationUnit::Environment() + { + return *m_environment; + } + + implementation::ConfigurationEnvironment& ConfigurationUnit::EnvironmentInternal() + { + return *m_environment; + } + + HRESULT STDMETHODCALLTYPE ConfigurationUnit::SetLifetimeWatcher(IUnknown* watcher) + { + return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } +} diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnit.h b/src/Microsoft.Management.Configuration/ConfigurationUnit.h index 613aca9b48..e19e5267d0 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationUnit.h +++ b/src/Microsoft.Management.Configuration/ConfigurationUnit.h @@ -1,93 +1,93 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationUnit.g.h" -#include "ConfigurationEnvironment.h" -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationUnit : ConfigurationUnitT>, AppInstaller::WinRT::LifetimeWatcherBase, AppInstaller::WinRT::ModuleCountBase - { - ConfigurationUnit(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - ConfigurationUnit(const guid& instanceIdentifier); - - implementation::ConfigurationEnvironment& EnvironmentInternal(); -#endif - - hstring Type(); - void Type(const hstring& value); - - guid InstanceIdentifier(); - - hstring Identifier(); - void Identifier(const hstring& value); - - ConfigurationUnitIntent Intent(); - void Intent(ConfigurationUnitIntent value); - - Windows::Foundation::Collections::IVector Dependencies(); - void Dependencies(const Windows::Foundation::Collections::IVector& value); - - Windows::Foundation::Collections::ValueSet Metadata(); - void Metadata(const Windows::Foundation::Collections::ValueSet& value); - - Windows::Foundation::Collections::ValueSet Settings(); - void Settings(const Windows::Foundation::Collections::ValueSet& value); - - IConfigurationUnitProcessorDetails Details(); - - ConfigurationUnitState State(); - - IConfigurationUnitResultInformation ResultInformation(); - - bool IsActive(); - void IsActive(bool value); - - Configuration::ConfigurationUnit Copy(); - - bool IsGroup(); - void IsGroup(bool value); - - Windows::Foundation::Collections::IVector Units(); - void Units(const Windows::Foundation::Collections::IVector& value); - - Configuration::ConfigurationEnvironment Environment(); - - HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Dependencies(std::vector&& value); - void Details(IConfigurationUnitProcessorDetails details); - void Units(std::vector&& value); - - private: - hstring m_type; - guid m_instanceIdentifier; - hstring m_identifier; - ConfigurationUnitIntent m_intent = ConfigurationUnitIntent::Apply; - Windows::Foundation::Collections::IVector m_dependencies{ winrt::multi_threaded_vector() }; - Windows::Foundation::Collections::ValueSet m_metadata; - Windows::Foundation::Collections::ValueSet m_settings; - IConfigurationUnitProcessorDetails m_details{ nullptr }; - bool m_isActive = true; - bool m_isGroup = false; - Windows::Foundation::Collections::IVector m_units = nullptr; - com_ptr m_environment{ make_self() }; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Configuration::factory_implementation -{ - struct ConfigurationUnit : ConfigurationUnitT - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationUnit.g.h" +#include "ConfigurationEnvironment.h" +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationUnit : ConfigurationUnitT>, AppInstaller::WinRT::LifetimeWatcherBase, AppInstaller::WinRT::ModuleCountBase + { + ConfigurationUnit(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + ConfigurationUnit(const guid& instanceIdentifier); + + implementation::ConfigurationEnvironment& EnvironmentInternal(); +#endif + + hstring Type(); + void Type(const hstring& value); + + guid InstanceIdentifier(); + + hstring Identifier(); + void Identifier(const hstring& value); + + ConfigurationUnitIntent Intent(); + void Intent(ConfigurationUnitIntent value); + + Windows::Foundation::Collections::IVector Dependencies(); + void Dependencies(const Windows::Foundation::Collections::IVector& value); + + Windows::Foundation::Collections::ValueSet Metadata(); + void Metadata(const Windows::Foundation::Collections::ValueSet& value); + + Windows::Foundation::Collections::ValueSet Settings(); + void Settings(const Windows::Foundation::Collections::ValueSet& value); + + IConfigurationUnitProcessorDetails Details(); + + ConfigurationUnitState State(); + + IConfigurationUnitResultInformation ResultInformation(); + + bool IsActive(); + void IsActive(bool value); + + Configuration::ConfigurationUnit Copy(); + + bool IsGroup(); + void IsGroup(bool value); + + Windows::Foundation::Collections::IVector Units(); + void Units(const Windows::Foundation::Collections::IVector& value); + + Configuration::ConfigurationEnvironment Environment(); + + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Dependencies(std::vector&& value); + void Details(IConfigurationUnitProcessorDetails details); + void Units(std::vector&& value); + + private: + hstring m_type; + guid m_instanceIdentifier; + hstring m_identifier; + ConfigurationUnitIntent m_intent = ConfigurationUnitIntent::Apply; + Windows::Foundation::Collections::IVector m_dependencies{ winrt::multi_threaded_vector() }; + Windows::Foundation::Collections::ValueSet m_metadata; + Windows::Foundation::Collections::ValueSet m_settings; + IConfigurationUnitProcessorDetails m_details{ nullptr }; + bool m_isActive = true; + bool m_isGroup = false; + Windows::Foundation::Collections::IVector m_units = nullptr; + com_ptr m_environment{ make_self() }; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Configuration::factory_implementation +{ + struct ConfigurationUnit : ConfigurationUnitT + { + }; +} +#endif diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp b/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp index bea6eb0517..6751f7913d 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp +++ b/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.cpp @@ -2,7 +2,7 @@ // Licensed under the MIT License. #include "pch.h" #include "ConfigurationUnitResultInformation.h" -#include "AppInstallerErrors.h" +#include "AppInstallerErrors.h" #include "AppInstallerStrings.h" namespace winrt::Microsoft::Management::Configuration::implementation @@ -25,10 +25,10 @@ namespace winrt::Microsoft::Management::Configuration::implementation } return ConfigurationUnitResultSource::Internal; - } - - hstring SanitizeString(std::wstring_view value) - { + } + + hstring SanitizeString(std::wstring_view value) + { using namespace AppInstaller::Utility; return hstring{ ConvertToUTF16(ConvertControlCodesToPictures(ConvertToUTF8(value))) }; } @@ -63,9 +63,9 @@ namespace winrt::Microsoft::Management::Configuration::implementation { m_resultCode = resultCode; m_resultSource = resultSource; - } - - void ConfigurationUnitResultInformation::Initialize(hresult resultCode, std::wstring_view description, std::wstring_view details, ConfigurationUnitResultSource resultSource) + } + + void ConfigurationUnitResultInformation::Initialize(hresult resultCode, std::wstring_view description, std::wstring_view details, ConfigurationUnitResultSource resultSource) { m_resultCode = resultCode; m_description = SanitizeString(description); diff --git a/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.h b/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.h index ee1fe23626..16acbf8022 100644 --- a/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.h +++ b/src/Microsoft.Management.Configuration/ConfigurationUnitResultInformation.h @@ -1,40 +1,40 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct ConfigurationUnitResultInformation : winrt::implements - { - ConfigurationUnitResultInformation() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(const Configuration::IConfigurationUnitResultInformation& other); - void Initialize(hresult resultCode, std::wstring_view description); - void Initialize(hresult resultCode, hstring description); - void Initialize(hresult resultCode, ConfigurationUnitResultSource resultSource); - void Initialize(hresult resultCode, std::wstring_view description, std::wstring_view details, ConfigurationUnitResultSource resultSource); -#endif - - hresult ResultCode() const; - void ResultCode(hresult resultCode); - - hstring Description(); - void Description(hstring value); - - hstring Details(); - void Details(hstring value); - - ConfigurationUnitResultSource ResultSource() const; - void ResultSource(ConfigurationUnitResultSource value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - hresult m_resultCode; - hstring m_description; - hstring m_details; - ConfigurationUnitResultSource m_resultSource = ConfigurationUnitResultSource::None; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct ConfigurationUnitResultInformation : winrt::implements + { + ConfigurationUnitResultInformation() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(const Configuration::IConfigurationUnitResultInformation& other); + void Initialize(hresult resultCode, std::wstring_view description); + void Initialize(hresult resultCode, hstring description); + void Initialize(hresult resultCode, ConfigurationUnitResultSource resultSource); + void Initialize(hresult resultCode, std::wstring_view description, std::wstring_view details, ConfigurationUnitResultSource resultSource); +#endif + + hresult ResultCode() const; + void ResultCode(hresult resultCode); + + hstring Description(); + void Description(hstring value); + + hstring Details(); + void Details(hstring value); + + ConfigurationUnitResultSource ResultSource() const; + void ResultSource(ConfigurationUnitResultSource value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + hresult m_resultCode; + hstring m_description; + hstring m_details; + ConfigurationUnitResultSource m_resultSource = ConfigurationUnitResultSource::None; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.cpp b/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.cpp index de9a2916ee..9693ca3b50 100644 --- a/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.cpp +++ b/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.cpp @@ -1,470 +1,470 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Database/ConfigurationDatabase.h" -#include "Database/Schema/IConfigurationDatabase.h" -#include "ConfigurationUnitResultInformation.h" -#include -#include -#include "Filesystem.h" - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - // Use an alternate location for the dev build history. -#ifdef AICLI_DISABLE_TEST_HOOKS - constexpr std::string_view s_Database_DirectoryName = "History"sv; -#else - constexpr std::string_view s_Database_DirectoryName = "DevHistory"sv; -#endif - - constexpr std::string_view s_Database_FileName = "config.db"sv; - - #define s_Database_MutexName L"WindowsPackageManager_Configuration_DatabaseMutex" - - std::vector ConvertStatusItems(const std::vector& input) - { - std::vector result; - - for (const auto& item : input) - { - ConfigurationDatabase::StatusItem statusItem{}; - std::tie( - statusItem.ChangeIdentifier, - statusItem.ChangeTime, - statusItem.SetInstanceIdentifier, - statusItem.InQueue, - statusItem.UnitInstanceIdentifier, - statusItem.State, - statusItem.ResultCode, - statusItem.ResultDescription, - statusItem.ResultDetails, - statusItem.ResultSource) = item; - result.emplace_back(std::move(statusItem)); - } - - return result; - } - } - - ConfigurationDatabase::ConfigurationDatabase() = default; - - ConfigurationDatabase::ConfigurationDatabase(ConfigurationDatabase&&) = default; - ConfigurationDatabase& ConfigurationDatabase::operator=(ConfigurationDatabase&&) = default; - - ConfigurationDatabase::~ConfigurationDatabase() = default; - - void ConfigurationDatabase::EnsureOpened(bool createIfNeeded) - { -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif - if (!std::atomic_load(&m_database)) - { - std::filesystem::path databaseDirectory = AppInstaller::Filesystem::GetPathTo(PathName::LocalState) / s_Database_DirectoryName; - std::filesystem::path databaseFile = databaseDirectory / s_Database_FileName; - - { - wil::unique_mutex databaseMutex; - databaseMutex.create(s_Database_MutexName); - auto databaseLock = databaseMutex.acquire(); - - if (!std::filesystem::is_regular_file(databaseFile) && createIfNeeded) - { - if (std::filesystem::exists(databaseFile)) - { - std::filesystem::remove_all(databaseDirectory); - } - - std::filesystem::create_directories(databaseDirectory); - - auto connection = std::make_shared(databaseFile, IConfigurationDatabase::GetLatestVersion()); - auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) }; - database->InitializeDatabase(); - - std::atomic_store(&m_connection, connection); - std::atomic_store(&m_database, database); - } - } - - if (!std::atomic_load(&m_connection)) - { - std::shared_ptr empty; - auto connection = std::make_shared(databaseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); - std::atomic_compare_exchange_strong(&m_connection, &empty, connection); - } - - if (!std::atomic_load(&m_database)) - { - std::shared_ptr empty; - auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(std::atomic_load(&m_connection), true) }; - std::atomic_compare_exchange_strong(&m_database, &empty, database); - } - } -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); -#endif - } - - template - auto ConfigurationDatabase::ExecuteReadOperation(std::string_view operationName, OperationT&& operation, bool requireDatabase) const - { - using ResultT = decltype(operation(std::declval&>())); - ResultT result{}; - -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif - auto database = std::atomic_load(&m_database); - - if (database) - { - auto transaction = BeginTransaction(operationName, false, database); - result = operation(database); - } - else if (requireDatabase) - { - THROW_HR(E_NOT_VALID_STATE); - } -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); -#endif - - return result; - } - - template - void ConfigurationDatabase::ExecuteWriteOperation(std::string_view operationName, OperationT&& operation, bool silentlyIgnoreNoDatabase) - { -#ifdef AICLI_DISABLE_TEST_HOOKS - // While under development, treat errors escaping this function as a test hook. - try - { -#endif - auto database = std::atomic_load(&m_database); - - if (!database) - { - THROW_HR_IF(E_NOT_VALID_STATE, !silentlyIgnoreNoDatabase); - return; - } - - auto transaction = BeginTransaction(operationName, true, database); - operation(database); - std::atomic_load(&m_connection)->SetLastWriteTime(); - transaction->Commit(); -#ifdef AICLI_DISABLE_TEST_HOOKS - } - CATCH_LOG(); -#endif - } - - std::vector ConfigurationDatabase::GetSetHistory() const - { - return ExecuteReadOperation("GetSetHistory", - [&](std::shared_ptr& database) - { - return database->GetSets(); - }); - } - - ConfigurationDatabase::ConfigurationSetPtr ConfigurationDatabase::GetSet(const GUID& instanceIdentifier) const - { - return ExecuteReadOperation("GetSet", - [&](std::shared_ptr& database) - { - return database->GetSet(instanceIdentifier); - }); - } - - void ConfigurationDatabase::WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory) - { - THROW_HR_IF_NULL(E_POINTER, configurationSet); - - ExecuteWriteOperation("WriteSetHistory", - [&](std::shared_ptr& database) - { - std::optional setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier()); - - if (!setRowId && !preferNewHistory) - { - // TODO: Use conflict detection code to check for a matching set - } - - if (setRowId) - { - database->UpdateSet(setRowId.value(), configurationSet); - } - else - { - database->AddSet(configurationSet); - } - }); - } - - void ConfigurationDatabase::RemoveSetHistory(const Configuration::ConfigurationSet& configurationSet) - { - THROW_HR_IF_NULL(E_POINTER, configurationSet); - - ExecuteWriteOperation("RemoveSetHistory", - [&](std::shared_ptr& database) - { - std::optional setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier()); - - if (!setRowId) - { - // TODO: Use conflict detection code to check for a matching set - } - - if (setRowId) - { - database->RemoveSet(setRowId.value()); - std::atomic_load(&m_connection)->SetLastWriteTime(); - } - }, true); - } - - - void ConfigurationDatabase::AddQueueItem(const Configuration::ConfigurationSet& configurationSet, const std::string& objectName) - { - THROW_HR_IF_NULL(E_POINTER, configurationSet); - - ExecuteWriteOperation("AddQueueItem", - [&](std::shared_ptr& database) - { - database->AddQueueItem(configurationSet.InstanceIdentifier(), objectName); - }); - } - - void ConfigurationDatabase::SetActiveQueueItem(const std::string& objectName) - { - ExecuteWriteOperation("SetActiveQueueItem", - [&](std::shared_ptr& database) - { - database->SetActiveQueueItem(objectName); - }); - } - - std::vector ConfigurationDatabase::GetQueueItems() const - { - return ExecuteReadOperation("GetQueueItems", - [&](std::shared_ptr& database) - { - std::vector result; - auto queueItems = database->GetQueueItems(); - result.reserve(queueItems.size()); - - for (const auto& item : queueItems) - { - QueueItem resultItem; - std::tie(resultItem.SetInstanceIdentifier, resultItem.ObjectName, resultItem.QueuedAt, resultItem.ProcessId, resultItem.Active) = item; - result.emplace_back(std::move(resultItem)); - } - - return result; - }, true); - } - - void ConfigurationDatabase::RemoveQueueItem(const std::string& objectName) - { - ExecuteWriteOperation("RemoveQueueItem", - [&](std::shared_ptr& database) - { - database->RemoveQueueItem(objectName); - }); - } - - std::vector ConfigurationDatabase::GetStatusSince(int64_t changeIdentifier) const - { - return ExecuteReadOperation("GetStatusSince", - [&](std::shared_ptr& database) - { - return ConvertStatusItems(database->GetStatusSince(changeIdentifier)); - }); - } - - ConfigurationDatabase::StatusBaseline ConfigurationDatabase::GetStatusBaseline() const - { - return ExecuteReadOperation("GetStatusBaseline", - [&](std::shared_ptr& database) - { - auto [changeIdentifier, setStatus] = database->GetStatusBaseline(); - - StatusBaseline result{}; - result.ChangeIdentifier = changeIdentifier; - result.SetStatus = ConvertStatusItems(setStatus); - return result; - }); - } - - void ConfigurationDatabase::AddListener(const std::string& objectName) - { - ExecuteWriteOperation("AddListener", - [&](std::shared_ptr& database) - { - database->AddListener(objectName); - }); - } - - void ConfigurationDatabase::RemoveListener(const std::string& objectName) - { - ExecuteWriteOperation("RemoveListener", - [&](std::shared_ptr& database) - { - database->RemoveListener(objectName); - }); - } - - std::vector ConfigurationDatabase::GetChangeListeners() const - { - return ExecuteReadOperation("GetChangeListeners", - [&](std::shared_ptr& database) - { - std::vector result; - - for (const auto& item : database->GetChangeListeners()) - { - StatusChangeListener listener{}; - std::tie(listener.ObjectName, listener.Started, listener.ProcessId) = item; - result.emplace_back(std::move(listener)); - } - - return result; - }); - } - - void ConfigurationDatabase::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) - { - ExecuteWriteOperation("UpdateSetState", - [&](std::shared_ptr& database) - { - database->UpdateSetState(setInstanceIdentifier, state); - }); - } - - void ConfigurationDatabase::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) - { - ExecuteWriteOperation("UpdateSetInQueue", - [&](std::shared_ptr& database) - { - database->UpdateSetInQueue(setInstanceIdentifier, inQueue); - }); - } - - void ConfigurationDatabase::UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData) - { - ExecuteWriteOperation("UpdateUnitState", - [&](std::shared_ptr& database) - { - database->UpdateUnitState(setInstanceIdentifier, changeData); - }); - } - - ConfigurationSetState ConfigurationDatabase::GetSetState(const guid& instanceIdentifier) - { - return ExecuteReadOperation("GetSetState", - [&](std::shared_ptr& database) - { - return database->GetSetState(instanceIdentifier); - }); - } - - std::chrono::system_clock::time_point ConfigurationDatabase::GetSetFirstApply(const guid& instanceIdentifier) - { - return ExecuteReadOperation("GetSetFirstApply", - [&](std::shared_ptr& database) - { - return database->GetSetFirstApply(instanceIdentifier); - }); - } - - std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyBegun(const guid& instanceIdentifier) - { - return ExecuteReadOperation("GetSetApplyBegun", - [&](std::shared_ptr& database) - { - return database->GetSetApplyBegun(instanceIdentifier); - }); - } - - std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyEnded(const guid& instanceIdentifier) - { - return ExecuteReadOperation("GetSetApplyEnded", - [&](std::shared_ptr& database) - { - return database->GetSetApplyEnded(instanceIdentifier); - }); - } - - ConfigurationUnitState ConfigurationDatabase::GetUnitState(const guid& instanceIdentifier) - { - return ExecuteReadOperation("GetUnitState", - [&](std::shared_ptr& database) - { - return database->GetUnitState(instanceIdentifier); - }); - } - - IConfigurationUnitResultInformation ConfigurationDatabase::GetUnitResultInformation(const guid& instanceIdentifier) - { - return ExecuteReadOperation("GetUnitResultInformation", - [&](std::shared_ptr& database) - { - com_ptr> result; - - auto resultInformation = database->GetUnitResultInformation(instanceIdentifier); - - if (resultInformation) - { - result = make_self>(); - result->Initialize( - std::get<0>(resultInformation.value()), - ConvertToUTF16(std::get<1>(resultInformation.value())), - ConvertToUTF16(std::get<2>(resultInformation.value())), - std::get<3>(resultInformation.value())); - } - - IConfigurationUnitResultInformation actualResult; - if (result) - { - actualResult = *result; - } - - return actualResult; - }); - } - - ConfigurationDatabase::TransactionLock ConfigurationDatabase::BeginTransaction(std::string_view name, bool forWrite, std::shared_ptr& database) const - { - auto connection = std::atomic_load(&m_connection); - THROW_HR_IF_NULL(E_NOT_VALID_STATE, connection); - - TransactionLock result = connection->TryBeginTransaction(name, forWrite); - - while (!result) - { - { - auto connectionLock = connection->LockConnection(); - auto newDatabase = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) }; - if (std::atomic_compare_exchange_strong(&m_database, &database, newDatabase)) - { - database = newDatabase; - } - } - - result = connection->TryBeginTransaction(name, forWrite); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Database/ConfigurationDatabase.h" +#include "Database/Schema/IConfigurationDatabase.h" +#include "ConfigurationUnitResultInformation.h" +#include +#include +#include "Filesystem.h" + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + // Use an alternate location for the dev build history. +#ifdef AICLI_DISABLE_TEST_HOOKS + constexpr std::string_view s_Database_DirectoryName = "History"sv; +#else + constexpr std::string_view s_Database_DirectoryName = "DevHistory"sv; +#endif + + constexpr std::string_view s_Database_FileName = "config.db"sv; + + #define s_Database_MutexName L"WindowsPackageManager_Configuration_DatabaseMutex" + + std::vector ConvertStatusItems(const std::vector& input) + { + std::vector result; + + for (const auto& item : input) + { + ConfigurationDatabase::StatusItem statusItem{}; + std::tie( + statusItem.ChangeIdentifier, + statusItem.ChangeTime, + statusItem.SetInstanceIdentifier, + statusItem.InQueue, + statusItem.UnitInstanceIdentifier, + statusItem.State, + statusItem.ResultCode, + statusItem.ResultDescription, + statusItem.ResultDetails, + statusItem.ResultSource) = item; + result.emplace_back(std::move(statusItem)); + } + + return result; + } + } + + ConfigurationDatabase::ConfigurationDatabase() = default; + + ConfigurationDatabase::ConfigurationDatabase(ConfigurationDatabase&&) = default; + ConfigurationDatabase& ConfigurationDatabase::operator=(ConfigurationDatabase&&) = default; + + ConfigurationDatabase::~ConfigurationDatabase() = default; + + void ConfigurationDatabase::EnsureOpened(bool createIfNeeded) + { +#ifdef AICLI_DISABLE_TEST_HOOKS + // While under development, treat errors escaping this function as a test hook. + try + { +#endif + if (!std::atomic_load(&m_database)) + { + std::filesystem::path databaseDirectory = AppInstaller::Filesystem::GetPathTo(PathName::LocalState) / s_Database_DirectoryName; + std::filesystem::path databaseFile = databaseDirectory / s_Database_FileName; + + { + wil::unique_mutex databaseMutex; + databaseMutex.create(s_Database_MutexName); + auto databaseLock = databaseMutex.acquire(); + + if (!std::filesystem::is_regular_file(databaseFile) && createIfNeeded) + { + if (std::filesystem::exists(databaseFile)) + { + std::filesystem::remove_all(databaseDirectory); + } + + std::filesystem::create_directories(databaseDirectory); + + auto connection = std::make_shared(databaseFile, IConfigurationDatabase::GetLatestVersion()); + auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) }; + database->InitializeDatabase(); + + std::atomic_store(&m_connection, connection); + std::atomic_store(&m_database, database); + } + } + + if (!std::atomic_load(&m_connection)) + { + std::shared_ptr empty; + auto connection = std::make_shared(databaseFile, SQLiteStorageBase::OpenDisposition::ReadWrite); + std::atomic_compare_exchange_strong(&m_connection, &empty, connection); + } + + if (!std::atomic_load(&m_database)) + { + std::shared_ptr empty; + auto database = std::shared_ptr{ IConfigurationDatabase::CreateFor(std::atomic_load(&m_connection), true) }; + std::atomic_compare_exchange_strong(&m_database, &empty, database); + } + } +#ifdef AICLI_DISABLE_TEST_HOOKS + } + CATCH_LOG(); +#endif + } + + template + auto ConfigurationDatabase::ExecuteReadOperation(std::string_view operationName, OperationT&& operation, bool requireDatabase) const + { + using ResultT = decltype(operation(std::declval&>())); + ResultT result{}; + +#ifdef AICLI_DISABLE_TEST_HOOKS + // While under development, treat errors escaping this function as a test hook. + try + { +#endif + auto database = std::atomic_load(&m_database); + + if (database) + { + auto transaction = BeginTransaction(operationName, false, database); + result = operation(database); + } + else if (requireDatabase) + { + THROW_HR(E_NOT_VALID_STATE); + } +#ifdef AICLI_DISABLE_TEST_HOOKS + } + CATCH_LOG(); +#endif + + return result; + } + + template + void ConfigurationDatabase::ExecuteWriteOperation(std::string_view operationName, OperationT&& operation, bool silentlyIgnoreNoDatabase) + { +#ifdef AICLI_DISABLE_TEST_HOOKS + // While under development, treat errors escaping this function as a test hook. + try + { +#endif + auto database = std::atomic_load(&m_database); + + if (!database) + { + THROW_HR_IF(E_NOT_VALID_STATE, !silentlyIgnoreNoDatabase); + return; + } + + auto transaction = BeginTransaction(operationName, true, database); + operation(database); + std::atomic_load(&m_connection)->SetLastWriteTime(); + transaction->Commit(); +#ifdef AICLI_DISABLE_TEST_HOOKS + } + CATCH_LOG(); +#endif + } + + std::vector ConfigurationDatabase::GetSetHistory() const + { + return ExecuteReadOperation("GetSetHistory", + [&](std::shared_ptr& database) + { + return database->GetSets(); + }); + } + + ConfigurationDatabase::ConfigurationSetPtr ConfigurationDatabase::GetSet(const GUID& instanceIdentifier) const + { + return ExecuteReadOperation("GetSet", + [&](std::shared_ptr& database) + { + return database->GetSet(instanceIdentifier); + }); + } + + void ConfigurationDatabase::WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory) + { + THROW_HR_IF_NULL(E_POINTER, configurationSet); + + ExecuteWriteOperation("WriteSetHistory", + [&](std::shared_ptr& database) + { + std::optional setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier()); + + if (!setRowId && !preferNewHistory) + { + // TODO: Use conflict detection code to check for a matching set + } + + if (setRowId) + { + database->UpdateSet(setRowId.value(), configurationSet); + } + else + { + database->AddSet(configurationSet); + } + }); + } + + void ConfigurationDatabase::RemoveSetHistory(const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF_NULL(E_POINTER, configurationSet); + + ExecuteWriteOperation("RemoveSetHistory", + [&](std::shared_ptr& database) + { + std::optional setRowId = database->GetSetRowId(configurationSet.InstanceIdentifier()); + + if (!setRowId) + { + // TODO: Use conflict detection code to check for a matching set + } + + if (setRowId) + { + database->RemoveSet(setRowId.value()); + std::atomic_load(&m_connection)->SetLastWriteTime(); + } + }, true); + } + + + void ConfigurationDatabase::AddQueueItem(const Configuration::ConfigurationSet& configurationSet, const std::string& objectName) + { + THROW_HR_IF_NULL(E_POINTER, configurationSet); + + ExecuteWriteOperation("AddQueueItem", + [&](std::shared_ptr& database) + { + database->AddQueueItem(configurationSet.InstanceIdentifier(), objectName); + }); + } + + void ConfigurationDatabase::SetActiveQueueItem(const std::string& objectName) + { + ExecuteWriteOperation("SetActiveQueueItem", + [&](std::shared_ptr& database) + { + database->SetActiveQueueItem(objectName); + }); + } + + std::vector ConfigurationDatabase::GetQueueItems() const + { + return ExecuteReadOperation("GetQueueItems", + [&](std::shared_ptr& database) + { + std::vector result; + auto queueItems = database->GetQueueItems(); + result.reserve(queueItems.size()); + + for (const auto& item : queueItems) + { + QueueItem resultItem; + std::tie(resultItem.SetInstanceIdentifier, resultItem.ObjectName, resultItem.QueuedAt, resultItem.ProcessId, resultItem.Active) = item; + result.emplace_back(std::move(resultItem)); + } + + return result; + }, true); + } + + void ConfigurationDatabase::RemoveQueueItem(const std::string& objectName) + { + ExecuteWriteOperation("RemoveQueueItem", + [&](std::shared_ptr& database) + { + database->RemoveQueueItem(objectName); + }); + } + + std::vector ConfigurationDatabase::GetStatusSince(int64_t changeIdentifier) const + { + return ExecuteReadOperation("GetStatusSince", + [&](std::shared_ptr& database) + { + return ConvertStatusItems(database->GetStatusSince(changeIdentifier)); + }); + } + + ConfigurationDatabase::StatusBaseline ConfigurationDatabase::GetStatusBaseline() const + { + return ExecuteReadOperation("GetStatusBaseline", + [&](std::shared_ptr& database) + { + auto [changeIdentifier, setStatus] = database->GetStatusBaseline(); + + StatusBaseline result{}; + result.ChangeIdentifier = changeIdentifier; + result.SetStatus = ConvertStatusItems(setStatus); + return result; + }); + } + + void ConfigurationDatabase::AddListener(const std::string& objectName) + { + ExecuteWriteOperation("AddListener", + [&](std::shared_ptr& database) + { + database->AddListener(objectName); + }); + } + + void ConfigurationDatabase::RemoveListener(const std::string& objectName) + { + ExecuteWriteOperation("RemoveListener", + [&](std::shared_ptr& database) + { + database->RemoveListener(objectName); + }); + } + + std::vector ConfigurationDatabase::GetChangeListeners() const + { + return ExecuteReadOperation("GetChangeListeners", + [&](std::shared_ptr& database) + { + std::vector result; + + for (const auto& item : database->GetChangeListeners()) + { + StatusChangeListener listener{}; + std::tie(listener.ObjectName, listener.Started, listener.ProcessId) = item; + result.emplace_back(std::move(listener)); + } + + return result; + }); + } + + void ConfigurationDatabase::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + ExecuteWriteOperation("UpdateSetState", + [&](std::shared_ptr& database) + { + database->UpdateSetState(setInstanceIdentifier, state); + }); + } + + void ConfigurationDatabase::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) + { + ExecuteWriteOperation("UpdateSetInQueue", + [&](std::shared_ptr& database) + { + database->UpdateSetInQueue(setInstanceIdentifier, inQueue); + }); + } + + void ConfigurationDatabase::UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData) + { + ExecuteWriteOperation("UpdateUnitState", + [&](std::shared_ptr& database) + { + database->UpdateUnitState(setInstanceIdentifier, changeData); + }); + } + + ConfigurationSetState ConfigurationDatabase::GetSetState(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetSetState", + [&](std::shared_ptr& database) + { + return database->GetSetState(instanceIdentifier); + }); + } + + std::chrono::system_clock::time_point ConfigurationDatabase::GetSetFirstApply(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetSetFirstApply", + [&](std::shared_ptr& database) + { + return database->GetSetFirstApply(instanceIdentifier); + }); + } + + std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyBegun(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetSetApplyBegun", + [&](std::shared_ptr& database) + { + return database->GetSetApplyBegun(instanceIdentifier); + }); + } + + std::chrono::system_clock::time_point ConfigurationDatabase::GetSetApplyEnded(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetSetApplyEnded", + [&](std::shared_ptr& database) + { + return database->GetSetApplyEnded(instanceIdentifier); + }); + } + + ConfigurationUnitState ConfigurationDatabase::GetUnitState(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetUnitState", + [&](std::shared_ptr& database) + { + return database->GetUnitState(instanceIdentifier); + }); + } + + IConfigurationUnitResultInformation ConfigurationDatabase::GetUnitResultInformation(const guid& instanceIdentifier) + { + return ExecuteReadOperation("GetUnitResultInformation", + [&](std::shared_ptr& database) + { + com_ptr> result; + + auto resultInformation = database->GetUnitResultInformation(instanceIdentifier); + + if (resultInformation) + { + result = make_self>(); + result->Initialize( + std::get<0>(resultInformation.value()), + ConvertToUTF16(std::get<1>(resultInformation.value())), + ConvertToUTF16(std::get<2>(resultInformation.value())), + std::get<3>(resultInformation.value())); + } + + IConfigurationUnitResultInformation actualResult; + if (result) + { + actualResult = *result; + } + + return actualResult; + }); + } + + ConfigurationDatabase::TransactionLock ConfigurationDatabase::BeginTransaction(std::string_view name, bool forWrite, std::shared_ptr& database) const + { + auto connection = std::atomic_load(&m_connection); + THROW_HR_IF_NULL(E_NOT_VALID_STATE, connection); + + TransactionLock result = connection->TryBeginTransaction(name, forWrite); + + while (!result) + { + { + auto connectionLock = connection->LockConnection(); + auto newDatabase = std::shared_ptr{ IConfigurationDatabase::CreateFor(connection) }; + if (std::atomic_compare_exchange_strong(&m_database, &database, newDatabase)) + { + database = newDatabase; + } + } + + result = connection->TryBeginTransaction(name, forWrite); + } + + return result; + } +} diff --git a/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.h b/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.h index d147090ca2..92b860e1dd 100644 --- a/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.h +++ b/src/Microsoft.Management.Configuration/Database/ConfigurationDatabase.h @@ -1,151 +1,151 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "ConfigurationSetChangeData.h" -#include -#include -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Forward declarations - struct ConfigurationSet; - struct IConfigurationDatabase; - - // Allows access to the configuration database. - struct ConfigurationDatabase - { - using ConfigurationSetPtr = winrt::com_ptr; - - ConfigurationDatabase(); - - ConfigurationDatabase(const ConfigurationDatabase&) = delete; - ConfigurationDatabase& operator=(const ConfigurationDatabase&) = delete; - - ConfigurationDatabase(ConfigurationDatabase&&); - ConfigurationDatabase& operator=(ConfigurationDatabase&&); - - ~ConfigurationDatabase(); - - // Ensures that the database connection is established and the schema interface is created appropriately. - // If `createIfNeeded` is false, this function will not create the database if it does not exist. - // If not connected, any read methods will return empty results and any write methods will throw. - void EnsureOpened(bool createIfNeeded = true); - - // Gets all of the configuration sets from the database. - std::vector GetSetHistory() const; - - // Gets the set with the given identifier. - ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) const; - - // Writes the given set to the database history, attempting to merge with a matching set if one exists unless preferNewHistory is true. - void WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory); - - // Removes the given set from the database history if it is present. - void RemoveSetHistory(const Configuration::ConfigurationSet& configurationSet); - - // Adds a new queue item for the given configuration set and object name. - void AddQueueItem(const Configuration::ConfigurationSet& configurationSet, const std::string& objectName); - - // Sets the queue item with the given object name as active. - void SetActiveQueueItem(const std::string& objectName); - - // Data about a queue item. - struct QueueItem - { - GUID SetInstanceIdentifier{}; - std::string ObjectName; - std::chrono::system_clock::time_point QueuedAt; - DWORD ProcessId{}; - bool Active = false; - }; - - // Gets all queue items in queue order (item at index 0 is active/next). - std::vector GetQueueItems() const; - - // Removes the queue item with the given object name. - void RemoveQueueItem(const std::string& objectName); - - // A status line item. - struct StatusItem - { - int64_t ChangeIdentifier; - std::chrono::system_clock::time_point ChangeTime; - GUID SetInstanceIdentifier; - bool InQueue; - std::optional UnitInstanceIdentifier; - int32_t State; - std::optional ResultCode; - std::string ResultDescription; - std::string ResultDetails; - ConfigurationUnitResultSource ResultSource; - }; - - // Gets all changed status items after the given change identifier. - std::vector GetStatusSince(int64_t changeIdentifier) const; - - // The status baseline data. - struct StatusBaseline - { - int64_t ChangeIdentifier = 0; - std::vector SetStatus; - }; - - // Gets the current status baseline. - StatusBaseline GetStatusBaseline() const; - - // Data about a status change listener. - struct StatusChangeListener - { - std::string ObjectName; - std::chrono::system_clock::time_point Started; - DWORD ProcessId{}; - }; - - // Adds a listener to the database. - void AddListener(const std::string& objectName); - - // Removes a listener from the database. - void RemoveListener(const std::string& objectName); - - // Gets all listeners in the database. - std::vector GetChangeListeners() const; - - // Updates the set state in the database. - void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); - - // Updates the set "in queue" state in the database. - void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); - - // Updates the unit state in the database. - void UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData); - - // Read various status values. - ConfigurationSetState GetSetState(const guid& instanceIdentifier); - std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier); - std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); - std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); - ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); - IConfigurationUnitResultInformation GetUnitResultInformation(const guid& instanceIdentifier); - - private: - std::shared_ptr m_connection; - mutable std::shared_ptr m_database; - - using TransactionLock = decltype(m_connection->TryBeginTransaction({}, true)); - - // Begins a transaction, which may require upgrading to a newer schema version. - TransactionLock BeginTransaction(std::string_view name, bool forWrite, std::shared_ptr& database) const; - - // Performs the boilerplate setup for a read, then executes the given operation. - template - auto ExecuteReadOperation(std::string_view operationName, OperationT&& operation, bool requireDatabase = false) const; - - // Performs the boilerplate setup for a write, then executes the given operation. - template - void ExecuteWriteOperation(std::string_view operationName, OperationT&& operation, bool silentlyIgnoreNoDatabase = false); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "ConfigurationSetChangeData.h" +#include +#include +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Forward declarations + struct ConfigurationSet; + struct IConfigurationDatabase; + + // Allows access to the configuration database. + struct ConfigurationDatabase + { + using ConfigurationSetPtr = winrt::com_ptr; + + ConfigurationDatabase(); + + ConfigurationDatabase(const ConfigurationDatabase&) = delete; + ConfigurationDatabase& operator=(const ConfigurationDatabase&) = delete; + + ConfigurationDatabase(ConfigurationDatabase&&); + ConfigurationDatabase& operator=(ConfigurationDatabase&&); + + ~ConfigurationDatabase(); + + // Ensures that the database connection is established and the schema interface is created appropriately. + // If `createIfNeeded` is false, this function will not create the database if it does not exist. + // If not connected, any read methods will return empty results and any write methods will throw. + void EnsureOpened(bool createIfNeeded = true); + + // Gets all of the configuration sets from the database. + std::vector GetSetHistory() const; + + // Gets the set with the given identifier. + ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) const; + + // Writes the given set to the database history, attempting to merge with a matching set if one exists unless preferNewHistory is true. + void WriteSetHistory(const Configuration::ConfigurationSet& configurationSet, bool preferNewHistory); + + // Removes the given set from the database history if it is present. + void RemoveSetHistory(const Configuration::ConfigurationSet& configurationSet); + + // Adds a new queue item for the given configuration set and object name. + void AddQueueItem(const Configuration::ConfigurationSet& configurationSet, const std::string& objectName); + + // Sets the queue item with the given object name as active. + void SetActiveQueueItem(const std::string& objectName); + + // Data about a queue item. + struct QueueItem + { + GUID SetInstanceIdentifier{}; + std::string ObjectName; + std::chrono::system_clock::time_point QueuedAt; + DWORD ProcessId{}; + bool Active = false; + }; + + // Gets all queue items in queue order (item at index 0 is active/next). + std::vector GetQueueItems() const; + + // Removes the queue item with the given object name. + void RemoveQueueItem(const std::string& objectName); + + // A status line item. + struct StatusItem + { + int64_t ChangeIdentifier; + std::chrono::system_clock::time_point ChangeTime; + GUID SetInstanceIdentifier; + bool InQueue; + std::optional UnitInstanceIdentifier; + int32_t State; + std::optional ResultCode; + std::string ResultDescription; + std::string ResultDetails; + ConfigurationUnitResultSource ResultSource; + }; + + // Gets all changed status items after the given change identifier. + std::vector GetStatusSince(int64_t changeIdentifier) const; + + // The status baseline data. + struct StatusBaseline + { + int64_t ChangeIdentifier = 0; + std::vector SetStatus; + }; + + // Gets the current status baseline. + StatusBaseline GetStatusBaseline() const; + + // Data about a status change listener. + struct StatusChangeListener + { + std::string ObjectName; + std::chrono::system_clock::time_point Started; + DWORD ProcessId{}; + }; + + // Adds a listener to the database. + void AddListener(const std::string& objectName); + + // Removes a listener from the database. + void RemoveListener(const std::string& objectName); + + // Gets all listeners in the database. + std::vector GetChangeListeners() const; + + // Updates the set state in the database. + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + + // Updates the set "in queue" state in the database. + void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); + + // Updates the unit state in the database. + void UpdateUnitState(const guid& setInstanceIdentifier, const com_ptr& changeData); + + // Read various status values. + ConfigurationSetState GetSetState(const guid& instanceIdentifier); + std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier); + std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); + std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + IConfigurationUnitResultInformation GetUnitResultInformation(const guid& instanceIdentifier); + + private: + std::shared_ptr m_connection; + mutable std::shared_ptr m_database; + + using TransactionLock = decltype(m_connection->TryBeginTransaction({}, true)); + + // Begins a transaction, which may require upgrading to a newer schema version. + TransactionLock BeginTransaction(std::string_view name, bool forWrite, std::shared_ptr& database) const; + + // Performs the boilerplate setup for a read, then executes the given operation. + template + auto ExecuteReadOperation(std::string_view operationName, OperationT&& operation, bool requireDatabase = false) const; + + // Performs the boilerplate setup for a write, then executes the given operation. + template + void ExecuteWriteOperation(std::string_view operationName, OperationT&& operation, bool silentlyIgnoreNoDatabase = false); + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface.h b/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface.h index 9729edbd56..c879eac6c2 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Database/Schema/IConfigurationDatabase.h" - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 -{ - struct Interface : public IConfigurationDatabase - { - Interface(std::shared_ptr storage); - - const AppInstaller::SQLite::Version& GetSchemaVersion() override; - - // Version 0.1 - void InitializeDatabase() override; - void AddSet(const Configuration::ConfigurationSet& configurationSet) override; - void UpdateSet(AppInstaller::SQLite::rowid_t target, const Configuration::ConfigurationSet& configurationSet) override; - void RemoveSet(AppInstaller::SQLite::rowid_t target) override; - std::vector GetSets() override; - std::optional GetSetRowId(const GUID& instanceIdentifier) override; - - // Version 0.2 - bool MigrateFrom(IConfigurationDatabase* current) override; - - // Version 0.3 - ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) override; - - protected: - std::shared_ptr m_storage; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/Schema/IConfigurationDatabase.h" + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 +{ + struct Interface : public IConfigurationDatabase + { + Interface(std::shared_ptr storage); + + const AppInstaller::SQLite::Version& GetSchemaVersion() override; + + // Version 0.1 + void InitializeDatabase() override; + void AddSet(const Configuration::ConfigurationSet& configurationSet) override; + void UpdateSet(AppInstaller::SQLite::rowid_t target, const Configuration::ConfigurationSet& configurationSet) override; + void RemoveSet(AppInstaller::SQLite::rowid_t target) override; + std::vector GetSets() override; + std::optional GetSetRowId(const GUID& instanceIdentifier) override; + + // Version 0.2 + bool MigrateFrom(IConfigurationDatabase* current) override; + + // Version 0.3 + ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) override; + + protected: + std::shared_ptr m_storage; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface_0_1.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface_0_1.cpp index 02a40427b6..d7e6208467 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface_0_1.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/Interface_0_1.cpp @@ -1,92 +1,92 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Interface.h" -#include "SetInfoTable.h" -#include "UnitInfoTable.h" - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 -{ - static constexpr AppInstaller::SQLite::Version s_InterfaceVersion{ 0, 1 }; - - Interface::Interface(std::shared_ptr storage) : - m_storage(std::move(storage)) - {} - - const AppInstaller::SQLite::Version& Interface::GetSchemaVersion() - { - return s_InterfaceVersion; - } - - void Interface::InitializeDatabase() - { - // Must enable WAL mode outside of a transaction - THROW_HR_IF(E_UNEXPECTED, !m_storage->GetConnection().SetJournalMode("WAL")); - - Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_1"); - - SetInfoTable setInfoTable(*m_storage); - setInfoTable.Create(); - - UnitInfoTable unitInfoTable(*m_storage); - unitInfoTable.Create(); - - savepoint.Commit(); - } - - void Interface::AddSet(const Configuration::ConfigurationSet& configurationSet) - { - Savepoint savepoint = Savepoint::Create(*m_storage, "AddSet_0_1"); - - SetInfoTable setInfoTable(*m_storage); - setInfoTable.Add(configurationSet); - - savepoint.Commit(); - } - - void Interface::UpdateSet(rowid_t target, const Configuration::ConfigurationSet& configurationSet) - { - Savepoint savepoint = Savepoint::Create(*m_storage, "UpdateSet_0_1"); - - SetInfoTable setInfoTable(*m_storage); - setInfoTable.Update(target, configurationSet); - - savepoint.Commit(); - } - - void Interface::RemoveSet(rowid_t target) - { - Savepoint savepoint = Savepoint::Create(*m_storage, "RemoveSet_0_1"); - - SetInfoTable setInfoTable(*m_storage); - setInfoTable.Remove(target); - - savepoint.Commit(); - } - - std::vector Interface::GetSets() - { - SetInfoTable setInfoTable(*m_storage); - return setInfoTable.GetAllSets(); - } - - std::optional Interface::GetSetRowId(const GUID& instanceIdentifier) - { - SetInfoTable setInfoTable(*m_storage); - return setInfoTable.GetSetRowId(instanceIdentifier); - } - - bool Interface::MigrateFrom(IConfigurationDatabase* current) - { - return current->GetSchemaVersion() == s_InterfaceVersion; - } - - Interface::ConfigurationSetPtr Interface::GetSet(const GUID& instanceIdentifier) - { - SetInfoTable setInfoTable(*m_storage); - return setInfoTable.GetSet(instanceIdentifier); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Interface.h" +#include "SetInfoTable.h" +#include "UnitInfoTable.h" + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 +{ + static constexpr AppInstaller::SQLite::Version s_InterfaceVersion{ 0, 1 }; + + Interface::Interface(std::shared_ptr storage) : + m_storage(std::move(storage)) + {} + + const AppInstaller::SQLite::Version& Interface::GetSchemaVersion() + { + return s_InterfaceVersion; + } + + void Interface::InitializeDatabase() + { + // Must enable WAL mode outside of a transaction + THROW_HR_IF(E_UNEXPECTED, !m_storage->GetConnection().SetJournalMode("WAL")); + + Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_1"); + + SetInfoTable setInfoTable(*m_storage); + setInfoTable.Create(); + + UnitInfoTable unitInfoTable(*m_storage); + unitInfoTable.Create(); + + savepoint.Commit(); + } + + void Interface::AddSet(const Configuration::ConfigurationSet& configurationSet) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "AddSet_0_1"); + + SetInfoTable setInfoTable(*m_storage); + setInfoTable.Add(configurationSet); + + savepoint.Commit(); + } + + void Interface::UpdateSet(rowid_t target, const Configuration::ConfigurationSet& configurationSet) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "UpdateSet_0_1"); + + SetInfoTable setInfoTable(*m_storage); + setInfoTable.Update(target, configurationSet); + + savepoint.Commit(); + } + + void Interface::RemoveSet(rowid_t target) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "RemoveSet_0_1"); + + SetInfoTable setInfoTable(*m_storage); + setInfoTable.Remove(target); + + savepoint.Commit(); + } + + std::vector Interface::GetSets() + { + SetInfoTable setInfoTable(*m_storage); + return setInfoTable.GetAllSets(); + } + + std::optional Interface::GetSetRowId(const GUID& instanceIdentifier) + { + SetInfoTable setInfoTable(*m_storage); + return setInfoTable.GetSetRowId(instanceIdentifier); + } + + bool Interface::MigrateFrom(IConfigurationDatabase* current) + { + return current->GetSchemaVersion() == s_InterfaceVersion; + } + + Interface::ConfigurationSetPtr Interface::GetSet(const GUID& instanceIdentifier) + { + SetInfoTable setInfoTable(*m_storage); + return setInfoTable.GetSet(instanceIdentifier); + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp index e2003aa68f..a7bcd24791 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.cpp @@ -1,264 +1,264 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "SetInfoTable.h" -#include "UnitInfoTable.h" -#include "ConfigurationSetSerializer.h" -#include "ConfigurationSetParser.h" -#include -#include -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::SQLite::Builder; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 -{ - namespace - { - constexpr std::string_view s_SetInfoTable_Table = "set_info"sv; - - constexpr std::string_view s_SetInfoTable_Column_InstanceIdentifier = "instance_identifier"sv; - constexpr std::string_view s_SetInfoTable_Column_Name = "name"sv; - constexpr std::string_view s_SetInfoTable_Column_Origin = "origin"sv; - constexpr std::string_view s_SetInfoTable_Column_Path = "path"sv; - constexpr std::string_view s_SetInfoTable_Column_FirstApply = "first_apply"sv; - constexpr std::string_view s_SetInfoTable_Column_SchemaVersion = "schema_version"sv; - constexpr std::string_view s_SetInfoTable_Column_Metadata = "metadata"sv; - constexpr std::string_view s_SetInfoTable_Column_Parameters = "parameters"sv; - constexpr std::string_view s_SetInfoTable_Column_Variables = "variables"sv; - - void BuildBaseSetSelectStatement(StatementBuilder& builder) - { - builder.Select({ - RowIDName, // 0 - s_SetInfoTable_Column_InstanceIdentifier, // 1 - s_SetInfoTable_Column_Name, // 2 - s_SetInfoTable_Column_Origin, // 3 - s_SetInfoTable_Column_Path, // 4 - s_SetInfoTable_Column_SchemaVersion, // 5 - s_SetInfoTable_Column_Metadata, // 6 - s_SetInfoTable_Column_Parameters, // 7 - s_SetInfoTable_Column_Variables, // 8 - }).From(s_SetInfoTable_Table); - } - - IConfigurationDatabase::ConfigurationSetPtr GetSetFromStatement(Statement& statement, UnitInfoTable& unitInfoTable) - { - auto configurationSet = make_self(statement.GetColumn(1)); - - configurationSet->Name(hstring{ ConvertToUTF16(statement.GetColumn(2)) }); - configurationSet->Origin(hstring{ ConvertToUTF16(statement.GetColumn(3)) }); - configurationSet->Path(hstring{ ConvertToUTF16(statement.GetColumn(4)) }); - - std::string schemaVersion = statement.GetColumn(5); - configurationSet->SchemaVersion(hstring{ ConvertToUTF16(schemaVersion) }); - - auto parser = ConfigurationSetParser::CreateForSchemaVersion(schemaVersion); - configurationSet->Metadata(parser->ParseValueSet(statement.GetColumn(6))); - parser->ExtractEnvironmentFromMetadata(configurationSet->Metadata(), configurationSet->EnvironmentInternal()); - - THROW_HR_IF(E_NOTIMPL, !statement.GetColumn(7).empty()); - configurationSet->Variables(parser->ParseValueSet(statement.GetColumn(8))); - - std::vector winrtUnits; - for (const auto& unit : unitInfoTable.GetAllUnitsForSet(statement.GetColumn(0), schemaVersion)) - { - winrtUnits.emplace_back(*unit); - } - configurationSet->Units(std::move(winrtUnits)); - - return configurationSet; - } - } - - SetInfoTable::SetInfoTable(Connection& connection) : m_connection(connection) {} - - std::string_view SetInfoTable::TableName() - { - return s_SetInfoTable_Table; - } - - std::string_view SetInfoTable::InstanceIdentifierColumn() - { - return s_SetInfoTable_Column_InstanceIdentifier; - } - - void SetInfoTable::Create() - { - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Create_0_1"); - - StatementBuilder tableBuilder; - tableBuilder.CreateTable(s_SetInfoTable_Table).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_SetInfoTable_Column_InstanceIdentifier, Type::Blob).Unique().NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Name, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Origin, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Path, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_FirstApply, Type::Int64).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_SchemaVersion, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Metadata, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Parameters, Type::Text).NotNull(), - ColumnBuilder(s_SetInfoTable_Column_Variables, Type::Text).NotNull(), - }); - - tableBuilder.Execute(m_connection); - - savepoint.Commit(); - } - - rowid_t SetInfoTable::Add(const Configuration::ConfigurationSet& configurationSet) - { - THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); - - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Add_0_1"); - - hstring schemaVersion = configurationSet.SchemaVersion(); - auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); - - StatementBuilder builder; - builder.InsertInto(s_SetInfoTable_Table).Columns({ - s_SetInfoTable_Column_InstanceIdentifier, - s_SetInfoTable_Column_Name, - s_SetInfoTable_Column_Origin, - s_SetInfoTable_Column_Path, - s_SetInfoTable_Column_FirstApply, - s_SetInfoTable_Column_SchemaVersion, - s_SetInfoTable_Column_Metadata, - s_SetInfoTable_Column_Parameters, - s_SetInfoTable_Column_Variables, - }).Values( - static_cast(configurationSet.InstanceIdentifier()), - ConvertToUTF8(configurationSet.Name()), - ConvertToUTF8(configurationSet.Origin()), - ConvertToUTF8(configurationSet.Path()), - GetCurrentUnixEpoch(), - ConvertToUTF8(schemaVersion), - serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment()), - std::string{}, // Parameters - serializer->SerializeValueSet(configurationSet.Variables()) - ); - - builder.Execute(m_connection); - rowid_t result = m_connection.GetLastInsertRowID(); - - UnitInfoTable unitInfoTable(m_connection); - - auto winrtUnits = configurationSet.Units(); - std::vector units{ winrtUnits.Size() }; - winrtUnits.GetMany(0, units); - - for (const auto& unit : units) - { - unitInfoTable.Add(unit, result, schemaVersion); - } - - savepoint.Commit(); - return result; - } - - void SetInfoTable::Update(rowid_t target, const Configuration::ConfigurationSet& configurationSet) - { - THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); - - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Update_0_1"); - - hstring schemaVersion = configurationSet.SchemaVersion(); - auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); - - StatementBuilder builder; - builder.Update(s_SetInfoTable_Table).Set(). - Column(s_SetInfoTable_Column_Name).Equals(ConvertToUTF8(configurationSet.Name())). - Column(s_SetInfoTable_Column_Origin).Equals(ConvertToUTF8(configurationSet.Origin())). - Column(s_SetInfoTable_Column_Path).Equals(ConvertToUTF8(configurationSet.Path())). - Column(s_SetInfoTable_Column_SchemaVersion).Equals(ConvertToUTF8(schemaVersion)). - Column(s_SetInfoTable_Column_Metadata).Equals(serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment())). - Column(s_SetInfoTable_Column_Variables).Equals(serializer->SerializeValueSet(configurationSet.Variables())). - Where(RowIDName).Equals(target); - - builder.Execute(m_connection); - - UnitInfoTable unitInfoTable(m_connection); - unitInfoTable.UpdateForSet(target, configurationSet.Units(), schemaVersion); - - savepoint.Commit(); - } - - void SetInfoTable::Remove(rowid_t target) - { - Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Remove_0_1"); - - StatementBuilder builder; - builder.DeleteFrom(s_SetInfoTable_Table).Where(RowIDName).Equals(target); - builder.Execute(m_connection); - - UnitInfoTable unitInfoTable(m_connection); - unitInfoTable.RemoveForSet(target); - - savepoint.Commit(); - } - - std::vector SetInfoTable::GetAllSets() - { - std::vector result; - - StatementBuilder builder; - BuildBaseSetSelectStatement(builder); - - Statement getAllSets = builder.Prepare(m_connection); - - UnitInfoTable unitInfoTable(m_connection); - - while (getAllSets.Step()) - { - result.emplace_back(GetSetFromStatement(getAllSets, unitInfoTable)); - } - - return result; - } - - std::optional SetInfoTable::GetSetRowId(const GUID& instanceIdentifier) - { - StatementBuilder builder; - builder.Select(RowIDName).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); - - Statement select = builder.Prepare(m_connection); - - if (select.Step()) - { - return select.GetColumn(0); - } - - return std::nullopt; - } - - IConfigurationDatabase::ConfigurationSetPtr SetInfoTable::GetSet(const GUID& instanceIdentifier) - { - IConfigurationDatabase::ConfigurationSetPtr result; - - StatementBuilder builder; - BuildBaseSetSelectStatement(builder); - builder.Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); - - Statement getSet = builder.Prepare(m_connection); - - if (getSet.Step()) - { - UnitInfoTable unitInfoTable(m_connection); - result = GetSetFromStatement(getSet, unitInfoTable); - } - - return result; - } - - std::chrono::system_clock::time_point SetInfoTable::GetSetFirstApply(const GUID& instanceIdentifier) - { - StatementBuilder builder; - builder.Select(s_SetInfoTable_Column_FirstApply).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); - - Statement statement = builder.Prepare(m_connection); - - return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn(0)) : std::chrono::system_clock::time_point{}); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "SetInfoTable.h" +#include "UnitInfoTable.h" +#include "ConfigurationSetSerializer.h" +#include "ConfigurationSetParser.h" +#include +#include +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 +{ + namespace + { + constexpr std::string_view s_SetInfoTable_Table = "set_info"sv; + + constexpr std::string_view s_SetInfoTable_Column_InstanceIdentifier = "instance_identifier"sv; + constexpr std::string_view s_SetInfoTable_Column_Name = "name"sv; + constexpr std::string_view s_SetInfoTable_Column_Origin = "origin"sv; + constexpr std::string_view s_SetInfoTable_Column_Path = "path"sv; + constexpr std::string_view s_SetInfoTable_Column_FirstApply = "first_apply"sv; + constexpr std::string_view s_SetInfoTable_Column_SchemaVersion = "schema_version"sv; + constexpr std::string_view s_SetInfoTable_Column_Metadata = "metadata"sv; + constexpr std::string_view s_SetInfoTable_Column_Parameters = "parameters"sv; + constexpr std::string_view s_SetInfoTable_Column_Variables = "variables"sv; + + void BuildBaseSetSelectStatement(StatementBuilder& builder) + { + builder.Select({ + RowIDName, // 0 + s_SetInfoTable_Column_InstanceIdentifier, // 1 + s_SetInfoTable_Column_Name, // 2 + s_SetInfoTable_Column_Origin, // 3 + s_SetInfoTable_Column_Path, // 4 + s_SetInfoTable_Column_SchemaVersion, // 5 + s_SetInfoTable_Column_Metadata, // 6 + s_SetInfoTable_Column_Parameters, // 7 + s_SetInfoTable_Column_Variables, // 8 + }).From(s_SetInfoTable_Table); + } + + IConfigurationDatabase::ConfigurationSetPtr GetSetFromStatement(Statement& statement, UnitInfoTable& unitInfoTable) + { + auto configurationSet = make_self(statement.GetColumn(1)); + + configurationSet->Name(hstring{ ConvertToUTF16(statement.GetColumn(2)) }); + configurationSet->Origin(hstring{ ConvertToUTF16(statement.GetColumn(3)) }); + configurationSet->Path(hstring{ ConvertToUTF16(statement.GetColumn(4)) }); + + std::string schemaVersion = statement.GetColumn(5); + configurationSet->SchemaVersion(hstring{ ConvertToUTF16(schemaVersion) }); + + auto parser = ConfigurationSetParser::CreateForSchemaVersion(schemaVersion); + configurationSet->Metadata(parser->ParseValueSet(statement.GetColumn(6))); + parser->ExtractEnvironmentFromMetadata(configurationSet->Metadata(), configurationSet->EnvironmentInternal()); + + THROW_HR_IF(E_NOTIMPL, !statement.GetColumn(7).empty()); + configurationSet->Variables(parser->ParseValueSet(statement.GetColumn(8))); + + std::vector winrtUnits; + for (const auto& unit : unitInfoTable.GetAllUnitsForSet(statement.GetColumn(0), schemaVersion)) + { + winrtUnits.emplace_back(*unit); + } + configurationSet->Units(std::move(winrtUnits)); + + return configurationSet; + } + } + + SetInfoTable::SetInfoTable(Connection& connection) : m_connection(connection) {} + + std::string_view SetInfoTable::TableName() + { + return s_SetInfoTable_Table; + } + + std::string_view SetInfoTable::InstanceIdentifierColumn() + { + return s_SetInfoTable_Column_InstanceIdentifier; + } + + void SetInfoTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Create_0_1"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_SetInfoTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_SetInfoTable_Column_InstanceIdentifier, Type::Blob).Unique().NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Name, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Origin, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Path, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_FirstApply, Type::Int64).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_SchemaVersion, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Metadata, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Parameters, Type::Text).NotNull(), + ColumnBuilder(s_SetInfoTable_Column_Variables, Type::Text).NotNull(), + }); + + tableBuilder.Execute(m_connection); + + savepoint.Commit(); + } + + rowid_t SetInfoTable::Add(const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); + + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Add_0_1"); + + hstring schemaVersion = configurationSet.SchemaVersion(); + auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); + + StatementBuilder builder; + builder.InsertInto(s_SetInfoTable_Table).Columns({ + s_SetInfoTable_Column_InstanceIdentifier, + s_SetInfoTable_Column_Name, + s_SetInfoTable_Column_Origin, + s_SetInfoTable_Column_Path, + s_SetInfoTable_Column_FirstApply, + s_SetInfoTable_Column_SchemaVersion, + s_SetInfoTable_Column_Metadata, + s_SetInfoTable_Column_Parameters, + s_SetInfoTable_Column_Variables, + }).Values( + static_cast(configurationSet.InstanceIdentifier()), + ConvertToUTF8(configurationSet.Name()), + ConvertToUTF8(configurationSet.Origin()), + ConvertToUTF8(configurationSet.Path()), + GetCurrentUnixEpoch(), + ConvertToUTF8(schemaVersion), + serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment()), + std::string{}, // Parameters + serializer->SerializeValueSet(configurationSet.Variables()) + ); + + builder.Execute(m_connection); + rowid_t result = m_connection.GetLastInsertRowID(); + + UnitInfoTable unitInfoTable(m_connection); + + auto winrtUnits = configurationSet.Units(); + std::vector units{ winrtUnits.Size() }; + winrtUnits.GetMany(0, units); + + for (const auto& unit : units) + { + unitInfoTable.Add(unit, result, schemaVersion); + } + + savepoint.Commit(); + return result; + } + + void SetInfoTable::Update(rowid_t target, const Configuration::ConfigurationSet& configurationSet) + { + THROW_HR_IF(E_NOTIMPL, configurationSet.Parameters().Size() > 0); + + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Update_0_1"); + + hstring schemaVersion = configurationSet.SchemaVersion(); + auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); + + StatementBuilder builder; + builder.Update(s_SetInfoTable_Table).Set(). + Column(s_SetInfoTable_Column_Name).Equals(ConvertToUTF8(configurationSet.Name())). + Column(s_SetInfoTable_Column_Origin).Equals(ConvertToUTF8(configurationSet.Origin())). + Column(s_SetInfoTable_Column_Path).Equals(ConvertToUTF8(configurationSet.Path())). + Column(s_SetInfoTable_Column_SchemaVersion).Equals(ConvertToUTF8(schemaVersion)). + Column(s_SetInfoTable_Column_Metadata).Equals(serializer->SerializeMetadataWithEnvironment(configurationSet.Metadata(), configurationSet.Environment())). + Column(s_SetInfoTable_Column_Variables).Equals(serializer->SerializeValueSet(configurationSet.Variables())). + Where(RowIDName).Equals(target); + + builder.Execute(m_connection); + + UnitInfoTable unitInfoTable(m_connection); + unitInfoTable.UpdateForSet(target, configurationSet.Units(), schemaVersion); + + savepoint.Commit(); + } + + void SetInfoTable::Remove(rowid_t target) + { + Savepoint savepoint = Savepoint::Create(m_connection, "SetInfoTable_Remove_0_1"); + + StatementBuilder builder; + builder.DeleteFrom(s_SetInfoTable_Table).Where(RowIDName).Equals(target); + builder.Execute(m_connection); + + UnitInfoTable unitInfoTable(m_connection); + unitInfoTable.RemoveForSet(target); + + savepoint.Commit(); + } + + std::vector SetInfoTable::GetAllSets() + { + std::vector result; + + StatementBuilder builder; + BuildBaseSetSelectStatement(builder); + + Statement getAllSets = builder.Prepare(m_connection); + + UnitInfoTable unitInfoTable(m_connection); + + while (getAllSets.Step()) + { + result.emplace_back(GetSetFromStatement(getAllSets, unitInfoTable)); + } + + return result; + } + + std::optional SetInfoTable::GetSetRowId(const GUID& instanceIdentifier) + { + StatementBuilder builder; + builder.Select(RowIDName).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement select = builder.Prepare(m_connection); + + if (select.Step()) + { + return select.GetColumn(0); + } + + return std::nullopt; + } + + IConfigurationDatabase::ConfigurationSetPtr SetInfoTable::GetSet(const GUID& instanceIdentifier) + { + IConfigurationDatabase::ConfigurationSetPtr result; + + StatementBuilder builder; + BuildBaseSetSelectStatement(builder); + builder.Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement getSet = builder.Prepare(m_connection); + + if (getSet.Step()) + { + UnitInfoTable unitInfoTable(m_connection); + result = GetSetFromStatement(getSet, unitInfoTable); + } + + return result; + } + + std::chrono::system_clock::time_point SetInfoTable::GetSetFirstApply(const GUID& instanceIdentifier) + { + StatementBuilder builder; + builder.Select(s_SetInfoTable_Column_FirstApply).From(s_SetInfoTable_Table).Where(s_SetInfoTable_Column_InstanceIdentifier).Equals(instanceIdentifier); + + Statement statement = builder.Prepare(m_connection); + + return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn(0)) : std::chrono::system_clock::time_point{}); + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.h index 67223bfc88..da038f3a56 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/SetInfoTable.h @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" -#include "Database/Schema/IConfigurationDatabase.h" -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 -{ - struct SetInfoTable - { - SetInfoTable(AppInstaller::SQLite::Connection& connection); - - static std::string_view TableName(); - static std::string_view InstanceIdentifierColumn(); - - // Creates the set info table. - void Create(); - - // Adds the given configuration set to the table. - // Returns the row id of the added set. - AppInstaller::SQLite::rowid_t Add(const Configuration::ConfigurationSet& configurationSet); - - // Updates the set with the target row id using the given set. - void Update(AppInstaller::SQLite::rowid_t target, const Configuration::ConfigurationSet& configurationSet); - - // Removes the set with the target row id. - void Remove(AppInstaller::SQLite::rowid_t target); - - // Gets all of the sets from the table. - std::vector GetAllSets(); - - // Gets the row id of the set with the given instance identifier. - std::optional GetSetRowId(const GUID& instanceIdentifier); - - // Gets the set with the given instance identifier. - IConfigurationDatabase::ConfigurationSetPtr GetSet(const GUID& instanceIdentifier); - - // Gets a set's first apply time. - std::chrono::system_clock::time_point GetSetFirstApply(const GUID& instanceIdentifier); - - private: - AppInstaller::SQLite::Connection& m_connection; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "Database/Schema/IConfigurationDatabase.h" +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 +{ + struct SetInfoTable + { + SetInfoTable(AppInstaller::SQLite::Connection& connection); + + static std::string_view TableName(); + static std::string_view InstanceIdentifierColumn(); + + // Creates the set info table. + void Create(); + + // Adds the given configuration set to the table. + // Returns the row id of the added set. + AppInstaller::SQLite::rowid_t Add(const Configuration::ConfigurationSet& configurationSet); + + // Updates the set with the target row id using the given set. + void Update(AppInstaller::SQLite::rowid_t target, const Configuration::ConfigurationSet& configurationSet); + + // Removes the set with the target row id. + void Remove(AppInstaller::SQLite::rowid_t target); + + // Gets all of the sets from the table. + std::vector GetAllSets(); + + // Gets the row id of the set with the given instance identifier. + std::optional GetSetRowId(const GUID& instanceIdentifier); + + // Gets the set with the given instance identifier. + IConfigurationDatabase::ConfigurationSetPtr GetSet(const GUID& instanceIdentifier); + + // Gets a set's first apply time. + std::chrono::system_clock::time_point GetSetFirstApply(const GUID& instanceIdentifier); + + private: + AppInstaller::SQLite::Connection& m_connection; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.cpp index 708accd1b1..ed41cfea3a 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.cpp @@ -1,229 +1,229 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "UnitInfoTable.h" -#include "ConfigurationUnit.h" -#include "ConfigurationSetParser.h" -#include "ConfigurationSetSerializer.h" -#include -#include -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::SQLite::Builder; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 -{ - namespace - { - constexpr std::string_view s_UnitInfoTable_Table = "unit_info"sv; - constexpr std::string_view s_UnitInfoTable_SetRowIdIndex = "unit_info_set_idx"sv; - - constexpr std::string_view s_UnitInfoTable_Column_SetRowId = "set_rowid"sv; - constexpr std::string_view s_UnitInfoTable_Column_ParentRowId = "parent_rowid"sv; - constexpr std::string_view s_UnitInfoTable_Column_InstanceIdentifier = "instance_identifier"sv; - constexpr std::string_view s_UnitInfoTable_Column_Type = "type"sv; - constexpr std::string_view s_UnitInfoTable_Column_Identifier = "identifier"sv; - constexpr std::string_view s_UnitInfoTable_Column_Intent = "intent"sv; - constexpr std::string_view s_UnitInfoTable_Column_Dependencies = "dependencies"sv; - constexpr std::string_view s_UnitInfoTable_Column_Metadata = "metadata"sv; - constexpr std::string_view s_UnitInfoTable_Column_Settings = "settings"sv; - constexpr std::string_view s_UnitInfoTable_Column_IsActive = "is_active"sv; - constexpr std::string_view s_UnitInfoTable_Column_IsGroup = "is_group"sv; - } - - UnitInfoTable::UnitInfoTable(Connection& connection) : m_connection(connection) {} - - void UnitInfoTable::Create() - { - Savepoint savepoint = Savepoint::Create(m_connection, "UnitInfoTable_Create_0_1"); - - StatementBuilder tableBuilder; - tableBuilder.CreateTable(s_UnitInfoTable_Table).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_UnitInfoTable_Column_SetRowId, Type::RowId).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_ParentRowId, Type::RowId), - ColumnBuilder(s_UnitInfoTable_Column_InstanceIdentifier, Type::Blob).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_Type, Type::Text).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_Identifier, Type::Text).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_Intent, Type::Int).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_Dependencies, Type::Text).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_Metadata, Type::Text).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_Settings, Type::Text).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_IsActive, Type::Bool).NotNull(), - ColumnBuilder(s_UnitInfoTable_Column_IsGroup, Type::Bool).NotNull(), - }); - - tableBuilder.Execute(m_connection); - - StatementBuilder indexBuilder; - indexBuilder.CreateIndex(s_UnitInfoTable_SetRowIdIndex).On(s_UnitInfoTable_Table).Columns(s_UnitInfoTable_Column_SetRowId); - - indexBuilder.Execute(m_connection); - - savepoint.Commit(); - } - - void UnitInfoTable::Add(const Configuration::ConfigurationUnit& configurationUnit, AppInstaller::SQLite::rowid_t setRowId, hstring schemaVersion) - { - Savepoint savepoint = Savepoint::Create(m_connection, "UnitInfoTable_Add_0_1"); - - StatementBuilder builder; - builder.InsertInto(s_UnitInfoTable_Table).Columns({ - s_UnitInfoTable_Column_SetRowId, - s_UnitInfoTable_Column_ParentRowId, - s_UnitInfoTable_Column_InstanceIdentifier, - s_UnitInfoTable_Column_Type, - s_UnitInfoTable_Column_Identifier, - s_UnitInfoTable_Column_Intent, - s_UnitInfoTable_Column_Dependencies, - s_UnitInfoTable_Column_Metadata, - s_UnitInfoTable_Column_Settings, - s_UnitInfoTable_Column_IsActive, - s_UnitInfoTable_Column_IsGroup, - }).Values( - Unbound, - Unbound, - Unbound, - Unbound, - Unbound, - Unbound, - Unbound, - Unbound, - Unbound, - Unbound, - Unbound - ); - - Statement insertStatement = builder.Prepare(m_connection); - - struct UnitsToInsert - { - std::optional Parent; - Configuration::ConfigurationUnit Unit; - }; - - std::queue unitsToInsert; - unitsToInsert.emplace(UnitsToInsert{ std::nullopt, configurationUnit }); - auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); - - while (!unitsToInsert.empty()) - { - const auto& current = unitsToInsert.front(); - - insertStatement.Reset(); - - bool isGroup = current.Unit.IsGroup(); - - insertStatement.Bind(1, setRowId); - insertStatement.Bind(2, current.Parent); - insertStatement.Bind(3, static_cast(current.Unit.InstanceIdentifier())); - insertStatement.Bind(4, ConvertToUTF8(current.Unit.Type())); - insertStatement.Bind(5, ConvertToUTF8(current.Unit.Identifier())); - insertStatement.Bind(6, AppInstaller::ToIntegral(current.Unit.Intent())); - insertStatement.Bind(7, serializer->SerializeStringArray(current.Unit.Dependencies())); - insertStatement.Bind(8, serializer->SerializeMetadataWithEnvironment(current.Unit.Metadata(), current.Unit.Environment())); - insertStatement.Bind(9, serializer->SerializeValueSet(current.Unit.Settings())); - insertStatement.Bind(10, current.Unit.IsActive()); - insertStatement.Bind(11, isGroup); - - insertStatement.Execute(); - - if (isGroup) - { - rowid_t currentRowId = m_connection.GetLastInsertRowID(); - - auto winrtUnits = current.Unit.Units(); - std::vector units{ winrtUnits.Size() }; - winrtUnits.GetMany(0, units); - - for (const auto& unit : units) - { - unitsToInsert.emplace(UnitsToInsert{ currentRowId, unit }); - } - } - - unitsToInsert.pop(); - } - - savepoint.Commit(); - } - - void UnitInfoTable::UpdateForSet(AppInstaller::SQLite::rowid_t target, const Windows::Foundation::Collections::IVector& winrtUnits, hstring schemaVersion) - { - Savepoint savepoint = Savepoint::Create(m_connection, "UnitInfoTable_UpdateForSet_0_1"); - - RemoveForSet(target); - - std::vector units{ winrtUnits.Size() }; - winrtUnits.GetMany(0, units); - - for (const auto& unit : units) - { - Add(unit, target, schemaVersion); - } - - savepoint.Commit(); - } - - void UnitInfoTable::RemoveForSet(AppInstaller::SQLite::rowid_t target) - { - StatementBuilder builder; - builder.DeleteFrom(s_UnitInfoTable_Table).Where(s_UnitInfoTable_Column_SetRowId).Equals(target); - builder.Execute(m_connection); - } - - std::vector UnitInfoTable::GetAllUnitsForSet(AppInstaller::SQLite::rowid_t setRowId, std::string_view schemaVersion) - { - StatementBuilder builder; - builder.Select({ - RowIDName, // 0 - s_UnitInfoTable_Column_ParentRowId, // 1 - s_UnitInfoTable_Column_InstanceIdentifier, // 2 - s_UnitInfoTable_Column_Type, // 3 - s_UnitInfoTable_Column_Identifier, // 4 - s_UnitInfoTable_Column_Intent, // 5 - s_UnitInfoTable_Column_Dependencies, // 6 - s_UnitInfoTable_Column_Metadata, // 7 - s_UnitInfoTable_Column_Settings, // 8 - s_UnitInfoTable_Column_IsActive, // 9 - s_UnitInfoTable_Column_IsGroup, // 10 - }).From(s_UnitInfoTable_Table).Where(s_UnitInfoTable_Column_SetRowId).Equals(setRowId); - - Statement statement = builder.Prepare(m_connection); - - std::vector result; - std::map rowToUnitMap; - auto parser = ConfigurationSetParser::CreateForSchemaVersion(std::string{ schemaVersion }); - - while (statement.Step()) - { - auto unit = make_self(statement.GetColumn(2)); - - unit->Type(hstring{ ConvertToUTF16(statement.GetColumn(3)) }); - unit->Identifier(hstring{ ConvertToUTF16(statement.GetColumn(4)) }); - unit->Intent(statement.GetColumn(5)); - unit->Dependencies(parser->ParseStringArray(statement.GetColumn(6))); - unit->Metadata(parser->ParseValueSet(statement.GetColumn(7))); - unit->Settings(parser->ParseValueSet(statement.GetColumn(8))); - unit->IsActive(statement.GetColumn(9)); - unit->IsGroup(statement.GetColumn(10)); - - parser->ExtractEnvironmentFromMetadata(unit->Metadata(), unit->EnvironmentInternal()); - - if (statement.GetColumnIsNull(1)) - { - result.emplace_back(unit); - } - else - { - rowToUnitMap.at(statement.GetColumn(1))->Units().Append(*unit); - } - - rowToUnitMap.emplace(statement.GetColumn(0), unit); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "UnitInfoTable.h" +#include "ConfigurationUnit.h" +#include "ConfigurationSetParser.h" +#include "ConfigurationSetSerializer.h" +#include +#include +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 +{ + namespace + { + constexpr std::string_view s_UnitInfoTable_Table = "unit_info"sv; + constexpr std::string_view s_UnitInfoTable_SetRowIdIndex = "unit_info_set_idx"sv; + + constexpr std::string_view s_UnitInfoTable_Column_SetRowId = "set_rowid"sv; + constexpr std::string_view s_UnitInfoTable_Column_ParentRowId = "parent_rowid"sv; + constexpr std::string_view s_UnitInfoTable_Column_InstanceIdentifier = "instance_identifier"sv; + constexpr std::string_view s_UnitInfoTable_Column_Type = "type"sv; + constexpr std::string_view s_UnitInfoTable_Column_Identifier = "identifier"sv; + constexpr std::string_view s_UnitInfoTable_Column_Intent = "intent"sv; + constexpr std::string_view s_UnitInfoTable_Column_Dependencies = "dependencies"sv; + constexpr std::string_view s_UnitInfoTable_Column_Metadata = "metadata"sv; + constexpr std::string_view s_UnitInfoTable_Column_Settings = "settings"sv; + constexpr std::string_view s_UnitInfoTable_Column_IsActive = "is_active"sv; + constexpr std::string_view s_UnitInfoTable_Column_IsGroup = "is_group"sv; + } + + UnitInfoTable::UnitInfoTable(Connection& connection) : m_connection(connection) {} + + void UnitInfoTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "UnitInfoTable_Create_0_1"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_UnitInfoTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_UnitInfoTable_Column_SetRowId, Type::RowId).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_ParentRowId, Type::RowId), + ColumnBuilder(s_UnitInfoTable_Column_InstanceIdentifier, Type::Blob).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_Type, Type::Text).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_Identifier, Type::Text).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_Intent, Type::Int).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_Dependencies, Type::Text).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_Metadata, Type::Text).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_Settings, Type::Text).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_IsActive, Type::Bool).NotNull(), + ColumnBuilder(s_UnitInfoTable_Column_IsGroup, Type::Bool).NotNull(), + }); + + tableBuilder.Execute(m_connection); + + StatementBuilder indexBuilder; + indexBuilder.CreateIndex(s_UnitInfoTable_SetRowIdIndex).On(s_UnitInfoTable_Table).Columns(s_UnitInfoTable_Column_SetRowId); + + indexBuilder.Execute(m_connection); + + savepoint.Commit(); + } + + void UnitInfoTable::Add(const Configuration::ConfigurationUnit& configurationUnit, AppInstaller::SQLite::rowid_t setRowId, hstring schemaVersion) + { + Savepoint savepoint = Savepoint::Create(m_connection, "UnitInfoTable_Add_0_1"); + + StatementBuilder builder; + builder.InsertInto(s_UnitInfoTable_Table).Columns({ + s_UnitInfoTable_Column_SetRowId, + s_UnitInfoTable_Column_ParentRowId, + s_UnitInfoTable_Column_InstanceIdentifier, + s_UnitInfoTable_Column_Type, + s_UnitInfoTable_Column_Identifier, + s_UnitInfoTable_Column_Intent, + s_UnitInfoTable_Column_Dependencies, + s_UnitInfoTable_Column_Metadata, + s_UnitInfoTable_Column_Settings, + s_UnitInfoTable_Column_IsActive, + s_UnitInfoTable_Column_IsGroup, + }).Values( + Unbound, + Unbound, + Unbound, + Unbound, + Unbound, + Unbound, + Unbound, + Unbound, + Unbound, + Unbound, + Unbound + ); + + Statement insertStatement = builder.Prepare(m_connection); + + struct UnitsToInsert + { + std::optional Parent; + Configuration::ConfigurationUnit Unit; + }; + + std::queue unitsToInsert; + unitsToInsert.emplace(UnitsToInsert{ std::nullopt, configurationUnit }); + auto serializer = ConfigurationSetSerializer::CreateSerializer(schemaVersion); + + while (!unitsToInsert.empty()) + { + const auto& current = unitsToInsert.front(); + + insertStatement.Reset(); + + bool isGroup = current.Unit.IsGroup(); + + insertStatement.Bind(1, setRowId); + insertStatement.Bind(2, current.Parent); + insertStatement.Bind(3, static_cast(current.Unit.InstanceIdentifier())); + insertStatement.Bind(4, ConvertToUTF8(current.Unit.Type())); + insertStatement.Bind(5, ConvertToUTF8(current.Unit.Identifier())); + insertStatement.Bind(6, AppInstaller::ToIntegral(current.Unit.Intent())); + insertStatement.Bind(7, serializer->SerializeStringArray(current.Unit.Dependencies())); + insertStatement.Bind(8, serializer->SerializeMetadataWithEnvironment(current.Unit.Metadata(), current.Unit.Environment())); + insertStatement.Bind(9, serializer->SerializeValueSet(current.Unit.Settings())); + insertStatement.Bind(10, current.Unit.IsActive()); + insertStatement.Bind(11, isGroup); + + insertStatement.Execute(); + + if (isGroup) + { + rowid_t currentRowId = m_connection.GetLastInsertRowID(); + + auto winrtUnits = current.Unit.Units(); + std::vector units{ winrtUnits.Size() }; + winrtUnits.GetMany(0, units); + + for (const auto& unit : units) + { + unitsToInsert.emplace(UnitsToInsert{ currentRowId, unit }); + } + } + + unitsToInsert.pop(); + } + + savepoint.Commit(); + } + + void UnitInfoTable::UpdateForSet(AppInstaller::SQLite::rowid_t target, const Windows::Foundation::Collections::IVector& winrtUnits, hstring schemaVersion) + { + Savepoint savepoint = Savepoint::Create(m_connection, "UnitInfoTable_UpdateForSet_0_1"); + + RemoveForSet(target); + + std::vector units{ winrtUnits.Size() }; + winrtUnits.GetMany(0, units); + + for (const auto& unit : units) + { + Add(unit, target, schemaVersion); + } + + savepoint.Commit(); + } + + void UnitInfoTable::RemoveForSet(AppInstaller::SQLite::rowid_t target) + { + StatementBuilder builder; + builder.DeleteFrom(s_UnitInfoTable_Table).Where(s_UnitInfoTable_Column_SetRowId).Equals(target); + builder.Execute(m_connection); + } + + std::vector UnitInfoTable::GetAllUnitsForSet(AppInstaller::SQLite::rowid_t setRowId, std::string_view schemaVersion) + { + StatementBuilder builder; + builder.Select({ + RowIDName, // 0 + s_UnitInfoTable_Column_ParentRowId, // 1 + s_UnitInfoTable_Column_InstanceIdentifier, // 2 + s_UnitInfoTable_Column_Type, // 3 + s_UnitInfoTable_Column_Identifier, // 4 + s_UnitInfoTable_Column_Intent, // 5 + s_UnitInfoTable_Column_Dependencies, // 6 + s_UnitInfoTable_Column_Metadata, // 7 + s_UnitInfoTable_Column_Settings, // 8 + s_UnitInfoTable_Column_IsActive, // 9 + s_UnitInfoTable_Column_IsGroup, // 10 + }).From(s_UnitInfoTable_Table).Where(s_UnitInfoTable_Column_SetRowId).Equals(setRowId); + + Statement statement = builder.Prepare(m_connection); + + std::vector result; + std::map rowToUnitMap; + auto parser = ConfigurationSetParser::CreateForSchemaVersion(std::string{ schemaVersion }); + + while (statement.Step()) + { + auto unit = make_self(statement.GetColumn(2)); + + unit->Type(hstring{ ConvertToUTF16(statement.GetColumn(3)) }); + unit->Identifier(hstring{ ConvertToUTF16(statement.GetColumn(4)) }); + unit->Intent(statement.GetColumn(5)); + unit->Dependencies(parser->ParseStringArray(statement.GetColumn(6))); + unit->Metadata(parser->ParseValueSet(statement.GetColumn(7))); + unit->Settings(parser->ParseValueSet(statement.GetColumn(8))); + unit->IsActive(statement.GetColumn(9)); + unit->IsGroup(statement.GetColumn(10)); + + parser->ExtractEnvironmentFromMetadata(unit->Metadata(), unit->EnvironmentInternal()); + + if (statement.GetColumnIsNull(1)) + { + result.emplace_back(unit); + } + else + { + rowToUnitMap.at(statement.GetColumn(1))->Units().Append(*unit); + } + + rowToUnitMap.emplace(statement.GetColumn(0), unit); + } + + return result; + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.h index 1bce7700fe..155b3d61c1 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_1/UnitInfoTable.h @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" -#include "Database/Schema/IConfigurationDatabase.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 -{ - struct UnitInfoTable - { - UnitInfoTable(AppInstaller::SQLite::Connection& connection); - - // Creates the unit info table. - void Create(); - - // Adds the given configuration unit to the table. - void Add(const Configuration::ConfigurationUnit& configurationUnit, AppInstaller::SQLite::rowid_t setRowId, hstring schemaVersion); - - // Updates the units for the target set. - void UpdateForSet(AppInstaller::SQLite::rowid_t target, const Windows::Foundation::Collections::IVector& units, hstring schemaVersion); - - // Removes the units from the target set. - void RemoveForSet(AppInstaller::SQLite::rowid_t target); - - // Gets all of the units for the given set. - std::vector GetAllUnitsForSet(AppInstaller::SQLite::rowid_t setRowId, std::string_view schemaVersion); - - private: - AppInstaller::SQLite::Connection& m_connection; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "Database/Schema/IConfigurationDatabase.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_1 +{ + struct UnitInfoTable + { + UnitInfoTable(AppInstaller::SQLite::Connection& connection); + + // Creates the unit info table. + void Create(); + + // Adds the given configuration unit to the table. + void Add(const Configuration::ConfigurationUnit& configurationUnit, AppInstaller::SQLite::rowid_t setRowId, hstring schemaVersion); + + // Updates the units for the target set. + void UpdateForSet(AppInstaller::SQLite::rowid_t target, const Windows::Foundation::Collections::IVector& units, hstring schemaVersion); + + // Removes the units from the target set. + void RemoveForSet(AppInstaller::SQLite::rowid_t target); + + // Gets all of the units for the given set. + std::vector GetAllUnitsForSet(AppInstaller::SQLite::rowid_t setRowId, std::string_view schemaVersion); + + private: + AppInstaller::SQLite::Connection& m_connection; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface.h b/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface.h index 9ce330c2b4..e89666bca2 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface.h @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Database/Schema/IConfigurationDatabase.h" -#include "Database/Schema/0_1/Interface.h" - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 -{ - struct Interface : public V0_1::Interface - { - using V0_1::Interface::Interface; - - const AppInstaller::SQLite::Version& GetSchemaVersion() override; - - // Version 0.1 - void InitializeDatabase() override; - - // Version 0.2 - bool MigrateFrom(IConfigurationDatabase* current) override; - void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) override; - void SetActiveQueueItem(const std::string& objectName) override; - std::vector> GetQueueItems() override; - void RemoveQueueItem(const std::string& objectName) override; - - private: - // Unconditionally attempts to migrate from the 0.1 base. - void MigrateFrom0_1(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/Schema/IConfigurationDatabase.h" +#include "Database/Schema/0_1/Interface.h" + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 +{ + struct Interface : public V0_1::Interface + { + using V0_1::Interface::Interface; + + const AppInstaller::SQLite::Version& GetSchemaVersion() override; + + // Version 0.1 + void InitializeDatabase() override; + + // Version 0.2 + bool MigrateFrom(IConfigurationDatabase* current) override; + void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) override; + void SetActiveQueueItem(const std::string& objectName) override; + std::vector> GetQueueItems() override; + void RemoveQueueItem(const std::string& objectName) override; + + private: + // Unconditionally attempts to migrate from the 0.1 base. + void MigrateFrom0_1(); + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface_0_2.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface_0_2.cpp index 3825d6e769..4998377093 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface_0_2.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_2/Interface_0_2.cpp @@ -1,83 +1,83 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Interface.h" -#include "QueueTable.h" -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 -{ - static constexpr AppInstaller::SQLite::Version s_InterfaceVersion{ 0, 2 }; - - const AppInstaller::SQLite::Version& Interface::GetSchemaVersion() - { - return s_InterfaceVersion; - } - - void Interface::InitializeDatabase() - { - V0_1::Interface::InitializeDatabase(); - - Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_2"); - MigrateFrom0_1(); - savepoint.Commit(); - } - - bool Interface::MigrateFrom(IConfigurationDatabase* current) - { - auto currentSchemaVersion = current->GetSchemaVersion(); - if (currentSchemaVersion < s_InterfaceVersion) - { - if (V0_1::Interface::MigrateFrom(current)) - { - Savepoint savepoint = Savepoint::Create(*m_storage, "MigrateFrom0_1"); - - MigrateFrom0_1(); - s_InterfaceVersion.SetSchemaVersion(*m_storage); - - savepoint.Commit(); - - return true; - } - } - else if (currentSchemaVersion == s_InterfaceVersion) - { - return true; - } - - return false; - } - - void Interface::AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) - { - QueueTable queueTable(*m_storage); - queueTable.AddQueueItemWithoutProcess(instanceIdentifier, objectName); - } - - void Interface::SetActiveQueueItem(const std::string& objectName) - { - QueueTable queueTable(*m_storage); - queueTable.SetActiveQueueItem(objectName); - } - - std::vector> Interface::GetQueueItems() - { - QueueTable queueTable(*m_storage); - return queueTable.GetQueueItemsWithoutProcess(); - } - - void Interface::RemoveQueueItem(const std::string& objectName) - { - QueueTable queueTable(*m_storage); - queueTable.RemoveQueueItem(objectName); - } - - void Interface::MigrateFrom0_1() - { - QueueTable queueTable(*m_storage); - queueTable.Create(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Interface.h" +#include "QueueTable.h" +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 +{ + static constexpr AppInstaller::SQLite::Version s_InterfaceVersion{ 0, 2 }; + + const AppInstaller::SQLite::Version& Interface::GetSchemaVersion() + { + return s_InterfaceVersion; + } + + void Interface::InitializeDatabase() + { + V0_1::Interface::InitializeDatabase(); + + Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_2"); + MigrateFrom0_1(); + savepoint.Commit(); + } + + bool Interface::MigrateFrom(IConfigurationDatabase* current) + { + auto currentSchemaVersion = current->GetSchemaVersion(); + if (currentSchemaVersion < s_InterfaceVersion) + { + if (V0_1::Interface::MigrateFrom(current)) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "MigrateFrom0_1"); + + MigrateFrom0_1(); + s_InterfaceVersion.SetSchemaVersion(*m_storage); + + savepoint.Commit(); + + return true; + } + } + else if (currentSchemaVersion == s_InterfaceVersion) + { + return true; + } + + return false; + } + + void Interface::AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) + { + QueueTable queueTable(*m_storage); + queueTable.AddQueueItemWithoutProcess(instanceIdentifier, objectName); + } + + void Interface::SetActiveQueueItem(const std::string& objectName) + { + QueueTable queueTable(*m_storage); + queueTable.SetActiveQueueItem(objectName); + } + + std::vector> Interface::GetQueueItems() + { + QueueTable queueTable(*m_storage); + return queueTable.GetQueueItemsWithoutProcess(); + } + + void Interface::RemoveQueueItem(const std::string& objectName) + { + QueueTable queueTable(*m_storage); + queueTable.RemoveQueueItem(objectName); + } + + void Interface::MigrateFrom0_1() + { + QueueTable queueTable(*m_storage); + queueTable.Create(); + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp index adcd41ccc4..2acad68092 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.cpp @@ -1,155 +1,155 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "QueueTable.h" -#include -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::SQLite::Builder; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 -{ - namespace - { - constexpr std::string_view s_QueueTable_Table = "queue"sv; - - constexpr std::string_view s_QueueTable_Column_SetInstanceIdentifier = "set_instance_identifier"sv; - constexpr std::string_view s_QueueTable_Column_ObjectName = "object_name"sv; - constexpr std::string_view s_QueueTable_Column_QueuedAt = "queued_at"sv; - constexpr std::string_view s_QueueTable_Column_Active = "active"sv; - constexpr std::string_view s_QueueTable_Column_Process = "process"sv; - } - - QueueTable::QueueTable(Connection& connection) : m_connection(connection) {} - - void QueueTable::Create() - { - Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_Create_0_2"); - - StatementBuilder tableBuilder; - tableBuilder.CreateTable(s_QueueTable_Table).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_QueueTable_Column_SetInstanceIdentifier, Type::Blob).NotNull(), - ColumnBuilder(s_QueueTable_Column_ObjectName, Type::Text).Unique().NotNull(), - ColumnBuilder(s_QueueTable_Column_QueuedAt, Type::Int64).NotNull(), - ColumnBuilder(s_QueueTable_Column_Active, Type::Bool).NotNull(), - }); - - tableBuilder.Execute(m_connection); - - savepoint.Commit(); - } - - void QueueTable::AddProcessColumn() - { - Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_AddProcessColumn_0_3"); - - StatementBuilder builder; - builder.AlterTable(s_QueueTable_Table).Add(s_QueueTable_Column_Process, Type::Int64).NotNull().Default(0); - - builder.Execute(m_connection); - - savepoint.Commit(); - } - - void QueueTable::AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName) - { - StatementBuilder builder; - builder.InsertInto(s_QueueTable_Table).Columns({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active - }).Values( - instanceIdentifier, - objectName, - GetCurrentUnixEpoch(), - false - ); - - builder.Execute(m_connection); - } - - void QueueTable::AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName) - { - StatementBuilder builder; - builder.InsertInto(s_QueueTable_Table).Columns({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active, - s_QueueTable_Column_Process - }).Values( - instanceIdentifier, - objectName, - GetCurrentUnixEpoch(), - false, - static_cast(GetCurrentProcessId()) - ); - - builder.Execute(m_connection); - } - - void QueueTable::SetActiveQueueItem(const std::string& objectName) - { - StatementBuilder builder; - builder.Update(s_QueueTable_Table).Set().Column(s_QueueTable_Column_Active).Equals(true).Where(s_QueueTable_Column_ObjectName).Equals(objectName); - - builder.Execute(m_connection); - } - - std::vector> QueueTable::GetQueueItemsWithoutProcess() - { - StatementBuilder builder; - builder.Select({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active - }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); - - Statement statement = builder.Prepare(m_connection); - - std::vector> result; - - while (statement.Step()) - { - result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), 0, statement.GetColumn(3))); - } - - return result; - } - - std::vector> QueueTable::GetQueueItemsWithProcess() - { - StatementBuilder builder; - builder.Select({ - s_QueueTable_Column_SetInstanceIdentifier, - s_QueueTable_Column_ObjectName, - s_QueueTable_Column_QueuedAt, - s_QueueTable_Column_Active, - s_QueueTable_Column_Process - }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); - - Statement statement = builder.Prepare(m_connection); - - std::vector> result; - - while (statement.Step()) - { - result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), static_cast(statement.GetColumn(4)), statement.GetColumn(3))); - } - - return result; - } - - void QueueTable::RemoveQueueItem(const std::string& objectName) - { - StatementBuilder builder; - builder.DeleteFrom(s_QueueTable_Table).Where(s_QueueTable_Column_ObjectName).Equals(objectName); - - builder.Execute(m_connection); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "QueueTable.h" +#include +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 +{ + namespace + { + constexpr std::string_view s_QueueTable_Table = "queue"sv; + + constexpr std::string_view s_QueueTable_Column_SetInstanceIdentifier = "set_instance_identifier"sv; + constexpr std::string_view s_QueueTable_Column_ObjectName = "object_name"sv; + constexpr std::string_view s_QueueTable_Column_QueuedAt = "queued_at"sv; + constexpr std::string_view s_QueueTable_Column_Active = "active"sv; + constexpr std::string_view s_QueueTable_Column_Process = "process"sv; + } + + QueueTable::QueueTable(Connection& connection) : m_connection(connection) {} + + void QueueTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_Create_0_2"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_QueueTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_QueueTable_Column_SetInstanceIdentifier, Type::Blob).NotNull(), + ColumnBuilder(s_QueueTable_Column_ObjectName, Type::Text).Unique().NotNull(), + ColumnBuilder(s_QueueTable_Column_QueuedAt, Type::Int64).NotNull(), + ColumnBuilder(s_QueueTable_Column_Active, Type::Bool).NotNull(), + }); + + tableBuilder.Execute(m_connection); + + savepoint.Commit(); + } + + void QueueTable::AddProcessColumn() + { + Savepoint savepoint = Savepoint::Create(m_connection, "QueueTable_AddProcessColumn_0_3"); + + StatementBuilder builder; + builder.AlterTable(s_QueueTable_Table).Add(s_QueueTable_Column_Process, Type::Int64).NotNull().Default(0); + + builder.Execute(m_connection); + + savepoint.Commit(); + } + + void QueueTable::AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName) + { + StatementBuilder builder; + builder.InsertInto(s_QueueTable_Table).Columns({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active + }).Values( + instanceIdentifier, + objectName, + GetCurrentUnixEpoch(), + false + ); + + builder.Execute(m_connection); + } + + void QueueTable::AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName) + { + StatementBuilder builder; + builder.InsertInto(s_QueueTable_Table).Columns({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active, + s_QueueTable_Column_Process + }).Values( + instanceIdentifier, + objectName, + GetCurrentUnixEpoch(), + false, + static_cast(GetCurrentProcessId()) + ); + + builder.Execute(m_connection); + } + + void QueueTable::SetActiveQueueItem(const std::string& objectName) + { + StatementBuilder builder; + builder.Update(s_QueueTable_Table).Set().Column(s_QueueTable_Column_Active).Equals(true).Where(s_QueueTable_Column_ObjectName).Equals(objectName); + + builder.Execute(m_connection); + } + + std::vector> QueueTable::GetQueueItemsWithoutProcess() + { + StatementBuilder builder; + builder.Select({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active + }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); + + Statement statement = builder.Prepare(m_connection); + + std::vector> result; + + while (statement.Step()) + { + result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), 0, statement.GetColumn(3))); + } + + return result; + } + + std::vector> QueueTable::GetQueueItemsWithProcess() + { + StatementBuilder builder; + builder.Select({ + s_QueueTable_Column_SetInstanceIdentifier, + s_QueueTable_Column_ObjectName, + s_QueueTable_Column_QueuedAt, + s_QueueTable_Column_Active, + s_QueueTable_Column_Process + }).From(s_QueueTable_Table).OrderBy({ s_QueueTable_Column_QueuedAt, RowIDName }); + + Statement statement = builder.Prepare(m_connection); + + std::vector> result; + + while (statement.Step()) + { + result.emplace_back(std::make_tuple(statement.GetColumn(0), statement.GetColumn(1), ConvertUnixEpochToSystemClock(statement.GetColumn(2)), static_cast(statement.GetColumn(4)), statement.GetColumn(3))); + } + + return result; + } + + void QueueTable::RemoveQueueItem(const std::string& objectName) + { + StatementBuilder builder; + builder.DeleteFrom(s_QueueTable_Table).Where(s_QueueTable_Column_ObjectName).Equals(objectName); + + builder.Execute(m_connection); + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.h index b2efb9e7db..f69ff5d112 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_2/QueueTable.h @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 -{ - struct QueueTable - { - QueueTable(AppInstaller::SQLite::Connection& connection); - - // Creates the queue table. - void Create(); - - // Adds the process column to the table. - void AddProcessColumn(); - - // Adds a new queue item for the given configuration set and object name. - void AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName); - - // Adds a new queue item for the given configuration set and object name. - void AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName); - - // Sets the queue item with the given object name as active. - void SetActiveQueueItem(const std::string& objectName); - - // Gets all queue items in queue order (item at index 0 is active/next). - std::vector> GetQueueItemsWithoutProcess(); - - // Gets all queue items in queue order (item at index 0 is active/next). - std::vector> GetQueueItemsWithProcess(); - - // Removes the queue item with the given object name. - void RemoveQueueItem(const std::string& objectName); - - private: - AppInstaller::SQLite::Connection& m_connection; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_2 +{ + struct QueueTable + { + QueueTable(AppInstaller::SQLite::Connection& connection); + + // Creates the queue table. + void Create(); + + // Adds the process column to the table. + void AddProcessColumn(); + + // Adds a new queue item for the given configuration set and object name. + void AddQueueItemWithoutProcess(const GUID& instanceIdentifier, const std::string& objectName); + + // Adds a new queue item for the given configuration set and object name. + void AddQueueItemWithProcess(const GUID& instanceIdentifier, const std::string& objectName); + + // Sets the queue item with the given object name as active. + void SetActiveQueueItem(const std::string& objectName); + + // Gets all queue items in queue order (item at index 0 is active/next). + std::vector> GetQueueItemsWithoutProcess(); + + // Gets all queue items in queue order (item at index 0 is active/next). + std::vector> GetQueueItemsWithProcess(); + + // Removes the queue item with the given object name. + void RemoveQueueItem(const std::string& objectName); + + private: + AppInstaller::SQLite::Connection& m_connection; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.cpp index bc735d3a2a..8d1330cf64 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.cpp @@ -1,86 +1,86 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ChangeListenerTable.h" -#include -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::SQLite::Builder; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 -{ - namespace - { - constexpr std::string_view s_ChangeListenerTable_Table = "change_listeners"sv; - - constexpr std::string_view s_ChangeListenerTable_Column_ObjectName = "object_name"sv; - constexpr std::string_view s_ChangeListenerTable_Column_StartedAt = "started_at"sv; - constexpr std::string_view s_ChangeListenerTable_Column_Process = "process"sv; - } - - ChangeListenerTable::ChangeListenerTable(Connection& connection) : m_connection(connection) {} - - void ChangeListenerTable::Create() - { - Savepoint savepoint = Savepoint::Create(m_connection, "ChangeListenerTable_Create_0_3"); - - StatementBuilder tableBuilder; - tableBuilder.CreateTable(s_ChangeListenerTable_Table).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_ChangeListenerTable_Column_ObjectName, Type::Text).Unique().NotNull(), - ColumnBuilder(s_ChangeListenerTable_Column_StartedAt, Type::Int64).NotNull(), - ColumnBuilder(s_ChangeListenerTable_Column_Process, Type::Int64).NotNull(), - }); - - tableBuilder.Execute(m_connection); - - savepoint.Commit(); - } - - void ChangeListenerTable::AddChangeListener(const std::string& objectName) - { - StatementBuilder builder; - builder.InsertInto(s_ChangeListenerTable_Table).Columns({ - s_ChangeListenerTable_Column_ObjectName, - s_ChangeListenerTable_Column_StartedAt, - s_ChangeListenerTable_Column_Process - }).Values( - objectName, - GetCurrentUnixEpoch(), - static_cast(GetCurrentProcessId()) - ); - - builder.Execute(m_connection); - } - - void ChangeListenerTable::RemoveChangeListener(const std::string& objectName) - { - StatementBuilder builder; - builder.DeleteFrom(s_ChangeListenerTable_Table).Where(s_ChangeListenerTable_Column_ObjectName).Equals(objectName); - - builder.Execute(m_connection); - } - - std::vector> ChangeListenerTable::GetChangeListeners() - { - StatementBuilder builder; - builder.Select({ - s_ChangeListenerTable_Column_ObjectName, - s_ChangeListenerTable_Column_StartedAt, - s_ChangeListenerTable_Column_Process - }).From(s_ChangeListenerTable_Table); - - Statement statement = builder.Prepare(m_connection); - - std::vector> result; - - while (statement.Step()) - { - result.emplace_back(std::make_tuple(statement.GetColumn(0), ConvertUnixEpochToSystemClock(statement.GetColumn(1)), static_cast(statement.GetColumn(2)))); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ChangeListenerTable.h" +#include +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + namespace + { + constexpr std::string_view s_ChangeListenerTable_Table = "change_listeners"sv; + + constexpr std::string_view s_ChangeListenerTable_Column_ObjectName = "object_name"sv; + constexpr std::string_view s_ChangeListenerTable_Column_StartedAt = "started_at"sv; + constexpr std::string_view s_ChangeListenerTable_Column_Process = "process"sv; + } + + ChangeListenerTable::ChangeListenerTable(Connection& connection) : m_connection(connection) {} + + void ChangeListenerTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "ChangeListenerTable_Create_0_3"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_ChangeListenerTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_ChangeListenerTable_Column_ObjectName, Type::Text).Unique().NotNull(), + ColumnBuilder(s_ChangeListenerTable_Column_StartedAt, Type::Int64).NotNull(), + ColumnBuilder(s_ChangeListenerTable_Column_Process, Type::Int64).NotNull(), + }); + + tableBuilder.Execute(m_connection); + + savepoint.Commit(); + } + + void ChangeListenerTable::AddChangeListener(const std::string& objectName) + { + StatementBuilder builder; + builder.InsertInto(s_ChangeListenerTable_Table).Columns({ + s_ChangeListenerTable_Column_ObjectName, + s_ChangeListenerTable_Column_StartedAt, + s_ChangeListenerTable_Column_Process + }).Values( + objectName, + GetCurrentUnixEpoch(), + static_cast(GetCurrentProcessId()) + ); + + builder.Execute(m_connection); + } + + void ChangeListenerTable::RemoveChangeListener(const std::string& objectName) + { + StatementBuilder builder; + builder.DeleteFrom(s_ChangeListenerTable_Table).Where(s_ChangeListenerTable_Column_ObjectName).Equals(objectName); + + builder.Execute(m_connection); + } + + std::vector> ChangeListenerTable::GetChangeListeners() + { + StatementBuilder builder; + builder.Select({ + s_ChangeListenerTable_Column_ObjectName, + s_ChangeListenerTable_Column_StartedAt, + s_ChangeListenerTable_Column_Process + }).From(s_ChangeListenerTable_Table); + + Statement statement = builder.Prepare(m_connection); + + std::vector> result; + + while (statement.Step()) + { + result.emplace_back(std::make_tuple(statement.GetColumn(0), ConvertUnixEpochToSystemClock(statement.GetColumn(1)), static_cast(statement.GetColumn(2)))); + } + + return result; + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.h index 30fad84e9b..43fbcb39c7 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_3/ChangeListenerTable.h @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 -{ - struct ChangeListenerTable - { - ChangeListenerTable(AppInstaller::SQLite::Connection& connection); - - // Creates the table. - void Create(); - - // Adds a new change listener to the table. - void AddChangeListener(const std::string& objectName); - - // Removes the change listener with the given name from the table. - void RemoveChangeListener(const std::string& objectName); - - // Gets all change listeners. - std::vector> GetChangeListeners(); - - private: - AppInstaller::SQLite::Connection& m_connection; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + struct ChangeListenerTable + { + ChangeListenerTable(AppInstaller::SQLite::Connection& connection); + + // Creates the table. + void Create(); + + // Adds a new change listener to the table. + void AddChangeListener(const std::string& objectName); + + // Removes the change listener with the given name from the table. + void RemoveChangeListener(const std::string& objectName); + + // Gets all change listeners. + std::vector> GetChangeListeners(); + + private: + AppInstaller::SQLite::Connection& m_connection; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface.h b/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface.h index 6c7d33443a..c9cb7cd436 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface.h @@ -1,44 +1,44 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Database/Schema/IConfigurationDatabase.h" -#include "Database/Schema/0_2/Interface.h" - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 -{ - struct Interface : public V0_2::Interface - { - using V0_2::Interface::Interface; - - const AppInstaller::SQLite::Version& GetSchemaVersion() override; - - // Version 0.1 - void InitializeDatabase() override; - void RemoveSet(AppInstaller::SQLite::rowid_t target) override; - - // Version 0.2 - bool MigrateFrom(IConfigurationDatabase* current) override; - void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) override; - std::vector> GetQueueItems() override; - - // Version 0.3 - std::vector GetStatusSince(int64_t changeIdentifier) override; - std::tuple> GetStatusBaseline() override; - void AddListener(const std::string& objectName) override; - void RemoveListener(const std::string& objectName) override; - std::vector> GetChangeListeners() override; - void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) override; - void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) override; - void UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData) override; - ConfigurationSetState GetSetState(const guid& instanceIdentifier) override; - std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier) override; - std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier) override; - std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier) override; - ConfigurationUnitState GetUnitState(const guid& instanceIdentifier) override; - std::optional> GetUnitResultInformation(const guid& instanceIdentifier) override; - - private: - // Unconditionally attempts to migrate from the 0.2 base. - void MigrateFrom0_2(); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/Schema/IConfigurationDatabase.h" +#include "Database/Schema/0_2/Interface.h" + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + struct Interface : public V0_2::Interface + { + using V0_2::Interface::Interface; + + const AppInstaller::SQLite::Version& GetSchemaVersion() override; + + // Version 0.1 + void InitializeDatabase() override; + void RemoveSet(AppInstaller::SQLite::rowid_t target) override; + + // Version 0.2 + bool MigrateFrom(IConfigurationDatabase* current) override; + void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) override; + std::vector> GetQueueItems() override; + + // Version 0.3 + std::vector GetStatusSince(int64_t changeIdentifier) override; + std::tuple> GetStatusBaseline() override; + void AddListener(const std::string& objectName) override; + void RemoveListener(const std::string& objectName) override; + std::vector> GetChangeListeners() override; + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) override; + void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) override; + void UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData) override; + ConfigurationSetState GetSetState(const guid& instanceIdentifier) override; + std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier) override; + std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier) override; + std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier) override; + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier) override; + std::optional> GetUnitResultInformation(const guid& instanceIdentifier) override; + + private: + // Unconditionally attempts to migrate from the 0.2 base. + void MigrateFrom0_2(); + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface_0_3.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface_0_3.cpp index b9d56f1a36..b1ebf0e8bb 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface_0_3.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_3/Interface_0_3.cpp @@ -1,176 +1,176 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Interface.h" -#include "Database/Schema/0_1/SetInfoTable.h" -#include "Database/Schema/0_2/QueueTable.h" -#include "StatusItemTable.h" -#include "ChangeListenerTable.h" -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 -{ - static constexpr AppInstaller::SQLite::Version s_InterfaceVersion{ 0, 3 }; - - const AppInstaller::SQLite::Version& Interface::GetSchemaVersion() - { - return s_InterfaceVersion; - } - - void Interface::InitializeDatabase() - { - V0_2::Interface::InitializeDatabase(); - - Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_3"); - MigrateFrom0_2(); - savepoint.Commit(); - } - - void Interface::RemoveSet(AppInstaller::SQLite::rowid_t target) - { - Savepoint savepoint = Savepoint::Create(*m_storage, "RemoveSet_0_3"); - - V0_2::Interface::RemoveSet(target); - - StatusItemTable statusItemTable(*m_storage); - statusItemTable.RemoveForSet(target); - - savepoint.Commit(); - } - - bool Interface::MigrateFrom(IConfigurationDatabase* current) - { - auto currentSchemaVersion = current->GetSchemaVersion(); - if (currentSchemaVersion < s_InterfaceVersion) - { - if (V0_2::Interface::MigrateFrom(current)) - { - Savepoint savepoint = Savepoint::Create(*m_storage, "MigrateFrom0_2"); - - MigrateFrom0_2(); - s_InterfaceVersion.SetSchemaVersion(*m_storage); - - savepoint.Commit(); - - return true; - } - } - else if (currentSchemaVersion == s_InterfaceVersion) - { - return true; - } - - return false; - } - - void Interface::AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) - { - V0_2::QueueTable queueTable(*m_storage); - queueTable.AddQueueItemWithProcess(instanceIdentifier, objectName); - } - - std::vector> Interface::GetQueueItems() - { - V0_2::QueueTable queueTable(*m_storage); - return queueTable.GetQueueItemsWithProcess(); - } - - void Interface::MigrateFrom0_2() - { - V0_2::QueueTable queueTable(*m_storage); - queueTable.AddProcessColumn(); - - ChangeListenerTable changeListenerTable(*m_storage); - changeListenerTable.Create(); - - StatusItemTable statusItemTable(*m_storage); - statusItemTable.Create(); - } - - std::vector Interface::GetStatusSince(int64_t changeIdentifier) - { - StatusItemTable statusItemTable(*m_storage); - return statusItemTable.GetStatusSince(changeIdentifier); - } - - std::tuple> Interface::GetStatusBaseline() - { - StatusItemTable statusItemTable(*m_storage); - return statusItemTable.GetStatusBaseline(); - } - - void Interface::AddListener(const std::string& objectName) - { - ChangeListenerTable changeListenerTable(*m_storage); - changeListenerTable.AddChangeListener(objectName); - } - - void Interface::RemoveListener(const std::string& objectName) - { - ChangeListenerTable changeListenerTable(*m_storage); - changeListenerTable.RemoveChangeListener(objectName); - } - - std::vector> Interface::GetChangeListeners() - { - ChangeListenerTable changeListenerTable(*m_storage); - return changeListenerTable.GetChangeListeners(); - } - - void Interface::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) - { - StatusItemTable statusItemTable(*m_storage); - statusItemTable.UpdateSetState(setInstanceIdentifier, state); - } - - void Interface::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) - { - StatusItemTable statusItemTable(*m_storage); - statusItemTable.UpdateSetInQueue(setInstanceIdentifier, inQueue); - } - - void Interface::UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData) - { - StatusItemTable statusItemTable(*m_storage); - statusItemTable.UpdateUnitState(setInstanceIdentifier, changeData); - } - - ConfigurationSetState Interface::GetSetState(const guid& instanceIdentifier) - { - StatusItemTable statusItemTable(*m_storage); - return statusItemTable.GetSetState(instanceIdentifier); - } - - std::chrono::system_clock::time_point Interface::GetSetFirstApply(const guid& instanceIdentifier) - { - V0_1::SetInfoTable setInfoTable(*m_storage); - return setInfoTable.GetSetFirstApply(instanceIdentifier); - } - - std::chrono::system_clock::time_point Interface::GetSetApplyBegun(const guid& instanceIdentifier) - { - StatusItemTable statusItemTable(*m_storage); - return statusItemTable.GetSetApplyBegun(instanceIdentifier); - } - - std::chrono::system_clock::time_point Interface::GetSetApplyEnded(const guid& instanceIdentifier) - { - StatusItemTable statusItemTable(*m_storage); - return statusItemTable.GetSetApplyEnded(instanceIdentifier); - } - - ConfigurationUnitState Interface::GetUnitState(const guid& instanceIdentifier) - { - StatusItemTable statusItemTable(*m_storage); - return statusItemTable.GetUnitState(instanceIdentifier); - } - - std::optional> Interface::GetUnitResultInformation(const guid& instanceIdentifier) - { - StatusItemTable statusItemTable(*m_storage); - return statusItemTable.GetUnitResultInformation(instanceIdentifier); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Interface.h" +#include "Database/Schema/0_1/SetInfoTable.h" +#include "Database/Schema/0_2/QueueTable.h" +#include "StatusItemTable.h" +#include "ChangeListenerTable.h" +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + static constexpr AppInstaller::SQLite::Version s_InterfaceVersion{ 0, 3 }; + + const AppInstaller::SQLite::Version& Interface::GetSchemaVersion() + { + return s_InterfaceVersion; + } + + void Interface::InitializeDatabase() + { + V0_2::Interface::InitializeDatabase(); + + Savepoint savepoint = Savepoint::Create(*m_storage, "InitializeDatabase_0_3"); + MigrateFrom0_2(); + savepoint.Commit(); + } + + void Interface::RemoveSet(AppInstaller::SQLite::rowid_t target) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "RemoveSet_0_3"); + + V0_2::Interface::RemoveSet(target); + + StatusItemTable statusItemTable(*m_storage); + statusItemTable.RemoveForSet(target); + + savepoint.Commit(); + } + + bool Interface::MigrateFrom(IConfigurationDatabase* current) + { + auto currentSchemaVersion = current->GetSchemaVersion(); + if (currentSchemaVersion < s_InterfaceVersion) + { + if (V0_2::Interface::MigrateFrom(current)) + { + Savepoint savepoint = Savepoint::Create(*m_storage, "MigrateFrom0_2"); + + MigrateFrom0_2(); + s_InterfaceVersion.SetSchemaVersion(*m_storage); + + savepoint.Commit(); + + return true; + } + } + else if (currentSchemaVersion == s_InterfaceVersion) + { + return true; + } + + return false; + } + + void Interface::AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName) + { + V0_2::QueueTable queueTable(*m_storage); + queueTable.AddQueueItemWithProcess(instanceIdentifier, objectName); + } + + std::vector> Interface::GetQueueItems() + { + V0_2::QueueTable queueTable(*m_storage); + return queueTable.GetQueueItemsWithProcess(); + } + + void Interface::MigrateFrom0_2() + { + V0_2::QueueTable queueTable(*m_storage); + queueTable.AddProcessColumn(); + + ChangeListenerTable changeListenerTable(*m_storage); + changeListenerTable.Create(); + + StatusItemTable statusItemTable(*m_storage); + statusItemTable.Create(); + } + + std::vector Interface::GetStatusSince(int64_t changeIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetStatusSince(changeIdentifier); + } + + std::tuple> Interface::GetStatusBaseline() + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetStatusBaseline(); + } + + void Interface::AddListener(const std::string& objectName) + { + ChangeListenerTable changeListenerTable(*m_storage); + changeListenerTable.AddChangeListener(objectName); + } + + void Interface::RemoveListener(const std::string& objectName) + { + ChangeListenerTable changeListenerTable(*m_storage); + changeListenerTable.RemoveChangeListener(objectName); + } + + std::vector> Interface::GetChangeListeners() + { + ChangeListenerTable changeListenerTable(*m_storage); + return changeListenerTable.GetChangeListeners(); + } + + void Interface::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + StatusItemTable statusItemTable(*m_storage); + statusItemTable.UpdateSetState(setInstanceIdentifier, state); + } + + void Interface::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) + { + StatusItemTable statusItemTable(*m_storage); + statusItemTable.UpdateSetInQueue(setInstanceIdentifier, inQueue); + } + + void Interface::UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData) + { + StatusItemTable statusItemTable(*m_storage); + statusItemTable.UpdateUnitState(setInstanceIdentifier, changeData); + } + + ConfigurationSetState Interface::GetSetState(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetSetState(instanceIdentifier); + } + + std::chrono::system_clock::time_point Interface::GetSetFirstApply(const guid& instanceIdentifier) + { + V0_1::SetInfoTable setInfoTable(*m_storage); + return setInfoTable.GetSetFirstApply(instanceIdentifier); + } + + std::chrono::system_clock::time_point Interface::GetSetApplyBegun(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetSetApplyBegun(instanceIdentifier); + } + + std::chrono::system_clock::time_point Interface::GetSetApplyEnded(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetSetApplyEnded(instanceIdentifier); + } + + ConfigurationUnitState Interface::GetUnitState(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetUnitState(instanceIdentifier); + } + + std::optional> Interface::GetUnitResultInformation(const guid& instanceIdentifier) + { + StatusItemTable statusItemTable(*m_storage); + return statusItemTable.GetUnitResultInformation(instanceIdentifier); + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.cpp b/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.cpp index 8465f3b0e8..d198f46211 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.cpp @@ -1,365 +1,365 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "StatusItemTable.h" -#include "Database/Schema/0_1/SetInfoTable.h" -#include -#include -#include -#include - -using namespace AppInstaller::SQLite; -using namespace AppInstaller::SQLite::Builder; -using namespace AppInstaller::Utility; - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 -{ - namespace - { - constexpr std::string_view s_StatusItemTable_Table = "status_items"sv; - constexpr std::string_view s_StatusItemTable_ChangeIdentifierIndex = "status_items_change_idx"sv; - constexpr std::string_view s_StatusItemTable_SetRowIdIndex = "status_items_set_idx"sv; - constexpr std::string_view s_StatusItemTable_UnitInstanceIndex = "status_items_unit_idx"sv; - - constexpr std::string_view s_StatusItemTable_Column_ChangeIdentifier = "change_identifier"sv; - constexpr std::string_view s_StatusItemTable_Column_ChangeTimeInitial = "change_time_initial"sv; - constexpr std::string_view s_StatusItemTable_Column_ChangeTimeLatest = "change_time_latest"sv; - constexpr std::string_view s_StatusItemTable_Column_SetRowId = "set_rowid"sv; - constexpr std::string_view s_StatusItemTable_Column_InQueue = "in_queue"sv; - constexpr std::string_view s_StatusItemTable_Column_UnitInstanceIdentifier = "unit_instance_identifier"sv; - constexpr std::string_view s_StatusItemTable_Column_State = "state"sv; - constexpr std::string_view s_StatusItemTable_Column_ResultCode = "result_code"sv; - constexpr std::string_view s_StatusItemTable_Column_ResultDescription = "result_description"sv; - constexpr std::string_view s_StatusItemTable_Column_ResultDetails = "result_details"sv; - constexpr std::string_view s_StatusItemTable_Column_ResultSource = "result_source"sv; - - void BuildBaseStatusSelectStatement(StatementBuilder& builder) - { - builder.Select({ - s_StatusItemTable_Column_ChangeIdentifier, // 0 - s_StatusItemTable_Column_ChangeTimeLatest, // 1 - V0_1::SetInfoTable::InstanceIdentifierColumn(), // 2 - s_StatusItemTable_Column_InQueue, // 3 - s_StatusItemTable_Column_UnitInstanceIdentifier, // 4 - s_StatusItemTable_Column_State, // 5 - s_StatusItemTable_Column_ResultCode, // 6 - s_StatusItemTable_Column_ResultDescription, // 7 - s_StatusItemTable_Column_ResultDetails, // 8 - s_StatusItemTable_Column_ResultSource, // 9 - }).From(s_StatusItemTable_Table).LeftOuterJoin(V0_1::SetInfoTable::TableName()).On(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }, QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }); - } - - IConfigurationDatabase::StatusItemTuple GetTupleFromStatement(Statement& statement) - { - return std::make_tuple( - statement.GetColumn(0), - ConvertUnixEpochToSystemClock(statement.GetColumn(1)), - statement.GetColumn(2), - statement.GetColumn(3), - statement.GetColumnIsNull(4) ? std::nullopt : std::make_optional(statement.GetColumn(4)), - statement.GetColumn(5), - statement.GetColumnIsNull(6) ? std::nullopt : std::make_optional(statement.GetColumn(6)), - statement.GetColumnIsNull(7) ? std::string{} : statement.GetColumn(7), - statement.GetColumnIsNull(8) ? std::string{} : statement.GetColumn(8), - statement.GetColumnIsNull(9) ? ConfigurationUnitResultSource::None : statement.GetColumn(9) - ); - } - - int64_t GetLatestChangeIdentifier(Connection& connection) - { - StatementBuilder getLatestChangeBuilder; - getLatestChangeBuilder.Select().Column(Aggregate::Max, s_StatusItemTable_Column_ChangeIdentifier).From(s_StatusItemTable_Table); - - Statement getLatestChange = getLatestChangeBuilder.Prepare(connection); - - return (getLatestChange.Step() ? getLatestChange.GetColumn(0) : 0); - } - - int64_t GetNextChangeIdentifier(Connection& connection) - { - return GetLatestChangeIdentifier(connection) + 1; - } - - void UpdateStatus( - Connection& connection, - const GUID& setInstanceIdentifier, - const std::optional& state, - const std::optional& inQueue, - const std::optional& unitInstanceIdentifier = std::nullopt, - const std::optional& resultCode = std::nullopt, - const std::optional& resultDescription = std::nullopt, - const std::optional& resultDetails = std::nullopt, - const std::optional& resultSource = std::nullopt) - { - static constexpr std::string_view s_alias = "sub_expression"; - - int64_t changeIdentifier = GetNextChangeIdentifier(connection); - int64_t changeTime = GetCurrentUnixEpoch(); - - // Statement like: - // Update status_items set state = 1 from (Select rowid from set_info where instance_identifier = "foo") as sub where status_items.set_rowid = sub.rowid - StatementBuilder updateBuilder; - updateBuilder.Update(s_StatusItemTable_Table).Set(); - - if (state) - { - updateBuilder.Column(s_StatusItemTable_Column_State).Equals(state.value()); - } - - if (inQueue) - { - updateBuilder.Column(s_StatusItemTable_Column_InQueue).Equals(inQueue.value()); - } - - if (resultCode) - { - updateBuilder.Column(s_StatusItemTable_Column_ResultCode).Equals(resultCode.value()); - } - - if (resultDescription) - { - updateBuilder.Column(s_StatusItemTable_Column_ResultDescription).Equals(resultDescription.value()); - } - - if (resultDetails) - { - updateBuilder.Column(s_StatusItemTable_Column_ResultDetails).Equals(resultDetails.value()); - } - - if (resultSource) - { - updateBuilder.Column(s_StatusItemTable_Column_ResultSource).Equals(resultSource.value()); - } - - updateBuilder. - Column(s_StatusItemTable_Column_ChangeIdentifier).Equals(changeIdentifier). - Column(s_StatusItemTable_Column_ChangeTimeLatest).Equals(changeTime). - From().BeginParenthetical(). - Select(RowIDName).From(V0_1::SetInfoTable::TableName()).Where(V0_1::SetInfoTable::InstanceIdentifierColumn()).Equals(setInstanceIdentifier). - EndParenthetical().As(s_alias).Where(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }).Equals(QualifiedColumn{ s_alias, RowIDName }). - And(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_UnitInstanceIdentifier }).Equals(unitInstanceIdentifier); - - updateBuilder.Execute(connection); - - if (connection.GetChanges() == 0) - { - // No change; we need to insert the status row - StatementBuilder insertBuilder; - insertBuilder.InsertInto(s_StatusItemTable_Table).Columns({ - s_StatusItemTable_Column_ChangeIdentifier, - s_StatusItemTable_Column_ChangeTimeInitial, - s_StatusItemTable_Column_ChangeTimeLatest, - s_StatusItemTable_Column_SetRowId, - s_StatusItemTable_Column_InQueue, - s_StatusItemTable_Column_UnitInstanceIdentifier, - s_StatusItemTable_Column_State, - s_StatusItemTable_Column_ResultCode, - s_StatusItemTable_Column_ResultDescription, - s_StatusItemTable_Column_ResultDetails, - s_StatusItemTable_Column_ResultSource, - }).Select(). - Value(changeIdentifier). - Value(changeTime). - Value(changeTime). - Column(QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }). - Value(inQueue.value_or(false)). - Value(unitInstanceIdentifier). - Value(state.value_or(0)). - Value(resultCode). - Value(resultDescription). - Value(resultDetails). - Value(resultSource). - From(V0_1::SetInfoTable::TableName()).Where(QualifiedColumn{ V0_1::SetInfoTable::TableName(), V0_1::SetInfoTable::InstanceIdentifierColumn() }).Equals(setInstanceIdentifier); - - insertBuilder.Execute(connection); - } - } - - Statement PrepareSelectStatusValues(Connection& connection, const std::optional& setInstanceIdentifier, const std::optional& unitInstanceIdentifier, std::initializer_list columns) - { - THROW_HR_IF(E_INVALIDARG, (setInstanceIdentifier && unitInstanceIdentifier) || (!setInstanceIdentifier && !unitInstanceIdentifier)); - - StatementBuilder builder; - builder.Select(columns).From(s_StatusItemTable_Table); - - if (setInstanceIdentifier) - { - builder.Join(V0_1::SetInfoTable::TableName()).On(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }, QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }). - Where(QualifiedColumn{ V0_1::SetInfoTable::TableName(), V0_1::SetInfoTable::InstanceIdentifierColumn() }).Equals(setInstanceIdentifier). - And(s_StatusItemTable_Column_UnitInstanceIdentifier).IsNull(); - } - else - { - builder.Where(s_StatusItemTable_Column_UnitInstanceIdentifier).Equals(unitInstanceIdentifier.value()); - } - - return builder.Prepare(connection); - } - } - - StatusItemTable::StatusItemTable(Connection& connection) : m_connection(connection) {} - - void StatusItemTable::Create() - { - Savepoint savepoint = Savepoint::Create(m_connection, "StatusItemTable_Create_0_3"); - - StatementBuilder tableBuilder; - tableBuilder.CreateTable(s_StatusItemTable_Table).Columns({ - IntegerPrimaryKey(), - ColumnBuilder(s_StatusItemTable_Column_ChangeIdentifier, Type::Int64).NotNull(), - ColumnBuilder(s_StatusItemTable_Column_ChangeTimeInitial, Type::Int64).NotNull(), - ColumnBuilder(s_StatusItemTable_Column_ChangeTimeLatest, Type::Int64).NotNull(), - ColumnBuilder(s_StatusItemTable_Column_SetRowId, Type::RowId).NotNull(), - ColumnBuilder(s_StatusItemTable_Column_InQueue, Type::Bool).NotNull(), - ColumnBuilder(s_StatusItemTable_Column_UnitInstanceIdentifier, Type::Blob), - ColumnBuilder(s_StatusItemTable_Column_State, Type::Int).NotNull(), - ColumnBuilder(s_StatusItemTable_Column_ResultCode, Type::Int), - ColumnBuilder(s_StatusItemTable_Column_ResultDescription, Type::Text), - ColumnBuilder(s_StatusItemTable_Column_ResultDetails, Type::Text), - ColumnBuilder(s_StatusItemTable_Column_ResultSource, Type::Int), - }); - - tableBuilder.Execute(m_connection); - - { - StatementBuilder indexBuilder; - indexBuilder.CreateIndex(s_StatusItemTable_ChangeIdentifierIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_ChangeIdentifier); - indexBuilder.Execute(m_connection); - } - - { - StatementBuilder indexBuilder; - indexBuilder.CreateIndex(s_StatusItemTable_SetRowIdIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_SetRowId); - indexBuilder.Execute(m_connection); - } - - { - StatementBuilder indexBuilder; - indexBuilder.CreateUniqueIndex(s_StatusItemTable_UnitInstanceIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_UnitInstanceIdentifier); - indexBuilder.Execute(m_connection); - } - - savepoint.Commit(); - } - - void StatusItemTable::RemoveForSet(AppInstaller::SQLite::rowid_t target) - { - StatementBuilder builder; - builder.DeleteFrom(s_StatusItemTable_Table).Where(s_StatusItemTable_Column_SetRowId).Equals(target); - builder.Execute(m_connection); - } - - std::vector StatusItemTable::GetStatusSince(int64_t changeIdentifier) - { - StatementBuilder builder; - BuildBaseStatusSelectStatement(builder); - builder.Where(s_StatusItemTable_Column_ChangeIdentifier).IsGreaterThan(changeIdentifier).OrderBy(s_StatusItemTable_Column_ChangeIdentifier); - - Statement statement = builder.Prepare(m_connection); - - std::vector result; - - while (statement.Step()) - { - result.emplace_back(GetTupleFromStatement(statement)); - } - - return result; - } - - std::tuple> StatusItemTable::GetStatusBaseline() - { - int64_t latestChange = GetLatestChangeIdentifier(m_connection); - std::vector setStatus; - - StatementBuilder builder; - BuildBaseStatusSelectStatement(builder); - builder.Where(s_StatusItemTable_Column_UnitInstanceIdentifier).IsNull(); - - Statement statement = builder.Prepare(m_connection); - - while (statement.Step()) - { - setStatus.emplace_back(GetTupleFromStatement(statement)); - } - - return std::make_tuple(latestChange, std::move(setStatus)); - } - - void StatusItemTable::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) - { - UpdateStatus(m_connection, setInstanceIdentifier, AppInstaller::ToIntegral(state), std::nullopt); - } - - void StatusItemTable::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) - { - UpdateStatus(m_connection, setInstanceIdentifier, std::nullopt, inQueue); - } - - void StatusItemTable::UpdateUnitState(const guid& setInstanceIdentifier, const IConfigurationDatabase::ConfigurationSetChangeDataPtr& changeData) - { - const auto& resultInformation = changeData->ResultInformation(); - - std::optional resultCode; - std::optional resultDescription; - std::optional resultDetails; - std::optional resultSource; - - if (resultInformation) - { - resultCode = resultInformation.ResultCode(); - resultDescription = ConvertToUTF8(resultInformation.Description()); - resultDetails = ConvertToUTF8(resultInformation.Details()); - resultSource = resultInformation.ResultSource(); - } - - UpdateStatus(m_connection, setInstanceIdentifier, AppInstaller::ToIntegral(changeData->UnitState()), std::nullopt, changeData->Unit().InstanceIdentifier(), resultCode, resultDescription, resultDetails, resultSource); - } - - ConfigurationSetState StatusItemTable::GetSetState(const guid& instanceIdentifier) - { - Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_State }); - - return (statement.Step() ? statement.GetColumn(0) : ConfigurationSetState::Unknown); - } - - std::chrono::system_clock::time_point StatusItemTable::GetSetApplyBegun(const GUID& instanceIdentifier) - { - Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_ChangeTimeInitial }); - - return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn(0)) : std::chrono::system_clock::time_point{}); - } - - std::chrono::system_clock::time_point StatusItemTable::GetSetApplyEnded(const GUID& instanceIdentifier) - { - Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_ChangeTimeLatest, s_StatusItemTable_Column_InQueue }); - - // Only return the end time if no longer in the queue - if (statement.Step() && !statement.GetColumn(1)) - { - return ConvertUnixEpochToSystemClock(statement.GetColumn(0)); - } - - return std::chrono::system_clock::time_point{}; - } - - ConfigurationUnitState StatusItemTable::GetUnitState(const guid& instanceIdentifier) - { - Statement statement = PrepareSelectStatusValues(m_connection, std::nullopt, instanceIdentifier, { s_StatusItemTable_Column_State }); - - return (statement.Step() ? statement.GetColumn(0) : ConfigurationUnitState::Unknown); - } - - std::optional> StatusItemTable::GetUnitResultInformation(const guid& instanceIdentifier) - { - Statement statement = PrepareSelectStatusValues(m_connection, std::nullopt, instanceIdentifier, - { s_StatusItemTable_Column_ResultCode, s_StatusItemTable_Column_ResultDescription, s_StatusItemTable_Column_ResultDetails, s_StatusItemTable_Column_ResultSource }); - - if (statement.Step() && !statement.GetColumnIsNull(0)) - { - return statement.GetRow(); - } - - return std::nullopt; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "StatusItemTable.h" +#include "Database/Schema/0_1/SetInfoTable.h" +#include +#include +#include +#include + +using namespace AppInstaller::SQLite; +using namespace AppInstaller::SQLite::Builder; +using namespace AppInstaller::Utility; + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + namespace + { + constexpr std::string_view s_StatusItemTable_Table = "status_items"sv; + constexpr std::string_view s_StatusItemTable_ChangeIdentifierIndex = "status_items_change_idx"sv; + constexpr std::string_view s_StatusItemTable_SetRowIdIndex = "status_items_set_idx"sv; + constexpr std::string_view s_StatusItemTable_UnitInstanceIndex = "status_items_unit_idx"sv; + + constexpr std::string_view s_StatusItemTable_Column_ChangeIdentifier = "change_identifier"sv; + constexpr std::string_view s_StatusItemTable_Column_ChangeTimeInitial = "change_time_initial"sv; + constexpr std::string_view s_StatusItemTable_Column_ChangeTimeLatest = "change_time_latest"sv; + constexpr std::string_view s_StatusItemTable_Column_SetRowId = "set_rowid"sv; + constexpr std::string_view s_StatusItemTable_Column_InQueue = "in_queue"sv; + constexpr std::string_view s_StatusItemTable_Column_UnitInstanceIdentifier = "unit_instance_identifier"sv; + constexpr std::string_view s_StatusItemTable_Column_State = "state"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultCode = "result_code"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultDescription = "result_description"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultDetails = "result_details"sv; + constexpr std::string_view s_StatusItemTable_Column_ResultSource = "result_source"sv; + + void BuildBaseStatusSelectStatement(StatementBuilder& builder) + { + builder.Select({ + s_StatusItemTable_Column_ChangeIdentifier, // 0 + s_StatusItemTable_Column_ChangeTimeLatest, // 1 + V0_1::SetInfoTable::InstanceIdentifierColumn(), // 2 + s_StatusItemTable_Column_InQueue, // 3 + s_StatusItemTable_Column_UnitInstanceIdentifier, // 4 + s_StatusItemTable_Column_State, // 5 + s_StatusItemTable_Column_ResultCode, // 6 + s_StatusItemTable_Column_ResultDescription, // 7 + s_StatusItemTable_Column_ResultDetails, // 8 + s_StatusItemTable_Column_ResultSource, // 9 + }).From(s_StatusItemTable_Table).LeftOuterJoin(V0_1::SetInfoTable::TableName()).On(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }, QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }); + } + + IConfigurationDatabase::StatusItemTuple GetTupleFromStatement(Statement& statement) + { + return std::make_tuple( + statement.GetColumn(0), + ConvertUnixEpochToSystemClock(statement.GetColumn(1)), + statement.GetColumn(2), + statement.GetColumn(3), + statement.GetColumnIsNull(4) ? std::nullopt : std::make_optional(statement.GetColumn(4)), + statement.GetColumn(5), + statement.GetColumnIsNull(6) ? std::nullopt : std::make_optional(statement.GetColumn(6)), + statement.GetColumnIsNull(7) ? std::string{} : statement.GetColumn(7), + statement.GetColumnIsNull(8) ? std::string{} : statement.GetColumn(8), + statement.GetColumnIsNull(9) ? ConfigurationUnitResultSource::None : statement.GetColumn(9) + ); + } + + int64_t GetLatestChangeIdentifier(Connection& connection) + { + StatementBuilder getLatestChangeBuilder; + getLatestChangeBuilder.Select().Column(Aggregate::Max, s_StatusItemTable_Column_ChangeIdentifier).From(s_StatusItemTable_Table); + + Statement getLatestChange = getLatestChangeBuilder.Prepare(connection); + + return (getLatestChange.Step() ? getLatestChange.GetColumn(0) : 0); + } + + int64_t GetNextChangeIdentifier(Connection& connection) + { + return GetLatestChangeIdentifier(connection) + 1; + } + + void UpdateStatus( + Connection& connection, + const GUID& setInstanceIdentifier, + const std::optional& state, + const std::optional& inQueue, + const std::optional& unitInstanceIdentifier = std::nullopt, + const std::optional& resultCode = std::nullopt, + const std::optional& resultDescription = std::nullopt, + const std::optional& resultDetails = std::nullopt, + const std::optional& resultSource = std::nullopt) + { + static constexpr std::string_view s_alias = "sub_expression"; + + int64_t changeIdentifier = GetNextChangeIdentifier(connection); + int64_t changeTime = GetCurrentUnixEpoch(); + + // Statement like: + // Update status_items set state = 1 from (Select rowid from set_info where instance_identifier = "foo") as sub where status_items.set_rowid = sub.rowid + StatementBuilder updateBuilder; + updateBuilder.Update(s_StatusItemTable_Table).Set(); + + if (state) + { + updateBuilder.Column(s_StatusItemTable_Column_State).Equals(state.value()); + } + + if (inQueue) + { + updateBuilder.Column(s_StatusItemTable_Column_InQueue).Equals(inQueue.value()); + } + + if (resultCode) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultCode).Equals(resultCode.value()); + } + + if (resultDescription) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultDescription).Equals(resultDescription.value()); + } + + if (resultDetails) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultDetails).Equals(resultDetails.value()); + } + + if (resultSource) + { + updateBuilder.Column(s_StatusItemTable_Column_ResultSource).Equals(resultSource.value()); + } + + updateBuilder. + Column(s_StatusItemTable_Column_ChangeIdentifier).Equals(changeIdentifier). + Column(s_StatusItemTable_Column_ChangeTimeLatest).Equals(changeTime). + From().BeginParenthetical(). + Select(RowIDName).From(V0_1::SetInfoTable::TableName()).Where(V0_1::SetInfoTable::InstanceIdentifierColumn()).Equals(setInstanceIdentifier). + EndParenthetical().As(s_alias).Where(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }).Equals(QualifiedColumn{ s_alias, RowIDName }). + And(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_UnitInstanceIdentifier }).Equals(unitInstanceIdentifier); + + updateBuilder.Execute(connection); + + if (connection.GetChanges() == 0) + { + // No change; we need to insert the status row + StatementBuilder insertBuilder; + insertBuilder.InsertInto(s_StatusItemTable_Table).Columns({ + s_StatusItemTable_Column_ChangeIdentifier, + s_StatusItemTable_Column_ChangeTimeInitial, + s_StatusItemTable_Column_ChangeTimeLatest, + s_StatusItemTable_Column_SetRowId, + s_StatusItemTable_Column_InQueue, + s_StatusItemTable_Column_UnitInstanceIdentifier, + s_StatusItemTable_Column_State, + s_StatusItemTable_Column_ResultCode, + s_StatusItemTable_Column_ResultDescription, + s_StatusItemTable_Column_ResultDetails, + s_StatusItemTable_Column_ResultSource, + }).Select(). + Value(changeIdentifier). + Value(changeTime). + Value(changeTime). + Column(QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }). + Value(inQueue.value_or(false)). + Value(unitInstanceIdentifier). + Value(state.value_or(0)). + Value(resultCode). + Value(resultDescription). + Value(resultDetails). + Value(resultSource). + From(V0_1::SetInfoTable::TableName()).Where(QualifiedColumn{ V0_1::SetInfoTable::TableName(), V0_1::SetInfoTable::InstanceIdentifierColumn() }).Equals(setInstanceIdentifier); + + insertBuilder.Execute(connection); + } + } + + Statement PrepareSelectStatusValues(Connection& connection, const std::optional& setInstanceIdentifier, const std::optional& unitInstanceIdentifier, std::initializer_list columns) + { + THROW_HR_IF(E_INVALIDARG, (setInstanceIdentifier && unitInstanceIdentifier) || (!setInstanceIdentifier && !unitInstanceIdentifier)); + + StatementBuilder builder; + builder.Select(columns).From(s_StatusItemTable_Table); + + if (setInstanceIdentifier) + { + builder.Join(V0_1::SetInfoTable::TableName()).On(QualifiedColumn{ s_StatusItemTable_Table, s_StatusItemTable_Column_SetRowId }, QualifiedColumn{ V0_1::SetInfoTable::TableName(), RowIDName }). + Where(QualifiedColumn{ V0_1::SetInfoTable::TableName(), V0_1::SetInfoTable::InstanceIdentifierColumn() }).Equals(setInstanceIdentifier). + And(s_StatusItemTable_Column_UnitInstanceIdentifier).IsNull(); + } + else + { + builder.Where(s_StatusItemTable_Column_UnitInstanceIdentifier).Equals(unitInstanceIdentifier.value()); + } + + return builder.Prepare(connection); + } + } + + StatusItemTable::StatusItemTable(Connection& connection) : m_connection(connection) {} + + void StatusItemTable::Create() + { + Savepoint savepoint = Savepoint::Create(m_connection, "StatusItemTable_Create_0_3"); + + StatementBuilder tableBuilder; + tableBuilder.CreateTable(s_StatusItemTable_Table).Columns({ + IntegerPrimaryKey(), + ColumnBuilder(s_StatusItemTable_Column_ChangeIdentifier, Type::Int64).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_ChangeTimeInitial, Type::Int64).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_ChangeTimeLatest, Type::Int64).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_SetRowId, Type::RowId).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_InQueue, Type::Bool).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_UnitInstanceIdentifier, Type::Blob), + ColumnBuilder(s_StatusItemTable_Column_State, Type::Int).NotNull(), + ColumnBuilder(s_StatusItemTable_Column_ResultCode, Type::Int), + ColumnBuilder(s_StatusItemTable_Column_ResultDescription, Type::Text), + ColumnBuilder(s_StatusItemTable_Column_ResultDetails, Type::Text), + ColumnBuilder(s_StatusItemTable_Column_ResultSource, Type::Int), + }); + + tableBuilder.Execute(m_connection); + + { + StatementBuilder indexBuilder; + indexBuilder.CreateIndex(s_StatusItemTable_ChangeIdentifierIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_ChangeIdentifier); + indexBuilder.Execute(m_connection); + } + + { + StatementBuilder indexBuilder; + indexBuilder.CreateIndex(s_StatusItemTable_SetRowIdIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_SetRowId); + indexBuilder.Execute(m_connection); + } + + { + StatementBuilder indexBuilder; + indexBuilder.CreateUniqueIndex(s_StatusItemTable_UnitInstanceIndex).On(s_StatusItemTable_Table).Columns(s_StatusItemTable_Column_UnitInstanceIdentifier); + indexBuilder.Execute(m_connection); + } + + savepoint.Commit(); + } + + void StatusItemTable::RemoveForSet(AppInstaller::SQLite::rowid_t target) + { + StatementBuilder builder; + builder.DeleteFrom(s_StatusItemTable_Table).Where(s_StatusItemTable_Column_SetRowId).Equals(target); + builder.Execute(m_connection); + } + + std::vector StatusItemTable::GetStatusSince(int64_t changeIdentifier) + { + StatementBuilder builder; + BuildBaseStatusSelectStatement(builder); + builder.Where(s_StatusItemTable_Column_ChangeIdentifier).IsGreaterThan(changeIdentifier).OrderBy(s_StatusItemTable_Column_ChangeIdentifier); + + Statement statement = builder.Prepare(m_connection); + + std::vector result; + + while (statement.Step()) + { + result.emplace_back(GetTupleFromStatement(statement)); + } + + return result; + } + + std::tuple> StatusItemTable::GetStatusBaseline() + { + int64_t latestChange = GetLatestChangeIdentifier(m_connection); + std::vector setStatus; + + StatementBuilder builder; + BuildBaseStatusSelectStatement(builder); + builder.Where(s_StatusItemTable_Column_UnitInstanceIdentifier).IsNull(); + + Statement statement = builder.Prepare(m_connection); + + while (statement.Step()) + { + setStatus.emplace_back(GetTupleFromStatement(statement)); + } + + return std::make_tuple(latestChange, std::move(setStatus)); + } + + void StatusItemTable::UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state) + { + UpdateStatus(m_connection, setInstanceIdentifier, AppInstaller::ToIntegral(state), std::nullopt); + } + + void StatusItemTable::UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue) + { + UpdateStatus(m_connection, setInstanceIdentifier, std::nullopt, inQueue); + } + + void StatusItemTable::UpdateUnitState(const guid& setInstanceIdentifier, const IConfigurationDatabase::ConfigurationSetChangeDataPtr& changeData) + { + const auto& resultInformation = changeData->ResultInformation(); + + std::optional resultCode; + std::optional resultDescription; + std::optional resultDetails; + std::optional resultSource; + + if (resultInformation) + { + resultCode = resultInformation.ResultCode(); + resultDescription = ConvertToUTF8(resultInformation.Description()); + resultDetails = ConvertToUTF8(resultInformation.Details()); + resultSource = resultInformation.ResultSource(); + } + + UpdateStatus(m_connection, setInstanceIdentifier, AppInstaller::ToIntegral(changeData->UnitState()), std::nullopt, changeData->Unit().InstanceIdentifier(), resultCode, resultDescription, resultDetails, resultSource); + } + + ConfigurationSetState StatusItemTable::GetSetState(const guid& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_State }); + + return (statement.Step() ? statement.GetColumn(0) : ConfigurationSetState::Unknown); + } + + std::chrono::system_clock::time_point StatusItemTable::GetSetApplyBegun(const GUID& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_ChangeTimeInitial }); + + return (statement.Step() ? ConvertUnixEpochToSystemClock(statement.GetColumn(0)) : std::chrono::system_clock::time_point{}); + } + + std::chrono::system_clock::time_point StatusItemTable::GetSetApplyEnded(const GUID& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, instanceIdentifier, std::nullopt, { s_StatusItemTable_Column_ChangeTimeLatest, s_StatusItemTable_Column_InQueue }); + + // Only return the end time if no longer in the queue + if (statement.Step() && !statement.GetColumn(1)) + { + return ConvertUnixEpochToSystemClock(statement.GetColumn(0)); + } + + return std::chrono::system_clock::time_point{}; + } + + ConfigurationUnitState StatusItemTable::GetUnitState(const guid& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, std::nullopt, instanceIdentifier, { s_StatusItemTable_Column_State }); + + return (statement.Step() ? statement.GetColumn(0) : ConfigurationUnitState::Unknown); + } + + std::optional> StatusItemTable::GetUnitResultInformation(const guid& instanceIdentifier) + { + Statement statement = PrepareSelectStatusValues(m_connection, std::nullopt, instanceIdentifier, + { s_StatusItemTable_Column_ResultCode, s_StatusItemTable_Column_ResultDescription, s_StatusItemTable_Column_ResultDetails, s_StatusItemTable_Column_ResultSource }); + + if (statement.Step() && !statement.GetColumnIsNull(0)) + { + return statement.GetRow(); + } + + return std::nullopt; + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.h b/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.h index 3354c6669d..08d4fb4bc2 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/0_3/StatusItemTable.h @@ -1,54 +1,54 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Database/Schema/IConfigurationDatabase.h" -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 -{ - struct StatusItemTable - { - StatusItemTable(AppInstaller::SQLite::Connection& connection); - - // Creates the table. - void Create(); - - // Removes the status for the target set. - void RemoveForSet(AppInstaller::SQLite::rowid_t target); - - // Gets all status items that have changed since the given change identifier, in order of changes (last item is the new latest change to pass next). - std::vector GetStatusSince(int64_t changeIdentifier); - - // Gets the latest change identifier and the set status items. - std::tuple> GetStatusBaseline(); - - // Updates a set's state. - void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); - - // Updates whether a set is in the queue or not. - void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); - - // Updates a unit's state. - void UpdateUnitState(const guid& setInstanceIdentifier, const IConfigurationDatabase::ConfigurationSetChangeDataPtr& changeData); - - // Gets a set's state. - ConfigurationSetState GetSetState(const guid& instanceIdentifier); - - // Gets a set's latest apply begin time. - std::chrono::system_clock::time_point GetSetApplyBegun(const GUID& instanceIdentifier); - - // Gets a set's latest apply end time. - std::chrono::system_clock::time_point GetSetApplyEnded(const GUID& instanceIdentifier); - - // Gets a unit's state. - ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); - - // Gets a unit's latest result information. - std::optional> GetUnitResultInformation(const guid& instanceIdentifier); - - private: - AppInstaller::SQLite::Connection& m_connection; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Database/Schema/IConfigurationDatabase.h" +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation::Database::Schema::V0_3 +{ + struct StatusItemTable + { + StatusItemTable(AppInstaller::SQLite::Connection& connection); + + // Creates the table. + void Create(); + + // Removes the status for the target set. + void RemoveForSet(AppInstaller::SQLite::rowid_t target); + + // Gets all status items that have changed since the given change identifier, in order of changes (last item is the new latest change to pass next). + std::vector GetStatusSince(int64_t changeIdentifier); + + // Gets the latest change identifier and the set status items. + std::tuple> GetStatusBaseline(); + + // Updates a set's state. + void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + + // Updates whether a set is in the queue or not. + void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); + + // Updates a unit's state. + void UpdateUnitState(const guid& setInstanceIdentifier, const IConfigurationDatabase::ConfigurationSetChangeDataPtr& changeData); + + // Gets a set's state. + ConfigurationSetState GetSetState(const guid& instanceIdentifier); + + // Gets a set's latest apply begin time. + std::chrono::system_clock::time_point GetSetApplyBegun(const GUID& instanceIdentifier); + + // Gets a set's latest apply end time. + std::chrono::system_clock::time_point GetSetApplyEnded(const GUID& instanceIdentifier); + + // Gets a unit's state. + ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + + // Gets a unit's latest result information. + std::optional> GetUnitResultInformation(const guid& instanceIdentifier); + + private: + AppInstaller::SQLite::Connection& m_connection; + }; +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.cpp b/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.cpp index 9581ba62a9..2cad17e19e 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.cpp +++ b/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.cpp @@ -1,144 +1,144 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Database/Schema/IConfigurationDatabase.h" - -#include "Database/Schema/0_1/Interface.h" -#include "Database/Schema/0_2/Interface.h" -#include "Database/Schema/0_3/Interface.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - std::unique_ptr CreateForVersion(const AppInstaller::SQLite::Version& version, const std::shared_ptr& storage) - { - using StorageT = std::shared_ptr; - - if (version.MajorVersion == 0) - { - constexpr std::array(*)(const StorageT& s), 3> versionCreatorMap = - { - [](const StorageT& s) { return std::unique_ptr(std::make_unique(s)); }, - [](const StorageT& s) { return std::unique_ptr(std::make_unique(s)); }, - [](const StorageT& s) { return std::unique_ptr(std::make_unique(s)); }, - }; - - size_t minorVersion = static_cast(version.MinorVersion); - if (minorVersion >= 1 && minorVersion <= versionCreatorMap.size()) - { - return versionCreatorMap[minorVersion - 1](storage); - } - } - - // We do not have the capacity to operate on this schema version - THROW_WIN32(ERROR_NOT_SUPPORTED); - } - } - - AppInstaller::SQLite::Version IConfigurationDatabase::GetLatestVersion() - { - return { 0, 3 }; - } - - std::unique_ptr IConfigurationDatabase::CreateFor(const std::shared_ptr& storage, bool allowMigration) - { - using StorageT = std::shared_ptr; - const AppInstaller::SQLite::Version& version = storage->GetVersion(); - - std::unique_ptr result = CreateForVersion(version, storage); - - AppInstaller::SQLite::Version latestVersion = GetLatestVersion(); - if (allowMigration && version < latestVersion) - { - // Always migrate to the latest version until a reason comes along to not do that - std::unique_ptr latest = CreateForVersion(latestVersion, storage); - THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !latest->MigrateFrom(result.get())); - result = std::move(latest); - } - - return result; - } - - void IConfigurationDatabase::AddQueueItem(const GUID&, const std::string&) - { - } - - void IConfigurationDatabase::SetActiveQueueItem(const std::string&) - { - } - - std::vector> IConfigurationDatabase::GetQueueItems() - { - return {}; - } - - void IConfigurationDatabase::RemoveQueueItem(const std::string&) - { - } - - std::vector IConfigurationDatabase::GetStatusSince(int64_t) - { - return {}; - } - - std::tuple> IConfigurationDatabase::GetStatusBaseline() - { - return { 0, {} }; - } - - void IConfigurationDatabase::AddListener(const std::string&) - { - } - - void IConfigurationDatabase::RemoveListener(const std::string&) - { - } - - std::vector> IConfigurationDatabase::GetChangeListeners() - { - return {}; - } - - void IConfigurationDatabase::UpdateSetState(const guid&, ConfigurationSetState) - { - } - - void IConfigurationDatabase::UpdateSetInQueue(const guid&, bool) - { - } - - void IConfigurationDatabase::UpdateUnitState(const guid&, const ConfigurationSetChangeDataPtr&) - { - } - - ConfigurationSetState IConfigurationDatabase::GetSetState(const guid&) - { - return ConfigurationSetState::Unknown; - } - - std::chrono::system_clock::time_point IConfigurationDatabase::GetSetFirstApply(const guid&) - { - return {}; - } - - std::chrono::system_clock::time_point IConfigurationDatabase::GetSetApplyBegun(const guid&) - { - return {}; - } - - std::chrono::system_clock::time_point IConfigurationDatabase::GetSetApplyEnded(const guid&) - { - return {}; - } - - ConfigurationUnitState IConfigurationDatabase::GetUnitState(const guid&) - { - return ConfigurationUnitState::Unknown; - } - - std::optional> IConfigurationDatabase::GetUnitResultInformation(const guid&) - { - return std::nullopt; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Database/Schema/IConfigurationDatabase.h" + +#include "Database/Schema/0_1/Interface.h" +#include "Database/Schema/0_2/Interface.h" +#include "Database/Schema/0_3/Interface.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + std::unique_ptr CreateForVersion(const AppInstaller::SQLite::Version& version, const std::shared_ptr& storage) + { + using StorageT = std::shared_ptr; + + if (version.MajorVersion == 0) + { + constexpr std::array(*)(const StorageT& s), 3> versionCreatorMap = + { + [](const StorageT& s) { return std::unique_ptr(std::make_unique(s)); }, + [](const StorageT& s) { return std::unique_ptr(std::make_unique(s)); }, + [](const StorageT& s) { return std::unique_ptr(std::make_unique(s)); }, + }; + + size_t minorVersion = static_cast(version.MinorVersion); + if (minorVersion >= 1 && minorVersion <= versionCreatorMap.size()) + { + return versionCreatorMap[minorVersion - 1](storage); + } + } + + // We do not have the capacity to operate on this schema version + THROW_WIN32(ERROR_NOT_SUPPORTED); + } + } + + AppInstaller::SQLite::Version IConfigurationDatabase::GetLatestVersion() + { + return { 0, 3 }; + } + + std::unique_ptr IConfigurationDatabase::CreateFor(const std::shared_ptr& storage, bool allowMigration) + { + using StorageT = std::shared_ptr; + const AppInstaller::SQLite::Version& version = storage->GetVersion(); + + std::unique_ptr result = CreateForVersion(version, storage); + + AppInstaller::SQLite::Version latestVersion = GetLatestVersion(); + if (allowMigration && version < latestVersion) + { + // Always migrate to the latest version until a reason comes along to not do that + std::unique_ptr latest = CreateForVersion(latestVersion, storage); + THROW_WIN32_IF(ERROR_NOT_SUPPORTED, !latest->MigrateFrom(result.get())); + result = std::move(latest); + } + + return result; + } + + void IConfigurationDatabase::AddQueueItem(const GUID&, const std::string&) + { + } + + void IConfigurationDatabase::SetActiveQueueItem(const std::string&) + { + } + + std::vector> IConfigurationDatabase::GetQueueItems() + { + return {}; + } + + void IConfigurationDatabase::RemoveQueueItem(const std::string&) + { + } + + std::vector IConfigurationDatabase::GetStatusSince(int64_t) + { + return {}; + } + + std::tuple> IConfigurationDatabase::GetStatusBaseline() + { + return { 0, {} }; + } + + void IConfigurationDatabase::AddListener(const std::string&) + { + } + + void IConfigurationDatabase::RemoveListener(const std::string&) + { + } + + std::vector> IConfigurationDatabase::GetChangeListeners() + { + return {}; + } + + void IConfigurationDatabase::UpdateSetState(const guid&, ConfigurationSetState) + { + } + + void IConfigurationDatabase::UpdateSetInQueue(const guid&, bool) + { + } + + void IConfigurationDatabase::UpdateUnitState(const guid&, const ConfigurationSetChangeDataPtr&) + { + } + + ConfigurationSetState IConfigurationDatabase::GetSetState(const guid&) + { + return ConfigurationSetState::Unknown; + } + + std::chrono::system_clock::time_point IConfigurationDatabase::GetSetFirstApply(const guid&) + { + return {}; + } + + std::chrono::system_clock::time_point IConfigurationDatabase::GetSetApplyBegun(const guid&) + { + return {}; + } + + std::chrono::system_clock::time_point IConfigurationDatabase::GetSetApplyEnded(const guid&) + { + return {}; + } + + ConfigurationUnitState IConfigurationDatabase::GetUnitState(const guid&) + { + return ConfigurationUnitState::Unknown; + } + + std::optional> IConfigurationDatabase::GetUnitResultInformation(const guid&) + { + return std::nullopt; + } +} diff --git a/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.h b/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.h index a924111734..dbd47614b4 100644 --- a/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.h +++ b/src/Microsoft.Management.Configuration/Database/Schema/IConfigurationDatabase.h @@ -1,133 +1,133 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" -#include "ConfigurationSet.h" -#include "ConfigurationUnit.h" -#include "ConfigurationSetChangeData.h" -#include -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Interface for interacting with the configuration database. - struct IConfigurationDatabase - { - using ConfigurationSetPtr = winrt::com_ptr; - using ConfigurationUnitPtr = winrt::com_ptr; - using ConfigurationSetChangeDataPtr = winrt::com_ptr; - - virtual ~IConfigurationDatabase() = default; - - // Gets the latest schema version for the configuration database. - static AppInstaller::SQLite::Version GetLatestVersion(); - - // Creates the version appropriate database object for the given storage. - static std::unique_ptr CreateFor(const std::shared_ptr& storage, bool allowMigration = false); - - // Gets the schema version from the current interface. - virtual const AppInstaller::SQLite::Version& GetSchemaVersion() = 0; - - // Version 0.1 - - // Acts on a database that has been created but contains no tables beyond metadata. - virtual void InitializeDatabase() = 0; - - // Adds the given set to the database. - virtual void AddSet(const Configuration::ConfigurationSet& configurationSet) = 0; - - // Updates the set with the given row id using the given set. - virtual void UpdateSet(AppInstaller::SQLite::rowid_t target, const Configuration::ConfigurationSet& configurationSet) = 0; - - // Removes the set with the given row id from the database. - virtual void RemoveSet(AppInstaller::SQLite::rowid_t target) = 0; - - // Gets all of the sets in the database. - virtual std::vector GetSets() = 0; - - // Gets the row id of the set with the given instance identifier, if present. - virtual std::optional GetSetRowId(const GUID& instanceIdentifier) = 0; - - // Version 0.2 - - // Migrates from the current interface given. - // Returns true if supported (or is already same schema version); false if not. - // Throws on errors that occur during an attempted migration. - virtual bool MigrateFrom(IConfigurationDatabase* current) = 0; - - // Adds a new queue item for the given configuration set and object name. - virtual void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName); - - // Sets the queue item with the given object name as active. - virtual void SetActiveQueueItem(const std::string& objectName); - - // Gets all queue items in queue order (item at index 0 is active/next). - virtual std::vector> GetQueueItems(); - - // Removes the queue item with the given object name. - virtual void RemoveQueueItem(const std::string& objectName); - - // Version 0.3 - - // Gets a set from the database. - virtual ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) = 0; - - // The tuple returned for status items. - using StatusItemTuple = std::tuple< - int64_t, - std::chrono::system_clock::time_point, - GUID, - bool, - std::optional, - int32_t, - std::optional, - std::string, - std::string, - ConfigurationUnitResultSource>; - - // Gets all status items that have changed since the given change identifier, in order of changes (last item is the new latest change to pass next). - virtual std::vector GetStatusSince(int64_t changeIdentifier); - - // Gets the latest change identifier and the set status items. - virtual std::tuple> GetStatusBaseline(); - - // Adds a new listener to the database. - virtual void AddListener(const std::string& objectName); - - // Removes a listener from the database. - virtual void RemoveListener(const std::string& objectName); - - // Gets all of the change listeners in the database. - virtual std::vector> GetChangeListeners(); - - // Updates a set's state. - virtual void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); - - // Updates whether a set is in the queue or not. - virtual void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); - - // Updates a unit's state. - virtual void UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData); - - // Gets a set's state. - virtual ConfigurationSetState GetSetState(const guid& instanceIdentifier); - - // Gets a set's first apply time. - virtual std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier); - - // Gets a set's latest apply begin time. - virtual std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); - - // Gets a set's latest apply end time. - virtual std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); - - // Gets a unit's state. - virtual ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); - - // Gets a unit's latest result information. - virtual std::optional> GetUnitResultInformation(const guid& instanceIdentifier); - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "ConfigurationSet.h" +#include "ConfigurationUnit.h" +#include "ConfigurationSetChangeData.h" +#include +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Interface for interacting with the configuration database. + struct IConfigurationDatabase + { + using ConfigurationSetPtr = winrt::com_ptr; + using ConfigurationUnitPtr = winrt::com_ptr; + using ConfigurationSetChangeDataPtr = winrt::com_ptr; + + virtual ~IConfigurationDatabase() = default; + + // Gets the latest schema version for the configuration database. + static AppInstaller::SQLite::Version GetLatestVersion(); + + // Creates the version appropriate database object for the given storage. + static std::unique_ptr CreateFor(const std::shared_ptr& storage, bool allowMigration = false); + + // Gets the schema version from the current interface. + virtual const AppInstaller::SQLite::Version& GetSchemaVersion() = 0; + + // Version 0.1 + + // Acts on a database that has been created but contains no tables beyond metadata. + virtual void InitializeDatabase() = 0; + + // Adds the given set to the database. + virtual void AddSet(const Configuration::ConfigurationSet& configurationSet) = 0; + + // Updates the set with the given row id using the given set. + virtual void UpdateSet(AppInstaller::SQLite::rowid_t target, const Configuration::ConfigurationSet& configurationSet) = 0; + + // Removes the set with the given row id from the database. + virtual void RemoveSet(AppInstaller::SQLite::rowid_t target) = 0; + + // Gets all of the sets in the database. + virtual std::vector GetSets() = 0; + + // Gets the row id of the set with the given instance identifier, if present. + virtual std::optional GetSetRowId(const GUID& instanceIdentifier) = 0; + + // Version 0.2 + + // Migrates from the current interface given. + // Returns true if supported (or is already same schema version); false if not. + // Throws on errors that occur during an attempted migration. + virtual bool MigrateFrom(IConfigurationDatabase* current) = 0; + + // Adds a new queue item for the given configuration set and object name. + virtual void AddQueueItem(const GUID& instanceIdentifier, const std::string& objectName); + + // Sets the queue item with the given object name as active. + virtual void SetActiveQueueItem(const std::string& objectName); + + // Gets all queue items in queue order (item at index 0 is active/next). + virtual std::vector> GetQueueItems(); + + // Removes the queue item with the given object name. + virtual void RemoveQueueItem(const std::string& objectName); + + // Version 0.3 + + // Gets a set from the database. + virtual ConfigurationSetPtr GetSet(const GUID& instanceIdentifier) = 0; + + // The tuple returned for status items. + using StatusItemTuple = std::tuple< + int64_t, + std::chrono::system_clock::time_point, + GUID, + bool, + std::optional, + int32_t, + std::optional, + std::string, + std::string, + ConfigurationUnitResultSource>; + + // Gets all status items that have changed since the given change identifier, in order of changes (last item is the new latest change to pass next). + virtual std::vector GetStatusSince(int64_t changeIdentifier); + + // Gets the latest change identifier and the set status items. + virtual std::tuple> GetStatusBaseline(); + + // Adds a new listener to the database. + virtual void AddListener(const std::string& objectName); + + // Removes a listener from the database. + virtual void RemoveListener(const std::string& objectName); + + // Gets all of the change listeners in the database. + virtual std::vector> GetChangeListeners(); + + // Updates a set's state. + virtual void UpdateSetState(const guid& setInstanceIdentifier, ConfigurationSetState state); + + // Updates whether a set is in the queue or not. + virtual void UpdateSetInQueue(const guid& setInstanceIdentifier, bool inQueue); + + // Updates a unit's state. + virtual void UpdateUnitState(const guid& setInstanceIdentifier, const ConfigurationSetChangeDataPtr& changeData); + + // Gets a set's state. + virtual ConfigurationSetState GetSetState(const guid& instanceIdentifier); + + // Gets a set's first apply time. + virtual std::chrono::system_clock::time_point GetSetFirstApply(const guid& instanceIdentifier); + + // Gets a set's latest apply begin time. + virtual std::chrono::system_clock::time_point GetSetApplyBegun(const guid& instanceIdentifier); + + // Gets a set's latest apply end time. + virtual std::chrono::system_clock::time_point GetSetApplyEnded(const guid& instanceIdentifier); + + // Gets a unit's state. + virtual ConfigurationUnitState GetUnitState(const guid& instanceIdentifier); + + // Gets a unit's latest result information. + virtual std::optional> GetUnitResultInformation(const guid& instanceIdentifier); + }; +} diff --git a/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.cpp b/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.cpp index 5218885a99..641b3ecc6d 100644 --- a/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.cpp +++ b/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.cpp @@ -1,171 +1,171 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DefaultSetGroupProcessor.h" -#include "ConfigurationSetApplyProcessor.h" -#include "ExceptionResultHelpers.h" -#include "TestGroupSettingsResult.h" -#include "TestSettingsResult.h" - -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - // Determines whether a configuration unit should be tested. - bool ShouldTestDuringTest(const Configuration::ConfigurationUnit& unit) - { - return unit.IsActive() && unit.Intent() != ConfigurationUnitIntent::Inform; - } - } - - void DefaultSetGroupProcessor::Initialize(const ConfigurationSet& set, const IConfigurationSetProcessor& setProcessor, ConfigThreadGlobals& threadGlobals, bool consistencyCheckOnly) - { - m_set = set; - m_setProcessor = setProcessor; - m_threadGlobals = &threadGlobals; - m_consistencyCheckOnly = consistencyCheckOnly; - } - - Windows::Foundation::IInspectable DefaultSetGroupProcessor::Group() - { - return m_set; - } - - Windows::Foundation::IAsyncOperation DefaultSetGroupProcessor::TestGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler) - { - auto strongThis = get_strong(); - co_await resume_background(); - - auto cancellation = co_await get_cancellation_token(); - cancellation.enable_propagation(); - - auto result = make_self>(); - result->Group(m_set); - - try - { - for (const auto& unit : m_set.Units()) - { - ThrowIf(cancellation()); - - AICLI_LOG_DIRECT(m_threadGlobals->GetDiagnosticLogger(), Config, Info, << "Testing configuration unit: `" << AppInstaller::Utility::ConvertToUTF8(unit.Identifier()) << "` [" << AppInstaller::Utility::ConvertToUTF8(unit.Type()) << ']'); - - ITestSettingsResult settingsResult; - ConfigurationTestResult testResult = ConfigurationTestResult::Unknown; - auto unitResult = make_self>(); - - if (ShouldTestDuringTest(unit)) - { - IConfigurationUnitProcessor unitProcessor; - - try - { - unitProcessor = m_setProcessor.CreateUnitProcessor(unit); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - - // Check again as creating the unit processor could take time - ThrowIf(cancellation()); - - if (unitProcessor) - { - IConfigurationGroupProcessor groupProcessor = unitProcessor.try_as(); - - if (groupProcessor) - { - auto testOperation = groupProcessor.TestGroupSettingsAsync([&](const auto&, const ITestSettingsResult& unitResult) - { - progressHandler(nullptr, unitResult); - }); - - ITestGroupSettingsResult groupResult = co_await testOperation; - - // Put all of the group's unit results in our unit results - for (const auto& groupUnitResult : groupResult.UnitResults()) - { - result->AppendUnitResult(groupUnitResult); - } - - // Convert group result into a unit result for the group - auto testSettingsResult = make_self>(); - testSettingsResult->Unit(unit); - testSettingsResult->TestResult(groupResult.TestResult()); - testSettingsResult->ResultInformation(groupResult.ResultInformation()); - settingsResult = *testSettingsResult; - } - else - { - try - { - settingsResult = unitProcessor.TestSettings(); - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), unitResult); - } - } - } - } - else - { - testResult = ConfigurationTestResult::NotRun; - } - - if (FAILED(unitResult->ResultCode())) - { - testResult = ConfigurationTestResult::Failed; - } - - // Check if we need to construct our own result object - if (!settingsResult) - { - auto testSettingsResult = make_self>(); - testSettingsResult->Unit(unit); - testSettingsResult->TestResult(testResult); - testSettingsResult->ResultInformation(*unitResult); - settingsResult = *testSettingsResult; - } - - result->AppendUnitResult(settingsResult); - - try - { - progressHandler(nullptr, settingsResult); - } - CATCH_LOG(); - } - - co_return *result; - } - catch (...) - { - ExtractUnitResultInformation(std::current_exception(), result->ResultInformationInternal()); - throw; - } - } - - Windows::Foundation::IAsyncOperation DefaultSetGroupProcessor::ApplyGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler) - { - auto strongThis = get_strong(); - co_await resume_background(); - - ConfigurationSetApplyProcessor applyProcessor{ m_set, m_setProcessor, { std::move(progressHandler), co_await winrt::get_cancellation_token() } }; - applyProcessor.Process(m_consistencyCheckOnly); - - co_return applyProcessor.Result(); - } - - void DefaultSetGroupProcessor::ThrowIf(bool cancellation) - { - if (cancellation) - { - AICLI_LOG_DIRECT(m_threadGlobals->GetDiagnosticLogger(), Config, Warning, << "Operation cancelled"); - throw winrt::hresult_canceled(); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DefaultSetGroupProcessor.h" +#include "ConfigurationSetApplyProcessor.h" +#include "ExceptionResultHelpers.h" +#include "TestGroupSettingsResult.h" +#include "TestSettingsResult.h" + +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + // Determines whether a configuration unit should be tested. + bool ShouldTestDuringTest(const Configuration::ConfigurationUnit& unit) + { + return unit.IsActive() && unit.Intent() != ConfigurationUnitIntent::Inform; + } + } + + void DefaultSetGroupProcessor::Initialize(const ConfigurationSet& set, const IConfigurationSetProcessor& setProcessor, ConfigThreadGlobals& threadGlobals, bool consistencyCheckOnly) + { + m_set = set; + m_setProcessor = setProcessor; + m_threadGlobals = &threadGlobals; + m_consistencyCheckOnly = consistencyCheckOnly; + } + + Windows::Foundation::IInspectable DefaultSetGroupProcessor::Group() + { + return m_set; + } + + Windows::Foundation::IAsyncOperation DefaultSetGroupProcessor::TestGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler) + { + auto strongThis = get_strong(); + co_await resume_background(); + + auto cancellation = co_await get_cancellation_token(); + cancellation.enable_propagation(); + + auto result = make_self>(); + result->Group(m_set); + + try + { + for (const auto& unit : m_set.Units()) + { + ThrowIf(cancellation()); + + AICLI_LOG_DIRECT(m_threadGlobals->GetDiagnosticLogger(), Config, Info, << "Testing configuration unit: `" << AppInstaller::Utility::ConvertToUTF8(unit.Identifier()) << "` [" << AppInstaller::Utility::ConvertToUTF8(unit.Type()) << ']'); + + ITestSettingsResult settingsResult; + ConfigurationTestResult testResult = ConfigurationTestResult::Unknown; + auto unitResult = make_self>(); + + if (ShouldTestDuringTest(unit)) + { + IConfigurationUnitProcessor unitProcessor; + + try + { + unitProcessor = m_setProcessor.CreateUnitProcessor(unit); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + + // Check again as creating the unit processor could take time + ThrowIf(cancellation()); + + if (unitProcessor) + { + IConfigurationGroupProcessor groupProcessor = unitProcessor.try_as(); + + if (groupProcessor) + { + auto testOperation = groupProcessor.TestGroupSettingsAsync([&](const auto&, const ITestSettingsResult& unitResult) + { + progressHandler(nullptr, unitResult); + }); + + ITestGroupSettingsResult groupResult = co_await testOperation; + + // Put all of the group's unit results in our unit results + for (const auto& groupUnitResult : groupResult.UnitResults()) + { + result->AppendUnitResult(groupUnitResult); + } + + // Convert group result into a unit result for the group + auto testSettingsResult = make_self>(); + testSettingsResult->Unit(unit); + testSettingsResult->TestResult(groupResult.TestResult()); + testSettingsResult->ResultInformation(groupResult.ResultInformation()); + settingsResult = *testSettingsResult; + } + else + { + try + { + settingsResult = unitProcessor.TestSettings(); + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), unitResult); + } + } + } + } + else + { + testResult = ConfigurationTestResult::NotRun; + } + + if (FAILED(unitResult->ResultCode())) + { + testResult = ConfigurationTestResult::Failed; + } + + // Check if we need to construct our own result object + if (!settingsResult) + { + auto testSettingsResult = make_self>(); + testSettingsResult->Unit(unit); + testSettingsResult->TestResult(testResult); + testSettingsResult->ResultInformation(*unitResult); + settingsResult = *testSettingsResult; + } + + result->AppendUnitResult(settingsResult); + + try + { + progressHandler(nullptr, settingsResult); + } + CATCH_LOG(); + } + + co_return *result; + } + catch (...) + { + ExtractUnitResultInformation(std::current_exception(), result->ResultInformationInternal()); + throw; + } + } + + Windows::Foundation::IAsyncOperation DefaultSetGroupProcessor::ApplyGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler) + { + auto strongThis = get_strong(); + co_await resume_background(); + + ConfigurationSetApplyProcessor applyProcessor{ m_set, m_setProcessor, { std::move(progressHandler), co_await winrt::get_cancellation_token() } }; + applyProcessor.Process(m_consistencyCheckOnly); + + co_return applyProcessor.Result(); + } + + void DefaultSetGroupProcessor::ThrowIf(bool cancellation) + { + if (cancellation) + { + AICLI_LOG_DIRECT(m_threadGlobals->GetDiagnosticLogger(), Config, Warning, << "Operation cancelled"); + throw winrt::hresult_canceled(); + } + } +} diff --git a/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.h b/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.h index 01c151a748..409efdc9b9 100644 --- a/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.h +++ b/src/Microsoft.Management.Configuration/DefaultSetGroupProcessor.h @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" -#include "ConfigThreadGlobals.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Implements the default group processing for configuration sets when the set processor doesn't handle it. - struct DefaultSetGroupProcessor : winrt::implements - { - using ConfigurationSet = Configuration::ConfigurationSet; - - DefaultSetGroupProcessor() = default; - - void Initialize(const ConfigurationSet& set, const IConfigurationSetProcessor& setProcessor, ConfigThreadGlobals& threadGlobals, bool consistencyCheckOnly = false); - - IInspectable Group(); - - Windows::Foundation::IAsyncOperation TestGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler); - - Windows::Foundation::IAsyncOperation ApplyGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler); - - private: - void ThrowIf(bool cancellation); - - ConfigurationSet m_set = nullptr; - IConfigurationSetProcessor m_setProcessor; - ConfigThreadGlobals* m_threadGlobals = nullptr; - bool m_consistencyCheckOnly = false; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "ConfigThreadGlobals.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Implements the default group processing for configuration sets when the set processor doesn't handle it. + struct DefaultSetGroupProcessor : winrt::implements + { + using ConfigurationSet = Configuration::ConfigurationSet; + + DefaultSetGroupProcessor() = default; + + void Initialize(const ConfigurationSet& set, const IConfigurationSetProcessor& setProcessor, ConfigThreadGlobals& threadGlobals, bool consistencyCheckOnly = false); + + IInspectable Group(); + + Windows::Foundation::IAsyncOperation TestGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler); + + Windows::Foundation::IAsyncOperation ApplyGroupSettingsAsync(Windows::Foundation::EventHandler progressHandler); + + private: + void ThrowIf(bool cancellation); + + ConfigurationSet m_set = nullptr; + IConfigurationSetProcessor m_setProcessor; + ConfigThreadGlobals* m_threadGlobals = nullptr; + bool m_consistencyCheckOnly = false; + }; +} diff --git a/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.cpp b/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.cpp index 7f76e5b20e..df8ba48ef5 100644 --- a/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.cpp +++ b/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.cpp @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "DiagnosticInformationInstance.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - void DiagnosticInformationInstance::Initialize(DiagnosticLevel level, std::wstring_view message) - { - m_level = level; - m_message = message; - } - - DiagnosticLevel DiagnosticInformationInstance::Level() - { - return m_level; - } - - void DiagnosticInformationInstance::Level(DiagnosticLevel value) - { - m_level = value; - } - - hstring DiagnosticInformationInstance::Message() - { - return m_message; - } - - void DiagnosticInformationInstance::Message(const hstring& value) - { - m_message = value; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "DiagnosticInformationInstance.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void DiagnosticInformationInstance::Initialize(DiagnosticLevel level, std::wstring_view message) + { + m_level = level; + m_message = message; + } + + DiagnosticLevel DiagnosticInformationInstance::Level() + { + return m_level; + } + + void DiagnosticInformationInstance::Level(DiagnosticLevel value) + { + m_level = value; + } + + hstring DiagnosticInformationInstance::Message() + { + return m_message; + } + + void DiagnosticInformationInstance::Message(const hstring& value) + { + m_message = value; + } +} diff --git a/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.h b/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.h index 17f88ccc04..d40ab8cb8e 100644 --- a/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.h +++ b/src/Microsoft.Management.Configuration/DiagnosticInformationInstance.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct DiagnosticInformationInstance : winrt::implements - { - DiagnosticInformationInstance() = default; - - void Initialize(DiagnosticLevel level, std::wstring_view message); - - DiagnosticLevel Level(); - void Level(DiagnosticLevel value); - - hstring Message(); - void Message(const hstring& value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - DiagnosticLevel m_level = DiagnosticLevel::Verbose; - hstring m_message; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct DiagnosticInformationInstance : winrt::implements + { + DiagnosticInformationInstance() = default; + + void Initialize(DiagnosticLevel level, std::wstring_view message); + + DiagnosticLevel Level(); + void Level(DiagnosticLevel value); + + hstring Message(); + void Message(const hstring& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + DiagnosticLevel m_level = DiagnosticLevel::Verbose; + hstring m_message; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/ExceptionResultHelpers.h b/src/Microsoft.Management.Configuration/ExceptionResultHelpers.h index b3cf1dd398..1eeed2028e 100644 --- a/src/Microsoft.Management.Configuration/ExceptionResultHelpers.h +++ b/src/Microsoft.Management.Configuration/ExceptionResultHelpers.h @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - template - void ExtractUnitResultInformation(std::exception_ptr exc, const UnitResult& unitResult) - { - try - { - std::rethrow_exception(exc); - } - catch (const winrt::hresult_error& hre) - { - unitResult->Initialize(hre.code(), hre.message()); - } - catch (const std::exception& ex) - { - unitResult->Initialize(E_FAIL, AppInstaller::Utility::ConvertToUTF16(ex.what())); - } - catch (...) - { - unitResult->Initialize(E_FAIL, hstring{}); - } - - AICLI_LOG(Config, Error, << "Unit Processor exception: " << AppInstaller::Logging::SetHRFormat << unitResult->ResultCode() << " [" << AppInstaller::Utility::ConvertToUTF8(unitResult->Description()) << "]"); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + template + void ExtractUnitResultInformation(std::exception_ptr exc, const UnitResult& unitResult) + { + try + { + std::rethrow_exception(exc); + } + catch (const winrt::hresult_error& hre) + { + unitResult->Initialize(hre.code(), hre.message()); + } + catch (const std::exception& ex) + { + unitResult->Initialize(E_FAIL, AppInstaller::Utility::ConvertToUTF16(ex.what())); + } + catch (...) + { + unitResult->Initialize(E_FAIL, hstring{}); + } + + AICLI_LOG(Config, Error, << "Unit Processor exception: " << AppInstaller::Logging::SetHRFormat << unitResult->ResultCode() << " [" << AppInstaller::Utility::ConvertToUTF8(unitResult->Description()) << "]"); + } +} diff --git a/src/Microsoft.Management.Configuration/Filesystem.cpp b/src/Microsoft.Management.Configuration/Filesystem.cpp index bf22896c5d..a4c526f9ed 100644 --- a/src/Microsoft.Management.Configuration/Filesystem.cpp +++ b/src/Microsoft.Management.Configuration/Filesystem.cpp @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Filesystem.h" - -using namespace std::string_view_literals; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace anon - { - constexpr std::string_view s_Configuration_LocalState = "Configuration"sv; - } - - AppInstaller::Filesystem::PathDetails GetPathDetailsFor(PathName path, bool forDisplay) - { - AppInstaller::Filesystem::PathDetails result; - // We should not create directories by default when they are retrieved for display purposes. - result.Create = !forDisplay; - - switch (path) - { - case PathName::LocalState: - result = GetPathDetailsFor(AppInstaller::Filesystem::PathName::UnpackagedLocalStateRoot, forDisplay); - result.Path /= anon::s_Configuration_LocalState; - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Filesystem.h" + +using namespace std::string_view_literals; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace anon + { + constexpr std::string_view s_Configuration_LocalState = "Configuration"sv; + } + + AppInstaller::Filesystem::PathDetails GetPathDetailsFor(PathName path, bool forDisplay) + { + AppInstaller::Filesystem::PathDetails result; + // We should not create directories by default when they are retrieved for display purposes. + result.Create = !forDisplay; + + switch (path) + { + case PathName::LocalState: + result = GetPathDetailsFor(AppInstaller::Filesystem::PathName::UnpackagedLocalStateRoot, forDisplay); + result.Path /= anon::s_Configuration_LocalState; + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return result; + } +} diff --git a/src/Microsoft.Management.Configuration/Filesystem.h b/src/Microsoft.Management.Configuration/Filesystem.h index 934066af69..da77144501 100644 --- a/src/Microsoft.Management.Configuration/Filesystem.h +++ b/src/Microsoft.Management.Configuration/Filesystem.h @@ -1,18 +1,18 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Paths used by configuration. - enum class PathName - { - // Local state root for configuration. - LocalState, - }; - - // Gets the PathDetails used for the given path. - // This is exposed primarily to allow for testing, GetPathTo should be preferred. - AppInstaller::Filesystem::PathDetails GetPathDetailsFor(PathName path, bool forDisplay = false); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Paths used by configuration. + enum class PathName + { + // Local state root for configuration. + LocalState, + }; + + // Gets the PathDetails used for the given path. + // This is exposed primarily to allow for testing, GetPathTo should be preferred. + AppInstaller::Filesystem::PathDetails GetPathDetailsFor(PathName path, bool forDisplay = false); +} diff --git a/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.cpp b/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.cpp index b1afe07663..1cbe2bc253 100644 --- a/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.cpp +++ b/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.cpp @@ -1,43 +1,43 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "FindUnitProcessorsOptions.h" -#include "FindUnitProcessorsOptions.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - hstring FindUnitProcessorsOptions::SearchPaths() const - { - return m_searchPaths; - } - - void FindUnitProcessorsOptions::SearchPaths(hstring const& value) - { - m_searchPaths = value; - } - - bool FindUnitProcessorsOptions::SearchPathsExclusive() const - { - return m_searchPathsExclusive; - } - - void FindUnitProcessorsOptions::SearchPathsExclusive(bool value) - { - m_searchPathsExclusive = value; - } - - Microsoft::Management::Configuration::ConfigurationUnitDetailFlags FindUnitProcessorsOptions::UnitDetailFlags() const - { - return m_detailFlags; - } - - void FindUnitProcessorsOptions::UnitDetailFlags(Microsoft::Management::Configuration::ConfigurationUnitDetailFlags value) - { - m_detailFlags = value; - } - - HRESULT STDMETHODCALLTYPE FindUnitProcessorsOptions::SetLifetimeWatcher(IUnknown* watcher) - { - return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "FindUnitProcessorsOptions.h" +#include "FindUnitProcessorsOptions.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + hstring FindUnitProcessorsOptions::SearchPaths() const + { + return m_searchPaths; + } + + void FindUnitProcessorsOptions::SearchPaths(hstring const& value) + { + m_searchPaths = value; + } + + bool FindUnitProcessorsOptions::SearchPathsExclusive() const + { + return m_searchPathsExclusive; + } + + void FindUnitProcessorsOptions::SearchPathsExclusive(bool value) + { + m_searchPathsExclusive = value; + } + + Microsoft::Management::Configuration::ConfigurationUnitDetailFlags FindUnitProcessorsOptions::UnitDetailFlags() const + { + return m_detailFlags; + } + + void FindUnitProcessorsOptions::UnitDetailFlags(Microsoft::Management::Configuration::ConfigurationUnitDetailFlags value) + { + m_detailFlags = value; + } + + HRESULT STDMETHODCALLTYPE FindUnitProcessorsOptions::SetLifetimeWatcher(IUnknown* watcher) + { + return AppInstaller::WinRT::LifetimeWatcherBase::SetLifetimeWatcher(watcher); + } +} diff --git a/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.h b/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.h index dc9764f2af..d7ada27d35 100644 --- a/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.h +++ b/src/Microsoft.Management.Configuration/FindUnitProcessorsOptions.h @@ -14,12 +14,12 @@ namespace winrt::Microsoft::Management::Configuration::implementation hstring SearchPaths() const; void SearchPaths(hstring const& value); - bool SearchPathsExclusive() const; + bool SearchPathsExclusive() const; void SearchPathsExclusive(bool value); - Microsoft::Management::Configuration::ConfigurationUnitDetailFlags UnitDetailFlags() const; - void UnitDetailFlags(Microsoft::Management::Configuration::ConfigurationUnitDetailFlags value); - + Microsoft::Management::Configuration::ConfigurationUnitDetailFlags UnitDetailFlags() const; + void UnitDetailFlags(Microsoft::Management::Configuration::ConfigurationUnitDetailFlags value); + HRESULT STDMETHODCALLTYPE SetLifetimeWatcher(IUnknown* watcher); #if !defined(INCLUDE_ONLY_INTERFACE_METHODS) diff --git a/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp b/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp index ca9abcd2a7..0bb96874bb 100644 --- a/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp +++ b/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.cpp @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "GetConfigurationSetDetailsResult.h" -#include "GetConfigurationSetDetailsResult.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - GetConfigurationSetDetailsResult::GetConfigurationSetDetailsResult() : - m_unitResults(multi_threaded_vector()) - {}; - - const Windows::Foundation::Collections::IVector& GetConfigurationSetDetailsResult::UnitResultsVector() - { - return m_unitResults; - } - - Windows::Foundation::Collections::IVectorView GetConfigurationSetDetailsResult::UnitResults() - { - return m_unitResults.GetView(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "GetConfigurationSetDetailsResult.h" +#include "GetConfigurationSetDetailsResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + GetConfigurationSetDetailsResult::GetConfigurationSetDetailsResult() : + m_unitResults(multi_threaded_vector()) + {}; + + const Windows::Foundation::Collections::IVector& GetConfigurationSetDetailsResult::UnitResultsVector() + { + return m_unitResults; + } + + Windows::Foundation::Collections::IVectorView GetConfigurationSetDetailsResult::UnitResults() + { + return m_unitResults.GetView(); + } +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.h b/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.h index cd7b8ce4f8..be14a92622 100644 --- a/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.h +++ b/src/Microsoft.Management.Configuration/GetConfigurationSetDetailsResult.h @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "GetConfigurationSetDetailsResult.g.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct GetConfigurationSetDetailsResult : GetConfigurationSetDetailsResultT - { - using GetConfigurationUnitDetailsResult = Configuration::GetConfigurationUnitDetailsResult; - - GetConfigurationSetDetailsResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - const Windows::Foundation::Collections::IVector& UnitResultsVector(); -#endif - - Windows::Foundation::Collections::IVectorView UnitResults(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - Windows::Foundation::Collections::IVector m_unitResults; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "GetConfigurationSetDetailsResult.g.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct GetConfigurationSetDetailsResult : GetConfigurationSetDetailsResultT + { + using GetConfigurationUnitDetailsResult = Configuration::GetConfigurationUnitDetailsResult; + + GetConfigurationSetDetailsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + const Windows::Foundation::Collections::IVector& UnitResultsVector(); +#endif + + Windows::Foundation::Collections::IVectorView UnitResults(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::Collections::IVector m_unitResults; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp index f941a97293..455bd78930 100644 --- a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp +++ b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.cpp @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "GetConfigurationUnitDetailsResult.h" -#include "GetConfigurationUnitDetailsResult.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - void GetConfigurationUnitDetailsResult::Unit(ConfigurationUnit value) - { - m_unit = std::move(value); - } - - void GetConfigurationUnitDetailsResult::Details(IConfigurationUnitProcessorDetails value) - { - m_details = std::move(value); - } - - void GetConfigurationUnitDetailsResult::ResultInformation(IConfigurationUnitResultInformation value) - { - m_resultInformation = std::move(value); - } - - ConfigurationUnit GetConfigurationUnitDetailsResult::Unit() - { - return m_unit; - } - - IConfigurationUnitProcessorDetails GetConfigurationUnitDetailsResult::Details() - { - return m_details; - } - - IConfigurationUnitResultInformation GetConfigurationUnitDetailsResult::ResultInformation() - { - return m_resultInformation; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "GetConfigurationUnitDetailsResult.h" +#include "GetConfigurationUnitDetailsResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void GetConfigurationUnitDetailsResult::Unit(ConfigurationUnit value) + { + m_unit = std::move(value); + } + + void GetConfigurationUnitDetailsResult::Details(IConfigurationUnitProcessorDetails value) + { + m_details = std::move(value); + } + + void GetConfigurationUnitDetailsResult::ResultInformation(IConfigurationUnitResultInformation value) + { + m_resultInformation = std::move(value); + } + + ConfigurationUnit GetConfigurationUnitDetailsResult::Unit() + { + return m_unit; + } + + IConfigurationUnitProcessorDetails GetConfigurationUnitDetailsResult::Details() + { + return m_details; + } + + IConfigurationUnitResultInformation GetConfigurationUnitDetailsResult::ResultInformation() + { + return m_resultInformation; + } +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h index b3b3690eb3..edb64ca7ae 100644 --- a/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h +++ b/src/Microsoft.Management.Configuration/GetConfigurationUnitDetailsResult.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "GetConfigurationUnitDetailsResult.g.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct GetConfigurationUnitDetailsResult : GetConfigurationUnitDetailsResultT - { - using ConfigurationUnit = Configuration::ConfigurationUnit; - - GetConfigurationUnitDetailsResult() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Unit(ConfigurationUnit value); - void Details(IConfigurationUnitProcessorDetails value); - void ResultInformation(IConfigurationUnitResultInformation value); -#endif - - ConfigurationUnit Unit(); - IConfigurationUnitProcessorDetails Details(); - IConfigurationUnitResultInformation ResultInformation(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ConfigurationUnit m_unit = nullptr; - IConfigurationUnitProcessorDetails m_details; - IConfigurationUnitResultInformation m_resultInformation; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "GetConfigurationUnitDetailsResult.g.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct GetConfigurationUnitDetailsResult : GetConfigurationUnitDetailsResultT + { + using ConfigurationUnit = Configuration::ConfigurationUnit; + + GetConfigurationUnitDetailsResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Unit(ConfigurationUnit value); + void Details(IConfigurationUnitProcessorDetails value); + void ResultInformation(IConfigurationUnitResultInformation value); +#endif + + ConfigurationUnit Unit(); + IConfigurationUnitProcessorDetails Details(); + IConfigurationUnitResultInformation ResultInformation(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationUnit m_unit = nullptr; + IConfigurationUnitProcessorDetails m_details; + IConfigurationUnitResultInformation m_resultInformation; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.cpp b/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.cpp index 467cf3ec6d..08a36ed49d 100644 --- a/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.cpp +++ b/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.cpp @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "GetConfigurationUnitSettingsResult.h" -#include "GetConfigurationUnitSettingsResult.g.cpp" -#include "ConfigurationUnitResultInformation.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - GetConfigurationUnitSettingsResult::GetConfigurationUnitSettingsResult() : - m_resultInformation(*make_self>()) - { - } - - void GetConfigurationUnitSettingsResult::ResultInformation(const IConfigurationUnitResultInformation& resultInformation) - { - m_resultInformation = resultInformation; - } - - IConfigurationUnitResultInformation GetConfigurationUnitSettingsResult::ResultInformation() const - { - return m_resultInformation; - } - - Windows::Foundation::Collections::ValueSet GetConfigurationUnitSettingsResult::Settings() - { - return m_settings; - } - - void GetConfigurationUnitSettingsResult::Settings(Windows::Foundation::Collections::ValueSet&& value) - { - m_settings = std::move(value); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "GetConfigurationUnitSettingsResult.h" +#include "GetConfigurationUnitSettingsResult.g.cpp" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + GetConfigurationUnitSettingsResult::GetConfigurationUnitSettingsResult() : + m_resultInformation(*make_self>()) + { + } + + void GetConfigurationUnitSettingsResult::ResultInformation(const IConfigurationUnitResultInformation& resultInformation) + { + m_resultInformation = resultInformation; + } + + IConfigurationUnitResultInformation GetConfigurationUnitSettingsResult::ResultInformation() const + { + return m_resultInformation; + } + + Windows::Foundation::Collections::ValueSet GetConfigurationUnitSettingsResult::Settings() + { + return m_settings; + } + + void GetConfigurationUnitSettingsResult::Settings(Windows::Foundation::Collections::ValueSet&& value) + { + m_settings = std::move(value); + } +} diff --git a/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.h b/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.h index 641eb5edf6..305d5aba88 100644 --- a/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.h +++ b/src/Microsoft.Management.Configuration/GetConfigurationUnitSettingsResult.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "GetConfigurationUnitSettingsResult.g.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct GetConfigurationUnitSettingsResult : GetConfigurationUnitSettingsResultT - { - GetConfigurationUnitSettingsResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void ResultInformation(const IConfigurationUnitResultInformation& resultInformation); - void Settings(Windows::Foundation::Collections::ValueSet&& value); -#endif - - IConfigurationUnitResultInformation ResultInformation() const; - Windows::Foundation::Collections::ValueSet Settings(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - IConfigurationUnitResultInformation m_resultInformation; - Windows::Foundation::Collections::ValueSet m_settings; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "GetConfigurationUnitSettingsResult.g.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct GetConfigurationUnitSettingsResult : GetConfigurationUnitSettingsResultT + { + GetConfigurationUnitSettingsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void ResultInformation(const IConfigurationUnitResultInformation& resultInformation); + void Settings(Windows::Foundation::Collections::ValueSet&& value); +#endif + + IConfigurationUnitResultInformation ResultInformation() const; + Windows::Foundation::Collections::ValueSet Settings(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + IConfigurationUnitResultInformation m_resultInformation; + Windows::Foundation::Collections::ValueSet m_settings; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl index 3d0616350e..9fc005d7bd 100644 --- a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl +++ b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.idl @@ -1,1145 +1,1145 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -namespace Microsoft.Management.Configuration -{ - [contractversion(4)] - apicontract Contract{}; - - // The current state of a configuration set. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationSetState - { - // The state of the configuration set is unknown. - Unknown, - // The configuration set is in the queue to be applied. - Pending, - // The configuration set is actively being applied. - InProgress, - // The configuration set has completed being applied. - Completed, - }; - - // The current state of a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationUnitState - { - // The state of the configuration unit is unknown. - Unknown, - // The configuration unit is in the queue to be applied. - Pending, - // The configuration unit is actively being applied. - InProgress, - // The configuration unit has completed being applied. - Completed, - // The configuration unit was not applied due to external factors. - Skipped, - }; - - // Defines the type of detail probing that is allowed about a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 1)] - [flags] - enum ConfigurationUnitDetailFlags - { - // For completeness. - None = 0, - // Only reads details from local data. - Local = 0x1, - // Can query the catalog information for details, but will not download any modules. - Catalog = 0x2, - // Can only read data, not write or execute. - ReadOnly = Local | Catalog, - // Can download modules, but not load them. - Download = 0x4, - // Can load modules for details. - Load = 0x8, - }; - - // The source of a result; for instance, the part of the system that generated a failure. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationUnitResultSource - { - // The source is not known, or more likely, there was no failure. - None, - // The result came from inside the configuration system; this is likely a bug. - Internal, - // The configuration set was ill formed. For instance, referencing a configuration unit - // that does not exist or a dependency that is not present. - ConfigurationSet, - // The external module that processes the configuration unit generated the result. - UnitProcessing, - // The system state is causing the error. - SystemState, - // The configuration unit was not run due to a precondition not being met. - // For example, if a dependency fails to be applied, this will be set. - Precondition, - }; - - // A security context; typically used to define the context in which a configuration unit should be processed. - [contract(Microsoft.Management.Configuration.Contract, 3)] - enum SecurityContext - { - // The default value; indicates that the configuration unit should be processed at the caller's current level. - Current, - // A standard user without administrator privileges. - Restricted, - // A user with administrator privileges. - Elevated, - }; - - // Information about the environment in which a configuration is processed. - [contract(Microsoft.Management.Configuration.Contract, 3)] - runtimeclass ConfigurationEnvironment - { - // The security context in which a configuration is processed. - SecurityContext Context; - - // A string that identifies the processor that should be used for the configuration. - String ProcessorIdentifier; - - // Processor specific properties. - Windows.Foundation.Collections.IMap ProcessorProperties{ get; }; - }; - - // Information on a result for a single unit of configuration. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationUnitResultInformation - { - // The error code of the failure. - HRESULT ResultCode{ get; }; - - // The short description of the failure. - String Description{ get; }; - - // A more detailed error message appropriate for diagnosing the root cause of an error. - String Details{ get; }; - - // The source of the result. - ConfigurationUnitResultSource ResultSource{ get; }; - } - - // Provides information for a specific configuration unit setting. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationUnitSettingDetails - { - // The name of the setting. - String Identifier{ get; }; - // A brief description of the setting. - String Title{ get; }; - // The detailed description of the setting. - String Description{ get; }; - // Whether the setting is a key. This is used to determine if different settings are in conflict. - Boolean IsKey{ get; }; - // Whether a non-empty value for the setting is required. - Boolean IsRequired{ get; }; - // Whether the setting should be serialized in order to be applied on another system. - // When the current settings are retrieved from the system, this can be used to exclude settings that are not relevant to a future application of the unit of configuration. - Boolean IsInformational{ get; }; - // The data type for the value of this setting. - Windows.Foundation.PropertyType Type{ get; }; - - // The schema fragment to be used for this setting. - // Using a single entry of the `properties` object from a JSON schema is suggested, for instance: - // { - // "$schema": "https://json-schema.org/draft/2020-12/schema", - // "scope": { - // "title": "Target scope", - // "description": "The scope at which the configuration should be applied.", - // "type": "string", - // "enum": [ - // "machine", - // "user" - // ] - // } - // } - String Schema{ get; }; - } - - // Provides information for a specific configuration unit within the runtime. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationUnitProcessorDetails - { - // The type of configuration unit. - String UnitType{ get; }; - // A description of the unit of configuration. - String UnitDescription{ get; }; - // The URI of the documentation for the unit of configuration. - Windows.Foundation.Uri UnitDocumentationUri{ get; }; - // The URI of the icon for the unit of configuration. - Windows.Foundation.Uri UnitIconUri{ get; }; - // The name of the module containing the unit of configuration. - String ModuleName{ get; }; - // The type of the module containing the unit of configuration. - String ModuleType{ get; }; - // The source of the module containing the unit of configuration. - String ModuleSource{ get; }; - // The description of the module containing the unit of configuration. - String ModuleDescription{ get; }; - // The URI of the documentation for the module containing the unit of configuration. - Windows.Foundation.Uri ModuleDocumentationUri{ get; }; - // The URI for the published module containing the unit of configuration. - Windows.Foundation.Uri PublishedModuleUri{ get; }; - // The version of the module containing the unit of configuration. - String Version{ get; }; - // The publishing date of the module containing the unit of configuration. - Windows.Foundation.DateTime PublishedDate{ get; }; - // Whether the module is already present on the system. - Boolean IsLocal{ get; }; - // The author of the module containing the unit of configuration. - String Author{ get; }; - // The publisher of the module containing the unit of configuration. - String Publisher{ get; }; - // The signing information of the module files containing the unit of configuration. - // May contain Windows.Security.Cryptography.Certificates.Certificate or Windows.Security.Cryptography.Certificates.CertificateChain. - Windows.Foundation.Collections.IVectorView SigningInformation{ get; }; - // The settings information for the unit of configuration. - Windows.Foundation.Collections.IVectorView Settings{ get; }; - // Does it comes from a public repository - Boolean IsPublic{ get; }; - } - - // Provides information for a specific configuration unit within the runtime. - [contract(Microsoft.Management.Configuration.Contract, 2)] - interface IConfigurationUnitProcessorDetails2 requires IConfigurationUnitProcessorDetails - { - // Determines if this configuration unit should be treated as a group. - Boolean IsGroup{ get; }; - } - - // Provides information for a specific configuration unit within the runtime. - [contract(Microsoft.Management.Configuration.Contract, 4)] - interface IConfigurationUnitProcessorDetails3 requires IConfigurationUnitProcessorDetails2 - { - // The path of the resource. - String Path{ get; }; - } - - // Defines how the configuration unit is to be used within the configuration system. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationUnitIntent - { - // The configuration unit will only be used to Test the current system state. - Assert, - // The configuration unit will only be used to Get the current system state. - Inform, - // The configuration unit will be used to Apply the current system state. - // The configuration unit will be used to Test and Get the current system state as part of that process. - Apply, - // The configuration unit's intent is unknown. - // Currently not supported. - Unknown, - }; - - // A single unit of configuration. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationUnit - { - // Creates an empty configuration unit for authoring purposes. - ConfigurationUnit(); - - // The type of the unit being configured; not a name for this instance. - String Type; - - // An identifier used to uniquely identify the instance of a configuration unit on the system. - // May change upon applying a configuration unit from a historical context if some part of the set changed. - Guid InstanceIdentifier{ get; }; - - // The identifier name of this instance within the set. - String Identifier; - - // Describes how this configuration unit will be used. - ConfigurationUnitIntent Intent; - - // The `Identifier` values of the configuration units that this unit depends on. - Windows.Foundation.Collections.IVector Dependencies; - - // The metadata properties associated with the configuration unit. - // TODO: This is being used to drive processing decisions when it should not. - // There is ongoing discussion about this at https://github.com/PowerShell/DSC/issues/47 and https://github.com/PowerShell/DSC/issues/92. - Windows.Foundation.Collections.ValueSet Metadata; - - // Contains the values that are for use by the configuration unit itself. - Windows.Foundation.Collections.ValueSet Settings; - - // Contains information on the origin of the configuration unit. - // May be null if ConfigurationProcessor.GetDetailsAsync has not been called yet. - IConfigurationUnitProcessorDetails Details{ get; }; - - // The current state of the configuration unit. - ConfigurationUnitState State{ get; }; - - // Contains information on the result of the latest attempt to apply the configuration unit. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // Controls whether this unit will be processed when part of a set. - Boolean IsActive; - - // Creates a copy of this configuration unit, with the following notes: - // InstanceIdentifier will be a new value - // Identifier will be empty - // Dependencies, Metadata, and Settings will by new containers with identical values inside - // Details will be the same value (not a copy, just another reference) - // State, ResultInformation, and ShouldApply will be their default constructed state - // IsGroup will be false and child units will not be copied - // Environment will be copied - ConfigurationUnit Copy(); - - [contract(Microsoft.Management.Configuration.Contract, 2)] - { - // Determines if this configuration unit should be treated as a group. - // A configuration unit group treats its `Settings` as the definition of child units. - Boolean IsGroup; - - // The configuration units that are part of this unit (if IsGroup is true). - Windows.Foundation.Collections.IVector Units; - } - - [contract(Microsoft.Management.Configuration.Contract, 3)] - { - // The environment in which to process the configuration unit. - // This defines the initial processing environment state used by the configuration system, - // and may be overridden by the processor later. - ConfigurationEnvironment Environment{ get; }; - } - } - - // The change event type that has occurred for a configuration set change. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationSetChangeEventType - { - Unknown, - // The change event was for the set state. Only ConfigurationSetChangeData.SetState is valid. - SetStateChanged, - // The change event was for the unit state. All ConfigurationSetChangeData properties are valid. - UnitStateChanged, - }; - - // The change data sent about changes to a specific set. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationSetChangeData - { - // The change event type that occurred. - ConfigurationSetChangeEventType Change{ get; }; - - // The state of the configuration set for this event (the ConfigurationSet can be used to get the current state, which may be different). - ConfigurationSetState SetState{ get; }; - - // The state of the configuration unit for this event (the ConfigurationUnit can be used to get the current state, which may be different). - ConfigurationUnitState UnitState{ get; }; - - // Contains information on the result of the attempt to apply the configuration unit. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // The configuration unit whose state changed. - ConfigurationUnit Unit{ get; }; - } - - // The definition of a configuration parameter; a value that may be provided to alter the processing of a configuration set. - [contract(Microsoft.Management.Configuration.Contract, 2)] - runtimeclass ConfigurationParameter - { - ConfigurationParameter(); - - // The name of the parameter. - String Name; - - // The description of the parameter. - String Description; - - // The metadata properties associated with the configuration parameter. - Windows.Foundation.Collections.ValueSet Metadata; - - // The value of the parameter should be treated as a secret; not logged our output. - Boolean IsSecure; - - // The type of the parameter. - Windows.Foundation.PropertyType Type; - - // The default value; may be null if no default is provided. - Object DefaultValue; - - // The set of allowed values; a null container indicates that the values are not restricted. - Windows.Foundation.Collections.IVector AllowedValues; - - // The minimum length for a parameter type with the concept (string, array, etc.). - UInt32 MinimumLength; - - // The maximum length for a parameter type with the concept (string, array, etc.). - UInt32 MaximumLength; - - // For comparable parameter types, the minimum value allowed (integrals, DateTime, etc.). - Object MinimumValue; - - // For comparable parameter types, the maximum value allowed (integrals, DateTime, etc.). - Object MaximumValue; - - // The input value; may be null if no value is provided. - Object ProvidedValue; - } - - // A configuration set contains a collection of configuration units and details about the set. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationSet - { - // Creates an empty configuration set for authoring purposes. - ConfigurationSet(); - - // The name of the set; if from a file this could be the file name. - String Name; - // The origin of the set; if it came from a repository it could be the remote URL (ex. https://github.com/microsoft/winget-cli.git). - String Origin; - // The location of the configuration set on the local filesystem. - // If this set is from history, the file may no longer exist or it's contents may have been changed. - String Path; - - // An identifier used to uniquely identify the instance of a configuration set on the system. - // May change upon applying a configuration set from a historical context if some part of the set changed. - Guid InstanceIdentifier{ get; }; - // The state that the set is in. - ConfigurationSetState State{ get; }; - // The time that this set was recorded with intent to apply. - Windows.Foundation.DateTime FirstApply{ get; }; - // The time that this set was last started to be applied. - Windows.Foundation.DateTime ApplyBegun{ get; }; - // The time that this set was last finished being applied (does not indicate success). - Windows.Foundation.DateTime ApplyEnded{ get; }; - - // The configuration units that are part of this set. - Windows.Foundation.Collections.IVector Units; - - // The schema version to use for the set. - // Will be set to the schema version when read in, and default to the latest if created manually. - // Setting SchemaVersion to a different value will change SchemaUri. - String SchemaVersion; - - // Only changes for this set are sent to this event. - // This includes things like: start/stop of the entire set for application, start/stop of a unit for application. - event Windows.Foundation.TypedEventHandler ConfigurationSetChange; - - // Writes the configuration set to the given stream. - void Serialize(Windows.Storage.Streams.IOutputStream stream); - - // Removes the configuration set from the recorded history, if present. - void Remove(); - - [contract(Microsoft.Management.Configuration.Contract, 2)] - { - // The metadata properties associated with the configuration set. - Windows.Foundation.Collections.ValueSet Metadata; - - // The parameters that this configuration set supports. - Windows.Foundation.Collections.IVector Parameters; - - // The variables that this configuration set uses. - Windows.Foundation.Collections.ValueSet Variables; - - // The schema URI to use for the set. - // Will be set to the schema version when read in, and default to the latest if created manually. - // Setting SchemaUri to a different value will change SchemaVersion. - Windows.Foundation.Uri SchemaUri; - } - - [contract(Microsoft.Management.Configuration.Contract, 3)] - { - // The environment in which to process the configuration set. - // This defines the initial processing environment state used by the configuration system, - // and may be overridden by the processor later. - ConfigurationEnvironment Environment{ get; }; - - // Gets the union of environments as defined by all of the active units within the set. - Windows.Foundation.Collections.IVector GetUnitEnvironments(); - } - } - - // The result of applying the settings with an IConfigurationUnitProcessor. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IApplySettingsResult - { - // The configuration unit. - ConfigurationUnit Unit{ get; }; - - // Indicates whether a reboot is required after the settings were applied. - Boolean RebootRequired{ get; }; - - // The result of applying the configuration unit. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // Informs the caller of the result of running a Test. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationTestResult - { - // The result is unknown. - Unknown, - // The system is in the state described by the configuration. - Positive, - // The system is not in the state described by the configuration. - Negative, - // Running the test failed. - Failed, - // The test was not run because it was not applicable. - NotRun, - }; - - // The result of testing the settings with an IConfigurationUnitProcessor. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface ITestSettingsResult - { - // The configuration unit. - ConfigurationUnit Unit{ get; }; - - // The result (if any) of running Test on the configuration unit. - ConfigurationTestResult TestResult{ get; }; - - // The result of testing the configuration unit. - // This is not the response for the test, but rather contains information about the actual attempt to run the test. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // The result of getting the settings with an IConfigurationUnitProcessor. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IGetSettingsResult - { - // The configuration unit. - ConfigurationUnit Unit{ get; }; - - // The current state of the system for the configuration unit. - Windows.Foundation.Collections.ValueSet Settings{ get; }; - - // The result of getting the configuration unit settings. - // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // The result of getting the settings for all instances of a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 2)] - interface IGetAllSettingsResult - { - // The configuration unit. - ConfigurationUnit Unit{ get; }; - - // The current state of the system for all the instances of the configuration unit. - Windows.Foundation.Collections.IVector Settings{ get; }; - - // The result of getting the configuration unit settings. - // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // The result of getting all of the units for a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 4)] - interface IGetAllUnitsResult - { - // The configuration unit. - ConfigurationUnit Unit{ get; }; - - // The units retrieved for the given unit. - Windows.Foundation.Collections.IVector Units{ get; }; - - // The result of getting the configuration units. - // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // Provides access to a specific configuration unit within the runtime. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationUnitProcessor - { - // The configuration unit that the processor was created for. - ConfigurationUnit Unit{ get; }; - - // Determines if the system is already in the state described by the configuration unit. - ITestSettingsResult TestSettings(); - - // Gets the current system state for the configuration unit. - IGetSettingsResult GetSettings(); - - // Applies the state described in the configuration unit. - IApplySettingsResult ApplySettings(); - } - - [contract(Microsoft.Management.Configuration.Contract, 2)] - interface IGetAllSettingsConfigurationUnitProcessor requires IConfigurationUnitProcessor - { - // Gets the current system state for all the instances of the configuration unit, each of which has their own settings. - IGetAllSettingsResult GetAllSettings(); - } - - [contract(Microsoft.Management.Configuration.Contract, 4)] - interface IGetAllUnitsConfigurationUnitProcessor requires IConfigurationUnitProcessor - { - // Gets all units for the configuration unit. - IGetAllUnitsResult GetAllUnits(); - } - - // Controls the lifetime of operations for a single configuration set. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationSetProcessor - { - // Gets the configuration unit processor details for the given unit. - IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); - - // Creates a configuration unit processor for the given unit. - IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit unit); - } - - // The definition of find unit processors option. - [contract(Microsoft.Management.Configuration.Contract, 4)] - runtimeclass FindUnitProcessorsOptions - { - FindUnitProcessorsOptions(); - - // Search paths for finding configuration unit processors. The value has the same format as - // PATH environment variable. Paths are absolute paths separated by semicolons(;). - String SearchPaths; - - // If the search paths provided are exclusive. - Boolean SearchPathsExclusive; - - // Defines the type of detail probing that is allowed when finding unit processors. - ConfigurationUnitDetailFlags UnitDetailFlags; - } - - // Find unit processors. - [contract(Microsoft.Management.Configuration.Contract, 4)] - interface IFindUnitProcessorsSetProcessor requires IConfigurationSetProcessor - { - // Find unit processors. - Windows.Foundation.Collections.IVector FindUnitProcessors(FindUnitProcessorsOptions findOptions); - } - - // The result of applying an individual unit settings with an IConfigurationGroupProcessor. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IApplyGroupMemberSettingsResult - { - // The configuration unit. - ConfigurationUnit Unit{ get; }; - - // The state of the unit. - // Properties other than `Unit` are not valid unless this value is `Completed`. - ConfigurationUnitState State{ get; }; - - // Will be true if the configuration unit was in the desired state (Test returns true) prior to the apply action. - Boolean PreviouslyInDesiredState{ get; }; - - // Indicates whether a reboot is required after the settings were applied. - Boolean RebootRequired{ get; }; - - // The result of applying the configuration unit. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // The result of applying the settings with an IConfigurationGroupProcessor. - [contract(Microsoft.Management.Configuration.Contract, 2)] - interface IApplyGroupSettingsResult - { - // The configuration group object (set or unit). - Object Group{ get; }; - - // Indicates whether a reboot is required after the settings were applied. - Boolean RebootRequired{ get; }; - - // The result of applying the configuration unit group. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // Results for each configuration unit in the group. - Windows.Foundation.Collections.IVector UnitResults{ get; }; - } - - // The result of testing the settings with an IConfigurationGroupProcessor. - [contract(Microsoft.Management.Configuration.Contract, 2)] - interface ITestGroupSettingsResult - { - // The configuration group object (set or unit). - Object Group{ get; }; - - // The result (if any) of running Test on the configuration unit group. - ConfigurationTestResult TestResult{ get; }; - - // The result of testing the configuration unit group. - // This is not the response for the test, but rather contains information about the actual attempt to run the test. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // Results for each configuration unit in the group. - Windows.Foundation.Collections.IVector UnitResults{ get; }; - } - - // Provides access to a specific configuration unit group within the runtime. - // An object returned by `CreateUnitProcessor` or `CreateSetProcessor` can implement this interface to indicate that it can take - // the responsibility of executing the units that it contains. - [contract(Microsoft.Management.Configuration.Contract, 2)] - interface IConfigurationGroupProcessor - { - // The configuration group object (set or unit). - Object Group{ get; }; - - // Determines if the system is already in the state described by the configuration unit group. - // Progress is expected for every descendant unit and finally the group unit itself. - Windows.Foundation.IAsyncOperation TestGroupSettingsAsync(Windows.Foundation.EventHandler progressHandler); - - // Applies the state described in the configuration unit group. - // Progress is expected for every descendant unit and finally the group unit itself. - Windows.Foundation.IAsyncOperation ApplyGroupSettingsAsync(Windows.Foundation.EventHandler progressHandler); - } - - // The level of the diagnostic information. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum DiagnosticLevel - { - Verbose, - Informational, - Warning, - Error, - Critical, - }; - - // Enables diagnostic information from the configuration system to be inspected/stored by callers. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IDiagnosticInformation - { - // Indicates the importance of the diagnostic information. - DiagnosticLevel Level{ get; }; - - // The diagnostic message. - String Message{ get; }; - } - - // Allows different runtimes to provide specialized handling of configuration processing. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationSetProcessorFactory - { - // Creates a configuration set processor for the given set. - IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet configurationSet); - - // Diagnostics event; useful for logging and/or verbose output. - event Windows.Foundation.EventHandler Diagnostics; - - // Indicates the minimum importance desired for diagnostics. - DiagnosticLevel MinimumLevel; - } - - // The change event type that has occurred. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationChangeEventType - { - Unknown, - SetAdded, - SetStateChanged, - SetRemoved, - }; - - // The change data sent about changes to sets. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationChangeData - { - // The change event type that occurred. - ConfigurationChangeEventType Change{ get; }; - - // The identifier used to uniquely identify the instance of a configuration set on the system. - Guid InstanceIdentifier{ get; }; - - // The state of the configuration set for this event (the ConfigurationSet can be used to get the current state, which may be different). - ConfigurationSetState State{ get; }; - } - - // The result of calling OpenConfigurationSet, containing either the set or details about the failure. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass OpenConfigurationSetResult - { - // The configuration set if successful; null otherwise. - ConfigurationSet Set{ get; }; - - // The result from opening the set. - HRESULT ResultCode{ get; }; - - // The field that is missing/invalid, if appropriate for the specific ResultCode. - String Field{ get; }; - - // The value of the field, if appropriate for the specific ResultCode. - String Value{ get; }; - - // The line number for the failure reason, if determined. - UInt32 Line{ get; }; - - // The column number for the failure reason, if determined. - UInt32 Column{ get; }; - } - - // The type of conflict between configuration sets that was detected. - [contract(Microsoft.Management.Configuration.Contract, 1)] - enum ConfigurationConflictType - { - Unknown, - // Indicates that the first configuration set has a matching name and origin to the second, which has already been applied. - // This is likely an update to the existing set, and should be applied as such, rather than an entirely new set. - MatchingOrigin, - // Indicates that the first configuration set is identical to the second, which has already been applied. - // This is based solely on the configuration unit settings. - IdenticalSetApplied, - // Indicates a conflict between the settings of two configuration units. - SettingsConflict, - }; - - // Describes a conflict between a setting of two configuration units. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationConflictSetting - { - // The name of the setting. - String Name{ get; }; - - // The value from the first configuration unit. - // These are the values from a `ValueSet`, and are thus required to be a `PropertyValue` or a 'ValueSet`. - Object FirstValue{ get; }; - - // The value from the second configuration unit. - // These are the values from a `ValueSet`, and are thus required to be a `PropertyValue` or a 'ValueSet`. - Object SecondValue{ get; }; - } - - // Describes a conflict between two configuration sets. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationConflict - { - // The type of conflict detected. - ConfigurationConflictType Conflict{ get; }; - - // The first of the configuration sets involved in the conflict. - ConfigurationSet FirstSet{ get; }; - - // The second of the configuration sets involved in the conflict. - ConfigurationSet SecondSet{ get; }; - - // The first of the configuration units involved in the conflict. - ConfigurationUnit FirstUnit{ get; }; - - // The second of the configuration units involved in the conflict. - ConfigurationUnit SecondUnit{ get; }; - - // Contains information about the particular settings that are conflicting. - Windows.Foundation.Collections.IVectorView Settings{ get; }; - } - - // The result of getting the configuration unit details. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass GetConfigurationUnitDetailsResult - { - // The configuration unit whose details were retrieved. - ConfigurationUnit Unit{ get; }; - - // The details, if they were able to be acquired successfully. - IConfigurationUnitProcessorDetails Details{ get; }; - - // The result of getting the configuration unit details. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // The result of getting the configuration set details. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass GetConfigurationSetDetailsResult - { - // The configuration unit whose details were retrieved. - Windows.Foundation.Collections.IVectorView UnitResults{ get; }; - } - - // Flags to control how a configuration set should be applied to the system. - [contract(Microsoft.Management.Configuration.Contract, 1)] - [flags] - enum ApplyConfigurationSetFlags - { - None = 0x0, - // Forces a new configuration set instance to be recorded when the set being applied matches a previous set's origin. - // The default behavior is to assume that the incoming set is an update to the existing set and overwrite it. - DoNotOverwriteMatchingOriginSet = 0x1, - // Does not apply the configuration set, only checks the configuration set for internal consistency. - PerformConsistencyCheckOnly = 0x2, - }; - - // The result of applying the settings for a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ApplyConfigurationUnitResult - { - // The configuration unit that was applied. - ConfigurationUnit Unit{ get; }; - - // The state of the configuration unit with regards to the current execution of ApplySet. - ConfigurationUnitState State{ get; }; - - // Will be true if the configuration unit was in the desired state (Test returns true) prior to the apply action. - Boolean PreviouslyInDesiredState{ get; }; - - // Indicates whether a reboot is required after the configuration unit was applied. - Boolean RebootRequired{ get; }; - - // The result of applying the configuration unit. - IConfigurationUnitResultInformation ResultInformation{ get; }; - } - - // The result of applying the settings for a configuration set. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ApplyConfigurationSetResult - { - // Results for each configuration unit in the set. - Windows.Foundation.Collections.IVectorView UnitResults{ get; }; - - // The overall result from applying the configuration set. - HRESULT ResultCode{ get; }; - } - - // The result of testing the settings for a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass TestConfigurationUnitResult - { - // The configuration unit that was tested. - ConfigurationUnit Unit{ get; }; - - // The result of testing the configuration unit. - // This is not the response for the test, but rather contains information about the actual attempt to run the test. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // The result (if any) of running Test on the configuration unit. - ConfigurationTestResult TestResult{ get; }; - } - - // The result of testing the settings for a configuration set. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass TestConfigurationSetResult - { - // Results for each configuration unit in the set. - Windows.Foundation.Collections.IVectorView UnitResults{ get; }; - - // The result (if any) of running Test on the configuration set. - // If this value is NotRun, every unit result will be NotRun. - // If this value is Positive, every unit result will be Positive (or NotRun with at least one being Positive). - // Any Negative result for a unit will result in this value being Negative. - // Any Failed result for a unit will result in this value being Failed (overriding the Negative statement above). - ConfigurationTestResult TestResult{ get; }; - } - - // The result of getting the settings for a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass GetConfigurationUnitSettingsResult - { - // The result of getting the configuration unit settings. - // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // The current state of the system for the configuration unit. - Windows.Foundation.Collections.ValueSet Settings { get; }; - } - - // The result of getting the settings for all the instances of a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 2)] - runtimeclass GetAllConfigurationUnitSettingsResult - { - // The result of getting the settings for all the instances of the configuration unit. - // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // The current state of the system for all the instances of the configuration unit. - Windows.Foundation.Collections.IVector Settings { get; }; - } - - // The result of getting the all units for a configuration unit. - [contract(Microsoft.Management.Configuration.Contract, 4)] - runtimeclass GetAllConfigurationUnitsResult - { - // The result of getting the all units for a configuration unit. - // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. - IConfigurationUnitResultInformation ResultInformation{ get; }; - - // The units retrieved for the given unit. - Windows.Foundation.Collections.IVector Units { get; }; - } - - // The configuration processor is responsible for the interactions with the system. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationProcessor - { - ConfigurationProcessor(IConfigurationSetProcessorFactory factory); - - // Diagnostics event; useful for logging and/or verbose output. - event Windows.Foundation.EventHandler Diagnostics; - - // Indicates the minimum importance desired for diagnostics. - DiagnosticLevel MinimumLevel; - - // Set the caller to used to identify the usage in telemetry events. - String Caller; - - // The identifier for the current activity, enabling multiple calls into the processor to be correlated. - Guid ActivityIdentifier; - - // If true, ETW events will be generated. Some of those events may be sent to Microsoft depending on the system settings. - Boolean GenerateTelemetryEvents; - - // Only top level configuration changes are sent to this event. - // This includes things like: creation of a new set for intent to run, start/stop of a set for application, deletion of a not started set. - event Windows.Foundation.TypedEventHandler ConfigurationChange; - - // Gets the configuration sets that have already been applied or with the intent to be applied (this may include in progress sets or those that are waiting on others). - Windows.Foundation.Collections.IVector GetConfigurationHistory(); - Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector > GetConfigurationHistoryAsync(); - - // Loads a configuration set from the given stream. - OpenConfigurationSetResult OpenConfigurationSet(Windows.Storage.Streams.IInputStream stream); - Windows.Foundation.IAsyncOperation OpenConfigurationSetAsync(Windows.Storage.Streams.IInputStream stream); - - // Checks for conflicts amongst the configuration sets provided, optionally including the configuration sets already applied to the system. - Windows.Foundation.Collections.IVector CheckForConflicts(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); - Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector > CheckForConflictsAsync(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); - - // Gets the details for all configuration units in a set. - GetConfigurationSetDetailsResult GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailFlags detailFlags); - Windows.Foundation.IAsyncOperationWithProgress GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailFlags detailFlags); - - // Gets the details for a configuration unit. - GetConfigurationUnitDetailsResult GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); - Windows.Foundation.IAsyncOperation GetUnitDetailsAsync(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); - - // Applies the configuration set state. - ApplyConfigurationSetResult ApplySet(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); - Windows.Foundation.IAsyncOperationWithProgress ApplySetAsync(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); - - // Tests the configuration set state. - TestConfigurationSetResult TestSet(ConfigurationSet configurationSet); - Windows.Foundation.IAsyncOperationWithProgress TestSetAsync(ConfigurationSet configurationSet); - - // Gets the current configuration unit settings. - GetConfigurationUnitSettingsResult GetUnitSettings(ConfigurationUnit unit); - Windows.Foundation.IAsyncOperation GetUnitSettingsAsync(ConfigurationUnit unit); - - [contract(Microsoft.Management.Configuration.Contract, 2)] - { - // Gets the current settings for all the instances of a configuration unit. - GetAllConfigurationUnitSettingsResult GetAllUnitSettings(ConfigurationUnit unit); - Windows.Foundation.IAsyncOperation GetAllUnitSettingsAsync(ConfigurationUnit unit); - } - - [contract(Microsoft.Management.Configuration.Contract, 4)] - { - // Gets all configuration units for the given unit type. - // Returned units may be of types other than the one passed in. - GetAllConfigurationUnitsResult GetAllUnits(ConfigurationUnit unit); - Windows.Foundation.IAsyncOperation GetAllUnitsAsync(ConfigurationUnit unit); - - // Find unit processors. - Windows.Foundation.Collections.IVector FindUnitProcessors(FindUnitProcessorsOptions findOptions); - Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector > FindUnitProcessorsAsync(FindUnitProcessorsOptions findOptions); - - // Apply the current configuration unit. - ApplyConfigurationUnitResult ApplyUnit(ConfigurationUnit unit); - Windows.Foundation.IAsyncOperation ApplyUnitAsync(ConfigurationUnit unit); - - // Test the current configuration unit. - TestConfigurationUnitResult TestUnit(ConfigurationUnit unit); - Windows.Foundation.IAsyncOperation TestUnitAsync(ConfigurationUnit unit); - } - } - - // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. - [contract(Microsoft.Management.Configuration.Contract, 1)] - interface IConfigurationStatics - { - // Creates an empty configuration unit. - ConfigurationUnit CreateConfigurationUnit(); - - // Creates an empty configuration set. - ConfigurationSet CreateConfigurationSet(); - - // Creates a processor factory for the given handler. - Windows.Foundation.IAsyncOperation CreateConfigurationSetProcessorFactoryAsync(String handler); - - // Creates a processor from the given factory. - ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory factory); - - // Whether configuration is enabled. - Boolean IsConfigurationAvailable{ get; }; - - // Enables configuration. Requires store access. - Windows.Foundation.IAsyncActionWithProgress EnsureConfigurationAvailableAsync(); - } - - // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. - [contract(Microsoft.Management.Configuration.Contract, 2)] - interface IConfigurationStatics2 requires IConfigurationStatics - { - // Creates an empty configuration parameter. - ConfigurationParameter CreateConfigurationParameter(); - } - - // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. - [contract(Microsoft.Management.Configuration.Contract, 4)] - interface IConfigurationStatics3 requires IConfigurationStatics2 - { - // Creates an empty configuration parameter. - FindUnitProcessorsOptions CreateFindUnitProcessorsOptions(); - } - - // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. - [contract(Microsoft.Management.Configuration.Contract, 1)] - runtimeclass ConfigurationStaticFunctions : [default]IConfigurationStatics, IConfigurationStatics2, IConfigurationStatics3 - { - ConfigurationStaticFunctions(); - } - - /// Force midl3 to generate vector marshalling info. - declare - { - // Due to the way that metadata (WinMD) based marshalling works, in order for any of these to be IIterable, they need to be - // included in the manifest of the package. Update the DumpProxyStubRegistrationsCommand to add any new types to make it easier - // to iterate over these collections, especially in C#. - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVector; - } - - // Provides a way to centralize the distribution of interfaces relevant to specific implementations of IConfigurationSetProcessorFactory. - namespace SetProcessorFactory - { - // The same as PowerShell ExecutionPolicy: - // https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies - enum PwshConfigurationProcessorPolicy - { - Unrestricted = 0, - RemoteSigned = 1, - AllSigned = 2, - Restricted = 3, - Bypass = 4, - Undefined = 5, - Default = RemoteSigned, - }; - - enum PwshConfigurationProcessorLocation - { - CurrentUser = 0, - AllUsers = 1, - WinGetModulePath = 2, - Custom = 3, - Default = WinGetModulePath, - }; - - // The properties provided by the "pwsh" processor factory. - interface IPwshConfigurationSetProcessorFactoryProperties - { - // The module paths to add to the processor. - // This will be in addition to any paths added by the processor and those inherent to PowerShell. - Windows.Foundation.Collections.IVectorView AdditionalModulePaths; - - // The execution policy to apply; must be set before taking actions with the processor. - PwshConfigurationProcessorPolicy Policy; - - // The location to install modules. Default is WinGetModulePath - PwshConfigurationProcessorLocation Location; - - // The custom location. Only applicable for Scope.Custom - String CustomLocation; - }; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Microsoft.Management.Configuration +{ + [contractversion(4)] + apicontract Contract{}; + + // The current state of a configuration set. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationSetState + { + // The state of the configuration set is unknown. + Unknown, + // The configuration set is in the queue to be applied. + Pending, + // The configuration set is actively being applied. + InProgress, + // The configuration set has completed being applied. + Completed, + }; + + // The current state of a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationUnitState + { + // The state of the configuration unit is unknown. + Unknown, + // The configuration unit is in the queue to be applied. + Pending, + // The configuration unit is actively being applied. + InProgress, + // The configuration unit has completed being applied. + Completed, + // The configuration unit was not applied due to external factors. + Skipped, + }; + + // Defines the type of detail probing that is allowed about a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 1)] + [flags] + enum ConfigurationUnitDetailFlags + { + // For completeness. + None = 0, + // Only reads details from local data. + Local = 0x1, + // Can query the catalog information for details, but will not download any modules. + Catalog = 0x2, + // Can only read data, not write or execute. + ReadOnly = Local | Catalog, + // Can download modules, but not load them. + Download = 0x4, + // Can load modules for details. + Load = 0x8, + }; + + // The source of a result; for instance, the part of the system that generated a failure. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationUnitResultSource + { + // The source is not known, or more likely, there was no failure. + None, + // The result came from inside the configuration system; this is likely a bug. + Internal, + // The configuration set was ill formed. For instance, referencing a configuration unit + // that does not exist or a dependency that is not present. + ConfigurationSet, + // The external module that processes the configuration unit generated the result. + UnitProcessing, + // The system state is causing the error. + SystemState, + // The configuration unit was not run due to a precondition not being met. + // For example, if a dependency fails to be applied, this will be set. + Precondition, + }; + + // A security context; typically used to define the context in which a configuration unit should be processed. + [contract(Microsoft.Management.Configuration.Contract, 3)] + enum SecurityContext + { + // The default value; indicates that the configuration unit should be processed at the caller's current level. + Current, + // A standard user without administrator privileges. + Restricted, + // A user with administrator privileges. + Elevated, + }; + + // Information about the environment in which a configuration is processed. + [contract(Microsoft.Management.Configuration.Contract, 3)] + runtimeclass ConfigurationEnvironment + { + // The security context in which a configuration is processed. + SecurityContext Context; + + // A string that identifies the processor that should be used for the configuration. + String ProcessorIdentifier; + + // Processor specific properties. + Windows.Foundation.Collections.IMap ProcessorProperties{ get; }; + }; + + // Information on a result for a single unit of configuration. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IConfigurationUnitResultInformation + { + // The error code of the failure. + HRESULT ResultCode{ get; }; + + // The short description of the failure. + String Description{ get; }; + + // A more detailed error message appropriate for diagnosing the root cause of an error. + String Details{ get; }; + + // The source of the result. + ConfigurationUnitResultSource ResultSource{ get; }; + } + + // Provides information for a specific configuration unit setting. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IConfigurationUnitSettingDetails + { + // The name of the setting. + String Identifier{ get; }; + // A brief description of the setting. + String Title{ get; }; + // The detailed description of the setting. + String Description{ get; }; + // Whether the setting is a key. This is used to determine if different settings are in conflict. + Boolean IsKey{ get; }; + // Whether a non-empty value for the setting is required. + Boolean IsRequired{ get; }; + // Whether the setting should be serialized in order to be applied on another system. + // When the current settings are retrieved from the system, this can be used to exclude settings that are not relevant to a future application of the unit of configuration. + Boolean IsInformational{ get; }; + // The data type for the value of this setting. + Windows.Foundation.PropertyType Type{ get; }; + + // The schema fragment to be used for this setting. + // Using a single entry of the `properties` object from a JSON schema is suggested, for instance: + // { + // "$schema": "https://json-schema.org/draft/2020-12/schema", + // "scope": { + // "title": "Target scope", + // "description": "The scope at which the configuration should be applied.", + // "type": "string", + // "enum": [ + // "machine", + // "user" + // ] + // } + // } + String Schema{ get; }; + } + + // Provides information for a specific configuration unit within the runtime. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IConfigurationUnitProcessorDetails + { + // The type of configuration unit. + String UnitType{ get; }; + // A description of the unit of configuration. + String UnitDescription{ get; }; + // The URI of the documentation for the unit of configuration. + Windows.Foundation.Uri UnitDocumentationUri{ get; }; + // The URI of the icon for the unit of configuration. + Windows.Foundation.Uri UnitIconUri{ get; }; + // The name of the module containing the unit of configuration. + String ModuleName{ get; }; + // The type of the module containing the unit of configuration. + String ModuleType{ get; }; + // The source of the module containing the unit of configuration. + String ModuleSource{ get; }; + // The description of the module containing the unit of configuration. + String ModuleDescription{ get; }; + // The URI of the documentation for the module containing the unit of configuration. + Windows.Foundation.Uri ModuleDocumentationUri{ get; }; + // The URI for the published module containing the unit of configuration. + Windows.Foundation.Uri PublishedModuleUri{ get; }; + // The version of the module containing the unit of configuration. + String Version{ get; }; + // The publishing date of the module containing the unit of configuration. + Windows.Foundation.DateTime PublishedDate{ get; }; + // Whether the module is already present on the system. + Boolean IsLocal{ get; }; + // The author of the module containing the unit of configuration. + String Author{ get; }; + // The publisher of the module containing the unit of configuration. + String Publisher{ get; }; + // The signing information of the module files containing the unit of configuration. + // May contain Windows.Security.Cryptography.Certificates.Certificate or Windows.Security.Cryptography.Certificates.CertificateChain. + Windows.Foundation.Collections.IVectorView SigningInformation{ get; }; + // The settings information for the unit of configuration. + Windows.Foundation.Collections.IVectorView Settings{ get; }; + // Does it comes from a public repository + Boolean IsPublic{ get; }; + } + + // Provides information for a specific configuration unit within the runtime. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IConfigurationUnitProcessorDetails2 requires IConfigurationUnitProcessorDetails + { + // Determines if this configuration unit should be treated as a group. + Boolean IsGroup{ get; }; + } + + // Provides information for a specific configuration unit within the runtime. + [contract(Microsoft.Management.Configuration.Contract, 4)] + interface IConfigurationUnitProcessorDetails3 requires IConfigurationUnitProcessorDetails2 + { + // The path of the resource. + String Path{ get; }; + } + + // Defines how the configuration unit is to be used within the configuration system. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationUnitIntent + { + // The configuration unit will only be used to Test the current system state. + Assert, + // The configuration unit will only be used to Get the current system state. + Inform, + // The configuration unit will be used to Apply the current system state. + // The configuration unit will be used to Test and Get the current system state as part of that process. + Apply, + // The configuration unit's intent is unknown. + // Currently not supported. + Unknown, + }; + + // A single unit of configuration. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationUnit + { + // Creates an empty configuration unit for authoring purposes. + ConfigurationUnit(); + + // The type of the unit being configured; not a name for this instance. + String Type; + + // An identifier used to uniquely identify the instance of a configuration unit on the system. + // May change upon applying a configuration unit from a historical context if some part of the set changed. + Guid InstanceIdentifier{ get; }; + + // The identifier name of this instance within the set. + String Identifier; + + // Describes how this configuration unit will be used. + ConfigurationUnitIntent Intent; + + // The `Identifier` values of the configuration units that this unit depends on. + Windows.Foundation.Collections.IVector Dependencies; + + // The metadata properties associated with the configuration unit. + // TODO: This is being used to drive processing decisions when it should not. + // There is ongoing discussion about this at https://github.com/PowerShell/DSC/issues/47 and https://github.com/PowerShell/DSC/issues/92. + Windows.Foundation.Collections.ValueSet Metadata; + + // Contains the values that are for use by the configuration unit itself. + Windows.Foundation.Collections.ValueSet Settings; + + // Contains information on the origin of the configuration unit. + // May be null if ConfigurationProcessor.GetDetailsAsync has not been called yet. + IConfigurationUnitProcessorDetails Details{ get; }; + + // The current state of the configuration unit. + ConfigurationUnitState State{ get; }; + + // Contains information on the result of the latest attempt to apply the configuration unit. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // Controls whether this unit will be processed when part of a set. + Boolean IsActive; + + // Creates a copy of this configuration unit, with the following notes: + // InstanceIdentifier will be a new value + // Identifier will be empty + // Dependencies, Metadata, and Settings will by new containers with identical values inside + // Details will be the same value (not a copy, just another reference) + // State, ResultInformation, and ShouldApply will be their default constructed state + // IsGroup will be false and child units will not be copied + // Environment will be copied + ConfigurationUnit Copy(); + + [contract(Microsoft.Management.Configuration.Contract, 2)] + { + // Determines if this configuration unit should be treated as a group. + // A configuration unit group treats its `Settings` as the definition of child units. + Boolean IsGroup; + + // The configuration units that are part of this unit (if IsGroup is true). + Windows.Foundation.Collections.IVector Units; + } + + [contract(Microsoft.Management.Configuration.Contract, 3)] + { + // The environment in which to process the configuration unit. + // This defines the initial processing environment state used by the configuration system, + // and may be overridden by the processor later. + ConfigurationEnvironment Environment{ get; }; + } + } + + // The change event type that has occurred for a configuration set change. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationSetChangeEventType + { + Unknown, + // The change event was for the set state. Only ConfigurationSetChangeData.SetState is valid. + SetStateChanged, + // The change event was for the unit state. All ConfigurationSetChangeData properties are valid. + UnitStateChanged, + }; + + // The change data sent about changes to a specific set. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationSetChangeData + { + // The change event type that occurred. + ConfigurationSetChangeEventType Change{ get; }; + + // The state of the configuration set for this event (the ConfigurationSet can be used to get the current state, which may be different). + ConfigurationSetState SetState{ get; }; + + // The state of the configuration unit for this event (the ConfigurationUnit can be used to get the current state, which may be different). + ConfigurationUnitState UnitState{ get; }; + + // Contains information on the result of the attempt to apply the configuration unit. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // The configuration unit whose state changed. + ConfigurationUnit Unit{ get; }; + } + + // The definition of a configuration parameter; a value that may be provided to alter the processing of a configuration set. + [contract(Microsoft.Management.Configuration.Contract, 2)] + runtimeclass ConfigurationParameter + { + ConfigurationParameter(); + + // The name of the parameter. + String Name; + + // The description of the parameter. + String Description; + + // The metadata properties associated with the configuration parameter. + Windows.Foundation.Collections.ValueSet Metadata; + + // The value of the parameter should be treated as a secret; not logged our output. + Boolean IsSecure; + + // The type of the parameter. + Windows.Foundation.PropertyType Type; + + // The default value; may be null if no default is provided. + Object DefaultValue; + + // The set of allowed values; a null container indicates that the values are not restricted. + Windows.Foundation.Collections.IVector AllowedValues; + + // The minimum length for a parameter type with the concept (string, array, etc.). + UInt32 MinimumLength; + + // The maximum length for a parameter type with the concept (string, array, etc.). + UInt32 MaximumLength; + + // For comparable parameter types, the minimum value allowed (integrals, DateTime, etc.). + Object MinimumValue; + + // For comparable parameter types, the maximum value allowed (integrals, DateTime, etc.). + Object MaximumValue; + + // The input value; may be null if no value is provided. + Object ProvidedValue; + } + + // A configuration set contains a collection of configuration units and details about the set. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationSet + { + // Creates an empty configuration set for authoring purposes. + ConfigurationSet(); + + // The name of the set; if from a file this could be the file name. + String Name; + // The origin of the set; if it came from a repository it could be the remote URL (ex. https://github.com/microsoft/winget-cli.git). + String Origin; + // The location of the configuration set on the local filesystem. + // If this set is from history, the file may no longer exist or it's contents may have been changed. + String Path; + + // An identifier used to uniquely identify the instance of a configuration set on the system. + // May change upon applying a configuration set from a historical context if some part of the set changed. + Guid InstanceIdentifier{ get; }; + // The state that the set is in. + ConfigurationSetState State{ get; }; + // The time that this set was recorded with intent to apply. + Windows.Foundation.DateTime FirstApply{ get; }; + // The time that this set was last started to be applied. + Windows.Foundation.DateTime ApplyBegun{ get; }; + // The time that this set was last finished being applied (does not indicate success). + Windows.Foundation.DateTime ApplyEnded{ get; }; + + // The configuration units that are part of this set. + Windows.Foundation.Collections.IVector Units; + + // The schema version to use for the set. + // Will be set to the schema version when read in, and default to the latest if created manually. + // Setting SchemaVersion to a different value will change SchemaUri. + String SchemaVersion; + + // Only changes for this set are sent to this event. + // This includes things like: start/stop of the entire set for application, start/stop of a unit for application. + event Windows.Foundation.TypedEventHandler ConfigurationSetChange; + + // Writes the configuration set to the given stream. + void Serialize(Windows.Storage.Streams.IOutputStream stream); + + // Removes the configuration set from the recorded history, if present. + void Remove(); + + [contract(Microsoft.Management.Configuration.Contract, 2)] + { + // The metadata properties associated with the configuration set. + Windows.Foundation.Collections.ValueSet Metadata; + + // The parameters that this configuration set supports. + Windows.Foundation.Collections.IVector Parameters; + + // The variables that this configuration set uses. + Windows.Foundation.Collections.ValueSet Variables; + + // The schema URI to use for the set. + // Will be set to the schema version when read in, and default to the latest if created manually. + // Setting SchemaUri to a different value will change SchemaVersion. + Windows.Foundation.Uri SchemaUri; + } + + [contract(Microsoft.Management.Configuration.Contract, 3)] + { + // The environment in which to process the configuration set. + // This defines the initial processing environment state used by the configuration system, + // and may be overridden by the processor later. + ConfigurationEnvironment Environment{ get; }; + + // Gets the union of environments as defined by all of the active units within the set. + Windows.Foundation.Collections.IVector GetUnitEnvironments(); + } + } + + // The result of applying the settings with an IConfigurationUnitProcessor. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IApplySettingsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // Indicates whether a reboot is required after the settings were applied. + Boolean RebootRequired{ get; }; + + // The result of applying the configuration unit. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // Informs the caller of the result of running a Test. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationTestResult + { + // The result is unknown. + Unknown, + // The system is in the state described by the configuration. + Positive, + // The system is not in the state described by the configuration. + Negative, + // Running the test failed. + Failed, + // The test was not run because it was not applicable. + NotRun, + }; + + // The result of testing the settings with an IConfigurationUnitProcessor. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface ITestSettingsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // The result (if any) of running Test on the configuration unit. + ConfigurationTestResult TestResult{ get; }; + + // The result of testing the configuration unit. + // This is not the response for the test, but rather contains information about the actual attempt to run the test. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of getting the settings with an IConfigurationUnitProcessor. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IGetSettingsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // The current state of the system for the configuration unit. + Windows.Foundation.Collections.ValueSet Settings{ get; }; + + // The result of getting the configuration unit settings. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of getting the settings for all instances of a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IGetAllSettingsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // The current state of the system for all the instances of the configuration unit. + Windows.Foundation.Collections.IVector Settings{ get; }; + + // The result of getting the configuration unit settings. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of getting all of the units for a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 4)] + interface IGetAllUnitsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // The units retrieved for the given unit. + Windows.Foundation.Collections.IVector Units{ get; }; + + // The result of getting the configuration units. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // Provides access to a specific configuration unit within the runtime. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IConfigurationUnitProcessor + { + // The configuration unit that the processor was created for. + ConfigurationUnit Unit{ get; }; + + // Determines if the system is already in the state described by the configuration unit. + ITestSettingsResult TestSettings(); + + // Gets the current system state for the configuration unit. + IGetSettingsResult GetSettings(); + + // Applies the state described in the configuration unit. + IApplySettingsResult ApplySettings(); + } + + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IGetAllSettingsConfigurationUnitProcessor requires IConfigurationUnitProcessor + { + // Gets the current system state for all the instances of the configuration unit, each of which has their own settings. + IGetAllSettingsResult GetAllSettings(); + } + + [contract(Microsoft.Management.Configuration.Contract, 4)] + interface IGetAllUnitsConfigurationUnitProcessor requires IConfigurationUnitProcessor + { + // Gets all units for the configuration unit. + IGetAllUnitsResult GetAllUnits(); + } + + // Controls the lifetime of operations for a single configuration set. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IConfigurationSetProcessor + { + // Gets the configuration unit processor details for the given unit. + IConfigurationUnitProcessorDetails GetUnitProcessorDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); + + // Creates a configuration unit processor for the given unit. + IConfigurationUnitProcessor CreateUnitProcessor(ConfigurationUnit unit); + } + + // The definition of find unit processors option. + [contract(Microsoft.Management.Configuration.Contract, 4)] + runtimeclass FindUnitProcessorsOptions + { + FindUnitProcessorsOptions(); + + // Search paths for finding configuration unit processors. The value has the same format as + // PATH environment variable. Paths are absolute paths separated by semicolons(;). + String SearchPaths; + + // If the search paths provided are exclusive. + Boolean SearchPathsExclusive; + + // Defines the type of detail probing that is allowed when finding unit processors. + ConfigurationUnitDetailFlags UnitDetailFlags; + } + + // Find unit processors. + [contract(Microsoft.Management.Configuration.Contract, 4)] + interface IFindUnitProcessorsSetProcessor requires IConfigurationSetProcessor + { + // Find unit processors. + Windows.Foundation.Collections.IVector FindUnitProcessors(FindUnitProcessorsOptions findOptions); + } + + // The result of applying an individual unit settings with an IConfigurationGroupProcessor. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IApplyGroupMemberSettingsResult + { + // The configuration unit. + ConfigurationUnit Unit{ get; }; + + // The state of the unit. + // Properties other than `Unit` are not valid unless this value is `Completed`. + ConfigurationUnitState State{ get; }; + + // Will be true if the configuration unit was in the desired state (Test returns true) prior to the apply action. + Boolean PreviouslyInDesiredState{ get; }; + + // Indicates whether a reboot is required after the settings were applied. + Boolean RebootRequired{ get; }; + + // The result of applying the configuration unit. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of applying the settings with an IConfigurationGroupProcessor. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IApplyGroupSettingsResult + { + // The configuration group object (set or unit). + Object Group{ get; }; + + // Indicates whether a reboot is required after the settings were applied. + Boolean RebootRequired{ get; }; + + // The result of applying the configuration unit group. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // Results for each configuration unit in the group. + Windows.Foundation.Collections.IVector UnitResults{ get; }; + } + + // The result of testing the settings with an IConfigurationGroupProcessor. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface ITestGroupSettingsResult + { + // The configuration group object (set or unit). + Object Group{ get; }; + + // The result (if any) of running Test on the configuration unit group. + ConfigurationTestResult TestResult{ get; }; + + // The result of testing the configuration unit group. + // This is not the response for the test, but rather contains information about the actual attempt to run the test. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // Results for each configuration unit in the group. + Windows.Foundation.Collections.IVector UnitResults{ get; }; + } + + // Provides access to a specific configuration unit group within the runtime. + // An object returned by `CreateUnitProcessor` or `CreateSetProcessor` can implement this interface to indicate that it can take + // the responsibility of executing the units that it contains. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IConfigurationGroupProcessor + { + // The configuration group object (set or unit). + Object Group{ get; }; + + // Determines if the system is already in the state described by the configuration unit group. + // Progress is expected for every descendant unit and finally the group unit itself. + Windows.Foundation.IAsyncOperation TestGroupSettingsAsync(Windows.Foundation.EventHandler progressHandler); + + // Applies the state described in the configuration unit group. + // Progress is expected for every descendant unit and finally the group unit itself. + Windows.Foundation.IAsyncOperation ApplyGroupSettingsAsync(Windows.Foundation.EventHandler progressHandler); + } + + // The level of the diagnostic information. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum DiagnosticLevel + { + Verbose, + Informational, + Warning, + Error, + Critical, + }; + + // Enables diagnostic information from the configuration system to be inspected/stored by callers. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IDiagnosticInformation + { + // Indicates the importance of the diagnostic information. + DiagnosticLevel Level{ get; }; + + // The diagnostic message. + String Message{ get; }; + } + + // Allows different runtimes to provide specialized handling of configuration processing. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IConfigurationSetProcessorFactory + { + // Creates a configuration set processor for the given set. + IConfigurationSetProcessor CreateSetProcessor(ConfigurationSet configurationSet); + + // Diagnostics event; useful for logging and/or verbose output. + event Windows.Foundation.EventHandler Diagnostics; + + // Indicates the minimum importance desired for diagnostics. + DiagnosticLevel MinimumLevel; + } + + // The change event type that has occurred. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationChangeEventType + { + Unknown, + SetAdded, + SetStateChanged, + SetRemoved, + }; + + // The change data sent about changes to sets. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationChangeData + { + // The change event type that occurred. + ConfigurationChangeEventType Change{ get; }; + + // The identifier used to uniquely identify the instance of a configuration set on the system. + Guid InstanceIdentifier{ get; }; + + // The state of the configuration set for this event (the ConfigurationSet can be used to get the current state, which may be different). + ConfigurationSetState State{ get; }; + } + + // The result of calling OpenConfigurationSet, containing either the set or details about the failure. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass OpenConfigurationSetResult + { + // The configuration set if successful; null otherwise. + ConfigurationSet Set{ get; }; + + // The result from opening the set. + HRESULT ResultCode{ get; }; + + // The field that is missing/invalid, if appropriate for the specific ResultCode. + String Field{ get; }; + + // The value of the field, if appropriate for the specific ResultCode. + String Value{ get; }; + + // The line number for the failure reason, if determined. + UInt32 Line{ get; }; + + // The column number for the failure reason, if determined. + UInt32 Column{ get; }; + } + + // The type of conflict between configuration sets that was detected. + [contract(Microsoft.Management.Configuration.Contract, 1)] + enum ConfigurationConflictType + { + Unknown, + // Indicates that the first configuration set has a matching name and origin to the second, which has already been applied. + // This is likely an update to the existing set, and should be applied as such, rather than an entirely new set. + MatchingOrigin, + // Indicates that the first configuration set is identical to the second, which has already been applied. + // This is based solely on the configuration unit settings. + IdenticalSetApplied, + // Indicates a conflict between the settings of two configuration units. + SettingsConflict, + }; + + // Describes a conflict between a setting of two configuration units. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationConflictSetting + { + // The name of the setting. + String Name{ get; }; + + // The value from the first configuration unit. + // These are the values from a `ValueSet`, and are thus required to be a `PropertyValue` or a 'ValueSet`. + Object FirstValue{ get; }; + + // The value from the second configuration unit. + // These are the values from a `ValueSet`, and are thus required to be a `PropertyValue` or a 'ValueSet`. + Object SecondValue{ get; }; + } + + // Describes a conflict between two configuration sets. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationConflict + { + // The type of conflict detected. + ConfigurationConflictType Conflict{ get; }; + + // The first of the configuration sets involved in the conflict. + ConfigurationSet FirstSet{ get; }; + + // The second of the configuration sets involved in the conflict. + ConfigurationSet SecondSet{ get; }; + + // The first of the configuration units involved in the conflict. + ConfigurationUnit FirstUnit{ get; }; + + // The second of the configuration units involved in the conflict. + ConfigurationUnit SecondUnit{ get; }; + + // Contains information about the particular settings that are conflicting. + Windows.Foundation.Collections.IVectorView Settings{ get; }; + } + + // The result of getting the configuration unit details. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass GetConfigurationUnitDetailsResult + { + // The configuration unit whose details were retrieved. + ConfigurationUnit Unit{ get; }; + + // The details, if they were able to be acquired successfully. + IConfigurationUnitProcessorDetails Details{ get; }; + + // The result of getting the configuration unit details. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of getting the configuration set details. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass GetConfigurationSetDetailsResult + { + // The configuration unit whose details were retrieved. + Windows.Foundation.Collections.IVectorView UnitResults{ get; }; + } + + // Flags to control how a configuration set should be applied to the system. + [contract(Microsoft.Management.Configuration.Contract, 1)] + [flags] + enum ApplyConfigurationSetFlags + { + None = 0x0, + // Forces a new configuration set instance to be recorded when the set being applied matches a previous set's origin. + // The default behavior is to assume that the incoming set is an update to the existing set and overwrite it. + DoNotOverwriteMatchingOriginSet = 0x1, + // Does not apply the configuration set, only checks the configuration set for internal consistency. + PerformConsistencyCheckOnly = 0x2, + }; + + // The result of applying the settings for a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ApplyConfigurationUnitResult + { + // The configuration unit that was applied. + ConfigurationUnit Unit{ get; }; + + // The state of the configuration unit with regards to the current execution of ApplySet. + ConfigurationUnitState State{ get; }; + + // Will be true if the configuration unit was in the desired state (Test returns true) prior to the apply action. + Boolean PreviouslyInDesiredState{ get; }; + + // Indicates whether a reboot is required after the configuration unit was applied. + Boolean RebootRequired{ get; }; + + // The result of applying the configuration unit. + IConfigurationUnitResultInformation ResultInformation{ get; }; + } + + // The result of applying the settings for a configuration set. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ApplyConfigurationSetResult + { + // Results for each configuration unit in the set. + Windows.Foundation.Collections.IVectorView UnitResults{ get; }; + + // The overall result from applying the configuration set. + HRESULT ResultCode{ get; }; + } + + // The result of testing the settings for a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass TestConfigurationUnitResult + { + // The configuration unit that was tested. + ConfigurationUnit Unit{ get; }; + + // The result of testing the configuration unit. + // This is not the response for the test, but rather contains information about the actual attempt to run the test. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // The result (if any) of running Test on the configuration unit. + ConfigurationTestResult TestResult{ get; }; + } + + // The result of testing the settings for a configuration set. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass TestConfigurationSetResult + { + // Results for each configuration unit in the set. + Windows.Foundation.Collections.IVectorView UnitResults{ get; }; + + // The result (if any) of running Test on the configuration set. + // If this value is NotRun, every unit result will be NotRun. + // If this value is Positive, every unit result will be Positive (or NotRun with at least one being Positive). + // Any Negative result for a unit will result in this value being Negative. + // Any Failed result for a unit will result in this value being Failed (overriding the Negative statement above). + ConfigurationTestResult TestResult{ get; }; + } + + // The result of getting the settings for a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass GetConfigurationUnitSettingsResult + { + // The result of getting the configuration unit settings. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // The current state of the system for the configuration unit. + Windows.Foundation.Collections.ValueSet Settings { get; }; + } + + // The result of getting the settings for all the instances of a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 2)] + runtimeclass GetAllConfigurationUnitSettingsResult + { + // The result of getting the settings for all the instances of the configuration unit. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // The current state of the system for all the instances of the configuration unit. + Windows.Foundation.Collections.IVector Settings { get; }; + } + + // The result of getting the all units for a configuration unit. + [contract(Microsoft.Management.Configuration.Contract, 4)] + runtimeclass GetAllConfigurationUnitsResult + { + // The result of getting the all units for a configuration unit. + // This is not the response for the retrieval, but rather contains information about the actual attempt to retrieve the settings. + IConfigurationUnitResultInformation ResultInformation{ get; }; + + // The units retrieved for the given unit. + Windows.Foundation.Collections.IVector Units { get; }; + } + + // The configuration processor is responsible for the interactions with the system. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationProcessor + { + ConfigurationProcessor(IConfigurationSetProcessorFactory factory); + + // Diagnostics event; useful for logging and/or verbose output. + event Windows.Foundation.EventHandler Diagnostics; + + // Indicates the minimum importance desired for diagnostics. + DiagnosticLevel MinimumLevel; + + // Set the caller to used to identify the usage in telemetry events. + String Caller; + + // The identifier for the current activity, enabling multiple calls into the processor to be correlated. + Guid ActivityIdentifier; + + // If true, ETW events will be generated. Some of those events may be sent to Microsoft depending on the system settings. + Boolean GenerateTelemetryEvents; + + // Only top level configuration changes are sent to this event. + // This includes things like: creation of a new set for intent to run, start/stop of a set for application, deletion of a not started set. + event Windows.Foundation.TypedEventHandler ConfigurationChange; + + // Gets the configuration sets that have already been applied or with the intent to be applied (this may include in progress sets or those that are waiting on others). + Windows.Foundation.Collections.IVector GetConfigurationHistory(); + Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector > GetConfigurationHistoryAsync(); + + // Loads a configuration set from the given stream. + OpenConfigurationSetResult OpenConfigurationSet(Windows.Storage.Streams.IInputStream stream); + Windows.Foundation.IAsyncOperation OpenConfigurationSetAsync(Windows.Storage.Streams.IInputStream stream); + + // Checks for conflicts amongst the configuration sets provided, optionally including the configuration sets already applied to the system. + Windows.Foundation.Collections.IVector CheckForConflicts(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); + Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector > CheckForConflictsAsync(Windows.Foundation.Collections.IVectorView configurationSets, Boolean includeConfigurationHistory); + + // Gets the details for all configuration units in a set. + GetConfigurationSetDetailsResult GetSetDetails(ConfigurationSet configurationSet, ConfigurationUnitDetailFlags detailFlags); + Windows.Foundation.IAsyncOperationWithProgress GetSetDetailsAsync(ConfigurationSet configurationSet, ConfigurationUnitDetailFlags detailFlags); + + // Gets the details for a configuration unit. + GetConfigurationUnitDetailsResult GetUnitDetails(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); + Windows.Foundation.IAsyncOperation GetUnitDetailsAsync(ConfigurationUnit unit, ConfigurationUnitDetailFlags detailFlags); + + // Applies the configuration set state. + ApplyConfigurationSetResult ApplySet(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); + Windows.Foundation.IAsyncOperationWithProgress ApplySetAsync(ConfigurationSet configurationSet, ApplyConfigurationSetFlags flags); + + // Tests the configuration set state. + TestConfigurationSetResult TestSet(ConfigurationSet configurationSet); + Windows.Foundation.IAsyncOperationWithProgress TestSetAsync(ConfigurationSet configurationSet); + + // Gets the current configuration unit settings. + GetConfigurationUnitSettingsResult GetUnitSettings(ConfigurationUnit unit); + Windows.Foundation.IAsyncOperation GetUnitSettingsAsync(ConfigurationUnit unit); + + [contract(Microsoft.Management.Configuration.Contract, 2)] + { + // Gets the current settings for all the instances of a configuration unit. + GetAllConfigurationUnitSettingsResult GetAllUnitSettings(ConfigurationUnit unit); + Windows.Foundation.IAsyncOperation GetAllUnitSettingsAsync(ConfigurationUnit unit); + } + + [contract(Microsoft.Management.Configuration.Contract, 4)] + { + // Gets all configuration units for the given unit type. + // Returned units may be of types other than the one passed in. + GetAllConfigurationUnitsResult GetAllUnits(ConfigurationUnit unit); + Windows.Foundation.IAsyncOperation GetAllUnitsAsync(ConfigurationUnit unit); + + // Find unit processors. + Windows.Foundation.Collections.IVector FindUnitProcessors(FindUnitProcessorsOptions findOptions); + Windows.Foundation.IAsyncOperation< Windows.Foundation.Collections.IVector > FindUnitProcessorsAsync(FindUnitProcessorsOptions findOptions); + + // Apply the current configuration unit. + ApplyConfigurationUnitResult ApplyUnit(ConfigurationUnit unit); + Windows.Foundation.IAsyncOperation ApplyUnitAsync(ConfigurationUnit unit); + + // Test the current configuration unit. + TestConfigurationUnitResult TestUnit(ConfigurationUnit unit); + Windows.Foundation.IAsyncOperation TestUnitAsync(ConfigurationUnit unit); + } + } + + // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. + [contract(Microsoft.Management.Configuration.Contract, 1)] + interface IConfigurationStatics + { + // Creates an empty configuration unit. + ConfigurationUnit CreateConfigurationUnit(); + + // Creates an empty configuration set. + ConfigurationSet CreateConfigurationSet(); + + // Creates a processor factory for the given handler. + Windows.Foundation.IAsyncOperation CreateConfigurationSetProcessorFactoryAsync(String handler); + + // Creates a processor from the given factory. + ConfigurationProcessor CreateConfigurationProcessor(IConfigurationSetProcessorFactory factory); + + // Whether configuration is enabled. + Boolean IsConfigurationAvailable{ get; }; + + // Enables configuration. Requires store access. + Windows.Foundation.IAsyncActionWithProgress EnsureConfigurationAvailableAsync(); + } + + // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. + [contract(Microsoft.Management.Configuration.Contract, 2)] + interface IConfigurationStatics2 requires IConfigurationStatics + { + // Creates an empty configuration parameter. + ConfigurationParameter CreateConfigurationParameter(); + } + + // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. + [contract(Microsoft.Management.Configuration.Contract, 4)] + interface IConfigurationStatics3 requires IConfigurationStatics2 + { + // Creates an empty configuration parameter. + FindUnitProcessorsOptions CreateFindUnitProcessorsOptions(); + } + + // Top level entry point for configuration, enabling easier usage in out-of-process scenarios. + [contract(Microsoft.Management.Configuration.Contract, 1)] + runtimeclass ConfigurationStaticFunctions : [default]IConfigurationStatics, IConfigurationStatics2, IConfigurationStatics3 + { + ConfigurationStaticFunctions(); + } + + /// Force midl3 to generate vector marshalling info. + declare + { + // Due to the way that metadata (WinMD) based marshalling works, in order for any of these to be IIterable, they need to be + // included in the manifest of the package. Update the DumpProxyStubRegistrationsCommand to add any new types to make it easier + // to iterate over these collections, especially in C#. + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVector; + } + + // Provides a way to centralize the distribution of interfaces relevant to specific implementations of IConfigurationSetProcessorFactory. + namespace SetProcessorFactory + { + // The same as PowerShell ExecutionPolicy: + // https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_execution_policies + enum PwshConfigurationProcessorPolicy + { + Unrestricted = 0, + RemoteSigned = 1, + AllSigned = 2, + Restricted = 3, + Bypass = 4, + Undefined = 5, + Default = RemoteSigned, + }; + + enum PwshConfigurationProcessorLocation + { + CurrentUser = 0, + AllUsers = 1, + WinGetModulePath = 2, + Custom = 3, + Default = WinGetModulePath, + }; + + // The properties provided by the "pwsh" processor factory. + interface IPwshConfigurationSetProcessorFactoryProperties + { + // The module paths to add to the processor. + // This will be in addition to any paths added by the processor and those inherent to PowerShell. + Windows.Foundation.Collections.IVectorView AdditionalModulePaths; + + // The execution policy to apply; must be set before taking actions with the processor. + PwshConfigurationProcessorPolicy Policy; + + // The location to install modules. Default is WinGetModulePath + PwshConfigurationProcessorLocation Location; + + // The custom location. Only applicable for Scope.Custom + String CustomLocation; + }; + } +} diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj index b881999f7f..53dfe11d66 100644 --- a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj +++ b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj @@ -1,326 +1,326 @@ - - - - - true - true - true - true - {CA460806-5E41-4E97-9A3D-1D74B433B663} - Microsoft.Management.Configuration - Microsoft.Management.Configuration - en-US - 14.0 - 10.0 - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - DynamicLibrary - false - true - - - true - true - - - false - true - false - Spectre - - - false - true - false - Spectre - - - - - - - - - - - - - $(VC_IncludePath);$(WindowsSDK_IncludePath); - $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ - $(PlatformTarget)\$(Configuration)\ - $(RootNamespace) - true - true - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - Level4 - %(AdditionalOptions) /bigobj - true - true - _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - - - Console - false - Microsoft_Management_Configuration.def - $(OutDir)$(ProjectName).winmd - Advapi32.lib;icuuc.lib;icuin.lib;onecoreuap.lib;winsqlite3.lib;%(AdditionalDependencies) - - - - - _DEBUG;%(PreprocessorDefinitions) - false - false - false - false - false - false - - - true - - - true - - - - - NDEBUG;%(PreprocessorDefinitions) - false - false - false - Guard - Guard - Guard - - - true - true - /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full %(AdditionalOptions) - /debug:full %(AdditionalOptions) - /debug:full %(AdditionalOptions) - true - true - - - - - NDEBUG;%(PreprocessorDefinitions) - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - Guard - Guard - Guard - - - true - true - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + true + true + true + true + {CA460806-5E41-4E97-9A3D-1D74B433B663} + Microsoft.Management.Configuration + Microsoft.Management.Configuration + en-US + 14.0 + 10.0 + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + DynamicLibrary + false + true + + + true + true + + + false + true + false + Spectre + + + false + true + false + Spectre + + + + + + + + + + + + + $(VC_IncludePath);$(WindowsSDK_IncludePath); + $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ + $(PlatformTarget)\$(Configuration)\ + $(RootNamespace) + true + true + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + true + true + _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + + + Console + false + Microsoft_Management_Configuration.def + $(OutDir)$(ProjectName).winmd + Advapi32.lib;icuuc.lib;icuin.lib;onecoreuap.lib;winsqlite3.lib;%(AdditionalDependencies) + + + + + _DEBUG;%(PreprocessorDefinitions) + false + false + false + false + false + false + + + true + + + true + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + false + Guard + Guard + Guard + + + true + true + /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full %(AdditionalOptions) + /debug:full %(AdditionalOptions) + /debug:full %(AdditionalOptions) + true + true + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + Guard + Guard + Guard + + + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + diff --git a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters index 203da91669..3901ba8f1b 100644 --- a/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters +++ b/src/Microsoft.Management.Configuration/Microsoft.Management.Configuration.vcxproj.filters @@ -1,386 +1,386 @@ - - - - - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - API Source - - - Parser - - - Parser - - - API Source - - - Internals - - - Internals - - - API Source - - - API Source - - - Telemetry - - - Telemetry - - - Parser - - - Internals - - - API Source - - - Internals - - - API Source - - - API Source - - - Internals - - - Parser - - - Internals - - - Internals - - - Internals - - - Internals - - - Parser - - - Parser - - - Parser - - - Database - - - Database\Schema - - - Database\Schema\0_1 - - - Internals - - - Database\Schema\0_1 - - - Database\Schema\0_1 - - - Internals - - - Database\Schema\0_2 - - - Database\Schema\0_2 - - - Internals - - - Database\Schema\0_3 - - - Database\Schema\0_3 - - - Database\Schema\0_3 - - - Parser - - - API Source - - - API Source - - - API Source - - - Internals - - - - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - API Headers - - - Parser - - - Parser - - - API Headers - - - Parser - - - Internals - - - Internals - - - Internals - - - API Headers - - - API Headers - - - Telemetry - - - Telemetry - - - Parser - - - Internals - - - API Headers - - - Internals - - - API Headers - - - API Headers - - - Internals - - - Parser - - - Parser - - - Internals - - - Internals - - - Internals - - - Internals - - - Parser - - - Parser - - - Parser - - - Database - - - Database\Schema - - - Database\Schema\0_1 - - - Internals - - - Database\Schema\0_1 - - - Database\Schema\0_1 - - - Internals - - - Database\Schema\0_2 - - - Database\Schema\0_2 - - - Internals - - - Database\Schema\0_3 - - - Database\Schema\0_3 - - - Database\Schema\0_3 - - - Parser - - - API Headers - - - API Headers - - - API Headers - - - Internals - - - - - - - - - - - - - {0826fc0e-120c-4e31-bde7-ce0a1278b1b1} - - - {6dbfe76d-646d-43b0-b162-e1dedad10ab8} - - - {c5f2f74e-de80-4235-abbd-bacd6771eaf2} - - - {b31f8336-b4d8-4c05-b08d-6b82c550a30b} - - - {5a02f1a5-14f3-4a28-8bed-212f3e6b1a00} - - - {c82c1df2-4ef3-4d54-9c18-a13ade2ab16a} - - - {6f544d8a-2c3f-4d26-9b53-84dbd2144d43} - - - {efb71f71-31e4-42db-9105-f10c2e89e1d5} - - - {f214d0f3-3e9c-469b-91ae-213315d39a69} - - - {d059436d-cd54-4cf2-96bc-4db53c617537} - - - - - + + + + + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + API Source + + + Parser + + + Parser + + + API Source + + + Internals + + + Internals + + + API Source + + + API Source + + + Telemetry + + + Telemetry + + + Parser + + + Internals + + + API Source + + + Internals + + + API Source + + + API Source + + + Internals + + + Parser + + + Internals + + + Internals + + + Internals + + + Internals + + + Parser + + + Parser + + + Parser + + + Database + + + Database\Schema + + + Database\Schema\0_1 + + + Internals + + + Database\Schema\0_1 + + + Database\Schema\0_1 + + + Internals + + + Database\Schema\0_2 + + + Database\Schema\0_2 + + + Internals + + + Database\Schema\0_3 + + + Database\Schema\0_3 + + + Database\Schema\0_3 + + + Parser + + + API Source + + + API Source + + + API Source + + + Internals + + + + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + API Headers + + + Parser + + + Parser + + + API Headers + + + Parser + + + Internals + + + Internals + + + Internals + + + API Headers + + + API Headers + + + Telemetry + + + Telemetry + + + Parser + + + Internals + + + API Headers + + + Internals + + + API Headers + + + API Headers + + + Internals + + + Parser + + + Parser + + + Internals + + + Internals + + + Internals + + + Internals + + + Parser + + + Parser + + + Parser + + + Database + + + Database\Schema + + + Database\Schema\0_1 + + + Internals + + + Database\Schema\0_1 + + + Database\Schema\0_1 + + + Internals + + + Database\Schema\0_2 + + + Database\Schema\0_2 + + + Internals + + + Database\Schema\0_3 + + + Database\Schema\0_3 + + + Database\Schema\0_3 + + + Parser + + + API Headers + + + API Headers + + + API Headers + + + Internals + + + + + + + + + + + + + {0826fc0e-120c-4e31-bde7-ce0a1278b1b1} + + + {6dbfe76d-646d-43b0-b162-e1dedad10ab8} + + + {c5f2f74e-de80-4235-abbd-bacd6771eaf2} + + + {b31f8336-b4d8-4c05-b08d-6b82c550a30b} + + + {5a02f1a5-14f3-4a28-8bed-212f3e6b1a00} + + + {c82c1df2-4ef3-4d54-9c18-a13ade2ab16a} + + + {6f544d8a-2c3f-4d26-9b53-84dbd2144d43} + + + {efb71f71-31e4-42db-9105-f10c2e89e1d5} + + + {f214d0f3-3e9c-469b-91ae-213315d39a69} + + + {d059436d-cd54-4cf2-96bc-4db53c617537} + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration/Microsoft_Management_Configuration.def b/src/Microsoft.Management.Configuration/Microsoft_Management_Configuration.def index 8c1a02932d..24e7c1235c 100644 --- a/src/Microsoft.Management.Configuration/Microsoft_Management_Configuration.def +++ b/src/Microsoft.Management.Configuration/Microsoft_Management_Configuration.def @@ -1,3 +1,3 @@ -EXPORTS -DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE -DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/src/Microsoft.Management.Configuration/ParsingMacros.h b/src/Microsoft.Management.Configuration/ParsingMacros.h index 52aeb3ad83..e14edb45e7 100644 --- a/src/Microsoft.Management.Configuration/ParsingMacros.h +++ b/src/Microsoft.Management.Configuration/ParsingMacros.h @@ -1,13 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#define CHECK_ERROR(_op_) (_op_); if (FAILED(m_result)) { return; } - -#define FIELD_TYPE_ERROR(_field_,_mark_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, (_field_), (_mark_)); return -#define FIELD_TYPE_ERROR_IF(_condition_,_field_,_mark_) if (_condition_) { FIELD_TYPE_ERROR(_field_,_mark_); } - -#define FIELD_MISSING_ERROR(_field_) SetError(WINGET_CONFIG_ERROR_MISSING_FIELD, (_field_)); return -#define FIELD_MISSING_ERROR_IF(_condition_,_field_) if (_condition_) { FIELD_MISSING_ERROR(_field_); } - -#define FIELD_VALUE_ERROR(_field_,_value_,_mark_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, (_field_), (_mark_), (_value_)); return -#define FIELD_VALUE_ERROR_IF(_condition_,_field_,_value_,_mark_) if (_condition_) { FIELD_VALUE_ERROR(_field_,_value_,_mark_); } +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#define CHECK_ERROR(_op_) (_op_); if (FAILED(m_result)) { return; } + +#define FIELD_TYPE_ERROR(_field_,_mark_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_TYPE, (_field_), (_mark_)); return +#define FIELD_TYPE_ERROR_IF(_condition_,_field_,_mark_) if (_condition_) { FIELD_TYPE_ERROR(_field_,_mark_); } + +#define FIELD_MISSING_ERROR(_field_) SetError(WINGET_CONFIG_ERROR_MISSING_FIELD, (_field_)); return +#define FIELD_MISSING_ERROR_IF(_condition_,_field_) if (_condition_) { FIELD_MISSING_ERROR(_field_); } + +#define FIELD_VALUE_ERROR(_field_,_value_,_mark_) SetError(WINGET_CONFIG_ERROR_INVALID_FIELD_VALUE, (_field_), (_mark_), (_value_)); return +#define FIELD_VALUE_ERROR_IF(_condition_,_field_,_value_,_mark_) if (_condition_) { FIELD_VALUE_ERROR(_field_,_value_,_mark_); } diff --git a/src/Microsoft.Management.Configuration/ShutdownSynchronization.cpp b/src/Microsoft.Management.Configuration/ShutdownSynchronization.cpp index 04b12b0a16..e8896f54ef 100644 --- a/src/Microsoft.Management.Configuration/ShutdownSynchronization.cpp +++ b/src/Microsoft.Management.Configuration/ShutdownSynchronization.cpp @@ -1,180 +1,180 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "ShutdownSynchronization.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - ShutdownAwareAsyncCancellation::ShutdownAwareAsyncCancellation() - { - m_defaultPromise = std::make_unique(); - m_cancellation = std::make_unique(winrt::impl::cancellation_token{ m_defaultPromise.get() }); - RegisterWithShutdownSynchronization(); - } - - ShutdownAwareAsyncCancellation::~ShutdownAwareAsyncCancellation() - { - if (m_cancellation) - { - ShutdownSynchronization::Instance().RegisterWorkEnd(m_cancellation->GetWeak()); - } - } - - bool ShutdownAwareAsyncCancellation::IsCancelled() const noexcept - { - return m_cancellation->IsCancelled(); - } - - void ShutdownAwareAsyncCancellation::ThrowIfCancelled() const - { - m_cancellation->ThrowIfCancelled(); - } - - void ShutdownAwareAsyncCancellation::Callback(winrt::delegate<>&& callback) const noexcept - { - m_cancellation->Callback(std::move(callback)); - } - - void ShutdownAwareAsyncCancellation::RegisterWithShutdownSynchronization() - { - ShutdownSynchronization::Instance().RegisterWorkBegin(m_cancellation->GetWeak()); - } - - Windows::Foundation::AsyncStatus ShutdownAwareAsyncCancellationPromise::Status() noexcept - { - return m_status.load(std::memory_order_acquire); - } - - void ShutdownAwareAsyncCancellationPromise::cancellation_callback(winrt::delegate<>&& cancel) noexcept - { - { - slim_lock_guard const guard(m_lock); - - if (m_status.load(std::memory_order_relaxed) != Windows::Foundation::AsyncStatus::Canceled) - { - m_cancel = std::move(cancel); - return; - } - } - - if (cancel) - { - cancel(); - } - } - - bool ShutdownAwareAsyncCancellationPromise::enable_cancellation_propagation(bool) noexcept - { - THROW_HR(E_NOTIMPL); - } - - void ShutdownAwareAsyncCancellationPromise::Cancel() noexcept - { - winrt::delegate<> cancel; - - { - slim_lock_guard const guard(m_lock); - - if (m_status.load(std::memory_order_relaxed) == Windows::Foundation::AsyncStatus::Started) - { - m_status.store(Windows::Foundation::AsyncStatus::Canceled, std::memory_order_relaxed); - cancel = std::move(m_cancel); - } - } - - if (cancel) - { - cancel(); - } - } - - ShutdownSynchronization& ShutdownSynchronization::Instance() - { - static ShutdownSynchronization s_instance; - return s_instance; - } - - void ShutdownSynchronization::BlockNewWork() - { - m_disabled = true; - } - - void ShutdownSynchronization::RegisterWorkBegin(CancellableWeakPtr&& ptr) - { - if (m_disabled) - { - THROW_HR(E_ABORT); - } - - std::lock_guard lock{ m_workLock }; - m_work.emplace(std::move(ptr)); - m_noActiveWork.ResetEvent(); - } - - void ShutdownSynchronization::RegisterWorkEnd(CancellableWeakPtr&& ptr) - { - std::lock_guard lock{ m_workLock }; - - auto itr = m_work.find(ptr); - if (itr != m_work.end()) - { - m_work.erase(itr); - - if (m_work.empty()) - { - m_noActiveWork.SetEvent(); - } - } - } - - void ShutdownSynchronization::CancelAllWork() - { - std::lock_guard lock{ m_workLock }; - - for (auto itr = m_work.begin(); itr != m_work.end(); ++itr) - { - if (auto locked = itr->lock()) - { - locked->Cancel(); - } - else - { - m_work.erase(itr); - } - } - - if (m_work.empty()) - { - m_noActiveWork.SetEvent(); - } - } - - void ShutdownSynchronization::Wait() - { - for (;;) - { - { - std::lock_guard lock{ m_workLock }; - - // Check for any inactive work before waiting - for (auto itr = m_work.begin(); itr != m_work.end(); ++itr) - { - if (!itr->lock()) - { - m_work.erase(itr); - } - } - - if (m_work.empty()) - { - break; - } - } - - if (m_noActiveWork.wait(250)) - { - break; - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "ShutdownSynchronization.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + ShutdownAwareAsyncCancellation::ShutdownAwareAsyncCancellation() + { + m_defaultPromise = std::make_unique(); + m_cancellation = std::make_unique(winrt::impl::cancellation_token{ m_defaultPromise.get() }); + RegisterWithShutdownSynchronization(); + } + + ShutdownAwareAsyncCancellation::~ShutdownAwareAsyncCancellation() + { + if (m_cancellation) + { + ShutdownSynchronization::Instance().RegisterWorkEnd(m_cancellation->GetWeak()); + } + } + + bool ShutdownAwareAsyncCancellation::IsCancelled() const noexcept + { + return m_cancellation->IsCancelled(); + } + + void ShutdownAwareAsyncCancellation::ThrowIfCancelled() const + { + m_cancellation->ThrowIfCancelled(); + } + + void ShutdownAwareAsyncCancellation::Callback(winrt::delegate<>&& callback) const noexcept + { + m_cancellation->Callback(std::move(callback)); + } + + void ShutdownAwareAsyncCancellation::RegisterWithShutdownSynchronization() + { + ShutdownSynchronization::Instance().RegisterWorkBegin(m_cancellation->GetWeak()); + } + + Windows::Foundation::AsyncStatus ShutdownAwareAsyncCancellationPromise::Status() noexcept + { + return m_status.load(std::memory_order_acquire); + } + + void ShutdownAwareAsyncCancellationPromise::cancellation_callback(winrt::delegate<>&& cancel) noexcept + { + { + slim_lock_guard const guard(m_lock); + + if (m_status.load(std::memory_order_relaxed) != Windows::Foundation::AsyncStatus::Canceled) + { + m_cancel = std::move(cancel); + return; + } + } + + if (cancel) + { + cancel(); + } + } + + bool ShutdownAwareAsyncCancellationPromise::enable_cancellation_propagation(bool) noexcept + { + THROW_HR(E_NOTIMPL); + } + + void ShutdownAwareAsyncCancellationPromise::Cancel() noexcept + { + winrt::delegate<> cancel; + + { + slim_lock_guard const guard(m_lock); + + if (m_status.load(std::memory_order_relaxed) == Windows::Foundation::AsyncStatus::Started) + { + m_status.store(Windows::Foundation::AsyncStatus::Canceled, std::memory_order_relaxed); + cancel = std::move(m_cancel); + } + } + + if (cancel) + { + cancel(); + } + } + + ShutdownSynchronization& ShutdownSynchronization::Instance() + { + static ShutdownSynchronization s_instance; + return s_instance; + } + + void ShutdownSynchronization::BlockNewWork() + { + m_disabled = true; + } + + void ShutdownSynchronization::RegisterWorkBegin(CancellableWeakPtr&& ptr) + { + if (m_disabled) + { + THROW_HR(E_ABORT); + } + + std::lock_guard lock{ m_workLock }; + m_work.emplace(std::move(ptr)); + m_noActiveWork.ResetEvent(); + } + + void ShutdownSynchronization::RegisterWorkEnd(CancellableWeakPtr&& ptr) + { + std::lock_guard lock{ m_workLock }; + + auto itr = m_work.find(ptr); + if (itr != m_work.end()) + { + m_work.erase(itr); + + if (m_work.empty()) + { + m_noActiveWork.SetEvent(); + } + } + } + + void ShutdownSynchronization::CancelAllWork() + { + std::lock_guard lock{ m_workLock }; + + for (auto itr = m_work.begin(); itr != m_work.end(); ++itr) + { + if (auto locked = itr->lock()) + { + locked->Cancel(); + } + else + { + m_work.erase(itr); + } + } + + if (m_work.empty()) + { + m_noActiveWork.SetEvent(); + } + } + + void ShutdownSynchronization::Wait() + { + for (;;) + { + { + std::lock_guard lock{ m_workLock }; + + // Check for any inactive work before waiting + for (auto itr = m_work.begin(); itr != m_work.end(); ++itr) + { + if (!itr->lock()) + { + m_work.erase(itr); + } + } + + if (m_work.empty()) + { + break; + } + } + + if (m_noActiveWork.wait(250)) + { + break; + } + } + } +} diff --git a/src/Microsoft.Management.Configuration/ShutdownSynchronization.h b/src/Microsoft.Management.Configuration/ShutdownSynchronization.h index c09c1c5d82..d003d7959b 100644 --- a/src/Microsoft.Management.Configuration/ShutdownSynchronization.h +++ b/src/Microsoft.Management.Configuration/ShutdownSynchronization.h @@ -1,173 +1,173 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Promise type implementation for cancellation_token - struct ShutdownAwareAsyncCancellationPromise - { - Windows::Foundation::AsyncStatus Status() noexcept; - void cancellation_callback(winrt::delegate<>&& cancel) noexcept; - bool enable_cancellation_propagation(bool value) noexcept; - void Cancel() noexcept; - - private: - slim_mutex m_lock; - winrt::delegate<> m_cancel; - std::atomic m_status{ Windows::Foundation::AsyncStatus::Started }; - }; - - // An AsyncCancellation that registers with ShutdownSynchronization. - struct ShutdownAwareAsyncCancellation - { - // Creates a cancellable object without an external cancellation token. - ShutdownAwareAsyncCancellation(); - - // Create a cancellation object from the winrt token. - template - ShutdownAwareAsyncCancellation(winrt::impl::cancellation_token&& token) - { - m_cancellation = std::make_unique(std::move(token)); - RegisterWithShutdownSynchronization(); - } - - // Removes the shutdown registration. - ~ShutdownAwareAsyncCancellation(); - - // Returns true if the operation has been cancelled, false if not. - bool IsCancelled() const noexcept; - - // Throws the appropriate exception if the operation has been cancelled. - void ThrowIfCancelled() const; - - // Sets a callback that will be invoked on cancellation. - void Callback(winrt::delegate<>&& callback) const noexcept; - - protected: - void RegisterWithShutdownSynchronization(); - - std::unique_ptr m_defaultPromise; - std::unique_ptr m_cancellation; - }; - - struct ShutdownSynchronization - { - using CancellableWeakPtr = std::weak_ptr; - - ShutdownSynchronization() = default; - - static ShutdownSynchronization& Instance(); - - // Signals that new work should be blocked. - void BlockNewWork(); - - // Call to register the begin and end of work. - void RegisterWorkBegin(CancellableWeakPtr&& ptr); - void RegisterWorkEnd(CancellableWeakPtr&& ptr); - - // Cancels all currently registered work. - void CancelAllWork(); - - // Waits for outstanding work to be completed. - void Wait(); - - private: - std::atomic_bool m_disabled{ false }; - std::mutex m_workLock; - std::set> m_work; - wil::slim_event_manual_reset m_noActiveWork{ true }; - }; - - // An AsyncProgress that registers with ShutdownSynchronization. - template - struct ShutdownAwareAsyncProgress - { - // Creates a cancellable object without an external cancellation token. - ShutdownAwareAsyncProgress() - { - m_defaultPromise = std::make_unique(); - m_progress = std::make_unique>(winrt::impl::cancellation_token{ m_defaultPromise.get() }); - RegisterWithShutdownSynchronization(); - } - - // Create a progress object from the winrt token. - template - ShutdownAwareAsyncProgress(winrt::impl::progress_token&& progress, winrt::impl::cancellation_token&& cancellation) - { - m_progress = std::make_unique>(std::move(progress), std::move(cancellation)); - RegisterWithShutdownSynchronization(); - } - - // Create a progress object from an EventHandler. - template - ShutdownAwareAsyncProgress(winrt::Windows::Foundation::EventHandler&& progress, winrt::impl::cancellation_token&& cancellation) - { - m_progress = std::make_unique>(std::move(progress), std::move(cancellation)); - RegisterWithShutdownSynchronization(); - } - - ShutdownAwareAsyncProgress(const ShutdownAwareAsyncProgress&) = delete; - ShutdownAwareAsyncProgress& operator=(const ShutdownAwareAsyncProgress&) = delete; - - ShutdownAwareAsyncProgress(ShutdownAwareAsyncProgress&&) = default; - ShutdownAwareAsyncProgress& operator=(ShutdownAwareAsyncProgress&&) = default; - - // Removes the shutdown registration. - ~ShutdownAwareAsyncProgress() - { - if (m_progress) - { - ShutdownSynchronization::Instance().RegisterWorkEnd(m_progress->GetWeak()); - } - } - - AppInstaller::WinRT::AsyncCancellation& GetCancellation() - { - return *m_progress; - } - - // Returns true if the operation has been cancelled, false if not. - bool IsCancelled() const noexcept - { - return m_progress->IsCancelled(); - } - - // Throws the appropriate exception if the operation has been cancelled. - void ThrowIfCancelled() const - { - m_progress->ThrowIfCancelled(); - } - - // Sets a callback that will be invoked on cancellation. - void Callback(winrt::delegate<>&& callback) const noexcept - { - m_progress->Callback(std::move(callback)); - } - - // Sends progress if this object is not empty. - void Progress(ProgressT const& progress) const - { - m_progress->Progress(progress); - } - - // Sets the result onto the progress object if it is not empty. - void Result(ResultT const& result) const - { - m_progress->Result(result); - } - - protected: - void RegisterWithShutdownSynchronization() - { - ShutdownSynchronization::Instance().RegisterWorkBegin(m_progress->GetWeak()); - } - - std::unique_ptr m_defaultPromise; - std::unique_ptr> m_progress; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Promise type implementation for cancellation_token + struct ShutdownAwareAsyncCancellationPromise + { + Windows::Foundation::AsyncStatus Status() noexcept; + void cancellation_callback(winrt::delegate<>&& cancel) noexcept; + bool enable_cancellation_propagation(bool value) noexcept; + void Cancel() noexcept; + + private: + slim_mutex m_lock; + winrt::delegate<> m_cancel; + std::atomic m_status{ Windows::Foundation::AsyncStatus::Started }; + }; + + // An AsyncCancellation that registers with ShutdownSynchronization. + struct ShutdownAwareAsyncCancellation + { + // Creates a cancellable object without an external cancellation token. + ShutdownAwareAsyncCancellation(); + + // Create a cancellation object from the winrt token. + template + ShutdownAwareAsyncCancellation(winrt::impl::cancellation_token&& token) + { + m_cancellation = std::make_unique(std::move(token)); + RegisterWithShutdownSynchronization(); + } + + // Removes the shutdown registration. + ~ShutdownAwareAsyncCancellation(); + + // Returns true if the operation has been cancelled, false if not. + bool IsCancelled() const noexcept; + + // Throws the appropriate exception if the operation has been cancelled. + void ThrowIfCancelled() const; + + // Sets a callback that will be invoked on cancellation. + void Callback(winrt::delegate<>&& callback) const noexcept; + + protected: + void RegisterWithShutdownSynchronization(); + + std::unique_ptr m_defaultPromise; + std::unique_ptr m_cancellation; + }; + + struct ShutdownSynchronization + { + using CancellableWeakPtr = std::weak_ptr; + + ShutdownSynchronization() = default; + + static ShutdownSynchronization& Instance(); + + // Signals that new work should be blocked. + void BlockNewWork(); + + // Call to register the begin and end of work. + void RegisterWorkBegin(CancellableWeakPtr&& ptr); + void RegisterWorkEnd(CancellableWeakPtr&& ptr); + + // Cancels all currently registered work. + void CancelAllWork(); + + // Waits for outstanding work to be completed. + void Wait(); + + private: + std::atomic_bool m_disabled{ false }; + std::mutex m_workLock; + std::set> m_work; + wil::slim_event_manual_reset m_noActiveWork{ true }; + }; + + // An AsyncProgress that registers with ShutdownSynchronization. + template + struct ShutdownAwareAsyncProgress + { + // Creates a cancellable object without an external cancellation token. + ShutdownAwareAsyncProgress() + { + m_defaultPromise = std::make_unique(); + m_progress = std::make_unique>(winrt::impl::cancellation_token{ m_defaultPromise.get() }); + RegisterWithShutdownSynchronization(); + } + + // Create a progress object from the winrt token. + template + ShutdownAwareAsyncProgress(winrt::impl::progress_token&& progress, winrt::impl::cancellation_token&& cancellation) + { + m_progress = std::make_unique>(std::move(progress), std::move(cancellation)); + RegisterWithShutdownSynchronization(); + } + + // Create a progress object from an EventHandler. + template + ShutdownAwareAsyncProgress(winrt::Windows::Foundation::EventHandler&& progress, winrt::impl::cancellation_token&& cancellation) + { + m_progress = std::make_unique>(std::move(progress), std::move(cancellation)); + RegisterWithShutdownSynchronization(); + } + + ShutdownAwareAsyncProgress(const ShutdownAwareAsyncProgress&) = delete; + ShutdownAwareAsyncProgress& operator=(const ShutdownAwareAsyncProgress&) = delete; + + ShutdownAwareAsyncProgress(ShutdownAwareAsyncProgress&&) = default; + ShutdownAwareAsyncProgress& operator=(ShutdownAwareAsyncProgress&&) = default; + + // Removes the shutdown registration. + ~ShutdownAwareAsyncProgress() + { + if (m_progress) + { + ShutdownSynchronization::Instance().RegisterWorkEnd(m_progress->GetWeak()); + } + } + + AppInstaller::WinRT::AsyncCancellation& GetCancellation() + { + return *m_progress; + } + + // Returns true if the operation has been cancelled, false if not. + bool IsCancelled() const noexcept + { + return m_progress->IsCancelled(); + } + + // Throws the appropriate exception if the operation has been cancelled. + void ThrowIfCancelled() const + { + m_progress->ThrowIfCancelled(); + } + + // Sets a callback that will be invoked on cancellation. + void Callback(winrt::delegate<>&& callback) const noexcept + { + m_progress->Callback(std::move(callback)); + } + + // Sends progress if this object is not empty. + void Progress(ProgressT const& progress) const + { + m_progress->Progress(progress); + } + + // Sets the result onto the progress object if it is not empty. + void Result(ResultT const& result) const + { + m_progress->Result(result); + } + + protected: + void RegisterWithShutdownSynchronization() + { + ShutdownSynchronization::Instance().RegisterWorkBegin(m_progress->GetWeak()); + } + + std::unique_ptr m_defaultPromise; + std::unique_ptr> m_progress; + }; +} diff --git a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp index 6f5e82cf4e..be54d3a98a 100644 --- a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp +++ b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.cpp @@ -1,413 +1,413 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Telemetry.h" -#include "TraceLogging.h" -#include -#include -#include -#include - -#define AICLI_TraceLoggingStringView(_sv_,_name_) TraceLoggingCountedUtf8String(_sv_.data(), static_cast(_sv_.size()), _name_) -#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast(_sv_.size()), _name_) - -#define AICLI_TraceLoggingProcessingSummaryForIntent(_forIntent_,_name_,_pluralName_) \ - TraceLoggingUInt32(_forIntent_.Count, _name_ ## "Count"), \ - TraceLoggingUInt32(_forIntent_.Run, _pluralName_ ## "Run"), \ - TraceLoggingUInt32(_forIntent_.Failed, _pluralName_ ## "Failed") - -#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ -g_hTraceProvider,\ -_eventName_,\ -GetActivityId(),\ -nullptr,\ -TraceLoggingCountedUtf8String(m_version.c_str(), static_cast(m_version.size()), "CodeVersion"),\ -TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast(m_caller.size()), "Caller"),\ -__VA_ARGS__) - -#ifdef AICLI_DISABLE_TEST_HOOKS - -#define WinGet_EventItem(_value_,_name_) -#define WinGet_SummaryForIntentItem(_forIntent_,_name_,_pluralName_) -#define WinGet_WriteEventToDiagnostics(_eventName_,...) - -#else - -struct WinGetAbsorbVA_ARGSCommas -{ - WinGetAbsorbVA_ARGSCommas(int, int) {} -}; - -inline std::ostream& operator<<(std::ostream& out, const WinGetAbsorbVA_ARGSCommas&) { return out; } -inline std::ostream& operator<<(std::ostream& out, std::wstring_view value) { out << AppInstaller::Utility::ConvertToUTF8(value); return out; } - -#define WinGet_EventItem(_value_,_name_) \ - 0) << (_name_) << ": " << (_value_) << '\n' << WinGetAbsorbVA_ARGSCommas(0 - -#define WinGet_SummaryForIntentItem(_forIntent_,_name_,_pluralName_) \ - WinGet_EventItem(_forIntent_.Count, _name_ ## "Count"), \ - WinGet_EventItem(_forIntent_.Run, _pluralName_ ## "Run"), \ - WinGet_EventItem(_forIntent_.Failed, _pluralName_ ## "Failed") - -#define WinGet_WriteEventToDiagnostics(_eventName_,...) \ -{ \ - std::ostringstream _debugEventStream; \ - _debugEventStream << \ - "#DebugEventStream\n" << \ - "Event: " << (_eventName_) << '\n' << \ - "ActivityID: " << *GetActivityId() << '\n' << \ - "CodeVersion: " << m_version << '\n' << \ - "Caller: " << m_caller << '\n' \ - << WinGetAbsorbVA_ARGSCommas(0, __VA_ARGS__ ,0) \ - ; \ - AICLI_LOG_LARGE_STRING(Config, Verbose, , _debugEventStream.str()); \ -} - -#endif - -using namespace std::string_view_literals; - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - namespace - { - // The data collected from running through a set of results. - struct ConfigRunSummaryData - { - hresult Result = S_OK; - ConfigurationUnitResultSource FailurePoint = ConfigurationUnitResultSource::None; - TelemetryTraceLogger::ProcessingSummaryForIntent AssertSummary{ ConfigurationUnitIntent::Assert }; - TelemetryTraceLogger::ProcessingSummaryForIntent InformSummary{ ConfigurationUnitIntent::Inform }; - TelemetryTraceLogger::ProcessingSummaryForIntent ApplySummary{ ConfigurationUnitIntent::Apply }; - }; - - size_t GetPriority(ConfigurationUnitResultSource source) - { - switch (source) - { - case ConfigurationUnitResultSource::Internal: return 0; - case ConfigurationUnitResultSource::UnitProcessing: return 100; - case ConfigurationUnitResultSource::SystemState: return 200; - case ConfigurationUnitResultSource::ConfigurationSet: return 300; - case ConfigurationUnitResultSource::Precondition: return 400; - default: return 500; - case ConfigurationUnitResultSource::None: return 600; - } - } - - bool FirstHasPriority(ConfigurationUnitResultSource first, ConfigurationUnitResultSource second) - { - return GetPriority(first) < GetPriority(second); - } - - void ProcessUnitResult(const Configuration::ConfigurationUnit unit, const IConfigurationUnitResultInformation& resultInformation, ConfigRunSummaryData& result) - { - hresult resultCode = resultInformation.ResultCode(); - if (FAILED(resultCode)) - { - if (result.Result == S_OK || result.Result == resultCode) - { - result.Result = resultCode; - } - else - { - result.Result = WINGET_CONFIG_ERROR_SET_APPLY_FAILED; - } - } - - ConfigurationUnitResultSource unitFailurePoint = resultInformation.ResultSource(); - if (FirstHasPriority(unitFailurePoint, result.FailurePoint)) - { - result.FailurePoint = unitFailurePoint; - } - - TelemetryTraceLogger::ProcessingSummaryForIntent* summaryItem = nullptr; - switch (unit.Intent()) - { - case ConfigurationUnitIntent::Assert: - summaryItem = &result.AssertSummary; - break; - case ConfigurationUnitIntent::Inform: - summaryItem = &result.InformSummary; - break; - case ConfigurationUnitIntent::Apply: - case ConfigurationUnitIntent::Unknown: - summaryItem = &result.ApplySummary; - break; - default: - return; - } - - summaryItem->Count++; - - ConfigurationUnitResultSource resultSource = resultInformation.ResultSource(); - if (resultSource != ConfigurationUnitResultSource::Precondition && - resultSource != ConfigurationUnitResultSource::ConfigurationSet) - { - summaryItem->Run++; - } - - if (FAILED(resultCode)) - { - summaryItem->Failed++; - } - } - - // Runs through a set of results, summarizing them. - template - ConfigRunSummaryData ProcessRunResult(const Enumerable& results) - { - ConfigRunSummaryData result; - - for (const auto& item : results) - { - ProcessUnitResult(item.Unit(), item.ResultInformation(), result); - } - - return result; - } - } - - TelemetryTraceLogger::TelemetryTraceLogger() - { - std::ignore = CoCreateGuid(&m_activityId); - m_version = AppInstaller::Runtime::GetClientVersion(); - } - - void TelemetryTraceLogger::SetActivityId(const guid& value) - { - m_activityId = value; - } - - const GUID* TelemetryTraceLogger::GetActivityId() const - { - return &m_activityId; - } - - bool TelemetryTraceLogger::EnableRuntime(bool value) - { - return m_isRuntimeEnabled.exchange(value); - } - - bool TelemetryTraceLogger::IsEnabled() const - { - return m_isRuntimeEnabled; - } - - void TelemetryTraceLogger::SetCaller(std::string_view caller) - { - m_caller = caller; - } - - std::string_view TelemetryTraceLogger::GetCaller() const - { - return m_caller; - } - - void TelemetryTraceLogger::LogConfigUnitRun( - const guid& setIdentifier, - const guid& unitIdentifier, - hstring unitName, - hstring moduleName, - ConfigurationUnitIntent unitIntent, - ConfigurationUnitIntent runIntent, - std::string_view action, - hresult result, - ConfigurationUnitResultSource failurePoint, - std::wstring_view settingNames) const noexcept try - { - // Change unknown to Apply for telemetry, as it will have been treated that way - if (unitIntent == ConfigurationUnitIntent::Unknown) - { - unitIntent = ConfigurationUnitIntent::Apply; - } - - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "ConfigUnitRun", - TraceLoggingGuid(setIdentifier, "SetID"), - TraceLoggingGuid(unitIdentifier, "UnitID"), - AICLI_TraceLoggingWStringView(unitName, "UnitName"), - AICLI_TraceLoggingWStringView(moduleName, "ModuleName"), - TraceLoggingInt32(static_cast(unitIntent), "UnitIntent"), - TraceLoggingInt32(static_cast(runIntent), "RunIntent"), - AICLI_TraceLoggingStringView(action, "Action"), - TraceLoggingHResult(result, "Result"), - TraceLoggingInt32(static_cast(failurePoint), "FailurePoint"), - AICLI_TraceLoggingWStringView(settingNames, "SettingsProvided"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - // Keep in sync with above event! - WinGet_WriteEventToDiagnostics( - "ConfigUnitRun", - WinGet_EventItem(setIdentifier, "SetID"), - WinGet_EventItem(unitIdentifier, "UnitID"), - WinGet_EventItem(unitName, "UnitName"), - WinGet_EventItem(moduleName, "ModuleName"), - WinGet_EventItem(static_cast(unitIntent), "UnitIntent"), - WinGet_EventItem(static_cast(runIntent), "RunIntent"), - WinGet_EventItem(action, "Action"), - WinGet_EventItem(result, "Result"), - WinGet_EventItem(static_cast(failurePoint), "FailurePoint"), - WinGet_EventItem(settingNames, "SettingsProvided")); - } - } - CATCH_LOG(); - - void TelemetryTraceLogger::LogConfigUnitRunIfAppropriate( - const guid& setIdentifier, - const Configuration::ConfigurationUnit& unit, - ConfigurationUnitIntent runIntent, - std::string_view action, - const IConfigurationUnitResultInformation& resultInformation) const noexcept try - { - if (!IsTelemetryEnabled()) - { - return; - } - - // We only want to send telemetry for publicly available units. - IConfigurationUnitProcessorDetails details = unit.Details(); - if (!details || !details.IsPublic()) - { - return; - } - - // Create a single string from the set of top level setting names, ex. "a|b|c". - const winrt::Windows::Foundation::Collections::ValueSet& settings = unit.Settings(); - std::wostringstream strstr; - - for (const auto& setting : settings) - { - strstr << static_cast(setting.Key()) << L'|'; - } - std::wstring allSettingsNames = strstr.str(); - if (!allSettingsNames.empty()) - { - allSettingsNames.pop_back(); - } - - LogConfigUnitRun(setIdentifier, unit.InstanceIdentifier(), unit.Type(), details.ModuleName(), unit.Intent(), runIntent, action, resultInformation.ResultCode(), resultInformation.ResultSource(), allSettingsNames); - } - CATCH_LOG(); - - void TelemetryTraceLogger::LogConfigProcessingSummary( - const guid& setIdentifier, - std::string_view inputHash, - ConfigurationUnitIntent runIntent, - hresult result, - ConfigurationUnitResultSource failurePoint, - const ProcessingSummaryForIntent& assertSummary, - const ProcessingSummaryForIntent& informSummary, - const ProcessingSummaryForIntent& applySummary) const noexcept try - { - if (IsTelemetryEnabled()) - { - AICLI_TraceLoggingWriteActivity( - "ConfigProcessingSummary", - TraceLoggingGuid(setIdentifier, "SetID"), - AICLI_TraceLoggingStringView(inputHash, "InputHash"), - TraceLoggingBool(false, "FromHistory"), // deprecated - TraceLoggingInt32(static_cast(runIntent), "RunIntent"), - TraceLoggingHResult(result, "Result"), - TraceLoggingInt32(static_cast(failurePoint), "FailurePoint"), - AICLI_TraceLoggingProcessingSummaryForIntent(assertSummary, "Assert", "Asserts"), - AICLI_TraceLoggingProcessingSummaryForIntent(informSummary, "Inform", "Informs"), - AICLI_TraceLoggingProcessingSummaryForIntent(applySummary, "Apply", "Applies"), - TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), - TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); - - // Keep in sync with above event! - WinGet_WriteEventToDiagnostics( - "ConfigProcessingSummary", - WinGet_EventItem(setIdentifier, "SetID"), - WinGet_EventItem(inputHash, "InputHash"), - WinGet_EventItem(false, "FromHistory"), // deprecated - WinGet_EventItem(static_cast(runIntent), "RunIntent"), - WinGet_EventItem(result, "Result"), - WinGet_EventItem(static_cast(failurePoint), "FailurePoint"), - WinGet_SummaryForIntentItem(assertSummary, "Assert", "Asserts"), - WinGet_SummaryForIntentItem(informSummary, "Inform", "Informs"), - WinGet_SummaryForIntentItem(applySummary, "Apply", "Applies")); - } - } - CATCH_LOG(); - - void TelemetryTraceLogger::LogConfigProcessingSummaryForTest( - const ConfigurationSet& configurationSet, - const TestConfigurationSetResult& result) const noexcept try - { - if (!IsTelemetryEnabled()) - { - return; - } - - ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Assert, - summaryData.Result, summaryData.FailurePoint, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); - } - CATCH_LOG(); - - void TelemetryTraceLogger::LogConfigProcessingSummaryForTestException( - const ConfigurationSet& configurationSet, - hresult error, - const TestConfigurationSetResult& result) const noexcept try - { - if (!IsTelemetryEnabled()) - { - return; - } - - ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Assert, - error, ConfigurationUnitResultSource::Internal, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); - } - CATCH_LOG(); - - void TelemetryTraceLogger::LogConfigProcessingSummaryForApply( - const ConfigurationSet& configurationSet, - const ApplyConfigurationSetResult& result) const noexcept try - { - if (!IsTelemetryEnabled()) - { - return; - } - - ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Apply, - result.ResultCode(), summaryData.FailurePoint, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); - } - CATCH_LOG(); - - void TelemetryTraceLogger::LogConfigProcessingSummaryForApplyException( - const ConfigurationSet& configurationSet, - hresult error, - const ApplyConfigurationSetResult& result) const noexcept try - { - if (!IsTelemetryEnabled()) - { - return; - } - - ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); - - LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Apply, - error, ConfigurationUnitResultSource::Internal, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); - } - CATCH_LOG(); - - bool TelemetryTraceLogger::IsTelemetryEnabled() const noexcept - { -#ifdef AICLI_DISABLE_TEST_HOOKS - return g_IsTelemetryProviderEnabled && m_isRuntimeEnabled; -#else - // For testing, only use the local enable state. - return m_isRuntimeEnabled; -#endif - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Telemetry.h" +#include "TraceLogging.h" +#include +#include +#include +#include + +#define AICLI_TraceLoggingStringView(_sv_,_name_) TraceLoggingCountedUtf8String(_sv_.data(), static_cast(_sv_.size()), _name_) +#define AICLI_TraceLoggingWStringView(_sv_,_name_) TraceLoggingCountedWideString(_sv_.data(), static_cast(_sv_.size()), _name_) + +#define AICLI_TraceLoggingProcessingSummaryForIntent(_forIntent_,_name_,_pluralName_) \ + TraceLoggingUInt32(_forIntent_.Count, _name_ ## "Count"), \ + TraceLoggingUInt32(_forIntent_.Run, _pluralName_ ## "Run"), \ + TraceLoggingUInt32(_forIntent_.Failed, _pluralName_ ## "Failed") + +#define AICLI_TraceLoggingWriteActivity(_eventName_,...) TraceLoggingWriteActivity(\ +g_hTraceProvider,\ +_eventName_,\ +GetActivityId(),\ +nullptr,\ +TraceLoggingCountedUtf8String(m_version.c_str(), static_cast(m_version.size()), "CodeVersion"),\ +TraceLoggingCountedUtf8String(m_caller.c_str(), static_cast(m_caller.size()), "Caller"),\ +__VA_ARGS__) + +#ifdef AICLI_DISABLE_TEST_HOOKS + +#define WinGet_EventItem(_value_,_name_) +#define WinGet_SummaryForIntentItem(_forIntent_,_name_,_pluralName_) +#define WinGet_WriteEventToDiagnostics(_eventName_,...) + +#else + +struct WinGetAbsorbVA_ARGSCommas +{ + WinGetAbsorbVA_ARGSCommas(int, int) {} +}; + +inline std::ostream& operator<<(std::ostream& out, const WinGetAbsorbVA_ARGSCommas&) { return out; } +inline std::ostream& operator<<(std::ostream& out, std::wstring_view value) { out << AppInstaller::Utility::ConvertToUTF8(value); return out; } + +#define WinGet_EventItem(_value_,_name_) \ + 0) << (_name_) << ": " << (_value_) << '\n' << WinGetAbsorbVA_ARGSCommas(0 + +#define WinGet_SummaryForIntentItem(_forIntent_,_name_,_pluralName_) \ + WinGet_EventItem(_forIntent_.Count, _name_ ## "Count"), \ + WinGet_EventItem(_forIntent_.Run, _pluralName_ ## "Run"), \ + WinGet_EventItem(_forIntent_.Failed, _pluralName_ ## "Failed") + +#define WinGet_WriteEventToDiagnostics(_eventName_,...) \ +{ \ + std::ostringstream _debugEventStream; \ + _debugEventStream << \ + "#DebugEventStream\n" << \ + "Event: " << (_eventName_) << '\n' << \ + "ActivityID: " << *GetActivityId() << '\n' << \ + "CodeVersion: " << m_version << '\n' << \ + "Caller: " << m_caller << '\n' \ + << WinGetAbsorbVA_ARGSCommas(0, __VA_ARGS__ ,0) \ + ; \ + AICLI_LOG_LARGE_STRING(Config, Verbose, , _debugEventStream.str()); \ +} + +#endif + +using namespace std::string_view_literals; + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + namespace + { + // The data collected from running through a set of results. + struct ConfigRunSummaryData + { + hresult Result = S_OK; + ConfigurationUnitResultSource FailurePoint = ConfigurationUnitResultSource::None; + TelemetryTraceLogger::ProcessingSummaryForIntent AssertSummary{ ConfigurationUnitIntent::Assert }; + TelemetryTraceLogger::ProcessingSummaryForIntent InformSummary{ ConfigurationUnitIntent::Inform }; + TelemetryTraceLogger::ProcessingSummaryForIntent ApplySummary{ ConfigurationUnitIntent::Apply }; + }; + + size_t GetPriority(ConfigurationUnitResultSource source) + { + switch (source) + { + case ConfigurationUnitResultSource::Internal: return 0; + case ConfigurationUnitResultSource::UnitProcessing: return 100; + case ConfigurationUnitResultSource::SystemState: return 200; + case ConfigurationUnitResultSource::ConfigurationSet: return 300; + case ConfigurationUnitResultSource::Precondition: return 400; + default: return 500; + case ConfigurationUnitResultSource::None: return 600; + } + } + + bool FirstHasPriority(ConfigurationUnitResultSource first, ConfigurationUnitResultSource second) + { + return GetPriority(first) < GetPriority(second); + } + + void ProcessUnitResult(const Configuration::ConfigurationUnit unit, const IConfigurationUnitResultInformation& resultInformation, ConfigRunSummaryData& result) + { + hresult resultCode = resultInformation.ResultCode(); + if (FAILED(resultCode)) + { + if (result.Result == S_OK || result.Result == resultCode) + { + result.Result = resultCode; + } + else + { + result.Result = WINGET_CONFIG_ERROR_SET_APPLY_FAILED; + } + } + + ConfigurationUnitResultSource unitFailurePoint = resultInformation.ResultSource(); + if (FirstHasPriority(unitFailurePoint, result.FailurePoint)) + { + result.FailurePoint = unitFailurePoint; + } + + TelemetryTraceLogger::ProcessingSummaryForIntent* summaryItem = nullptr; + switch (unit.Intent()) + { + case ConfigurationUnitIntent::Assert: + summaryItem = &result.AssertSummary; + break; + case ConfigurationUnitIntent::Inform: + summaryItem = &result.InformSummary; + break; + case ConfigurationUnitIntent::Apply: + case ConfigurationUnitIntent::Unknown: + summaryItem = &result.ApplySummary; + break; + default: + return; + } + + summaryItem->Count++; + + ConfigurationUnitResultSource resultSource = resultInformation.ResultSource(); + if (resultSource != ConfigurationUnitResultSource::Precondition && + resultSource != ConfigurationUnitResultSource::ConfigurationSet) + { + summaryItem->Run++; + } + + if (FAILED(resultCode)) + { + summaryItem->Failed++; + } + } + + // Runs through a set of results, summarizing them. + template + ConfigRunSummaryData ProcessRunResult(const Enumerable& results) + { + ConfigRunSummaryData result; + + for (const auto& item : results) + { + ProcessUnitResult(item.Unit(), item.ResultInformation(), result); + } + + return result; + } + } + + TelemetryTraceLogger::TelemetryTraceLogger() + { + std::ignore = CoCreateGuid(&m_activityId); + m_version = AppInstaller::Runtime::GetClientVersion(); + } + + void TelemetryTraceLogger::SetActivityId(const guid& value) + { + m_activityId = value; + } + + const GUID* TelemetryTraceLogger::GetActivityId() const + { + return &m_activityId; + } + + bool TelemetryTraceLogger::EnableRuntime(bool value) + { + return m_isRuntimeEnabled.exchange(value); + } + + bool TelemetryTraceLogger::IsEnabled() const + { + return m_isRuntimeEnabled; + } + + void TelemetryTraceLogger::SetCaller(std::string_view caller) + { + m_caller = caller; + } + + std::string_view TelemetryTraceLogger::GetCaller() const + { + return m_caller; + } + + void TelemetryTraceLogger::LogConfigUnitRun( + const guid& setIdentifier, + const guid& unitIdentifier, + hstring unitName, + hstring moduleName, + ConfigurationUnitIntent unitIntent, + ConfigurationUnitIntent runIntent, + std::string_view action, + hresult result, + ConfigurationUnitResultSource failurePoint, + std::wstring_view settingNames) const noexcept try + { + // Change unknown to Apply for telemetry, as it will have been treated that way + if (unitIntent == ConfigurationUnitIntent::Unknown) + { + unitIntent = ConfigurationUnitIntent::Apply; + } + + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "ConfigUnitRun", + TraceLoggingGuid(setIdentifier, "SetID"), + TraceLoggingGuid(unitIdentifier, "UnitID"), + AICLI_TraceLoggingWStringView(unitName, "UnitName"), + AICLI_TraceLoggingWStringView(moduleName, "ModuleName"), + TraceLoggingInt32(static_cast(unitIntent), "UnitIntent"), + TraceLoggingInt32(static_cast(runIntent), "RunIntent"), + AICLI_TraceLoggingStringView(action, "Action"), + TraceLoggingHResult(result, "Result"), + TraceLoggingInt32(static_cast(failurePoint), "FailurePoint"), + AICLI_TraceLoggingWStringView(settingNames, "SettingsProvided"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + // Keep in sync with above event! + WinGet_WriteEventToDiagnostics( + "ConfigUnitRun", + WinGet_EventItem(setIdentifier, "SetID"), + WinGet_EventItem(unitIdentifier, "UnitID"), + WinGet_EventItem(unitName, "UnitName"), + WinGet_EventItem(moduleName, "ModuleName"), + WinGet_EventItem(static_cast(unitIntent), "UnitIntent"), + WinGet_EventItem(static_cast(runIntent), "RunIntent"), + WinGet_EventItem(action, "Action"), + WinGet_EventItem(result, "Result"), + WinGet_EventItem(static_cast(failurePoint), "FailurePoint"), + WinGet_EventItem(settingNames, "SettingsProvided")); + } + } + CATCH_LOG(); + + void TelemetryTraceLogger::LogConfigUnitRunIfAppropriate( + const guid& setIdentifier, + const Configuration::ConfigurationUnit& unit, + ConfigurationUnitIntent runIntent, + std::string_view action, + const IConfigurationUnitResultInformation& resultInformation) const noexcept try + { + if (!IsTelemetryEnabled()) + { + return; + } + + // We only want to send telemetry for publicly available units. + IConfigurationUnitProcessorDetails details = unit.Details(); + if (!details || !details.IsPublic()) + { + return; + } + + // Create a single string from the set of top level setting names, ex. "a|b|c". + const winrt::Windows::Foundation::Collections::ValueSet& settings = unit.Settings(); + std::wostringstream strstr; + + for (const auto& setting : settings) + { + strstr << static_cast(setting.Key()) << L'|'; + } + std::wstring allSettingsNames = strstr.str(); + if (!allSettingsNames.empty()) + { + allSettingsNames.pop_back(); + } + + LogConfigUnitRun(setIdentifier, unit.InstanceIdentifier(), unit.Type(), details.ModuleName(), unit.Intent(), runIntent, action, resultInformation.ResultCode(), resultInformation.ResultSource(), allSettingsNames); + } + CATCH_LOG(); + + void TelemetryTraceLogger::LogConfigProcessingSummary( + const guid& setIdentifier, + std::string_view inputHash, + ConfigurationUnitIntent runIntent, + hresult result, + ConfigurationUnitResultSource failurePoint, + const ProcessingSummaryForIntent& assertSummary, + const ProcessingSummaryForIntent& informSummary, + const ProcessingSummaryForIntent& applySummary) const noexcept try + { + if (IsTelemetryEnabled()) + { + AICLI_TraceLoggingWriteActivity( + "ConfigProcessingSummary", + TraceLoggingGuid(setIdentifier, "SetID"), + AICLI_TraceLoggingStringView(inputHash, "InputHash"), + TraceLoggingBool(false, "FromHistory"), // deprecated + TraceLoggingInt32(static_cast(runIntent), "RunIntent"), + TraceLoggingHResult(result, "Result"), + TraceLoggingInt32(static_cast(failurePoint), "FailurePoint"), + AICLI_TraceLoggingProcessingSummaryForIntent(assertSummary, "Assert", "Asserts"), + AICLI_TraceLoggingProcessingSummaryForIntent(informSummary, "Inform", "Informs"), + AICLI_TraceLoggingProcessingSummaryForIntent(applySummary, "Apply", "Applies"), + TelemetryPrivacyDataTag(PDT_ProductAndServicePerformance), + TraceLoggingKeyword(MICROSOFT_KEYWORD_CRITICAL_DATA)); + + // Keep in sync with above event! + WinGet_WriteEventToDiagnostics( + "ConfigProcessingSummary", + WinGet_EventItem(setIdentifier, "SetID"), + WinGet_EventItem(inputHash, "InputHash"), + WinGet_EventItem(false, "FromHistory"), // deprecated + WinGet_EventItem(static_cast(runIntent), "RunIntent"), + WinGet_EventItem(result, "Result"), + WinGet_EventItem(static_cast(failurePoint), "FailurePoint"), + WinGet_SummaryForIntentItem(assertSummary, "Assert", "Asserts"), + WinGet_SummaryForIntentItem(informSummary, "Inform", "Informs"), + WinGet_SummaryForIntentItem(applySummary, "Apply", "Applies")); + } + } + CATCH_LOG(); + + void TelemetryTraceLogger::LogConfigProcessingSummaryForTest( + const ConfigurationSet& configurationSet, + const TestConfigurationSetResult& result) const noexcept try + { + if (!IsTelemetryEnabled()) + { + return; + } + + ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); + + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Assert, + summaryData.Result, summaryData.FailurePoint, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); + } + CATCH_LOG(); + + void TelemetryTraceLogger::LogConfigProcessingSummaryForTestException( + const ConfigurationSet& configurationSet, + hresult error, + const TestConfigurationSetResult& result) const noexcept try + { + if (!IsTelemetryEnabled()) + { + return; + } + + ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); + + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Assert, + error, ConfigurationUnitResultSource::Internal, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); + } + CATCH_LOG(); + + void TelemetryTraceLogger::LogConfigProcessingSummaryForApply( + const ConfigurationSet& configurationSet, + const ApplyConfigurationSetResult& result) const noexcept try + { + if (!IsTelemetryEnabled()) + { + return; + } + + ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); + + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Apply, + result.ResultCode(), summaryData.FailurePoint, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); + } + CATCH_LOG(); + + void TelemetryTraceLogger::LogConfigProcessingSummaryForApplyException( + const ConfigurationSet& configurationSet, + hresult error, + const ApplyConfigurationSetResult& result) const noexcept try + { + if (!IsTelemetryEnabled()) + { + return; + } + + ConfigRunSummaryData summaryData = ProcessRunResult(result.UnitResults()); + + LogConfigProcessingSummary(configurationSet.InstanceIdentifier(), configurationSet.GetInputHash(), ConfigurationUnitIntent::Apply, + error, ConfigurationUnitResultSource::Internal, summaryData.AssertSummary, summaryData.InformSummary, summaryData.ApplySummary); + } + CATCH_LOG(); + + bool TelemetryTraceLogger::IsTelemetryEnabled() const noexcept + { +#ifdef AICLI_DISABLE_TEST_HOOKS + return g_IsTelemetryProviderEnabled && m_isRuntimeEnabled; +#else + // For testing, only use the local enable state. + return m_isRuntimeEnabled; +#endif + } +} diff --git a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h index 8e40b7bdf6..141c026afa 100644 --- a/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h +++ b/src/Microsoft.Management.Configuration/Telemetry/Telemetry.h @@ -1,127 +1,127 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include "ConfigurationUnitResultInformation.h" -#include "ConfigurationSet.h" -#include "TestConfigurationSetResult.h" -#include "ApplyConfigurationSetResult.h" - -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - // Provides the ability to write telemetry events. - struct TelemetryTraceLogger - { - TelemetryTraceLogger(); - - TelemetryTraceLogger(const TelemetryTraceLogger&) = default; - TelemetryTraceLogger& operator=(const TelemetryTraceLogger&) = default; - - TelemetryTraceLogger(TelemetryTraceLogger&&) = default; - TelemetryTraceLogger& operator=(TelemetryTraceLogger&&) = default; - - // Control whether this trace logger is enabled at runtime. - // Returns the previous value. - bool EnableRuntime(bool value); - - // Returns a value indicating whether the logger is enabled. - bool IsEnabled() const; - - // Sets the current activity identifier. - void SetActivityId(const guid& value); - - // Return address of m_activityId - const GUID* GetActivityId() const; - - // Store the passed in name of the caller - void SetCaller(std::string_view caller); - - // Get the current caller value - std::string_view GetCaller() const; - - static constexpr std::string_view GetAction = "get"; - static constexpr std::string_view ApplyAction = "apply"; - static constexpr std::string_view TestAction = "test"; - static constexpr std::string_view ExportAction = "export"; - - // Logs information about running a configuration unit. - // The caller is expected to only call this for failures from publicly available units. - void LogConfigUnitRun( - const guid& setIdentifier, - const guid& unitIdentifier, - hstring unitName, - hstring moduleName, - ConfigurationUnitIntent unitIntent, - ConfigurationUnitIntent runIntent, - std::string_view action, - hresult result, - ConfigurationUnitResultSource failurePoint, - std::wstring_view settingNames) const noexcept; - - // Logs information about running a configuration unit in the appropriate conditions. - void LogConfigUnitRunIfAppropriate( - const guid& setIdentifier, - const Configuration::ConfigurationUnit& unit, - ConfigurationUnitIntent runIntent, - std::string_view action, - const IConfigurationUnitResultInformation& resultInformation) const noexcept; - - // The summary information for a specific unit intent. - struct ProcessingSummaryForIntent - { - ConfigurationUnitIntent Intent; - uint32_t Count; - uint32_t Run; - uint32_t Failed; - }; - - // Logs a processing summary event for a configuration set. - void LogConfigProcessingSummary( - const guid& setIdentifier, - std::string_view inputHash, - ConfigurationUnitIntent runIntent, - hresult result, - ConfigurationUnitResultSource failurePoint, - const ProcessingSummaryForIntent& assertSummary, - const ProcessingSummaryForIntent& informSummary, - const ProcessingSummaryForIntent& applySummary) const noexcept; - - // Logs a processing summary event for a configuration set test run. - void LogConfigProcessingSummaryForTest( - const ConfigurationSet& configurationSet, - const TestConfigurationSetResult& result) const noexcept; - - // Logs a processing summary event for a configuration set test run exception. - void LogConfigProcessingSummaryForTestException( - const ConfigurationSet& configurationSet, - hresult error, - const TestConfigurationSetResult& result) const noexcept; - - // Logs a processing summary event for a configuration set apply run. - void LogConfigProcessingSummaryForApply( - const ConfigurationSet& configurationSet, - const ApplyConfigurationSetResult& result) const noexcept; - - // Logs a processing summary event for a configuration set apply run exception. - void LogConfigProcessingSummaryForApplyException( - const ConfigurationSet& configurationSet, - hresult error, - const ApplyConfigurationSetResult& result) const noexcept; - - protected: - bool IsTelemetryEnabled() const noexcept; - - CopyConstructibleAtomic m_isRuntimeEnabled{ true }; - - GUID m_activityId = GUID_NULL; - std::string m_version; - std::string m_caller; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include "ConfigurationUnitResultInformation.h" +#include "ConfigurationSet.h" +#include "TestConfigurationSetResult.h" +#include "ApplyConfigurationSetResult.h" + +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + // Provides the ability to write telemetry events. + struct TelemetryTraceLogger + { + TelemetryTraceLogger(); + + TelemetryTraceLogger(const TelemetryTraceLogger&) = default; + TelemetryTraceLogger& operator=(const TelemetryTraceLogger&) = default; + + TelemetryTraceLogger(TelemetryTraceLogger&&) = default; + TelemetryTraceLogger& operator=(TelemetryTraceLogger&&) = default; + + // Control whether this trace logger is enabled at runtime. + // Returns the previous value. + bool EnableRuntime(bool value); + + // Returns a value indicating whether the logger is enabled. + bool IsEnabled() const; + + // Sets the current activity identifier. + void SetActivityId(const guid& value); + + // Return address of m_activityId + const GUID* GetActivityId() const; + + // Store the passed in name of the caller + void SetCaller(std::string_view caller); + + // Get the current caller value + std::string_view GetCaller() const; + + static constexpr std::string_view GetAction = "get"; + static constexpr std::string_view ApplyAction = "apply"; + static constexpr std::string_view TestAction = "test"; + static constexpr std::string_view ExportAction = "export"; + + // Logs information about running a configuration unit. + // The caller is expected to only call this for failures from publicly available units. + void LogConfigUnitRun( + const guid& setIdentifier, + const guid& unitIdentifier, + hstring unitName, + hstring moduleName, + ConfigurationUnitIntent unitIntent, + ConfigurationUnitIntent runIntent, + std::string_view action, + hresult result, + ConfigurationUnitResultSource failurePoint, + std::wstring_view settingNames) const noexcept; + + // Logs information about running a configuration unit in the appropriate conditions. + void LogConfigUnitRunIfAppropriate( + const guid& setIdentifier, + const Configuration::ConfigurationUnit& unit, + ConfigurationUnitIntent runIntent, + std::string_view action, + const IConfigurationUnitResultInformation& resultInformation) const noexcept; + + // The summary information for a specific unit intent. + struct ProcessingSummaryForIntent + { + ConfigurationUnitIntent Intent; + uint32_t Count; + uint32_t Run; + uint32_t Failed; + }; + + // Logs a processing summary event for a configuration set. + void LogConfigProcessingSummary( + const guid& setIdentifier, + std::string_view inputHash, + ConfigurationUnitIntent runIntent, + hresult result, + ConfigurationUnitResultSource failurePoint, + const ProcessingSummaryForIntent& assertSummary, + const ProcessingSummaryForIntent& informSummary, + const ProcessingSummaryForIntent& applySummary) const noexcept; + + // Logs a processing summary event for a configuration set test run. + void LogConfigProcessingSummaryForTest( + const ConfigurationSet& configurationSet, + const TestConfigurationSetResult& result) const noexcept; + + // Logs a processing summary event for a configuration set test run exception. + void LogConfigProcessingSummaryForTestException( + const ConfigurationSet& configurationSet, + hresult error, + const TestConfigurationSetResult& result) const noexcept; + + // Logs a processing summary event for a configuration set apply run. + void LogConfigProcessingSummaryForApply( + const ConfigurationSet& configurationSet, + const ApplyConfigurationSetResult& result) const noexcept; + + // Logs a processing summary event for a configuration set apply run exception. + void LogConfigProcessingSummaryForApplyException( + const ConfigurationSet& configurationSet, + hresult error, + const ApplyConfigurationSetResult& result) const noexcept; + + protected: + bool IsTelemetryEnabled() const noexcept; + + CopyConstructibleAtomic m_isRuntimeEnabled{ true }; + + GUID m_activityId = GUID_NULL; + std::string m_version; + std::string m_caller; + }; +} diff --git a/src/Microsoft.Management.Configuration/TestConfigurationSetResult.cpp b/src/Microsoft.Management.Configuration/TestConfigurationSetResult.cpp index 2b68b7ac87..f36508fb06 100644 --- a/src/Microsoft.Management.Configuration/TestConfigurationSetResult.cpp +++ b/src/Microsoft.Management.Configuration/TestConfigurationSetResult.cpp @@ -1,66 +1,66 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestConfigurationSetResult.h" -#include "TestConfigurationSetResult.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - TestConfigurationSetResult::TestConfigurationSetResult() : m_unitResults(multi_threaded_vector()) - { - } - - void TestConfigurationSetResult::AppendUnitResult(const TestConfigurationUnitResult& unitResult) - { - m_unitResults.Append(unitResult); - - // Also aggregate the result of this incoming test into the overall result - m_testResult = FoldInTestResult(m_testResult, unitResult.TestResult()); - } - - ConfigurationTestResult TestConfigurationSetResult::FoldInTestResult(ConfigurationTestResult current, ConfigurationTestResult incoming) - { - switch (current) - { - case ConfigurationTestResult::Unknown: - case ConfigurationTestResult::NotRun: - // In these "default" cases, just take the unit result - return incoming; - break; - case ConfigurationTestResult::Positive: - if (incoming == ConfigurationTestResult::Negative || incoming == ConfigurationTestResult::Failed) - { - return incoming; - } - break; - case ConfigurationTestResult::Negative: - if (incoming == ConfigurationTestResult::Failed) - { - return incoming; - } - break; - case ConfigurationTestResult::Failed: - // If a unit failed, the set failed - break; - default: - THROW_HR(E_UNEXPECTED); - } - - return current; - } - - Windows::Foundation::Collections::IVectorView TestConfigurationSetResult::UnitResults() const - { - return m_unitResults.GetView(); - } - - ConfigurationTestResult TestConfigurationSetResult::TestResult() const - { - return m_testResult; - } - - void TestConfigurationSetResult::TestResult(ConfigurationTestResult value) - { - m_testResult = value; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestConfigurationSetResult.h" +#include "TestConfigurationSetResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + TestConfigurationSetResult::TestConfigurationSetResult() : m_unitResults(multi_threaded_vector()) + { + } + + void TestConfigurationSetResult::AppendUnitResult(const TestConfigurationUnitResult& unitResult) + { + m_unitResults.Append(unitResult); + + // Also aggregate the result of this incoming test into the overall result + m_testResult = FoldInTestResult(m_testResult, unitResult.TestResult()); + } + + ConfigurationTestResult TestConfigurationSetResult::FoldInTestResult(ConfigurationTestResult current, ConfigurationTestResult incoming) + { + switch (current) + { + case ConfigurationTestResult::Unknown: + case ConfigurationTestResult::NotRun: + // In these "default" cases, just take the unit result + return incoming; + break; + case ConfigurationTestResult::Positive: + if (incoming == ConfigurationTestResult::Negative || incoming == ConfigurationTestResult::Failed) + { + return incoming; + } + break; + case ConfigurationTestResult::Negative: + if (incoming == ConfigurationTestResult::Failed) + { + return incoming; + } + break; + case ConfigurationTestResult::Failed: + // If a unit failed, the set failed + break; + default: + THROW_HR(E_UNEXPECTED); + } + + return current; + } + + Windows::Foundation::Collections::IVectorView TestConfigurationSetResult::UnitResults() const + { + return m_unitResults.GetView(); + } + + ConfigurationTestResult TestConfigurationSetResult::TestResult() const + { + return m_testResult; + } + + void TestConfigurationSetResult::TestResult(ConfigurationTestResult value) + { + m_testResult = value; + } +} diff --git a/src/Microsoft.Management.Configuration/TestConfigurationSetResult.h b/src/Microsoft.Management.Configuration/TestConfigurationSetResult.h index 6d58930f5d..d2cec1fbfc 100644 --- a/src/Microsoft.Management.Configuration/TestConfigurationSetResult.h +++ b/src/Microsoft.Management.Configuration/TestConfigurationSetResult.h @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "TestConfigurationSetResult.g.h" -#include - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct TestConfigurationSetResult : TestConfigurationSetResultT - { - TestConfigurationSetResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void AppendUnitResult(const TestConfigurationUnitResult& unitResult); - void TestResult(ConfigurationTestResult value); - - static ConfigurationTestResult FoldInTestResult(ConfigurationTestResult current, ConfigurationTestResult incoming); -#endif - - Windows::Foundation::Collections::IVectorView UnitResults() const; - ConfigurationTestResult TestResult() const; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - Windows::Foundation::Collections::IVector m_unitResults = nullptr; - ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "TestConfigurationSetResult.g.h" +#include + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct TestConfigurationSetResult : TestConfigurationSetResultT + { + TestConfigurationSetResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void AppendUnitResult(const TestConfigurationUnitResult& unitResult); + void TestResult(ConfigurationTestResult value); + + static ConfigurationTestResult FoldInTestResult(ConfigurationTestResult current, ConfigurationTestResult incoming); +#endif + + Windows::Foundation::Collections::IVectorView UnitResults() const; + ConfigurationTestResult TestResult() const; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::Collections::IVector m_unitResults = nullptr; + ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.cpp b/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.cpp index 41a95ae00a..e5d8f6f61f 100644 --- a/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.cpp +++ b/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.cpp @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestConfigurationUnitResult.h" -#include "TestConfigurationUnitResult.g.cpp" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - void TestConfigurationUnitResult::Initialize(const ITestSettingsResult& result) - { - m_unit = result.Unit(); - THROW_HR_IF(E_POINTER, !m_unit); - m_testResult = result.TestResult(); - m_resultInformation = result.ResultInformation(); - THROW_HR_IF(E_POINTER, !m_resultInformation); - } - - void TestConfigurationUnitResult::Unit(const ConfigurationUnit& unit) - { - m_unit = unit; - } - - ConfigurationUnit TestConfigurationUnitResult::Unit() - { - return m_unit; - } - - IConfigurationUnitResultInformation TestConfigurationUnitResult::ResultInformation() - { - return m_resultInformation; - } - - void TestConfigurationUnitResult::ResultInformation(const IConfigurationUnitResultInformation& value) - { - m_resultInformation = value; - } - - ConfigurationTestResult TestConfigurationUnitResult::TestResult() - { - return m_testResult; - } - - void TestConfigurationUnitResult::TestResult(ConfigurationTestResult value) - { - m_testResult = value; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestConfigurationUnitResult.h" +#include "TestConfigurationUnitResult.g.cpp" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void TestConfigurationUnitResult::Initialize(const ITestSettingsResult& result) + { + m_unit = result.Unit(); + THROW_HR_IF(E_POINTER, !m_unit); + m_testResult = result.TestResult(); + m_resultInformation = result.ResultInformation(); + THROW_HR_IF(E_POINTER, !m_resultInformation); + } + + void TestConfigurationUnitResult::Unit(const ConfigurationUnit& unit) + { + m_unit = unit; + } + + ConfigurationUnit TestConfigurationUnitResult::Unit() + { + return m_unit; + } + + IConfigurationUnitResultInformation TestConfigurationUnitResult::ResultInformation() + { + return m_resultInformation; + } + + void TestConfigurationUnitResult::ResultInformation(const IConfigurationUnitResultInformation& value) + { + m_resultInformation = value; + } + + ConfigurationTestResult TestConfigurationUnitResult::TestResult() + { + return m_testResult; + } + + void TestConfigurationUnitResult::TestResult(ConfigurationTestResult value) + { + m_testResult = value; + } +} diff --git a/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.h b/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.h index d3e629e93e..4165eb210c 100644 --- a/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.h +++ b/src/Microsoft.Management.Configuration/TestConfigurationUnitResult.h @@ -1,33 +1,33 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "TestConfigurationUnitResult.g.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct TestConfigurationUnitResult : TestConfigurationUnitResultT - { - using ConfigurationUnit = Configuration::ConfigurationUnit; - - TestConfigurationUnitResult() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(const ITestSettingsResult& result); - - void Unit(const ConfigurationUnit& unit); - void ResultInformation(const IConfigurationUnitResultInformation& value); - void TestResult(ConfigurationTestResult value); -#endif - - ConfigurationUnit Unit(); - IConfigurationUnitResultInformation ResultInformation(); - ConfigurationTestResult TestResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ConfigurationUnit m_unit = nullptr; - IConfigurationUnitResultInformation m_resultInformation; - ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "TestConfigurationUnitResult.g.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct TestConfigurationUnitResult : TestConfigurationUnitResultT + { + using ConfigurationUnit = Configuration::ConfigurationUnit; + + TestConfigurationUnitResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(const ITestSettingsResult& result); + + void Unit(const ConfigurationUnit& unit); + void ResultInformation(const IConfigurationUnitResultInformation& value); + void TestResult(ConfigurationTestResult value); +#endif + + ConfigurationUnit Unit(); + IConfigurationUnitResultInformation ResultInformation(); + ConfigurationTestResult TestResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationUnit m_unit = nullptr; + IConfigurationUnitResultInformation m_resultInformation; + ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/TestGroupSettingsResult.cpp b/src/Microsoft.Management.Configuration/TestGroupSettingsResult.cpp index c3bae0b220..954080dd98 100644 --- a/src/Microsoft.Management.Configuration/TestGroupSettingsResult.cpp +++ b/src/Microsoft.Management.Configuration/TestGroupSettingsResult.cpp @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestGroupSettingsResult.h" -#include "TestConfigurationSetResult.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - TestGroupSettingsResult::TestGroupSettingsResult() : - m_resultInformation(make_self>()), - m_unitResults(winrt::multi_threaded_vector()) - {} - - void TestGroupSettingsResult::Group(const Windows::Foundation::IInspectable& value) - { - m_group = value; - } - - void TestGroupSettingsResult::TestResult(ConfigurationTestResult value) - { - m_testResult = value; - } - - TestGroupSettingsResult::ResultInformationPtr TestGroupSettingsResult::ResultInformationInternal() - { - return m_resultInformation; - } - - void TestGroupSettingsResult::AppendUnitResult(const ITestSettingsResult& value) - { - m_unitResults.Append(value); - - // Also aggregate the result of this incoming test into the overall result - m_testResult = TestConfigurationSetResult::FoldInTestResult(m_testResult, value.TestResult()); - } - - Windows::Foundation::IInspectable TestGroupSettingsResult::Group() - { - return m_group; - } - - ConfigurationTestResult TestGroupSettingsResult::TestResult() - { - return m_testResult; - } - - IConfigurationUnitResultInformation TestGroupSettingsResult::ResultInformation() - { - return *m_resultInformation; - } - - Windows::Foundation::Collections::IVector TestGroupSettingsResult::UnitResults() - { - return m_unitResults; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestGroupSettingsResult.h" +#include "TestConfigurationSetResult.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + TestGroupSettingsResult::TestGroupSettingsResult() : + m_resultInformation(make_self>()), + m_unitResults(winrt::multi_threaded_vector()) + {} + + void TestGroupSettingsResult::Group(const Windows::Foundation::IInspectable& value) + { + m_group = value; + } + + void TestGroupSettingsResult::TestResult(ConfigurationTestResult value) + { + m_testResult = value; + } + + TestGroupSettingsResult::ResultInformationPtr TestGroupSettingsResult::ResultInformationInternal() + { + return m_resultInformation; + } + + void TestGroupSettingsResult::AppendUnitResult(const ITestSettingsResult& value) + { + m_unitResults.Append(value); + + // Also aggregate the result of this incoming test into the overall result + m_testResult = TestConfigurationSetResult::FoldInTestResult(m_testResult, value.TestResult()); + } + + Windows::Foundation::IInspectable TestGroupSettingsResult::Group() + { + return m_group; + } + + ConfigurationTestResult TestGroupSettingsResult::TestResult() + { + return m_testResult; + } + + IConfigurationUnitResultInformation TestGroupSettingsResult::ResultInformation() + { + return *m_resultInformation; + } + + Windows::Foundation::Collections::IVector TestGroupSettingsResult::UnitResults() + { + return m_unitResults; + } +} diff --git a/src/Microsoft.Management.Configuration/TestGroupSettingsResult.h b/src/Microsoft.Management.Configuration/TestGroupSettingsResult.h index df6c526eed..15a311eaec 100644 --- a/src/Microsoft.Management.Configuration/TestGroupSettingsResult.h +++ b/src/Microsoft.Management.Configuration/TestGroupSettingsResult.h @@ -1,35 +1,35 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" -#include "ConfigurationUnitResultInformation.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct TestGroupSettingsResult : winrt::implements - { - TestGroupSettingsResult(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - using ResultInformationPtr = decltype(make_self>()); - - void Group(const Windows::Foundation::IInspectable& value); - void TestResult(ConfigurationTestResult value); - ResultInformationPtr ResultInformationInternal(); - void AppendUnitResult(const ITestSettingsResult& value); -#endif - - Windows::Foundation::IInspectable Group(); - ConfigurationTestResult TestResult(); - IConfigurationUnitResultInformation ResultInformation(); - Windows::Foundation::Collections::IVector UnitResults(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - Windows::Foundation::IInspectable m_group; - ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; - ResultInformationPtr m_resultInformation; - Windows::Foundation::Collections::IVector m_unitResults; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct TestGroupSettingsResult : winrt::implements + { + TestGroupSettingsResult(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + using ResultInformationPtr = decltype(make_self>()); + + void Group(const Windows::Foundation::IInspectable& value); + void TestResult(ConfigurationTestResult value); + ResultInformationPtr ResultInformationInternal(); + void AppendUnitResult(const ITestSettingsResult& value); +#endif + + Windows::Foundation::IInspectable Group(); + ConfigurationTestResult TestResult(); + IConfigurationUnitResultInformation ResultInformation(); + Windows::Foundation::Collections::IVector UnitResults(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + Windows::Foundation::IInspectable m_group; + ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; + ResultInformationPtr m_resultInformation; + Windows::Foundation::Collections::IVector m_unitResults; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/TestSettingsResult.cpp b/src/Microsoft.Management.Configuration/TestSettingsResult.cpp index ea9deec418..acd7872605 100644 --- a/src/Microsoft.Management.Configuration/TestSettingsResult.cpp +++ b/src/Microsoft.Management.Configuration/TestSettingsResult.cpp @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "TestSettingsResult.h" -#include "ConfigurationUnitResultInformation.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - void TestSettingsResult::Unit(const ConfigurationUnit& value) - { - m_unit = value; - } - - void TestSettingsResult::TestResult(ConfigurationTestResult value) - { - m_testResult = value; - } - - void TestSettingsResult::ResultInformation(const IConfigurationUnitResultInformation& value) - { - m_resultInformation = value; - } - - ConfigurationUnit TestSettingsResult::Unit() - { - return m_unit; - } - - ConfigurationTestResult TestSettingsResult::TestResult() - { - return m_testResult; - } - - IConfigurationUnitResultInformation TestSettingsResult::ResultInformation() - { - return m_resultInformation; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "TestSettingsResult.h" +#include "ConfigurationUnitResultInformation.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + void TestSettingsResult::Unit(const ConfigurationUnit& value) + { + m_unit = value; + } + + void TestSettingsResult::TestResult(ConfigurationTestResult value) + { + m_testResult = value; + } + + void TestSettingsResult::ResultInformation(const IConfigurationUnitResultInformation& value) + { + m_resultInformation = value; + } + + ConfigurationUnit TestSettingsResult::Unit() + { + return m_unit; + } + + ConfigurationTestResult TestSettingsResult::TestResult() + { + return m_testResult; + } + + IConfigurationUnitResultInformation TestSettingsResult::ResultInformation() + { + return m_resultInformation; + } +} diff --git a/src/Microsoft.Management.Configuration/TestSettingsResult.h b/src/Microsoft.Management.Configuration/TestSettingsResult.h index 72e193dcec..708087faff 100644 --- a/src/Microsoft.Management.Configuration/TestSettingsResult.h +++ b/src/Microsoft.Management.Configuration/TestSettingsResult.h @@ -1,31 +1,31 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "winrt/Microsoft.Management.Configuration.h" - -namespace winrt::Microsoft::Management::Configuration::implementation -{ - struct TestSettingsResult : winrt::implements - { - using ConfigurationUnit = Configuration::ConfigurationUnit; - - TestSettingsResult() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Unit(const ConfigurationUnit& value); - void TestResult(ConfigurationTestResult value); - void ResultInformation(const IConfigurationUnitResultInformation& value); -#endif - - ConfigurationUnit Unit(); - ConfigurationTestResult TestResult(); - IConfigurationUnitResultInformation ResultInformation(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ConfigurationUnit m_unit = nullptr; - ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; - IConfigurationUnitResultInformation m_resultInformation; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "winrt/Microsoft.Management.Configuration.h" + +namespace winrt::Microsoft::Management::Configuration::implementation +{ + struct TestSettingsResult : winrt::implements + { + using ConfigurationUnit = Configuration::ConfigurationUnit; + + TestSettingsResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Unit(const ConfigurationUnit& value); + void TestResult(ConfigurationTestResult value); + void ResultInformation(const IConfigurationUnitResultInformation& value); +#endif + + ConfigurationUnit Unit(); + ConfigurationTestResult TestResult(); + IConfigurationUnitResultInformation ResultInformation(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ConfigurationUnit m_unit = nullptr; + ConfigurationTestResult m_testResult = ConfigurationTestResult::Unknown; + IConfigurationUnitResultInformation m_resultInformation; +#endif + }; +} diff --git a/src/Microsoft.Management.Configuration/packages.config b/src/Microsoft.Management.Configuration/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/Microsoft.Management.Configuration/packages.config +++ b/src/Microsoft.Management.Configuration/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Configuration/pch.cpp b/src/Microsoft.Management.Configuration/pch.cpp index 147dc1b6e0..e4b1bd6915 100644 --- a/src/Microsoft.Management.Configuration/pch.cpp +++ b/src/Microsoft.Management.Configuration/pch.cpp @@ -1,3 +1,3 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" diff --git a/src/Microsoft.Management.Configuration/pch.h b/src/Microsoft.Management.Configuration/pch.h index 3b6c6a7c13..fac21029cd 100644 --- a/src/Microsoft.Management.Configuration/pch.h +++ b/src/Microsoft.Management.Configuration/pch.h @@ -1,39 +1,39 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#define NOMINMAX - -#include -#include -#include -#include - -#pragma warning( push ) -#pragma warning ( disable : 4324 4467 6388 ) -// 4324 Structure was padded due to alignment specifier -// 4467 Allow use of uuid attribute for com object creation. -// 6388 Allow CreateInstance. -#include -#include -#pragma warning( pop ) - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define NOMINMAX + +#include +#include +#include +#include + +#pragma warning( push ) +#pragma warning ( disable : 4324 4467 6388 ) +// 4324 Structure was padded due to alignment specifier +// 4467 Allow use of uuid attribute for com object creation. +// 6388 Allow CreateInstance. +#include +#include +#pragma warning( pop ) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/src/Microsoft.Management.Deployment.CsWinRTProjection/Microsoft.Management.Deployment.CsWinRTProjection.csproj b/src/Microsoft.Management.Deployment.CsWinRTProjection/Microsoft.Management.Deployment.CsWinRTProjection.csproj index eb140a34e4..be26da6d44 100644 --- a/src/Microsoft.Management.Deployment.CsWinRTProjection/Microsoft.Management.Deployment.CsWinRTProjection.csproj +++ b/src/Microsoft.Management.Deployment.CsWinRTProjection/Microsoft.Management.Deployment.CsWinRTProjection.csproj @@ -1,46 +1,46 @@ - - - - net8.0-windows10.0.26100.0 - AnyCpu - enable - enable - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - Debug;Release;ReleaseStatic - true - true - true - - - - Microsoft.Management.Deployment - $(OutDir) - 10.0.26100.0 - - 10.0.17763.0 - - True - - - - - None - - - - - - - - - Content - PreserveNewest - True - - - - + + + + net8.0-windows10.0.26100.0 + AnyCpu + enable + enable + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + Debug;Release;ReleaseStatic + true + true + true + + + + Microsoft.Management.Deployment + $(OutDir) + 10.0.26100.0 + + 10.0.17763.0 + + True + + + + + None + + + + + + + + + Content + PreserveNewest + True + + + + diff --git a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest index 3760de0c4c..55e76a5abd 100644 --- a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest +++ b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.dll.manifest @@ -1,115 +1,115 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj index f8c160ef24..25367bc4b5 100644 --- a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj +++ b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj @@ -1,388 +1,388 @@ - - - - - true - true - true - 15.0 - Win32Proj - {9ac3c6a4-1875-4d3e-bf9c-c31e81eff6b4} - MicrosoftManagementDeploymentInProc - 10.0.26100.0 - 10.0.17763.0 - true - false - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - DynamicLibrary - - - true - true - - - false - true - false - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - true - true - false - false - stdcpp17 - stdcpp17 - true - true - true - true - false - false - - - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - WIN32;%(PreprocessorDefinitions) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - true - false - stdcpp17 - true - true - false - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - true - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full %(AdditionalOptions) - /debug:full %(AdditionalOptions) - /debug:full %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - - - - - Create - - - - - {2046b5af-666d-4ce8-8d3e-c32c57908a56} - - - - - - - - - - true - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - + + + + + true + true + true + 15.0 + Win32Proj + {9ac3c6a4-1875-4d3e-bf9c-c31e81eff6b4} + MicrosoftManagementDeploymentInProc + 10.0.26100.0 + 10.0.17763.0 + true + false + + + + + Debug + ARM64 + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + DynamicLibrary + + + true + true + + + false + true + false + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + true + true + false + false + stdcpp17 + stdcpp17 + true + true + true + true + false + false + + + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + WIN32;%(PreprocessorDefinitions) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + true + false + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + true + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full %(AdditionalOptions) + /debug:full %(AdditionalOptions) + /debug:full %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + + + + + Create + + + + + {2046b5af-666d-4ce8-8d3e-c32c57908a56} + + + + + + + + + + true + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + diff --git a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj.filters b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj.filters index 16ee3e64f9..94cb824253 100644 --- a/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj.filters +++ b/src/Microsoft.Management.Deployment.InProc/Microsoft.Management.Deployment.InProc.vcxproj.filters @@ -1,43 +1,43 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - - - Source Files - - - Source Files - - - - - - - Source Files - - - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + Source Files + + + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment.InProc/dllmain.cpp b/src/Microsoft.Management.Deployment.InProc/dllmain.cpp index 31fbd30757..24e228f6ae 100644 --- a/src/Microsoft.Management.Deployment.InProc/dllmain.cpp +++ b/src/Microsoft.Management.Deployment.InProc/dllmain.cpp @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include - -EXTERN_C BOOL WINAPI DllMain( - HMODULE /* hModule */, - DWORD reason, - LPVOID /* lpReserved */) -{ - switch (reason) - { - case DLL_PROCESS_ATTACH: - { - if (FAILED(WindowsPackageManagerInProcModuleInitialize())) - { - return FALSE; - } - } - break; - } - return TRUE; -} - -STDAPI DllGetClassObject( - REFCLSID rclsid, - REFIID riid, - LPVOID* ppv) -{ - RETURN_HR(WindowsPackageManagerInProcModuleGetClassObject(rclsid, riid, ppv)); -} - -STDAPI DllCanUnloadNow() -{ - return WindowsPackageManagerInProcModuleTerminate() ? S_OK : S_FALSE; -} - -STDAPI DllGetActivationFactory(HSTRING classId, void** factory) -{ - return WindowsPackageManagerInProcModuleGetActivationFactory(classId, factory); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include + +EXTERN_C BOOL WINAPI DllMain( + HMODULE /* hModule */, + DWORD reason, + LPVOID /* lpReserved */) +{ + switch (reason) + { + case DLL_PROCESS_ATTACH: + { + if (FAILED(WindowsPackageManagerInProcModuleInitialize())) + { + return FALSE; + } + } + break; + } + return TRUE; +} + +STDAPI DllGetClassObject( + REFCLSID rclsid, + REFIID riid, + LPVOID* ppv) +{ + RETURN_HR(WindowsPackageManagerInProcModuleGetClassObject(rclsid, riid, ppv)); +} + +STDAPI DllCanUnloadNow() +{ + return WindowsPackageManagerInProcModuleTerminate() ? S_OK : S_FALSE; +} + +STDAPI DllGetActivationFactory(HSTRING classId, void** factory) +{ + return WindowsPackageManagerInProcModuleGetActivationFactory(classId, factory); +} diff --git a/src/Microsoft.Management.Deployment.InProc/packages.config b/src/Microsoft.Management.Deployment.InProc/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/Microsoft.Management.Deployment.InProc/packages.config +++ b/src/Microsoft.Management.Deployment.InProc/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment.InProc/pch.cpp b/src/Microsoft.Management.Deployment.InProc/pch.cpp index 855ee69a1e..c084a84a60 100644 --- a/src/Microsoft.Management.Deployment.InProc/pch.cpp +++ b/src/Microsoft.Management.Deployment.InProc/pch.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" diff --git a/src/Microsoft.Management.Deployment.InProc/pch.h b/src/Microsoft.Management.Deployment.InProc/pch.h index 098c04e086..82ebe32728 100644 --- a/src/Microsoft.Management.Deployment.InProc/pch.h +++ b/src/Microsoft.Management.Deployment.InProc/pch.h @@ -1,5 +1,5 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#define WIN32_LEAN_AND_MEAN -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#define WIN32_LEAN_AND_MEAN +#include #include \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp b/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp index 087210d08b..3dce519551 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp +++ b/src/Microsoft.Management.Deployment.OutOfProc/Factory.cpp @@ -1,194 +1,194 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Factory.h" -#include -#include -#include - -using namespace std::string_view_literals; - -namespace Microsoft::Management::Deployment::OutOfProc -{ - namespace - { -#if USE_PROD_CLSIDS - constexpr CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, { 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 } }; //C53A4F16-787E-42A4-B304-29EFFB4BF597 - constexpr CLSID CLSID_InstallOptions = { 0x1095f097, 0xEB96, 0x453B, { 0xB4, 0xE6, 0x16, 0x13, 0x63, 0x7F, 0x3B, 0x14 } }; //1095F097-EB96-453B-B4E6-1613637F3B14 - constexpr CLSID CLSID_UninstallOptions = { 0xE1D9A11E, 0x9F85, 0x4D87, { 0x9C, 0x17, 0x2B, 0x93, 0x14, 0x3A, 0xDB, 0x8D } }; //E1D9A11E-9F85-4D87-9C17-2B93143ADB8D - constexpr CLSID CLSID_FindPackagesOptions = { 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; //572DED96-9C60-4526-8F92-EE7D91D38C1A - constexpr CLSID CLSID_PackageMatchFilter = { 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; //D02C9DAF-99DC-429C-B503-4E504E4AB000 - constexpr CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; //526534B8-7E46-47C8-8416-B1685C327D37 - constexpr CLSID CLSID_DownloadOptions = { 0x4CBABE76, 0x7322, 0x4BE4, { 0x9C, 0xEA, 0x25, 0x89, 0xA8, 0x06, 0x82, 0xDC } }; //4CBABE76-7322-4BE4-9CEA-2589A80682DC - constexpr CLSID CLSID_AuthenticationArguments = { 0xBA580786, 0xBDE3, 0x4F6C, { 0xB8, 0xF3, 0x44, 0x69, 0x8A, 0xC8, 0x71, 0x1A } }; //BA580786-BDE3-4F6C-B8F3-44698AC8711A - constexpr CLSID CLSID_RepairOptions = { 0x0498F441, 0x3097, 0x455F, { 0x9C, 0xAF, 0x14, 0x8F, 0x28, 0x29, 0x38, 0x65 } }; //0498F441-3097-455F-9CAF-148F28293865 - constexpr CLSID CLSID_AddPackageCatalogOptions = { 0xDB9D012D, 0x00D7, 0x47EE, { 0x8F, 0xB1, 0x60, 0x6E, 0x10, 0xAC, 0x4F, 0x51 } }; //DB9D012D-00D7-47EE-8FB1-606E10AC4F51 - constexpr CLSID CLSID_RemovePackageCatalogOptions = { 0x032B1C58, 0xB975, 0x469B, { 0xA0, 0x13, 0xE6, 0x32, 0xB6, 0xEC, 0xE8, 0xD8 } }; //032B1C58-B975-469B-A013-E632B6ECE8D8 - constexpr CLSID CLSID_EditPackageCatalogOptions = { 0xA9F5E736, 0x68CE, 0x463C, { 0xBA, 0x6D, 0xDE, 0x96, 0x8F, 0x0C, 0xCE, 0x04 } }; //A9F5E736-68CE-463C-BA6D-DE968F0CCE04 -#else - constexpr CLSID CLSID_PackageManager = { 0x74CB3139, 0xB7C5, 0x4B9E, { 0x93, 0x88, 0xE6, 0x61, 0x6D, 0xEA, 0x28, 0x8C } }; //74CB3139-B7C5-4B9E-9388-E6616DEA288C - constexpr CLSID CLSID_InstallOptions = { 0x44FE0580, 0x62F7, 0x44D4, { 0x9E, 0x91, 0xAA, 0x96, 0x14, 0xAB, 0x3E, 0x86 } }; //44FE0580-62F7-44D4-9E91-AA9614AB3E86 - constexpr CLSID CLSID_UninstallOptions = { 0xAA2A5C04, 0x1AD9, 0x46C4, { 0xB7, 0x4F, 0x6B, 0x33, 0x4A, 0xD7, 0xEB, 0x8C } }; //AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C - constexpr CLSID CLSID_FindPackagesOptions = { 0x1BD8FF3A, 0xEC50, 0x4F69, { 0xAE, 0xEE, 0xDF, 0x4C, 0x9D, 0x3B, 0xAA, 0x96 } }; //1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96 - constexpr CLSID CLSID_PackageMatchFilter = { 0x3F85B9F4, 0x487A, 0x4C48, { 0x90, 0x35, 0x29, 0x03, 0xF8, 0xA6, 0xD9, 0xE8 } }; //3F85B9F4-487A-4C48-9035-2903F8A6D9E8 - constexpr CLSID CLSID_CreateCompositePackageCatalogOptions = { 0xEE160901, 0xB317, 0x4EA7, { 0x9C, 0xC6, 0x53, 0x55, 0xC6, 0xD7, 0xD8, 0xA7 } }; //EE160901-B317-4EA7-9CC6-5355C6D7D8A7 - constexpr CLSID CLSID_DownloadOptions = { 0x8EF324ED, 0x367C, 0x4880, { 0x83, 0xE5, 0xBB, 0x2A, 0xBD, 0x0B, 0x72, 0xF6 } }; //8EF324ED-367C-4880-83E5-BB2ABD0B72F6 - constexpr CLSID CLSID_AuthenticationArguments = { 0x6484A61D, 0x50FA, 0x41F0, { 0xB7, 0x1E, 0xF4, 0x37, 0x0C, 0x6E, 0xB3, 0x7C } }; //6484A61D-50FA-41F0-B71E-F4370C6EB37C - constexpr CLSID CLSID_RepairOptions = { 0xE62BB1E7, 0xC7B2, 0x4AEC, { 0x9E, 0x28, 0xFB, 0x64, 0x9B, 0x30, 0xFF, 0x03 } }; //E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03 - constexpr CLSID CLSID_AddPackageCatalogOptions = { 0xD58C7E4C, 0x70E6, 0x476C, { 0xA5, 0xD4, 0x80, 0x34, 0x1E, 0xD8, 0x02, 0x52 } }; //D58C7E4C-70E6-476C-A5D4-80341ED80252 - constexpr CLSID CLSID_RemovePackageCatalogOptions = { 0x87A96609, 0x1A39, 0x4955, { 0xBE, 0x72, 0x71, 0x74, 0xE1, 0x47, 0xB7, 0xDC } }; //87A96609-1A39-4955-BE72-7174E147B7DC - constexpr CLSID CLSID_EditPackageCatalogOptions = { 0x29B19238, 0x81AD, 0x4A8E, { 0xA2, 0xFC, 0xAD, 0xF1, 0x7C, 0x38, 0xCA, 0xEB } }; //29B19238-81AD-4A8E-A2FC-ADF17C38CAEB - -#endif - - struct NameCLSIDPair - { - std::wstring_view Name; - GUID CLSID; - }; - - constexpr std::array s_nameCLSIDPairs - { - NameCLSIDPair{ L"Microsoft.Management.Deployment.PackageManager"sv, CLSID_PackageManager }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.InstallOptions"sv, CLSID_InstallOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.UninstallOptions"sv, CLSID_UninstallOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.FindPackagesOptions"sv, CLSID_FindPackagesOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.PackageMatchFilter"sv, CLSID_PackageMatchFilter }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.CreateCompositePackageCatalogOptions"sv, CLSID_CreateCompositePackageCatalogOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.DownloadOptions"sv, CLSID_DownloadOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.AuthenticationArguments"sv, CLSID_AuthenticationArguments }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.RepairOptions"sv, CLSID_RepairOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.AddPackageCatalogOptions"sv, CLSID_AddPackageCatalogOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.RemovePackageCatalogOptions"sv, CLSID_RemovePackageCatalogOptions }, - NameCLSIDPair{ L"Microsoft.Management.Deployment.EditPackageCatalogOptions"sv, CLSID_EditPackageCatalogOptions }, - }; - - bool IsCLSIDPresent(const GUID& clsid) - { - for (const auto& pair : s_nameCLSIDPairs) - { - if (pair.CLSID == clsid) - { - return true; - } - } - - return false; - } - - const GUID* GetCLSIDFor(HSTRING clsid) - { - UINT32 length = 0; - PCWSTR buffer = WindowsGetStringRawBuffer(clsid, &length); - std::wstring_view clsidView{ buffer, length }; - - for (const auto& pair : s_nameCLSIDPairs) - { - if (pair.Name == clsidView) - { - return &pair.CLSID; - } - } - - return nullptr; - } - - winrt::Windows::Foundation::IInspectable CreateOOPObject(const GUID& clsid) - { - bool isAdmin = AppInstaller::Runtime::IsRunningAsAdmin(); - - try - { - return winrt::create_instance(clsid, CLSCTX_LOCAL_SERVER | CLSCTX_NO_CODE_DOWNLOAD); - } - catch (const winrt::hresult_error& hre) - { - // We only want to fall through to trying the manual activation if we are running as admin and couldn't find the registration. - if (!(isAdmin && hre.code() == REGDB_E_CLASSNOTREG)) - { - throw; - } - } - - winrt::com_ptr<::IUnknown> result; - THROW_IF_FAILED(WinGetServerManualActivation_CreateInstance(clsid, winrt::guid_of(), 0, result.put_void())); - return result.as(); - } - } - - Factory::Factory(const GUID& clsid) : m_clsid(clsid) - { - IncrementRefCount(); - } - - Factory::Factory(HSTRING clsid) : m_clsid(*GetCLSIDFor(clsid)) - { - IncrementRefCount(); - } - - Factory::~Factory() - { - DecrementRefCount(); - } - - bool Factory::HasReferences() - { - return s_referenceCount.load() != 0; - } - - void Factory::Terminate() - { - WinGetServerManualActivation_Terminate(); - } - - bool Factory::IsCLSID(const GUID& clsid) - { - return IsCLSIDPresent(clsid); - } - - bool Factory::IsCLSID(HSTRING clsid) - { - return GetCLSIDFor(clsid) != nullptr; - } - - winrt::Windows::Foundation::IInspectable Factory::ActivateInstance() - { - return CreateOOPObject(m_clsid); - } - - HRESULT STDMETHODCALLTYPE Factory::CreateInstance(::IUnknown* pUnkOuter, REFIID riid, void** ppvObject) try - { - RETURN_HR_IF(E_POINTER, !ppvObject); - *ppvObject = nullptr; - RETURN_HR_IF(CLASS_E_NOAGGREGATION, pUnkOuter != nullptr); - - return CreateOOPObject(m_clsid).as(riid, ppvObject); - } - CATCH_RETURN(); - - HRESULT STDMETHODCALLTYPE Factory::LockServer(BOOL fLock) - { - if (fLock) - { - IncrementRefCount(); - } - else - { - DecrementRefCount(); - } - - return S_OK; - } - - void Factory::IncrementRefCount() - { - ++s_referenceCount; - } - - void Factory::DecrementRefCount() - { - --s_referenceCount; - } - - std::atomic Factory::s_referenceCount = ATOMIC_VAR_INIT(0); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Factory.h" +#include +#include +#include + +using namespace std::string_view_literals; + +namespace Microsoft::Management::Deployment::OutOfProc +{ + namespace + { +#if USE_PROD_CLSIDS + constexpr CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, { 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 } }; //C53A4F16-787E-42A4-B304-29EFFB4BF597 + constexpr CLSID CLSID_InstallOptions = { 0x1095f097, 0xEB96, 0x453B, { 0xB4, 0xE6, 0x16, 0x13, 0x63, 0x7F, 0x3B, 0x14 } }; //1095F097-EB96-453B-B4E6-1613637F3B14 + constexpr CLSID CLSID_UninstallOptions = { 0xE1D9A11E, 0x9F85, 0x4D87, { 0x9C, 0x17, 0x2B, 0x93, 0x14, 0x3A, 0xDB, 0x8D } }; //E1D9A11E-9F85-4D87-9C17-2B93143ADB8D + constexpr CLSID CLSID_FindPackagesOptions = { 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; //572DED96-9C60-4526-8F92-EE7D91D38C1A + constexpr CLSID CLSID_PackageMatchFilter = { 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; //D02C9DAF-99DC-429C-B503-4E504E4AB000 + constexpr CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; //526534B8-7E46-47C8-8416-B1685C327D37 + constexpr CLSID CLSID_DownloadOptions = { 0x4CBABE76, 0x7322, 0x4BE4, { 0x9C, 0xEA, 0x25, 0x89, 0xA8, 0x06, 0x82, 0xDC } }; //4CBABE76-7322-4BE4-9CEA-2589A80682DC + constexpr CLSID CLSID_AuthenticationArguments = { 0xBA580786, 0xBDE3, 0x4F6C, { 0xB8, 0xF3, 0x44, 0x69, 0x8A, 0xC8, 0x71, 0x1A } }; //BA580786-BDE3-4F6C-B8F3-44698AC8711A + constexpr CLSID CLSID_RepairOptions = { 0x0498F441, 0x3097, 0x455F, { 0x9C, 0xAF, 0x14, 0x8F, 0x28, 0x29, 0x38, 0x65 } }; //0498F441-3097-455F-9CAF-148F28293865 + constexpr CLSID CLSID_AddPackageCatalogOptions = { 0xDB9D012D, 0x00D7, 0x47EE, { 0x8F, 0xB1, 0x60, 0x6E, 0x10, 0xAC, 0x4F, 0x51 } }; //DB9D012D-00D7-47EE-8FB1-606E10AC4F51 + constexpr CLSID CLSID_RemovePackageCatalogOptions = { 0x032B1C58, 0xB975, 0x469B, { 0xA0, 0x13, 0xE6, 0x32, 0xB6, 0xEC, 0xE8, 0xD8 } }; //032B1C58-B975-469B-A013-E632B6ECE8D8 + constexpr CLSID CLSID_EditPackageCatalogOptions = { 0xA9F5E736, 0x68CE, 0x463C, { 0xBA, 0x6D, 0xDE, 0x96, 0x8F, 0x0C, 0xCE, 0x04 } }; //A9F5E736-68CE-463C-BA6D-DE968F0CCE04 +#else + constexpr CLSID CLSID_PackageManager = { 0x74CB3139, 0xB7C5, 0x4B9E, { 0x93, 0x88, 0xE6, 0x61, 0x6D, 0xEA, 0x28, 0x8C } }; //74CB3139-B7C5-4B9E-9388-E6616DEA288C + constexpr CLSID CLSID_InstallOptions = { 0x44FE0580, 0x62F7, 0x44D4, { 0x9E, 0x91, 0xAA, 0x96, 0x14, 0xAB, 0x3E, 0x86 } }; //44FE0580-62F7-44D4-9E91-AA9614AB3E86 + constexpr CLSID CLSID_UninstallOptions = { 0xAA2A5C04, 0x1AD9, 0x46C4, { 0xB7, 0x4F, 0x6B, 0x33, 0x4A, 0xD7, 0xEB, 0x8C } }; //AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C + constexpr CLSID CLSID_FindPackagesOptions = { 0x1BD8FF3A, 0xEC50, 0x4F69, { 0xAE, 0xEE, 0xDF, 0x4C, 0x9D, 0x3B, 0xAA, 0x96 } }; //1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96 + constexpr CLSID CLSID_PackageMatchFilter = { 0x3F85B9F4, 0x487A, 0x4C48, { 0x90, 0x35, 0x29, 0x03, 0xF8, 0xA6, 0xD9, 0xE8 } }; //3F85B9F4-487A-4C48-9035-2903F8A6D9E8 + constexpr CLSID CLSID_CreateCompositePackageCatalogOptions = { 0xEE160901, 0xB317, 0x4EA7, { 0x9C, 0xC6, 0x53, 0x55, 0xC6, 0xD7, 0xD8, 0xA7 } }; //EE160901-B317-4EA7-9CC6-5355C6D7D8A7 + constexpr CLSID CLSID_DownloadOptions = { 0x8EF324ED, 0x367C, 0x4880, { 0x83, 0xE5, 0xBB, 0x2A, 0xBD, 0x0B, 0x72, 0xF6 } }; //8EF324ED-367C-4880-83E5-BB2ABD0B72F6 + constexpr CLSID CLSID_AuthenticationArguments = { 0x6484A61D, 0x50FA, 0x41F0, { 0xB7, 0x1E, 0xF4, 0x37, 0x0C, 0x6E, 0xB3, 0x7C } }; //6484A61D-50FA-41F0-B71E-F4370C6EB37C + constexpr CLSID CLSID_RepairOptions = { 0xE62BB1E7, 0xC7B2, 0x4AEC, { 0x9E, 0x28, 0xFB, 0x64, 0x9B, 0x30, 0xFF, 0x03 } }; //E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03 + constexpr CLSID CLSID_AddPackageCatalogOptions = { 0xD58C7E4C, 0x70E6, 0x476C, { 0xA5, 0xD4, 0x80, 0x34, 0x1E, 0xD8, 0x02, 0x52 } }; //D58C7E4C-70E6-476C-A5D4-80341ED80252 + constexpr CLSID CLSID_RemovePackageCatalogOptions = { 0x87A96609, 0x1A39, 0x4955, { 0xBE, 0x72, 0x71, 0x74, 0xE1, 0x47, 0xB7, 0xDC } }; //87A96609-1A39-4955-BE72-7174E147B7DC + constexpr CLSID CLSID_EditPackageCatalogOptions = { 0x29B19238, 0x81AD, 0x4A8E, { 0xA2, 0xFC, 0xAD, 0xF1, 0x7C, 0x38, 0xCA, 0xEB } }; //29B19238-81AD-4A8E-A2FC-ADF17C38CAEB + +#endif + + struct NameCLSIDPair + { + std::wstring_view Name; + GUID CLSID; + }; + + constexpr std::array s_nameCLSIDPairs + { + NameCLSIDPair{ L"Microsoft.Management.Deployment.PackageManager"sv, CLSID_PackageManager }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.InstallOptions"sv, CLSID_InstallOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.UninstallOptions"sv, CLSID_UninstallOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.FindPackagesOptions"sv, CLSID_FindPackagesOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.PackageMatchFilter"sv, CLSID_PackageMatchFilter }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.CreateCompositePackageCatalogOptions"sv, CLSID_CreateCompositePackageCatalogOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.DownloadOptions"sv, CLSID_DownloadOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.AuthenticationArguments"sv, CLSID_AuthenticationArguments }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.RepairOptions"sv, CLSID_RepairOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.AddPackageCatalogOptions"sv, CLSID_AddPackageCatalogOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.RemovePackageCatalogOptions"sv, CLSID_RemovePackageCatalogOptions }, + NameCLSIDPair{ L"Microsoft.Management.Deployment.EditPackageCatalogOptions"sv, CLSID_EditPackageCatalogOptions }, + }; + + bool IsCLSIDPresent(const GUID& clsid) + { + for (const auto& pair : s_nameCLSIDPairs) + { + if (pair.CLSID == clsid) + { + return true; + } + } + + return false; + } + + const GUID* GetCLSIDFor(HSTRING clsid) + { + UINT32 length = 0; + PCWSTR buffer = WindowsGetStringRawBuffer(clsid, &length); + std::wstring_view clsidView{ buffer, length }; + + for (const auto& pair : s_nameCLSIDPairs) + { + if (pair.Name == clsidView) + { + return &pair.CLSID; + } + } + + return nullptr; + } + + winrt::Windows::Foundation::IInspectable CreateOOPObject(const GUID& clsid) + { + bool isAdmin = AppInstaller::Runtime::IsRunningAsAdmin(); + + try + { + return winrt::create_instance(clsid, CLSCTX_LOCAL_SERVER | CLSCTX_NO_CODE_DOWNLOAD); + } + catch (const winrt::hresult_error& hre) + { + // We only want to fall through to trying the manual activation if we are running as admin and couldn't find the registration. + if (!(isAdmin && hre.code() == REGDB_E_CLASSNOTREG)) + { + throw; + } + } + + winrt::com_ptr<::IUnknown> result; + THROW_IF_FAILED(WinGetServerManualActivation_CreateInstance(clsid, winrt::guid_of(), 0, result.put_void())); + return result.as(); + } + } + + Factory::Factory(const GUID& clsid) : m_clsid(clsid) + { + IncrementRefCount(); + } + + Factory::Factory(HSTRING clsid) : m_clsid(*GetCLSIDFor(clsid)) + { + IncrementRefCount(); + } + + Factory::~Factory() + { + DecrementRefCount(); + } + + bool Factory::HasReferences() + { + return s_referenceCount.load() != 0; + } + + void Factory::Terminate() + { + WinGetServerManualActivation_Terminate(); + } + + bool Factory::IsCLSID(const GUID& clsid) + { + return IsCLSIDPresent(clsid); + } + + bool Factory::IsCLSID(HSTRING clsid) + { + return GetCLSIDFor(clsid) != nullptr; + } + + winrt::Windows::Foundation::IInspectable Factory::ActivateInstance() + { + return CreateOOPObject(m_clsid); + } + + HRESULT STDMETHODCALLTYPE Factory::CreateInstance(::IUnknown* pUnkOuter, REFIID riid, void** ppvObject) try + { + RETURN_HR_IF(E_POINTER, !ppvObject); + *ppvObject = nullptr; + RETURN_HR_IF(CLASS_E_NOAGGREGATION, pUnkOuter != nullptr); + + return CreateOOPObject(m_clsid).as(riid, ppvObject); + } + CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE Factory::LockServer(BOOL fLock) + { + if (fLock) + { + IncrementRefCount(); + } + else + { + DecrementRefCount(); + } + + return S_OK; + } + + void Factory::IncrementRefCount() + { + ++s_referenceCount; + } + + void Factory::DecrementRefCount() + { + --s_referenceCount; + } + + std::atomic Factory::s_referenceCount = ATOMIC_VAR_INIT(0); +} diff --git a/src/Microsoft.Management.Deployment.OutOfProc/Factory.h b/src/Microsoft.Management.Deployment.OutOfProc/Factory.h index c50eb1fa17..9b2aba69d0 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/Factory.h +++ b/src/Microsoft.Management.Deployment.OutOfProc/Factory.h @@ -1,44 +1,44 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace Microsoft::Management::Deployment::OutOfProc -{ - struct Factory : winrt::implements - { - Factory(const GUID& clsid); - Factory(HSTRING clsid); - ~Factory(); - - // Returns true if the reference count is not 0; false if it is. - static bool HasReferences(); - - // Forcibly destroys any static objects. - static void Terminate(); - - // Determines if the given CLSID is the CLSID for the factory. - static bool IsCLSID(const GUID& clsid); - - // Determines if the given CLSID is the CLSID for the factory. - static bool IsCLSID(HSTRING clsid); - - // IActivationFactory - winrt::Windows::Foundation::IInspectable ActivateInstance(); - - // IClassFactory - HRESULT STDMETHODCALLTYPE CreateInstance(::IUnknown *pUnkOuter, REFIID riid, void **ppvObject); - HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock); - - private: - static void IncrementRefCount(); - static void DecrementRefCount(); - - static std::atomic s_referenceCount; - - GUID m_clsid; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace Microsoft::Management::Deployment::OutOfProc +{ + struct Factory : winrt::implements + { + Factory(const GUID& clsid); + Factory(HSTRING clsid); + ~Factory(); + + // Returns true if the reference count is not 0; false if it is. + static bool HasReferences(); + + // Forcibly destroys any static objects. + static void Terminate(); + + // Determines if the given CLSID is the CLSID for the factory. + static bool IsCLSID(const GUID& clsid); + + // Determines if the given CLSID is the CLSID for the factory. + static bool IsCLSID(HSTRING clsid); + + // IActivationFactory + winrt::Windows::Foundation::IInspectable ActivateInstance(); + + // IClassFactory + HRESULT STDMETHODCALLTYPE CreateInstance(::IUnknown *pUnkOuter, REFIID riid, void **ppvObject); + HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock); + + private: + static void IncrementRefCount(); + static void DecrementRefCount(); + + static std::atomic s_referenceCount; + + GUID m_clsid; + }; +} diff --git a/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj b/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj index 2e22e93b95..292dc22273 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj +++ b/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj @@ -1,425 +1,425 @@ - - - - - true - true - true - 15.0 - Win32Proj - MicrosoftManagementDeploymentOutOfProc - 10.0.26100.0 - 10.0.17763.0 - true - false - {0BA531C8-CF0C-405B-8221-0FE51BA529D1} - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - DynamicLibrary - - - true - true - - - false - true - false - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - true - false - false - stdcpp17 - stdcpp17 - true - true - true - true - 6001 - 6001 - false - false - - - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - WIN32;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - false - stdcpp17 - true - true - 6001 - false - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - true - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - 6001 - 6001 - 6001 - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - 6001 - 6001 - 6001 - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - - - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - NotUsing - - - - - Create - - - - - - - - - - {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} - - - {1cc41a9a-ae66-459d-9210-1e572dd7be69} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - + + + + + true + true + true + 15.0 + Win32Proj + MicrosoftManagementDeploymentOutOfProc + 10.0.26100.0 + 10.0.17763.0 + true + false + {0BA531C8-CF0C-405B-8221-0FE51BA529D1} + + + + + Debug + ARM64 + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + DynamicLibrary + + + true + true + + + false + true + false + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + true + false + false + stdcpp17 + stdcpp17 + true + true + true + true + 6001 + 6001 + false + false + + + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + WIN32;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + true + 6001 + false + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + true + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + 6001 + 6001 + 6001 + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + $(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\WinGetServer;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + 6001 + 6001 + 6001 + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + + + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + NotUsing + + + + + Create + + + + + + + + + + {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} + + + {1cc41a9a-ae66-459d-9210-1e572dd7be69} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + diff --git a/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj.filters b/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj.filters index c64e8e06ad..0a8e916016 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj.filters +++ b/src/Microsoft.Management.Deployment.OutOfProc/Microsoft.Management.Deployment.OutOfProc.vcxproj.filters @@ -1,58 +1,58 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - {6017fd94-3eb1-40bc-964f-5dd571077d3c} - - - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - WinGetServerManualActivation - - - WinGetServerManualActivation - - - WinGetServerManualActivation - - - - - - - Source Files - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + {6017fd94-3eb1-40bc-964f-5dd571077d3c} + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + WinGetServerManualActivation + + + WinGetServerManualActivation + + + WinGetServerManualActivation + + + + + + + Source Files + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment.OutOfProc/dllmain.cpp b/src/Microsoft.Management.Deployment.OutOfProc/dllmain.cpp index b5275c0afa..f4f280528a 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/dllmain.cpp +++ b/src/Microsoft.Management.Deployment.OutOfProc/dllmain.cpp @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Factory.h" -#include - -using namespace Microsoft::Management::Deployment::OutOfProc; - -EXTERN_C BOOL WINAPI DllMain( - HMODULE /* hModule */, - DWORD reason, - LPVOID /* lpReserved */) -{ - switch (reason) - { - case DLL_PROCESS_DETACH: - Factory::Terminate(); - break; - } - - return TRUE; -} - -_Check_return_ -STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) try -{ - RETURN_HR_IF(E_POINTER, !ppv); - *ppv = nullptr; - - winrt::Windows::Foundation::IUnknown result; - - if (Factory::IsCLSID(rclsid)) - { - result = winrt::make(rclsid).as(); - } - - if (result) - { - return result.as(riid, ppv); - } - - return REGDB_E_CLASSNOTREG; -} -CATCH_RETURN(); - -__control_entrypoint(DllExport) -STDAPI DllCanUnloadNow() -{ - return Factory::HasReferences() ? S_FALSE : S_OK; -} - -STDAPI DllGetActivationFactory(HSTRING classId, void** factory) try -{ - RETURN_HR_IF(E_POINTER, !factory); - *factory = nullptr; - - winrt::Windows::Foundation::IUnknown result; - - if (Factory::IsCLSID(classId)) - { - result = winrt::make(classId).as(); - } - - if (result) - { - return result.as(winrt::guid_of(), factory); - } - - return REGDB_E_CLASSNOTREG; -} -CATCH_RETURN(); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Factory.h" +#include + +using namespace Microsoft::Management::Deployment::OutOfProc; + +EXTERN_C BOOL WINAPI DllMain( + HMODULE /* hModule */, + DWORD reason, + LPVOID /* lpReserved */) +{ + switch (reason) + { + case DLL_PROCESS_DETACH: + Factory::Terminate(); + break; + } + + return TRUE; +} + +_Check_return_ +STDAPI DllGetClassObject(_In_ REFCLSID rclsid, _In_ REFIID riid, _Outptr_ LPVOID FAR* ppv) try +{ + RETURN_HR_IF(E_POINTER, !ppv); + *ppv = nullptr; + + winrt::Windows::Foundation::IUnknown result; + + if (Factory::IsCLSID(rclsid)) + { + result = winrt::make(rclsid).as(); + } + + if (result) + { + return result.as(riid, ppv); + } + + return REGDB_E_CLASSNOTREG; +} +CATCH_RETURN(); + +__control_entrypoint(DllExport) +STDAPI DllCanUnloadNow() +{ + return Factory::HasReferences() ? S_FALSE : S_OK; +} + +STDAPI DllGetActivationFactory(HSTRING classId, void** factory) try +{ + RETURN_HR_IF(E_POINTER, !factory); + *factory = nullptr; + + winrt::Windows::Foundation::IUnknown result; + + if (Factory::IsCLSID(classId)) + { + result = winrt::make(classId).as(); + } + + if (result) + { + return result.as(winrt::guid_of(), factory); + } + + return REGDB_E_CLASSNOTREG; +} +CATCH_RETURN(); diff --git a/src/Microsoft.Management.Deployment.OutOfProc/packages.config b/src/Microsoft.Management.Deployment.OutOfProc/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/packages.config +++ b/src/Microsoft.Management.Deployment.OutOfProc/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment.OutOfProc/pch.cpp b/src/Microsoft.Management.Deployment.OutOfProc/pch.cpp index 855ee69a1e..c084a84a60 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/pch.cpp +++ b/src/Microsoft.Management.Deployment.OutOfProc/pch.cpp @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" diff --git a/src/Microsoft.Management.Deployment.OutOfProc/pch.h b/src/Microsoft.Management.Deployment.OutOfProc/pch.h index 4d676fbb5e..7da5c77b03 100644 --- a/src/Microsoft.Management.Deployment.OutOfProc/pch.h +++ b/src/Microsoft.Management.Deployment.OutOfProc/pch.h @@ -1,13 +1,13 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -#include -#include - -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + +#include +#include + +#include +#include diff --git a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs index ecae3141cf..f44eb93e5b 100644 --- a/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs +++ b/src/Microsoft.Management.Deployment.Projection/ClassesDefinition.cs @@ -68,8 +68,8 @@ internal static class ClassesDefinition [ClsidContext.OutOfProc] = new Guid("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"), [ClsidContext.OutOfProcDev] = new Guid("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"), } - }, - + }, + [typeof(DownloadOptions)] = new() { ProjectedClassType = typeof(DownloadOptions), @@ -92,8 +92,8 @@ internal static class ClassesDefinition [ClsidContext.OutOfProc] = new Guid("D02C9DAF-99DC-429C-B503-4E504E4AB000"), [ClsidContext.OutOfProcDev] = new Guid("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"), } - }, - + }, + [typeof(AuthenticationArguments)] = new() { ProjectedClassType = typeof(AuthenticationArguments), @@ -104,8 +104,8 @@ internal static class ClassesDefinition [ClsidContext.OutOfProc] = new Guid("BA580786-BDE3-4F6C-B8F3-44698AC8711A"), [ClsidContext.OutOfProcDev] = new Guid("6484A61D-50FA-41F0-B71E-F4370C6EB37C"), } - }, - + }, + [typeof(PackageManagerSettings)] = new() { ProjectedClassType = typeof(PackageManagerSettings), @@ -114,54 +114,54 @@ internal static class ClassesDefinition { [ClsidContext.InProc] = new Guid("80CF9D63-5505-4342-B9B4-BB87895CA8BB"), } - }, - - [typeof(RepairOptions)] = new () - { - ProjectedClassType = typeof(RepairOptions), - InterfaceType = typeof(IRepairOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("30C024C4-852C-4DD4-9810-1348C51EF9BB"), - [ClsidContext.OutOfProc] = new Guid("0498F441-3097-455F-9CAF-148F28293865"), - [ClsidContext.OutOfProcDev] = new Guid("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"), - } - }, - - [typeof(AddPackageCatalogOptions)] = new() - { - ProjectedClassType = typeof(AddPackageCatalogOptions), - InterfaceType = typeof(IAddPackageCatalogOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("24E6F1FA-E4C3-4ACD-965D-DF213FD58F15"), - [ClsidContext.OutOfProc] = new Guid("DB9D012D-00D7-47EE-8FB1-606E10AC4F51"), - [ClsidContext.OutOfProcDev] = new Guid("D58C7E4C-70E6-476C-A5D4-80341ED80252"), - } - }, - - [typeof(RemovePackageCatalogOptions)] = new() - { - ProjectedClassType = typeof(RemovePackageCatalogOptions), - InterfaceType = typeof(IRemovePackageCatalogOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("1125D3A6-E2CE-479A-91D5-71A3F6F8B00B"), - [ClsidContext.OutOfProc] = new Guid("032B1C58-B975-469B-A013-E632B6ECE8D8"), - [ClsidContext.OutOfProcDev] = new Guid("87A96609-1A39-4955-BE72-7174E147B7DC"), - } - }, - - [typeof(EditPackageCatalogOptions)] = new() - { - ProjectedClassType = typeof(EditPackageCatalogOptions), - InterfaceType = typeof(IEditPackageCatalogOptions), - Clsids = new Dictionary() - { - [ClsidContext.InProc] = new Guid("E8E12FE1-AB77-40C4-A562-E91FB51B4E82"), - [ClsidContext.OutOfProc] = new Guid("A9F5E736-68CE-463C-BA6D-DE968F0CCE04"), - [ClsidContext.OutOfProcDev] = new Guid("29B19238-81AD-4A8E-A2FC-ADF17C38CAEB"), - } + }, + + [typeof(RepairOptions)] = new () + { + ProjectedClassType = typeof(RepairOptions), + InterfaceType = typeof(IRepairOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("30C024C4-852C-4DD4-9810-1348C51EF9BB"), + [ClsidContext.OutOfProc] = new Guid("0498F441-3097-455F-9CAF-148F28293865"), + [ClsidContext.OutOfProcDev] = new Guid("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"), + } + }, + + [typeof(AddPackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(AddPackageCatalogOptions), + InterfaceType = typeof(IAddPackageCatalogOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("24E6F1FA-E4C3-4ACD-965D-DF213FD58F15"), + [ClsidContext.OutOfProc] = new Guid("DB9D012D-00D7-47EE-8FB1-606E10AC4F51"), + [ClsidContext.OutOfProcDev] = new Guid("D58C7E4C-70E6-476C-A5D4-80341ED80252"), + } + }, + + [typeof(RemovePackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(RemovePackageCatalogOptions), + InterfaceType = typeof(IRemovePackageCatalogOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("1125D3A6-E2CE-479A-91D5-71A3F6F8B00B"), + [ClsidContext.OutOfProc] = new Guid("032B1C58-B975-469B-A013-E632B6ECE8D8"), + [ClsidContext.OutOfProcDev] = new Guid("87A96609-1A39-4955-BE72-7174E147B7DC"), + } + }, + + [typeof(EditPackageCatalogOptions)] = new() + { + ProjectedClassType = typeof(EditPackageCatalogOptions), + InterfaceType = typeof(IEditPackageCatalogOptions), + Clsids = new Dictionary() + { + [ClsidContext.InProc] = new Guid("E8E12FE1-AB77-40C4-A562-E91FB51B4E82"), + [ClsidContext.OutOfProc] = new Guid("A9F5E736-68CE-463C-BA6D-DE968F0CCE04"), + [ClsidContext.OutOfProcDev] = new Guid("29B19238-81AD-4A8E-A2FC-ADF17C38CAEB"), + } } }; diff --git a/src/Microsoft.Management.Deployment.Projection/Initializers/ActivationFactoryInstanceInitializer.cs b/src/Microsoft.Management.Deployment.Projection/Initializers/ActivationFactoryInstanceInitializer.cs index 8c589e43a1..d345045bf0 100644 --- a/src/Microsoft.Management.Deployment.Projection/Initializers/ActivationFactoryInstanceInitializer.cs +++ b/src/Microsoft.Management.Deployment.Projection/Initializers/ActivationFactoryInstanceInitializer.cs @@ -18,8 +18,8 @@ public class ActivationFactoryInstanceInitializer : PolicyEnforcedInstanceInitia /// /// In-process context /// - public override ClsidContext Context => ClsidContext.InProc; - + public override ClsidContext Context => ClsidContext.InProc; + /// /// Calls default projected class constructor implemented by CsWinRT. /// Default constructor uses DllGetActivationFactory to create an object @@ -27,6 +27,6 @@ public class ActivationFactoryInstanceInitializer : PolicyEnforcedInstanceInitia /// /// Projected class type /// Instance of the provided type. - protected override T CreateInstanceInternal() => new(); + protected override T CreateInstanceInternal() => new(); } } diff --git a/src/Microsoft.Management.Deployment.Projection/Initializers/LocalServerInstanceInitializer.cs b/src/Microsoft.Management.Deployment.Projection/Initializers/LocalServerInstanceInitializer.cs index 787a45b6c5..cf517683ec 100644 --- a/src/Microsoft.Management.Deployment.Projection/Initializers/LocalServerInstanceInitializer.cs +++ b/src/Microsoft.Management.Deployment.Projection/Initializers/LocalServerInstanceInitializer.cs @@ -2,8 +2,8 @@ // Licensed under the MIT License. namespace Microsoft.Management.Deployment.Projection -{ - using Microsoft.Management.Deployment.Projection.Initializers; +{ + using Microsoft.Management.Deployment.Projection.Initializers; using WinRT; // Out-of-process COM instance initializer. diff --git a/src/Microsoft.Management.Deployment.Projection/Initializers/PolicyEnforcedInstanceInitializer.cs b/src/Microsoft.Management.Deployment.Projection/Initializers/PolicyEnforcedInstanceInitializer.cs index 34c171266a..36128fa638 100644 --- a/src/Microsoft.Management.Deployment.Projection/Initializers/PolicyEnforcedInstanceInitializer.cs +++ b/src/Microsoft.Management.Deployment.Projection/Initializers/PolicyEnforcedInstanceInitializer.cs @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.Management.Deployment.Projection.Initializers -{ - using Microsoft.WinGet.SharedLib.Exceptions; - using Microsoft.WinGet.SharedLib.PolicySettings; - - /// - /// An abstract base that enforces group policy before creating derived class instance. - /// - public abstract class PolicyEnforcedInstanceInitializer : IInstanceInitializer - { - /// - /// CLSID context. - /// - public abstract ClsidContext Context { get; } - - /// - /// Create instance of the provided type. - /// - /// Projected class type. - /// Instance of the provided type. - public T CreateInstance() where T : new() - { - GroupPolicy groupPolicy = GroupPolicy.GetInstance(); - - if (!groupPolicy.IsEnabled(Policy.WinGet)) - { - throw new GroupPolicyException(Policy.WinGet, GroupPolicyFailureType.BlockedByPolicy); - } - - return this.CreateInstanceInternal(); - } - - protected abstract T CreateInstanceInternal() where T : new(); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.Management.Deployment.Projection.Initializers +{ + using Microsoft.WinGet.SharedLib.Exceptions; + using Microsoft.WinGet.SharedLib.PolicySettings; + + /// + /// An abstract base that enforces group policy before creating derived class instance. + /// + public abstract class PolicyEnforcedInstanceInitializer : IInstanceInitializer + { + /// + /// CLSID context. + /// + public abstract ClsidContext Context { get; } + + /// + /// Create instance of the provided type. + /// + /// Projected class type. + /// Instance of the provided type. + public T CreateInstance() where T : new() + { + GroupPolicy groupPolicy = GroupPolicy.GetInstance(); + + if (!groupPolicy.IsEnabled(Policy.WinGet)) + { + throw new GroupPolicyException(Policy.WinGet, GroupPolicyFailureType.BlockedByPolicy); + } + + return this.CreateInstanceInternal(); + } + + protected abstract T CreateInstanceInternal() where T : new(); + } +} diff --git a/src/Microsoft.Management.Deployment.Projection/Utils/ComUtils.cs b/src/Microsoft.Management.Deployment.Projection/Utils/ComUtils.cs index 2268606f23..209164c3e5 100644 --- a/src/Microsoft.Management.Deployment.Projection/Utils/ComUtils.cs +++ b/src/Microsoft.Management.Deployment.Projection/Utils/ComUtils.cs @@ -7,9 +7,9 @@ namespace Microsoft.Management.Deployment.Projection using System.Runtime.InteropServices; internal static class ComUtils - { + { [DllImport("api-ms-win-core-com-l1-1-0.dll")] - private static extern unsafe int CoCreateInstance(ref Guid clsid, IntPtr outer, uint clsContext, ref Guid iid, IntPtr* instance); + private static extern unsafe int CoCreateInstance(ref Guid clsid, IntPtr outer, uint clsContext, ref Guid iid, IntPtr* instance); /// /// CLSCTX enumeration diff --git a/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs b/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs index 356d1ff8b3..9d9daa7cfb 100644 --- a/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs +++ b/src/Microsoft.Management.Deployment.Projection/WinGetProjectionFactory.cs @@ -34,13 +34,13 @@ public WinGetProjectionFactory(IInstanceInitializer instanceInitializer) public AuthenticationArguments CreateAuthenticationArguments() => InstanceInitializer.CreateInstance(); public PackageManagerSettings CreatePackageManagerSettings() => InstanceInitializer.CreateInstance(); - + public RepairOptions CreateRepairOptions() => InstanceInitializer.CreateInstance(); - - public AddPackageCatalogOptions CreateAddPackageCatalogOptions() => InstanceInitializer.CreateInstance(); - - public RemovePackageCatalogOptions CreateRemovePackageCatalogOptions() => InstanceInitializer.CreateInstance(); - + + public AddPackageCatalogOptions CreateAddPackageCatalogOptions() => InstanceInitializer.CreateInstance(); + + public RemovePackageCatalogOptions CreateRemovePackageCatalogOptions() => InstanceInitializer.CreateInstance(); + public EditPackageCatalogOptions CreateEditPackageCatalogOptions() => InstanceInitializer.CreateInstance(); } } diff --git a/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.cpp b/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.cpp index d06a9c366a..c267a393f2 100644 --- a/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.cpp +++ b/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.cpp @@ -1,77 +1,77 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "AddPackageCatalogOptions.h" -#pragma warning( pop ) -#include "AddPackageCatalogOptions.g.cpp" -#include "Converters.h" -#include "Helpers.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - hstring AddPackageCatalogOptions::Name() - { - return hstring(m_name); - } - void AddPackageCatalogOptions::Name(hstring const& value) - { - m_name = value; - } - hstring AddPackageCatalogOptions::SourceUri() - { - return hstring(m_sourceUri); - } - void AddPackageCatalogOptions::SourceUri(hstring const& value) - { - m_sourceUri = value; - } - hstring AddPackageCatalogOptions::Type() - { - return hstring(m_type); - } - void AddPackageCatalogOptions::Type(hstring const& value) - { - m_type = value; - } - winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel AddPackageCatalogOptions::TrustLevel() - { - return m_trustLevel; - } - void AddPackageCatalogOptions::TrustLevel(winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel const& value) - { - m_trustLevel = value; - } - hstring AddPackageCatalogOptions::CustomHeader() - { - return hstring(m_customHeader); - } - void AddPackageCatalogOptions::CustomHeader(hstring const& value) - { - m_customHeader = value; - } - bool AddPackageCatalogOptions::Explicit() - { - return m_explicit; - } - void AddPackageCatalogOptions::Explicit(bool value) - { - m_explicit = value; - } - - int32_t AddPackageCatalogOptions::Priority() - { - return m_priority; - } - - void AddPackageCatalogOptions::Priority(int32_t value) - { - m_priority = value; - } - - CoCreatableMicrosoftManagementDeploymentClass(AddPackageCatalogOptions); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "AddPackageCatalogOptions.h" +#pragma warning( pop ) +#include "AddPackageCatalogOptions.g.cpp" +#include "Converters.h" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + hstring AddPackageCatalogOptions::Name() + { + return hstring(m_name); + } + void AddPackageCatalogOptions::Name(hstring const& value) + { + m_name = value; + } + hstring AddPackageCatalogOptions::SourceUri() + { + return hstring(m_sourceUri); + } + void AddPackageCatalogOptions::SourceUri(hstring const& value) + { + m_sourceUri = value; + } + hstring AddPackageCatalogOptions::Type() + { + return hstring(m_type); + } + void AddPackageCatalogOptions::Type(hstring const& value) + { + m_type = value; + } + winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel AddPackageCatalogOptions::TrustLevel() + { + return m_trustLevel; + } + void AddPackageCatalogOptions::TrustLevel(winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel const& value) + { + m_trustLevel = value; + } + hstring AddPackageCatalogOptions::CustomHeader() + { + return hstring(m_customHeader); + } + void AddPackageCatalogOptions::CustomHeader(hstring const& value) + { + m_customHeader = value; + } + bool AddPackageCatalogOptions::Explicit() + { + return m_explicit; + } + void AddPackageCatalogOptions::Explicit(bool value) + { + m_explicit = value; + } + + int32_t AddPackageCatalogOptions::Priority() + { + return m_priority; + } + + void AddPackageCatalogOptions::Priority(int32_t value) + { + m_priority = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(AddPackageCatalogOptions); +} diff --git a/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h b/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h index b9ac230748..170b8407e9 100644 --- a/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h +++ b/src/Microsoft.Management.Deployment/AddPackageCatalogOptions.h @@ -1,56 +1,56 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AddPackageCatalogOptions.g.h" -#include "public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions)] - struct AddPackageCatalogOptions : AddPackageCatalogOptionsT - { - AddPackageCatalogOptions() = default; - - hstring Name(); - void Name(hstring const& value); - - hstring SourceUri(); - void SourceUri(hstring const& value); - - hstring Type(); - void Type(hstring const& value); - - winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel TrustLevel(); - void TrustLevel(winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel const& value); - - hstring CustomHeader(); - void CustomHeader(hstring const& value); - - bool Explicit(); - void Explicit(bool value); - - int32_t Priority(); - void Priority(int32_t value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - hstring m_name = L""; - hstring m_sourceUri = L""; - hstring m_type = L""; - winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel m_trustLevel = winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel::None; - hstring m_customHeader = L""; - bool m_explicit = false; - int32_t m_priority = 0; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct AddPackageCatalogOptions : AddPackageCatalogOptionsT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AddPackageCatalogOptions.g.h" +#include "public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions)] + struct AddPackageCatalogOptions : AddPackageCatalogOptionsT + { + AddPackageCatalogOptions() = default; + + hstring Name(); + void Name(hstring const& value); + + hstring SourceUri(); + void SourceUri(hstring const& value); + + hstring Type(); + void Type(hstring const& value); + + winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel TrustLevel(); + void TrustLevel(winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel const& value); + + hstring CustomHeader(); + void CustomHeader(hstring const& value); + + bool Explicit(); + void Explicit(bool value); + + int32_t Priority(); + void Priority(int32_t value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + hstring m_name = L""; + hstring m_sourceUri = L""; + hstring m_type = L""; + winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel m_trustLevel = winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel::None; + hstring m_customHeader = L""; + bool m_explicit = false; + int32_t m_priority = 0; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct AddPackageCatalogOptions : AddPackageCatalogOptionsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/AddPackageCatalogResult.cpp b/src/Microsoft.Management.Deployment/AddPackageCatalogResult.cpp index f337c873e0..542875f87e 100644 --- a/src/Microsoft.Management.Deployment/AddPackageCatalogResult.cpp +++ b/src/Microsoft.Management.Deployment/AddPackageCatalogResult.cpp @@ -1,25 +1,25 @@ // Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "AddPackageCatalogResult.h" -#include "AddPackageCatalogResult.g.cpp" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void AddPackageCatalogResult::Initialize( - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status, - winrt::hresult extendedErrorCode) - { - m_status = status; - m_extendedErrorCode = extendedErrorCode; - } - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus AddPackageCatalogResult::Status() - { - return m_status; - } - winrt::hresult AddPackageCatalogResult::ExtendedErrorCode() - { - return m_extendedErrorCode; - } -} +// Licensed under the MIT License. +#include "pch.h" +#include "AddPackageCatalogResult.h" +#include "AddPackageCatalogResult.g.cpp" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void AddPackageCatalogResult::Initialize( + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status, + winrt::hresult extendedErrorCode) + { + m_status = status; + m_extendedErrorCode = extendedErrorCode; + } + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus AddPackageCatalogResult::Status() + { + return m_status; + } + winrt::hresult AddPackageCatalogResult::ExtendedErrorCode() + { + return m_extendedErrorCode; + } +} diff --git a/src/Microsoft.Management.Deployment/AddPackageCatalogResult.h b/src/Microsoft.Management.Deployment/AddPackageCatalogResult.h index a12def883b..d8316e610c 100644 --- a/src/Microsoft.Management.Deployment/AddPackageCatalogResult.h +++ b/src/Microsoft.Management.Deployment/AddPackageCatalogResult.h @@ -1,28 +1,28 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AddPackageCatalogResult.g.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - struct AddPackageCatalogResult : AddPackageCatalogResultT - { - AddPackageCatalogResult() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize( - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status, - winrt::hresult extendedErrorCode); -#endif - - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus Status(); - winrt::hresult ExtendedErrorCode(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus m_status = winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus::Ok; - winrt::hresult m_extendedErrorCode = S_OK; -#endif - }; -} -#pragma once +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AddPackageCatalogResult.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct AddPackageCatalogResult : AddPackageCatalogResultT + { + AddPackageCatalogResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize( + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status, + winrt::hresult extendedErrorCode); +#endif + + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus Status(); + winrt::hresult ExtendedErrorCode(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus m_status = winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus::Ok; + winrt::hresult m_extendedErrorCode = S_OK; +#endif + }; +} +#pragma once diff --git a/src/Microsoft.Management.Deployment/CanUnload.cpp b/src/Microsoft.Management.Deployment/CanUnload.cpp index bc03b3d027..bdb173122c 100644 --- a/src/Microsoft.Management.Deployment/CanUnload.cpp +++ b/src/Microsoft.Management.Deployment/CanUnload.cpp @@ -1,18 +1,18 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - static std::atomic_bool s_canUnload = true; - - void SetCanUnload(bool value) - { - s_canUnload = value; - } - - bool GetCanUnload() - { - return s_canUnload; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + static std::atomic_bool s_canUnload = true; + + void SetCanUnload(bool value) + { + s_canUnload = value; + } + + bool GetCanUnload() + { + return s_canUnload; + } +} diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.cpp b/src/Microsoft.Management.Deployment/CatalogPackage.cpp index f816bd67d9..b1477c5a5d 100644 --- a/src/Microsoft.Management.Deployment/CatalogPackage.cpp +++ b/src/Microsoft.Management.Deployment/CatalogPackage.cpp @@ -1,184 +1,184 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include "CatalogPackage.h" -#include "CatalogPackage.g.cpp" -#include "PackageCatalog.h" -#include "PackageVersionInfo.h" -#include "PackageVersionId.h" -#include "PackageInstallerInstalledStatus.h" -#include "CheckInstalledStatusResult.h" -#include -#include -#include -#include - - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void CatalogPackage::Initialize( - ::AppInstaller::Repository::Source source, - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> package) - { - m_source = std::move(source); - m_package = std::move(package); - } - hstring CatalogPackage::Id() - { - return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Id).get()); - } - hstring CatalogPackage::Name() - { - return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Name)); - } - Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::InstalledVersion() - { - std::call_once(m_installedVersionOnceFlag, - [&]() - { - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> installedVersion = GetInstalledVersion(m_package); - if (installedVersion) - { - auto installedVersionImpl = winrt::make_self>(); - installedVersionImpl->Initialize(std::move(installedVersion)); - m_installedVersion = *installedVersionImpl; - } - }); - return m_installedVersion; - } - Windows::Foundation::Collections::IVectorView CatalogPackage::AvailableVersions() - { - std::call_once(m_availableVersionsOnceFlag, - [&]() - { - // Vector hasn't been populated yet. - for (auto const& versionKey : ::AppInstaller::Repository::GetAllAvailableVersions(m_package)->GetVersionKeys()) - { - auto packageVersionId = winrt::make_self>(); - packageVersionId->Initialize(versionKey); - m_availableVersions.Append(*packageVersionId); - } - }); - return m_availableVersions.GetView(); - } - - void CatalogPackage::InitializeLatestApplicableVersion() - { - std::call_once(m_latestApplicableVersionOnceFlag, - [&]() - { - auto data = AppInstaller::Repository::GetLatestApplicableVersion(m_package); - - m_updateAvailable = data.UpdateAvailable; - - if (data.LatestApplicableVersion) - { - // DefaultInstallVersion hasn't been created yet, create and populate it. - // DefaultInstallVersion is the latest applicable version of the internal package object. - auto latestVersionImpl = winrt::make_self>(); - latestVersionImpl->Initialize(std::move(data.LatestApplicableVersion)); - m_latestApplicableVersion = *latestVersionImpl; - } - }); - } - - Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::DefaultInstallVersion() - { - InitializeLatestApplicableVersion(); - - if (m_latestApplicableVersion) - { - return m_latestApplicableVersion; - } - else if (AvailableVersions().Size() > 0) - { - return GetPackageVersionInfo(m_availableVersions.GetAt(0)); - } - else - { - return nullptr; - } - } - - Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::GetPackageVersionInfo(Microsoft::Management::Deployment::PackageVersionId const& versionKey) - { - winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; - - ::AppInstaller::Repository::PackageVersionKey internalVersionKey(winrt::to_string(versionKey.PackageCatalogId()), winrt::to_string(versionKey.Version()), winrt::to_string(versionKey.Channel())); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = - ::AppInstaller::Repository::GetAllAvailableVersions(m_package)->GetVersion(internalVersionKey); - if (availableVersion) - { - auto packageVersionInfoImpl = winrt::make_self>(); - packageVersionInfoImpl->Initialize(std::move(availableVersion)); - packageVersionInfo =*packageVersionInfoImpl; - } - return packageVersionInfo; - } - - bool CatalogPackage::IsUpdateAvailable() - { - InitializeLatestApplicableVersion(); - return m_updateAvailable; - } - - Windows::Foundation::IAsyncOperation CatalogPackage::CheckInstalledStatusAsync( - Microsoft::Management::Deployment::InstalledStatusType checkTypes) - { - co_return CheckInstalledStatus(checkTypes); - } - Microsoft::Management::Deployment::CheckInstalledStatusResult CatalogPackage::CheckInstalledStatus( - Microsoft::Management::Deployment::InstalledStatusType checkTypes) - { - Microsoft::Management::Deployment::CheckInstalledStatusResultStatus status = winrt::Microsoft::Management::Deployment::CheckInstalledStatusResultStatus::Ok; - Windows::Foundation::Collections::IVector installedStatus{ - winrt::single_threaded_vector() }; - - try - { - auto checkResult = ::AppInstaller::Repository::CheckPackageInstalledStatus(m_package, static_cast<::AppInstaller::Repository::InstalledStatusType>(checkTypes)); - - // Build the result object from the checkResult - for (auto const& entry : checkResult) - { - auto checkInstallerResult = winrt::make_self>(); - checkInstallerResult->Initialize(entry); - - installedStatus.Append(*checkInstallerResult); - } - } - catch (...) - { - status = winrt::Microsoft::Management::Deployment::CheckInstalledStatusResultStatus::InternalError; - } - - auto checkInstalledStatusResult = winrt::make_self>(); - checkInstalledStatusResult->Initialize(status, installedStatus); - return *checkInstalledStatusResult; - } - winrt::Windows::Foundation::IAsyncOperation CatalogPackage::CheckInstalledStatusAsync() - { - co_return CheckInstalledStatus(InstalledStatusType::AllChecks); - } - winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult CatalogPackage::CheckInstalledStatus() - { - return CheckInstalledStatus(InstalledStatusType::AllChecks); - } - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> CatalogPackage::GetRepositoryPackage() - { - return m_package; - } - - Windows::Foundation::IReference CatalogPackage::CatalogPriority() - { - return AppInstaller::Repository::GetSourcePriority(m_package); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include "CatalogPackage.h" +#include "CatalogPackage.g.cpp" +#include "PackageCatalog.h" +#include "PackageVersionInfo.h" +#include "PackageVersionId.h" +#include "PackageInstallerInstalledStatus.h" +#include "CheckInstalledStatusResult.h" +#include +#include +#include +#include + + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void CatalogPackage::Initialize( + ::AppInstaller::Repository::Source source, + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> package) + { + m_source = std::move(source); + m_package = std::move(package); + } + hstring CatalogPackage::Id() + { + return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Id).get()); + } + hstring CatalogPackage::Name() + { + return winrt::to_hstring(m_package->GetProperty(::AppInstaller::Repository::PackageProperty::Name)); + } + Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::InstalledVersion() + { + std::call_once(m_installedVersionOnceFlag, + [&]() + { + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> installedVersion = GetInstalledVersion(m_package); + if (installedVersion) + { + auto installedVersionImpl = winrt::make_self>(); + installedVersionImpl->Initialize(std::move(installedVersion)); + m_installedVersion = *installedVersionImpl; + } + }); + return m_installedVersion; + } + Windows::Foundation::Collections::IVectorView CatalogPackage::AvailableVersions() + { + std::call_once(m_availableVersionsOnceFlag, + [&]() + { + // Vector hasn't been populated yet. + for (auto const& versionKey : ::AppInstaller::Repository::GetAllAvailableVersions(m_package)->GetVersionKeys()) + { + auto packageVersionId = winrt::make_self>(); + packageVersionId->Initialize(versionKey); + m_availableVersions.Append(*packageVersionId); + } + }); + return m_availableVersions.GetView(); + } + + void CatalogPackage::InitializeLatestApplicableVersion() + { + std::call_once(m_latestApplicableVersionOnceFlag, + [&]() + { + auto data = AppInstaller::Repository::GetLatestApplicableVersion(m_package); + + m_updateAvailable = data.UpdateAvailable; + + if (data.LatestApplicableVersion) + { + // DefaultInstallVersion hasn't been created yet, create and populate it. + // DefaultInstallVersion is the latest applicable version of the internal package object. + auto latestVersionImpl = winrt::make_self>(); + latestVersionImpl->Initialize(std::move(data.LatestApplicableVersion)); + m_latestApplicableVersion = *latestVersionImpl; + } + }); + } + + Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::DefaultInstallVersion() + { + InitializeLatestApplicableVersion(); + + if (m_latestApplicableVersion) + { + return m_latestApplicableVersion; + } + else if (AvailableVersions().Size() > 0) + { + return GetPackageVersionInfo(m_availableVersions.GetAt(0)); + } + else + { + return nullptr; + } + } + + Microsoft::Management::Deployment::PackageVersionInfo CatalogPackage::GetPackageVersionInfo(Microsoft::Management::Deployment::PackageVersionId const& versionKey) + { + winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; + + ::AppInstaller::Repository::PackageVersionKey internalVersionKey(winrt::to_string(versionKey.PackageCatalogId()), winrt::to_string(versionKey.Version()), winrt::to_string(versionKey.Channel())); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> availableVersion = + ::AppInstaller::Repository::GetAllAvailableVersions(m_package)->GetVersion(internalVersionKey); + if (availableVersion) + { + auto packageVersionInfoImpl = winrt::make_self>(); + packageVersionInfoImpl->Initialize(std::move(availableVersion)); + packageVersionInfo =*packageVersionInfoImpl; + } + return packageVersionInfo; + } + + bool CatalogPackage::IsUpdateAvailable() + { + InitializeLatestApplicableVersion(); + return m_updateAvailable; + } + + Windows::Foundation::IAsyncOperation CatalogPackage::CheckInstalledStatusAsync( + Microsoft::Management::Deployment::InstalledStatusType checkTypes) + { + co_return CheckInstalledStatus(checkTypes); + } + Microsoft::Management::Deployment::CheckInstalledStatusResult CatalogPackage::CheckInstalledStatus( + Microsoft::Management::Deployment::InstalledStatusType checkTypes) + { + Microsoft::Management::Deployment::CheckInstalledStatusResultStatus status = winrt::Microsoft::Management::Deployment::CheckInstalledStatusResultStatus::Ok; + Windows::Foundation::Collections::IVector installedStatus{ + winrt::single_threaded_vector() }; + + try + { + auto checkResult = ::AppInstaller::Repository::CheckPackageInstalledStatus(m_package, static_cast<::AppInstaller::Repository::InstalledStatusType>(checkTypes)); + + // Build the result object from the checkResult + for (auto const& entry : checkResult) + { + auto checkInstallerResult = winrt::make_self>(); + checkInstallerResult->Initialize(entry); + + installedStatus.Append(*checkInstallerResult); + } + } + catch (...) + { + status = winrt::Microsoft::Management::Deployment::CheckInstalledStatusResultStatus::InternalError; + } + + auto checkInstalledStatusResult = winrt::make_self>(); + checkInstalledStatusResult->Initialize(status, installedStatus); + return *checkInstalledStatusResult; + } + winrt::Windows::Foundation::IAsyncOperation CatalogPackage::CheckInstalledStatusAsync() + { + co_return CheckInstalledStatus(InstalledStatusType::AllChecks); + } + winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult CatalogPackage::CheckInstalledStatus() + { + return CheckInstalledStatus(InstalledStatusType::AllChecks); + } + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> CatalogPackage::GetRepositoryPackage() + { + return m_package; + } + + Windows::Foundation::IReference CatalogPackage::CatalogPriority() + { + return AppInstaller::Repository::GetSourcePriority(m_package); + } +} diff --git a/src/Microsoft.Management.Deployment/CatalogPackage.h b/src/Microsoft.Management.Deployment/CatalogPackage.h index 78b65b2c45..24f1e4dccb 100644 --- a/src/Microsoft.Management.Deployment/CatalogPackage.h +++ b/src/Microsoft.Management.Deployment/CatalogPackage.h @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "CatalogPackage.g.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - struct CatalogPackage : CatalogPackageT - { - CatalogPackage() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize( - ::AppInstaller::Repository::Source source, - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> package); - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> GetRepositoryPackage(); -#endif - - hstring Id(); - hstring Name(); - winrt::Microsoft::Management::Deployment::PackageVersionInfo InstalledVersion(); - winrt::Windows::Foundation::Collections::IVectorView AvailableVersions(); - winrt::Microsoft::Management::Deployment::PackageVersionInfo DefaultInstallVersion(); - winrt::Microsoft::Management::Deployment::PackageVersionInfo GetPackageVersionInfo(winrt::Microsoft::Management::Deployment::PackageVersionId const& versionKey); - bool IsUpdateAvailable(); - // Contract 5.0 - winrt::Windows::Foundation::IAsyncOperation CheckInstalledStatusAsync( - winrt::Microsoft::Management::Deployment::InstalledStatusType checkTypes); - winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult CheckInstalledStatus( - winrt::Microsoft::Management::Deployment::InstalledStatusType checkTypes); - winrt::Windows::Foundation::IAsyncOperation CheckInstalledStatusAsync(); - winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult CheckInstalledStatus(); - - Windows::Foundation::IReference CatalogPriority(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - ::AppInstaller::Repository::Source m_source; - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> m_package; - bool m_updateAvailable = false; - Windows::Foundation::Collections::IVector m_availableVersions{ winrt::single_threaded_vector() }; - winrt::Microsoft::Management::Deployment::PackageVersionInfo m_installedVersion{ nullptr }; - winrt::Microsoft::Management::Deployment::PackageVersionInfo m_latestApplicableVersion{ nullptr }; - std::once_flag m_installedVersionOnceFlag; - std::once_flag m_availableVersionsOnceFlag; - std::once_flag m_latestApplicableVersionOnceFlag; - - void InitializeLatestApplicableVersion(); -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "CatalogPackage.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct CatalogPackage : CatalogPackageT + { + CatalogPackage() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize( + ::AppInstaller::Repository::Source source, + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> package); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> GetRepositoryPackage(); +#endif + + hstring Id(); + hstring Name(); + winrt::Microsoft::Management::Deployment::PackageVersionInfo InstalledVersion(); + winrt::Windows::Foundation::Collections::IVectorView AvailableVersions(); + winrt::Microsoft::Management::Deployment::PackageVersionInfo DefaultInstallVersion(); + winrt::Microsoft::Management::Deployment::PackageVersionInfo GetPackageVersionInfo(winrt::Microsoft::Management::Deployment::PackageVersionId const& versionKey); + bool IsUpdateAvailable(); + // Contract 5.0 + winrt::Windows::Foundation::IAsyncOperation CheckInstalledStatusAsync( + winrt::Microsoft::Management::Deployment::InstalledStatusType checkTypes); + winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult CheckInstalledStatus( + winrt::Microsoft::Management::Deployment::InstalledStatusType checkTypes); + winrt::Windows::Foundation::IAsyncOperation CheckInstalledStatusAsync(); + winrt::Microsoft::Management::Deployment::CheckInstalledStatusResult CheckInstalledStatus(); + + Windows::Foundation::IReference CatalogPriority(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + ::AppInstaller::Repository::Source m_source; + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> m_package; + bool m_updateAvailable = false; + Windows::Foundation::Collections::IVector m_availableVersions{ winrt::single_threaded_vector() }; + winrt::Microsoft::Management::Deployment::PackageVersionInfo m_installedVersion{ nullptr }; + winrt::Microsoft::Management::Deployment::PackageVersionInfo m_latestApplicableVersion{ nullptr }; + std::once_flag m_installedVersionOnceFlag; + std::once_flag m_availableVersionsOnceFlag; + std::once_flag m_latestApplicableVersionOnceFlag; + + void InitializeLatestApplicableVersion(); +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/CheckInstalledStatusResult.cpp b/src/Microsoft.Management.Deployment/CheckInstalledStatusResult.cpp index d847eacd09..e6fa864847 100644 --- a/src/Microsoft.Management.Deployment/CheckInstalledStatusResult.cpp +++ b/src/Microsoft.Management.Deployment/CheckInstalledStatusResult.cpp @@ -14,12 +14,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation m_status = status; m_installedStatus = installedStatus; } - winrt::Microsoft::Management::Deployment::CheckInstalledStatusResultStatus CheckInstalledStatusResult::Status() - { - return m_status; + winrt::Microsoft::Management::Deployment::CheckInstalledStatusResultStatus CheckInstalledStatusResult::Status() + { + return m_status; } - winrt::Windows::Foundation::Collections::IVectorView CheckInstalledStatusResult::PackageInstalledStatus() - { - return m_installedStatus.GetView(); + winrt::Windows::Foundation::Collections::IVectorView CheckInstalledStatusResult::PackageInstalledStatus() + { + return m_installedStatus.GetView(); } } diff --git a/src/Microsoft.Management.Deployment/ComClsids.cpp b/src/Microsoft.Management.Deployment/ComClsids.cpp index 24c64ec942..0a5832dc79 100644 --- a/src/Microsoft.Management.Deployment/ComClsids.cpp +++ b/src/Microsoft.Management.Deployment/ComClsids.cpp @@ -13,9 +13,9 @@ #include "PackageMatchFilter.h" #include "PackageManagerSettings.h" #include "DownloadOptions.h" -#include "AuthenticationArguments.h" -#include "RepairOptions.h" -#include "AddPackageCatalogOptions.h" +#include "AuthenticationArguments.h" +#include "RepairOptions.h" +#include "AddPackageCatalogOptions.h" #include "RemovePackageCatalogOptions.h" #include "EditPackageCatalogOptions.h" #pragma warning( pop ) @@ -59,26 +59,26 @@ namespace winrt::Microsoft::Management::Deployment else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_PackageManagerSettings)) { return __uuidof(winrt::Microsoft::Management::Deployment::implementation::PackageManagerSettings); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RepairOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RepairOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::AddPackageCatalogOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RemovePackageCatalogOptions); - } - else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions)) - { - return __uuidof(winrt::Microsoft::Management::Deployment::implementation::EditPackageCatalogOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RepairOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RepairOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::AddPackageCatalogOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::RemovePackageCatalogOptions); + } + else if (IsEqualCLSID(clsid, WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions)) + { + return __uuidof(winrt::Microsoft::Management::Deployment::implementation::EditPackageCatalogOptions); } else { return CLSID_NULL; } } -} +} diff --git a/src/Microsoft.Management.Deployment/ConnectResult.cpp b/src/Microsoft.Management.Deployment/ConnectResult.cpp index 620bed709d..d5e821d0a4 100644 --- a/src/Microsoft.Management.Deployment/ConnectResult.cpp +++ b/src/Microsoft.Management.Deployment/ConnectResult.cpp @@ -9,7 +9,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation void ConnectResult::Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus status, winrt::Microsoft::Management::Deployment::PackageCatalog packageCatalog, winrt::hresult extendedErrorCode) { m_status = status; - m_packageCatalog = packageCatalog; + m_packageCatalog = packageCatalog; m_extendedErrorCode = extendedErrorCode; } winrt::Microsoft::Management::Deployment::ConnectResultStatus ConnectResult::Status() @@ -19,9 +19,9 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageCatalog ConnectResult::PackageCatalog() { return m_packageCatalog; - } - - winrt::hresult ConnectResult::ExtendedErrorCode() + } + + winrt::hresult ConnectResult::ExtendedErrorCode() { return m_extendedErrorCode; } diff --git a/src/Microsoft.Management.Deployment/Converters.cpp b/src/Microsoft.Management.Deployment/Converters.cpp index 3c3b4f238b..6594a6c054 100644 --- a/src/Microsoft.Management.Deployment/Converters.cpp +++ b/src/Microsoft.Management.Deployment/Converters.cpp @@ -1,559 +1,559 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include "Workflows/WorkflowBase.h" -#include "Converters.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field) - { - Microsoft::Management::Deployment::PackageMatchField matchField = Microsoft::Management::Deployment::PackageMatchField::Id; - switch (field) - { - case ::AppInstaller::Repository::PackageMatchField::Command: - matchField = Microsoft::Management::Deployment::PackageMatchField::Command; - break; - case ::AppInstaller::Repository::PackageMatchField::Id: - matchField = Microsoft::Management::Deployment::PackageMatchField::Id; - break; - case ::AppInstaller::Repository::PackageMatchField::Moniker: - matchField = Microsoft::Management::Deployment::PackageMatchField::Moniker; - break; - case ::AppInstaller::Repository::PackageMatchField::Name: - matchField = Microsoft::Management::Deployment::PackageMatchField::Name; - break; - case ::AppInstaller::Repository::PackageMatchField::Tag: - matchField = Microsoft::Management::Deployment::PackageMatchField::Tag; - break; - case ::AppInstaller::Repository::PackageMatchField::ProductCode: - matchField = Microsoft::Management::Deployment::PackageMatchField::ProductCode; - break; - case ::AppInstaller::Repository::PackageMatchField::PackageFamilyName: - matchField = Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName; - break; - default: - matchField = Microsoft::Management::Deployment::PackageMatchField::Id; - break; - } - return matchField; - } - - ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(Microsoft::Management::Deployment::PackageMatchField field) - { - ::AppInstaller::Repository::PackageMatchField matchField = ::AppInstaller::Repository::PackageMatchField::Id; - switch (field) - { - case Microsoft::Management::Deployment::PackageMatchField::Command: - matchField = ::AppInstaller::Repository::PackageMatchField::Command; - break; - case Microsoft::Management::Deployment::PackageMatchField::Id: - matchField = ::AppInstaller::Repository::PackageMatchField::Id; - break; - case Microsoft::Management::Deployment::PackageMatchField::Moniker: - matchField = ::AppInstaller::Repository::PackageMatchField::Moniker; - break; - case Microsoft::Management::Deployment::PackageMatchField::Name: - matchField = ::AppInstaller::Repository::PackageMatchField::Name; - break; - case Microsoft::Management::Deployment::PackageMatchField::Tag: - matchField = ::AppInstaller::Repository::PackageMatchField::Tag; - break; - case Microsoft::Management::Deployment::PackageMatchField::ProductCode: - matchField = ::AppInstaller::Repository::PackageMatchField::ProductCode; - break; - case Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName: - matchField = ::AppInstaller::Repository::PackageMatchField::PackageFamilyName; - break; - default: - matchField = ::AppInstaller::Repository::PackageMatchField::Id; - break; - } - return matchField; - } - - Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type) - { - Microsoft::Management::Deployment::PackageFieldMatchOption matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; - switch (type) - { - case ::AppInstaller::Repository::MatchType::CaseInsensitive: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive; - break; - case ::AppInstaller::Repository::MatchType::Exact: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; - break; - case ::AppInstaller::Repository::MatchType::StartsWith: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive; - break; - case ::AppInstaller::Repository::MatchType::Substring: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive; - break; - default: - matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; - break; - } - return matchOption; - } - - ::AppInstaller::Repository::MatchType GetRepositoryMatchType(Microsoft::Management::Deployment::PackageFieldMatchOption option) - { - ::AppInstaller::Repository::MatchType packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; - switch (option) - { - case Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::CaseInsensitive; - break; - case Microsoft::Management::Deployment::PackageFieldMatchOption::Equals: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; - break; - case Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::StartsWith; - break; - case Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Substring; - break; - default: - packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; - break; - } - return packageFieldMatchOption; - } - - ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior) - { - ::AppInstaller::Repository::CompositeSearchBehavior repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; - switch (searchBehavior) - { - case Microsoft::Management::Deployment::CompositeSearchBehavior::LocalCatalogs: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::Installed; - break; - case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; - break; - case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromAllCatalogs: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; - break; - case Microsoft::Management::Deployment::CompositeSearchBehavior::AllCatalogs: - default: - repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; - break; - } - return repositorySearchBehavior; - } - - ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField) - { - ::AppInstaller::Repository::PackageVersionMetadata metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; - switch (packageVersionMetadataField) - { - case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledLocation: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledScope: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledScope; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::InstallerType: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledType; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::PublisherDisplayName: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::Publisher; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::SilentUninstallCommand: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::SilentUninstallCommand; - break; - case Microsoft::Management::Deployment::PackageVersionMetadataField::StandardUninstallCommand: - metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::StandardUninstallCommand; - break; - } - return metadataKey; - } - - winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult) - { - winrt::Microsoft::Management::Deployment::FindPackagesResultStatus resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; - switch (hresult) - { - case(S_OK): - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; - break; - case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::BlockedByPolicy; - break; - case APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE: - case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA: - case APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND: - case APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR: - case APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE: - case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION: - case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::CatalogError; - break; - case E_INVALIDARG: - case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InvalidOptions; - break; - case APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER: - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AuthenticationError; - break; - case HTTP_E_STATUS_DENIED: - case HTTP_E_STATUS_FORBIDDEN: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AccessDenied; - break; - case APPINSTALLER_CLI_ERROR_COMMAND_FAILED: - case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: - case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: - default: - resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InternalError; - break; - } - return resultStatus; - } - - std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture) - { - return ::AppInstaller::Utility::ConvertToArchitectureEnum(architecture); - } - - std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture) - { - switch (architecture) - { - case ::AppInstaller::Utility::Architecture::X86: - return winrt::Windows::System::ProcessorArchitecture::X86; - case ::AppInstaller::Utility::Architecture::Arm: - return winrt::Windows::System::ProcessorArchitecture::Arm; - case ::AppInstaller::Utility::Architecture::X64: - return winrt::Windows::System::ProcessorArchitecture::X64; - case ::AppInstaller::Utility::Architecture::Neutral: - return winrt::Windows::System::ProcessorArchitecture::Neutral; - case ::AppInstaller::Utility::Architecture::Arm64: - return winrt::Windows::System::ProcessorArchitecture::Arm64; - } - - return {}; - } - - std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope) - { - switch (scope) - { - case winrt::Microsoft::Management::Deployment::PackageInstallScope::Any: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::User: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, false); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::System: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, false); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::UserOrUnknown: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, true); - case winrt::Microsoft::Management::Deployment::PackageInstallScope::SystemOrUnknown: - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, true); - } - - return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); - } - - winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType) - { - switch (installerType) - { - case ::AppInstaller::Manifest::InstallerTypeEnum::Burn: - return Microsoft::Management::Deployment::PackageInstallerType::Burn; - case ::AppInstaller::Manifest::InstallerTypeEnum::Exe: - return Microsoft::Management::Deployment::PackageInstallerType::Exe; - case ::AppInstaller::Manifest::InstallerTypeEnum::Inno: - return Microsoft::Management::Deployment::PackageInstallerType::Inno; - case ::AppInstaller::Manifest::InstallerTypeEnum::Msi: - return Microsoft::Management::Deployment::PackageInstallerType::Msi; - case ::AppInstaller::Manifest::InstallerTypeEnum::Msix: - return Microsoft::Management::Deployment::PackageInstallerType::Msix; - case ::AppInstaller::Manifest::InstallerTypeEnum::MSStore: - return Microsoft::Management::Deployment::PackageInstallerType::MSStore; - case ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft: - return Microsoft::Management::Deployment::PackageInstallerType::Nullsoft; - case ::AppInstaller::Manifest::InstallerTypeEnum::Portable: - return Microsoft::Management::Deployment::PackageInstallerType::Portable; - case ::AppInstaller::Manifest::InstallerTypeEnum::Wix: - return Microsoft::Management::Deployment::PackageInstallerType::Wix; - case ::AppInstaller::Manifest::InstallerTypeEnum::Zip: - return Microsoft::Management::Deployment::PackageInstallerType::Zip; - case ::AppInstaller::Manifest::InstallerTypeEnum::Unknown: - return Microsoft::Management::Deployment::PackageInstallerType::Unknown; - } - - return Microsoft::Management::Deployment::PackageInstallerType::Unknown; - } - - ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType) - { - switch (installerType) - { - case Microsoft::Management::Deployment::PackageInstallerType::Burn: - return ::AppInstaller::Manifest::InstallerTypeEnum::Burn; - case Microsoft::Management::Deployment::PackageInstallerType::Exe: - return ::AppInstaller::Manifest::InstallerTypeEnum::Exe; - case Microsoft::Management::Deployment::PackageInstallerType::Inno: - return ::AppInstaller::Manifest::InstallerTypeEnum::Inno; - case Microsoft::Management::Deployment::PackageInstallerType::Msi: - return ::AppInstaller::Manifest::InstallerTypeEnum::Msi; - case Microsoft::Management::Deployment::PackageInstallerType::Msix: - return ::AppInstaller::Manifest::InstallerTypeEnum::Msix; - case Microsoft::Management::Deployment::PackageInstallerType::MSStore: - return ::AppInstaller::Manifest::InstallerTypeEnum::MSStore; - case Microsoft::Management::Deployment::PackageInstallerType::Nullsoft: - return ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft; - case Microsoft::Management::Deployment::PackageInstallerType::Portable: - return ::AppInstaller::Manifest::InstallerTypeEnum::Portable; - case Microsoft::Management::Deployment::PackageInstallerType::Wix: - return ::AppInstaller::Manifest::InstallerTypeEnum::Wix; - case Microsoft::Management::Deployment::PackageInstallerType::Zip: - return ::AppInstaller::Manifest::InstallerTypeEnum::Zip; - case Microsoft::Management::Deployment::PackageInstallerType::Unknown: - return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; - } - - return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; - } - - winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope) - { - switch (installerScope) - { - case ::AppInstaller::Manifest::ScopeEnum::User: - return Microsoft::Management::Deployment::PackageInstallerScope::User; - case ::AppInstaller::Manifest::ScopeEnum::Machine: - return Microsoft::Management::Deployment::PackageInstallerScope::System; - case ::AppInstaller::Manifest::ScopeEnum::Unknown: - return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; - } - - return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; - } - - ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope) - { - switch (scope) - { - case winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any: - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - case winrt::Microsoft::Management::Deployment::PackageUninstallScope::User: - return ::AppInstaller::Manifest::ScopeEnum::User; - case winrt::Microsoft::Management::Deployment::PackageUninstallScope::System: - return ::AppInstaller::Manifest::ScopeEnum::Machine; - } - - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - } - - ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope) - { - switch (scope) - { - case winrt::Microsoft::Management::Deployment::PackageRepairScope::Any: - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - case winrt::Microsoft::Management::Deployment::PackageRepairScope::User: - return ::AppInstaller::Manifest::ScopeEnum::User; - case winrt::Microsoft::Management::Deployment::PackageRepairScope::System: - return ::AppInstaller::Manifest::ScopeEnum::Machine; - } - - return ::AppInstaller::Manifest::ScopeEnum::Unknown; - } - - winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement) - { - switch (elevationRequirement) - { - case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationRequired: - return Microsoft::Management::Deployment::ElevationRequirement::ElevationRequired; - case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationProhibited: - return Microsoft::Management::Deployment::ElevationRequirement::ElevationProhibited; - case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevatesSelf: - return Microsoft::Management::Deployment::ElevationRequirement::ElevatesSelf; - case ::AppInstaller::Manifest::ElevationRequirementEnum::Unknown: - return Microsoft::Management::Deployment::ElevationRequirement::Unknown; - } - - return Microsoft::Management::Deployment::ElevationRequirement::Unknown; - } - - winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType) - { - switch (iconFileType) - { - case ::AppInstaller::Manifest::IconFileTypeEnum::Ico: - return Microsoft::Management::Deployment::IconFileType::Ico; - case ::AppInstaller::Manifest::IconFileTypeEnum::Jpeg: - return Microsoft::Management::Deployment::IconFileType::Jpeg; - case ::AppInstaller::Manifest::IconFileTypeEnum::Png: - return Microsoft::Management::Deployment::IconFileType::Png; - } - - return Microsoft::Management::Deployment::IconFileType::Unknown; - } - - winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution) - { - switch (iconResolution) - { - case ::AppInstaller::Manifest::IconResolutionEnum::Custom: - return Microsoft::Management::Deployment::IconResolution::Custom; - case ::AppInstaller::Manifest::IconResolutionEnum::Square16: - return Microsoft::Management::Deployment::IconResolution::Square16; - case ::AppInstaller::Manifest::IconResolutionEnum::Square20: - return Microsoft::Management::Deployment::IconResolution::Square20; - case ::AppInstaller::Manifest::IconResolutionEnum::Square24: - return Microsoft::Management::Deployment::IconResolution::Square24; - case ::AppInstaller::Manifest::IconResolutionEnum::Square30: - return Microsoft::Management::Deployment::IconResolution::Square30; - case ::AppInstaller::Manifest::IconResolutionEnum::Square32: - return Microsoft::Management::Deployment::IconResolution::Square32; - case ::AppInstaller::Manifest::IconResolutionEnum::Square36: - return Microsoft::Management::Deployment::IconResolution::Square36; - case ::AppInstaller::Manifest::IconResolutionEnum::Square40: - return Microsoft::Management::Deployment::IconResolution::Square40; - case ::AppInstaller::Manifest::IconResolutionEnum::Square48: - return Microsoft::Management::Deployment::IconResolution::Square48; - case ::AppInstaller::Manifest::IconResolutionEnum::Square60: - return Microsoft::Management::Deployment::IconResolution::Square60; - case ::AppInstaller::Manifest::IconResolutionEnum::Square64: - return Microsoft::Management::Deployment::IconResolution::Square64; - case ::AppInstaller::Manifest::IconResolutionEnum::Square72: - return Microsoft::Management::Deployment::IconResolution::Square72; - case ::AppInstaller::Manifest::IconResolutionEnum::Square80: - return Microsoft::Management::Deployment::IconResolution::Square80; - case ::AppInstaller::Manifest::IconResolutionEnum::Square96: - return Microsoft::Management::Deployment::IconResolution::Square96; - case ::AppInstaller::Manifest::IconResolutionEnum::Square256: - return Microsoft::Management::Deployment::IconResolution::Square256; - } - - return Microsoft::Management::Deployment::IconResolution::Custom; - } - - winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme) - { - switch (iconTheme) - { - case ::AppInstaller::Manifest::IconThemeEnum::Default: - return Microsoft::Management::Deployment::IconTheme::Default; - case ::AppInstaller::Manifest::IconThemeEnum::Light: - return Microsoft::Management::Deployment::IconTheme::Light; - case ::AppInstaller::Manifest::IconThemeEnum::Dark: - return Microsoft::Management::Deployment::IconTheme::Dark; - case ::AppInstaller::Manifest::IconThemeEnum::HighContrast: - return Microsoft::Management::Deployment::IconTheme::HighContrast; - } - - return Microsoft::Management::Deployment::IconTheme::Unknown; - } - - winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType) - { - switch (authType) - { - case ::AppInstaller::Authentication::AuthenticationType::None: - 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; - } - - ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode) - { - switch (authMode) - { - case winrt::Microsoft::Management::Deployment::AuthenticationMode::Interactive: - return ::AppInstaller::Authentication::AuthenticationMode::Interactive; - case winrt::Microsoft::Management::Deployment::AuthenticationMode::SilentPreferred: - return ::AppInstaller::Authentication::AuthenticationMode::SilentPreferred; - case winrt::Microsoft::Management::Deployment::AuthenticationMode::Silent: - return ::AppInstaller::Authentication::AuthenticationMode::Silent; - } - - return ::AppInstaller::Authentication::AuthenticationMode::Unknown; - } - - ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs) - { - ::AppInstaller::Authentication::AuthenticationArguments result; - result.Mode = ::AppInstaller::Authentication::AuthenticationMode::Silent; // Default to silent for com invocations. - - if (authArgs) - { - result.Mode = GetAuthenticationMode(authArgs.AuthenticationMode()); - result.AuthenticationAccount = ::AppInstaller::Utility::ConvertToUTF8(authArgs.AuthenticationAccount()); - } - - return result; - } - - AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: - return AddPackageCatalogStatus::AuthenticationError; - case APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE: - case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: - case APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE: - case APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS: - case APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS: - return AddPackageCatalogStatus::InvalidOptions; - default: - return HandleCommonCatalogOperationStatus(hresult); - } - } - - RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: - return RemovePackageCatalogStatus::InvalidOptions; - case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: - return RemovePackageCatalogStatus::CatalogError; - default: - return HandleCommonCatalogOperationStatus(hresult); - } - } - - EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: - return EditPackageCatalogStatus::InvalidOptions; - case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: - return EditPackageCatalogStatus::CatalogError; - default: - return HandleCommonCatalogOperationStatus(hresult); - } - } - - ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(WindowsPlatform value) - { - switch (value) - { - case WindowsPlatform::Unknown: return AppInstaller::Manifest::PlatformEnum::Unknown; - case WindowsPlatform::Universal: return AppInstaller::Manifest::PlatformEnum::Universal; - case WindowsPlatform::Desktop: return AppInstaller::Manifest::PlatformEnum::Desktop; - case WindowsPlatform::IoT: return AppInstaller::Manifest::PlatformEnum::IoT; - case WindowsPlatform::Team: return AppInstaller::Manifest::PlatformEnum::Team; - case WindowsPlatform::Holographic: return AppInstaller::Manifest::PlatformEnum::Holographic; - default: return AppInstaller::Manifest::PlatformEnum::Unknown; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include "Workflows/WorkflowBase.h" +#include "Converters.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field) + { + Microsoft::Management::Deployment::PackageMatchField matchField = Microsoft::Management::Deployment::PackageMatchField::Id; + switch (field) + { + case ::AppInstaller::Repository::PackageMatchField::Command: + matchField = Microsoft::Management::Deployment::PackageMatchField::Command; + break; + case ::AppInstaller::Repository::PackageMatchField::Id: + matchField = Microsoft::Management::Deployment::PackageMatchField::Id; + break; + case ::AppInstaller::Repository::PackageMatchField::Moniker: + matchField = Microsoft::Management::Deployment::PackageMatchField::Moniker; + break; + case ::AppInstaller::Repository::PackageMatchField::Name: + matchField = Microsoft::Management::Deployment::PackageMatchField::Name; + break; + case ::AppInstaller::Repository::PackageMatchField::Tag: + matchField = Microsoft::Management::Deployment::PackageMatchField::Tag; + break; + case ::AppInstaller::Repository::PackageMatchField::ProductCode: + matchField = Microsoft::Management::Deployment::PackageMatchField::ProductCode; + break; + case ::AppInstaller::Repository::PackageMatchField::PackageFamilyName: + matchField = Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName; + break; + default: + matchField = Microsoft::Management::Deployment::PackageMatchField::Id; + break; + } + return matchField; + } + + ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(Microsoft::Management::Deployment::PackageMatchField field) + { + ::AppInstaller::Repository::PackageMatchField matchField = ::AppInstaller::Repository::PackageMatchField::Id; + switch (field) + { + case Microsoft::Management::Deployment::PackageMatchField::Command: + matchField = ::AppInstaller::Repository::PackageMatchField::Command; + break; + case Microsoft::Management::Deployment::PackageMatchField::Id: + matchField = ::AppInstaller::Repository::PackageMatchField::Id; + break; + case Microsoft::Management::Deployment::PackageMatchField::Moniker: + matchField = ::AppInstaller::Repository::PackageMatchField::Moniker; + break; + case Microsoft::Management::Deployment::PackageMatchField::Name: + matchField = ::AppInstaller::Repository::PackageMatchField::Name; + break; + case Microsoft::Management::Deployment::PackageMatchField::Tag: + matchField = ::AppInstaller::Repository::PackageMatchField::Tag; + break; + case Microsoft::Management::Deployment::PackageMatchField::ProductCode: + matchField = ::AppInstaller::Repository::PackageMatchField::ProductCode; + break; + case Microsoft::Management::Deployment::PackageMatchField::PackageFamilyName: + matchField = ::AppInstaller::Repository::PackageMatchField::PackageFamilyName; + break; + default: + matchField = ::AppInstaller::Repository::PackageMatchField::Id; + break; + } + return matchField; + } + + Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type) + { + Microsoft::Management::Deployment::PackageFieldMatchOption matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; + switch (type) + { + case ::AppInstaller::Repository::MatchType::CaseInsensitive: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive; + break; + case ::AppInstaller::Repository::MatchType::Exact: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; + break; + case ::AppInstaller::Repository::MatchType::StartsWith: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive; + break; + case ::AppInstaller::Repository::MatchType::Substring: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive; + break; + default: + matchOption = Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; + break; + } + return matchOption; + } + + ::AppInstaller::Repository::MatchType GetRepositoryMatchType(Microsoft::Management::Deployment::PackageFieldMatchOption option) + { + ::AppInstaller::Repository::MatchType packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; + switch (option) + { + case Microsoft::Management::Deployment::PackageFieldMatchOption::EqualsCaseInsensitive: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::CaseInsensitive; + break; + case Microsoft::Management::Deployment::PackageFieldMatchOption::Equals: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; + break; + case Microsoft::Management::Deployment::PackageFieldMatchOption::StartsWithCaseInsensitive: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::StartsWith; + break; + case Microsoft::Management::Deployment::PackageFieldMatchOption::ContainsCaseInsensitive: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Substring; + break; + default: + packageFieldMatchOption = ::AppInstaller::Repository::MatchType::Exact; + break; + } + return packageFieldMatchOption; + } + + ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior) + { + ::AppInstaller::Repository::CompositeSearchBehavior repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; + switch (searchBehavior) + { + case Microsoft::Management::Deployment::CompositeSearchBehavior::LocalCatalogs: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::Installed; + break; + case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; + break; + case Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromAllCatalogs: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AvailablePackages; + break; + case Microsoft::Management::Deployment::CompositeSearchBehavior::AllCatalogs: + default: + repositorySearchBehavior = ::AppInstaller::Repository::CompositeSearchBehavior::AllPackages; + break; + } + return repositorySearchBehavior; + } + + ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField) + { + ::AppInstaller::Repository::PackageVersionMetadata metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; + switch (packageVersionMetadataField) + { + case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledLocation: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledLocation; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::InstalledScope: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledScope; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::InstallerType: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::InstalledType; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::PublisherDisplayName: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::Publisher; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::SilentUninstallCommand: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::SilentUninstallCommand; + break; + case Microsoft::Management::Deployment::PackageVersionMetadataField::StandardUninstallCommand: + metadataKey = ::AppInstaller::Repository::PackageVersionMetadata::StandardUninstallCommand; + break; + } + return metadataKey; + } + + winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult) + { + winrt::Microsoft::Management::Deployment::FindPackagesResultStatus resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; + switch (hresult) + { + case(S_OK): + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::Ok; + break; + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::BlockedByPolicy; + break; + case APPINSTALLER_CLI_ERROR_UNSUPPORTED_RESTSOURCE: + case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_DATA: + case APPINSTALLER_CLI_ERROR_RESTAPI_ENDPOINT_NOT_FOUND: + case APPINSTALLER_CLI_ERROR_RESTAPI_INTERNAL_ERROR: + case APPINSTALLER_CLI_ERROR_RESTAPI_UNSUPPORTED_MIME_TYPE: + case APPINSTALLER_CLI_ERROR_RESTSOURCE_INVALID_VERSION: + case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::CatalogError; + break; + case E_INVALIDARG: + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InvalidOptions; + break; + case APPINSTALLER_CLI_ERROR_INVALID_AUTHENTICATION_INFO: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_FAILED: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INTERACTIVE_REQUIRED: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_CANCELLED_BY_USER: + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_INCORRECT_ACCOUNT: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AuthenticationError; + break; + case HTTP_E_STATUS_DENIED: + case HTTP_E_STATUS_FORBIDDEN: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::AccessDenied; + break; + case APPINSTALLER_CLI_ERROR_COMMAND_FAILED: + case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: + case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: + default: + resultStatus = winrt::Microsoft::Management::Deployment::FindPackagesResultStatus::InternalError; + break; + } + return resultStatus; + } + + std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture) + { + return ::AppInstaller::Utility::ConvertToArchitectureEnum(architecture); + } + + std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture) + { + switch (architecture) + { + case ::AppInstaller::Utility::Architecture::X86: + return winrt::Windows::System::ProcessorArchitecture::X86; + case ::AppInstaller::Utility::Architecture::Arm: + return winrt::Windows::System::ProcessorArchitecture::Arm; + case ::AppInstaller::Utility::Architecture::X64: + return winrt::Windows::System::ProcessorArchitecture::X64; + case ::AppInstaller::Utility::Architecture::Neutral: + return winrt::Windows::System::ProcessorArchitecture::Neutral; + case ::AppInstaller::Utility::Architecture::Arm64: + return winrt::Windows::System::ProcessorArchitecture::Arm64; + } + + return {}; + } + + std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageInstallScope::Any: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::User: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, false); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::System: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, false); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::UserOrUnknown: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::User, true); + case winrt::Microsoft::Management::Deployment::PackageInstallScope::SystemOrUnknown: + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Machine, true); + } + + return std::make_pair(::AppInstaller::Manifest::ScopeEnum::Unknown, false); + } + + winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType) + { + switch (installerType) + { + case ::AppInstaller::Manifest::InstallerTypeEnum::Burn: + return Microsoft::Management::Deployment::PackageInstallerType::Burn; + case ::AppInstaller::Manifest::InstallerTypeEnum::Exe: + return Microsoft::Management::Deployment::PackageInstallerType::Exe; + case ::AppInstaller::Manifest::InstallerTypeEnum::Inno: + return Microsoft::Management::Deployment::PackageInstallerType::Inno; + case ::AppInstaller::Manifest::InstallerTypeEnum::Msi: + return Microsoft::Management::Deployment::PackageInstallerType::Msi; + case ::AppInstaller::Manifest::InstallerTypeEnum::Msix: + return Microsoft::Management::Deployment::PackageInstallerType::Msix; + case ::AppInstaller::Manifest::InstallerTypeEnum::MSStore: + return Microsoft::Management::Deployment::PackageInstallerType::MSStore; + case ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft: + return Microsoft::Management::Deployment::PackageInstallerType::Nullsoft; + case ::AppInstaller::Manifest::InstallerTypeEnum::Portable: + return Microsoft::Management::Deployment::PackageInstallerType::Portable; + case ::AppInstaller::Manifest::InstallerTypeEnum::Wix: + return Microsoft::Management::Deployment::PackageInstallerType::Wix; + case ::AppInstaller::Manifest::InstallerTypeEnum::Zip: + return Microsoft::Management::Deployment::PackageInstallerType::Zip; + case ::AppInstaller::Manifest::InstallerTypeEnum::Unknown: + return Microsoft::Management::Deployment::PackageInstallerType::Unknown; + } + + return Microsoft::Management::Deployment::PackageInstallerType::Unknown; + } + + ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType) + { + switch (installerType) + { + case Microsoft::Management::Deployment::PackageInstallerType::Burn: + return ::AppInstaller::Manifest::InstallerTypeEnum::Burn; + case Microsoft::Management::Deployment::PackageInstallerType::Exe: + return ::AppInstaller::Manifest::InstallerTypeEnum::Exe; + case Microsoft::Management::Deployment::PackageInstallerType::Inno: + return ::AppInstaller::Manifest::InstallerTypeEnum::Inno; + case Microsoft::Management::Deployment::PackageInstallerType::Msi: + return ::AppInstaller::Manifest::InstallerTypeEnum::Msi; + case Microsoft::Management::Deployment::PackageInstallerType::Msix: + return ::AppInstaller::Manifest::InstallerTypeEnum::Msix; + case Microsoft::Management::Deployment::PackageInstallerType::MSStore: + return ::AppInstaller::Manifest::InstallerTypeEnum::MSStore; + case Microsoft::Management::Deployment::PackageInstallerType::Nullsoft: + return ::AppInstaller::Manifest::InstallerTypeEnum::Nullsoft; + case Microsoft::Management::Deployment::PackageInstallerType::Portable: + return ::AppInstaller::Manifest::InstallerTypeEnum::Portable; + case Microsoft::Management::Deployment::PackageInstallerType::Wix: + return ::AppInstaller::Manifest::InstallerTypeEnum::Wix; + case Microsoft::Management::Deployment::PackageInstallerType::Zip: + return ::AppInstaller::Manifest::InstallerTypeEnum::Zip; + case Microsoft::Management::Deployment::PackageInstallerType::Unknown: + return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; + } + + return ::AppInstaller::Manifest::InstallerTypeEnum::Unknown; + } + + winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope) + { + switch (installerScope) + { + case ::AppInstaller::Manifest::ScopeEnum::User: + return Microsoft::Management::Deployment::PackageInstallerScope::User; + case ::AppInstaller::Manifest::ScopeEnum::Machine: + return Microsoft::Management::Deployment::PackageInstallerScope::System; + case ::AppInstaller::Manifest::ScopeEnum::Unknown: + return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; + } + + return Microsoft::Management::Deployment::PackageInstallerScope::Unknown; + } + + ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any: + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::User: + return ::AppInstaller::Manifest::ScopeEnum::User; + case winrt::Microsoft::Management::Deployment::PackageUninstallScope::System: + return ::AppInstaller::Manifest::ScopeEnum::Machine; + } + + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + } + + ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope) + { + switch (scope) + { + case winrt::Microsoft::Management::Deployment::PackageRepairScope::Any: + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + case winrt::Microsoft::Management::Deployment::PackageRepairScope::User: + return ::AppInstaller::Manifest::ScopeEnum::User; + case winrt::Microsoft::Management::Deployment::PackageRepairScope::System: + return ::AppInstaller::Manifest::ScopeEnum::Machine; + } + + return ::AppInstaller::Manifest::ScopeEnum::Unknown; + } + + winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement) + { + switch (elevationRequirement) + { + case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationRequired: + return Microsoft::Management::Deployment::ElevationRequirement::ElevationRequired; + case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevationProhibited: + return Microsoft::Management::Deployment::ElevationRequirement::ElevationProhibited; + case ::AppInstaller::Manifest::ElevationRequirementEnum::ElevatesSelf: + return Microsoft::Management::Deployment::ElevationRequirement::ElevatesSelf; + case ::AppInstaller::Manifest::ElevationRequirementEnum::Unknown: + return Microsoft::Management::Deployment::ElevationRequirement::Unknown; + } + + return Microsoft::Management::Deployment::ElevationRequirement::Unknown; + } + + winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType) + { + switch (iconFileType) + { + case ::AppInstaller::Manifest::IconFileTypeEnum::Ico: + return Microsoft::Management::Deployment::IconFileType::Ico; + case ::AppInstaller::Manifest::IconFileTypeEnum::Jpeg: + return Microsoft::Management::Deployment::IconFileType::Jpeg; + case ::AppInstaller::Manifest::IconFileTypeEnum::Png: + return Microsoft::Management::Deployment::IconFileType::Png; + } + + return Microsoft::Management::Deployment::IconFileType::Unknown; + } + + winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution) + { + switch (iconResolution) + { + case ::AppInstaller::Manifest::IconResolutionEnum::Custom: + return Microsoft::Management::Deployment::IconResolution::Custom; + case ::AppInstaller::Manifest::IconResolutionEnum::Square16: + return Microsoft::Management::Deployment::IconResolution::Square16; + case ::AppInstaller::Manifest::IconResolutionEnum::Square20: + return Microsoft::Management::Deployment::IconResolution::Square20; + case ::AppInstaller::Manifest::IconResolutionEnum::Square24: + return Microsoft::Management::Deployment::IconResolution::Square24; + case ::AppInstaller::Manifest::IconResolutionEnum::Square30: + return Microsoft::Management::Deployment::IconResolution::Square30; + case ::AppInstaller::Manifest::IconResolutionEnum::Square32: + return Microsoft::Management::Deployment::IconResolution::Square32; + case ::AppInstaller::Manifest::IconResolutionEnum::Square36: + return Microsoft::Management::Deployment::IconResolution::Square36; + case ::AppInstaller::Manifest::IconResolutionEnum::Square40: + return Microsoft::Management::Deployment::IconResolution::Square40; + case ::AppInstaller::Manifest::IconResolutionEnum::Square48: + return Microsoft::Management::Deployment::IconResolution::Square48; + case ::AppInstaller::Manifest::IconResolutionEnum::Square60: + return Microsoft::Management::Deployment::IconResolution::Square60; + case ::AppInstaller::Manifest::IconResolutionEnum::Square64: + return Microsoft::Management::Deployment::IconResolution::Square64; + case ::AppInstaller::Manifest::IconResolutionEnum::Square72: + return Microsoft::Management::Deployment::IconResolution::Square72; + case ::AppInstaller::Manifest::IconResolutionEnum::Square80: + return Microsoft::Management::Deployment::IconResolution::Square80; + case ::AppInstaller::Manifest::IconResolutionEnum::Square96: + return Microsoft::Management::Deployment::IconResolution::Square96; + case ::AppInstaller::Manifest::IconResolutionEnum::Square256: + return Microsoft::Management::Deployment::IconResolution::Square256; + } + + return Microsoft::Management::Deployment::IconResolution::Custom; + } + + winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme) + { + switch (iconTheme) + { + case ::AppInstaller::Manifest::IconThemeEnum::Default: + return Microsoft::Management::Deployment::IconTheme::Default; + case ::AppInstaller::Manifest::IconThemeEnum::Light: + return Microsoft::Management::Deployment::IconTheme::Light; + case ::AppInstaller::Manifest::IconThemeEnum::Dark: + return Microsoft::Management::Deployment::IconTheme::Dark; + case ::AppInstaller::Manifest::IconThemeEnum::HighContrast: + return Microsoft::Management::Deployment::IconTheme::HighContrast; + } + + return Microsoft::Management::Deployment::IconTheme::Unknown; + } + + winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType) + { + switch (authType) + { + case ::AppInstaller::Authentication::AuthenticationType::None: + 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; + } + + ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode) + { + switch (authMode) + { + case winrt::Microsoft::Management::Deployment::AuthenticationMode::Interactive: + return ::AppInstaller::Authentication::AuthenticationMode::Interactive; + case winrt::Microsoft::Management::Deployment::AuthenticationMode::SilentPreferred: + return ::AppInstaller::Authentication::AuthenticationMode::SilentPreferred; + case winrt::Microsoft::Management::Deployment::AuthenticationMode::Silent: + return ::AppInstaller::Authentication::AuthenticationMode::Silent; + } + + return ::AppInstaller::Authentication::AuthenticationMode::Unknown; + } + + ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs) + { + ::AppInstaller::Authentication::AuthenticationArguments result; + result.Mode = ::AppInstaller::Authentication::AuthenticationMode::Silent; // Default to silent for com invocations. + + if (authArgs) + { + result.Mode = GetAuthenticationMode(authArgs.AuthenticationMode()); + result.AuthenticationAccount = ::AppInstaller::Utility::ConvertToUTF8(authArgs.AuthenticationAccount()); + } + + return result; + } + + AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED: + return AddPackageCatalogStatus::AuthenticationError; + case APPINSTALLER_CLI_ERROR_SOURCE_NOT_SECURE: + case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: + case APPINSTALLER_CLI_ERROR_SOURCE_NOT_REMOTE: + case APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS: + case APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS: + return AddPackageCatalogStatus::InvalidOptions; + default: + return HandleCommonCatalogOperationStatus(hresult); + } + } + + RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: + return RemovePackageCatalogStatus::InvalidOptions; + case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: + return RemovePackageCatalogStatus::CatalogError; + default: + return HandleCommonCatalogOperationStatus(hresult); + } + } + + EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST: + return EditPackageCatalogStatus::InvalidOptions; + case APPINSTALLER_CLI_ERROR_INVALID_SOURCE_TYPE: + return EditPackageCatalogStatus::CatalogError; + default: + return HandleCommonCatalogOperationStatus(hresult); + } + } + + ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(WindowsPlatform value) + { + switch (value) + { + case WindowsPlatform::Unknown: return AppInstaller::Manifest::PlatformEnum::Unknown; + case WindowsPlatform::Universal: return AppInstaller::Manifest::PlatformEnum::Universal; + case WindowsPlatform::Desktop: return AppInstaller::Manifest::PlatformEnum::Desktop; + case WindowsPlatform::IoT: return AppInstaller::Manifest::PlatformEnum::IoT; + case WindowsPlatform::Team: return AppInstaller::Manifest::PlatformEnum::Team; + case WindowsPlatform::Holographic: return AppInstaller::Manifest::PlatformEnum::Holographic; + default: return AppInstaller::Manifest::PlatformEnum::Unknown; + } + } +} diff --git a/src/Microsoft.Management.Deployment/Converters.h b/src/Microsoft.Management.Deployment/Converters.h index 643658705c..d6344cbd99 100644 --- a/src/Microsoft.Management.Deployment/Converters.h +++ b/src/Microsoft.Management.Deployment/Converters.h @@ -1,208 +1,208 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "PackageMatchFilter.g.h" -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - winrt::Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field); - ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(winrt::Microsoft::Management::Deployment::PackageMatchField field); - winrt::Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type); - ::AppInstaller::Repository::MatchType GetRepositoryMatchType(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption option); - ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior); - ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField); - winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult); - std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture); - std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture); - std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope); - ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType); - winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType); - winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope); - ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope); - winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement); - winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType); - winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution); - winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme); - winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType); - ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode); - ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs); - ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope); - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult); - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult); - winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult); - ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(winrt::Microsoft::Management::Deployment::WindowsPlatform value); - -#define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_, _repairResultStatus_) \ - if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_installResultStatus_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_uninstallResultStatus_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_downloadResultStatus_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - resultStatus = TStatus::_repairResultStatus_; \ - } \ - - template - TStatus GetOperationResultStatus(::AppInstaller::CLI::Workflow::ExecutionStage executionStage, winrt::hresult hresult) - { - TStatus resultStatus = TStatus::Ok; - - // Map some known hresults to specific statuses, otherwise use the execution stage to determine the status. - switch (hresult) - { - case S_OK: - resultStatus = TStatus::Ok; - break; - case APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY: - case APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY: - case APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED: - case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: - resultStatus = TStatus::BlockedByPolicy; - break; - case APPINSTALLER_CLI_ERROR_INVALID_MANIFEST: - resultStatus = TStatus::ManifestError; - break; - case E_INVALIDARG: - case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: - resultStatus = TStatus::InvalidOptions; - break; - case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER: - WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableRepairer); - break; - case APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE: - case APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH: - case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN: - case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER: - WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableUpgrade, InternalError, InternalError, InternalError); - break; - case APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND: - case APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED: - WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, InternalError); - break; - case APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND: - case APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE: - case APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED: - case APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED: - case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: - WINGET_GET_OPERATION_RESULT_STATUS(InternalError, InternalError, InternalError, RepairError); - break; - case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: - WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); - break; - case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: - case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: - case APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY: - case APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION: - case APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE: - case APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA: - case APPINSTALLER_CLI_ERROR_LIBYAML_ERROR: - case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: - resultStatus = TStatus::InternalError; - break; - default: - switch (executionStage) - { - case ::AppInstaller::CLI::Workflow::ExecutionStage::Initial: - resultStatus = TStatus::InternalError; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::ParseArgs: - resultStatus = TStatus::InvalidOptions; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Discovery: - resultStatus = TStatus::CatalogError; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Download: - WINGET_GET_OPERATION_RESULT_STATUS(DownloadError, InternalError, DownloadError, DownloadError); - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::PreExecution: - resultStatus = TStatus::InternalError; - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::Execution: - WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, RepairError); - break; - case ::AppInstaller::CLI::Workflow::ExecutionStage::PostExecution: - resultStatus = TStatus::InternalError; - break; - default: - resultStatus = TStatus::InternalError; - break; - } - } - - return resultStatus; - } - - template - TStatus HandleCommonCatalogOperationStatus(winrt::hresult hresult) - { - // Common status handling for AddPackageCatalogStatus and RemovePackageCatalogStatus. - if constexpr (std::is_same_v || std::is_same_v) - { - switch (hresult) - { - case APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN: - case E_ACCESSDENIED: - return TStatus::AccessDenied; - case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: - case E_INVALIDARG: - return TStatus::InvalidOptions; - default: - break; - } - } - - // Common status handling for AddPackageCatalogStatus, RemovePackageCatalogStatus, and RefreshPackageCatalogStatus. - switch (hresult) - { - case S_OK: - return TStatus::Ok; - case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: - return TStatus::GroupPolicyError; - case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: - return TStatus::CatalogError; - case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: - default: - return TStatus::InternalError; - } - } - - template - TStatus GetPackageCatalogOperationStatus(winrt::hresult hresult) - { - if constexpr (std::is_same_v) - { - return GetAddPackageCatalogOperationStatus(hresult); - } - else if constexpr (std::is_same_v) - { - return GetRemovePackageCatalogOperationStatus(hresult); - } - else if constexpr (std::is_same_v) - { - return HandleCommonCatalogOperationStatus(hresult); - } - else if constexpr (std::is_same_v) - { - return GetEditPackageCatalogOperationStatus(hresult); - } - else - { - throw winrt::hresult_error(E_UNEXPECTED); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageMatchFilter.g.h" +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + winrt::Microsoft::Management::Deployment::PackageMatchField GetDeploymentMatchField(::AppInstaller::Repository::PackageMatchField field); + ::AppInstaller::Repository::PackageMatchField GetRepositoryMatchField(winrt::Microsoft::Management::Deployment::PackageMatchField field); + winrt::Microsoft::Management::Deployment::PackageFieldMatchOption GetDeploymentMatchOption(::AppInstaller::Repository::MatchType type); + ::AppInstaller::Repository::MatchType GetRepositoryMatchType(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption option); + ::AppInstaller::Repository::CompositeSearchBehavior GetRepositoryCompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior searchBehavior); + ::AppInstaller::Repository::PackageVersionMetadata GetRepositoryPackageVersionMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField packageVersionMetadataField); + winrt::Microsoft::Management::Deployment::FindPackagesResultStatus FindPackagesResultStatus(winrt::hresult hresult); + std::optional<::AppInstaller::Utility::Architecture> GetUtilityArchitecture(winrt::Windows::System::ProcessorArchitecture architecture); + std::optional GetWindowsSystemProcessorArchitecture(::AppInstaller::Utility::Architecture architecture); + std::pair<::AppInstaller::Manifest::ScopeEnum, bool> GetManifestScope(winrt::Microsoft::Management::Deployment::PackageInstallScope scope); + ::AppInstaller::Manifest::InstallerTypeEnum GetManifestInstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType installerType); + winrt::Microsoft::Management::Deployment::PackageInstallerType GetDeploymentInstallerType(::AppInstaller::Manifest::InstallerTypeEnum installerType); + winrt::Microsoft::Management::Deployment::PackageInstallerScope GetDeploymentInstallerScope(::AppInstaller::Manifest::ScopeEnum installerScope); + ::AppInstaller::Manifest::ScopeEnum GetManifestUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope scope); + winrt::Microsoft::Management::Deployment::ElevationRequirement GetDeploymentElevationRequirement(::AppInstaller::Manifest::ElevationRequirementEnum elevationRequirement); + winrt::Microsoft::Management::Deployment::IconFileType GetDeploymentIconFileType(::AppInstaller::Manifest::IconFileTypeEnum iconFileType); + winrt::Microsoft::Management::Deployment::IconResolution GetDeploymentIconResolution(::AppInstaller::Manifest::IconResolutionEnum iconResolution); + winrt::Microsoft::Management::Deployment::IconTheme GetDeploymentIconTheme(::AppInstaller::Manifest::IconThemeEnum iconTheme); + winrt::Microsoft::Management::Deployment::AuthenticationType GetDeploymentAuthenticationType(::AppInstaller::Authentication::AuthenticationType authType); + ::AppInstaller::Authentication::AuthenticationMode GetAuthenticationMode(winrt::Microsoft::Management::Deployment::AuthenticationMode authMode); + ::AppInstaller::Authentication::AuthenticationArguments GetAuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments authArgs); + ::AppInstaller::Manifest::ScopeEnum GetManifestRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope scope); + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus GetAddPackageCatalogOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus GetRemovePackageCatalogOperationStatus(winrt::hresult hresult); + winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus GetEditPackageCatalogOperationStatus(winrt::hresult hresult); + ::AppInstaller::Manifest::PlatformEnum GetPlatformEnum(winrt::Microsoft::Management::Deployment::WindowsPlatform value); + +#define WINGET_GET_OPERATION_RESULT_STATUS(_installResultStatus_, _uninstallResultStatus_, _downloadResultStatus_, _repairResultStatus_) \ + if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_installResultStatus_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_uninstallResultStatus_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_downloadResultStatus_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + resultStatus = TStatus::_repairResultStatus_; \ + } \ + + template + TStatus GetOperationResultStatus(::AppInstaller::CLI::Workflow::ExecutionStage executionStage, winrt::hresult hresult) + { + TStatus resultStatus = TStatus::Ok; + + // Map some known hresults to specific statuses, otherwise use the execution stage to determine the status. + switch (hresult) + { + case S_OK: + resultStatus = TStatus::Ok; + break; + case APPINSTALLER_CLI_ERROR_MSSTORE_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_MSSTORE_APP_BLOCKED_BY_POLICY: + case APPINSTALLER_CLI_ERROR_EXPERIMENTAL_FEATURE_DISABLED: + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + resultStatus = TStatus::BlockedByPolicy; + break; + case APPINSTALLER_CLI_ERROR_INVALID_MANIFEST: + resultStatus = TStatus::ManifestError; + break; + case E_INVALIDARG: + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + resultStatus = TStatus::InvalidOptions; + break; + case APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER: + WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableInstallers, InternalError, NoApplicableInstallers, NoApplicableRepairer); + break; + case APPINSTALLER_CLI_ERROR_UPDATE_NOT_APPLICABLE: + case APPINSTALLER_CLI_ERROR_UPDATE_INSTALL_TECHNOLOGY_MISMATCH: + case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN: + case APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER: + WINGET_GET_OPERATION_RESULT_STATUS(NoApplicableUpgrade, InternalError, InternalError, InternalError); + break; + case APPINSTALLER_CLI_ERROR_NO_UNINSTALL_INFO_FOUND: + case APPINSTALLER_CLI_ERROR_EXEC_UNINSTALL_COMMAND_FAILED: + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, InternalError); + break; + case APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND: + case APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE: + case APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED: + case APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED: + case APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED: + WINGET_GET_OPERATION_RESULT_STATUS(InternalError, InternalError, InternalError, RepairError); + break; + case APPINSTALLER_CLI_ERROR_PACKAGE_AGREEMENTS_NOT_ACCEPTED: + WINGET_GET_OPERATION_RESULT_STATUS(PackageAgreementsNotAccepted, InternalError, PackageAgreementsNotAccepted, PackageAgreementsNotAccepted); + break; + case APPINSTALLER_CLI_ERROR_CANNOT_WRITE_TO_UPLEVEL_INDEX: + case APPINSTALLER_CLI_ERROR_INDEX_INTEGRITY_COMPROMISED: + case APPINSTALLER_CLI_ERROR_YAML_INIT_FAILED: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_MAPPING_KEY: + case APPINSTALLER_CLI_ERROR_YAML_DUPLICATE_MAPPING_KEY: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_OPERATION: + case APPINSTALLER_CLI_ERROR_YAML_DOC_BUILD_FAILED: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_EMITTER_STATE: + case APPINSTALLER_CLI_ERROR_YAML_INVALID_DATA: + case APPINSTALLER_CLI_ERROR_LIBYAML_ERROR: + case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: + resultStatus = TStatus::InternalError; + break; + default: + switch (executionStage) + { + case ::AppInstaller::CLI::Workflow::ExecutionStage::Initial: + resultStatus = TStatus::InternalError; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::ParseArgs: + resultStatus = TStatus::InvalidOptions; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::Discovery: + resultStatus = TStatus::CatalogError; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::Download: + WINGET_GET_OPERATION_RESULT_STATUS(DownloadError, InternalError, DownloadError, DownloadError); + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::PreExecution: + resultStatus = TStatus::InternalError; + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::Execution: + WINGET_GET_OPERATION_RESULT_STATUS(InstallError, UninstallError, InternalError, RepairError); + break; + case ::AppInstaller::CLI::Workflow::ExecutionStage::PostExecution: + resultStatus = TStatus::InternalError; + break; + default: + resultStatus = TStatus::InternalError; + break; + } + } + + return resultStatus; + } + + template + TStatus HandleCommonCatalogOperationStatus(winrt::hresult hresult) + { + // Common status handling for AddPackageCatalogStatus and RemovePackageCatalogStatus. + if constexpr (std::is_same_v || std::is_same_v) + { + switch (hresult) + { + case APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN: + case E_ACCESSDENIED: + return TStatus::AccessDenied; + case APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS: + case E_INVALIDARG: + return TStatus::InvalidOptions; + default: + break; + } + } + + // Common status handling for AddPackageCatalogStatus, RemovePackageCatalogStatus, and RefreshPackageCatalogStatus. + switch (hresult) + { + case S_OK: + return TStatus::Ok; + case APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY: + return TStatus::GroupPolicyError; + case APPINSTALLER_CLI_ERROR_SOURCE_DATA_INTEGRITY_FAILURE: + return TStatus::CatalogError; + case APPINSTALLER_CLI_ERROR_INTERNAL_ERROR: + default: + return TStatus::InternalError; + } + } + + template + TStatus GetPackageCatalogOperationStatus(winrt::hresult hresult) + { + if constexpr (std::is_same_v) + { + return GetAddPackageCatalogOperationStatus(hresult); + } + else if constexpr (std::is_same_v) + { + return GetRemovePackageCatalogOperationStatus(hresult); + } + else if constexpr (std::is_same_v) + { + return HandleCommonCatalogOperationStatus(hresult); + } + else if constexpr (std::is_same_v) + { + return GetEditPackageCatalogOperationStatus(hresult); + } + else + { + throw winrt::hresult_error(E_UNEXPECTED); + } + } +} diff --git a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp index 84fe9dc562..ba3aa144ea 100644 --- a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp +++ b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.cpp @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "CreateCompositePackageCatalogOptions.h" -#pragma warning( pop ) -#include "CreateCompositePackageCatalogOptions.g.cpp" -#include "Helpers.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - Windows::Foundation::Collections::IVector CreateCompositePackageCatalogOptions::Catalogs() - { - return m_catalogs; - } - winrt::Microsoft::Management::Deployment::CompositeSearchBehavior CreateCompositePackageCatalogOptions::CompositeSearchBehavior() - { - return m_compositeSearchBehavior; - } - void CreateCompositePackageCatalogOptions::CompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior const& value) - { - m_compositeSearchBehavior = value; - } - winrt::Microsoft::Management::Deployment::PackageInstallScope CreateCompositePackageCatalogOptions::InstalledScope() - { - return m_installedScope; - } - void CreateCompositePackageCatalogOptions::InstalledScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value) - { - m_installedScope = value; - } - - CoCreatableMicrosoftManagementDeploymentClass(CreateCompositePackageCatalogOptions); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "CreateCompositePackageCatalogOptions.h" +#pragma warning( pop ) +#include "CreateCompositePackageCatalogOptions.g.cpp" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + Windows::Foundation::Collections::IVector CreateCompositePackageCatalogOptions::Catalogs() + { + return m_catalogs; + } + winrt::Microsoft::Management::Deployment::CompositeSearchBehavior CreateCompositePackageCatalogOptions::CompositeSearchBehavior() + { + return m_compositeSearchBehavior; + } + void CreateCompositePackageCatalogOptions::CompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior const& value) + { + m_compositeSearchBehavior = value; + } + winrt::Microsoft::Management::Deployment::PackageInstallScope CreateCompositePackageCatalogOptions::InstalledScope() + { + return m_installedScope; + } + void CreateCompositePackageCatalogOptions::InstalledScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value) + { + m_installedScope = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(CreateCompositePackageCatalogOptions); +} diff --git a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h index c6ab08a243..ac89090029 100644 --- a/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h +++ b/src/Microsoft.Management.Deployment/CreateCompositePackageCatalogOptions.h @@ -1,39 +1,39 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "CreateCompositePackageCatalogOptions.g.h" -#include "Public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions)] - struct CreateCompositePackageCatalogOptions : CreateCompositePackageCatalogOptionsT - { - CreateCompositePackageCatalogOptions() = default; - - winrt::Windows::Foundation::Collections::IVector Catalogs(); - winrt::Microsoft::Management::Deployment::CompositeSearchBehavior CompositeSearchBehavior(); - void CompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior const& value); - winrt::Microsoft::Management::Deployment::PackageInstallScope InstalledScope(); - void InstalledScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Windows::Foundation::Collections::IVector m_catalogs{ winrt::single_threaded_vector() }; - winrt::Microsoft::Management::Deployment::CompositeSearchBehavior m_compositeSearchBehavior = winrt::Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromAllCatalogs; - winrt::Microsoft::Management::Deployment::PackageInstallScope m_installedScope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct CreateCompositePackageCatalogOptions : - CreateCompositePackageCatalogOptionsT, - AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "CreateCompositePackageCatalogOptions.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions)] + struct CreateCompositePackageCatalogOptions : CreateCompositePackageCatalogOptionsT + { + CreateCompositePackageCatalogOptions() = default; + + winrt::Windows::Foundation::Collections::IVector Catalogs(); + winrt::Microsoft::Management::Deployment::CompositeSearchBehavior CompositeSearchBehavior(); + void CompositeSearchBehavior(winrt::Microsoft::Management::Deployment::CompositeSearchBehavior const& value); + winrt::Microsoft::Management::Deployment::PackageInstallScope InstalledScope(); + void InstalledScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Windows::Foundation::Collections::IVector m_catalogs{ winrt::single_threaded_vector() }; + winrt::Microsoft::Management::Deployment::CompositeSearchBehavior m_compositeSearchBehavior = winrt::Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromAllCatalogs; + winrt::Microsoft::Management::Deployment::PackageInstallScope m_installedScope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct CreateCompositePackageCatalogOptions : + CreateCompositePackageCatalogOptionsT, + AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/DownloadOptions.cpp b/src/Microsoft.Management.Deployment/DownloadOptions.cpp index 912f151c86..093e90c72f 100644 --- a/src/Microsoft.Management.Deployment/DownloadOptions.cpp +++ b/src/Microsoft.Management.Deployment/DownloadOptions.cpp @@ -97,45 +97,45 @@ 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; - } + } + winrt::Microsoft::Management::Deployment::AuthenticationArguments DownloadOptions::AuthenticationArguments() + { + return m_authenticationArguments; + } + void DownloadOptions::AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value) + { + m_authenticationArguments = value; + } - bool DownloadOptions::SkipMicrosoftStoreLicense() + bool DownloadOptions::SkipMicrosoftStoreLicense() { return m_skipMicrosoftStoreLicense; - } + } - void DownloadOptions::SkipMicrosoftStoreLicense(bool value) - { - m_skipMicrosoftStoreLicense = value; - } + void DownloadOptions::SkipMicrosoftStoreLicense(bool value) + { + m_skipMicrosoftStoreLicense = value; + } - winrt::Microsoft::Management::Deployment::WindowsPlatform DownloadOptions::Platform() + winrt::Microsoft::Management::Deployment::WindowsPlatform DownloadOptions::Platform() { return m_platform; - } + } - void DownloadOptions::Platform(winrt::Microsoft::Management::Deployment::WindowsPlatform value) - { - m_platform = value; - } + void DownloadOptions::Platform(winrt::Microsoft::Management::Deployment::WindowsPlatform value) + { + m_platform = value; + } - hstring DownloadOptions::TargetOSVersion() + hstring DownloadOptions::TargetOSVersion() { return hstring(m_targetOSVersion); - } + } - void DownloadOptions::TargetOSVersion(hstring const& value) - { - m_targetOSVersion = value; - } + void DownloadOptions::TargetOSVersion(hstring const& value) + { + m_targetOSVersion = value; + } CoCreatableMicrosoftManagementDeploymentClass(DownloadOptions); } diff --git a/src/Microsoft.Management.Deployment/DownloadOptions.h b/src/Microsoft.Management.Deployment/DownloadOptions.h index 1243c3e70b..8c6794ffd4 100644 --- a/src/Microsoft.Management.Deployment/DownloadOptions.h +++ b/src/Microsoft.Management.Deployment/DownloadOptions.h @@ -1,71 +1,71 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "DownloadOptions.g.h" -#include "Public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_DownloadOptions)] - struct DownloadOptions : DownloadOptionsT - { - DownloadOptions(); - - winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); - void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); - winrt::Microsoft::Management::Deployment::PackageInstallScope Scope(); - void Scope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); - winrt::Microsoft::Management::Deployment::PackageInstallerType InstallerType(); - void InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value); - winrt::Windows::System::ProcessorArchitecture Architecture(); - void Architecture(winrt::Windows::System::ProcessorArchitecture const& value); - hstring Locale(); - void Locale(hstring const& value); - hstring DownloadDirectory(); - void DownloadDirectory(hstring const& value); - bool AllowHashMismatch(); - void AllowHashMismatch(bool value); - bool SkipDependencies(); - void SkipDependencies(bool value); - bool AcceptPackageAgreements(); - void AcceptPackageAgreements(bool value); - hstring CorrelationData(); - void CorrelationData(hstring const& value); - winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); - void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); - bool SkipMicrosoftStoreLicense(); - void SkipMicrosoftStoreLicense(bool value); - winrt::Microsoft::Management::Deployment::WindowsPlatform Platform(); - void Platform(winrt::Microsoft::Management::Deployment::WindowsPlatform value); - hstring TargetOSVersion(); - void TargetOSVersion(hstring const& value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; - winrt::Microsoft::Management::Deployment::PackageInstallScope m_scope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; - winrt::Microsoft::Management::Deployment::PackageInstallerType m_installerType = winrt::Microsoft::Management::Deployment::PackageInstallerType::Unknown; - winrt::Windows::System::ProcessorArchitecture m_architecture = winrt::Windows::System::ProcessorArchitecture::Unknown; - std::wstring m_locale = L""; - std::wstring m_downloadDirectory = L""; - bool m_allowHashMismatch = false; - bool m_skipDependencies = false; - bool m_acceptPackageAgreements = true; - std::wstring m_correlationData = L""; - winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; - bool m_skipMicrosoftStoreLicense = false; - winrt::Microsoft::Management::Deployment::WindowsPlatform m_platform = winrt::Microsoft::Management::Deployment::WindowsPlatform::Unknown; - std::wstring m_targetOSVersion; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct DownloadOptions : DownloadOptionsT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "DownloadOptions.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_DownloadOptions)] + struct DownloadOptions : DownloadOptionsT + { + DownloadOptions(); + + winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); + void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); + winrt::Microsoft::Management::Deployment::PackageInstallScope Scope(); + void Scope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); + winrt::Microsoft::Management::Deployment::PackageInstallerType InstallerType(); + void InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value); + winrt::Windows::System::ProcessorArchitecture Architecture(); + void Architecture(winrt::Windows::System::ProcessorArchitecture const& value); + hstring Locale(); + void Locale(hstring const& value); + hstring DownloadDirectory(); + void DownloadDirectory(hstring const& value); + bool AllowHashMismatch(); + void AllowHashMismatch(bool value); + bool SkipDependencies(); + void SkipDependencies(bool value); + bool AcceptPackageAgreements(); + void AcceptPackageAgreements(bool value); + hstring CorrelationData(); + void CorrelationData(hstring const& value); + winrt::Microsoft::Management::Deployment::AuthenticationArguments AuthenticationArguments(); + void AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value); + bool SkipMicrosoftStoreLicense(); + void SkipMicrosoftStoreLicense(bool value); + winrt::Microsoft::Management::Deployment::WindowsPlatform Platform(); + void Platform(winrt::Microsoft::Management::Deployment::WindowsPlatform value); + hstring TargetOSVersion(); + void TargetOSVersion(hstring const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; + winrt::Microsoft::Management::Deployment::PackageInstallScope m_scope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; + winrt::Microsoft::Management::Deployment::PackageInstallerType m_installerType = winrt::Microsoft::Management::Deployment::PackageInstallerType::Unknown; + winrt::Windows::System::ProcessorArchitecture m_architecture = winrt::Windows::System::ProcessorArchitecture::Unknown; + std::wstring m_locale = L""; + std::wstring m_downloadDirectory = L""; + bool m_allowHashMismatch = false; + bool m_skipDependencies = false; + bool m_acceptPackageAgreements = true; + std::wstring m_correlationData = L""; + winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; + bool m_skipMicrosoftStoreLicense = false; + winrt::Microsoft::Management::Deployment::WindowsPlatform m_platform = winrt::Microsoft::Management::Deployment::WindowsPlatform::Unknown; + std::wstring m_targetOSVersion; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct DownloadOptions : DownloadOptionsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/FindPackagesOptions.cpp b/src/Microsoft.Management.Deployment/FindPackagesOptions.cpp index 9c17269e02..685cae0bfe 100644 --- a/src/Microsoft.Management.Deployment/FindPackagesOptions.cpp +++ b/src/Microsoft.Management.Deployment/FindPackagesOptions.cpp @@ -1,34 +1,34 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "FindPackagesOptions.h" -#pragma warning( pop ) -#include "FindPackagesOptions.g.cpp" -#include "Helpers.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - winrt::Windows::Foundation::Collections::IVector FindPackagesOptions::Selectors() - { - return m_selectors; - } - winrt::Windows::Foundation::Collections::IVector FindPackagesOptions::Filters() - { - return m_filters; - } - uint32_t FindPackagesOptions::ResultLimit() - { - return m_resultLimit; - } - void FindPackagesOptions::ResultLimit(uint32_t value) - { - m_resultLimit = value; - } - - CoCreatableMicrosoftManagementDeploymentClass(FindPackagesOptions); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "FindPackagesOptions.h" +#pragma warning( pop ) +#include "FindPackagesOptions.g.cpp" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + winrt::Windows::Foundation::Collections::IVector FindPackagesOptions::Selectors() + { + return m_selectors; + } + winrt::Windows::Foundation::Collections::IVector FindPackagesOptions::Filters() + { + return m_filters; + } + uint32_t FindPackagesOptions::ResultLimit() + { + return m_resultLimit; + } + void FindPackagesOptions::ResultLimit(uint32_t value) + { + m_resultLimit = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(FindPackagesOptions); +} diff --git a/src/Microsoft.Management.Deployment/FindPackagesOptions.h b/src/Microsoft.Management.Deployment/FindPackagesOptions.h index 7be5e5ff0a..37141f5325 100644 --- a/src/Microsoft.Management.Deployment/FindPackagesOptions.h +++ b/src/Microsoft.Management.Deployment/FindPackagesOptions.h @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "FindPackagesOptions.g.h" -#include "Public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions)] - struct FindPackagesOptions : FindPackagesOptionsT - { - FindPackagesOptions() = default; - - winrt::Windows::Foundation::Collections::IVector Selectors(); - winrt::Windows::Foundation::Collections::IVector Filters(); - uint32_t ResultLimit(); - void ResultLimit(uint32_t value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - uint32_t m_resultLimit = 0; - Windows::Foundation::Collections::IVector m_selectors{ - winrt::single_threaded_vector() }; - Windows::Foundation::Collections::IVector m_filters{ - winrt::single_threaded_vector() }; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct FindPackagesOptions : FindPackagesOptionsT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "FindPackagesOptions.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions)] + struct FindPackagesOptions : FindPackagesOptionsT + { + FindPackagesOptions() = default; + + winrt::Windows::Foundation::Collections::IVector Selectors(); + winrt::Windows::Foundation::Collections::IVector Filters(); + uint32_t ResultLimit(); + void ResultLimit(uint32_t value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + uint32_t m_resultLimit = 0; + Windows::Foundation::Collections::IVector m_selectors{ + winrt::single_threaded_vector() }; + Windows::Foundation::Collections::IVector m_filters{ + winrt::single_threaded_vector() }; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct FindPackagesOptions : FindPackagesOptionsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/FindPackagesResult.cpp b/src/Microsoft.Management.Deployment/FindPackagesResult.cpp index 1c9c93dcef..734f2e6866 100644 --- a/src/Microsoft.Management.Deployment/FindPackagesResult.cpp +++ b/src/Microsoft.Management.Deployment/FindPackagesResult.cpp @@ -10,7 +10,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation void FindPackagesResult::Initialize( winrt::Microsoft::Management::Deployment::FindPackagesResultStatus status, bool wasLimitExceeded, - Windows::Foundation::Collections::IVector matches, + Windows::Foundation::Collections::IVector matches, winrt::hresult extendedErrorCode) { m_status = status; diff --git a/src/Microsoft.Management.Deployment/FindPackagesResult.h b/src/Microsoft.Management.Deployment/FindPackagesResult.h index aff8a1ece3..af565aa9cf 100644 --- a/src/Microsoft.Management.Deployment/FindPackagesResult.h +++ b/src/Microsoft.Management.Deployment/FindPackagesResult.h @@ -14,7 +14,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation void Initialize( winrt::Microsoft::Management::Deployment::FindPackagesResultStatus status, bool wasLimitExceeded, - Windows::Foundation::Collections::IVector matches, + Windows::Foundation::Collections::IVector matches, winrt::hresult extendedErrorCode); #endif diff --git a/src/Microsoft.Management.Deployment/Helpers.cpp b/src/Microsoft.Management.Deployment/Helpers.cpp index 8fa1462b60..0a48bfa2ac 100644 --- a/src/Microsoft.Management.Deployment/Helpers.cpp +++ b/src/Microsoft.Management.Deployment/Helpers.cpp @@ -1,218 +1,218 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace std::string_literals; -using namespace std::string_view_literals; - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - namespace - { - static std::optional s_callerName; - static wil::srwlock s_callerNameLock; - } - - void SetComCallerName(std::string name) - { - auto lock = s_callerNameLock.lock_exclusive(); - s_callerName.emplace(std::move(name)); - } - - std::string GetComCallerName(std::string defaultNameIfNotSet) - { - auto lock = s_callerNameLock.lock_shared(); - return s_callerName.has_value() ? s_callerName.value() : defaultNameIfNotSet; - } - - std::pair GetCallerProcessId() - { - RPC_STATUS rpcStatus = RPC_S_OK; - RPC_CALL_ATTRIBUTES callAttributes = {}; - callAttributes.Version = RPC_CALL_ATTRIBUTES_VERSION; - callAttributes.Flags = RPC_QUERY_CLIENT_PID; - rpcStatus = RpcServerInqCallAttributes(nullptr, &callAttributes); - - if (rpcStatus == RPC_S_NO_CALL_ACTIVE || - (rpcStatus == RPC_S_OK && HandleToULong(callAttributes.ClientPID) == GetCurrentProcessId())) - { - // in-proc is supported now. - return { S_OK, GetCurrentProcessId() }; - } - else if (rpcStatus == RPC_S_OK) - { - // out-of-proc case. - return { S_OK, HandleToULong(callAttributes.ClientPID) }; - } - else - { - return { E_ACCESSDENIED, 0 }; - } - } - - bool IsInProcCaller() - { - auto [hr, callerProcessId] = GetCallerProcessId(); - return SUCCEEDED(hr) && callerProcessId == GetCurrentProcessId(); - } - - std::wstring_view GetStringForCapability(Capability capability) - { - switch (capability) - { - case Capability::PackageManagement: - return L"packageManagement"sv; - case Capability::PackageQuery: - return L"packageQuery"sv; - default: - winrt::throw_hresult(E_UNEXPECTED); - } - } - - HRESULT EnsureProcessHasCapability(Capability requiredCapability, DWORD callerProcessId) - { - bool allowed = false; - - if (winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent(winrt::name_of())) - { - // Get the caller process id and use it to check if the caller has permissions to access the feature. - winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus status = winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus::DeniedBySystem; - - winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapability capability{ nullptr }; - - try - { - capability = winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapability::CreateWithProcessIdForUser(nullptr, GetStringForCapability(requiredCapability), callerProcessId); - } - catch (const winrt::hresult_invalid_argument&) - { - } - - if (capability) - { - status = capability.CheckAccess(); - - return ((status == winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus::Allowed) ? S_OK : E_ACCESSDENIED); - } - } - - // If AppCapability is not present, require at least medium IL callers - auto requiredIntegrityLevel = AppInstaller::Security::IntegrityLevel::Medium; - - if (callerProcessId != GetCurrentProcessId()) - { - allowed = AppInstaller::Security::IsCOMCallerIntegrityLevelAtLeast(requiredIntegrityLevel); - } - else - { - allowed = AppInstaller::Security::IsCurrentIntegrityLevelAtLeast(requiredIntegrityLevel); - } - - return (allowed ? S_OK : E_ACCESSDENIED); - } - - HRESULT EnsureComCallerHasCapability(Capability requiredCapability) - { - auto [hr, callerProcessId] = GetCallerProcessId(); - RETURN_IF_FAILED(hr); - hr = EnsureProcessHasCapability(requiredCapability, callerProcessId); - // The Windows.Management.Deployment API has set the precedent that packageManagement is a superset of packageQuery - // and packageQuery does not need to be declared separately. - if (FAILED(hr) && requiredCapability == Capability::PackageQuery) - { - hr = EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId); - } - RETURN_HR(hr); - } - - // Best effort at getting caller info. This should only be used for logging. - std::wstring TryGetCallerProcessInfo(DWORD callerProcessId) - { - wil::unique_process_handle processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, callerProcessId)); - if (processHandle) - { - WCHAR packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH]{}; - UINT32 length = ARRAYSIZE(packageFamilyName); - if (::GetPackageFamilyName(processHandle.get(), &length, packageFamilyName) == ERROR_SUCCESS) - { - // If the package is calling into itself, fall through to the executable name - if (AppInstaller::Runtime::GetPackageFamilyName() != packageFamilyName) - { - return { packageFamilyName }; - } - } - - // if the caller doesn't have an AppUserModelID then fall back to the executable name - std::filesystem::path executablePath = AppInstaller::Filesystem::GetExecutablePathForProcess(processHandle.get()); - if (executablePath.has_filename()) - { - return executablePath.filename(); - } - else if (!executablePath.empty()) - { - AICLI_LOG(Fail, Error, << "Unable to get valid executable for process ID [" << callerProcessId << "]: " << executablePath); - } - } - - return {}; - } - - std::string GetCallerName() - { - // See if caller name is set by caller - std::string callerName = GetComCallerName(""); - - // Get process string - if (callerName.empty()) - { - try - { - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - if (SUCCEEDED(hrGetCallerId)) - { - callerName = AppInstaller::Utility::ConvertToUTF8(TryGetCallerProcessInfo(callerProcessId)); - } - } - CATCH_LOG(); - } - - if (callerName.empty()) - { - callerName = "UnknownComCaller"; - } - - return callerName; - } - - bool IsBackgroundProcessForPolicy() - { - bool isBackgroundProcessForPolicy = false; - try - { - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - if (SUCCEEDED(hrGetCallerId) && callerProcessId != GetCurrentProcessId()) - { - // OutOfProc case, we check for explorer.exe - auto callerNameWide = AppInstaller::Utility::ConvertToUTF16(GetCallerName()); - auto processName = AppInstaller::Utility::ConvertToUTF8(std::filesystem::path{ callerNameWide }.filename().wstring()); - if (::AppInstaller::Utility::CaseInsensitiveEquals("explorer.exe", processName) || - ::AppInstaller::Utility::CaseInsensitiveEquals("taskhostw.exe", processName)) - { - isBackgroundProcessForPolicy = true; - } - } - } - CATCH_LOG(); - - return isBackgroundProcessForPolicy; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std::string_literals; +using namespace std::string_view_literals; + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + namespace + { + static std::optional s_callerName; + static wil::srwlock s_callerNameLock; + } + + void SetComCallerName(std::string name) + { + auto lock = s_callerNameLock.lock_exclusive(); + s_callerName.emplace(std::move(name)); + } + + std::string GetComCallerName(std::string defaultNameIfNotSet) + { + auto lock = s_callerNameLock.lock_shared(); + return s_callerName.has_value() ? s_callerName.value() : defaultNameIfNotSet; + } + + std::pair GetCallerProcessId() + { + RPC_STATUS rpcStatus = RPC_S_OK; + RPC_CALL_ATTRIBUTES callAttributes = {}; + callAttributes.Version = RPC_CALL_ATTRIBUTES_VERSION; + callAttributes.Flags = RPC_QUERY_CLIENT_PID; + rpcStatus = RpcServerInqCallAttributes(nullptr, &callAttributes); + + if (rpcStatus == RPC_S_NO_CALL_ACTIVE || + (rpcStatus == RPC_S_OK && HandleToULong(callAttributes.ClientPID) == GetCurrentProcessId())) + { + // in-proc is supported now. + return { S_OK, GetCurrentProcessId() }; + } + else if (rpcStatus == RPC_S_OK) + { + // out-of-proc case. + return { S_OK, HandleToULong(callAttributes.ClientPID) }; + } + else + { + return { E_ACCESSDENIED, 0 }; + } + } + + bool IsInProcCaller() + { + auto [hr, callerProcessId] = GetCallerProcessId(); + return SUCCEEDED(hr) && callerProcessId == GetCurrentProcessId(); + } + + std::wstring_view GetStringForCapability(Capability capability) + { + switch (capability) + { + case Capability::PackageManagement: + return L"packageManagement"sv; + case Capability::PackageQuery: + return L"packageQuery"sv; + default: + winrt::throw_hresult(E_UNEXPECTED); + } + } + + HRESULT EnsureProcessHasCapability(Capability requiredCapability, DWORD callerProcessId) + { + bool allowed = false; + + if (winrt::Windows::Foundation::Metadata::ApiInformation::IsTypePresent(winrt::name_of())) + { + // Get the caller process id and use it to check if the caller has permissions to access the feature. + winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus status = winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus::DeniedBySystem; + + winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapability capability{ nullptr }; + + try + { + capability = winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapability::CreateWithProcessIdForUser(nullptr, GetStringForCapability(requiredCapability), callerProcessId); + } + catch (const winrt::hresult_invalid_argument&) + { + } + + if (capability) + { + status = capability.CheckAccess(); + + return ((status == winrt::Windows::Security::Authorization::AppCapabilityAccess::AppCapabilityAccessStatus::Allowed) ? S_OK : E_ACCESSDENIED); + } + } + + // If AppCapability is not present, require at least medium IL callers + auto requiredIntegrityLevel = AppInstaller::Security::IntegrityLevel::Medium; + + if (callerProcessId != GetCurrentProcessId()) + { + allowed = AppInstaller::Security::IsCOMCallerIntegrityLevelAtLeast(requiredIntegrityLevel); + } + else + { + allowed = AppInstaller::Security::IsCurrentIntegrityLevelAtLeast(requiredIntegrityLevel); + } + + return (allowed ? S_OK : E_ACCESSDENIED); + } + + HRESULT EnsureComCallerHasCapability(Capability requiredCapability) + { + auto [hr, callerProcessId] = GetCallerProcessId(); + RETURN_IF_FAILED(hr); + hr = EnsureProcessHasCapability(requiredCapability, callerProcessId); + // The Windows.Management.Deployment API has set the precedent that packageManagement is a superset of packageQuery + // and packageQuery does not need to be declared separately. + if (FAILED(hr) && requiredCapability == Capability::PackageQuery) + { + hr = EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId); + } + RETURN_HR(hr); + } + + // Best effort at getting caller info. This should only be used for logging. + std::wstring TryGetCallerProcessInfo(DWORD callerProcessId) + { + wil::unique_process_handle processHandle(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, callerProcessId)); + if (processHandle) + { + WCHAR packageFamilyName[PACKAGE_FAMILY_NAME_MAX_LENGTH]{}; + UINT32 length = ARRAYSIZE(packageFamilyName); + if (::GetPackageFamilyName(processHandle.get(), &length, packageFamilyName) == ERROR_SUCCESS) + { + // If the package is calling into itself, fall through to the executable name + if (AppInstaller::Runtime::GetPackageFamilyName() != packageFamilyName) + { + return { packageFamilyName }; + } + } + + // if the caller doesn't have an AppUserModelID then fall back to the executable name + std::filesystem::path executablePath = AppInstaller::Filesystem::GetExecutablePathForProcess(processHandle.get()); + if (executablePath.has_filename()) + { + return executablePath.filename(); + } + else if (!executablePath.empty()) + { + AICLI_LOG(Fail, Error, << "Unable to get valid executable for process ID [" << callerProcessId << "]: " << executablePath); + } + } + + return {}; + } + + std::string GetCallerName() + { + // See if caller name is set by caller + std::string callerName = GetComCallerName(""); + + // Get process string + if (callerName.empty()) + { + try + { + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + if (SUCCEEDED(hrGetCallerId)) + { + callerName = AppInstaller::Utility::ConvertToUTF8(TryGetCallerProcessInfo(callerProcessId)); + } + } + CATCH_LOG(); + } + + if (callerName.empty()) + { + callerName = "UnknownComCaller"; + } + + return callerName; + } + + bool IsBackgroundProcessForPolicy() + { + bool isBackgroundProcessForPolicy = false; + try + { + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + if (SUCCEEDED(hrGetCallerId) && callerProcessId != GetCurrentProcessId()) + { + // OutOfProc case, we check for explorer.exe + auto callerNameWide = AppInstaller::Utility::ConvertToUTF16(GetCallerName()); + auto processName = AppInstaller::Utility::ConvertToUTF8(std::filesystem::path{ callerNameWide }.filename().wstring()); + if (::AppInstaller::Utility::CaseInsensitiveEquals("explorer.exe", processName) || + ::AppInstaller::Utility::CaseInsensitiveEquals("taskhostw.exe", processName)) + { + isBackgroundProcessForPolicy = true; + } + } + } + CATCH_LOG(); + + return isBackgroundProcessForPolicy; + } +} diff --git a/src/Microsoft.Management.Deployment/Helpers.h b/src/Microsoft.Management.Deployment/Helpers.h index 2df39ea7cc..8a769e191f 100644 --- a/src/Microsoft.Management.Deployment/Helpers.h +++ b/src/Microsoft.Management.Deployment/Helpers.h @@ -1,24 +1,24 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "Public/CoCreatableMicrosoftManagementDeploymentClass.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void SetComCallerName(std::string name); - std::string GetComCallerName(std::string defaultNameIfNotSet); - - enum class Capability - { - PackageManagement, - PackageQuery - }; - - HRESULT EnsureProcessHasCapability(Capability requiredCapability, DWORD callerProcessId); - HRESULT EnsureComCallerHasCapability(Capability requiredCapability); - std::pair GetCallerProcessId(); - bool IsInProcCaller(); - std::wstring TryGetCallerProcessInfo(DWORD callerProcessId); - std::string GetCallerName(); - bool IsBackgroundProcessForPolicy(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "Public/CoCreatableMicrosoftManagementDeploymentClass.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void SetComCallerName(std::string name); + std::string GetComCallerName(std::string defaultNameIfNotSet); + + enum class Capability + { + PackageManagement, + PackageQuery + }; + + HRESULT EnsureProcessHasCapability(Capability requiredCapability, DWORD callerProcessId); + HRESULT EnsureComCallerHasCapability(Capability requiredCapability); + std::pair GetCallerProcessId(); + bool IsInProcCaller(); + std::wstring TryGetCallerProcessInfo(DWORD callerProcessId); + std::string GetCallerName(); + bool IsBackgroundProcessForPolicy(); +} diff --git a/src/Microsoft.Management.Deployment/InstallOptions.cpp b/src/Microsoft.Management.Deployment/InstallOptions.cpp index 09189fd76e..fba214ede4 100644 --- a/src/Microsoft.Management.Deployment/InstallOptions.cpp +++ b/src/Microsoft.Management.Deployment/InstallOptions.cpp @@ -1,172 +1,172 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "InstallOptions.h" -#pragma warning( pop ) -#include "InstallOptions.g.cpp" -#include "Converters.h" -#include "Helpers.h" - -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - InstallOptions::InstallOptions() - { - // Populate the allowed architectures with the default values for the machine - for (AppInstaller::Utility::Architecture architecture : AppInstaller::Utility::GetApplicableArchitectures()) - { - auto convertedArchitecture = GetWindowsSystemProcessorArchitecture(architecture); - if (convertedArchitecture) - { - m_allowedArchitectures.Append(convertedArchitecture.value()); - } - } - } - winrt::Microsoft::Management::Deployment::PackageVersionId InstallOptions::PackageVersionId() - { - return m_packageVersionId; - } - void InstallOptions::PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value) - { - m_packageVersionId = value; - } - hstring InstallOptions::PreferredInstallLocation() - { - return hstring(m_preferredInstallLocation); - } - void InstallOptions::PreferredInstallLocation(hstring const& value) - { - m_preferredInstallLocation = value; - } - winrt::Microsoft::Management::Deployment::PackageInstallScope InstallOptions::PackageInstallScope() - { - return m_packageInstallScope; - } - void InstallOptions::PackageInstallScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value) - { - m_packageInstallScope = value; - } - winrt::Microsoft::Management::Deployment::PackageInstallMode InstallOptions::PackageInstallMode() - { - return m_packageInstallMode; - } - void InstallOptions::PackageInstallMode(winrt::Microsoft::Management::Deployment::PackageInstallMode const& value) - { - m_packageInstallMode = value; - } - winrt::Microsoft::Management::Deployment::PackageInstallerType InstallOptions::InstallerType() - { - return m_installerType; - } - void InstallOptions::InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value) - { - m_installerType = value; - } - hstring InstallOptions::LogOutputPath() - { - return hstring(m_logOutputPath); - } - void InstallOptions::LogOutputPath(hstring const& value) - { - m_logOutputPath = value; - } - bool InstallOptions::AllowHashMismatch() - { - return m_allowHashMismatch; - } - void InstallOptions::AllowHashMismatch(bool value) - { - m_allowHashMismatch = value; - } - bool InstallOptions::BypassIsStoreClientBlockedPolicyCheck() - { - return m_bypassIsStoreClientBlockedPolicyCheck; - } - void InstallOptions::BypassIsStoreClientBlockedPolicyCheck(bool value) - { - m_bypassIsStoreClientBlockedPolicyCheck = value; - } - hstring InstallOptions::ReplacementInstallerArguments() - { - return hstring(m_replacementInstallerArguments); - } - void InstallOptions::ReplacementInstallerArguments(hstring const& value) - { - m_replacementInstallerArguments = value; - } - hstring InstallOptions::AdditionalInstallerArguments() - { - return hstring(m_additionalInstallerArguments); - } - void InstallOptions::AdditionalInstallerArguments(hstring const& value) - { - m_additionalInstallerArguments = value; - } - hstring InstallOptions::CorrelationData() - { - return hstring(m_correlationData); - } - void InstallOptions::CorrelationData(hstring const& value) - { - m_correlationData = value; - } - hstring InstallOptions::AdditionalPackageCatalogArguments() - { - return hstring(m_additionalPackageCatalogArguments); - } - void InstallOptions::AdditionalPackageCatalogArguments(hstring const& value) - { - m_additionalPackageCatalogArguments = value; - } - winrt::Windows::Foundation::Collections::IVector InstallOptions::AllowedArchitectures() - { - return m_allowedArchitectures; - } - bool InstallOptions::AllowUpgradeToUnknownVersion() - { - return m_allowUpgradeToUnknownVersion; - } - void InstallOptions::AllowUpgradeToUnknownVersion(bool value) - { - m_allowUpgradeToUnknownVersion = value; - } - bool InstallOptions::Force() - { - return m_force; - } - void InstallOptions::Force(bool value) - { - m_force = value; - } - void InstallOptions::AcceptPackageAgreements(bool value) - { - m_acceptPackageAgreements = value; - } - bool InstallOptions::AcceptPackageAgreements() - { - return m_acceptPackageAgreements; - } - void InstallOptions::SkipDependencies(bool value) - { - m_skipDependencies = value; - } - bool InstallOptions::SkipDependencies() - { - 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); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "InstallOptions.h" +#pragma warning( pop ) +#include "InstallOptions.g.cpp" +#include "Converters.h" +#include "Helpers.h" + +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + InstallOptions::InstallOptions() + { + // Populate the allowed architectures with the default values for the machine + for (AppInstaller::Utility::Architecture architecture : AppInstaller::Utility::GetApplicableArchitectures()) + { + auto convertedArchitecture = GetWindowsSystemProcessorArchitecture(architecture); + if (convertedArchitecture) + { + m_allowedArchitectures.Append(convertedArchitecture.value()); + } + } + } + winrt::Microsoft::Management::Deployment::PackageVersionId InstallOptions::PackageVersionId() + { + return m_packageVersionId; + } + void InstallOptions::PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value) + { + m_packageVersionId = value; + } + hstring InstallOptions::PreferredInstallLocation() + { + return hstring(m_preferredInstallLocation); + } + void InstallOptions::PreferredInstallLocation(hstring const& value) + { + m_preferredInstallLocation = value; + } + winrt::Microsoft::Management::Deployment::PackageInstallScope InstallOptions::PackageInstallScope() + { + return m_packageInstallScope; + } + void InstallOptions::PackageInstallScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value) + { + m_packageInstallScope = value; + } + winrt::Microsoft::Management::Deployment::PackageInstallMode InstallOptions::PackageInstallMode() + { + return m_packageInstallMode; + } + void InstallOptions::PackageInstallMode(winrt::Microsoft::Management::Deployment::PackageInstallMode const& value) + { + m_packageInstallMode = value; + } + winrt::Microsoft::Management::Deployment::PackageInstallerType InstallOptions::InstallerType() + { + return m_installerType; + } + void InstallOptions::InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value) + { + m_installerType = value; + } + hstring InstallOptions::LogOutputPath() + { + return hstring(m_logOutputPath); + } + void InstallOptions::LogOutputPath(hstring const& value) + { + m_logOutputPath = value; + } + bool InstallOptions::AllowHashMismatch() + { + return m_allowHashMismatch; + } + void InstallOptions::AllowHashMismatch(bool value) + { + m_allowHashMismatch = value; + } + bool InstallOptions::BypassIsStoreClientBlockedPolicyCheck() + { + return m_bypassIsStoreClientBlockedPolicyCheck; + } + void InstallOptions::BypassIsStoreClientBlockedPolicyCheck(bool value) + { + m_bypassIsStoreClientBlockedPolicyCheck = value; + } + hstring InstallOptions::ReplacementInstallerArguments() + { + return hstring(m_replacementInstallerArguments); + } + void InstallOptions::ReplacementInstallerArguments(hstring const& value) + { + m_replacementInstallerArguments = value; + } + hstring InstallOptions::AdditionalInstallerArguments() + { + return hstring(m_additionalInstallerArguments); + } + void InstallOptions::AdditionalInstallerArguments(hstring const& value) + { + m_additionalInstallerArguments = value; + } + hstring InstallOptions::CorrelationData() + { + return hstring(m_correlationData); + } + void InstallOptions::CorrelationData(hstring const& value) + { + m_correlationData = value; + } + hstring InstallOptions::AdditionalPackageCatalogArguments() + { + return hstring(m_additionalPackageCatalogArguments); + } + void InstallOptions::AdditionalPackageCatalogArguments(hstring const& value) + { + m_additionalPackageCatalogArguments = value; + } + winrt::Windows::Foundation::Collections::IVector InstallOptions::AllowedArchitectures() + { + return m_allowedArchitectures; + } + bool InstallOptions::AllowUpgradeToUnknownVersion() + { + return m_allowUpgradeToUnknownVersion; + } + void InstallOptions::AllowUpgradeToUnknownVersion(bool value) + { + m_allowUpgradeToUnknownVersion = value; + } + bool InstallOptions::Force() + { + return m_force; + } + void InstallOptions::Force(bool value) + { + m_force = value; + } + void InstallOptions::AcceptPackageAgreements(bool value) + { + m_acceptPackageAgreements = value; + } + bool InstallOptions::AcceptPackageAgreements() + { + return m_acceptPackageAgreements; + } + void InstallOptions::SkipDependencies(bool value) + { + m_skipDependencies = value; + } + bool InstallOptions::SkipDependencies() + { + 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 8915e294a8..adabf080d3 100644 --- a/src/Microsoft.Management.Deployment/InstallOptions.h +++ b/src/Microsoft.Management.Deployment/InstallOptions.h @@ -1,83 +1,83 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "InstallOptions.g.h" -#include "Public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_InstallOptions)] - struct InstallOptions : InstallOptionsT - { - InstallOptions(); - - winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); - void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); - hstring PreferredInstallLocation(); - void PreferredInstallLocation(hstring const& value); - winrt::Microsoft::Management::Deployment::PackageInstallScope PackageInstallScope(); - void PackageInstallScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); - winrt::Microsoft::Management::Deployment::PackageInstallMode PackageInstallMode(); - void PackageInstallMode(winrt::Microsoft::Management::Deployment::PackageInstallMode const& value); - winrt::Microsoft::Management::Deployment::PackageInstallerType InstallerType(); - void InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value); - hstring LogOutputPath(); - void LogOutputPath(hstring const& value); - bool AllowHashMismatch(); - void AllowHashMismatch(bool value); - bool BypassIsStoreClientBlockedPolicyCheck(); - void BypassIsStoreClientBlockedPolicyCheck(bool value); - hstring ReplacementInstallerArguments(); - void ReplacementInstallerArguments(hstring const& value); - hstring AdditionalInstallerArguments(); - void AdditionalInstallerArguments(hstring const& value); - hstring CorrelationData(); - void CorrelationData(hstring const& value); - hstring AdditionalPackageCatalogArguments(); - void AdditionalPackageCatalogArguments(hstring const& value); - winrt::Windows::Foundation::Collections::IVector AllowedArchitectures(); - bool AllowUpgradeToUnknownVersion(); - void AllowUpgradeToUnknownVersion(bool value); - bool Force(); - void Force(bool value); - bool AcceptPackageAgreements(); - 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: - winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; - std::wstring m_preferredInstallLocation = L""; - winrt::Microsoft::Management::Deployment::PackageInstallScope m_packageInstallScope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; - winrt::Microsoft::Management::Deployment::PackageInstallMode m_packageInstallMode = winrt::Microsoft::Management::Deployment::PackageInstallMode::Default; - winrt::Microsoft::Management::Deployment::PackageInstallerType m_installerType = winrt::Microsoft::Management::Deployment::PackageInstallerType::Unknown; - std::wstring m_logOutputPath = L""; - bool m_allowHashMismatch = false; - bool m_bypassIsStoreClientBlockedPolicyCheck = false; - std::wstring m_replacementInstallerArguments = L""; - std::wstring m_additionalInstallerArguments = L""; - std::wstring m_correlationData = L""; - std::wstring m_additionalPackageCatalogArguments = L""; - Windows::Foundation::Collections::IVector m_allowedArchitectures{ - winrt::single_threaded_vector() }; - bool m_allowUpgradeToUnknownVersion = false; - bool m_force = false; - bool m_acceptPackageAgreements = true; - bool m_skipDependencies = false; - winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct InstallOptions : InstallOptionsT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "InstallOptions.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_InstallOptions)] + struct InstallOptions : InstallOptionsT + { + InstallOptions(); + + winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); + void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); + hstring PreferredInstallLocation(); + void PreferredInstallLocation(hstring const& value); + winrt::Microsoft::Management::Deployment::PackageInstallScope PackageInstallScope(); + void PackageInstallScope(winrt::Microsoft::Management::Deployment::PackageInstallScope const& value); + winrt::Microsoft::Management::Deployment::PackageInstallMode PackageInstallMode(); + void PackageInstallMode(winrt::Microsoft::Management::Deployment::PackageInstallMode const& value); + winrt::Microsoft::Management::Deployment::PackageInstallerType InstallerType(); + void InstallerType(winrt::Microsoft::Management::Deployment::PackageInstallerType const& value); + hstring LogOutputPath(); + void LogOutputPath(hstring const& value); + bool AllowHashMismatch(); + void AllowHashMismatch(bool value); + bool BypassIsStoreClientBlockedPolicyCheck(); + void BypassIsStoreClientBlockedPolicyCheck(bool value); + hstring ReplacementInstallerArguments(); + void ReplacementInstallerArguments(hstring const& value); + hstring AdditionalInstallerArguments(); + void AdditionalInstallerArguments(hstring const& value); + hstring CorrelationData(); + void CorrelationData(hstring const& value); + hstring AdditionalPackageCatalogArguments(); + void AdditionalPackageCatalogArguments(hstring const& value); + winrt::Windows::Foundation::Collections::IVector AllowedArchitectures(); + bool AllowUpgradeToUnknownVersion(); + void AllowUpgradeToUnknownVersion(bool value); + bool Force(); + void Force(bool value); + bool AcceptPackageAgreements(); + 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: + winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; + std::wstring m_preferredInstallLocation = L""; + winrt::Microsoft::Management::Deployment::PackageInstallScope m_packageInstallScope = winrt::Microsoft::Management::Deployment::PackageInstallScope::Any; + winrt::Microsoft::Management::Deployment::PackageInstallMode m_packageInstallMode = winrt::Microsoft::Management::Deployment::PackageInstallMode::Default; + winrt::Microsoft::Management::Deployment::PackageInstallerType m_installerType = winrt::Microsoft::Management::Deployment::PackageInstallerType::Unknown; + std::wstring m_logOutputPath = L""; + bool m_allowHashMismatch = false; + bool m_bypassIsStoreClientBlockedPolicyCheck = false; + std::wstring m_replacementInstallerArguments = L""; + std::wstring m_additionalInstallerArguments = L""; + std::wstring m_correlationData = L""; + std::wstring m_additionalPackageCatalogArguments = L""; + Windows::Foundation::Collections::IVector m_allowedArchitectures{ + winrt::single_threaded_vector() }; + bool m_allowUpgradeToUnknownVersion = false; + bool m_force = false; + bool m_acceptPackageAgreements = true; + bool m_skipDependencies = false; + winrt::Microsoft::Management::Deployment::AuthenticationArguments m_authenticationArguments{ nullptr }; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct InstallOptions : InstallOptionsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/InstalledStatus.cpp b/src/Microsoft.Management.Deployment/InstalledStatus.cpp index e0d7be28cd..935054ff70 100644 --- a/src/Microsoft.Management.Deployment/InstalledStatus.cpp +++ b/src/Microsoft.Management.Deployment/InstalledStatus.cpp @@ -12,16 +12,16 @@ namespace winrt::Microsoft::Management::Deployment::implementation m_path = winrt::to_hstring(installedStatus.Path); m_status = installedStatus.Status; } - winrt::Microsoft::Management::Deployment::InstalledStatusType InstalledStatus::Type() - { - return m_type; + winrt::Microsoft::Management::Deployment::InstalledStatusType InstalledStatus::Type() + { + return m_type; } - hstring InstalledStatus::Path() - { - return m_path; + hstring InstalledStatus::Path() + { + return m_path; } - winrt::hresult InstalledStatus::Status() - { - return m_status; + winrt::hresult InstalledStatus::Status() + { + return m_status; } } diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj index 57899d710d..5eb84d2782 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj @@ -1,286 +1,286 @@ - - - - - true - true - true - true - {1cc41a9a-ae66-459d-9210-1e572dd7be69} - Microsoft.Management.Deployment - Microsoft.Management.Deployment - en-US - 14.0 - 10.0 - 10.0.26100.0 - 10.0.17763.0 - true - -library Microsoft_Management_Deployment - - - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - StaticLibrary - false - - - true - true - - - false - true - false - Spectre - - - false - true - false - Spectre - - - - - - - - - - - - - - - - $(VC_IncludePath);$(WindowsSDK_IncludePath); - $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ - $(PlatformTarget)\$(Configuration)\ - $(RootNamespace).Server - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - Level4 - %(AdditionalOptions) /bigobj - true - true - _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) - $(ProjectDir)..\AppInstallerCLICore;$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - - - Console - false - Microsoft_Management_Deployment.def - $(OutDir)$(ProjectName).winmd - AppInstallerCLICore.lib;AppInstallerCommonCore.lib;AppInstallerRepositoryCore.lib;JsonCppLib.lib;YamlCppLib.lib;wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;pure.lib%(AdditionalDependencies) - - - - - _DEBUG;%(PreprocessorDefinitions) - $(OutDir)$(TargetName)Debug.pdb - false - false - false - - - - - NDEBUG;%(PreprocessorDefinitions) - false - false - false - - - true - true - - - - - NDEBUG;%(PreprocessorDefinitions) - MultiThreaded - MultiThreaded - MultiThreaded - false - false - false - - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Create - - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + true + true + true + true + {1cc41a9a-ae66-459d-9210-1e572dd7be69} + Microsoft.Management.Deployment + Microsoft.Management.Deployment + en-US + 14.0 + 10.0 + 10.0.26100.0 + 10.0.17763.0 + true + -library Microsoft_Management_Deployment + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + StaticLibrary + false + + + true + true + + + false + true + false + Spectre + + + false + true + false + Spectre + + + + + + + + + + + + + + + + $(VC_IncludePath);$(WindowsSDK_IncludePath); + $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ + $(PlatformTarget)\$(Configuration)\ + $(RootNamespace).Server + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + true + true + _WINRT_DLL;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + $(ProjectDir)..\AppInstallerCLICore;$(ProjectDir);$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + + + Console + false + Microsoft_Management_Deployment.def + $(OutDir)$(ProjectName).winmd + AppInstallerCLICore.lib;AppInstallerCommonCore.lib;AppInstallerRepositoryCore.lib;JsonCppLib.lib;YamlCppLib.lib;wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;pure.lib%(AdditionalDependencies) + + + + + _DEBUG;%(PreprocessorDefinitions) + $(OutDir)$(TargetName)Debug.pdb + false + false + false + + + + + NDEBUG;%(PreprocessorDefinitions) + false + false + false + + + true + true + + + + + NDEBUG;%(PreprocessorDefinitions) + MultiThreaded + MultiThreaded + MultiThreaded + false + false + false + + + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Create + + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters index 6c00ab8c5d..429ce6a42e 100644 --- a/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters +++ b/src/Microsoft.Management.Deployment/Microsoft.Management.Deployment.vcxproj.filters @@ -1,128 +1,128 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Public - - - Public - - - - - - - - - - - - - - - - Public - - - - - - - - - - - - - {9c3907ed-84d9-4485-9b15-04c50717f0ab} - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Public + + + Public + + + + + + + + + + + + + + + + Public + + + + + + + + + + + + + {9c3907ed-84d9-4485-9b15-04c50717f0ab} + + + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/Microsoft_Management_Deployment.def b/src/Microsoft.Management.Deployment/Microsoft_Management_Deployment.def index 8c1a02932d..24e7c1235c 100644 --- a/src/Microsoft.Management.Deployment/Microsoft_Management_Deployment.def +++ b/src/Microsoft.Management.Deployment/Microsoft_Management_Deployment.def @@ -1,3 +1,3 @@ -EXPORTS -DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE -DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE +EXPORTS +DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE +DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE diff --git a/src/Microsoft.Management.Deployment/PackageCatalog.cpp b/src/Microsoft.Management.Deployment/PackageCatalog.cpp index 4b0cf1fe4f..5eca733aa4 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalog.cpp +++ b/src/Microsoft.Management.Deployment/PackageCatalog.cpp @@ -1,175 +1,175 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include "Workflows/WorkflowBase.h" -#include "Converters.h" -#include "PackageCatalog.h" -#include "PackageCatalog.g.cpp" -#include "PackageCatalogInfo.h" -#include "FindPackagesResult.h" -#include "MatchResult.h" -#include "CatalogPackage.h" -#include "Commands/RootCommand.h" -#include "ExecutionContext.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "PackageMatchFilter.h" -#pragma warning( pop ) -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void PackageCatalog::Initialize( - winrt::Microsoft::Management::Deployment::PackageCatalogInfo info, - ::AppInstaller::Repository::Source source, - bool isComposite) - { - m_info = info; - m_source = std::move(source); - m_isComposite = isComposite; - } - bool PackageCatalog::IsComposite() - { - // Can't use m_source->IsComposite for this because all remote sources are turned into composite sources - // behind the scenes when being opened in PackageCatalogReference.cpp so that CatalogPackage.IsInstalled works. - return m_isComposite; - } - winrt::Microsoft::Management::Deployment::PackageCatalogInfo PackageCatalog::Info() - { - return m_info; - } - winrt::Windows::Foundation::IAsyncOperation PackageCatalog::FindPackagesAsync(winrt::Microsoft::Management::Deployment::FindPackagesOptions options) - { - auto strong_this = get_strong(); - co_await resume_background(); - co_return FindPackages(options); - } - - HRESULT PopulateSearchRequestFromVector( - ::AppInstaller::Repository::SearchRequest* searchRequest, - Windows::Foundation::Collections::IVector vector, - bool isSelector) - { - // Populates either the Filters vector of a searchRequest (if isSelector is false), - // or the Inclusions and Query (if true) - for (uint32_t i = 0; i < vector.Size(); ++i) - { - Microsoft::Management::Deployment::PackageMatchFilter filter = vector.GetAt(i); - - if (filter.Value().size() == 0) - { - // If the caller did not add a value it can't actually be used to filter or include anything so just ignore it. - continue; - } - ::AppInstaller::Repository::MatchType packageFieldMatchOption = GetRepositoryMatchType(filter.Option()); - ::AppInstaller::Repository::PackageMatchField matchField = GetRepositoryMatchField(filter.Field()); - - if (isSelector) - { - if (filter.Field() == Microsoft::Management::Deployment::PackageMatchField::CatalogDefault) - { - if (searchRequest->Query.has_value()) - { - // CatalogDefault match field can't be used twice. - return E_INVALIDARG; - } - searchRequest->Query = ::AppInstaller::Repository::RequestMatch(packageFieldMatchOption, winrt::to_string(filter.Value())); - } - else - { - auto matchFilter = ::AppInstaller::Repository::PackageMatchFilter(matchField, packageFieldMatchOption, winrt::to_string(filter.Value())); - searchRequest->Inclusions.emplace_back(matchFilter); - } - } - else - { - if (filter.Field() == Microsoft::Management::Deployment::PackageMatchField::CatalogDefault) - { - // CatalogDefault match fields can't be used in the Filters. - return E_INVALIDARG; - } - auto matchFilter = ::AppInstaller::Repository::PackageMatchFilter(matchField, packageFieldMatchOption, winrt::to_string(filter.Value())); - searchRequest->Filters.emplace_back(matchFilter); - } - } - return S_OK; - } - - HRESULT PopulateSearchRequest( - ::AppInstaller::Repository::SearchRequest* searchRequest, - winrt::Microsoft::Management::Deployment::FindPackagesOptions const& options) - { - RETURN_IF_FAILED(PopulateSearchRequestFromVector(searchRequest, options.Filters(), false)); - RETURN_IF_FAILED(PopulateSearchRequestFromVector(searchRequest, options.Selectors(), true)); - return S_OK; - } - - winrt::Microsoft::Management::Deployment::FindPackagesResult GetFindPackagesResult(HRESULT hr, bool isTruncated, Windows::Foundation::Collections::IVector matches) - { - auto findPackagesResult = winrt::make_self>(); - // TODO: Add search timeout and error code. - winrt::Microsoft::Management::Deployment::FindPackagesResultStatus status = FindPackagesResultStatus(hr); - findPackagesResult->Initialize(status, isTruncated, matches, hr); - return *findPackagesResult; - } - - winrt::Microsoft::Management::Deployment::FindPackagesResult PackageCatalog::FindPackages(winrt::Microsoft::Management::Deployment::FindPackagesOptions const& options) - { - bool isTruncated = false; - Windows::Foundation::Collections::IVector matches{ winrt::single_threaded_vector() }; - ::AppInstaller::Repository::SearchRequest searchRequest; - - HRESULT hr = S_OK; - try - { - // No need to check for caller capability again since packageQuery was required in order to get the PackageCatalog object through Connect - - if (FAILED(hr = PopulateSearchRequest(&searchRequest, options))) - { - return GetFindPackagesResult(hr, isTruncated, matches); - } - - searchRequest.MaximumResults = options.ResultLimit(); - auto searchResult = m_source.Search(searchRequest); - - // Handle failures by just rethrowing the first one for now. - // TODO: Look into updating the COM interface to enable the single source - // failures to flow out. - if (!searchResult.Failures.empty()) - { - std::rethrow_exception(searchResult.Failures[0].Exception); - } - - // Build the result object from the searchResult - for (size_t i = 0; i < searchResult.Matches.size(); ++i) - { - auto match = searchResult.Matches[i]; - auto catalogPackage = winrt::make_self>(); - catalogPackage->Initialize(m_source, match.Package); - - auto packageMatchFilter = winrt::make_self>(); - packageMatchFilter->Initialize(match.MatchCriteria); - - auto matchResult = winrt::make_self>(); - matchResult->Initialize(*catalogPackage, *packageMatchFilter); - - matches.Append(*matchResult); - } - isTruncated = searchResult.Truncated; - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - - return GetFindPackagesResult(hr, isTruncated, matches); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include "Workflows/WorkflowBase.h" +#include "Converters.h" +#include "PackageCatalog.h" +#include "PackageCatalog.g.cpp" +#include "PackageCatalogInfo.h" +#include "FindPackagesResult.h" +#include "MatchResult.h" +#include "CatalogPackage.h" +#include "Commands/RootCommand.h" +#include "ExecutionContext.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "PackageMatchFilter.h" +#pragma warning( pop ) +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void PackageCatalog::Initialize( + winrt::Microsoft::Management::Deployment::PackageCatalogInfo info, + ::AppInstaller::Repository::Source source, + bool isComposite) + { + m_info = info; + m_source = std::move(source); + m_isComposite = isComposite; + } + bool PackageCatalog::IsComposite() + { + // Can't use m_source->IsComposite for this because all remote sources are turned into composite sources + // behind the scenes when being opened in PackageCatalogReference.cpp so that CatalogPackage.IsInstalled works. + return m_isComposite; + } + winrt::Microsoft::Management::Deployment::PackageCatalogInfo PackageCatalog::Info() + { + return m_info; + } + winrt::Windows::Foundation::IAsyncOperation PackageCatalog::FindPackagesAsync(winrt::Microsoft::Management::Deployment::FindPackagesOptions options) + { + auto strong_this = get_strong(); + co_await resume_background(); + co_return FindPackages(options); + } + + HRESULT PopulateSearchRequestFromVector( + ::AppInstaller::Repository::SearchRequest* searchRequest, + Windows::Foundation::Collections::IVector vector, + bool isSelector) + { + // Populates either the Filters vector of a searchRequest (if isSelector is false), + // or the Inclusions and Query (if true) + for (uint32_t i = 0; i < vector.Size(); ++i) + { + Microsoft::Management::Deployment::PackageMatchFilter filter = vector.GetAt(i); + + if (filter.Value().size() == 0) + { + // If the caller did not add a value it can't actually be used to filter or include anything so just ignore it. + continue; + } + ::AppInstaller::Repository::MatchType packageFieldMatchOption = GetRepositoryMatchType(filter.Option()); + ::AppInstaller::Repository::PackageMatchField matchField = GetRepositoryMatchField(filter.Field()); + + if (isSelector) + { + if (filter.Field() == Microsoft::Management::Deployment::PackageMatchField::CatalogDefault) + { + if (searchRequest->Query.has_value()) + { + // CatalogDefault match field can't be used twice. + return E_INVALIDARG; + } + searchRequest->Query = ::AppInstaller::Repository::RequestMatch(packageFieldMatchOption, winrt::to_string(filter.Value())); + } + else + { + auto matchFilter = ::AppInstaller::Repository::PackageMatchFilter(matchField, packageFieldMatchOption, winrt::to_string(filter.Value())); + searchRequest->Inclusions.emplace_back(matchFilter); + } + } + else + { + if (filter.Field() == Microsoft::Management::Deployment::PackageMatchField::CatalogDefault) + { + // CatalogDefault match fields can't be used in the Filters. + return E_INVALIDARG; + } + auto matchFilter = ::AppInstaller::Repository::PackageMatchFilter(matchField, packageFieldMatchOption, winrt::to_string(filter.Value())); + searchRequest->Filters.emplace_back(matchFilter); + } + } + return S_OK; + } + + HRESULT PopulateSearchRequest( + ::AppInstaller::Repository::SearchRequest* searchRequest, + winrt::Microsoft::Management::Deployment::FindPackagesOptions const& options) + { + RETURN_IF_FAILED(PopulateSearchRequestFromVector(searchRequest, options.Filters(), false)); + RETURN_IF_FAILED(PopulateSearchRequestFromVector(searchRequest, options.Selectors(), true)); + return S_OK; + } + + winrt::Microsoft::Management::Deployment::FindPackagesResult GetFindPackagesResult(HRESULT hr, bool isTruncated, Windows::Foundation::Collections::IVector matches) + { + auto findPackagesResult = winrt::make_self>(); + // TODO: Add search timeout and error code. + winrt::Microsoft::Management::Deployment::FindPackagesResultStatus status = FindPackagesResultStatus(hr); + findPackagesResult->Initialize(status, isTruncated, matches, hr); + return *findPackagesResult; + } + + winrt::Microsoft::Management::Deployment::FindPackagesResult PackageCatalog::FindPackages(winrt::Microsoft::Management::Deployment::FindPackagesOptions const& options) + { + bool isTruncated = false; + Windows::Foundation::Collections::IVector matches{ winrt::single_threaded_vector() }; + ::AppInstaller::Repository::SearchRequest searchRequest; + + HRESULT hr = S_OK; + try + { + // No need to check for caller capability again since packageQuery was required in order to get the PackageCatalog object through Connect + + if (FAILED(hr = PopulateSearchRequest(&searchRequest, options))) + { + return GetFindPackagesResult(hr, isTruncated, matches); + } + + searchRequest.MaximumResults = options.ResultLimit(); + auto searchResult = m_source.Search(searchRequest); + + // Handle failures by just rethrowing the first one for now. + // TODO: Look into updating the COM interface to enable the single source + // failures to flow out. + if (!searchResult.Failures.empty()) + { + std::rethrow_exception(searchResult.Failures[0].Exception); + } + + // Build the result object from the searchResult + for (size_t i = 0; i < searchResult.Matches.size(); ++i) + { + auto match = searchResult.Matches[i]; + auto catalogPackage = winrt::make_self>(); + catalogPackage->Initialize(m_source, match.Package); + + auto packageMatchFilter = winrt::make_self>(); + packageMatchFilter->Initialize(match.MatchCriteria); + + auto matchResult = winrt::make_self>(); + matchResult->Initialize(*catalogPackage, *packageMatchFilter); + + matches.Append(*matchResult); + } + isTruncated = searchResult.Truncated; + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + + return GetFindPackagesResult(hr, isTruncated, matches); + } +} diff --git a/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp index b0eaf3dee7..979068a30d 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp +++ b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.cpp @@ -1,18 +1,18 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackageCatalogConnectionValidationEventArgs.h" -#include "PackageCatalogConnectionValidationEventArgs.g.cpp" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void PackageCatalogConnectionValidationEventArgs::Initialize(winrt::Windows::Security::Cryptography::Certificates::Certificate serverCertificate) - { - m_serverCertificate = serverCertificate; - } - - winrt::Windows::Security::Cryptography::Certificates::Certificate PackageCatalogConnectionValidationEventArgs::ServerCertificate() - { - return m_serverCertificate; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageCatalogConnectionValidationEventArgs.h" +#include "PackageCatalogConnectionValidationEventArgs.g.cpp" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void PackageCatalogConnectionValidationEventArgs::Initialize(winrt::Windows::Security::Cryptography::Certificates::Certificate serverCertificate) + { + m_serverCertificate = serverCertificate; + } + + winrt::Windows::Security::Cryptography::Certificates::Certificate PackageCatalogConnectionValidationEventArgs::ServerCertificate() + { + return m_serverCertificate; + } +} diff --git a/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h index 58798b2e3d..a6c61af92f 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h +++ b/src/Microsoft.Management.Deployment/PackageCatalogConnectionValidationEventArgs.h @@ -1,23 +1,23 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "PackageCatalogConnectionValidationEventArgs.g.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - struct PackageCatalogConnectionValidationEventArgs : PackageCatalogConnectionValidationEventArgsT - { - PackageCatalogConnectionValidationEventArgs() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(winrt::Windows::Security::Cryptography::Certificates::Certificate serverCertificate); -#endif - - winrt::Windows::Security::Cryptography::Certificates::Certificate ServerCertificate(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Windows::Security::Cryptography::Certificates::Certificate m_serverCertificate{ nullptr }; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageCatalogConnectionValidationEventArgs.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct PackageCatalogConnectionValidationEventArgs : PackageCatalogConnectionValidationEventArgsT + { + PackageCatalogConnectionValidationEventArgs() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(winrt::Windows::Security::Cryptography::Certificates::Certificate serverCertificate); +#endif + + winrt::Windows::Security::Cryptography::Certificates::Certificate ServerCertificate(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Windows::Security::Cryptography::Certificates::Certificate m_serverCertificate{ nullptr }; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/PackageCatalogInfo.cpp b/src/Microsoft.Management.Deployment/PackageCatalogInfo.cpp index 65d938bc13..0306cb58b9 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogInfo.cpp +++ b/src/Microsoft.Management.Deployment/PackageCatalogInfo.cpp @@ -49,20 +49,20 @@ namespace winrt::Microsoft::Management::Deployment::implementation } } winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel PackageCatalogInfo::TrustLevel() - { - if (WI_IsFlagSet(m_sourceDetails.TrustLevel, ::AppInstaller::Repository::SourceTrustLevel::Trusted)) + { + if (WI_IsFlagSet(m_sourceDetails.TrustLevel, ::AppInstaller::Repository::SourceTrustLevel::Trusted)) { - return PackageCatalogTrustLevel::Trusted; - } + return PackageCatalogTrustLevel::Trusted; + } return PackageCatalogTrustLevel::None; } bool PackageCatalogInfo::Explicit() { return m_sourceDetails.Explicit; - } - - int32_t PackageCatalogInfo::Priority() + } + + int32_t PackageCatalogInfo::Priority() { return m_sourceDetails.Priority; } diff --git a/src/Microsoft.Management.Deployment/PackageCatalogProgress.cpp b/src/Microsoft.Management.Deployment/PackageCatalogProgress.cpp index e82f1d7fdb..5a5169ad34 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogProgress.cpp +++ b/src/Microsoft.Management.Deployment/PackageCatalogProgress.cpp @@ -1,157 +1,157 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "PackageCatalogProgress.h" -#include "AppInstallerStrings.h" -#include "Microsoft/PredefinedInstalledSourceFactory.h" - -using namespace AppInstaller; -using namespace AppInstaller::Repository; - -namespace winrt::Microsoft::Management::Deployment -{ - namespace ProgressSinkFactory - { - std::shared_ptr CreatePackageCatalogProgressSink(std::string sourceType, std::function progressReporter, bool removeOperation) - { - if (sourceType.empty() - || Utility::CaseInsensitiveEquals( Repository::Microsoft::PredefinedInstalledSourceFactory::Type(), sourceType)) - { - std::vector> progressWeights; - - // There is no download operation for remove operation, so use only percentage based progress to account for uninstall. - if (removeOperation) - { - // it is percentage based progress. - progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Percent, 1.0)); - } - else - { - // Add/Update operation has two progress types: - // 1. Bytes for downloading index and - // 2. Percent for index installation. - progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Bytes, 0.7)); - progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Percent, 0.3)); - } - - return std::make_shared(progressWeights, progressReporter); - } - else - { - return std::make_shared(progressReporter); - } - } - } - - CompletionOnlyProgressSink::CompletionOnlyProgressSink(std::function progressReporter) : - m_progressReporter(progressReporter) - { - if (!m_progressReporter) - { - THROW_HR(E_INVALIDARG); - } - } - - void CompletionOnlyProgressSink::OnProgress(uint64_t /*current*/, uint64_t /*maximum*/, AppInstaller::ProgressType /*type*/) - { - } - - void CompletionOnlyProgressSink::SetProgressMessage(std::string_view /*message*/) - { - } - - void CompletionOnlyProgressSink::BeginProgress() - { - m_progressReporter(0); - } - - void CompletionOnlyProgressSink::EndProgress(bool /*hideProgressWhenDone*/) - { - m_progressReporter(100); - } - - PreIndexedPackageCatalogProgressSink::PreIndexedPackageCatalogProgressSink(std::vector> progressWeights, std::function progressReporter) : - m_progressWeights(progressWeights), m_progressReporter(progressReporter) - { - if (!m_progressReporter) - { - THROW_HR(E_INVALIDARG); - } - - // If no weights are provided, default to percent. - if (m_progressWeights.empty()) - { - m_progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Percent, 1.0)); - } - - // Calculate the total weight. - double totalWeight = 0; - for (const auto& weight : m_progressWeights) - { - if (weight.first != AppInstaller::ProgressType::None) - { - totalWeight += weight.second; - } - } - - // If the total weight is greater than 1, throw an exception. - if (totalWeight != 1.0) - { - THROW_HR(E_INVALIDARG); - } - } - - void PreIndexedPackageCatalogProgressSink::OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) - { - if (maximum == 0 || type == AppInstaller::ProgressType::None) - { - return; - } - - double progress = static_cast(current) / maximum; - m_progressValues[type] = progress; - - double totalProgress = 0.0; - double totalWeight = 0.0; - - // Calculate the total progress. - for (const auto& [progressType, weight] : m_progressWeights) - { - double progressValue = m_progressValues[progressType]; - - // [NOTE:] Sequential execution assumption & Handling incomplete progress reports : - // This progress calculation assumes that each operation is executed sequentially, meaning the download must be complete before - // the installation begins.If the download fails, the installation will not proceed.However, there may be cases where the previous - // operation completes successfully, but its onprogress callback does not report 100% completion(e.g., the last progress report for - // the download was at 90%, but the download is complete, and the installation has started).This can result in the total progress not - // reaching 100% after the last operation completes due to the gap in the previous operation's progress report.To handle this, consider - // the progress for the last operation as complete by assigning its full weight while computing progress for the following operation. - // For example, while computing progress for the installation, consider the download operation complete even if it did not report progress - // exactly at 100%. - if (progressValue != 0) - { - totalProgress = totalWeight; - } - - // Adjust the total progress value based on the weight. - totalWeight += weight; - totalProgress += progressValue * weight; - } - - m_progressReporter(totalProgress * 100); - } - - void PreIndexedPackageCatalogProgressSink::SetProgressMessage(std::string_view /*message*/) - { - } - - void PreIndexedPackageCatalogProgressSink::BeginProgress() - { - m_progressReporter(0); - } - - void PreIndexedPackageCatalogProgressSink::EndProgress(bool /*hideProgressWhenDone*/) - { - m_progressReporter(100); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "PackageCatalogProgress.h" +#include "AppInstallerStrings.h" +#include "Microsoft/PredefinedInstalledSourceFactory.h" + +using namespace AppInstaller; +using namespace AppInstaller::Repository; + +namespace winrt::Microsoft::Management::Deployment +{ + namespace ProgressSinkFactory + { + std::shared_ptr CreatePackageCatalogProgressSink(std::string sourceType, std::function progressReporter, bool removeOperation) + { + if (sourceType.empty() + || Utility::CaseInsensitiveEquals( Repository::Microsoft::PredefinedInstalledSourceFactory::Type(), sourceType)) + { + std::vector> progressWeights; + + // There is no download operation for remove operation, so use only percentage based progress to account for uninstall. + if (removeOperation) + { + // it is percentage based progress. + progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Percent, 1.0)); + } + else + { + // Add/Update operation has two progress types: + // 1. Bytes for downloading index and + // 2. Percent for index installation. + progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Bytes, 0.7)); + progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Percent, 0.3)); + } + + return std::make_shared(progressWeights, progressReporter); + } + else + { + return std::make_shared(progressReporter); + } + } + } + + CompletionOnlyProgressSink::CompletionOnlyProgressSink(std::function progressReporter) : + m_progressReporter(progressReporter) + { + if (!m_progressReporter) + { + THROW_HR(E_INVALIDARG); + } + } + + void CompletionOnlyProgressSink::OnProgress(uint64_t /*current*/, uint64_t /*maximum*/, AppInstaller::ProgressType /*type*/) + { + } + + void CompletionOnlyProgressSink::SetProgressMessage(std::string_view /*message*/) + { + } + + void CompletionOnlyProgressSink::BeginProgress() + { + m_progressReporter(0); + } + + void CompletionOnlyProgressSink::EndProgress(bool /*hideProgressWhenDone*/) + { + m_progressReporter(100); + } + + PreIndexedPackageCatalogProgressSink::PreIndexedPackageCatalogProgressSink(std::vector> progressWeights, std::function progressReporter) : + m_progressWeights(progressWeights), m_progressReporter(progressReporter) + { + if (!m_progressReporter) + { + THROW_HR(E_INVALIDARG); + } + + // If no weights are provided, default to percent. + if (m_progressWeights.empty()) + { + m_progressWeights.push_back(std::make_pair(AppInstaller::ProgressType::Percent, 1.0)); + } + + // Calculate the total weight. + double totalWeight = 0; + for (const auto& weight : m_progressWeights) + { + if (weight.first != AppInstaller::ProgressType::None) + { + totalWeight += weight.second; + } + } + + // If the total weight is greater than 1, throw an exception. + if (totalWeight != 1.0) + { + THROW_HR(E_INVALIDARG); + } + } + + void PreIndexedPackageCatalogProgressSink::OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) + { + if (maximum == 0 || type == AppInstaller::ProgressType::None) + { + return; + } + + double progress = static_cast(current) / maximum; + m_progressValues[type] = progress; + + double totalProgress = 0.0; + double totalWeight = 0.0; + + // Calculate the total progress. + for (const auto& [progressType, weight] : m_progressWeights) + { + double progressValue = m_progressValues[progressType]; + + // [NOTE:] Sequential execution assumption & Handling incomplete progress reports : + // This progress calculation assumes that each operation is executed sequentially, meaning the download must be complete before + // the installation begins.If the download fails, the installation will not proceed.However, there may be cases where the previous + // operation completes successfully, but its onprogress callback does not report 100% completion(e.g., the last progress report for + // the download was at 90%, but the download is complete, and the installation has started).This can result in the total progress not + // reaching 100% after the last operation completes due to the gap in the previous operation's progress report.To handle this, consider + // the progress for the last operation as complete by assigning its full weight while computing progress for the following operation. + // For example, while computing progress for the installation, consider the download operation complete even if it did not report progress + // exactly at 100%. + if (progressValue != 0) + { + totalProgress = totalWeight; + } + + // Adjust the total progress value based on the weight. + totalWeight += weight; + totalProgress += progressValue * weight; + } + + m_progressReporter(totalProgress * 100); + } + + void PreIndexedPackageCatalogProgressSink::SetProgressMessage(std::string_view /*message*/) + { + } + + void PreIndexedPackageCatalogProgressSink::BeginProgress() + { + m_progressReporter(0); + } + + void PreIndexedPackageCatalogProgressSink::EndProgress(bool /*hideProgressWhenDone*/) + { + m_progressReporter(100); + } +} diff --git a/src/Microsoft.Management.Deployment/PackageCatalogProgress.h b/src/Microsoft.Management.Deployment/PackageCatalogProgress.h index 4f175d054e..7b5e51703b 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogProgress.h +++ b/src/Microsoft.Management.Deployment/PackageCatalogProgress.h @@ -1,73 +1,73 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "AppInstallerProgress.h" -#include -#include - -namespace winrt::Microsoft::Management::Deployment -{ - namespace ProgressSinkFactory - { - /// - /// Creates a progress sink for package catalog operations based on sourceType. - /// - /// sourceType. - /// callback function that reports progress to caller. - /// Default value is false. Identifies if the operation is a PackageCatalog removal and requests the ProgressSink. - /// IProgressSink. - std::shared_ptr CreatePackageCatalogProgressSink(std::string sourceType, std::function progressReporter, bool removeOperation = false); - } - - /// - /// Progress sink that only reports start and completion to caller. - /// - struct CompletionOnlyProgressSink : AppInstaller::IProgressSink - { - /// - /// Constructor. - /// - /// callback that reports progress to caller. - CompletionOnlyProgressSink(std::function progressReporter); - - void OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) override; - void SetProgressMessage(std::string_view message) override; - void BeginProgress() override; - void EndProgress(bool hideProgressWhenDone) override; - - private: - std::function m_progressReporter; - }; - - /// - /// Progress sink for pre-indexed package catalog operations. - /// capable of reporting progress for download and installation of index. - /// Add/update operation has two progress types: Bytes for downloading index and Percent for index installation. - /// Remove operation has only percentage based progress. - /// - struct PreIndexedPackageCatalogProgressSink : AppInstaller::IProgressSink - { - /// - /// Constructor. - /// - /// ProgressType weight map. - /// Callback function that reports progress to caller. - PreIndexedPackageCatalogProgressSink(std::vector> progressWeights, std::function progressReporter); - - /// - /// Reports combined progress to caller when configured for multiple progress types. - /// - /// The current progress value. - /// The maximum progress value. - /// ProgressType for which progress is applicable. - void OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) override; - void SetProgressMessage(std::string_view message) override; - void BeginProgress() override; - void EndProgress(bool hideProgressWhenDone) override; - - private: - std::vector> m_progressWeights; - std::function m_progressReporter; - std::unordered_map m_progressValues; - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "AppInstallerProgress.h" +#include +#include + +namespace winrt::Microsoft::Management::Deployment +{ + namespace ProgressSinkFactory + { + /// + /// Creates a progress sink for package catalog operations based on sourceType. + /// + /// sourceType. + /// callback function that reports progress to caller. + /// Default value is false. Identifies if the operation is a PackageCatalog removal and requests the ProgressSink. + /// IProgressSink. + std::shared_ptr CreatePackageCatalogProgressSink(std::string sourceType, std::function progressReporter, bool removeOperation = false); + } + + /// + /// Progress sink that only reports start and completion to caller. + /// + struct CompletionOnlyProgressSink : AppInstaller::IProgressSink + { + /// + /// Constructor. + /// + /// callback that reports progress to caller. + CompletionOnlyProgressSink(std::function progressReporter); + + void OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) override; + void SetProgressMessage(std::string_view message) override; + void BeginProgress() override; + void EndProgress(bool hideProgressWhenDone) override; + + private: + std::function m_progressReporter; + }; + + /// + /// Progress sink for pre-indexed package catalog operations. + /// capable of reporting progress for download and installation of index. + /// Add/update operation has two progress types: Bytes for downloading index and Percent for index installation. + /// Remove operation has only percentage based progress. + /// + struct PreIndexedPackageCatalogProgressSink : AppInstaller::IProgressSink + { + /// + /// Constructor. + /// + /// ProgressType weight map. + /// Callback function that reports progress to caller. + PreIndexedPackageCatalogProgressSink(std::vector> progressWeights, std::function progressReporter); + + /// + /// Reports combined progress to caller when configured for multiple progress types. + /// + /// The current progress value. + /// The maximum progress value. + /// ProgressType for which progress is applicable. + void OnProgress(uint64_t current, uint64_t maximum, AppInstaller::ProgressType type) override; + void SetProgressMessage(std::string_view message) override; + void BeginProgress() override; + void EndProgress(bool hideProgressWhenDone) override; + + private: + std::vector> m_progressWeights; + std::function m_progressReporter; + std::unordered_map m_progressValues; + }; +} diff --git a/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp b/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp index 12ef4f333f..44fa2ad604 100644 --- a/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp +++ b/src/Microsoft.Management.Deployment/PackageCatalogReference.cpp @@ -1,396 +1,396 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include "PackageCatalogReference.h" -#include "PackageCatalogReference.g.cpp" -#include "PackageCatalogInfo.h" -#include "PackageCatalog.h" -#include "PackageCatalogConnectionValidationEventArgs.h" -#include "SourceAgreement.h" -#include "ConnectResult.h" -#include "AuthenticationInfo.h" -#include "Workflows/WorkflowBase.h" -#include "Converters.h" -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - namespace - { - winrt::Microsoft::Management::Deployment::RefreshPackageCatalogResult GetRefreshPackageCatalogResult(winrt::hresult terminationStatus) - { - winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); - auto updateResult = winrt::make_self>(); - updateResult->Initialize(status, terminationStatus); - return *updateResult; - } - - // Returns true if the ConnectionValidationHandler may be set for the given source. - // Returns false if the BypassCertificatePinningForMicrosoftStore policy is explicitly - // disabled and the source is the well-known MicrosoftStore catalog. - bool IsConnectionValidationHandlerEnabledForSource(const ::AppInstaller::Repository::Source& source) - { - using namespace AppInstaller::Settings; - if (source.IsWellKnownSource(::AppInstaller::Repository::WellKnownSource::MicrosoftStore)) - { - return GroupPolicies().GetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore) != PolicyState::Disabled; - } - - return true; - } - - std::function MakeServerCertificateValidationCallback( - winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler const& handler) - { - if (!handler) - { - return {}; - } - return [handler](PCCERT_CONTEXT certContext) -> bool - { - auto certBytes = winrt::array_view{ certContext->pbCertEncoded, certContext->pbCertEncoded + certContext->cbCertEncoded }; - auto buffer = winrt::Windows::Security::Cryptography::CryptographicBuffer::CreateFromByteArray(certBytes); - winrt::Windows::Security::Cryptography::Certificates::Certificate cert{ buffer }; - auto args = winrt::make_self>(); - args->Initialize(cert); - auto handlerResult = handler(*args); - AICLI_LOG(Repo, Info, << "PackageCatalogConnectionValidationHandler returned: " << handlerResult); - return handlerResult == winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationResult::Ok; - }; - } - } - - void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::PackageCatalogInfo packageCatalogInfo, ::AppInstaller::Repository::Source sourceReference) - { - m_info = packageCatalogInfo; - m_sourceReference = std::move(sourceReference); - m_packageCatalogBackgroundUpdateInterval = ::AppInstaller::Settings::User().Get<::AppInstaller::Settings::Setting::AutoUpdateTimeInMinutes>(); - - if (IsBackgroundProcessForPolicy()) - { - // Delay the default update interval for these background processes - static constexpr winrt::Windows::Foundation::TimeSpan s_PackageCatalogUpdateIntervalDelay_Base = 168h; //1 week - - // Add a bit of randomness to the default interval time - std::default_random_engine randomEngine(std::random_device{}()); - std::uniform_int_distribution distribution(0, 604800); - - m_packageCatalogBackgroundUpdateInterval = s_PackageCatalogUpdateIntervalDelay_Base + std::chrono::seconds(distribution(randomEngine)); - - // Prevent any update / data processing by default for these background processes for now - m_installedPackageInformationOnly = m_sourceReference.IsWellKnownSource(AppInstaller::Repository::WellKnownSource::WinGet) || - m_sourceReference.IsWellKnownSource(AppInstaller::Repository::WellKnownSource::WinGetFont); - } - } - - void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions options) - { - m_compositePackageCatalogOptions = options; - } - - bool PackageCatalogReference::IsComposite() - { - return (m_compositePackageCatalogOptions != nullptr); - } - winrt::Microsoft::Management::Deployment::PackageCatalogInfo PackageCatalogReference::Info() - { - return m_info; - } - winrt::Windows::Foundation::IAsyncOperation PackageCatalogReference::ConnectAsync() - { - co_return Connect(); - } - winrt::Microsoft::Management::Deployment::ConnectResult GetConnectCatalogErrorResult(hresult hr) - { - auto connectResult = winrt::make_self>(); - connectResult->Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus::CatalogError, nullptr, hr); - return *connectResult; - } - winrt::Microsoft::Management::Deployment::ConnectResult GetConnectSourceAgreementsNotAcceptedErrorResult() - { - auto connectResult = winrt::make_self>(); - connectResult->Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus::SourceAgreementsNotAccepted, nullptr, APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED); - return *connectResult; - } - winrt::Microsoft::Management::Deployment::ConnectResult PackageCatalogReference::Connect() try - { - HRESULT hr = EnsureComCallerHasCapability(Capability::PackageQuery); - if (FAILED(hr)) - { - // TODO: When more error codes are added, this should go back as something other than CatalogError. - return GetConnectCatalogErrorResult(hr); - } - - std::string callerName = GetCallerName(); - - ::AppInstaller::ProgressCallback progress; - ::AppInstaller::Repository::Source source; - if (m_compositePackageCatalogOptions) - { - std::vector<::AppInstaller::Repository::Source> remoteSources; - - for (uint32_t i = 0; i < m_compositePackageCatalogOptions.Catalogs().Size(); ++i) - { - auto catalog = m_compositePackageCatalogOptions.Catalogs().GetAt(i); - if (!catalog.AcceptSourceAgreements() && catalog.SourceAgreements().Size() != 0) - { - return GetConnectSourceAgreementsNotAcceptedErrorResult(); - } - - winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference* catalogImpl = get_self(catalog); - auto copy = catalogImpl->m_sourceReference; - copy.SetCaller(callerName); - copy.SetBackgroundUpdateInterval(catalog.PackageCatalogBackgroundUpdateInterval()); - copy.InstalledPackageInformationOnly(catalog.InstalledPackageInformationOnly()); - auto validationCallback = MakeServerCertificateValidationCallback(catalogImpl->m_connectionValidationHandler); - if (validationCallback) - { - copy.SetServerCertificateValidationCallback(std::move(validationCallback)); - } - if (catalog.AuthenticationInfo().AuthenticationType() != winrt::Microsoft::Management::Deployment::AuthenticationType::None) - { - copy.SetAuthenticationArguments(GetAuthenticationArguments(catalog.AuthenticationArguments())); - } - copy.Open(progress); - remoteSources.emplace_back(std::move(copy)); - } - - // Create the aggregated source. - source = ::AppInstaller::Repository::Source{ remoteSources }; - - // Create composite with installed source if needed. - ::AppInstaller::Repository::CompositeSearchBehavior searchBehavior = GetRepositoryCompositeSearchBehavior(m_compositePackageCatalogOptions.CompositeSearchBehavior()); - - // Check if search behavior indicates that the caller does not want to do local correlation. - if (m_compositePackageCatalogOptions.CompositeSearchBehavior() != Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs) - { - ::AppInstaller::Repository::Source installedSource; - auto manifestInstalledScope = GetManifestScope(m_compositePackageCatalogOptions.InstalledScope()).first; - if (manifestInstalledScope == ::AppInstaller::Manifest::ScopeEnum::User) - { - installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::InstalledUser }; - } - else if (manifestInstalledScope == ::AppInstaller::Manifest::ScopeEnum::Machine) - { - installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::InstalledMachine }; - } - else - { - installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; - } - - installedSource.Open(progress); - source = ::AppInstaller::Repository::Source{ installedSource, source, searchBehavior }; - } - } - else - { - if (!AcceptSourceAgreements() && SourceAgreements().Size() != 0) - { - return GetConnectSourceAgreementsNotAcceptedErrorResult(); - } - - source = m_sourceReference; - source.SetCaller(callerName); - source.SetBackgroundUpdateInterval(PackageCatalogBackgroundUpdateInterval()); - source.InstalledPackageInformationOnly(m_installedPackageInformationOnly); - auto validationCallback = MakeServerCertificateValidationCallback(m_connectionValidationHandler); - if (validationCallback) - { - source.SetServerCertificateValidationCallback(std::move(validationCallback)); - } - if (AuthenticationInfo().AuthenticationType() != winrt::Microsoft::Management::Deployment::AuthenticationType::None) - { - source.SetAuthenticationArguments(GetAuthenticationArguments(m_authenticationArguments)); - } - source.Open(progress); - } - - if (!source) - { - // We call `Open` on each individual source above, meaning that they should throw any error that occurs. - // If the source is still not open at this point it is a bug. - return GetConnectCatalogErrorResult(E_UNEXPECTED); - } - - // Have to make another package catalog info because source->GetDetails has more fields than m_info does. - // Specifically, Rest sources do not have the Ids filled in m_info since they only get the id from the rest server after being Opened. - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(source.GetDetails()); - auto connectResult = winrt::make_self>(); - auto packageCatalog = winrt::make_self>(); - packageCatalog->Initialize(*packageCatalogInfo, source, (m_compositePackageCatalogOptions != nullptr)); - connectResult->Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus::Ok, *packageCatalog, S_OK); - return *connectResult; - } - catch (...) - { - return GetConnectCatalogErrorResult(AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception())); - } - - winrt::Windows::Foundation::Collections::IVectorView PackageCatalogReference::SourceAgreements() - { - std::call_once(m_sourceAgreementsOnceFlag, - [&]() - { - if (!IsComposite()) - { - for (auto const& agreement : m_sourceReference.GetInformation().SourceAgreements) - { - auto sourceAgreement = winrt::make_self>(); - sourceAgreement->Initialize(agreement); - m_sourceAgreements.Append(*sourceAgreement); - } - } - }); - return m_sourceAgreements.GetView(); - } - hstring PackageCatalogReference::AdditionalPackageCatalogArguments() - { - if (!IsComposite()) - { - if (m_additionalPackageCatalogArguments.has_value()) - { - return winrt::to_hstring(m_additionalPackageCatalogArguments.value()); - } - } - - return {}; - } - void PackageCatalogReference::AdditionalPackageCatalogArguments(hstring const& value) - { - if (IsComposite()) - { - // Can't set AdditionalPackageCatalogArguments on a composite. Callers should set it on each non-composite PackageCatalogReference in the composite. - throw winrt::hresult_illegal_state_change(); - } - else - { - m_additionalPackageCatalogArguments = ::AppInstaller::Utility::ConvertToUTF8(value); - m_sourceReference.SetCustomHeader(m_additionalPackageCatalogArguments); - } - } - void PackageCatalogReference::AcceptSourceAgreements(bool value) - { - if (IsComposite()) - { - // Can't set AcceptSourceAgreements on a composite. Callers should set it on each non-composite PackageCatalogReference in the composite. - throw winrt::hresult_illegal_state_change(); - } - m_acceptSourceAgreements = value; - } - bool PackageCatalogReference::AcceptSourceAgreements() - { - return m_acceptSourceAgreements; - } - - void PackageCatalogReference::PackageCatalogBackgroundUpdateInterval(winrt::Windows::Foundation::TimeSpan const& value) - { - if (IsComposite()) - { - // Can't set PackageCatalogBackgroundUpdateInterval on a composite. Callers should set it on each non-composite PackageCatalogReference in the composite. - throw winrt::hresult_illegal_state_change(); - } - m_packageCatalogBackgroundUpdateInterval = value; - } - winrt::Windows::Foundation::TimeSpan PackageCatalogReference::PackageCatalogBackgroundUpdateInterval() - { - return m_packageCatalogBackgroundUpdateInterval; - } - - bool PackageCatalogReference::InstalledPackageInformationOnly() - { - return m_installedPackageInformationOnly; - } - - void PackageCatalogReference::InstalledPackageInformationOnly(bool value) - { - if (IsComposite()) - { - throw winrt::hresult_illegal_state_change(); - } - - m_installedPackageInformationOnly = value; - } - winrt::Microsoft::Management::Deployment::AuthenticationArguments PackageCatalogReference::AuthenticationArguments() - { - return m_authenticationArguments; - } - void PackageCatalogReference::AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value) - { - if (IsComposite()) - { - throw winrt::hresult_illegal_state_change(); - } - - m_authenticationArguments = value; - } - winrt::Microsoft::Management::Deployment::AuthenticationInfo PackageCatalogReference::AuthenticationInfo() - { - std::call_once(m_authenticationInfoOnceFlag, - [&]() - { - if (!IsComposite()) - { - auto authenticationInfo = winrt::make_self>(); - authenticationInfo->Initialize(m_sourceReference.GetInformation().Authentication); - m_authenticationInfo = *authenticationInfo; - } - }); - return m_authenticationInfo; - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageCatalogReference::RefreshPackageCatalogAsync() - { - HRESULT terminationHR = S_OK; - try { - // Check for permissions and get caller info for telemetry - THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - - auto strong_this = get_strong(); - auto report_progress{ co_await winrt::get_progress_token() }; - co_await winrt::resume_background(); - - auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(this->m_sourceReference.GetDetails().Type, report_progress); - - packageCatalogProgressSink->BeginProgress(); - ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); - this->m_sourceReference.Update(progress); - packageCatalogProgressSink->EndProgress(false); - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - co_return GetRefreshPackageCatalogResult(terminationHR); - } - - winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler PackageCatalogReference::ConnectionValidationHandler() - { - return m_connectionValidationHandler; - } - - void PackageCatalogReference::ConnectionValidationHandler(winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler const& value) - { - THROW_HR_IF(E_ACCESSDENIED, !IsInProcCaller()); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !IsConnectionValidationHandlerEnabledForSource(m_sourceReference)); - - m_connectionValidationHandler = value; - } - - bool PackageCatalogReference::IsConnectionValidationHandlerEnabled() - { - return IsInProcCaller() && IsConnectionValidationHandlerEnabledForSource(m_sourceReference); - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "PackageCatalogReference.h" +#include "PackageCatalogReference.g.cpp" +#include "PackageCatalogInfo.h" +#include "PackageCatalog.h" +#include "PackageCatalogConnectionValidationEventArgs.h" +#include "SourceAgreement.h" +#include "ConnectResult.h" +#include "AuthenticationInfo.h" +#include "Workflows/WorkflowBase.h" +#include "Converters.h" +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + namespace + { + winrt::Microsoft::Management::Deployment::RefreshPackageCatalogResult GetRefreshPackageCatalogResult(winrt::hresult terminationStatus) + { + winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); + auto updateResult = winrt::make_self>(); + updateResult->Initialize(status, terminationStatus); + return *updateResult; + } + + // Returns true if the ConnectionValidationHandler may be set for the given source. + // Returns false if the BypassCertificatePinningForMicrosoftStore policy is explicitly + // disabled and the source is the well-known MicrosoftStore catalog. + bool IsConnectionValidationHandlerEnabledForSource(const ::AppInstaller::Repository::Source& source) + { + using namespace AppInstaller::Settings; + if (source.IsWellKnownSource(::AppInstaller::Repository::WellKnownSource::MicrosoftStore)) + { + return GroupPolicies().GetState(TogglePolicy::Policy::BypassCertificatePinningForMicrosoftStore) != PolicyState::Disabled; + } + + return true; + } + + std::function MakeServerCertificateValidationCallback( + winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler const& handler) + { + if (!handler) + { + return {}; + } + return [handler](PCCERT_CONTEXT certContext) -> bool + { + auto certBytes = winrt::array_view{ certContext->pbCertEncoded, certContext->pbCertEncoded + certContext->cbCertEncoded }; + auto buffer = winrt::Windows::Security::Cryptography::CryptographicBuffer::CreateFromByteArray(certBytes); + winrt::Windows::Security::Cryptography::Certificates::Certificate cert{ buffer }; + auto args = winrt::make_self>(); + args->Initialize(cert); + auto handlerResult = handler(*args); + AICLI_LOG(Repo, Info, << "PackageCatalogConnectionValidationHandler returned: " << handlerResult); + return handlerResult == winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationResult::Ok; + }; + } + } + + void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::PackageCatalogInfo packageCatalogInfo, ::AppInstaller::Repository::Source sourceReference) + { + m_info = packageCatalogInfo; + m_sourceReference = std::move(sourceReference); + m_packageCatalogBackgroundUpdateInterval = ::AppInstaller::Settings::User().Get<::AppInstaller::Settings::Setting::AutoUpdateTimeInMinutes>(); + + if (IsBackgroundProcessForPolicy()) + { + // Delay the default update interval for these background processes + static constexpr winrt::Windows::Foundation::TimeSpan s_PackageCatalogUpdateIntervalDelay_Base = 168h; //1 week + + // Add a bit of randomness to the default interval time + std::default_random_engine randomEngine(std::random_device{}()); + std::uniform_int_distribution distribution(0, 604800); + + m_packageCatalogBackgroundUpdateInterval = s_PackageCatalogUpdateIntervalDelay_Base + std::chrono::seconds(distribution(randomEngine)); + + // Prevent any update / data processing by default for these background processes for now + m_installedPackageInformationOnly = m_sourceReference.IsWellKnownSource(AppInstaller::Repository::WellKnownSource::WinGet) || + m_sourceReference.IsWellKnownSource(AppInstaller::Repository::WellKnownSource::WinGetFont); + } + } + + void PackageCatalogReference::Initialize(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions options) + { + m_compositePackageCatalogOptions = options; + } + + bool PackageCatalogReference::IsComposite() + { + return (m_compositePackageCatalogOptions != nullptr); + } + winrt::Microsoft::Management::Deployment::PackageCatalogInfo PackageCatalogReference::Info() + { + return m_info; + } + winrt::Windows::Foundation::IAsyncOperation PackageCatalogReference::ConnectAsync() + { + co_return Connect(); + } + winrt::Microsoft::Management::Deployment::ConnectResult GetConnectCatalogErrorResult(hresult hr) + { + auto connectResult = winrt::make_self>(); + connectResult->Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus::CatalogError, nullptr, hr); + return *connectResult; + } + winrt::Microsoft::Management::Deployment::ConnectResult GetConnectSourceAgreementsNotAcceptedErrorResult() + { + auto connectResult = winrt::make_self>(); + connectResult->Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus::SourceAgreementsNotAccepted, nullptr, APPINSTALLER_CLI_ERROR_SOURCE_AGREEMENTS_NOT_ACCEPTED); + return *connectResult; + } + winrt::Microsoft::Management::Deployment::ConnectResult PackageCatalogReference::Connect() try + { + HRESULT hr = EnsureComCallerHasCapability(Capability::PackageQuery); + if (FAILED(hr)) + { + // TODO: When more error codes are added, this should go back as something other than CatalogError. + return GetConnectCatalogErrorResult(hr); + } + + std::string callerName = GetCallerName(); + + ::AppInstaller::ProgressCallback progress; + ::AppInstaller::Repository::Source source; + if (m_compositePackageCatalogOptions) + { + std::vector<::AppInstaller::Repository::Source> remoteSources; + + for (uint32_t i = 0; i < m_compositePackageCatalogOptions.Catalogs().Size(); ++i) + { + auto catalog = m_compositePackageCatalogOptions.Catalogs().GetAt(i); + if (!catalog.AcceptSourceAgreements() && catalog.SourceAgreements().Size() != 0) + { + return GetConnectSourceAgreementsNotAcceptedErrorResult(); + } + + winrt::Microsoft::Management::Deployment::implementation::PackageCatalogReference* catalogImpl = get_self(catalog); + auto copy = catalogImpl->m_sourceReference; + copy.SetCaller(callerName); + copy.SetBackgroundUpdateInterval(catalog.PackageCatalogBackgroundUpdateInterval()); + copy.InstalledPackageInformationOnly(catalog.InstalledPackageInformationOnly()); + auto validationCallback = MakeServerCertificateValidationCallback(catalogImpl->m_connectionValidationHandler); + if (validationCallback) + { + copy.SetServerCertificateValidationCallback(std::move(validationCallback)); + } + if (catalog.AuthenticationInfo().AuthenticationType() != winrt::Microsoft::Management::Deployment::AuthenticationType::None) + { + copy.SetAuthenticationArguments(GetAuthenticationArguments(catalog.AuthenticationArguments())); + } + copy.Open(progress); + remoteSources.emplace_back(std::move(copy)); + } + + // Create the aggregated source. + source = ::AppInstaller::Repository::Source{ remoteSources }; + + // Create composite with installed source if needed. + ::AppInstaller::Repository::CompositeSearchBehavior searchBehavior = GetRepositoryCompositeSearchBehavior(m_compositePackageCatalogOptions.CompositeSearchBehavior()); + + // Check if search behavior indicates that the caller does not want to do local correlation. + if (m_compositePackageCatalogOptions.CompositeSearchBehavior() != Microsoft::Management::Deployment::CompositeSearchBehavior::RemotePackagesFromRemoteCatalogs) + { + ::AppInstaller::Repository::Source installedSource; + auto manifestInstalledScope = GetManifestScope(m_compositePackageCatalogOptions.InstalledScope()).first; + if (manifestInstalledScope == ::AppInstaller::Manifest::ScopeEnum::User) + { + installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::InstalledUser }; + } + else if (manifestInstalledScope == ::AppInstaller::Manifest::ScopeEnum::Machine) + { + installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::InstalledMachine }; + } + else + { + installedSource = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; + } + + installedSource.Open(progress); + source = ::AppInstaller::Repository::Source{ installedSource, source, searchBehavior }; + } + } + else + { + if (!AcceptSourceAgreements() && SourceAgreements().Size() != 0) + { + return GetConnectSourceAgreementsNotAcceptedErrorResult(); + } + + source = m_sourceReference; + source.SetCaller(callerName); + source.SetBackgroundUpdateInterval(PackageCatalogBackgroundUpdateInterval()); + source.InstalledPackageInformationOnly(m_installedPackageInformationOnly); + auto validationCallback = MakeServerCertificateValidationCallback(m_connectionValidationHandler); + if (validationCallback) + { + source.SetServerCertificateValidationCallback(std::move(validationCallback)); + } + if (AuthenticationInfo().AuthenticationType() != winrt::Microsoft::Management::Deployment::AuthenticationType::None) + { + source.SetAuthenticationArguments(GetAuthenticationArguments(m_authenticationArguments)); + } + source.Open(progress); + } + + if (!source) + { + // We call `Open` on each individual source above, meaning that they should throw any error that occurs. + // If the source is still not open at this point it is a bug. + return GetConnectCatalogErrorResult(E_UNEXPECTED); + } + + // Have to make another package catalog info because source->GetDetails has more fields than m_info does. + // Specifically, Rest sources do not have the Ids filled in m_info since they only get the id from the rest server after being Opened. + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(source.GetDetails()); + auto connectResult = winrt::make_self>(); + auto packageCatalog = winrt::make_self>(); + packageCatalog->Initialize(*packageCatalogInfo, source, (m_compositePackageCatalogOptions != nullptr)); + connectResult->Initialize(winrt::Microsoft::Management::Deployment::ConnectResultStatus::Ok, *packageCatalog, S_OK); + return *connectResult; + } + catch (...) + { + return GetConnectCatalogErrorResult(AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception())); + } + + winrt::Windows::Foundation::Collections::IVectorView PackageCatalogReference::SourceAgreements() + { + std::call_once(m_sourceAgreementsOnceFlag, + [&]() + { + if (!IsComposite()) + { + for (auto const& agreement : m_sourceReference.GetInformation().SourceAgreements) + { + auto sourceAgreement = winrt::make_self>(); + sourceAgreement->Initialize(agreement); + m_sourceAgreements.Append(*sourceAgreement); + } + } + }); + return m_sourceAgreements.GetView(); + } + hstring PackageCatalogReference::AdditionalPackageCatalogArguments() + { + if (!IsComposite()) + { + if (m_additionalPackageCatalogArguments.has_value()) + { + return winrt::to_hstring(m_additionalPackageCatalogArguments.value()); + } + } + + return {}; + } + void PackageCatalogReference::AdditionalPackageCatalogArguments(hstring const& value) + { + if (IsComposite()) + { + // Can't set AdditionalPackageCatalogArguments on a composite. Callers should set it on each non-composite PackageCatalogReference in the composite. + throw winrt::hresult_illegal_state_change(); + } + else + { + m_additionalPackageCatalogArguments = ::AppInstaller::Utility::ConvertToUTF8(value); + m_sourceReference.SetCustomHeader(m_additionalPackageCatalogArguments); + } + } + void PackageCatalogReference::AcceptSourceAgreements(bool value) + { + if (IsComposite()) + { + // Can't set AcceptSourceAgreements on a composite. Callers should set it on each non-composite PackageCatalogReference in the composite. + throw winrt::hresult_illegal_state_change(); + } + m_acceptSourceAgreements = value; + } + bool PackageCatalogReference::AcceptSourceAgreements() + { + return m_acceptSourceAgreements; + } + + void PackageCatalogReference::PackageCatalogBackgroundUpdateInterval(winrt::Windows::Foundation::TimeSpan const& value) + { + if (IsComposite()) + { + // Can't set PackageCatalogBackgroundUpdateInterval on a composite. Callers should set it on each non-composite PackageCatalogReference in the composite. + throw winrt::hresult_illegal_state_change(); + } + m_packageCatalogBackgroundUpdateInterval = value; + } + winrt::Windows::Foundation::TimeSpan PackageCatalogReference::PackageCatalogBackgroundUpdateInterval() + { + return m_packageCatalogBackgroundUpdateInterval; + } + + bool PackageCatalogReference::InstalledPackageInformationOnly() + { + return m_installedPackageInformationOnly; + } + + void PackageCatalogReference::InstalledPackageInformationOnly(bool value) + { + if (IsComposite()) + { + throw winrt::hresult_illegal_state_change(); + } + + m_installedPackageInformationOnly = value; + } + winrt::Microsoft::Management::Deployment::AuthenticationArguments PackageCatalogReference::AuthenticationArguments() + { + return m_authenticationArguments; + } + void PackageCatalogReference::AuthenticationArguments(winrt::Microsoft::Management::Deployment::AuthenticationArguments const& value) + { + if (IsComposite()) + { + throw winrt::hresult_illegal_state_change(); + } + + m_authenticationArguments = value; + } + winrt::Microsoft::Management::Deployment::AuthenticationInfo PackageCatalogReference::AuthenticationInfo() + { + std::call_once(m_authenticationInfoOnceFlag, + [&]() + { + if (!IsComposite()) + { + auto authenticationInfo = winrt::make_self>(); + authenticationInfo->Initialize(m_sourceReference.GetInformation().Authentication); + m_authenticationInfo = *authenticationInfo; + } + }); + return m_authenticationInfo; + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageCatalogReference::RefreshPackageCatalogAsync() + { + HRESULT terminationHR = S_OK; + try { + // Check for permissions and get caller info for telemetry + THROW_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + + auto strong_this = get_strong(); + auto report_progress{ co_await winrt::get_progress_token() }; + co_await winrt::resume_background(); + + auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(this->m_sourceReference.GetDetails().Type, report_progress); + + packageCatalogProgressSink->BeginProgress(); + ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); + this->m_sourceReference.Update(progress); + packageCatalogProgressSink->EndProgress(false); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + co_return GetRefreshPackageCatalogResult(terminationHR); + } + + winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler PackageCatalogReference::ConnectionValidationHandler() + { + return m_connectionValidationHandler; + } + + void PackageCatalogReference::ConnectionValidationHandler(winrt::Microsoft::Management::Deployment::PackageCatalogConnectionValidationHandler const& value) + { + THROW_HR_IF(E_ACCESSDENIED, !IsInProcCaller()); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !IsConnectionValidationHandlerEnabledForSource(m_sourceReference)); + + m_connectionValidationHandler = value; + } + + bool PackageCatalogReference::IsConnectionValidationHandlerEnabled() + { + return IsInProcCaller() && IsConnectionValidationHandlerEnabledForSource(m_sourceReference); + } +} diff --git a/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp b/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp index f6e53ecd04..ad13947b4b 100644 --- a/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp +++ b/src/Microsoft.Management.Deployment/PackageInstallerInfo.cpp @@ -2,7 +2,7 @@ // 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 @@ -13,26 +13,26 @@ namespace winrt::Microsoft::Management::Deployment::implementation { m_manifestInstaller = manifestInstaller; } - winrt::Microsoft::Management::Deployment::PackageInstallerType PackageInstallerInfo::InstallerType() - { - return GetDeploymentInstallerType(m_manifestInstaller.BaseInstallerType); + winrt::Microsoft::Management::Deployment::PackageInstallerType PackageInstallerInfo::InstallerType() + { + return GetDeploymentInstallerType(m_manifestInstaller.BaseInstallerType); } - winrt::Microsoft::Management::Deployment::PackageInstallerType PackageInstallerInfo::NestedInstallerType() - { - return GetDeploymentInstallerType(m_manifestInstaller.NestedInstallerType); + winrt::Microsoft::Management::Deployment::PackageInstallerType PackageInstallerInfo::NestedInstallerType() + { + return GetDeploymentInstallerType(m_manifestInstaller.NestedInstallerType); } - winrt::Windows::System::ProcessorArchitecture PackageInstallerInfo::Architecture() - { - auto convertedArchitecture = GetWindowsSystemProcessorArchitecture(m_manifestInstaller.Arch); - return convertedArchitecture ? convertedArchitecture.value() : Windows::System::ProcessorArchitecture::Unknown; + winrt::Windows::System::ProcessorArchitecture PackageInstallerInfo::Architecture() + { + auto convertedArchitecture = GetWindowsSystemProcessorArchitecture(m_manifestInstaller.Arch); + return convertedArchitecture ? convertedArchitecture.value() : Windows::System::ProcessorArchitecture::Unknown; } - winrt::Microsoft::Management::Deployment::PackageInstallerScope PackageInstallerInfo::Scope() - { - return GetDeploymentInstallerScope(m_manifestInstaller.Scope); + winrt::Microsoft::Management::Deployment::PackageInstallerScope PackageInstallerInfo::Scope() + { + return GetDeploymentInstallerScope(m_manifestInstaller.Scope); } - hstring PackageInstallerInfo::Locale() - { - return winrt::to_hstring(m_manifestInstaller.Locale); + hstring PackageInstallerInfo::Locale() + { + return winrt::to_hstring(m_manifestInstaller.Locale); } winrt::Microsoft::Management::Deployment::ElevationRequirement PackageInstallerInfo::ElevationRequirement() { @@ -40,13 +40,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation } 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; - }); + 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 de09dd32bd..3240753855 100644 --- a/src/Microsoft.Management.Deployment/PackageInstallerInfo.h +++ b/src/Microsoft.Management.Deployment/PackageInstallerInfo.h @@ -20,13 +20,13 @@ namespace winrt::Microsoft::Management::Deployment::implementation winrt::Microsoft::Management::Deployment::PackageInstallerScope Scope(); hstring Locale(); // Contract 6.0 - winrt::Microsoft::Management::Deployment::ElevationRequirement ElevationRequirement(); - // Contract 12.0 + 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/PackageInstallerInstalledStatus.cpp b/src/Microsoft.Management.Deployment/PackageInstallerInstalledStatus.cpp index 5fb10472eb..af85ced8d0 100644 --- a/src/Microsoft.Management.Deployment/PackageInstallerInstalledStatus.cpp +++ b/src/Microsoft.Management.Deployment/PackageInstallerInstalledStatus.cpp @@ -27,12 +27,12 @@ namespace winrt::Microsoft::Management::Deployment::implementation m_installedStatus.Append(*status); } } - winrt::Microsoft::Management::Deployment::PackageInstallerInfo PackageInstallerInstalledStatus::InstallerInfo() - { - return m_installerInfo; + winrt::Microsoft::Management::Deployment::PackageInstallerInfo PackageInstallerInstalledStatus::InstallerInfo() + { + return m_installerInfo; } - winrt::Windows::Foundation::Collections::IVectorView PackageInstallerInstalledStatus::InstallerInstalledStatus() - { - return m_installedStatus.GetView(); + winrt::Windows::Foundation::Collections::IVectorView PackageInstallerInstalledStatus::InstallerInstalledStatus() + { + return m_installedStatus.GetView(); } } diff --git a/src/Microsoft.Management.Deployment/PackageManager.cpp b/src/Microsoft.Management.Deployment/PackageManager.cpp index 39d53771b5..6470aad0ed 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.cpp +++ b/src/Microsoft.Management.Deployment/PackageManager.cpp @@ -1,1492 +1,1492 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "Public/AppInstallerCLICore.h" -#include "Microsoft/PredefinedInstalledSourceFactory.h" -#include "Commands/RootCommand.h" -#include "ComContext.h" -#include "ExecutionContext.h" -#include "Workflows/WorkflowBase.h" -#include -#include -#include -#include "Commands/COMCommand.h" -#include -#include -#include -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "PackageManager.h" -#pragma warning( pop ) -#include "PackageManager.g.cpp" -#include "CatalogPackage.h" -#include "DownloadResult.h" -#include "InstallResult.h" -#include "UninstallResult.h" -#include "RepairResult.h" -#include "PackageCatalogInfo.h" -#include "PackageCatalogReference.h" -#include "PackageVersionInfo.h" -#include "PackageVersionId.h" -#include "AddPackageCatalogResult.h" -#include "RemovePackageCatalogResult.h" -#include "EditPackageCatalogResult.h" -#include "Converters.h" -#include "Helpers.h" -#include "ContextOrchestrator.h" -#include "AppInstallerRuntime.h" -#include -#include - -using namespace std::literals::chrono_literals; -using namespace ::AppInstaller::CLI; -using namespace ::AppInstaller::CLI::Execution; - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - namespace - { - void LogStartupIfApplicable() - { - static std::once_flag logStartupOnceFlag; - std::call_once(logStartupOnceFlag, - [&]() - { - ::AppInstaller::Logging::Telemetry().SetCaller(GetCallerName()); - ::AppInstaller::Logging::Telemetry().LogStartup(true); - }); - } - - winrt::Microsoft::Management::Deployment::AddPackageCatalogResult GetAddPackageCatalogResult(winrt::hresult terminationStatus) - { - winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); - auto addPackageCatalogResult = winrt::make_self>(); - addPackageCatalogResult->Initialize(status, terminationStatus); - return *addPackageCatalogResult; - } - - void CheckForDuplicateSource(const std::string& name, const std::string& type, const std::string& sourceUri) - { - auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); - - std::string sourceType = type; - - // [NOTE:] If the source type is not specified, the default source type will be used for validation.In cases where the source type is empty, - // it remains unassigned until the add operation, at which point it is assigned.Without this default assignment, an empty string could be - // compared to the default type, potentially allowing different source names with the same URI to be seen as unique. - // To avoid this, assign the default source type prior to comparison. - if (sourceType.empty()) - { - // This method of obtaining the default source type is slightly expensive as it requires creating a SourceFactory object - // and fetching the type name.Nonetheless, it future-proofs the code against any changes in the SourceFactory's default type. - sourceType = ::AppInstaller::Repository::Source::GetDefaultSourceType(); - } - - for (const auto& source : sourceList) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, ::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)); - - bool sourceUriAlreadyExists = !source.Arg.empty() && source.Arg == sourceUri && source.Type == sourceType; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS, sourceUriAlreadyExists); - } - } - - ::AppInstaller::Repository::Source CreateSourceFromOptions(const winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions& options) - { - std::string name = winrt::to_string(options.Name()); - std::string type = winrt::to_string(options.Type()); - std::string sourceUri = winrt::to_string(options.SourceUri()); - - AppInstaller::Repository::SourceTrustLevel trustLevel = AppInstaller::Repository::SourceTrustLevel::None; - if (options.TrustLevel() == winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel::Trusted) - { - trustLevel = AppInstaller::Repository::SourceTrustLevel::Trusted; - } - - CheckForDuplicateSource(name, type, sourceUri); - - ::AppInstaller::Repository::SourceEdit additionalProperties; - additionalProperties.Explicit = options.Explicit(); - additionalProperties.Priority = options.Priority(); - - ::AppInstaller::Repository::Source source = ::AppInstaller::Repository::Source{ name, sourceUri, type, trustLevel, additionalProperties }; - - std::string customHeader = winrt::to_string(options.CustomHeader()); - if (!customHeader.empty()) - { - source.SetCustomHeader(customHeader); - } - - auto sourceInfo = source.GetInformation(); - - if (sourceInfo.Authentication.Type == ::AppInstaller::Authentication::AuthenticationType::Unknown) - { - THROW_HR(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); - } - - return source; - } - - winrt::Microsoft::Management::Deployment::RemovePackageCatalogResult GetRemovePackageCatalogResult(winrt::hresult terminationStatus) - { - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); - auto removeResult = winrt::make_self>(); - removeResult->Initialize(status, terminationStatus); - return *removeResult; - } - - std::optional<::AppInstaller::Repository::SourceDetails> GetMatchingSource(const std::string& name) - { - auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); - - for (const auto& source : sourceList) - { - if (::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)) - { - return source; // Return the first matching source - } - } - - return std::nullopt; // Return std::nullopt if no matching source is found - } - } - - PackageManager::PackageManager() - { - Execution::ContextOrchestrator::RegisterForShutdownSynchronization(); - } - - winrt::Windows::Foundation::Collections::IVectorView PackageManager::GetPackageCatalogs() - { - LogStartupIfApplicable(); - Windows::Foundation::Collections::IVector catalogs{ winrt::single_threaded_vector() }; - std::vector<::AppInstaller::Repository::SourceDetails> sources = ::AppInstaller::Repository::Source::GetCurrentSources(); - for (uint32_t i = 0; i < sources.size(); i++) - { - auto packageCatalogInfo = winrt::make_self>(); - ::AppInstaller::Repository::Source sourceReference{ sources.at(i).Name }; - packageCatalogInfo->Initialize(sourceReference.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, sourceReference); - catalogs.Append(*packageCatalogRef); - } - return catalogs.GetView(); - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog) - { - LogStartupIfApplicable(); - ::AppInstaller::Repository::Source source; - switch (predefinedPackageCatalog) - { - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalog: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGet }; - break; - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::MicrosoftStore: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::MicrosoftStore }; - break; - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::DesktopFrameworks: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::DesktopFrameworks }; - break; - case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalogFont: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGetFont }; - break; - default: - throw hresult_invalid_argument(); - } - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(source.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, source); - return *packageCatalogRef; - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog) - { - LogStartupIfApplicable(); - ::AppInstaller::Repository::Source source; - switch (localPackageCatalog) - { - case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstalledPackages: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; - break; - case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstallingPackages: - source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installing }; - break; - default: - throw hresult_invalid_argument(); - } - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(source.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, source); - return *packageCatalogRef; - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPackageCatalogByName(hstring const& catalogName) - { - LogStartupIfApplicable(); - std::string name = winrt::to_string(catalogName); - if (name.empty()) - { - return nullptr; - } - - ::AppInstaller::Repository::Source source{ name }; - // Create the catalog object if the source is found, otherwise return null. Don't throw. - if (source) - { - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(source.GetDetails()); - auto packageCatalogRef = winrt::make_self>(); - packageCatalogRef->Initialize(*packageCatalogInfo, source); - return *packageCatalogRef; - } - else - { - return nullptr; - } - } - - void AddPackageManifestToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo, ::AppInstaller::CLI::Execution::Context* context) - { - winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* packageVersionInfoImpl = get_self(packageVersionInfo); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalPackageVersion = packageVersionInfoImpl->GetRepositoryPackageVersion(); - ::AppInstaller::Manifest::Manifest manifest = internalPackageVersion->GetManifest(); - - std::string targetLocale; - if (context->Args.Contains(::AppInstaller::CLI::Execution::Args::Type::Locale)) - { - targetLocale = context->Args.GetArg(::AppInstaller::CLI::Execution::Args::Type::Locale); - } - manifest.ApplyLocale(targetLocale); - - context->GetThreadGlobals().GetTelemetryLogger().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get<::AppInstaller::Manifest::Localization::PackageName>(), manifest.Version); - - context->Add<::AppInstaller::CLI::Execution::Data::Manifest>(std::move(manifest)); - context->Add<::AppInstaller::CLI::Execution::Data::PackageVersion>(std::move(internalPackageVersion)); - } - - void AddInstalledVersionToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo, ::AppInstaller::CLI::Execution::Context* context) - { - winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* installedVersionInfoImpl = get_self(installedVersionInfo); - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalInstalledVersion = installedVersionInfoImpl->GetRepositoryPackageVersion(); - context->Add(internalInstalledVersion); - } - - winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options) - { - LogStartupIfApplicable(); - if (!options) - { - // Can't make a composite source if the options aren't specified. - throw hresult_invalid_argument(); - } - - for (uint32_t i = 0; i < options.Catalogs().Size(); ++i) - { - auto catalog = options.Catalogs().GetAt(i); - if (catalog.IsComposite()) - { - // Can't make a composite source out of a source that's already a composite. - throw hresult_invalid_argument(); - } - } - auto packageCatalogImpl = winrt::make_self>(); - packageCatalogImpl->Initialize(options); - return *packageCatalogImpl; - } - - winrt::Microsoft::Management::Deployment::InstallResult GetInstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t installerError, winrt::hstring correlationData, bool rebootRequired) - { - winrt::Microsoft::Management::Deployment::InstallResultStatus installResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto installResult = winrt::make_self>(); - installResult->Initialize(installResultStatus, terminationHR, installerError, correlationData, rebootRequired); - return *installResult; - } - - winrt::Microsoft::Management::Deployment::UninstallResult GetUninstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t uninstallerError, winrt::hstring correlationData, bool rebootRequired) - { - winrt::Microsoft::Management::Deployment::UninstallResultStatus uninstallResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto uninstallResult = winrt::make_self>(); - uninstallResult->Initialize(uninstallResultStatus, terminationHR, uninstallerError, correlationData, rebootRequired); - return *uninstallResult; - } - - winrt::Microsoft::Management::Deployment::DownloadResult GetDownloadResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, winrt::hstring correlationData) - { - winrt::Microsoft::Management::Deployment::DownloadResultStatus downloadResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto downloadResult = winrt::make_self>(); - downloadResult->Initialize(downloadResultStatus, terminationHR, correlationData); - return *downloadResult; - } - - winrt::Microsoft::Management::Deployment::RepairResult GetRepairResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t repairError, winrt::hstring correlationData, bool rebootRequired) - { - winrt::Microsoft::Management::Deployment::RepairResultStatus repairResultStatus = GetOperationResultStatus(executionStage, terminationHR); - auto repairResult = winrt::make_self>(); - repairResult->Initialize(repairResultStatus, terminationHR, repairError, correlationData, rebootRequired); - return *repairResult; - } - - template - TResult GetOperationResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t operationError, winrt::hstring correlationData, bool rebootRequired) - { - if constexpr (std::is_same_v) - { - return GetInstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); - } - else if constexpr (std::is_same_v) - { - return GetUninstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); - } - else if constexpr (std::is_same_v) - { - return GetDownloadResult(executionStage, terminationHR, correlationData); - } - else if constexpr (std::is_same_v) - { - return GetRepairResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); - } - } - -#define WINGET_GET_PROGRESS_STATE(_installState_, _uninstallState_, _repairState_) \ - if constexpr (std::is_same_v) \ - { \ - progressState = TState::_installState_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - progressState = TState::_uninstallState_; \ - } \ - else if constexpr (std::is_same_v) \ - { \ - progressState = TState::_repairState_; \ - } \ - - template - std::optional GetProgress( - ReportType reportType, - uint64_t current, - uint64_t maximum, - ::AppInstaller::ProgressType progressType, - ::Workflow::ExecutionStage executionPhase) - { - bool reportProgress = false; - TState progressState = TState::Queued; - double downloadProgress = 0; - double operationProgress = 0; - uint64_t downloadBytesDownloaded = 0; - uint64_t downloadBytesRequired = 0; - switch (executionPhase) - { - case ::Workflow::ExecutionStage::Initial: - case ::Workflow::ExecutionStage::ParseArgs: - case ::Workflow::ExecutionStage::Discovery: - // We already reported queued progress up front. - break; - case ::Workflow::ExecutionStage::Download: - if constexpr (std::is_same_v || - std::is_same_v) - { - progressState = TState::Downloading; - if (reportType == ReportType::BeginProgress) - { - reportProgress = true; - } - else if (progressType == ::AppInstaller::ProgressType::Bytes) - { - downloadBytesDownloaded = current; - downloadBytesRequired = maximum; - if (maximum > 0 && maximum >= current) - { - reportProgress = true; - downloadProgress = static_cast(current) / static_cast(maximum); - } - } - } - break; - case ::Workflow::ExecutionStage::PreExecution: - // Wait until installer starts to report operation. - break; - case ::Workflow::ExecutionStage::Execution: - WINGET_GET_PROGRESS_STATE(Installing, Uninstalling, Repairing); - downloadProgress = 1; - if (reportType == ReportType::ExecutionPhaseUpdate) - { - // Operation is starting. Send progress so callers know the AsyncOperation can't be cancelled. - reportProgress = true; - } - else if (reportType == ReportType::EndProgress) - { - // Operation is "finished". May not have succeeded. - reportProgress = true; - operationProgress = 1; - } - else if (progressType == ::AppInstaller::ProgressType::Percent) - { - if (maximum > 0 && maximum >= current) - { - // Operation is progressing - reportProgress = true; - operationProgress = static_cast(current) / static_cast(maximum); - } - } - break; - case ::Workflow::ExecutionStage::PostExecution: - if (reportType == ReportType::ExecutionPhaseUpdate) - { - // Send PostInstall progress when it switches to PostExecution phase. - reportProgress = true; - WINGET_GET_PROGRESS_STATE(PostInstall, PostUninstall, PostRepair); - downloadProgress = 1; - operationProgress = 1; - } - break; - } - if (reportProgress) - { - if constexpr (std::is_same_v) - { - TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress, operationProgress }; - return progress; - } - else if constexpr (std::is_same_v) - { - TProgress progress{ progressState, operationProgress }; - return progress; - } - else if constexpr (std::is_same_v) - { - TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress }; - return progress; - } - else if constexpr (std::is_same_v) - { - TProgress progress{ progressState, operationProgress }; - return progress; - } - } - else - { - return {}; - } - } - - template - Microsoft::Management::Deployment::PackageVersionInfo GetPackageVersionInfo(winrt::Microsoft::Management::Deployment::CatalogPackage package, TOptions options) - { - Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; - - winrt::Microsoft::Management::Deployment::PackageVersionId versionId = (options) ? options.PackageVersionId() : nullptr; - // If the version of the package is specified use that, otherwise use the default. - if (versionId) - { - packageVersionInfo = package.GetPackageVersionInfo(versionId); - } - else - { - if constexpr (std::is_same_v) - { - packageVersionInfo = package.DefaultInstallVersion(); - } - else if constexpr (std::is_same_v) - { - // For download, applicability check is not needed. Just use latest. - if (package.AvailableVersions().Size() > 0) - { - packageVersionInfo = package.GetPackageVersionInfo(package.AvailableVersions().GetAt(0)); - } - } - } - // If the specified version wasn't found then return a failure. This is unusual, since all packages that came from a non-local catalog have a default version, - // and the versionId is strongly typed and comes from the CatalogPackage.GetAvailableVersions. - // If version is not specified, DefaultInstallVersion may be empty due to applicability check. - THROW_HR_IF(versionId ? APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND : APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER, !packageVersionInfo); - return packageVersionInfo; - } - - void PopulateContextFromInstallOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::InstallOptions options) - { - if (options) - { - if (!options.LogOutputPath().empty()) - { - context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); - context->Args.AddArg(Execution::Args::Type::VerboseLogs); - } - if (options.AllowHashMismatch()) - { - context->Args.AddArg(Execution::Args::Type::HashOverride); - } - - if (options.BypassIsStoreClientBlockedPolicyCheck()) - { - context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - } - - if (options.Force()) - { - context->Args.AddArg(Execution::Args::Type::Force); - } - - // If the PackageInstallScope is anything other than ::Any then set it as a requirement. - auto manifestScope = GetManifestScope(options.PackageInstallScope()); - if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); - context->Add(manifestScope.second); - } - - if (options.PackageInstallMode() == PackageInstallMode::Interactive) - { - context->Args.AddArg(Execution::Args::Type::Interactive); - } - else if (options.PackageInstallMode() == PackageInstallMode::Silent) - { - context->Args.AddArg(Execution::Args::Type::Silent); - } - - auto installerType = GetManifestInstallerType(options.InstallerType()); - if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallerType, AppInstaller::Manifest::InstallerTypeToString(installerType)); - } - - if (!options.PreferredInstallLocation().empty()) - { - context->Args.AddArg(Execution::Args::Type::InstallLocation, ::AppInstaller::Utility::ConvertToUTF8(options.PreferredInstallLocation())); - } - - if (!options.ReplacementInstallerArguments().empty()) - { - context->Args.AddArg(Execution::Args::Type::Override, ::AppInstaller::Utility::ConvertToUTF8(options.ReplacementInstallerArguments())); - } - - if (!options.AdditionalInstallerArguments().empty()) - { - context->Args.AddArg(Execution::Args::Type::CustomSwitches, ::AppInstaller::Utility::ConvertToUTF8(options.AdditionalInstallerArguments())); - } - - if (options.AllowedArchitectures().Size() != 0) - { - std::vector allowedArchitectures; - for (auto architecture : options.AllowedArchitectures()) - { - auto convertedArchitecture = GetUtilityArchitecture(architecture); - if (convertedArchitecture) - { - allowedArchitectures.push_back(convertedArchitecture.value()); - } - } - context->Add(std::move(allowedArchitectures)); - } - - // Note: AdditionalPackageCatalogArguments is not needed during install since the manifest is already known so no additional calls to the source are needed. The property is deprecated. - - if (options.AcceptPackageAgreements()) - { - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - - if (options.SkipDependencies()) - { - 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 - { - // Note: If no install options are specified, we assume the caller is accepting the package agreements by default. - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - } - - void PopulateContextFromUninstallOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::UninstallOptions options) - { - if (options) - { - if (!options.LogOutputPath().empty()) - { - context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); - context->Args.AddArg(Execution::Args::Type::VerboseLogs); - } - if (options.Force()) - { - context->Args.AddArg(Execution::Args::Type::Force); - } - - if (options.PackageUninstallMode() == PackageUninstallMode::Interactive) - { - context->Args.AddArg(Execution::Args::Type::Interactive); - } - else if (options.PackageUninstallMode() == PackageUninstallMode::Silent) - { - context->Args.AddArg(Execution::Args::Type::Silent); - } - - auto uninstallScope = GetManifestUninstallScope(options.PackageUninstallScope()); - if (uninstallScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(uninstallScope)); - } - } - } - - void PopulateContextFromDownloadOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::DownloadOptions options) - { - if (options) - { - if (!options.DownloadDirectory().empty()) - { - context->Args.AddArg(Execution::Args::Type::DownloadDirectory, ::AppInstaller::Utility::ConvertToUTF8(options.DownloadDirectory())); - } - if (!options.Locale().empty()) - { - context->Args.AddArg(Execution::Args::Type::Locale, ::AppInstaller::Utility::ConvertToUTF8(options.Locale())); - } - if (options.AllowHashMismatch()) - { - context->Args.AddArg(Execution::Args::Type::HashOverride); - } - if (options.SkipDependencies()) - { - context->Args.AddArg(Execution::Args::Type::SkipDependencies); - } - if (options.AcceptPackageAgreements()) - { - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - auto manifestScope = GetManifestScope(options.Scope()); - if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); - } - - auto architecture = options.Architecture(); - if (architecture != Windows::System::ProcessorArchitecture::Unknown) - { - auto convertedArchitecture = GetUtilityArchitecture(architecture); - if (convertedArchitecture) - { - context->Args.AddArg(Execution::Args::Type::InstallerArchitecture, ToString(convertedArchitecture.value())); - } - } - - auto installerType = GetManifestInstallerType(options.InstallerType()); - if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) - { - 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())); - } - - if (options.SkipMicrosoftStoreLicense()) - { - context->Args.AddArg(Execution::Args::Type::SkipMicrosoftStorePackageLicense); - } - - WindowsPlatform platform = options.Platform(); - if (platform != WindowsPlatform::Unknown) - { - context->Args.AddArg(Execution::Args::Type::Platform, AppInstaller::Manifest::PlatformToString(GetPlatformEnum(platform))); - } - - hstring targetOSVersion = options.TargetOSVersion(); - if (!targetOSVersion.empty()) - { - context->Args.AddArg(Execution::Args::Type::OSVersion, ::AppInstaller::Utility::ConvertToUTF8(targetOSVersion)); - } - } - } - - void PopulateContextFromRepairOptions( - ::AppInstaller::CLI::Execution::Context* context, - winrt::Microsoft::Management::Deployment::RepairOptions options) - { - if (options) - { - if (!options.LogOutputPath().empty()) - { - context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); - context->Args.AddArg(Execution::Args::Type::VerboseLogs); - } - - if (options.PackageRepairMode() == PackageRepairMode::Interactive) - { - context->Args.AddArg(Execution::Args::Type::Interactive); - } - else if (options.PackageRepairMode() == PackageRepairMode::Silent) - { - context->Args.AddArg(Execution::Args::Type::Silent); - } - - if (options.AcceptPackageAgreements()) - { - context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); - } - - if (options.AllowHashMismatch()) - { - context->Args.AddArg(Execution::Args::Type::HashOverride); - } - - if (options.BypassIsStoreClientBlockedPolicyCheck()) - { - context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); - } - - if (options.Force()) - { - context->Args.AddArg(Execution::Args::Type::Force); - } - - auto repairScope = GetManifestRepairScope(options.PackageRepairScope()); - if (repairScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - 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())); - } - } - } - - template - std::unique_ptr CreateContextFromOperationOptions( - TOptions options, - std::wstring callerProcessInfoString) - { - std::unique_ptr context = std::make_unique(); - hstring correlationData = (options) ? options.CorrelationData() : L""; - - context->SetContextLoggers(correlationData, GetComCallerName(AppInstaller::Utility::ConvertToUTF8(callerProcessInfoString))); - - // Convert the options to arguments for the installer. - if constexpr (std::is_same_v) - { - PopulateContextFromInstallOptions(context.get(), options); - } - else if constexpr (std::is_same_v) - { - PopulateContextFromUninstallOptions(context.get(), options); - } - else if constexpr (std::is_same_v) - { - PopulateContextFromDownloadOptions(context.get(), options); - } - else if constexpr (std::is_same_v) - { - PopulateContextFromRepairOptions(context.get(), options); - } - - return context; - } - - std::shared_ptr GetExistingQueueItemForPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - std::shared_ptr queueItem = nullptr; - std::unique_ptr context = std::make_unique(); - if (catalogInfo) - { - // If the caller has passed in the catalog they expect the package to have come from, then only look for an install from that catalog. - // Fail if they've used a catalog that doesn't have an Id. This can currently happen for Info objects that come from PackageCatalogReference objects for REST catalogs. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, catalogInfo.Id().empty()); - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ catalogInfo.Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - return queueItem; - } - - // If the caller has not specified the catalog, then check InstalledVersion. When the package comes from the Installing catalog the PackageCatalog - // of the InstalledVersion will be set to the original catalog that the install was from, so checking the InstalledVersion first is most likely to - // find a result. - Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo = package.InstalledVersion(); - if (installedVersionInfo) - { - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ installedVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - if (queueItem) - { - return queueItem; - } - } - - // If InstalledVersion was not found, check DefaultInstallVersion - Microsoft::Management::Deployment::PackageVersionInfo defaultInstallVersionInfo = package.DefaultInstallVersion(); - if (defaultInstallVersionInfo) - { - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ defaultInstallVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - if (queueItem) - { - return queueItem; - } - } - - // Finally check all catalogs in AvailableVersions. - for (Microsoft::Management::Deployment::PackageVersionId versionId : package.AvailableVersions()) - { - auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ package.GetPackageVersionInfo(versionId).PackageCatalog().Info().Id() }, std::move(context)); - queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); - if (queueItem) - { - return queueItem; - } - } - return nullptr; - } - - std::unique_ptr CreateQueueItemForInstall( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package, - winrt::Microsoft::Management::Deployment::InstallOptions options, - bool isUpgrade) - { - // Add manifest and PackageVersion to context for install/upgrade. - // If the version of the package is specified use that, otherwise use the default. - Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); - AddPackageManifestToContext(packageVersionInfo, comContext.get()); - - if (isUpgrade) - { - AppInstaller::Utility::VersionAndChannel installedVersion{ winrt::to_string(package.InstalledVersion().Version()), winrt::to_string(package.InstalledVersion().Channel()) }; - AppInstaller::Utility::VersionAndChannel upgradeVersion{ winrt::to_string(packageVersionInfo.Version()), winrt::to_string(packageVersionInfo.Channel()) }; - - // Perform upgrade version check - if (upgradeVersion.GetVersion().IsUnknown()) - { - if (!(options.AllowUpgradeToUnknownVersion() && - AppInstaller::Utility::ICUCaseInsensitiveEquals(installedVersion.GetChannel().ToString(), upgradeVersion.GetChannel().ToString()))) - { - THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN); - } - } - else if (!installedVersion.IsUpdatedBy(upgradeVersion)) - { - THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER); - } - - // Set upgrade flag - comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseUpdate); - // Add installed version - AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); - } - - return Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext), isUpgrade); - } - - std::unique_ptr CreateQueueItemForUninstall( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package) - { - // Add installed version - AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); - - // Add Package which is used by RecordUninstall later for removing from tracking catalog of correlated available sources as best effort - winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); - comContext->Add(internalPackage); - - return Execution::OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); - } - - std::unique_ptr CreateQueueItemForDownload( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package, - winrt::Microsoft::Management::Deployment::DownloadOptions options) - { - // Add manifest and PackageVersion to context for download. - // If the version of the package is specified use that, otherwise use the default. - Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); - AddPackageManifestToContext(packageVersionInfo, comContext.get()); - - comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); - - return Execution::OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext)); - } - - std::unique_ptr CreateQueueItemForRepair( - std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, - winrt::Microsoft::Management::Deployment::CatalogPackage package) - { - // Add installed version - AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); - - // Add Package which is used to co-relate installed package with available package for repair - winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); - std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); - comContext->Add(internalPackage); - - comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseRepair); - - return Execution::OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); - } - - template - winrt::Windows::Foundation::IAsyncOperationWithProgress GetPackageOperation( - bool canCancelQueueItem, - std::shared_ptr queueItemParam, - winrt::Microsoft::Management::Deployment::CatalogPackage package = nullptr, - TOptions options = nullptr, - std::wstring callerProcessInfoString = {}, - bool isUpgrade = false) - { - winrt::hresult terminationHR = S_OK; - uint32_t operationError = 0; - hstring correlationData = (options) ? options.CorrelationData() : L""; - ::Workflow::ExecutionStage executionStage = ::Workflow::ExecutionStage::Initial; - - try - { - // re-scope the parameter to inside the try block to avoid lifetime management issues. - std::shared_ptr queueItem = std::move(queueItemParam); - - auto report_progress{ co_await winrt::get_progress_token() }; - auto cancellationToken{ co_await winrt::get_cancellation_token() }; - // co_await does not guarantee that it's on a background thread, so do so explicitly. - co_await winrt::resume_background(); - - if (queueItem == nullptr) - { - std::unique_ptr comContext = CreateContextFromOperationOptions(options, callerProcessInfoString); - - if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForInstall(std::move(comContext), package, options, isUpgrade); - } - else if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForUninstall(std::move(comContext), package); - } - else if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForDownload(std::move(comContext), package, options); - } - else if constexpr (std::is_same_v) - { - queueItem = CreateQueueItemForRepair(std::move(comContext), package); - } - - Execution::ContextOrchestrator::Instance().EnqueueAndRunItem(queueItem); - - if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0, 0, 0 }; - report_progress(queuedProgress); - } - else if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0 }; - report_progress(queuedProgress); - } - else if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0 }; - report_progress(queuedProgress); - } - else if constexpr (std::is_same_v) - { - TProgress queuedProgress{ TProgressState::Queued, 0 }; - report_progress(queuedProgress); - } - } - { - // correlation data is not passed in when retrieving an existing queue item, so get it from the existing context. - correlationData = hstring(queueItem->GetContext().GetCorrelationJson()); - } - - wil::unique_event progressEvent{ wil::EventOptions::None }; - - std::atomic operationProgress; - queueItem->GetContext().AddProgressCallbackFunction([&operationProgress, &progressEvent]( - ReportType reportType, - uint64_t current, - uint64_t maximum, - ::AppInstaller::ProgressType progressType, - ::Workflow::ExecutionStage executionPhase) - { - std::optional operationProgressOptional = GetProgress(reportType, current, maximum, progressType, executionPhase); - if (operationProgressOptional.has_value()) - { - operationProgress = operationProgressOptional.value(); - progressEvent.SetEvent(); - } - return; - } - ); - - std::weak_ptr weakQueueItem(queueItem); - cancellationToken.callback([weakQueueItem, &canCancelQueueItem] - { - if (canCancelQueueItem) - { - auto strongQueueItem = weakQueueItem.lock(); - if (strongQueueItem) { - // The cancellation of the AsyncOperation on the client triggers Cancel which causes the Execute to end. - Execution::ContextOrchestrator::Instance().CancelQueueItem(*strongQueueItem); - } - } - }); - - // Wait for completion or progress events. - // Waiting for both on the same thread ensures that progress is never reported after the async operation itself has completed. - bool completionEventFired = false; - HANDLE operationEvents[2]; - operationEvents[0] = progressEvent.get(); - operationEvents[1] = queueItem->GetCompletedEvent().get(); - while (!completionEventFired) - { - DWORD dwEvent = WaitForMultipleObjects( - _countof(operationEvents) /* number of events */, - operationEvents /* event array */, - FALSE /* bWaitAll, FALSE to wake on any event */, - INFINITE /* wait until operation completion */); - - switch (dwEvent) - { - // operationEvents[0] was signaled, progress - case WAIT_OBJECT_0 + 0: - // The report_progress call will hang when making callbacks to suspended processes so it's important that this is now on a background thread. - // Progress events are not queued - some will be missed if multiple progress events are fired from the ComContext to the callback - // while the report_progress call is hung\in progress. - // Duplicate progress events can be fired if another progress event comes from the ComContext to the callback after the listener - // has been awaked, but before it has gotten the installProgress. - report_progress(operationProgress); - break; - - // operationEvents[1] was signaled, operation completed - case WAIT_OBJECT_0 + 1: - completionEventFired = true; - break; - - // Return value is invalid. - default: - THROW_LAST_ERROR(); - } - } - - if (completionEventFired) - { - // The install command has finished, check for success/failure and how far it got. - terminationHR = queueItem->GetContext().GetTerminationHR(); - executionStage = queueItem->GetContext().GetExecutionStage(); - if (queueItem->GetContext().Contains(Data::OperationReturnCode)) - { - operationError = static_cast(queueItem->GetContext().Get()); - } - } - } - WINGET_CATCH_STORE(terminationHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - - // TODO - RebootRequired not yet populated, msi arguments not returned from Execute. - co_return GetOperationResult(executionStage, terminationHR, operationError, correlationData, false); - } - - template - winrt::Windows::Foundation::IAsyncOperationWithProgress GetEmptyAsynchronousResultForOperation( - HRESULT hr, - hstring correlationData) - { - // If a function uses co_await or co_return (i.e. if it is a co_routine), it cannot use return directly. - // This helper helps a function that is not a coroutine itself to return errors asynchronously. - co_return GetOperationResult(::Workflow::ExecutionStage::Initial, hr, 0, correlationData, false); - } - -#define WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be upgraded. - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString), true /* isUpgrade */); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - hstring correlationData; - WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::shared_ptr queueItem = nullptr; - bool canCancelQueueItem = false; - try - { - // Check for permissions - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - if (!canCancelQueueItem) - { - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); - } - - // Get the queueItem synchronously. - queueItem = GetExistingQueueItemForPackage(package, catalogInfo); - if (queueItem == nullptr || - (queueItem->GetPackageOperationType() != PackageOperationType::Install && queueItem->GetPackageOperationType() != PackageOperationType::Upgrade)) - { - return nullptr; - } - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - canCancelQueueItem, std::move(queueItem)); - } - -#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be uninstalled. - WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - hstring correlationData; - WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::shared_ptr queueItem = nullptr; - bool canCancelQueueItem = false; - try - { - // Check for permissions - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); - canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - if (!canCancelQueueItem) - { - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); - } - - // Get the queueItem synchronously. - queueItem = GetExistingQueueItemForPackage(package, catalogInfo); - if (queueItem == nullptr || - queueItem->GetPackageOperationType() != PackageOperationType::Uninstall) - { - return nullptr; - } - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - canCancelQueueItem, std::move(queueItem)); - } - -#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) - { - hstring correlationData; - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - - HRESULT hr = S_OK; - std::shared_ptr queueItem = nullptr; - try - { - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); - - // Get the queueItem synchronously. - queueItem = GetExistingQueueItemForPackage(package, catalogInfo); - if (queueItem == nullptr || - queueItem->GetPackageOperationType() != PackageOperationType::Download) - { - return nullptr; - } - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation(true, std::move(queueItem)); - } - -#define WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} -#define WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, FAILED(hr)) } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options) - { - hstring correlationData = (options) ? options.CorrelationData() : L""; - - // options and catalog can both be null, package must be set. - WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); - // the package should have an installed version to be repaired. - WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); - - HRESULT hr = S_OK; - std::wstring callerProcessInfoString; - try - { - // Check for permissions and get caller info for telemetry. - // This must be done before any co_awaits since it requires info from the rpc caller thread. - auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hrGetCallerId); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); - callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); - } - WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); - WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr); - - return GetPackageOperation( - true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options) - { - LogStartupIfApplicable(); - - // options must be set. - THROW_HR_IF_NULL(E_POINTER, options); - THROW_HR_IF(E_INVALIDARG, options.Name().empty()); - THROW_HR_IF(E_INVALIDARG, options.SourceUri().empty()); - - HRESULT terminationHR = S_OK; - try { - - // Check if running as admin/system. - // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, - // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); - - ::AppInstaller::Repository::Source sourceToAdd = CreateSourceFromOptions(options); - - auto strong_this = get_strong(); - auto report_progress{ co_await winrt::get_progress_token() }; - co_await winrt::resume_background(); - - std::string type = winrt::to_string(options.Type()); - auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(type, report_progress ); - - packageCatalogProgressSink->BeginProgress(); - ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); - sourceToAdd.Add(progress); - packageCatalogProgressSink->EndProgress(false); - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - co_return GetAddPackageCatalogResult(terminationHR); - } - - winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options) - { - LogStartupIfApplicable(); - - // options must be set. - THROW_HR_IF_NULL(E_POINTER, options); - THROW_HR_IF(E_INVALIDARG, options.Name().empty()); - - HRESULT terminationHR = S_OK; - try { - - // Check if running as admin/system. - // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, - // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); - - auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); - - auto strong_this = get_strong(); - auto report_progress{ co_await winrt::get_progress_token() }; - co_await winrt::resume_background(); - - auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(matchingSource.value().Type, report_progress, true); - - packageCatalogProgressSink->BeginProgress(); - ::AppInstaller::Repository::Source sourceToRemove = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; - ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); - - // If the PreserveData option is set, this is equivalent to the WinGet CLI Reset command on a single source; otherwise, it removes the source. - if (options.PreserveData()) - { - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !sourceToRemove.DropSource(matchingSource.value().Name)); - } - else - { - sourceToRemove.Remove(progress); - } - packageCatalogProgressSink->EndProgress(false); - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - co_return GetRemovePackageCatalogResult(terminationHR); - } - - winrt::hstring PackageManager::Version() const - { - return winrt::hstring{ AppInstaller::Utility::ConvertToUTF16(AppInstaller::Runtime::GetClientVersion()) }; - } - - winrt::Microsoft::Management::Deployment::EditPackageCatalogResult GetEditPackageCatalogResult(winrt::hresult terminationStatus) - { - winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); - auto editResult = winrt::make_self>(); - editResult->Initialize(status, terminationStatus); - return *editResult; - } - - winrt::Microsoft::Management::Deployment::EditPackageCatalogResult PackageManager::EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options) - { - LogStartupIfApplicable(); - - // options must be set. - THROW_HR_IF_NULL(E_POINTER, options); - THROW_HR_IF(E_INVALIDARG, options.Name().empty()); - - HRESULT terminationHR = S_OK; - try { - - // Check if running as admin/system. - // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, - // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. - THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); - - auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); - ::AppInstaller::Repository::Source sourceToEdit = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; - - ::AppInstaller::Repository::SourceEdit edits; - edits.Explicit = options.Explicit(); - edits.Priority = options.Priority(); - - if (sourceToEdit.RequiresChanges(edits)) - { - sourceToEdit.Edit(edits); - } - } - catch (...) - { - terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); - } - - return GetEditPackageCatalogResult(terminationHR); - } - - CoCreatableMicrosoftManagementDeploymentClass(PackageManager); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "Public/AppInstallerCLICore.h" +#include "Microsoft/PredefinedInstalledSourceFactory.h" +#include "Commands/RootCommand.h" +#include "ComContext.h" +#include "ExecutionContext.h" +#include "Workflows/WorkflowBase.h" +#include +#include +#include +#include "Commands/COMCommand.h" +#include +#include +#include +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "PackageManager.h" +#pragma warning( pop ) +#include "PackageManager.g.cpp" +#include "CatalogPackage.h" +#include "DownloadResult.h" +#include "InstallResult.h" +#include "UninstallResult.h" +#include "RepairResult.h" +#include "PackageCatalogInfo.h" +#include "PackageCatalogReference.h" +#include "PackageVersionInfo.h" +#include "PackageVersionId.h" +#include "AddPackageCatalogResult.h" +#include "RemovePackageCatalogResult.h" +#include "EditPackageCatalogResult.h" +#include "Converters.h" +#include "Helpers.h" +#include "ContextOrchestrator.h" +#include "AppInstallerRuntime.h" +#include +#include + +using namespace std::literals::chrono_literals; +using namespace ::AppInstaller::CLI; +using namespace ::AppInstaller::CLI::Execution; + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + namespace + { + void LogStartupIfApplicable() + { + static std::once_flag logStartupOnceFlag; + std::call_once(logStartupOnceFlag, + [&]() + { + ::AppInstaller::Logging::Telemetry().SetCaller(GetCallerName()); + ::AppInstaller::Logging::Telemetry().LogStartup(true); + }); + } + + winrt::Microsoft::Management::Deployment::AddPackageCatalogResult GetAddPackageCatalogResult(winrt::hresult terminationStatus) + { + winrt::Microsoft::Management::Deployment::AddPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); + auto addPackageCatalogResult = winrt::make_self>(); + addPackageCatalogResult->Initialize(status, terminationStatus); + return *addPackageCatalogResult; + } + + void CheckForDuplicateSource(const std::string& name, const std::string& type, const std::string& sourceUri) + { + auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); + + std::string sourceType = type; + + // [NOTE:] If the source type is not specified, the default source type will be used for validation.In cases where the source type is empty, + // it remains unassigned until the add operation, at which point it is assigned.Without this default assignment, an empty string could be + // compared to the default type, potentially allowing different source names with the same URI to be seen as unique. + // To avoid this, assign the default source type prior to comparison. + if (sourceType.empty()) + { + // This method of obtaining the default source type is slightly expensive as it requires creating a SourceFactory object + // and fetching the type name.Nonetheless, it future-proofs the code against any changes in the SourceFactory's default type. + sourceType = ::AppInstaller::Repository::Source::GetDefaultSourceType(); + } + + for (const auto& source : sourceList) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_ALREADY_EXISTS, ::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)); + + bool sourceUriAlreadyExists = !source.Arg.empty() && source.Arg == sourceUri && source.Type == sourceType; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_ARG_ALREADY_EXISTS, sourceUriAlreadyExists); + } + } + + ::AppInstaller::Repository::Source CreateSourceFromOptions(const winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions& options) + { + std::string name = winrt::to_string(options.Name()); + std::string type = winrt::to_string(options.Type()); + std::string sourceUri = winrt::to_string(options.SourceUri()); + + AppInstaller::Repository::SourceTrustLevel trustLevel = AppInstaller::Repository::SourceTrustLevel::None; + if (options.TrustLevel() == winrt::Microsoft::Management::Deployment::PackageCatalogTrustLevel::Trusted) + { + trustLevel = AppInstaller::Repository::SourceTrustLevel::Trusted; + } + + CheckForDuplicateSource(name, type, sourceUri); + + ::AppInstaller::Repository::SourceEdit additionalProperties; + additionalProperties.Explicit = options.Explicit(); + additionalProperties.Priority = options.Priority(); + + ::AppInstaller::Repository::Source source = ::AppInstaller::Repository::Source{ name, sourceUri, type, trustLevel, additionalProperties }; + + std::string customHeader = winrt::to_string(options.CustomHeader()); + if (!customHeader.empty()) + { + source.SetCustomHeader(customHeader); + } + + auto sourceInfo = source.GetInformation(); + + if (sourceInfo.Authentication.Type == ::AppInstaller::Authentication::AuthenticationType::Unknown) + { + THROW_HR(APPINSTALLER_CLI_ERROR_AUTHENTICATION_TYPE_NOT_SUPPORTED); + } + + return source; + } + + winrt::Microsoft::Management::Deployment::RemovePackageCatalogResult GetRemovePackageCatalogResult(winrt::hresult terminationStatus) + { + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); + auto removeResult = winrt::make_self>(); + removeResult->Initialize(status, terminationStatus); + return *removeResult; + } + + std::optional<::AppInstaller::Repository::SourceDetails> GetMatchingSource(const std::string& name) + { + auto sourceList = ::AppInstaller::Repository::Source::GetCurrentSources(); + + for (const auto& source : sourceList) + { + if (::AppInstaller::Utility::ICUCaseInsensitiveEquals(source.Name, name)) + { + return source; // Return the first matching source + } + } + + return std::nullopt; // Return std::nullopt if no matching source is found + } + } + + PackageManager::PackageManager() + { + Execution::ContextOrchestrator::RegisterForShutdownSynchronization(); + } + + winrt::Windows::Foundation::Collections::IVectorView PackageManager::GetPackageCatalogs() + { + LogStartupIfApplicable(); + Windows::Foundation::Collections::IVector catalogs{ winrt::single_threaded_vector() }; + std::vector<::AppInstaller::Repository::SourceDetails> sources = ::AppInstaller::Repository::Source::GetCurrentSources(); + for (uint32_t i = 0; i < sources.size(); i++) + { + auto packageCatalogInfo = winrt::make_self>(); + ::AppInstaller::Repository::Source sourceReference{ sources.at(i).Name }; + packageCatalogInfo->Initialize(sourceReference.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, sourceReference); + catalogs.Append(*packageCatalogRef); + } + return catalogs.GetView(); + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog) + { + LogStartupIfApplicable(); + ::AppInstaller::Repository::Source source; + switch (predefinedPackageCatalog) + { + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalog: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGet }; + break; + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::MicrosoftStore: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::MicrosoftStore }; + break; + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::DesktopFrameworks: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::DesktopFrameworks }; + break; + case winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog::OpenWindowsCatalogFont: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::WellKnownSource::WinGetFont }; + break; + default: + throw hresult_invalid_argument(); + } + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(source.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, source); + return *packageCatalogRef; + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog) + { + LogStartupIfApplicable(); + ::AppInstaller::Repository::Source source; + switch (localPackageCatalog) + { + case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstalledPackages: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installed }; + break; + case winrt::Microsoft::Management::Deployment::LocalPackageCatalog::InstallingPackages: + source = ::AppInstaller::Repository::Source{ ::AppInstaller::Repository::PredefinedSource::Installing }; + break; + default: + throw hresult_invalid_argument(); + } + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(source.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, source); + return *packageCatalogRef; + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::GetPackageCatalogByName(hstring const& catalogName) + { + LogStartupIfApplicable(); + std::string name = winrt::to_string(catalogName); + if (name.empty()) + { + return nullptr; + } + + ::AppInstaller::Repository::Source source{ name }; + // Create the catalog object if the source is found, otherwise return null. Don't throw. + if (source) + { + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(source.GetDetails()); + auto packageCatalogRef = winrt::make_self>(); + packageCatalogRef->Initialize(*packageCatalogInfo, source); + return *packageCatalogRef; + } + else + { + return nullptr; + } + } + + void AddPackageManifestToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo, ::AppInstaller::CLI::Execution::Context* context) + { + winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* packageVersionInfoImpl = get_self(packageVersionInfo); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalPackageVersion = packageVersionInfoImpl->GetRepositoryPackageVersion(); + ::AppInstaller::Manifest::Manifest manifest = internalPackageVersion->GetManifest(); + + std::string targetLocale; + if (context->Args.Contains(::AppInstaller::CLI::Execution::Args::Type::Locale)) + { + targetLocale = context->Args.GetArg(::AppInstaller::CLI::Execution::Args::Type::Locale); + } + manifest.ApplyLocale(targetLocale); + + context->GetThreadGlobals().GetTelemetryLogger().LogManifestFields(manifest.Id, manifest.DefaultLocalization.Get<::AppInstaller::Manifest::Localization::PackageName>(), manifest.Version); + + context->Add<::AppInstaller::CLI::Execution::Data::Manifest>(std::move(manifest)); + context->Add<::AppInstaller::CLI::Execution::Data::PackageVersion>(std::move(internalPackageVersion)); + } + + void AddInstalledVersionToContext(winrt::Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo, ::AppInstaller::CLI::Execution::Context* context) + { + winrt::Microsoft::Management::Deployment::implementation::PackageVersionInfo* installedVersionInfoImpl = get_self(installedVersionInfo); + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> internalInstalledVersion = installedVersionInfoImpl->GetRepositoryPackageVersion(); + context->Add(internalInstalledVersion); + } + + winrt::Microsoft::Management::Deployment::PackageCatalogReference PackageManager::CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options) + { + LogStartupIfApplicable(); + if (!options) + { + // Can't make a composite source if the options aren't specified. + throw hresult_invalid_argument(); + } + + for (uint32_t i = 0; i < options.Catalogs().Size(); ++i) + { + auto catalog = options.Catalogs().GetAt(i); + if (catalog.IsComposite()) + { + // Can't make a composite source out of a source that's already a composite. + throw hresult_invalid_argument(); + } + } + auto packageCatalogImpl = winrt::make_self>(); + packageCatalogImpl->Initialize(options); + return *packageCatalogImpl; + } + + winrt::Microsoft::Management::Deployment::InstallResult GetInstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t installerError, winrt::hstring correlationData, bool rebootRequired) + { + winrt::Microsoft::Management::Deployment::InstallResultStatus installResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto installResult = winrt::make_self>(); + installResult->Initialize(installResultStatus, terminationHR, installerError, correlationData, rebootRequired); + return *installResult; + } + + winrt::Microsoft::Management::Deployment::UninstallResult GetUninstallResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t uninstallerError, winrt::hstring correlationData, bool rebootRequired) + { + winrt::Microsoft::Management::Deployment::UninstallResultStatus uninstallResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto uninstallResult = winrt::make_self>(); + uninstallResult->Initialize(uninstallResultStatus, terminationHR, uninstallerError, correlationData, rebootRequired); + return *uninstallResult; + } + + winrt::Microsoft::Management::Deployment::DownloadResult GetDownloadResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, winrt::hstring correlationData) + { + winrt::Microsoft::Management::Deployment::DownloadResultStatus downloadResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto downloadResult = winrt::make_self>(); + downloadResult->Initialize(downloadResultStatus, terminationHR, correlationData); + return *downloadResult; + } + + winrt::Microsoft::Management::Deployment::RepairResult GetRepairResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t repairError, winrt::hstring correlationData, bool rebootRequired) + { + winrt::Microsoft::Management::Deployment::RepairResultStatus repairResultStatus = GetOperationResultStatus(executionStage, terminationHR); + auto repairResult = winrt::make_self>(); + repairResult->Initialize(repairResultStatus, terminationHR, repairError, correlationData, rebootRequired); + return *repairResult; + } + + template + TResult GetOperationResult(::Workflow::ExecutionStage executionStage, winrt::hresult terminationHR, uint32_t operationError, winrt::hstring correlationData, bool rebootRequired) + { + if constexpr (std::is_same_v) + { + return GetInstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); + } + else if constexpr (std::is_same_v) + { + return GetUninstallResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); + } + else if constexpr (std::is_same_v) + { + return GetDownloadResult(executionStage, terminationHR, correlationData); + } + else if constexpr (std::is_same_v) + { + return GetRepairResult(executionStage, terminationHR, operationError, correlationData, rebootRequired); + } + } + +#define WINGET_GET_PROGRESS_STATE(_installState_, _uninstallState_, _repairState_) \ + if constexpr (std::is_same_v) \ + { \ + progressState = TState::_installState_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + progressState = TState::_uninstallState_; \ + } \ + else if constexpr (std::is_same_v) \ + { \ + progressState = TState::_repairState_; \ + } \ + + template + std::optional GetProgress( + ReportType reportType, + uint64_t current, + uint64_t maximum, + ::AppInstaller::ProgressType progressType, + ::Workflow::ExecutionStage executionPhase) + { + bool reportProgress = false; + TState progressState = TState::Queued; + double downloadProgress = 0; + double operationProgress = 0; + uint64_t downloadBytesDownloaded = 0; + uint64_t downloadBytesRequired = 0; + switch (executionPhase) + { + case ::Workflow::ExecutionStage::Initial: + case ::Workflow::ExecutionStage::ParseArgs: + case ::Workflow::ExecutionStage::Discovery: + // We already reported queued progress up front. + break; + case ::Workflow::ExecutionStage::Download: + if constexpr (std::is_same_v || + std::is_same_v) + { + progressState = TState::Downloading; + if (reportType == ReportType::BeginProgress) + { + reportProgress = true; + } + else if (progressType == ::AppInstaller::ProgressType::Bytes) + { + downloadBytesDownloaded = current; + downloadBytesRequired = maximum; + if (maximum > 0 && maximum >= current) + { + reportProgress = true; + downloadProgress = static_cast(current) / static_cast(maximum); + } + } + } + break; + case ::Workflow::ExecutionStage::PreExecution: + // Wait until installer starts to report operation. + break; + case ::Workflow::ExecutionStage::Execution: + WINGET_GET_PROGRESS_STATE(Installing, Uninstalling, Repairing); + downloadProgress = 1; + if (reportType == ReportType::ExecutionPhaseUpdate) + { + // Operation is starting. Send progress so callers know the AsyncOperation can't be cancelled. + reportProgress = true; + } + else if (reportType == ReportType::EndProgress) + { + // Operation is "finished". May not have succeeded. + reportProgress = true; + operationProgress = 1; + } + else if (progressType == ::AppInstaller::ProgressType::Percent) + { + if (maximum > 0 && maximum >= current) + { + // Operation is progressing + reportProgress = true; + operationProgress = static_cast(current) / static_cast(maximum); + } + } + break; + case ::Workflow::ExecutionStage::PostExecution: + if (reportType == ReportType::ExecutionPhaseUpdate) + { + // Send PostInstall progress when it switches to PostExecution phase. + reportProgress = true; + WINGET_GET_PROGRESS_STATE(PostInstall, PostUninstall, PostRepair); + downloadProgress = 1; + operationProgress = 1; + } + break; + } + if (reportProgress) + { + if constexpr (std::is_same_v) + { + TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress, operationProgress }; + return progress; + } + else if constexpr (std::is_same_v) + { + TProgress progress{ progressState, operationProgress }; + return progress; + } + else if constexpr (std::is_same_v) + { + TProgress progress{ progressState, downloadBytesDownloaded, downloadBytesRequired, downloadProgress }; + return progress; + } + else if constexpr (std::is_same_v) + { + TProgress progress{ progressState, operationProgress }; + return progress; + } + } + else + { + return {}; + } + } + + template + Microsoft::Management::Deployment::PackageVersionInfo GetPackageVersionInfo(winrt::Microsoft::Management::Deployment::CatalogPackage package, TOptions options) + { + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo{ nullptr }; + + winrt::Microsoft::Management::Deployment::PackageVersionId versionId = (options) ? options.PackageVersionId() : nullptr; + // If the version of the package is specified use that, otherwise use the default. + if (versionId) + { + packageVersionInfo = package.GetPackageVersionInfo(versionId); + } + else + { + if constexpr (std::is_same_v) + { + packageVersionInfo = package.DefaultInstallVersion(); + } + else if constexpr (std::is_same_v) + { + // For download, applicability check is not needed. Just use latest. + if (package.AvailableVersions().Size() > 0) + { + packageVersionInfo = package.GetPackageVersionInfo(package.AvailableVersions().GetAt(0)); + } + } + } + // If the specified version wasn't found then return a failure. This is unusual, since all packages that came from a non-local catalog have a default version, + // and the versionId is strongly typed and comes from the CatalogPackage.GetAvailableVersions. + // If version is not specified, DefaultInstallVersion may be empty due to applicability check. + THROW_HR_IF(versionId ? APPINSTALLER_CLI_ERROR_NO_MANIFEST_FOUND : APPINSTALLER_CLI_ERROR_NO_APPLICABLE_INSTALLER, !packageVersionInfo); + return packageVersionInfo; + } + + void PopulateContextFromInstallOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::InstallOptions options) + { + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + if (options.AllowHashMismatch()) + { + context->Args.AddArg(Execution::Args::Type::HashOverride); + } + + if (options.BypassIsStoreClientBlockedPolicyCheck()) + { + context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + } + + if (options.Force()) + { + context->Args.AddArg(Execution::Args::Type::Force); + } + + // If the PackageInstallScope is anything other than ::Any then set it as a requirement. + auto manifestScope = GetManifestScope(options.PackageInstallScope()); + if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); + context->Add(manifestScope.second); + } + + if (options.PackageInstallMode() == PackageInstallMode::Interactive) + { + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageInstallMode() == PackageInstallMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + auto installerType = GetManifestInstallerType(options.InstallerType()); + if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallerType, AppInstaller::Manifest::InstallerTypeToString(installerType)); + } + + if (!options.PreferredInstallLocation().empty()) + { + context->Args.AddArg(Execution::Args::Type::InstallLocation, ::AppInstaller::Utility::ConvertToUTF8(options.PreferredInstallLocation())); + } + + if (!options.ReplacementInstallerArguments().empty()) + { + context->Args.AddArg(Execution::Args::Type::Override, ::AppInstaller::Utility::ConvertToUTF8(options.ReplacementInstallerArguments())); + } + + if (!options.AdditionalInstallerArguments().empty()) + { + context->Args.AddArg(Execution::Args::Type::CustomSwitches, ::AppInstaller::Utility::ConvertToUTF8(options.AdditionalInstallerArguments())); + } + + if (options.AllowedArchitectures().Size() != 0) + { + std::vector allowedArchitectures; + for (auto architecture : options.AllowedArchitectures()) + { + auto convertedArchitecture = GetUtilityArchitecture(architecture); + if (convertedArchitecture) + { + allowedArchitectures.push_back(convertedArchitecture.value()); + } + } + context->Add(std::move(allowedArchitectures)); + } + + // Note: AdditionalPackageCatalogArguments is not needed during install since the manifest is already known so no additional calls to the source are needed. The property is deprecated. + + if (options.AcceptPackageAgreements()) + { + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + + if (options.SkipDependencies()) + { + 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 + { + // Note: If no install options are specified, we assume the caller is accepting the package agreements by default. + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + } + + void PopulateContextFromUninstallOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::UninstallOptions options) + { + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + if (options.Force()) + { + context->Args.AddArg(Execution::Args::Type::Force); + } + + if (options.PackageUninstallMode() == PackageUninstallMode::Interactive) + { + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageUninstallMode() == PackageUninstallMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + auto uninstallScope = GetManifestUninstallScope(options.PackageUninstallScope()); + if (uninstallScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(uninstallScope)); + } + } + } + + void PopulateContextFromDownloadOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::DownloadOptions options) + { + if (options) + { + if (!options.DownloadDirectory().empty()) + { + context->Args.AddArg(Execution::Args::Type::DownloadDirectory, ::AppInstaller::Utility::ConvertToUTF8(options.DownloadDirectory())); + } + if (!options.Locale().empty()) + { + context->Args.AddArg(Execution::Args::Type::Locale, ::AppInstaller::Utility::ConvertToUTF8(options.Locale())); + } + if (options.AllowHashMismatch()) + { + context->Args.AddArg(Execution::Args::Type::HashOverride); + } + if (options.SkipDependencies()) + { + context->Args.AddArg(Execution::Args::Type::SkipDependencies); + } + if (options.AcceptPackageAgreements()) + { + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + auto manifestScope = GetManifestScope(options.Scope()); + if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + context->Args.AddArg(Execution::Args::Type::InstallScope, ScopeToString(manifestScope.first)); + } + + auto architecture = options.Architecture(); + if (architecture != Windows::System::ProcessorArchitecture::Unknown) + { + auto convertedArchitecture = GetUtilityArchitecture(architecture); + if (convertedArchitecture) + { + context->Args.AddArg(Execution::Args::Type::InstallerArchitecture, ToString(convertedArchitecture.value())); + } + } + + auto installerType = GetManifestInstallerType(options.InstallerType()); + if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) + { + 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())); + } + + if (options.SkipMicrosoftStoreLicense()) + { + context->Args.AddArg(Execution::Args::Type::SkipMicrosoftStorePackageLicense); + } + + WindowsPlatform platform = options.Platform(); + if (platform != WindowsPlatform::Unknown) + { + context->Args.AddArg(Execution::Args::Type::Platform, AppInstaller::Manifest::PlatformToString(GetPlatformEnum(platform))); + } + + hstring targetOSVersion = options.TargetOSVersion(); + if (!targetOSVersion.empty()) + { + context->Args.AddArg(Execution::Args::Type::OSVersion, ::AppInstaller::Utility::ConvertToUTF8(targetOSVersion)); + } + } + } + + void PopulateContextFromRepairOptions( + ::AppInstaller::CLI::Execution::Context* context, + winrt::Microsoft::Management::Deployment::RepairOptions options) + { + if (options) + { + if (!options.LogOutputPath().empty()) + { + context->Args.AddArg(Execution::Args::Type::Log, ::AppInstaller::Utility::ConvertToUTF8(options.LogOutputPath())); + context->Args.AddArg(Execution::Args::Type::VerboseLogs); + } + + if (options.PackageRepairMode() == PackageRepairMode::Interactive) + { + context->Args.AddArg(Execution::Args::Type::Interactive); + } + else if (options.PackageRepairMode() == PackageRepairMode::Silent) + { + context->Args.AddArg(Execution::Args::Type::Silent); + } + + if (options.AcceptPackageAgreements()) + { + context->Args.AddArg(Execution::Args::Type::AcceptPackageAgreements); + } + + if (options.AllowHashMismatch()) + { + context->Args.AddArg(Execution::Args::Type::HashOverride); + } + + if (options.BypassIsStoreClientBlockedPolicyCheck()) + { + context->SetFlags(Execution::ContextFlag::BypassIsStoreClientBlockedPolicyCheck); + } + + if (options.Force()) + { + context->Args.AddArg(Execution::Args::Type::Force); + } + + auto repairScope = GetManifestRepairScope(options.PackageRepairScope()); + if (repairScope != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + 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())); + } + } + } + + template + std::unique_ptr CreateContextFromOperationOptions( + TOptions options, + std::wstring callerProcessInfoString) + { + std::unique_ptr context = std::make_unique(); + hstring correlationData = (options) ? options.CorrelationData() : L""; + + context->SetContextLoggers(correlationData, GetComCallerName(AppInstaller::Utility::ConvertToUTF8(callerProcessInfoString))); + + // Convert the options to arguments for the installer. + if constexpr (std::is_same_v) + { + PopulateContextFromInstallOptions(context.get(), options); + } + else if constexpr (std::is_same_v) + { + PopulateContextFromUninstallOptions(context.get(), options); + } + else if constexpr (std::is_same_v) + { + PopulateContextFromDownloadOptions(context.get(), options); + } + else if constexpr (std::is_same_v) + { + PopulateContextFromRepairOptions(context.get(), options); + } + + return context; + } + + std::shared_ptr GetExistingQueueItemForPackage(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + std::shared_ptr queueItem = nullptr; + std::unique_ptr context = std::make_unique(); + if (catalogInfo) + { + // If the caller has passed in the catalog they expect the package to have come from, then only look for an install from that catalog. + // Fail if they've used a catalog that doesn't have an Id. This can currently happen for Info objects that come from PackageCatalogReference objects for REST catalogs. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, catalogInfo.Id().empty()); + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ catalogInfo.Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + return queueItem; + } + + // If the caller has not specified the catalog, then check InstalledVersion. When the package comes from the Installing catalog the PackageCatalog + // of the InstalledVersion will be set to the original catalog that the install was from, so checking the InstalledVersion first is most likely to + // find a result. + Microsoft::Management::Deployment::PackageVersionInfo installedVersionInfo = package.InstalledVersion(); + if (installedVersionInfo) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ installedVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } + + // If InstalledVersion was not found, check DefaultInstallVersion + Microsoft::Management::Deployment::PackageVersionInfo defaultInstallVersionInfo = package.DefaultInstallVersion(); + if (defaultInstallVersionInfo) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ defaultInstallVersionInfo.PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } + + // Finally check all catalogs in AvailableVersions. + for (Microsoft::Management::Deployment::PackageVersionId versionId : package.AvailableVersions()) + { + auto searchItem = Execution::OrchestratorQueueItemFactory::CreateItemForSearch(std::wstring{ package.Id() }, std::wstring{ package.GetPackageVersionInfo(versionId).PackageCatalog().Info().Id() }, std::move(context)); + queueItem = Execution::ContextOrchestrator::Instance().GetQueueItem(searchItem->GetId()); + if (queueItem) + { + return queueItem; + } + } + return nullptr; + } + + std::unique_ptr CreateQueueItemForInstall( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::InstallOptions options, + bool isUpgrade) + { + // Add manifest and PackageVersion to context for install/upgrade. + // If the version of the package is specified use that, otherwise use the default. + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); + AddPackageManifestToContext(packageVersionInfo, comContext.get()); + + if (isUpgrade) + { + AppInstaller::Utility::VersionAndChannel installedVersion{ winrt::to_string(package.InstalledVersion().Version()), winrt::to_string(package.InstalledVersion().Channel()) }; + AppInstaller::Utility::VersionAndChannel upgradeVersion{ winrt::to_string(packageVersionInfo.Version()), winrt::to_string(packageVersionInfo.Channel()) }; + + // Perform upgrade version check + if (upgradeVersion.GetVersion().IsUnknown()) + { + if (!(options.AllowUpgradeToUnknownVersion() && + AppInstaller::Utility::ICUCaseInsensitiveEquals(installedVersion.GetChannel().ToString(), upgradeVersion.GetChannel().ToString()))) + { + THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_UNKNOWN); + } + } + else if (!installedVersion.IsUpdatedBy(upgradeVersion)) + { + THROW_HR(APPINSTALLER_CLI_ERROR_UPGRADE_VERSION_NOT_NEWER); + } + + // Set upgrade flag + comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseUpdate); + // Add installed version + AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); + } + + return Execution::OrchestratorQueueItemFactory::CreateItemForInstall(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext), isUpgrade); + } + + std::unique_ptr CreateQueueItemForUninstall( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + // Add installed version + AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); + + // Add Package which is used by RecordUninstall later for removing from tracking catalog of correlated available sources as best effort + winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); + comContext->Add(internalPackage); + + return Execution::OrchestratorQueueItemFactory::CreateItemForUninstall(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); + } + + std::unique_ptr CreateQueueItemForDownload( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package, + winrt::Microsoft::Management::Deployment::DownloadOptions options) + { + // Add manifest and PackageVersion to context for download. + // If the version of the package is specified use that, otherwise use the default. + Microsoft::Management::Deployment::PackageVersionInfo packageVersionInfo = GetPackageVersionInfo(package, options); + AddPackageManifestToContext(packageVersionInfo, comContext.get()); + + comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerDownloadOnly); + + return Execution::OrchestratorQueueItemFactory::CreateItemForDownload(std::wstring{ package.Id() }, std::wstring{ packageVersionInfo.PackageCatalog().Info().Id() }, std::move(comContext)); + } + + std::unique_ptr CreateQueueItemForRepair( + std::unique_ptr<::AppInstaller::CLI::Execution::COMContext> comContext, + winrt::Microsoft::Management::Deployment::CatalogPackage package) + { + // Add installed version + AddInstalledVersionToContext(package.InstalledVersion(), comContext.get()); + + // Add Package which is used to co-relate installed package with available package for repair + winrt::Microsoft::Management::Deployment::implementation::CatalogPackage* catalogPackageImpl = get_self(package); + std::shared_ptr<::AppInstaller::Repository::ICompositePackage> internalPackage = catalogPackageImpl->GetRepositoryPackage(); + comContext->Add(internalPackage); + + comContext->SetFlags(AppInstaller::CLI::Execution::ContextFlag::InstallerExecutionUseRepair); + + return Execution::OrchestratorQueueItemFactory::CreateItemForRepair(std::wstring{ package.Id() }, std::wstring{ package.InstalledVersion().PackageCatalog().Info().Id() }, std::move(comContext)); + } + + template + winrt::Windows::Foundation::IAsyncOperationWithProgress GetPackageOperation( + bool canCancelQueueItem, + std::shared_ptr queueItemParam, + winrt::Microsoft::Management::Deployment::CatalogPackage package = nullptr, + TOptions options = nullptr, + std::wstring callerProcessInfoString = {}, + bool isUpgrade = false) + { + winrt::hresult terminationHR = S_OK; + uint32_t operationError = 0; + hstring correlationData = (options) ? options.CorrelationData() : L""; + ::Workflow::ExecutionStage executionStage = ::Workflow::ExecutionStage::Initial; + + try + { + // re-scope the parameter to inside the try block to avoid lifetime management issues. + std::shared_ptr queueItem = std::move(queueItemParam); + + auto report_progress{ co_await winrt::get_progress_token() }; + auto cancellationToken{ co_await winrt::get_cancellation_token() }; + // co_await does not guarantee that it's on a background thread, so do so explicitly. + co_await winrt::resume_background(); + + if (queueItem == nullptr) + { + std::unique_ptr comContext = CreateContextFromOperationOptions(options, callerProcessInfoString); + + if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForInstall(std::move(comContext), package, options, isUpgrade); + } + else if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForUninstall(std::move(comContext), package); + } + else if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForDownload(std::move(comContext), package, options); + } + else if constexpr (std::is_same_v) + { + queueItem = CreateQueueItemForRepair(std::move(comContext), package); + } + + Execution::ContextOrchestrator::Instance().EnqueueAndRunItem(queueItem); + + if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0, 0, 0 }; + report_progress(queuedProgress); + } + else if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0 }; + report_progress(queuedProgress); + } + else if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0 }; + report_progress(queuedProgress); + } + else if constexpr (std::is_same_v) + { + TProgress queuedProgress{ TProgressState::Queued, 0 }; + report_progress(queuedProgress); + } + } + { + // correlation data is not passed in when retrieving an existing queue item, so get it from the existing context. + correlationData = hstring(queueItem->GetContext().GetCorrelationJson()); + } + + wil::unique_event progressEvent{ wil::EventOptions::None }; + + std::atomic operationProgress; + queueItem->GetContext().AddProgressCallbackFunction([&operationProgress, &progressEvent]( + ReportType reportType, + uint64_t current, + uint64_t maximum, + ::AppInstaller::ProgressType progressType, + ::Workflow::ExecutionStage executionPhase) + { + std::optional operationProgressOptional = GetProgress(reportType, current, maximum, progressType, executionPhase); + if (operationProgressOptional.has_value()) + { + operationProgress = operationProgressOptional.value(); + progressEvent.SetEvent(); + } + return; + } + ); + + std::weak_ptr weakQueueItem(queueItem); + cancellationToken.callback([weakQueueItem, &canCancelQueueItem] + { + if (canCancelQueueItem) + { + auto strongQueueItem = weakQueueItem.lock(); + if (strongQueueItem) { + // The cancellation of the AsyncOperation on the client triggers Cancel which causes the Execute to end. + Execution::ContextOrchestrator::Instance().CancelQueueItem(*strongQueueItem); + } + } + }); + + // Wait for completion or progress events. + // Waiting for both on the same thread ensures that progress is never reported after the async operation itself has completed. + bool completionEventFired = false; + HANDLE operationEvents[2]; + operationEvents[0] = progressEvent.get(); + operationEvents[1] = queueItem->GetCompletedEvent().get(); + while (!completionEventFired) + { + DWORD dwEvent = WaitForMultipleObjects( + _countof(operationEvents) /* number of events */, + operationEvents /* event array */, + FALSE /* bWaitAll, FALSE to wake on any event */, + INFINITE /* wait until operation completion */); + + switch (dwEvent) + { + // operationEvents[0] was signaled, progress + case WAIT_OBJECT_0 + 0: + // The report_progress call will hang when making callbacks to suspended processes so it's important that this is now on a background thread. + // Progress events are not queued - some will be missed if multiple progress events are fired from the ComContext to the callback + // while the report_progress call is hung\in progress. + // Duplicate progress events can be fired if another progress event comes from the ComContext to the callback after the listener + // has been awaked, but before it has gotten the installProgress. + report_progress(operationProgress); + break; + + // operationEvents[1] was signaled, operation completed + case WAIT_OBJECT_0 + 1: + completionEventFired = true; + break; + + // Return value is invalid. + default: + THROW_LAST_ERROR(); + } + } + + if (completionEventFired) + { + // The install command has finished, check for success/failure and how far it got. + terminationHR = queueItem->GetContext().GetTerminationHR(); + executionStage = queueItem->GetContext().GetExecutionStage(); + if (queueItem->GetContext().Contains(Data::OperationReturnCode)) + { + operationError = static_cast(queueItem->GetContext().Get()); + } + } + } + WINGET_CATCH_STORE(terminationHR, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + + // TODO - RebootRequired not yet populated, msi arguments not returned from Execute. + co_return GetOperationResult(executionStage, terminationHR, operationError, correlationData, false); + } + + template + winrt::Windows::Foundation::IAsyncOperationWithProgress GetEmptyAsynchronousResultForOperation( + HRESULT hr, + hstring correlationData) + { + // If a function uses co_await or co_return (i.e. if it is a co_routine), it cannot use return directly. + // This helper helps a function that is not a coroutine itself to return errors asynchronously. + co_return GetOperationResult(::Workflow::ExecutionStage::Initial, hr, 0, correlationData, false); + } + +#define WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_INSTALL_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + // the package should have an installed version to be upgraded. + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString), true /* isUpgrade */); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + hstring correlationData; + WINGET_RETURN_INSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::shared_ptr queueItem = nullptr; + bool canCancelQueueItem = false; + try + { + // Check for permissions + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + if (!canCancelQueueItem) + { + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); + } + + // Get the queueItem synchronously. + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + if (queueItem == nullptr || + (queueItem->GetPackageOperationType() != PackageOperationType::Install && queueItem->GetPackageOperationType() != PackageOperationType::Upgrade)) + { + return nullptr; + } + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_INSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + canCancelQueueItem, std::move(queueItem)); + } + +#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_UNINSTALL_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + // the package should have an installed version to be uninstalled. + WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + hstring correlationData; + WINGET_RETURN_UNINSTALL_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::shared_ptr queueItem = nullptr; + bool canCancelQueueItem = false; + try + { + // Check for permissions + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hrGetCallerId); + canCancelQueueItem = SUCCEEDED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + if (!canCancelQueueItem) + { + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageQuery, callerProcessId)); + } + + // Get the queueItem synchronously. + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + if (queueItem == nullptr || + queueItem->GetPackageOperationType() != PackageOperationType::Uninstall) + { + return nullptr; + } + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_UNINSTALL_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + canCancelQueueItem, std::move(queueItem)); + } + +#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo) + { + hstring correlationData; + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + + HRESULT hr = S_OK; + std::shared_ptr queueItem = nullptr; + try + { + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(EnsureComCallerHasCapability(Capability::PackageQuery)); + + // Get the queueItem synchronously. + queueItem = GetExistingQueueItemForPackage(package, catalogInfo); + if (queueItem == nullptr || + queueItem->GetPackageOperationType() != PackageOperationType::Download) + { + return nullptr; + } + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_DOWNLOAD_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation(true, std::move(queueItem)); + } + +#define WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, boolVal) { if(boolVal) { return GetEmptyAsynchronousResultForOperation(hr, correlationData); }} +#define WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr) { WINGET_RETURN_REPAIR_RESULT_HR_IF(hr, FAILED(hr)) } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options) + { + hstring correlationData = (options) ? options.CorrelationData() : L""; + + // options and catalog can both be null, package must be set. + WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package); + // the package should have an installed version to be repaired. + WINGET_RETURN_REPAIR_RESULT_HR_IF(APPINSTALLER_CLI_ERROR_INVALID_CL_ARGUMENTS, !package.InstalledVersion()); + + HRESULT hr = S_OK; + std::wstring callerProcessInfoString; + try + { + // Check for permissions and get caller info for telemetry. + // This must be done before any co_awaits since it requires info from the rpc caller thread. + auto [hrGetCallerId, callerProcessId] = GetCallerProcessId(); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hrGetCallerId); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(EnsureProcessHasCapability(Capability::PackageManagement, callerProcessId)); + callerProcessInfoString = TryGetCallerProcessInfo(callerProcessId); + } + WINGET_CATCH_STORE(hr, APPINSTALLER_CLI_ERROR_COMMAND_FAILED); + WINGET_RETURN_REPAIR_RESULT_HR_IF_FAILED(hr); + + return GetPackageOperation( + true /*canCancelQueueItem*/, nullptr /*queueItem*/, package, options, std::move(callerProcessInfoString)); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options) + { + LogStartupIfApplicable(); + + // options must be set. + THROW_HR_IF_NULL(E_POINTER, options); + THROW_HR_IF(E_INVALIDARG, options.Name().empty()); + THROW_HR_IF(E_INVALIDARG, options.SourceUri().empty()); + + HRESULT terminationHR = S_OK; + try { + + // Check if running as admin/system. + // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, + // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); + + ::AppInstaller::Repository::Source sourceToAdd = CreateSourceFromOptions(options); + + auto strong_this = get_strong(); + auto report_progress{ co_await winrt::get_progress_token() }; + co_await winrt::resume_background(); + + std::string type = winrt::to_string(options.Type()); + auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(type, report_progress ); + + packageCatalogProgressSink->BeginProgress(); + ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); + sourceToAdd.Add(progress); + packageCatalogProgressSink->EndProgress(false); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + co_return GetAddPackageCatalogResult(terminationHR); + } + + winrt::Windows::Foundation::IAsyncOperationWithProgress PackageManager::RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options) + { + LogStartupIfApplicable(); + + // options must be set. + THROW_HR_IF_NULL(E_POINTER, options); + THROW_HR_IF(E_INVALIDARG, options.Name().empty()); + + HRESULT terminationHR = S_OK; + try { + + // Check if running as admin/system. + // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, + // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); + + auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); + + auto strong_this = get_strong(); + auto report_progress{ co_await winrt::get_progress_token() }; + co_await winrt::resume_background(); + + auto packageCatalogProgressSink = winrt::Microsoft::Management::Deployment::ProgressSinkFactory::CreatePackageCatalogProgressSink(matchingSource.value().Type, report_progress, true); + + packageCatalogProgressSink->BeginProgress(); + ::AppInstaller::Repository::Source sourceToRemove = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; + ::AppInstaller::ProgressCallback progress(packageCatalogProgressSink.get()); + + // If the PreserveData option is set, this is equivalent to the WinGet CLI Reset command on a single source; otherwise, it removes the source. + if (options.PreserveData()) + { + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !sourceToRemove.DropSource(matchingSource.value().Name)); + } + else + { + sourceToRemove.Remove(progress); + } + packageCatalogProgressSink->EndProgress(false); + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + co_return GetRemovePackageCatalogResult(terminationHR); + } + + winrt::hstring PackageManager::Version() const + { + return winrt::hstring{ AppInstaller::Utility::ConvertToUTF16(AppInstaller::Runtime::GetClientVersion()) }; + } + + winrt::Microsoft::Management::Deployment::EditPackageCatalogResult GetEditPackageCatalogResult(winrt::hresult terminationStatus) + { + winrt::Microsoft::Management::Deployment::EditPackageCatalogStatus status = GetPackageCatalogOperationStatus(terminationStatus); + auto editResult = winrt::make_self>(); + editResult->Initialize(status, terminationStatus); + return *editResult; + } + + winrt::Microsoft::Management::Deployment::EditPackageCatalogResult PackageManager::EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options) + { + LogStartupIfApplicable(); + + // options must be set. + THROW_HR_IF_NULL(E_POINTER, options); + THROW_HR_IF(E_INVALIDARG, options.Name().empty()); + + HRESULT terminationHR = S_OK; + try { + + // Check if running as admin/system. + // [NOTE:] For OutOfProc calls, the Windows Package Manager Service executes in the context initiated by the caller process, + // so the same admin/system validation check is applicable for both InProc and OutOfProc calls. + THROW_HR_IF(APPINSTALLER_CLI_ERROR_COMMAND_REQUIRES_ADMIN, !AppInstaller::Runtime::IsRunningAsAdminOrSystem()); + + auto matchingSource = GetMatchingSource(winrt::to_string(options.Name())); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_NAME_DOES_NOT_EXIST, !matchingSource.has_value()); + ::AppInstaller::Repository::Source sourceToEdit = ::AppInstaller::Repository::Source{ matchingSource.value().Name }; + + ::AppInstaller::Repository::SourceEdit edits; + edits.Explicit = options.Explicit(); + edits.Priority = options.Priority(); + + if (sourceToEdit.RequiresChanges(edits)) + { + sourceToEdit.Edit(edits); + } + } + catch (...) + { + terminationHR = AppInstaller::CLI::Workflow::HandleException(nullptr, std::current_exception()); + } + + return GetEditPackageCatalogResult(terminationHR); + } + + CoCreatableMicrosoftManagementDeploymentClass(PackageManager); +} diff --git a/src/Microsoft.Management.Deployment/PackageManager.h b/src/Microsoft.Management.Deployment/PackageManager.h index c973df2dbe..5cabd9f6ab 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.h +++ b/src/Microsoft.Management.Deployment/PackageManager.h @@ -1,72 +1,72 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "PackageManager.g.h" -#include "Public/ComClsids.h" -#include - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -// Forward declaration -namespace AppInstaller::CLI::Execution -{ - struct Context; -} -#endif - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_PackageManager)] - struct PackageManager : PackageManagerT - { - PackageManager(); - - winrt::Windows::Foundation::Collections::IVectorView GetPackageCatalogs(); - winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog); - winrt::Microsoft::Management::Deployment::PackageCatalogReference GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog); - winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPackageCatalogByName(hstring const& catalogName); - winrt::Microsoft::Management::Deployment::PackageCatalogReference CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); - // Contract 2.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); - // Contract 4.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); - // Contract 7.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); - // Contract 11.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options); - // Contract 12.0 - winrt::Windows::Foundation::IAsyncOperationWithProgress - AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options); - winrt::Windows::Foundation::IAsyncOperationWithProgress - RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options); - // Contract 13.0 - winrt::hstring Version() const; - // Contract 28.0 - winrt::Microsoft::Management::Deployment::EditPackageCatalogResult EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options); - }; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void SetComCallerName(std::string name); - void PopulateContextFromInstallOptions(AppInstaller::CLI::Execution::Context* context, winrt::Microsoft::Management::Deployment::InstallOptions options); -#endif -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct PackageManager : PackageManagerT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageManager.g.h" +#include "Public/ComClsids.h" +#include + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +// Forward declaration +namespace AppInstaller::CLI::Execution +{ + struct Context; +} +#endif + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_PackageManager)] + struct PackageManager : PackageManagerT + { + PackageManager(); + + winrt::Windows::Foundation::Collections::IVectorView GetPackageCatalogs(); + winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPredefinedPackageCatalog(winrt::Microsoft::Management::Deployment::PredefinedPackageCatalog const& predefinedPackageCatalog); + winrt::Microsoft::Management::Deployment::PackageCatalogReference GetLocalPackageCatalog(winrt::Microsoft::Management::Deployment::LocalPackageCatalog const& localPackageCatalog); + winrt::Microsoft::Management::Deployment::PackageCatalogReference GetPackageCatalogByName(hstring const& catalogName); + winrt::Microsoft::Management::Deployment::PackageCatalogReference CreateCompositePackageCatalog(winrt::Microsoft::Management::Deployment::CreateCompositePackageCatalogOptions const& options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + InstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); + // Contract 2.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + GetInstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); + // Contract 4.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + UpgradePackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::InstallOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + UninstallPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::UninstallOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + GetUninstallProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); + // Contract 7.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + DownloadPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::DownloadOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + GetDownloadProgress(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::PackageCatalogInfo catalogInfo); + // Contract 11.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + RepairPackageAsync(winrt::Microsoft::Management::Deployment::CatalogPackage package, winrt::Microsoft::Management::Deployment::RepairOptions options); + // Contract 12.0 + winrt::Windows::Foundation::IAsyncOperationWithProgress + AddPackageCatalogAsync(winrt::Microsoft::Management::Deployment::AddPackageCatalogOptions options); + winrt::Windows::Foundation::IAsyncOperationWithProgress + RemovePackageCatalogAsync(winrt::Microsoft::Management::Deployment::RemovePackageCatalogOptions options); + // Contract 13.0 + winrt::hstring Version() const; + // Contract 28.0 + winrt::Microsoft::Management::Deployment::EditPackageCatalogResult EditPackageCatalog(winrt::Microsoft::Management::Deployment::EditPackageCatalogOptions options); + }; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void SetComCallerName(std::string name); + void PopulateContextFromInstallOptions(AppInstaller::CLI::Execution::Context* context, winrt::Microsoft::Management::Deployment::InstallOptions options); +#endif +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct PackageManager : PackageManagerT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 4a26b4ec12..7dce535960 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1,1830 +1,1830 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -namespace Microsoft.Management.Deployment -{ - [contractversion(29)] // For version 1.29 - apicontract WindowsPackageManagerContract{}; - - /// State of the install - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallProgressState - { - /// The install is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from downloading or installing. - Queued, - /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will - /// end the download and prevent the package from installing. - Downloading, - /// The install is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not - /// stop the installation or the post install cleanup. - Installing, - /// The installer has completed and cleanup actions are in progress. Cancellation of the - /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the install. - PostInstall, - /// The operation has completed. - Finished, - }; - - /// Progress object for the install - /// DESIGN NOTE: percentage for the install as a whole is purposefully not included as there is no way to - /// estimate progress when the installer is running. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - struct InstallProgress - { - /// State of the install - PackageInstallProgressState State; - /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes downloaded if known - UInt64 BytesDownloaded; - /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes required if known - UInt64 BytesRequired; - /// Download percentage completed - Double DownloadProgress; - /// Install percentage if known. - Double InstallationProgress; - }; - - /// Status of the Install call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum InstallResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - DownloadError, - InstallError, - ManifestError, - NoApplicableInstallers, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - NoApplicableUpgrade, - }, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - PackageAgreementsNotAccepted, - } - }; - - /// Result of the install - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass InstallResult - { - /// Used by a caller to correlate the install with a caller's data. - String CorrelationData { get; }; - /// Whether a restart is required to complete the install. - Boolean RebootRequired { get; }; - - /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED - InstallResultStatus Status { get; }; - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// The error code from the install attempt. Only valid if the Status is InstallError. - /// This value's meaning will require knowledge of the specific installer or install technology. - UInt32 InstallerErrorCode { get; }; - } - } - - /// State of the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - enum PackageUninstallProgressState - { - /// The uninstall is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from uninstalling. - Queued, - /// The uninstall is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not - /// stop the installation or the post uninstall steps. - Uninstalling, - /// The uninstaller has completed and cleanup actions are in progress. Cancellation of the - /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the uninstall. - PostUninstall, - /// The operation has completed. - Finished, - }; - - /// Progress object for the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - struct UninstallProgress - { - /// State of the uninstall - PackageUninstallProgressState State; - /// Uninstall percentage if known. - Double UninstallationProgress; - }; - - /// Status of the uninstall call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - enum UninstallResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - UninstallError, - ManifestError, - }; - - /// Result of the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - runtimeclass UninstallResult - { - /// Used by a caller to correlate the install with a caller's data. - String CorrelationData { get; }; - /// Whether a restart is required to complete the install. - Boolean RebootRequired { get; }; - - /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED - UninstallResultStatus Status { get; }; - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - - /// The error code from the uninstall attempt. Only valid if the Status is UninstallError. - /// This value's meaning will require knowledge of the specific uninstaller or install technology. - UInt32 UninstallerErrorCode { get; }; - } - - /// State of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum PackageRepairProgressState - { - /// The repair is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from repairing. - Queued, - /// The repair is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not - /// stop the repair or the post repair steps. - Repairing, - /// The repair has completed and cleanup actions are in progress. Cancellation of the - /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the repair. - PostRepair, - /// The operation has completed. - Finished, - }; - - /// Progress object for the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - struct RepairProgress - { - /// State of the repair - PackageRepairProgressState State; - - /// Repair percentage if known. - Double RepairCompletionProgress; - }; - - /// Status of the repair call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - /// DESIGN NOTE: RepairResultStatus from AppInstallerErrors.h is not implemented in V1. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum RepairResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - DownloadError, - InternalError, - InvalidOptions, - RepairError, - ManifestError, - NoApplicableRepairer, - PackageAgreementsNotAccepted, - }; - - /// Result of the repair - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - runtimeclass RepairResult - { - /// Used by a caller to correlate the repair with a caller's data. - String CorrelationData { get; }; - - /// Whether a restart is required to complete the repair. - Boolean RebootRequired { get; }; - - /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED - RepairResultStatus Status { get; }; - - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - - /// The error code from the repair attempt. Only valid if the Status is RepairError. - /// This value's meaning will require knowledge of the specific repairer or repair technology. - UInt32 RepairerErrorCode { get; }; - } - - /// State of the download - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - enum PackageDownloadProgressState - { - /// The download is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this - /// state will prevent the package from downloading. - Queued, - /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will - /// end the download. - Downloading, - /// The operation has completed. - Finished, - }; - - /// Status of the download call - /// Implementation Note: Errors mapped from AppInstallerErrors.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - enum DownloadResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - DownloadError, - ManifestError, - NoApplicableInstallers, - PackageAgreementsNotAccepted, - }; - - /// Result of the download - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - runtimeclass DownloadResult - { - /// Used by a caller to correlate the download with a caller's data. - String CorrelationData { get; }; - - /// Batched error code. - DownloadResultStatus Status { get; }; - - /// The error code of the overall operation. - HRESULT ExtendedErrorCode { get; }; - }; - - /// Progress object for the uninstall - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - struct PackageDownloadProgress - { - /// State of the download - PackageDownloadProgressState State; - - /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes downloaded if known - UInt64 BytesDownloaded; - - /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. - /// Number of bytes required if known - UInt64 BytesRequired; - - /// Download percentage completed - Double DownloadProgress; - }; - - /// IMPLEMENTATION NOTE: SourceOrigin from winget/RepositorySource.h - /// Defines the origin of the package catalog details. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageCatalogOrigin - { - /// Predefined means it came as part of the Windows Package Manager package and cannot be removed. - Predefined, - /// User means it was added by the user and could be removed. - User, - }; - - /// IMPLEMENTATION NOTE: SourceTrustLevel from winget/RepositorySource.h - /// Defines the trust level of the package catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageCatalogTrustLevel - { - None, - Trusted, - }; - - /// IMPLEMENTATION NOTE: SourceDetails from winget/RepositorySource.h - /// Interface for retrieving information about an package catalog without acting on it. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalogInfo - { - /// The package catalog's unique identifier. - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.Winget.Source_8wekyb3d8bbwe" - /// For contoso sample on msdn "contoso" - String Id { get; }; - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name { get; }; - /// The type of the package catalog. - /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". - /// For contoso sample on msdn "Microsoft.PreIndexed.Package" - String Type { get; }; - /// The argument used when adding the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" - /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" - String Argument { get; }; - /// The last time that this package catalog was updated. - Windows.Foundation.DateTime LastUpdateTime { get; }; - /// The origin of the package catalog. - PackageCatalogOrigin Origin { get; }; - /// The trust level of the package catalog - PackageCatalogTrustLevel TrustLevel { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - /// Excludes a source from discovery unless specified. - Boolean Explicit{ get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// The priority of this catalog. Higher values are sorted first. - Int32 Priority{ get; }; - } - } - - /// A metadata item of a package version. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageVersionMetadataField - { - /// The InstallerType of an installed package - InstallerType, - /// The Scope of an installed package - InstalledScope, - /// The system path where the package is installed - InstalledLocation, - /// The standard uninstall command; which may be interactive - StandardUninstallCommand, - /// An uninstall command that should be non-interactive - SilentUninstallCommand, - /// The publisher of the package - PublisherDisplayName, - }; - - /// The result of a comparison. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - enum CompareResult - { - /// The comparison did not result in a succesful ordering. - Unknown, - /// The object value is lesser than the given value. - Lesser, - /// The object value is equal to the given value. - Equal, - /// The object value is greater than the given value. - Greater, - }; - - /// IMPLEMENTATION NOTE: IPackageVersion from winget/RepositorySearch.h - /// A single package version. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageVersionInfo - { - /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from winget/RepositorySearch.h - /// Gets any metadata associated with this package version. - /// Primarily stores data on installed packages. - /// Metadata fields may have no value (e.g. packages that aren't installed will not have an InstalledLocation). - String GetMetadata(PackageVersionMetadataField metadataField); - /// IMPLEMENTATION NOTE: PackageVersionProperty fields from winget/RepositorySearch.h - String Id { get; }; - String DisplayName { get; }; - String Version { get; }; - String Channel { get; }; - /// DESIGN NOTE: RelativePath from winget/RepositorySearch.h is excluded as not needed. - /// String RelativePath; - - /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from winget/RepositorySearch.h - /// PackageFamilyName and ProductCode can have multiple values. - Windows.Foundation.Collections.IVectorView PackageFamilyNames { get; }; - Windows.Foundation.Collections.IVectorView ProductCodes { get; }; - - /// Gets the package catalog where this package version is from. - PackageCatalog PackageCatalog { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - { - /// Compares the given value against the package version of this object, with the result being - /// the enum value that represents where PackageVersionInfo::Version is ordered relative to the - /// versionString. "if (this.CompareToVersion(that) == Greater)" can be thought of as reading - /// the sentence "If this is compared to version that and is found to be greater". - /// IE if PackageVersionInfo::Version returns "2", then CompareToVersion("1") will return Greater. - /// Passing in an empty string will result in Unknown. - CompareResult CompareToVersion(String versionString); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// Checks if this package version has at least one applicable installer. - Boolean HasApplicableInstaller(InstallOptions options); - - /// Gets the publisher string for this package version, if one is available. - String Publisher { get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// Gets the package catalog metadata of this package version with the default localization based on user settings. - CatalogPackageMetadata GetCatalogPackageMetadata(); - - /// Gets the package catalog metadata of this package version with the preferred locale. - CatalogPackageMetadata GetCatalogPackageMetadata(String preferredLocale); - - /// Gets the applicable installer for this package version. - PackageInstallerInfo GetApplicableInstaller(InstallOptions options); - } - } - - /// IMPLEMENTATION NOTE: PackageVersionKey from winget/RepositorySearch.h - /// A key to identify a package version within a package. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageVersionId - { - /// The package catalog id that this version came from. - String PackageCatalogId { get; }; - /// The version. - String Version { get; }; - /// The channel. - String Channel { get; }; - }; - - /// The package installer type. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum PackageInstallerType - { - /// Unknown type. - Unknown, - /// Inno type. - Inno, - /// Wix type. - Wix, - /// Msi type. - Msi, - /// Nullsoft type. - Nullsoft, - /// Zip type. - Zip, - /// Msix or Appx type. - Msix, - /// Exe type. - Exe, - /// Burn type. - Burn, - /// MSStore type. - MSStore, - /// Portable type. - Portable, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - { - /// Font type. - Font, - }, - }; - - /// The package installer scope. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum PackageInstallerScope - { - /// Scope not declared. - Unknown, - /// User scope. - User, - /// System scope. - System, - }; - - /// The package installer elevation requirement. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum ElevationRequirement - { - /// Elevation requirement not declared. - Unknown, - /// Package installer requires elevation. - ElevationRequired, - /// Package installer prohibits elevation. - ElevationProhibited, - /// Package installer elevates self. - ElevatesSelf, - }; - - /// Interface for retrieving information about a package installer. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass PackageInstallerInfo - { - /// The package installer type. - PackageInstallerType InstallerType { get; }; - /// The nested package installer type for archives. - PackageInstallerType NestedInstallerType { get; }; - /// The package installer architecture. - Windows.System.ProcessorArchitecture Architecture { get; }; - /// The package installer scope. - PackageInstallerScope Scope { get; }; - /// The package installer locale. - String Locale { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// 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. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - [flags] - enum InstalledStatusType - { - /// None is checked. - None = 0x0, - /// Check Apps and Features entry. - AppsAndFeaturesEntry = 0x0001, - /// Check Apps and Features entry install location if applicable. - AppsAndFeaturesEntryInstallLocation = 0x0002, - /// Check Apps and Features entry install location with installed files if applicable. - AppsAndFeaturesEntryInstallLocationFile = 0x0004, - /// Check default install location if applicable. - DefaultInstallLocation = 0x0008, - /// Check default install location with installed files if applicable. - DefaultInstallLocationFile = 0x0010, - - /// Below are helper values for calling CheckInstalledStatus as input. - /// AppsAndFeaturesEntry related checks - AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, - /// DefaultInstallLocation related checks - AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, - /// All checks - AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, - }; - - /// Interface representing an individual installed status. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass InstalledStatus - { - /// The installed status type. - InstalledStatusType Type { get; }; - /// The installed status path. - String Path { get; }; - /// The installed status result. - HRESULT Status { get; }; - }; - - /// Interface for retrieving information about a package installer installed status. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass PackageInstallerInstalledStatus - { - /// The package installer info. - PackageInstallerInfo InstallerInfo { get; }; - /// A list of various types of installed status of the package installer. - Windows.Foundation.Collections.IVectorView InstallerInstalledStatus { get; }; - }; - - /// Status of the check installed status call. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum CheckInstalledStatusResultStatus - { - Ok, - InternalError, - }; - - /// Interface for retrieving information about a package installer installed status. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - runtimeclass CheckInstalledStatusResult - { - /// Status of the check installed status call. - CheckInstalledStatusResultStatus Status { get; }; - - /// A list of package installer installed status. - Windows.Foundation.Collections.IVectorView PackageInstalledStatus { get; }; - }; - - /// IMPLEMENTATION NOTE: IPackage from winget/RepositorySearch.h - /// A package, potentially containing information about it's local state and the available versions. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass CatalogPackage - { - /// IMPLEMENTATION NOTE: PackageProperty fields from winget/RepositorySearch.h - /// Gets a property of this package. - String Id { get; }; - String Name { get; }; - - /// Gets the installed package information if the package is installed. - PackageVersionInfo InstalledVersion { get; }; - - /// Gets all available versions of this package. Ordering is not guaranteed. - Windows.Foundation.Collections.IVectorView AvailableVersions { get; }; - - /// Gets the version of this package that will be installed if version is not set in InstallOptions. - PackageVersionInfo DefaultInstallVersion { get; }; - - /// Gets a specific version of this package. - PackageVersionInfo GetPackageVersionInfo(PackageVersionId versionKey); - - /// Gets a value indicating whether an available version is newer than the installed version. - Boolean IsUpdateAvailable { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Check the installed status of the package. For more accurate and complete installed status, it's required to - /// call this method from a composite package from a newly created package catalog with installed info. - /// This may require downloading information from a server. - Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(InstalledStatusType checkTypes); - CheckInstalledStatusResult CheckInstalledStatus(InstalledStatusType checkTypes); - Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(); - CheckInstalledStatusResult CheckInstalledStatus(); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// Determines the priority of the catalog for this package object. - /// This should match the priority of the DefaultInstallVersion, but it is much more efficient than using that route. - /// May be null if the package refers only to an installed item. - Windows.Foundation.IReference CatalogPriority { get; }; - } - } - - /// IMPLEMENTATION NOTE: CompositeSearchBehavior from winget/RepositorySource.h - /// Search behavior for composite catalogs. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum CompositeSearchBehavior - { - /// Search local catalogs only - LocalCatalogs, - /// Search remote catalogs only, don't check local catalogs for InstalledVersion - RemotePackagesFromRemoteCatalogs, - /// Search remote catalogs, and check local catalogs for InstalledVersion - RemotePackagesFromAllCatalogs, - /// Search both local and remote catalogs. - AllCatalogs, - }; - - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageFieldMatchOption - { - Equals, - EqualsCaseInsensitive, - StartsWithCaseInsensitive, - ContainsCaseInsensitive, - }; - - /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h - /// The field to match on. - /// The values must be declared in order of preference in search results. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageMatchField - { - CatalogDefault, - Id, - Name, - Moniker, - Command, - Tag, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 3)] - { - PackageFamilyName, - ProductCode, - } - /// DESIGN NOTE: The following PackageFieldMatchOption from winget/RepositorySearch.h are not implemented in V1. - /// NormalizedNameAndPublisher, - }; - - /// IMPLEMENTATION NOTE: PackageMatchFilter from winget/RepositorySearch.h - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageMatchFilter - { - PackageMatchFilter(); - /// The type of string comparison for matching - PackageFieldMatchOption Option; - /// The field to search - PackageMatchField Field; - /// The value to match - String Value; - /// DESIGN NOTE: "Additional" from RequestMatch winget/RepositorySearch.h is not implemented here. - } - - /// IMPLEMENTATION NOTE: MatchResult from winget/RepositorySearch.h - /// A single result from the search. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass MatchResult - { - /// The package found by the search request. - CatalogPackage CatalogPackage { get; }; - - /// The highest order field on which the package matched the search. - PackageMatchFilter MatchCriteria { get; }; - } - - /// Status of the FindPackages call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum FindPackagesResultStatus - { - Ok, - BlockedByPolicy, - CatalogError, - InternalError, - InvalidOptions, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - { - AuthenticationError, - AccessDenied, - } - }; - - /// IMPLEMENTATION NOTE: SearchResult from winget/RepositorySearch.h - /// Search result data returned from FindPackages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass FindPackagesResult - { - /// Error codes - FindPackagesResultStatus Status{ get; }; - - /// The full set of results from the search. - Windows.Foundation.Collections.IVectorView Matches { get; }; - - /// If true, the results were truncated by the given ResultLimit - /// USAGE NOTE: Windows Package Manager does not support result pagination, there is no way to continue - /// getting more results. - Boolean WasLimitExceeded { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - /// The error code of the operation. - HRESULT ExtendedErrorCode{ get; }; - } - } - - /// Options for FindPackages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass FindPackagesOptions - { - FindPackagesOptions(); - - /// DESIGN NOTE: - /// This class maps to SearchRequest from winget/RepositorySearch.h - /// That class is a container for data used to filter the available manifests in an package catalog. - /// Its properties can be thought of as: - /// (Query || Inclusions...) && Filters... - /// If Query and Inclusions are both empty, the starting data set will be the entire database. - /// Everything && Filters... - /// That has been translated in this api so that - /// Inclusions are Selectors below - /// Filters are Filters below - /// Query is PackageFieldMatchOption::PackageCatalogDefined and in the Selector list. - /// USAGE NOTE: Only one selector with PackageFieldMatchOption::PackageCatalogDefined is allowed. - - /// Selectors = you have to match at least one selector (if there are no selectors, then nothing is selected) - Windows.Foundation.Collections.IVector Selectors { get; }; - /// Filters = you have to match all filters(if there are no filters, then there is no filtering of selected items) - Windows.Foundation.Collections.IVector Filters{ get; }; - - /// Restricts the length of the returned results to the specified count. - UInt32 ResultLimit; - } - - /// IMPLEMENTATION NOTE: Source from winget/RepositorySource.h - /// A catalog for searching for packages - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalog - { - /// Gets a value indicating whether this package catalog is a composite of other package catalogs, - /// and thus the packages may come from disparate package catalogs as well. - Boolean IsComposite { get; }; - /// The details of the package catalog if it is not a composite. - PackageCatalogInfo Info { get; }; - - /// Searches for Packages in the catalog. - Windows.Foundation.IAsyncOperation FindPackagesAsync(FindPackagesOptions options); - FindPackagesResult FindPackages(FindPackagesOptions options); - } - - /// Authentication mode - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - enum AuthenticationMode - { - /// Always use interactive authentication flow on first authentication request, following requests may use cached result. - Interactive, - /// Try silent authentication flow first. If failed, use interactive authentication flow. - SilentPreferred, - /// Only use silent authentication flow. If failed, fail the authentication. - Silent, - }; - - /// Authentication related arguments - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - runtimeclass AuthenticationArguments - { - AuthenticationArguments(); - - /// Choice of authentication flow behavior. - AuthenticationMode AuthenticationMode; - /// Optional. The authentication account to be used for authentication. - String AuthenticationAccount; - } - - /// Authentication method - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - enum AuthenticationType - { - Unknown, - None, - MicrosoftEntraId, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - MicrosoftEntraIdForAzureBlobStorage, - } - }; - - /// Microsoft Entra Id related authentication info. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - runtimeclass MicrosoftEntraIdAuthenticationInfo - { - /// The resource identifier or resource uri. - String Resource { get; }; - /// Requested scope. May be empty. - String Scope { get; }; - } - - /// Authentication info. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] - runtimeclass AuthenticationInfo - { - /// The authentication type. - AuthenticationType AuthenticationType { get; }; - /// Microsoft Entra Id related authentication info. - MicrosoftEntraIdAuthenticationInfo MicrosoftEntraIdAuthenticationInfo { get; }; - } - - /// Result of a connection validation callback. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - enum PackageCatalogConnectionValidationResult - { - /// The connection was accepted. - Ok, - /// The connection was rejected because the certificate was not accepted. - CertificateRejected, - }; - - /// Arguments provided to a connection validation callback. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - runtimeclass PackageCatalogConnectionValidationEventArgs - { - /// The server certificate presented during the connection. - Windows.Security.Cryptography.Certificates.Certificate ServerCertificate { get; }; - } - - /// Callback invoked to validate a catalog connection. - /// Return Ok to accept the connection or another value to reject it for that reason. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - delegate PackageCatalogConnectionValidationResult PackageCatalogConnectionValidationHandler(PackageCatalogConnectionValidationEventArgs args); - - /// Status of the Connect call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum ConnectResultStatus - { - Ok, - CatalogError, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - SourceAgreementsNotAccepted, - } - }; - - /// Result of the Connect call - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass ConnectResult - { - /// Error codes - ConnectResultStatus Status { get; }; - - PackageCatalog PackageCatalog { get; }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - /// The error code of the operation. - HRESULT ExtendedErrorCode{ get; }; - } - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - enum RefreshPackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - }; - - /// IMPLEMENTATION NOTE: RefreshPackageCatalogResult - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass RefreshPackageCatalogResult - { - RefreshPackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - /// A reference to a catalog that callers can try to Connect. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageCatalogReference - { - /// Gets a value indicating whether this package catalog is a composite of other package catalogs, - /// and thus the packages may come from disparate package catalogs as well. - Boolean IsComposite { get; }; - - /// The details of the package catalog if it is not a composite. - PackageCatalogInfo Info { get; }; - - /// Opens a catalog. Required before searching. For remote catalogs (i.e. not Installed and Installing) this - /// may require downloading information from a server. - Windows.Foundation.IAsyncOperation ConnectAsync(); - ConnectResult Connect(); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - { - /// A string that will be passed to the source server if using a REST source - String AdditionalPackageCatalogArguments; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// Gets the required agreements for connecting to the package catalog (source). - Windows.Foundation.Collections.IVectorView SourceAgreements { get; }; - - Boolean AcceptSourceAgreements; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 8)] - { - /// Time interval for package catalog to check for an update. Setting to zero will disable the check for update. - Windows.Foundation.TimeSpan PackageCatalogBackgroundUpdateInterval; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 9)] - { - /// When set to true, the opened catalog will only provide the information regarding packages installed from this catalog. - /// In this mode, no external resources should be required. - Boolean InstalledPackageInformationOnly; - } - - [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; - - /// Authentication info from the package catalog. - /// This is defined by individual package catalog. - AuthenticationInfo AuthenticationInfo { get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Updates the package catalog. - /// The progress value, represented as a double, indicates the percentage of update package catalog operation completion. - /// The progress range is from 0 to 100. - Windows.Foundation.IAsyncOperationWithProgress RefreshPackageCatalogAsync(); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// A callback invoked to validate the server certificate during Connect or ConnectAsync. - /// Only available to in-process callers; out-of-process callers will receive E_ACCESSDENIED on set. - /// If the BypassCertificatePinningForMicrosoftStore group policy is disabled, this cannot be set - /// for the MicrosoftStore catalog; attempting to do so produces APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY. - PackageCatalogConnectionValidationHandler ConnectionValidationHandler; - - /// Indicates whether the ConnectionValidationHandler can be set for this catalog reference. - /// Returns false if setting the handler would be blocked by policy (e.g., the - /// BypassCertificatePinningForMicrosoftStore group policy is disabled for the MicrosoftStore catalog). - Boolean IsConnectionValidationHandlerEnabled { get; }; - } - } - - /// Catalogs with PackageCatalogOrigin Predefined - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PredefinedPackageCatalog - { - OpenWindowsCatalog, - MicrosoftStore, - DesktopFrameworks, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - { - OpenWindowsCatalogFont, - }, - }; - - /// Local Catalogs with PackageCatalogOrigin Predefined - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum LocalPackageCatalog - { - InstalledPackages, - InstallingPackages - }; - - /// Options for creating a composite catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass CreateCompositePackageCatalogOptions - { - CreateCompositePackageCatalogOptions(); - - /// Create a composite catalog to allow searching a user defined or pre defined source - /// and a local source (Installed packages) together - IVector Catalogs { get; }; - /// Sets the default search behavior if the catalog is a composite catalog. - CompositeSearchBehavior CompositeSearchBehavior; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Create installed package catalog with required installed scope. - PackageInstallScope InstalledScope; - } - } - - /// Required install scope for the package. If the package does not have an installer that - /// supports the specified scope the Install call will fail with InstallResultStatus.NoApplicableInstallers - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallScope - { - /// An installer with any install scope is valid. - Any, - /// Only User install scope installers are valid - User, - /// Only System installers will be valid - System, - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Both User and Unknown install scope installers are valid - UserOrUnknown, - /// Both System and Unknown install scope installers are valid - SystemOrUnknown, - } - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - enum PackageInstallMode - { - /// The default experience for the installer. Installer may show some UI. - Default, - /// Runs the installer in silent mode. This suppresses the installer's UI to the extent - /// possible (installer may still show some required UI). - Silent, - /// Runs the installer in interactive mode. - Interactive, - }; - - /// Options when installing a package. - /// Intended to allow full compatibility with the "winget install" command line interface. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass InstallOptions - { - InstallOptions(); - - /// Optionally specifies the version from the package to install. If unspecified, the CatalogPackage.DefaultInstallVersion - /// version is used. DefaultInstallVersion is the latest applicable version of the package. DefaultInstallVersion may be - /// empty if there's no applicable version. In that case, install attempts without setting this PackageVersionId - /// will return No Applicable Installer error code. - PackageVersionId PackageVersionId; - - /// Specifies alternate location to install package (if supported). - String PreferredInstallLocation; - /// User or Machine. - PackageInstallScope PackageInstallScope; - /// Silent, Interactive, or Default - PackageInstallMode PackageInstallMode; - /// Directs the logging to a log file. If provided, the installer must have write access to the file - String LogOutputPath; - /// Continues the install even if the hash in the catalog does not match the linked installer. - Boolean AllowHashMismatch; - /// A string that will be passed to the installer. - /// IMPLEMENTATION NOTE: maps to "--override" in the winget cmd line - String ReplacementInstallerArguments; - - /// Used by a caller to correlate the install with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - /// A string that will be passed to the source server if using a REST source - String AdditionalPackageCatalogArguments; - - [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. - Windows.Foundation.Collections.IVector AllowedArchitectures { get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// Allow the upgrade to continue for upgrade packages with manifest versions Unknown. - Boolean AllowUpgradeToUnknownVersion; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Force the operation to continue upon non security related failures. - Boolean Force; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - { - /// A string that will be passed to the installer - /// IMPLEMENTATION NOTE: maps to "--custom" in the winget cmd line - String AdditionalInstallerArguments; - - /// Accept the package agreements required for installation. - Boolean AcceptPackageAgreements; - - /// Bypasses the Disabled Store Policy - Boolean BypassIsStoreClientBlockedPolicyCheck; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - { - /// 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)] - enum PackageUninstallMode - { - /// The default experience for the installer. Installer may show some UI. - Default, - /// Runs the installer in silent mode. This suppresses the installer's UI to the extent - /// possible (installer may still show some required UI). - Silent, - /// Runs the installer in interactive mode. - Interactive, - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - enum PackageUninstallScope - { - /// Use default uninstall behavior. - Any, - /// Uninstall for current user. Currently only applicable to msix. - User, - /// Uninstall for all users. Currently only applicable to msix. - System, - }; - - /// Options when uninstalling a package. - /// Intended to allow full compatibility with the "winget uninstall" command line interface. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - runtimeclass UninstallOptions - { - UninstallOptions(); - - /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for uninstall. - PackageVersionId PackageVersionId; - - /// Silent, Interactive, or Default - PackageUninstallMode PackageUninstallMode; - - /// Directs the logging to a log file. If provided, the installer must have write access to the file - String LogOutputPath; - - /// Used by a caller to correlate the install with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] - { - /// Force the operation to continue upon non security related failures. - Boolean Force; - // The scope the uninstall will perform. Currently only applicable to msix. - PackageUninstallScope PackageUninstallScope; - } - } - - /// The Windows platform type. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - enum WindowsPlatform - { - /// An unknown platform - Unknown, - /// Windows.Universal - Universal, - /// Windows.Desktop - Desktop, - /// Windows.IoT - IoT, - /// Windows.Team - Team, - /// Windows.Holographic - Holographic, - }; - - /// Options when downloading a package. - /// Intended to allow full compatibility with the "winget download" command line interface. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - runtimeclass DownloadOptions - { - DownloadOptions(); - - /// Optionally specifies the version from the package to download. If unspecified the version matching - /// CatalogPackage.GetLatestVersion() is used. - PackageVersionId PackageVersionId; - - /// The package installer type. - PackageInstallerType InstallerType; - - /// The package installer scope. - PackageInstallScope Scope; - - /// The package installer architecture. - Windows.System.ProcessorArchitecture Architecture; - - /// The package installer locale. - String Locale; - - /// The directory where the installers are downloaded to. - String DownloadDirectory; - - /// Continues the download even if the hash in the catalog does not match the linked installer. - Boolean AllowHashMismatch; - - /// Skip downloading the dependencies for the package. - Boolean SkipDependencies; - - /// Accept the package agreements required for download. - Boolean AcceptPackageAgreements; - - /// 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, 13)] - { - /// If the package is licensed from the Microsoft Store, setting this value to true will not attempt to download the license file. - Boolean SkipMicrosoftStoreLicense; - - /// The platform to download the package for. - WindowsPlatform Platform; - - /// When applicable, uses the provided value as the target OS version for the download. - String TargetOSVersion; - } - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum PackageRepairMode - { - /// The default experience for the installer. Installer may show some UI. - Default, - /// Runs the installer in silent mode. This suppresses the installer's UI to the extent - /// possible (installer may still show some required UI). - Silent, - /// Runs the installer in interactive mode. - Interactive, - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - enum PackageRepairScope - { - /// Use default repair behavior. - Any, - /// Repair for current user. Currently only applicable to msix. - User, - /// Repair for all users. - System, - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - runtimeclass RepairOptions - { - RepairOptions(); - - /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for repair. - PackageVersionId PackageVersionId; - - /// The package Repair scope. - PackageRepairScope PackageRepairScope; - - /// The package repair mode. - PackageRepairMode PackageRepairMode; - - /// Optional parameter specifying Accept the package agreements required for download. - Boolean AcceptPackageAgreements; - - /// Used by a caller to correlate the repair with a caller's data. - /// The string must be JSON encoded. - String CorrelationData; - - /// Continues the download even if the hash in the catalog does not match the linked installer used for repair. - Boolean AllowHashMismatch; - - /// Directs the logging to a log file. If provided, the installer must have write access to the file - String LogOutputPath; - - /// Force the operation to continue upon non security related failures. - Boolean Force; - - /// 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 - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass Documentation - { - String DocumentLabel { get; }; - - String DocumentUrl { get; }; - } - - /// Icon resolution - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum IconResolution - { - Custom, - Square16, - Square20, - Square24, - Square30, - Square32, - Square36, - Square40, - Square48, - Square60, - Square64, - Square72, - Square80, - Square96, - Square256, - }; - - /// Icon file type - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum IconFileType - { - Unknown, - Jpeg, - Png, - Ico, - }; - - /// Icon theme - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - enum IconTheme - { - Unknown, - Default, - Light, - Dark, - HighContrast, - }; - - /// IMPLEMENTATION NOTE: Icon from AppInstaller::Manifest::Icon - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass Icon - { - String Url { get; }; - - IconFileType FileType{ get; }; - - IconResolution Resolution{ get; }; - - IconTheme Theme{ get; }; - - UInt8[] Sha256 { get; }; - } - - /// IMPLEMENTATION NOTE: SourceAgreement from AppInstaller::Manifest::SourceAgreement - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass SourceAgreement - { - String Label { get; }; - - String Text { get; }; - - String Url { get; }; - } - - /// IMPLEMENTATION NOTE: PackageAgreement from AppInstaller::Manifest::Agreement - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass PackageAgreement - { - String Label { get; }; - - String Text { get; }; - - String Url { get; }; - } - - /// IMPLEMENTATION NOTE: CatalogPackageMetadata from AppInstaller::Manifest::Localization - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] - runtimeclass CatalogPackageMetadata - { - String Locale { get; }; - - String Publisher { get; }; - - String PublisherUrl { get; }; - - String PublisherSupportUrl { get; }; - - String PrivacyUrl { get; }; - - String Author { get; }; - - String PackageName { get; }; - - String PackageUrl { get; }; - - String License { get; }; - - String LicenseUrl { get; }; - - String Copyright { get; }; - - String CopyrightUrl { get; }; - - String ShortDescription { get; }; - - String Description { get; }; - - Windows.Foundation.Collections.IVectorView Tags { get; }; - - Windows.Foundation.Collections.IVectorView Agreements { get; }; - - Windows.Foundation.Collections.IVectorView Documentations { get; }; - - Windows.Foundation.Collections.IVectorView Icons { get; }; - - String ReleaseNotes { get; }; - - String ReleaseNotesUrl { get; }; - - String PurchaseUrl { get; }; - - String InstallationNotes { get; }; - } - - /// IMPLEMENTATION NOTE: AddPackageCatalogOptions - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass AddPackageCatalogOptions - { - AddPackageCatalogOptions(); - - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name; - - /// The SourceUri used when adding the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" - /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" - String SourceUri; - - /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" - /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". - /// For contoso sample on msdn "Microsoft.PreIndexed.Package" - String Type; - - /// The trust level of the catalog to add. - PackageCatalogTrustLevel TrustLevel; - - /// Custom header to pass to the catalog. - String CustomHeader; - - /// Excludes a source from discovery unless specified. - Boolean Explicit; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// The priority of this catalog. Higher values are sorted first. - Int32 Priority; - } - }; - - /// IMPLEMENTATION NOTE: AddPackageCatalogStatus - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - enum AddPackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - InvalidOptions, - AccessDenied, - AuthenticationError, - }; - - /// IMPLEMENTATION NOTE: AddPackageCatalogResult - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass AddPackageCatalogResult - { - AddPackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogOptions - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass RemovePackageCatalogOptions - { - RemovePackageCatalogOptions(); - - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name; - - /// By default, the value is 'false', resulting in the removal of the package catalog registration - /// from the winget Package catalogs list and the deletion of all associated system artifacts. This - /// mirrors the WinGet Source remove operation on a specific Package Catalog. - /// If set to 'true', it removes the package catalog registration from the Windows Package Catalogs - /// list without any cleanup, similar to the WinGet source reset operation on a specific Package - /// Catalog. - Boolean PreserveData; - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - enum RemovePackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - AccessDenied, - InvalidOptions, - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogResult - /// Result of removing a package catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - runtimeclass RemovePackageCatalogResult - { - RemovePackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - /// IMPLEMENTATION NOTE: EditPackageCatalogOptions - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - runtimeclass EditPackageCatalogOptions - { - EditPackageCatalogOptions(); - - /// The name of the package catalog. - /// SAMPLE VALUES: For OpenWindowsCatalog "winget". - /// For contoso sample on msdn "contoso" - String Name; - - /// Editing the Explicit property has three states: true, false, and not specified (null). - Windows.Foundation.IReference Explicit; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] - { - /// The priority of this catalog. Higher values are sorted first. - Windows.Foundation.IReference Priority; - } - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - enum EditPackageCatalogStatus - { - Ok, - GroupPolicyError, - CatalogError, - InternalError, - AccessDenied, - InvalidOptions, - }; - - /// IMPLEMENTATION NOTE: RemovePackageCatalogResult - /// Result of editing a package catalog. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - runtimeclass EditPackageCatalogResult - { - EditPackageCatalogStatus Status { get; }; - - /// Error codes - HRESULT ExtendedErrorCode { get; }; - }; - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] - runtimeclass PackageManager - { - PackageManager(); - - /// Get the available catalogs. Each source will have a separate catalog. - /// This does not open the catalog. These catalogs can be used individually or merged with CreateCompositePackageCatalogAsync. - /// IMPLEMENTATION NOTE: This is a list of sources returned by Windows Package Manager source list - Windows.Foundation.Collections.IVectorView GetPackageCatalogs(); - /// Get a built in catalog - PackageCatalogReference GetPredefinedPackageCatalog(PredefinedPackageCatalog predefinedPackageCatalog); - /// Get a built in catalog - PackageCatalogReference GetLocalPackageCatalog(LocalPackageCatalog localPackageCatalog); - /// Get a catalog by a known name - PackageCatalogReference GetPackageCatalogByName(String catalogName); - /// Get a composite catalog to allow searching a user defined or pre defined source and a local source - /// (Installing, Installed) together at the same time. - PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] - { - /// Add a catalog to the Windows Package Catalogs. - /// The progress value, represented as a double, indicates the percentage of add package catalog operation completion. - /// The progress range is from 0 to 100. - Windows.Foundation.IAsyncOperationWithProgress AddPackageCatalogAsync(AddPackageCatalogOptions options); - - /// Unregisters a Package Catalog from the Windows Package Catalogs and eliminates the system artifacts based on the provided options. - /// The progress value, represented as a double, indicates the percentage of remove package catalog operation completion. - /// The progress range is from 0 to 100. - Windows.Foundation.IAsyncOperationWithProgress RemovePackageCatalogAsync(RemovePackageCatalogOptions options); - } - - /// Install the specified package - Windows.Foundation.IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] - { - /// Get install progress - Windows.Foundation.IAsyncOperationWithProgress GetInstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - { - /// Upgrade the specified package - Windows.Foundation.IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options); - - /// Uninstall the specified package - Windows.Foundation.IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options); - - /// Get uninstall progress - Windows.Foundation.IAsyncOperationWithProgress GetUninstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] - { - // Download the specified package - Windows.Foundation.IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options); - - // Get download progress - Windows.Foundation.IAsyncOperationWithProgress GetDownloadProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] - { - // Repair the specified package - Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] - { - // The version of the Windows Package Manager that is running. - String Version{ get; }; - } - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - { - /// Edit an existing Windows Package Catalog. - EditPackageCatalogResult EditPackageCatalog(EditPackageCatalogOptions options); - } - } - - /// Global settings for PackageManager operations. - /// This settings should be invoked prior to invocation of PackageManager class. - /// This settings is only exposed in in-proc Com invocation. - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] - runtimeclass PackageManagerSettings - { - PackageManagerSettings(); - - /// Sets caller name to be used in telemetry logging. Default value is the calling process name. - /// Call this before any PackageManager operations. - /// Returns true if successful, false if caller name is already set. - /// This is a one time setup, multiple calls will not override existing caller name. - Boolean SetCallerIdentifier(String callerIdentifier); - - /// Sets state name for state separation. If not set, state will be written in a default location and states may be affected by other callers. - /// Call this before any PackageManager operations. - /// Returns true if successful, false if state name is already set. - /// This is a one time setup, multiple calls will not override existing state name. - Boolean SetStateIdentifier(String stateIdentifier); - - /// Sets custom UserSettings. - /// Returns true if successful, false if settingsContent cannot be parsed or UserSettings is already created. - /// This is a one time setup, multiple calls will not override existing UserSettings. - Boolean SetUserSettings(String settingsContent); - - [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] - { - /// Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not. - /// This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if - /// there are active objects at all times. If set to true it will allow the unload when there are no - /// active objects. - /// Defaults to true. - Boolean CanUnloadPreference{ get; set; }; - - /// Gets or sets a value indicating whether the module should listen for termination signals (CTRL+C, window messages, package updates) - /// and begin the process of cancelling active operations and preventing new ones. - /// If set to false, the caller is responsible for handling these termination signals and cancelling active operations as necessary. - /// Set this to the desired state before any PackageManager operations. Changing it after the first operation for the process may have undefined behavior. - /// Defaults to true. - Boolean TerminationSignalMonitoring{ get; set; }; - } - } - - /// Force midl3 to generate vector marshalling info. - declare - { - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - interface Windows.Foundation.Collections.IVector; - interface Windows.Foundation.Collections.IVectorView; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +namespace Microsoft.Management.Deployment +{ + [contractversion(29)] // For version 1.29 + apicontract WindowsPackageManagerContract{}; + + /// State of the install + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallProgressState + { + /// The install is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from downloading or installing. + Queued, + /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will + /// end the download and prevent the package from installing. + Downloading, + /// The install is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the installation or the post install cleanup. + Installing, + /// The installer has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the install. + PostInstall, + /// The operation has completed. + Finished, + }; + + /// Progress object for the install + /// DESIGN NOTE: percentage for the install as a whole is purposefully not included as there is no way to + /// estimate progress when the installer is running. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + struct InstallProgress + { + /// State of the install + PackageInstallProgressState State; + /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes downloaded if known + UInt64 BytesDownloaded; + /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes required if known + UInt64 BytesRequired; + /// Download percentage completed + Double DownloadProgress; + /// Install percentage if known. + Double InstallationProgress; + }; + + /// Status of the Install call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum InstallResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + DownloadError, + InstallError, + ManifestError, + NoApplicableInstallers, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + NoApplicableUpgrade, + }, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + PackageAgreementsNotAccepted, + } + }; + + /// Result of the install + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass InstallResult + { + /// Used by a caller to correlate the install with a caller's data. + String CorrelationData { get; }; + /// Whether a restart is required to complete the install. + Boolean RebootRequired { get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + InstallResultStatus Status { get; }; + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// The error code from the install attempt. Only valid if the Status is InstallError. + /// This value's meaning will require knowledge of the specific installer or install technology. + UInt32 InstallerErrorCode { get; }; + } + } + + /// State of the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + enum PackageUninstallProgressState + { + /// The uninstall is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from uninstalling. + Queued, + /// The uninstall is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the installation or the post uninstall steps. + Uninstalling, + /// The uninstaller has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the uninstall. + PostUninstall, + /// The operation has completed. + Finished, + }; + + /// Progress object for the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + struct UninstallProgress + { + /// State of the uninstall + PackageUninstallProgressState State; + /// Uninstall percentage if known. + Double UninstallationProgress; + }; + + /// Status of the uninstall call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + enum UninstallResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + UninstallError, + ManifestError, + }; + + /// Result of the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + runtimeclass UninstallResult + { + /// Used by a caller to correlate the install with a caller's data. + String CorrelationData { get; }; + /// Whether a restart is required to complete the install. + Boolean RebootRequired { get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + UninstallResultStatus Status { get; }; + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + + /// The error code from the uninstall attempt. Only valid if the Status is UninstallError. + /// This value's meaning will require knowledge of the specific uninstaller or install technology. + UInt32 UninstallerErrorCode { get; }; + } + + /// State of the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairProgressState + { + /// The repair is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from repairing. + Queued, + /// The repair is in progress. Cancellation of the IAsyncOperationWithProgress in this state will not + /// stop the repair or the post repair steps. + Repairing, + /// The repair has completed and cleanup actions are in progress. Cancellation of the + /// IAsyncOperationWithProgress in this state will not stop cleanup or roll back the repair. + PostRepair, + /// The operation has completed. + Finished, + }; + + /// Progress object for the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + struct RepairProgress + { + /// State of the repair + PackageRepairProgressState State; + + /// Repair percentage if known. + Double RepairCompletionProgress; + }; + + /// Status of the repair call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + /// DESIGN NOTE: RepairResultStatus from AppInstallerErrors.h is not implemented in V1. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum RepairResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + DownloadError, + InternalError, + InvalidOptions, + RepairError, + ManifestError, + NoApplicableRepairer, + PackageAgreementsNotAccepted, + }; + + /// Result of the repair + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + runtimeclass RepairResult + { + /// Used by a caller to correlate the repair with a caller's data. + String CorrelationData { get; }; + + /// Whether a restart is required to complete the repair. + Boolean RebootRequired { get; }; + + /// Batched error code, example APPINSTALLER_CLI_ERROR_SHELLEXEC_INSTALL_FAILED + RepairResultStatus Status { get; }; + + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + + /// The error code from the repair attempt. Only valid if the Status is RepairError. + /// This value's meaning will require knowledge of the specific repairer or repair technology. + UInt32 RepairerErrorCode { get; }; + } + + /// State of the download + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + enum PackageDownloadProgressState + { + /// The download is queued but not yet active. Cancellation of the IAsyncOperationWithProgress in this + /// state will prevent the package from downloading. + Queued, + /// The installer is downloading. Cancellation of the IAsyncOperationWithProgress in this state will + /// end the download. + Downloading, + /// The operation has completed. + Finished, + }; + + /// Status of the download call + /// Implementation Note: Errors mapped from AppInstallerErrors.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + enum DownloadResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + DownloadError, + ManifestError, + NoApplicableInstallers, + PackageAgreementsNotAccepted, + }; + + /// Result of the download + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + runtimeclass DownloadResult + { + /// Used by a caller to correlate the download with a caller's data. + String CorrelationData { get; }; + + /// Batched error code. + DownloadResultStatus Status { get; }; + + /// The error code of the overall operation. + HRESULT ExtendedErrorCode { get; }; + }; + + /// Progress object for the uninstall + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + struct PackageDownloadProgress + { + /// State of the download + PackageDownloadProgressState State; + + /// DESIGN NOTE: BytesDownloaded may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes downloaded if known + UInt64 BytesDownloaded; + + /// DESIGN NOTE: BytesRequired may only be available for downloads done by Windows Package Manager itself. + /// Number of bytes required if known + UInt64 BytesRequired; + + /// Download percentage completed + Double DownloadProgress; + }; + + /// IMPLEMENTATION NOTE: SourceOrigin from winget/RepositorySource.h + /// Defines the origin of the package catalog details. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageCatalogOrigin + { + /// Predefined means it came as part of the Windows Package Manager package and cannot be removed. + Predefined, + /// User means it was added by the user and could be removed. + User, + }; + + /// IMPLEMENTATION NOTE: SourceTrustLevel from winget/RepositorySource.h + /// Defines the trust level of the package catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageCatalogTrustLevel + { + None, + Trusted, + }; + + /// IMPLEMENTATION NOTE: SourceDetails from winget/RepositorySource.h + /// Interface for retrieving information about an package catalog without acting on it. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalogInfo + { + /// The package catalog's unique identifier. + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.Winget.Source_8wekyb3d8bbwe" + /// For contoso sample on msdn "contoso" + String Id { get; }; + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name { get; }; + /// The type of the package catalog. + /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". + /// For contoso sample on msdn "Microsoft.PreIndexed.Package" + String Type { get; }; + /// The argument used when adding the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" + /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" + String Argument { get; }; + /// The last time that this package catalog was updated. + Windows.Foundation.DateTime LastUpdateTime { get; }; + /// The origin of the package catalog. + PackageCatalogOrigin Origin { get; }; + /// The trust level of the package catalog + PackageCatalogTrustLevel TrustLevel { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + /// Excludes a source from discovery unless specified. + Boolean Explicit{ get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// The priority of this catalog. Higher values are sorted first. + Int32 Priority{ get; }; + } + } + + /// A metadata item of a package version. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageVersionMetadataField + { + /// The InstallerType of an installed package + InstallerType, + /// The Scope of an installed package + InstalledScope, + /// The system path where the package is installed + InstalledLocation, + /// The standard uninstall command; which may be interactive + StandardUninstallCommand, + /// An uninstall command that should be non-interactive + SilentUninstallCommand, + /// The publisher of the package + PublisherDisplayName, + }; + + /// The result of a comparison. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + enum CompareResult + { + /// The comparison did not result in a succesful ordering. + Unknown, + /// The object value is lesser than the given value. + Lesser, + /// The object value is equal to the given value. + Equal, + /// The object value is greater than the given value. + Greater, + }; + + /// IMPLEMENTATION NOTE: IPackageVersion from winget/RepositorySearch.h + /// A single package version. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageVersionInfo + { + /// IMPLEMENTATION NOTE: PackageVersionMetadata fields from winget/RepositorySearch.h + /// Gets any metadata associated with this package version. + /// Primarily stores data on installed packages. + /// Metadata fields may have no value (e.g. packages that aren't installed will not have an InstalledLocation). + String GetMetadata(PackageVersionMetadataField metadataField); + /// IMPLEMENTATION NOTE: PackageVersionProperty fields from winget/RepositorySearch.h + String Id { get; }; + String DisplayName { get; }; + String Version { get; }; + String Channel { get; }; + /// DESIGN NOTE: RelativePath from winget/RepositorySearch.h is excluded as not needed. + /// String RelativePath; + + /// IMPLEMENTATION NOTE: PackageVersionMultiProperty fields from winget/RepositorySearch.h + /// PackageFamilyName and ProductCode can have multiple values. + Windows.Foundation.Collections.IVectorView PackageFamilyNames { get; }; + Windows.Foundation.Collections.IVectorView ProductCodes { get; }; + + /// Gets the package catalog where this package version is from. + PackageCatalog PackageCatalog { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// Compares the given value against the package version of this object, with the result being + /// the enum value that represents where PackageVersionInfo::Version is ordered relative to the + /// versionString. "if (this.CompareToVersion(that) == Greater)" can be thought of as reading + /// the sentence "If this is compared to version that and is found to be greater". + /// IE if PackageVersionInfo::Version returns "2", then CompareToVersion("1") will return Greater. + /// Passing in an empty string will result in Unknown. + CompareResult CompareToVersion(String versionString); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// Checks if this package version has at least one applicable installer. + Boolean HasApplicableInstaller(InstallOptions options); + + /// Gets the publisher string for this package version, if one is available. + String Publisher { get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// Gets the package catalog metadata of this package version with the default localization based on user settings. + CatalogPackageMetadata GetCatalogPackageMetadata(); + + /// Gets the package catalog metadata of this package version with the preferred locale. + CatalogPackageMetadata GetCatalogPackageMetadata(String preferredLocale); + + /// Gets the applicable installer for this package version. + PackageInstallerInfo GetApplicableInstaller(InstallOptions options); + } + } + + /// IMPLEMENTATION NOTE: PackageVersionKey from winget/RepositorySearch.h + /// A key to identify a package version within a package. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageVersionId + { + /// The package catalog id that this version came from. + String PackageCatalogId { get; }; + /// The version. + String Version { get; }; + /// The channel. + String Channel { get; }; + }; + + /// The package installer type. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum PackageInstallerType + { + /// Unknown type. + Unknown, + /// Inno type. + Inno, + /// Wix type. + Wix, + /// Msi type. + Msi, + /// Nullsoft type. + Nullsoft, + /// Zip type. + Zip, + /// Msix or Appx type. + Msix, + /// Exe type. + Exe, + /// Burn type. + Burn, + /// MSStore type. + MSStore, + /// Portable type. + Portable, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + { + /// Font type. + Font, + }, + }; + + /// The package installer scope. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum PackageInstallerScope + { + /// Scope not declared. + Unknown, + /// User scope. + User, + /// System scope. + System, + }; + + /// The package installer elevation requirement. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum ElevationRequirement + { + /// Elevation requirement not declared. + Unknown, + /// Package installer requires elevation. + ElevationRequired, + /// Package installer prohibits elevation. + ElevationProhibited, + /// Package installer elevates self. + ElevatesSelf, + }; + + /// Interface for retrieving information about a package installer. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass PackageInstallerInfo + { + /// The package installer type. + PackageInstallerType InstallerType { get; }; + /// The nested package installer type for archives. + PackageInstallerType NestedInstallerType { get; }; + /// The package installer architecture. + Windows.System.ProcessorArchitecture Architecture { get; }; + /// The package installer scope. + PackageInstallerScope Scope { get; }; + /// The package installer locale. + String Locale { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// 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. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + [flags] + enum InstalledStatusType + { + /// None is checked. + None = 0x0, + /// Check Apps and Features entry. + AppsAndFeaturesEntry = 0x0001, + /// Check Apps and Features entry install location if applicable. + AppsAndFeaturesEntryInstallLocation = 0x0002, + /// Check Apps and Features entry install location with installed files if applicable. + AppsAndFeaturesEntryInstallLocationFile = 0x0004, + /// Check default install location if applicable. + DefaultInstallLocation = 0x0008, + /// Check default install location with installed files if applicable. + DefaultInstallLocationFile = 0x0010, + + /// Below are helper values for calling CheckInstalledStatus as input. + /// AppsAndFeaturesEntry related checks + AllAppsAndFeaturesEntryChecks = AppsAndFeaturesEntry | AppsAndFeaturesEntryInstallLocation | AppsAndFeaturesEntryInstallLocationFile, + /// DefaultInstallLocation related checks + AllDefaultInstallLocationChecks = DefaultInstallLocation | DefaultInstallLocationFile, + /// All checks + AllChecks = AllAppsAndFeaturesEntryChecks | AllDefaultInstallLocationChecks, + }; + + /// Interface representing an individual installed status. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass InstalledStatus + { + /// The installed status type. + InstalledStatusType Type { get; }; + /// The installed status path. + String Path { get; }; + /// The installed status result. + HRESULT Status { get; }; + }; + + /// Interface for retrieving information about a package installer installed status. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass PackageInstallerInstalledStatus + { + /// The package installer info. + PackageInstallerInfo InstallerInfo { get; }; + /// A list of various types of installed status of the package installer. + Windows.Foundation.Collections.IVectorView InstallerInstalledStatus { get; }; + }; + + /// Status of the check installed status call. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum CheckInstalledStatusResultStatus + { + Ok, + InternalError, + }; + + /// Interface for retrieving information about a package installer installed status. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + runtimeclass CheckInstalledStatusResult + { + /// Status of the check installed status call. + CheckInstalledStatusResultStatus Status { get; }; + + /// A list of package installer installed status. + Windows.Foundation.Collections.IVectorView PackageInstalledStatus { get; }; + }; + + /// IMPLEMENTATION NOTE: IPackage from winget/RepositorySearch.h + /// A package, potentially containing information about it's local state and the available versions. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass CatalogPackage + { + /// IMPLEMENTATION NOTE: PackageProperty fields from winget/RepositorySearch.h + /// Gets a property of this package. + String Id { get; }; + String Name { get; }; + + /// Gets the installed package information if the package is installed. + PackageVersionInfo InstalledVersion { get; }; + + /// Gets all available versions of this package. Ordering is not guaranteed. + Windows.Foundation.Collections.IVectorView AvailableVersions { get; }; + + /// Gets the version of this package that will be installed if version is not set in InstallOptions. + PackageVersionInfo DefaultInstallVersion { get; }; + + /// Gets a specific version of this package. + PackageVersionInfo GetPackageVersionInfo(PackageVersionId versionKey); + + /// Gets a value indicating whether an available version is newer than the installed version. + Boolean IsUpdateAvailable { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Check the installed status of the package. For more accurate and complete installed status, it's required to + /// call this method from a composite package from a newly created package catalog with installed info. + /// This may require downloading information from a server. + Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(InstalledStatusType checkTypes); + CheckInstalledStatusResult CheckInstalledStatus(InstalledStatusType checkTypes); + Windows.Foundation.IAsyncOperation CheckInstalledStatusAsync(); + CheckInstalledStatusResult CheckInstalledStatus(); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// Determines the priority of the catalog for this package object. + /// This should match the priority of the DefaultInstallVersion, but it is much more efficient than using that route. + /// May be null if the package refers only to an installed item. + Windows.Foundation.IReference CatalogPriority { get; }; + } + } + + /// IMPLEMENTATION NOTE: CompositeSearchBehavior from winget/RepositorySource.h + /// Search behavior for composite catalogs. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum CompositeSearchBehavior + { + /// Search local catalogs only + LocalCatalogs, + /// Search remote catalogs only, don't check local catalogs for InstalledVersion + RemotePackagesFromRemoteCatalogs, + /// Search remote catalogs, and check local catalogs for InstalledVersion + RemotePackagesFromAllCatalogs, + /// Search both local and remote catalogs. + AllCatalogs, + }; + + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageFieldMatchOption + { + Equals, + EqualsCaseInsensitive, + StartsWithCaseInsensitive, + ContainsCaseInsensitive, + }; + + /// IMPLEMENTATION NOTE: PackageFieldMatchOption from winget/RepositorySearch.h + /// The field to match on. + /// The values must be declared in order of preference in search results. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageMatchField + { + CatalogDefault, + Id, + Name, + Moniker, + Command, + Tag, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 3)] + { + PackageFamilyName, + ProductCode, + } + /// DESIGN NOTE: The following PackageFieldMatchOption from winget/RepositorySearch.h are not implemented in V1. + /// NormalizedNameAndPublisher, + }; + + /// IMPLEMENTATION NOTE: PackageMatchFilter from winget/RepositorySearch.h + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageMatchFilter + { + PackageMatchFilter(); + /// The type of string comparison for matching + PackageFieldMatchOption Option; + /// The field to search + PackageMatchField Field; + /// The value to match + String Value; + /// DESIGN NOTE: "Additional" from RequestMatch winget/RepositorySearch.h is not implemented here. + } + + /// IMPLEMENTATION NOTE: MatchResult from winget/RepositorySearch.h + /// A single result from the search. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass MatchResult + { + /// The package found by the search request. + CatalogPackage CatalogPackage { get; }; + + /// The highest order field on which the package matched the search. + PackageMatchFilter MatchCriteria { get; }; + } + + /// Status of the FindPackages call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum FindPackagesResultStatus + { + Ok, + BlockedByPolicy, + CatalogError, + InternalError, + InvalidOptions, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + { + AuthenticationError, + AccessDenied, + } + }; + + /// IMPLEMENTATION NOTE: SearchResult from winget/RepositorySearch.h + /// Search result data returned from FindPackages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass FindPackagesResult + { + /// Error codes + FindPackagesResultStatus Status{ get; }; + + /// The full set of results from the search. + Windows.Foundation.Collections.IVectorView Matches { get; }; + + /// If true, the results were truncated by the given ResultLimit + /// USAGE NOTE: Windows Package Manager does not support result pagination, there is no way to continue + /// getting more results. + Boolean WasLimitExceeded { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + /// The error code of the operation. + HRESULT ExtendedErrorCode{ get; }; + } + } + + /// Options for FindPackages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass FindPackagesOptions + { + FindPackagesOptions(); + + /// DESIGN NOTE: + /// This class maps to SearchRequest from winget/RepositorySearch.h + /// That class is a container for data used to filter the available manifests in an package catalog. + /// Its properties can be thought of as: + /// (Query || Inclusions...) && Filters... + /// If Query and Inclusions are both empty, the starting data set will be the entire database. + /// Everything && Filters... + /// That has been translated in this api so that + /// Inclusions are Selectors below + /// Filters are Filters below + /// Query is PackageFieldMatchOption::PackageCatalogDefined and in the Selector list. + /// USAGE NOTE: Only one selector with PackageFieldMatchOption::PackageCatalogDefined is allowed. + + /// Selectors = you have to match at least one selector (if there are no selectors, then nothing is selected) + Windows.Foundation.Collections.IVector Selectors { get; }; + /// Filters = you have to match all filters(if there are no filters, then there is no filtering of selected items) + Windows.Foundation.Collections.IVector Filters{ get; }; + + /// Restricts the length of the returned results to the specified count. + UInt32 ResultLimit; + } + + /// IMPLEMENTATION NOTE: Source from winget/RepositorySource.h + /// A catalog for searching for packages + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalog + { + /// Gets a value indicating whether this package catalog is a composite of other package catalogs, + /// and thus the packages may come from disparate package catalogs as well. + Boolean IsComposite { get; }; + /// The details of the package catalog if it is not a composite. + PackageCatalogInfo Info { get; }; + + /// Searches for Packages in the catalog. + Windows.Foundation.IAsyncOperation FindPackagesAsync(FindPackagesOptions options); + FindPackagesResult FindPackages(FindPackagesOptions options); + } + + /// Authentication mode + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + enum AuthenticationMode + { + /// Always use interactive authentication flow on first authentication request, following requests may use cached result. + Interactive, + /// Try silent authentication flow first. If failed, use interactive authentication flow. + SilentPreferred, + /// Only use silent authentication flow. If failed, fail the authentication. + Silent, + }; + + /// Authentication related arguments + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + runtimeclass AuthenticationArguments + { + AuthenticationArguments(); + + /// Choice of authentication flow behavior. + AuthenticationMode AuthenticationMode; + /// Optional. The authentication account to be used for authentication. + String AuthenticationAccount; + } + + /// Authentication method + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + enum AuthenticationType + { + Unknown, + None, + MicrosoftEntraId, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + MicrosoftEntraIdForAzureBlobStorage, + } + }; + + /// Microsoft Entra Id related authentication info. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + runtimeclass MicrosoftEntraIdAuthenticationInfo + { + /// The resource identifier or resource uri. + String Resource { get; }; + /// Requested scope. May be empty. + String Scope { get; }; + } + + /// Authentication info. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 10)] + runtimeclass AuthenticationInfo + { + /// The authentication type. + AuthenticationType AuthenticationType { get; }; + /// Microsoft Entra Id related authentication info. + MicrosoftEntraIdAuthenticationInfo MicrosoftEntraIdAuthenticationInfo { get; }; + } + + /// Result of a connection validation callback. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + enum PackageCatalogConnectionValidationResult + { + /// The connection was accepted. + Ok, + /// The connection was rejected because the certificate was not accepted. + CertificateRejected, + }; + + /// Arguments provided to a connection validation callback. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + runtimeclass PackageCatalogConnectionValidationEventArgs + { + /// The server certificate presented during the connection. + Windows.Security.Cryptography.Certificates.Certificate ServerCertificate { get; }; + } + + /// Callback invoked to validate a catalog connection. + /// Return Ok to accept the connection or another value to reject it for that reason. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + delegate PackageCatalogConnectionValidationResult PackageCatalogConnectionValidationHandler(PackageCatalogConnectionValidationEventArgs args); + + /// Status of the Connect call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum ConnectResultStatus + { + Ok, + CatalogError, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + SourceAgreementsNotAccepted, + } + }; + + /// Result of the Connect call + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass ConnectResult + { + /// Error codes + ConnectResultStatus Status { get; }; + + PackageCatalog PackageCatalog { get; }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + /// The error code of the operation. + HRESULT ExtendedErrorCode{ get; }; + } + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + enum RefreshPackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + }; + + /// IMPLEMENTATION NOTE: RefreshPackageCatalogResult + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass RefreshPackageCatalogResult + { + RefreshPackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + /// A reference to a catalog that callers can try to Connect. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageCatalogReference + { + /// Gets a value indicating whether this package catalog is a composite of other package catalogs, + /// and thus the packages may come from disparate package catalogs as well. + Boolean IsComposite { get; }; + + /// The details of the package catalog if it is not a composite. + PackageCatalogInfo Info { get; }; + + /// Opens a catalog. Required before searching. For remote catalogs (i.e. not Installed and Installing) this + /// may require downloading information from a server. + Windows.Foundation.IAsyncOperation ConnectAsync(); + ConnectResult Connect(); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// A string that will be passed to the source server if using a REST source + String AdditionalPackageCatalogArguments; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// Gets the required agreements for connecting to the package catalog (source). + Windows.Foundation.Collections.IVectorView SourceAgreements { get; }; + + Boolean AcceptSourceAgreements; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 8)] + { + /// Time interval for package catalog to check for an update. Setting to zero will disable the check for update. + Windows.Foundation.TimeSpan PackageCatalogBackgroundUpdateInterval; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 9)] + { + /// When set to true, the opened catalog will only provide the information regarding packages installed from this catalog. + /// In this mode, no external resources should be required. + Boolean InstalledPackageInformationOnly; + } + + [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; + + /// Authentication info from the package catalog. + /// This is defined by individual package catalog. + AuthenticationInfo AuthenticationInfo { get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Updates the package catalog. + /// The progress value, represented as a double, indicates the percentage of update package catalog operation completion. + /// The progress range is from 0 to 100. + Windows.Foundation.IAsyncOperationWithProgress RefreshPackageCatalogAsync(); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// A callback invoked to validate the server certificate during Connect or ConnectAsync. + /// Only available to in-process callers; out-of-process callers will receive E_ACCESSDENIED on set. + /// If the BypassCertificatePinningForMicrosoftStore group policy is disabled, this cannot be set + /// for the MicrosoftStore catalog; attempting to do so produces APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY. + PackageCatalogConnectionValidationHandler ConnectionValidationHandler; + + /// Indicates whether the ConnectionValidationHandler can be set for this catalog reference. + /// Returns false if setting the handler would be blocked by policy (e.g., the + /// BypassCertificatePinningForMicrosoftStore group policy is disabled for the MicrosoftStore catalog). + Boolean IsConnectionValidationHandlerEnabled { get; }; + } + } + + /// Catalogs with PackageCatalogOrigin Predefined + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PredefinedPackageCatalog + { + OpenWindowsCatalog, + MicrosoftStore, + DesktopFrameworks, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + { + OpenWindowsCatalogFont, + }, + }; + + /// Local Catalogs with PackageCatalogOrigin Predefined + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum LocalPackageCatalog + { + InstalledPackages, + InstallingPackages + }; + + /// Options for creating a composite catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass CreateCompositePackageCatalogOptions + { + CreateCompositePackageCatalogOptions(); + + /// Create a composite catalog to allow searching a user defined or pre defined source + /// and a local source (Installed packages) together + IVector Catalogs { get; }; + /// Sets the default search behavior if the catalog is a composite catalog. + CompositeSearchBehavior CompositeSearchBehavior; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Create installed package catalog with required installed scope. + PackageInstallScope InstalledScope; + } + } + + /// Required install scope for the package. If the package does not have an installer that + /// supports the specified scope the Install call will fail with InstallResultStatus.NoApplicableInstallers + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallScope + { + /// An installer with any install scope is valid. + Any, + /// Only User install scope installers are valid + User, + /// Only System installers will be valid + System, + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Both User and Unknown install scope installers are valid + UserOrUnknown, + /// Both System and Unknown install scope installers are valid + SystemOrUnknown, + } + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + enum PackageInstallMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + /// Options when installing a package. + /// Intended to allow full compatibility with the "winget install" command line interface. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass InstallOptions + { + InstallOptions(); + + /// Optionally specifies the version from the package to install. If unspecified, the CatalogPackage.DefaultInstallVersion + /// version is used. DefaultInstallVersion is the latest applicable version of the package. DefaultInstallVersion may be + /// empty if there's no applicable version. In that case, install attempts without setting this PackageVersionId + /// will return No Applicable Installer error code. + PackageVersionId PackageVersionId; + + /// Specifies alternate location to install package (if supported). + String PreferredInstallLocation; + /// User or Machine. + PackageInstallScope PackageInstallScope; + /// Silent, Interactive, or Default + PackageInstallMode PackageInstallMode; + /// Directs the logging to a log file. If provided, the installer must have write access to the file + String LogOutputPath; + /// Continues the install even if the hash in the catalog does not match the linked installer. + Boolean AllowHashMismatch; + /// A string that will be passed to the installer. + /// IMPLEMENTATION NOTE: maps to "--override" in the winget cmd line + String ReplacementInstallerArguments; + + /// Used by a caller to correlate the install with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + /// A string that will be passed to the source server if using a REST source + String AdditionalPackageCatalogArguments; + + [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. + Windows.Foundation.Collections.IVector AllowedArchitectures { get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// Allow the upgrade to continue for upgrade packages with manifest versions Unknown. + Boolean AllowUpgradeToUnknownVersion; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Force the operation to continue upon non security related failures. + Boolean Force; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + { + /// A string that will be passed to the installer + /// IMPLEMENTATION NOTE: maps to "--custom" in the winget cmd line + String AdditionalInstallerArguments; + + /// Accept the package agreements required for installation. + Boolean AcceptPackageAgreements; + + /// Bypasses the Disabled Store Policy + Boolean BypassIsStoreClientBlockedPolicyCheck; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + { + /// 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)] + enum PackageUninstallMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + enum PackageUninstallScope + { + /// Use default uninstall behavior. + Any, + /// Uninstall for current user. Currently only applicable to msix. + User, + /// Uninstall for all users. Currently only applicable to msix. + System, + }; + + /// Options when uninstalling a package. + /// Intended to allow full compatibility with the "winget uninstall" command line interface. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + runtimeclass UninstallOptions + { + UninstallOptions(); + + /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for uninstall. + PackageVersionId PackageVersionId; + + /// Silent, Interactive, or Default + PackageUninstallMode PackageUninstallMode; + + /// Directs the logging to a log file. If provided, the installer must have write access to the file + String LogOutputPath; + + /// Used by a caller to correlate the install with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 5)] + { + /// Force the operation to continue upon non security related failures. + Boolean Force; + // The scope the uninstall will perform. Currently only applicable to msix. + PackageUninstallScope PackageUninstallScope; + } + } + + /// The Windows platform type. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + enum WindowsPlatform + { + /// An unknown platform + Unknown, + /// Windows.Universal + Universal, + /// Windows.Desktop + Desktop, + /// Windows.IoT + IoT, + /// Windows.Team + Team, + /// Windows.Holographic + Holographic, + }; + + /// Options when downloading a package. + /// Intended to allow full compatibility with the "winget download" command line interface. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + runtimeclass DownloadOptions + { + DownloadOptions(); + + /// Optionally specifies the version from the package to download. If unspecified the version matching + /// CatalogPackage.GetLatestVersion() is used. + PackageVersionId PackageVersionId; + + /// The package installer type. + PackageInstallerType InstallerType; + + /// The package installer scope. + PackageInstallScope Scope; + + /// The package installer architecture. + Windows.System.ProcessorArchitecture Architecture; + + /// The package installer locale. + String Locale; + + /// The directory where the installers are downloaded to. + String DownloadDirectory; + + /// Continues the download even if the hash in the catalog does not match the linked installer. + Boolean AllowHashMismatch; + + /// Skip downloading the dependencies for the package. + Boolean SkipDependencies; + + /// Accept the package agreements required for download. + Boolean AcceptPackageAgreements; + + /// 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, 13)] + { + /// If the package is licensed from the Microsoft Store, setting this value to true will not attempt to download the license file. + Boolean SkipMicrosoftStoreLicense; + + /// The platform to download the package for. + WindowsPlatform Platform; + + /// When applicable, uses the provided value as the target OS version for the download. + String TargetOSVersion; + } + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairMode + { + /// The default experience for the installer. Installer may show some UI. + Default, + /// Runs the installer in silent mode. This suppresses the installer's UI to the extent + /// possible (installer may still show some required UI). + Silent, + /// Runs the installer in interactive mode. + Interactive, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + enum PackageRepairScope + { + /// Use default repair behavior. + Any, + /// Repair for current user. Currently only applicable to msix. + User, + /// Repair for all users. + System, + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + runtimeclass RepairOptions + { + RepairOptions(); + + /// This property is not currently used. The version of CatalogPackage.InstalledVersion is used for repair. + PackageVersionId PackageVersionId; + + /// The package Repair scope. + PackageRepairScope PackageRepairScope; + + /// The package repair mode. + PackageRepairMode PackageRepairMode; + + /// Optional parameter specifying Accept the package agreements required for download. + Boolean AcceptPackageAgreements; + + /// Used by a caller to correlate the repair with a caller's data. + /// The string must be JSON encoded. + String CorrelationData; + + /// Continues the download even if the hash in the catalog does not match the linked installer used for repair. + Boolean AllowHashMismatch; + + /// Directs the logging to a log file. If provided, the installer must have write access to the file + String LogOutputPath; + + /// Force the operation to continue upon non security related failures. + Boolean Force; + + /// 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 + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass Documentation + { + String DocumentLabel { get; }; + + String DocumentUrl { get; }; + } + + /// Icon resolution + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconResolution + { + Custom, + Square16, + Square20, + Square24, + Square30, + Square32, + Square36, + Square40, + Square48, + Square60, + Square64, + Square72, + Square80, + Square96, + Square256, + }; + + /// Icon file type + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconFileType + { + Unknown, + Jpeg, + Png, + Ico, + }; + + /// Icon theme + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + enum IconTheme + { + Unknown, + Default, + Light, + Dark, + HighContrast, + }; + + /// IMPLEMENTATION NOTE: Icon from AppInstaller::Manifest::Icon + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass Icon + { + String Url { get; }; + + IconFileType FileType{ get; }; + + IconResolution Resolution{ get; }; + + IconTheme Theme{ get; }; + + UInt8[] Sha256 { get; }; + } + + /// IMPLEMENTATION NOTE: SourceAgreement from AppInstaller::Manifest::SourceAgreement + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass SourceAgreement + { + String Label { get; }; + + String Text { get; }; + + String Url { get; }; + } + + /// IMPLEMENTATION NOTE: PackageAgreement from AppInstaller::Manifest::Agreement + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass PackageAgreement + { + String Label { get; }; + + String Text { get; }; + + String Url { get; }; + } + + /// IMPLEMENTATION NOTE: CatalogPackageMetadata from AppInstaller::Manifest::Localization + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 6)] + runtimeclass CatalogPackageMetadata + { + String Locale { get; }; + + String Publisher { get; }; + + String PublisherUrl { get; }; + + String PublisherSupportUrl { get; }; + + String PrivacyUrl { get; }; + + String Author { get; }; + + String PackageName { get; }; + + String PackageUrl { get; }; + + String License { get; }; + + String LicenseUrl { get; }; + + String Copyright { get; }; + + String CopyrightUrl { get; }; + + String ShortDescription { get; }; + + String Description { get; }; + + Windows.Foundation.Collections.IVectorView Tags { get; }; + + Windows.Foundation.Collections.IVectorView Agreements { get; }; + + Windows.Foundation.Collections.IVectorView Documentations { get; }; + + Windows.Foundation.Collections.IVectorView Icons { get; }; + + String ReleaseNotes { get; }; + + String ReleaseNotesUrl { get; }; + + String PurchaseUrl { get; }; + + String InstallationNotes { get; }; + } + + /// IMPLEMENTATION NOTE: AddPackageCatalogOptions + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass AddPackageCatalogOptions + { + AddPackageCatalogOptions(); + + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name; + + /// The SourceUri used when adding the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "https://winget.azureedge.net/cache" + /// For contoso sample on msdn "https://pkgmgr-int.azureedge.net/cache" + String SourceUri; + + /// ALLOWED VALUES: "Microsoft.Rest", "Microsoft.PreIndexed.Package" + /// SAMPLE VALUES: For OpenWindowsCatalog "Microsoft.PreIndexed.Package". + /// For contoso sample on msdn "Microsoft.PreIndexed.Package" + String Type; + + /// The trust level of the catalog to add. + PackageCatalogTrustLevel TrustLevel; + + /// Custom header to pass to the catalog. + String CustomHeader; + + /// Excludes a source from discovery unless specified. + Boolean Explicit; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// The priority of this catalog. Higher values are sorted first. + Int32 Priority; + } + }; + + /// IMPLEMENTATION NOTE: AddPackageCatalogStatus + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + enum AddPackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + InvalidOptions, + AccessDenied, + AuthenticationError, + }; + + /// IMPLEMENTATION NOTE: AddPackageCatalogResult + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass AddPackageCatalogResult + { + AddPackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogOptions + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass RemovePackageCatalogOptions + { + RemovePackageCatalogOptions(); + + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name; + + /// By default, the value is 'false', resulting in the removal of the package catalog registration + /// from the winget Package catalogs list and the deletion of all associated system artifacts. This + /// mirrors the WinGet Source remove operation on a specific Package Catalog. + /// If set to 'true', it removes the package catalog registration from the Windows Package Catalogs + /// list without any cleanup, similar to the WinGet source reset operation on a specific Package + /// Catalog. + Boolean PreserveData; + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + enum RemovePackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + AccessDenied, + InvalidOptions, + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogResult + /// Result of removing a package catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + runtimeclass RemovePackageCatalogResult + { + RemovePackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + /// IMPLEMENTATION NOTE: EditPackageCatalogOptions + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + runtimeclass EditPackageCatalogOptions + { + EditPackageCatalogOptions(); + + /// The name of the package catalog. + /// SAMPLE VALUES: For OpenWindowsCatalog "winget". + /// For contoso sample on msdn "contoso" + String Name; + + /// Editing the Explicit property has three states: true, false, and not specified (null). + Windows.Foundation.IReference Explicit; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 29)] + { + /// The priority of this catalog. Higher values are sorted first. + Windows.Foundation.IReference Priority; + } + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogStatus + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + enum EditPackageCatalogStatus + { + Ok, + GroupPolicyError, + CatalogError, + InternalError, + AccessDenied, + InvalidOptions, + }; + + /// IMPLEMENTATION NOTE: RemovePackageCatalogResult + /// Result of editing a package catalog. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + runtimeclass EditPackageCatalogResult + { + EditPackageCatalogStatus Status { get; }; + + /// Error codes + HRESULT ExtendedErrorCode { get; }; + }; + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 1)] + runtimeclass PackageManager + { + PackageManager(); + + /// Get the available catalogs. Each source will have a separate catalog. + /// This does not open the catalog. These catalogs can be used individually or merged with CreateCompositePackageCatalogAsync. + /// IMPLEMENTATION NOTE: This is a list of sources returned by Windows Package Manager source list + Windows.Foundation.Collections.IVectorView GetPackageCatalogs(); + /// Get a built in catalog + PackageCatalogReference GetPredefinedPackageCatalog(PredefinedPackageCatalog predefinedPackageCatalog); + /// Get a built in catalog + PackageCatalogReference GetLocalPackageCatalog(LocalPackageCatalog localPackageCatalog); + /// Get a catalog by a known name + PackageCatalogReference GetPackageCatalogByName(String catalogName); + /// Get a composite catalog to allow searching a user defined or pre defined source and a local source + /// (Installing, Installed) together at the same time. + PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 12)] + { + /// Add a catalog to the Windows Package Catalogs. + /// The progress value, represented as a double, indicates the percentage of add package catalog operation completion. + /// The progress range is from 0 to 100. + Windows.Foundation.IAsyncOperationWithProgress AddPackageCatalogAsync(AddPackageCatalogOptions options); + + /// Unregisters a Package Catalog from the Windows Package Catalogs and eliminates the system artifacts based on the provided options. + /// The progress value, represented as a double, indicates the percentage of remove package catalog operation completion. + /// The progress range is from 0 to 100. + Windows.Foundation.IAsyncOperationWithProgress RemovePackageCatalogAsync(RemovePackageCatalogOptions options); + } + + /// Install the specified package + Windows.Foundation.IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 2)] + { + /// Get install progress + Windows.Foundation.IAsyncOperationWithProgress GetInstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + { + /// Upgrade the specified package + Windows.Foundation.IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options); + + /// Uninstall the specified package + Windows.Foundation.IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options); + + /// Get uninstall progress + Windows.Foundation.IAsyncOperationWithProgress GetUninstallProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 7)] + { + // Download the specified package + Windows.Foundation.IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options); + + // Get download progress + Windows.Foundation.IAsyncOperationWithProgress GetDownloadProgress(CatalogPackage package, PackageCatalogInfo catalogInfo); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 11)] + { + // Repair the specified package + Windows.Foundation.IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options); + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 13)] + { + // The version of the Windows Package Manager that is running. + String Version{ get; }; + } + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + { + /// Edit an existing Windows Package Catalog. + EditPackageCatalogResult EditPackageCatalog(EditPackageCatalogOptions options); + } + } + + /// Global settings for PackageManager operations. + /// This settings should be invoked prior to invocation of PackageManager class. + /// This settings is only exposed in in-proc Com invocation. + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 4)] + runtimeclass PackageManagerSettings + { + PackageManagerSettings(); + + /// Sets caller name to be used in telemetry logging. Default value is the calling process name. + /// Call this before any PackageManager operations. + /// Returns true if successful, false if caller name is already set. + /// This is a one time setup, multiple calls will not override existing caller name. + Boolean SetCallerIdentifier(String callerIdentifier); + + /// Sets state name for state separation. If not set, state will be written in a default location and states may be affected by other callers. + /// Call this before any PackageManager operations. + /// Returns true if successful, false if state name is already set. + /// This is a one time setup, multiple calls will not override existing state name. + Boolean SetStateIdentifier(String stateIdentifier); + + /// Sets custom UserSettings. + /// Returns true if successful, false if settingsContent cannot be parsed or UserSettings is already created. + /// This is a one time setup, multiple calls will not override existing UserSettings. + Boolean SetUserSettings(String settingsContent); + + [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] + { + /// Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not. + /// This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if + /// there are active objects at all times. If set to true it will allow the unload when there are no + /// active objects. + /// Defaults to true. + Boolean CanUnloadPreference{ get; set; }; + + /// Gets or sets a value indicating whether the module should listen for termination signals (CTRL+C, window messages, package updates) + /// and begin the process of cancelling active operations and preventing new ones. + /// If set to false, the caller is responsible for handling these termination signals and cancelling active operations as necessary. + /// Set this to the desired state before any PackageManager operations. Changing it after the first operation for the process may have undefined behavior. + /// Defaults to true. + Boolean TerminationSignalMonitoring{ get; set; }; + } + } + + /// Force midl3 to generate vector marshalling info. + declare + { + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + interface Windows.Foundation.Collections.IVector; + interface Windows.Foundation.Collections.IVectorView; + } +} diff --git a/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp b/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp index 2cd2ac00cb..4e616dc502 100644 --- a/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp +++ b/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp @@ -1,83 +1,83 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "PackageManager.h" -#include "PackageManagerSettings.h" -#pragma warning( pop ) -#include "PackageManagerSettings.g.cpp" -#include "Helpers.h" -#include "Public/CanUnload.h" -#include "Public/ShutdownMonitoring.h" -#include -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - bool PackageManagerSettings::SetCallerIdentifier(hstring const& callerIdentifier) - { - bool success = false; - static std::once_flag setCallerOnceFlag; - std::call_once(setCallerOnceFlag, - [&]() - { - SetComCallerName(AppInstaller::Utility::ConvertToUTF8(callerIdentifier)); - success = true; - }); - return success; - } - bool PackageManagerSettings::SetStateIdentifier(hstring const& stateIdentifier) - { - bool success = false; - static std::once_flag setStateOnceFlag; - std::call_once(setStateOnceFlag, - [&]() - { - AppInstaller::Runtime::SetRuntimePathStateName(AppInstaller::Utility::ConvertToUTF8(stateIdentifier)); - success = true; - }); - return success; - } - bool PackageManagerSettings::SetUserSettings(hstring const& settingsContent) - { - bool success = false; - static std::once_flag setSettingsOnceFlag; - std::call_once(setSettingsOnceFlag, - [&]() - { - success = AppInstaller::Settings::TryInitializeCustomUserSettings(AppInstaller::Utility::ConvertToUTF8(settingsContent)); - if (success) - { - AppInstaller::Logging::Log().SetEnabledChannels(AppInstaller::Settings::User().Get()); - AppInstaller::Logging::Log().SetLevel(AppInstaller::Settings::User().Get()); - } - }); - return success; - } - - bool PackageManagerSettings::CanUnloadPreference() const - { - return GetCanUnload(); - } - - void PackageManagerSettings::CanUnloadPreference(bool value) - { - SetCanUnload(value); - } - - bool PackageManagerSettings::TerminationSignalMonitoring() const - { - return AppInstaller::ShutdownMonitoring::TerminationSignalHandler::Enabled(); - } - - void PackageManagerSettings::TerminationSignalMonitoring(bool value) - { - AppInstaller::ShutdownMonitoring::TerminationSignalHandler::Enabled(value); - } - - CoCreatableMicrosoftManagementDeploymentClass(PackageManagerSettings); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "PackageManager.h" +#include "PackageManagerSettings.h" +#pragma warning( pop ) +#include "PackageManagerSettings.g.cpp" +#include "Helpers.h" +#include "Public/CanUnload.h" +#include "Public/ShutdownMonitoring.h" +#include +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + bool PackageManagerSettings::SetCallerIdentifier(hstring const& callerIdentifier) + { + bool success = false; + static std::once_flag setCallerOnceFlag; + std::call_once(setCallerOnceFlag, + [&]() + { + SetComCallerName(AppInstaller::Utility::ConvertToUTF8(callerIdentifier)); + success = true; + }); + return success; + } + bool PackageManagerSettings::SetStateIdentifier(hstring const& stateIdentifier) + { + bool success = false; + static std::once_flag setStateOnceFlag; + std::call_once(setStateOnceFlag, + [&]() + { + AppInstaller::Runtime::SetRuntimePathStateName(AppInstaller::Utility::ConvertToUTF8(stateIdentifier)); + success = true; + }); + return success; + } + bool PackageManagerSettings::SetUserSettings(hstring const& settingsContent) + { + bool success = false; + static std::once_flag setSettingsOnceFlag; + std::call_once(setSettingsOnceFlag, + [&]() + { + success = AppInstaller::Settings::TryInitializeCustomUserSettings(AppInstaller::Utility::ConvertToUTF8(settingsContent)); + if (success) + { + AppInstaller::Logging::Log().SetEnabledChannels(AppInstaller::Settings::User().Get()); + AppInstaller::Logging::Log().SetLevel(AppInstaller::Settings::User().Get()); + } + }); + return success; + } + + bool PackageManagerSettings::CanUnloadPreference() const + { + return GetCanUnload(); + } + + void PackageManagerSettings::CanUnloadPreference(bool value) + { + SetCanUnload(value); + } + + bool PackageManagerSettings::TerminationSignalMonitoring() const + { + return AppInstaller::ShutdownMonitoring::TerminationSignalHandler::Enabled(); + } + + void PackageManagerSettings::TerminationSignalMonitoring(bool value) + { + AppInstaller::ShutdownMonitoring::TerminationSignalHandler::Enabled(value); + } + + CoCreatableMicrosoftManagementDeploymentClass(PackageManagerSettings); +} diff --git a/src/Microsoft.Management.Deployment/PackageManagerSettings.h b/src/Microsoft.Management.Deployment/PackageManagerSettings.h index e258953d7f..76b44b3ac4 100644 --- a/src/Microsoft.Management.Deployment/PackageManagerSettings.h +++ b/src/Microsoft.Management.Deployment/PackageManagerSettings.h @@ -1,35 +1,35 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "PackageManagerSettings.g.h" -#include "Public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_INPROC_ONLY_COM_CLSID_PackageManagerSettings)] - struct PackageManagerSettings : PackageManagerSettingsT - { - PackageManagerSettings() = default; - - // Contract 4.0 - bool SetCallerIdentifier(hstring const& callerIdentifier); - bool SetStateIdentifier(hstring const& stateIdentifier); - bool SetUserSettings(hstring const& settingsContent); - - // Contract 28 - bool CanUnloadPreference() const; - void CanUnloadPreference(bool value); - bool TerminationSignalMonitoring() const; - void TerminationSignalMonitoring(bool value); - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct PackageManagerSettings : PackageManagerSettingsT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageManagerSettings.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_INPROC_ONLY_COM_CLSID_PackageManagerSettings)] + struct PackageManagerSettings : PackageManagerSettingsT + { + PackageManagerSettings() = default; + + // Contract 4.0 + bool SetCallerIdentifier(hstring const& callerIdentifier); + bool SetStateIdentifier(hstring const& stateIdentifier); + bool SetUserSettings(hstring const& settingsContent); + + // Contract 28 + bool CanUnloadPreference() const; + void CanUnloadPreference(bool value); + bool TerminationSignalMonitoring() const; + void TerminationSignalMonitoring(bool value); + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct PackageManagerSettings : PackageManagerSettingsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/PackageMatchFilter.cpp b/src/Microsoft.Management.Deployment/PackageMatchFilter.cpp index 208a87d0ea..ec1505ee8a 100644 --- a/src/Microsoft.Management.Deployment/PackageMatchFilter.cpp +++ b/src/Microsoft.Management.Deployment/PackageMatchFilter.cpp @@ -1,51 +1,51 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include "Workflows/WorkflowBase.h" -#include "Converters.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "PackageMatchFilter.h" -#pragma warning( pop ) -#include "PackageMatchFilter.g.cpp" -#include "Helpers.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void PackageMatchFilter::Initialize(::AppInstaller::Repository::PackageMatchFilter matchFilter) - { - m_value = winrt::to_hstring(matchFilter.Value); - m_matchField = GetDeploymentMatchField(matchFilter.Field); - m_packageFieldMatchOption = GetDeploymentMatchOption(matchFilter.Type); - } - winrt::Microsoft::Management::Deployment::PackageFieldMatchOption PackageMatchFilter::Option() - { - return m_packageFieldMatchOption; - } - void PackageMatchFilter::Option(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption const& value) - { - m_packageFieldMatchOption = value; - } - winrt::Microsoft::Management::Deployment::PackageMatchField PackageMatchFilter::Field() - { - return m_matchField; - } - void PackageMatchFilter::Field(winrt::Microsoft::Management::Deployment::PackageMatchField const& value) - { - m_matchField = value; - } - hstring PackageMatchFilter::Value() - { - return hstring(m_value); - } - void PackageMatchFilter::Value(hstring const& value) - { - m_value = value; - } - - CoCreatableMicrosoftManagementDeploymentClass(PackageMatchFilter); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include "Workflows/WorkflowBase.h" +#include "Converters.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "PackageMatchFilter.h" +#pragma warning( pop ) +#include "PackageMatchFilter.g.cpp" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void PackageMatchFilter::Initialize(::AppInstaller::Repository::PackageMatchFilter matchFilter) + { + m_value = winrt::to_hstring(matchFilter.Value); + m_matchField = GetDeploymentMatchField(matchFilter.Field); + m_packageFieldMatchOption = GetDeploymentMatchOption(matchFilter.Type); + } + winrt::Microsoft::Management::Deployment::PackageFieldMatchOption PackageMatchFilter::Option() + { + return m_packageFieldMatchOption; + } + void PackageMatchFilter::Option(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption const& value) + { + m_packageFieldMatchOption = value; + } + winrt::Microsoft::Management::Deployment::PackageMatchField PackageMatchFilter::Field() + { + return m_matchField; + } + void PackageMatchFilter::Field(winrt::Microsoft::Management::Deployment::PackageMatchField const& value) + { + m_matchField = value; + } + hstring PackageMatchFilter::Value() + { + return hstring(m_value); + } + void PackageMatchFilter::Value(hstring const& value) + { + m_value = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(PackageMatchFilter); +} diff --git a/src/Microsoft.Management.Deployment/PackageMatchFilter.h b/src/Microsoft.Management.Deployment/PackageMatchFilter.h index db3aa6d38b..4d5f767326 100644 --- a/src/Microsoft.Management.Deployment/PackageMatchFilter.h +++ b/src/Microsoft.Management.Deployment/PackageMatchFilter.h @@ -1,49 +1,49 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "PackageMatchFilter.g.h" -#include "Public/ComClsids.h" -#include - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace AppInstaller::Repository -{ - struct PackageMatchFilter; -} -#endif - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter)] - struct PackageMatchFilter : PackageMatchFilterT - { - PackageMatchFilter() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize(::AppInstaller::Repository::PackageMatchFilter matchFilter); -#endif - - winrt::Microsoft::Management::Deployment::PackageFieldMatchOption Option(); - void Option(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption const& value); - winrt::Microsoft::Management::Deployment::PackageMatchField Field(); - void Field(winrt::Microsoft::Management::Deployment::PackageMatchField const& value); - hstring Value(); - void Value(hstring const& value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - hstring m_value; - winrt::Microsoft::Management::Deployment::PackageMatchField m_matchField = winrt::Microsoft::Management::Deployment::PackageMatchField::CatalogDefault; - winrt::Microsoft::Management::Deployment::PackageFieldMatchOption m_packageFieldMatchOption = winrt::Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct PackageMatchFilter : PackageMatchFilterT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "PackageMatchFilter.g.h" +#include "Public/ComClsids.h" +#include + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace AppInstaller::Repository +{ + struct PackageMatchFilter; +} +#endif + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter)] + struct PackageMatchFilter : PackageMatchFilterT + { + PackageMatchFilter() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize(::AppInstaller::Repository::PackageMatchFilter matchFilter); +#endif + + winrt::Microsoft::Management::Deployment::PackageFieldMatchOption Option(); + void Option(winrt::Microsoft::Management::Deployment::PackageFieldMatchOption const& value); + winrt::Microsoft::Management::Deployment::PackageMatchField Field(); + void Field(winrt::Microsoft::Management::Deployment::PackageMatchField const& value); + hstring Value(); + void Value(hstring const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + hstring m_value; + winrt::Microsoft::Management::Deployment::PackageMatchField m_matchField = winrt::Microsoft::Management::Deployment::PackageMatchField::CatalogDefault; + winrt::Microsoft::Management::Deployment::PackageFieldMatchOption m_packageFieldMatchOption = winrt::Microsoft::Management::Deployment::PackageFieldMatchOption::Equals; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct PackageMatchFilter : PackageMatchFilterT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp index 6d2d245c57..f6e2bd2308 100644 --- a/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp +++ b/src/Microsoft.Management.Deployment/PackageVersionInfo.cpp @@ -1,229 +1,229 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include -#include -#include "PackageVersionInfo.h" -#include "PackageVersionInfo.g.cpp" -#include "PackageCatalogInfo.h" -#include "PackageCatalog.h" -#include "PackageInstallerInfo.h" -#include "CatalogPackage.h" -#include "CatalogPackageMetadata.h" -#include "ComContext.h" -#include "Workflows/WorkflowBase.h" -#include -#include "winget/RepositorySearch.h" -#include "AppInstallerVersions.h" -#include "Converters.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 ) -// 4467 Allow use of uuid attribute for com object creation. -#include "PackageManager.h" -#pragma warning( pop ) -#include -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - namespace - { - // Do the same thing as PopulateContextFromInstallOptions but without all the string conversions. - AppInstaller::Manifest::ManifestComparator::Options GetComparatorOptionsFromInstallOptions(InstallOptions options) - { - AppInstaller::Manifest::ManifestComparator::Options result; - - auto installerType = GetManifestInstallerType(options.InstallerType()); - if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) - { - result.RequestedInstallerType = installerType; - } - - auto manifestScope = GetManifestScope(options.PackageInstallScope()); - if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) - { - result.RequestedInstallerScope = manifestScope.first; - result.AllowUnknownScope = manifestScope.second; - } - - if (options.AllowedArchitectures().Size() != 0) - { - for (auto architecture : options.AllowedArchitectures()) - { - auto convertedArchitecture = GetUtilityArchitecture(architecture); - if (convertedArchitecture) - { - result.AllowedArchitectures.push_back(convertedArchitecture.value()); - } - } - } - - return result; - } - } - - void PackageVersionInfo::Initialize(std::shared_ptr<::AppInstaller::Repository::IPackageVersion> packageVersion) - { - m_packageVersion = std::move(packageVersion); - } - std::shared_ptr<::AppInstaller::Repository::IPackageVersion> PackageVersionInfo::GetRepositoryPackageVersion() - { - return m_packageVersion; - } - hstring PackageVersionInfo::GetMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField const& metadataField) - { - ::AppInstaller::Repository::PackageVersionMetadata metadataKey = GetRepositoryPackageVersionMetadata(metadataField); - ::AppInstaller::Repository::IPackageVersion::Metadata metadata = m_packageVersion->GetMetadata(); - auto result = metadata.find(metadataKey); - if (result == metadata.end()) - { - return {}; - } - - hstring resultString = winrt::to_hstring(result->second); - // The api uses "System" rather than "Machine" for install scope. - if (metadataField == PackageVersionMetadataField::InstalledScope && resultString == L"Machine") - { - return winrt::to_hstring(L"System"); - } - return resultString; - } - hstring PackageVersionInfo::Id() - { - return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Id).get()); - } - hstring PackageVersionInfo::DisplayName() - { - return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Name).get()); - } - hstring PackageVersionInfo::Publisher() - { - return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Publisher).get()); - } - hstring PackageVersionInfo::Version() - { - return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Version).get()); - } - hstring PackageVersionInfo::Channel() - { - return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Channel).get()); - } - winrt::Windows::Foundation::Collections::IVectorView PackageVersionInfo::PackageFamilyNames() - { - if (!m_packageFamilyNames) - { - // Vector hasn't been created yet, create and populate it. - auto packageFamilyNames = winrt::single_threaded_vector(); - for (auto&& string : m_packageVersion->GetMultiProperty(::AppInstaller::Repository::PackageVersionMultiProperty::PackageFamilyName)) - { - packageFamilyNames.Append(winrt::to_hstring(string)); - } - m_packageFamilyNames = packageFamilyNames; - } - return m_packageFamilyNames.GetView(); - } - winrt::Windows::Foundation::Collections::IVectorView PackageVersionInfo::ProductCodes() - { - if (!m_productCodes) - { - // Vector hasn't been created yet, create and populate it. - auto productCodes = winrt::single_threaded_vector(); - for (auto&& string : m_packageVersion->GetMultiProperty(::AppInstaller::Repository::PackageVersionMultiProperty::ProductCode)) - { - productCodes.Append(winrt::to_hstring(string)); - } - m_productCodes = productCodes; - } - return m_productCodes.GetView(); - } - winrt::Microsoft::Management::Deployment::PackageCatalog PackageVersionInfo::PackageCatalog() - { - if (!m_packageCatalog) - { - auto packageCatalogInfo = winrt::make_self>(); - packageCatalogInfo->Initialize(m_packageVersion->GetSource().GetDetails()); - auto packageCatalog = winrt::make_self>(); - packageCatalog->Initialize(*packageCatalogInfo, m_packageVersion->GetSource(), false); - m_packageCatalog = *packageCatalog; - } - return m_packageCatalog; - } - - winrt::Microsoft::Management::Deployment::CompareResult PackageVersionInfo::CompareToVersion(const hstring& versionString) - { - if (versionString.empty()) - { - return CompareResult::Unknown; - } - - AppInstaller::Utility::Version thisVersion{ m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Version).get() }; - AppInstaller::Utility::Version otherVersion{ AppInstaller::Utility::ConvertToUTF8(versionString) }; - - if (thisVersion < otherVersion) - { - return CompareResult::Lesser; - } - else if (otherVersion < thisVersion) - { - return CompareResult::Greater; - } - else - { - return CompareResult::Equal; - } - } - bool PackageVersionInfo::HasApplicableInstaller(InstallOptions options) - { - AppInstaller::Manifest::ManifestComparator manifestComparator{ GetComparatorOptionsFromInstallOptions(options) }; - AppInstaller::Manifest::Manifest manifest = m_packageVersion->GetManifest(); - auto result = manifestComparator.GetPreferredInstaller(manifest); - return result.installer.has_value(); - } - winrt::Microsoft::Management::Deployment::PackageInstallerInfo PackageVersionInfo::GetApplicableInstaller(InstallOptions options) - { - AppInstaller::Manifest::ManifestComparator manifestComparator{ GetComparatorOptionsFromInstallOptions(options) }; - AppInstaller::Manifest::Manifest manifest = m_packageVersion->GetManifest(); - auto result = manifestComparator.GetPreferredInstaller(manifest); - - if (result.installer.has_value()) - { - auto packageInstallerInfo = winrt::make_self>(); - packageInstallerInfo->Initialize(result.installer.value()); - return *packageInstallerInfo; - } - else - { - return nullptr; - } - } - Microsoft::Management::Deployment::CatalogPackageMetadata PackageVersionInfo::GetCatalogPackageMetadata() - { - auto catalogPackageMetadata = winrt::make_self>(); - if (m_packageVersion) - { - auto manifest = m_packageVersion->GetManifest(); - manifest.ApplyLocale(); - catalogPackageMetadata->Initialize(manifest.CurrentLocalization); - } - - return *catalogPackageMetadata; - } - Microsoft::Management::Deployment::CatalogPackageMetadata PackageVersionInfo::GetCatalogPackageMetadata(const hstring& preferredLocale) - { - std::string localeString = winrt::to_string(preferredLocale); - if (!::AppInstaller::Locale::IsWellFormedBcp47Tag(localeString)) - { - throw hresult_invalid_argument(); - } - - auto catalogPackageMetadata = winrt::make_self>(); - if (m_packageVersion) - { - auto manifest = m_packageVersion->GetManifest(); - manifest.ApplyLocale(localeString); - catalogPackageMetadata->Initialize(manifest.CurrentLocalization); - } - - return *catalogPackageMetadata; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include +#include +#include "PackageVersionInfo.h" +#include "PackageVersionInfo.g.cpp" +#include "PackageCatalogInfo.h" +#include "PackageCatalog.h" +#include "PackageInstallerInfo.h" +#include "CatalogPackage.h" +#include "CatalogPackageMetadata.h" +#include "ComContext.h" +#include "Workflows/WorkflowBase.h" +#include +#include "winget/RepositorySearch.h" +#include "AppInstallerVersions.h" +#include "Converters.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 ) +// 4467 Allow use of uuid attribute for com object creation. +#include "PackageManager.h" +#pragma warning( pop ) +#include +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + namespace + { + // Do the same thing as PopulateContextFromInstallOptions but without all the string conversions. + AppInstaller::Manifest::ManifestComparator::Options GetComparatorOptionsFromInstallOptions(InstallOptions options) + { + AppInstaller::Manifest::ManifestComparator::Options result; + + auto installerType = GetManifestInstallerType(options.InstallerType()); + if (installerType != AppInstaller::Manifest::InstallerTypeEnum::Unknown) + { + result.RequestedInstallerType = installerType; + } + + auto manifestScope = GetManifestScope(options.PackageInstallScope()); + if (manifestScope.first != ::AppInstaller::Manifest::ScopeEnum::Unknown) + { + result.RequestedInstallerScope = manifestScope.first; + result.AllowUnknownScope = manifestScope.second; + } + + if (options.AllowedArchitectures().Size() != 0) + { + for (auto architecture : options.AllowedArchitectures()) + { + auto convertedArchitecture = GetUtilityArchitecture(architecture); + if (convertedArchitecture) + { + result.AllowedArchitectures.push_back(convertedArchitecture.value()); + } + } + } + + return result; + } + } + + void PackageVersionInfo::Initialize(std::shared_ptr<::AppInstaller::Repository::IPackageVersion> packageVersion) + { + m_packageVersion = std::move(packageVersion); + } + std::shared_ptr<::AppInstaller::Repository::IPackageVersion> PackageVersionInfo::GetRepositoryPackageVersion() + { + return m_packageVersion; + } + hstring PackageVersionInfo::GetMetadata(winrt::Microsoft::Management::Deployment::PackageVersionMetadataField const& metadataField) + { + ::AppInstaller::Repository::PackageVersionMetadata metadataKey = GetRepositoryPackageVersionMetadata(metadataField); + ::AppInstaller::Repository::IPackageVersion::Metadata metadata = m_packageVersion->GetMetadata(); + auto result = metadata.find(metadataKey); + if (result == metadata.end()) + { + return {}; + } + + hstring resultString = winrt::to_hstring(result->second); + // The api uses "System" rather than "Machine" for install scope. + if (metadataField == PackageVersionMetadataField::InstalledScope && resultString == L"Machine") + { + return winrt::to_hstring(L"System"); + } + return resultString; + } + hstring PackageVersionInfo::Id() + { + return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Id).get()); + } + hstring PackageVersionInfo::DisplayName() + { + return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Name).get()); + } + hstring PackageVersionInfo::Publisher() + { + return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Publisher).get()); + } + hstring PackageVersionInfo::Version() + { + return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Version).get()); + } + hstring PackageVersionInfo::Channel() + { + return winrt::to_hstring(m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Channel).get()); + } + winrt::Windows::Foundation::Collections::IVectorView PackageVersionInfo::PackageFamilyNames() + { + if (!m_packageFamilyNames) + { + // Vector hasn't been created yet, create and populate it. + auto packageFamilyNames = winrt::single_threaded_vector(); + for (auto&& string : m_packageVersion->GetMultiProperty(::AppInstaller::Repository::PackageVersionMultiProperty::PackageFamilyName)) + { + packageFamilyNames.Append(winrt::to_hstring(string)); + } + m_packageFamilyNames = packageFamilyNames; + } + return m_packageFamilyNames.GetView(); + } + winrt::Windows::Foundation::Collections::IVectorView PackageVersionInfo::ProductCodes() + { + if (!m_productCodes) + { + // Vector hasn't been created yet, create and populate it. + auto productCodes = winrt::single_threaded_vector(); + for (auto&& string : m_packageVersion->GetMultiProperty(::AppInstaller::Repository::PackageVersionMultiProperty::ProductCode)) + { + productCodes.Append(winrt::to_hstring(string)); + } + m_productCodes = productCodes; + } + return m_productCodes.GetView(); + } + winrt::Microsoft::Management::Deployment::PackageCatalog PackageVersionInfo::PackageCatalog() + { + if (!m_packageCatalog) + { + auto packageCatalogInfo = winrt::make_self>(); + packageCatalogInfo->Initialize(m_packageVersion->GetSource().GetDetails()); + auto packageCatalog = winrt::make_self>(); + packageCatalog->Initialize(*packageCatalogInfo, m_packageVersion->GetSource(), false); + m_packageCatalog = *packageCatalog; + } + return m_packageCatalog; + } + + winrt::Microsoft::Management::Deployment::CompareResult PackageVersionInfo::CompareToVersion(const hstring& versionString) + { + if (versionString.empty()) + { + return CompareResult::Unknown; + } + + AppInstaller::Utility::Version thisVersion{ m_packageVersion->GetProperty(::AppInstaller::Repository::PackageVersionProperty::Version).get() }; + AppInstaller::Utility::Version otherVersion{ AppInstaller::Utility::ConvertToUTF8(versionString) }; + + if (thisVersion < otherVersion) + { + return CompareResult::Lesser; + } + else if (otherVersion < thisVersion) + { + return CompareResult::Greater; + } + else + { + return CompareResult::Equal; + } + } + bool PackageVersionInfo::HasApplicableInstaller(InstallOptions options) + { + AppInstaller::Manifest::ManifestComparator manifestComparator{ GetComparatorOptionsFromInstallOptions(options) }; + AppInstaller::Manifest::Manifest manifest = m_packageVersion->GetManifest(); + auto result = manifestComparator.GetPreferredInstaller(manifest); + return result.installer.has_value(); + } + winrt::Microsoft::Management::Deployment::PackageInstallerInfo PackageVersionInfo::GetApplicableInstaller(InstallOptions options) + { + AppInstaller::Manifest::ManifestComparator manifestComparator{ GetComparatorOptionsFromInstallOptions(options) }; + AppInstaller::Manifest::Manifest manifest = m_packageVersion->GetManifest(); + auto result = manifestComparator.GetPreferredInstaller(manifest); + + if (result.installer.has_value()) + { + auto packageInstallerInfo = winrt::make_self>(); + packageInstallerInfo->Initialize(result.installer.value()); + return *packageInstallerInfo; + } + else + { + return nullptr; + } + } + Microsoft::Management::Deployment::CatalogPackageMetadata PackageVersionInfo::GetCatalogPackageMetadata() + { + auto catalogPackageMetadata = winrt::make_self>(); + if (m_packageVersion) + { + auto manifest = m_packageVersion->GetManifest(); + manifest.ApplyLocale(); + catalogPackageMetadata->Initialize(manifest.CurrentLocalization); + } + + return *catalogPackageMetadata; + } + Microsoft::Management::Deployment::CatalogPackageMetadata PackageVersionInfo::GetCatalogPackageMetadata(const hstring& preferredLocale) + { + std::string localeString = winrt::to_string(preferredLocale); + if (!::AppInstaller::Locale::IsWellFormedBcp47Tag(localeString)) + { + throw hresult_invalid_argument(); + } + + auto catalogPackageMetadata = winrt::make_self>(); + if (m_packageVersion) + { + auto manifest = m_packageVersion->GetManifest(); + manifest.ApplyLocale(localeString); + catalogPackageMetadata->Initialize(manifest.CurrentLocalization); + } + + return *catalogPackageMetadata; + } +} diff --git a/src/Microsoft.Management.Deployment/Public/CanUnload.h b/src/Microsoft.Management.Deployment/Public/CanUnload.h index 960a9eaf2e..a284f87ded 100644 --- a/src/Microsoft.Management.Deployment/Public/CanUnload.h +++ b/src/Microsoft.Management.Deployment/Public/CanUnload.h @@ -1,12 +1,12 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - // Sets whether the module can unload or not. - void SetCanUnload(bool value); - - // Gets whether the module can unload or not. - bool GetCanUnload(); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + // Sets whether the module can unload or not. + void SetCanUnload(bool value); + + // Gets whether the module can unload or not. + bool GetCanUnload(); +} diff --git a/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h b/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h index d43af6915a..b0564ec4f4 100644 --- a/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h +++ b/src/Microsoft.Management.Deployment/Public/CoCreatableMicrosoftManagementDeploymentClass.h @@ -1,32 +1,32 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - // Enable custom code to run before creating any object through the factory. - // Currently that means requiring the overall WinGet policy to be enabled. - template - class wrl_factory_for_winrt_com_class : public ::wil::wrl_factory_for_winrt_com_class - { - public: - IFACEMETHODIMP CreateInstance(_In_opt_::IUnknown* unknownOuter, REFIID riid, _COM_Outptr_ void** object) noexcept try - { - *object = nullptr; - RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); - - return ::wil::wrl_factory_for_winrt_com_class::CreateInstance(unknownOuter, riid, object); - } - CATCH_RETURN() - }; - -#define CoCreatableMicrosoftManagementDeploymentClass(className) \ - CoCreatableClassWithFactory(className, ::winrt::Microsoft::Management::Deployment::implementation::wrl_factory_for_winrt_com_class) \ - void CoCreatableMicrosoftManagementDeploymentClass_WRL_ModuleCountCheckFor_ ## className() { \ - static_assert(__is_base_of(::AppInstaller::WinRT::ModuleCountBase, ::winrt::Microsoft::Management::Deployment::factory_implementation:: ## className), "Object factories must derive from AppInstaller::WinRT::ModuleCountBase"); \ - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + // Enable custom code to run before creating any object through the factory. + // Currently that means requiring the overall WinGet policy to be enabled. + template + class wrl_factory_for_winrt_com_class : public ::wil::wrl_factory_for_winrt_com_class + { + public: + IFACEMETHODIMP CreateInstance(_In_opt_::IUnknown* unknownOuter, REFIID riid, _COM_Outptr_ void** object) noexcept try + { + *object = nullptr; + RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); + + return ::wil::wrl_factory_for_winrt_com_class::CreateInstance(unknownOuter, riid, object); + } + CATCH_RETURN() + }; + +#define CoCreatableMicrosoftManagementDeploymentClass(className) \ + CoCreatableClassWithFactory(className, ::winrt::Microsoft::Management::Deployment::implementation::wrl_factory_for_winrt_com_class) \ + void CoCreatableMicrosoftManagementDeploymentClass_WRL_ModuleCountCheckFor_ ## className() { \ + static_assert(__is_base_of(::AppInstaller::WinRT::ModuleCountBase, ::winrt::Microsoft::Management::Deployment::factory_implementation:: ## className), "Object factories must derive from AppInstaller::WinRT::ModuleCountBase"); \ + } +} diff --git a/src/Microsoft.Management.Deployment/Public/ComClsids.h b/src/Microsoft.Management.Deployment/Public/ComClsids.h index e9137d788f..177c2e1c65 100644 --- a/src/Microsoft.Management.Deployment/Public/ComClsids.h +++ b/src/Microsoft.Management.Deployment/Public/ComClsids.h @@ -1,58 +1,58 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -// Clsids for out-of-proc com invocation -#if USE_PROD_CLSIDS -#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "C53A4F16-787E-42A4-B304-29EFFB4BF597" -#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "572DED96-9C60-4526-8F92-EE7D91D38C1A" -#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "526534B8-7E46-47C8-8416-B1685C327D37" -#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "1095F097-EB96-453B-B4E6-1613637F3B14" -#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "E1D9A11E-9F85-4D87-9C17-2B93143ADB8D" -#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "D02C9DAF-99DC-429C-B503-4E504E4AB000" -#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "73D763B7-2937-432F-A97A-D98A4A596126" -#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "4CBABE76-7322-4BE4-9CEA-2589A80682DC" -#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "BA580786-BDE3-4F6C-B8F3-44698AC8711A" -#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "0498F441-3097-455F-9CAF-148F28293865" -#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "DB9D012D-00D7-47EE-8FB1-606E10AC4F51" -#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "032B1C58-B975-469B-A013-E632B6ECE8D8" -#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "A9F5E736-68CE-463C-BA6D-DE968F0CCE04" -#else -#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "74CB3139-B7C5-4B9E-9388-E6616DEA288C" -#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96" -#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "EE160901-B317-4EA7-9CC6-5355C6D7D8A7" -#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "44FE0580-62F7-44D4-9E91-AA9614AB3E86" -#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C" -#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "3F85B9F4-487A-4C48-9035-2903F8A6D9E8" -#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "C9ED7917-66AB-4E31-A92A-F65F18EF7933" -#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "8EF324ED-367C-4880-83E5-BB2ABD0B72F6" -#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "6484A61D-50FA-41F0-B71E-F4370C6EB37C" -#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03" -#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "D58C7E4C-70E6-476C-A5D4-80341ED80252" -#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "87A96609-1A39-4955-BE72-7174E147B7DC" -#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "29B19238-81AD-4A8E-A2FC-ADF17C38CAEB" -#endif - -// Clsids only used in in-proc invocation -#define WINGET_INPROC_ONLY_COM_CLSID_PackageManagerSettings "80CF9D63-5505-4342-B9B4-BB87895CA8BB" - -namespace winrt::Microsoft::Management::Deployment -{ - // clsid constants for in-proc com invocation - const CLSID WINGET_INPROC_COM_CLSID_PackageManager = { 0x2DDE4456, 0x64D9, 0x4673, 0x8F, 0x7E, 0xA4, 0xF1, 0x9A, 0x2E, 0x6C, 0xC3 }; // 2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3 - const CLSID WINGET_INPROC_COM_CLSID_FindPackagesOptions = { 0x96B9A53A, 0x9228, 0x4DA0, 0xB0, 0x13, 0xBB, 0x1B, 0x20, 0x31, 0xAB, 0x3D }; // 96B9A53A-9228-4DA0-B013-BB1B2031AB3D - const CLSID WINGET_INPROC_COM_CLSID_CreateCompositePackageCatalogOptions = { 0x768318A6, 0x2EB5, 0x400D, 0x84, 0xD0, 0xDF, 0x35, 0x34, 0xC3, 0x0F, 0x5D }; // 768318A6-2EB5-400D-84D0-DF3534C30F5D - const CLSID WINGET_INPROC_COM_CLSID_InstallOptions = { 0xE2AF3BA8, 0x8A88, 0x4766, 0x9D, 0xDA, 0xAE, 0x40, 0x13, 0xAD, 0xE2, 0x86 }; // E2AF3BA8-8A88-4766-9DDA-AE4013ADE286 - const CLSID WINGET_INPROC_COM_CLSID_UninstallOptions = { 0x869CB959, 0xEB54, 0x425C, 0xA1, 0xE4, 0x1A, 0x1C, 0x29, 0x1C, 0x64, 0xE9 }; // 869CB959-EB54-425C-A1E4-1A1C291C64E9 - const CLSID WINGET_INPROC_COM_CLSID_PackageMatchFilter = { 0x57DC8962, 0x7343, 0x42CD, 0xB9, 0x1C, 0x04, 0xF6, 0xA2, 0x5D, 0xB1, 0xD0 }; // 57DC8962-7343-42CD-B91C-04F6A25DB1D0 - const CLSID WINGET_INPROC_COM_CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB - const CLSID WINGET_INPROC_COM_CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 - const CLSID WINGET_INPROC_COM_CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 - const CLSID WINGET_INPROC_COM_CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} - const CLSID WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} - const CLSID WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} - const CLSID WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions = { 0xe8e12fe1, 0xab77, 0x40c4, 0xa5, 0x62, 0xe9, 0x1f, 0xb5, 0x1b, 0x4e, 0x82 }; // {E8E12FE1-AB77-40C4-A562-E91FB51B4E82} - - CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +// Clsids for out-of-proc com invocation +#if USE_PROD_CLSIDS +#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "C53A4F16-787E-42A4-B304-29EFFB4BF597" +#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "572DED96-9C60-4526-8F92-EE7D91D38C1A" +#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "526534B8-7E46-47C8-8416-B1685C327D37" +#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "1095F097-EB96-453B-B4E6-1613637F3B14" +#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "E1D9A11E-9F85-4D87-9C17-2B93143ADB8D" +#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "D02C9DAF-99DC-429C-B503-4E504E4AB000" +#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "73D763B7-2937-432F-A97A-D98A4A596126" +#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "4CBABE76-7322-4BE4-9CEA-2589A80682DC" +#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "BA580786-BDE3-4F6C-B8F3-44698AC8711A" +#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "0498F441-3097-455F-9CAF-148F28293865" +#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "DB9D012D-00D7-47EE-8FB1-606E10AC4F51" +#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "032B1C58-B975-469B-A013-E632B6ECE8D8" +#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "A9F5E736-68CE-463C-BA6D-DE968F0CCE04" +#else +#define WINGET_OUTOFPROC_COM_CLSID_PackageManager "74CB3139-B7C5-4B9E-9388-E6616DEA288C" +#define WINGET_OUTOFPROC_COM_CLSID_FindPackagesOptions "1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96" +#define WINGET_OUTOFPROC_COM_CLSID_CreateCompositePackageCatalogOptions "EE160901-B317-4EA7-9CC6-5355C6D7D8A7" +#define WINGET_OUTOFPROC_COM_CLSID_InstallOptions "44FE0580-62F7-44D4-9E91-AA9614AB3E86" +#define WINGET_OUTOFPROC_COM_CLSID_UninstallOptions "AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C" +#define WINGET_OUTOFPROC_COM_CLSID_PackageMatchFilter "3F85B9F4-487A-4C48-9035-2903F8A6D9E8" +#define WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions "C9ED7917-66AB-4E31-A92A-F65F18EF7933" +#define WINGET_OUTOFPROC_COM_CLSID_DownloadOptions "8EF324ED-367C-4880-83E5-BB2ABD0B72F6" +#define WINGET_OUTOFPROC_COM_CLSID_AuthenticationArguments "6484A61D-50FA-41F0-B71E-F4370C6EB37C" +#define WINGET_OUTOFPROC_COM_CLSID_RepairOptions "E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03" +#define WINGET_OUTOFPROC_COM_CLSID_AddPackageCatalogOptions "D58C7E4C-70E6-476C-A5D4-80341ED80252" +#define WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions "87A96609-1A39-4955-BE72-7174E147B7DC" +#define WINGET_OUTOFPROC_COM_CLSID_EditPackageCatalogOptions "29B19238-81AD-4A8E-A2FC-ADF17C38CAEB" +#endif + +// Clsids only used in in-proc invocation +#define WINGET_INPROC_ONLY_COM_CLSID_PackageManagerSettings "80CF9D63-5505-4342-B9B4-BB87895CA8BB" + +namespace winrt::Microsoft::Management::Deployment +{ + // clsid constants for in-proc com invocation + const CLSID WINGET_INPROC_COM_CLSID_PackageManager = { 0x2DDE4456, 0x64D9, 0x4673, 0x8F, 0x7E, 0xA4, 0xF1, 0x9A, 0x2E, 0x6C, 0xC3 }; // 2DDE4456-64D9-4673-8F7E-A4F19A2E6CC3 + const CLSID WINGET_INPROC_COM_CLSID_FindPackagesOptions = { 0x96B9A53A, 0x9228, 0x4DA0, 0xB0, 0x13, 0xBB, 0x1B, 0x20, 0x31, 0xAB, 0x3D }; // 96B9A53A-9228-4DA0-B013-BB1B2031AB3D + const CLSID WINGET_INPROC_COM_CLSID_CreateCompositePackageCatalogOptions = { 0x768318A6, 0x2EB5, 0x400D, 0x84, 0xD0, 0xDF, 0x35, 0x34, 0xC3, 0x0F, 0x5D }; // 768318A6-2EB5-400D-84D0-DF3534C30F5D + const CLSID WINGET_INPROC_COM_CLSID_InstallOptions = { 0xE2AF3BA8, 0x8A88, 0x4766, 0x9D, 0xDA, 0xAE, 0x40, 0x13, 0xAD, 0xE2, 0x86 }; // E2AF3BA8-8A88-4766-9DDA-AE4013ADE286 + const CLSID WINGET_INPROC_COM_CLSID_UninstallOptions = { 0x869CB959, 0xEB54, 0x425C, 0xA1, 0xE4, 0x1A, 0x1C, 0x29, 0x1C, 0x64, 0xE9 }; // 869CB959-EB54-425C-A1E4-1A1C291C64E9 + const CLSID WINGET_INPROC_COM_CLSID_PackageMatchFilter = { 0x57DC8962, 0x7343, 0x42CD, 0xB9, 0x1C, 0x04, 0xF6, 0xA2, 0x5D, 0xB1, 0xD0 }; // 57DC8962-7343-42CD-B91C-04F6A25DB1D0 + const CLSID WINGET_INPROC_COM_CLSID_PackageManagerSettings = { 0x80CF9D63, 0x5505, 0x4342, 0xB9, 0xB4, 0xBB, 0x87, 0x89, 0x5C, 0xA8, 0xBB }; // 80CF9D63-5505-4342-B9B4-BB87895CA8BB + const CLSID WINGET_INPROC_COM_CLSID_DownloadOptions = { 0x4288DF96, 0xFDC9, 0x4B68, 0xB4, 0x03, 0x19, 0x3D, 0xBB, 0xF5, 0x6A, 0x24 }; // 4288DF96-FDC9-4B68-B403-193DBBF56A24 + const CLSID WINGET_INPROC_COM_CLSID_AuthenticationArguments = { 0x8D593114, 0x1CF1, 0x43B9, 0x87, 0x22, 0x4D, 0xBB, 0x30, 0x10, 0x32, 0x96 }; // 8D593114-1CF1-43B9-8722-4DBB30103296 + const CLSID WINGET_INPROC_COM_CLSID_RepairOptions = { 0x30c024c4, 0x852c, 0x4dd4, 0x98, 0x10, 0x13, 0x48, 0xc5, 0x1e, 0xf9, 0xbb }; // {30C024C4-852C-4DD4-9810-1348C51EF9BB} + const CLSID WINGET_INPROC_COM_CLSID_AddPackageCatalogOptions = { 0x24e6f1fa, 0xe4c3, 0x4acd, 0x96, 0x5d, 0xdf, 0x21, 0x3f, 0xd5, 0x8f, 0x15 }; // {24E6F1FA-E4C3-4ACD-965D-DF213FD58F15} + const CLSID WINGET_INPROC_COM_CLSID_RemovePackageCatalogOptions = { 0x1125d3a6, 0xe2ce, 0x479a, 0x91, 0xd5, 0x71, 0xa3, 0xf6, 0xf8, 0xb0, 0xb }; // {1125D3A6-E2CE-479A-91D5-71A3F6F8B00B} + const CLSID WINGET_INPROC_COM_CLSID_EditPackageCatalogOptions = { 0xe8e12fe1, 0xab77, 0x40c4, 0xa5, 0x62, 0xe9, 0x1f, 0xb5, 0x1b, 0x4e, 0x82 }; // {E8E12FE1-AB77-40C4-A562-E91FB51B4E82} + + CLSID GetRedirectedClsidFromInProcClsid(REFCLSID clsid); +} diff --git a/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.cpp b/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.cpp index 7a0fe2c586..c0c0d80f2c 100644 --- a/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.cpp +++ b/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.cpp @@ -1,27 +1,27 @@ // Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RefreshPackageCatalogResult.h" -#include "RefreshPackageCatalogResult.g.cpp" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void RefreshPackageCatalogResult::Initialize( - winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus status, - winrt::hresult extendedErrorCode) - { - m_status = status; - m_extendedErrorCode = extendedErrorCode; - } - - winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus RefreshPackageCatalogResult::Status() - { - return m_status; - } - - winrt::hresult RefreshPackageCatalogResult::ExtendedErrorCode() - { - return m_extendedErrorCode; - } -} +// Licensed under the MIT License. +#include "pch.h" +#include "RefreshPackageCatalogResult.h" +#include "RefreshPackageCatalogResult.g.cpp" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void RefreshPackageCatalogResult::Initialize( + winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus status, + winrt::hresult extendedErrorCode) + { + m_status = status; + m_extendedErrorCode = extendedErrorCode; + } + + winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus RefreshPackageCatalogResult::Status() + { + return m_status; + } + + winrt::hresult RefreshPackageCatalogResult::ExtendedErrorCode() + { + return m_extendedErrorCode; + } +} diff --git a/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.h b/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.h index 08fd3cb448..e97b71a17d 100644 --- a/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.h +++ b/src/Microsoft.Management.Deployment/RefreshPackageCatalogResult.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "RefreshPackageCatalogResult.g.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - struct RefreshPackageCatalogResult : RefreshPackageCatalogResultT - { - RefreshPackageCatalogResult() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize( - winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus status, - winrt::hresult extendedErrorCode); -#endif - - winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus Status(); - winrt::hresult ExtendedErrorCode(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus m_status = winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus::Ok; - winrt::hresult m_extendedErrorCode = S_OK; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "RefreshPackageCatalogResult.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct RefreshPackageCatalogResult : RefreshPackageCatalogResultT + { + RefreshPackageCatalogResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize( + winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus status, + winrt::hresult extendedErrorCode); +#endif + + winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus Status(); + winrt::hresult ExtendedErrorCode(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus m_status = winrt::Microsoft::Management::Deployment::RefreshPackageCatalogStatus::Ok; + winrt::hresult m_extendedErrorCode = S_OK; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.cpp b/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.cpp index e66d3dcb0f..f34a75f9d7 100644 --- a/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.cpp +++ b/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.cpp @@ -1,35 +1,35 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "RemovePackageCatalogOptions.h" -#pragma warning( pop ) -#include "RemovePackageCatalogOptions.g.cpp" -#include "Converters.h" -#include "Helpers.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - hstring RemovePackageCatalogOptions::Name() - { - return hstring(m_name); - } - void RemovePackageCatalogOptions::Name(hstring const& value) - { - m_name = value; - } - bool RemovePackageCatalogOptions::PreserveData() - { - return m_preserveData; - } - void RemovePackageCatalogOptions::PreserveData(bool const& value) - { - m_preserveData = value; - } - - CoCreatableMicrosoftManagementDeploymentClass(RemovePackageCatalogOptions); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "RemovePackageCatalogOptions.h" +#pragma warning( pop ) +#include "RemovePackageCatalogOptions.g.cpp" +#include "Converters.h" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + hstring RemovePackageCatalogOptions::Name() + { + return hstring(m_name); + } + void RemovePackageCatalogOptions::Name(hstring const& value) + { + m_name = value; + } + bool RemovePackageCatalogOptions::PreserveData() + { + return m_preserveData; + } + void RemovePackageCatalogOptions::PreserveData(bool const& value) + { + m_preserveData = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(RemovePackageCatalogOptions); +} diff --git a/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h b/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h index 6edaf47b2e..45981f1bbf 100644 --- a/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h +++ b/src/Microsoft.Management.Deployment/RemovePackageCatalogOptions.h @@ -1,38 +1,38 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "RemovePackageCatalogOptions.g.h" -#include "public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions)] - struct RemovePackageCatalogOptions : RemovePackageCatalogOptionsT - { - RemovePackageCatalogOptions() = default; - - hstring Name(); - void Name(hstring const& value); - - bool PreserveData(); - void PreserveData(bool const& value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - hstring m_name = L""; - bool m_preserveData = false; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct RemovePackageCatalogOptions : - RemovePackageCatalogOptionsT, - AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "RemovePackageCatalogOptions.g.h" +#include "public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_RemovePackageCatalogOptions)] + struct RemovePackageCatalogOptions : RemovePackageCatalogOptionsT + { + RemovePackageCatalogOptions() = default; + + hstring Name(); + void Name(hstring const& value); + + bool PreserveData(); + void PreserveData(bool const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + hstring m_name = L""; + bool m_preserveData = false; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct RemovePackageCatalogOptions : + RemovePackageCatalogOptionsT, + AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.cpp b/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.cpp index b74be4bf79..e1dfaeb036 100644 --- a/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.cpp +++ b/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.cpp @@ -1,25 +1,25 @@ // Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "RemovePackageCatalogResult.h" -#include "RemovePackageCatalogResult.g.cpp" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void RemovePackageCatalogResult::Initialize( - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status, - winrt::hresult extendedErrorCode) - { - m_status = status; - m_extendedErrorCode = extendedErrorCode; - } - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus RemovePackageCatalogResult::Status() - { - return m_status; - } - winrt::hresult RemovePackageCatalogResult::ExtendedErrorCode() - { - return m_extendedErrorCode; - } -} +// Licensed under the MIT License. +#include "pch.h" +#include "RemovePackageCatalogResult.h" +#include "RemovePackageCatalogResult.g.cpp" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void RemovePackageCatalogResult::Initialize( + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status, + winrt::hresult extendedErrorCode) + { + m_status = status; + m_extendedErrorCode = extendedErrorCode; + } + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus RemovePackageCatalogResult::Status() + { + return m_status; + } + winrt::hresult RemovePackageCatalogResult::ExtendedErrorCode() + { + return m_extendedErrorCode; + } +} diff --git a/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.h b/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.h index 1f00690e1e..446e2f5f66 100644 --- a/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.h +++ b/src/Microsoft.Management.Deployment/RemovePackageCatalogResult.h @@ -1,27 +1,27 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "RemovePackageCatalogResult.g.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - struct RemovePackageCatalogResult : RemovePackageCatalogResultT - { - RemovePackageCatalogResult() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize( - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status, - winrt::hresult extendedErrorCode); -#endif - - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus Status(); - winrt::hresult ExtendedErrorCode(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus m_status = winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus::Ok; - winrt::hresult m_extendedErrorCode = S_OK; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "RemovePackageCatalogResult.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct RemovePackageCatalogResult : RemovePackageCatalogResultT + { + RemovePackageCatalogResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize( + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus status, + winrt::hresult extendedErrorCode); +#endif + + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus Status(); + winrt::hresult ExtendedErrorCode(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus m_status = winrt::Microsoft::Management::Deployment::RemovePackageCatalogStatus::Ok; + winrt::hresult m_extendedErrorCode = S_OK; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/RepairOptions.cpp b/src/Microsoft.Management.Deployment/RepairOptions.cpp index 48b065109e..9d9ff1a8d1 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.cpp +++ b/src/Microsoft.Management.Deployment/RepairOptions.cpp @@ -1,122 +1,122 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "RepairOptions.h" -#pragma warning( pop ) -#include "RepairOptions.g.cpp" -#include "Converters.h" -#include "Helpers.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - RepairOptions::RepairOptions() - { - } - - winrt::Microsoft::Management::Deployment::PackageVersionId RepairOptions::PackageVersionId() - { - return m_packageVersionId; - } - void RepairOptions::PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value) - { - m_packageVersionId = value; - } - - winrt::Microsoft::Management::Deployment::PackageRepairScope RepairOptions::PackageRepairScope() - { - return m_packageRepairScope; - } - - void RepairOptions::PackageRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope const& value) - { - m_packageRepairScope = value; - } - - winrt::Microsoft::Management::Deployment::PackageRepairMode RepairOptions::PackageRepairMode() - { - return m_packageRepairMode; - } - - void RepairOptions::PackageRepairMode(winrt::Microsoft::Management::Deployment::PackageRepairMode const& value) - { - m_packageRepairMode = value; - } - - bool RepairOptions::AcceptPackageAgreements() - { - return m_acceptPackageAgreements; - } - - void RepairOptions::AcceptPackageAgreements(bool value) - { - m_acceptPackageAgreements = value; - } - - hstring RepairOptions::LogOutputPath() - { - return hstring(m_logOutputPath); - } - - void RepairOptions::LogOutputPath(hstring const& value) - { - m_logOutputPath = value; - } - - hstring RepairOptions::CorrelationData() - { - return hstring(m_correlationData); - } - - void RepairOptions::CorrelationData(hstring const& value) - { - m_correlationData = value; - } - - bool RepairOptions::AllowHashMismatch() - { - return m_allowHashMismatch; - } - - void RepairOptions::AllowHashMismatch(bool value) - { - m_allowHashMismatch = value; - } - - bool RepairOptions::BypassIsStoreClientBlockedPolicyCheck() - { - return m_bypassIsStoreClientBlockedPolicyCheck; - } - - void RepairOptions::BypassIsStoreClientBlockedPolicyCheck(bool value) - { - m_bypassIsStoreClientBlockedPolicyCheck = value; - } - - bool RepairOptions::Force() - { - return m_force; - } - - void RepairOptions::Force(bool value) - { - 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); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "RepairOptions.h" +#pragma warning( pop ) +#include "RepairOptions.g.cpp" +#include "Converters.h" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + RepairOptions::RepairOptions() + { + } + + winrt::Microsoft::Management::Deployment::PackageVersionId RepairOptions::PackageVersionId() + { + return m_packageVersionId; + } + void RepairOptions::PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value) + { + m_packageVersionId = value; + } + + winrt::Microsoft::Management::Deployment::PackageRepairScope RepairOptions::PackageRepairScope() + { + return m_packageRepairScope; + } + + void RepairOptions::PackageRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope const& value) + { + m_packageRepairScope = value; + } + + winrt::Microsoft::Management::Deployment::PackageRepairMode RepairOptions::PackageRepairMode() + { + return m_packageRepairMode; + } + + void RepairOptions::PackageRepairMode(winrt::Microsoft::Management::Deployment::PackageRepairMode const& value) + { + m_packageRepairMode = value; + } + + bool RepairOptions::AcceptPackageAgreements() + { + return m_acceptPackageAgreements; + } + + void RepairOptions::AcceptPackageAgreements(bool value) + { + m_acceptPackageAgreements = value; + } + + hstring RepairOptions::LogOutputPath() + { + return hstring(m_logOutputPath); + } + + void RepairOptions::LogOutputPath(hstring const& value) + { + m_logOutputPath = value; + } + + hstring RepairOptions::CorrelationData() + { + return hstring(m_correlationData); + } + + void RepairOptions::CorrelationData(hstring const& value) + { + m_correlationData = value; + } + + bool RepairOptions::AllowHashMismatch() + { + return m_allowHashMismatch; + } + + void RepairOptions::AllowHashMismatch(bool value) + { + m_allowHashMismatch = value; + } + + bool RepairOptions::BypassIsStoreClientBlockedPolicyCheck() + { + return m_bypassIsStoreClientBlockedPolicyCheck; + } + + void RepairOptions::BypassIsStoreClientBlockedPolicyCheck(bool value) + { + m_bypassIsStoreClientBlockedPolicyCheck = value; + } + + bool RepairOptions::Force() + { + return m_force; + } + + void RepairOptions::Force(bool value) + { + 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 d25837a82f..d1b26a446a 100644 --- a/src/Microsoft.Management.Deployment/RepairOptions.h +++ b/src/Microsoft.Management.Deployment/RepairOptions.h @@ -1,59 +1,59 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "RepairOptions.g.h" -#include "Public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_RepairOptions)] - struct RepairOptions : RepairOptionsT - { - RepairOptions(); - - winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); - void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); - winrt::Microsoft::Management::Deployment::PackageRepairScope PackageRepairScope(); - void PackageRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope const& value); - winrt::Microsoft::Management::Deployment::PackageRepairMode PackageRepairMode(); - void PackageRepairMode(winrt::Microsoft::Management::Deployment::PackageRepairMode const& value); - bool AcceptPackageAgreements(); - void AcceptPackageAgreements(bool value); - hstring LogOutputPath(); - void LogOutputPath(hstring const& value); - hstring CorrelationData(); - void CorrelationData(hstring const& value); - bool AllowHashMismatch(); - void AllowHashMismatch(bool value); - bool BypassIsStoreClientBlockedPolicyCheck(); - 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: - winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; - bool m_acceptPackageAgreements = false; - bool m_allowHashMismatch = false; - bool m_bypassIsStoreClientBlockedPolicyCheck = false; - bool m_force = false; - std::wstring m_logOutputPath = L""; - 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 - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct RepairOptions : RepairOptionsT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "RepairOptions.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_RepairOptions)] + struct RepairOptions : RepairOptionsT + { + RepairOptions(); + + winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); + void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); + winrt::Microsoft::Management::Deployment::PackageRepairScope PackageRepairScope(); + void PackageRepairScope(winrt::Microsoft::Management::Deployment::PackageRepairScope const& value); + winrt::Microsoft::Management::Deployment::PackageRepairMode PackageRepairMode(); + void PackageRepairMode(winrt::Microsoft::Management::Deployment::PackageRepairMode const& value); + bool AcceptPackageAgreements(); + void AcceptPackageAgreements(bool value); + hstring LogOutputPath(); + void LogOutputPath(hstring const& value); + hstring CorrelationData(); + void CorrelationData(hstring const& value); + bool AllowHashMismatch(); + void AllowHashMismatch(bool value); + bool BypassIsStoreClientBlockedPolicyCheck(); + 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: + winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; + bool m_acceptPackageAgreements = false; + bool m_allowHashMismatch = false; + bool m_bypassIsStoreClientBlockedPolicyCheck = false; + bool m_force = false; + std::wstring m_logOutputPath = L""; + 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 + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct RepairOptions : RepairOptionsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/RepairResult.cpp b/src/Microsoft.Management.Deployment/RepairResult.cpp index c36725528a..fa220f1f94 100644 --- a/src/Microsoft.Management.Deployment/RepairResult.cpp +++ b/src/Microsoft.Management.Deployment/RepairResult.cpp @@ -1,44 +1,44 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include "pch.h" -#include "RepairResult.h" -#include "RepairResult.g.cpp" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - void RepairResult::Initialize( - winrt::Microsoft::Management::Deployment::RepairResultStatus status, - winrt::hresult extendedErrorCode, - uint32_t repairerErrorCode, - hstring const& correlationData, - bool rebootRequired) - { - m_status = status; - m_extendedErrorCode = extendedErrorCode; - m_repairerErrorCode = repairerErrorCode; - m_correlationData = correlationData; - m_rebootRequired = rebootRequired; - } - hstring RepairResult::CorrelationData() - { - return hstring(m_correlationData); - } - bool RepairResult::RebootRequired() - { - return m_rebootRequired; - } - winrt::Microsoft::Management::Deployment::RepairResultStatus RepairResult::Status() - { - return m_status; - } - winrt::hresult RepairResult::ExtendedErrorCode() - { - return m_extendedErrorCode; - } - uint32_t RepairResult::RepairerErrorCode() - { - return m_repairerErrorCode; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" +#include "RepairResult.h" +#include "RepairResult.g.cpp" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + void RepairResult::Initialize( + winrt::Microsoft::Management::Deployment::RepairResultStatus status, + winrt::hresult extendedErrorCode, + uint32_t repairerErrorCode, + hstring const& correlationData, + bool rebootRequired) + { + m_status = status; + m_extendedErrorCode = extendedErrorCode; + m_repairerErrorCode = repairerErrorCode; + m_correlationData = correlationData; + m_rebootRequired = rebootRequired; + } + hstring RepairResult::CorrelationData() + { + return hstring(m_correlationData); + } + bool RepairResult::RebootRequired() + { + return m_rebootRequired; + } + winrt::Microsoft::Management::Deployment::RepairResultStatus RepairResult::Status() + { + return m_status; + } + winrt::hresult RepairResult::ExtendedErrorCode() + { + return m_extendedErrorCode; + } + uint32_t RepairResult::RepairerErrorCode() + { + return m_repairerErrorCode; + } +} diff --git a/src/Microsoft.Management.Deployment/RepairResult.h b/src/Microsoft.Management.Deployment/RepairResult.h index de41b23579..13ad79ffe2 100644 --- a/src/Microsoft.Management.Deployment/RepairResult.h +++ b/src/Microsoft.Management.Deployment/RepairResult.h @@ -1,36 +1,36 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "RepairResult.g.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - struct RepairResult : RepairResultT - { - RepairResult() = default; - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - void Initialize( - winrt::Microsoft::Management::Deployment::RepairResultStatus status, - winrt::hresult extendedErrorCode, - uint32_t repairerErrorCode, - hstring const& correlationData, - bool rebootRequired); -#endif - - hstring CorrelationData(); - bool RebootRequired(); - winrt::Microsoft::Management::Deployment::RepairResultStatus Status(); - winrt::hresult ExtendedErrorCode(); - uint32_t RepairerErrorCode(); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - std::wstring m_correlationData = L""; - bool m_rebootRequired = false; - winrt::Microsoft::Management::Deployment::RepairResultStatus m_status = winrt::Microsoft::Management::Deployment::RepairResultStatus::Ok; - winrt::hresult m_extendedErrorCode = S_OK; - uint32_t m_repairerErrorCode = 0; -#endif - }; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "RepairResult.g.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + struct RepairResult : RepairResultT + { + RepairResult() = default; + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + void Initialize( + winrt::Microsoft::Management::Deployment::RepairResultStatus status, + winrt::hresult extendedErrorCode, + uint32_t repairerErrorCode, + hstring const& correlationData, + bool rebootRequired); +#endif + + hstring CorrelationData(); + bool RebootRequired(); + winrt::Microsoft::Management::Deployment::RepairResultStatus Status(); + winrt::hresult ExtendedErrorCode(); + uint32_t RepairerErrorCode(); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + std::wstring m_correlationData = L""; + bool m_rebootRequired = false; + winrt::Microsoft::Management::Deployment::RepairResultStatus m_status = winrt::Microsoft::Management::Deployment::RepairResultStatus::Ok; + winrt::hresult m_extendedErrorCode = S_OK; + uint32_t m_repairerErrorCode = 0; +#endif + }; +} diff --git a/src/Microsoft.Management.Deployment/UninstallOptions.cpp b/src/Microsoft.Management.Deployment/UninstallOptions.cpp index b1b7f5621e..d6b59fee7d 100644 --- a/src/Microsoft.Management.Deployment/UninstallOptions.cpp +++ b/src/Microsoft.Management.Deployment/UninstallOptions.cpp @@ -1,70 +1,70 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#pragma warning( push ) -#pragma warning ( disable : 4467 6388) -// 6388 Allow CreateInstance. -#include -// 4467 Allow use of uuid attribute for com object creation. -#include "UninstallOptions.h" -#pragma warning( pop ) -#include "UninstallOptions.g.cpp" -#include "Converters.h" -#include "Helpers.h" - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - UninstallOptions::UninstallOptions() - { - } - winrt::Microsoft::Management::Deployment::PackageVersionId UninstallOptions::PackageVersionId() - { - return m_packageVersionId; - } - void UninstallOptions::PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value) - { - m_packageVersionId = value; - } - winrt::Microsoft::Management::Deployment::PackageUninstallMode UninstallOptions::PackageUninstallMode() - { - return m_packageUninstallMode; - } - void UninstallOptions::PackageUninstallMode(winrt::Microsoft::Management::Deployment::PackageUninstallMode const& value) - { - m_packageUninstallMode = value; - } - hstring UninstallOptions::LogOutputPath() - { - return hstring(m_logOutputPath); - } - void UninstallOptions::LogOutputPath(hstring const& value) - { - m_logOutputPath = value; - } - hstring UninstallOptions::CorrelationData() - { - return hstring(m_correlationData); - } - void UninstallOptions::CorrelationData(hstring const& value) - { - m_correlationData = value; - } - bool UninstallOptions::Force() - { - return m_force; - } - void UninstallOptions::Force(bool value) - { - m_force = value; - } - winrt::Microsoft::Management::Deployment::PackageUninstallScope UninstallOptions::PackageUninstallScope() - { - return m_packageUninstallScope; - } - void UninstallOptions::PackageUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope const& value) - { - m_packageUninstallScope = value; - } - - CoCreatableMicrosoftManagementDeploymentClass(UninstallOptions); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#pragma warning( push ) +#pragma warning ( disable : 4467 6388) +// 6388 Allow CreateInstance. +#include +// 4467 Allow use of uuid attribute for com object creation. +#include "UninstallOptions.h" +#pragma warning( pop ) +#include "UninstallOptions.g.cpp" +#include "Converters.h" +#include "Helpers.h" + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + UninstallOptions::UninstallOptions() + { + } + winrt::Microsoft::Management::Deployment::PackageVersionId UninstallOptions::PackageVersionId() + { + return m_packageVersionId; + } + void UninstallOptions::PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value) + { + m_packageVersionId = value; + } + winrt::Microsoft::Management::Deployment::PackageUninstallMode UninstallOptions::PackageUninstallMode() + { + return m_packageUninstallMode; + } + void UninstallOptions::PackageUninstallMode(winrt::Microsoft::Management::Deployment::PackageUninstallMode const& value) + { + m_packageUninstallMode = value; + } + hstring UninstallOptions::LogOutputPath() + { + return hstring(m_logOutputPath); + } + void UninstallOptions::LogOutputPath(hstring const& value) + { + m_logOutputPath = value; + } + hstring UninstallOptions::CorrelationData() + { + return hstring(m_correlationData); + } + void UninstallOptions::CorrelationData(hstring const& value) + { + m_correlationData = value; + } + bool UninstallOptions::Force() + { + return m_force; + } + void UninstallOptions::Force(bool value) + { + m_force = value; + } + winrt::Microsoft::Management::Deployment::PackageUninstallScope UninstallOptions::PackageUninstallScope() + { + return m_packageUninstallScope; + } + void UninstallOptions::PackageUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope const& value) + { + m_packageUninstallScope = value; + } + + CoCreatableMicrosoftManagementDeploymentClass(UninstallOptions); +} diff --git a/src/Microsoft.Management.Deployment/UninstallOptions.h b/src/Microsoft.Management.Deployment/UninstallOptions.h index f505dcedbd..946a0cd04c 100644 --- a/src/Microsoft.Management.Deployment/UninstallOptions.h +++ b/src/Microsoft.Management.Deployment/UninstallOptions.h @@ -1,47 +1,47 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include "UninstallOptions.g.h" -#include "Public/ComClsids.h" -#include - -namespace winrt::Microsoft::Management::Deployment::implementation -{ - [uuid(WINGET_OUTOFPROC_COM_CLSID_UninstallOptions)] - struct UninstallOptions : UninstallOptionsT - { - UninstallOptions(); - - winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); - void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); - winrt::Microsoft::Management::Deployment::PackageUninstallMode PackageUninstallMode(); - void PackageUninstallMode(winrt::Microsoft::Management::Deployment::PackageUninstallMode const& value); - hstring LogOutputPath(); - void LogOutputPath(hstring const& value); - hstring CorrelationData(); - void CorrelationData(hstring const& value); - bool Force(); - void Force(bool value); - winrt::Microsoft::Management::Deployment::PackageUninstallScope PackageUninstallScope(); - void PackageUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope const& value); - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) - private: - winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; - winrt::Microsoft::Management::Deployment::PackageUninstallMode m_packageUninstallMode = winrt::Microsoft::Management::Deployment::PackageUninstallMode::Default; - std::wstring m_logOutputPath = L""; - std::wstring m_correlationData = L""; - bool m_force = false; - winrt::Microsoft::Management::Deployment::PackageUninstallScope m_packageUninstallScope = winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any; -#endif - }; -} - -#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) -namespace winrt::Microsoft::Management::Deployment::factory_implementation -{ - struct UninstallOptions : UninstallOptionsT, AppInstaller::WinRT::ModuleCountBase - { - }; -} -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include "UninstallOptions.g.h" +#include "Public/ComClsids.h" +#include + +namespace winrt::Microsoft::Management::Deployment::implementation +{ + [uuid(WINGET_OUTOFPROC_COM_CLSID_UninstallOptions)] + struct UninstallOptions : UninstallOptionsT + { + UninstallOptions(); + + winrt::Microsoft::Management::Deployment::PackageVersionId PackageVersionId(); + void PackageVersionId(winrt::Microsoft::Management::Deployment::PackageVersionId const& value); + winrt::Microsoft::Management::Deployment::PackageUninstallMode PackageUninstallMode(); + void PackageUninstallMode(winrt::Microsoft::Management::Deployment::PackageUninstallMode const& value); + hstring LogOutputPath(); + void LogOutputPath(hstring const& value); + hstring CorrelationData(); + void CorrelationData(hstring const& value); + bool Force(); + void Force(bool value); + winrt::Microsoft::Management::Deployment::PackageUninstallScope PackageUninstallScope(); + void PackageUninstallScope(winrt::Microsoft::Management::Deployment::PackageUninstallScope const& value); + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) + private: + winrt::Microsoft::Management::Deployment::PackageVersionId m_packageVersionId{ nullptr }; + winrt::Microsoft::Management::Deployment::PackageUninstallMode m_packageUninstallMode = winrt::Microsoft::Management::Deployment::PackageUninstallMode::Default; + std::wstring m_logOutputPath = L""; + std::wstring m_correlationData = L""; + bool m_force = false; + winrt::Microsoft::Management::Deployment::PackageUninstallScope m_packageUninstallScope = winrt::Microsoft::Management::Deployment::PackageUninstallScope::Any; +#endif + }; +} + +#if !defined(INCLUDE_ONLY_INTERFACE_METHODS) +namespace winrt::Microsoft::Management::Deployment::factory_implementation +{ + struct UninstallOptions : UninstallOptionsT, AppInstaller::WinRT::ModuleCountBase + { + }; +} +#endif diff --git a/src/Microsoft.Management.Deployment/packages.config b/src/Microsoft.Management.Deployment/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/Microsoft.Management.Deployment/packages.config +++ b/src/Microsoft.Management.Deployment/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/Microsoft.Management.Deployment/pch.cpp b/src/Microsoft.Management.Deployment/pch.cpp index 147dc1b6e0..e4b1bd6915 100644 --- a/src/Microsoft.Management.Deployment/pch.cpp +++ b/src/Microsoft.Management.Deployment/pch.cpp @@ -1,3 +1,3 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" diff --git a/src/Microsoft.Management.Deployment/pch.h b/src/Microsoft.Management.Deployment/pch.h index b26798bcc1..a1fd8a4ecf 100644 --- a/src/Microsoft.Management.Deployment/pch.h +++ b/src/Microsoft.Management.Deployment/pch.h @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include diff --git a/src/PowerShell/CommonFiles/PowerShellCmdlet.cs b/src/PowerShell/CommonFiles/PowerShellCmdlet.cs index 1006e2cb58..038dd90091 100644 --- a/src/PowerShell/CommonFiles/PowerShellCmdlet.cs +++ b/src/PowerShell/CommonFiles/PowerShellCmdlet.cs @@ -1,632 +1,632 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Common.Command -{ - using System; - using System.Collections.Concurrent; - using System.Collections.Generic; - using System.Management.Automation; - using System.Runtime.ExceptionServices; - using System.Threading; - using System.Threading.Tasks; - using Microsoft.WinGet.Resources; - using Microsoft.WinGet.SharedLib.Exceptions; - using Microsoft.WinGet.SharedLib.PolicySettings; - - /// - /// This must be the base class for every cmdlet for winget PowerShell modules. - /// It supports: - /// - Async operations. - /// - Execute on an MTA. If the thread is already running on an MTA it will executed it, otherwise - /// it will create a new MTA thread. - /// Wait must be used to synchronously wait con the task. - /// - public abstract class PowerShellCmdlet - { - private const string Debug = "Debug"; - private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; - - private readonly PSCmdlet psCmdlet; - private readonly Thread pwshThread; - - private readonly CancellationTokenSource source = new (); - private readonly SemaphoreSlim semaphore = new (1, 1); - private readonly ManualResetEventSlim pwshThreadActionReady = new (false); - private readonly ManualResetEventSlim pwshThreadActionCompleted = new (false); - - private BlockingCollection queuedStreams = new (); - private int progressActivityId = 0; - private ConcurrentDictionary progressRecords = new (); - private Action? pwshThreadAction = null; - private ExceptionDispatchInfo? pwshThreadEdi = null; - - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - /// Policies. - public PowerShellCmdlet(PSCmdlet psCmdlet, HashSet policies) - { - // Passing Debug will make all the message actions to be Inquire. For async operations - // and the current queue message implementation this doesn't make sense. - // PowerShell will inquire for any message giving the impression that the task is - // paused, but the async operation is still running. - if (psCmdlet.MyInvocation.BoundParameters.ContainsKey(Debug)) - { - throw new NotSupportedException(Resources.DebugNotSupported); - } - - this.ValidatePolicies(policies); - - this.psCmdlet = psCmdlet; - this.pwshThread = Thread.CurrentThread; - } - - /// - /// Request cancellation for this command. - /// - public void Cancel() - { - this.source.Cancel(); - } - - /// - /// Execute the delegate in a MTA thread. - /// Caller must wait on task. - /// - /// Function to execute. - /// A representing the asynchronous operation. - internal Task RunOnMTA(Func func) - { - // .NET 4.8 doesn't support TaskCompletionSource. -#if POWERSHELL_WINDOWS - throw new NotImplementedException(); -#else - // This must be called in the main thread. - if (this.pwshThread != Thread.CurrentThread) - { - throw new InvalidOperationException(); - } - - if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) - { - this.Write(StreamType.Verbose, "Already running on MTA"); - try - { - Task result = func(); - result.ContinueWith((task) => this.Complete(), TaskContinuationOptions.ExecuteSynchronously); - return result; - } - catch - { - this.Complete(); - throw; - } - } - - this.Write(StreamType.Verbose, "Creating MTA thread"); - var tcs = new TaskCompletionSource(); - var thread = new Thread(() => - { - try - { - func().GetAwaiter().GetResult(); - tcs.SetResult(); - } - catch (Exception e) - { - tcs.SetException(e); - } - finally - { - this.Complete(); - } - }); - - thread.SetApartmentState(ApartmentState.MTA); - thread.Start(); - return tcs.Task; -#endif - } - - /// - /// Execute the delegate in a MTA thread. - /// Caller must wait on task. - /// - /// Function to execute. - /// Return type of function. - /// A representing the asynchronous operation. - internal Task RunOnMTA(Func> func) - { - // This must be called in the main thread. - if (this.pwshThread != Thread.CurrentThread) - { - throw new InvalidOperationException(); - } - - if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) - { - this.Write(StreamType.Verbose, "Already running on MTA"); - try - { - Task result = func(); - result.ContinueWith((task) => this.Complete(), TaskContinuationOptions.ExecuteSynchronously); - return result; - } - catch - { - this.Complete(); - throw; - } - } - - this.Write(StreamType.Verbose, "Creating MTA thread"); - var tcs = new TaskCompletionSource(); - var thread = new Thread(() => - { - try - { - var result = func().GetAwaiter().GetResult(); - tcs.SetResult(result); - } - catch (Exception e) - { - tcs.SetException(e); - } - finally - { - this.Complete(); - } - }); - - thread.SetApartmentState(ApartmentState.MTA); - thread.Start(); - return tcs.Task; - } - - /// - /// Execute the delegate in a MTA thread. - /// Synchronous call. - /// - /// Function to execute. - /// Return type of function. - /// A representing the asynchronous operation. - internal TResult RunOnMTA(Func func) - { - // This must be called in the main thread. - if (this.pwshThread != Thread.CurrentThread) - { - throw new InvalidOperationException(); - } - - if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) - { - this.Write(StreamType.Verbose, "Already running on MTA"); - try - { - return func(); - } - finally - { - this.Complete(); - } - } - - this.Write(StreamType.Verbose, "Creating MTA thread"); - var tcs = new TaskCompletionSource(); - var thread = new Thread(() => - { - try - { - var result = func(); - tcs.SetResult(result); - } - catch (Exception e) - { - tcs.SetException(e); - } - finally - { - this.Complete(); - } - }); - - thread.SetApartmentState(ApartmentState.MTA); - thread.Start(); - this.Wait(tcs.Task); - return tcs.Task.Result; - } - - /// - /// Executes an action in the main thread. - /// Blocks until call is executed. - /// - /// Action to perform. - internal void ExecuteInPowerShellThread(Action action) - { - if (this.pwshThread == Thread.CurrentThread) - { - action(); - return; - } - - this.WaitForOurTurn(); - - this.pwshThreadAction = action; - this.pwshThreadActionReady.Set(); - this.WaitMainThreadActionCompletion(); - } - - /// - /// Waits for the task to be completed. This MUST be called from the main thread. - /// - /// Task to wait for. - /// The cmdlet that can write to PowerShell. - internal void Wait(Task runningTask, PowerShellCmdlet? writeCmdlet = null) - { - writeCmdlet ??= this; - - // This must be called in the main thread. - if (this.pwshThread != Thread.CurrentThread) - { - throw new InvalidOperationException(); - } - - do - { - if (this.pwshThreadActionReady.IsSet) - { - // Someone needs the main thread. - this.pwshThreadActionReady.Reset(); - - if (this.pwshThreadAction != null) - { - try - { - this.pwshThreadEdi = null; - this.pwshThreadAction(); - } - catch (Exception e) - { - // Make sure we don't throw in the PowerShell thread, this way - // we'll get a more meaningful stack by Get-Error. - this.pwshThreadEdi = ExceptionDispatchInfo.Capture(e); - } - - this.pwshThreadAction = null; - } - - // Done. - this.pwshThreadActionCompleted.Set(); - } - - // Take from the blocking collection. - if (!this.queuedStreams.IsCompleted && this.queuedStreams.Count > 0) - { - try - { - var queuedOutput = this.queuedStreams.Take(); - if (queuedOutput != null) - { - this.CmdletWrite(queuedOutput.Type, queuedOutput.Data, writeCmdlet); - } - } - catch (InvalidOperationException) - { - // An InvalidOperationException means that Take() was called on a completed collection. - } - } - } - while (!(runningTask.IsCompleted && this.queuedStreams.IsCompleted)); - - if (runningTask.IsFaulted) - { - // If IsFaulted is true, the task's Status is equal to Faulted, - // and its Exception property will be non-null. - AggregateException? ae = runningTask.Exception! as AggregateException; - if (ae != null && ae.InnerExceptions.Count == 1) - { - ExceptionDispatchInfo.Capture(ae.InnerExceptions[0]).Throw(); - } - - throw runningTask.Exception!; - } - } - - /// - /// Writes into the corresponding stream if running on the main thread. - /// Otherwise queue the message. - /// Is the caller responsibility to use the correct types. - /// - /// Stream type. - /// Data. - internal void Write(StreamType type, object data) - { - if (type == StreamType.Progress) - { - ProgressRecord progressRecord = (ProgressRecord)data; - if (progressRecord.RecordType == ProgressRecordType.Completed) - { - throw new NotSupportedException("Use CompleteProgress"); - } - - // Keep track of all progress activity. - _ = this.progressRecords.TryAdd(progressRecord.ActivityId, progressRecord.RecordType); - } - - if (this.pwshThread == Thread.CurrentThread) - { - this.CmdletWrite(type, data, this); - return; - } - - this.queuedStreams.Add(new QueuedStream(type, data)); - } - - /// - /// Helper to compute percentage and write progress for processing activities. - /// - /// Activity id. - /// The activity in progress. - /// The status of the activity. - /// Number of completed actions. - /// The expected total. - internal void WriteProgressWithPercentage(int activityId, string activity, string status, int completed, int total) - { - double percentComplete = (double)completed / total; - var record = new ProgressRecord(activityId, activity, status) - { - RecordType = ProgressRecordType.Processing, - PercentComplete = (int)(100.0 * percentComplete), - }; - this.Write(StreamType.Progress, record); - } - - /// - /// Helper to complete progress records. - /// - /// Activity id. - /// The activity in progress. - /// The status of the activity. - /// Force write complete progress. - internal void CompleteProgress(int activityId, string activity, string status, bool force = false) - { - var record = new ProgressRecord(activityId, activity, status) - { - RecordType = ProgressRecordType.Completed, - PercentComplete = 100, - }; - - if (!this.progressRecords.TryAdd(activityId, record.RecordType)) - { - _ = this.progressRecords.TryUpdate(activityId, record.RecordType, ProgressRecordType.Processing); - } - - if (this.pwshThread == Thread.CurrentThread) - { - this.CmdletWrite(StreamType.Progress, record, this); - } - else - { - // You should only use force if you know the cmdlet that is completing this progress is a sync cmdlet that - // is running in an async context. A sync cmdlet is anything that doesn't start with Start-* - if (force) - { - this.ExecuteInPowerShellThread(() => this.CmdletWrite(StreamType.Progress, record, this)); - } - else - { - this.queuedStreams.Add(new QueuedStream(StreamType.Progress, record)); - } - } - } - - /// - /// Writes to PowerShell streams. - /// This method must be called in the original thread. - /// WARNING: You must only call this when the task is completed. - /// - /// The cmdlet that can write to PowerShell. - internal void ConsumeAndWriteStreams(PowerShellCmdlet writeCmdlet) - { - // This must be called in the main thread. - if (this.pwshThread != Thread.CurrentThread) - { - throw new InvalidOperationException(); - } - - // Take from the blocking collection until is completed. - try - { - while (true) - { - var queuedOutput = this.queuedStreams.Take(); - if (queuedOutput != null) - { - this.CmdletWrite(queuedOutput.Type, queuedOutput.Data, writeCmdlet); - } - } - } - catch (InvalidOperationException) - { - // We are done. - // An InvalidOperationException means that Take() was called on a completed collection. - } - } - - /// - /// Gets a new progress activity id. - /// - /// The new progress record id. - internal int GetNewProgressActivityId() - { - return Interlocked.Increment(ref this.progressActivityId); - } - - /// - /// Gets the cancellation token. - /// - /// CancellationToken. - internal CancellationToken GetCancellationToken() - { - return this.source.Token; - } - - /// - /// Gets the current file system location from the cmdlet. - /// - /// Path. - internal string GetCurrentFileSystemLocation() - { - return this.psCmdlet.SessionState.Path.CurrentFileSystemLocation.Path; - } - - /// - /// Sets a variable. - /// - /// Variable name. - /// Value. - internal void SetVariable(string variableName, object value) - { - this.psCmdlet.SessionState.PSVariable.Set(variableName, value); - } - - /// - /// Prompts the user if it should continue processing if possible. - /// - /// Message. - /// If the operation should continue. - internal bool ShouldProcess(string target) - { - // If not on the main thread just continue. - if (this.pwshThread != Thread.CurrentThread) - { - return true; - } - - return this.psCmdlet.ShouldProcess(target); - } - - private void Complete() - { - this.queuedStreams.CompleteAdding(); - } - - private void CmdletWrite(StreamType streamType, object data, PowerShellCmdlet writeCmdlet) - { - switch (streamType) - { - case StreamType.Debug: - throw new NotSupportedException(); - case StreamType.Verbose: - writeCmdlet.psCmdlet.WriteVerbose((string)data); - break; - case StreamType.Warning: - writeCmdlet.psCmdlet.WriteWarning((string)data); - break; - case StreamType.Error: - writeCmdlet.psCmdlet.WriteError((ErrorRecord)data); - break; - case StreamType.Progress: - // If the activity is already completed don't write progress. - var progressRecord = (ProgressRecord)data; - if (this.progressRecords[progressRecord.ActivityId] == progressRecord.RecordType) - { - writeCmdlet.psCmdlet.WriteProgress(progressRecord); - } - - break; - case StreamType.Object: - writeCmdlet.psCmdlet.WriteObject(data); - break; - case StreamType.Information: - writeCmdlet.psCmdlet.WriteInformation(data, WriteInformationTags); - break; - } - } - - private void ValidatePolicies(HashSet policies) - { - GroupPolicy groupPolicy = GroupPolicy.GetInstance(); - - if (policies.Contains(Policy.WinGet)) - { - if (!groupPolicy.IsEnabled(Policy.WinGet)) - { - throw new GroupPolicyException(Policy.WinGet, GroupPolicyFailureType.BlockedByPolicy); - } - - policies.Remove(Policy.WinGet); - } - - if (policies.Contains(Policy.Configuration)) - { - if (!groupPolicy.IsEnabled(Policy.Configuration)) - { - throw new GroupPolicyException(Policy.Configuration, GroupPolicyFailureType.BlockedByPolicy); - } - - policies.Remove(Policy.Configuration); - } - - if (policies.Contains(Policy.WinGetCommandLineInterfaces)) - { - if (!groupPolicy.IsEnabled(Policy.WinGetCommandLineInterfaces)) - { - throw new GroupPolicyException(Policy.WinGetCommandLineInterfaces, GroupPolicyFailureType.BlockedByPolicy); - } - - policies.Remove(Policy.WinGetCommandLineInterfaces); - } - - if (policies.Count > 0) - { - throw new NotSupportedException($"Invalid policies {string.Join(",", policies)}"); - } - } - - private void WaitForOurTurn() - { - this.semaphore.Wait(this.GetCancellationToken()); - this.pwshThreadActionCompleted.Reset(); - } - - private void WaitMainThreadActionCompletion() - { - WaitHandle.WaitAny(new[] - { - this.GetCancellationToken().WaitHandle, - this.pwshThreadActionCompleted.WaitHandle, - }); - - try - { - if (this.pwshThreadEdi != null) - { - this.pwshThreadEdi.Throw(); - } - } - finally - { - this.semaphore.Release(); - } - } - - private class QueuedStream - { - public QueuedStream(StreamType type, object data) - { - this.Type = type; - this.Data = data; - } - - public StreamType Type { get; } - - public object Data { get; } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Common.Command +{ + using System; + using System.Collections.Concurrent; + using System.Collections.Generic; + using System.Management.Automation; + using System.Runtime.ExceptionServices; + using System.Threading; + using System.Threading.Tasks; + using Microsoft.WinGet.Resources; + using Microsoft.WinGet.SharedLib.Exceptions; + using Microsoft.WinGet.SharedLib.PolicySettings; + + /// + /// This must be the base class for every cmdlet for winget PowerShell modules. + /// It supports: + /// - Async operations. + /// - Execute on an MTA. If the thread is already running on an MTA it will executed it, otherwise + /// it will create a new MTA thread. + /// Wait must be used to synchronously wait con the task. + /// + public abstract class PowerShellCmdlet + { + private const string Debug = "Debug"; + private static readonly string[] WriteInformationTags = new string[] { "PSHOST" }; + + private readonly PSCmdlet psCmdlet; + private readonly Thread pwshThread; + + private readonly CancellationTokenSource source = new (); + private readonly SemaphoreSlim semaphore = new (1, 1); + private readonly ManualResetEventSlim pwshThreadActionReady = new (false); + private readonly ManualResetEventSlim pwshThreadActionCompleted = new (false); + + private BlockingCollection queuedStreams = new (); + private int progressActivityId = 0; + private ConcurrentDictionary progressRecords = new (); + private Action? pwshThreadAction = null; + private ExceptionDispatchInfo? pwshThreadEdi = null; + + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + /// Policies. + public PowerShellCmdlet(PSCmdlet psCmdlet, HashSet policies) + { + // Passing Debug will make all the message actions to be Inquire. For async operations + // and the current queue message implementation this doesn't make sense. + // PowerShell will inquire for any message giving the impression that the task is + // paused, but the async operation is still running. + if (psCmdlet.MyInvocation.BoundParameters.ContainsKey(Debug)) + { + throw new NotSupportedException(Resources.DebugNotSupported); + } + + this.ValidatePolicies(policies); + + this.psCmdlet = psCmdlet; + this.pwshThread = Thread.CurrentThread; + } + + /// + /// Request cancellation for this command. + /// + public void Cancel() + { + this.source.Cancel(); + } + + /// + /// Execute the delegate in a MTA thread. + /// Caller must wait on task. + /// + /// Function to execute. + /// A representing the asynchronous operation. + internal Task RunOnMTA(Func func) + { + // .NET 4.8 doesn't support TaskCompletionSource. +#if POWERSHELL_WINDOWS + throw new NotImplementedException(); +#else + // This must be called in the main thread. + if (this.pwshThread != Thread.CurrentThread) + { + throw new InvalidOperationException(); + } + + if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) + { + this.Write(StreamType.Verbose, "Already running on MTA"); + try + { + Task result = func(); + result.ContinueWith((task) => this.Complete(), TaskContinuationOptions.ExecuteSynchronously); + return result; + } + catch + { + this.Complete(); + throw; + } + } + + this.Write(StreamType.Verbose, "Creating MTA thread"); + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + func().GetAwaiter().GetResult(); + tcs.SetResult(); + } + catch (Exception e) + { + tcs.SetException(e); + } + finally + { + this.Complete(); + } + }); + + thread.SetApartmentState(ApartmentState.MTA); + thread.Start(); + return tcs.Task; +#endif + } + + /// + /// Execute the delegate in a MTA thread. + /// Caller must wait on task. + /// + /// Function to execute. + /// Return type of function. + /// A representing the asynchronous operation. + internal Task RunOnMTA(Func> func) + { + // This must be called in the main thread. + if (this.pwshThread != Thread.CurrentThread) + { + throw new InvalidOperationException(); + } + + if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) + { + this.Write(StreamType.Verbose, "Already running on MTA"); + try + { + Task result = func(); + result.ContinueWith((task) => this.Complete(), TaskContinuationOptions.ExecuteSynchronously); + return result; + } + catch + { + this.Complete(); + throw; + } + } + + this.Write(StreamType.Verbose, "Creating MTA thread"); + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + var result = func().GetAwaiter().GetResult(); + tcs.SetResult(result); + } + catch (Exception e) + { + tcs.SetException(e); + } + finally + { + this.Complete(); + } + }); + + thread.SetApartmentState(ApartmentState.MTA); + thread.Start(); + return tcs.Task; + } + + /// + /// Execute the delegate in a MTA thread. + /// Synchronous call. + /// + /// Function to execute. + /// Return type of function. + /// A representing the asynchronous operation. + internal TResult RunOnMTA(Func func) + { + // This must be called in the main thread. + if (this.pwshThread != Thread.CurrentThread) + { + throw new InvalidOperationException(); + } + + if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) + { + this.Write(StreamType.Verbose, "Already running on MTA"); + try + { + return func(); + } + finally + { + this.Complete(); + } + } + + this.Write(StreamType.Verbose, "Creating MTA thread"); + var tcs = new TaskCompletionSource(); + var thread = new Thread(() => + { + try + { + var result = func(); + tcs.SetResult(result); + } + catch (Exception e) + { + tcs.SetException(e); + } + finally + { + this.Complete(); + } + }); + + thread.SetApartmentState(ApartmentState.MTA); + thread.Start(); + this.Wait(tcs.Task); + return tcs.Task.Result; + } + + /// + /// Executes an action in the main thread. + /// Blocks until call is executed. + /// + /// Action to perform. + internal void ExecuteInPowerShellThread(Action action) + { + if (this.pwshThread == Thread.CurrentThread) + { + action(); + return; + } + + this.WaitForOurTurn(); + + this.pwshThreadAction = action; + this.pwshThreadActionReady.Set(); + this.WaitMainThreadActionCompletion(); + } + + /// + /// Waits for the task to be completed. This MUST be called from the main thread. + /// + /// Task to wait for. + /// The cmdlet that can write to PowerShell. + internal void Wait(Task runningTask, PowerShellCmdlet? writeCmdlet = null) + { + writeCmdlet ??= this; + + // This must be called in the main thread. + if (this.pwshThread != Thread.CurrentThread) + { + throw new InvalidOperationException(); + } + + do + { + if (this.pwshThreadActionReady.IsSet) + { + // Someone needs the main thread. + this.pwshThreadActionReady.Reset(); + + if (this.pwshThreadAction != null) + { + try + { + this.pwshThreadEdi = null; + this.pwshThreadAction(); + } + catch (Exception e) + { + // Make sure we don't throw in the PowerShell thread, this way + // we'll get a more meaningful stack by Get-Error. + this.pwshThreadEdi = ExceptionDispatchInfo.Capture(e); + } + + this.pwshThreadAction = null; + } + + // Done. + this.pwshThreadActionCompleted.Set(); + } + + // Take from the blocking collection. + if (!this.queuedStreams.IsCompleted && this.queuedStreams.Count > 0) + { + try + { + var queuedOutput = this.queuedStreams.Take(); + if (queuedOutput != null) + { + this.CmdletWrite(queuedOutput.Type, queuedOutput.Data, writeCmdlet); + } + } + catch (InvalidOperationException) + { + // An InvalidOperationException means that Take() was called on a completed collection. + } + } + } + while (!(runningTask.IsCompleted && this.queuedStreams.IsCompleted)); + + if (runningTask.IsFaulted) + { + // If IsFaulted is true, the task's Status is equal to Faulted, + // and its Exception property will be non-null. + AggregateException? ae = runningTask.Exception! as AggregateException; + if (ae != null && ae.InnerExceptions.Count == 1) + { + ExceptionDispatchInfo.Capture(ae.InnerExceptions[0]).Throw(); + } + + throw runningTask.Exception!; + } + } + + /// + /// Writes into the corresponding stream if running on the main thread. + /// Otherwise queue the message. + /// Is the caller responsibility to use the correct types. + /// + /// Stream type. + /// Data. + internal void Write(StreamType type, object data) + { + if (type == StreamType.Progress) + { + ProgressRecord progressRecord = (ProgressRecord)data; + if (progressRecord.RecordType == ProgressRecordType.Completed) + { + throw new NotSupportedException("Use CompleteProgress"); + } + + // Keep track of all progress activity. + _ = this.progressRecords.TryAdd(progressRecord.ActivityId, progressRecord.RecordType); + } + + if (this.pwshThread == Thread.CurrentThread) + { + this.CmdletWrite(type, data, this); + return; + } + + this.queuedStreams.Add(new QueuedStream(type, data)); + } + + /// + /// Helper to compute percentage and write progress for processing activities. + /// + /// Activity id. + /// The activity in progress. + /// The status of the activity. + /// Number of completed actions. + /// The expected total. + internal void WriteProgressWithPercentage(int activityId, string activity, string status, int completed, int total) + { + double percentComplete = (double)completed / total; + var record = new ProgressRecord(activityId, activity, status) + { + RecordType = ProgressRecordType.Processing, + PercentComplete = (int)(100.0 * percentComplete), + }; + this.Write(StreamType.Progress, record); + } + + /// + /// Helper to complete progress records. + /// + /// Activity id. + /// The activity in progress. + /// The status of the activity. + /// Force write complete progress. + internal void CompleteProgress(int activityId, string activity, string status, bool force = false) + { + var record = new ProgressRecord(activityId, activity, status) + { + RecordType = ProgressRecordType.Completed, + PercentComplete = 100, + }; + + if (!this.progressRecords.TryAdd(activityId, record.RecordType)) + { + _ = this.progressRecords.TryUpdate(activityId, record.RecordType, ProgressRecordType.Processing); + } + + if (this.pwshThread == Thread.CurrentThread) + { + this.CmdletWrite(StreamType.Progress, record, this); + } + else + { + // You should only use force if you know the cmdlet that is completing this progress is a sync cmdlet that + // is running in an async context. A sync cmdlet is anything that doesn't start with Start-* + if (force) + { + this.ExecuteInPowerShellThread(() => this.CmdletWrite(StreamType.Progress, record, this)); + } + else + { + this.queuedStreams.Add(new QueuedStream(StreamType.Progress, record)); + } + } + } + + /// + /// Writes to PowerShell streams. + /// This method must be called in the original thread. + /// WARNING: You must only call this when the task is completed. + /// + /// The cmdlet that can write to PowerShell. + internal void ConsumeAndWriteStreams(PowerShellCmdlet writeCmdlet) + { + // This must be called in the main thread. + if (this.pwshThread != Thread.CurrentThread) + { + throw new InvalidOperationException(); + } + + // Take from the blocking collection until is completed. + try + { + while (true) + { + var queuedOutput = this.queuedStreams.Take(); + if (queuedOutput != null) + { + this.CmdletWrite(queuedOutput.Type, queuedOutput.Data, writeCmdlet); + } + } + } + catch (InvalidOperationException) + { + // We are done. + // An InvalidOperationException means that Take() was called on a completed collection. + } + } + + /// + /// Gets a new progress activity id. + /// + /// The new progress record id. + internal int GetNewProgressActivityId() + { + return Interlocked.Increment(ref this.progressActivityId); + } + + /// + /// Gets the cancellation token. + /// + /// CancellationToken. + internal CancellationToken GetCancellationToken() + { + return this.source.Token; + } + + /// + /// Gets the current file system location from the cmdlet. + /// + /// Path. + internal string GetCurrentFileSystemLocation() + { + return this.psCmdlet.SessionState.Path.CurrentFileSystemLocation.Path; + } + + /// + /// Sets a variable. + /// + /// Variable name. + /// Value. + internal void SetVariable(string variableName, object value) + { + this.psCmdlet.SessionState.PSVariable.Set(variableName, value); + } + + /// + /// Prompts the user if it should continue processing if possible. + /// + /// Message. + /// If the operation should continue. + internal bool ShouldProcess(string target) + { + // If not on the main thread just continue. + if (this.pwshThread != Thread.CurrentThread) + { + return true; + } + + return this.psCmdlet.ShouldProcess(target); + } + + private void Complete() + { + this.queuedStreams.CompleteAdding(); + } + + private void CmdletWrite(StreamType streamType, object data, PowerShellCmdlet writeCmdlet) + { + switch (streamType) + { + case StreamType.Debug: + throw new NotSupportedException(); + case StreamType.Verbose: + writeCmdlet.psCmdlet.WriteVerbose((string)data); + break; + case StreamType.Warning: + writeCmdlet.psCmdlet.WriteWarning((string)data); + break; + case StreamType.Error: + writeCmdlet.psCmdlet.WriteError((ErrorRecord)data); + break; + case StreamType.Progress: + // If the activity is already completed don't write progress. + var progressRecord = (ProgressRecord)data; + if (this.progressRecords[progressRecord.ActivityId] == progressRecord.RecordType) + { + writeCmdlet.psCmdlet.WriteProgress(progressRecord); + } + + break; + case StreamType.Object: + writeCmdlet.psCmdlet.WriteObject(data); + break; + case StreamType.Information: + writeCmdlet.psCmdlet.WriteInformation(data, WriteInformationTags); + break; + } + } + + private void ValidatePolicies(HashSet policies) + { + GroupPolicy groupPolicy = GroupPolicy.GetInstance(); + + if (policies.Contains(Policy.WinGet)) + { + if (!groupPolicy.IsEnabled(Policy.WinGet)) + { + throw new GroupPolicyException(Policy.WinGet, GroupPolicyFailureType.BlockedByPolicy); + } + + policies.Remove(Policy.WinGet); + } + + if (policies.Contains(Policy.Configuration)) + { + if (!groupPolicy.IsEnabled(Policy.Configuration)) + { + throw new GroupPolicyException(Policy.Configuration, GroupPolicyFailureType.BlockedByPolicy); + } + + policies.Remove(Policy.Configuration); + } + + if (policies.Contains(Policy.WinGetCommandLineInterfaces)) + { + if (!groupPolicy.IsEnabled(Policy.WinGetCommandLineInterfaces)) + { + throw new GroupPolicyException(Policy.WinGetCommandLineInterfaces, GroupPolicyFailureType.BlockedByPolicy); + } + + policies.Remove(Policy.WinGetCommandLineInterfaces); + } + + if (policies.Count > 0) + { + throw new NotSupportedException($"Invalid policies {string.Join(",", policies)}"); + } + } + + private void WaitForOurTurn() + { + this.semaphore.Wait(this.GetCancellationToken()); + this.pwshThreadActionCompleted.Reset(); + } + + private void WaitMainThreadActionCompletion() + { + WaitHandle.WaitAny(new[] + { + this.GetCancellationToken().WaitHandle, + this.pwshThreadActionCompleted.WaitHandle, + }); + + try + { + if (this.pwshThreadEdi != null) + { + this.pwshThreadEdi.Throw(); + } + } + finally + { + this.semaphore.Release(); + } + } + + private class QueuedStream + { + public QueuedStream(StreamType type, object data) + { + this.Type = type; + this.Data = data; + } + + public StreamType Type { get; } + + public object Data { get; } + } + } +} diff --git a/src/PowerShell/CommonFiles/StreamType.cs b/src/PowerShell/CommonFiles/StreamType.cs index 251f93c5fa..b0d4dc6c79 100644 --- a/src/PowerShell/CommonFiles/StreamType.cs +++ b/src/PowerShell/CommonFiles/StreamType.cs @@ -1,49 +1,49 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Common.Command -{ - /// - /// The write stream type of the cmdlet. - /// - public enum StreamType - { - /// - /// Debug. - /// - Debug, - - /// - /// Verbose. - /// - Verbose, - - /// - /// Warning. - /// - Warning, - - /// - /// Error. - /// - Error, - - /// - /// Progress. - /// - Progress, - - /// - /// Object. - /// - Object, - - /// - /// Information. - /// - Information, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Common.Command +{ + /// + /// The write stream type of the cmdlet. + /// + public enum StreamType + { + /// + /// Debug. + /// + Debug, + + /// + /// Verbose. + /// + Verbose, + + /// + /// Warning. + /// + Warning, + + /// + /// Error. + /// + Error, + + /// + /// Progress. + /// + Progress, + + /// + /// Object. + /// + Object, + + /// + /// Information. + /// + Information, + } +} diff --git a/src/PowerShell/CommonFiles/WinGetAssemblyLoadContext.cs b/src/PowerShell/CommonFiles/WinGetAssemblyLoadContext.cs index 38b1f7c431..98e326b0df 100644 --- a/src/PowerShell/CommonFiles/WinGetAssemblyLoadContext.cs +++ b/src/PowerShell/CommonFiles/WinGetAssemblyLoadContext.cs @@ -1,138 +1,138 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- -#if !POWERSHELL_WINDOWS -namespace Microsoft.WinGet.Resolver -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using System.Runtime.InteropServices; - using System.Runtime.Loader; - - /// - /// Custom assembly load context for this module. - /// This helps us load our dependencies without carrying about apps importing this module. - /// All dependencies except the Engine dll needs to be under a Dependencies directory. - /// - internal class WinGetAssemblyLoadContext : AssemblyLoadContext - { - // The assemblies must be loaded in the default context. - // Loading WinRT.Runtime.dll in an ALC when is already loaded in the default context - // will result on 'Attempt to update previously set global instance.' - private static readonly IEnumerable DefaultContextAssemblies = new string[] - { - @"WinRT.Runtime.dll", - }; - - private static readonly string SharedDependencyPath; - private static readonly string SharedArchDependencyPath; - private static readonly string DirectDependencyPath; - - private static readonly WinGetAssemblyLoadContext WinGetAcl = new (); - - static WinGetAssemblyLoadContext() - { - var self = typeof(WinGetAssemblyLoadContext).Assembly; - SharedDependencyPath = Path.Combine( - Path.GetDirectoryName(self.Location), - "SharedDependencies"); - SharedArchDependencyPath = Path.Combine( - SharedDependencyPath, - RuntimeInformation.ProcessArchitecture.ToString().ToLower()); - DirectDependencyPath = Path.Combine( - Path.GetDirectoryName(self.Location), - "DirectDependencies"); - } - - private WinGetAssemblyLoadContext() - : base("WinGetAssemblyLoadContext", isCollectible: false) - { - } - - /// - /// Handler to resolve assemblies. - /// - /// Assembly load context. - /// Assembly name. - /// The assembly, null if not in our assembly location. - internal static Assembly ResolvingHandler(AssemblyLoadContext context, AssemblyName assemblyName) - { - string name = $"{assemblyName.Name}.dll"; - if (DefaultContextAssemblies.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - string sharedPath = Path.Combine(SharedDependencyPath, name); - if (File.Exists(sharedPath)) - { - return AssemblyLoadContext.Default.LoadFromAssemblyPath(sharedPath); - } - } - - string path = Path.Combine(DirectDependencyPath, name); - if (File.Exists(path)) - { - return WinGetAcl.LoadFromAssemblyName(assemblyName); - } - - return null; - } - - /// - /// Handler to resolve unmanaged assemblies. - /// - /// Assembly initiating the unmanaged load. - /// Unmanaged dll name. - /// The assembly ptr, zero if not in our assembly location. - internal static IntPtr ResolvingUnmanagedDllHandler(Assembly assembly, string unmanagedDllName) - { - return WinGetAcl.LoadUnmanagedDll(unmanagedDllName); - } - - /// - protected override Assembly Load(AssemblyName assemblyName) - { - string name = $"{assemblyName.Name}.dll"; - if (DefaultContextAssemblies.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))) - { - return null; - } - - string path = Path.Combine(SharedDependencyPath, name); - if (File.Exists(path)) - { - return this.LoadFromAssemblyPath(path); - } - - path = Path.Combine(SharedArchDependencyPath, name); - if (File.Exists(path)) - { - return this.LoadFromAssemblyPath(path); - } - - path = Path.Combine(DirectDependencyPath, name); - if (File.Exists(path)) - { - return this.LoadFromAssemblyPath(path); - } - - return null; - } - - /// - protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) - { - string path = Path.Combine(SharedArchDependencyPath, unmanagedDllName); - if (File.Exists(path)) - { - return this.LoadUnmanagedDllFromPath(path); - } - - return IntPtr.Zero; - } - } -} -#endif +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +#if !POWERSHELL_WINDOWS +namespace Microsoft.WinGet.Resolver +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using System.Runtime.InteropServices; + using System.Runtime.Loader; + + /// + /// Custom assembly load context for this module. + /// This helps us load our dependencies without carrying about apps importing this module. + /// All dependencies except the Engine dll needs to be under a Dependencies directory. + /// + internal class WinGetAssemblyLoadContext : AssemblyLoadContext + { + // The assemblies must be loaded in the default context. + // Loading WinRT.Runtime.dll in an ALC when is already loaded in the default context + // will result on 'Attempt to update previously set global instance.' + private static readonly IEnumerable DefaultContextAssemblies = new string[] + { + @"WinRT.Runtime.dll", + }; + + private static readonly string SharedDependencyPath; + private static readonly string SharedArchDependencyPath; + private static readonly string DirectDependencyPath; + + private static readonly WinGetAssemblyLoadContext WinGetAcl = new (); + + static WinGetAssemblyLoadContext() + { + var self = typeof(WinGetAssemblyLoadContext).Assembly; + SharedDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "SharedDependencies"); + SharedArchDependencyPath = Path.Combine( + SharedDependencyPath, + RuntimeInformation.ProcessArchitecture.ToString().ToLower()); + DirectDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "DirectDependencies"); + } + + private WinGetAssemblyLoadContext() + : base("WinGetAssemblyLoadContext", isCollectible: false) + { + } + + /// + /// Handler to resolve assemblies. + /// + /// Assembly load context. + /// Assembly name. + /// The assembly, null if not in our assembly location. + internal static Assembly ResolvingHandler(AssemblyLoadContext context, AssemblyName assemblyName) + { + string name = $"{assemblyName.Name}.dll"; + if (DefaultContextAssemblies.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + string sharedPath = Path.Combine(SharedDependencyPath, name); + if (File.Exists(sharedPath)) + { + return AssemblyLoadContext.Default.LoadFromAssemblyPath(sharedPath); + } + } + + string path = Path.Combine(DirectDependencyPath, name); + if (File.Exists(path)) + { + return WinGetAcl.LoadFromAssemblyName(assemblyName); + } + + return null; + } + + /// + /// Handler to resolve unmanaged assemblies. + /// + /// Assembly initiating the unmanaged load. + /// Unmanaged dll name. + /// The assembly ptr, zero if not in our assembly location. + internal static IntPtr ResolvingUnmanagedDllHandler(Assembly assembly, string unmanagedDllName) + { + return WinGetAcl.LoadUnmanagedDll(unmanagedDllName); + } + + /// + protected override Assembly Load(AssemblyName assemblyName) + { + string name = $"{assemblyName.Name}.dll"; + if (DefaultContextAssemblies.Any(a => a.Equals(name, StringComparison.OrdinalIgnoreCase))) + { + return null; + } + + string path = Path.Combine(SharedDependencyPath, name); + if (File.Exists(path)) + { + return this.LoadFromAssemblyPath(path); + } + + path = Path.Combine(SharedArchDependencyPath, name); + if (File.Exists(path)) + { + return this.LoadFromAssemblyPath(path); + } + + path = Path.Combine(DirectDependencyPath, name); + if (File.Exists(path)) + { + return this.LoadFromAssemblyPath(path); + } + + return null; + } + + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + string path = Path.Combine(SharedArchDependencyPath, unmanagedDllName); + if (File.Exists(path)) + { + return this.LoadUnmanagedDllFromPath(path); + } + + return IntPtr.Zero; + } + } +} +#endif diff --git a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md index 2b62027f9d..8dd0b982fc 100644 --- a/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md +++ b/src/PowerShell/Help/Microsoft.WinGet.Client/Repair-WinGetPackage.md @@ -1,303 +1,303 @@ ---- -external help file: Microsoft.WinGet.Client.Cmdlets.dll-Help.xml -Module Name: Microsoft.WinGet.Client -ms.date: 08/26/2024 -online version: -schema: 2.0.0 ---- - -# Repair-WinGetPackage - -## SYNOPSIS -Repairs a WinGet Package. - -## SYNTAX - -### FoundSet (Default) -``` -Repair-WinGetPackage [-Mode ] [-Log ] [-Version ] [-Id ] - [-Name ] [-Moniker ] [-Source ] [[-Query] ] - [-MatchOption ] [-ProgressAction ] [-WhatIf] [-Confirm] - [] -``` - -### GivenSet -``` -Repair-WinGetPackage [-Mode ] [-Log ] [[-PSCatalogPackage] ] - [-Version ] [-ProgressAction ] [-WhatIf] [-Confirm] [] -``` - -## DESCRIPTION -This command repairs a WinGet package from your computer, provided the package includes repair support. -The command includes parameters to specify values used to search for installed packages. By default, -all string-based searches are case-insensitive substring searches. Wildcards are not supported. -You can change the search behavior using the **MatchOption** parameter. - -> **Note: Not all packages support repair.** - -## EXAMPLES - -### Example 1: Repair a package using a query -```powershell -Repair-WinGetPackage -Query "Microsoft.GDK.2406" -``` -This example shows how to repair a package using a query. The **Query** parameter is positional, so you -don't need to include the parameter name before the query string. - -### Example 3 : Repair a package by Id -```powershell -Repair-WinGetPackage -Id "Microsoft.GDK.2406" -``` -This example shows how to repair a package by specifying the package identifier. - -If the package identifier is available from more than one source, you must provide additional search -criteria to select a specific instance of the package. - -### Example 3: Repair a package using by Name -```powershell -Repair-WinGetPackage -Name "Microsoft Game Development Kit - 240602 (June 2024 Update 2)" -``` -This example shows how to repair a package using the package name. - -> **Note: Please note that the examples mentioned above are mainly reference examples for the repair cmdlet and may not be operational as is, since many installers don't support repair as a standard functionality. For the Microsoft.GDK.2406 example, the assumption is that Microsoft.GDK.2406 supports repair capability and the author of the installer has provided the necessary repair context/switches in the Package Manifest in the Package Source referenced by the WinGet Client.** - -## PARAMETERS - -### -Id - -Specify the package identifier to search for. By default, the command does a case-insensitive -substring match. - -```yaml -Type: System.String -Parameter Sets: FoundSet -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Log - -Specify the location for the installer repair log. The value can be a fully-qualified or relative path and must include the file name. For example: `$env:TEMP\package.log`. - -> **Note: Not all installers support this option.** - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### -MatchOption - -Specify the match option for a WinGet package query. This parameter accepts the following values: - -```yaml -Type: Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption -Parameter Sets: FoundSet -Aliases: -Accepted values: Equals, EqualsCaseInsensitive, StartsWithCaseInsensitive, ContainsCaseInsensitive - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Mode - -Specify the output mode for the installer. The parameter accepts the following values: - -```yaml -Type: Microsoft.WinGet.Client.PSObjects.PSPackageRepairMode -Parameter Sets: (All) -Aliases: -Accepted values: Default, Silent, Interactive - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Moniker - -Specify the moniker of the WinGet package to repair. For example, the moniker for the -Microsoft.PowerShell package is `pwsh`. - -```yaml -Type: System.String -Parameter Sets: FoundSet -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Name - -Specify the name of the WinGet package name. The name contains space, you must enclose the name in -quotes. - -```yaml -Type: System.String -Parameter Sets: FoundSet -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -PSCatalogPackage -Provide **PSCatalogPackage** object. You can get a **PSCatalogPackage** object by using the -`Find-WinGetPackage` or `Get-WingetPackage` commands. - - -```yaml -Type: Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage -Parameter Sets: GivenSet -Aliases: InputObject - -Required: False -Position: 0 -Default value: None -Accept pipeline input: True (ByPropertyName, ByValue) -Accept wildcard characters: False -``` - -### -Query - -Specify one or more strings to search for. By default, the command searches all configured sources. -Wildcards are not supported. The command compares the value provided to the following package -manifest properties: - - - `PackageIdentifier` - - `PackageName` - - `Moniker` - - `Tags` - -The command does a case-insensitive substring comparison of these properties. - -```yaml -Type: System.String[] -Parameter Sets: FoundSet -Aliases: - -Required: False -Position: 0 -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Source - -Specify the name of a configured WinGet source. - -```yaml -Type: System.String -Parameter Sets: FoundSet -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Version - -Specify the version of the package to be repaired. - -```yaml -Type: System.String -Parameter Sets: (All) -Aliases: - -Required: False -Position: Named -Default value: None -Accept pipeline input: True (ByPropertyName) -Accept wildcard characters: False -``` - -### -Confirm - -Prompts you for confirmation before running the cmdlet. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: cf - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - - -### -WhatIf -Shows what would happen if the cmdlet runs. -The cmdlet is not run. - -```yaml -Type: System.Management.Automation.SwitchParameter -Parameter Sets: (All) -Aliases: wi - -Required: False -Position: Named -Default value: None -Accept pipeline input: False -Accept wildcard characters: False -``` - -### CommonParameters -This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). - -## INPUTS - -### Microsoft.WinGet.Client.PSObjects.PSPackageRepairMode - -### Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage - -### System.String - -### System.String[] - -### Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption - -## OUTPUTS - -### Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult - -## NOTES - -## RELATED LINKS - -[Find-WinGetPackage](Find-WinGetPackage.md) - -[Install-WinGetPackage](Install-WinGetPackage.md) - -[Uninstall-WinGetPackage](Uninstall-WinGetPackage.md) +--- +external help file: Microsoft.WinGet.Client.Cmdlets.dll-Help.xml +Module Name: Microsoft.WinGet.Client +ms.date: 08/26/2024 +online version: +schema: 2.0.0 +--- + +# Repair-WinGetPackage + +## SYNOPSIS +Repairs a WinGet Package. + +## SYNTAX + +### FoundSet (Default) +``` +Repair-WinGetPackage [-Mode ] [-Log ] [-Version ] [-Id ] + [-Name ] [-Moniker ] [-Source ] [[-Query] ] + [-MatchOption ] [-ProgressAction ] [-WhatIf] [-Confirm] + [] +``` + +### GivenSet +``` +Repair-WinGetPackage [-Mode ] [-Log ] [[-PSCatalogPackage] ] + [-Version ] [-ProgressAction ] [-WhatIf] [-Confirm] [] +``` + +## DESCRIPTION +This command repairs a WinGet package from your computer, provided the package includes repair support. +The command includes parameters to specify values used to search for installed packages. By default, +all string-based searches are case-insensitive substring searches. Wildcards are not supported. +You can change the search behavior using the **MatchOption** parameter. + +> **Note: Not all packages support repair.** + +## EXAMPLES + +### Example 1: Repair a package using a query +```powershell +Repair-WinGetPackage -Query "Microsoft.GDK.2406" +``` +This example shows how to repair a package using a query. The **Query** parameter is positional, so you +don't need to include the parameter name before the query string. + +### Example 3 : Repair a package by Id +```powershell +Repair-WinGetPackage -Id "Microsoft.GDK.2406" +``` +This example shows how to repair a package by specifying the package identifier. + +If the package identifier is available from more than one source, you must provide additional search +criteria to select a specific instance of the package. + +### Example 3: Repair a package using by Name +```powershell +Repair-WinGetPackage -Name "Microsoft Game Development Kit - 240602 (June 2024 Update 2)" +``` +This example shows how to repair a package using the package name. + +> **Note: Please note that the examples mentioned above are mainly reference examples for the repair cmdlet and may not be operational as is, since many installers don't support repair as a standard functionality. For the Microsoft.GDK.2406 example, the assumption is that Microsoft.GDK.2406 supports repair capability and the author of the installer has provided the necessary repair context/switches in the Package Manifest in the Package Source referenced by the WinGet Client.** + +## PARAMETERS + +### -Id + +Specify the package identifier to search for. By default, the command does a case-insensitive +substring match. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Log + +Specify the location for the installer repair log. The value can be a fully-qualified or relative path and must include the file name. For example: `$env:TEMP\package.log`. + +> **Note: Not all installers support this option.** + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### -MatchOption + +Specify the match option for a WinGet package query. This parameter accepts the following values: + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption +Parameter Sets: FoundSet +Aliases: +Accepted values: Equals, EqualsCaseInsensitive, StartsWithCaseInsensitive, ContainsCaseInsensitive + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Mode + +Specify the output mode for the installer. The parameter accepts the following values: + +```yaml +Type: Microsoft.WinGet.Client.PSObjects.PSPackageRepairMode +Parameter Sets: (All) +Aliases: +Accepted values: Default, Silent, Interactive + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Moniker + +Specify the moniker of the WinGet package to repair. For example, the moniker for the +Microsoft.PowerShell package is `pwsh`. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Name + +Specify the name of the WinGet package name. The name contains space, you must enclose the name in +quotes. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -PSCatalogPackage +Provide **PSCatalogPackage** object. You can get a **PSCatalogPackage** object by using the +`Find-WinGetPackage` or `Get-WingetPackage` commands. + + +```yaml +Type: Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage +Parameter Sets: GivenSet +Aliases: InputObject + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName, ByValue) +Accept wildcard characters: False +``` + +### -Query + +Specify one or more strings to search for. By default, the command searches all configured sources. +Wildcards are not supported. The command compares the value provided to the following package +manifest properties: + + - `PackageIdentifier` + - `PackageName` + - `Moniker` + - `Tags` + +The command does a case-insensitive substring comparison of these properties. + +```yaml +Type: System.String[] +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: 0 +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Source + +Specify the name of a configured WinGet source. + +```yaml +Type: System.String +Parameter Sets: FoundSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Version + +Specify the version of the package to be repaired. + +```yaml +Type: System.String +Parameter Sets: (All) +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: True (ByPropertyName) +Accept wildcard characters: False +``` + +### -Confirm + +Prompts you for confirmation before running the cmdlet. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: cf + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + + +### -WhatIf +Shows what would happen if the cmdlet runs. +The cmdlet is not run. + +```yaml +Type: System.Management.Automation.SwitchParameter +Parameter Sets: (All) +Aliases: wi + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + +### CommonParameters +This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). + +## INPUTS + +### Microsoft.WinGet.Client.PSObjects.PSPackageRepairMode + +### Microsoft.WinGet.Client.Engine.PSObjects.PSCatalogPackage + +### System.String + +### System.String[] + +### Microsoft.WinGet.Client.PSObjects.PSPackageFieldMatchOption + +## OUTPUTS + +### Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult + +## NOTES + +## RELATED LINKS + +[Find-WinGetPackage](Find-WinGetPackage.md) + +[Install-WinGetPackage](Install-WinGetPackage.md) + +[Uninstall-WinGetPackage](Uninstall-WinGetPackage.md) diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddSourceCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddSourceCmdlet.cs index aa9cf2a0a4..42ee1162e2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddSourceCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AddSourceCmdlet.cs @@ -1,92 +1,92 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Cmdlets.PSObjects; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Adds a source. Requires admin. - /// - [Cmdlet(VerbsCommon.Add, Constants.WinGetNouns.Source)] - [Alias("awgs")] - public sealed class AddSourceCmdlet : PSCmdlet - { - /// - /// Gets or sets the name of the source to add. - /// - [Parameter( - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public string Name { get; set; } - - /// - /// Gets or sets the argument of the source to add. - /// - [Parameter( - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public string Argument { get; set; } - - /// - /// Gets or sets the type of the source to add. - /// - [Parameter( - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] -#if AICLI_DISABLE_TEST_HOOKS - [ValidateSet( - "Microsoft.Rest", - "Microsoft.PreIndexed.Package")] -#else - [ValidateSet( - "Microsoft.Rest", - "Microsoft.PreIndexed.Package", - "Microsoft.Test.Configurable")] -#endif - public string Type { get; set; } - - /// - /// Gets or sets the trust level of the source to add. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public PSSourceTrustLevel TrustLevel { get; set; } = PSSourceTrustLevel.Default; - - /// - /// Gets or sets a value indicating whether the source to add is explicit. - /// - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Explicit { get; set; } - - /// - /// Gets or sets a value indicating the priority of the source. Higher values are sorted first. - /// - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public int Priority { get; set; } - - /// - /// Adds source. - /// - protected override void ProcessRecord() - { - var command = new CliCommand(this); - command.AddSource(this.Name, this.Argument, this.Type, this.ConvertPSSourceTrustLevelToString(this.TrustLevel), this.Explicit.ToBool(), this.Priority); - } - - private string ConvertPSSourceTrustLevelToString(PSSourceTrustLevel trustLevel) => trustLevel switch - { - PSSourceTrustLevel.Default => string.Empty, - _ => trustLevel.ToString(), - }; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Cmdlets.PSObjects; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Adds a source. Requires admin. + /// + [Cmdlet(VerbsCommon.Add, Constants.WinGetNouns.Source)] + [Alias("awgs")] + public sealed class AddSourceCmdlet : PSCmdlet + { + /// + /// Gets or sets the name of the source to add. + /// + [Parameter( + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Gets or sets the argument of the source to add. + /// + [Parameter( + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public string Argument { get; set; } + + /// + /// Gets or sets the type of the source to add. + /// + [Parameter( + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] +#if AICLI_DISABLE_TEST_HOOKS + [ValidateSet( + "Microsoft.Rest", + "Microsoft.PreIndexed.Package")] +#else + [ValidateSet( + "Microsoft.Rest", + "Microsoft.PreIndexed.Package", + "Microsoft.Test.Configurable")] +#endif + public string Type { get; set; } + + /// + /// Gets or sets the trust level of the source to add. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSSourceTrustLevel TrustLevel { get; set; } = PSSourceTrustLevel.Default; + + /// + /// Gets or sets a value indicating whether the source to add is explicit. + /// + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Explicit { get; set; } + + /// + /// Gets or sets a value indicating the priority of the source. Higher values are sorted first. + /// + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public int Priority { get; set; } + + /// + /// Adds source. + /// + protected override void ProcessRecord() + { + var command = new CliCommand(this); + command.AddSource(this.Name, this.Argument, this.Type, this.ConvertPSSourceTrustLevelToString(this.TrustLevel), this.Explicit.ToBool(), this.Priority); + } + + private string ConvertPSSourceTrustLevelToString(PSSourceTrustLevel trustLevel) => trustLevel switch + { + PSSourceTrustLevel.Default => string.Empty, + _ => trustLevel.ToString(), + }; + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs index b33bdbd502..47e227b03c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/AssertWinGetPackageManagerCmdlet.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Assert-WinGetPackageManager. Verifies winget is installed properly. - /// - [Cmdlet( - VerbsLifecycle.Assert, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - [Alias("awgpm")] - public class AssertWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet - { - /// - /// Validates winget is installed correctly. If not, throws an exception - /// with the reason why, if any. - /// - protected override void ProcessRecord() - { - var command = new WinGetPackageManagerCommand(this); - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - command.AssertUsingLatest(this.IncludePrerelease.ToBool()); - } - else - { - command.Assert(this.Version); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Assert-WinGetPackageManager. Verifies winget is installed properly. + /// + [Cmdlet( + VerbsLifecycle.Assert, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + [Alias("awgpm")] + public class AssertWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + /// + /// Validates winget is installed correctly. If not, throws an exception + /// with the reason why, if any. + /// + protected override void ProcessRecord() + { + var command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + command.AssertUsingLatest(this.IncludePrerelease.ToBool()); + } + else + { + command.Assert(this.Version); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderCmdlet.cs index 0a6a1d9c7f..e92481820e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderCmdlet.cs @@ -1,69 +1,69 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.PSObjects; - - /// - /// This is the base class for all commands that might need to search for a package. It contains an initial - /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. - /// - public abstract class FinderCmdlet : PSCmdlet - { - /// - /// Gets or sets the field that is matched against the identifier of a package. - /// - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Id { get; set; } - - /// - /// Gets or sets the field that is matched against the name of a package. - /// - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Name { get; set; } - - /// - /// Gets or sets the field that is matched against the moniker of a package. - /// - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Moniker { get; set; } - - /// - /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. - /// - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public string Source { get; set; } - - /// - /// Gets or sets the strings that match against every field of a package. - /// - [Parameter( - ParameterSetName = Constants.FoundSet, - Position = 0, - ValueFromPipelineByPropertyName = true, - ValueFromRemainingArguments = true)] - public string[] Query { get; set; } - - /// - /// Gets or sets how to match against package fields. Default ContainsCaseInsensitive. - /// - [Parameter( - ParameterSetName = Constants.FoundSet, - ValueFromPipelineByPropertyName = true)] - public PSPackageFieldMatchOption MatchOption { get; set; } = PSPackageFieldMatchOption.ContainsCaseInsensitive; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.PSObjects; + + /// + /// This is the base class for all commands that might need to search for a package. It contains an initial + /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. + /// + public abstract class FinderCmdlet : PSCmdlet + { + /// + /// Gets or sets the field that is matched against the identifier of a package. + /// + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Id { get; set; } + + /// + /// Gets or sets the field that is matched against the name of a package. + /// + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Gets or sets the field that is matched against the moniker of a package. + /// + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Moniker { get; set; } + + /// + /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. + /// + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public string Source { get; set; } + + /// + /// Gets or sets the strings that match against every field of a package. + /// + [Parameter( + ParameterSetName = Constants.FoundSet, + Position = 0, + ValueFromPipelineByPropertyName = true, + ValueFromRemainingArguments = true)] + public string[] Query { get; set; } + + /// + /// Gets or sets how to match against package fields. Default ContainsCaseInsensitive. + /// + [Parameter( + ParameterSetName = Constants.FoundSet, + ValueFromPipelineByPropertyName = true)] + public PSPackageFieldMatchOption MatchOption { get; set; } = PSPackageFieldMatchOption.ContainsCaseInsensitive; + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderExtendedCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderExtendedCmdlet.cs index 1c44fab4f0..247acbf5b0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderExtendedCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/FinderExtendedCmdlet.cs @@ -2,16 +2,16 @@ // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- + namespace Microsoft.WinGet.Client.Commands.Common { - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + /// - /// This is the base class for the commands whose sole purpose is to filter a list of packages i.e., - /// the "search" and "list" commands. This class contains an extended set of parameters suited for + /// This is the base class for the commands whose sole purpose is to filter a list of packages i.e., + /// the "search" and "list" commands. This class contains an extended set of parameters suited for /// that purpose. /// public abstract class FinderExtendedCmdlet : FinderCmdlet diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs index 8d430c6aa3..76d174435d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/InstallCmdlet.cs @@ -5,10 +5,10 @@ // ----------------------------------------------------------------------------- namespace Microsoft.WinGet.Client.Commands.Common -{ +{ using System.IO; using System.Management.Automation; - using Microsoft.WinGet.Client.PSObjects; + using Microsoft.WinGet.Client.PSObjects; /// /// This is the base class for all commands that parse a FindPackagesOptions result @@ -49,8 +49,8 @@ public string Location ? value : this.SessionState.Path.CurrentFileSystemLocation + @"\" + value; } - } - + } + /// /// Gets or sets the path to the logging file. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs index afef05f574..077885f57e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/PackageCmdlet.cs @@ -2,27 +2,27 @@ // // Copyright (c) Microsoft Corporation. Licensed under the MIT License. // -// ----------------------------------------------------------------------------- - +// ----------------------------------------------------------------------------- + namespace Microsoft.WinGet.Client.Commands.Common { - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.PSObjects; - + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.PSObjects; + /// /// This is the base class for commands which operate on a specific package and version i.e., /// the "install", "uninstall", and "upgrade" commands. /// public abstract class PackageCmdlet : FinderCmdlet - { - /// - /// Initializes a new instance of the class. + { + /// + /// Initializes a new instance of the class. /// - public PackageCmdlet() - { - // The default match option for single package operations. - this.MatchOption = PSObjects.PSPackageFieldMatchOption.EqualsCaseInsensitive; + public PackageCmdlet() + { + // The default match option for single package operations. + this.MatchOption = PSObjects.PSPackageFieldMatchOption.EqualsCaseInsensitive; } /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs index daddb3c243..1bb3d84286 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/Common/WinGetPackageManagerCmdlet.cs @@ -1,44 +1,44 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands.Common -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - - /// - /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. - /// - public abstract class WinGetPackageManagerCmdlet : PSCmdlet - { - /// - /// Gets or sets the optional version. - /// - [Parameter( - ParameterSetName = Constants.IntegrityVersionSet, - ValueFromPipelineByPropertyName = true)] - public string Version { get; set; } = string.Empty; - - /// - /// Gets or sets a value indicating whether to use latest. - /// - [Parameter( - ParameterSetName = Constants.IntegrityLatestSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter Latest { get; set; } - - /// - /// Gets or sets a value indicating whether to include prerelease winget versions. - /// - [Parameter( - ParameterSetName = Constants.IntegrityLatestSet, - ValueFromPipelineByPropertyName = true)] - [Parameter( - ParameterSetName = Constants.IntegrityVersionSet, - ValueFromPipelineByPropertyName = true)] - public SwitchParameter IncludePrerelease { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands.Common +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + + /// + /// Common parameters for Assert-WinGetPackageManager and Repair-WinGetPackageManager. + /// + public abstract class WinGetPackageManagerCmdlet : PSCmdlet + { + /// + /// Gets or sets the optional version. + /// + [Parameter( + ParameterSetName = Constants.IntegrityVersionSet, + ValueFromPipelineByPropertyName = true)] + public string Version { get; set; } = string.Empty; + + /// + /// Gets or sets a value indicating whether to use latest. + /// + [Parameter( + ParameterSetName = Constants.IntegrityLatestSet, + ValueFromPipelineByPropertyName = true)] + public SwitchParameter Latest { get; set; } + + /// + /// Gets or sets a value indicating whether to include prerelease winget versions. + /// + [Parameter( + ParameterSetName = Constants.IntegrityLatestSet, + ValueFromPipelineByPropertyName = true)] + [Parameter( + ParameterSetName = Constants.IntegrityVersionSet, + ValueFromPipelineByPropertyName = true)] + public SwitchParameter IncludePrerelease { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/DisableSettingCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/DisableSettingCmdlet.cs index 0e7bdf51cd..396c00d7cc 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/DisableSettingCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/DisableSettingCmdlet.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Disables an admin setting. Requires admin. - /// - [Cmdlet(VerbsLifecycle.Disable, Constants.WinGetNouns.Setting)] - [Alias("dwgs")] - public sealed class DisableSettingCmdlet : PSCmdlet - { - /// - /// Gets or sets the name of the setting to disable. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - [ValidateSet( - "LocalManifestFiles", - "BypassCertificatePinningForMicrosoftStore", - "InstallerHashOverride", - "LocalArchiveMalwareScanOverride", - "ProxyCommandLineOptions", - "ConfigurationProcessorPath")] - public string Name { get; set; } - - /// - /// Disables the admin setting. - /// - protected override void ProcessRecord() - { - var command = new CliCommand(this); - command.DisableSetting(this.Name); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Disables an admin setting. Requires admin. + /// + [Cmdlet(VerbsLifecycle.Disable, Constants.WinGetNouns.Setting)] + [Alias("dwgs")] + public sealed class DisableSettingCmdlet : PSCmdlet + { + /// + /// Gets or sets the name of the setting to disable. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateSet( + "LocalManifestFiles", + "BypassCertificatePinningForMicrosoftStore", + "InstallerHashOverride", + "LocalArchiveMalwareScanOverride", + "ProxyCommandLineOptions", + "ConfigurationProcessorPath")] + public string Name { get; set; } + + /// + /// Disables the admin setting. + /// + protected override void ProcessRecord() + { + var command = new CliCommand(this); + command.DisableSetting(this.Name); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/EnableSettingCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/EnableSettingCmdlet.cs index 5fa3586577..2ea3dc1dd6 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/EnableSettingCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/EnableSettingCmdlet.cs @@ -1,46 +1,46 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Enables an admin setting. Requires admin. - /// - [Cmdlet(VerbsLifecycle.Enable, Constants.WinGetNouns.Setting)] - [Alias("ewgs")] - public sealed class EnableSettingCmdlet : PSCmdlet - { - /// - /// Gets or sets the name of the setting to enable. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - [ValidateSet( - "LocalManifestFiles", - "BypassCertificatePinningForMicrosoftStore", - "InstallerHashOverride", - "LocalArchiveMalwareScanOverride", - "ProxyCommandLineOptions", - "ConfigurationProcessorPath")] - public string Name { get; set; } - - /// - /// Enables the admin setting. - /// - protected override void ProcessRecord() - { - var command = new CliCommand(this); - command.EnableSetting(this.Name); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Enables an admin setting. Requires admin. + /// + [Cmdlet(VerbsLifecycle.Enable, Constants.WinGetNouns.Setting)] + [Alias("ewgs")] + public sealed class EnableSettingCmdlet : PSCmdlet + { + /// + /// Gets or sets the name of the setting to enable. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + [ValidateSet( + "LocalManifestFiles", + "BypassCertificatePinningForMicrosoftStore", + "InstallerHashOverride", + "LocalArchiveMalwareScanOverride", + "ProxyCommandLineOptions", + "ConfigurationProcessorPath")] + public string Name { get; set; } + + /// + /// Enables the admin setting. + /// + protected override void ProcessRecord() + { + var command = new CliCommand(this); + command.EnableSetting(this.Name); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ExportPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ExportPackageCmdlet.cs index 5785b64bc7..f7ae923557 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ExportPackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ExportPackageCmdlet.cs @@ -1,91 +1,91 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - using Microsoft.WinGet.Client.Engine.PSObjects; - - /// - /// Downloads a package installer from the pipeline or from a configured source. - /// - [Cmdlet( - VerbsData.Export, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [Alias("ewgp")] - [OutputType(typeof(PSDownloadResult))] - public sealed class ExportPackageCmdlet : InstallerSelectionCmdlet - { - private DownloadCommand command = null; - - /// - /// Gets or sets the directory where the installer will be downloaded to. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public string DownloadDirectory { get; set; } - - /// - /// Gets or sets a value indicating whether to skip acquiring the license from a Store package. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter SkipMicrosoftStoreLicense { get; set; } - - /// - /// Gets or sets the platform to download the package for. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public PSObjects.PSWindowsPlatform Platform { get; set; } - - /// - /// Gets or sets the target OS version to download for. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public string TargetOSVersion { get; set; } - - /// - /// Installs a package from the pipeline or from a configured source. - /// - protected override void ProcessRecord() - { - this.command = new DownloadCommand( - this, - this.PSCatalogPackage, - this.Version, - this.Id, - this.Name, - this.Moniker, - this.Source, - this.Query, - this.AllowHashMismatch.ToBool(), - this.SkipDependencies.ToBool(), - this.Locale); - this.command.Download( - this.DownloadDirectory, - this.MatchOption.ToString(), - this.Scope.ToString(), - this.Architecture.ToString(), - this.InstallerType.ToString(), - this.SkipMicrosoftStoreLicense.ToBool(), - this.Platform.ToString(), - this.TargetOSVersion); - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.command != null) - { - this.command.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// Downloads a package installer from the pipeline or from a configured source. + /// + [Cmdlet( + VerbsData.Export, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [Alias("ewgp")] + [OutputType(typeof(PSDownloadResult))] + public sealed class ExportPackageCmdlet : InstallerSelectionCmdlet + { + private DownloadCommand command = null; + + /// + /// Gets or sets the directory where the installer will be downloaded to. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string DownloadDirectory { get; set; } + + /// + /// Gets or sets a value indicating whether to skip acquiring the license from a Store package. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter SkipMicrosoftStoreLicense { get; set; } + + /// + /// Gets or sets the platform to download the package for. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSObjects.PSWindowsPlatform Platform { get; set; } + + /// + /// Gets or sets the target OS version to download for. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string TargetOSVersion { get; set; } + + /// + /// Installs a package from the pipeline or from a configured source. + /// + protected override void ProcessRecord() + { + this.command = new DownloadCommand( + this, + this.PSCatalogPackage, + this.Version, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.AllowHashMismatch.ToBool(), + this.SkipDependencies.ToBool(), + this.Locale); + this.command.Download( + this.DownloadDirectory, + this.MatchOption.ToString(), + this.Scope.ToString(), + this.Architecture.ToString(), + this.InstallerType.ToString(), + this.SkipMicrosoftStoreLicense.ToBool(), + this.Platform.ToString(), + this.TargetOSVersion); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/FindPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/FindPackageCmdlet.cs index 229fbe1831..cf020fd00a 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/FindPackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/FindPackageCmdlet.cs @@ -1,42 +1,42 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - using Microsoft.WinGet.Client.Engine.PSObjects; - - /// - /// Searches configured sources for packages. - /// - [Cmdlet(VerbsCommon.Find, Constants.WinGetNouns.Package)] - [Alias("fdwgp")] - [OutputType(typeof(PSFoundCatalogPackage))] - public sealed class FindPackageCmdlet : FinderExtendedCmdlet - { - /// - /// Searches for configured sources for packages. - /// - protected override void ProcessRecord() - { - var command = new FinderPackageCommand( - this, - this.Id, - this.Name, - this.Moniker, - this.Source, - this.Query, - this.Tag, - this.Command, - this.Count); - - command.Find(this.MatchOption.ToString()); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// Searches configured sources for packages. + /// + [Cmdlet(VerbsCommon.Find, Constants.WinGetNouns.Package)] + [Alias("fdwgp")] + [OutputType(typeof(PSFoundCatalogPackage))] + public sealed class FindPackageCmdlet : FinderExtendedCmdlet + { + /// + /// Searches for configured sources for packages. + /// + protected override void ProcessRecord() + { + var command = new FinderPackageCommand( + this, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.Tag, + this.Command, + this.Count); + + command.Find(this.MatchOption.ToString()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetSourceCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetSourceCmdlet.cs index f19ee12f6a..95a3da6e70 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetSourceCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetSourceCmdlet.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - using Microsoft.WinGet.Client.Engine.PSObjects; - - /// - /// Retrieves the list of configured sources. - /// - [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Source)] - [Alias("gwgso")] - [OutputType(typeof(PSSourceResult))] - public sealed class GetSourceCmdlet : PSCmdlet - { - /// - /// Gets or sets the name of the source to retrieve. - /// - [Parameter( - Position = 0, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public string Name { get; set; } - - /// - /// Returns the list of configured sources. - /// - protected override void ProcessRecord() - { - var command = new SourceCommand(this); - command.Get(this.Name); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// Retrieves the list of configured sources. + /// + [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Source)] + [Alias("gwgso")] + [OutputType(typeof(PSSourceResult))] + public sealed class GetSourceCmdlet : PSCmdlet + { + /// + /// Gets or sets the name of the source to retrieve. + /// + [Parameter( + Position = 0, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Returns the list of configured sources. + /// + protected override void ProcessRecord() + { + var command = new SourceCommand(this); + command.Get(this.Name); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetVersionCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetVersionCmdlet.cs index c15cdbe5d2..f1623d8c2b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetVersionCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/GetVersionCmdlet.cs @@ -1,30 +1,30 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Get-WinGetVersion. Gets the current version of winget. - /// - [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Version)] - [Alias("gwgv")] - [OutputType(typeof(string))] - public class GetVersionCmdlet : PSCmdlet - { - /// - /// Writes the winget version. - /// - protected override void ProcessRecord() - { - var command = new VersionCommand(this); - command.Get(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Get-WinGetVersion. Gets the current version of winget. + /// + [Cmdlet(VerbsCommon.Get, Constants.WinGetNouns.Version)] + [Alias("gwgv")] + [OutputType(typeof(string))] + public class GetVersionCmdlet : PSCmdlet + { + /// + /// Writes the winget version. + /// + protected override void ProcessRecord() + { + var command = new VersionCommand(this); + command.Get(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs index 968319e891..37a1d07522 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallPackageCmdlet.cs @@ -1,66 +1,66 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - using Microsoft.WinGet.Client.Engine.PSObjects; - - /// - /// Installs a package from the pipeline or from a configured source. - /// - [Cmdlet( - VerbsLifecycle.Install, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [Alias("iswgp")] - [OutputType(typeof(PSInstallResult))] - public sealed class InstallPackageCmdlet : InstallCmdlet - { - private InstallerPackageCommand command = null; - - /// - /// Installs a package from the pipeline or from a configured source. - /// - protected override void ProcessRecord() - { - this.command = new InstallerPackageCommand( - this, - this.Override, - this.Custom, - this.Location, - this.AllowHashMismatch.ToBool(), - this.Force.ToBool(), - this.Header, - this.PSCatalogPackage, - this.Version, - this.Log, - this.Id, - this.Name, - this.Moniker, - this.Source, - this.Query, - this.SkipDependencies.ToBool()); - - this.command.Install(this.MatchOption.ToString(), this.Scope.ToString(), this.Architecture.ToString(), this.Mode.ToString(), this.InstallerType.ToString()); - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.command != null) - { - this.command.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// Installs a package from the pipeline or from a configured source. + /// + [Cmdlet( + VerbsLifecycle.Install, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [Alias("iswgp")] + [OutputType(typeof(PSInstallResult))] + public sealed class InstallPackageCmdlet : InstallCmdlet + { + private InstallerPackageCommand command = null; + + /// + /// Installs a package from the pipeline or from a configured source. + /// + protected override void ProcessRecord() + { + this.command = new InstallerPackageCommand( + this, + this.Override, + this.Custom, + this.Location, + this.AllowHashMismatch.ToBool(), + this.Force.ToBool(), + this.Header, + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.SkipDependencies.ToBool()); + + this.command.Install(this.MatchOption.ToString(), this.Scope.ToString(), this.Architecture.ToString(), this.Mode.ToString(), this.InstallerType.ToString()); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallerSelectionCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallerSelectionCmdlet.cs index 7f4735fcac..7876cb6eab 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallerSelectionCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/InstallerSelectionCmdlet.cs @@ -6,10 +6,10 @@ namespace Microsoft.WinGet.Client.Commands { - using System.Management.Automation; + using System.Management.Automation; using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.PSObjects; - + using Microsoft.WinGet.Client.PSObjects; + /// /// This is the base class for all commands that select an installer from a given package. /// Contains shared arguments for the install, update, and download commands. @@ -26,26 +26,26 @@ public abstract class InstallerSelectionCmdlet : PackageCmdlet /// Gets or sets the architecture of the installer to be downloaded. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public PSProcessorArchitecture Architecture { get; set; } = PSProcessorArchitecture.Default; - - /// - /// Gets or sets the installer type to be downloaded. - /// + public PSProcessorArchitecture Architecture { get; set; } = PSProcessorArchitecture.Default; + + /// + /// Gets or sets the installer type to be downloaded. + /// [Parameter(ValueFromPipelineByPropertyName = true)] - public PSPackageInstallerType InstallerType { get; set; } = PSPackageInstallerType.Default; + public PSPackageInstallerType InstallerType { get; set; } = PSPackageInstallerType.Default; /// /// Gets or sets the locale of the installer to be downloaded. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public string Locale { get; set; } - + public string Locale { get; set; } + /// /// Gets or sets the scope of the installer to be downloaded. /// [Parameter(ValueFromPipelineByPropertyName = true)] - public PSPackageInstallScope Scope { get; set; } = PSPackageInstallScope.Any; - + public PSPackageInstallScope Scope { get; set; } = PSPackageInstallScope.Any; + /// /// Gets or sets a value indicating whether skip dependencies. /// diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageFieldMatchOption.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageFieldMatchOption.cs index 893ae3320a..12bf881579 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageFieldMatchOption.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageFieldMatchOption.cs @@ -1,34 +1,34 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// This should mimic Microsoft.Management.Deployment.PackageFileMatchOption. - /// - public enum PSPackageFieldMatchOption - { - /// - /// Equals. - /// - Equals, - - /// - /// EqualsCaseInsensitive. - /// - EqualsCaseInsensitive, - - /// - /// StartsWithCaseInsensitive. - /// - StartsWithCaseInsensitive, - - /// - /// ContainsCaseInsensitive - /// - ContainsCaseInsensitive, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// This should mimic Microsoft.Management.Deployment.PackageFileMatchOption. + /// + public enum PSPackageFieldMatchOption + { + /// + /// Equals. + /// + Equals, + + /// + /// EqualsCaseInsensitive. + /// + EqualsCaseInsensitive, + + /// + /// StartsWithCaseInsensitive. + /// + StartsWithCaseInsensitive, + + /// + /// ContainsCaseInsensitive + /// + ContainsCaseInsensitive, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallMode.cs index ae953d44d6..22459cf137 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallMode.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallMode.cs @@ -1,29 +1,29 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// This should mimic Microsoft.Management.Deployment.PackageInstallMode. - /// - public enum PSPackageInstallMode - { - /// - /// Default, - /// - Default, - - /// - /// Silent, - /// - Silent, - - /// - /// Interactive, - /// - Interactive, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// This should mimic Microsoft.Management.Deployment.PackageInstallMode. + /// + public enum PSPackageInstallMode + { + /// + /// Default, + /// + Default, + + /// + /// Silent, + /// + Silent, + + /// + /// Interactive, + /// + Interactive, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallScope.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallScope.cs index bffd016935..5b308f1729 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallScope.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallScope.cs @@ -1,39 +1,39 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// This must match Microsoft.Management.Deployment.PackageInstallScope. - /// - public enum PSPackageInstallScope - { - /// - /// Any. - /// - Any, - - /// - /// User. - /// - User, - - /// - /// System. - /// - System, - - /// - /// User or unknown. - /// - UserOrUnknown, - - /// - /// System or unknown. - /// - SystemOrUnknown, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// This must match Microsoft.Management.Deployment.PackageInstallScope. + /// + public enum PSPackageInstallScope + { + /// + /// Any. + /// + Any, + + /// + /// User. + /// + User, + + /// + /// System. + /// + System, + + /// + /// User or unknown. + /// + UserOrUnknown, + + /// + /// System or unknown. + /// + SystemOrUnknown, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallerType.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallerType.cs index 5c7ccfbc4b..553d2004c4 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallerType.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageInstallerType.cs @@ -1,69 +1,69 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// The installer type of the package. - /// - public enum PSPackageInstallerType - { - /// - /// Let winget decide. - /// - Default, - - /// - /// Inno, - /// - Inno, - - /// - /// Wix. - /// - Wix, - - /// - /// Msi. - /// - Msi, - - /// - /// Nullsoft. - /// - Nullsoft, - - /// - /// Zip. - /// - Zip, - - /// - /// Msix. - /// - Msix, - - /// - /// Exe. - /// - Exe, - - /// - /// Burn. - /// - Burn, - - /// - /// MSStore, - /// - MSStore, - - /// - /// Portable. - /// - Portable, - } +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// The installer type of the package. + /// + public enum PSPackageInstallerType + { + /// + /// Let winget decide. + /// + Default, + + /// + /// Inno, + /// + Inno, + + /// + /// Wix. + /// + Wix, + + /// + /// Msi. + /// + Msi, + + /// + /// Nullsoft. + /// + Nullsoft, + + /// + /// Zip. + /// + Zip, + + /// + /// Msix. + /// + Msix, + + /// + /// Exe. + /// + Exe, + + /// + /// Burn. + /// + Burn, + + /// + /// MSStore, + /// + MSStore, + + /// + /// Portable. + /// + Portable, + } } \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs index 445378fa26..fa66460e12 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageRepairMode.cs @@ -1,29 +1,29 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// Must match Microsoft.Management.Deployment.PackageRepairMode. - /// - public enum PSPackageRepairMode - { - /// - /// Default. - /// - Default, - - /// - /// Silent. - /// - Silent, - - /// - /// Interactive. - /// - Interactive, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// Must match Microsoft.Management.Deployment.PackageRepairMode. + /// + public enum PSPackageRepairMode + { + /// + /// Default. + /// + Default, + + /// + /// Silent. + /// + Silent, + + /// + /// Interactive. + /// + Interactive, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageUninstallMode.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageUninstallMode.cs index e71200e959..eeae576d46 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageUninstallMode.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSPackageUninstallMode.cs @@ -1,29 +1,29 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// Must match Microsoft.Management.Deployment.PackageUninstallMode. - /// - public enum PSPackageUninstallMode - { - /// - /// Default. - /// - Default, - - /// - /// Silent. - /// - Silent, - - /// - /// Interactive. - /// - Interactive, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// Must match Microsoft.Management.Deployment.PackageUninstallMode. + /// + public enum PSPackageUninstallMode + { + /// + /// Default. + /// + Default, + + /// + /// Silent. + /// + Silent, + + /// + /// Interactive. + /// + Interactive, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSProcessorArchitecture.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSProcessorArchitecture.cs index 0ee707f995..07cb97dc02 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSProcessorArchitecture.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSProcessorArchitecture.cs @@ -1,39 +1,39 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// The processor architecture of the package to install. - /// - public enum PSProcessorArchitecture - { - /// - /// Let winget decide. - /// - Default, - - /// - /// x86, - /// - X86, - - /// - /// ARM. - /// - Arm, - - /// - /// x64. - /// - X64, - - /// - /// Arm64 - /// - Arm64, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// The processor architecture of the package to install. + /// + public enum PSProcessorArchitecture + { + /// + /// Let winget decide. + /// + Default, + + /// + /// x86, + /// + X86, + + /// + /// ARM. + /// + Arm, + + /// + /// x64. + /// + X64, + + /// + /// Arm64 + /// + Arm64, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSWindowsPlatform.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSWindowsPlatform.cs index 1f6abb2881..5f2e4acfc9 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSWindowsPlatform.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/PSObjects/PSWindowsPlatform.cs @@ -1,44 +1,44 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.PSObjects -{ - /// - /// The Windows platform type. - /// - public enum PSWindowsPlatform - { - /// - /// Let winget decide. - /// - Default, - - /// - /// Windows.Universal - /// - Universal, - - /// - /// Windows.Desktop - /// - Desktop, - - /// - /// Windows.IoT - /// - IoT, - - /// - /// Windows.Team - /// - Team, - - /// - /// Windows.Holographic - /// - Holographic, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.PSObjects +{ + /// + /// The Windows platform type. + /// + public enum PSWindowsPlatform + { + /// + /// Let winget decide. + /// + Default, + + /// + /// Windows.Universal + /// + Universal, + + /// + /// Windows.Desktop + /// + Desktop, + + /// + /// Windows.IoT + /// + IoT, + + /// + /// Windows.Team + /// + Team, + + /// + /// Windows.Holographic + /// + Holographic, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemoveSourceCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemoveSourceCmdlet.cs index cf30a7d016..ddb65dcacc 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemoveSourceCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RemoveSourceCmdlet.cs @@ -1,39 +1,39 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Removes a source. Requires admin. - /// - [Cmdlet(VerbsCommon.Remove, Constants.WinGetNouns.Source)] - [Alias("rwgs")] - public sealed class RemoveSourceCmdlet : PSCmdlet - { - /// - /// Gets or sets the name of the source to remove. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public string Name { get; set; } - - /// - /// Removes source. - /// - protected override void ProcessRecord() - { - var command = new CliCommand(this); - command.RemoveSource(this.Name); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Removes a source. Requires admin. + /// + [Cmdlet(VerbsCommon.Remove, Constants.WinGetNouns.Source)] + [Alias("rwgs")] + public sealed class RemoveSourceCmdlet : PSCmdlet + { + /// + /// Gets or sets the name of the source to remove. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Removes source. + /// + protected override void ProcessRecord() + { + var command = new CliCommand(this); + command.RemoveSource(this.Name); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs index 49e5bba479..4dc8b7b7b5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairPackageCmdlet.cs @@ -1,84 +1,84 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Client.PSObjects; - - /// - /// This class defines the repair package command. - /// - [Cmdlet( - VerbsDiagnostic.Repair, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [OutputType(typeof(PSRepairResult))] - public sealed class RepairPackageCmdlet : PackageCmdlet - { - private RepairPackageCommand command = null; - - /// - /// Gets or sets the desired mode for the repair process. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public PSPackageRepairMode Mode { get; set; } = PSPackageRepairMode.Default; - - /// - /// Gets or sets the path to the logging file. - /// - [Parameter] - public string Log { get; set; } - - /// - /// Gets or sets a value indicating whether to skip the installer hash validation check. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AllowHashMismatch { get; set; } - - /// - /// Gets or sets a value indicating whether to continue upon non security related failures. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } - - /// - /// Repairs a package from the local system. - /// - protected override void ProcessRecord() - { - this.command = new RepairPackageCommand( - this, - this.AllowHashMismatch.ToBool(), - this.Force.ToBool(), - this.PSCatalogPackage, - this.Version, - this.Log, - this.Id, - this.Name, - this.Moniker, - this.Source, - this.Query); - this.command.Repair(this.MatchOption.ToString(), this.Mode.ToString()); - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.command != null) - { - this.command.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Client.PSObjects; + + /// + /// This class defines the repair package command. + /// + [Cmdlet( + VerbsDiagnostic.Repair, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [OutputType(typeof(PSRepairResult))] + public sealed class RepairPackageCmdlet : PackageCmdlet + { + private RepairPackageCommand command = null; + + /// + /// Gets or sets the desired mode for the repair process. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageRepairMode Mode { get; set; } = PSPackageRepairMode.Default; + + /// + /// Gets or sets the path to the logging file. + /// + [Parameter] + public string Log { get; set; } + + /// + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AllowHashMismatch { get; set; } + + /// + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// + /// Repairs a package from the local system. + /// + protected override void ProcessRecord() + { + this.command = new RepairPackageCommand( + this, + this.AllowHashMismatch.ToBool(), + this.Force.ToBool(), + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query); + this.command.Repair(this.MatchOption.ToString(), this.Mode.ToString()); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs index d60bc088e9..76b94ca438 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/RepairWinGetPackageManagerCmdlet.cs @@ -1,67 +1,67 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Repair-WinGetPackageManager. Repairs winget if needed. - /// - [Cmdlet( - VerbsDiagnostic.Repair, - Constants.WinGetNouns.WinGetPackageManager, - DefaultParameterSetName = Constants.IntegrityVersionSet)] - [Alias("rpwgpm")] - [OutputType(typeof(int))] - public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet - { - private WinGetPackageManagerCommand command = null; - - /// - /// Gets or sets a value indicating whether to repair for all users. Requires admin. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AllUsers { get; set; } - - /// - /// Gets or sets a value indicating whether to force application shutdown. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } - - /// - /// Attempts to repair winget. - /// TODO: consider WhatIf and Confirm options. - /// - protected override void ProcessRecord() - { - this.command = new WinGetPackageManagerCommand(this); - if (this.ParameterSetName == Constants.IntegrityLatestSet) - { - this.command.RepairUsingLatest(this.IncludePrerelease.ToBool(), this.AllUsers.ToBool(), this.Force.ToBool()); - } - else - { - this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool(), this.IncludePrerelease.ToBool()); - } - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.command != null) - { - this.command.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Repair-WinGetPackageManager. Repairs winget if needed. + /// + [Cmdlet( + VerbsDiagnostic.Repair, + Constants.WinGetNouns.WinGetPackageManager, + DefaultParameterSetName = Constants.IntegrityVersionSet)] + [Alias("rpwgpm")] + [OutputType(typeof(int))] + public class RepairWinGetPackageManagerCmdlet : WinGetPackageManagerCmdlet + { + private WinGetPackageManagerCommand command = null; + + /// + /// Gets or sets a value indicating whether to repair for all users. Requires admin. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AllUsers { get; set; } + + /// + /// Gets or sets a value indicating whether to force application shutdown. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// + /// Attempts to repair winget. + /// TODO: consider WhatIf and Confirm options. + /// + protected override void ProcessRecord() + { + this.command = new WinGetPackageManagerCommand(this); + if (this.ParameterSetName == Constants.IntegrityLatestSet) + { + this.command.RepairUsingLatest(this.IncludePrerelease.ToBool(), this.AllUsers.ToBool(), this.Force.ToBool()); + } + else + { + this.command.Repair(this.Version, this.AllUsers.ToBool(), this.Force.ToBool(), this.IncludePrerelease.ToBool()); + } + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetSourceCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetSourceCmdlet.cs index 1dfdcee6ad..e9c9f414f2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetSourceCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/ResetSourceCmdlet.cs @@ -1,54 +1,54 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - - /// - /// Resets a source. Requires admin. - /// - [Cmdlet(VerbsCommon.Reset, Constants.WinGetNouns.Source, DefaultParameterSetName = Constants.DefaultSet)] - [Alias("rswgs")] - public sealed class ResetSourceCmdlet : PSCmdlet - { - /// - /// Gets or sets the name of the source to reset. - /// - [Parameter( - Position = 0, - Mandatory = true, - ParameterSetName = Constants.DefaultSet, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public string Name { get; set; } - - /// - /// Gets or sets a value indicating whether to reset all sources. - /// - [Parameter(ParameterSetName = Constants.OptionalSet, ValueFromPipelineByPropertyName = true)] - public SwitchParameter All { get; set; } - - /// - /// Resets source. - /// - protected override void ProcessRecord() - { - var command = new CliCommand(this); - - if (!string.IsNullOrEmpty(this.Name)) - { - command.ResetSourceByName(this.Name); - } - else if (this.All) - { - command.ResetAllSources(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Cmdlets.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + + /// + /// Resets a source. Requires admin. + /// + [Cmdlet(VerbsCommon.Reset, Constants.WinGetNouns.Source, DefaultParameterSetName = Constants.DefaultSet)] + [Alias("rswgs")] + public sealed class ResetSourceCmdlet : PSCmdlet + { + /// + /// Gets or sets the name of the source to reset. + /// + [Parameter( + Position = 0, + Mandatory = true, + ParameterSetName = Constants.DefaultSet, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public string Name { get; set; } + + /// + /// Gets or sets a value indicating whether to reset all sources. + /// + [Parameter(ParameterSetName = Constants.OptionalSet, ValueFromPipelineByPropertyName = true)] + public SwitchParameter All { get; set; } + + /// + /// Resets source. + /// + protected override void ProcessRecord() + { + var command = new CliCommand(this); + + if (!string.IsNullOrEmpty(this.Name)) + { + command.ResetSourceByName(this.Name); + } + else if (this.All) + { + command.ResetAllSources(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs index 46518a7d2d..bf61b6de02 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UninstallPackageCmdlet.cs @@ -1,77 +1,77 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Client.PSObjects; - - /// - /// Uninstalls a package from the local system. - /// - [Cmdlet( - VerbsLifecycle.Uninstall, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [Alias("uswgp")] - [OutputType(typeof(PSUninstallResult))] - public sealed class UninstallPackageCmdlet : PackageCmdlet - { - private UninstallPackageCommand command = null; - - /// - /// Gets or sets the desired mode for the uninstallation process. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public PSPackageUninstallMode Mode { get; set; } = PSPackageUninstallMode.Default; - - /// - /// Gets or sets a value indicating whether to continue upon non security related failures. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter Force { get; set; } - - /// - /// Gets or sets the path to the logging file. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public string Log { get; set; } - - /// - /// Uninstalls a package from the local system. - /// - protected override void ProcessRecord() - { - this.command = new UninstallPackageCommand( - this, - this.PSCatalogPackage, - this.Version, - this.Log, - this.Id, - this.Name, - this.Moniker, - this.Source, - this.Query); - this.command.Uninstall(this.MatchOption.ToString(), this.Mode.ToString(), this.Force.ToBool()); - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.command != null) - { - this.command.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Client.PSObjects; + + /// + /// Uninstalls a package from the local system. + /// + [Cmdlet( + VerbsLifecycle.Uninstall, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [Alias("uswgp")] + [OutputType(typeof(PSUninstallResult))] + public sealed class UninstallPackageCmdlet : PackageCmdlet + { + private UninstallPackageCommand command = null; + + /// + /// Gets or sets the desired mode for the uninstallation process. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public PSPackageUninstallMode Mode { get; set; } = PSPackageUninstallMode.Default; + + /// + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter Force { get; set; } + + /// + /// Gets or sets the path to the logging file. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string Log { get; set; } + + /// + /// Uninstalls a package from the local system. + /// + protected override void ProcessRecord() + { + this.command = new UninstallPackageCommand( + this, + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query); + this.command.Uninstall(this.MatchOption.ToString(), this.Mode.ToString(), this.Force.ToBool()); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.command != null) + { + this.command.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs index bdcf9832d3..d6cedc0b9b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Cmdlets/UpdatePackageCmdlet.cs @@ -1,58 +1,58 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Commands.Common; - using Microsoft.WinGet.Client.Common; - using Microsoft.WinGet.Client.Engine.Commands; - using Microsoft.WinGet.Client.Engine.PSObjects; - - /// - /// This commands updates a package from the pipeline or from the local system. - /// - [Cmdlet( - VerbsData.Update, - Constants.WinGetNouns.Package, - DefaultParameterSetName = Constants.FoundSet, - SupportsShouldProcess = true)] - [Alias("udwgp")] - [OutputType(typeof(PSInstallResult))] - public sealed class UpdatePackageCmdlet : InstallCmdlet - { - /// - /// Gets or sets a value indicating whether updating to an unknown version is allowed. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter IncludeUnknown { get; set; } - - /// - /// Updates a package from the pipeline or from the local system. - /// - protected override void ProcessRecord() - { - var command = new InstallerPackageCommand( - this, - this.Override, - this.Custom, - this.Location, - this.AllowHashMismatch.ToBool(), - this.Force.ToBool(), - this.Header, - this.PSCatalogPackage, - this.Version, - this.Log, - this.Id, - this.Name, - this.Moniker, - this.Source, - this.Query, - this.SkipDependencies); - command.Update(this.IncludeUnknown.ToBool(), this.MatchOption.ToString(), this.Scope.ToString(), this.Architecture.ToString(), this.Mode.ToString(), this.InstallerType.ToString()); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Commands.Common; + using Microsoft.WinGet.Client.Common; + using Microsoft.WinGet.Client.Engine.Commands; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// This commands updates a package from the pipeline or from the local system. + /// + [Cmdlet( + VerbsData.Update, + Constants.WinGetNouns.Package, + DefaultParameterSetName = Constants.FoundSet, + SupportsShouldProcess = true)] + [Alias("udwgp")] + [OutputType(typeof(PSInstallResult))] + public sealed class UpdatePackageCmdlet : InstallCmdlet + { + /// + /// Gets or sets a value indicating whether updating to an unknown version is allowed. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter IncludeUnknown { get; set; } + + /// + /// Updates a package from the pipeline or from the local system. + /// + protected override void ProcessRecord() + { + var command = new InstallerPackageCommand( + this, + this.Override, + this.Custom, + this.Location, + this.AllowHashMismatch.ToBool(), + this.Force.ToBool(), + this.Header, + this.PSCatalogPackage, + this.Version, + this.Log, + this.Id, + this.Name, + this.Moniker, + this.Source, + this.Query, + this.SkipDependencies); + command.Update(this.IncludeUnknown.ToBool(), this.MatchOption.ToString(), this.Scope.ToString(), this.Architecture.ToString(), this.Mode.ToString(), this.InstallerType.ToString()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs index 62847c6567..c2b203e0ed 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Common/Constants.cs @@ -30,16 +30,16 @@ internal static class Constants /// This parameter set indicates that a package was not provided via a parameter or the pipeline and it /// needs to be found by searching a package source. /// - public const string FoundSet = "FoundSet"; - - /// - /// This parameter set indicates the default parameters associated with a cmdlet. - /// - public const string DefaultSet = "DefaultSet"; - - /// - /// This parameter set indicates the optional parameters associated with a cmdlet. - /// + public const string FoundSet = "FoundSet"; + + /// + /// This parameter set indicates the default parameters associated with a cmdlet. + /// + public const string DefaultSet = "DefaultSet"; + + /// + /// This parameter set indicates the optional parameters associated with a cmdlet. + /// public const string OptionalSet = "OptionalSet"; /// 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 951b362f6c..abda34d2da 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 @@ -1,118 +1,118 @@ - - - - - - net8.0-windows10.0.26100.0 - false - net48 - Debug;Release;ReleaseStatic - - - - true - 10 - $(SolutionDir)$(Platform)\$(Configuration)\ - $(BuildOutputDirectory)$(MSBuildProjectName) - $(CoreFramework);$(DesktopFramework) - $(OutputPath)\Microsoft.WinGet.Client.Cmdlets.xml - - - - - - - - true - - - - true - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - POWERSHELL_WINDOWS - - - - win - - - - - - - - - - - None - - - - - $(BuildOutputDirectory)PowerShell\Microsoft.WinGet.Client - $(PowerShellModuleOutputDirectory)\$(TargetFramework) - - - - $(PowerShellModuleRuntimesDir)\SharedDependencies - $(PowerShellModuleRuntimesDir)\DirectDependencies - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + net8.0-windows10.0.26100.0 + false + net48 + Debug;Release;ReleaseStatic + + + + true + 10 + $(SolutionDir)$(Platform)\$(Configuration)\ + $(BuildOutputDirectory)$(MSBuildProjectName) + $(CoreFramework);$(DesktopFramework) + $(OutputPath)\Microsoft.WinGet.Client.Cmdlets.xml + + + + + + + + true + + + + true + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + POWERSHELL_WINDOWS + + + + win + + + + + + + + + + + None + + + + + $(BuildOutputDirectory)PowerShell\Microsoft.WinGet.Client + $(PowerShellModuleOutputDirectory)\$(TargetFramework) + + + + $(PowerShellModuleRuntimesDir)\SharedDependencies + $(PowerShellModuleRuntimesDir)\DirectDependencies + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Properties/AssemblyInfo.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Properties/AssemblyInfo.cs index d9d343a224..bf84c41824 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Properties/AssemblyInfo.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Properties/AssemblyInfo.cs @@ -1,16 +1,16 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -#if NET - -using System.Runtime.Versioning; - -// Forcibly set the target and supported platforms due to the internal build setup. -// Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.26100.0")] -[assembly: SupportedOSPlatform("Windows10.0.18362.0")] - -#endif +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +#if NET + +using System.Runtime.Versioning; + +// Forcibly set the target and supported platforms due to the internal build setup. +// Keep in sync with project versions. +[assembly: TargetPlatform("Windows10.0.26100.0")] +[assembly: SupportedOSPlatform("Windows10.0.18362.0")] + +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs index 98a75e89f7..3d7d4acaac 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/ModuleInit.cs @@ -1,58 +1,58 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Resolver -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using System.Runtime.InteropServices; - -#if !POWERSHELL_WINDOWS - using System.Runtime.Loader; -#else - using Microsoft.WinGet.Client.Cmdlets.Resolver; -#endif - - /// - /// Initialization class for this module. - /// - public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup - { - private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64, Architecture.Arm64 }; - - /// - public void OnImport() - { - var arch = RuntimeInformation.ProcessArchitecture; - if (!ValidArchs.Contains(arch)) - { - throw new NotSupportedException(arch.ToString()); - } - -#if !POWERSHELL_WINDOWS - AssemblyLoadContext.Default.Resolving += WinGetAssemblyLoadContext.ResolvingHandler; -#else - // If we really need to avoid dependency conflicts, we could create a custom domain and handle the serialization boundaries. - // PowerShell doesn't recommended because its complications. - AppDomain currentDomain = AppDomain.CurrentDomain; - currentDomain.AssemblyResolve += WinGetAppDomain.Handler; -#endif - } - - /// - public void OnRemove(PSModuleInfo module) - { -#if !POWERSHELL_WINDOWS - AssemblyLoadContext.Default.Resolving -= WinGetAssemblyLoadContext.ResolvingHandler; -#else - AppDomain currentDomain = AppDomain.CurrentDomain; - currentDomain.AssemblyResolve -= WinGetAppDomain.Handler; -#endif - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Resolver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using System.Runtime.InteropServices; + +#if !POWERSHELL_WINDOWS + using System.Runtime.Loader; +#else + using Microsoft.WinGet.Client.Cmdlets.Resolver; +#endif + + /// + /// Initialization class for this module. + /// + public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup + { + private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64, Architecture.Arm64 }; + + /// + public void OnImport() + { + var arch = RuntimeInformation.ProcessArchitecture; + if (!ValidArchs.Contains(arch)) + { + throw new NotSupportedException(arch.ToString()); + } + +#if !POWERSHELL_WINDOWS + AssemblyLoadContext.Default.Resolving += WinGetAssemblyLoadContext.ResolvingHandler; +#else + // If we really need to avoid dependency conflicts, we could create a custom domain and handle the serialization boundaries. + // PowerShell doesn't recommended because its complications. + AppDomain currentDomain = AppDomain.CurrentDomain; + currentDomain.AssemblyResolve += WinGetAppDomain.Handler; +#endif + } + + /// + public void OnRemove(PSModuleInfo module) + { +#if !POWERSHELL_WINDOWS + AssemblyLoadContext.Default.Resolving -= WinGetAssemblyLoadContext.ResolvingHandler; +#else + AppDomain currentDomain = AppDomain.CurrentDomain; + currentDomain.AssemblyResolve -= WinGetAppDomain.Handler; +#endif + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs index cb89b0a6a4..b3002b37a3 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Cmdlets/Resolver/WinGetAppDomain.cs @@ -1,69 +1,69 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- -#if POWERSHELL_WINDOWS - -namespace Microsoft.WinGet.Client.Cmdlets.Resolver -{ - using System; - using System.IO; - using System.Reflection; - using System.Runtime.InteropServices; - - /// - /// Resolver for assemblies. - /// - internal static class WinGetAppDomain - { - private static readonly string SharedDependencyPath; - private static readonly string SharedArchDependencyPath; - private static readonly string DirectDependencyPath; - - static WinGetAppDomain() - { - var self = typeof(WinGetAppDomain).Assembly; - SharedDependencyPath = Path.Combine( - Path.GetDirectoryName(self.Location), - "SharedDependencies"); - SharedArchDependencyPath = Path.Combine( - SharedDependencyPath, - RuntimeInformation.ProcessArchitecture.ToString().ToLower()); - DirectDependencyPath = Path.Combine( - Path.GetDirectoryName(self.Location), - "DirectDependencies"); - } - - /// - /// Handler to register in the AppDomain. - /// - /// Source. - /// Event args. - /// The assembly if found. - public static Assembly Handler(object source, ResolveEventArgs args) - { - string name = $"{new AssemblyName(args.Name).Name}.dll"; - string path = Path.Combine(SharedDependencyPath, name); - if (File.Exists(path)) - { - return Assembly.LoadFile(path); - } - - path = Path.Combine(SharedArchDependencyPath, name); - if (File.Exists(path)) - { - return Assembly.LoadFile(path); - } - - path = Path.Combine(DirectDependencyPath, name); - if (File.Exists(path)) - { - return Assembly.LoadFile(path); - } - - return null; - } - } -} -#endif +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +#if POWERSHELL_WINDOWS + +namespace Microsoft.WinGet.Client.Cmdlets.Resolver +{ + using System; + using System.IO; + using System.Reflection; + using System.Runtime.InteropServices; + + /// + /// Resolver for assemblies. + /// + internal static class WinGetAppDomain + { + private static readonly string SharedDependencyPath; + private static readonly string SharedArchDependencyPath; + private static readonly string DirectDependencyPath; + + static WinGetAppDomain() + { + var self = typeof(WinGetAppDomain).Assembly; + SharedDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "SharedDependencies"); + SharedArchDependencyPath = Path.Combine( + SharedDependencyPath, + RuntimeInformation.ProcessArchitecture.ToString().ToLower()); + DirectDependencyPath = Path.Combine( + Path.GetDirectoryName(self.Location), + "DirectDependencies"); + } + + /// + /// Handler to register in the AppDomain. + /// + /// Source. + /// Event args. + /// The assembly if found. + public static Assembly Handler(object source, ResolveEventArgs args) + { + string name = $"{new AssemblyName(args.Name).Name}.dll"; + string path = Path.Combine(SharedDependencyPath, name); + if (File.Exists(path)) + { + return Assembly.LoadFile(path); + } + + path = Path.Combine(SharedArchDependencyPath, name); + if (File.Exists(path)) + { + return Assembly.LoadFile(path); + } + + path = Path.Combine(DirectDependencyPath, name); + if (File.Exists(path)) + { + return Assembly.LoadFile(path); + } + + return null; + } + } +} +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Attributes/FilterAttribute.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Attributes/FilterAttribute.cs index 519aa50636..0a0f2a4582 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Attributes/FilterAttribute.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Attributes/FilterAttribute.cs @@ -1,25 +1,25 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Attributes -{ - using System; - using Microsoft.Management.Deployment; - - /// - /// A is constructed by introspecting on the inheritance tree and - /// looking for parameters that are marked with this attribute. Properties that are marked with this - /// attribute are added to the object. - /// - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - internal class FilterAttribute : Attribute - { - /// - /// Gets or sets the field that the filter will be matching against. - /// - public PackageMatchField Field { get; set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Attributes +{ + using System; + using Microsoft.Management.Deployment; + + /// + /// A is constructed by introspecting on the inheritance tree and + /// looking for parameters that are marked with this attribute. Properties that are marked with this + /// attribute are added to the object. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + internal class FilterAttribute : Attribute + { + /// + /// Gets or sets the field that the filter will be matching against. + /// + public PackageMatchField Field { get; set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs index 270e4e0e5f..577a0bedba 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/CliCommand.cs @@ -1,145 +1,145 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Common.Command; - - /// - /// Commands that just calls winget.exe underneath. - /// - public sealed class CliCommand : BaseCommand - { - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - public CliCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Enables admin setting. - /// - /// Setting name. - public void EnableSetting(string name) - { - Utilities.VerifyAdmin(); - _ = this.Run(new WinGetCLICommandBuilder("settings").AppendOption("enable", name)); - } - - /// - /// Disables admin setting. - /// - /// Setting name. - public void DisableSetting(string name) - { - Utilities.VerifyAdmin(); - _ = this.Run(new WinGetCLICommandBuilder("settings").AppendOption("disable", name)); - } - - /// - /// Gets winget settings. - /// - /// Return as string. - public void GetSettings(bool asPlainText) - { - var result = this.Run(new WinGetCLICommandBuilder("settings").AppendSubCommand("export")); - - if (asPlainText) - { - this.Write(StreamType.Object, result.StdOut); - } - else - { - this.Write(StreamType.Object, Utilities.ConvertToHashtable(result.StdOut)); - } - } - - /// - /// Adds source. - /// - /// Name of source. - /// Arg of source. - /// Type of source. - /// Trust level of source. - /// Make source explicit. - /// Set the priority if the source. - public void AddSource(string name, string arg, string type, string trustLevel, bool isExplicit, int priority) - { - Utilities.VerifyAdmin(); - var builder = new WinGetCLICommandBuilder("source") - .AppendSubCommand("add") - .AppendOption("name", name) - .AppendOption("arg", arg); - - if (!string.IsNullOrEmpty(type)) - { - builder.AppendOption("type", type); - } - - if (!string.IsNullOrEmpty(trustLevel)) - { - builder.AppendOption("trust-level", trustLevel); - } - - if (isExplicit) - { - builder.AppendSwitch("explicit"); - } - - if (priority != 0) - { - builder.AppendOption("priority", priority.ToString()); - } - - _ = this.Run(builder, 300000); - } - - /// - /// Removes source. - /// - /// Name of source. - public void RemoveSource(string name) - { - Utilities.VerifyAdmin(); - _ = this.Run(new WinGetCLICommandBuilder("source").AppendSubCommand("remove").AppendOption("name", name)); - } - - /// - /// Resets a source. - /// - /// Name of source. - public void ResetSourceByName(string name) - { - Utilities.VerifyAdmin(); - _ = this.Run(new WinGetCLICommandBuilder("source").AppendSubCommand("reset").AppendOption("name", name).AppendSwitch("force")); - } - - /// - /// Resets all sources and adds the defaults. - /// - public void ResetAllSources() - { - Utilities.VerifyAdmin(); - _ = this.Run(new WinGetCLICommandBuilder("source").AppendSubCommand("reset").AppendSwitch("force")); - } - - private WinGetCLICommandResult Run(WinGetCLICommandBuilder builder, int timeOut = 60000) - { - var wingetCliWrapper = new WingetCLIWrapper(); - var result = wingetCliWrapper.RunCommand(this, builder, timeOut); - result.VerifyExitCode(); - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Common.Command; + + /// + /// Commands that just calls winget.exe underneath. + /// + public sealed class CliCommand : BaseCommand + { + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + public CliCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Enables admin setting. + /// + /// Setting name. + public void EnableSetting(string name) + { + Utilities.VerifyAdmin(); + _ = this.Run(new WinGetCLICommandBuilder("settings").AppendOption("enable", name)); + } + + /// + /// Disables admin setting. + /// + /// Setting name. + public void DisableSetting(string name) + { + Utilities.VerifyAdmin(); + _ = this.Run(new WinGetCLICommandBuilder("settings").AppendOption("disable", name)); + } + + /// + /// Gets winget settings. + /// + /// Return as string. + public void GetSettings(bool asPlainText) + { + var result = this.Run(new WinGetCLICommandBuilder("settings").AppendSubCommand("export")); + + if (asPlainText) + { + this.Write(StreamType.Object, result.StdOut); + } + else + { + this.Write(StreamType.Object, Utilities.ConvertToHashtable(result.StdOut)); + } + } + + /// + /// Adds source. + /// + /// Name of source. + /// Arg of source. + /// Type of source. + /// Trust level of source. + /// Make source explicit. + /// Set the priority if the source. + public void AddSource(string name, string arg, string type, string trustLevel, bool isExplicit, int priority) + { + Utilities.VerifyAdmin(); + var builder = new WinGetCLICommandBuilder("source") + .AppendSubCommand("add") + .AppendOption("name", name) + .AppendOption("arg", arg); + + if (!string.IsNullOrEmpty(type)) + { + builder.AppendOption("type", type); + } + + if (!string.IsNullOrEmpty(trustLevel)) + { + builder.AppendOption("trust-level", trustLevel); + } + + if (isExplicit) + { + builder.AppendSwitch("explicit"); + } + + if (priority != 0) + { + builder.AppendOption("priority", priority.ToString()); + } + + _ = this.Run(builder, 300000); + } + + /// + /// Removes source. + /// + /// Name of source. + public void RemoveSource(string name) + { + Utilities.VerifyAdmin(); + _ = this.Run(new WinGetCLICommandBuilder("source").AppendSubCommand("remove").AppendOption("name", name)); + } + + /// + /// Resets a source. + /// + /// Name of source. + public void ResetSourceByName(string name) + { + Utilities.VerifyAdmin(); + _ = this.Run(new WinGetCLICommandBuilder("source").AppendSubCommand("reset").AppendOption("name", name).AppendSwitch("force")); + } + + /// + /// Resets all sources and adds the defaults. + /// + public void ResetAllSources() + { + Utilities.VerifyAdmin(); + _ = this.Run(new WinGetCLICommandBuilder("source").AppendSubCommand("reset").AppendSwitch("force")); + } + + private WinGetCLICommandResult Run(WinGetCLICommandBuilder builder, int timeOut = 60000) + { + var wingetCliWrapper = new WingetCLIWrapper(); + var result = wingetCliWrapper.RunCommand(this, builder, timeOut); + result.VerifyExitCode(); + + return result; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/BaseCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/BaseCommand.cs index 40ab1d3f25..49c5f2b40f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/BaseCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/BaseCommand.cs @@ -1,28 +1,28 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands.Common -{ - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.SharedLib.PolicySettings; - - /// - /// Base class for all Cmdlets. - /// - public abstract class BaseCommand : PowerShellCmdlet - { - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - internal BaseCommand(PSCmdlet psCmdlet) - : base(psCmdlet, new HashSet { Policy.WinGet, Policy.WinGetCommandLineInterfaces }) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.SharedLib.PolicySettings; + + /// + /// Base class for all Cmdlets. + /// + public abstract class BaseCommand : PowerShellCmdlet + { + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + internal BaseCommand(PSCmdlet psCmdlet) + : base(psCmdlet, new HashSet { Policy.WinGet, Policy.WinGetCommandLineInterfaces }) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderCommand.cs index 445690b248..16cf70686e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderCommand.cs @@ -1,214 +1,214 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands.Common -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using System.Reflection; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Attributes; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Helpers; - - /// - /// This is the base class for all commands that might need to search for a package. It contains an initial - /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. - /// - public abstract class FinderCommand : ManagementDeploymentCommand - { - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - internal FinderCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Gets or sets the field that is matched against the identifier of a package. - /// - [Filter(Field = PackageMatchField.Id)] - protected string? Id { get; set; } - - /// - /// Gets or sets the field that is matched against the name of a package. - /// - [Filter(Field = PackageMatchField.Name)] - protected string? Name { get; set; } - - /// - /// Gets or sets the field that is matched against the moniker of a package. - /// - [Filter(Field = PackageMatchField.Moniker)] - protected string? Moniker { get; set; } - - /// - /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. - /// - protected string? Source { get; set; } - - /// - /// Gets or sets how to match against package fields. - /// -#pragma warning disable SA1011 // Closing square brackets should be spaced correctly - protected string[]? Query { get; set; } -#pragma warning restore SA1011 // Closing square brackets should be spaced correctly - - private string? QueryAsJoinedString - { - get - { - return this.Query is null - ? null - : string.Join(" ", this.Query); - } - } - - /// - /// Searches for packages based on the configured parameters. - /// - /// The value. - /// The limit on the number of matches returned. - /// The match option. - /// A list of objects. - internal IReadOnlyList FindPackages( - CompositeSearchBehavior behavior, - uint limit, - PackageFieldMatchOption match) - { - PackageCatalog catalog = this.GetPackageCatalog(behavior); - FindPackagesOptions options = this.GetFindPackagesOptions(limit, match); - return this.GetMatchResults(catalog, options); - } - - /// - /// Sets the find package options for a query input. - /// DO NOT pass PackageFieldMatchOption WinRT enum type in this method. - /// That will cause the type to attempt to be loaded in the construction - /// of this method and throw a different exception for Windows PowerShell. - /// - /// The options object. - /// The match type as string. - /// The query value. - internal virtual void SetQueryInFindPackagesOptions( - ref FindPackagesOptions options, - string match, - string? value) - { - var selector = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); - selector.Field = PackageMatchField.CatalogDefault; - selector.Value = value ?? string.Empty; - selector.Option = PSEnumHelpers.ToPackageFieldMatchOption(match); - options.Selectors.Add(selector); - } - - private void AddFilterToFindPackagesOptionsIfNotNull( - ref FindPackagesOptions options, - PackageMatchField field, - PackageFieldMatchOption match, - string? value) - { - if (value != null) - { - var filter = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); - filter.Field = field; - filter.Value = value; - filter.Option = match; - options.Filters.Add(filter); - } - } - - private IReadOnlyList GetMatchResults( - PackageCatalog catalog, - FindPackagesOptions options) - { - FindPackagesResult result = catalog.FindPackages(options); - if (result.Status == FindPackagesResultStatus.Ok) - { - return result.Matches; - } - else - { - throw new FindPackagesException(result.Status); - } - } - - private PackageCatalog GetPackageCatalog(CompositeSearchBehavior behavior) - { - PackageCatalogReference reference = this.GetPackageCatalogReference(behavior); - ConnectResult result = reference.Connect(); - if (result.Status == ConnectResultStatus.Ok) - { - return result.PackageCatalog; - } - else - { - throw new CatalogConnectException(result.ExtendedErrorCode); - } - } - - private PackageCatalogReference GetPackageCatalogReference(CompositeSearchBehavior behavior) - { - CreateCompositePackageCatalogOptions options = ManagementDeploymentFactory.Instance.CreateCreateCompositePackageCatalogOptions(); - IReadOnlyList references = this.GetPackageCatalogReferences(this.Source); - for (var i = 0; i < references.Count; i++) - { - var reference = references[i]; - bool isExplicit = false; - try - { - // Execute in try block to catch interface not implemented on older servers. - isExplicit = reference.Info.Explicit; - } - catch - { - // Assume that any failure other than the interface not implemented to get Explicit - // will result in other failures shortly after this (like the server being gone). - } - - if (!isExplicit) - { - options.Catalogs.Add(reference); - } - } - - options.CompositeSearchBehavior = behavior; - return PackageManagerWrapper.Instance.CreateCompositePackageCatalog(options); - } - - private FindPackagesOptions GetFindPackagesOptions(uint limit, PackageFieldMatchOption match) - { - var options = ManagementDeploymentFactory.Instance.CreateFindPackagesOptions(); - this.SetQueryInFindPackagesOptions(ref options, match.ToString(), this.QueryAsJoinedString); - this.AddAttributedFiltersToFindPackagesOptions(ref options, match); - options.ResultLimit = limit; - return options; - } - - private void AddAttributedFiltersToFindPackagesOptions( - ref FindPackagesOptions options, - PackageFieldMatchOption match) - { - IEnumerable properties = this.GetType() - .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) - .Where(property => Attribute.IsDefined(property, typeof(FilterAttribute))); - - foreach (PropertyInfo info in properties) - { - if (info.GetCustomAttribute(typeof(FilterAttribute), true) is FilterAttribute attribute) - { - PackageMatchField field = attribute.Field; - string? value = info.GetValue(this, null) as string; - this.AddFilterToFindPackagesOptionsIfNotNull(ref options, field, match, value); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using System.Reflection; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Attributes; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + + /// + /// This is the base class for all commands that might need to search for a package. It contains an initial + /// set of parameters that corresponds to the intersection of i.e., the "install" and "search" commands. + /// + public abstract class FinderCommand : ManagementDeploymentCommand + { + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + internal FinderCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Gets or sets the field that is matched against the identifier of a package. + /// + [Filter(Field = PackageMatchField.Id)] + protected string? Id { get; set; } + + /// + /// Gets or sets the field that is matched against the name of a package. + /// + [Filter(Field = PackageMatchField.Name)] + protected string? Name { get; set; } + + /// + /// Gets or sets the field that is matched against the moniker of a package. + /// + [Filter(Field = PackageMatchField.Moniker)] + protected string? Moniker { get; set; } + + /// + /// Gets or sets the name of the source to search for packages. If null, then all sources are searched. + /// + protected string? Source { get; set; } + + /// + /// Gets or sets how to match against package fields. + /// +#pragma warning disable SA1011 // Closing square brackets should be spaced correctly + protected string[]? Query { get; set; } +#pragma warning restore SA1011 // Closing square brackets should be spaced correctly + + private string? QueryAsJoinedString + { + get + { + return this.Query is null + ? null + : string.Join(" ", this.Query); + } + } + + /// + /// Searches for packages based on the configured parameters. + /// + /// The value. + /// The limit on the number of matches returned. + /// The match option. + /// A list of objects. + internal IReadOnlyList FindPackages( + CompositeSearchBehavior behavior, + uint limit, + PackageFieldMatchOption match) + { + PackageCatalog catalog = this.GetPackageCatalog(behavior); + FindPackagesOptions options = this.GetFindPackagesOptions(limit, match); + return this.GetMatchResults(catalog, options); + } + + /// + /// Sets the find package options for a query input. + /// DO NOT pass PackageFieldMatchOption WinRT enum type in this method. + /// That will cause the type to attempt to be loaded in the construction + /// of this method and throw a different exception for Windows PowerShell. + /// + /// The options object. + /// The match type as string. + /// The query value. + internal virtual void SetQueryInFindPackagesOptions( + ref FindPackagesOptions options, + string match, + string? value) + { + var selector = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); + selector.Field = PackageMatchField.CatalogDefault; + selector.Value = value ?? string.Empty; + selector.Option = PSEnumHelpers.ToPackageFieldMatchOption(match); + options.Selectors.Add(selector); + } + + private void AddFilterToFindPackagesOptionsIfNotNull( + ref FindPackagesOptions options, + PackageMatchField field, + PackageFieldMatchOption match, + string? value) + { + if (value != null) + { + var filter = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); + filter.Field = field; + filter.Value = value; + filter.Option = match; + options.Filters.Add(filter); + } + } + + private IReadOnlyList GetMatchResults( + PackageCatalog catalog, + FindPackagesOptions options) + { + FindPackagesResult result = catalog.FindPackages(options); + if (result.Status == FindPackagesResultStatus.Ok) + { + return result.Matches; + } + else + { + throw new FindPackagesException(result.Status); + } + } + + private PackageCatalog GetPackageCatalog(CompositeSearchBehavior behavior) + { + PackageCatalogReference reference = this.GetPackageCatalogReference(behavior); + ConnectResult result = reference.Connect(); + if (result.Status == ConnectResultStatus.Ok) + { + return result.PackageCatalog; + } + else + { + throw new CatalogConnectException(result.ExtendedErrorCode); + } + } + + private PackageCatalogReference GetPackageCatalogReference(CompositeSearchBehavior behavior) + { + CreateCompositePackageCatalogOptions options = ManagementDeploymentFactory.Instance.CreateCreateCompositePackageCatalogOptions(); + IReadOnlyList references = this.GetPackageCatalogReferences(this.Source); + for (var i = 0; i < references.Count; i++) + { + var reference = references[i]; + bool isExplicit = false; + try + { + // Execute in try block to catch interface not implemented on older servers. + isExplicit = reference.Info.Explicit; + } + catch + { + // Assume that any failure other than the interface not implemented to get Explicit + // will result in other failures shortly after this (like the server being gone). + } + + if (!isExplicit) + { + options.Catalogs.Add(reference); + } + } + + options.CompositeSearchBehavior = behavior; + return PackageManagerWrapper.Instance.CreateCompositePackageCatalog(options); + } + + private FindPackagesOptions GetFindPackagesOptions(uint limit, PackageFieldMatchOption match) + { + var options = ManagementDeploymentFactory.Instance.CreateFindPackagesOptions(); + this.SetQueryInFindPackagesOptions(ref options, match.ToString(), this.QueryAsJoinedString); + this.AddAttributedFiltersToFindPackagesOptions(ref options, match); + options.ResultLimit = limit; + return options; + } + + private void AddAttributedFiltersToFindPackagesOptions( + ref FindPackagesOptions options, + PackageFieldMatchOption match) + { + IEnumerable properties = this.GetType() + .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance) + .Where(property => Attribute.IsDefined(property, typeof(FilterAttribute))); + + foreach (PropertyInfo info in properties) + { + if (info.GetCustomAttribute(typeof(FilterAttribute), true) is FilterAttribute attribute) + { + PackageMatchField field = attribute.Field; + string? value = info.GetValue(this, null) as string; + this.AddFilterToFindPackagesOptionsIfNotNull(ref options, field, match, value); + } + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderExtendedCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderExtendedCommand.cs index 4b3442891b..6539e68f04 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderExtendedCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/FinderExtendedCommand.cs @@ -1,58 +1,58 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands.Common -{ - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Attributes; - - /// - /// This is the base class for the commands whose sole purpose is to filter a list of packages i.e., - /// the "search" and "list" commands. This class contains an extended set of parameters suited for - /// that purpose. - /// - public abstract class FinderExtendedCommand : FinderCommand - { - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - internal FinderExtendedCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Gets or sets the filter that is matched against the tags of the package. - /// - [Filter(Field = PackageMatchField.Tag)] - protected string? Tag { get; set; } - - /// - /// Gets or sets the filter that is matched against the commands of the package. - /// - [Filter(Field = PackageMatchField.Command)] - protected string? Command { get; set; } - - /// - /// Gets or sets the maximum number of results returned. - /// - protected uint Count { get; set; } - - /// - /// Searches for packages from configured sources. - /// - /// A value. - /// The match option. - /// A list of objects. - internal IReadOnlyList FindPackages(CompositeSearchBehavior behavior, PackageFieldMatchOption match) - { - return this.FindPackages(behavior, this.Count, match); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Attributes; + + /// + /// This is the base class for the commands whose sole purpose is to filter a list of packages i.e., + /// the "search" and "list" commands. This class contains an extended set of parameters suited for + /// that purpose. + /// + public abstract class FinderExtendedCommand : FinderCommand + { + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + internal FinderExtendedCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Gets or sets the filter that is matched against the tags of the package. + /// + [Filter(Field = PackageMatchField.Tag)] + protected string? Tag { get; set; } + + /// + /// Gets or sets the filter that is matched against the commands of the package. + /// + [Filter(Field = PackageMatchField.Command)] + protected string? Command { get; set; } + + /// + /// Gets or sets the maximum number of results returned. + /// + protected uint Count { get; set; } + + /// + /// Searches for packages from configured sources. + /// + /// A value. + /// The match option. + /// A list of objects. + internal IReadOnlyList FindPackages(CompositeSearchBehavior behavior, PackageFieldMatchOption match) + { + return this.FindPackages(behavior, this.Count, match); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs index 944f29a1c8..7bcac8d927 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/InstallCommand.cs @@ -1,118 +1,118 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands.Common -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Helpers; - - /// - /// This is the base class for all commands that parse a result - /// from the provided parameters i.e., the "install" and "upgrade" commands. - /// - public abstract class InstallCommand : PackageCommand - { - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - internal InstallCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Gets or sets a value indicating whether to skip the installer hash validation check. - /// - protected bool AllowHashMismatch { get; set; } - - /// - /// Gets or sets a value indicating whether to skip dependencies. - /// - protected bool SkipDependencies { get; set; } - - /// - /// Gets or sets the override arguments to be passed on to the installer. - /// - protected string? Override { get; set; } - - /// - /// Gets or sets the arguments to be passed on to the installer in addition to the defaults. - /// - protected string? Custom { get; set; } - - /// - /// Gets or sets the installation location. - /// - protected string? Location { get; set; } - - /// - /// Gets or sets the path to the logging file. - /// - protected string? Log { get; set; } - - /// - /// Gets or sets a value indicating whether to continue upon non security related failures. - /// - protected bool Force { get; set; } - - /// - /// Gets or sets the optional HTTP Header to pass on to the REST Source. - /// - protected string? Header { get; set; } - - /// - /// Gets the install options from the configured parameters. - /// DO NOT pass PackageInstallMode WinRT enum type in this method. - /// That will cause the type to attempt to be loaded in the construction - /// of this method and throw a different exception for Windows PowerShell. - /// - /// The to install. - /// Package install mode as string. - /// An instance. - internal virtual InstallOptions GetInstallOptions(PackageVersionId? version, string mode) - { - InstallOptions options = ManagementDeploymentFactory.Instance.CreateInstallOptions(); - options.AllowHashMismatch = this.AllowHashMismatch; - options.SkipDependencies = this.SkipDependencies; - options.Force = this.Force; - options.PackageInstallMode = PSEnumHelpers.ToPackageInstallMode(mode); - if (version != null) - { - options.PackageVersionId = version; - } - - if (this.Log != null) - { - options.LogOutputPath = this.Log; - } - - if (this.Override != null) - { - options.ReplacementInstallerArguments = this.Override; - } - - // Since these arguments are appended to the installer at runtime, it doesn't make sense to append them if they are whitespace - if (!string.IsNullOrWhiteSpace(this.Custom)) - { - options.AdditionalInstallerArguments = this.Custom; - } - - if (this.Location != null) - { - options.PreferredInstallLocation = this.Location; - } - - if (this.Header != null) - { - options.AdditionalPackageCatalogArguments = this.Header; - } - - return options; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Helpers; + + /// + /// This is the base class for all commands that parse a result + /// from the provided parameters i.e., the "install" and "upgrade" commands. + /// + public abstract class InstallCommand : PackageCommand + { + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + internal InstallCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// + protected bool AllowHashMismatch { get; set; } + + /// + /// Gets or sets a value indicating whether to skip dependencies. + /// + protected bool SkipDependencies { get; set; } + + /// + /// Gets or sets the override arguments to be passed on to the installer. + /// + protected string? Override { get; set; } + + /// + /// Gets or sets the arguments to be passed on to the installer in addition to the defaults. + /// + protected string? Custom { get; set; } + + /// + /// Gets or sets the installation location. + /// + protected string? Location { get; set; } + + /// + /// Gets or sets the path to the logging file. + /// + protected string? Log { get; set; } + + /// + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// + protected bool Force { get; set; } + + /// + /// Gets or sets the optional HTTP Header to pass on to the REST Source. + /// + protected string? Header { get; set; } + + /// + /// Gets the install options from the configured parameters. + /// DO NOT pass PackageInstallMode WinRT enum type in this method. + /// That will cause the type to attempt to be loaded in the construction + /// of this method and throw a different exception for Windows PowerShell. + /// + /// The to install. + /// Package install mode as string. + /// An instance. + internal virtual InstallOptions GetInstallOptions(PackageVersionId? version, string mode) + { + InstallOptions options = ManagementDeploymentFactory.Instance.CreateInstallOptions(); + options.AllowHashMismatch = this.AllowHashMismatch; + options.SkipDependencies = this.SkipDependencies; + options.Force = this.Force; + options.PackageInstallMode = PSEnumHelpers.ToPackageInstallMode(mode); + if (version != null) + { + options.PackageVersionId = version; + } + + if (this.Log != null) + { + options.LogOutputPath = this.Log; + } + + if (this.Override != null) + { + options.ReplacementInstallerArguments = this.Override; + } + + // Since these arguments are appended to the installer at runtime, it doesn't make sense to append them if they are whitespace + if (!string.IsNullOrWhiteSpace(this.Custom)) + { + options.AdditionalInstallerArguments = this.Custom; + } + + if (this.Location != null) + { + options.PreferredInstallLocation = this.Location; + } + + if (this.Header != null) + { + options.AdditionalPackageCatalogArguments = this.Header; + } + + return options; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs index bdb608aa98..45417fdba0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/ManagementDeploymentCommand.cs @@ -1,100 +1,100 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands.Common -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using System.Threading.Tasks; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Helpers; - - /// - /// This is the base class for all of the commands in this module that use the COM APIs. - /// - public abstract class ManagementDeploymentCommand : BaseCommand - { - static ManagementDeploymentCommand() - { - WinRTHelpers.Initialize(); - } - - /// - /// Initializes a new instance of the class. - /// - /// psCmdlet. - internal ManagementDeploymentCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { -#if POWERSHELL_WINDOWS - if (Utilities.UsesInProcWinget) - { - throw new WindowsPowerShellNotSupported(); - } -#endif - } - - /// - /// Retrieves the specified source or all sources if is null. - /// - /// A list of instances. - /// The name of the source to retrieve. If null, then all sources are returned. - /// The source does not exist. - internal IReadOnlyList GetPackageCatalogReferences(string? source) - { - if (string.IsNullOrEmpty(source)) - { - return PackageManagerWrapper.Instance.GetPackageCatalogs(); - } - else - { - return new List() - { - PackageManagerWrapper.Instance.GetPackageCatalogByName(source!) - ?? throw new InvalidSourceException(source!), - }; - } - } - - /// - /// Executes the cmdlet. All cmdlets that uses the COM APIs and don't call async functions MUST use this method. - /// The inproc COM API may deadlock on an STA thread. - /// - /// The type of result of the cmdlet. - /// Cmdlet function. - /// The result of the cmdlet. - protected TResult Execute(Func func) - { - if (Utilities.UsesInProcWinget) - { - return this.RunOnMTA(func); - } - - return func(); - } - - /// - /// Executes the cmdlet in a different thread and waits for results. - /// - /// The type of result of the cmdlet. - /// Cmdlet function. - /// The result of the cmdlet. - protected TResult Execute(Func> func) - { - var runningTask = this.RunOnMTA( - async () => - { - return await func(); - }); - - this.Wait(runningTask); - return runningTask.Result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + + /// + /// This is the base class for all of the commands in this module that use the COM APIs. + /// + public abstract class ManagementDeploymentCommand : BaseCommand + { + static ManagementDeploymentCommand() + { + WinRTHelpers.Initialize(); + } + + /// + /// Initializes a new instance of the class. + /// + /// psCmdlet. + internal ManagementDeploymentCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { +#if POWERSHELL_WINDOWS + if (Utilities.UsesInProcWinget) + { + throw new WindowsPowerShellNotSupported(); + } +#endif + } + + /// + /// Retrieves the specified source or all sources if is null. + /// + /// A list of instances. + /// The name of the source to retrieve. If null, then all sources are returned. + /// The source does not exist. + internal IReadOnlyList GetPackageCatalogReferences(string? source) + { + if (string.IsNullOrEmpty(source)) + { + return PackageManagerWrapper.Instance.GetPackageCatalogs(); + } + else + { + return new List() + { + PackageManagerWrapper.Instance.GetPackageCatalogByName(source!) + ?? throw new InvalidSourceException(source!), + }; + } + } + + /// + /// Executes the cmdlet. All cmdlets that uses the COM APIs and don't call async functions MUST use this method. + /// The inproc COM API may deadlock on an STA thread. + /// + /// The type of result of the cmdlet. + /// Cmdlet function. + /// The result of the cmdlet. + protected TResult Execute(Func func) + { + if (Utilities.UsesInProcWinget) + { + return this.RunOnMTA(func); + } + + return func(); + } + + /// + /// Executes the cmdlet in a different thread and waits for results. + /// + /// The type of result of the cmdlet. + /// Cmdlet function. + /// The result of the cmdlet. + protected TResult Execute(Func> func) + { + var runningTask = this.RunOnMTA( + async () => + { + return await func(); + }); + + this.Wait(runningTask); + return runningTask.Result; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs index 62f8c6ce22..4bbd65b142 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/Common/PackageCommand.cs @@ -1,186 +1,186 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands.Common -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using System.Threading.Tasks; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Extensions; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Client.Engine.PSObjects; - - /// - /// This is the base class for commands which operate on a specific package and version i.e., - /// the "install", "uninstall", "download", and "upgrade" commands. - /// - public abstract class PackageCommand : FinderCommand - { - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - internal PackageCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Gets or sets the package to directly install. - /// - /// - /// Must match the name of the field on the class. - /// - protected PSCatalogPackage? CatalogPackage { get; set; } = null; - - /// - /// Gets or sets the version to install. - /// - protected string? Version { get; set; } - - /// - /// Executes a command targeting a specific package version. - /// - /// Type of callback's result. - /// The value. - /// The match option. - /// The method to call after retrieving the package and version to operate upon. - /// Result of the callback. - internal async Task?> GetPackageAndExecuteAsync( - CompositeSearchBehavior behavior, - PackageFieldMatchOption match, - Func> callback) - where TResult : class - { - CatalogPackage package = this.GetCatalogPackage(behavior, match); - PackageVersionId? version = this.GetPackageVersionId(package); - if (this.ShouldProcess(package.ToString(version))) - { - var result = await callback(package, version); - return new Tuple(result, package); - } - - return null; - } - - /// - /// Sets the find package options for a query input that is looking for a specific package. - /// DO NOT pass PackageFieldMatchOption WinRT enum type in this method. - /// That will cause the type to attempt to be loaded in the construction - /// of this method and throw a different exception for Windows PowerShell. - /// - /// The options object. - /// The match type. - /// The query value. - internal override void SetQueryInFindPackagesOptions( - ref FindPackagesOptions options, - string match, - string? value) - { - var matchOption = PSEnumHelpers.ToPackageFieldMatchOption(match); - foreach (PackageMatchField field in new PackageMatchField[] { PackageMatchField.Id, PackageMatchField.Name, PackageMatchField.Moniker }) - { - var selector = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); - selector.Field = field; - selector.Value = value ?? string.Empty; - selector.Option = matchOption; - options.Selectors.Add(selector); - } - } - - private CatalogPackage GetCatalogPackage(CompositeSearchBehavior behavior, PackageFieldMatchOption match) - { - if (this.CatalogPackage != null) - { - // The package was already provided via a parameter or the pipeline. - return this.CatalogPackage.CatalogPackageCOM; - } - else - { - IReadOnlyList results = this.FindPackages(behavior, 0, match); - if (results.Count == 1) - { - // Exactly one package matched, so we can just return it. - return results[0].CatalogPackage; - } - else if (results.Count == 0) - { - // No packages matched, we need to throw an error. - throw new NoPackageFoundException(); - } - else - { - if (behavior != CompositeSearchBehavior.LocalCatalogs) - { - List highestPriorityResults = new List(); - int? highestPriority = null; - - for (int i = 0; i < results.Count; i++) - { - MatchResult result = results[i]; - int? priority = result.CatalogPackage.CatalogPriority; - - if ((highestPriority == null && priority != null) || highestPriority < priority) - { - // Current priority is higher; reset. - highestPriority = priority; - highestPriorityResults.Clear(); - } - else if (highestPriority == priority) - { - // Priority is equal, add to the list. - } - else - { - // Current priority is lower, ignore the match. - continue; - } - - highestPriorityResults.Add(result); - } - - if (highestPriorityResults.Count == 1) - { - return highestPriorityResults[0].CatalogPackage; - } - else - { - throw new VagueCriteriaException(highestPriorityResults); - } - } - - // Too many packages matched! The user needs to refine their input. - throw new VagueCriteriaException(results); - } - } - } - - private PackageVersionId? GetPackageVersionId(CatalogPackage package) - { - if (this.Version != null) - { - for (var i = 0; i < package.AvailableVersions.Count; i++) - { - PackageVersionInfo versionInfo = package.GetPackageVersionInfo(package.AvailableVersions[i]); - - if (versionInfo != null && versionInfo.CompareToVersion(this.Version) == CompareResult.Equal) - { - return package.AvailableVersions[i]; - } - } - - throw new InvalidVersionException(this.Version); - } - else - { - return null; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands.Common +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Extensions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + + /// + /// This is the base class for commands which operate on a specific package and version i.e., + /// the "install", "uninstall", "download", and "upgrade" commands. + /// + public abstract class PackageCommand : FinderCommand + { + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + internal PackageCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Gets or sets the package to directly install. + /// + /// + /// Must match the name of the field on the class. + /// + protected PSCatalogPackage? CatalogPackage { get; set; } = null; + + /// + /// Gets or sets the version to install. + /// + protected string? Version { get; set; } + + /// + /// Executes a command targeting a specific package version. + /// + /// Type of callback's result. + /// The value. + /// The match option. + /// The method to call after retrieving the package and version to operate upon. + /// Result of the callback. + internal async Task?> GetPackageAndExecuteAsync( + CompositeSearchBehavior behavior, + PackageFieldMatchOption match, + Func> callback) + where TResult : class + { + CatalogPackage package = this.GetCatalogPackage(behavior, match); + PackageVersionId? version = this.GetPackageVersionId(package); + if (this.ShouldProcess(package.ToString(version))) + { + var result = await callback(package, version); + return new Tuple(result, package); + } + + return null; + } + + /// + /// Sets the find package options for a query input that is looking for a specific package. + /// DO NOT pass PackageFieldMatchOption WinRT enum type in this method. + /// That will cause the type to attempt to be loaded in the construction + /// of this method and throw a different exception for Windows PowerShell. + /// + /// The options object. + /// The match type. + /// The query value. + internal override void SetQueryInFindPackagesOptions( + ref FindPackagesOptions options, + string match, + string? value) + { + var matchOption = PSEnumHelpers.ToPackageFieldMatchOption(match); + foreach (PackageMatchField field in new PackageMatchField[] { PackageMatchField.Id, PackageMatchField.Name, PackageMatchField.Moniker }) + { + var selector = ManagementDeploymentFactory.Instance.CreatePackageMatchFilter(); + selector.Field = field; + selector.Value = value ?? string.Empty; + selector.Option = matchOption; + options.Selectors.Add(selector); + } + } + + private CatalogPackage GetCatalogPackage(CompositeSearchBehavior behavior, PackageFieldMatchOption match) + { + if (this.CatalogPackage != null) + { + // The package was already provided via a parameter or the pipeline. + return this.CatalogPackage.CatalogPackageCOM; + } + else + { + IReadOnlyList results = this.FindPackages(behavior, 0, match); + if (results.Count == 1) + { + // Exactly one package matched, so we can just return it. + return results[0].CatalogPackage; + } + else if (results.Count == 0) + { + // No packages matched, we need to throw an error. + throw new NoPackageFoundException(); + } + else + { + if (behavior != CompositeSearchBehavior.LocalCatalogs) + { + List highestPriorityResults = new List(); + int? highestPriority = null; + + for (int i = 0; i < results.Count; i++) + { + MatchResult result = results[i]; + int? priority = result.CatalogPackage.CatalogPriority; + + if ((highestPriority == null && priority != null) || highestPriority < priority) + { + // Current priority is higher; reset. + highestPriority = priority; + highestPriorityResults.Clear(); + } + else if (highestPriority == priority) + { + // Priority is equal, add to the list. + } + else + { + // Current priority is lower, ignore the match. + continue; + } + + highestPriorityResults.Add(result); + } + + if (highestPriorityResults.Count == 1) + { + return highestPriorityResults[0].CatalogPackage; + } + else + { + throw new VagueCriteriaException(highestPriorityResults); + } + } + + // Too many packages matched! The user needs to refine their input. + throw new VagueCriteriaException(results); + } + } + } + + private PackageVersionId? GetPackageVersionId(CatalogPackage package) + { + if (this.Version != null) + { + for (var i = 0; i < package.AvailableVersions.Count; i++) + { + PackageVersionInfo versionInfo = package.GetPackageVersionInfo(package.AvailableVersions[i]); + + if (versionInfo != null && versionInfo.CompareToVersion(this.Version) == CompareResult.Equal) + { + return package.AvailableVersions[i]; + } + } + + throw new InvalidVersionException(this.Version); + } + else + { + return null; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/DownloadCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/DownloadCommand.cs index 6433b2e068..bf418bbd7d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/DownloadCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/DownloadCommand.cs @@ -1,182 +1,182 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using System.Threading.Tasks; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - - /// - /// Downloads a package installer. - /// - public sealed class DownloadCommand : PackageCommand - { - /// - /// Initializes a new instance of the class. - /// - /// Caller cmdlet. - /// PSCatalogPackage. - /// Version to install. - /// Package identifier. - /// Name of package. - /// Moniker of package. - /// Source to search. If null, all are searched. - /// Match against any field of a package. - /// To skip the installer hash validation check. - /// To skip package dependencies. - /// Locale of the package. - public DownloadCommand( - PSCmdlet psCmdlet, - PSCatalogPackage psCatalogPackage, - string version, - string id, - string name, - string moniker, - string source, - string[] query, - bool allowHashMismatch, - bool skipDependencies, - string locale) - : base(psCmdlet) - { - // PackageCommand - if (psCatalogPackage != null) - { - this.CatalogPackage = psCatalogPackage; - } - - this.Version = version; - - // FinderCommand - this.Id = id; - this.Name = name; - this.Moniker = moniker; - this.Source = source; - this.Query = query; - - // DownloadCommand - this.AllowHashMismatch = allowHashMismatch; - this.SkipDependencies = skipDependencies; - this.Locale = locale; - } - - /// - /// Gets or sets a value indicating whether to skip the installer hash validation check. - /// - private bool AllowHashMismatch { get; set; } - - /// - /// Gets or sets a value indicating whether to skip dependencies. - /// - private bool SkipDependencies { get; set; } - - /// - /// Gets or sets the locale to install. - /// - private string? Locale { get; set; } - - /// - /// Process download package. - /// - /// The target directory where the installer will be downloaded to. - /// PSPackageFieldMatchOption. - /// PSPackageInstallScope. - /// PSProcessorArchitecture. - /// PSPackageInstallerType. - /// If true, skips downloading a Store license. - /// The platform to download the package for. - /// The target OS version to download for. - public void Download( - string downloadDirectory, - string psPackageFieldMatchOption, - string psPackageInstallScope, - string psProcessorArchitecture, - string psPackageInstallerType, - bool skipMicrosoftStoreLicense, - string psWindowsPlatform, - string targetOSVersion) - { - var result = this.Execute( - async () => await this.GetPackageAndExecuteAsync( - CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), - async (package, version) => - { - DownloadOptions options = this.GetDownloadOptions(version); - - if (!string.IsNullOrEmpty(downloadDirectory)) - { - options.DownloadDirectory = downloadDirectory; - } - - if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) - { - options.Architecture = PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture); - } - - if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) - { - options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); - } - - options.Scope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); - options.SkipMicrosoftStoreLicense = skipMicrosoftStoreLicense; - - if (!PSEnumHelpers.IsDefaultEnum(psWindowsPlatform)) - { - options.Platform = PSEnumHelpers.ToWindowsPlatform(psWindowsPlatform); - } - - if (!string.IsNullOrEmpty(targetOSVersion)) - { - options.TargetOSVersion = targetOSVersion; - } - - return await this.DownloadPackageAsync(package, options); - })); - - if (result != null) - { - this.Write(StreamType.Object, new PSDownloadResult(result.Item1, result.Item2)); - } - } - - private DownloadOptions GetDownloadOptions(PackageVersionId? version) - { - var options = ManagementDeploymentFactory.Instance.CreateDownloadOptions(); - if (version != null) - { - options.PackageVersionId = version; - } - - if (this.Locale != null) - { - options.Locale = this.Locale; - } - - options.AllowHashMismatch = this.AllowHashMismatch; - options.SkipDependencies = this.SkipDependencies; - - return options; - } - - private async Task DownloadPackageAsync( - CatalogPackage package, - DownloadOptions options) - { - var activity = string.Format(Resources.ProgressRecordActivityExporting, package.Name); - var progressOperation = new DownloadOperationWithProgress(this, activity); - return await progressOperation.ExecuteAsync( - () => PackageManagerWrapper.Instance.DownloadPackageAsync(package, options)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + + /// + /// Downloads a package installer. + /// + public sealed class DownloadCommand : PackageCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// PSCatalogPackage. + /// Version to install. + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. If null, all are searched. + /// Match against any field of a package. + /// To skip the installer hash validation check. + /// To skip package dependencies. + /// Locale of the package. + public DownloadCommand( + PSCmdlet psCmdlet, + PSCatalogPackage psCatalogPackage, + string version, + string id, + string name, + string moniker, + string source, + string[] query, + bool allowHashMismatch, + bool skipDependencies, + string locale) + : base(psCmdlet) + { + // PackageCommand + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage; + } + + this.Version = version; + + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + + // DownloadCommand + this.AllowHashMismatch = allowHashMismatch; + this.SkipDependencies = skipDependencies; + this.Locale = locale; + } + + /// + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// + private bool AllowHashMismatch { get; set; } + + /// + /// Gets or sets a value indicating whether to skip dependencies. + /// + private bool SkipDependencies { get; set; } + + /// + /// Gets or sets the locale to install. + /// + private string? Locale { get; set; } + + /// + /// Process download package. + /// + /// The target directory where the installer will be downloaded to. + /// PSPackageFieldMatchOption. + /// PSPackageInstallScope. + /// PSProcessorArchitecture. + /// PSPackageInstallerType. + /// If true, skips downloading a Store license. + /// The platform to download the package for. + /// The target OS version to download for. + public void Download( + string downloadDirectory, + string psPackageFieldMatchOption, + string psPackageInstallScope, + string psProcessorArchitecture, + string psPackageInstallerType, + bool skipMicrosoftStoreLicense, + string psWindowsPlatform, + string targetOSVersion) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + DownloadOptions options = this.GetDownloadOptions(version); + + if (!string.IsNullOrEmpty(downloadDirectory)) + { + options.DownloadDirectory = downloadDirectory; + } + + if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) + { + options.Architecture = PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture); + } + + if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) + { + options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); + } + + options.Scope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); + options.SkipMicrosoftStoreLicense = skipMicrosoftStoreLicense; + + if (!PSEnumHelpers.IsDefaultEnum(psWindowsPlatform)) + { + options.Platform = PSEnumHelpers.ToWindowsPlatform(psWindowsPlatform); + } + + if (!string.IsNullOrEmpty(targetOSVersion)) + { + options.TargetOSVersion = targetOSVersion; + } + + return await this.DownloadPackageAsync(package, options); + })); + + if (result != null) + { + this.Write(StreamType.Object, new PSDownloadResult(result.Item1, result.Item2)); + } + } + + private DownloadOptions GetDownloadOptions(PackageVersionId? version) + { + var options = ManagementDeploymentFactory.Instance.CreateDownloadOptions(); + if (version != null) + { + options.PackageVersionId = version; + } + + if (this.Locale != null) + { + options.Locale = this.Locale; + } + + options.AllowHashMismatch = this.AllowHashMismatch; + options.SkipDependencies = this.SkipDependencies; + + return options; + } + + private async Task DownloadPackageAsync( + CatalogPackage package, + DownloadOptions options) + { + var activity = string.Format(Resources.ProgressRecordActivityExporting, package.Name); + var progressOperation = new DownloadOperationWithProgress(this, activity); + return await progressOperation.ExecuteAsync( + () => PackageManagerWrapper.Instance.DownloadPackageAsync(package, options)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs index fdfa06ce3e..a763c6933c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/FinderPackageCommand.cs @@ -1,91 +1,91 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Common.Command; - - /// - /// Searches configured sources for packages. - /// - public sealed class FinderPackageCommand : FinderExtendedCommand - { - /// - /// Initializes a new instance of the class. - /// - /// Caller cmdlet. - /// Package identifier. - /// Name of package. - /// Moniker of package. - /// Source to search. If null, all are searched. - /// Match against any field of a package. - /// Tag of the package. - /// Command of the package. - /// Max results to return. - public FinderPackageCommand( - PSCmdlet psCmdlet, - string id, - string name, - string moniker, - string source, - string[] query, - string tag, - string command, - uint count) - : base(psCmdlet) - { - // FinderCommand - this.Id = id; - this.Name = name; - this.Moniker = moniker; - this.Source = source; - this.Query = query; - - // FinderExtendedCommand - this.Tag = tag; - this.Command = command; - this.Count = count; - } - - /// - /// Process find package command. - /// - /// PSPackageFieldMatchOption. - public void Find(string psPackageFieldMatchOption) - { - var results = this.Execute( - () => this.FindPackages( - CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); - - for (var i = 0; i < results.Count; i++) - { - this.Write(StreamType.Object, new PSFoundCatalogPackage(results[i].CatalogPackage)); - } - } - - /// - /// Process get package command. - /// - /// PSPackageFieldMatchOption. - public void Get(string psPackageFieldMatchOption) - { - var results = this.Execute( - () => this.FindPackages( - CompositeSearchBehavior.LocalCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); - for (var i = 0; i < results.Count; i++) - { - this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage)); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + + /// + /// Searches configured sources for packages. + /// + public sealed class FinderPackageCommand : FinderExtendedCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. If null, all are searched. + /// Match against any field of a package. + /// Tag of the package. + /// Command of the package. + /// Max results to return. + public FinderPackageCommand( + PSCmdlet psCmdlet, + string id, + string name, + string moniker, + string source, + string[] query, + string tag, + string command, + uint count) + : base(psCmdlet) + { + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + + // FinderExtendedCommand + this.Tag = tag; + this.Command = command; + this.Count = count; + } + + /// + /// Process find package command. + /// + /// PSPackageFieldMatchOption. + public void Find(string psPackageFieldMatchOption) + { + var results = this.Execute( + () => this.FindPackages( + CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); + + for (var i = 0; i < results.Count; i++) + { + this.Write(StreamType.Object, new PSFoundCatalogPackage(results[i].CatalogPackage)); + } + } + + /// + /// Process get package command. + /// + /// PSPackageFieldMatchOption. + public void Get(string psPackageFieldMatchOption) + { + var results = this.Execute( + () => this.FindPackages( + CompositeSearchBehavior.LocalCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption))); + for (var i = 0; i < results.Count; i++) + { + this.Write(StreamType.Object, new PSInstalledCatalogPackage(results[i].CatalogPackage)); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs index 4c21779f39..5ed6a86925 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/InstallerPackageCommand.cs @@ -1,201 +1,201 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using System.Threading.Tasks; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - - /// - /// Installs or updates a package from the pipeline or from a configured source. - /// - public sealed class InstallerPackageCommand : InstallCommand - { - /// - /// Initializes a new instance of the class. - /// - /// Caller cmdlet. - /// Override arguments to be passed on to the installer. - /// Additional arguments. - /// Installation location. - /// To skip the installer hash validation check. - /// To continue upon non security related failures. - /// HTTP Header to pass on to the REST Source. - /// PSCatalogPackage. - /// Version to install. - /// Logging file location. - /// Package identifier. - /// Name of package. - /// Moniker of package. - /// Source to search. If null, all are searched. - /// Match against any field of a package. - /// To skip package dependencies. - public InstallerPackageCommand( - PSCmdlet psCmdlet, - string @override, - string custom, - string location, - bool allowHashMismatch, - bool force, - string header, - PSCatalogPackage psCatalogPackage, - string version, - string log, - string id, - string name, - string moniker, - string source, - string[] query, - bool skipDependencies) - : base(psCmdlet) - { - // InstallCommand. - this.Override = @override; - this.Custom = custom; - this.Location = location; - this.Force = force; - this.Header = header; - this.AllowHashMismatch = allowHashMismatch; - this.SkipDependencies = skipDependencies; - this.Log = log; - - // PackageCommand. - if (psCatalogPackage != null) - { - this.CatalogPackage = psCatalogPackage; - } - - this.Version = version; - - // FinderCommand - this.Id = id; - this.Name = name; - this.Moniker = moniker; - this.Source = source; - this.Query = query; - } - - /// - /// Process install package command. - /// - /// PSPackageFieldMatchOption. - /// PSPackageInstallScope. - /// PSProcessorArchitecture. - /// PSPackageInstallMode. - /// PSPackageInstallerType. - public void Install( - string psPackageFieldMatchOption, - string psPackageInstallScope, - string psProcessorArchitecture, - string psPackageInstallMode, - string psPackageInstallerType) - { - var result = this.Execute( - async () => await this.GetPackageAndExecuteAsync( - CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), - async (package, version) => - { - InstallOptions options = this.GetInstallOptions(version, psPackageInstallMode); - if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) - { - options.AllowedArchitectures.Clear(); - options.AllowedArchitectures.Add(PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture)); - } - - if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) - { - options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); - } - - options.PackageInstallScope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); - - return await this.InstallPackageAsync(package, options); - })); - - if (result != null) - { - this.Write(StreamType.Object, new PSInstallResult(result.Item1, result.Item2)); - } - } - - /// - /// Process update package command. - /// - /// If updating to an unknown version is allowed. - /// PSPackageFieldMatchOption. - /// PSPackageInstallScope. - /// PSProcessorArchitecture. - /// PSPackageInstallMode. - /// PSPackageInstallerType. - public void Update( - bool includeUnknown, - string psPackageFieldMatchOption, - string psPackageInstallScope, - string psProcessorArchitecture, - string psPackageInstallMode, - string psPackageInstallerType) - { - var result = this.Execute( - async () => await this.GetPackageAndExecuteAsync( - CompositeSearchBehavior.LocalCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), - async (package, version) => - { - InstallOptions options = this.GetInstallOptions(version, psPackageInstallMode); - options.AllowUpgradeToUnknownVersion = includeUnknown; - - if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) - { - options.AllowedArchitectures.Clear(); - options.AllowedArchitectures.Add(PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture)); - } - - if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) - { - options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); - } - - options.PackageInstallScope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); - - return await this.UpgradePackageAsync(package, options); - })); - - if (result != null) - { - this.Write(StreamType.Object, new PSInstallResult(result.Item1, result.Item2)); - } - } - - private async Task InstallPackageAsync( - CatalogPackage package, - InstallOptions options) - { - var installOperation = new InstallOperationWithProgress( - this, - string.Format(Resources.ProgressRecordActivityInstalling, package.Name)); - return await installOperation.ExecuteAsync( - () => PackageManagerWrapper.Instance.InstallPackageAsync(package, options)); - } - - private async Task UpgradePackageAsync( - CatalogPackage package, - InstallOptions options) - { - var installOperation = new InstallOperationWithProgress( - this, - string.Format(Resources.ProgressRecordActivityUpdating, package.Name)); - return await installOperation.ExecuteAsync( - () => PackageManagerWrapper.Instance.UpgradePackageAsync(package, options)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + + /// + /// Installs or updates a package from the pipeline or from a configured source. + /// + public sealed class InstallerPackageCommand : InstallCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// Override arguments to be passed on to the installer. + /// Additional arguments. + /// Installation location. + /// To skip the installer hash validation check. + /// To continue upon non security related failures. + /// HTTP Header to pass on to the REST Source. + /// PSCatalogPackage. + /// Version to install. + /// Logging file location. + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. If null, all are searched. + /// Match against any field of a package. + /// To skip package dependencies. + public InstallerPackageCommand( + PSCmdlet psCmdlet, + string @override, + string custom, + string location, + bool allowHashMismatch, + bool force, + string header, + PSCatalogPackage psCatalogPackage, + string version, + string log, + string id, + string name, + string moniker, + string source, + string[] query, + bool skipDependencies) + : base(psCmdlet) + { + // InstallCommand. + this.Override = @override; + this.Custom = custom; + this.Location = location; + this.Force = force; + this.Header = header; + this.AllowHashMismatch = allowHashMismatch; + this.SkipDependencies = skipDependencies; + this.Log = log; + + // PackageCommand. + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage; + } + + this.Version = version; + + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + } + + /// + /// Process install package command. + /// + /// PSPackageFieldMatchOption. + /// PSPackageInstallScope. + /// PSProcessorArchitecture. + /// PSPackageInstallMode. + /// PSPackageInstallerType. + public void Install( + string psPackageFieldMatchOption, + string psPackageInstallScope, + string psProcessorArchitecture, + string psPackageInstallMode, + string psPackageInstallerType) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.RemotePackagesFromRemoteCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + InstallOptions options = this.GetInstallOptions(version, psPackageInstallMode); + if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) + { + options.AllowedArchitectures.Clear(); + options.AllowedArchitectures.Add(PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture)); + } + + if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) + { + options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); + } + + options.PackageInstallScope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); + + return await this.InstallPackageAsync(package, options); + })); + + if (result != null) + { + this.Write(StreamType.Object, new PSInstallResult(result.Item1, result.Item2)); + } + } + + /// + /// Process update package command. + /// + /// If updating to an unknown version is allowed. + /// PSPackageFieldMatchOption. + /// PSPackageInstallScope. + /// PSProcessorArchitecture. + /// PSPackageInstallMode. + /// PSPackageInstallerType. + public void Update( + bool includeUnknown, + string psPackageFieldMatchOption, + string psPackageInstallScope, + string psProcessorArchitecture, + string psPackageInstallMode, + string psPackageInstallerType) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.LocalCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + InstallOptions options = this.GetInstallOptions(version, psPackageInstallMode); + options.AllowUpgradeToUnknownVersion = includeUnknown; + + if (!PSEnumHelpers.IsDefaultEnum(psProcessorArchitecture)) + { + options.AllowedArchitectures.Clear(); + options.AllowedArchitectures.Add(PSEnumHelpers.ToProcessorArchitecture(psProcessorArchitecture)); + } + + if (!PSEnumHelpers.IsDefaultEnum(psPackageInstallerType)) + { + options.InstallerType = PSEnumHelpers.ToPackageInstallerType(psPackageInstallerType); + } + + options.PackageInstallScope = PSEnumHelpers.ToPackageInstallScope(psPackageInstallScope); + + return await this.UpgradePackageAsync(package, options); + })); + + if (result != null) + { + this.Write(StreamType.Object, new PSInstallResult(result.Item1, result.Item2)); + } + } + + private async Task InstallPackageAsync( + CatalogPackage package, + InstallOptions options) + { + var installOperation = new InstallOperationWithProgress( + this, + string.Format(Resources.ProgressRecordActivityInstalling, package.Name)); + return await installOperation.ExecuteAsync( + () => PackageManagerWrapper.Instance.InstallPackageAsync(package, options)); + } + + private async Task UpgradePackageAsync( + CatalogPackage package, + InstallOptions options) + { + var installOperation = new InstallOperationWithProgress( + this, + string.Format(Resources.ProgressRecordActivityUpdating, package.Name)); + return await installOperation.ExecuteAsync( + () => PackageManagerWrapper.Instance.UpgradePackageAsync(package, options)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs index 143906fa2c..f7476bdc0b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/RepairPackageCommand.cs @@ -1,157 +1,157 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System; - using System.Management.Automation; - using System.Threading.Tasks; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Resources; - - /// - /// This class defines the repair package command. - /// - public sealed class RepairPackageCommand : PackageCommand - { - /// - /// Initializes a new instance of the class. - /// - /// Caller cmdlet. - /// PSCatalogPackage. - /// To skip the installer hash validation check. - /// To continue upon non security related failures. - /// Version to repair. - /// Logging file location. - /// Package identifier. - /// Name of package. - /// Moniker of package. - /// Source to search. if null, all are searched. - /// Match against any field of a package. - public RepairPackageCommand( - PSCmdlet psCmdlet, - bool allowHashMismatch, - bool force, - PSCatalogPackage psCatalogPackage, - string version, - string log, - string id, - string name, - string moniker, - string source, - string[] query) - : base(psCmdlet) - { - this.Force = force; - this.AllowHashMismatch = allowHashMismatch; - - if (psCatalogPackage != null) - { - this.CatalogPackage = psCatalogPackage; - } - - this.Version = version; - - this.Id = id; - this.Name = name; - this.Moniker = moniker; - this.Source = source; - this.Query = query; - - this.Log = log; - } - - /// - /// Gets or sets the path to the logging file. - /// - private string? Log { get; set; } - - /// - /// Gets or sets a value indicating whether to continue upon non security related failures. - /// - private bool Force { get; set; } - - /// - /// Gets or sets a value indicating whether to skip the installer hash validation check. - /// - private bool AllowHashMismatch { get; set; } - - /// - /// Process repair package. - /// - /// PSPackageFieldMatchOption. - /// PSPackageRepairMode. - public void Repair( - string psPackageFieldMatchOption, - string psPackageRepairMode) - { - var result = this.Execute( - async () => await this.GetPackageAndExecuteAsync( - CompositeSearchBehavior.AllCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), - async (package, version) => - { - var repairOptions = this.GetRepairOptions(version, PSEnumHelpers.ToPackageRepairMode(psPackageRepairMode)); - return await this.RepairPackageAsync(package, repairOptions); - })); - - if (result != null) - { - if (result.Item1.Status == RepairResultStatus.RepairError - && result.Item1.ExtendedErrorCode != null) - { - if (result.Item1.ExtendedErrorCode.InnerException != null) - { - throw new WinGetRepairPackageException(result.Item1.ExtendedErrorCode.HResult, result.Item1.RepairerErrorCode, result.Item1.ExtendedErrorCode.InnerException); - } - - throw new WinGetRepairPackageException(result.Item1.ExtendedErrorCode.HResult, result.Item1.RepairerErrorCode); - } - - this.Write(WinGet.Common.Command.StreamType.Object, new PSRepairResult(result.Item1, result.Item2)); - } - } - - private RepairOptions GetRepairOptions( - PackageVersionId? version, - PackageRepairMode repairMode) - { - var options = ManagementDeploymentFactory.Instance.CreateRepairOptions(); - options.AllowHashMismatch = this.AllowHashMismatch; - options.Force = this.Force; - - if (this.Log != null) - { - options.LogOutputPath = this.Log; - } - - options.PackageRepairMode = repairMode; - - if (version != null) - { - options.PackageVersionId = version; - } - - return options; - } - - private async Task RepairPackageAsync( - CatalogPackage catalogPackage, - RepairOptions repairOptions) - { - var progressOperation = new RepairOperationWithProgress( - this, - string.Format(Resources.ProgressRecordActivityRepairing, catalogPackage.Name)); - - return await progressOperation.ExecuteAsync( - () => PackageManagerWrapper.Instance.RepairPackageAsync(catalogPackage, repairOptions)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Resources; + + /// + /// This class defines the repair package command. + /// + public sealed class RepairPackageCommand : PackageCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// PSCatalogPackage. + /// To skip the installer hash validation check. + /// To continue upon non security related failures. + /// Version to repair. + /// Logging file location. + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. if null, all are searched. + /// Match against any field of a package. + public RepairPackageCommand( + PSCmdlet psCmdlet, + bool allowHashMismatch, + bool force, + PSCatalogPackage psCatalogPackage, + string version, + string log, + string id, + string name, + string moniker, + string source, + string[] query) + : base(psCmdlet) + { + this.Force = force; + this.AllowHashMismatch = allowHashMismatch; + + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage; + } + + this.Version = version; + + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + + this.Log = log; + } + + /// + /// Gets or sets the path to the logging file. + /// + private string? Log { get; set; } + + /// + /// Gets or sets a value indicating whether to continue upon non security related failures. + /// + private bool Force { get; set; } + + /// + /// Gets or sets a value indicating whether to skip the installer hash validation check. + /// + private bool AllowHashMismatch { get; set; } + + /// + /// Process repair package. + /// + /// PSPackageFieldMatchOption. + /// PSPackageRepairMode. + public void Repair( + string psPackageFieldMatchOption, + string psPackageRepairMode) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.AllCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + var repairOptions = this.GetRepairOptions(version, PSEnumHelpers.ToPackageRepairMode(psPackageRepairMode)); + return await this.RepairPackageAsync(package, repairOptions); + })); + + if (result != null) + { + if (result.Item1.Status == RepairResultStatus.RepairError + && result.Item1.ExtendedErrorCode != null) + { + if (result.Item1.ExtendedErrorCode.InnerException != null) + { + throw new WinGetRepairPackageException(result.Item1.ExtendedErrorCode.HResult, result.Item1.RepairerErrorCode, result.Item1.ExtendedErrorCode.InnerException); + } + + throw new WinGetRepairPackageException(result.Item1.ExtendedErrorCode.HResult, result.Item1.RepairerErrorCode); + } + + this.Write(WinGet.Common.Command.StreamType.Object, new PSRepairResult(result.Item1, result.Item2)); + } + } + + private RepairOptions GetRepairOptions( + PackageVersionId? version, + PackageRepairMode repairMode) + { + var options = ManagementDeploymentFactory.Instance.CreateRepairOptions(); + options.AllowHashMismatch = this.AllowHashMismatch; + options.Force = this.Force; + + if (this.Log != null) + { + options.LogOutputPath = this.Log; + } + + options.PackageRepairMode = repairMode; + + if (version != null) + { + options.PackageVersionId = version; + } + + return options; + } + + private async Task RepairPackageAsync( + CatalogPackage catalogPackage, + RepairOptions repairOptions) + { + var progressOperation = new RepairOperationWithProgress( + this, + string.Format(Resources.ProgressRecordActivityRepairing, catalogPackage.Name)); + + return await progressOperation.ExecuteAsync( + () => PackageManagerWrapper.Instance.RepairPackageAsync(catalogPackage, repairOptions)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/SourceCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/SourceCommand.cs index dd9cde8087..c3ea742980 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/SourceCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/SourceCommand.cs @@ -1,43 +1,43 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Common.Command; - - /// - /// Wrapper for source cmdlets. - /// - public sealed class SourceCommand : ManagementDeploymentCommand - { - /// - /// Initializes a new instance of the class. - /// Wrapper for Source commands. - /// - /// Caller cmdlet. - public SourceCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Get-WinGetSource. - /// - /// Optional name. - public void Get(string name) - { - var results = this.Execute( - () => this.GetPackageCatalogReferences(name)); - for (var i = 0; i < results.Count; i++) - { - this.Write(StreamType.Object, new PSSourceResult(results[i])); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + + /// + /// Wrapper for source cmdlets. + /// + public sealed class SourceCommand : ManagementDeploymentCommand + { + /// + /// Initializes a new instance of the class. + /// Wrapper for Source commands. + /// + /// Caller cmdlet. + public SourceCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Get-WinGetSource. + /// + /// Optional name. + public void Get(string name) + { + var results = this.Execute( + () => this.GetPackageCatalogReferences(name)); + for (var i = 0; i < results.Count; i++) + { + this.Write(StreamType.Object, new PSSourceResult(results[i])); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs index c8455e6891..e5bcaeaf98 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UninstallPackageCommand.cs @@ -1,131 +1,131 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using System.Threading.Tasks; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Client.Engine.PSObjects; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - - /// - /// Uninstalls a package from the local system. - /// - public sealed class UninstallPackageCommand : PackageCommand - { - /// - /// Initializes a new instance of the class. - /// - /// Caller cmdlet. - /// PSCatalogPackage. - /// Version to install. - /// Logging file location. - /// Package identifier. - /// Name of package. - /// Moniker of package. - /// Source to search. If null, all are searched. - /// Match against any field of a package. - public UninstallPackageCommand( - PSCmdlet psCmdlet, - PSCatalogPackage psCatalogPackage, - string version, - string log, - string id, - string name, - string moniker, - string source, - string[] query) - : base(psCmdlet) - { - // PackageCommand. - if (psCatalogPackage != null) - { - this.CatalogPackage = psCatalogPackage; - } - - this.Version = version; - - // FinderCommand - this.Id = id; - this.Name = name; - this.Moniker = moniker; - this.Source = source; - this.Query = query; - - // UninstallPackageCommand - this.Log = log; - } - - /// - /// Gets or sets the path to the logging file. - /// - private string? Log { get; set; } - - /// - /// Process uninstall package. - /// - /// PSPackageFieldMatchOption. - /// PSPackageUninstallMode. - /// Force. - public void Uninstall( - string psPackageFieldMatchOption, - string psPackageUninstallMode, - bool force) - { - var result = this.Execute( - async () => await this.GetPackageAndExecuteAsync( - CompositeSearchBehavior.LocalCatalogs, - PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), - async (package, version) => - { - UninstallOptions options = this.GetUninstallOptions(version, PSEnumHelpers.ToPackageUninstallMode(psPackageUninstallMode), force); - return await this.UninstallPackageAsync(package, options); - })); - - if (result != null) - { - this.Write(StreamType.Object, new PSUninstallResult(result.Item1, result.Item2)); - } - } - - private UninstallOptions GetUninstallOptions( - PackageVersionId? version, - PackageUninstallMode packageUninstallMode, - bool force) - { - var options = ManagementDeploymentFactory.Instance.CreateUninstallOptions(); - options.Force = force; - if (this.Log != null) - { - options.LogOutputPath = this.Log; - } - - options.PackageUninstallMode = packageUninstallMode; - - if (version != null) - { - options.PackageVersionId = version; - } - - return options; - } - - private async Task UninstallPackageAsync( - CatalogPackage package, - UninstallOptions options) - { - var progressOperation = new UninstallOperationWithProgress( - this, - string.Format(Resources.ProgressRecordActivityUninstalling, package.Name)); - return await progressOperation.ExecuteAsync( - () => PackageManagerWrapper.Instance.UninstallPackageAsync(package, options)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Client.Engine.PSObjects; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + + /// + /// Uninstalls a package from the local system. + /// + public sealed class UninstallPackageCommand : PackageCommand + { + /// + /// Initializes a new instance of the class. + /// + /// Caller cmdlet. + /// PSCatalogPackage. + /// Version to install. + /// Logging file location. + /// Package identifier. + /// Name of package. + /// Moniker of package. + /// Source to search. If null, all are searched. + /// Match against any field of a package. + public UninstallPackageCommand( + PSCmdlet psCmdlet, + PSCatalogPackage psCatalogPackage, + string version, + string log, + string id, + string name, + string moniker, + string source, + string[] query) + : base(psCmdlet) + { + // PackageCommand. + if (psCatalogPackage != null) + { + this.CatalogPackage = psCatalogPackage; + } + + this.Version = version; + + // FinderCommand + this.Id = id; + this.Name = name; + this.Moniker = moniker; + this.Source = source; + this.Query = query; + + // UninstallPackageCommand + this.Log = log; + } + + /// + /// Gets or sets the path to the logging file. + /// + private string? Log { get; set; } + + /// + /// Process uninstall package. + /// + /// PSPackageFieldMatchOption. + /// PSPackageUninstallMode. + /// Force. + public void Uninstall( + string psPackageFieldMatchOption, + string psPackageUninstallMode, + bool force) + { + var result = this.Execute( + async () => await this.GetPackageAndExecuteAsync( + CompositeSearchBehavior.LocalCatalogs, + PSEnumHelpers.ToPackageFieldMatchOption(psPackageFieldMatchOption), + async (package, version) => + { + UninstallOptions options = this.GetUninstallOptions(version, PSEnumHelpers.ToPackageUninstallMode(psPackageUninstallMode), force); + return await this.UninstallPackageAsync(package, options); + })); + + if (result != null) + { + this.Write(StreamType.Object, new PSUninstallResult(result.Item1, result.Item2)); + } + } + + private UninstallOptions GetUninstallOptions( + PackageVersionId? version, + PackageUninstallMode packageUninstallMode, + bool force) + { + var options = ManagementDeploymentFactory.Instance.CreateUninstallOptions(); + options.Force = force; + if (this.Log != null) + { + options.LogOutputPath = this.Log; + } + + options.PackageUninstallMode = packageUninstallMode; + + if (version != null) + { + options.PackageVersionId = version; + } + + return options; + } + + private async Task UninstallPackageAsync( + CatalogPackage package, + UninstallOptions options) + { + var progressOperation = new UninstallOperationWithProgress( + this, + string.Format(Resources.ProgressRecordActivityUninstalling, package.Name)); + return await progressOperation.ExecuteAsync( + () => PackageManagerWrapper.Instance.UninstallPackageAsync(package, options)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs index dee742ad74..54fb476650 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/UserSettingsCommand.cs @@ -1,266 +1,266 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System; - using System.Collections; - using System.IO; - using System.Linq; - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Common.Command; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - - /// - /// Class used by the user settings cmdlets. - /// - public sealed class UserSettingsCommand : BaseCommand - { - private const string SchemaKey = "$schema"; - private const string SchemaValue = "https://aka.ms/winget-settings.schema.json"; - - private static string? winGetSettingsFilePath; - - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - public UserSettingsCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - // Doing it in the static constructor will show the user running in system context: - // The type initializer for 'Microsoft.WinGet.Client.Engine.Commands.UserSettingsCommand' threw an exception. - // Here would be "The specified method is not supported." - if (winGetSettingsFilePath == null) - { - var wingetCliWrapper = new WingetCLIWrapper(); - var settingsResult = wingetCliWrapper.RunCommand(this, new WinGetCLICommandBuilder("settings").AppendSubCommand("export")); - - // Read the user settings file property. - var userSettingsFile = Utilities.ConvertToHashtable(settingsResult.StdOut)["userSettingsFile"] ?? throw new ArgumentNullException("userSettingsFile"); - winGetSettingsFilePath = (string)userSettingsFile; - } - } - - /// - /// Get-WinGetUserSetting. - /// - public void Get() - { - this.Write(StreamType.Object, this.GetLocalSettingsAsHashtable()); - } - - /// - /// Test-WinGetUserSetting. - /// - /// Input user settings. - /// Ignore comparing settings that are not part of the input. - public void Test(Hashtable userSettings, bool ignoreNotSet) - { - this.Write(StreamType.Object, this.CompareUserSettings(userSettings, ignoreNotSet)); - } - - /// - /// Set-WinGetUserSetting. - /// - /// Input user settings. - /// Merge the current user settings and the input settings. - public void Set(Hashtable userSettings, bool merge) - { - var newSettings = HashtableToJObject(userSettings); - - // Merge settings. - if (merge) - { - var currentSettings = this.LocalSettingsFileToJObject(); - - // To make the input settings triumph, they need to be merged into the existing settings. - currentSettings.Merge(newSettings, new JsonMergeSettings - { - MergeArrayHandling = MergeArrayHandling.Union, - MergeNullValueHandling = MergeNullValueHandling.Ignore, - }); - - newSettings = currentSettings; - } - - // Add schema if not there. - if (!newSettings.ContainsKey(SchemaKey)) - { - newSettings.Add(SchemaKey, SchemaValue); - } - - var orderedSettings = this.CreateAlphabeticallyOrderedJObject(newSettings); - - // Write settings. - var settingsJson = orderedSettings.ToString(Formatting.Indented); - File.WriteAllText( - winGetSettingsFilePath!, - settingsJson); - - this.Write(StreamType.Object, Utilities.ConvertToHashtable(settingsJson)); - } - - private static JObject HashtableToJObject(Hashtable hashtable) - { - return (JObject)JToken.FromObject(hashtable); - } - - private Hashtable GetLocalSettingsAsHashtable() - { - var content = File.Exists(winGetSettingsFilePath) ? - File.ReadAllText(winGetSettingsFilePath) : - string.Empty; - - return Utilities.ConvertToHashtable(content); - } - - private JObject LocalSettingsFileToJObject() - { - try - { - return File.Exists(winGetSettingsFilePath) ? - JObject.Parse(File.ReadAllText(winGetSettingsFilePath)) : - new JObject(); - } - catch (JsonReaderException e) - { - this.Write(StreamType.Verbose, e.Message); - throw new UserSettingsReadException(e); - } - } - - private bool CompareUserSettings(Hashtable userSettings, bool ignoreNotSet) - { - try - { - var currentSettings = this.LocalSettingsFileToJObject(); - var newSettings = HashtableToJObject(userSettings); - - // Don't fail because of the schema. - if (currentSettings.ContainsKey(SchemaKey)) - { - currentSettings.Remove(SchemaKey); - } - - if (newSettings.ContainsKey(SchemaKey)) - { - newSettings.Remove(SchemaKey); - } - - if (ignoreNotSet) - { - return this.PartialDeepEquals(newSettings, currentSettings); - } - - return JToken.DeepEquals(newSettings, currentSettings); - } - catch (Exception e) - { - this.Write(StreamType.Verbose, e.Message); - return false; - } - } - - /// - /// Partially compares json. All properties and values of json must exist and have the same value - /// as otherJson. - /// This doesn't support deep JArray object comparison, but we don't have arrays of type object so far :). - /// - /// Main json. - /// otherJson. - /// True is otherJson partially contains json. - private bool PartialDeepEquals(JToken json, JToken? otherJson) - { - if (JToken.DeepEquals(json, otherJson)) - { - return true; - } - - if (otherJson == null) - { - return false; - } - - // If they are a JValue (string, integer, date, etc) or they are a JArray and DeepEquals fails then not equal. - if ((json is JValue && otherJson is JValue) || - (json is JArray && otherJson is JArray)) - { - this.Write( - StreamType.Verbose, - $"'{json.ToString(Formatting.None)}' != '{otherJson.ToString(Formatting.None)}'"); - return false; - } - - // If its not the same type then don't bother. - if (json.Type != otherJson.Type) - { - this.Write( - StreamType.Verbose, - $"Mismatch types '{json.ToString(Formatting.None)}' '{otherJson.ToString(Formatting.None)}'"); - return false; - } - - // Look deeply. - if (json.Type == JTokenType.Object) - { - var jObject = (JObject)json; - var otherJObject = (JObject)otherJson; - - var properties = jObject.Properties(); - foreach (var property in properties) - { - // If the property is not there then give up. - if (!otherJObject.ContainsKey(property.Name)) - { - this.Write(StreamType.Verbose, $"{property.Name} not found."); - return false; - } - - if (!this.PartialDeepEquals(property.Value, otherJObject.GetValue(property.Name))) - { - // Found inequality within a property. We are done. - return false; - } - } - } - - return true; - } - - /// - /// Helper method to order alphabetically properties. Newtonsoft doesn't have a nice way - /// to do it via a custom JsonConverter. - /// - /// JObject. - /// New ordered JObject. - private JObject CreateAlphabeticallyOrderedJObject(JObject jObject) - { - JObject newJObject = new (); - var orderedProperties = jObject.Properties().OrderBy(p => p.Name, StringComparer.Ordinal); - foreach (var property in orderedProperties) - { - if (property.Value.Type == JTokenType.Object) - { - newJObject.Add( - property.Name, - this.CreateAlphabeticallyOrderedJObject((JObject)property.Value)); - } - else - { - newJObject.Add(property); - } - } - - return newJObject; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Collections; + using System.IO; + using System.Linq; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Common.Command; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// + /// Class used by the user settings cmdlets. + /// + public sealed class UserSettingsCommand : BaseCommand + { + private const string SchemaKey = "$schema"; + private const string SchemaValue = "https://aka.ms/winget-settings.schema.json"; + + private static string? winGetSettingsFilePath; + + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + public UserSettingsCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + // Doing it in the static constructor will show the user running in system context: + // The type initializer for 'Microsoft.WinGet.Client.Engine.Commands.UserSettingsCommand' threw an exception. + // Here would be "The specified method is not supported." + if (winGetSettingsFilePath == null) + { + var wingetCliWrapper = new WingetCLIWrapper(); + var settingsResult = wingetCliWrapper.RunCommand(this, new WinGetCLICommandBuilder("settings").AppendSubCommand("export")); + + // Read the user settings file property. + var userSettingsFile = Utilities.ConvertToHashtable(settingsResult.StdOut)["userSettingsFile"] ?? throw new ArgumentNullException("userSettingsFile"); + winGetSettingsFilePath = (string)userSettingsFile; + } + } + + /// + /// Get-WinGetUserSetting. + /// + public void Get() + { + this.Write(StreamType.Object, this.GetLocalSettingsAsHashtable()); + } + + /// + /// Test-WinGetUserSetting. + /// + /// Input user settings. + /// Ignore comparing settings that are not part of the input. + public void Test(Hashtable userSettings, bool ignoreNotSet) + { + this.Write(StreamType.Object, this.CompareUserSettings(userSettings, ignoreNotSet)); + } + + /// + /// Set-WinGetUserSetting. + /// + /// Input user settings. + /// Merge the current user settings and the input settings. + public void Set(Hashtable userSettings, bool merge) + { + var newSettings = HashtableToJObject(userSettings); + + // Merge settings. + if (merge) + { + var currentSettings = this.LocalSettingsFileToJObject(); + + // To make the input settings triumph, they need to be merged into the existing settings. + currentSettings.Merge(newSettings, new JsonMergeSettings + { + MergeArrayHandling = MergeArrayHandling.Union, + MergeNullValueHandling = MergeNullValueHandling.Ignore, + }); + + newSettings = currentSettings; + } + + // Add schema if not there. + if (!newSettings.ContainsKey(SchemaKey)) + { + newSettings.Add(SchemaKey, SchemaValue); + } + + var orderedSettings = this.CreateAlphabeticallyOrderedJObject(newSettings); + + // Write settings. + var settingsJson = orderedSettings.ToString(Formatting.Indented); + File.WriteAllText( + winGetSettingsFilePath!, + settingsJson); + + this.Write(StreamType.Object, Utilities.ConvertToHashtable(settingsJson)); + } + + private static JObject HashtableToJObject(Hashtable hashtable) + { + return (JObject)JToken.FromObject(hashtable); + } + + private Hashtable GetLocalSettingsAsHashtable() + { + var content = File.Exists(winGetSettingsFilePath) ? + File.ReadAllText(winGetSettingsFilePath) : + string.Empty; + + return Utilities.ConvertToHashtable(content); + } + + private JObject LocalSettingsFileToJObject() + { + try + { + return File.Exists(winGetSettingsFilePath) ? + JObject.Parse(File.ReadAllText(winGetSettingsFilePath)) : + new JObject(); + } + catch (JsonReaderException e) + { + this.Write(StreamType.Verbose, e.Message); + throw new UserSettingsReadException(e); + } + } + + private bool CompareUserSettings(Hashtable userSettings, bool ignoreNotSet) + { + try + { + var currentSettings = this.LocalSettingsFileToJObject(); + var newSettings = HashtableToJObject(userSettings); + + // Don't fail because of the schema. + if (currentSettings.ContainsKey(SchemaKey)) + { + currentSettings.Remove(SchemaKey); + } + + if (newSettings.ContainsKey(SchemaKey)) + { + newSettings.Remove(SchemaKey); + } + + if (ignoreNotSet) + { + return this.PartialDeepEquals(newSettings, currentSettings); + } + + return JToken.DeepEquals(newSettings, currentSettings); + } + catch (Exception e) + { + this.Write(StreamType.Verbose, e.Message); + return false; + } + } + + /// + /// Partially compares json. All properties and values of json must exist and have the same value + /// as otherJson. + /// This doesn't support deep JArray object comparison, but we don't have arrays of type object so far :). + /// + /// Main json. + /// otherJson. + /// True is otherJson partially contains json. + private bool PartialDeepEquals(JToken json, JToken? otherJson) + { + if (JToken.DeepEquals(json, otherJson)) + { + return true; + } + + if (otherJson == null) + { + return false; + } + + // If they are a JValue (string, integer, date, etc) or they are a JArray and DeepEquals fails then not equal. + if ((json is JValue && otherJson is JValue) || + (json is JArray && otherJson is JArray)) + { + this.Write( + StreamType.Verbose, + $"'{json.ToString(Formatting.None)}' != '{otherJson.ToString(Formatting.None)}'"); + return false; + } + + // If its not the same type then don't bother. + if (json.Type != otherJson.Type) + { + this.Write( + StreamType.Verbose, + $"Mismatch types '{json.ToString(Formatting.None)}' '{otherJson.ToString(Formatting.None)}'"); + return false; + } + + // Look deeply. + if (json.Type == JTokenType.Object) + { + var jObject = (JObject)json; + var otherJObject = (JObject)otherJson; + + var properties = jObject.Properties(); + foreach (var property in properties) + { + // If the property is not there then give up. + if (!otherJObject.ContainsKey(property.Name)) + { + this.Write(StreamType.Verbose, $"{property.Name} not found."); + return false; + } + + if (!this.PartialDeepEquals(property.Value, otherJObject.GetValue(property.Name))) + { + // Found inequality within a property. We are done. + return false; + } + } + } + + return true; + } + + /// + /// Helper method to order alphabetically properties. Newtonsoft doesn't have a nice way + /// to do it via a custom JsonConverter. + /// + /// JObject. + /// New ordered JObject. + private JObject CreateAlphabeticallyOrderedJObject(JObject jObject) + { + JObject newJObject = new (); + var orderedProperties = jObject.Properties().OrderBy(p => p.Name, StringComparer.Ordinal); + foreach (var property in orderedProperties) + { + if (property.Value.Type == JTokenType.Object) + { + newJObject.Add( + property.Name, + this.CreateAlphabeticallyOrderedJObject((JObject)property.Value)); + } + else + { + newJObject.Add(property); + } + } + + return newJObject; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs index 4bc1a762b9..140f0457fe 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/VersionCommand.cs @@ -1,36 +1,36 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Common.Command; - - /// - /// Version commands. - /// - public sealed class VersionCommand : ManagementDeploymentCommand - { - /// - /// Initializes a new instance of the class. - /// - /// The caller cmdlet. - public VersionCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Get-WinGetVersion. Gets the currently installed winget version. - /// - public void Get() - { - this.Write(StreamType.Object, this.Execute(() => WinGetVersion.InstalledWinGetVersion(this).TagVersion)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Common.Command; + + /// + /// Version commands. + /// + public sealed class VersionCommand : ManagementDeploymentCommand + { + /// + /// Initializes a new instance of the class. + /// + /// The caller cmdlet. + public VersionCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Get-WinGetVersion. Gets the currently installed winget version. + /// + public void Get() + { + this.Write(StreamType.Object, this.Execute(() => WinGetVersion.InstalledWinGetVersion(this).TagVersion)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs index 0a26f2d910..f1e283c3d2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Commands/WinGetPackageManagerCommand.cs @@ -1,278 +1,278 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Commands -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using System.Threading.Tasks; - using Microsoft.WinGet.Client.Engine.Commands.Common; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - using static Microsoft.WinGet.Client.Engine.Common.Constants; - - /// - /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. - /// - public sealed class WinGetPackageManagerCommand : ManagementDeploymentCommand - { - private const string EnvPath = "env:PATH"; - - /// - /// Initializes a new instance of the class. - /// - /// Cmdlet being executed. - public WinGetPackageManagerCommand(PSCmdlet psCmdlet) - : base(psCmdlet) - { - } - - /// - /// Asserts winget version is the latest version on winget-cli. - /// - /// Use prerelease version on GitHub. - public void AssertUsingLatest(bool preRelease) - { - var runningTask = this.RunOnMTA( - async () => - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); - string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); - this.Assert(expectedVersion); - return true; - }); - - this.Wait(runningTask); - } - - /// - /// Asserts the version installed is the specified. - /// - /// The expected version. - public void Assert(string expectedVersion) - { - WinGetIntegrity.AssertWinGet(this, expectedVersion); - } - - /// - /// Repairs winget using the latest version on winget-cli. - /// - /// Use prerelease version on GitHub. - /// Install for all users. Requires admin. - /// Force application shutdown. - public void RepairUsingLatest(bool preRelease, bool allUsers, bool force) - { - this.ValidateWhenAllUsers(allUsers); - var runningTask = this.RunOnMTA( - async () => - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); - string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); - await this.RepairStateMachineAsync(expectedVersion, allUsers, force); - return true; - }); - - this.Wait(runningTask); - } - - /// - /// Repairs winget if needed. - /// - /// The expected version, if any. - /// Install for all users. Requires admin. - /// Force application shutdown. - /// Include prerelease versions when matching version. - public void Repair(string expectedVersion, bool allUsers, bool force, bool includePrerelease) - { - this.ValidateWhenAllUsers(allUsers); - var runningTask = this.RunOnMTA( - async () => - { - if (!string.IsNullOrWhiteSpace(expectedVersion)) - { - this.Write(StreamType.Verbose, $"Attempting to resolve version '{expectedVersion}'"); - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); - try - { - var resolvedVersion = await gitHubClient.ResolveVersionAsync(expectedVersion, includePrerelease); - if (!string.IsNullOrEmpty(resolvedVersion)) - { - this.Write(StreamType.Verbose, $"Matching version found: {resolvedVersion}"); - expectedVersion = resolvedVersion!; - } - else - { - this.Write(StreamType.Warning, $"No matching version found for {expectedVersion}"); - } - } - catch (Exception ex) - { - this.Write(StreamType.Warning, $"Could not resolve version '{expectedVersion}': {ex.Message}"); - } - } - else - { - this.Write(StreamType.Verbose, "No version specified."); - } - - await this.RepairStateMachineAsync(expectedVersion, allUsers, force); - return true; - }); - this.Wait(runningTask); - } - - private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force) - { - var seenCategories = new HashSet(); - var cancellationToken = this.GetCancellationToken(); - - var currentCategory = IntegrityCategory.Unknown; - while (currentCategory != IntegrityCategory.Installed) - { - cancellationToken.ThrowIfCancellationRequested(); - - try - { - WinGetIntegrity.AssertWinGet(this, expectedVersion); - this.Write(StreamType.Verbose, $"WinGet is in a good state."); - currentCategory = IntegrityCategory.Installed; - } - catch (WinGetIntegrityException e) - { - currentCategory = e.Category; - - if (seenCategories.Contains(currentCategory)) - { - this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); - throw; - } - - this.Write(StreamType.Verbose, $"Integrity category type: {currentCategory}"); - seenCategories.Add(currentCategory); - - switch (currentCategory) - { - case IntegrityCategory.UnexpectedVersion: - await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), e.InstalledVersion, allUsers, force); - break; - case IntegrityCategory.NotInPath: - this.RepairEnvPath(); - break; - case IntegrityCategory.AppInstallerNotRegistered: - await this.RegisterAsync(expectedVersion, allUsers); - break; - case IntegrityCategory.AppInstallerNotInstalled: - case IntegrityCategory.AppInstallerNotSupported: - case IntegrityCategory.Failure: - await this.InstallAsync(expectedVersion, allUsers, force); - break; - case IntegrityCategory.AppInstallerNoLicense: - // This requires -AllUsers in admin mode. - if (allUsers && Utilities.ExecutingAsAdministrator) - { - await this.InstallAsync(expectedVersion, allUsers, force); - } - else - { - throw new WinGetRepairException(e); - } - - break; - case IntegrityCategory.WinGetSourceNotInstalled: - await this.InstallWinGetSourceAsync(); - break; - case IntegrityCategory.AppExecutionAliasDisabled: - case IntegrityCategory.Unknown: - throw new WinGetRepairException(e); - default: - throw new NotSupportedException(); - } - } - } - } - - private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, WinGetVersion? installedVersion, bool allUsers, bool force) - { - if (installedVersion == null) - { - installedVersion = WinGetVersion.InstalledWinGetVersion(this); - } - - bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0; - - string message = $"Installed WinGet version '{installedVersion.TagVersion}' " + - $"Installing WinGet version '{toInstallVersion.TagVersion}' " + - $"Is downgrade {isDowngrade}"; - this.Write( - StreamType.Verbose, - message); - var appxModule = new AppxModuleHelper(this); - await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion.TagVersion, allUsers, isDowngrade, force); - } - - private async Task InstallAsync(string toInstallVersion, bool allUsers, bool force) - { - // If we are here and toInstallVersion is empty, it means that they just ran Repair-WinGetPackageManager. - // When there is not version specified, we don't want to assume an empty version means latest, but in - // this particular case we need to. - if (string.IsNullOrEmpty(toInstallVersion)) - { - var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); - toInstallVersion = await gitHubClient.GetLatestReleaseTagNameAsync(false); - } - - var appxModule = new AppxModuleHelper(this); - await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force); - } - - private async Task InstallWinGetSourceAsync() - { - this.Write(StreamType.Verbose, "Installing winget source"); - var appxModule = new AppxModuleHelper(this); - await appxModule.InstallWinGetSourceAsync(); - } - - private async Task RegisterAsync(string toRegisterVersion, bool allUsers) - { - var appxModule = new AppxModuleHelper(this); - await appxModule.RegisterAppInstallerAsync(toRegisterVersion, allUsers); - } - - private void RepairEnvPath() - { - // Add windows app path to user PATH environment variable - Utilities.AddWindowsAppToPath(); - - // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. - string? envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); - string? envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); - string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; - this.SetVariable(EnvPath, newPwshPathEnv); - - this.Write(StreamType.Verbose, $"PATH environment variable updated"); - } - - private void ValidateWhenAllUsers(bool allUsers) - { - if (allUsers) - { - if (Utilities.ExecutingAsSystem) - { - throw new NotSupportedException(); - } - - if (!Utilities.ExecutingAsAdministrator) - { - throw new WinGetRepairException(Resources.RepairAllUsersMessage); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Commands +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using System.Threading.Tasks; + using Microsoft.WinGet.Client.Engine.Commands.Common; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + using static Microsoft.WinGet.Client.Engine.Common.Constants; + + /// + /// Used by Repair-WinGetPackageManager and Assert-WinGetPackageManager. + /// + public sealed class WinGetPackageManagerCommand : ManagementDeploymentCommand + { + private const string EnvPath = "env:PATH"; + + /// + /// Initializes a new instance of the class. + /// + /// Cmdlet being executed. + public WinGetPackageManagerCommand(PSCmdlet psCmdlet) + : base(psCmdlet) + { + } + + /// + /// Asserts winget version is the latest version on winget-cli. + /// + /// Use prerelease version on GitHub. + public void AssertUsingLatest(bool preRelease) + { + var runningTask = this.RunOnMTA( + async () => + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); + string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); + this.Assert(expectedVersion); + return true; + }); + + this.Wait(runningTask); + } + + /// + /// Asserts the version installed is the specified. + /// + /// The expected version. + public void Assert(string expectedVersion) + { + WinGetIntegrity.AssertWinGet(this, expectedVersion); + } + + /// + /// Repairs winget using the latest version on winget-cli. + /// + /// Use prerelease version on GitHub. + /// Install for all users. Requires admin. + /// Force application shutdown. + public void RepairUsingLatest(bool preRelease, bool allUsers, bool force) + { + this.ValidateWhenAllUsers(allUsers); + var runningTask = this.RunOnMTA( + async () => + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); + string expectedVersion = await gitHubClient.GetLatestReleaseTagNameAsync(preRelease); + await this.RepairStateMachineAsync(expectedVersion, allUsers, force); + return true; + }); + + this.Wait(runningTask); + } + + /// + /// Repairs winget if needed. + /// + /// The expected version, if any. + /// Install for all users. Requires admin. + /// Force application shutdown. + /// Include prerelease versions when matching version. + public void Repair(string expectedVersion, bool allUsers, bool force, bool includePrerelease) + { + this.ValidateWhenAllUsers(allUsers); + var runningTask = this.RunOnMTA( + async () => + { + if (!string.IsNullOrWhiteSpace(expectedVersion)) + { + this.Write(StreamType.Verbose, $"Attempting to resolve version '{expectedVersion}'"); + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); + try + { + var resolvedVersion = await gitHubClient.ResolveVersionAsync(expectedVersion, includePrerelease); + if (!string.IsNullOrEmpty(resolvedVersion)) + { + this.Write(StreamType.Verbose, $"Matching version found: {resolvedVersion}"); + expectedVersion = resolvedVersion!; + } + else + { + this.Write(StreamType.Warning, $"No matching version found for {expectedVersion}"); + } + } + catch (Exception ex) + { + this.Write(StreamType.Warning, $"Could not resolve version '{expectedVersion}': {ex.Message}"); + } + } + else + { + this.Write(StreamType.Verbose, "No version specified."); + } + + await this.RepairStateMachineAsync(expectedVersion, allUsers, force); + return true; + }); + this.Wait(runningTask); + } + + private async Task RepairStateMachineAsync(string expectedVersion, bool allUsers, bool force) + { + var seenCategories = new HashSet(); + var cancellationToken = this.GetCancellationToken(); + + var currentCategory = IntegrityCategory.Unknown; + while (currentCategory != IntegrityCategory.Installed) + { + cancellationToken.ThrowIfCancellationRequested(); + + try + { + WinGetIntegrity.AssertWinGet(this, expectedVersion); + this.Write(StreamType.Verbose, $"WinGet is in a good state."); + currentCategory = IntegrityCategory.Installed; + } + catch (WinGetIntegrityException e) + { + currentCategory = e.Category; + + if (seenCategories.Contains(currentCategory)) + { + this.Write(StreamType.Verbose, $"{currentCategory} encountered previously"); + throw; + } + + this.Write(StreamType.Verbose, $"Integrity category type: {currentCategory}"); + seenCategories.Add(currentCategory); + + switch (currentCategory) + { + case IntegrityCategory.UnexpectedVersion: + await this.InstallDifferentVersionAsync(new WinGetVersion(expectedVersion), e.InstalledVersion, allUsers, force); + break; + case IntegrityCategory.NotInPath: + this.RepairEnvPath(); + break; + case IntegrityCategory.AppInstallerNotRegistered: + await this.RegisterAsync(expectedVersion, allUsers); + break; + case IntegrityCategory.AppInstallerNotInstalled: + case IntegrityCategory.AppInstallerNotSupported: + case IntegrityCategory.Failure: + await this.InstallAsync(expectedVersion, allUsers, force); + break; + case IntegrityCategory.AppInstallerNoLicense: + // This requires -AllUsers in admin mode. + if (allUsers && Utilities.ExecutingAsAdministrator) + { + await this.InstallAsync(expectedVersion, allUsers, force); + } + else + { + throw new WinGetRepairException(e); + } + + break; + case IntegrityCategory.WinGetSourceNotInstalled: + await this.InstallWinGetSourceAsync(); + break; + case IntegrityCategory.AppExecutionAliasDisabled: + case IntegrityCategory.Unknown: + throw new WinGetRepairException(e); + default: + throw new NotSupportedException(); + } + } + } + } + + private async Task InstallDifferentVersionAsync(WinGetVersion toInstallVersion, WinGetVersion? installedVersion, bool allUsers, bool force) + { + if (installedVersion == null) + { + installedVersion = WinGetVersion.InstalledWinGetVersion(this); + } + + bool isDowngrade = installedVersion.CompareAsDeployment(toInstallVersion) > 0; + + string message = $"Installed WinGet version '{installedVersion.TagVersion}' " + + $"Installing WinGet version '{toInstallVersion.TagVersion}' " + + $"Is downgrade {isDowngrade}"; + this.Write( + StreamType.Verbose, + message); + var appxModule = new AppxModuleHelper(this); + await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion.TagVersion, allUsers, isDowngrade, force); + } + + private async Task InstallAsync(string toInstallVersion, bool allUsers, bool force) + { + // If we are here and toInstallVersion is empty, it means that they just ran Repair-WinGetPackageManager. + // When there is not version specified, we don't want to assume an empty version means latest, but in + // this particular case we need to. + if (string.IsNullOrEmpty(toInstallVersion)) + { + var gitHubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this); + toInstallVersion = await gitHubClient.GetLatestReleaseTagNameAsync(false); + } + + var appxModule = new AppxModuleHelper(this); + await appxModule.InstallFromGitHubReleaseAsync(toInstallVersion, allUsers, false, force); + } + + private async Task InstallWinGetSourceAsync() + { + this.Write(StreamType.Verbose, "Installing winget source"); + var appxModule = new AppxModuleHelper(this); + await appxModule.InstallWinGetSourceAsync(); + } + + private async Task RegisterAsync(string toRegisterVersion, bool allUsers) + { + var appxModule = new AppxModuleHelper(this); + await appxModule.RegisterAppInstallerAsync(toRegisterVersion, allUsers); + } + + private void RepairEnvPath() + { + // Add windows app path to user PATH environment variable + Utilities.AddWindowsAppToPath(); + + // Update this sessions PowerShell environment so the user doesn't have to restart the terminal. + string? envPathUser = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); + string? envPathMachine = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.Machine); + string newPwshPathEnv = $"{envPathMachine};{envPathUser}"; + this.SetVariable(EnvPath, newPwshPathEnv); + + this.Write(StreamType.Verbose, $"PATH environment variable updated"); + } + + private void ValidateWhenAllUsers(bool allUsers) + { + if (allUsers) + { + if (Utilities.ExecutingAsSystem) + { + throw new NotSupportedException(); + } + + if (!Utilities.ExecutingAsAdministrator) + { + throw new WinGetRepairException(Resources.RepairAllUsersMessage); + } + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs index b1f1510c43..ab100686fd 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Constants.cs @@ -1,69 +1,69 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Common -{ - /// - /// This class contains all of the configurable constants for this project. - /// - internal static class Constants - { - /// - /// WinGet package family name. - /// -#if USE_PROD_CLSIDS - public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; -#else - public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; -#endif - - /// - /// Winget executable name. - /// -#if USE_PROD_CLSIDS - public const string WinGetExe = "winget.exe"; -#else - public const string WinGetExe = "wingetdev.exe"; -#endif - - /// - /// Name of PATH environment variable. - /// - public const string PathEnvVar = "PATH"; - - /// - /// One MB. - /// - public const int OneMB = 1024 * 1024; - - /// - /// Repository owners. - /// - public class RepositoryOwner - { - /// - /// Microsoft org. - /// - public const string Microsoft = "microsoft"; - } - - /// - /// Repository names. - /// - public class RepositoryName - { - /// - /// https://github.com/microsoft/winget-cli . - /// - public const string WinGetCli = "winget-cli"; - - /// - /// https://github.com/microsoft/microsoft-ui-xaml . - /// - public const string UiXaml = "microsoft-ui-xaml"; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + /// + /// This class contains all of the configurable constants for this project. + /// + internal static class Constants + { + /// + /// WinGet package family name. + /// +#if USE_PROD_CLSIDS + public const string WingetPackageFamilyName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; +#else + public const string WingetPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; +#endif + + /// + /// Winget executable name. + /// +#if USE_PROD_CLSIDS + public const string WinGetExe = "winget.exe"; +#else + public const string WinGetExe = "wingetdev.exe"; +#endif + + /// + /// Name of PATH environment variable. + /// + public const string PathEnvVar = "PATH"; + + /// + /// One MB. + /// + public const int OneMB = 1024 * 1024; + + /// + /// Repository owners. + /// + public class RepositoryOwner + { + /// + /// Microsoft org. + /// + public const string Microsoft = "microsoft"; + } + + /// + /// Repository names. + /// + public class RepositoryName + { + /// + /// https://github.com/microsoft/winget-cli . + /// + public const string WinGetCli = "winget-cli"; + + /// + /// https://github.com/microsoft/microsoft-ui-xaml . + /// + public const string UiXaml = "microsoft-ui-xaml"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs index f5a927e49e..5286edc809 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/ErrorCode.cs @@ -1,59 +1,59 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Common -{ - /// - /// Error code constants. - /// - internal static class ErrorCode - { - /// - /// Error code for ERROR_FILE_NOT_FOUND. - /// - public const int FileNotFound = unchecked((int)0x80070002); - - /// - /// Error code for RPC_S_SERVER_UNAVAILABLE. - /// - public const int RpcServerUnavailable = unchecked((int)0x800706BA); - - /// - /// Error code for RPC_S_CALL_FAILED. - /// - public const int RpcCallFailed = unchecked((int)0x800706BE); - - /// - /// Error code for ERROR_PACKAGE_NOT_REGISTERED_FOR_USER. - /// - public const int PackageNotRegisteredForUser = unchecked((int)0x80073D35); - - /// - /// Error code for APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND. - /// - public const int NoRepairInfoFound = unchecked((int)0x8A150079); - - /// - /// Error code for APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE. - /// - public const int RepairNotApplicable = unchecked((int)0x8A15007A); - - /// - /// Error code for APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED . - /// - public const int RepairerFailure = unchecked((int)0x8A15007B); - - /// - /// Error code for APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED. - /// - public const int RepairNotSupported = unchecked((int)0x8A15007C); - - /// - /// Error code for APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED. - /// - public const int AdminContextRepairProhibited = unchecked((int)0x8A15007D); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + /// + /// Error code constants. + /// + internal static class ErrorCode + { + /// + /// Error code for ERROR_FILE_NOT_FOUND. + /// + public const int FileNotFound = unchecked((int)0x80070002); + + /// + /// Error code for RPC_S_SERVER_UNAVAILABLE. + /// + public const int RpcServerUnavailable = unchecked((int)0x800706BA); + + /// + /// Error code for RPC_S_CALL_FAILED. + /// + public const int RpcCallFailed = unchecked((int)0x800706BE); + + /// + /// Error code for ERROR_PACKAGE_NOT_REGISTERED_FOR_USER. + /// + public const int PackageNotRegisteredForUser = unchecked((int)0x80073D35); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_NO_REPAIR_INFO_FOUND. + /// + public const int NoRepairInfoFound = unchecked((int)0x8A150079); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_REPAIR_NOT_APPLICABLE. + /// + public const int RepairNotApplicable = unchecked((int)0x8A15007A); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_EXEC_REPAIR_FAILED . + /// + public const int RepairerFailure = unchecked((int)0x8A15007B); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_REPAIR_NOT_SUPPORTED. + /// + public const int RepairNotSupported = unchecked((int)0x8A15007C); + + /// + /// Error code for APPINSTALLER_CLI_ERROR_ADMIN_CONTEXT_REPAIR_PROHIBITED. + /// + public const int AdminContextRepairProhibited = unchecked((int)0x8A15007D); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs index 22fdebb85f..ea7549a953 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/IntegrityCategory.cs @@ -1,74 +1,74 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Common -{ - /// - /// The type of the integrity check failure. - /// - public enum IntegrityCategory - { - /// - /// WinGet is correctly installed. - /// - Installed, - - /// - /// The version installed is not what is expected. - /// - UnexpectedVersion, - - /// - /// Unknown reason. - /// - Unknown, - - /// - /// A failure resulted on a simple winget command that shouldn't happen. - /// - Failure, - - /// - /// WindowsAppPath not in PATH environment variable. - /// - NotInPath, - - /// - /// Winget's app execution alias disabled. - /// - AppExecutionAliasDisabled, - - /// - /// Windows OS is not supported. - /// - OsNotSupported, - - /// - /// AppInstaller package is not installed. - /// - AppInstallerNotInstalled, - - /// - /// AppInstaller package is not registered. - /// - AppInstallerNotRegistered, - - /// - /// Installed App Installer package is not supported. - /// - AppInstallerNotSupported, - - /// - /// No applicable license found. - /// - AppInstallerNoLicense, - - /// - /// WinGet source is not installed. - /// - WinGetSourceNotInstalled, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + /// + /// The type of the integrity check failure. + /// + public enum IntegrityCategory + { + /// + /// WinGet is correctly installed. + /// + Installed, + + /// + /// The version installed is not what is expected. + /// + UnexpectedVersion, + + /// + /// Unknown reason. + /// + Unknown, + + /// + /// A failure resulted on a simple winget command that shouldn't happen. + /// + Failure, + + /// + /// WindowsAppPath not in PATH environment variable. + /// + NotInPath, + + /// + /// Winget's app execution alias disabled. + /// + AppExecutionAliasDisabled, + + /// + /// Windows OS is not supported. + /// + OsNotSupported, + + /// + /// AppInstaller package is not installed. + /// + AppInstallerNotInstalled, + + /// + /// AppInstaller package is not registered. + /// + AppInstallerNotRegistered, + + /// + /// Installed App Installer package is not supported. + /// + AppInstallerNotSupported, + + /// + /// No applicable license found. + /// + AppInstallerNoLicense, + + /// + /// WinGet source is not installed. + /// + WinGetSourceNotInstalled, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Utilities.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Utilities.cs index 05b205ef5a..6d5df012ef 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Utilities.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/Utilities.cs @@ -1,225 +1,225 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Common -{ - using System; - using System.Collections; - using System.Collections.Generic; - using System.IO; - using System.Management.Automation; - using System.Security.Principal; - using System.Threading; - using Microsoft.WinGet.Resources; - using Newtonsoft.Json; - using Newtonsoft.Json.Linq; - - /// - /// This class contains various helper methods for this project. - /// - internal static class Utilities - { - /// - /// Gets a value indicating whether the current assembly is executing in an administrative context. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ExecutingAsAdministrator - { - get - { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new (identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - } - - /// - /// Gets a value indicating whether the current assembly is executing as a SYSTEM user. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ExecutingAsSystem - { - get - { - return WindowsIdentity.GetCurrent().IsSystem; - } - } - - /// - /// Gets a value indicating whether the current execution context will use in-proc winget. - /// - public static bool UsesInProcWinget - { - get - { - return ExecutingAsSystem; - } - } - - /// - /// Gets a value indicating whether the current thread is executing as STA. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ThreadIsSTA - { - get - { - return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; - } - } - - /// - /// Gets the windows app path for local app data. - /// - public static string LocalDataWindowsAppPath - { - get - { - return Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Microsoft\WindowsApps"); - } - } - - /// - /// Gets the windows app path for program files. - /// - public static string ProgramFilesWindowsAppPath - { - get - { - return Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%\WindowsApps"); - } - } - - /// - /// Throws if not running as admin. - /// - public static void VerifyAdmin() - { - if (!Utilities.ExecutingAsAdministrator) - { - throw new PSNotSupportedException(Resources.RequiresAdminMessage); - } - } - - /// - /// Adds the WindowsApp local app data path to the user environment path. - /// - public static void AddWindowsAppToPath() - { - var scope = EnvironmentVariableTarget.User; - string? envPathValue = Environment.GetEnvironmentVariable(Constants.PathEnvVar, scope); - if (string.IsNullOrEmpty(envPathValue) || !envPathValue.Contains(Utilities.LocalDataWindowsAppPath)) - { - Environment.SetEnvironmentVariable( - Constants.PathEnvVar, - $"{envPathValue};{Utilities.LocalDataWindowsAppPath}", - scope); - } - } - - /// - /// This is based of https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs. - /// So we can convert JSON to Hashtable for Windows PowerShell and PowerShell Core. - /// - /// String content. - /// The hashtable. - public static Hashtable ConvertToHashtable(string content) - { - if (string.IsNullOrEmpty(content)) - { - return new Hashtable(); - } - - var obj = JsonConvert.DeserializeObject( - content, - new JsonSerializerSettings - { - // This TypeNameHandling setting is required to be secure. - TypeNameHandling = TypeNameHandling.None, - MetadataPropertyHandling = MetadataPropertyHandling.Ignore, - MaxDepth = 1024, - }); - - // It only makes sense that the deserialized object is a dictionary to start. - return obj switch - { - JObject dictionary => PopulateHashTableFromJDictionary(dictionary), - _ => throw new InvalidDataException() - }; - } - - private static Hashtable PopulateHashTableFromJDictionary(JObject entries) - { - Hashtable result = new (entries.Count); - foreach (var entry in entries) - { - switch (entry.Value) - { - case JArray list: - { - // Array - var listResult = PopulateHashTableFromJArray(list); - result.Add(entry.Key, listResult); - break; - } - - case JObject dic: - { - // Dictionary - var dicResult = PopulateHashTableFromJDictionary(dic); - result.Add(entry.Key, dicResult); - break; - } - - case JValue value: - { - result.Add(entry.Key, value.Value); - break; - } - } - } - - return result; - } - - private static ICollection PopulateHashTableFromJArray(JArray list) - { - var result = new object?[list.Count]; - - for (var index = 0; index < list.Count; index++) - { - var element = list[index]; - - switch (element) - { - case JArray array: - { - // Array - var listResult = PopulateHashTableFromJArray(array); - result[index] = listResult; - break; - } - - case JObject dic: - { - // Dictionary - var dicResult = PopulateHashTableFromJDictionary(dic); - result[index] = dicResult; - break; - } - - case JValue value: - { - result[index] = value.Value; - break; - } - } - } - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.IO; + using System.Management.Automation; + using System.Security.Principal; + using System.Threading; + using Microsoft.WinGet.Resources; + using Newtonsoft.Json; + using Newtonsoft.Json.Linq; + + /// + /// This class contains various helper methods for this project. + /// + internal static class Utilities + { + /// + /// Gets a value indicating whether the current assembly is executing in an administrative context. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ExecutingAsAdministrator + { + get + { + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new (identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + + /// + /// Gets a value indicating whether the current assembly is executing as a SYSTEM user. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ExecutingAsSystem + { + get + { + return WindowsIdentity.GetCurrent().IsSystem; + } + } + + /// + /// Gets a value indicating whether the current execution context will use in-proc winget. + /// + public static bool UsesInProcWinget + { + get + { + return ExecutingAsSystem; + } + } + + /// + /// Gets a value indicating whether the current thread is executing as STA. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ThreadIsSTA + { + get + { + return Thread.CurrentThread.GetApartmentState() == ApartmentState.STA; + } + } + + /// + /// Gets the windows app path for local app data. + /// + public static string LocalDataWindowsAppPath + { + get + { + return Environment.ExpandEnvironmentVariables(@"%LOCALAPPDATA%\Microsoft\WindowsApps"); + } + } + + /// + /// Gets the windows app path for program files. + /// + public static string ProgramFilesWindowsAppPath + { + get + { + return Environment.ExpandEnvironmentVariables(@"%PROGRAMFILES%\WindowsApps"); + } + } + + /// + /// Throws if not running as admin. + /// + public static void VerifyAdmin() + { + if (!Utilities.ExecutingAsAdministrator) + { + throw new PSNotSupportedException(Resources.RequiresAdminMessage); + } + } + + /// + /// Adds the WindowsApp local app data path to the user environment path. + /// + public static void AddWindowsAppToPath() + { + var scope = EnvironmentVariableTarget.User; + string? envPathValue = Environment.GetEnvironmentVariable(Constants.PathEnvVar, scope); + if (string.IsNullOrEmpty(envPathValue) || !envPathValue.Contains(Utilities.LocalDataWindowsAppPath)) + { + Environment.SetEnvironmentVariable( + Constants.PathEnvVar, + $"{envPathValue};{Utilities.LocalDataWindowsAppPath}", + scope); + } + } + + /// + /// This is based of https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.Commands.Utility/commands/utility/WebCmdlet/JsonObject.cs. + /// So we can convert JSON to Hashtable for Windows PowerShell and PowerShell Core. + /// + /// String content. + /// The hashtable. + public static Hashtable ConvertToHashtable(string content) + { + if (string.IsNullOrEmpty(content)) + { + return new Hashtable(); + } + + var obj = JsonConvert.DeserializeObject( + content, + new JsonSerializerSettings + { + // This TypeNameHandling setting is required to be secure. + TypeNameHandling = TypeNameHandling.None, + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + MaxDepth = 1024, + }); + + // It only makes sense that the deserialized object is a dictionary to start. + return obj switch + { + JObject dictionary => PopulateHashTableFromJDictionary(dictionary), + _ => throw new InvalidDataException() + }; + } + + private static Hashtable PopulateHashTableFromJDictionary(JObject entries) + { + Hashtable result = new (entries.Count); + foreach (var entry in entries) + { + switch (entry.Value) + { + case JArray list: + { + // Array + var listResult = PopulateHashTableFromJArray(list); + result.Add(entry.Key, listResult); + break; + } + + case JObject dic: + { + // Dictionary + var dicResult = PopulateHashTableFromJDictionary(dic); + result.Add(entry.Key, dicResult); + break; + } + + case JValue value: + { + result.Add(entry.Key, value.Value); + break; + } + } + } + + return result; + } + + private static ICollection PopulateHashTableFromJArray(JArray list) + { + var result = new object?[list.Count]; + + for (var index = 0; index < list.Count; index++) + { + var element = list[index]; + + switch (element) + { + case JArray array: + { + // Array + var listResult = PopulateHashTableFromJArray(array); + result[index] = listResult; + break; + } + + case JObject dic: + { + // Dictionary + var dicResult = PopulateHashTableFromJDictionary(dic); + result[index] = dicResult; + break; + } + + case JValue value: + { + result[index] = value.Value; + break; + } + } + } + + return result; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs index 90226a906e..e647db1049 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Common/WinGetIntegrity.cs @@ -1,185 +1,185 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Common -{ - using System; - using System.ComponentModel; - using System.IO; - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - - /// - /// Validates winget runs correctly. - /// - internal static class WinGetIntegrity - { - /// - /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. - /// - /// The calling cmdlet. - /// Expected version. - public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVersion) - { - // In-proc shouldn't have other dependencies and thus should be ok. - if (Utilities.UsesInProcWinget) - { - // Only check the OS version support for in-proc - if (!IsSupportedOSVersion()) - { - throw new WinGetIntegrityException(IntegrityCategory.OsNotSupported); - } - - return; - } - - WinGetCLICommandResult? versionResult = null; - - try - { - // Start by calling winget without its WindowsApp PFN path. - // If it succeeds and the exit code is 0 then we are good. - versionResult = WinGetVersion.RunWinGetVersionFromCLI(pwshCmdlet, false); - versionResult.VerifyExitCode(); - } - catch (Win32Exception e) - { - pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Win32Exception {e.Message}"); - throw new WinGetIntegrityException(GetReason(pwshCmdlet)); - } - catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) - { - pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' WinGetCLIException {e.Message}"); - throw new WinGetIntegrityException(IntegrityCategory.Failure, e); - } - catch (Exception e) - { - pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Exception {e.Message}"); - throw new WinGetIntegrityException(IntegrityCategory.Unknown, e); - } - - // WinGet is installed. Verify version if needed. - if (!string.IsNullOrEmpty(expectedVersion)) - { - // This assumes caller knows that the version exist. - WinGetVersion expectedWinGetVersion = new WinGetVersion(expectedVersion); - var installedVersion = WinGetVersion.InstalledWinGetVersion(pwshCmdlet, versionResult); - if (expectedWinGetVersion.CompareTo(installedVersion) != 0) - { - throw new WinGetIntegrityException( - IntegrityCategory.UnexpectedVersion, - string.Format( - Resources.IntegrityUnexpectedVersionMessage, - installedVersion.TagVersion, - expectedVersion)) - { InstalledVersion = installedVersion }; - } - } - - // Verify that the winget source is installed. - var appxModule = new AppxModuleHelper(pwshCmdlet); - if (!appxModule.IsWinGetSourceInstalled()) - { - throw new WinGetIntegrityException(IntegrityCategory.WinGetSourceNotInstalled); - } - } - - private static IntegrityCategory GetReason(PowerShellCmdlet pwshCmdlet) - { - // Ok, so you are here because calling winget --version failed. Lets try to figure out why. - var category = IntegrityCategory.Unknown; - pwshCmdlet.ExecuteInPowerShellThread(() => - { - // When running winget.exe on PowerShell the message of the Win32Exception will distinguish between - // 'The system cannot find the file specified' and 'No applicable app licenses found' but of course - // the HRESULT is the same (E_FAIL). - // To not compare strings let Powershell handle it. If calling winget throws an - // ApplicationFailedException then is most likely that the license is not there. - try - { - var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - ps.AddCommand("winget").Invoke(); - } - catch (ApplicationFailedException e) - { - pwshCmdlet.Write(StreamType.Verbose, e.Message); - category = IntegrityCategory.AppInstallerNoLicense; - } - catch (Exception) - { - } - }); - - if (category != IntegrityCategory.Unknown) - { - return category; - } - - // First lets check if the file is there, which means it is installed or someone is taking our place. - if (File.Exists(WingetCLIWrapper.WinGetFullPath)) - { - // The file exists, but we couldn't call it... Well maybe winget's app execution alias is not enabled. - // The trick is knowing that a magical file appears under WindowsApp when its enabled. - string wingetAliasPath = Path.Combine(Utilities.LocalDataWindowsAppPath, Constants.WinGetExe); - if (File.Exists(wingetAliasPath)) - { - // App execution alias is enabled. Then maybe the path? - string? envPath = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); - if (string.IsNullOrEmpty(envPath) || - !envPath.EndsWith(Utilities.LocalDataWindowsAppPath) || - !envPath.Contains($"{Utilities.LocalDataWindowsAppPath};")) - { - return IntegrityCategory.NotInPath; - } - } - else - { - return IntegrityCategory.AppExecutionAliasDisabled; - } - } - - // Not under %LOCALAPPDATA%\\Microsoft\\WindowsApps\PFN\ - - // Check OS version - if (!IsSupportedOSVersion()) - { - return IntegrityCategory.OsNotSupported; - } - - // It could be that AppInstaller package is old or the package is not - // registered at this point. To know that, call Get-AppxPackage. - var appxModule = new AppxModuleHelper(pwshCmdlet); - string? version = appxModule.GetAppInstallerPropertyValue("Version"); - if (version is null) - { - // This can happen in Windows Sandbox. - return IntegrityCategory.AppInstallerNotInstalled; - } - - // Now AppInstaller version has to be greater than 1.11.11451 - var minAppInstallerVersion = new Version(1, 11, 11451); - var appInstallerVersion = new Version(version); - if (appInstallerVersion.CompareTo(minAppInstallerVersion) < 0) - { - return IntegrityCategory.AppInstallerNotSupported; - } - - // If we get here, we know the package is in the machine but not registered for the user. - return IntegrityCategory.AppInstallerNotRegistered; - } - - private static bool IsSupportedOSVersion() - { - // Windows version has to be equal or newer than 10.0.17763.0 - var minWindowsVersion = new Version(10, 0, 17763, 0); - var osVersion = Environment.OSVersion.Version; - return osVersion.CompareTo(minWindowsVersion) >= 0; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Common +{ + using System; + using System.ComponentModel; + using System.IO; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + + /// + /// Validates winget runs correctly. + /// + internal static class WinGetIntegrity + { + /// + /// Verifies winget runs correctly. If it doesn't, tries to find the reason why it failed. + /// + /// The calling cmdlet. + /// Expected version. + public static void AssertWinGet(PowerShellCmdlet pwshCmdlet, string expectedVersion) + { + // In-proc shouldn't have other dependencies and thus should be ok. + if (Utilities.UsesInProcWinget) + { + // Only check the OS version support for in-proc + if (!IsSupportedOSVersion()) + { + throw new WinGetIntegrityException(IntegrityCategory.OsNotSupported); + } + + return; + } + + WinGetCLICommandResult? versionResult = null; + + try + { + // Start by calling winget without its WindowsApp PFN path. + // If it succeeds and the exit code is 0 then we are good. + versionResult = WinGetVersion.RunWinGetVersionFromCLI(pwshCmdlet, false); + versionResult.VerifyExitCode(); + } + catch (Win32Exception e) + { + pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Win32Exception {e.Message}"); + throw new WinGetIntegrityException(GetReason(pwshCmdlet)); + } + catch (Exception e) when (e is WinGetCLIException || e is WinGetCLITimeoutException) + { + pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' WinGetCLIException {e.Message}"); + throw new WinGetIntegrityException(IntegrityCategory.Failure, e); + } + catch (Exception e) + { + pwshCmdlet.Write(StreamType.Verbose, $"'winget.exe' Exception {e.Message}"); + throw new WinGetIntegrityException(IntegrityCategory.Unknown, e); + } + + // WinGet is installed. Verify version if needed. + if (!string.IsNullOrEmpty(expectedVersion)) + { + // This assumes caller knows that the version exist. + WinGetVersion expectedWinGetVersion = new WinGetVersion(expectedVersion); + var installedVersion = WinGetVersion.InstalledWinGetVersion(pwshCmdlet, versionResult); + if (expectedWinGetVersion.CompareTo(installedVersion) != 0) + { + throw new WinGetIntegrityException( + IntegrityCategory.UnexpectedVersion, + string.Format( + Resources.IntegrityUnexpectedVersionMessage, + installedVersion.TagVersion, + expectedVersion)) + { InstalledVersion = installedVersion }; + } + } + + // Verify that the winget source is installed. + var appxModule = new AppxModuleHelper(pwshCmdlet); + if (!appxModule.IsWinGetSourceInstalled()) + { + throw new WinGetIntegrityException(IntegrityCategory.WinGetSourceNotInstalled); + } + } + + private static IntegrityCategory GetReason(PowerShellCmdlet pwshCmdlet) + { + // Ok, so you are here because calling winget --version failed. Lets try to figure out why. + var category = IntegrityCategory.Unknown; + pwshCmdlet.ExecuteInPowerShellThread(() => + { + // When running winget.exe on PowerShell the message of the Win32Exception will distinguish between + // 'The system cannot find the file specified' and 'No applicable app licenses found' but of course + // the HRESULT is the same (E_FAIL). + // To not compare strings let Powershell handle it. If calling winget throws an + // ApplicationFailedException then is most likely that the license is not there. + try + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand("winget").Invoke(); + } + catch (ApplicationFailedException e) + { + pwshCmdlet.Write(StreamType.Verbose, e.Message); + category = IntegrityCategory.AppInstallerNoLicense; + } + catch (Exception) + { + } + }); + + if (category != IntegrityCategory.Unknown) + { + return category; + } + + // First lets check if the file is there, which means it is installed or someone is taking our place. + if (File.Exists(WingetCLIWrapper.WinGetFullPath)) + { + // The file exists, but we couldn't call it... Well maybe winget's app execution alias is not enabled. + // The trick is knowing that a magical file appears under WindowsApp when its enabled. + string wingetAliasPath = Path.Combine(Utilities.LocalDataWindowsAppPath, Constants.WinGetExe); + if (File.Exists(wingetAliasPath)) + { + // App execution alias is enabled. Then maybe the path? + string? envPath = Environment.GetEnvironmentVariable(Constants.PathEnvVar, EnvironmentVariableTarget.User); + if (string.IsNullOrEmpty(envPath) || + !envPath.EndsWith(Utilities.LocalDataWindowsAppPath) || + !envPath.Contains($"{Utilities.LocalDataWindowsAppPath};")) + { + return IntegrityCategory.NotInPath; + } + } + else + { + return IntegrityCategory.AppExecutionAliasDisabled; + } + } + + // Not under %LOCALAPPDATA%\\Microsoft\\WindowsApps\PFN\ + + // Check OS version + if (!IsSupportedOSVersion()) + { + return IntegrityCategory.OsNotSupported; + } + + // It could be that AppInstaller package is old or the package is not + // registered at this point. To know that, call Get-AppxPackage. + var appxModule = new AppxModuleHelper(pwshCmdlet); + string? version = appxModule.GetAppInstallerPropertyValue("Version"); + if (version is null) + { + // This can happen in Windows Sandbox. + return IntegrityCategory.AppInstallerNotInstalled; + } + + // Now AppInstaller version has to be greater than 1.11.11451 + var minAppInstallerVersion = new Version(1, 11, 11451); + var appInstallerVersion = new Version(version); + if (appInstallerVersion.CompareTo(minAppInstallerVersion) < 0) + { + return IntegrityCategory.AppInstallerNotSupported; + } + + // If we get here, we know the package is in the machine but not registered for the user. + return IntegrityCategory.AppInstallerNotRegistered; + } + + private static bool IsSupportedOSVersion() + { + // Windows version has to be equal or newer than 10.0.17763.0 + var minWindowsVersion = new Version(10, 0, 17763, 0); + var osVersion = Environment.OSVersion.Version; + return osVersion.CompareTo(minWindowsVersion) >= 0; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/CatalogConnectException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/CatalogConnectException.cs index 6f9ab0c53e..0e8f5789a2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/CatalogConnectException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/CatalogConnectException.cs @@ -1,28 +1,28 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Resources; - - /// - /// Failed connecting to catalog. - /// - [Serializable] - public class CatalogConnectException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - /// The exception that lead to this one. - public CatalogConnectException(Exception inner) - : base(Resources.CatalogConnectExceptionMessage, inner) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Resources; + + /// + /// Failed connecting to catalog. + /// + [Serializable] + public class CatalogConnectException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// The exception that lead to this one. + public CatalogConnectException(Exception inner) + : base(Resources.CatalogConnectExceptionMessage, inner) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/FindPackagesException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/FindPackagesException.cs index b139f32f54..bdcf2f6910 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/FindPackagesException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/FindPackagesException.cs @@ -1,37 +1,37 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Resources; - - /// - /// Raised when there is an error searching for packages. - /// - [Serializable] - internal class FindPackagesException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - /// A value. - public FindPackagesException(FindPackagesResultStatus status) - : base(string.Format( - Resources.FindPackagesExceptionMessage, - status.ToString())) - { - this.Status = status; - } - - /// - /// Gets the error status. - /// - public FindPackagesResultStatus Status { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Resources; + + /// + /// Raised when there is an error searching for packages. + /// + [Serializable] + internal class FindPackagesException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// A value. + public FindPackagesException(FindPackagesResultStatus status) + : base(string.Format( + Resources.FindPackagesExceptionMessage, + status.ToString())) + { + this.Status = status; + } + + /// + /// Gets the error status. + /// + public FindPackagesResultStatus Status { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidSourceException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidSourceException.cs index ca2680be6f..2a2a617def 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidSourceException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidSourceException.cs @@ -1,33 +1,33 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using Microsoft.WinGet.Resources; - - /// - /// Invalid source. - /// - [Serializable] - public class InvalidSourceException : ArgumentException - { - /// - /// Initializes a new instance of the class. - /// - /// Source name. - public InvalidSourceException(string sourceName) - : base(string.Format(Resources.InvalidSourceExceptionMessage, sourceName)) - { - this.SourceName = sourceName; - } - - /// - /// Gets the source name. - /// - public string SourceName { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using Microsoft.WinGet.Resources; + + /// + /// Invalid source. + /// + [Serializable] + public class InvalidSourceException : ArgumentException + { + /// + /// Initializes a new instance of the class. + /// + /// Source name. + public InvalidSourceException(string sourceName) + : base(string.Format(Resources.InvalidSourceExceptionMessage, sourceName)) + { + this.SourceName = sourceName; + } + + /// + /// Gets the source name. + /// + public string SourceName { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidVersionException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidVersionException.cs index 0dc69ae10f..7f3d93230e 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidVersionException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/InvalidVersionException.cs @@ -1,33 +1,33 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using Microsoft.WinGet.Resources; - - /// - /// Invalid version. - /// - [Serializable] - public class InvalidVersionException : ArgumentException - { - /// - /// Initializes a new instance of the class. - /// - /// Version. - public InvalidVersionException(string version) - : base(string.Format(Resources.InvalidVersionExceptionMessage, version)) - { - this.Version = version; - } - - /// - /// Gets the version. - /// - public string Version { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using Microsoft.WinGet.Resources; + + /// + /// Invalid version. + /// + [Serializable] + public class InvalidVersionException : ArgumentException + { + /// + /// Initializes a new instance of the class. + /// + /// Version. + public InvalidVersionException(string version) + : base(string.Format(Resources.InvalidVersionExceptionMessage, version)) + { + this.Version = version; + } + + /// + /// Gets the version. + /// + public string Version { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/NoPackageFoundException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/NoPackageFoundException.cs index 835356cf58..d42971c69b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/NoPackageFoundException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/NoPackageFoundException.cs @@ -1,27 +1,27 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Resources; - - /// - /// No package found. - /// - [Serializable] - public class NoPackageFoundException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - public NoPackageFoundException() - : base(Resources.NoPackageFoundExceptionMessage) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Resources; + + /// + /// No package found. + /// + [Serializable] + public class NoPackageFoundException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + public NoPackageFoundException() + : base(Resources.NoPackageFoundExceptionMessage) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/SingleThreadedApartmentException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/SingleThreadedApartmentException.cs index eeb6f594b4..ce01dffe54 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/SingleThreadedApartmentException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/SingleThreadedApartmentException.cs @@ -1,27 +1,27 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Resources; - - /// - /// No package found. - /// - [Serializable] - public class SingleThreadedApartmentException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - public SingleThreadedApartmentException() - : base(Resources.SingleThreadedApartmentNotSupportedMessage) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Resources; + + /// + /// No package found. + /// + [Serializable] + public class SingleThreadedApartmentException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + public SingleThreadedApartmentException() + : base(Resources.SingleThreadedApartmentNotSupportedMessage) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/UserSettingsReadException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/UserSettingsReadException.cs index 1bfb691798..8df5a23374 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/UserSettingsReadException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/UserSettingsReadException.cs @@ -1,36 +1,36 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Resources; - - /// - /// Settings.json file is invalid. - /// - [Serializable] - public class UserSettingsReadException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - public UserSettingsReadException() - : base(Resources.UserSettingsReadException) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Inner exception. - public UserSettingsReadException(Exception inner) - : base(Resources.UserSettingsReadException, inner) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Resources; + + /// + /// Settings.json file is invalid. + /// + [Serializable] + public class UserSettingsReadException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + public UserSettingsReadException() + : base(Resources.UserSettingsReadException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Inner exception. + public UserSettingsReadException(Exception inner) + : base(Resources.UserSettingsReadException, inner) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/VagueCriteriaException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/VagueCriteriaException.cs index f28b301e87..c4adf0b802 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/VagueCriteriaException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/VagueCriteriaException.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Collections.Generic; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Extensions; - using Microsoft.WinGet.Resources; - - /// - /// Raised when search criteria for installing or updating a package is too vague. - /// - [Serializable] - internal class VagueCriteriaException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - /// The list of conflicting packages of length at least two. - public VagueCriteriaException(IReadOnlyList results) - : base(string.Format( - Resources.VagueCriteriaExceptionMessage, - results[0].CatalogPackage.ToString(null), - results[1].CatalogPackage.ToString(null), - results.Count - 2)) - { - this.MatchResults = results; - } - - /// - /// Gets the list of conflicting packages. - /// - public IReadOnlyList MatchResults { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Collections.Generic; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Extensions; + using Microsoft.WinGet.Resources; + + /// + /// Raised when search criteria for installing or updating a package is too vague. + /// + [Serializable] + internal class VagueCriteriaException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// The list of conflicting packages of length at least two. + public VagueCriteriaException(IReadOnlyList results) + : base(string.Format( + Resources.VagueCriteriaExceptionMessage, + results[0].CatalogPackage.ToString(null), + results[1].CatalogPackage.ToString(null), + results.Count - 2)) + { + this.MatchResults = results; + } + + /// + /// Gets the list of conflicting packages. + /// + public IReadOnlyList MatchResults { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLIException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLIException.cs index b00b8f40b1..b9c9adca80 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLIException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLIException.cs @@ -1,60 +1,60 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System.Management.Automation; - using Microsoft.WinGet.Resources; - - /// - /// WinGet cli exception. - /// - public class WinGetCLIException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - /// Command. - /// Parameters. - /// Exit code. - /// Standard output. - /// Standard error. - public WinGetCLIException(string command, string? parameters, int exitCode, string stdOut, string stdErr) - : base(string.Format(Resources.WinGetCLIExceptionMessage, command, parameters, exitCode)) - { - this.Command = command; - this.Parameters = parameters; - this.ExitCode = exitCode; - this.StdOut = stdOut; - this.StdErr = stdErr; - } - - /// - /// Gets the command. - /// - public string Command { get; private set; } - - /// - /// Gets the parameters. - /// - public string? Parameters { get; private set; } - - /// - /// Gets the exit code. - /// - public int ExitCode { get; private set; } - - /// - /// Gets the standard output. - /// - public string StdOut { get; private set; } - - /// - /// Gets the standard error. - /// - public string StdErr { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System.Management.Automation; + using Microsoft.WinGet.Resources; + + /// + /// WinGet cli exception. + /// + public class WinGetCLIException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// Command. + /// Parameters. + /// Exit code. + /// Standard output. + /// Standard error. + public WinGetCLIException(string command, string? parameters, int exitCode, string stdOut, string stdErr) + : base(string.Format(Resources.WinGetCLIExceptionMessage, command, parameters, exitCode)) + { + this.Command = command; + this.Parameters = parameters; + this.ExitCode = exitCode; + this.StdOut = stdOut; + this.StdErr = stdErr; + } + + /// + /// Gets the command. + /// + public string Command { get; private set; } + + /// + /// Gets the parameters. + /// + public string? Parameters { get; private set; } + + /// + /// Gets the exit code. + /// + public int ExitCode { get; private set; } + + /// + /// Gets the standard output. + /// + public string StdOut { get; private set; } + + /// + /// Gets the standard error. + /// + public string StdErr { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLITimeoutException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLITimeoutException.cs index 38f36dff0d..05a8fab7c3 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLITimeoutException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetCLITimeoutException.cs @@ -1,27 +1,27 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using Microsoft.WinGet.Resources; - - /// - /// Time out exception for a winget cli command. - /// - public class WinGetCLITimeoutException : TimeoutException - { - /// - /// Initializes a new instance of the class. - /// - /// Command. - /// Parameters. - public WinGetCLITimeoutException(string command, string? parameters) - : base(string.Format(Resources.WinGetCLITimeoutExceptionMessage, command, parameters)) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using Microsoft.WinGet.Resources; + + /// + /// Time out exception for a winget cli command. + /// + public class WinGetCLITimeoutException : TimeoutException + { + /// + /// Initializes a new instance of the class. + /// + /// Command. + /// Parameters. + public WinGetCLITimeoutException(string command, string? parameters) + : base(string.Format(Resources.WinGetCLITimeoutExceptionMessage, command, parameters)) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs index 4ffee8283d..5b1415674f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetIntegrityException.cs @@ -1,75 +1,75 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Helpers; - using Microsoft.WinGet.Resources; - - /// - /// WinGet Integrity exception. - /// - public class WinGetIntegrityException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - /// Category failure. - public WinGetIntegrityException(IntegrityCategory category) - : base(GetMessage(category)) - { - this.Category = category; - } - - /// - /// Initializes a new instance of the class. - /// - /// Category failure. - /// Inner exception. - public WinGetIntegrityException(IntegrityCategory category, Exception inner) - : base(GetMessage(category), inner) - { - this.Category = category; - } - - /// - /// Initializes a new instance of the class. - /// - /// Category failure. - /// Message. - public WinGetIntegrityException(IntegrityCategory category, string message) - : base(message) - { - this.Category = category; - } - - /// - /// Gets the category of the integrity failure. - /// - public IntegrityCategory Category { get; } - - /// - /// Gets or sets the installed version. - /// - internal WinGetVersion? InstalledVersion { get; set; } - - private static string GetMessage(IntegrityCategory category) => category switch - { - IntegrityCategory.Failure => Resources.IntegrityFailureMessage, - IntegrityCategory.NotInPath => Resources.IntegrityNotInPathMessage, - IntegrityCategory.AppExecutionAliasDisabled => Resources.IntegrityAppExecutionAliasDisabledMessage, - IntegrityCategory.OsNotSupported => Resources.IntegrityOsNotSupportedMessage, - IntegrityCategory.AppInstallerNotInstalled => Resources.IntegrityAppInstallerNotInstalledMessage, - IntegrityCategory.AppInstallerNotRegistered => Resources.IntegrityAppInstallerNotRegisteredMessage, - IntegrityCategory.AppInstallerNotSupported => Resources.IntegrityAppInstallerNotSupportedMessage, - IntegrityCategory.AppInstallerNoLicense => Resources.IntegrityAppInstallerLicense, - _ => Resources.IntegrityUnknownMessage, - }; - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Helpers; + using Microsoft.WinGet.Resources; + + /// + /// WinGet Integrity exception. + /// + public class WinGetIntegrityException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// Category failure. + public WinGetIntegrityException(IntegrityCategory category) + : base(GetMessage(category)) + { + this.Category = category; + } + + /// + /// Initializes a new instance of the class. + /// + /// Category failure. + /// Inner exception. + public WinGetIntegrityException(IntegrityCategory category, Exception inner) + : base(GetMessage(category), inner) + { + this.Category = category; + } + + /// + /// Initializes a new instance of the class. + /// + /// Category failure. + /// Message. + public WinGetIntegrityException(IntegrityCategory category, string message) + : base(message) + { + this.Category = category; + } + + /// + /// Gets the category of the integrity failure. + /// + public IntegrityCategory Category { get; } + + /// + /// Gets or sets the installed version. + /// + internal WinGetVersion? InstalledVersion { get; set; } + + private static string GetMessage(IntegrityCategory category) => category switch + { + IntegrityCategory.Failure => Resources.IntegrityFailureMessage, + IntegrityCategory.NotInPath => Resources.IntegrityNotInPathMessage, + IntegrityCategory.AppExecutionAliasDisabled => Resources.IntegrityAppExecutionAliasDisabledMessage, + IntegrityCategory.OsNotSupported => Resources.IntegrityOsNotSupportedMessage, + IntegrityCategory.AppInstallerNotInstalled => Resources.IntegrityAppInstallerNotInstalledMessage, + IntegrityCategory.AppInstallerNotRegistered => Resources.IntegrityAppInstallerNotRegisteredMessage, + IntegrityCategory.AppInstallerNotSupported => Resources.IntegrityAppInstallerNotSupportedMessage, + IntegrityCategory.AppInstallerNoLicense => Resources.IntegrityAppInstallerLicense, + _ => Resources.IntegrityUnknownMessage, + }; + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairException.cs index b3513b0b81..8f9180bb90 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairException.cs @@ -1,70 +1,70 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Resources; - - /// - /// WinGet repair exception. - /// - [Serializable] - public class WinGetRepairException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - /// Integrity exception. - public WinGetRepairException(WinGetIntegrityException ie) - : base(GetMessage(ie), ie) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Inner exception. - public WinGetRepairException(Exception e) - : base(Resources.RepairFailureMessage, e) - { - } - - /// - /// Initializes a new instance of the class. - /// - public WinGetRepairException() - : base(Resources.RepairFailureMessage) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Message.. - public WinGetRepairException(string message) - : base(message) - { - } - - private static string GetMessage(WinGetIntegrityException ie) - { - string message = Resources.RepairFailureMessage; - if (ie.Category == IntegrityCategory.AppInstallerNoLicense) - { - message += $" {Resources.RepairAllUsersHelpMessage}"; - } - else if (ie.Category == IntegrityCategory.AppExecutionAliasDisabled) - { - message += $" {Resources.RepairAppExecutionAliasMessage}"; - } - - return message; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Resources; + + /// + /// WinGet repair exception. + /// + [Serializable] + public class WinGetRepairException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// Integrity exception. + public WinGetRepairException(WinGetIntegrityException ie) + : base(GetMessage(ie), ie) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Inner exception. + public WinGetRepairException(Exception e) + : base(Resources.RepairFailureMessage, e) + { + } + + /// + /// Initializes a new instance of the class. + /// + public WinGetRepairException() + : base(Resources.RepairFailureMessage) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Message.. + public WinGetRepairException(string message) + : base(message) + { + } + + private static string GetMessage(WinGetIntegrityException ie) + { + string message = Resources.RepairFailureMessage; + if (ie.Category == IntegrityCategory.AppInstallerNoLicense) + { + message += $" {Resources.RepairAllUsersHelpMessage}"; + } + else if (ie.Category == IntegrityCategory.AppExecutionAliasDisabled) + { + message += $" {Resources.RepairAppExecutionAliasMessage}"; + } + + return message; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs index 76071d645c..c8bf6457b1 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WinGetRepairPackageException.cs @@ -1,79 +1,79 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Resources; - - /// - /// WinGetRepairPackageException. - /// - [Serializable] - public class WinGetRepairPackageException : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - /// Repair operation ExtendedErrorCode Hresult. - public WinGetRepairPackageException(int hresult) - : base(GetMessage(hresult)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Repair operation ExtendedErrorCode Hresult. - /// Repairer exit code. - public WinGetRepairPackageException(int hresult, uint repairerExitCode) - : base(GetMessage(hresult, repairerExitCode)) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Repair operation ExtendedErrorCode Hresult. - /// InnerException. - public WinGetRepairPackageException(int hresult, Exception innerException) - : base(GetMessage(hresult), innerException) - { - } - - /// - /// Initializes a new instance of the class. - /// - /// Repair operation ExtendedErrorCode Hresult. - /// Repairer exit code. - /// InnerException. - public WinGetRepairPackageException(int hresult, uint repairerExitCode, Exception innerException) - : base(GetMessage(hresult, repairerExitCode), innerException) - { - } - - private static string GetMessage(int hresult, uint repairerExitCode = 0) - { - switch (hresult) - { - case ErrorCode.NoRepairInfoFound: - return Resources.NoRepairInfoFound; - case ErrorCode.RepairerFailure: - return string.Format(Resources.RepairerFailure, repairerExitCode); - case ErrorCode.RepairNotSupported: - return Resources.RepairOperationNotSupported; - case ErrorCode.RepairNotApplicable: - return Resources.RepairDifferentInstallTechnology; - case ErrorCode.AdminContextRepairProhibited: - return Resources.NoAdminRepairForUserScopePackage; - default: - return string.Format(Resources.UnknownRepairFailure, hresult); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Resources; + + /// + /// WinGetRepairPackageException. + /// + [Serializable] + public class WinGetRepairPackageException : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + public WinGetRepairPackageException(int hresult) + : base(GetMessage(hresult)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + /// Repairer exit code. + public WinGetRepairPackageException(int hresult, uint repairerExitCode) + : base(GetMessage(hresult, repairerExitCode)) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + /// InnerException. + public WinGetRepairPackageException(int hresult, Exception innerException) + : base(GetMessage(hresult), innerException) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// Repair operation ExtendedErrorCode Hresult. + /// Repairer exit code. + /// InnerException. + public WinGetRepairPackageException(int hresult, uint repairerExitCode, Exception innerException) + : base(GetMessage(hresult, repairerExitCode), innerException) + { + } + + private static string GetMessage(int hresult, uint repairerExitCode = 0) + { + switch (hresult) + { + case ErrorCode.NoRepairInfoFound: + return Resources.NoRepairInfoFound; + case ErrorCode.RepairerFailure: + return string.Format(Resources.RepairerFailure, repairerExitCode); + case ErrorCode.RepairNotSupported: + return Resources.RepairOperationNotSupported; + case ErrorCode.RepairNotApplicable: + return Resources.RepairDifferentInstallTechnology; + case ErrorCode.AdminContextRepairProhibited: + return Resources.NoAdminRepairForUserScopePackage; + default: + return string.Format(Resources.UnknownRepairFailure, hresult); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WindowsPowerShellNotSupported.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WindowsPowerShellNotSupported.cs index ca5d3a538a..5ee570a599 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WindowsPowerShellNotSupported.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Exceptions/WindowsPowerShellNotSupported.cs @@ -1,27 +1,27 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Exceptions -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Resources; - - /// - /// Windows PowerShell is not supported. - /// - [Serializable] - public class WindowsPowerShellNotSupported : RuntimeException - { - /// - /// Initializes a new instance of the class. - /// - public WindowsPowerShellNotSupported() - : base(Resources.WindowsPowerShellNotSupported) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Exceptions +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Resources; + + /// + /// Windows PowerShell is not supported. + /// + [Serializable] + public class WindowsPowerShellNotSupported : RuntimeException + { + /// + /// Initializes a new instance of the class. + /// + public WindowsPowerShellNotSupported() + : base(Resources.WindowsPowerShellNotSupported) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/CatalogPackageExtensions.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/CatalogPackageExtensions.cs index 987e23e4c6..cda2cd6e2d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/CatalogPackageExtensions.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/CatalogPackageExtensions.cs @@ -1,60 +1,60 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Extensions -{ - using Microsoft.Management.Deployment; - - /// - /// Extensions for the class. - /// - internal static class CatalogPackageExtensions - { - /// - /// Converts a to a string previewing the specified version. - /// - /// A instance. - /// A instance. If null, the latest available version is used. - /// A instance. - public static string ToString( - this CatalogPackage package, - PackageVersionId? version) - { - if ((version != null) || (package.AvailableVersions.Count > 0)) - { - string versionString = (version is null) - ? package.AvailableVersions[0].Version - : version.Version; - return $"{package.Name} [{package.Id}] Version {versionString}"; - } - else - { - // There were no available versions! - return $"{package.Name} [{package.Id}]"; - } - } - - /// - /// Gets the best effort source name of a that matches its Id. - /// This source name is used together with Id in operation output classes for display purposes. - /// - /// A instance. - /// The best effort source name of the package. - public static string? GetSourceName(this CatalogPackage package) - { - for (int i = 0; i < package.AvailableVersions.Count; ++i) - { - var versionInfo = package.GetPackageVersionInfo(package.AvailableVersions[i]); - if (versionInfo.Id == package.Id) - { - return versionInfo.PackageCatalog.Info.Name; - } - } - - return null; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Extensions +{ + using Microsoft.Management.Deployment; + + /// + /// Extensions for the class. + /// + internal static class CatalogPackageExtensions + { + /// + /// Converts a to a string previewing the specified version. + /// + /// A instance. + /// A instance. If null, the latest available version is used. + /// A instance. + public static string ToString( + this CatalogPackage package, + PackageVersionId? version) + { + if ((version != null) || (package.AvailableVersions.Count > 0)) + { + string versionString = (version is null) + ? package.AvailableVersions[0].Version + : version.Version; + return $"{package.Name} [{package.Id}] Version {versionString}"; + } + else + { + // There were no available versions! + return $"{package.Name} [{package.Id}]"; + } + } + + /// + /// Gets the best effort source name of a that matches its Id. + /// This source name is used together with Id in operation output classes for display purposes. + /// + /// A instance. + /// The best effort source name of the package. + public static string? GetSourceName(this CatalogPackage package) + { + for (int i = 0; i < package.AvailableVersions.Count; ++i) + { + var versionInfo = package.GetPackageVersionInfo(package.AvailableVersions[i]); + if (versionInfo.Id == package.Id) + { + return versionInfo.PackageCatalog.Info.Name; + } + } + + return null; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs index 15c3c704c9..4c0a5e7807 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Extensions/ReleaseExtensions.cs @@ -1,67 +1,67 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Extensions -{ - using System.Linq; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Resources; - using Octokit; - - /// - /// Extension methods for Octokit.Release. - /// - internal static class ReleaseExtensions - { - /// - /// Gets the Asset. - /// - /// GitHub release. - /// Name of asset. - /// The asset. - public static ReleaseAsset GetAsset(this Release release, string name) - { - var asset = TryGetAsset(release, name); - - if (asset != null) - { - return asset; - } - - throw new WinGetRepairException(string.Format(Resources.ReleaseAssetNotFound, name)); - } - - /// - /// Gets the Asset if present. - /// - /// GitHub release. - /// Name of asset. - /// The asset, or null if not found. - public static ReleaseAsset? TryGetAsset(this Release release, string name) - { - var assets = release.Assets.Where(a => a.Name == name); - return assets.Any() ? assets.First() : null; - } - - /// - /// Gets the asset that ends with the string. - /// - /// GitHub release. - /// Asset last part name. - /// The asset. - public static ReleaseAsset GetAssetEndsWith(this Release release, string name) - { - var assets = release.Assets.Where(a => a.Name.EndsWith(name)); - - if (assets.Any()) - { - return assets.First(); - } - - throw new WinGetRepairException(string.Format(Resources.ReleaseAssetNotFound, name)); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Extensions +{ + using System.Linq; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Resources; + using Octokit; + + /// + /// Extension methods for Octokit.Release. + /// + internal static class ReleaseExtensions + { + /// + /// Gets the Asset. + /// + /// GitHub release. + /// Name of asset. + /// The asset. + public static ReleaseAsset GetAsset(this Release release, string name) + { + var asset = TryGetAsset(release, name); + + if (asset != null) + { + return asset; + } + + throw new WinGetRepairException(string.Format(Resources.ReleaseAssetNotFound, name)); + } + + /// + /// Gets the Asset if present. + /// + /// GitHub release. + /// Name of asset. + /// The asset, or null if not found. + public static ReleaseAsset? TryGetAsset(this Release release, string name) + { + var assets = release.Assets.Where(a => a.Name == name); + return assets.Any() ? assets.First() : null; + } + + /// + /// Gets the asset that ends with the string. + /// + /// GitHub release. + /// Asset last part name. + /// The asset. + public static ReleaseAsset GetAssetEndsWith(this Release release, string name) + { + var assets = release.Assets.Where(a => a.Name.EndsWith(name)); + + if (assets.Any()) + { + return assets.First(); + } + + throw new WinGetRepairException(string.Format(Resources.ReleaseAssetNotFound, name)); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs index b5337c3f8c..3fb91246be 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/AppxModuleHelper.cs @@ -1,864 +1,864 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using System.Collections.ObjectModel; - using System.IO; - using System.IO.Compression; - using System.Linq; - using System.Management.Automation; - using System.Runtime.InteropServices; - using System.Threading.Tasks; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Extensions; - using Microsoft.WinGet.Common.Command; - using Newtonsoft.Json; - using Octokit; - using Semver; - using static Microsoft.WinGet.Client.Engine.Common.Constants; - - /// - /// Helper to make calls to the Appx module. - /// - internal class AppxModuleHelper - { - // Cmdlets - private const string ImportModule = "Import-Module"; - private const string GetAppxPackage = "Get-AppxPackage"; - private const string AddAppxPackage = "Add-AppxPackage"; - private const string AddAppxProvisionedPackage = "Add-AppxProvisionedPackage"; - private const string GetCommand = "Get-Command"; - - // Parameters name - private const string Name = "Name"; - private const string Path = "Path"; - private const string ErrorAction = "ErrorAction"; - private const string WarningAction = "WarningAction"; - private const string PackagePath = "PackagePath"; - private const string LicensePath = "LicensePath"; - private const string Module = "Module"; - private const string StubPackageOption = "StubPackageOption"; - private const string PackageTypeFilter = "PackageTypeFilter"; - - // Parameter Values - private const string Appx = "Appx"; - private const string Stop = "Stop"; - private const string SilentlyContinue = "SilentlyContinue"; - private const string Online = "Online"; - private const string UsePreference = "UsePreference"; - private const string Framework = "Framework"; - - // Options - private const string UseWindowsPowerShell = "UseWindowsPowerShell"; - private const string ForceUpdateFromAnyVersion = "ForceUpdateFromAnyVersion"; - private const string Register = "Register"; - private const string DisableDevelopmentMode = "DisableDevelopmentMode"; - private const string ForceTargetApplicationShutdown = "ForceTargetApplicationShutdown"; - private const string AllUsers = "AllUsers"; - - private const string AppInstallerName = "Microsoft.DesktopAppInstaller"; - private const string AppxManifest = "AppxManifest.xml"; - private const string PackageFullName = "PackageFullName"; - private const string Version = "Version"; - - private const string DependencyArchitectureEnvironmentVariable = "WINGET_PACKAGE_MANAGER_REPAIR_DEPENDENCY_ARCHITECTURES"; - - // Assets - private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; - private const string DependenciesJsonName = "DesktopAppInstaller_Dependencies.json"; - private const string DependenciesZipName = "DesktopAppInstaller_Dependencies.zip"; - private const string License = "License1.xml"; - - // Format of a dependency package such as 'x64\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64.appx' - private const string ExtractedDependencyPath = "{0}\\{1}_{2}_{0}.appx"; - - // Dependencies - // VCLibs - private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop"; - private const string VCLibsUWPDesktopVersion = "14.0.30704.0"; - private const string VCLibsUWPDesktopX64 = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"; - private const string VCLibsUWPDesktopX86 = "https://aka.ms/Microsoft.VCLibs.x86.14.00.Desktop.appx"; - private const string VCLibsUWPDesktopArm64 = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx"; - - // Xaml - private const string XamlPackage28 = "Microsoft.UI.Xaml.2.8"; - private const string XamlReleaseTag286 = "v2.8.6"; - private const string MinimumWinGetReleaseTagForXaml28 = "v1.7.10514"; - - private const string XamlPackage27 = "Microsoft.UI.Xaml.2.7"; - private const string XamlReleaseTag273 = "v2.7.3"; - - // WinGet Source - private const string WinGetSourceName = "Microsoft.Winget.Source"; - private const string WinGetSourceMsixName = "source2.msix"; - private const string WinGetSourceUrl = $"https://cdn.winget.microsoft.com/cache/{WinGetSourceMsixName}"; - - private readonly PowerShellCmdlet pwshCmdlet; - private readonly HttpClientHelper httpClientHelper; - private Lazy> frameworkArchitectures; - - /// - /// Initializes a new instance of the class. - /// - /// The calling cmdlet. - public AppxModuleHelper(PowerShellCmdlet pwshCmdlet) - { - this.pwshCmdlet = pwshCmdlet; - this.httpClientHelper = new HttpClientHelper(); - this.frameworkArchitectures = new Lazy>(() => this.InitFrameworkArchitectures()); - } - - /// - /// Calls Get-AppxPackage Microsoft.DesktopAppInstaller. - /// - /// Whether to get for all users. - /// Result of Get-AppxPackage. - public PSObject? GetAppInstallerObject(bool allUsers = false) - { - return this.GetAppxObject(AppInstallerName, allUsers); - } - - /// - /// Calls Get-AppxPackage Microsoft.Winget.Source. - /// - /// Result of Get-AppxPackage. - public PSObject? GetWinGetSourceObject() - { - return this.GetAppxObject(WinGetSourceName); - } - - /// - /// Gets the string value a property from the Get-AppxPackage object of AppInstaller. - /// - /// Property name. - /// Whether to get for all users. - /// Value, null if doesn't exist. - public string? GetAppInstallerPropertyValue(string propertyName, bool allUsers = false) - { - string? result = null; - var packageObj = this.GetAppInstallerObject(allUsers); - if (packageObj is not null) - { - var property = packageObj.Properties.Where(p => p.Name == propertyName).FirstOrDefault(); - if (property is not null) - { - result = property.Value as string; - } - } - - return result; - } - - /// - /// Checks if winget source is installed. - /// - /// True if installed. - public bool IsWinGetSourceInstalled() - { - return this.GetWinGetSourceObject() is not null; - } - - /// - /// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml. - /// - /// Release tag of GitHub release. - /// Whether to register for all users. - /// A representing the asynchronous operation. - public async Task RegisterAppInstallerAsync(string releaseTag, bool allUsers) - { - if (string.IsNullOrEmpty(releaseTag)) - { - string? versionFromLocalPackage = this.GetAppInstallerPropertyValue(Version, allUsers); - - if (versionFromLocalPackage == null) - { - throw new ArgumentNullException(Version); - } - - var packageVersion = new Version(versionFromLocalPackage); - if (packageVersion.Major == 1 && packageVersion.Minor > 15 && packageVersion.Minor < 28) - { - releaseTag = $"v1.{packageVersion.Minor - 15}.{packageVersion.Build}"; - } - else - { - releaseTag = $"v{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}"; - } - } - - // Ensure that all dependencies are present when attempting to register. - // If dependencies are missing, a provisioned package can appear to only need registration, - // but will fail to register. `InstallDependenciesAsync` checks for the packages before - // acting, so it should be mostly a no-op if they are already available. - await this.InstallDependenciesAsync(releaseTag); - - this.RegisterAppInstallerInternal(allUsers); - } - - /// - /// Install AppInstaller's bundle from a GitHub release. - /// - /// Release tag of GitHub release. - /// If install for all users is needed. - /// Is downgrade. - /// Force application shutdown. - /// A representing the asynchronous operation. - public async Task InstallFromGitHubReleaseAsync(string releaseTag, bool allUsers, bool isDowngrade, bool force) - { - await this.InstallDependenciesAsync(releaseTag); - - if (isDowngrade) - { - // Add-AppxProvisionedPackage doesn't support downgrade. - await this.AddAppInstallerBundleAsync(releaseTag, true, force); - - if (allUsers) - { - await this.AddProvisionPackageAsync(releaseTag); - } - } - else - { - if (allUsers) - { - await this.AddProvisionPackageAsync(releaseTag); - } - else - { - await this.AddAppInstallerBundleAsync(releaseTag, false, force); - } - } - } - - /// - /// Installs the WinGet source by downloading and adding package. - /// - /// A representing the asynchronous operation. - public async Task InstallWinGetSourceAsync() - { - await this.DownloadPackageAndAddAsync(WinGetSourceUrl, WinGetSourceMsixName, options: null); - } - - /// - /// Gets the Xaml dependency package name and release tag based on the provided WinGet release tag. - /// - /// WinGet release tag. - /// A tuple in the format of (XamlPackageName, XamlReleaseTag). - private static Tuple GetXamlDependencyVersionInfo(string releaseTag) - { - var targetVersion = SemVersion.Parse(releaseTag, SemVersionStyles.AllowLowerV); - - if (targetVersion.CompareSortOrderTo(SemVersion.Parse(MinimumWinGetReleaseTagForXaml28, SemVersionStyles.AllowLowerV)) >= 0) - { - return Tuple.Create(XamlPackage28, XamlReleaseTag286); - } - else - { - return Tuple.Create(XamlPackage27, XamlReleaseTag273); - } - } - - private async Task AddProvisionPackageAsync(string releaseTag) - { - var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this.pwshCmdlet); - var release = await githubClient.GetReleaseAsync(releaseTag); - - var bundleAsset = release.GetAsset(MsixBundleName); - using var bundleFile = new TempFile(fileName: MsixBundleName); - await this.httpClientHelper.DownloadUrlWithProgressAsync( - bundleAsset.BrowserDownloadUrl, bundleFile.FullPath, this.pwshCmdlet); - - var licenseAsset = release.GetAssetEndsWith(License); - using var licenseFile = new TempFile(fileName: licenseAsset.Name); - await this.httpClientHelper.DownloadUrlWithProgressAsync( - licenseAsset.BrowserDownloadUrl, licenseFile.FullPath, this.pwshCmdlet); - - try - { - this.pwshCmdlet.ExecuteInPowerShellThread( - () => - { - var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - ps.AddCommand(AddAppxProvisionedPackage) - .AddParameter(Online) - .AddParameter(PackagePath, bundleFile.FullPath) - .AddParameter(LicensePath, licenseFile.FullPath) - .AddParameter(ErrorAction, Stop) - .Invoke(); - }); - - // Register the package after provisioning so that it is - // available immediately. - this.RegisterAppInstallerInternal(allUsers: true); - } - catch (RuntimeException e) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Failed installing bundle via Add-AppxProvisionedPackage {e}"); - throw; - } - } - - private async Task AddAppInstallerBundleAsync(string releaseTag, bool downgrade, bool force) - { - var options = new List(); - if (downgrade) - { - options.Add(ForceUpdateFromAnyVersion); - } - - if (force) - { - options.Add(ForceTargetApplicationShutdown); - } - - var parameters = new Dictionary(); - if (this.IsStubPackageOptionPresent()) - { - parameters.Add(StubPackageOption, UsePreference); - } - - try - { - var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this.pwshCmdlet); - var release = await githubClient.GetReleaseAsync(releaseTag); - - var bundleAsset = release.GetAsset(MsixBundleName); - await this.AddAppxPackageAsUriAsync(bundleAsset.BrowserDownloadUrl, MsixBundleName, parameters, options); - } - catch (RuntimeException e) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Failed installing bundle via Add-AppxPackage {e}"); - throw; - } - } - - private PSObject? GetAppxObject(string packageName, bool allUsers = false) - { - var options = new List(); - if (allUsers) - { - options.Add(AllUsers); - } - - return this.ExecuteAppxCmdlet( - GetAppxPackage, - new Dictionary - { - { Name, packageName }, - }, - options) - .FirstOrDefault(); - } - - private async Task InstallDependenciesAsync(string releaseTag) - { - bool result = await this.InstallDependenciesFromGitHubArchive(releaseTag); - - if (!result) - { - // A better implementation would use Add-AppxPackage with -DependencyPath, but - // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter - // gets deserialized from Core the result is a single string which breaks Add-AppxPackage. - // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath - // if we are in Core, then start powershell.exe and run the same command. Right now, we just - // do Add-AppxPackage for each one. - // This method no longer works for versions >1.9 as the vclibs url has been deprecated. - await this.InstallVCLibsDependenciesFromUriAsync(); - await this.InstallUiXamlAsync(releaseTag); - } - } - - /// - /// Extracts all of the architectures used by framework packages. - /// - /// The set of architectures used by installed framework packages. - private HashSet InitFrameworkArchitectures() - { - HashSet architectures = new HashSet(); - - // Read the override from the environment variable if it exists. - string? environmentVariable = Environment.GetEnvironmentVariable(DependencyArchitectureEnvironmentVariable); - if (environmentVariable != null) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Using environment variable {DependencyArchitectureEnvironmentVariable} for frameworks: {environmentVariable}"); - - foreach (string architectureString in environmentVariable.Split(',', ';')) - { - Architecture architecture; - if (Enum.TryParse(architectureString, true, out architecture)) - { - if (architectures.Add(architecture)) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Framework architecture from environment variable: {architectureString}"); - } - } - } - - return architectures; - } - - // If there are any framework packages already installed, use the same architecture as them. - var result = this.ExecuteAppxCmdlet( - GetAppxPackage, - new Dictionary - { - { PackageTypeFilter, Framework }, - }); - - if (result != null && - result.Count > 0) - { - foreach (dynamic psobject in result) - { - string? architectureString = psobject?.Architecture?.ToString(); - if (architectureString == null) - { - continue; - } - - Architecture architecture; - if (Enum.TryParse(architectureString, true, out architecture)) - { - if (architectures.Add(architecture)) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Found framework architecture: {architectureString}"); - } - } - } - } - - // Fall back to guessing from the current OS architecture. - // This may have issues on ARM64 because RuntimeInformation.OSArchitecture seems to just lie sometimes. - // See https://github.com/microsoft/winget-cli/issues/5020 - if (architectures.Count == 0) - { - var arch = RuntimeInformation.OSArchitecture; - this.pwshCmdlet.Write(StreamType.Verbose, $"OS architecture: {arch.ToString()}"); - - if (arch == Architecture.X64) - { - architectures.Add(Architecture.X64); - } - else if (arch == Architecture.X86) - { - architectures.Add(Architecture.X86); - } - else if (arch == Architecture.Arm64) - { - // Let deployment figure it out - architectures.Add(Architecture.Arm64); - architectures.Add(Architecture.X64); - architectures.Add(Architecture.X86); - } - } - - return architectures; - } - - private Dictionary GetDependenciesByArch(PackageDependency dependencies) - { - Dictionary appxPackages = new Dictionary(); - - foreach (var architecture in this.frameworkArchitectures.Value) - { - switch (architecture) - { - case Architecture.X86: - appxPackages.Add("x86", string.Format(ExtractedDependencyPath, "x86", dependencies.Name, dependencies.Version)); - break; - case Architecture.X64: - appxPackages.Add("x64", string.Format(ExtractedDependencyPath, "x64", dependencies.Name, dependencies.Version)); - break; - case Architecture.Arm64: - appxPackages.Add("arm64", string.Format(ExtractedDependencyPath, "arm64", dependencies.Name, dependencies.Version)); - break; - default: - this.pwshCmdlet.Write(StreamType.Verbose, $"GetDependenciesByArch: Ignoring {architecture}"); - break; - } - } - - return appxPackages; - } - - private void FindMissingDependencies(Dictionary dependencies, string packageName, string requiredVersion) - { - var result = this.ExecuteAppxCmdlet( - GetAppxPackage, - new Dictionary - { - { Name, packageName }, - }); - - Version minimumVersion = new Version(requiredVersion); - - if (result != null && - result.Count > 0) - { - foreach (dynamic psobject in result) - { - string? versionString = psobject?.Version?.ToString(); - if (versionString == null) - { - continue; - } - - Version packageVersion = new Version(versionString); - - if (packageVersion >= minimumVersion) - { - string? architectureString = psobject?.Architecture?.ToString(); - if (architectureString == null) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} dependency has no architecture value: {psobject?.PackageFullName ?? ""}"); - continue; - } - - architectureString = architectureString.ToLower(); - - if (dependencies.ContainsKey(architectureString)) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} {architectureString} dependency satisfied by: {psobject?.PackageFullName ?? ""}"); - dependencies.Remove(architectureString); - } - } - else - { - this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} is lower than minimum required version [{minimumVersion}]: {psobject?.PackageFullName ?? ""}"); - } - } - } - } - - private async Task InstallVCLibsDependenciesFromUriAsync() - { - Dictionary vcLibsDependencies = this.GetVCLibsDependencies(); - this.FindMissingDependencies(vcLibsDependencies, VCLibsUWPDesktop, VCLibsUWPDesktopVersion); - - if (vcLibsDependencies.Count != 0) - { - this.pwshCmdlet.Write(StreamType.Verbose, "Couldn't find required VCLibs packages"); - - foreach (var vclibPair in vcLibsDependencies) - { - string vclib = vclibPair.Value; - await this.AddAppxPackageAsUriAsync(vclib, vclib.Substring(vclib.LastIndexOf('/') + 1)); - } - } - else - { - this.pwshCmdlet.Write(StreamType.Verbose, $"VCLibs are updated."); - } - } - - // Returns a boolean value indicating whether dependencies were successfully installed from the GitHub release assets. - private async Task InstallDependenciesFromGitHubArchive(string releaseTag) - { - var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this.pwshCmdlet); - var release = await githubClient.GetReleaseAsync(releaseTag); - - ReleaseAsset? dependenciesJsonAsset = release.TryGetAsset(DependenciesJsonName); - if (dependenciesJsonAsset is null) - { - return false; - } - - using var dependenciesJsonFile = new TempFile(); - await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesJsonAsset.BrowserDownloadUrl, dependenciesJsonFile.FullPath, this.pwshCmdlet); - - using StreamReader r = new StreamReader(dependenciesJsonFile.FullPath); - string json = r.ReadToEnd(); - WingetDependencies? wingetDependencies = JsonConvert.DeserializeObject(json); - - if (wingetDependencies is null) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to deserialize dependencies json file."); - return false; - } - - List missingDependencies = new List(); - foreach (var dependency in wingetDependencies.Dependencies) - { - Dictionary dependenciesByArch = this.GetDependenciesByArch(dependency); - this.FindMissingDependencies(dependenciesByArch, dependency.Name, dependency.Version); - - foreach (var pair in dependenciesByArch) - { - missingDependencies.Add(pair.Value); - } - } - - if (missingDependencies.Count != 0) - { - using var dependenciesZipFile = new TempFile(); - using var extractedDirectory = new TempDirectory(); - - ReleaseAsset? dependenciesZipAsset = release.TryGetAsset(DependenciesZipName); - if (dependenciesZipAsset is null) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Dependencies zip asset not found on GitHub asset."); - return false; - } - - await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesZipAsset.BrowserDownloadUrl, dependenciesZipFile.FullPath, this.pwshCmdlet); - ZipFile.ExtractToDirectory(dependenciesZipFile.FullPath, extractedDirectory.FullDirectoryPath); - - foreach (var entry in missingDependencies) - { - string fullPath = System.IO.Path.Combine(extractedDirectory.FullDirectoryPath, entry); - if (!File.Exists(fullPath)) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}"); - return false; - } - - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, fullPath }, - { ErrorAction, Stop }, - }); - } - } - - return true; - } - - private Dictionary GetVCLibsDependencies() - { - Dictionary vcLibsDependencies = new Dictionary(); - - foreach (var architecture in this.frameworkArchitectures.Value) - { - switch (architecture) - { - case Architecture.X86: - vcLibsDependencies.Add("x86", VCLibsUWPDesktopX86); - break; - case Architecture.X64: - vcLibsDependencies.Add("x64", VCLibsUWPDesktopX64); - break; - case Architecture.Arm64: - vcLibsDependencies.Add("arm64", VCLibsUWPDesktopArm64); - break; - default: - this.pwshCmdlet.Write(StreamType.Verbose, $"GetVCLibsDependencies: Ignoring {architecture}"); - break; - } - } - - return vcLibsDependencies; - } - - private async Task InstallUiXamlAsync(string releaseTag) - { - (string xamlPackageName, string xamlReleaseTag) = GetXamlDependencyVersionInfo(releaseTag); - string xamlAssetX64 = string.Format("{0}.x64.appx", xamlPackageName); - string xamlAssetX86 = string.Format("{0}.x86.appx", xamlPackageName); - string xamlAssetArm64 = string.Format("{0}.arm64.appx", xamlPackageName); - - var uiXamlObjs = this.GetAppxObject(xamlPackageName); - if (uiXamlObjs is null) - { - var githubRelease = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.UiXaml, this.pwshCmdlet); - - var xamlRelease = await githubRelease.GetReleaseAsync(xamlReleaseTag); - - var packagesToInstall = new List(); - - foreach (var architecture in this.frameworkArchitectures.Value) - { - switch (architecture) - { - case Architecture.X86: - packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetX86)); - break; - case Architecture.X64: - packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetX64)); - break; - case Architecture.Arm64: - packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetArm64)); - break; - default: - this.pwshCmdlet.Write(StreamType.Verbose, $"InstallUiXamlAsync: Ignoring {architecture}"); - break; - } - } - - foreach (var package in packagesToInstall) - { - await this.AddAppxPackageAsUriAsync(package.BrowserDownloadUrl, package.Name); - } - } - } - - private async Task AddAppxPackageAsUriAsync(string packageUri, string fileName, Dictionary? parameters = null, IList? options = null) - { - try - { - var thisParams = new Dictionary - { - { Path, packageUri }, - { ErrorAction, Stop }, - }; - - if (parameters != null) - { - foreach (var param in parameters) - { - thisParams.Add(param.Key, param.Value); - } - } - - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - thisParams, - options); - } - catch (RuntimeException e) - { - // If we couldn't install it via URI, try download and install. - if (e.ErrorRecord.CategoryInfo.Category == ErrorCategory.OpenError) - { - this.pwshCmdlet.Write(StreamType.Verbose, $"Failed adding package [{packageUri}]. Retrying downloading it."); - await this.DownloadPackageAndAddAsync(packageUri, fileName, options); - } - else - { - this.pwshCmdlet.Write(StreamType.Error, e.ErrorRecord); - throw; - } - } - } - - private async Task DownloadPackageAndAddAsync(string packageUrl, string fileName, IList? options) - { - using var tempFile = new TempFile(fileName: fileName); - - await this.httpClientHelper.DownloadUrlWithProgressAsync(packageUrl, tempFile.FullPath, this.pwshCmdlet); - - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, tempFile.FullPath }, - { ErrorAction, Stop }, - }, - options); - } - - private Collection ExecuteAppxCmdlet(string cmdlet, Dictionary? parameters = null, IList? options = null) - { - Collection result = new Collection(); - - this.pwshCmdlet.ExecuteInPowerShellThread( - () => - { - var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - - // There's a bug in the Appx Module that it can't be loaded from Core in pre 10.0.22453.0 builds without - // the -UseWindowsPowerShell option. In post 10.0.22453.0 builds there's really no difference between - // using or not -UseWindowsPowerShell as it will automatically get loaded using WinPSCompatSession remoting session. - // https://github.com/PowerShell/PowerShell/issues/13138. - // Set warning action to silently continue to avoid the console with - // 'Module Appx is loaded in Windows PowerShell using WinPSCompatSession remoting session' -#if !POWERSHELL_WINDOWS - ps.AddCommand(ImportModule) - .AddParameter(Name, Appx) - .AddParameter(UseWindowsPowerShell) - .AddParameter(WarningAction, SilentlyContinue) - .AddStatement(); -#endif - - string cmd = cmdlet; - ps.AddCommand(cmdlet); - - if (parameters != null) - { - foreach (var p in parameters) - { - cmd += $" -{p.Key} {p.Value}"; - } - - ps.AddParameters(parameters); - } - - if (options != null) - { - foreach (var option in options) - { - cmd += $" -{option}"; - ps.AddParameter(option); - } - } - - this.pwshCmdlet.Write(StreamType.Verbose, $"Executing Appx cmdlet {cmd}"); - result = ps.Invoke(); - }); - - return result; - } - - private bool IsStubPackageOptionPresent() - { - bool result = false; - this.pwshCmdlet.ExecuteInPowerShellThread( - () => - { - var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - -#if !POWERSHELL_WINDOWS - ps.AddCommand(ImportModule) - .AddParameter(Name, Appx) - .AddParameter(UseWindowsPowerShell) - .AddParameter(WarningAction, SilentlyContinue) - .AddStatement(); -#endif - - var cmdInfo = ps.AddCommand(GetCommand) - .AddParameter(Name, AddAppxPackage) - .AddParameter(Module, Appx) - .Invoke() - .FirstOrDefault(); - - result = cmdInfo != null && cmdInfo.Parameters.ContainsKey(StubPackageOption); - }); - - return result; - } - - private void RegisterAppInstallerInternal(bool allUsers = false) - { - string? packageFullName = this.GetAppInstallerPropertyValue(PackageFullName, allUsers); - - if (packageFullName == null) - { - throw new ArgumentNullException(PackageFullName); - } - - string appxManifestPath = System.IO.Path.Combine( - Utilities.ProgramFilesWindowsAppPath, - packageFullName, - AppxManifest); - - _ = this.ExecuteAppxCmdlet( - AddAppxPackage, - new Dictionary - { - { Path, appxManifestPath }, - }, - new List - { - Register, - DisableDevelopmentMode, - }); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.IO; + using System.IO.Compression; + using System.Linq; + using System.Management.Automation; + using System.Runtime.InteropServices; + using System.Threading.Tasks; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Extensions; + using Microsoft.WinGet.Common.Command; + using Newtonsoft.Json; + using Octokit; + using Semver; + using static Microsoft.WinGet.Client.Engine.Common.Constants; + + /// + /// Helper to make calls to the Appx module. + /// + internal class AppxModuleHelper + { + // Cmdlets + private const string ImportModule = "Import-Module"; + private const string GetAppxPackage = "Get-AppxPackage"; + private const string AddAppxPackage = "Add-AppxPackage"; + private const string AddAppxProvisionedPackage = "Add-AppxProvisionedPackage"; + private const string GetCommand = "Get-Command"; + + // Parameters name + private const string Name = "Name"; + private const string Path = "Path"; + private const string ErrorAction = "ErrorAction"; + private const string WarningAction = "WarningAction"; + private const string PackagePath = "PackagePath"; + private const string LicensePath = "LicensePath"; + private const string Module = "Module"; + private const string StubPackageOption = "StubPackageOption"; + private const string PackageTypeFilter = "PackageTypeFilter"; + + // Parameter Values + private const string Appx = "Appx"; + private const string Stop = "Stop"; + private const string SilentlyContinue = "SilentlyContinue"; + private const string Online = "Online"; + private const string UsePreference = "UsePreference"; + private const string Framework = "Framework"; + + // Options + private const string UseWindowsPowerShell = "UseWindowsPowerShell"; + private const string ForceUpdateFromAnyVersion = "ForceUpdateFromAnyVersion"; + private const string Register = "Register"; + private const string DisableDevelopmentMode = "DisableDevelopmentMode"; + private const string ForceTargetApplicationShutdown = "ForceTargetApplicationShutdown"; + private const string AllUsers = "AllUsers"; + + private const string AppInstallerName = "Microsoft.DesktopAppInstaller"; + private const string AppxManifest = "AppxManifest.xml"; + private const string PackageFullName = "PackageFullName"; + private const string Version = "Version"; + + private const string DependencyArchitectureEnvironmentVariable = "WINGET_PACKAGE_MANAGER_REPAIR_DEPENDENCY_ARCHITECTURES"; + + // Assets + private const string MsixBundleName = "Microsoft.DesktopAppInstaller_8wekyb3d8bbwe.msixbundle"; + private const string DependenciesJsonName = "DesktopAppInstaller_Dependencies.json"; + private const string DependenciesZipName = "DesktopAppInstaller_Dependencies.zip"; + private const string License = "License1.xml"; + + // Format of a dependency package such as 'x64\Microsoft.VCLibs.140.00.UWPDesktop_14.0.33728.0_x64.appx' + private const string ExtractedDependencyPath = "{0}\\{1}_{2}_{0}.appx"; + + // Dependencies + // VCLibs + private const string VCLibsUWPDesktop = "Microsoft.VCLibs.140.00.UWPDesktop"; + private const string VCLibsUWPDesktopVersion = "14.0.30704.0"; + private const string VCLibsUWPDesktopX64 = "https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx"; + private const string VCLibsUWPDesktopX86 = "https://aka.ms/Microsoft.VCLibs.x86.14.00.Desktop.appx"; + private const string VCLibsUWPDesktopArm64 = "https://aka.ms/Microsoft.VCLibs.arm64.14.00.Desktop.appx"; + + // Xaml + private const string XamlPackage28 = "Microsoft.UI.Xaml.2.8"; + private const string XamlReleaseTag286 = "v2.8.6"; + private const string MinimumWinGetReleaseTagForXaml28 = "v1.7.10514"; + + private const string XamlPackage27 = "Microsoft.UI.Xaml.2.7"; + private const string XamlReleaseTag273 = "v2.7.3"; + + // WinGet Source + private const string WinGetSourceName = "Microsoft.Winget.Source"; + private const string WinGetSourceMsixName = "source2.msix"; + private const string WinGetSourceUrl = $"https://cdn.winget.microsoft.com/cache/{WinGetSourceMsixName}"; + + private readonly PowerShellCmdlet pwshCmdlet; + private readonly HttpClientHelper httpClientHelper; + private Lazy> frameworkArchitectures; + + /// + /// Initializes a new instance of the class. + /// + /// The calling cmdlet. + public AppxModuleHelper(PowerShellCmdlet pwshCmdlet) + { + this.pwshCmdlet = pwshCmdlet; + this.httpClientHelper = new HttpClientHelper(); + this.frameworkArchitectures = new Lazy>(() => this.InitFrameworkArchitectures()); + } + + /// + /// Calls Get-AppxPackage Microsoft.DesktopAppInstaller. + /// + /// Whether to get for all users. + /// Result of Get-AppxPackage. + public PSObject? GetAppInstallerObject(bool allUsers = false) + { + return this.GetAppxObject(AppInstallerName, allUsers); + } + + /// + /// Calls Get-AppxPackage Microsoft.Winget.Source. + /// + /// Result of Get-AppxPackage. + public PSObject? GetWinGetSourceObject() + { + return this.GetAppxObject(WinGetSourceName); + } + + /// + /// Gets the string value a property from the Get-AppxPackage object of AppInstaller. + /// + /// Property name. + /// Whether to get for all users. + /// Value, null if doesn't exist. + public string? GetAppInstallerPropertyValue(string propertyName, bool allUsers = false) + { + string? result = null; + var packageObj = this.GetAppInstallerObject(allUsers); + if (packageObj is not null) + { + var property = packageObj.Properties.Where(p => p.Name == propertyName).FirstOrDefault(); + if (property is not null) + { + result = property.Value as string; + } + } + + return result; + } + + /// + /// Checks if winget source is installed. + /// + /// True if installed. + public bool IsWinGetSourceInstalled() + { + return this.GetWinGetSourceObject() is not null; + } + + /// + /// Calls Add-AppxPackage to register with AppInstaller's AppxManifest.xml. + /// + /// Release tag of GitHub release. + /// Whether to register for all users. + /// A representing the asynchronous operation. + public async Task RegisterAppInstallerAsync(string releaseTag, bool allUsers) + { + if (string.IsNullOrEmpty(releaseTag)) + { + string? versionFromLocalPackage = this.GetAppInstallerPropertyValue(Version, allUsers); + + if (versionFromLocalPackage == null) + { + throw new ArgumentNullException(Version); + } + + var packageVersion = new Version(versionFromLocalPackage); + if (packageVersion.Major == 1 && packageVersion.Minor > 15 && packageVersion.Minor < 28) + { + releaseTag = $"v1.{packageVersion.Minor - 15}.{packageVersion.Build}"; + } + else + { + releaseTag = $"v{packageVersion.Major}.{packageVersion.Minor}.{packageVersion.Build}"; + } + } + + // Ensure that all dependencies are present when attempting to register. + // If dependencies are missing, a provisioned package can appear to only need registration, + // but will fail to register. `InstallDependenciesAsync` checks for the packages before + // acting, so it should be mostly a no-op if they are already available. + await this.InstallDependenciesAsync(releaseTag); + + this.RegisterAppInstallerInternal(allUsers); + } + + /// + /// Install AppInstaller's bundle from a GitHub release. + /// + /// Release tag of GitHub release. + /// If install for all users is needed. + /// Is downgrade. + /// Force application shutdown. + /// A representing the asynchronous operation. + public async Task InstallFromGitHubReleaseAsync(string releaseTag, bool allUsers, bool isDowngrade, bool force) + { + await this.InstallDependenciesAsync(releaseTag); + + if (isDowngrade) + { + // Add-AppxProvisionedPackage doesn't support downgrade. + await this.AddAppInstallerBundleAsync(releaseTag, true, force); + + if (allUsers) + { + await this.AddProvisionPackageAsync(releaseTag); + } + } + else + { + if (allUsers) + { + await this.AddProvisionPackageAsync(releaseTag); + } + else + { + await this.AddAppInstallerBundleAsync(releaseTag, false, force); + } + } + } + + /// + /// Installs the WinGet source by downloading and adding package. + /// + /// A representing the asynchronous operation. + public async Task InstallWinGetSourceAsync() + { + await this.DownloadPackageAndAddAsync(WinGetSourceUrl, WinGetSourceMsixName, options: null); + } + + /// + /// Gets the Xaml dependency package name and release tag based on the provided WinGet release tag. + /// + /// WinGet release tag. + /// A tuple in the format of (XamlPackageName, XamlReleaseTag). + private static Tuple GetXamlDependencyVersionInfo(string releaseTag) + { + var targetVersion = SemVersion.Parse(releaseTag, SemVersionStyles.AllowLowerV); + + if (targetVersion.CompareSortOrderTo(SemVersion.Parse(MinimumWinGetReleaseTagForXaml28, SemVersionStyles.AllowLowerV)) >= 0) + { + return Tuple.Create(XamlPackage28, XamlReleaseTag286); + } + else + { + return Tuple.Create(XamlPackage27, XamlReleaseTag273); + } + } + + private async Task AddProvisionPackageAsync(string releaseTag) + { + var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this.pwshCmdlet); + var release = await githubClient.GetReleaseAsync(releaseTag); + + var bundleAsset = release.GetAsset(MsixBundleName); + using var bundleFile = new TempFile(fileName: MsixBundleName); + await this.httpClientHelper.DownloadUrlWithProgressAsync( + bundleAsset.BrowserDownloadUrl, bundleFile.FullPath, this.pwshCmdlet); + + var licenseAsset = release.GetAssetEndsWith(License); + using var licenseFile = new TempFile(fileName: licenseAsset.Name); + await this.httpClientHelper.DownloadUrlWithProgressAsync( + licenseAsset.BrowserDownloadUrl, licenseFile.FullPath, this.pwshCmdlet); + + try + { + this.pwshCmdlet.ExecuteInPowerShellThread( + () => + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + ps.AddCommand(AddAppxProvisionedPackage) + .AddParameter(Online) + .AddParameter(PackagePath, bundleFile.FullPath) + .AddParameter(LicensePath, licenseFile.FullPath) + .AddParameter(ErrorAction, Stop) + .Invoke(); + }); + + // Register the package after provisioning so that it is + // available immediately. + this.RegisterAppInstallerInternal(allUsers: true); + } + catch (RuntimeException e) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Failed installing bundle via Add-AppxProvisionedPackage {e}"); + throw; + } + } + + private async Task AddAppInstallerBundleAsync(string releaseTag, bool downgrade, bool force) + { + var options = new List(); + if (downgrade) + { + options.Add(ForceUpdateFromAnyVersion); + } + + if (force) + { + options.Add(ForceTargetApplicationShutdown); + } + + var parameters = new Dictionary(); + if (this.IsStubPackageOptionPresent()) + { + parameters.Add(StubPackageOption, UsePreference); + } + + try + { + var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this.pwshCmdlet); + var release = await githubClient.GetReleaseAsync(releaseTag); + + var bundleAsset = release.GetAsset(MsixBundleName); + await this.AddAppxPackageAsUriAsync(bundleAsset.BrowserDownloadUrl, MsixBundleName, parameters, options); + } + catch (RuntimeException e) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Failed installing bundle via Add-AppxPackage {e}"); + throw; + } + } + + private PSObject? GetAppxObject(string packageName, bool allUsers = false) + { + var options = new List(); + if (allUsers) + { + options.Add(AllUsers); + } + + return this.ExecuteAppxCmdlet( + GetAppxPackage, + new Dictionary + { + { Name, packageName }, + }, + options) + .FirstOrDefault(); + } + + private async Task InstallDependenciesAsync(string releaseTag) + { + bool result = await this.InstallDependenciesFromGitHubArchive(releaseTag); + + if (!result) + { + // A better implementation would use Add-AppxPackage with -DependencyPath, but + // the Appx module needs to be remoted into Windows PowerShell. When the string[] parameter + // gets deserialized from Core the result is a single string which breaks Add-AppxPackage. + // Here we should: if we are in Windows Powershell then run Add-AppxPackage with -DependencyPath + // if we are in Core, then start powershell.exe and run the same command. Right now, we just + // do Add-AppxPackage for each one. + // This method no longer works for versions >1.9 as the vclibs url has been deprecated. + await this.InstallVCLibsDependenciesFromUriAsync(); + await this.InstallUiXamlAsync(releaseTag); + } + } + + /// + /// Extracts all of the architectures used by framework packages. + /// + /// The set of architectures used by installed framework packages. + private HashSet InitFrameworkArchitectures() + { + HashSet architectures = new HashSet(); + + // Read the override from the environment variable if it exists. + string? environmentVariable = Environment.GetEnvironmentVariable(DependencyArchitectureEnvironmentVariable); + if (environmentVariable != null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Using environment variable {DependencyArchitectureEnvironmentVariable} for frameworks: {environmentVariable}"); + + foreach (string architectureString in environmentVariable.Split(',', ';')) + { + Architecture architecture; + if (Enum.TryParse(architectureString, true, out architecture)) + { + if (architectures.Add(architecture)) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Framework architecture from environment variable: {architectureString}"); + } + } + } + + return architectures; + } + + // If there are any framework packages already installed, use the same architecture as them. + var result = this.ExecuteAppxCmdlet( + GetAppxPackage, + new Dictionary + { + { PackageTypeFilter, Framework }, + }); + + if (result != null && + result.Count > 0) + { + foreach (dynamic psobject in result) + { + string? architectureString = psobject?.Architecture?.ToString(); + if (architectureString == null) + { + continue; + } + + Architecture architecture; + if (Enum.TryParse(architectureString, true, out architecture)) + { + if (architectures.Add(architecture)) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Found framework architecture: {architectureString}"); + } + } + } + } + + // Fall back to guessing from the current OS architecture. + // This may have issues on ARM64 because RuntimeInformation.OSArchitecture seems to just lie sometimes. + // See https://github.com/microsoft/winget-cli/issues/5020 + if (architectures.Count == 0) + { + var arch = RuntimeInformation.OSArchitecture; + this.pwshCmdlet.Write(StreamType.Verbose, $"OS architecture: {arch.ToString()}"); + + if (arch == Architecture.X64) + { + architectures.Add(Architecture.X64); + } + else if (arch == Architecture.X86) + { + architectures.Add(Architecture.X86); + } + else if (arch == Architecture.Arm64) + { + // Let deployment figure it out + architectures.Add(Architecture.Arm64); + architectures.Add(Architecture.X64); + architectures.Add(Architecture.X86); + } + } + + return architectures; + } + + private Dictionary GetDependenciesByArch(PackageDependency dependencies) + { + Dictionary appxPackages = new Dictionary(); + + foreach (var architecture in this.frameworkArchitectures.Value) + { + switch (architecture) + { + case Architecture.X86: + appxPackages.Add("x86", string.Format(ExtractedDependencyPath, "x86", dependencies.Name, dependencies.Version)); + break; + case Architecture.X64: + appxPackages.Add("x64", string.Format(ExtractedDependencyPath, "x64", dependencies.Name, dependencies.Version)); + break; + case Architecture.Arm64: + appxPackages.Add("arm64", string.Format(ExtractedDependencyPath, "arm64", dependencies.Name, dependencies.Version)); + break; + default: + this.pwshCmdlet.Write(StreamType.Verbose, $"GetDependenciesByArch: Ignoring {architecture}"); + break; + } + } + + return appxPackages; + } + + private void FindMissingDependencies(Dictionary dependencies, string packageName, string requiredVersion) + { + var result = this.ExecuteAppxCmdlet( + GetAppxPackage, + new Dictionary + { + { Name, packageName }, + }); + + Version minimumVersion = new Version(requiredVersion); + + if (result != null && + result.Count > 0) + { + foreach (dynamic psobject in result) + { + string? versionString = psobject?.Version?.ToString(); + if (versionString == null) + { + continue; + } + + Version packageVersion = new Version(versionString); + + if (packageVersion >= minimumVersion) + { + string? architectureString = psobject?.Architecture?.ToString(); + if (architectureString == null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} dependency has no architecture value: {psobject?.PackageFullName ?? ""}"); + continue; + } + + architectureString = architectureString.ToLower(); + + if (dependencies.ContainsKey(architectureString)) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} {architectureString} dependency satisfied by: {psobject?.PackageFullName ?? ""}"); + dependencies.Remove(architectureString); + } + } + else + { + this.pwshCmdlet.Write(StreamType.Verbose, $"{packageName} is lower than minimum required version [{minimumVersion}]: {psobject?.PackageFullName ?? ""}"); + } + } + } + } + + private async Task InstallVCLibsDependenciesFromUriAsync() + { + Dictionary vcLibsDependencies = this.GetVCLibsDependencies(); + this.FindMissingDependencies(vcLibsDependencies, VCLibsUWPDesktop, VCLibsUWPDesktopVersion); + + if (vcLibsDependencies.Count != 0) + { + this.pwshCmdlet.Write(StreamType.Verbose, "Couldn't find required VCLibs packages"); + + foreach (var vclibPair in vcLibsDependencies) + { + string vclib = vclibPair.Value; + await this.AddAppxPackageAsUriAsync(vclib, vclib.Substring(vclib.LastIndexOf('/') + 1)); + } + } + else + { + this.pwshCmdlet.Write(StreamType.Verbose, $"VCLibs are updated."); + } + } + + // Returns a boolean value indicating whether dependencies were successfully installed from the GitHub release assets. + private async Task InstallDependenciesFromGitHubArchive(string releaseTag) + { + var githubClient = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.WinGetCli, this.pwshCmdlet); + var release = await githubClient.GetReleaseAsync(releaseTag); + + ReleaseAsset? dependenciesJsonAsset = release.TryGetAsset(DependenciesJsonName); + if (dependenciesJsonAsset is null) + { + return false; + } + + using var dependenciesJsonFile = new TempFile(); + await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesJsonAsset.BrowserDownloadUrl, dependenciesJsonFile.FullPath, this.pwshCmdlet); + + using StreamReader r = new StreamReader(dependenciesJsonFile.FullPath); + string json = r.ReadToEnd(); + WingetDependencies? wingetDependencies = JsonConvert.DeserializeObject(json); + + if (wingetDependencies is null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Failed to deserialize dependencies json file."); + return false; + } + + List missingDependencies = new List(); + foreach (var dependency in wingetDependencies.Dependencies) + { + Dictionary dependenciesByArch = this.GetDependenciesByArch(dependency); + this.FindMissingDependencies(dependenciesByArch, dependency.Name, dependency.Version); + + foreach (var pair in dependenciesByArch) + { + missingDependencies.Add(pair.Value); + } + } + + if (missingDependencies.Count != 0) + { + using var dependenciesZipFile = new TempFile(); + using var extractedDirectory = new TempDirectory(); + + ReleaseAsset? dependenciesZipAsset = release.TryGetAsset(DependenciesZipName); + if (dependenciesZipAsset is null) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Dependencies zip asset not found on GitHub asset."); + return false; + } + + await this.httpClientHelper.DownloadUrlWithProgressAsync(dependenciesZipAsset.BrowserDownloadUrl, dependenciesZipFile.FullPath, this.pwshCmdlet); + ZipFile.ExtractToDirectory(dependenciesZipFile.FullPath, extractedDirectory.FullDirectoryPath); + + foreach (var entry in missingDependencies) + { + string fullPath = System.IO.Path.Combine(extractedDirectory.FullDirectoryPath, entry); + if (!File.Exists(fullPath)) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Package dependency not found in archive: {fullPath}"); + return false; + } + + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + new Dictionary + { + { Path, fullPath }, + { ErrorAction, Stop }, + }); + } + } + + return true; + } + + private Dictionary GetVCLibsDependencies() + { + Dictionary vcLibsDependencies = new Dictionary(); + + foreach (var architecture in this.frameworkArchitectures.Value) + { + switch (architecture) + { + case Architecture.X86: + vcLibsDependencies.Add("x86", VCLibsUWPDesktopX86); + break; + case Architecture.X64: + vcLibsDependencies.Add("x64", VCLibsUWPDesktopX64); + break; + case Architecture.Arm64: + vcLibsDependencies.Add("arm64", VCLibsUWPDesktopArm64); + break; + default: + this.pwshCmdlet.Write(StreamType.Verbose, $"GetVCLibsDependencies: Ignoring {architecture}"); + break; + } + } + + return vcLibsDependencies; + } + + private async Task InstallUiXamlAsync(string releaseTag) + { + (string xamlPackageName, string xamlReleaseTag) = GetXamlDependencyVersionInfo(releaseTag); + string xamlAssetX64 = string.Format("{0}.x64.appx", xamlPackageName); + string xamlAssetX86 = string.Format("{0}.x86.appx", xamlPackageName); + string xamlAssetArm64 = string.Format("{0}.arm64.appx", xamlPackageName); + + var uiXamlObjs = this.GetAppxObject(xamlPackageName); + if (uiXamlObjs is null) + { + var githubRelease = new GitHubClient(RepositoryOwner.Microsoft, RepositoryName.UiXaml, this.pwshCmdlet); + + var xamlRelease = await githubRelease.GetReleaseAsync(xamlReleaseTag); + + var packagesToInstall = new List(); + + foreach (var architecture in this.frameworkArchitectures.Value) + { + switch (architecture) + { + case Architecture.X86: + packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetX86)); + break; + case Architecture.X64: + packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetX64)); + break; + case Architecture.Arm64: + packagesToInstall.Add(xamlRelease.GetAsset(xamlAssetArm64)); + break; + default: + this.pwshCmdlet.Write(StreamType.Verbose, $"InstallUiXamlAsync: Ignoring {architecture}"); + break; + } + } + + foreach (var package in packagesToInstall) + { + await this.AddAppxPackageAsUriAsync(package.BrowserDownloadUrl, package.Name); + } + } + } + + private async Task AddAppxPackageAsUriAsync(string packageUri, string fileName, Dictionary? parameters = null, IList? options = null) + { + try + { + var thisParams = new Dictionary + { + { Path, packageUri }, + { ErrorAction, Stop }, + }; + + if (parameters != null) + { + foreach (var param in parameters) + { + thisParams.Add(param.Key, param.Value); + } + } + + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + thisParams, + options); + } + catch (RuntimeException e) + { + // If we couldn't install it via URI, try download and install. + if (e.ErrorRecord.CategoryInfo.Category == ErrorCategory.OpenError) + { + this.pwshCmdlet.Write(StreamType.Verbose, $"Failed adding package [{packageUri}]. Retrying downloading it."); + await this.DownloadPackageAndAddAsync(packageUri, fileName, options); + } + else + { + this.pwshCmdlet.Write(StreamType.Error, e.ErrorRecord); + throw; + } + } + } + + private async Task DownloadPackageAndAddAsync(string packageUrl, string fileName, IList? options) + { + using var tempFile = new TempFile(fileName: fileName); + + await this.httpClientHelper.DownloadUrlWithProgressAsync(packageUrl, tempFile.FullPath, this.pwshCmdlet); + + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + new Dictionary + { + { Path, tempFile.FullPath }, + { ErrorAction, Stop }, + }, + options); + } + + private Collection ExecuteAppxCmdlet(string cmdlet, Dictionary? parameters = null, IList? options = null) + { + Collection result = new Collection(); + + this.pwshCmdlet.ExecuteInPowerShellThread( + () => + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + + // There's a bug in the Appx Module that it can't be loaded from Core in pre 10.0.22453.0 builds without + // the -UseWindowsPowerShell option. In post 10.0.22453.0 builds there's really no difference between + // using or not -UseWindowsPowerShell as it will automatically get loaded using WinPSCompatSession remoting session. + // https://github.com/PowerShell/PowerShell/issues/13138. + // Set warning action to silently continue to avoid the console with + // 'Module Appx is loaded in Windows PowerShell using WinPSCompatSession remoting session' +#if !POWERSHELL_WINDOWS + ps.AddCommand(ImportModule) + .AddParameter(Name, Appx) + .AddParameter(UseWindowsPowerShell) + .AddParameter(WarningAction, SilentlyContinue) + .AddStatement(); +#endif + + string cmd = cmdlet; + ps.AddCommand(cmdlet); + + if (parameters != null) + { + foreach (var p in parameters) + { + cmd += $" -{p.Key} {p.Value}"; + } + + ps.AddParameters(parameters); + } + + if (options != null) + { + foreach (var option in options) + { + cmd += $" -{option}"; + ps.AddParameter(option); + } + } + + this.pwshCmdlet.Write(StreamType.Verbose, $"Executing Appx cmdlet {cmd}"); + result = ps.Invoke(); + }); + + return result; + } + + private bool IsStubPackageOptionPresent() + { + bool result = false; + this.pwshCmdlet.ExecuteInPowerShellThread( + () => + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + +#if !POWERSHELL_WINDOWS + ps.AddCommand(ImportModule) + .AddParameter(Name, Appx) + .AddParameter(UseWindowsPowerShell) + .AddParameter(WarningAction, SilentlyContinue) + .AddStatement(); +#endif + + var cmdInfo = ps.AddCommand(GetCommand) + .AddParameter(Name, AddAppxPackage) + .AddParameter(Module, Appx) + .Invoke() + .FirstOrDefault(); + + result = cmdInfo != null && cmdInfo.Parameters.ContainsKey(StubPackageOption); + }); + + return result; + } + + private void RegisterAppInstallerInternal(bool allUsers = false) + { + string? packageFullName = this.GetAppInstallerPropertyValue(PackageFullName, allUsers); + + if (packageFullName == null) + { + throw new ArgumentNullException(PackageFullName); + } + + string appxManifestPath = System.IO.Path.Combine( + Utilities.ProgramFilesWindowsAppPath, + packageFullName, + AppxManifest); + + _ = this.ExecuteAppxCmdlet( + AddAppxPackage, + new Dictionary + { + { Path, appxManifestPath }, + }, + new List + { + Register, + DisableDevelopmentMode, + }); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/DownloadOperationWithProgress.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/DownloadOperationWithProgress.cs index 90d82dad8e..d62b266395 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/DownloadOperationWithProgress.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/DownloadOperationWithProgress.cs @@ -1,42 +1,42 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - using Windows.Foundation; - - /// - /// Handler progress for package download. - /// - internal class DownloadOperationWithProgress : OperationWithProgressBase - { - /// - /// Initializes a new instance of the class. - /// - /// A instance. - /// Activity. - public DownloadOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) - : base(pwshCmdlet, activity) - { - } - - /// - public override void Progress(IAsyncOperationWithProgress operation, PackageDownloadProgress progress) - { - ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) - { - RecordType = ProgressRecordType.Processing, - }; - record.StatusDescription = Resources.DownloadingMessage; - record.PercentComplete = (int)(progress.DownloadProgress * 100); - this.PwshCmdlet.Write(StreamType.Progress, record); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + using Windows.Foundation; + + /// + /// Handler progress for package download. + /// + internal class DownloadOperationWithProgress : OperationWithProgressBase + { + /// + /// Initializes a new instance of the class. + /// + /// A instance. + /// Activity. + public DownloadOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) + : base(pwshCmdlet, activity) + { + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, PackageDownloadProgress progress) + { + ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }; + record.StatusDescription = Resources.DownloadingMessage; + record.PercentComplete = (int)(progress.DownloadProgress * 100); + this.PwshCmdlet.Write(StreamType.Progress, record); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs index 63d2393d9e..7277745967 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/GitHubClient.cs @@ -1,221 +1,221 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Microsoft.WinGet.Common.Command; - using Octokit; - using static Microsoft.WinGet.Client.Engine.Common.Constants; - - /// - /// Handles GitHub interactions. - /// - internal class GitHubClient - { - private readonly string owner; - private readonly string repo; - private readonly IGitHubClient gitHubClient; - private readonly PowerShellCmdlet? pwshCmdlet; - - /// - /// Initializes a new instance of the class. - /// - /// Owner. - /// Repository. - /// Optional PowerShell cmdlet for logging. - public GitHubClient(string owner, string repo, PowerShellCmdlet? pwshCmdlet = null) - { - this.pwshCmdlet = pwshCmdlet; - var octokitClient = new Octokit.GitHubClient(new ProductHeaderValue(HttpClientHelper.UserAgent)); - - string? token = ResolveGitHubToken(pwshCmdlet); - if (!string.IsNullOrWhiteSpace(token)) - { - octokitClient.Credentials = new Credentials(token); - } - - this.gitHubClient = octokitClient; - this.owner = owner; - this.repo = repo; - } - - /// - /// Gets a release. - /// - /// Release tag. - /// The Release. - public async Task GetReleaseAsync(string releaseTag) - { - return await this.gitHubClient.Repository.Release.Get(this.owner, this.repo, releaseTag); - } - - /// - /// Gets the latest released and waits. - /// - /// Include prerelease. - /// Latest version. - public async Task GetLatestReleaseTagNameAsync(bool includePrerelease) - { - return (await this.GetLatestReleaseAsync(includePrerelease)).TagName; - } - - /// - /// Gets the latest released version. - /// - /// Include prerelease. - /// Latest version. - public async Task GetLatestReleaseAsync(bool includePrerelease) - { - var allReleases = await this.GetAllReleasesAsync(); - allReleases = includePrerelease ? allReleases : allReleases.Where(r => !r.Prerelease).ToList(); - return allReleases.Select(r => new { Release = r, WinGetVersion = new WinGetVersion(r.TagName) }) - .OrderBy(rv => rv.WinGetVersion.Version) - .Last().Release; - } - - /// - /// Gets all releases. - /// - /// All releases. - public async Task> GetAllReleasesAsync() - { - return await this.gitHubClient.Repository.Release.GetAll(this.owner, this.repo); - } - - /// - /// Resolve a version string to the latest matching version from GitHub releases. - /// - /// Version string to resolve. Can include wildcards (*). - /// Whether to include prerelease versions in the search. - /// Resolved version string or null if no match found. - public async Task ResolveVersionAsync(string version, bool includePrerelease) - { - if (!WinGetVersion.VersionHasWildcard(version)) - { - return version; - } - - var allReleases = await this.GetAllReleasesAsync(); - var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName)); - if (TryGetLatestMatchingVersion(allWinGetReleases, version, includePrerelease, out var latestVersion)) - { - return latestVersion!.TagVersion; - } - - return null; - } - - /// - /// Reads all known GitHub token environment variables, logs their presence, - /// and selects the one to use based on precedence. - /// GH_TOKEN takes precedence over GITHUB_TOKEN, matching GitHub CLI behavior. - /// See: https://cli.github.com/manual/gh_help_environment. - /// - /// Optional PowerShell cmdlet for logging. - /// The selected token value, or null if none found. - internal static string? ResolveGitHubToken(PowerShellCmdlet? pwshCmdlet = null) - { - string? ghToken = Environment.GetEnvironmentVariable("GH_TOKEN"); - string? githubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); - - bool hasGhToken = !string.IsNullOrWhiteSpace(ghToken); - bool hasGithubToken = !string.IsNullOrWhiteSpace(githubToken); - - pwshCmdlet?.Write(StreamType.Verbose, $"GH_TOKEN environment variable: {(hasGhToken ? "found" : "not found")}"); - pwshCmdlet?.Write(StreamType.Verbose, $"GITHUB_TOKEN environment variable: {(hasGithubToken ? "found" : "not found")}"); - - if (hasGhToken) - { - pwshCmdlet?.Write(StreamType.Verbose, "Using authenticated GitHub API requests via GH_TOKEN environment variable."); - return ghToken; - } - else if (hasGithubToken) - { - pwshCmdlet?.Write(StreamType.Verbose, "Using authenticated GitHub API requests via GITHUB_TOKEN environment variable."); - return githubToken; - } - - pwshCmdlet?.Write(StreamType.Verbose, "No GitHub token found. Using unauthenticated GitHub API requests."); - return null; - } - - /// - /// Tries to get the latest version matching the pattern. - /// - /// - /// Pattern only supports leading and trailing wildcards. - /// - For example, the pattern can be: 1.11.*, 1.11.3*, 1.11.*3 - /// - But it cannot be: 1.*1*.1 or 1.1*1.1. - /// - /// List of versions to match against. - /// Pattern to match. - /// Include prerelease versions. - /// The resulting version. - /// True if a matching version was found. - private static bool TryGetLatestMatchingVersion(IEnumerable versions, string pattern, bool includePrerelease, out WinGetVersion? result) - { - pattern = string.IsNullOrWhiteSpace(pattern) ? "*" : pattern; - - var parts = pattern.Split('.'); - var major = parts.ElementAtOrDefault(0); - var minor = parts.ElementAtOrDefault(1); - var build = parts.ElementAtOrDefault(2); - var revision = parts.ElementAtOrDefault(3); - - if (!includePrerelease) - { - versions = versions.Where(v => !v.IsPrerelease); - } - - versions = versions - .Where(v => - VersionPartMatch(major, v.Version.Major) && - VersionPartMatch(minor, v.Version.Minor) && - VersionPartMatch(build, v.Version.Build) && - VersionPartMatch(revision, v.Version.Revision)) - .OrderBy(f => f.Version); - - if (!versions.Any()) - { - result = null!; - return false; - } - - result = versions.Last(); - return true; - } - - /// - /// Checks if a version part matches a pattern. - /// - /// Version part pattern. - /// Version part value. - /// True if the part matches the pattern. - private static bool VersionPartMatch(string? partPattern, int partValue) - { - if (string.IsNullOrWhiteSpace(partPattern)) - { - return true; - } - - if (partPattern!.StartsWith("*")) - { - return partValue.ToString().EndsWith(partPattern.TrimStart('*')); - } - - if (partPattern!.EndsWith("*")) - { - return partValue.ToString().StartsWith(partPattern.TrimEnd('*')); - } - - return partPattern == partValue.ToString(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.WinGet.Common.Command; + using Octokit; + using static Microsoft.WinGet.Client.Engine.Common.Constants; + + /// + /// Handles GitHub interactions. + /// + internal class GitHubClient + { + private readonly string owner; + private readonly string repo; + private readonly IGitHubClient gitHubClient; + private readonly PowerShellCmdlet? pwshCmdlet; + + /// + /// Initializes a new instance of the class. + /// + /// Owner. + /// Repository. + /// Optional PowerShell cmdlet for logging. + public GitHubClient(string owner, string repo, PowerShellCmdlet? pwshCmdlet = null) + { + this.pwshCmdlet = pwshCmdlet; + var octokitClient = new Octokit.GitHubClient(new ProductHeaderValue(HttpClientHelper.UserAgent)); + + string? token = ResolveGitHubToken(pwshCmdlet); + if (!string.IsNullOrWhiteSpace(token)) + { + octokitClient.Credentials = new Credentials(token); + } + + this.gitHubClient = octokitClient; + this.owner = owner; + this.repo = repo; + } + + /// + /// Gets a release. + /// + /// Release tag. + /// The Release. + public async Task GetReleaseAsync(string releaseTag) + { + return await this.gitHubClient.Repository.Release.Get(this.owner, this.repo, releaseTag); + } + + /// + /// Gets the latest released and waits. + /// + /// Include prerelease. + /// Latest version. + public async Task GetLatestReleaseTagNameAsync(bool includePrerelease) + { + return (await this.GetLatestReleaseAsync(includePrerelease)).TagName; + } + + /// + /// Gets the latest released version. + /// + /// Include prerelease. + /// Latest version. + public async Task GetLatestReleaseAsync(bool includePrerelease) + { + var allReleases = await this.GetAllReleasesAsync(); + allReleases = includePrerelease ? allReleases : allReleases.Where(r => !r.Prerelease).ToList(); + return allReleases.Select(r => new { Release = r, WinGetVersion = new WinGetVersion(r.TagName) }) + .OrderBy(rv => rv.WinGetVersion.Version) + .Last().Release; + } + + /// + /// Gets all releases. + /// + /// All releases. + public async Task> GetAllReleasesAsync() + { + return await this.gitHubClient.Repository.Release.GetAll(this.owner, this.repo); + } + + /// + /// Resolve a version string to the latest matching version from GitHub releases. + /// + /// Version string to resolve. Can include wildcards (*). + /// Whether to include prerelease versions in the search. + /// Resolved version string or null if no match found. + public async Task ResolveVersionAsync(string version, bool includePrerelease) + { + if (!WinGetVersion.VersionHasWildcard(version)) + { + return version; + } + + var allReleases = await this.GetAllReleasesAsync(); + var allWinGetReleases = allReleases.Select(r => new WinGetVersion(r.TagName)); + if (TryGetLatestMatchingVersion(allWinGetReleases, version, includePrerelease, out var latestVersion)) + { + return latestVersion!.TagVersion; + } + + return null; + } + + /// + /// Reads all known GitHub token environment variables, logs their presence, + /// and selects the one to use based on precedence. + /// GH_TOKEN takes precedence over GITHUB_TOKEN, matching GitHub CLI behavior. + /// See: https://cli.github.com/manual/gh_help_environment. + /// + /// Optional PowerShell cmdlet for logging. + /// The selected token value, or null if none found. + internal static string? ResolveGitHubToken(PowerShellCmdlet? pwshCmdlet = null) + { + string? ghToken = Environment.GetEnvironmentVariable("GH_TOKEN"); + string? githubToken = Environment.GetEnvironmentVariable("GITHUB_TOKEN"); + + bool hasGhToken = !string.IsNullOrWhiteSpace(ghToken); + bool hasGithubToken = !string.IsNullOrWhiteSpace(githubToken); + + pwshCmdlet?.Write(StreamType.Verbose, $"GH_TOKEN environment variable: {(hasGhToken ? "found" : "not found")}"); + pwshCmdlet?.Write(StreamType.Verbose, $"GITHUB_TOKEN environment variable: {(hasGithubToken ? "found" : "not found")}"); + + if (hasGhToken) + { + pwshCmdlet?.Write(StreamType.Verbose, "Using authenticated GitHub API requests via GH_TOKEN environment variable."); + return ghToken; + } + else if (hasGithubToken) + { + pwshCmdlet?.Write(StreamType.Verbose, "Using authenticated GitHub API requests via GITHUB_TOKEN environment variable."); + return githubToken; + } + + pwshCmdlet?.Write(StreamType.Verbose, "No GitHub token found. Using unauthenticated GitHub API requests."); + return null; + } + + /// + /// Tries to get the latest version matching the pattern. + /// + /// + /// Pattern only supports leading and trailing wildcards. + /// - For example, the pattern can be: 1.11.*, 1.11.3*, 1.11.*3 + /// - But it cannot be: 1.*1*.1 or 1.1*1.1. + /// + /// List of versions to match against. + /// Pattern to match. + /// Include prerelease versions. + /// The resulting version. + /// True if a matching version was found. + private static bool TryGetLatestMatchingVersion(IEnumerable versions, string pattern, bool includePrerelease, out WinGetVersion? result) + { + pattern = string.IsNullOrWhiteSpace(pattern) ? "*" : pattern; + + var parts = pattern.Split('.'); + var major = parts.ElementAtOrDefault(0); + var minor = parts.ElementAtOrDefault(1); + var build = parts.ElementAtOrDefault(2); + var revision = parts.ElementAtOrDefault(3); + + if (!includePrerelease) + { + versions = versions.Where(v => !v.IsPrerelease); + } + + versions = versions + .Where(v => + VersionPartMatch(major, v.Version.Major) && + VersionPartMatch(minor, v.Version.Minor) && + VersionPartMatch(build, v.Version.Build) && + VersionPartMatch(revision, v.Version.Revision)) + .OrderBy(f => f.Version); + + if (!versions.Any()) + { + result = null!; + return false; + } + + result = versions.Last(); + return true; + } + + /// + /// Checks if a version part matches a pattern. + /// + /// Version part pattern. + /// Version part value. + /// True if the part matches the pattern. + private static bool VersionPartMatch(string? partPattern, int partValue) + { + if (string.IsNullOrWhiteSpace(partPattern)) + { + return true; + } + + if (partPattern!.StartsWith("*")) + { + return partValue.ToString().EndsWith(partPattern.TrimStart('*')); + } + + if (partPattern!.EndsWith("*")) + { + return partValue.ToString().StartsWith(partPattern.TrimEnd('*')); + } + + return partPattern == partValue.ToString(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/HttpClientHelper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/HttpClientHelper.cs index 35113a6d21..431a982a7d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/HttpClientHelper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/HttpClientHelper.cs @@ -1,115 +1,115 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Diagnostics; - using System.IO; - using System.Management.Automation; - using System.Net.Http; - using System.Threading.Tasks; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - - /// - /// Helper class for HttpClient calls. - /// - internal class HttpClientHelper - { - /// - /// The user agent of this module. - /// - public const string UserAgent = "winget-powershell"; - - private static readonly HttpClient Client; - - static HttpClientHelper() - { - Client = new HttpClient(); - } - - /// - /// Downloads a file from a url. - /// - /// Url. - /// File name. - /// /// PowershellCmdlet. - /// A representing the asynchronous operation. - public async Task DownloadUrlWithProgressAsync(string url, string fileName, PowerShellCmdlet pwshCmdlet) - { - pwshCmdlet.Write(StreamType.Verbose, $"Downloading {url}"); - using var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.Add("User-Agent", UserAgent); - - var cancellationToken = pwshCmdlet.GetCancellationToken(); - using var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - response.EnsureSuccessStatusCode(); - - try - { - long? contentLength = response.Content.Headers.ContentLength; - var responseStream = await response.Content.ReadAsStreamAsync(); - - using var fileStream = File.Open(fileName, FileMode.OpenOrCreate); - - if (contentLength.HasValue) - { - pwshCmdlet.Write(StreamType.Verbose, $"Size {contentLength} bytes"); - - byte[] buffer = new byte[Constants.OneMB]; - int bytesRead, totalBytes = 0; - - var activityId = pwshCmdlet.GetNewProgressActivityId(); - double lengthInMB = (double)contentLength.Value / Constants.OneMB; - try - { - int maxPercentComplete = 0; - while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) - { - await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken); - totalBytes += bytesRead; - - int percentComplete = (int)((double)totalBytes / contentLength * 100); - if (percentComplete > maxPercentComplete) - { - maxPercentComplete = percentComplete; - ProgressRecord record = new (activityId, url, Resources.DownloadingMessage) - { - RecordType = ProgressRecordType.Processing, - }; - - double progress = (double)totalBytes / Constants.OneMB; - record.StatusDescription = $"{progress:0.0} MB / {lengthInMB:0.0} MB"; - record.PercentComplete = percentComplete; - pwshCmdlet.Write(StreamType.Progress, record); - } - } - } - finally - { - pwshCmdlet.CompleteProgress(activityId, url, Resources.DownloadingMessage, true); - } - } - else - { - pwshCmdlet.Write(StreamType.Verbose, $"Content-Length not found in response"); - await responseStream.CopyToAsync(fileStream); - } - } - catch (Exception) - { - if (File.Exists(fileName)) - { - File.Delete(fileName); - } - - throw; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Diagnostics; + using System.IO; + using System.Management.Automation; + using System.Net.Http; + using System.Threading.Tasks; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + + /// + /// Helper class for HttpClient calls. + /// + internal class HttpClientHelper + { + /// + /// The user agent of this module. + /// + public const string UserAgent = "winget-powershell"; + + private static readonly HttpClient Client; + + static HttpClientHelper() + { + Client = new HttpClient(); + } + + /// + /// Downloads a file from a url. + /// + /// Url. + /// File name. + /// /// PowershellCmdlet. + /// A representing the asynchronous operation. + public async Task DownloadUrlWithProgressAsync(string url, string fileName, PowerShellCmdlet pwshCmdlet) + { + pwshCmdlet.Write(StreamType.Verbose, $"Downloading {url}"); + using var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.Add("User-Agent", UserAgent); + + var cancellationToken = pwshCmdlet.GetCancellationToken(); + using var response = await Client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); + response.EnsureSuccessStatusCode(); + + try + { + long? contentLength = response.Content.Headers.ContentLength; + var responseStream = await response.Content.ReadAsStreamAsync(); + + using var fileStream = File.Open(fileName, FileMode.OpenOrCreate); + + if (contentLength.HasValue) + { + pwshCmdlet.Write(StreamType.Verbose, $"Size {contentLength} bytes"); + + byte[] buffer = new byte[Constants.OneMB]; + int bytesRead, totalBytes = 0; + + var activityId = pwshCmdlet.GetNewProgressActivityId(); + double lengthInMB = (double)contentLength.Value / Constants.OneMB; + try + { + int maxPercentComplete = 0; + while ((bytesRead = await responseStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken)) > 0) + { + await fileStream.WriteAsync(buffer, 0, bytesRead, cancellationToken); + totalBytes += bytesRead; + + int percentComplete = (int)((double)totalBytes / contentLength * 100); + if (percentComplete > maxPercentComplete) + { + maxPercentComplete = percentComplete; + ProgressRecord record = new (activityId, url, Resources.DownloadingMessage) + { + RecordType = ProgressRecordType.Processing, + }; + + double progress = (double)totalBytes / Constants.OneMB; + record.StatusDescription = $"{progress:0.0} MB / {lengthInMB:0.0} MB"; + record.PercentComplete = percentComplete; + pwshCmdlet.Write(StreamType.Progress, record); + } + } + } + finally + { + pwshCmdlet.CompleteProgress(activityId, url, Resources.DownloadingMessage, true); + } + } + else + { + pwshCmdlet.Write(StreamType.Verbose, $"Content-Length not found in response"); + await responseStream.CopyToAsync(fileStream); + } + } + catch (Exception) + { + if (File.Exists(fileName)) + { + File.Delete(fileName); + } + + throw; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/InstallOperationWithProgress.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/InstallOperationWithProgress.cs index ef75d4dad3..eee9cf2983 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/InstallOperationWithProgress.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/InstallOperationWithProgress.cs @@ -1,53 +1,53 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Common.Command; - using Windows.Foundation; - - /// - /// Handlers install or update operations with progress. - /// - internal class InstallOperationWithProgress : OperationWithProgressBase - { - /// - /// Initializes a new instance of the class. - /// - /// A instance. - /// Activity. - public InstallOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) - : base(pwshCmdlet, activity) - { - } - - /// - public override void Progress(IAsyncOperationWithProgress operation, InstallProgress progress) - { - ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) - { - RecordType = ProgressRecordType.Processing, - }; - - if (progress.State == PackageInstallProgressState.Downloading && progress.BytesRequired != 0) - { - double downloaded = (double)progress.BytesDownloaded / Constants.OneMB; - double total = (double)progress.BytesRequired / Constants.OneMB; - record.StatusDescription = $"{downloaded:0.0} MB / {total:0.0} MB"; - record.PercentComplete = (int)(progress.DownloadProgress * 100); - } - else if (progress.State == PackageInstallProgressState.Installing) - { - record.PercentComplete = (int)(progress.InstallationProgress * 100); - } - - this.PwshCmdlet.Write(StreamType.Progress, record); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Common.Command; + using Windows.Foundation; + + /// + /// Handlers install or update operations with progress. + /// + internal class InstallOperationWithProgress : OperationWithProgressBase + { + /// + /// Initializes a new instance of the class. + /// + /// A instance. + /// Activity. + public InstallOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) + : base(pwshCmdlet, activity) + { + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, InstallProgress progress) + { + ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }; + + if (progress.State == PackageInstallProgressState.Downloading && progress.BytesRequired != 0) + { + double downloaded = (double)progress.BytesDownloaded / Constants.OneMB; + double total = (double)progress.BytesRequired / Constants.OneMB; + record.StatusDescription = $"{downloaded:0.0} MB / {total:0.0} MB"; + record.PercentComplete = (int)(progress.DownloadProgress * 100); + } + else if (progress.State == PackageInstallProgressState.Installing) + { + record.PercentComplete = (int)(progress.InstallationProgress * 100); + } + + this.PwshCmdlet.Write(StreamType.Progress, record); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs index 76d9121e4a..f3ed366af5 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/ManagementDeploymentFactory.cs @@ -1,236 +1,236 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - -#if NET - using WinRT; -#endif - - /// - /// Constructs instances of classes from the namespace. - /// - internal sealed class ManagementDeploymentFactory - { -#if USE_PROD_CLSIDS - private static readonly Guid PackageManagerClsid = Guid.Parse("C53A4F16-787E-42A4-B304-29EFFB4BF597"); - private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("572DED96-9C60-4526-8F92-EE7D91D38C1A"); - private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("526534B8-7E46-47C8-8416-B1685C327D37"); - private static readonly Guid InstallOptionsClsid = Guid.Parse("1095F097-EB96-453B-B4E6-1613637F3B14"); - private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); - private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); - private static readonly Guid DownloadOptionsClsid = Guid.Parse("4CBABE76-7322-4BE4-9CEA-2589A80682DC"); - private static readonly Guid RepairOptionsClsid = Guid.Parse("0498F441-3097-455F-9CAF-148F28293865"); -#else - private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); - private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); - private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"); - private static readonly Guid InstallOptionsClsid = Guid.Parse("44FE0580-62F7-44D4-9E91-AA9614AB3E86"); - private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); - private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); - private static readonly Guid DownloadOptionsClsid = Guid.Parse("8EF324ED-367C-4880-83E5-BB2ABD0B72F6"); - private static readonly Guid RepairOptionsClsid = Guid.Parse("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"); -#endif - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? FindPackagesOptionsType = Type.GetTypeFromCLSID(FindPackagesOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? CreateCompositePackageCatalogOptionsType = Type.GetTypeFromCLSID(CreateCompositePackageCatalogOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? InstallOptionsType = Type.GetTypeFromCLSID(InstallOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? UninstallOptionsType = Type.GetTypeFromCLSID(UninstallOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? DownloadOptionsType = Type.GetTypeFromCLSID(DownloadOptionsClsid); - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static readonly Type? RepairOptionsType = Type.GetTypeFromCLSID(RepairOptionsClsid); - - // These GUIDs correspond to the CSWinRT interface IIDs generated for Microsoft.Management.Deployment.Projection - // and are auto-generated by the WinRT tool. - private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); - private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); - private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); - private static readonly Guid InstallOptionsIid = Guid.Parse("6EE9DB69-AB48-5E72-A474-33A924CD23B3"); - private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); - private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); - private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); - private static readonly Guid RepairOptionsIid = Guid.Parse("263F0546-2D7E-53A0-B8D1-75B74817FF18"); - - private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; - - private static readonly Lazy Lazy = new (() => new ManagementDeploymentFactory()); - - /// - /// Initializes static members of the class. - /// - static ManagementDeploymentFactory() - { - if (Utilities.UsesInProcWinget) - { - PackageManagerSettings settings = new PackageManagerSettings(); - settings.SetCallerIdentifier("PowerShellInProc"); - } - } - - private ManagementDeploymentFactory() - { - } - - /// - /// Gets the instance object. - /// - public static ManagementDeploymentFactory Instance - { - get { return Lazy.Value; } - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public PackageManager CreatePackageManager() - { - var result = Create(PackageManagerType, PackageManagerIid); - - if (!Utilities.UsesInProcWinget) - { - _ = CoAllowSetForegroundWindow(result, IntPtr.Zero); - } - - return result; - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public FindPackagesOptions CreateFindPackagesOptions() - { - return Create(FindPackagesOptionsType, FindPackagesOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() - { - return Create(CreateCompositePackageCatalogOptionsType, CreateCompositePackageCatalogOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// An instance. - public InstallOptions CreateInstallOptions() - { - return Create(InstallOptionsType, InstallOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public UninstallOptions CreateUninstallOptions() - { - return Create(UninstallOptionsType, UninstallOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public DownloadOptions CreateDownloadOptions() - { - return Create(DownloadOptionsType, DownloadOptionsIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public PackageMatchFilter CreatePackageMatchFilter() - { - return Create(PackageMatchFilterType, PackageMatchFilterIid); - } - - /// - /// Creates an instance of the class. - /// - /// A instance. - public RepairOptions CreateRepairOptions() - { - return Create(RepairOptionsType, RepairOptionsIid); - } - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] - private static T Create(Type? type, in Guid iid) - where T : new() - { - if (type == null) - { - throw new ArgumentNullException(iid.ToString()); - } - - if (Utilities.UsesInProcWinget) - { - // This doesn't work on Windows PowerShell - // If we want to support it, we need something that loads the - // Microsoft.Management.Deployment.dll for .NET framework as CsWinRT - // does for .NET Core - return new T(); - } - - object? instance = null; - - if (Utilities.ExecutingAsAdministrator) - { - int hr = WinRTHelpers.ManualActivation(type.GUID, iid, 0, out instance); - - if (hr < 0) - { - if (hr == ErrorCode.FileNotFound || hr == ErrorCode.PackageNotRegisteredForUser) - { - throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); - } - else - { - throw new COMException($"Failed to create instance: {hr}", hr); - } - } - } - else - { - instance = Activator.CreateInstance(type); - } - - if (instance == null) - { - throw new ArgumentNullException(); - } - -#if NET - IntPtr pointer = Marshal.GetIUnknownForObject(instance); - return MarshalInterface.FromAbi(pointer); -#else - return (T)instance; -#endif - } - - [DllImport("ole32.dll", ExactSpelling = true, PreserveSig = true)] - private static extern int CoAllowSetForegroundWindow([MarshalAs(UnmanagedType.IUnknown)] object pUnk, IntPtr reserved); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + +#if NET + using WinRT; +#endif + + /// + /// Constructs instances of classes from the namespace. + /// + internal sealed class ManagementDeploymentFactory + { +#if USE_PROD_CLSIDS + private static readonly Guid PackageManagerClsid = Guid.Parse("C53A4F16-787E-42A4-B304-29EFFB4BF597"); + private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("572DED96-9C60-4526-8F92-EE7D91D38C1A"); + private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("526534B8-7E46-47C8-8416-B1685C327D37"); + private static readonly Guid InstallOptionsClsid = Guid.Parse("1095F097-EB96-453B-B4E6-1613637F3B14"); + private static readonly Guid UninstallOptionsClsid = Guid.Parse("E1D9A11E-9F85-4D87-9C17-2B93143ADB8D"); + private static readonly Guid PackageMatchFilterClsid = Guid.Parse("D02C9DAF-99DC-429C-B503-4E504E4AB000"); + private static readonly Guid DownloadOptionsClsid = Guid.Parse("4CBABE76-7322-4BE4-9CEA-2589A80682DC"); + private static readonly Guid RepairOptionsClsid = Guid.Parse("0498F441-3097-455F-9CAF-148F28293865"); +#else + private static readonly Guid PackageManagerClsid = Guid.Parse("74CB3139-B7C5-4B9E-9388-E6616DEA288C"); + private static readonly Guid FindPackagesOptionsClsid = Guid.Parse("1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96"); + private static readonly Guid CreateCompositePackageCatalogOptionsClsid = Guid.Parse("EE160901-B317-4EA7-9CC6-5355C6D7D8A7"); + private static readonly Guid InstallOptionsClsid = Guid.Parse("44FE0580-62F7-44D4-9E91-AA9614AB3E86"); + private static readonly Guid UninstallOptionsClsid = Guid.Parse("AA2A5C04-1AD9-46C4-B74F-6B334AD7EB8C"); + private static readonly Guid PackageMatchFilterClsid = Guid.Parse("3F85B9F4-487A-4C48-9035-2903F8A6D9E8"); + private static readonly Guid DownloadOptionsClsid = Guid.Parse("8EF324ED-367C-4880-83E5-BB2ABD0B72F6"); + private static readonly Guid RepairOptionsClsid = Guid.Parse("E62BB1E7-C7B2-4AEC-9E28-FB649B30FF03"); +#endif + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? PackageManagerType = Type.GetTypeFromCLSID(PackageManagerClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? FindPackagesOptionsType = Type.GetTypeFromCLSID(FindPackagesOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? CreateCompositePackageCatalogOptionsType = Type.GetTypeFromCLSID(CreateCompositePackageCatalogOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? InstallOptionsType = Type.GetTypeFromCLSID(InstallOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? UninstallOptionsType = Type.GetTypeFromCLSID(UninstallOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? PackageMatchFilterType = Type.GetTypeFromCLSID(PackageMatchFilterClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? DownloadOptionsType = Type.GetTypeFromCLSID(DownloadOptionsClsid); + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static readonly Type? RepairOptionsType = Type.GetTypeFromCLSID(RepairOptionsClsid); + + // These GUIDs correspond to the CSWinRT interface IIDs generated for Microsoft.Management.Deployment.Projection + // and are auto-generated by the WinRT tool. + private static readonly Guid PackageManagerIid = Guid.Parse("B375E3B9-F2E0-5C93-87A7-B67497F7E593"); + private static readonly Guid FindPackagesOptionsIid = Guid.Parse("A5270EDD-7DA7-57A3-BACE-F2593553561F"); + private static readonly Guid CreateCompositePackageCatalogOptionsIid = Guid.Parse("21ABAA76-089D-51C5-A745-C85EEFE70116"); + private static readonly Guid InstallOptionsIid = Guid.Parse("6EE9DB69-AB48-5E72-A474-33A924CD23B3"); + private static readonly Guid UninstallOptionsIid = Guid.Parse("3EBC67F0-8339-594B-8A42-F90B69D02BBE"); + private static readonly Guid PackageMatchFilterIid = Guid.Parse("D981ECA3-4DE5-5AD7-967A-698C7D60FC3B"); + private static readonly Guid DownloadOptionsIid = Guid.Parse("94C92C4B-43F5-5CA3-BBBE-9F432C9546BC"); + private static readonly Guid RepairOptionsIid = Guid.Parse("263F0546-2D7E-53A0-B8D1-75B74817FF18"); + + private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64 }; + + private static readonly Lazy Lazy = new (() => new ManagementDeploymentFactory()); + + /// + /// Initializes static members of the class. + /// + static ManagementDeploymentFactory() + { + if (Utilities.UsesInProcWinget) + { + PackageManagerSettings settings = new PackageManagerSettings(); + settings.SetCallerIdentifier("PowerShellInProc"); + } + } + + private ManagementDeploymentFactory() + { + } + + /// + /// Gets the instance object. + /// + public static ManagementDeploymentFactory Instance + { + get { return Lazy.Value; } + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public PackageManager CreatePackageManager() + { + var result = Create(PackageManagerType, PackageManagerIid); + + if (!Utilities.UsesInProcWinget) + { + _ = CoAllowSetForegroundWindow(result, IntPtr.Zero); + } + + return result; + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public FindPackagesOptions CreateFindPackagesOptions() + { + return Create(FindPackagesOptionsType, FindPackagesOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() + { + return Create(CreateCompositePackageCatalogOptionsType, CreateCompositePackageCatalogOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// An instance. + public InstallOptions CreateInstallOptions() + { + return Create(InstallOptionsType, InstallOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public UninstallOptions CreateUninstallOptions() + { + return Create(UninstallOptionsType, UninstallOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public DownloadOptions CreateDownloadOptions() + { + return Create(DownloadOptionsType, DownloadOptionsIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public PackageMatchFilter CreatePackageMatchFilter() + { + return Create(PackageMatchFilterType, PackageMatchFilterIid); + } + + /// + /// Creates an instance of the class. + /// + /// A instance. + public RepairOptions CreateRepairOptions() + { + return Create(RepairOptionsType, RepairOptionsIid); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "COM only usage.")] + private static T Create(Type? type, in Guid iid) + where T : new() + { + if (type == null) + { + throw new ArgumentNullException(iid.ToString()); + } + + if (Utilities.UsesInProcWinget) + { + // This doesn't work on Windows PowerShell + // If we want to support it, we need something that loads the + // Microsoft.Management.Deployment.dll for .NET framework as CsWinRT + // does for .NET Core + return new T(); + } + + object? instance = null; + + if (Utilities.ExecutingAsAdministrator) + { + int hr = WinRTHelpers.ManualActivation(type.GUID, iid, 0, out instance); + + if (hr < 0) + { + if (hr == ErrorCode.FileNotFound || hr == ErrorCode.PackageNotRegisteredForUser) + { + throw new WinGetIntegrityException(IntegrityCategory.AppInstallerNotInstalled); + } + else + { + throw new COMException($"Failed to create instance: {hr}", hr); + } + } + } + else + { + instance = Activator.CreateInstance(type); + } + + if (instance == null) + { + throw new ArgumentNullException(); + } + +#if NET + IntPtr pointer = Marshal.GetIUnknownForObject(instance); + return MarshalInterface.FromAbi(pointer); +#else + return (T)instance; +#endif + } + + [DllImport("ole32.dll", ExactSpelling = true, PreserveSig = true)] + private static extern int CoAllowSetForegroundWindow([MarshalAs(UnmanagedType.IUnknown)] object pUnk, IntPtr reserved); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/OperationWithProgressBase.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/OperationWithProgressBase.cs index e5cb2535cd..0af56e45b0 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/OperationWithProgressBase.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/OperationWithProgressBase.cs @@ -1,110 +1,110 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Runtime.InteropServices; - using System.Threading.Tasks; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - using Windows.Foundation; - - /// - /// Base class for async operations with progress. - /// - /// The operation result. - /// Progress data. - internal abstract class OperationWithProgressBase - { - private static bool isProgressEnabled; - - static OperationWithProgressBase() - { - // Progress on arm64 will produce an AV because there's an OS bug where marshaling structs over a certain size fail. - // Fix is in 10.0.26068.0, for build before that disable progress. - if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) - { - var minWindowsVersion = new Version(10, 0, 26068, 0); - var osVersion = Environment.OSVersion.Version; - isProgressEnabled = osVersion.CompareTo(minWindowsVersion) >= 0; - } - else - { - isProgressEnabled = true; - } - } - - /// - /// Initializes a new instance of the class. - /// - /// A instance. - /// Activity. - public OperationWithProgressBase( - PowerShellCmdlet pwshCmdlet, - string activity) - { - this.PwshCmdlet = pwshCmdlet; - this.ActivityId = pwshCmdlet.GetNewProgressActivityId(); - this.Activity = activity; - } - - /// - /// Gets the PowerShellCmdlet. - /// - protected PowerShellCmdlet PwshCmdlet { get; } - - /// - /// Gets the progress activity id. - /// - protected int ActivityId { get; } - - /// - /// Gets the activity. - /// - protected string Activity { get; } - - /// - /// Progress callback. - /// - /// Async operation in progress. - /// Progress data. - public abstract void Progress(IAsyncOperationWithProgress operation, TProgressData progress); - - /// - /// Starts the operation and executes it as task. - /// Supports cancellation. - /// - /// Lambda with operation. - /// TOperationReturn. - public async Task ExecuteAsync(Func> func) - { - var operation = func(); - - if (isProgressEnabled) - { - operation.Progress = this.Progress; - } - - try - { - return await operation.AsTask(this.PwshCmdlet.GetCancellationToken()); - } - finally - { - this.Complete(); - } - } - - /// - /// Completes progress for this activity. - /// - protected virtual void Complete() - { - this.PwshCmdlet.CompleteProgress(this.ActivityId, this.Activity, Resources.Completed, true); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Runtime.InteropServices; + using System.Threading.Tasks; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + using Windows.Foundation; + + /// + /// Base class for async operations with progress. + /// + /// The operation result. + /// Progress data. + internal abstract class OperationWithProgressBase + { + private static bool isProgressEnabled; + + static OperationWithProgressBase() + { + // Progress on arm64 will produce an AV because there's an OS bug where marshaling structs over a certain size fail. + // Fix is in 10.0.26068.0, for build before that disable progress. + if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64) + { + var minWindowsVersion = new Version(10, 0, 26068, 0); + var osVersion = Environment.OSVersion.Version; + isProgressEnabled = osVersion.CompareTo(minWindowsVersion) >= 0; + } + else + { + isProgressEnabled = true; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// A instance. + /// Activity. + public OperationWithProgressBase( + PowerShellCmdlet pwshCmdlet, + string activity) + { + this.PwshCmdlet = pwshCmdlet; + this.ActivityId = pwshCmdlet.GetNewProgressActivityId(); + this.Activity = activity; + } + + /// + /// Gets the PowerShellCmdlet. + /// + protected PowerShellCmdlet PwshCmdlet { get; } + + /// + /// Gets the progress activity id. + /// + protected int ActivityId { get; } + + /// + /// Gets the activity. + /// + protected string Activity { get; } + + /// + /// Progress callback. + /// + /// Async operation in progress. + /// Progress data. + public abstract void Progress(IAsyncOperationWithProgress operation, TProgressData progress); + + /// + /// Starts the operation and executes it as task. + /// Supports cancellation. + /// + /// Lambda with operation. + /// TOperationReturn. + public async Task ExecuteAsync(Func> func) + { + var operation = func(); + + if (isProgressEnabled) + { + operation.Progress = this.Progress; + } + + try + { + return await operation.AsTask(this.PwshCmdlet.GetCancellationToken()); + } + finally + { + this.Complete(); + } + } + + /// + /// Completes progress for this activity. + /// + protected virtual void Complete() + { + this.PwshCmdlet.CompleteProgress(this.ActivityId, this.Activity, Resources.Completed, true); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs index 2228a1c1c4..de3f0d82b6 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PSEnumHelpers.cs @@ -1,172 +1,172 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using Microsoft.Management.Deployment; - using Newtonsoft.Json.Linq; - using Windows.System; - - /// - /// Extension methods for PS Enum wrappers for Microsoft.Management.Deployment enums. - /// - internal static class PSEnumHelpers - { - /// - /// Checks if the provided enum string value matches the 'Default' value for PS Enums. - /// - /// Enum string value. - /// Boolean value. - public static bool IsDefaultEnum(string value) - { - return string.Equals(value, "Default", StringComparison.OrdinalIgnoreCase); - } - - /// - /// Converts PSPackageInstallMode string value to PackageInstallMode. - /// - /// PSPackageInstallMode to string value. - /// PackageInstallMode. - public static PackageInstallMode ToPackageInstallMode(string value) - { - return value switch - { - "Default" => PackageInstallMode.Default, - "Silent" => PackageInstallMode.Silent, - "Interactive" => PackageInstallMode.Interactive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageInstallScope string value to PackageInstallScope. - /// - /// PSPackageInstallScope to string value. - /// PackageInstallScope. - public static PackageInstallScope ToPackageInstallScope(string value) - { - return value switch - { - "Any" => PackageInstallScope.Any, - "User" => PackageInstallScope.User, - "System" => PackageInstallScope.System, - "UserOrUnknown" => PackageInstallScope.UserOrUnknown, - "SystemOrUnknown" => PackageInstallScope.SystemOrUnknown, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSProcessorArchitecture string value to ProcessorArchitecture. - /// - /// PSProcessorArchitecture to string value. - /// ProcessorArchitecture. - public static ProcessorArchitecture ToProcessorArchitecture(string value) - { - return value switch - { - "X86" => ProcessorArchitecture.X86, - "Arm" => ProcessorArchitecture.Arm, - "X64" => ProcessorArchitecture.X64, - "Arm64" => ProcessorArchitecture.Arm64, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageUninstallMode string value to PackageUninstallMode. - /// - /// PSPackageUninstallMode string value. - /// PackageUninstallMode. - public static PackageUninstallMode ToPackageUninstallMode(string value) - { - return value switch - { - "Default" => PackageUninstallMode.Default, - "Silent" => PackageUninstallMode.Silent, - "Interactive" => PackageUninstallMode.Interactive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageFieldMatchOption string value to PackageFieldMatchOption. - /// - /// PSPackageFieldMatchOption string value. - /// PackageFieldMatchOption. - public static PackageFieldMatchOption ToPackageFieldMatchOption(string value) - { - return value switch - { - "Equals" => PackageFieldMatchOption.Equals, - "EqualsCaseInsensitive" => PackageFieldMatchOption.EqualsCaseInsensitive, - "StartsWithCaseInsensitive" => PackageFieldMatchOption.StartsWithCaseInsensitive, - "ContainsCaseInsensitive" => PackageFieldMatchOption.ContainsCaseInsensitive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageInstallerType string value to PackageInstallerType. - /// - /// PSPackageInstallerType string value. - /// PackageInstallerType. - public static PackageInstallerType ToPackageInstallerType(string value) - { - return value switch - { - "Unknown" => PackageInstallerType.Unknown, - "Inno" => PackageInstallerType.Inno, - "Wix" => PackageInstallerType.Wix, - "Msi" => PackageInstallerType.Msi, - "Nullsoft" => PackageInstallerType.Nullsoft, - "Zip" => PackageInstallerType.Zip, - "Msix" => PackageInstallerType.Msix, - "Exe" => PackageInstallerType.Exe, - "Burn" => PackageInstallerType.Burn, - "MSStore" => PackageInstallerType.MSStore, - "Portable" => PackageInstallerType.Portable, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSPackageRepairMode string value to PackageRepairMode. - /// - /// PSPackageRepairMode string value. - /// PackageRepairMode. - public static PackageRepairMode ToPackageRepairMode(string value) - { - return value switch - { - "Default" => PackageRepairMode.Default, - "Silent" => PackageRepairMode.Silent, - "Interactive" => PackageRepairMode.Interactive, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts PSWindowsPlatform string value to WindowsPlatform. - /// - /// PSWindowsPlatform string value. - /// WindowsPlatform. - public static WindowsPlatform ToWindowsPlatform(string value) - { - return value switch - { - "Default" => WindowsPlatform.Unknown, - "Universal" => WindowsPlatform.Universal, - "Desktop" => WindowsPlatform.Desktop, - "IoT" => WindowsPlatform.IoT, - "Team" => WindowsPlatform.Team, - "Holographic" => WindowsPlatform.Holographic, - _ => throw new InvalidOperationException(), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using Microsoft.Management.Deployment; + using Newtonsoft.Json.Linq; + using Windows.System; + + /// + /// Extension methods for PS Enum wrappers for Microsoft.Management.Deployment enums. + /// + internal static class PSEnumHelpers + { + /// + /// Checks if the provided enum string value matches the 'Default' value for PS Enums. + /// + /// Enum string value. + /// Boolean value. + public static bool IsDefaultEnum(string value) + { + return string.Equals(value, "Default", StringComparison.OrdinalIgnoreCase); + } + + /// + /// Converts PSPackageInstallMode string value to PackageInstallMode. + /// + /// PSPackageInstallMode to string value. + /// PackageInstallMode. + public static PackageInstallMode ToPackageInstallMode(string value) + { + return value switch + { + "Default" => PackageInstallMode.Default, + "Silent" => PackageInstallMode.Silent, + "Interactive" => PackageInstallMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageInstallScope string value to PackageInstallScope. + /// + /// PSPackageInstallScope to string value. + /// PackageInstallScope. + public static PackageInstallScope ToPackageInstallScope(string value) + { + return value switch + { + "Any" => PackageInstallScope.Any, + "User" => PackageInstallScope.User, + "System" => PackageInstallScope.System, + "UserOrUnknown" => PackageInstallScope.UserOrUnknown, + "SystemOrUnknown" => PackageInstallScope.SystemOrUnknown, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSProcessorArchitecture string value to ProcessorArchitecture. + /// + /// PSProcessorArchitecture to string value. + /// ProcessorArchitecture. + public static ProcessorArchitecture ToProcessorArchitecture(string value) + { + return value switch + { + "X86" => ProcessorArchitecture.X86, + "Arm" => ProcessorArchitecture.Arm, + "X64" => ProcessorArchitecture.X64, + "Arm64" => ProcessorArchitecture.Arm64, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageUninstallMode string value to PackageUninstallMode. + /// + /// PSPackageUninstallMode string value. + /// PackageUninstallMode. + public static PackageUninstallMode ToPackageUninstallMode(string value) + { + return value switch + { + "Default" => PackageUninstallMode.Default, + "Silent" => PackageUninstallMode.Silent, + "Interactive" => PackageUninstallMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageFieldMatchOption string value to PackageFieldMatchOption. + /// + /// PSPackageFieldMatchOption string value. + /// PackageFieldMatchOption. + public static PackageFieldMatchOption ToPackageFieldMatchOption(string value) + { + return value switch + { + "Equals" => PackageFieldMatchOption.Equals, + "EqualsCaseInsensitive" => PackageFieldMatchOption.EqualsCaseInsensitive, + "StartsWithCaseInsensitive" => PackageFieldMatchOption.StartsWithCaseInsensitive, + "ContainsCaseInsensitive" => PackageFieldMatchOption.ContainsCaseInsensitive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageInstallerType string value to PackageInstallerType. + /// + /// PSPackageInstallerType string value. + /// PackageInstallerType. + public static PackageInstallerType ToPackageInstallerType(string value) + { + return value switch + { + "Unknown" => PackageInstallerType.Unknown, + "Inno" => PackageInstallerType.Inno, + "Wix" => PackageInstallerType.Wix, + "Msi" => PackageInstallerType.Msi, + "Nullsoft" => PackageInstallerType.Nullsoft, + "Zip" => PackageInstallerType.Zip, + "Msix" => PackageInstallerType.Msix, + "Exe" => PackageInstallerType.Exe, + "Burn" => PackageInstallerType.Burn, + "MSStore" => PackageInstallerType.MSStore, + "Portable" => PackageInstallerType.Portable, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSPackageRepairMode string value to PackageRepairMode. + /// + /// PSPackageRepairMode string value. + /// PackageRepairMode. + public static PackageRepairMode ToPackageRepairMode(string value) + { + return value switch + { + "Default" => PackageRepairMode.Default, + "Silent" => PackageRepairMode.Silent, + "Interactive" => PackageRepairMode.Interactive, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts PSWindowsPlatform string value to WindowsPlatform. + /// + /// PSWindowsPlatform string value. + /// WindowsPlatform. + public static WindowsPlatform ToWindowsPlatform(string value) + { + return value switch + { + "Default" => WindowsPlatform.Unknown, + "Universal" => WindowsPlatform.Universal, + "Desktop" => WindowsPlatform.Desktop, + "IoT" => WindowsPlatform.IoT, + "Team" => WindowsPlatform.Team, + "Holographic" => WindowsPlatform.Holographic, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs index f1ef5204e0..ec56a4b6d2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/PackageManagerWrapper.cs @@ -1,189 +1,189 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using System.Runtime.InteropServices; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Windows.Foundation; - - /// - /// Wrapper for PackageManager that handles rpc disconnections. - /// The object is disconnected when the server is killed or on an update. - /// - internal sealed class PackageManagerWrapper - { - private static readonly Lazy Lazy = new (() => new PackageManagerWrapper()); - - private PackageManager packageManager = null!; - - private PackageManagerWrapper() - { - } - - /// - /// Gets the instance object. - /// - public static PackageManagerWrapper Instance - { - get { return Lazy.Value; } - } - - /// - /// Wrapper for InstallPackageAsync. - /// - /// The package to install. - /// The install options. - /// An async operation with progress. - public IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options) - { - return this.Execute( - () => this.packageManager.InstallPackageAsync(package, options), - false); - } - - /// - /// Wrapper for UpgradePackageAsync. - /// - /// The package to upgrade. - /// The install options. - /// An async operation with progress. - public IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options) - { - return this.Execute( - () => this.packageManager.UpgradePackageAsync(package, options), - false); - } - - /// - /// Wrapper for UninstallPackageAsync. - /// - /// The package to uninstall. - /// The uninstall options. - /// An async operation with progress. - public IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options) - { - return this.Execute( - () => this.packageManager.UninstallPackageAsync(package, options), - false); - } - - /// - /// Wrapper for DownloadPackageAsync. - /// - /// The package to download. - /// The download options. - /// An async operation with progress. - public IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options) - { - return this.Execute( - () => this.packageManager.DownloadPackageAsync(package, options), - false); - } - - /// - /// Wrapper for RepairPackagesAsync. - /// - /// The package to repair. - /// The repair options. - /// An async operation with progress. - public IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options) - { - return this.Execute( - () => this.packageManager.RepairPackageAsync(package, options), - false); - } - - /// - /// Wrapper for GetPackageCatalogs. - /// - /// A list of PackageCatalogReferences. - public IReadOnlyList GetPackageCatalogs() - { - return this.Execute( - () => this.packageManager.GetPackageCatalogs(), - true); - } - - /// - /// Wrapper for GetPackageCatalogByName. - /// - /// The name of the source. - /// A PackageCatalogReference. - public PackageCatalogReference GetPackageCatalogByName(string source) - { - return this.Execute( - () => this.packageManager.GetPackageCatalogByName(source), - true); - } - - /// - /// Wrapper for CreateCompositePackageCatalog. - /// - /// CreateCompositePackageCatalogOptions. - /// A PackageCatalogReference. - public PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options) - { - return this.Execute( - () => this.packageManager.CreateCompositePackageCatalog(options), - false); - } - - /// - /// Gets the version of the package manager that is running. - /// - /// The version string. - public string? GetVersion() - { - try - { - return this.Execute(() => this.packageManager.Version, true); - } - catch - { - return null; - } - } - - private TReturn Execute(Func func, bool canRetry) - { - if (Utilities.UsesInProcWinget && Utilities.ThreadIsSTA) - { - // If you failed here, then you didn't wrap your call in ManagementDeploymentCommand.Execute - throw new SingleThreadedApartmentException(); - } - - bool stopRetry = false; - while (true) - { - if (this.packageManager == null) - { - this.packageManager = ManagementDeploymentFactory.Instance.CreatePackageManager(); - } - - try - { - return func(); - } - catch (COMException ex) when (ex.HResult == ErrorCode.RpcServerUnavailable || ex.HResult == ErrorCode.RpcCallFailed) - { - this.packageManager = null!; - - if (stopRetry || !canRetry) - { - throw; - } - - stopRetry = true; - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.Runtime.InteropServices; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Windows.Foundation; + + /// + /// Wrapper for PackageManager that handles rpc disconnections. + /// The object is disconnected when the server is killed or on an update. + /// + internal sealed class PackageManagerWrapper + { + private static readonly Lazy Lazy = new (() => new PackageManagerWrapper()); + + private PackageManager packageManager = null!; + + private PackageManagerWrapper() + { + } + + /// + /// Gets the instance object. + /// + public static PackageManagerWrapper Instance + { + get { return Lazy.Value; } + } + + /// + /// Wrapper for InstallPackageAsync. + /// + /// The package to install. + /// The install options. + /// An async operation with progress. + public IAsyncOperationWithProgress InstallPackageAsync(CatalogPackage package, InstallOptions options) + { + return this.Execute( + () => this.packageManager.InstallPackageAsync(package, options), + false); + } + + /// + /// Wrapper for UpgradePackageAsync. + /// + /// The package to upgrade. + /// The install options. + /// An async operation with progress. + public IAsyncOperationWithProgress UpgradePackageAsync(CatalogPackage package, InstallOptions options) + { + return this.Execute( + () => this.packageManager.UpgradePackageAsync(package, options), + false); + } + + /// + /// Wrapper for UninstallPackageAsync. + /// + /// The package to uninstall. + /// The uninstall options. + /// An async operation with progress. + public IAsyncOperationWithProgress UninstallPackageAsync(CatalogPackage package, UninstallOptions options) + { + return this.Execute( + () => this.packageManager.UninstallPackageAsync(package, options), + false); + } + + /// + /// Wrapper for DownloadPackageAsync. + /// + /// The package to download. + /// The download options. + /// An async operation with progress. + public IAsyncOperationWithProgress DownloadPackageAsync(CatalogPackage package, DownloadOptions options) + { + return this.Execute( + () => this.packageManager.DownloadPackageAsync(package, options), + false); + } + + /// + /// Wrapper for RepairPackagesAsync. + /// + /// The package to repair. + /// The repair options. + /// An async operation with progress. + public IAsyncOperationWithProgress RepairPackageAsync(CatalogPackage package, RepairOptions options) + { + return this.Execute( + () => this.packageManager.RepairPackageAsync(package, options), + false); + } + + /// + /// Wrapper for GetPackageCatalogs. + /// + /// A list of PackageCatalogReferences. + public IReadOnlyList GetPackageCatalogs() + { + return this.Execute( + () => this.packageManager.GetPackageCatalogs(), + true); + } + + /// + /// Wrapper for GetPackageCatalogByName. + /// + /// The name of the source. + /// A PackageCatalogReference. + public PackageCatalogReference GetPackageCatalogByName(string source) + { + return this.Execute( + () => this.packageManager.GetPackageCatalogByName(source), + true); + } + + /// + /// Wrapper for CreateCompositePackageCatalog. + /// + /// CreateCompositePackageCatalogOptions. + /// A PackageCatalogReference. + public PackageCatalogReference CreateCompositePackageCatalog(CreateCompositePackageCatalogOptions options) + { + return this.Execute( + () => this.packageManager.CreateCompositePackageCatalog(options), + false); + } + + /// + /// Gets the version of the package manager that is running. + /// + /// The version string. + public string? GetVersion() + { + try + { + return this.Execute(() => this.packageManager.Version, true); + } + catch + { + return null; + } + } + + private TReturn Execute(Func func, bool canRetry) + { + if (Utilities.UsesInProcWinget && Utilities.ThreadIsSTA) + { + // If you failed here, then you didn't wrap your call in ManagementDeploymentCommand.Execute + throw new SingleThreadedApartmentException(); + } + + bool stopRetry = false; + while (true) + { + if (this.packageManager == null) + { + this.packageManager = ManagementDeploymentFactory.Instance.CreatePackageManager(); + } + + try + { + return func(); + } + catch (COMException ex) when (ex.HResult == ErrorCode.RpcServerUnavailable || ex.HResult == ErrorCode.RpcCallFailed) + { + this.packageManager = null!; + + if (stopRetry || !canRetry) + { + throw; + } + + stopRetry = true; + } + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs index d773dd1fa4..86f6919750 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/RepairOperationWithProgress.cs @@ -1,44 +1,44 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - using Windows.Foundation; - - /// - /// Handler for Repair Operation with Progress. - /// - internal class RepairOperationWithProgress : OperationWithProgressBase - { - /// - /// Initializes a new instance of the class. - /// - /// instance. - /// Activity. - public RepairOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) - : base(pwshCmdlet, activity) - { - } - - /// - public override void Progress(IAsyncOperationWithProgress operation, RepairProgress progress) - { - ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) - { - RecordType = ProgressRecordType.Processing, - }; - - record.StatusDescription = Resources.Repairing; - record.PercentComplete = (int)(progress.RepairCompletionProgress * 100); - this.PwshCmdlet.Write(StreamType.Progress, record); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + using Windows.Foundation; + + /// + /// Handler for Repair Operation with Progress. + /// + internal class RepairOperationWithProgress : OperationWithProgressBase + { + /// + /// Initializes a new instance of the class. + /// + /// instance. + /// Activity. + public RepairOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) + : base(pwshCmdlet, activity) + { + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, RepairProgress progress) + { + ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }; + + record.StatusDescription = Resources.Repairing; + record.PercentComplete = (int)(progress.RepairCompletionProgress * 100); + this.PwshCmdlet.Write(StreamType.Progress, record); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs index 8c087860d2..5f0c9b8d38 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempDirectory.cs @@ -1,118 +1,118 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.IO; - - /// - /// Creates a temporary directory in the user's temporary directory. - /// - internal class TempDirectory : IDisposable - { - private readonly bool cleanup; - private bool disposed = false; - - /// - /// Initializes a new instance of the class. - /// - /// Optional directory name. If null, creates a random directory name. - /// Delete directory if already exists. Default true. - /// Deletes directory at disposing time. Default true. - public TempDirectory( - string? directoryName = null, - bool deleteIfExists = true, - bool cleanup = true) - { - if (directoryName is null) - { - this.DirectoryName = Path.GetRandomFileName(); - } - else - { - this.DirectoryName = directoryName; - } - - this.FullDirectoryPath = Path.Combine(Path.GetTempPath(), this.DirectoryName); - - if (deleteIfExists && Directory.Exists(this.FullDirectoryPath)) - { - Directory.Delete(this.FullDirectoryPath, true); - } - - Directory.CreateDirectory(this.FullDirectoryPath); - this.cleanup = cleanup; - } - - /// - /// Gets the directory name. - /// - public string DirectoryName { get; } - - /// - /// Gets the full directory name. - /// - public string FullDirectoryPath { get; } - - /// - /// IDisposable.Dispose . - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Copies all contents of a directory into this directory. - /// - /// Source directory. - public void CopyDirectory(string sourceDir) - { - this.CopyDirectory(sourceDir, this.FullDirectoryPath); - } - - /// - /// Protected disposed. - /// - /// Disposing. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (this.cleanup && Directory.Exists(this.FullDirectoryPath)) - { - Directory.Delete(this.FullDirectoryPath, true); - } - - this.disposed = true; - } - } - - private void CopyDirectory(string sourceDir, string destinationDir) - { - var dir = new DirectoryInfo(sourceDir); - - if (!dir.Exists) - { - throw new DirectoryNotFoundException(dir.FullName); - } - - Directory.CreateDirectory(destinationDir); - - foreach (FileInfo file in dir.GetFiles()) - { - file.CopyTo(Path.Combine(destinationDir, file.Name)); - } - - foreach (DirectoryInfo subDir in dir.GetDirectories()) - { - this.CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name)); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.IO; + + /// + /// Creates a temporary directory in the user's temporary directory. + /// + internal class TempDirectory : IDisposable + { + private readonly bool cleanup; + private bool disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// Optional directory name. If null, creates a random directory name. + /// Delete directory if already exists. Default true. + /// Deletes directory at disposing time. Default true. + public TempDirectory( + string? directoryName = null, + bool deleteIfExists = true, + bool cleanup = true) + { + if (directoryName is null) + { + this.DirectoryName = Path.GetRandomFileName(); + } + else + { + this.DirectoryName = directoryName; + } + + this.FullDirectoryPath = Path.Combine(Path.GetTempPath(), this.DirectoryName); + + if (deleteIfExists && Directory.Exists(this.FullDirectoryPath)) + { + Directory.Delete(this.FullDirectoryPath, true); + } + + Directory.CreateDirectory(this.FullDirectoryPath); + this.cleanup = cleanup; + } + + /// + /// Gets the directory name. + /// + public string DirectoryName { get; } + + /// + /// Gets the full directory name. + /// + public string FullDirectoryPath { get; } + + /// + /// IDisposable.Dispose . + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Copies all contents of a directory into this directory. + /// + /// Source directory. + public void CopyDirectory(string sourceDir) + { + this.CopyDirectory(sourceDir, this.FullDirectoryPath); + } + + /// + /// Protected disposed. + /// + /// Disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (this.cleanup && Directory.Exists(this.FullDirectoryPath)) + { + Directory.Delete(this.FullDirectoryPath, true); + } + + this.disposed = true; + } + } + + private void CopyDirectory(string sourceDir, string destinationDir) + { + var dir = new DirectoryInfo(sourceDir); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException(dir.FullName); + } + + Directory.CreateDirectory(destinationDir); + + foreach (FileInfo file in dir.GetFiles()) + { + file.CopyTo(Path.Combine(destinationDir, file.Name)); + } + + foreach (DirectoryInfo subDir in dir.GetDirectories()) + { + this.CopyDirectory(subDir.FullName, Path.Combine(destinationDir, subDir.Name)); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempFile.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempFile.cs index a3673100a9..bcb797a703 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempFile.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/TempFile.cs @@ -1,112 +1,112 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.IO; - - /// - /// Creates a temporary file in the user's temporary directory. - /// - internal class TempFile : IDisposable - { - private readonly bool cleanup; - - private bool disposed = false; - - /// - /// Initializes a new instance of the class. - /// - /// Optional file name. If null, creates a random file name. - /// Delete file if already exists. Default true. - /// Optional content. If not null or empty, creates file and writes to it. - /// Deletes file at disposing time. Default true. - public TempFile( - string? fileName = null, - bool deleteIfExists = true, - string? content = null, - bool cleanup = true) - { - if (fileName is null) - { - this.FileName = Path.GetRandomFileName(); - this.FullPath = Path.Combine(Path.GetTempPath(), this.FileName); - } - else - { - this.FileName = fileName; - var randomDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Directory.CreateDirectory(randomDir); - this.FullPath = Path.Combine(randomDir, this.FileName); - } - - if (deleteIfExists && File.Exists(this.FullPath)) - { - File.Delete(this.FullPath); - } - - if (!string.IsNullOrWhiteSpace(content)) - { - this.CreateFile(content); - } - - this.cleanup = cleanup; - } - - /// - /// Gets the file name. - /// - public string FileName { get; } - - /// - /// Gets the full path. - /// - public string FullPath { get; } - - /// - /// IDisposable.Dispose. - /// - public void Dispose() - { - this.Dispose(true); - GC.SuppressFinalize(this); - } - - /// - /// Creates the file. - /// - /// Content. - public void CreateFile(string? content = null) - { - if (content is null) - { - using var fs = File.Create(this.FullPath); - } - else - { - File.WriteAllText(this.FullPath, content); - } - } - - /// - /// Protected disposed. - /// - /// Disposing. - protected virtual void Dispose(bool disposing) - { - if (!this.disposed) - { - if (this.cleanup && File.Exists(this.FullPath)) - { - File.Delete(this.FullPath); - } - - this.disposed = true; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.IO; + + /// + /// Creates a temporary file in the user's temporary directory. + /// + internal class TempFile : IDisposable + { + private readonly bool cleanup; + + private bool disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// Optional file name. If null, creates a random file name. + /// Delete file if already exists. Default true. + /// Optional content. If not null or empty, creates file and writes to it. + /// Deletes file at disposing time. Default true. + public TempFile( + string? fileName = null, + bool deleteIfExists = true, + string? content = null, + bool cleanup = true) + { + if (fileName is null) + { + this.FileName = Path.GetRandomFileName(); + this.FullPath = Path.Combine(Path.GetTempPath(), this.FileName); + } + else + { + this.FileName = fileName; + var randomDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(randomDir); + this.FullPath = Path.Combine(randomDir, this.FileName); + } + + if (deleteIfExists && File.Exists(this.FullPath)) + { + File.Delete(this.FullPath); + } + + if (!string.IsNullOrWhiteSpace(content)) + { + this.CreateFile(content); + } + + this.cleanup = cleanup; + } + + /// + /// Gets the file name. + /// + public string FileName { get; } + + /// + /// Gets the full path. + /// + public string FullPath { get; } + + /// + /// IDisposable.Dispose. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Creates the file. + /// + /// Content. + public void CreateFile(string? content = null) + { + if (content is null) + { + using var fs = File.Create(this.FullPath); + } + else + { + File.WriteAllText(this.FullPath, content); + } + } + + /// + /// Protected disposed. + /// + /// Disposing. + protected virtual void Dispose(bool disposing) + { + if (!this.disposed) + { + if (this.cleanup && File.Exists(this.FullPath)) + { + File.Delete(this.FullPath); + } + + this.disposed = true; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/UninstallOperationWithProgress.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/UninstallOperationWithProgress.cs index 91bd82495d..ffd04b641d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/UninstallOperationWithProgress.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/UninstallOperationWithProgress.cs @@ -1,42 +1,42 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System.Management.Automation; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - using Windows.Foundation; - - /// - /// Handler progress for uninstall. - /// - internal class UninstallOperationWithProgress : OperationWithProgressBase - { - /// - /// Initializes a new instance of the class. - /// - /// A instance. - /// Activity. - public UninstallOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) - : base(pwshCmdlet, activity) - { - } - - /// - public override void Progress(IAsyncOperationWithProgress operation, UninstallProgress progress) - { - ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) - { - RecordType = ProgressRecordType.Processing, - }; - record.StatusDescription = Resources.Uninstalling; - record.PercentComplete = (int)(progress.UninstallationProgress * 100); - this.PwshCmdlet.Write(StreamType.Progress, record); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Management.Automation; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + using Windows.Foundation; + + /// + /// Handler progress for uninstall. + /// + internal class UninstallOperationWithProgress : OperationWithProgressBase + { + /// + /// Initializes a new instance of the class. + /// + /// A instance. + /// Activity. + public UninstallOperationWithProgress(PowerShellCmdlet pwshCmdlet, string activity) + : base(pwshCmdlet, activity) + { + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, UninstallProgress progress) + { + ProgressRecord record = new (this.ActivityId, this.Activity, progress.State.ToString()) + { + RecordType = ProgressRecordType.Processing, + }; + record.StatusDescription = Resources.Uninstalling; + record.PercentComplete = (int)(progress.UninstallationProgress * 100); + this.PwshCmdlet.Write(StreamType.Progress, record); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandBuilder.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandBuilder.cs index eb271431ec..263044dd25 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandBuilder.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandBuilder.cs @@ -1,148 +1,148 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System.Collections.Generic; - using System.Text; - - /// - /// Represents a builder for WinGet CLI commands. - /// - public class WinGetCLICommandBuilder - { - private readonly List commands; - private readonly List parameters; - - /// - /// Initializes a new instance of the class. - /// - /// The commands to initialize the builder with. - public WinGetCLICommandBuilder(params string[] commands) - { - this.commands = new (commands); - this.parameters = new (); - } - - /// - /// Gets the main command (e.g. [empty], settings, source). - /// - public string Command => string.Join(" ", this.commands); - - /// - /// Gets the constructed parameters string. - /// - public string Parameters => string.Join(" ", this.parameters); - - /// - /// Appends a switch to the command. - /// - /// The name of the switch to append. - /// The current instance of . - public WinGetCLICommandBuilder AppendSwitch(string switchName) - { - this.parameters.Add($"--{switchName}"); - return this; - } - - /// - /// Appends a sub-command to the command. - /// - /// The sub-command to append. - /// The current instance of . - public WinGetCLICommandBuilder AppendSubCommand(string subCommand) - { - this.commands.Add(subCommand); - return this; - } - - /// - /// Appends an option with its value to the command. - /// - /// The name of the option to append. - /// The value of the option to append. - /// The current instance of . - public WinGetCLICommandBuilder AppendOption(string option, string? value) - { - if (value == null) - { - return this; - } - - this.parameters.Add($"--{option} {this.Escape(value)}"); - return this; - } - - /// - /// Converts the command builder to its string representation. - /// - /// The string representation of the command. - public override string ToString() - { - var parametersString = this.Parameters; - var commandString = this.Command; - if (string.IsNullOrEmpty(commandString)) - { - return parametersString; - } - - if (string.IsNullOrEmpty(parametersString)) - { - return commandString; - } - - return $"{commandString} {parametersString}"; - } - - /// - /// Escapes a command-line argument according to Windows command-line parsing rules. - /// References: - /// - https://devblogs.microsoft.com/oldnewthing/20100917-00/?p=12833 - /// - https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments. - /// - /// The argument to escape. - /// The escaped argument. - private string Escape(string arg) - { - if (string.IsNullOrEmpty(arg)) - { - return "\"\""; - } - - var sb = new StringBuilder(arg.Length + 2); - sb.Append('"'); - - int bs = 0; - foreach (char c in arg) - { - if (c == '\\') - { - bs++; - } - else if (c == '"') - { - sb.Append('\\', (bs * 2) + 1); - sb.Append('"'); - bs = 0; - } - else - { - sb.Append('\\', bs); - sb.Append(c); - bs = 0; - } - } - - if (bs > 0) - { - sb.Append('\\', bs * 2); - } - - sb.Append('"'); - return sb.ToString(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System.Collections.Generic; + using System.Text; + + /// + /// Represents a builder for WinGet CLI commands. + /// + public class WinGetCLICommandBuilder + { + private readonly List commands; + private readonly List parameters; + + /// + /// Initializes a new instance of the class. + /// + /// The commands to initialize the builder with. + public WinGetCLICommandBuilder(params string[] commands) + { + this.commands = new (commands); + this.parameters = new (); + } + + /// + /// Gets the main command (e.g. [empty], settings, source). + /// + public string Command => string.Join(" ", this.commands); + + /// + /// Gets the constructed parameters string. + /// + public string Parameters => string.Join(" ", this.parameters); + + /// + /// Appends a switch to the command. + /// + /// The name of the switch to append. + /// The current instance of . + public WinGetCLICommandBuilder AppendSwitch(string switchName) + { + this.parameters.Add($"--{switchName}"); + return this; + } + + /// + /// Appends a sub-command to the command. + /// + /// The sub-command to append. + /// The current instance of . + public WinGetCLICommandBuilder AppendSubCommand(string subCommand) + { + this.commands.Add(subCommand); + return this; + } + + /// + /// Appends an option with its value to the command. + /// + /// The name of the option to append. + /// The value of the option to append. + /// The current instance of . + public WinGetCLICommandBuilder AppendOption(string option, string? value) + { + if (value == null) + { + return this; + } + + this.parameters.Add($"--{option} {this.Escape(value)}"); + return this; + } + + /// + /// Converts the command builder to its string representation. + /// + /// The string representation of the command. + public override string ToString() + { + var parametersString = this.Parameters; + var commandString = this.Command; + if (string.IsNullOrEmpty(commandString)) + { + return parametersString; + } + + if (string.IsNullOrEmpty(parametersString)) + { + return commandString; + } + + return $"{commandString} {parametersString}"; + } + + /// + /// Escapes a command-line argument according to Windows command-line parsing rules. + /// References: + /// - https://devblogs.microsoft.com/oldnewthing/20100917-00/?p=12833 + /// - https://learn.microsoft.com/en-us/cpp/c-language/parsing-c-command-line-arguments. + /// + /// The argument to escape. + /// The escaped argument. + private string Escape(string arg) + { + if (string.IsNullOrEmpty(arg)) + { + return "\"\""; + } + + var sb = new StringBuilder(arg.Length + 2); + sb.Append('"'); + + int bs = 0; + foreach (char c in arg) + { + if (c == '\\') + { + bs++; + } + else if (c == '"') + { + sb.Append('\\', (bs * 2) + 1); + sb.Append('"'); + bs = 0; + } + else + { + sb.Append('\\', bs); + sb.Append(c); + bs = 0; + } + } + + if (bs > 0) + { + sb.Append('\\', bs * 2); + } + + sb.Append('"'); + return sb.ToString(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandResult.cs index 3b65d4753d..0daa91afb6 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetCLICommandResult.cs @@ -1,75 +1,75 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using Microsoft.WinGet.Client.Engine.Exceptions; - - /// - /// Winget cli command result. - /// - internal class WinGetCLICommandResult - { - /// - /// Initializes a new instance of the class. - /// - /// Command. - /// Parameters. - /// Exit code. - /// Standard output. - /// Standard error. - public WinGetCLICommandResult(string command, string? parameters, int exitCode, string stdOut, string stdErr) - { - this.Command = command; - this.Parameters = parameters; - this.ExitCode = exitCode; - this.StdOut = stdOut; - this.StdErr = stdErr; - } - - /// - /// Gets the command. - /// - public string Command { get; private set; } - - /// - /// Gets the parameters. - /// - public string? Parameters { get; private set; } - - /// - /// Gets the exit code. - /// - public int ExitCode { get; private set; } - - /// - /// Gets the standard output. - /// - public string StdOut { get; private set; } - - /// - /// Gets the standard error. - /// - public string StdErr { get; private set; } - - /// - /// Verifies exit code. - /// - /// Optional exit code. - public void VerifyExitCode(int exitCode = 0) - { - if (this.ExitCode != exitCode) - { - throw new WinGetCLIException( - this.Command, - this.Parameters, - this.ExitCode, - this.StdOut, - this.StdErr); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using Microsoft.WinGet.Client.Engine.Exceptions; + + /// + /// Winget cli command result. + /// + internal class WinGetCLICommandResult + { + /// + /// Initializes a new instance of the class. + /// + /// Command. + /// Parameters. + /// Exit code. + /// Standard output. + /// Standard error. + public WinGetCLICommandResult(string command, string? parameters, int exitCode, string stdOut, string stdErr) + { + this.Command = command; + this.Parameters = parameters; + this.ExitCode = exitCode; + this.StdOut = stdOut; + this.StdErr = stdErr; + } + + /// + /// Gets the command. + /// + public string Command { get; private set; } + + /// + /// Gets the parameters. + /// + public string? Parameters { get; private set; } + + /// + /// Gets the exit code. + /// + public int ExitCode { get; private set; } + + /// + /// Gets the standard output. + /// + public string StdOut { get; private set; } + + /// + /// Gets the standard error. + /// + public string StdErr { get; private set; } + + /// + /// Verifies exit code. + /// + /// Optional exit code. + public void VerifyExitCode(int exitCode = 0) + { + if (this.ExitCode != exitCode) + { + throw new WinGetCLIException( + this.Command, + this.Parameters, + this.ExitCode, + this.StdOut, + this.StdErr); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs index 0c93914ef1..90290f453b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinGetVersion.cs @@ -1,157 +1,157 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using Microsoft.WinGet.Common.Command; - - /// - /// WinGetVersion. Parse the string version returned by winget --version to allow comparisons. - /// - internal class WinGetVersion - { - /// - /// Initializes a new instance of the class. - /// - /// String Version. - public WinGetVersion(string version) - { - if (string.IsNullOrWhiteSpace(version)) - { - throw new ArgumentNullException(nameof(version)); - } - - string toParseVersion = version; - - // WinGet version starts with v - if (toParseVersion[0] == 'v') - { - this.TagVersion = version; - toParseVersion = toParseVersion.Substring(1); - - // Handle v-0.2*, v-0.3*, v-0.4* - if (toParseVersion.Length > 0 && toParseVersion[0] == '-') - { - toParseVersion = toParseVersion.Substring(1); - } - } - else - { - // WinGet version always start with v. - this.TagVersion = 'v' + version; - } - - // WinGet version might end with -preview - if (toParseVersion.EndsWith("-preview")) - { - this.IsPrerelease = true; - toParseVersion = toParseVersion.Substring(0, toParseVersion.IndexOf('-')); - } - - this.Version = Version.Parse(toParseVersion); - } - - /// - /// Gets the version as it appears as a tag. - /// - public string TagVersion { get; } - - /// - /// Gets the version. - /// - public System.Version Version { get; } - - /// - /// Gets a value indicating whether is this version is a prerelease. - /// - public bool IsPrerelease { get; } - - /// - /// Runs the winget version command. - /// - /// PowerShell cmdlet. - /// Use full path or not. - /// The command result. - public static WinGetCLICommandResult RunWinGetVersionFromCLI(PowerShellCmdlet pwshCmdlet, bool fullPath = true) - { - var wingetCliWrapper = new WingetCLIWrapper(fullPath); - return wingetCliWrapper.RunCommand(pwshCmdlet, new WinGetCLICommandBuilder().AppendSwitch("--version")); - } - - /// - /// Gets the version of the installed winget. - /// - /// PowerShell cmdlet. - /// A command result from running previously. - /// The WinGetVersion. - public static WinGetVersion InstalledWinGetVersion(PowerShellCmdlet pwshCmdlet, WinGetCLICommandResult? versionResult = null) - { - if (versionResult == null || versionResult.ExitCode != 0) - { - // Try getting the version through COM if it is available (user might have an older build installed) - string? comVersion = PackageManagerWrapper.Instance.GetVersion(); - if (comVersion != null) - { - return new WinGetVersion(comVersion); - } - - versionResult = RunWinGetVersionFromCLI(pwshCmdlet); - } - - return new WinGetVersion(versionResult.StdOut.Replace(Environment.NewLine, string.Empty)); - } - - /// - /// Checks if the version string has a wildcard. - /// - /// The version string. - /// True if it has a wildcard, false otherwise. - public static bool VersionHasWildcard(string version) - { - return version.Contains("*"); - } - - /// - /// Version.CompareTo taking into account prerelease. - /// From semver: Pre-release versions have a lower precedence than the associated normal version. - /// - /// Other winget version. - /// - /// A signed integer that indicates the relative values of the two objects. Less than 0 - /// means this version is before other version. 0 means they are equal. Greater than 0 - /// means this version is greater. - /// - public int CompareTo(WinGetVersion otherVersion) - { - if (this.IsPrerelease && !otherVersion.IsPrerelease) - { - return -1; - } - - if (!this.IsPrerelease && otherVersion.IsPrerelease) - { - return 1; - } - - return this.Version.CompareTo(otherVersion.Version); - } - - /// - /// Deployment doesn't care about semver or prerelease builds. - /// - /// Other version. - /// - /// A signed integer that indicates the relative values of the two objects. Less than 0 - /// means this version is before other version. 0 means they are equal. Greater than 0 - /// means this version is greater. - /// - public int CompareAsDeployment(WinGetVersion otherVersion) - { - return this.Version.CompareTo(otherVersion.Version); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using Microsoft.WinGet.Common.Command; + + /// + /// WinGetVersion. Parse the string version returned by winget --version to allow comparisons. + /// + internal class WinGetVersion + { + /// + /// Initializes a new instance of the class. + /// + /// String Version. + public WinGetVersion(string version) + { + if (string.IsNullOrWhiteSpace(version)) + { + throw new ArgumentNullException(nameof(version)); + } + + string toParseVersion = version; + + // WinGet version starts with v + if (toParseVersion[0] == 'v') + { + this.TagVersion = version; + toParseVersion = toParseVersion.Substring(1); + + // Handle v-0.2*, v-0.3*, v-0.4* + if (toParseVersion.Length > 0 && toParseVersion[0] == '-') + { + toParseVersion = toParseVersion.Substring(1); + } + } + else + { + // WinGet version always start with v. + this.TagVersion = 'v' + version; + } + + // WinGet version might end with -preview + if (toParseVersion.EndsWith("-preview")) + { + this.IsPrerelease = true; + toParseVersion = toParseVersion.Substring(0, toParseVersion.IndexOf('-')); + } + + this.Version = Version.Parse(toParseVersion); + } + + /// + /// Gets the version as it appears as a tag. + /// + public string TagVersion { get; } + + /// + /// Gets the version. + /// + public System.Version Version { get; } + + /// + /// Gets a value indicating whether is this version is a prerelease. + /// + public bool IsPrerelease { get; } + + /// + /// Runs the winget version command. + /// + /// PowerShell cmdlet. + /// Use full path or not. + /// The command result. + public static WinGetCLICommandResult RunWinGetVersionFromCLI(PowerShellCmdlet pwshCmdlet, bool fullPath = true) + { + var wingetCliWrapper = new WingetCLIWrapper(fullPath); + return wingetCliWrapper.RunCommand(pwshCmdlet, new WinGetCLICommandBuilder().AppendSwitch("--version")); + } + + /// + /// Gets the version of the installed winget. + /// + /// PowerShell cmdlet. + /// A command result from running previously. + /// The WinGetVersion. + public static WinGetVersion InstalledWinGetVersion(PowerShellCmdlet pwshCmdlet, WinGetCLICommandResult? versionResult = null) + { + if (versionResult == null || versionResult.ExitCode != 0) + { + // Try getting the version through COM if it is available (user might have an older build installed) + string? comVersion = PackageManagerWrapper.Instance.GetVersion(); + if (comVersion != null) + { + return new WinGetVersion(comVersion); + } + + versionResult = RunWinGetVersionFromCLI(pwshCmdlet); + } + + return new WinGetVersion(versionResult.StdOut.Replace(Environment.NewLine, string.Empty)); + } + + /// + /// Checks if the version string has a wildcard. + /// + /// The version string. + /// True if it has a wildcard, false otherwise. + public static bool VersionHasWildcard(string version) + { + return version.Contains("*"); + } + + /// + /// Version.CompareTo taking into account prerelease. + /// From semver: Pre-release versions have a lower precedence than the associated normal version. + /// + /// Other winget version. + /// + /// A signed integer that indicates the relative values of the two objects. Less than 0 + /// means this version is before other version. 0 means they are equal. Greater than 0 + /// means this version is greater. + /// + public int CompareTo(WinGetVersion otherVersion) + { + if (this.IsPrerelease && !otherVersion.IsPrerelease) + { + return -1; + } + + if (!this.IsPrerelease && otherVersion.IsPrerelease) + { + return 1; + } + + return this.Version.CompareTo(otherVersion.Version); + } + + /// + /// Deployment doesn't care about semver or prerelease builds. + /// + /// Other version. + /// + /// A signed integer that indicates the relative values of the two objects. Less than 0 + /// means this version is before other version. 0 means they are equal. Greater than 0 + /// means this version is greater. + /// + public int CompareAsDeployment(WinGetVersion otherVersion) + { + return this.Version.CompareTo(otherVersion.Version); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs index 1ea255813a..87516fb75b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WinRTHelpers.cs @@ -1,94 +1,94 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.IO; - using System.Runtime.InteropServices; - - /// - /// Helper class for winrtact.dll calls. - /// - internal static class WinRTHelpers - { -#if POWERSHELL_WINDOWS - private static readonly string ArchDependencyPath; - - static WinRTHelpers() - { - ArchDependencyPath = Path.Combine( - Path.GetDirectoryName( - Path.GetDirectoryName(typeof(WinRTHelpers).Assembly.Location)), - "SharedDependencies", - RuntimeInformation.ProcessArchitecture.ToString().ToLower()); - } -#endif - - /// - /// Calls winrtact_Initialize. - /// - public static void Initialize() - { -#if POWERSHELL_WINDOWS - SetDllDirectoryW(ArchDependencyPath); - - try - { -#endif - InitializeUndockedRegFreeWinRT(); - -#if POWERSHELL_WINDOWS - } - finally - { - SetDllDirectoryW(null); - } -#endif - } - - /// - /// Calls WinGetServerManualActivation_CreateInstance. - /// - /// Class id. - /// IID. - /// Flags. - /// Out object. - /// Result of WinGetServerManualActivation_CreateInstance. - public static int ManualActivation(Guid clsid, Guid iid, uint flags, out object instance) - { -#if POWERSHELL_WINDOWS - SetDllDirectoryW(ArchDependencyPath); - - try - { -#endif - return WinGetServerManualActivation_CreateInstance(clsid, iid, flags, out instance); - -#if POWERSHELL_WINDOWS - } - finally - { - SetDllDirectoryW(null); - } -#endif - } - - [DllImport("winrtact.dll", EntryPoint = "winrtact_Initialize", ExactSpelling = true, PreserveSig = true)] - private static extern void InitializeUndockedRegFreeWinRT(); - - [DllImport("winrtact.dll", EntryPoint = "WinGetServerManualActivation_CreateInstance", ExactSpelling = true, PreserveSig = true)] - private static extern int WinGetServerManualActivation_CreateInstance( - [In, MarshalAs(UnmanagedType.LPStruct)] Guid clsid, - [In, MarshalAs(UnmanagedType.LPStruct)] Guid iid, - uint flags, - [Out, MarshalAs(UnmanagedType.IUnknown)] out object instance); - - [DllImport("kernel32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - private static extern bool SetDllDirectoryW([MarshalAs(UnmanagedType.LPWStr)] string? directory); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.IO; + using System.Runtime.InteropServices; + + /// + /// Helper class for winrtact.dll calls. + /// + internal static class WinRTHelpers + { +#if POWERSHELL_WINDOWS + private static readonly string ArchDependencyPath; + + static WinRTHelpers() + { + ArchDependencyPath = Path.Combine( + Path.GetDirectoryName( + Path.GetDirectoryName(typeof(WinRTHelpers).Assembly.Location)), + "SharedDependencies", + RuntimeInformation.ProcessArchitecture.ToString().ToLower()); + } +#endif + + /// + /// Calls winrtact_Initialize. + /// + public static void Initialize() + { +#if POWERSHELL_WINDOWS + SetDllDirectoryW(ArchDependencyPath); + + try + { +#endif + InitializeUndockedRegFreeWinRT(); + +#if POWERSHELL_WINDOWS + } + finally + { + SetDllDirectoryW(null); + } +#endif + } + + /// + /// Calls WinGetServerManualActivation_CreateInstance. + /// + /// Class id. + /// IID. + /// Flags. + /// Out object. + /// Result of WinGetServerManualActivation_CreateInstance. + public static int ManualActivation(Guid clsid, Guid iid, uint flags, out object instance) + { +#if POWERSHELL_WINDOWS + SetDllDirectoryW(ArchDependencyPath); + + try + { +#endif + return WinGetServerManualActivation_CreateInstance(clsid, iid, flags, out instance); + +#if POWERSHELL_WINDOWS + } + finally + { + SetDllDirectoryW(null); + } +#endif + } + + [DllImport("winrtact.dll", EntryPoint = "winrtact_Initialize", ExactSpelling = true, PreserveSig = true)] + private static extern void InitializeUndockedRegFreeWinRT(); + + [DllImport("winrtact.dll", EntryPoint = "WinGetServerManualActivation_CreateInstance", ExactSpelling = true, PreserveSig = true)] + private static extern int WinGetServerManualActivation_CreateInstance( + [In, MarshalAs(UnmanagedType.LPStruct)] Guid clsid, + [In, MarshalAs(UnmanagedType.LPStruct)] Guid iid, + uint flags, + [Out, MarshalAs(UnmanagedType.IUnknown)] out object instance); + + [DllImport("kernel32.dll", SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetDllDirectoryW([MarshalAs(UnmanagedType.LPWStr)] string? directory); + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs index 7c2606da45..d9c76a810c 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Helpers/WingetCLIWrapper.cs @@ -1,103 +1,103 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.Helpers -{ - using System; - using System.Diagnostics; - using System.IO; - using Microsoft.WinGet.Client.Engine.Common; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Common.Command; - - /// - /// Calls winget directly. - /// - internal class WingetCLIWrapper - { - /// - /// The file name to use in start info. - /// - private readonly string wingetPath; - - /// - /// Initializes a new instance of the class. - /// When app execution alias is disabled the path of the exe is - /// in the package family name directory in the local app data windows app directory. If its enabled then there's - /// link in the windows app data directory. To avoid checking if its enabled or not, just look in the package - /// family name directory. - /// For test, point to the wingetdev executable. - /// - /// Use full path or not. - public WingetCLIWrapper(bool fullPath = true) - { - if (Utilities.ExecutingAsSystem) - { - throw new NotSupportedException(); - } - - if (fullPath) - { - this.wingetPath = WinGetFullPath; - } - else - { - this.wingetPath = Constants.WinGetExe; - } - } - - /// - /// Gets the full path of winget executable. - /// - public static string WinGetFullPath - { - get - { - return Path.Combine( - Utilities.LocalDataWindowsAppPath, - Constants.WingetPackageFamilyName, - Constants.WinGetExe); - } - } - - /// - /// Runs winget command with parameters. - /// - /// PowerShell cmdlet. - /// The command builder. - /// Time out. - /// WinGetCommandResult. - internal WinGetCLICommandResult RunCommand(PowerShellCmdlet pwshCmdlet, WinGetCLICommandBuilder builder, int timeOut = 60000) - { - var args = builder.ToString(); - pwshCmdlet.Write(StreamType.Verbose, $"Running {this.wingetPath} with {args}"); - - Process p = new () - { - StartInfo = new (this.wingetPath, args) - { - UseShellExecute = false, - RedirectStandardOutput = true, - RedirectStandardError = true, - }, - }; - - p.Start(); - - if (p.WaitForExit(timeOut)) - { - return new WinGetCLICommandResult( - builder.Command, - builder.Parameters, - p.ExitCode, - p.StandardOutput.ReadToEnd(), - p.StandardError.ReadToEnd()); - } - - throw new WinGetCLITimeoutException(builder.Command, builder.Parameters); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.Helpers +{ + using System; + using System.Diagnostics; + using System.IO; + using Microsoft.WinGet.Client.Engine.Common; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Common.Command; + + /// + /// Calls winget directly. + /// + internal class WingetCLIWrapper + { + /// + /// The file name to use in start info. + /// + private readonly string wingetPath; + + /// + /// Initializes a new instance of the class. + /// When app execution alias is disabled the path of the exe is + /// in the package family name directory in the local app data windows app directory. If its enabled then there's + /// link in the windows app data directory. To avoid checking if its enabled or not, just look in the package + /// family name directory. + /// For test, point to the wingetdev executable. + /// + /// Use full path or not. + public WingetCLIWrapper(bool fullPath = true) + { + if (Utilities.ExecutingAsSystem) + { + throw new NotSupportedException(); + } + + if (fullPath) + { + this.wingetPath = WinGetFullPath; + } + else + { + this.wingetPath = Constants.WinGetExe; + } + } + + /// + /// Gets the full path of winget executable. + /// + public static string WinGetFullPath + { + get + { + return Path.Combine( + Utilities.LocalDataWindowsAppPath, + Constants.WingetPackageFamilyName, + Constants.WinGetExe); + } + } + + /// + /// Runs winget command with parameters. + /// + /// PowerShell cmdlet. + /// The command builder. + /// Time out. + /// WinGetCommandResult. + internal WinGetCLICommandResult RunCommand(PowerShellCmdlet pwshCmdlet, WinGetCLICommandBuilder builder, int timeOut = 60000) + { + var args = builder.ToString(); + pwshCmdlet.Write(StreamType.Verbose, $"Running {this.wingetPath} with {args}"); + + Process p = new () + { + StartInfo = new (this.wingetPath, args) + { + UseShellExecute = false, + RedirectStandardOutput = true, + RedirectStandardError = true, + }, + }; + + p.Start(); + + if (p.WaitForExit(timeOut)) + { + return new WinGetCLICommandResult( + builder.Command, + builder.Parameters, + p.ExitCode, + p.StandardOutput.ReadToEnd(), + p.StandardError.ReadToEnd()); + } + + throw new WinGetCLITimeoutException(builder.Command, builder.Parameters); + } + } +} 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 c29901fb09..a8a800abb3 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 @@ -1,143 +1,143 @@ - - - - - - 10.0.26100.0 - net8.0-windows$(TargetWindowsVersion) - false - net48 - Debug;Release;ReleaseStatic - false - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\ - $(BuildOutputDirectory)$(MSBuildProjectName) - $(CoreFramework);$(DesktopFramework) - $(OutputPath)\Microsoft.WinGet.Client.Engine.xml - 10.0.18362.0 - enable - - - - - - - - None - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - Content - Always - - - Content - Always - - - Content - Always - - - Content - Always - - - - - - - - 1591 - - - - true - - - - true - - - - win - - - - 10 - $(DefineConstants);POWERSHELL_WINDOWS - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Microsoft.WinGet.Resources - - - - - - - - Microsoft.Management.Deployment; - Windows.Data.Text.TextSegmen; - Windows.Devices.Geolocation; - Windows.Foundation; - Windows.Globalization.DayOfWee; - Windows.Networking.Connectivity; - Windows.Networking.DomainNameTyp; - Windows.Networking.EndpointPai; - Windows.Networking.IEndpointPai; - Windows.Networking.HostNam; - Windows.Networking.IHostNam; - Windows.Security.Cryptography.Certificates; - Windows.Storage; - Windows.Storage.Provider.FileUpdateStatu; - Windows.System.ProcessorArchitectur; - Windows.System.Use; - Windows.System.IUse; - - - Windows.Foundation.PropertyType; - Windows.Storage.Provider; - - $(TargetWindowsVersion) - - 10.0.17763.0 - - - + + + + + + 10.0.26100.0 + net8.0-windows$(TargetWindowsVersion) + false + net48 + Debug;Release;ReleaseStatic + false + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(BuildOutputDirectory)$(MSBuildProjectName) + $(CoreFramework);$(DesktopFramework) + $(OutputPath)\Microsoft.WinGet.Client.Engine.xml + 10.0.18362.0 + enable + + + + + + + + None + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + Content + Always + + + Content + Always + + + Content + Always + + + Content + Always + + + + + + + + 1591 + + + + true + + + + true + + + + win + + + + 10 + $(DefineConstants);POWERSHELL_WINDOWS + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Microsoft.WinGet.Resources + + + + + + + + Microsoft.Management.Deployment; + Windows.Data.Text.TextSegmen; + Windows.Devices.Geolocation; + Windows.Foundation; + Windows.Globalization.DayOfWee; + Windows.Networking.Connectivity; + Windows.Networking.DomainNameTyp; + Windows.Networking.EndpointPai; + Windows.Networking.IEndpointPai; + Windows.Networking.HostNam; + Windows.Networking.IHostNam; + Windows.Security.Cryptography.Certificates; + Windows.Storage; + Windows.Storage.Provider.FileUpdateStatu; + Windows.System.ProcessorArchitectur; + Windows.System.Use; + Windows.System.IUse; + + + Windows.Foundation.PropertyType; + Windows.Storage.Provider; + + $(TargetWindowsVersion) + + 10.0.17763.0 + + + diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs index cad651f27c..1e4cf5c47f 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCatalogPackage.cs @@ -1,128 +1,128 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System.Linq; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Exceptions; - using Microsoft.WinGet.Client.Engine.Extensions; - - /// - /// CatalogPackage wrapper object for displaying to PowerShell. - /// - public abstract class PSCatalogPackage - { - /// - /// Initializes a new instance of the class. - /// - /// CatalogPackage COM object. - internal PSCatalogPackage(Management.Deployment.CatalogPackage catalogPackage) - { - this.CatalogPackageCOM = catalogPackage; - } - - /// - /// Gets the name of the catalog package. - /// - public string Name - { - get - { - return this.CatalogPackageCOM.Name; - } - } - - /// - /// Gets the id of the catalog package. - /// - public string Id - { - get - { - return this.CatalogPackageCOM.Id; - } - } - - /// - /// Gets a value indicating whether an update is available. - /// - public bool IsUpdateAvailable - { - get - { - return this.CatalogPackageCOM.IsUpdateAvailable; - } - } - - /// - /// Gets the source name of the catalog package. - /// - public string? Source - { - get - { - return this.CatalogPackageCOM.GetSourceName(); - } - } - - /// - /// Gets list of strings representing the available versions. - /// - public string[] AvailableVersions - { - get - { - return this.AvailablePackageVersionIds.Select(i => i.Version).ToArray(); - } - } - - /// - /// Gets the catalog package COM object. - /// - internal CatalogPackage CatalogPackageCOM { get; private set; } - - /// - /// Gets a list of available package version ids for the package. - /// - private PackageVersionId[] AvailablePackageVersionIds - { - get - { - return this.CatalogPackageCOM.AvailableVersions.ToArray(); - } - } - - /// - /// Checks the installed status of the catalog package. - /// - /// CheckInstalledStatus string. - public string CheckInstalledStatus() - { - return this.CatalogPackageCOM.CheckInstalledStatus().Status.ToString(); - } - - /// - /// Gets the PackageVersionInfo PSObject that corresponds with the version string. - /// - /// Version string. - /// PackageVersionInfo PSObject. - /// Throws an exception if no package is found. - public PSPackageVersionInfo GetPackageVersionInfo(string version) - { - // get specific version that matches - PackageVersionId? packageVersionId = this.AvailablePackageVersionIds.FirstOrDefault(x => x.Version == version); - if (packageVersionId != null) - { - return new PSPackageVersionInfo(this.CatalogPackageCOM.GetPackageVersionInfo(packageVersionId)); - } - else - { - throw new NoPackageFoundException(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System.Linq; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Exceptions; + using Microsoft.WinGet.Client.Engine.Extensions; + + /// + /// CatalogPackage wrapper object for displaying to PowerShell. + /// + public abstract class PSCatalogPackage + { + /// + /// Initializes a new instance of the class. + /// + /// CatalogPackage COM object. + internal PSCatalogPackage(Management.Deployment.CatalogPackage catalogPackage) + { + this.CatalogPackageCOM = catalogPackage; + } + + /// + /// Gets the name of the catalog package. + /// + public string Name + { + get + { + return this.CatalogPackageCOM.Name; + } + } + + /// + /// Gets the id of the catalog package. + /// + public string Id + { + get + { + return this.CatalogPackageCOM.Id; + } + } + + /// + /// Gets a value indicating whether an update is available. + /// + public bool IsUpdateAvailable + { + get + { + return this.CatalogPackageCOM.IsUpdateAvailable; + } + } + + /// + /// Gets the source name of the catalog package. + /// + public string? Source + { + get + { + return this.CatalogPackageCOM.GetSourceName(); + } + } + + /// + /// Gets list of strings representing the available versions. + /// + public string[] AvailableVersions + { + get + { + return this.AvailablePackageVersionIds.Select(i => i.Version).ToArray(); + } + } + + /// + /// Gets the catalog package COM object. + /// + internal CatalogPackage CatalogPackageCOM { get; private set; } + + /// + /// Gets a list of available package version ids for the package. + /// + private PackageVersionId[] AvailablePackageVersionIds + { + get + { + return this.CatalogPackageCOM.AvailableVersions.ToArray(); + } + } + + /// + /// Checks the installed status of the catalog package. + /// + /// CheckInstalledStatus string. + public string CheckInstalledStatus() + { + return this.CatalogPackageCOM.CheckInstalledStatus().Status.ToString(); + } + + /// + /// Gets the PackageVersionInfo PSObject that corresponds with the version string. + /// + /// Version string. + /// PackageVersionInfo PSObject. + /// Throws an exception if no package is found. + public PSPackageVersionInfo GetPackageVersionInfo(string version) + { + // get specific version that matches + PackageVersionId? packageVersionId = this.AvailablePackageVersionIds.FirstOrDefault(x => x.Version == version); + if (packageVersionId != null) + { + return new PSPackageVersionInfo(this.CatalogPackageCOM.GetPackageVersionInfo(packageVersionId)); + } + else + { + throw new NoPackageFoundException(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCompareResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCompareResult.cs index 4401889e63..9c3a329031 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCompareResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSCompareResult.cs @@ -1,34 +1,34 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - /// - /// Must match Microsoft.Management.Deployment.CompareResult. - /// - public enum PSCompareResult - { - /// - /// Unknown, - /// - Unknown, - - /// - /// Lesser. - /// - Lesser, - - /// - /// Equal, - /// - Equal, - - /// - /// Greater, - /// - Greater, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + /// + /// Must match Microsoft.Management.Deployment.CompareResult. + /// + public enum PSCompareResult + { + /// + /// Unknown, + /// + Unknown, + + /// + /// Lesser. + /// + Lesser, + + /// + /// Equal, + /// + Equal, + + /// + /// Greater, + /// + Greater, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSDownloadResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSDownloadResult.cs index 1a1eb979ee..0ea6497c62 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSDownloadResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSDownloadResult.cs @@ -1,116 +1,116 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Extensions; - - /// - /// PSDownloadResult. - /// - public sealed class PSDownloadResult - { - private readonly DownloadResult downloadResult; - private readonly CatalogPackage catalogPackage; - - /// - /// Initializes a new instance of the class. - /// - /// The download result COM object. - /// The catalog package COM object. - internal PSDownloadResult(DownloadResult downloadResult, CatalogPackage catalogPackage) - { - this.downloadResult = downloadResult; - this.catalogPackage = catalogPackage; - } - - /// - /// Gets the id of the downloaded package. - /// - public string Id - { - get - { - return this.catalogPackage.Id; - } - } - - /// - /// Gets the name of the downloaded package. - /// - public string Name - { - get - { - return this.catalogPackage.Name; - } - } - - /// - /// Gets the source name of the downloaded package. - /// - public string? Source - { - get - { - return this.catalogPackage.GetSourceName(); - } - } - - /// - /// Gets the correlation data of the downloaded result. - /// - public string CorrelationData - { - get - { - return this.downloadResult.CorrelationData; - } - } - - /// - /// Gets the extended error code exception of the failed download result. - /// - public Exception ExtendedErrorCode - { - get - { - return this.downloadResult.ExtendedErrorCode; - } - } - - /// - /// Gets the status of the download. - /// - public string Status - { - get - { - return this.downloadResult.Status.ToString(); - } - } - - /// - /// If the download succeeded. - /// - /// True if installation succeeded. - public bool Succeeded() - { - return this.downloadResult.Status == DownloadResultStatus.Ok; - } - - /// - /// Message with error information. - /// - /// Error message. - public string ErrorMessage() - { - return $"DownloadStatus '{this.Status}' ExtendedError '{this.ExtendedErrorCode.HResult}'"; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Extensions; + + /// + /// PSDownloadResult. + /// + public sealed class PSDownloadResult + { + private readonly DownloadResult downloadResult; + private readonly CatalogPackage catalogPackage; + + /// + /// Initializes a new instance of the class. + /// + /// The download result COM object. + /// The catalog package COM object. + internal PSDownloadResult(DownloadResult downloadResult, CatalogPackage catalogPackage) + { + this.downloadResult = downloadResult; + this.catalogPackage = catalogPackage; + } + + /// + /// Gets the id of the downloaded package. + /// + public string Id + { + get + { + return this.catalogPackage.Id; + } + } + + /// + /// Gets the name of the downloaded package. + /// + public string Name + { + get + { + return this.catalogPackage.Name; + } + } + + /// + /// Gets the source name of the downloaded package. + /// + public string? Source + { + get + { + return this.catalogPackage.GetSourceName(); + } + } + + /// + /// Gets the correlation data of the downloaded result. + /// + public string CorrelationData + { + get + { + return this.downloadResult.CorrelationData; + } + } + + /// + /// Gets the extended error code exception of the failed download result. + /// + public Exception ExtendedErrorCode + { + get + { + return this.downloadResult.ExtendedErrorCode; + } + } + + /// + /// Gets the status of the download. + /// + public string Status + { + get + { + return this.downloadResult.Status.ToString(); + } + } + + /// + /// If the download succeeded. + /// + /// True if installation succeeded. + public bool Succeeded() + { + return this.downloadResult.Status == DownloadResultStatus.Ok; + } + + /// + /// Message with error information. + /// + /// Error message. + public string ErrorMessage() + { + return $"DownloadStatus '{this.Status}' ExtendedError '{this.ExtendedErrorCode.HResult}'"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSFoundCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSFoundCatalogPackage.cs index a01b9ce9d6..23ea14e8e7 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSFoundCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSFoundCatalogPackage.cs @@ -1,33 +1,33 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using Microsoft.Management.Deployment; - - /// - /// FoundCatalogPackage wrapper object for displaying to PowerShell. - /// - public sealed class PSFoundCatalogPackage : PSCatalogPackage - { - /// - /// Initializes a new instance of the class. - /// - /// The catalog package COM object. - internal PSFoundCatalogPackage(CatalogPackage catalogPackage) - : base(catalogPackage) - { - } - - /// - /// Gets the default install version of the catalog package. - /// - public string? Version - { - get { return this.CatalogPackageCOM.DefaultInstallVersion?.Version; } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using Microsoft.Management.Deployment; + + /// + /// FoundCatalogPackage wrapper object for displaying to PowerShell. + /// + public sealed class PSFoundCatalogPackage : PSCatalogPackage + { + /// + /// Initializes a new instance of the class. + /// + /// The catalog package COM object. + internal PSFoundCatalogPackage(CatalogPackage catalogPackage) + : base(catalogPackage) + { + } + + /// + /// Gets the default install version of the catalog package. + /// + public string? Version + { + get { return this.CatalogPackageCOM.DefaultInstallVersion?.Version; } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstallResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstallResult.cs index 7f263753d9..e8305c60b4 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstallResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstallResult.cs @@ -1,138 +1,138 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Extensions; - - /// - /// PSInstallResult. - /// - public sealed class PSInstallResult - { - private readonly InstallResult installResult; - private readonly CatalogPackage catalogPackage; - - /// - /// Initializes a new instance of the class. - /// - /// The install result COM object. - /// The catalog package COM object. - internal PSInstallResult(InstallResult installResult, CatalogPackage catalogPackage) - { - this.installResult = installResult; - this.catalogPackage = catalogPackage; - } - - /// - /// Gets the id of the installed package. - /// - public string Id - { - get - { - return this.catalogPackage.Id; - } - } - - /// - /// Gets the name of the installed package. - /// - public string Name - { - get - { - return this.catalogPackage.Name; - } - } - - /// - /// Gets the source name of the installed package. - /// - public string? Source - { - get - { - return this.catalogPackage.GetSourceName(); - } - } - - /// - /// Gets the correlation data of the install result. - /// - public string CorrelationData - { - get - { - return this.installResult.CorrelationData; - } - } - - /// - /// Gets the error code of an install. - /// - public uint InstallerErrorCode - { - get - { - return this.installResult.InstallerErrorCode; - } - } - - /// - /// Gets the extended error code exception of the failed install result. - /// - public Exception ExtendedErrorCode - { - get - { - return this.installResult.ExtendedErrorCode; - } - } - - /// - /// Gets a value indicating whether a reboot is required. - /// - public bool RebootRequired - { - get - { - return this.installResult.RebootRequired; - } - } - - /// - /// Gets the status of the install. - /// - public string Status - { - get - { - return this.installResult.Status.ToString(); - } - } - - /// - /// If the installation succeeded. - /// - /// True if installation succeeded. - public bool Succeeded() - { - return this.installResult.Status == InstallResultStatus.Ok; - } - - /// - /// Message with error information. - /// - /// Error message. - public string ErrorMessage() - { - return $"InstallStatus '{this.Status}' InstallerErrorCode '{this.InstallerErrorCode}' ExtendedError '{this.ExtendedErrorCode.HResult}'"; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Extensions; + + /// + /// PSInstallResult. + /// + public sealed class PSInstallResult + { + private readonly InstallResult installResult; + private readonly CatalogPackage catalogPackage; + + /// + /// Initializes a new instance of the class. + /// + /// The install result COM object. + /// The catalog package COM object. + internal PSInstallResult(InstallResult installResult, CatalogPackage catalogPackage) + { + this.installResult = installResult; + this.catalogPackage = catalogPackage; + } + + /// + /// Gets the id of the installed package. + /// + public string Id + { + get + { + return this.catalogPackage.Id; + } + } + + /// + /// Gets the name of the installed package. + /// + public string Name + { + get + { + return this.catalogPackage.Name; + } + } + + /// + /// Gets the source name of the installed package. + /// + public string? Source + { + get + { + return this.catalogPackage.GetSourceName(); + } + } + + /// + /// Gets the correlation data of the install result. + /// + public string CorrelationData + { + get + { + return this.installResult.CorrelationData; + } + } + + /// + /// Gets the error code of an install. + /// + public uint InstallerErrorCode + { + get + { + return this.installResult.InstallerErrorCode; + } + } + + /// + /// Gets the extended error code exception of the failed install result. + /// + public Exception ExtendedErrorCode + { + get + { + return this.installResult.ExtendedErrorCode; + } + } + + /// + /// Gets a value indicating whether a reboot is required. + /// + public bool RebootRequired + { + get + { + return this.installResult.RebootRequired; + } + } + + /// + /// Gets the status of the install. + /// + public string Status + { + get + { + return this.installResult.Status.ToString(); + } + } + + /// + /// If the installation succeeded. + /// + /// True if installation succeeded. + public bool Succeeded() + { + return this.installResult.Status == InstallResultStatus.Ok; + } + + /// + /// Message with error information. + /// + /// Error message. + public string ErrorMessage() + { + return $"InstallStatus '{this.Status}' InstallerErrorCode '{this.InstallerErrorCode}' ExtendedError '{this.ExtendedErrorCode.HResult}'"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs index 3c3dcb7519..0858537e50 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSInstalledCatalogPackage.cs @@ -1,51 +1,51 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System; - using Microsoft.Management.Deployment; - - /// - /// InstalledCatalogPackage wrapper object for displaying to PowerShell. - /// - public sealed class PSInstalledCatalogPackage : PSCatalogPackage - { - /// - /// Initializes a new instance of the class. - /// - /// The catalog package COM object. - internal PSInstalledCatalogPackage(CatalogPackage catalogPackage) - : base(catalogPackage) - { - } - - /// - /// Gets the installed version of the catalog package. - /// - public string InstalledVersion - { - get { return this.CatalogPackageCOM.InstalledVersion.Version; } - } - - /// - /// Compares versions. - /// - /// Version. - /// PSCompareResult. - public PSCompareResult CompareToVersion(string version) - { - return this.CatalogPackageCOM.InstalledVersion.CompareToVersion(version) switch - { - CompareResult.Unknown => PSCompareResult.Unknown, - CompareResult.Lesser => PSCompareResult.Lesser, - CompareResult.Equal => PSCompareResult.Equal, - CompareResult.Greater => PSCompareResult.Greater, - _ => throw new InvalidOperationException(), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + + /// + /// InstalledCatalogPackage wrapper object for displaying to PowerShell. + /// + public sealed class PSInstalledCatalogPackage : PSCatalogPackage + { + /// + /// Initializes a new instance of the class. + /// + /// The catalog package COM object. + internal PSInstalledCatalogPackage(CatalogPackage catalogPackage) + : base(catalogPackage) + { + } + + /// + /// Gets the installed version of the catalog package. + /// + public string InstalledVersion + { + get { return this.CatalogPackageCOM.InstalledVersion.Version; } + } + + /// + /// Compares versions. + /// + /// Version. + /// PSCompareResult. + public PSCompareResult CompareToVersion(string version) + { + return this.CatalogPackageCOM.InstalledVersion.CompareToVersion(version) switch + { + CompareResult.Unknown => PSCompareResult.Unknown, + CompareResult.Lesser => PSCompareResult.Lesser, + CompareResult.Equal => PSCompareResult.Equal, + CompareResult.Greater => PSCompareResult.Greater, + _ => throw new InvalidOperationException(), + }; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackageVersionInfo.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackageVersionInfo.cs index 5a4cc6146e..d9909988ab 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackageVersionInfo.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSPackageVersionInfo.cs @@ -1,104 +1,104 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System.Linq; - using Microsoft.Management.Deployment; - - /// - /// PackageVersionInfo wrapper object for displaying to PowerShell. - /// - public sealed class PSPackageVersionInfo - { - private PackageVersionInfo packageVersionInfo; - - /// - /// Initializes a new instance of the class. - /// - /// PackageVersionInfo COM object. - internal PSPackageVersionInfo(PackageVersionInfo packageVersionInfo) - { - this.packageVersionInfo = packageVersionInfo; - } - - /// - /// Gets the name of the package version info. - /// - public string DisplayName - { - get - { - return this.packageVersionInfo.DisplayName; - } - } - - /// - /// Gets the id of the package version info. - /// - public string Id - { - get - { - return this.packageVersionInfo.Id; - } - } - - /// - /// Gets the publisher of the package version info. - /// - public string Publisher - { - get - { - return this.packageVersionInfo.Publisher; - } - } - - /// - /// Gets the channel of the package version info. - /// - public string Channel - { - get - { - return this.packageVersionInfo.Channel; - } - } - - /// - /// Gets the list of package family names of the package version info. - /// - public string[] PackageFamilyNames - { - get - { - return this.packageVersionInfo.PackageFamilyNames.ToArray(); - } - } - - /// - /// Gets the list of product codes of the package version info. - /// - public string[] ProductCodes - { - get - { - return this.packageVersionInfo.ProductCodes.ToArray(); - } - } - - /// - /// Compares the version string with the package version info and returns the CompareResult. - /// - /// Version string. - /// CompareResult string. - public string CompareToVersion(string version) - { - return this.packageVersionInfo.CompareToVersion(version).ToString(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System.Linq; + using Microsoft.Management.Deployment; + + /// + /// PackageVersionInfo wrapper object for displaying to PowerShell. + /// + public sealed class PSPackageVersionInfo + { + private PackageVersionInfo packageVersionInfo; + + /// + /// Initializes a new instance of the class. + /// + /// PackageVersionInfo COM object. + internal PSPackageVersionInfo(PackageVersionInfo packageVersionInfo) + { + this.packageVersionInfo = packageVersionInfo; + } + + /// + /// Gets the name of the package version info. + /// + public string DisplayName + { + get + { + return this.packageVersionInfo.DisplayName; + } + } + + /// + /// Gets the id of the package version info. + /// + public string Id + { + get + { + return this.packageVersionInfo.Id; + } + } + + /// + /// Gets the publisher of the package version info. + /// + public string Publisher + { + get + { + return this.packageVersionInfo.Publisher; + } + } + + /// + /// Gets the channel of the package version info. + /// + public string Channel + { + get + { + return this.packageVersionInfo.Channel; + } + } + + /// + /// Gets the list of package family names of the package version info. + /// + public string[] PackageFamilyNames + { + get + { + return this.packageVersionInfo.PackageFamilyNames.ToArray(); + } + } + + /// + /// Gets the list of product codes of the package version info. + /// + public string[] ProductCodes + { + get + { + return this.packageVersionInfo.ProductCodes.ToArray(); + } + } + + /// + /// Compares the version string with the package version info and returns the CompareResult. + /// + /// Version string. + /// CompareResult string. + public string CompareToVersion(string version) + { + return this.packageVersionInfo.CompareToVersion(version).ToString(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs index f9fd7ff3d3..fa2057ac06 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSRepairResult.cs @@ -1,138 +1,138 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Extensions; - - /// - /// PSRepairResult. - /// - public sealed class PSRepairResult - { - private readonly RepairResult repairResult; - private readonly CatalogPackage catalogPackage; - - /// - /// Initializes a new instance of the class. - /// - /// The Repair result COM Object. - /// The catalog package COM Object. - internal PSRepairResult(RepairResult repairResult, CatalogPackage catalogPackage) - { - this.repairResult = repairResult; - this.catalogPackage = catalogPackage; - } - - /// - /// Gets the id of the repaired package. - /// - public string Id - { - get - { - return this.catalogPackage.Id; - } - } - - /// - /// Gets the name of the repaired package. - /// - public string Name - { - get - { - return this.catalogPackage.Name; - } - } - - /// - /// Gets the source name of the repaired package. - /// - public string? Source - { - get - { - return this.catalogPackage.GetSourceName(); - } - } - - /// - /// Gets the correlation data of the repair result. - /// - public string CorrelationData - { - get - { - return this.repairResult.CorrelationData; - } - } - - /// - /// Gets the extended error code exception of the failed repair result. - /// - public Exception ExtendedErrorCode - { - get - { - return this.repairResult.ExtendedErrorCode; - } - } - - /// - /// Gets a value indicating whether a reboot is required. - /// - public bool RebootRequired - { - get - { - return this.repairResult.RebootRequired; - } - } - - /// - /// Gets the error code of a repair. - /// - public uint RepairErrorCode - { - get - { - return this.repairResult.RepairerErrorCode; - } - } - - /// - /// Gets the status of the repair result. - /// - public string Status - { - get - { - return this.repairResult.Status.ToString(); - } - } - - /// - /// If the repair succeeded. - /// - /// True if repair succeeded. - public bool Succeeded() - { - return this.repairResult.Status == RepairResultStatus.Ok; - } - - /// - /// Message with error information. - /// - /// Error message. - public string ErrorMessage() - { - return $"RepairStatus : '{this.Status}' RepairErrorCode: '{this.RepairErrorCode}' ExtendedError: '{this.ExtendedErrorCode.HResult}'"; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Extensions; + + /// + /// PSRepairResult. + /// + public sealed class PSRepairResult + { + private readonly RepairResult repairResult; + private readonly CatalogPackage catalogPackage; + + /// + /// Initializes a new instance of the class. + /// + /// The Repair result COM Object. + /// The catalog package COM Object. + internal PSRepairResult(RepairResult repairResult, CatalogPackage catalogPackage) + { + this.repairResult = repairResult; + this.catalogPackage = catalogPackage; + } + + /// + /// Gets the id of the repaired package. + /// + public string Id + { + get + { + return this.catalogPackage.Id; + } + } + + /// + /// Gets the name of the repaired package. + /// + public string Name + { + get + { + return this.catalogPackage.Name; + } + } + + /// + /// Gets the source name of the repaired package. + /// + public string? Source + { + get + { + return this.catalogPackage.GetSourceName(); + } + } + + /// + /// Gets the correlation data of the repair result. + /// + public string CorrelationData + { + get + { + return this.repairResult.CorrelationData; + } + } + + /// + /// Gets the extended error code exception of the failed repair result. + /// + public Exception ExtendedErrorCode + { + get + { + return this.repairResult.ExtendedErrorCode; + } + } + + /// + /// Gets a value indicating whether a reboot is required. + /// + public bool RebootRequired + { + get + { + return this.repairResult.RebootRequired; + } + } + + /// + /// Gets the error code of a repair. + /// + public uint RepairErrorCode + { + get + { + return this.repairResult.RepairerErrorCode; + } + } + + /// + /// Gets the status of the repair result. + /// + public string Status + { + get + { + return this.repairResult.Status.ToString(); + } + } + + /// + /// If the repair succeeded. + /// + /// True if repair succeeded. + public bool Succeeded() + { + return this.repairResult.Status == RepairResultStatus.Ok; + } + + /// + /// Message with error information. + /// + /// Error message. + public string ErrorMessage() + { + return $"RepairStatus : '{this.Status}' RepairErrorCode: '{this.RepairErrorCode}' ExtendedError: '{this.ExtendedErrorCode.HResult}'"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSSourceResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSSourceResult.cs index 75a5f282f8..f2641310e2 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSSourceResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSSourceResult.cs @@ -1,59 +1,59 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - /// - /// SourceResult wrapper object for displaying to PowerShell. - /// - public sealed class PSSourceResult - { - /// - /// Initializes a new instance of the class. - /// - /// The PackageCatalogReference COM object. - internal PSSourceResult(Management.Deployment.PackageCatalogReference catalogReference) - { - var info = catalogReference.Info; - this.Name = info.Name; - this.Argument = info.Argument; - this.Type = info.Type; - this.TrustLevel = info.TrustLevel.ToString(); - this.Explicit = info.Explicit; - this.Priority = info.Priority; - } - - /// - /// Gets the name of the source. - /// - public string Name { get; private set; } - - /// - /// Gets the argument of the source. - /// - public string Argument { get; private set; } - - /// - /// Gets the type of the source. - /// - public string Type { get; private set; } - - /// - /// Gets the trust level of the source. - /// - public string TrustLevel { get; private set; } - - /// - /// Gets a value indicating whether the source must be explicitly specified for discovery. - /// - public bool Explicit { get; private set; } - - /// - /// Gets a value indicating the priority of the source. Higher values are sorted first. - /// - public int Priority { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + /// + /// SourceResult wrapper object for displaying to PowerShell. + /// + public sealed class PSSourceResult + { + /// + /// Initializes a new instance of the class. + /// + /// The PackageCatalogReference COM object. + internal PSSourceResult(Management.Deployment.PackageCatalogReference catalogReference) + { + var info = catalogReference.Info; + this.Name = info.Name; + this.Argument = info.Argument; + this.Type = info.Type; + this.TrustLevel = info.TrustLevel.ToString(); + this.Explicit = info.Explicit; + this.Priority = info.Priority; + } + + /// + /// Gets the name of the source. + /// + public string Name { get; private set; } + + /// + /// Gets the argument of the source. + /// + public string Argument { get; private set; } + + /// + /// Gets the type of the source. + /// + public string Type { get; private set; } + + /// + /// Gets the trust level of the source. + /// + public string TrustLevel { get; private set; } + + /// + /// Gets a value indicating whether the source must be explicitly specified for discovery. + /// + public bool Explicit { get; private set; } + + /// + /// Gets a value indicating the priority of the source. Higher values are sorted first. + /// + public int Priority { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSUninstallResult.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSUninstallResult.cs index adff194212..7217f2fc04 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSUninstallResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/PSObjects/PSUninstallResult.cs @@ -1,138 +1,138 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Client.Engine.PSObjects -{ - using System; - using Microsoft.Management.Deployment; - using Microsoft.WinGet.Client.Engine.Extensions; - - /// - /// UninstallResult wrapper object for displaying to PowerShell. - /// - public sealed class PSUninstallResult - { - private readonly UninstallResult uninstallResult; - private readonly CatalogPackage catalogPackage; - - /// - /// Initializes a new instance of the class. - /// - /// The uninstall result COM object. - /// The catalog package COM object. - internal PSUninstallResult(UninstallResult uninstallResult, CatalogPackage catalogPackage) - { - this.uninstallResult = uninstallResult; - this.catalogPackage = catalogPackage; - } - - /// - /// Gets the id of the uninstalled package. - /// - public string Id - { - get - { - return this.catalogPackage.Id; - } - } - - /// - /// Gets the name of the uninstalled package. - /// - public string Name - { - get - { - return this.catalogPackage.Name; - } - } - - /// - /// Gets the source name of the uninstalled package. - /// - public string? Source - { - get - { - return this.catalogPackage.GetSourceName(); - } - } - - /// - /// Gets the correlation data of the uninstall result. - /// - public string CorrelationData - { - get - { - return this.uninstallResult.CorrelationData; - } - } - - /// - /// Gets the extended error code exception of the failed uninstall result. - /// - public Exception ExtendedErrorCode - { - get - { - return this.uninstallResult.ExtendedErrorCode; - } - } - - /// - /// Gets a value indicating whether a reboot is required. - /// - public bool RebootRequired - { - get - { - return this.uninstallResult.RebootRequired; - } - } - - /// - /// Gets the status of the uninstall. - /// - public string Status - { - get - { - return this.uninstallResult.Status.ToString(); - } - } - - /// - /// Gets the error code of an uninstall. - /// - public uint UninstallerErrorCode - { - get - { - return this.uninstallResult.UninstallerErrorCode; - } - } - - /// - /// If the uninstall succeeded. - /// - /// True if uninstall succeeded. - public bool Succeeded() - { - return this.uninstallResult.Status == UninstallResultStatus.Ok; - } - - /// - /// Message with error information. - /// - /// Error message. - public string ErrorMessage() - { - return $"UninstallStatus '{this.Status}' UninstallerErrorCode '{this.UninstallerErrorCode}' ExtendedError '{this.ExtendedErrorCode.HResult}'"; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Client.Engine.PSObjects +{ + using System; + using Microsoft.Management.Deployment; + using Microsoft.WinGet.Client.Engine.Extensions; + + /// + /// UninstallResult wrapper object for displaying to PowerShell. + /// + public sealed class PSUninstallResult + { + private readonly UninstallResult uninstallResult; + private readonly CatalogPackage catalogPackage; + + /// + /// Initializes a new instance of the class. + /// + /// The uninstall result COM object. + /// The catalog package COM object. + internal PSUninstallResult(UninstallResult uninstallResult, CatalogPackage catalogPackage) + { + this.uninstallResult = uninstallResult; + this.catalogPackage = catalogPackage; + } + + /// + /// Gets the id of the uninstalled package. + /// + public string Id + { + get + { + return this.catalogPackage.Id; + } + } + + /// + /// Gets the name of the uninstalled package. + /// + public string Name + { + get + { + return this.catalogPackage.Name; + } + } + + /// + /// Gets the source name of the uninstalled package. + /// + public string? Source + { + get + { + return this.catalogPackage.GetSourceName(); + } + } + + /// + /// Gets the correlation data of the uninstall result. + /// + public string CorrelationData + { + get + { + return this.uninstallResult.CorrelationData; + } + } + + /// + /// Gets the extended error code exception of the failed uninstall result. + /// + public Exception ExtendedErrorCode + { + get + { + return this.uninstallResult.ExtendedErrorCode; + } + } + + /// + /// Gets a value indicating whether a reboot is required. + /// + public bool RebootRequired + { + get + { + return this.uninstallResult.RebootRequired; + } + } + + /// + /// Gets the status of the uninstall. + /// + public string Status + { + get + { + return this.uninstallResult.Status.ToString(); + } + } + + /// + /// Gets the error code of an uninstall. + /// + public uint UninstallerErrorCode + { + get + { + return this.uninstallResult.UninstallerErrorCode; + } + } + + /// + /// If the uninstall succeeded. + /// + /// True if uninstall succeeded. + public bool Succeeded() + { + return this.uninstallResult.Status == UninstallResultStatus.Ok; + } + + /// + /// Message with error information. + /// + /// Error message. + public string ErrorMessage() + { + return $"UninstallStatus '{this.Status}' UninstallerErrorCode '{this.UninstallerErrorCode}' ExtendedError '{this.ExtendedErrorCode.HResult}'"; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/AssemblyInfo.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/AssemblyInfo.cs index 220b92c920..48424eb570 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/AssemblyInfo.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/AssemblyInfo.cs @@ -1,21 +1,21 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -using System.Runtime.CompilerServices; -#if NET -using System.Runtime.Versioning; -#endif - -[assembly: InternalsVisibleTo("Microsoft.WinGet.UnitTests")] - -#if NET - -// Forcibly set the target and supported platforms due to the internal build setup. -// Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.26100.0")] -[assembly: SupportedOSPlatform("Windows10.0.18362.0")] - -#endif +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +using System.Runtime.CompilerServices; +#if NET +using System.Runtime.Versioning; +#endif + +[assembly: InternalsVisibleTo("Microsoft.WinGet.UnitTests")] + +#if NET + +// Forcibly set the target and supported platforms due to the internal build setup. +// Keep in sync with project versions. +[assembly: TargetPlatform("Windows10.0.26100.0")] +[assembly: SupportedOSPlatform("Windows10.0.18362.0")] + +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs index 060cb7b614..cd756085a3 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.Designer.cs @@ -1,450 +1,450 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.WinGet.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.Client.Engine.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to An error occurred while connecting to the catalog.. - /// - internal static string CatalogConnectExceptionMessage { - get { - return ResourceManager.GetString("CatalogConnectExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Completed. - /// - internal static string Completed { - get { - return ResourceManager.GetString("Completed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Debug parameter not supported. - /// - internal static string DebugNotSupported { - get { - return ResourceManager.GetString("DebugNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Downloading. - /// - internal static string DownloadingMessage { - get { - return ResourceManager.GetString("DownloadingMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An error occurred while searching for packages: {0}. - /// - internal static string FindPackagesExceptionMessage { - get { - return ResourceManager.GetString("FindPackagesExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled.. - /// - internal static string IntegrityAppExecutionAliasDisabledMessage { - get { - return ResourceManager.GetString("IntegrityAppExecutionAliasDisabledMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No applicable license found.. - /// - internal static string IntegrityAppInstallerLicense { - get { - return ResourceManager.GetString("IntegrityAppInstallerLicense", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The App Installer is not installed.. - /// - internal static string IntegrityAppInstallerNotInstalledMessage { - get { - return ResourceManager.GetString("IntegrityAppInstallerNotInstalledMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The App Installer is not registered.. - /// - internal static string IntegrityAppInstallerNotRegisteredMessage { - get { - return ResourceManager.GetString("IntegrityAppInstallerNotRegisteredMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The App Installer does not contain the Windows Package Manager.. - /// - internal static string IntegrityAppInstallerNotSupportedMessage { - get { - return ResourceManager.GetString("IntegrityAppInstallerNotSupportedMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Windows Package Manager returned an unexcepted result.. - /// - internal static string IntegrityFailureMessage { - get { - return ResourceManager.GetString("IntegrityFailureMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The App Installer did not automatically add the PATH environment variable.. - /// - internal static string IntegrityNotInPathMessage { - get { - return ResourceManager.GetString("IntegrityNotInPathMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The Windows Package Manager requires Windows Version 1809 (October 2018 Update) or later.. - /// - internal static string IntegrityOsNotSupportedMessage { - get { - return ResourceManager.GetString("IntegrityOsNotSupportedMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}'. - /// - internal static string IntegrityUnexpectedVersionMessage { - get { - return ResourceManager.GetString("IntegrityUnexpectedVersionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unable to execute winget command.. - /// - internal static string IntegrityUnknownMessage { - get { - return ResourceManager.GetString("IntegrityUnknownMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No source matches the given value: {0}. - /// - internal static string InvalidSourceExceptionMessage { - get { - return ResourceManager.GetString("InvalidSourceExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No versions matched the given value: {0}. - /// - internal static string InvalidVersionExceptionMessage { - get { - return ResourceManager.GetString("InvalidVersionExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Repair operations involving administrator privileges are not permitted on packages installed within the user scope.. - /// - internal static string NoAdminRepairForUserScopePackage { - get { - return ResourceManager.GetString("NoAdminRepairForUserScopePackage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to No packages matched the given input criteria.. - /// - internal static string NoPackageFoundExceptionMessage { - get { - return ResourceManager.GetString("NoPackageFoundExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The repair command for this package is not available in the Package Manifest. Please reach out to the package publisher for assistance.. - /// - internal static string NoRepairInfoFound { - get { - return ResourceManager.GetString("NoRepairInfoFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Exporting '{0}'. - /// - internal static string ProgressRecordActivityExporting { - get { - return ResourceManager.GetString("ProgressRecordActivityExporting", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Installing '{0}'. - /// - internal static string ProgressRecordActivityInstalling { - get { - return ResourceManager.GetString("ProgressRecordActivityInstalling", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Repairing '{0}'. - /// - internal static string ProgressRecordActivityRepairing { - get { - return ResourceManager.GetString("ProgressRecordActivityRepairing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Uninstalling '{0}'. - /// - internal static string ProgressRecordActivityUninstalling { - get { - return ResourceManager.GetString("ProgressRecordActivityUninstalling", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Updating '{0}'. - /// - internal static string ProgressRecordActivityUpdating { - get { - return ResourceManager.GetString("ProgressRecordActivityUpdating", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find asset {0}. - /// - internal static string ReleaseAssetNotFound { - get { - return ResourceManager.GetString("ReleaseAssetNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Try running with -AllUsers in administrator mode.. - /// - internal static string RepairAllUsersHelpMessage { - get { - return ResourceManager.GetString("RepairAllUsersHelpMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to -AllUsers requires administrator mode.. - /// - internal static string RepairAllUsersMessage { - get { - return ResourceManager.GetString("RepairAllUsersMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it.. - /// - internal static string RepairAppExecutionAliasMessage { - get { - return ResourceManager.GetString("RepairAppExecutionAliasMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The installer technology in use does not match the version currently installed.. - /// - internal static string RepairDifferentInstallTechnology { - get { - return ResourceManager.GetString("RepairDifferentInstallTechnology", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The repair operation was unsuccessful, exiting with Repairer error code: {0}.. - /// - internal static string RepairerFailure { - get { - return ResourceManager.GetString("RepairerFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to repair winget.. - /// - internal static string RepairFailureMessage { - get { - return ResourceManager.GetString("RepairFailureMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Repairing. - /// - internal static string Repairing { - get { - return ResourceManager.GetString("Repairing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The current installer technology does not support repair. Please reach out to the package vendor for assistance.. - /// - internal static string RepairOperationNotSupported { - get { - return ResourceManager.GetString("RepairOperationNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This cmdlet requires administrator privileges to execute.. - /// - internal static string RequiresAdminMessage { - get { - return ResourceManager.GetString("RequiresAdminMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Single threaded apartment (STA) is not currently supported in this context; run PowerShell in Multi-threaded apartment mode (MTA).. - /// - internal static string SingleThreadedApartmentNotSupportedMessage { - get { - return ResourceManager.GetString("SingleThreadedApartmentNotSupportedMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Uninstalling. - /// - internal static string Uninstalling { - get { - return ResourceManager.GetString("Uninstalling", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to An unexpected error happened while trying to repair the package. Error code:{0}. - /// - internal static string UnknownRepairFailure { - get { - return ResourceManager.GetString("UnknownRepairFailure", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to User settings file is invalid.. - /// - internal static string UserSettingsReadException { - get { - return ResourceManager.GetString("UserSettingsReadException", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to {0}, {1}, and {2} other packages matched the input criteria. Please refine the input.. - /// - internal static string VagueCriteriaExceptionMessage { - get { - return ResourceManager.GetString("VagueCriteriaExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This cmdlet is not supported in Windows PowerShell.. - /// - internal static string WindowsPowerShellNotSupported { - get { - return ResourceManager.GetString("WindowsPowerShellNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Winget command '{0}' with parameters '{1}' failed with exit code '{2}'.. - /// - internal static string WinGetCLIExceptionMessage { - get { - return ResourceManager.GetString("WinGetCLIExceptionMessage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Winget command timed out: {0} {1}. - /// - internal static string WinGetCLITimeoutExceptionMessage { - get { - return ResourceManager.GetString("WinGetCLITimeoutExceptionMessage", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.WinGet.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.Client.Engine.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to An error occurred while connecting to the catalog.. + /// + internal static string CatalogConnectExceptionMessage { + get { + return ResourceManager.GetString("CatalogConnectExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Completed. + /// + internal static string Completed { + get { + return ResourceManager.GetString("Completed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Debug parameter not supported. + /// + internal static string DebugNotSupported { + get { + return ResourceManager.GetString("DebugNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Downloading. + /// + internal static string DownloadingMessage { + get { + return ResourceManager.GetString("DownloadingMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An error occurred while searching for packages: {0}. + /// + internal static string FindPackagesExceptionMessage { + get { + return ResourceManager.GetString("FindPackagesExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled.. + /// + internal static string IntegrityAppExecutionAliasDisabledMessage { + get { + return ResourceManager.GetString("IntegrityAppExecutionAliasDisabledMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No applicable license found.. + /// + internal static string IntegrityAppInstallerLicense { + get { + return ResourceManager.GetString("IntegrityAppInstallerLicense", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer is not installed.. + /// + internal static string IntegrityAppInstallerNotInstalledMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotInstalledMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer is not registered.. + /// + internal static string IntegrityAppInstallerNotRegisteredMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotRegisteredMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer does not contain the Windows Package Manager.. + /// + internal static string IntegrityAppInstallerNotSupportedMessage { + get { + return ResourceManager.GetString("IntegrityAppInstallerNotSupportedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Windows Package Manager returned an unexcepted result.. + /// + internal static string IntegrityFailureMessage { + get { + return ResourceManager.GetString("IntegrityFailureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Installer did not automatically add the PATH environment variable.. + /// + internal static string IntegrityNotInPathMessage { + get { + return ResourceManager.GetString("IntegrityNotInPathMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The Windows Package Manager requires Windows Version 1809 (October 2018 Update) or later.. + /// + internal static string IntegrityOsNotSupportedMessage { + get { + return ResourceManager.GetString("IntegrityOsNotSupportedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}'. + /// + internal static string IntegrityUnexpectedVersionMessage { + get { + return ResourceManager.GetString("IntegrityUnexpectedVersionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to execute winget command.. + /// + internal static string IntegrityUnknownMessage { + get { + return ResourceManager.GetString("IntegrityUnknownMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No source matches the given value: {0}. + /// + internal static string InvalidSourceExceptionMessage { + get { + return ResourceManager.GetString("InvalidSourceExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No versions matched the given value: {0}. + /// + internal static string InvalidVersionExceptionMessage { + get { + return ResourceManager.GetString("InvalidVersionExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Repair operations involving administrator privileges are not permitted on packages installed within the user scope.. + /// + internal static string NoAdminRepairForUserScopePackage { + get { + return ResourceManager.GetString("NoAdminRepairForUserScopePackage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No packages matched the given input criteria.. + /// + internal static string NoPackageFoundExceptionMessage { + get { + return ResourceManager.GetString("NoPackageFoundExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The repair command for this package is not available in the Package Manifest. Please reach out to the package publisher for assistance.. + /// + internal static string NoRepairInfoFound { + get { + return ResourceManager.GetString("NoRepairInfoFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Exporting '{0}'. + /// + internal static string ProgressRecordActivityExporting { + get { + return ResourceManager.GetString("ProgressRecordActivityExporting", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Installing '{0}'. + /// + internal static string ProgressRecordActivityInstalling { + get { + return ResourceManager.GetString("ProgressRecordActivityInstalling", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Repairing '{0}'. + /// + internal static string ProgressRecordActivityRepairing { + get { + return ResourceManager.GetString("ProgressRecordActivityRepairing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Uninstalling '{0}'. + /// + internal static string ProgressRecordActivityUninstalling { + get { + return ResourceManager.GetString("ProgressRecordActivityUninstalling", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Updating '{0}'. + /// + internal static string ProgressRecordActivityUpdating { + get { + return ResourceManager.GetString("ProgressRecordActivityUpdating", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find asset {0}. + /// + internal static string ReleaseAssetNotFound { + get { + return ResourceManager.GetString("ReleaseAssetNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Try running with -AllUsers in administrator mode.. + /// + internal static string RepairAllUsersHelpMessage { + get { + return ResourceManager.GetString("RepairAllUsersHelpMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to -AllUsers requires administrator mode.. + /// + internal static string RepairAllUsersMessage { + get { + return ResourceManager.GetString("RepairAllUsersMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it.. + /// + internal static string RepairAppExecutionAliasMessage { + get { + return ResourceManager.GetString("RepairAppExecutionAliasMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The installer technology in use does not match the version currently installed.. + /// + internal static string RepairDifferentInstallTechnology { + get { + return ResourceManager.GetString("RepairDifferentInstallTechnology", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The repair operation was unsuccessful, exiting with Repairer error code: {0}.. + /// + internal static string RepairerFailure { + get { + return ResourceManager.GetString("RepairerFailure", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to repair winget.. + /// + internal static string RepairFailureMessage { + get { + return ResourceManager.GetString("RepairFailureMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Repairing. + /// + internal static string Repairing { + get { + return ResourceManager.GetString("Repairing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The current installer technology does not support repair. Please reach out to the package vendor for assistance.. + /// + internal static string RepairOperationNotSupported { + get { + return ResourceManager.GetString("RepairOperationNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This cmdlet requires administrator privileges to execute.. + /// + internal static string RequiresAdminMessage { + get { + return ResourceManager.GetString("RequiresAdminMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Single threaded apartment (STA) is not currently supported in this context; run PowerShell in Multi-threaded apartment mode (MTA).. + /// + internal static string SingleThreadedApartmentNotSupportedMessage { + get { + return ResourceManager.GetString("SingleThreadedApartmentNotSupportedMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Uninstalling. + /// + internal static string Uninstalling { + get { + return ResourceManager.GetString("Uninstalling", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to An unexpected error happened while trying to repair the package. Error code:{0}. + /// + internal static string UnknownRepairFailure { + get { + return ResourceManager.GetString("UnknownRepairFailure", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to User settings file is invalid.. + /// + internal static string UserSettingsReadException { + get { + return ResourceManager.GetString("UserSettingsReadException", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0}, {1}, and {2} other packages matched the input criteria. Please refine the input.. + /// + internal static string VagueCriteriaExceptionMessage { + get { + return ResourceManager.GetString("VagueCriteriaExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This cmdlet is not supported in Windows PowerShell.. + /// + internal static string WindowsPowerShellNotSupported { + get { + return ResourceManager.GetString("WindowsPowerShellNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Winget command '{0}' with parameters '{1}' failed with exit code '{2}'.. + /// + internal static string WinGetCLIExceptionMessage { + get { + return ResourceManager.GetString("WinGetCLIExceptionMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Winget command timed out: {0} {1}. + /// + internal static string WinGetCLITimeoutExceptionMessage { + get { + return ResourceManager.GetString("WinGetCLITimeoutExceptionMessage", resourceCulture); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx index 174ff2c4ab..b5aae6b6ad 100644 --- a/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Client.Engine/Properties/Resources.resx @@ -1,267 +1,267 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - No source matches the given value: {0} - {Locked="{0}"} {0} - The name of the source that was not found. - - - An error occurred while searching for packages: {0} - {Locked="{0}"} {0} - A string representation of the error status returned by the catalog. - - - Installing '{0}' - {Locked="{0}"} {0} - The name of the package being installed. - - - Uninstalling '{0}' - {Locked="{0}"} {0} - The name of the package being uninstalled. - - - Updating '{0}' - {Locked="{0}"} {0} - The name of the package being updated. - - - An error occurred while connecting to the catalog. - - - No versions matched the given value: {0} - {Locked="{0}"} {0} - The version string provided by the user. - - - No packages matched the given input criteria. - - - {0}, {1}, and {2} other packages matched the input criteria. Please refine the input. - {Locked="{0}","{1}","{2}"} {0} - The first conflicting package as a string. {1} - The second conflicting package. {2} - The number of other packages that also matched the input criteria. - - - Unable to execute winget command. - - - Winget command '{0}' with parameters '{1}' failed with exit code '{2}'. - {Locked="{0}","{1}","{2}"} {0} - The winget command executed. {1} - The parameters of the command. {2} - The exit code. - - - User settings file is invalid. - - - The App Execution Alias for the Windows Package Manager is disabled. - - - The App Installer is not installed. - - - The App Installer does not contain the Windows Package Manager. - - - Windows Package Manager returned an unexcepted result. - - - The App Installer did not automatically add the PATH environment variable. - - - The Windows Package Manager requires Windows Version 1809 (October 2018 Update) or later. - - - Winget command timed out: {0} {1} - {Locked="{0}","{1}"} {0} - The winget command executed. {1} - The parameters of the command. - - - The App Installer is not registered. - - - The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it. - - - The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}' - {Locked="{0}","{1}"} {0} - The winget current installed winget version. {1} - The expected winget version. - - - Single threaded apartment (STA) is not currently supported in this context; run PowerShell in Multi-threaded apartment mode (MTA). - {Locked="STA","MTA"} - - - This cmdlet is not supported in Windows PowerShell. - - - No applicable license found. - - - Try running with -AllUsers in administrator mode. - {Locked="-AllUsers"} - - - -AllUsers requires administrator mode. - {Locked="-AllUsers"} - - - Failed to repair winget. - - - This cmdlet requires administrator privileges to execute. - - - Debug parameter not supported - - - Downloading - - - Completed - - - Uninstalling - - - Cannot find asset {0} - {Locked="{0}"} {0} - The asset name - - - Exporting '{0}' - {Locked="{0}"} {0} - The name of the package being exported. - - - Repairing '{0}' - {Locked="{0}"} {0} - The name of the package being repaired. - - - Repairing - - - The repair command for this package is not available in the Package Manifest. Please reach out to the package publisher for assistance. - - - The installer technology in use does not match the version currently installed. - - - The repair operation was unsuccessful, exiting with Repairer error code: {0}. - {Locked="{0}"} {0} - The error code of the repairer. - - - The current installer technology does not support repair. Please reach out to the package vendor for assistance. - - - Repair operations involving administrator privileges are not permitted on packages installed within the user scope. - - - An unexpected error happened while trying to repair the package. Error code:{0} - {Locked="{0}"} {0} - The error code from the repair operation. - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + No source matches the given value: {0} + {Locked="{0}"} {0} - The name of the source that was not found. + + + An error occurred while searching for packages: {0} + {Locked="{0}"} {0} - A string representation of the error status returned by the catalog. + + + Installing '{0}' + {Locked="{0}"} {0} - The name of the package being installed. + + + Uninstalling '{0}' + {Locked="{0}"} {0} - The name of the package being uninstalled. + + + Updating '{0}' + {Locked="{0}"} {0} - The name of the package being updated. + + + An error occurred while connecting to the catalog. + + + No versions matched the given value: {0} + {Locked="{0}"} {0} - The version string provided by the user. + + + No packages matched the given input criteria. + + + {0}, {1}, and {2} other packages matched the input criteria. Please refine the input. + {Locked="{0}","{1}","{2}"} {0} - The first conflicting package as a string. {1} - The second conflicting package. {2} - The number of other packages that also matched the input criteria. + + + Unable to execute winget command. + + + Winget command '{0}' with parameters '{1}' failed with exit code '{2}'. + {Locked="{0}","{1}","{2}"} {0} - The winget command executed. {1} - The parameters of the command. {2} - The exit code. + + + User settings file is invalid. + + + The App Execution Alias for the Windows Package Manager is disabled. + + + The App Installer is not installed. + + + The App Installer does not contain the Windows Package Manager. + + + Windows Package Manager returned an unexcepted result. + + + The App Installer did not automatically add the PATH environment variable. + + + The Windows Package Manager requires Windows Version 1809 (October 2018 Update) or later. + + + Winget command timed out: {0} {1} + {Locked="{0}","{1}"} {0} - The winget command executed. {1} - The parameters of the command. + + + The App Installer is not registered. + + + The App Execution Alias for the Windows Package Manager is disabled. You should enable the App Execution Alias for the Windows Package Manager. Go to App execution aliases option in Apps & features Settings to enable it. + + + The installed winget version doesn't match the expectation. Installer version '{0}' Expected version '{1}' + {Locked="{0}","{1}"} {0} - The winget current installed winget version. {1} - The expected winget version. + + + Single threaded apartment (STA) is not currently supported in this context; run PowerShell in Multi-threaded apartment mode (MTA). + {Locked="STA","MTA"} + + + This cmdlet is not supported in Windows PowerShell. + + + No applicable license found. + + + Try running with -AllUsers in administrator mode. + {Locked="-AllUsers"} + + + -AllUsers requires administrator mode. + {Locked="-AllUsers"} + + + Failed to repair winget. + + + This cmdlet requires administrator privileges to execute. + + + Debug parameter not supported + + + Downloading + + + Completed + + + Uninstalling + + + Cannot find asset {0} + {Locked="{0}"} {0} - The asset name + + + Exporting '{0}' + {Locked="{0}"} {0} - The name of the package being exported. + + + Repairing '{0}' + {Locked="{0}"} {0} - The name of the package being repaired. + + + Repairing + + + The repair command for this package is not available in the Package Manifest. Please reach out to the package publisher for assistance. + + + The installer technology in use does not match the version currently installed. + + + The repair operation was unsuccessful, exiting with Repairer error code: {0}. + {Locked="{0}"} {0} - The error code of the repairer. + + + The current installer technology does not support repair. Please reach out to the package vendor for assistance. + + + Repair operations involving administrator privileges are not permitted on packages installed within the user scope. + + + An unexpected error happened while trying to repair the package. Error code:{0} + {Locked="{0}"} {0} - The error code from the repair operation. + + diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_AddRemoveSource.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_AddRemoveSource.ps1 index e14c7908b8..9183846b4b 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_AddRemoveSource.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_AddRemoveSource.ps1 @@ -1,25 +1,25 @@ -<# - .SYNOPSIS - Example for 'Get-WinGetSource', 'Add-WinGetSource', 'Remove-WinGetSource', and 'Reset-WinGetSource' cmdlet. - Cmdlets to allow you to manage sources for the Windows Package Manager. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# List current sources. -Get-WinGetSource - -# Add REST source -Add-WinGetSource -Name 'Contoso' -Argument 'https://www.contoso.com/cache' -Type 'Microsoft.Rest' - -# Remove source by name -Remove-WinGetSource -Name 'Contoso' - -# Reset to default sources +<# + .SYNOPSIS + Example for 'Get-WinGetSource', 'Add-WinGetSource', 'Remove-WinGetSource', and 'Reset-WinGetSource' cmdlet. + Cmdlets to allow you to manage sources for the Windows Package Manager. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# List current sources. +Get-WinGetSource + +# Add REST source +Add-WinGetSource -Name 'Contoso' -Argument 'https://www.contoso.com/cache' -Type 'Microsoft.Rest' + +# Remove source by name +Remove-WinGetSource -Name 'Contoso' + +# Reset to default sources Reset-WinGetSource \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_EnableSettings.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_EnableSettings.ps1 index 3f90290a51..e74a49e6af 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_EnableSettings.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_EnableSettings.ps1 @@ -1,19 +1,19 @@ -<# - .SYNOPSIS - Example for 'Enable-WinGetSetting' and 'Disable-WinGetSetting' cmdlets. - Cmdlet for enabling/disabling a specified WinGet setting. May require elevation. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# Enables the 'LocalManifestFiles' setting. -Enable-WinGetSetting -Name LocalManifestFiles - -# Disables the 'LocalManifestFiles' setting. +<# + .SYNOPSIS + Example for 'Enable-WinGetSetting' and 'Disable-WinGetSetting' cmdlets. + Cmdlet for enabling/disabling a specified WinGet setting. May require elevation. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# Enables the 'LocalManifestFiles' setting. +Enable-WinGetSetting -Name LocalManifestFiles + +# Disables the 'LocalManifestFiles' setting. Disable-WinGetSetting -Name LocalManifestFiles \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_FindPackage.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_FindPackage.ps1 index c75143844f..d0fe296644 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_FindPackage.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_FindPackage.ps1 @@ -1,28 +1,28 @@ -<# - .SYNOPSIS - Example for 'Find-WinGetPackage' cmdlet. - Displays all applications available for installation. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# Find all available packages -Find-WinGetPackage - -# Find package by name -Find-WinGetPackage -Name git - -# Find 10 packages by name -Find-WinGetPackage -Name git -Count 10 - -# Find package by package identifier -Find-WinGetPackage -Id git.git - -# Find exact package from a specific source. +<# + .SYNOPSIS + Example for 'Find-WinGetPackage' cmdlet. + Displays all applications available for installation. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# Find all available packages +Find-WinGetPackage + +# Find package by name +Find-WinGetPackage -Name git + +# Find 10 packages by name +Find-WinGetPackage -Name git -Count 10 + +# Find package by package identifier +Find-WinGetPackage -Id git.git + +# Find exact package from a specific source. Find-WinGetPackage -Id Git.Git -Source winget -Exact \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetPackage.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetPackage.ps1 index 36499275e0..481ed77934 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetPackage.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetPackage.ps1 @@ -1,28 +1,28 @@ -<# - .SYNOPSIS - Example for 'Get-WinGetPackage' cmdlet. - Displays a list of the applications currently installed on your computer. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# List all installed packages -Get-WinGetPackage - -# List installed package by name -Get-WinGetPackage -Name git - -# List 10 packages by name -Get-WinGetPackage -Name git -Count 10 - -# List package by package identifier -Get-WinGetPackage -Id git.git - -# List exact package from a specific source. +<# + .SYNOPSIS + Example for 'Get-WinGetPackage' cmdlet. + Displays a list of the applications currently installed on your computer. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# List all installed packages +Get-WinGetPackage + +# List installed package by name +Get-WinGetPackage -Name git + +# List 10 packages by name +Get-WinGetPackage -Name git -Count 10 + +# List package by package identifier +Get-WinGetPackage -Id git.git + +# List exact package from a specific source. Get-WinGetPackage -Id Git.Git -Source winget -Exact \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetVersion.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetVersion.ps1 index d0cae7521c..e276379397 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetVersion.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_GetVersion.ps1 @@ -1,17 +1,17 @@ -<# - .SYNOPSIS - Example for 'Get-WinGetVersion' cmdlet. - Prints the current client version. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -$version = Get-WinGetVersion - +<# + .SYNOPSIS + Example for 'Get-WinGetVersion' cmdlet. + Prints the current client version. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +$version = Get-WinGetVersion + Write-Host($version); \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_InstallPackage.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_InstallPackage.ps1 index b2b9359f3a..6675a82b16 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_InstallPackage.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_InstallPackage.ps1 @@ -1,25 +1,25 @@ -<# - .SYNOPSIS - Example for 'Install-WinGetPackage' cmdlet. - Installs the specified application based on the provided arguments. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# Install a package by name -Install-WinGetPackage -Name powertoys - -# Install a package by version and package identifier. -Install-WinGetPackage -Id Microsoft.PowerToys -Version 0.15.2 - -# Install a package from a specific source -Install-WinGetPackage -Id Microsoft.PowerToys -Source winget - -# Install a package with a specific architecture and scope. -Install-WinGetPackage -Id Microsoft.PowerToys -Architecture X64 -Scope User +<# + .SYNOPSIS + Example for 'Install-WinGetPackage' cmdlet. + Installs the specified application based on the provided arguments. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# Install a package by name +Install-WinGetPackage -Name powertoys + +# Install a package by version and package identifier. +Install-WinGetPackage -Id Microsoft.PowerToys -Version 0.15.2 + +# Install a package from a specific source +Install-WinGetPackage -Id Microsoft.PowerToys -Source winget + +# Install a package with a specific architecture and scope. +Install-WinGetPackage -Id Microsoft.PowerToys -Architecture X64 -Scope User diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 index a9a1622659..de8be0c832 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_RepairPackage.ps1 @@ -1,22 +1,22 @@ -<# - .SYNOPSIS - Example for 'Repair-WinGetPackage' cmdlet. - Repairs the specified application. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# Repair a package by name -Repair-WinGetPackage -Name "PowerToys FileLocksmith Context Menu" - -# Repair a package by version and package identifier. -Repair-WinGetPackage -Id "MSIX\Microsoft.PowerToys.FileLocksmithContextMenu_1.0.0.0_neutral__8wekyb3d8bbwe" - -# Repair a package from a specific source -Repair-WinGetPackage -Id "MSIX\Microsoft.PowerToys.FileLocksmithContextMenu_1.0.0.0_neutral__8wekyb3d8bbwe" -Source winget +<# + .SYNOPSIS + Example for 'Repair-WinGetPackage' cmdlet. + Repairs the specified application. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# Repair a package by name +Repair-WinGetPackage -Name "PowerToys FileLocksmith Context Menu" + +# Repair a package by version and package identifier. +Repair-WinGetPackage -Id "MSIX\Microsoft.PowerToys.FileLocksmithContextMenu_1.0.0.0_neutral__8wekyb3d8bbwe" + +# Repair a package from a specific source +Repair-WinGetPackage -Id "MSIX\Microsoft.PowerToys.FileLocksmithContextMenu_1.0.0.0_neutral__8wekyb3d8bbwe" -Source winget diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UninstallPackage.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UninstallPackage.ps1 index 3b7cbb9b8f..1e118321b7 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UninstallPackage.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UninstallPackage.ps1 @@ -1,22 +1,22 @@ -<# - .SYNOPSIS - Example for 'Uninstall-WinGetPackage' cmdlet. - Uninstalls the specified application. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# Uninstall a package by name -Uninstall-WinGetPackage -Name powertoys - -# Uninstall a package by version and package identifier. -Uninstall-WinGetPackage -Id Microsoft.PowerToys -Version 0.15.2 - -# Uninstall a package from a specific source -Uninstall-WinGetPackage -Id Microsoft.PowerToys -Source winget +<# + .SYNOPSIS + Example for 'Uninstall-WinGetPackage' cmdlet. + Uninstalls the specified application. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# Uninstall a package by name +Uninstall-WinGetPackage -Name powertoys + +# Uninstall a package by version and package identifier. +Uninstall-WinGetPackage -Id Microsoft.PowerToys -Version 0.15.2 + +# Uninstall a package from a specific source +Uninstall-WinGetPackage -Id Microsoft.PowerToys -Source winget diff --git a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UpdatePackage.ps1 b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UpdatePackage.ps1 index a95ea4799f..abbefbe509 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UpdatePackage.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Client/Examples/Sample_UpdatePackage.ps1 @@ -1,25 +1,25 @@ -<# - .SYNOPSIS - Example for 'Update-WinGetPackage' cmdlet. - Updates the specified application. -#> - -# TODO: Replace parameter with actual module name from PSGallery once module is released. -Param ( - [Parameter(Mandatory)] - $ModulePath -) - -Import-Module -Name $ModulePath - -# Update a package by name -Update-WinGetPackage -Name powertoys - -# Update a package by version and package identifier. -Update-WinGetPackage -Id Microsoft.PowerToys -Version 0.15.2 - -# Update a package with silent mode -Update-WinGetPackage -Id Microsoft.PowerToys -Mode Silent - -# Force update a package +<# + .SYNOPSIS + Example for 'Update-WinGetPackage' cmdlet. + Updates the specified application. +#> + +# TODO: Replace parameter with actual module name from PSGallery once module is released. +Param ( + [Parameter(Mandatory)] + $ModulePath +) + +Import-Module -Name $ModulePath + +# Update a package by name +Update-WinGetPackage -Name powertoys + +# Update a package by version and package identifier. +Update-WinGetPackage -Id Microsoft.PowerToys -Version 0.15.2 + +# Update a package with silent mode +Update-WinGetPackage -Id Microsoft.PowerToys -Mode Silent + +# Force update a package Update-WinGetPackage -Id Microsoft.PowerToys -Force \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml index 82ac33cd78..f5fabc1a6d 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml +++ b/src/PowerShell/Microsoft.WinGet.Client/ModuleFiles/Format.ps1xml @@ -364,38 +364,38 @@ - - - Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult - + + + Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult + Microsoft.WinGet.Client.Engine.PSObjects.PSRepairResult - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -426,8 +426,8 @@ - + - + diff --git a/src/PowerShell/Microsoft.WinGet.Client/README.md b/src/PowerShell/Microsoft.WinGet.Client/README.md index 99565c7516..f270294700 100644 --- a/src/PowerShell/Microsoft.WinGet.Client/README.md +++ b/src/PowerShell/Microsoft.WinGet.Client/README.md @@ -1,58 +1,58 @@ -# Windows Package Manager PowerShell Module - -The Windows Package Manager PowerShell Module is made up on two components - -1. The `Microsoft.WinGet.Client.Cmdlets` project which contains cmdlet implementations. -2. The `Microsoft.WinGet.Client.Engine` project which contain the real logic for the cmdlets. - -## Building the PowerShell Module Locally - -After building the Microsoft.WinGet.Client.Cmdlets project, the `Microsoft.WinGet.Client` PowerShell module can be found in the output directory in the `PowerShell` folder. For example if you built the project as x64 release, you should expect to find the module files in `$(SolutionDirectory)/src/x64/Release/PowerShell`. - -This project has after build targets that will copy all the necessary files in the correct location. - -## Adding a new cmdlet -In order to avoid [assembly dependency conflicts](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?view=powershell-7.3) this project uses a custom `AssemblyLoadContext` that load all dependencies. - -Microsoft.WinGet.Client.Cmdlets.dll is the binary that gets loaded when the module is imported. When Microsoft.WinGet.Client.Engine.dll is getting loaded the resolving handler use the custom ALC to load it. Then all the dependencies of that binary will be loaded using that custom context. - -The dependencies are laid out in two directories: `DirectDependencies` and `SharedDependencies`. The resolving handler looks for binaries under `DirectDependencies` and uses the custom ALC to load them. The custom ALC load any binaries in `DirectDependencies` and `SharedDependencies`. - -Exception: WinRT.Runtime.dll doesn't support getting loaded in multiple times in the same process, because it affects static state in the CLR itself. We special case it to get loaded in by the default loader. - -If the new cmdlet introduces a new dependency, please make sure to add it in the after build targets to copy it in the Dependencies directory. - -## Cmdlets -- Assert-WinGetPackageManager -- Find-WinGetPackage -- Get-WinGetPackage -- Get-WinGetSource -- Get-WinGetUserSetting -- Get-WinGetVersion -- Install-WinGetPackage -- Repair-WinGetPackageManager -- Set-WinGetUserSetting -- Test-WinGetUserSetting -- Uninstall-WinGetPackage -- Update-WinGetPackage -- Add-WinGetSource -- Disable-WinGetSetting -- Enable-WinGetSetting -- Get-WinGetSetting -- Remove-WinGetSource -- Reset-WinGetSource -- Repair-WinGetPackage - -## Quick Start Guide - -**To run the module, make sure you are using the latest version of PowerShell (not Windows PowerShell)**. - -``` -winget install --id Microsoft.Powershell -``` - -Import the module manifest (Microsoft.WinGet.Client.psd1) by running the following command: - -``` -Import-Module -``` +# Windows Package Manager PowerShell Module + +The Windows Package Manager PowerShell Module is made up on two components + +1. The `Microsoft.WinGet.Client.Cmdlets` project which contains cmdlet implementations. +2. The `Microsoft.WinGet.Client.Engine` project which contain the real logic for the cmdlets. + +## Building the PowerShell Module Locally + +After building the Microsoft.WinGet.Client.Cmdlets project, the `Microsoft.WinGet.Client` PowerShell module can be found in the output directory in the `PowerShell` folder. For example if you built the project as x64 release, you should expect to find the module files in `$(SolutionDirectory)/src/x64/Release/PowerShell`. + +This project has after build targets that will copy all the necessary files in the correct location. + +## Adding a new cmdlet +In order to avoid [assembly dependency conflicts](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?view=powershell-7.3) this project uses a custom `AssemblyLoadContext` that load all dependencies. + +Microsoft.WinGet.Client.Cmdlets.dll is the binary that gets loaded when the module is imported. When Microsoft.WinGet.Client.Engine.dll is getting loaded the resolving handler use the custom ALC to load it. Then all the dependencies of that binary will be loaded using that custom context. + +The dependencies are laid out in two directories: `DirectDependencies` and `SharedDependencies`. The resolving handler looks for binaries under `DirectDependencies` and uses the custom ALC to load them. The custom ALC load any binaries in `DirectDependencies` and `SharedDependencies`. + +Exception: WinRT.Runtime.dll doesn't support getting loaded in multiple times in the same process, because it affects static state in the CLR itself. We special case it to get loaded in by the default loader. + +If the new cmdlet introduces a new dependency, please make sure to add it in the after build targets to copy it in the Dependencies directory. + +## Cmdlets +- Assert-WinGetPackageManager +- Find-WinGetPackage +- Get-WinGetPackage +- Get-WinGetSource +- Get-WinGetUserSetting +- Get-WinGetVersion +- Install-WinGetPackage +- Repair-WinGetPackageManager +- Set-WinGetUserSetting +- Test-WinGetUserSetting +- Uninstall-WinGetPackage +- Update-WinGetPackage +- Add-WinGetSource +- Disable-WinGetSetting +- Enable-WinGetSetting +- Get-WinGetSetting +- Remove-WinGetSource +- Reset-WinGetSource +- Repair-WinGetPackage + +## Quick Start Guide + +**To run the module, make sure you are using the latest version of PowerShell (not Windows PowerShell)**. + +``` +winget install --id Microsoft.Powershell +``` + +Import the module manifest (Microsoft.WinGet.Client.psd1) by running the following command: + +``` +Import-Module +``` diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/Common/OpenConfiguration.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/Common/OpenConfiguration.cs index 0a56568918..d3425c9aa0 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/Common/OpenConfiguration.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/Common/OpenConfiguration.cs @@ -1,79 +1,79 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets.Common -{ - using System.Management.Automation; - using Microsoft.PowerShell; - using Microsoft.WinGet.Configuration.Helpers; - - /// - /// The parameters used to open a configuration. - /// - public abstract class OpenConfiguration : PSCmdlet - { - /// - /// Gets or sets the configuration file. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipelineByPropertyName = true, - ParameterSetName = Constants.ParameterSet.OpenConfigurationSetFromFile)] - public string File { get; set; } - - /// - /// Gets or sets the configuration history item identifier. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipelineByPropertyName = true, - ParameterSetName = Constants.ParameterSet.OpenConfigurationSetFromHistory)] - public string InstanceIdentifier { get; set; } - - /// - /// Gets or sets a value indicating whether all configuration history items should be returned. - /// - [Parameter( - Mandatory = true, - ParameterSetName = Constants.ParameterSet.OpenAllConfigurationSetsFromHistory)] - public SwitchParameter All { get; set; } - - /// - /// Gets or sets custom location to install modules. - /// - [Parameter( - Position = 1, - ValueFromPipelineByPropertyName = true)] - public string ModulePath { get; set; } - - /// - /// Gets or sets custom DSCv3 processor path. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public string ProcessorPath { get; set; } - - /// - /// Gets the execution policy to use. - /// - protected ExecutionPolicy ExecutionPolicy { get; private set; } = ExecutionPolicy.Undefined; - - /// - /// Gets a value indicating whether to use telemetry or not. - /// - protected bool CanUseTelemetry { get; private set; } - - /// - /// Pre-processing operations. - /// - protected override void BeginProcessing() - { - this.ExecutionPolicy = Utilities.GetExecutionPolicy(); - this.CanUseTelemetry = Utilities.CanUseTelemetry(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets.Common +{ + using System.Management.Automation; + using Microsoft.PowerShell; + using Microsoft.WinGet.Configuration.Helpers; + + /// + /// The parameters used to open a configuration. + /// + public abstract class OpenConfiguration : PSCmdlet + { + /// + /// Gets or sets the configuration file. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipelineByPropertyName = true, + ParameterSetName = Constants.ParameterSet.OpenConfigurationSetFromFile)] + public string File { get; set; } + + /// + /// Gets or sets the configuration history item identifier. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipelineByPropertyName = true, + ParameterSetName = Constants.ParameterSet.OpenConfigurationSetFromHistory)] + public string InstanceIdentifier { get; set; } + + /// + /// Gets or sets a value indicating whether all configuration history items should be returned. + /// + [Parameter( + Mandatory = true, + ParameterSetName = Constants.ParameterSet.OpenAllConfigurationSetsFromHistory)] + public SwitchParameter All { get; set; } + + /// + /// Gets or sets custom location to install modules. + /// + [Parameter( + Position = 1, + ValueFromPipelineByPropertyName = true)] + public string ModulePath { get; set; } + + /// + /// Gets or sets custom DSCv3 processor path. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public string ProcessorPath { get; set; } + + /// + /// Gets the execution policy to use. + /// + protected ExecutionPolicy ExecutionPolicy { get; private set; } = ExecutionPolicy.Undefined; + + /// + /// Gets a value indicating whether to use telemetry or not. + /// + protected bool CanUseTelemetry { get; private set; } + + /// + /// Pre-processing operations. + /// + protected override void BeginProcessing() + { + this.ExecutionPolicy = Utilities.GetExecutionPolicy(); + this.CanUseTelemetry = Utilities.CanUseTelemetry(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/CompleteWinGetConfigurationCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/CompleteWinGetConfigurationCmdlet.cs index 2fdb31c849..a25404d368 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/CompleteWinGetConfigurationCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/CompleteWinGetConfigurationCmdlet.cs @@ -1,55 +1,55 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Complete-WinGetConfiguration. - /// Completes a configuration previously started by Start-WinGetConfiguration. - /// Waits for completion. - /// - [Cmdlet(VerbsLifecycle.Complete, "WinGetConfiguration")] - [Alias("cmpwgc")] - public sealed class CompleteWinGetConfigurationCmdlet : PSCmdlet - { - private ConfigurationCommand runningCommand = null; - - /// - /// Gets or sets the configuration task. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationJob ConfigurationJob { get; set; } - - /// - /// Starts to apply the configuration and wait for it to complete. - /// - protected override void ProcessRecord() - { - this.runningCommand = new ConfigurationCommand(this); - this.runningCommand.Continue(this.ConfigurationJob); - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.runningCommand != null) - { - this.runningCommand.Cancel(this.ConfigurationJob); - this.runningCommand.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Complete-WinGetConfiguration. + /// Completes a configuration previously started by Start-WinGetConfiguration. + /// Waits for completion. + /// + [Cmdlet(VerbsLifecycle.Complete, "WinGetConfiguration")] + [Alias("cmpwgc")] + public sealed class CompleteWinGetConfigurationCmdlet : PSCmdlet + { + private ConfigurationCommand runningCommand = null; + + /// + /// Gets or sets the configuration task. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationJob ConfigurationJob { get; set; } + + /// + /// Starts to apply the configuration and wait for it to complete. + /// + protected override void ProcessRecord() + { + this.runningCommand = new ConfigurationCommand(this); + this.runningCommand.Continue(this.ConfigurationJob); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.runningCommand != null) + { + this.runningCommand.Cancel(this.ConfigurationJob); + this.runningCommand.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConfirmWinGetConfigurationCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConfirmWinGetConfigurationCmdlet.cs index 72df06d19a..91796e3fe9 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConfirmWinGetConfigurationCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConfirmWinGetConfigurationCmdlet.cs @@ -1,53 +1,53 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Confirm-WinGetConfiguration - /// Validates winget configuration. - /// - [Cmdlet(VerbsLifecycle.Confirm, "WinGetConfiguration")] - [Alias("cnwgc")] - public class ConfirmWinGetConfigurationCmdlet : PSCmdlet - { - private ConfigurationCommand runningCommand = null; - - /// - /// Gets or sets the configuration set. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationSet Set { get; set; } - - /// - /// Validate configuration. - /// - protected override void ProcessRecord() - { - this.runningCommand = new ConfigurationCommand(this); - this.runningCommand.Validate(this.Set); - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.runningCommand != null) - { - this.runningCommand.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Confirm-WinGetConfiguration + /// Validates winget configuration. + /// + [Cmdlet(VerbsLifecycle.Confirm, "WinGetConfiguration")] + [Alias("cnwgc")] + public class ConfirmWinGetConfigurationCmdlet : PSCmdlet + { + private ConfigurationCommand runningCommand = null; + + /// + /// Gets or sets the configuration set. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationSet Set { get; set; } + + /// + /// Validate configuration. + /// + protected override void ProcessRecord() + { + this.runningCommand = new ConfigurationCommand(this); + this.runningCommand.Validate(this.Set); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.runningCommand != null) + { + this.runningCommand.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConvertToWinGetConfigurationYamlCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConvertToWinGetConfigurationYamlCmdlet.cs index 615075190d..1b7db1c86e 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConvertToWinGetConfigurationYamlCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/ConvertToWinGetConfigurationYamlCmdlet.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// ConvertTo-WinGetConfigurationYaml - /// Serializes a PSConfigurationSet to a YAML string. - /// - [Cmdlet(VerbsData.ConvertTo, "WinGetConfigurationYaml")] - [Alias("ctwgcy")] - public sealed class ConvertToWinGetConfigurationYamlCmdlet : PSCmdlet - { - /// - /// Gets or sets the configuration set. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationSet Set { get; set; } - - /// - /// Converts the given set to a string. - /// - protected override void ProcessRecord() - { - var configCommand = new ConfigurationCommand(this); - configCommand.Serialize(this.Set); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// ConvertTo-WinGetConfigurationYaml + /// Serializes a PSConfigurationSet to a YAML string. + /// + [Cmdlet(VerbsData.ConvertTo, "WinGetConfigurationYaml")] + [Alias("ctwgcy")] + public sealed class ConvertToWinGetConfigurationYamlCmdlet : PSCmdlet + { + /// + /// Gets or sets the configuration set. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationSet Set { get; set; } + + /// + /// Converts the given set to a string. + /// + protected override void ProcessRecord() + { + var configCommand = new ConfigurationCommand(this); + configCommand.Serialize(this.Set); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationCmdlet.cs index 2314ab96fa..9a3ccb886d 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationCmdlet.cs @@ -1,56 +1,56 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Cmdlets.Common; - using Microsoft.WinGet.Configuration.Engine.Commands; - - /// - /// Get-WinGetConfiguration. - /// Opens a configuration set. - /// - [Cmdlet(VerbsCommon.Get, "WinGetConfiguration", DefaultParameterSetName = Helpers.Constants.ParameterSet.OpenConfigurationSetFromFile)] - [Alias("gwgc")] - public sealed class GetWinGetConfigurationCmdlet : OpenConfiguration - { - /// - /// Opens the configuration set. - /// - protected override void ProcessRecord() - { - var configCommand = new ConfigurationCommand(this); - - if (this.ParameterSetName == Helpers.Constants.ParameterSet.OpenConfigurationSetFromFile) - { - configCommand.Get( - this.File, - this.ModulePath, - this.ExecutionPolicy, - this.ProcessorPath, - this.CanUseTelemetry); - } - else if (this.ParameterSetName == Helpers.Constants.ParameterSet.OpenConfigurationSetFromHistory) - { - configCommand.GetFromHistory( - this.InstanceIdentifier, - this.ModulePath, - this.ExecutionPolicy, - this.ProcessorPath, - this.CanUseTelemetry); - } - else if (this.ParameterSetName == Helpers.Constants.ParameterSet.OpenAllConfigurationSetsFromHistory) - { - configCommand.GetAllFromHistory( - this.ModulePath, - this.ExecutionPolicy, - this.ProcessorPath, - this.CanUseTelemetry); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Cmdlets.Common; + using Microsoft.WinGet.Configuration.Engine.Commands; + + /// + /// Get-WinGetConfiguration. + /// Opens a configuration set. + /// + [Cmdlet(VerbsCommon.Get, "WinGetConfiguration", DefaultParameterSetName = Helpers.Constants.ParameterSet.OpenConfigurationSetFromFile)] + [Alias("gwgc")] + public sealed class GetWinGetConfigurationCmdlet : OpenConfiguration + { + /// + /// Opens the configuration set. + /// + protected override void ProcessRecord() + { + var configCommand = new ConfigurationCommand(this); + + if (this.ParameterSetName == Helpers.Constants.ParameterSet.OpenConfigurationSetFromFile) + { + configCommand.Get( + this.File, + this.ModulePath, + this.ExecutionPolicy, + this.ProcessorPath, + this.CanUseTelemetry); + } + else if (this.ParameterSetName == Helpers.Constants.ParameterSet.OpenConfigurationSetFromHistory) + { + configCommand.GetFromHistory( + this.InstanceIdentifier, + this.ModulePath, + this.ExecutionPolicy, + this.ProcessorPath, + this.CanUseTelemetry); + } + else if (this.ParameterSetName == Helpers.Constants.ParameterSet.OpenAllConfigurationSetsFromHistory) + { + configCommand.GetAllFromHistory( + this.ModulePath, + this.ExecutionPolicy, + this.ProcessorPath, + this.CanUseTelemetry); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationDetailsCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationDetailsCmdlet.cs index a567637210..5825323ca9 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationDetailsCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/GetWinGetConfigurationDetailsCmdlet.cs @@ -1,54 +1,54 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using System.Threading; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Get-WinGetConfigurationDetails. - /// Gets the details for the units in a configuration set. - /// - [Cmdlet(VerbsCommon.Get, "WinGetConfigurationDetails")] - [Alias("gwgcd")] - public sealed class GetWinGetConfigurationDetailsCmdlet : PSCmdlet - { - private ConfigurationCommand runningCommand = null; - - /// - /// Gets or sets the configuration set. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationSet Set { get; set; } - - /// - /// Starts configuration and wait for it to complete. - /// - protected override void ProcessRecord() - { - this.runningCommand = new ConfigurationCommand(this); - this.runningCommand.GetDetails(this.Set); - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.runningCommand != null) - { - this.runningCommand.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using System.Threading; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Get-WinGetConfigurationDetails. + /// Gets the details for the units in a configuration set. + /// + [Cmdlet(VerbsCommon.Get, "WinGetConfigurationDetails")] + [Alias("gwgcd")] + public sealed class GetWinGetConfigurationDetailsCmdlet : PSCmdlet + { + private ConfigurationCommand runningCommand = null; + + /// + /// Gets or sets the configuration set. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationSet Set { get; set; } + + /// + /// Starts configuration and wait for it to complete. + /// + protected override void ProcessRecord() + { + this.runningCommand = new ConfigurationCommand(this); + this.runningCommand.GetDetails(this.Set); + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.runningCommand != null) + { + this.runningCommand.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/InvokeWinGetConfigurationCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/InvokeWinGetConfigurationCmdlet.cs index 1db374fea2..2b7ea9e97c 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/InvokeWinGetConfigurationCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/InvokeWinGetConfigurationCmdlet.cs @@ -1,73 +1,73 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System; - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Invoke-WinGetConfiguration. - /// Applies the configuration. - /// Wait for completion. - /// - [Cmdlet(VerbsLifecycle.Invoke, "WinGetConfiguration")] - [Alias("iwgc")] - public sealed class InvokeWinGetConfigurationCmdlet : PSCmdlet - { - private bool acceptedAgreements = false; - private ConfigurationCommand runningCommand = null; - - /// - /// Gets or sets the configuration set. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationSet Set { get; set; } - - /// - /// Gets or sets a value indicating whether to accept the configuration agreements. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AcceptConfigurationAgreements { get; set; } - - /// - /// Pre-processing operations. - /// - protected override void BeginProcessing() - { - this.acceptedAgreements = ConfigurationCommand.ConfirmConfigurationProcessing(this, this.AcceptConfigurationAgreements.ToBool(), true); - } - - /// - /// Starts to apply the configuration and wait for it to complete. - /// - protected override void ProcessRecord() - { - if (this.acceptedAgreements) - { - this.runningCommand = new ConfigurationCommand(this); - this.runningCommand.Apply(this.Set); - } - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.runningCommand != null) - { - this.runningCommand.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System; + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Invoke-WinGetConfiguration. + /// Applies the configuration. + /// Wait for completion. + /// + [Cmdlet(VerbsLifecycle.Invoke, "WinGetConfiguration")] + [Alias("iwgc")] + public sealed class InvokeWinGetConfigurationCmdlet : PSCmdlet + { + private bool acceptedAgreements = false; + private ConfigurationCommand runningCommand = null; + + /// + /// Gets or sets the configuration set. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationSet Set { get; set; } + + /// + /// Gets or sets a value indicating whether to accept the configuration agreements. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AcceptConfigurationAgreements { get; set; } + + /// + /// Pre-processing operations. + /// + protected override void BeginProcessing() + { + this.acceptedAgreements = ConfigurationCommand.ConfirmConfigurationProcessing(this, this.AcceptConfigurationAgreements.ToBool(), true); + } + + /// + /// Starts to apply the configuration and wait for it to complete. + /// + protected override void ProcessRecord() + { + if (this.acceptedAgreements) + { + this.runningCommand = new ConfigurationCommand(this); + this.runningCommand.Apply(this.Set); + } + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.runningCommand != null) + { + this.runningCommand.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/RemoveWinGetConfigurationHistoryCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/RemoveWinGetConfigurationHistoryCmdlet.cs index 7f69b91aaa..64fa426b5a 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/RemoveWinGetConfigurationHistoryCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/RemoveWinGetConfigurationHistoryCmdlet.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Remove-WinGetConfigurationHistory. - /// Removes the given configuration set from history. - /// - [Cmdlet(VerbsCommon.Remove, "WinGetConfigurationHistory")] - [Alias("rwgch")] - public sealed class RemoveWinGetConfigurationHistoryCmdlet : PSCmdlet - { - /// - /// Gets or sets the configuration set. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationSet Set { get; set; } - - /// - /// Removes the given set from history. - /// - protected override void ProcessRecord() - { - var configCommand = new ConfigurationCommand(this); - configCommand.Remove(this.Set); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Remove-WinGetConfigurationHistory. + /// Removes the given configuration set from history. + /// + [Cmdlet(VerbsCommon.Remove, "WinGetConfigurationHistory")] + [Alias("rwgch")] + public sealed class RemoveWinGetConfigurationHistoryCmdlet : PSCmdlet + { + /// + /// Gets or sets the configuration set. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationSet Set { get; set; } + + /// + /// Removes the given set from history. + /// + protected override void ProcessRecord() + { + var configCommand = new ConfigurationCommand(this); + configCommand.Remove(this.Set); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StartWinGetConfigurationCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StartWinGetConfigurationCmdlet.cs index 494cfc66d7..50002bd14f 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StartWinGetConfigurationCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StartWinGetConfigurationCmdlet.cs @@ -1,60 +1,60 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Start-WinGetConfiguration. - /// Start to apply the configuration. - /// Does not wait for completion. - /// - [Cmdlet(VerbsLifecycle.Start, "WinGetConfiguration")] - [Alias("sawgc")] - public sealed class StartWinGetConfigurationCmdlet : PSCmdlet - { - private bool acceptedAgreements = false; - - /// - /// Gets or sets the configuration set. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationSet Set { get; set; } - - /// - /// Gets or sets a value indicating whether to accept the configuration agreements. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AcceptConfigurationAgreements { get; set; } - - /// - /// Pre-processing operations. - /// - protected override void BeginProcessing() - { - this.acceptedAgreements = ConfigurationCommand.ConfirmConfigurationProcessing(this, this.AcceptConfigurationAgreements.ToBool(), true); - } - - /// - /// Starts to apply the configuration and wait for it to complete. - /// - protected override void ProcessRecord() - { - if (this.acceptedAgreements) - { - var configCommand = new ConfigurationCommand(this); - configCommand.StartApply(this.Set); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Start-WinGetConfiguration. + /// Start to apply the configuration. + /// Does not wait for completion. + /// + [Cmdlet(VerbsLifecycle.Start, "WinGetConfiguration")] + [Alias("sawgc")] + public sealed class StartWinGetConfigurationCmdlet : PSCmdlet + { + private bool acceptedAgreements = false; + + /// + /// Gets or sets the configuration set. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationSet Set { get; set; } + + /// + /// Gets or sets a value indicating whether to accept the configuration agreements. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AcceptConfigurationAgreements { get; set; } + + /// + /// Pre-processing operations. + /// + protected override void BeginProcessing() + { + this.acceptedAgreements = ConfigurationCommand.ConfirmConfigurationProcessing(this, this.AcceptConfigurationAgreements.ToBool(), true); + } + + /// + /// Starts to apply the configuration and wait for it to complete. + /// + protected override void ProcessRecord() + { + if (this.acceptedAgreements) + { + var configCommand = new ConfigurationCommand(this); + configCommand.StartApply(this.Set); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StopWinGetConfigurationCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StopWinGetConfigurationCmdlet.cs index da367b4fa2..84e3b5ac36 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StopWinGetConfigurationCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/StopWinGetConfigurationCmdlet.cs @@ -1,40 +1,40 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Stop-WinGetConfiguration. - /// Cancels a configuration previously started by Start-WinGetConfiguration. - /// - [Cmdlet(VerbsLifecycle.Stop, "WinGetConfiguration")] - [Alias("spwgc")] - public sealed class StopWinGetConfigurationCmdlet : PSCmdlet - { - /// - /// Gets or sets the configuration task. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationJob ConfigurationJob { get; set; } - - /// - /// Starts to apply the configuration and wait for it to complete. - /// - protected override void ProcessRecord() - { - var configCommand = new ConfigurationCommand(this); - configCommand.Cancel(this.ConfigurationJob); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Stop-WinGetConfiguration. + /// Cancels a configuration previously started by Start-WinGetConfiguration. + /// + [Cmdlet(VerbsLifecycle.Stop, "WinGetConfiguration")] + [Alias("spwgc")] + public sealed class StopWinGetConfigurationCmdlet : PSCmdlet + { + /// + /// Gets or sets the configuration task. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationJob ConfigurationJob { get; set; } + + /// + /// Starts to apply the configuration and wait for it to complete. + /// + protected override void ProcessRecord() + { + var configCommand = new ConfigurationCommand(this); + configCommand.Cancel(this.ConfigurationJob); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/TestWinGetConfigurationCmdlet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/TestWinGetConfigurationCmdlet.cs index 772c132320..3692c7413d 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/TestWinGetConfigurationCmdlet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Cmdlets/TestWinGetConfigurationCmdlet.cs @@ -1,71 +1,71 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Cmdlets -{ - using System.Management.Automation; - using Microsoft.WinGet.Configuration.Engine.Commands; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Test-WinGetConfiguration - /// Tests configuration. - /// - [Cmdlet(VerbsDiagnostic.Test, "WinGetConfiguration")] - [Alias("twgc")] - public class TestWinGetConfigurationCmdlet : PSCmdlet - { - private bool acceptedAgreements = false; - private ConfigurationCommand runningCommand = null; - - /// - /// Gets or sets the configuration set. - /// - [Parameter( - Position = 0, - Mandatory = true, - ValueFromPipeline = true, - ValueFromPipelineByPropertyName = true)] - public PSConfigurationSet Set { get; set; } - - /// - /// Gets or sets a value indicating whether to accept the configuration agreements. - /// - [Parameter(ValueFromPipelineByPropertyName = true)] - public SwitchParameter AcceptConfigurationAgreements { get; set; } - - /// - /// Pre-processing operations. - /// - protected override void BeginProcessing() - { - this.acceptedAgreements = ConfigurationCommand.ConfirmConfigurationProcessing(this, this.AcceptConfigurationAgreements.ToBool(), false); - } - - /// - /// Test configuration. - /// - protected override void ProcessRecord() - { - if (this.acceptedAgreements) - { - this.runningCommand = new ConfigurationCommand(this); - this.runningCommand.Test(this.Set); - } - } - - /// - /// Interrupts currently running code within the command. - /// - protected override void StopProcessing() - { - if (this.runningCommand != null) - { - this.runningCommand.Cancel(); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Cmdlets +{ + using System.Management.Automation; + using Microsoft.WinGet.Configuration.Engine.Commands; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Test-WinGetConfiguration + /// Tests configuration. + /// + [Cmdlet(VerbsDiagnostic.Test, "WinGetConfiguration")] + [Alias("twgc")] + public class TestWinGetConfigurationCmdlet : PSCmdlet + { + private bool acceptedAgreements = false; + private ConfigurationCommand runningCommand = null; + + /// + /// Gets or sets the configuration set. + /// + [Parameter( + Position = 0, + Mandatory = true, + ValueFromPipeline = true, + ValueFromPipelineByPropertyName = true)] + public PSConfigurationSet Set { get; set; } + + /// + /// Gets or sets a value indicating whether to accept the configuration agreements. + /// + [Parameter(ValueFromPipelineByPropertyName = true)] + public SwitchParameter AcceptConfigurationAgreements { get; set; } + + /// + /// Pre-processing operations. + /// + protected override void BeginProcessing() + { + this.acceptedAgreements = ConfigurationCommand.ConfirmConfigurationProcessing(this, this.AcceptConfigurationAgreements.ToBool(), false); + } + + /// + /// Test configuration. + /// + protected override void ProcessRecord() + { + if (this.acceptedAgreements) + { + this.runningCommand = new ConfigurationCommand(this); + this.runningCommand.Test(this.Set); + } + } + + /// + /// Interrupts currently running code within the command. + /// + protected override void StopProcessing() + { + if (this.runningCommand != null) + { + this.runningCommand.Cancel(); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Constants.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Constants.cs index 4ad1e0b463..07b074a335 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Constants.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Constants.cs @@ -1,24 +1,24 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Helpers -{ - /// - /// Constants. - /// - internal static class Constants - { -#pragma warning disable SA1600 // ElementsMustBeDocumented - internal static class ParameterSet - { - internal const string OpenConfigurationSetFromFile = "OpenConfigurationSetFromFile"; - internal const string OpenConfigurationSetFromString = "OpenConfigurationSetFromString"; - internal const string OpenConfigurationSetFromHistory = "OpenConfigurationSetFromHistory"; - internal const string OpenAllConfigurationSetsFromHistory = "OpenAllConfigurationSetsFromHistory"; - } -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Helpers +{ + /// + /// Constants. + /// + internal static class Constants + { +#pragma warning disable SA1600 // ElementsMustBeDocumented + internal static class ParameterSet + { + internal const string OpenConfigurationSetFromFile = "OpenConfigurationSetFromFile"; + internal const string OpenConfigurationSetFromString = "OpenConfigurationSetFromString"; + internal const string OpenConfigurationSetFromHistory = "OpenConfigurationSetFromHistory"; + internal const string OpenAllConfigurationSetsFromHistory = "OpenAllConfigurationSetsFromHistory"; + } +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Utilities.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Utilities.cs index b0092d28df..6b467b3085 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Utilities.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Helpers/Utilities.cs @@ -1,73 +1,73 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Helpers -{ - using System; - using System.Linq; - using System.Management.Automation; - using Microsoft.PowerShell; - - /// - /// Utilities for this cmdlets. - /// - internal static class Utilities - { - /// - /// Gets the execution policy. - /// - /// ExecutionPolicy. - public static ExecutionPolicy GetExecutionPolicy() - { - var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); - return ps.AddCommand("Get-ExecutionPolicy").Invoke().First(); - } - - /// - /// Determine if telemetry can be used. It follows the same telemetry rules as PowerShell. - /// To opt-out of this telemetry, set the environment variable $env:POWERSHELL_TELEMETRY_OPTOUT to true, yes, or 1. - /// This method is the same as GetEnvironmentVariableAsBool from PowerShell but only for POWERSHELL_TELEMETRY_OPTOUT. - /// - /// If telemetry can be used. - public static bool CanUseTelemetry() - { - var str = Environment.GetEnvironmentVariable("POWERSHELL_TELEMETRY_OPTOUT"); - if (string.IsNullOrEmpty(str)) - { - return true; - } - - var boolStr = str.AsSpan(); - - if (boolStr.Length == 1) - { - if (boolStr[0] == '1') - { - return false; - } - } - - if (boolStr.Length == 3 && - (boolStr[0] == 'y' || boolStr[0] == 'Y') && - (boolStr[1] == 'e' || boolStr[1] == 'E') && - (boolStr[2] == 's' || boolStr[2] == 'S')) - { - return false; - } - - if (boolStr.Length == 4 && - (boolStr[0] == 't' || boolStr[0] == 'T') && - (boolStr[1] == 'r' || boolStr[1] == 'R') && - (boolStr[2] == 'u' || boolStr[2] == 'U') && - (boolStr[3] == 'e' || boolStr[3] == 'E')) - { - return false; - } - - return true; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Helpers +{ + using System; + using System.Linq; + using System.Management.Automation; + using Microsoft.PowerShell; + + /// + /// Utilities for this cmdlets. + /// + internal static class Utilities + { + /// + /// Gets the execution policy. + /// + /// ExecutionPolicy. + public static ExecutionPolicy GetExecutionPolicy() + { + var ps = PowerShell.Create(RunspaceMode.CurrentRunspace); + return ps.AddCommand("Get-ExecutionPolicy").Invoke().First(); + } + + /// + /// Determine if telemetry can be used. It follows the same telemetry rules as PowerShell. + /// To opt-out of this telemetry, set the environment variable $env:POWERSHELL_TELEMETRY_OPTOUT to true, yes, or 1. + /// This method is the same as GetEnvironmentVariableAsBool from PowerShell but only for POWERSHELL_TELEMETRY_OPTOUT. + /// + /// If telemetry can be used. + public static bool CanUseTelemetry() + { + var str = Environment.GetEnvironmentVariable("POWERSHELL_TELEMETRY_OPTOUT"); + if (string.IsNullOrEmpty(str)) + { + return true; + } + + var boolStr = str.AsSpan(); + + if (boolStr.Length == 1) + { + if (boolStr[0] == '1') + { + return false; + } + } + + if (boolStr.Length == 3 && + (boolStr[0] == 'y' || boolStr[0] == 'Y') && + (boolStr[1] == 'e' || boolStr[1] == 'E') && + (boolStr[2] == 's' || boolStr[2] == 'S')) + { + return false; + } + + if (boolStr.Length == 4 && + (boolStr[0] == 't' || boolStr[0] == 'T') && + (boolStr[1] == 'r' || boolStr[1] == 'R') && + (boolStr[2] == 'u' || boolStr[2] == 'U') && + (boolStr[3] == 'e' || boolStr[3] == 'E')) + { + return false; + } + + return true; + } + } +} 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 8eeb85c0a5..7426888cd2 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 @@ -1,93 +1,93 @@ - - - - - net8.0-windows10.0.26100 - false - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - true - $(OutputPath)\$(MSBuildProjectName).xml - win - $(SolutionDir)$(Platform)\$(Configuration)\ - Microsoft.WinGet.Configuration - Debug;Release;ReleaseStatic - - - - true - - - - true - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - $(BuildOutputDirectory)PowerShell\Microsoft.WinGet.Configuration - $(PowerShellModuleOutputDirectory)\SharedDependencies - $(PowerShellModuleOutputDirectory)\DirectDependencies - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + net8.0-windows10.0.26100 + false + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + true + $(OutputPath)\$(MSBuildProjectName).xml + win + $(SolutionDir)$(Platform)\$(Configuration)\ + Microsoft.WinGet.Configuration + Debug;Release;ReleaseStatic + + + + true + + + + true + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + $(BuildOutputDirectory)PowerShell\Microsoft.WinGet.Configuration + $(PowerShellModuleOutputDirectory)\SharedDependencies + $(PowerShellModuleOutputDirectory)\DirectDependencies + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Properties/AssemblyInfo.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Properties/AssemblyInfo.cs index d9d343a224..bf84c41824 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Properties/AssemblyInfo.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Properties/AssemblyInfo.cs @@ -1,16 +1,16 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -#if NET - -using System.Runtime.Versioning; - -// Forcibly set the target and supported platforms due to the internal build setup. -// Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.26100.0")] -[assembly: SupportedOSPlatform("Windows10.0.18362.0")] - -#endif +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +#if NET + +using System.Runtime.Versioning; + +// Forcibly set the target and supported platforms due to the internal build setup. +// Keep in sync with project versions. +[assembly: TargetPlatform("Windows10.0.26100.0")] +[assembly: SupportedOSPlatform("Windows10.0.18362.0")] + +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Resolver/ModuleInit.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Resolver/ModuleInit.cs index 0174b29efb..4739807496 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Resolver/ModuleInit.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Cmdlets/Resolver/ModuleInit.cs @@ -1,43 +1,43 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Resolver -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using System.Runtime.InteropServices; - using System.Runtime.Loader; - - /// - /// Initialization class for this module. - /// - public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup - { - private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64, Architecture.Arm64 }; - - /// - public void OnImport() - { - var arch = RuntimeInformation.ProcessArchitecture; - if (!ValidArchs.Contains(arch)) - { - throw new NotSupportedException(arch.ToString()); - } - - AssemblyLoadContext.Default.Resolving += WinGetAssemblyLoadContext.ResolvingHandler; - AssemblyLoadContext.Default.ResolvingUnmanagedDll += WinGetAssemblyLoadContext.ResolvingUnmanagedDllHandler; - } - - /// - public void OnRemove(PSModuleInfo module) - { - AssemblyLoadContext.Default.ResolvingUnmanagedDll -= WinGetAssemblyLoadContext.ResolvingUnmanagedDllHandler; - AssemblyLoadContext.Default.Resolving -= WinGetAssemblyLoadContext.ResolvingHandler; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Resolver +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using System.Runtime.InteropServices; + using System.Runtime.Loader; + + /// + /// Initialization class for this module. + /// + public class ModuleInit : IModuleAssemblyInitializer, IModuleAssemblyCleanup + { + private static readonly IEnumerable ValidArchs = new Architecture[] { Architecture.X86, Architecture.X64, Architecture.Arm64 }; + + /// + public void OnImport() + { + var arch = RuntimeInformation.ProcessArchitecture; + if (!ValidArchs.Contains(arch)) + { + throw new NotSupportedException(arch.ToString()); + } + + AssemblyLoadContext.Default.Resolving += WinGetAssemblyLoadContext.ResolvingHandler; + AssemblyLoadContext.Default.ResolvingUnmanagedDll += WinGetAssemblyLoadContext.ResolvingUnmanagedDllHandler; + } + + /// + public void OnRemove(PSModuleInfo module) + { + AssemblyLoadContext.Default.ResolvingUnmanagedDll -= WinGetAssemblyLoadContext.ResolvingUnmanagedDllHandler; + AssemblyLoadContext.Default.Resolving -= WinGetAssemblyLoadContext.ResolvingHandler; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs index 6134f582f8..cc9d79f62c 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Commands/ConfigurationCommand.cs @@ -1,735 +1,735 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Commands -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Management.Automation; - using System.Management.Automation.Runspaces; - using System.Text; - using System.Threading.Tasks; - using Microsoft.Management.Configuration; - using Microsoft.Management.Configuration.Processor; - using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; - using Microsoft.PowerShell; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Configuration.Engine.Exceptions; - using Microsoft.WinGet.Configuration.Engine.Helpers; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - using Microsoft.WinGet.Resources; - using Microsoft.WinGet.SharedLib.Exceptions; - using Microsoft.WinGet.SharedLib.PolicySettings; - using Windows.Storage; - using Windows.Storage.Streams; - using WinRT; - - /// - /// Class that deals configuration commands. - /// - public sealed class ConfigurationCommand : PowerShellCmdlet - { - private const string ProcessorEngineDSCv3 = "dscv3"; - private const string ProcessorEnginePowerShell = "pwsh"; - - private const string DSCv3FactoryMapKeyDscExecutablePath = "DscExecutablePath"; - private const string DSCv3FactoryMapKeyFoundDscExecutablePath = "FoundDscExecutablePath"; - private const string DSCv3FactoryMapKeyFindDscStateMachine = "FindDscStateMachine"; - - private const string WinGetClientModule = "Microsoft.WinGet.Client"; - private const string StableDSCv3PackageId = "9NVTPZWRC6KQ"; - private const string PreviewDSCv3PackageId = "9PCX3HX4HZ0Z"; - - /// - /// Initializes a new instance of the class. - /// - /// PSCmdlet. - public ConfigurationCommand(PSCmdlet psCmdlet) - : base(psCmdlet, new HashSet { Policy.WinGet, Policy.Configuration, Policy.WinGetCommandLineInterfaces }) - { - } - - /// - /// Verify user accept agreements. - /// - /// PSCmdlet. - /// Has already accepted. - /// If prompt is for apply. - /// If accepted. - public static bool ConfirmConfigurationProcessing(PSCmdlet psCmdlet, bool hasAccepted, bool isApply) - { - bool result = false; - if (!hasAccepted) - { - var prompt = isApply ? Resources.ConfigurationWarningPromptApply : Resources.ConfigurationWarningPromptTest; - bool yesToAll = false; - bool noToAll = false; - result = psCmdlet.ShouldContinue(prompt, Resources.ConfigurationWarning, true, ref yesToAll, ref noToAll); - - if (yesToAll) - { - result = true; - } - else if (noToAll) - { - result = false; - } - } - else - { - // This way even if they set WarningActionPreference.Ignore we will still print the - // warning message if the agreements didn't get accepted. - psCmdlet.WriteWarning(Resources.ConfigurationWarning); - result = true; - } - - return result; - } - - /// - /// Open a configuration set. - /// - /// Configuration file path. - /// The module path to use. - /// Execution policy. - /// The processor path to use. - /// If telemetry can be used. - public void Get( - string configFile, - string modulePath, - ExecutionPolicy executionPolicy, - string processorPath, - bool canUseTelemetry) - { - var openParams = new OpenConfigurationParameters( - this, configFile, modulePath, executionPolicy, processorPath, canUseTelemetry); - - // Start task. - var runningTask = this.RunOnMTA( - async () => - { - return (await this.OpenConfigurationSetAsync(openParams)) !; - }); - - this.Wait(runningTask); - this.Write(StreamType.Object, runningTask.Result); - } - - /// - /// Open a configuration set from history. - /// - /// Instance identifier. - /// The module path to use. - /// Execution policy. - /// The processor path to use. - /// If telemetry can be used. - public void GetFromHistory( - string instanceIdentifier, - string modulePath, - ExecutionPolicy executionPolicy, - string processorPath, - bool canUseTelemetry) - { - var openParams = new OpenConfigurationParameters( - this, instanceIdentifier, modulePath, executionPolicy, processorPath, canUseTelemetry, fromHistory: true); - - // Start task. - var runningTask = this.RunOnMTA( - async () => - { - return await this.OpenConfigurationSetAsync(openParams); - }); - - this.Wait(runningTask); - if (runningTask.Result != null) - { - this.Write(StreamType.Object, runningTask.Result); - } - } - - /// - /// Opens all configuration sets from history. - /// - /// The module path to use. - /// Execution policy. - /// The processor path to use. - /// If telemetry can be used. - public void GetAllFromHistory( - string modulePath, - ExecutionPolicy executionPolicy, - string processorPath, - bool canUseTelemetry) - { - var openParams = new OpenConfigurationParameters( - this, modulePath, executionPolicy, processorPath, canUseTelemetry); - - // Start task. - var runningTask = this.RunOnMTA( - async () => - { - return await this.GetConfigurationSetHistoryAsync(openParams); - }); - - this.Wait(runningTask); - this.Write(StreamType.Object, runningTask.Result); - } - - /// - /// Gets the details of a configuration set. - /// - /// PSConfigurationSet. - public void GetDetails(PSConfigurationSet psConfigurationSet) - { - psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); - - if (!psConfigurationSet.HasDetails) - { - if (!psConfigurationSet.CanProcess()) - { - throw new InvalidOperationException(); - } - - var runningTask = this.RunOnMTA( - async () => - { - try - { - psConfigurationSet = await this.GetSetDetailsAsync(psConfigurationSet, false); - } - finally - { - psConfigurationSet.DoneProcessing(); - } - - return psConfigurationSet; - }); - - this.Wait(runningTask); - psConfigurationSet = runningTask.Result; - } - else - { - this.Write(StreamType.Warning, "Details already obtained for this set"); - } - - this.Write(StreamType.Object, psConfigurationSet); - } - - /// - /// Starts configuration. - /// - /// PSConfigurationSet. - public void StartApply(PSConfigurationSet psConfigurationSet) - { - // if (psConfigurationSet.Set.State == ConfigurationSetState.Completed) - if (psConfigurationSet.ApplyCompleted) - { - this.Write(StreamType.Warning, "Processing this set is completed"); - throw new InvalidOperationException(); - } - - if (!psConfigurationSet.CanProcess()) - { - throw new InvalidOperationException(); - } - - var configurationJob = this.StartApplyInternal(psConfigurationSet); - this.Write(StreamType.Object, configurationJob); - } - - /// - /// Applies configuration. - /// - /// PSConfigurationSet. - public void Apply(PSConfigurationSet psConfigurationSet) => this.ContinueHelper(this.StartApplyInternal(psConfigurationSet)); - - /// - /// Continue a configuration job. - /// - /// The configuration job. - public void Continue(PSConfigurationJob psConfigurationJob) - { - if (psConfigurationJob.ApplyConfigurationTask.IsCompleted) - { - // It is safe to print all output. - psConfigurationJob.StartCommand.ConsumeAndWriteStreams(this); - - this.Write(StreamType.Verbose, "The task was completed before waiting"); - if (psConfigurationJob.ApplyConfigurationTask.IsCompletedSuccessfully) - { - this.Write(StreamType.Verbose, "Completed successfully"); - this.Write(StreamType.Object, psConfigurationJob.ApplyConfigurationTask.Result); - return; - } - else if (psConfigurationJob.ApplyConfigurationTask.IsFaulted) - { - this.Write(StreamType.Verbose, "Completed faulted before waiting"); - - // Maybe just write error? - throw psConfigurationJob.ApplyConfigurationTask.Exception!; - } - } - - this.ContinueHelper(psConfigurationJob); - } - - /// - /// Test configuration. - /// - /// PSConfigurationSet. - public void Test(PSConfigurationSet psConfigurationSet) - { - psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); - - if (!psConfigurationSet.CanProcess()) - { - throw new InvalidOperationException(); - } - - var runningTask = this.RunOnMTA( - async () => - { - try - { - return await this.TestConfigurationAsync(psConfigurationSet); - } - finally - { - psConfigurationSet.DoneProcessing(); - } - }); - - this.Wait(runningTask); - this.Write(StreamType.Object, runningTask.Result); - } - - /// - /// Validates configuration. - /// - /// PSConfigurationSet. - public void Validate(PSConfigurationSet psConfigurationSet) - { - psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); - - if (!psConfigurationSet.CanProcess()) - { - throw new InvalidOperationException(); - } - - var runningTask = this.RunOnMTA( - async () => - { - try - { - var setResult = await this.ApplyConfigurationAsync(psConfigurationSet, ApplyConfigurationSetFlags.PerformConsistencyCheckOnly); - return new PSValidateConfigurationSetResult(setResult); - } - finally - { - psConfigurationSet.DoneProcessing(); - } - }); - - this.Wait(runningTask); - this.Write(StreamType.Object, runningTask.Result); - } - - /// - /// Cancels a configuration job. - /// - /// PSConfiguration job. - public void Cancel(PSConfigurationJob psConfigurationJob) - { - psConfigurationJob.StartCommand.Cancel(); - } - - /// - /// Removes a configuration set from history. - /// - /// PSConfiguration set. - public void Remove(PSConfigurationSet psConfigurationSet) - { - psConfigurationSet.Set.Remove(); - } - - /// - /// Serializes a configuration set and outputs the string. - /// - /// PSConfiguration set. - public void Serialize(PSConfigurationSet psConfigurationSet) - { - // Start task. - var result = this.RunOnMTA( - () => - { - return this.SerializeMTA(psConfigurationSet); - }); - - this.Write(StreamType.Object, result); - } - - private void ContinueHelper(PSConfigurationJob psConfigurationJob) - { - // Signal the command that it can write to streams and wait for task. - this.Write(StreamType.Verbose, "Waiting for task to complete"); - psConfigurationJob.StartCommand.Wait(psConfigurationJob.ApplyConfigurationTask, this); - this.Write(StreamType.Object, psConfigurationJob.ApplyConfigurationTask.Result); - } - - private IConfigurationSetProcessorFactory CreatePowerShellProcessorFactory(OpenConfigurationParameters openParams) - { - var factory = new PowerShellConfigurationSetProcessorFactory(); - - var properties = factory.As(); - properties.Policy = openParams.Policy; - properties.ProcessorType = PowerShellConfigurationProcessorType.Default; - properties.Location = openParams.Location; - if (properties.Location == PowerShellConfigurationProcessorLocation.Custom) - { - properties.CustomLocation = openParams.CustomLocation; - } - - return factory; - } - - private async Task CreateDSCv3ProcessorFactory(OpenConfigurationParameters openParams) - { - var factory = new DSCv3ConfigurationSetProcessorFactory(); - - var factoryMap = factory.As>(); - if (!string.IsNullOrEmpty(openParams.ProcessorPath)) - { - if (!GroupPolicy.GetInstance().IsEnabled(Policy.ConfigurationProcessorPath)) - { - throw new GroupPolicyException(Policy.ConfigurationProcessorPath, GroupPolicyFailureType.BlockedByPolicy); - } - - factoryMap.Add(DSCv3FactoryMapKeyDscExecutablePath, openParams.ProcessorPath); - } - else - { - while (true) - { - string? nextTransition = null; - factoryMap.TryGetValue(DSCv3FactoryMapKeyFindDscStateMachine, out nextTransition); - - if (nextTransition == "Found") - { - break; - } - else if (nextTransition == "InstallStable") - { - this.Write(StreamType.Verbose, "Installing stable DSC..."); - await this.InstallDSCv3Package(openParams, StableDSCv3PackageId); - } - else if (nextTransition == "InstallPreview") - { - this.Write(StreamType.Verbose, "Installing preview DSC..."); - await this.InstallDSCv3Package(openParams, PreviewDSCv3PackageId); - } - else if (nextTransition == "NotFound") - { - this.Write(StreamType.Warning, Resources.ConfigurationInstallDscPackageFailed); - throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); - } - else - { - this.Write(StreamType.Warning, $"Unrecognized value from FindDscStateMachine: {nextTransition ?? ""}"); - throw new InvalidOperationException($"Internal error: Unrecognized value from FindDscStateMachine: {nextTransition ?? ""}"); - } - } - } - - return factory; - } - - private async Task InstallDSCv3Package(OpenConfigurationParameters openParams, string productId) - { - this.Write(StreamType.Information, Resources.ConfigurationInstallDscPackage); - - InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); - initialSessionState.ExecutionPolicy = openParams.ExecutionPolicy; - Runspace runspace = RunspaceFactory.CreateRunspace(initialSessionState); - runspace.Open(); - PowerShell installDSCv3 = PowerShell.Create(runspace).AddScript( - $@" - if (-not (Get-Module -ListAvailable -Name {WinGetClientModule})) - {{ - Install-Module -Name {WinGetClientModule} -Confirm:$False -Force - }} - - $installResult = Install-WingetPackage -Id {productId} -Source msstore - if ($installResult.Status -ne 'Ok') - {{ - Write-Error ""Failed to install DSCv3 package. Status: $($installResult.Status). ExtendedErrorCode: $($installResult.ExtendedErrorCode)."" - }} - "); - - await installDSCv3.InvokeAsync(); - - if (installDSCv3.HadErrors) - { - this.Write(StreamType.Verbose, installDSCv3.GetErrorMessage() ?? ""); - this.Write(StreamType.Warning, Resources.ConfigurationInstallDscPackageFailed); - throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); - } - } - - private async Task CreateConfigurationProcessorWithSet(OpenConfigurationParameters openParams, ConfigurationSet set) - { - string processorIdentifier = set.Environment.ProcessorIdentifier; - - if (string.IsNullOrEmpty(processorIdentifier) || ProcessorEnginePowerShell.Equals(processorIdentifier, StringComparison.OrdinalIgnoreCase)) - { - // Default to PowerShell - return new PSConfigurationProcessor(this.CreatePowerShellProcessorFactory(openParams), this, openParams.CanUseTelemetry); - } - else if (ProcessorEngineDSCv3.Equals(processorIdentifier, StringComparison.OrdinalIgnoreCase)) - { - return new PSConfigurationProcessor(await this.CreateDSCv3ProcessorFactory(openParams), this, openParams.CanUseTelemetry); - } - else - { - throw new NotSupportedException(string.Format(Resources.ProcessorEngineNotSupported, processorIdentifier)); - } - } - - private async Task OpenConfigurationSetAsync(OpenConfigurationParameters openParams) - { - this.Write(StreamType.Verbose, Resources.ConfigurationInitializing); - - var processorWithoutFactory = new PSConfigurationProcessor(null, this, openParams.CanUseTelemetry); - - if (!openParams.FromHistory) - { - this.Write(StreamType.Verbose, Resources.ConfigurationReadingConfigFile); - var stream = await FileRandomAccessStream.OpenAsync(openParams.ConfigFile, FileAccessMode.Read); - - OpenConfigurationSetResult openResult = await processorWithoutFactory.Processor.OpenConfigurationSetAsync(stream); - if (openResult.ResultCode != null) - { - throw new OpenConfigurationSetException(openResult, openParams.ConfigFile); - } - - var set = openResult.Set; - - // This should match winget's OpenConfigurationSet or OpenConfigurationSetAsync - // should be modify to take the full path and handle it. - set.Name = Path.GetFileName(openParams.ConfigFile); - set.Origin = Path.GetDirectoryName(openParams.ConfigFile); - set.Path = openParams.ConfigFile; - - return new PSConfigurationSet(await this.CreateConfigurationProcessorWithSet(openParams, set), set); - } - else - { - Guid instanceIdentifier = Guid.Parse(openParams.ConfigFile); - - this.Write(StreamType.Verbose, Resources.ConfigurationReadingConfigHistory); - - var historySets = await processorWithoutFactory.Processor.GetConfigurationHistoryAsync(); - - ConfigurationSet? result = null; - foreach (var historySet in historySets) - { - if (historySet.InstanceIdentifier == instanceIdentifier) - { - result = historySet; - break; - } - } - - return result != null ? new PSConfigurationSet(await this.CreateConfigurationProcessorWithSet(openParams, result), result) : null; - } - } - - private async Task GetConfigurationSetHistoryAsync(OpenConfigurationParameters openParams) - { - this.Write(StreamType.Verbose, Resources.ConfigurationInitializing); - - var processorWithoutFactory = new PSConfigurationProcessor(null, this, openParams.CanUseTelemetry); - - this.Write(StreamType.Verbose, Resources.ConfigurationReadingConfigHistory); - - var historySets = await processorWithoutFactory.Processor.GetConfigurationHistoryAsync(); - - PSConfigurationSet[] result = new PSConfigurationSet[historySets.Count]; - for (int i = 0; i < historySets.Count; ++i) - { - result[i] = new PSConfigurationSet(await this.CreateConfigurationProcessorWithSet(openParams, historySets[i]), historySets[i]); - } - - return result; - } - - private PSConfigurationJob StartApplyInternal(PSConfigurationSet psConfigurationSet) - { - psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); - - var runningTask = this.RunOnMTA( - async () => - { - try - { - var setResult = await this.ApplyConfigurationAsync(psConfigurationSet, ApplyConfigurationSetFlags.None); - psConfigurationSet.ApplyCompleted = true; - return new PSApplyConfigurationSetResult(setResult); - } - finally - { - psConfigurationSet.DoneProcessing(); - } - }); - - return new PSConfigurationJob(runningTask, this); - } - - private async Task ApplyConfigurationAsync(PSConfigurationSet psConfigurationSet, ApplyConfigurationSetFlags flags) - { - if (!psConfigurationSet.HasDetails) - { - this.Write(StreamType.Verbose, "Getting details for configuration set"); - await this.GetSetDetailsAsync(psConfigurationSet, true); - } - - var processor = psConfigurationSet.PsProcessor.Processor; - var set = psConfigurationSet.Set; - - var applyProgressOutput = new ApplyConfigurationSetProgressOutput( - this, - this.GetNewProgressActivityId(), - Resources.ConfigurationApply, - Resources.OperationInProgress, - Resources.OperationCompleted, - set.Units.Count); - - var applyTask = processor.ApplySetAsync(set, flags); - applyTask.Progress = applyProgressOutput.Progress; - - try - { - var result = await applyTask.AsTask(this.GetCancellationToken()); - applyProgressOutput.HandleProgress(result); - return result; - } - finally - { - applyProgressOutput.CompleteProgress(); - } - } - - private async Task TestConfigurationAsync(PSConfigurationSet psConfigurationSet) - { - if (!psConfigurationSet.HasDetails) - { - this.Write(StreamType.Verbose, "Getting details for configuration set"); - await this.GetSetDetailsAsync(psConfigurationSet, true); - } - - var processor = psConfigurationSet.PsProcessor.Processor; - var set = psConfigurationSet.Set; - - var testProgressOutput = new TestConfigurationSetProgressOutput( - this, - this.GetNewProgressActivityId(), - Resources.ConfigurationAssert, - Resources.OperationInProgress, - Resources.OperationCompleted, - set.Units.Count); - - var testTask = processor.TestSetAsync(set); - testTask.Progress = testProgressOutput.Progress; - - try - { - var result = await testTask.AsTask(this.GetCancellationToken()); - testProgressOutput.HandleProgress(result); - - return new PSTestConfigurationSetResult(result); - } - finally - { - testProgressOutput.CompleteProgress(); - } - } - - private async Task GetSetDetailsAsync(PSConfigurationSet psConfigurationSet, bool warnOnError) - { - var processor = psConfigurationSet.PsProcessor.Processor; - var set = psConfigurationSet.Set; - var totalUnitsCount = set.Units.Count; - - if (totalUnitsCount == 0) - { - this.Write(StreamType.Warning, Resources.ConfigurationFileEmpty); - return psConfigurationSet; - } - - try - { - var detailsProgressOutput = new GetConfigurationSetDetailsProgressOutput( - this, - this.GetNewProgressActivityId(), - Resources.ConfigurationGettingDetails, - Resources.OperationInProgress, - Resources.OperationCompleted, - totalUnitsCount); - - var detailsTask = processor.GetSetDetailsAsync(set, ConfigurationUnitDetailFlags.ReadOnly); - detailsTask.Progress = detailsProgressOutput.Progress; - - try - { - var result = await detailsTask.AsTask(this.GetCancellationToken()); - detailsProgressOutput.HandleProgress(result); - - if (result.UnitResults.Where(u => u.ResultInformation.ResultCode != null).Any()) - { - throw new GetDetailsException(result.UnitResults); - } - - if (detailsProgressOutput.UnitsShown == 0) - { - throw new GetDetailsException(); - } - - psConfigurationSet.HasDetails = true; - } - finally - { - detailsProgressOutput.CompleteProgress(); - } - } - catch (Exception e) - { - if (warnOnError) - { - this.Write(StreamType.Warning, e.Message); - } - else - { - throw; - } - } - - return psConfigurationSet; - } - - /// - /// Serializes a configuration set and outputs the string. - /// - /// PSConfiguration set. - /// The string version of the set. - private string SerializeMTA(PSConfigurationSet psConfigurationSet) - { - MemoryStream stream = new MemoryStream(); - psConfigurationSet.Set.Serialize(stream.AsOutputStream()); - return Encoding.UTF8.GetString(stream.ToArray()); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Commands +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Runspaces; + using System.Text; + using System.Threading.Tasks; + using Microsoft.Management.Configuration; + using Microsoft.Management.Configuration.Processor; + using Microsoft.Management.Configuration.Processor.PowerShell.Extensions; + using Microsoft.PowerShell; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Configuration.Engine.Exceptions; + using Microsoft.WinGet.Configuration.Engine.Helpers; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + using Microsoft.WinGet.Resources; + using Microsoft.WinGet.SharedLib.Exceptions; + using Microsoft.WinGet.SharedLib.PolicySettings; + using Windows.Storage; + using Windows.Storage.Streams; + using WinRT; + + /// + /// Class that deals configuration commands. + /// + public sealed class ConfigurationCommand : PowerShellCmdlet + { + private const string ProcessorEngineDSCv3 = "dscv3"; + private const string ProcessorEnginePowerShell = "pwsh"; + + private const string DSCv3FactoryMapKeyDscExecutablePath = "DscExecutablePath"; + private const string DSCv3FactoryMapKeyFoundDscExecutablePath = "FoundDscExecutablePath"; + private const string DSCv3FactoryMapKeyFindDscStateMachine = "FindDscStateMachine"; + + private const string WinGetClientModule = "Microsoft.WinGet.Client"; + private const string StableDSCv3PackageId = "9NVTPZWRC6KQ"; + private const string PreviewDSCv3PackageId = "9PCX3HX4HZ0Z"; + + /// + /// Initializes a new instance of the class. + /// + /// PSCmdlet. + public ConfigurationCommand(PSCmdlet psCmdlet) + : base(psCmdlet, new HashSet { Policy.WinGet, Policy.Configuration, Policy.WinGetCommandLineInterfaces }) + { + } + + /// + /// Verify user accept agreements. + /// + /// PSCmdlet. + /// Has already accepted. + /// If prompt is for apply. + /// If accepted. + public static bool ConfirmConfigurationProcessing(PSCmdlet psCmdlet, bool hasAccepted, bool isApply) + { + bool result = false; + if (!hasAccepted) + { + var prompt = isApply ? Resources.ConfigurationWarningPromptApply : Resources.ConfigurationWarningPromptTest; + bool yesToAll = false; + bool noToAll = false; + result = psCmdlet.ShouldContinue(prompt, Resources.ConfigurationWarning, true, ref yesToAll, ref noToAll); + + if (yesToAll) + { + result = true; + } + else if (noToAll) + { + result = false; + } + } + else + { + // This way even if they set WarningActionPreference.Ignore we will still print the + // warning message if the agreements didn't get accepted. + psCmdlet.WriteWarning(Resources.ConfigurationWarning); + result = true; + } + + return result; + } + + /// + /// Open a configuration set. + /// + /// Configuration file path. + /// The module path to use. + /// Execution policy. + /// The processor path to use. + /// If telemetry can be used. + public void Get( + string configFile, + string modulePath, + ExecutionPolicy executionPolicy, + string processorPath, + bool canUseTelemetry) + { + var openParams = new OpenConfigurationParameters( + this, configFile, modulePath, executionPolicy, processorPath, canUseTelemetry); + + // Start task. + var runningTask = this.RunOnMTA( + async () => + { + return (await this.OpenConfigurationSetAsync(openParams)) !; + }); + + this.Wait(runningTask); + this.Write(StreamType.Object, runningTask.Result); + } + + /// + /// Open a configuration set from history. + /// + /// Instance identifier. + /// The module path to use. + /// Execution policy. + /// The processor path to use. + /// If telemetry can be used. + public void GetFromHistory( + string instanceIdentifier, + string modulePath, + ExecutionPolicy executionPolicy, + string processorPath, + bool canUseTelemetry) + { + var openParams = new OpenConfigurationParameters( + this, instanceIdentifier, modulePath, executionPolicy, processorPath, canUseTelemetry, fromHistory: true); + + // Start task. + var runningTask = this.RunOnMTA( + async () => + { + return await this.OpenConfigurationSetAsync(openParams); + }); + + this.Wait(runningTask); + if (runningTask.Result != null) + { + this.Write(StreamType.Object, runningTask.Result); + } + } + + /// + /// Opens all configuration sets from history. + /// + /// The module path to use. + /// Execution policy. + /// The processor path to use. + /// If telemetry can be used. + public void GetAllFromHistory( + string modulePath, + ExecutionPolicy executionPolicy, + string processorPath, + bool canUseTelemetry) + { + var openParams = new OpenConfigurationParameters( + this, modulePath, executionPolicy, processorPath, canUseTelemetry); + + // Start task. + var runningTask = this.RunOnMTA( + async () => + { + return await this.GetConfigurationSetHistoryAsync(openParams); + }); + + this.Wait(runningTask); + this.Write(StreamType.Object, runningTask.Result); + } + + /// + /// Gets the details of a configuration set. + /// + /// PSConfigurationSet. + public void GetDetails(PSConfigurationSet psConfigurationSet) + { + psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); + + if (!psConfigurationSet.HasDetails) + { + if (!psConfigurationSet.CanProcess()) + { + throw new InvalidOperationException(); + } + + var runningTask = this.RunOnMTA( + async () => + { + try + { + psConfigurationSet = await this.GetSetDetailsAsync(psConfigurationSet, false); + } + finally + { + psConfigurationSet.DoneProcessing(); + } + + return psConfigurationSet; + }); + + this.Wait(runningTask); + psConfigurationSet = runningTask.Result; + } + else + { + this.Write(StreamType.Warning, "Details already obtained for this set"); + } + + this.Write(StreamType.Object, psConfigurationSet); + } + + /// + /// Starts configuration. + /// + /// PSConfigurationSet. + public void StartApply(PSConfigurationSet psConfigurationSet) + { + // if (psConfigurationSet.Set.State == ConfigurationSetState.Completed) + if (psConfigurationSet.ApplyCompleted) + { + this.Write(StreamType.Warning, "Processing this set is completed"); + throw new InvalidOperationException(); + } + + if (!psConfigurationSet.CanProcess()) + { + throw new InvalidOperationException(); + } + + var configurationJob = this.StartApplyInternal(psConfigurationSet); + this.Write(StreamType.Object, configurationJob); + } + + /// + /// Applies configuration. + /// + /// PSConfigurationSet. + public void Apply(PSConfigurationSet psConfigurationSet) => this.ContinueHelper(this.StartApplyInternal(psConfigurationSet)); + + /// + /// Continue a configuration job. + /// + /// The configuration job. + public void Continue(PSConfigurationJob psConfigurationJob) + { + if (psConfigurationJob.ApplyConfigurationTask.IsCompleted) + { + // It is safe to print all output. + psConfigurationJob.StartCommand.ConsumeAndWriteStreams(this); + + this.Write(StreamType.Verbose, "The task was completed before waiting"); + if (psConfigurationJob.ApplyConfigurationTask.IsCompletedSuccessfully) + { + this.Write(StreamType.Verbose, "Completed successfully"); + this.Write(StreamType.Object, psConfigurationJob.ApplyConfigurationTask.Result); + return; + } + else if (psConfigurationJob.ApplyConfigurationTask.IsFaulted) + { + this.Write(StreamType.Verbose, "Completed faulted before waiting"); + + // Maybe just write error? + throw psConfigurationJob.ApplyConfigurationTask.Exception!; + } + } + + this.ContinueHelper(psConfigurationJob); + } + + /// + /// Test configuration. + /// + /// PSConfigurationSet. + public void Test(PSConfigurationSet psConfigurationSet) + { + psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); + + if (!psConfigurationSet.CanProcess()) + { + throw new InvalidOperationException(); + } + + var runningTask = this.RunOnMTA( + async () => + { + try + { + return await this.TestConfigurationAsync(psConfigurationSet); + } + finally + { + psConfigurationSet.DoneProcessing(); + } + }); + + this.Wait(runningTask); + this.Write(StreamType.Object, runningTask.Result); + } + + /// + /// Validates configuration. + /// + /// PSConfigurationSet. + public void Validate(PSConfigurationSet psConfigurationSet) + { + psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); + + if (!psConfigurationSet.CanProcess()) + { + throw new InvalidOperationException(); + } + + var runningTask = this.RunOnMTA( + async () => + { + try + { + var setResult = await this.ApplyConfigurationAsync(psConfigurationSet, ApplyConfigurationSetFlags.PerformConsistencyCheckOnly); + return new PSValidateConfigurationSetResult(setResult); + } + finally + { + psConfigurationSet.DoneProcessing(); + } + }); + + this.Wait(runningTask); + this.Write(StreamType.Object, runningTask.Result); + } + + /// + /// Cancels a configuration job. + /// + /// PSConfiguration job. + public void Cancel(PSConfigurationJob psConfigurationJob) + { + psConfigurationJob.StartCommand.Cancel(); + } + + /// + /// Removes a configuration set from history. + /// + /// PSConfiguration set. + public void Remove(PSConfigurationSet psConfigurationSet) + { + psConfigurationSet.Set.Remove(); + } + + /// + /// Serializes a configuration set and outputs the string. + /// + /// PSConfiguration set. + public void Serialize(PSConfigurationSet psConfigurationSet) + { + // Start task. + var result = this.RunOnMTA( + () => + { + return this.SerializeMTA(psConfigurationSet); + }); + + this.Write(StreamType.Object, result); + } + + private void ContinueHelper(PSConfigurationJob psConfigurationJob) + { + // Signal the command that it can write to streams and wait for task. + this.Write(StreamType.Verbose, "Waiting for task to complete"); + psConfigurationJob.StartCommand.Wait(psConfigurationJob.ApplyConfigurationTask, this); + this.Write(StreamType.Object, psConfigurationJob.ApplyConfigurationTask.Result); + } + + private IConfigurationSetProcessorFactory CreatePowerShellProcessorFactory(OpenConfigurationParameters openParams) + { + var factory = new PowerShellConfigurationSetProcessorFactory(); + + var properties = factory.As(); + properties.Policy = openParams.Policy; + properties.ProcessorType = PowerShellConfigurationProcessorType.Default; + properties.Location = openParams.Location; + if (properties.Location == PowerShellConfigurationProcessorLocation.Custom) + { + properties.CustomLocation = openParams.CustomLocation; + } + + return factory; + } + + private async Task CreateDSCv3ProcessorFactory(OpenConfigurationParameters openParams) + { + var factory = new DSCv3ConfigurationSetProcessorFactory(); + + var factoryMap = factory.As>(); + if (!string.IsNullOrEmpty(openParams.ProcessorPath)) + { + if (!GroupPolicy.GetInstance().IsEnabled(Policy.ConfigurationProcessorPath)) + { + throw new GroupPolicyException(Policy.ConfigurationProcessorPath, GroupPolicyFailureType.BlockedByPolicy); + } + + factoryMap.Add(DSCv3FactoryMapKeyDscExecutablePath, openParams.ProcessorPath); + } + else + { + while (true) + { + string? nextTransition = null; + factoryMap.TryGetValue(DSCv3FactoryMapKeyFindDscStateMachine, out nextTransition); + + if (nextTransition == "Found") + { + break; + } + else if (nextTransition == "InstallStable") + { + this.Write(StreamType.Verbose, "Installing stable DSC..."); + await this.InstallDSCv3Package(openParams, StableDSCv3PackageId); + } + else if (nextTransition == "InstallPreview") + { + this.Write(StreamType.Verbose, "Installing preview DSC..."); + await this.InstallDSCv3Package(openParams, PreviewDSCv3PackageId); + } + else if (nextTransition == "NotFound") + { + this.Write(StreamType.Warning, Resources.ConfigurationInstallDscPackageFailed); + throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); + } + else + { + this.Write(StreamType.Warning, $"Unrecognized value from FindDscStateMachine: {nextTransition ?? ""}"); + throw new InvalidOperationException($"Internal error: Unrecognized value from FindDscStateMachine: {nextTransition ?? ""}"); + } + } + } + + return factory; + } + + private async Task InstallDSCv3Package(OpenConfigurationParameters openParams, string productId) + { + this.Write(StreamType.Information, Resources.ConfigurationInstallDscPackage); + + InitialSessionState initialSessionState = InitialSessionState.CreateDefault(); + initialSessionState.ExecutionPolicy = openParams.ExecutionPolicy; + Runspace runspace = RunspaceFactory.CreateRunspace(initialSessionState); + runspace.Open(); + PowerShell installDSCv3 = PowerShell.Create(runspace).AddScript( + $@" + if (-not (Get-Module -ListAvailable -Name {WinGetClientModule})) + {{ + Install-Module -Name {WinGetClientModule} -Confirm:$False -Force + }} + + $installResult = Install-WingetPackage -Id {productId} -Source msstore + if ($installResult.Status -ne 'Ok') + {{ + Write-Error ""Failed to install DSCv3 package. Status: $($installResult.Status). ExtendedErrorCode: $($installResult.ExtendedErrorCode)."" + }} + "); + + await installDSCv3.InvokeAsync(); + + if (installDSCv3.HadErrors) + { + this.Write(StreamType.Verbose, installDSCv3.GetErrorMessage() ?? ""); + this.Write(StreamType.Warning, Resources.ConfigurationInstallDscPackageFailed); + throw new FileNotFoundException(Resources.DscExeNotFound, "dsc.exe"); + } + } + + private async Task CreateConfigurationProcessorWithSet(OpenConfigurationParameters openParams, ConfigurationSet set) + { + string processorIdentifier = set.Environment.ProcessorIdentifier; + + if (string.IsNullOrEmpty(processorIdentifier) || ProcessorEnginePowerShell.Equals(processorIdentifier, StringComparison.OrdinalIgnoreCase)) + { + // Default to PowerShell + return new PSConfigurationProcessor(this.CreatePowerShellProcessorFactory(openParams), this, openParams.CanUseTelemetry); + } + else if (ProcessorEngineDSCv3.Equals(processorIdentifier, StringComparison.OrdinalIgnoreCase)) + { + return new PSConfigurationProcessor(await this.CreateDSCv3ProcessorFactory(openParams), this, openParams.CanUseTelemetry); + } + else + { + throw new NotSupportedException(string.Format(Resources.ProcessorEngineNotSupported, processorIdentifier)); + } + } + + private async Task OpenConfigurationSetAsync(OpenConfigurationParameters openParams) + { + this.Write(StreamType.Verbose, Resources.ConfigurationInitializing); + + var processorWithoutFactory = new PSConfigurationProcessor(null, this, openParams.CanUseTelemetry); + + if (!openParams.FromHistory) + { + this.Write(StreamType.Verbose, Resources.ConfigurationReadingConfigFile); + var stream = await FileRandomAccessStream.OpenAsync(openParams.ConfigFile, FileAccessMode.Read); + + OpenConfigurationSetResult openResult = await processorWithoutFactory.Processor.OpenConfigurationSetAsync(stream); + if (openResult.ResultCode != null) + { + throw new OpenConfigurationSetException(openResult, openParams.ConfigFile); + } + + var set = openResult.Set; + + // This should match winget's OpenConfigurationSet or OpenConfigurationSetAsync + // should be modify to take the full path and handle it. + set.Name = Path.GetFileName(openParams.ConfigFile); + set.Origin = Path.GetDirectoryName(openParams.ConfigFile); + set.Path = openParams.ConfigFile; + + return new PSConfigurationSet(await this.CreateConfigurationProcessorWithSet(openParams, set), set); + } + else + { + Guid instanceIdentifier = Guid.Parse(openParams.ConfigFile); + + this.Write(StreamType.Verbose, Resources.ConfigurationReadingConfigHistory); + + var historySets = await processorWithoutFactory.Processor.GetConfigurationHistoryAsync(); + + ConfigurationSet? result = null; + foreach (var historySet in historySets) + { + if (historySet.InstanceIdentifier == instanceIdentifier) + { + result = historySet; + break; + } + } + + return result != null ? new PSConfigurationSet(await this.CreateConfigurationProcessorWithSet(openParams, result), result) : null; + } + } + + private async Task GetConfigurationSetHistoryAsync(OpenConfigurationParameters openParams) + { + this.Write(StreamType.Verbose, Resources.ConfigurationInitializing); + + var processorWithoutFactory = new PSConfigurationProcessor(null, this, openParams.CanUseTelemetry); + + this.Write(StreamType.Verbose, Resources.ConfigurationReadingConfigHistory); + + var historySets = await processorWithoutFactory.Processor.GetConfigurationHistoryAsync(); + + PSConfigurationSet[] result = new PSConfigurationSet[historySets.Count]; + for (int i = 0; i < historySets.Count; ++i) + { + result[i] = new PSConfigurationSet(await this.CreateConfigurationProcessorWithSet(openParams, historySets[i]), historySets[i]); + } + + return result; + } + + private PSConfigurationJob StartApplyInternal(PSConfigurationSet psConfigurationSet) + { + psConfigurationSet.PsProcessor.UpdateDiagnosticCmdlet(this); + + var runningTask = this.RunOnMTA( + async () => + { + try + { + var setResult = await this.ApplyConfigurationAsync(psConfigurationSet, ApplyConfigurationSetFlags.None); + psConfigurationSet.ApplyCompleted = true; + return new PSApplyConfigurationSetResult(setResult); + } + finally + { + psConfigurationSet.DoneProcessing(); + } + }); + + return new PSConfigurationJob(runningTask, this); + } + + private async Task ApplyConfigurationAsync(PSConfigurationSet psConfigurationSet, ApplyConfigurationSetFlags flags) + { + if (!psConfigurationSet.HasDetails) + { + this.Write(StreamType.Verbose, "Getting details for configuration set"); + await this.GetSetDetailsAsync(psConfigurationSet, true); + } + + var processor = psConfigurationSet.PsProcessor.Processor; + var set = psConfigurationSet.Set; + + var applyProgressOutput = new ApplyConfigurationSetProgressOutput( + this, + this.GetNewProgressActivityId(), + Resources.ConfigurationApply, + Resources.OperationInProgress, + Resources.OperationCompleted, + set.Units.Count); + + var applyTask = processor.ApplySetAsync(set, flags); + applyTask.Progress = applyProgressOutput.Progress; + + try + { + var result = await applyTask.AsTask(this.GetCancellationToken()); + applyProgressOutput.HandleProgress(result); + return result; + } + finally + { + applyProgressOutput.CompleteProgress(); + } + } + + private async Task TestConfigurationAsync(PSConfigurationSet psConfigurationSet) + { + if (!psConfigurationSet.HasDetails) + { + this.Write(StreamType.Verbose, "Getting details for configuration set"); + await this.GetSetDetailsAsync(psConfigurationSet, true); + } + + var processor = psConfigurationSet.PsProcessor.Processor; + var set = psConfigurationSet.Set; + + var testProgressOutput = new TestConfigurationSetProgressOutput( + this, + this.GetNewProgressActivityId(), + Resources.ConfigurationAssert, + Resources.OperationInProgress, + Resources.OperationCompleted, + set.Units.Count); + + var testTask = processor.TestSetAsync(set); + testTask.Progress = testProgressOutput.Progress; + + try + { + var result = await testTask.AsTask(this.GetCancellationToken()); + testProgressOutput.HandleProgress(result); + + return new PSTestConfigurationSetResult(result); + } + finally + { + testProgressOutput.CompleteProgress(); + } + } + + private async Task GetSetDetailsAsync(PSConfigurationSet psConfigurationSet, bool warnOnError) + { + var processor = psConfigurationSet.PsProcessor.Processor; + var set = psConfigurationSet.Set; + var totalUnitsCount = set.Units.Count; + + if (totalUnitsCount == 0) + { + this.Write(StreamType.Warning, Resources.ConfigurationFileEmpty); + return psConfigurationSet; + } + + try + { + var detailsProgressOutput = new GetConfigurationSetDetailsProgressOutput( + this, + this.GetNewProgressActivityId(), + Resources.ConfigurationGettingDetails, + Resources.OperationInProgress, + Resources.OperationCompleted, + totalUnitsCount); + + var detailsTask = processor.GetSetDetailsAsync(set, ConfigurationUnitDetailFlags.ReadOnly); + detailsTask.Progress = detailsProgressOutput.Progress; + + try + { + var result = await detailsTask.AsTask(this.GetCancellationToken()); + detailsProgressOutput.HandleProgress(result); + + if (result.UnitResults.Where(u => u.ResultInformation.ResultCode != null).Any()) + { + throw new GetDetailsException(result.UnitResults); + } + + if (detailsProgressOutput.UnitsShown == 0) + { + throw new GetDetailsException(); + } + + psConfigurationSet.HasDetails = true; + } + finally + { + detailsProgressOutput.CompleteProgress(); + } + } + catch (Exception e) + { + if (warnOnError) + { + this.Write(StreamType.Warning, e.Message); + } + else + { + throw; + } + } + + return psConfigurationSet; + } + + /// + /// Serializes a configuration set and outputs the string. + /// + /// PSConfiguration set. + /// The string version of the set. + private string SerializeMTA(PSConfigurationSet psConfigurationSet) + { + MemoryStream stream = new MemoryStream(); + psConfigurationSet.Set.Serialize(stream.AsOutputStream()); + return Encoding.UTF8.GetString(stream.ToArray()); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ApplyConfigurationException.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ApplyConfigurationException.cs index cf2c77d33b..9d62723121 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ApplyConfigurationException.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ApplyConfigurationException.cs @@ -1,43 +1,43 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Exceptions -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - using Microsoft.WinGet.Resources; - - /// - /// Exception thrown when there's an error when configuration is applied. - /// - public class ApplyConfigurationException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Apply Result. - internal ApplyConfigurationException(ApplyConfigurationSetResult applyResult) - : base(Resources.ConfigurationFailedToApply) - { - this.HResult = applyResult.ResultCode?.HResult ?? ErrorCodes.WingetConfigErrorSetApplyFailed; - - var results = new List(); - foreach (var unitResult in applyResult.UnitResults) - { - results.Add(new PSApplyConfigurationUnitResult(unitResult)); - } - - this.UnitResults = results; - } - - /// - /// Gets the result of the units. - /// - public IReadOnlyList UnitResults { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Exceptions +{ + using System; + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + using Microsoft.WinGet.Resources; + + /// + /// Exception thrown when there's an error when configuration is applied. + /// + public class ApplyConfigurationException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Apply Result. + internal ApplyConfigurationException(ApplyConfigurationSetResult applyResult) + : base(Resources.ConfigurationFailedToApply) + { + this.HResult = applyResult.ResultCode?.HResult ?? ErrorCodes.WingetConfigErrorSetApplyFailed; + + var results = new List(); + foreach (var unitResult in applyResult.UnitResults) + { + results.Add(new PSApplyConfigurationUnitResult(unitResult)); + } + + this.UnitResults = results; + } + + /// + /// Gets the result of the units. + /// + public IReadOnlyList UnitResults { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorCodes.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorCodes.cs index 9b6d9abf72..9c3806bc26 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorCodes.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorCodes.cs @@ -1,47 +1,47 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Exceptions -{ - /// - /// This should match the ones in AppInstallerErrors.h. - /// - internal static class ErrorCodes - { -#pragma warning disable SA1600 // ElementsMustBeDocumented -#pragma warning disable SA1310 // Field names should not contain underscore - internal const int S_OK = 0; - - internal const int WingetConfigErrorInvalidConfigurationFile = unchecked((int)0x8A15C001); - internal const int WingetConfigErrorInvalidYaml = unchecked((int)0x8A15C002); - internal const int WingetConfigErrorInvalidFieldType = unchecked((int)0x8A15C003); - internal const int WingetConfigErrorUnknownConfigurationFileVersion = unchecked((int)0x8A15C004); - internal const int WingetConfigErrorSetApplyFailed = unchecked((int)0x8A15C005); - internal const int WingetConfigErrorDuplicateIdentifier = unchecked((int)0x8A15C006); - internal const int WingetConfigErrorMissingDependency = unchecked((int)0x8A15C007); - internal const int WingetConfigErrorDependencyUnsatisfied = unchecked((int)0x8A15C008); - internal const int WingetConfigErrorAssertionFailed = unchecked((int)0x8A15C009); - internal const int WingetConfigErrorManuallySkipped = unchecked((int)0x8A15C00A); - internal const int WingetConfigErrorWarningNotAccepted = unchecked((int)0x8A15C00B); - internal const int WingetConfigErrorSetDependencyCycle = unchecked((int)0x8A15C00C); - internal const int WingetConfigErrorInvalidFieldValue = unchecked((int)0x8A15C00D); - internal const int WingetConfigErrorMissingField = unchecked((int)0x8A15C00E); - - internal const int WinGetConfigUnitNotFound = unchecked((int)0x8A15C101); - internal const int WinGetConfigUnitNotFoundRepository = unchecked((int)0x8A15C102); - internal const int WinGetConfigUnitMultipleMatches = unchecked((int)0x8A15C103); - internal const int WinGetConfigUnitInvokeGet = unchecked((int)0x8A15C104); - internal const int WinGetConfigUnitInvokeTest = unchecked((int)0x8A15C105); - internal const int WinGetConfigUnitInvokeSet = unchecked((int)0x8A15C106); - internal const int WinGetConfigUnitModuleConflict = unchecked((int)0x8A15C107); - internal const int WinGetConfigUnitImportModule = unchecked((int)0x8A15C108); - internal const int WinGetConfigUnitInvokeInvalidResult = unchecked((int)0x8A15C109); - internal const int WinGetConfigUnitSettingConfigRoot = unchecked((int)0x8A15C110); - internal const int WinGetConfigUnitImportModuleAdmin = unchecked((int)0x8A15C111); -#pragma warning restore SA1310 // Field names should not contain underscore -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Exceptions +{ + /// + /// This should match the ones in AppInstallerErrors.h. + /// + internal static class ErrorCodes + { +#pragma warning disable SA1600 // ElementsMustBeDocumented +#pragma warning disable SA1310 // Field names should not contain underscore + internal const int S_OK = 0; + + internal const int WingetConfigErrorInvalidConfigurationFile = unchecked((int)0x8A15C001); + internal const int WingetConfigErrorInvalidYaml = unchecked((int)0x8A15C002); + internal const int WingetConfigErrorInvalidFieldType = unchecked((int)0x8A15C003); + internal const int WingetConfigErrorUnknownConfigurationFileVersion = unchecked((int)0x8A15C004); + internal const int WingetConfigErrorSetApplyFailed = unchecked((int)0x8A15C005); + internal const int WingetConfigErrorDuplicateIdentifier = unchecked((int)0x8A15C006); + internal const int WingetConfigErrorMissingDependency = unchecked((int)0x8A15C007); + internal const int WingetConfigErrorDependencyUnsatisfied = unchecked((int)0x8A15C008); + internal const int WingetConfigErrorAssertionFailed = unchecked((int)0x8A15C009); + internal const int WingetConfigErrorManuallySkipped = unchecked((int)0x8A15C00A); + internal const int WingetConfigErrorWarningNotAccepted = unchecked((int)0x8A15C00B); + internal const int WingetConfigErrorSetDependencyCycle = unchecked((int)0x8A15C00C); + internal const int WingetConfigErrorInvalidFieldValue = unchecked((int)0x8A15C00D); + internal const int WingetConfigErrorMissingField = unchecked((int)0x8A15C00E); + + internal const int WinGetConfigUnitNotFound = unchecked((int)0x8A15C101); + internal const int WinGetConfigUnitNotFoundRepository = unchecked((int)0x8A15C102); + internal const int WinGetConfigUnitMultipleMatches = unchecked((int)0x8A15C103); + internal const int WinGetConfigUnitInvokeGet = unchecked((int)0x8A15C104); + internal const int WinGetConfigUnitInvokeTest = unchecked((int)0x8A15C105); + internal const int WinGetConfigUnitInvokeSet = unchecked((int)0x8A15C106); + internal const int WinGetConfigUnitModuleConflict = unchecked((int)0x8A15C107); + internal const int WinGetConfigUnitImportModule = unchecked((int)0x8A15C108); + internal const int WinGetConfigUnitInvokeInvalidResult = unchecked((int)0x8A15C109); + internal const int WinGetConfigUnitSettingConfigRoot = unchecked((int)0x8A15C110); + internal const int WinGetConfigUnitImportModuleAdmin = unchecked((int)0x8A15C111); +#pragma warning restore SA1310 // Field names should not contain underscore +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorRecordErrorId.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorRecordErrorId.cs index 2dfdc124e8..3f03cbd18a 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorRecordErrorId.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/ErrorRecordErrorId.cs @@ -1,19 +1,19 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Exceptions -{ - /// - /// ErrorId used for the ErrorRecords. - /// - internal enum ErrorRecordErrorId - { - /// - /// Error message from diagnostics. - /// - ConfigurationDiagnosticError, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Exceptions +{ + /// + /// ErrorId used for the ErrorRecords. + /// + internal enum ErrorRecordErrorId + { + /// + /// Error message from diagnostics. + /// + ConfigurationDiagnosticError, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/GetDetailsException.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/GetDetailsException.cs index fccdad5896..4202ff6a03 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/GetDetailsException.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/GetDetailsException.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Exceptions -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - using Microsoft.WinGet.Resources; - - /// - /// Exception thrown while getting details. - /// - public class GetDetailsException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Unit results. - internal GetDetailsException(IReadOnlyList? unitResults = null) - : base(Resources.ConfigurationFailedToGetDetails) - { - var results = new List(); - - if (unitResults != null) - { - foreach (var result in unitResults) - { - results.Add(new PSGetConfigurationDetailsResult(result)); - } - } - - this.UnitDetailsResults = results; - } - - /// - /// Gets the unit details result. - /// - public IReadOnlyList UnitDetailsResults { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Exceptions +{ + using System; + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + using Microsoft.WinGet.Resources; + + /// + /// Exception thrown while getting details. + /// + public class GetDetailsException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Unit results. + internal GetDetailsException(IReadOnlyList? unitResults = null) + : base(Resources.ConfigurationFailedToGetDetails) + { + var results = new List(); + + if (unitResults != null) + { + foreach (var result in unitResults) + { + results.Add(new PSGetConfigurationDetailsResult(result)); + } + } + + this.UnitDetailsResults = results; + } + + /// + /// Gets the unit details result. + /// + public IReadOnlyList UnitDetailsResults { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/OpenConfigurationSetException.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/OpenConfigurationSetException.cs index da1d811ce3..c6902fa2c3 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/OpenConfigurationSetException.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Exceptions/OpenConfigurationSetException.cs @@ -1,63 +1,63 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Exceptions -{ - using System; - using System.Text; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Resources; - - /// - /// Exception thrown when failed to open a configuration set. - /// - public class OpenConfigurationSetException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Open Result. - /// Configuration file. - internal OpenConfigurationSetException(OpenConfigurationSetResult openResult, string configurationFile) - : base(GetMessage(openResult, configurationFile)) - { - } - - private static string GetMessage(OpenConfigurationSetResult openResult, string configurationFile) - { - var sb = new StringBuilder(); - sb.Append($"Failed to open configuration set at {configurationFile} with error 0x{openResult.ResultCode.HResult:X} "); - - switch (openResult.ResultCode.HResult) - { - case ErrorCodes.WingetConfigErrorInvalidFieldType: - sb.Append(string.Format(Resources.ConfigurationFieldInvalidType, openResult.Field)); - break; - case ErrorCodes.WingetConfigErrorInvalidFieldValue: - sb.Append(string.Format(Resources.ConfigurationFieldInvalidValue, openResult.Field, openResult.Value)); - break; - case ErrorCodes.WingetConfigErrorMissingField: - sb.Append(string.Format(Resources.ConfigurationFieldMissing, openResult.Field)); - break; - case ErrorCodes.WingetConfigErrorUnknownConfigurationFileVersion: - sb.Append(string.Format(Resources.ConfigurationFileVersionUnknown, openResult.Value)); - break; - case ErrorCodes.WingetConfigErrorInvalidConfigurationFile: - case ErrorCodes.WingetConfigErrorInvalidYaml: - default: - sb.Append(Resources.ConfigurationFileInvalid); - break; - } - - if (openResult.Line != 0) - { - sb.Append($" {string.Format(Resources.SeeLineAndColumn, openResult.Line, openResult.Column)}"); - } - - return sb.ToString(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Exceptions +{ + using System; + using System.Text; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Resources; + + /// + /// Exception thrown when failed to open a configuration set. + /// + public class OpenConfigurationSetException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Open Result. + /// Configuration file. + internal OpenConfigurationSetException(OpenConfigurationSetResult openResult, string configurationFile) + : base(GetMessage(openResult, configurationFile)) + { + } + + private static string GetMessage(OpenConfigurationSetResult openResult, string configurationFile) + { + var sb = new StringBuilder(); + sb.Append($"Failed to open configuration set at {configurationFile} with error 0x{openResult.ResultCode.HResult:X} "); + + switch (openResult.ResultCode.HResult) + { + case ErrorCodes.WingetConfigErrorInvalidFieldType: + sb.Append(string.Format(Resources.ConfigurationFieldInvalidType, openResult.Field)); + break; + case ErrorCodes.WingetConfigErrorInvalidFieldValue: + sb.Append(string.Format(Resources.ConfigurationFieldInvalidValue, openResult.Field, openResult.Value)); + break; + case ErrorCodes.WingetConfigErrorMissingField: + sb.Append(string.Format(Resources.ConfigurationFieldMissing, openResult.Field)); + break; + case ErrorCodes.WingetConfigErrorUnknownConfigurationFileVersion: + sb.Append(string.Format(Resources.ConfigurationFileVersionUnknown, openResult.Value)); + break; + case ErrorCodes.WingetConfigErrorInvalidConfigurationFile: + case ErrorCodes.WingetConfigErrorInvalidYaml: + default: + sb.Append(Resources.ConfigurationFileInvalid); + break; + } + + if (openResult.Line != 0) + { + sb.Append($" {string.Format(Resources.SeeLineAndColumn, openResult.Line, openResult.Column)}"); + } + + return sb.ToString(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Extensions/ValueSetExtensions.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Extensions/ValueSetExtensions.cs index b5b83dbfec..d4f1c222fc 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Extensions/ValueSetExtensions.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Extensions/ValueSetExtensions.cs @@ -1,33 +1,33 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Extensions -{ - using Windows.Foundation.Collections; - - /// - /// Extension methods for Value set. - /// - internal static class ValueSetExtensions - { - /// - /// Gets the string value of a given key. - /// Null is doesn't exist or cast can't be done. - /// - /// Value set. - /// Key. - /// String value. - public static string? TryGetStringValue(this ValueSet valueSet, string key) - { - if (valueSet.TryGetValue(key, out object value)) - { - return value as string; - } - - return null; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Extensions +{ + using Windows.Foundation.Collections; + + /// + /// Extension methods for Value set. + /// + internal static class ValueSetExtensions + { + /// + /// Gets the string value of a given key. + /// Null is doesn't exist or cast can't be done. + /// + /// Value set. + /// Key. + /// String value. + public static string? TryGetStringValue(this ValueSet valueSet, string key) + { + if (valueSet.TryGetValue(key, out object value)) + { + return value as string; + } + + return null; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ApplyConfigurationSetProgressOutput.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ApplyConfigurationSetProgressOutput.cs index b6dd427d14..dc43aa7bf0 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ApplyConfigurationSetProgressOutput.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ApplyConfigurationSetProgressOutput.cs @@ -1,84 +1,84 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Helpers -{ - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Common.Command; - using Windows.Foundation; - - /// - /// Helper to handle progress callbacks from ApplySetAsync. - /// - internal class ApplyConfigurationSetProgressOutput : ConfigurationSetProgressOutputBase - { - private bool isFirstProgress = true; - - /// - /// Initializes a new instance of the class. - /// - /// Command that outputs the messages. - /// The activity id of the progress bar. - /// The activity. - /// The message in the progress bar. - /// The activity complete message. - /// Total of units expected. - public ApplyConfigurationSetProgressOutput(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) - : base(cmd, activityId, activity, inProgressMessage, completeMessage, totalUnitsExpected) - { - } - - /// - public override void Progress(IAsyncOperationWithProgress operation, ConfigurationSetChangeData data) - { - if (this.isFirstProgress) - { - this.HandleProgress(operation.GetResults()); - } - - switch (data.Change) - { - case ConfigurationSetChangeEventType.UnitStateChanged: - this.HandleUnitProgress(data.Unit, data.UnitState); - break; - } - } - - /// - public override void HandleProgress(ApplyConfigurationSetResult result) - { - if (!this.isFirstProgress) - { - this.isFirstProgress = false; - foreach (var unitResult in result.UnitResults) - { - this.HandleUnitProgress(unitResult.Unit, unitResult.State); - } - } - } - - private void HandleUnitProgress(ConfigurationUnit unit, ConfigurationUnitState state) - { - if (this.UnitsCompleted.Contains(unit.InstanceIdentifier)) - { - return; - } - - switch (state) - { - case ConfigurationUnitState.Pending: - // The unreported progress handler may send pending units, just ignore them - break; - case ConfigurationUnitState.InProgress: - break; - case ConfigurationUnitState.Completed: - case ConfigurationUnitState.Skipped: - this.CompleteUnit(unit); - break; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Helpers +{ + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Common.Command; + using Windows.Foundation; + + /// + /// Helper to handle progress callbacks from ApplySetAsync. + /// + internal class ApplyConfigurationSetProgressOutput : ConfigurationSetProgressOutputBase + { + private bool isFirstProgress = true; + + /// + /// Initializes a new instance of the class. + /// + /// Command that outputs the messages. + /// The activity id of the progress bar. + /// The activity. + /// The message in the progress bar. + /// The activity complete message. + /// Total of units expected. + public ApplyConfigurationSetProgressOutput(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) + : base(cmd, activityId, activity, inProgressMessage, completeMessage, totalUnitsExpected) + { + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, ConfigurationSetChangeData data) + { + if (this.isFirstProgress) + { + this.HandleProgress(operation.GetResults()); + } + + switch (data.Change) + { + case ConfigurationSetChangeEventType.UnitStateChanged: + this.HandleUnitProgress(data.Unit, data.UnitState); + break; + } + } + + /// + public override void HandleProgress(ApplyConfigurationSetResult result) + { + if (!this.isFirstProgress) + { + this.isFirstProgress = false; + foreach (var unitResult in result.UnitResults) + { + this.HandleUnitProgress(unitResult.Unit, unitResult.State); + } + } + } + + private void HandleUnitProgress(ConfigurationUnit unit, ConfigurationUnitState state) + { + if (this.UnitsCompleted.Contains(unit.InstanceIdentifier)) + { + return; + } + + switch (state) + { + case ConfigurationUnitState.Pending: + // The unreported progress handler may send pending units, just ignore them + break; + case ConfigurationUnitState.InProgress: + break; + case ConfigurationUnitState.Completed: + case ConfigurationUnitState.Skipped: + this.CompleteUnit(unit); + break; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationSetProgressOutputBase.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationSetProgressOutputBase.cs index b03fa13536..2c101f12f9 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationSetProgressOutputBase.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationSetProgressOutputBase.cs @@ -1,90 +1,90 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Common.Command; - using Windows.Foundation; - - /// - /// Helper to handle progress callbacks. - /// - /// The operation result. - /// Progress data. - internal abstract class ConfigurationSetProgressOutputBase - { - private readonly PowerShellCmdlet cmd; - private readonly int activityId; - private readonly string activity; - private readonly string inProgressMessage; - private readonly string completeMessage; - private readonly int totalUnitsExpected; - - /// - /// Initializes a new instance of the class. - /// - /// Command that outputs the messages. - /// The activity id of the progress bar. - /// The activity. - /// The message in the progress bar. - /// The activity complete message. - /// Total of units expected. - public ConfigurationSetProgressOutputBase(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) - { - this.cmd = cmd; - this.activityId = activityId; - this.activity = activity; - this.inProgressMessage = inProgressMessage; - this.completeMessage = completeMessage; - this.totalUnitsExpected = totalUnitsExpected; - - // Write initial progress record. - // For some reason, if this is 0 the progress bar is shown full. Start with 1% - this.cmd.WriteProgressWithPercentage(activityId, activity, $"{this.inProgressMessage} 0/{this.totalUnitsExpected}", 1, 100); - } - - /// - /// Gets or sets a hash set with the completed units. - /// - protected HashSet UnitsCompleted { get; set; } = new (); - - /// - /// Progress callback. - /// - /// Async operation in progress. - /// Change data. - public abstract void Progress(IAsyncOperationWithProgress operation, TProgressData data); - - /// - /// Handle progress. - /// - /// Set result. - public abstract void HandleProgress(TOperationResult result); - - /// - /// Completes the progress bar. - /// - public void CompleteProgress() - { - this.cmd.CompleteProgress(this.activityId, this.activity, this.completeMessage); - } - - /// - /// Marks a unit as completed and increase progress. - /// - /// Unit. - protected void CompleteUnit(ConfigurationUnit unit) - { - if (this.UnitsCompleted.Add(unit.InstanceIdentifier)) - { - this.cmd.WriteProgressWithPercentage(this.activityId, this.activity, $"{this.inProgressMessage} {this.UnitsCompleted.Count}/{this.totalUnitsExpected}", this.UnitsCompleted.Count, this.totalUnitsExpected); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Common.Command; + using Windows.Foundation; + + /// + /// Helper to handle progress callbacks. + /// + /// The operation result. + /// Progress data. + internal abstract class ConfigurationSetProgressOutputBase + { + private readonly PowerShellCmdlet cmd; + private readonly int activityId; + private readonly string activity; + private readonly string inProgressMessage; + private readonly string completeMessage; + private readonly int totalUnitsExpected; + + /// + /// Initializes a new instance of the class. + /// + /// Command that outputs the messages. + /// The activity id of the progress bar. + /// The activity. + /// The message in the progress bar. + /// The activity complete message. + /// Total of units expected. + public ConfigurationSetProgressOutputBase(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) + { + this.cmd = cmd; + this.activityId = activityId; + this.activity = activity; + this.inProgressMessage = inProgressMessage; + this.completeMessage = completeMessage; + this.totalUnitsExpected = totalUnitsExpected; + + // Write initial progress record. + // For some reason, if this is 0 the progress bar is shown full. Start with 1% + this.cmd.WriteProgressWithPercentage(activityId, activity, $"{this.inProgressMessage} 0/{this.totalUnitsExpected}", 1, 100); + } + + /// + /// Gets or sets a hash set with the completed units. + /// + protected HashSet UnitsCompleted { get; set; } = new (); + + /// + /// Progress callback. + /// + /// Async operation in progress. + /// Change data. + public abstract void Progress(IAsyncOperationWithProgress operation, TProgressData data); + + /// + /// Handle progress. + /// + /// Set result. + public abstract void HandleProgress(TOperationResult result); + + /// + /// Completes the progress bar. + /// + public void CompleteProgress() + { + this.cmd.CompleteProgress(this.activityId, this.activity, this.completeMessage); + } + + /// + /// Marks a unit as completed and increase progress. + /// + /// Unit. + protected void CompleteUnit(ConfigurationUnit unit) + { + if (this.UnitsCompleted.Add(unit.InstanceIdentifier)) + { + this.cmd.WriteProgressWithPercentage(this.activityId, this.activity, $"{this.inProgressMessage} {this.UnitsCompleted.Count}/{this.totalUnitsExpected}", this.UnitsCompleted.Count, this.totalUnitsExpected); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationUnitInformation.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationUnitInformation.cs index 59a74fcd1e..6bf04f0818 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationUnitInformation.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/ConfigurationUnitInformation.cs @@ -1,323 +1,323 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Helpers -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Management.Automation; - using System.Text; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Extensions; - using Microsoft.WinGet.Resources; - using Windows.Foundation.Collections; - - /// - /// Helper class to construct the information messages for a unit. - /// This must match or be as close as possible to winget's OutputConfigurationUnitInformation. - /// - internal class ConfigurationUnitInformation - { - private const string Description = "description"; - private const string Module = "module"; - private const string TreatAsArray = "treatAsArray"; - - private readonly string header; - private readonly string information; - - /// - /// Initializes a new instance of the class. - /// - /// Configuration unit. - public ConfigurationUnitInformation(ConfigurationUnit unit) - { - this.header = this.CreateHeader(unit, unit.Details != null ? unit.Details.UnitType : unit.Type); - this.information = this.CreateInformation(unit); - } - - /// - /// Gets the header information message. - /// - /// Header information message. - public HostInformationMessage GetHeader() - { - return Utilities.CreateInformationMessage(this.header, foregroundColor: ConsoleColor.Cyan); - } - - /// - /// Gets the information message. - /// - /// Information message. - public HostInformationMessage GetInformation() - { - return Utilities.CreateInformationMessage(this.information); - } - - private string CreateHeader(ConfigurationUnit unit, string name) - { - var sb = new StringBuilder(); - sb.Append($"{this.IntentToString(unit.Intent)} :: {name}"); - - string identifier = unit.Identifier; - if (!string.IsNullOrEmpty(identifier)) - { - sb.Append($" [{identifier}]"); - } - - return sb.ToString(); - } - - private string CreateInformation(ConfigurationUnit unit) - { - IConfigurationUnitProcessorDetails details = unit.Details; - ValueSet metadata = unit.Metadata; - - var sb = new StringBuilder(); - if (details != null) - { - this.CreateInformationWithDetails(ref sb, details, metadata); - } - else - { - this.CreateInformationWithoutDetails(ref sb, metadata); - } - - // -- Sample output footer -- - // Dependencies: dep1, dep2, ... - // Settings: - // <... settings splat> - var dependencies = unit.Dependencies; - if (dependencies.Count > 0) - { - var dependencySb = new StringBuilder(); - foreach (var dependency in dependencies) - { - dependencySb.Append($" {dependency}"); - } - - sb.AppendLine($" {string.Format(Resources.ConfigurationDependencies, dependencySb.ToString())}"); - } - - var settings = unit.Settings; - if (settings.Count > 0) - { - sb.AppendLine($" {Resources.ConfigurationSettings}"); - this.AppendValueSet(ref sb, settings, 4); - } - - return sb.ToString(); - } - - // -- Sample output when IConfigurationUnitProcessorDetails present -- - // Intent :: UnitName [Identifier] - // UnitDocumentationUri - // Description - // "Module": ModuleName "by" Author / Publisher (IsLocal / ModuleSource) - // "Signed by": SigningCertificateChain (leaf subject CN) - // PublishedModuleUri / ModuleDocumentationUri - // ModuleDescription - private void CreateInformationWithDetails(ref StringBuilder sb, IConfigurationUnitProcessorDetails details, ValueSet directives) - { - var unitDocumentationUri = details.UnitDocumentationUri; - if (unitDocumentationUri != null) - { - sb.AppendLine($" {unitDocumentationUri.AbsoluteUri}"); - } - - var unitDescriptionFromDetails = details.UnitDescription; - if (!string.IsNullOrEmpty(unitDescriptionFromDetails)) - { - sb.AppendLine($" {unitDescriptionFromDetails}"); - } - else - { - var unitDescriptionFromDirectives = directives.TryGetStringValue(Description); - if (!string.IsNullOrEmpty(unitDescriptionFromDirectives)) - { - sb.AppendLine($" {unitDescriptionFromDirectives}"); - } - } - - var author = details.Author; - if (string.IsNullOrEmpty(author)) - { - author = details.Publisher; - } - - if (details.IsLocal) - { - sb.AppendLine($" {string.Format(Resources.ConfigurationModuleWithDetails, details.ModuleName, author, Resources.ConfigurationLocal)}"); - } - else - { - sb.AppendLine($" {string.Format(Resources.ConfigurationModuleWithDetails, details.ModuleName, author, details.ModuleSource)}"); - } - - // TODO: see signature information in ConfigurationFlow.cpp - var moduleUri = details.PublishedModuleUri; - if (moduleUri == null) - { - moduleUri = details.ModuleDocumentationUri; - } - - if (moduleUri != null) - { - sb.AppendLine($" {moduleUri.AbsoluteUri}"); - } - - var moduleDescription = details.ModuleDescription; - if (!string.IsNullOrEmpty(moduleDescription)) - { - sb.AppendLine($" {moduleDescription}"); - } - } - - // -- Sample output when no IConfigurationUnitProcessorDetails present -- - // Intent :: UnitName [identifier] - // Description (from directives) - // "Module": module - private void CreateInformationWithoutDetails(ref StringBuilder sb, ValueSet directives) - { - var unitDescriptionFromDirectives = directives.TryGetStringValue(Description); - if (!string.IsNullOrEmpty(unitDescriptionFromDirectives)) - { - sb.AppendLine($" {unitDescriptionFromDirectives}"); - } - - var unitModuleFromDirectives = directives.TryGetStringValue(Module); - if (!string.IsNullOrEmpty(unitModuleFromDirectives)) - { - sb.AppendLine($" {string.Format(Resources.ConfigurationModuleNameOnly, unitModuleFromDirectives)}"); - } - } - - private void AppendValueSet(ref StringBuilder sb, ValueSet valueSet, int indent) - { - var indentString = new string(' ', indent); - - foreach (var value in valueSet) - { - sb.Append($"{indentString}{value.Key}:"); - - // Can't use IPropertyValue here... - var obj = value.Value; - var innerValueSet = obj as ValueSet; - if (innerValueSet != null) - { - sb.AppendLine(); - if (innerValueSet.ContainsKey(TreatAsArray)) - { - this.AppendValueSetAsArray(ref sb, innerValueSet, indent + 2); - } - else - { - this.AppendValueSet(ref sb, innerValueSet, indent + 2); - } - } - else - { - this.AppendPropertyValue(ref sb, obj); - } - } - } - - private void AppendValueSetAsArray(ref StringBuilder sb, ValueSet valueSet, int indent) - { - var indentString = new string(' ', indent); - - var sortedList = new SortedList(); - - foreach (var keyValuePair in valueSet) - { - if (keyValuePair.Key != TreatAsArray) - { - if (int.TryParse(keyValuePair.Key, out int key)) - { - sortedList.Add(key, keyValuePair.Value); - } - else - { - throw new InvalidOperationException(keyValuePair.Key); - } - } - } - - foreach (var arrayValue in sortedList) - { - sb.Append($"{indentString}-"); - var obj = arrayValue.Value; - - var innerValueSet = obj as ValueSet; - if (innerValueSet == null) - { - this.AppendPropertyValue(ref sb, obj); - } - else - { - var size = innerValueSet.Count; - if (size > 0) - { - // First one is special. - var first = innerValueSet.First(); - sb.Append($" {first.Key}:"); - - var firstValueSet = first.Value as ValueSet; - if (firstValueSet == null) - { - this.AppendPropertyValue(ref sb, first.Value); - } - else - { - sb.AppendLine(); - this.AppendValueSet(ref sb, firstValueSet, indent + 4); - } - - if (size > 1) - { - innerValueSet.Remove(first.Key); - this.AppendValueSet(ref sb, innerValueSet, indent + 2); - innerValueSet.Add(first); - } - } - } - } - } - - private void AppendPropertyValue(ref StringBuilder sb, object value) - { - Type type = value.GetType(); - if (type == typeof(string)) - { - sb.AppendLine($" {(string)value}"); - } - else if (type == typeof(bool)) - { - string message = (bool)value ? "true" : "false"; - sb.AppendLine($" {message}"); - } - else if (type == typeof(long)) - { - sb.AppendLine($" {(long)value}"); - } - else - { - sb.AppendLine($" [Debug:PropertyType={type}]"); - } - } - - private string IntentToString(ConfigurationUnitIntent intent) - { - return intent switch - { - ConfigurationUnitIntent.Assert => Resources.ConfigurationAssert, - ConfigurationUnitIntent.Inform => Resources.ConfigurationInform, - ConfigurationUnitIntent.Apply => Resources.ConfigurationApply, - _ => string.Empty, - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Helpers +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Management.Automation; + using System.Text; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Extensions; + using Microsoft.WinGet.Resources; + using Windows.Foundation.Collections; + + /// + /// Helper class to construct the information messages for a unit. + /// This must match or be as close as possible to winget's OutputConfigurationUnitInformation. + /// + internal class ConfigurationUnitInformation + { + private const string Description = "description"; + private const string Module = "module"; + private const string TreatAsArray = "treatAsArray"; + + private readonly string header; + private readonly string information; + + /// + /// Initializes a new instance of the class. + /// + /// Configuration unit. + public ConfigurationUnitInformation(ConfigurationUnit unit) + { + this.header = this.CreateHeader(unit, unit.Details != null ? unit.Details.UnitType : unit.Type); + this.information = this.CreateInformation(unit); + } + + /// + /// Gets the header information message. + /// + /// Header information message. + public HostInformationMessage GetHeader() + { + return Utilities.CreateInformationMessage(this.header, foregroundColor: ConsoleColor.Cyan); + } + + /// + /// Gets the information message. + /// + /// Information message. + public HostInformationMessage GetInformation() + { + return Utilities.CreateInformationMessage(this.information); + } + + private string CreateHeader(ConfigurationUnit unit, string name) + { + var sb = new StringBuilder(); + sb.Append($"{this.IntentToString(unit.Intent)} :: {name}"); + + string identifier = unit.Identifier; + if (!string.IsNullOrEmpty(identifier)) + { + sb.Append($" [{identifier}]"); + } + + return sb.ToString(); + } + + private string CreateInformation(ConfigurationUnit unit) + { + IConfigurationUnitProcessorDetails details = unit.Details; + ValueSet metadata = unit.Metadata; + + var sb = new StringBuilder(); + if (details != null) + { + this.CreateInformationWithDetails(ref sb, details, metadata); + } + else + { + this.CreateInformationWithoutDetails(ref sb, metadata); + } + + // -- Sample output footer -- + // Dependencies: dep1, dep2, ... + // Settings: + // <... settings splat> + var dependencies = unit.Dependencies; + if (dependencies.Count > 0) + { + var dependencySb = new StringBuilder(); + foreach (var dependency in dependencies) + { + dependencySb.Append($" {dependency}"); + } + + sb.AppendLine($" {string.Format(Resources.ConfigurationDependencies, dependencySb.ToString())}"); + } + + var settings = unit.Settings; + if (settings.Count > 0) + { + sb.AppendLine($" {Resources.ConfigurationSettings}"); + this.AppendValueSet(ref sb, settings, 4); + } + + return sb.ToString(); + } + + // -- Sample output when IConfigurationUnitProcessorDetails present -- + // Intent :: UnitName [Identifier] + // UnitDocumentationUri + // Description + // "Module": ModuleName "by" Author / Publisher (IsLocal / ModuleSource) + // "Signed by": SigningCertificateChain (leaf subject CN) + // PublishedModuleUri / ModuleDocumentationUri + // ModuleDescription + private void CreateInformationWithDetails(ref StringBuilder sb, IConfigurationUnitProcessorDetails details, ValueSet directives) + { + var unitDocumentationUri = details.UnitDocumentationUri; + if (unitDocumentationUri != null) + { + sb.AppendLine($" {unitDocumentationUri.AbsoluteUri}"); + } + + var unitDescriptionFromDetails = details.UnitDescription; + if (!string.IsNullOrEmpty(unitDescriptionFromDetails)) + { + sb.AppendLine($" {unitDescriptionFromDetails}"); + } + else + { + var unitDescriptionFromDirectives = directives.TryGetStringValue(Description); + if (!string.IsNullOrEmpty(unitDescriptionFromDirectives)) + { + sb.AppendLine($" {unitDescriptionFromDirectives}"); + } + } + + var author = details.Author; + if (string.IsNullOrEmpty(author)) + { + author = details.Publisher; + } + + if (details.IsLocal) + { + sb.AppendLine($" {string.Format(Resources.ConfigurationModuleWithDetails, details.ModuleName, author, Resources.ConfigurationLocal)}"); + } + else + { + sb.AppendLine($" {string.Format(Resources.ConfigurationModuleWithDetails, details.ModuleName, author, details.ModuleSource)}"); + } + + // TODO: see signature information in ConfigurationFlow.cpp + var moduleUri = details.PublishedModuleUri; + if (moduleUri == null) + { + moduleUri = details.ModuleDocumentationUri; + } + + if (moduleUri != null) + { + sb.AppendLine($" {moduleUri.AbsoluteUri}"); + } + + var moduleDescription = details.ModuleDescription; + if (!string.IsNullOrEmpty(moduleDescription)) + { + sb.AppendLine($" {moduleDescription}"); + } + } + + // -- Sample output when no IConfigurationUnitProcessorDetails present -- + // Intent :: UnitName [identifier] + // Description (from directives) + // "Module": module + private void CreateInformationWithoutDetails(ref StringBuilder sb, ValueSet directives) + { + var unitDescriptionFromDirectives = directives.TryGetStringValue(Description); + if (!string.IsNullOrEmpty(unitDescriptionFromDirectives)) + { + sb.AppendLine($" {unitDescriptionFromDirectives}"); + } + + var unitModuleFromDirectives = directives.TryGetStringValue(Module); + if (!string.IsNullOrEmpty(unitModuleFromDirectives)) + { + sb.AppendLine($" {string.Format(Resources.ConfigurationModuleNameOnly, unitModuleFromDirectives)}"); + } + } + + private void AppendValueSet(ref StringBuilder sb, ValueSet valueSet, int indent) + { + var indentString = new string(' ', indent); + + foreach (var value in valueSet) + { + sb.Append($"{indentString}{value.Key}:"); + + // Can't use IPropertyValue here... + var obj = value.Value; + var innerValueSet = obj as ValueSet; + if (innerValueSet != null) + { + sb.AppendLine(); + if (innerValueSet.ContainsKey(TreatAsArray)) + { + this.AppendValueSetAsArray(ref sb, innerValueSet, indent + 2); + } + else + { + this.AppendValueSet(ref sb, innerValueSet, indent + 2); + } + } + else + { + this.AppendPropertyValue(ref sb, obj); + } + } + } + + private void AppendValueSetAsArray(ref StringBuilder sb, ValueSet valueSet, int indent) + { + var indentString = new string(' ', indent); + + var sortedList = new SortedList(); + + foreach (var keyValuePair in valueSet) + { + if (keyValuePair.Key != TreatAsArray) + { + if (int.TryParse(keyValuePair.Key, out int key)) + { + sortedList.Add(key, keyValuePair.Value); + } + else + { + throw new InvalidOperationException(keyValuePair.Key); + } + } + } + + foreach (var arrayValue in sortedList) + { + sb.Append($"{indentString}-"); + var obj = arrayValue.Value; + + var innerValueSet = obj as ValueSet; + if (innerValueSet == null) + { + this.AppendPropertyValue(ref sb, obj); + } + else + { + var size = innerValueSet.Count; + if (size > 0) + { + // First one is special. + var first = innerValueSet.First(); + sb.Append($" {first.Key}:"); + + var firstValueSet = first.Value as ValueSet; + if (firstValueSet == null) + { + this.AppendPropertyValue(ref sb, first.Value); + } + else + { + sb.AppendLine(); + this.AppendValueSet(ref sb, firstValueSet, indent + 4); + } + + if (size > 1) + { + innerValueSet.Remove(first.Key); + this.AppendValueSet(ref sb, innerValueSet, indent + 2); + innerValueSet.Add(first); + } + } + } + } + } + + private void AppendPropertyValue(ref StringBuilder sb, object value) + { + Type type = value.GetType(); + if (type == typeof(string)) + { + sb.AppendLine($" {(string)value}"); + } + else if (type == typeof(bool)) + { + string message = (bool)value ? "true" : "false"; + sb.AppendLine($" {message}"); + } + else if (type == typeof(long)) + { + sb.AppendLine($" {(long)value}"); + } + else + { + sb.AppendLine($" [Debug:PropertyType={type}]"); + } + } + + private string IntentToString(ConfigurationUnitIntent intent) + { + return intent switch + { + ConfigurationUnitIntent.Assert => Resources.ConfigurationAssert, + ConfigurationUnitIntent.Inform => Resources.ConfigurationInform, + ConfigurationUnitIntent.Apply => Resources.ConfigurationApply, + _ => string.Empty, + }; + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/GetConfigurationSetDetailsProgressOutput.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/GetConfigurationSetDetailsProgressOutput.cs index b667bef98e..af243f22a5 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/GetConfigurationSetDetailsProgressOutput.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/GetConfigurationSetDetailsProgressOutput.cs @@ -1,55 +1,55 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Helpers -{ - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Common.Command; - using Windows.Foundation; - - /// - /// Helper to handle progress callback from GetSetDetailsAsync. - /// - internal class GetConfigurationSetDetailsProgressOutput : ConfigurationSetProgressOutputBase - { - /// - /// Initializes a new instance of the class. - /// - /// Command that outputs the messages. - /// The activity id of the progress bar. - /// The activity. - /// The message in the progress bar. - /// The activity complete message. - /// Total of units expected. - public GetConfigurationSetDetailsProgressOutput(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) - : base(cmd, activityId, activity, inProgressMessage, completeMessage, totalUnitsExpected) - { - } - - /// - /// Gets the units shown. - /// - public int UnitsShown - { - get { return this.UnitsCompleted.Count; } - } - - /// - public override void Progress(IAsyncOperationWithProgress operation, GetConfigurationUnitDetailsResult result) - { - this.HandleProgress(operation.GetResults()); - } - - /// - public override void HandleProgress(GetConfigurationSetDetailsResult result) - { - foreach (var unitResult in result.UnitResults) - { - this.CompleteUnit(unitResult.Unit); - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Helpers +{ + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Common.Command; + using Windows.Foundation; + + /// + /// Helper to handle progress callback from GetSetDetailsAsync. + /// + internal class GetConfigurationSetDetailsProgressOutput : ConfigurationSetProgressOutputBase + { + /// + /// Initializes a new instance of the class. + /// + /// Command that outputs the messages. + /// The activity id of the progress bar. + /// The activity. + /// The message in the progress bar. + /// The activity complete message. + /// Total of units expected. + public GetConfigurationSetDetailsProgressOutput(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) + : base(cmd, activityId, activity, inProgressMessage, completeMessage, totalUnitsExpected) + { + } + + /// + /// Gets the units shown. + /// + public int UnitsShown + { + get { return this.UnitsCompleted.Count; } + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, GetConfigurationUnitDetailsResult result) + { + this.HandleProgress(operation.GetResults()); + } + + /// + public override void HandleProgress(GetConfigurationSetDetailsResult result) + { + foreach (var unitResult in result.UnitResults) + { + this.CompleteUnit(unitResult.Unit); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/OpenConfigurationParameters.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/OpenConfigurationParameters.cs index 91065e7f80..0611c7abf2 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/OpenConfigurationParameters.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/OpenConfigurationParameters.cs @@ -1,200 +1,200 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Helpers -{ - using System; - using System.IO; - using System.Management.Automation; - using Microsoft.Management.Configuration.Processor; - using Microsoft.PowerShell; - using Microsoft.WinGet.Common.Command; - using Microsoft.WinGet.Resources; - - /// - /// The parameters used to open a configuration. - /// - internal class OpenConfigurationParameters - { - private const string Default = "default"; - private const string AllUsers = "allusers"; - private const string CurrentUser = "currentuser"; - - /// - /// Initializes a new instance of the class. - /// - /// PowerShellCmdlet. - /// The configuration file. - /// The module path to use. - /// Execution policy. - /// The processor path to use. - /// If telemetry can be used. - /// If the configuration is from history; changes the meaning of `ConfigFile` to the instance identifier. - public OpenConfigurationParameters( - PowerShellCmdlet pwshCmdlet, - string file, - string modulePath, - ExecutionPolicy executionPolicy, - string processorPath, - bool canUseTelemetry, - bool fromHistory = false) - { - if (!fromHistory) - { - this.ConfigFile = this.VerifyFile(file, pwshCmdlet); - } - else - { - this.ConfigFile = file; - } - - this.InitializeModulePath(modulePath); - this.ExecutionPolicy = executionPolicy; - this.Policy = this.GetConfigurationProcessorPolicy(executionPolicy); - this.ProcessorPath = processorPath; - this.CanUseTelemetry = canUseTelemetry; - this.FromHistory = fromHistory; - } - - /// - /// Initializes a new instance of the class. - /// - /// PowerShellCmdlet. - /// The module path to use. - /// Execution policy. - /// The processor path to use. - /// If telemetry can be used. - public OpenConfigurationParameters( - PowerShellCmdlet pwshCmdlet, - string modulePath, - ExecutionPolicy executionPolicy, - string processorPath, - bool canUseTelemetry) - { - this.ConfigFile = string.Empty; - this.InitializeModulePath(modulePath); - this.ExecutionPolicy = executionPolicy; - this.Policy = this.GetConfigurationProcessorPolicy(executionPolicy); - this.ProcessorPath = processorPath; - this.CanUseTelemetry = canUseTelemetry; - } - - /// - /// Gets the configuration file. - /// - public string ConfigFile { get; } - - /// - /// Gets the location to install the modules. - /// - public PowerShellConfigurationProcessorLocation Location { get; private set; } - - /// - /// Gets the custom location to install modules. - /// - public string? CustomLocation { get; private set; } - - /// - /// Gets the original ExecutionPolicy value. - /// - public ExecutionPolicy ExecutionPolicy { get; private set; } = ExecutionPolicy.Undefined; - - /// - /// Gets execution policy of the processor. - /// - public PowerShellConfigurationProcessorPolicy Policy { get; } - - /// - /// Gets DSCv3 processor path. - /// - public string ProcessorPath { get; } - - /// - /// Gets a value indicating whether to use telemetry or not. - /// - public bool CanUseTelemetry { get; } - - /// - /// Gets a value indicating whether the configuration is from history. - /// - public bool FromHistory { get; } - - private string VerifyFile(string filePath, PowerShellCmdlet pwshCmdlet) - { - if (!Path.IsPathRooted(filePath)) - { - filePath = Path.GetFullPath( - Path.Combine( - pwshCmdlet.GetCurrentFileSystemLocation(), - filePath)); - } - else - { - filePath = Path.GetFullPath(filePath); - } - - if (!File.Exists(filePath)) - { - throw new FileNotFoundException(filePath); - } - - return filePath; - } - - private PowerShellConfigurationProcessorPolicy GetConfigurationProcessorPolicy(ExecutionPolicy policy) - { - return policy switch - { - ExecutionPolicy.Unrestricted => PowerShellConfigurationProcessorPolicy.Unrestricted, - ExecutionPolicy.RemoteSigned => PowerShellConfigurationProcessorPolicy.RemoteSigned, - ExecutionPolicy.AllSigned => PowerShellConfigurationProcessorPolicy.AllSigned, - ExecutionPolicy.Restricted => PowerShellConfigurationProcessorPolicy.Restricted, - ExecutionPolicy.Bypass => PowerShellConfigurationProcessorPolicy.Bypass, - _ => throw new InvalidOperationException(), - }; - } - - private void InitializeModulePath(string modulePath) - { - // TODO: Create cmdlet that specify the global custom location for a PowerShell session. - if (!string.IsNullOrEmpty(modulePath)) - { - if (string.Compare(modulePath, Default, true) == 0) - { - this.Location = PowerShellConfigurationProcessorLocation.Default; - } - else if (string.Compare(modulePath, CurrentUser, true) == 0) - { - this.Location = PowerShellConfigurationProcessorLocation.CurrentUser; - } - else if (string.Compare(modulePath, AllUsers, true) == 0) - { - if (!Utilities.ExecutingAsAdministrator) - { - throw new ArgumentException(Resources.ConfigurationAllUsersElevated); - } - - this.Location = PowerShellConfigurationProcessorLocation.AllUsers; - } - else - { - string customLocation = modulePath; - if (!Path.IsPathRooted(customLocation)) - { - throw new ArgumentException(Resources.ConfigurationModulePathArgError); - } - - this.CustomLocation = Path.GetFullPath(customLocation); - this.Location = PowerShellConfigurationProcessorLocation.Custom; - } - } - else - { - this.Location = PowerShellConfigurationProcessorLocation.WinGetModulePath; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Helpers +{ + using System; + using System.IO; + using System.Management.Automation; + using Microsoft.Management.Configuration.Processor; + using Microsoft.PowerShell; + using Microsoft.WinGet.Common.Command; + using Microsoft.WinGet.Resources; + + /// + /// The parameters used to open a configuration. + /// + internal class OpenConfigurationParameters + { + private const string Default = "default"; + private const string AllUsers = "allusers"; + private const string CurrentUser = "currentuser"; + + /// + /// Initializes a new instance of the class. + /// + /// PowerShellCmdlet. + /// The configuration file. + /// The module path to use. + /// Execution policy. + /// The processor path to use. + /// If telemetry can be used. + /// If the configuration is from history; changes the meaning of `ConfigFile` to the instance identifier. + public OpenConfigurationParameters( + PowerShellCmdlet pwshCmdlet, + string file, + string modulePath, + ExecutionPolicy executionPolicy, + string processorPath, + bool canUseTelemetry, + bool fromHistory = false) + { + if (!fromHistory) + { + this.ConfigFile = this.VerifyFile(file, pwshCmdlet); + } + else + { + this.ConfigFile = file; + } + + this.InitializeModulePath(modulePath); + this.ExecutionPolicy = executionPolicy; + this.Policy = this.GetConfigurationProcessorPolicy(executionPolicy); + this.ProcessorPath = processorPath; + this.CanUseTelemetry = canUseTelemetry; + this.FromHistory = fromHistory; + } + + /// + /// Initializes a new instance of the class. + /// + /// PowerShellCmdlet. + /// The module path to use. + /// Execution policy. + /// The processor path to use. + /// If telemetry can be used. + public OpenConfigurationParameters( + PowerShellCmdlet pwshCmdlet, + string modulePath, + ExecutionPolicy executionPolicy, + string processorPath, + bool canUseTelemetry) + { + this.ConfigFile = string.Empty; + this.InitializeModulePath(modulePath); + this.ExecutionPolicy = executionPolicy; + this.Policy = this.GetConfigurationProcessorPolicy(executionPolicy); + this.ProcessorPath = processorPath; + this.CanUseTelemetry = canUseTelemetry; + } + + /// + /// Gets the configuration file. + /// + public string ConfigFile { get; } + + /// + /// Gets the location to install the modules. + /// + public PowerShellConfigurationProcessorLocation Location { get; private set; } + + /// + /// Gets the custom location to install modules. + /// + public string? CustomLocation { get; private set; } + + /// + /// Gets the original ExecutionPolicy value. + /// + public ExecutionPolicy ExecutionPolicy { get; private set; } = ExecutionPolicy.Undefined; + + /// + /// Gets execution policy of the processor. + /// + public PowerShellConfigurationProcessorPolicy Policy { get; } + + /// + /// Gets DSCv3 processor path. + /// + public string ProcessorPath { get; } + + /// + /// Gets a value indicating whether to use telemetry or not. + /// + public bool CanUseTelemetry { get; } + + /// + /// Gets a value indicating whether the configuration is from history. + /// + public bool FromHistory { get; } + + private string VerifyFile(string filePath, PowerShellCmdlet pwshCmdlet) + { + if (!Path.IsPathRooted(filePath)) + { + filePath = Path.GetFullPath( + Path.Combine( + pwshCmdlet.GetCurrentFileSystemLocation(), + filePath)); + } + else + { + filePath = Path.GetFullPath(filePath); + } + + if (!File.Exists(filePath)) + { + throw new FileNotFoundException(filePath); + } + + return filePath; + } + + private PowerShellConfigurationProcessorPolicy GetConfigurationProcessorPolicy(ExecutionPolicy policy) + { + return policy switch + { + ExecutionPolicy.Unrestricted => PowerShellConfigurationProcessorPolicy.Unrestricted, + ExecutionPolicy.RemoteSigned => PowerShellConfigurationProcessorPolicy.RemoteSigned, + ExecutionPolicy.AllSigned => PowerShellConfigurationProcessorPolicy.AllSigned, + ExecutionPolicy.Restricted => PowerShellConfigurationProcessorPolicy.Restricted, + ExecutionPolicy.Bypass => PowerShellConfigurationProcessorPolicy.Bypass, + _ => throw new InvalidOperationException(), + }; + } + + private void InitializeModulePath(string modulePath) + { + // TODO: Create cmdlet that specify the global custom location for a PowerShell session. + if (!string.IsNullOrEmpty(modulePath)) + { + if (string.Compare(modulePath, Default, true) == 0) + { + this.Location = PowerShellConfigurationProcessorLocation.Default; + } + else if (string.Compare(modulePath, CurrentUser, true) == 0) + { + this.Location = PowerShellConfigurationProcessorLocation.CurrentUser; + } + else if (string.Compare(modulePath, AllUsers, true) == 0) + { + if (!Utilities.ExecutingAsAdministrator) + { + throw new ArgumentException(Resources.ConfigurationAllUsersElevated); + } + + this.Location = PowerShellConfigurationProcessorLocation.AllUsers; + } + else + { + string customLocation = modulePath; + if (!Path.IsPathRooted(customLocation)) + { + throw new ArgumentException(Resources.ConfigurationModulePathArgError); + } + + this.CustomLocation = Path.GetFullPath(customLocation); + this.Location = PowerShellConfigurationProcessorLocation.Custom; + } + } + else + { + this.Location = PowerShellConfigurationProcessorLocation.WinGetModulePath; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/TestConfigurationSetProgressOutput.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/TestConfigurationSetProgressOutput.cs index e8aa26a7b4..d78ec2dc38 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/TestConfigurationSetProgressOutput.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/TestConfigurationSetProgressOutput.cs @@ -1,58 +1,58 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Helpers -{ - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Common.Command; - using Windows.Foundation; - - /// - /// Helper to handle progress callbacks for TestSetAsync. - /// - internal class TestConfigurationSetProgressOutput : ConfigurationSetProgressOutputBase - { - private bool isFirstProgress = true; - - /// - /// Initializes a new instance of the class. - /// - /// Command that outputs the messages. - /// The activity id of the progress bar. - /// The activity. - /// The message in the progress bar. - /// The activity complete message. - /// Total of units expected. - public TestConfigurationSetProgressOutput(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) - : base(cmd, activityId, activity, inProgressMessage, completeMessage, totalUnitsExpected) - { - } - - /// - public override void Progress(IAsyncOperationWithProgress operation, TestConfigurationUnitResult data) - { - if (this.isFirstProgress) - { - this.HandleProgress(operation.GetResults()); - } - - this.CompleteUnit(data.Unit); - } - - /// - public override void HandleProgress(TestConfigurationSetResult result) - { - if (!this.isFirstProgress) - { - this.isFirstProgress = false; - foreach (var unitResult in result.UnitResults) - { - this.CompleteUnit(unitResult.Unit); - } - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Helpers +{ + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Common.Command; + using Windows.Foundation; + + /// + /// Helper to handle progress callbacks for TestSetAsync. + /// + internal class TestConfigurationSetProgressOutput : ConfigurationSetProgressOutputBase + { + private bool isFirstProgress = true; + + /// + /// Initializes a new instance of the class. + /// + /// Command that outputs the messages. + /// The activity id of the progress bar. + /// The activity. + /// The message in the progress bar. + /// The activity complete message. + /// Total of units expected. + public TestConfigurationSetProgressOutput(PowerShellCmdlet cmd, int activityId, string activity, string inProgressMessage, string completeMessage, int totalUnitsExpected) + : base(cmd, activityId, activity, inProgressMessage, completeMessage, totalUnitsExpected) + { + } + + /// + public override void Progress(IAsyncOperationWithProgress operation, TestConfigurationUnitResult data) + { + if (this.isFirstProgress) + { + this.HandleProgress(operation.GetResults()); + } + + this.CompleteUnit(data.Unit); + } + + /// + public override void HandleProgress(TestConfigurationSetResult result) + { + if (!this.isFirstProgress) + { + this.isFirstProgress = false; + foreach (var unitResult in result.UnitResults) + { + this.CompleteUnit(unitResult.Unit); + } + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/Utilities.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/Utilities.cs index 6c02fb0e42..0f84321fc5 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/Utilities.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Helpers/Utilities.cs @@ -1,123 +1,123 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.Helpers -{ - using System; - using System.Linq; - using System.Management.Automation; - using System.Management.Automation.Host; - using System.Security.Principal; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.PSObjects; - - /// - /// Helper methods. - /// - internal static class Utilities - { - /// - /// Gets a value indicating whether the current assembly is executing in an administrative context. - /// - [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] - public static bool ExecutingAsAdministrator - { - get - { - WindowsIdentity identity = WindowsIdentity.GetCurrent(); - WindowsPrincipal principal = new (identity); - return principal.IsInRole(WindowsBuiltInRole.Administrator); - } - } - - /// - /// Helper for StreamType.Information. Creates a HostInformationMessage with - /// the specified information. - /// - /// Message. - /// Add not to add a new line. - /// Optional foreground color. - /// Optional background color. - /// The information message. - public static HostInformationMessage CreateInformationMessage( - string message, - bool noNewLine = false, - ConsoleColor? foregroundColor = null, - ConsoleColor? backgroundColor = null) - { - var infoMessage = new HostInformationMessage - { - Message = message, - NoNewLine = noNewLine, - }; - - try - { - infoMessage.ForegroundColor = foregroundColor; - infoMessage.BackgroundColor = backgroundColor; - } - catch (HostException) - { - // Expected if the host is not interactive, or doesn't have Foreground / Background colors. - } - - return infoMessage; - } - - /// - /// Splits the message into lines. - /// - /// Message. - /// Max lines. - /// Lines. - public static string[] SplitIntoLines(string message, int maxLines) - { - var lines = message.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); - if (lines.Length > maxLines) - { - return lines.Take(maxLines).ToArray(); - } - - return lines; - } - - /// - /// Converts ConfigurationTestResult value to PSConfigurationTestResult. - /// - /// ConfigurationTestResult value. - /// PSConfigurationTestResult. - public static PSConfigurationTestResult ToPSConfigurationTestResult(ConfigurationTestResult value) - { - return value switch - { - ConfigurationTestResult.Unknown => PSConfigurationTestResult.Unknown, - ConfigurationTestResult.Positive => PSConfigurationTestResult.Positive, - ConfigurationTestResult.Negative => PSConfigurationTestResult.Negative, - ConfigurationTestResult.Failed => PSConfigurationTestResult.Failed, - ConfigurationTestResult.NotRun => PSConfigurationTestResult.NotRun, - _ => throw new InvalidOperationException(), - }; - } - - /// - /// Converts ConfigurationUnitState value to PSConfigurationUnitState. - /// - /// ConfigurationUnitState value. - /// PSConfigurationUnitState. - public static PSConfigurationUnitState ToPSConfigurationUnitState(ConfigurationUnitState value) - { - return value switch - { - ConfigurationUnitState.Unknown => PSConfigurationUnitState.Unknown, - ConfigurationUnitState.Pending => PSConfigurationUnitState.Pending, - ConfigurationUnitState.InProgress => PSConfigurationUnitState.InProgress, - ConfigurationUnitState.Completed => PSConfigurationUnitState.Completed, - ConfigurationUnitState.Skipped => PSConfigurationUnitState.Skipped, - _ => throw new InvalidOperationException(), - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.Helpers +{ + using System; + using System.Linq; + using System.Management.Automation; + using System.Management.Automation.Host; + using System.Security.Principal; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.PSObjects; + + /// + /// Helper methods. + /// + internal static class Utilities + { + /// + /// Gets a value indicating whether the current assembly is executing in an administrative context. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Interoperability", "CA1416:Validate platform compatibility", Justification = "Windows only API")] + public static bool ExecutingAsAdministrator + { + get + { + WindowsIdentity identity = WindowsIdentity.GetCurrent(); + WindowsPrincipal principal = new (identity); + return principal.IsInRole(WindowsBuiltInRole.Administrator); + } + } + + /// + /// Helper for StreamType.Information. Creates a HostInformationMessage with + /// the specified information. + /// + /// Message. + /// Add not to add a new line. + /// Optional foreground color. + /// Optional background color. + /// The information message. + public static HostInformationMessage CreateInformationMessage( + string message, + bool noNewLine = false, + ConsoleColor? foregroundColor = null, + ConsoleColor? backgroundColor = null) + { + var infoMessage = new HostInformationMessage + { + Message = message, + NoNewLine = noNewLine, + }; + + try + { + infoMessage.ForegroundColor = foregroundColor; + infoMessage.BackgroundColor = backgroundColor; + } + catch (HostException) + { + // Expected if the host is not interactive, or doesn't have Foreground / Background colors. + } + + return infoMessage; + } + + /// + /// Splits the message into lines. + /// + /// Message. + /// Max lines. + /// Lines. + public static string[] SplitIntoLines(string message, int maxLines) + { + var lines = message.Split(new char[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + if (lines.Length > maxLines) + { + return lines.Take(maxLines).ToArray(); + } + + return lines; + } + + /// + /// Converts ConfigurationTestResult value to PSConfigurationTestResult. + /// + /// ConfigurationTestResult value. + /// PSConfigurationTestResult. + public static PSConfigurationTestResult ToPSConfigurationTestResult(ConfigurationTestResult value) + { + return value switch + { + ConfigurationTestResult.Unknown => PSConfigurationTestResult.Unknown, + ConfigurationTestResult.Positive => PSConfigurationTestResult.Positive, + ConfigurationTestResult.Negative => PSConfigurationTestResult.Negative, + ConfigurationTestResult.Failed => PSConfigurationTestResult.Failed, + ConfigurationTestResult.NotRun => PSConfigurationTestResult.NotRun, + _ => throw new InvalidOperationException(), + }; + } + + /// + /// Converts ConfigurationUnitState value to PSConfigurationUnitState. + /// + /// ConfigurationUnitState value. + /// PSConfigurationUnitState. + public static PSConfigurationUnitState ToPSConfigurationUnitState(ConfigurationUnitState value) + { + return value switch + { + ConfigurationUnitState.Unknown => PSConfigurationUnitState.Unknown, + ConfigurationUnitState.Pending => PSConfigurationUnitState.Pending, + ConfigurationUnitState.InProgress => PSConfigurationUnitState.InProgress, + ConfigurationUnitState.Completed => PSConfigurationUnitState.Completed, + ConfigurationUnitState.Skipped => PSConfigurationUnitState.Skipped, + _ => throw new InvalidOperationException(), + }; + } + } +} 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 74c60332d5..3aa35d3718 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 @@ -1,64 +1,64 @@ - - - - - net8.0-windows10.0.26100 - false - enable - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - true - $(OutputPath)\$(MSBuildProjectName).xml - win - Debug;Release;ReleaseStatic - - - - true - - - - true - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - True - True - Resources.resx - - - - - - ResXFileCodeGenerator - Resources.Designer.cs - Microsoft.WinGet.Resources - - - - + + + + + net8.0-windows10.0.26100 + false + enable + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + true + $(OutputPath)\$(MSBuildProjectName).xml + win + Debug;Release;ReleaseStatic + + + + true + + + + true + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + Microsoft.WinGet.Resources + + + + diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationSetResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationSetResult.cs index bc3170d864..ed9316da11 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationSetResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationSetResult.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Exceptions; - - /// - /// Wrapper for ApplyConfigurationSetResult. - /// - public class PSApplyConfigurationSetResult - { - /// - /// Initializes a new instance of the class. - /// - /// Apply set result. - internal PSApplyConfigurationSetResult(ApplyConfigurationSetResult applySetResult) - { - this.ResultCode = applySetResult.ResultCode?.HResult ?? ErrorCodes.S_OK; - - var unitResults = new List(); - foreach (var unitResult in applySetResult.UnitResults) - { - unitResults.Add(new PSApplyConfigurationUnitResult(unitResult)); - } - - this.UnitResults = unitResults; - } - - /// - /// Gets the result code. - /// - public int ResultCode { get; private init; } - - /// - /// Gets the results of the units. - /// - public IReadOnlyList UnitResults { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Exceptions; + + /// + /// Wrapper for ApplyConfigurationSetResult. + /// + public class PSApplyConfigurationSetResult + { + /// + /// Initializes a new instance of the class. + /// + /// Apply set result. + internal PSApplyConfigurationSetResult(ApplyConfigurationSetResult applySetResult) + { + this.ResultCode = applySetResult.ResultCode?.HResult ?? ErrorCodes.S_OK; + + var unitResults = new List(); + foreach (var unitResult in applySetResult.UnitResults) + { + unitResults.Add(new PSApplyConfigurationUnitResult(unitResult)); + } + + this.UnitResults = unitResults; + } + + /// + /// Gets the result code. + /// + public int ResultCode { get; private init; } + + /// + /// Gets the results of the units. + /// + public IReadOnlyList UnitResults { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationUnitResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationUnitResult.cs index 3b7f161c27..0b604f2f11 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationUnitResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSApplyConfigurationUnitResult.cs @@ -1,43 +1,43 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Helpers; - - /// - /// The apply result of a configuration unit. - /// - public class PSApplyConfigurationUnitResult : PSUnitResult - { - /// - /// Initializes a new instance of the class. - /// - /// Apply unit result. - internal PSApplyConfigurationUnitResult(ApplyConfigurationUnitResult unitResult) - : base(unitResult.Unit, unitResult.ResultInformation) - { - this.State = Utilities.ToPSConfigurationUnitState(unitResult.State); - this.PreviouslyInDesiredState = unitResult.PreviouslyInDesiredState; - } - - /// - /// Gets the unit state. - /// - public PSConfigurationUnitState State { get; private init; } - - /// - /// Gets a value indicating whether the unit was in a previous desired state. - /// - public bool PreviouslyInDesiredState { get; private init; } - - /// - /// Gets a value indicating whether a reboot is required after the configuration unit was applied. - /// - public bool RebootRequired { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Helpers; + + /// + /// The apply result of a configuration unit. + /// + public class PSApplyConfigurationUnitResult : PSUnitResult + { + /// + /// Initializes a new instance of the class. + /// + /// Apply unit result. + internal PSApplyConfigurationUnitResult(ApplyConfigurationUnitResult unitResult) + : base(unitResult.Unit, unitResult.ResultInformation) + { + this.State = Utilities.ToPSConfigurationUnitState(unitResult.State); + this.PreviouslyInDesiredState = unitResult.PreviouslyInDesiredState; + } + + /// + /// Gets the unit state. + /// + public PSConfigurationUnitState State { get; private init; } + + /// + /// Gets a value indicating whether the unit was in a previous desired state. + /// + public bool PreviouslyInDesiredState { get; private init; } + + /// + /// Gets a value indicating whether a reboot is required after the configuration unit was applied. + /// + public bool RebootRequired { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationJob.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationJob.cs index d9f28fee63..16bb1d954d 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationJob.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationJob.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using System.Threading.Tasks; - using Microsoft.WinGet.Common.Command; - - /// - /// This is a wrapper object for asynchronous task for this module. - /// Contains the necessary information to continue the operation. - /// - public class PSConfigurationJob - { - /// - /// Initializes a new instance of the class. - /// - /// The apply configuration task. - /// The start command. - internal PSConfigurationJob( - Task applyConfigTask, - PowerShellCmdlet startCommand) - { - this.ApplyConfigurationTask = applyConfigTask; - this.StartCommand = startCommand; - } - - /// - /// Gets the running configuration task. - /// - internal Task ApplyConfigurationTask { get; private set; } - - /// - /// Gets the command that started async operation. - /// - internal PowerShellCmdlet StartCommand { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using System.Threading.Tasks; + using Microsoft.WinGet.Common.Command; + + /// + /// This is a wrapper object for asynchronous task for this module. + /// Contains the necessary information to continue the operation. + /// + public class PSConfigurationJob + { + /// + /// Initializes a new instance of the class. + /// + /// The apply configuration task. + /// The start command. + internal PSConfigurationJob( + Task applyConfigTask, + PowerShellCmdlet startCommand) + { + this.ApplyConfigurationTask = applyConfigTask; + this.StartCommand = startCommand; + } + + /// + /// Gets the running configuration task. + /// + internal Task ApplyConfigurationTask { get; private set; } + + /// + /// Gets the command that started async operation. + /// + internal PowerShellCmdlet StartCommand { get; private set; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationProcessor.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationProcessor.cs index 93bd96c594..8c69c20ad6 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationProcessor.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationProcessor.cs @@ -1,79 +1,79 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using System; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Common.Command; - - /// - /// Creates configuration processor and set up diagnostic logging. - /// If this object is the input of another cmdlet and the cmdlet is not a - /// long running task (aka not Continue-*) for now the caller is responsible - /// of updating the AsyncCommand of this object. - /// In the future we can implement a singleton that handles all the signaling - /// for main thread actions. - /// - public class PSConfigurationProcessor - { - private static readonly object CmdletLock = new (); - - private PowerShellCmdlet diagnosticCommand; - - /// - /// Initializes a new instance of the class. - /// - /// Processor Factory. If null, the functions (i.e. set/unit operations) may be limited. - /// AsyncCommand to use for diagnostics. - /// If telemetry can be used. - internal PSConfigurationProcessor(IConfigurationSetProcessorFactory? factory, PowerShellCmdlet diagnosticCommand, bool canUseTelemetry) - { - this.Processor = new ConfigurationProcessor(factory); - this.Processor.MinimumLevel = DiagnosticLevel.Verbose; - this.Processor.Caller = "Microsoft.WinGet.Configuration"; - this.Processor.Diagnostics += (sender, args) => this.LogConfigurationDiagnostics(args); - this.Processor.GenerateTelemetryEvents = canUseTelemetry; - this.diagnosticCommand = diagnosticCommand; - } - - /// - /// Gets the ConfigurationProcessor. - /// - internal ConfigurationProcessor Processor { get; private set; } - - /// - /// Updates the cmdlet that is used for diagnostics. - /// - /// New diagnostic command. - internal void UpdateDiagnosticCmdlet(PowerShellCmdlet newDiagnosticCommand) - { - lock (CmdletLock) - { - this.diagnosticCommand = newDiagnosticCommand; - } - } - - private void LogConfigurationDiagnostics(IDiagnosticInformation diagnosticInformation) - { - try - { - PowerShellCmdlet pwshCmdlet = this.diagnosticCommand; - if (pwshCmdlet != null) - { - // Printing each diagnostic error in their own equivalent stream is too noisy. - // If users want them they have to specify -Verbose. - string tag = $"[Diagnostic{diagnosticInformation.Level}] "; - pwshCmdlet.Write(StreamType.Verbose, $"{tag}{diagnosticInformation.Message}"); - } - } - catch (Exception) - { - // Please don't throw here. - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using System; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Common.Command; + + /// + /// Creates configuration processor and set up diagnostic logging. + /// If this object is the input of another cmdlet and the cmdlet is not a + /// long running task (aka not Continue-*) for now the caller is responsible + /// of updating the AsyncCommand of this object. + /// In the future we can implement a singleton that handles all the signaling + /// for main thread actions. + /// + public class PSConfigurationProcessor + { + private static readonly object CmdletLock = new (); + + private PowerShellCmdlet diagnosticCommand; + + /// + /// Initializes a new instance of the class. + /// + /// Processor Factory. If null, the functions (i.e. set/unit operations) may be limited. + /// AsyncCommand to use for diagnostics. + /// If telemetry can be used. + internal PSConfigurationProcessor(IConfigurationSetProcessorFactory? factory, PowerShellCmdlet diagnosticCommand, bool canUseTelemetry) + { + this.Processor = new ConfigurationProcessor(factory); + this.Processor.MinimumLevel = DiagnosticLevel.Verbose; + this.Processor.Caller = "Microsoft.WinGet.Configuration"; + this.Processor.Diagnostics += (sender, args) => this.LogConfigurationDiagnostics(args); + this.Processor.GenerateTelemetryEvents = canUseTelemetry; + this.diagnosticCommand = diagnosticCommand; + } + + /// + /// Gets the ConfigurationProcessor. + /// + internal ConfigurationProcessor Processor { get; private set; } + + /// + /// Updates the cmdlet that is used for diagnostics. + /// + /// New diagnostic command. + internal void UpdateDiagnosticCmdlet(PowerShellCmdlet newDiagnosticCommand) + { + lock (CmdletLock) + { + this.diagnosticCommand = newDiagnosticCommand; + } + } + + private void LogConfigurationDiagnostics(IDiagnosticInformation diagnosticInformation) + { + try + { + PowerShellCmdlet pwshCmdlet = this.diagnosticCommand; + if (pwshCmdlet != null) + { + // Printing each diagnostic error in their own equivalent stream is too noisy. + // If users want them they have to specify -Verbose. + string tag = $"[Diagnostic{diagnosticInformation.Level}] "; + pwshCmdlet.Write(StreamType.Verbose, $"{tag}{diagnosticInformation.Message}"); + } + } + catch (Exception) + { + // Please don't throw here. + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationSet.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationSet.cs index ae8725ab00..2e797941ab 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationSet.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationSet.cs @@ -1,166 +1,166 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using System; - using Microsoft.Management.Configuration; - - /// - /// Wrapper for ConfigurationSet. - /// - public sealed class PSConfigurationSet - { - private static readonly object ProcessorLock = new (); - private volatile bool hasDetails = false; - private volatile bool operationInProgress = false; - - /// - /// Initializes a new instance of the class. - /// - /// The configuration processor wrapper. - /// The configuration set. - internal PSConfigurationSet(PSConfigurationProcessor psProcessor, ConfigurationSet set) - { - this.PsProcessor = psProcessor; - this.Set = set; - } - - /// - /// Gets the name. - /// - public string Name - { - get - { - return this.Set.Name; - } - } - - /// - /// Gets the instance identifier. - /// - public Guid InstanceIdentifier - { - get - { - return this.Set.InstanceIdentifier; - } - } - - /// - /// Gets the origin. - /// - public string Origin - { - get - { - return this.Set.Origin; - } - } - - /// - /// Gets the source. - /// - public string Source - { - get - { - return this.Set.Path; - } - } - - /// - /// Gets the schema version. - /// - public string SchemaVersion - { - get - { - return this.Set.SchemaVersion; - } - } - - /// - /// Gets the state. - /// TODO: enable once implemented. - /// - internal string State - { - get - { - return this.Set.State.ToString(); - } - } - - /// - /// Gets or sets a value indicating whether apply ran. - /// TODO: remove once State is implemented. - /// - internal bool ApplyCompleted { get; set; } - - /// - /// Gets the PSConfigurationProcessor. - /// - internal PSConfigurationProcessor PsProcessor { get; private set; } - - /// - /// Gets the ConfigurationSet. - /// - internal ConfigurationSet Set { get; private set; } - - /// - /// Gets or sets a value indicating whether the details had been retrieved for this set. - /// - internal bool HasDetails - { - get - { - lock (ProcessorLock) - { - return this.hasDetails; - } - } - - set - { - lock (ProcessorLock) - { - this.hasDetails = value; - } - } - } - - /// - /// Checks if the object is being used by another cmdlet. If not, blocks it for the caller. - /// - /// True if no one is using me. - internal bool CanProcess() - { - lock (ProcessorLock) - { - if (!this.operationInProgress) - { - this.operationInProgress = true; - return true; - } - - return false; - } - } - - /// - /// The object is no longer in use by a cmdlet. - /// - internal void DoneProcessing() - { - lock (ProcessorLock) - { - this.operationInProgress = false; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using System; + using Microsoft.Management.Configuration; + + /// + /// Wrapper for ConfigurationSet. + /// + public sealed class PSConfigurationSet + { + private static readonly object ProcessorLock = new (); + private volatile bool hasDetails = false; + private volatile bool operationInProgress = false; + + /// + /// Initializes a new instance of the class. + /// + /// The configuration processor wrapper. + /// The configuration set. + internal PSConfigurationSet(PSConfigurationProcessor psProcessor, ConfigurationSet set) + { + this.PsProcessor = psProcessor; + this.Set = set; + } + + /// + /// Gets the name. + /// + public string Name + { + get + { + return this.Set.Name; + } + } + + /// + /// Gets the instance identifier. + /// + public Guid InstanceIdentifier + { + get + { + return this.Set.InstanceIdentifier; + } + } + + /// + /// Gets the origin. + /// + public string Origin + { + get + { + return this.Set.Origin; + } + } + + /// + /// Gets the source. + /// + public string Source + { + get + { + return this.Set.Path; + } + } + + /// + /// Gets the schema version. + /// + public string SchemaVersion + { + get + { + return this.Set.SchemaVersion; + } + } + + /// + /// Gets the state. + /// TODO: enable once implemented. + /// + internal string State + { + get + { + return this.Set.State.ToString(); + } + } + + /// + /// Gets or sets a value indicating whether apply ran. + /// TODO: remove once State is implemented. + /// + internal bool ApplyCompleted { get; set; } + + /// + /// Gets the PSConfigurationProcessor. + /// + internal PSConfigurationProcessor PsProcessor { get; private set; } + + /// + /// Gets the ConfigurationSet. + /// + internal ConfigurationSet Set { get; private set; } + + /// + /// Gets or sets a value indicating whether the details had been retrieved for this set. + /// + internal bool HasDetails + { + get + { + lock (ProcessorLock) + { + return this.hasDetails; + } + } + + set + { + lock (ProcessorLock) + { + this.hasDetails = value; + } + } + } + + /// + /// Checks if the object is being used by another cmdlet. If not, blocks it for the caller. + /// + /// True if no one is using me. + internal bool CanProcess() + { + lock (ProcessorLock) + { + if (!this.operationInProgress) + { + this.operationInProgress = true; + return true; + } + + return false; + } + } + + /// + /// The object is no longer in use by a cmdlet. + /// + internal void DoneProcessing() + { + lock (ProcessorLock) + { + this.operationInProgress = false; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationTestResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationTestResult.cs index 75bca6afe9..b6fbd679e7 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationTestResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationTestResult.cs @@ -1,39 +1,39 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - /// - /// Must match ConfigurationTestResult. - /// - public enum PSConfigurationTestResult - { - /// - /// The result is unknown. - /// - Unknown, - - /// - /// The system is in the state described by the configuration. - /// - Positive, - - /// - /// The system is not in the state described by the configuration. - /// - Negative, - - /// - /// Running the test failed. - /// - Failed, - - /// - /// The test was not run because it was not applicable. - /// - NotRun, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + /// + /// Must match ConfigurationTestResult. + /// + public enum PSConfigurationTestResult + { + /// + /// The result is unknown. + /// + Unknown, + + /// + /// The system is in the state described by the configuration. + /// + Positive, + + /// + /// The system is not in the state described by the configuration. + /// + Negative, + + /// + /// Running the test failed. + /// + Failed, + + /// + /// The test was not run because it was not applicable. + /// + NotRun, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationUnitState.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationUnitState.cs index b5b60366ec..2d24bea112 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationUnitState.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSConfigurationUnitState.cs @@ -1,39 +1,39 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - /// - /// Must match ConfigurationUnitState. - /// - public enum PSConfigurationUnitState - { - /// - /// The state of the configuration unit is unknown. - /// - Unknown, - - /// - /// The configuration unit is in the queue to be applied. - /// - Pending, - - /// - /// The configuration unit is actively being applied. - /// - InProgress, - - /// - /// The configuration unit has completed being applied. - /// - Completed, - - /// - /// The configuration unit was not applied due to external factors. - /// - Skipped, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + /// + /// Must match ConfigurationUnitState. + /// + public enum PSConfigurationUnitState + { + /// + /// The state of the configuration unit is unknown. + /// + Unknown, + + /// + /// The configuration unit is in the queue to be applied. + /// + Pending, + + /// + /// The configuration unit is actively being applied. + /// + InProgress, + + /// + /// The configuration unit has completed being applied. + /// + Completed, + + /// + /// The configuration unit was not applied due to external factors. + /// + Skipped, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSGetConfigurationDetailsResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSGetConfigurationDetailsResult.cs index 3fe2902c68..aae4b2c3db 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSGetConfigurationDetailsResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSGetConfigurationDetailsResult.cs @@ -1,49 +1,49 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using System; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Exceptions; - - /// - /// Result for getting the details of a unit. - /// - public class PSGetConfigurationDetailsResult - { - /// - /// Initializes a new instance of the class. - /// - /// Get unit details result. - internal PSGetConfigurationDetailsResult(GetConfigurationUnitDetailsResult result) - { - this.Type = result.Unit.Type; - this.ResultCode = result.ResultInformation?.ResultCode?.HResult ?? ErrorCodes.S_OK; - - if (result.ResultInformation?.ResultCode != null) - { - this.ErrorMessage = $"Failed to get unit details for {this.Type} 0x{this.ResultCode:X}" + - $"{Environment.NewLine}Description: '{result.ResultInformation.Description}'{Environment.NewLine}Details: '{result.ResultInformation.Details}'"; - } - } - - /// - /// Gets the unit type. - /// - public string Type { get; private init; } - - /// - /// Gets the result code. - /// - public int ResultCode { get; private init; } - - /// - /// Gets the error message. - /// - public string? ErrorMessage { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using System; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Exceptions; + + /// + /// Result for getting the details of a unit. + /// + public class PSGetConfigurationDetailsResult + { + /// + /// Initializes a new instance of the class. + /// + /// Get unit details result. + internal PSGetConfigurationDetailsResult(GetConfigurationUnitDetailsResult result) + { + this.Type = result.Unit.Type; + this.ResultCode = result.ResultInformation?.ResultCode?.HResult ?? ErrorCodes.S_OK; + + if (result.ResultInformation?.ResultCode != null) + { + this.ErrorMessage = $"Failed to get unit details for {this.Type} 0x{this.ResultCode:X}" + + $"{Environment.NewLine}Description: '{result.ResultInformation.Description}'{Environment.NewLine}Details: '{result.ResultInformation.Details}'"; + } + } + + /// + /// Gets the unit type. + /// + public string Type { get; private init; } + + /// + /// Gets the result code. + /// + public int ResultCode { get; private init; } + + /// + /// Gets the error message. + /// + public string? ErrorMessage { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationSetResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationSetResult.cs index a0e3c47202..3c19716c04 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationSetResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationSetResult.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Helpers; - - /// - /// Wrapper for TestConfigurationSetResult. - /// - public class PSTestConfigurationSetResult - { - /// - /// Initializes a new instance of the class. - /// - /// Test set result. - internal PSTestConfigurationSetResult(TestConfigurationSetResult testSetResult) - { - this.TestResult = Utilities.ToPSConfigurationTestResult(testSetResult.TestResult); - - var unitResults = new List(); - foreach (var unitResult in testSetResult.UnitResults) - { - unitResults.Add(new PSTestConfigurationUnitResult(unitResult)); - } - - this.UnitResults = unitResults; - } - - /// - /// Gets the test result. - /// - public PSConfigurationTestResult TestResult { get; private init; } - - /// - /// Gets the results of the units. - /// - public IReadOnlyList UnitResults { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Helpers; + + /// + /// Wrapper for TestConfigurationSetResult. + /// + public class PSTestConfigurationSetResult + { + /// + /// Initializes a new instance of the class. + /// + /// Test set result. + internal PSTestConfigurationSetResult(TestConfigurationSetResult testSetResult) + { + this.TestResult = Utilities.ToPSConfigurationTestResult(testSetResult.TestResult); + + var unitResults = new List(); + foreach (var unitResult in testSetResult.UnitResults) + { + unitResults.Add(new PSTestConfigurationUnitResult(unitResult)); + } + + this.UnitResults = unitResults; + } + + /// + /// Gets the test result. + /// + public PSConfigurationTestResult TestResult { get; private init; } + + /// + /// Gets the results of the units. + /// + public IReadOnlyList UnitResults { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationUnitResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationUnitResult.cs index 0e35a1196a..ef39e7de49 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationUnitResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSTestConfigurationUnitResult.cs @@ -1,32 +1,32 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Helpers; - - /// - /// Wrapper for TestConfigurationUnitResult. - /// - public class PSTestConfigurationUnitResult : PSUnitResult - { - /// - /// Initializes a new instance of the class. - /// - /// Test unit result. - internal PSTestConfigurationUnitResult(TestConfigurationUnitResult testUnitResult) - : base(testUnitResult.Unit, testUnitResult.ResultInformation) - { - this.TestResult = Utilities.ToPSConfigurationTestResult(testUnitResult.TestResult); - } - - /// - /// Gets the test result. - /// - public PSConfigurationTestResult TestResult { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Helpers; + + /// + /// Wrapper for TestConfigurationUnitResult. + /// + public class PSTestConfigurationUnitResult : PSUnitResult + { + /// + /// Initializes a new instance of the class. + /// + /// Test unit result. + internal PSTestConfigurationUnitResult(TestConfigurationUnitResult testUnitResult) + : base(testUnitResult.Unit, testUnitResult.ResultInformation) + { + this.TestResult = Utilities.ToPSConfigurationTestResult(testUnitResult.TestResult); + } + + /// + /// Gets the test result. + /// + public PSConfigurationTestResult TestResult { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSUnitResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSUnitResult.cs index b53d380644..53957cbbc6 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSUnitResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSUnitResult.cs @@ -1,132 +1,132 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Exceptions; - using Microsoft.WinGet.Resources; - - /// - /// Unit result. - /// - public abstract class PSUnitResult - { - /// - /// Initializes a new instance of the class. - /// - /// Unit. - /// Result info. - internal PSUnitResult(ConfigurationUnit unit, IConfigurationUnitResultInformation resultInfo) - { - this.Type = unit.Type; - this.ResultCode = resultInfo.ResultCode?.HResult ?? ErrorCodes.S_OK; - - if (this.ResultCode != ErrorCodes.S_OK) - { - this.Message = this.GetUnitMessage(unit, resultInfo); - this.Description = resultInfo.Description.Trim(); - this.Details = resultInfo.Details; - } - } - - /// - /// Gets the unit type. - /// - public string Type { get; private init; } - - /// - /// Gets the result code. - /// - public int ResultCode { get; private init; } - - /// - /// Gets the message. - /// - public string? Message { get; private init; } - - /// - /// Gets the short description. - /// - public string? Description { get; private init; } - - /// - /// Gets detailed information. - /// - public string? Details { get; private init; } - - private string GetUnitMessage(ConfigurationUnit unit, IConfigurationUnitResultInformation resultInfo) - { - if (resultInfo.ResultCode == null) - { - if (unit.State == ConfigurationUnitState.Skipped) - { - return string.Format(Resources.ConfigurationUnitSkipped, "null"); - } - - return string.Format(Resources.ConfigurationUnitFailed, "null"); - } - - int resultCode = resultInfo.ResultCode.HResult; - switch (resultCode) - { - case ErrorCodes.WingetConfigErrorDuplicateIdentifier: - return string.Format(Resources.ConfigurationUnitHasDuplicateIdentifier, unit.Identifier); - case ErrorCodes.WingetConfigErrorMissingDependency: - return string.Format(Resources.ConfigurationUnitHasMissingDependency, resultInfo.Details); - case ErrorCodes.WingetConfigErrorAssertionFailed: - return Resources.ConfigurationUnitAssertHadNegativeResult; - case ErrorCodes.WinGetConfigUnitNotFound: - return Resources.ConfigurationUnitNotFoundInModule; - case ErrorCodes.WinGetConfigUnitNotFoundRepository: - return Resources.ConfigurationUnitNotFound; - case ErrorCodes.WinGetConfigUnitMultipleMatches: - return Resources.ConfigurationUnitMultipleMatches; - case ErrorCodes.WinGetConfigUnitInvokeGet: - return Resources.ConfigurationUnitFailedDuringGet; - case ErrorCodes.WinGetConfigUnitInvokeTest: - return Resources.ConfigurationUnitFailedDuringTest; - case ErrorCodes.WinGetConfigUnitInvokeSet: - return Resources.ConfigurationUnitFailedDuringSet; - case ErrorCodes.WinGetConfigUnitModuleConflict: - return Resources.ConfigurationUnitModuleConflict; - case ErrorCodes.WinGetConfigUnitImportModule: - return Resources.ConfigurationUnitModuleImportFailed; - case ErrorCodes.WinGetConfigUnitInvokeInvalidResult: - return Resources.ConfigurationUnitReturnedInvalidResult; - case ErrorCodes.WingetConfigErrorManuallySkipped: - return Resources.ConfigurationUnitManuallySkipped; - case ErrorCodes.WingetConfigErrorDependencyUnsatisfied: - return Resources.ConfigurationUnitNotRunDueToDependency; - case ErrorCodes.WinGetConfigUnitSettingConfigRoot: - return Resources.WinGetConfigUnitSettingConfigRoot; - case ErrorCodes.WinGetConfigUnitImportModuleAdmin: - return Resources.WinGetConfigUnitImportModuleAdmin; - } - - switch (resultInfo.ResultSource) - { - case ConfigurationUnitResultSource.ConfigurationSet: - return string.Format(Resources.ConfigurationUnitFailedConfigSet, resultCode); - case ConfigurationUnitResultSource.Internal: - return string.Format(Resources.ConfigurationUnitFailedInternal, resultCode); - case ConfigurationUnitResultSource.Precondition: - return string.Format(Resources.ConfigurationUnitFailedPrecondition, resultCode); - case ConfigurationUnitResultSource.SystemState: - return string.Format(Resources.ConfigurationUnitFailedSystemState, resultCode); - case ConfigurationUnitResultSource.UnitProcessing: - return string.Format(Resources.ConfigurationUnitFailedUnitProcessing, resultCode); - } - - if (unit.State == ConfigurationUnitState.Skipped) - { - return string.Format(Resources.ConfigurationUnitSkipped, resultCode); - } - - return string.Format(Resources.ConfigurationUnitFailed, resultCode); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Exceptions; + using Microsoft.WinGet.Resources; + + /// + /// Unit result. + /// + public abstract class PSUnitResult + { + /// + /// Initializes a new instance of the class. + /// + /// Unit. + /// Result info. + internal PSUnitResult(ConfigurationUnit unit, IConfigurationUnitResultInformation resultInfo) + { + this.Type = unit.Type; + this.ResultCode = resultInfo.ResultCode?.HResult ?? ErrorCodes.S_OK; + + if (this.ResultCode != ErrorCodes.S_OK) + { + this.Message = this.GetUnitMessage(unit, resultInfo); + this.Description = resultInfo.Description.Trim(); + this.Details = resultInfo.Details; + } + } + + /// + /// Gets the unit type. + /// + public string Type { get; private init; } + + /// + /// Gets the result code. + /// + public int ResultCode { get; private init; } + + /// + /// Gets the message. + /// + public string? Message { get; private init; } + + /// + /// Gets the short description. + /// + public string? Description { get; private init; } + + /// + /// Gets detailed information. + /// + public string? Details { get; private init; } + + private string GetUnitMessage(ConfigurationUnit unit, IConfigurationUnitResultInformation resultInfo) + { + if (resultInfo.ResultCode == null) + { + if (unit.State == ConfigurationUnitState.Skipped) + { + return string.Format(Resources.ConfigurationUnitSkipped, "null"); + } + + return string.Format(Resources.ConfigurationUnitFailed, "null"); + } + + int resultCode = resultInfo.ResultCode.HResult; + switch (resultCode) + { + case ErrorCodes.WingetConfigErrorDuplicateIdentifier: + return string.Format(Resources.ConfigurationUnitHasDuplicateIdentifier, unit.Identifier); + case ErrorCodes.WingetConfigErrorMissingDependency: + return string.Format(Resources.ConfigurationUnitHasMissingDependency, resultInfo.Details); + case ErrorCodes.WingetConfigErrorAssertionFailed: + return Resources.ConfigurationUnitAssertHadNegativeResult; + case ErrorCodes.WinGetConfigUnitNotFound: + return Resources.ConfigurationUnitNotFoundInModule; + case ErrorCodes.WinGetConfigUnitNotFoundRepository: + return Resources.ConfigurationUnitNotFound; + case ErrorCodes.WinGetConfigUnitMultipleMatches: + return Resources.ConfigurationUnitMultipleMatches; + case ErrorCodes.WinGetConfigUnitInvokeGet: + return Resources.ConfigurationUnitFailedDuringGet; + case ErrorCodes.WinGetConfigUnitInvokeTest: + return Resources.ConfigurationUnitFailedDuringTest; + case ErrorCodes.WinGetConfigUnitInvokeSet: + return Resources.ConfigurationUnitFailedDuringSet; + case ErrorCodes.WinGetConfigUnitModuleConflict: + return Resources.ConfigurationUnitModuleConflict; + case ErrorCodes.WinGetConfigUnitImportModule: + return Resources.ConfigurationUnitModuleImportFailed; + case ErrorCodes.WinGetConfigUnitInvokeInvalidResult: + return Resources.ConfigurationUnitReturnedInvalidResult; + case ErrorCodes.WingetConfigErrorManuallySkipped: + return Resources.ConfigurationUnitManuallySkipped; + case ErrorCodes.WingetConfigErrorDependencyUnsatisfied: + return Resources.ConfigurationUnitNotRunDueToDependency; + case ErrorCodes.WinGetConfigUnitSettingConfigRoot: + return Resources.WinGetConfigUnitSettingConfigRoot; + case ErrorCodes.WinGetConfigUnitImportModuleAdmin: + return Resources.WinGetConfigUnitImportModuleAdmin; + } + + switch (resultInfo.ResultSource) + { + case ConfigurationUnitResultSource.ConfigurationSet: + return string.Format(Resources.ConfigurationUnitFailedConfigSet, resultCode); + case ConfigurationUnitResultSource.Internal: + return string.Format(Resources.ConfigurationUnitFailedInternal, resultCode); + case ConfigurationUnitResultSource.Precondition: + return string.Format(Resources.ConfigurationUnitFailedPrecondition, resultCode); + case ConfigurationUnitResultSource.SystemState: + return string.Format(Resources.ConfigurationUnitFailedSystemState, resultCode); + case ConfigurationUnitResultSource.UnitProcessing: + return string.Format(Resources.ConfigurationUnitFailedUnitProcessing, resultCode); + } + + if (unit.State == ConfigurationUnitState.Skipped) + { + return string.Format(Resources.ConfigurationUnitSkipped, resultCode); + } + + return string.Format(Resources.ConfigurationUnitFailed, resultCode); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationSetResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationSetResult.cs index d695f403e6..51da4feff7 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationSetResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationSetResult.cs @@ -1,45 +1,45 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using System.Collections.Generic; - using Microsoft.Management.Configuration; - using Microsoft.WinGet.Configuration.Engine.Exceptions; - - /// - /// Wrapper for ApplyConfigurationSetResult for validate. - /// - public class PSValidateConfigurationSetResult - { - /// - /// Initializes a new instance of the class. - /// - /// Apply set result. - internal PSValidateConfigurationSetResult(ApplyConfigurationSetResult applySetResult) - { - this.ResultCode = applySetResult.ResultCode?.HResult ?? ErrorCodes.S_OK; - - var unitResults = new List(); - foreach (var unitResult in applySetResult.UnitResults) - { - unitResults.Add(new PSValidateConfigurationUnitResult(unitResult)); - } - - this.UnitResults = unitResults; - } - - /// - /// Gets the result code. - /// - public int ResultCode { get; private init; } - - /// - /// Gets the results of the units. - /// - public IReadOnlyList UnitResults { get; private init; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using System.Collections.Generic; + using Microsoft.Management.Configuration; + using Microsoft.WinGet.Configuration.Engine.Exceptions; + + /// + /// Wrapper for ApplyConfigurationSetResult for validate. + /// + public class PSValidateConfigurationSetResult + { + /// + /// Initializes a new instance of the class. + /// + /// Apply set result. + internal PSValidateConfigurationSetResult(ApplyConfigurationSetResult applySetResult) + { + this.ResultCode = applySetResult.ResultCode?.HResult ?? ErrorCodes.S_OK; + + var unitResults = new List(); + foreach (var unitResult in applySetResult.UnitResults) + { + unitResults.Add(new PSValidateConfigurationUnitResult(unitResult)); + } + + this.UnitResults = unitResults; + } + + /// + /// Gets the result code. + /// + public int ResultCode { get; private init; } + + /// + /// Gets the results of the units. + /// + public IReadOnlyList UnitResults { get; private init; } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationUnitResult.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationUnitResult.cs index 47452be064..b7f3b61cc7 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationUnitResult.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/PSObjects/PSValidateConfigurationUnitResult.cs @@ -1,25 +1,25 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.Configuration.Engine.PSObjects -{ - using Microsoft.Management.Configuration; - - /// - /// The validate result of a configuration unit. - /// - public class PSValidateConfigurationUnitResult : PSUnitResult - { - /// - /// Initializes a new instance of the class. - /// - /// Apply unit result. - internal PSValidateConfigurationUnitResult(ApplyConfigurationUnitResult unitResult) - : base(unitResult.Unit, unitResult.ResultInformation) - { - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.Configuration.Engine.PSObjects +{ + using Microsoft.Management.Configuration; + + /// + /// The validate result of a configuration unit. + /// + public class PSValidateConfigurationUnitResult : PSUnitResult + { + /// + /// Initializes a new instance of the class. + /// + /// Apply unit result. + internal PSValidateConfigurationUnitResult(ApplyConfigurationUnitResult unitResult) + : base(unitResult.Unit, unitResult.ResultInformation) + { + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Properties/AssemblyInfo.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Properties/AssemblyInfo.cs index d9d343a224..bf84c41824 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Properties/AssemblyInfo.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Properties/AssemblyInfo.cs @@ -1,16 +1,16 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -#if NET - -using System.Runtime.Versioning; - -// Forcibly set the target and supported platforms due to the internal build setup. -// Keep in sync with project versions. -[assembly: TargetPlatform("Windows10.0.26100.0")] -[assembly: SupportedOSPlatform("Windows10.0.18362.0")] - -#endif +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +#if NET + +using System.Runtime.Versioning; + +// Forcibly set the target and supported platforms due to the internal build setup. +// Keep in sync with project versions. +[assembly: TargetPlatform("Windows10.0.26100.0")] +[assembly: SupportedOSPlatform("Windows10.0.18362.0")] + +#endif diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.Designer.cs b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.Designer.cs index 5d81368c75..5a3bfd79d6 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.Designer.cs @@ -1,621 +1,621 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.WinGet.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.Configuration.Engine.Resources.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Accepts the configuration warning, preventing an interactive prompt. - /// - internal static string ConfigurationAcceptWarningArgumentDescription { - get { - return ResourceManager.GetString("ConfigurationAcceptWarningArgumentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to `-ModulePath AllUsers` require administrator privileges to execute.. - /// - internal static string ConfigurationAllUsersElevated { - get { - return ResourceManager.GetString("ConfigurationAllUsersElevated", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Apply. - /// - internal static string ConfigurationApply { - get { - return ResourceManager.GetString("ConfigurationApply", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Assert. - /// - internal static string ConfigurationAssert { - get { - return ResourceManager.GetString("ConfigurationAssert", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Dependencies:{0}. - /// - internal static string ConfigurationDependencies { - get { - return ResourceManager.GetString("ConfigurationDependencies", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to <See the log file for additional details>. - /// - internal static string ConfigurationDescriptionWasTruncated { - get { - return ResourceManager.GetString("ConfigurationDescriptionWasTruncated", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Some of the configuration was not applied successfully.. - /// - internal static string ConfigurationFailedToApply { - get { - return ResourceManager.GetString("ConfigurationFailedToApply", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to get detailed information about the configuration.. - /// - internal static string ConfigurationFailedToGetDetails { - get { - return ResourceManager.GetString("ConfigurationFailedToGetDetails", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field '{0}' in the configuration file is the wrong type.. - /// - internal static string ConfigurationFieldInvalidType { - get { - return ResourceManager.GetString("ConfigurationFieldInvalidType", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field '{0}' has an invalid value: {1}. - /// - internal static string ConfigurationFieldInvalidValue { - get { - return ResourceManager.GetString("ConfigurationFieldInvalidValue", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The field '{0}' is missing or empty.. - /// - internal static string ConfigurationFieldMissing { - get { - return ResourceManager.GetString("ConfigurationFieldMissing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The path to the configuration file.. - /// - internal static string ConfigurationFileArgumentDescription { - get { - return ResourceManager.GetString("ConfigurationFileArgumentDescription", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration is empty.. - /// - internal static string ConfigurationFileEmpty { - get { - return ResourceManager.GetString("ConfigurationFileEmpty", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration file is invalid.. - /// - internal static string ConfigurationFileInvalid { - get { - return ResourceManager.GetString("ConfigurationFileInvalid", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuration file version {0} is not known.. - /// - internal static string ConfigurationFileVersionUnknown { - get { - return ResourceManager.GetString("ConfigurationFileVersionUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Retrieving configuration details. - /// - internal static string ConfigurationGettingDetails { - get { - return ResourceManager.GetString("ConfigurationGettingDetails", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Inform. - /// - internal static string ConfigurationInform { - get { - return ResourceManager.GetString("ConfigurationInform", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Initializing configuration system. - /// - internal static string ConfigurationInitializing { - get { - return ResourceManager.GetString("ConfigurationInitializing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Desired State Configuration package not found on the system. Installing the package.... - /// - internal static string ConfigurationInstallDscPackage { - get { - return ResourceManager.GetString("ConfigurationInstallDscPackage", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through -ProcessorPath argument.. - /// - internal static string ConfigurationInstallDscPackageFailed { - get { - return ResourceManager.GetString("ConfigurationInstallDscPackageFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Local. - /// - internal static string ConfigurationLocal { - get { - return ResourceManager.GetString("ConfigurationLocal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Module: {0}. - /// - internal static string ConfigurationModuleNameOnly { - get { - return ResourceManager.GetString("ConfigurationModuleNameOnly", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to `-ModulePath` value must be `CurrentUser`, `AllUsers`, `Default` or an absolute path.. - /// - internal static string ConfigurationModulePathArgError { - get { - return ResourceManager.GetString("ConfigurationModulePathArgError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Module: {0} by {1} [{2}]. - /// - internal static string ConfigurationModuleWithDetails { - get { - return ResourceManager.GetString("ConfigurationModuleWithDetails", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reading configuration file. - /// - internal static string ConfigurationReadingConfigFile { - get { - return ResourceManager.GetString("ConfigurationReadingConfigFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Reading configuration history. - /// - internal static string ConfigurationReadingConfigHistory { - get { - return ResourceManager.GetString("ConfigurationReadingConfigHistory", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Settings:. - /// - internal static string ConfigurationSettings { - get { - return ResourceManager.GetString("ConfigurationSettings", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Configuration successfully applied.. - /// - internal static string ConfigurationSuccessfullyApplied { - get { - return ResourceManager.GetString("ConfigurationSuccessfullyApplied", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The system is not in the desired state asserted by the configuration.. - /// - internal static string ConfigurationUnitAssertHadNegativeResult { - get { - return ResourceManager.GetString("ConfigurationUnitAssertHadNegativeResult", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This configuration unit failed for an unknown reason: {0}. - /// - internal static string ConfigurationUnitFailed { - get { - return ResourceManager.GetString("ConfigurationUnitFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed due to the configuration: {0}. - /// - internal static string ConfigurationUnitFailedConfigSet { - get { - return ResourceManager.GetString("ConfigurationUnitFailedConfigSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed while attempting to get the current system state.. - /// - internal static string ConfigurationUnitFailedDuringGet { - get { - return ResourceManager.GetString("ConfigurationUnitFailedDuringGet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed while attempting to apply the desired state.. - /// - internal static string ConfigurationUnitFailedDuringSet { - get { - return ResourceManager.GetString("ConfigurationUnitFailedDuringSet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed while attempting to test the current system state.. - /// - internal static string ConfigurationUnitFailedDuringTest { - get { - return ResourceManager.GetString("ConfigurationUnitFailedDuringTest", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed due to an internal error: {0}. - /// - internal static string ConfigurationUnitFailedInternal { - get { - return ResourceManager.GetString("ConfigurationUnitFailedInternal", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed due to a precondition not being valid: {0}. - /// - internal static string ConfigurationUnitFailedPrecondition { - get { - return ResourceManager.GetString("ConfigurationUnitFailedPrecondition", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed due to the system state: {0}. - /// - internal static string ConfigurationUnitFailedSystemState { - get { - return ResourceManager.GetString("ConfigurationUnitFailedSystemState", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit failed while attempting to run: {0}. - /// - internal static string ConfigurationUnitFailedUnitProcessing { - get { - return ResourceManager.GetString("ConfigurationUnitFailedUnitProcessing", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration contains the identifier `{0}` multiple times.. - /// - internal static string ConfigurationUnitHasDuplicateIdentifier { - get { - return ResourceManager.GetString("ConfigurationUnitHasDuplicateIdentifier", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The dependency `{0}` was not found within the configuration.. - /// - internal static string ConfigurationUnitHasMissingDependency { - get { - return ResourceManager.GetString("ConfigurationUnitHasMissingDependency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This configuration unit was manually skipped.. - /// - internal static string ConfigurationUnitManuallySkipped { - get { - return ResourceManager.GetString("ConfigurationUnitManuallySkipped", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The module for the configuration unit is available in multiple locations with the same version.. - /// - internal static string ConfigurationUnitModuleConflict { - get { - return ResourceManager.GetString("ConfigurationUnitModuleConflict", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loading the module for the configuration unit failed.. - /// - internal static string ConfigurationUnitModuleImportFailed { - get { - return ResourceManager.GetString("ConfigurationUnitModuleImportFailed", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Multiple matches were found for the configuration unit; specify the module to select the correct one.. - /// - internal static string ConfigurationUnitMultipleMatches { - get { - return ResourceManager.GetString("ConfigurationUnitMultipleMatches", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit could not be found.. - /// - internal static string ConfigurationUnitNotFound { - get { - return ResourceManager.GetString("ConfigurationUnitNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit was not in the module as expected.. - /// - internal static string ConfigurationUnitNotFoundInModule { - get { - return ResourceManager.GetString("ConfigurationUnitNotFoundInModule", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This configuration unit was not run because a dependency failed or was not run.. - /// - internal static string ConfigurationUnitNotRunDueToDependency { - get { - return ResourceManager.GetString("ConfigurationUnitNotRunDueToDependency", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This configuration unit was not run because an assert failed or was false.. - /// - internal static string ConfigurationUnitNotRunDueToFailedAssert { - get { - return ResourceManager.GetString("ConfigurationUnitNotRunDueToFailedAssert", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to The configuration unit returned an unexpected result during execution.. - /// - internal static string ConfigurationUnitReturnedInvalidResult { - get { - return ResourceManager.GetString("ConfigurationUnitReturnedInvalidResult", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to This configuration unit was not run for an unknown reason: {0}. - /// - internal static string ConfigurationUnitSkipped { - get { - return ResourceManager.GetString("ConfigurationUnitSkipped", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Another configuration is being applied to the system. This configuration will continue as soon as is possible.... - /// - internal static string ConfigurationWaitingOnAnother { - get { - return ResourceManager.GetString("ConfigurationWaitingOnAnother", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applic [rest of string was truncated]";. - /// - internal static string ConfigurationWarning { - get { - return ResourceManager.GetString("ConfigurationWarning", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Have you reviewed the configuration and would you like to proceed applying it to the system?. - /// - internal static string ConfigurationWarningPromptApply { - get { - return ResourceManager.GetString("ConfigurationWarningPromptApply", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Have you reviewed the configuration and would you like to proceed verifying it against the system?. - /// - internal static string ConfigurationWarningPromptTest { - get { - return ResourceManager.GetString("ConfigurationWarningPromptTest", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Debug parameter not supported. - /// - internal static string DebugNotSupported { - get { - return ResourceManager.GetString("DebugNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Cannot find dsc.exe.. - /// - internal static string DscExeNotFound { - get { - return ResourceManager.GetString("DscExeNotFound", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Completed. - /// - internal static string OperationCompleted { - get { - return ResourceManager.GetString("OperationCompleted", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to In progress. - /// - internal static string OperationInProgress { - get { - return ResourceManager.GetString("OperationInProgress", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Processor engine not supported. Processor engine: {0}. - /// - internal static string ProcessorEngineNotSupported { - get { - return ResourceManager.GetString("ProcessorEngineNotSupported", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to See line {0}, column {1} in the file.. - /// - internal static string SeeLineAndColumn { - get { - return ResourceManager.GetString("SeeLineAndColumn", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Loading the module for the configuration unit failed because it requires administrator privileges to run.. - /// - internal static string WinGetConfigUnitImportModuleAdmin { - get { - return ResourceManager.GetString("WinGetConfigUnitImportModuleAdmin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to A unit contains a setting that requires the config root.. - /// - internal static string WinGetConfigUnitSettingConfigRoot { - get { - return ResourceManager.GetString("WinGetConfigUnitSettingConfigRoot", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.WinGet.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.Configuration.Engine.Resources.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Accepts the configuration warning, preventing an interactive prompt. + /// + internal static string ConfigurationAcceptWarningArgumentDescription { + get { + return ResourceManager.GetString("ConfigurationAcceptWarningArgumentDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `-ModulePath AllUsers` require administrator privileges to execute.. + /// + internal static string ConfigurationAllUsersElevated { + get { + return ResourceManager.GetString("ConfigurationAllUsersElevated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Apply. + /// + internal static string ConfigurationApply { + get { + return ResourceManager.GetString("ConfigurationApply", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Assert. + /// + internal static string ConfigurationAssert { + get { + return ResourceManager.GetString("ConfigurationAssert", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Dependencies:{0}. + /// + internal static string ConfigurationDependencies { + get { + return ResourceManager.GetString("ConfigurationDependencies", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to <See the log file for additional details>. + /// + internal static string ConfigurationDescriptionWasTruncated { + get { + return ResourceManager.GetString("ConfigurationDescriptionWasTruncated", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Some of the configuration was not applied successfully.. + /// + internal static string ConfigurationFailedToApply { + get { + return ResourceManager.GetString("ConfigurationFailedToApply", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to get detailed information about the configuration.. + /// + internal static string ConfigurationFailedToGetDetails { + get { + return ResourceManager.GetString("ConfigurationFailedToGetDetails", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The field '{0}' in the configuration file is the wrong type.. + /// + internal static string ConfigurationFieldInvalidType { + get { + return ResourceManager.GetString("ConfigurationFieldInvalidType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The field '{0}' has an invalid value: {1}. + /// + internal static string ConfigurationFieldInvalidValue { + get { + return ResourceManager.GetString("ConfigurationFieldInvalidValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The field '{0}' is missing or empty.. + /// + internal static string ConfigurationFieldMissing { + get { + return ResourceManager.GetString("ConfigurationFieldMissing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The path to the configuration file.. + /// + internal static string ConfigurationFileArgumentDescription { + get { + return ResourceManager.GetString("ConfigurationFileArgumentDescription", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration is empty.. + /// + internal static string ConfigurationFileEmpty { + get { + return ResourceManager.GetString("ConfigurationFileEmpty", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration file is invalid.. + /// + internal static string ConfigurationFileInvalid { + get { + return ResourceManager.GetString("ConfigurationFileInvalid", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuration file version {0} is not known.. + /// + internal static string ConfigurationFileVersionUnknown { + get { + return ResourceManager.GetString("ConfigurationFileVersionUnknown", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Retrieving configuration details. + /// + internal static string ConfigurationGettingDetails { + get { + return ResourceManager.GetString("ConfigurationGettingDetails", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Inform. + /// + internal static string ConfigurationInform { + get { + return ResourceManager.GetString("ConfigurationInform", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Initializing configuration system. + /// + internal static string ConfigurationInitializing { + get { + return ResourceManager.GetString("ConfigurationInitializing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Desired State Configuration package not found on the system. Installing the package.... + /// + internal static string ConfigurationInstallDscPackage { + get { + return ResourceManager.GetString("ConfigurationInstallDscPackage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through -ProcessorPath argument.. + /// + internal static string ConfigurationInstallDscPackageFailed { + get { + return ResourceManager.GetString("ConfigurationInstallDscPackageFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Local. + /// + internal static string ConfigurationLocal { + get { + return ResourceManager.GetString("ConfigurationLocal", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Module: {0}. + /// + internal static string ConfigurationModuleNameOnly { + get { + return ResourceManager.GetString("ConfigurationModuleNameOnly", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to `-ModulePath` value must be `CurrentUser`, `AllUsers`, `Default` or an absolute path.. + /// + internal static string ConfigurationModulePathArgError { + get { + return ResourceManager.GetString("ConfigurationModulePathArgError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Module: {0} by {1} [{2}]. + /// + internal static string ConfigurationModuleWithDetails { + get { + return ResourceManager.GetString("ConfigurationModuleWithDetails", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reading configuration file. + /// + internal static string ConfigurationReadingConfigFile { + get { + return ResourceManager.GetString("ConfigurationReadingConfigFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reading configuration history. + /// + internal static string ConfigurationReadingConfigHistory { + get { + return ResourceManager.GetString("ConfigurationReadingConfigHistory", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Settings:. + /// + internal static string ConfigurationSettings { + get { + return ResourceManager.GetString("ConfigurationSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Configuration successfully applied.. + /// + internal static string ConfigurationSuccessfullyApplied { + get { + return ResourceManager.GetString("ConfigurationSuccessfullyApplied", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The system is not in the desired state asserted by the configuration.. + /// + internal static string ConfigurationUnitAssertHadNegativeResult { + get { + return ResourceManager.GetString("ConfigurationUnitAssertHadNegativeResult", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This configuration unit failed for an unknown reason: {0}. + /// + internal static string ConfigurationUnitFailed { + get { + return ResourceManager.GetString("ConfigurationUnitFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed due to the configuration: {0}. + /// + internal static string ConfigurationUnitFailedConfigSet { + get { + return ResourceManager.GetString("ConfigurationUnitFailedConfigSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed while attempting to get the current system state.. + /// + internal static string ConfigurationUnitFailedDuringGet { + get { + return ResourceManager.GetString("ConfigurationUnitFailedDuringGet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed while attempting to apply the desired state.. + /// + internal static string ConfigurationUnitFailedDuringSet { + get { + return ResourceManager.GetString("ConfigurationUnitFailedDuringSet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed while attempting to test the current system state.. + /// + internal static string ConfigurationUnitFailedDuringTest { + get { + return ResourceManager.GetString("ConfigurationUnitFailedDuringTest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed due to an internal error: {0}. + /// + internal static string ConfigurationUnitFailedInternal { + get { + return ResourceManager.GetString("ConfigurationUnitFailedInternal", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed due to a precondition not being valid: {0}. + /// + internal static string ConfigurationUnitFailedPrecondition { + get { + return ResourceManager.GetString("ConfigurationUnitFailedPrecondition", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed due to the system state: {0}. + /// + internal static string ConfigurationUnitFailedSystemState { + get { + return ResourceManager.GetString("ConfigurationUnitFailedSystemState", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit failed while attempting to run: {0}. + /// + internal static string ConfigurationUnitFailedUnitProcessing { + get { + return ResourceManager.GetString("ConfigurationUnitFailedUnitProcessing", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration contains the identifier `{0}` multiple times.. + /// + internal static string ConfigurationUnitHasDuplicateIdentifier { + get { + return ResourceManager.GetString("ConfigurationUnitHasDuplicateIdentifier", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The dependency `{0}` was not found within the configuration.. + /// + internal static string ConfigurationUnitHasMissingDependency { + get { + return ResourceManager.GetString("ConfigurationUnitHasMissingDependency", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This configuration unit was manually skipped.. + /// + internal static string ConfigurationUnitManuallySkipped { + get { + return ResourceManager.GetString("ConfigurationUnitManuallySkipped", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The module for the configuration unit is available in multiple locations with the same version.. + /// + internal static string ConfigurationUnitModuleConflict { + get { + return ResourceManager.GetString("ConfigurationUnitModuleConflict", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Loading the module for the configuration unit failed.. + /// + internal static string ConfigurationUnitModuleImportFailed { + get { + return ResourceManager.GetString("ConfigurationUnitModuleImportFailed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Multiple matches were found for the configuration unit; specify the module to select the correct one.. + /// + internal static string ConfigurationUnitMultipleMatches { + get { + return ResourceManager.GetString("ConfigurationUnitMultipleMatches", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit could not be found.. + /// + internal static string ConfigurationUnitNotFound { + get { + return ResourceManager.GetString("ConfigurationUnitNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit was not in the module as expected.. + /// + internal static string ConfigurationUnitNotFoundInModule { + get { + return ResourceManager.GetString("ConfigurationUnitNotFoundInModule", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This configuration unit was not run because a dependency failed or was not run.. + /// + internal static string ConfigurationUnitNotRunDueToDependency { + get { + return ResourceManager.GetString("ConfigurationUnitNotRunDueToDependency", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This configuration unit was not run because an assert failed or was false.. + /// + internal static string ConfigurationUnitNotRunDueToFailedAssert { + get { + return ResourceManager.GetString("ConfigurationUnitNotRunDueToFailedAssert", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The configuration unit returned an unexpected result during execution.. + /// + internal static string ConfigurationUnitReturnedInvalidResult { + get { + return ResourceManager.GetString("ConfigurationUnitReturnedInvalidResult", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to This configuration unit was not run for an unknown reason: {0}. + /// + internal static string ConfigurationUnitSkipped { + get { + return ResourceManager.GetString("ConfigurationUnitSkipped", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Another configuration is being applied to the system. This configuration will continue as soon as is possible.... + /// + internal static string ConfigurationWaitingOnAnother { + get { + return ResourceManager.GetString("ConfigurationWaitingOnAnother", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applic [rest of string was truncated]";. + /// + internal static string ConfigurationWarning { + get { + return ResourceManager.GetString("ConfigurationWarning", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Have you reviewed the configuration and would you like to proceed applying it to the system?. + /// + internal static string ConfigurationWarningPromptApply { + get { + return ResourceManager.GetString("ConfigurationWarningPromptApply", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Have you reviewed the configuration and would you like to proceed verifying it against the system?. + /// + internal static string ConfigurationWarningPromptTest { + get { + return ResourceManager.GetString("ConfigurationWarningPromptTest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Debug parameter not supported. + /// + internal static string DebugNotSupported { + get { + return ResourceManager.GetString("DebugNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot find dsc.exe.. + /// + internal static string DscExeNotFound { + get { + return ResourceManager.GetString("DscExeNotFound", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Completed. + /// + internal static string OperationCompleted { + get { + return ResourceManager.GetString("OperationCompleted", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to In progress. + /// + internal static string OperationInProgress { + get { + return ResourceManager.GetString("OperationInProgress", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Processor engine not supported. Processor engine: {0}. + /// + internal static string ProcessorEngineNotSupported { + get { + return ResourceManager.GetString("ProcessorEngineNotSupported", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to See line {0}, column {1} in the file.. + /// + internal static string SeeLineAndColumn { + get { + return ResourceManager.GetString("SeeLineAndColumn", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Loading the module for the configuration unit failed because it requires administrator privileges to run.. + /// + internal static string WinGetConfigUnitImportModuleAdmin { + get { + return ResourceManager.GetString("WinGetConfigUnitImportModuleAdmin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to A unit contains a setting that requires the config root.. + /// + internal static string WinGetConfigUnitSettingConfigRoot { + get { + return ResourceManager.GetString("WinGetConfigUnitSettingConfigRoot", resourceCulture); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.resx b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.resx index d9be4a7b9b..1bf9caee5e 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.resx +++ b/src/PowerShell/Microsoft.WinGet.Configuration.Engine/Resources/Resources.resx @@ -1,336 +1,336 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - The field '{0}' in the configuration file is the wrong type. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. - - - The path to the configuration file. - - - The configuration file is invalid. - - - Configuration file version {0} is not known. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the version of the configuration file. - - - Accepts the configuration warning, preventing an interactive prompt - - - Apply - Indicates that this item is used to write state - - - Assert - Indicates that this item is used to check/assert the state rather than write to it - - - Dependencies:{0} - {Locked="{0}"} Label displaying a list of dependencies. {0} is replaced with a space separated list of identifiers referencing other items. - - - Some of the configuration was not applied successfully. - - - Failed to get detailed information about the configuration. - - - Inform - Indicates that this item is used to retrieve values for future use rather than writing them - - - Local - Used to indicate that the item is present on the device. - - - Module: {0} - {Locked="{0}"} Label displaying a module name. {0} is replaced with the name of the module from the user input file. - - - Module: {0} by {1} [{2}] - {Locked="{0}","{1}","{2}"} Label displaying module information. {0} is replaced by the module name. {1} is replaced by the module author. {2} is replaced by a string indicating the source of the module. - - - Settings: - Label for the values that are used as inputs for this item when applying state - - - Configuration successfully applied. - - - Another configuration is being applied to the system. This configuration will continue as soon as is possible... - - - You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services. - Legal approved. Do not change without approval. - - - Have you reviewed the configuration and would you like to proceed applying it to the system? - PM approved. - - - The configuration is empty. - - - <See the log file for additional details> - The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable. - - - Retrieving configuration details - - - Initializing configuration system - - - Reading configuration file - - - The system is not in the desired state asserted by the configuration. - - - This configuration unit failed for an unknown reason: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to the configuration: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed while attempting to get the current system state. - - - The configuration unit failed while attempting to apply the desired state. - - - The configuration unit failed while attempting to test the current system state. - - - The configuration unit failed due to an internal error: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to a precondition not being valid: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed due to the system state: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration unit failed while attempting to run: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The configuration contains the identifier `{0}` multiple times. - {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. - - - The dependency `{0}` was not found within the configuration. - {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. - - - This configuration unit was manually skipped. - - - The module for the configuration unit is available in multiple locations with the same version. - - - Loading the module for the configuration unit failed. - - - Multiple matches were found for the configuration unit; specify the module to select the correct one. - - - The configuration unit could not be found. - - - The configuration unit was not in the module as expected. - - - This configuration unit was not run because a dependency failed or was not run. - - - This configuration unit was not run because an assert failed or was false. - - - The configuration unit returned an unexpected result during execution. - - - This configuration unit was not run for an unknown reason: {0} - {Locked="{0}"} {0} is a placeholder for the unrecognized error code. - - - The field '{0}' has an invalid value: {1} - {Locked="{0}","{1}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. {1} is a placeholder for the invalid value. - - - The field '{0}' is missing or empty. - {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the expected field name from the file. - - - See line {0}, column {1} in the file. - {Locked="{0}","{1}"} Indicates the file location of the error, {0} and {1} are placeholders for numbers of the line and column, respectively. - - - Completed - - - In progress - - - Debug parameter not supported - - - `-ModulePath AllUsers` require administrator privileges to execute. - {Locked="-ModulePath AllUsers"} - - - `-ModulePath` value must be `CurrentUser`, `AllUsers`, `Default` or an absolute path. - {Locked="{-ModulePath}, {CurrentUser}, {AllUsers}, {Default}} - - - Have you reviewed the configuration and would you like to proceed verifying it against the system? - - - Loading the module for the configuration unit failed because it requires administrator privileges to run. - - - A unit contains a setting that requires the config root. - - - Reading configuration history - - - Desired State Configuration package not found on the system. Installing the package... - - - Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through -ProcessorPath argument. - {Locked="dsc.exe","-ProcessorPath"} - - - Processor engine not supported. Processor engine: {0} - {Locked="{0}"} - - - Cannot find dsc.exe. - {Locked="{dsc.exe}"} - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The field '{0}' in the configuration file is the wrong type. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. + + + The path to the configuration file. + + + The configuration file is invalid. + + + Configuration file version {0} is not known. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the version of the configuration file. + + + Accepts the configuration warning, preventing an interactive prompt + + + Apply + Indicates that this item is used to write state + + + Assert + Indicates that this item is used to check/assert the state rather than write to it + + + Dependencies:{0} + {Locked="{0}"} Label displaying a list of dependencies. {0} is replaced with a space separated list of identifiers referencing other items. + + + Some of the configuration was not applied successfully. + + + Failed to get detailed information about the configuration. + + + Inform + Indicates that this item is used to retrieve values for future use rather than writing them + + + Local + Used to indicate that the item is present on the device. + + + Module: {0} + {Locked="{0}"} Label displaying a module name. {0} is replaced with the name of the module from the user input file. + + + Module: {0} by {1} [{2}] + {Locked="{0}","{1}","{2}"} Label displaying module information. {0} is replaced by the module name. {1} is replaced by the module author. {2} is replaced by a string indicating the source of the module. + + + Settings: + Label for the values that are used as inputs for this item when applying state + + + Configuration successfully applied. + + + Another configuration is being applied to the system. This configuration will continue as soon as is possible... + + + You are responsible for understanding the configuration settings you are choosing to execute. Microsoft is not responsible for the configuration file you have authored or imported. This configuration may change settings in Windows, install software, change software settings (including security settings), and accept user agreements to third-party packages and services on your behalf.  By running this configuration file, you acknowledge that you understand and agree to these resources and settings. Any applications installed are licensed to you by their owners. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages or services. + Legal approved. Do not change without approval. + + + Have you reviewed the configuration and would you like to proceed applying it to the system? + PM approved. + + + The configuration is empty. + + + <See the log file for additional details> + The brackets are intended to make the value stand out from other text which it will follow. Any locale appropriate mechanism that achieves this is acceptable. + + + Retrieving configuration details + + + Initializing configuration system + + + Reading configuration file + + + The system is not in the desired state asserted by the configuration. + + + This configuration unit failed for an unknown reason: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to the configuration: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed while attempting to get the current system state. + + + The configuration unit failed while attempting to apply the desired state. + + + The configuration unit failed while attempting to test the current system state. + + + The configuration unit failed due to an internal error: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to a precondition not being valid: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed due to the system state: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration unit failed while attempting to run: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The configuration contains the identifier `{0}` multiple times. + {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. + + + The dependency `{0}` was not found within the configuration. + {Locked="{0}"} {0} is a placeholder that is replaced by the identifier string from the user input file. + + + This configuration unit was manually skipped. + + + The module for the configuration unit is available in multiple locations with the same version. + + + Loading the module for the configuration unit failed. + + + Multiple matches were found for the configuration unit; specify the module to select the correct one. + + + The configuration unit could not be found. + + + The configuration unit was not in the module as expected. + + + This configuration unit was not run because a dependency failed or was not run. + + + This configuration unit was not run because an assert failed or was false. + + + The configuration unit returned an unexpected result during execution. + + + This configuration unit was not run for an unknown reason: {0} + {Locked="{0}"} {0} is a placeholder for the unrecognized error code. + + + The field '{0}' has an invalid value: {1} + {Locked="{0}","{1}"} An error in reading a configuration file. {0} is a placeholder replaced by the field name from the file. {1} is a placeholder for the invalid value. + + + The field '{0}' is missing or empty. + {Locked="{0}"} An error in reading a configuration file. {0} is a placeholder replaced by the expected field name from the file. + + + See line {0}, column {1} in the file. + {Locked="{0}","{1}"} Indicates the file location of the error, {0} and {1} are placeholders for numbers of the line and column, respectively. + + + Completed + + + In progress + + + Debug parameter not supported + + + `-ModulePath AllUsers` require administrator privileges to execute. + {Locked="-ModulePath AllUsers"} + + + `-ModulePath` value must be `CurrentUser`, `AllUsers`, `Default` or an absolute path. + {Locked="{-ModulePath}, {CurrentUser}, {AllUsers}, {Default}} + + + Have you reviewed the configuration and would you like to proceed verifying it against the system? + + + Loading the module for the configuration unit failed because it requires administrator privileges to run. + + + A unit contains a setting that requires the config root. + + + Reading configuration history + + + Desired State Configuration package not found on the system. Installing the package... + + + Failed to install Desired State Configuration package. Install the package manually or provide the path to dsc.exe through -ProcessorPath argument. + {Locked="dsc.exe","-ProcessorPath"} + + + Processor engine not supported. Processor engine: {0} + {Locked="{0}"} + + + Cannot find dsc.exe. + {Locked="{dsc.exe}"} + + diff --git a/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_InvokeConfiguration.ps1 b/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_InvokeConfiguration.ps1 index 0058220f72..173044b792 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_InvokeConfiguration.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_InvokeConfiguration.ps1 @@ -1,26 +1,26 @@ -<# - .SYNOPSIS - Performs a WinGet Configuration and synchronously wait for its completion. - Use Invoke-WinGetConfiguration to perform a WinGet Configuration. -#> - -param ( - [Parameter(Mandatory)] - $configFile -) - -if (-not(Get-Module -Name Microsoft.WinGet.Configuration -ListAvailable)) -{ - Install-Module Microsoft.WinGet.Configuration -AllowPrerelease -} - -Import-Module Microsoft.WinGet.Configuration - -$configSet = Get-WinGetConfiguration -File $configFile - -# Calling this cmdlet is not required, but it will performed by Invoke/Start -# if not done before. -$configSet = Get-WinGetConfigurationDetails -Set $configSet - -# Optionally pass -AcceptConfigurationAgreements to accept the agreements. -Invoke-WinGetConfiguration -Set $configSet +<# + .SYNOPSIS + Performs a WinGet Configuration and synchronously wait for its completion. + Use Invoke-WinGetConfiguration to perform a WinGet Configuration. +#> + +param ( + [Parameter(Mandatory)] + $configFile +) + +if (-not(Get-Module -Name Microsoft.WinGet.Configuration -ListAvailable)) +{ + Install-Module Microsoft.WinGet.Configuration -AllowPrerelease +} + +Import-Module Microsoft.WinGet.Configuration + +$configSet = Get-WinGetConfiguration -File $configFile + +# Calling this cmdlet is not required, but it will performed by Invoke/Start +# if not done before. +$configSet = Get-WinGetConfigurationDetails -Set $configSet + +# Optionally pass -AcceptConfigurationAgreements to accept the agreements. +Invoke-WinGetConfiguration -Set $configSet diff --git a/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_StartConfiguration.ps1 b/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_StartConfiguration.ps1 index c3f2fde0d5..926628c7b3 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_StartConfiguration.ps1 +++ b/src/PowerShell/Microsoft.WinGet.Configuration/Examples/Sample_StartConfiguration.ps1 @@ -1,23 +1,23 @@ -<# - .SYNOPSIS - Performs a WinGet Configuration asynchronously. - Use Start-WinGetConfiguration to perform a WinGet Configuration. -#> - -param ( - [Parameter(Mandatory)] - $configFile -) - -if (-not(Get-Module -Name Microsoft.WinGet.Configuration -ListAvailable)) -{ - Install-Module Microsoft.WinGet.Configuration -AllowPrerelease -} - -Import-Module Microsoft.WinGet.Configuration - -# Starts the configuration in the background -$configJob = Get-WinGetConfiguration -File $configFile | Start-WinGetConfiguration - -# This will block until the configuration is completed. Or print the results if already done. -Complete-WinGetConfiguration -ConfigurationJob $configJob +<# + .SYNOPSIS + Performs a WinGet Configuration asynchronously. + Use Start-WinGetConfiguration to perform a WinGet Configuration. +#> + +param ( + [Parameter(Mandatory)] + $configFile +) + +if (-not(Get-Module -Name Microsoft.WinGet.Configuration -ListAvailable)) +{ + Install-Module Microsoft.WinGet.Configuration -AllowPrerelease +} + +Import-Module Microsoft.WinGet.Configuration + +# Starts the configuration in the background +$configJob = Get-WinGetConfiguration -File $configFile | Start-WinGetConfiguration + +# This will block until the configuration is completed. Or print the results if already done. +Complete-WinGetConfiguration -ConfigurationJob $configJob 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 9349bc6576..d1ef2dc4c9 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration/ModuleFiles/Microsoft.WinGet.Configuration.psd1 +++ b/src/PowerShell/Microsoft.WinGet.Configuration/ModuleFiles/Microsoft.WinGet.Configuration.psd1 @@ -1,40 +1,40 @@ -@{ - RootModule = "Microsoft.WinGet.Configuration.Cmdlets.dll" - ModuleVersion = '0.0.1' - CompatiblePSEditions = 'Core' - GUID = '79b6b07b-7be5-4673-9cd1-fcbe3d79ba82' - Author = 'Microsoft Corporation' - CompanyName = 'Microsoft Corporation' - Copyright = '(c) Microsoft Corporation. All rights reserved.' - Description = 'PowerShell Module for the Windows Package Manager Configuration.' - PowerShellVersion = '7.4.6' - - FunctionsToExport = @() - AliasesToExport = @('cmpwgc', 'cnwgc', 'ctwgcy', 'gwgc', 'gwgcd', 'iwgc', 'rwgch', 'sawgc', 'spwgc','twgc') - - CmdletsToExport = @( - "Complete-WinGetConfiguration" - "Get-WinGetConfiguration" - "Get-WinGetConfigurationDetails" - "Invoke-WinGetConfiguration" - "Start-WinGetConfiguration" - "Test-WinGetConfiguration" - "Confirm-WinGetConfiguration" - "Stop-WinGetConfiguration" - "Remove-WinGetConfigurationHistory" - "ConvertTo-WinGetConfigurationYaml" - ) - - PrivateData = @{ - PSData = @{ - Tags = @( - 'WindowsPackageManager', - 'WinGet' - ) - ProjectUri = 'https://github.com/microsoft/winget-cli' - IconUri = 'https://aka.ms/winget-icon' - Prerelease = 'alpha' - } - } -} - +@{ + RootModule = "Microsoft.WinGet.Configuration.Cmdlets.dll" + ModuleVersion = '0.0.1' + CompatiblePSEditions = 'Core' + GUID = '79b6b07b-7be5-4673-9cd1-fcbe3d79ba82' + Author = 'Microsoft Corporation' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) Microsoft Corporation. All rights reserved.' + Description = 'PowerShell Module for the Windows Package Manager Configuration.' + PowerShellVersion = '7.4.6' + + FunctionsToExport = @() + AliasesToExport = @('cmpwgc', 'cnwgc', 'ctwgcy', 'gwgc', 'gwgcd', 'iwgc', 'rwgch', 'sawgc', 'spwgc','twgc') + + CmdletsToExport = @( + "Complete-WinGetConfiguration" + "Get-WinGetConfiguration" + "Get-WinGetConfigurationDetails" + "Invoke-WinGetConfiguration" + "Start-WinGetConfiguration" + "Test-WinGetConfiguration" + "Confirm-WinGetConfiguration" + "Stop-WinGetConfiguration" + "Remove-WinGetConfigurationHistory" + "ConvertTo-WinGetConfigurationYaml" + ) + + PrivateData = @{ + PSData = @{ + Tags = @( + 'WindowsPackageManager', + 'WinGet' + ) + ProjectUri = 'https://github.com/microsoft/winget-cli' + IconUri = 'https://aka.ms/winget-icon' + Prerelease = 'alpha' + } + } +} + diff --git a/src/PowerShell/Microsoft.WinGet.Configuration/README.md b/src/PowerShell/Microsoft.WinGet.Configuration/README.md index 12ab37e73e..a883ff2932 100644 --- a/src/PowerShell/Microsoft.WinGet.Configuration/README.md +++ b/src/PowerShell/Microsoft.WinGet.Configuration/README.md @@ -1,77 +1,77 @@ -# Windows Package Manager Configuration PowerShell Module - -The Windows Package Manager Configuration PowerShell Module is made up on two components - -1. The `Microsoft.WinGet.Configuration.Cmdlets` project which contains cmdlet implementations. -2. The `Microsoft.WinGet.Configuration.Engine` project which contain the real logic for the cmdlets. - -## Cmdlets -- Get-WinGetConfiguration -- Get-WinGetConfigurationDetails -- Invoke-WinGetConfiguration -- Start-WinGetConfiguration -- Complete-WinGetConfiguration - -## Syntax -``` -Get-WinGetConfiguration -File [] - -Get-WinGetConfigurationDetails -Set [] - -Invoke-WinGetConfiguration -Set [-AcceptConfigurationAgreements] [] - -Start-WinGetConfiguration -Set [-AcceptConfigurationAgreements] [] - -Complete-WinGetConfiguration -ConfigurationJob [] -``` - -## Prerequisites - -Minimum PowerShell 7 version: 7.2.8 - -## Telemetry -Telemetry is enabled by default. To disable it one should set the POWERSHELL_TELEMETRY_OPTOUT env variable to “1”, “yes” or “true”. - -## Building the PowerShell Module Locally -After building the Microsoft.WinGet.Configuration.Cmdlets project, the `Microsoft.WinGet.Configuration` PowerShell module can be found in the output directory in the `PowerShell` folder. For example if you built the project as x64 release, you should expect to find the module files in `$(SolutionDirectory)/src/x64/Release/PowerShell`. - - -## Adding a new cmdlet -In order to avoid [assembly dependency conflicts](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?view=powershell-7.3) this project uses a custom `AssemblyLoadContext` that load all dependencies. - -Microsoft.WinGet.Configuration.Cmdlets.dll is the binary that gets loaded when the module is imported. When Microsoft.WinGet.Configuration.Engine.dll is getting loaded the resolving handler use the custom ALC to load it. Then all the dependencies of that binary will be loaded using that custom context. - -The dependencies are laid out in two directories: `DirectDependencies` and `SharedDependencies`. The resolving handler looks for binaries under `DirectDependencies` and uses the custom ALC to load them. The custom ALC load any binaries in `DirectDependencies` and `SharedDependencies`. - -Exception: WinRT.Runtime.dll doesn't support getting loaded in multiple times in the same process, because it affects static state in the CLR itself. We special case it to get loaded in by the default loader. - -### Current layout. -``` -Microsoft.WinGet.Configuration.Cmdlets.dll -DirectDependencies\Microsoft.WinGet.Configuration.Engine.dll -SharedDependencies\Microsoft.Management.Configuration.Processor.dll -SharedDependencies\Microsoft.Windows.SDK.NET.dll -SharedDependencies\WinRT.Runtime.dll -SharedDependencies\x64\Microsoft.Management.Configuration.dll -SharedDependencies\x64\Microsoft.Management.Configuration.Projection.dll -SharedDependencies\x86\Microsoft.Management.Configuration.dll -SharedDependencies\x86\Microsoft.Management.Configuration.Projection.dll -``` -If the new cmdlet introduces a new dependency, please make sure to add it to the proper location in the AfterBuild tasks in Microsoft.WinGet.Configuration.Cmdlets.csproj. - -### Dependency graph -```mermaid -graph TD; - subgraph DF[Default Loader] - DF_1[Cmdlets.dll] - DF_2[WinRT.Runtime.dll] - end - subgraph ALC[Custom ALC] - ALC_1[Engine.dll] - ALC_2[Other dependencies] - ALC_1--> ALC_2 - end - -DF_1--> ALC_1 -ALC_1 --> DF_2 -``` +# Windows Package Manager Configuration PowerShell Module + +The Windows Package Manager Configuration PowerShell Module is made up on two components + +1. The `Microsoft.WinGet.Configuration.Cmdlets` project which contains cmdlet implementations. +2. The `Microsoft.WinGet.Configuration.Engine` project which contain the real logic for the cmdlets. + +## Cmdlets +- Get-WinGetConfiguration +- Get-WinGetConfigurationDetails +- Invoke-WinGetConfiguration +- Start-WinGetConfiguration +- Complete-WinGetConfiguration + +## Syntax +``` +Get-WinGetConfiguration -File [] + +Get-WinGetConfigurationDetails -Set [] + +Invoke-WinGetConfiguration -Set [-AcceptConfigurationAgreements] [] + +Start-WinGetConfiguration -Set [-AcceptConfigurationAgreements] [] + +Complete-WinGetConfiguration -ConfigurationJob [] +``` + +## Prerequisites + +Minimum PowerShell 7 version: 7.2.8 + +## Telemetry +Telemetry is enabled by default. To disable it one should set the POWERSHELL_TELEMETRY_OPTOUT env variable to “1”, “yes” or “true”. + +## Building the PowerShell Module Locally +After building the Microsoft.WinGet.Configuration.Cmdlets project, the `Microsoft.WinGet.Configuration` PowerShell module can be found in the output directory in the `PowerShell` folder. For example if you built the project as x64 release, you should expect to find the module files in `$(SolutionDirectory)/src/x64/Release/PowerShell`. + + +## Adding a new cmdlet +In order to avoid [assembly dependency conflicts](https://learn.microsoft.com/en-us/powershell/scripting/dev-cross-plat/resolving-dependency-conflicts?view=powershell-7.3) this project uses a custom `AssemblyLoadContext` that load all dependencies. + +Microsoft.WinGet.Configuration.Cmdlets.dll is the binary that gets loaded when the module is imported. When Microsoft.WinGet.Configuration.Engine.dll is getting loaded the resolving handler use the custom ALC to load it. Then all the dependencies of that binary will be loaded using that custom context. + +The dependencies are laid out in two directories: `DirectDependencies` and `SharedDependencies`. The resolving handler looks for binaries under `DirectDependencies` and uses the custom ALC to load them. The custom ALC load any binaries in `DirectDependencies` and `SharedDependencies`. + +Exception: WinRT.Runtime.dll doesn't support getting loaded in multiple times in the same process, because it affects static state in the CLR itself. We special case it to get loaded in by the default loader. + +### Current layout. +``` +Microsoft.WinGet.Configuration.Cmdlets.dll +DirectDependencies\Microsoft.WinGet.Configuration.Engine.dll +SharedDependencies\Microsoft.Management.Configuration.Processor.dll +SharedDependencies\Microsoft.Windows.SDK.NET.dll +SharedDependencies\WinRT.Runtime.dll +SharedDependencies\x64\Microsoft.Management.Configuration.dll +SharedDependencies\x64\Microsoft.Management.Configuration.Projection.dll +SharedDependencies\x86\Microsoft.Management.Configuration.dll +SharedDependencies\x86\Microsoft.Management.Configuration.Projection.dll +``` +If the new cmdlet introduces a new dependency, please make sure to add it to the proper location in the AfterBuild tasks in Microsoft.WinGet.Configuration.Cmdlets.csproj. + +### Dependency graph +```mermaid +graph TD; + subgraph DF[Default Loader] + DF_1[Cmdlets.dll] + DF_2[WinRT.Runtime.dll] + end + subgraph ALC[Custom ALC] + ALC_1[Engine.dll] + ALC_2[Other dependencies] + ALC_1--> ALC_2 + end + +DF_1--> ALC_1 +ALC_1 --> DF_2 +``` diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 index fcbe96f023..07d307f22f 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psd1 @@ -1,140 +1,140 @@ -# -# Module manifest for module 'Microsoft.WinGet.DSC' -# -# Created by: Microsoft Corporation -# - -@{ - - # Script module or binary module file associated with this manifest. - RootModule = 'Microsoft.WinGet.DSC.psm1' - - # Version number of this module. - ModuleVersion = '0.0.1' - - # Supported PSEditions - CompatiblePSEditions = 'Core' - - # ID used to uniquely identify this module - GUID = '8c9326eb-595a-40eb-8696-b289e8085cad' - - # Author of this module - Author = 'Microsoft Corporation' - - # Company or vendor of this module - CompanyName = 'Microsoft Corporation' - - # Copyright statement for this module - Copyright = '(c) Microsoft Corporation. All rights reserved.' - - # Description of the functionality provided by this module - Description = 'PowerShell Module with DSC resources related to WinGet configurations' - - # Minimum version of the PowerShell engine required by this module - PowerShellVersion = '7.2' - - # Name of the PowerShell host required by this module - # PowerShellHostName = '' - - # Minimum version of the PowerShell host required by this module - # PowerShellHostVersion = '' - - # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # DotNetFrameworkVersion = '' - - # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. - # ClrVersion = '' - - # Processor architecture (None, X86, Amd64) required by this module - # ProcessorArchitecture = '' - - # Modules that must be imported into the global environment prior to importing this module - RequiredModules = @('Microsoft.WinGet.Client') - - # Assemblies that must be loaded prior to importing this module - # RequiredAssemblies = @() - - # Script files (.ps1) that are run in the caller's environment prior to importing this module. - # ScriptsToProcess = @() - - # Type files (.ps1xml) to be loaded when importing this module - # TypesToProcess = @() - - # Format files (.ps1xml) to be loaded when importing this module - # FormatsToProcess = @() - - # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess - # NestedModules = @() - - # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. - # FunctionsToExport = @() - - # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. - # CmdletsToExport = @() - - # Variables to export from this module - # VariablesToExport = @() - - # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. - # AliasesToExport = @() - - # DSC resources to export from this module - DscResourcesToExport = @( - 'WinGetUserSettings' - 'WinGetAdminSettings' - 'WinGetSource' - 'WinGetPackageManager' - 'WinGetPackage' - ) - - # List of all modules packaged with this module - # ModuleList = @() - - # List of all files packaged with this module - # FileList = @() - - # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. - PrivateData = @{ - - PSData = @{ - - # Tags applied to this module. These help with module discovery in online galleries. - Tags = @( - 'PSEdition_Core', - 'Windows', - 'WindowsPackageManager' - ) - - # A URL to the license for this module. - # LicenseUri = '' - - # A URL to the main website for this project. - ProjectUri = 'https://github.com/microsoft/winget-cli' - - # A URL to an icon representing this module. - IconUri = 'https://aka.ms/winget-icon' - - # ReleaseNotes of this module - # ReleaseNotes = '' - - # Prerelease string of this module - Prerelease = 'alpha' - - # Flag to indicate whether the module requires explicit user acceptance for install/update/save - # RequireLicenseAcceptance = $false - - # External dependent modules of this module - # ExternalModuleDependencies = @() - - } # End of PSData hashtable - - } # End of PrivateData hashtable - - # HelpInfo URI of this module - # HelpInfoURI = '' - - # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. - # DefaultCommandPrefix = '' - - } +# +# Module manifest for module 'Microsoft.WinGet.DSC' +# +# Created by: Microsoft Corporation +# + +@{ + + # Script module or binary module file associated with this manifest. + RootModule = 'Microsoft.WinGet.DSC.psm1' + + # Version number of this module. + ModuleVersion = '0.0.1' + + # Supported PSEditions + CompatiblePSEditions = 'Core' + + # ID used to uniquely identify this module + GUID = '8c9326eb-595a-40eb-8696-b289e8085cad' + + # Author of this module + Author = 'Microsoft Corporation' + + # Company or vendor of this module + CompanyName = 'Microsoft Corporation' + + # Copyright statement for this module + Copyright = '(c) Microsoft Corporation. All rights reserved.' + + # Description of the functionality provided by this module + Description = 'PowerShell Module with DSC resources related to WinGet configurations' + + # Minimum version of the PowerShell engine required by this module + PowerShellVersion = '7.2' + + # Name of the PowerShell host required by this module + # PowerShellHostName = '' + + # Minimum version of the PowerShell host required by this module + # PowerShellHostVersion = '' + + # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # DotNetFrameworkVersion = '' + + # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. + # ClrVersion = '' + + # Processor architecture (None, X86, Amd64) required by this module + # ProcessorArchitecture = '' + + # Modules that must be imported into the global environment prior to importing this module + RequiredModules = @('Microsoft.WinGet.Client') + + # Assemblies that must be loaded prior to importing this module + # RequiredAssemblies = @() + + # Script files (.ps1) that are run in the caller's environment prior to importing this module. + # ScriptsToProcess = @() + + # Type files (.ps1xml) to be loaded when importing this module + # TypesToProcess = @() + + # Format files (.ps1xml) to be loaded when importing this module + # FormatsToProcess = @() + + # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess + # NestedModules = @() + + # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. + # FunctionsToExport = @() + + # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. + # CmdletsToExport = @() + + # Variables to export from this module + # VariablesToExport = @() + + # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. + # AliasesToExport = @() + + # DSC resources to export from this module + DscResourcesToExport = @( + 'WinGetUserSettings' + 'WinGetAdminSettings' + 'WinGetSource' + 'WinGetPackageManager' + 'WinGetPackage' + ) + + # List of all modules packaged with this module + # ModuleList = @() + + # List of all files packaged with this module + # FileList = @() + + # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. + PrivateData = @{ + + PSData = @{ + + # Tags applied to this module. These help with module discovery in online galleries. + Tags = @( + 'PSEdition_Core', + 'Windows', + 'WindowsPackageManager' + ) + + # A URL to the license for this module. + # LicenseUri = '' + + # A URL to the main website for this project. + ProjectUri = 'https://github.com/microsoft/winget-cli' + + # A URL to an icon representing this module. + IconUri = 'https://aka.ms/winget-icon' + + # ReleaseNotes of this module + # ReleaseNotes = '' + + # Prerelease string of this module + Prerelease = 'alpha' + + # Flag to indicate whether the module requires explicit user acceptance for install/update/save + # RequireLicenseAcceptance = $false + + # External dependent modules of this module + # ExternalModuleDependencies = @() + + } # End of PSData hashtable + + } # End of PrivateData hashtable + + # HelpInfo URI of this module + # HelpInfoURI = '' + + # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. + # DefaultCommandPrefix = '' + + } \ No newline at end of file diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 index cd4650262e..6e5d411565 100644 --- a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 +++ b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 @@ -1,680 +1,680 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -using namespace System.Collections.Generic - -# Check that we are running as an administrator -function Assert-IsAdministrator -{ - $windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() - $windowsPrincipal = New-Object -TypeName 'System.Security.Principal.WindowsPrincipal' -ArgumentList @( $windowsIdentity ) - - $adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator - - if (-not $windowsPrincipal.IsInRole($adminRole)) - { - New-InvalidOperationException -Message "This resource must run as an Administrator." - } -} - -#region enums -enum WinGetAction -{ - Partial - Full -} - -enum WinGetEnsure -{ - Absent - Present -} - -enum WinGetMatchOption -{ - Equals - EqualsCaseInsensitive - StartsWithCaseInsensitive - ContainsCaseInsensitive -} - -enum WinGetInstallMode -{ - Default - Silent - Interactive -} - -enum WinGetTrustLevel -{ - Undefined - None - Trusted -} - -#endregion enums - -#region DscResources -# Author here all DSC Resources. -# DSC Powershell doesn't support binary DSC resources without the MOF schema. -# DSC Powershell classes aren't discoverable if placed outside of the psm1. - -# This resource is in charge of managing the settings.json file of winget. -[DSCResource()] -class WinGetUserSettings -{ - # We need a key. Do not set. - [DscProperty(Key)] - [string]$SID - - # A hash table with the desired settings. - [DscProperty(Mandatory)] - [Hashtable]$Settings - - [DscProperty()] - [WinGetAction]$Action = [WinGetAction]::Full - - # Gets the current UserSettings by looking at the settings.json file for the current user. - [WinGetUserSettings] Get() - { - $userSettings = Get-WinGetUserSetting - $result = @{ - SID = '' - Settings = $userSettings - } - return $result - } - - # Tests if desired properties match. - [bool] Test() - { - $hashArgs = @{ - UserSettings = $this.Settings - } - - if ($this.Action -eq [WinGetAction]::Partial) - { - $hashArgs.Add('IgnoreNotSet', $true) - } - - return Test-WinGetUserSetting @hashArgs - } - - # Sets the desired properties. - [void] Set() - { - $hashArgs = @{ - UserSettings = $this.Settings - } - - if ($this.Action -eq [WinGetAction]::Partial) - { - $hashArgs.Add('Merge', $true) - } - - Set-WinGetUserSetting @hashArgs - } -} - -# Handles configuration of administrator settings. -[DSCResource()] -class WinGetAdminSettings -{ - # We need a key. Do not set. - [DscProperty(Key)] - [string]$SID - - # A hash table with the desired admin settings. - [DscProperty(Mandatory)] - [Hashtable]$Settings - - # Gets the administrator settings. - [WinGetAdminSettings] Get() - { - $settingsJson = Get-WinGetSetting - # Get admin setting values. - - $result = @{ - SID = '' - Settings = $settingsJson.adminSettings - } - return $result - } - - # Tests if administrator settings given are set as expected. - # This doesn't do a full comparison to allow users to don't have to update - # their resource every time a new admin setting is added on winget. - [bool] Test() - { - $adminSettings = $this.Get().Settings - foreach ($adminSetting in $adminSettings.GetEnumerator()) - { - if ($this.Settings.ContainsKey($adminSetting.Name)) - { - if ($this.Settings[$adminSetting.Name] -ne $adminSetting.Value) - { - return $false - } - } - } - - return $true - } - - # Sets the desired properties. - [void] Set() - { - Assert-IsAdministrator - - # It might be better to implement an internal Test with one value, or - # create a new instances with only one setting than calling Enable/Disable - # for all of them even if only one is different. - if (-not $this.Test()) - { - foreach ($adminSetting in $this.Settings.GetEnumerator()) - { - if ($adminSetting.Value) - { - Enable-WinGetSetting -Name $adminSetting.Name - } - else - { - Disable-WinGetSetting -Name $adminSetting.Name - } - } - } - } -} - -[DSCResource()] -class WinGetSource -{ - [DscProperty(Key, Mandatory)] - [string]$Name - - [DscProperty(Mandatory)] - [string]$Argument - - [DscProperty()] - [string]$Type - - [DscProperty()] - [WinGetTrustLevel]$TrustLevel = [WinGetTrustLevel]::Undefined - - [DscProperty()] - [nullable[bool]]$Explicit = $null - - [DscProperty()] - [nullable[int]]$Priority = $null - - [DscProperty()] - [WinGetEnsure]$Ensure = [WinGetEnsure]::Present - - [WinGetSource] Get() - { - if ([String]::IsNullOrWhiteSpace($this.Name)) - { - throw "A value must be provided for WinGetSource::Name" - } - - $currentSource = $null - - try { - $currentSource = Get-WinGetSource -Name $this.Name - } - catch { - } - - $result = [WinGetSource]::new() - - if ($currentSource) - { - $result.Ensure = [WinGetEnsure]::Present - $result.Name = $currentSource.Name - $result.Argument = $currentSource.Argument - $result.Type = $currentSource.Type - $result.TrustLevel = $currentSource.TrustLevel - $result.Explicit = $currentSource.Explicit - $result.Priority = $currentSource.Priority - } - else - { - $result.Ensure = [WinGetEnsure]::Absent - $result.Name = $this.Name - } - - return $result - } - - [bool] Test() - { - return $this.TestAgainstCurrent($this.Get()) - } - - [void] Set() - { - Assert-IsAdministrator - - $currentSource = $this.Get() - - $removeSource = $false - $resetSource = $false - $addSource = $false - - if ($this.Ensure -eq [WinGetEnsure]::Present) - { - if ($currentSource.Ensure -eq [WinGetEnsure]::Present) - { - if (-not $this.TestAgainstCurrent($currentSource)) - { - $resetSource = $true - $addSource = $true - } - # else in desired state - } - else - { - $addSource = $true - } - } - else - { - if ($currentSource.Ensure -eq [WinGetEnsure]::Present) - { - $removeSource = $true - } - # else in desired state (Absent) - } - - if ($removeSource) - { - Remove-WinGetSource -Name $this.Name - } - # Only remove OR reset should be true, not both - elseif ($resetSource) - { - Reset-WinGetSource -Name $this.Name - } - - if ($addSource) - { - $hashArgs = @{ - Name = $this.Name - Argument = $this.Argument - } - - if (-not [string]::IsNullOrWhiteSpace($this.Type)) - { - $hashArgs.Add("Type", $this.Type) - } - - if ($this.TrustLevel -ne [WinGetTrustLevel]::Undefined) - { - $hashArgs.Add("TrustLevel", $this.TrustLevel) - } - - if ($null -ne $this.Explicit) - { - $hashArgs.Add("Explicit", $this.Explicit) - } - - if ($null -ne $this.Priority) - { - $hashArgs.Add("Priority", $this.Priority) - } - - Add-WinGetSource @hashArgs - } - } - - # Test $this against a value retrieved from Get - # We don't need to check Name because it is the Key for Get - [bool] hidden TestAgainstCurrent([WinGetSource]$currentSource) - { - if ($this.Ensure -eq [WinGetEnsure]::Absent -and - $currentSource.Ensure -eq [WinGetEnsure]::Absent) - { - return $true - } - - if ($this.Ensure -ne $currentSource.Ensure -or - $this.Argument -ne $currentSource.Argument) - { - return $false - } - - if (-not([string]::IsNullOrWhiteSpace($this.Type)) -and - $this.Type -ne $currentSource.Type) - { - return $false - } - - if ($this.TrustLevel -ne [WinGetTrustLevel]::Undefined -and - $this.TrustLevel -ne $currentSource.TrustLevel) - { - return $false - } - - if ($null -ne $this.Explicit -and - $this.Explicit -ne $currentSource.Explicit) - { - return $false - } - - if ($null -ne $this.Priority -and - $this.Priority -ne $currentSource.Priority) - { - return $false - } - - return $true - } -} - -# TODO: It would be nice if these resource has a non configurable property that has extra information that comes from -# GitHub. We could implement it here or add more cmdlets in Microsoft.WinGet.Client. -[DSCResource()] -class WinGetPackageManager -{ - # We need a key. Do not set. - [DscProperty(Key)] - [string]$SID - - [DscProperty()] - [string]$Version = "" - - [DscProperty()] - [bool]$UseLatest - - [DscProperty()] - [bool]$UseLatestPreRelease - - # If winget is not installed the version will be empty. - [WinGetPackageManager] Get() - { - $integrityResource = [WinGetPackageManager]::new() - if ($integrityResource.Test()) - { - $integrityResource.Version = Get-WinGetVersion - } - - return $integrityResource - } - - # Tests winget is installed. - [bool] Test() - { - try - { - $hashArgs = @{} - - if ($this.UseLatest) - { - $hashArgs.Add("Latest", $true) - } elseif ($this.UseLatestPreRelease) - { - $hashArgs.Add("Latest", $true) - $hashArgs.Add("IncludePrerelease", $true) - } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) - { - $hashArgs.Add("Version", $this.Version) - } - - Assert-WinGetPackageManager @hashArgs - } - catch - { - return $false - } - - return $true - } - - # Repairs Winget. - [void] Set() - { - if (-not $this.Test()) - { - $result = -1 - $hashArgs = @{} - - if ($this.UseLatest) - { - $hashArgs.Add("Latest", $true) - } elseif ($this.UseLatestPreRelease) - { - $hashArgs.Add("Latest", $true) - $hashArgs.Add("IncludePrerelease", $true) - } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) - { - $hashArgs.Add("Version", $this.Version) - } - - $result = Repair-WinGetPackageManager @hashArgs - - if ($result -ne 0) - { - # TODO: Localize. - throw "Failed to repair winget. Result $result" - } - } - } -} - -[DSCResource()] -class WinGetPackage -{ - [DscProperty(Key, Mandatory)] - [string]$Id - - [DscProperty(Key)] - [string]$Source - - [DscProperty()] - [string]$Version - - [DscProperty()] - [WinGetEnsure]$Ensure = [WinGetEnsure]::Present - - [DscProperty()] - [WinGetMatchOption]$MatchOption = [WinGetMatchOption]::EqualsCaseInsensitive - - [DscProperty()] - [bool]$UseLatest = $false - - [DSCProperty()] - [WinGetInstallMode]$InstallMode = [WinGetInstallMode]::Silent - - [PSObject] hidden $CatalogPackage = $null - - [WinGetPackage] Get() - { - if ([String]::IsNullOrWhiteSpace($this.Id)) - { - throw "A value must be provided for WinGetPackage::Id" - } - - $result = [WinGetPackage]::new() - - $hashArgs = @{ - Id = $this.Id - MatchOption = $this.MatchOption - } - - if (-not([string]::IsNullOrWhiteSpace($this.Source))) - { - $hashArgs.Add("Source", $this.Source) - } - - $result.CatalogPackage = Get-WinGetPackage @hashArgs - if ($null -ne $result.CatalogPackage) - { - $result.Ensure = [WinGetEnsure]::Present - $result.Id = $result.CatalogPackage.Id - $result.Source = $result.CatalogPackage.Source - $result.Version = $result.CatalogPackage.InstalledVersion - $result.UseLatest = -not $result.CatalogPackage.IsUpdateAvailable - } - else - { - $result.Ensure = [WinGetEnsure]::Absent - $result.Id = $this.Id - $result.MatchOption = $this.MatchOption - $result.Source = $this.Source - } - - return $result - } - - [bool] Test() - { - return $this.TestAgainstCurrent($this.Get()) - } - - [void] Set() - { - $currentPackage = $this.Get() - - if (-not $this.TestAgainstCurrent($currentPackage)) - { - $hashArgs = @{ - Id = $this.Id - MatchOption = $this.MatchOption - Mode = $this.InstallMode - } - - if ($this.Ensure -eq [WinGetEnsure]::Present) - { - if (-not([string]::IsNullOrWhiteSpace($this.Source))) - { - $hashArgs.Add("Source", $this.Source) - } - - if ($currentPackage.Ensure -eq [WinGetEnsure]::Present) - { - if ($this.UseLatest) - { - $this.TryUpdate($hashArgs) - } - elseif (-not([string]::IsNullOrWhiteSpace($this.Version))) - { - $hashArgs.Add("Version", $this.Version) - - $compareResult = $currentPackage.CatalogPackage.CompareToVersion($this.Version) - switch ($compareResult) - { - 'Lesser' - { - $this.TryUpdate($hashArgs) - break - } - {'Greater' -or 'Unknown'} - { - # The installed package has a greater version or unknown. Uninstall and install. - $this.Uninstall() - $this.Install($hashArgs) - break - } - } - } - } - else - { - if (-not([string]::IsNullOrWhiteSpace($this.Version))) - { - $hashArgs.Add("Version", $this.Version) - } - - $this.Install($hashArgs) - } - } - else - { - $this.Uninstall() - } - } - } - - [bool] hidden TestAgainstCurrent([WinGetPackage]$currentPackage) - { - if ($this.Ensure -eq [WinGetEnsure]::Absent -and - $currentPackage.Ensure -eq [WinGetEnsure]::Absent) - { - return $true - } - - $this.CatalogPackage = $currentPackage.CatalogPackage - - if ($this.Ensure -ne $currentPackage.Ensure) - { - return $false - } - - # At this point we know is installed. - # If asked for latest, but there are updates available. - if ($this.UseLatest) - { - if (-not $currentPackage.UseLatest) - { - return $false - } - } - # If there is an specific version, compare with the current installed version. - elseif (-not ([string]::IsNullOrWhiteSpace($this.Version))) - { - $compareResult = $currentPackage.CatalogPackage.CompareToVersion($this.Version) - if ($compareResult -ne 'Equal') - { - return $false - } - } - - return $true - } - - hidden Install([Hashtable]$hashArgs) - { - $installResult = Install-WinGetPackage @hashArgs - if (-not $installResult.Succeeded()) - { - # TODO: Localize. - throw "WinGetPackage Failed installing $($this.Id). $($installResult.ErrorMessage())" - } - } - - hidden Uninstall() - { - $uninstallResult = Uninstall-WinGetPackage -PSCatalogPackage $this.CatalogPackage - if (-not $uninstallResult.Succeeded()) - { - # TODO: Localize. - throw "WinGetPackage Failed uninstalling $($this.Id). $($uninstallResult.ErrorMessage())" - } - } - - hidden Update([Hashtable]$hashArgs) - { - $updateResult = Update-WinGetPackage @hashArgs - if (-not $updateResult.Succeeded()) - { - # TODO: Localize. - throw "WinGetPackage Failed updating $($this.Id). $($updateResult.ErrorMessage())" - } - } - - # Tries to update, if not, uninstall and install. - hidden TryUpdate([Hashtable]$hashArgs) - { - try - { - $this.Update($hashArgs) - } - catch - { - $this.Uninstall() - $this.Install($hashArgs) - } - } -} - -#endregion DscResources +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +using namespace System.Collections.Generic + +# Check that we are running as an administrator +function Assert-IsAdministrator +{ + $windowsIdentity = [System.Security.Principal.WindowsIdentity]::GetCurrent() + $windowsPrincipal = New-Object -TypeName 'System.Security.Principal.WindowsPrincipal' -ArgumentList @( $windowsIdentity ) + + $adminRole = [System.Security.Principal.WindowsBuiltInRole]::Administrator + + if (-not $windowsPrincipal.IsInRole($adminRole)) + { + New-InvalidOperationException -Message "This resource must run as an Administrator." + } +} + +#region enums +enum WinGetAction +{ + Partial + Full +} + +enum WinGetEnsure +{ + Absent + Present +} + +enum WinGetMatchOption +{ + Equals + EqualsCaseInsensitive + StartsWithCaseInsensitive + ContainsCaseInsensitive +} + +enum WinGetInstallMode +{ + Default + Silent + Interactive +} + +enum WinGetTrustLevel +{ + Undefined + None + Trusted +} + +#endregion enums + +#region DscResources +# Author here all DSC Resources. +# DSC Powershell doesn't support binary DSC resources without the MOF schema. +# DSC Powershell classes aren't discoverable if placed outside of the psm1. + +# This resource is in charge of managing the settings.json file of winget. +[DSCResource()] +class WinGetUserSettings +{ + # We need a key. Do not set. + [DscProperty(Key)] + [string]$SID + + # A hash table with the desired settings. + [DscProperty(Mandatory)] + [Hashtable]$Settings + + [DscProperty()] + [WinGetAction]$Action = [WinGetAction]::Full + + # Gets the current UserSettings by looking at the settings.json file for the current user. + [WinGetUserSettings] Get() + { + $userSettings = Get-WinGetUserSetting + $result = @{ + SID = '' + Settings = $userSettings + } + return $result + } + + # Tests if desired properties match. + [bool] Test() + { + $hashArgs = @{ + UserSettings = $this.Settings + } + + if ($this.Action -eq [WinGetAction]::Partial) + { + $hashArgs.Add('IgnoreNotSet', $true) + } + + return Test-WinGetUserSetting @hashArgs + } + + # Sets the desired properties. + [void] Set() + { + $hashArgs = @{ + UserSettings = $this.Settings + } + + if ($this.Action -eq [WinGetAction]::Partial) + { + $hashArgs.Add('Merge', $true) + } + + Set-WinGetUserSetting @hashArgs + } +} + +# Handles configuration of administrator settings. +[DSCResource()] +class WinGetAdminSettings +{ + # We need a key. Do not set. + [DscProperty(Key)] + [string]$SID + + # A hash table with the desired admin settings. + [DscProperty(Mandatory)] + [Hashtable]$Settings + + # Gets the administrator settings. + [WinGetAdminSettings] Get() + { + $settingsJson = Get-WinGetSetting + # Get admin setting values. + + $result = @{ + SID = '' + Settings = $settingsJson.adminSettings + } + return $result + } + + # Tests if administrator settings given are set as expected. + # This doesn't do a full comparison to allow users to don't have to update + # their resource every time a new admin setting is added on winget. + [bool] Test() + { + $adminSettings = $this.Get().Settings + foreach ($adminSetting in $adminSettings.GetEnumerator()) + { + if ($this.Settings.ContainsKey($adminSetting.Name)) + { + if ($this.Settings[$adminSetting.Name] -ne $adminSetting.Value) + { + return $false + } + } + } + + return $true + } + + # Sets the desired properties. + [void] Set() + { + Assert-IsAdministrator + + # It might be better to implement an internal Test with one value, or + # create a new instances with only one setting than calling Enable/Disable + # for all of them even if only one is different. + if (-not $this.Test()) + { + foreach ($adminSetting in $this.Settings.GetEnumerator()) + { + if ($adminSetting.Value) + { + Enable-WinGetSetting -Name $adminSetting.Name + } + else + { + Disable-WinGetSetting -Name $adminSetting.Name + } + } + } + } +} + +[DSCResource()] +class WinGetSource +{ + [DscProperty(Key, Mandatory)] + [string]$Name + + [DscProperty(Mandatory)] + [string]$Argument + + [DscProperty()] + [string]$Type + + [DscProperty()] + [WinGetTrustLevel]$TrustLevel = [WinGetTrustLevel]::Undefined + + [DscProperty()] + [nullable[bool]]$Explicit = $null + + [DscProperty()] + [nullable[int]]$Priority = $null + + [DscProperty()] + [WinGetEnsure]$Ensure = [WinGetEnsure]::Present + + [WinGetSource] Get() + { + if ([String]::IsNullOrWhiteSpace($this.Name)) + { + throw "A value must be provided for WinGetSource::Name" + } + + $currentSource = $null + + try { + $currentSource = Get-WinGetSource -Name $this.Name + } + catch { + } + + $result = [WinGetSource]::new() + + if ($currentSource) + { + $result.Ensure = [WinGetEnsure]::Present + $result.Name = $currentSource.Name + $result.Argument = $currentSource.Argument + $result.Type = $currentSource.Type + $result.TrustLevel = $currentSource.TrustLevel + $result.Explicit = $currentSource.Explicit + $result.Priority = $currentSource.Priority + } + else + { + $result.Ensure = [WinGetEnsure]::Absent + $result.Name = $this.Name + } + + return $result + } + + [bool] Test() + { + return $this.TestAgainstCurrent($this.Get()) + } + + [void] Set() + { + Assert-IsAdministrator + + $currentSource = $this.Get() + + $removeSource = $false + $resetSource = $false + $addSource = $false + + if ($this.Ensure -eq [WinGetEnsure]::Present) + { + if ($currentSource.Ensure -eq [WinGetEnsure]::Present) + { + if (-not $this.TestAgainstCurrent($currentSource)) + { + $resetSource = $true + $addSource = $true + } + # else in desired state + } + else + { + $addSource = $true + } + } + else + { + if ($currentSource.Ensure -eq [WinGetEnsure]::Present) + { + $removeSource = $true + } + # else in desired state (Absent) + } + + if ($removeSource) + { + Remove-WinGetSource -Name $this.Name + } + # Only remove OR reset should be true, not both + elseif ($resetSource) + { + Reset-WinGetSource -Name $this.Name + } + + if ($addSource) + { + $hashArgs = @{ + Name = $this.Name + Argument = $this.Argument + } + + if (-not [string]::IsNullOrWhiteSpace($this.Type)) + { + $hashArgs.Add("Type", $this.Type) + } + + if ($this.TrustLevel -ne [WinGetTrustLevel]::Undefined) + { + $hashArgs.Add("TrustLevel", $this.TrustLevel) + } + + if ($null -ne $this.Explicit) + { + $hashArgs.Add("Explicit", $this.Explicit) + } + + if ($null -ne $this.Priority) + { + $hashArgs.Add("Priority", $this.Priority) + } + + Add-WinGetSource @hashArgs + } + } + + # Test $this against a value retrieved from Get + # We don't need to check Name because it is the Key for Get + [bool] hidden TestAgainstCurrent([WinGetSource]$currentSource) + { + if ($this.Ensure -eq [WinGetEnsure]::Absent -and + $currentSource.Ensure -eq [WinGetEnsure]::Absent) + { + return $true + } + + if ($this.Ensure -ne $currentSource.Ensure -or + $this.Argument -ne $currentSource.Argument) + { + return $false + } + + if (-not([string]::IsNullOrWhiteSpace($this.Type)) -and + $this.Type -ne $currentSource.Type) + { + return $false + } + + if ($this.TrustLevel -ne [WinGetTrustLevel]::Undefined -and + $this.TrustLevel -ne $currentSource.TrustLevel) + { + return $false + } + + if ($null -ne $this.Explicit -and + $this.Explicit -ne $currentSource.Explicit) + { + return $false + } + + if ($null -ne $this.Priority -and + $this.Priority -ne $currentSource.Priority) + { + return $false + } + + return $true + } +} + +# TODO: It would be nice if these resource has a non configurable property that has extra information that comes from +# GitHub. We could implement it here or add more cmdlets in Microsoft.WinGet.Client. +[DSCResource()] +class WinGetPackageManager +{ + # We need a key. Do not set. + [DscProperty(Key)] + [string]$SID + + [DscProperty()] + [string]$Version = "" + + [DscProperty()] + [bool]$UseLatest + + [DscProperty()] + [bool]$UseLatestPreRelease + + # If winget is not installed the version will be empty. + [WinGetPackageManager] Get() + { + $integrityResource = [WinGetPackageManager]::new() + if ($integrityResource.Test()) + { + $integrityResource.Version = Get-WinGetVersion + } + + return $integrityResource + } + + # Tests winget is installed. + [bool] Test() + { + try + { + $hashArgs = @{} + + if ($this.UseLatest) + { + $hashArgs.Add("Latest", $true) + } elseif ($this.UseLatestPreRelease) + { + $hashArgs.Add("Latest", $true) + $hashArgs.Add("IncludePrerelease", $true) + } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) + { + $hashArgs.Add("Version", $this.Version) + } + + Assert-WinGetPackageManager @hashArgs + } + catch + { + return $false + } + + return $true + } + + # Repairs Winget. + [void] Set() + { + if (-not $this.Test()) + { + $result = -1 + $hashArgs = @{} + + if ($this.UseLatest) + { + $hashArgs.Add("Latest", $true) + } elseif ($this.UseLatestPreRelease) + { + $hashArgs.Add("Latest", $true) + $hashArgs.Add("IncludePrerelease", $true) + } elseif (-not [string]::IsNullOrWhiteSpace($this.Version)) + { + $hashArgs.Add("Version", $this.Version) + } + + $result = Repair-WinGetPackageManager @hashArgs + + if ($result -ne 0) + { + # TODO: Localize. + throw "Failed to repair winget. Result $result" + } + } + } +} + +[DSCResource()] +class WinGetPackage +{ + [DscProperty(Key, Mandatory)] + [string]$Id + + [DscProperty(Key)] + [string]$Source + + [DscProperty()] + [string]$Version + + [DscProperty()] + [WinGetEnsure]$Ensure = [WinGetEnsure]::Present + + [DscProperty()] + [WinGetMatchOption]$MatchOption = [WinGetMatchOption]::EqualsCaseInsensitive + + [DscProperty()] + [bool]$UseLatest = $false + + [DSCProperty()] + [WinGetInstallMode]$InstallMode = [WinGetInstallMode]::Silent + + [PSObject] hidden $CatalogPackage = $null + + [WinGetPackage] Get() + { + if ([String]::IsNullOrWhiteSpace($this.Id)) + { + throw "A value must be provided for WinGetPackage::Id" + } + + $result = [WinGetPackage]::new() + + $hashArgs = @{ + Id = $this.Id + MatchOption = $this.MatchOption + } + + if (-not([string]::IsNullOrWhiteSpace($this.Source))) + { + $hashArgs.Add("Source", $this.Source) + } + + $result.CatalogPackage = Get-WinGetPackage @hashArgs + if ($null -ne $result.CatalogPackage) + { + $result.Ensure = [WinGetEnsure]::Present + $result.Id = $result.CatalogPackage.Id + $result.Source = $result.CatalogPackage.Source + $result.Version = $result.CatalogPackage.InstalledVersion + $result.UseLatest = -not $result.CatalogPackage.IsUpdateAvailable + } + else + { + $result.Ensure = [WinGetEnsure]::Absent + $result.Id = $this.Id + $result.MatchOption = $this.MatchOption + $result.Source = $this.Source + } + + return $result + } + + [bool] Test() + { + return $this.TestAgainstCurrent($this.Get()) + } + + [void] Set() + { + $currentPackage = $this.Get() + + if (-not $this.TestAgainstCurrent($currentPackage)) + { + $hashArgs = @{ + Id = $this.Id + MatchOption = $this.MatchOption + Mode = $this.InstallMode + } + + if ($this.Ensure -eq [WinGetEnsure]::Present) + { + if (-not([string]::IsNullOrWhiteSpace($this.Source))) + { + $hashArgs.Add("Source", $this.Source) + } + + if ($currentPackage.Ensure -eq [WinGetEnsure]::Present) + { + if ($this.UseLatest) + { + $this.TryUpdate($hashArgs) + } + elseif (-not([string]::IsNullOrWhiteSpace($this.Version))) + { + $hashArgs.Add("Version", $this.Version) + + $compareResult = $currentPackage.CatalogPackage.CompareToVersion($this.Version) + switch ($compareResult) + { + 'Lesser' + { + $this.TryUpdate($hashArgs) + break + } + {'Greater' -or 'Unknown'} + { + # The installed package has a greater version or unknown. Uninstall and install. + $this.Uninstall() + $this.Install($hashArgs) + break + } + } + } + } + else + { + if (-not([string]::IsNullOrWhiteSpace($this.Version))) + { + $hashArgs.Add("Version", $this.Version) + } + + $this.Install($hashArgs) + } + } + else + { + $this.Uninstall() + } + } + } + + [bool] hidden TestAgainstCurrent([WinGetPackage]$currentPackage) + { + if ($this.Ensure -eq [WinGetEnsure]::Absent -and + $currentPackage.Ensure -eq [WinGetEnsure]::Absent) + { + return $true + } + + $this.CatalogPackage = $currentPackage.CatalogPackage + + if ($this.Ensure -ne $currentPackage.Ensure) + { + return $false + } + + # At this point we know is installed. + # If asked for latest, but there are updates available. + if ($this.UseLatest) + { + if (-not $currentPackage.UseLatest) + { + return $false + } + } + # If there is an specific version, compare with the current installed version. + elseif (-not ([string]::IsNullOrWhiteSpace($this.Version))) + { + $compareResult = $currentPackage.CatalogPackage.CompareToVersion($this.Version) + if ($compareResult -ne 'Equal') + { + return $false + } + } + + return $true + } + + hidden Install([Hashtable]$hashArgs) + { + $installResult = Install-WinGetPackage @hashArgs + if (-not $installResult.Succeeded()) + { + # TODO: Localize. + throw "WinGetPackage Failed installing $($this.Id). $($installResult.ErrorMessage())" + } + } + + hidden Uninstall() + { + $uninstallResult = Uninstall-WinGetPackage -PSCatalogPackage $this.CatalogPackage + if (-not $uninstallResult.Succeeded()) + { + # TODO: Localize. + throw "WinGetPackage Failed uninstalling $($this.Id). $($uninstallResult.ErrorMessage())" + } + } + + hidden Update([Hashtable]$hashArgs) + { + $updateResult = Update-WinGetPackage @hashArgs + if (-not $updateResult.Succeeded()) + { + # TODO: Localize. + throw "WinGetPackage Failed updating $($this.Id). $($updateResult.ErrorMessage())" + } + } + + # Tries to update, if not, uninstall and install. + hidden TryUpdate([Hashtable]$hashArgs) + { + try + { + $this.Update($hashArgs) + } + catch + { + $this.Uninstall() + $this.Install($hashArgs) + } + } +} + +#endregion DscResources diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/ErrorCodes.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/ErrorCodes.cs index 6c32e58800..6a4233e367 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/ErrorCodes.cs +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/ErrorCodes.cs @@ -1,19 +1,19 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.SharedLib.Exceptions -{ - /// - /// This should match the ones in AppInstallerErrors.h. - /// - internal static class ErrorCodes - { -#pragma warning disable SA1600 // ElementsMustBeDocumented - internal const int AppInstallerCLIErrorInternalError = unchecked((int)0x8A150001); - internal const int AppInstallerCLIErrorBlockedByPolicy = unchecked((int)0x8A15003A); -#pragma warning restore SA1600 // ElementsMustBeDocumented - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.SharedLib.Exceptions +{ + /// + /// This should match the ones in AppInstallerErrors.h. + /// + internal static class ErrorCodes + { +#pragma warning disable SA1600 // ElementsMustBeDocumented + internal const int AppInstallerCLIErrorInternalError = unchecked((int)0x8A150001); + internal const int AppInstallerCLIErrorBlockedByPolicy = unchecked((int)0x8A15003A); +#pragma warning restore SA1600 // ElementsMustBeDocumented + } +} diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/GroupPolicyException.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/GroupPolicyException.cs index d620244da5..a8ac12fe78 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/GroupPolicyException.cs +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/Exceptions/GroupPolicyException.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.SharedLib.Exceptions -{ - using System; - using Microsoft.WinGet.SharedLib.Extensions; - using Microsoft.WinGet.SharedLib.PolicySettings; - - /// - /// Class that implements GroupPolicyException. - /// - [Serializable] - public class GroupPolicyException : Exception - { - /// - /// Initializes a new instance of the class. - /// - /// Policy. - /// GroupPolicyFailureType. +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.SharedLib.Exceptions +{ + using System; + using Microsoft.WinGet.SharedLib.Extensions; + using Microsoft.WinGet.SharedLib.PolicySettings; + + /// + /// Class that implements GroupPolicyException. + /// + [Serializable] + public class GroupPolicyException : Exception + { + /// + /// Initializes a new instance of the class. + /// + /// Policy. + /// GroupPolicyFailureType. public GroupPolicyException(Policy policy, GroupPolicyFailureType policyFailureType) : base(string.Format(policyFailureType.GetFailureString(), policy.GetResourceString())) { this.HResult = policyFailureType.GetErrorCode(); - } - - /// - /// Initializes a new instance of the class. - /// - /// GroupPolicyFailureType. - /// InnerException. + } + + /// + /// Initializes a new instance of the class. + /// + /// GroupPolicyFailureType. + /// InnerException. public GroupPolicyException(GroupPolicyFailureType policyFailureType, Exception innerException) - : base(policyFailureType.GetFailureString(), innerException) - { - this.HResult = policyFailureType.GetErrorCode(); - } - } -} + : base(policyFailureType.GetFailureString(), innerException) + { + this.HResult = policyFailureType.GetErrorCode(); + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Extensions/EnumPolicyExtension.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/Extensions/EnumPolicyExtension.cs index ef1887a420..1d4967e6a3 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/Extensions/EnumPolicyExtension.cs +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/Extensions/EnumPolicyExtension.cs @@ -1,97 +1,97 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.SharedLib.Extensions -{ - using Microsoft.WinGet.SharedLib.Exceptions; - using Microsoft.WinGet.SharedLib.PolicySettings; - using Microsoft.WinGet.SharedLib.Resources; - - /// - /// Implements extensions methods around Group Policy Enum type. - /// - public static class EnumPolicyExtension - { - /// - /// Gets ResourceString for the mapped Policy type. - /// - /// Policy. - /// Resource string. - public static string GetResourceString(this Policy policy) - { - switch (policy) - { - case Policy.WinGet: - return GroupPolicyResource.PolicyEnableWinGet; - case Policy.Settings: - return GroupPolicyResource.PolicyEnableWinGetSettings; - case Policy.ExperimentalFeatures: - return GroupPolicyResource.PolicyEnableExperimentalFeatures; - case Policy.LocalManifestFiles: - return GroupPolicyResource.PolicyEnableLocalManifests; - case Policy.HashOverride: - return GroupPolicyResource.PolicyEnableHashOverride; - case Policy.LocalArchiveMalwareScanOverride: - return GroupPolicyResource.PolicyEnableLocalArchiveMalwareScanOverride; - case Policy.DefaultSource: - return GroupPolicyResource.PolicyEnableDefaultSource; - case Policy.MSStoreSource: - return GroupPolicyResource.PolicyEnableMSStoreSource; - case Policy.FontSource: - return GroupPolicyResource.PolicyEnableFontSource; - case Policy.AdditionalSources: - return GroupPolicyResource.PolicyAdditionalSources; - case Policy.AllowedSources: - return GroupPolicyResource.PolicyAllowedSources; - case Policy.BypassCertificatePinningForMicrosoftStore: - return GroupPolicyResource.PolicyEnableBypassCertificatePinningForMicrosoftStore; - case Policy.WinGetCommandLineInterfaces: - return GroupPolicyResource.PolicyEnableWindowsPackageManagerCommandLineInterfaces; - case Policy.Configuration: - return GroupPolicyResource.PolicyEnableWinGetConfiguration; - default: - return string.Empty; - } - } - - /// - /// Gets failure string mapped to GroupPolicyFailureType. - /// - /// GroupPolicyFailureType. - /// Failure resources string. - public static string GetFailureString(this GroupPolicyFailureType policyFailure) - { - switch (policyFailure) - { - case GroupPolicyFailureType.BlockedByPolicy: - return GroupPolicyResource.ErrorBlockedByPolicy; - case GroupPolicyFailureType.NotFound: - return GroupPolicyResource.PolicyNotFound; - case GroupPolicyFailureType.LoadError: - return GroupPolicyResource.PolicyLoadError; - default: return string.Empty; - } - } - - /// - /// Gets ErrorCode mapped to GroupPolicyFailureType. - /// - /// GroupPolicyFailureType. - /// ErrorCode. - public static int GetErrorCode(this GroupPolicyFailureType policyFailure) - { - switch (policyFailure) - { - case GroupPolicyFailureType.BlockedByPolicy: - return ErrorCodes.AppInstallerCLIErrorBlockedByPolicy; - case GroupPolicyFailureType.NotFound: - case GroupPolicyFailureType.LoadError: - default: - return ErrorCodes.AppInstallerCLIErrorInternalError; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.SharedLib.Extensions +{ + using Microsoft.WinGet.SharedLib.Exceptions; + using Microsoft.WinGet.SharedLib.PolicySettings; + using Microsoft.WinGet.SharedLib.Resources; + + /// + /// Implements extensions methods around Group Policy Enum type. + /// + public static class EnumPolicyExtension + { + /// + /// Gets ResourceString for the mapped Policy type. + /// + /// Policy. + /// Resource string. + public static string GetResourceString(this Policy policy) + { + switch (policy) + { + case Policy.WinGet: + return GroupPolicyResource.PolicyEnableWinGet; + case Policy.Settings: + return GroupPolicyResource.PolicyEnableWinGetSettings; + case Policy.ExperimentalFeatures: + return GroupPolicyResource.PolicyEnableExperimentalFeatures; + case Policy.LocalManifestFiles: + return GroupPolicyResource.PolicyEnableLocalManifests; + case Policy.HashOverride: + return GroupPolicyResource.PolicyEnableHashOverride; + case Policy.LocalArchiveMalwareScanOverride: + return GroupPolicyResource.PolicyEnableLocalArchiveMalwareScanOverride; + case Policy.DefaultSource: + return GroupPolicyResource.PolicyEnableDefaultSource; + case Policy.MSStoreSource: + return GroupPolicyResource.PolicyEnableMSStoreSource; + case Policy.FontSource: + return GroupPolicyResource.PolicyEnableFontSource; + case Policy.AdditionalSources: + return GroupPolicyResource.PolicyAdditionalSources; + case Policy.AllowedSources: + return GroupPolicyResource.PolicyAllowedSources; + case Policy.BypassCertificatePinningForMicrosoftStore: + return GroupPolicyResource.PolicyEnableBypassCertificatePinningForMicrosoftStore; + case Policy.WinGetCommandLineInterfaces: + return GroupPolicyResource.PolicyEnableWindowsPackageManagerCommandLineInterfaces; + case Policy.Configuration: + return GroupPolicyResource.PolicyEnableWinGetConfiguration; + default: + return string.Empty; + } + } + + /// + /// Gets failure string mapped to GroupPolicyFailureType. + /// + /// GroupPolicyFailureType. + /// Failure resources string. + public static string GetFailureString(this GroupPolicyFailureType policyFailure) + { + switch (policyFailure) + { + case GroupPolicyFailureType.BlockedByPolicy: + return GroupPolicyResource.ErrorBlockedByPolicy; + case GroupPolicyFailureType.NotFound: + return GroupPolicyResource.PolicyNotFound; + case GroupPolicyFailureType.LoadError: + return GroupPolicyResource.PolicyLoadError; + default: return string.Empty; + } + } + + /// + /// Gets ErrorCode mapped to GroupPolicyFailureType. + /// + /// GroupPolicyFailureType. + /// ErrorCode. + public static int GetErrorCode(this GroupPolicyFailureType policyFailure) + { + switch (policyFailure) + { + case GroupPolicyFailureType.BlockedByPolicy: + return ErrorCodes.AppInstallerCLIErrorBlockedByPolicy; + case GroupPolicyFailureType.NotFound: + case GroupPolicyFailureType.LoadError: + default: + return ErrorCodes.AppInstallerCLIErrorInternalError; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Microsoft.WinGet.SharedLib.csproj b/src/PowerShell/Microsoft.WinGet.SharedLib/Microsoft.WinGet.SharedLib.csproj index 20b282b37f..33ef8d163f 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/Microsoft.WinGet.SharedLib.csproj +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/Microsoft.WinGet.SharedLib.csproj @@ -1,37 +1,37 @@ - - - - netstandard2.0 - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - $(OutputPath)\$(MSBuildProjectName).xml - Debug;Release;ReleaseStatic - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - True - True - GroupPolicyResource.resx - - - - - - ResXFileCodeGenerator - GroupPolicyResource.Designer.cs - - - - + + + + netstandard2.0 + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + $(OutputPath)\$(MSBuildProjectName).xml + Debug;Release;ReleaseStatic + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + True + True + GroupPolicyResource.resx + + + + + + ResXFileCodeGenerator + GroupPolicyResource.Designer.cs + + + + diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/Enums.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/Enums.cs index ff52b27144..2e1af63dea 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/Enums.cs +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/Enums.cs @@ -1,136 +1,136 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.SharedLib.PolicySettings -{ - /// - /// Group policy state. - /// - public enum PolicyState - { - /// - /// Not-Configured - /// - NotConfigured, - - /// - /// Disabled. - /// - Disabled, - - /// - /// Enabled. - /// - Enabled, - } - - /// - /// Supported Toggle policy types. - /// - public enum Policy - { - /// - /// EnableAppInstaller. - /// - WinGet, - - /// - /// Enable WinGet Settings. - /// - Settings, - - /// - /// Enable Experimental Features. - /// - ExperimentalFeatures, - - /// - /// Enable Local Manifest Files. - /// - LocalManifestFiles, - - /// - /// Enable Hash Override. - /// - HashOverride, - - /// - /// Enable Local Archive Malware scan override. - /// - LocalArchiveMalwareScanOverride, - - /// - /// Enable DefaultSource. - /// - DefaultSource, - - /// - /// Enable Microsoft Store Source. - /// - MSStoreSource, - - /// - /// Enable Font Source. - /// - FontSource, - - /// - /// Enable Additional Source. - /// - AdditionalSources, - - /// - /// Enabled Allowed Sources. - /// - AllowedSources, - - /// - /// Enable Bypass Certificate Pinning for Microsoft Store. - /// - BypassCertificatePinningForMicrosoftStore, - - /// - /// Enabled Command line Interfaces. - /// - WinGetCommandLineInterfaces, - - /// - /// Enabled configuration. - /// - Configuration, - - /// - /// Enabled MCP server. - /// - McpServer, - - /// - /// Enable Configuration Processor Path. - /// - ConfigurationProcessorPath, - } - - /// - /// Group policy failures kind. - /// - public enum GroupPolicyFailureType - { - /// - /// Blocked by Policy. - /// - BlockedByPolicy, - - /// - /// Policy not found or failed to read. - /// - NotFound, - - /// - /// Group policy load error. - /// - LoadError, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.SharedLib.PolicySettings +{ + /// + /// Group policy state. + /// + public enum PolicyState + { + /// + /// Not-Configured + /// + NotConfigured, + + /// + /// Disabled. + /// + Disabled, + + /// + /// Enabled. + /// + Enabled, + } + + /// + /// Supported Toggle policy types. + /// + public enum Policy + { + /// + /// EnableAppInstaller. + /// + WinGet, + + /// + /// Enable WinGet Settings. + /// + Settings, + + /// + /// Enable Experimental Features. + /// + ExperimentalFeatures, + + /// + /// Enable Local Manifest Files. + /// + LocalManifestFiles, + + /// + /// Enable Hash Override. + /// + HashOverride, + + /// + /// Enable Local Archive Malware scan override. + /// + LocalArchiveMalwareScanOverride, + + /// + /// Enable DefaultSource. + /// + DefaultSource, + + /// + /// Enable Microsoft Store Source. + /// + MSStoreSource, + + /// + /// Enable Font Source. + /// + FontSource, + + /// + /// Enable Additional Source. + /// + AdditionalSources, + + /// + /// Enabled Allowed Sources. + /// + AllowedSources, + + /// + /// Enable Bypass Certificate Pinning for Microsoft Store. + /// + BypassCertificatePinningForMicrosoftStore, + + /// + /// Enabled Command line Interfaces. + /// + WinGetCommandLineInterfaces, + + /// + /// Enabled configuration. + /// + Configuration, + + /// + /// Enabled MCP server. + /// + McpServer, + + /// + /// Enable Configuration Processor Path. + /// + ConfigurationProcessorPath, + } + + /// + /// Group policy failures kind. + /// + public enum GroupPolicyFailureType + { + /// + /// Blocked by Policy. + /// + BlockedByPolicy, + + /// + /// Policy not found or failed to read. + /// + NotFound, + + /// + /// Group policy load error. + /// + LoadError, + } +} diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/GroupPolicy.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/GroupPolicy.cs index 5219db8946..bc66f61dea 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/GroupPolicy.cs +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/GroupPolicy.cs @@ -1,124 +1,124 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGet.SharedLib.PolicySettings -{ - using System; - using System.Collections.Generic; - - using Microsoft.Win32; - using Microsoft.WinGet.SharedLib.Exceptions; - - /// - /// Helper class to read Group Policies backed by registry store. - /// - public class GroupPolicy - { - private const string AppInstallerPolicyRegistryPath = "SOFTWARE\\Policies\\Microsoft\\Windows\\AppInstaller"; - - private Dictionary togglePolicyMap; - +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGet.SharedLib.PolicySettings +{ + using System; + using System.Collections.Generic; + + using Microsoft.Win32; + using Microsoft.WinGet.SharedLib.Exceptions; + + /// + /// Helper class to read Group Policies backed by registry store. + /// + public class GroupPolicy + { + private const string AppInstallerPolicyRegistryPath = "SOFTWARE\\Policies\\Microsoft\\Windows\\AppInstaller"; + + private Dictionary togglePolicyMap; + private GroupPolicy() - { - this.togglePolicyMap = new Dictionary(); - } - - /// - /// Gets an instance of GroupPolicy. - /// - /// An instance of type class. - public static GroupPolicy GetInstance() - { - GroupPolicy groupPolicy = new GroupPolicy(); - - try - { - List togglePolicies = TogglePolicy.GetAllPolicies(); - groupPolicy.Load(togglePolicies); - - return groupPolicy; - } - catch (Exception ex) - { - throw new GroupPolicyException(GroupPolicyFailureType.LoadError, ex); - } - } - - /// - /// Gets the current of status of Policy. - /// - /// Policy. - /// Policy configuration status. - public PolicyState GetPolicyState(Policy policy) - { - if (!this.togglePolicyMap.ContainsKey(policy)) - { - throw new GroupPolicyException(policy, GroupPolicyFailureType.NotFound); - } - - return this.togglePolicyMap[policy].State; - } - - /// - /// Return status of is Policy enabled. - /// - /// Policy. - /// Boolean status indicates is Policy enabled. - public bool IsEnabled(Policy policy) - { - if (!this.togglePolicyMap.ContainsKey(policy)) - { - throw new GroupPolicyException(policy, GroupPolicyFailureType.NotFound); - } - - TogglePolicy togglePolicy = this.togglePolicyMap[policy]; - - if (togglePolicy.State == PolicyState.Enabled - || (togglePolicy.State == PolicyState.NotConfigured && togglePolicy.EnabledByDefault)) - { - return true; - } - - return false; - } - - /// - /// Loads the collection of TogglePolicy. - /// - /// Enumerable collection of TogglePolicy. - internal void Load(IEnumerable policies) - { - using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(AppInstallerPolicyRegistryPath)) - { - foreach (TogglePolicy togglePolicy in policies) - { - // It is likely expected if none of the Windows Package Manager policies are configured i.e Not Configured. - if (regKey != null) - { - var policyValue = regKey.GetValue(togglePolicy.RegistryValueName); - - RegistryValueKind valueKind = RegistryValueKind.None; - - if (policyValue != null) - { - valueKind = regKey.GetValueKind(togglePolicy.RegistryValueName); - } - -#pragma warning disable CS8604 // Possible null reference argument. - togglePolicy.SetValue(policyValue, valueKind); -#pragma warning restore CS8604 // Possible null reference argument. - } - - if (!this.togglePolicyMap.ContainsKey(togglePolicy.PolicyType) - || (this.togglePolicyMap.ContainsKey(togglePolicy.PolicyType) && this.togglePolicyMap[togglePolicy.PolicyType] == null)) - { - this.togglePolicyMap.Add(togglePolicy.PolicyType, togglePolicy); - } - } - } - } - } -} + { + this.togglePolicyMap = new Dictionary(); + } + + /// + /// Gets an instance of GroupPolicy. + /// + /// An instance of type class. + public static GroupPolicy GetInstance() + { + GroupPolicy groupPolicy = new GroupPolicy(); + + try + { + List togglePolicies = TogglePolicy.GetAllPolicies(); + groupPolicy.Load(togglePolicies); + + return groupPolicy; + } + catch (Exception ex) + { + throw new GroupPolicyException(GroupPolicyFailureType.LoadError, ex); + } + } + + /// + /// Gets the current of status of Policy. + /// + /// Policy. + /// Policy configuration status. + public PolicyState GetPolicyState(Policy policy) + { + if (!this.togglePolicyMap.ContainsKey(policy)) + { + throw new GroupPolicyException(policy, GroupPolicyFailureType.NotFound); + } + + return this.togglePolicyMap[policy].State; + } + + /// + /// Return status of is Policy enabled. + /// + /// Policy. + /// Boolean status indicates is Policy enabled. + public bool IsEnabled(Policy policy) + { + if (!this.togglePolicyMap.ContainsKey(policy)) + { + throw new GroupPolicyException(policy, GroupPolicyFailureType.NotFound); + } + + TogglePolicy togglePolicy = this.togglePolicyMap[policy]; + + if (togglePolicy.State == PolicyState.Enabled + || (togglePolicy.State == PolicyState.NotConfigured && togglePolicy.EnabledByDefault)) + { + return true; + } + + return false; + } + + /// + /// Loads the collection of TogglePolicy. + /// + /// Enumerable collection of TogglePolicy. + internal void Load(IEnumerable policies) + { + using (RegistryKey regKey = Registry.LocalMachine.OpenSubKey(AppInstallerPolicyRegistryPath)) + { + foreach (TogglePolicy togglePolicy in policies) + { + // It is likely expected if none of the Windows Package Manager policies are configured i.e Not Configured. + if (regKey != null) + { + var policyValue = regKey.GetValue(togglePolicy.RegistryValueName); + + RegistryValueKind valueKind = RegistryValueKind.None; + + if (policyValue != null) + { + valueKind = regKey.GetValueKind(togglePolicy.RegistryValueName); + } + +#pragma warning disable CS8604 // Possible null reference argument. + togglePolicy.SetValue(policyValue, valueKind); +#pragma warning restore CS8604 // Possible null reference argument. + } + + if (!this.togglePolicyMap.ContainsKey(togglePolicy.PolicyType) + || (this.togglePolicyMap.ContainsKey(togglePolicy.PolicyType) && this.togglePolicyMap[togglePolicy.PolicyType] == null)) + { + this.togglePolicyMap.Add(togglePolicy.PolicyType, togglePolicy); + } + } + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/TogglePolicy.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/TogglePolicy.cs index d3c474aef8..f88e2a597c 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/TogglePolicy.cs +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/PolicySettings/TogglePolicy.cs @@ -1,37 +1,37 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- -namespace Microsoft.WinGet.SharedLib.PolicySettings -{ - using System; - using System.Collections.Generic; - - using Microsoft.Win32; - using Microsoft.WinGet.SharedLib.Resources; - - /// - /// A policyType that acts as a toggle to enable or disable a feature. - /// They are backed by a DWORD policyValue with values 0 and 1. - /// - public class TogglePolicy - { - private readonly Policy policyType; - private readonly bool isEnabledByDefault; - private readonly string registryValueName; - private readonly string resourceString; - - private PolicyState state; - private int? value; - - /// - /// Initializes a new instance of the class. - /// - /// PolicyType. - /// RegistryValue Name. - /// PolicyType ResourceString. - /// Is EnabledByDefault. +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- +namespace Microsoft.WinGet.SharedLib.PolicySettings +{ + using System; + using System.Collections.Generic; + + using Microsoft.Win32; + using Microsoft.WinGet.SharedLib.Resources; + + /// + /// A policyType that acts as a toggle to enable or disable a feature. + /// They are backed by a DWORD policyValue with values 0 and 1. + /// + public class TogglePolicy + { + private readonly Policy policyType; + private readonly bool isEnabledByDefault; + private readonly string registryValueName; + private readonly string resourceString; + + private PolicyState state; + private int? value; + + /// + /// Initializes a new instance of the class. + /// + /// PolicyType. + /// RegistryValue Name. + /// PolicyType ResourceString. + /// Is EnabledByDefault. internal TogglePolicy(Policy policy, string registryValueName, string policyResourceString, bool isEnabledByDefault = true) { this.policyType = policy; @@ -39,145 +39,145 @@ internal TogglePolicy(Policy policy, string registryValueName, string policyReso this.resourceString = policyResourceString; this.isEnabledByDefault = isEnabledByDefault; this.state = PolicyState.NotConfigured; - } - - /// - /// Gets PolicyType. - /// - public Policy PolicyType - { - get { return this.policyType; } - } - - /// - /// Gets Resource string associated with policy. - /// - public string ResourceString - { - get { return this.resourceString; } - } - - /// - /// Gets Policy State. - /// - public PolicyState State - { - get { return this.state; } - } - - /// - /// Gets a value indicating whether gets Is policy EnabledByDefault. - /// - internal bool EnabledByDefault - { - get { return this.isEnabledByDefault; } - } - - /// - /// Gets Policy RegistryValue Name. - /// - internal string RegistryValueName - { - get { return this.registryValueName; } - } - - /// - /// Gets value of the policy. - /// - internal int? Value - { - get { return this.value; } - } - - /// - /// Creates an instance of TogglePolicy class. - /// - /// Policy. - /// An instance of class. - /// Argument exception is return if not matching Policy type passed. - internal static TogglePolicy Create(Policy policy) - { - switch (policy) - { - case Policy.WinGet: - return new TogglePolicy(policy, "EnableAppInstaller", GroupPolicyResource.PolicyEnableWinGet); - case Policy.Settings: - return new TogglePolicy(policy, "EnableSettings", GroupPolicyResource.PolicyEnableWinGetSettings); - case Policy.ExperimentalFeatures: - return new TogglePolicy(policy, "EnableExperimentalFeatures", GroupPolicyResource.PolicyEnableExperimentalFeatures); - case Policy.LocalManifestFiles: - return new TogglePolicy(policy, "EnableLocalManifestFiles", GroupPolicyResource.PolicyEnableLocalManifests); - case Policy.HashOverride: - return new TogglePolicy(policy, "EnableHashOverride", GroupPolicyResource.PolicyEnableHashOverride); - case Policy.LocalArchiveMalwareScanOverride: - return new TogglePolicy(policy, "EnableLocalArchiveMalwareScanOverride", GroupPolicyResource.PolicyEnableLocalArchiveMalwareScanOverride); - case Policy.DefaultSource: - return new TogglePolicy(policy, "EnableDefaultSource", GroupPolicyResource.PolicyEnableDefaultSource); - case Policy.MSStoreSource: - return new TogglePolicy(policy, "EnableMicrosoftStoreSource", GroupPolicyResource.PolicyEnableMSStoreSource); - case Policy.FontSource: - return new TogglePolicy(policy, "EnableFontSource", GroupPolicyResource.PolicyEnableFontSource); - case Policy.AdditionalSources: - return new TogglePolicy(policy, "EnableAdditionalSources", GroupPolicyResource.PolicyAdditionalSources); - case Policy.AllowedSources: - return new TogglePolicy(policy, "EnableAllowedSources", GroupPolicyResource.PolicyAllowedSources); - case Policy.BypassCertificatePinningForMicrosoftStore: - return new TogglePolicy(policy, "EnableBypassCertificatePinningForMicrosoftStore", GroupPolicyResource.PolicyEnableBypassCertificatePinningForMicrosoftStore); - case Policy.WinGetCommandLineInterfaces: - return new TogglePolicy(policy, "EnableWindowsPackageManagerCommandLineInterfaces", GroupPolicyResource.PolicyEnableWindowsPackageManagerCommandLineInterfaces); - case Policy.Configuration: - return new TogglePolicy(policy, "EnableWindowsPackageManagerConfiguration", GroupPolicyResource.PolicyEnableWinGetConfiguration); - case Policy.McpServer: - return new TogglePolicy(policy, "EnableWindowsPackageManagerMcpServer", GroupPolicyResource.PolicyEnableMcpServer); - case Policy.ConfigurationProcessorPath: - return new TogglePolicy(policy, "EnableWindowsPackageManagerConfigurationProcessorPath", GroupPolicyResource.PolicyEnableConfigurationProcessorPath); - default: - throw new ArgumentException(null, nameof(policy)); - } - } - - /// - /// Creates a list of TogglePolicy instances. - /// - /// List of type class. - internal static List GetAllPolicies() - { - List togglePolicies = new List(); - - foreach (Policy policy in Enum.GetValues(typeof(Policy))) - { - togglePolicies.Add(Create(policy)); - } - - return togglePolicies; - } - - /// - /// Sets value and state of the policy. - /// - /// PolicyValue object. - /// RegistryValueKind. - internal void SetValue(object policyValue, RegistryValueKind registryValueKind) - { - if (registryValueKind != RegistryValueKind.DWord - || policyValue == null) - { - this.state = PolicyState.NotConfigured; - this.value = null; - - return; - } - - this.value = (int)policyValue; - - if (this.value != 0) - { - this.state = PolicyState.Enabled; - } - else - { - this.state = PolicyState.Disabled; - } - } - } -} + } + + /// + /// Gets PolicyType. + /// + public Policy PolicyType + { + get { return this.policyType; } + } + + /// + /// Gets Resource string associated with policy. + /// + public string ResourceString + { + get { return this.resourceString; } + } + + /// + /// Gets Policy State. + /// + public PolicyState State + { + get { return this.state; } + } + + /// + /// Gets a value indicating whether gets Is policy EnabledByDefault. + /// + internal bool EnabledByDefault + { + get { return this.isEnabledByDefault; } + } + + /// + /// Gets Policy RegistryValue Name. + /// + internal string RegistryValueName + { + get { return this.registryValueName; } + } + + /// + /// Gets value of the policy. + /// + internal int? Value + { + get { return this.value; } + } + + /// + /// Creates an instance of TogglePolicy class. + /// + /// Policy. + /// An instance of class. + /// Argument exception is return if not matching Policy type passed. + internal static TogglePolicy Create(Policy policy) + { + switch (policy) + { + case Policy.WinGet: + return new TogglePolicy(policy, "EnableAppInstaller", GroupPolicyResource.PolicyEnableWinGet); + case Policy.Settings: + return new TogglePolicy(policy, "EnableSettings", GroupPolicyResource.PolicyEnableWinGetSettings); + case Policy.ExperimentalFeatures: + return new TogglePolicy(policy, "EnableExperimentalFeatures", GroupPolicyResource.PolicyEnableExperimentalFeatures); + case Policy.LocalManifestFiles: + return new TogglePolicy(policy, "EnableLocalManifestFiles", GroupPolicyResource.PolicyEnableLocalManifests); + case Policy.HashOverride: + return new TogglePolicy(policy, "EnableHashOverride", GroupPolicyResource.PolicyEnableHashOverride); + case Policy.LocalArchiveMalwareScanOverride: + return new TogglePolicy(policy, "EnableLocalArchiveMalwareScanOverride", GroupPolicyResource.PolicyEnableLocalArchiveMalwareScanOverride); + case Policy.DefaultSource: + return new TogglePolicy(policy, "EnableDefaultSource", GroupPolicyResource.PolicyEnableDefaultSource); + case Policy.MSStoreSource: + return new TogglePolicy(policy, "EnableMicrosoftStoreSource", GroupPolicyResource.PolicyEnableMSStoreSource); + case Policy.FontSource: + return new TogglePolicy(policy, "EnableFontSource", GroupPolicyResource.PolicyEnableFontSource); + case Policy.AdditionalSources: + return new TogglePolicy(policy, "EnableAdditionalSources", GroupPolicyResource.PolicyAdditionalSources); + case Policy.AllowedSources: + return new TogglePolicy(policy, "EnableAllowedSources", GroupPolicyResource.PolicyAllowedSources); + case Policy.BypassCertificatePinningForMicrosoftStore: + return new TogglePolicy(policy, "EnableBypassCertificatePinningForMicrosoftStore", GroupPolicyResource.PolicyEnableBypassCertificatePinningForMicrosoftStore); + case Policy.WinGetCommandLineInterfaces: + return new TogglePolicy(policy, "EnableWindowsPackageManagerCommandLineInterfaces", GroupPolicyResource.PolicyEnableWindowsPackageManagerCommandLineInterfaces); + case Policy.Configuration: + return new TogglePolicy(policy, "EnableWindowsPackageManagerConfiguration", GroupPolicyResource.PolicyEnableWinGetConfiguration); + case Policy.McpServer: + return new TogglePolicy(policy, "EnableWindowsPackageManagerMcpServer", GroupPolicyResource.PolicyEnableMcpServer); + case Policy.ConfigurationProcessorPath: + return new TogglePolicy(policy, "EnableWindowsPackageManagerConfigurationProcessorPath", GroupPolicyResource.PolicyEnableConfigurationProcessorPath); + default: + throw new ArgumentException(null, nameof(policy)); + } + } + + /// + /// Creates a list of TogglePolicy instances. + /// + /// List of type class. + internal static List GetAllPolicies() + { + List togglePolicies = new List(); + + foreach (Policy policy in Enum.GetValues(typeof(Policy))) + { + togglePolicies.Add(Create(policy)); + } + + return togglePolicies; + } + + /// + /// Sets value and state of the policy. + /// + /// PolicyValue object. + /// RegistryValueKind. + internal void SetValue(object policyValue, RegistryValueKind registryValueKind) + { + if (registryValueKind != RegistryValueKind.DWord + || policyValue == null) + { + this.state = PolicyState.NotConfigured; + this.value = null; + + return; + } + + this.value = (int)policyValue; + + if (this.value != 0) + { + this.state = PolicyState.Enabled; + } + else + { + this.state = PolicyState.Disabled; + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.Designer.cs b/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.Designer.cs index e1fc9fb680..1c583dca50 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.Designer.cs +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.Designer.cs @@ -1,243 +1,243 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.WinGet.SharedLib.Resources { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class GroupPolicyResource { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal GroupPolicyResource() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.SharedLib.Resources.GroupPolicyResource", typeof(GroupPolicyResource).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to This operation is disabled by Group Policy : {0}. - /// - internal static string ErrorBlockedByPolicy { - get { - return ResourceManager.GetString("ErrorBlockedByPolicy", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Additional Windows App Installer Sources. - /// - internal static string PolicyAdditionalSources { - get { - return ResourceManager.GetString("PolicyAdditionalSources", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Allowed Sources. - /// - internal static string PolicyAllowedSources { - get { - return ResourceManager.GetString("PolicyAllowedSources", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass. - /// - internal static string PolicyEnableBypassCertificatePinningForMicrosoftStore { - get { - return ResourceManager.GetString("PolicyEnableBypassCertificatePinningForMicrosoftStore", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows Package Manager Configuration processor path. - /// - internal static string PolicyEnableConfigurationProcessorPath { - get { - return ResourceManager.GetString("PolicyEnableConfigurationProcessorPath", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Default Source. - /// - internal static string PolicyEnableDefaultSource { - get { - return ResourceManager.GetString("PolicyEnableDefaultSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Experimental Features. - /// - internal static string PolicyEnableExperimentalFeatures { - get { - return ResourceManager.GetString("PolicyEnableExperimentalFeatures", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Font Source. - /// - internal static string PolicyEnableFontSource { - get { - return ResourceManager.GetString("PolicyEnableFontSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Hash Override. - /// - internal static string PolicyEnableHashOverride { - get { - return ResourceManager.GetString("PolicyEnableHashOverride", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Local Archive Malware Scan Override. - /// - internal static string PolicyEnableLocalArchiveMalwareScanOverride { - get { - return ResourceManager.GetString("PolicyEnableLocalArchiveMalwareScanOverride", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Local Manifest Files. - /// - internal static string PolicyEnableLocalManifests { - get { - return ResourceManager.GetString("PolicyEnableLocalManifests", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows Package Manager MCP Server. - /// - internal static string PolicyEnableMcpServer { - get { - return ResourceManager.GetString("PolicyEnableMcpServer", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows App Installer Microsoft Store Source. - /// - internal static string PolicyEnableMSStoreSource { - get { - return ResourceManager.GetString("PolicyEnableMSStoreSource", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows Package Manager proxy command line options. - /// - internal static string PolicyEnableProxyCommandLineOptions { - get { - return ResourceManager.GetString("PolicyEnableProxyCommandLineOptions", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows Package Manager command line interfaces. - /// - internal static string PolicyEnableWindowsPackageManagerCommandLineInterfaces { - get { - return ResourceManager.GetString("PolicyEnableWindowsPackageManagerCommandLineInterfaces", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows Package Manager. - /// - internal static string PolicyEnableWinGet { - get { - return ResourceManager.GetString("PolicyEnableWinGet", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows Package Manager Configuration. - /// - internal static string PolicyEnableWinGetConfiguration { - get { - return ResourceManager.GetString("PolicyEnableWinGetConfiguration", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Enable Windows Package Manager Settings. - /// - internal static string PolicyEnableWinGetSettings { - get { - return ResourceManager.GetString("PolicyEnableWinGetSettings", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error occurred when loading Windows Package Manager Group Policy Settings. - /// - internal static string PolicyLoadError { - get { - return ResourceManager.GetString("PolicyLoadError", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Error occurred when retrieving policy settings for the Group Policy : {0}. - /// - internal static string PolicyNotFound { - get { - return ResourceManager.GetString("PolicyNotFound", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.WinGet.SharedLib.Resources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class GroupPolicyResource { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal GroupPolicyResource() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.WinGet.SharedLib.Resources.GroupPolicyResource", typeof(GroupPolicyResource).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to This operation is disabled by Group Policy : {0}. + /// + internal static string ErrorBlockedByPolicy { + get { + return ResourceManager.GetString("ErrorBlockedByPolicy", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Additional Windows App Installer Sources. + /// + internal static string PolicyAdditionalSources { + get { + return ResourceManager.GetString("PolicyAdditionalSources", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Allowed Sources. + /// + internal static string PolicyAllowedSources { + get { + return ResourceManager.GetString("PolicyAllowedSources", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass. + /// + internal static string PolicyEnableBypassCertificatePinningForMicrosoftStore { + get { + return ResourceManager.GetString("PolicyEnableBypassCertificatePinningForMicrosoftStore", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows Package Manager Configuration processor path. + /// + internal static string PolicyEnableConfigurationProcessorPath { + get { + return ResourceManager.GetString("PolicyEnableConfigurationProcessorPath", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Default Source. + /// + internal static string PolicyEnableDefaultSource { + get { + return ResourceManager.GetString("PolicyEnableDefaultSource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Experimental Features. + /// + internal static string PolicyEnableExperimentalFeatures { + get { + return ResourceManager.GetString("PolicyEnableExperimentalFeatures", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Font Source. + /// + internal static string PolicyEnableFontSource { + get { + return ResourceManager.GetString("PolicyEnableFontSource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Hash Override. + /// + internal static string PolicyEnableHashOverride { + get { + return ResourceManager.GetString("PolicyEnableHashOverride", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Local Archive Malware Scan Override. + /// + internal static string PolicyEnableLocalArchiveMalwareScanOverride { + get { + return ResourceManager.GetString("PolicyEnableLocalArchiveMalwareScanOverride", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Local Manifest Files. + /// + internal static string PolicyEnableLocalManifests { + get { + return ResourceManager.GetString("PolicyEnableLocalManifests", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows Package Manager MCP Server. + /// + internal static string PolicyEnableMcpServer { + get { + return ResourceManager.GetString("PolicyEnableMcpServer", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows App Installer Microsoft Store Source. + /// + internal static string PolicyEnableMSStoreSource { + get { + return ResourceManager.GetString("PolicyEnableMSStoreSource", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows Package Manager proxy command line options. + /// + internal static string PolicyEnableProxyCommandLineOptions { + get { + return ResourceManager.GetString("PolicyEnableProxyCommandLineOptions", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows Package Manager command line interfaces. + /// + internal static string PolicyEnableWindowsPackageManagerCommandLineInterfaces { + get { + return ResourceManager.GetString("PolicyEnableWindowsPackageManagerCommandLineInterfaces", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows Package Manager. + /// + internal static string PolicyEnableWinGet { + get { + return ResourceManager.GetString("PolicyEnableWinGet", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows Package Manager Configuration. + /// + internal static string PolicyEnableWinGetConfiguration { + get { + return ResourceManager.GetString("PolicyEnableWinGetConfiguration", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Enable Windows Package Manager Settings. + /// + internal static string PolicyEnableWinGetSettings { + get { + return ResourceManager.GetString("PolicyEnableWinGetSettings", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error occurred when loading Windows Package Manager Group Policy Settings. + /// + internal static string PolicyLoadError { + get { + return ResourceManager.GetString("PolicyLoadError", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error occurred when retrieving policy settings for the Group Policy : {0}. + /// + internal static string PolicyNotFound { + get { + return ResourceManager.GetString("PolicyNotFound", resourceCulture); + } + } + } +} diff --git a/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.resx b/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.resx index 2600d36298..da72828454 100644 --- a/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.resx +++ b/src/PowerShell/Microsoft.WinGet.SharedLib/Resources/GroupPolicyResource.resx @@ -1,180 +1,180 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - This operation is disabled by Group Policy : {0} - - - Enable Additional Windows App Installer Sources - - - Enable Windows App Installer Allowed Sources - - - Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass - - - Enable Windows App Installer Default Source - - - Enable Windows App Installer Experimental Features - - - Enable Windows App Installer Hash Override - - - Enable Windows App Installer Local Archive Malware Scan Override - - - Enable Windows App Installer Local Manifest Files - - - Enable Windows Package Manager MCP Server - - - Enable Windows App Installer Microsoft Store Source - - - Enable Windows App Installer Font Source - - - Enable Windows Package Manager proxy command line options - - - Enable Windows Package Manager Configuration processor path - - - Enable Windows Package Manager command line interfaces - - - Enable Windows Package Manager - - - Enable Windows Package Manager Configuration - - - Enable Windows Package Manager Settings - - - Error occurred when loading Windows Package Manager Group Policy Settings - - - Error occurred when retrieving policy settings for the Group Policy : {0} - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + This operation is disabled by Group Policy : {0} + + + Enable Additional Windows App Installer Sources + + + Enable Windows App Installer Allowed Sources + + + Enable Windows App Installer Microsoft Store Source Pinned Certificate Bypass + + + Enable Windows App Installer Default Source + + + Enable Windows App Installer Experimental Features + + + Enable Windows App Installer Hash Override + + + Enable Windows App Installer Local Archive Malware Scan Override + + + Enable Windows App Installer Local Manifest Files + + + Enable Windows Package Manager MCP Server + + + Enable Windows App Installer Microsoft Store Source + + + Enable Windows App Installer Font Source + + + Enable Windows Package Manager proxy command line options + + + Enable Windows Package Manager Configuration processor path + + + Enable Windows Package Manager command line interfaces + + + Enable Windows Package Manager + + + Enable Windows Package Manager Configuration + + + Enable Windows Package Manager Settings + + + Error occurred when loading Windows Package Manager Group Policy Settings + + + Error occurred when retrieving policy settings for the Group Policy : {0} + + diff --git a/src/PowerShell/scripts/Execute-WinGetTests.ps1 b/src/PowerShell/scripts/Execute-WinGetTests.ps1 index d2f5d58505..d01afe602f 100644 --- a/src/PowerShell/scripts/Execute-WinGetTests.ps1 +++ b/src/PowerShell/scripts/Execute-WinGetTests.ps1 @@ -1,128 +1,128 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[CmdletBinding()] -param( - # The version of the client PS module to use. - [string]$ClientModuleVersion, - - # The version of the configuration PS module to use. - [string]$ConfigurationModuleVersion, - - # The version of the client to use. Use 'existing' to skip updating. - [string]$ClientVersion, - - # The version of Powershell to use. Use 'existing' to skip updating. - [string]$PwshVersion, - - # The path to the binaries to run the web server. - [string]$LocalhostWebServerPath = (Join-Path $PSScriptRoot "..\LocalhostWebServer"), - - # The path to the files to be hosted by the web server. - [string]$HostedFilePath = (Join-Path $PSScriptRoot "..\TestLocalIndex"), - - # The path to the certificate that signed the source.msix package. - [string]$SourceCertPath = (Join-Path $PSScriptRoot "..\TestData\AppInstallerTest.cer"), - - # The path to the configuration test data. - [string]$ConfigurationTestDataPath = (Join-Path $PSScriptRoot "..\TestData\Configuration"), - - # The path to pwsh.exe - [string]$PwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe', - - # Switches to prevent installing various runtimes required for the tests - [switch]$SkipVCRuntime, - [switch]$SkipDotNetRuntime, - [switch]$SkipAspNetRuntime, - - # The path to write results to. - [string]$ResultsPath = (Join-Path ([System.IO.Path]::GetTempPath()) (New-Guid)) -) - -# Ensure we can connect to PS Gallery -Install-PackageProvider -Name NuGet -Force | Out-Null - -# Get the client module -if ([System.String]::IsNullOrEmpty($ClientModuleVersion)) -{ - Install-Module Microsoft.WinGet.Client -Force -} -else -{ - Install-Module Microsoft.WinGet.Client -RequiredVersion $ClientModuleVersion -Force -} - -# Get the client -if ([System.String]::IsNullOrEmpty($ClientVersion)) -{ - Repair-WingetPackageManager -Latest -} -elseif ($ClientVersion -eq "existing") -{ - # Use version already present -} -else -{ - Repair-WingetPackageManager -Version $ClientVersion -} - -# Get pwsh -if ([System.String]::IsNullOrEmpty($PwshVersion)) -{ - winget install Microsoft.PowerShell -s winget -} -elseif ($PwshVersion -eq "existing") -{ - # Use version already present -} -else -{ - winget install Microsoft.PowerShell -s winget -v $PwshVersion -} - -# Get VC Runtime -if (-not $SkipVCRuntime) -{ - winget install 'Microsoft.VCRedist.2015+.x64' -s winget -} - -# Install .NET 6 for the local web server -if (-not $SkipDotNetRuntime) -{ - winget install Microsoft.DotNet.Runtime.6 -s winget -} - -if (-not $SkipAspNetRuntime) -{ - winget install Microsoft.DotNet.AspNetCore.6 -s winget -} - -# Generate a new TLS certificate and trust it -$TLSCertificate = New-SelfSignedCertificate -CertStoreLocation "Cert:\LocalMachine\My" -DnsName "localhost" -$CertTempPath = Join-Path $env:TEMP New-Guid -Export-Certificate -Cert $TLSCertificate -FilePath $CertTempPath -Import-Certificate -FilePath $CertTempPath -CertStoreLocation "Cert:\LocalMachine\Root" - -# Start local host web server -.\Run-LocalhostWebServer.ps1 -BuildRoot $LocalhostWebServerPath -StaticFileRoot $HostedFilePath -CertPath $TLSCertificate.PSPath -SourceCert $SourceCertPath - -# Get the configuration module using pwsh since AllowPrerelease doesn't working in Windows PowerShell baseline -if ([System.String]::IsNullOrEmpty($ConfigurationModuleVersion)) -{ - & $PwshPath -Command "Install-Module Microsoft.WinGet.Configuration -Force -AllowPrerelease" -} -else -{ - & $PwshPath -Command "Install-Module Microsoft.WinGet.Configuration -RequiredVersion $ConfigurationModuleVersion -Force -AllowPrerelease" -} - -# Create local PS repo -$InitRepositoryPath = Join-Path $ConfigurationTestDataPath Init-TestRepository.ps1 -& $PwshPath -ExecutionPolicy Unrestricted -Command "$InitRepositoryPath -Force" - -# Run tests -& $PwshPath -ExecutionPolicy Unrestricted -Command ".\RunTests.ps1 -TargetProduction -ConfigurationTestDataPath $ConfigurationTestDataPath -outputPath $ResultsPath" - -# Terminate the local web server -Get-Process LocalhostWebServer -ErrorAction Ignore | Stop-Process -ErrorAction Ignore - -Write-Host "Results: $ResultsPath" +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + # The version of the client PS module to use. + [string]$ClientModuleVersion, + + # The version of the configuration PS module to use. + [string]$ConfigurationModuleVersion, + + # The version of the client to use. Use 'existing' to skip updating. + [string]$ClientVersion, + + # The version of Powershell to use. Use 'existing' to skip updating. + [string]$PwshVersion, + + # The path to the binaries to run the web server. + [string]$LocalhostWebServerPath = (Join-Path $PSScriptRoot "..\LocalhostWebServer"), + + # The path to the files to be hosted by the web server. + [string]$HostedFilePath = (Join-Path $PSScriptRoot "..\TestLocalIndex"), + + # The path to the certificate that signed the source.msix package. + [string]$SourceCertPath = (Join-Path $PSScriptRoot "..\TestData\AppInstallerTest.cer"), + + # The path to the configuration test data. + [string]$ConfigurationTestDataPath = (Join-Path $PSScriptRoot "..\TestData\Configuration"), + + # The path to pwsh.exe + [string]$PwshPath = 'C:\Program Files\PowerShell\7\pwsh.exe', + + # Switches to prevent installing various runtimes required for the tests + [switch]$SkipVCRuntime, + [switch]$SkipDotNetRuntime, + [switch]$SkipAspNetRuntime, + + # The path to write results to. + [string]$ResultsPath = (Join-Path ([System.IO.Path]::GetTempPath()) (New-Guid)) +) + +# Ensure we can connect to PS Gallery +Install-PackageProvider -Name NuGet -Force | Out-Null + +# Get the client module +if ([System.String]::IsNullOrEmpty($ClientModuleVersion)) +{ + Install-Module Microsoft.WinGet.Client -Force +} +else +{ + Install-Module Microsoft.WinGet.Client -RequiredVersion $ClientModuleVersion -Force +} + +# Get the client +if ([System.String]::IsNullOrEmpty($ClientVersion)) +{ + Repair-WingetPackageManager -Latest +} +elseif ($ClientVersion -eq "existing") +{ + # Use version already present +} +else +{ + Repair-WingetPackageManager -Version $ClientVersion +} + +# Get pwsh +if ([System.String]::IsNullOrEmpty($PwshVersion)) +{ + winget install Microsoft.PowerShell -s winget +} +elseif ($PwshVersion -eq "existing") +{ + # Use version already present +} +else +{ + winget install Microsoft.PowerShell -s winget -v $PwshVersion +} + +# Get VC Runtime +if (-not $SkipVCRuntime) +{ + winget install 'Microsoft.VCRedist.2015+.x64' -s winget +} + +# Install .NET 6 for the local web server +if (-not $SkipDotNetRuntime) +{ + winget install Microsoft.DotNet.Runtime.6 -s winget +} + +if (-not $SkipAspNetRuntime) +{ + winget install Microsoft.DotNet.AspNetCore.6 -s winget +} + +# Generate a new TLS certificate and trust it +$TLSCertificate = New-SelfSignedCertificate -CertStoreLocation "Cert:\LocalMachine\My" -DnsName "localhost" +$CertTempPath = Join-Path $env:TEMP New-Guid +Export-Certificate -Cert $TLSCertificate -FilePath $CertTempPath +Import-Certificate -FilePath $CertTempPath -CertStoreLocation "Cert:\LocalMachine\Root" + +# Start local host web server +.\Run-LocalhostWebServer.ps1 -BuildRoot $LocalhostWebServerPath -StaticFileRoot $HostedFilePath -CertPath $TLSCertificate.PSPath -SourceCert $SourceCertPath + +# Get the configuration module using pwsh since AllowPrerelease doesn't working in Windows PowerShell baseline +if ([System.String]::IsNullOrEmpty($ConfigurationModuleVersion)) +{ + & $PwshPath -Command "Install-Module Microsoft.WinGet.Configuration -Force -AllowPrerelease" +} +else +{ + & $PwshPath -Command "Install-Module Microsoft.WinGet.Configuration -RequiredVersion $ConfigurationModuleVersion -Force -AllowPrerelease" +} + +# Create local PS repo +$InitRepositoryPath = Join-Path $ConfigurationTestDataPath Init-TestRepository.ps1 +& $PwshPath -ExecutionPolicy Unrestricted -Command "$InitRepositoryPath -Force" + +# Run tests +& $PwshPath -ExecutionPolicy Unrestricted -Command ".\RunTests.ps1 -TargetProduction -ConfigurationTestDataPath $ConfigurationTestDataPath -outputPath $ResultsPath" + +# Terminate the local web server +Get-Process LocalhostWebServer -ErrorAction Ignore | Stop-Process -ErrorAction Ignore + +Write-Host "Results: $ResultsPath" diff --git a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 index 92416a3100..d1a2742a5e 100644 --- a/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 +++ b/src/PowerShell/scripts/Initialize-LocalWinGetModules.ps1 @@ -1,250 +1,250 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Helper script to setup the modules locally. - - Copies the PowerShell modules output into this location. - - Copies the modules files from the project because there's no guarantee they are updated in the module output - location. - - Adds the module location to PSModulePath if not there. - - Import Microsoft.WinGet.* modules. - - .PARAMETER Platform - The platform we are building for. - - .PARAMETER Configuration - The configuration we are building in. -#> - -[CmdletBinding()] -param ( - [Parameter(Mandatory)] - [ValidateSet("Debug", "Release", "ReleaseStatic")] - [string] - $Configuration, - - [ValidateSet("Client", "DSC", "Configuration", "All")] - [string] - $ModuleType = "All", - - [string] - $BuildRoot = "", - - [switch] - $SkipImportModule, - - [switch] - $Clean -) - -class WinGetModule -{ - [string]$Name - [string]$ModuleRoot - [string]$Output - [string[]]$ArchSpecificFiles = $null - - WinGetModule([string]$n, [string]$m, [string]$o) - { - $this.Name = $n - $this.ModuleRoot = $m - $this.Output = Join-Path $o $this.Name - New-Item $this.Output -ItemType Directory -ErrorAction SilentlyContinue - - if (Get-Module -Name $this.Name) - { - Remove-Module $this.Name -Force - } - } - - [void]PrepareScriptFiles() - { - Write-Verbose "Copying script files: $($this.ModuleRoot) -> $($this.Output)" - xcopy $this.ModuleRoot $this.Output /d /s /f /y - } - - [void]PrepareBinaryFiles([string] $buildRoot, [string] $config) - { - Write-Verbose "Copying binary files: $buildRoot\AnyCpu\$config\PowerShell\$($this.Name)\* -> $($this.Output)" - $copyErrors = $null - Copy-Item "$buildRoot\AnyCpu\$config\PowerShell\$($this.Name)\*" $this.Output -Force -Recurse -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } - } - - # Location is the path relative to the out module directory - [void]AddArchSpecificFiles([string[]] $files, [string]$location, [string] $buildRoot, [string] $config) - { - $x64Path = "$($this.Output)\$location\x64\" - $x86Path = "$($this.Output)\$location\x86\" - $arm64Path = "$($this.Output)\$location\arm64\" - if (-not (Test-Path $x64Path)) - { - New-Item $x64Path -ItemType directory - } - - if (-not (Test-Path $x86Path)) - { - New-Item $x86Path -ItemType directory - } - - if (-not (Test-Path $arm64Path)) - { - New-Item $arm64Path -ItemType directory - } - - foreach ($f in $files) - { - $copyErrors = $null - Copy-Item "$buildRoot\x64\$config\$f" "$($this.Output)\$location\x64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } - Copy-Item "$buildRoot\x86\$config\$f" "$($this.Output)\$location\x86\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } - Copy-Item "$buildRoot\arm64\$config\$f" "$($this.Output)\$location\arm64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } - } - } - - [void]AddAnyCpuSpecificFilesToArch([string[]] $files, [string]$location, [string] $buildRoot, [string] $config) - { - $x64Path = "$($this.Output)\$location\x64\" - $x86Path = "$($this.Output)\$location\x86\" - $arm64Path = "$($this.Output)\$location\arm64\" - if (-not (Test-Path $x64Path)) - { - New-Item $x64Path -ItemType directory - } - - if (-not (Test-Path $x86Path)) - { - New-Item $x86Path -ItemType directory - } - - if (-not (Test-Path $arm64Path)) - { - New-Item $arm64Path -ItemType directory - } - - foreach ($f in $files) - { - $copyErrors = $null - Copy-Item "$buildRoot\AnyCpu\$config\$f" "$($this.Output)\$location\x64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } - Copy-Item "$buildRoot\AnyCpu\$config\$f" "$($this.Output)\$location\x86\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } - Copy-Item "$buildRoot\AnyCpu\$config\$f" "$($this.Output)\$location\arm64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } - } - } -} - -[Flags()] enum ModuleType -{ - None = 0 - Client = 1 - DSC = 2 - Configuration = 4 -} - -$local:moduleToConfigure = [ModuleType]::None -switch ($ModuleType) -{ - "Client" - { - $moduleToConfigure = [ModuleType]::Client; - break - } - - "DSC" - { - $moduleToConfigure = [ModuleType]::DSC; - break - } - - "Configuration" - { - $moduleToConfigure = [ModuleType]::Configuration; - break - } - - "All" - { - $moduleToConfigure = [ModuleType]::Client + [ModuleType]::DSC + [ModuleType]::Configuration; - break - } -} - -# I know it makes sense, but please don't do a clean up of $moduleRootOutput. When the modules are loaded -# there's no way to tell PowerShell to release the binary dlls that are loaded. -$local:moduleRootOutput = "$PSScriptRoot\Module\" - -if ($BuildRoot -eq "") -{ - $BuildRoot = "$PSScriptRoot\..\.."; -} - -if ($Clean -and (Test-Path $moduleRootOutput)) -{ - Remove-Item $moduleRootOutput -Recurse -} - -# Modules, they should be in dependency order so that when importing we don't pick up the release modules. -$local:modules = @() -if ($moduleToConfigure.HasFlag([ModuleType]::Client)) -{ - Write-Host "Setting up Microsoft.WinGet.Client" - $module = [WinGetModule]::new("Microsoft.WinGet.Client", "$PSScriptRoot\..\Microsoft.WinGet.Client\ModuleFiles\", $moduleRootOutput) - $module.PrepareBinaryFiles($BuildRoot, $Configuration) - $module.PrepareScriptFiles() - $additionalFiles = @( - "Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.dll" - "Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd" - "WindowsPackageManager\WindowsPackageManager.dll" - "UndockedRegFreeWinRT\winrtact.dll" - ) - $module.AddArchSpecificFiles($additionalFiles, "net8.0-windows10.0.26100.0\SharedDependencies", $BuildRoot, $Configuration) - $module.AddArchSpecificFiles($additionalFiles, "net48\SharedDependencies", $BuildRoot, $Configuration) - $modules += $module -} - -if ($moduleToConfigure.HasFlag([ModuleType]::DSC)) -{ - Write-Host "Setting up Microsoft.WinGet.DSC" - $module = [WinGetModule]::new("Microsoft.WinGet.DSC", "$PSScriptRoot\..\Microsoft.WinGet.DSC\", $moduleRootOutput) - $module.PrepareScriptFiles() - $modules += $module -} - -if ($moduleToConfigure.HasFlag([ModuleType]::Configuration)) -{ - Write-Host "Setting up Microsoft.WinGet.Configuration" - $module = [WinGetModule]::new("Microsoft.WinGet.Configuration", "$PSScriptRoot\..\Microsoft.WinGet.Configuration\ModuleFiles\", $moduleRootOutput) - $module.PrepareBinaryFiles($BuildRoot, $Configuration) - $module.PrepareScriptFiles() - $additionalFiles = @( - "Microsoft.Management.Configuration\Microsoft.Management.Configuration.dll" - ) - $module.AddArchSpecificFiles($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) - $additionalFiles = @( - "Microsoft.Management.Configuration.Projection\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.Projection.dll" - ) - $module.AddAnyCpuSpecificFilesToArch($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) - $modules += $module -} - -# Add it to module path if not there. -if (-not $env:PSModulePath.Contains($moduleRootOutput)) -{ - Write-Host "Added $moduleRootOutput to PSModulePath" -ForegroundColor Green - $env:PSModulePath += ";$moduleRootOutput" -} - -# Now import modules. -if (-not $SkipImportModule) -{ - foreach($module in $modules) - { - Write-Host "Importing module $($module.Name)" -ForegroundColor Green - Import-Module "$moduleRootOutput\$($module.Name)\$($module.Name).psd1" -Force - } -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Helper script to setup the modules locally. + - Copies the PowerShell modules output into this location. + - Copies the modules files from the project because there's no guarantee they are updated in the module output + location. + - Adds the module location to PSModulePath if not there. + - Import Microsoft.WinGet.* modules. + + .PARAMETER Platform + The platform we are building for. + + .PARAMETER Configuration + The configuration we are building in. +#> + +[CmdletBinding()] +param ( + [Parameter(Mandatory)] + [ValidateSet("Debug", "Release", "ReleaseStatic")] + [string] + $Configuration, + + [ValidateSet("Client", "DSC", "Configuration", "All")] + [string] + $ModuleType = "All", + + [string] + $BuildRoot = "", + + [switch] + $SkipImportModule, + + [switch] + $Clean +) + +class WinGetModule +{ + [string]$Name + [string]$ModuleRoot + [string]$Output + [string[]]$ArchSpecificFiles = $null + + WinGetModule([string]$n, [string]$m, [string]$o) + { + $this.Name = $n + $this.ModuleRoot = $m + $this.Output = Join-Path $o $this.Name + New-Item $this.Output -ItemType Directory -ErrorAction SilentlyContinue + + if (Get-Module -Name $this.Name) + { + Remove-Module $this.Name -Force + } + } + + [void]PrepareScriptFiles() + { + Write-Verbose "Copying script files: $($this.ModuleRoot) -> $($this.Output)" + xcopy $this.ModuleRoot $this.Output /d /s /f /y + } + + [void]PrepareBinaryFiles([string] $buildRoot, [string] $config) + { + Write-Verbose "Copying binary files: $buildRoot\AnyCpu\$config\PowerShell\$($this.Name)\* -> $($this.Output)" + $copyErrors = $null + Copy-Item "$buildRoot\AnyCpu\$config\PowerShell\$($this.Name)\*" $this.Output -Force -Recurse -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } + } + + # Location is the path relative to the out module directory + [void]AddArchSpecificFiles([string[]] $files, [string]$location, [string] $buildRoot, [string] $config) + { + $x64Path = "$($this.Output)\$location\x64\" + $x86Path = "$($this.Output)\$location\x86\" + $arm64Path = "$($this.Output)\$location\arm64\" + if (-not (Test-Path $x64Path)) + { + New-Item $x64Path -ItemType directory + } + + if (-not (Test-Path $x86Path)) + { + New-Item $x86Path -ItemType directory + } + + if (-not (Test-Path $arm64Path)) + { + New-Item $arm64Path -ItemType directory + } + + foreach ($f in $files) + { + $copyErrors = $null + Copy-Item "$buildRoot\x64\$config\$f" "$($this.Output)\$location\x64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } + Copy-Item "$buildRoot\x86\$config\$f" "$($this.Output)\$location\x86\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } + Copy-Item "$buildRoot\arm64\$config\$f" "$($this.Output)\$location\arm64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } + } + } + + [void]AddAnyCpuSpecificFilesToArch([string[]] $files, [string]$location, [string] $buildRoot, [string] $config) + { + $x64Path = "$($this.Output)\$location\x64\" + $x86Path = "$($this.Output)\$location\x86\" + $arm64Path = "$($this.Output)\$location\arm64\" + if (-not (Test-Path $x64Path)) + { + New-Item $x64Path -ItemType directory + } + + if (-not (Test-Path $x86Path)) + { + New-Item $x86Path -ItemType directory + } + + if (-not (Test-Path $arm64Path)) + { + New-Item $arm64Path -ItemType directory + } + + foreach ($f in $files) + { + $copyErrors = $null + Copy-Item "$buildRoot\AnyCpu\$config\$f" "$($this.Output)\$location\x64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } + Copy-Item "$buildRoot\AnyCpu\$config\$f" "$($this.Output)\$location\x86\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } + Copy-Item "$buildRoot\AnyCpu\$config\$f" "$($this.Output)\$location\arm64\" -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } + } + } +} + +[Flags()] enum ModuleType +{ + None = 0 + Client = 1 + DSC = 2 + Configuration = 4 +} + +$local:moduleToConfigure = [ModuleType]::None +switch ($ModuleType) +{ + "Client" + { + $moduleToConfigure = [ModuleType]::Client; + break + } + + "DSC" + { + $moduleToConfigure = [ModuleType]::DSC; + break + } + + "Configuration" + { + $moduleToConfigure = [ModuleType]::Configuration; + break + } + + "All" + { + $moduleToConfigure = [ModuleType]::Client + [ModuleType]::DSC + [ModuleType]::Configuration; + break + } +} + +# I know it makes sense, but please don't do a clean up of $moduleRootOutput. When the modules are loaded +# there's no way to tell PowerShell to release the binary dlls that are loaded. +$local:moduleRootOutput = "$PSScriptRoot\Module\" + +if ($BuildRoot -eq "") +{ + $BuildRoot = "$PSScriptRoot\..\.."; +} + +if ($Clean -and (Test-Path $moduleRootOutput)) +{ + Remove-Item $moduleRootOutput -Recurse +} + +# Modules, they should be in dependency order so that when importing we don't pick up the release modules. +$local:modules = @() +if ($moduleToConfigure.HasFlag([ModuleType]::Client)) +{ + Write-Host "Setting up Microsoft.WinGet.Client" + $module = [WinGetModule]::new("Microsoft.WinGet.Client", "$PSScriptRoot\..\Microsoft.WinGet.Client\ModuleFiles\", $moduleRootOutput) + $module.PrepareBinaryFiles($BuildRoot, $Configuration) + $module.PrepareScriptFiles() + $additionalFiles = @( + "Microsoft.Management.Deployment.InProc\Microsoft.Management.Deployment.dll" + "Microsoft.Management.Deployment\Microsoft.Management.Deployment.winmd" + "WindowsPackageManager\WindowsPackageManager.dll" + "UndockedRegFreeWinRT\winrtact.dll" + ) + $module.AddArchSpecificFiles($additionalFiles, "net8.0-windows10.0.26100.0\SharedDependencies", $BuildRoot, $Configuration) + $module.AddArchSpecificFiles($additionalFiles, "net48\SharedDependencies", $BuildRoot, $Configuration) + $modules += $module +} + +if ($moduleToConfigure.HasFlag([ModuleType]::DSC)) +{ + Write-Host "Setting up Microsoft.WinGet.DSC" + $module = [WinGetModule]::new("Microsoft.WinGet.DSC", "$PSScriptRoot\..\Microsoft.WinGet.DSC\", $moduleRootOutput) + $module.PrepareScriptFiles() + $modules += $module +} + +if ($moduleToConfigure.HasFlag([ModuleType]::Configuration)) +{ + Write-Host "Setting up Microsoft.WinGet.Configuration" + $module = [WinGetModule]::new("Microsoft.WinGet.Configuration", "$PSScriptRoot\..\Microsoft.WinGet.Configuration\ModuleFiles\", $moduleRootOutput) + $module.PrepareBinaryFiles($BuildRoot, $Configuration) + $module.PrepareScriptFiles() + $additionalFiles = @( + "Microsoft.Management.Configuration\Microsoft.Management.Configuration.dll" + ) + $module.AddArchSpecificFiles($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) + $additionalFiles = @( + "Microsoft.Management.Configuration.Projection\net8.0-windows10.0.26100.0\Microsoft.Management.Configuration.Projection.dll" + ) + $module.AddAnyCpuSpecificFilesToArch($additionalFiles, "SharedDependencies", $BuildRoot, $Configuration) + $modules += $module +} + +# Add it to module path if not there. +if (-not $env:PSModulePath.Contains($moduleRootOutput)) +{ + Write-Host "Added $moduleRootOutput to PSModulePath" -ForegroundColor Green + $env:PSModulePath += ";$moduleRootOutput" +} + +# Now import modules. +if (-not $SkipImportModule) +{ + foreach($module in $modules) + { + Write-Host "Importing module $($module.Name)" -ForegroundColor Green + Import-Module "$moduleRootOutput\$($module.Name)\$($module.Name).psd1" -Force + } +} diff --git a/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 index 931687d083..94bf17ba19 100644 --- a/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetAdminSettingsResourceSample.ps1 @@ -1,63 +1,63 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Simple sample on how to use WinGetAdminSettings DSC resource. - Requires PSDesiredStateConfiguration version 2.0.6 - - IMPORTANT: This will leave LocalManifestFiles enabled - Run as admin for set. -#> - -#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC - -using module Microsoft.WinGet.DSC -using namespace System.Collections.Generic - -$resource = @{ - Name = 'WinGetAdminSettings' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - } -} - -$getResult = Invoke-DscResource @resource -Method Get -Write-Host "Current sources" -$getResult.Settings - -$expectedSources = [List[Hashtable]]::new() -$expectedSources.Add(@{ - Name = "winget" - Arg = "https://cdn.winget.microsoft.com/cache" -}) - -# Lets see if LocalManifestFiles is enabled -$resource.Property = @{ - Settings = @{ - LocalManifestFiles = $true - } -} - -$testResult = Invoke-DscResource @resource -Method Test -if (-not $testResult.InDesiredState) -{ - Write-Host "LocalManifestFiles is disabled, enabling" - Invoke-DscResource @resource -Method Set | Out-Null - - # Now try again - $testResult2 = Invoke-DscResource @resource -Method Test - if (-not $testResult.InDesiredState) - { - Write-Host "LocalManifestFiles is now enabled" - } - else - { - Write-Host "Is there a bug somewhere?" - return - } -} -else -{ - Write-Host "LocalManifestFiles is already enabled" -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Simple sample on how to use WinGetAdminSettings DSC resource. + Requires PSDesiredStateConfiguration version 2.0.6 + + IMPORTANT: This will leave LocalManifestFiles enabled + Run as admin for set. +#> + +#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC + +using module Microsoft.WinGet.DSC +using namespace System.Collections.Generic + +$resource = @{ + Name = 'WinGetAdminSettings' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + } +} + +$getResult = Invoke-DscResource @resource -Method Get +Write-Host "Current sources" +$getResult.Settings + +$expectedSources = [List[Hashtable]]::new() +$expectedSources.Add(@{ + Name = "winget" + Arg = "https://cdn.winget.microsoft.com/cache" +}) + +# Lets see if LocalManifestFiles is enabled +$resource.Property = @{ + Settings = @{ + LocalManifestFiles = $true + } +} + +$testResult = Invoke-DscResource @resource -Method Test +if (-not $testResult.InDesiredState) +{ + Write-Host "LocalManifestFiles is disabled, enabling" + Invoke-DscResource @resource -Method Set | Out-Null + + # Now try again + $testResult2 = Invoke-DscResource @resource -Method Test + if (-not $testResult.InDesiredState) + { + Write-Host "LocalManifestFiles is now enabled" + } + else + { + Write-Host "Is there a bug somewhere?" + return + } +} +else +{ + Write-Host "LocalManifestFiles is already enabled" +} diff --git a/src/PowerShell/scripts/samples/WinGetPackageManagerSample.ps1 b/src/PowerShell/scripts/samples/WinGetPackageManagerSample.ps1 index db679e989b..b503dcaf06 100644 --- a/src/PowerShell/scripts/samples/WinGetPackageManagerSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetPackageManagerSample.ps1 @@ -1,127 +1,127 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Simple sample on how to use WinGetIntegrity DSC resource. - Requires PSDesiredStateConfiguration version 2.0.6 - - IMPORTANT: This will modify the winget you have installed -#> - -#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC - -using module Microsoft.WinGet.DSC - -$resource = @{ - Name = 'WinGetPackageManager' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - } -} - -# This just demonstrate the Get command. -$getResult = Invoke-DscResource @resource -Method Get - -if (-not([string]::IsNullOrWhiteSpace($getResult.Version))) -{ - Write-Host "Current winget version $($getResult.Version)" -} -else -{ - # If the result of get contains an empty version, it means that winget is not installed. This is not the right - # way to do it though, is better to call Test with an empty Version property. - if (-not (Invoke-DscResource @resource -Method Test).InDesiredState) - { - Write-Host "winget is not installed" - } - else - { - Write-Error "BUG BUG!!" - return - } -} - -# At the time I'm doing this the second latest released winget version is v1.3.2091. Lets assume you want to stay there forever. -$v132091 = "v1.3.2091" -$resource = @{ - Name = 'WinGetPackageManager' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - Version = $v132091 - } -} - -$testResult = Invoke-DscResource @resource -Method Test -if ($testResult.InDesiredState) -{ - Write-Host "winget is already in a good state (aka in version $v132091)" -} -else -{ - # Oh no, we are not in a good state. Lets get you there. - # Internally, Set calls Repair-WinGet -Version v1.3.2691 which means that it will try to repair winget - # by downloading v1.3.2691 and installing it if needed. For example, if your AppInstaller is not registered - # it will register the package and then verify the specified version is installed. - Invoke-DscResource @resource -Method Set | Out-Null - - # Now this should work. - $testResult = Invoke-DscResource @resource -Method Test - if ($testResult.InDesiredState) - { - Write-Host "winget is in a good state (aka in version $v132091)" - } - else - { - Write-Error "BUG BUG!!" - return - } -} - -# Now, lets say that you want to have always the latest winget installed. You can specify UseLatest. -$resource = @{ - Name = 'WinGetPackageManager' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - UseLatest = $true - } -} -$testResult = Invoke-DscResource @resource -Method Test -if ($testResult.InDesiredState) -{ - Write-Host "winget version is the latest version" -} -else -{ - Write-Host "winget version is not latest version" -} - -# You can also do UseLatestPreRelease -$resource = @{ - Name = 'WinGetPackageManager' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - UseLatestPreRelease = $true - } -} -$testResult = Invoke-DscResource @resource -Method Test -if ($testResult.InDesiredState) -{ - Write-Host "winget version is the latest prerelease version" -} -else -{ - # Get the latest prerelease. - Invoke-DscResource @resource -Method Set | Out-Null - - $testResult = Invoke-DscResource @resource -Method Test - if ($testResult.InDesiredState) - { - Write-Host "winget version is the latest prerelease version" - } - else - { - Write-Error "BUG BUG!!" - return - } -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Simple sample on how to use WinGetIntegrity DSC resource. + Requires PSDesiredStateConfiguration version 2.0.6 + + IMPORTANT: This will modify the winget you have installed +#> + +#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC + +using module Microsoft.WinGet.DSC + +$resource = @{ + Name = 'WinGetPackageManager' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + } +} + +# This just demonstrate the Get command. +$getResult = Invoke-DscResource @resource -Method Get + +if (-not([string]::IsNullOrWhiteSpace($getResult.Version))) +{ + Write-Host "Current winget version $($getResult.Version)" +} +else +{ + # If the result of get contains an empty version, it means that winget is not installed. This is not the right + # way to do it though, is better to call Test with an empty Version property. + if (-not (Invoke-DscResource @resource -Method Test).InDesiredState) + { + Write-Host "winget is not installed" + } + else + { + Write-Error "BUG BUG!!" + return + } +} + +# At the time I'm doing this the second latest released winget version is v1.3.2091. Lets assume you want to stay there forever. +$v132091 = "v1.3.2091" +$resource = @{ + Name = 'WinGetPackageManager' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + Version = $v132091 + } +} + +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "winget is already in a good state (aka in version $v132091)" +} +else +{ + # Oh no, we are not in a good state. Lets get you there. + # Internally, Set calls Repair-WinGet -Version v1.3.2691 which means that it will try to repair winget + # by downloading v1.3.2691 and installing it if needed. For example, if your AppInstaller is not registered + # it will register the package and then verify the specified version is installed. + Invoke-DscResource @resource -Method Set | Out-Null + + # Now this should work. + $testResult = Invoke-DscResource @resource -Method Test + if ($testResult.InDesiredState) + { + Write-Host "winget is in a good state (aka in version $v132091)" + } + else + { + Write-Error "BUG BUG!!" + return + } +} + +# Now, lets say that you want to have always the latest winget installed. You can specify UseLatest. +$resource = @{ + Name = 'WinGetPackageManager' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + UseLatest = $true + } +} +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "winget version is the latest version" +} +else +{ + Write-Host "winget version is not latest version" +} + +# You can also do UseLatestPreRelease +$resource = @{ + Name = 'WinGetPackageManager' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + UseLatestPreRelease = $true + } +} +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "winget version is the latest prerelease version" +} +else +{ + # Get the latest prerelease. + Invoke-DscResource @resource -Method Set | Out-Null + + $testResult = Invoke-DscResource @resource -Method Test + if ($testResult.InDesiredState) + { + Write-Host "winget version is the latest prerelease version" + } + else + { + Write-Error "BUG BUG!!" + return + } +} diff --git a/src/PowerShell/scripts/samples/WinGetPackageResourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetPackageResourceSample.ps1 index 1af5d0d439..4a582fcdd6 100644 --- a/src/PowerShell/scripts/samples/WinGetPackageResourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetPackageResourceSample.ps1 @@ -1,75 +1,75 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Simple sample on how to use WinGetPackage DSC resource. - Requires PSDesiredStateConfiguration v2 and enabling the - PSDesiredStateConfiguration.InvokeDscResource experimental feature - `Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource` - IMPORTANT: This will install Microsoft.PowerToys. -#> - -#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC - -using module Microsoft.WinGet.DSC - -$resource = @{ - Name = 'WinGetPackage' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - Id = 'Microsoft.PowerToys' - Ensure = "Absent" - } -} - -$testResult = Invoke-DscResource @resource -Method Test -if ($testResult.InDesiredState) -{ - Write-Host "PowerToys is not installed." -} -else -{ - Write-Host "PowerToys is installed." -} - -# Default value of Ensure is present. -$resource = @{ - Name = 'WinGetPackage' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - Id = 'Microsoft.PowerToys' - Version = '0.65.0' - } -} - -$testResult = Invoke-DscResource @resource -Method Test -if ($testResult.InDesiredState) -{ - Write-Host "PowerToys 0.65.0 is installed." -} -else -{ - Write-Host "PowerToys 0.65.0 is not installed." - - Invoke-DscResource @resource -Method Set -} - -$resource = @{ - Name = 'WinGetPackage' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - Id = 'Microsoft.PowerToys' - } -} -$testResult = Invoke-DscResource @resource -Method Test -if ($testResult.InDesiredState) -{ - Write-Host "PowerToys latest version is installed." -} -else -{ - Write-Host "PowerToys latest version is no installed." - - Invoke-DscResource @resource -Method Set +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Simple sample on how to use WinGetPackage DSC resource. + Requires PSDesiredStateConfiguration v2 and enabling the + PSDesiredStateConfiguration.InvokeDscResource experimental feature + `Enable-ExperimentalFeature -Name PSDesiredStateConfiguration.InvokeDscResource` + IMPORTANT: This will install Microsoft.PowerToys. +#> + +#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC + +using module Microsoft.WinGet.DSC + +$resource = @{ + Name = 'WinGetPackage' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + Id = 'Microsoft.PowerToys' + Ensure = "Absent" + } +} + +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "PowerToys is not installed." +} +else +{ + Write-Host "PowerToys is installed." +} + +# Default value of Ensure is present. +$resource = @{ + Name = 'WinGetPackage' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + Id = 'Microsoft.PowerToys' + Version = '0.65.0' + } +} + +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "PowerToys 0.65.0 is installed." +} +else +{ + Write-Host "PowerToys 0.65.0 is not installed." + + Invoke-DscResource @resource -Method Set +} + +$resource = @{ + Name = 'WinGetPackage' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + Id = 'Microsoft.PowerToys' + } +} +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "PowerToys latest version is installed." +} +else +{ + Write-Host "PowerToys latest version is no installed." + + Invoke-DscResource @resource -Method Set } \ No newline at end of file diff --git a/src/PowerShell/scripts/samples/WinGetSourceSample.ps1 b/src/PowerShell/scripts/samples/WinGetSourceSample.ps1 index 1ea136065c..5664f0bd71 100644 --- a/src/PowerShell/scripts/samples/WinGetSourceSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetSourceSample.ps1 @@ -1,79 +1,79 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Simple sample on how to use WinGetSourceResource DSC resource. - Requires PSDesiredStateConfiguration version 2.0.6 - - IMPORTANT: This deletes the main winget source and add it again. - Run as admin for set. -#> - -#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC - -using module Microsoft.WinGet.DSC -using namespace System.Collections.Generic - -[CmdletBinding()] -param ( - [Parameter()] - [string] - $SourceName = "winget", - - [Parameter()] - [string] - $Argument = "https://cdn.winget.microsoft.com/cache", - - [Parameter()] - [string] - $Type = "" -) - -$resource = @{ - Name = 'WinGetSourceResource' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - Name = $SourceName - } -} - -$getResult = Invoke-DscResource @resource -Method Get -Write-Host "Current sources" -Format-List $getResult -Force - -$resource.Property = @{ - Argument = $Argument - Type = $Type -} - -# The default value comparison for test is Partial, so if you have the winget source this should succeed. -$testResult = Invoke-DscResource @resource -Method Test -if ($testResult.InDesiredState) -{ - Write-Host "winget source is present" -} -else -{ - Write-Host "winget source is not present" - return -} - -# Removing winget. Note this will fail if not run as admin. -$resource.Property = @{ - Ensure = [Ensure]::Absent -} - -Invoke-DscResource @resource -Method Set | Out-Null -Write-Host "winget source removed" - -# Test again -$testResult = Invoke-DscResource @resource -Method Test -if (-not $testResult.InDesiredState) -{ - Write-Host "winget source is still present." -} -else -{ - Write-Host "winget was removed." -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Simple sample on how to use WinGetSourceResource DSC resource. + Requires PSDesiredStateConfiguration version 2.0.6 + + IMPORTANT: This deletes the main winget source and add it again. + Run as admin for set. +#> + +#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC + +using module Microsoft.WinGet.DSC +using namespace System.Collections.Generic + +[CmdletBinding()] +param ( + [Parameter()] + [string] + $SourceName = "winget", + + [Parameter()] + [string] + $Argument = "https://cdn.winget.microsoft.com/cache", + + [Parameter()] + [string] + $Type = "" +) + +$resource = @{ + Name = 'WinGetSourceResource' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + Name = $SourceName + } +} + +$getResult = Invoke-DscResource @resource -Method Get +Write-Host "Current sources" +Format-List $getResult -Force + +$resource.Property = @{ + Argument = $Argument + Type = $Type +} + +# The default value comparison for test is Partial, so if you have the winget source this should succeed. +$testResult = Invoke-DscResource @resource -Method Test +if ($testResult.InDesiredState) +{ + Write-Host "winget source is present" +} +else +{ + Write-Host "winget source is not present" + return +} + +# Removing winget. Note this will fail if not run as admin. +$resource.Property = @{ + Ensure = [Ensure]::Absent +} + +Invoke-DscResource @resource -Method Set | Out-Null +Write-Host "winget source removed" + +# Test again +$testResult = Invoke-DscResource @resource -Method Test +if (-not $testResult.InDesiredState) +{ + Write-Host "winget source is still present." +} +else +{ + Write-Host "winget was removed." +} diff --git a/src/PowerShell/scripts/samples/WinGetUserSettingsPackageManagerSample.ps1 b/src/PowerShell/scripts/samples/WinGetUserSettingsPackageManagerSample.ps1 index 1a169e2ae5..1f4f9f39fb 100644 --- a/src/PowerShell/scripts/samples/WinGetUserSettingsPackageManagerSample.ps1 +++ b/src/PowerShell/scripts/samples/WinGetUserSettingsPackageManagerSample.ps1 @@ -1,72 +1,72 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Simple sample on how to use WinGetUserSettings DSC resource. - Requires PSDesiredStateConfiguration version 2.0.6 - - IMPORTANT: If you loaded the released modules this will modify your settings. - Use the -Restore to get back to your original settings - - .PARAMETER Restore - Restore back to the original user settings. -#> - -#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC - -using module Microsoft.WinGet.DSC - -[CmdletBinding()] -param ( - [Parameter()] - [switch] - $Restore -) - -$resource = @{ - Name = 'WinGetUserSettings' - ModuleName = 'Microsoft.WinGet.DSC' - Property = @{ - } -} - -# Get current settings -$getResult = Invoke-DscResource @resource -Method Get -Write-Host "Current Settings" -$settingsBackup = $getResult.Settings -$getResult.Settings | ConvertTo-Json - -# Test if telemetry is disabled -$resource.Property = @{ - Settings = @{ - telemetry = @{ - disable = $false - } - } - # If you want to check that this setting is the only setting set use [WinGetAction]::Full - Action = [WinGetAction]::Partial -} - -$testResult = Invoke-DscResource @resource -Method Test -if (-not $testResult.InDesiredState) -{ - Write-Host "Adding telemetry setting" - Invoke-DscResource @resource -Method Set | Out-Null - - Write-Host "New settings" - $getResult = Invoke-DscResource @resource -Method Get - $getResult.Settings | ConvertTo-Json -} -else -{ - Write-Host "Telemetry is already disabled" -} - -if ($Restore) -{ - $resource.Property.Settings = $settingsBackup - $resource.Property.Action = [WinGetAction]::Full - Invoke-DscResource @resource -Method Set | Out-Null - Write-Host "Settings restored." -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Simple sample on how to use WinGetUserSettings DSC resource. + Requires PSDesiredStateConfiguration version 2.0.6 + + IMPORTANT: If you loaded the released modules this will modify your settings. + Use the -Restore to get back to your original settings + + .PARAMETER Restore + Restore back to the original user settings. +#> + +#Requires -Modules Microsoft.WinGet.Client, Microsoft.WinGet.DSC + +using module Microsoft.WinGet.DSC + +[CmdletBinding()] +param ( + [Parameter()] + [switch] + $Restore +) + +$resource = @{ + Name = 'WinGetUserSettings' + ModuleName = 'Microsoft.WinGet.DSC' + Property = @{ + } +} + +# Get current settings +$getResult = Invoke-DscResource @resource -Method Get +Write-Host "Current Settings" +$settingsBackup = $getResult.Settings +$getResult.Settings | ConvertTo-Json + +# Test if telemetry is disabled +$resource.Property = @{ + Settings = @{ + telemetry = @{ + disable = $false + } + } + # If you want to check that this setting is the only setting set use [WinGetAction]::Full + Action = [WinGetAction]::Partial +} + +$testResult = Invoke-DscResource @resource -Method Test +if (-not $testResult.InDesiredState) +{ + Write-Host "Adding telemetry setting" + Invoke-DscResource @resource -Method Set | Out-Null + + Write-Host "New settings" + $getResult = Invoke-DscResource @resource -Method Get + $getResult.Settings | ConvertTo-Json +} +else +{ + Write-Host "Telemetry is already disabled" +} + +if ($Restore) +{ + $resource.Property.Settings = $settingsBackup + $resource.Property.Action = [WinGetAction]::Full + Invoke-DscResource @resource -Method Set | Out-Null + Write-Host "Settings restored." +} diff --git a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 index f8042c2565..9cd9a08564 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Client.Tests.ps1 @@ -1,1025 +1,1025 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# -.Synopsis - Pester tests related to the Microsoft.WinGet.Client PowerShell module. - The tests require the localhost web server to be running and serving the test data. - 'Invoke-Pester' should be called in an admin PowerShell window. -#> -[CmdletBinding()] -param( - # Whether to use production or developement targets. - [switch]$TargetProduction -) - -BeforeAll { - if ($TargetProduction) - { - $wingetExeName = "winget.exe" - } - else - { - $wingetExeName = "wingetdev.exe" - } - - $settingsFilePath = (ConvertFrom-Json (& $wingetExeName settings export)).userSettingsFile - $originalSettingsContent = Get-Content -Path $settingsFilePath -Raw - - $deviceGroupPolicyRoot = "HKLM:\Software\Policies\Microsoft\Windows" - $wingetPolicyKeyName = "AppInstaller" - $wingetGroupPolicyRegistryRoot = $deviceGroupPolicyRoot + "\" + $wingetPolicyKeyName - - Import-Module Microsoft.WinGet.Client - - function SetWinGetSettingsHelper($settings) { - $content = ConvertTo-Json $settings -Depth 4 - Set-Content -Path $settingsFilePath -Value $content - } - - function RestoreWinGetSettings() { - Set-Content -Path $settingsFilePath -Value $originalSettingsContent - } - - # Source Add requires admin privileges, this will only execute successfully in an elevated PowerShell. - function AddTestSource { - try { - Get-WinGetSource -Name 'TestSource' - } - catch { - Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' - } - } - - # This is a workaround to an issue where the server takes longer than expected to terminate when - # running from PowerShell. This can cause other E2E tests to fail when attempting to reset the test source. - function RemoveTestSource { - try { - # Source Remove requires admin privileges, this will only execute successfully in an elevated PowerShell. - $testSource = Get-WinGetSource | Where-Object -Property 'Name' -eq 'TestSource' - if ($null -ne $testSource) - { - # Source Remove requires admin privileges - Remove-WinGetSource -Name 'TestSource' - } - } - catch { - # Non-admin - Start-Process -FilePath $wingetExeName -ArgumentList "source remove TestSource" - } - } - - function CreatePolicyKeyIfNotExists() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if(-Not($registryExists)) - { - New-Item -Path $deviceGroupPolicyRoot -Name $wingetPolicyKeyName - } - } - - function CleanupGroupPolicyKeyIfExists() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if($registryExists) - { - Remove-Item -Path $wingetGroupPolicyRegistryRoot -Recurse - } - } - - function CleanupGroupPolicies() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if($registryExists) - { - Remove-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name * - } - } - - function WaitForWindowsPackageManagerServer([bool]$force = $false) - { - $processes = Get-Process | Where-Object { $_.Name -eq "WindowsPackageManagerServer" } - foreach ($p in $processes) - { - if ($force) - { - Stop-Process $p - } - - $timeout = 300 - $secondsToWait = 5 - $time = 0 - while ($p.HasExited -eq $false) - { - $time += $secondsToWait - if ($time -ge $timeout ) - { - throw "Timeout waiting for $($p.Id) to exit" - } - Start-Sleep -Seconds 5 - } - } - } - - function GetRandomTestDirectory() - { - return Join-Path -Path $env:Temp -ChildPath "WingetPwshTest-$(New-Guid)" - } - - function Validate-WinGetResultCommonFields([psobject]$result, [psobject]$expected) { - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be $expected.Id - $result.Name | Should -Be $expected.Name - $result.Source | Should -Be $expected.Source - $result.Status | Should -Be $expected.Status - } - - function Validate-WinGetPackageOperationResult([psobject]$result, [psobject]$expected, [string]$operationType) - { - Validate-WinGetResultCommonFields $result $expected - $result.RebootRequired | Should -Be $expected.RebootRequired - - switch ($operationType) { - 'install' { - $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode - } - 'update' { - $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode - } - 'repair' { - $result.RepairErrorCode | Should -Be $expected.RepairErrorCode - } - 'uninstall' { - $result.UninstallerErrorCode | Should -Be $expected.UninstallerErrorCode - } - default { - throw "Unknown operation type: $operationType" - } - } - } -} - -Describe 'Get-WinGetVersion' { - - It 'Get-WinGetVersion' { - $version = Get-WinGetVersion - $version | Should -Not -BeNullOrEmpty -ErrorAction Stop - } -} - -Describe 'Reset-WinGetSource' { - BeforeAll { - AddTestSource - } - - # Requires admin - It 'Resets all sources' { - Reset-WinGetSource -All - } - - It 'Test source should be removed' { - { Get-WinGetSource -Name 'TestSource' } | Should -Throw - } -} - -Describe 'Get|Add|Reset-WinGetSource' { - - BeforeAll { - $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} - SetWinGetSettingsHelper $ogSettings - - Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' -Explicit -Priority 42 - } - - It 'Get Test source' { - $source = Get-WinGetSource -Name 'TestSource' - - $source | Should -Not -BeNullOrEmpty -ErrorAction Stop - $source.Name | Should -Be 'TestSource' - $source.Argument | Should -Be 'https://localhost:5001/TestKit/' - $source.Type | Should -Be 'Microsoft.PreIndexed.Package' - $source.TrustLevel | Should -Be 'Trusted' - $source.Explicit | Should -Be $true - $source.Priority | Should -Be 42 - } - - It 'Get fake source' { - { Get-WinGetSource -Name 'Fake' } | Should -Throw - } - - # This tests require admin - It 'Reset Test source' { - Reset-WinGetSource -Name TestSource - } - - AfterAll { - RemoveTestSource - RestoreWinGetSettings - } -} - -Describe 'Find-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - It 'Given no parameters, lists all available packages' { - $allPackages = Find-WinGetPackage -Source TestSource - $allPackages.Count | Should -BeGreaterThan 0 - } - - It 'Find by Id' { - $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestExampleInstaller' - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package.Name | Should -Be 'TestExampleInstaller' - $package.Id | Should -Be 'AppInstallerTest.TestExampleInstaller' - $package.Version | Should -Be '1.2.3.4' - $package.Source | Should -Be 'TestSource' - } - - It 'Find by Name' { - $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package[0].Name | Should -Be 'TestPortableExe' - $package[1].Name | Should -Be 'TestPortableExeWithCommand' - } - - It 'Find by Name sort by Version' { - $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' | Sort-Object 'Version' - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package[0].Name | Should -Be 'TestPortableExeWithCommand' - $package[1].Name | Should -Be 'TestPortableExe' - } - - It 'Find package and verify PackageVersionInfo' { - $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestPortableExe' -MatchOption Equals - - $package | Should -Not -BeNullOrEmpty -ErrorAction Stop - $package.AvailableVersions[0] | Should -Be '3.0.0.0' - $package.AvailableVersions[1] | Should -Be '2.0.0.0' - $package.AvailableVersions.Count | Should -Be 4 - - $packageVersionInfo = $package.GetPackageVersionInfo("3.0.0.0") - $packageVersionInfo.DisplayName | Should -Be 'TestPortableExe' - $packageVersionInfo.Id | Should -Be 'AppInstallerTest.TestPortableExe' - $packageVersionInfo.CompareToVersion("2.0.0.0") | Should -Be 'Greater' - $packageVersionInfo.CompareToVersion("4.0.0.0") | Should -Be 'Lesser' - } -} - -Describe 'Install|Update|Uninstall-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - BeforeEach { - $expectedExeInstallerResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestExeInstaller" - Name = "TestExeInstaller" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - UninstallerErrorCode = 0 - } - - $expectedPortableInstallerResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestPortableExe" - Name = "TestPortableExe" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install by Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' - } - - It 'Install by exact Name and Version' { - $result = Install-WinGetPackage -Name TestPortableExe -Version '2.0.0.0' -MatchOption Equals - Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'install' - } - - It 'Update by Id' { - $result = Update-WinGetPackage -Id AppInstallerTest.TestExeInstaller - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'update' - } - - It 'Update by Name' { - $result = Update-WinGetPackage -Name TestPortableExe - Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'update' - } - - It 'Uninstall by Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'uninstall' - } - - It 'Uninstall by Name' { - $result = Uninstall-WinGetPackage -Name TestPortableExe - Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'uninstall' - } - - AfterAll { - # Uninstall all test packages after each for proper cleanup. - $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - } - - $testPortable = Get-WinGetPackage -Id AppInstallerTest.TestPortableExe -MatchOption Equals - if ($testPortable.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestPortableExe - } - } -} - -Describe 'Install|Repair|Uninstall-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - Context 'MSIX Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestMsixInstaller" - Name = "TestMsixInstaller" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install MSIX By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Repair MSIX By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It 'Uninstall MSIX By Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - Context 'Burn installer "Modify" Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestModifyRepair" - Name = "TestModifyRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install Burn Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Repair Burn Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It 'Uninstall Burn Installer By Id' { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - Context 'Exe Installer "Uninstaller" Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.UninstallerRepair" - Name = "UninstallerRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install Exe Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Uninstaller Repair Exe Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It "Uninstall Exe Installer By Id" { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - Context 'Inno "Installer" Repair Scenario' { - BeforeEach { - $expectedResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestInstallerRepair" - Name = "TestInstallerRepair" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - RepairErrorCode = 0 - UninstallerErrorCode = 0 - } - } - - It 'Install Exe Installer By Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'install' - } - - It 'Installer Repair Exe Installer By Id' { - $result = Repair-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'repair' - } - - It "Uninstall Exe Installer By Id" { - $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' - } - } - - AfterAll { - # Uninstall all test packages after each for proper cleanup. - $testMsix = Get-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - if ($testMsix.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller - } - - $testBurn = Get-WinGetPackage -Id AppInstallerTest.TestModifyRepair - if ($testBurn.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair - } - - $testExe = Get-WinGetPackage -Id AppInstallerTest.UninstallerRepair - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair - } - - $testInno = Get-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - if ($testInno.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair - } - } -} - -Describe 'Install-WinGetPackage Source Priority' { - - It 'Install equal Priority' { - AddTestSource - Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' - - { Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' } | Should -Throw - } - - It 'Install higher Priority' { - $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} - SetWinGetSettingsHelper $ogSettings - - RemoveTestSource - Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -Priority 1 - Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' - - $expectedExeInstallerResult = [PSCustomObject]@{ - Id = "AppInstallerTest.TestExeInstaller" - Name = "TestExeInstaller" - Source = "TestSource" - Status = 'Ok' - RebootRequired = 'False' - InstallerErrorCode = 0 - UninstallerErrorCode = 0 - } - - $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' - Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' - } - - AfterEach { - $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - } - - Remove-WinGetSource -Name 'dummyPackageSource' - RemoveTestSource - RestoreWinGetSettings - } -} - -Describe 'Get-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - It 'Install by Id' { - $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.InstallerErrorCode | Should -Be 0 - $result.Status | Should -Be 'Ok' - $result.RebootRequired | Should -Be 'False' - } - - It 'Get package by Id' { - $result = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Name | Should -Be 'TestExeInstaller' - $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' - $result.InstalledVersion | Should -Be '1.0.0.0' - $result.Source | Should -Be 'TestSource' - $result.AvailableVersions[0] | Should -Be '2.0.0.0' - } - - It 'Get package by Name' { - $result = Get-WinGetPackage -Name TestExeInstaller - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.Name | Should -Be 'TestExeInstaller' - $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' - $result.InstalledVersion | Should -Be '1.0.0.0' - $result.Source | Should -Be 'TestSource' - $result.AvailableVersions[0] | Should -Be '2.0.0.0' - } - - AfterAll { - # Uninstall all test packages after each for proper cleanup. - $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller - if ($testExe.Count -gt 0) - { - Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller - } - } -} - -Describe 'Export-WinGetPackage' { - - BeforeAll { - AddTestSource - } - - It 'Download by Id' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - # Download directory should be created and have exactly two files (installer and manifest file). - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - It 'Download by Locale' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -Locale 'zh-CN' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" - $result.Name | Should -Be "TestMultipleInstallers" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - It 'Download by InstallerType' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -InstallerType 'msi' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" - $result.Name | Should -Be "TestMultipleInstallers" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - It 'Download by InstallerType that does not exist' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -InstallerType 'zip' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'NoApplicableInstallers' - $result.ExtendedErrorCode | Should -Not -BeNullOrEmpty - Test-Path -Path $testDirectory | Should -Be $false - } - - It 'Download with short Version' { - $testDirectory = GetRandomTestDirectory - $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1' -DownloadDirectory $testDirectory - - $result | Should -Not -BeNullOrEmpty - $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" - $result.Name | Should -Be "TestExeInstaller" - $result.Source | Should -Be "TestSource" - $result.Status | Should -Be 'Ok' - - # Download directory should be created and have exactly two files (installer and manifest file). - Test-Path -Path $testDirectory | Should -Be $true - (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 - } - - AfterEach { - if (Test-Path $testDirectory) { - Remove-Item $testDirectory -Force -Recurse - } - } -} - -Describe 'Get-WinGetUserSetting' { - - It 'Get setting' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $userSettings = Get-WinGetUserSetting - $userSettings | Should -Not -BeNullOrEmpty -ErrorAction Stop - $userSettings.Count | Should -Be 2 - $userSettings.visual.progressBar | Should -Be 'rainbow' - $userSettings.experimentalFeatures.experimentalArg | Should -Be $false - $userSettings.experimentalFeatures.experimentalCmd | Should -Be $true - } - - It 'Get settings. Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - { Get-WinGetUserSetting } | Should -Throw - } - - AfterAll { - RestoreWinGetSettings - } -} - -Describe 'Test-WinGetUserSetting' { - - It 'Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - - $inputSettings = @{ visual= @{ progressBar="retro"} } - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Equal' { - $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - Test-WinGetUserSetting -UserSettings $ogSettings | Should -Be $true - } - - It 'Equal. Ignore schema' { - Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' - - $inputSettings = @{ visual= @{ progressBar="retro"} } - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $true - } - - It 'Not Equal string' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Not Equal bool' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Not Equal. More settings' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Not Equal. More settings input' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false - } - - It 'Equal IgnoreNotSet' { - $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - Test-WinGetUserSetting -UserSettings $ogSettings -IgnoreNotSet | Should -Be $true - } - - It 'Equal IgnoreNotSet. More settings' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $true - } - - It 'Not Equal IgnoreNotSet. More settings input' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - It 'Not Equal bool IgnoreNotSet' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - It 'Not Equal array IgnoreNotSet' { - $ogSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "x64")} }} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "arm64")} }} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - It 'Not Equal wrong type IgnoreNotSet' { - $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=4 ; experimentalCmd=$true}} - Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false - } - - AfterAll { - RestoreWinGetSettings - } -} - -Describe 'Set-WinGetUserSetting' { - - It 'Overwrites' { - $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} - $result = Set-WinGetUserSetting -UserSettings $inputSettings - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.visual.progressBar | Should -Be "rainbow" - $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.experimentalFeatures.experimentalArg | Should -Be $true - $result.experimentalFeatures.experimentalCmd | Should -Be $false - $result.source | Should -BeNullOrEmpty - } - - It 'Merge' { - $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} - SetWinGetSettingsHelper $ogSettings - - $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} - $result = Set-WinGetUserSetting -UserSettings $inputSettings -Merge - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.visual.progressBar | Should -Be "rainbow" - $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.experimentalFeatures.experimentalArg | Should -Be $true - $result.experimentalFeatures.experimentalCmd | Should -Be $false - $result.source | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.source.autoUpdateIntervalInMinutes | Should -Be 3 - } - - It 'Schema.' { - Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' - - $inputSettings = @{ visual= @{ progressBar="retro"} } - $result = Set-WinGetUserSetting -UserSettings $inputSettings - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual.progressBar | Should -Be "retro" - } - - It 'Overwrites Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - - $inputSettings = @{ visual= @{ progressBar="retro"} } - $result = Set-WinGetUserSetting -UserSettings $inputSettings - - $result | Should -Not -BeNullOrEmpty -ErrorAction Stop - $result.'$schema' | Should -Not -BeNullOrEmpty - $result.visual.progressBar | Should -Be "retro" - } - - It 'Overwrites Bad json file' { - Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." - - $inputSettings = @{ visual= @{ progressBar="retro"} } - { Set-WinGetUserSetting -UserSettings $inputSettings -Merge } | Should -Throw - } - - AfterAll { - RestoreWinGetSettings - } -} - -Describe 'Get|Enable|Disable-WinGetSetting' { - - It 'Get-WinGetSetting' { - $settings = Get-WinGetSetting - $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop - $settings.'$schema' | Should -Not -BeNullOrEmpty - $settings.adminSettings | Should -Not -BeNullOrEmpty - $settings.userSettingsFile | Should -Be $settingsFilePath - } - - # This tests require admin - It 'Enable|Disable' { - $settings = Get-WinGetSetting - $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop - $settings.adminSettings | Should -Not -BeNullOrEmpty - $settings.adminSettings.LocalManifestFiles | Should -Be $false - - Enable-WinGetSetting -Name LocalManifestFiles - - $afterEnable = Get-WinGetSetting - $afterEnable | Should -Not -BeNullOrEmpty -ErrorAction Stop - $afterEnable.adminSettings | Should -Not -BeNullOrEmpty - $afterEnable.adminSettings.LocalManifestFiles | Should -Be $true - - Disable-WingetSetting -Name LocalManifestFiles - - $afterDisable = Get-WinGetSetting - $afterDisable | Should -Not -BeNullOrEmpty -ErrorAction Stop - $afterDisable.adminSettings | Should -Not -BeNullOrEmpty - $afterDisable.adminSettings.LocalManifestFiles | Should -Be $false - } -} - -Describe 'Test-GroupPolicies' { - BeforeAll { - CleanupGroupPolicies - CreatePolicyKeyIfNotExists - } - - It "Disable WinGetPolicy and run Get-WinGetVersion" { - $policyKeyValueName = "EnableAppInstaller" - - Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 - $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName - $registryKey | Should -Not -BeNullOrEmpty - $registryKey.EnableAppInstaller | Should -Be 0 - - { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager" - - CleanupGroupPolicies - } - - It "Disable EnableWindowsPackageManagerCommandLineInterfaces Policy and run Get-WinGetVersion" { - $policyKeyValueName = "EnableWindowsPackageManagerCommandLineInterfaces" - - Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 - $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName - $registryKey | Should -Not -BeNullOrEmpty - $registryKey.EnableWindowsPackageManagerCommandLineInterfaces | Should -Be 0 - - { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager command line interfaces" - - CleanupGroupPolicies - } - - AfterAll { - CleanupGroupPolicies - CleanupGroupPolicyKeyIfExists - } -} - -Describe 'WindowsPackageManagerServer' -Skip:($PSEdition -eq "Desktop") { - - BeforeEach { - AddTestSource - WaitForWindowsPackageManagerServer $true - } - - # When WindowsPackageManagerServer dies, we should not fail. - It 'Forced termination' { - $source = Get-WinGetSource -Name 'TestSource' - $source | Should -Not -BeNullOrEmpty - $source.Name | Should -Be 'TestSource' - - $process = Get-Process -Name "WindowsPackageManagerServer" - $process | Should -Not -BeNullOrEmpty - - # At least one is running. - $process | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty - - WaitForWindowsPackageManagerServer $true - - # From the ones we got, at least one exited - $process | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty - - $source2 = Get-WinGetSource -Name 'TestSource' - $source2 | Should -Not -BeNullOrEmpty - $source2.Name | Should -Be 'TestSource' - - $process2 = Get-Process -Name "WindowsPackageManagerServer" - $process2 | Should -Not -BeNullOrEmpty - $process2.Id | Should -Not -Be $process.Id - } - - # The Microsoft.WinGet.Client has static proxy objects of WindowsPackageManagerServer - # This tests does all the Microsoft.WinGet.Client calls in a different pwsh instance. - It 'Graceful termination' { - $typeTable = [System.Management.Automation.Runspaces.TypeTable]::LoadDefaultTypeFiles() - $oopRunspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateOutOfProcessRunspace($typeTable) - $oopRunspace.Open() - $oopPwsh = [PowerShell]::Create() - $oopPwsh.Runspace = $oopRunspace - $oopPwshPid = $oopPwsh.AddScript("`$PID").Invoke() - $oopPwshProcess = Get-Process -Id $oopPwshPid - $oopPwshProcess.HasExited | Should -Be $false - - $source = $oopPwsh.AddScript("Get-WinGetSource -Name TestSource").Invoke() - $source | Should -Not -BeNullOrEmpty - $source.Name | Should -Be 'TestSource' - - $wingetProcess = Get-Process -Name "WindowsPackageManagerServer" - $wingetProcess | Should -Not -BeNullOrEmpty - - # At least one is running. - $wingetProcess | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty - - $oopRunspace.Close() - - Start-Sleep -Seconds 30 - $oopPwshProcess.HasExited | Should -Be $true - - # From the ones we got, at least one exited - WaitForWindowsPackageManagerServer - $wingetProcess | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty - } -} - -AfterAll { - RestoreWinGetSettings - RemoveTestSource -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Pester tests related to the Microsoft.WinGet.Client PowerShell module. + The tests require the localhost web server to be running and serving the test data. + 'Invoke-Pester' should be called in an admin PowerShell window. +#> +[CmdletBinding()] +param( + # Whether to use production or developement targets. + [switch]$TargetProduction +) + +BeforeAll { + if ($TargetProduction) + { + $wingetExeName = "winget.exe" + } + else + { + $wingetExeName = "wingetdev.exe" + } + + $settingsFilePath = (ConvertFrom-Json (& $wingetExeName settings export)).userSettingsFile + $originalSettingsContent = Get-Content -Path $settingsFilePath -Raw + + $deviceGroupPolicyRoot = "HKLM:\Software\Policies\Microsoft\Windows" + $wingetPolicyKeyName = "AppInstaller" + $wingetGroupPolicyRegistryRoot = $deviceGroupPolicyRoot + "\" + $wingetPolicyKeyName + + Import-Module Microsoft.WinGet.Client + + function SetWinGetSettingsHelper($settings) { + $content = ConvertTo-Json $settings -Depth 4 + Set-Content -Path $settingsFilePath -Value $content + } + + function RestoreWinGetSettings() { + Set-Content -Path $settingsFilePath -Value $originalSettingsContent + } + + # Source Add requires admin privileges, this will only execute successfully in an elevated PowerShell. + function AddTestSource { + try { + Get-WinGetSource -Name 'TestSource' + } + catch { + Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' + } + } + + # This is a workaround to an issue where the server takes longer than expected to terminate when + # running from PowerShell. This can cause other E2E tests to fail when attempting to reset the test source. + function RemoveTestSource { + try { + # Source Remove requires admin privileges, this will only execute successfully in an elevated PowerShell. + $testSource = Get-WinGetSource | Where-Object -Property 'Name' -eq 'TestSource' + if ($null -ne $testSource) + { + # Source Remove requires admin privileges + Remove-WinGetSource -Name 'TestSource' + } + } + catch { + # Non-admin + Start-Process -FilePath $wingetExeName -ArgumentList "source remove TestSource" + } + } + + function CreatePolicyKeyIfNotExists() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if(-Not($registryExists)) + { + New-Item -Path $deviceGroupPolicyRoot -Name $wingetPolicyKeyName + } + } + + function CleanupGroupPolicyKeyIfExists() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if($registryExists) + { + Remove-Item -Path $wingetGroupPolicyRegistryRoot -Recurse + } + } + + function CleanupGroupPolicies() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if($registryExists) + { + Remove-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name * + } + } + + function WaitForWindowsPackageManagerServer([bool]$force = $false) + { + $processes = Get-Process | Where-Object { $_.Name -eq "WindowsPackageManagerServer" } + foreach ($p in $processes) + { + if ($force) + { + Stop-Process $p + } + + $timeout = 300 + $secondsToWait = 5 + $time = 0 + while ($p.HasExited -eq $false) + { + $time += $secondsToWait + if ($time -ge $timeout ) + { + throw "Timeout waiting for $($p.Id) to exit" + } + Start-Sleep -Seconds 5 + } + } + } + + function GetRandomTestDirectory() + { + return Join-Path -Path $env:Temp -ChildPath "WingetPwshTest-$(New-Guid)" + } + + function Validate-WinGetResultCommonFields([psobject]$result, [psobject]$expected) { + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be $expected.Id + $result.Name | Should -Be $expected.Name + $result.Source | Should -Be $expected.Source + $result.Status | Should -Be $expected.Status + } + + function Validate-WinGetPackageOperationResult([psobject]$result, [psobject]$expected, [string]$operationType) + { + Validate-WinGetResultCommonFields $result $expected + $result.RebootRequired | Should -Be $expected.RebootRequired + + switch ($operationType) { + 'install' { + $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode + } + 'update' { + $result.InstallerErrorCode | Should -Be $expected.InstallerErrorCode + } + 'repair' { + $result.RepairErrorCode | Should -Be $expected.RepairErrorCode + } + 'uninstall' { + $result.UninstallerErrorCode | Should -Be $expected.UninstallerErrorCode + } + default { + throw "Unknown operation type: $operationType" + } + } + } +} + +Describe 'Get-WinGetVersion' { + + It 'Get-WinGetVersion' { + $version = Get-WinGetVersion + $version | Should -Not -BeNullOrEmpty -ErrorAction Stop + } +} + +Describe 'Reset-WinGetSource' { + BeforeAll { + AddTestSource + } + + # Requires admin + It 'Resets all sources' { + Reset-WinGetSource -All + } + + It 'Test source should be removed' { + { Get-WinGetSource -Name 'TestSource' } | Should -Throw + } +} + +Describe 'Get|Add|Reset-WinGetSource' { + + BeforeAll { + $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} + SetWinGetSettingsHelper $ogSettings + + Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -TrustLevel 'Trusted' -Explicit -Priority 42 + } + + It 'Get Test source' { + $source = Get-WinGetSource -Name 'TestSource' + + $source | Should -Not -BeNullOrEmpty -ErrorAction Stop + $source.Name | Should -Be 'TestSource' + $source.Argument | Should -Be 'https://localhost:5001/TestKit/' + $source.Type | Should -Be 'Microsoft.PreIndexed.Package' + $source.TrustLevel | Should -Be 'Trusted' + $source.Explicit | Should -Be $true + $source.Priority | Should -Be 42 + } + + It 'Get fake source' { + { Get-WinGetSource -Name 'Fake' } | Should -Throw + } + + # This tests require admin + It 'Reset Test source' { + Reset-WinGetSource -Name TestSource + } + + AfterAll { + RemoveTestSource + RestoreWinGetSettings + } +} + +Describe 'Find-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Given no parameters, lists all available packages' { + $allPackages = Find-WinGetPackage -Source TestSource + $allPackages.Count | Should -BeGreaterThan 0 + } + + It 'Find by Id' { + $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestExampleInstaller' + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package.Name | Should -Be 'TestExampleInstaller' + $package.Id | Should -Be 'AppInstallerTest.TestExampleInstaller' + $package.Version | Should -Be '1.2.3.4' + $package.Source | Should -Be 'TestSource' + } + + It 'Find by Name' { + $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package[0].Name | Should -Be 'TestPortableExe' + $package[1].Name | Should -Be 'TestPortableExeWithCommand' + } + + It 'Find by Name sort by Version' { + $package = Find-WinGetPackage -Source 'TestSource' -Name 'TestPortableExe' | Sort-Object 'Version' + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package[0].Name | Should -Be 'TestPortableExeWithCommand' + $package[1].Name | Should -Be 'TestPortableExe' + } + + It 'Find package and verify PackageVersionInfo' { + $package = Find-WinGetPackage -Source 'TestSource' -Id 'AppInstallerTest.TestPortableExe' -MatchOption Equals + + $package | Should -Not -BeNullOrEmpty -ErrorAction Stop + $package.AvailableVersions[0] | Should -Be '3.0.0.0' + $package.AvailableVersions[1] | Should -Be '2.0.0.0' + $package.AvailableVersions.Count | Should -Be 4 + + $packageVersionInfo = $package.GetPackageVersionInfo("3.0.0.0") + $packageVersionInfo.DisplayName | Should -Be 'TestPortableExe' + $packageVersionInfo.Id | Should -Be 'AppInstallerTest.TestPortableExe' + $packageVersionInfo.CompareToVersion("2.0.0.0") | Should -Be 'Greater' + $packageVersionInfo.CompareToVersion("4.0.0.0") | Should -Be 'Lesser' + } +} + +Describe 'Install|Update|Uninstall-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + BeforeEach { + $expectedExeInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestExeInstaller" + Name = "TestExeInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } + + $expectedPortableInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestPortableExe" + Name = "TestPortableExe" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install by Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' + } + + It 'Install by exact Name and Version' { + $result = Install-WinGetPackage -Name TestPortableExe -Version '2.0.0.0' -MatchOption Equals + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'install' + } + + It 'Update by Id' { + $result = Update-WinGetPackage -Id AppInstallerTest.TestExeInstaller + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'update' + } + + It 'Update by Name' { + $result = Update-WinGetPackage -Name TestPortableExe + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'update' + } + + It 'Uninstall by Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'uninstall' + } + + It 'Uninstall by Name' { + $result = Uninstall-WinGetPackage -Name TestPortableExe + Validate-WinGetPackageOperationResult $result $expectedPortableInstallerResult 'uninstall' + } + + AfterAll { + # Uninstall all test packages after each for proper cleanup. + $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + } + + $testPortable = Get-WinGetPackage -Id AppInstallerTest.TestPortableExe -MatchOption Equals + if ($testPortable.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestPortableExe + } + } +} + +Describe 'Install|Repair|Uninstall-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + Context 'MSIX Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestMsixInstaller" + Name = "TestMsixInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install MSIX By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Repair MSIX By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It 'Uninstall MSIX By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + Context 'Burn installer "Modify" Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestModifyRepair" + Name = "TestModifyRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install Burn Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Repair Burn Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It 'Uninstall Burn Installer By Id' { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + Context 'Exe Installer "Uninstaller" Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.UninstallerRepair" + Name = "UninstallerRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install Exe Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.UninstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Uninstaller Repair Exe Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.UninstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It "Uninstall Exe Installer By Id" { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + Context 'Inno "Installer" Repair Scenario' { + BeforeEach { + $expectedResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestInstallerRepair" + Name = "TestInstallerRepair" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + RepairErrorCode = 0 + UninstallerErrorCode = 0 + } + } + + It 'Install Exe Installer By Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'install' + } + + It 'Installer Repair Exe Installer By Id' { + $result = Repair-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'repair' + } + + It "Uninstall Exe Installer By Id" { + $result = Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + Validate-WinGetPackageOperationResult $result $expectedResult 'uninstall' + } + } + + AfterAll { + # Uninstall all test packages after each for proper cleanup. + $testMsix = Get-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + if ($testMsix.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestMsixInstaller + } + + $testBurn = Get-WinGetPackage -Id AppInstallerTest.TestModifyRepair + if ($testBurn.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestModifyRepair + } + + $testExe = Get-WinGetPackage -Id AppInstallerTest.UninstallerRepair + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.UninstallerRepair + } + + $testInno = Get-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + if ($testInno.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestInstallerRepair + } + } +} + +Describe 'Install-WinGetPackage Source Priority' { + + It 'Install equal Priority' { + AddTestSource + Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' + + { Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' } | Should -Throw + } + + It 'Install higher Priority' { + $ogSettings = @{ experimentalFeatures= @{sourcePriority=$true}} + SetWinGetSettingsHelper $ogSettings + + RemoveTestSource + Add-WinGetSource -Name 'TestSource' -Arg 'https://localhost:5001/TestKit/' -Priority 1 + Add-WinGetSource -Name 'dummyPackageSource' -Type 'Microsoft.Test.Configurable' -Arg '{"ContainsPackage":true}' + + $expectedExeInstallerResult = [PSCustomObject]@{ + Id = "AppInstallerTest.TestExeInstaller" + Name = "TestExeInstaller" + Source = "TestSource" + Status = 'Ok' + RebootRequired = 'False' + InstallerErrorCode = 0 + UninstallerErrorCode = 0 + } + + $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + Validate-WinGetPackageOperationResult $result $expectedExeInstallerResult 'install' + } + + AfterEach { + $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller -MatchOption Equals + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + } + + Remove-WinGetSource -Name 'dummyPackageSource' + RemoveTestSource + RestoreWinGetSettings + } +} + +Describe 'Get-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Install by Id' { + $result = Install-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.InstallerErrorCode | Should -Be 0 + $result.Status | Should -Be 'Ok' + $result.RebootRequired | Should -Be 'False' + } + + It 'Get package by Id' { + $result = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Name | Should -Be 'TestExeInstaller' + $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' + $result.InstalledVersion | Should -Be '1.0.0.0' + $result.Source | Should -Be 'TestSource' + $result.AvailableVersions[0] | Should -Be '2.0.0.0' + } + + It 'Get package by Name' { + $result = Get-WinGetPackage -Name TestExeInstaller + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.Name | Should -Be 'TestExeInstaller' + $result.Id | Should -Be 'AppInstallerTest.TestExeInstaller' + $result.InstalledVersion | Should -Be '1.0.0.0' + $result.Source | Should -Be 'TestSource' + $result.AvailableVersions[0] | Should -Be '2.0.0.0' + } + + AfterAll { + # Uninstall all test packages after each for proper cleanup. + $testExe = Get-WinGetPackage -Id AppInstallerTest.TestExeInstaller + if ($testExe.Count -gt 0) + { + Uninstall-WinGetPackage -Id AppInstallerTest.TestExeInstaller + } + } +} + +Describe 'Export-WinGetPackage' { + + BeforeAll { + AddTestSource + } + + It 'Download by Id' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + # Download directory should be created and have exactly two files (installer and manifest file). + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by Locale' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -Locale 'zh-CN' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" + $result.Name | Should -Be "TestMultipleInstallers" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by InstallerType' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestMultipleInstallers -InstallerType 'msi' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestMultipleInstallers" + $result.Name | Should -Be "TestMultipleInstallers" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + It 'Download by InstallerType that does not exist' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1.0.0.0' -InstallerType 'zip' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'NoApplicableInstallers' + $result.ExtendedErrorCode | Should -Not -BeNullOrEmpty + Test-Path -Path $testDirectory | Should -Be $false + } + + It 'Download with short Version' { + $testDirectory = GetRandomTestDirectory + $result = Export-WinGetPackage -Id AppInstallerTest.TestExeInstaller -Version '1' -DownloadDirectory $testDirectory + + $result | Should -Not -BeNullOrEmpty + $result.Id | Should -Be "AppInstallerTest.TestExeInstaller" + $result.Name | Should -Be "TestExeInstaller" + $result.Source | Should -Be "TestSource" + $result.Status | Should -Be 'Ok' + + # Download directory should be created and have exactly two files (installer and manifest file). + Test-Path -Path $testDirectory | Should -Be $true + (Get-ChildItem -Path $testDirectory -Force | Measure-Object).Count | Should -Be 2 + } + + AfterEach { + if (Test-Path $testDirectory) { + Remove-Item $testDirectory -Force -Recurse + } + } +} + +Describe 'Get-WinGetUserSetting' { + + It 'Get setting' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $userSettings = Get-WinGetUserSetting + $userSettings | Should -Not -BeNullOrEmpty -ErrorAction Stop + $userSettings.Count | Should -Be 2 + $userSettings.visual.progressBar | Should -Be 'rainbow' + $userSettings.experimentalFeatures.experimentalArg | Should -Be $false + $userSettings.experimentalFeatures.experimentalCmd | Should -Be $true + } + + It 'Get settings. Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + { Get-WinGetUserSetting } | Should -Throw + } + + AfterAll { + RestoreWinGetSettings + } +} + +Describe 'Test-WinGetUserSetting' { + + It 'Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + + $inputSettings = @{ visual= @{ progressBar="retro"} } + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Equal' { + $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + Test-WinGetUserSetting -UserSettings $ogSettings | Should -Be $true + } + + It 'Equal. Ignore schema' { + Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' + + $inputSettings = @{ visual= @{ progressBar="retro"} } + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $true + } + + It 'Not Equal string' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Not Equal bool' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Not Equal. More settings' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Not Equal. More settings input' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings | Should -Be $false + } + + It 'Equal IgnoreNotSet' { + $ogSettings = @{ visual= @{ progressBar="retro"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + Test-WinGetUserSetting -UserSettings $ogSettings -IgnoreNotSet | Should -Be $true + } + + It 'Equal IgnoreNotSet. More settings' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false }} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $true + } + + It 'Not Equal IgnoreNotSet. More settings input' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + It 'Not Equal bool IgnoreNotSet' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$false ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + It 'Not Equal array IgnoreNotSet' { + $ogSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "x64")} }} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ installBehavior= @{ preferences= @{ architectures = @("x86", "arm64")} }} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + It 'Not Equal wrong type IgnoreNotSet' { + $ogSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$true}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=4 ; experimentalCmd=$true}} + Test-WinGetUserSetting -UserSettings $inputSettings -IgnoreNotSet | Should -Be $false + } + + AfterAll { + RestoreWinGetSettings + } +} + +Describe 'Set-WinGetUserSetting' { + + It 'Overwrites' { + $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} + $result = Set-WinGetUserSetting -UserSettings $inputSettings + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.visual.progressBar | Should -Be "rainbow" + $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.experimentalFeatures.experimentalArg | Should -Be $true + $result.experimentalFeatures.experimentalCmd | Should -Be $false + $result.source | Should -BeNullOrEmpty + } + + It 'Merge' { + $ogSettings = @{ source= @{ autoUpdateIntervalInMinutes=3}} + SetWinGetSettingsHelper $ogSettings + + $inputSettings = @{ visual= @{ progressBar="rainbow"} ; experimentalFeatures= @{experimentalArg=$true ; experimentalCmd=$false}} + $result = Set-WinGetUserSetting -UserSettings $inputSettings -Merge + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.visual.progressBar | Should -Be "rainbow" + $result.experimentalFeatures | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.experimentalFeatures.experimentalArg | Should -Be $true + $result.experimentalFeatures.experimentalCmd | Should -Be $false + $result.source | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.source.autoUpdateIntervalInMinutes | Should -Be 3 + } + + It 'Schema.' { + Set-Content -Path $settingsFilePath -Value '{ "$schema": "https://aka.ms/winget-settings.schema.json", "visual": { "progressBar": "retro" } }' + + $inputSettings = @{ visual= @{ progressBar="retro"} } + $result = Set-WinGetUserSetting -UserSettings $inputSettings + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual.progressBar | Should -Be "retro" + } + + It 'Overwrites Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + + $inputSettings = @{ visual= @{ progressBar="retro"} } + $result = Set-WinGetUserSetting -UserSettings $inputSettings + + $result | Should -Not -BeNullOrEmpty -ErrorAction Stop + $result.'$schema' | Should -Not -BeNullOrEmpty + $result.visual.progressBar | Should -Be "retro" + } + + It 'Overwrites Bad json file' { + Set-Content -Path $settingsFilePath -Value "Hi, im not a json. Thank you, Test." + + $inputSettings = @{ visual= @{ progressBar="retro"} } + { Set-WinGetUserSetting -UserSettings $inputSettings -Merge } | Should -Throw + } + + AfterAll { + RestoreWinGetSettings + } +} + +Describe 'Get|Enable|Disable-WinGetSetting' { + + It 'Get-WinGetSetting' { + $settings = Get-WinGetSetting + $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop + $settings.'$schema' | Should -Not -BeNullOrEmpty + $settings.adminSettings | Should -Not -BeNullOrEmpty + $settings.userSettingsFile | Should -Be $settingsFilePath + } + + # This tests require admin + It 'Enable|Disable' { + $settings = Get-WinGetSetting + $settings | Should -Not -BeNullOrEmpty -ErrorAction Stop + $settings.adminSettings | Should -Not -BeNullOrEmpty + $settings.adminSettings.LocalManifestFiles | Should -Be $false + + Enable-WinGetSetting -Name LocalManifestFiles + + $afterEnable = Get-WinGetSetting + $afterEnable | Should -Not -BeNullOrEmpty -ErrorAction Stop + $afterEnable.adminSettings | Should -Not -BeNullOrEmpty + $afterEnable.adminSettings.LocalManifestFiles | Should -Be $true + + Disable-WingetSetting -Name LocalManifestFiles + + $afterDisable = Get-WinGetSetting + $afterDisable | Should -Not -BeNullOrEmpty -ErrorAction Stop + $afterDisable.adminSettings | Should -Not -BeNullOrEmpty + $afterDisable.adminSettings.LocalManifestFiles | Should -Be $false + } +} + +Describe 'Test-GroupPolicies' { + BeforeAll { + CleanupGroupPolicies + CreatePolicyKeyIfNotExists + } + + It "Disable WinGetPolicy and run Get-WinGetVersion" { + $policyKeyValueName = "EnableAppInstaller" + + Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 + $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName + $registryKey | Should -Not -BeNullOrEmpty + $registryKey.EnableAppInstaller | Should -Be 0 + + { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager" + + CleanupGroupPolicies + } + + It "Disable EnableWindowsPackageManagerCommandLineInterfaces Policy and run Get-WinGetVersion" { + $policyKeyValueName = "EnableWindowsPackageManagerCommandLineInterfaces" + + Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 + $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName + $registryKey | Should -Not -BeNullOrEmpty + $registryKey.EnableWindowsPackageManagerCommandLineInterfaces | Should -Be 0 + + { Get-WinGetVersion } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager command line interfaces" + + CleanupGroupPolicies + } + + AfterAll { + CleanupGroupPolicies + CleanupGroupPolicyKeyIfExists + } +} + +Describe 'WindowsPackageManagerServer' -Skip:($PSEdition -eq "Desktop") { + + BeforeEach { + AddTestSource + WaitForWindowsPackageManagerServer $true + } + + # When WindowsPackageManagerServer dies, we should not fail. + It 'Forced termination' { + $source = Get-WinGetSource -Name 'TestSource' + $source | Should -Not -BeNullOrEmpty + $source.Name | Should -Be 'TestSource' + + $process = Get-Process -Name "WindowsPackageManagerServer" + $process | Should -Not -BeNullOrEmpty + + # At least one is running. + $process | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty + + WaitForWindowsPackageManagerServer $true + + # From the ones we got, at least one exited + $process | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty + + $source2 = Get-WinGetSource -Name 'TestSource' + $source2 | Should -Not -BeNullOrEmpty + $source2.Name | Should -Be 'TestSource' + + $process2 = Get-Process -Name "WindowsPackageManagerServer" + $process2 | Should -Not -BeNullOrEmpty + $process2.Id | Should -Not -Be $process.Id + } + + # The Microsoft.WinGet.Client has static proxy objects of WindowsPackageManagerServer + # This tests does all the Microsoft.WinGet.Client calls in a different pwsh instance. + It 'Graceful termination' { + $typeTable = [System.Management.Automation.Runspaces.TypeTable]::LoadDefaultTypeFiles() + $oopRunspace = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateOutOfProcessRunspace($typeTable) + $oopRunspace.Open() + $oopPwsh = [PowerShell]::Create() + $oopPwsh.Runspace = $oopRunspace + $oopPwshPid = $oopPwsh.AddScript("`$PID").Invoke() + $oopPwshProcess = Get-Process -Id $oopPwshPid + $oopPwshProcess.HasExited | Should -Be $false + + $source = $oopPwsh.AddScript("Get-WinGetSource -Name TestSource").Invoke() + $source | Should -Not -BeNullOrEmpty + $source.Name | Should -Be 'TestSource' + + $wingetProcess = Get-Process -Name "WindowsPackageManagerServer" + $wingetProcess | Should -Not -BeNullOrEmpty + + # At least one is running. + $wingetProcess | Where-Object { $_.HasExited -eq $false } | Should -Not -BeNullOrEmpty + + $oopRunspace.Close() + + Start-Sleep -Seconds 30 + $oopPwshProcess.HasExited | Should -Be $true + + # From the ones we got, at least one exited + WaitForWindowsPackageManagerServer + $wingetProcess | Where-Object { $_.HasExited -eq $true } | Should -Not -BeNullOrEmpty + } +} + +AfterAll { + RestoreWinGetSettings + RemoveTestSource +} diff --git a/src/PowerShell/tests/Microsoft.WinGet.Configuration.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.Configuration.Tests.ps1 index a1197b3b74..47eed2ea26 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.Configuration.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.Configuration.Tests.ps1 @@ -1,1050 +1,1050 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# -.Synopsis - Pester tests related to the Microsoft.WinGet.Configuration PowerShell module. - 'Invoke-Pester' should be called in an admin PowerShell window. - Requires local test repo to be setup. -#> -[CmdletBinding()] -param( - # The location of the test data - [string]$ConfigurationTestDataPath -) - -BeforeAll { - $env:POWERSHELL_TELEMETRY_OPTOUT = "true" - $deviceGroupPolicyRoot = "HKLM:\Software\Policies\Microsoft\Windows" - $wingetPolicyKeyName = "AppInstaller" - $wingetGroupPolicyRegistryRoot = $deviceGroupPolicyRoot + "\" + $wingetPolicyKeyName - $e2eTestModule = "xE2ETestResource" - - Import-Module Microsoft.WinGet.Configuration - - # TODO: Installing within the test run on the build server (only) somehow causes below error: - # [CORE] Started MSStore package execution. ProductId: 9PCX3HX4HZ0Z PackageFamilyName: Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe - # [CLI ] MSStore install failed. ProductId: 9PCX3HX4HZ0Z HResult: 0x80010002 - # So install the DSCv3 package here. - Import-Module Microsoft.WinGet.Client - # We prefer to use preview (9PCX3HX4HZ0Z) to catch issues early, but if it causes blocking use stable (9NVTPZWRC6KQ) until it is resolved. - $installResult = Install-WingetPackage -Id 9NVTPZWRC6KQ -Source msstore - if ($installResult.Status -ne 'Ok') - { - Write-Error "Failed to install DSCv3 package. Status: $($installResult.Status). ExtendedErrorCode: $($installResult.ExtendedErrorCode)." -ErrorAction Stop - } - - function CreatePolicyKeyIfNotExists() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if(-Not($registryExists)) - { - New-Item -Path $deviceGroupPolicyRoot -Name $wingetPolicyKeyName - } - } - - function CleanupGroupPolicyKeyIfExists() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if($registryExists) - { - Remove-Item -Path $wingetGroupPolicyRegistryRoot -Recurse - } - } - - function CleanupGroupPolicies() - { - $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot - - if($registryExists) - { - Remove-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name * - } - } - - if ([System.String]::IsNullOrEmpty($ConfigurationTestDataPath)) - { - $ConfigurationTestDataPath = (Join-Path $PSScriptRoot "..\..\AppInstallerCLIE2ETests\TestData\Configuration\") - } - - function GetConfigTestDataPath() - { - return $ConfigurationTestDataPath - } - - function DeleteConfigTxtFiles() - { - Get-ChildItem $(GetConfigTestDataPath) -Filter *.txt -Recurse | ForEach-Object { Remove-Item $_ } - } - - function GetConfigTestDataFile([string] $fileName) - { - $path = Join-Path $(GetConfigTestDataPath) $fileName - - if (-not (Test-Path $path)) - { - throw "$path does not exists" - } - - return $path - } - - enum TestModuleLocation - { - CurrentUser - AllUsers - Custom - DefaultLocation - } - - function GetExpectedModulePath([TestModuleLocation]$testModuleLocation) - { - switch ($testModuleLocation) - { - ([TestModuleLocation]::CurrentUser) - { - $path = [Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments) - return Join-Path $path "PowerShell\Modules" - } - ([TestModuleLocation]::AllUsers) - { - $path = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFiles) - return Join-Path $path "PowerShell\Modules" - } - ([TestModuleLocation]::DefaultLocation) - { - $path = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData) - return Join-Path $path "Microsoft\WinGet\Configuration\Modules" - } - ([TestModuleLocation]::Custom) - { - return Join-Path $env:TEMP "E2EPesterCustomModules" - } - default - { - throw $testModuleLocation - } - } - } - - function CleanupPsModulePath() - { - $wingetPath = GetExpectedModulePath DefaultLocation - $customPath = GetExpectedModulePath Custom - $modulePath = $env:PsModulePath - $newModulePath = ($modulePath.Split(';') | Where-Object { $_ -ne $wingetPath } | Where-Object { $_ -ne $customPath }) -join ';' - $env:PsModulePath = $newModulePath - } - - function EnsureModuleState([string]$moduleName, [bool]$present, [string]$repository = $null, [TestModuleLocation]$testModuleLocation = [TestModuleLocation]::CurrentUser) - { - CleanupPsModulePath - $wingetPath = GetExpectedModulePath DefaultLocation - $customPath = GetExpectedModulePath Custom - $env:PsModulePath += ";$wingetPath;$customPath" - - $availableModules = Get-Module $moduleName -ListAvailable - $isPresent = $null -ne $availableModules - - if ($isPresent) - { - foreach ($module in $availableModules) - { - try - { - $item = Get-Item $module.Path -ErrorAction Stop - while ($item.Name -ne $moduleName) - { - $item = Get-Item $item.PSParentPath -ErrorAction Stop - } - - if (-not $present) - { - Get-ChildItem $item.FullName -Recurse | Remove-Item -Force -Recurse -ErrorAction Stop - Remove-Item $item -ErrorAction Stop - } - else - { - # Must be in the right location - $expected = GetExpectedModulePath $testModuleLocation - if ($expected -ne $item.Parent.FullName) - { - Get-ChildItem $item.FullName -Recurse | Remove-Item -Force -Recurse -ErrorAction Stop - Remove-Item $item -ErrorAction Stop - $isPresent = $false - } - } - } - catch [System.Management.Automation.ItemNotFoundException] - { - Write-Host "Item not found, ignoring..." $_.Exception.Message - } - } - } - - if ((-not $isPresent) -and $present) - { - $params = @{ - Name = $moduleName - Force = $true - } - - if (-not [string]::IsNullOrEmpty($repository)) - { - $params.Add('Repository', $repository) - } - - if (($testModuleLocation -eq [TestModuleLocation]::CurrentUser) -or - ($testModuleLocation -eq [TestModuleLocation]::AllUsers)) - { - if ($testModuleLocation -eq [TestModuleLocation]::AllUsers) - { - $params.Add('Scope', 'AllUsers') - } - - Install-Module @params - } - else - { - $path = $customPath - if (($testModuleLocation -eq [TestModuleLocation]::WinGetModulePath) -or - ($testModuleLocation -eq [TestModuleLocation]::DefaultLocation)) - { - $path = $wingetPath - } - $params.Add('Path', $path) - - Save-Module @params - } - } - - CleanupPsModulePath - } - - function EnsureDSCv3TestResourcePresence() - { - $localAppDataPath = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData) - $resourcePath = Join-Path $localAppDataPath "Microsoft\WindowsApps\test-file.dsc.resource.json" - if (-not (Test-Path $resourcePath)) - { - wingetdev dscv3 test-file --manifest -o $resourcePath - } - } -} - -Describe 'Test-GroupPolicies' { - BeforeAll { - CleanupGroupPolicies - CreatePolicyKeyIfNotExists - } - - It "Disable WinGetPolicy and run Get-WinGetConfiguration" { - - $policyKeyValueName = "EnableAppInstaller" - - Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 - $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName - $registryKey | Should -Not -BeNullOrEmpty - $registryKey.EnableAppInstaller | Should -Be 0 - - # [NOTE:] We don't need a valid yml file path to test Group Policy blocking scenario as it is the earliest check, - # so just using some random file path for this test. - { Get-WinGetConfiguration -File "Z:\NonExisting_SettingsFile.yml" } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager" - - CleanupGroupPolicies - } - - It "Disable EnableWindowsPackageManagerCommandLineInterfaces Policy and run Get-WinGetConfiguration" { - $policyKeyValueName = "EnableWindowsPackageManagerCommandLineInterfaces" - - Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 - $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName - $registryKey | Should -Not -BeNullOrEmpty - $registryKey.EnableWindowsPackageManagerCommandLineInterfaces | Should -Be 0 - - # [NOTE:] We don't need a valid yml file path to test Group Policy blocking scenario as it is the earliest check, - # so just using some random file path for this test. - { Get-WinGetConfiguration -File "Z:\NonExisting_SettingsFile.yml" } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager command line interfaces" - - CleanupGroupPolicies - } - - It "Disable EnableWindowsPackageManagerConfiguration Policy and run Get-WinGetConfiguration" { - $policyKeyValueName = "EnableWindowsPackageManagerConfiguration" - - Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 - $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName - $registryKey | Should -Not -BeNullOrEmpty - $registryKey.EnableWindowsPackageManagerConfiguration | Should -Be 0 - - # [NOTE:] We don't need a valid yml file path to test Group Policy blocking scenario as it is the earliest check, - # so just using some random file path for this test. - { Get-WinGetConfiguration -File "Z:\NonExisting_SettingsFile.yml" } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager Configuration" - - CleanupGroupPolicies - } - - AfterAll { - CleanupGroupPolicies - CleanupGroupPolicyKeyIfExists - } -} - -Describe 'Get configuration' { - - It 'Get configuration and details' { - EnsureModuleState $e2eTestModule $false - - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $set = Get-WinGetConfigurationDetails -Set $set - $set | Should -Not -BeNullOrEmpty - } - - It 'Get configuration and details DSCv3' { - EnsureDSCv3TestResourcePresence - - $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $set = Get-WinGetConfigurationDetails -Set $set - $set | Should -Not -BeNullOrEmpty - } - - It 'Get details piped' { - EnsureModuleState $e2eTestModule $false - - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile | Get-WinGetConfigurationDetails - $set | Should -Not -BeNullOrEmpty - } - - It 'Get configuration and details positional' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration $testFile - $set | Should -Not -BeNullOrEmpty - - $set = Get-WinGetConfigurationDetails $set - $set | Should -Not -BeNullOrEmpty - } - - It 'File doesnt exit' { - $testFile = "c:\dir\fakeFile.txt" - { Get-WinGetConfiguration -File $testFile } | Should -Throw $testFile - } - - It 'Invalid file' { - $testFile = GetConfigTestDataFile "Empty.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C002*" - } - - It 'Missing property' { - $testFile = GetConfigTestDataFile "NotConfig.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw '*0x8A15C00E*$schema*missing*' - } - - It 'Missing configurationVersion' { - $testFile = GetConfigTestDataFile "NoVersion.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C00E*configurationVersion*missing*" - } - - It 'Unknown version' { - $testFile = GetConfigTestDataFile "UnknownVersion.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C004*Configuration file version*is not known.*" - } - - It 'Resource wrong type' { - $testFile = GetConfigTestDataFile "ResourcesNotASequence.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C003*resources*wrong type*" - } - - It 'Unit wrong type' { - $testFile = GetConfigTestDataFile "UnitNotAMap.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C003*resources*0*wrong type*" - } - - It 'No resource name' { - $testFile = GetConfigTestDataFile "NoResourceName.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C00D*resource*invalid value*Module/*" - } - - It 'Module mismatch' { - $testFile = GetConfigTestDataFile "ModuleMismatch.yml" - { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C00D*invalid value*DifferentModule*" - } -} - -Describe 'Invoke-WinGetConfiguration' { - - BeforeEach { - DeleteConfigTxtFiles - } - -<# PS Gallery tests are unreliable. - It 'From Gallery' { - EnsureModuleState "XmlContentDsc" $false - - $testFile = GetConfigTestDataFile "PSGallery_NoModule_NoSettings.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286075 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be -1978285819 - } -#> - - It 'From TestRepo' { - EnsureModuleState $e2eTestModule $false - - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - - $expectedModule = Join-Path $(GetExpectedModulePath DefaultLocation) $e2eTestModule - Test-Path $expectedModule | Should -Be $true - } - - It 'From TestRepo Location' -ForEach @( - @{ Location = "CurrentUser"; } - @{ Location = "AllUsers"; } - @{ Location = "DefaultLocation"; } - @{ Location = "Custom"; }) { - $modulePath = "'" - switch ($location) - { - ([TestModuleLocation]::CurrentUser) - { - $modulePath = "currentuser" - break - } - ([TestModuleLocation]::AllUsers) - { - $modulePath = "allusers" - break - } - ([TestModuleLocation]::DefaultLocation) - { - $modulePath = "default" - break - } - ([TestModuleLocation]::Custom) - { - $modulePath = GetExpectedModulePath Custom - break - } - default { - throw $location - } - } - - EnsureModuleState $e2eTestModule $false - - $testFile = GetConfigTestDataFile "Configure_TestRepo_Location.yml" - $set = Get-WinGetConfiguration -File $testFile -ModulePath $modulePath - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedModule = Join-Path $(GetExpectedModulePath $location) $e2eTestModule - Test-Path $expectedModule | Should -Be $true - } - - It 'From DSCv3' { - EnsureDSCv3TestResourcePresence - - $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "ShowDetails_DSCv3.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "DSCv3 Contents!" - } - - It 'Piped' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $result = Get-WinGetConfiguration -File $testFile | Invoke-WinGetConfiguration -AcceptConfigurationAgreements - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } - - It 'Positional' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } - - It 'Independent Resource - One Failure' { - $testFile = GetConfigTestDataFile "IndependentResources_OneFailure.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286075 - $result.UnitResults.Count | Should -Be 2 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be -1978285819 - $result.UnitResults[1].State | Should -Be "Completed" - $result.UnitResults[1].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "IndependentResources_OneFailure.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } - - It 'Dependent Resource - Failure' { - $testFile = GetConfigTestDataFile "DependentResources_Failure.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286075 - $result.UnitResults.Count | Should -Be 2 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be -1978285819 - $result.UnitResults[1].State | Should -Be "Skipped" - $result.UnitResults[1].ResultCode | Should -Be -1978286072 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "DependentResources_Failure.txt" - Test-Path $expectedFile | Should -Be $false - } - - It 'ResourceCaseInsensitive' { - $testFile = GetConfigTestDataFile "ResourceCaseInsensitive.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "ResourceCaseInsensitive.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } -} - -Describe 'Start|Complete-WinGetConfiguration' { - - BeforeEach { - DeleteConfigTxtFiles - } - -<# PS Gallery tests are unreliable. - It 'From Gallery' { - EnsureModuleState "XmlContentDsc" $false - - $testFile = GetConfigTestDataFile "PSGallery_NoModule_NoSettings.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration -ConfigurationJob $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286075 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be -1978285819 - } -#> - - It 'From TestRepo' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration -ConfigurationJob $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - - # Verify can't be used after. - { Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set } | Should -Throw "Operation is not valid due to the current state of the object." - } - - It 'From TestRepo Location' -ForEach @( - @{ Location = "CurrentUser"; } - @{ Location = "AllUsers"; } - @{ Location = "DefaultLocation"; } - @{ Location = "Custom"; }) { - $modulePath = "'" - switch ($location) - { - ([TestModuleLocation]::CurrentUser) - { - $modulePath = "currentuser" - break - } - ([TestModuleLocation]::AllUsers) - { - $modulePath = "allusers" - break - } - ([TestModuleLocation]::DefaultLocation) - { - $modulePath = "default" - break - } - ([TestModuleLocation]::Custom) - { - $modulePath = GetExpectedModulePath Custom - break - } - default { - throw $location - } - } - - EnsureModuleState $e2eTestModule $false - - $testFile = GetConfigTestDataFile "Configure_TestRepo_Location.yml" - $set = Get-WinGetConfiguration -File $testFile -ModulePath $modulePath - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration -ConfigurationJob $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedModule = Join-Path $(GetExpectedModulePath $location) $e2eTestModule - Test-Path $expectedModule | Should -Be $true - } - - It 'From DSCv3' { - EnsureDSCv3TestResourcePresence - - $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration -ConfigurationJob $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "ShowDetails_DSCv3.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "DSCv3 Contents!" - - # Verify can't be used after. - { Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set } | Should -Throw "Operation is not valid due to the current state of the object." - } - - It 'Piped' { - DeleteConfigTxtFiles - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $result = Get-WinGetConfiguration -File $testFile | Start-WinGetConfiguration -AcceptConfigurationAgreements | Complete-WinGetConfiguration - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } - - It 'Positional' { - DeleteConfigTxtFiles - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration $testFile - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } - - It 'Independent Resource - One Failure' { - $testFile = GetConfigTestDataFile "IndependentResources_OneFailure.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration -ConfigurationJob $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286075 - $result.UnitResults.Count | Should -Be 2 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be -1978285819 - - $result.UnitResults[1].State | Should -Be "Completed" - $result.UnitResults[1].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "IndependentResources_OneFailure.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } - - It 'Dependent Resource - Failure' { - $testFile = GetConfigTestDataFile "DependentResources_Failure.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration -ConfigurationJob $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286075 - $result.UnitResults.Count | Should -Be 2 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be -1978285819 - $result.UnitResults[1].State | Should -Be "Skipped" - $result.UnitResults[1].ResultCode | Should -Be -1978286072 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "DependentResources_Failure.txt" - Test-Path $expectedFile | Should -Be $false - } - - It 'ResourceCaseInsensitive' { - $testFile = GetConfigTestDataFile "ResourceCaseInsensitive.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $job | Should -Not -BeNullOrEmpty - - $result = Complete-WinGetConfiguration -ConfigurationJob $job - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $expectedFile = Join-Path $(GetConfigTestDataPath) "ResourceCaseInsensitive.txt" - Test-Path $expectedFile | Should -Be $true - Get-Content $expectedFile -Raw | Should -Be "Contents!" - } -} - -Describe 'Test-WinGetConfiguration' { - - BeforeEach { - DeleteConfigTxtFiles - } - - It 'Negative' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.TestResult | Should -Be "Negative" - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].TestResult | Should -Be "Negative" - $result.UnitResults[0].ResultCode | Should -Be 0 - } - - It 'Positive' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" - Set-Content -Path $expectedFile -Value "Contents!" -NoNewline - - $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.TestResult | Should -Be "Positive" - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].TestResult | Should -Be "Positive" - $result.UnitResults[0].ResultCode | Should -Be 0 - } - - It 'Negative DSCv3' { - EnsureDSCv3TestResourcePresence - - $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.TestResult | Should -Be "Negative" - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].TestResult | Should -Be "Negative" - $result.UnitResults[0].ResultCode | Should -Be 0 - } - - It 'Positive DSCv3' { - EnsureDSCv3TestResourcePresence - - $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $expectedFile = Join-Path $(GetConfigTestDataPath) "ShowDetails_DSCv3.txt" - Set-Content -Path $expectedFile -Value "DSCv3 Contents!" -NoNewline - - $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.TestResult | Should -Be "Positive" - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].TestResult | Should -Be "Positive" - $result.UnitResults[0].ResultCode | Should -Be 0 - } - - It 'Piped' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $result = Get-WinGetConfiguration -File $testFile | Test-WinGetConfiguration -AcceptConfigurationAgreements - $result | Should -Not -BeNullOrEmpty - $result.TestResult | Should -Be "Negative" - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].TestResult | Should -Be "Negative" - $result.UnitResults[0].ResultCode | Should -Be 0 - } - - It 'Positional' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Test-WinGetConfiguration -AcceptConfigurationAgreements $set - $result | Should -Not -BeNullOrEmpty - $result.TestResult | Should -Be "Negative" - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].TestResult | Should -Be "Negative" - $result.UnitResults[0].ResultCode | Should -Be 0 - } - - It "Failed" { - $testFile = GetConfigTestDataFile "IndependentResources_OneFailure.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.TestResult | Should -Be "Failed" - $result.UnitResults.Count | Should -Be 2 - $result.UnitResults[0].TestResult | Should -Be "Failed" - $result.UnitResults[0].ResultCode | Should -Be -1978285819 - $result.UnitResults[1].TestResult | Should -Be "Negative" - $result.UnitResults[1].ResultCode | Should -Be 0 - } -} - -Describe 'Confirm-WinGetConfiguration' { - - It 'Duplicate Identifiers' { - $testFile = GetConfigTestDataFile "DuplicateIdentifiers.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Confirm-WinGetConfiguration -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286074 - $result.UnitResults.Count | Should -Be 3 - $result.UnitResults[0].ResultCode | Should -Be -1978286074 - $result.UnitResults[1].ResultCode | Should -Be -1978286074 - $result.UnitResults[2].ResultCode | Should -Be 0 - } - - It 'Missing dependency' { - $testFile = GetConfigTestDataFile "MissingDependency.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Confirm-WinGetConfiguration -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286073 - $result.UnitResults.Count | Should -Be 3 - $result.UnitResults[0].ResultCode | Should -Be 0 - $result.UnitResults[1].ResultCode | Should -Be 0 - $result.UnitResults[2].ResultCode | Should -Be -1978286073 - } - - It 'Dependency cycle' { - $testFile = GetConfigTestDataFile "DependencyCycle.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Confirm-WinGetConfiguration -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286068 - $result.UnitResults.Count | Should -Be 3 - $result.UnitResults[0].ResultCode | Should -Be -1978286072 - $result.UnitResults[1].ResultCode | Should -Be -1978286072 - $result.UnitResults[2].ResultCode | Should -Be 0 - } - - It 'No issue' { - $testFile = GetConfigTestDataFile "PSGallery_NoSettings.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Confirm-WinGetConfiguration -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - } - - It 'Piped' { - $testFile = GetConfigTestDataFile "DuplicateIdentifiers.yml" - $result = Get-WinGetConfiguration -File $testFile | Confirm-WinGetConfiguration - $result.UnitResults.Count | Should -Be 3 - $result.UnitResults[0].ResultCode | Should -Be -1978286074 - $result.UnitResults[1].ResultCode | Should -Be -1978286074 - $result.UnitResults[2].ResultCode | Should -Be 0 - } - - It 'Positional' { - $testFile = GetConfigTestDataFile "DuplicateIdentifiers.yml" - $set = Get-WinGetConfiguration $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Confirm-WinGetConfiguration $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be -1978286074 - $result.UnitResults.Count | Should -Be 3 - $result.UnitResults[0].ResultCode | Should -Be -1978286074 - $result.UnitResults[1].ResultCode | Should -Be -1978286074 - $result.UnitResults[2].ResultCode | Should -Be 0 - } -} - -Describe 'Configuration History' { - - BeforeEach { - DeleteConfigTxtFiles - } - - It 'History Lifecycle' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set - $result | Should -Not -BeNullOrEmpty - $result.ResultCode | Should -Be 0 - $result.UnitResults.Count | Should -Be 1 - $result.UnitResults[0].State | Should -Be "Completed" - $result.UnitResults[0].ResultCode | Should -Be 0 - - $historySet = Get-WinGetConfiguration -InstanceIdentifier $set.InstanceIdentifier - $historySet | Should -Not -BeNullOrEmpty - $historySet.InstanceIdentifier | Should -Be $set.InstanceIdentifier - - $allHistory = Get-WinGetConfiguration -All - $allHistory | Should -Not -BeNullOrEmpty - - $historySet | Remove-WinGetConfigurationHistory - - $historySetAfterRemove = Get-WinGetConfiguration -InstanceIdentifier $set.InstanceIdentifier - $historySetAfterRemove | Should -BeNullOrEmpty - } -} - -Describe 'Configuration Serialization' { - - It 'Basic Serialization' { - $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" - $set = Get-WinGetConfiguration -File $testFile - $set | Should -Not -BeNullOrEmpty - - $result = ConvertTo-WinGetConfigurationYaml -Set $set - $result | Should -Not -BeNullOrEmpty - - $tempFile = New-TemporaryFile - Set-Content -Path $tempFile -Value $result - - $roundTripSet = Get-WinGetConfiguration -File $tempFile.VersionInfo.FileName - $roundTripSet | Should -Not -BeNullOrEmpty - } -} - -AfterAll { - CleanupGroupPolicies - CleanupGroupPolicyKeyIfExists - CleanupPsModulePath - DeleteConfigTxtFiles -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Pester tests related to the Microsoft.WinGet.Configuration PowerShell module. + 'Invoke-Pester' should be called in an admin PowerShell window. + Requires local test repo to be setup. +#> +[CmdletBinding()] +param( + # The location of the test data + [string]$ConfigurationTestDataPath +) + +BeforeAll { + $env:POWERSHELL_TELEMETRY_OPTOUT = "true" + $deviceGroupPolicyRoot = "HKLM:\Software\Policies\Microsoft\Windows" + $wingetPolicyKeyName = "AppInstaller" + $wingetGroupPolicyRegistryRoot = $deviceGroupPolicyRoot + "\" + $wingetPolicyKeyName + $e2eTestModule = "xE2ETestResource" + + Import-Module Microsoft.WinGet.Configuration + + # TODO: Installing within the test run on the build server (only) somehow causes below error: + # [CORE] Started MSStore package execution. ProductId: 9PCX3HX4HZ0Z PackageFamilyName: Microsoft.DesiredStateConfiguration-Preview_8wekyb3d8bbwe + # [CLI ] MSStore install failed. ProductId: 9PCX3HX4HZ0Z HResult: 0x80010002 + # So install the DSCv3 package here. + Import-Module Microsoft.WinGet.Client + # We prefer to use preview (9PCX3HX4HZ0Z) to catch issues early, but if it causes blocking use stable (9NVTPZWRC6KQ) until it is resolved. + $installResult = Install-WingetPackage -Id 9NVTPZWRC6KQ -Source msstore + if ($installResult.Status -ne 'Ok') + { + Write-Error "Failed to install DSCv3 package. Status: $($installResult.Status). ExtendedErrorCode: $($installResult.ExtendedErrorCode)." -ErrorAction Stop + } + + function CreatePolicyKeyIfNotExists() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if(-Not($registryExists)) + { + New-Item -Path $deviceGroupPolicyRoot -Name $wingetPolicyKeyName + } + } + + function CleanupGroupPolicyKeyIfExists() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if($registryExists) + { + Remove-Item -Path $wingetGroupPolicyRegistryRoot -Recurse + } + } + + function CleanupGroupPolicies() + { + $registryExists = test-path -Path $wingetGroupPolicyRegistryRoot + + if($registryExists) + { + Remove-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name * + } + } + + if ([System.String]::IsNullOrEmpty($ConfigurationTestDataPath)) + { + $ConfigurationTestDataPath = (Join-Path $PSScriptRoot "..\..\AppInstallerCLIE2ETests\TestData\Configuration\") + } + + function GetConfigTestDataPath() + { + return $ConfigurationTestDataPath + } + + function DeleteConfigTxtFiles() + { + Get-ChildItem $(GetConfigTestDataPath) -Filter *.txt -Recurse | ForEach-Object { Remove-Item $_ } + } + + function GetConfigTestDataFile([string] $fileName) + { + $path = Join-Path $(GetConfigTestDataPath) $fileName + + if (-not (Test-Path $path)) + { + throw "$path does not exists" + } + + return $path + } + + enum TestModuleLocation + { + CurrentUser + AllUsers + Custom + DefaultLocation + } + + function GetExpectedModulePath([TestModuleLocation]$testModuleLocation) + { + switch ($testModuleLocation) + { + ([TestModuleLocation]::CurrentUser) + { + $path = [Environment]::GetFolderPath([Environment+SpecialFolder]::MyDocuments) + return Join-Path $path "PowerShell\Modules" + } + ([TestModuleLocation]::AllUsers) + { + $path = [Environment]::GetFolderPath([Environment+SpecialFolder]::ProgramFiles) + return Join-Path $path "PowerShell\Modules" + } + ([TestModuleLocation]::DefaultLocation) + { + $path = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData) + return Join-Path $path "Microsoft\WinGet\Configuration\Modules" + } + ([TestModuleLocation]::Custom) + { + return Join-Path $env:TEMP "E2EPesterCustomModules" + } + default + { + throw $testModuleLocation + } + } + } + + function CleanupPsModulePath() + { + $wingetPath = GetExpectedModulePath DefaultLocation + $customPath = GetExpectedModulePath Custom + $modulePath = $env:PsModulePath + $newModulePath = ($modulePath.Split(';') | Where-Object { $_ -ne $wingetPath } | Where-Object { $_ -ne $customPath }) -join ';' + $env:PsModulePath = $newModulePath + } + + function EnsureModuleState([string]$moduleName, [bool]$present, [string]$repository = $null, [TestModuleLocation]$testModuleLocation = [TestModuleLocation]::CurrentUser) + { + CleanupPsModulePath + $wingetPath = GetExpectedModulePath DefaultLocation + $customPath = GetExpectedModulePath Custom + $env:PsModulePath += ";$wingetPath;$customPath" + + $availableModules = Get-Module $moduleName -ListAvailable + $isPresent = $null -ne $availableModules + + if ($isPresent) + { + foreach ($module in $availableModules) + { + try + { + $item = Get-Item $module.Path -ErrorAction Stop + while ($item.Name -ne $moduleName) + { + $item = Get-Item $item.PSParentPath -ErrorAction Stop + } + + if (-not $present) + { + Get-ChildItem $item.FullName -Recurse | Remove-Item -Force -Recurse -ErrorAction Stop + Remove-Item $item -ErrorAction Stop + } + else + { + # Must be in the right location + $expected = GetExpectedModulePath $testModuleLocation + if ($expected -ne $item.Parent.FullName) + { + Get-ChildItem $item.FullName -Recurse | Remove-Item -Force -Recurse -ErrorAction Stop + Remove-Item $item -ErrorAction Stop + $isPresent = $false + } + } + } + catch [System.Management.Automation.ItemNotFoundException] + { + Write-Host "Item not found, ignoring..." $_.Exception.Message + } + } + } + + if ((-not $isPresent) -and $present) + { + $params = @{ + Name = $moduleName + Force = $true + } + + if (-not [string]::IsNullOrEmpty($repository)) + { + $params.Add('Repository', $repository) + } + + if (($testModuleLocation -eq [TestModuleLocation]::CurrentUser) -or + ($testModuleLocation -eq [TestModuleLocation]::AllUsers)) + { + if ($testModuleLocation -eq [TestModuleLocation]::AllUsers) + { + $params.Add('Scope', 'AllUsers') + } + + Install-Module @params + } + else + { + $path = $customPath + if (($testModuleLocation -eq [TestModuleLocation]::WinGetModulePath) -or + ($testModuleLocation -eq [TestModuleLocation]::DefaultLocation)) + { + $path = $wingetPath + } + $params.Add('Path', $path) + + Save-Module @params + } + } + + CleanupPsModulePath + } + + function EnsureDSCv3TestResourcePresence() + { + $localAppDataPath = [Environment]::GetFolderPath([Environment+SpecialFolder]::LocalApplicationData) + $resourcePath = Join-Path $localAppDataPath "Microsoft\WindowsApps\test-file.dsc.resource.json" + if (-not (Test-Path $resourcePath)) + { + wingetdev dscv3 test-file --manifest -o $resourcePath + } + } +} + +Describe 'Test-GroupPolicies' { + BeforeAll { + CleanupGroupPolicies + CreatePolicyKeyIfNotExists + } + + It "Disable WinGetPolicy and run Get-WinGetConfiguration" { + + $policyKeyValueName = "EnableAppInstaller" + + Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 + $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName + $registryKey | Should -Not -BeNullOrEmpty + $registryKey.EnableAppInstaller | Should -Be 0 + + # [NOTE:] We don't need a valid yml file path to test Group Policy blocking scenario as it is the earliest check, + # so just using some random file path for this test. + { Get-WinGetConfiguration -File "Z:\NonExisting_SettingsFile.yml" } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager" + + CleanupGroupPolicies + } + + It "Disable EnableWindowsPackageManagerCommandLineInterfaces Policy and run Get-WinGetConfiguration" { + $policyKeyValueName = "EnableWindowsPackageManagerCommandLineInterfaces" + + Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 + $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName + $registryKey | Should -Not -BeNullOrEmpty + $registryKey.EnableWindowsPackageManagerCommandLineInterfaces | Should -Be 0 + + # [NOTE:] We don't need a valid yml file path to test Group Policy blocking scenario as it is the earliest check, + # so just using some random file path for this test. + { Get-WinGetConfiguration -File "Z:\NonExisting_SettingsFile.yml" } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager command line interfaces" + + CleanupGroupPolicies + } + + It "Disable EnableWindowsPackageManagerConfiguration Policy and run Get-WinGetConfiguration" { + $policyKeyValueName = "EnableWindowsPackageManagerConfiguration" + + Set-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName -Value 0 + $registryKey = Get-ItemProperty -Path $wingetGroupPolicyRegistryRoot -Name $policyKeyValueName + $registryKey | Should -Not -BeNullOrEmpty + $registryKey.EnableWindowsPackageManagerConfiguration | Should -Be 0 + + # [NOTE:] We don't need a valid yml file path to test Group Policy blocking scenario as it is the earliest check, + # so just using some random file path for this test. + { Get-WinGetConfiguration -File "Z:\NonExisting_SettingsFile.yml" } | Should -Throw "This operation is disabled by Group Policy : Enable Windows Package Manager Configuration" + + CleanupGroupPolicies + } + + AfterAll { + CleanupGroupPolicies + CleanupGroupPolicyKeyIfExists + } +} + +Describe 'Get configuration' { + + It 'Get configuration and details' { + EnsureModuleState $e2eTestModule $false + + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $set = Get-WinGetConfigurationDetails -Set $set + $set | Should -Not -BeNullOrEmpty + } + + It 'Get configuration and details DSCv3' { + EnsureDSCv3TestResourcePresence + + $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $set = Get-WinGetConfigurationDetails -Set $set + $set | Should -Not -BeNullOrEmpty + } + + It 'Get details piped' { + EnsureModuleState $e2eTestModule $false + + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile | Get-WinGetConfigurationDetails + $set | Should -Not -BeNullOrEmpty + } + + It 'Get configuration and details positional' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration $testFile + $set | Should -Not -BeNullOrEmpty + + $set = Get-WinGetConfigurationDetails $set + $set | Should -Not -BeNullOrEmpty + } + + It 'File doesnt exit' { + $testFile = "c:\dir\fakeFile.txt" + { Get-WinGetConfiguration -File $testFile } | Should -Throw $testFile + } + + It 'Invalid file' { + $testFile = GetConfigTestDataFile "Empty.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C002*" + } + + It 'Missing property' { + $testFile = GetConfigTestDataFile "NotConfig.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw '*0x8A15C00E*$schema*missing*' + } + + It 'Missing configurationVersion' { + $testFile = GetConfigTestDataFile "NoVersion.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C00E*configurationVersion*missing*" + } + + It 'Unknown version' { + $testFile = GetConfigTestDataFile "UnknownVersion.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C004*Configuration file version*is not known.*" + } + + It 'Resource wrong type' { + $testFile = GetConfigTestDataFile "ResourcesNotASequence.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C003*resources*wrong type*" + } + + It 'Unit wrong type' { + $testFile = GetConfigTestDataFile "UnitNotAMap.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C003*resources*0*wrong type*" + } + + It 'No resource name' { + $testFile = GetConfigTestDataFile "NoResourceName.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C00D*resource*invalid value*Module/*" + } + + It 'Module mismatch' { + $testFile = GetConfigTestDataFile "ModuleMismatch.yml" + { Get-WinGetConfiguration -File $testFile } | Should -Throw "*0x8A15C00D*invalid value*DifferentModule*" + } +} + +Describe 'Invoke-WinGetConfiguration' { + + BeforeEach { + DeleteConfigTxtFiles + } + +<# PS Gallery tests are unreliable. + It 'From Gallery' { + EnsureModuleState "XmlContentDsc" $false + + $testFile = GetConfigTestDataFile "PSGallery_NoModule_NoSettings.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286075 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be -1978285819 + } +#> + + It 'From TestRepo' { + EnsureModuleState $e2eTestModule $false + + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + + $expectedModule = Join-Path $(GetExpectedModulePath DefaultLocation) $e2eTestModule + Test-Path $expectedModule | Should -Be $true + } + + It 'From TestRepo Location' -ForEach @( + @{ Location = "CurrentUser"; } + @{ Location = "AllUsers"; } + @{ Location = "DefaultLocation"; } + @{ Location = "Custom"; }) { + $modulePath = "'" + switch ($location) + { + ([TestModuleLocation]::CurrentUser) + { + $modulePath = "currentuser" + break + } + ([TestModuleLocation]::AllUsers) + { + $modulePath = "allusers" + break + } + ([TestModuleLocation]::DefaultLocation) + { + $modulePath = "default" + break + } + ([TestModuleLocation]::Custom) + { + $modulePath = GetExpectedModulePath Custom + break + } + default { + throw $location + } + } + + EnsureModuleState $e2eTestModule $false + + $testFile = GetConfigTestDataFile "Configure_TestRepo_Location.yml" + $set = Get-WinGetConfiguration -File $testFile -ModulePath $modulePath + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedModule = Join-Path $(GetExpectedModulePath $location) $e2eTestModule + Test-Path $expectedModule | Should -Be $true + } + + It 'From DSCv3' { + EnsureDSCv3TestResourcePresence + + $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "ShowDetails_DSCv3.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "DSCv3 Contents!" + } + + It 'Piped' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $result = Get-WinGetConfiguration -File $testFile | Invoke-WinGetConfiguration -AcceptConfigurationAgreements + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } + + It 'Positional' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } + + It 'Independent Resource - One Failure' { + $testFile = GetConfigTestDataFile "IndependentResources_OneFailure.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286075 + $result.UnitResults.Count | Should -Be 2 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be -1978285819 + $result.UnitResults[1].State | Should -Be "Completed" + $result.UnitResults[1].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "IndependentResources_OneFailure.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } + + It 'Dependent Resource - Failure' { + $testFile = GetConfigTestDataFile "DependentResources_Failure.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286075 + $result.UnitResults.Count | Should -Be 2 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be -1978285819 + $result.UnitResults[1].State | Should -Be "Skipped" + $result.UnitResults[1].ResultCode | Should -Be -1978286072 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "DependentResources_Failure.txt" + Test-Path $expectedFile | Should -Be $false + } + + It 'ResourceCaseInsensitive' { + $testFile = GetConfigTestDataFile "ResourceCaseInsensitive.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "ResourceCaseInsensitive.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } +} + +Describe 'Start|Complete-WinGetConfiguration' { + + BeforeEach { + DeleteConfigTxtFiles + } + +<# PS Gallery tests are unreliable. + It 'From Gallery' { + EnsureModuleState "XmlContentDsc" $false + + $testFile = GetConfigTestDataFile "PSGallery_NoModule_NoSettings.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration -ConfigurationJob $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286075 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be -1978285819 + } +#> + + It 'From TestRepo' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration -ConfigurationJob $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + + # Verify can't be used after. + { Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set } | Should -Throw "Operation is not valid due to the current state of the object." + } + + It 'From TestRepo Location' -ForEach @( + @{ Location = "CurrentUser"; } + @{ Location = "AllUsers"; } + @{ Location = "DefaultLocation"; } + @{ Location = "Custom"; }) { + $modulePath = "'" + switch ($location) + { + ([TestModuleLocation]::CurrentUser) + { + $modulePath = "currentuser" + break + } + ([TestModuleLocation]::AllUsers) + { + $modulePath = "allusers" + break + } + ([TestModuleLocation]::DefaultLocation) + { + $modulePath = "default" + break + } + ([TestModuleLocation]::Custom) + { + $modulePath = GetExpectedModulePath Custom + break + } + default { + throw $location + } + } + + EnsureModuleState $e2eTestModule $false + + $testFile = GetConfigTestDataFile "Configure_TestRepo_Location.yml" + $set = Get-WinGetConfiguration -File $testFile -ModulePath $modulePath + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration -ConfigurationJob $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedModule = Join-Path $(GetExpectedModulePath $location) $e2eTestModule + Test-Path $expectedModule | Should -Be $true + } + + It 'From DSCv3' { + EnsureDSCv3TestResourcePresence + + $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration -ConfigurationJob $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "ShowDetails_DSCv3.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "DSCv3 Contents!" + + # Verify can't be used after. + { Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set } | Should -Throw "Operation is not valid due to the current state of the object." + } + + It 'Piped' { + DeleteConfigTxtFiles + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $result = Get-WinGetConfiguration -File $testFile | Start-WinGetConfiguration -AcceptConfigurationAgreements | Complete-WinGetConfiguration + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } + + It 'Positional' { + DeleteConfigTxtFiles + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration $testFile + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } + + It 'Independent Resource - One Failure' { + $testFile = GetConfigTestDataFile "IndependentResources_OneFailure.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration -ConfigurationJob $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286075 + $result.UnitResults.Count | Should -Be 2 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be -1978285819 + + $result.UnitResults[1].State | Should -Be "Completed" + $result.UnitResults[1].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "IndependentResources_OneFailure.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } + + It 'Dependent Resource - Failure' { + $testFile = GetConfigTestDataFile "DependentResources_Failure.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration -ConfigurationJob $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286075 + $result.UnitResults.Count | Should -Be 2 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be -1978285819 + $result.UnitResults[1].State | Should -Be "Skipped" + $result.UnitResults[1].ResultCode | Should -Be -1978286072 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "DependentResources_Failure.txt" + Test-Path $expectedFile | Should -Be $false + } + + It 'ResourceCaseInsensitive' { + $testFile = GetConfigTestDataFile "ResourceCaseInsensitive.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $job = Start-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $job | Should -Not -BeNullOrEmpty + + $result = Complete-WinGetConfiguration -ConfigurationJob $job + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $expectedFile = Join-Path $(GetConfigTestDataPath) "ResourceCaseInsensitive.txt" + Test-Path $expectedFile | Should -Be $true + Get-Content $expectedFile -Raw | Should -Be "Contents!" + } +} + +Describe 'Test-WinGetConfiguration' { + + BeforeEach { + DeleteConfigTxtFiles + } + + It 'Negative' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.TestResult | Should -Be "Negative" + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].TestResult | Should -Be "Negative" + $result.UnitResults[0].ResultCode | Should -Be 0 + } + + It 'Positive' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $expectedFile = Join-Path $(GetConfigTestDataPath) "Configure_TestRepo.txt" + Set-Content -Path $expectedFile -Value "Contents!" -NoNewline + + $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.TestResult | Should -Be "Positive" + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].TestResult | Should -Be "Positive" + $result.UnitResults[0].ResultCode | Should -Be 0 + } + + It 'Negative DSCv3' { + EnsureDSCv3TestResourcePresence + + $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.TestResult | Should -Be "Negative" + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].TestResult | Should -Be "Negative" + $result.UnitResults[0].ResultCode | Should -Be 0 + } + + It 'Positive DSCv3' { + EnsureDSCv3TestResourcePresence + + $testFile = GetConfigTestDataFile "ShowDetails_DSCv3.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $expectedFile = Join-Path $(GetConfigTestDataPath) "ShowDetails_DSCv3.txt" + Set-Content -Path $expectedFile -Value "DSCv3 Contents!" -NoNewline + + $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.TestResult | Should -Be "Positive" + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].TestResult | Should -Be "Positive" + $result.UnitResults[0].ResultCode | Should -Be 0 + } + + It 'Piped' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $result = Get-WinGetConfiguration -File $testFile | Test-WinGetConfiguration -AcceptConfigurationAgreements + $result | Should -Not -BeNullOrEmpty + $result.TestResult | Should -Be "Negative" + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].TestResult | Should -Be "Negative" + $result.UnitResults[0].ResultCode | Should -Be 0 + } + + It 'Positional' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Test-WinGetConfiguration -AcceptConfigurationAgreements $set + $result | Should -Not -BeNullOrEmpty + $result.TestResult | Should -Be "Negative" + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].TestResult | Should -Be "Negative" + $result.UnitResults[0].ResultCode | Should -Be 0 + } + + It "Failed" { + $testFile = GetConfigTestDataFile "IndependentResources_OneFailure.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Test-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.TestResult | Should -Be "Failed" + $result.UnitResults.Count | Should -Be 2 + $result.UnitResults[0].TestResult | Should -Be "Failed" + $result.UnitResults[0].ResultCode | Should -Be -1978285819 + $result.UnitResults[1].TestResult | Should -Be "Negative" + $result.UnitResults[1].ResultCode | Should -Be 0 + } +} + +Describe 'Confirm-WinGetConfiguration' { + + It 'Duplicate Identifiers' { + $testFile = GetConfigTestDataFile "DuplicateIdentifiers.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Confirm-WinGetConfiguration -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286074 + $result.UnitResults.Count | Should -Be 3 + $result.UnitResults[0].ResultCode | Should -Be -1978286074 + $result.UnitResults[1].ResultCode | Should -Be -1978286074 + $result.UnitResults[2].ResultCode | Should -Be 0 + } + + It 'Missing dependency' { + $testFile = GetConfigTestDataFile "MissingDependency.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Confirm-WinGetConfiguration -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286073 + $result.UnitResults.Count | Should -Be 3 + $result.UnitResults[0].ResultCode | Should -Be 0 + $result.UnitResults[1].ResultCode | Should -Be 0 + $result.UnitResults[2].ResultCode | Should -Be -1978286073 + } + + It 'Dependency cycle' { + $testFile = GetConfigTestDataFile "DependencyCycle.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Confirm-WinGetConfiguration -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286068 + $result.UnitResults.Count | Should -Be 3 + $result.UnitResults[0].ResultCode | Should -Be -1978286072 + $result.UnitResults[1].ResultCode | Should -Be -1978286072 + $result.UnitResults[2].ResultCode | Should -Be 0 + } + + It 'No issue' { + $testFile = GetConfigTestDataFile "PSGallery_NoSettings.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Confirm-WinGetConfiguration -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + } + + It 'Piped' { + $testFile = GetConfigTestDataFile "DuplicateIdentifiers.yml" + $result = Get-WinGetConfiguration -File $testFile | Confirm-WinGetConfiguration + $result.UnitResults.Count | Should -Be 3 + $result.UnitResults[0].ResultCode | Should -Be -1978286074 + $result.UnitResults[1].ResultCode | Should -Be -1978286074 + $result.UnitResults[2].ResultCode | Should -Be 0 + } + + It 'Positional' { + $testFile = GetConfigTestDataFile "DuplicateIdentifiers.yml" + $set = Get-WinGetConfiguration $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Confirm-WinGetConfiguration $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be -1978286074 + $result.UnitResults.Count | Should -Be 3 + $result.UnitResults[0].ResultCode | Should -Be -1978286074 + $result.UnitResults[1].ResultCode | Should -Be -1978286074 + $result.UnitResults[2].ResultCode | Should -Be 0 + } +} + +Describe 'Configuration History' { + + BeforeEach { + DeleteConfigTxtFiles + } + + It 'History Lifecycle' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = Invoke-WinGetConfiguration -AcceptConfigurationAgreements -Set $set + $result | Should -Not -BeNullOrEmpty + $result.ResultCode | Should -Be 0 + $result.UnitResults.Count | Should -Be 1 + $result.UnitResults[0].State | Should -Be "Completed" + $result.UnitResults[0].ResultCode | Should -Be 0 + + $historySet = Get-WinGetConfiguration -InstanceIdentifier $set.InstanceIdentifier + $historySet | Should -Not -BeNullOrEmpty + $historySet.InstanceIdentifier | Should -Be $set.InstanceIdentifier + + $allHistory = Get-WinGetConfiguration -All + $allHistory | Should -Not -BeNullOrEmpty + + $historySet | Remove-WinGetConfigurationHistory + + $historySetAfterRemove = Get-WinGetConfiguration -InstanceIdentifier $set.InstanceIdentifier + $historySetAfterRemove | Should -BeNullOrEmpty + } +} + +Describe 'Configuration Serialization' { + + It 'Basic Serialization' { + $testFile = GetConfigTestDataFile "Configure_TestRepo.yml" + $set = Get-WinGetConfiguration -File $testFile + $set | Should -Not -BeNullOrEmpty + + $result = ConvertTo-WinGetConfigurationYaml -Set $set + $result | Should -Not -BeNullOrEmpty + + $tempFile = New-TemporaryFile + Set-Content -Path $tempFile -Value $result + + $roundTripSet = Get-WinGetConfiguration -File $tempFile.VersionInfo.FileName + $roundTripSet | Should -Not -BeNullOrEmpty + } +} + +AfterAll { + CleanupGroupPolicies + CleanupGroupPolicyKeyIfExists + CleanupPsModulePath + DeleteConfigTxtFiles +} diff --git a/src/PowerShell/tests/Microsoft.WinGet.DSC.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.DSC.Tests.ps1 index 627bb8ebb5..6a4305c6be 100644 --- a/src/PowerShell/tests/Microsoft.WinGet.DSC.Tests.ps1 +++ b/src/PowerShell/tests/Microsoft.WinGet.DSC.Tests.ps1 @@ -1,247 +1,247 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# -.Synopsis - Pester tests related to the Microsoft.WinGet.DSC PowerShell module. - The tests require the localhost web server to be running and serving the test data. - 'Invoke-Pester' should be called in an admin PowerShell window. -#> - -BeforeAll { - Install-Module -Name PSDesiredStateConfiguration -Force -SkipPublisherCheck - Import-Module Microsoft.WinGet.Client - Import-Module Microsoft.WinGet.DSC - - # Helper function for calling Invoke-DscResource on the Microsoft.WinGet.DSC module. - function InvokeWinGetDSC() { - param ( - [Parameter()] - [string]$Name, - - [Parameter()] - [string]$Method, - - [Parameter()] - [hashtable]$Property - ) - - return Invoke-DscResource -Name $Name -ModuleName Microsoft.WinGet.DSC -Method $Method -Property $Property - } -} - -Describe 'List available DSC resources'{ - It 'Shows DSC Resources'{ - $expectedDSCResources = "WinGetAdminSettings", "WinGetPackage", "WinGetPackageManager", "WinGetSource", "WinGetUserSettings" - $availableDSCResources = (Get-DscResource -Module Microsoft.WinGet.DSC).Name - $availableDSCResources.length | Should -Be 5 - $availableDSCResources | Where-Object {$expectedDSCResources -notcontains $_} | Should -BeNullOrEmpty -ErrorAction Stop - } -} - -Describe 'WinGetAdminSettings' { - - BeforeAll { - $initialAdminSettings = (Get-WinGetSetting).adminSettings - $adminSettingsHash = @{ - BypassCertificatePinningForMicrosoftStore = !$initialAdminSettings.BypassCertificatePinningForMicrosoftStore; - InstallerHashOverride = !$initialAdminSettings.InstallerHashOverride; - LocalManifestFiles = !$initialAdminSettings.LocalManifestFiles; - LocalArchiveMalwareScanOverride = !$initialAdminSettings.LocalArchiveMalwareScanOverride; - } - } - - It 'Get admin settings' { - $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Get -Property @{ Settings = $adminSettingsHash } - $adminSettings = $result.Settings - $adminSettings.BypassCertificatePinningForMicrosoftStore | Should -Be $initialAdminSettings.BypassCertificatePinningForMicrosoftStore - $adminSettings.InstallerHashOverride | Should -Be $initialAdminSettings.InstallerHashOverride - $adminSettings.LocalManifestFiles | Should -Be $initialAdminSettings.LocalManifestFiles - $adminSettings.LocalArchiveMalwareScanOverride | Should -Be $initialAdminSettings.LocalArchiveMalwareScanOverride - } - - It 'Test admin settings' { - $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Test -Property @{ Settings = $adminSettingsHash } - $result.InDesiredState | Should -Be $false - } - - It 'Set admin settings' { - InvokeWinGetDSC -Name WinGetAdminSettings -Method Set -Property @{ Settings = $adminSettingsHash } - - # Verify settings were applied. - $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Get -Property @{ Settings = $adminSettingsHash } - $adminSettings = $result.Settings - $adminSettings.BypassCertificatePinningForMicrosoftStore | Should -Not -Be $initialAdminSettings.BypassCertificatePinningForMicrosoftStore - $adminSettings.InstallerHashOverride | Should -Not -Be $initialAdminSettings.InstallerHashOverride - $adminSettings.LocalManifestFiles | Should -Not -Be $initialAdminSettings.LocalManifestFiles - $adminSettings.LocalArchiveMalwareScanOverride | Should -Not -Be $initialAdminSettings.LocalArchiveMalwareScanOverride - - $testResult = InvokeWinGetDSC -Name WinGetAdminSettings -Method Test -Property @{ Settings = $adminSettingsHash } - $testResult | Should -Be $true - } - - AfterAll { - InvokeWinGetDSC -Name WinGetAdminSettings -Method Set -Property @{ Settings = $initialAdminSettings } - } -} - -Describe 'WinGetUserSettings' { - BeforeAll { - # Delete existing user settings file. - $settingsFilePath = (Get-WinGetSetting).userSettingsFile - $backupSettingsFilePath = $settingsFilePath + ".backup" - - if (Test-Path -Path $settingsFilePath) - { - Remove-Item $settingsFilePath - } - - if (Test-Path -Path $backupSettingsFilePath) - { - Remove-Item $backupSettingsFilePath - } - - $userSettingsHash = @{ - experimentalFeatures = @{ directMSI = $true }; - installBehavior = @{ Preferences = @{ Scope = 'User' }} - } - } - - It 'Get user settings' { - $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Get -Property @{ Settings = $userSettingsHash } - $result.Settings.Count | Should -Be 0 - } - - It 'Test user settings' { - $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Test -Property @{ Settings = $userSettingsHash } - $result.InDesiredState | Should -Be $false - } - - It 'Set user settings' { - InvokeWinGetDSC -Name WinGetUserSettings -Method Set -Property @{ Settings = $userSettingsHash } - - # Verify user settings were applied. - $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Get -Property @{ Settings = $userSettingsHash } - $userSettings = $result.Settings - $userSettings.experimentalFeatures.directMSI | Should -Be $true - $userSettings.installBehavior.Preferences.Scope | Should -Be 'User' - } -} - -Describe 'WinGetSource' { - BeforeAll { - InvokeWinGetDSC -Name WinGetUserSettings -Method Set -Property @{ Settings = @{ experimentalFeatures = @{ sourcePriority = $true } } } - - $testSourceName = 'TestSource' - $testSourceArg = 'https://localhost:5001/TestKit/' - $testSourceType = 'Microsoft.PreIndexed.Package' - - InvokeWinGetDSC -Name WinGetSource -Method Set -Property @{ Ensure = 'Absent'; Name = $testSourceName } - } - - It 'Get WinGet source' { - $result = InvokeWinGetDSC -Name WinGetSource -Method Get -Property @{ Name = $testSourceName } - $result.Ensure | Should -Be 'Absent' - } - - It 'Test WinGet source' { - $result = InvokeWinGetDSC -Name WinGetSource -Method Test -Property @{ Ensure='Present'; Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType } - $result.InDesiredState | Should -Be $false - } - - It 'Set WinGet source' { - InvokeWinGetDSC -Name WinGetSource -Method Set -Property @{ Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType; TrustLevel = 'Trusted'; Explicit = $true; Priority = 42 } - - $result = InvokeWinGetDSC -Name WinGetSource -Method Test -Property @{ Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType } - $result.InDesiredState | Should -Be $true - - $result = InvokeWinGetDSC -Name WinGetSource -Method Get -Property @{ Name = $testSourceName } - $result.Name | Should -Be $testSourceName - $result.Type | Should -Be $testSourceType - $result.Argument | Should -Be $testSourceArg - $result.TrustLevel | Should -Be 'Trusted' - $result.Explicit | Should -Be $true - $result.Priority | Should -Be 42 - } -} - -Describe 'WinGetPackage' { - BeforeAll { - $testSourceName = 'TestSource' - $testSourceArg = 'https://localhost:5001/TestKit/' - $testSourceType = 'Microsoft.PreIndexed.Package' - - $testPackageId = 'AppInstallerTest.TestExeInstaller' - $testPackageVersion = '1.0.0.0' - - InvokeWinGetDSC -Name WinGetSource -Method Set -Property @{ Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType; TrustLevel = 'Trusted'; Explicit = $false } - } - - It 'Get WinGetPackage' { - $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; Version = $testPackageVersion } - $result.Ensure | Should -Be 'Absent' - } - - It 'Test WinGetPackage' { - $result = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Id = $testPackageId; Version = $testPackageVersion } - $result.InDesiredState | Should -Be $false - } - - It 'Install WinGetPackage' { - InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; Version = $testPackageVersion } - - # Verify package installed. - $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; Version = $testPackageVersion } - $result.Ensure | Should -Be 'Present' - $result.UseLatest | Should -Be $false - $result.Version | Should -Be $testPackageVersion - } - - It 'Update WinGetPackage' { - $testResult = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Id = $testPackageId; UseLatest = $true } - $testResult.InDesiredState | Should -Be $false - - InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; UseLatest = $true } - - # Verify package updated. - $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; UseLatest = $true } - $result.Ensure | Should -Be 'Present' - $result.UseLatest | Should -Be $true - $result.Version | Should -Not -Be $testPackageVersion - } - - It 'Uninstall WinGetPackage' { - InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; UseLatest = $true } - - $testResult = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Ensure = 'Absent'; Id = $testPackageId } - $testResult.InDesiredState | Should -Be $false - - InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Ensure = 'Absent'; Id = $testPackageId } - - # Verify package uninstalled. - $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Ensure = 'Absent'; Id = $testPackageId } - $result.Ensure | Should -Be 'Absent' - } - - AfterAll { - InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Ensure = 'Absent'; Id = $testPackageId} - } -} - -Describe 'WinGetPackageManager' { - It 'Get WinGet version' { - $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Get -Property @{} - $result.Version | Should -Not -Be $null - } - - It 'Test WinGet version' { - $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Test -Property @{ Version = "1.2.3.4" } - $result.InDesiredState | Should -Be $false - - $currentVersion = Get-WinGetVersion - $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Test -Property @{ Version = $currentVersion } - $result.InDesiredState | Should -Be $true - } - - # TODO: Add test to verify Set method for WinGetPackageManager -} +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Pester tests related to the Microsoft.WinGet.DSC PowerShell module. + The tests require the localhost web server to be running and serving the test data. + 'Invoke-Pester' should be called in an admin PowerShell window. +#> + +BeforeAll { + Install-Module -Name PSDesiredStateConfiguration -Force -SkipPublisherCheck + Import-Module Microsoft.WinGet.Client + Import-Module Microsoft.WinGet.DSC + + # Helper function for calling Invoke-DscResource on the Microsoft.WinGet.DSC module. + function InvokeWinGetDSC() { + param ( + [Parameter()] + [string]$Name, + + [Parameter()] + [string]$Method, + + [Parameter()] + [hashtable]$Property + ) + + return Invoke-DscResource -Name $Name -ModuleName Microsoft.WinGet.DSC -Method $Method -Property $Property + } +} + +Describe 'List available DSC resources'{ + It 'Shows DSC Resources'{ + $expectedDSCResources = "WinGetAdminSettings", "WinGetPackage", "WinGetPackageManager", "WinGetSource", "WinGetUserSettings" + $availableDSCResources = (Get-DscResource -Module Microsoft.WinGet.DSC).Name + $availableDSCResources.length | Should -Be 5 + $availableDSCResources | Where-Object {$expectedDSCResources -notcontains $_} | Should -BeNullOrEmpty -ErrorAction Stop + } +} + +Describe 'WinGetAdminSettings' { + + BeforeAll { + $initialAdminSettings = (Get-WinGetSetting).adminSettings + $adminSettingsHash = @{ + BypassCertificatePinningForMicrosoftStore = !$initialAdminSettings.BypassCertificatePinningForMicrosoftStore; + InstallerHashOverride = !$initialAdminSettings.InstallerHashOverride; + LocalManifestFiles = !$initialAdminSettings.LocalManifestFiles; + LocalArchiveMalwareScanOverride = !$initialAdminSettings.LocalArchiveMalwareScanOverride; + } + } + + It 'Get admin settings' { + $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Get -Property @{ Settings = $adminSettingsHash } + $adminSettings = $result.Settings + $adminSettings.BypassCertificatePinningForMicrosoftStore | Should -Be $initialAdminSettings.BypassCertificatePinningForMicrosoftStore + $adminSettings.InstallerHashOverride | Should -Be $initialAdminSettings.InstallerHashOverride + $adminSettings.LocalManifestFiles | Should -Be $initialAdminSettings.LocalManifestFiles + $adminSettings.LocalArchiveMalwareScanOverride | Should -Be $initialAdminSettings.LocalArchiveMalwareScanOverride + } + + It 'Test admin settings' { + $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Test -Property @{ Settings = $adminSettingsHash } + $result.InDesiredState | Should -Be $false + } + + It 'Set admin settings' { + InvokeWinGetDSC -Name WinGetAdminSettings -Method Set -Property @{ Settings = $adminSettingsHash } + + # Verify settings were applied. + $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Get -Property @{ Settings = $adminSettingsHash } + $adminSettings = $result.Settings + $adminSettings.BypassCertificatePinningForMicrosoftStore | Should -Not -Be $initialAdminSettings.BypassCertificatePinningForMicrosoftStore + $adminSettings.InstallerHashOverride | Should -Not -Be $initialAdminSettings.InstallerHashOverride + $adminSettings.LocalManifestFiles | Should -Not -Be $initialAdminSettings.LocalManifestFiles + $adminSettings.LocalArchiveMalwareScanOverride | Should -Not -Be $initialAdminSettings.LocalArchiveMalwareScanOverride + + $testResult = InvokeWinGetDSC -Name WinGetAdminSettings -Method Test -Property @{ Settings = $adminSettingsHash } + $testResult | Should -Be $true + } + + AfterAll { + InvokeWinGetDSC -Name WinGetAdminSettings -Method Set -Property @{ Settings = $initialAdminSettings } + } +} + +Describe 'WinGetUserSettings' { + BeforeAll { + # Delete existing user settings file. + $settingsFilePath = (Get-WinGetSetting).userSettingsFile + $backupSettingsFilePath = $settingsFilePath + ".backup" + + if (Test-Path -Path $settingsFilePath) + { + Remove-Item $settingsFilePath + } + + if (Test-Path -Path $backupSettingsFilePath) + { + Remove-Item $backupSettingsFilePath + } + + $userSettingsHash = @{ + experimentalFeatures = @{ directMSI = $true }; + installBehavior = @{ Preferences = @{ Scope = 'User' }} + } + } + + It 'Get user settings' { + $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Get -Property @{ Settings = $userSettingsHash } + $result.Settings.Count | Should -Be 0 + } + + It 'Test user settings' { + $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Test -Property @{ Settings = $userSettingsHash } + $result.InDesiredState | Should -Be $false + } + + It 'Set user settings' { + InvokeWinGetDSC -Name WinGetUserSettings -Method Set -Property @{ Settings = $userSettingsHash } + + # Verify user settings were applied. + $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Get -Property @{ Settings = $userSettingsHash } + $userSettings = $result.Settings + $userSettings.experimentalFeatures.directMSI | Should -Be $true + $userSettings.installBehavior.Preferences.Scope | Should -Be 'User' + } +} + +Describe 'WinGetSource' { + BeforeAll { + InvokeWinGetDSC -Name WinGetUserSettings -Method Set -Property @{ Settings = @{ experimentalFeatures = @{ sourcePriority = $true } } } + + $testSourceName = 'TestSource' + $testSourceArg = 'https://localhost:5001/TestKit/' + $testSourceType = 'Microsoft.PreIndexed.Package' + + InvokeWinGetDSC -Name WinGetSource -Method Set -Property @{ Ensure = 'Absent'; Name = $testSourceName } + } + + It 'Get WinGet source' { + $result = InvokeWinGetDSC -Name WinGetSource -Method Get -Property @{ Name = $testSourceName } + $result.Ensure | Should -Be 'Absent' + } + + It 'Test WinGet source' { + $result = InvokeWinGetDSC -Name WinGetSource -Method Test -Property @{ Ensure='Present'; Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType } + $result.InDesiredState | Should -Be $false + } + + It 'Set WinGet source' { + InvokeWinGetDSC -Name WinGetSource -Method Set -Property @{ Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType; TrustLevel = 'Trusted'; Explicit = $true; Priority = 42 } + + $result = InvokeWinGetDSC -Name WinGetSource -Method Test -Property @{ Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType } + $result.InDesiredState | Should -Be $true + + $result = InvokeWinGetDSC -Name WinGetSource -Method Get -Property @{ Name = $testSourceName } + $result.Name | Should -Be $testSourceName + $result.Type | Should -Be $testSourceType + $result.Argument | Should -Be $testSourceArg + $result.TrustLevel | Should -Be 'Trusted' + $result.Explicit | Should -Be $true + $result.Priority | Should -Be 42 + } +} + +Describe 'WinGetPackage' { + BeforeAll { + $testSourceName = 'TestSource' + $testSourceArg = 'https://localhost:5001/TestKit/' + $testSourceType = 'Microsoft.PreIndexed.Package' + + $testPackageId = 'AppInstallerTest.TestExeInstaller' + $testPackageVersion = '1.0.0.0' + + InvokeWinGetDSC -Name WinGetSource -Method Set -Property @{ Name = $testSourceName; Argument = $testSourceArg; Type = $testSourceType; TrustLevel = 'Trusted'; Explicit = $false } + } + + It 'Get WinGetPackage' { + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; Version = $testPackageVersion } + $result.Ensure | Should -Be 'Absent' + } + + It 'Test WinGetPackage' { + $result = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Id = $testPackageId; Version = $testPackageVersion } + $result.InDesiredState | Should -Be $false + } + + It 'Install WinGetPackage' { + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; Version = $testPackageVersion } + + # Verify package installed. + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; Version = $testPackageVersion } + $result.Ensure | Should -Be 'Present' + $result.UseLatest | Should -Be $false + $result.Version | Should -Be $testPackageVersion + } + + It 'Update WinGetPackage' { + $testResult = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Id = $testPackageId; UseLatest = $true } + $testResult.InDesiredState | Should -Be $false + + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; UseLatest = $true } + + # Verify package updated. + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; UseLatest = $true } + $result.Ensure | Should -Be 'Present' + $result.UseLatest | Should -Be $true + $result.Version | Should -Not -Be $testPackageVersion + } + + It 'Uninstall WinGetPackage' { + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; UseLatest = $true } + + $testResult = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Ensure = 'Absent'; Id = $testPackageId } + $testResult.InDesiredState | Should -Be $false + + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Ensure = 'Absent'; Id = $testPackageId } + + # Verify package uninstalled. + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Ensure = 'Absent'; Id = $testPackageId } + $result.Ensure | Should -Be 'Absent' + } + + AfterAll { + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Ensure = 'Absent'; Id = $testPackageId} + } +} + +Describe 'WinGetPackageManager' { + It 'Get WinGet version' { + $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Get -Property @{} + $result.Version | Should -Not -Be $null + } + + It 'Test WinGet version' { + $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Test -Property @{ Version = "1.2.3.4" } + $result.InDesiredState | Should -Be $false + + $currentVersion = Get-WinGetVersion + $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Test -Property @{ Version = $currentVersion } + $result.InDesiredState | Should -Be $true + } + + # TODO: Add test to verify Set method for WinGetPackageManager +} diff --git a/src/PowerShell/tests/RunTests.ps1 b/src/PowerShell/tests/RunTests.ps1 index e005eb782e..4490d2acaf 100644 --- a/src/PowerShell/tests/RunTests.ps1 +++ b/src/PowerShell/tests/RunTests.ps1 @@ -1,80 +1,80 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -[CmdletBinding()] -param( - [string]$testModulesPath, - [string]$outputPath, - [string]$packageLayoutPath, - [switch]$TargetProduction, - [string]$ConfigurationTestDataPath -) - -# This updates pester not always necessary but worth noting -Install-Module -Name Pester -Force -SkipPublisherCheck -Import-Module Pester - -if (-not [System.String]::IsNullOrEmpty($testModulesPath)) -{ - $env:PSModulePath += ";$testModulesPath" -} - -if (-not (Test-Path $outputPath)) -{ - New-Item -Path $outputPath -ItemType Directory -} -else -{ - Remove-Item $outputPath\* -Recurse -Force -} - -# Register the package -if (-not [System.String]::IsNullOrEmpty($packageLayoutPath)) -{ - $local:packageManifestPath = Join-Path $packageLayoutPath "AppxManifest.xml" - - Import-Module Appx -UseWindowsPowerShell - Add-AppxPackage -Register $local:packageManifestPath - - # Configure crash dump and log file settings - if ($TargetProduction) - { - $local:wingetExeName = "winget.exe" - } - else - { - $local:wingetExeName = "wingetdev.exe" - } - - $local:settingsExport = ConvertFrom-Json (& $local:wingetExeName settings export) - $local:settingsFilePath = $local:settingsExport.userSettingsFile - $local:settingsFileContent = ConvertTo-Json @{ debugging= @{ enableSelfInitiatedMinidump=$true ; keepAllLogFiles=$true } } - - Set-Content -Path $local:settingsFilePath -Value $local:settingsFileContent -} - -$clientConfig = New-PesterConfiguration -$clientConfig.TestResult.OutputFormat = "NUnitXML" -$clientConfig.TestResult.OutputPath = "$outputPath\Tests-WinGetClient.XML" -$clientConfig.TestResult.Enabled = $true -$clientConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.Client.Tests.ps1" -Data @{ TargetProduction = $TargetProduction } - -Invoke-Pester -Configuration $clientConfig - -if ($PSEdition -eq "Core") -{ - $configConfig = New-PesterConfiguration - $configConfig.TestResult.OutputFormat = "NUnitXML" - $configConfig.TestResult.OutputPath = "$outputPath\Tests-WinGetConfiguration.XML" - $configConfig.TestResult.Enabled = $true - $configConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.Configuration.Tests.ps1" -Data @{ ConfigurationTestDataPath = $ConfigurationTestDataPath } - - Invoke-Pester -Configuration $configConfig - - $dscConfig = New-PesterConfiguration - $dscConfig.TestResult.OutputFormat = "NUnitXML" - $dscConfig.TestResult.OutputPath = "$outputPath\Tests-WinGetDSC.XML" - $dscConfig.TestResult.Enabled = $true - $dscConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.DSC.Tests.ps1" - - Invoke-Pester -Configuration $dscConfig -} +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. +[CmdletBinding()] +param( + [string]$testModulesPath, + [string]$outputPath, + [string]$packageLayoutPath, + [switch]$TargetProduction, + [string]$ConfigurationTestDataPath +) + +# This updates pester not always necessary but worth noting +Install-Module -Name Pester -Force -SkipPublisherCheck +Import-Module Pester + +if (-not [System.String]::IsNullOrEmpty($testModulesPath)) +{ + $env:PSModulePath += ";$testModulesPath" +} + +if (-not (Test-Path $outputPath)) +{ + New-Item -Path $outputPath -ItemType Directory +} +else +{ + Remove-Item $outputPath\* -Recurse -Force +} + +# Register the package +if (-not [System.String]::IsNullOrEmpty($packageLayoutPath)) +{ + $local:packageManifestPath = Join-Path $packageLayoutPath "AppxManifest.xml" + + Import-Module Appx -UseWindowsPowerShell + Add-AppxPackage -Register $local:packageManifestPath + + # Configure crash dump and log file settings + if ($TargetProduction) + { + $local:wingetExeName = "winget.exe" + } + else + { + $local:wingetExeName = "wingetdev.exe" + } + + $local:settingsExport = ConvertFrom-Json (& $local:wingetExeName settings export) + $local:settingsFilePath = $local:settingsExport.userSettingsFile + $local:settingsFileContent = ConvertTo-Json @{ debugging= @{ enableSelfInitiatedMinidump=$true ; keepAllLogFiles=$true } } + + Set-Content -Path $local:settingsFilePath -Value $local:settingsFileContent +} + +$clientConfig = New-PesterConfiguration +$clientConfig.TestResult.OutputFormat = "NUnitXML" +$clientConfig.TestResult.OutputPath = "$outputPath\Tests-WinGetClient.XML" +$clientConfig.TestResult.Enabled = $true +$clientConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.Client.Tests.ps1" -Data @{ TargetProduction = $TargetProduction } + +Invoke-Pester -Configuration $clientConfig + +if ($PSEdition -eq "Core") +{ + $configConfig = New-PesterConfiguration + $configConfig.TestResult.OutputFormat = "NUnitXML" + $configConfig.TestResult.OutputPath = "$outputPath\Tests-WinGetConfiguration.XML" + $configConfig.TestResult.Enabled = $true + $configConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.Configuration.Tests.ps1" -Data @{ ConfigurationTestDataPath = $ConfigurationTestDataPath } + + Invoke-Pester -Configuration $configConfig + + $dscConfig = New-PesterConfiguration + $dscConfig.TestResult.OutputFormat = "NUnitXML" + $dscConfig.TestResult.OutputPath = "$outputPath\Tests-WinGetDSC.XML" + $dscConfig.TestResult.Enabled = $true + $dscConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.DSC.Tests.ps1" + + Invoke-Pester -Configuration $dscConfig +} diff --git a/src/PureLib/readme.md b/src/PureLib/readme.md index 5080dfaeac..a53563aad6 100644 --- a/src/PureLib/readme.md +++ b/src/PureLib/readme.md @@ -1,17 +1,17 @@ -## PureLib - -Do not change code under the pure directory; it contains pure source code version [1.0.4](https://github.com/ronomon/pure/releases/tag/v1.0.4). -It is created using git subtree command: - - git subtree add --prefix=src/PureLib/pure https://github.com/ronomon/pure.git v1.0.4 --squash - -The PureLib.vcxitems is created to make pure code compiled as part of the WinGet solution. - -#### Steps used to create the VS project files. - -1. VS Create project from existing code wizard to create a Shared Items template. -2. Remove code not needed by WinGet project. Basically keeping only the top level headers. -3. Add the PureLib headers to the shared items. -4. Modify the created vcxitems file to add the PureLib directory to the include path. - +## PureLib + +Do not change code under the pure directory; it contains pure source code version [1.0.4](https://github.com/ronomon/pure/releases/tag/v1.0.4). +It is created using git subtree command: + + git subtree add --prefix=src/PureLib/pure https://github.com/ronomon/pure.git v1.0.4 --squash + +The PureLib.vcxitems is created to make pure code compiled as part of the WinGet solution. + +#### Steps used to create the VS project files. + +1. VS Create project from existing code wizard to create a Shared Items template. +2. Remove code not needed by WinGet project. Basically keeping only the top level headers. +3. Add the PureLib headers to the shared items. +4. Modify the created vcxitems file to add the PureLib directory to the include path. + > **When committing the PR, DO NOT squash it. The two commits are needed as is to allow for future subtree pulls.**
\ No newline at end of file diff --git a/src/Update-VcxprojNugetPackageVersions.ps1 b/src/Update-VcxprojNugetPackageVersions.ps1 index e5bfd43722..ada2a43b59 100644 --- a/src/Update-VcxprojNugetPackageVersions.ps1 +++ b/src/Update-VcxprojNugetPackageVersions.ps1 @@ -1,304 +1,304 @@ -#Requires -Version 5.1 - -<# -.SYNOPSIS - Updates the version of a specific NuGet package across all .vcxproj files and their associated packages.config files. - -.DESCRIPTION - This script searches for all projects that use a specified NuGet package and updates both the packages.config - file and the .vcxproj file to use a new version. It uses Get-VcxprojNugetPackageVersions.ps1 to find target - projects and then performs the necessary updates. - -.PARAMETER PackageName - The name of the NuGet package to update (e.g., "Newtonsoft.Json"). - -.PARAMETER NewVersion - The new version to update to (e.g., "13.0.3"). - -.PARAMETER WhatIf - Shows what changes would be made without actually making them. - -.PARAMETER Backup - Creates backup files (.bak) before making changes. - -.EXAMPLE - .\Update-VcxprojNugetPackageVersions.ps1 -PackageName "Newtonsoft.Json" -NewVersion "13.0.3" - -.EXAMPLE - .\Update-VcxprojNugetPackageVersions.ps1 -PackageName "Microsoft.VisualStudio.TestTools.UnitTesting" -NewVersion "2.2.8" -WhatIf - -.EXAMPLE - .\Update-VcxprojNugetPackageVersions.ps1 -PackageName "boost" -NewVersion "1.82.0" -Backup:$false -#> - -[CmdletBinding(SupportsShouldProcess)] -param( - [Parameter(Mandatory = $true)] - [string]$PackageName, - - [Parameter(Mandatory = $true)] - [string]$NewVersion, - - [switch]$Backup -) - -# Import the Get-VcxprojNugetPackageVersions script -$getPackagesScript = Join-Path $PSScriptRoot "Get-VcxprojNugetPackageVersions.ps1" - -if (-not (Test-Path $getPackagesScript)) { - Write-Error "Could not find Get-VcxprojNugetPackageVersions.ps1 in the same directory as this script." - exit 1 -} - -function Update-PackagesConfig { - param( - [string]$PackagesConfigPath, - [string]$PackageName, - [string]$OldVersion, - [string]$NewVersion, - [string]$TargetFramework, - [bool]$CreateBackup = $true, - [bool]$WhatIfMode = $false - ) - - if (-not (Test-Path $PackagesConfigPath)) { - Write-Warning "packages.config not found: $PackagesConfigPath" - return $false - } - - try { - # Create backup if requested - if ($CreateBackup -and -not $WhatIfMode) { - $backupPath = "$PackagesConfigPath.bak" - Copy-Item $PackagesConfigPath $backupPath -Force - Write-Verbose "Created backup: $backupPath" - } - - # Load and parse the XML - [xml]$packagesXml = Get-Content $PackagesConfigPath -ErrorAction Stop - - $packageNode = $packagesXml.packages.package | Where-Object { $_.id -eq $PackageName } - - if (-not $packageNode) { - Write-Warning "Package '$PackageName' not found in $PackagesConfigPath" - return $false - } - - $currentVersion = $packageNode.version - - # Skip if version is already the target version - if ($currentVersion -eq $NewVersion) { - Write-Verbose "Skipping $PackagesConfigPath - version is already $NewVersion" - return $false - } - - if ($WhatIfMode) { - Write-Host "WHATIF: Would update $PackagesConfigPath" -ForegroundColor Yellow - Write-Host " Package: $PackageName" -ForegroundColor Cyan - Write-Host " Current Version: $currentVersion" -ForegroundColor Red - Write-Host " New Version: $NewVersion" -ForegroundColor Green - return $true - } - - # Update the version - $packageNode.version = $NewVersion - - # Save the updated XML - $packagesXml.Save($PackagesConfigPath) - - Write-Host "Updated packages.config: $PackagesConfigPath" -ForegroundColor Green - Write-Host " ${PackageName}: $currentVersion → $NewVersion" -ForegroundColor Cyan - - return $true - } - catch { - Write-Error "Failed to update packages.config '$PackagesConfigPath': $($_.Exception.Message)" - return $false - } -} - -function Update-VcxprojFile { - param( - [string]$VcxprojPath, - [string]$PackageName, - [string]$OldVersion, - [string]$NewVersion, - [bool]$CreateBackup = $true, - [bool]$WhatIfMode = $false - ) - - if (-not (Test-Path $VcxprojPath)) { - Write-Warning ".vcxproj file not found: $VcxprojPath" - return $false - } - - # Skip if version is already the target version - if ($OldVersion -eq $NewVersion) { - Write-Verbose "Skipping $VcxprojPath - version is already $NewVersion" - return $false - } - - try { - # Read the file content - $content = Get-Content $VcxprojPath -Raw -ErrorAction Stop - - # Pattern to match package references in the format "PACKAGE-NAME.VERSION" - $oldPattern = [regex]::Escape("$PackageName.$OldVersion") - $newPattern = "$PackageName.$NewVersion" - - # Check if there are any matches - if ($content -notmatch $oldPattern) { - Write-Verbose "No references to $PackageName.$OldVersion found in $VcxprojPath" - return $false - } - - if ($WhatIfMode) { - $regexMatches = [regex]::Matches($content, $oldPattern) - Write-Host "WHATIF: Would update $VcxprojPath" -ForegroundColor Yellow - Write-Host " Found $($regexMatches.Count) reference(s) to update:" -ForegroundColor Cyan - Write-Host " $oldPattern → $newPattern" -ForegroundColor Cyan - return $true - } - - # Create backup if requested - if ($CreateBackup) { - $backupPath = "$VcxprojPath.bak" - Copy-Item $VcxprojPath $backupPath -Force - Write-Verbose "Created backup: $backupPath" - } - - # Replace all occurrences - $updatedContent = $content -replace $oldPattern, $newPattern - - # Count the number of replacements made - $originalMatches = [regex]::Matches($content, $oldPattern).Count - $remainingMatches = [regex]::Matches($updatedContent, $oldPattern).Count - $replacementCount = $originalMatches - $remainingMatches - - # Save the updated content - Set-Content $VcxprojPath -Value $updatedContent -NoNewline -Encoding UTF8 -ErrorAction Stop - - Write-Host "Updated .vcxproj file: $VcxprojPath" -ForegroundColor Green - Write-Host " Replaced $replacementCount reference(s): $oldPattern → $newPattern" -ForegroundColor Cyan - - return $true - } - catch { - Write-Error "Failed to update .vcxproj file '$VcxprojPath': $($_.Exception.Message)" - return $false - } -} - -function Show-UpdateSummary { - param( - [array]$TargetPackages, - [int]$SuccessfulPackagesConfig, - [int]$SuccessfulVcxproj, - [string]$PackageName, - [string]$NewVersion - ) - - Write-Host "`n=== UPDATE SUMMARY ===" -ForegroundColor Cyan - Write-Host "Package: $PackageName" -ForegroundColor White - Write-Host "New Version: $NewVersion" -ForegroundColor White - Write-Host "Projects found with package: $($TargetPackages.Count)" -ForegroundColor White - Write-Host "packages.config files updated: $SuccessfulPackagesConfig" -ForegroundColor Green - Write-Host ".vcxproj files updated: $SuccessfulVcxproj" -ForegroundColor Green - - # Show unique versions being replaced - $uniqueVersions = $TargetPackages | Group-Object Version | Sort-Object Name - if ($uniqueVersions.Count -gt 0) { - Write-Host "`nVersions being replaced:" -ForegroundColor Cyan - foreach ($version in $uniqueVersions) { - Write-Host " $($version.Name) (in $($version.Count) project(s))" -ForegroundColor Yellow - } - } -} - -# Main execution -try { - Write-Host "Starting package version update process..." -ForegroundColor Cyan - Write-Host "Package: $PackageName" -ForegroundColor White - Write-Host "New Version: $NewVersion" -ForegroundColor White - - if ($WhatIfPreference) { - Write-Host "Mode: WHATIF (no changes will be made)" -ForegroundColor Yellow - } else { - Write-Host "Backup files: $(if ($Backup) { 'Enabled' } else { 'Disabled' })" -ForegroundColor White - } - - Write-Host "`nSearching for projects using package '$PackageName'..." -ForegroundColor Cyan - - # Use the Get-VcxprojNugetPackageVersions script to find target packages - $targetPackages = & $getPackagesScript -PackageFilter $PackageName -OutputFormat Object - - if ($targetPackages.Count -eq 0) { - Write-Warning "No projects found using package '$PackageName'." - return - } - - Write-Host "Found $($targetPackages.Count) reference(s) to package '$PackageName'" -ForegroundColor Green - - # Group by project to avoid duplicate processing - $projectGroups = $targetPackages | Group-Object ProjectFile - - Write-Host "`nProjects with package:" -ForegroundColor Cyan - foreach ($group in $projectGroups) { - $project = $group.Group[0] # Get the first package info for project details - Write-Host " $($project.ProjectName) - Version(s): $($group.Group.Version -join ', ')" -ForegroundColor White - } - - if ($WhatIfPreference) { - Write-Host "`n=== WHATIF MODE - SHOWING PLANNED CHANGES ===" -ForegroundColor Yellow - } else { - Write-Host "`nStarting updates..." -ForegroundColor Cyan - } - - $successfulPackagesConfig = 0 - $successfulVcxproj = 0 - $processedCount = 0 - - foreach ($package in $targetPackages) { - $processedCount++ - - if (-not $WhatIfPreference) { - Write-Progress -Activity "Updating package versions" -Status "Processing $($package.ProjectName)" -PercentComplete (($processedCount / $targetPackages.Count) * 100) - } - - # Update packages.config - $packagesConfigSuccess = Update-PackagesConfig -PackagesConfigPath $package.PackagesConfigPath -PackageName $PackageName -OldVersion $package.Version -NewVersion $NewVersion -TargetFramework $package.TargetFramework -CreateBackup $Backup.ToBool() -WhatIfMode $WhatIfPreference - - if ($packagesConfigSuccess) { - $successfulPackagesConfig++ - } - - # Update .vcxproj file - $vcxprojSuccess = Update-VcxprojFile -VcxprojPath $package.ProjectFile -PackageName $PackageName -OldVersion $package.Version -NewVersion $NewVersion -CreateBackup $Backup.ToBool() -WhatIfMode $WhatIfPreference - - if ($vcxprojSuccess) { - $successfulVcxproj++ - } - } - - if (-not $WhatIfPreference) { - Write-Progress -Activity "Updating package versions" -Completed - } - - # Show summary - Show-UpdateSummary -TargetPackages $targetPackages -SuccessfulPackagesConfig $successfulPackagesConfig -SuccessfulVcxproj $successfulVcxproj -PackageName $PackageName -NewVersion $NewVersion - - if ($WhatIfPreference) { - Write-Host "`nTo perform these updates, run the script without -WhatIf" -ForegroundColor Yellow - } else { - Write-Host "`nUpdate process completed!" -ForegroundColor Green - - if ($Backup) { - Write-Host "Backup files (.bak) have been created for all modified files." -ForegroundColor Cyan - } - } -} -catch { - Write-Error "An error occurred during execution: $($_.Exception.Message)" - Write-Error $_.ScriptStackTrace - exit 1 -} +#Requires -Version 5.1 + +<# +.SYNOPSIS + Updates the version of a specific NuGet package across all .vcxproj files and their associated packages.config files. + +.DESCRIPTION + This script searches for all projects that use a specified NuGet package and updates both the packages.config + file and the .vcxproj file to use a new version. It uses Get-VcxprojNugetPackageVersions.ps1 to find target + projects and then performs the necessary updates. + +.PARAMETER PackageName + The name of the NuGet package to update (e.g., "Newtonsoft.Json"). + +.PARAMETER NewVersion + The new version to update to (e.g., "13.0.3"). + +.PARAMETER WhatIf + Shows what changes would be made without actually making them. + +.PARAMETER Backup + Creates backup files (.bak) before making changes. + +.EXAMPLE + .\Update-VcxprojNugetPackageVersions.ps1 -PackageName "Newtonsoft.Json" -NewVersion "13.0.3" + +.EXAMPLE + .\Update-VcxprojNugetPackageVersions.ps1 -PackageName "Microsoft.VisualStudio.TestTools.UnitTesting" -NewVersion "2.2.8" -WhatIf + +.EXAMPLE + .\Update-VcxprojNugetPackageVersions.ps1 -PackageName "boost" -NewVersion "1.82.0" -Backup:$false +#> + +[CmdletBinding(SupportsShouldProcess)] +param( + [Parameter(Mandatory = $true)] + [string]$PackageName, + + [Parameter(Mandatory = $true)] + [string]$NewVersion, + + [switch]$Backup +) + +# Import the Get-VcxprojNugetPackageVersions script +$getPackagesScript = Join-Path $PSScriptRoot "Get-VcxprojNugetPackageVersions.ps1" + +if (-not (Test-Path $getPackagesScript)) { + Write-Error "Could not find Get-VcxprojNugetPackageVersions.ps1 in the same directory as this script." + exit 1 +} + +function Update-PackagesConfig { + param( + [string]$PackagesConfigPath, + [string]$PackageName, + [string]$OldVersion, + [string]$NewVersion, + [string]$TargetFramework, + [bool]$CreateBackup = $true, + [bool]$WhatIfMode = $false + ) + + if (-not (Test-Path $PackagesConfigPath)) { + Write-Warning "packages.config not found: $PackagesConfigPath" + return $false + } + + try { + # Create backup if requested + if ($CreateBackup -and -not $WhatIfMode) { + $backupPath = "$PackagesConfigPath.bak" + Copy-Item $PackagesConfigPath $backupPath -Force + Write-Verbose "Created backup: $backupPath" + } + + # Load and parse the XML + [xml]$packagesXml = Get-Content $PackagesConfigPath -ErrorAction Stop + + $packageNode = $packagesXml.packages.package | Where-Object { $_.id -eq $PackageName } + + if (-not $packageNode) { + Write-Warning "Package '$PackageName' not found in $PackagesConfigPath" + return $false + } + + $currentVersion = $packageNode.version + + # Skip if version is already the target version + if ($currentVersion -eq $NewVersion) { + Write-Verbose "Skipping $PackagesConfigPath - version is already $NewVersion" + return $false + } + + if ($WhatIfMode) { + Write-Host "WHATIF: Would update $PackagesConfigPath" -ForegroundColor Yellow + Write-Host " Package: $PackageName" -ForegroundColor Cyan + Write-Host " Current Version: $currentVersion" -ForegroundColor Red + Write-Host " New Version: $NewVersion" -ForegroundColor Green + return $true + } + + # Update the version + $packageNode.version = $NewVersion + + # Save the updated XML + $packagesXml.Save($PackagesConfigPath) + + Write-Host "Updated packages.config: $PackagesConfigPath" -ForegroundColor Green + Write-Host " ${PackageName}: $currentVersion → $NewVersion" -ForegroundColor Cyan + + return $true + } + catch { + Write-Error "Failed to update packages.config '$PackagesConfigPath': $($_.Exception.Message)" + return $false + } +} + +function Update-VcxprojFile { + param( + [string]$VcxprojPath, + [string]$PackageName, + [string]$OldVersion, + [string]$NewVersion, + [bool]$CreateBackup = $true, + [bool]$WhatIfMode = $false + ) + + if (-not (Test-Path $VcxprojPath)) { + Write-Warning ".vcxproj file not found: $VcxprojPath" + return $false + } + + # Skip if version is already the target version + if ($OldVersion -eq $NewVersion) { + Write-Verbose "Skipping $VcxprojPath - version is already $NewVersion" + return $false + } + + try { + # Read the file content + $content = Get-Content $VcxprojPath -Raw -ErrorAction Stop + + # Pattern to match package references in the format "PACKAGE-NAME.VERSION" + $oldPattern = [regex]::Escape("$PackageName.$OldVersion") + $newPattern = "$PackageName.$NewVersion" + + # Check if there are any matches + if ($content -notmatch $oldPattern) { + Write-Verbose "No references to $PackageName.$OldVersion found in $VcxprojPath" + return $false + } + + if ($WhatIfMode) { + $regexMatches = [regex]::Matches($content, $oldPattern) + Write-Host "WHATIF: Would update $VcxprojPath" -ForegroundColor Yellow + Write-Host " Found $($regexMatches.Count) reference(s) to update:" -ForegroundColor Cyan + Write-Host " $oldPattern → $newPattern" -ForegroundColor Cyan + return $true + } + + # Create backup if requested + if ($CreateBackup) { + $backupPath = "$VcxprojPath.bak" + Copy-Item $VcxprojPath $backupPath -Force + Write-Verbose "Created backup: $backupPath" + } + + # Replace all occurrences + $updatedContent = $content -replace $oldPattern, $newPattern + + # Count the number of replacements made + $originalMatches = [regex]::Matches($content, $oldPattern).Count + $remainingMatches = [regex]::Matches($updatedContent, $oldPattern).Count + $replacementCount = $originalMatches - $remainingMatches + + # Save the updated content + Set-Content $VcxprojPath -Value $updatedContent -NoNewline -Encoding UTF8 -ErrorAction Stop + + Write-Host "Updated .vcxproj file: $VcxprojPath" -ForegroundColor Green + Write-Host " Replaced $replacementCount reference(s): $oldPattern → $newPattern" -ForegroundColor Cyan + + return $true + } + catch { + Write-Error "Failed to update .vcxproj file '$VcxprojPath': $($_.Exception.Message)" + return $false + } +} + +function Show-UpdateSummary { + param( + [array]$TargetPackages, + [int]$SuccessfulPackagesConfig, + [int]$SuccessfulVcxproj, + [string]$PackageName, + [string]$NewVersion + ) + + Write-Host "`n=== UPDATE SUMMARY ===" -ForegroundColor Cyan + Write-Host "Package: $PackageName" -ForegroundColor White + Write-Host "New Version: $NewVersion" -ForegroundColor White + Write-Host "Projects found with package: $($TargetPackages.Count)" -ForegroundColor White + Write-Host "packages.config files updated: $SuccessfulPackagesConfig" -ForegroundColor Green + Write-Host ".vcxproj files updated: $SuccessfulVcxproj" -ForegroundColor Green + + # Show unique versions being replaced + $uniqueVersions = $TargetPackages | Group-Object Version | Sort-Object Name + if ($uniqueVersions.Count -gt 0) { + Write-Host "`nVersions being replaced:" -ForegroundColor Cyan + foreach ($version in $uniqueVersions) { + Write-Host " $($version.Name) (in $($version.Count) project(s))" -ForegroundColor Yellow + } + } +} + +# Main execution +try { + Write-Host "Starting package version update process..." -ForegroundColor Cyan + Write-Host "Package: $PackageName" -ForegroundColor White + Write-Host "New Version: $NewVersion" -ForegroundColor White + + if ($WhatIfPreference) { + Write-Host "Mode: WHATIF (no changes will be made)" -ForegroundColor Yellow + } else { + Write-Host "Backup files: $(if ($Backup) { 'Enabled' } else { 'Disabled' })" -ForegroundColor White + } + + Write-Host "`nSearching for projects using package '$PackageName'..." -ForegroundColor Cyan + + # Use the Get-VcxprojNugetPackageVersions script to find target packages + $targetPackages = & $getPackagesScript -PackageFilter $PackageName -OutputFormat Object + + if ($targetPackages.Count -eq 0) { + Write-Warning "No projects found using package '$PackageName'." + return + } + + Write-Host "Found $($targetPackages.Count) reference(s) to package '$PackageName'" -ForegroundColor Green + + # Group by project to avoid duplicate processing + $projectGroups = $targetPackages | Group-Object ProjectFile + + Write-Host "`nProjects with package:" -ForegroundColor Cyan + foreach ($group in $projectGroups) { + $project = $group.Group[0] # Get the first package info for project details + Write-Host " $($project.ProjectName) - Version(s): $($group.Group.Version -join ', ')" -ForegroundColor White + } + + if ($WhatIfPreference) { + Write-Host "`n=== WHATIF MODE - SHOWING PLANNED CHANGES ===" -ForegroundColor Yellow + } else { + Write-Host "`nStarting updates..." -ForegroundColor Cyan + } + + $successfulPackagesConfig = 0 + $successfulVcxproj = 0 + $processedCount = 0 + + foreach ($package in $targetPackages) { + $processedCount++ + + if (-not $WhatIfPreference) { + Write-Progress -Activity "Updating package versions" -Status "Processing $($package.ProjectName)" -PercentComplete (($processedCount / $targetPackages.Count) * 100) + } + + # Update packages.config + $packagesConfigSuccess = Update-PackagesConfig -PackagesConfigPath $package.PackagesConfigPath -PackageName $PackageName -OldVersion $package.Version -NewVersion $NewVersion -TargetFramework $package.TargetFramework -CreateBackup $Backup.ToBool() -WhatIfMode $WhatIfPreference + + if ($packagesConfigSuccess) { + $successfulPackagesConfig++ + } + + # Update .vcxproj file + $vcxprojSuccess = Update-VcxprojFile -VcxprojPath $package.ProjectFile -PackageName $PackageName -OldVersion $package.Version -NewVersion $NewVersion -CreateBackup $Backup.ToBool() -WhatIfMode $WhatIfPreference + + if ($vcxprojSuccess) { + $successfulVcxproj++ + } + } + + if (-not $WhatIfPreference) { + Write-Progress -Activity "Updating package versions" -Completed + } + + # Show summary + Show-UpdateSummary -TargetPackages $targetPackages -SuccessfulPackagesConfig $successfulPackagesConfig -SuccessfulVcxproj $successfulVcxproj -PackageName $PackageName -NewVersion $NewVersion + + if ($WhatIfPreference) { + Write-Host "`nTo perform these updates, run the script without -WhatIf" -ForegroundColor Yellow + } else { + Write-Host "`nUpdate process completed!" -ForegroundColor Green + + if ($Backup) { + Write-Host "Backup files (.bak) have been created for all modified files." -ForegroundColor Cyan + } + } +} +catch { + Write-Error "An error occurred during execution: $($_.Exception.Message)" + Write-Error $_.ScriptStackTrace + exit 1 +} diff --git a/src/VcpkgCustomTriplets/arm64-release-static.cmake b/src/VcpkgCustomTriplets/arm64-release-static.cmake index 4b3429d32d..7be8927cea 100644 --- a/src/VcpkgCustomTriplets/arm64-release-static.cmake +++ b/src/VcpkgCustomTriplets/arm64-release-static.cmake @@ -1,4 +1,4 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE static) -set(VCPKG_BUILD_TYPE release) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE static) +set(VCPKG_BUILD_TYPE release) diff --git a/src/VcpkgCustomTriplets/arm64-release.cmake b/src/VcpkgCustomTriplets/arm64-release.cmake index 5245cceee6..6b29972166 100644 --- a/src/VcpkgCustomTriplets/arm64-release.cmake +++ b/src/VcpkgCustomTriplets/arm64-release.cmake @@ -1,4 +1,4 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE dynamic) -set(VCPKG_BUILD_TYPE release) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_BUILD_TYPE release) diff --git a/src/VcpkgCustomTriplets/arm64.cmake b/src/VcpkgCustomTriplets/arm64.cmake index 9d6a505c42..46362afa33 100644 --- a/src/VcpkgCustomTriplets/arm64.cmake +++ b/src/VcpkgCustomTriplets/arm64.cmake @@ -1,3 +1,3 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE arm64) -set(VCPKG_CRT_LINKAGE dynamic) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE arm64) +set(VCPKG_CRT_LINKAGE dynamic) diff --git a/src/VcpkgCustomTriplets/x64-release-static.cmake b/src/VcpkgCustomTriplets/x64-release-static.cmake index 674f376c60..5a39be7f63 100644 --- a/src/VcpkgCustomTriplets/x64-release-static.cmake +++ b/src/VcpkgCustomTriplets/x64-release-static.cmake @@ -1,4 +1,4 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE static) -set(VCPKG_BUILD_TYPE release) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE static) +set(VCPKG_BUILD_TYPE release) diff --git a/src/VcpkgCustomTriplets/x64-release.cmake b/src/VcpkgCustomTriplets/x64-release.cmake index 4e503bc0f2..7c89dfc29b 100644 --- a/src/VcpkgCustomTriplets/x64-release.cmake +++ b/src/VcpkgCustomTriplets/x64-release.cmake @@ -1,4 +1,4 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) -set(VCPKG_BUILD_TYPE release) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_BUILD_TYPE release) diff --git a/src/VcpkgCustomTriplets/x64.cmake b/src/VcpkgCustomTriplets/x64.cmake index bba00de546..c2fe3f72d9 100644 --- a/src/VcpkgCustomTriplets/x64.cmake +++ b/src/VcpkgCustomTriplets/x64.cmake @@ -1,3 +1,3 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE x64) -set(VCPKG_CRT_LINKAGE dynamic) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE x64) +set(VCPKG_CRT_LINKAGE dynamic) diff --git a/src/VcpkgCustomTriplets/x86-release-static.cmake b/src/VcpkgCustomTriplets/x86-release-static.cmake index 757ecca277..9e1b22e3f1 100644 --- a/src/VcpkgCustomTriplets/x86-release-static.cmake +++ b/src/VcpkgCustomTriplets/x86-release-static.cmake @@ -1,4 +1,4 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE x86) -set(VCPKG_CRT_LINKAGE static) -set(VCPKG_BUILD_TYPE release) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE static) +set(VCPKG_BUILD_TYPE release) diff --git a/src/VcpkgCustomTriplets/x86-release.cmake b/src/VcpkgCustomTriplets/x86-release.cmake index 8647144df4..adc2f6dca4 100644 --- a/src/VcpkgCustomTriplets/x86-release.cmake +++ b/src/VcpkgCustomTriplets/x86-release.cmake @@ -1,4 +1,4 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE x86) -set(VCPKG_CRT_LINKAGE dynamic) -set(VCPKG_BUILD_TYPE release) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE dynamic) +set(VCPKG_BUILD_TYPE release) diff --git a/src/VcpkgCustomTriplets/x86.cmake b/src/VcpkgCustomTriplets/x86.cmake index 9dea5560b7..8f29fbdfbd 100644 --- a/src/VcpkgCustomTriplets/x86.cmake +++ b/src/VcpkgCustomTriplets/x86.cmake @@ -1,3 +1,3 @@ -include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") -set(VCPKG_TARGET_ARCHITECTURE x86) -set(VCPKG_CRT_LINKAGE dynamic) +include("${CMAKE_CURRENT_LIST_DIR}/common.cmake") +set(VCPKG_TARGET_ARCHITECTURE x86) +set(VCPKG_CRT_LINKAGE dynamic) diff --git a/src/WinGetMCPServer/Exceptions/ToolResponseException.cs b/src/WinGetMCPServer/Exceptions/ToolResponseException.cs index 18fb323b86..e614a3664f 100644 --- a/src/WinGetMCPServer/Exceptions/ToolResponseException.cs +++ b/src/WinGetMCPServer/Exceptions/ToolResponseException.cs @@ -1,23 +1,23 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetMCPServer.Exceptions -{ - using ModelContextProtocol.Protocol; - - /// - /// An exception that contains a tool response. - /// - internal class ToolResponseException : Exception - { - public ToolResponseException(CallToolResult toolResponse) - { - this.Response = toolResponse; - } - - public CallToolResult Response { get; private set; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetMCPServer.Exceptions +{ + using ModelContextProtocol.Protocol; + + /// + /// An exception that contains a tool response. + /// + internal class ToolResponseException : Exception + { + public ToolResponseException(CallToolResult toolResponse) + { + this.Response = toolResponse; + } + + public CallToolResult Response { get; private set; } + } +} diff --git a/src/WinGetMCPServer/Extensions/PackageListExtensions.cs b/src/WinGetMCPServer/Extensions/PackageListExtensions.cs index 4944f3c46a..ca0d292771 100644 --- a/src/WinGetMCPServer/Extensions/PackageListExtensions.cs +++ b/src/WinGetMCPServer/Extensions/PackageListExtensions.cs @@ -13,12 +13,12 @@ namespace WinGetMCPServer.Extensions /// Extensions for List. ///
internal static class PackageListExtensions - { - /// - /// Adds the packages from a find to the list. - /// - /// The list to add to. - /// A find result. + { + /// + /// Adds the packages from a find to the list. + /// + /// The list to add to. + /// A find result. /// The list. static public List AddPackages(this List list, FindPackagesResult findResult) { @@ -28,16 +28,16 @@ static public List AddPackages(this List l } return list; - } - - /// - /// Creates a from the specified . - /// - /// The method populates the with details such as the - /// package identifier, name, installation status, and update availability. If the package is installed, - /// additional information such as the catalog name and installed location is included. - /// The catalog package from which to create the result. Cannot be null. - /// A representing the state of the specified catalog package, including + } + + /// + /// Creates a from the specified . + /// + /// The method populates the with details such as the + /// package identifier, name, installation status, and update availability. If the package is installed, + /// additional information such as the catalog name and installed location is included. + /// The catalog package from which to create the result. Cannot be null. + /// A representing the state of the specified catalog package, including /// installation status and available updates. static public FindPackageResult FindPackageResultFromCatalogPackage(CatalogPackage package) { diff --git a/src/WinGetMCPServer/Program.cs b/src/WinGetMCPServer/Program.cs index 55f7c766d6..e10538c84d 100644 --- a/src/WinGetMCPServer/Program.cs +++ b/src/WinGetMCPServer/Program.cs @@ -1,35 +1,35 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetMCPServer -{ - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Hosting; - using Microsoft.Extensions.Logging; - using ModelContextProtocol.Protocol; - - internal class Program - { - private const string ServerName = "winget-mcp"; - - static void Main(string[] args) - { - var builder = Host.CreateApplicationBuilder(); - builder.Logging.AddConsole(consoleOptions => { consoleOptions.LogToStandardErrorThreshold = LogLevel.Trace; }); - - builder.Services - .AddMcpServer(configureOptions => - { - // TODO: More options setup? - configureOptions.ServerInfo = new Implementation() { Name = ServerName, Version = ServerConnection.Instance.Version }; - }) - .WithStdioServerTransport() - .WithTools(); - - builder.Build().Run(); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetMCPServer +{ + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Hosting; + using Microsoft.Extensions.Logging; + using ModelContextProtocol.Protocol; + + internal class Program + { + private const string ServerName = "winget-mcp"; + + static void Main(string[] args) + { + var builder = Host.CreateApplicationBuilder(); + builder.Logging.AddConsole(consoleOptions => { consoleOptions.LogToStandardErrorThreshold = LogLevel.Trace; }); + + builder.Services + .AddMcpServer(configureOptions => + { + // TODO: More options setup? + configureOptions.ServerInfo = new Implementation() { Name = ServerName, Version = ServerConnection.Instance.Version }; + }) + .WithStdioServerTransport() + .WithTools(); + + builder.Build().Run(); + } + } +} diff --git a/src/WinGetMCPServer/Response/PackageResponse.cs b/src/WinGetMCPServer/Response/PackageResponse.cs index 520a83a33a..6888f8fe88 100644 --- a/src/WinGetMCPServer/Response/PackageResponse.cs +++ b/src/WinGetMCPServer/Response/PackageResponse.cs @@ -1,256 +1,256 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetMCPServer.Response -{ - using Microsoft.Management.Deployment; - using ModelContextProtocol.Protocol; - using WinGetMCPServer.Extensions; - - /// - /// Contains reusable responses for package tools. - /// - internal static class PackageResponse - { - /// - /// Creates a response for a ConnectResult error. - /// - /// The connect result. - /// The response. - public static CallToolResult ForConnectError(ConnectResult connectResult) - { - return new CallToolResult() - { - IsError = true, - Content = [new TextContentBlock() { Text = $"Failed when connecting to the package source with error: {connectResult.ExtendedErrorCode.Message} [0x{connectResult.ExtendedErrorCode.HResult:X8}]" }], - }; - } - - /// - /// Creates a response for a FindPackagesResult error. - /// - /// The find packages result. - /// The response. - public static CallToolResult ForFindError(FindPackagesResult findResult) - { - return new CallToolResult() - { - IsError = true, - Content = [new TextContentBlock() { Text = $"Failed when finding packages with reason {findResult.Status} and error: {findResult.ExtendedErrorCode.Message} [0x{findResult.ExtendedErrorCode.HResult:X8}]" }], - }; - } - - /// - /// Creates a response that indicates the operation was cancelled before any changes were made. - /// - /// The response. - public static CallToolResult ForCancelBeforeSystemChange() - { - return new CallToolResult() - { - IsError = true, - Content = [new TextContentBlock() { Text = $"The operation was cancelled before any system change was started" }], - }; - } - - /// - /// Creates a response for not finding any packages. - /// - /// The identifier used when searching. - /// The source that was searched. - /// The response. - public static CallToolResult ForEmptyFind(string identifer, string? source) - { - PackageIdentityErrorResult result = new() - { - Message = "Did not find a package with the requested identifier", - Identifier = identifer, - Source = source, - }; - - return ToolResponse.FromObject(result); - } - - /// - /// Creates a response for finding multiple packages when only 1 is required. - /// - /// The identifier used when searching. - /// The source that was searched. - /// The result that contains multiple packages. - /// The response. - public static CallToolResult ForMultiFind(string identifer, string? source, FindPackagesResult findResult) - { - PackageIdentityErrorResult result = new() - { - Message = "Found multiple packages matching the requested identifier; provide a more specific identifier and/or source", - Identifier = identifer, - Source = source, - }; - - result.Packages.AddPackages(findResult); - - return ToolResponse.FromObject(result); - } - - /// - /// Creates a response for a package that is not installed. - /// - /// The identifier used when searching. - /// The source that was searched. - /// The response. - public static CallToolResult ForNotInstalled(string identifier, string? source) - { - PackageIdentityErrorResult result = new() - { - Message = "The package is not installed; use install-winget-package to install it", - Identifier = identifier, - Source = source, - }; - - return ToolResponse.FromObject(result, isError: true); - } - - /// - /// Creates a response for an upgrade operation. - /// - /// The upgrade operation result. - /// The post-upgrade package data. - /// The response. - public static CallToolResult ForUpgradeOperation(InstallResult installResult, FindPackagesResult? findResult) - { - InstallOperationResult result = new InstallOperationResult(); - - switch (installResult.Status) - { - case InstallResultStatus.Ok: - result.Message = "Upgrade completed successfully"; - break; - case InstallResultStatus.BlockedByPolicy: - result.Message = "Upgrade was blocked by policy"; - break; - case InstallResultStatus.CatalogError: - result.Message = "An error occurred with the source"; - break; - case InstallResultStatus.InternalError: - result.Message = "An internal WinGet error occurred"; - break; - case InstallResultStatus.InvalidOptions: - result.Message = "The upgrade options were invalid"; - break; - case InstallResultStatus.DownloadError: - result.Message = "An error occurred while downloading the package installer"; - break; - case InstallResultStatus.InstallError: - result.Message = "The package installer failed during the upgrade"; - break; - case InstallResultStatus.ManifestError: - result.Message = "The package manifest was invalid"; - break; - case InstallResultStatus.NoApplicableInstallers: - result.Message = "No applicable package installers were available for this system"; - break; - case InstallResultStatus.NoApplicableUpgrade: - result.Message = "No applicable upgrade was available for this system"; - break; - case InstallResultStatus.PackageAgreementsNotAccepted: - result.Message = "The package requires accepting agreements; please upgrade manually"; - break; - default: - result.Message = "Unknown upgrade status"; - break; - } - - if (installResult.RebootRequired) - { - result.RebootRequired = true; - } - - result.ErrorCode = installResult.ExtendedErrorCode?.HResult; - - if (installResult.Status == InstallResultStatus.InstallError) - { - result.InstallerErrorCode = installResult.InstallerErrorCode; - } - - if (findResult != null && findResult.Status == FindPackagesResultStatus.Ok && findResult.Matches?.Count == 1) - { - result.InstalledPackageInformation = PackageListExtensions.FindPackageResultFromCatalogPackage(findResult.Matches[0].CatalogPackage); - } - - return ToolResponse.FromObject(result, installResult.Status != InstallResultStatus.Ok); - } - - /// - /// Creates a response for an install operation. - /// - /// The install operation result. - /// The post-install package data. - /// The response. - public static CallToolResult ForInstallOperation(InstallResult installResult, FindPackagesResult? findResult) - { - InstallOperationResult result = new InstallOperationResult(); - - switch (installResult.Status) - { - case InstallResultStatus.Ok: - result.Message = "Install completed successfully"; - break; - case InstallResultStatus.BlockedByPolicy: - result.Message = "Installation was blocked by policy"; - break; - case InstallResultStatus.CatalogError: - result.Message = "An error occurred with the source"; - break; - case InstallResultStatus.InternalError: - result.Message = "An internal WinGet error occurred"; - break; - case InstallResultStatus.InvalidOptions: - result.Message = "The install options were invalid"; - break; - case InstallResultStatus.DownloadError: - result.Message = "An error occurred while downloading the package installer"; - break; - case InstallResultStatus.InstallError: - result.Message = "The package installer failed during installation"; - break; - case InstallResultStatus.ManifestError: - result.Message = "The package manifest was invalid"; - break; - case InstallResultStatus.NoApplicableInstallers: - result.Message = "No applicable package installers were available for this system"; - break; - case InstallResultStatus.NoApplicableUpgrade: - result.Message = "No applicable upgrade was available for this system"; - break; - case InstallResultStatus.PackageAgreementsNotAccepted: - result.Message = "The package requires accepting agreements; please install manually"; - break; - default: - result.Message = "Unknown install status"; - break; - } - - if (installResult.RebootRequired) - { - result.RebootRequired = true; - } - - result.ErrorCode = installResult.ExtendedErrorCode?.HResult; - - if (installResult.Status == InstallResultStatus.InstallError) - { - result.InstallerErrorCode = installResult.InstallerErrorCode; - } - - if (findResult != null && findResult.Status == FindPackagesResultStatus.Ok && findResult.Matches?.Count == 1) - { - result.InstalledPackageInformation = PackageListExtensions.FindPackageResultFromCatalogPackage(findResult.Matches[0].CatalogPackage); - } - - return ToolResponse.FromObject(result, installResult.Status != InstallResultStatus.Ok); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetMCPServer.Response +{ + using Microsoft.Management.Deployment; + using ModelContextProtocol.Protocol; + using WinGetMCPServer.Extensions; + + /// + /// Contains reusable responses for package tools. + /// + internal static class PackageResponse + { + /// + /// Creates a response for a ConnectResult error. + /// + /// The connect result. + /// The response. + public static CallToolResult ForConnectError(ConnectResult connectResult) + { + return new CallToolResult() + { + IsError = true, + Content = [new TextContentBlock() { Text = $"Failed when connecting to the package source with error: {connectResult.ExtendedErrorCode.Message} [0x{connectResult.ExtendedErrorCode.HResult:X8}]" }], + }; + } + + /// + /// Creates a response for a FindPackagesResult error. + /// + /// The find packages result. + /// The response. + public static CallToolResult ForFindError(FindPackagesResult findResult) + { + return new CallToolResult() + { + IsError = true, + Content = [new TextContentBlock() { Text = $"Failed when finding packages with reason {findResult.Status} and error: {findResult.ExtendedErrorCode.Message} [0x{findResult.ExtendedErrorCode.HResult:X8}]" }], + }; + } + + /// + /// Creates a response that indicates the operation was cancelled before any changes were made. + /// + /// The response. + public static CallToolResult ForCancelBeforeSystemChange() + { + return new CallToolResult() + { + IsError = true, + Content = [new TextContentBlock() { Text = $"The operation was cancelled before any system change was started" }], + }; + } + + /// + /// Creates a response for not finding any packages. + /// + /// The identifier used when searching. + /// The source that was searched. + /// The response. + public static CallToolResult ForEmptyFind(string identifer, string? source) + { + PackageIdentityErrorResult result = new() + { + Message = "Did not find a package with the requested identifier", + Identifier = identifer, + Source = source, + }; + + return ToolResponse.FromObject(result); + } + + /// + /// Creates a response for finding multiple packages when only 1 is required. + /// + /// The identifier used when searching. + /// The source that was searched. + /// The result that contains multiple packages. + /// The response. + public static CallToolResult ForMultiFind(string identifer, string? source, FindPackagesResult findResult) + { + PackageIdentityErrorResult result = new() + { + Message = "Found multiple packages matching the requested identifier; provide a more specific identifier and/or source", + Identifier = identifer, + Source = source, + }; + + result.Packages.AddPackages(findResult); + + return ToolResponse.FromObject(result); + } + + /// + /// Creates a response for a package that is not installed. + /// + /// The identifier used when searching. + /// The source that was searched. + /// The response. + public static CallToolResult ForNotInstalled(string identifier, string? source) + { + PackageIdentityErrorResult result = new() + { + Message = "The package is not installed; use install-winget-package to install it", + Identifier = identifier, + Source = source, + }; + + return ToolResponse.FromObject(result, isError: true); + } + + /// + /// Creates a response for an upgrade operation. + /// + /// The upgrade operation result. + /// The post-upgrade package data. + /// The response. + public static CallToolResult ForUpgradeOperation(InstallResult installResult, FindPackagesResult? findResult) + { + InstallOperationResult result = new InstallOperationResult(); + + switch (installResult.Status) + { + case InstallResultStatus.Ok: + result.Message = "Upgrade completed successfully"; + break; + case InstallResultStatus.BlockedByPolicy: + result.Message = "Upgrade was blocked by policy"; + break; + case InstallResultStatus.CatalogError: + result.Message = "An error occurred with the source"; + break; + case InstallResultStatus.InternalError: + result.Message = "An internal WinGet error occurred"; + break; + case InstallResultStatus.InvalidOptions: + result.Message = "The upgrade options were invalid"; + break; + case InstallResultStatus.DownloadError: + result.Message = "An error occurred while downloading the package installer"; + break; + case InstallResultStatus.InstallError: + result.Message = "The package installer failed during the upgrade"; + break; + case InstallResultStatus.ManifestError: + result.Message = "The package manifest was invalid"; + break; + case InstallResultStatus.NoApplicableInstallers: + result.Message = "No applicable package installers were available for this system"; + break; + case InstallResultStatus.NoApplicableUpgrade: + result.Message = "No applicable upgrade was available for this system"; + break; + case InstallResultStatus.PackageAgreementsNotAccepted: + result.Message = "The package requires accepting agreements; please upgrade manually"; + break; + default: + result.Message = "Unknown upgrade status"; + break; + } + + if (installResult.RebootRequired) + { + result.RebootRequired = true; + } + + result.ErrorCode = installResult.ExtendedErrorCode?.HResult; + + if (installResult.Status == InstallResultStatus.InstallError) + { + result.InstallerErrorCode = installResult.InstallerErrorCode; + } + + if (findResult != null && findResult.Status == FindPackagesResultStatus.Ok && findResult.Matches?.Count == 1) + { + result.InstalledPackageInformation = PackageListExtensions.FindPackageResultFromCatalogPackage(findResult.Matches[0].CatalogPackage); + } + + return ToolResponse.FromObject(result, installResult.Status != InstallResultStatus.Ok); + } + + /// + /// Creates a response for an install operation. + /// + /// The install operation result. + /// The post-install package data. + /// The response. + public static CallToolResult ForInstallOperation(InstallResult installResult, FindPackagesResult? findResult) + { + InstallOperationResult result = new InstallOperationResult(); + + switch (installResult.Status) + { + case InstallResultStatus.Ok: + result.Message = "Install completed successfully"; + break; + case InstallResultStatus.BlockedByPolicy: + result.Message = "Installation was blocked by policy"; + break; + case InstallResultStatus.CatalogError: + result.Message = "An error occurred with the source"; + break; + case InstallResultStatus.InternalError: + result.Message = "An internal WinGet error occurred"; + break; + case InstallResultStatus.InvalidOptions: + result.Message = "The install options were invalid"; + break; + case InstallResultStatus.DownloadError: + result.Message = "An error occurred while downloading the package installer"; + break; + case InstallResultStatus.InstallError: + result.Message = "The package installer failed during installation"; + break; + case InstallResultStatus.ManifestError: + result.Message = "The package manifest was invalid"; + break; + case InstallResultStatus.NoApplicableInstallers: + result.Message = "No applicable package installers were available for this system"; + break; + case InstallResultStatus.NoApplicableUpgrade: + result.Message = "No applicable upgrade was available for this system"; + break; + case InstallResultStatus.PackageAgreementsNotAccepted: + result.Message = "The package requires accepting agreements; please install manually"; + break; + default: + result.Message = "Unknown install status"; + break; + } + + if (installResult.RebootRequired) + { + result.RebootRequired = true; + } + + result.ErrorCode = installResult.ExtendedErrorCode?.HResult; + + if (installResult.Status == InstallResultStatus.InstallError) + { + result.InstallerErrorCode = installResult.InstallerErrorCode; + } + + if (findResult != null && findResult.Status == FindPackagesResultStatus.Ok && findResult.Matches?.Count == 1) + { + result.InstalledPackageInformation = PackageListExtensions.FindPackageResultFromCatalogPackage(findResult.Matches[0].CatalogPackage); + } + + return ToolResponse.FromObject(result, installResult.Status != InstallResultStatus.Ok); + } + } +} diff --git a/src/WinGetMCPServer/Response/ToolResponse.cs b/src/WinGetMCPServer/Response/ToolResponse.cs index 96effe49ac..799cf687b0 100644 --- a/src/WinGetMCPServer/Response/ToolResponse.cs +++ b/src/WinGetMCPServer/Response/ToolResponse.cs @@ -1,80 +1,80 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetMCPServer.Response -{ - using Microsoft.WinGet.SharedLib.PolicySettings; - using ModelContextProtocol.Protocol; - using System.Text.Json; - using System.Text.Json.Serialization; - using WinGetMCPServer.Exceptions; - using static System.Runtime.InteropServices.JavaScript.JSType; - - /// - /// Contains reusable responses for tools. - /// - internal static class ToolResponse - { - /// - /// Checks whether the server is disabled by group policy. - /// - public static void CheckGroupPolicy() - { - if (!GroupPolicy.GetInstance().IsEnabled(Policy.McpServer)) - { - throw new ToolResponseException(new CallToolResult() - { - IsError = true, - Content = [new TextContentBlock() { Text = "The Windows Package Manager MCP server is disabled by group policy." }] - }); - } - } - - /// - /// Constructs a response from an object. - /// - /// The object to return in the response. - /// Whether or not the response is an error. - /// The response. - public static CallToolResult FromObject(object value, bool isError = false) - { - return FromObject(value, isError, GetDefaultJsonOptions()); - } - - /// - /// Constructs a response from an object. - /// - /// The object to return in the response. - /// Whether or not the response is an error. - /// The JSON serializer options for serializing the object. - /// The response. - public static CallToolResult FromObject(object value, bool isError, JsonSerializerOptions jsonSerializerOptions) - { - return new CallToolResult() - { - IsError = isError, - Content = [new TextContentBlock() { Text = JsonSerializer.Serialize(value, GetDefaultJsonOptions()) }] - }; - } - - /// - /// Gets the default serialization options. - /// - /// The default serialization options. - public static JsonSerializerOptions GetDefaultJsonOptions() - { - return new JsonSerializerOptions() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - Converters = - { - new JsonStringEnumConverter(), - }, - }; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetMCPServer.Response +{ + using Microsoft.WinGet.SharedLib.PolicySettings; + using ModelContextProtocol.Protocol; + using System.Text.Json; + using System.Text.Json.Serialization; + using WinGetMCPServer.Exceptions; + using static System.Runtime.InteropServices.JavaScript.JSType; + + /// + /// Contains reusable responses for tools. + /// + internal static class ToolResponse + { + /// + /// Checks whether the server is disabled by group policy. + /// + public static void CheckGroupPolicy() + { + if (!GroupPolicy.GetInstance().IsEnabled(Policy.McpServer)) + { + throw new ToolResponseException(new CallToolResult() + { + IsError = true, + Content = [new TextContentBlock() { Text = "The Windows Package Manager MCP server is disabled by group policy." }] + }); + } + } + + /// + /// Constructs a response from an object. + /// + /// The object to return in the response. + /// Whether or not the response is an error. + /// The response. + public static CallToolResult FromObject(object value, bool isError = false) + { + return FromObject(value, isError, GetDefaultJsonOptions()); + } + + /// + /// Constructs a response from an object. + /// + /// The object to return in the response. + /// Whether or not the response is an error. + /// The JSON serializer options for serializing the object. + /// The response. + public static CallToolResult FromObject(object value, bool isError, JsonSerializerOptions jsonSerializerOptions) + { + return new CallToolResult() + { + IsError = isError, + Content = [new TextContentBlock() { Text = JsonSerializer.Serialize(value, GetDefaultJsonOptions()) }] + }; + } + + /// + /// Gets the default serialization options. + /// + /// The default serialization options. + public static JsonSerializerOptions GetDefaultJsonOptions() + { + return new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + Converters = + { + new JsonStringEnumConverter(), + }, + }; + } + } +} diff --git a/src/WinGetMCPServer/ServerConnection.cs b/src/WinGetMCPServer/ServerConnection.cs index a2f7d8f4c0..989f3374d1 100644 --- a/src/WinGetMCPServer/ServerConnection.cs +++ b/src/WinGetMCPServer/ServerConnection.cs @@ -1,41 +1,41 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetMCPServer -{ - using Microsoft.Management.Deployment; - - /// - /// Maintains the connection to the COM server. - /// - internal static class ServerConnection - { - private static PackageManager? packageManager = null; - - public static PackageManager Instance - { - get - { - if (packageManager == null) - { - packageManager = new PackageManager(); - } - - try - { - // Perform the simplest available call to check if the COM server is still active. - _ = packageManager.Version; - } - catch - { - packageManager = new PackageManager(); - } - - return packageManager; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetMCPServer +{ + using Microsoft.Management.Deployment; + + /// + /// Maintains the connection to the COM server. + /// + internal static class ServerConnection + { + private static PackageManager? packageManager = null; + + public static PackageManager Instance + { + get + { + if (packageManager == null) + { + packageManager = new PackageManager(); + } + + try + { + // Perform the simplest available call to check if the COM server is still active. + _ = packageManager.Version; + } + catch + { + packageManager = new PackageManager(); + } + + return packageManager; + } + } + } +} diff --git a/src/WinGetMCPServer/WinGetMCPServer.csproj b/src/WinGetMCPServer/WinGetMCPServer.csproj index b16d186b10..e93e39b7d3 100644 --- a/src/WinGetMCPServer/WinGetMCPServer.csproj +++ b/src/WinGetMCPServer/WinGetMCPServer.csproj @@ -1,72 +1,72 @@ - - - - Exe - 10.0.26100.0 - net8.0-windows$(TargetWindowsVersion) - enable - enable - 10.0.17763.0 - x64;x86;arm64 - $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ - true - win-x64;win-x86;win-arm64 - - - - win-x64 - - - - win-x86 - - - - win-arm64 - - - - - - - - - - - False - - - - - - - - - Microsoft.Management.Deployment; - Windows.Data.Text.TextSegmen; - Windows.Devices.Geolocation; - Windows.Foundation; - Windows.Globalization.DayOfWee; - Windows.Networking.Connectivity; - Windows.Networking.DomainNameTyp; - Windows.Networking.EndpointPai; - Windows.Networking.IEndpointPai; - Windows.Networking.HostNam; - Windows.Networking.IHostNam; - Windows.Security.Cryptography.Certificates; - Windows.Storage; - Windows.Storage.Provider.FileUpdateStatu; - Windows.System.ProcessorArchitectur; - Windows.System.Use; - Windows.System.IUse; - - - Windows.Foundation.PropertyType; - Windows.Storage.Provider; - - $(TargetWindowsVersion) - - 10.0.17763.0 - - - + + + + Exe + 10.0.26100.0 + net8.0-windows$(TargetWindowsVersion) + enable + enable + 10.0.17763.0 + x64;x86;arm64 + $(SolutionDir)$(Platform)\$(Configuration)\$(MSBuildProjectName)\ + true + win-x64;win-x86;win-arm64 + + + + win-x64 + + + + win-x86 + + + + win-arm64 + + + + + + + + + + + False + + + + + + + + + Microsoft.Management.Deployment; + Windows.Data.Text.TextSegmen; + Windows.Devices.Geolocation; + Windows.Foundation; + Windows.Globalization.DayOfWee; + Windows.Networking.Connectivity; + Windows.Networking.DomainNameTyp; + Windows.Networking.EndpointPai; + Windows.Networking.IEndpointPai; + Windows.Networking.HostNam; + Windows.Networking.IHostNam; + Windows.Security.Cryptography.Certificates; + Windows.Storage; + Windows.Storage.Provider.FileUpdateStatu; + Windows.System.ProcessorArchitectur; + Windows.System.Use; + Windows.System.IUse; + + + Windows.Foundation.PropertyType; + Windows.Storage.Provider; + + $(TargetWindowsVersion) + + 10.0.17763.0 + + + diff --git a/src/WinGetMCPServer/WingetPackageTools.cs b/src/WinGetMCPServer/WingetPackageTools.cs index 24fbbe1243..955876a9c7 100644 --- a/src/WinGetMCPServer/WingetPackageTools.cs +++ b/src/WinGetMCPServer/WingetPackageTools.cs @@ -1,333 +1,333 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetMCPServer -{ - using System.ComponentModel; - using Microsoft.Management.Deployment; - using ModelContextProtocol.Protocol; - using ModelContextProtocol.Server; - using ModelContextProtocol; - using Windows.Foundation; - using WinGetMCPServer.Extensions; - using WinGetMCPServer.Response; - using WinGetMCPServer.Exceptions; - - /// - /// WinGet package tools. - /// - [McpServerToolType] - internal class WingetPackageTools - { - private PackageManager packageManager; - - public WingetPackageTools() - { - packageManager = ServerConnection.Instance; - } - - [McpServerTool( - Name = "find-winget-packages", - Title = "Find WinGet Packages", - ReadOnly = true, - OpenWorld = false)] - [Description("Find installed and available packages using WinGet. To list all installed packages that have available upgrades (equivalent to 'winget upgrade'), call with upgradeable=true and no query. To filter upgradeable packages by name, call with upgradeable=true and a query. To search for packages to install, call with upgradeable=false and a required query.")] - public CallToolResult FindPackages( - [Description("Find packages identified by this value. Required when upgradeable is false; optionally filters results when upgradeable is true.")] string? query = null, - [Description("When true, only return installed packages that have available upgrades")] bool upgradeable = false) - { - try - { - ToolResponse.CheckGroupPolicy(); - - if (!upgradeable && string.IsNullOrEmpty(query)) - { - return new CallToolResult() - { - IsError = true, - Content = [new TextContentBlock() { Text = "A query is required when upgradeable is false" }], - }; - } - - // Use LocalCatalogs when listing upgrades to enumerate only installed packages, - // consistent with `winget upgrade`. Remote catalogs are still included in the - // composite so IsUpdateAvailable remains accurate. - var catalog = ConnectCatalog(searchBehavior: upgradeable - ? CompositeSearchBehavior.LocalCatalogs - : CompositeSearchBehavior.AllCatalogs); - - FindPackagesResult findResult; - if (string.IsNullOrEmpty(query)) - { - // This can only happen in the case that upgradeable is true, in which case this - // won't accidentally list all packages from all catalogs - findResult = FindAllPackages(catalog); - } - else - { - // First attempt a more exact match - findResult = FindForQuery(catalog, query, fullStringMatch: true); - - // If nothing is found, expand to a looser search - if ((findResult.Matches?.Count ?? 0) == 0) - { - findResult = FindForQuery(catalog, query, fullStringMatch: false); - } - } - - if (findResult.Status != FindPackagesResultStatus.Ok) - { - return PackageResponse.ForFindError(findResult); - } - - List contents = new List(); - if (upgradeable) - { - for (int i = 0; i < findResult.Matches?.Count; ++i) - { - var package = findResult.Matches[i].CatalogPackage; - if (package.IsUpdateAvailable) - { - contents.Add(PackageListExtensions.FindPackageResultFromCatalogPackage(package)); - } - } - } - else - { - contents.AddPackages(findResult); - } - - return ToolResponse.FromObject(contents); - } - catch (ToolResponseException e) - { - return e.Response; - } - } - - [McpServerTool( - Name = "install-winget-package", - Title = "Install WinGet Package", - ReadOnly = false, - Destructive = true, - Idempotent = false, - OpenWorld = false)] - [Description("Install or upgrade a package using WinGet. When upgradeOnly is true, only upgrades an already-installed package and returns an error if it is not installed. When upgradeOnly is false (default), installs the package if not present or upgrades it if already installed.")] - public async Task InstallPackage( - [Description("The identifier of the WinGet package")] string identifier, - IProgress progress, - CancellationToken cancellationToken, - [Description("The source containing the package")] string? source = null, - [Description("When true, only upgrade an already-installed package; returns an error if the package is not installed")] bool upgradeOnly = false) - { - try - { - ToolResponse.CheckGroupPolicy(); - - var packageCatalog = ConnectCatalog(source); - - if (cancellationToken.IsCancellationRequested) - { - return PackageResponse.ForCancelBeforeSystemChange(); - } - - // First attempt a more exact match - var findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: false); - - if (cancellationToken.IsCancellationRequested) - { - return PackageResponse.ForCancelBeforeSystemChange(); - } - - // If nothing is found, expand to a looser search - if ((findResult.Matches?.Count ?? 0) == 0) - { - findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: true); - } - - if (findResult.Status != FindPackagesResultStatus.Ok) - { - return PackageResponse.ForFindError(findResult); - } - - if (findResult.Matches?.Count == 0) - { - return PackageResponse.ForEmptyFind(identifier, source); - } - else if (findResult.Matches?.Count > 1) - { - return PackageResponse.ForMultiFind(identifier, source, findResult); - } - - CatalogPackage catalogPackage = findResult.Matches![0].CatalogPackage; - - if (upgradeOnly && catalogPackage.InstalledVersion == null) - { - return PackageResponse.ForNotInstalled(identifier, source); - } - - InstallOptions options = new InstallOptions(); - IAsyncOperationWithProgress? operation = null; - - if (cancellationToken.IsCancellationRequested) - { - return PackageResponse.ForCancelBeforeSystemChange(); - } - - if (catalogPackage.InstalledVersion == null) - { - operation = packageManager.InstallPackageAsync(catalogPackage, options); - } - else - { - operation = packageManager.UpgradePackageAsync(catalogPackage, options); - } - - operation.Progress = (asyncInfo, progressInfo) => progress.Report(CreateInstallProgressNotification(ref progressInfo)); - using CancellationTokenRegistration registration = cancellationToken.Register(() => operation.Cancel()); - - var installResult = await operation; - findResult = null; - - if (installResult.Status == InstallResultStatus.Ok) - { - // Send a completed progress entry in the event that async progress forwarding didn't - progress.Report(CreateInstallProgressNotification(PackageInstallProgressState.Finished, 1.0, 1.0)); - findResult = ReFindForPackage(catalogPackage.DefaultInstallVersion); - } - - return upgradeOnly - ? PackageResponse.ForUpgradeOperation(installResult, findResult) - : PackageResponse.ForInstallOperation(installResult, findResult); - } - catch (ToolResponseException e) - { - return e.Response; - } - } - - private ConnectResult ConnectCatalogWithResult(string? catalog = null, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior.AllCatalogs) - { - CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = new CreateCompositePackageCatalogOptions(); - - var catalogs = packageManager.GetPackageCatalogs(); - for (int i = 0; i < catalogs.Count; ++i) - { - var catalogRef = catalogs[i]; - if ((string.IsNullOrEmpty(catalog) && !catalogRef.Info.Explicit) - || catalogRef?.Info.Name == catalog) - { - createCompositePackageCatalogOptions.Catalogs.Add(catalogRef); - } - } - createCompositePackageCatalogOptions.CompositeSearchBehavior = searchBehavior; - - var compositeRef = packageManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); - return compositeRef.Connect(); - } - - private PackageCatalog ConnectCatalog(string? catalog = null, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior.AllCatalogs) - { - var result = ConnectCatalogWithResult(catalog, searchBehavior); - if (result.Status != ConnectResultStatus.Ok) - { - throw new ToolResponseException(PackageResponse.ForConnectError(result)); - } - return result.PackageCatalog; - } - - private FindPackagesResult FindForQuery(PackageCatalog catalog, string query, bool fullStringMatch) - { - PackageFieldMatchOption fullStringMatchOption = fullStringMatch ? PackageFieldMatchOption.EqualsCaseInsensitive : PackageFieldMatchOption.ContainsCaseInsensitive; - - FindPackagesOptions findPackageOptions = new(); - findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Id, Option = fullStringMatchOption, Value = query }); - findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Name, Option = fullStringMatchOption, Value = query }); - findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Moniker, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); - - return catalog!.FindPackages(findPackageOptions); - } - - private FindPackagesResult FindForIdentifier(PackageCatalog catalog, string query, bool expandedFields) - { - FindPackagesOptions findPackageOptions = new(); - findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Id, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); - - if (expandedFields) - { - findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Name, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); - findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Moniker, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); - } - - return catalog!.FindPackages(findPackageOptions); - } - - private FindPackagesResult FindAllPackages(PackageCatalog catalog) - { - FindPackagesOptions findPackageOptions = new(); - return catalog!.FindPackages(findPackageOptions); - } - - private FindPackagesResult? ReFindForPackage(PackageVersionInfo packageVersionInfo) - { - var connectResult = ConnectCatalogWithResult(packageVersionInfo.PackageCatalog.Info.Id); - - if (connectResult.Status != ConnectResultStatus.Ok) - { - return null; - } - - var catalog = connectResult.PackageCatalog; - - FindPackagesOptions findPackageOptions = new(); - findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Id, Option = PackageFieldMatchOption.Equals, Value = packageVersionInfo.Id }); - - return catalog!.FindPackages(findPackageOptions); - } - - private static ProgressNotificationValue CreateInstallProgressNotification(ref InstallProgress installProgress) - { - return CreateInstallProgressNotification(installProgress.State, installProgress.DownloadProgress, installProgress.InstallationProgress); - } - - private static ProgressNotificationValue CreateInstallProgressNotification(PackageInstallProgressState state, double downloadProgress, double installProgress) - { - string? message = null; - - switch (state) - { - case PackageInstallProgressState.Queued: - message = "The install operation is queued"; - break; - case PackageInstallProgressState.Downloading: - message = "The package installer is being downloaded"; - break; - case PackageInstallProgressState.Installing: - message = "The package is being installed"; - break; - case PackageInstallProgressState.PostInstall: - message = "The installation operation is wrapping up"; - break; - case PackageInstallProgressState.Finished: - message = "The install is complete"; - break; - default: - message = "Unknown install state"; - break; - } - - const float downloadPercentage = 0.8f; - - ProgressNotificationValue result = new ProgressNotificationValue() - { - Progress = (float)((downloadProgress * downloadPercentage) + (installProgress * (1.0f - downloadPercentage))), - Message = message, - }; - - return result; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetMCPServer +{ + using System.ComponentModel; + using Microsoft.Management.Deployment; + using ModelContextProtocol.Protocol; + using ModelContextProtocol.Server; + using ModelContextProtocol; + using Windows.Foundation; + using WinGetMCPServer.Extensions; + using WinGetMCPServer.Response; + using WinGetMCPServer.Exceptions; + + /// + /// WinGet package tools. + /// + [McpServerToolType] + internal class WingetPackageTools + { + private PackageManager packageManager; + + public WingetPackageTools() + { + packageManager = ServerConnection.Instance; + } + + [McpServerTool( + Name = "find-winget-packages", + Title = "Find WinGet Packages", + ReadOnly = true, + OpenWorld = false)] + [Description("Find installed and available packages using WinGet. To list all installed packages that have available upgrades (equivalent to 'winget upgrade'), call with upgradeable=true and no query. To filter upgradeable packages by name, call with upgradeable=true and a query. To search for packages to install, call with upgradeable=false and a required query.")] + public CallToolResult FindPackages( + [Description("Find packages identified by this value. Required when upgradeable is false; optionally filters results when upgradeable is true.")] string? query = null, + [Description("When true, only return installed packages that have available upgrades")] bool upgradeable = false) + { + try + { + ToolResponse.CheckGroupPolicy(); + + if (!upgradeable && string.IsNullOrEmpty(query)) + { + return new CallToolResult() + { + IsError = true, + Content = [new TextContentBlock() { Text = "A query is required when upgradeable is false" }], + }; + } + + // Use LocalCatalogs when listing upgrades to enumerate only installed packages, + // consistent with `winget upgrade`. Remote catalogs are still included in the + // composite so IsUpdateAvailable remains accurate. + var catalog = ConnectCatalog(searchBehavior: upgradeable + ? CompositeSearchBehavior.LocalCatalogs + : CompositeSearchBehavior.AllCatalogs); + + FindPackagesResult findResult; + if (string.IsNullOrEmpty(query)) + { + // This can only happen in the case that upgradeable is true, in which case this + // won't accidentally list all packages from all catalogs + findResult = FindAllPackages(catalog); + } + else + { + // First attempt a more exact match + findResult = FindForQuery(catalog, query, fullStringMatch: true); + + // If nothing is found, expand to a looser search + if ((findResult.Matches?.Count ?? 0) == 0) + { + findResult = FindForQuery(catalog, query, fullStringMatch: false); + } + } + + if (findResult.Status != FindPackagesResultStatus.Ok) + { + return PackageResponse.ForFindError(findResult); + } + + List contents = new List(); + if (upgradeable) + { + for (int i = 0; i < findResult.Matches?.Count; ++i) + { + var package = findResult.Matches[i].CatalogPackage; + if (package.IsUpdateAvailable) + { + contents.Add(PackageListExtensions.FindPackageResultFromCatalogPackage(package)); + } + } + } + else + { + contents.AddPackages(findResult); + } + + return ToolResponse.FromObject(contents); + } + catch (ToolResponseException e) + { + return e.Response; + } + } + + [McpServerTool( + Name = "install-winget-package", + Title = "Install WinGet Package", + ReadOnly = false, + Destructive = true, + Idempotent = false, + OpenWorld = false)] + [Description("Install or upgrade a package using WinGet. When upgradeOnly is true, only upgrades an already-installed package and returns an error if it is not installed. When upgradeOnly is false (default), installs the package if not present or upgrades it if already installed.")] + public async Task InstallPackage( + [Description("The identifier of the WinGet package")] string identifier, + IProgress progress, + CancellationToken cancellationToken, + [Description("The source containing the package")] string? source = null, + [Description("When true, only upgrade an already-installed package; returns an error if the package is not installed")] bool upgradeOnly = false) + { + try + { + ToolResponse.CheckGroupPolicy(); + + var packageCatalog = ConnectCatalog(source); + + if (cancellationToken.IsCancellationRequested) + { + return PackageResponse.ForCancelBeforeSystemChange(); + } + + // First attempt a more exact match + var findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: false); + + if (cancellationToken.IsCancellationRequested) + { + return PackageResponse.ForCancelBeforeSystemChange(); + } + + // If nothing is found, expand to a looser search + if ((findResult.Matches?.Count ?? 0) == 0) + { + findResult = FindForIdentifier(packageCatalog, identifier, expandedFields: true); + } + + if (findResult.Status != FindPackagesResultStatus.Ok) + { + return PackageResponse.ForFindError(findResult); + } + + if (findResult.Matches?.Count == 0) + { + return PackageResponse.ForEmptyFind(identifier, source); + } + else if (findResult.Matches?.Count > 1) + { + return PackageResponse.ForMultiFind(identifier, source, findResult); + } + + CatalogPackage catalogPackage = findResult.Matches![0].CatalogPackage; + + if (upgradeOnly && catalogPackage.InstalledVersion == null) + { + return PackageResponse.ForNotInstalled(identifier, source); + } + + InstallOptions options = new InstallOptions(); + IAsyncOperationWithProgress? operation = null; + + if (cancellationToken.IsCancellationRequested) + { + return PackageResponse.ForCancelBeforeSystemChange(); + } + + if (catalogPackage.InstalledVersion == null) + { + operation = packageManager.InstallPackageAsync(catalogPackage, options); + } + else + { + operation = packageManager.UpgradePackageAsync(catalogPackage, options); + } + + operation.Progress = (asyncInfo, progressInfo) => progress.Report(CreateInstallProgressNotification(ref progressInfo)); + using CancellationTokenRegistration registration = cancellationToken.Register(() => operation.Cancel()); + + var installResult = await operation; + findResult = null; + + if (installResult.Status == InstallResultStatus.Ok) + { + // Send a completed progress entry in the event that async progress forwarding didn't + progress.Report(CreateInstallProgressNotification(PackageInstallProgressState.Finished, 1.0, 1.0)); + findResult = ReFindForPackage(catalogPackage.DefaultInstallVersion); + } + + return upgradeOnly + ? PackageResponse.ForUpgradeOperation(installResult, findResult) + : PackageResponse.ForInstallOperation(installResult, findResult); + } + catch (ToolResponseException e) + { + return e.Response; + } + } + + private ConnectResult ConnectCatalogWithResult(string? catalog = null, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior.AllCatalogs) + { + CreateCompositePackageCatalogOptions createCompositePackageCatalogOptions = new CreateCompositePackageCatalogOptions(); + + var catalogs = packageManager.GetPackageCatalogs(); + for (int i = 0; i < catalogs.Count; ++i) + { + var catalogRef = catalogs[i]; + if ((string.IsNullOrEmpty(catalog) && !catalogRef.Info.Explicit) + || catalogRef?.Info.Name == catalog) + { + createCompositePackageCatalogOptions.Catalogs.Add(catalogRef); + } + } + createCompositePackageCatalogOptions.CompositeSearchBehavior = searchBehavior; + + var compositeRef = packageManager.CreateCompositePackageCatalog(createCompositePackageCatalogOptions); + return compositeRef.Connect(); + } + + private PackageCatalog ConnectCatalog(string? catalog = null, CompositeSearchBehavior searchBehavior = CompositeSearchBehavior.AllCatalogs) + { + var result = ConnectCatalogWithResult(catalog, searchBehavior); + if (result.Status != ConnectResultStatus.Ok) + { + throw new ToolResponseException(PackageResponse.ForConnectError(result)); + } + return result.PackageCatalog; + } + + private FindPackagesResult FindForQuery(PackageCatalog catalog, string query, bool fullStringMatch) + { + PackageFieldMatchOption fullStringMatchOption = fullStringMatch ? PackageFieldMatchOption.EqualsCaseInsensitive : PackageFieldMatchOption.ContainsCaseInsensitive; + + FindPackagesOptions findPackageOptions = new(); + findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Id, Option = fullStringMatchOption, Value = query }); + findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Name, Option = fullStringMatchOption, Value = query }); + findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Moniker, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); + + return catalog!.FindPackages(findPackageOptions); + } + + private FindPackagesResult FindForIdentifier(PackageCatalog catalog, string query, bool expandedFields) + { + FindPackagesOptions findPackageOptions = new(); + findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Id, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); + + if (expandedFields) + { + findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Name, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); + findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Moniker, Option = PackageFieldMatchOption.EqualsCaseInsensitive, Value = query }); + } + + return catalog!.FindPackages(findPackageOptions); + } + + private FindPackagesResult FindAllPackages(PackageCatalog catalog) + { + FindPackagesOptions findPackageOptions = new(); + return catalog!.FindPackages(findPackageOptions); + } + + private FindPackagesResult? ReFindForPackage(PackageVersionInfo packageVersionInfo) + { + var connectResult = ConnectCatalogWithResult(packageVersionInfo.PackageCatalog.Info.Id); + + if (connectResult.Status != ConnectResultStatus.Ok) + { + return null; + } + + var catalog = connectResult.PackageCatalog; + + FindPackagesOptions findPackageOptions = new(); + findPackageOptions.Selectors.Add(new PackageMatchFilter() { Field = PackageMatchField.Id, Option = PackageFieldMatchOption.Equals, Value = packageVersionInfo.Id }); + + return catalog!.FindPackages(findPackageOptions); + } + + private static ProgressNotificationValue CreateInstallProgressNotification(ref InstallProgress installProgress) + { + return CreateInstallProgressNotification(installProgress.State, installProgress.DownloadProgress, installProgress.InstallationProgress); + } + + private static ProgressNotificationValue CreateInstallProgressNotification(PackageInstallProgressState state, double downloadProgress, double installProgress) + { + string? message = null; + + switch (state) + { + case PackageInstallProgressState.Queued: + message = "The install operation is queued"; + break; + case PackageInstallProgressState.Downloading: + message = "The package installer is being downloaded"; + break; + case PackageInstallProgressState.Installing: + message = "The package is being installed"; + break; + case PackageInstallProgressState.PostInstall: + message = "The installation operation is wrapping up"; + break; + case PackageInstallProgressState.Finished: + message = "The install is complete"; + break; + default: + message = "Unknown install state"; + break; + } + + const float downloadPercentage = 0.8f; + + ProgressNotificationValue result = new ProgressNotificationValue() + { + Progress = (float)((downloadProgress * downloadPercentage) + (installProgress * (1.0f - downloadPercentage))), + Message = message, + }; + + return result; + } + } +} diff --git a/src/WinGetSchemas/PackagesSchema.h b/src/WinGetSchemas/PackagesSchema.h index 77fd5d0020..1a1f10e5f5 100644 --- a/src/WinGetSchemas/PackagesSchema.h +++ b/src/WinGetSchemas/PackagesSchema.h @@ -1,8 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#define PACKAGESSCHEMA_RESOURCE_TYPE 300 - -#define IDX_PACKAGES_SCHEMA_V1 301 -#define IDX_PACKAGES_SCHEMA_V2 302 +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define PACKAGESSCHEMA_RESOURCE_TYPE 300 + +#define IDX_PACKAGES_SCHEMA_V1 301 +#define IDX_PACKAGES_SCHEMA_V2 302 diff --git a/src/WinGetSchemas/WinGetSchemas.rc b/src/WinGetSchemas/WinGetSchemas.rc index 14ba79a9c0..7416dc3df3 100644 --- a/src/WinGetSchemas/WinGetSchemas.rc +++ b/src/WinGetSchemas/WinGetSchemas.rc @@ -1,67 +1,67 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" -#include "PackagesSchema.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE 9, 1 - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - -///////////////////////////////////////////////////////////////////////////// -// -// Packages schema -// -IDX_PACKAGES_SCHEMA_V1 PACKAGESSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\packages\\packages.schema.1.0.json" -IDX_PACKAGES_SCHEMA_V2 PACKAGESSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\packages\\packages.schema.2.0.json" +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "PackagesSchema.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE 9, 1 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + +///////////////////////////////////////////////////////////////////////////// +// +// Packages schema +// +IDX_PACKAGES_SCHEMA_V1 PACKAGESSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\packages\\packages.schema.1.0.json" +IDX_PACKAGES_SCHEMA_V2 PACKAGESSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\packages\\packages.schema.2.0.json" diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems b/src/WinGetSchemas/WinGetSchemas.vcxitems index 36517bf71c..625424d4ff 100644 --- a/src/WinGetSchemas/WinGetSchemas.vcxitems +++ b/src/WinGetSchemas/WinGetSchemas.vcxitems @@ -1,29 +1,29 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - {952b513f-8a00-4d74-9271-925afb3c6252} - - - - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) - - - - - - - - - - - - - - - - - - + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {952b513f-8a00-4d74-9271-925afb3c6252} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WinGetSchemas/WinGetSchemas.vcxitems.filters b/src/WinGetSchemas/WinGetSchemas.vcxitems.filters index de1a2218cc..cd89a3236b 100644 --- a/src/WinGetSchemas/WinGetSchemas.vcxitems.filters +++ b/src/WinGetSchemas/WinGetSchemas.vcxitems.filters @@ -1,32 +1,32 @@ - - - - - {9b8a4c46-6227-45fe-840b-8f50fb10ddb1} - - - {931a4cce-b01f-4d2f-b39a-8600f2010a97} - - - - - settings - - - packages - - - packages - - - settings - - - - - - - - - + + + + + {9b8a4c46-6227-45fe-840b-8f50fb10ddb1} + + + {931a4cce-b01f-4d2f-b39a-8600f2010a97} + + + + + settings + + + packages + + + packages + + + settings + + + + + + + + + \ No newline at end of file diff --git a/src/WinGetSchemas/resource.h b/src/WinGetSchemas/resource.h index 80bec7af27..0e8c4797c1 100644 --- a/src/WinGetSchemas/resource.h +++ b/src/WinGetSchemas/resource.h @@ -1,14 +1,14 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by WinGetSchemas.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by WinGetSchemas.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/WinGetServer/PropertySheet.props b/src/WinGetServer/PropertySheet.props index 273b9fbfed..e34141b019 100644 --- a/src/WinGetServer/PropertySheet.props +++ b/src/WinGetServer/PropertySheet.props @@ -1,16 +1,16 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/src/WinGetServer/Utils.cpp b/src/WinGetServer/Utils.cpp index 31069713eb..5053dfbbdf 100644 --- a/src/WinGetServer/Utils.cpp +++ b/src/WinGetServer/Utils.cpp @@ -1,52 +1,52 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "Utils.h" -#pragma warning( push ) -#pragma warning ( disable : 6001 6388 6553) -#include -#pragma warning( pop ) -#include -#include -#include - -unsigned char* GetUCharString(const std::string& str) -{ - return reinterpret_cast(const_cast(str.c_str())); -} - -std::string GetUserSID() -{ - HANDLE hToken = NULL; - THROW_LAST_ERROR_IF(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)); - - DWORD dwBufferSize = 0; - THROW_LAST_ERROR_IF(!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufferSize) && GetLastError() != ERROR_INSUFFICIENT_BUFFER); - - std::vector buffer; - buffer.resize(dwBufferSize); - PTOKEN_USER pTokenUser = reinterpret_cast(&buffer[0]); - - THROW_LAST_ERROR_IF(!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)); - THROW_HR_IF(CO_E_INVALIDSID, !IsValidSid(pTokenUser->User.Sid)); - - LPSTR pszSID = NULL; - THROW_LAST_ERROR_IF(!ConvertSidToStringSidA(pTokenUser->User.Sid, &pszSID)); - return std::string{ pszSID }; -} - -wil::unique_event CreateOrOpenServerStartEvent() -{ - wil::unique_event result; - - for (int i = 0; !result && i < 2; ++i) - { - if (!result.try_create(wil::EventOptions::ManualReset, L"WinGetServerStartEvent")) - { - result.try_open(L"WinGetServerStartEvent"); - } - } - - THROW_LAST_ERROR_IF(!result); - - return result; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "Utils.h" +#pragma warning( push ) +#pragma warning ( disable : 6001 6388 6553) +#include +#pragma warning( pop ) +#include +#include +#include + +unsigned char* GetUCharString(const std::string& str) +{ + return reinterpret_cast(const_cast(str.c_str())); +} + +std::string GetUserSID() +{ + HANDLE hToken = NULL; + THROW_LAST_ERROR_IF(!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)); + + DWORD dwBufferSize = 0; + THROW_LAST_ERROR_IF(!GetTokenInformation(hToken, TokenUser, NULL, 0, &dwBufferSize) && GetLastError() != ERROR_INSUFFICIENT_BUFFER); + + std::vector buffer; + buffer.resize(dwBufferSize); + PTOKEN_USER pTokenUser = reinterpret_cast(&buffer[0]); + + THROW_LAST_ERROR_IF(!GetTokenInformation(hToken, TokenUser, pTokenUser, dwBufferSize, &dwBufferSize)); + THROW_HR_IF(CO_E_INVALIDSID, !IsValidSid(pTokenUser->User.Sid)); + + LPSTR pszSID = NULL; + THROW_LAST_ERROR_IF(!ConvertSidToStringSidA(pTokenUser->User.Sid, &pszSID)); + return std::string{ pszSID }; +} + +wil::unique_event CreateOrOpenServerStartEvent() +{ + wil::unique_event result; + + for (int i = 0; !result && i < 2; ++i) + { + if (!result.try_create(wil::EventOptions::ManualReset, L"WinGetServerStartEvent")) + { + result.try_open(L"WinGetServerStartEvent"); + } + } + + THROW_LAST_ERROR_IF(!result); + + return result; +} diff --git a/src/WinGetServer/Utils.h b/src/WinGetServer/Utils.h index 0e1cc8745f..a74131a31a 100644 --- a/src/WinGetServer/Utils.h +++ b/src/WinGetServer/Utils.h @@ -1,14 +1,14 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#pragma warning( push ) -#pragma warning ( disable : 6001 6388 6553) -#include -#pragma warning( pop ) -#include - -unsigned char* GetUCharString(const std::string& str); - -std::string GetUserSID(); - -wil::unique_event CreateOrOpenServerStartEvent(); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#pragma warning( push ) +#pragma warning ( disable : 6001 6388 6553) +#include +#pragma warning( pop ) +#include + +unsigned char* GetUCharString(const std::string& str); + +std::string GetUserSID(); + +wil::unique_event CreateOrOpenServerStartEvent(); diff --git a/src/WinGetServer/WinGetServer.exe.manifest b/src/WinGetServer/WinGetServer.exe.manifest index 0dba1d9b9e..c5a6e7a4e6 100644 --- a/src/WinGetServer/WinGetServer.exe.manifest +++ b/src/WinGetServer/WinGetServer.exe.manifest @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/src/WinGetServer/WinGetServer.idl b/src/WinGetServer/WinGetServer.idl index 87f8a7a42c..0e29c8e4bd 100644 --- a/src/WinGetServer/WinGetServer.idl +++ b/src/WinGetServer/WinGetServer.idl @@ -1,17 +1,17 @@ -import "wtypesbase.idl"; - -[ - uuid(0ca09dda-857f-479f-ba4b-da875d90051e), - version(1.0), - implicit_handle(handle_t WinGetServerManualActivation_IfHandle) -] -interface WinGetServerManualActivation -{ - HRESULT CreateInstance( - [in] GUID clsid, - [in] GUID iid, - [in] UINT32 flags, - [out, ref] UINT32* pcbBuffer, - [out, ref, size_is(, *pcbBuffer)] BYTE** ppBuffer - ); +import "wtypesbase.idl"; + +[ + uuid(0ca09dda-857f-479f-ba4b-da875d90051e), + version(1.0), + implicit_handle(handle_t WinGetServerManualActivation_IfHandle) +] +interface WinGetServerManualActivation +{ + HRESULT CreateInstance( + [in] GUID clsid, + [in] GUID iid, + [in] UINT32 flags, + [out, ref] UINT32* pcbBuffer, + [out, ref, size_is(, *pcbBuffer)] BYTE** ppBuffer + ); } \ No newline at end of file diff --git a/src/WinGetServer/WinGetServer.vcxproj b/src/WinGetServer/WinGetServer.vcxproj index 5088e625d9..00c1bed271 100644 --- a/src/WinGetServer/WinGetServer.vcxproj +++ b/src/WinGetServer/WinGetServer.vcxproj @@ -1,179 +1,179 @@ - - - - true - true - true - true - 15.0 - {2b00d362-ac92-41f3-a8d2-5b1599bdca01} - Win32Proj - WinGetServer - 10.0.26100.0 - 10.0.17763.0 - true - WinGetServer - WindowsPackageManagerServer - - - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - Application - - - true - true - - - false - true - false - Spectre - - - - - - - - - - $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ - $(PlatformTarget)\$(Configuration)\ - $(OutDir)..\Microsoft.Management.Deployment;$(OutDir)..\AppInstallerCLICore;$(OutDir)..\AppInstallerRepositoryCore;$(OutDir)..\YamlCppLib;$(OutDir)..\AppInstallerCommonCore;$(LibraryPath) - true - false - ..\CodeAnalysis.ruleset - - - - - NotUsing - pch.h - $(IntDir)pch.pch - _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - stdcpp17 - Level4 - %(AdditionalOptions) /permissive- /bigobj - true - true - true - $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) - false - - - Windows - false - $(OutDir)..\Microsoft.Management.Deployment;$(OutDir)..\AppInstallerCLICore;$(OutDir)..\AppInstallerRepositoryCore;$(OutDir)..\YamlCppLib;$(OutDir)..\AppInstallerCommonCore;%(AdditionalLibraryDirectories) - Rpcrt4.lib;Advapi32.lib;Shell32.lib;Ole32.lib;%(AdditionalDependencies) - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - - - true - - - - - WIN32;%(PreprocessorDefinitions) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - Guard - - - true - true - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - - - - - - true - - - - - - - - - - - - - - {2046b5af-666d-4ce8-8d3e-c32c57908a56} - - - - - false - Stub - Stub - true - WinGetServer.h - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + true + true + true + true + 15.0 + {2b00d362-ac92-41f3-a8d2-5b1599bdca01} + Win32Proj + WinGetServer + 10.0.26100.0 + 10.0.17763.0 + true + WinGetServer + WindowsPackageManagerServer + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + Application + + + true + true + + + false + true + false + Spectre + + + + + + + + + + $(SolutionDir)$(PlatformTarget)\$(Configuration)\$(ProjectName)\ + $(PlatformTarget)\$(Configuration)\ + $(OutDir)..\Microsoft.Management.Deployment;$(OutDir)..\AppInstallerCLICore;$(OutDir)..\AppInstallerRepositoryCore;$(OutDir)..\YamlCppLib;$(OutDir)..\AppInstallerCommonCore;$(LibraryPath) + true + false + ..\CodeAnalysis.ruleset + + + + + NotUsing + pch.h + $(IntDir)pch.pch + _CONSOLE;WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + stdcpp17 + Level4 + %(AdditionalOptions) /permissive- /bigobj + true + true + true + $(ProjectDir)..\WindowsPackageManager;%(AdditionalIncludeDirectories) + false + + + Windows + false + $(OutDir)..\Microsoft.Management.Deployment;$(OutDir)..\AppInstallerCLICore;$(OutDir)..\AppInstallerRepositoryCore;$(OutDir)..\YamlCppLib;$(OutDir)..\AppInstallerCommonCore;%(AdditionalLibraryDirectories) + Rpcrt4.lib;Advapi32.lib;Shell32.lib;Ole32.lib;%(AdditionalDependencies) + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + true + + + + + WIN32;%(PreprocessorDefinitions) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + Guard + + + true + true + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + + + + + + true + + + + + + + + + + + + + + {2046b5af-666d-4ce8-8d3e-c32c57908a56} + + + + + false + Stub + Stub + true + WinGetServer.h + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + diff --git a/src/WinGetServer/WinGetServer.vcxproj.filters b/src/WinGetServer/WinGetServer.vcxproj.filters index e5d09ef341..89b34e046d 100644 --- a/src/WinGetServer/WinGetServer.vcxproj.filters +++ b/src/WinGetServer/WinGetServer.vcxproj.filters @@ -1,62 +1,62 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - - - - - - - - Resource Files - - - - - Source Files - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + + + + + + + + Resource Files + + + + + Source Files + + + + + \ No newline at end of file diff --git a/src/WinGetServer/WinGetServerManualActivation_Client.cpp b/src/WinGetServer/WinGetServerManualActivation_Client.cpp index 1c512a2ee0..d9c1a11ff1 100644 --- a/src/WinGetServer/WinGetServerManualActivation_Client.cpp +++ b/src/WinGetServer/WinGetServerManualActivation_Client.cpp @@ -1,248 +1,248 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "WinGetServer.h" -#include "appmodel.h" -#include "Utils.h" - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#ifdef USE_PROD_WINGET_SERVER -const std::wstring_view s_ServerPackageFamilyName = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; -const std::wstring_view s_ServerFileName = L"WindowsPackageManagerServer.exe"; -#else -const std::wstring_view s_LocalAppDataRelativeServerExePath = L"Microsoft\\WindowsApps\\WinGetDevCLI_8wekyb3d8bbwe\\WindowsPackageManagerServerDev.exe"; -const std::wstring_view s_ServerPackageFamilyName = L"WinGetDevCLI_8wekyb3d8bbwe"; -const std::wstring_view s_ServerFileName = L"WinGetServer\\WindowsPackageManagerServer.exe"; -#endif - -_Must_inspect_result_ -_Ret_maybenull_ _Post_writable_byte_size_(size) -void* __RPC_USER MIDL_user_allocate(_In_ size_t size) -{ - return malloc(size); -} - -void __RPC_USER MIDL_user_free(_Pre_maybenull_ _Post_invalid_ void* ptr) -{ - if (ptr) - { - free(ptr); - } -} - -struct FreeWithRpcStringFree { void operator()(RPC_CSTR* in) { RpcStringFreeA(in); } }; -using UniqueRpcString = std::unique_ptr; - -struct DeleteWithMidlFree { void operator()(void* m) { MIDL_user_free(m); } }; -using UniqueMidl = std::unique_ptr; - -void InitializeRpcBinding() -{ - std::string protocol = "ncacn_np"; - std::string endpoint = "\\pipe\\WinGetServerManualActivation_" + GetUserSID(); - - unsigned char* binding = nullptr; - UniqueRpcString bindingPtr; - - RPC_STATUS status = RpcStringBindingComposeA(nullptr, GetUCharString(protocol), nullptr, GetUCharString(endpoint), nullptr, &binding); - THROW_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); - bindingPtr.reset(&binding); - - status = RpcBindingFromStringBindingA(binding, &WinGetServerManualActivation_IfHandle); - THROW_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); -} - -struct ServerProcessLauncher -{ - ServerProcessLauncher() - { - try - { - m_serverExePath = GetPackageLocation(s_ServerPackageFamilyName, s_ServerFileName) / s_ServerFileName; - -#ifndef USE_PROD_WINGET_SERVER - // The feature that allows directly launching a packaged process as long as it has a matching alias - // requires a failure to trigger, and the dev package ACL does not force this to happen. Attempting - // to use the other code path results in an unpackaged server, causing other issues. - // We run the product code above to ensure that it is functioning properly, but then replace it with - // the path of the alias. - m_serverExePath = GetKnownFolderPath(FOLDERID_LocalAppData) / s_LocalAppDataRelativeServerExePath; -#endif - } - catch (wil::ResultException& re) - { - m_hr = re.GetErrorCode(); - } - } - - HRESULT LaunchWinGetServerWithManualActivation() - { - RETURN_IF_FAILED(m_hr); - - std::wstring commandLineInput = L"\"" + std::wstring{ m_serverExePath } + L"\" --manualActivation"; - - STARTUPINFO info = { sizeof(info) }; - wil::unique_process_information process; - - RETURN_LAST_ERROR_IF(!CreateProcessW(NULL, &commandLineInput[0], NULL, NULL, FALSE, 0, NULL, NULL, &info, &process)); - - // Wait for manual reset event from server before proceeding with COM activation. - wil::unique_event manualResetEvent = CreateOrOpenServerStartEvent(); - manualResetEvent.wait(10000); - - return S_OK; - } - -private: - std::filesystem::path GetPackageLocation(std::wstring_view packageFamilyName, std::wstring_view fileName) - { - std::wstring pfn{ packageFamilyName }; - UINT32 count = 0; - std::unique_ptr fullNames; - UINT32 bufferLength = 0; - std::unique_ptr buffer; - std::unique_ptr properties; - - LONG result = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &count, nullptr, &bufferLength, nullptr, nullptr); - THROW_WIN32_IF(result, result != ERROR_INSUFFICIENT_BUFFER); - - for (size_t i = 0; i < 10 && result == ERROR_INSUFFICIENT_BUFFER; ++i) - { - fullNames = std::make_unique(count); - buffer = std::make_unique(bufferLength); - properties = std::make_unique(count); - - result = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &count, fullNames.get(), &bufferLength, buffer.get(), properties.get()); - } - - THROW_IF_WIN32_ERROR(result); - - for (UINT32 i = 0; i < count; ++i) - { - // Includes null terminator - UINT32 pathLength = 0; - result = GetPackagePathByFullName(fullNames[i], &pathLength, nullptr); - if (result != ERROR_INSUFFICIENT_BUFFER) - { - continue; - } - - std::wstring packagePath; - packagePath.resize(static_cast(pathLength)); - - if (FAILED_WIN32(GetPackagePathByFullName(fullNames[i], &pathLength, &packagePath[0]))) - { - continue; - } - packagePath.resize(static_cast(pathLength - 1), L'\0'); - - std::filesystem::path resultPath = std::move(packagePath); - std::filesystem::path exePath = resultPath / fileName; - - if (GetFileAttributesW(exePath.c_str()) != INVALID_FILE_ATTRIBUTES) - { - return resultPath; - } - } - - THROW_WIN32(ERROR_PACKAGE_NOT_REGISTERED_FOR_USER); - } - - std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) - { - wil::unique_cotaskmem_string knownFolder = nullptr; - THROW_IF_FAILED(SHGetKnownFolderPath(id, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_PACKAGE_REDIRECTION, NULL, &knownFolder)); - return knownFolder.get(); - } - - std::filesystem::path m_serverExePath; - HRESULT m_hr = S_OK; -}; - -HRESULT CallCreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, UINT32* bufferByteCount, BYTE** buffer) -{ - RpcTryExcept - { - RETURN_IF_FAILED(CreateInstance(rclsid, riid, flags, bufferByteCount, buffer)); - } - RpcExcept(1) - { - return HRESULT_FROM_WIN32(RpcExceptionCode()); - } - RpcEndExcept; - - return S_OK; -} - -HRESULT CreateComInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out) -{ - UINT32 bufferByteCount = 0; - BYTE* buffer = nullptr; - UniqueMidl bufferPtr; - - RETURN_IF_FAILED(CallCreateInstance(rclsid, riid, flags, &bufferByteCount, &buffer)); - - bufferPtr.reset(buffer); - - wil::com_ptr stream; - RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); - RETURN_IF_FAILED(stream->Write(buffer, bufferByteCount, nullptr)); - RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - - wil::com_ptr output; - RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), riid, reinterpret_cast(&output))); - *out = output.detach(); - return S_OK; -} - -extern "C" HRESULT WinGetServerManualActivation_CreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out) -{ - RETURN_HR_IF_NULL(E_POINTER, out); - - static std::once_flag rpcBindingOnce; - try - { - std::call_once(rpcBindingOnce, InitializeRpcBinding); - } - CATCH_RETURN(); - - HRESULT result = CreateComInstance(rclsid, riid, flags, out); - if (FAILED(result)) - { - ServerProcessLauncher launcher; - - for (int i = 0; i < 3; i++) - { - result = launcher.LaunchWinGetServerWithManualActivation(); - if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_PACKAGE_NOT_REGISTERED_FOR_USER)) - { - break; - } - - result = CreateComInstance(rclsid, riid, flags, out); - if (SUCCEEDED(result)) - { - break; - } - - Sleep(200); - } - } - - return result; -} - -extern "C" HRESULT WinGetServerManualActivation_Terminate() -{ - RpcBindingFree(&WinGetServerManualActivation_IfHandle); - return S_OK; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "WinGetServer.h" +#include "appmodel.h" +#include "Utils.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef USE_PROD_WINGET_SERVER +const std::wstring_view s_ServerPackageFamilyName = L"Microsoft.DesktopAppInstaller_8wekyb3d8bbwe"; +const std::wstring_view s_ServerFileName = L"WindowsPackageManagerServer.exe"; +#else +const std::wstring_view s_LocalAppDataRelativeServerExePath = L"Microsoft\\WindowsApps\\WinGetDevCLI_8wekyb3d8bbwe\\WindowsPackageManagerServerDev.exe"; +const std::wstring_view s_ServerPackageFamilyName = L"WinGetDevCLI_8wekyb3d8bbwe"; +const std::wstring_view s_ServerFileName = L"WinGetServer\\WindowsPackageManagerServer.exe"; +#endif + +_Must_inspect_result_ +_Ret_maybenull_ _Post_writable_byte_size_(size) +void* __RPC_USER MIDL_user_allocate(_In_ size_t size) +{ + return malloc(size); +} + +void __RPC_USER MIDL_user_free(_Pre_maybenull_ _Post_invalid_ void* ptr) +{ + if (ptr) + { + free(ptr); + } +} + +struct FreeWithRpcStringFree { void operator()(RPC_CSTR* in) { RpcStringFreeA(in); } }; +using UniqueRpcString = std::unique_ptr; + +struct DeleteWithMidlFree { void operator()(void* m) { MIDL_user_free(m); } }; +using UniqueMidl = std::unique_ptr; + +void InitializeRpcBinding() +{ + std::string protocol = "ncacn_np"; + std::string endpoint = "\\pipe\\WinGetServerManualActivation_" + GetUserSID(); + + unsigned char* binding = nullptr; + UniqueRpcString bindingPtr; + + RPC_STATUS status = RpcStringBindingComposeA(nullptr, GetUCharString(protocol), nullptr, GetUCharString(endpoint), nullptr, &binding); + THROW_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); + bindingPtr.reset(&binding); + + status = RpcBindingFromStringBindingA(binding, &WinGetServerManualActivation_IfHandle); + THROW_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); +} + +struct ServerProcessLauncher +{ + ServerProcessLauncher() + { + try + { + m_serverExePath = GetPackageLocation(s_ServerPackageFamilyName, s_ServerFileName) / s_ServerFileName; + +#ifndef USE_PROD_WINGET_SERVER + // The feature that allows directly launching a packaged process as long as it has a matching alias + // requires a failure to trigger, and the dev package ACL does not force this to happen. Attempting + // to use the other code path results in an unpackaged server, causing other issues. + // We run the product code above to ensure that it is functioning properly, but then replace it with + // the path of the alias. + m_serverExePath = GetKnownFolderPath(FOLDERID_LocalAppData) / s_LocalAppDataRelativeServerExePath; +#endif + } + catch (wil::ResultException& re) + { + m_hr = re.GetErrorCode(); + } + } + + HRESULT LaunchWinGetServerWithManualActivation() + { + RETURN_IF_FAILED(m_hr); + + std::wstring commandLineInput = L"\"" + std::wstring{ m_serverExePath } + L"\" --manualActivation"; + + STARTUPINFO info = { sizeof(info) }; + wil::unique_process_information process; + + RETURN_LAST_ERROR_IF(!CreateProcessW(NULL, &commandLineInput[0], NULL, NULL, FALSE, 0, NULL, NULL, &info, &process)); + + // Wait for manual reset event from server before proceeding with COM activation. + wil::unique_event manualResetEvent = CreateOrOpenServerStartEvent(); + manualResetEvent.wait(10000); + + return S_OK; + } + +private: + std::filesystem::path GetPackageLocation(std::wstring_view packageFamilyName, std::wstring_view fileName) + { + std::wstring pfn{ packageFamilyName }; + UINT32 count = 0; + std::unique_ptr fullNames; + UINT32 bufferLength = 0; + std::unique_ptr buffer; + std::unique_ptr properties; + + LONG result = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &count, nullptr, &bufferLength, nullptr, nullptr); + THROW_WIN32_IF(result, result != ERROR_INSUFFICIENT_BUFFER); + + for (size_t i = 0; i < 10 && result == ERROR_INSUFFICIENT_BUFFER; ++i) + { + fullNames = std::make_unique(count); + buffer = std::make_unique(bufferLength); + properties = std::make_unique(count); + + result = FindPackagesByPackageFamily(pfn.c_str(), PACKAGE_FILTER_HEAD, &count, fullNames.get(), &bufferLength, buffer.get(), properties.get()); + } + + THROW_IF_WIN32_ERROR(result); + + for (UINT32 i = 0; i < count; ++i) + { + // Includes null terminator + UINT32 pathLength = 0; + result = GetPackagePathByFullName(fullNames[i], &pathLength, nullptr); + if (result != ERROR_INSUFFICIENT_BUFFER) + { + continue; + } + + std::wstring packagePath; + packagePath.resize(static_cast(pathLength)); + + if (FAILED_WIN32(GetPackagePathByFullName(fullNames[i], &pathLength, &packagePath[0]))) + { + continue; + } + packagePath.resize(static_cast(pathLength - 1), L'\0'); + + std::filesystem::path resultPath = std::move(packagePath); + std::filesystem::path exePath = resultPath / fileName; + + if (GetFileAttributesW(exePath.c_str()) != INVALID_FILE_ATTRIBUTES) + { + return resultPath; + } + } + + THROW_WIN32(ERROR_PACKAGE_NOT_REGISTERED_FOR_USER); + } + + std::filesystem::path GetKnownFolderPath(const KNOWNFOLDERID& id) + { + wil::unique_cotaskmem_string knownFolder = nullptr; + THROW_IF_FAILED(SHGetKnownFolderPath(id, KF_FLAG_NO_ALIAS | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_PACKAGE_REDIRECTION, NULL, &knownFolder)); + return knownFolder.get(); + } + + std::filesystem::path m_serverExePath; + HRESULT m_hr = S_OK; +}; + +HRESULT CallCreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, UINT32* bufferByteCount, BYTE** buffer) +{ + RpcTryExcept + { + RETURN_IF_FAILED(CreateInstance(rclsid, riid, flags, bufferByteCount, buffer)); + } + RpcExcept(1) + { + return HRESULT_FROM_WIN32(RpcExceptionCode()); + } + RpcEndExcept; + + return S_OK; +} + +HRESULT CreateComInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out) +{ + UINT32 bufferByteCount = 0; + BYTE* buffer = nullptr; + UniqueMidl bufferPtr; + + RETURN_IF_FAILED(CallCreateInstance(rclsid, riid, flags, &bufferByteCount, &buffer)); + + bufferPtr.reset(buffer); + + wil::com_ptr stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + RETURN_IF_FAILED(stream->Write(buffer, bufferByteCount, nullptr)); + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + + wil::com_ptr output; + RETURN_IF_FAILED(CoUnmarshalInterface(stream.get(), riid, reinterpret_cast(&output))); + *out = output.detach(); + return S_OK; +} + +extern "C" HRESULT WinGetServerManualActivation_CreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out) +{ + RETURN_HR_IF_NULL(E_POINTER, out); + + static std::once_flag rpcBindingOnce; + try + { + std::call_once(rpcBindingOnce, InitializeRpcBinding); + } + CATCH_RETURN(); + + HRESULT result = CreateComInstance(rclsid, riid, flags, out); + if (FAILED(result)) + { + ServerProcessLauncher launcher; + + for (int i = 0; i < 3; i++) + { + result = launcher.LaunchWinGetServerWithManualActivation(); + if (result == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND) || result == HRESULT_FROM_WIN32(ERROR_PACKAGE_NOT_REGISTERED_FOR_USER)) + { + break; + } + + result = CreateComInstance(rclsid, riid, flags, out); + if (SUCCEEDED(result)) + { + break; + } + + Sleep(200); + } + } + + return result; +} + +extern "C" HRESULT WinGetServerManualActivation_Terminate() +{ + RpcBindingFree(&WinGetServerManualActivation_IfHandle); + return S_OK; +} diff --git a/src/WinGetServer/WinGetServerManualActivation_Client.h b/src/WinGetServer/WinGetServerManualActivation_Client.h index ae9e7ffd16..20e32ef1d5 100644 --- a/src/WinGetServer/WinGetServerManualActivation_Client.h +++ b/src/WinGetServer/WinGetServerManualActivation_Client.h @@ -1,8 +1,8 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -extern "C" HRESULT WinGetServerManualActivation_CreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out); - -extern "C" HRESULT WinGetServerManualActivation_Terminate(); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +extern "C" HRESULT WinGetServerManualActivation_CreateInstance(REFCLSID rclsid, REFIID riid, UINT32 flags, void** out); + +extern "C" HRESULT WinGetServerManualActivation_Terminate(); diff --git a/src/WinGetServer/WinMain.cpp b/src/WinGetServer/WinMain.cpp index b39f379483..68b34c438a 100644 --- a/src/WinGetServer/WinMain.cpp +++ b/src/WinGetServer/WinMain.cpp @@ -1,239 +1,239 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#define NOMINMAX -#pragma warning( push ) -#pragma warning ( disable : 6001 6388 6553) -#include -#include -#pragma warning( pop ) -#include -#include -#include -#include -#include "WinGetServer.h" -#include "Utils.h" - -#include -#include -#include -#include - -// Holds the wwinmain open until COM tells us there are no more server connections -wil::unique_event _comServerExitEvent; - -// Routine Description: -// - Called back when COM says there is nothing left for our server to do and we can tear down. -static void _releaseNotifier() noexcept -{ - _comServerExitEvent.SetEvent(); -} - -HRESULT WindowsPackageManagerServerInitializeRPCServer() -{ - std::string userSID = GetUserSID(); - std::string endpoint = "\\pipe\\WinGetServerManualActivation_" + userSID; - RPC_STATUS status = RpcServerUseProtseqEpA(GetUCharString("ncacn_np"), RPC_C_PROTSEQ_MAX_REQS_DEFAULT, GetUCharString(endpoint), nullptr); - RETURN_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); - - // The goal of this security descriptor is to restrict RPC server access only to the user in admin mode. - // (ML;;NW;;;HI) specifies a high mandatory integrity level (requires admin). - // (A;;GA;;;UserSID) specifies access only for the user with the user SID (i.e. self). - wil::unique_hlocal_security_descriptor securityDescriptor; - std::string securityDescriptorString = "S:(ML;;NW;;;HI)D:(A;;GA;;;" + userSID + ")"; - RETURN_LAST_ERROR_IF(!ConvertStringSecurityDescriptorToSecurityDescriptorA(securityDescriptorString.c_str(), SDDL_REVISION_1, &securityDescriptor, nullptr)); - - status = RpcServerRegisterIf3(WinGetServerManualActivation_v1_0_s_ifspec, nullptr, nullptr, RPC_IF_ALLOW_LOCAL_ONLY | RPC_IF_AUTOLISTEN, RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0, nullptr, securityDescriptor.get()); - RETURN_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); - - return S_OK; -} - -_Must_inspect_result_ -_Ret_maybenull_ _Post_writable_byte_size_(size) -void* __RPC_USER MIDL_user_allocate(_In_ size_t size) -{ - return malloc(size); -} - -void __RPC_USER MIDL_user_free(_Pre_maybenull_ _Post_invalid_ void* ptr) -{ - if (ptr) - { - free(ptr); - } -} - -extern "C" HRESULT CreateInstance( - /* [in] */ GUID clsid, - /* [in] */ GUID iid, - /* [in] */ UINT32, - /* [ref][out] */ UINT32 * pcbBuffer, - /* [size_is][size_is][ref][out] */ BYTE * *ppBuffer) -{ - RETURN_HR_IF_NULL(E_POINTER, pcbBuffer); - RETURN_HR_IF_NULL(E_POINTER, ppBuffer); - - wil::com_ptr stream; - RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); - - wil::com_ptr instance; - RETURN_IF_FAILED(WindowsPackageManagerServerCreateInstance(clsid, iid, reinterpret_cast(&instance))); - - RETURN_IF_FAILED(CoMarshalInterface(stream.get(), iid, instance.get(), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); - - ULARGE_INTEGER streamSize{}; - RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); - RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, streamSize.QuadPart > std::numeric_limits::max()); - - UINT32 bufferSize = static_cast(streamSize.QuadPart); - - struct DeleteWithMidlFree { void operator()(void* m) { MIDL_user_free(m); } }; - std::unique_ptr buffer{ reinterpret_cast(MIDL_user_allocate(bufferSize)) }; - - RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); - ULONG bytesRead = 0; - RETURN_IF_FAILED(stream->Read(buffer.get(), bufferSize, &bytesRead)); - RETURN_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); - - *pcbBuffer = bufferSize; - *ppBuffer = buffer.release(); - - return S_OK; -} - -HRESULT InitializeComSecurity() -{ - wil::unique_hlocal_security_descriptor securityDescriptor; - // Allow Self, System, Built-in Admin and App Container access. 3 is COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL - std::string securityDescriptorString = "O:SYG:SYD:(A;;3;;;PS)(A;;3;;;SY)(A;;3;;;BA)(A;;3;;;AC)"; - RETURN_LAST_ERROR_IF(!ConvertStringSecurityDescriptorToSecurityDescriptorA(securityDescriptorString.c_str(), SDDL_REVISION_1, &securityDescriptor, nullptr)); - - // Make absolute security descriptor as CoInitializeSecurity required - SECURITY_DESCRIPTOR absoluteSecurityDescriptor; - DWORD securityDescriptorSize = sizeof(SECURITY_DESCRIPTOR); - - DWORD daclSize = 0; - DWORD saclSize = 0; - DWORD ownerSize = 0; - DWORD groupSize = 0; - - // Get required size - BOOL result = MakeAbsoluteSD(securityDescriptor.get(), &absoluteSecurityDescriptor, &securityDescriptorSize, nullptr, &daclSize, nullptr, &saclSize, nullptr, &ownerSize, nullptr, &groupSize); - RETURN_HR_IF_MSG(E_FAIL, result || GetLastError() != ERROR_INSUFFICIENT_BUFFER, "MakeAbsoluteSD failed to return buffer sizes"); - - std::vector dacl(daclSize); - std::vector sacl(saclSize); - std::vector owner(ownerSize); - std::vector group(groupSize); - - RETURN_LAST_ERROR_IF(!MakeAbsoluteSD(securityDescriptor.get(), &absoluteSecurityDescriptor, &securityDescriptorSize, (PACL)dacl.data(), &daclSize, (PACL)sacl.data(), &saclSize, (PACL)owner.data(), &ownerSize, (PACL)group.data(), &groupSize)); - - // Initialize com security - RETURN_IF_FAILED(CoInitializeSecurity( - &absoluteSecurityDescriptor, // Security descriptor - -1, // Authentication services count. -1 is let com choose. - nullptr, // Authentication services array - nullptr, // Reserved - RPC_C_AUTHN_LEVEL_DEFAULT, // Authentication level. - RPC_C_IMP_LEVEL_IDENTIFY, // Impersonation level. Identify client. - nullptr, // Authentication list - EOAC_NONE, // Additional capabilities - nullptr // Reserved - )); - - return S_OK; -} - -int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR cmdLine, _In_ int) -{ - wil::SetResultLoggingCallback(&WindowsPackageManagerServerWilResultLoggingCallback); - - RETURN_IF_FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)); - - // Enable fast rundown of objects so that the server exits faster when clients go away. - { - wil::com_ptr globalOptions; - RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); - RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); - RETURN_IF_FAILED(globalOptions->Set(COMGLB_UNMARSHALING_POLICY, COMGLB_UNMARSHALING_POLICY_STRONG)); - RETURN_IF_FAILED(globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY)); - } - - // Command line parsing - int argc = 0; - LPWSTR* argv = CommandLineToArgvW(cmdLine, &argc); - RETURN_LAST_ERROR_IF(!argv); - - bool manualActivation = false; - - // If command line gets more complicated, consider more complex parsing - if (argc == 1 && std::wstring_view{ L"--manualActivation" } == argv[0]) - { - manualActivation = true; - } - - // For packaged com activation, initialize com security. - // For manual activation, leave as default. We'll not register objects for manual activation. - if (!manualActivation) - { - // This must be called after IGlobalOptions (fast rundown setting cannot be changed after CoInitializeSecurity) - // This must be called before WindowsPackageManagerServerInitialize (when setting the logs - // to Windows.Storage folders, automatic CoInitializeSecurity is triggered) - RETURN_IF_FAILED(InitializeComSecurity()); - } - - RETURN_IF_FAILED(WindowsPackageManagerServerInitialize()); - - _comServerExitEvent.create(); - RETURN_IF_FAILED(WindowsPackageManagerServerModuleCreate(&_releaseNotifier)); - try - { - // Manual reset event to notify the client that the server is available. - wil::unique_event manualResetEvent; - - if (manualActivation) - { - // For manual activation, do not register com objects - // so that only RPC channel can be used. - HANDLE hMutex = NULL; - hMutex = CreateMutex(NULL, FALSE, TEXT("WinGetServerMutex")); - RETURN_LAST_ERROR_IF_NULL(hMutex); - - DWORD waitResult = WaitForSingleObject(hMutex, 0); - if (waitResult != WAIT_OBJECT_0 && waitResult != WAIT_ABANDONED) - { - return HRESULT_FROM_WIN32(ERROR_SERVICE_ALREADY_RUNNING); - } - - RETURN_IF_FAILED(WindowsPackageManagerServerInitializeRPCServer()); - - manualResetEvent = CreateOrOpenServerStartEvent(); - manualResetEvent.SetEvent(); - } - else - { - // Register all the CoCreatableClassWrlCreatorMapInclude classes - RETURN_IF_FAILED(WindowsPackageManagerServerModuleRegister()); - } - - _comServerExitEvent.wait(); - WindowsPackageManagerServerLog("Server shutting down after exit event signaled."); - - if (manualResetEvent) - { - manualResetEvent.reset(); - } - - if (!manualActivation) - { - RETURN_IF_FAILED(WindowsPackageManagerServerModuleUnregister()); - } - } - catch (...) - { - LOG_CAUGHT_EXCEPTION(); - RETURN_CAUGHT_EXCEPTION(); - } - - return 0; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#define NOMINMAX +#pragma warning( push ) +#pragma warning ( disable : 6001 6388 6553) +#include +#include +#pragma warning( pop ) +#include +#include +#include +#include +#include "WinGetServer.h" +#include "Utils.h" + +#include +#include +#include +#include + +// Holds the wwinmain open until COM tells us there are no more server connections +wil::unique_event _comServerExitEvent; + +// Routine Description: +// - Called back when COM says there is nothing left for our server to do and we can tear down. +static void _releaseNotifier() noexcept +{ + _comServerExitEvent.SetEvent(); +} + +HRESULT WindowsPackageManagerServerInitializeRPCServer() +{ + std::string userSID = GetUserSID(); + std::string endpoint = "\\pipe\\WinGetServerManualActivation_" + userSID; + RPC_STATUS status = RpcServerUseProtseqEpA(GetUCharString("ncacn_np"), RPC_C_PROTSEQ_MAX_REQS_DEFAULT, GetUCharString(endpoint), nullptr); + RETURN_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); + + // The goal of this security descriptor is to restrict RPC server access only to the user in admin mode. + // (ML;;NW;;;HI) specifies a high mandatory integrity level (requires admin). + // (A;;GA;;;UserSID) specifies access only for the user with the user SID (i.e. self). + wil::unique_hlocal_security_descriptor securityDescriptor; + std::string securityDescriptorString = "S:(ML;;NW;;;HI)D:(A;;GA;;;" + userSID + ")"; + RETURN_LAST_ERROR_IF(!ConvertStringSecurityDescriptorToSecurityDescriptorA(securityDescriptorString.c_str(), SDDL_REVISION_1, &securityDescriptor, nullptr)); + + status = RpcServerRegisterIf3(WinGetServerManualActivation_v1_0_s_ifspec, nullptr, nullptr, RPC_IF_ALLOW_LOCAL_ONLY | RPC_IF_AUTOLISTEN, RPC_C_LISTEN_MAX_CALLS_DEFAULT, 0, nullptr, securityDescriptor.get()); + RETURN_HR_IF(HRESULT_FROM_WIN32(status), status != RPC_S_OK); + + return S_OK; +} + +_Must_inspect_result_ +_Ret_maybenull_ _Post_writable_byte_size_(size) +void* __RPC_USER MIDL_user_allocate(_In_ size_t size) +{ + return malloc(size); +} + +void __RPC_USER MIDL_user_free(_Pre_maybenull_ _Post_invalid_ void* ptr) +{ + if (ptr) + { + free(ptr); + } +} + +extern "C" HRESULT CreateInstance( + /* [in] */ GUID clsid, + /* [in] */ GUID iid, + /* [in] */ UINT32, + /* [ref][out] */ UINT32 * pcbBuffer, + /* [size_is][size_is][ref][out] */ BYTE * *ppBuffer) +{ + RETURN_HR_IF_NULL(E_POINTER, pcbBuffer); + RETURN_HR_IF_NULL(E_POINTER, ppBuffer); + + wil::com_ptr stream; + RETURN_IF_FAILED(CreateStreamOnHGlobal(nullptr, TRUE, &stream)); + + wil::com_ptr instance; + RETURN_IF_FAILED(WindowsPackageManagerServerCreateInstance(clsid, iid, reinterpret_cast(&instance))); + + RETURN_IF_FAILED(CoMarshalInterface(stream.get(), iid, instance.get(), MSHCTX_LOCAL, nullptr, MSHLFLAGS_NORMAL)); + + ULARGE_INTEGER streamSize{}; + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_CUR, &streamSize)); + RETURN_HR_IF(E_NOT_SUFFICIENT_BUFFER, streamSize.QuadPart > std::numeric_limits::max()); + + UINT32 bufferSize = static_cast(streamSize.QuadPart); + + struct DeleteWithMidlFree { void operator()(void* m) { MIDL_user_free(m); } }; + std::unique_ptr buffer{ reinterpret_cast(MIDL_user_allocate(bufferSize)) }; + + RETURN_IF_FAILED(stream->Seek({}, STREAM_SEEK_SET, nullptr)); + ULONG bytesRead = 0; + RETURN_IF_FAILED(stream->Read(buffer.get(), bufferSize, &bytesRead)); + RETURN_HR_IF(E_UNEXPECTED, bytesRead != bufferSize); + + *pcbBuffer = bufferSize; + *ppBuffer = buffer.release(); + + return S_OK; +} + +HRESULT InitializeComSecurity() +{ + wil::unique_hlocal_security_descriptor securityDescriptor; + // Allow Self, System, Built-in Admin and App Container access. 3 is COM_RIGHTS_EXECUTE | COM_RIGHTS_EXECUTE_LOCAL + std::string securityDescriptorString = "O:SYG:SYD:(A;;3;;;PS)(A;;3;;;SY)(A;;3;;;BA)(A;;3;;;AC)"; + RETURN_LAST_ERROR_IF(!ConvertStringSecurityDescriptorToSecurityDescriptorA(securityDescriptorString.c_str(), SDDL_REVISION_1, &securityDescriptor, nullptr)); + + // Make absolute security descriptor as CoInitializeSecurity required + SECURITY_DESCRIPTOR absoluteSecurityDescriptor; + DWORD securityDescriptorSize = sizeof(SECURITY_DESCRIPTOR); + + DWORD daclSize = 0; + DWORD saclSize = 0; + DWORD ownerSize = 0; + DWORD groupSize = 0; + + // Get required size + BOOL result = MakeAbsoluteSD(securityDescriptor.get(), &absoluteSecurityDescriptor, &securityDescriptorSize, nullptr, &daclSize, nullptr, &saclSize, nullptr, &ownerSize, nullptr, &groupSize); + RETURN_HR_IF_MSG(E_FAIL, result || GetLastError() != ERROR_INSUFFICIENT_BUFFER, "MakeAbsoluteSD failed to return buffer sizes"); + + std::vector dacl(daclSize); + std::vector sacl(saclSize); + std::vector owner(ownerSize); + std::vector group(groupSize); + + RETURN_LAST_ERROR_IF(!MakeAbsoluteSD(securityDescriptor.get(), &absoluteSecurityDescriptor, &securityDescriptorSize, (PACL)dacl.data(), &daclSize, (PACL)sacl.data(), &saclSize, (PACL)owner.data(), &ownerSize, (PACL)group.data(), &groupSize)); + + // Initialize com security + RETURN_IF_FAILED(CoInitializeSecurity( + &absoluteSecurityDescriptor, // Security descriptor + -1, // Authentication services count. -1 is let com choose. + nullptr, // Authentication services array + nullptr, // Reserved + RPC_C_AUTHN_LEVEL_DEFAULT, // Authentication level. + RPC_C_IMP_LEVEL_IDENTIFY, // Impersonation level. Identify client. + nullptr, // Authentication list + EOAC_NONE, // Additional capabilities + nullptr // Reserved + )); + + return S_OK; +} + +int __stdcall wWinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPWSTR cmdLine, _In_ int) +{ + wil::SetResultLoggingCallback(&WindowsPackageManagerServerWilResultLoggingCallback); + + RETURN_IF_FAILED(CoInitializeEx(nullptr, COINIT_MULTITHREADED)); + + // Enable fast rundown of objects so that the server exits faster when clients go away. + { + wil::com_ptr globalOptions; + RETURN_IF_FAILED(CoCreateInstance(CLSID_GlobalOptions, nullptr, CLSCTX_INPROC, IID_PPV_ARGS(&globalOptions))); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_RO_SETTINGS, COMGLB_FAST_RUNDOWN)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_UNMARSHALING_POLICY, COMGLB_UNMARSHALING_POLICY_STRONG)); + RETURN_IF_FAILED(globalOptions->Set(COMGLB_EXCEPTION_HANDLING, COMGLB_EXCEPTION_DONOT_HANDLE_ANY)); + } + + // Command line parsing + int argc = 0; + LPWSTR* argv = CommandLineToArgvW(cmdLine, &argc); + RETURN_LAST_ERROR_IF(!argv); + + bool manualActivation = false; + + // If command line gets more complicated, consider more complex parsing + if (argc == 1 && std::wstring_view{ L"--manualActivation" } == argv[0]) + { + manualActivation = true; + } + + // For packaged com activation, initialize com security. + // For manual activation, leave as default. We'll not register objects for manual activation. + if (!manualActivation) + { + // This must be called after IGlobalOptions (fast rundown setting cannot be changed after CoInitializeSecurity) + // This must be called before WindowsPackageManagerServerInitialize (when setting the logs + // to Windows.Storage folders, automatic CoInitializeSecurity is triggered) + RETURN_IF_FAILED(InitializeComSecurity()); + } + + RETURN_IF_FAILED(WindowsPackageManagerServerInitialize()); + + _comServerExitEvent.create(); + RETURN_IF_FAILED(WindowsPackageManagerServerModuleCreate(&_releaseNotifier)); + try + { + // Manual reset event to notify the client that the server is available. + wil::unique_event manualResetEvent; + + if (manualActivation) + { + // For manual activation, do not register com objects + // so that only RPC channel can be used. + HANDLE hMutex = NULL; + hMutex = CreateMutex(NULL, FALSE, TEXT("WinGetServerMutex")); + RETURN_LAST_ERROR_IF_NULL(hMutex); + + DWORD waitResult = WaitForSingleObject(hMutex, 0); + if (waitResult != WAIT_OBJECT_0 && waitResult != WAIT_ABANDONED) + { + return HRESULT_FROM_WIN32(ERROR_SERVICE_ALREADY_RUNNING); + } + + RETURN_IF_FAILED(WindowsPackageManagerServerInitializeRPCServer()); + + manualResetEvent = CreateOrOpenServerStartEvent(); + manualResetEvent.SetEvent(); + } + else + { + // Register all the CoCreatableClassWrlCreatorMapInclude classes + RETURN_IF_FAILED(WindowsPackageManagerServerModuleRegister()); + } + + _comServerExitEvent.wait(); + WindowsPackageManagerServerLog("Server shutting down after exit event signaled."); + + if (manualResetEvent) + { + manualResetEvent.reset(); + } + + if (!manualActivation) + { + RETURN_IF_FAILED(WindowsPackageManagerServerModuleUnregister()); + } + } + catch (...) + { + LOG_CAUGHT_EXCEPTION(); + RETURN_CAUGHT_EXCEPTION(); + } + + return 0; +} diff --git a/src/WinGetServer/packages.config b/src/WinGetServer/packages.config index a4d92efeae..d690ede8a3 100644 --- a/src/WinGetServer/packages.config +++ b/src/WinGetServer/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/WinGetServer/resource.h b/src/WinGetServer/resource.h index 283fde90e5..9d0a1bdcea 100644 --- a/src/WinGetServer/resource.h +++ b/src/WinGetServer/resource.h @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/WinGetSourceCreator/Helpers.cs b/src/WinGetSourceCreator/Helpers.cs index 7e34735064..834cb7a806 100644 --- a/src/WinGetSourceCreator/Helpers.cs +++ b/src/WinGetSourceCreator/Helpers.cs @@ -1,212 +1,212 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.WinGetSourceCreator -{ - using global::WinGetSourceCreator.Model; - using Microsoft.Msix.Utils.ProcessRunner; - using System.Diagnostics; - using System.Xml; - - internal static class Helpers - { - public static void SignInstaller(SourceInstaller installer, Signature signature) - { - if (installer.Type == InstallerType.Msix) - { - SignMsixFile(installer.InstallerFile, signature); - } - else - { - SignFile(installer.InstallerFile, signature); - } - } - - public static void SignFile(string fileToSign, Signature signature) - { - if (!File.Exists(fileToSign)) - { - throw new FileNotFoundException(fileToSign); - } - - if (!File.Exists(signature.CertFile)) - { - throw new FileNotFoundException(signature.CertFile); - } - - string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; - string signtoolExecutable = Path.Combine(pathToSDK, "signtool.exe"); - string command = $"sign /a /fd sha256 /f \"{signature.CertFile}\" "; - if (!string.IsNullOrEmpty(signature.Password)) - { - command += $"/p {signature.Password} "; - } - if (!string.IsNullOrEmpty(signature.TimestampServer)) - { - command += $"/tr {signature.TimestampServer} /td sha256 "; - } - command += $"\"{fileToSign}\""; - RunCommand(signtoolExecutable, command); - } - - public static void SignMsixFile(string fileToSign, Signature signature) - { - if (!File.Exists(fileToSign)) - { - throw new FileNotFoundException(fileToSign); - } - - // Modify publisher if needed. - if (signature.Publisher != null) - { - string tmpPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - Unpack(fileToSign, tmpPath); - ModifyAppxManifestIdentity(Path.Combine(tmpPath, "AppxManifest.xml"), signature.Publisher); - Pack(fileToSign, tmpPath); - - try - { - Directory.Delete(tmpPath, true); - } - catch (Exception) - { - } - } - - SignFile(fileToSign, signature); - } - - public static void Unpack(string package, string outDir) - { - if (!File.Exists(package)) - { - throw new FileNotFoundException(package); - } - - string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; - string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); - string args = $"unpack /nv /p \"{package}\" /d \"{outDir}\""; - Process p = new Process - { - StartInfo = new ProcessStartInfo(makeappxExecutable, args) - }; - p.Start(); - p.WaitForExit(); - } - - public static void PackWithMappingFile(string outputPackage, string mappingFile) - { - if (!File.Exists(mappingFile)) - { - throw new FileNotFoundException(mappingFile); - } - - string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; - string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); - string args = $"pack /o /nv /f \"{mappingFile}\" /p \"{outputPackage}\""; - RunCommand(makeappxExecutable, args); - } - - public static void Pack(string outputPackage, string directoryToPack) - { - if (!Directory.Exists(directoryToPack)) - { - throw new DirectoryNotFoundException(directoryToPack); - } - - if (File.Exists(outputPackage)) - { - File.Delete(outputPackage); - } - - string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; - string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); - string args = $"pack /o /d \"{directoryToPack}\" /p \"{outputPackage}\""; - RunCommand(makeappxExecutable, args); - } - - public static void RunCommand(string command, string args, string? workingDirectory = null) - { - Process p = new() - { - StartInfo = new ProcessStartInfo(command, args) - }; - - if (workingDirectory != null) - { - p.StartInfo.WorkingDirectory = workingDirectory; - } - p.Start(); - p.WaitForExit(); - } - - // If in the future we edit more elements, this should be a nice wrapper class. - public static void ModifyAppxManifestIdentity(string manifestFile, string? identityPublisher) - { - if (!File.Exists(manifestFile)) - { - throw new FileNotFoundException(manifestFile); - } - - var xmlDoc = new XmlDocument(); - XmlNamespaceManager namespaces = new XmlNamespaceManager(xmlDoc.NameTable); - namespaces.AddNamespace("n", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); - xmlDoc.Load(manifestFile); - var identityNode = xmlDoc.SelectSingleNode("/n:Package/n:Identity", namespaces); - if (identityNode == null) - { - throw new NullReferenceException("Identity node"); - } - - if (!string.IsNullOrEmpty(identityPublisher)) - { - var attr = identityNode.Attributes?["Publisher"]; - if (attr == null) - { - throw new NullReferenceException("Publisher attribute"); - } - attr.Value = identityPublisher; - } - - xmlDoc.Save(manifestFile); - } - - // Gets the AppxSignature.p7x file. - public static string GetSignatureFileFromMsix(string packageFilePath) - { - string extractedPackageDest = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - if (Directory.Exists(extractedPackageDest)) - { - Directory.Delete(extractedPackageDest, true); - } - - Helpers.Unpack(packageFilePath, extractedPackageDest); - - return Path.Combine(extractedPackageDest, "AppxSignature.p7x"); - } - - public static void CopyDirectory(string sourceDirName, string destDirName) - { - if (!Directory.Exists(destDirName)) - { - Directory.CreateDirectory(destDirName); - } - - DirectoryInfo dir = new (sourceDirName); - DirectoryInfo[] dirs = dir.GetDirectories(); - - FileInfo[] files = dir.GetFiles(); - foreach (FileInfo file in files) - { - string temppath = Path.Combine(destDirName, file.Name); - file.CopyTo(temppath, false); - } - - foreach (DirectoryInfo subdir in dirs) - { - string temppath = Path.Combine(destDirName, subdir.Name); - CopyDirectory(subdir.FullName, temppath); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.WinGetSourceCreator +{ + using global::WinGetSourceCreator.Model; + using Microsoft.Msix.Utils.ProcessRunner; + using System.Diagnostics; + using System.Xml; + + internal static class Helpers + { + public static void SignInstaller(SourceInstaller installer, Signature signature) + { + if (installer.Type == InstallerType.Msix) + { + SignMsixFile(installer.InstallerFile, signature); + } + else + { + SignFile(installer.InstallerFile, signature); + } + } + + public static void SignFile(string fileToSign, Signature signature) + { + if (!File.Exists(fileToSign)) + { + throw new FileNotFoundException(fileToSign); + } + + if (!File.Exists(signature.CertFile)) + { + throw new FileNotFoundException(signature.CertFile); + } + + string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; + string signtoolExecutable = Path.Combine(pathToSDK, "signtool.exe"); + string command = $"sign /a /fd sha256 /f \"{signature.CertFile}\" "; + if (!string.IsNullOrEmpty(signature.Password)) + { + command += $"/p {signature.Password} "; + } + if (!string.IsNullOrEmpty(signature.TimestampServer)) + { + command += $"/tr {signature.TimestampServer} /td sha256 "; + } + command += $"\"{fileToSign}\""; + RunCommand(signtoolExecutable, command); + } + + public static void SignMsixFile(string fileToSign, Signature signature) + { + if (!File.Exists(fileToSign)) + { + throw new FileNotFoundException(fileToSign); + } + + // Modify publisher if needed. + if (signature.Publisher != null) + { + string tmpPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Unpack(fileToSign, tmpPath); + ModifyAppxManifestIdentity(Path.Combine(tmpPath, "AppxManifest.xml"), signature.Publisher); + Pack(fileToSign, tmpPath); + + try + { + Directory.Delete(tmpPath, true); + } + catch (Exception) + { + } + } + + SignFile(fileToSign, signature); + } + + public static void Unpack(string package, string outDir) + { + if (!File.Exists(package)) + { + throw new FileNotFoundException(package); + } + + string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; + string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); + string args = $"unpack /nv /p \"{package}\" /d \"{outDir}\""; + Process p = new Process + { + StartInfo = new ProcessStartInfo(makeappxExecutable, args) + }; + p.Start(); + p.WaitForExit(); + } + + public static void PackWithMappingFile(string outputPackage, string mappingFile) + { + if (!File.Exists(mappingFile)) + { + throw new FileNotFoundException(mappingFile); + } + + string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; + string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); + string args = $"pack /o /nv /f \"{mappingFile}\" /p \"{outputPackage}\""; + RunCommand(makeappxExecutable, args); + } + + public static void Pack(string outputPackage, string directoryToPack) + { + if (!Directory.Exists(directoryToPack)) + { + throw new DirectoryNotFoundException(directoryToPack); + } + + if (File.Exists(outputPackage)) + { + File.Delete(outputPackage); + } + + string pathToSDK = SDKDetector.Instance.LatestSDKBinPath; + string makeappxExecutable = Path.Combine(pathToSDK, "makeappx.exe"); + string args = $"pack /o /d \"{directoryToPack}\" /p \"{outputPackage}\""; + RunCommand(makeappxExecutable, args); + } + + public static void RunCommand(string command, string args, string? workingDirectory = null) + { + Process p = new() + { + StartInfo = new ProcessStartInfo(command, args) + }; + + if (workingDirectory != null) + { + p.StartInfo.WorkingDirectory = workingDirectory; + } + p.Start(); + p.WaitForExit(); + } + + // If in the future we edit more elements, this should be a nice wrapper class. + public static void ModifyAppxManifestIdentity(string manifestFile, string? identityPublisher) + { + if (!File.Exists(manifestFile)) + { + throw new FileNotFoundException(manifestFile); + } + + var xmlDoc = new XmlDocument(); + XmlNamespaceManager namespaces = new XmlNamespaceManager(xmlDoc.NameTable); + namespaces.AddNamespace("n", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); + xmlDoc.Load(manifestFile); + var identityNode = xmlDoc.SelectSingleNode("/n:Package/n:Identity", namespaces); + if (identityNode == null) + { + throw new NullReferenceException("Identity node"); + } + + if (!string.IsNullOrEmpty(identityPublisher)) + { + var attr = identityNode.Attributes?["Publisher"]; + if (attr == null) + { + throw new NullReferenceException("Publisher attribute"); + } + attr.Value = identityPublisher; + } + + xmlDoc.Save(manifestFile); + } + + // Gets the AppxSignature.p7x file. + public static string GetSignatureFileFromMsix(string packageFilePath) + { + string extractedPackageDest = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + if (Directory.Exists(extractedPackageDest)) + { + Directory.Delete(extractedPackageDest, true); + } + + Helpers.Unpack(packageFilePath, extractedPackageDest); + + return Path.Combine(extractedPackageDest, "AppxSignature.p7x"); + } + + public static void CopyDirectory(string sourceDirName, string destDirName) + { + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + DirectoryInfo dir = new (sourceDirName); + DirectoryInfo[] dirs = dir.GetDirectories(); + + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + string temppath = Path.Combine(destDirName, file.Name); + file.CopyTo(temppath, false); + } + + foreach (DirectoryInfo subdir in dirs) + { + string temppath = Path.Combine(destDirName, subdir.Name); + CopyDirectory(subdir.FullName, temppath); + } + } + } +} diff --git a/src/WinGetSourceCreator/ManifestTokens.cs b/src/WinGetSourceCreator/ManifestTokens.cs index 9f70d3e595..618ef5d802 100644 --- a/src/WinGetSourceCreator/ManifestTokens.cs +++ b/src/WinGetSourceCreator/ManifestTokens.cs @@ -1,54 +1,54 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.WinGetSourceCreator -{ - using System.Security.Cryptography; - - public class ManifestTokens - { - public ManifestTokens() - { - } - - public Dictionary Tokens { get; private set; } = new Dictionary(); - - public void AddHashToken(string file, string token) - { - if (!token.StartsWith("<") || !token.EndsWith(">")) - { - throw new Exception("Token should be in the form of "); - } - - var hash = HashFile(file); - this.Tokens.Add(token, hash); - } - - /// - /// Gets the hash of the specified file. - /// - /// File path. - /// Hash of file. - private static string HashFile(string filePath) - { - if (!File.Exists(filePath)) - { - throw new FileNotFoundException(filePath); - } - - string hash = string.Empty; - - using SHA256 mySHA256 = SHA256.Create(); - using FileStream fs = File.OpenRead(filePath); - fs.Position = 0; - byte[] hashValue = mySHA256.ComputeHash(fs); - - for (int i = 0; i < hashValue.Length; i++) - { - hash += $"{hashValue[i]:X2}"; - } - - return hash; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.WinGetSourceCreator +{ + using System.Security.Cryptography; + + public class ManifestTokens + { + public ManifestTokens() + { + } + + public Dictionary Tokens { get; private set; } = new Dictionary(); + + public void AddHashToken(string file, string token) + { + if (!token.StartsWith("<") || !token.EndsWith(">")) + { + throw new Exception("Token should be in the form of "); + } + + var hash = HashFile(file); + this.Tokens.Add(token, hash); + } + + /// + /// Gets the hash of the specified file. + /// + /// File path. + /// Hash of file. + private static string HashFile(string filePath) + { + if (!File.Exists(filePath)) + { + throw new FileNotFoundException(filePath); + } + + string hash = string.Empty; + + using SHA256 mySHA256 = SHA256.Create(); + using FileStream fs = File.OpenRead(filePath); + fs.Position = 0; + byte[] hashValue = mySHA256.ComputeHash(fs); + + for (int i = 0; i < hashValue.Length; i++) + { + hash += $"{hashValue[i]:X2}"; + } + + return hash; + } + } +} diff --git a/src/WinGetSourceCreator/Model/DynamicInstaller.cs b/src/WinGetSourceCreator/Model/DynamicInstaller.cs index ea79d56173..f1a772acf7 100644 --- a/src/WinGetSourceCreator/Model/DynamicInstaller.cs +++ b/src/WinGetSourceCreator/Model/DynamicInstaller.cs @@ -1,88 +1,88 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WinGetSourceCreator.Model -{ - using Microsoft.WinGetSourceCreator; - using System.IO.Compression; - - public class DynamicInstaller : Installer - { - // Input depends on the Type. - // For zip it is the directories or files that need to included in the zip - public List Input { get; set; } = new List(); - - internal new void Validate() - { - base.Validate(); - } - - public string Create(string workingDirectory) - { - string outputFile = this.Name; - if (!Path.IsPathFullyQualified(outputFile)) - { - outputFile = Path.Combine(workingDirectory, outputFile); - } - - var parent = Path.GetDirectoryName(outputFile); - if (!string.IsNullOrEmpty(parent)) - { - Directory.CreateDirectory(parent); - } - - if (this.Type == InstallerType.Zip) - { - CreateZipInstaller(outputFile); - } - else - { - throw new NotImplementedException(); - } - - return outputFile; - } - - private void CreateZipInstaller(string outputFile) - { - var tmpPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - if (Directory.Exists(tmpPath)) - { - Directory.Delete(tmpPath, true); - } - Directory.CreateDirectory(tmpPath); - - foreach (var input in this.Input) - { - if (!Path.IsPathFullyQualified(input)) - { - throw new InvalidOperationException($"Must be a fully qualified name {input}"); - } - - if (File.Exists(input)) - { - // TODO: maybe we want to preserve the dir? - File.Copy(input, Path.Combine(tmpPath, Path.GetFileName(input)), true); - } - else if (Directory.Exists(input)) - { - Helpers.CopyDirectory(input, tmpPath); - } - else - { - throw new InvalidOperationException(input); - } - } - - ZipFile.CreateFromDirectory(tmpPath, outputFile); - - try - { - Directory.Delete(tmpPath, true); - } - catch (Exception) - { - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WinGetSourceCreator.Model +{ + using Microsoft.WinGetSourceCreator; + using System.IO.Compression; + + public class DynamicInstaller : Installer + { + // Input depends on the Type. + // For zip it is the directories or files that need to included in the zip + public List Input { get; set; } = new List(); + + internal new void Validate() + { + base.Validate(); + } + + public string Create(string workingDirectory) + { + string outputFile = this.Name; + if (!Path.IsPathFullyQualified(outputFile)) + { + outputFile = Path.Combine(workingDirectory, outputFile); + } + + var parent = Path.GetDirectoryName(outputFile); + if (!string.IsNullOrEmpty(parent)) + { + Directory.CreateDirectory(parent); + } + + if (this.Type == InstallerType.Zip) + { + CreateZipInstaller(outputFile); + } + else + { + throw new NotImplementedException(); + } + + return outputFile; + } + + private void CreateZipInstaller(string outputFile) + { + var tmpPath = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + if (Directory.Exists(tmpPath)) + { + Directory.Delete(tmpPath, true); + } + Directory.CreateDirectory(tmpPath); + + foreach (var input in this.Input) + { + if (!Path.IsPathFullyQualified(input)) + { + throw new InvalidOperationException($"Must be a fully qualified name {input}"); + } + + if (File.Exists(input)) + { + // TODO: maybe we want to preserve the dir? + File.Copy(input, Path.Combine(tmpPath, Path.GetFileName(input)), true); + } + else if (Directory.Exists(input)) + { + Helpers.CopyDirectory(input, tmpPath); + } + else + { + throw new InvalidOperationException(input); + } + } + + ZipFile.CreateFromDirectory(tmpPath, outputFile); + + try + { + Directory.Delete(tmpPath, true); + } + catch (Exception) + { + } + } + } +} diff --git a/src/WinGetSourceCreator/Model/Installer.cs b/src/WinGetSourceCreator/Model/Installer.cs index e5cad447b5..c71c732116 100644 --- a/src/WinGetSourceCreator/Model/Installer.cs +++ b/src/WinGetSourceCreator/Model/Installer.cs @@ -1,42 +1,42 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WinGetSourceCreator.Model -{ - public abstract class Installer - { - public InstallerType Type { get; set; } - - // The name of the installer when it copied or created. - public string Name { get; set; } = string.Empty; - - // The identifying token of the installer in the manifests. - public string? HashToken { get; set; } - - // An optional token relevant to the installer. - // If the installer is an msix, this is the token used for the appx signature hash. - public string? SignatureToken { get; set; } - - public Signature? Signature { get; set; } - - public bool SkipSignature { get; set; } - - protected void Validate() - { - if (string.IsNullOrEmpty(this.Name)) - { - throw new ArgumentNullException(nameof(this.Name)); - } - - if (this.Signature != null) - { - this.Signature.Validate(); - } - - if (this.Type != InstallerType.Msix && !string.IsNullOrEmpty(this.SignatureToken)) - { - throw new Exception($"{nameof(this.SignatureToken)} can only be used for MSIX"); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WinGetSourceCreator.Model +{ + public abstract class Installer + { + public InstallerType Type { get; set; } + + // The name of the installer when it copied or created. + public string Name { get; set; } = string.Empty; + + // The identifying token of the installer in the manifests. + public string? HashToken { get; set; } + + // An optional token relevant to the installer. + // If the installer is an msix, this is the token used for the appx signature hash. + public string? SignatureToken { get; set; } + + public Signature? Signature { get; set; } + + public bool SkipSignature { get; set; } + + protected void Validate() + { + if (string.IsNullOrEmpty(this.Name)) + { + throw new ArgumentNullException(nameof(this.Name)); + } + + if (this.Signature != null) + { + this.Signature.Validate(); + } + + if (this.Type != InstallerType.Msix && !string.IsNullOrEmpty(this.SignatureToken)) + { + throw new Exception($"{nameof(this.SignatureToken)} can only be used for MSIX"); + } + } + } +} diff --git a/src/WinGetSourceCreator/Model/InstallerType.cs b/src/WinGetSourceCreator/Model/InstallerType.cs index 526b20fc77..e5cce1424a 100644 --- a/src/WinGetSourceCreator/Model/InstallerType.cs +++ b/src/WinGetSourceCreator/Model/InstallerType.cs @@ -1,14 +1,14 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WinGetSourceCreator.Model -{ - public enum InstallerType - { - Msix, - Exe, - Msi, - Zip, - Font, - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WinGetSourceCreator.Model +{ + public enum InstallerType + { + Msix, + Exe, + Msi, + Zip, + Font, + } +} diff --git a/src/WinGetSourceCreator/Model/LocalInstaller.cs b/src/WinGetSourceCreator/Model/LocalInstaller.cs index 6bcfb62b40..006ccfc671 100644 --- a/src/WinGetSourceCreator/Model/LocalInstaller.cs +++ b/src/WinGetSourceCreator/Model/LocalInstaller.cs @@ -1,26 +1,26 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WinGetSourceCreator.Model -{ - public class LocalInstaller : Installer - { - // The full path of the installer. - // Gets copied to the output directory and optionally signed. - public string Input { get; set; } = string.Empty; - - internal new void Validate() - { - base.Validate(); - if (string.IsNullOrEmpty(this.Input)) - { - throw new ArgumentNullException(nameof(this.Input)); - } - - if (!File.Exists(this.Input)) - { - throw new FileNotFoundException(this.Input); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WinGetSourceCreator.Model +{ + public class LocalInstaller : Installer + { + // The full path of the installer. + // Gets copied to the output directory and optionally signed. + public string Input { get; set; } = string.Empty; + + internal new void Validate() + { + base.Validate(); + if (string.IsNullOrEmpty(this.Input)) + { + throw new ArgumentNullException(nameof(this.Input)); + } + + if (!File.Exists(this.Input)) + { + throw new FileNotFoundException(this.Input); + } + } + } +} diff --git a/src/WinGetSourceCreator/Model/LocalSource.cs b/src/WinGetSourceCreator/Model/LocalSource.cs index da93456a1c..79d8bd6a2a 100644 --- a/src/WinGetSourceCreator/Model/LocalSource.cs +++ b/src/WinGetSourceCreator/Model/LocalSource.cs @@ -1,83 +1,83 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WinGetSourceCreator.Model -{ - public class LocalSource - { - // Full path of the input appx manifest. - // Will be used to generate the package. - public string AppxManifest { get; set; } = string.Empty; - - // The working directory where manifest will copied and referenced by the index. - public string WorkingDirectory { get; set; } = string.Empty; - - // Input manifests. - // If it is a file the manifest gets copied to the input directory. - // If it is a directory the manifests will be copied preserving the sub dirs structure. - public List LocalManifests { get; set; } = new(); - - public List? LocalInstallers { get; set; } - - public List? DynamicInstallers { get; set; } - - public Signature? Signature { get; set; } - - public void Validate() - { - if (string.IsNullOrEmpty(this.AppxManifest)) - { - throw new ArgumentNullException(nameof(this.AppxManifest)); - } - - if (string.IsNullOrEmpty(this.WorkingDirectory)) - { - throw new ArgumentNullException(nameof(this.WorkingDirectory)); - } - - if (this.LocalManifests.Count == 0) - { - throw new ArgumentException(nameof(this.LocalManifests)); - } - - if (this.LocalInstallers != null) - { - foreach (var installer in this.LocalInstallers) - { - installer.Validate(); - } - } - - if (this.DynamicInstallers != null) - { - foreach (var installer in this.DynamicInstallers) - { - installer.Validate(); - } - } - - if (this.Signature != null) - { - this.Signature.Validate(); - } - } - - public string GetIndexName() - { - return "index.db"; - } - - public string GetSourceName(int version) - { - switch (version) - { - case 1: - return "source.msix"; - case 2: - return "source2.msix"; - } - - throw new ArgumentOutOfRangeException(nameof(version), version, "Unknown source major version"); - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WinGetSourceCreator.Model +{ + public class LocalSource + { + // Full path of the input appx manifest. + // Will be used to generate the package. + public string AppxManifest { get; set; } = string.Empty; + + // The working directory where manifest will copied and referenced by the index. + public string WorkingDirectory { get; set; } = string.Empty; + + // Input manifests. + // If it is a file the manifest gets copied to the input directory. + // If it is a directory the manifests will be copied preserving the sub dirs structure. + public List LocalManifests { get; set; } = new(); + + public List? LocalInstallers { get; set; } + + public List? DynamicInstallers { get; set; } + + public Signature? Signature { get; set; } + + public void Validate() + { + if (string.IsNullOrEmpty(this.AppxManifest)) + { + throw new ArgumentNullException(nameof(this.AppxManifest)); + } + + if (string.IsNullOrEmpty(this.WorkingDirectory)) + { + throw new ArgumentNullException(nameof(this.WorkingDirectory)); + } + + if (this.LocalManifests.Count == 0) + { + throw new ArgumentException(nameof(this.LocalManifests)); + } + + if (this.LocalInstallers != null) + { + foreach (var installer in this.LocalInstallers) + { + installer.Validate(); + } + } + + if (this.DynamicInstallers != null) + { + foreach (var installer in this.DynamicInstallers) + { + installer.Validate(); + } + } + + if (this.Signature != null) + { + this.Signature.Validate(); + } + } + + public string GetIndexName() + { + return "index.db"; + } + + public string GetSourceName(int version) + { + switch (version) + { + case 1: + return "source.msix"; + case 2: + return "source2.msix"; + } + + throw new ArgumentOutOfRangeException(nameof(version), version, "Unknown source major version"); + } + } +} diff --git a/src/WinGetSourceCreator/Model/Signature.cs b/src/WinGetSourceCreator/Model/Signature.cs index 817af8822a..ebaab06341 100644 --- a/src/WinGetSourceCreator/Model/Signature.cs +++ b/src/WinGetSourceCreator/Model/Signature.cs @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WinGetSourceCreator.Model -{ - public class Signature - { - // Full path of the certificate used to sign the package and installers - public string CertFile { get; set; } = string.Empty; - - public string? Password { get; set; } - - // The publisher for the AppxPackage Identity Name property. - public string? Publisher { get; set; } - - // RFC 3161 timestamp server URL (e.g. http://timestamp.digicert.com). - // When set, a countersignature timestamp is added so the signature remains - // valid after the signing certificate expires. - public string? TimestampServer { get; set; } - - internal void Validate() - { - if (string.IsNullOrEmpty(this.CertFile)) - { - throw new ArgumentNullException(nameof(this.CertFile)); - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WinGetSourceCreator.Model +{ + public class Signature + { + // Full path of the certificate used to sign the package and installers + public string CertFile { get; set; } = string.Empty; + + public string? Password { get; set; } + + // The publisher for the AppxPackage Identity Name property. + public string? Publisher { get; set; } + + // RFC 3161 timestamp server URL (e.g. http://timestamp.digicert.com). + // When set, a countersignature timestamp is added so the signature remains + // valid after the signing certificate expires. + public string? TimestampServer { get; set; } + + internal void Validate() + { + if (string.IsNullOrEmpty(this.CertFile)) + { + throw new ArgumentNullException(nameof(this.CertFile)); + } + } + } +} diff --git a/src/WinGetSourceCreator/Model/SourceInstaller.cs b/src/WinGetSourceCreator/Model/SourceInstaller.cs index f614505874..208f4dc938 100644 --- a/src/WinGetSourceCreator/Model/SourceInstaller.cs +++ b/src/WinGetSourceCreator/Model/SourceInstaller.cs @@ -1,40 +1,40 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace WinGetSourceCreator.Model -{ - public class SourceInstaller : Installer - { - public SourceInstaller(string workingDirectory, DynamicInstaller installer) - { - this.Initialize(installer); - this.InstallerFile = installer.Create(workingDirectory); - } - - public SourceInstaller(string workingDirectory, LocalInstaller installer) - { - this.Initialize(installer); - this.InstallerFile = Path.Combine(workingDirectory, this.Name); - var parent = Path.GetDirectoryName(this.InstallerFile); - if (!string.IsNullOrEmpty(parent)) - { - Directory.CreateDirectory(parent); - } - File.Copy(installer.Input, this.InstallerFile, true); - } - - public string InstallerFile { get; private set; } - - private void Initialize(Installer installer) - { - foreach (var installerProperty in installer.GetType().GetProperties()) - { - var toProperty = this.GetType().GetProperty(installerProperty.Name); - if (toProperty != null) - { - toProperty.SetValue(this, installerProperty.GetValue(installer)); - } - } - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace WinGetSourceCreator.Model +{ + public class SourceInstaller : Installer + { + public SourceInstaller(string workingDirectory, DynamicInstaller installer) + { + this.Initialize(installer); + this.InstallerFile = installer.Create(workingDirectory); + } + + public SourceInstaller(string workingDirectory, LocalInstaller installer) + { + this.Initialize(installer); + this.InstallerFile = Path.Combine(workingDirectory, this.Name); + var parent = Path.GetDirectoryName(this.InstallerFile); + if (!string.IsNullOrEmpty(parent)) + { + Directory.CreateDirectory(parent); + } + File.Copy(installer.Input, this.InstallerFile, true); + } + + public string InstallerFile { get; private set; } + + private void Initialize(Installer installer) + { + foreach (var installerProperty in installer.GetType().GetProperties()) + { + var toProperty = this.GetType().GetProperty(installerProperty.Name); + if (toProperty != null) + { + toProperty.SetValue(this, installerProperty.GetValue(installer)); + } + } + } + } +} diff --git a/src/WinGetSourceCreator/WinGetLocalSource.cs b/src/WinGetSourceCreator/WinGetLocalSource.cs index 167a97cc43..bff56efc31 100644 --- a/src/WinGetSourceCreator/WinGetLocalSource.cs +++ b/src/WinGetSourceCreator/WinGetLocalSource.cs @@ -1,294 +1,294 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -namespace Microsoft.WinGetSourceCreator -{ - using global::WinGetSourceCreator.Model; - using System.Text.Json.Serialization; - using System.Text.Json; - using Microsoft.WinGetUtil.Api; - using Microsoft.WinGetUtil.Interfaces; - - public class WinGetLocalSource - { - private readonly string workingDirectory; - private readonly ManifestTokens tokens; - private readonly Signature? signature; - - public static void CreateFromLocalSourceFile(string localSourceFile) - { - var content = File.ReadAllText(localSourceFile); - content = Environment.ExpandEnvironmentVariables(content); - - var options = new JsonSerializerOptions - { - PropertyNameCaseInsensitive = true, - Converters = - { - new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) - } - }; - - content = content.Replace("\\", "/"); - - var localSource = JsonSerializer.Deserialize(content, options); - if (localSource == null) - { - throw new Exception("Failed deserializing"); - } - - CreateLocalSource(localSource); - } - - public static void CreateLocalSource(LocalSource localSource) - { - localSource.Validate(); - - var wingetSource = new WinGetLocalSource(localSource.WorkingDirectory, localSource.Signature); - - if (localSource.LocalInstallers != null) - { - foreach (var installer in localSource.LocalInstallers) - { - wingetSource.PrepareLocalInstaller(installer); - } - } - - if (localSource.DynamicInstallers != null) - { - foreach (var installer in localSource.DynamicInstallers) - { - wingetSource.PrepareDynamicInstaller(installer); - } - } - - foreach (var localManifest in localSource.LocalManifests) - { - wingetSource.PrepareManifest(localManifest); - } - - var indexV2File = wingetSource.CreateIndex(localSource.GetIndexName(), 2, 0); - _ = wingetSource.CreatePackage(localSource.GetSourceName(2), localSource.AppxManifest, indexV2File, localSource.Signature); - - var indexV1File = wingetSource.CreateIndex(localSource.GetIndexName()); - _ = wingetSource.CreatePackage(localSource.GetSourceName(1), localSource.AppxManifest, indexV1File, localSource.Signature); - } - - public WinGetLocalSource(string workingDirectory, Signature? signature) - { - this.workingDirectory = Path.GetFullPath(workingDirectory); - - if (Directory.Exists(workingDirectory)) - { - Directory.Delete(workingDirectory, true); - } - Directory.CreateDirectory(workingDirectory); - - this.tokens = new(); - this.signature = signature; - } - - public void PrepareDynamicInstaller(DynamicInstaller installer) - { - var sourceInstaller = new SourceInstaller(this.workingDirectory, installer); - PrepareInstaller(sourceInstaller); - } - - public void PrepareLocalInstaller(LocalInstaller installer) - { - var sourceInstaller = new SourceInstaller(this.workingDirectory, installer); - PrepareInstaller(sourceInstaller); - } - - public void PrepareManifest(string input) - { - if (File.Exists(input)) - { - - CopyManifestFile(input, Path.Combine(this.workingDirectory, Path.GetFileName(input))); - } - else - { - CopyManifestFiles(input, this.workingDirectory); - } - } - - public string CreateIndex(string indexName, uint? majorVersion = null, uint? minorVersion = null) - { - string fullPath = Path.Combine(this.workingDirectory, indexName); - - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - } - - WinGetFactory factory = new (); - using IWinGetSQLiteIndex indexHelper = majorVersion == null ? factory.SQLiteIndexCreateLatestVersion(fullPath) : factory.SQLiteIndexCreate(fullPath, majorVersion.Value, minorVersion.GetValueOrDefault()); - - Queue filesQueue = new(Directory.EnumerateFiles(this.workingDirectory, "*.yaml", SearchOption.AllDirectories)); - while (filesQueue.Count > 0) - { - int currentCount = filesQueue.Count; - - for (int i = 0; i < currentCount; i++) - { - string file = filesQueue.Dequeue(); - try - { - var rel = Path.GetRelativePath(this.workingDirectory, file); - indexHelper.AddManifest(file, rel); - } - catch - { - // If adding manifest to index fails, add to queue and try again. - // This can occur if there is a package dependency that has not yet been added to the index. - filesQueue.Enqueue(file); - } - } - - if (filesQueue.Count == currentCount) - { - throw new InvalidOperationException("Failed to add all manifests in directory to index."); - } - } - - indexHelper.PrepareForPackaging(); - - return fullPath; - } - - public string CreatePackage(string packageName, string inputAppxManifestFile, string indexPath, Signature? signature) - { - if (!File.Exists(inputAppxManifestFile)) - { - throw new FileNotFoundException(inputAppxManifestFile); - } - - if (!File.Exists(indexPath)) - { - throw new FileNotFoundException(indexPath); - } - - string appxManifestFile = Path.Combine(this.workingDirectory, "AppxManifest.xml"); - File.Copy(inputAppxManifestFile, appxManifestFile, true); - - if (signature != null && signature.Publisher != null) - { - Helpers.ModifyAppxManifestIdentity(appxManifestFile, signature.Publisher); - } - - string mappingFile = Path.Combine(this.workingDirectory, "MappingFile.txt"); - - { - using StreamWriter outputFile = new(mappingFile, false); - outputFile.WriteLine("[Files]"); - outputFile.WriteLine($"\"{indexPath}\" \"Public\\{Path.GetFileName(indexPath)}\""); - outputFile.WriteLine($"\"{appxManifestFile}\" \"AppxManifest.xml\""); - } - - string outputPackage = Path.Combine(this.workingDirectory, packageName); - Helpers.PackWithMappingFile(outputPackage, mappingFile); - - if (signature != null) - { - Helpers.SignFile(outputPackage, signature); - } - - return outputPackage; - } - - // Copies all .yaml files - private void CopyManifestFiles(string sourceDir, string destDir) - { - DirectoryInfo dir = new DirectoryInfo(sourceDir); - DirectoryInfo[] dirs = dir.GetDirectories(); - - FileInfo[] files = dir.GetFiles(); - foreach (FileInfo file in files) - { - if (file.Extension == ".yaml") - { - CopyManifestFile(file.FullName, Path.Combine(destDir, file.Name)); - } - } - - foreach (DirectoryInfo subdir in dirs) - { - CopyManifestFiles(subdir.FullName, Path.Combine(destDir, subdir.Name)); - } - } - - // Copies a file and replaces any token found. - private void CopyManifestFile(string sourceFile, string destinationFile) - { - if (!File.Exists(sourceFile)) - { - throw new FileNotFoundException(sourceFile); - } - - var content = File.ReadAllText(sourceFile); - - foreach (var token in this.tokens.Tokens) - { - if (content.Contains(token.Key)) - { - content = content.Replace(token.Key, token.Value); - } - } - - File.WriteAllText(destinationFile, content); - } - - private void PrepareInstaller(SourceInstaller installer) - { - // Sign installer if needed. - if (!installer.SkipSignature) - { - var sig = this.GetSignature(installer); - if (sig != null) - { - Helpers.SignInstaller(installer, sig); - } - } - - // Process hash token if needed. - if (!string.IsNullOrEmpty(installer.HashToken)) - { - this.tokens.AddHashToken(installer.InstallerFile, installer.HashToken); - } - - // Extra steps. - // An msix can include the signature token. - if (installer.Type == InstallerType.Msix) - { - if (!string.IsNullOrEmpty(installer.SignatureToken)) - { - var signatureFilePath = Helpers.GetSignatureFileFromMsix(installer.InstallerFile); - this.tokens.AddHashToken(signatureFilePath, installer.SignatureToken); - - try - { - var dir = Path.GetDirectoryName(signatureFilePath); - if (!string.IsNullOrEmpty(dir)) - { - Directory.Delete(dir, true); - } - } - catch (Exception) - { - } - } - } - } - - private Signature? GetSignature(Installer installer) - { - if (installer.Type == InstallerType.Zip) - { - return null; - } - - return installer.Signature == null ? this.signature : installer.Signature; - } - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace Microsoft.WinGetSourceCreator +{ + using global::WinGetSourceCreator.Model; + using System.Text.Json.Serialization; + using System.Text.Json; + using Microsoft.WinGetUtil.Api; + using Microsoft.WinGetUtil.Interfaces; + + public class WinGetLocalSource + { + private readonly string workingDirectory; + private readonly ManifestTokens tokens; + private readonly Signature? signature; + + public static void CreateFromLocalSourceFile(string localSourceFile) + { + var content = File.ReadAllText(localSourceFile); + content = Environment.ExpandEnvironmentVariables(content); + + var options = new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + Converters = + { + new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) + } + }; + + content = content.Replace("\\", "/"); + + var localSource = JsonSerializer.Deserialize(content, options); + if (localSource == null) + { + throw new Exception("Failed deserializing"); + } + + CreateLocalSource(localSource); + } + + public static void CreateLocalSource(LocalSource localSource) + { + localSource.Validate(); + + var wingetSource = new WinGetLocalSource(localSource.WorkingDirectory, localSource.Signature); + + if (localSource.LocalInstallers != null) + { + foreach (var installer in localSource.LocalInstallers) + { + wingetSource.PrepareLocalInstaller(installer); + } + } + + if (localSource.DynamicInstallers != null) + { + foreach (var installer in localSource.DynamicInstallers) + { + wingetSource.PrepareDynamicInstaller(installer); + } + } + + foreach (var localManifest in localSource.LocalManifests) + { + wingetSource.PrepareManifest(localManifest); + } + + var indexV2File = wingetSource.CreateIndex(localSource.GetIndexName(), 2, 0); + _ = wingetSource.CreatePackage(localSource.GetSourceName(2), localSource.AppxManifest, indexV2File, localSource.Signature); + + var indexV1File = wingetSource.CreateIndex(localSource.GetIndexName()); + _ = wingetSource.CreatePackage(localSource.GetSourceName(1), localSource.AppxManifest, indexV1File, localSource.Signature); + } + + public WinGetLocalSource(string workingDirectory, Signature? signature) + { + this.workingDirectory = Path.GetFullPath(workingDirectory); + + if (Directory.Exists(workingDirectory)) + { + Directory.Delete(workingDirectory, true); + } + Directory.CreateDirectory(workingDirectory); + + this.tokens = new(); + this.signature = signature; + } + + public void PrepareDynamicInstaller(DynamicInstaller installer) + { + var sourceInstaller = new SourceInstaller(this.workingDirectory, installer); + PrepareInstaller(sourceInstaller); + } + + public void PrepareLocalInstaller(LocalInstaller installer) + { + var sourceInstaller = new SourceInstaller(this.workingDirectory, installer); + PrepareInstaller(sourceInstaller); + } + + public void PrepareManifest(string input) + { + if (File.Exists(input)) + { + + CopyManifestFile(input, Path.Combine(this.workingDirectory, Path.GetFileName(input))); + } + else + { + CopyManifestFiles(input, this.workingDirectory); + } + } + + public string CreateIndex(string indexName, uint? majorVersion = null, uint? minorVersion = null) + { + string fullPath = Path.Combine(this.workingDirectory, indexName); + + if (File.Exists(fullPath)) + { + File.Delete(fullPath); + } + + WinGetFactory factory = new (); + using IWinGetSQLiteIndex indexHelper = majorVersion == null ? factory.SQLiteIndexCreateLatestVersion(fullPath) : factory.SQLiteIndexCreate(fullPath, majorVersion.Value, minorVersion.GetValueOrDefault()); + + Queue filesQueue = new(Directory.EnumerateFiles(this.workingDirectory, "*.yaml", SearchOption.AllDirectories)); + while (filesQueue.Count > 0) + { + int currentCount = filesQueue.Count; + + for (int i = 0; i < currentCount; i++) + { + string file = filesQueue.Dequeue(); + try + { + var rel = Path.GetRelativePath(this.workingDirectory, file); + indexHelper.AddManifest(file, rel); + } + catch + { + // If adding manifest to index fails, add to queue and try again. + // This can occur if there is a package dependency that has not yet been added to the index. + filesQueue.Enqueue(file); + } + } + + if (filesQueue.Count == currentCount) + { + throw new InvalidOperationException("Failed to add all manifests in directory to index."); + } + } + + indexHelper.PrepareForPackaging(); + + return fullPath; + } + + public string CreatePackage(string packageName, string inputAppxManifestFile, string indexPath, Signature? signature) + { + if (!File.Exists(inputAppxManifestFile)) + { + throw new FileNotFoundException(inputAppxManifestFile); + } + + if (!File.Exists(indexPath)) + { + throw new FileNotFoundException(indexPath); + } + + string appxManifestFile = Path.Combine(this.workingDirectory, "AppxManifest.xml"); + File.Copy(inputAppxManifestFile, appxManifestFile, true); + + if (signature != null && signature.Publisher != null) + { + Helpers.ModifyAppxManifestIdentity(appxManifestFile, signature.Publisher); + } + + string mappingFile = Path.Combine(this.workingDirectory, "MappingFile.txt"); + + { + using StreamWriter outputFile = new(mappingFile, false); + outputFile.WriteLine("[Files]"); + outputFile.WriteLine($"\"{indexPath}\" \"Public\\{Path.GetFileName(indexPath)}\""); + outputFile.WriteLine($"\"{appxManifestFile}\" \"AppxManifest.xml\""); + } + + string outputPackage = Path.Combine(this.workingDirectory, packageName); + Helpers.PackWithMappingFile(outputPackage, mappingFile); + + if (signature != null) + { + Helpers.SignFile(outputPackage, signature); + } + + return outputPackage; + } + + // Copies all .yaml files + private void CopyManifestFiles(string sourceDir, string destDir) + { + DirectoryInfo dir = new DirectoryInfo(sourceDir); + DirectoryInfo[] dirs = dir.GetDirectories(); + + FileInfo[] files = dir.GetFiles(); + foreach (FileInfo file in files) + { + if (file.Extension == ".yaml") + { + CopyManifestFile(file.FullName, Path.Combine(destDir, file.Name)); + } + } + + foreach (DirectoryInfo subdir in dirs) + { + CopyManifestFiles(subdir.FullName, Path.Combine(destDir, subdir.Name)); + } + } + + // Copies a file and replaces any token found. + private void CopyManifestFile(string sourceFile, string destinationFile) + { + if (!File.Exists(sourceFile)) + { + throw new FileNotFoundException(sourceFile); + } + + var content = File.ReadAllText(sourceFile); + + foreach (var token in this.tokens.Tokens) + { + if (content.Contains(token.Key)) + { + content = content.Replace(token.Key, token.Value); + } + } + + File.WriteAllText(destinationFile, content); + } + + private void PrepareInstaller(SourceInstaller installer) + { + // Sign installer if needed. + if (!installer.SkipSignature) + { + var sig = this.GetSignature(installer); + if (sig != null) + { + Helpers.SignInstaller(installer, sig); + } + } + + // Process hash token if needed. + if (!string.IsNullOrEmpty(installer.HashToken)) + { + this.tokens.AddHashToken(installer.InstallerFile, installer.HashToken); + } + + // Extra steps. + // An msix can include the signature token. + if (installer.Type == InstallerType.Msix) + { + if (!string.IsNullOrEmpty(installer.SignatureToken)) + { + var signatureFilePath = Helpers.GetSignatureFileFromMsix(installer.InstallerFile); + this.tokens.AddHashToken(signatureFilePath, installer.SignatureToken); + + try + { + var dir = Path.GetDirectoryName(signatureFilePath); + if (!string.IsNullOrEmpty(dir)) + { + Directory.Delete(dir, true); + } + } + catch (Exception) + { + } + } + } + } + + private Signature? GetSignature(Installer installer) + { + if (installer.Type == InstallerType.Zip) + { + return null; + } + + return installer.Signature == null ? this.signature : installer.Signature; + } + } +} diff --git a/src/WinGetSourceCreator/WinGetSourceCreator.csproj b/src/WinGetSourceCreator/WinGetSourceCreator.csproj index 79635e7e74..b68a80ca22 100644 --- a/src/WinGetSourceCreator/WinGetSourceCreator.csproj +++ b/src/WinGetSourceCreator/WinGetSourceCreator.csproj @@ -1,18 +1,18 @@ - - - - net8.0 - $(SolutionDir)$(Platform)\$(Configuration)\WinGetSourceCreator\ - enable - enable - - - - - - - - - - - + + + + net8.0 + $(SolutionDir)$(Platform)\$(Configuration)\WinGetSourceCreator\ + enable + enable + + + + + + + + + + + diff --git a/src/WinGetTestCommon/WinGetServerInstance.cs b/src/WinGetTestCommon/WinGetServerInstance.cs index 6a9292b9d5..348c78ed16 100644 --- a/src/WinGetTestCommon/WinGetServerInstance.cs +++ b/src/WinGetTestCommon/WinGetServerInstance.cs @@ -1,196 +1,196 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetTestCommon -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Runtime.InteropServices; - - /// - /// Represents an instance of a Windows Package Manager (WinGet) server. - /// - public class WinGetServerInstance - { - /// - /// The name of the executable for the COM server. - /// - public const string ServerExecutableName = "WindowsPackageManagerServer"; - - /// - /// The package family name for the development package. - /// - public const string DevelopmentPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; - - /// - /// The window name for the COM server message window. - /// - public const string TargetWindowName = "WingetMessageOnlyWindow"; - - /// - /// Gets the process for the server. - /// - public required Process Process { get; init; } - - /// - /// Gets a value indicating whether the current server has an associated window. - /// - public bool HasWindow - { - get - { - return EnumerateWindowHandles(TargetWindowName).Count > 0; - } - } - - /// - /// Sends a specified message to a window. - /// - /// The message to be sent to the window. - /// True to indicate that the message was sent and processed within the timeout; false otherwise. - public bool SendMessage(WindowMessage message) - { - const int TRUE = 0x1; - const int ENDSESSION_CLOSEAPP = 0x1; - const uint SMTO_ABORTIFHUNG = 0x0002; - const uint TIMEOUT_MS = 5000; - - var windowHandles = EnumerateWindowHandles(TargetWindowName); - - if (windowHandles.Count > 1) - { - throw new InvalidOperationException($"Target process has more than one window named `{TargetWindowName}`"); - } - - foreach (var hWnd in windowHandles) - { - IntPtr result; - bool success; - switch (message) - { - case WindowMessage.Close: - success = SendMessageTimeout(hWnd, (uint)message, IntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; - break; - case WindowMessage.QueryEndSession: - success = SendMessageTimeout(hWnd, (uint)message, IntPtr.Zero, (IntPtr)ENDSESSION_CLOSEAPP, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; - break; - case WindowMessage.EndSession: - success = SendMessageTimeout(hWnd, (uint)message, (IntPtr)TRUE, (IntPtr)ENDSESSION_CLOSEAPP, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; - break; - default: - throw new NotImplementedException("Unexpected window message"); - } - - return success; - } - - return false; - } - - /// - /// Retrieves an array of all available WinGet server instances. - /// - /// - /// An array of objects representing the available server instances. - /// The array will be empty if no instances are available. - /// - public static List GetInstances() - { - Process[] processes = Process.GetProcessesByName(ServerExecutableName); - List result = new List(); - - foreach (Process process in processes) - { - try - { - string? familyName = GetProcessPackageFamilyName(process); - if (familyName == DevelopmentPackageFamilyName) - { - result.Add(new WinGetServerInstance { Process = process }); - } - } - catch - { - // Ignore processes that we can't access or that aren't packaged - } - } - - return result; - } - - private static string? GetProcessPackageFamilyName(Process process) - { - const int ERROR_INSUFFICIENT_BUFFER = 122; - int length = 0; - int result = GetPackageFamilyName(process.Handle, ref length, null); - if (result == ERROR_INSUFFICIENT_BUFFER) - { - var sb = new System.Text.StringBuilder(length); - result = GetPackageFamilyName(process.Handle, ref length, sb); - if (result == 0) - { - return sb.ToString(); - } - } - return null; - } - - private List EnumerateWindowHandles(string windowName) - { - List windowHandles = new List(); - int processId = Process.Id; - - bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam) - { - GetWindowThreadProcessId(hWnd, out int windowProcessId); - if (windowProcessId == processId) - { - // Get the window title - var sb = new System.Text.StringBuilder(256); - int length = GetWindowText(hWnd, sb, sb.Capacity); - if (length > 0 && sb.ToString() == windowName) - { - windowHandles.Add(hWnd); - } - } - return true; - } - - EnumWindows(EnumWindowsProc, IntPtr.Zero); - return windowHandles; - } - - [DllImport("user32.dll")] - private static extern bool EnumWindows(EnumWindowsProcDelegate lpEnumFunc, IntPtr lParam); - - private delegate bool EnumWindowsProcDelegate(IntPtr hWnd, IntPtr lParam); - - [DllImport("user32.dll", SetLastError = true)] - private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); - - [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount); - - [DllImport("user32.dll", SetLastError = true)] - private static extern IntPtr SendMessageTimeout( - IntPtr hWnd, - uint Msg, - IntPtr wParam, - IntPtr lParam, - uint fuFlags, - uint uTimeout, - out IntPtr lpdwResult - ); - - [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] - private static extern int GetPackageFamilyName( - IntPtr hProcess, - ref int packageFamilyNameLength, - System.Text.StringBuilder? packageFamilyName - ); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetTestCommon +{ + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.Runtime.InteropServices; + + /// + /// Represents an instance of a Windows Package Manager (WinGet) server. + /// + public class WinGetServerInstance + { + /// + /// The name of the executable for the COM server. + /// + public const string ServerExecutableName = "WindowsPackageManagerServer"; + + /// + /// The package family name for the development package. + /// + public const string DevelopmentPackageFamilyName = "WinGetDevCLI_8wekyb3d8bbwe"; + + /// + /// The window name for the COM server message window. + /// + public const string TargetWindowName = "WingetMessageOnlyWindow"; + + /// + /// Gets the process for the server. + /// + public required Process Process { get; init; } + + /// + /// Gets a value indicating whether the current server has an associated window. + /// + public bool HasWindow + { + get + { + return EnumerateWindowHandles(TargetWindowName).Count > 0; + } + } + + /// + /// Sends a specified message to a window. + /// + /// The message to be sent to the window. + /// True to indicate that the message was sent and processed within the timeout; false otherwise. + public bool SendMessage(WindowMessage message) + { + const int TRUE = 0x1; + const int ENDSESSION_CLOSEAPP = 0x1; + const uint SMTO_ABORTIFHUNG = 0x0002; + const uint TIMEOUT_MS = 5000; + + var windowHandles = EnumerateWindowHandles(TargetWindowName); + + if (windowHandles.Count > 1) + { + throw new InvalidOperationException($"Target process has more than one window named `{TargetWindowName}`"); + } + + foreach (var hWnd in windowHandles) + { + IntPtr result; + bool success; + switch (message) + { + case WindowMessage.Close: + success = SendMessageTimeout(hWnd, (uint)message, IntPtr.Zero, IntPtr.Zero, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; + break; + case WindowMessage.QueryEndSession: + success = SendMessageTimeout(hWnd, (uint)message, IntPtr.Zero, (IntPtr)ENDSESSION_CLOSEAPP, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; + break; + case WindowMessage.EndSession: + success = SendMessageTimeout(hWnd, (uint)message, (IntPtr)TRUE, (IntPtr)ENDSESSION_CLOSEAPP, SMTO_ABORTIFHUNG, TIMEOUT_MS, out result) != IntPtr.Zero; + break; + default: + throw new NotImplementedException("Unexpected window message"); + } + + return success; + } + + return false; + } + + /// + /// Retrieves an array of all available WinGet server instances. + /// + /// + /// An array of objects representing the available server instances. + /// The array will be empty if no instances are available. + /// + public static List GetInstances() + { + Process[] processes = Process.GetProcessesByName(ServerExecutableName); + List result = new List(); + + foreach (Process process in processes) + { + try + { + string? familyName = GetProcessPackageFamilyName(process); + if (familyName == DevelopmentPackageFamilyName) + { + result.Add(new WinGetServerInstance { Process = process }); + } + } + catch + { + // Ignore processes that we can't access or that aren't packaged + } + } + + return result; + } + + private static string? GetProcessPackageFamilyName(Process process) + { + const int ERROR_INSUFFICIENT_BUFFER = 122; + int length = 0; + int result = GetPackageFamilyName(process.Handle, ref length, null); + if (result == ERROR_INSUFFICIENT_BUFFER) + { + var sb = new System.Text.StringBuilder(length); + result = GetPackageFamilyName(process.Handle, ref length, sb); + if (result == 0) + { + return sb.ToString(); + } + } + return null; + } + + private List EnumerateWindowHandles(string windowName) + { + List windowHandles = new List(); + int processId = Process.Id; + + bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam) + { + GetWindowThreadProcessId(hWnd, out int windowProcessId); + if (windowProcessId == processId) + { + // Get the window title + var sb = new System.Text.StringBuilder(256); + int length = GetWindowText(hWnd, sb, sb.Capacity); + if (length > 0 && sb.ToString() == windowName) + { + windowHandles.Add(hWnd); + } + } + return true; + } + + EnumWindows(EnumWindowsProc, IntPtr.Zero); + return windowHandles; + } + + [DllImport("user32.dll")] + private static extern bool EnumWindows(EnumWindowsProcDelegate lpEnumFunc, IntPtr lParam); + + private delegate bool EnumWindowsProcDelegate(IntPtr hWnd, IntPtr lParam); + + [DllImport("user32.dll", SetLastError = true)] + private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); + + [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern int GetWindowText(IntPtr hWnd, System.Text.StringBuilder lpString, int nMaxCount); + + [DllImport("user32.dll", SetLastError = true)] + private static extern IntPtr SendMessageTimeout( + IntPtr hWnd, + uint Msg, + IntPtr wParam, + IntPtr lParam, + uint fuFlags, + uint uTimeout, + out IntPtr lpdwResult + ); + + [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] + private static extern int GetPackageFamilyName( + IntPtr hProcess, + ref int packageFamilyNameLength, + System.Text.StringBuilder? packageFamilyName + ); + } +} diff --git a/src/WinGetTestCommon/WinGetTestCommon.csproj b/src/WinGetTestCommon/WinGetTestCommon.csproj index c28064fb5f..890f74acdb 100644 --- a/src/WinGetTestCommon/WinGetTestCommon.csproj +++ b/src/WinGetTestCommon/WinGetTestCommon.csproj @@ -1,10 +1,10 @@ - - - - net8.0-windows - $(SolutionDir)$(Platform)\$(Configuration)\WinGetTestCommon\ - x64;x86;arm64 - Library - enable - - + + + + net8.0-windows + $(SolutionDir)$(Platform)\$(Configuration)\WinGetTestCommon\ + x64;x86;arm64 + Library + enable + + diff --git a/src/WinGetTestCommon/WindowMessage.cs b/src/WinGetTestCommon/WindowMessage.cs index d3954366c5..5aee4f9bd0 100644 --- a/src/WinGetTestCommon/WindowMessage.cs +++ b/src/WinGetTestCommon/WindowMessage.cs @@ -1,29 +1,29 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetTestCommon -{ - /// - /// Represents the Windows messages that can be sent or received by a window. - /// - public enum WindowMessage - { - /// - /// WM_CLOSE - /// - Close = 0x0010, - - /// - /// WM_QUERYENDSESSION - /// - QueryEndSession = 0x0011, - - /// - /// WM_ENDSESSION - /// - EndSession = 0x0016, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetTestCommon +{ + /// + /// Represents the Windows messages that can be sent or received by a window. + /// + public enum WindowMessage + { + /// + /// WM_CLOSE + /// + Close = 0x0010, + + /// + /// WM_QUERYENDSESSION + /// + QueryEndSession = 0x0011, + + /// + /// WM_ENDSESSION + /// + EndSession = 0x0016, + } +} diff --git a/src/WinGetUtil/Exports.cpp b/src/WinGetUtil/Exports.cpp index ccd4cf383d..1330731ff8 100644 --- a/src/WinGetUtil/Exports.cpp +++ b/src/WinGetUtil/Exports.cpp @@ -1,667 +1,667 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" -#include "WinGetUtil.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller::Utility; -using namespace AppInstaller::Manifest; -using namespace AppInstaller::Repository; -using namespace AppInstaller::Repository::Metadata; -using namespace AppInstaller::Repository::Microsoft; - -namespace -{ - std::filesystem::path GetPathOrEmpty(WINGET_STRING potentiallyNullPath) - { - return potentiallyNullPath ? std::filesystem::path{ potentiallyNullPath } : std::filesystem::path{}; - } - - SQLiteIndex::Property GetSQLiteIndexProperty(WinGetSQLiteIndexProperty property) - { - switch (property) - { - case WinGetSQLiteIndexProperty_PackageUpdateTrackingBaseTime: return SQLiteIndex::Property::PackageUpdateTrackingBaseTime; - case WinGetSQLiteIndexProperty_IntermediateFileOutputPath: return SQLiteIndex::Property::IntermediateFileOutputPath; - } - - THROW_HR(E_INVALIDARG); - } -} - -extern "C" -{ - WINGET_UTIL_API WinGetLoggingInit(WINGET_STRING logPath) try - { - THROW_HR_IF(E_INVALIDARG, !logPath); - - thread_local AppInstaller::ThreadLocalStorage::WingetThreadGlobals threadGlobals; - thread_local std::once_flag initLogging; - - std::call_once(initLogging, []() { - std::unique_ptr previous = threadGlobals.SetForCurrentThread(); - // Intentionally release to leave the local ThreadGlobals. - previous.release(); - // Enable all logs for now. - AppInstaller::Logging::Log().SetEnabledChannels(AppInstaller::Logging::Channel::All); - AppInstaller::Logging::Log().SetLevel(AppInstaller::Logging::Level::Verbose); - AppInstaller::Logging::EnableWilFailureTelemetry(); - }); - - std::filesystem::path pathAsPath = logPath; - std::string loggerName = AppInstaller::Logging::FileLogger::GetNameForPath(pathAsPath); - - if (!AppInstaller::Logging::Log().ContainsLogger(loggerName)) - { - // Let FileLogger use default file prefix - AppInstaller::Logging::FileLogger::Add(pathAsPath); - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetLoggingTerm(WINGET_STRING logPath) try - { - if (logPath) - { - std::string loggerName = AppInstaller::Logging::FileLogger::GetNameForPath(logPath); - (void)AppInstaller::Logging::Log().RemoveLogger(loggerName); - } - else - { - AppInstaller::Logging::Log().RemoveAllLoggers(); - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexCreate(WINGET_STRING filePath, UINT32 majorVersion, UINT32 minorVersion, WINGET_SQLITE_INDEX_HANDLE* index) try - { - THROW_HR_IF(E_INVALIDARG, !filePath); - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !!*index); - - std::string filePathUtf8 = ConvertToUTF8(filePath); - AppInstaller::SQLite::Version internalVersion{ majorVersion, minorVersion }; - - std::unique_ptr result = std::make_unique(SQLiteIndex::CreateNew(filePathUtf8, internalVersion)); - - *index = static_cast(result.release()); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexOpen(WINGET_STRING filePath, WINGET_SQLITE_INDEX_HANDLE* index) try - { - THROW_HR_IF(E_INVALIDARG, !filePath); - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !!*index); - - std::string filePathUtf8 = ConvertToUTF8(filePath); - - std::unique_ptr result = std::make_unique(SQLiteIndex::Open(filePathUtf8, SQLiteIndex::OpenDisposition::ReadWrite)); - - *index = static_cast(result.release()); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexClose(WINGET_SQLITE_INDEX_HANDLE index) try - { - std::unique_ptr toClose(reinterpret_cast(index)); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexMigrate( - WINGET_SQLITE_INDEX_HANDLE index, - UINT32 majorVersion, - UINT32 minorVersion) try - { - THROW_HR_IF(E_INVALIDARG, !index); - - return reinterpret_cast(index)->MigrateTo({ majorVersion, minorVersion }) ? S_OK : HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); - } - CATCH_RETURN() - - - WINGET_UTIL_API WinGetSQLiteIndexSetProperty( - WINGET_SQLITE_INDEX_HANDLE index, - WinGetSQLiteIndexProperty property, - WINGET_STRING value) try - { - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !value); - - std::string valueUtf8 = ConvertToUTF8(value); - - reinterpret_cast(index)->SetProperty(GetSQLiteIndexProperty(property), valueUtf8); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexAddManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, WINGET_STRING relativePath) try - { - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !manifestPath); - THROW_HR_IF(E_INVALIDARG, !relativePath); - - reinterpret_cast(index)->AddManifest(manifestPath, relativePath); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexUpdateManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath, - BOOL* indexModified) try - { - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !manifestPath); - THROW_HR_IF(E_INVALIDARG, !relativePath); - - bool result = reinterpret_cast(index)->UpdateManifest(manifestPath, relativePath); - if (indexModified) - { - *indexModified = (result ? TRUE : FALSE); - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexAddOrUpdateManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath, - BOOL* indexModified) try - { - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !manifestPath); - THROW_HR_IF(E_INVALIDARG, !relativePath); - - bool result = reinterpret_cast(index)->AddOrUpdateManifest(manifestPath, relativePath); - if (indexModified) - { - *indexModified = (result ? TRUE : FALSE); - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexRemoveManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath) try - { - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !manifestPath); - THROW_HR_IF(E_INVALIDARG, !relativePath); - - reinterpret_cast(index)->RemoveManifest(manifestPath, relativePath); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexPrepareForPackaging( - WINGET_SQLITE_INDEX_HANDLE index) try - { - THROW_HR_IF(E_INVALIDARG, !index); - - reinterpret_cast(index)->PrepareForPackaging(); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetSQLiteIndexCheckConsistency( - WINGET_SQLITE_INDEX_HANDLE index, - BOOL* succeeded) try - { - THROW_HR_IF(E_INVALIDARG, !index); - THROW_HR_IF(E_INVALIDARG, !succeeded); - - auto sqliteIndex = reinterpret_cast(index); - bool result = sqliteIndex->CheckConsistency(true); - - *succeeded = (result ? TRUE : FALSE); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetValidateManifest( - WINGET_STRING manifestPath, - BOOL* succeeded, - WINGET_STRING_OUT* message) try - { - THROW_HR_IF(E_INVALIDARG, !manifestPath); - THROW_HR_IF(E_INVALIDARG, !succeeded); - - try - { - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.ThrowOnWarning = true; - - (void)YamlParser::CreateFromPath(manifestPath, validateOption); - - *succeeded = TRUE; - } - catch (const ManifestException& e) - { - *succeeded = e.IsWarningOnly(); - if (message) - { - *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); - } - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetValidateManifestV2( - WINGET_STRING inputPath, - BOOL* succeeded, - WINGET_STRING_OUT* message, - WINGET_STRING mergedManifestPath, - WinGetValidateManifestOption option) try - { - THROW_HR_IF(E_INVALIDARG, !inputPath); - THROW_HR_IF(E_INVALIDARG, !succeeded); - - try - { - ManifestValidateOption validateOption; - validateOption.FullValidation = true; - validateOption.ThrowOnWarning = true; - validateOption.SchemaValidationOnly = WI_IsFlagSet(option, WinGetValidateManifestOption::SchemaValidationOnly); - validateOption.ErrorOnVerifiedPublisherFields = WI_IsFlagSet(option, WinGetValidateManifestOption::ErrorOnVerifiedPublisherFields); - validateOption.InstallerValidation = WI_IsFlagSet(option, WinGetValidateManifestOption::InstallerValidations); - validateOption.ErrorOnNetworkAddressInSwitches = WI_IsFlagSet(option, WinGetValidateManifestOption::ErrorOnNetworkAddressInSwitches); - - (void)YamlParser::CreateFromPath(inputPath, validateOption, mergedManifestPath ? mergedManifestPath : L""); - - *succeeded = TRUE; - } - catch (const ManifestException& e) - { - *succeeded = e.IsWarningOnly(); - if (message) - { - *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); - } - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetCreateManifest( - WINGET_STRING inputPath, - BOOL* succeeded, - WINGET_MANIFEST_HANDLE* manifest, - WINGET_STRING_OUT* message, - WINGET_STRING mergedManifestPath, - WinGetCreateManifestOption option) try - { - THROW_HR_IF(E_INVALIDARG, !inputPath); - THROW_HR_IF(E_INVALIDARG, !succeeded); - THROW_HR_IF(E_INVALIDARG, !!*manifest); - // ErrorOnVerifiedPublisherFields can only be used with SchemaAndSemanticValidation - THROW_HR_IF(E_INVALIDARG, (WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnErrorOnVerifiedPublisherFields) && WI_IsFlagClear(option, WinGetCreateManifestOption::SchemaAndSemanticValidation))); - - *succeeded = false; - *manifest = nullptr; - - try - { - ManifestValidateOption validateOption; - - if (WI_IsFlagSet(option, WinGetCreateManifestOption::SchemaValidation) || WI_IsFlagSet(option, WinGetCreateManifestOption::SchemaAndSemanticValidation)) - { - validateOption.FullValidation = true; - validateOption.ThrowOnWarning = true; - validateOption.SchemaValidationOnly = WI_IsFlagClear(option, WinGetCreateManifestOption::SchemaAndSemanticValidation); - validateOption.ErrorOnVerifiedPublisherFields = WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnErrorOnVerifiedPublisherFields); - validateOption.ErrorOnNetworkAddressInSwitches = WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnErrorOnNetworkAddressInSwitches); - } - - if (WI_IsFlagSet(option, WinGetCreateManifestOption::AllowShadowManifest)) - { - validateOption.AllowShadowManifest = true; - } - - std::unique_ptr result = std::make_unique(YamlParser::CreateFromPath(inputPath, validateOption, mergedManifestPath ? mergedManifestPath : L"")); - - *manifest = static_cast(result.release()); - *succeeded = true; - } - catch (const ManifestException& e) - { - *succeeded = e.IsWarningOnly(); - if (*succeeded) - { - ManifestValidateOption validateOption; - if (WI_IsFlagSet(option, WinGetCreateManifestOption::AllowShadowManifest)) - { - validateOption.AllowShadowManifest = true; - } - - std::unique_ptr result = std::make_unique(YamlParser::CreateFromPath(inputPath, validateOption)); - *manifest = static_cast(result.release()); - } - if (message) - { - if (WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnResponseAsJson)) - { - *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorJson()).c_str()); - } - else - { - *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); - } - } - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetCloseManifest( - WINGET_MANIFEST_HANDLE manifest) try - { - THROW_HR_IF(E_INVALIDARG, !manifest); - - std::unique_ptr toClose{ reinterpret_cast(manifest) }; - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetValidateManifestV3( - WINGET_MANIFEST_HANDLE manifest, - WINGET_SQLITE_INDEX_HANDLE index, - WinGetValidateManifestResult* result, - WINGET_STRING_OUT* message, - WinGetValidateManifestOptionV2 option, - WinGetValidateManifestOperationType operationType) try - { - THROW_HR_IF(E_INVALIDARG, !manifest); - THROW_HR_IF(E_INVALIDARG, !result); - // Index should be provided if DependenciesValidation or ArpVersionValidation is to be performed - THROW_HR_IF(E_INVALIDARG, !index && (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::DependenciesValidation) || WI_IsFlagSet(option, WinGetValidateManifestOptionV2::ArpVersionValidation))); - THROW_HR_IF(E_INVALIDARG, option == WinGetValidateManifestOptionV2::None); - - *result = WinGetValidateManifestResult::InternalError; - - std::string validationMessage; - auto validationResult = WinGetValidateManifestResult::Success; - - Manifest* manifestPtr = reinterpret_cast(manifest); - SQLiteIndex* sqliteIndex = reinterpret_cast(index); - - if (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::DependenciesValidation)) - { - try - { - if (operationType == WinGetValidateManifestOperationType::OperationTypeDelete) - { - PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(sqliteIndex, *manifestPtr); - } - else - { - PackageDependenciesValidation::ValidateManifestDependencies(sqliteIndex, *manifestPtr); - } - } - catch (const ManifestException& e) - { - if (!e.IsWarningOnly()) - { - validationResult |= WinGetValidateManifestResult::DependenciesValidationFailure; - } - - validationResult |= static_cast( AppInstaller::Manifest::GetDependenciesValidationResultFromException(e) ); - - if (message) - { - validationMessage += e.GetManifestErrorMessage(); - } - } - } - - if (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::ArpVersionValidation)) - { - try - { - ValidateManifestArpVersion(sqliteIndex, *manifestPtr); - } - catch (const ManifestException& e) - { - WI_SetFlagIf(validationResult, WinGetValidateManifestResult::ArpVersionValidationFailure, !e.IsWarningOnly()); - if (message) - { - validationMessage += e.GetManifestErrorMessage(); - } - } - } - - if (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::InstallerValidation)) - { - try - { - auto errors = ValidateManifestInstallers(*manifestPtr); - if (errors.size() > 0) - { - // Throw the errors as ManifestExceptions to get processed errors and message. - THROW_EXCEPTION(ManifestException({ std::move(errors) })); - } - } - catch (const ManifestException& e) - { - WI_SetFlagIf(validationResult, WinGetValidateManifestResult::InstallerValidationFailure, !e.IsWarningOnly()); - if (message) - { - validationMessage += e.GetManifestErrorMessage(); - } - } - } - - *result = validationResult; - if (message) - { - *message = ::SysAllocString(ConvertToUTF16(validationMessage).c_str()); - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetValidateManifestDependencies( - WINGET_STRING inputPath, - BOOL* succeeded, - WINGET_STRING_OUT* message, - WINGET_SQLITE_INDEX_HANDLE index, - WinGetValidateManifestDependenciesOption validationOption) try - { - THROW_HR_IF(E_INVALIDARG, !inputPath); - THROW_HR_IF(E_INVALIDARG, !succeeded); - - try - { - Manifest manifest = YamlParser::CreateFromPath(inputPath); - SQLiteIndex* sqliteIndex(reinterpret_cast(index)); - - switch (validationOption) - { - case WinGetValidateManifestDependenciesOption::DefaultValidation: - PackageDependenciesValidation::ValidateManifestDependencies(sqliteIndex, manifest); - break; - case WinGetValidateManifestDependenciesOption::ForDelete: - PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(sqliteIndex, manifest); - break; - default: - THROW_HR(E_INVALIDARG); - } - - *succeeded = TRUE; - } - catch (const ManifestException& e) - { - *succeeded = e.IsWarningOnly(); - if (message) - { - *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); - } - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetDownload( - WINGET_STRING url, - WINGET_STRING filePath, - BYTE* sha256Hash, - UINT32 sha256HashLength) try - { - THROW_HR_IF(E_INVALIDARG, !url); - THROW_HR_IF(E_INVALIDARG, !filePath); - - bool computeHash = sha256Hash != nullptr && sha256HashLength != 0; - THROW_HR_IF(E_INVALIDARG, !computeHash && (sha256Hash != nullptr || sha256HashLength != 0)); - THROW_HR_IF(E_INVALIDARG, computeHash && sha256HashLength != 32); - - AppInstaller::ProgressCallback callback; - auto downloadResult = Download(ConvertToUTF8(url), filePath, DownloadType::WinGetUtil, callback); - - // At this point, if computeHash is set we have verified that the buffer is valid and 32 bytes. - if (computeHash) - { - const auto& hash = downloadResult.Sha256Hash; - - // The SHA 256 hash length should always be 32 bytes. - THROW_HR_IF(E_UNEXPECTED, hash.size() != sha256HashLength); - std::copy(hash.begin(), hash.end(), sha256Hash); - } - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetCompareVersions( - WINGET_STRING versionA, - WINGET_STRING versionB, - INT* comparisonResult) try - { - THROW_HR_IF(E_INVALIDARG, !versionA); - THROW_HR_IF(E_INVALIDARG, !versionB); - - Version vA{ ConvertToUTF8(versionA) }; - Version vB{ ConvertToUTF8(versionB) }; - - *comparisonResult = vA < vB ? -1 : (vA == vB ? 0 : 1); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetBeginInstallerMetadataCollection( - WINGET_STRING inputJSON, - WINGET_STRING logFilePath, - WinGetBeginInstallerMetadataCollectionOptions options, - WINGET_INSTALLER_METADATA_COLLECTION_HANDLE* collectionHandle) try - { - THROW_HR_IF(E_INVALIDARG, !inputJSON); - THROW_HR_IF(E_INVALIDARG, !collectionHandle); - THROW_HR_IF(E_INVALIDARG, !!*collectionHandle); - // Flags specifying what inputJSON means are mutually exclusive - THROW_HR_IF(E_INVALIDARG, !WI_IsClearOrSingleFlagSetInMask(options, - WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath | WinGetBeginInstallerMetadataCollectionOption_InputIsURI)); - - std::unique_ptr result; - - if (WI_IsFlagSet(options, WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath)) - { - result = InstallerMetadataCollectionContext::FromFile(inputJSON, GetPathOrEmpty(logFilePath)); - } - else if (WI_IsFlagSet(options, WinGetBeginInstallerMetadataCollectionOption_InputIsURI)) - { - result = InstallerMetadataCollectionContext::FromURI(inputJSON, GetPathOrEmpty(logFilePath)); - } - else - { - result = InstallerMetadataCollectionContext::FromJSON(inputJSON, GetPathOrEmpty(logFilePath)); - } - - *collectionHandle = static_cast(result.release()); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetCompleteInstallerMetadataCollection( - WINGET_INSTALLER_METADATA_COLLECTION_HANDLE collectionHandle, - WINGET_STRING outputFilePath, - WinGetCompleteInstallerMetadataCollectionOptions options) try - { - THROW_HR_IF(E_INVALIDARG, !collectionHandle); - - // Since we always free the handle from calling this function, we can just store it in a unique_ptr from the start - std::unique_ptr context{ reinterpret_cast(collectionHandle) }; - - if (WI_IsFlagSet(options, WinGetCompleteInstallerMetadataCollectionOption_Abandon)) - { - return S_OK; - } - - THROW_HR_IF(E_INVALIDARG, !outputFilePath); - - context->Complete(outputFilePath); - - return S_OK; - } - CATCH_RETURN() - - WINGET_UTIL_API WinGetMergeInstallerMetadata( - WINGET_STRING inputJSON, - WINGET_STRING_OUT* outputJSON, - UINT32 maximumOutputSizeInBytes, - WINGET_STRING logFilePath, - WinGetMergeInstallerMetadataOptions) try - { - THROW_HR_IF(E_INVALIDARG, !inputJSON); - THROW_HR_IF(E_INVALIDARG, !outputJSON); - - std::wstring merged = InstallerMetadataCollectionContext::Merge(inputJSON, maximumOutputSizeInBytes, GetPathOrEmpty(logFilePath)); - *outputJSON = ::SysAllocString(merged.c_str()); - - return S_OK; - } - CATCH_RETURN() -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" +#include "WinGetUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller::Utility; +using namespace AppInstaller::Manifest; +using namespace AppInstaller::Repository; +using namespace AppInstaller::Repository::Metadata; +using namespace AppInstaller::Repository::Microsoft; + +namespace +{ + std::filesystem::path GetPathOrEmpty(WINGET_STRING potentiallyNullPath) + { + return potentiallyNullPath ? std::filesystem::path{ potentiallyNullPath } : std::filesystem::path{}; + } + + SQLiteIndex::Property GetSQLiteIndexProperty(WinGetSQLiteIndexProperty property) + { + switch (property) + { + case WinGetSQLiteIndexProperty_PackageUpdateTrackingBaseTime: return SQLiteIndex::Property::PackageUpdateTrackingBaseTime; + case WinGetSQLiteIndexProperty_IntermediateFileOutputPath: return SQLiteIndex::Property::IntermediateFileOutputPath; + } + + THROW_HR(E_INVALIDARG); + } +} + +extern "C" +{ + WINGET_UTIL_API WinGetLoggingInit(WINGET_STRING logPath) try + { + THROW_HR_IF(E_INVALIDARG, !logPath); + + thread_local AppInstaller::ThreadLocalStorage::WingetThreadGlobals threadGlobals; + thread_local std::once_flag initLogging; + + std::call_once(initLogging, []() { + std::unique_ptr previous = threadGlobals.SetForCurrentThread(); + // Intentionally release to leave the local ThreadGlobals. + previous.release(); + // Enable all logs for now. + AppInstaller::Logging::Log().SetEnabledChannels(AppInstaller::Logging::Channel::All); + AppInstaller::Logging::Log().SetLevel(AppInstaller::Logging::Level::Verbose); + AppInstaller::Logging::EnableWilFailureTelemetry(); + }); + + std::filesystem::path pathAsPath = logPath; + std::string loggerName = AppInstaller::Logging::FileLogger::GetNameForPath(pathAsPath); + + if (!AppInstaller::Logging::Log().ContainsLogger(loggerName)) + { + // Let FileLogger use default file prefix + AppInstaller::Logging::FileLogger::Add(pathAsPath); + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetLoggingTerm(WINGET_STRING logPath) try + { + if (logPath) + { + std::string loggerName = AppInstaller::Logging::FileLogger::GetNameForPath(logPath); + (void)AppInstaller::Logging::Log().RemoveLogger(loggerName); + } + else + { + AppInstaller::Logging::Log().RemoveAllLoggers(); + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexCreate(WINGET_STRING filePath, UINT32 majorVersion, UINT32 minorVersion, WINGET_SQLITE_INDEX_HANDLE* index) try + { + THROW_HR_IF(E_INVALIDARG, !filePath); + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !!*index); + + std::string filePathUtf8 = ConvertToUTF8(filePath); + AppInstaller::SQLite::Version internalVersion{ majorVersion, minorVersion }; + + std::unique_ptr result = std::make_unique(SQLiteIndex::CreateNew(filePathUtf8, internalVersion)); + + *index = static_cast(result.release()); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexOpen(WINGET_STRING filePath, WINGET_SQLITE_INDEX_HANDLE* index) try + { + THROW_HR_IF(E_INVALIDARG, !filePath); + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !!*index); + + std::string filePathUtf8 = ConvertToUTF8(filePath); + + std::unique_ptr result = std::make_unique(SQLiteIndex::Open(filePathUtf8, SQLiteIndex::OpenDisposition::ReadWrite)); + + *index = static_cast(result.release()); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexClose(WINGET_SQLITE_INDEX_HANDLE index) try + { + std::unique_ptr toClose(reinterpret_cast(index)); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexMigrate( + WINGET_SQLITE_INDEX_HANDLE index, + UINT32 majorVersion, + UINT32 minorVersion) try + { + THROW_HR_IF(E_INVALIDARG, !index); + + return reinterpret_cast(index)->MigrateTo({ majorVersion, minorVersion }) ? S_OK : HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); + } + CATCH_RETURN() + + + WINGET_UTIL_API WinGetSQLiteIndexSetProperty( + WINGET_SQLITE_INDEX_HANDLE index, + WinGetSQLiteIndexProperty property, + WINGET_STRING value) try + { + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !value); + + std::string valueUtf8 = ConvertToUTF8(value); + + reinterpret_cast(index)->SetProperty(GetSQLiteIndexProperty(property), valueUtf8); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexAddManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, WINGET_STRING relativePath) try + { + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !manifestPath); + THROW_HR_IF(E_INVALIDARG, !relativePath); + + reinterpret_cast(index)->AddManifest(manifestPath, relativePath); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexUpdateManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath, + BOOL* indexModified) try + { + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !manifestPath); + THROW_HR_IF(E_INVALIDARG, !relativePath); + + bool result = reinterpret_cast(index)->UpdateManifest(manifestPath, relativePath); + if (indexModified) + { + *indexModified = (result ? TRUE : FALSE); + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexAddOrUpdateManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath, + BOOL* indexModified) try + { + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !manifestPath); + THROW_HR_IF(E_INVALIDARG, !relativePath); + + bool result = reinterpret_cast(index)->AddOrUpdateManifest(manifestPath, relativePath); + if (indexModified) + { + *indexModified = (result ? TRUE : FALSE); + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexRemoveManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath) try + { + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !manifestPath); + THROW_HR_IF(E_INVALIDARG, !relativePath); + + reinterpret_cast(index)->RemoveManifest(manifestPath, relativePath); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexPrepareForPackaging( + WINGET_SQLITE_INDEX_HANDLE index) try + { + THROW_HR_IF(E_INVALIDARG, !index); + + reinterpret_cast(index)->PrepareForPackaging(); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetSQLiteIndexCheckConsistency( + WINGET_SQLITE_INDEX_HANDLE index, + BOOL* succeeded) try + { + THROW_HR_IF(E_INVALIDARG, !index); + THROW_HR_IF(E_INVALIDARG, !succeeded); + + auto sqliteIndex = reinterpret_cast(index); + bool result = sqliteIndex->CheckConsistency(true); + + *succeeded = (result ? TRUE : FALSE); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetValidateManifest( + WINGET_STRING manifestPath, + BOOL* succeeded, + WINGET_STRING_OUT* message) try + { + THROW_HR_IF(E_INVALIDARG, !manifestPath); + THROW_HR_IF(E_INVALIDARG, !succeeded); + + try + { + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + + (void)YamlParser::CreateFromPath(manifestPath, validateOption); + + *succeeded = TRUE; + } + catch (const ManifestException& e) + { + *succeeded = e.IsWarningOnly(); + if (message) + { + *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); + } + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetValidateManifestV2( + WINGET_STRING inputPath, + BOOL* succeeded, + WINGET_STRING_OUT* message, + WINGET_STRING mergedManifestPath, + WinGetValidateManifestOption option) try + { + THROW_HR_IF(E_INVALIDARG, !inputPath); + THROW_HR_IF(E_INVALIDARG, !succeeded); + + try + { + ManifestValidateOption validateOption; + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + validateOption.SchemaValidationOnly = WI_IsFlagSet(option, WinGetValidateManifestOption::SchemaValidationOnly); + validateOption.ErrorOnVerifiedPublisherFields = WI_IsFlagSet(option, WinGetValidateManifestOption::ErrorOnVerifiedPublisherFields); + validateOption.InstallerValidation = WI_IsFlagSet(option, WinGetValidateManifestOption::InstallerValidations); + validateOption.ErrorOnNetworkAddressInSwitches = WI_IsFlagSet(option, WinGetValidateManifestOption::ErrorOnNetworkAddressInSwitches); + + (void)YamlParser::CreateFromPath(inputPath, validateOption, mergedManifestPath ? mergedManifestPath : L""); + + *succeeded = TRUE; + } + catch (const ManifestException& e) + { + *succeeded = e.IsWarningOnly(); + if (message) + { + *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); + } + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetCreateManifest( + WINGET_STRING inputPath, + BOOL* succeeded, + WINGET_MANIFEST_HANDLE* manifest, + WINGET_STRING_OUT* message, + WINGET_STRING mergedManifestPath, + WinGetCreateManifestOption option) try + { + THROW_HR_IF(E_INVALIDARG, !inputPath); + THROW_HR_IF(E_INVALIDARG, !succeeded); + THROW_HR_IF(E_INVALIDARG, !!*manifest); + // ErrorOnVerifiedPublisherFields can only be used with SchemaAndSemanticValidation + THROW_HR_IF(E_INVALIDARG, (WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnErrorOnVerifiedPublisherFields) && WI_IsFlagClear(option, WinGetCreateManifestOption::SchemaAndSemanticValidation))); + + *succeeded = false; + *manifest = nullptr; + + try + { + ManifestValidateOption validateOption; + + if (WI_IsFlagSet(option, WinGetCreateManifestOption::SchemaValidation) || WI_IsFlagSet(option, WinGetCreateManifestOption::SchemaAndSemanticValidation)) + { + validateOption.FullValidation = true; + validateOption.ThrowOnWarning = true; + validateOption.SchemaValidationOnly = WI_IsFlagClear(option, WinGetCreateManifestOption::SchemaAndSemanticValidation); + validateOption.ErrorOnVerifiedPublisherFields = WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnErrorOnVerifiedPublisherFields); + validateOption.ErrorOnNetworkAddressInSwitches = WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnErrorOnNetworkAddressInSwitches); + } + + if (WI_IsFlagSet(option, WinGetCreateManifestOption::AllowShadowManifest)) + { + validateOption.AllowShadowManifest = true; + } + + std::unique_ptr result = std::make_unique(YamlParser::CreateFromPath(inputPath, validateOption, mergedManifestPath ? mergedManifestPath : L"")); + + *manifest = static_cast(result.release()); + *succeeded = true; + } + catch (const ManifestException& e) + { + *succeeded = e.IsWarningOnly(); + if (*succeeded) + { + ManifestValidateOption validateOption; + if (WI_IsFlagSet(option, WinGetCreateManifestOption::AllowShadowManifest)) + { + validateOption.AllowShadowManifest = true; + } + + std::unique_ptr result = std::make_unique(YamlParser::CreateFromPath(inputPath, validateOption)); + *manifest = static_cast(result.release()); + } + if (message) + { + if (WI_IsFlagSet(option, WinGetCreateManifestOption::ReturnResponseAsJson)) + { + *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorJson()).c_str()); + } + else + { + *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); + } + } + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetCloseManifest( + WINGET_MANIFEST_HANDLE manifest) try + { + THROW_HR_IF(E_INVALIDARG, !manifest); + + std::unique_ptr toClose{ reinterpret_cast(manifest) }; + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetValidateManifestV3( + WINGET_MANIFEST_HANDLE manifest, + WINGET_SQLITE_INDEX_HANDLE index, + WinGetValidateManifestResult* result, + WINGET_STRING_OUT* message, + WinGetValidateManifestOptionV2 option, + WinGetValidateManifestOperationType operationType) try + { + THROW_HR_IF(E_INVALIDARG, !manifest); + THROW_HR_IF(E_INVALIDARG, !result); + // Index should be provided if DependenciesValidation or ArpVersionValidation is to be performed + THROW_HR_IF(E_INVALIDARG, !index && (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::DependenciesValidation) || WI_IsFlagSet(option, WinGetValidateManifestOptionV2::ArpVersionValidation))); + THROW_HR_IF(E_INVALIDARG, option == WinGetValidateManifestOptionV2::None); + + *result = WinGetValidateManifestResult::InternalError; + + std::string validationMessage; + auto validationResult = WinGetValidateManifestResult::Success; + + Manifest* manifestPtr = reinterpret_cast(manifest); + SQLiteIndex* sqliteIndex = reinterpret_cast(index); + + if (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::DependenciesValidation)) + { + try + { + if (operationType == WinGetValidateManifestOperationType::OperationTypeDelete) + { + PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(sqliteIndex, *manifestPtr); + } + else + { + PackageDependenciesValidation::ValidateManifestDependencies(sqliteIndex, *manifestPtr); + } + } + catch (const ManifestException& e) + { + if (!e.IsWarningOnly()) + { + validationResult |= WinGetValidateManifestResult::DependenciesValidationFailure; + } + + validationResult |= static_cast( AppInstaller::Manifest::GetDependenciesValidationResultFromException(e) ); + + if (message) + { + validationMessage += e.GetManifestErrorMessage(); + } + } + } + + if (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::ArpVersionValidation)) + { + try + { + ValidateManifestArpVersion(sqliteIndex, *manifestPtr); + } + catch (const ManifestException& e) + { + WI_SetFlagIf(validationResult, WinGetValidateManifestResult::ArpVersionValidationFailure, !e.IsWarningOnly()); + if (message) + { + validationMessage += e.GetManifestErrorMessage(); + } + } + } + + if (WI_IsFlagSet(option, WinGetValidateManifestOptionV2::InstallerValidation)) + { + try + { + auto errors = ValidateManifestInstallers(*manifestPtr); + if (errors.size() > 0) + { + // Throw the errors as ManifestExceptions to get processed errors and message. + THROW_EXCEPTION(ManifestException({ std::move(errors) })); + } + } + catch (const ManifestException& e) + { + WI_SetFlagIf(validationResult, WinGetValidateManifestResult::InstallerValidationFailure, !e.IsWarningOnly()); + if (message) + { + validationMessage += e.GetManifestErrorMessage(); + } + } + } + + *result = validationResult; + if (message) + { + *message = ::SysAllocString(ConvertToUTF16(validationMessage).c_str()); + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetValidateManifestDependencies( + WINGET_STRING inputPath, + BOOL* succeeded, + WINGET_STRING_OUT* message, + WINGET_SQLITE_INDEX_HANDLE index, + WinGetValidateManifestDependenciesOption validationOption) try + { + THROW_HR_IF(E_INVALIDARG, !inputPath); + THROW_HR_IF(E_INVALIDARG, !succeeded); + + try + { + Manifest manifest = YamlParser::CreateFromPath(inputPath); + SQLiteIndex* sqliteIndex(reinterpret_cast(index)); + + switch (validationOption) + { + case WinGetValidateManifestDependenciesOption::DefaultValidation: + PackageDependenciesValidation::ValidateManifestDependencies(sqliteIndex, manifest); + break; + case WinGetValidateManifestDependenciesOption::ForDelete: + PackageDependenciesValidation::VerifyDependenciesStructureForManifestDelete(sqliteIndex, manifest); + break; + default: + THROW_HR(E_INVALIDARG); + } + + *succeeded = TRUE; + } + catch (const ManifestException& e) + { + *succeeded = e.IsWarningOnly(); + if (message) + { + *message = ::SysAllocString(ConvertToUTF16(e.GetManifestErrorMessage()).c_str()); + } + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetDownload( + WINGET_STRING url, + WINGET_STRING filePath, + BYTE* sha256Hash, + UINT32 sha256HashLength) try + { + THROW_HR_IF(E_INVALIDARG, !url); + THROW_HR_IF(E_INVALIDARG, !filePath); + + bool computeHash = sha256Hash != nullptr && sha256HashLength != 0; + THROW_HR_IF(E_INVALIDARG, !computeHash && (sha256Hash != nullptr || sha256HashLength != 0)); + THROW_HR_IF(E_INVALIDARG, computeHash && sha256HashLength != 32); + + AppInstaller::ProgressCallback callback; + auto downloadResult = Download(ConvertToUTF8(url), filePath, DownloadType::WinGetUtil, callback); + + // At this point, if computeHash is set we have verified that the buffer is valid and 32 bytes. + if (computeHash) + { + const auto& hash = downloadResult.Sha256Hash; + + // The SHA 256 hash length should always be 32 bytes. + THROW_HR_IF(E_UNEXPECTED, hash.size() != sha256HashLength); + std::copy(hash.begin(), hash.end(), sha256Hash); + } + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetCompareVersions( + WINGET_STRING versionA, + WINGET_STRING versionB, + INT* comparisonResult) try + { + THROW_HR_IF(E_INVALIDARG, !versionA); + THROW_HR_IF(E_INVALIDARG, !versionB); + + Version vA{ ConvertToUTF8(versionA) }; + Version vB{ ConvertToUTF8(versionB) }; + + *comparisonResult = vA < vB ? -1 : (vA == vB ? 0 : 1); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetBeginInstallerMetadataCollection( + WINGET_STRING inputJSON, + WINGET_STRING logFilePath, + WinGetBeginInstallerMetadataCollectionOptions options, + WINGET_INSTALLER_METADATA_COLLECTION_HANDLE* collectionHandle) try + { + THROW_HR_IF(E_INVALIDARG, !inputJSON); + THROW_HR_IF(E_INVALIDARG, !collectionHandle); + THROW_HR_IF(E_INVALIDARG, !!*collectionHandle); + // Flags specifying what inputJSON means are mutually exclusive + THROW_HR_IF(E_INVALIDARG, !WI_IsClearOrSingleFlagSetInMask(options, + WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath | WinGetBeginInstallerMetadataCollectionOption_InputIsURI)); + + std::unique_ptr result; + + if (WI_IsFlagSet(options, WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath)) + { + result = InstallerMetadataCollectionContext::FromFile(inputJSON, GetPathOrEmpty(logFilePath)); + } + else if (WI_IsFlagSet(options, WinGetBeginInstallerMetadataCollectionOption_InputIsURI)) + { + result = InstallerMetadataCollectionContext::FromURI(inputJSON, GetPathOrEmpty(logFilePath)); + } + else + { + result = InstallerMetadataCollectionContext::FromJSON(inputJSON, GetPathOrEmpty(logFilePath)); + } + + *collectionHandle = static_cast(result.release()); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetCompleteInstallerMetadataCollection( + WINGET_INSTALLER_METADATA_COLLECTION_HANDLE collectionHandle, + WINGET_STRING outputFilePath, + WinGetCompleteInstallerMetadataCollectionOptions options) try + { + THROW_HR_IF(E_INVALIDARG, !collectionHandle); + + // Since we always free the handle from calling this function, we can just store it in a unique_ptr from the start + std::unique_ptr context{ reinterpret_cast(collectionHandle) }; + + if (WI_IsFlagSet(options, WinGetCompleteInstallerMetadataCollectionOption_Abandon)) + { + return S_OK; + } + + THROW_HR_IF(E_INVALIDARG, !outputFilePath); + + context->Complete(outputFilePath); + + return S_OK; + } + CATCH_RETURN() + + WINGET_UTIL_API WinGetMergeInstallerMetadata( + WINGET_STRING inputJSON, + WINGET_STRING_OUT* outputJSON, + UINT32 maximumOutputSizeInBytes, + WINGET_STRING logFilePath, + WinGetMergeInstallerMetadataOptions) try + { + THROW_HR_IF(E_INVALIDARG, !inputJSON); + THROW_HR_IF(E_INVALIDARG, !outputJSON); + + std::wstring merged = InstallerMetadataCollectionContext::Merge(inputJSON, maximumOutputSizeInBytes, GetPathOrEmpty(logFilePath)); + *outputJSON = ::SysAllocString(merged.c_str()); + + return S_OK; + } + CATCH_RETURN() +} diff --git a/src/WinGetUtil/Source.def b/src/WinGetUtil/Source.def index 2ec0f6637d..d1fff42994 100644 --- a/src/WinGetUtil/Source.def +++ b/src/WinGetUtil/Source.def @@ -1,26 +1,26 @@ -LIBRARY WinGetUtil -EXPORTS - WinGetLoggingInit - WinGetLoggingTerm - WinGetSQLiteIndexCreate - WinGetSQLiteIndexOpen - WinGetSQLiteIndexClose - WinGetSQLiteIndexAddManifest - WinGetSQLiteIndexUpdateManifest - WinGetSQLiteIndexAddOrUpdateManifest - WinGetSQLiteIndexRemoveManifest - WinGetSQLiteIndexPrepareForPackaging - WinGetSQLiteIndexCheckConsistency - WinGetValidateManifest - WinGetDownload - WinGetCompareVersions - WinGetValidateManifestV2 - WinGetValidateManifestDependencies - WinGetCreateManifest - WinGetCloseManifest - WinGetValidateManifestV3 - WinGetBeginInstallerMetadataCollection - WinGetCompleteInstallerMetadataCollection - WinGetMergeInstallerMetadata - WinGetSQLiteIndexMigrate - WinGetSQLiteIndexSetProperty +LIBRARY WinGetUtil +EXPORTS + WinGetLoggingInit + WinGetLoggingTerm + WinGetSQLiteIndexCreate + WinGetSQLiteIndexOpen + WinGetSQLiteIndexClose + WinGetSQLiteIndexAddManifest + WinGetSQLiteIndexUpdateManifest + WinGetSQLiteIndexAddOrUpdateManifest + WinGetSQLiteIndexRemoveManifest + WinGetSQLiteIndexPrepareForPackaging + WinGetSQLiteIndexCheckConsistency + WinGetValidateManifest + WinGetDownload + WinGetCompareVersions + WinGetValidateManifestV2 + WinGetValidateManifestDependencies + WinGetCreateManifest + WinGetCloseManifest + WinGetValidateManifestV3 + WinGetBeginInstallerMetadataCollection + WinGetCompleteInstallerMetadataCollection + WinGetMergeInstallerMetadata + WinGetSQLiteIndexMigrate + WinGetSQLiteIndexSetProperty diff --git a/src/WinGetUtil/WinGetUtil.h b/src/WinGetUtil/WinGetUtil.h index 92ace69421..a089d600ef 100644 --- a/src/WinGetUtil/WinGetUtil.h +++ b/src/WinGetUtil/WinGetUtil.h @@ -1,318 +1,318 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -extern "C" -{ - // A handle to the index. - typedef void* WINGET_SQLITE_INDEX_HANDLE; - - // A handle to the manifest. - typedef void* WINGET_MANIFEST_HANDLE; - - // A string taken in by the utility; in UTF16. - typedef wchar_t const* const WINGET_STRING; - - // A string returned by the utility; in UTF16. - typedef BSTR WINGET_STRING_OUT; - -#define WINGET_UTIL_API HRESULT __stdcall - -#define WINGET_SQLITE_INDEX_VERSION_LATEST ((UINT32)-1) - - enum WinGetValidateManifestOption - { - Default = 0, - SchemaValidationOnly = 0x1, - ErrorOnVerifiedPublisherFields = 0x2, - InstallerValidations = 0x4, - ErrorOnNetworkAddressInSwitches = 0x8, - }; - - DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestOption); - - enum WinGetCreateManifestOption - { - // Just create the manifest without any validation - NoValidation = 0, - // Only validate against json schema - SchemaValidation = 0x1, - // Validate against schema and also perform semantic validation - SchemaAndSemanticValidation = 0x2, - // Use shadow manifest - AllowShadowManifest = 0x4, - - /// Below options are additional validation behaviors if needed - - // Return error on manifest fields that require verified publishers, used during semantic validation - ReturnErrorOnVerifiedPublisherFields = 0x1000, - - // Return error if a network address is present in installer switches. - ReturnErrorOnNetworkAddressInSwitches = 0x2000, - - // Return the failure or warning message as a JSON string containing both the full message - // and a structured list of individual errors/warnings. - ReturnResponseAsJson = 0x4000, - }; - - DEFINE_ENUM_FLAG_OPERATORS(WinGetCreateManifestOption); - - enum WinGetValidateManifestOptionV2 - { - // No validation, caller will get E_INVALIDARG - None = 0, - // Dependencies validation against index - DependenciesValidation = 0x1, - // Arp version validation against index - ArpVersionValidation = 0x2, - // Installer validation - InstallerValidation = 0x4, - }; - - DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestOptionV2); - - enum WinGetValidateManifestOperationType - { - OperationTypeAdd = 0, - OperationTypeUpdate = 1, - OperationTypeDelete = 2, - }; - - enum WinGetValidateManifestResult - { - Success = 0, - - // Each validation step should have an enum for corresponding failure. - DependenciesValidationFailure = 0x1, - ArpVersionValidationFailure = 0x2, - InstallerValidationFailure = 0x4, - - // Dependencies validation result. - SingleManifestPackageHasDependencies = 0x10000, - MultiManifestPackageHasDependencies = 0x20000, - MissingManifestDependenciesNode = 0x40000, - NoSuitableMinVersionDependency = 0x80000, - FoundDependencyLoop = 0x100000, - - // Internal error meaning validation does not complete as desired. - InternalError = 0x1000, - }; - - DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestResult); - - enum WinGetValidateManifestDependenciesOption - { - DefaultValidation = 0, - ForDelete = 0x1, - }; - - DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestDependenciesOption); - - // Initializes the logging infrastructure. - WINGET_UTIL_API WinGetLoggingInit( - WINGET_STRING logPath); - - // Removes the given log file from the logging infrastructure. - // If logPath is nullptr, then remove all loggers. - WINGET_UTIL_API WinGetLoggingTerm( - WINGET_STRING logPath); - - // Creates a new index file at filePath with the given version. - WINGET_UTIL_API WinGetSQLiteIndexCreate( - WINGET_STRING filePath, - UINT32 majorVersion, - UINT32 minorVersion, - WINGET_SQLITE_INDEX_HANDLE* index); - - // Opens an existing index at filePath. - WINGET_UTIL_API WinGetSQLiteIndexOpen( - WINGET_STRING filePath, - WINGET_SQLITE_INDEX_HANDLE* index); - - // Closes the index. - WINGET_UTIL_API WinGetSQLiteIndexClose( - WINGET_SQLITE_INDEX_HANDLE index); - - // Migrates the index to the new version specified. - WINGET_UTIL_API WinGetSQLiteIndexMigrate( - WINGET_SQLITE_INDEX_HANDLE index, - UINT32 majorVersion, - UINT32 minorVersion); - - enum WinGetSQLiteIndexProperty - { - WinGetSQLiteIndexProperty_PackageUpdateTrackingBaseTime = 0, - WinGetSQLiteIndexProperty_IntermediateFileOutputPath = 1, - }; - - // Sets the given property on the index. - WINGET_UTIL_API WinGetSQLiteIndexSetProperty( - WINGET_SQLITE_INDEX_HANDLE index, - WinGetSQLiteIndexProperty property, - WINGET_STRING value); - - // Adds the manifest at the repository relative path to the index. - // If the function succeeds, the manifest has been added. - WINGET_UTIL_API WinGetSQLiteIndexAddManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath); - - // Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the index was modified by the function. - WINGET_UTIL_API WinGetSQLiteIndexUpdateManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath, - BOOL* indexModified); - - // Adds or Updates the manifest with matching { Id, Version, Channel } in the index. - // The return value indicates whether the manifest was added (true) or updated (false). - WINGET_UTIL_API WinGetSQLiteIndexAddOrUpdateManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath, - BOOL* indexModified); - - // Removes the manifest with matching { Id, Version, Channel } from the index. - // Path is currently ignored. - WINGET_UTIL_API WinGetSQLiteIndexRemoveManifest( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath); - - // Removes data that is no longer needed for an index that is to be published. - WINGET_UTIL_API WinGetSQLiteIndexPrepareForPackaging( - WINGET_SQLITE_INDEX_HANDLE index); - - // Checks the index for consistency, ensuring that at a minimum all referenced rows actually exist. - WINGET_UTIL_API WinGetSQLiteIndexCheckConsistency( - WINGET_SQLITE_INDEX_HANDLE index, - BOOL* succeeded); - - // Validates a given manifest. Returns a bool for validation result and - // a string representing validation errors if validation failed. - WINGET_UTIL_API WinGetValidateManifest( - WINGET_STRING manifestPath, - BOOL* succeeded, - WINGET_STRING_OUT* message); - - // Validates a given manifest. Returns a bool for validation result and - // a string representing validation errors if validation failed. - // If mergedManifestPath is provided, this method will write a merged manifest - // to the location specified by mergedManifestPath - WINGET_UTIL_API WinGetValidateManifestV2( - WINGET_STRING inputPath, - BOOL* succeeded, - WINGET_STRING_OUT* message, - WINGET_STRING mergedManifestPath, - WinGetValidateManifestOption option); - - // Creates a given manifest with optional validation. Returns a bool for operation result and - // a string representing validation errors if validation failed. - // If mergedManifestPath is provided, this method will write a merged manifest - // to the location specified by mergedManifestPath - WINGET_UTIL_API WinGetCreateManifest( - WINGET_STRING inputPath, - BOOL* succeeded, - WINGET_MANIFEST_HANDLE* manifest, - WINGET_STRING_OUT* message, - WINGET_STRING mergedManifestPath, - WinGetCreateManifestOption option); - - // Closes a given manifest. - WINGET_UTIL_API WinGetCloseManifest( - WINGET_MANIFEST_HANDLE manifest); - - // Validates a given manifest. Returns WinGetValidateManifestResult for validation result and - // a string representing validation errors if validation failed. - // If result is 0, it is success. Otherwise, caller can check the result with flags to see - // which phases failed. - WINGET_UTIL_API WinGetValidateManifestV3( - WINGET_MANIFEST_HANDLE manifest, - WINGET_SQLITE_INDEX_HANDLE index, - WinGetValidateManifestResult* result, - WINGET_STRING_OUT* message, - WinGetValidateManifestOptionV2 option, - WinGetValidateManifestOperationType operationType); - - // Validates a given manifest with dependencies. Returns a bool for validation result and - // a string representing validation errors if validation failed. - // If mergedManifestPath is provided, this method will write a merged manifest - // to the location specified by mergedManifestPath - WINGET_UTIL_API WinGetValidateManifestDependencies( - WINGET_STRING inputPath, - BOOL* succeeded, - WINGET_STRING_OUT* message, - WINGET_SQLITE_INDEX_HANDLE index, - WinGetValidateManifestDependenciesOption dependenciesValidationOption); - - // Downloads a file to the given path, returning the SHA 256 hash of the file. - WINGET_UTIL_API WinGetDownload( - WINGET_STRING url, - WINGET_STRING filePath, - BYTE* sha256Hash, - UINT32 sha256HashLength); - - // Compares two version strings, returning -1 if versionA is less than versionB, 0 if they're equal, or 1 if versionA is greater than versionB - WINGET_UTIL_API WinGetCompareVersions( - WINGET_STRING versionA, - WINGET_STRING versionB, - INT* comparisonResult); - - // A handle to the metadata collection object. - typedef void* WINGET_INSTALLER_METADATA_COLLECTION_HANDLE; - - // Option flags for WinGetBeginInstallerMetadataCollection. - enum WinGetBeginInstallerMetadataCollectionOptions - { - WinGetBeginInstallerMetadataCollectionOption_None = 0, - // The inputJSON is a local file path, not a JSON string. - WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath = 0x1, - // The inputJSON is a remote URI, not a JSON string. - WinGetBeginInstallerMetadataCollectionOption_InputIsURI = 0x2, - }; - - DEFINE_ENUM_FLAG_OPERATORS(WinGetBeginInstallerMetadataCollectionOptions); - - // Begins the installer metadata collection process. - // By default, inputJSON is expected to be a JSON string. See the WinGetBeginInstallerMetadataCollectionOptions for more options. - // logFilePath optionally specifies where to write the log file for the collection operation. - // The collectionHandle is owned by the caller and must be passed to WinGetCompleteInstallerMetadataCollection to free it. - WINGET_UTIL_API WinGetBeginInstallerMetadataCollection( - WINGET_STRING inputJSON, - WINGET_STRING logFilePath, - WinGetBeginInstallerMetadataCollectionOptions options, - WINGET_INSTALLER_METADATA_COLLECTION_HANDLE* collectionHandle); - - // Option flags for WinGetCompleteInstallerMetadataCollection. - enum WinGetCompleteInstallerMetadataCollectionOptions - { - WinGetCompleteInstallerMetadataCollectionOption_None = 0, - // Complete will simply free the collection handle without doing any additional work. - WinGetCompleteInstallerMetadataCollectionOption_Abandon = 0x1, - }; - - DEFINE_ENUM_FLAG_OPERATORS(WinGetCompleteInstallerMetadataCollectionOptions); - - // Completes the installer metadata collection process. - // Always frees the collectionHandle; WinGetCompleteInstallerMetadataCollection must be called exactly once for each call to WinGetBeginInstallerMetadataCollection. - WINGET_UTIL_API WinGetCompleteInstallerMetadataCollection( - WINGET_INSTALLER_METADATA_COLLECTION_HANDLE collectionHandle, - WINGET_STRING outputFilePath, - WinGetCompleteInstallerMetadataCollectionOptions options); - - // Option flags for WinGetMergeInstallerMetadata. - enum WinGetMergeInstallerMetadataOptions - { - WinGetMergeInstallerMetadataOptions_None = 0, - }; - - // Merges the given JSON metadata documents into a single one. - WINGET_UTIL_API WinGetMergeInstallerMetadata( - WINGET_STRING inputJSON, - WINGET_STRING_OUT* outputJSON, - UINT32 maximumOutputSizeInBytes, - WINGET_STRING logFilePath, - WinGetMergeInstallerMetadataOptions options); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +extern "C" +{ + // A handle to the index. + typedef void* WINGET_SQLITE_INDEX_HANDLE; + + // A handle to the manifest. + typedef void* WINGET_MANIFEST_HANDLE; + + // A string taken in by the utility; in UTF16. + typedef wchar_t const* const WINGET_STRING; + + // A string returned by the utility; in UTF16. + typedef BSTR WINGET_STRING_OUT; + +#define WINGET_UTIL_API HRESULT __stdcall + +#define WINGET_SQLITE_INDEX_VERSION_LATEST ((UINT32)-1) + + enum WinGetValidateManifestOption + { + Default = 0, + SchemaValidationOnly = 0x1, + ErrorOnVerifiedPublisherFields = 0x2, + InstallerValidations = 0x4, + ErrorOnNetworkAddressInSwitches = 0x8, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestOption); + + enum WinGetCreateManifestOption + { + // Just create the manifest without any validation + NoValidation = 0, + // Only validate against json schema + SchemaValidation = 0x1, + // Validate against schema and also perform semantic validation + SchemaAndSemanticValidation = 0x2, + // Use shadow manifest + AllowShadowManifest = 0x4, + + /// Below options are additional validation behaviors if needed + + // Return error on manifest fields that require verified publishers, used during semantic validation + ReturnErrorOnVerifiedPublisherFields = 0x1000, + + // Return error if a network address is present in installer switches. + ReturnErrorOnNetworkAddressInSwitches = 0x2000, + + // Return the failure or warning message as a JSON string containing both the full message + // and a structured list of individual errors/warnings. + ReturnResponseAsJson = 0x4000, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetCreateManifestOption); + + enum WinGetValidateManifestOptionV2 + { + // No validation, caller will get E_INVALIDARG + None = 0, + // Dependencies validation against index + DependenciesValidation = 0x1, + // Arp version validation against index + ArpVersionValidation = 0x2, + // Installer validation + InstallerValidation = 0x4, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestOptionV2); + + enum WinGetValidateManifestOperationType + { + OperationTypeAdd = 0, + OperationTypeUpdate = 1, + OperationTypeDelete = 2, + }; + + enum WinGetValidateManifestResult + { + Success = 0, + + // Each validation step should have an enum for corresponding failure. + DependenciesValidationFailure = 0x1, + ArpVersionValidationFailure = 0x2, + InstallerValidationFailure = 0x4, + + // Dependencies validation result. + SingleManifestPackageHasDependencies = 0x10000, + MultiManifestPackageHasDependencies = 0x20000, + MissingManifestDependenciesNode = 0x40000, + NoSuitableMinVersionDependency = 0x80000, + FoundDependencyLoop = 0x100000, + + // Internal error meaning validation does not complete as desired. + InternalError = 0x1000, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestResult); + + enum WinGetValidateManifestDependenciesOption + { + DefaultValidation = 0, + ForDelete = 0x1, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetValidateManifestDependenciesOption); + + // Initializes the logging infrastructure. + WINGET_UTIL_API WinGetLoggingInit( + WINGET_STRING logPath); + + // Removes the given log file from the logging infrastructure. + // If logPath is nullptr, then remove all loggers. + WINGET_UTIL_API WinGetLoggingTerm( + WINGET_STRING logPath); + + // Creates a new index file at filePath with the given version. + WINGET_UTIL_API WinGetSQLiteIndexCreate( + WINGET_STRING filePath, + UINT32 majorVersion, + UINT32 minorVersion, + WINGET_SQLITE_INDEX_HANDLE* index); + + // Opens an existing index at filePath. + WINGET_UTIL_API WinGetSQLiteIndexOpen( + WINGET_STRING filePath, + WINGET_SQLITE_INDEX_HANDLE* index); + + // Closes the index. + WINGET_UTIL_API WinGetSQLiteIndexClose( + WINGET_SQLITE_INDEX_HANDLE index); + + // Migrates the index to the new version specified. + WINGET_UTIL_API WinGetSQLiteIndexMigrate( + WINGET_SQLITE_INDEX_HANDLE index, + UINT32 majorVersion, + UINT32 minorVersion); + + enum WinGetSQLiteIndexProperty + { + WinGetSQLiteIndexProperty_PackageUpdateTrackingBaseTime = 0, + WinGetSQLiteIndexProperty_IntermediateFileOutputPath = 1, + }; + + // Sets the given property on the index. + WINGET_UTIL_API WinGetSQLiteIndexSetProperty( + WINGET_SQLITE_INDEX_HANDLE index, + WinGetSQLiteIndexProperty property, + WINGET_STRING value); + + // Adds the manifest at the repository relative path to the index. + // If the function succeeds, the manifest has been added. + WINGET_UTIL_API WinGetSQLiteIndexAddManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath); + + // Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the index was modified by the function. + WINGET_UTIL_API WinGetSQLiteIndexUpdateManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath, + BOOL* indexModified); + + // Adds or Updates the manifest with matching { Id, Version, Channel } in the index. + // The return value indicates whether the manifest was added (true) or updated (false). + WINGET_UTIL_API WinGetSQLiteIndexAddOrUpdateManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath, + BOOL* indexModified); + + // Removes the manifest with matching { Id, Version, Channel } from the index. + // Path is currently ignored. + WINGET_UTIL_API WinGetSQLiteIndexRemoveManifest( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath); + + // Removes data that is no longer needed for an index that is to be published. + WINGET_UTIL_API WinGetSQLiteIndexPrepareForPackaging( + WINGET_SQLITE_INDEX_HANDLE index); + + // Checks the index for consistency, ensuring that at a minimum all referenced rows actually exist. + WINGET_UTIL_API WinGetSQLiteIndexCheckConsistency( + WINGET_SQLITE_INDEX_HANDLE index, + BOOL* succeeded); + + // Validates a given manifest. Returns a bool for validation result and + // a string representing validation errors if validation failed. + WINGET_UTIL_API WinGetValidateManifest( + WINGET_STRING manifestPath, + BOOL* succeeded, + WINGET_STRING_OUT* message); + + // Validates a given manifest. Returns a bool for validation result and + // a string representing validation errors if validation failed. + // If mergedManifestPath is provided, this method will write a merged manifest + // to the location specified by mergedManifestPath + WINGET_UTIL_API WinGetValidateManifestV2( + WINGET_STRING inputPath, + BOOL* succeeded, + WINGET_STRING_OUT* message, + WINGET_STRING mergedManifestPath, + WinGetValidateManifestOption option); + + // Creates a given manifest with optional validation. Returns a bool for operation result and + // a string representing validation errors if validation failed. + // If mergedManifestPath is provided, this method will write a merged manifest + // to the location specified by mergedManifestPath + WINGET_UTIL_API WinGetCreateManifest( + WINGET_STRING inputPath, + BOOL* succeeded, + WINGET_MANIFEST_HANDLE* manifest, + WINGET_STRING_OUT* message, + WINGET_STRING mergedManifestPath, + WinGetCreateManifestOption option); + + // Closes a given manifest. + WINGET_UTIL_API WinGetCloseManifest( + WINGET_MANIFEST_HANDLE manifest); + + // Validates a given manifest. Returns WinGetValidateManifestResult for validation result and + // a string representing validation errors if validation failed. + // If result is 0, it is success. Otherwise, caller can check the result with flags to see + // which phases failed. + WINGET_UTIL_API WinGetValidateManifestV3( + WINGET_MANIFEST_HANDLE manifest, + WINGET_SQLITE_INDEX_HANDLE index, + WinGetValidateManifestResult* result, + WINGET_STRING_OUT* message, + WinGetValidateManifestOptionV2 option, + WinGetValidateManifestOperationType operationType); + + // Validates a given manifest with dependencies. Returns a bool for validation result and + // a string representing validation errors if validation failed. + // If mergedManifestPath is provided, this method will write a merged manifest + // to the location specified by mergedManifestPath + WINGET_UTIL_API WinGetValidateManifestDependencies( + WINGET_STRING inputPath, + BOOL* succeeded, + WINGET_STRING_OUT* message, + WINGET_SQLITE_INDEX_HANDLE index, + WinGetValidateManifestDependenciesOption dependenciesValidationOption); + + // Downloads a file to the given path, returning the SHA 256 hash of the file. + WINGET_UTIL_API WinGetDownload( + WINGET_STRING url, + WINGET_STRING filePath, + BYTE* sha256Hash, + UINT32 sha256HashLength); + + // Compares two version strings, returning -1 if versionA is less than versionB, 0 if they're equal, or 1 if versionA is greater than versionB + WINGET_UTIL_API WinGetCompareVersions( + WINGET_STRING versionA, + WINGET_STRING versionB, + INT* comparisonResult); + + // A handle to the metadata collection object. + typedef void* WINGET_INSTALLER_METADATA_COLLECTION_HANDLE; + + // Option flags for WinGetBeginInstallerMetadataCollection. + enum WinGetBeginInstallerMetadataCollectionOptions + { + WinGetBeginInstallerMetadataCollectionOption_None = 0, + // The inputJSON is a local file path, not a JSON string. + WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath = 0x1, + // The inputJSON is a remote URI, not a JSON string. + WinGetBeginInstallerMetadataCollectionOption_InputIsURI = 0x2, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetBeginInstallerMetadataCollectionOptions); + + // Begins the installer metadata collection process. + // By default, inputJSON is expected to be a JSON string. See the WinGetBeginInstallerMetadataCollectionOptions for more options. + // logFilePath optionally specifies where to write the log file for the collection operation. + // The collectionHandle is owned by the caller and must be passed to WinGetCompleteInstallerMetadataCollection to free it. + WINGET_UTIL_API WinGetBeginInstallerMetadataCollection( + WINGET_STRING inputJSON, + WINGET_STRING logFilePath, + WinGetBeginInstallerMetadataCollectionOptions options, + WINGET_INSTALLER_METADATA_COLLECTION_HANDLE* collectionHandle); + + // Option flags for WinGetCompleteInstallerMetadataCollection. + enum WinGetCompleteInstallerMetadataCollectionOptions + { + WinGetCompleteInstallerMetadataCollectionOption_None = 0, + // Complete will simply free the collection handle without doing any additional work. + WinGetCompleteInstallerMetadataCollectionOption_Abandon = 0x1, + }; + + DEFINE_ENUM_FLAG_OPERATORS(WinGetCompleteInstallerMetadataCollectionOptions); + + // Completes the installer metadata collection process. + // Always frees the collectionHandle; WinGetCompleteInstallerMetadataCollection must be called exactly once for each call to WinGetBeginInstallerMetadataCollection. + WINGET_UTIL_API WinGetCompleteInstallerMetadataCollection( + WINGET_INSTALLER_METADATA_COLLECTION_HANDLE collectionHandle, + WINGET_STRING outputFilePath, + WinGetCompleteInstallerMetadataCollectionOptions options); + + // Option flags for WinGetMergeInstallerMetadata. + enum WinGetMergeInstallerMetadataOptions + { + WinGetMergeInstallerMetadataOptions_None = 0, + }; + + // Merges the given JSON metadata documents into a single one. + WINGET_UTIL_API WinGetMergeInstallerMetadata( + WINGET_STRING inputJSON, + WINGET_STRING_OUT* outputJSON, + UINT32 maximumOutputSizeInBytes, + WINGET_STRING logFilePath, + WinGetMergeInstallerMetadataOptions options); +} diff --git a/src/WinGetUtil/WinGetUtil.vcxproj b/src/WinGetUtil/WinGetUtil.vcxproj index 3ce65543b3..12066a2a3b 100644 --- a/src/WinGetUtil/WinGetUtil.vcxproj +++ b/src/WinGetUtil/WinGetUtil.vcxproj @@ -1,283 +1,283 @@ - - - - - true - true - true - 15.0 - {FB313532-38B0-4676-9303-AB200AA13576} - Win32Proj - WinGetUtil - 10.0.26100.0 - 10.0.17763.0 - true - - - - - Debug - ARM64 - - - Debug - Win32 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - DynamicLibrary - - - true - true - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - - - Use - pch.h - $(IntDir)pch.pch - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) - true - true - false - false - true - true - true - true - false - false - - - false - Windows - Windows - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) - winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - false - true - true - false - - - Windows - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - true - true - true - false - false - false - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) - winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) - winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) - /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full %(AdditionalOptions) - /debug:full %(AdditionalOptions) - /debug:full %(AdditionalOptions) - true - true - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - - - - - - Create - - - - - - - - - - {5890d6ed-7c3b-40f3-b436-b54f640d9e65} - - - {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - + + + + + true + true + true + 15.0 + {FB313532-38B0-4676-9303-AB200AA13576} + Win32Proj + WinGetUtil + 10.0.26100.0 + 10.0.17763.0 + true + + + + + Debug + ARM64 + + + Debug + Win32 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + DynamicLibrary + + + true + true + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + + + Use + pch.h + $(IntDir)pch.pch + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\JsonCppLib\json;%(AdditionalIncludeDirectories) + true + true + false + false + true + true + true + true + false + false + + + false + Windows + Windows + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) + winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + WIN32;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + false + true + true + false + + + Windows + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions);CLICOREDLLBUILD + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCommonCore;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerRepositoryCore\Public;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + true + true + true + false + false + false + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) + winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) + winsqlite3.dll;icuuc.dll;icuin.dll;gdi32.dll;%(DelayLoadDLLs) + /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full %(AdditionalOptions) + /debug:full %(AdditionalOptions) + /debug:full %(AdditionalOptions) + true + true + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + + + + + + Create + + + + + + + + + + {5890d6ed-7c3b-40f3-b436-b54f640d9e65} + + + {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + diff --git a/src/WinGetUtil/WinGetUtil.vcxproj.filters b/src/WinGetUtil/WinGetUtil.vcxproj.filters index 529885e975..0617fc64e3 100644 --- a/src/WinGetUtil/WinGetUtil.vcxproj.filters +++ b/src/WinGetUtil/WinGetUtil.vcxproj.filters @@ -1,40 +1,40 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - Header Files - - - - - Source Files - - - Source Files - - - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + + + Source Files + + \ No newline at end of file diff --git a/src/WinGetUtil/packages.config b/src/WinGetUtil/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/WinGetUtil/packages.config +++ b/src/WinGetUtil/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/APIUnitTests/ManifestUnitTests.cs b/src/WinGetUtilInterop.UnitTests/APIUnitTests/ManifestUnitTests.cs index 52a15bea44..d2f27463d8 100644 --- a/src/WinGetUtilInterop.UnitTests/APIUnitTests/ManifestUnitTests.cs +++ b/src/WinGetUtilInterop.UnitTests/APIUnitTests/ManifestUnitTests.cs @@ -1,240 +1,240 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetUtilInterop.UnitTests.APIUnitTests -{ - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Reflection; - using Microsoft.WinGetUtil.Api; - using Microsoft.WinGetUtil.Common; - using Microsoft.WinGetUtil.Manifest.V1; - using Microsoft.WinGetUtil.Models.V1; - using WinGetUtilInterop.UnitTests.Common; - using Xunit; - using Xunit.Abstractions; - - /// - /// API manifests tests. - /// - public class ManifestUnitTests - { - private static string testCollateralDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral"); - private readonly ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Output Helper. - public ManifestUnitTests(ITestOutputHelper log) - { - this.log = log; - } - - /// - /// Test creating a manifest with shadow. - /// - [FactSkipx64CI] - public void CreateShadowManifest() - { - var input = Path.Combine(testCollateralDir, "Shadow"); - var mergedManifestPath = Path.GetTempFileName(); - var logFile = Path.GetTempFileName(); - - var factory = new WinGetFactory(); - using var log = factory.LoggingInit(logFile); - using var result = factory.CreateManifest( - input, - mergedManifestPath, - WinGetCreateManifestOption.SchemaAndSemanticValidation | WinGetCreateManifestOption.AllowShadowManifest); - - Assert.NotNull(result); - Assert.True(result.IsValid); - Assert.NotNull(result.ManifestHandle); - - var manifest = Manifest.CreateManifestFromPath(mergedManifestPath); - - Assert.Equal("microsoft.msixsdk", manifest.Id); - Assert.Equal("1.7.32", manifest.Version); - Assert.Single(manifest.Installers); - Assert.Equal("en-US", manifest.PackageLocale); - Assert.Equal("Microsoft", manifest.Publisher); - Assert.Equal("MSIX SDK", manifest.PackageName); - Assert.Equal("MIT License", manifest.License); - Assert.Equal("The MSIX SDK project is an effort to enable developers", manifest.Description); - Assert.Equal("This is MSIX SDK", manifest.ShortDescription); - - Assert.Single(manifest.Icons); - Assert.Equal("https://shadowIcon-default", manifest.Icons[0].IconUrl); - Assert.Equal("ico", manifest.Icons[0].IconFileType); - Assert.Equal("custom", manifest.Icons[0].IconResolution); - Assert.Equal("default", manifest.Icons[0].IconTheme); - Assert.Equal("1111111111111111111111111111111111111111111111111111111111111111", manifest.Icons[0].IconSha256); - - Assert.Equal(2, manifest.Localization.Count); - - var enGBLocale = manifest.Localization.Where(l => l.PackageLocale == "en-gb").FirstOrDefault(); - Assert.NotNull(enGBLocale); - Assert.Single(enGBLocale.Icons); - Assert.Equal("https://shadowIcon-en-GB", enGBLocale.Icons[0].IconUrl); - Assert.Equal("png", enGBLocale.Icons[0].IconFileType); - Assert.Equal("32x32", enGBLocale.Icons[0].IconResolution); - Assert.Equal("light", enGBLocale.Icons[0].IconTheme); - Assert.Equal("2222222222222222222222222222222222222222222222222222222222222222", enGBLocale.Icons[0].IconSha256); - - var frFRLocale = manifest.Localization.Where(l => l.PackageLocale == "fr-FR").FirstOrDefault(); - Assert.NotNull(frFRLocale); - Assert.Single(frFRLocale.Icons); - Assert.Equal("https://shadowIcon-fr-FR", frFRLocale.Icons[0].IconUrl); - Assert.Equal("jpeg", frFRLocale.Icons[0].IconFileType); - Assert.Equal("20x20", frFRLocale.Icons[0].IconResolution); - Assert.Equal("dark", frFRLocale.Icons[0].IconTheme); - Assert.Equal("3333333333333333333333333333333333333333333333333333333333333333", frFRLocale.Icons[0].IconSha256); - } - - /// - /// Test serializing the shadow manifest. - /// - [Fact] - public void SerializeShadowManifest() - { - var shadowManifest = ManifestShadow.CreateManifest(); - shadowManifest.Id = "Package.package"; - shadowManifest.Version = "1.0"; - shadowManifest.PackageLocale = "en-US"; - shadowManifest.ManifestVersion = "1.5"; - shadowManifest.Icons = new List - { - new ManifestIcon() - { - IconUrl = "iconUrl", - IconFileType = "fileType", - IconResolution = "iconResolution", - IconTheme = "iconTheme", - IconSha256 = "iconSha256", - }, - }; - shadowManifest.Localization = new List - { - new ManifestShadowLocalization() - { - PackageLocale = "es-MX", - Icons = new List() - { - new ManifestIcon() - { - IconUrl = "iconUrl-esMX", - IconFileType = "fileType-esMX", - IconResolution = "iconResolution-esMX", - IconTheme = "iconTheme-esMX", - IconSha256 = "iconSha256-esMX", - }, - }, - }, - new ManifestShadowLocalization() - { - PackageLocale = "de-DE", - Icons = new List() - { - new ManifestIcon() - { - IconUrl = "iconUrl-de-DE", - IconFileType = "fileType-de-DE", - IconResolution = "iconResolution-de-DE", - IconTheme = "iconTheme-de-DE", - IconSha256 = "iconSha256-de-DE", - }, - }, - }, - }; - - var serialized = shadowManifest.Serialize(); - Assert.Equal(File.ReadAllText(Path.Combine(testCollateralDir, "ExpectedShadowManifest.yaml")), serialized); - this.log.WriteLine(serialized); - } - - /// - /// Accessing Diagnostics without ReturnResponseAsJSON throws InvalidOperationException. - /// - [Fact] - public void CreateManifestResult_DiagnosticsThrowsWithoutJsonFlag() - { - using var result = new CreateManifestResult(isValid: true, message: null, manifestHandle: null); - Assert.Throws(() => result.Diagnostics); - } - - /// - /// CreateManifest with ReturnResponseAsJSON and a valid manifest returns empty Diagnostics. - /// - [Fact] - public void CreateManifest_WithJsonFlag_ValidManifest_ReturnsDiagnosticsEmpty() - { - var input = Path.Combine(testCollateralDir, "Shadow"); - var mergedManifestPath = Path.GetTempFileName(); - var logFile = Path.GetTempFileName(); - - var factory = new WinGetFactory(); - using var log = factory.LoggingInit(logFile); - using var result = factory.CreateManifest( - input, - mergedManifestPath, - WinGetCreateManifestOption.SchemaAndSemanticValidation - | WinGetCreateManifestOption.AllowShadowManifest - | WinGetCreateManifestOption.ReturnResponseAsJson); - - Assert.True(result.IsValid); - Assert.Null(result.Message); - Assert.NotNull(result.Diagnostics); - Assert.Empty(result.Diagnostics); - } - - /// - /// CreateManifest with ReturnResponseAsJSON and an invalid manifest returns structured Diagnostics. - /// - [Fact] - public void CreateManifest_WithJsonFlag_InvalidManifest_ReturnsDiagnosticsPopulated() - { - // V1ManifestInfoMissingRequiredPackageLocale.yaml is a locale-type manifest without PackageLocale. - var input = Path.Combine(testCollateralDir, "V1ManifestInfoMissingRequiredPackageLocale.yaml"); - var logFile = Path.GetTempFileName(); - - var factory = new WinGetFactory(); - using var log = factory.LoggingInit(logFile); - using var result = factory.CreateManifest( - input, - mergedManifestPath: null, - WinGetCreateManifestOption.SchemaAndSemanticValidation | WinGetCreateManifestOption.ReturnResponseAsJson); - - Assert.False(result.IsValid); - Assert.NotNull(result.Message); - Assert.NotEmpty(result.Message); - - var diagnostics = result.Diagnostics; - Assert.NotNull(diagnostics); - Assert.NotEmpty(diagnostics); - - // At least one diagnostic should be an error. - Assert.Contains(diagnostics, d => d.Level == ManifestDiagnosticLevel.Error); - - // Every diagnostic must have a non-empty message and a known (non-Unknown) error ID. - Assert.All(diagnostics, d => - { - Assert.NotEqual(ManifestErrorId.Unknown, d.ErrorId); - Assert.False(string.IsNullOrEmpty(d.Message)); - }); - - // The Message property must equal the fullMessage from the JSON (same content as without the flag). - using var resultWithoutJson = factory.CreateManifest( - input, - mergedManifestPath: null, - WinGetCreateManifestOption.SchemaAndSemanticValidation); - - Assert.Equal(resultWithoutJson.Message, result.Message); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetUtilInterop.UnitTests.APIUnitTests +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Reflection; + using Microsoft.WinGetUtil.Api; + using Microsoft.WinGetUtil.Common; + using Microsoft.WinGetUtil.Manifest.V1; + using Microsoft.WinGetUtil.Models.V1; + using WinGetUtilInterop.UnitTests.Common; + using Xunit; + using Xunit.Abstractions; + + /// + /// API manifests tests. + /// + public class ManifestUnitTests + { + private static string testCollateralDir = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral"); + private readonly ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Output Helper. + public ManifestUnitTests(ITestOutputHelper log) + { + this.log = log; + } + + /// + /// Test creating a manifest with shadow. + /// + [FactSkipx64CI] + public void CreateShadowManifest() + { + var input = Path.Combine(testCollateralDir, "Shadow"); + var mergedManifestPath = Path.GetTempFileName(); + var logFile = Path.GetTempFileName(); + + var factory = new WinGetFactory(); + using var log = factory.LoggingInit(logFile); + using var result = factory.CreateManifest( + input, + mergedManifestPath, + WinGetCreateManifestOption.SchemaAndSemanticValidation | WinGetCreateManifestOption.AllowShadowManifest); + + Assert.NotNull(result); + Assert.True(result.IsValid); + Assert.NotNull(result.ManifestHandle); + + var manifest = Manifest.CreateManifestFromPath(mergedManifestPath); + + Assert.Equal("microsoft.msixsdk", manifest.Id); + Assert.Equal("1.7.32", manifest.Version); + Assert.Single(manifest.Installers); + Assert.Equal("en-US", manifest.PackageLocale); + Assert.Equal("Microsoft", manifest.Publisher); + Assert.Equal("MSIX SDK", manifest.PackageName); + Assert.Equal("MIT License", manifest.License); + Assert.Equal("The MSIX SDK project is an effort to enable developers", manifest.Description); + Assert.Equal("This is MSIX SDK", manifest.ShortDescription); + + Assert.Single(manifest.Icons); + Assert.Equal("https://shadowIcon-default", manifest.Icons[0].IconUrl); + Assert.Equal("ico", manifest.Icons[0].IconFileType); + Assert.Equal("custom", manifest.Icons[0].IconResolution); + Assert.Equal("default", manifest.Icons[0].IconTheme); + Assert.Equal("1111111111111111111111111111111111111111111111111111111111111111", manifest.Icons[0].IconSha256); + + Assert.Equal(2, manifest.Localization.Count); + + var enGBLocale = manifest.Localization.Where(l => l.PackageLocale == "en-gb").FirstOrDefault(); + Assert.NotNull(enGBLocale); + Assert.Single(enGBLocale.Icons); + Assert.Equal("https://shadowIcon-en-GB", enGBLocale.Icons[0].IconUrl); + Assert.Equal("png", enGBLocale.Icons[0].IconFileType); + Assert.Equal("32x32", enGBLocale.Icons[0].IconResolution); + Assert.Equal("light", enGBLocale.Icons[0].IconTheme); + Assert.Equal("2222222222222222222222222222222222222222222222222222222222222222", enGBLocale.Icons[0].IconSha256); + + var frFRLocale = manifest.Localization.Where(l => l.PackageLocale == "fr-FR").FirstOrDefault(); + Assert.NotNull(frFRLocale); + Assert.Single(frFRLocale.Icons); + Assert.Equal("https://shadowIcon-fr-FR", frFRLocale.Icons[0].IconUrl); + Assert.Equal("jpeg", frFRLocale.Icons[0].IconFileType); + Assert.Equal("20x20", frFRLocale.Icons[0].IconResolution); + Assert.Equal("dark", frFRLocale.Icons[0].IconTheme); + Assert.Equal("3333333333333333333333333333333333333333333333333333333333333333", frFRLocale.Icons[0].IconSha256); + } + + /// + /// Test serializing the shadow manifest. + /// + [Fact] + public void SerializeShadowManifest() + { + var shadowManifest = ManifestShadow.CreateManifest(); + shadowManifest.Id = "Package.package"; + shadowManifest.Version = "1.0"; + shadowManifest.PackageLocale = "en-US"; + shadowManifest.ManifestVersion = "1.5"; + shadowManifest.Icons = new List + { + new ManifestIcon() + { + IconUrl = "iconUrl", + IconFileType = "fileType", + IconResolution = "iconResolution", + IconTheme = "iconTheme", + IconSha256 = "iconSha256", + }, + }; + shadowManifest.Localization = new List + { + new ManifestShadowLocalization() + { + PackageLocale = "es-MX", + Icons = new List() + { + new ManifestIcon() + { + IconUrl = "iconUrl-esMX", + IconFileType = "fileType-esMX", + IconResolution = "iconResolution-esMX", + IconTheme = "iconTheme-esMX", + IconSha256 = "iconSha256-esMX", + }, + }, + }, + new ManifestShadowLocalization() + { + PackageLocale = "de-DE", + Icons = new List() + { + new ManifestIcon() + { + IconUrl = "iconUrl-de-DE", + IconFileType = "fileType-de-DE", + IconResolution = "iconResolution-de-DE", + IconTheme = "iconTheme-de-DE", + IconSha256 = "iconSha256-de-DE", + }, + }, + }, + }; + + var serialized = shadowManifest.Serialize(); + Assert.Equal(File.ReadAllText(Path.Combine(testCollateralDir, "ExpectedShadowManifest.yaml")), serialized); + this.log.WriteLine(serialized); + } + + /// + /// Accessing Diagnostics without ReturnResponseAsJSON throws InvalidOperationException. + /// + [Fact] + public void CreateManifestResult_DiagnosticsThrowsWithoutJsonFlag() + { + using var result = new CreateManifestResult(isValid: true, message: null, manifestHandle: null); + Assert.Throws(() => result.Diagnostics); + } + + /// + /// CreateManifest with ReturnResponseAsJSON and a valid manifest returns empty Diagnostics. + /// + [Fact] + public void CreateManifest_WithJsonFlag_ValidManifest_ReturnsDiagnosticsEmpty() + { + var input = Path.Combine(testCollateralDir, "Shadow"); + var mergedManifestPath = Path.GetTempFileName(); + var logFile = Path.GetTempFileName(); + + var factory = new WinGetFactory(); + using var log = factory.LoggingInit(logFile); + using var result = factory.CreateManifest( + input, + mergedManifestPath, + WinGetCreateManifestOption.SchemaAndSemanticValidation + | WinGetCreateManifestOption.AllowShadowManifest + | WinGetCreateManifestOption.ReturnResponseAsJson); + + Assert.True(result.IsValid); + Assert.Null(result.Message); + Assert.NotNull(result.Diagnostics); + Assert.Empty(result.Diagnostics); + } + + /// + /// CreateManifest with ReturnResponseAsJSON and an invalid manifest returns structured Diagnostics. + /// + [Fact] + public void CreateManifest_WithJsonFlag_InvalidManifest_ReturnsDiagnosticsPopulated() + { + // V1ManifestInfoMissingRequiredPackageLocale.yaml is a locale-type manifest without PackageLocale. + var input = Path.Combine(testCollateralDir, "V1ManifestInfoMissingRequiredPackageLocale.yaml"); + var logFile = Path.GetTempFileName(); + + var factory = new WinGetFactory(); + using var log = factory.LoggingInit(logFile); + using var result = factory.CreateManifest( + input, + mergedManifestPath: null, + WinGetCreateManifestOption.SchemaAndSemanticValidation | WinGetCreateManifestOption.ReturnResponseAsJson); + + Assert.False(result.IsValid); + Assert.NotNull(result.Message); + Assert.NotEmpty(result.Message); + + var diagnostics = result.Diagnostics; + Assert.NotNull(diagnostics); + Assert.NotEmpty(diagnostics); + + // At least one diagnostic should be an error. + Assert.Contains(diagnostics, d => d.Level == ManifestDiagnosticLevel.Error); + + // Every diagnostic must have a non-empty message and a known (non-Unknown) error ID. + Assert.All(diagnostics, d => + { + Assert.NotEqual(ManifestErrorId.Unknown, d.ErrorId); + Assert.False(string.IsNullOrEmpty(d.Message)); + }); + + // The Message property must equal the fullMessage from the JSON (same content as without the flag). + using var resultWithoutJson = factory.CreateManifest( + input, + mergedManifestPath: null, + WinGetCreateManifestOption.SchemaAndSemanticValidation); + + Assert.Equal(resultWithoutJson.Message, result.Message); + } + } +} diff --git a/src/WinGetUtilInterop.UnitTests/Common/DisplayTestMethodNameAttribute.cs b/src/WinGetUtilInterop.UnitTests/Common/DisplayTestMethodNameAttribute.cs index 9cb26bb519..01eadb973e 100644 --- a/src/WinGetUtilInterop.UnitTests/Common/DisplayTestMethodNameAttribute.cs +++ b/src/WinGetUtilInterop.UnitTests/Common/DisplayTestMethodNameAttribute.cs @@ -1,33 +1,33 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGetUtil.UnitTests.Common.Logging -{ - using System; - using System.Reflection; - using Microsoft.Msix.Utils.Logger; - using Xunit.Sdk; - - /// - /// This will log the name the method in OWC utils logger. - /// - internal class DisplayTestMethodNameAttribute : BeforeAfterTestAttribute - { - /// - public override void Before(MethodInfo methodUnderTest) - { - Logger.Info("-----------------------------------------------------------------------"); - Logger.Info($"Starting test {methodUnderTest.Name}{Environment.NewLine}"); - } - - /// - public override void After(MethodInfo methodUnderTest) - { - Logger.Info($"{Environment.NewLine}Finish test {methodUnderTest.Name}"); - Logger.Info("-----------------------------------------------------------------------\n"); - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.UnitTests.Common.Logging +{ + using System; + using System.Reflection; + using Microsoft.Msix.Utils.Logger; + using Xunit.Sdk; + + /// + /// This will log the name the method in OWC utils logger. + /// + internal class DisplayTestMethodNameAttribute : BeforeAfterTestAttribute + { + /// + public override void Before(MethodInfo methodUnderTest) + { + Logger.Info("-----------------------------------------------------------------------"); + Logger.Info($"Starting test {methodUnderTest.Name}{Environment.NewLine}"); + } + + /// + public override void After(MethodInfo methodUnderTest) + { + Logger.Info($"{Environment.NewLine}Finish test {methodUnderTest.Name}"); + Logger.Info("-----------------------------------------------------------------------\n"); + } + } +} diff --git a/src/WinGetUtilInterop.UnitTests/Common/FactSkipx64CI.cs b/src/WinGetUtilInterop.UnitTests/Common/FactSkipx64CI.cs index 58d705f0fb..bebd25b75d 100644 --- a/src/WinGetUtilInterop.UnitTests/Common/FactSkipx64CI.cs +++ b/src/WinGetUtilInterop.UnitTests/Common/FactSkipx64CI.cs @@ -1,28 +1,28 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetUtilInterop.UnitTests.Common -{ - using System; - using Xunit; - - /// - /// Skip fact tests if running in CI x64 builds. - /// - public class FactSkipx64CI : FactAttribute - { - /// - /// Initializes a new instance of the class. - /// - public FactSkipx64CI() - { - if (Environment.Is64BitProcess && Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") is not null) - { - this.Skip = "Skip test for x64 CI builds"; - } - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetUtilInterop.UnitTests.Common +{ + using System; + using Xunit; + + /// + /// Skip fact tests if running in CI x64 builds. + /// + public class FactSkipx64CI : FactAttribute + { + /// + /// Initializes a new instance of the class. + /// + public FactSkipx64CI() + { + if (Environment.Is64BitProcess && Environment.GetEnvironmentVariable("BUILD_BUILDNUMBER") is not null) + { + this.Skip = "Skip test for x64 CI builds"; + } + } + } +} diff --git a/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/ManifestEqualityUnitTests.cs b/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/ManifestEqualityUnitTests.cs index 95d568b063..0ab382408f 100644 --- a/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/ManifestEqualityUnitTests.cs +++ b/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/ManifestEqualityUnitTests.cs @@ -1,156 +1,156 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetUtilInterop.UnitTests.ManifestUnitTest -{ - using System.IO; - using System.Reflection; - using Microsoft.WinGetUtil.Models.Preview; - using Microsoft.WinGetUtil.UnitTests.Common.Logging; - using Xunit; - using Xunit.Abstractions; - - /// - /// Manifest equality tests. - /// - public class ManifestEqualityUnitTests - { - private ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Output Helper. - public ManifestEqualityUnitTests(ITestOutputHelper log) - { - this.log = log; - } - - /// - /// All manifest are equal but some manifest are more equal than others. - /// - [Fact] - [DisplayTestMethodName] - public void ManifestEquals() - { - // All equality properties. - Manifest allEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEquality)); - Manifest otherAllEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEquality)); - AssertEquivalence(allEquality, allEquality); - AssertEquivalence(allEquality, otherAllEquality); - - // With all equality properties and non equality properties. - Manifest allEqualityWithDescription = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEqualityWithDescription)); - AssertEquivalence(allEquality, allEqualityWithDescription); - - // With some equality properties. - Manifest someEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEquality)); - Manifest otherSomeEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEquality)); - AssertEquivalence(someEquality, otherSomeEquality); - - // With some equality properties and some non equality properties - Manifest someEqualityWithLocalization = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithLocalization)); - AssertEquivalence(someEquality, someEqualityWithLocalization); - - // Equality where a Switch object is not serialized - Manifest someEqualityWithoutSwitch = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); - Manifest otherSomeEqualityWithoutSwitch = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); - AssertEquivalence(someEqualityWithoutSwitch, otherSomeEqualityWithoutSwitch); - - // Equality where Installers list is not serialized - Manifest someEqualityWithoutInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutInstallers)); - Manifest otherSomeEqualityWithoutInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutInstallers)); - AssertEquivalence(someEqualityWithoutInstaller, otherSomeEqualityWithoutInstaller); - } - - /// - /// Every manifest is unique in its own way. - /// - [Fact] - [DisplayTestMethodName] - public void ManifestNotEquals() - { - // All equality properties. - Manifest allEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEquality)); - Assert.False(allEquality == null); - - Manifest allEqualityOther = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.DifferentId)); - AssertNotEquivalent(allEquality, allEqualityOther); - - Manifest someEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEquality)); - Manifest withoutSwitches = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); - AssertNotEquivalent(someEquality, withoutSwitches); - - Manifest withoutInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); - AssertNotEquivalent(someEquality, withoutInstaller); - - Manifest oneInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.OneInstaller)); - AssertNotEquivalent(allEquality, oneInstaller); - } - - private static string ReadFile(string fileName) - { - string location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - return File.ReadAllText(Path.Combine(location, "TestCollateral", fileName)); - } - - private static void AssertEquivalence(Manifest first, Manifest second) - { - Assert.True(first.IsManifestEquivalent(second)); - } - - private static void AssertNotEquivalent(Manifest first, Manifest second) - { - Assert.False(first.IsManifestEquivalent(second)); - } - - /// - /// Helper class with manifest strings. - /// - internal class ManifestStrings - { - /// - /// Manifest with all properties that provide equality. - /// - public const string AllEquality = "AllEquality.yaml"; - - /// - /// Manifest with all properties that provide equality. - /// - public const string AllEqualityWithDescription = "AllEqualityWithDescription.yaml"; - - /// - /// Manifest with some properties that provide equality. - /// - public const string SomeEquality = "SomeEquality.yaml"; - - /// - /// Manifest with some properties that provide equality. - /// - public const string SomeEqualityWithLocalization = "SomeEqualityWithLocalization.yaml"; - - /// - /// Manifest with some properties specifically without switches. - /// - public const string SomeEqualityWithoutSwitches = "SomeEqualityWithoutSwitches.yaml"; - - /// - /// Manifest with some properties specifically without installers. - /// - public const string SomeEqualityWithoutInstallers = "SomeEqualityWithoutInstallers.yaml"; - - /// - /// Manifest with all properties that provide equality. - /// - public const string DifferentId = "DifferentId.yaml"; - - /// - /// Manifest with all properties that provide equality. - /// - public const string OneInstaller = "OneInstaller.yaml"; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetUtilInterop.UnitTests.ManifestUnitTest +{ + using System.IO; + using System.Reflection; + using Microsoft.WinGetUtil.Models.Preview; + using Microsoft.WinGetUtil.UnitTests.Common.Logging; + using Xunit; + using Xunit.Abstractions; + + /// + /// Manifest equality tests. + /// + public class ManifestEqualityUnitTests + { + private ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Output Helper. + public ManifestEqualityUnitTests(ITestOutputHelper log) + { + this.log = log; + } + + /// + /// All manifest are equal but some manifest are more equal than others. + /// + [Fact] + [DisplayTestMethodName] + public void ManifestEquals() + { + // All equality properties. + Manifest allEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEquality)); + Manifest otherAllEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEquality)); + AssertEquivalence(allEquality, allEquality); + AssertEquivalence(allEquality, otherAllEquality); + + // With all equality properties and non equality properties. + Manifest allEqualityWithDescription = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEqualityWithDescription)); + AssertEquivalence(allEquality, allEqualityWithDescription); + + // With some equality properties. + Manifest someEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEquality)); + Manifest otherSomeEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEquality)); + AssertEquivalence(someEquality, otherSomeEquality); + + // With some equality properties and some non equality properties + Manifest someEqualityWithLocalization = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithLocalization)); + AssertEquivalence(someEquality, someEqualityWithLocalization); + + // Equality where a Switch object is not serialized + Manifest someEqualityWithoutSwitch = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); + Manifest otherSomeEqualityWithoutSwitch = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); + AssertEquivalence(someEqualityWithoutSwitch, otherSomeEqualityWithoutSwitch); + + // Equality where Installers list is not serialized + Manifest someEqualityWithoutInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutInstallers)); + Manifest otherSomeEqualityWithoutInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutInstallers)); + AssertEquivalence(someEqualityWithoutInstaller, otherSomeEqualityWithoutInstaller); + } + + /// + /// Every manifest is unique in its own way. + /// + [Fact] + [DisplayTestMethodName] + public void ManifestNotEquals() + { + // All equality properties. + Manifest allEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.AllEquality)); + Assert.False(allEquality == null); + + Manifest allEqualityOther = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.DifferentId)); + AssertNotEquivalent(allEquality, allEqualityOther); + + Manifest someEquality = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEquality)); + Manifest withoutSwitches = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); + AssertNotEquivalent(someEquality, withoutSwitches); + + Manifest withoutInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.SomeEqualityWithoutSwitches)); + AssertNotEquivalent(someEquality, withoutInstaller); + + Manifest oneInstaller = Manifest.CreateManifestFromString(ReadFile(ManifestStrings.OneInstaller)); + AssertNotEquivalent(allEquality, oneInstaller); + } + + private static string ReadFile(string fileName) + { + string location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + return File.ReadAllText(Path.Combine(location, "TestCollateral", fileName)); + } + + private static void AssertEquivalence(Manifest first, Manifest second) + { + Assert.True(first.IsManifestEquivalent(second)); + } + + private static void AssertNotEquivalent(Manifest first, Manifest second) + { + Assert.False(first.IsManifestEquivalent(second)); + } + + /// + /// Helper class with manifest strings. + /// + internal class ManifestStrings + { + /// + /// Manifest with all properties that provide equality. + /// + public const string AllEquality = "AllEquality.yaml"; + + /// + /// Manifest with all properties that provide equality. + /// + public const string AllEqualityWithDescription = "AllEqualityWithDescription.yaml"; + + /// + /// Manifest with some properties that provide equality. + /// + public const string SomeEquality = "SomeEquality.yaml"; + + /// + /// Manifest with some properties that provide equality. + /// + public const string SomeEqualityWithLocalization = "SomeEqualityWithLocalization.yaml"; + + /// + /// Manifest with some properties specifically without switches. + /// + public const string SomeEqualityWithoutSwitches = "SomeEqualityWithoutSwitches.yaml"; + + /// + /// Manifest with some properties specifically without installers. + /// + public const string SomeEqualityWithoutInstallers = "SomeEqualityWithoutInstallers.yaml"; + + /// + /// Manifest with all properties that provide equality. + /// + public const string DifferentId = "DifferentId.yaml"; + + /// + /// Manifest with all properties that provide equality. + /// + public const string OneInstaller = "OneInstaller.yaml"; + } + } +} diff --git a/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs b/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs index d01b309dad..44e160f8b8 100644 --- a/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs +++ b/src/WinGetUtilInterop.UnitTests/ManifestUnitTest/V1ManifestReadTest.cs @@ -1,594 +1,594 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace WinGetUtilInterop.UnitTests.ManifestUnitTest -{ - using System.IO; - using System.Reflection; - using Microsoft.WinGetUtil.Models.V1; - using Microsoft.WinGetUtil.UnitTests.Common.Logging; - using Xunit; - using Xunit.Abstractions; - - /// - /// Manifest equality tests. - /// - public class V1ManifestReadTest - { - private ITestOutputHelper log; - - /// - /// Initializes a new instance of the class. - /// - /// Output Helper. - public V1ManifestReadTest(ITestOutputHelper log) - { - this.log = log; - } - - private enum TestManifestVersion - { - V1_0_0, - V1_1_0, - V1_6_0, - V1_7_0, - V1_9_0, - V1_10_0, - V1_12_0, - V1_28_0, - } - - /// - /// Read v1 manifest. - /// - [Fact] - [DisplayTestMethodName] - public void ReadV1ManifestsAndVerifyContents() - { - Manifest v1_0_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_0_0ManifestMerged)); - - this.ValidateManifestFields(v1_0_0manifest, TestManifestVersion.V1_0_0); - - Manifest v1_1_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_1_0ManifestMerged)); - - this.ValidateManifestFields(v1_1_0manifest, TestManifestVersion.V1_1_0); - - Manifest v1_6_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_6_0ManifestMerged)); - - this.ValidateManifestFields(v1_6_0manifest, TestManifestVersion.V1_6_0); - - Manifest v1_7_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_7_0ManifestMerged)); - - this.ValidateManifestFields(v1_7_0manifest, TestManifestVersion.V1_7_0); - - Manifest v1_9_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_9_0ManifestMerged)); - - this.ValidateManifestFields(v1_9_0manifest, TestManifestVersion.V1_9_0); - - Manifest v1_10_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_10_0ManifestMerged)); - - this.ValidateManifestFields(v1_10_0manifest, TestManifestVersion.V1_10_0); - - Manifest v1_12_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_12_0ManifestMerged)); - - this.ValidateManifestFields(v1_12_0manifest, TestManifestVersion.V1_12_0); - - Manifest v1_28_0manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_28_0ManifestMerged)); - - this.ValidateManifestFields(v1_28_0manifest, TestManifestVersion.V1_28_0); - } - - /// - /// Read v1 manifest without additional localization. - /// - [Fact] - [DisplayTestMethodName] - public void ReadV1ManifestNoLocalization() - { - Manifest v1manifest = Manifest.CreateManifestFromPath( - Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1ManifestNoLocalization)); - - // Calling GetURIs() on manifest without localization should not fail. - v1manifest.GetURIs(); - } - - /// - /// Read bad min manifest info. - /// - [Fact] - [DisplayTestMethodName] - public void ReadV1LocaleManifestInfoNoPackageLocale() - { - Assert.Throws( - () => - { - _ = MinManifestInfo.CreateManifestInfoFromPath( - Path.Combine( - Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), - "TestCollateral", - ManifestStrings.V1ManifestInfoMissingRequiredPackageLocale)); - }); - } - - private void ValidateManifestFields(Manifest manifest, TestManifestVersion manifestVersion) - { - Assert.Equal("microsoft.msixsdk", manifest.Id); - Assert.Equal("1.7.32", manifest.Version); - - // Default locale - Assert.Equal("en-US", manifest.PackageLocale); - Assert.Equal("Microsoft", manifest.Publisher); - Assert.Equal("https://www.microsoft.com", manifest.PublisherUrl); - Assert.Equal("https://www.microsoft.com/support", manifest.PublisherSupportUrl); - Assert.Equal("https://www.microsoft.com/privacy", manifest.PrivacyUrl); - Assert.Equal("Microsoft", manifest.Author); - Assert.Equal("MSIX SDK", manifest.PackageName); - Assert.Equal("https://www.microsoft.com/msixsdk/home", manifest.PackageUrl); - Assert.Equal("MIT License", manifest.License); - Assert.Equal("https://www.microsoft.com/msixsdk/license", manifest.LicenseUrl); - Assert.Equal("Copyright Microsoft Corporation", manifest.Copyright); - Assert.Equal("https://www.microsoft.com/msixsdk/copyright", manifest.CopyrightUrl); - Assert.Equal("This is MSIX SDK", manifest.ShortDescription); - Assert.Equal("The MSIX SDK project is an effort to enable developers", manifest.Description); - Assert.Equal("msixsdk", manifest.Moniker); - Assert.Equal(2, manifest.Tags.Count); - Assert.Equal("appxsdk", manifest.Tags[0]); - Assert.Equal("msixsdk", manifest.Tags[1]); - - if (manifestVersion >= TestManifestVersion.V1_1_0) - { - Assert.Equal("Default release notes", manifest.ReleaseNotes); - Assert.Equal("https://DefaultReleaseNotes.net", manifest.ReleaseNotesUrl); - Assert.Single(manifest.Agreements); - Assert.Equal("DefaultLabel", manifest.Agreements[0].AgreementLabel); - Assert.Equal("DefaultText", manifest.Agreements[0].Agreement); - Assert.Equal("https://DefaultAgreementUrl.net", manifest.Agreements[0].AgreementUrl); - } - - if (manifestVersion >= TestManifestVersion.V1_6_0) - { - Assert.Equal("Default installation notes", manifest.InstallationNotes); - Assert.Equal("https://DefaultPurchaseUrl.com", manifest.PurchaseUrl); - - Assert.Single(manifest.Documentations); - ManifestDocumentation manifestDocumentation = manifest.Documentations[0]; - - Assert.Equal("Default document label", manifestDocumentation.DocumentLabel); - Assert.Equal("https://DefaultDocumentUrl.com", manifestDocumentation.DocumentUrl); - - Assert.Single(manifest.Icons); - ManifestIcon icon = manifest.Icons[0]; - - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123", icon.IconSha256); - Assert.Equal("default", icon.IconTheme); - Assert.Equal("https://testIcon", icon.IconUrl); - Assert.Equal("custom", icon.IconResolution); - Assert.Equal("ico", icon.IconFileType); - } - - // Default installer - Assert.Equal("en-US", manifest.InstallerLocale); - Assert.Equal(2, manifest.Platform.Count); - Assert.Equal("Windows.Desktop", manifest.Platform[0]); - Assert.Equal("Windows.Universal", manifest.Platform[1]); - Assert.Equal("10.0.0.0", manifest.MinimumOSVersion); - - Assert.Equal("zip", manifest.InstallerType); - Assert.Equal("machine", manifest.Scope); - Assert.Equal(3, manifest.InstallModes.Count); - Assert.Equal("interactive", manifest.InstallModes[0]); - Assert.Equal("silent", manifest.InstallModes[1]); - Assert.Equal("silentWithProgress", manifest.InstallModes[2]); - - var defaultSwitches = manifest.Switches; - Assert.Equal("/custom", defaultSwitches.Custom); - Assert.Equal("/silentwithprogress", defaultSwitches.SilentWithProgress); - Assert.Equal("/silence", defaultSwitches.Silent); - Assert.Equal("/interactive", defaultSwitches.Interactive); - Assert.Equal("/log=", defaultSwitches.Log); - Assert.Equal("/dir=", defaultSwitches.InstallLocation); - Assert.Equal("/upgrade", defaultSwitches.Upgrade); - - Assert.Equal(2, manifest.InstallerSuccessCodes.Count); - Assert.Equal(1, manifest.InstallerSuccessCodes[0]); - Assert.Equal(0x80070005, manifest.InstallerSuccessCodes[1]); - Assert.Equal("uninstallPrevious", manifest.UpgradeBehavior); - Assert.Equal(2, manifest.Commands.Count); - Assert.Equal("makemsix", manifest.Commands[0]); - Assert.Equal("makeappx", manifest.Commands[1]); - Assert.Equal(2, manifest.Protocols.Count); - Assert.Equal("protocol1", manifest.Protocols[0]); - Assert.Equal("protocol2", manifest.Protocols[1]); - Assert.Equal(4, manifest.FileExtensions.Count); - Assert.Equal("appx", manifest.FileExtensions[0]); - Assert.Equal("msix", manifest.FileExtensions[1]); - Assert.Equal("appxbundle", manifest.FileExtensions[2]); - Assert.Equal("msixbundle", manifest.FileExtensions[3]); - - Assert.Single(manifest.Dependencies.WindowsFeatures); - Assert.Equal("IIS", manifest.Dependencies.WindowsFeatures[0]); - Assert.Single(manifest.Dependencies.WindowsLibraries); - Assert.Equal("VC Runtime", manifest.Dependencies.WindowsLibraries[0]); - Assert.Single(manifest.Dependencies.PackageDependencies); - Assert.Equal("Microsoft.MsixSdkDep", manifest.Dependencies.PackageDependencies[0].PackageIdentifier); - Assert.Equal("1.0.0", manifest.Dependencies.PackageDependencies[0].MinimumVersion); - Assert.Single(manifest.Dependencies.ExternalDependencies); - Assert.Equal("Outside dependencies", manifest.Dependencies.ExternalDependencies[0]); - - Assert.Single(manifest.Capabilities); - Assert.Equal("internetClient", manifest.Capabilities[0]); - Assert.Single(manifest.RestrictedCapabilities); - Assert.Equal("runFullTrust", manifest.RestrictedCapabilities[0]); - Assert.Equal("Microsoft.DesktopAppInstaller_8wekyb3d8bbwe", manifest.PackageFamilyName); - Assert.Equal("{Foo}", manifest.ProductCode); - - if (manifestVersion >= TestManifestVersion.V1_1_0) - { - Assert.Equal("2021-01-01", manifest.ReleaseDate); - Assert.True(manifest.InstallerAbortsTerminal); - Assert.True(manifest.InstallLocationRequired); - Assert.True(manifest.RequireExplicitUpgrade); - Assert.Equal("elevatesSelf", manifest.ElevationRequirement); - Assert.Single(manifest.UnsupportedOSArchitectures); - Assert.Equal("arm", manifest.UnsupportedOSArchitectures[0]); - Assert.Single(manifest.AppsAndFeaturesEntries); - Assert.Equal("DisplayName", manifest.AppsAndFeaturesEntries[0].DisplayName); - Assert.Equal("DisplayVersion", manifest.AppsAndFeaturesEntries[0].DisplayVersion); - Assert.Equal("Publisher", manifest.AppsAndFeaturesEntries[0].Publisher); - Assert.Equal("ProductCode", manifest.AppsAndFeaturesEntries[0].ProductCode); - Assert.Equal("UpgradeCode", manifest.AppsAndFeaturesEntries[0].UpgradeCode); - Assert.Equal("exe", manifest.AppsAndFeaturesEntries[0].InstallerType); - Assert.Single(manifest.Markets.AllowedMarkets); - Assert.Equal("US", manifest.Markets.AllowedMarkets[0]); - Assert.Equal(2, manifest.ExpectedReturnCodes.Count); - Assert.Equal(2, manifest.ExpectedReturnCodes[0].InstallerReturnCode); - Assert.Equal("contactSupport", manifest.ExpectedReturnCodes[0].ReturnResponse); - } - - if (manifestVersion >= TestManifestVersion.V1_6_0) - { - Assert.Equal("msi", manifest.NestedInstallerType); - Assert.Single(manifest.NestedInstallerFiles); - InstallerNestedInstallerFile installerNestedInstallerFile = manifest.NestedInstallerFiles[0]; - Assert.Equal("RelativeFilePath", installerNestedInstallerFile.RelativeFilePath); - Assert.Equal("PortableCommandAlias", installerNestedInstallerFile.PortableCommandAlias); - - InstallerInstallationMetadata installerInstallationMetadata = manifest.InstallationMetadata; - Assert.Equal("%ProgramFiles%\\TestApp", installerInstallationMetadata.DefaultInstallLocation); - Assert.Single(installerInstallationMetadata.Files); - - ManifestInstallerFile installerFile = installerInstallationMetadata.Files[0]; - Assert.Equal("main.exe", installerFile.RelativeFilePath); - Assert.Equal("DisplayName", installerFile.DisplayName); - Assert.Equal("/arg", installerFile.InvocationParameter); - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installerFile.FileSha256); - - Assert.Single(manifest.UnsupportedArguments); - Assert.Equal("log", manifest.UnsupportedArguments[0]); - - Assert.Single(manifest.UnsupportedOSArchitectures); - Assert.Equal("arm", manifest.UnsupportedOSArchitectures[0]); - - Assert.True(manifest.DisplayInstallWarnings); - Assert.True(manifest.DownloadCommandProhibited); - - Assert.Equal("https://defaultReturnResponseUrl.com", manifest.ExpectedReturnCodes[0].ReturnResponseUrl); - } - - if (manifestVersion >= TestManifestVersion.V1_7_0) - { - Assert.Equal("/repair", defaultSwitches.Repair); - Assert.Equal("uninstaller", manifest.RepairBehavior); - } - - if (manifestVersion >= TestManifestVersion.V1_9_0) - { - Assert.True(manifest.ArchiveBinariesDependOnPath); - } - - if (manifestVersion >= TestManifestVersion.V1_10_0) - { - Assert.Equal("microsoftEntraId", manifest.Authentication.AuthenticationType); - Assert.Equal("DefaultResource", manifest.Authentication.MicrosoftEntraIdAuthenticationInfo.Resource); - Assert.Equal("DefaultScope", manifest.Authentication.MicrosoftEntraIdAuthenticationInfo.Scope); - } - - // Individual installers - if (manifestVersion >= TestManifestVersion.V1_12_0) - { - Assert.Equal(4, manifest.Installers.Count); - } - else - { - Assert.Equal(2, manifest.Installers.Count); - } - - ManifestInstaller installer1 = manifest.Installers[0]; - Assert.Equal("x86", installer1.Arch); - Assert.Equal("en-GB", installer1.InstallerLocale); - Assert.Single(installer1.Platform); - Assert.Equal("Windows.Desktop", installer1.Platform[0]); - Assert.Equal("10.0.1.0", installer1.MinimumOSVersion); - Assert.Equal("msix", installer1.InstallerType); - Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx86.msix", installer1.Url); - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer1.Sha256); - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer1.SignatureSha256); - Assert.Equal("user", installer1.Scope); - Assert.Single(installer1.InstallModes); - Assert.Equal("interactive", installer1.InstallModes[0]); - - var installer1Switches = installer1.Switches; - Assert.Equal("/c", installer1Switches.Custom); - Assert.Equal("/sp", installer1Switches.SilentWithProgress); - Assert.Equal("/s", installer1Switches.Silent); - Assert.Equal("/i", installer1Switches.Interactive); - Assert.Equal("/l=", installer1Switches.Log); - Assert.Equal("/d=", installer1Switches.InstallLocation); - Assert.Equal("/u", installer1Switches.Upgrade); - - Assert.Equal("install", installer1.UpgradeBehavior); - Assert.Equal(2, installer1.Commands.Count); - Assert.Equal("makemsixPreview", installer1.Commands[0]); - Assert.Equal("makeappxPreview", installer1.Commands[1]); - Assert.Equal(2, installer1.Protocols.Count); - Assert.Equal("protocol1preview", installer1.Protocols[0]); - Assert.Equal("protocol2preview", installer1.Protocols[1]); - Assert.Equal(4, installer1.FileExtensions.Count); - Assert.Equal("appxbundle", installer1.FileExtensions[0]); - Assert.Equal("msixbundle", installer1.FileExtensions[1]); - Assert.Equal("appx", installer1.FileExtensions[2]); - Assert.Equal("msix", installer1.FileExtensions[3]); - - Assert.Single(installer1.Dependencies.WindowsFeatures); - Assert.Equal("PreviewIIS", installer1.Dependencies.WindowsFeatures[0]); - Assert.Single(installer1.Dependencies.WindowsLibraries); - Assert.Equal("Preview VC Runtime", installer1.Dependencies.WindowsLibraries[0]); - Assert.Single(installer1.Dependencies.PackageDependencies); - Assert.Equal("Microsoft.MsixSdkDepPreview", installer1.Dependencies.PackageDependencies[0].PackageIdentifier); - Assert.Single(installer1.Dependencies.ExternalDependencies); - Assert.Equal("Preview Outside dependencies", installer1.Dependencies.ExternalDependencies[0]); - - Assert.Single(installer1.Capabilities); - Assert.Equal("internetClientPreview", installer1.Capabilities[0]); - Assert.Single(installer1.RestrictedCapabilities); - Assert.Equal("runFullTrustPreview", installer1.RestrictedCapabilities[0]); - Assert.Equal("Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe", installer1.PackageFamilyName); - - if (manifestVersion >= TestManifestVersion.V1_1_0) - { - Assert.Equal("2021-02-02", installer1.ReleaseDate); - Assert.True(!installer1.InstallerAbortsTerminal); - Assert.True(!installer1.InstallLocationRequired); - Assert.True(!installer1.RequireExplicitUpgrade); - Assert.Equal("elevationRequired", installer1.ElevationRequirement); - Assert.Single(installer1.UnsupportedOSArchitectures); - Assert.Equal("arm64", installer1.UnsupportedOSArchitectures[0]); - Assert.Single(installer1.Markets.ExcludedMarkets); - Assert.Equal("US", installer1.Markets.ExcludedMarkets[0]); - Assert.Single(installer1.ExpectedReturnCodes); - Assert.Equal(2, installer1.ExpectedReturnCodes[0].InstallerReturnCode); - Assert.Equal("contactSupport", installer1.ExpectedReturnCodes[0].ReturnResponse); - } - - ManifestInstaller installer2 = manifest.Installers[1]; - Assert.Equal("x64", installer2.Arch); - Assert.Equal("exe", installer2.InstallerType); - Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx64.exe", installer2.Url); - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer2.Sha256); - Assert.Equal("{Bar}", installer2.ProductCode); - - if (manifestVersion >= TestManifestVersion.V1_6_0) - { - Assert.Single(installer1.InstallationMetadata.Files); - ManifestInstallerFile installerFile2 = installer1.InstallationMetadata.Files[0]; - Assert.Equal("main2.exe", installerFile2.RelativeFilePath); - Assert.Equal("DisplayName2", installerFile2.DisplayName); - Assert.Equal("/arg2", installerFile2.InvocationParameter); - Assert.Equal("79D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installerFile2.FileSha256); - - Assert.Equal("msi", installer1.NestedInstallerType); - - InstallerNestedInstallerFile installerNestedInstallerFile2 = installer1.NestedInstallerFiles[0]; - Assert.Equal("RelativeFilePath2", installerNestedInstallerFile2.RelativeFilePath); - Assert.Equal("PortableCommandAlias2", installerNestedInstallerFile2.PortableCommandAlias); - - Assert.Single(installer1.UnsupportedArguments); - Assert.Equal("location", installer1.UnsupportedArguments[0]); - - Assert.True(installer1.DisplayInstallWarnings); - Assert.True(installer1.DownloadCommandProhibited); - - Assert.Equal("https://returnResponseUrl.com", installer1.ExpectedReturnCodes[0].ReturnResponseUrl); - } - - if (manifestVersion >= TestManifestVersion.V1_7_0) - { - Assert.Equal("/r", installer1Switches.Repair); - Assert.Equal("modify", installer1.RepairBehavior); - } - - if (manifestVersion >= TestManifestVersion.V1_9_0) - { - Assert.False(installer1.ArchiveBinariesDependOnPath); - Assert.Equal("fakeIdentifier", installer2.ProductId); - } - - if (manifestVersion >= TestManifestVersion.V1_10_0) - { - Assert.Equal("microsoftEntraId", installer1.Authentication.AuthenticationType); - Assert.Equal("Resource", installer1.Authentication.MicrosoftEntraIdAuthenticationInfo.Resource); - Assert.Equal("Scope", installer1.Authentication.MicrosoftEntraIdAuthenticationInfo.Scope); - } - - // Additional Localizations - Assert.Single(manifest.Localization); - ManifestLocalization localization1 = manifest.Localization[0]; - Assert.Equal("en-GB", localization1.PackageLocale); - Assert.Equal("Microsoft UK", localization1.Publisher); - Assert.Equal("https://www.microsoft.com/UK", localization1.PublisherUrl); - Assert.Equal("https://www.microsoft.com/support/UK", localization1.PublisherSupportUrl); - Assert.Equal("https://www.microsoft.com/privacy/UK", localization1.PrivacyUrl); - Assert.Equal("Microsoft UK", localization1.Author); - Assert.Equal("MSIX SDK UK", localization1.PackageName); - Assert.Equal("https://www.microsoft.com/msixsdk/home/UK", localization1.PackageUrl); - Assert.Equal("MIT License UK", localization1.License); - Assert.Equal("https://www.microsoft.com/msixsdk/license/UK", localization1.LicenseUrl); - Assert.Equal("Copyright Microsoft Corporation UK", localization1.Copyright); - Assert.Equal("https://www.microsoft.com/msixsdk/copyright/UK", localization1.CopyrightUrl); - Assert.Equal("This is MSIX SDK UK", localization1.ShortDescription); - Assert.Equal("The MSIX SDK project is an effort to enable developers UK", localization1.Description); - Assert.Equal(2, localization1.Tags.Count); - Assert.Equal("appxsdkUK", localization1.Tags[0]); - Assert.Equal("msixsdkUK", localization1.Tags[1]); - - if (manifestVersion >= TestManifestVersion.V1_1_0) - { - Assert.Equal("Release notes", localization1.ReleaseNotes); - Assert.Equal("https://ReleaseNotes.net", localization1.ReleaseNotesUrl); - Assert.Single(localization1.Agreements); - Assert.Equal("Label", localization1.Agreements[0].AgreementLabel); - Assert.Equal("Text", localization1.Agreements[0].Agreement); - Assert.Equal("https://AgreementUrl.net", localization1.Agreements[0].AgreementUrl); - } - - if (manifestVersion >= TestManifestVersion.V1_6_0) - { - Assert.Equal("Installation notes", localization1.InstallationNotes); - Assert.Equal("https://PurchaseUrl.com", localization1.PurchaseUrl); - - Assert.Single(localization1.Documentations); - ManifestDocumentation manifestDocumentation = localization1.Documentations[0]; - - Assert.Equal("Document label", manifestDocumentation.DocumentLabel); - Assert.Equal("https://DocumentUrl.com", manifestDocumentation.DocumentUrl); - - Assert.Single(localization1.Icons); - ManifestIcon icon = localization1.Icons[0]; - - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321", icon.IconSha256); - Assert.Equal("dark", icon.IconTheme); - Assert.Equal("https://testIcon2", icon.IconUrl); - Assert.Equal("32x32", icon.IconResolution); - Assert.Equal("png", icon.IconFileType); - } - - if (manifestVersion >= TestManifestVersion.V1_12_0) - { - // Manifest v12 adds support for the Font installer type and NestedInstallerType, - // with two additional installers added to the merged manifest to verify these. - ManifestInstaller installer3 = manifest.Installers[2]; - Assert.Equal("neutral", installer3.Arch); - Assert.Equal("zip", installer3.InstallerType); - Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx64.exe", installer3.Url); - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer3.Sha256); - Assert.Equal("font", installer3.NestedInstallerType); - Assert.Equal(5, installer3.NestedInstallerFiles.Count); - Assert.Equal("relativeFilePath1.otf", installer3.NestedInstallerFiles[0].RelativeFilePath); - Assert.Equal("relativeFilePath2.ttf", installer3.NestedInstallerFiles[1].RelativeFilePath); - Assert.Equal("relativeFilePath3.fnt", installer3.NestedInstallerFiles[2].RelativeFilePath); - Assert.Equal("relativeFilePath4.ttc", installer3.NestedInstallerFiles[3].RelativeFilePath); - Assert.Equal("relativeFilePath5.otc", installer3.NestedInstallerFiles[4].RelativeFilePath); - - ManifestInstaller installer4 = manifest.Installers[3]; - Assert.Equal("neutral", installer4.Arch); - Assert.Equal("font", installer4.InstallerType); - Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx64.exe", installer4.Url); - Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer4.Sha256); - } - - if (manifestVersion >= TestManifestVersion.V1_28_0) - { - // Root level DesiredStateConfiguration - Assert.NotNull(manifest.DesiredStateConfiguration); - Assert.Single(manifest.DesiredStateConfiguration.PowerShell); - Assert.Equal("https://www.powershellgallery.com/api/v2", manifest.DesiredStateConfiguration.PowerShell[0].RepositoryUrl); - Assert.Equal("DefaultTestModule", manifest.DesiredStateConfiguration.PowerShell[0].ModuleName); - Assert.Single(manifest.DesiredStateConfiguration.PowerShell[0].Resources); - Assert.Equal("DefaultTestResource", manifest.DesiredStateConfiguration.PowerShell[0].Resources[0].Name); - Assert.NotNull(manifest.DesiredStateConfiguration.DSCv3); - Assert.Single(manifest.DesiredStateConfiguration.DSCv3.Resources); - Assert.Equal("DefaultPublisher.DefaultProduct/DefaultResource", manifest.DesiredStateConfiguration.DSCv3.Resources[0].Type); - - // Installer level DesiredStateConfiguration - Assert.NotNull(installer1.DesiredStateConfiguration); - Assert.Single(installer1.DesiredStateConfiguration.PowerShell); - Assert.Equal("https://www.powershellgallery.com/api/v2", installer1.DesiredStateConfiguration.PowerShell[0].RepositoryUrl); - Assert.Equal("TestModule", installer1.DesiredStateConfiguration.PowerShell[0].ModuleName); - Assert.Single(installer1.DesiredStateConfiguration.PowerShell[0].Resources); - Assert.Equal("TestResource", installer1.DesiredStateConfiguration.PowerShell[0].Resources[0].Name); - Assert.NotNull(installer1.DesiredStateConfiguration.DSCv3); - Assert.Single(installer1.DesiredStateConfiguration.DSCv3.Resources); - Assert.Equal("TestPublisher.TestProduct/TestResource", installer1.DesiredStateConfiguration.DSCv3.Resources[0].Type); - } - } - - /// - /// Helper class with manifest strings. - /// - internal class ManifestStrings - { -#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore - /// - /// Merged v1 manifest. - /// - public const string V1_0_0ManifestMerged = "V1ManifestMerged.yaml"; - - /// - /// Merged v1.1 manifest. - /// - public const string V1_1_0ManifestMerged = "V1_1ManifestMerged.yaml"; - - /// - /// Merged v1.6 manifest. - /// - public const string V1_6_0ManifestMerged = "V1_6ManifestMerged.yaml"; - - /// - /// Merged v1.7 manifest. - /// - public const string V1_7_0ManifestMerged = "V1_7ManifestMerged.yaml"; - - /// - /// Merged v1.9 manifest. - /// - public const string V1_9_0ManifestMerged = "V1_9ManifestMerged.yaml"; - - /// - /// Merged v1.10 manifest. - /// - public const string V1_10_0ManifestMerged = "V1_10ManifestMerged.yaml"; - - /// - /// Merged v1.12 manifest. - /// - public const string V1_12_0ManifestMerged = "V1_12ManifestMerged.yaml"; - - /// - /// Merged v1.28 manifest. - /// - public const string V1_28_0ManifestMerged = "V1_28ManifestMerged.yaml"; -#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore - - /// - /// Merged v1 manifest without localization. - /// - public const string V1ManifestNoLocalization = "V1ManifestNoLocalization.yaml"; - - /// - /// Merged v1 manifest missing required PackageLocale. - /// - public const string V1ManifestInfoMissingRequiredPackageLocale = "V1ManifestInfoMissingRequiredPackageLocale.yaml"; - } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace WinGetUtilInterop.UnitTests.ManifestUnitTest +{ + using System.IO; + using System.Reflection; + using Microsoft.WinGetUtil.Models.V1; + using Microsoft.WinGetUtil.UnitTests.Common.Logging; + using Xunit; + using Xunit.Abstractions; + + /// + /// Manifest equality tests. + /// + public class V1ManifestReadTest + { + private ITestOutputHelper log; + + /// + /// Initializes a new instance of the class. + /// + /// Output Helper. + public V1ManifestReadTest(ITestOutputHelper log) + { + this.log = log; + } + + private enum TestManifestVersion + { + V1_0_0, + V1_1_0, + V1_6_0, + V1_7_0, + V1_9_0, + V1_10_0, + V1_12_0, + V1_28_0, + } + + /// + /// Read v1 manifest. + /// + [Fact] + [DisplayTestMethodName] + public void ReadV1ManifestsAndVerifyContents() + { + Manifest v1_0_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_0_0ManifestMerged)); + + this.ValidateManifestFields(v1_0_0manifest, TestManifestVersion.V1_0_0); + + Manifest v1_1_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_1_0ManifestMerged)); + + this.ValidateManifestFields(v1_1_0manifest, TestManifestVersion.V1_1_0); + + Manifest v1_6_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_6_0ManifestMerged)); + + this.ValidateManifestFields(v1_6_0manifest, TestManifestVersion.V1_6_0); + + Manifest v1_7_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_7_0ManifestMerged)); + + this.ValidateManifestFields(v1_7_0manifest, TestManifestVersion.V1_7_0); + + Manifest v1_9_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_9_0ManifestMerged)); + + this.ValidateManifestFields(v1_9_0manifest, TestManifestVersion.V1_9_0); + + Manifest v1_10_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_10_0ManifestMerged)); + + this.ValidateManifestFields(v1_10_0manifest, TestManifestVersion.V1_10_0); + + Manifest v1_12_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_12_0ManifestMerged)); + + this.ValidateManifestFields(v1_12_0manifest, TestManifestVersion.V1_12_0); + + Manifest v1_28_0manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1_28_0ManifestMerged)); + + this.ValidateManifestFields(v1_28_0manifest, TestManifestVersion.V1_28_0); + } + + /// + /// Read v1 manifest without additional localization. + /// + [Fact] + [DisplayTestMethodName] + public void ReadV1ManifestNoLocalization() + { + Manifest v1manifest = Manifest.CreateManifestFromPath( + Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "TestCollateral", ManifestStrings.V1ManifestNoLocalization)); + + // Calling GetURIs() on manifest without localization should not fail. + v1manifest.GetURIs(); + } + + /// + /// Read bad min manifest info. + /// + [Fact] + [DisplayTestMethodName] + public void ReadV1LocaleManifestInfoNoPackageLocale() + { + Assert.Throws( + () => + { + _ = MinManifestInfo.CreateManifestInfoFromPath( + Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "TestCollateral", + ManifestStrings.V1ManifestInfoMissingRequiredPackageLocale)); + }); + } + + private void ValidateManifestFields(Manifest manifest, TestManifestVersion manifestVersion) + { + Assert.Equal("microsoft.msixsdk", manifest.Id); + Assert.Equal("1.7.32", manifest.Version); + + // Default locale + Assert.Equal("en-US", manifest.PackageLocale); + Assert.Equal("Microsoft", manifest.Publisher); + Assert.Equal("https://www.microsoft.com", manifest.PublisherUrl); + Assert.Equal("https://www.microsoft.com/support", manifest.PublisherSupportUrl); + Assert.Equal("https://www.microsoft.com/privacy", manifest.PrivacyUrl); + Assert.Equal("Microsoft", manifest.Author); + Assert.Equal("MSIX SDK", manifest.PackageName); + Assert.Equal("https://www.microsoft.com/msixsdk/home", manifest.PackageUrl); + Assert.Equal("MIT License", manifest.License); + Assert.Equal("https://www.microsoft.com/msixsdk/license", manifest.LicenseUrl); + Assert.Equal("Copyright Microsoft Corporation", manifest.Copyright); + Assert.Equal("https://www.microsoft.com/msixsdk/copyright", manifest.CopyrightUrl); + Assert.Equal("This is MSIX SDK", manifest.ShortDescription); + Assert.Equal("The MSIX SDK project is an effort to enable developers", manifest.Description); + Assert.Equal("msixsdk", manifest.Moniker); + Assert.Equal(2, manifest.Tags.Count); + Assert.Equal("appxsdk", manifest.Tags[0]); + Assert.Equal("msixsdk", manifest.Tags[1]); + + if (manifestVersion >= TestManifestVersion.V1_1_0) + { + Assert.Equal("Default release notes", manifest.ReleaseNotes); + Assert.Equal("https://DefaultReleaseNotes.net", manifest.ReleaseNotesUrl); + Assert.Single(manifest.Agreements); + Assert.Equal("DefaultLabel", manifest.Agreements[0].AgreementLabel); + Assert.Equal("DefaultText", manifest.Agreements[0].Agreement); + Assert.Equal("https://DefaultAgreementUrl.net", manifest.Agreements[0].AgreementUrl); + } + + if (manifestVersion >= TestManifestVersion.V1_6_0) + { + Assert.Equal("Default installation notes", manifest.InstallationNotes); + Assert.Equal("https://DefaultPurchaseUrl.com", manifest.PurchaseUrl); + + Assert.Single(manifest.Documentations); + ManifestDocumentation manifestDocumentation = manifest.Documentations[0]; + + Assert.Equal("Default document label", manifestDocumentation.DocumentLabel); + Assert.Equal("https://DefaultDocumentUrl.com", manifestDocumentation.DocumentUrl); + + Assert.Single(manifest.Icons); + ManifestIcon icon = manifest.Icons[0]; + + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8123", icon.IconSha256); + Assert.Equal("default", icon.IconTheme); + Assert.Equal("https://testIcon", icon.IconUrl); + Assert.Equal("custom", icon.IconResolution); + Assert.Equal("ico", icon.IconFileType); + } + + // Default installer + Assert.Equal("en-US", manifest.InstallerLocale); + Assert.Equal(2, manifest.Platform.Count); + Assert.Equal("Windows.Desktop", manifest.Platform[0]); + Assert.Equal("Windows.Universal", manifest.Platform[1]); + Assert.Equal("10.0.0.0", manifest.MinimumOSVersion); + + Assert.Equal("zip", manifest.InstallerType); + Assert.Equal("machine", manifest.Scope); + Assert.Equal(3, manifest.InstallModes.Count); + Assert.Equal("interactive", manifest.InstallModes[0]); + Assert.Equal("silent", manifest.InstallModes[1]); + Assert.Equal("silentWithProgress", manifest.InstallModes[2]); + + var defaultSwitches = manifest.Switches; + Assert.Equal("/custom", defaultSwitches.Custom); + Assert.Equal("/silentwithprogress", defaultSwitches.SilentWithProgress); + Assert.Equal("/silence", defaultSwitches.Silent); + Assert.Equal("/interactive", defaultSwitches.Interactive); + Assert.Equal("/log=", defaultSwitches.Log); + Assert.Equal("/dir=", defaultSwitches.InstallLocation); + Assert.Equal("/upgrade", defaultSwitches.Upgrade); + + Assert.Equal(2, manifest.InstallerSuccessCodes.Count); + Assert.Equal(1, manifest.InstallerSuccessCodes[0]); + Assert.Equal(0x80070005, manifest.InstallerSuccessCodes[1]); + Assert.Equal("uninstallPrevious", manifest.UpgradeBehavior); + Assert.Equal(2, manifest.Commands.Count); + Assert.Equal("makemsix", manifest.Commands[0]); + Assert.Equal("makeappx", manifest.Commands[1]); + Assert.Equal(2, manifest.Protocols.Count); + Assert.Equal("protocol1", manifest.Protocols[0]); + Assert.Equal("protocol2", manifest.Protocols[1]); + Assert.Equal(4, manifest.FileExtensions.Count); + Assert.Equal("appx", manifest.FileExtensions[0]); + Assert.Equal("msix", manifest.FileExtensions[1]); + Assert.Equal("appxbundle", manifest.FileExtensions[2]); + Assert.Equal("msixbundle", manifest.FileExtensions[3]); + + Assert.Single(manifest.Dependencies.WindowsFeatures); + Assert.Equal("IIS", manifest.Dependencies.WindowsFeatures[0]); + Assert.Single(manifest.Dependencies.WindowsLibraries); + Assert.Equal("VC Runtime", manifest.Dependencies.WindowsLibraries[0]); + Assert.Single(manifest.Dependencies.PackageDependencies); + Assert.Equal("Microsoft.MsixSdkDep", manifest.Dependencies.PackageDependencies[0].PackageIdentifier); + Assert.Equal("1.0.0", manifest.Dependencies.PackageDependencies[0].MinimumVersion); + Assert.Single(manifest.Dependencies.ExternalDependencies); + Assert.Equal("Outside dependencies", manifest.Dependencies.ExternalDependencies[0]); + + Assert.Single(manifest.Capabilities); + Assert.Equal("internetClient", manifest.Capabilities[0]); + Assert.Single(manifest.RestrictedCapabilities); + Assert.Equal("runFullTrust", manifest.RestrictedCapabilities[0]); + Assert.Equal("Microsoft.DesktopAppInstaller_8wekyb3d8bbwe", manifest.PackageFamilyName); + Assert.Equal("{Foo}", manifest.ProductCode); + + if (manifestVersion >= TestManifestVersion.V1_1_0) + { + Assert.Equal("2021-01-01", manifest.ReleaseDate); + Assert.True(manifest.InstallerAbortsTerminal); + Assert.True(manifest.InstallLocationRequired); + Assert.True(manifest.RequireExplicitUpgrade); + Assert.Equal("elevatesSelf", manifest.ElevationRequirement); + Assert.Single(manifest.UnsupportedOSArchitectures); + Assert.Equal("arm", manifest.UnsupportedOSArchitectures[0]); + Assert.Single(manifest.AppsAndFeaturesEntries); + Assert.Equal("DisplayName", manifest.AppsAndFeaturesEntries[0].DisplayName); + Assert.Equal("DisplayVersion", manifest.AppsAndFeaturesEntries[0].DisplayVersion); + Assert.Equal("Publisher", manifest.AppsAndFeaturesEntries[0].Publisher); + Assert.Equal("ProductCode", manifest.AppsAndFeaturesEntries[0].ProductCode); + Assert.Equal("UpgradeCode", manifest.AppsAndFeaturesEntries[0].UpgradeCode); + Assert.Equal("exe", manifest.AppsAndFeaturesEntries[0].InstallerType); + Assert.Single(manifest.Markets.AllowedMarkets); + Assert.Equal("US", manifest.Markets.AllowedMarkets[0]); + Assert.Equal(2, manifest.ExpectedReturnCodes.Count); + Assert.Equal(2, manifest.ExpectedReturnCodes[0].InstallerReturnCode); + Assert.Equal("contactSupport", manifest.ExpectedReturnCodes[0].ReturnResponse); + } + + if (manifestVersion >= TestManifestVersion.V1_6_0) + { + Assert.Equal("msi", manifest.NestedInstallerType); + Assert.Single(manifest.NestedInstallerFiles); + InstallerNestedInstallerFile installerNestedInstallerFile = manifest.NestedInstallerFiles[0]; + Assert.Equal("RelativeFilePath", installerNestedInstallerFile.RelativeFilePath); + Assert.Equal("PortableCommandAlias", installerNestedInstallerFile.PortableCommandAlias); + + InstallerInstallationMetadata installerInstallationMetadata = manifest.InstallationMetadata; + Assert.Equal("%ProgramFiles%\\TestApp", installerInstallationMetadata.DefaultInstallLocation); + Assert.Single(installerInstallationMetadata.Files); + + ManifestInstallerFile installerFile = installerInstallationMetadata.Files[0]; + Assert.Equal("main.exe", installerFile.RelativeFilePath); + Assert.Equal("DisplayName", installerFile.DisplayName); + Assert.Equal("/arg", installerFile.InvocationParameter); + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installerFile.FileSha256); + + Assert.Single(manifest.UnsupportedArguments); + Assert.Equal("log", manifest.UnsupportedArguments[0]); + + Assert.Single(manifest.UnsupportedOSArchitectures); + Assert.Equal("arm", manifest.UnsupportedOSArchitectures[0]); + + Assert.True(manifest.DisplayInstallWarnings); + Assert.True(manifest.DownloadCommandProhibited); + + Assert.Equal("https://defaultReturnResponseUrl.com", manifest.ExpectedReturnCodes[0].ReturnResponseUrl); + } + + if (manifestVersion >= TestManifestVersion.V1_7_0) + { + Assert.Equal("/repair", defaultSwitches.Repair); + Assert.Equal("uninstaller", manifest.RepairBehavior); + } + + if (manifestVersion >= TestManifestVersion.V1_9_0) + { + Assert.True(manifest.ArchiveBinariesDependOnPath); + } + + if (manifestVersion >= TestManifestVersion.V1_10_0) + { + Assert.Equal("microsoftEntraId", manifest.Authentication.AuthenticationType); + Assert.Equal("DefaultResource", manifest.Authentication.MicrosoftEntraIdAuthenticationInfo.Resource); + Assert.Equal("DefaultScope", manifest.Authentication.MicrosoftEntraIdAuthenticationInfo.Scope); + } + + // Individual installers + if (manifestVersion >= TestManifestVersion.V1_12_0) + { + Assert.Equal(4, manifest.Installers.Count); + } + else + { + Assert.Equal(2, manifest.Installers.Count); + } + + ManifestInstaller installer1 = manifest.Installers[0]; + Assert.Equal("x86", installer1.Arch); + Assert.Equal("en-GB", installer1.InstallerLocale); + Assert.Single(installer1.Platform); + Assert.Equal("Windows.Desktop", installer1.Platform[0]); + Assert.Equal("10.0.1.0", installer1.MinimumOSVersion); + Assert.Equal("msix", installer1.InstallerType); + Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx86.msix", installer1.Url); + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer1.Sha256); + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer1.SignatureSha256); + Assert.Equal("user", installer1.Scope); + Assert.Single(installer1.InstallModes); + Assert.Equal("interactive", installer1.InstallModes[0]); + + var installer1Switches = installer1.Switches; + Assert.Equal("/c", installer1Switches.Custom); + Assert.Equal("/sp", installer1Switches.SilentWithProgress); + Assert.Equal("/s", installer1Switches.Silent); + Assert.Equal("/i", installer1Switches.Interactive); + Assert.Equal("/l=", installer1Switches.Log); + Assert.Equal("/d=", installer1Switches.InstallLocation); + Assert.Equal("/u", installer1Switches.Upgrade); + + Assert.Equal("install", installer1.UpgradeBehavior); + Assert.Equal(2, installer1.Commands.Count); + Assert.Equal("makemsixPreview", installer1.Commands[0]); + Assert.Equal("makeappxPreview", installer1.Commands[1]); + Assert.Equal(2, installer1.Protocols.Count); + Assert.Equal("protocol1preview", installer1.Protocols[0]); + Assert.Equal("protocol2preview", installer1.Protocols[1]); + Assert.Equal(4, installer1.FileExtensions.Count); + Assert.Equal("appxbundle", installer1.FileExtensions[0]); + Assert.Equal("msixbundle", installer1.FileExtensions[1]); + Assert.Equal("appx", installer1.FileExtensions[2]); + Assert.Equal("msix", installer1.FileExtensions[3]); + + Assert.Single(installer1.Dependencies.WindowsFeatures); + Assert.Equal("PreviewIIS", installer1.Dependencies.WindowsFeatures[0]); + Assert.Single(installer1.Dependencies.WindowsLibraries); + Assert.Equal("Preview VC Runtime", installer1.Dependencies.WindowsLibraries[0]); + Assert.Single(installer1.Dependencies.PackageDependencies); + Assert.Equal("Microsoft.MsixSdkDepPreview", installer1.Dependencies.PackageDependencies[0].PackageIdentifier); + Assert.Single(installer1.Dependencies.ExternalDependencies); + Assert.Equal("Preview Outside dependencies", installer1.Dependencies.ExternalDependencies[0]); + + Assert.Single(installer1.Capabilities); + Assert.Equal("internetClientPreview", installer1.Capabilities[0]); + Assert.Single(installer1.RestrictedCapabilities); + Assert.Equal("runFullTrustPreview", installer1.RestrictedCapabilities[0]); + Assert.Equal("Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe", installer1.PackageFamilyName); + + if (manifestVersion >= TestManifestVersion.V1_1_0) + { + Assert.Equal("2021-02-02", installer1.ReleaseDate); + Assert.True(!installer1.InstallerAbortsTerminal); + Assert.True(!installer1.InstallLocationRequired); + Assert.True(!installer1.RequireExplicitUpgrade); + Assert.Equal("elevationRequired", installer1.ElevationRequirement); + Assert.Single(installer1.UnsupportedOSArchitectures); + Assert.Equal("arm64", installer1.UnsupportedOSArchitectures[0]); + Assert.Single(installer1.Markets.ExcludedMarkets); + Assert.Equal("US", installer1.Markets.ExcludedMarkets[0]); + Assert.Single(installer1.ExpectedReturnCodes); + Assert.Equal(2, installer1.ExpectedReturnCodes[0].InstallerReturnCode); + Assert.Equal("contactSupport", installer1.ExpectedReturnCodes[0].ReturnResponse); + } + + ManifestInstaller installer2 = manifest.Installers[1]; + Assert.Equal("x64", installer2.Arch); + Assert.Equal("exe", installer2.InstallerType); + Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx64.exe", installer2.Url); + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer2.Sha256); + Assert.Equal("{Bar}", installer2.ProductCode); + + if (manifestVersion >= TestManifestVersion.V1_6_0) + { + Assert.Single(installer1.InstallationMetadata.Files); + ManifestInstallerFile installerFile2 = installer1.InstallationMetadata.Files[0]; + Assert.Equal("main2.exe", installerFile2.RelativeFilePath); + Assert.Equal("DisplayName2", installerFile2.DisplayName); + Assert.Equal("/arg2", installerFile2.InvocationParameter); + Assert.Equal("79D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installerFile2.FileSha256); + + Assert.Equal("msi", installer1.NestedInstallerType); + + InstallerNestedInstallerFile installerNestedInstallerFile2 = installer1.NestedInstallerFiles[0]; + Assert.Equal("RelativeFilePath2", installerNestedInstallerFile2.RelativeFilePath); + Assert.Equal("PortableCommandAlias2", installerNestedInstallerFile2.PortableCommandAlias); + + Assert.Single(installer1.UnsupportedArguments); + Assert.Equal("location", installer1.UnsupportedArguments[0]); + + Assert.True(installer1.DisplayInstallWarnings); + Assert.True(installer1.DownloadCommandProhibited); + + Assert.Equal("https://returnResponseUrl.com", installer1.ExpectedReturnCodes[0].ReturnResponseUrl); + } + + if (manifestVersion >= TestManifestVersion.V1_7_0) + { + Assert.Equal("/r", installer1Switches.Repair); + Assert.Equal("modify", installer1.RepairBehavior); + } + + if (manifestVersion >= TestManifestVersion.V1_9_0) + { + Assert.False(installer1.ArchiveBinariesDependOnPath); + Assert.Equal("fakeIdentifier", installer2.ProductId); + } + + if (manifestVersion >= TestManifestVersion.V1_10_0) + { + Assert.Equal("microsoftEntraId", installer1.Authentication.AuthenticationType); + Assert.Equal("Resource", installer1.Authentication.MicrosoftEntraIdAuthenticationInfo.Resource); + Assert.Equal("Scope", installer1.Authentication.MicrosoftEntraIdAuthenticationInfo.Scope); + } + + // Additional Localizations + Assert.Single(manifest.Localization); + ManifestLocalization localization1 = manifest.Localization[0]; + Assert.Equal("en-GB", localization1.PackageLocale); + Assert.Equal("Microsoft UK", localization1.Publisher); + Assert.Equal("https://www.microsoft.com/UK", localization1.PublisherUrl); + Assert.Equal("https://www.microsoft.com/support/UK", localization1.PublisherSupportUrl); + Assert.Equal("https://www.microsoft.com/privacy/UK", localization1.PrivacyUrl); + Assert.Equal("Microsoft UK", localization1.Author); + Assert.Equal("MSIX SDK UK", localization1.PackageName); + Assert.Equal("https://www.microsoft.com/msixsdk/home/UK", localization1.PackageUrl); + Assert.Equal("MIT License UK", localization1.License); + Assert.Equal("https://www.microsoft.com/msixsdk/license/UK", localization1.LicenseUrl); + Assert.Equal("Copyright Microsoft Corporation UK", localization1.Copyright); + Assert.Equal("https://www.microsoft.com/msixsdk/copyright/UK", localization1.CopyrightUrl); + Assert.Equal("This is MSIX SDK UK", localization1.ShortDescription); + Assert.Equal("The MSIX SDK project is an effort to enable developers UK", localization1.Description); + Assert.Equal(2, localization1.Tags.Count); + Assert.Equal("appxsdkUK", localization1.Tags[0]); + Assert.Equal("msixsdkUK", localization1.Tags[1]); + + if (manifestVersion >= TestManifestVersion.V1_1_0) + { + Assert.Equal("Release notes", localization1.ReleaseNotes); + Assert.Equal("https://ReleaseNotes.net", localization1.ReleaseNotesUrl); + Assert.Single(localization1.Agreements); + Assert.Equal("Label", localization1.Agreements[0].AgreementLabel); + Assert.Equal("Text", localization1.Agreements[0].Agreement); + Assert.Equal("https://AgreementUrl.net", localization1.Agreements[0].AgreementUrl); + } + + if (manifestVersion >= TestManifestVersion.V1_6_0) + { + Assert.Equal("Installation notes", localization1.InstallationNotes); + Assert.Equal("https://PurchaseUrl.com", localization1.PurchaseUrl); + + Assert.Single(localization1.Documentations); + ManifestDocumentation manifestDocumentation = localization1.Documentations[0]; + + Assert.Equal("Document label", manifestDocumentation.DocumentLabel); + Assert.Equal("https://DocumentUrl.com", manifestDocumentation.DocumentUrl); + + Assert.Single(localization1.Icons); + ManifestIcon icon = localization1.Icons[0]; + + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8321", icon.IconSha256); + Assert.Equal("dark", icon.IconTheme); + Assert.Equal("https://testIcon2", icon.IconUrl); + Assert.Equal("32x32", icon.IconResolution); + Assert.Equal("png", icon.IconFileType); + } + + if (manifestVersion >= TestManifestVersion.V1_12_0) + { + // Manifest v12 adds support for the Font installer type and NestedInstallerType, + // with two additional installers added to the merged manifest to verify these. + ManifestInstaller installer3 = manifest.Installers[2]; + Assert.Equal("neutral", installer3.Arch); + Assert.Equal("zip", installer3.InstallerType); + Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx64.exe", installer3.Url); + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer3.Sha256); + Assert.Equal("font", installer3.NestedInstallerType); + Assert.Equal(5, installer3.NestedInstallerFiles.Count); + Assert.Equal("relativeFilePath1.otf", installer3.NestedInstallerFiles[0].RelativeFilePath); + Assert.Equal("relativeFilePath2.ttf", installer3.NestedInstallerFiles[1].RelativeFilePath); + Assert.Equal("relativeFilePath3.fnt", installer3.NestedInstallerFiles[2].RelativeFilePath); + Assert.Equal("relativeFilePath4.ttc", installer3.NestedInstallerFiles[3].RelativeFilePath); + Assert.Equal("relativeFilePath5.otc", installer3.NestedInstallerFiles[4].RelativeFilePath); + + ManifestInstaller installer4 = manifest.Installers[3]; + Assert.Equal("neutral", installer4.Arch); + Assert.Equal("font", installer4.InstallerType); + Assert.Equal("https://www.microsoft.com/msixsdk/msixsdkx64.exe", installer4.Url); + Assert.Equal("69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82", installer4.Sha256); + } + + if (manifestVersion >= TestManifestVersion.V1_28_0) + { + // Root level DesiredStateConfiguration + Assert.NotNull(manifest.DesiredStateConfiguration); + Assert.Single(manifest.DesiredStateConfiguration.PowerShell); + Assert.Equal("https://www.powershellgallery.com/api/v2", manifest.DesiredStateConfiguration.PowerShell[0].RepositoryUrl); + Assert.Equal("DefaultTestModule", manifest.DesiredStateConfiguration.PowerShell[0].ModuleName); + Assert.Single(manifest.DesiredStateConfiguration.PowerShell[0].Resources); + Assert.Equal("DefaultTestResource", manifest.DesiredStateConfiguration.PowerShell[0].Resources[0].Name); + Assert.NotNull(manifest.DesiredStateConfiguration.DSCv3); + Assert.Single(manifest.DesiredStateConfiguration.DSCv3.Resources); + Assert.Equal("DefaultPublisher.DefaultProduct/DefaultResource", manifest.DesiredStateConfiguration.DSCv3.Resources[0].Type); + + // Installer level DesiredStateConfiguration + Assert.NotNull(installer1.DesiredStateConfiguration); + Assert.Single(installer1.DesiredStateConfiguration.PowerShell); + Assert.Equal("https://www.powershellgallery.com/api/v2", installer1.DesiredStateConfiguration.PowerShell[0].RepositoryUrl); + Assert.Equal("TestModule", installer1.DesiredStateConfiguration.PowerShell[0].ModuleName); + Assert.Single(installer1.DesiredStateConfiguration.PowerShell[0].Resources); + Assert.Equal("TestResource", installer1.DesiredStateConfiguration.PowerShell[0].Resources[0].Name); + Assert.NotNull(installer1.DesiredStateConfiguration.DSCv3); + Assert.Single(installer1.DesiredStateConfiguration.DSCv3.Resources); + Assert.Equal("TestPublisher.TestProduct/TestResource", installer1.DesiredStateConfiguration.DSCv3.Resources[0].Type); + } + } + + /// + /// Helper class with manifest strings. + /// + internal class ManifestStrings + { +#pragma warning disable SA1310 // FieldNamesMustNotContainUnderscore + /// + /// Merged v1 manifest. + /// + public const string V1_0_0ManifestMerged = "V1ManifestMerged.yaml"; + + /// + /// Merged v1.1 manifest. + /// + public const string V1_1_0ManifestMerged = "V1_1ManifestMerged.yaml"; + + /// + /// Merged v1.6 manifest. + /// + public const string V1_6_0ManifestMerged = "V1_6ManifestMerged.yaml"; + + /// + /// Merged v1.7 manifest. + /// + public const string V1_7_0ManifestMerged = "V1_7ManifestMerged.yaml"; + + /// + /// Merged v1.9 manifest. + /// + public const string V1_9_0ManifestMerged = "V1_9ManifestMerged.yaml"; + + /// + /// Merged v1.10 manifest. + /// + public const string V1_10_0ManifestMerged = "V1_10ManifestMerged.yaml"; + + /// + /// Merged v1.12 manifest. + /// + public const string V1_12_0ManifestMerged = "V1_12ManifestMerged.yaml"; + + /// + /// Merged v1.28 manifest. + /// + public const string V1_28_0ManifestMerged = "V1_28ManifestMerged.yaml"; +#pragma warning restore SA1310 // FieldNamesMustNotContainUnderscore + + /// + /// Merged v1 manifest without localization. + /// + public const string V1ManifestNoLocalization = "V1ManifestNoLocalization.yaml"; + + /// + /// Merged v1 manifest missing required PackageLocale. + /// + public const string V1ManifestInfoMissingRequiredPackageLocale = "V1ManifestInfoMissingRequiredPackageLocale.yaml"; + } + } +} diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEquality.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEquality.yaml index f7bb552f6b..7d44651f2b 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEquality.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEquality.yaml @@ -1,27 +1,27 @@ -Id: id.id -Version: 1.2.3 -Publisher: test -InstallerType: exe -Switches: - Custom: custom - Silent: /s - SilentWithProgress: /sp - Interactive: /i - Language: en-US - Log: /l= - InstallLocation: /d= -Installers: - - Arch: x86 - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Language: en-US - InstallerType: msix - Scope: user - Switches: - Language: /en-US - Custom: /s - - Arch: x64 - Url: https://contoso.com/publiccontainer/contosoinstaller64.exe - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 - Language: en-US +Id: id.id +Version: 1.2.3 +Publisher: test +InstallerType: exe +Switches: + Custom: custom + Silent: /s + SilentWithProgress: /sp + Interactive: /i + Language: en-US + Log: /l= + InstallLocation: /d= +Installers: + - Arch: x86 + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Language: en-US + InstallerType: msix + Scope: user + Switches: + Language: /en-US + Custom: /s + - Arch: x64 + Url: https://contoso.com/publiccontainer/contosoinstaller64.exe + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 + Language: en-US Scope: user \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEqualityWithDescription.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEqualityWithDescription.yaml index be070b9a0c..c066ff99ed 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEqualityWithDescription.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/AllEqualityWithDescription.yaml @@ -1,28 +1,28 @@ -Id: id.id -Version: 1.2.3 -Publisher: test -InstallerType: exe -Description: This is a description -Switches: - Custom: custom - Silent: /s - SilentWithProgress: /sp - Interactive: /i - Language: en-US - Log: /l= - InstallLocation: /d= -Installers: - - Arch: x86 - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Language: en-US - InstallerType: msix - Scope: user - Switches: - Language: /en-US - Custom: /s - - Arch: x64 - Url: https://contoso.com/publiccontainer/contosoinstaller64.exe - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 - Language: en-US +Id: id.id +Version: 1.2.3 +Publisher: test +InstallerType: exe +Description: This is a description +Switches: + Custom: custom + Silent: /s + SilentWithProgress: /sp + Interactive: /i + Language: en-US + Log: /l= + InstallLocation: /d= +Installers: + - Arch: x86 + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Language: en-US + InstallerType: msix + Scope: user + Switches: + Language: /en-US + Custom: /s + - Arch: x64 + Url: https://contoso.com/publiccontainer/contosoinstaller64.exe + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 + Language: en-US Scope: user \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/DifferentId.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/DifferentId.yaml index 8194a6265c..b5ee700b48 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/DifferentId.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/DifferentId.yaml @@ -1,27 +1,27 @@ -Id: id.id2 -Version: 1.2.3 -Publisher: test -InstallerType: exe -Switches: - Custom: custom - Silent: /s - SilentWithProgress: /sp - Interactive: /i - Language: en-US - Log: /l= - InstallLocation: /d= -Installers: - - Arch: x86 - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - Language: en-US - InstallerType: msix - Scope: user - Switches: - Language: /en-US - Custom: /s - - Arch: x64 - Url: https://contoso.com/publiccontainer/contosoinstaller64.exe - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 - Language: en-US +Id: id.id2 +Version: 1.2.3 +Publisher: test +InstallerType: exe +Switches: + Custom: custom + Silent: /s + SilentWithProgress: /sp + Interactive: /i + Language: en-US + Log: /l= + InstallLocation: /d= +Installers: + - Arch: x86 + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + Language: en-US + InstallerType: msix + Scope: user + Switches: + Language: /en-US + Custom: /s + - Arch: x64 + Url: https://contoso.com/publiccontainer/contosoinstaller64.exe + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 + Language: en-US Scope: user \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/ExpectedShadowManifest.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/ExpectedShadowManifest.yaml index d515749419..8008053537 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/ExpectedShadowManifest.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/ExpectedShadowManifest.yaml @@ -1,26 +1,26 @@ -PackageIdentifier: Package.package -PackageVersion: 1.0 -ManifestType: shadow -ManifestVersion: 1.5 -PackageLocale: en-US -Icons: -- IconUrl: iconUrl - IconFileType: fileType - IconResolution: iconResolution - IconTheme: iconTheme - IconSha256: iconSha256 -Localization: -- PackageLocale: es-MX - Icons: - - IconUrl: iconUrl-esMX - IconFileType: fileType-esMX - IconResolution: iconResolution-esMX - IconTheme: iconTheme-esMX - IconSha256: iconSha256-esMX -- PackageLocale: de-DE - Icons: - - IconUrl: iconUrl-de-DE - IconFileType: fileType-de-DE - IconResolution: iconResolution-de-DE - IconTheme: iconTheme-de-DE - IconSha256: iconSha256-de-DE +PackageIdentifier: Package.package +PackageVersion: 1.0 +ManifestType: shadow +ManifestVersion: 1.5 +PackageLocale: en-US +Icons: +- IconUrl: iconUrl + IconFileType: fileType + IconResolution: iconResolution + IconTheme: iconTheme + IconSha256: iconSha256 +Localization: +- PackageLocale: es-MX + Icons: + - IconUrl: iconUrl-esMX + IconFileType: fileType-esMX + IconResolution: iconResolution-esMX + IconTheme: iconTheme-esMX + IconSha256: iconSha256-esMX +- PackageLocale: de-DE + Icons: + - IconUrl: iconUrl-de-DE + IconFileType: fileType-de-DE + IconResolution: iconResolution-de-DE + IconTheme: iconTheme-de-DE + IconSha256: iconSha256-de-DE diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/OneInstaller.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/OneInstaller.yaml index d5244dad00..3a637bb2fb 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/OneInstaller.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/OneInstaller.yaml @@ -1,18 +1,18 @@ -Id: id.id -Version: 1.2.3 -Publisher: test -InstallerType: exe -Switches: - Custom: custom - Silent: /s - SilentWithProgress: /sp - Interactive: /i - Language: en-US - Log: /l= - InstallLocation: /d= -Installers: - - Arch: x64 - Url: https://contoso.com/publiccontainer/contosoinstaller64.exe - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 - Language: en-US +Id: id.id +Version: 1.2.3 +Publisher: test +InstallerType: exe +Switches: + Custom: custom + Silent: /s + SilentWithProgress: /sp + Interactive: /i + Language: en-US + Log: /l= + InstallLocation: /d= +Installers: + - Arch: x64 + Url: https://contoso.com/publiccontainer/contosoinstaller64.exe + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 + Language: en-US Scope: user \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEquality.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEquality.yaml index 752c54c756..f523813402 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEquality.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEquality.yaml @@ -1,12 +1,12 @@ -Id: id.id -Version: 1.2.3 -Switches: - Custom: custom - Silent: /s -Installers: - - Arch: x86 - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - - Arch: x64 - Url: https://contoso.com/publiccontainer/contosoinstaller64.exe +Id: id.id +Version: 1.2.3 +Switches: + Custom: custom + Silent: /s +Installers: + - Arch: x86 + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + - Arch: x64 + Url: https://contoso.com/publiccontainer/contosoinstaller64.exe Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithLocalization.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithLocalization.yaml index c2a7ab6405..b143c46e60 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithLocalization.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithLocalization.yaml @@ -1,17 +1,17 @@ -Id: id.id -Version: 1.2.3 -Switches: - Custom: custom - Silent: /s -Installers: - - Arch: x86 - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - - Arch: x64 - Url: https://contoso.com/publiccontainer/contosoinstaller64.exe - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 -Localization: - - Language: es-MX - Description: Text to display for es-MX - Homepage: https://github.com/microsoft/msix-packaging/es-MX +Id: id.id +Version: 1.2.3 +Switches: + Custom: custom + Silent: /s +Installers: + - Arch: x86 + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + - Arch: x64 + Url: https://contoso.com/publiccontainer/contosoinstaller64.exe + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 +Localization: + - Language: es-MX + Description: Text to display for es-MX + Homepage: https://github.com/microsoft/msix-packaging/es-MX LicenseUrl: https://github.com/microsoft/msix-packaging/blob/master/LICENSE-es-MX \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutInstallers.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutInstallers.yaml index cbc6e97f39..6d99f8dd0b 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutInstallers.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutInstallers.yaml @@ -1,5 +1,5 @@ -Id: id.id -Version: 1.2.3 -Switches: - Custom: custom +Id: id.id +Version: 1.2.3 +Switches: + Custom: custom Silent: /s \ No newline at end of file diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutSwitches.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutSwitches.yaml index 8dd9970705..8741f17db2 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutSwitches.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/SomeEqualityWithoutSwitches.yaml @@ -1,9 +1,9 @@ -Id: id.id -Version: 1.2.3 -Installers: - - Arch: x86 - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - - Arch: x64 - Url: https://contoso.com/publiccontainer/contosoinstaller64.exe - Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 +Id: id.id +Version: 1.2.3 +Installers: + - Arch: x86 + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + - Arch: x64 + Url: https://contoso.com/publiccontainer/contosoinstaller64.exe + Sha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C83 diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_with_bom.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_with_bom.yaml index 89509f3415..51c0849849 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_with_bom.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_with_bom.yaml @@ -1,14 +1,14 @@ -PackageIdentifier: IntegrationTests.ManifestUpdate.OneInstallerCreate -PackageVersion: 1.0 -PackageName: ManifestUpdate Test -Publisher: Contoso -ShortDescription: Manifest update test for Create manifest create. -License: Text -Installers: -- Architecture: x64 - InstallerUrl: https://stpkgmanvalwestustest.blob.core.windows.net/test-installers/VSCodeUserSetup-x64-1.46.1.exe - InstallerType: inno - InstallerSha256: e29f73f5b6bad9ea5a484d9a934141a03d3fa39c55efd6e65e6e02dea6be9aa5 -PackageLocale: en-US -ManifestType: singleton -ManifestVersion: 1.0.0 +PackageIdentifier: IntegrationTests.ManifestUpdate.OneInstallerCreate +PackageVersion: 1.0 +PackageName: ManifestUpdate Test +Publisher: Contoso +ShortDescription: Manifest update test for Create manifest create. +License: Text +Installers: +- Architecture: x64 + InstallerUrl: https://stpkgmanvalwestustest.blob.core.windows.net/test-installers/VSCodeUserSetup-x64-1.46.1.exe + InstallerType: inno + InstallerSha256: e29f73f5b6bad9ea5a484d9a934141a03d3fa39c55efd6e65e6e02dea6be9aa5 +PackageLocale: en-US +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_without_bom.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_without_bom.yaml index 5cabec114b..805ecad41b 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_without_bom.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/Test_yaml_without_bom.yaml @@ -1,14 +1,14 @@ -PackageIdentifier: IntegrationTests.ManifestUpdate.OneInstallerCreate -PackageVersion: 1.0 -PackageName: ManifestUpdate Test -Publisher: Contoso -ShortDescription: Manifest update test for Create manifest create. -License: Text -Installers: -- Architecture: x64 - InstallerUrl: https://stpkgmanvalwestustest.blob.core.windows.net/test-installers/VSCodeUserSetup-x64-1.46.1.exe - InstallerType: inno - InstallerSha256: e29f73f5b6bad9ea5a484d9a934141a03d3fa39c55efd6e65e6e02dea6be9aa5 -PackageLocale: en-US -ManifestType: singleton -ManifestVersion: 1.0.0 +PackageIdentifier: IntegrationTests.ManifestUpdate.OneInstallerCreate +PackageVersion: 1.0 +PackageName: ManifestUpdate Test +Publisher: Contoso +ShortDescription: Manifest update test for Create manifest create. +License: Text +Installers: +- Architecture: x64 + InstallerUrl: https://stpkgmanvalwestustest.blob.core.windows.net/test-installers/VSCodeUserSetup-x64-1.46.1.exe + InstallerType: inno + InstallerSha256: e29f73f5b6bad9ea5a484d9a934141a03d3fa39c55efd6e65e6e02dea6be9aa5 +PackageLocale: en-US +ManifestType: singleton +ManifestVersion: 1.0.0 diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestInfoMissingRequiredPackageLocale.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestInfoMissingRequiredPackageLocale.yaml index ac75e4fe5b..79026bc326 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestInfoMissingRequiredPackageLocale.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestInfoMissingRequiredPackageLocale.yaml @@ -1,20 +1,20 @@ -PackageIdentifier: microsoft.msixsdk -PackageVersion: 1.7.32 -Publisher: Microsoft UK -PublisherUrl: https://www.microsoft.com/UK -PublisherSupportUrl: https://www.microsoft.com/support/UK -PrivacyUrl: https://www.microsoft.com/privacy/UK -Author: Microsoft UK -PackageName: MSIX SDK UK -PackageUrl: https://www.microsoft.com/msixsdk/home/UK -License: MIT License UK -LicenseUrl: https://www.microsoft.com/msixsdk/license/UK -Copyright: Copyright Microsoft Corporation UK -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK -ShortDescription: This is MSIX SDK UK -Description: The MSIX SDK project is an effort to enable developers UK -Tags: - - "appxsdkUK" - - "msixsdkUK" -ManifestType: locale -ManifestVersion: 1.0.0 +PackageIdentifier: microsoft.msixsdk +PackageVersion: 1.7.32 +Publisher: Microsoft UK +PublisherUrl: https://www.microsoft.com/UK +PublisherSupportUrl: https://www.microsoft.com/support/UK +PrivacyUrl: https://www.microsoft.com/privacy/UK +Author: Microsoft UK +PackageName: MSIX SDK UK +PackageUrl: https://www.microsoft.com/msixsdk/home/UK +License: MIT License UK +LicenseUrl: https://www.microsoft.com/msixsdk/license/UK +Copyright: Copyright Microsoft Corporation UK +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK +ShortDescription: This is MSIX SDK UK +Description: The MSIX SDK project is an effort to enable developers UK +Tags: + - "appxsdkUK" + - "msixsdkUK" +ManifestType: locale +ManifestVersion: 1.0.0 diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestMerged.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestMerged.yaml index 1ef62fcd6d..d73cad872f 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestMerged.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestMerged.yaml @@ -1,142 +1,142 @@ -Author: Microsoft -Capabilities: -- internetClient -Commands: -- makemsix -- makeappx -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -Dependencies: - ExternalDependencies: - - Outside dependencies - PackageDependencies: - - MinimumVersion: 1.0.0 - PackageIdentifier: Microsoft.MsixSdkDep - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime -Description: The MSIX SDK project is an effort to enable developers -FileExtensions: -- appx -- msix -- appxbundle -- msixbundle -InstallModes: -- interactive -- silent -- silentWithProgress -InstallerLocale: en-US -InstallerSuccessCodes: -- 1 -- 0x80070005 -InstallerSwitches: - Custom: /custom - InstallLocation: /dir= - Interactive: /interactive - Log: /log= - Silent: /silence - SilentWithProgress: /silentwithprogress - Upgrade: /upgrade -InstallerType: zip -Installers: -- Architecture: x86 - Capabilities: - - internetClientPreview - Commands: - - makemsixPreview - - makeappxPreview - Dependencies: - ExternalDependencies: - - Preview Outside dependencies - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - InstallModes: - - interactive - InstallerLocale: en-GB - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - InstallerSwitches: - Custom: /c - InstallLocation: /d= - Interactive: /i - Log: /l= - Silent: /s - SilentWithProgress: /sp - Upgrade: /u - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - MinimumOSVersion: 10.0.1.0 - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Platform: - - Windows.Desktop - Protocols: - - protocol1preview - - protocol2preview - RestrictedCapabilities: - - runFullTrustPreview - Scope: user - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - UpgradeBehavior: install -- Architecture: x64 - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - InstallerType: exe - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe - ProductCode: '{Bar}' -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Localization: -- Author: Microsoft UK - Copyright: Copyright Microsoft Corporation UK - CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK - Description: The MSIX SDK project is an effort to enable developers UK - License: MIT License UK - LicenseUrl: https://www.microsoft.com/msixsdk/license/UK - PackageLocale: en-GB - PackageName: MSIX SDK UK - PackageUrl: https://www.microsoft.com/msixsdk/home/UK - PrivacyUrl: https://www.microsoft.com/privacy/UK - Publisher: Microsoft UK - PublisherSupportUrl: https://www.microsoft.com/support/UK - PublisherUrl: https://www.microsoft.com/UK - ShortDescription: This is MSIX SDK UK - Tags: - - appxsdkUK - - msixsdkUK -ManifestType: merged -ManifestVersion: 1.0.0 -MinimumOSVersion: 10.0.0.0 -Moniker: msixsdk -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -PackageIdentifier: microsoft.msixsdk -PackageLocale: en-US -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -PackageVersion: 1.7.32 -Platform: -- Windows.Desktop -- Windows.Universal -PrivacyUrl: https://www.microsoft.com/privacy -ProductCode: '{Foo}' -Protocols: -- protocol1 -- protocol2 -Publisher: Microsoft -PublisherSupportUrl: https://www.microsoft.com/support -PublisherUrl: https://www.microsoft.com -RestrictedCapabilities: -- runFullTrust -Scope: machine -ShortDescription: This is MSIX SDK -Tags: -- appxsdk -- msixsdk -UpgradeBehavior: uninstallPrevious +Author: Microsoft +Capabilities: +- internetClient +Commands: +- makemsix +- makeappx +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +Dependencies: + ExternalDependencies: + - Outside dependencies + PackageDependencies: + - MinimumVersion: 1.0.0 + PackageIdentifier: Microsoft.MsixSdkDep + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime +Description: The MSIX SDK project is an effort to enable developers +FileExtensions: +- appx +- msix +- appxbundle +- msixbundle +InstallModes: +- interactive +- silent +- silentWithProgress +InstallerLocale: en-US +InstallerSuccessCodes: +- 1 +- 0x80070005 +InstallerSwitches: + Custom: /custom + InstallLocation: /dir= + Interactive: /interactive + Log: /log= + Silent: /silence + SilentWithProgress: /silentwithprogress + Upgrade: /upgrade +InstallerType: zip +Installers: +- Architecture: x86 + Capabilities: + - internetClientPreview + Commands: + - makemsixPreview + - makeappxPreview + Dependencies: + ExternalDependencies: + - Preview Outside dependencies + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + InstallModes: + - interactive + InstallerLocale: en-GB + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + InstallerSwitches: + Custom: /c + InstallLocation: /d= + Interactive: /i + Log: /l= + Silent: /s + SilentWithProgress: /sp + Upgrade: /u + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + MinimumOSVersion: 10.0.1.0 + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Platform: + - Windows.Desktop + Protocols: + - protocol1preview + - protocol2preview + RestrictedCapabilities: + - runFullTrustPreview + Scope: user + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + UpgradeBehavior: install +- Architecture: x64 + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + InstallerType: exe + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + ProductCode: '{Bar}' +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Localization: +- Author: Microsoft UK + Copyright: Copyright Microsoft Corporation UK + CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK + Description: The MSIX SDK project is an effort to enable developers UK + License: MIT License UK + LicenseUrl: https://www.microsoft.com/msixsdk/license/UK + PackageLocale: en-GB + PackageName: MSIX SDK UK + PackageUrl: https://www.microsoft.com/msixsdk/home/UK + PrivacyUrl: https://www.microsoft.com/privacy/UK + Publisher: Microsoft UK + PublisherSupportUrl: https://www.microsoft.com/support/UK + PublisherUrl: https://www.microsoft.com/UK + ShortDescription: This is MSIX SDK UK + Tags: + - appxsdkUK + - msixsdkUK +ManifestType: merged +ManifestVersion: 1.0.0 +MinimumOSVersion: 10.0.0.0 +Moniker: msixsdk +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +PackageIdentifier: microsoft.msixsdk +PackageLocale: en-US +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +PackageVersion: 1.7.32 +Platform: +- Windows.Desktop +- Windows.Universal +PrivacyUrl: https://www.microsoft.com/privacy +ProductCode: '{Foo}' +Protocols: +- protocol1 +- protocol2 +Publisher: Microsoft +PublisherSupportUrl: https://www.microsoft.com/support +PublisherUrl: https://www.microsoft.com +RestrictedCapabilities: +- runFullTrust +Scope: machine +ShortDescription: This is MSIX SDK +Tags: +- appxsdk +- msixsdk +UpgradeBehavior: uninstallPrevious diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestNoLocalization.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestNoLocalization.yaml index f72e359cd9..cbcf3ba654 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestNoLocalization.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1ManifestNoLocalization.yaml @@ -1,117 +1,117 @@ -Author: Microsoft -Capabilities: -- internetClient -Commands: -- makemsix -- makeappx -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://github.com/Microsoft/msix-packaging/copyright -Dependencies: - ExternalDependencies: - - Outside dependencies - PackageDependencies: - - MinimumVersion: 1.0.0 - PackageIdentifier: Microsoft.MsixSdkDep - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime -Description: The MSIX SDK project is an effort to enable developers -FileExtensions: -- appx -- msix -- appxbundle -- msixbundle -InstallModes: -- user -- machine -InstallerLocale: en-US -InstallerSuccessCodes: -- 1 -- 0x80070005 -InstallerSwitches: - Custom: /custom - InstallLocation: /dir= - Interactive: /interactive - Log: /log= - Silent: /silence - SilentWithProgress: /silentwithprogress - Upgrade: /upgrade -InstallerType: zip -Installers: -- Architecture: x64 - Commands: - - makemsixPreview - - makeappxPreview - Dependencies: - ExternalDependencies: - - Preview Outside dependencies - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - InstallModes: - - user - InstallerLocale: en-GB - InstallerSha256: a4d23b49269a7cb1e09fbfb1d532c7b1eacb9acba5e2c6172b6206e885296052 - InstallerSwitches: - Custom: /c - InstallLocation: /d= - Interactive: /i - Log: /l= - Silent: /s - SilentWithProgress: /sp - Upgrade: /u - InstallerType: msi - InstallerUrl: https://github.com/microsoft/msix-packaging/releases/download/MSIX-Core-1.1-release/msixmgrSetup-1.1.0.0-x64.msi - MinimumOSVersion: 10.0.1.0 - Platform: - - Windows.Desktop - Protocols: - - protocol1preview - - protocol2preview - Scope: user - UpgradeBehavior: install -- Architecture: x86 - InstallerLocale: en-GB - InstallerSha256: a4d23b49269a7cb1e09fbfb1d532c7b1eacb9acba5e2c6172b6206e885296052 - InstallerType: msi - InstallerUrl: https://github.com/microsoft/msix-packaging/releases/download/MSIX-Core-1.1-release/msixmgrSetup-1.1.0.0-x86.msi -License: MIT License -LicenseUrl: https://github.com/Microsoft/msix-packaging/license -ManifestType: merged -ManifestVersion: 1.0.0 -MinimumOSVersion: 10.0.0.0 -Moniker: msixsdk -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -PackageIdentifier: microsoft.msixsdk -PackageLocale: en-US -PackageName: MSIX SDK -PackageUrl: https://github.com/Microsoft/msix-packaging -PackageVersion: 1.7.32 -Platform: -- Windows.Desktop -- Windows.Universal -PrivacyUrl: https://www.microsoft.com/privacy -ProductCode: '{Foo}' -Protocols: -- protocol1 -- protocol2 -Publisher: Microsoft -PublisherSupportUrl: https://www.microsoft.com/support -PublisherUrl: https://www.microsoft.com -RestrictedCapabilities: -- runFullTrust -Scope: machine -ShortDescription: This is MSIX SDK -Tags: -- appxsdk -- msixsdk -UpgradeBehavior: uninstallPrevious +Author: Microsoft +Capabilities: +- internetClient +Commands: +- makemsix +- makeappx +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://github.com/Microsoft/msix-packaging/copyright +Dependencies: + ExternalDependencies: + - Outside dependencies + PackageDependencies: + - MinimumVersion: 1.0.0 + PackageIdentifier: Microsoft.MsixSdkDep + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime +Description: The MSIX SDK project is an effort to enable developers +FileExtensions: +- appx +- msix +- appxbundle +- msixbundle +InstallModes: +- user +- machine +InstallerLocale: en-US +InstallerSuccessCodes: +- 1 +- 0x80070005 +InstallerSwitches: + Custom: /custom + InstallLocation: /dir= + Interactive: /interactive + Log: /log= + Silent: /silence + SilentWithProgress: /silentwithprogress + Upgrade: /upgrade +InstallerType: zip +Installers: +- Architecture: x64 + Commands: + - makemsixPreview + - makeappxPreview + Dependencies: + ExternalDependencies: + - Preview Outside dependencies + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + InstallModes: + - user + InstallerLocale: en-GB + InstallerSha256: a4d23b49269a7cb1e09fbfb1d532c7b1eacb9acba5e2c6172b6206e885296052 + InstallerSwitches: + Custom: /c + InstallLocation: /d= + Interactive: /i + Log: /l= + Silent: /s + SilentWithProgress: /sp + Upgrade: /u + InstallerType: msi + InstallerUrl: https://github.com/microsoft/msix-packaging/releases/download/MSIX-Core-1.1-release/msixmgrSetup-1.1.0.0-x64.msi + MinimumOSVersion: 10.0.1.0 + Platform: + - Windows.Desktop + Protocols: + - protocol1preview + - protocol2preview + Scope: user + UpgradeBehavior: install +- Architecture: x86 + InstallerLocale: en-GB + InstallerSha256: a4d23b49269a7cb1e09fbfb1d532c7b1eacb9acba5e2c6172b6206e885296052 + InstallerType: msi + InstallerUrl: https://github.com/microsoft/msix-packaging/releases/download/MSIX-Core-1.1-release/msixmgrSetup-1.1.0.0-x86.msi +License: MIT License +LicenseUrl: https://github.com/Microsoft/msix-packaging/license +ManifestType: merged +ManifestVersion: 1.0.0 +MinimumOSVersion: 10.0.0.0 +Moniker: msixsdk +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +PackageIdentifier: microsoft.msixsdk +PackageLocale: en-US +PackageName: MSIX SDK +PackageUrl: https://github.com/Microsoft/msix-packaging +PackageVersion: 1.7.32 +Platform: +- Windows.Desktop +- Windows.Universal +PrivacyUrl: https://www.microsoft.com/privacy +ProductCode: '{Foo}' +Protocols: +- protocol1 +- protocol2 +Publisher: Microsoft +PublisherSupportUrl: https://www.microsoft.com/support +PublisherUrl: https://www.microsoft.com +RestrictedCapabilities: +- runFullTrust +Scope: machine +ShortDescription: This is MSIX SDK +Tags: +- appxsdk +- msixsdk +UpgradeBehavior: uninstallPrevious diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_10ManifestMerged.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_10ManifestMerged.yaml index 43dfae3b7e..4ce27fe7c8 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_10ManifestMerged.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_10ManifestMerged.yaml @@ -106,7 +106,7 @@ Markets: - US ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://defaultReturnResponseUrl.com - InstallerReturnCode: 3 ReturnResponse: custom @@ -126,11 +126,11 @@ InstallationMetadata: DisplayName: "DisplayName" DownloadCommandProhibited: true ArchiveBinariesDependOnPath: true -RepairBehavior: uninstaller -Authentication: - AuthenticationType: microsoftEntraId - MicrosoftEntraIdAuthenticationInfo: - Resource: DefaultResource +RepairBehavior: uninstaller +Authentication: + AuthenticationType: microsoftEntraId + MicrosoftEntraIdAuthenticationInfo: + Resource: DefaultResource Scope: DefaultScope Localization: - Agreements: @@ -235,7 +235,7 @@ Installers: - "US" ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://returnResponseUrl.com DownloadCommandProhibited: true ArchiveBinariesDependOnPath: false @@ -247,17 +247,17 @@ Installers: FileSha256: 79D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 FileType: launch2 InvocationParameter: "/arg2" - DisplayName: "DisplayName2" - Authentication: - AuthenticationType: microsoftEntraId - MicrosoftEntraIdAuthenticationInfo: - Resource: Resource + DisplayName: "DisplayName2" + Authentication: + AuthenticationType: microsoftEntraId + MicrosoftEntraIdAuthenticationInfo: + Resource: Resource Scope: Scope - Architecture: x64 InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe InstallerType: exe - ProductCode: '{Bar}' + ProductCode: '{Bar}' MSStoreProductIdentifier: fakeIdentifier ManifestType: merged -ManifestVersion: 1.10.0 +ManifestVersion: 1.10.0 diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_1ManifestMerged.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_1ManifestMerged.yaml index eaf2be893d..d1ffc8b615 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_1ManifestMerged.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_1ManifestMerged.yaml @@ -1,189 +1,189 @@ -Agreements: -- Agreement: DefaultText - AgreementLabel: DefaultLabel - AgreementUrl: https://DefaultAgreementUrl.net -AppsAndFeaturesEntries: -- DisplayName: DisplayName - DisplayVersion: DisplayVersion - InstallerType: exe - ProductCode: ProductCode - Publisher: Publisher - UpgradeCode: UpgradeCode -Author: Microsoft -Capabilities: -- internetClient -Commands: -- makemsix -- makeappx -Copyright: Copyright Microsoft Corporation -CopyrightUrl: https://www.microsoft.com/msixsdk/copyright -Dependencies: - ExternalDependencies: - - Outside dependencies - PackageDependencies: - - MinimumVersion: 1.0.0 - PackageIdentifier: Microsoft.MsixSdkDep - WindowsFeatures: - - IIS - WindowsLibraries: - - VC Runtime -Description: The MSIX SDK project is an effort to enable developers -ElevationRequirement: elevatesSelf -ExpectedReturnCodes: -- InstallerReturnCode: 2 - ReturnResponse: contactSupport -- InstallerReturnCode: 3 - ReturnResponse: custom -FileExtensions: -- appx -- msix -- appxbundle -- msixbundle -InstallLocationRequired: true -InstallModes: -- interactive -- silent -- silentWithProgress -InstallerAbortsTerminal: true -InstallerLocale: en-US -InstallerSuccessCodes: -- 1 -- 0x80070005 -InstallerSwitches: - Custom: /custom - InstallLocation: /dir= - Interactive: /interactive - Log: /log= - Silent: /silence - SilentWithProgress: /silentwithprogress - Upgrade: /upgrade -InstallerType: zip -Installers: -- Architecture: x86 - Capabilities: - - internetClientPreview - Commands: - - makemsixPreview - - makeappxPreview - Dependencies: - ExternalDependencies: - - Preview Outside dependencies - PackageDependencies: - - PackageIdentifier: Microsoft.MsixSdkDepPreview - WindowsFeatures: - - PreviewIIS - WindowsLibraries: - - Preview VC Runtime - ElevationRequirement: elevationRequired - ExpectedReturnCodes: - - InstallerReturnCode: 2 - ReturnResponse: contactSupport - FileExtensions: - - appxbundle - - msixbundle - - appx - - msix - InstallLocationRequired: false - InstallModes: - - interactive - InstallerAbortsTerminal: false - InstallerLocale: en-GB - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - InstallerSwitches: - Custom: /c - InstallLocation: /d= - Interactive: /i - Log: /l= - Silent: /s - SilentWithProgress: /sp - Upgrade: /u - InstallerType: msix - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix - Markets: - ExcludedMarkets: - - US - MinimumOSVersion: 10.0.1.0 - PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe - Platform: - - Windows.Desktop - Protocols: - - protocol1preview - - protocol2preview - ReleaseDate: 2021-02-02 - RequireExplicitUpgrade: false - RestrictedCapabilities: - - runFullTrustPreview - Scope: user - SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - UnsupportedOSArchitectures: - - arm64 - UpgradeBehavior: install -- Architecture: x64 - InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 - InstallerType: exe - InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe - ProductCode: '{Bar}' -License: MIT License -LicenseUrl: https://www.microsoft.com/msixsdk/license -Localization: -- Agreements: - - Agreement: Text - AgreementLabel: Label - AgreementUrl: https://AgreementUrl.net - Author: Microsoft UK - Copyright: Copyright Microsoft Corporation UK - CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK - Description: The MSIX SDK project is an effort to enable developers UK - License: MIT License UK - LicenseUrl: https://www.microsoft.com/msixsdk/license/UK - PackageLocale: en-GB - PackageName: MSIX SDK UK - PackageUrl: https://www.microsoft.com/msixsdk/home/UK - PrivacyUrl: https://www.microsoft.com/privacy/UK - Publisher: Microsoft UK - PublisherSupportUrl: https://www.microsoft.com/support/UK - PublisherUrl: https://www.microsoft.com/UK - ReleaseNotes: Release notes - ReleaseNotesUrl: https://ReleaseNotes.net - ShortDescription: This is MSIX SDK UK - Tags: - - appxsdkUK - - msixsdkUK -ManifestType: merged -ManifestVersion: 1.1.0 -Markets: - AllowedMarkets: - - US -MinimumOSVersion: 10.0.0.0 -Moniker: msixsdk -PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe -PackageIdentifier: microsoft.msixsdk -PackageLocale: en-US -PackageName: MSIX SDK -PackageUrl: https://www.microsoft.com/msixsdk/home -PackageVersion: 1.7.32 -Platform: -- Windows.Desktop -- Windows.Universal -PrivacyUrl: https://www.microsoft.com/privacy -ProductCode: '{Foo}' -Protocols: -- protocol1 -- protocol2 -Publisher: Microsoft -PublisherSupportUrl: https://www.microsoft.com/support -PublisherUrl: https://www.microsoft.com -ReleaseDate: 2021-01-01 -ReleaseNotes: Default release notes -ReleaseNotesUrl: https://DefaultReleaseNotes.net -RequireExplicitUpgrade: true -RestrictedCapabilities: -- runFullTrust -Scope: machine -ShortDescription: This is MSIX SDK -Tags: -- appxsdk -- msixsdk -UnsupportedOSArchitectures: -- arm -UpgradeBehavior: uninstallPrevious +Agreements: +- Agreement: DefaultText + AgreementLabel: DefaultLabel + AgreementUrl: https://DefaultAgreementUrl.net +AppsAndFeaturesEntries: +- DisplayName: DisplayName + DisplayVersion: DisplayVersion + InstallerType: exe + ProductCode: ProductCode + Publisher: Publisher + UpgradeCode: UpgradeCode +Author: Microsoft +Capabilities: +- internetClient +Commands: +- makemsix +- makeappx +Copyright: Copyright Microsoft Corporation +CopyrightUrl: https://www.microsoft.com/msixsdk/copyright +Dependencies: + ExternalDependencies: + - Outside dependencies + PackageDependencies: + - MinimumVersion: 1.0.0 + PackageIdentifier: Microsoft.MsixSdkDep + WindowsFeatures: + - IIS + WindowsLibraries: + - VC Runtime +Description: The MSIX SDK project is an effort to enable developers +ElevationRequirement: elevatesSelf +ExpectedReturnCodes: +- InstallerReturnCode: 2 + ReturnResponse: contactSupport +- InstallerReturnCode: 3 + ReturnResponse: custom +FileExtensions: +- appx +- msix +- appxbundle +- msixbundle +InstallLocationRequired: true +InstallModes: +- interactive +- silent +- silentWithProgress +InstallerAbortsTerminal: true +InstallerLocale: en-US +InstallerSuccessCodes: +- 1 +- 0x80070005 +InstallerSwitches: + Custom: /custom + InstallLocation: /dir= + Interactive: /interactive + Log: /log= + Silent: /silence + SilentWithProgress: /silentwithprogress + Upgrade: /upgrade +InstallerType: zip +Installers: +- Architecture: x86 + Capabilities: + - internetClientPreview + Commands: + - makemsixPreview + - makeappxPreview + Dependencies: + ExternalDependencies: + - Preview Outside dependencies + PackageDependencies: + - PackageIdentifier: Microsoft.MsixSdkDepPreview + WindowsFeatures: + - PreviewIIS + WindowsLibraries: + - Preview VC Runtime + ElevationRequirement: elevationRequired + ExpectedReturnCodes: + - InstallerReturnCode: 2 + ReturnResponse: contactSupport + FileExtensions: + - appxbundle + - msixbundle + - appx + - msix + InstallLocationRequired: false + InstallModes: + - interactive + InstallerAbortsTerminal: false + InstallerLocale: en-GB + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + InstallerSwitches: + Custom: /c + InstallLocation: /d= + Interactive: /i + Log: /l= + Silent: /s + SilentWithProgress: /sp + Upgrade: /u + InstallerType: msix + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx86.msix + Markets: + ExcludedMarkets: + - US + MinimumOSVersion: 10.0.1.0 + PackageFamilyName: Microsoft.DesktopAppInstallerPreview_8wekyb3d8bbwe + Platform: + - Windows.Desktop + Protocols: + - protocol1preview + - protocol2preview + ReleaseDate: 2021-02-02 + RequireExplicitUpgrade: false + RestrictedCapabilities: + - runFullTrustPreview + Scope: user + SignatureSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + UnsupportedOSArchitectures: + - arm64 + UpgradeBehavior: install +- Architecture: x64 + InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 + InstallerType: exe + InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe + ProductCode: '{Bar}' +License: MIT License +LicenseUrl: https://www.microsoft.com/msixsdk/license +Localization: +- Agreements: + - Agreement: Text + AgreementLabel: Label + AgreementUrl: https://AgreementUrl.net + Author: Microsoft UK + Copyright: Copyright Microsoft Corporation UK + CopyrightUrl: https://www.microsoft.com/msixsdk/copyright/UK + Description: The MSIX SDK project is an effort to enable developers UK + License: MIT License UK + LicenseUrl: https://www.microsoft.com/msixsdk/license/UK + PackageLocale: en-GB + PackageName: MSIX SDK UK + PackageUrl: https://www.microsoft.com/msixsdk/home/UK + PrivacyUrl: https://www.microsoft.com/privacy/UK + Publisher: Microsoft UK + PublisherSupportUrl: https://www.microsoft.com/support/UK + PublisherUrl: https://www.microsoft.com/UK + ReleaseNotes: Release notes + ReleaseNotesUrl: https://ReleaseNotes.net + ShortDescription: This is MSIX SDK UK + Tags: + - appxsdkUK + - msixsdkUK +ManifestType: merged +ManifestVersion: 1.1.0 +Markets: + AllowedMarkets: + - US +MinimumOSVersion: 10.0.0.0 +Moniker: msixsdk +PackageFamilyName: Microsoft.DesktopAppInstaller_8wekyb3d8bbwe +PackageIdentifier: microsoft.msixsdk +PackageLocale: en-US +PackageName: MSIX SDK +PackageUrl: https://www.microsoft.com/msixsdk/home +PackageVersion: 1.7.32 +Platform: +- Windows.Desktop +- Windows.Universal +PrivacyUrl: https://www.microsoft.com/privacy +ProductCode: '{Foo}' +Protocols: +- protocol1 +- protocol2 +Publisher: Microsoft +PublisherSupportUrl: https://www.microsoft.com/support +PublisherUrl: https://www.microsoft.com +ReleaseDate: 2021-01-01 +ReleaseNotes: Default release notes +ReleaseNotesUrl: https://DefaultReleaseNotes.net +RequireExplicitUpgrade: true +RestrictedCapabilities: +- runFullTrust +Scope: machine +ShortDescription: This is MSIX SDK +Tags: +- appxsdk +- msixsdk +UnsupportedOSArchitectures: +- arm +UpgradeBehavior: uninstallPrevious diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_6ManifestMerged.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_6ManifestMerged.yaml index 1eb70ac0ab..f8f0eb6698 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_6ManifestMerged.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_6ManifestMerged.yaml @@ -105,7 +105,7 @@ Markets: - US ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://defaultReturnResponseUrl.com - InstallerReturnCode: 3 ReturnResponse: custom @@ -226,7 +226,7 @@ Installers: - "US" ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://returnResponseUrl.com DownloadCommandProhibited: true InstallationMetadata: @@ -243,4 +243,4 @@ Installers: InstallerType: exe ProductCode: '{Bar}' ManifestType: merged -ManifestVersion: 1.6.0 +ManifestVersion: 1.6.0 diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_7ManifestMerged.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_7ManifestMerged.yaml index 42640d1993..90c7cfab9d 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_7ManifestMerged.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_7ManifestMerged.yaml @@ -106,7 +106,7 @@ Markets: - US ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://defaultReturnResponseUrl.com - InstallerReturnCode: 3 ReturnResponse: custom @@ -229,7 +229,7 @@ Installers: - "US" ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://returnResponseUrl.com DownloadCommandProhibited: true RepairBehavior: modify @@ -247,4 +247,4 @@ Installers: InstallerType: exe ProductCode: '{Bar}' ManifestType: merged -ManifestVersion: 1.7.0 +ManifestVersion: 1.7.0 diff --git a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_9ManifestMerged.yaml b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_9ManifestMerged.yaml index 5c588770f3..f626f49608 100644 --- a/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_9ManifestMerged.yaml +++ b/src/WinGetUtilInterop.UnitTests/TestCollateral/V1_9ManifestMerged.yaml @@ -106,7 +106,7 @@ Markets: - US ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://defaultReturnResponseUrl.com - InstallerReturnCode: 3 ReturnResponse: custom @@ -230,7 +230,7 @@ Installers: - "US" ExpectedReturnCodes: - InstallerReturnCode: 2 - ReturnResponse: contactSupport + ReturnResponse: contactSupport ReturnResponseUrl: https://returnResponseUrl.com DownloadCommandProhibited: true ArchiveBinariesDependOnPath: false @@ -247,7 +247,7 @@ Installers: InstallerSha256: 69D84CA8899800A5575CE31798293CD4FEBAB1D734A07C2E51E56A28E0DF8C82 InstallerUrl: https://www.microsoft.com/msixsdk/msixsdkx64.exe InstallerType: exe - ProductCode: '{Bar}' + ProductCode: '{Bar}' MSStoreProductIdentifier: fakeIdentifier ManifestType: merged -ManifestVersion: 1.9.0 +ManifestVersion: 1.9.0 diff --git a/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj b/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj index 2a2b444ee5..c30dc8e518 100644 --- a/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj +++ b/src/WinGetUtilInterop.UnitTests/WinGetUtilInterop.UnitTests.csproj @@ -1,132 +1,132 @@ - - - - net8.0 - x64;x86 - - - - - - - - - - - - - all - - - - - - - - - - - Content - PreserveNewest - True - - - - - - Always - - - Always - - - Always - - - PreserveNewest - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - Always - - - PreserveNewest - - - - - - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - + + + + net8.0 + x64;x86 + + + + + + + + + + + + + all + + + + + + + + + + + Content + PreserveNewest + True + + + + + + Always + + + Always + + + Always + + + PreserveNewest + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + Always + + + PreserveNewest + + + + + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/WinGetUtilInterop/Api/WinGetSQLiteIndex.cs b/src/WinGetUtilInterop/Api/WinGetSQLiteIndex.cs index 8a39390435..5b0c7a856e 100644 --- a/src/WinGetUtilInterop/Api/WinGetSQLiteIndex.cs +++ b/src/WinGetUtilInterop/Api/WinGetSQLiteIndex.cs @@ -26,10 +26,10 @@ public sealed class WinGetSQLiteIndex : IWinGetSQLiteIndex internal WinGetSQLiteIndex(IntPtr indexHandle) { this.indexHandle = indexHandle; - } - - /// - public void MigrateTo(uint majorVersion, uint minorVersion) + } + + /// + public void MigrateTo(uint majorVersion, uint minorVersion) { try { @@ -38,11 +38,11 @@ public void MigrateTo(uint majorVersion, uint minorVersion) catch (Exception e) { throw new WinGetSQLiteIndexException(e); - } - } - - /// - public void SetProperty(SQLiteIndexProperty property, string value) + } + } + + /// + public void SetProperty(SQLiteIndexProperty property, string value) { try { @@ -51,7 +51,7 @@ public void SetProperty(SQLiteIndexProperty property, string value) catch (Exception e) { throw new WinGetSQLiteIndexException(e); - } + } } /// @@ -195,8 +195,8 @@ public void Dispose(bool disposing) /// /// Sets a property on the index. /// - /// Handle of the index. - /// The property to set. + /// Handle of the index. + /// The property to set. /// The value to set. /// HRESULT. [DllImport(Constants.DllName, CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, PreserveSig = false)] diff --git a/src/WinGetUtilInterop/Common/Enums.cs b/src/WinGetUtilInterop/Common/Enums.cs index 7b43ad4895..d194da4ccf 100644 --- a/src/WinGetUtilInterop/Common/Enums.cs +++ b/src/WinGetUtilInterop/Common/Enums.cs @@ -31,8 +31,8 @@ public enum WinGetCreateManifestOption ///
SchemaAndSemanticValidation = 0x2, - /// - /// Allow shadow manifest + /// + /// Allow shadow manifest /// AllowShadowManifest = 0x4, @@ -41,8 +41,8 @@ public enum WinGetCreateManifestOption /// /// Return error on manifest fields that require verified publishers, used during semantic validation /// - ReturnErrorOnVerifiedPublisherFields = 0x1000, - + ReturnErrorOnVerifiedPublisherFields = 0x1000, + /// /// Return error if a network address is present in installer switches. /// diff --git a/src/WinGetUtilInterop/Common/Helpers.cs b/src/WinGetUtilInterop/Common/Helpers.cs index 83f9e56792..9b03e75c00 100644 --- a/src/WinGetUtilInterop/Common/Helpers.cs +++ b/src/WinGetUtilInterop/Common/Helpers.cs @@ -26,9 +26,9 @@ public static IDeserializer CreateDeserializer() .Build(); } - /// - /// Helper to serialize the manifest. - /// + /// + /// Helper to serialize the manifest. + /// /// ISerializer object. public static ISerializer CreateSerializer() { diff --git a/src/WinGetUtilInterop/Common/ManifestDiagnostic.cs b/src/WinGetUtilInterop/Common/ManifestDiagnostic.cs index 0292287221..8cc1d2c382 100644 --- a/src/WinGetUtilInterop/Common/ManifestDiagnostic.cs +++ b/src/WinGetUtilInterop/Common/ManifestDiagnostic.cs @@ -1,101 +1,101 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGetUtil.Common -{ - /// - /// Severity level of a manifest diagnostic. - /// - public enum ManifestDiagnosticLevel - { - /// - /// The diagnostic is a warning; the manifest may still be valid. - /// - Warning, - - /// - /// The diagnostic is an error; the manifest is invalid. - /// - Error, - } - - /// - /// Represents a single manifest error or warning returned by . - /// - public class ManifestDiagnostic - { - /// - /// Initializes a new instance of the class. - /// - /// The error identifier. - /// The human-readable error message. - /// The field or context associated with the error. - /// The field value that caused the error, if any. - /// The 1-based line number in the source file, or 0 if unknown. - /// The 1-based column number in the source file, or 0 if unknown. - /// The severity level of the diagnostic. - /// The source file name, if applicable. - public ManifestDiagnostic( - ManifestErrorId errorId, - string message, - string context, - string value, - long line, - long column, - ManifestDiagnosticLevel level, - string file) - { - this.ErrorId = errorId; - this.Message = message; - this.Context = context; - this.Value = value; - this.Line = line; - this.Column = column; - this.Level = level; - this.File = file; - } - - /// - /// Gets the error identifier. - /// - public ManifestErrorId ErrorId { get; } - - /// - /// Gets the human-readable error message. - /// - public string Message { get; } - - /// - /// Gets the field or context associated with the error. - /// - public string Context { get; } - - /// - /// Gets the field value that caused the error, if any. - /// - public string Value { get; } - - /// - /// Gets the 1-based line number in the source file, or 0 if unknown. - /// - public long Line { get; } - - /// - /// Gets the 1-based column number in the source file, or 0 if unknown. - /// - public long Column { get; } - - /// - /// Gets the severity level of this diagnostic. - /// - public ManifestDiagnosticLevel Level { get; } - - /// - /// Gets the source file name associated with this diagnostic, if applicable. - /// - public string File { get; } - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Common +{ + /// + /// Severity level of a manifest diagnostic. + /// + public enum ManifestDiagnosticLevel + { + /// + /// The diagnostic is a warning; the manifest may still be valid. + /// + Warning, + + /// + /// The diagnostic is an error; the manifest is invalid. + /// + Error, + } + + /// + /// Represents a single manifest error or warning returned by . + /// + public class ManifestDiagnostic + { + /// + /// Initializes a new instance of the class. + /// + /// The error identifier. + /// The human-readable error message. + /// The field or context associated with the error. + /// The field value that caused the error, if any. + /// The 1-based line number in the source file, or 0 if unknown. + /// The 1-based column number in the source file, or 0 if unknown. + /// The severity level of the diagnostic. + /// The source file name, if applicable. + public ManifestDiagnostic( + ManifestErrorId errorId, + string message, + string context, + string value, + long line, + long column, + ManifestDiagnosticLevel level, + string file) + { + this.ErrorId = errorId; + this.Message = message; + this.Context = context; + this.Value = value; + this.Line = line; + this.Column = column; + this.Level = level; + this.File = file; + } + + /// + /// Gets the error identifier. + /// + public ManifestErrorId ErrorId { get; } + + /// + /// Gets the human-readable error message. + /// + public string Message { get; } + + /// + /// Gets the field or context associated with the error. + /// + public string Context { get; } + + /// + /// Gets the field value that caused the error, if any. + /// + public string Value { get; } + + /// + /// Gets the 1-based line number in the source file, or 0 if unknown. + /// + public long Line { get; } + + /// + /// Gets the 1-based column number in the source file, or 0 if unknown. + /// + public long Column { get; } + + /// + /// Gets the severity level of this diagnostic. + /// + public ManifestDiagnosticLevel Level { get; } + + /// + /// Gets the source file name associated with this diagnostic, if applicable. + /// + public string File { get; } + } +} diff --git a/src/WinGetUtilInterop/Common/ManifestErrorId.cs b/src/WinGetUtilInterop/Common/ManifestErrorId.cs index 607bce3d2e..433e35f698 100644 --- a/src/WinGetUtilInterop/Common/ManifestErrorId.cs +++ b/src/WinGetUtilInterop/Common/ManifestErrorId.cs @@ -1,210 +1,210 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGetUtil.Common -{ - /// - /// Identifies a specific manifest validation error or warning. - /// Each value corresponds to a WINGET_DEFINE_RESOURCE_STRINGID entry in - /// src/AppInstallerCommonCore/Public/winget/ManifestValidation.h (ManifestError namespace) - /// and an entry in the ErrorIdToMessageMap in - /// src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp. - /// When adding, removing, or renaming an error ID, all three locations must be updated. - /// - public enum ManifestErrorId - { - /// - /// The error ID was not recognized (e.g. defined in a newer native version). - /// - Unknown = 0, - - /// Approximate version not allowed. - ApproximateVersionNotAllowed, - - /// Arp Validation Error. - ArpValidationError, - - /// DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index. Existing DisplayVersion range in index: - ArpVersionOverlapWithIndex, - - /// Internal error while validating DisplayVersion against index. - ArpVersionValidationInternalError, - - /// Contains a blocked MSI property. - BlockedMsiProperty, - - /// Both AllowedMarkets and ExcludedMarkets defined. - BothAllowedAndExcludedMarketsDefined, - - /// Installer switch contains network address. - ContainsNetworkAddress, - - /// Duplicate portable command alias found. - DuplicatePortableCommandAlias, - - /// Duplicate relative file path found. - DuplicateRelativeFilePath, - - /// The multi file manifest contains duplicate PackageLocale. - DuplicateMultiFileManifestLocale, - - /// The multi file manifest should contain only one file with the particular ManifestType. - DuplicateMultiFileManifestType, - - /// Duplicate installer entry found. - DuplicateInstallerEntry, - - /// Multiple Installer URLs found with the same InstallerSha256. Please ensure the accuracy of the URLs. - DuplicateInstallerHash, - - /// Duplicate installer return code found. - DuplicateReturnCodeEntry, - - /// Only zero or one entry for Apps and Features may be specified for InstallerType portable. - ExceededAppsAndFeaturesEntryLimit, - - /// Only zero or one value for Commands may be specified for InstallerType portable. - ExceededCommandsLimit, - - /// Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes. - ExceededNestedInstallerFilesLimit, - - /// Silent and SilentWithProgress switches are not specified for InstallerType exe. Please make sure the installer can run unattended. - ExeInstallerMissingSilentSwitches, - - /// Duplicate field found in the manifest. - FieldDuplicate, - - /// Failed to process field. - FieldFailedToProcess, - - /// All field names should be PascalCased. - FieldIsNotPascalCase, - - /// Field is not supported. - FieldNotSupported, - - /// Field usage requires verified publishers. - FieldRequireVerifiedPublisher, - - /// Unknown field. - FieldUnknown, - - /// Field value is not supported. - FieldValueNotSupported, - - /// Loop found. - FoundDependencyLoop, - - /// The multi file manifest is incomplete. A multi file manifest must contain at least version, installer and defaultLocale manifest. - IncompleteMultiFileManifest, - - /// The values of InstallerSha256 do not match for all instances of the same InstallerUrl. - InconsistentInstallerHash, - - /// DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest. - InconsistentMultiFileManifestDefaultLocale, - - /// The multi file manifest has inconsistent field values. - InconsistentMultiFileManifestFieldValue, - - /// Failed to process installer. - InstallerFailedToProcess, - - /// Inconsistent value in the manifest. - InstallerMsixInconsistencies, - - /// The specified installer type does not support PackageFamilyName. - InstallerTypeDoesNotSupportPackageFamilyName, - - /// The specified installer type does not support ProductCode. - InstallerTypeDoesNotSupportProductCode, - - /// The specified installer type does not write to Apps and Features entry. - InstallerTypeDoesNotWriteAppsAndFeaturesEntry, - - /// The locale value is not a well formed bcp47 language tag. - InvalidBcp47Value, - - /// Invalid field value. - InvalidFieldValue, - - /// Contains invalid MSI switches. - InvalidMsiSwitches, - - /// Encountered unexpected root node. - InvalidRootNode, - - /// The provided value is not a valid Windows feature name. - InvalidWindowsFeatureName, - - /// Dependency not found: - MissingManifestDependenciesNode, - - /// Failed to calculate MSIX signature hash. Please verify that the input file is a valid, signed MSIX. - MsixSignatureHashFailed, - - /// Deleting the manifest will break the following dependencies. - MultiManifestPackageHasDependencies, - - /// No Suitable Minimum Version: - NoSuitableMinVersionDependency, - - /// No supported platforms. - NoSupportedPlatforms, - - /// Optional field missing. - OptionalFieldMissing, - - /// Portable command alias must not point to a location outside of base directory. - PortableCommandAliasEscapesDirectory, - - /// Relative file path must not point to a location outside of archive directory. - RelativeFilePathEscapesDirectory, - - /// Required field with empty value. - RequiredFieldEmpty, - - /// Required field missing. - RequiredFieldMissing, - - /// Schema Error. - SchemaError, - - /// Scope is not supported for InstallerType portable. - ScopeNotSupported, - - /// Shadow manifest is not allowed. - ShadowManifestNotAllowed, - - /// Package has a single manifest and is a dependency of other manifests. - SingleManifestPackageHasDependencies, - - /// The multi file manifest should not contain file with the particular ManifestType. - UnsupportedMultiFileManifestType, - - /// Schema header not found. - SchemaHeaderNotFound, - - /// The schema header is invalid. Please verify that the schema header is present and formatted correctly. - InvalidSchemaHeader, - - /// The manifest type in the schema header does not match the ManifestType property value in the manifest. - SchemaHeaderManifestTypeMismatch, - - /// The manifest version in the schema header does not match the ManifestVersion property value in the manifest. - SchemaHeaderManifestVersionMismatch, - - /// The schema header URL does not match the expected pattern. - SchemaHeaderUrlPatternMismatch, - - /// The file type of the referenced file is not allowed. - InvalidPortableFiletype, - - /// The file type of the referenced file is not a supported font file type. - InvalidFontFiletype, - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Common +{ + /// + /// Identifies a specific manifest validation error or warning. + /// Each value corresponds to a WINGET_DEFINE_RESOURCE_STRINGID entry in + /// src/AppInstallerCommonCore/Public/winget/ManifestValidation.h (ManifestError namespace) + /// and an entry in the ErrorIdToMessageMap in + /// src/AppInstallerCommonCore/Manifest/ManifestValidation.cpp. + /// When adding, removing, or renaming an error ID, all three locations must be updated. + /// + public enum ManifestErrorId + { + /// + /// The error ID was not recognized (e.g. defined in a newer native version). + /// + Unknown = 0, + + /// Approximate version not allowed. + ApproximateVersionNotAllowed, + + /// Arp Validation Error. + ArpValidationError, + + /// DisplayVersion declared in the manifest has overlap with existing DisplayVersion range in the index. Existing DisplayVersion range in index: + ArpVersionOverlapWithIndex, + + /// Internal error while validating DisplayVersion against index. + ArpVersionValidationInternalError, + + /// Contains a blocked MSI property. + BlockedMsiProperty, + + /// Both AllowedMarkets and ExcludedMarkets defined. + BothAllowedAndExcludedMarketsDefined, + + /// Installer switch contains network address. + ContainsNetworkAddress, + + /// Duplicate portable command alias found. + DuplicatePortableCommandAlias, + + /// Duplicate relative file path found. + DuplicateRelativeFilePath, + + /// The multi file manifest contains duplicate PackageLocale. + DuplicateMultiFileManifestLocale, + + /// The multi file manifest should contain only one file with the particular ManifestType. + DuplicateMultiFileManifestType, + + /// Duplicate installer entry found. + DuplicateInstallerEntry, + + /// Multiple Installer URLs found with the same InstallerSha256. Please ensure the accuracy of the URLs. + DuplicateInstallerHash, + + /// Duplicate installer return code found. + DuplicateReturnCodeEntry, + + /// Only zero or one entry for Apps and Features may be specified for InstallerType portable. + ExceededAppsAndFeaturesEntryLimit, + + /// Only zero or one value for Commands may be specified for InstallerType portable. + ExceededCommandsLimit, + + /// Only one entry for NestedInstallerFiles can be specified for non-portable InstallerTypes. + ExceededNestedInstallerFilesLimit, + + /// Silent and SilentWithProgress switches are not specified for InstallerType exe. Please make sure the installer can run unattended. + ExeInstallerMissingSilentSwitches, + + /// Duplicate field found in the manifest. + FieldDuplicate, + + /// Failed to process field. + FieldFailedToProcess, + + /// All field names should be PascalCased. + FieldIsNotPascalCase, + + /// Field is not supported. + FieldNotSupported, + + /// Field usage requires verified publishers. + FieldRequireVerifiedPublisher, + + /// Unknown field. + FieldUnknown, + + /// Field value is not supported. + FieldValueNotSupported, + + /// Loop found. + FoundDependencyLoop, + + /// The multi file manifest is incomplete. A multi file manifest must contain at least version, installer and defaultLocale manifest. + IncompleteMultiFileManifest, + + /// The values of InstallerSha256 do not match for all instances of the same InstallerUrl. + InconsistentInstallerHash, + + /// DefaultLocale value in version manifest does not match PackageLocale value in defaultLocale manifest. + InconsistentMultiFileManifestDefaultLocale, + + /// The multi file manifest has inconsistent field values. + InconsistentMultiFileManifestFieldValue, + + /// Failed to process installer. + InstallerFailedToProcess, + + /// Inconsistent value in the manifest. + InstallerMsixInconsistencies, + + /// The specified installer type does not support PackageFamilyName. + InstallerTypeDoesNotSupportPackageFamilyName, + + /// The specified installer type does not support ProductCode. + InstallerTypeDoesNotSupportProductCode, + + /// The specified installer type does not write to Apps and Features entry. + InstallerTypeDoesNotWriteAppsAndFeaturesEntry, + + /// The locale value is not a well formed bcp47 language tag. + InvalidBcp47Value, + + /// Invalid field value. + InvalidFieldValue, + + /// Contains invalid MSI switches. + InvalidMsiSwitches, + + /// Encountered unexpected root node. + InvalidRootNode, + + /// The provided value is not a valid Windows feature name. + InvalidWindowsFeatureName, + + /// Dependency not found: + MissingManifestDependenciesNode, + + /// Failed to calculate MSIX signature hash. Please verify that the input file is a valid, signed MSIX. + MsixSignatureHashFailed, + + /// Deleting the manifest will break the following dependencies. + MultiManifestPackageHasDependencies, + + /// No Suitable Minimum Version: + NoSuitableMinVersionDependency, + + /// No supported platforms. + NoSupportedPlatforms, + + /// Optional field missing. + OptionalFieldMissing, + + /// Portable command alias must not point to a location outside of base directory. + PortableCommandAliasEscapesDirectory, + + /// Relative file path must not point to a location outside of archive directory. + RelativeFilePathEscapesDirectory, + + /// Required field with empty value. + RequiredFieldEmpty, + + /// Required field missing. + RequiredFieldMissing, + + /// Schema Error. + SchemaError, + + /// Scope is not supported for InstallerType portable. + ScopeNotSupported, + + /// Shadow manifest is not allowed. + ShadowManifestNotAllowed, + + /// Package has a single manifest and is a dependency of other manifests. + SingleManifestPackageHasDependencies, + + /// The multi file manifest should not contain file with the particular ManifestType. + UnsupportedMultiFileManifestType, + + /// Schema header not found. + SchemaHeaderNotFound, + + /// The schema header is invalid. Please verify that the schema header is present and formatted correctly. + InvalidSchemaHeader, + + /// The manifest type in the schema header does not match the ManifestType property value in the manifest. + SchemaHeaderManifestTypeMismatch, + + /// The manifest version in the schema header does not match the ManifestVersion property value in the manifest. + SchemaHeaderManifestVersionMismatch, + + /// The schema header URL does not match the expected pattern. + SchemaHeaderUrlPatternMismatch, + + /// The file type of the referenced file is not allowed. + InvalidPortableFiletype, + + /// The file type of the referenced file is not a supported font file type. + InvalidFontFiletype, + } +} diff --git a/src/WinGetUtilInterop/Interfaces/IWinGetSQLiteIndex.cs b/src/WinGetUtilInterop/Interfaces/IWinGetSQLiteIndex.cs index 15c1e30d6f..b819e6fcc5 100644 --- a/src/WinGetUtilInterop/Interfaces/IWinGetSQLiteIndex.cs +++ b/src/WinGetUtilInterop/Interfaces/IWinGetSQLiteIndex.cs @@ -1,97 +1,97 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGetUtil.Interfaces -{ - using System; - - /// - /// The properties that can be set with IWinGetSQLiteIndex::SetProperty. - /// The values must match those in WinGetUtil.h. - /// - public enum SQLiteIndexProperty - { - /// - /// The base time to use for update tracking. The value is in the Unix epoch. - /// Set to an empty string to use the current time. - /// Set to 0 to force all files to be output. - /// - PackageUpdateTrackingBaseTime = 0, - - /// - /// The full path to a base directory where intermediate files will be output. - /// The path does not need to exist, and may not be created if no files need to be written. - /// - IntermediateFileOutputPath = 1, - } - - /// - /// Interface for index operations. - /// - public interface IWinGetSQLiteIndex : IDisposable - { - /// - /// Migrates the index to the given version. - /// - /// Major version. - /// Minor version. - void MigrateTo(uint majorVersion, uint minorVersion); - - /// - /// Sets the given property to the given value. - /// - /// The property to set. - /// The value to set. - void SetProperty(SQLiteIndexProperty property, string value); - - /// - /// Adds manifest to index. - /// - /// Manifest to add. - /// Path of the manifest in the repository. - void AddManifest(string manifestPath, string relativePath); - - /// - /// Updates manifest in the index. - /// - /// Path to manifest to modify. - /// Path of the manifest in the repository. - /// True if index was modified. - bool UpdateManifest(string manifestPath, string relativePath); - - /// - /// Adds or Updates manifest in the index. - /// - /// Path to manifest. - /// Path of the manifest in the repository. - /// True if added; false if updated. - bool AddOrUpdateManifest(string manifestPath, string relativePath); - - /// - /// Delete manifest from index. - /// - /// Path to manifest to modify. - /// Path of the manifest in the repository. - void RemoveManifest(string manifestPath, string relativePath); - - /// - /// Wrapper for WinGetSQLiteIndexPrepareForPackaging. - /// - void PrepareForPackaging(); - - /// - /// Checks the index for consistency, ensuring that at a minimum all referenced rows actually exist. - /// - /// Is index consistent. - bool IsIndexConsistent(); - - /// - /// Gets the managed index handle. It is used in additional manifest validation that requires an index. - /// - /// The managed index handle. - IntPtr GetIndexHandle(); - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Interfaces +{ + using System; + + /// + /// The properties that can be set with IWinGetSQLiteIndex::SetProperty. + /// The values must match those in WinGetUtil.h. + /// + public enum SQLiteIndexProperty + { + /// + /// The base time to use for update tracking. The value is in the Unix epoch. + /// Set to an empty string to use the current time. + /// Set to 0 to force all files to be output. + /// + PackageUpdateTrackingBaseTime = 0, + + /// + /// The full path to a base directory where intermediate files will be output. + /// The path does not need to exist, and may not be created if no files need to be written. + /// + IntermediateFileOutputPath = 1, + } + + /// + /// Interface for index operations. + /// + public interface IWinGetSQLiteIndex : IDisposable + { + /// + /// Migrates the index to the given version. + /// + /// Major version. + /// Minor version. + void MigrateTo(uint majorVersion, uint minorVersion); + + /// + /// Sets the given property to the given value. + /// + /// The property to set. + /// The value to set. + void SetProperty(SQLiteIndexProperty property, string value); + + /// + /// Adds manifest to index. + /// + /// Manifest to add. + /// Path of the manifest in the repository. + void AddManifest(string manifestPath, string relativePath); + + /// + /// Updates manifest in the index. + /// + /// Path to manifest to modify. + /// Path of the manifest in the repository. + /// True if index was modified. + bool UpdateManifest(string manifestPath, string relativePath); + + /// + /// Adds or Updates manifest in the index. + /// + /// Path to manifest. + /// Path of the manifest in the repository. + /// True if added; false if updated. + bool AddOrUpdateManifest(string manifestPath, string relativePath); + + /// + /// Delete manifest from index. + /// + /// Path to manifest to modify. + /// Path of the manifest in the repository. + void RemoveManifest(string manifestPath, string relativePath); + + /// + /// Wrapper for WinGetSQLiteIndexPrepareForPackaging. + /// + void PrepareForPackaging(); + + /// + /// Checks the index for consistency, ensuring that at a minimum all referenced rows actually exist. + /// + /// Is index consistent. + bool IsIndexConsistent(); + + /// + /// Gets the managed index handle. It is used in additional manifest validation that requires an index. + /// + /// The managed index handle. + IntPtr GetIndexHandle(); + } +} diff --git a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs index 2c6eebbb1c..eb09b369fe 100644 --- a/src/WinGetUtilInterop/Manifest/ManifestVersion.cs +++ b/src/WinGetUtilInterop/Manifest/ManifestVersion.cs @@ -1,73 +1,73 @@ -// ----------------------------------------------------------------------------- -// -// Copyright (c) Microsoft Corporation. Licensed under the MIT License. -// -// ----------------------------------------------------------------------------- - -namespace Microsoft.WinGetUtil.Manifest -{ - /// - /// Supported manifest version values. - /// - public static class ManifestVersion - { -#pragma warning disable SA1310 // Field names should not contain underscore - - /// - /// V1 manifest version for GA. - /// - public const string ManifestVersionV1 = "1.0.0"; - - /// - /// V1.1 manifest version. - /// - public const string ManifestVersionV1_1 = "1.1.0"; - - /// - /// V1.2 manifest version. - /// - public const string ManifestVersionV1_2 = "1.2.0"; - - /// - /// V1.4 manifest version. - /// - public const string ManifestVersionV1_4 = "1.4.0"; - - /// - /// V1.5 manifest version. - /// - public const string ManifestVersionV1_5 = "1.5.0"; - - /// - /// V1.6 manifest version. - /// - public const string ManifestVersionV1_6 = "1.6.0"; - - /// - /// V1.7 manifest version. - /// - public const string ManifestVersionV1_7 = "1.7.0"; - - /// - /// V1.9 manifest version. - /// - public const string ManifestVersionV1_9 = "1.9.0"; - - /// - /// V1.10 manifest version. - /// - public const string ManifestVersionV1_10 = "1.10.0"; - - /// - /// V1.12 manifest version. - /// - public const string ManifestVersionV1_12 = "1.12.0"; - - /// - /// V1.28 manifest version. - /// - public const string ManifestVersionV1_28 = "1.28.0"; - -#pragma warning restore SA1310 // Field names should not contain underscore - } -} +// ----------------------------------------------------------------------------- +// +// Copyright (c) Microsoft Corporation. Licensed under the MIT License. +// +// ----------------------------------------------------------------------------- + +namespace Microsoft.WinGetUtil.Manifest +{ + /// + /// Supported manifest version values. + /// + public static class ManifestVersion + { +#pragma warning disable SA1310 // Field names should not contain underscore + + /// + /// V1 manifest version for GA. + /// + public const string ManifestVersionV1 = "1.0.0"; + + /// + /// V1.1 manifest version. + /// + public const string ManifestVersionV1_1 = "1.1.0"; + + /// + /// V1.2 manifest version. + /// + public const string ManifestVersionV1_2 = "1.2.0"; + + /// + /// V1.4 manifest version. + /// + public const string ManifestVersionV1_4 = "1.4.0"; + + /// + /// V1.5 manifest version. + /// + public const string ManifestVersionV1_5 = "1.5.0"; + + /// + /// V1.6 manifest version. + /// + public const string ManifestVersionV1_6 = "1.6.0"; + + /// + /// V1.7 manifest version. + /// + public const string ManifestVersionV1_7 = "1.7.0"; + + /// + /// V1.9 manifest version. + /// + public const string ManifestVersionV1_9 = "1.9.0"; + + /// + /// V1.10 manifest version. + /// + public const string ManifestVersionV1_10 = "1.10.0"; + + /// + /// V1.12 manifest version. + /// + public const string ManifestVersionV1_12 = "1.12.0"; + + /// + /// V1.28 manifest version. + /// + public const string ManifestVersionV1_28 = "1.28.0"; + +#pragma warning restore SA1310 // Field names should not contain underscore + } +} diff --git a/src/WinGetUtilInterop/Manifest/V1/InstallerExpectedReturnCode.cs b/src/WinGetUtilInterop/Manifest/V1/InstallerExpectedReturnCode.cs index 56d543521f..837ec52b41 100644 --- a/src/WinGetUtilInterop/Manifest/V1/InstallerExpectedReturnCode.cs +++ b/src/WinGetUtilInterop/Manifest/V1/InstallerExpectedReturnCode.cs @@ -19,11 +19,11 @@ public class InstallerExpectedReturnCode /// /// Gets or sets the corresponding response category. /// - public string ReturnResponse { get; set; } - + public string ReturnResponse { get; set; } + /// /// Gets or sets the corresponding response url. /// public string ReturnResponseUrl { get; set; } } -} +} diff --git a/src/WinGetUtilInterop/Manifest/V1/ManifestShadow.cs b/src/WinGetUtilInterop/Manifest/V1/ManifestShadow.cs index b42e61b614..19cdd1f6e8 100644 --- a/src/WinGetUtilInterop/Manifest/V1/ManifestShadow.cs +++ b/src/WinGetUtilInterop/Manifest/V1/ManifestShadow.cs @@ -5,8 +5,8 @@ // ----------------------------------------------------------------------------- namespace Microsoft.WinGetUtil.Manifest.V1 -{ - using System; +{ + using System; using System.Collections.Generic; using System.IO; using Microsoft.WinGetUtil.Common; @@ -39,8 +39,8 @@ public sealed class ManifestShadow /// /// Gets or sets the manifest version. /// - public string ManifestVersion { get; set; } - + public string ManifestVersion { get; set; } + /// /// Gets or sets the default locale. /// @@ -56,17 +56,17 @@ public sealed class ManifestShadow /// public List Localization { get; set; } - /// - /// Creates a shadow manifest. - /// Assigns the correct ManifestType. - /// + /// + /// Creates a shadow manifest. + /// Assigns the correct ManifestType. + /// /// A shadow manifest. - public static ManifestShadow CreateManifest() - { - return new ManifestShadow() - { - ManifestType = "shadow", - }; + public static ManifestShadow CreateManifest() + { + return new ManifestShadow() + { + ManifestType = "shadow", + }; } /// @@ -120,17 +120,17 @@ public static ManifestShadow CreateManifestFromString(string value) var shadow = deserializer.Deserialize(value); shadow.Validate(); return shadow; - } - - /// - /// Serializes the shadow manifest. - /// + } + + /// + /// Serializes the shadow manifest. + /// /// Serialized shadow manifest as string. - public string Serialize() - { - this.Validate(); - var serializer = Helpers.CreateSerializer(); - return serializer.Serialize(this); + public string Serialize() + { + this.Validate(); + var serializer = Helpers.CreateSerializer(); + return serializer.Serialize(this); } private void Validate() diff --git a/src/WinGetUtilInterop/scripts/CreateLocalNuget.ps1 b/src/WinGetUtilInterop/scripts/CreateLocalNuget.ps1 index 4121499365..5b8b9a9990 100644 --- a/src/WinGetUtilInterop/scripts/CreateLocalNuget.ps1 +++ b/src/WinGetUtilInterop/scripts/CreateLocalNuget.ps1 @@ -1,85 +1,85 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -<# - .SYNOPSIS - Creates a local Microsoft.WindowsPackageManager.Utils nuget package and add it to a local nuget feed. -#> - -[CmdletBinding()] -param ( - [Parameter(Mandatory)] - [ValidateSet("Debug", "Release", "ReleaseStatic")] - [string] - $Configuration, - - [Parameter(Mandatory)] - [string] - $NugetVersion, - - [string] - $BuildRoot = "", - - [string] - $LocalNugetSource = "" -) - -if ($BuildRoot -eq "") -{ - $BuildRoot = "$PSScriptRoot\..\.."; -} - -$local:repoPath = "$PSScriptRoot\..\..\..\" - -# Create all directories and copy files in location expected from the nuspec. -# The paths contains 'release' but it whatever configuration the param is. -$local:nugetWorkingDir = "$PSScriptRoot\NugetFiles" -$local:x64NugetPath = "$nugetWorkingDir\Build.x64release\src\x64\Release\WinGetUtil" -$local:x86NugetPath = "$nugetWorkingDir\Build.x86release\src\x86\Release\WinGetUtil" -$local:manifestsNugetPath = "$nugetWorkingDir\Build.x64release\schemas\JSON\manifests" -$local:interopNugetPath = "$nugetWorkingDir\Build.x64release\src\WinGetUtilInterop\bin\Release\netstandard2.1" -$local:targetsNugetPath = "$nugetWorkingDir\Build.x64release\src\WinGetUtilInterop\build" - -Write-Host "Prepare nuget files" -if (Test-Path $nugetWorkingDir) -{ - Remove-Item $nugetWorkingDir -Recurse -} -New-Item $nugetWorkingDir -ItemType directory | Out-Null -New-Item $x64NugetPath -ItemType directory | Out-Null -New-Item $x86NugetPath -ItemType directory | Out-Null -New-Item $manifestsNugetPath -ItemType directory | Out-Null -New-Item $interopNugetPath -ItemType directory | Out-Null -New-Item $targetsNugetPath -ItemType directory | Out-Null - -function CopyFile([string]$in, [string]$out) -{ - $copyErrors = $null - Copy-Item $in $out -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue - $copyErrors | ForEach-Object { Write-Warning $_ } -} - -CopyFile "$BuildRoot\x64\$Configuration\WinGetUtil\WinGetUtil.dll" "$x64NugetPath\WinGetUtil.dll" -CopyFile "$BuildRoot\x64\$Configuration\WinGetUtil\WinGetUtil.pdb" "$x64NugetPath\WinGetUtil.pdb" -CopyFile "$BuildRoot\x86\$Configuration\WinGetUtil\WinGetUtil.dll" "$x86NugetPath\WinGetUtil.dll" -CopyFile "$BuildRoot\x86\$Configuration\WinGetUtil\WinGetUtil.pdb" "$x86NugetPath\WinGetUtil.pdb" -CopyFile "$repoPath\src\WinGetUtilInterop\bin\$Configuration\netstandard2.1\WinGetUtilInterop.dll" "$interopNugetPath\WinGetUtilInterop.dll" -CopyFile "$repoPath\src\WinGetUtilInterop\bin\$Configuration\netstandard2.1\WinGetUtilInterop.pdb" "$interopNugetPath\WinGetUtilInterop.pdb" -CopyFile "$repoPath\src\WinGetUtilInterop\build\Microsoft.WindowsPackageManager.Utils.targets" "$targetsNugetPath\Microsoft.WindowsPackageManager.Utils.targets" -CopyFile "$PSScriptRoot\WinGetUtilDev.nuspec" "$nugetWorkingDir\WinGetUtilDev.nuspec" -Copy-Item "$repoPath\schemas\JSON\manifests" $manifestsNugetPath -Recurse - -# Create nuget -Write-Host "Creating nuget package" -$local:result = nuget pack .\NugetFiles\WinGetUtilDev.nuspec -Version $NugetVersion -OutputDirectory NugetOut -$local:outFile = $result -match "nupkg" -$outFile = $outFile[0] -$outFile = $outFile.Substring($outFile.IndexOf("'") + 1, $outFile.LastIndexOf("'") - $outFile.IndexOf("'") - 1) -Write-Host "Created $outFile" - -if ($LocalNugetSource -ne "") -{ - Write-Host "Adding $outFile to local nuget feed" - nuget add $outFile -Source $LocalNugetSource -} - +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# + .SYNOPSIS + Creates a local Microsoft.WindowsPackageManager.Utils nuget package and add it to a local nuget feed. +#> + +[CmdletBinding()] +param ( + [Parameter(Mandatory)] + [ValidateSet("Debug", "Release", "ReleaseStatic")] + [string] + $Configuration, + + [Parameter(Mandatory)] + [string] + $NugetVersion, + + [string] + $BuildRoot = "", + + [string] + $LocalNugetSource = "" +) + +if ($BuildRoot -eq "") +{ + $BuildRoot = "$PSScriptRoot\..\.."; +} + +$local:repoPath = "$PSScriptRoot\..\..\..\" + +# Create all directories and copy files in location expected from the nuspec. +# The paths contains 'release' but it whatever configuration the param is. +$local:nugetWorkingDir = "$PSScriptRoot\NugetFiles" +$local:x64NugetPath = "$nugetWorkingDir\Build.x64release\src\x64\Release\WinGetUtil" +$local:x86NugetPath = "$nugetWorkingDir\Build.x86release\src\x86\Release\WinGetUtil" +$local:manifestsNugetPath = "$nugetWorkingDir\Build.x64release\schemas\JSON\manifests" +$local:interopNugetPath = "$nugetWorkingDir\Build.x64release\src\WinGetUtilInterop\bin\Release\netstandard2.1" +$local:targetsNugetPath = "$nugetWorkingDir\Build.x64release\src\WinGetUtilInterop\build" + +Write-Host "Prepare nuget files" +if (Test-Path $nugetWorkingDir) +{ + Remove-Item $nugetWorkingDir -Recurse +} +New-Item $nugetWorkingDir -ItemType directory | Out-Null +New-Item $x64NugetPath -ItemType directory | Out-Null +New-Item $x86NugetPath -ItemType directory | Out-Null +New-Item $manifestsNugetPath -ItemType directory | Out-Null +New-Item $interopNugetPath -ItemType directory | Out-Null +New-Item $targetsNugetPath -ItemType directory | Out-Null + +function CopyFile([string]$in, [string]$out) +{ + $copyErrors = $null + Copy-Item $in $out -Force -ErrorVariable copyErrors -ErrorAction SilentlyContinue + $copyErrors | ForEach-Object { Write-Warning $_ } +} + +CopyFile "$BuildRoot\x64\$Configuration\WinGetUtil\WinGetUtil.dll" "$x64NugetPath\WinGetUtil.dll" +CopyFile "$BuildRoot\x64\$Configuration\WinGetUtil\WinGetUtil.pdb" "$x64NugetPath\WinGetUtil.pdb" +CopyFile "$BuildRoot\x86\$Configuration\WinGetUtil\WinGetUtil.dll" "$x86NugetPath\WinGetUtil.dll" +CopyFile "$BuildRoot\x86\$Configuration\WinGetUtil\WinGetUtil.pdb" "$x86NugetPath\WinGetUtil.pdb" +CopyFile "$repoPath\src\WinGetUtilInterop\bin\$Configuration\netstandard2.1\WinGetUtilInterop.dll" "$interopNugetPath\WinGetUtilInterop.dll" +CopyFile "$repoPath\src\WinGetUtilInterop\bin\$Configuration\netstandard2.1\WinGetUtilInterop.pdb" "$interopNugetPath\WinGetUtilInterop.pdb" +CopyFile "$repoPath\src\WinGetUtilInterop\build\Microsoft.WindowsPackageManager.Utils.targets" "$targetsNugetPath\Microsoft.WindowsPackageManager.Utils.targets" +CopyFile "$PSScriptRoot\WinGetUtilDev.nuspec" "$nugetWorkingDir\WinGetUtilDev.nuspec" +Copy-Item "$repoPath\schemas\JSON\manifests" $manifestsNugetPath -Recurse + +# Create nuget +Write-Host "Creating nuget package" +$local:result = nuget pack .\NugetFiles\WinGetUtilDev.nuspec -Version $NugetVersion -OutputDirectory NugetOut +$local:outFile = $result -match "nupkg" +$outFile = $outFile[0] +$outFile = $outFile.Substring($outFile.IndexOf("'") + 1, $outFile.LastIndexOf("'") - $outFile.IndexOf("'") - 1) +Write-Host "Created $outFile" + +if ($LocalNugetSource -ne "") +{ + Write-Host "Adding $outFile to local nuget feed" + nuget add $outFile -Source $LocalNugetSource +} + diff --git a/src/WinGetUtilInterop/scripts/WinGetUtilDev.nuspec b/src/WinGetUtilInterop/scripts/WinGetUtilDev.nuspec index d1b92f0e46..a48047c359 100644 --- a/src/WinGetUtilInterop/scripts/WinGetUtilDev.nuspec +++ b/src/WinGetUtilInterop/scripts/WinGetUtilDev.nuspec @@ -1,31 +1,31 @@ - - - - Microsoft.WindowsPackageManager.Utils - $version$ - - Microsoft - - https://github.com/microsoft/winget-cli - MIT - true - The utility binary for use with the WinGet CLI. - © Microsoft Corporation. All rights reserved. - winget - - - - - - - - - - - - - - - - + + + + Microsoft.WindowsPackageManager.Utils + $version$ + + Microsoft + + https://github.com/microsoft/winget-cli + MIT + true + The utility binary for use with the WinGet CLI. + © Microsoft Corporation. All rights reserved. + winget + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WinGetYamlFuzzing/OneFuzzConfig.json b/src/WinGetYamlFuzzing/OneFuzzConfig.json index d431bb29be..1addd3f4c0 100644 --- a/src/WinGetYamlFuzzing/OneFuzzConfig.json +++ b/src/WinGetYamlFuzzing/OneFuzzConfig.json @@ -1,37 +1,37 @@ -{ - "ConfigVersion": 3, - "Entries": [ - { - "JobNotificationEmail": "peetdev@microsoft.com", - "Skip": false, - "Fuzzer": { - "$type": "libfuzzer", - "FuzzingHarnessExecutableName": "WinGetYamlFuzzing.exe" - }, - "OneFuzzJobs": [ - { - "ProjectName": "winget-fuzzing", - "TargetName": "yamlFuzzer" - } - ], - "JobDependencies": [ - "WinGetYamlFuzzing.exe", - "WinGetYamlFuzzing.pdb", - "WinGetYamlFuzzing.lib", - "clang_rt.asan_dynamic*.dll" - ], - "AdoTemplate": { - "Org": "microsoft", - "Project": "OS", - "AssignedTo": "ranm@microsoft.com", - "AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\InstaDev", - "IterationPath": "OS" - }, - "codeCoverage": { - "org": "ms", - "project": "winget-cli", - "pipelineId": "630" - } - } - ] -} +{ + "ConfigVersion": 3, + "Entries": [ + { + "JobNotificationEmail": "peetdev@microsoft.com", + "Skip": false, + "Fuzzer": { + "$type": "libfuzzer", + "FuzzingHarnessExecutableName": "WinGetYamlFuzzing.exe" + }, + "OneFuzzJobs": [ + { + "ProjectName": "winget-fuzzing", + "TargetName": "yamlFuzzer" + } + ], + "JobDependencies": [ + "WinGetYamlFuzzing.exe", + "WinGetYamlFuzzing.pdb", + "WinGetYamlFuzzing.lib", + "clang_rt.asan_dynamic*.dll" + ], + "AdoTemplate": { + "Org": "microsoft", + "Project": "OS", + "AssignedTo": "ranm@microsoft.com", + "AreaPath": "OS\\Windows Client and Services\\WinPD\\DFX-Developer Fundamentals and Experiences\\DEFT\\InstaDev", + "IterationPath": "OS" + }, + "codeCoverage": { + "org": "ms", + "project": "winget-cli", + "pipelineId": "630" + } + } + ] +} diff --git a/src/WinGetYamlFuzzing/README.md b/src/WinGetYamlFuzzing/README.md index a6bcb515d9..9613a73b94 100644 --- a/src/WinGetYamlFuzzing/README.md +++ b/src/WinGetYamlFuzzing/README.md @@ -1,20 +1,20 @@ ---- -author: Ryan Fu @ryfu-msft -last updated: 02/07/2024 ---- - -# WinGetYamlFuzzing - -The goal of this project is to create a [libFuzzer](http://llvm.org/docs/LibFuzzer.html) based fuzzer for our YAML manifest parsing. - -This project only supports the `Fuzzing` configuration in either the `x64` or `x86` platform. The build output directory will be located at `$(ProjectDirectory)\src\$(Platform)\Fuzzing\` - -WinGetYamlFuzzer is compiled with `/fsanitize=fuzzer`. This injects the LibFuzzer main function which invokes `LLVMFuzzerTestOneInput`. The LibFuzzer engine code is statically linked into the WinGetYamlFuzzer executable, which is how OneFuzz will interact with the fuzzer by providing the appropriate command-line arguments. - -The fuzzer and all libraries that it references need to be compiled with ASan and SanCov (along with various SanCov compiler flags). In order to run the fuzzer, the ASan runtime DLL is required. This file is copied to the output directory as a post-build step from `$(VCToolsInstallDir)\bin\Hostx64\x64\clang_rt.asan_dynamic-x86_64.dll​`. - -## Submitting fuzzing artifacts to OneFuzz - -The `OneFuzzConfig.json` file contains the information required to submit the fuzzing artifacts. This is where the job dependencies are specified, which includes the fuzzer executable (WinGetYamlFuzzer.exe) and all referenced libraries. This file is copied to the fuzzing build output directory. - -The `onefuzz-task@0` task called in our build pipeline yaml file will handle submitting all of the specified fuzzing artifacts to the OneFuzz service which will run the fuzzer and generate ADO bugs to our team if any are encountered. All of the specified job dependencies must be present when submitting to the OneFuzz ADO tas including the OneFuzzConfig.json file. +--- +author: Ryan Fu @ryfu-msft +last updated: 02/07/2024 +--- + +# WinGetYamlFuzzing + +The goal of this project is to create a [libFuzzer](http://llvm.org/docs/LibFuzzer.html) based fuzzer for our YAML manifest parsing. + +This project only supports the `Fuzzing` configuration in either the `x64` or `x86` platform. The build output directory will be located at `$(ProjectDirectory)\src\$(Platform)\Fuzzing\` + +WinGetYamlFuzzer is compiled with `/fsanitize=fuzzer`. This injects the LibFuzzer main function which invokes `LLVMFuzzerTestOneInput`. The LibFuzzer engine code is statically linked into the WinGetYamlFuzzer executable, which is how OneFuzz will interact with the fuzzer by providing the appropriate command-line arguments. + +The fuzzer and all libraries that it references need to be compiled with ASan and SanCov (along with various SanCov compiler flags). In order to run the fuzzer, the ASan runtime DLL is required. This file is copied to the output directory as a post-build step from `$(VCToolsInstallDir)\bin\Hostx64\x64\clang_rt.asan_dynamic-x86_64.dll​`. + +## Submitting fuzzing artifacts to OneFuzz + +The `OneFuzzConfig.json` file contains the information required to submit the fuzzing artifacts. This is where the job dependencies are specified, which includes the fuzzer executable (WinGetYamlFuzzer.exe) and all referenced libraries. This file is copied to the fuzzing build output directory. + +The `onefuzz-task@0` task called in our build pipeline yaml file will handle submitting all of the specified fuzzing artifacts to the OneFuzz service which will run the fuzzer and generate ADO bugs to our team if any are encountered. All of the specified job dependencies must be present when submitting to the OneFuzz ADO tas including the OneFuzzConfig.json file. diff --git a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp index 6800a0a128..8ba3a1fbfd 100644 --- a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp +++ b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.cpp @@ -1,58 +1,58 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include -#include -#include - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) -{ - std::string input{ reinterpret_cast(data), size }; - - try - { - AppInstaller::Manifest::Manifest manifest = AppInstaller::Manifest::YamlParser::Create(input); - } - catch (...) {} - - return 0; -} - -#ifndef WINGET_DISABLE_FOR_FUZZING - -#include - -// Emulate libFuzzer main by just sending all files in the corpus (last arg) to the fuzzer. -int main(int argc, char** argv) -{ - if (argc <= 1) - { - return 1; - } - - std::filesystem::path corpus = argv[argc - 1]; - - if (std::filesystem::is_directory(corpus)) - { - for (auto& file : std::filesystem::directory_iterator{ corpus }) - { - if (!file.is_directory()) - { - std::ifstream stream{ file.path(), std::ios_base::in | std::ios_base::binary }; - std::string contents = AppInstaller::Utility::ReadEntireStream(stream); - - LLVMFuzzerTestOneInput(reinterpret_cast(contents.data()), contents.size()); - } - } - } - else - { - std::ifstream stream{ corpus, std::ios_base::in | std::ios_base::binary }; - std::string contents = AppInstaller::Utility::ReadEntireStream(stream); - - LLVMFuzzerTestOneInput(reinterpret_cast(contents.data()), contents.size()); - } - - return 0; -} - -#endif +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t * data, size_t size) +{ + std::string input{ reinterpret_cast(data), size }; + + try + { + AppInstaller::Manifest::Manifest manifest = AppInstaller::Manifest::YamlParser::Create(input); + } + catch (...) {} + + return 0; +} + +#ifndef WINGET_DISABLE_FOR_FUZZING + +#include + +// Emulate libFuzzer main by just sending all files in the corpus (last arg) to the fuzzer. +int main(int argc, char** argv) +{ + if (argc <= 1) + { + return 1; + } + + std::filesystem::path corpus = argv[argc - 1]; + + if (std::filesystem::is_directory(corpus)) + { + for (auto& file : std::filesystem::directory_iterator{ corpus }) + { + if (!file.is_directory()) + { + std::ifstream stream{ file.path(), std::ios_base::in | std::ios_base::binary }; + std::string contents = AppInstaller::Utility::ReadEntireStream(stream); + + LLVMFuzzerTestOneInput(reinterpret_cast(contents.data()), contents.size()); + } + } + } + else + { + std::ifstream stream{ corpus, std::ios_base::in | std::ios_base::binary }; + std::string contents = AppInstaller::Utility::ReadEntireStream(stream); + + LLVMFuzzerTestOneInput(reinterpret_cast(contents.data()), contents.size()); + } + + return 0; +} + +#endif diff --git a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj index 84889b932a..f47a72e910 100644 --- a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj +++ b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj @@ -1,111 +1,111 @@ - - - - - - Fuzzing - x64 - - - Fuzzing - Win32 - - - - 16.0 - Win32Proj - {1622da16-914f-4f57-a259-d5169003cc8c} - WinGetYamlFuzzing - 10.0 - - - - Application - false - false - true - true - - - - - - - - - - - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;WINGET_DISABLE_FOR_FUZZING;%(PreprocessorDefinitions);_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION - true - stdcpp17 - $(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) - MultiThreaded - /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div %(AdditionalOptions) - - - Console - true - true - true - legacy_stdio_definitions.lib;libsancov.lib;icuuc.lib;icuin.lib;urlmon.lib;Bcrypt.lib;wininet.lib;shlwapi.lib;Crypt32.lib;%(AdditionalDependencies) - - - xcopy /y "$(VCToolsInstallDir)bin\Hostx64\x64\clang_rt.asan_dynamic-x86_64.dll" "$(OutDir)" - Copy the required ASan runtime DLL to the output directory. - - - xcopy /y "$(VCToolsInstallDir)bin\Hostx64\x86\clang_rt.asan_dynamic-i386.dll" "$(OutDir)" - Copy the required ASan runtime DLL to the output directory. - - - - - - - - {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} - - - {5890d6ed-7c3b-40f3-b436-b54f640d9e65} - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + + Fuzzing + x64 + + + Fuzzing + Win32 + + + + 16.0 + Win32Proj + {1622da16-914f-4f57-a259-d5169003cc8c} + WinGetYamlFuzzing + 10.0 + + + + Application + false + false + true + true + + + + + + + + + + + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;WINGET_DISABLE_FOR_FUZZING;%(PreprocessorDefinitions);_DISABLE_VECTOR_ANNOTATION;_DISABLE_STRING_ANNOTATION + true + stdcpp17 + $(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;%(AdditionalIncludeDirectories) + MultiThreaded + /fsanitize=address /fsanitize-coverage=inline-8bit-counters /fsanitize-coverage=edge /fsanitize-coverage=trace-cmp /fsanitize-coverage=trace-div %(AdditionalOptions) + + + Console + true + true + true + legacy_stdio_definitions.lib;libsancov.lib;icuuc.lib;icuin.lib;urlmon.lib;Bcrypt.lib;wininet.lib;shlwapi.lib;Crypt32.lib;%(AdditionalDependencies) + + + xcopy /y "$(VCToolsInstallDir)bin\Hostx64\x64\clang_rt.asan_dynamic-x86_64.dll" "$(OutDir)" + Copy the required ASan runtime DLL to the output directory. + + + xcopy /y "$(VCToolsInstallDir)bin\Hostx64\x86\clang_rt.asan_dynamic-i386.dll" "$(OutDir)" + Copy the required ASan runtime DLL to the output directory. + + + + + + + + {f3f6e699-bc5d-4950-8a05-e49dd9eb0d51} + + + {5890d6ed-7c3b-40f3-b436-b54f640d9e65} + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + diff --git a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj.filters b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj.filters index ae409ae3e8..eadba52630 100644 --- a/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj.filters +++ b/src/WinGetYamlFuzzing/WinGetYamlFuzzing.vcxproj.filters @@ -1,32 +1,32 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - - - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + + + + + + + + \ No newline at end of file diff --git a/src/WinGetYamlFuzzing/dictionary.txt b/src/WinGetYamlFuzzing/dictionary.txt index 2957aea3aa..e279d0b708 100644 --- a/src/WinGetYamlFuzzing/dictionary.txt +++ b/src/WinGetYamlFuzzing/dictionary.txt @@ -1,18 +1,18 @@ -"[" -"]" -"{" -"}" -"-" -"," -"&" -"<<" -":" -"|" -"!!" -">" -"\"" -"'" - -integer="123" -float="12.5" -mantissa="1.3e+9" +"[" +"]" +"{" +"}" +"-" +"," +"&" +"<<" +":" +"|" +"!!" +">" +"\"" +"'" + +integer="123" +float="12.5" +mantissa="1.3e+9" diff --git a/src/WinGetYamlFuzzing/packages.config b/src/WinGetYamlFuzzing/packages.config index 18270c6de7..f83207db21 100644 --- a/src/WinGetYamlFuzzing/packages.config +++ b/src/WinGetYamlFuzzing/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp b/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp index 54aa42531b..6322b83dad 100644 --- a/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp +++ b/src/WindowsPackageManager/ConfigurationStaticFunctions.cpp @@ -1,324 +1,324 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace AppInstaller::SelfManagement; -using namespace winrt::Microsoft::Management::Deployment; -using namespace winrt::Windows::Foundation::Collections; - -namespace ConfigurationShim -{ - namespace - { - static std::atomic_bool s_canBeCreated{ true }; - - auto GetInternalStatics() - { - return winrt::Microsoft::Management::Configuration::ConfigurationStaticFunctions().as(); - } - - void BlockNewWorkForShutdown(AppInstaller::CancelReason) - { - GetInternalStatics()->BlockNewWorkForShutdown(); - } - - void BeginShutdown(AppInstaller::CancelReason) - { - GetInternalStatics()->BeginShutdown(); - } - - void WaitForShutdown() - { - GetInternalStatics()->WaitForShutdown(); - } - - void RegisterForShutdownSynchronization() - { - static std::once_flag registerComponentOnceFlag; - std::call_once(registerComponentOnceFlag, - [&]() - { - using namespace AppInstaller::ShutdownMonitoring; - - ServerShutdownSynchronization::ComponentSystem component; - component.BlockNewWork = BlockNewWorkForShutdown; - component.BeginShutdown = BeginShutdown; - component.Wait = WaitForShutdown; - - ServerShutdownSynchronization::AddComponent(component); - }); - } - } - - CLSID CLSID_ConfigurationObjectLifetimeWatcher = { 0x89a8f1d4,0x1e24,0x46a4,{0x9f,0x6c,0x65,0x78,0xb0,0x47,0xf2,0xf7} }; - - struct - DECLSPEC_UUID("89a8f1d4-1e24-46a4-9f6c-6578b047f2f7") - ConfigurationObjectLifetimeWatcher : winrt::implements - { - }; - - struct - DECLSPEC_UUID(WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions) - ConfigurationStaticFunctionsShim : winrt::implements - { - ConfigurationStaticFunctionsShim() - { - auto threadGlobalsRestore = m_threadGlobals.SetForCurrentThread(); - auto& diagnosticsLogger = m_threadGlobals.GetDiagnosticLogger(); - diagnosticsLogger.SetEnabledChannels(AppInstaller::Logging::Channel::All); - diagnosticsLogger.SetLevel(AppInstaller::Logging::Level::Verbose); - diagnosticsLogger.AddLogger(std::make_unique("WinGetCFG"sv)); - - if (IsConfigurationAvailable()) - { - m_statics = winrt::Microsoft::Management::Configuration::ConfigurationStaticFunctions().as(); - RegisterForShutdownSynchronization(); - } - } - - winrt::Microsoft::Management::Configuration::ConfigurationUnit CreateConfigurationUnit() - { - THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - if (!m_statics) - { - THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); - } - - auto result = m_statics.CreateConfigurationUnit(); - result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); - return result; - } - - winrt::Microsoft::Management::Configuration::ConfigurationSet CreateConfigurationSet() - { - THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - if (!m_statics) - { - THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); - } - - auto result = m_statics.CreateConfigurationSet(); - result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); - return result; - } - - winrt::Windows::Foundation::IAsyncOperation CreateConfigurationSetProcessorFactoryAsync(winrt::hstring const& handler) - { - THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - auto strong_this{ get_strong() }; - std::wstring lowerHandler = AppInstaller::Utility::ToLower(handler); - - co_await winrt::resume_background(); - - auto threadGlobalsRestore = m_threadGlobals.SetForCurrentThread(); - winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory result; - - if (lowerHandler == AppInstaller::Configuration::PowerShellHandlerIdentifier) - { - result = AppInstaller::CLI::ConfigurationRemoting::CreateOutOfProcessFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::PowerShell); - } - else if (lowerHandler == AppInstaller::Configuration::DynamicRuntimeHandlerIdentifier) - { - result = AppInstaller::CLI::ConfigurationRemoting::CreateDynamicRuntimeFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::PowerShell); - } - else if (lowerHandler == AppInstaller::Configuration::DSCv3HandlerIdentifier) - { - result = AppInstaller::CLI::ConfigurationRemoting::CreateOutOfProcessFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::DSCv3); - } - else if (lowerHandler == AppInstaller::Configuration::DSCv3DynamicRuntimeHandlerIdentifier) - { - result = AppInstaller::CLI::ConfigurationRemoting::CreateDynamicRuntimeFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::DSCv3); - } - - if (result) - { - // Objects returned here *must* implement ILifetimeWatcher for now. - // If we create OOP objects implemented elsewhere in the future, decide then how to exempt those while still ensuring we - // don't accidentally create a lifetime bug by basing it solely off the QI result. - result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); - co_return result; - } - - AICLI_LOG(Config, Error, << "Unknown handler in CreateConfigurationSetProcessorFactory: " << AppInstaller::Utility::ConvertToUTF8(handler)); - THROW_HR(E_NOT_SET); - } - - winrt::Microsoft::Management::Configuration::ConfigurationProcessor CreateConfigurationProcessor(winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory const& factory) - { - THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - if (!m_statics) - { - THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); - } - - auto result = m_statics.CreateConfigurationProcessor(factory); - result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); - return result; - } - - bool IsConfigurationAvailable() - { - return !IsStubPackage(); - } - - winrt::Windows::Foundation::IAsyncActionWithProgress EnsureConfigurationAvailableAsync() - { - THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - if (IsConfigurationAvailable()) - { - return; - } - - auto strong_this{ get_strong() }; - co_await winrt::resume_background(); - - SetStubPreferred(false); - - PackageManager packageManager; - PackageCatalogReference catalogRef{ packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::MicrosoftStore) }; - THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, !catalogRef); - - ConnectResult connectResult = catalogRef.Connect(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED, connectResult.Status() != ConnectResultStatus::Ok); - - PackageCatalog catalog = connectResult.PackageCatalog(); - - FindPackagesOptions findPackagesOptions; - PackageMatchFilter filter; - filter.Field(PackageMatchField::Id); - filter.Option(PackageFieldMatchOption::Equals); - filter.Value(AppInstaller::MSStore::s_AppInstallerProductId); - findPackagesOptions.Filters().Append(filter); - - FindPackagesResult findPackagesResult{ catalog.FindPackages(findPackagesOptions) }; - - auto matches = findPackagesResult.Matches(); - THROW_HR_IF(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE, matches.Size() == 0); - auto catalogPackage = matches.GetAt(0).CatalogPackage(); - - InstallOptions installOptions; - installOptions.AcceptPackageAgreements(true); - installOptions.AllowUpgradeToUnknownVersion(true); - installOptions.Force(true); - - auto progress = co_await winrt::get_progress_token(); - - // Don't use UpgradePackageAsync, we don't support upgrade for packages from the msstore - // it has to be install and internally we know is an update. - auto installTask = packageManager.InstallPackageAsync(catalogPackage, installOptions); - installTask.Progress([progress](auto const&, InstallProgress installProgress) - { - if (installProgress.State == PackageInstallProgressState::Downloading && installProgress.BytesRequired != 0) - { - progress((uint32_t)(installProgress.DownloadProgress * 80)); - } - else if (installProgress.State == PackageInstallProgressState::Installing) - { - progress(((uint32_t)installProgress.InstallationProgress * 20) + 80); - } - }); - - co_await installTask; - s_canBeCreated = false; - } - - winrt::Microsoft::Management::Configuration::ConfigurationParameter CreateConfigurationParameter() - { - THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - if (!m_statics) - { - THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); - } - - auto result = m_statics.CreateConfigurationParameter(); - result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); - return result; - } - - winrt::Microsoft::Management::Configuration::FindUnitProcessorsOptions CreateFindUnitProcessorsOptions() - { - THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - if (!m_statics) - { - THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); - } - - auto result = m_statics.CreateFindUnitProcessorsOptions(); - result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); - return result; - } - - private: - // Returns a lifetime watcher object that is currently *unowned*. - IUnknown* CreateLifetimeWatcher() - { - ::Microsoft::WRL::ComPtr factory; - THROW_IF_FAILED(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().GetClassObject(CLSID_ConfigurationObjectLifetimeWatcher, IID_PPV_ARGS(&factory))); - winrt::com_ptr out; - THROW_IF_FAILED(factory->CreateInstance(nullptr, __uuidof(IUnknown), out.put_void())); - return out.detach(); - } - - winrt::Microsoft::Management::Configuration::IConfigurationStatics3 m_statics = nullptr; - AppInstaller::ThreadLocalStorage::WingetThreadGlobals m_threadGlobals; - }; - - // Enable custom code to run before creating any object through the factory. - template - class ConfigurationFactory : public ::wil::wrl_factory_for_winrt_com_class - { - public: - IFACEMETHODIMP CreateInstance(_In_opt_::IUnknown* unknownOuter, REFIID riid, _COM_Outptr_ void** object) noexcept try - { - *object = nullptr; - RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); - RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::Configuration)); - RETURN_HR_IF(E_ACCESSDENIED, !::AppInstaller::Security::IsCOMCallerSameUserAndIntegrityLevel()); - - RETURN_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); - - return ::wil::wrl_factory_for_winrt_com_class::CreateInstance(unknownOuter, riid, object); - } - CATCH_RETURN() - }; - -#define CoCreatableMicrosoftManagementConfigurationClass(className) \ - CoCreatableClassWithFactory(className, ::ConfigurationShim::ConfigurationFactory) - - // Disable 6388 as it seems to be falsely warning -#pragma warning(push) -#pragma warning(disable : 6388) - CoCreatableCppWinRtClass(ConfigurationObjectLifetimeWatcher); - CoCreatableMicrosoftManagementConfigurationClass(ConfigurationStaticFunctionsShim); -#pragma warning(pop) -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace AppInstaller::SelfManagement; +using namespace winrt::Microsoft::Management::Deployment; +using namespace winrt::Windows::Foundation::Collections; + +namespace ConfigurationShim +{ + namespace + { + static std::atomic_bool s_canBeCreated{ true }; + + auto GetInternalStatics() + { + return winrt::Microsoft::Management::Configuration::ConfigurationStaticFunctions().as(); + } + + void BlockNewWorkForShutdown(AppInstaller::CancelReason) + { + GetInternalStatics()->BlockNewWorkForShutdown(); + } + + void BeginShutdown(AppInstaller::CancelReason) + { + GetInternalStatics()->BeginShutdown(); + } + + void WaitForShutdown() + { + GetInternalStatics()->WaitForShutdown(); + } + + void RegisterForShutdownSynchronization() + { + static std::once_flag registerComponentOnceFlag; + std::call_once(registerComponentOnceFlag, + [&]() + { + using namespace AppInstaller::ShutdownMonitoring; + + ServerShutdownSynchronization::ComponentSystem component; + component.BlockNewWork = BlockNewWorkForShutdown; + component.BeginShutdown = BeginShutdown; + component.Wait = WaitForShutdown; + + ServerShutdownSynchronization::AddComponent(component); + }); + } + } + + CLSID CLSID_ConfigurationObjectLifetimeWatcher = { 0x89a8f1d4,0x1e24,0x46a4,{0x9f,0x6c,0x65,0x78,0xb0,0x47,0xf2,0xf7} }; + + struct + DECLSPEC_UUID("89a8f1d4-1e24-46a4-9f6c-6578b047f2f7") + ConfigurationObjectLifetimeWatcher : winrt::implements + { + }; + + struct + DECLSPEC_UUID(WINGET_OUTOFPROC_COM_CLSID_ConfigurationStaticFunctions) + ConfigurationStaticFunctionsShim : winrt::implements + { + ConfigurationStaticFunctionsShim() + { + auto threadGlobalsRestore = m_threadGlobals.SetForCurrentThread(); + auto& diagnosticsLogger = m_threadGlobals.GetDiagnosticLogger(); + diagnosticsLogger.SetEnabledChannels(AppInstaller::Logging::Channel::All); + diagnosticsLogger.SetLevel(AppInstaller::Logging::Level::Verbose); + diagnosticsLogger.AddLogger(std::make_unique("WinGetCFG"sv)); + + if (IsConfigurationAvailable()) + { + m_statics = winrt::Microsoft::Management::Configuration::ConfigurationStaticFunctions().as(); + RegisterForShutdownSynchronization(); + } + } + + winrt::Microsoft::Management::Configuration::ConfigurationUnit CreateConfigurationUnit() + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + + auto result = m_statics.CreateConfigurationUnit(); + result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); + return result; + } + + winrt::Microsoft::Management::Configuration::ConfigurationSet CreateConfigurationSet() + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + + auto result = m_statics.CreateConfigurationSet(); + result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); + return result; + } + + winrt::Windows::Foundation::IAsyncOperation CreateConfigurationSetProcessorFactoryAsync(winrt::hstring const& handler) + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + auto strong_this{ get_strong() }; + std::wstring lowerHandler = AppInstaller::Utility::ToLower(handler); + + co_await winrt::resume_background(); + + auto threadGlobalsRestore = m_threadGlobals.SetForCurrentThread(); + winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory result; + + if (lowerHandler == AppInstaller::Configuration::PowerShellHandlerIdentifier) + { + result = AppInstaller::CLI::ConfigurationRemoting::CreateOutOfProcessFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::PowerShell); + } + else if (lowerHandler == AppInstaller::Configuration::DynamicRuntimeHandlerIdentifier) + { + result = AppInstaller::CLI::ConfigurationRemoting::CreateDynamicRuntimeFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::PowerShell); + } + else if (lowerHandler == AppInstaller::Configuration::DSCv3HandlerIdentifier) + { + result = AppInstaller::CLI::ConfigurationRemoting::CreateOutOfProcessFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::DSCv3); + } + else if (lowerHandler == AppInstaller::Configuration::DSCv3DynamicRuntimeHandlerIdentifier) + { + result = AppInstaller::CLI::ConfigurationRemoting::CreateDynamicRuntimeFactory(AppInstaller::CLI::ConfigurationRemoting::ProcessorEngine::DSCv3); + } + + if (result) + { + // Objects returned here *must* implement ILifetimeWatcher for now. + // If we create OOP objects implemented elsewhere in the future, decide then how to exempt those while still ensuring we + // don't accidentally create a lifetime bug by basing it solely off the QI result. + result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); + co_return result; + } + + AICLI_LOG(Config, Error, << "Unknown handler in CreateConfigurationSetProcessorFactory: " << AppInstaller::Utility::ConvertToUTF8(handler)); + THROW_HR(E_NOT_SET); + } + + winrt::Microsoft::Management::Configuration::ConfigurationProcessor CreateConfigurationProcessor(winrt::Microsoft::Management::Configuration::IConfigurationSetProcessorFactory const& factory) + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + + auto result = m_statics.CreateConfigurationProcessor(factory); + result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); + return result; + } + + bool IsConfigurationAvailable() + { + return !IsStubPackage(); + } + + winrt::Windows::Foundation::IAsyncActionWithProgress EnsureConfigurationAvailableAsync() + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (IsConfigurationAvailable()) + { + return; + } + + auto strong_this{ get_strong() }; + co_await winrt::resume_background(); + + SetStubPreferred(false); + + PackageManager packageManager; + PackageCatalogReference catalogRef{ packageManager.GetPredefinedPackageCatalog(PredefinedPackageCatalog::MicrosoftStore) }; + THROW_HR_IF(APPINSTALLER_CLI_ERROR_INTERNAL_ERROR, !catalogRef); + + ConnectResult connectResult = catalogRef.Connect(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_SOURCE_OPEN_FAILED, connectResult.Status() != ConnectResultStatus::Ok); + + PackageCatalog catalog = connectResult.PackageCatalog(); + + FindPackagesOptions findPackagesOptions; + PackageMatchFilter filter; + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::Equals); + filter.Value(AppInstaller::MSStore::s_AppInstallerProductId); + findPackagesOptions.Filters().Append(filter); + + FindPackagesResult findPackagesResult{ catalog.FindPackages(findPackagesOptions) }; + + auto matches = findPackagesResult.Matches(); + THROW_HR_IF(APPINSTALLER_CLI_ERROR_MISSING_PACKAGE, matches.Size() == 0); + auto catalogPackage = matches.GetAt(0).CatalogPackage(); + + InstallOptions installOptions; + installOptions.AcceptPackageAgreements(true); + installOptions.AllowUpgradeToUnknownVersion(true); + installOptions.Force(true); + + auto progress = co_await winrt::get_progress_token(); + + // Don't use UpgradePackageAsync, we don't support upgrade for packages from the msstore + // it has to be install and internally we know is an update. + auto installTask = packageManager.InstallPackageAsync(catalogPackage, installOptions); + installTask.Progress([progress](auto const&, InstallProgress installProgress) + { + if (installProgress.State == PackageInstallProgressState::Downloading && installProgress.BytesRequired != 0) + { + progress((uint32_t)(installProgress.DownloadProgress * 80)); + } + else if (installProgress.State == PackageInstallProgressState::Installing) + { + progress(((uint32_t)installProgress.InstallationProgress * 20) + 80); + } + }); + + co_await installTask; + s_canBeCreated = false; + } + + winrt::Microsoft::Management::Configuration::ConfigurationParameter CreateConfigurationParameter() + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + + auto result = m_statics.CreateConfigurationParameter(); + result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); + return result; + } + + winrt::Microsoft::Management::Configuration::FindUnitProcessorsOptions CreateFindUnitProcessorsOptions() + { + THROW_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + if (!m_statics) + { + THROW_HR(APPINSTALLER_CLI_ERROR_PACKAGE_IS_STUB); + } + + auto result = m_statics.CreateFindUnitProcessorsOptions(); + result.as()->SetLifetimeWatcher(CreateLifetimeWatcher()); + return result; + } + + private: + // Returns a lifetime watcher object that is currently *unowned*. + IUnknown* CreateLifetimeWatcher() + { + ::Microsoft::WRL::ComPtr factory; + THROW_IF_FAILED(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().GetClassObject(CLSID_ConfigurationObjectLifetimeWatcher, IID_PPV_ARGS(&factory))); + winrt::com_ptr out; + THROW_IF_FAILED(factory->CreateInstance(nullptr, __uuidof(IUnknown), out.put_void())); + return out.detach(); + } + + winrt::Microsoft::Management::Configuration::IConfigurationStatics3 m_statics = nullptr; + AppInstaller::ThreadLocalStorage::WingetThreadGlobals m_threadGlobals; + }; + + // Enable custom code to run before creating any object through the factory. + template + class ConfigurationFactory : public ::wil::wrl_factory_for_winrt_com_class + { + public: + IFACEMETHODIMP CreateInstance(_In_opt_::IUnknown* unknownOuter, REFIID riid, _COM_Outptr_ void** object) noexcept try + { + *object = nullptr; + RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); + RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::Configuration)); + RETURN_HR_IF(E_ACCESSDENIED, !::AppInstaller::Security::IsCOMCallerSameUserAndIntegrityLevel()); + + RETURN_HR_IF(CO_E_CLASS_DISABLED, !s_canBeCreated); + + return ::wil::wrl_factory_for_winrt_com_class::CreateInstance(unknownOuter, riid, object); + } + CATCH_RETURN() + }; + +#define CoCreatableMicrosoftManagementConfigurationClass(className) \ + CoCreatableClassWithFactory(className, ::ConfigurationShim::ConfigurationFactory) + + // Disable 6388 as it seems to be falsely warning +#pragma warning(push) +#pragma warning(disable : 6388) + CoCreatableCppWinRtClass(ConfigurationObjectLifetimeWatcher); + CoCreatableMicrosoftManagementConfigurationClass(ConfigurationStaticFunctionsShim); +#pragma warning(pop) +} diff --git a/src/WindowsPackageManager/Source.def b/src/WindowsPackageManager/Source.def index 887d711dd2..29e3ac8c24 100644 --- a/src/WindowsPackageManager/Source.def +++ b/src/WindowsPackageManager/Source.def @@ -1,15 +1,15 @@ -LIBRARY WindowsPackageManager -EXPORTS - WindowsPackageManagerCLIMain - WindowsPackageManagerServerInitialize - WindowsPackageManagerServerModuleCreate - WindowsPackageManagerServerModuleRegister - WindowsPackageManagerServerModuleUnregister - WindowsPackageManagerServerWilResultLoggingCallback - WindowsPackageManagerServerCreateInstance - WindowsPackageManagerInProcModuleInitialize - WindowsPackageManagerInProcModuleTerminate - WindowsPackageManagerInProcModuleGetClassObject - WindowsPackageManagerInProcModuleGetActivationFactory - WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization - WindowsPackageManagerServerLog +LIBRARY WindowsPackageManager +EXPORTS + WindowsPackageManagerCLIMain + WindowsPackageManagerServerInitialize + WindowsPackageManagerServerModuleCreate + WindowsPackageManagerServerModuleRegister + WindowsPackageManagerServerModuleUnregister + WindowsPackageManagerServerWilResultLoggingCallback + WindowsPackageManagerServerCreateInstance + WindowsPackageManagerInProcModuleInitialize + WindowsPackageManagerInProcModuleTerminate + WindowsPackageManagerInProcModuleGetClassObject + WindowsPackageManagerInProcModuleGetActivationFactory + WindowsPackageManagerConfigurationCompleteOutOfProcessFactoryInitialization + WindowsPackageManagerServerLog diff --git a/src/WindowsPackageManager/WindowsPackageManager.h b/src/WindowsPackageManager/WindowsPackageManager.h index 37241403a8..7832160137 100644 --- a/src/WindowsPackageManager/WindowsPackageManager.h +++ b/src/WindowsPackageManager/WindowsPackageManager.h @@ -1,54 +1,54 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once -#include - -// Forward declaration -namespace wil { struct FailureInfo; } - -extern "C" -{ -#define WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION __stdcall -#define WINDOWS_PACKAGE_MANAGER_API HRESULT WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION - - using WindowsPackageManagerServerModuleTerminationCallback = void (*)(); - - // The core function to act against command line input. - int WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerCLIMain(int argc, wchar_t const** argv); - - // Initializes the Windows Package Manager COM server. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerInitialize(); - - // Creates the server module with the given termination callback. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleCreate(WindowsPackageManagerServerModuleTerminationCallback callback); - - // Registers the server module class factories. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleRegister(); - - // Unregisters the server module class factories. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleUnregister(); - - // Callback for logging the WIL result reported from the server. - void WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerServerWilResultLoggingCallback(const wil::FailureInfo& info) noexcept; - - // Creates an out-of-proc instance for manual activation scenarios. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerCreateInstance(REFCLSID rclsid, REFIID riid, void** out); - - // Creates module for in-proc COM invocation. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleInitialize(); - - // Try to terminate the module for in-proc COM. Returns false if there's still active objects. - bool WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerInProcModuleTerminate(); - - // DllGetClassObject for in-proc COM for cpp winrt runtime classes. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetClassObject( - REFCLSID rclsid, - REFIID riid, - LPVOID* ppv); - - // DllGetActivationFactory for in-proc cpp winrt runtime classes. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetActivationFactory(HSTRING classId, void** factory); - - // Allows logging from callers. - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerLog(const char* message); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once +#include + +// Forward declaration +namespace wil { struct FailureInfo; } + +extern "C" +{ +#define WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION __stdcall +#define WINDOWS_PACKAGE_MANAGER_API HRESULT WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION + + using WindowsPackageManagerServerModuleTerminationCallback = void (*)(); + + // The core function to act against command line input. + int WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerCLIMain(int argc, wchar_t const** argv); + + // Initializes the Windows Package Manager COM server. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerInitialize(); + + // Creates the server module with the given termination callback. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleCreate(WindowsPackageManagerServerModuleTerminationCallback callback); + + // Registers the server module class factories. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleRegister(); + + // Unregisters the server module class factories. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleUnregister(); + + // Callback for logging the WIL result reported from the server. + void WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerServerWilResultLoggingCallback(const wil::FailureInfo& info) noexcept; + + // Creates an out-of-proc instance for manual activation scenarios. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerCreateInstance(REFCLSID rclsid, REFIID riid, void** out); + + // Creates module for in-proc COM invocation. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleInitialize(); + + // Try to terminate the module for in-proc COM. Returns false if there's still active objects. + bool WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerInProcModuleTerminate(); + + // DllGetClassObject for in-proc COM for cpp winrt runtime classes. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetClassObject( + REFCLSID rclsid, + REFIID riid, + LPVOID* ppv); + + // DllGetActivationFactory for in-proc cpp winrt runtime classes. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetActivationFactory(HSTRING classId, void** factory); + + // Allows logging from callers. + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerLog(const char* message); +} diff --git a/src/WindowsPackageManager/WindowsPackageManager.vcxproj b/src/WindowsPackageManager/WindowsPackageManager.vcxproj index 2d016963f2..f8ab04be8e 100644 --- a/src/WindowsPackageManager/WindowsPackageManager.vcxproj +++ b/src/WindowsPackageManager/WindowsPackageManager.vcxproj @@ -1,445 +1,445 @@ - - - - - true - true - true - 15.0 - {2046B5AF-666D-4CE8-8D3E-C32C57908A56} - Win32Proj - WindowsPackageManager - 10.0.26100.0 - 10.0.17763.0 - true - false - - . - - - - - Debug - ARM64 - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - ARM64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - DynamicLibrary - - - true - true - - - false - true - false - - - false - true - false - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - Spectre - - - - - - - - - - - - - - - - - - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - true - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - true - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ - true - false - ..\CodeAnalysis.ruleset - - - false - - - - x64-release-static - Release - - - x64 - Debug - - - x64-release - Release - - - arm64 - Debug - - - x86 - Debug - - - arm64-release - Release - - - x86-release - Release - - - arm64-release-static - Release - - - x86-release-static - Release - - - - NotUsing - _CONSOLE;%(PreprocessorDefinitions) - Level4 - %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - true - true - false - false - stdcpp17 - stdcpp17 - true - true - true - true - 4324 - 4324 - false - false - - - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - true - gdi32.dll - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - WIN32;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - true - false - stdcpp17 - true - true - 4324 - false - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - true - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - 4324 - 4324 - 4324 - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - gdi32.dll - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) - true - true - true - Guard - Guard - Guard - stdcpp17 - stdcpp17 - stdcpp17 - true - true - true - false - false - false - MultiThreaded - MultiThreaded - MultiThreaded - 4324 - 4324 - 4324 - false - false - false - - - true - true - false - Windows - Windows - Windows - Source.def - Source.def - Source.def - wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - true - true - gdi32.dll - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) - - - - - - - - - - - - - - - - - - {1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d} - - - {5890d6ed-7c3b-40f3-b436-b54f640d9e65} - - - {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} - - - {ca460806-5e41-4e97-9a3d-1d74b433b663} - - - {1cc41a9a-ae66-459d-9210-1e572dd7be69} - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - + + + + + true + true + true + 15.0 + {2046B5AF-666D-4CE8-8D3E-C32C57908A56} + Win32Proj + WindowsPackageManager + 10.0.26100.0 + 10.0.17763.0 + true + false + + . + + + + + Debug + ARM64 + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + ARM64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + DynamicLibrary + + + true + true + + + false + true + false + + + false + true + false + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + Spectre + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + true + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + true + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)x86\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + $(SolutionDir)$(Platform)\$(Configuration)\$(ProjectName)\ + true + false + ..\CodeAnalysis.ruleset + + + false + + + + x64-release-static + Release + + + x64 + Debug + + + x64-release + Release + + + arm64 + Debug + + + x86 + Debug + + + arm64-release + Release + + + x86-release + Release + + + arm64-release-static + Release + + + x86-release-static + Release + + + + NotUsing + _CONSOLE;%(PreprocessorDefinitions) + Level4 + %(AdditionalOptions) /permissive- /bigobj /D _SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + true + true + false + false + stdcpp17 + stdcpp17 + true + true + true + true + 4324 + 4324 + false + false + + + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + true + gdi32.dll + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + WIN32;%(PreprocessorDefinitions) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + true + false + stdcpp17 + true + true + 4324 + false + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + true + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + 4324 + 4324 + 4324 + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + gdi32.dll + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + $(ProjectDir);$(ProjectDir)..\AppInstallerCLICore\Public\;$(ProjectDir)..\AppInstallerRepositoryCore;$(ProjectDir)..\AppInstallerCommonCore\Public;$(ProjectDir)..\AppInstallerSharedLib\Public;$(ProjectDir)..\Microsoft.Management.Deployment\Public;%(AdditionalIncludeDirectories) + true + true + true + Guard + Guard + Guard + stdcpp17 + stdcpp17 + stdcpp17 + true + true + true + false + false + false + MultiThreaded + MultiThreaded + MultiThreaded + 4324 + 4324 + 4324 + false + false + false + + + true + true + false + Windows + Windows + Windows + Source.def + Source.def + Source.def + wininet.lib;shell32.lib;winsqlite3.lib;shlwapi.lib;icuuc.lib;icuin.lib;urlmon.lib;Advapi32.lib;winhttp.lib;onecoreuap.lib;msi.lib;gdi32.lib;%(AdditionalDependencies) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + true + true + gdi32.dll + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + $(ProjectDir)..\manifest\shared.manifest %(AdditionalManifestFiles) + + + + + + + + + + + + + + + + + + {1c6e0108-2860-4b17-9f7e-fa5c6c1f3d3d} + + + {5890d6ed-7c3b-40f3-b436-b54f640d9e65} + + + {5eb88068-5fb9-4e69-89b2-72dbc5e068f9} + + + {ca460806-5e41-4e97-9a3d-1d74b433b663} + + + {1cc41a9a-ae66-459d-9210-1e572dd7be69} + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + diff --git a/src/WindowsPackageManager/WindowsPackageManager.vcxproj.filters b/src/WindowsPackageManager/WindowsPackageManager.vcxproj.filters index 50dc4c72ca..8216871a75 100644 --- a/src/WindowsPackageManager/WindowsPackageManager.vcxproj.filters +++ b/src/WindowsPackageManager/WindowsPackageManager.vcxproj.filters @@ -1,40 +1,40 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Header Files - - - - - Source Files - - - Source Files - - - Source Files - - - - - - - Source Files - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + + + + + Source Files + + \ No newline at end of file diff --git a/src/WindowsPackageManager/main.cpp b/src/WindowsPackageManager/main.cpp index 873c48b7a4..fecbef7961 100644 --- a/src/WindowsPackageManager/main.cpp +++ b/src/WindowsPackageManager/main.cpp @@ -1,160 +1,160 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include -#pragma warning( push ) -#pragma warning ( disable : 4324 ) -#include -#pragma warning( pop ) -#include - -#include "WindowsPackageManager.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace winrt::Microsoft::Management::Deployment; - -// CreatorMap for out-of-proc com registration and direct in-proc com class construction -CoCreatableClassWrlCreatorMapInclude(PackageManager); -CoCreatableClassWrlCreatorMapInclude(FindPackagesOptions); -CoCreatableClassWrlCreatorMapInclude(CreateCompositePackageCatalogOptions); -CoCreatableClassWrlCreatorMapInclude(InstallOptions); -CoCreatableClassWrlCreatorMapInclude(UninstallOptions); -CoCreatableClassWrlCreatorMapInclude(DownloadOptions); -CoCreatableClassWrlCreatorMapInclude(PackageMatchFilter); -CoCreatableClassWrlCreatorMapInclude(AuthenticationArguments); -CoCreatableClassWrlCreatorMapInclude(PackageManagerSettings); -CoCreatableClassWrlCreatorMapInclude(RepairOptions); -CoCreatableClassWrlCreatorMapInclude(AddPackageCatalogOptions); -CoCreatableClassWrlCreatorMapInclude(RemovePackageCatalogOptions); - -// Shim for configuration static functions -CoCreatableClassWrlCreatorMapInclude(ConfigurationStaticFunctionsShim); - -extern "C" -{ - int WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerCLIMain(int argc, wchar_t const** argv) try - { - ::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::Create(); - return AppInstaller::CLI::CoreMain(argc, argv); - } - CATCH_RETURN(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerInitialize() try - { - AppInstaller::CLI::ServerInitialize(); - return S_OK; - } - CATCH_RETURN(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleCreate(WindowsPackageManagerServerModuleTerminationCallback callback) try - { - AppInstaller::ShutdownMonitoring::ServerShutdownSynchronization::Initialize(callback); - ::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::Create(callback); - return S_OK; - } - CATCH_RETURN(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleRegister() try - { - RETURN_HR(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().RegisterObjects()); - } - CATCH_RETURN(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleUnregister() try - { - RETURN_HR(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().UnregisterObjects()); - } - CATCH_RETURN(); - - void WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerServerWilResultLoggingCallback(const wil::FailureInfo& failure) noexcept try - { - AppInstaller::Logging::Telemetry().LogFailure(failure); - } - CATCH_LOG(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerCreateInstance(REFCLSID rclsid, REFIID riid, void** out) try - { - RETURN_HR_IF_NULL(E_POINTER, out); - ::Microsoft::WRL::ComPtr factory; - RETURN_IF_FAILED(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().GetClassObject(rclsid, IID_PPV_ARGS(&factory))); - RETURN_HR(factory->CreateInstance(nullptr, riid, out)); - } - CATCH_RETURN(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleInitialize() try - { - ::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::Create(); - AppInstaller::CLI::InProcInitialize(); - return S_OK; - } - CATCH_RETURN(); - - bool WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerInProcModuleTerminate() - { - try - { - // Check whether the caller wants us to allow unloads - if (implementation::GetCanUnload()) - { - // The WRL object count is used to track externally visible objects, which largely means objects created with the `wil::details::module_count_wrapper` type wrapper. - // Configuration objects use a composition based tracking that is similar in nature (only when OOP). - // - // In-proc DllCanUnloadNow should not be blocked by our internal objects, but they must be destroyed on unload or a future reload will attempt to destroy them - // and our module may have moved. So when we don't have any more objects that we gave to callers, remove all of our static lifetime objects and indicate - // that we can now be unloaded. - if (::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::GetModule().Terminate()) - { - AppInstaller::WinRT::COMStaticStorageStatics::ResetAll(); - return true; - } - } - } - catch (...) {} - - return false; - } - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetClassObject( - REFCLSID rclsid, - REFIID riid, - LPVOID* ppv) try - { - CLSID redirectedClsid = GetRedirectedClsidFromInProcClsid(rclsid); - RETURN_HR_IF(CLASS_E_CLASSNOTAVAILABLE, IsEqualCLSID(redirectedClsid, CLSID_NULL)); - RETURN_HR(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::GetModule().GetClassObject(redirectedClsid, riid, ppv)); - } - CATCH_RETURN(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetActivationFactory(HSTRING classId, void** factory) try - { - RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); - - return WINRT_GetActivationFactory(classId, factory); - } - CATCH_RETURN(); - - WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerLog(const char* message) try - { - AppInstaller::Logging::Log().Write(AppInstaller::Logging::Channel::Core, AppInstaller::Logging::Level::Info, message); - return S_OK; - } - CATCH_RETURN(); - -#ifndef AICLI_DISABLE_TEST_HOOKS - __declspec(dllexport) WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerTestHook_ReloadGroupPolicy() try - { - AppInstaller::Settings::GroupPolicy::Instance().Reload(); - return S_OK; - } - CATCH_RETURN(); -#endif -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include +#pragma warning( push ) +#pragma warning ( disable : 4324 ) +#include +#pragma warning( pop ) +#include + +#include "WindowsPackageManager.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace winrt::Microsoft::Management::Deployment; + +// CreatorMap for out-of-proc com registration and direct in-proc com class construction +CoCreatableClassWrlCreatorMapInclude(PackageManager); +CoCreatableClassWrlCreatorMapInclude(FindPackagesOptions); +CoCreatableClassWrlCreatorMapInclude(CreateCompositePackageCatalogOptions); +CoCreatableClassWrlCreatorMapInclude(InstallOptions); +CoCreatableClassWrlCreatorMapInclude(UninstallOptions); +CoCreatableClassWrlCreatorMapInclude(DownloadOptions); +CoCreatableClassWrlCreatorMapInclude(PackageMatchFilter); +CoCreatableClassWrlCreatorMapInclude(AuthenticationArguments); +CoCreatableClassWrlCreatorMapInclude(PackageManagerSettings); +CoCreatableClassWrlCreatorMapInclude(RepairOptions); +CoCreatableClassWrlCreatorMapInclude(AddPackageCatalogOptions); +CoCreatableClassWrlCreatorMapInclude(RemovePackageCatalogOptions); + +// Shim for configuration static functions +CoCreatableClassWrlCreatorMapInclude(ConfigurationStaticFunctionsShim); + +extern "C" +{ + int WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerCLIMain(int argc, wchar_t const** argv) try + { + ::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::Create(); + return AppInstaller::CLI::CoreMain(argc, argv); + } + CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerInitialize() try + { + AppInstaller::CLI::ServerInitialize(); + return S_OK; + } + CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleCreate(WindowsPackageManagerServerModuleTerminationCallback callback) try + { + AppInstaller::ShutdownMonitoring::ServerShutdownSynchronization::Initialize(callback); + ::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::Create(callback); + return S_OK; + } + CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleRegister() try + { + RETURN_HR(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().RegisterObjects()); + } + CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerModuleUnregister() try + { + RETURN_HR(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().UnregisterObjects()); + } + CATCH_RETURN(); + + void WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerServerWilResultLoggingCallback(const wil::FailureInfo& failure) noexcept try + { + AppInstaller::Logging::Telemetry().LogFailure(failure); + } + CATCH_LOG(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerCreateInstance(REFCLSID rclsid, REFIID riid, void** out) try + { + RETURN_HR_IF_NULL(E_POINTER, out); + ::Microsoft::WRL::ComPtr factory; + RETURN_IF_FAILED(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::OutOfProc>::GetModule().GetClassObject(rclsid, IID_PPV_ARGS(&factory))); + RETURN_HR(factory->CreateInstance(nullptr, riid, out)); + } + CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleInitialize() try + { + ::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::Create(); + AppInstaller::CLI::InProcInitialize(); + return S_OK; + } + CATCH_RETURN(); + + bool WINDOWS_PACKAGE_MANAGER_API_CALLING_CONVENTION WindowsPackageManagerInProcModuleTerminate() + { + try + { + // Check whether the caller wants us to allow unloads + if (implementation::GetCanUnload()) + { + // The WRL object count is used to track externally visible objects, which largely means objects created with the `wil::details::module_count_wrapper` type wrapper. + // Configuration objects use a composition based tracking that is similar in nature (only when OOP). + // + // In-proc DllCanUnloadNow should not be blocked by our internal objects, but they must be destroyed on unload or a future reload will attempt to destroy them + // and our module may have moved. So when we don't have any more objects that we gave to callers, remove all of our static lifetime objects and indicate + // that we can now be unloaded. + if (::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::GetModule().Terminate()) + { + AppInstaller::WinRT::COMStaticStorageStatics::ResetAll(); + return true; + } + } + } + catch (...) {} + + return false; + } + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetClassObject( + REFCLSID rclsid, + REFIID riid, + LPVOID* ppv) try + { + CLSID redirectedClsid = GetRedirectedClsidFromInProcClsid(rclsid); + RETURN_HR_IF(CLASS_E_CLASSNOTAVAILABLE, IsEqualCLSID(redirectedClsid, CLSID_NULL)); + RETURN_HR(::Microsoft::WRL::Module<::Microsoft::WRL::ModuleType::InProc>::GetModule().GetClassObject(redirectedClsid, riid, ppv)); + } + CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerInProcModuleGetActivationFactory(HSTRING classId, void** factory) try + { + RETURN_HR_IF(APPINSTALLER_CLI_ERROR_BLOCKED_BY_POLICY, !::AppInstaller::Settings::GroupPolicies().IsEnabled(::AppInstaller::Settings::TogglePolicy::Policy::WinGet)); + + return WINRT_GetActivationFactory(classId, factory); + } + CATCH_RETURN(); + + WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerServerLog(const char* message) try + { + AppInstaller::Logging::Log().Write(AppInstaller::Logging::Channel::Core, AppInstaller::Logging::Level::Info, message); + return S_OK; + } + CATCH_RETURN(); + +#ifndef AICLI_DISABLE_TEST_HOOKS + __declspec(dllexport) WINDOWS_PACKAGE_MANAGER_API WindowsPackageManagerTestHook_ReloadGroupPolicy() try + { + AppInstaller::Settings::GroupPolicy::Instance().Reload(); + return S_OK; + } + CATCH_RETURN(); +#endif +} diff --git a/src/WindowsPackageManager/packages.config b/src/WindowsPackageManager/packages.config index f7979cb735..3a8e0698a3 100644 --- a/src/WindowsPackageManager/packages.config +++ b/src/WindowsPackageManager/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/src/Xlang/README.md b/src/Xlang/README.md index 929a40259b..92b97dd317 100644 --- a/src/Xlang/README.md +++ b/src/Xlang/README.md @@ -1,20 +1,20 @@ -## Microsoft/Xlang/UndockedRegFreeWinRT - -Do not change code under the UndockedRegFreeWinRT directory; it contains Microsoft/Xlang/UndockedRegFreeWinRT source code. - -It is created using git subtree command and points to this specific commit [cfe510d0d2b07484fea2c6d77163de017738c100]: - - git subtree add --prefix=src/Xlang/UndockedRegFreeWinRT https://github.com/microsoft/xlang cfe510d0d2b07484fea2c6d77163de017738c100 --squash - -Any future PR updates to this subtree should not be squashed. - -The default project files are used to make the UndockedRegFreeWinRT project compile as part of the WinGet solution: - - "UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj" - "UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj.filters" - -#### Steps used to create the VS project files. - -1. Add UndockedRegFreeWinRT project files to WinGet solution. -2. Remove code and files not needed by WinGet project. Basically removing everything except what is contained in the UndockedRegFreeWinRT folder. Test code from that folder is also removed. +## Microsoft/Xlang/UndockedRegFreeWinRT + +Do not change code under the UndockedRegFreeWinRT directory; it contains Microsoft/Xlang/UndockedRegFreeWinRT source code. + +It is created using git subtree command and points to this specific commit [cfe510d0d2b07484fea2c6d77163de017738c100]: + + git subtree add --prefix=src/Xlang/UndockedRegFreeWinRT https://github.com/microsoft/xlang cfe510d0d2b07484fea2c6d77163de017738c100 --squash + +Any future PR updates to this subtree should not be squashed. + +The default project files are used to make the UndockedRegFreeWinRT project compile as part of the WinGet solution: + + "UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj" + "UndockedRegFreeWinRT\UndockedRegFreeWinRT\UndockedRegFreeWinRT.vcxproj.filters" + +#### Steps used to create the VS project files. + +1. Add UndockedRegFreeWinRT project files to WinGet solution. +2. Remove code and files not needed by WinGet project. Basically removing everything except what is contained in the UndockedRegFreeWinRT folder. Test code from that folder is also removed. 3. Modify the vcxproj file to follow settings from other project files in the WinGet solution. \ No newline at end of file diff --git a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj index 2390e0af11..d9b3c0e9d6 100644 --- a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj +++ b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj @@ -1,331 +1,331 @@ - - - - - Debug - Win32 - - - ReleaseStatic - ARM64 - - - ReleaseStatic - Win32 - - - ReleaseStatic - x64 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - Debug - ARM64 - - - Release - ARM64 - - - - 16.0 - {31ED69A8-5310-45A9-953F-56C351D2C3E1} - Win32Proj - UndockedRegFreeWinRT - 10.0.26100.0 - $(Platform) - x86 - - - - DynamicLibrary - - - true - - - false - true - - - false - true - - - - - - - - - - - - - winrtact - true - $(Platform)\$(Configuration)\ - $(SolutionDir)$(BuildPlatform)\$(Configuration)\$(ProjectName)\ - - - winrtact - false - $(Platform)\$(Configuration)\ - $(SolutionDir)$(BuildPlatform)\$(Configuration)\$(ProjectName)\ - Spectre - - - winrtact - false - $(Platform)\$(Configuration)\ - $(SolutionDir)$(BuildPlatform)\$(Configuration)\$(ProjectName)\ - Spectre - - - true - - - - NotUsing - TurnOffAllWarnings - true - WIN32;_DEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - - - Windows - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - - - - - NotUsing - TurnOffAllWarnings - true - _DEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - - - Windows - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - - - - - NotUsing - TurnOffAllWarnings - true - _DEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - - - Windows - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - - - - - NotUsing - Level3 - true - true - true - WIN32;NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - Guard - - - Windows - true - true - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - $(VC_LibraryPath_VC_x86_Desktop_spectre);$(VC_LibraryPath_VC_x86_OneCore_spectre);%(AdditionalLibraryDirectories) - - - - - NotUsing - Level3 - true - true - true - WIN32;NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - Guard - MultiThreaded - - - Windows - true - true - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - $(VC_LibraryPath_VC_x86_Desktop_spectre);$(VC_LibraryPath_VC_x86_OneCore_spectre);%(AdditionalLibraryDirectories) - - - - - NotUsing - Level3 - true - true - true - NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - Guard - - - Windows - true - true - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - $(VC_LibraryPath_VC_x64_Desktop_spectre);$(VC_LibraryPath_VC_x64_OneCore_spectre);%(AdditionalLibraryDirectories) - - - - - NotUsing - Level3 - true - true - true - NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - Guard - MultiThreaded - - - Windows - true - true - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - $(VC_LibraryPath_VC_x64_Desktop_spectre);$(VC_LibraryPath_VC_x64_OneCore_spectre);%(AdditionalLibraryDirectories) - - - - - NotUsing - Level3 - true - true - true - NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - Guard - - - Windows - true - true - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) - $(VC_LibraryPath_VC_ARM64_Desktop_spectre);$(VC_LibraryPath_VC_ARM64_OneCore_spectre);%(AdditionalLibraryDirectories) - - - - - NotUsing - Level3 - true - true - true - NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) - true - pch.h - stdcpp17 - Guard - MultiThreaded - - - Windows - true - true - true - false - comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib - winrtact.def - $(VC_LibraryPath_VC_ARM64_Desktop_spectre);$(VC_LibraryPath_VC_ARM64_OneCore_spectre);%(AdditionalLibraryDirectories) - - - - - - - - - - - - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + Debug + Win32 + + + ReleaseStatic + ARM64 + + + ReleaseStatic + Win32 + + + ReleaseStatic + x64 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + Debug + ARM64 + + + Release + ARM64 + + + + 16.0 + {31ED69A8-5310-45A9-953F-56C351D2C3E1} + Win32Proj + UndockedRegFreeWinRT + 10.0.26100.0 + $(Platform) + x86 + + + + DynamicLibrary + + + true + + + false + true + + + false + true + + + + + + + + + + + + + winrtact + true + $(Platform)\$(Configuration)\ + $(SolutionDir)$(BuildPlatform)\$(Configuration)\$(ProjectName)\ + + + winrtact + false + $(Platform)\$(Configuration)\ + $(SolutionDir)$(BuildPlatform)\$(Configuration)\$(ProjectName)\ + Spectre + + + winrtact + false + $(Platform)\$(Configuration)\ + $(SolutionDir)$(BuildPlatform)\$(Configuration)\$(ProjectName)\ + Spectre + + + true + + + + NotUsing + TurnOffAllWarnings + true + WIN32;_DEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + + + Windows + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + + + + + NotUsing + TurnOffAllWarnings + true + _DEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + + + Windows + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + + + + + NotUsing + TurnOffAllWarnings + true + _DEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + + + Windows + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + + + + + NotUsing + Level3 + true + true + true + WIN32;NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + Guard + + + Windows + true + true + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + $(VC_LibraryPath_VC_x86_Desktop_spectre);$(VC_LibraryPath_VC_x86_OneCore_spectre);%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + WIN32;NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + Guard + MultiThreaded + + + Windows + true + true + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + $(VC_LibraryPath_VC_x86_Desktop_spectre);$(VC_LibraryPath_VC_x86_OneCore_spectre);%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + Guard + + + Windows + true + true + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + $(VC_LibraryPath_VC_x64_Desktop_spectre);$(VC_LibraryPath_VC_x64_OneCore_spectre);%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + Guard + MultiThreaded + + + Windows + true + true + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + $(VC_LibraryPath_VC_x64_Desktop_spectre);$(VC_LibraryPath_VC_x64_OneCore_spectre);%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + Guard + + + Windows + true + true + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + /debug:full /debugtype:cv,fixup /incremental:no %(AdditionalOptions) + $(VC_LibraryPath_VC_ARM64_Desktop_spectre);$(VC_LibraryPath_VC_ARM64_OneCore_spectre);%(AdditionalLibraryDirectories) + + + + + NotUsing + Level3 + true + true + true + NDEBUG;UNDOCKEDREGFREEWINRT_EXPORTS;_WINDOWS;_USRDLL;%(PreprocessorDefinitions) + true + pch.h + stdcpp17 + Guard + MultiThreaded + + + Windows + true + true + true + false + comsuppw.lib;shlwapi.lib;xmllite.lib;runtimeobject.lib;Pathcch.lib;Rometadata.lib;Rpcrt4.lib;Shell32.lib;Advapi32.lib + winrtact.def + $(VC_LibraryPath_VC_ARM64_Desktop_spectre);$(VC_LibraryPath_VC_ARM64_OneCore_spectre);%(AdditionalLibraryDirectories) + + + + + + + + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + diff --git a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters index 5e8d0ce801..709d50f8a5 100644 --- a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters +++ b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/UndockedRegFreeWinRT.vcxproj.filters @@ -1,61 +1,61 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - - Source Files - - - - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - Header Files - - - Header Files - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + + Source Files + + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + \ No newline at end of file diff --git a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/packages.config b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/packages.config index a4d92efeae..d690ede8a3 100644 --- a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/packages.config +++ b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/packages.config @@ -1,4 +1,4 @@ - - - + + + \ No newline at end of file diff --git a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/typeresolution.cpp b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/typeresolution.cpp index fe6b92773f..58770c0aa9 100644 --- a/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/typeresolution.cpp +++ b/src/Xlang/UndockedRegFreeWinRT/src/UndockedRegFreeWinRT/UndockedRegFreeWinRT/typeresolution.cpp @@ -507,8 +507,8 @@ namespace UndockedRegFreeWinRT else { hr = RO_E_METADATA_NAME_NOT_FOUND; - } - + } + // If not successful looking next to an unpackaged exe, try looking next to our DLL. if (FAILED(hr)) { @@ -523,12 +523,12 @@ namespace UndockedRegFreeWinRT ppMetaDataImport, pmdTypeDef); - if (SUCCEEDED(dllHR)) + if (SUCCEEDED(dllHR)) { hr = dllHR; } - } - + } + return hr; } diff --git a/src/binver/Update-BinVer.ps1 b/src/binver/Update-BinVer.ps1 index d54d2c8890..7b02fedf9c 100644 --- a/src/binver/Update-BinVer.ps1 +++ b/src/binver/Update-BinVer.ps1 @@ -1,113 +1,113 @@ -<# -.SYNOPSIS - Updates the given version header file to match the version info parse from git. -.DESCRIPTION - Uses "git describe --tags" to determine the current version, then updates the given file - to match. See the existing version.h for the format. -.PARAMETER TargetFile - The file to update with version information. If not given, simply outputs the version info. -.PARAMETER BuildVersion - The build version to use. -.PARAMETER MajorMinorOverride - The major and minor version in the format of Major.Minor to use. - Or skip to skip the major and minor version override. -.PARAMETER OutVar - Output a pipeline variable with the version. -#> -param( - [Parameter(Mandatory=$false)] - [string]$TargetFile, - - [Parameter(Mandatory=$false)] - [int]$BuildVersion = 0, - - [Parameter(Mandatory=$false)] - [string]$MajorMinorOverride, - - [switch]$OutVar -) - -$Local:Major = 0; -$Local:Minor = 0; -$Local:SkipMajorMinorOverride = $false; - -if ($MajorMinorOverride -match "([0-9]+)\.([0-9]+)") -{ - $Local:Major = $Matches[1] - $Local:Minor = $Matches[2] -} -elseif ($MajorMinorOverride -eq "skip") -{ - $Local:SkipMajorMinorOverride = $true; - - Write-Host "Major minor version override skipped" -} -else -{ - $ErrorActionPreference = 'SilentlyContinue' - $Local:GitDescribeText = git describe --tags; - $ErrorActionPreference = 'Continue' - - Write-Host "Git describe: $Local:GitDescribeText" - - if ($Local:GitDescribeText -match "v([0-9]+)\.([0-9]+)") - { - $Local:Major = $Matches[1] - $Local:Minor = $Matches[2] - } - else - { - Write-Host "Describe did not match regex and major/minor weren't explicitly provided; using zeros" - } -} - -if ($Local:SkipMajorMinorOverride) -{ - Write-Host "Using build version only: $BuildVersion" -} -else -{ - Write-Host "Using version: $Local:Major.$Local:Minor.$BuildVersion" -} - -if ($OutVar) -{ - Write-Host "##vso[task.setvariable variable=tag;isOutput=true]$Local:Major.$Local:Minor" -} - -if (![String]::IsNullOrEmpty($TargetFile)) -{ - $Local:FullPath = Resolve-Path $TargetFile - Write-Host "Updating file: $Local:FullPath" - if (Test-Path $TargetFile) - { - $Local:ResultContent = "" - foreach ($Local:line in [System.IO.File]::ReadLines($Local:FullPath)) - { - if (-Not($Local:SkipMajorMinorOverride) -And $Local:line.StartsWith("#define VERSION_MAJOR")) - { - $Local:ResultContent += "#define VERSION_MAJOR $Local:Major"; - } - elseif (-Not($Local:SkipMajorMinorOverride) -And $Local:line.StartsWith("#define VERSION_MINOR")) - { - $Local:ResultContent += "#define VERSION_MINOR $Local:Minor"; - } - elseif ($Local:line.StartsWith("#define VERSION_BUILD")) - { - $Local:ResultContent += "#define VERSION_BUILD $BuildVersion"; - } - else - { - $Local:ResultContent += $Local:line; - } - $Local:ResultContent += [System.Environment]::NewLine; - } - Set-Content -Path $Local:FullPath -Value $Local:ResultContent - } - else - { - Write-Error "Did not find target file: $TargetFile" - } -} - -exit 0 +<# +.SYNOPSIS + Updates the given version header file to match the version info parse from git. +.DESCRIPTION + Uses "git describe --tags" to determine the current version, then updates the given file + to match. See the existing version.h for the format. +.PARAMETER TargetFile + The file to update with version information. If not given, simply outputs the version info. +.PARAMETER BuildVersion + The build version to use. +.PARAMETER MajorMinorOverride + The major and minor version in the format of Major.Minor to use. + Or skip to skip the major and minor version override. +.PARAMETER OutVar + Output a pipeline variable with the version. +#> +param( + [Parameter(Mandatory=$false)] + [string]$TargetFile, + + [Parameter(Mandatory=$false)] + [int]$BuildVersion = 0, + + [Parameter(Mandatory=$false)] + [string]$MajorMinorOverride, + + [switch]$OutVar +) + +$Local:Major = 0; +$Local:Minor = 0; +$Local:SkipMajorMinorOverride = $false; + +if ($MajorMinorOverride -match "([0-9]+)\.([0-9]+)") +{ + $Local:Major = $Matches[1] + $Local:Minor = $Matches[2] +} +elseif ($MajorMinorOverride -eq "skip") +{ + $Local:SkipMajorMinorOverride = $true; + + Write-Host "Major minor version override skipped" +} +else +{ + $ErrorActionPreference = 'SilentlyContinue' + $Local:GitDescribeText = git describe --tags; + $ErrorActionPreference = 'Continue' + + Write-Host "Git describe: $Local:GitDescribeText" + + if ($Local:GitDescribeText -match "v([0-9]+)\.([0-9]+)") + { + $Local:Major = $Matches[1] + $Local:Minor = $Matches[2] + } + else + { + Write-Host "Describe did not match regex and major/minor weren't explicitly provided; using zeros" + } +} + +if ($Local:SkipMajorMinorOverride) +{ + Write-Host "Using build version only: $BuildVersion" +} +else +{ + Write-Host "Using version: $Local:Major.$Local:Minor.$BuildVersion" +} + +if ($OutVar) +{ + Write-Host "##vso[task.setvariable variable=tag;isOutput=true]$Local:Major.$Local:Minor" +} + +if (![String]::IsNullOrEmpty($TargetFile)) +{ + $Local:FullPath = Resolve-Path $TargetFile + Write-Host "Updating file: $Local:FullPath" + if (Test-Path $TargetFile) + { + $Local:ResultContent = "" + foreach ($Local:line in [System.IO.File]::ReadLines($Local:FullPath)) + { + if (-Not($Local:SkipMajorMinorOverride) -And $Local:line.StartsWith("#define VERSION_MAJOR")) + { + $Local:ResultContent += "#define VERSION_MAJOR $Local:Major"; + } + elseif (-Not($Local:SkipMajorMinorOverride) -And $Local:line.StartsWith("#define VERSION_MINOR")) + { + $Local:ResultContent += "#define VERSION_MINOR $Local:Minor"; + } + elseif ($Local:line.StartsWith("#define VERSION_BUILD")) + { + $Local:ResultContent += "#define VERSION_BUILD $BuildVersion"; + } + else + { + $Local:ResultContent += $Local:line; + } + $Local:ResultContent += [System.Environment]::NewLine; + } + Set-Content -Path $Local:FullPath -Value $Local:ResultContent + } + else + { + Write-Error "Did not find target file: $TargetFile" + } +} + +exit 0 diff --git a/src/binver/binver.vcxitems b/src/binver/binver.vcxitems index db924d2228..18a018d4f1 100644 --- a/src/binver/binver.vcxitems +++ b/src/binver/binver.vcxitems @@ -1,26 +1,26 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - {6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86} - - - - %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)include - - - - - - - - - - - - - - - + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {6e36ddd7-1602-474e-b1d7-d0a7e1d5ad86} + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory)include + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/binver/binver/resource.h b/src/binver/binver/resource.h index c3bc570152..28f5b8c858 100644 --- a/src/binver/binver/resource.h +++ b/src/binver/binver/resource.h @@ -1,14 +1,14 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by version.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by version.rc + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/src/binver/binver/version.h b/src/binver/binver/version.h index f807f8fe28..3fc7744c7e 100644 --- a/src/binver/binver/version.h +++ b/src/binver/binver/version.h @@ -1,32 +1,32 @@ -#define STRINGIZE2(s) #s -#define STRINGIZE(s) STRINGIZE2(s) - -#define VERSION_MAJOR 1 -#define VERSION_MINOR 29 -#define VERSION_BUILD 0 -#define VERSION_REVISION 0 - -#define VER_FILE_DESCRIPTION_STR "WinGet CLI" -#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_REVISION -#define VER_FILE_VERSION_STR STRINGIZE(VERSION_MAJOR) \ - "." STRINGIZE(VERSION_MINOR) \ - "." STRINGIZE(VERSION_BUILD) \ - "." STRINGIZE(VERSION_REVISION) \ - -#define VER_PRODUCTNAME_STR "WinGet CLI" -#define VER_PRODUCT_VERSION VER_FILE_VERSION -#define VER_PRODUCT_VERSION_STR VER_FILE_VERSION_STR -#define VER_ORIGINAL_FILENAME_STR "winget.exe" -#define VER_INTERNAL_NAME_STR VER_ORIGINAL_FILENAME_STR -#define VER_COPYRIGHT_STR "Copyright (c) Microsoft Corporation" - -#ifdef _DEBUG -#define VER_VER_DEBUG VS_FF_DEBUG -#else -#define VER_VER_DEBUG 0 -#endif - -#define VER_FILEOS VOS_NT_WINDOWS32 -#define VER_FILEFLAGS VER_VER_DEBUG -#define VER_FILETYPE VFT_APP - +#define STRINGIZE2(s) #s +#define STRINGIZE(s) STRINGIZE2(s) + +#define VERSION_MAJOR 1 +#define VERSION_MINOR 29 +#define VERSION_BUILD 0 +#define VERSION_REVISION 0 + +#define VER_FILE_DESCRIPTION_STR "WinGet CLI" +#define VER_FILE_VERSION VERSION_MAJOR, VERSION_MINOR, VERSION_BUILD, VERSION_REVISION +#define VER_FILE_VERSION_STR STRINGIZE(VERSION_MAJOR) \ + "." STRINGIZE(VERSION_MINOR) \ + "." STRINGIZE(VERSION_BUILD) \ + "." STRINGIZE(VERSION_REVISION) \ + +#define VER_PRODUCTNAME_STR "WinGet CLI" +#define VER_PRODUCT_VERSION VER_FILE_VERSION +#define VER_PRODUCT_VERSION_STR VER_FILE_VERSION_STR +#define VER_ORIGINAL_FILENAME_STR "winget.exe" +#define VER_INTERNAL_NAME_STR VER_ORIGINAL_FILENAME_STR +#define VER_COPYRIGHT_STR "Copyright (c) Microsoft Corporation" + +#ifdef _DEBUG +#define VER_VER_DEBUG VS_FF_DEBUG +#else +#define VER_VER_DEBUG 0 +#endif + +#define VER_FILEOS VOS_NT_WINDOWS32 +#define VER_FILEFLAGS VER_VER_DEBUG +#define VER_FILETYPE VFT_APP + diff --git a/src/binver/binver/version.rc b/src/binver/binver/version.rc index e35a87260c..0c1e126818 100644 --- a/src/binver/binver/version.rc +++ b/src/binver/binver/version.rc @@ -1,96 +1,96 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" -#include "version.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "winres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// English (United States) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -#pragma code_page(1252) - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""winres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Version -// - -VS_VERSION_INFO VERSIONINFO - FILEVERSION VER_FILE_VERSION - PRODUCTVERSION VER_PRODUCT_VERSION - FILEFLAGSMASK 0x3fL - FILEFLAGS VER_FILEFLAGS - FILEOS VER_FILEOS - FILETYPE VER_FILETYPE - FILESUBTYPE 0x0L -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "FileDescription", VER_FILE_DESCRIPTION_STR "\0" - VALUE "FileVersion", VER_FILE_VERSION_STR "\0" - VALUE "InternalName", VER_INTERNAL_NAME_STR "\0" - VALUE "LegalCopyright", VER_COPYRIGHT_STR "\0" - VALUE "OriginalFilename", VER_ORIGINAL_FILENAME_STR "\0" - VALUE "ProductName", VER_PRODUCTNAME_STR - VALUE "ProductVersion", VER_PRODUCT_VERSION_STR "\0" - END - END - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x409, 1200 - END -END - -#endif // English (United States) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" +#include "version.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VER_FILE_VERSION + PRODUCTVERSION VER_PRODUCT_VERSION + FILEFLAGSMASK 0x3fL + FILEFLAGS VER_FILEFLAGS + FILEOS VER_FILEOS + FILETYPE VER_FILETYPE + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "FileDescription", VER_FILE_DESCRIPTION_STR "\0" + VALUE "FileVersion", VER_FILE_VERSION_STR "\0" + VALUE "InternalName", VER_INTERNAL_NAME_STR "\0" + VALUE "LegalCopyright", VER_COPYRIGHT_STR "\0" + VALUE "OriginalFilename", VER_ORIGINAL_FILENAME_STR "\0" + VALUE "ProductName", VER_PRODUCTNAME_STR + VALUE "ProductVersion", VER_PRODUCT_VERSION_STR "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/src/manifest/shared.manifest b/src/manifest/shared.manifest index b337b7508c..8f6505c54d 100644 --- a/src/manifest/shared.manifest +++ b/src/manifest/shared.manifest @@ -1,25 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/nuget.config b/src/nuget.config index 32ddd30f3b..ca8e03c6da 100644 --- a/src/nuget.config +++ b/src/nuget.config @@ -1,10 +1,10 @@ - - - - - - - - - + + + + + + + + + \ No newline at end of file diff --git a/src/targets/EmbeddedCsWinRT.targets b/src/targets/EmbeddedCsWinRT.targets index 1d756e3adb..77315f9e0b 100644 --- a/src/targets/EmbeddedCsWinRT.targets +++ b/src/targets/EmbeddedCsWinRT.targets @@ -1,33 +1,33 @@ - - - true - false - - - - - - - - - - - - - - - - - - - - - - - - - + + + true + false + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/targets/ReferenceEmbeddedCsWinRTProject.targets b/src/targets/ReferenceEmbeddedCsWinRTProject.targets index 17d18b25a3..2c8deb0536 100644 --- a/src/targets/ReferenceEmbeddedCsWinRTProject.targets +++ b/src/targets/ReferenceEmbeddedCsWinRTProject.targets @@ -1,46 +1,46 @@ - - - false - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + false + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/e2e-setup.yml b/templates/e2e-setup.yml index 9537b6ff77..118f6b4d86 100644 --- a/templates/e2e-setup.yml +++ b/templates/e2e-setup.yml @@ -1,59 +1,59 @@ -# Configures local test source and local PowerShell repository. -parameters: -- name: sourceDir - type: string -- name: localhostWebServerArgs - type: string -- name: signingCertOutDir - type: string - default: $(Agent.TempDirectory) - -steps: - - pwsh: | - $newCertArguments = @{ - Type = "Custom" - Subject = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" - KeyUsage = "DigitalSignature" - TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") - CertStoreLocation = "Cert:\CurrentUser\My" - } - $cert = New-SelfSignedCertificate @newCertArguments - - $certPfxPath = Join-Path $(Agent.TempDirectory) TestSigningCert.pfx - $certCerPath = Join-Path ${{ parameters.signingCertOutDir }} TestSigningCert.cer - $certPassword = (New-Guid).ToString() - $certSecurePassword = ConvertTo-SecureString $certPassword -AsPlainText - - Export-PfxCertificate -Cert $cert -FilePath $certPfxPath -Password $certSecurePassword - Export-Certificate -Cert $cert -FilePath $certCerPath - - Write-Host "##vso[task.setvariable variable=TestSigningCert.PfxPath;]$certPfxPath" - Write-Host "##vso[task.setvariable variable=TestSigningCert.CerPath;]$certCerPath" - Write-Host "##vso[task.setvariable variable=TestSigningCert.Password;]$certPassword" - displayName: Create test codesigning cert - condition: succeededOrFailed() - - - pwsh: | - $httpsCertPath = Join-Path $(Agent.TempDirectory) HttpsCert.pfx - $httpsCertPassword = (New-Guid).ToString() - dotnet dev-certs https --export-path $httpsCertPath --password $httpsCertPassword - - $securePassword = ConvertTo-SecureString $httpsCertPassword -AsPlainText - Import-PfxCertificate -FilePath $httpsCertPath -Password $securePassword -CertStoreLocation Cert:\LocalMachine\Root - - Write-Host "##vso[task.setvariable variable=HttpsCert.Path;]$httpsCertPath" - Write-Host "##vso[task.setvariable variable=HttpsCert.Password;]$httpsCertPassword" - displayName: Create and install localhost HTTPS cert - condition: succeededOrFailed() - - - pwsh: ${{ parameters.sourceDir }}\src\LocalhostWebServer\Run-LocalhostWebServer.ps1 -CertPath $(HttpsCert.Path) -CertPassword $(HttpsCert.Password) -OutCertFile $(Agent.TempDirectory)\servercert.cer ${{ parameters.localhostWebServerArgs }} - displayName: Launch LocalhostWebServer - condition: succeededOrFailed() - - - task: PowerShell@2 - displayName: Setup Local PS Repository - condition: succeededOrFailed() - inputs: - filePath: '${{ parameters.sourceDir }}\src\AppInstallerCLIE2ETests\TestData\Configuration\Init-TestRepository.ps1' - arguments: '-Force' - pwsh: true +# Configures local test source and local PowerShell repository. +parameters: +- name: sourceDir + type: string +- name: localhostWebServerArgs + type: string +- name: signingCertOutDir + type: string + default: $(Agent.TempDirectory) + +steps: + - pwsh: | + $newCertArguments = @{ + Type = "Custom" + Subject = "CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US" + KeyUsage = "DigitalSignature" + TextExtension = @("2.5.29.37={text}1.3.6.1.5.5.7.3.3", "2.5.29.19={text}") + CertStoreLocation = "Cert:\CurrentUser\My" + } + $cert = New-SelfSignedCertificate @newCertArguments + + $certPfxPath = Join-Path $(Agent.TempDirectory) TestSigningCert.pfx + $certCerPath = Join-Path ${{ parameters.signingCertOutDir }} TestSigningCert.cer + $certPassword = (New-Guid).ToString() + $certSecurePassword = ConvertTo-SecureString $certPassword -AsPlainText + + Export-PfxCertificate -Cert $cert -FilePath $certPfxPath -Password $certSecurePassword + Export-Certificate -Cert $cert -FilePath $certCerPath + + Write-Host "##vso[task.setvariable variable=TestSigningCert.PfxPath;]$certPfxPath" + Write-Host "##vso[task.setvariable variable=TestSigningCert.CerPath;]$certCerPath" + Write-Host "##vso[task.setvariable variable=TestSigningCert.Password;]$certPassword" + displayName: Create test codesigning cert + condition: succeededOrFailed() + + - pwsh: | + $httpsCertPath = Join-Path $(Agent.TempDirectory) HttpsCert.pfx + $httpsCertPassword = (New-Guid).ToString() + dotnet dev-certs https --export-path $httpsCertPath --password $httpsCertPassword + + $securePassword = ConvertTo-SecureString $httpsCertPassword -AsPlainText + Import-PfxCertificate -FilePath $httpsCertPath -Password $securePassword -CertStoreLocation Cert:\LocalMachine\Root + + Write-Host "##vso[task.setvariable variable=HttpsCert.Path;]$httpsCertPath" + Write-Host "##vso[task.setvariable variable=HttpsCert.Password;]$httpsCertPassword" + displayName: Create and install localhost HTTPS cert + condition: succeededOrFailed() + + - pwsh: ${{ parameters.sourceDir }}\src\LocalhostWebServer\Run-LocalhostWebServer.ps1 -CertPath $(HttpsCert.Path) -CertPassword $(HttpsCert.Password) -OutCertFile $(Agent.TempDirectory)\servercert.cer ${{ parameters.localhostWebServerArgs }} + displayName: Launch LocalhostWebServer + condition: succeededOrFailed() + + - task: PowerShell@2 + displayName: Setup Local PS Repository + condition: succeededOrFailed() + inputs: + filePath: '${{ parameters.sourceDir }}\src\AppInstallerCLIE2ETests\TestData\Configuration\Init-TestRepository.ps1' + arguments: '-Force' + pwsh: true diff --git a/templates/e2e-test.template.yml b/templates/e2e-test.template.yml index 9231e26b04..9a2e9720f6 100644 --- a/templates/e2e-test.template.yml +++ b/templates/e2e-test.template.yml @@ -1,52 +1,52 @@ -parameters: -- name: title - type: string -- name: isPackaged - type: boolean -- name: filter - type: string -- name: experimentalFeatures - type: string - default: 'none' - -steps: - - task: CmdLine@2 - displayName: Start COM trace for ${{ parameters.title }} - condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) - inputs: - script: 'wpr -start $(Build.SourcesDirectory)\tools\COMTrace\ComTrace.wprp -filemode' - - - task: VSTest@2 - displayName: Run ${{ parameters.title }} - condition: succeededOrFailed() - inputs: - testRunTitle: ${{ parameters.title }} - testSelector: 'testAssemblies' - testAssemblyVer2: '$(buildOutDir)\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' - testFiltercriteria: ${{ parameters.filter }} - ${{ if eq(parameters.isPackaged, true) }}: - overrideTestrunParameters: '-PackagedContext true - -AICLIPackagePath $(packageLayoutDir) - -AICLIPath wingetdev.exe - -LooseFileRegistration true - -StaticFileRootPath $(buildOutDir)\E2ETests\TestLocalIndex - -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1 - -LocalServerCertPath $(Agent.TempDirectory)\servercert.cer - -SkipTestSource true - -InprocTestbedUseTestPackage true - -ForcedExperimentalFeatures ${{ parameters.experimentalFeatures }}' - ${{ else }}: - overrideTestrunParameters: '-PackagedContext false - -AICLIPath $(packageLayoutDir)\AppInstallerCLI\winget.exe - -StaticFileRootPath $(buildOutDir)\E2ETests\TestLocalIndex - -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1 - -LocalServerCertPath $(Agent.TempDirectory)\servercert.cer - -SkipTestSource true - -InprocTestbedUseTestPackage true - -ForcedExperimentalFeatures ${{ parameters.experimentalFeatures }}' - - - task: CmdLine@2 - displayName: Complete COM trace for ${{ parameters.title }} - condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) - inputs: - script: 'wpr -stop "$(artifactsDir)\ComTrace - ${{ parameters.title }}.etl"' +parameters: +- name: title + type: string +- name: isPackaged + type: boolean +- name: filter + type: string +- name: experimentalFeatures + type: string + default: 'none' + +steps: + - task: CmdLine@2 + displayName: Start COM trace for ${{ parameters.title }} + condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) + inputs: + script: 'wpr -start $(Build.SourcesDirectory)\tools\COMTrace\ComTrace.wprp -filemode' + + - task: VSTest@2 + displayName: Run ${{ parameters.title }} + condition: succeededOrFailed() + inputs: + testRunTitle: ${{ parameters.title }} + testSelector: 'testAssemblies' + testAssemblyVer2: '$(buildOutDir)\AppInstallerCLIE2ETests\AppInstallerCLIE2ETests.dll' + testFiltercriteria: ${{ parameters.filter }} + ${{ if eq(parameters.isPackaged, true) }}: + overrideTestrunParameters: '-PackagedContext true + -AICLIPackagePath $(packageLayoutDir) + -AICLIPath wingetdev.exe + -LooseFileRegistration true + -StaticFileRootPath $(buildOutDir)\E2ETests\TestLocalIndex + -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1 + -LocalServerCertPath $(Agent.TempDirectory)\servercert.cer + -SkipTestSource true + -InprocTestbedUseTestPackage true + -ForcedExperimentalFeatures ${{ parameters.experimentalFeatures }}' + ${{ else }}: + overrideTestrunParameters: '-PackagedContext false + -AICLIPath $(packageLayoutDir)\AppInstallerCLI\winget.exe + -StaticFileRootPath $(buildOutDir)\E2ETests\TestLocalIndex + -PowerShellModulePath $(buildOutDir)\PowerShell\Microsoft.WinGet.Client\Microsoft.WinGet.Client.psd1 + -LocalServerCertPath $(Agent.TempDirectory)\servercert.cer + -SkipTestSource true + -InprocTestbedUseTestPackage true + -ForcedExperimentalFeatures ${{ parameters.experimentalFeatures }}' + + - task: CmdLine@2 + displayName: Complete COM trace for ${{ parameters.title }} + condition: and(succeededOrFailed(), eq(variables['System.debug'], true)) + inputs: + script: 'wpr -stop "$(artifactsDir)\ComTrace - ${{ parameters.title }}.etl"' diff --git a/tools/COMTrace/ComTrace.wprp b/tools/COMTrace/ComTrace.wprp index 1fc059125f..a3931d18d8 100644 --- a/tools/COMTrace/ComTrace.wprp +++ b/tools/COMTrace/ComTrace.wprp @@ -1,49 +1,49 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/CorrelationTestbed/InSandboxScript.ps1 b/tools/CorrelationTestbed/InSandboxScript.ps1 index 5dd6778e94..e261f56f4b 100644 --- a/tools/CorrelationTestbed/InSandboxScript.ps1 +++ b/tools/CorrelationTestbed/InSandboxScript.ps1 @@ -1,128 +1,128 @@ -Param( - [String] $DesktopAppInstallerPath, - [String[]] $DesktopAppInstallerDependencyPath, - [String] $PackageIdentifier, - [String] $SourceName, - [String] $OutputPath, - [Switch] $UseDev, - [Switch] $MetadataCollection, - [String] $System32Path, - [Int32] $GeoID = 0 -) - -if ($GeoID -ne 0) -{ - Write-Host "--> Setting GeoID to $GeoID" - Set-WinHomeLocation -GeoId $GeoID -} - -function Get-ARPTable { - $registry_paths = @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') - return Get-ItemProperty $registry_paths -ErrorAction SilentlyContinue | - Select-Object DisplayName, DisplayVersion, Publisher, @{N='ProductCode'; E={$_.PSChildName}} | - Where-Object {$null -ne $_.DisplayName } -} - -$ProgressPreference = 'SilentlyContinue' - -$desktopPath = "C:\Users\WDAGUtilityAccount\Desktop" - -$regFilesDirPath = Join-Path $desktopPath "RegFiles" - -if (Test-Path $regFilesDirPath) -{ - foreach ($regFile in (Get-ChildItem $regFilesDirPath)) - { - - Write-Host @" ---> Importing reg file $($regFile.FullName) -"@ - reg import $($regFile.FullName) - } -} - -Write-Host @" ---> Installing WinGet - -"@ - -if ($UseDev) -{ - foreach($dependency in $DesktopAppInstallerDependencyPath) - { - Write-Host @" - ----> Installing $dependency -"@ - Add-AppxPackage -Path $dependency - } - - Write-Host @" - ----> Enabling dev mode -"@ - reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - - $devPackageManifestPath = Join-Path $desktopPath "DevPackage\AppxManifest.xml" - Write-Host @" - ----> Installing $devPackageManifestPath -"@ - Add-AppxPackage -Path $devPackageManifestPath -Register -} -else -{ - Install-PackageProvider -Name NuGet -Force | Out-Null - Install-Module Microsoft.WinGet.Client -Force | Out-Null - Repair-WingetPackageManager -Latest -} - -$originalARP = Get-ARPTable - -Write-Host @" - ---> Installing $PackageIdentifier - -"@ - -$installAndCorrelateOutPath = Join-Path $OutputPath "install_and_correlate.json" - -$installAndCheckCorrelationExe = Join-Path $desktopPath "InstallAndCheckCorrelation\InstallAndCheckCorrelation.exe" -$installAndCheckCorrelationArgs = @('-id', $PackageIdentifier, '-src', $SourceName, '-out', $installAndCorrelateOutPath) - -if ($UseDev) -{ - $installAndCheckCorrelationArgs += '-dev' -} - -if ($MetadataCollection) -{ - $wingetUtilPath = Join-Path $PSScriptRoot "WinGetUtil.dll" - $installAndCheckCorrelationArgs += ('-meta', $wingetUtilPath, '-sys32', $System32Path) -} - -& $installAndCheckCorrelationExe $installAndCheckCorrelationArgs - -Write-Host @" - ---> Copying logs -"@ - -if ($UseDev) -{ - Copy-Item -Recurse (Join-Path $env:LOCALAPPDATA "Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir") $OutputPath -} -else -{ - Copy-Item -Recurse (Join-Path $env:LOCALAPPDATA "Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir") $OutputPath -} - - -Write-Host @" - ---> Comparing ARP Entries -"@ - -$arpCompared = (Compare-Object (Get-ARPTable) $originalARP -Property DisplayName,DisplayVersion,Publisher,ProductCode) -$arpCompared | Select-Object -Property * -ExcludeProperty SideIndicator | Format-Table - -$arpCompared | Select-Object -Property * -ExcludeProperty SideIndicator | Format-Table | Out-File (Join-Path $OutputPath "ARPCompare.txt") - -"Done" | Out-File (Join-Path $OutputPath "done.txt") +Param( + [String] $DesktopAppInstallerPath, + [String[]] $DesktopAppInstallerDependencyPath, + [String] $PackageIdentifier, + [String] $SourceName, + [String] $OutputPath, + [Switch] $UseDev, + [Switch] $MetadataCollection, + [String] $System32Path, + [Int32] $GeoID = 0 +) + +if ($GeoID -ne 0) +{ + Write-Host "--> Setting GeoID to $GeoID" + Set-WinHomeLocation -GeoId $GeoID +} + +function Get-ARPTable { + $registry_paths = @('HKLM:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*','HKLM:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*', 'HKCU:\Software\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*') + return Get-ItemProperty $registry_paths -ErrorAction SilentlyContinue | + Select-Object DisplayName, DisplayVersion, Publisher, @{N='ProductCode'; E={$_.PSChildName}} | + Where-Object {$null -ne $_.DisplayName } +} + +$ProgressPreference = 'SilentlyContinue' + +$desktopPath = "C:\Users\WDAGUtilityAccount\Desktop" + +$regFilesDirPath = Join-Path $desktopPath "RegFiles" + +if (Test-Path $regFilesDirPath) +{ + foreach ($regFile in (Get-ChildItem $regFilesDirPath)) + { + + Write-Host @" +--> Importing reg file $($regFile.FullName) +"@ + reg import $($regFile.FullName) + } +} + +Write-Host @" +--> Installing WinGet + +"@ + +if ($UseDev) +{ + foreach($dependency in $DesktopAppInstallerDependencyPath) + { + Write-Host @" + ----> Installing $dependency +"@ + Add-AppxPackage -Path $dependency + } + + Write-Host @" + ----> Enabling dev mode +"@ + reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" + + $devPackageManifestPath = Join-Path $desktopPath "DevPackage\AppxManifest.xml" + Write-Host @" + ----> Installing $devPackageManifestPath +"@ + Add-AppxPackage -Path $devPackageManifestPath -Register +} +else +{ + Install-PackageProvider -Name NuGet -Force | Out-Null + Install-Module Microsoft.WinGet.Client -Force | Out-Null + Repair-WingetPackageManager -Latest +} + +$originalARP = Get-ARPTable + +Write-Host @" + +--> Installing $PackageIdentifier + +"@ + +$installAndCorrelateOutPath = Join-Path $OutputPath "install_and_correlate.json" + +$installAndCheckCorrelationExe = Join-Path $desktopPath "InstallAndCheckCorrelation\InstallAndCheckCorrelation.exe" +$installAndCheckCorrelationArgs = @('-id', $PackageIdentifier, '-src', $SourceName, '-out', $installAndCorrelateOutPath) + +if ($UseDev) +{ + $installAndCheckCorrelationArgs += '-dev' +} + +if ($MetadataCollection) +{ + $wingetUtilPath = Join-Path $PSScriptRoot "WinGetUtil.dll" + $installAndCheckCorrelationArgs += ('-meta', $wingetUtilPath, '-sys32', $System32Path) +} + +& $installAndCheckCorrelationExe $installAndCheckCorrelationArgs + +Write-Host @" + +--> Copying logs +"@ + +if ($UseDev) +{ + Copy-Item -Recurse (Join-Path $env:LOCALAPPDATA "Packages\WinGetDevCLI_8wekyb3d8bbwe\LocalState\DiagOutputDir") $OutputPath +} +else +{ + Copy-Item -Recurse (Join-Path $env:LOCALAPPDATA "Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\DiagOutputDir") $OutputPath +} + + +Write-Host @" + +--> Comparing ARP Entries +"@ + +$arpCompared = (Compare-Object (Get-ARPTable) $originalARP -Property DisplayName,DisplayVersion,Publisher,ProductCode) +$arpCompared | Select-Object -Property * -ExcludeProperty SideIndicator | Format-Table + +$arpCompared | Select-Object -Property * -ExcludeProperty SideIndicator | Format-Table | Out-File (Join-Path $OutputPath "ARPCompare.txt") + +"Done" | Out-File (Join-Path $OutputPath "done.txt") diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation.sln b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation.sln index 03abce3ce3..af6197b7b7 100644 --- a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation.sln +++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation.sln @@ -1,31 +1,31 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31911.196 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InstallAndCheckCorrelation", "InstallAndCheckCorrelation\InstallAndCheckCorrelation.vcxproj", "{204CD25F-AAEA-4CA1-AB9F-A26747976932}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x64.ActiveCfg = Debug|x64 - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x64.Build.0 = Debug|x64 - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x86.ActiveCfg = Debug|Win32 - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x86.Build.0 = Debug|Win32 - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x64.ActiveCfg = Release|x64 - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x64.Build.0 = Release|x64 - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x86.ActiveCfg = Release|Win32 - {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {77ED5C57-8A8C-4D87-9818-ABFB99B8E9D2} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31911.196 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InstallAndCheckCorrelation", "InstallAndCheckCorrelation\InstallAndCheckCorrelation.vcxproj", "{204CD25F-AAEA-4CA1-AB9F-A26747976932}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x64.ActiveCfg = Debug|x64 + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x64.Build.0 = Debug|x64 + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x86.ActiveCfg = Debug|Win32 + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Debug|x86.Build.0 = Debug|Win32 + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x64.ActiveCfg = Release|x64 + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x64.Build.0 = Release|x64 + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x86.ActiveCfg = Release|Win32 + {204CD25F-AAEA-4CA1-AB9F-A26747976932}.Release|x86.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {77ED5C57-8A8C-4D87-9818-ABFB99B8E9D2} + EndGlobalSection +EndGlobal diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.cpp b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.cpp index f55c962138..696433a27e 100644 --- a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.cpp +++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.cpp @@ -1,761 +1,761 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -#include - -using namespace std::string_view_literals; -using namespace winrt::Microsoft::Management::Deployment; -using namespace winrt::Windows::Foundation; - -template -struct JSONPair -{ - JSONPair(std::string_view name, const T& value, bool comma = true) : - Name(name), Value(value), Comma(comma) - {} - - std::string_view Name; - const T& Value; - bool Comma; -}; - -template -struct JSONControl -{ - constexpr static bool quote = true; - constexpr static bool output = true; -}; - -template <> -struct JSONControl -{ - constexpr static bool quote = false; - constexpr static bool output = true; -}; - -template <> -struct JSONControl -{ - constexpr static bool quote = false; - constexpr static bool output = true; -}; - -template <> -struct JSONControl -{ - constexpr static bool quote = false; - constexpr static bool output = false; -}; - -template -std::ostream& operator<<(std::ostream& out, const JSONPair& pair) -{ - out << '"' << pair.Name << "\": "; - if (JSONControl::quote) - { - out << '"'; - } - if (JSONControl::output) - { - out << pair.Value; - } - if (JSONControl::quote) - { - out << '"'; - } - if (pair.Comma) - { - out << ','; - } - return out << std::endl; -} - -std::string ConvertToUTF8(std::wstring_view input) -{ - if (input.empty()) - { - return {}; - } - - int utf8ByteCount = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0, nullptr, nullptr); - THROW_LAST_ERROR_IF(utf8ByteCount == 0); - - // Since the string view should not contain the null char, the result won't either. - // This allows us to use the resulting size value directly in the string constructor. - std::string result(wil::safe_cast(utf8ByteCount), '\0'); - - int utf8BytesWritten = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size()), nullptr, nullptr); - FAIL_FAST_HR_IF(E_UNEXPECTED, utf8ByteCount != utf8BytesWritten); - - return result; -} - -std::wstring ConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8) -{ - if (input.empty()) - { - return {}; - } - - int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0); - THROW_LAST_ERROR_IF(utf16CharCount == 0); - - // Since the string view should not contain the null char, the result won't either. - // This allows us to use the resulting size value directly in the string constructor. - std::wstring result(wil::safe_cast(utf16CharCount), L'\0'); - - int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size())); - FAIL_FAST_HR_IF(E_UNEXPECTED, utf16CharCount != utf16CharsWritten); - - return result; -} - -// CLSIDs for WinGet package -const CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 }; //C53A4F16-787E-42A4-B304-29EFFB4BF597 -const CLSID CLSID_InstallOptions = { 0x1095f097, 0xEB96, 0x453B, 0xB4, 0xE6, 0x16, 0x13, 0x63, 0x7F, 0x3B, 0x14 }; //1095F097-EB96-453B-B4E6-1613637F3B14 -const CLSID CLSID_FindPackagesOptions = { 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; //572DED96-9C60-4526-8F92-EE7D91D38C1A -const CLSID CLSID_PackageMatchFilter = { 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; //D02C9DAF-99DC-429C-B503-4E504E4AB000 -const CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; //526534B8-7E46-47C8-8416-B1685C327D37 - -// CLSIDs for WinGetDev package -const CLSID CLSID_PackageManager2 = { 0x74CB3139, 0xB7C5, 0x4B9E, { 0x93, 0x88, 0xE6, 0x61, 0x6D, 0xEA, 0x28, 0x8C } }; //74CB3139-B7C5-4B9E-9388-E6616DEA288C -const CLSID CLSID_InstallOptions2 = { 0x44FE0580, 0x62F7, 0x44D4, 0x9E, 0x91, 0xAA, 0x96, 0x14, 0xAB, 0x3E, 0x86 }; //44FE0580-62F7-44D4-9E91-AA9614AB3E86 -const CLSID CLSID_FindPackagesOptions2 = { 0x1BD8FF3A, 0xEC50, 0x4F69, { 0xAE, 0xEE, 0xDF, 0x4C, 0x9D, 0x3B, 0xAA, 0x96 } }; //1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96 -const CLSID CLSID_PackageMatchFilter2 = { 0x3F85B9F4, 0x487A, 0x4C48, { 0x90, 0x35, 0x29, 0x03, 0xF8, 0xA6, 0xD9, 0xE8 } }; //3F85B9F4-487A-4C48-9035-2903F8A6D9E8 -const CLSID CLSID_CreateCompositePackageCatalogOptions2 = { 0xEE160901, 0xB317, 0x4EA7, { 0x9C, 0xC6, 0x53, 0x55, 0xC6, 0xD7, 0xD8, 0xA7 } }; //EE160901-B317-4EA7-9CC6-5355C6D7D8A7 - -// Helper object to make cleaner error handling -struct Main -{ - std::string packageIdentifier; - std::string sourceName; - std::filesystem::path outputPath; - bool metadataCollection = false; - std::filesystem::path wingetUtilPath; - std::filesystem::path sys32Path; - bool useDevCLSIDs = false; - bool onlyCorrelate = false; - - int ParseArgs(int argc, char** argv) - { - // Supports the following arguments: - // -id : [Required] The PackageIdentifier to install and check for correlation - // -src : [Required] The source name for the package to install - // -out : [Required] The file to write results to - // -dev : [Optional] Use the dev CLSIDs - // -cor : [Optional] Only correlate the package - - for (int i = 1; i < argc; ++i) - { - if ("-id"sv == argv[i] && i + 1 < argc) - { - packageIdentifier = argv[++i]; - } - else if ("-src"sv == argv[i] && i + 1 < argc) - { - sourceName = argv[++i]; - } - else if ("-out"sv == argv[i] && i + 1 < argc) - { - outputPath = argv[++i]; - } - else if ("-meta"sv == argv[i] && i + 1 < argc) - { - metadataCollection = true; - wingetUtilPath = argv[++i]; - } - else if ("-sys32"sv == argv[i] && i + 1 < argc) - { - sys32Path = argv[++i]; - } - else if ("-dev"sv == argv[i]) - { - useDevCLSIDs = true; - } - else if ("-cor"sv == argv[i]) - { - onlyCorrelate = true; - } - } - - // Check inputs - if (outputPath.empty()) - { - std::cout << "No output file path specified, use -out" << std::endl; - return 2; - } - - if (!outputPath.has_stem()) - { - std::cout << "Output path is not a file" << std::endl; - return 3; - } - - std::filesystem::create_directories(outputPath.parent_path()); - - outputStream.open(outputPath); - - if (!outputStream) - { - std::cout << "Output file could not be created" << std::endl; - return 4; - } - - return 0; - } - - PackageManager CreatePackageManager() - { - if (useDevCLSIDs) - { - return winrt::create_instance(CLSID_PackageManager2, CLSCTX_ALL); - } - return winrt::create_instance(CLSID_PackageManager, CLSCTX_ALL); - } - - InstallOptions CreateInstallOptions() - { - if (useDevCLSIDs) - { - return winrt::create_instance(CLSID_InstallOptions2, CLSCTX_ALL); - } - return winrt::create_instance(CLSID_InstallOptions, CLSCTX_ALL); - } - - FindPackagesOptions CreateFindPackagesOptions() - { - if (useDevCLSIDs) - { - return winrt::create_instance(CLSID_FindPackagesOptions2, CLSCTX_ALL); - } - return winrt::create_instance(CLSID_FindPackagesOptions, CLSCTX_ALL); - } - - CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() - { - if (useDevCLSIDs) - { - return winrt::create_instance(CLSID_CreateCompositePackageCatalogOptions2, CLSCTX_ALL); - } - return winrt::create_instance(CLSID_CreateCompositePackageCatalogOptions, CLSCTX_ALL); - } - - PackageMatchFilter CreatePackageMatchFilter() - { - if (useDevCLSIDs) - { - return winrt::create_instance(CLSID_PackageMatchFilter2, CLSCTX_ALL); - } - return winrt::create_instance(CLSID_PackageMatchFilter, CLSCTX_ALL); - } - - std::ofstream outputStream; - - // Result file outputs - HRESULT hr = S_OK; - std::string error; - std::string phase; - std::string action; - std::string packageName; - std::string packagePublisher; - bool correlatePackageKnown = false; - std::string packageKnownName; - std::string packageKnownPublisher; - bool correlateArchive = false; - std::string archiveName; - std::string archivePublisher; - - void ValidateArgs() - { - if (packageIdentifier.empty()) - { - hr = E_INVALIDARG; - error = "A package identifier must be supplied, use -id"; - return; - } - - if (sourceName.empty()) - { - hr = E_INVALIDARG; - error = "A source name must be supplied, use -src"; - return; - } - - if (metadataCollection && sys32Path.empty()) - { - hr = E_INVALIDARG; - error = "Metadata collection requires mapping in the host's System32"; - return; - } - } - - using WinGetBeginInstallerMetadataCollectionPtr = HRESULT (__stdcall *)( - WINGET_STRING inputJSON, - WINGET_STRING logFilePath, - WinGetBeginInstallerMetadataCollectionOptions options, - WINGET_INSTALLER_METADATA_COLLECTION_HANDLE* collectionHandle); - - using WinGetCompleteInstallerMetadataCollectionPtr = HRESULT(__stdcall*)( - WINGET_INSTALLER_METADATA_COLLECTION_HANDLE collectionHandle, - WINGET_STRING outputFilePath, - WinGetCompleteInstallerMetadataCollectionOptions options); - - WinGetBeginInstallerMetadataCollectionPtr WinGetBeginInstallerMetadataCollection = nullptr; - WinGetCompleteInstallerMetadataCollectionPtr WinGetCompleteInstallerMetadataCollection = nullptr; - WINGET_INSTALLER_METADATA_COLLECTION_HANDLE MetadataCollectionHandle = nullptr; - - void LoadWingetUtil() - { - AddDllDirectory(sys32Path.wstring().c_str()); - HMODULE wingetutilModule = LoadLibraryExW(wingetUtilPath.wstring().c_str(), nullptr, LOAD_LIBRARY_SEARCH_USER_DIRS); - if (!wingetutilModule) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - return; - } - - this->WinGetBeginInstallerMetadataCollection = reinterpret_cast(GetProcAddress(wingetutilModule, "WinGetBeginInstallerMetadataCollection")); - if (!this->WinGetBeginInstallerMetadataCollection) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - return; - } - - this->WinGetCompleteInstallerMetadataCollection = reinterpret_cast(GetProcAddress(wingetutilModule, "WinGetCompleteInstallerMetadataCollection")); - if (!this->WinGetCompleteInstallerMetadataCollection) - { - hr = HRESULT_FROM_WIN32(GetLastError()); - return; - } - } - - void BeginMetadataCollection() - { - std::filesystem::path metadataInputPath = outputPath.parent_path(); - metadataInputPath /= "metadata_input.json"; - std::ofstream stream{ metadataInputPath }; - - stream << "{" << std::endl; - stream << JSONPair{ "supportedMetadataVersion", "1.2"}; - // TODO: Could theoretically produce this if we could enumerate the data via COM - // stream << JSONPair{ "currentMetadata", "" }; - stream << JSONPair{ "submissionData", nullptr, false } << "\n{\n"; - stream << JSONPair{ "submissionIdentifier", packageIdentifier, false }; - stream << "\n},\n"; - stream << JSONPair{ "packageData", nullptr, false } << "\n{\n"; - stream << JSONPair{ "installerHash", "none" }; - stream << JSONPair{ "DefaultLocale", nullptr, false } << "\n{\n"; - stream << JSONPair{ "PackageLocale", "x-neutral" }; - stream << JSONPair{ "PackageName", packageName }; - stream << JSONPair{ "Publisher", packagePublisher, false }; - stream << "\n}\n"; - stream << "\n},\n"; - // Keep at the end to prevent a dangling comma - stream << JSONPair{ "version", "1.0", false } << "}" << std::endl; - - std::filesystem::path metadataLogPath = outputPath.parent_path(); - metadataLogPath /= "metadata_log.txt"; - - hr = this->WinGetBeginInstallerMetadataCollection(metadataInputPath.wstring().c_str(), metadataLogPath.wstring().c_str(), WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath, &MetadataCollectionHandle); - if (FAILED(hr)) - { - return; - } - } - - void CompleteMetadataCollection() - { - std::filesystem::path metadataOutputPath = outputPath.parent_path(); - metadataOutputPath /= "metadata_output.json"; - - hr = this->WinGetCompleteInstallerMetadataCollection(MetadataCollectionHandle, metadataOutputPath.wstring().c_str(), WinGetCompleteInstallerMetadataCollectionOption_None); - if (FAILED(hr)) - { - return; - } - } - - void Install() - { - try - { - action = "Create package manager"; - auto packageManager = CreatePackageManager(); - - action = "Get source reference"; - auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName)); - - action = "Connecting to catalog"; - auto connectResult = sourceRef.Connect(); - - if (connectResult.Status() != ConnectResultStatus::Ok) - { - hr = E_FAIL; - error = "Error connecting to catalog"; - return; - } - - auto catalog = connectResult.PackageCatalog(); - - action = "Create find options"; - auto findOptions = CreateFindPackagesOptions(); - - action = "Add package id filter"; - auto filter = CreatePackageMatchFilter(); - filter.Field(PackageMatchField::Id); - filter.Option(PackageFieldMatchOption::Equals); - filter.Value(ConvertToUTF16(packageIdentifier)); - findOptions.Filters().Append(filter); - - action = "Find package"; - auto findResult = catalog.FindPackages(findOptions); - - if (findResult.Status() != FindPackagesResultStatus::Ok) - { - hr = E_FAIL; - std::ostringstream stream; - stream << "Error " << static_cast(findResult.Status()) << " finding package"; - error = std::move(stream).str(); - return; - } - - action = "Get match"; - auto matches = findResult.Matches(); - - if (matches.Size() == 0) - { - hr = E_NOT_SET; - error = "Package not found"; - return; - } - - auto package = matches.GetAt(0).CatalogPackage(); - - action = "Inspect package"; - auto installVersion = package.DefaultInstallVersion(); - packageName = ConvertToUTF8(installVersion.DisplayName()); - packagePublisher = ConvertToUTF8(installVersion.Publisher()); - - if (metadataCollection) - { - BeginMetadataCollection(); - if (FAILED(hr)) - { - return; - } - } - - if (!onlyCorrelate) - { - action = "Create install options"; - auto installOptions = CreateInstallOptions(); - - installOptions.PackageInstallScope(PackageInstallScope::Any); - installOptions.PackageInstallMode(PackageInstallMode::Silent); - - std::cout << "Beginning to install " << packageIdentifier << " (" << packageName << ") from " << sourceName << "..." << std::endl; - action = "Install package"; - auto installOperation = packageManager.InstallPackageAsync(package, installOptions); - - if (installOperation.wait_for(std::chrono::minutes(10)) != AsyncStatus::Completed) - { - hr = E_FAIL; - error = "Install operation timed out"; - return; - } - - auto installResult = installOperation.GetResults(); - if (installResult.Status() != InstallResultStatus::Ok) - { - hr = installResult.ExtendedErrorCode(); - error = "Error installing package"; - return; - } - } - - if (metadataCollection) - { - CompleteMetadataCollection(); - if (FAILED(hr)) - { - return; - } - } - } - catch (const winrt::hresult_error& hre) - { - hr = hre.code(); - error = ConvertToUTF8(hre.message()); - } - } - - void CorrelatePackageKnown() - { - try - { - action = "Create package manager"; - auto packageManager = CreatePackageManager(); - - action = "Get source reference"; - auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName)); - - action = "Create composite catalog options"; - auto compOptions = CreateCreateCompositePackageCatalogOptions(); - - compOptions.Catalogs().Append(sourceRef); - compOptions.CompositeSearchBehavior(CompositeSearchBehavior::RemotePackagesFromAllCatalogs); - - action = "Create composite catalog reference"; - auto compRef = packageManager.CreateCompositePackageCatalog(compOptions); - - action = "Connecting to catalog"; - auto connectResult = compRef.Connect(); - - if (connectResult.Status() != ConnectResultStatus::Ok) - { - hr = E_FAIL; - error = "Error connecting to catalog"; - return; - } - - auto catalog = connectResult.PackageCatalog(); - - action = "Create find options"; - auto findOptions = CreateFindPackagesOptions(); - - action = "Add package id filter"; - auto filter = CreatePackageMatchFilter(); - filter.Field(PackageMatchField::Id); - filter.Option(PackageFieldMatchOption::Equals); - filter.Value(ConvertToUTF16(packageIdentifier)); - findOptions.Filters().Append(filter); - - action = "Find package"; - auto findResult = catalog.FindPackages(findOptions); - - if (findResult.Status() != FindPackagesResultStatus::Ok) - { - hr = E_FAIL; - error = "Error finding packages"; - return; - } - - action = "Get match"; - auto matches = findResult.Matches(); - - if (matches.Size() == 0) - { - hr = E_NOT_SET; - error = "Package not found"; - return; - } - - auto package = matches.GetAt(0).CatalogPackage(); - - action = "Inspect package for installed version"; - auto installed = package.InstalledVersion(); - - if (installed) - { - correlatePackageKnown = true; - packageKnownName = ConvertToUTF8(installed.DisplayName()); - packageKnownPublisher = ConvertToUTF8(installed.Publisher()); - } - } - catch (const winrt::hresult_error& hre) - { - hr = hre.code(); - error = ConvertToUTF8(hre.message()); - } - } - - void CorrelateArchive() - { - try - { - action = "Create package manager"; - auto packageManager = CreatePackageManager(); - - action = "Get source reference"; - auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName)); - - action = "Create composite catalog options"; - auto compOptions = CreateCreateCompositePackageCatalogOptions(); - - compOptions.Catalogs().Append(sourceRef); - compOptions.CompositeSearchBehavior(CompositeSearchBehavior::LocalCatalogs); - - action = "Create composite catalog reference"; - auto compRef = packageManager.CreateCompositePackageCatalog(compOptions); - - action = "Connecting to catalog"; - auto connectResult = compRef.Connect(); - - if (connectResult.Status() != ConnectResultStatus::Ok) - { - hr = E_FAIL; - error = "Error connecting to catalog"; - return; - } - - auto catalog = connectResult.PackageCatalog(); - - action = "Create find options"; - auto findOptions = CreateFindPackagesOptions(); - - action = "Find package"; - auto findResult = catalog.FindPackages(findOptions); - - if (findResult.Status() != FindPackagesResultStatus::Ok) - { - hr = E_FAIL; - error = "Error finding packages"; - return; - } - - action = "Get matches"; - auto matches = findResult.Matches(); - - action = "Get source info"; - auto sourceInfo = sourceRef.Info(); - auto sourceIdentifier = sourceInfo.Id(); - auto sourceType = sourceInfo.Type(); - - for (const auto& match : matches) - { - auto package = match.CatalogPackage(); - - if (ConvertToUTF8(package.Id()) != packageIdentifier) - { - continue; - } - - auto installed = package.InstalledVersion(); - - if (installed) - { - auto installedCatalogInfo = installed.PackageCatalog().Info(); - - if (installedCatalogInfo.Id() == sourceIdentifier && installedCatalogInfo.Type() == sourceType) - { - correlateArchive = true; - archiveName = ConvertToUTF8(installed.DisplayName()); - archivePublisher = ConvertToUTF8(installed.Publisher()); - break; - } - } - } - } - catch (const winrt::hresult_error& hre) - { - hr = hre.code(); - error = ConvertToUTF8(hre.message()); - } - } - - void ReportResult() - { - if (outputStream) - { - outputStream << "{" << std::endl; - outputStream << JSONPair{ "PackageIdentifier", packageIdentifier }; - outputStream << JSONPair{ "Source", sourceName }; - outputStream << JSONPair{ "UseDev", useDevCLSIDs }; - outputStream << JSONPair{ "Error", error }; - outputStream << JSONPair{ "Phase", phase }; - outputStream << JSONPair{ "Action", action }; - outputStream << JSONPair{ "PackageName", packageName }; - outputStream << JSONPair{ "PackagePublisher", packagePublisher }; - outputStream << JSONPair{ "CorrelatePackageKnown", correlatePackageKnown }; - outputStream << JSONPair{ "PackageKnownName", packageKnownName }; - outputStream << JSONPair{ "PackageKnownPublisher", packageKnownPublisher }; - outputStream << JSONPair{ "CorrelateArchive", correlateArchive }; - outputStream << JSONPair{ "ArchiveName", archiveName }; - outputStream << JSONPair{ "ArchivePublisher", archivePublisher }; - // Keep at the end to prevent a dangling comma - outputStream << JSONPair{ "HRESULT", hr, false } << "}" << std::endl; - } - } - - void main(int argc, char** argv) - { - hr = ParseArgs(argc, argv); - if (hr != 0) - { - return; - } - - ValidateArgs(); - if (FAILED(hr)) - { - return; - } - - if (metadataCollection) - { - LoadWingetUtil(); - if (FAILED(hr)) - { - return; - } - } - - auto co_uninitialize = wil::CoInitializeEx(); - - // Execute the install step - phase = "Install"; - std::cout << "Connecting to PackageManager..." << std::endl; - Install(); - if (FAILED(hr)) - { - return; - } - - // Check for the installed package being correlated when the remote package is known, - // as when trying to determine information about a single known package. - phase = "Correlate when package known"; - std::cout << "Correlating package when known..." << std::endl; - CorrelatePackageKnown(); - - // Check for the installed package being correlated when archiving local package information. - phase = "Correlate when archiving"; - std::cout << "Correlating package when archiving..." << std::endl; - CorrelateArchive(); - - std::cout << "Done" << std::endl; - phase = "Completed"; - action.clear(); - } -}; - -int main(int argc, char** argv) try -{ - Main mainMain; - mainMain.main(argc, argv); - mainMain.ReportResult(); - return mainMain.hr; -} -catch (const std::exception& e) -{ - std::cout << "Exception occurred: " << e.what() << std::endl; - return 1; -} -catch (...) -{ - std::cout << "Unknown exception occurred" << std::endl; - return 1; -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include + +using namespace std::string_view_literals; +using namespace winrt::Microsoft::Management::Deployment; +using namespace winrt::Windows::Foundation; + +template +struct JSONPair +{ + JSONPair(std::string_view name, const T& value, bool comma = true) : + Name(name), Value(value), Comma(comma) + {} + + std::string_view Name; + const T& Value; + bool Comma; +}; + +template +struct JSONControl +{ + constexpr static bool quote = true; + constexpr static bool output = true; +}; + +template <> +struct JSONControl +{ + constexpr static bool quote = false; + constexpr static bool output = true; +}; + +template <> +struct JSONControl +{ + constexpr static bool quote = false; + constexpr static bool output = true; +}; + +template <> +struct JSONControl +{ + constexpr static bool quote = false; + constexpr static bool output = false; +}; + +template +std::ostream& operator<<(std::ostream& out, const JSONPair& pair) +{ + out << '"' << pair.Name << "\": "; + if (JSONControl::quote) + { + out << '"'; + } + if (JSONControl::output) + { + out << pair.Value; + } + if (JSONControl::quote) + { + out << '"'; + } + if (pair.Comma) + { + out << ','; + } + return out << std::endl; +} + +std::string ConvertToUTF8(std::wstring_view input) +{ + if (input.empty()) + { + return {}; + } + + int utf8ByteCount = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0, nullptr, nullptr); + THROW_LAST_ERROR_IF(utf8ByteCount == 0); + + // Since the string view should not contain the null char, the result won't either. + // This allows us to use the resulting size value directly in the string constructor. + std::string result(wil::safe_cast(utf8ByteCount), '\0'); + + int utf8BytesWritten = WideCharToMultiByte(CP_UTF8, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size()), nullptr, nullptr); + FAIL_FAST_HR_IF(E_UNEXPECTED, utf8ByteCount != utf8BytesWritten); + + return result; +} + +std::wstring ConvertToUTF16(std::string_view input, UINT codePage = CP_UTF8) +{ + if (input.empty()) + { + return {}; + } + + int utf16CharCount = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), nullptr, 0); + THROW_LAST_ERROR_IF(utf16CharCount == 0); + + // Since the string view should not contain the null char, the result won't either. + // This allows us to use the resulting size value directly in the string constructor. + std::wstring result(wil::safe_cast(utf16CharCount), L'\0'); + + int utf16CharsWritten = MultiByteToWideChar(codePage, 0, input.data(), wil::safe_cast(input.length()), &result[0], wil::safe_cast(result.size())); + FAIL_FAST_HR_IF(E_UNEXPECTED, utf16CharCount != utf16CharsWritten); + + return result; +} + +// CLSIDs for WinGet package +const CLSID CLSID_PackageManager = { 0xC53A4F16, 0x787E, 0x42A4, 0xB3, 0x04, 0x29, 0xEF, 0xFB, 0x4B, 0xF5, 0x97 }; //C53A4F16-787E-42A4-B304-29EFFB4BF597 +const CLSID CLSID_InstallOptions = { 0x1095f097, 0xEB96, 0x453B, 0xB4, 0xE6, 0x16, 0x13, 0x63, 0x7F, 0x3B, 0x14 }; //1095F097-EB96-453B-B4E6-1613637F3B14 +const CLSID CLSID_FindPackagesOptions = { 0x572DED96, 0x9C60, 0x4526, { 0x8F, 0x92, 0xEE, 0x7D, 0x91, 0xD3, 0x8C, 0x1A } }; //572DED96-9C60-4526-8F92-EE7D91D38C1A +const CLSID CLSID_PackageMatchFilter = { 0xD02C9DAF, 0x99DC, 0x429C, { 0xB5, 0x03, 0x4E, 0x50, 0x4E, 0x4A, 0xB0, 0x00 } }; //D02C9DAF-99DC-429C-B503-4E504E4AB000 +const CLSID CLSID_CreateCompositePackageCatalogOptions = { 0x526534B8, 0x7E46, 0x47C8, { 0x84, 0x16, 0xB1, 0x68, 0x5C, 0x32, 0x7D, 0x37 } }; //526534B8-7E46-47C8-8416-B1685C327D37 + +// CLSIDs for WinGetDev package +const CLSID CLSID_PackageManager2 = { 0x74CB3139, 0xB7C5, 0x4B9E, { 0x93, 0x88, 0xE6, 0x61, 0x6D, 0xEA, 0x28, 0x8C } }; //74CB3139-B7C5-4B9E-9388-E6616DEA288C +const CLSID CLSID_InstallOptions2 = { 0x44FE0580, 0x62F7, 0x44D4, 0x9E, 0x91, 0xAA, 0x96, 0x14, 0xAB, 0x3E, 0x86 }; //44FE0580-62F7-44D4-9E91-AA9614AB3E86 +const CLSID CLSID_FindPackagesOptions2 = { 0x1BD8FF3A, 0xEC50, 0x4F69, { 0xAE, 0xEE, 0xDF, 0x4C, 0x9D, 0x3B, 0xAA, 0x96 } }; //1BD8FF3A-EC50-4F69-AEEE-DF4C9D3BAA96 +const CLSID CLSID_PackageMatchFilter2 = { 0x3F85B9F4, 0x487A, 0x4C48, { 0x90, 0x35, 0x29, 0x03, 0xF8, 0xA6, 0xD9, 0xE8 } }; //3F85B9F4-487A-4C48-9035-2903F8A6D9E8 +const CLSID CLSID_CreateCompositePackageCatalogOptions2 = { 0xEE160901, 0xB317, 0x4EA7, { 0x9C, 0xC6, 0x53, 0x55, 0xC6, 0xD7, 0xD8, 0xA7 } }; //EE160901-B317-4EA7-9CC6-5355C6D7D8A7 + +// Helper object to make cleaner error handling +struct Main +{ + std::string packageIdentifier; + std::string sourceName; + std::filesystem::path outputPath; + bool metadataCollection = false; + std::filesystem::path wingetUtilPath; + std::filesystem::path sys32Path; + bool useDevCLSIDs = false; + bool onlyCorrelate = false; + + int ParseArgs(int argc, char** argv) + { + // Supports the following arguments: + // -id : [Required] The PackageIdentifier to install and check for correlation + // -src : [Required] The source name for the package to install + // -out : [Required] The file to write results to + // -dev : [Optional] Use the dev CLSIDs + // -cor : [Optional] Only correlate the package + + for (int i = 1; i < argc; ++i) + { + if ("-id"sv == argv[i] && i + 1 < argc) + { + packageIdentifier = argv[++i]; + } + else if ("-src"sv == argv[i] && i + 1 < argc) + { + sourceName = argv[++i]; + } + else if ("-out"sv == argv[i] && i + 1 < argc) + { + outputPath = argv[++i]; + } + else if ("-meta"sv == argv[i] && i + 1 < argc) + { + metadataCollection = true; + wingetUtilPath = argv[++i]; + } + else if ("-sys32"sv == argv[i] && i + 1 < argc) + { + sys32Path = argv[++i]; + } + else if ("-dev"sv == argv[i]) + { + useDevCLSIDs = true; + } + else if ("-cor"sv == argv[i]) + { + onlyCorrelate = true; + } + } + + // Check inputs + if (outputPath.empty()) + { + std::cout << "No output file path specified, use -out" << std::endl; + return 2; + } + + if (!outputPath.has_stem()) + { + std::cout << "Output path is not a file" << std::endl; + return 3; + } + + std::filesystem::create_directories(outputPath.parent_path()); + + outputStream.open(outputPath); + + if (!outputStream) + { + std::cout << "Output file could not be created" << std::endl; + return 4; + } + + return 0; + } + + PackageManager CreatePackageManager() + { + if (useDevCLSIDs) + { + return winrt::create_instance(CLSID_PackageManager2, CLSCTX_ALL); + } + return winrt::create_instance(CLSID_PackageManager, CLSCTX_ALL); + } + + InstallOptions CreateInstallOptions() + { + if (useDevCLSIDs) + { + return winrt::create_instance(CLSID_InstallOptions2, CLSCTX_ALL); + } + return winrt::create_instance(CLSID_InstallOptions, CLSCTX_ALL); + } + + FindPackagesOptions CreateFindPackagesOptions() + { + if (useDevCLSIDs) + { + return winrt::create_instance(CLSID_FindPackagesOptions2, CLSCTX_ALL); + } + return winrt::create_instance(CLSID_FindPackagesOptions, CLSCTX_ALL); + } + + CreateCompositePackageCatalogOptions CreateCreateCompositePackageCatalogOptions() + { + if (useDevCLSIDs) + { + return winrt::create_instance(CLSID_CreateCompositePackageCatalogOptions2, CLSCTX_ALL); + } + return winrt::create_instance(CLSID_CreateCompositePackageCatalogOptions, CLSCTX_ALL); + } + + PackageMatchFilter CreatePackageMatchFilter() + { + if (useDevCLSIDs) + { + return winrt::create_instance(CLSID_PackageMatchFilter2, CLSCTX_ALL); + } + return winrt::create_instance(CLSID_PackageMatchFilter, CLSCTX_ALL); + } + + std::ofstream outputStream; + + // Result file outputs + HRESULT hr = S_OK; + std::string error; + std::string phase; + std::string action; + std::string packageName; + std::string packagePublisher; + bool correlatePackageKnown = false; + std::string packageKnownName; + std::string packageKnownPublisher; + bool correlateArchive = false; + std::string archiveName; + std::string archivePublisher; + + void ValidateArgs() + { + if (packageIdentifier.empty()) + { + hr = E_INVALIDARG; + error = "A package identifier must be supplied, use -id"; + return; + } + + if (sourceName.empty()) + { + hr = E_INVALIDARG; + error = "A source name must be supplied, use -src"; + return; + } + + if (metadataCollection && sys32Path.empty()) + { + hr = E_INVALIDARG; + error = "Metadata collection requires mapping in the host's System32"; + return; + } + } + + using WinGetBeginInstallerMetadataCollectionPtr = HRESULT (__stdcall *)( + WINGET_STRING inputJSON, + WINGET_STRING logFilePath, + WinGetBeginInstallerMetadataCollectionOptions options, + WINGET_INSTALLER_METADATA_COLLECTION_HANDLE* collectionHandle); + + using WinGetCompleteInstallerMetadataCollectionPtr = HRESULT(__stdcall*)( + WINGET_INSTALLER_METADATA_COLLECTION_HANDLE collectionHandle, + WINGET_STRING outputFilePath, + WinGetCompleteInstallerMetadataCollectionOptions options); + + WinGetBeginInstallerMetadataCollectionPtr WinGetBeginInstallerMetadataCollection = nullptr; + WinGetCompleteInstallerMetadataCollectionPtr WinGetCompleteInstallerMetadataCollection = nullptr; + WINGET_INSTALLER_METADATA_COLLECTION_HANDLE MetadataCollectionHandle = nullptr; + + void LoadWingetUtil() + { + AddDllDirectory(sys32Path.wstring().c_str()); + HMODULE wingetutilModule = LoadLibraryExW(wingetUtilPath.wstring().c_str(), nullptr, LOAD_LIBRARY_SEARCH_USER_DIRS); + if (!wingetutilModule) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return; + } + + this->WinGetBeginInstallerMetadataCollection = reinterpret_cast(GetProcAddress(wingetutilModule, "WinGetBeginInstallerMetadataCollection")); + if (!this->WinGetBeginInstallerMetadataCollection) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return; + } + + this->WinGetCompleteInstallerMetadataCollection = reinterpret_cast(GetProcAddress(wingetutilModule, "WinGetCompleteInstallerMetadataCollection")); + if (!this->WinGetCompleteInstallerMetadataCollection) + { + hr = HRESULT_FROM_WIN32(GetLastError()); + return; + } + } + + void BeginMetadataCollection() + { + std::filesystem::path metadataInputPath = outputPath.parent_path(); + metadataInputPath /= "metadata_input.json"; + std::ofstream stream{ metadataInputPath }; + + stream << "{" << std::endl; + stream << JSONPair{ "supportedMetadataVersion", "1.2"}; + // TODO: Could theoretically produce this if we could enumerate the data via COM + // stream << JSONPair{ "currentMetadata", "" }; + stream << JSONPair{ "submissionData", nullptr, false } << "\n{\n"; + stream << JSONPair{ "submissionIdentifier", packageIdentifier, false }; + stream << "\n},\n"; + stream << JSONPair{ "packageData", nullptr, false } << "\n{\n"; + stream << JSONPair{ "installerHash", "none" }; + stream << JSONPair{ "DefaultLocale", nullptr, false } << "\n{\n"; + stream << JSONPair{ "PackageLocale", "x-neutral" }; + stream << JSONPair{ "PackageName", packageName }; + stream << JSONPair{ "Publisher", packagePublisher, false }; + stream << "\n}\n"; + stream << "\n},\n"; + // Keep at the end to prevent a dangling comma + stream << JSONPair{ "version", "1.0", false } << "}" << std::endl; + + std::filesystem::path metadataLogPath = outputPath.parent_path(); + metadataLogPath /= "metadata_log.txt"; + + hr = this->WinGetBeginInstallerMetadataCollection(metadataInputPath.wstring().c_str(), metadataLogPath.wstring().c_str(), WinGetBeginInstallerMetadataCollectionOption_InputIsFilePath, &MetadataCollectionHandle); + if (FAILED(hr)) + { + return; + } + } + + void CompleteMetadataCollection() + { + std::filesystem::path metadataOutputPath = outputPath.parent_path(); + metadataOutputPath /= "metadata_output.json"; + + hr = this->WinGetCompleteInstallerMetadataCollection(MetadataCollectionHandle, metadataOutputPath.wstring().c_str(), WinGetCompleteInstallerMetadataCollectionOption_None); + if (FAILED(hr)) + { + return; + } + } + + void Install() + { + try + { + action = "Create package manager"; + auto packageManager = CreatePackageManager(); + + action = "Get source reference"; + auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName)); + + action = "Connecting to catalog"; + auto connectResult = sourceRef.Connect(); + + if (connectResult.Status() != ConnectResultStatus::Ok) + { + hr = E_FAIL; + error = "Error connecting to catalog"; + return; + } + + auto catalog = connectResult.PackageCatalog(); + + action = "Create find options"; + auto findOptions = CreateFindPackagesOptions(); + + action = "Add package id filter"; + auto filter = CreatePackageMatchFilter(); + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::Equals); + filter.Value(ConvertToUTF16(packageIdentifier)); + findOptions.Filters().Append(filter); + + action = "Find package"; + auto findResult = catalog.FindPackages(findOptions); + + if (findResult.Status() != FindPackagesResultStatus::Ok) + { + hr = E_FAIL; + std::ostringstream stream; + stream << "Error " << static_cast(findResult.Status()) << " finding package"; + error = std::move(stream).str(); + return; + } + + action = "Get match"; + auto matches = findResult.Matches(); + + if (matches.Size() == 0) + { + hr = E_NOT_SET; + error = "Package not found"; + return; + } + + auto package = matches.GetAt(0).CatalogPackage(); + + action = "Inspect package"; + auto installVersion = package.DefaultInstallVersion(); + packageName = ConvertToUTF8(installVersion.DisplayName()); + packagePublisher = ConvertToUTF8(installVersion.Publisher()); + + if (metadataCollection) + { + BeginMetadataCollection(); + if (FAILED(hr)) + { + return; + } + } + + if (!onlyCorrelate) + { + action = "Create install options"; + auto installOptions = CreateInstallOptions(); + + installOptions.PackageInstallScope(PackageInstallScope::Any); + installOptions.PackageInstallMode(PackageInstallMode::Silent); + + std::cout << "Beginning to install " << packageIdentifier << " (" << packageName << ") from " << sourceName << "..." << std::endl; + action = "Install package"; + auto installOperation = packageManager.InstallPackageAsync(package, installOptions); + + if (installOperation.wait_for(std::chrono::minutes(10)) != AsyncStatus::Completed) + { + hr = E_FAIL; + error = "Install operation timed out"; + return; + } + + auto installResult = installOperation.GetResults(); + if (installResult.Status() != InstallResultStatus::Ok) + { + hr = installResult.ExtendedErrorCode(); + error = "Error installing package"; + return; + } + } + + if (metadataCollection) + { + CompleteMetadataCollection(); + if (FAILED(hr)) + { + return; + } + } + } + catch (const winrt::hresult_error& hre) + { + hr = hre.code(); + error = ConvertToUTF8(hre.message()); + } + } + + void CorrelatePackageKnown() + { + try + { + action = "Create package manager"; + auto packageManager = CreatePackageManager(); + + action = "Get source reference"; + auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName)); + + action = "Create composite catalog options"; + auto compOptions = CreateCreateCompositePackageCatalogOptions(); + + compOptions.Catalogs().Append(sourceRef); + compOptions.CompositeSearchBehavior(CompositeSearchBehavior::RemotePackagesFromAllCatalogs); + + action = "Create composite catalog reference"; + auto compRef = packageManager.CreateCompositePackageCatalog(compOptions); + + action = "Connecting to catalog"; + auto connectResult = compRef.Connect(); + + if (connectResult.Status() != ConnectResultStatus::Ok) + { + hr = E_FAIL; + error = "Error connecting to catalog"; + return; + } + + auto catalog = connectResult.PackageCatalog(); + + action = "Create find options"; + auto findOptions = CreateFindPackagesOptions(); + + action = "Add package id filter"; + auto filter = CreatePackageMatchFilter(); + filter.Field(PackageMatchField::Id); + filter.Option(PackageFieldMatchOption::Equals); + filter.Value(ConvertToUTF16(packageIdentifier)); + findOptions.Filters().Append(filter); + + action = "Find package"; + auto findResult = catalog.FindPackages(findOptions); + + if (findResult.Status() != FindPackagesResultStatus::Ok) + { + hr = E_FAIL; + error = "Error finding packages"; + return; + } + + action = "Get match"; + auto matches = findResult.Matches(); + + if (matches.Size() == 0) + { + hr = E_NOT_SET; + error = "Package not found"; + return; + } + + auto package = matches.GetAt(0).CatalogPackage(); + + action = "Inspect package for installed version"; + auto installed = package.InstalledVersion(); + + if (installed) + { + correlatePackageKnown = true; + packageKnownName = ConvertToUTF8(installed.DisplayName()); + packageKnownPublisher = ConvertToUTF8(installed.Publisher()); + } + } + catch (const winrt::hresult_error& hre) + { + hr = hre.code(); + error = ConvertToUTF8(hre.message()); + } + } + + void CorrelateArchive() + { + try + { + action = "Create package manager"; + auto packageManager = CreatePackageManager(); + + action = "Get source reference"; + auto sourceRef = packageManager.GetPackageCatalogByName(ConvertToUTF16(sourceName)); + + action = "Create composite catalog options"; + auto compOptions = CreateCreateCompositePackageCatalogOptions(); + + compOptions.Catalogs().Append(sourceRef); + compOptions.CompositeSearchBehavior(CompositeSearchBehavior::LocalCatalogs); + + action = "Create composite catalog reference"; + auto compRef = packageManager.CreateCompositePackageCatalog(compOptions); + + action = "Connecting to catalog"; + auto connectResult = compRef.Connect(); + + if (connectResult.Status() != ConnectResultStatus::Ok) + { + hr = E_FAIL; + error = "Error connecting to catalog"; + return; + } + + auto catalog = connectResult.PackageCatalog(); + + action = "Create find options"; + auto findOptions = CreateFindPackagesOptions(); + + action = "Find package"; + auto findResult = catalog.FindPackages(findOptions); + + if (findResult.Status() != FindPackagesResultStatus::Ok) + { + hr = E_FAIL; + error = "Error finding packages"; + return; + } + + action = "Get matches"; + auto matches = findResult.Matches(); + + action = "Get source info"; + auto sourceInfo = sourceRef.Info(); + auto sourceIdentifier = sourceInfo.Id(); + auto sourceType = sourceInfo.Type(); + + for (const auto& match : matches) + { + auto package = match.CatalogPackage(); + + if (ConvertToUTF8(package.Id()) != packageIdentifier) + { + continue; + } + + auto installed = package.InstalledVersion(); + + if (installed) + { + auto installedCatalogInfo = installed.PackageCatalog().Info(); + + if (installedCatalogInfo.Id() == sourceIdentifier && installedCatalogInfo.Type() == sourceType) + { + correlateArchive = true; + archiveName = ConvertToUTF8(installed.DisplayName()); + archivePublisher = ConvertToUTF8(installed.Publisher()); + break; + } + } + } + } + catch (const winrt::hresult_error& hre) + { + hr = hre.code(); + error = ConvertToUTF8(hre.message()); + } + } + + void ReportResult() + { + if (outputStream) + { + outputStream << "{" << std::endl; + outputStream << JSONPair{ "PackageIdentifier", packageIdentifier }; + outputStream << JSONPair{ "Source", sourceName }; + outputStream << JSONPair{ "UseDev", useDevCLSIDs }; + outputStream << JSONPair{ "Error", error }; + outputStream << JSONPair{ "Phase", phase }; + outputStream << JSONPair{ "Action", action }; + outputStream << JSONPair{ "PackageName", packageName }; + outputStream << JSONPair{ "PackagePublisher", packagePublisher }; + outputStream << JSONPair{ "CorrelatePackageKnown", correlatePackageKnown }; + outputStream << JSONPair{ "PackageKnownName", packageKnownName }; + outputStream << JSONPair{ "PackageKnownPublisher", packageKnownPublisher }; + outputStream << JSONPair{ "CorrelateArchive", correlateArchive }; + outputStream << JSONPair{ "ArchiveName", archiveName }; + outputStream << JSONPair{ "ArchivePublisher", archivePublisher }; + // Keep at the end to prevent a dangling comma + outputStream << JSONPair{ "HRESULT", hr, false } << "}" << std::endl; + } + } + + void main(int argc, char** argv) + { + hr = ParseArgs(argc, argv); + if (hr != 0) + { + return; + } + + ValidateArgs(); + if (FAILED(hr)) + { + return; + } + + if (metadataCollection) + { + LoadWingetUtil(); + if (FAILED(hr)) + { + return; + } + } + + auto co_uninitialize = wil::CoInitializeEx(); + + // Execute the install step + phase = "Install"; + std::cout << "Connecting to PackageManager..." << std::endl; + Install(); + if (FAILED(hr)) + { + return; + } + + // Check for the installed package being correlated when the remote package is known, + // as when trying to determine information about a single known package. + phase = "Correlate when package known"; + std::cout << "Correlating package when known..." << std::endl; + CorrelatePackageKnown(); + + // Check for the installed package being correlated when archiving local package information. + phase = "Correlate when archiving"; + std::cout << "Correlating package when archiving..." << std::endl; + CorrelateArchive(); + + std::cout << "Done" << std::endl; + phase = "Completed"; + action.clear(); + } +}; + +int main(int argc, char** argv) try +{ + Main mainMain; + mainMain.main(argc, argv); + mainMain.ReportResult(); + return mainMain.hr; +} +catch (const std::exception& e) +{ + std::cout << "Exception occurred: " << e.what() << std::endl; + return 1; +} +catch (...) +{ + std::cout << "Unknown exception occurred" << std::endl; + return 1; +} diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj index 0faf6aaf05..bbb181ab02 100644 --- a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj +++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj @@ -1,168 +1,168 @@ - - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {204cd25f-aaea-4ca1-ab9f-a26747976932} - InstallAndCheckCorrelation - 10.0 - - - - Application - true - - - Application - false - true - - - - - - - - - - - - - - - - - - - - - true - - - false - - - true - - - false - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - MultiThreaded - $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) - - - Console - true - true - true - - - - - - - - true - Document - true - true - true - - - - - - - - Microsoft.Management.Deployment.winmd - true - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - + + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 16.0 + Win32Proj + {204cd25f-aaea-4ca1-ab9f-a26747976932} + InstallAndCheckCorrelation + 10.0 + + + + Application + true + + + Application + false + true + + + + + + + + + + + + + + + + + + + + + true + + + false + + + true + + + false + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) + + + Console + true + true + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp17 + MultiThreaded + $(SolutionDir)..\..\..\src\WinGetUtil;%(AdditionalIncludeDirectories) + + + Console + true + true + true + + + + + + + + true + Document + true + true + true + + + + + + + + Microsoft.Management.Deployment.winmd + true + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj.filters b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj.filters index 1adefe7af1..9e928f5eff 100644 --- a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj.filters +++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/InstallAndCheckCorrelation.vcxproj.filters @@ -1,28 +1,28 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + + + + \ No newline at end of file diff --git a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/packages.config b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/packages.config index 8d19b2fdd6..f37bdb9e7c 100644 --- a/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/packages.config +++ b/tools/CorrelationTestbed/InstallAndCheckCorrelation/InstallAndCheckCorrelation/packages.config @@ -1,5 +1,5 @@ - - - - + + + + \ No newline at end of file diff --git a/tools/CorrelationTestbed/Process-CorrelationResults.ps1 b/tools/CorrelationTestbed/Process-CorrelationResults.ps1 index 7a2c51c0ba..d05b9bbe58 100644 --- a/tools/CorrelationTestbed/Process-CorrelationResults.ps1 +++ b/tools/CorrelationTestbed/Process-CorrelationResults.ps1 @@ -1,94 +1,94 @@ -Param( - [Parameter(Position = 0, HelpMessage = "The root location of the results.")] - [String] $ResultsPath -) - -$resultFile = Join-Path $ResultsPath "results.csv" -$failedFile = Join-Path $ResultsPath "failed.csv" -$statsFile = Join-Path $ResultsPath "stats.json" - -if (Test-Path $resultFile) -{ - Remove-Item $resultFile -Force -} - -if (Test-Path $failedFile) -{ - Remove-Item $failedFile -Force -} - -$stats = @{ - Total = 0 - Missing = 0 - Completed = 0 - Failed = 0 - CorrelatePackageKnown = 0 - CorrelateArchive = 0 - CorrelateMetadata = 0 - CorrelationDisagreement = 0 - CorrelateArchiveRatio = 0 - CorrelatePackageKnownRatio = 0 - CorrelateMetadataRatio = 0 - CorrelationDisagreementRatio = 0 -} - -# Aggregate results in a single CSV file -foreach ($result in (Get-ChildItem $ResultsPath -Directory)) -{ - $stats.Total++ - - $resultJSON = Join-Path $result.FullName "install_and_correlate.json" - if (Test-Path $resultJSON) - { - $resultObj = (Get-Content -Path $resultJSON -Encoding utf8 | ConvertFrom-Json) - } - - if (-not $resultObj) - { - # Result JSON file does not exist or is empty - $stats.Missing++ - continue - } - - $metadataJSON = Join-Path $result.FullName "metadata_output.json" - if (Test-Path $metadataJSON) - { - $metadataObj = (Get-Content -Path $metadataJSON -Encoding utf8 | ConvertFrom-Json) - } - - if ($resultObj.HRESULT -eq 0) - { - $stats.Completed++ - $stats.CorrelateArchive += $resultObj.CorrelateArchive - $stats.CorrelatePackageKnown += $resultObj.CorrelatePackageKnown - if ($metadataObj -and $metadataObj.status -eq "Success") - { - $stats.CorrelateMetadata += 1 - Add-Member -InputObject $resultObj -MemberType NoteProperty -Name "CorrelateMetadata" -Value 1 -Force - Add-Member -InputObject $resultObj -MemberType NoteProperty -Name "MetadataName" -Value $metadataObj.metadata[0].metadata[0].AppsAndFeaturesEntries[0].DisplayName -Force - Add-Member -InputObject $resultObj -MemberType NoteProperty -Name "MetadataPublisher" -Value $metadataObj.metadata[0].metadata[0].AppsAndFeaturesEntries[0].Publisher -Force - - if ($resultObj.PackageKnownName -ne "" -and $resultObj.MetadataName -ne "" -and $resultObj.PackageKnownName -ne $resultObj.MetadataName) - { - $stats.CorrelationDisagreement += 1 - } - } - Export-Csv -InputObject ($resultObj | Select-Object -Property * -ExcludeProperty @("Error", "Phase", "Action", "HRESULT") ) -Path $resultFile -Append -Encoding utf8 - } - else - { - $stats.Failed++ - Export-Csv -InputObject $resultObj -Path $failedFile -Append -Encoding utf8 - } -} - -# Write some stats to a file for quick evaluation -$stats.CompletedRatio = $stats.Completed / $stats.Total -if ($stats.Completed -ne 0) -{ - $stats.CorrelateArchiveRatio = $stats.CorrelateArchive / $stats.Completed - $stats.CorrelatePackageKnownRatio = $stats.CorrelatePackageKnown / $stats.Completed - $stats.CorrelateMetadataRatio = $stats.CorrelateMetadata / $stats.Completed - $stats.CorrelationDisagreementRatio = $stats.CorrelationDisagreement / $stats.Completed -} -$stats | ConvertTo-Json | Out-File $statsFile -Force +Param( + [Parameter(Position = 0, HelpMessage = "The root location of the results.")] + [String] $ResultsPath +) + +$resultFile = Join-Path $ResultsPath "results.csv" +$failedFile = Join-Path $ResultsPath "failed.csv" +$statsFile = Join-Path $ResultsPath "stats.json" + +if (Test-Path $resultFile) +{ + Remove-Item $resultFile -Force +} + +if (Test-Path $failedFile) +{ + Remove-Item $failedFile -Force +} + +$stats = @{ + Total = 0 + Missing = 0 + Completed = 0 + Failed = 0 + CorrelatePackageKnown = 0 + CorrelateArchive = 0 + CorrelateMetadata = 0 + CorrelationDisagreement = 0 + CorrelateArchiveRatio = 0 + CorrelatePackageKnownRatio = 0 + CorrelateMetadataRatio = 0 + CorrelationDisagreementRatio = 0 +} + +# Aggregate results in a single CSV file +foreach ($result in (Get-ChildItem $ResultsPath -Directory)) +{ + $stats.Total++ + + $resultJSON = Join-Path $result.FullName "install_and_correlate.json" + if (Test-Path $resultJSON) + { + $resultObj = (Get-Content -Path $resultJSON -Encoding utf8 | ConvertFrom-Json) + } + + if (-not $resultObj) + { + # Result JSON file does not exist or is empty + $stats.Missing++ + continue + } + + $metadataJSON = Join-Path $result.FullName "metadata_output.json" + if (Test-Path $metadataJSON) + { + $metadataObj = (Get-Content -Path $metadataJSON -Encoding utf8 | ConvertFrom-Json) + } + + if ($resultObj.HRESULT -eq 0) + { + $stats.Completed++ + $stats.CorrelateArchive += $resultObj.CorrelateArchive + $stats.CorrelatePackageKnown += $resultObj.CorrelatePackageKnown + if ($metadataObj -and $metadataObj.status -eq "Success") + { + $stats.CorrelateMetadata += 1 + Add-Member -InputObject $resultObj -MemberType NoteProperty -Name "CorrelateMetadata" -Value 1 -Force + Add-Member -InputObject $resultObj -MemberType NoteProperty -Name "MetadataName" -Value $metadataObj.metadata[0].metadata[0].AppsAndFeaturesEntries[0].DisplayName -Force + Add-Member -InputObject $resultObj -MemberType NoteProperty -Name "MetadataPublisher" -Value $metadataObj.metadata[0].metadata[0].AppsAndFeaturesEntries[0].Publisher -Force + + if ($resultObj.PackageKnownName -ne "" -and $resultObj.MetadataName -ne "" -and $resultObj.PackageKnownName -ne $resultObj.MetadataName) + { + $stats.CorrelationDisagreement += 1 + } + } + Export-Csv -InputObject ($resultObj | Select-Object -Property * -ExcludeProperty @("Error", "Phase", "Action", "HRESULT") ) -Path $resultFile -Append -Encoding utf8 + } + else + { + $stats.Failed++ + Export-Csv -InputObject $resultObj -Path $failedFile -Append -Encoding utf8 + } +} + +# Write some stats to a file for quick evaluation +$stats.CompletedRatio = $stats.Completed / $stats.Total +if ($stats.Completed -ne 0) +{ + $stats.CorrelateArchiveRatio = $stats.CorrelateArchive / $stats.Completed + $stats.CorrelatePackageKnownRatio = $stats.CorrelatePackageKnown / $stats.Completed + $stats.CorrelateMetadataRatio = $stats.CorrelateMetadata / $stats.Completed + $stats.CorrelationDisagreementRatio = $stats.CorrelationDisagreement / $stats.Completed +} +$stats | ConvertTo-Json | Out-File $statsFile -Force diff --git a/tools/CorrelationTestbed/Readme.md b/tools/CorrelationTestbed/Readme.md index 6da294b584..61cb4de4dc 100644 --- a/tools/CorrelationTestbed/Readme.md +++ b/tools/CorrelationTestbed/Readme.md @@ -1,44 +1,44 @@ -# E2E correlation testing -This directory holds a few scripts and a test project, all centered around enabling end-to-end validation of our correlation between system artifacts and packages in external sources. - -The test project uses the COM API to first install a package, then attempts to check for correlation in two ways (directions): -1. When the remote package identity is known, the caller will usually look it up via that remote identity. This path can enable additional information to be retrieved to make the correlation with. -2. When listing all of the local packages, the remote identity must be determined for each one. We can use only data known locally to make the correlation. - -The primary script, `Test-CorrelationInSandbox.ps1`, is based off of the sandbox test script in winget-pkgs (thanks to many people). It sets up the sandbox and initial script to run there for each package to test, then waits for a sentinel file to be created in the output location. The results of all of running the test project exe, as well as the ARP differences and winget logs are put in that output location, then the sandbox is destroyed for the next package to run. - -``` -Test-CorrelationInSandbox.ps1 --- Required -- -[[-PackageIdentifiers] ] :: A set of package ids to test -[[-Source] ] :: The name of the source that the packages are from, ex. "winget" - --- Optional -- -[-ExePath ] :: Path to the test exe; defaults to the Release output location -[-UseDev] :: Switch to use the local dev winget build -[-DevPackagePath ] :: Path to the local dev *Release* winget build; defaults to the normal location -[-ResultsPath ] :: Path to output the results to; defaults to a temp directory -[-RegFileDirectory ] :: Path to a directory containing .reg files to insert before the test, creating noise for correlation -[-MetadataCollection] :: Switch to enable collection of metadata -[-WingetUtilPath ] :: Path to the WinGetUtil.dll to use; defaults to Debug build location -``` - -Once testing is done, `Process-CorrelationResults.ps1` will take all of the results JSON files and put them into `results.csv` in the directory. If any tests failed to run, they will be in `failed.csv`. There are correlation columns in the CSV that can be averaged in Excel to get the correlation percentage. - -## Running a test -### Setup -First you must have built the test exe located in `InstallAndCheckCorrelation`. By default the Release x64 version is picked up by the script, so it is easiest to build that one. - -If you want to run against the local dev build of the winget COM server, the default is again to use Release x64. The sandbox will not run the debug build for unknown reasons currently. - -### Run the test pass -Run the `Test-CorrelationInSandbox.ps1` script, then wait for a while since it is going to download and install every package serially, with a little bit of overhead in between. - -A simple example call is: -``` -Test-CorrelationInSandbox.ps1 -PackageIdentifiers @("Microsoft.VisualStudioCode") -Source winget -``` -This will use the latest version of winget available on github. Adding `-UseDev` should be enough to use the local build instead if Release x64 is already deployed onto the host machine. - -### Collate the results +# E2E correlation testing +This directory holds a few scripts and a test project, all centered around enabling end-to-end validation of our correlation between system artifacts and packages in external sources. + +The test project uses the COM API to first install a package, then attempts to check for correlation in two ways (directions): +1. When the remote package identity is known, the caller will usually look it up via that remote identity. This path can enable additional information to be retrieved to make the correlation with. +2. When listing all of the local packages, the remote identity must be determined for each one. We can use only data known locally to make the correlation. + +The primary script, `Test-CorrelationInSandbox.ps1`, is based off of the sandbox test script in winget-pkgs (thanks to many people). It sets up the sandbox and initial script to run there for each package to test, then waits for a sentinel file to be created in the output location. The results of all of running the test project exe, as well as the ARP differences and winget logs are put in that output location, then the sandbox is destroyed for the next package to run. + +``` +Test-CorrelationInSandbox.ps1 +-- Required -- +[[-PackageIdentifiers] ] :: A set of package ids to test +[[-Source] ] :: The name of the source that the packages are from, ex. "winget" + +-- Optional -- +[-ExePath ] :: Path to the test exe; defaults to the Release output location +[-UseDev] :: Switch to use the local dev winget build +[-DevPackagePath ] :: Path to the local dev *Release* winget build; defaults to the normal location +[-ResultsPath ] :: Path to output the results to; defaults to a temp directory +[-RegFileDirectory ] :: Path to a directory containing .reg files to insert before the test, creating noise for correlation +[-MetadataCollection] :: Switch to enable collection of metadata +[-WingetUtilPath ] :: Path to the WinGetUtil.dll to use; defaults to Debug build location +``` + +Once testing is done, `Process-CorrelationResults.ps1` will take all of the results JSON files and put them into `results.csv` in the directory. If any tests failed to run, they will be in `failed.csv`. There are correlation columns in the CSV that can be averaged in Excel to get the correlation percentage. + +## Running a test +### Setup +First you must have built the test exe located in `InstallAndCheckCorrelation`. By default the Release x64 version is picked up by the script, so it is easiest to build that one. + +If you want to run against the local dev build of the winget COM server, the default is again to use Release x64. The sandbox will not run the debug build for unknown reasons currently. + +### Run the test pass +Run the `Test-CorrelationInSandbox.ps1` script, then wait for a while since it is going to download and install every package serially, with a little bit of overhead in between. + +A simple example call is: +``` +Test-CorrelationInSandbox.ps1 -PackageIdentifiers @("Microsoft.VisualStudioCode") -Source winget +``` +This will use the latest version of winget available on github. Adding `-UseDev` should be enough to use the local build instead if Release x64 is already deployed onto the host machine. + +### Collate the results Running `Process-CorrelationResults.ps1` on the directory output at the end of `Test-CorrelationInSandbox.ps1` will place a CSV file with the results combined together. These results can be inspected for correctness and an overall correlation score determined from the different correlation paths. \ No newline at end of file diff --git a/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 b/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 index 37722a04a2..de46719c71 100644 --- a/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 +++ b/tools/CorrelationTestbed/Test-CorrelationInSandbox.ps1 @@ -1,384 +1,384 @@ -# Started by copying from: -# https://github.com/microsoft/winget-pkgs/blob/c393e50b66448cc25a5cd27aa754d37677f42ce2/Tools/SandboxTest.ps1 - -Param( - [Parameter(Position = 0, HelpMessage = "The package identifiers to test.")] - [String[]] $PackageIdentifiers, - [Parameter(Position = 1, HelpMessage = "The source name that the package identifiers are from.")] - [String] $Source, - [Parameter(HelpMessage = "The directory where the correlation program is located.")] - [String] $ExePath, - [Parameter(HelpMessage = "Indicates that the local dev build should be used rather than the published package.")] - [Switch] $UseDev, - [Parameter(HelpMessage = "The directory where local dev build is located; only the release build works.")] - [String] $DevPackagePath, - [Parameter(HelpMessage = "The results output path.")] - [String] $ResultsPath, - [Parameter(HelpMessage = "The path to registry files that should be injected before the test.")] - [String] $RegFileDirectory, - [Parameter(HelpMessage = "Indicates that the metadata collection process should be run.")] - [Switch] $MetadataCollection, - [Parameter(HelpMessage = "The path to WinGetUtil.dll.")] - [String] $WingetUtilPath, - [Parameter(HelpMessage = "Wait for user input before tearing down each sandbox.")] - [Switch] $Wait -) - -$ErrorActionPreference = "Stop" - -# Validate that the ExePath points to a reasonable location - -if (-not $ExePath) -{ - $ExePath = Join-Path $PSScriptRoot "InstallAndCheckCorrelation\x64\Release" -} - -$ExePath = [System.IO.Path]::GetFullPath($ExePath) - -if (-not (Test-Path (Join-Path $ExePath "InstallAndCheckCorrelation.exe"))) -{ - Write-Error -Category InvalidArgument -Message @" -InstallAndCheckCorrelation.exe does not exist in the path $ExePath -Either build it, or provide the location using -ExePath -"@ -} - -# Validate that the local dev manifest exists - -if ($UseDev) -{ - if (-not $DevPackagePath) - { - $DevPackagePath = Join-Path $PSScriptRoot "..\..\src\AppInstallerCLIPackage\bin\x64\Release\AppX" - } - - $DevPackagePath = [System.IO.Path]::GetFullPath($DevPackagePath) - - if ($DevPackagePath.ToLower().Contains("debug")) - { - Write-Error -Category InvalidArgument -Message @" -The Debug dev package does not work for unknown reasons. -Use the Release build or figure out how to make debug work and fix the scripts. -"@ - } - - if (-not (Test-Path (Join-Path $DevPackagePath "AppxManifest.xml"))) - { - Write-Error -Category InvalidArgument -Message @" -AppxManifest.xml does not exist in the path $DevPackagePath -Either build the local dev package, or provide the location using -DevPackagePath -"@ - } -} - -# Validate that WinGetUtil.dll exists if metadata collection is requested - -if ($MetadataCollection) -{ - if (-not $WingetUtilPath) - { - $WingetUtilPath = Join-Path $PSScriptRoot "..\..\src\x64\Debug\WinGetUtil\WinGetUtil.dll" - } - - $WingetUtilPath = [System.IO.Path]::GetFullPath($WingetUtilPath) - - if (-not (Test-Path $WingetUtilPath)) - { - Write-Error -Category InvalidArgument -Message @" -WinGetUtil.dll does not exist in the path $WingetUtilPath -Either build the binary, or provide the location using -WingetUtilPath -"@ - } -} - -# Check if Windows Sandbox is enabled - -if (-Not (Get-Command 'WindowsSandbox' -ErrorAction SilentlyContinue)) -{ - Write-Error -Category NotInstalled -Message @' -Windows Sandbox does not seem to be available. Check the following URL for prerequisites and further details: -https://docs.microsoft.com/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview - -You can run the following command in an elevated PowerShell for enabling Windows Sandbox: -$ Enable-WindowsOptionalFeature -Online -FeatureName 'Containers-DisposableClientVM' -'@ -} - -# Close Windows Sandbox - -function Close-WindowsSandbox { - $sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue - if ($sandbox) - { - Write-Host '--> Closing Windows Sandbox' - - $sandboxServer = Get-Process 'WindowsSandbox' -ErrorAction SilentlyContinue - - $sandbox | Stop-Process - $sandbox | Wait-Process -Timeout 120 - - # Also wait for the server to close - if ($sandboxServer) - { - try - { - $sandboxServer | Wait-Process -Timeout 120 - } - catch [System.TimeoutException] - { - # Force stop the server if it does not automatically stop - $sandboxServer | Stop-Process - $sandboxServer | Wait-Process -Timeout 120 - } - - } - - Write-Host - } - Remove-Variable sandbox -} - -Close-WindowsSandbox - -# Create output location for results - -if (-not $ResultsPath) -{ - $ResultsPath = Join-Path ([System.IO.Path]::GetTempPath()) (New-Guid) -} - -$ResultsPath = [System.IO.Path]::GetFullPath($ResultsPath) - -if (Test-Path $ResultsPath) -{ - Remove-Item -Recurse $ResultsPath -Force -} - -New-Item -ItemType Directory $ResultsPath | Out-Null - -# Initialize Temp Folder - -$tempFolderName = 'CorrelationTestStaging' -$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $tempFolderName - -New-Item $tempFolder -ItemType Directory -ErrorAction SilentlyContinue | Out-Null - -# Set dependencies - -$desktopInSandbox = 'C:\Users\WDAGUtilityAccount\Desktop' - -$apiLatestUrl = 'https://api.github.com/repos/microsoft/winget-cli/releases/latest' - -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$WebClient = New-Object System.Net.WebClient - -$vcLibsUwp = @{ - fileName = 'Microsoft.VCLibs.x64.14.00.Desktop.appx' - url = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx' - hash = '9BFDE6CFCC530EF073AB4BC9C4817575F63BE1251DD75AAA58CB89299697A569' - folderInLocal = Join-Path ${env:ProgramFiles(x86)} "Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Retail\x64" -} - -if ($UseDev) -{ - $dependencies = @($vcLibsUwp) -} - -# Clean temp directory - -Get-ChildItem $tempFolder -Recurse -Exclude $dependencies.fileName | Remove-Item -Force -Recurse - -# Download dependencies - -Write-Host '--> Checking dependencies' - -foreach ($dependency in $dependencies) -{ - $dependency.pathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $dependency.fileName) - - # First see if the file exists locally to copy instead of downloading - if ($dependency.folderInLocal -ne $null) - { - $dependencyFilePath = Join-Path -Path $dependency.folderInLocal -ChildPath $dependency.fileName - if (Test-Path -Path $dependencyFilePath -PathType Leaf) - { - $dependencyFilePath - $tempFolder - Copy-Item -Path $dependencyFilePath -Destination $tempFolder -Force - continue - } - } - - # File does not exist locally, we need to download - - $dependency.file = Join-Path -Path $tempFolder -ChildPath $dependency.fileName - - # Only download if the file does not exist, or its hash does not match. - if (-Not ((Test-Path -Path $dependency.file -PathType Leaf) -And $dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) - { - Write-Host @" - - Downloading: - $($dependency.url) -"@ - - try - { - $WebClient.DownloadFile($dependency.url, $dependency.file) - } - catch - { - #Pass the exception as an inner exception - throw [System.Net.WebException]::new("Error downloading $($dependency.url).",$_.Exception) - } - if (-not ($dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) - { - throw [System.Activities.VersionMismatchException]::new('Dependency hash does not match the downloaded file') - } - } -} - -if ($MetadataCollection) -{ - Copy-Item -Path $WingetUtilPath -Destination $tempFolder -Force -} - -$HostGeoID = (Get-WinHomeLocation).GeoId - -# Copy main script - -$mainPs1FileName = 'InSandboxScript.ps1' -Copy-Item (Join-Path $PSScriptRoot $mainPs1FileName) (Join-Path $tempFolder $mainPs1FileName) - -foreach ($packageIdentifier in $PackageIdentifiers) -{ - - # Create temporary location for output - $outPath = Join-Path $ResultsPath $packageIdentifier - New-Item -ItemType Directory $outPath | Out-Null - - $outPathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Split-Path -Path $outPath -Leaf) - $system32PathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "hostSystem32" - - if ($UseDev) - { - $dependenciesPathsInSandbox = "@('$($vcLibsUwp.pathInSandbox)')" - } - - $bootstrapPs1Content = ".\$mainPs1FileName -DesktopAppInstallerDependencyPath @($dependenciesPathsInSandbox) -PackageIdentifier '$packageIdentifier' -SourceName '$Source' -OutputPath '$outPathInSandbox' -System32Path '$system32PathInSandbox' -GeoID $HostGeoID" - - if ($UseDev) - { - $bootstrapPs1Content += " -UseDev" - } - - if ($MetadataCollection) - { - $bootstrapPs1Content += " -MetadataCollection" - } - - $bootstrapPs1FileName = 'Bootstrap.ps1' - $bootstrapPs1Content | Out-File (Join-Path $tempFolder $bootstrapPs1FileName) -Force - - # Create Windows Sandbox configuration file - - $bootstrapPs1InSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $bootstrapPs1FileName) - $tempFolderInSandbox = Join-Path -Path $desktopInSandbox -ChildPath $tempFolderName - $exePathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "InstallAndCheckCorrelation" - - $devPackageInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "DevPackage" - $devPackageXMLFragment = "" - - if ($UseDev) - { - $devPackageXMLFragment = @" - - $DevPackagePath - $devPackageInSandbox - -"@ - } - - $regFileDirInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "RegFiles" - $regFileDirXMLFragment = "" - - if ($RegFileDirectory) - { - $regFileDirXMLFragment = @" - - $RegFileDirectory - $regFileDirInSandbox - true - -"@ - } - - $sandboxTestWsbContent = @" - - - - $tempFolder - true - - - $ExePath - $exePathInSandbox - true - - - C:\Windows\System32 - $system32PathInSandbox - true - - $devPackageXMLFragment - $regFileDirXMLFragment - - $outPath - - - - PowerShell Start-Process PowerShell -WindowStyle Maximized -WorkingDirectory '$tempFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -NoLogo -File $bootstrapPs1InSandbox' - - -"@ - - $sandboxTestWsbFileName = 'SandboxTest.wsb' - $sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName - $sandboxTestWsbContent | Out-File $sandboxTestWsbFile -Force - - Write-Host @" ---> Starting Windows Sandbox: - - Package: $packageIdentifier - - Output directory: $outPath -"@ - - Write-Host - - WindowsSandbox $SandboxTestWsbFile - - $outputFileBlockerPath = Join-Path $outPath "done.txt" - - # The correlation program should time out on its own after 10m. - $waitTimeout = [System.TimeSpan]::new(0, 15, 0) - $startWaitTime = Get-Date - - while (-not (Test-Path $outputFileBlockerPath)) - { - $elapsedTime = (Get-Date) - $startWaitTime - if ($elapsedTime -gt $waitTimeout) - { - break - } - Start-Sleep 1 - } - - if ($Wait) - { - Read-Host "Press Enter to close sandbox and continue..." - } - - Close-WindowsSandbox -} - -Write-Host @" ---> Results are located at $ResultsPath -"@ +# Started by copying from: +# https://github.com/microsoft/winget-pkgs/blob/c393e50b66448cc25a5cd27aa754d37677f42ce2/Tools/SandboxTest.ps1 + +Param( + [Parameter(Position = 0, HelpMessage = "The package identifiers to test.")] + [String[]] $PackageIdentifiers, + [Parameter(Position = 1, HelpMessage = "The source name that the package identifiers are from.")] + [String] $Source, + [Parameter(HelpMessage = "The directory where the correlation program is located.")] + [String] $ExePath, + [Parameter(HelpMessage = "Indicates that the local dev build should be used rather than the published package.")] + [Switch] $UseDev, + [Parameter(HelpMessage = "The directory where local dev build is located; only the release build works.")] + [String] $DevPackagePath, + [Parameter(HelpMessage = "The results output path.")] + [String] $ResultsPath, + [Parameter(HelpMessage = "The path to registry files that should be injected before the test.")] + [String] $RegFileDirectory, + [Parameter(HelpMessage = "Indicates that the metadata collection process should be run.")] + [Switch] $MetadataCollection, + [Parameter(HelpMessage = "The path to WinGetUtil.dll.")] + [String] $WingetUtilPath, + [Parameter(HelpMessage = "Wait for user input before tearing down each sandbox.")] + [Switch] $Wait +) + +$ErrorActionPreference = "Stop" + +# Validate that the ExePath points to a reasonable location + +if (-not $ExePath) +{ + $ExePath = Join-Path $PSScriptRoot "InstallAndCheckCorrelation\x64\Release" +} + +$ExePath = [System.IO.Path]::GetFullPath($ExePath) + +if (-not (Test-Path (Join-Path $ExePath "InstallAndCheckCorrelation.exe"))) +{ + Write-Error -Category InvalidArgument -Message @" +InstallAndCheckCorrelation.exe does not exist in the path $ExePath +Either build it, or provide the location using -ExePath +"@ +} + +# Validate that the local dev manifest exists + +if ($UseDev) +{ + if (-not $DevPackagePath) + { + $DevPackagePath = Join-Path $PSScriptRoot "..\..\src\AppInstallerCLIPackage\bin\x64\Release\AppX" + } + + $DevPackagePath = [System.IO.Path]::GetFullPath($DevPackagePath) + + if ($DevPackagePath.ToLower().Contains("debug")) + { + Write-Error -Category InvalidArgument -Message @" +The Debug dev package does not work for unknown reasons. +Use the Release build or figure out how to make debug work and fix the scripts. +"@ + } + + if (-not (Test-Path (Join-Path $DevPackagePath "AppxManifest.xml"))) + { + Write-Error -Category InvalidArgument -Message @" +AppxManifest.xml does not exist in the path $DevPackagePath +Either build the local dev package, or provide the location using -DevPackagePath +"@ + } +} + +# Validate that WinGetUtil.dll exists if metadata collection is requested + +if ($MetadataCollection) +{ + if (-not $WingetUtilPath) + { + $WingetUtilPath = Join-Path $PSScriptRoot "..\..\src\x64\Debug\WinGetUtil\WinGetUtil.dll" + } + + $WingetUtilPath = [System.IO.Path]::GetFullPath($WingetUtilPath) + + if (-not (Test-Path $WingetUtilPath)) + { + Write-Error -Category InvalidArgument -Message @" +WinGetUtil.dll does not exist in the path $WingetUtilPath +Either build the binary, or provide the location using -WingetUtilPath +"@ + } +} + +# Check if Windows Sandbox is enabled + +if (-Not (Get-Command 'WindowsSandbox' -ErrorAction SilentlyContinue)) +{ + Write-Error -Category NotInstalled -Message @' +Windows Sandbox does not seem to be available. Check the following URL for prerequisites and further details: +https://docs.microsoft.com/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview + +You can run the following command in an elevated PowerShell for enabling Windows Sandbox: +$ Enable-WindowsOptionalFeature -Online -FeatureName 'Containers-DisposableClientVM' +'@ +} + +# Close Windows Sandbox + +function Close-WindowsSandbox { + $sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue + if ($sandbox) + { + Write-Host '--> Closing Windows Sandbox' + + $sandboxServer = Get-Process 'WindowsSandbox' -ErrorAction SilentlyContinue + + $sandbox | Stop-Process + $sandbox | Wait-Process -Timeout 120 + + # Also wait for the server to close + if ($sandboxServer) + { + try + { + $sandboxServer | Wait-Process -Timeout 120 + } + catch [System.TimeoutException] + { + # Force stop the server if it does not automatically stop + $sandboxServer | Stop-Process + $sandboxServer | Wait-Process -Timeout 120 + } + + } + + Write-Host + } + Remove-Variable sandbox +} + +Close-WindowsSandbox + +# Create output location for results + +if (-not $ResultsPath) +{ + $ResultsPath = Join-Path ([System.IO.Path]::GetTempPath()) (New-Guid) +} + +$ResultsPath = [System.IO.Path]::GetFullPath($ResultsPath) + +if (Test-Path $ResultsPath) +{ + Remove-Item -Recurse $ResultsPath -Force +} + +New-Item -ItemType Directory $ResultsPath | Out-Null + +# Initialize Temp Folder + +$tempFolderName = 'CorrelationTestStaging' +$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $tempFolderName + +New-Item $tempFolder -ItemType Directory -ErrorAction SilentlyContinue | Out-Null + +# Set dependencies + +$desktopInSandbox = 'C:\Users\WDAGUtilityAccount\Desktop' + +$apiLatestUrl = 'https://api.github.com/repos/microsoft/winget-cli/releases/latest' + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$WebClient = New-Object System.Net.WebClient + +$vcLibsUwp = @{ + fileName = 'Microsoft.VCLibs.x64.14.00.Desktop.appx' + url = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx' + hash = '9BFDE6CFCC530EF073AB4BC9C4817575F63BE1251DD75AAA58CB89299697A569' + folderInLocal = Join-Path ${env:ProgramFiles(x86)} "Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Retail\x64" +} + +if ($UseDev) +{ + $dependencies = @($vcLibsUwp) +} + +# Clean temp directory + +Get-ChildItem $tempFolder -Recurse -Exclude $dependencies.fileName | Remove-Item -Force -Recurse + +# Download dependencies + +Write-Host '--> Checking dependencies' + +foreach ($dependency in $dependencies) +{ + $dependency.pathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $dependency.fileName) + + # First see if the file exists locally to copy instead of downloading + if ($dependency.folderInLocal -ne $null) + { + $dependencyFilePath = Join-Path -Path $dependency.folderInLocal -ChildPath $dependency.fileName + if (Test-Path -Path $dependencyFilePath -PathType Leaf) + { + $dependencyFilePath + $tempFolder + Copy-Item -Path $dependencyFilePath -Destination $tempFolder -Force + continue + } + } + + # File does not exist locally, we need to download + + $dependency.file = Join-Path -Path $tempFolder -ChildPath $dependency.fileName + + # Only download if the file does not exist, or its hash does not match. + if (-Not ((Test-Path -Path $dependency.file -PathType Leaf) -And $dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) + { + Write-Host @" + - Downloading: + $($dependency.url) +"@ + + try + { + $WebClient.DownloadFile($dependency.url, $dependency.file) + } + catch + { + #Pass the exception as an inner exception + throw [System.Net.WebException]::new("Error downloading $($dependency.url).",$_.Exception) + } + if (-not ($dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) + { + throw [System.Activities.VersionMismatchException]::new('Dependency hash does not match the downloaded file') + } + } +} + +if ($MetadataCollection) +{ + Copy-Item -Path $WingetUtilPath -Destination $tempFolder -Force +} + +$HostGeoID = (Get-WinHomeLocation).GeoId + +# Copy main script + +$mainPs1FileName = 'InSandboxScript.ps1' +Copy-Item (Join-Path $PSScriptRoot $mainPs1FileName) (Join-Path $tempFolder $mainPs1FileName) + +foreach ($packageIdentifier in $PackageIdentifiers) +{ + + # Create temporary location for output + $outPath = Join-Path $ResultsPath $packageIdentifier + New-Item -ItemType Directory $outPath | Out-Null + + $outPathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Split-Path -Path $outPath -Leaf) + $system32PathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "hostSystem32" + + if ($UseDev) + { + $dependenciesPathsInSandbox = "@('$($vcLibsUwp.pathInSandbox)')" + } + + $bootstrapPs1Content = ".\$mainPs1FileName -DesktopAppInstallerDependencyPath @($dependenciesPathsInSandbox) -PackageIdentifier '$packageIdentifier' -SourceName '$Source' -OutputPath '$outPathInSandbox' -System32Path '$system32PathInSandbox' -GeoID $HostGeoID" + + if ($UseDev) + { + $bootstrapPs1Content += " -UseDev" + } + + if ($MetadataCollection) + { + $bootstrapPs1Content += " -MetadataCollection" + } + + $bootstrapPs1FileName = 'Bootstrap.ps1' + $bootstrapPs1Content | Out-File (Join-Path $tempFolder $bootstrapPs1FileName) -Force + + # Create Windows Sandbox configuration file + + $bootstrapPs1InSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $bootstrapPs1FileName) + $tempFolderInSandbox = Join-Path -Path $desktopInSandbox -ChildPath $tempFolderName + $exePathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "InstallAndCheckCorrelation" + + $devPackageInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "DevPackage" + $devPackageXMLFragment = "" + + if ($UseDev) + { + $devPackageXMLFragment = @" + + $DevPackagePath + $devPackageInSandbox + +"@ + } + + $regFileDirInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "RegFiles" + $regFileDirXMLFragment = "" + + if ($RegFileDirectory) + { + $regFileDirXMLFragment = @" + + $RegFileDirectory + $regFileDirInSandbox + true + +"@ + } + + $sandboxTestWsbContent = @" + + + + $tempFolder + true + + + $ExePath + $exePathInSandbox + true + + + C:\Windows\System32 + $system32PathInSandbox + true + + $devPackageXMLFragment + $regFileDirXMLFragment + + $outPath + + + + PowerShell Start-Process PowerShell -WindowStyle Maximized -WorkingDirectory '$tempFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -NoLogo -File $bootstrapPs1InSandbox' + + +"@ + + $sandboxTestWsbFileName = 'SandboxTest.wsb' + $sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName + $sandboxTestWsbContent | Out-File $sandboxTestWsbFile -Force + + Write-Host @" +--> Starting Windows Sandbox: + - Package: $packageIdentifier + - Output directory: $outPath +"@ + + Write-Host + + WindowsSandbox $SandboxTestWsbFile + + $outputFileBlockerPath = Join-Path $outPath "done.txt" + + # The correlation program should time out on its own after 10m. + $waitTimeout = [System.TimeSpan]::new(0, 15, 0) + $startWaitTime = Get-Date + + while (-not (Test-Path $outputFileBlockerPath)) + { + $elapsedTime = (Get-Date) - $startWaitTime + if ($elapsedTime -gt $waitTimeout) + { + break + } + Start-Sleep 1 + } + + if ($Wait) + { + Read-Host "Press Enter to close sandbox and continue..." + } + + Close-WindowsSandbox +} + +Write-Host @" +--> Results are located at $ResultsPath +"@ diff --git a/tools/DevInSandbox/InSandboxScript.ps1 b/tools/DevInSandbox/InSandboxScript.ps1 index 8158343f60..691087b39d 100644 --- a/tools/DevInSandbox/InSandboxScript.ps1 +++ b/tools/DevInSandbox/InSandboxScript.ps1 @@ -1,31 +1,31 @@ -Param( - [String[]] $DesktopAppInstallerDependencyPath -) - -$ProgressPreference = 'SilentlyContinue' - -$desktopPath = "C:\Users\WDAGUtilityAccount\Desktop" - -Write-Host @" ---> Installing WinGet - -"@ - -foreach($dependency in $DesktopAppInstallerDependencyPath) -{ - Write-Host @" - ----> Installing $dependency -"@ - Add-AppxPackage -Path $dependency -} - -Write-Host @" - ----> Enabling dev mode -"@ -reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" - -$devPackageManifestPath = Join-Path $desktopPath "DevPackage\AppxManifest.xml" -Write-Host @" - ----> Installing $devPackageManifestPath -"@ -Add-AppxPackage -Path $devPackageManifestPath -Register +Param( + [String[]] $DesktopAppInstallerDependencyPath +) + +$ProgressPreference = 'SilentlyContinue' + +$desktopPath = "C:\Users\WDAGUtilityAccount\Desktop" + +Write-Host @" +--> Installing WinGet + +"@ + +foreach($dependency in $DesktopAppInstallerDependencyPath) +{ + Write-Host @" + ----> Installing $dependency +"@ + Add-AppxPackage -Path $dependency +} + +Write-Host @" + ----> Enabling dev mode +"@ +reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1" + +$devPackageManifestPath = Join-Path $desktopPath "DevPackage\AppxManifest.xml" +Write-Host @" + ----> Installing $devPackageManifestPath +"@ +Add-AppxPackage -Path $devPackageManifestPath -Register diff --git a/tools/DevInSandbox/Launch-DevPackageInSandbox.ps1 b/tools/DevInSandbox/Launch-DevPackageInSandbox.ps1 index f21a494791..7c00e52367 100644 --- a/tools/DevInSandbox/Launch-DevPackageInSandbox.ps1 +++ b/tools/DevInSandbox/Launch-DevPackageInSandbox.ps1 @@ -1,216 +1,216 @@ -Param( - [Parameter(HelpMessage = "The directory where local dev build is located; only the release build works.")] - [String] $DevPackagePath -) - -$ErrorActionPreference = "Stop" - -# Validate that the local dev manifest exists - -if (-not $DevPackagePath) -{ - $DevPackagePath = Join-Path $PSScriptRoot "..\..\src\AppInstallerCLIPackage\bin\x64\Release\AppX" -} - -$DevPackagePath = [System.IO.Path]::GetFullPath($DevPackagePath) - -if ($DevPackagePath.ToLower().Contains("debug")) -{ - Write-Error -Category InvalidArgument -Message @" -The Debug dev package does not work for unknown reasons. -Use the Release build or figure out how to make debug work and fix the scripts. -"@ -} - -if (-not (Test-Path (Join-Path $DevPackagePath "AppxManifest.xml"))) -{ - Write-Error -Category InvalidArgument -Message @" -AppxManifest.xml does not exist in the path $DevPackagePath -Either build the local dev package, or provide the location using -DevPackagePath -"@ -} - -# Check if Windows Sandbox is enabled - -if (-Not (Get-Command 'WindowsSandbox' -ErrorAction SilentlyContinue)) -{ - Write-Error -Category NotInstalled -Message @' -Windows Sandbox does not seem to be available. Check the following URL for prerequisites and further details: -https://docs.microsoft.com/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview - -You can run the following command in an elevated PowerShell for enabling Windows Sandbox: -$ Enable-WindowsOptionalFeature -Online -FeatureName 'Containers-DisposableClientVM' -'@ -} - -# Close Windows Sandbox - -function Close-WindowsSandbox { - $sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue - if ($sandbox) - { - Write-Host '--> Closing Windows Sandbox' - - $sandboxServer = Get-Process 'WindowsSandbox' -ErrorAction SilentlyContinue - - $sandbox | Stop-Process - $sandbox | Wait-Process -Timeout 120 - - # Also wait for the server to close - if ($sandboxServer) - { - try - { - $sandboxServer | Wait-Process -Timeout 120 - } - catch [System.TimeoutException] - { - # Force stop the server if it does not automatically stop - $sandboxServer | Stop-Process - $sandboxServer | Wait-Process -Timeout 120 - } - - } - - Write-Host - } - Remove-Variable sandbox -} - -Close-WindowsSandbox - -# Initialize Temp Folder - -$tempFolderName = 'DevInSandboxStaging' -$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $tempFolderName - -New-Item $tempFolder -ItemType Directory -ErrorAction SilentlyContinue | Out-Null - -# Set dependencies - -$desktopInSandbox = 'C:\Users\WDAGUtilityAccount\Desktop' - -[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -$WebClient = New-Object System.Net.WebClient - -# Hide the progress bar of Invoke-WebRequest -$oldProgressPreference = $ProgressPreference -$ProgressPreference = 'SilentlyContinue' - -$ProgressPreference = $oldProgressPreference - -$vcLibsUwp = @{ - fileName = 'Microsoft.VCLibs.x64.14.00.Desktop.appx' - url = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx' - hash = '9BFDE6CFCC530EF073AB4BC9C4817575F63BE1251DD75AAA58CB89299697A569' - folderInLocal = Join-Path ${env:ProgramFiles(x86)} "Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Retail\x64" -} - -$dependencies = @($vcLibsUwp) - -# Clean temp directory - -Get-ChildItem $tempFolder -Recurse -Exclude $dependencies.fileName | Remove-Item -Force -Recurse - -# Download dependencies - -Write-Host '--> Checking dependencies' - -foreach ($dependency in $dependencies) -{ - $dependency.pathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $dependency.fileName) - - # First see if the file exists locally to copy instead of downloading - if ($dependency.folderInLocal -ne $null) - { - $dependencyFilePath = Join-Path -Path $dependency.folderInLocal -ChildPath $dependency.fileName - if (Test-Path -Path $dependencyFilePath -PathType Leaf) - { - $dependencyFilePath - $tempFolder - Copy-Item -Path $dependencyFilePath -Destination $tempFolder -Force - continue - } - } - - # File does not exist locally, we need to download - - $dependency.file = Join-Path -Path $tempFolder -ChildPath $dependency.fileName - - # Only download if the file does not exist, or its hash does not match. - if (-Not ((Test-Path -Path $dependency.file -PathType Leaf) -And $dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) - { - Write-Host @" - - Downloading: - $($dependency.url) -"@ - - try - { - $WebClient.DownloadFile($dependency.url, $dependency.file) - } - catch - { - #Pass the exception as an inner exception - throw [System.Net.WebException]::new("Error downloading $($dependency.url).",$_.Exception) - } - if (-not ($dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) - { - throw [System.Activities.VersionMismatchException]::new('Dependency hash does not match the downloaded file') - } - } -} - -# Copy main script - -$mainPs1FileName = 'InSandboxScript.ps1' -Copy-Item (Join-Path $PSScriptRoot $mainPs1FileName) (Join-Path $tempFolder $mainPs1FileName) - -$dependenciesPathsInSandbox = "@('$($vcLibsUwp.pathInSandbox)')" -$bootstrapPs1Content = ".\$mainPs1FileName -DesktopAppInstallerDependencyPath @($dependenciesPathsInSandbox)" - -$bootstrapPs1FileName = 'Bootstrap.ps1' -$bootstrapPs1Content | Out-File (Join-Path $tempFolder $bootstrapPs1FileName) -Force - -# Create Windows Sandbox configuration file - -$bootstrapPs1InSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $bootstrapPs1FileName) -$tempFolderInSandbox = Join-Path -Path $desktopInSandbox -ChildPath $tempFolderName - -$devPackageInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "DevPackage" -$devPackageXMLFragment = "" - -$devPackageXMLFragment = @" - - $DevPackagePath - $devPackageInSandbox - -"@ - -$sandboxTestWsbContent = @" - - - - $tempFolder - true - - $devPackageXMLFragment - - - PowerShell Start-Process PowerShell -WindowStyle Maximized -WorkingDirectory '$tempFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -NoLogo -File $bootstrapPs1InSandbox' - - -"@ - -$sandboxTestWsbFileName = 'SandboxTest.wsb' -$sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName -$sandboxTestWsbContent | Out-File $sandboxTestWsbFile -Force - -Write-Host @" ---> Starting Windows Sandbox - $sandboxTestWsbFile -"@ - -Write-Host - -WindowsSandbox $SandboxTestWsbFile +Param( + [Parameter(HelpMessage = "The directory where local dev build is located; only the release build works.")] + [String] $DevPackagePath +) + +$ErrorActionPreference = "Stop" + +# Validate that the local dev manifest exists + +if (-not $DevPackagePath) +{ + $DevPackagePath = Join-Path $PSScriptRoot "..\..\src\AppInstallerCLIPackage\bin\x64\Release\AppX" +} + +$DevPackagePath = [System.IO.Path]::GetFullPath($DevPackagePath) + +if ($DevPackagePath.ToLower().Contains("debug")) +{ + Write-Error -Category InvalidArgument -Message @" +The Debug dev package does not work for unknown reasons. +Use the Release build or figure out how to make debug work and fix the scripts. +"@ +} + +if (-not (Test-Path (Join-Path $DevPackagePath "AppxManifest.xml"))) +{ + Write-Error -Category InvalidArgument -Message @" +AppxManifest.xml does not exist in the path $DevPackagePath +Either build the local dev package, or provide the location using -DevPackagePath +"@ +} + +# Check if Windows Sandbox is enabled + +if (-Not (Get-Command 'WindowsSandbox' -ErrorAction SilentlyContinue)) +{ + Write-Error -Category NotInstalled -Message @' +Windows Sandbox does not seem to be available. Check the following URL for prerequisites and further details: +https://docs.microsoft.com/windows/security/threat-protection/windows-sandbox/windows-sandbox-overview + +You can run the following command in an elevated PowerShell for enabling Windows Sandbox: +$ Enable-WindowsOptionalFeature -Online -FeatureName 'Containers-DisposableClientVM' +'@ +} + +# Close Windows Sandbox + +function Close-WindowsSandbox { + $sandbox = Get-Process 'WindowsSandboxClient' -ErrorAction SilentlyContinue + if ($sandbox) + { + Write-Host '--> Closing Windows Sandbox' + + $sandboxServer = Get-Process 'WindowsSandbox' -ErrorAction SilentlyContinue + + $sandbox | Stop-Process + $sandbox | Wait-Process -Timeout 120 + + # Also wait for the server to close + if ($sandboxServer) + { + try + { + $sandboxServer | Wait-Process -Timeout 120 + } + catch [System.TimeoutException] + { + # Force stop the server if it does not automatically stop + $sandboxServer | Stop-Process + $sandboxServer | Wait-Process -Timeout 120 + } + + } + + Write-Host + } + Remove-Variable sandbox +} + +Close-WindowsSandbox + +# Initialize Temp Folder + +$tempFolderName = 'DevInSandboxStaging' +$tempFolder = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath $tempFolderName + +New-Item $tempFolder -ItemType Directory -ErrorAction SilentlyContinue | Out-Null + +# Set dependencies + +$desktopInSandbox = 'C:\Users\WDAGUtilityAccount\Desktop' + +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$WebClient = New-Object System.Net.WebClient + +# Hide the progress bar of Invoke-WebRequest +$oldProgressPreference = $ProgressPreference +$ProgressPreference = 'SilentlyContinue' + +$ProgressPreference = $oldProgressPreference + +$vcLibsUwp = @{ + fileName = 'Microsoft.VCLibs.x64.14.00.Desktop.appx' + url = 'https://aka.ms/Microsoft.VCLibs.x64.14.00.Desktop.appx' + hash = '9BFDE6CFCC530EF073AB4BC9C4817575F63BE1251DD75AAA58CB89299697A569' + folderInLocal = Join-Path ${env:ProgramFiles(x86)} "Microsoft SDKs\Windows Kits\10\ExtensionSDKs\Microsoft.VCLibs.Desktop\14.0\Appx\Retail\x64" +} + +$dependencies = @($vcLibsUwp) + +# Clean temp directory + +Get-ChildItem $tempFolder -Recurse -Exclude $dependencies.fileName | Remove-Item -Force -Recurse + +# Download dependencies + +Write-Host '--> Checking dependencies' + +foreach ($dependency in $dependencies) +{ + $dependency.pathInSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $dependency.fileName) + + # First see if the file exists locally to copy instead of downloading + if ($dependency.folderInLocal -ne $null) + { + $dependencyFilePath = Join-Path -Path $dependency.folderInLocal -ChildPath $dependency.fileName + if (Test-Path -Path $dependencyFilePath -PathType Leaf) + { + $dependencyFilePath + $tempFolder + Copy-Item -Path $dependencyFilePath -Destination $tempFolder -Force + continue + } + } + + # File does not exist locally, we need to download + + $dependency.file = Join-Path -Path $tempFolder -ChildPath $dependency.fileName + + # Only download if the file does not exist, or its hash does not match. + if (-Not ((Test-Path -Path $dependency.file -PathType Leaf) -And $dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) + { + Write-Host @" + - Downloading: + $($dependency.url) +"@ + + try + { + $WebClient.DownloadFile($dependency.url, $dependency.file) + } + catch + { + #Pass the exception as an inner exception + throw [System.Net.WebException]::new("Error downloading $($dependency.url).",$_.Exception) + } + if (-not ($dependency.hash -eq $(Get-FileHash $dependency.file).Hash)) + { + throw [System.Activities.VersionMismatchException]::new('Dependency hash does not match the downloaded file') + } + } +} + +# Copy main script + +$mainPs1FileName = 'InSandboxScript.ps1' +Copy-Item (Join-Path $PSScriptRoot $mainPs1FileName) (Join-Path $tempFolder $mainPs1FileName) + +$dependenciesPathsInSandbox = "@('$($vcLibsUwp.pathInSandbox)')" +$bootstrapPs1Content = ".\$mainPs1FileName -DesktopAppInstallerDependencyPath @($dependenciesPathsInSandbox)" + +$bootstrapPs1FileName = 'Bootstrap.ps1' +$bootstrapPs1Content | Out-File (Join-Path $tempFolder $bootstrapPs1FileName) -Force + +# Create Windows Sandbox configuration file + +$bootstrapPs1InSandbox = Join-Path -Path $desktopInSandbox -ChildPath (Join-Path -Path $tempFolderName -ChildPath $bootstrapPs1FileName) +$tempFolderInSandbox = Join-Path -Path $desktopInSandbox -ChildPath $tempFolderName + +$devPackageInSandbox = Join-Path -Path $desktopInSandbox -ChildPath "DevPackage" +$devPackageXMLFragment = "" + +$devPackageXMLFragment = @" + + $DevPackagePath + $devPackageInSandbox + +"@ + +$sandboxTestWsbContent = @" + + + + $tempFolder + true + + $devPackageXMLFragment + + + PowerShell Start-Process PowerShell -WindowStyle Maximized -WorkingDirectory '$tempFolderInSandbox' -ArgumentList '-ExecutionPolicy Bypass -NoExit -NoLogo -File $bootstrapPs1InSandbox' + + +"@ + +$sandboxTestWsbFileName = 'SandboxTest.wsb' +$sandboxTestWsbFile = Join-Path -Path $tempFolder -ChildPath $sandboxTestWsbFileName +$sandboxTestWsbContent | Out-File $sandboxTestWsbFile -Force + +Write-Host @" +--> Starting Windows Sandbox + $sandboxTestWsbFile +"@ + +Write-Host + +WindowsSandbox $SandboxTestWsbFile diff --git a/tools/HAMTrace/WER.HostActivityManager.wprp b/tools/HAMTrace/WER.HostActivityManager.wprp index 57c000f14e..f4bc0ff5e9 100644 --- a/tools/HAMTrace/WER.HostActivityManager.wprp +++ b/tools/HAMTrace/WER.HostActivityManager.wprp @@ -1,268 +1,268 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/IndexComparisonTool/IndexComparisonTool.vcxproj b/tools/IndexComparisonTool/IndexComparisonTool.vcxproj index 1b7a11945d..9d38cfd54e 100644 --- a/tools/IndexComparisonTool/IndexComparisonTool.vcxproj +++ b/tools/IndexComparisonTool/IndexComparisonTool.vcxproj @@ -1,116 +1,116 @@ - - - - - Debug - ARM64 - - - Debug - x64 - - - Release - ARM64 - - - Release - x64 - - - - - 16.0 - {2F8A1C3E-7B4D-4E9F-A6C2-1D5E8B3F0A7C} - Win32Proj - IndexComparisonTool - 10.0.26100.0 - 10.0.17763.0 - true - - - - - - Application - Unicode - v143 - - - true - true - - - false - true - false - - - - - - - - - - - - $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(ProjectDir)obj\$(Platform)\$(Configuration)\ - - - - - Use - pch.h - $(IntDir)pch.pch - Level4 - true - true - /permissive- /std:c++17 %(AdditionalOptions) - - UNICODE;_UNICODE;%(PreprocessorDefinitions) - - - Console - - - %(AdditionalDependencies) - - - - - - Disabled - _DEBUG;%(PreprocessorDefinitions) - - - - - - MaxSpeed - true - true - NDEBUG;%(PreprocessorDefinitions) - - - true - true - - - - - - - - - - - Create - - - - - + + + + + Debug + ARM64 + + + Debug + x64 + + + Release + ARM64 + + + Release + x64 + + + + + 16.0 + {2F8A1C3E-7B4D-4E9F-A6C2-1D5E8B3F0A7C} + Win32Proj + IndexComparisonTool + 10.0.26100.0 + 10.0.17763.0 + true + + + + + + Application + Unicode + v143 + + + true + true + + + false + true + false + + + + + + + + + + + + $(ProjectDir)bin\$(Platform)\$(Configuration)\ + $(ProjectDir)obj\$(Platform)\$(Configuration)\ + + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + true + true + /permissive- /std:c++17 %(AdditionalOptions) + + UNICODE;_UNICODE;%(PreprocessorDefinitions) + + + Console + + + %(AdditionalDependencies) + + + + + + Disabled + _DEBUG;%(PreprocessorDefinitions) + + + + + + MaxSpeed + true + true + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + + Create + + + + + diff --git a/tools/IndexComparisonTool/IndexComparisonTool.vcxproj.filters b/tools/IndexComparisonTool/IndexComparisonTool.vcxproj.filters index dbb44d8f44..2fc24ea663 100644 --- a/tools/IndexComparisonTool/IndexComparisonTool.vcxproj.filters +++ b/tools/IndexComparisonTool/IndexComparisonTool.vcxproj.filters @@ -1,29 +1,29 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;hm;inl;inc;ipp;xsd - - - - - Source Files - - - Source Files - - - - - Header Files - - - Header Files - - - + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;ipp;xsd + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + diff --git a/tools/IndexComparisonTool/WinGetUtil.h b/tools/IndexComparisonTool/WinGetUtil.h index 94531e46aa..090c3cf687 100644 --- a/tools/IndexComparisonTool/WinGetUtil.h +++ b/tools/IndexComparisonTool/WinGetUtil.h @@ -1,29 +1,29 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -// Minimal WinGetUtil types and function pointer declarations for use -// with runtime loading via LoadLibrary. Only the APIs used by -// IndexComparisonTool are defined here. -#pragma once - -typedef void* WINGET_SQLITE_INDEX_HANDLE; -typedef wchar_t const* WINGET_STRING; - -#define WINGET_SQLITE_INDEX_VERSION_LATEST ((UINT32)-1) - -typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexCreate)( - WINGET_STRING filePath, - UINT32 majorVersion, - UINT32 minorVersion, - WINGET_SQLITE_INDEX_HANDLE* index); - -typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexAddManifest)( - WINGET_SQLITE_INDEX_HANDLE index, - WINGET_STRING manifestPath, - WINGET_STRING relativePath); - -typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexPrepareForPackaging)( - WINGET_SQLITE_INDEX_HANDLE index); - -typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexClose)( - WINGET_SQLITE_INDEX_HANDLE index); +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +// Minimal WinGetUtil types and function pointer declarations for use +// with runtime loading via LoadLibrary. Only the APIs used by +// IndexComparisonTool are defined here. +#pragma once + +typedef void* WINGET_SQLITE_INDEX_HANDLE; +typedef wchar_t const* WINGET_STRING; + +#define WINGET_SQLITE_INDEX_VERSION_LATEST ((UINT32)-1) + +typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexCreate)( + WINGET_STRING filePath, + UINT32 majorVersion, + UINT32 minorVersion, + WINGET_SQLITE_INDEX_HANDLE* index); + +typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexAddManifest)( + WINGET_SQLITE_INDEX_HANDLE index, + WINGET_STRING manifestPath, + WINGET_STRING relativePath); + +typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexPrepareForPackaging)( + WINGET_SQLITE_INDEX_HANDLE index); + +typedef HRESULT (__stdcall *PFN_WinGetSQLiteIndexClose)( + WINGET_SQLITE_INDEX_HANDLE index); diff --git a/tools/IndexComparisonTool/main.cpp b/tools/IndexComparisonTool/main.cpp index 51bd1903a1..be792339e9 100644 --- a/tools/IndexComparisonTool/main.cpp +++ b/tools/IndexComparisonTool/main.cpp @@ -224,9 +224,9 @@ namespace ManifestStats stats; std::vector retryList; - + retry: - uint64_t processed = 0; + uint64_t processed = 0; auto start = std::chrono::steady_clock::now(); for (const auto& dir : manifestDirs) { @@ -241,9 +241,9 @@ namespace } if (++processed % 1000 == 0) - { - auto now = std::chrono::steady_clock::now(); - auto duration = now - start; + { + auto now = std::chrono::steady_clock::now(); + auto duration = now - start; start = now; std::wcout << L" " << processed << L" / " << manifestDirs.size() << L" processed (~" << std::chrono::duration_cast(duration / 1000).count() << L"ms per)...\r"; } @@ -251,17 +251,17 @@ namespace // Retry failures until no progress is made if (!retryList.empty()) - { - if (retryList.size() < manifestDirs.size()) + { + if (retryList.size() < manifestDirs.size()) { std::wcout << L"\nRetrying " << retryList.size() << L" failed manifests...\n"; - manifestDirs = std::move(retryList); - retryList.clear(); - goto retry; - } - else + manifestDirs = std::move(retryList); + retryList.clear(); + goto retry; + } + else { - std::wcout << L"\nDropping " << retryList.size() << L" failed manifests...\n"; + std::wcout << L"\nDropping " << retryList.size() << L" failed manifests...\n"; stats.failed = retryList.size(); } } @@ -585,7 +585,7 @@ namespace if (ratioDelta >= 0) ratioSS << L"+"; ratioSS << std::fixed << std::setprecision(2) << (ratioDelta * 100) << L"pp" << L" (was " << std::setprecision(1) << (baseRatio * 100) << L"%)"; - + std::wcout << L'\n'; std::wcout << L" vs baseline (" << baselineFile.filename().wstring() << L"):\n"; std::wcout << L" Raw size: " << signedBytes(rawDelta) @@ -623,9 +623,9 @@ int wmain(int argc, wchar_t* argv[]) return 1; } - std::filesystem::path indexPath; - ManifestStats stats{}; - if (!args.prebuilt) + std::filesystem::path indexPath; + ManifestStats stats{}; + if (!args.prebuilt) { // Load WinGetUtil.dll at runtime WinGetApi api = LoadWinGetUtil(args.wingetUtilPath); @@ -679,11 +679,11 @@ int wmain(int argc, wchar_t* argv[]) indexClosed = true; api.Close(index); - } - else - { - indexPath = args.manifestsDir; - } + } + else + { + indexPath = args.manifestsDir; + } // Optionally query table stats from the now-closed database std::vector tables; @@ -726,4 +726,4 @@ int wmain(int argc, wchar_t* argv[]) } return 0; -} +} diff --git a/tools/IndexComparisonTool/pch.cpp b/tools/IndexComparisonTool/pch.cpp index 5494963b40..b7f2ce9c04 100644 --- a/tools/IndexComparisonTool/pch.cpp +++ b/tools/IndexComparisonTool/pch.cpp @@ -1,3 +1,3 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#include "pch.h" +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#include "pch.h" diff --git a/tools/IndexComparisonTool/pch.h b/tools/IndexComparisonTool/pch.h index a571c2abbd..06af16fa5f 100644 --- a/tools/IndexComparisonTool/pch.h +++ b/tools/IndexComparisonTool/pch.h @@ -1,22 +1,22 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. -#pragma once - -#define NOMINMAX -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. +#pragma once + +#define NOMINMAX +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/tools/SampleWinGetUWPCaller/AppInstallerCaller/App.xaml b/tools/SampleWinGetUWPCaller/AppInstallerCaller/App.xaml index 8cef30bb57..2760ecc44f 100644 --- a/tools/SampleWinGetUWPCaller/AppInstallerCaller/App.xaml +++ b/tools/SampleWinGetUWPCaller/AppInstallerCaller/App.xaml @@ -1,4 +1,4 @@ - - - - - true - true - true - true - {37f1fd2a-4d63-45a0-82aa-66ef126cb322} - AppInstallerCaller - AppInstallerCaller - en-US - 15.0 - true - Windows Store - 10.0 - 10.0.26100.0 - 10.0.17763.0 - - - - - Debug - ARM64 - - - Debug - Win32 - - - Debug - x64 - - - Release - ARM64 - - - Release - Win32 - - - Release - x64 - - - - Application - - - true - true - - - false - true - false - - - - - - - - - - - - False - True - SHA256 - True - True - x64 - 0 - Always - - - - Use - pch.h - $(IntDir)pch.pch - Level4 - %(AdditionalOptions) /bigobj - - /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) - - - WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) - - - false - - - - - _DEBUG;%(PreprocessorDefinitions) - - - - - - - - - NDEBUG;%(PreprocessorDefinitions) - - - true - true - - - - - - - - - - - App.xaml - - - MainPage.xaml - - - - - Designer - - - Designer - - - - - Designer - - - - - - - - - - - - - - - Create - - - App.xaml - - - MainPage.xaml - - - - - - App.xaml - - - MainPage.xaml - - - - - - - - - GeneratedFromServer\Microsoft.Management.Deployment.winmd - true - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - + + + + + true + true + true + true + {37f1fd2a-4d63-45a0-82aa-66ef126cb322} + AppInstallerCaller + AppInstallerCaller + en-US + 15.0 + true + Windows Store + 10.0 + 10.0.26100.0 + 10.0.17763.0 + + + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM64 + + + Release + Win32 + + + Release + x64 + + + + Application + + + true + true + + + false + true + false + + + + + + + + + + + + False + True + SHA256 + True + True + x64 + 0 + Always + + + + Use + pch.h + $(IntDir)pch.pch + Level4 + %(AdditionalOptions) /bigobj + + /DWINRT_NO_MAKE_DETECTION %(AdditionalOptions) + + + WIN32_LEAN_AND_MEAN;WINRT_LEAN_AND_MEAN;%(PreprocessorDefinitions) + + + false + + + + + _DEBUG;%(PreprocessorDefinitions) + + + + + + + + + NDEBUG;%(PreprocessorDefinitions) + + + true + true + + + + + + + + + + + App.xaml + + + MainPage.xaml + + + + + Designer + + + Designer + + + + + Designer + + + + + + + + + + + + + + + Create + + + App.xaml + + + MainPage.xaml + + + + + + App.xaml + + + MainPage.xaml + + + + + + + + + GeneratedFromServer\Microsoft.Management.Deployment.winmd + true + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/tools/SampleWinGetUWPCaller/AppInstallerCaller/AppInstallerCaller.vcxproj.filters b/tools/SampleWinGetUWPCaller/AppInstallerCaller/AppInstallerCaller.vcxproj.filters index a05e4efc78..e320bb1f72 100644 --- a/tools/SampleWinGetUWPCaller/AppInstallerCaller/AppInstallerCaller.vcxproj.filters +++ b/tools/SampleWinGetUWPCaller/AppInstallerCaller/AppInstallerCaller.vcxproj.filters @@ -1,59 +1,59 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - Assets - - - - - - - - - - - - {adeebf4e-70b7-41ae-936b-ded00e6e6777} - - + + + + + + + + + + + + + + + + + + + + + + + + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + Assets + + + + + + + + + + + + {adeebf4e-70b7-41ae-936b-ded00e6e6777} + + \ No newline at end of file diff --git a/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.cpp b/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.cpp index bff63e885f..fb20048880 100644 --- a/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.cpp +++ b/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.cpp @@ -142,12 +142,12 @@ namespace winrt::AppInstallerCaller::implementation IAsyncOperationWithProgress MainPage::DownloadPackage(CatalogPackage package, std::wstring downloadDirectory) { PackageManager packageManager = CreatePackageManager(); - DownloadOptions downloadOptions = CreateDownloadOptions(); - - if (!downloadDirectory.empty()) - { - downloadOptions.DownloadDirectory(downloadDirectory); - } + DownloadOptions downloadOptions = CreateDownloadOptions(); + + if (!downloadDirectory.empty()) + { + downloadOptions.DownloadDirectory(downloadDirectory); + } return packageManager.DownloadPackageAsync(package, downloadOptions); } @@ -184,12 +184,12 @@ namespace winrt::AppInstallerCaller::implementation default: statusText.Text(L""); } - } - + } + IAsyncAction UpdateUIDownloadProgress( - PackageDownloadProgress progress, - winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, - winrt::Windows::UI::Xaml::Controls::TextBlock statusText) + PackageDownloadProgress progress, + winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, + winrt::Windows::UI::Xaml::Controls::TextBlock statusText) { co_await winrt::resume_foreground(progressBar.Dispatcher()); progressBar.Value(progress.DownloadProgress * 100); @@ -293,14 +293,14 @@ namespace winrt::AppInstallerCaller::implementation winrt::Windows::UI::Xaml::Controls::Button cancelButton, winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, winrt::Windows::UI::Xaml::Controls::TextBlock statusText) - { - operation.Progress([=]( - IAsyncOperationWithProgress const& /* sender */, - PackageDownloadProgress const& progress) - { - UpdateUIDownloadProgress(progress, progressBar, statusText).get(); - }); - + { + operation.Progress([=]( + IAsyncOperationWithProgress const& /* sender */, + PackageDownloadProgress const& progress) + { + UpdateUIDownloadProgress(progress, progressBar, statusText).get(); + }); + winrt::hresult downloadOperationHr = S_OK; std::wstring errorMessage{ L"Unknown Error" }; DownloadResult downloadResult{ nullptr }; @@ -351,7 +351,7 @@ namespace winrt::AppInstallerCaller::implementation failText << L"Download failed: " << downloadResult.ExtendedErrorCode(); downloadButton.Content(box_value(L"Download")); statusText.Text(failText.str()); - } + } } IAsyncAction MainPage::GetSources(winrt::Windows::UI::Xaml::Controls::Button button) @@ -569,7 +569,7 @@ namespace winrt::AppInstallerCaller::implementation } IAsyncAction MainPage::FindPackage( - winrt::Windows::UI::Xaml::Controls::Button installButton, + winrt::Windows::UI::Xaml::Controls::Button installButton, winrt::Windows::UI::Xaml::Controls::Button downloadButton, winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, winrt::Windows::UI::Xaml::Controls::TextBlock statusText) @@ -618,7 +618,7 @@ namespace winrt::AppInstallerCaller::implementation else { co_await winrt::resume_foreground(installButton.Dispatcher()); - installButton.IsEnabled(true); + installButton.IsEnabled(true); downloadButton.IsEnabled(true); statusText.Text(L"Found the package to install or download."); } @@ -635,12 +635,12 @@ namespace winrt::AppInstallerCaller::implementation void MainPage::SearchButtonClickHandler(IInspectable const&, RoutedEventArgs const&) { m_installAppId = catalogIdTextBox().Text(); - installButton().IsEnabled(false); + installButton().IsEnabled(false); downloadButton().IsEnabled(false); cancelButton().IsEnabled(false); installStatusText().Text(L"Looking for package."); FindPackage(installButton(), downloadButton(), installProgressBar(), installStatusText()); - } + } void MainPage::InstallButtonClickHandler(IInspectable const&, RoutedEventArgs const&) { @@ -656,11 +656,11 @@ namespace winrt::AppInstallerCaller::implementation { m_installPackageOperation.Cancel(); } - } + } void MainPage::DownloadButtonClickHandler(IInspectable const&, RoutedEventArgs const&) - { - m_downloadDirectory = downloadDirectoryTextBox().Text(); + { + m_downloadDirectory = downloadDirectoryTextBox().Text(); if (m_downloadPackageOperation == nullptr || m_downloadPackageOperation.Status() != AsyncStatus::Started) { @@ -674,7 +674,7 @@ namespace winrt::AppInstallerCaller::implementation { m_downloadPackageOperation.Cancel(); } - } + } void MainPage::RefreshInstalledButtonClickHandler(IInspectable const&, RoutedEventArgs const&) { diff --git a/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.h b/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.h index ee05c1f66b..cd749fc0c4 100644 --- a/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.h +++ b/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.h @@ -44,7 +44,7 @@ namespace winrt::AppInstallerCaller::implementation winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, winrt::Windows::UI::Xaml::Controls::TextBlock statusText); Windows::Foundation::IAsyncAction FindPackage( - winrt::Windows::UI::Xaml::Controls::Button installButton, + winrt::Windows::UI::Xaml::Controls::Button installButton, winrt::Windows::UI::Xaml::Controls::Button downloadButton, winrt::Windows::UI::Xaml::Controls::ProgressBar progressBar, winrt::Windows::UI::Xaml::Controls::TextBlock statusText); @@ -68,7 +68,7 @@ namespace winrt::AppInstallerCaller::implementation Windows::Foundation::Collections::IObservableVector m_installingPackageViews; Windows::Foundation::IAsyncOperationWithProgress m_installPackageOperation; Windows::Foundation::IAsyncOperationWithProgress m_downloadPackageOperation; - std::wstring m_installAppId; + std::wstring m_installAppId; std::wstring m_downloadDirectory; Deployment::PackageManager m_packageManager{ nullptr }; bool m_useDev = false; diff --git a/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.xaml b/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.xaml index c2bb22aeef..d338f15cd4 100644 --- a/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.xaml +++ b/tools/SampleWinGetUWPCaller/AppInstallerCaller/MainPage.xaml @@ -1,4 +1,4 @@ - [CHAN] message -YYYY-MM-DD HH:MM:SS.mmm [CHAN] [SUBCHAN] message ← subchannel variant -``` - -Where `` is an optional single-letter severity marker: `V`=Verbose, `I`=Info, `W`=Warning, `E`=Error, `C`=Critical. Older log files without the marker are also supported. - -## Development - -### Prerequisites -- Node.js 18+ -- VS Code 1.85+ - -### Build - -```bash -cd tools/WinGetLogViewer -npm install -npm run compile -``` - -### Run / Debug - -1. Open `tools/WinGetLogViewer/` as a workspace in VS Code. -2. Press **F5** — this launches the Extension Development Host. -3. Open any `WinGet-*.log` or `WinGetCOM-*.log` file; the viewer opens automatically. - -### Package - -```bash -npm run package -``` - -This produces `winget-log-viewer-.vsix`. Install it via **Extensions: Install from VSIX…** or: - -```bash -code --install-extension winget-log-viewer-0.1.0.vsix -``` - -## Keyboard shortcuts - -| Key | Action | -|-----|--------| -| **Ctrl+F** (in viewer) | Focus the search box | - -## Roadmap / ideas - -- Per-session filter presets (save/restore named filter combinations) -- Fold/collapse groups of lines by channel -- Correlation ID linking (when GUIDs appear in messages) +# WinGet Log Viewer + +A VS Code extension that makes reading WinGet diagnostic log files fast and pleasant. + +## Features + +### Rich log viewer (WebView editor) +Opens automatically for `WinGet-*.log` and `WinGetCOM-*.log` files. Use the **"Open in WinGet Log Viewer"** command (or right-click any `.log` file in the Explorer) to open any log file in the viewer. + +**Channel color badges** — each of the nine WinGet channels gets a distinct color: + +| Channel | Description | +|---------|-------------| +| ❤️ `FAIL` | Failures / exceptions | +| 💙 `CLI` | CLI command handling | +| 💜 `SQL` | SQLite / index operations | +| 🩵 `REPO` | Repository / source operations | +| 💛 `YAML` | YAML manifest parsing | +| 🤍 `CORE` | Core runtime | +| 💚 `TEST` | Test infrastructure | +| 🩶 `CONF` | Configuration / DSC | +| 🧡 `WORK` | Workflow execution | + +**Subchannel detection** — when a sub-component routes its log lines through a parent channel, the original `[CHAN]` tag appears at the start of the message. The viewer detects this and renders it as a secondary subchannel badge. + +**Dynamic filters** — toggle individual channels and subchannels on/off with checkboxes. Use "All" / "None" quick links per section. + +**Quick text search** — type in the Search box to live-filter visible lines. + +**Severity highlighting** — rows that contain error/warning keywords get a colored left border and message highlighting. + +**Time delta** — enable "Show time delta" to see the elapsed time between consecutive visible lines (`+Xms` / `+Xs`). Great for spotting slow operations. + +**Jump to error** — use the ↑ Error / ↓ Error buttons to navigate between error and critical log lines. + +**Follow mode** — enable "Follow (live tail)" to auto-scroll as the log file grows on disk. New line count is shown briefly in the status bar. + +**Export filtered** — "Copy visible lines" sends all currently visible (filtered) raw log lines to the clipboard. + +**Wrap long lines** — enable "Wrap long lines" to prevent horizontal scrolling on very long messages. + +### Syntax highlighting (standard editor) +When a WinGet log file is opened in VS Code's standard text editor, the TextMate grammar provides coloring for timestamps, channel badges, subchannels, and error/warning keywords — no additional setup required. + +## Log format + +``` +YYYY-MM-DD HH:MM:SS.mmm [CHAN] message +YYYY-MM-DD HH:MM:SS.mmm [CHAN] [SUBCHAN] message ← subchannel variant +``` + +Where `` is an optional single-letter severity marker: `V`=Verbose, `I`=Info, `W`=Warning, `E`=Error, `C`=Critical. Older log files without the marker are also supported. + +## Development + +### Prerequisites +- Node.js 18+ +- VS Code 1.85+ + +### Build + +```bash +cd tools/WinGetLogViewer +npm install +npm run compile +``` + +### Run / Debug + +1. Open `tools/WinGetLogViewer/` as a workspace in VS Code. +2. Press **F5** — this launches the Extension Development Host. +3. Open any `WinGet-*.log` or `WinGetCOM-*.log` file; the viewer opens automatically. + +### Package + +```bash +npm run package +``` + +This produces `winget-log-viewer-.vsix`. Install it via **Extensions: Install from VSIX…** or: + +```bash +code --install-extension winget-log-viewer-0.1.0.vsix +``` + +## Keyboard shortcuts + +| Key | Action | +|-----|--------| +| **Ctrl+F** (in viewer) | Focus the search box | + +## Roadmap / ideas + +- Per-session filter presets (save/restore named filter combinations) +- Fold/collapse groups of lines by channel +- Correlation ID linking (when GUIDs appear in messages) diff --git a/tools/WinGetLogViewer/language-configuration.json b/tools/WinGetLogViewer/language-configuration.json index 4d20c18e2a..c3d73eeb65 100644 --- a/tools/WinGetLogViewer/language-configuration.json +++ b/tools/WinGetLogViewer/language-configuration.json @@ -1,6 +1,6 @@ -{ - "comments": {}, - "brackets": [], - "autoClosingPairs": [], - "surroundingPairs": [] -} +{ + "comments": {}, + "brackets": [], + "autoClosingPairs": [], + "surroundingPairs": [] +} diff --git a/tools/WinGetLogViewer/media/viewer.css b/tools/WinGetLogViewer/media/viewer.css index 42b954f0e1..9d18ba63b8 100644 --- a/tools/WinGetLogViewer/media/viewer.css +++ b/tools/WinGetLogViewer/media/viewer.css @@ -1,387 +1,387 @@ -/* ── Reset & base ──────────────────────────────────────── */ -*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } - -:root { - --font-mono: 'Cascadia Code', 'Consolas', 'Courier New', monospace; - --font-ui: var(--vscode-font-family, system-ui, sans-serif); - --font-size: var(--vscode-editor-font-size, 13px); - - --bg: var(--vscode-editor-background, #1e1e1e); - --fg: var(--vscode-editor-foreground, #d4d4d4); - --sidebar-bg: var(--vscode-sideBar-background, #252526); - --sidebar-fg: var(--vscode-sideBar-foreground, #cccccc); - --border: var(--vscode-panel-border, #444); - --input-bg: var(--vscode-input-background, #3c3c3c); - --input-fg: var(--vscode-input-foreground, #cccccc); - --input-border:var(--vscode-input-border, #555); - --btn-bg: var(--vscode-button-background, #0e639c); - --btn-fg: var(--vscode-button-foreground, #ffffff); - --hover-bg: var(--vscode-list-hoverBackground, #2a2d2e); - --select-bg: var(--vscode-list-activeSelectionBackground, #094771); - --stats-bg: var(--vscode-statusBar-background, #007acc); - --stats-fg: var(--vscode-statusBar-foreground, #ffffff); - - /* Channel colors */ - --ch-fail: #f14c4c; - --ch-cli: #4fc1ff; - --ch-sql: #c792ea; - --ch-repo: #89ddff; - --ch-yaml: #ffcb6b; - --ch-core: #858585; - --ch-test: #c3e88d; - --ch-conf: #7fdbca; - --ch-work: #f78c6c; - --ch-unknown: #aaaaaa; - - --row-height: 20px; - --sidebar-width: 210px; -} - -html, body { - height: 100%; - overflow: hidden; - background: var(--bg); - color: var(--fg); - font-family: var(--font-ui); - font-size: var(--font-size); -} - -/* ── Layout ────────────────────────────────────────────── */ -body { display: flex; height: 100vh; } - -#sidebar { - width: var(--sidebar-width); - min-width: var(--sidebar-width); - background: var(--sidebar-bg); - color: var(--sidebar-fg); - border-right: 1px solid var(--border); - overflow-y: auto; - display: flex; - flex-direction: column; - gap: 0; -} - -#main { - flex: 1; - display: flex; - flex-direction: column; - overflow: hidden; -} - -/* ── Stats bar ─────────────────────────────────────────── */ -#stats-bar { - display: flex; - align-items: center; - justify-content: space-between; - padding: 2px 10px; - background: var(--stats-bg); - color: var(--stats-fg); - font-size: 11px; - flex-shrink: 0; - user-select: none; -} -#status-indicator { - font-size: 10px; - opacity: 0.8; -} - -/* ── Log container ─────────────────────────────────────── */ -#log-container { - flex: 1; - overflow: auto; - overflow-anchor: none; /* prevent scroll anchoring from cascading when top spacer grows */ - position: relative; - font-family: var(--font-mono); - font-size: var(--font-size); -} - -#log-rows { - position: relative; -} - -/* ── Log row ───────────────────────────────────────────── */ -.log-row { - display: flex; - align-items: baseline; - min-height: var(--row-height); - padding: 1px 8px; - border-left: 3px solid transparent; - white-space: nowrap; - cursor: default; - transition: background 0.05s; -} -.log-row:hover { background: var(--hover-bg); } -.log-row.selected { background: var(--select-bg); } -.log-row.wrap-lines { white-space: pre-wrap; word-break: break-all; } - -/* Severity left-border */ -.log-row.sev-error { border-left-color: var(--ch-fail); } -.log-row.sev-warning { border-left-color: var(--ch-yaml); } - -.log-row.sev-error .log-message { color: #f88; } -.log-row.sev-warning .log-message { color: var(--ch-yaml); } -.log-row.sev-verbose .log-message { opacity: 0.55; } - -/* ── Row parts ─────────────────────────────────────────── */ -.log-timestamp { - color: #6a9955; - margin-right: 6px; - flex-shrink: 0; - font-size: 0.9em; - user-select: none; -} - -.log-delta { - font-size: 0.82em; - margin-right: 6px; - flex-shrink: 0; - min-width: 60px; - text-align: right; - user-select: none; - color: #888; /* default: under 1 second */ -} -/* 1 s – 1 min */ -.log-delta.delta-slow { color: var(--ch-yaml); font-weight: 600; } -/* over 1 min */ -.log-delta.delta-very-slow { color: var(--ch-fail); font-weight: 700; } - -.log-channel { - display: inline-block; - min-width: 52px; - padding: 0 4px; - margin-right: 6px; - border-radius: 3px; - font-size: 0.82em; - font-weight: 700; - text-align: center; - flex-shrink: 0; - user-select: none; -} - -.log-subchannel { - display: inline-block; - padding: 0 3px; - margin-right: 6px; - border-radius: 3px; - font-size: 0.78em; - font-weight: 600; - text-align: center; - flex-shrink: 0; - opacity: 0.85; - user-select: none; - border: 1px solid currentColor; -} - -.log-message { - overflow: hidden; - text-overflow: ellipsis; - flex: 1; -} -.wrap-lines .log-message { - overflow: visible; - text-overflow: unset; - white-space: pre-wrap; - word-break: break-all; -} - -/* ── Continuation rows (timestamp-less lines promoted to full rows) ── */ -.log-row.is-continuation { - border-left-color: var(--border); - padding-left: 20px; -} -.log-row.is-continuation .log-message { - opacity: 0.75; - white-space: pre; - overflow: hidden; - text-overflow: ellipsis; -} -.log-row.wrap-lines.is-continuation .log-message { - white-space: pre-wrap; - word-break: break-all; - overflow: visible; - text-overflow: unset; -} -/* ── Level badge(shown when explicit marker is present) ── */ -.log-level { - display: inline-block; - min-width: 22px; - padding: 0 3px; - margin-right: 6px; - border-radius: 3px; - font-size: 0.8em; - font-weight: 700; - text-align: center; - flex-shrink: 0; - user-select: none; -} -.lv-V { background: #4a4a4a; color: #aaa; } -.lv-I { background: #1a5276; color: #aed6f1; } -.lv-W { background: #7d6608; color: var(--ch-yaml); } -.lv-E { background: #7b241c; color: #f1948a; } -.lv-C { background: var(--ch-fail); color: #000; font-weight: 900; } - -/* ── Channel color assignments ─────────────────────────── */ -.ch-FAIL { background: var(--ch-fail); color: #000; } -.ch-CLI { background: var(--ch-cli); color: #000; } -.ch-SQL { background: var(--ch-sql); color: #000; } -.ch-REPO { background: var(--ch-repo); color: #000; } -.ch-YAML { background: var(--ch-yaml); color: #000; } -.ch-CORE { background: var(--ch-core); color: #fff; } -.ch-TEST { background: var(--ch-test); color: #000; } -.ch-CONF { background: var(--ch-conf); color: #000; } -.ch-WORK { background: var(--ch-work); color: #000; } - -/* Subchannel uses text color (no background fill) */ -.sub-FAIL { color: var(--ch-fail); } -.sub-CLI { color: var(--ch-cli); } -.sub-SQL { color: var(--ch-sql); } -.sub-REPO { color: var(--ch-repo); } -.sub-YAML { color: var(--ch-yaml); } -.sub-CORE { color: var(--ch-core); } -.sub-TEST { color: var(--ch-test); } -.sub-CONF { color: var(--ch-conf); } -.sub-WORK { color: var(--ch-work); } - -/* ── Sidebar sections ──────────────────────────────────── */ -.sidebar-section { - padding: 8px 10px; - border-bottom: 1px solid var(--border); -} - -.section-header { - display: flex; - align-items: center; - justify-content: space-between; - font-size: 0.78em; - font-weight: 700; - text-transform: uppercase; - letter-spacing: 0.06em; - opacity: 0.7; - margin-bottom: 6px; - user-select: none; -} - -.header-actions { display: flex; gap: 6px; } - -.link-btn { - background: none; - border: none; - color: var(--vscode-textLink-foreground, #4fc1ff); - cursor: pointer; - font-size: 0.9em; - padding: 0; - text-decoration: underline; -} -.link-btn:hover { opacity: 0.8; } - -.full-btn { - width: 100%; - background: var(--btn-bg); - color: var(--btn-fg); - border: none; - border-radius: 3px; - padding: 5px 8px; - cursor: pointer; - font-size: 0.88em; -} -.full-btn:hover { opacity: 0.9; } - -/* ── Search input ──────────────────────────────────────── */ -#search-input { - width: 100%; - background: var(--input-bg); - color: var(--input-fg); - border: 1px solid var(--input-border); - border-radius: 3px; - padding: 4px 6px; - font-size: 0.9em; - outline: none; -} -#search-input:focus { border-color: var(--vscode-focusBorder, #007acc); } - -/* ── Filter lists ──────────────────────────────────────── */ -.filter-list { - list-style: none; - display: flex; - flex-direction: column; - gap: 2px; -} - -.filter-item { - display: flex; - align-items: center; - gap: 6px; - font-size: 0.88em; - cursor: pointer; - padding: 2px 2px; - border-radius: 3px; -} -.filter-item:hover { background: var(--hover-bg); } - -.filter-item input[type="checkbox"] { - cursor: pointer; - flex-shrink: 0; - accent-color: var(--vscode-checkbox-background, #007acc); -} - -.filter-badge { - display: inline-block; - min-width: 44px; - padding: 1px 4px; - border-radius: 3px; - font-size: 0.8em; - font-weight: 700; - text-align: center; -} - -.filter-item .filter-count { - margin-left: auto; - font-size: 0.78em; - opacity: 0.55; -} - -/* ── Options ───────────────────────────────────────────── */ -.option-label { - display: flex; - align-items: center; - gap: 6px; - font-size: 0.88em; - cursor: pointer; - padding: 2px 0; -} - -/* ── Navigation buttons ────────────────────────────────── */ -.nav-buttons { - display: flex; - gap: 6px; -} -.nav-buttons button { - flex: 1; - background: var(--hover-bg); - color: var(--sidebar-fg); - border: 1px solid var(--border); - border-radius: 3px; - padding: 4px 4px; - cursor: pointer; - font-size: 0.82em; -} -.nav-buttons button:hover { background: var(--select-bg); color: #fff; } - -/* ── Keyword highlights inside message ─────────────────── */ -.kw-error { color: #f88; font-weight: 600; } -.kw-warning { color: var(--ch-yaml); } -.kw-hex { color: var(--ch-conf); font-family: var(--font-mono); } - -/* ── Search match highlight ────────────────────────────── */ -mark.search-match { - background: var(--vscode-editor-findMatchHighlightBackground, rgba(255, 200, 0, 0.55)); - color: inherit; - border-radius: 2px; - padding: 0 1px; - outline: 1px solid var(--vscode-editor-findMatchHighlightBorder, rgba(255, 200, 0, 0.9)); -} - -/* ── Scrollbar ─────────────────────────────────────────── */ -#log-container::-webkit-scrollbar { width: 8px; height: 8px; } -#log-container::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } -#log-container::-webkit-scrollbar-track { background: transparent; } +/* ── Reset & base ──────────────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } + +:root { + --font-mono: 'Cascadia Code', 'Consolas', 'Courier New', monospace; + --font-ui: var(--vscode-font-family, system-ui, sans-serif); + --font-size: var(--vscode-editor-font-size, 13px); + + --bg: var(--vscode-editor-background, #1e1e1e); + --fg: var(--vscode-editor-foreground, #d4d4d4); + --sidebar-bg: var(--vscode-sideBar-background, #252526); + --sidebar-fg: var(--vscode-sideBar-foreground, #cccccc); + --border: var(--vscode-panel-border, #444); + --input-bg: var(--vscode-input-background, #3c3c3c); + --input-fg: var(--vscode-input-foreground, #cccccc); + --input-border:var(--vscode-input-border, #555); + --btn-bg: var(--vscode-button-background, #0e639c); + --btn-fg: var(--vscode-button-foreground, #ffffff); + --hover-bg: var(--vscode-list-hoverBackground, #2a2d2e); + --select-bg: var(--vscode-list-activeSelectionBackground, #094771); + --stats-bg: var(--vscode-statusBar-background, #007acc); + --stats-fg: var(--vscode-statusBar-foreground, #ffffff); + + /* Channel colors */ + --ch-fail: #f14c4c; + --ch-cli: #4fc1ff; + --ch-sql: #c792ea; + --ch-repo: #89ddff; + --ch-yaml: #ffcb6b; + --ch-core: #858585; + --ch-test: #c3e88d; + --ch-conf: #7fdbca; + --ch-work: #f78c6c; + --ch-unknown: #aaaaaa; + + --row-height: 20px; + --sidebar-width: 210px; +} + +html, body { + height: 100%; + overflow: hidden; + background: var(--bg); + color: var(--fg); + font-family: var(--font-ui); + font-size: var(--font-size); +} + +/* ── Layout ────────────────────────────────────────────── */ +body { display: flex; height: 100vh; } + +#sidebar { + width: var(--sidebar-width); + min-width: var(--sidebar-width); + background: var(--sidebar-bg); + color: var(--sidebar-fg); + border-right: 1px solid var(--border); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: 0; +} + +#main { + flex: 1; + display: flex; + flex-direction: column; + overflow: hidden; +} + +/* ── Stats bar ─────────────────────────────────────────── */ +#stats-bar { + display: flex; + align-items: center; + justify-content: space-between; + padding: 2px 10px; + background: var(--stats-bg); + color: var(--stats-fg); + font-size: 11px; + flex-shrink: 0; + user-select: none; +} +#status-indicator { + font-size: 10px; + opacity: 0.8; +} + +/* ── Log container ─────────────────────────────────────── */ +#log-container { + flex: 1; + overflow: auto; + overflow-anchor: none; /* prevent scroll anchoring from cascading when top spacer grows */ + position: relative; + font-family: var(--font-mono); + font-size: var(--font-size); +} + +#log-rows { + position: relative; +} + +/* ── Log row ───────────────────────────────────────────── */ +.log-row { + display: flex; + align-items: baseline; + min-height: var(--row-height); + padding: 1px 8px; + border-left: 3px solid transparent; + white-space: nowrap; + cursor: default; + transition: background 0.05s; +} +.log-row:hover { background: var(--hover-bg); } +.log-row.selected { background: var(--select-bg); } +.log-row.wrap-lines { white-space: pre-wrap; word-break: break-all; } + +/* Severity left-border */ +.log-row.sev-error { border-left-color: var(--ch-fail); } +.log-row.sev-warning { border-left-color: var(--ch-yaml); } + +.log-row.sev-error .log-message { color: #f88; } +.log-row.sev-warning .log-message { color: var(--ch-yaml); } +.log-row.sev-verbose .log-message { opacity: 0.55; } + +/* ── Row parts ─────────────────────────────────────────── */ +.log-timestamp { + color: #6a9955; + margin-right: 6px; + flex-shrink: 0; + font-size: 0.9em; + user-select: none; +} + +.log-delta { + font-size: 0.82em; + margin-right: 6px; + flex-shrink: 0; + min-width: 60px; + text-align: right; + user-select: none; + color: #888; /* default: under 1 second */ +} +/* 1 s – 1 min */ +.log-delta.delta-slow { color: var(--ch-yaml); font-weight: 600; } +/* over 1 min */ +.log-delta.delta-very-slow { color: var(--ch-fail); font-weight: 700; } + +.log-channel { + display: inline-block; + min-width: 52px; + padding: 0 4px; + margin-right: 6px; + border-radius: 3px; + font-size: 0.82em; + font-weight: 700; + text-align: center; + flex-shrink: 0; + user-select: none; +} + +.log-subchannel { + display: inline-block; + padding: 0 3px; + margin-right: 6px; + border-radius: 3px; + font-size: 0.78em; + font-weight: 600; + text-align: center; + flex-shrink: 0; + opacity: 0.85; + user-select: none; + border: 1px solid currentColor; +} + +.log-message { + overflow: hidden; + text-overflow: ellipsis; + flex: 1; +} +.wrap-lines .log-message { + overflow: visible; + text-overflow: unset; + white-space: pre-wrap; + word-break: break-all; +} + +/* ── Continuation rows (timestamp-less lines promoted to full rows) ── */ +.log-row.is-continuation { + border-left-color: var(--border); + padding-left: 20px; +} +.log-row.is-continuation .log-message { + opacity: 0.75; + white-space: pre; + overflow: hidden; + text-overflow: ellipsis; +} +.log-row.wrap-lines.is-continuation .log-message { + white-space: pre-wrap; + word-break: break-all; + overflow: visible; + text-overflow: unset; +} +/* ── Level badge(shown when explicit marker is present) ── */ +.log-level { + display: inline-block; + min-width: 22px; + padding: 0 3px; + margin-right: 6px; + border-radius: 3px; + font-size: 0.8em; + font-weight: 700; + text-align: center; + flex-shrink: 0; + user-select: none; +} +.lv-V { background: #4a4a4a; color: #aaa; } +.lv-I { background: #1a5276; color: #aed6f1; } +.lv-W { background: #7d6608; color: var(--ch-yaml); } +.lv-E { background: #7b241c; color: #f1948a; } +.lv-C { background: var(--ch-fail); color: #000; font-weight: 900; } + +/* ── Channel color assignments ─────────────────────────── */ +.ch-FAIL { background: var(--ch-fail); color: #000; } +.ch-CLI { background: var(--ch-cli); color: #000; } +.ch-SQL { background: var(--ch-sql); color: #000; } +.ch-REPO { background: var(--ch-repo); color: #000; } +.ch-YAML { background: var(--ch-yaml); color: #000; } +.ch-CORE { background: var(--ch-core); color: #fff; } +.ch-TEST { background: var(--ch-test); color: #000; } +.ch-CONF { background: var(--ch-conf); color: #000; } +.ch-WORK { background: var(--ch-work); color: #000; } + +/* Subchannel uses text color (no background fill) */ +.sub-FAIL { color: var(--ch-fail); } +.sub-CLI { color: var(--ch-cli); } +.sub-SQL { color: var(--ch-sql); } +.sub-REPO { color: var(--ch-repo); } +.sub-YAML { color: var(--ch-yaml); } +.sub-CORE { color: var(--ch-core); } +.sub-TEST { color: var(--ch-test); } +.sub-CONF { color: var(--ch-conf); } +.sub-WORK { color: var(--ch-work); } + +/* ── Sidebar sections ──────────────────────────────────── */ +.sidebar-section { + padding: 8px 10px; + border-bottom: 1px solid var(--border); +} + +.section-header { + display: flex; + align-items: center; + justify-content: space-between; + font-size: 0.78em; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.06em; + opacity: 0.7; + margin-bottom: 6px; + user-select: none; +} + +.header-actions { display: flex; gap: 6px; } + +.link-btn { + background: none; + border: none; + color: var(--vscode-textLink-foreground, #4fc1ff); + cursor: pointer; + font-size: 0.9em; + padding: 0; + text-decoration: underline; +} +.link-btn:hover { opacity: 0.8; } + +.full-btn { + width: 100%; + background: var(--btn-bg); + color: var(--btn-fg); + border: none; + border-radius: 3px; + padding: 5px 8px; + cursor: pointer; + font-size: 0.88em; +} +.full-btn:hover { opacity: 0.9; } + +/* ── Search input ──────────────────────────────────────── */ +#search-input { + width: 100%; + background: var(--input-bg); + color: var(--input-fg); + border: 1px solid var(--input-border); + border-radius: 3px; + padding: 4px 6px; + font-size: 0.9em; + outline: none; +} +#search-input:focus { border-color: var(--vscode-focusBorder, #007acc); } + +/* ── Filter lists ──────────────────────────────────────── */ +.filter-list { + list-style: none; + display: flex; + flex-direction: column; + gap: 2px; +} + +.filter-item { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.88em; + cursor: pointer; + padding: 2px 2px; + border-radius: 3px; +} +.filter-item:hover { background: var(--hover-bg); } + +.filter-item input[type="checkbox"] { + cursor: pointer; + flex-shrink: 0; + accent-color: var(--vscode-checkbox-background, #007acc); +} + +.filter-badge { + display: inline-block; + min-width: 44px; + padding: 1px 4px; + border-radius: 3px; + font-size: 0.8em; + font-weight: 700; + text-align: center; +} + +.filter-item .filter-count { + margin-left: auto; + font-size: 0.78em; + opacity: 0.55; +} + +/* ── Options ───────────────────────────────────────────── */ +.option-label { + display: flex; + align-items: center; + gap: 6px; + font-size: 0.88em; + cursor: pointer; + padding: 2px 0; +} + +/* ── Navigation buttons ────────────────────────────────── */ +.nav-buttons { + display: flex; + gap: 6px; +} +.nav-buttons button { + flex: 1; + background: var(--hover-bg); + color: var(--sidebar-fg); + border: 1px solid var(--border); + border-radius: 3px; + padding: 4px 4px; + cursor: pointer; + font-size: 0.82em; +} +.nav-buttons button:hover { background: var(--select-bg); color: #fff; } + +/* ── Keyword highlights inside message ─────────────────── */ +.kw-error { color: #f88; font-weight: 600; } +.kw-warning { color: var(--ch-yaml); } +.kw-hex { color: var(--ch-conf); font-family: var(--font-mono); } + +/* ── Search match highlight ────────────────────────────── */ +mark.search-match { + background: var(--vscode-editor-findMatchHighlightBackground, rgba(255, 200, 0, 0.55)); + color: inherit; + border-radius: 2px; + padding: 0 1px; + outline: 1px solid var(--vscode-editor-findMatchHighlightBorder, rgba(255, 200, 0, 0.9)); +} + +/* ── Scrollbar ─────────────────────────────────────────── */ +#log-container::-webkit-scrollbar { width: 8px; height: 8px; } +#log-container::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; } +#log-container::-webkit-scrollbar-track { background: transparent; } diff --git a/tools/WinGetLogViewer/media/viewer.html b/tools/WinGetLogViewer/media/viewer.html index c871579376..5374a39058 100644 --- a/tools/WinGetLogViewer/media/viewer.html +++ b/tools/WinGetLogViewer/media/viewer.html @@ -1,100 +1,100 @@ - - - - - - - - WinGet Log Viewer - - - - - - -
-
- Loading… - -
-
- -
-
-
-
-
- - - - + + + + + + + + WinGet Log Viewer + + + + + + +
+
+ Loading… + +
+
+ +
+
+
+
+
+ + + + diff --git a/tools/WinGetLogViewer/package.json b/tools/WinGetLogViewer/package.json index 34a6dc6e15..eb4fb25959 100644 --- a/tools/WinGetLogViewer/package.json +++ b/tools/WinGetLogViewer/package.json @@ -1,94 +1,94 @@ -{ - "name": "winget-log-viewer", - "displayName": "WinGet Log Viewer", - "description": "Rich viewer for WinGet diagnostic log files with channel filtering, highlighting, and live follow mode.", - "version": "0.1.0", - "publisher": "microsoft", - "repository": { - "type": "git", - "url": "https://github.com/microsoft/winget-cli" - }, - "license": "MIT", - "engines": { - "vscode": "^1.85.0" - }, - "categories": [ - "Other" - ], - "activationEvents": [], - "main": "./out/extension.js", - "contributes": { - "customEditors": [ - { - "viewType": "wingetLogViewer.logViewer", - "displayName": "WinGet Log Viewer", - "selector": [ - { - "filenamePattern": "WinGet-*.log" - }, - { - "filenamePattern": "WinGetCOM-*.log" - } - ], - "priority": "default" - } - ], - "commands": [ - { - "command": "wingetLogViewer.open", - "title": "Open in WinGet Log Viewer", - "category": "WinGet Log Viewer" - } - ], - "languages": [ - { - "id": "winget-log", - "aliases": [ - "WinGet Log" - ], - "filenamePatterns": [ - "WinGet-*.log", - "WinGetCOM-*.log" - ], - "configuration": "./language-configuration.json" - } - ], - "grammars": [ - { - "language": "winget-log", - "scopeName": "text.winget-log", - "path": "./syntaxes/winget-log.tmLanguage.json" - } - ], - "menus": { - "explorer/context": [ - { - "command": "wingetLogViewer.open", - "when": "resourceExtname == .log", - "group": "navigation" - } - ], - "editor/title": [ - { - "command": "wingetLogViewer.open", - "when": "resourceExtname == .log && activeCustomEditorId != 'wingetLogViewer.logViewer'", - "group": "navigation" - } - ] - } - }, - "scripts": { - "vscode:prepublish": "npm run compile", - "compile": "tsc -p ./", - "watch": "tsc -watch -p ./", - "pretest": "npm run compile", - "lint": "eslint src --ext ts", - "package": "vsce package" - }, - "devDependencies": { - "@types/vscode": "^1.85.0", - "@types/node": "20.x", - "@vscode/vsce": "^3.0.0", - "typescript": "^5.3.0" - } -} +{ + "name": "winget-log-viewer", + "displayName": "WinGet Log Viewer", + "description": "Rich viewer for WinGet diagnostic log files with channel filtering, highlighting, and live follow mode.", + "version": "0.1.0", + "publisher": "microsoft", + "repository": { + "type": "git", + "url": "https://github.com/microsoft/winget-cli" + }, + "license": "MIT", + "engines": { + "vscode": "^1.85.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [], + "main": "./out/extension.js", + "contributes": { + "customEditors": [ + { + "viewType": "wingetLogViewer.logViewer", + "displayName": "WinGet Log Viewer", + "selector": [ + { + "filenamePattern": "WinGet-*.log" + }, + { + "filenamePattern": "WinGetCOM-*.log" + } + ], + "priority": "default" + } + ], + "commands": [ + { + "command": "wingetLogViewer.open", + "title": "Open in WinGet Log Viewer", + "category": "WinGet Log Viewer" + } + ], + "languages": [ + { + "id": "winget-log", + "aliases": [ + "WinGet Log" + ], + "filenamePatterns": [ + "WinGet-*.log", + "WinGetCOM-*.log" + ], + "configuration": "./language-configuration.json" + } + ], + "grammars": [ + { + "language": "winget-log", + "scopeName": "text.winget-log", + "path": "./syntaxes/winget-log.tmLanguage.json" + } + ], + "menus": { + "explorer/context": [ + { + "command": "wingetLogViewer.open", + "when": "resourceExtname == .log", + "group": "navigation" + } + ], + "editor/title": [ + { + "command": "wingetLogViewer.open", + "when": "resourceExtname == .log && activeCustomEditorId != 'wingetLogViewer.logViewer'", + "group": "navigation" + } + ] + } + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "pretest": "npm run compile", + "lint": "eslint src --ext ts", + "package": "vsce package" + }, + "devDependencies": { + "@types/vscode": "^1.85.0", + "@types/node": "20.x", + "@vscode/vsce": "^3.0.0", + "typescript": "^5.3.0" + } +} diff --git a/tools/WinGetLogViewer/src/extension.ts b/tools/WinGetLogViewer/src/extension.ts index a57d751782..519150fa84 100644 --- a/tools/WinGetLogViewer/src/extension.ts +++ b/tools/WinGetLogViewer/src/extension.ts @@ -1,41 +1,41 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import * as vscode from 'vscode'; -import { LogViewerProvider } from './logViewerProvider'; - -export function activate(context: vscode.ExtensionContext): void { - const provider = new LogViewerProvider(context); - - // Register the custom editor for WinGet-*.log and WinGetCOM-*.log - context.subscriptions.push( - vscode.window.registerCustomEditorProvider( - LogViewerProvider.viewType, - provider, - { - webviewOptions: { - retainContextWhenHidden: true, - }, - supportsMultipleEditorsPerDocument: false, - }, - ), - ); - - // "Open in WinGet Log Viewer" command — works on any .log file - context.subscriptions.push( - vscode.commands.registerCommand('wingetLogViewer.open', (uri?: vscode.Uri) => { - // When invoked from the editor title or context menu, uri is provided. - // When invoked from the command palette, use the active editor's document. - const target = uri ?? vscode.window.activeTextEditor?.document.uri; - if (!target) { - vscode.window.showErrorMessage('WinGet Log Viewer: no file to open. Open a log file first.'); - return; - } - provider.openFile(target); - }), - ); -} - -export function deactivate(): void { - // Nothing to clean up; subscriptions are disposed automatically. -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; +import { LogViewerProvider } from './logViewerProvider'; + +export function activate(context: vscode.ExtensionContext): void { + const provider = new LogViewerProvider(context); + + // Register the custom editor for WinGet-*.log and WinGetCOM-*.log + context.subscriptions.push( + vscode.window.registerCustomEditorProvider( + LogViewerProvider.viewType, + provider, + { + webviewOptions: { + retainContextWhenHidden: true, + }, + supportsMultipleEditorsPerDocument: false, + }, + ), + ); + + // "Open in WinGet Log Viewer" command — works on any .log file + context.subscriptions.push( + vscode.commands.registerCommand('wingetLogViewer.open', (uri?: vscode.Uri) => { + // When invoked from the editor title or context menu, uri is provided. + // When invoked from the command palette, use the active editor's document. + const target = uri ?? vscode.window.activeTextEditor?.document.uri; + if (!target) { + vscode.window.showErrorMessage('WinGet Log Viewer: no file to open. Open a log file first.'); + return; + } + provider.openFile(target); + }), + ); +} + +export function deactivate(): void { + // Nothing to clean up; subscriptions are disposed automatically. +} diff --git a/tools/WinGetLogViewer/src/logParser.ts b/tools/WinGetLogViewer/src/logParser.ts index 9604ce1295..e14750e91b 100644 --- a/tools/WinGetLogViewer/src/logParser.ts +++ b/tools/WinGetLogViewer/src/logParser.ts @@ -1,144 +1,144 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -/** - * A parsed WinGet log entry. - * - * Log line format (new): - * YYYY-MM-DD HH:MM:SS.mmm [CHAN] [SUBCHAN] message - * - * Log line format (old, without level marker): - * YYYY-MM-DD HH:MM:SS.mmm [CHAN] [SUBCHAN] message - * - * The subchannel is optional and appears when a sub-component routes its logs - * through a parent channel. In that case the original channel tag is embedded - * as the first token of the message text. - */ -export interface LogEntry { - /** 0-based index in the original file. */ - lineIndex: number; - /** Raw, unmodified primary log line. */ - raw: string; - /** Timestamp string as it appears in the file, e.g. "2026-04-14 22:39:42.041". */ - timestamp: string; - /** Explicit level char from marker (V/I/W/E/C), or undefined for old-format logs. */ - levelChar?: LevelChar; - /** Primary channel name, e.g. "CLI", "CORE", "FAIL". */ - channel: string; - /** Optional subchannel detected from the message prefix, e.g. "REPO". */ - subchannel?: string; - /** Message text after channel (and optional subchannel) have been stripped. */ - message: string; - /** Severity derived from the explicit level marker when present. */ - severity: Severity; - /** - * Raw text of any lines that followed this entry without a timestamp of their own. - * These are continuation lines (e.g. multi-line output, stack traces) that belong - * to this log entry. - */ - continuationLines?: string[]; -} - -export type LevelChar = 'V' | 'I' | 'W' | 'E' | 'C'; -export type Severity = 'verbose' | 'info' | 'warning' | 'error' | 'crit'; - -const LEVEL_CHAR_TO_SEVERITY: Record = { - V: 'verbose', - I: 'info', - W: 'warning', - E: 'error', - C: 'crit', -}; - -/** All known WinGet primary channels in display order. */ -export const KNOWN_CHANNELS: readonly string[] = [ - 'FAIL', 'CLI', 'SQL', 'REPO', 'YAML', 'CORE', 'TEST', 'CONF', 'WORK', -]; - -/** All level chars in severity order. */ -export const LEVEL_CHARS: readonly LevelChar[] = ['V', 'I', 'W', 'E', 'C']; - -// YYYY-MM-DD HH:MM:SS.mmm [optional: ] [CHAN(+spaces)] rest-of-line -// Year may be 2 or 4 digits for forward/backward compat. -const LOG_LINE_RE = /^(\d{2,4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(?:<([VIWEC])>\s+)?\[([A-Z]{2,8})\s*\]\s?(.*)$/; - -// Optional subchannel at the very start of the message: [CHAN] or [CHAN ] … -const SUBCHANNEL_RE = /^\[([A-Z]{2,8})\s*\]\s?(.*)$/; - -/** - * Parse a raw log text (full file content) into an array of LogEntry objects. - * Lines that do not match the expected format are skipped. - */ -export function parseLog(text: string): LogEntry[] { - const lines = text.split(/\r?\n/); - // Drop trailing empty lines (standard file ending or blank lines at EOF). - while (lines.length > 0 && lines[lines.length - 1] === '') { lines.pop(); } - const entries: LogEntry[] = []; - - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const m = LOG_LINE_RE.exec(line); - if (!m) { - // Continuation line — attach to the most recent entry. - if (entries.length > 0) { - const last = entries[entries.length - 1]; - (last.continuationLines ??= []).push(line); - } - continue; - } - - const [, timestamp, rawLevel, channel, rest] = m; - const levelChar = rawLevel as LevelChar | undefined; - - let subchannel: string | undefined; - let message = rest; - - const sub = SUBCHANNEL_RE.exec(rest); - if (sub) { - subchannel = sub[1]; - message = sub[2]; - } - - const severity: Severity = levelChar - ? LEVEL_CHAR_TO_SEVERITY[levelChar] - : 'info'; - - entries.push({ - lineIndex: i, - raw: line, - timestamp, - levelChar, - channel, - subchannel, - message, - severity, - }); - } - - return entries; -} - -/** Collect the unique set of channels present in the given entries, preserving known order. */ -export function collectChannels(entries: LogEntry[]): string[] { - const found = new Set(entries.map(e => e.channel)); - const ordered = KNOWN_CHANNELS.filter(c => found.has(c)); - // Append any unexpected channel names after the known ones. - for (const c of found) { - if (!KNOWN_CHANNELS.includes(c)) { ordered.push(c); } - } - return ordered; -} - -/** Collect the unique set of subchannels present in the given entries, sorted. */ -export function collectSubchannels(entries: LogEntry[]): string[] { - const found = new Set(); - for (const e of entries) { - if (e.subchannel) { found.add(e.subchannel); } - } - return [...found].sort(); -} - -/** Returns true if the log file contains any entries with an explicit level marker. */ -export function hasExplicitLevels(entries: LogEntry[]): boolean { - return entries.some(e => e.levelChar !== undefined); -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * A parsed WinGet log entry. + * + * Log line format (new): + * YYYY-MM-DD HH:MM:SS.mmm [CHAN] [SUBCHAN] message + * + * Log line format (old, without level marker): + * YYYY-MM-DD HH:MM:SS.mmm [CHAN] [SUBCHAN] message + * + * The subchannel is optional and appears when a sub-component routes its logs + * through a parent channel. In that case the original channel tag is embedded + * as the first token of the message text. + */ +export interface LogEntry { + /** 0-based index in the original file. */ + lineIndex: number; + /** Raw, unmodified primary log line. */ + raw: string; + /** Timestamp string as it appears in the file, e.g. "2026-04-14 22:39:42.041". */ + timestamp: string; + /** Explicit level char from marker (V/I/W/E/C), or undefined for old-format logs. */ + levelChar?: LevelChar; + /** Primary channel name, e.g. "CLI", "CORE", "FAIL". */ + channel: string; + /** Optional subchannel detected from the message prefix, e.g. "REPO". */ + subchannel?: string; + /** Message text after channel (and optional subchannel) have been stripped. */ + message: string; + /** Severity derived from the explicit level marker when present. */ + severity: Severity; + /** + * Raw text of any lines that followed this entry without a timestamp of their own. + * These are continuation lines (e.g. multi-line output, stack traces) that belong + * to this log entry. + */ + continuationLines?: string[]; +} + +export type LevelChar = 'V' | 'I' | 'W' | 'E' | 'C'; +export type Severity = 'verbose' | 'info' | 'warning' | 'error' | 'crit'; + +const LEVEL_CHAR_TO_SEVERITY: Record = { + V: 'verbose', + I: 'info', + W: 'warning', + E: 'error', + C: 'crit', +}; + +/** All known WinGet primary channels in display order. */ +export const KNOWN_CHANNELS: readonly string[] = [ + 'FAIL', 'CLI', 'SQL', 'REPO', 'YAML', 'CORE', 'TEST', 'CONF', 'WORK', +]; + +/** All level chars in severity order. */ +export const LEVEL_CHARS: readonly LevelChar[] = ['V', 'I', 'W', 'E', 'C']; + +// YYYY-MM-DD HH:MM:SS.mmm [optional: ] [CHAN(+spaces)] rest-of-line +// Year may be 2 or 4 digits for forward/backward compat. +const LOG_LINE_RE = /^(\d{2,4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\s+(?:<([VIWEC])>\s+)?\[([A-Z]{2,8})\s*\]\s?(.*)$/; + +// Optional subchannel at the very start of the message: [CHAN] or [CHAN ] … +const SUBCHANNEL_RE = /^\[([A-Z]{2,8})\s*\]\s?(.*)$/; + +/** + * Parse a raw log text (full file content) into an array of LogEntry objects. + * Lines that do not match the expected format are skipped. + */ +export function parseLog(text: string): LogEntry[] { + const lines = text.split(/\r?\n/); + // Drop trailing empty lines (standard file ending or blank lines at EOF). + while (lines.length > 0 && lines[lines.length - 1] === '') { lines.pop(); } + const entries: LogEntry[] = []; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const m = LOG_LINE_RE.exec(line); + if (!m) { + // Continuation line — attach to the most recent entry. + if (entries.length > 0) { + const last = entries[entries.length - 1]; + (last.continuationLines ??= []).push(line); + } + continue; + } + + const [, timestamp, rawLevel, channel, rest] = m; + const levelChar = rawLevel as LevelChar | undefined; + + let subchannel: string | undefined; + let message = rest; + + const sub = SUBCHANNEL_RE.exec(rest); + if (sub) { + subchannel = sub[1]; + message = sub[2]; + } + + const severity: Severity = levelChar + ? LEVEL_CHAR_TO_SEVERITY[levelChar] + : 'info'; + + entries.push({ + lineIndex: i, + raw: line, + timestamp, + levelChar, + channel, + subchannel, + message, + severity, + }); + } + + return entries; +} + +/** Collect the unique set of channels present in the given entries, preserving known order. */ +export function collectChannels(entries: LogEntry[]): string[] { + const found = new Set(entries.map(e => e.channel)); + const ordered = KNOWN_CHANNELS.filter(c => found.has(c)); + // Append any unexpected channel names after the known ones. + for (const c of found) { + if (!KNOWN_CHANNELS.includes(c)) { ordered.push(c); } + } + return ordered; +} + +/** Collect the unique set of subchannels present in the given entries, sorted. */ +export function collectSubchannels(entries: LogEntry[]): string[] { + const found = new Set(); + for (const e of entries) { + if (e.subchannel) { found.add(e.subchannel); } + } + return [...found].sort(); +} + +/** Returns true if the log file contains any entries with an explicit level marker. */ +export function hasExplicitLevels(entries: LogEntry[]): boolean { + return entries.some(e => e.levelChar !== undefined); +} diff --git a/tools/WinGetLogViewer/src/logViewerProvider.ts b/tools/WinGetLogViewer/src/logViewerProvider.ts index ecf820b86c..ea4e5577aa 100644 --- a/tools/WinGetLogViewer/src/logViewerProvider.ts +++ b/tools/WinGetLogViewer/src/logViewerProvider.ts @@ -1,197 +1,197 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT License. - -import * as vscode from 'vscode'; -import * as path from 'path'; -import * as fs from 'fs'; -import { parseLog, collectChannels, collectSubchannels, hasExplicitLevels } from './logParser'; - -/** - * CustomReadonlyEditorProvider that renders WinGet log files in a rich WebView. - */ -export class LogViewerProvider implements vscode.CustomReadonlyEditorProvider { - - public static readonly viewType = 'wingetLogViewer.logViewer'; - - /** Track open panels so they can be updated by the follow-mode watcher. */ - private readonly _panels = new Map(); - - constructor(private readonly _context: vscode.ExtensionContext) {} - - // ── CustomReadonlyEditorProvider ──────────────────────────────── - - async openCustomDocument( - uri: vscode.Uri, - ): Promise { - return { uri, dispose: () => {} }; - } - - async resolveCustomEditor( - document: vscode.CustomDocument, - webviewPanel: vscode.WebviewPanel, - ): Promise { - const filePath = document.uri.fsPath; - - webviewPanel.webview.options = { - enableScripts: true, - localResourceRoots: [ - vscode.Uri.file(path.join(this._context.extensionPath, 'media')), - ], - }; - - webviewPanel.webview.html = this._buildHtml(webviewPanel.webview); - - // Send initial data once the webview is ready. - const sendLog = () => { - try { - const raw = fs.readFileSync(filePath, 'utf8'); - const entries = parseLog(raw); - webviewPanel.webview.postMessage({ - type: 'load', - entries, - channels: collectChannels(entries), - subchannels: collectSubchannels(entries), - hasLevels: hasExplicitLevels(entries), - }); - } catch (err) { - vscode.window.showErrorMessage(`WinGet Log Viewer: failed to read ${filePath}: ${err}`); - } - }; - - webviewPanel.webview.onDidReceiveMessage(msg => { - if (msg.type === 'ready') { - sendLog(); - } else if (msg.type === 'export') { - vscode.env.clipboard.writeText(msg.text).then(() => { - const lineCount = (msg.text as string).split('\n').length; - vscode.window.showInformationMessage(`WinGet Log Viewer: ${lineCount} line(s) copied to clipboard.`); - }); - } - }); - - // Follow mode: poll for file changes using fs.watchFile (polling is reliable even when - // another process holds the file open, unlike ReadDirectoryChangesW-based watchers). - let lastMtime = 0; - const sendUpdate = () => { - try { - const raw = fs.readFileSync(filePath, 'utf8'); - const entries = parseLog(raw); - webviewPanel.webview.postMessage({ type: 'update', entries }); - } catch { /* ignore read errors during live tail */ } - }; - - fs.watchFile(filePath, { interval: 500, persistent: false }, (curr, prev) => { - if (curr.mtimeMs !== prev.mtimeMs) { - sendUpdate(); - } - }); - - const key = filePath; - this._panels.set(key, { panel: webviewPanel }); - webviewPanel.onDidDispose(() => { - fs.unwatchFile(filePath); - this._panels.delete(key); - }); - } - - // ── Public: open any log file in a new panel ───────────────────── - - /** - * Open a specific file URI in the viewer (used by the "Open in WinGet Log Viewer" command). - */ - public openFile(uri: vscode.Uri): void { - const filePath = uri.fsPath; - const fileName = path.basename(filePath); - - const panel = vscode.window.createWebviewPanel( - LogViewerProvider.viewType, - fileName, - vscode.ViewColumn.Active, - { - enableScripts: true, - localResourceRoots: [ - vscode.Uri.file(path.join(this._context.extensionPath, 'media')), - ], - retainContextWhenHidden: true, - }, - ); - - panel.webview.html = this._buildHtml(panel.webview); - - const sendLoad = () => { - try { - const raw = fs.readFileSync(filePath, 'utf8'); - const entries = parseLog(raw); - panel.webview.postMessage({ - type: 'load', - entries, - channels: collectChannels(entries), - subchannels: collectSubchannels(entries), - hasLevels: hasExplicitLevels(entries), - }); - } catch (err) { - vscode.window.showErrorMessage(`WinGet Log Viewer: failed to read ${filePath}: ${err}`); - } - }; - - panel.webview.onDidReceiveMessage(msg => { - if (msg.type === 'ready') { - sendLoad(); - } else if (msg.type === 'export') { - vscode.env.clipboard.writeText(msg.text).then(() => { - const lineCount = (msg.text as string).split('\n').length; - vscode.window.showInformationMessage(`WinGet Log Viewer: ${lineCount} line(s) copied to clipboard.`); - }); - } - }); - - fs.watchFile(filePath, { interval: 500, persistent: false }, (curr, prev) => { - if (curr.mtimeMs !== prev.mtimeMs) { - try { - const raw = fs.readFileSync(filePath, 'utf8'); - const entries = parseLog(raw); - panel.webview.postMessage({ type: 'update', entries }); - } catch { /* ignore */ } - } - }); - - const key = filePath + '#manual'; - this._panels.set(key, { panel }); - panel.onDidDispose(() => { - fs.unwatchFile(filePath); - this._panels.delete(key); - }); - } - - // ── HTML builder ───────────────────────────────────────────────── - - private _buildHtml(webview: vscode.Webview): string { - const nonce = this._getNonce(); - const cspSource = webview.cspSource; - - const cssUri = webview.asWebviewUri( - vscode.Uri.file(path.join(this._context.extensionPath, 'media', 'viewer.css')), - ); - const jsUri = webview.asWebviewUri( - vscode.Uri.file(path.join(this._context.extensionPath, 'media', 'viewer.js')), - ); - - const templatePath = path.join(this._context.extensionPath, 'media', 'viewer.html'); - let html = fs.readFileSync(templatePath, 'utf8'); - html = html - .replace(/\{\{nonce\}\}/g, nonce) - .replace(/\{\{cspSource\}\}/g, cspSource) - .replace(/\{\{viewerCssUri\}\}/g, cssUri.toString()) - .replace(/\{\{viewerJsUri\}\}/g, jsUri.toString()); - return html; - } - - private _getNonce(): string { - let text = ''; - const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - for (let i = 0; i < 32; i++) { - text += possible.charAt(Math.floor(Math.random() * possible.length)); - } - return text; - } -} +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +import * as vscode from 'vscode'; +import * as path from 'path'; +import * as fs from 'fs'; +import { parseLog, collectChannels, collectSubchannels, hasExplicitLevels } from './logParser'; + +/** + * CustomReadonlyEditorProvider that renders WinGet log files in a rich WebView. + */ +export class LogViewerProvider implements vscode.CustomReadonlyEditorProvider { + + public static readonly viewType = 'wingetLogViewer.logViewer'; + + /** Track open panels so they can be updated by the follow-mode watcher. */ + private readonly _panels = new Map(); + + constructor(private readonly _context: vscode.ExtensionContext) {} + + // ── CustomReadonlyEditorProvider ──────────────────────────────── + + async openCustomDocument( + uri: vscode.Uri, + ): Promise { + return { uri, dispose: () => {} }; + } + + async resolveCustomEditor( + document: vscode.CustomDocument, + webviewPanel: vscode.WebviewPanel, + ): Promise { + const filePath = document.uri.fsPath; + + webviewPanel.webview.options = { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.file(path.join(this._context.extensionPath, 'media')), + ], + }; + + webviewPanel.webview.html = this._buildHtml(webviewPanel.webview); + + // Send initial data once the webview is ready. + const sendLog = () => { + try { + const raw = fs.readFileSync(filePath, 'utf8'); + const entries = parseLog(raw); + webviewPanel.webview.postMessage({ + type: 'load', + entries, + channels: collectChannels(entries), + subchannels: collectSubchannels(entries), + hasLevels: hasExplicitLevels(entries), + }); + } catch (err) { + vscode.window.showErrorMessage(`WinGet Log Viewer: failed to read ${filePath}: ${err}`); + } + }; + + webviewPanel.webview.onDidReceiveMessage(msg => { + if (msg.type === 'ready') { + sendLog(); + } else if (msg.type === 'export') { + vscode.env.clipboard.writeText(msg.text).then(() => { + const lineCount = (msg.text as string).split('\n').length; + vscode.window.showInformationMessage(`WinGet Log Viewer: ${lineCount} line(s) copied to clipboard.`); + }); + } + }); + + // Follow mode: poll for file changes using fs.watchFile (polling is reliable even when + // another process holds the file open, unlike ReadDirectoryChangesW-based watchers). + let lastMtime = 0; + const sendUpdate = () => { + try { + const raw = fs.readFileSync(filePath, 'utf8'); + const entries = parseLog(raw); + webviewPanel.webview.postMessage({ type: 'update', entries }); + } catch { /* ignore read errors during live tail */ } + }; + + fs.watchFile(filePath, { interval: 500, persistent: false }, (curr, prev) => { + if (curr.mtimeMs !== prev.mtimeMs) { + sendUpdate(); + } + }); + + const key = filePath; + this._panels.set(key, { panel: webviewPanel }); + webviewPanel.onDidDispose(() => { + fs.unwatchFile(filePath); + this._panels.delete(key); + }); + } + + // ── Public: open any log file in a new panel ───────────────────── + + /** + * Open a specific file URI in the viewer (used by the "Open in WinGet Log Viewer" command). + */ + public openFile(uri: vscode.Uri): void { + const filePath = uri.fsPath; + const fileName = path.basename(filePath); + + const panel = vscode.window.createWebviewPanel( + LogViewerProvider.viewType, + fileName, + vscode.ViewColumn.Active, + { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.file(path.join(this._context.extensionPath, 'media')), + ], + retainContextWhenHidden: true, + }, + ); + + panel.webview.html = this._buildHtml(panel.webview); + + const sendLoad = () => { + try { + const raw = fs.readFileSync(filePath, 'utf8'); + const entries = parseLog(raw); + panel.webview.postMessage({ + type: 'load', + entries, + channels: collectChannels(entries), + subchannels: collectSubchannels(entries), + hasLevels: hasExplicitLevels(entries), + }); + } catch (err) { + vscode.window.showErrorMessage(`WinGet Log Viewer: failed to read ${filePath}: ${err}`); + } + }; + + panel.webview.onDidReceiveMessage(msg => { + if (msg.type === 'ready') { + sendLoad(); + } else if (msg.type === 'export') { + vscode.env.clipboard.writeText(msg.text).then(() => { + const lineCount = (msg.text as string).split('\n').length; + vscode.window.showInformationMessage(`WinGet Log Viewer: ${lineCount} line(s) copied to clipboard.`); + }); + } + }); + + fs.watchFile(filePath, { interval: 500, persistent: false }, (curr, prev) => { + if (curr.mtimeMs !== prev.mtimeMs) { + try { + const raw = fs.readFileSync(filePath, 'utf8'); + const entries = parseLog(raw); + panel.webview.postMessage({ type: 'update', entries }); + } catch { /* ignore */ } + } + }); + + const key = filePath + '#manual'; + this._panels.set(key, { panel }); + panel.onDidDispose(() => { + fs.unwatchFile(filePath); + this._panels.delete(key); + }); + } + + // ── HTML builder ───────────────────────────────────────────────── + + private _buildHtml(webview: vscode.Webview): string { + const nonce = this._getNonce(); + const cspSource = webview.cspSource; + + const cssUri = webview.asWebviewUri( + vscode.Uri.file(path.join(this._context.extensionPath, 'media', 'viewer.css')), + ); + const jsUri = webview.asWebviewUri( + vscode.Uri.file(path.join(this._context.extensionPath, 'media', 'viewer.js')), + ); + + const templatePath = path.join(this._context.extensionPath, 'media', 'viewer.html'); + let html = fs.readFileSync(templatePath, 'utf8'); + html = html + .replace(/\{\{nonce\}\}/g, nonce) + .replace(/\{\{cspSource\}\}/g, cspSource) + .replace(/\{\{viewerCssUri\}\}/g, cssUri.toString()) + .replace(/\{\{viewerJsUri\}\}/g, jsUri.toString()); + return html; + } + + private _getNonce(): string { + let text = ''; + const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; + } +} diff --git a/tools/WinGetLogViewer/syntaxes/winget-log.tmLanguage.json b/tools/WinGetLogViewer/syntaxes/winget-log.tmLanguage.json index 606b052602..5c63443ad3 100644 --- a/tools/WinGetLogViewer/syntaxes/winget-log.tmLanguage.json +++ b/tools/WinGetLogViewer/syntaxes/winget-log.tmLanguage.json @@ -1,41 +1,41 @@ -{ - "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", - "name": "WinGet Log", - "scopeName": "text.winget-log", - "patterns": [ - { "include": "#logLine" } - ], - "repository": { - "logLine": { - "comment": "Full log line: TIMESTAMP [] [CHAN] [SUBCHAN] message", - "match": "^(\\d{2,4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\s+(?:(<[VIWEC]>)\\s+)?(\\[(FAIL|CLI |SQL |REPO|YAML|CORE|TEST|CONF|WORK)\\s*\\])(\\s+\\[([A-Z]{2,8})\\s*\\])?(.*)$", - "captures": { - "1": { "name": "constant.numeric.timestamp.winget-log" }, - "2": { "name": "keyword.operator.level-marker.winget-log" }, - "3": { "patterns": [{ "include": "#channelBadge" }] }, - "6": { "name": "entity.name.type.subchannel.winget-log" }, - "7": { "patterns": [{ "include": "#messageText" }] } - } - }, - "channelBadge": { - "patterns": [ - { "match": "\\[FAIL\\s*\\]", "name": "invalid.illegal.channel.fail.winget-log" }, - { "match": "\\[CLI\\s*\\]", "name": "support.function.channel.cli.winget-log" }, - { "match": "\\[SQL\\s*\\]", "name": "entity.name.function.channel.sql.winget-log" }, - { "match": "\\[REPO\\s*\\]", "name": "support.class.channel.repo.winget-log" }, - { "match": "\\[YAML\\s*\\]", "name": "constant.language.channel.yaml.winget-log" }, - { "match": "\\[CORE\\s*\\]", "name": "comment.channel.core.winget-log" }, - { "match": "\\[TEST\\s*\\]", "name": "markup.inserted.channel.test.winget-log" }, - { "match": "\\[CONF\\s*\\]", "name": "keyword.control.channel.conf.winget-log" }, - { "match": "\\[WORK\\s*\\]", "name": "variable.parameter.channel.work.winget-log" } - ] - }, - "messageText": { - "patterns": [ - { "match": "\\b(Error|Fail(ed|ure)?|Exception|HRESULT|0x[0-9a-fA-F]+)\\b", "name": "invalid.illegal.error-keyword.winget-log" }, - { "match": "\\b(Warn(ing)?|Deprecated)\\b", "name": "support.type.warning-keyword.winget-log" }, - { "match": "\\b(Verbose|Trace|Debug)\\b", "name": "comment.verbose-keyword.winget-log" } - ] - } - } -} +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "WinGet Log", + "scopeName": "text.winget-log", + "patterns": [ + { "include": "#logLine" } + ], + "repository": { + "logLine": { + "comment": "Full log line: TIMESTAMP [] [CHAN] [SUBCHAN] message", + "match": "^(\\d{2,4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}\\.\\d{3})\\s+(?:(<[VIWEC]>)\\s+)?(\\[(FAIL|CLI |SQL |REPO|YAML|CORE|TEST|CONF|WORK)\\s*\\])(\\s+\\[([A-Z]{2,8})\\s*\\])?(.*)$", + "captures": { + "1": { "name": "constant.numeric.timestamp.winget-log" }, + "2": { "name": "keyword.operator.level-marker.winget-log" }, + "3": { "patterns": [{ "include": "#channelBadge" }] }, + "6": { "name": "entity.name.type.subchannel.winget-log" }, + "7": { "patterns": [{ "include": "#messageText" }] } + } + }, + "channelBadge": { + "patterns": [ + { "match": "\\[FAIL\\s*\\]", "name": "invalid.illegal.channel.fail.winget-log" }, + { "match": "\\[CLI\\s*\\]", "name": "support.function.channel.cli.winget-log" }, + { "match": "\\[SQL\\s*\\]", "name": "entity.name.function.channel.sql.winget-log" }, + { "match": "\\[REPO\\s*\\]", "name": "support.class.channel.repo.winget-log" }, + { "match": "\\[YAML\\s*\\]", "name": "constant.language.channel.yaml.winget-log" }, + { "match": "\\[CORE\\s*\\]", "name": "comment.channel.core.winget-log" }, + { "match": "\\[TEST\\s*\\]", "name": "markup.inserted.channel.test.winget-log" }, + { "match": "\\[CONF\\s*\\]", "name": "keyword.control.channel.conf.winget-log" }, + { "match": "\\[WORK\\s*\\]", "name": "variable.parameter.channel.work.winget-log" } + ] + }, + "messageText": { + "patterns": [ + { "match": "\\b(Error|Fail(ed|ure)?|Exception|HRESULT|0x[0-9a-fA-F]+)\\b", "name": "invalid.illegal.error-keyword.winget-log" }, + { "match": "\\b(Warn(ing)?|Deprecated)\\b", "name": "support.type.warning-keyword.winget-log" }, + { "match": "\\b(Verbose|Trace|Debug)\\b", "name": "comment.verbose-keyword.winget-log" } + ] + } + } +} diff --git a/tools/WinGetLogViewer/tsconfig.json b/tools/WinGetLogViewer/tsconfig.json index 61d02af9c9..7ecbd101ba 100644 --- a/tools/WinGetLogViewer/tsconfig.json +++ b/tools/WinGetLogViewer/tsconfig.json @@ -1,15 +1,15 @@ -{ - "compilerOptions": { - "module": "commonjs", - "target": "ES2020", - "lib": ["ES2020"], - "outDir": "./out", - "rootDir": "./src", - "sourceMap": true, - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true - }, - "include": ["src/**/*.ts"], - "exclude": ["node_modules", ".vscode-test"] -} +{ + "compilerOptions": { + "module": "commonjs", + "target": "ES2020", + "lib": ["ES2020"], + "outDir": "./out", + "rootDir": "./src", + "sourceMap": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"], + "exclude": ["node_modules", ".vscode-test"] +} From a75967e455a4fc51790bcd124f9e0520227eeed5 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 5 Jun 2026 15:48:24 -0700 Subject: [PATCH 2/2] Simplify .gitattributes - drop explicit LF overrides for `.md`, `.json`, `.js`, etc. -- `text=auto eol=crlf` handles those correctly; no tooling in this repo requires LF for those types - drop `src/PowerShell/ExternalModules/** -text` -- `text=auto` already detects UTF-16 LE files as binary natively - drop explicit `binary` entries -- redundant with `text=auto` binary detection - keep `*.patch text eol=lf` -- vcpkg applies these against LF source trees via `git apply`; CRLF patch content risks mismatched hunk context --- .gitattributes | 27 +- .../MSFT_PackageManagement.psm1 | 1224 ++++++------ .../PackageManagementDscUtilities.psm1 | 1030 +++++----- .../1.4.8.1/PSGetModuleInfo.xml | 332 ++-- .../1.4.8.1/PackageManagement.Resources.psd1 | 456 ++--- .../1.4.8.1/PackageManagement.format.ps1xml | 1404 +++++++------- .../1.4.8.1/PackageManagement.psd1 | 626 +++--- .../1.4.8.1/PackageManagement.psm1 | 538 ++--- .../1.4.8.1/PackageProviderFunctions.psm1 | 1192 ++++++------ .../MSFT_PSModule/MSFT_PSModule.psm1 | 1726 ++++++++--------- .../MSFT_PSModule/MSFT_PSModule.schema.mfl | 48 +- .../MSFT_PSModule/MSFT_PSModule.schema.mof | 428 ++-- .../en-US/MSFT_PSModule.strings.psd1 | 456 ++--- .../MSFT_PSRepository/MSFT_PSRepository.psm1 | 1002 +++++----- .../MSFT_PSRepository.schema.mfl | 36 +- .../MSFT_PSRepository.schema.mof | 414 ++-- .../en-US/MSFT_PSRepository.strings.psd1 | 430 ++-- .../PowerShellGet.LocalizationHelper.psm1 | 886 ++++----- .../PowerShellGet.ResourceHelper.psm1 | 1140 +++++------ .../PowerShellGet.ResourceHelper.strings.psd1 | 444 ++--- .../PowerShellGet/2.2.5/PSGet.Format.ps1xml | 818 ++++---- .../PowerShellGet/2.2.5/PSGet.Resource.psd1 | 932 ++++----- .../PowerShellGet/2.2.5/PSGetModuleInfo.xml | 424 ++-- .../PowerShellGet/2.2.5/PowerShellGet.psd1 | 908 ++++----- .../2.2.5/en-US/PSGet.Resource.psd1 | 932 ++++----- 25 files changed, 8916 insertions(+), 8937 deletions(-) diff --git a/.gitattributes b/.gitattributes index 9b82293bbd..36e0b0c7cb 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,27 +2,6 @@ # Git always stores text as LF in the object store; eol= controls the working tree only. * text=auto eol=crlf -# Cross-platform formats - no Windows tooling owns these, LF is safer for external consumers -*.editorconfig text eol=lf -*.gitattributes text eol=lf -*.gitignore text eol=lf -*.json text eol=lf -*.js text eol=lf -*.md text eol=lf -*.patch text eol=lf -*.txt text eol=lf - -# Vendored external modules - preserve byte-for-byte as shipped upstream -# (some files are UTF-16 LE; text=auto would misidentify or corrupt them) -src/PowerShell/ExternalModules/** -text - -# Binary assets - suppress line-ending conversion and text diffs -*.cer binary -*.crt binary -*.dll binary -*.exe binary -*.gif binary -*.ico binary -*.msi binary -*.msix binary -*.png binary +# Patch files must stay LF - git apply matches content lines against the target source +# tree, which vcpkg manages as LF internally +*.patch text eol=lf diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/MSFT_PackageManagement/MSFT_PackageManagement.psm1 b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/MSFT_PackageManagement/MSFT_PackageManagement.psm1 index 1f97db073e..77e3b8a0e7 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/MSFT_PackageManagement/MSFT_PackageManagement.psm1 +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/MSFT_PackageManagement/MSFT_PackageManagement.psm1 @@ -1,612 +1,612 @@ -# -# Copyright (c) Microsoft Corporation. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# This PS DSC resource enables installing a package. The resource uses Install-Package cmdlet -# to install the package from various providers/sources. - -Import-LocalizedData -BindingVariable LocalizedData -filename MSFT_PackageManagement.strings.psd1 - -Import-Module -Name "$PSScriptRoot\..\PackageManagementDscUtilities.psm1" - -function Get-TargetResource -{ - <# - .SYNOPSIS - - This DSC resource provides a mechanism to download and install packages on a computer. - - Get-TargetResource returns the current state of the resource. - - .PARAMETER Name - Specifies the name of the Package to be installed or uninstalled. - - .PARAMETER Source - Specifies the name of the package source where the package can be found. - This can either be a URI or a source registered with Register-PackageSource cmdlet. - The DSC resource MSFT_PackageManagementSource can also register a package source. - - .PARAMETER RequiredVersion - Specifies the exact version of the package that you want to install. If you do not specify this parameter, - this DSC resource installs the newest available version of the package that also satisfies any - maximum version specified by the MaximumVersion parameter. - - .PARAMETER MaximumVersion - Specifies the maximum allowed version of the package that you want to install. If you do not specify this parameter, - this DSC resource installs the highest-numbered available version of the package. - - .PARAMETER MinimumVersion - Specifies the minimum allowed version of the package that you want to install. If you do not add this parameter, - this DSC resource intalls the highest available version of the package that also satisfies any maximum - specified version specified by the MaximumVersion parameter. - - .PARAMETER SourceCredential - Specifies a user account that has rights to install a package for a specified package provider or source. - - .PARAMETER ProviderName - Specifies a package provider name to which to scope your package search. You can get package provider names - by running the Get-PackageProvider cmdlet. - - .PARAMETER AdditionalParameters - Provider specific parameters that are passed as an Hashtable. For example, for NuGet provider you can - pass additional parameters like DestinationPath. - #> - - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.String] - $MaximumVersion, - - [Parameter()] - [System.String] - $Source, - - [Parameter()] - [PSCredential] $SourceCredential, - - [Parameter()] - [System.String] - $ProviderName, - - [Parameter()] - [Microsoft.Management.Infrastructure.CimInstance[]]$AdditionalParameters - ) - - $ensure = "Absent" - $null = $PSBoundParameters.Remove("Source") - $null = $PSBoundParameters.Remove("SourceCredential") - - if ($AdditionalParameters) - { - foreach($instance in $AdditionalParameters) - { - Write-Verbose ('AdditionalParameter: {0}, AdditionalParameterValue: {1}' -f $instance.Key, $instance.Value) - $null = $PSBoundParameters.Add($instance.Key, $instance.Value) - } - } - $null = $PSBoundParameters.Remove("AdditionalParameters") - - $verboseMessage =$localizedData.StartGetPackage -f (GetMessageFromParameterDictionary $PSBoundParameters),$env:PSModulePath - Write-Verbose -Message $verboseMessage - $result = PackageManagement\Get-Package @PSBoundParameters -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - - if ($result.count -eq 1) - { - Write-Verbose -Message ($localizedData.PackageFound -f $Name) - $ensure = "Present" - } - elseif ($result.count -gt 1) - { - Write-Verbose -Message ($localizedData.MultiplePackagesFound -f $Name) - $ensure = "Present" - } - else - { - Write-Verbose -Message ($localizedData.PackageNotFound -f $($Name)) - } - - Write-Debug -Message "Source $($Name) is $($ensure)" - - - if ($ensure -eq 'Absent') - { - return @{ - Ensure = $ensure - Name = $Name - ProviderName = $ProviderName - RequiredVersion = $RequiredVersion - MinimumVersion = $MinimumVersion - MaximumVersion = $MaximumVersion - } - } - else - { - if ($result.Count -gt 1) - { - $result = $result[0] - } - - return @{ - Ensure = $ensure - Name = $result.Name - ProviderName = $result.ProviderName - Source = $result.source - RequiredVersion = $result.Version - } - } -} - -function Test-TargetResource -{ - <# - .SYNOPSIS - - This DSC resource provides a mechanism to download and install packages on a computer. - - Test-TargetResource returns a boolean which determines whether the resource is in - desired state or not. - - .PARAMETER Name - Specifies the name of the Package to be installed or uninstalled. - - .PARAMETER Source - Specifies the name of the package source where the package can be found. - This can either be a URI or a source registered with Register-PackageSource cmdlet. - The DSC resource MSFT_PackageManagementSource can also register a package source. - - .PARAMETER RequiredVersion - Specifies the exact version of the package that you want to install. If you do not specify this parameter, - this DSC resource installs the newest available version of the package that also satisfies any - maximum version specified by the MaximumVersion parameter. - - .PARAMETER MaximumVersion - Specifies the maximum allowed version of the package that you want to install. If you do not specify this parameter, - this DSC resource installs the highest-numbered available version of the package. - - .PARAMETER MinimumVersion - Specifies the minimum allowed version of the package that you want to install. If you do not add this parameter, - this DSC resource intalls the highest available version of the package that also satisfies any maximum - specified version specified by the MaximumVersion parameter. - - .PARAMETER SourceCredential - Specifies a user account that has rights to install a package for a specified package provider or source. - - .PARAMETER ProviderName - Specifies a package provider name to which to scope your package search. You can get package provider names - by running the Get-PackageProvider cmdlet. - - .PARAMETER AdditionalParameters - Provider specific parameters that are passed as an Hashtable. For example, for NuGet provider you can - pass additional parameters like DestinationPath. - #> - - [CmdletBinding()] - [OutputType([bool])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.String] - $MaximumVersion, - - [Parameter()] - [System.String] - $Source, - - [Parameter()] - [PSCredential] $SourceCredential, - - [ValidateSet("Present","Absent")] - [System.String] - $Ensure="Present", - - [Parameter()] - [System.String] - $ProviderName, - - [Parameter()] - [Microsoft.Management.Infrastructure.CimInstance[]]$AdditionalParameters - ) - - - Write-Verbose -Message ($localizedData.StartTestPackage -f (GetMessageFromParameterDictionary $PSBoundParameters)) - $null = $PSBoundParameters.Remove("Ensure") - - $temp = Get-TargetResource @PSBoundParameters - - if ($temp.Ensure -eq $ensure) - { - Write-Verbose -Message ($localizedData.InDesiredState -f $Name, $Ensure, $temp.Ensure) - return $True - } - else - { - Write-Verbose -Message ($localizedData.NotInDesiredState -f $Name,$ensure,$temp.ensure) - return [bool] $False - } -} - -function Set-TargetResource -{ - <# - .SYNOPSIS - - This DSC resource provides a mechanism to download and install packages on a computer. - - Set-TargetResource either intalls or uninstall a package as defined by the vaule of Ensure parameter. - - .PARAMETER Name - Specifies the name of the Package to be installed or uninstalled. - - .PARAMETER Source - Specifies the name of the package source where the package can be found. - This can either be a URI or a source registered with Register-PackageSource cmdlet. - The DSC resource MSFT_PackageManagementSource can also register a package source. - - .PARAMETER RequiredVersion - Specifies the exact version of the package that you want to install. If you do not specify this parameter, - this DSC resource installs the newest available version of the package that also satisfies any - maximum version specified by the MaximumVersion parameter. - - .PARAMETER MaximumVersion - Specifies the maximum allowed version of the package that you want to install. If you do not specify this parameter, - this DSC resource installs the highest-numbered available version of the package. - - .PARAMETER MinimumVersion - Specifies the minimum allowed version of the package that you want to install. If you do not add this parameter, - this DSC resource intalls the highest available version of the package that also satisfies any maximum - specified version specified by the MaximumVersion parameter. - - .PARAMETER SourceCredential - Specifies a user account that has rights to install a package for a specified package provider or source. - - .PARAMETER ProviderName - Specifies a package provider name to which to scope your package search. You can get package provider names - by running the Get-PackageProvider cmdlet. - - .PARAMETER AdditionalParameters - Provider specific parameters that are passed as an Hashtable. For example, for NuGet provider you can - pass additional parameters like DestinationPath. - #> - - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.String] - $MaximumVersion, - - [Parameter()] - [System.String] - $Source, - - [Parameter()] - [PSCredential] $SourceCredential, - - [ValidateSet("Present","Absent")] - [System.String] - $Ensure="Present", - - [Parameter()] - [System.String] - $ProviderName, - - [Parameter()] - [Microsoft.Management.Infrastructure.CimInstance[]]$AdditionalParameters - ) - - Write-Verbose -Message ($localizedData.StartSetPackage -f (GetMessageFromParameterDictionary $PSBoundParameters)) - - $null = $PSBoundParameters.Remove("Ensure") - - if ($PSBoundParameters.ContainsKey("SourceCredential")) - { - $PSBoundParameters.Add("Credential", $SourceCredential) - $null = $PSBoundParameters.Remove("SourceCredential") - } - - if ($AdditionalParameters) - { - foreach($instance in $AdditionalParameters) - { - Write-Verbose ('AdditionalParameter: {0}, AdditionalParameterValue: {1}' -f $instance.Key, $instance.Value) - $null = $PSBoundParameters.Add($instance.Key, $instance.Value) - } - } - - $PSBoundParameters.Remove("AdditionalParameters") - - - # We do not want others to control the behavior of ErrorAction - # while calling Install-Package/Uninstall-Package. - $PSBoundParameters.Remove("ErrorAction") - if ($Ensure -eq "Present") - { - PackageManagement\Install-Package @PSBoundParameters -ErrorAction Stop - } - else - { - # we dont source location for uninstalling an already - # installed package - $PSBoundParameters.Remove("Source") - # Ensure is Absent - PackageManagement\Uninstall-Package @PSBoundParameters -ErrorAction Stop - } - } - - function GetMessageFromParameterDictionary - { - <# - Returns a strng of form "ParameterName:ParameterValue" - Used with Write-Verbose message. The input is mostly $PSBoundParameters - #> - param([System.Collections.IDictionary] $paramDictionary) - - $returnValue = "" - $paramDictionary.Keys | ForEach-Object { $returnValue += "-{0} {1} " -f $_,$paramDictionary[$_] } - return $returnValue - } - -Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource - - -# SIG # Begin signature block -# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBNHExVsaD0kyJT -# faO9Fdru5c5dZNw/I2f/WOIZwqnZQKCCDYEwggX/MIID56ADAgECAhMzAAACUosz -# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I -# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O -# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA -# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o -# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 -# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 -# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp -# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 -# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u -# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 -# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti -# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z -# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf -# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK -# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW -# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F -# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg8R1Pmfow -# cdRZqYArE66BbwlcVzLXx5t2tD5hSPvyDEowQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQAeXps4tjAFtlAdjZLwORhr3TtESrcKHZnVBHZ0zYbM -# unaWRYc0OysS4qo+BomxtH9p6wL8QAOILHlQvCYdMoEI2jbAbBzb61wFHb8g2qeX -# l5dLpSgZnOU+Ej+r2EieXi4DSYANTCOsEVnHWa+9tYZ1qZYB7dDysy0hMJ9F0lpI -# FLhooei9J7aB5z1sSUEpBqoEgSn/+unhSlHMTasmYyzxBsb+KoFbwzavZPFluy5R -# HmWt7m0HSfkzScoYM0CUpnCRAyQELq/ubtCIbxbDfddl4JGJToAABc3akuXY9WDc -# 2MdT2gqPPX0+Acp861u/hlYfiZipnoE0K3cSZ6WJH2JvoYIXADCCFvwGCisGAQQB -# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME -# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEICEiVK6WSVgxKzH21qRCzrs8poUE/CmvuRL5UN+N -# A1gpAgZitMncVAUYEzIwMjIwNzAxMjEwMDAwLjM2NVowBIACAfSggdCkgc0wgcox -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p -# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg -# RVNOOjIyNjQtRTMzRS03ODBDMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt -# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGYdrOMxdAFoQEAAQAAAZgw -# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 -# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh -# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN -# MjExMjAyMTkwNTE1WhcNMjMwMjI4MTkwNTE1WjCByjELMAkGA1UEBhMCVVMxEzAR -# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p -# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg -# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MjI2NC1FMzNFLTc4 -# MEMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G -# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG1JWsVksp8xG4sLMnfxfit3ShI+7G -# 1MfTT+5XvQzuAOe8r5MRAFITTmjFxzoLFfmaxLvPVlmDgkDi0rqsOs9Al9jVwYSF -# VF/wWC2+B76OysiyRjw+NPj5A4cmMhPqIdNkRLCE+wtuI/wCaq3/Lf4koDGudIcE -# YRgMqqToOOUIV4e7EdYb3k9rYPN7SslwsLFSp+Fvm/Qcy5KqfkmMX4S3oJx7HdiQ -# hKbK1C6Zfib+761bmrdPLT6eddlnywls7hCrIIuFtgUbUj6KJIZn1MbYY8hrAM59 -# tvLpeGmFW3GjeBAmvBxAn7o9Lp2nykT1w9I0s9ddwpFnjLT2PK74GDSsxFUZG1Ut -# Lypi/kZcg9WenPAZpUtPFfO5Mtif8Ja8jXXLIP6K+b5LiQV8oIxFSBfgFN7/TL2t -# SSfQVcvqX1mcSOrx/tsgq3L6YAxI6Pl4h1zQrcAmToypEoPYNc/RlSBk6ljmNyND -# sX3gtK8p6c7HCWUhF+YjMgfanQmMjUYsbjdEsCyL6QAojZ0f6kteN4cV6obFwcUE -# viYygWbedaT86OGe9LEOxPuhzgFv2ZobVr0J8hl1FVdcZFbfFN/gdjHZ/ncDDqLN -# WgcoMoEhwwzo7FAObqKaxfB5zCBqYSj45miNO5g3hP8AgC0eSCHl3rK7JPMr1B+8 -# JTHtwRkSKz/+cwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFG6RhHKNpsg3mgons7LR -# 5YHTzeE3MB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY -# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p -# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF -# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w -# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo -# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI -# hvcNAQELBQADggIBACT6B6F33i/89zXTgqQ8L6CYMHx9BiaHOV+wk53JOriCzeaL -# jYgRyssJhmnnJ/CdHa5qjcSwvRptWpZJPVK5sxhOIjRBPgs/3+ER0vS87IA+aGbf -# 7NF7LZZlxWPOl/yFBg9qZ3tpOGOohQInQn5zpV23hWopaN4c49jGJHLPAfy9u7+Z -# SGQuw14CsW/XRLELHT18I60W0uKOBa5Pm2ViohMovcbpNUCEERqIO9WPwzIwMRRw -# 34/LgjuslHJop+/1Ve/CfyNqweUmwepQHJrd+wTLUlgm4ENbXF6i52jFfYpESwLd -# An56o/pj+grsd2LrAEPQRyh49rWvI/qZfOhtT2FWmzFw6IJvZ7CzT1O+Fc0gIDBN -# qass5QbmkOkKYy9U7nFA6qn3ZZ+MrZMsJTj7gxAf0yMkVqwYWZRk4brY9q8JDPmc -# fNSjRrVfpYyzEVEqemGanmxvDDTzS2wkSBa3zcNwOgYhWBTmJdLgyiWJGeqyj1m5 -# bwNgnOw6NzXCiVMzfbztdkqOdTR88LtAJGNRjevWjQd5XitGuegSp2mMJglFzRwk -# ncQau1BJsCj/1aDY4oMiO8conkmaWBrYe11QCS896/sZwSdnEUJak0qpnBRFB+TH -# RIxIivCKNbxG2QRZ8dh95cOXgo0YvBN5a1p+iJ3vNwzneU2AIC7z3rrIbN2fMIIH -# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB -# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl -# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp -# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw -# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE -# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z -# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ -# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh -# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx -# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc -# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc -# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo -# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi -# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 -# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH -# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X -# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE -# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ -# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 -# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd -# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE -# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t -# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI -# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB -# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud -# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By -# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO -# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy -# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC -# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk -# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng -# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 -# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC -# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 -# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU -# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh -# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ -# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp -# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI -# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4 -# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G -# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw -# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U -# aGFsZXMgVFNTIEVTTjoyMjY0LUUzM0UtNzgwQzElMCMGA1UEAxMcTWljcm9zb2Z0 -# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA8ywe/iF5M8fIU2aT -# 6yQ3vnPpV5OggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu -# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv -# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN -# BgkqhkiG9w0BAQUFAAIFAOZp1AIwIhgPMjAyMjA3MDIwNDEzNTRaGA8yMDIyMDcw -# MzA0MTM1NFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5mnUAgIBADAKAgEAAgIN -# NAIB/zAHAgEAAgIRtDAKAgUA5mslggIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor -# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA -# A4GBAIcnMC784m6T/edBW3cq9r/yFVNA0QEvStf82Kw/R7tlaHDt097cO2b04KMC -# V009JquDovHm5hKa95HNl/EcSVOy0XSzCkkFRCFfHvQyCepHU+f2lAfJkKPfQoBH -# UYXvMMVFDB2X/i6sM5rB50pw+es9vpAGic/VoX7HQkPz/x74MYIEDTCCBAkCAQEw -# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT -# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE -# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGYdrOMxdAFoQEA -# AQAAAZgwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B -# CRABBDAvBgkqhkiG9w0BCQQxIgQgPSBQh4cqea+akWI4bmlSCCKf/KlFhtOwg6A6 -# 6ghYSTQwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCC/ps4GOTn/9wO1NhHM -# 9Qfe0loB3slkw1FF3r+bh21WxDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w -# IFBDQSAyMDEwAhMzAAABmHazjMXQBaEBAAEAAAGYMCIEIES78xtiuDaV9ywXzTl8 -# XWUH8mqeO6XZWBLLhxayXSClMA0GCSqGSIb3DQEBCwUABIICAJXsTUtHPNUMQu1x -# h6OSxx18cwjD6ncubNtd0T2LpuIaJ6P+Dog/ZNDl2qUBHPn6UvCdrCQSVXxWyVKj -# bsPvxf7gLJPmCOlCgQ+F4ocGoMx759e0H9NWGUD1OczeBEkcVtwycN4ZboslQbiP -# W32gbaZFOaujV+Xigrl4OtIebOZXH47ys+B8rSHX+sb2wejdUy5UyUexiLg6t1Jj -# Y253gM9vW5GqRpseJwAZ7gukmjvEjeCpC16l2hNi++vo8ktAkduWDsz6RbDBqqz+ -# g9FjOkAPZ//+eA0BGHrOwn2Piw8UCSl48hInvC/hqvS67T2ysb+nTy/Wn2BUHdaa -# 6bfaGh0nqclQjWjkRfcxPxk1b61mYErRaBmqZvgrQK/FiFA/TYt1vegvpDKrz2Ey -# /hKY2GaAKlDE2fjRRvUgzVXw7j9ZbJfmrTC/QzXvnfYyAGvdj5M93DwmOWL4rfRH -# QDQVxegfmlrolnZUE5BirFk4mdZT7mihghMO8POWpo2oG5ehqasflzQNaXyq97w7 -# tLe78SK0CBIqDuEbOVznD1/wDCLha0iYwKmBHmhk5ElB36whfabC6PvHS5jndpSa -# STUZG+2PcqUTGR3CVrZOVarkLXshyHgDSJfkS5aCW4GPDsqWmhznCoIZWDdlkak1 -# vE5R3wCfHoZ7HLzzkdvfZlIhuwOn -# SIG # End signature block +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# This PS DSC resource enables installing a package. The resource uses Install-Package cmdlet +# to install the package from various providers/sources. + +Import-LocalizedData -BindingVariable LocalizedData -filename MSFT_PackageManagement.strings.psd1 + +Import-Module -Name "$PSScriptRoot\..\PackageManagementDscUtilities.psm1" + +function Get-TargetResource +{ + <# + .SYNOPSIS + + This DSC resource provides a mechanism to download and install packages on a computer. + + Get-TargetResource returns the current state of the resource. + + .PARAMETER Name + Specifies the name of the Package to be installed or uninstalled. + + .PARAMETER Source + Specifies the name of the package source where the package can be found. + This can either be a URI or a source registered with Register-PackageSource cmdlet. + The DSC resource MSFT_PackageManagementSource can also register a package source. + + .PARAMETER RequiredVersion + Specifies the exact version of the package that you want to install. If you do not specify this parameter, + this DSC resource installs the newest available version of the package that also satisfies any + maximum version specified by the MaximumVersion parameter. + + .PARAMETER MaximumVersion + Specifies the maximum allowed version of the package that you want to install. If you do not specify this parameter, + this DSC resource installs the highest-numbered available version of the package. + + .PARAMETER MinimumVersion + Specifies the minimum allowed version of the package that you want to install. If you do not add this parameter, + this DSC resource intalls the highest available version of the package that also satisfies any maximum + specified version specified by the MaximumVersion parameter. + + .PARAMETER SourceCredential + Specifies a user account that has rights to install a package for a specified package provider or source. + + .PARAMETER ProviderName + Specifies a package provider name to which to scope your package search. You can get package provider names + by running the Get-PackageProvider cmdlet. + + .PARAMETER AdditionalParameters + Provider specific parameters that are passed as an Hashtable. For example, for NuGet provider you can + pass additional parameters like DestinationPath. + #> + + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.String] + $MaximumVersion, + + [Parameter()] + [System.String] + $Source, + + [Parameter()] + [PSCredential] $SourceCredential, + + [Parameter()] + [System.String] + $ProviderName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]]$AdditionalParameters + ) + + $ensure = "Absent" + $null = $PSBoundParameters.Remove("Source") + $null = $PSBoundParameters.Remove("SourceCredential") + + if ($AdditionalParameters) + { + foreach($instance in $AdditionalParameters) + { + Write-Verbose ('AdditionalParameter: {0}, AdditionalParameterValue: {1}' -f $instance.Key, $instance.Value) + $null = $PSBoundParameters.Add($instance.Key, $instance.Value) + } + } + $null = $PSBoundParameters.Remove("AdditionalParameters") + + $verboseMessage =$localizedData.StartGetPackage -f (GetMessageFromParameterDictionary $PSBoundParameters),$env:PSModulePath + Write-Verbose -Message $verboseMessage + $result = PackageManagement\Get-Package @PSBoundParameters -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + + if ($result.count -eq 1) + { + Write-Verbose -Message ($localizedData.PackageFound -f $Name) + $ensure = "Present" + } + elseif ($result.count -gt 1) + { + Write-Verbose -Message ($localizedData.MultiplePackagesFound -f $Name) + $ensure = "Present" + } + else + { + Write-Verbose -Message ($localizedData.PackageNotFound -f $($Name)) + } + + Write-Debug -Message "Source $($Name) is $($ensure)" + + + if ($ensure -eq 'Absent') + { + return @{ + Ensure = $ensure + Name = $Name + ProviderName = $ProviderName + RequiredVersion = $RequiredVersion + MinimumVersion = $MinimumVersion + MaximumVersion = $MaximumVersion + } + } + else + { + if ($result.Count -gt 1) + { + $result = $result[0] + } + + return @{ + Ensure = $ensure + Name = $result.Name + ProviderName = $result.ProviderName + Source = $result.source + RequiredVersion = $result.Version + } + } +} + +function Test-TargetResource +{ + <# + .SYNOPSIS + + This DSC resource provides a mechanism to download and install packages on a computer. + + Test-TargetResource returns a boolean which determines whether the resource is in + desired state or not. + + .PARAMETER Name + Specifies the name of the Package to be installed or uninstalled. + + .PARAMETER Source + Specifies the name of the package source where the package can be found. + This can either be a URI or a source registered with Register-PackageSource cmdlet. + The DSC resource MSFT_PackageManagementSource can also register a package source. + + .PARAMETER RequiredVersion + Specifies the exact version of the package that you want to install. If you do not specify this parameter, + this DSC resource installs the newest available version of the package that also satisfies any + maximum version specified by the MaximumVersion parameter. + + .PARAMETER MaximumVersion + Specifies the maximum allowed version of the package that you want to install. If you do not specify this parameter, + this DSC resource installs the highest-numbered available version of the package. + + .PARAMETER MinimumVersion + Specifies the minimum allowed version of the package that you want to install. If you do not add this parameter, + this DSC resource intalls the highest available version of the package that also satisfies any maximum + specified version specified by the MaximumVersion parameter. + + .PARAMETER SourceCredential + Specifies a user account that has rights to install a package for a specified package provider or source. + + .PARAMETER ProviderName + Specifies a package provider name to which to scope your package search. You can get package provider names + by running the Get-PackageProvider cmdlet. + + .PARAMETER AdditionalParameters + Provider specific parameters that are passed as an Hashtable. For example, for NuGet provider you can + pass additional parameters like DestinationPath. + #> + + [CmdletBinding()] + [OutputType([bool])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.String] + $MaximumVersion, + + [Parameter()] + [System.String] + $Source, + + [Parameter()] + [PSCredential] $SourceCredential, + + [ValidateSet("Present","Absent")] + [System.String] + $Ensure="Present", + + [Parameter()] + [System.String] + $ProviderName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]]$AdditionalParameters + ) + + + Write-Verbose -Message ($localizedData.StartTestPackage -f (GetMessageFromParameterDictionary $PSBoundParameters)) + $null = $PSBoundParameters.Remove("Ensure") + + $temp = Get-TargetResource @PSBoundParameters + + if ($temp.Ensure -eq $ensure) + { + Write-Verbose -Message ($localizedData.InDesiredState -f $Name, $Ensure, $temp.Ensure) + return $True + } + else + { + Write-Verbose -Message ($localizedData.NotInDesiredState -f $Name,$ensure,$temp.ensure) + return [bool] $False + } +} + +function Set-TargetResource +{ + <# + .SYNOPSIS + + This DSC resource provides a mechanism to download and install packages on a computer. + + Set-TargetResource either intalls or uninstall a package as defined by the vaule of Ensure parameter. + + .PARAMETER Name + Specifies the name of the Package to be installed or uninstalled. + + .PARAMETER Source + Specifies the name of the package source where the package can be found. + This can either be a URI or a source registered with Register-PackageSource cmdlet. + The DSC resource MSFT_PackageManagementSource can also register a package source. + + .PARAMETER RequiredVersion + Specifies the exact version of the package that you want to install. If you do not specify this parameter, + this DSC resource installs the newest available version of the package that also satisfies any + maximum version specified by the MaximumVersion parameter. + + .PARAMETER MaximumVersion + Specifies the maximum allowed version of the package that you want to install. If you do not specify this parameter, + this DSC resource installs the highest-numbered available version of the package. + + .PARAMETER MinimumVersion + Specifies the minimum allowed version of the package that you want to install. If you do not add this parameter, + this DSC resource intalls the highest available version of the package that also satisfies any maximum + specified version specified by the MaximumVersion parameter. + + .PARAMETER SourceCredential + Specifies a user account that has rights to install a package for a specified package provider or source. + + .PARAMETER ProviderName + Specifies a package provider name to which to scope your package search. You can get package provider names + by running the Get-PackageProvider cmdlet. + + .PARAMETER AdditionalParameters + Provider specific parameters that are passed as an Hashtable. For example, for NuGet provider you can + pass additional parameters like DestinationPath. + #> + + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.String] + $MaximumVersion, + + [Parameter()] + [System.String] + $Source, + + [Parameter()] + [PSCredential] $SourceCredential, + + [ValidateSet("Present","Absent")] + [System.String] + $Ensure="Present", + + [Parameter()] + [System.String] + $ProviderName, + + [Parameter()] + [Microsoft.Management.Infrastructure.CimInstance[]]$AdditionalParameters + ) + + Write-Verbose -Message ($localizedData.StartSetPackage -f (GetMessageFromParameterDictionary $PSBoundParameters)) + + $null = $PSBoundParameters.Remove("Ensure") + + if ($PSBoundParameters.ContainsKey("SourceCredential")) + { + $PSBoundParameters.Add("Credential", $SourceCredential) + $null = $PSBoundParameters.Remove("SourceCredential") + } + + if ($AdditionalParameters) + { + foreach($instance in $AdditionalParameters) + { + Write-Verbose ('AdditionalParameter: {0}, AdditionalParameterValue: {1}' -f $instance.Key, $instance.Value) + $null = $PSBoundParameters.Add($instance.Key, $instance.Value) + } + } + + $PSBoundParameters.Remove("AdditionalParameters") + + + # We do not want others to control the behavior of ErrorAction + # while calling Install-Package/Uninstall-Package. + $PSBoundParameters.Remove("ErrorAction") + if ($Ensure -eq "Present") + { + PackageManagement\Install-Package @PSBoundParameters -ErrorAction Stop + } + else + { + # we dont source location for uninstalling an already + # installed package + $PSBoundParameters.Remove("Source") + # Ensure is Absent + PackageManagement\Uninstall-Package @PSBoundParameters -ErrorAction Stop + } + } + + function GetMessageFromParameterDictionary + { + <# + Returns a strng of form "ParameterName:ParameterValue" + Used with Write-Verbose message. The input is mostly $PSBoundParameters + #> + param([System.Collections.IDictionary] $paramDictionary) + + $returnValue = "" + $paramDictionary.Keys | ForEach-Object { $returnValue += "-{0} {1} " -f $_,$paramDictionary[$_] } + return $returnValue + } + +Export-ModuleMember -function Get-TargetResource, Set-TargetResource, Test-TargetResource + + +# SIG # Begin signature block +# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBNHExVsaD0kyJT +# faO9Fdru5c5dZNw/I2f/WOIZwqnZQKCCDYEwggX/MIID56ADAgECAhMzAAACUosz +# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I +# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O +# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA +# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o +# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 +# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 +# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp +# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 +# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u +# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 +# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti +# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z +# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf +# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK +# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW +# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F +# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg8R1Pmfow +# cdRZqYArE66BbwlcVzLXx5t2tD5hSPvyDEowQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQAeXps4tjAFtlAdjZLwORhr3TtESrcKHZnVBHZ0zYbM +# unaWRYc0OysS4qo+BomxtH9p6wL8QAOILHlQvCYdMoEI2jbAbBzb61wFHb8g2qeX +# l5dLpSgZnOU+Ej+r2EieXi4DSYANTCOsEVnHWa+9tYZ1qZYB7dDysy0hMJ9F0lpI +# FLhooei9J7aB5z1sSUEpBqoEgSn/+unhSlHMTasmYyzxBsb+KoFbwzavZPFluy5R +# HmWt7m0HSfkzScoYM0CUpnCRAyQELq/ubtCIbxbDfddl4JGJToAABc3akuXY9WDc +# 2MdT2gqPPX0+Acp861u/hlYfiZipnoE0K3cSZ6WJH2JvoYIXADCCFvwGCisGAQQB +# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME +# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEICEiVK6WSVgxKzH21qRCzrs8poUE/CmvuRL5UN+N +# A1gpAgZitMncVAUYEzIwMjIwNzAxMjEwMDAwLjM2NVowBIACAfSggdCkgc0wgcox +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p +# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg +# RVNOOjIyNjQtRTMzRS03ODBDMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt +# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGYdrOMxdAFoQEAAQAAAZgw +# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 +# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh +# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN +# MjExMjAyMTkwNTE1WhcNMjMwMjI4MTkwNTE1WjCByjELMAkGA1UEBhMCVVMxEzAR +# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p +# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg +# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046MjI2NC1FMzNFLTc4 +# MEMxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G +# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDG1JWsVksp8xG4sLMnfxfit3ShI+7G +# 1MfTT+5XvQzuAOe8r5MRAFITTmjFxzoLFfmaxLvPVlmDgkDi0rqsOs9Al9jVwYSF +# VF/wWC2+B76OysiyRjw+NPj5A4cmMhPqIdNkRLCE+wtuI/wCaq3/Lf4koDGudIcE +# YRgMqqToOOUIV4e7EdYb3k9rYPN7SslwsLFSp+Fvm/Qcy5KqfkmMX4S3oJx7HdiQ +# hKbK1C6Zfib+761bmrdPLT6eddlnywls7hCrIIuFtgUbUj6KJIZn1MbYY8hrAM59 +# tvLpeGmFW3GjeBAmvBxAn7o9Lp2nykT1w9I0s9ddwpFnjLT2PK74GDSsxFUZG1Ut +# Lypi/kZcg9WenPAZpUtPFfO5Mtif8Ja8jXXLIP6K+b5LiQV8oIxFSBfgFN7/TL2t +# SSfQVcvqX1mcSOrx/tsgq3L6YAxI6Pl4h1zQrcAmToypEoPYNc/RlSBk6ljmNyND +# sX3gtK8p6c7HCWUhF+YjMgfanQmMjUYsbjdEsCyL6QAojZ0f6kteN4cV6obFwcUE +# viYygWbedaT86OGe9LEOxPuhzgFv2ZobVr0J8hl1FVdcZFbfFN/gdjHZ/ncDDqLN +# WgcoMoEhwwzo7FAObqKaxfB5zCBqYSj45miNO5g3hP8AgC0eSCHl3rK7JPMr1B+8 +# JTHtwRkSKz/+cwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFG6RhHKNpsg3mgons7LR +# 5YHTzeE3MB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY +# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p +# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF +# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w +# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo +# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI +# hvcNAQELBQADggIBACT6B6F33i/89zXTgqQ8L6CYMHx9BiaHOV+wk53JOriCzeaL +# jYgRyssJhmnnJ/CdHa5qjcSwvRptWpZJPVK5sxhOIjRBPgs/3+ER0vS87IA+aGbf +# 7NF7LZZlxWPOl/yFBg9qZ3tpOGOohQInQn5zpV23hWopaN4c49jGJHLPAfy9u7+Z +# SGQuw14CsW/XRLELHT18I60W0uKOBa5Pm2ViohMovcbpNUCEERqIO9WPwzIwMRRw +# 34/LgjuslHJop+/1Ve/CfyNqweUmwepQHJrd+wTLUlgm4ENbXF6i52jFfYpESwLd +# An56o/pj+grsd2LrAEPQRyh49rWvI/qZfOhtT2FWmzFw6IJvZ7CzT1O+Fc0gIDBN +# qass5QbmkOkKYy9U7nFA6qn3ZZ+MrZMsJTj7gxAf0yMkVqwYWZRk4brY9q8JDPmc +# fNSjRrVfpYyzEVEqemGanmxvDDTzS2wkSBa3zcNwOgYhWBTmJdLgyiWJGeqyj1m5 +# bwNgnOw6NzXCiVMzfbztdkqOdTR88LtAJGNRjevWjQd5XitGuegSp2mMJglFzRwk +# ncQau1BJsCj/1aDY4oMiO8conkmaWBrYe11QCS896/sZwSdnEUJak0qpnBRFB+TH +# RIxIivCKNbxG2QRZ8dh95cOXgo0YvBN5a1p+iJ3vNwzneU2AIC7z3rrIbN2fMIIH +# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB +# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl +# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp +# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw +# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE +# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z +# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ +# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh +# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx +# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc +# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc +# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo +# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi +# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 +# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH +# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X +# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE +# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ +# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 +# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd +# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE +# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI +# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB +# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud +# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By +# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO +# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy +# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC +# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk +# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng +# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 +# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC +# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 +# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU +# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh +# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ +# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp +# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI +# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4 +# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G +# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw +# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U +# aGFsZXMgVFNTIEVTTjoyMjY0LUUzM0UtNzgwQzElMCMGA1UEAxMcTWljcm9zb2Z0 +# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA8ywe/iF5M8fIU2aT +# 6yQ3vnPpV5OggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv +# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN +# BgkqhkiG9w0BAQUFAAIFAOZp1AIwIhgPMjAyMjA3MDIwNDEzNTRaGA8yMDIyMDcw +# MzA0MTM1NFowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5mnUAgIBADAKAgEAAgIN +# NAIB/zAHAgEAAgIRtDAKAgUA5mslggIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor +# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA +# A4GBAIcnMC784m6T/edBW3cq9r/yFVNA0QEvStf82Kw/R7tlaHDt097cO2b04KMC +# V009JquDovHm5hKa95HNl/EcSVOy0XSzCkkFRCFfHvQyCepHU+f2lAfJkKPfQoBH +# UYXvMMVFDB2X/i6sM5rB50pw+es9vpAGic/VoX7HQkPz/x74MYIEDTCCBAkCAQEw +# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT +# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE +# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGYdrOMxdAFoQEA +# AQAAAZgwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B +# CRABBDAvBgkqhkiG9w0BCQQxIgQgPSBQh4cqea+akWI4bmlSCCKf/KlFhtOwg6A6 +# 6ghYSTQwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCC/ps4GOTn/9wO1NhHM +# 9Qfe0loB3slkw1FF3r+bh21WxDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFBDQSAyMDEwAhMzAAABmHazjMXQBaEBAAEAAAGYMCIEIES78xtiuDaV9ywXzTl8 +# XWUH8mqeO6XZWBLLhxayXSClMA0GCSqGSIb3DQEBCwUABIICAJXsTUtHPNUMQu1x +# h6OSxx18cwjD6ncubNtd0T2LpuIaJ6P+Dog/ZNDl2qUBHPn6UvCdrCQSVXxWyVKj +# bsPvxf7gLJPmCOlCgQ+F4ocGoMx759e0H9NWGUD1OczeBEkcVtwycN4ZboslQbiP +# W32gbaZFOaujV+Xigrl4OtIebOZXH47ys+B8rSHX+sb2wejdUy5UyUexiLg6t1Jj +# Y253gM9vW5GqRpseJwAZ7gukmjvEjeCpC16l2hNi++vo8ktAkduWDsz6RbDBqqz+ +# g9FjOkAPZ//+eA0BGHrOwn2Piw8UCSl48hInvC/hqvS67T2ysb+nTy/Wn2BUHdaa +# 6bfaGh0nqclQjWjkRfcxPxk1b61mYErRaBmqZvgrQK/FiFA/TYt1vegvpDKrz2Ey +# /hKY2GaAKlDE2fjRRvUgzVXw7j9ZbJfmrTC/QzXvnfYyAGvdj5M93DwmOWL4rfRH +# QDQVxegfmlrolnZUE5BirFk4mdZT7mihghMO8POWpo2oG5ehqasflzQNaXyq97w7 +# tLe78SK0CBIqDuEbOVznD1/wDCLha0iYwKmBHmhk5ElB36whfabC6PvHS5jndpSa +# STUZG+2PcqUTGR3CVrZOVarkLXshyHgDSJfkS5aCW4GPDsqWmhznCoIZWDdlkak1 +# vE5R3wCfHoZ7HLzzkdvfZlIhuwOn +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/PackageManagementDscUtilities.psm1 b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/PackageManagementDscUtilities.psm1 index e9468c747c..7987f4f95b 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/PackageManagementDscUtilities.psm1 +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/DSCResources/PackageManagementDscUtilities.psm1 @@ -1,515 +1,515 @@ -# -# Copyright (c) Microsoft Corporation. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -#Helper functions for PackageManagement DSC Resouces - -Import-LocalizedData -BindingVariable LocalizedData -filename PackageManagementDscUtilities.strings.psd1 - - - Function ExtractArguments -{ - <# - .SYNOPSIS - - This is a helper function that extract the parameters from a given table. - - .PARAMETER FunctionBoundParameters - Specifies the hashtable containing a set of parameters to be extracted - - .PARAMETER ArgumentNames - Specifies A list of arguments you want to extract - #> - - Param - ( - [parameter(Mandatory = $true)] - [System.Collections.Hashtable] - $FunctionBoundParameters, - - #A list of arguments you want to extract - [parameter(Mandatory = $true)] - [System.String[]]$ArgumentNames - ) - - Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) - - $returnValue=@{} - - foreach ($arg in $ArgumentNames) - { - if($FunctionBoundParameters.ContainsKey($arg)) - { - #Found an argument we are looking for, so we add it to return collection - $returnValue.Add($arg,$FunctionBoundParameters[$arg]) - } - } - - return $returnValue - } - -function ThrowError -{ - <# - .SYNOPSIS - - This is a helper function that throws an error. - - .PARAMETER ExceptionName - Specifies the type of errors, e.g. System.ArgumentException - - .PARAMETER ExceptionMessage - Specifies the exception message - - .PARAMETER ErrorId - Specifies an identifier of the error - - .PARAMETER ErrorCategory - Specifies the error category, e.g., InvalidArgument defined in System.Management.Automation. - - #> - - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ExceptionName, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ExceptionMessage, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ErrorId, - - [parameter(Mandatory = $true)] - [ValidateNotNull()] - [System.Management.Automation.ErrorCategory] - $ErrorCategory - ) - - Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) - - $exception = New-Object -TypeName $ExceptionName -ArgumentList $ExceptionMessage; - $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList ($exception, $ErrorId, $ErrorCategory, $null) - throw $errorRecord -} - -Function ValidateArgument -{ - <# - .SYNOPSIS - - This is a helper function that validates the arguments. - - .PARAMETER Argument - Specifies the argument to be validated. - - .PARAMETER Type - Specifies the type of argument. - #> - - [CmdletBinding()] - param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [string]$Argument, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String]$Type, - - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [String]$ProviderName - ) - - Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) - - switch ($Type) - { - - "SourceUri" - { - # Checks whether given URI represents specific scheme - # Most common schemes: file, http, https, ftp - $scheme =@('http', 'https', 'file', 'ftp') - - $newUri = $Argument -as [System.URI] - $returnValue = ($newUri -and $newUri.AbsoluteURI -and ($scheme -icontains $newuri.Scheme)) - - if ($returnValue -eq $false) - { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.InValidUri -f $Argument)` - -ErrorId "InValidUri" ` - -ErrorCategory InvalidArgument - } - - #Check whether it's a valid uri. Wait for the response within 2mins. - <#$result = Invoke-WebRequest $newUri -TimeoutSec 120 -UseBasicParsing -ErrorAction SilentlyContinue - - if ($null -eq (([xml]$result.Content).service )) - { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.InValidUri -f $Argument)` - -ErrorId "InValidUri" ` - -ErrorCategory InvalidArgument - }#> - - } - "DestinationPath" - { - $returnValue = Test-Path -Path $Argument - if ($returnValue -eq $false) - { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.PathDoesNotExist -f $Argument)` - -ErrorId "PathDoesNotExist" ` - -ErrorCategory InvalidArgument - } - } - "PackageSource" - { - #Argument can be either the package source Name or source Uri. - - #Check if the source is a uri - $uri = $Argument -as [System.URI] - - if($uri -and $uri.AbsoluteURI) - { - # Check if it's a valid Uri - ValidateArgument -Argument $Argument -Type "SourceUri" -ProviderName $ProviderName - } - else - { - #Check if it's a registered package source name - $source = PackageManagement\Get-PackageSource -Name $Argument -ProviderName $ProviderName -verbose -ErrorVariable ev - if ((-not $source) -or $ev) - { - #We do not need to throw error here as Get-PackageSource does already - Write-Verbose -Message ($LocalizedData.SourceNotFound -f $source) - } - } - } - default - { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.UnexpectedArgument -f $Type)` - -ErrorId "UnexpectedArgument" ` - -ErrorCategory InvalidArgument - } - } -} - -Function ValidateVersionArgument -{ - <# - .SYNOPSIS - - This is a helper function that does the version validation. - - .PARAMETER RequiredVersion - Provides the required version. - - .PARAMETER MaximumVersion - Provides the maximum version. - - .PARAMETER MinimumVersion - Provides the minimum version. - #> - - [CmdletBinding()] - param - ( - [string]$RequiredVersion, - [string]$MinimumVersion, - [string]$MaximumVersion - - ) - - Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) - - $isValid = $false - - #Case 1: No further check required if a user provides either none or one of these: minimumVersion, maximumVersion, and requiredVersion - if ($PSBoundParameters.Count -le 1) - { - return $true - } - - #Case 2: #If no RequiredVersion is provided - if (-not $PSBoundParameters.ContainsKey('RequiredVersion')) - { - #If no RequiredVersion, both MinimumVersion and MaximumVersion are provided. Otherwise fall into the Case #1 - $isValid = $PSBoundParameters['MinimumVersion'] -le $PSBoundParameters['MaximumVersion'] - } - - #Case 3: RequiredVersion is provided. - # In this case MinimumVersion and/or MaximumVersion also are provided. Otherwise fall in to Case #1. - # This is an invalid case. When RequiredVersion is provided, others are not allowed. so $isValid is false, which is already set in the init - - if ($isValid -eq $false) - { - ThrowError -ExceptionName "System.ArgumentException" ` - -ExceptionMessage ($LocalizedData.VersionError)` - -ErrorId "VersionError" ` - -ErrorCategory InvalidArgument - } -} - -Function Get-InstallationPolicy -{ - <# - .SYNOPSIS - - This is a helper function that retrives the InstallationPolicy from the given repository. - - .PARAMETER RepositoryName - Provides the repository Name. - - #> - - Param - ( - [parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String]$RepositoryName - ) - - Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) - - $repositoryobj = PackageManagement\Get-PackageSource -Name $RepositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - if ($repositoryobj) - { - return $repositoryobj.IsTrusted - } -} - -# SIG # Begin signature block -# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD+xe8u4YoS6UEO -# jtW70wceL89huvuluOvdcbeefpOXLqCCDYEwggX/MIID56ADAgECAhMzAAACUosz -# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I -# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O -# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA -# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o -# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 -# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 -# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp -# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 -# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u -# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 -# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti -# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z -# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf -# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK -# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW -# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F -# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgCZ2K5xbK -# 27tyibqtMV5AHpyNN7lNy3nCNEZ+gshCPtAwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQCyqH1a6wilw9tLp1PBwdpHqB1Ami+jaRaJh0DD1pMa -# 0Wv61B/88vYVxgOsFfBcBqZvVoQsYtlAjEcx/dg5Vamacy+LfO+8cm8uRq1uSXOq -# gn3FDJ6Xy0j3pcU44/X1uzh8KoUaQJzNpcjLn8WpqABD0w7WfIA7A+o1yMZUcUsP -# XejK8HpbV8Qrtz9okvl/hsK0zQuajckvm/odt+IgmwBr4yomlRFJ1AAqyKYp//4H -# 7gPo5CBjh3H7wHn0mXMkES24T37LXmxxSncAShECTMvJMhiuM01TN6PziqI6ER0S -# XFvKYHyfqwXvsfn71rcKAIWWjtWXz4a1mIZjkXJfUcf7oYIXADCCFvwGCisGAQQB -# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME -# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIB/6Ll6BcpT6c9557ujfliR3kAn4lZRikouo1ahJ -# /NiIAgZitNXDgmUYEzIwMjIwNzAxMjEwMDAwLjQ0OVowBIACAfSggdCkgc0wgcox -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p -# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg -# RVNOOjhBODItRTM0Ri05RERBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt -# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGZyI+vrbZ9vosAAQAAAZkw -# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 -# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh -# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN -# MjExMjAyMTkwNTE2WhcNMjMwMjI4MTkwNTE2WjCByjELMAkGA1UEBhMCVVMxEzAR -# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p -# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg -# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046OEE4Mi1FMzRGLTlE -# REExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G -# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4E/lXXKMsy9rVa2a8bRb0Ar/Pj4+b -# KiAgMgKayvCMFn3ddGof8eWFgJWp5JdKjWjrnmW1r9tHpcP2kFpjXp2Udrj55jt5 -# NYi1MERcoIo+E29XuCwFAMJftGdvsWea/OTQPIFsZEWqEteXdRncyVwct5xFzBIC -# 1JWCdmfc7R59RMIyvgWjIz8356mweowkOstN1fe53KIJ8flrYILIQWsNRMOT3znA -# GwIb9kyL54C6jZjFxOSusGYmVQ+Gr/qZQELw1ipx9s5jNP1LSpOpfTEBFu+y9KLN -# BmMBARkSPpTFkGEyGSwGGgSdOi6BU6FPK+6urZ830jrRemK4JkIJ9tQhlGcIhAjh -# cqZStn+38lRjVvrfbBI5EpI2NwlVIK2ibGW7sWeTAz/yNPNISUbQhGAJse/OgGj/ -# 1qz/Ha9mqfYZ8BHchNxn08nWkqyrjrKicQyxuD8mCatTrVSbOJYfQyZdHR9a4vgy -# GeZEXBYQNAlIuB37QCOAgs/VeDU8M4dc/IlrTyC0uV1SS4Gk8zV+5X5eRu+XORN8 -# FWqzI6k/9y6cWwOWMK6aUN1XqLcaF/sm9rX84eKW2lhDc3C31WLjp8UOfOHZfPuy -# y54xfilnhhCPy4QKJ9jggoqqeeEhCEfgDYjy+PByV/e5HDB2xHdtlL93wltAkI3a -# Cxo84kVPBCa0OwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFI26Vrg+nGWvrvIh0dQP -# EonENR0QMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY -# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p -# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF -# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w -# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo -# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI -# hvcNAQELBQADggIBAHGzWh29ibBNro3ns8E3EOHGsLB1Gzk90SFYUKBilIu4jDbR -# 7qbvXNd8nnl/z5D9LKgw3T81jqy5tMiWp+p4jYBBk3PRx1ySqLUfhF5ZMWolRzW+ -# cQZGXV38iSmdAUG0CpR5x1rMdPIrTczVUFsOYGqmkoUQ/dRiVL4iAXJLCNTj4x3Y -# wIQcCPt0ijJVinPIMAYzA8f99BbeiskyI0BHGAd0kGUX2I2/puYnlyS8toBnANjh -# 21xgvEuaZ2dvRqvWk/i1XIlO67au/XCeMTvXhPOIUmq80U32Tifw3SSiBKTyir7m -# oWH1i7H2q5QAnrBxuyy//ZsDfARDV/Atmj5jr6ATfRHDdUanQpeoBS+iylNU6RAR -# u8g+TMCu/ZndZmrs9w+8galUIGg+GmlNk07fXJ58Oc+qFqgNAsNkMi+dSzKkWGA4 -# /klJFn0XichXL8+t7KOayXKGzQja6CdtCjisnyS8hbv4PKhaeMtf68wJWKKOs0tt -# 2AJfYC5vSbH9ck8BGj2e/yQXEZEu88L5/fHK5XUk/IKXx3zaLkxXTSZ43Ea/WKXV -# BzMasHZ3Pmny0moEekAXx1UhLNNYv4Vum33VirxSB6r/GKQxFSHu7yFfrWQpYyyD -# H119TmhAedS8T1VabqdtO5ZP2E14TK82Vyxy3xEPelOo4dRIlhm7XY6k9B68MIIH -# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB -# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl -# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp -# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw -# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE -# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z -# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ -# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh -# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx -# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc -# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc -# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo -# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi -# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 -# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH -# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X -# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE -# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ -# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 -# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd -# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE -# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t -# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI -# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB -# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud -# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By -# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO -# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy -# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC -# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk -# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng -# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 -# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC -# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 -# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU -# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh -# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ -# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp -# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI -# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4 -# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G -# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw -# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U -# aGFsZXMgVFNTIEVTTjo4QTgyLUUzNEYtOUREQTElMCMGA1UEAxMcTWljcm9zb2Z0 -# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAku/zYujnqapN6BJ9 -# MJ5jtgDrlOuggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu -# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv -# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN -# BgkqhkiG9w0BAQUFAAIFAOZpNz0wIhgPMjAyMjA3MDExNzA1MDFaGA8yMDIyMDcw -# MjE3MDUwMVowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5mk3PQIBADAKAgEAAgIR -# FQIB/zAHAgEAAgIRszAKAgUA5mqIvQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor -# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA -# A4GBAFPnj5aYeiMxAJhCBC3XyLLyNYf/OubzwI5EX68hNX+TC87/MMvxocAgfMk1 -# zLkJek8rPLlWGWf2oeGQYwyOQ0OMjD5YU4LnkVjN0l027+vwT2Rafc6RIWFiQbIG -# RRBFfXDFgyuM97dVEf8ICSKsRlCCgFAV8JMNdND7JtN+xEb6MYIEDTCCBAkCAQEw -# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT -# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE -# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGZyI+vrbZ9vosA -# AQAAAZkwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B -# CRABBDAvBgkqhkiG9w0BCQQxIgQgkB6lWYhfhU1/DxjvSurvGL3RL89ss6hg4476 -# tkRKHTwwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBmfWn58qKN7WWpBTYO -# rUO1BSCSnKPLC/G7wCOIc2JsfjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w -# IFBDQSAyMDEwAhMzAAABmciPr622fb6LAAEAAAGZMCIEILRj7rb7JnV73e8wLbPq -# CULQreKtMxdbry8TgOxho5edMA0GCSqGSIb3DQEBCwUABIICAKDNJpuPLp51vjon -# QG0U01L3EGiaTgFCweFx7+x4vL5mVQ8JQBPaYJoy+34wnXF2U+0FwXW0SiTzzIHb -# 0lFXKRkSMqCPnpW/1Oz474m9w95SvR86JgRC/kuJWtyD+TZg++zCafJeaLAXnuj3 -# LbpJlbNp7P6jW5Kz05J8xrz/Q+5g16VQ7RqqHImBKubLqAHEWrRcZiOXZZuIyDT4 -# 2L1vyDMMVHjGZfghQO5on6LsAmBmS2Ne7VteWUQxVWI/0yl1e81xjLzPFmiU0PrL -# oWwI+fYVuKCYo5bwYQEhlVb5iVhNYYeNo0Voyh5Nnl75ClP/Xp/jeRb4hI9w6cdf -# rlHWIhrKmNDb2Hmu7fVOF+A4O2j32EXG7sJPMNmboMFOeQf8eTcohw0LImS/eay/ -# 6vAcPeJw7jhHpJG20zDfusn4HDR1nD4oKTENvK7EIMSheRvsxAR1KS90sQLoPkyc -# k5rkNTr8MHhvE4Nz5eeNfO7FIw2xPxwRST4p/hLes9xUw4H59TdSe9H0N92q118J -# NNo/xo7e5d6lSLoonrL/TIYbvuoqM5PGw8HPS8ve09kV00MXTAGcnxpr2j1CzMAv -# Y4i7KMGesQfOB5ounL187ZV/e+bhhHfLA0mgSnehHjXtQP/MNOvQ9/EXIK3/xcXg -# mr4TmGPRZkuxUnXFoqN0pPOr9NOY -# SIG # End signature block +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +#Helper functions for PackageManagement DSC Resouces + +Import-LocalizedData -BindingVariable LocalizedData -filename PackageManagementDscUtilities.strings.psd1 + + + Function ExtractArguments +{ + <# + .SYNOPSIS + + This is a helper function that extract the parameters from a given table. + + .PARAMETER FunctionBoundParameters + Specifies the hashtable containing a set of parameters to be extracted + + .PARAMETER ArgumentNames + Specifies A list of arguments you want to extract + #> + + Param + ( + [parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $FunctionBoundParameters, + + #A list of arguments you want to extract + [parameter(Mandatory = $true)] + [System.String[]]$ArgumentNames + ) + + Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) + + $returnValue=@{} + + foreach ($arg in $ArgumentNames) + { + if($FunctionBoundParameters.ContainsKey($arg)) + { + #Found an argument we are looking for, so we add it to return collection + $returnValue.Add($arg,$FunctionBoundParameters[$arg]) + } + } + + return $returnValue + } + +function ThrowError +{ + <# + .SYNOPSIS + + This is a helper function that throws an error. + + .PARAMETER ExceptionName + Specifies the type of errors, e.g. System.ArgumentException + + .PARAMETER ExceptionMessage + Specifies the exception message + + .PARAMETER ErrorId + Specifies an identifier of the error + + .PARAMETER ErrorCategory + Specifies the error category, e.g., InvalidArgument defined in System.Management.Automation. + + #> + + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ExceptionName, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ExceptionMessage, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ErrorId, + + [parameter(Mandatory = $true)] + [ValidateNotNull()] + [System.Management.Automation.ErrorCategory] + $ErrorCategory + ) + + Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) + + $exception = New-Object -TypeName $ExceptionName -ArgumentList $ExceptionMessage; + $errorRecord = New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList ($exception, $ErrorId, $ErrorCategory, $null) + throw $errorRecord +} + +Function ValidateArgument +{ + <# + .SYNOPSIS + + This is a helper function that validates the arguments. + + .PARAMETER Argument + Specifies the argument to be validated. + + .PARAMETER Type + Specifies the type of argument. + #> + + [CmdletBinding()] + param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [string]$Argument, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String]$Type, + + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [String]$ProviderName + ) + + Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) + + switch ($Type) + { + + "SourceUri" + { + # Checks whether given URI represents specific scheme + # Most common schemes: file, http, https, ftp + $scheme =@('http', 'https', 'file', 'ftp') + + $newUri = $Argument -as [System.URI] + $returnValue = ($newUri -and $newUri.AbsoluteURI -and ($scheme -icontains $newuri.Scheme)) + + if ($returnValue -eq $false) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage ($LocalizedData.InValidUri -f $Argument)` + -ErrorId "InValidUri" ` + -ErrorCategory InvalidArgument + } + + #Check whether it's a valid uri. Wait for the response within 2mins. + <#$result = Invoke-WebRequest $newUri -TimeoutSec 120 -UseBasicParsing -ErrorAction SilentlyContinue + + if ($null -eq (([xml]$result.Content).service )) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage ($LocalizedData.InValidUri -f $Argument)` + -ErrorId "InValidUri" ` + -ErrorCategory InvalidArgument + }#> + + } + "DestinationPath" + { + $returnValue = Test-Path -Path $Argument + if ($returnValue -eq $false) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage ($LocalizedData.PathDoesNotExist -f $Argument)` + -ErrorId "PathDoesNotExist" ` + -ErrorCategory InvalidArgument + } + } + "PackageSource" + { + #Argument can be either the package source Name or source Uri. + + #Check if the source is a uri + $uri = $Argument -as [System.URI] + + if($uri -and $uri.AbsoluteURI) + { + # Check if it's a valid Uri + ValidateArgument -Argument $Argument -Type "SourceUri" -ProviderName $ProviderName + } + else + { + #Check if it's a registered package source name + $source = PackageManagement\Get-PackageSource -Name $Argument -ProviderName $ProviderName -verbose -ErrorVariable ev + if ((-not $source) -or $ev) + { + #We do not need to throw error here as Get-PackageSource does already + Write-Verbose -Message ($LocalizedData.SourceNotFound -f $source) + } + } + } + default + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage ($LocalizedData.UnexpectedArgument -f $Type)` + -ErrorId "UnexpectedArgument" ` + -ErrorCategory InvalidArgument + } + } +} + +Function ValidateVersionArgument +{ + <# + .SYNOPSIS + + This is a helper function that does the version validation. + + .PARAMETER RequiredVersion + Provides the required version. + + .PARAMETER MaximumVersion + Provides the maximum version. + + .PARAMETER MinimumVersion + Provides the minimum version. + #> + + [CmdletBinding()] + param + ( + [string]$RequiredVersion, + [string]$MinimumVersion, + [string]$MaximumVersion + + ) + + Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) + + $isValid = $false + + #Case 1: No further check required if a user provides either none or one of these: minimumVersion, maximumVersion, and requiredVersion + if ($PSBoundParameters.Count -le 1) + { + return $true + } + + #Case 2: #If no RequiredVersion is provided + if (-not $PSBoundParameters.ContainsKey('RequiredVersion')) + { + #If no RequiredVersion, both MinimumVersion and MaximumVersion are provided. Otherwise fall into the Case #1 + $isValid = $PSBoundParameters['MinimumVersion'] -le $PSBoundParameters['MaximumVersion'] + } + + #Case 3: RequiredVersion is provided. + # In this case MinimumVersion and/or MaximumVersion also are provided. Otherwise fall in to Case #1. + # This is an invalid case. When RequiredVersion is provided, others are not allowed. so $isValid is false, which is already set in the init + + if ($isValid -eq $false) + { + ThrowError -ExceptionName "System.ArgumentException" ` + -ExceptionMessage ($LocalizedData.VersionError)` + -ErrorId "VersionError" ` + -ErrorCategory InvalidArgument + } +} + +Function Get-InstallationPolicy +{ + <# + .SYNOPSIS + + This is a helper function that retrives the InstallationPolicy from the given repository. + + .PARAMETER RepositoryName + Provides the repository Name. + + #> + + Param + ( + [parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String]$RepositoryName + ) + + Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.mycommand)) + + $repositoryobj = PackageManagement\Get-PackageSource -Name $RepositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + if ($repositoryobj) + { + return $repositoryobj.IsTrusted + } +} + +# SIG # Begin signature block +# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD+xe8u4YoS6UEO +# jtW70wceL89huvuluOvdcbeefpOXLqCCDYEwggX/MIID56ADAgECAhMzAAACUosz +# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I +# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O +# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA +# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o +# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 +# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 +# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp +# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 +# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u +# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 +# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti +# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z +# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf +# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK +# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW +# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F +# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgCZ2K5xbK +# 27tyibqtMV5AHpyNN7lNy3nCNEZ+gshCPtAwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQCyqH1a6wilw9tLp1PBwdpHqB1Ami+jaRaJh0DD1pMa +# 0Wv61B/88vYVxgOsFfBcBqZvVoQsYtlAjEcx/dg5Vamacy+LfO+8cm8uRq1uSXOq +# gn3FDJ6Xy0j3pcU44/X1uzh8KoUaQJzNpcjLn8WpqABD0w7WfIA7A+o1yMZUcUsP +# XejK8HpbV8Qrtz9okvl/hsK0zQuajckvm/odt+IgmwBr4yomlRFJ1AAqyKYp//4H +# 7gPo5CBjh3H7wHn0mXMkES24T37LXmxxSncAShECTMvJMhiuM01TN6PziqI6ER0S +# XFvKYHyfqwXvsfn71rcKAIWWjtWXz4a1mIZjkXJfUcf7oYIXADCCFvwGCisGAQQB +# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME +# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIB/6Ll6BcpT6c9557ujfliR3kAn4lZRikouo1ahJ +# /NiIAgZitNXDgmUYEzIwMjIwNzAxMjEwMDAwLjQ0OVowBIACAfSggdCkgc0wgcox +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p +# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg +# RVNOOjhBODItRTM0Ri05RERBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt +# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGZyI+vrbZ9vosAAQAAAZkw +# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 +# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh +# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN +# MjExMjAyMTkwNTE2WhcNMjMwMjI4MTkwNTE2WjCByjELMAkGA1UEBhMCVVMxEzAR +# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p +# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg +# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046OEE4Mi1FMzRGLTlE +# REExJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G +# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC4E/lXXKMsy9rVa2a8bRb0Ar/Pj4+b +# KiAgMgKayvCMFn3ddGof8eWFgJWp5JdKjWjrnmW1r9tHpcP2kFpjXp2Udrj55jt5 +# NYi1MERcoIo+E29XuCwFAMJftGdvsWea/OTQPIFsZEWqEteXdRncyVwct5xFzBIC +# 1JWCdmfc7R59RMIyvgWjIz8356mweowkOstN1fe53KIJ8flrYILIQWsNRMOT3znA +# GwIb9kyL54C6jZjFxOSusGYmVQ+Gr/qZQELw1ipx9s5jNP1LSpOpfTEBFu+y9KLN +# BmMBARkSPpTFkGEyGSwGGgSdOi6BU6FPK+6urZ830jrRemK4JkIJ9tQhlGcIhAjh +# cqZStn+38lRjVvrfbBI5EpI2NwlVIK2ibGW7sWeTAz/yNPNISUbQhGAJse/OgGj/ +# 1qz/Ha9mqfYZ8BHchNxn08nWkqyrjrKicQyxuD8mCatTrVSbOJYfQyZdHR9a4vgy +# GeZEXBYQNAlIuB37QCOAgs/VeDU8M4dc/IlrTyC0uV1SS4Gk8zV+5X5eRu+XORN8 +# FWqzI6k/9y6cWwOWMK6aUN1XqLcaF/sm9rX84eKW2lhDc3C31WLjp8UOfOHZfPuy +# y54xfilnhhCPy4QKJ9jggoqqeeEhCEfgDYjy+PByV/e5HDB2xHdtlL93wltAkI3a +# Cxo84kVPBCa0OwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFI26Vrg+nGWvrvIh0dQP +# EonENR0QMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY +# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p +# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF +# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w +# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo +# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI +# hvcNAQELBQADggIBAHGzWh29ibBNro3ns8E3EOHGsLB1Gzk90SFYUKBilIu4jDbR +# 7qbvXNd8nnl/z5D9LKgw3T81jqy5tMiWp+p4jYBBk3PRx1ySqLUfhF5ZMWolRzW+ +# cQZGXV38iSmdAUG0CpR5x1rMdPIrTczVUFsOYGqmkoUQ/dRiVL4iAXJLCNTj4x3Y +# wIQcCPt0ijJVinPIMAYzA8f99BbeiskyI0BHGAd0kGUX2I2/puYnlyS8toBnANjh +# 21xgvEuaZ2dvRqvWk/i1XIlO67au/XCeMTvXhPOIUmq80U32Tifw3SSiBKTyir7m +# oWH1i7H2q5QAnrBxuyy//ZsDfARDV/Atmj5jr6ATfRHDdUanQpeoBS+iylNU6RAR +# u8g+TMCu/ZndZmrs9w+8galUIGg+GmlNk07fXJ58Oc+qFqgNAsNkMi+dSzKkWGA4 +# /klJFn0XichXL8+t7KOayXKGzQja6CdtCjisnyS8hbv4PKhaeMtf68wJWKKOs0tt +# 2AJfYC5vSbH9ck8BGj2e/yQXEZEu88L5/fHK5XUk/IKXx3zaLkxXTSZ43Ea/WKXV +# BzMasHZ3Pmny0moEekAXx1UhLNNYv4Vum33VirxSB6r/GKQxFSHu7yFfrWQpYyyD +# H119TmhAedS8T1VabqdtO5ZP2E14TK82Vyxy3xEPelOo4dRIlhm7XY6k9B68MIIH +# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB +# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl +# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp +# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw +# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE +# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z +# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ +# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh +# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx +# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc +# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc +# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo +# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi +# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 +# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH +# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X +# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE +# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ +# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 +# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd +# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE +# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI +# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB +# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud +# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By +# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO +# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy +# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC +# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk +# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng +# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 +# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC +# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 +# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU +# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh +# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ +# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp +# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI +# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4 +# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G +# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw +# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U +# aGFsZXMgVFNTIEVTTjo4QTgyLUUzNEYtOUREQTElMCMGA1UEAxMcTWljcm9zb2Z0 +# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUAku/zYujnqapN6BJ9 +# MJ5jtgDrlOuggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv +# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN +# BgkqhkiG9w0BAQUFAAIFAOZpNz0wIhgPMjAyMjA3MDExNzA1MDFaGA8yMDIyMDcw +# MjE3MDUwMVowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5mk3PQIBADAKAgEAAgIR +# FQIB/zAHAgEAAgIRszAKAgUA5mqIvQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor +# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA +# A4GBAFPnj5aYeiMxAJhCBC3XyLLyNYf/OubzwI5EX68hNX+TC87/MMvxocAgfMk1 +# zLkJek8rPLlWGWf2oeGQYwyOQ0OMjD5YU4LnkVjN0l027+vwT2Rafc6RIWFiQbIG +# RRBFfXDFgyuM97dVEf8ICSKsRlCCgFAV8JMNdND7JtN+xEb6MYIEDTCCBAkCAQEw +# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT +# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE +# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGZyI+vrbZ9vosA +# AQAAAZkwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B +# CRABBDAvBgkqhkiG9w0BCQQxIgQgkB6lWYhfhU1/DxjvSurvGL3RL89ss6hg4476 +# tkRKHTwwgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBmfWn58qKN7WWpBTYO +# rUO1BSCSnKPLC/G7wCOIc2JsfjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFBDQSAyMDEwAhMzAAABmciPr622fb6LAAEAAAGZMCIEILRj7rb7JnV73e8wLbPq +# CULQreKtMxdbry8TgOxho5edMA0GCSqGSIb3DQEBCwUABIICAKDNJpuPLp51vjon +# QG0U01L3EGiaTgFCweFx7+x4vL5mVQ8JQBPaYJoy+34wnXF2U+0FwXW0SiTzzIHb +# 0lFXKRkSMqCPnpW/1Oz474m9w95SvR86JgRC/kuJWtyD+TZg++zCafJeaLAXnuj3 +# LbpJlbNp7P6jW5Kz05J8xrz/Q+5g16VQ7RqqHImBKubLqAHEWrRcZiOXZZuIyDT4 +# 2L1vyDMMVHjGZfghQO5on6LsAmBmS2Ne7VteWUQxVWI/0yl1e81xjLzPFmiU0PrL +# oWwI+fYVuKCYo5bwYQEhlVb5iVhNYYeNo0Voyh5Nnl75ClP/Xp/jeRb4hI9w6cdf +# rlHWIhrKmNDb2Hmu7fVOF+A4O2j32EXG7sJPMNmboMFOeQf8eTcohw0LImS/eay/ +# 6vAcPeJw7jhHpJG20zDfusn4HDR1nD4oKTENvK7EIMSheRvsxAR1KS90sQLoPkyc +# k5rkNTr8MHhvE4Nz5eeNfO7FIw2xPxwRST4p/hLes9xUw4H59TdSe9H0N92q118J +# NNo/xo7e5d6lSLoonrL/TIYbvuoqM5PGw8HPS8ve09kV00MXTAGcnxpr2j1CzMAv +# Y4i7KMGesQfOB5ounL187ZV/e+bhhHfLA0mgSnehHjXtQP/MNOvQ9/EXIK3/xcXg +# mr4TmGPRZkuxUnXFoqN0pPOr9NOY +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PSGetModuleInfo.xml b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PSGetModuleInfo.xml index f519005ee0..db6950c4b5 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PSGetModuleInfo.xml +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PSGetModuleInfo.xml @@ -1,166 +1,166 @@ - - - - Microsoft.PowerShell.Commands.PSRepositoryItemInfo - System.Management.Automation.PSCustomObject - System.Object - - - PackageManagement - 1.4.8.1 - Module - PackageManagement (a.k.a. OneGet) is a new way to discover and install software packages from around the web._x000D__x000A_ It is a manager or multiplexor of existing package managers (also called package providers) that unifies Windows package management with a single Windows PowerShell interface. With PackageManagement, you can do the following._x000D__x000A_ - Manage a list of software repositories in which packages can be searched, acquired and installed_x000D__x000A_ - Discover software packages_x000D__x000A_ - Seamlessly install, uninstall, and inventory packages from one or more software repositories - Microsoft Corporation - - - System.Object[] - System.Array - System.Object - - - PowerShellTeam - alerickson - NateLehman - krishnayalavarthi - anamnavi - - - (C) Microsoft Corporation. All rights reserved. -
2022-07-01T21:21:49-07:00
- - - - https://oneget.org/ - - - - - PackageManagement - PSEdition_Core - PSEdition_Desktop - Linux - Mac - PSModule - - - - - System.Collections.Hashtable - System.Object - - - - Command - - - - Find-Package - Get-Package - Get-PackageProvider - Get-PackageSource - Install-Package - Import-PackageProvider - Find-PackageProvider - Install-PackageProvider - Register-PackageSource - Set-PackageSource - Unregister-PackageSource - Uninstall-Package - Save-Package - - - - - Function - - - - - - - Cmdlet - - - - Find-Package - Get-Package - Get-PackageProvider - Get-PackageSource - Install-Package - Import-PackageProvider - Find-PackageProvider - Install-PackageProvider - Register-PackageSource - Set-PackageSource - Unregister-PackageSource - Uninstall-Package - Save-Package - - - - - DscResource - - - - MSFT_PackageManagement - MSFT_PackageManagementSource - - - - - Workflow - - - - RoleCapability - - - - - - ## 1.4.8.1_x000D__x000A_- Update PackageManagement's strong name signing_x000D__x000A__x000D__x000A_## 1.4.8_x000D__x000A_- Add NuGet as a source when generating nuget.config file for user in the NuGet Provider_x000D__x000A__x000D__x000A_## 1.4.7_x000D__x000A_- Update security protocol to use TLS 1.2_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_## 1.4.6_x000D__x000A_- Update `HelpInfoUri` to point to the latest content_x000D__x000A__x000D__x000A_## 1.4.5_x000D__x000A_- Bug fix for deadlock when getting parameters in an event_x000D__x000A__x000D__x000A_## 1.4.4_x000D__x000A_- Bug fix when installing modules from private feeds_x000D__x000A__x000D__x000A_ ## 1.4.3_x000D__x000A_- Another bug fix when registering repositories with PowerShellGet_x000D__x000A__x000D__x000A_## 1.4.2_x000D__x000A_- Bug fix for passing credentials from PowerShellGet when registering repositories_x000D__x000A__x000D__x000A_## 1.4.1_x000D__x000A_- Bug fix for using credential provider installed in Visual Studio_x000D__x000A__x000D__x000A_## 1.4_x000D__x000A_- Allow credential persistance for registering private repositories and finding or installing packages from those repositories_x000D__x000A__x000D__x000A_## 1.3.2_x000D__x000A_- Enable bootstrap on PSCore_x000D__x000A_- Bug fix to run on .NET Core 3.0_x000D__x000A__x000D__x000A_## 1.3.1_x000D__x000A_- Targets net452 and netstandard2.0 instead of net451, netcoreapp2.0, and netstandard1.6_x000D__x000A_ _x000D__x000A_## Previous releases are not included in this Changelog - - - - - https://www.powershellgallery.com/api/v2 - PSGallery - NuGet - - - System.Management.Automation.PSCustomObject - System.Object - - - (C) Microsoft Corporation. All rights reserved. - PackageManagement (a.k.a. OneGet) is a new way to discover and install software packages from around the web._x000D__x000A_ It is a manager or multiplexor of existing package managers (also called package providers) that unifies Windows package management with a single Windows PowerShell interface. With PackageManagement, you can do the following._x000D__x000A_ - Manage a list of software repositories in which packages can be searched, acquired and installed_x000D__x000A_ - Discover software packages_x000D__x000A_ - Seamlessly install, uninstall, and inventory packages from one or more software repositories - False - ## 1.4.8.1_x000D__x000A_- Update PackageManagement's strong name signing_x000D__x000A__x000D__x000A_## 1.4.8_x000D__x000A_- Add NuGet as a source when generating nuget.config file for user in the NuGet Provider_x000D__x000A__x000D__x000A_## 1.4.7_x000D__x000A_- Update security protocol to use TLS 1.2_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_## 1.4.6_x000D__x000A_- Update `HelpInfoUri` to point to the latest content_x000D__x000A__x000D__x000A_## 1.4.5_x000D__x000A_- Bug fix for deadlock when getting parameters in an event_x000D__x000A__x000D__x000A_## 1.4.4_x000D__x000A_- Bug fix when installing modules from private feeds_x000D__x000A__x000D__x000A_ ## 1.4.3_x000D__x000A_- Another bug fix when registering repositories with PowerShellGet_x000D__x000A__x000D__x000A_## 1.4.2_x000D__x000A_- Bug fix for passing credentials from PowerShellGet when registering repositories_x000D__x000A__x000D__x000A_## 1.4.1_x000D__x000A_- Bug fix for using credential provider installed in Visual Studio_x000D__x000A__x000D__x000A_## 1.4_x000D__x000A_- Allow credential persistance for registering private repositories and finding or installing packages from those repositories_x000D__x000A__x000D__x000A_## 1.3.2_x000D__x000A_- Enable bootstrap on PSCore_x000D__x000A_- Bug fix to run on .NET Core 3.0_x000D__x000A__x000D__x000A_## 1.3.1_x000D__x000A_- Targets net452 and netstandard2.0 instead of net451, netcoreapp2.0, and netstandard1.6_x000D__x000A_ _x000D__x000A_## Previous releases are not included in this Changelog - True - True - 38794156 - 128454435 - 1153134 - 7/1/2022 9:21:49 PM -07:00 - 7/1/2022 9:21:49 PM -07:00 - 7/5/2022 6:42:00 PM -07:00 - 3/21/2023 6:34:18 PM -07:00 - PackageManagement PSEdition_Core PSEdition_Desktop Linux Mac PSModule PSCmdlet_Find-Package PSCommand_Find-Package PSCmdlet_Get-Package PSCommand_Get-Package PSCmdlet_Get-PackageProvider PSCommand_Get-PackageProvider PSCmdlet_Get-PackageSource PSCommand_Get-PackageSource PSCmdlet_Install-Package PSCommand_Install-Package PSCmdlet_Import-PackageProvider PSCommand_Import-PackageProvider PSCmdlet_Find-PackageProvider PSCommand_Find-PackageProvider PSCmdlet_Install-PackageProvider PSCommand_Install-PackageProvider PSCmdlet_Register-PackageSource PSCommand_Register-PackageSource PSCmdlet_Set-PackageSource PSCommand_Set-PackageSource PSCmdlet_Unregister-PackageSource PSCommand_Unregister-PackageSource PSCmdlet_Uninstall-Package PSCommand_Uninstall-Package PSCmdlet_Save-Package PSCommand_Save-Package PSIncludes_Cmdlet PSDscResource_MSFT_PackageManagement PSDscResource_MSFT_PackageManagementSource PSIncludes_DscResource - False - 2023-03-21T18:34:18Z - 1.4.8.1 - Microsoft Corporation - false - Module - PackageManagement.nuspec|PackageManagement.format.ps1xml|PackageManagement.psd1|PackageManagement.psm1|PackageManagement.Resources.psd1|PackageProviderFunctions.psm1|DSCResources\PackageManagementDscUtilities.psm1|DSCResources\PackageManagementDscUtilities.strings.psd1|fullclr\Microsoft.PackageManagement.ArchiverProviders.dll|fullclr\Microsoft.PackageManagement.CoreProviders.dll|fullclr\Microsoft.PackageManagement.dll|fullclr\Microsoft.PackageManagement.MetaProvider.PowerShell.dll|fullclr\Microsoft.PackageManagement.MsiProvider.dll|fullclr\Microsoft.PackageManagement.MsuProvider.dll|fullclr\Microsoft.PackageManagement.NuGetProvider.dll|fullclr\Microsoft.PowerShell.PackageManagement.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.ArchiverProviders.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.CoreProviders.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.MetaProvider.PowerShell.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.NuGetProvider.dll|coreclr\netstandard2.0\Microsoft.PowerShell.PackageManagement.dll|DSCResources\MSFT_PackageManagement\MSFT_PackageManagement.psm1|DSCResources\MSFT_PackageManagement\MSFT_PackageManagement.schema.mof|DSCResources\MSFT_PackageManagement\MSFT_PackageManagement.strings.psd1|DSCResources\MSFT_PackageManagementSource\MSFT_PackageManagementSource.psm1|DSCResources\MSFT_PackageManagementSource\MSFT_PackageManagementSource.schema.mof|DSCResources\MSFT_PackageManagementSource\MSFT_PackageManagementSource.strings.psd1 - 4ae9fd46-338a-459c-8186-07f910774cb8 - 3.0 - 4.0 - Microsoft Corporation - - - D:\mspkg\src\PowerShell\ExternalModules\PackageManagement\1.4.8.1 -
-
-
+ + + + Microsoft.PowerShell.Commands.PSRepositoryItemInfo + System.Management.Automation.PSCustomObject + System.Object + + + PackageManagement + 1.4.8.1 + Module + PackageManagement (a.k.a. OneGet) is a new way to discover and install software packages from around the web._x000D__x000A_ It is a manager or multiplexor of existing package managers (also called package providers) that unifies Windows package management with a single Windows PowerShell interface. With PackageManagement, you can do the following._x000D__x000A_ - Manage a list of software repositories in which packages can be searched, acquired and installed_x000D__x000A_ - Discover software packages_x000D__x000A_ - Seamlessly install, uninstall, and inventory packages from one or more software repositories + Microsoft Corporation + + + System.Object[] + System.Array + System.Object + + + PowerShellTeam + alerickson + NateLehman + krishnayalavarthi + anamnavi + + + (C) Microsoft Corporation. All rights reserved. +
2022-07-01T21:21:49-07:00
+ + + + https://oneget.org/ + + + + + PackageManagement + PSEdition_Core + PSEdition_Desktop + Linux + Mac + PSModule + + + + + System.Collections.Hashtable + System.Object + + + + Command + + + + Find-Package + Get-Package + Get-PackageProvider + Get-PackageSource + Install-Package + Import-PackageProvider + Find-PackageProvider + Install-PackageProvider + Register-PackageSource + Set-PackageSource + Unregister-PackageSource + Uninstall-Package + Save-Package + + + + + Function + + + + + + + Cmdlet + + + + Find-Package + Get-Package + Get-PackageProvider + Get-PackageSource + Install-Package + Import-PackageProvider + Find-PackageProvider + Install-PackageProvider + Register-PackageSource + Set-PackageSource + Unregister-PackageSource + Uninstall-Package + Save-Package + + + + + DscResource + + + + MSFT_PackageManagement + MSFT_PackageManagementSource + + + + + Workflow + + + + RoleCapability + + + + + + ## 1.4.8.1_x000D__x000A_- Update PackageManagement's strong name signing_x000D__x000A__x000D__x000A_## 1.4.8_x000D__x000A_- Add NuGet as a source when generating nuget.config file for user in the NuGet Provider_x000D__x000A__x000D__x000A_## 1.4.7_x000D__x000A_- Update security protocol to use TLS 1.2_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_## 1.4.6_x000D__x000A_- Update `HelpInfoUri` to point to the latest content_x000D__x000A__x000D__x000A_## 1.4.5_x000D__x000A_- Bug fix for deadlock when getting parameters in an event_x000D__x000A__x000D__x000A_## 1.4.4_x000D__x000A_- Bug fix when installing modules from private feeds_x000D__x000A__x000D__x000A_ ## 1.4.3_x000D__x000A_- Another bug fix when registering repositories with PowerShellGet_x000D__x000A__x000D__x000A_## 1.4.2_x000D__x000A_- Bug fix for passing credentials from PowerShellGet when registering repositories_x000D__x000A__x000D__x000A_## 1.4.1_x000D__x000A_- Bug fix for using credential provider installed in Visual Studio_x000D__x000A__x000D__x000A_## 1.4_x000D__x000A_- Allow credential persistance for registering private repositories and finding or installing packages from those repositories_x000D__x000A__x000D__x000A_## 1.3.2_x000D__x000A_- Enable bootstrap on PSCore_x000D__x000A_- Bug fix to run on .NET Core 3.0_x000D__x000A__x000D__x000A_## 1.3.1_x000D__x000A_- Targets net452 and netstandard2.0 instead of net451, netcoreapp2.0, and netstandard1.6_x000D__x000A_ _x000D__x000A_## Previous releases are not included in this Changelog + + + + + https://www.powershellgallery.com/api/v2 + PSGallery + NuGet + + + System.Management.Automation.PSCustomObject + System.Object + + + (C) Microsoft Corporation. All rights reserved. + PackageManagement (a.k.a. OneGet) is a new way to discover and install software packages from around the web._x000D__x000A_ It is a manager or multiplexor of existing package managers (also called package providers) that unifies Windows package management with a single Windows PowerShell interface. With PackageManagement, you can do the following._x000D__x000A_ - Manage a list of software repositories in which packages can be searched, acquired and installed_x000D__x000A_ - Discover software packages_x000D__x000A_ - Seamlessly install, uninstall, and inventory packages from one or more software repositories + False + ## 1.4.8.1_x000D__x000A_- Update PackageManagement's strong name signing_x000D__x000A__x000D__x000A_## 1.4.8_x000D__x000A_- Add NuGet as a source when generating nuget.config file for user in the NuGet Provider_x000D__x000A__x000D__x000A_## 1.4.7_x000D__x000A_- Update security protocol to use TLS 1.2_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_## 1.4.6_x000D__x000A_- Update `HelpInfoUri` to point to the latest content_x000D__x000A__x000D__x000A_## 1.4.5_x000D__x000A_- Bug fix for deadlock when getting parameters in an event_x000D__x000A__x000D__x000A_## 1.4.4_x000D__x000A_- Bug fix when installing modules from private feeds_x000D__x000A__x000D__x000A_ ## 1.4.3_x000D__x000A_- Another bug fix when registering repositories with PowerShellGet_x000D__x000A__x000D__x000A_## 1.4.2_x000D__x000A_- Bug fix for passing credentials from PowerShellGet when registering repositories_x000D__x000A__x000D__x000A_## 1.4.1_x000D__x000A_- Bug fix for using credential provider installed in Visual Studio_x000D__x000A__x000D__x000A_## 1.4_x000D__x000A_- Allow credential persistance for registering private repositories and finding or installing packages from those repositories_x000D__x000A__x000D__x000A_## 1.3.2_x000D__x000A_- Enable bootstrap on PSCore_x000D__x000A_- Bug fix to run on .NET Core 3.0_x000D__x000A__x000D__x000A_## 1.3.1_x000D__x000A_- Targets net452 and netstandard2.0 instead of net451, netcoreapp2.0, and netstandard1.6_x000D__x000A_ _x000D__x000A_## Previous releases are not included in this Changelog + True + True + 38794156 + 128454435 + 1153134 + 7/1/2022 9:21:49 PM -07:00 + 7/1/2022 9:21:49 PM -07:00 + 7/5/2022 6:42:00 PM -07:00 + 3/21/2023 6:34:18 PM -07:00 + PackageManagement PSEdition_Core PSEdition_Desktop Linux Mac PSModule PSCmdlet_Find-Package PSCommand_Find-Package PSCmdlet_Get-Package PSCommand_Get-Package PSCmdlet_Get-PackageProvider PSCommand_Get-PackageProvider PSCmdlet_Get-PackageSource PSCommand_Get-PackageSource PSCmdlet_Install-Package PSCommand_Install-Package PSCmdlet_Import-PackageProvider PSCommand_Import-PackageProvider PSCmdlet_Find-PackageProvider PSCommand_Find-PackageProvider PSCmdlet_Install-PackageProvider PSCommand_Install-PackageProvider PSCmdlet_Register-PackageSource PSCommand_Register-PackageSource PSCmdlet_Set-PackageSource PSCommand_Set-PackageSource PSCmdlet_Unregister-PackageSource PSCommand_Unregister-PackageSource PSCmdlet_Uninstall-Package PSCommand_Uninstall-Package PSCmdlet_Save-Package PSCommand_Save-Package PSIncludes_Cmdlet PSDscResource_MSFT_PackageManagement PSDscResource_MSFT_PackageManagementSource PSIncludes_DscResource + False + 2023-03-21T18:34:18Z + 1.4.8.1 + Microsoft Corporation + false + Module + PackageManagement.nuspec|PackageManagement.format.ps1xml|PackageManagement.psd1|PackageManagement.psm1|PackageManagement.Resources.psd1|PackageProviderFunctions.psm1|DSCResources\PackageManagementDscUtilities.psm1|DSCResources\PackageManagementDscUtilities.strings.psd1|fullclr\Microsoft.PackageManagement.ArchiverProviders.dll|fullclr\Microsoft.PackageManagement.CoreProviders.dll|fullclr\Microsoft.PackageManagement.dll|fullclr\Microsoft.PackageManagement.MetaProvider.PowerShell.dll|fullclr\Microsoft.PackageManagement.MsiProvider.dll|fullclr\Microsoft.PackageManagement.MsuProvider.dll|fullclr\Microsoft.PackageManagement.NuGetProvider.dll|fullclr\Microsoft.PowerShell.PackageManagement.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.ArchiverProviders.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.CoreProviders.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.MetaProvider.PowerShell.dll|coreclr\netstandard2.0\Microsoft.PackageManagement.NuGetProvider.dll|coreclr\netstandard2.0\Microsoft.PowerShell.PackageManagement.dll|DSCResources\MSFT_PackageManagement\MSFT_PackageManagement.psm1|DSCResources\MSFT_PackageManagement\MSFT_PackageManagement.schema.mof|DSCResources\MSFT_PackageManagement\MSFT_PackageManagement.strings.psd1|DSCResources\MSFT_PackageManagementSource\MSFT_PackageManagementSource.psm1|DSCResources\MSFT_PackageManagementSource\MSFT_PackageManagementSource.schema.mof|DSCResources\MSFT_PackageManagementSource\MSFT_PackageManagementSource.strings.psd1 + 4ae9fd46-338a-459c-8186-07f910774cb8 + 3.0 + 4.0 + Microsoft Corporation + + + D:\mspkg\src\PowerShell\ExternalModules\PackageManagement\1.4.8.1 +
+
+
diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.Resources.psd1 b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.Resources.psd1 index 9199ffcff0..d0baf171ec 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.Resources.psd1 +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.Resources.psd1 @@ -1,228 +1,228 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Localized PackageManagement.Resources.psd1 -# -######################################################################################### - -ConvertFrom-StringData @' -###PSLOC - - OldPowerShellCoreVersion=PackageManagement no longer supports PowerShell Core '{0}'. Please install the latest version of PowerShell Core from 'https://aka.ms/i6t6o3' and try again. -###PSLOC -'@ -# SIG # Begin signature block -# MIInogYJKoZIhvcNAQcCoIInkzCCJ48CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBIBsoYrH/WngZG -# wcwl3oPmFWjeg9nVV4OusKRd1BszsaCCDYUwggYDMIID66ADAgECAhMzAAACU+OD -# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1 -# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN -# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5 -# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY -# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4 -# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw -# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh -# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW -# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v -# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw -# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov -# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx -# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB -# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ -# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF -# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR -# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa -# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR -# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA -# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz -# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j -# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y -# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq -# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK -# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV -# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv -# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm -# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw -# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE -# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD -# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG -# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la -# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc -# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D -# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ -# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk -# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 -# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd -# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL -# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd -# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 -# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS -# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI -# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL -# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD -# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv -# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF -# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h -# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA -# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn -# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 -# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b -# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ -# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy -# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp -# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi -# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb -# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS -# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL -# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX -# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXMwghlvAgEBMIGVMH4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p -# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA -# AlMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw -# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEII0M -# q4L3/1947bwnMVvGP5Dmwbb971etV96SBKgFIvZRMEIGCisGAQQBgjcCAQwxNDAy -# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20wDQYJKoZIhvcNAQEBBQAEggEAobv6NU6XVA64f3V2uUUkdzyvJ6z+Fh3abvbP -# G8Jes921e5G3kFIkII8uqVjju3kaR0V6CprgswF4sRa6DG8q6i5XGczd7uNgt4fn -# 11MLiky6r6km9N53YZHfSQunYurfHKee4D4qfYluaLCPQCSQ5Jri0I8cBAbYfb3k -# hKJaiMV+DesnimsETP0eExyCjzYgt9+calYTAZc1yCMzQxoshOp5AIsj/VFv184V -# 9p/QsfHKIfbK+eNb9VclXcSu1Nbxi/D82w5SdVVjyjViE7GyEMG76J9UZveKUlth -# FYZ+IPWT9xZ9POk4+wzGHP49jCVInEWFnmObLgF1kE/LJ4K79KGCFv0wghb5Bgor -# BgEEAYI3AwMBMYIW6TCCFuUGCSqGSIb3DQEHAqCCFtYwghbSAgEDMQ8wDQYJYIZI -# AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE -# WQoDATAxMA0GCWCGSAFlAwQCAQUABCA6fmswW5cfPwuOva/mZGh76WqyLmVw68W5 -# Kyj7PkdiJQIGYrThpN06GBMyMDIyMDcwMTIxMDAwMS45NjNaMASAAgH0oIHQpIHN -# MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL -# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjpFQUNFLUUzMTYtQzkxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCEVQwggcMMIIE9KADAgECAhMzAAABmsB1osQhbT6FAAEA -# AAGaMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTIxMTIwMjE5MDUxN1oXDTIzMDIyODE5MDUxN1owgcoxCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy -# aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkVBQ0UtRTMx -# Ni1DOTFEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC -# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2nIGrCort2RhFP5q+gObfaFw -# IG7AiatDZzrvueM2T7fWP7axB0k5aRNp+I7muFZ2nROLH9jYPMX1MQ0DzuFW/91B -# 4YXR4gpy6FCLFt8LRNjj8xxYQHFDc8bkqZOuu6JuKPxnGj5cIiDeGXQ8Ujs+qI0j -# U/Ws7Cl8EBQHLuHPbbL14rpffbInwt7NnRBCdPwYch4iQMLHFODdp5tVA3+LjAHw -# tQe0gUGS99LLD8olI1O4CIo69SEZQQHQWJoskdBe0Sb88vnYsI5tCLI93/G7FSKv -# YGZFFscRZCmS3wcpXhKOATJkTGRPfgH06a0J3upnI7VQHQS0Sl714y0lz0eoeeKb -# bbEoSmldyD+g6em10X9hm9gn3VUsbctxxwFMmV7hcILiFdjlt4Bd5BUCt7i+kGbz -# fGuigdIbaNOlffDrXstTkzr59ZkZwL1buFo/H9XXPvXDj3T4LRc+HHd+5kUTxJAH -# V9mGnk4KXDRMWvowmzkjfvlbTUnMcLuAIz6E30I7kPi9afEjGX4IE/JIWl2llmfb -# y7zuzyMCGeG9kit/15lqZNAJmk4WuUBtH7ubr3eGGf8S7iP5IsB1nE8pL4gGTpcJ -# K57KGGSSdN0bCAFr+lB52IwCPBt1IAhRZQJtJ4LkN6yF+eKZro0vN5YK5tWKmy9i -# 65YZovfDJNpLQhwlykcCAwEAAaOCATYwggEyMB0GA1UdDgQWBBRftp5Z8JzbUeml -# Wb0KlcitNivRcDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV -# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny -# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI -# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy -# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G -# CSqGSIb3DQEBCwUAA4ICAQAAE7uHzEbUR9tPpzcxgFxcXVxKUT032zNCyQ3jXuEA -# sY9BTPsKyXbulCqzNsELjt9VA3EOJ61CQXvNTeltkbxGvMTV42ztKszYrcFHzlS3 -# maeh1RnDU7WBDALyvZP/9HWgRcW6dOAczGiMmh0cu8vyv82fXJBMO4xfVbCapa8K -# pMfR6iPyAbAqSXZU7SgZf/i0Ww/LVr8OhQ60pL/yA4inGqzxNAVOv/2xV72ef4e3 -# YhNd3ar+Qz1OSp+PfR71DgHBxt9YK/0yTxH7aqiuNHX6QftWwT0swHn+fKycUSVz -# SeutRmzmeXuuBLsiEL9FaOWabWlmYn7UOaYJs7WmQrjSCL8TxwsryAI5kn0bl+1M -# pHtJNva0k67kbAVSLInxt/YJXbG8ozr5Aze0t6SbU8CVdE6AuFVoNNJKbp5O9jzk -# bqd9WoVvfX1N48QYdnx44nn42VGtPHf50EHS1gs2nbbaZGbwoB/3XPDLbNgsK3MQ -# j2eafVbhnKshYStiOj0tDzpzLn+9Ed5a5eWPO3TvH+Cr/N25IauYPiK2OSry3CBB -# EeZLebrqK6VsyZgTRgfutjlTTM/dmCRZfy7fjb5BhU7hmcvekyzD3S3KzUqTxlea -# h6px5a/8FM/VAFYkyiQK70m75P7IlO5otvaKkcW9GoQeKGFTzbr+3HB0wRqjTRqJ -# eDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL -# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH -# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV -# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X -# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR -# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p -# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh -# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM -# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm -# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB -# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb -# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO -# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw -# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW -# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w -# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK -# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2 -# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH -# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB -# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v -# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM -# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0 -# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF -# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD -# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW -# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny -# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH -# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp -# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF -# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx -# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+ -# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2 -# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw -# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7 -# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO -# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL -# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L -# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5 -# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE -# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLLMIICNAIB -# ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x -# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv -# bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE -# CxMdVGhhbGVzIFRTUyBFU046RUFDRS1FMzE2LUM5MUQxJTAjBgNVBAMTHE1pY3Jv -# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAAG6rjJ1Ampv -# 5uzsdVL/xjbNY5rvoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh -# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD -# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw -# MTAwDQYJKoZIhvcNAQEFBQACBQDmaULZMCIYDzIwMjIwNzAxMTc1NDMzWhgPMjAy -# MjA3MDIxNzU0MzNaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOZpQtkCAQAwBwIB -# AAICBV0wBwIBAAICEd8wCgIFAOZqlFkCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYK -# KwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUF -# AAOBgQBE0fx25fV4T83pZxB4CQQfrko37LMB2A+I//kXKtd0XKnuTaaJ9YQIdTEd -# 1QWiTcnM/TW+yNCfE5QNhZFzxVxS5WXksZaiA0wLTqJfA+jm2Mi0FfZefFr4L6DU -# pyKjEWvhlLiUfperOIuvHm37XN0f3LDOHWx+6CbZ1xT+jNePMzGCBA0wggQJAgEB -# MIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH -# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV -# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABmsB1osQhbT6F -# AAEAAAGaMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcN -# AQkQAQQwLwYJKoZIhvcNAQkEMSIEIF2O7hc1ZhwoL1uHKtS6plCa34qF+9dG3vi7 -# tPHcCrGRMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgAU5A4zgRFH2Z5YoC -# Yi+d/S8fp7K/zRVU5yhV9N9IjWAwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG -# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj -# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt -# cCBQQ0EgMjAxMAITMwAAAZrAdaLEIW0+hQABAAABmjAiBCDlYijfDa/nMtdfyLez -# 76eMSHmBEDD3QIEOBB5OFeXcFjANBgkqhkiG9w0BAQsFAASCAgAJWWMLeusd6nZn -# 4Q8YPFoh1x2D0C3ApP00erz4ctpJSw2NSvnCQgSKrGdzehx62PNh/yYbmUqYd6Gp -# LP3ErMpA56cVVekiF0ECBa5llP4a1UTpEcO0eZnApz93o7UJ8g0RDspFydd459dS -# 8389MfETeVGw4WiD3FV97IawqrZ2bpMzAn7nhD2s8oI36NZ6fhbykA68nzLGJXdj -# HmmoG1DSJIIgHL0IR5u8OY7LarpcJQUGcMH09bN29J8J2Z2Uzrz52Q7t4LVGb9Oj -# kT6LnOYe+zGtFZAPWm20xFHAHnDa6GkemWGGmm6kZHdBTsJSN7TmBbKEcT9k9AkJ -# yv5N7grvD6YcpOE949BRaGzGQ26JKJDzUqjJ7VUycWyEq35+QZFTGj3LS4xlgeoA -# 1en1N5dfxGEHVbhIgmfaLYrmq1L5mMZFnXrxQdaj6/6ew1ddxAnq/fcs2EIhZcWm -# htBTC+qsi5WmUxoglCGjj2iFNGs61a9B5yDFVVQaZIk/Mi2hjGINj65XF0r0vuYd -# XDklHpWC0UQe41KckrKEliYTmWQkIMjaraCubRxjpywXFwecO8TDDW6031xVwd0r -# t77jQA1+tAVjzEg5jo3T3woTA73hLZ1qCRHGUZzrpe+wRFrAGZM5eZhTHwqhYTC0 -# ym9JZhmcOB5iC6VOR/jxKibZ3kyfgg== -# SIG # End signature block +######################################################################################### +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Localized PackageManagement.Resources.psd1 +# +######################################################################################### + +ConvertFrom-StringData @' +###PSLOC + + OldPowerShellCoreVersion=PackageManagement no longer supports PowerShell Core '{0}'. Please install the latest version of PowerShell Core from 'https://aka.ms/i6t6o3' and try again. +###PSLOC +'@ +# SIG # Begin signature block +# MIInogYJKoZIhvcNAQcCoIInkzCCJ48CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBIBsoYrH/WngZG +# wcwl3oPmFWjeg9nVV4OusKRd1BszsaCCDYUwggYDMIID66ADAgECAhMzAAACU+OD +# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1 +# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN +# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5 +# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY +# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4 +# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw +# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh +# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW +# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v +# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw +# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov +# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx +# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ +# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF +# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR +# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa +# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR +# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA +# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz +# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j +# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y +# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq +# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK +# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV +# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv +# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm +# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw +# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD +# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG +# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la +# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc +# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D +# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ +# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk +# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 +# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd +# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL +# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd +# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 +# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS +# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI +# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL +# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD +# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv +# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF +# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h +# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA +# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn +# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 +# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b +# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ +# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy +# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp +# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi +# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb +# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS +# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL +# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX +# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXMwghlvAgEBMIGVMH4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p +# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA +# AlMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw +# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEII0M +# q4L3/1947bwnMVvGP5Dmwbb971etV96SBKgFIvZRMEIGCisGAQQBgjcCAQwxNDAy +# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20wDQYJKoZIhvcNAQEBBQAEggEAobv6NU6XVA64f3V2uUUkdzyvJ6z+Fh3abvbP +# G8Jes921e5G3kFIkII8uqVjju3kaR0V6CprgswF4sRa6DG8q6i5XGczd7uNgt4fn +# 11MLiky6r6km9N53YZHfSQunYurfHKee4D4qfYluaLCPQCSQ5Jri0I8cBAbYfb3k +# hKJaiMV+DesnimsETP0eExyCjzYgt9+calYTAZc1yCMzQxoshOp5AIsj/VFv184V +# 9p/QsfHKIfbK+eNb9VclXcSu1Nbxi/D82w5SdVVjyjViE7GyEMG76J9UZveKUlth +# FYZ+IPWT9xZ9POk4+wzGHP49jCVInEWFnmObLgF1kE/LJ4K79KGCFv0wghb5Bgor +# BgEEAYI3AwMBMYIW6TCCFuUGCSqGSIb3DQEHAqCCFtYwghbSAgEDMQ8wDQYJYIZI +# AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE +# WQoDATAxMA0GCWCGSAFlAwQCAQUABCA6fmswW5cfPwuOva/mZGh76WqyLmVw68W5 +# Kyj7PkdiJQIGYrThpN06GBMyMDIyMDcwMTIxMDAwMS45NjNaMASAAgH0oIHQpIHN +# MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL +# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjpFQUNFLUUzMTYtQzkxRDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCEVQwggcMMIIE9KADAgECAhMzAAABmsB1osQhbT6FAAEA +# AAGaMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTIxMTIwMjE5MDUxN1oXDTIzMDIyODE5MDUxN1owgcoxCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy +# aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkVBQ0UtRTMx +# Ni1DOTFEMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC +# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2nIGrCort2RhFP5q+gObfaFw +# IG7AiatDZzrvueM2T7fWP7axB0k5aRNp+I7muFZ2nROLH9jYPMX1MQ0DzuFW/91B +# 4YXR4gpy6FCLFt8LRNjj8xxYQHFDc8bkqZOuu6JuKPxnGj5cIiDeGXQ8Ujs+qI0j +# U/Ws7Cl8EBQHLuHPbbL14rpffbInwt7NnRBCdPwYch4iQMLHFODdp5tVA3+LjAHw +# tQe0gUGS99LLD8olI1O4CIo69SEZQQHQWJoskdBe0Sb88vnYsI5tCLI93/G7FSKv +# YGZFFscRZCmS3wcpXhKOATJkTGRPfgH06a0J3upnI7VQHQS0Sl714y0lz0eoeeKb +# bbEoSmldyD+g6em10X9hm9gn3VUsbctxxwFMmV7hcILiFdjlt4Bd5BUCt7i+kGbz +# fGuigdIbaNOlffDrXstTkzr59ZkZwL1buFo/H9XXPvXDj3T4LRc+HHd+5kUTxJAH +# V9mGnk4KXDRMWvowmzkjfvlbTUnMcLuAIz6E30I7kPi9afEjGX4IE/JIWl2llmfb +# y7zuzyMCGeG9kit/15lqZNAJmk4WuUBtH7ubr3eGGf8S7iP5IsB1nE8pL4gGTpcJ +# K57KGGSSdN0bCAFr+lB52IwCPBt1IAhRZQJtJ4LkN6yF+eKZro0vN5YK5tWKmy9i +# 65YZovfDJNpLQhwlykcCAwEAAaOCATYwggEyMB0GA1UdDgQWBBRftp5Z8JzbUeml +# Wb0KlcitNivRcDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV +# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny +# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI +# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy +# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G +# CSqGSIb3DQEBCwUAA4ICAQAAE7uHzEbUR9tPpzcxgFxcXVxKUT032zNCyQ3jXuEA +# sY9BTPsKyXbulCqzNsELjt9VA3EOJ61CQXvNTeltkbxGvMTV42ztKszYrcFHzlS3 +# maeh1RnDU7WBDALyvZP/9HWgRcW6dOAczGiMmh0cu8vyv82fXJBMO4xfVbCapa8K +# pMfR6iPyAbAqSXZU7SgZf/i0Ww/LVr8OhQ60pL/yA4inGqzxNAVOv/2xV72ef4e3 +# YhNd3ar+Qz1OSp+PfR71DgHBxt9YK/0yTxH7aqiuNHX6QftWwT0swHn+fKycUSVz +# SeutRmzmeXuuBLsiEL9FaOWabWlmYn7UOaYJs7WmQrjSCL8TxwsryAI5kn0bl+1M +# pHtJNva0k67kbAVSLInxt/YJXbG8ozr5Aze0t6SbU8CVdE6AuFVoNNJKbp5O9jzk +# bqd9WoVvfX1N48QYdnx44nn42VGtPHf50EHS1gs2nbbaZGbwoB/3XPDLbNgsK3MQ +# j2eafVbhnKshYStiOj0tDzpzLn+9Ed5a5eWPO3TvH+Cr/N25IauYPiK2OSry3CBB +# EeZLebrqK6VsyZgTRgfutjlTTM/dmCRZfy7fjb5BhU7hmcvekyzD3S3KzUqTxlea +# h6px5a/8FM/VAFYkyiQK70m75P7IlO5otvaKkcW9GoQeKGFTzbr+3HB0wRqjTRqJ +# eDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL +# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH +# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV +# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X +# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR +# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p +# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh +# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM +# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm +# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB +# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb +# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO +# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw +# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW +# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w +# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK +# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2 +# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH +# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB +# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v +# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM +# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0 +# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF +# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW +# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH +# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp +# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF +# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx +# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+ +# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2 +# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw +# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7 +# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO +# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL +# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L +# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5 +# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE +# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLLMIICNAIB +# ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE +# CxMdVGhhbGVzIFRTUyBFU046RUFDRS1FMzE2LUM5MUQxJTAjBgNVBAMTHE1pY3Jv +# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAAG6rjJ1Ampv +# 5uzsdVL/xjbNY5rvoIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh +# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD +# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw +# MTAwDQYJKoZIhvcNAQEFBQACBQDmaULZMCIYDzIwMjIwNzAxMTc1NDMzWhgPMjAy +# MjA3MDIxNzU0MzNaMHQwOgYKKwYBBAGEWQoEATEsMCowCgIFAOZpQtkCAQAwBwIB +# AAICBV0wBwIBAAICEd8wCgIFAOZqlFkCAQAwNgYKKwYBBAGEWQoEAjEoMCYwDAYK +# KwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0BAQUF +# AAOBgQBE0fx25fV4T83pZxB4CQQfrko37LMB2A+I//kXKtd0XKnuTaaJ9YQIdTEd +# 1QWiTcnM/TW+yNCfE5QNhZFzxVxS5WXksZaiA0wLTqJfA+jm2Mi0FfZefFr4L6DU +# pyKjEWvhlLiUfperOIuvHm37XN0f3LDOHWx+6CbZ1xT+jNePMzGCBA0wggQJAgEB +# MIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH +# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNV +# BAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABmsB1osQhbT6F +# AAEAAAGaMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZIhvcN +# AQkQAQQwLwYJKoZIhvcNAQkEMSIEIF2O7hc1ZhwoL1uHKtS6plCa34qF+9dG3vi7 +# tPHcCrGRMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgAU5A4zgRFH2Z5YoC +# Yi+d/S8fp7K/zRVU5yhV9N9IjWAwgZgwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEG +# A1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWlj +# cm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFt +# cCBQQ0EgMjAxMAITMwAAAZrAdaLEIW0+hQABAAABmjAiBCDlYijfDa/nMtdfyLez +# 76eMSHmBEDD3QIEOBB5OFeXcFjANBgkqhkiG9w0BAQsFAASCAgAJWWMLeusd6nZn +# 4Q8YPFoh1x2D0C3ApP00erz4ctpJSw2NSvnCQgSKrGdzehx62PNh/yYbmUqYd6Gp +# LP3ErMpA56cVVekiF0ECBa5llP4a1UTpEcO0eZnApz93o7UJ8g0RDspFydd459dS +# 8389MfETeVGw4WiD3FV97IawqrZ2bpMzAn7nhD2s8oI36NZ6fhbykA68nzLGJXdj +# HmmoG1DSJIIgHL0IR5u8OY7LarpcJQUGcMH09bN29J8J2Z2Uzrz52Q7t4LVGb9Oj +# kT6LnOYe+zGtFZAPWm20xFHAHnDa6GkemWGGmm6kZHdBTsJSN7TmBbKEcT9k9AkJ +# yv5N7grvD6YcpOE949BRaGzGQ26JKJDzUqjJ7VUycWyEq35+QZFTGj3LS4xlgeoA +# 1en1N5dfxGEHVbhIgmfaLYrmq1L5mMZFnXrxQdaj6/6ew1ddxAnq/fcs2EIhZcWm +# htBTC+qsi5WmUxoglCGjj2iFNGs61a9B5yDFVVQaZIk/Mi2hjGINj65XF0r0vuYd +# XDklHpWC0UQe41KckrKEliYTmWQkIMjaraCubRxjpywXFwecO8TDDW6031xVwd0r +# t77jQA1+tAVjzEg5jo3T3woTA73hLZ1qCRHGUZzrpe+wRFrAGZM5eZhTHwqhYTC0 +# ym9JZhmcOB5iC6VOR/jxKibZ3kyfgg== +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.format.ps1xml b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.format.ps1xml index 3412ded443..74cbf594d1 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.format.ps1xml +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.format.ps1xml @@ -1,702 +1,702 @@ - - - - - - - package - - Microsoft.PackageManagement.Packaging.SoftwareIdentity - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity - - - - - 30 - - - 16 - - - 16 - - - - - - - - - Name - - - Version - - - Source - - - Summary - - - - - - - - - GetPackage - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#GetPackage - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#GetPackage - - - - - 30 - - - 16 - - - 32 - - - - - - - - - Name - - - Version - - - Source - - - ProviderName - - - - - - - - - packageWithLongSourceName - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName - - - - - 30 - - - 16 - - - 32 - - - - - - - - - Name - - - Version - - - Source - - - Summary - - - - - - - - - packageWithLongSourceNameAndCulture - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture - - - - - 30 - - - 16 - - - 16 - - - 32 - - - - - - - - - Name - - - Version - - - Culture - - - Source - - - Summary - - - - - - - - - packageWithLongSourceNameAndName - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayLongName - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayLongName - - - - - 45 - - - 16 - - - 32 - - - - - - - - - Name - - - Version - - - Source - - - Summary - - - - - - - - packageWithLongSourceNameAndCultureAndName - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture#DisplayLongName - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture#DisplayLongName - - - - - 42 - - - 16 - - - 16 - - - 24 - - - - - - - - - Name - - - Version - - - Culture - - - Source - - - Summary - - - - - - - - - PackageWithCulture - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture - - - - - 30 - - - 16 - - - 16 - - - 16 - - - - - - - - - Name - - - Version - - - Culture - - - Source - - - Summary - - - - - - - - PackageWithLongName - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongName - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongName - - - - - 52 - - - 16 - - - 16 - - - - - - - - - Name - - - Version - - - Source - - - Summary - - - - - - - - PackageWithCultureAndLongName - - Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture#DisplayLongName - Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture#DisplayLongName - - - - - 52 - - - 16 - - - 16 - - - 16 - - - - - - - - - Name - - - Version - - - Culture - - - Source - - - Summary - - - - - - - - PackageSource - - Microsoft.PackageManagement.Packaging.PackageSource - Deserialized.Microsoft.PackageManagement.Packaging.PackageSource - - - - - 32 - - - 16 - - - 10 - - - - - - - - - Name - - - ProviderName - - - IsTrusted - - - Location - - - - - - - - - PackageProvider - - Microsoft.PackageManagement.Implementation.PackageProvider - Deserialized.Microsoft.PackageManagement.Implementation.PackageProvider - - - - - 24 - - - 16 - - - - - - - - - - Name - - - Version - - - $options = $_.DynamicOptions.Name | select-object -unique; $options -join ", " - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + package + + Microsoft.PackageManagement.Packaging.SoftwareIdentity + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity + + + + + 30 + + + 16 + + + 16 + + + + + + + + + Name + + + Version + + + Source + + + Summary + + + + + + + + + GetPackage + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#GetPackage + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#GetPackage + + + + + 30 + + + 16 + + + 32 + + + + + + + + + Name + + + Version + + + Source + + + ProviderName + + + + + + + + + packageWithLongSourceName + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName + + + + + 30 + + + 16 + + + 32 + + + + + + + + + Name + + + Version + + + Source + + + Summary + + + + + + + + + packageWithLongSourceNameAndCulture + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture + + + + + 30 + + + 16 + + + 16 + + + 32 + + + + + + + + + Name + + + Version + + + Culture + + + Source + + + Summary + + + + + + + + + packageWithLongSourceNameAndName + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayLongName + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayLongName + + + + + 45 + + + 16 + + + 32 + + + + + + + + + Name + + + Version + + + Source + + + Summary + + + + + + + + packageWithLongSourceNameAndCultureAndName + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture#DisplayLongName + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongSourceName#DisplayCulture#DisplayLongName + + + + + 42 + + + 16 + + + 16 + + + 24 + + + + + + + + + Name + + + Version + + + Culture + + + Source + + + Summary + + + + + + + + + PackageWithCulture + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture + + + + + 30 + + + 16 + + + 16 + + + 16 + + + + + + + + + Name + + + Version + + + Culture + + + Source + + + Summary + + + + + + + + PackageWithLongName + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongName + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayLongName + + + + + 52 + + + 16 + + + 16 + + + + + + + + + Name + + + Version + + + Source + + + Summary + + + + + + + + PackageWithCultureAndLongName + + Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture#DisplayLongName + Deserialized.Microsoft.PackageManagement.Packaging.SoftwareIdentity#DisplayCulture#DisplayLongName + + + + + 52 + + + 16 + + + 16 + + + 16 + + + + + + + + + Name + + + Version + + + Culture + + + Source + + + Summary + + + + + + + + PackageSource + + Microsoft.PackageManagement.Packaging.PackageSource + Deserialized.Microsoft.PackageManagement.Packaging.PackageSource + + + + + 32 + + + 16 + + + 10 + + + + + + + + + Name + + + ProviderName + + + IsTrusted + + + Location + + + + + + + + + PackageProvider + + Microsoft.PackageManagement.Implementation.PackageProvider + Deserialized.Microsoft.PackageManagement.Implementation.PackageProvider + + + + + 24 + + + 16 + + + + + + + + + + Name + + + Version + + + $options = $_.DynamicOptions.Name | select-object -unique; $options -join ", " + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psd1 b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psd1 index 925e5c1a73..9cfd387238 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psd1 +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psd1 @@ -1,313 +1,313 @@ -### -# ==++== -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -### -@{ - GUID = "4ae9fd46-338a-459c-8186-07f910774cb8" - Author = "Microsoft Corporation" - CompanyName = "Microsoft Corporation" - Copyright = "(C) Microsoft Corporation. All rights reserved." - HelpInfoUri = "https://go.microsoft.com/fwlink/?linkid=2113634" - ModuleVersion = "1.4.8.1" - PowerShellVersion = "3.0" - ClrVersion = "4.0" - RootModule = "PackageManagement.psm1" - Description = 'PackageManagement (a.k.a. OneGet) is a new way to discover and install software packages from around the web. - It is a manager or multiplexor of existing package managers (also called package providers) that unifies Windows package management with a single Windows PowerShell interface. With PackageManagement, you can do the following. - - Manage a list of software repositories in which packages can be searched, acquired and installed - - Discover software packages - - Seamlessly install, uninstall, and inventory packages from one or more software repositories' - - CmdletsToExport = @( - 'Find-Package', - 'Get-Package', - 'Get-PackageProvider', - 'Get-PackageSource', - 'Install-Package', - 'Import-PackageProvider' - 'Find-PackageProvider' - 'Install-PackageProvider' - 'Register-PackageSource', - 'Set-PackageSource', - 'Unregister-PackageSource', - 'Uninstall-Package' - 'Save-Package' - ) - - FormatsToProcess = @('PackageManagement.format.ps1xml') - - PrivateData = @{ - PSData = @{ - Tags = @('PackageManagement', 'PSEdition_Core', 'PSEdition_Desktop', 'Linux', 'Mac') - ProjectUri = 'https://oneget.org' - ReleaseNotes = @' -## 1.4.8.1 -- Update PackageManagement's strong name signing - -## 1.4.8 -- Add NuGet as a source when generating nuget.config file for user in the NuGet Provider - -## 1.4.7 -- Update security protocol to use TLS 1.2 -- Remove catalog file - -## 1.4.6 -- Update `HelpInfoUri` to point to the latest content - -## 1.4.5 -- Bug fix for deadlock when getting parameters in an event - -## 1.4.4 -- Bug fix when installing modules from private feeds - - ## 1.4.3 -- Another bug fix when registering repositories with PowerShellGet - -## 1.4.2 -- Bug fix for passing credentials from PowerShellGet when registering repositories - -## 1.4.1 -- Bug fix for using credential provider installed in Visual Studio - -## 1.4 -- Allow credential persistance for registering private repositories and finding or installing packages from those repositories - -## 1.3.2 -- Enable bootstrap on PSCore -- Bug fix to run on .NET Core 3.0 - -## 1.3.1 -- Targets net452 and netstandard2.0 instead of net451, netcoreapp2.0, and netstandard1.6 - -## Previous releases are not included in this Changelog -'@ - } - } -} - -# SIG # Begin signature block -# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCANw97w1D+bi5LY -# 8ZEuubcA0tI0Z0h+CImFRYop+IIqQaCCDYEwggX/MIID56ADAgECAhMzAAACUosz -# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I -# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O -# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA -# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o -# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 -# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 -# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp -# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 -# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u -# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 -# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti -# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z -# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf -# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK -# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW -# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F -# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgiRM0SyHn -# 1V4nqTIo3jXRvJycxDAVVlA2FtX6n3qWqfwwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQCPJRvOZr6CasCkw1WMLfBA6LopbQ7shmcqRwM1eDEa -# 20F09jJVj45dL1xe5F1nEFixo3ejN83D6X9Tm96NfNf5vjqC4AfG8M+4/NGo4Vta -# doFQs1KjdLELNAZZFY7i241P9+ayzDTh5ON5F6BehV4Sex00Kbg0mHB76GhBKUJL -# PqfBbc4KIurIA/Rk1wtXWTuGKdL3+eOVn+DscOEv+0Jjlgzr5UvkFw7tVXhS2Y9z -# dxL8TBqkugR60I+IHB0yiAQwePAqfnVHwPoX0EeldwdwN/B9Iidei1wOa6+ddWr7 -# jxz8J4fLzH7k3V3xRDF+z14+6BbOL7dnX3Q7TEDnTIJgoYIXADCCFvwGCisGAQQB -# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME -# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIMfYTjqpmIZRLR5Dn8vRnb89EjdawUtU1A43DUFK -# mjO1AgZitJ9dXI0YEzIwMjIwNzAxMjEwMDAwLjQ4N1owBIACAfSggdCkgc0wgcox -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p -# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg -# RVNOOkU1QTYtRTI3Qy01OTJFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt -# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGVt/wN1uM3MSUAAQAAAZUw -# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 -# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh -# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN -# MjExMjAyMTkwNTEyWhcNMjMwMjI4MTkwNTEyWjCByjELMAkGA1UEBhMCVVMxEzAR -# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p -# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg -# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RTVBNi1FMjdDLTU5 -# MkUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G -# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfbUEMZ7ZLOz9aoRCeJL4hhT9Q8JZB -# 2xaVlMNCt3bwhcTI5GLPrt2e93DAsmlqOzw1cFiPPg6S5sLCXz7LbbUQpLha8S4v -# 2qccMtTokEaDQS+QJErnAsl6VSmRvAy0nlj+C/PaZuLb3OzY0ARw7UeCZLpyWPPH -# +k5MdYj6NUDTNoXqbzQHCuPs+fgIoro5y3DHoO077g6Ir2THIx1yfVFEt5zDcFPO -# YMg4yBi4A6Xc3hm9tZ6w849nBvVKwm5YALfH3y/f3n4LnN61b1wzAx3ZCZjf13UK -# bpE7p6DYJrHRB/+pwFjG99TwHH6uXzDeZT6/r6qH7AABwn8fpYc1TmleFY8YRuVz -# zjp9VkPHV8VzvzLL7QK2kteeXLL/Y4lvjL6hzyOmE+1LVD3lEbYho1zCt+F7bU+F -# pjyBfTC4i/wHsptb218YlbkQt1i1B6llmJwVFwCLX7gxQ48QIGUacMy8kp1+zczY -# +SxlpaEgNmQkfc1raPh9y5sMa6X48+x0K7B8OqDoXcTiECIjJetxwtuBlQseJ05H -# RfisfgFm09kG7vdHEo3NbUuMMBFikc4boN9Ufm0iUhq/JtqV0Kwrv9Cv3ayDgdNw -# EWiL2a65InEWSpRTYfsCQ03eqEh5A3rwV/KfUFcit+DrP+9VcDpjWRsCokZv4tgn -# 5qAXNMtHa8NiqQIDAQABo4IBNjCCATIwHQYDVR0OBBYEFKuX02ICFFdXgrcCBmDJ -# fH5v/KkXMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY -# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p -# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF -# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w -# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo -# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI -# hvcNAQELBQADggIBAOCzNt4fJ+jOvQuq0Itn37IZrYNBGswAi+IAFM3YGK/wGQlE -# ncgjmNBuac95W2fAL6xtFVfMfkeqSLMLqoidVsU9Bm4DEBjaWNOT9uX/tcYiJSfF -# QM0rDbrl8V4nM88RZF56G/qJW9g5dIqOSoimzKUt/Q7WH6VByW0sar5wGvgovK3q -# FadwKShzRYcEqTkHH2zip5e73jezPHx2+taYqJG5xJzdDErZ1nMixRjaHs3Kpcsm -# ZYuxsIRfBYOJvAFGymTGRv5PuwsNps9Ech1Aasq84H/Y/8xN3GQj4P3MiDn8izUB -# DCuXIfHYk39bqnaAmFbUiCby+WWpuzdk4oDKz/sWwrnsoQ72uEGVEN7+kyw9+HSo -# 5i8l8Zg1Ymj9tUgDpVUGjAduoLyHQ7XqknKmS9kJSBKk4okEDg0Id6LeKLQwH1e4 -# aVeTyUYwcBX3wg7pLJQWvR7na2SGrtl/23YGQTudmWOryhx9lnU7KBGV/aNvz0tT -# pcsucsK+cZFKDEkWB/oUFVrtyun6ND5pYZNj0CgRup5grVACq/Agb+EOGLCD+zEt -# GNop4tfKvsYb64257NJ9XrMHgpCib76WT34RPmCBByxLUkHxHq5zCyYNu0IFXAt1 -# AVicw14M+czLYIVM7NOyVpFdcB1B9MiJik7peSii0XTRdl5/V/KscTaCBFz3MIIH -# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB -# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl -# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp -# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw -# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE -# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z -# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ -# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh -# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx -# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc -# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc -# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo -# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi -# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 -# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH -# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X -# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE -# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ -# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 -# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd -# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE -# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t -# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI -# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB -# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud -# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By -# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO -# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy -# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC -# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk -# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng -# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 -# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC -# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 -# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU -# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh -# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ -# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp -# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI -# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4 -# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G -# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw -# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U -# aGFsZXMgVFNTIEVTTjpFNUE2LUUyN0MtNTkyRTElMCMGA1UEAxMcTWljcm9zb2Z0 -# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA0Y+CyLezGgVHWFNm -# KI1LuE/hY6uggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu -# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv -# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN -# BgkqhkiG9w0BAQUFAAIFAOZpqYQwIhgPMjAyMjA3MDIwMTEyMzZaGA8yMDIyMDcw -# MzAxMTIzNlowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5mmphAIBADAKAgEAAgIe -# WAIB/zAHAgEAAgIR3TAKAgUA5mr7BAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor -# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA -# A4GBAGizLxBkK6B2oQmB80+aM7sOfXuAgkYj93m3glu2ZQ242mTevtDINaDoV+qc -# 7BhagNfrWoCVESVZEjaqCYrV/VVxnmuKU0VXJoUnLcNW0cmK3aTV1fENg1wMmi/r -# TFKkd1udFGkqk+aoUxPKMtV19/TbRjUL/5qxCuVE0uACxjXOMYIEDTCCBAkCAQEw -# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT -# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE -# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGVt/wN1uM3MSUA -# AQAAAZUwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B -# CRABBDAvBgkqhkiG9w0BCQQxIgQg7wxBntfZfrgc+uuByghKGEFJWZuIL6DEXRVE -# WWTTqNowgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBc5kvhjZALe2mhIz/Q -# d7keVOmA/cC1dzKZT4ybLEkCxzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w -# IFBDQSAyMDEwAhMzAAABlbf8DdbjNzElAAEAAAGVMCIEIKYVsiXt/OMHtVXLwf4B -# pSdKHBzozlb2nRtmmiDl84fKMA0GCSqGSIb3DQEBCwUABIICAIVHstuzLLngbRuA -# f6xugLOjcbg1MNZdn9l1UIHXSFSPGQWqiL2dK4o6sjWpDFkdt2muopWbZRGFBmGw -# VIuZeYRztWON1VcW177otKa1hlV7WyF/VvEqytpXyjybORUwmCkKyLOxl9X6yzWi -# INaJPOYk09kAQkUFt/MNYIsxr++dQX14DOoJxm9tpCEvIYl9mnUU+iQnE5B9AEMw -# +nC4D7IMA1+6smM7fbSJa7o4BHfyje8PHB3w9GF223mZTG0EhBlultQkMSpV/c88 -# 9hsbwx16Cr5sY9M/lSRt4oC3qzSuTmYd6VYJ/ILt9ptrpOkaYCiXXRx8Cfz7w53w -# Au/J8xJjNWvrKxkcc8XiUXPfGGTXujyiS2MqvztBkg6wCduFKqogmvOtQiiwQQxE -# G6lU/rss27omoTUc41EawOr1km5y+fUS9aoYX9K8NNhFH6TSni3dp/+Hiyif1T7X -# g0cBy4yHuYxMmRrFcmGeplW3KhXHfkJjbHaVs1QgnRfkgFuypwF5YoFWrW7Xgj+a -# ZCDKSoYq45E4v0ryIvyu0shBoHQXREAzpBv3L9h5A9vEFQG4alCI57oSbdqJ1YIa -# ggkTQHR2CWdB7FnQilCqqZjSnAtXYZh/RD+PX6fg1UyUUQf5ohnw951pQeKYTYHm -# Fwut+RibzdbHEF/kLZr6SZsDupCv -# SIG # End signature block +### +# ==++== +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +### +@{ + GUID = "4ae9fd46-338a-459c-8186-07f910774cb8" + Author = "Microsoft Corporation" + CompanyName = "Microsoft Corporation" + Copyright = "(C) Microsoft Corporation. All rights reserved." + HelpInfoUri = "https://go.microsoft.com/fwlink/?linkid=2113634" + ModuleVersion = "1.4.8.1" + PowerShellVersion = "3.0" + ClrVersion = "4.0" + RootModule = "PackageManagement.psm1" + Description = 'PackageManagement (a.k.a. OneGet) is a new way to discover and install software packages from around the web. + It is a manager or multiplexor of existing package managers (also called package providers) that unifies Windows package management with a single Windows PowerShell interface. With PackageManagement, you can do the following. + - Manage a list of software repositories in which packages can be searched, acquired and installed + - Discover software packages + - Seamlessly install, uninstall, and inventory packages from one or more software repositories' + + CmdletsToExport = @( + 'Find-Package', + 'Get-Package', + 'Get-PackageProvider', + 'Get-PackageSource', + 'Install-Package', + 'Import-PackageProvider' + 'Find-PackageProvider' + 'Install-PackageProvider' + 'Register-PackageSource', + 'Set-PackageSource', + 'Unregister-PackageSource', + 'Uninstall-Package' + 'Save-Package' + ) + + FormatsToProcess = @('PackageManagement.format.ps1xml') + + PrivateData = @{ + PSData = @{ + Tags = @('PackageManagement', 'PSEdition_Core', 'PSEdition_Desktop', 'Linux', 'Mac') + ProjectUri = 'https://oneget.org' + ReleaseNotes = @' +## 1.4.8.1 +- Update PackageManagement's strong name signing + +## 1.4.8 +- Add NuGet as a source when generating nuget.config file for user in the NuGet Provider + +## 1.4.7 +- Update security protocol to use TLS 1.2 +- Remove catalog file + +## 1.4.6 +- Update `HelpInfoUri` to point to the latest content + +## 1.4.5 +- Bug fix for deadlock when getting parameters in an event + +## 1.4.4 +- Bug fix when installing modules from private feeds + + ## 1.4.3 +- Another bug fix when registering repositories with PowerShellGet + +## 1.4.2 +- Bug fix for passing credentials from PowerShellGet when registering repositories + +## 1.4.1 +- Bug fix for using credential provider installed in Visual Studio + +## 1.4 +- Allow credential persistance for registering private repositories and finding or installing packages from those repositories + +## 1.3.2 +- Enable bootstrap on PSCore +- Bug fix to run on .NET Core 3.0 + +## 1.3.1 +- Targets net452 and netstandard2.0 instead of net451, netcoreapp2.0, and netstandard1.6 + +## Previous releases are not included in this Changelog +'@ + } + } +} + +# SIG # Begin signature block +# MIInoQYJKoZIhvcNAQcCoIInkjCCJ44CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCANw97w1D+bi5LY +# 8ZEuubcA0tI0Z0h+CImFRYop+IIqQaCCDYEwggX/MIID56ADAgECAhMzAAACUosz +# qviV8znbAAAAAAJSMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMjU5WhcNMjIwOTAxMTgzMjU5WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDQ5M+Ps/X7BNuv5B/0I6uoDwj0NJOo1KrVQqO7ggRXccklyTrWL4xMShjIou2I +# sbYnF67wXzVAq5Om4oe+LfzSDOzjcb6ms00gBo0OQaqwQ1BijyJ7NvDf80I1fW9O +# L76Kt0Wpc2zrGhzcHdb7upPrvxvSNNUvxK3sgw7YTt31410vpEp8yfBEl/hd8ZzA +# v47DCgJ5j1zm295s1RVZHNp6MoiQFVOECm4AwK2l28i+YER1JO4IplTH44uvzX9o +# RnJHaMvWzZEpozPy4jNO2DDqbcNs4zh7AWMhE1PWFVA+CHI/En5nASvCvLmuR/t8 +# q4bc8XR8QIZJQSp+2U6m2ldNAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUNZJaEUGL2Guwt7ZOAu4efEYXedEw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDY3NTk3MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAFkk3 +# uSxkTEBh1NtAl7BivIEsAWdgX1qZ+EdZMYbQKasY6IhSLXRMxF1B3OKdR9K/kccp +# kvNcGl8D7YyYS4mhCUMBR+VLrg3f8PUj38A9V5aiY2/Jok7WZFOAmjPRNNGnyeg7 +# l0lTiThFqE+2aOs6+heegqAdelGgNJKRHLWRuhGKuLIw5lkgx9Ky+QvZrn/Ddi8u +# TIgWKp+MGG8xY6PBvvjgt9jQShlnPrZ3UY8Bvwy6rynhXBaV0V0TTL0gEx7eh/K1 +# o8Miaru6s/7FyqOLeUS4vTHh9TgBL5DtxCYurXbSBVtL1Fj44+Od/6cmC9mmvrti +# yG709Y3Rd3YdJj2f3GJq7Y7KdWq0QYhatKhBeg4fxjhg0yut2g6aM1mxjNPrE48z +# 6HWCNGu9gMK5ZudldRw4a45Z06Aoktof0CqOyTErvq0YjoE4Xpa0+87T/PVUXNqf +# 7Y+qSU7+9LtLQuMYR4w3cSPjuNusvLf9gBnch5RqM7kaDtYWDgLyB42EfsxeMqwK +# WwA+TVi0HrWRqfSx2olbE56hJcEkMjOSKz3sRuupFCX3UroyYf52L+2iVTrda8XW +# esPG62Mnn3T8AuLfzeJFuAbfOSERx7IFZO92UPoXE1uEjL5skl1yTZB3MubgOA4F +# 8KoRNhviFAEST+nG8c8uIsbZeb08SeYQMqjVEmkwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIZdjCCGXICAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAlKLM6r4lfM52wAAAAACUjAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgiRM0SyHn +# 1V4nqTIo3jXRvJycxDAVVlA2FtX6n3qWqfwwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQCPJRvOZr6CasCkw1WMLfBA6LopbQ7shmcqRwM1eDEa +# 20F09jJVj45dL1xe5F1nEFixo3ejN83D6X9Tm96NfNf5vjqC4AfG8M+4/NGo4Vta +# doFQs1KjdLELNAZZFY7i241P9+ayzDTh5ON5F6BehV4Sex00Kbg0mHB76GhBKUJL +# PqfBbc4KIurIA/Rk1wtXWTuGKdL3+eOVn+DscOEv+0Jjlgzr5UvkFw7tVXhS2Y9z +# dxL8TBqkugR60I+IHB0yiAQwePAqfnVHwPoX0EeldwdwN/B9Iidei1wOa6+ddWr7 +# jxz8J4fLzH7k3V3xRDF+z14+6BbOL7dnX3Q7TEDnTIJgoYIXADCCFvwGCisGAQQB +# gjcDAwExghbsMIIW6AYJKoZIhvcNAQcCoIIW2TCCFtUCAQMxDzANBglghkgBZQME +# AgEFADCCAVEGCyqGSIb3DQEJEAEEoIIBQASCATwwggE4AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIMfYTjqpmIZRLR5Dn8vRnb89EjdawUtU1A43DUFK +# mjO1AgZitJ9dXI0YEzIwMjIwNzAxMjEwMDAwLjQ4N1owBIACAfSggdCkgc0wgcox +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1p +# Y3Jvc29mdCBBbWVyaWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1Mg +# RVNOOkU1QTYtRTI3Qy01OTJFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFt +# cCBTZXJ2aWNloIIRVzCCBwwwggT0oAMCAQICEzMAAAGVt/wN1uM3MSUAAQAAAZUw +# DQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0 +# b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3Jh +# dGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAwHhcN +# MjExMjAyMTkwNTEyWhcNMjMwMjI4MTkwNTEyWjCByjELMAkGA1UEBhMCVVMxEzAR +# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p +# Y3Jvc29mdCBDb3Jwb3JhdGlvbjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2Eg +# T3BlcmF0aW9uczEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046RTVBNi1FMjdDLTU5 +# MkUxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2UwggIiMA0G +# CSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCfbUEMZ7ZLOz9aoRCeJL4hhT9Q8JZB +# 2xaVlMNCt3bwhcTI5GLPrt2e93DAsmlqOzw1cFiPPg6S5sLCXz7LbbUQpLha8S4v +# 2qccMtTokEaDQS+QJErnAsl6VSmRvAy0nlj+C/PaZuLb3OzY0ARw7UeCZLpyWPPH +# +k5MdYj6NUDTNoXqbzQHCuPs+fgIoro5y3DHoO077g6Ir2THIx1yfVFEt5zDcFPO +# YMg4yBi4A6Xc3hm9tZ6w849nBvVKwm5YALfH3y/f3n4LnN61b1wzAx3ZCZjf13UK +# bpE7p6DYJrHRB/+pwFjG99TwHH6uXzDeZT6/r6qH7AABwn8fpYc1TmleFY8YRuVz +# zjp9VkPHV8VzvzLL7QK2kteeXLL/Y4lvjL6hzyOmE+1LVD3lEbYho1zCt+F7bU+F +# pjyBfTC4i/wHsptb218YlbkQt1i1B6llmJwVFwCLX7gxQ48QIGUacMy8kp1+zczY +# +SxlpaEgNmQkfc1raPh9y5sMa6X48+x0K7B8OqDoXcTiECIjJetxwtuBlQseJ05H +# RfisfgFm09kG7vdHEo3NbUuMMBFikc4boN9Ufm0iUhq/JtqV0Kwrv9Cv3ayDgdNw +# EWiL2a65InEWSpRTYfsCQ03eqEh5A3rwV/KfUFcit+DrP+9VcDpjWRsCokZv4tgn +# 5qAXNMtHa8NiqQIDAQABo4IBNjCCATIwHQYDVR0OBBYEFKuX02ICFFdXgrcCBmDJ +# fH5v/KkXMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMF8GA1UdHwRY +# MFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01p +# Y3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEpLmNybDBsBggrBgEF +# BQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w +# a2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUyMFBDQSUyMDIwMTAo +# MSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYBBQUHAwgwDQYJKoZI +# hvcNAQELBQADggIBAOCzNt4fJ+jOvQuq0Itn37IZrYNBGswAi+IAFM3YGK/wGQlE +# ncgjmNBuac95W2fAL6xtFVfMfkeqSLMLqoidVsU9Bm4DEBjaWNOT9uX/tcYiJSfF +# QM0rDbrl8V4nM88RZF56G/qJW9g5dIqOSoimzKUt/Q7WH6VByW0sar5wGvgovK3q +# FadwKShzRYcEqTkHH2zip5e73jezPHx2+taYqJG5xJzdDErZ1nMixRjaHs3Kpcsm +# ZYuxsIRfBYOJvAFGymTGRv5PuwsNps9Ech1Aasq84H/Y/8xN3GQj4P3MiDn8izUB +# DCuXIfHYk39bqnaAmFbUiCby+WWpuzdk4oDKz/sWwrnsoQ72uEGVEN7+kyw9+HSo +# 5i8l8Zg1Ymj9tUgDpVUGjAduoLyHQ7XqknKmS9kJSBKk4okEDg0Id6LeKLQwH1e4 +# aVeTyUYwcBX3wg7pLJQWvR7na2SGrtl/23YGQTudmWOryhx9lnU7KBGV/aNvz0tT +# pcsucsK+cZFKDEkWB/oUFVrtyun6ND5pYZNj0CgRup5grVACq/Agb+EOGLCD+zEt +# GNop4tfKvsYb64257NJ9XrMHgpCib76WT34RPmCBByxLUkHxHq5zCyYNu0IFXAt1 +# AVicw14M+czLYIVM7NOyVpFdcB1B9MiJik7peSii0XTRdl5/V/KscTaCBFz3MIIH +# cTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkqhkiG9w0BAQsFADCB +# iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1Jl +# ZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMp +# TWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMjEw +# OTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UE +# CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9z +# b2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQ +# Q0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAOThpkzntHIh +# C3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+F2Az/1xPx2b3lVNx +# WuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU88V29YZQ3MFEyHFc +# UTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqYO7oaezOtgFt+jBAc +# nVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzpcGkNyjYtcI4xyDUo +# veO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0XnRm7KMtXAhjBcTyzi +# YrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1zcRfNN0Sidb9pSB9 +# fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZNN3SUHDSCD/AQ8rdH +# GO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLRvWoYWmEBc8pnol7X +# KHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTYuVD5C4lh8zYGNRiE +# R9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUXk8A8FdsaN8cIFRg/ +# eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB2TASBgkrBgEEAYI3 +# FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKRPEY1Kc8Q/y8E7jAd +# BgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0gBFUwUzBRBgwrBgEE +# AYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29t +# L3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQMMAoGCCsGAQUFBwMI +# MBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIBhjAPBgNVHRMB +# Af8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fOmhjEMFYGA1Ud +# HwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3By +# b2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggrBgEFBQcBAQRO +# MEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2Vy +# dHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqGSIb3DQEBCwUAA4IC +# AQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOXPTEztTnXwnE2P9pk +# bHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6cqYJWAAOwBb6J6Gng +# ugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/zjj3G82jfZfakVqr3 +# lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz/AyeixmJ5/ALaoHC +# gRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyRgNI95ko+ZjtPu4b6 +# MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdUbZ1jdEgssU5HLcEU +# BHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo3GcZKCS6OEuabvsh +# VGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4Ku+xBZj1p/cvBQUl+ +# fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10CgaiQuPNtq6TPmb/wrp +# NPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9vMvpe784cETRkPHI +# qzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGCAs4wggI3AgEBMIH4 +# oYHQpIHNMIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4G +# A1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUw +# IwYDVQQLExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1U +# aGFsZXMgVFNTIEVTTjpFNUE2LUUyN0MtNTkyRTElMCMGA1UEAxMcTWljcm9zb2Z0 +# IFRpbWUtU3RhbXAgU2VydmljZaIjCgEBMAcGBSsOAwIaAxUA0Y+CyLezGgVHWFNm +# KI1LuE/hY6uggYMwgYCkfjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu +# Z3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv +# cmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDAN +# BgkqhkiG9w0BAQUFAAIFAOZpqYQwIhgPMjAyMjA3MDIwMTEyMzZaGA8yMDIyMDcw +# MzAxMTIzNlowdzA9BgorBgEEAYRZCgQBMS8wLTAKAgUA5mmphAIBADAKAgEAAgIe +# WAIB/zAHAgEAAgIR3TAKAgUA5mr7BAIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgor +# BgEEAYRZCgMCoAowCAIBAAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUA +# A4GBAGizLxBkK6B2oQmB80+aM7sOfXuAgkYj93m3glu2ZQ242mTevtDINaDoV+qc +# 7BhagNfrWoCVESVZEjaqCYrV/VVxnmuKU0VXJoUnLcNW0cmK3aTV1fENg1wMmi/r +# TFKkd1udFGkqk+aoUxPKMtV19/TbRjUL/5qxCuVE0uACxjXOMYIEDTCCBAkCAQEw +# gZMwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcT +# B1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UE +# AxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAGVt/wN1uM3MSUA +# AQAAAZUwDQYJYIZIAWUDBAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0B +# CRABBDAvBgkqhkiG9w0BCQQxIgQg7wxBntfZfrgc+uuByghKGEFJWZuIL6DEXRVE +# WWTTqNowgfoGCyqGSIb3DQEJEAIvMYHqMIHnMIHkMIG9BCBc5kvhjZALe2mhIz/Q +# d7keVOmA/cC1dzKZT4ybLEkCxzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1w +# IFBDQSAyMDEwAhMzAAABlbf8DdbjNzElAAEAAAGVMCIEIKYVsiXt/OMHtVXLwf4B +# pSdKHBzozlb2nRtmmiDl84fKMA0GCSqGSIb3DQEBCwUABIICAIVHstuzLLngbRuA +# f6xugLOjcbg1MNZdn9l1UIHXSFSPGQWqiL2dK4o6sjWpDFkdt2muopWbZRGFBmGw +# VIuZeYRztWON1VcW177otKa1hlV7WyF/VvEqytpXyjybORUwmCkKyLOxl9X6yzWi +# INaJPOYk09kAQkUFt/MNYIsxr++dQX14DOoJxm9tpCEvIYl9mnUU+iQnE5B9AEMw +# +nC4D7IMA1+6smM7fbSJa7o4BHfyje8PHB3w9GF223mZTG0EhBlultQkMSpV/c88 +# 9hsbwx16Cr5sY9M/lSRt4oC3qzSuTmYd6VYJ/ILt9ptrpOkaYCiXXRx8Cfz7w53w +# Au/J8xJjNWvrKxkcc8XiUXPfGGTXujyiS2MqvztBkg6wCduFKqogmvOtQiiwQQxE +# G6lU/rss27omoTUc41EawOr1km5y+fUS9aoYX9K8NNhFH6TSni3dp/+Hiyif1T7X +# g0cBy4yHuYxMmRrFcmGeplW3KhXHfkJjbHaVs1QgnRfkgFuypwF5YoFWrW7Xgj+a +# ZCDKSoYq45E4v0ryIvyu0shBoHQXREAzpBv3L9h5A9vEFQG4alCI57oSbdqJ1YIa +# ggkTQHR2CWdB7FnQilCqqZjSnAtXYZh/RD+PX6fg1UyUUQf5ohnw951pQeKYTYHm +# Fwut+RibzdbHEF/kLZr6SZsDupCv +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psm1 b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psm1 index 7e8095f135..e84f6a01d2 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psm1 +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageManagement.psm1 @@ -1,269 +1,269 @@ -# -# Script module for module 'PackageManagement' -# -Set-StrictMode -Version Latest -Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PackageManagement.Resources.psd1 - -# Summary: PackageManagement is supported on Windows PowerShell 3.0 or later, Nano Server and PowerShellCore -$isCore = ($PSVersionTable.Keys -contains "PSEdition") -and ($PSVersionTable.PSEdition -ne 'Desktop') -$binarySubPath = '' -if ($isCore) -{ - $binarySubPath = Join-Path -Path 'coreclr' -ChildPath 'netstandard2.0' -} else { - $binarySubPath = 'fullclr' -} - -# Set up some helper variables to make it easier to work with the module -$script:PSModule = $ExecutionContext.SessionState.Module -$script:PSModuleRoot = $script:PSModule.ModuleBase - -$script:PkgMgmt = 'Microsoft.PackageManagement.dll' -$script:PSPkgMgmt = 'Microsoft.PowerShell.PackageManagement.dll' - - -# Try to import the OneGet assemblies at the same directory regardless fullclr or coreclr -$OneGetModulePath = Join-Path -Path $script:PSModuleRoot -ChildPath $script:PkgMgmt -$binaryModuleRoot = $script:PSModuleRoot - - -if(-not (Test-Path -Path $OneGetModulePath)) -{ - # Import the appropriate nested binary module based on the current PowerShell version - $binaryModuleRoot = Join-Path -Path $script:PSModuleRoot -ChildPath $binarySubPath - $OneGetModulePath = Join-Path -Path $binaryModuleRoot -ChildPath $script:PkgMgmt -} - -$PSOneGetModulePath = Join-Path -Path $binaryModuleRoot -ChildPath $script:PSPkgMgmt -$OneGetModule = Import-Module -Name $OneGetModulePath -PassThru -$PSOneGetModule = Import-Module -Name $PSOneGetModulePath -PassThru - - -# When the module is unloaded, remove the nested binary module that was loaded with it -if($OneGetModule) -{ - $script:PSModule.OnRemove = { - Remove-Module -ModuleInfo $OneGetModule - } -} - -if($PSOneGetModule) -{ - $script:PSModule.OnRemove = { - Remove-Module -ModuleInfo $PSOneGetModule - } -} -# SIG # Begin signature block -# MIInpQYJKoZIhvcNAQcCoIInljCCJ5ICAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAu+2bjkKZwIc24 -# sUnQmGnTVuZ6xttIA9Ea89zWbpdeVaCCDYUwggYDMIID66ADAgECAhMzAAACU+OD -# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1 -# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN -# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5 -# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY -# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4 -# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw -# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh -# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW -# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v -# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw -# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov -# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx -# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB -# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ -# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF -# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR -# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa -# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR -# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA -# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz -# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j -# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y -# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq -# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK -# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV -# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv -# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm -# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw -# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE -# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD -# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG -# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la -# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc -# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D -# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ -# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk -# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 -# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd -# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL -# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd -# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 -# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS -# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI -# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL -# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD -# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv -# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF -# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h -# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA -# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn -# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 -# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b -# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ -# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy -# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp -# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi -# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb -# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS -# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL -# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX -# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXYwghlyAgEBMIGVMH4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p -# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA -# AlMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw -# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMQt -# y3cqdLHMkORDousYFit1gZHgDA5G8ruNWC2M1yt8MEIGCisGAQQBgjcCAQwxNDAy -# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20wDQYJKoZIhvcNAQEBBQAEggEACK1h1k5yJH9t/LbulIT8EoMZ45iY6fTsCiXj -# iBdhKfo1OenarZN4LivqwI6cxXSZWNzvIQ2umswdxzfMJy+0aBwrQ5zc++MxF4p8 -# p04vI4uuwSnZpFgBSS57hdZd64NoBXkan+YMMz01THWrz0AFFBI4euN9ASE5lF5r -# rZ1YvyTPhsgleGVXcV45YsM6Xp0sTnvtgdmHeyUknVefJwR4ZmaNp6EC3QKuHkna -# B686mvfbKsMXtPvOMt6tN2MTMPCPrSJ/+D2aV+JKwjqk+MmIAZ0JeTXq7Oy3H4i3 -# eR66oB24MwbOPL57sgVN35CK3trEV9eSwJbks6fIplb+UZlW9aGCFwAwghb8Bgor -# BgEEAYI3AwMBMYIW7DCCFugGCSqGSIb3DQEHAqCCFtkwghbVAgEDMQ8wDQYJYIZI -# AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE -# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBu5SYV/TjMU+1To3jN1ZjWGQGhTTO1qrzJ -# 2j1cR5AwHgIGYrTVw4J4GBMyMDIyMDcwMTIxMDAwMS4wODNaMASAAgH0oIHQpIHN -# MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL -# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjo4QTgyLUUzNEYtOUREQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCEVcwggcMMIIE9KADAgECAhMzAAABmciPr622fb6LAAEA -# AAGZMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTIxMTIwMjE5MDUxNloXDTIzMDIyODE5MDUxNlowgcoxCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy -# aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjhBODItRTM0 -# Ri05RERBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC -# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuBP5V1yjLMva1WtmvG0W9AK/ -# z4+PmyogIDICmsrwjBZ93XRqH/HlhYCVqeSXSo1o655lta/bR6XD9pBaY16dlHa4 -# +eY7eTWItTBEXKCKPhNvV7gsBQDCX7Rnb7Fnmvzk0DyBbGRFqhLXl3UZ3MlcHLec -# RcwSAtSVgnZn3O0efUTCMr4FoyM/N+epsHqMJDrLTdX3udyiCfH5a2CCyEFrDUTD -# k985wBsCG/ZMi+eAuo2YxcTkrrBmJlUPhq/6mUBC8NYqcfbOYzT9S0qTqX0xARbv -# svSizQZjAQEZEj6UxZBhMhksBhoEnTougVOhTyvurq2fN9I60XpiuCZCCfbUIZRn -# CIQI4XKmUrZ/t/JUY1b632wSORKSNjcJVSCtomxlu7FnkwM/8jTzSElG0IRgCbHv -# zoBo/9as/x2vZqn2GfAR3ITcZ9PJ1pKsq46yonEMsbg/JgmrU61UmziWH0MmXR0f -# WuL4MhnmRFwWEDQJSLgd+0AjgILP1Xg1PDOHXPyJa08gtLldUkuBpPM1fuV+Xkbv -# lzkTfBVqsyOpP/cunFsDljCumlDdV6i3Ghf7Jva1/OHiltpYQ3Nwt9Vi46fFDnzh -# 2Xz7ssueMX4pZ4YQj8uECifY4IKKqnnhIQhH4A2I8vjwclf3uRwwdsR3bZS/d8Jb -# QJCN2gsaPOJFTwQmtDsCAwEAAaOCATYwggEyMB0GA1UdDgQWBBSNula4Ppxlr67y -# IdHUDxKJxDUdEDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV -# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny -# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI -# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy -# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G -# CSqGSIb3DQEBCwUAA4ICAQBxs1odvYmwTa6N57PBNxDhxrCwdRs5PdEhWFCgYpSL -# uIw20e6m71zXfJ55f8+Q/SyoMN0/NY6subTIlqfqeI2AQZNz0cdckqi1H4ReWTFq -# JUc1vnEGRl1d/IkpnQFBtAqUecdazHTyK03M1VBbDmBqppKFEP3UYlS+IgFySwjU -# 4+Md2MCEHAj7dIoyVYpzyDAGMwPH/fQW3orJMiNARxgHdJBlF9iNv6bmJ5ckvLaA -# ZwDY4dtcYLxLmmdnb0ar1pP4tVyJTuu2rv1wnjE714TziFJqvNFN9k4n8N0kogSk -# 8oq+5qFh9Yux9quUAJ6wcbssv/2bA3wEQ1fwLZo+Y6+gE30Rw3VGp0KXqAUvospT -# VOkQEbvIPkzArv2Z3WZq7PcPvIGpVCBoPhppTZNO31yefDnPqhaoDQLDZDIvnUsy -# pFhgOP5JSRZ9F4nIVy/Preyjmslyhs0I2ugnbQo4rJ8kvIW7+DyoWnjLX+vMCVii -# jrNLbdgCX2Aub0mx/XJPARo9nv8kFxGRLvPC+f3xyuV1JPyCl8d82i5MV00meNxG -# v1il1QczGrB2dz5p8tJqBHpAF8dVISzTWL+Fbpt91Yq8Ugeq/xikMRUh7u8hX61k -# KWMsgx9dfU5oQHnUvE9VWm6nbTuWT9hNeEyvNlcsct8RD3pTqOHUSJYZu12OpPQe -# vDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL -# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH -# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV -# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X -# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR -# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p -# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh -# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM -# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm -# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB -# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb -# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO -# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw -# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW -# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w -# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK -# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2 -# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH -# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB -# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v -# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM -# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0 -# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF -# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD -# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW -# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny -# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH -# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp -# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF -# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx -# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+ -# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2 -# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw -# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7 -# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO -# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL -# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L -# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5 -# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE -# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLOMIICNwIB -# ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x -# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv -# bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE -# CxMdVGhhbGVzIFRTUyBFU046OEE4Mi1FMzRGLTlEREExJTAjBgNVBAMTHE1pY3Jv -# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAJLv82Lo56mq -# TegSfTCeY7YA65TroIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh -# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD -# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw -# MTAwDQYJKoZIhvcNAQEFBQACBQDmaTc9MCIYDzIwMjIwNzAxMTcwNTAxWhgPMjAy -# MjA3MDIxNzA1MDFaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOZpNz0CAQAwCgIB -# AAICERUCAf8wBwIBAAICEbMwCgIFAOZqiL0CAQAwNgYKKwYBBAGEWQoEAjEoMCYw -# DAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0B -# AQUFAAOBgQBT54+WmHojMQCYQgQt18iy8jWH/zrm88CORF+vITV/kwvO/zDL8aHA -# IHzJNcy5CXpPKzy5Vhln9qHhkGMMjkNDjIw+WFOC55FYzdJdNu/r8E9kWn3OkSFh -# YkGyBkUQRX1wxYMrjPe3VRH/CAkirEZQgoBQFfCTDXTQ+ybTfsRG+jGCBA0wggQJ -# AgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD -# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk -# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABmciPr622 -# fb6LAAEAAAGZMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZI -# hvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIE7ONnfPZ+1TgSlm38TzyWp8xzS0ag8S -# us6x6ktqqn9FMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgZn1p+fKije1l -# qQU2Dq1DtQUgkpyjywvxu8AjiHNibH4wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzET -# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV -# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T -# dGFtcCBQQ0EgMjAxMAITMwAAAZnIj6+ttn2+iwABAAABmTAiBCC0Y+62+yZ1e93v -# MC2z6glC0K3irTMXW68vE4DsYaOXnTANBgkqhkiG9w0BAQsFAASCAgBbNcmClGcW -# 0/bHfTnMXyPfcg+udBEUubR+oR3GKng5bIH4LXzBiLXiGeChHgHzN81BtET8g+84 -# mgQKCHEKer9K1IVHhn5meD6PzDmknpB5U+ZvzmNXgRVL23iK81mDJdEq3RwrfwSJ -# f86PUlrVmK3MdyRzfP92miVpeIzcqTPNQJzzOfFPgi07dyLVCZ2ZT11uAz+WksxW -# XjrD+DGzxK0EQDbltXlfIxcrT+FIF+yYA3HVs/g4HUMgT2MsOFjzyGu3MD/rNbb6 -# 8p2wGoYB1WkVzuEvV3S4L/ZO/oTUB59aM4XnvFhi0tsQKNJYuqysTAdIdRaJWH3p -# vp4OG/R0RTRXfmGueTwOUP6xbZXSluQa+a3PlHgGsnP8nwB6Mhty3tgzy46SSmdC -# feHAd+MN+x5Qx9ujIaMLa9bqWf/Q9lCgwIHbxNOIsoC6O3N15IAI/t39U0qMBXe1 -# Cm7DBSk7ldHponGKcteZ27k/Aqc5R8blqt0zgruhMRq5G6Rp8+m3VwWAwZiDjlC8 -# hRUuIcE8AuGCrTtu0omm8tNyjDFd0ypl81x5HceIlHrpwCIUaVd5gTGNd1/dJyW3 -# CTgdG0/M7ELkTjNfLi16NGtEeS+i+UYe2Cm32pPxXwx+q+YJ0N9wojtS6450z19x -# StE87RWDz8GuJ4o02QeRpwQQwVTHpnjaAA== -# SIG # End signature block +# +# Script module for module 'PackageManagement' +# +Set-StrictMode -Version Latest +Microsoft.PowerShell.Utility\Import-LocalizedData LocalizedData -filename PackageManagement.Resources.psd1 + +# Summary: PackageManagement is supported on Windows PowerShell 3.0 or later, Nano Server and PowerShellCore +$isCore = ($PSVersionTable.Keys -contains "PSEdition") -and ($PSVersionTable.PSEdition -ne 'Desktop') +$binarySubPath = '' +if ($isCore) +{ + $binarySubPath = Join-Path -Path 'coreclr' -ChildPath 'netstandard2.0' +} else { + $binarySubPath = 'fullclr' +} + +# Set up some helper variables to make it easier to work with the module +$script:PSModule = $ExecutionContext.SessionState.Module +$script:PSModuleRoot = $script:PSModule.ModuleBase + +$script:PkgMgmt = 'Microsoft.PackageManagement.dll' +$script:PSPkgMgmt = 'Microsoft.PowerShell.PackageManagement.dll' + + +# Try to import the OneGet assemblies at the same directory regardless fullclr or coreclr +$OneGetModulePath = Join-Path -Path $script:PSModuleRoot -ChildPath $script:PkgMgmt +$binaryModuleRoot = $script:PSModuleRoot + + +if(-not (Test-Path -Path $OneGetModulePath)) +{ + # Import the appropriate nested binary module based on the current PowerShell version + $binaryModuleRoot = Join-Path -Path $script:PSModuleRoot -ChildPath $binarySubPath + $OneGetModulePath = Join-Path -Path $binaryModuleRoot -ChildPath $script:PkgMgmt +} + +$PSOneGetModulePath = Join-Path -Path $binaryModuleRoot -ChildPath $script:PSPkgMgmt +$OneGetModule = Import-Module -Name $OneGetModulePath -PassThru +$PSOneGetModule = Import-Module -Name $PSOneGetModulePath -PassThru + + +# When the module is unloaded, remove the nested binary module that was loaded with it +if($OneGetModule) +{ + $script:PSModule.OnRemove = { + Remove-Module -ModuleInfo $OneGetModule + } +} + +if($PSOneGetModule) +{ + $script:PSModule.OnRemove = { + Remove-Module -ModuleInfo $PSOneGetModule + } +} +# SIG # Begin signature block +# MIInpQYJKoZIhvcNAQcCoIInljCCJ5ICAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAu+2bjkKZwIc24 +# sUnQmGnTVuZ6xttIA9Ea89zWbpdeVaCCDYUwggYDMIID66ADAgECAhMzAAACU+OD +# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1 +# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN +# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5 +# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY +# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4 +# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw +# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh +# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW +# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v +# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw +# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov +# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx +# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ +# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF +# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR +# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa +# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR +# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA +# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz +# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j +# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y +# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq +# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK +# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV +# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv +# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm +# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw +# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD +# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG +# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la +# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc +# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D +# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ +# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk +# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 +# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd +# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL +# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd +# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 +# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS +# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI +# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL +# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD +# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv +# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF +# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h +# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA +# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn +# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 +# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b +# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ +# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy +# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp +# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi +# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb +# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS +# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL +# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX +# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGXYwghlyAgEBMIGVMH4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p +# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA +# AlMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw +# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIMQt +# y3cqdLHMkORDousYFit1gZHgDA5G8ruNWC2M1yt8MEIGCisGAQQBgjcCAQwxNDAy +# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20wDQYJKoZIhvcNAQEBBQAEggEACK1h1k5yJH9t/LbulIT8EoMZ45iY6fTsCiXj +# iBdhKfo1OenarZN4LivqwI6cxXSZWNzvIQ2umswdxzfMJy+0aBwrQ5zc++MxF4p8 +# p04vI4uuwSnZpFgBSS57hdZd64NoBXkan+YMMz01THWrz0AFFBI4euN9ASE5lF5r +# rZ1YvyTPhsgleGVXcV45YsM6Xp0sTnvtgdmHeyUknVefJwR4ZmaNp6EC3QKuHkna +# B686mvfbKsMXtPvOMt6tN2MTMPCPrSJ/+D2aV+JKwjqk+MmIAZ0JeTXq7Oy3H4i3 +# eR66oB24MwbOPL57sgVN35CK3trEV9eSwJbks6fIplb+UZlW9aGCFwAwghb8Bgor +# BgEEAYI3AwMBMYIW7DCCFugGCSqGSIb3DQEHAqCCFtkwghbVAgEDMQ8wDQYJYIZI +# AWUDBAIBBQAwggFRBgsqhkiG9w0BCRABBKCCAUAEggE8MIIBOAIBAQYKKwYBBAGE +# WQoDATAxMA0GCWCGSAFlAwQCAQUABCBu5SYV/TjMU+1To3jN1ZjWGQGhTTO1qrzJ +# 2j1cR5AwHgIGYrTVw4J4GBMyMDIyMDcwMTIxMDAwMS4wODNaMASAAgH0oIHQpIHN +# MIHKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSUwIwYDVQQL +# ExxNaWNyb3NvZnQgQW1lcmljYSBPcGVyYXRpb25zMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjo4QTgyLUUzNEYtOUREQTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCEVcwggcMMIIE9KADAgECAhMzAAABmciPr622fb6LAAEA +# AAGZMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTIxMTIwMjE5MDUxNloXDTIzMDIyODE5MDUxNlowgcoxCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJTAjBgNVBAsTHE1pY3Jvc29mdCBBbWVy +# aWNhIE9wZXJhdGlvbnMxJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjhBODItRTM0 +# Ri05RERBMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNlMIIC +# IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuBP5V1yjLMva1WtmvG0W9AK/ +# z4+PmyogIDICmsrwjBZ93XRqH/HlhYCVqeSXSo1o655lta/bR6XD9pBaY16dlHa4 +# +eY7eTWItTBEXKCKPhNvV7gsBQDCX7Rnb7Fnmvzk0DyBbGRFqhLXl3UZ3MlcHLec +# RcwSAtSVgnZn3O0efUTCMr4FoyM/N+epsHqMJDrLTdX3udyiCfH5a2CCyEFrDUTD +# k985wBsCG/ZMi+eAuo2YxcTkrrBmJlUPhq/6mUBC8NYqcfbOYzT9S0qTqX0xARbv +# svSizQZjAQEZEj6UxZBhMhksBhoEnTougVOhTyvurq2fN9I60XpiuCZCCfbUIZRn +# CIQI4XKmUrZ/t/JUY1b632wSORKSNjcJVSCtomxlu7FnkwM/8jTzSElG0IRgCbHv +# zoBo/9as/x2vZqn2GfAR3ITcZ9PJ1pKsq46yonEMsbg/JgmrU61UmziWH0MmXR0f +# WuL4MhnmRFwWEDQJSLgd+0AjgILP1Xg1PDOHXPyJa08gtLldUkuBpPM1fuV+Xkbv +# lzkTfBVqsyOpP/cunFsDljCumlDdV6i3Ghf7Jva1/OHiltpYQ3Nwt9Vi46fFDnzh +# 2Xz7ssueMX4pZ4YQj8uECifY4IKKqnnhIQhH4A2I8vjwclf3uRwwdsR3bZS/d8Jb +# QJCN2gsaPOJFTwQmtDsCAwEAAaOCATYwggEyMB0GA1UdDgQWBBSNula4Ppxlr67y +# IdHUDxKJxDUdEDAfBgNVHSMEGDAWgBSfpxVdAF5iXYP05dJlpxtTNRnpcjBfBgNV +# HR8EWDBWMFSgUqBQhk5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2Ny +# bC9NaWNyb3NvZnQlMjBUaW1lLVN0YW1wJTIwUENBJTIwMjAxMCgxKS5jcmwwbAYI +# KwYBBQUHAQEEYDBeMFwGCCsGAQUFBzAChlBodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpb3BzL2NlcnRzL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAy +# MDEwKDEpLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsGAQUFBwMIMA0G +# CSqGSIb3DQEBCwUAA4ICAQBxs1odvYmwTa6N57PBNxDhxrCwdRs5PdEhWFCgYpSL +# uIw20e6m71zXfJ55f8+Q/SyoMN0/NY6subTIlqfqeI2AQZNz0cdckqi1H4ReWTFq +# JUc1vnEGRl1d/IkpnQFBtAqUecdazHTyK03M1VBbDmBqppKFEP3UYlS+IgFySwjU +# 4+Md2MCEHAj7dIoyVYpzyDAGMwPH/fQW3orJMiNARxgHdJBlF9iNv6bmJ5ckvLaA +# ZwDY4dtcYLxLmmdnb0ar1pP4tVyJTuu2rv1wnjE714TziFJqvNFN9k4n8N0kogSk +# 8oq+5qFh9Yux9quUAJ6wcbssv/2bA3wEQ1fwLZo+Y6+gE30Rw3VGp0KXqAUvospT +# VOkQEbvIPkzArv2Z3WZq7PcPvIGpVCBoPhppTZNO31yefDnPqhaoDQLDZDIvnUsy +# pFhgOP5JSRZ9F4nIVy/Preyjmslyhs0I2ugnbQo4rJ8kvIW7+DyoWnjLX+vMCVii +# jrNLbdgCX2Aub0mx/XJPARo9nv8kFxGRLvPC+f3xyuV1JPyCl8d82i5MV00meNxG +# v1il1QczGrB2dz5p8tJqBHpAF8dVISzTWL+Fbpt91Yq8Ugeq/xikMRUh7u8hX61k +# KWMsgx9dfU5oQHnUvE9VWm6nbTuWT9hNeEyvNlcsct8RD3pTqOHUSJYZu12OpPQe +# vDCCB3EwggVZoAMCAQICEzMAAAAVxedrngKbSZkAAAAAABUwDQYJKoZIhvcNAQEL +# BQAwgYgxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQH +# EwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xMjAwBgNV +# BAMTKU1pY3Jvc29mdCBSb290IENlcnRpZmljYXRlIEF1dGhvcml0eSAyMDEwMB4X +# DTIxMDkzMDE4MjIyNVoXDTMwMDkzMDE4MzIyNVowfDELMAkGA1UEBhMCVVMxEzAR +# BgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1p +# Y3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3Rh +# bXAgUENBIDIwMTAwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDk4aZM +# 57RyIQt5osvXJHm9DtWC0/3unAcH0qlsTnXIyjVX9gF/bErg4r25PhdgM/9cT8dm +# 95VTcVrifkpa/rg2Z4VGIwy1jRPPdzLAEBjoYH1qUoNEt6aORmsHFPPFdvWGUNzB +# RMhxXFExN6AKOG6N7dcP2CZTfDlhAnrEqv1yaa8dq6z2Nr41JmTamDu6GnszrYBb +# fowQHJ1S/rboYiXcag/PXfT+jlPP1uyFVk3v3byNpOORj7I5LFGc6XBpDco2LXCO +# Mcg1KL3jtIckw+DJj361VI/c+gVVmG1oO5pGve2krnopN6zL64NF50ZuyjLVwIYw +# XE8s4mKyzbnijYjklqwBSru+cakXW2dg3viSkR4dPf0gz3N9QZpGdc3EXzTdEonW +# /aUgfX782Z5F37ZyL9t9X4C626p+Nuw2TPYrbqgSUei/BQOj0XOmTTd0lBw0gg/w +# EPK3Rxjtp+iZfD9M269ewvPV2HM9Q07BMzlMjgK8QmguEOqEUUbi0b1qGFphAXPK +# Z6Je1yh2AuIzGHLXpyDwwvoSCtdjbwzJNmSLW6CmgyFdXzB0kZSU2LlQ+QuJYfM2 +# BjUYhEfb3BvR/bLUHMVr9lxSUV0S2yW6r1AFemzFER1y7435UsSFF5PAPBXbGjfH +# CBUYP3irRbb1Hode2o+eFnJpxq57t7c+auIurQIDAQABo4IB3TCCAdkwEgYJKwYB +# BAGCNxUBBAUCAwEAATAjBgkrBgEEAYI3FQIEFgQUKqdS/mTEmr6CkTxGNSnPEP8v +# BO4wHQYDVR0OBBYEFJ+nFV0AXmJdg/Tl0mWnG1M1GelyMFwGA1UdIARVMFMwUQYM +# KwYBBAGCN0yDfQEBMEEwPwYIKwYBBQUHAgEWM2h0dHA6Ly93d3cubWljcm9zb2Z0 +# LmNvbS9wa2lvcHMvRG9jcy9SZXBvc2l0b3J5Lmh0bTATBgNVHSUEDDAKBggrBgEF +# BQcDCDAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMCAYYwDwYD +# VR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvXzpoYxDBW +# BgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2Ny +# bC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYIKwYBBQUH +# AQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtp +# L2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDANBgkqhkiG9w0BAQsF +# AAOCAgEAnVV9/Cqt4SwfZwExJFvhnnJL/Klv6lwUtj5OR2R4sQaTlz0xM7U518Jx +# Nj/aZGx80HU5bbsPMeTCj/ts0aGUGCLu6WZnOlNN3Zi6th542DYunKmCVgADsAW+ +# iehp4LoJ7nvfam++Kctu2D9IdQHZGN5tggz1bSNU5HhTdSRXud2f8449xvNo32X2 +# pFaq95W2KFUn0CS9QKC/GbYSEhFdPSfgQJY4rPf5KYnDvBewVIVCs/wMnosZiefw +# C2qBwoEZQhlSdYo2wh3DYXMuLGt7bj8sCXgU6ZGyqVvfSaN0DLzskYDSPeZKPmY7 +# T7uG+jIa2Zb0j/aRAfbOxnT99kxybxCrdTDFNLB62FD+CljdQDzHVG2dY3RILLFO +# Ry3BFARxv2T5JL5zbcqOCb2zAVdJVGTZc9d/HltEAY5aGZFrDZ+kKNxnGSgkujhL +# mm77IVRrakURR6nxt67I6IleT53S0Ex2tVdUCbFpAUR+fKFhbHP+CrvsQWY9af3L +# wUFJfn6Tvsv4O+S3Fb+0zj6lMVGEvL8CwYKiexcdFYmNcP7ntdAoGokLjzbaukz5 +# m/8K6TT4JDVnK+ANuOaMmdbhIurwJ0I9JZTmdHRbatGePu1+oDEzfbzL6Xu/OHBE +# 0ZDxyKs6ijoIYn/ZcGNTTY3ugm2lBRDBcQZqELQdVTNYs6FwZvKhggLOMIICNwIB +# ATCB+KGB0KSBzTCByjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjElMCMGA1UECxMcTWljcm9zb2Z0IEFtZXJpY2EgT3BlcmF0aW9uczEmMCQGA1UE +# CxMdVGhhbGVzIFRTUyBFU046OEE4Mi1FMzRGLTlEREExJTAjBgNVBAMTHE1pY3Jv +# c29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMCGgMVAJLv82Lo56mq +# TegSfTCeY7YA65TroIGDMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldh +# c2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBD +# b3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIw +# MTAwDQYJKoZIhvcNAQEFBQACBQDmaTc9MCIYDzIwMjIwNzAxMTcwNTAxWhgPMjAy +# MjA3MDIxNzA1MDFaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIFAOZpNz0CAQAwCgIB +# AAICERUCAf8wBwIBAAICEbMwCgIFAOZqiL0CAQAwNgYKKwYBBAGEWQoEAjEoMCYw +# DAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGGoDANBgkqhkiG9w0B +# AQUFAAOBgQBT54+WmHojMQCYQgQt18iy8jWH/zrm88CORF+vITV/kwvO/zDL8aHA +# IHzJNcy5CXpPKzy5Vhln9qHhkGMMjkNDjIw+WFOC55FYzdJdNu/r8E9kWn3OkSFh +# YkGyBkUQRX1wxYMrjPe3VRH/CAkirEZQgoBQFfCTDXTQ+ybTfsRG+jGCBA0wggQJ +# AgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYD +# VQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAk +# BgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABmciPr622 +# fb6LAAEAAAGZMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG9w0BCQMxDQYLKoZI +# hvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIE7ONnfPZ+1TgSlm38TzyWp8xzS0ag8S +# us6x6ktqqn9FMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCBvQQgZn1p+fKije1l +# qQU2Dq1DtQUgkpyjywvxu8AjiHNibH4wgZgwgYCkfjB8MQswCQYDVQQGEwJVUzET +# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV +# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T +# dGFtcCBQQ0EgMjAxMAITMwAAAZnIj6+ttn2+iwABAAABmTAiBCC0Y+62+yZ1e93v +# MC2z6glC0K3irTMXW68vE4DsYaOXnTANBgkqhkiG9w0BAQsFAASCAgBbNcmClGcW +# 0/bHfTnMXyPfcg+udBEUubR+oR3GKng5bIH4LXzBiLXiGeChHgHzN81BtET8g+84 +# mgQKCHEKer9K1IVHhn5meD6PzDmknpB5U+ZvzmNXgRVL23iK81mDJdEq3RwrfwSJ +# f86PUlrVmK3MdyRzfP92miVpeIzcqTPNQJzzOfFPgi07dyLVCZ2ZT11uAz+WksxW +# XjrD+DGzxK0EQDbltXlfIxcrT+FIF+yYA3HVs/g4HUMgT2MsOFjzyGu3MD/rNbb6 +# 8p2wGoYB1WkVzuEvV3S4L/ZO/oTUB59aM4XnvFhi0tsQKNJYuqysTAdIdRaJWH3p +# vp4OG/R0RTRXfmGueTwOUP6xbZXSluQa+a3PlHgGsnP8nwB6Mhty3tgzy46SSmdC +# feHAd+MN+x5Qx9ujIaMLa9bqWf/Q9lCgwIHbxNOIsoC6O3N15IAI/t39U0qMBXe1 +# Cm7DBSk7ldHponGKcteZ27k/Aqc5R8blqt0zgruhMRq5G6Rp8+m3VwWAwZiDjlC8 +# hRUuIcE8AuGCrTtu0omm8tNyjDFd0ypl81x5HceIlHrpwCIUaVd5gTGNd1/dJyW3 +# CTgdG0/M7ELkTjNfLi16NGtEeS+i+UYe2Cm32pPxXwx+q+YJ0N9wojtS6450z19x +# StE87RWDz8GuJ4o02QeRpwQQwVTHpnjaAA== +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageProviderFunctions.psm1 b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageProviderFunctions.psm1 index db514f6ce1..8d82f0f146 100644 --- a/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageProviderFunctions.psm1 +++ b/src/PowerShell/ExternalModules/PackageManagement/1.4.8.1/PackageProviderFunctions.psm1 @@ -1,596 +1,596 @@ -### -# ==++== -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# -### - -<# - Overrides the default Write-Debug so that the output gets routed back thru the - $request.Debug() function -#> -function Write-Debug { - param( - [Parameter(Mandatory=$true)][string] $message, - [parameter(ValueFromRemainingArguments=$true)] - [object[]] - $args= @() - ) - - if( -not $request ) { - if( -not $args ) { - Microsoft.PowerShell.Utility\write-verbose $message - return - } - - $msg = [system.string]::format($message, $args) - Microsoft.PowerShell.Utility\write-verbose $msg - return - } - - if( -not $args ) { - $null = $request.Debug($message); - return - } - $null = $request.Debug($message,$args); -} - -function Write-Error { - param( - [Parameter(Mandatory=$true)][string] $Message, - [Parameter()][string] $Category, - [Parameter()][string] $ErrorId, - [Parameter()][string] $TargetObject - ) - - $null = $request.Warning($Message); -} - -<# - Overrides the default Write-Verbose so that the output gets routed back thru the - $request.Verbose() function -#> - -function Write-Progress { - param( - [CmdletBinding()] - - [Parameter(Position=0)] - [string] - $Activity, - - # This parameter is not supported by request object - [Parameter(Position=1)] - [ValidateNotNullOrEmpty()] - [string] - $Status, - - [Parameter(Position=2)] - [ValidateRange(0,[int]::MaxValue)] - [int] - $Id, - - [Parameter()] - [int] - $PercentComplete=-1, - - # This parameter is not supported by request object - [Parameter()] - [int] - $SecondsRemaining=-1, - - # This parameter is not supported by request object - [Parameter()] - [string] - $CurrentOperation, - - [Parameter()] - [ValidateRange(-1,[int]::MaxValue)] - [int] - $ParentID=-1, - - [Parameter()] - [switch] - $Completed, - - # This parameter is not supported by request object - [Parameter()] - [int] - $SourceID, - - [object[]] - $args= @() - ) - - $params = @{} - - if ($PSBoundParameters.ContainsKey("Activity")) { - $params.Add("Activity", $PSBoundParameters["Activity"]) - } - - if ($PSBoundParameters.ContainsKey("Status")) { - $params.Add("Status", $PSBoundParameters["Status"]) - } - - if ($PSBoundParameters.ContainsKey("PercentComplete")) { - $params.Add("PercentComplete", $PSBoundParameters["PercentComplete"]) - } - - if ($PSBoundParameters.ContainsKey("Id")) { - $params.Add("Id", $PSBoundParameters["Id"]) - } - - if ($PSBoundParameters.ContainsKey("ParentID")) { - $params.Add("ParentID", $PSBoundParameters["ParentID"]) - } - - if ($PSBoundParameters.ContainsKey("Completed")) { - $params.Add("Completed", $PSBoundParameters["Completed"]) - } - - if( -not $request ) { - if( -not $args ) { - Microsoft.PowerShell.Utility\Write-Progress @params - return - } - - $params["Activity"] = [system.string]::format($Activity, $args) - Microsoft.PowerShell.Utility\Write-Progress @params - return - } - - if( -not $args ) { - $request.Progress($Activity, $Status, $Id, $PercentComplete, $SecondsRemaining, $CurrentOperation, $ParentID, $Completed) - } - -} - -function Write-Verbose{ - param( - [Parameter(Mandatory=$true)][string] $message, - [parameter(ValueFromRemainingArguments=$true)] - [object[]] - $args= @() - ) - - if( -not $request ) { - if( -not $args ) { - Microsoft.PowerShell.Utility\write-verbose $message - return - } - - $msg = [system.string]::format($message, $args) - Microsoft.PowerShell.Utility\write-verbose $msg - return - } - - if( -not $args ) { - $null = $request.Verbose($message); - return - } - $null = $request.Verbose($message,$args); -} - -<# - Overrides the default Write-Warning so that the output gets routed back thru the - $request.Warning() function -#> - -function Write-Warning{ - param( - [Parameter(Mandatory=$true)][string] $message, - [parameter(ValueFromRemainingArguments=$true)] - [object[]] - $args= @() - ) - - if( -not $request ) { - if( -not $args ) { - Microsoft.PowerShell.Utility\write-warning $message - return - } - - $msg = [system.string]::format($message, $args) - Microsoft.PowerShell.Utility\write-warning $msg - return - } - - if( -not $args ) { - $null = $request.Warning($message); - return - } - $null = $request.Warning($message,$args); -} - -<# - Creates a new instance of a PackageSource object -#> -function New-PackageSource { - param( - [Parameter(Mandatory=$true)][string] $name, - [Parameter(Mandatory=$true)][string] $location, - [Parameter(Mandatory=$true)][bool] $trusted, - [Parameter(Mandatory=$true)][bool] $registered, - [bool] $valid = $false, - [System.Collections.Hashtable] $details = $null - ) - - return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.PackageSource -ArgumentList $name,$location,$trusted,$registered,$valid,$details -} - -<# - Creates a new instance of a SoftwareIdentity object -#> -function New-SoftwareIdentity { - param( - [Parameter(Mandatory=$true)][string] $fastPackageReference, - [Parameter(Mandatory=$true)][string] $name, - [Parameter(Mandatory=$true)][string] $version, - [Parameter(Mandatory=$true)][string] $versionScheme, - [Parameter(Mandatory=$true)][string] $source, - [string] $summary, - [string] $searchKey = $null, - [string] $fullPath = $null, - [string] $filename = $null, - [System.Collections.Hashtable] $details = $null, - [System.Collections.ArrayList] $entities = $null, - [System.Collections.ArrayList] $links = $null, - [bool] $fromTrustedSource = $false, - [System.Collections.ArrayList] $dependencies = $null, - [string] $tagId = $null, - [string] $culture = $null, - [string] $destination = $null - ) - return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.SoftwareIdentity -ArgumentList $fastPackageReference, $name, $version, $versionScheme, $source, $summary, $searchKey, $fullPath, $filename , $details , $entities, $links, $fromTrustedSource, $dependencies, $tagId, $culture, $destination -} - -<# - Creates a new instance of a SoftwareIdentity object based on an xml string -#> -function New-SoftwareIdentityFromXml { - param( - [Parameter(Mandatory=$true)][string] $xmlSwidtag, - [bool] $commitImmediately = $false - ) - - return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.SoftwareIdentity -ArgumentList $xmlSwidtag, $commitImmediately -} - -<# - Creates a new instance of a DyamicOption object -#> -function New-DynamicOption { - param( - [Parameter(Mandatory=$true)][Microsoft.PackageManagement.MetaProvider.PowerShell.OptionCategory] $category, - [Parameter(Mandatory=$true)][string] $name, - [Parameter(Mandatory=$true)][Microsoft.PackageManagement.MetaProvider.PowerShell.OptionType] $expectedType, - [Parameter(Mandatory=$true)][bool] $isRequired, - [System.Collections.ArrayList] $permittedValues = $null - ) - - if( -not $permittedValues ) { - return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.DynamicOption -ArgumentList $category,$name, $expectedType, $isRequired - } - return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.DynamicOption -ArgumentList $category,$name, $expectedType, $isRequired, $permittedValues.ToArray() -} - -<# - Creates a new instance of a Feature object -#> -function New-Feature { - param( - [Parameter(Mandatory=$true)][string] $name, - [System.Collections.ArrayList] $values = $null - ) - - if( -not $values ) { - return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Feature -ArgumentList $name - } - return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Feature -ArgumentList $name, $values.ToArray() -} - -<# - Duplicates the $request object and overrides the client-supplied data with the specified values. -#> -function New-Request { - param( - [System.Collections.Hashtable] $options = $null, - [System.Collections.ArrayList] $sources = $null, - [PSCredential] $credential = $null - ) - - return $request.CloneRequest( $options, $sources, $credential ) -} - -function New-Entity { - param( - [Parameter(Mandatory=$true)][string] $name, - [Parameter(Mandatory=$true,ParameterSetName="role")][string] $role, - [Parameter(Mandatory=$true,ParameterSetName="roles")][System.Collections.ArrayList]$roles, - [string] $regId = $null, - [string] $thumbprint= $null - ) - - $o = New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Entity - $o.Name = $name - - # support role as a NMTOKENS string or an array of strings - if( $role ) { - $o.Role = $role - } - if( $roles ) { - $o.Roles = $roles - } - - $o.regId = $regId - $o.thumbprint = $thumbprint - return $o -} - -function New-Link { - param( - [Parameter(Mandatory=$true)][string] $HRef, - [Parameter(Mandatory=$true)][string] $relationship, - [string] $mediaType = $null, - [string] $ownership = $null, - [string] $use= $null, - [string] $appliesToMedia= $null, - [string] $artifact = $null - ) - - $o = New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Link - - $o.HRef = $HRef - $o.Relationship =$relationship - $o.MediaType =$mediaType - $o.Ownership =$ownership - $o.Use = $use - $o.AppliesToMedia = $appliesToMedia - $o.Artifact = $artifact - - return $o -} - -function New-Dependency { - param( - [Parameter(Mandatory=$true)][string] $providerName, - [Parameter(Mandatory=$true)][string] $packageName, - [string] $version= $null, - [string] $source = $null, - [string] $appliesTo = $null - ) - - $o = New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Dependency - - $o.ProviderName = $providerName - $o.PackageName =$packageName - $o.Version =$version - $o.Source =$source - $o.AppliesTo = $appliesTo - - return $o -} -# SIG # Begin signature block -# MIInsQYJKoZIhvcNAQcCoIInojCCJ54CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDs5FoY/w7B0Miq -# Kfi0Wb7Z3JGNbdfm1vY/fLsAXNclnKCCDYUwggYDMIID66ADAgECAhMzAAACU+OD -# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1 -# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN -# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5 -# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY -# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4 -# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw -# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh -# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW -# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v -# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw -# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov -# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx -# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB -# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ -# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF -# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR -# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa -# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR -# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA -# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz -# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j -# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y -# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq -# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK -# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV -# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv -# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm -# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw -# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE -# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD -# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG -# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la -# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc -# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D -# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ -# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk -# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 -# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd -# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL -# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd -# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 -# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS -# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI -# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL -# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD -# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv -# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf -# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF -# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h -# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA -# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn -# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 -# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b -# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ -# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy -# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp -# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi -# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb -# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS -# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL -# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX -# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGYIwghl+AgEBMIGVMH4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p -# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA -# AlMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw -# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFtW -# lCf/wF6GbazDLehu9mS8WGiUm3tcu/5KpjXCCIesMEIGCisGAQQBgjcCAQwxNDAy -# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20wDQYJKoZIhvcNAQEBBQAEggEAwETHSA6ffhhSRTPNmFZi9dFIoroMG7jii7/9 -# LMoCAZgwSeSzaZs3vo1EaRFFZvaKoQkPITRK9rBjINt9jLGalSs2jrwdM/E5wa8k -# bKFY4VEB/aJffdDIGiAsMu+gOWxCOFS8xdYzlZfRlFe9f6VVRtCyubOok3BSZRrq -# cCFpCprCa34w/J8erd7FF54qJ2IrO3bEd4w+23w3qg1BwtPr/vqLchKyIEBjzpcL -# 78/CJEUpFbtz/VuvyLfeVoSEnj6Ctf0tFDvRDNXGPqp1ZJSitKDsrQseb2K5/pZO -# nyykNPzpxJmIaLHDgBjtGlXuiHUEuJAFV96oucoIydX1dqQal6GCFwwwghcIBgor -# BgEEAYI3AwMBMYIW+DCCFvQGCSqGSIb3DQEHAqCCFuUwghbhAgEDMQ8wDQYJYIZI -# AWUDBAIBBQAwggFVBgsqhkiG9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGE -# WQoDATAxMA0GCWCGSAFlAwQCAQUABCCmK+qErRnVeAaOr2etmjZMIBNv7JyifxQE -# HuWjeTnqvAIGYrGiJBRoGBMyMDIyMDcwMTIxMDAwMS42ODRaMASAAgH0oIHUpIHR -# MIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQL -# EyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhh -# bGVzIFRTUyBFU046NDYyRi1FMzE5LTNGMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBU -# aW1lLVN0YW1wIFNlcnZpY2WgghFfMIIHEDCCBPigAwIBAgITMwAAAaQHz+OPo7pv -# 1gABAAABpDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg -# MjAxMDAeFw0yMjAzMDIxODUxMThaFw0yMzA1MTExODUxMThaMIHOMQswCQYDVQQG -# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG -# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg -# T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046 -# NDYyRi1FMzE5LTNGMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl -# cnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDAR44A+hT8vNT1 -# IXDiFRoeGzkmqut+GPk41toTRfQZZ1sSyQhLjIlemBecemEzO09WSzOjZx9MIT8q -# Ys921WUZsIBsk1ESn1cjyfPUd1mmfxzL3ACWZwjIC/pjqcRPeIMECQ/6qPFKrjqw -# igmP33I3IcVfMjJHyKj+vR51n1tK2rZPiNhmRdiEhckbbxLsSb2nCBQxZEF49x/l -# 8vSB8zaqovoOeIkIzgDerN7OvJouq6r+vg/Qz1T4NXr+sKKyNxZWM6zywiLp7G7W -# Ld18N2hyjHwPkh/AleIqif3hGVD9bhSU+dDADzUJSMFhEWunHHElQeZjdmIB3/Mw -# 1KkFOJNvw1sPteIi5MK4DZX3Wd/Fd8ZsQvZmXPWJ8BXN9sYtHMz8zdeQvMImRCKg -# nXcW8IpnPtC7Tymp3UV5NoTH8INF6WWicQ3y04L2I1VOT104AddJoVgAP2KLIGwf -# Cs7wMVz56xJ2IN1y1pIAWfpTqx76orM5RQhkAvayj1RTwgrHst+elYX3F5b8ACWr -# gJO1dJy1U4MIv+SC8h33xLmWA568emvrJ6g0xy/2akbAeRx6tFwaP4uwVbjF50kl -# 5RQqNzp/CDpfCTikOAqyJa4valiWDMbEiArHKLYDg6GDjuJZl5bSjgdJdCAIRF8E -# kiiA+UAGvcE6SGoHmtoc4yOklGNVvwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFOLQ -# E5+s+AgS9sWUHdI4zekp4yTCMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1 -# GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w -# a2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEp -# LmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWlj -# cm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUy -# MFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB -# BQUHAwgwDQYJKoZIhvcNAQELBQADggIBAAlWHFDRDJck7jwwRoYmdVOePLLBeido -# PUBJVhG9nGeHS9PuRvO9tf4IkbUz74MUIQxeayQoxxo/JxUqjhPH52M/b4G9mHJW -# B75KCllCTg8Y4VkvktOmS0f5w0vOR3gwA9BRnbgAPNEO7xs5Jylto8aDR02++CkB -# DFolCtTNjwzfniEj1z4T7nRlRi2yBAJNRqI+VY820LiyoZtk5OGttq5F5HhPfIMj -# aIx5QYR22+53sd8xgUwRpFbcLdrne6jdq3KbiYbCf7y/9F2C7cjpO3kkGXX8ntE0 -# 9f6o9fIklx7CFw4RzrkyqgYomraKOFJ8JO7hsjNJb9/Gba/mKWo0j/qdDxDER/UX -# X6ykZuGx1eQpjkyMwJnOPWGbeNIYZVcJQpRQODPs593Mi5hBsHzag+vd4Q+Vt73K -# Z4X98YWW1Vk1aSR9Qjxk5keMuVPZMcMrCvFZXwhUcGFGueuNCrICL9bSYRfS13pl -# iDxJ7sPSZ8x2d4ksOXW00l6fR5nTiSM7Dvv7Y0MGVgUhap2smhr92PMNSmIkCUvH -# CiYcJ4RoAT28mp/hOQ/U8mPXSpWdxYpLLcDOISmBhFJYN7amlhIpVsGvUmjXrTcY -# 0n4Goe/Nqs2400IcA4HOiX9OxdmpNGDJzSRR7AW9TT8O+3YZqPZIvL6yzgfvnehp -# tmf4w6QzkrLfMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkq -# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x -# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv -# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 -# IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQG -# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG -# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg -# VGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC -# ggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+ -# F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU -# 88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqY -# O7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzp -# cGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0Xn -# Rm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1 -# zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZN -# N3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLR -# vWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTY -# uVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUX -# k8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB -# 2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKR -# PEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0g -# BFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5t -# aWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQM -# MAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE -# AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ -# W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv -# bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa -# BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0 -# LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqG -# SIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOX -# PTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6c -# qYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/z -# jj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz -# /AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyR -# gNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdU -# bZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo -# 3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4K -# u+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10Cga -# iQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9 -# vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGC -# AtIwggI7AgEBMIH8oYHUpIHRMIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz -# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv -# cnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8g -# UmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046NDYyRi1FMzE5LTNGMjAxJTAj -# BgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMC -# GgMVADQcKOKTa3xC+g1aPrcPerxiby6foIGDMIGApH4wfDELMAkGA1UEBhMCVVMx -# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT -# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDmaU8/MCIYDzIwMjIwNzAx -# MTQ0NzI3WhgPMjAyMjA3MDIxNDQ3MjdaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIF -# AOZpTz8CAQAwCgIBAAICCNwCAf8wBwIBAAICEp4wCgIFAOZqoL8CAQAwNgYKKwYB -# BAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGG -# oDANBgkqhkiG9w0BAQUFAAOBgQChh1QY1GFw/ekl4Qr+s3lJOVBWDUpw+EfxjUrG -# H7ud+AMLNhAxbx7uEfNyNYsi5CnaaXgA2vO00zzDNvaugU/yTYxEm2j0wybQTDfb -# BMyUPQB6VhcFgCAOC4CQalIa1F5xOFEYV2P5/OY3Fza/pCGQKiBEDyRG6PfCbmAZ -# qvg7cTGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# AhMzAAABpAfP44+jum/WAAEAAAGkMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG -# 9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIADlNEKhG3BnRkhq -# cXD/tb/UR5BXxdcKHp5Lt8yia4ZxMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCB -# vQQgBfzgoyEmcKTASfDCd1sDAhd6jmuWBxRuieLh42rqefgwgZgwgYCkfjB8MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy -# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAaQHz+OPo7pv1gABAAABpDAi -# BCBoSzA9nCaw+Pk/XGE5FyHMvYr0QOQne8MeefMn3+/YZjANBgkqhkiG9w0BAQsF -# AASCAgAVCHZ4zg4+oaqb2mTbyBn1iELiCCLutlG+ij8/By/6KzRjhS79sCiJwxuR -# 0X6tVul89m21uZwJPqhV7v4FkoAC9phxUr5T7YxGrPXzHpJitYLWCG9GewR2PfPr -# y7JhUCPAN4hNHR4Z8GLJFTXv1eQyKkeDlSxivC29GWn1X7eTfAKCwtDyjkWeoe4c -# hrHzgdREjT7p7z0UKXn5zT4CYsdCYYePTcrtcPQ8YKRZfQhVhAcHPzo1f2r5bRdr -# EkAmFHpx0hhKEPRke/dszJ14ykLOqEgRMAqyzgE9WUGc87TGdhGrsB7kZHPgnDLn -# MyP5FReopTZDLlmEg++XN1/FCfVr1aUVXwlr8U5BQLMhhz/L14pX7Rpho15O13WR -# FpuqsPpy+Age70j5efz9BiSnT4jyUzkFdKSbUf0512UrfhVsqw7POXkuvd+VSqH0 -# i3VpF6E7MlPUw/xWBE8xyfGISmMM4awoQBjOs4ir60ktDowkvckkmoSGgf9YLFXt -# jwnh65g7XvREpcLsJpOx6PvMKxWQ0ePPy1kl1uKWWpSe7kNwdMywl60FBFC1hWVi -# ktBE98W2j6B4MwGSL2F7hWz7LNs1ROI8YAPaj6+2gK7oYEQvd0wVvsNyzDQ6oIiJ -# VU2Rt5sblCaZvMF4XMba1eIRpNOrm45iPMmRsypgZdBSy4wtOw== -# SIG # End signature block +### +# ==++== +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +### + +<# + Overrides the default Write-Debug so that the output gets routed back thru the + $request.Debug() function +#> +function Write-Debug { + param( + [Parameter(Mandatory=$true)][string] $message, + [parameter(ValueFromRemainingArguments=$true)] + [object[]] + $args= @() + ) + + if( -not $request ) { + if( -not $args ) { + Microsoft.PowerShell.Utility\write-verbose $message + return + } + + $msg = [system.string]::format($message, $args) + Microsoft.PowerShell.Utility\write-verbose $msg + return + } + + if( -not $args ) { + $null = $request.Debug($message); + return + } + $null = $request.Debug($message,$args); +} + +function Write-Error { + param( + [Parameter(Mandatory=$true)][string] $Message, + [Parameter()][string] $Category, + [Parameter()][string] $ErrorId, + [Parameter()][string] $TargetObject + ) + + $null = $request.Warning($Message); +} + +<# + Overrides the default Write-Verbose so that the output gets routed back thru the + $request.Verbose() function +#> + +function Write-Progress { + param( + [CmdletBinding()] + + [Parameter(Position=0)] + [string] + $Activity, + + # This parameter is not supported by request object + [Parameter(Position=1)] + [ValidateNotNullOrEmpty()] + [string] + $Status, + + [Parameter(Position=2)] + [ValidateRange(0,[int]::MaxValue)] + [int] + $Id, + + [Parameter()] + [int] + $PercentComplete=-1, + + # This parameter is not supported by request object + [Parameter()] + [int] + $SecondsRemaining=-1, + + # This parameter is not supported by request object + [Parameter()] + [string] + $CurrentOperation, + + [Parameter()] + [ValidateRange(-1,[int]::MaxValue)] + [int] + $ParentID=-1, + + [Parameter()] + [switch] + $Completed, + + # This parameter is not supported by request object + [Parameter()] + [int] + $SourceID, + + [object[]] + $args= @() + ) + + $params = @{} + + if ($PSBoundParameters.ContainsKey("Activity")) { + $params.Add("Activity", $PSBoundParameters["Activity"]) + } + + if ($PSBoundParameters.ContainsKey("Status")) { + $params.Add("Status", $PSBoundParameters["Status"]) + } + + if ($PSBoundParameters.ContainsKey("PercentComplete")) { + $params.Add("PercentComplete", $PSBoundParameters["PercentComplete"]) + } + + if ($PSBoundParameters.ContainsKey("Id")) { + $params.Add("Id", $PSBoundParameters["Id"]) + } + + if ($PSBoundParameters.ContainsKey("ParentID")) { + $params.Add("ParentID", $PSBoundParameters["ParentID"]) + } + + if ($PSBoundParameters.ContainsKey("Completed")) { + $params.Add("Completed", $PSBoundParameters["Completed"]) + } + + if( -not $request ) { + if( -not $args ) { + Microsoft.PowerShell.Utility\Write-Progress @params + return + } + + $params["Activity"] = [system.string]::format($Activity, $args) + Microsoft.PowerShell.Utility\Write-Progress @params + return + } + + if( -not $args ) { + $request.Progress($Activity, $Status, $Id, $PercentComplete, $SecondsRemaining, $CurrentOperation, $ParentID, $Completed) + } + +} + +function Write-Verbose{ + param( + [Parameter(Mandatory=$true)][string] $message, + [parameter(ValueFromRemainingArguments=$true)] + [object[]] + $args= @() + ) + + if( -not $request ) { + if( -not $args ) { + Microsoft.PowerShell.Utility\write-verbose $message + return + } + + $msg = [system.string]::format($message, $args) + Microsoft.PowerShell.Utility\write-verbose $msg + return + } + + if( -not $args ) { + $null = $request.Verbose($message); + return + } + $null = $request.Verbose($message,$args); +} + +<# + Overrides the default Write-Warning so that the output gets routed back thru the + $request.Warning() function +#> + +function Write-Warning{ + param( + [Parameter(Mandatory=$true)][string] $message, + [parameter(ValueFromRemainingArguments=$true)] + [object[]] + $args= @() + ) + + if( -not $request ) { + if( -not $args ) { + Microsoft.PowerShell.Utility\write-warning $message + return + } + + $msg = [system.string]::format($message, $args) + Microsoft.PowerShell.Utility\write-warning $msg + return + } + + if( -not $args ) { + $null = $request.Warning($message); + return + } + $null = $request.Warning($message,$args); +} + +<# + Creates a new instance of a PackageSource object +#> +function New-PackageSource { + param( + [Parameter(Mandatory=$true)][string] $name, + [Parameter(Mandatory=$true)][string] $location, + [Parameter(Mandatory=$true)][bool] $trusted, + [Parameter(Mandatory=$true)][bool] $registered, + [bool] $valid = $false, + [System.Collections.Hashtable] $details = $null + ) + + return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.PackageSource -ArgumentList $name,$location,$trusted,$registered,$valid,$details +} + +<# + Creates a new instance of a SoftwareIdentity object +#> +function New-SoftwareIdentity { + param( + [Parameter(Mandatory=$true)][string] $fastPackageReference, + [Parameter(Mandatory=$true)][string] $name, + [Parameter(Mandatory=$true)][string] $version, + [Parameter(Mandatory=$true)][string] $versionScheme, + [Parameter(Mandatory=$true)][string] $source, + [string] $summary, + [string] $searchKey = $null, + [string] $fullPath = $null, + [string] $filename = $null, + [System.Collections.Hashtable] $details = $null, + [System.Collections.ArrayList] $entities = $null, + [System.Collections.ArrayList] $links = $null, + [bool] $fromTrustedSource = $false, + [System.Collections.ArrayList] $dependencies = $null, + [string] $tagId = $null, + [string] $culture = $null, + [string] $destination = $null + ) + return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.SoftwareIdentity -ArgumentList $fastPackageReference, $name, $version, $versionScheme, $source, $summary, $searchKey, $fullPath, $filename , $details , $entities, $links, $fromTrustedSource, $dependencies, $tagId, $culture, $destination +} + +<# + Creates a new instance of a SoftwareIdentity object based on an xml string +#> +function New-SoftwareIdentityFromXml { + param( + [Parameter(Mandatory=$true)][string] $xmlSwidtag, + [bool] $commitImmediately = $false + ) + + return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.SoftwareIdentity -ArgumentList $xmlSwidtag, $commitImmediately +} + +<# + Creates a new instance of a DyamicOption object +#> +function New-DynamicOption { + param( + [Parameter(Mandatory=$true)][Microsoft.PackageManagement.MetaProvider.PowerShell.OptionCategory] $category, + [Parameter(Mandatory=$true)][string] $name, + [Parameter(Mandatory=$true)][Microsoft.PackageManagement.MetaProvider.PowerShell.OptionType] $expectedType, + [Parameter(Mandatory=$true)][bool] $isRequired, + [System.Collections.ArrayList] $permittedValues = $null + ) + + if( -not $permittedValues ) { + return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.DynamicOption -ArgumentList $category,$name, $expectedType, $isRequired + } + return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.DynamicOption -ArgumentList $category,$name, $expectedType, $isRequired, $permittedValues.ToArray() +} + +<# + Creates a new instance of a Feature object +#> +function New-Feature { + param( + [Parameter(Mandatory=$true)][string] $name, + [System.Collections.ArrayList] $values = $null + ) + + if( -not $values ) { + return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Feature -ArgumentList $name + } + return New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Feature -ArgumentList $name, $values.ToArray() +} + +<# + Duplicates the $request object and overrides the client-supplied data with the specified values. +#> +function New-Request { + param( + [System.Collections.Hashtable] $options = $null, + [System.Collections.ArrayList] $sources = $null, + [PSCredential] $credential = $null + ) + + return $request.CloneRequest( $options, $sources, $credential ) +} + +function New-Entity { + param( + [Parameter(Mandatory=$true)][string] $name, + [Parameter(Mandatory=$true,ParameterSetName="role")][string] $role, + [Parameter(Mandatory=$true,ParameterSetName="roles")][System.Collections.ArrayList]$roles, + [string] $regId = $null, + [string] $thumbprint= $null + ) + + $o = New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Entity + $o.Name = $name + + # support role as a NMTOKENS string or an array of strings + if( $role ) { + $o.Role = $role + } + if( $roles ) { + $o.Roles = $roles + } + + $o.regId = $regId + $o.thumbprint = $thumbprint + return $o +} + +function New-Link { + param( + [Parameter(Mandatory=$true)][string] $HRef, + [Parameter(Mandatory=$true)][string] $relationship, + [string] $mediaType = $null, + [string] $ownership = $null, + [string] $use= $null, + [string] $appliesToMedia= $null, + [string] $artifact = $null + ) + + $o = New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Link + + $o.HRef = $HRef + $o.Relationship =$relationship + $o.MediaType =$mediaType + $o.Ownership =$ownership + $o.Use = $use + $o.AppliesToMedia = $appliesToMedia + $o.Artifact = $artifact + + return $o +} + +function New-Dependency { + param( + [Parameter(Mandatory=$true)][string] $providerName, + [Parameter(Mandatory=$true)][string] $packageName, + [string] $version= $null, + [string] $source = $null, + [string] $appliesTo = $null + ) + + $o = New-Object -TypeName Microsoft.PackageManagement.MetaProvider.PowerShell.Dependency + + $o.ProviderName = $providerName + $o.PackageName =$packageName + $o.Version =$version + $o.Source =$source + $o.AppliesTo = $appliesTo + + return $o +} +# SIG # Begin signature block +# MIInsQYJKoZIhvcNAQcCoIInojCCJ54CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDs5FoY/w7B0Miq +# Kfi0Wb7Z3JGNbdfm1vY/fLsAXNclnKCCDYUwggYDMIID66ADAgECAhMzAAACU+OD +# 3pbexW7MAAAAAAJTMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjEwOTAyMTgzMzAwWhcNMjIwOTAxMTgzMzAwWjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDLhxHwq3OhH+4J+SX4qS/VQG8HybccH7tnG+BUqrXubfGuDFYPZ29uCuHfQlO1 +# lygLgMpJ4Geh6/6poQ5VkDKfVssn6aA1PCzIh8iOPMQ9Mju3sLF9Sn+Pzuaie4BN +# rp0MuZLDEXgVYx2WNjmzqcxC7dY9SC3znOh5qUy2vnmWygC7b9kj0d3JrGtjc5q5 +# 0WfV3WLXAQHkeRROsJFBZfXFGoSvRljFFUAjU/zdhP92P+1JiRRRikVy/sqIhMDY +# +7tVdzlE2fwnKOv9LShgKeyEevgMl0B1Fq7E2YeBZKF6KlhmYi9CE1350cnTUoU4 +# YpQSnZo0YAnaenREDLfFGKTdAgMBAAGjggGCMIIBfjAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUlZpLWIccXoxessA/DRbe26glhEMw +# VAYDVR0RBE0wS6RJMEcxLTArBgNVBAsTJE1pY3Jvc29mdCBJcmVsYW5kIE9wZXJh +# dGlvbnMgTGltaXRlZDEWMBQGA1UEBRMNMjMwMDEyKzQ2NzU5ODAfBgNVHSMEGDAW +# gBRIbmTlUAXTgqoXNzcitW2oynUClTBUBgNVHR8ETTBLMEmgR6BFhkNodHRwOi8v +# d3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NybC9NaWNDb2RTaWdQQ0EyMDExXzIw +# MTEtMDctMDguY3JsMGEGCCsGAQUFBwEBBFUwUzBRBggrBgEFBQcwAoZFaHR0cDov +# L3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9jZXJ0cy9NaWNDb2RTaWdQQ0EyMDEx +# XzIwMTEtMDctMDguY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZIhvcNAQELBQADggIB +# AKVY+yKcJVVxf9W2vNkL5ufjOpqcvVOOOdVyjy1dmsO4O8khWhqrecdVZp09adOZ +# 8kcMtQ0U+oKx484Jg11cc4Ck0FyOBnp+YIFbOxYCqzaqMcaRAgy48n1tbz/EFYiF +# zJmMiGnlgWFCStONPvQOBD2y/Ej3qBRnGy9EZS1EDlRN/8l5Rs3HX2lZhd9WuukR +# bUk83U99TPJyo12cU0Mb3n1HJv/JZpwSyqb3O0o4HExVJSkwN1m42fSVIVtXVVSa +# YZiVpv32GoD/dyAS/gyplfR6FI3RnCOomzlycSqoz0zBCPFiCMhVhQ6qn+J0GhgR +# BJvGKizw+5lTfnBFoqKZJDROz+uGDl9tw6JvnVqAZKGrWv/CsYaegaPePFrAVSxA +# yUwOFTkAqtNC8uAee+rv2V5xLw8FfpKJ5yKiMKnCKrIaFQDr5AZ7f2ejGGDf+8Tz +# OiK1AgBvOW3iTEEa/at8Z4+s1CmnEAkAi0cLjB72CJedU1LAswdOCWM2MDIZVo9j +# 0T74OkJLTjPd3WNEyw0rBXTyhlbYQsYt7ElT2l2TTlF5EmpVixGtj4ChNjWoKr9y +# TAqtadd2Ym5FNB792GzwNwa631BPCgBJmcRpFKXt0VEQq7UXVNYBiBRd+x4yvjqq +# 5aF7XC5nXCgjbCk7IXwmOphNuNDNiRq83Ejjnc7mxrJGMIIHejCCBWKgAwIBAgIK +# YQ6Q0gAAAAAAAzANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNV +# BAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jv +# c29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlm +# aWNhdGUgQXV0aG9yaXR5IDIwMTEwHhcNMTEwNzA4MjA1OTA5WhcNMjYwNzA4MjEw +# OTA5WjB+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UE +# BxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSgwJgYD +# VQQDEx9NaWNyb3NvZnQgQ29kZSBTaWduaW5nIFBDQSAyMDExMIICIjANBgkqhkiG +# 9w0BAQEFAAOCAg8AMIICCgKCAgEAq/D6chAcLq3YbqqCEE00uvK2WCGfQhsqa+la +# UKq4BjgaBEm6f8MMHt03a8YS2AvwOMKZBrDIOdUBFDFC04kNeWSHfpRgJGyvnkmc +# 6Whe0t+bU7IKLMOv2akrrnoJr9eWWcpgGgXpZnboMlImEi/nqwhQz7NEt13YxC4D +# dato88tt8zpcoRb0RrrgOGSsbmQ1eKagYw8t00CT+OPeBw3VXHmlSSnnDb6gE3e+ +# lD3v++MrWhAfTVYoonpy4BI6t0le2O3tQ5GD2Xuye4Yb2T6xjF3oiU+EGvKhL1nk +# kDstrjNYxbc+/jLTswM9sbKvkjh+0p2ALPVOVpEhNSXDOW5kf1O6nA+tGSOEy/S6 +# A4aN91/w0FK/jJSHvMAhdCVfGCi2zCcoOCWYOUo2z3yxkq4cI6epZuxhH2rhKEmd +# X4jiJV3TIUs+UsS1Vz8kA/DRelsv1SPjcF0PUUZ3s/gA4bysAoJf28AVs70b1FVL +# 5zmhD+kjSbwYuER8ReTBw3J64HLnJN+/RpnF78IcV9uDjexNSTCnq47f7Fufr/zd +# sGbiwZeBe+3W7UvnSSmnEyimp31ngOaKYnhfsi+E11ecXL93KCjx7W3DKI8sj0A3 +# T8HhhUSJxAlMxdSlQy90lfdu+HggWCwTXWCVmj5PM4TasIgX3p5O9JawvEagbJjS +# 4NaIjAsCAwEAAaOCAe0wggHpMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBRI +# bmTlUAXTgqoXNzcitW2oynUClTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTAL +# BgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBRyLToCMZBD +# uRQFTuHqp8cx0SOJNDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8vY3JsLm1pY3Jv +# c29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3JsMF4GCCsGAQUFBwEBBFIwUDBOBggrBgEFBQcwAoZCaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL3BraS9jZXJ0cy9NaWNSb29DZXJBdXQyMDExXzIwMTFf +# MDNfMjIuY3J0MIGfBgNVHSAEgZcwgZQwgZEGCSsGAQQBgjcuAzCBgzA/BggrBgEF +# BQcCARYzaHR0cDovL3d3dy5taWNyb3NvZnQuY29tL3BraW9wcy9kb2NzL3ByaW1h +# cnljcHMuaHRtMEAGCCsGAQUFBwICMDQeMiAdAEwAZQBnAGEAbABfAHAAbwBsAGkA +# YwB5AF8AcwB0AGEAdABlAG0AZQBuAHQALiAdMA0GCSqGSIb3DQEBCwUAA4ICAQBn +# 8oalmOBUeRou09h0ZyKbC5YR4WOSmUKWfdJ5DJDBZV8uLD74w3LRbYP+vj/oCso7 +# v0epo/Np22O/IjWll11lhJB9i0ZQVdgMknzSGksc8zxCi1LQsP1r4z4HLimb5j0b +# pdS1HXeUOeLpZMlEPXh6I/MTfaaQdION9MsmAkYqwooQu6SpBQyb7Wj6aC6VoCo/ +# KmtYSWMfCWluWpiW5IP0wI/zRive/DvQvTXvbiWu5a8n7dDd8w6vmSiXmE0OPQvy +# CInWH8MyGOLwxS3OW560STkKxgrCxq2u5bLZ2xWIUUVYODJxJxp/sfQn+N4sOiBp +# mLJZiWhub6e3dMNABQamASooPoI/E01mC8CzTfXhj38cbxV9Rad25UAqZaPDXVJi +# hsMdYzaXht/a8/jyFqGaJ+HNpZfQ7l1jQeNbB5yHPgZ3BtEGsXUfFL5hYbXw3MYb +# BL7fQccOKO7eZS/sl/ahXJbYANahRr1Z85elCUtIEJmAH9AAKcWxm6U/RXceNcbS +# oqKfenoi+kiVH6v7RyOA9Z74v2u3S5fi63V4GuzqN5l5GEv/1rMjaHXmr/r8i+sL +# gOppO6/8MO0ETI7f33VtY5E90Z1WTk+/gFcioXgRMiF670EKsT/7qMykXcGhiJtX +# cVZOSEXAQsmbdlsKgEhr/Xmfwb1tbWrJUnMTDXpQzTGCGYIwghl+AgEBMIGVMH4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01p +# Y3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTECEzMAAAJT44Pelt7FbswAAAAA +# AlMwDQYJYIZIAWUDBAIBBQCgga4wGQYJKoZIhvcNAQkDMQwGCisGAQQBgjcCAQQw +# HAYKKwYBBAGCNwIBCzEOMAwGCisGAQQBgjcCARUwLwYJKoZIhvcNAQkEMSIEIFtW +# lCf/wF6GbazDLehu9mS8WGiUm3tcu/5KpjXCCIesMEIGCisGAQQBgjcCAQwxNDAy +# oBSAEgBNAGkAYwByAG8AcwBvAGYAdKEagBhodHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20wDQYJKoZIhvcNAQEBBQAEggEAwETHSA6ffhhSRTPNmFZi9dFIoroMG7jii7/9 +# LMoCAZgwSeSzaZs3vo1EaRFFZvaKoQkPITRK9rBjINt9jLGalSs2jrwdM/E5wa8k +# bKFY4VEB/aJffdDIGiAsMu+gOWxCOFS8xdYzlZfRlFe9f6VVRtCyubOok3BSZRrq +# cCFpCprCa34w/J8erd7FF54qJ2IrO3bEd4w+23w3qg1BwtPr/vqLchKyIEBjzpcL +# 78/CJEUpFbtz/VuvyLfeVoSEnj6Ctf0tFDvRDNXGPqp1ZJSitKDsrQseb2K5/pZO +# nyykNPzpxJmIaLHDgBjtGlXuiHUEuJAFV96oucoIydX1dqQal6GCFwwwghcIBgor +# BgEEAYI3AwMBMYIW+DCCFvQGCSqGSIb3DQEHAqCCFuUwghbhAgEDMQ8wDQYJYIZI +# AWUDBAIBBQAwggFVBgsqhkiG9w0BCRABBKCCAUQEggFAMIIBPAIBAQYKKwYBBAGE +# WQoDATAxMA0GCWCGSAFlAwQCAQUABCCmK+qErRnVeAaOr2etmjZMIBNv7JyifxQE +# HuWjeTnqvAIGYrGiJBRoGBMyMDIyMDcwMTIxMDAwMS42ODRaMASAAgH0oIHUpIHR +# MIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQL +# EyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhh +# bGVzIFRTUyBFU046NDYyRi1FMzE5LTNGMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBU +# aW1lLVN0YW1wIFNlcnZpY2WgghFfMIIHEDCCBPigAwIBAgITMwAAAaQHz+OPo7pv +# 1gABAAABpDANBgkqhkiG9w0BAQsFADB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0Eg +# MjAxMDAeFw0yMjAzMDIxODUxMThaFw0yMzA1MTExODUxMThaMIHOMQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQg +# T3BlcmF0aW9ucyBQdWVydG8gUmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046 +# NDYyRi1FMzE5LTNGMjAxJTAjBgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNl +# cnZpY2UwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDAR44A+hT8vNT1 +# IXDiFRoeGzkmqut+GPk41toTRfQZZ1sSyQhLjIlemBecemEzO09WSzOjZx9MIT8q +# Ys921WUZsIBsk1ESn1cjyfPUd1mmfxzL3ACWZwjIC/pjqcRPeIMECQ/6qPFKrjqw +# igmP33I3IcVfMjJHyKj+vR51n1tK2rZPiNhmRdiEhckbbxLsSb2nCBQxZEF49x/l +# 8vSB8zaqovoOeIkIzgDerN7OvJouq6r+vg/Qz1T4NXr+sKKyNxZWM6zywiLp7G7W +# Ld18N2hyjHwPkh/AleIqif3hGVD9bhSU+dDADzUJSMFhEWunHHElQeZjdmIB3/Mw +# 1KkFOJNvw1sPteIi5MK4DZX3Wd/Fd8ZsQvZmXPWJ8BXN9sYtHMz8zdeQvMImRCKg +# nXcW8IpnPtC7Tymp3UV5NoTH8INF6WWicQ3y04L2I1VOT104AddJoVgAP2KLIGwf +# Cs7wMVz56xJ2IN1y1pIAWfpTqx76orM5RQhkAvayj1RTwgrHst+elYX3F5b8ACWr +# gJO1dJy1U4MIv+SC8h33xLmWA568emvrJ6g0xy/2akbAeRx6tFwaP4uwVbjF50kl +# 5RQqNzp/CDpfCTikOAqyJa4valiWDMbEiArHKLYDg6GDjuJZl5bSjgdJdCAIRF8E +# kiiA+UAGvcE6SGoHmtoc4yOklGNVvwIDAQABo4IBNjCCATIwHQYDVR0OBBYEFOLQ +# E5+s+AgS9sWUHdI4zekp4yTCMB8GA1UdIwQYMBaAFJ+nFV0AXmJdg/Tl0mWnG1M1 +# GelyMF8GA1UdHwRYMFYwVKBSoFCGTmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9w +# a2lvcHMvY3JsL01pY3Jvc29mdCUyMFRpbWUtU3RhbXAlMjBQQ0ElMjAyMDEwKDEp +# LmNybDBsBggrBgEFBQcBAQRgMF4wXAYIKwYBBQUHMAKGUGh0dHA6Ly93d3cubWlj +# cm9zb2Z0LmNvbS9wa2lvcHMvY2VydHMvTWljcm9zb2Z0JTIwVGltZS1TdGFtcCUy +# MFBDQSUyMDIwMTAoMSkuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB +# BQUHAwgwDQYJKoZIhvcNAQELBQADggIBAAlWHFDRDJck7jwwRoYmdVOePLLBeido +# PUBJVhG9nGeHS9PuRvO9tf4IkbUz74MUIQxeayQoxxo/JxUqjhPH52M/b4G9mHJW +# B75KCllCTg8Y4VkvktOmS0f5w0vOR3gwA9BRnbgAPNEO7xs5Jylto8aDR02++CkB +# DFolCtTNjwzfniEj1z4T7nRlRi2yBAJNRqI+VY820LiyoZtk5OGttq5F5HhPfIMj +# aIx5QYR22+53sd8xgUwRpFbcLdrne6jdq3KbiYbCf7y/9F2C7cjpO3kkGXX8ntE0 +# 9f6o9fIklx7CFw4RzrkyqgYomraKOFJ8JO7hsjNJb9/Gba/mKWo0j/qdDxDER/UX +# X6ykZuGx1eQpjkyMwJnOPWGbeNIYZVcJQpRQODPs593Mi5hBsHzag+vd4Q+Vt73K +# Z4X98YWW1Vk1aSR9Qjxk5keMuVPZMcMrCvFZXwhUcGFGueuNCrICL9bSYRfS13pl +# iDxJ7sPSZ8x2d4ksOXW00l6fR5nTiSM7Dvv7Y0MGVgUhap2smhr92PMNSmIkCUvH +# CiYcJ4RoAT28mp/hOQ/U8mPXSpWdxYpLLcDOISmBhFJYN7amlhIpVsGvUmjXrTcY +# 0n4Goe/Nqs2400IcA4HOiX9OxdmpNGDJzSRR7AW9TT8O+3YZqPZIvL6yzgfvnehp +# tmf4w6QzkrLfMIIHcTCCBVmgAwIBAgITMwAAABXF52ueAptJmQAAAAAAFTANBgkq +# hkiG9w0BAQsFADCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJvb3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 +# IDIwMTAwHhcNMjEwOTMwMTgyMjI1WhcNMzAwOTMwMTgzMjI1WjB8MQswCQYDVQQG +# EwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG +# A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQg +# VGltZS1TdGFtcCBQQ0EgMjAxMDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoC +# ggIBAOThpkzntHIhC3miy9ckeb0O1YLT/e6cBwfSqWxOdcjKNVf2AX9sSuDivbk+ +# F2Az/1xPx2b3lVNxWuJ+Slr+uDZnhUYjDLWNE893MsAQGOhgfWpSg0S3po5GawcU +# 88V29YZQ3MFEyHFcUTE3oAo4bo3t1w/YJlN8OWECesSq/XJprx2rrPY2vjUmZNqY +# O7oaezOtgFt+jBAcnVL+tuhiJdxqD89d9P6OU8/W7IVWTe/dvI2k45GPsjksUZzp +# cGkNyjYtcI4xyDUoveO0hyTD4MmPfrVUj9z6BVWYbWg7mka97aSueik3rMvrg0Xn +# Rm7KMtXAhjBcTyziYrLNueKNiOSWrAFKu75xqRdbZ2De+JKRHh09/SDPc31BmkZ1 +# zcRfNN0Sidb9pSB9fvzZnkXftnIv231fgLrbqn427DZM9ituqBJR6L8FA6PRc6ZN +# N3SUHDSCD/AQ8rdHGO2n6Jl8P0zbr17C89XYcz1DTsEzOUyOArxCaC4Q6oRRRuLR +# vWoYWmEBc8pnol7XKHYC4jMYctenIPDC+hIK12NvDMk2ZItboKaDIV1fMHSRlJTY +# uVD5C4lh8zYGNRiER9vcG9H9stQcxWv2XFJRXRLbJbqvUAV6bMURHXLvjflSxIUX +# k8A8FdsaN8cIFRg/eKtFtvUeh17aj54WcmnGrnu3tz5q4i6tAgMBAAGjggHdMIIB +# 2TASBgkrBgEEAYI3FQEEBQIDAQABMCMGCSsGAQQBgjcVAgQWBBQqp1L+ZMSavoKR +# PEY1Kc8Q/y8E7jAdBgNVHQ4EFgQUn6cVXQBeYl2D9OXSZacbUzUZ6XIwXAYDVR0g +# BFUwUzBRBgwrBgEEAYI3TIN9AQEwQTA/BggrBgEFBQcCARYzaHR0cDovL3d3dy5t +# aWNyb3NvZnQuY29tL3BraW9wcy9Eb2NzL1JlcG9zaXRvcnkuaHRtMBMGA1UdJQQM +# MAoGCCsGAQUFBwMIMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQE +# AwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQ +# W9fOmhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNv +# bS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBa +# BggrBgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0 +# LmNvbS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MA0GCSqG +# SIb3DQEBCwUAA4ICAQCdVX38Kq3hLB9nATEkW+Geckv8qW/qXBS2Pk5HZHixBpOX +# PTEztTnXwnE2P9pkbHzQdTltuw8x5MKP+2zRoZQYIu7pZmc6U03dmLq2HnjYNi6c +# qYJWAAOwBb6J6Gngugnue99qb74py27YP0h1AdkY3m2CDPVtI1TkeFN1JFe53Z/z +# jj3G82jfZfakVqr3lbYoVSfQJL1AoL8ZthISEV09J+BAljis9/kpicO8F7BUhUKz +# /AyeixmJ5/ALaoHCgRlCGVJ1ijbCHcNhcy4sa3tuPywJeBTpkbKpW99Jo3QMvOyR +# gNI95ko+ZjtPu4b6MhrZlvSP9pEB9s7GdP32THJvEKt1MMU0sHrYUP4KWN1APMdU +# bZ1jdEgssU5HLcEUBHG/ZPkkvnNtyo4JvbMBV0lUZNlz138eW0QBjloZkWsNn6Qo +# 3GcZKCS6OEuabvshVGtqRRFHqfG3rsjoiV5PndLQTHa1V1QJsWkBRH58oWFsc/4K +# u+xBZj1p/cvBQUl+fpO+y/g75LcVv7TOPqUxUYS8vwLBgqJ7Fx0ViY1w/ue10Cga +# iQuPNtq6TPmb/wrpNPgkNWcr4A245oyZ1uEi6vAnQj0llOZ0dFtq0Z4+7X6gMTN9 +# vMvpe784cETRkPHIqzqKOghif9lwY1NNje6CbaUFEMFxBmoQtB1VM1izoXBm8qGC +# AtIwggI7AgEBMIH8oYHUpIHRMIHOMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2Fz +# aGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENv +# cnBvcmF0aW9uMSkwJwYDVQQLEyBNaWNyb3NvZnQgT3BlcmF0aW9ucyBQdWVydG8g +# UmljbzEmMCQGA1UECxMdVGhhbGVzIFRTUyBFU046NDYyRi1FMzE5LTNGMjAxJTAj +# BgNVBAMTHE1pY3Jvc29mdCBUaW1lLVN0YW1wIFNlcnZpY2WiIwoBATAHBgUrDgMC +# GgMVADQcKOKTa3xC+g1aPrcPerxiby6foIGDMIGApH4wfDELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgUENBIDIwMTAwDQYJKoZIhvcNAQEFBQACBQDmaU8/MCIYDzIwMjIwNzAx +# MTQ0NzI3WhgPMjAyMjA3MDIxNDQ3MjdaMHcwPQYKKwYBBAGEWQoEATEvMC0wCgIF +# AOZpTz8CAQAwCgIBAAICCNwCAf8wBwIBAAICEp4wCgIFAOZqoL8CAQAwNgYKKwYB +# BAGEWQoEAjEoMCYwDAYKKwYBBAGEWQoDAqAKMAgCAQACAwehIKEKMAgCAQACAwGG +# oDANBgkqhkiG9w0BAQUFAAOBgQChh1QY1GFw/ekl4Qr+s3lJOVBWDUpw+EfxjUrG +# H7ud+AMLNhAxbx7uEfNyNYsi5CnaaXgA2vO00zzDNvaugU/yTYxEm2j0wybQTDfb +# BMyUPQB6VhcFgCAOC4CQalIa1F5xOFEYV2P5/OY3Fza/pCGQKiBEDyRG6PfCbmAZ +# qvg7cTGCBA0wggQJAgEBMIGTMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# AhMzAAABpAfP44+jum/WAAEAAAGkMA0GCWCGSAFlAwQCAQUAoIIBSjAaBgkqhkiG +# 9w0BCQMxDQYLKoZIhvcNAQkQAQQwLwYJKoZIhvcNAQkEMSIEIADlNEKhG3BnRkhq +# cXD/tb/UR5BXxdcKHp5Lt8yia4ZxMIH6BgsqhkiG9w0BCRACLzGB6jCB5zCB5DCB +# vQQgBfzgoyEmcKTASfDCd1sDAhd6jmuWBxRuieLh42rqefgwgZgwgYCkfjB8MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNy +# b3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMAITMwAAAaQHz+OPo7pv1gABAAABpDAi +# BCBoSzA9nCaw+Pk/XGE5FyHMvYr0QOQne8MeefMn3+/YZjANBgkqhkiG9w0BAQsF +# AASCAgAVCHZ4zg4+oaqb2mTbyBn1iELiCCLutlG+ij8/By/6KzRjhS79sCiJwxuR +# 0X6tVul89m21uZwJPqhV7v4FkoAC9phxUr5T7YxGrPXzHpJitYLWCG9GewR2PfPr +# y7JhUCPAN4hNHR4Z8GLJFTXv1eQyKkeDlSxivC29GWn1X7eTfAKCwtDyjkWeoe4c +# hrHzgdREjT7p7z0UKXn5zT4CYsdCYYePTcrtcPQ8YKRZfQhVhAcHPzo1f2r5bRdr +# EkAmFHpx0hhKEPRke/dszJ14ykLOqEgRMAqyzgE9WUGc87TGdhGrsB7kZHPgnDLn +# MyP5FReopTZDLlmEg++XN1/FCfVr1aUVXwlr8U5BQLMhhz/L14pX7Rpho15O13WR +# FpuqsPpy+Age70j5efz9BiSnT4jyUzkFdKSbUf0512UrfhVsqw7POXkuvd+VSqH0 +# i3VpF6E7MlPUw/xWBE8xyfGISmMM4awoQBjOs4ir60ktDowkvckkmoSGgf9YLFXt +# jwnh65g7XvREpcLsJpOx6PvMKxWQ0ePPy1kl1uKWWpSe7kNwdMywl60FBFC1hWVi +# ktBE98W2j6B4MwGSL2F7hWz7LNs1ROI8YAPaj6+2gK7oYEQvd0wVvsNyzDQ6oIiJ +# VU2Rt5sblCaZvMF4XMba1eIRpNOrm45iPMmRsypgZdBSy4wtOw== +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.psm1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.psm1 index e0373e97e4..f804196610 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.psm1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.psm1 @@ -1,863 +1,863 @@ -# -# Copyright (c) Microsoft Corporation. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# - -$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent - -# Import localization helper functions. -$helperName = 'PowerShellGet.LocalizationHelper' -$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" -Import-Module -Name $dscResourcesFolderFilePath - -$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_PSModule' -ScriptRoot $PSScriptRoot - -# Import resource helper functions. -$helperName = 'PowerShellGet.ResourceHelper' -$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" -Import-Module -Name $dscResourcesFolderFilePath - -<# - .SYNOPSIS - This DSC resource provides a mechanism to download PowerShell modules from the PowerShell - Gallery and install it on your computer. - - Get-TargetResource returns the current state of the resource. - - .PARAMETER Name - Specifies the name of the PowerShell module to be installed or uninstalled. - - .PARAMETER Repository - Specifies the name of the module source repository where the module can be found. - - .PARAMETER RequiredVersion - Provides the version of the module you want to install or uninstall. - - .PARAMETER MaximumVersion - Provides the maximum version of the module you want to install or uninstall. - - .PARAMETER MinimumVersion - Provides the minimum version of the module you want to install or uninstall. - - .PARAMETER Force - Forces the installation of modules. If a module of the same name and version already exists on the computer, - this parameter overwrites the existing module with one of the same name that was found by the command. - - .PARAMETER AllowClobber - Allows the installation of modules regardless of if other existing module on the computer have cmdlets - of the same name. - - .PARAMETER SkipPublisherCheck - Allows the installation of modules that have not been catalog signed. -#> -function Get-TargetResource { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $Repository = 'PSGallery', - - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MaximumVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.Boolean] - $Force, - - [Parameter()] - [System.Boolean] - $AllowClobber, - - [Parameter()] - [System.Boolean] - $SkipPublisherCheck - ) - - $returnValue = @{ - Ensure = 'Absent' - Name = $Name - Repository = $Repository - Description = $null - Guid = $null - ModuleBase = $null - ModuleType = $null - Author = $null - InstalledVersion = $null - RequiredVersion = $RequiredVersion - MinimumVersion = $MinimumVersion - MaximumVersion = $MaximumVersion - Force = $Force - AllowClobber = $AllowClobber - SkipPublisherCheck = $SkipPublisherCheck - InstallationPolicy = $null - } - - Write-Verbose -Message ($localizedData.GetTargetResourceMessage -f $Name) - - $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') - - # Get the module with the right version and repository properties. - $modules = Get-RightModule @extractedArguments -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - # If the module is found, the count > 0 - if ($modules.Count -gt 0) { - Write-Verbose -Message ($localizedData.ModuleFound -f $Name) - - # Find a module with the latest version and return its properties. - $latestModule = $modules[0] - - foreach ($module in $modules) { - if ($module.Version -gt $latestModule.Version) { - $latestModule = $module - } - } - - # Check if the repository matches. - $repositoryName = Get-ModuleRepositoryName -Module $latestModule -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - if ($repositoryName) { - $installationPolicy = Get-InstallationPolicy -RepositoryName $repositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - } - - if ($installationPolicy) { - $installationPolicyReturnValue = 'Trusted' - } - else { - $installationPolicyReturnValue = 'Untrusted' - } - - $returnValue.Ensure = 'Present' - $returnValue.Repository = $repositoryName - $returnValue.Description = $latestModule.Description - $returnValue.Guid = $latestModule.Guid - $returnValue.ModuleBase = $latestModule.ModuleBase - $returnValue.ModuleType = $latestModule.ModuleType - $returnValue.Author = $latestModule.Author - $returnValue.InstalledVersion = $latestModule.Version - $returnValue.InstallationPolicy = $installationPolicyReturnValue - } - else { - Write-Verbose -Message ($localizedData.ModuleNotFound -f $Name) - } - - return $returnValue -} - -<# - .SYNOPSIS - This DSC resource provides a mechanism to download PowerShell modules from the PowerShell - Gallery and install it on your computer. - - Test-TargetResource validates whether the resource is currently in the desired state. - - .PARAMETER Ensure - Determines whether the module to be installed or uninstalled. - - .PARAMETER Name - Specifies the name of the PowerShell module to be installed or uninstalled. - - .PARAMETER Repository - Specifies the name of the module source repository where the module can be found. - - .PARAMETER InstallationPolicy - Determines whether you trust the source repository where the module resides. - - .PARAMETER RequiredVersion - Provides the version of the module you want to install or uninstall. - - .PARAMETER MaximumVersion - Provides the maximum version of the module you want to install or uninstall. - - .PARAMETER MinimumVersion - Provides the minimum version of the module you want to install or uninstall. - - .PARAMETER Force - Forces the installation of modules. If a module of the same name and version already exists on the computer, - this parameter overwrites the existing module with one of the same name that was found by the command. - - .PARAMETER AllowClobber - Allows the installation of modules regardless of if other existing module on the computer have cmdlets - of the same name. - - .PARAMETER SkipPublisherCheck - Allows the installation of modules that have not been catalog signed. -#> -function Test-TargetResource { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $Repository = 'PSGallery', - - [Parameter()] - [ValidateSet('Trusted', 'Untrusted')] - [System.String] - $InstallationPolicy = 'Untrusted', - - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MaximumVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.Boolean] - $Force, - - [Parameter()] - [System.Boolean] - $AllowClobber, - - [Parameter()] - [System.Boolean] - $SkipPublisherCheck - ) - - Write-Verbose -Message ($localizedData.TestTargetResourceMessage -f $Name) - - $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') - - $status = Get-TargetResource @extractedArguments - - # The ensure returned from Get-TargetResource is not equal to the desired $Ensure. - if ($status.Ensure -ieq $Ensure) { - Write-Verbose -Message ($localizedData.InDesiredState -f $Name) - return $true - } - else { - Write-Verbose -Message ($localizedData.NotInDesiredState -f $Name) - return $false - } -} - -<# - .SYNOPSIS - This DSC resource provides a mechanism to download PowerShell modules from the PowerShell - Gallery and install it on your computer. - - Set-TargetResource sets the resource to the desired state. "Make it so". - - .PARAMETER Ensure - Determines whether the module to be installed or uninstalled. - - .PARAMETER Name - Specifies the name of the PowerShell module to be installed or uninstalled. - - .PARAMETER Repository - Specifies the name of the module source repository where the module can be found. - - .PARAMETER InstallationPolicy - Determines whether you trust the source repository where the module resides. - - .PARAMETER RequiredVersion - Provides the version of the module you want to install or uninstall. - - .PARAMETER MaximumVersion - Provides the maximum version of the module you want to install or uninstall. - - .PARAMETER MinimumVersion - Provides the minimum version of the module you want to install or uninstall. - - .PARAMETER Force - Forces the installation of modules. If a module of the same name and version already exists on the computer, - this parameter overwrites the existing module with one of the same name that was found by the command. - - .PARAMETER AllowClobber - Allows the installation of modules regardless of if other existing module on the computer have cmdlets - of the same name. - - .PARAMETER SkipPublisherCheck - Allows the installation of modules that have not been catalog signed. -#> -function Set-TargetResource { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-TryStatement', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-CatchClause', '')] - [CmdletBinding()] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $Repository = 'PSGallery', - - [Parameter()] - [ValidateSet('Trusted', 'Untrusted')] - [System.String] - $InstallationPolicy = 'Untrusted', - - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MaximumVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.Boolean] - $Force, - - [Parameter()] - [System.Boolean] - $AllowClobber, - - [Parameter()] - [System.Boolean] - $SkipPublisherCheck - ) - - # Validate the repository argument - if ($PSBoundParameters.ContainsKey('Repository')) { - Test-ParameterValue -Value $Repository -Type 'PackageSource' -ProviderName 'PowerShellGet' -Verbose - } - - if ($Ensure -ieq 'Present') { - # Version check - $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ('MinimumVersion', 'MaximumVersion', 'RequiredVersion') - - $null = Test-VersionParameter @extractedArguments - - try { - $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') - - Write-Verbose -Message ($localizedData.StartFindModule -f $Name) - - $modules = Find-Module @extractedArguments -ErrorVariable ev - } - catch { - $errorMessage = $script:localizedData.ModuleNotFoundInRepository -f $Name - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - - $trusted = $null - $moduleFound = $null - - foreach ($m in $modules) { - # Check for the installation policy. - $trusted = Get-InstallationPolicy -RepositoryName $m.Repository -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - # Stop the loop if found a trusted repository. - if ($trusted) { - $moduleFound = $m - break; - } - } - - try { - # The repository is trusted, so we install it. - if ($trusted) { - Write-Verbose -Message ($localizedData.StartInstallModule -f $Name, $moduleFound.Version.toString(), $moduleFound.Repository) - - # Extract the installation options. - $extractedSwitches = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters -ArgumentNames ('Force', 'AllowClobber', 'SkipPublisherCheck') - - $moduleFound | Install-Module @extractedSwitches 2>&1 | out-string | Write-Verbose - } - # The repository is untrusted but user's installation policy is trusted, so we install it with a warning. - elseif ($InstallationPolicy -ieq 'Trusted') { - Write-Warning -Message ($localizedData.InstallationPolicyWarning -f $Name, $modules[0].Repository, $InstallationPolicy) - - # Extract installation options (Force implied by InstallationPolicy). - $extractedSwitches = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters -ArgumentNames ('AllowClobber', 'SkipPublisherCheck') - - # If all the repositories are untrusted, we choose the first one. - $modules[0] | Install-Module @extractedSwitches -Force 2>&1 | out-string | Write-Verbose - } - # Both user and repository is untrusted - else { - $errorMessage = $script:localizedData.InstallationPolicyFailed -f $InstallationPolicy, 'Untrusted' - New-InvalidOperationException -Message $errorMessage - } - - Write-Verbose -Message ($localizedData.InstalledSuccess -f $Name) - } - catch { - $errorMessage = $script:localizedData.FailToInstall -f $Name - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } - # Ensure=Absent - else { - - $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') - - # Get the module with the right version and repository properties. - $modules = Get-RightModule @extractedArguments - - if (-not $modules) { - $errorMessage = $script:localizedData.ModuleWithRightPropertyNotFound -f $Name - New-InvalidOperationException -Message $errorMessage - } - - foreach ($module in $modules) { - # Get the path where the module is installed. - $path = $module.ModuleBase - - Write-Verbose -Message ($localizedData.StartUnInstallModule -f $Name) - - try { - <# - There is no Uninstall-Module cmdlet for Windows PowerShell 4.0, - so we will remove the ModuleBase folder as an uninstall operation. - #> - Microsoft.PowerShell.Management\Remove-Item -Path $path -Force -Recurse - - Write-Verbose -Message ($localizedData.UnInstalledSuccess -f $module.Name) - } - catch { - $errorMessage = $script:localizedData.FailToUninstall -f $module.Name - New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ - } - } # foreach - } # Ensure=Absent -} - -<# - .SYNOPSIS - This is a helper function. It returns the modules that meet the specified versions and the repository requirements. - - .PARAMETER Name - Specifies the name of the PowerShell module. - - .PARAMETER RequiredVersion - Provides the version of the module you want to install or uninstall. - - .PARAMETER MaximumVersion - Provides the maximum version of the module you want to install or uninstall. - - .PARAMETER MinimumVersion - Provides the minimum version of the module you want to install or uninstall. - - .PARAMETER Repository - Specifies the name of the module source repository where the module can be found. -#> -function Get-RightModule { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Name, - - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.String] - $MaximumVersion, - - [Parameter()] - [System.String] - $Repository - ) - - Write-Verbose -Message ($localizedData.StartGetModule -f $($Name)) - - $modules = Microsoft.PowerShell.Core\Get-Module -Name $Name -ListAvailable -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - if (-not $modules) { - return $null - } - - <# - As Get-Module does not take RequiredVersion, MinimumVersion, MaximumVersion, or Repository, - below we need to check whether the modules are containing the right version and repository - location. - #> - - $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames ('MaximumVersion', 'MinimumVersion', 'RequiredVersion') - $returnVal = @() - - foreach ($m in $modules) { - $versionMatch = $false - $installedVersion = $m.Version - - # Case 1 - a user provides none of RequiredVersion, MinimumVersion, MaximumVersion - if ($extractedArguments.Count -eq 0) { - $versionMatch = $true - } - - # Case 2 - a user provides RequiredVersion - elseif ($extractedArguments.ContainsKey('RequiredVersion')) { - # Check if it matches with the installed version - $versionMatch = ($installedVersion -eq [System.Version] $RequiredVersion) - } - else { - - # Case 3 - a user provides MinimumVersion - if ($extractedArguments.ContainsKey('MinimumVersion')) { - $versionMatch = ($installedVersion -ge [System.Version] $extractedArguments['MinimumVersion']) - } - - # Case 4 - a user provides MaximumVersion - if ($extractedArguments.ContainsKey('MaximumVersion')) { - $isLessThanMax = ($installedVersion -le [System.Version] $extractedArguments['MaximumVersion']) - - if ($extractedArguments.ContainsKey('MinimumVersion')) { - $versionMatch = $versionMatch -and $isLessThanMax - } - else { - $versionMatch = $isLessThanMax - } - } - - # Case 5 - Both MinimumVersion and MaximumVersion are provided. It's covered by the above. - # Do not return $false yet to allow the foreach to continue - if (-not $versionMatch) { - Write-Verbose -Message ($localizedData.VersionMismatch -f $Name, $installedVersion) - $versionMatch = $false - } - } - - # Case 6 - Version matches but need to check if the module is from the right repository. - if ($versionMatch) { - # A user does not provide Repository, we are good - if (-not $PSBoundParameters.ContainsKey('Repository')) { - Write-Verbose -Message ($localizedData.ModuleFound -f "$Name $installedVersion") - $returnVal += $m - } - else { - # Check if the Repository matches - $sourceName = Get-ModuleRepositoryName -Module $m - - if ($Repository -ieq $sourceName) { - Write-Verbose -Message ($localizedData.ModuleFound -f "$Name $installedVersion") - $returnVal += $m - } - else { - Write-Verbose -Message ($localizedData.RepositoryMismatch -f $($Name), $($sourceName)) - } - } - } - } # foreach - - return $returnVal -} - -<# - .SYNOPSIS - This is a helper function that returns the module's repository name. - - .PARAMETER Module - Specifies the name of the PowerShell module. -#> -function Get-ModuleRepositoryName { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.Object] - $Module - ) - - <# - RepositorySourceLocation property is supported in PS V5 only. To work with the earlier - PowerShell version, we need to do a different way. PSGetModuleInfo.xml exists for any - PowerShell modules downloaded through PSModule provider. - #> - $psGetModuleInfoFileName = 'PSGetModuleInfo.xml' - $psGetModuleInfoPath = Microsoft.PowerShell.Management\Join-Path -Path $Module.ModuleBase -ChildPath $psGetModuleInfoFileName - - Write-Verbose -Message ($localizedData.FoundModulePath -f $psGetModuleInfoPath) - - if (Microsoft.PowerShell.Management\Test-path -Path $psGetModuleInfoPath) { - $psGetModuleInfo = Microsoft.PowerShell.Utility\Import-Clixml -Path $psGetModuleInfoPath - - return $psGetModuleInfo.Repository - } -} - -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCig4H1kVMWxA7U -# uX1JaRBJmDsHSZHmuBUTkDBwWHafCqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg2t+ikpO2 -# 0WdDEf/JRKsEEOWFz2O6CEnKmWwPbrvPFuIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQBaj/JIwAbud/IMJN3SHVwenJwEjzrmM3cPznYlvkyE -# rnB6RiDYnohm+72wkCmVWobuualV9Xw8OGA9w7MvJjZuEnFP9TwDjlWinR4FpdVl -# YX49u4g50iMR2isaKTxMA2nA2LbyFpTLMqi6ynPLnv251xTdumj/XRLZjHdpr4Pt -# p6HQz45/jMWRtlh8x3piJPrgpINuJtmO91tnMQnykF++QgteDqKSboXOu2Wg0oCn -# Ao0eOifIujyUp2ez9Cqx2hzkMOqNa5jZD4JetiDTWBTkN8XHRbsfCVwyEQzxrpXn -# wAFT7jMzuI8o8xdCuCvuDokIzcnm+rKZJIy6Rwp6Cn11oYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIL/3GmTbcOXlRJe8Pu+1/QVBo0Mk83o/PGK7iApB -# aAutAgZfYQoJ7SUYEzIwMjAwOTIyMjIxOTUxLjg4NlowBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjo4OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABLCKvRZd1+RvuAAAA -# AAEsMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTUwM1oXDTIxMDMxNzAxMTUwM1owgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4OTdB -# LUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPK1zgSSq+MxAYo3qpCt -# QDxSMPPJy6mm/wfEJNjNUnYtLFBwl1BUS5trEk/t41ldxITKehs+ABxYqo4Qxsg3 -# Gy1ugKiwHAnYiiekfC+ZhptNFgtnDZIn45zC0AlVr/6UfLtsLcHCh1XElLUHfEC0 -# nBuQcM/SpYo9e3l1qY5NdMgDGxCsmCKdiZfYXIu+U0UYIBhdzmSHnB3fxZOBVcr5 -# htFHEBBNt/rFJlm/A4yb8oBsp+Uf0p5QwmO/bCcdqB15JpylOhZmWs0sUfJKlK9E -# rAhBwGki2eIRFKsQBdkXS9PWpF1w2gIJRvSkDEaCf+lbGTPdSzHSbfREWOF9wY3i -# Yj8CAwEAAaOCARswggEXMB0GA1UdDgQWBBRRahZSGfrCQhCyIyGH9DkiaW7L0zAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBPFxHIwi4vAH49w9Svmz6K3tM55RlW -# 5pPeULXdut2Rqy6Ys0+VpZsbuaEoxs6Z1C3hMbkiqZFxxyltxJpuHTyGTg61zfNI -# F5n6RsYF3s7IElDXNfZznF1/2iWc6uRPZK8rxxUJ/7emYXZCYwuUY0XjsCpP9pbR -# RKeJi6r5arSyI+NfKxvgoM21JNt1BcdlXuAecdd/k8UjxCscffanoK2n6LFw1PcZ -# lEO7NId7o+soM2C0QY5BYdghpn7uqopB6ixyFIIkDXFub+1E7GmAEwfU6VwEHL7y -# 9rNE8bd+JrQs+yAtkkHy9FmXg/PsGq1daVzX1So7CJ6nyphpuHSN3VfTMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4 -# OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUADE5OKSMoNx/mYxYWap1RTOohbJ2ggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMUwsMwIhgPMjAyMDA5MjIyMjM2NTFaGA8yMDIwMDkyMzIyMzY1MVowdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA4xTCwwIBADAKAgEAAgIlfwIB/zAHAgEAAgIRzTAK -# AgUA4xYUQwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEmZCk5PsMA6oG40 -# YulPUpxDtQA+JAuXyNrLI4wJcdmv34Bzmj9gk3gv6ZxQZxlvZjA1O1P3lcKGIYco -# 9UkRdAeP6Zzfim6U3UoiUz1AHKuSm6iLux7Z/hNtgEs2ZpYXxOK5JZ/9Pa2I5mg0 -# VxsGdXBxl6TOZqbB6aGNG1ZKytRWMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEsIq9Fl3X5G+4AAAAAASwwDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQgioHn9SQtTXybUUTzgPmt61UPBGskDV9AErEC62Y8+H4wgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCBbn/0uFFh42hTM5XOoKdXevBaiSxmYK9Ilcn9n -# u5ZH4TCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# LCKvRZd1+RvuAAAAAAEsMCIEIJmkMFyuUW5yiXAemMCWHyk8T1EMMlB1cEiVD4/E -# +vhqMA0GCSqGSIb3DQEBCwUABIIBAOyFm0zpO+VogyMfTuCFeJjMyyDnAJRy/KhW -# jtq11pPg3e11HU0LuNa+ZpCrJXbSWlF+tLkU9eQXPpGPzO7ITGhxhnq5wyhfzA8a -# 9TRGlDMUNHp+6WhH6BZ86zUqMpcYx5zN0H9TaJTpC8JJ7HrNTW8zp032ocr6IXVr -# M6mOa0MUZsE366+OXRpCCm0fokT50uvXWbcRdngS9ZT5xmz134T1iS4Fds7ZOC5W -# m0VofKEuaTYbTc5aF0WN/OGMvowHRxlJhG7zhMX8ZuXGvbgzANq/o7tZu0HmGFTN -# rPs5smC3VEWVpNWgOTfrO3bfaWkAdsuBeLVXZUog+M4wmTDFSPs= -# SIG # End signature block +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent + +# Import localization helper functions. +$helperName = 'PowerShellGet.LocalizationHelper' +$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" +Import-Module -Name $dscResourcesFolderFilePath + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_PSModule' -ScriptRoot $PSScriptRoot + +# Import resource helper functions. +$helperName = 'PowerShellGet.ResourceHelper' +$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" +Import-Module -Name $dscResourcesFolderFilePath + +<# + .SYNOPSIS + This DSC resource provides a mechanism to download PowerShell modules from the PowerShell + Gallery and install it on your computer. + + Get-TargetResource returns the current state of the resource. + + .PARAMETER Name + Specifies the name of the PowerShell module to be installed or uninstalled. + + .PARAMETER Repository + Specifies the name of the module source repository where the module can be found. + + .PARAMETER RequiredVersion + Provides the version of the module you want to install or uninstall. + + .PARAMETER MaximumVersion + Provides the maximum version of the module you want to install or uninstall. + + .PARAMETER MinimumVersion + Provides the minimum version of the module you want to install or uninstall. + + .PARAMETER Force + Forces the installation of modules. If a module of the same name and version already exists on the computer, + this parameter overwrites the existing module with one of the same name that was found by the command. + + .PARAMETER AllowClobber + Allows the installation of modules regardless of if other existing module on the computer have cmdlets + of the same name. + + .PARAMETER SkipPublisherCheck + Allows the installation of modules that have not been catalog signed. +#> +function Get-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $Repository = 'PSGallery', + + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MaximumVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.Boolean] + $Force, + + [Parameter()] + [System.Boolean] + $AllowClobber, + + [Parameter()] + [System.Boolean] + $SkipPublisherCheck + ) + + $returnValue = @{ + Ensure = 'Absent' + Name = $Name + Repository = $Repository + Description = $null + Guid = $null + ModuleBase = $null + ModuleType = $null + Author = $null + InstalledVersion = $null + RequiredVersion = $RequiredVersion + MinimumVersion = $MinimumVersion + MaximumVersion = $MaximumVersion + Force = $Force + AllowClobber = $AllowClobber + SkipPublisherCheck = $SkipPublisherCheck + InstallationPolicy = $null + } + + Write-Verbose -Message ($localizedData.GetTargetResourceMessage -f $Name) + + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') + + # Get the module with the right version and repository properties. + $modules = Get-RightModule @extractedArguments -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + # If the module is found, the count > 0 + if ($modules.Count -gt 0) { + Write-Verbose -Message ($localizedData.ModuleFound -f $Name) + + # Find a module with the latest version and return its properties. + $latestModule = $modules[0] + + foreach ($module in $modules) { + if ($module.Version -gt $latestModule.Version) { + $latestModule = $module + } + } + + # Check if the repository matches. + $repositoryName = Get-ModuleRepositoryName -Module $latestModule -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + if ($repositoryName) { + $installationPolicy = Get-InstallationPolicy -RepositoryName $repositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + } + + if ($installationPolicy) { + $installationPolicyReturnValue = 'Trusted' + } + else { + $installationPolicyReturnValue = 'Untrusted' + } + + $returnValue.Ensure = 'Present' + $returnValue.Repository = $repositoryName + $returnValue.Description = $latestModule.Description + $returnValue.Guid = $latestModule.Guid + $returnValue.ModuleBase = $latestModule.ModuleBase + $returnValue.ModuleType = $latestModule.ModuleType + $returnValue.Author = $latestModule.Author + $returnValue.InstalledVersion = $latestModule.Version + $returnValue.InstallationPolicy = $installationPolicyReturnValue + } + else { + Write-Verbose -Message ($localizedData.ModuleNotFound -f $Name) + } + + return $returnValue +} + +<# + .SYNOPSIS + This DSC resource provides a mechanism to download PowerShell modules from the PowerShell + Gallery and install it on your computer. + + Test-TargetResource validates whether the resource is currently in the desired state. + + .PARAMETER Ensure + Determines whether the module to be installed or uninstalled. + + .PARAMETER Name + Specifies the name of the PowerShell module to be installed or uninstalled. + + .PARAMETER Repository + Specifies the name of the module source repository where the module can be found. + + .PARAMETER InstallationPolicy + Determines whether you trust the source repository where the module resides. + + .PARAMETER RequiredVersion + Provides the version of the module you want to install or uninstall. + + .PARAMETER MaximumVersion + Provides the maximum version of the module you want to install or uninstall. + + .PARAMETER MinimumVersion + Provides the minimum version of the module you want to install or uninstall. + + .PARAMETER Force + Forces the installation of modules. If a module of the same name and version already exists on the computer, + this parameter overwrites the existing module with one of the same name that was found by the command. + + .PARAMETER AllowClobber + Allows the installation of modules regardless of if other existing module on the computer have cmdlets + of the same name. + + .PARAMETER SkipPublisherCheck + Allows the installation of modules that have not been catalog signed. +#> +function Test-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $Repository = 'PSGallery', + + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] + [System.String] + $InstallationPolicy = 'Untrusted', + + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MaximumVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.Boolean] + $Force, + + [Parameter()] + [System.Boolean] + $AllowClobber, + + [Parameter()] + [System.Boolean] + $SkipPublisherCheck + ) + + Write-Verbose -Message ($localizedData.TestTargetResourceMessage -f $Name) + + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') + + $status = Get-TargetResource @extractedArguments + + # The ensure returned from Get-TargetResource is not equal to the desired $Ensure. + if ($status.Ensure -ieq $Ensure) { + Write-Verbose -Message ($localizedData.InDesiredState -f $Name) + return $true + } + else { + Write-Verbose -Message ($localizedData.NotInDesiredState -f $Name) + return $false + } +} + +<# + .SYNOPSIS + This DSC resource provides a mechanism to download PowerShell modules from the PowerShell + Gallery and install it on your computer. + + Set-TargetResource sets the resource to the desired state. "Make it so". + + .PARAMETER Ensure + Determines whether the module to be installed or uninstalled. + + .PARAMETER Name + Specifies the name of the PowerShell module to be installed or uninstalled. + + .PARAMETER Repository + Specifies the name of the module source repository where the module can be found. + + .PARAMETER InstallationPolicy + Determines whether you trust the source repository where the module resides. + + .PARAMETER RequiredVersion + Provides the version of the module you want to install or uninstall. + + .PARAMETER MaximumVersion + Provides the maximum version of the module you want to install or uninstall. + + .PARAMETER MinimumVersion + Provides the minimum version of the module you want to install or uninstall. + + .PARAMETER Force + Forces the installation of modules. If a module of the same name and version already exists on the computer, + this parameter overwrites the existing module with one of the same name that was found by the command. + + .PARAMETER AllowClobber + Allows the installation of modules regardless of if other existing module on the computer have cmdlets + of the same name. + + .PARAMETER SkipPublisherCheck + Allows the installation of modules that have not been catalog signed. +#> +function Set-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-TryStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-CatchClause', '')] + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $Repository = 'PSGallery', + + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] + [System.String] + $InstallationPolicy = 'Untrusted', + + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MaximumVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.Boolean] + $Force, + + [Parameter()] + [System.Boolean] + $AllowClobber, + + [Parameter()] + [System.Boolean] + $SkipPublisherCheck + ) + + # Validate the repository argument + if ($PSBoundParameters.ContainsKey('Repository')) { + Test-ParameterValue -Value $Repository -Type 'PackageSource' -ProviderName 'PowerShellGet' -Verbose + } + + if ($Ensure -ieq 'Present') { + # Version check + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('MinimumVersion', 'MaximumVersion', 'RequiredVersion') + + $null = Test-VersionParameter @extractedArguments + + try { + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') + + Write-Verbose -Message ($localizedData.StartFindModule -f $Name) + + $modules = Find-Module @extractedArguments -ErrorVariable ev + } + catch { + $errorMessage = $script:localizedData.ModuleNotFoundInRepository -f $Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + + $trusted = $null + $moduleFound = $null + + foreach ($m in $modules) { + # Check for the installation policy. + $trusted = Get-InstallationPolicy -RepositoryName $m.Repository -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + # Stop the loop if found a trusted repository. + if ($trusted) { + $moduleFound = $m + break; + } + } + + try { + # The repository is trusted, so we install it. + if ($trusted) { + Write-Verbose -Message ($localizedData.StartInstallModule -f $Name, $moduleFound.Version.toString(), $moduleFound.Repository) + + # Extract the installation options. + $extractedSwitches = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters -ArgumentNames ('Force', 'AllowClobber', 'SkipPublisherCheck') + + $moduleFound | Install-Module @extractedSwitches 2>&1 | out-string | Write-Verbose + } + # The repository is untrusted but user's installation policy is trusted, so we install it with a warning. + elseif ($InstallationPolicy -ieq 'Trusted') { + Write-Warning -Message ($localizedData.InstallationPolicyWarning -f $Name, $modules[0].Repository, $InstallationPolicy) + + # Extract installation options (Force implied by InstallationPolicy). + $extractedSwitches = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters -ArgumentNames ('AllowClobber', 'SkipPublisherCheck') + + # If all the repositories are untrusted, we choose the first one. + $modules[0] | Install-Module @extractedSwitches -Force 2>&1 | out-string | Write-Verbose + } + # Both user and repository is untrusted + else { + $errorMessage = $script:localizedData.InstallationPolicyFailed -f $InstallationPolicy, 'Untrusted' + New-InvalidOperationException -Message $errorMessage + } + + Write-Verbose -Message ($localizedData.InstalledSuccess -f $Name) + } + catch { + $errorMessage = $script:localizedData.FailToInstall -f $Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } + # Ensure=Absent + else { + + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('Name', 'Repository', 'MinimumVersion', 'MaximumVersion', 'RequiredVersion') + + # Get the module with the right version and repository properties. + $modules = Get-RightModule @extractedArguments + + if (-not $modules) { + $errorMessage = $script:localizedData.ModuleWithRightPropertyNotFound -f $Name + New-InvalidOperationException -Message $errorMessage + } + + foreach ($module in $modules) { + # Get the path where the module is installed. + $path = $module.ModuleBase + + Write-Verbose -Message ($localizedData.StartUnInstallModule -f $Name) + + try { + <# + There is no Uninstall-Module cmdlet for Windows PowerShell 4.0, + so we will remove the ModuleBase folder as an uninstall operation. + #> + Microsoft.PowerShell.Management\Remove-Item -Path $path -Force -Recurse + + Write-Verbose -Message ($localizedData.UnInstalledSuccess -f $module.Name) + } + catch { + $errorMessage = $script:localizedData.FailToUninstall -f $module.Name + New-InvalidOperationException -Message $errorMessage -ErrorRecord $_ + } + } # foreach + } # Ensure=Absent +} + +<# + .SYNOPSIS + This is a helper function. It returns the modules that meet the specified versions and the repository requirements. + + .PARAMETER Name + Specifies the name of the PowerShell module. + + .PARAMETER RequiredVersion + Provides the version of the module you want to install or uninstall. + + .PARAMETER MaximumVersion + Provides the maximum version of the module you want to install or uninstall. + + .PARAMETER MinimumVersion + Provides the minimum version of the module you want to install or uninstall. + + .PARAMETER Repository + Specifies the name of the module source repository where the module can be found. +#> +function Get-RightModule { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-ForEachStatement', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Name, + + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.String] + $MaximumVersion, + + [Parameter()] + [System.String] + $Repository + ) + + Write-Verbose -Message ($localizedData.StartGetModule -f $($Name)) + + $modules = Microsoft.PowerShell.Core\Get-Module -Name $Name -ListAvailable -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + if (-not $modules) { + return $null + } + + <# + As Get-Module does not take RequiredVersion, MinimumVersion, MaximumVersion, or Repository, + below we need to check whether the modules are containing the right version and repository + location. + #> + + $extractedArguments = New-SplatParameterHashTable -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames ('MaximumVersion', 'MinimumVersion', 'RequiredVersion') + $returnVal = @() + + foreach ($m in $modules) { + $versionMatch = $false + $installedVersion = $m.Version + + # Case 1 - a user provides none of RequiredVersion, MinimumVersion, MaximumVersion + if ($extractedArguments.Count -eq 0) { + $versionMatch = $true + } + + # Case 2 - a user provides RequiredVersion + elseif ($extractedArguments.ContainsKey('RequiredVersion')) { + # Check if it matches with the installed version + $versionMatch = ($installedVersion -eq [System.Version] $RequiredVersion) + } + else { + + # Case 3 - a user provides MinimumVersion + if ($extractedArguments.ContainsKey('MinimumVersion')) { + $versionMatch = ($installedVersion -ge [System.Version] $extractedArguments['MinimumVersion']) + } + + # Case 4 - a user provides MaximumVersion + if ($extractedArguments.ContainsKey('MaximumVersion')) { + $isLessThanMax = ($installedVersion -le [System.Version] $extractedArguments['MaximumVersion']) + + if ($extractedArguments.ContainsKey('MinimumVersion')) { + $versionMatch = $versionMatch -and $isLessThanMax + } + else { + $versionMatch = $isLessThanMax + } + } + + # Case 5 - Both MinimumVersion and MaximumVersion are provided. It's covered by the above. + # Do not return $false yet to allow the foreach to continue + if (-not $versionMatch) { + Write-Verbose -Message ($localizedData.VersionMismatch -f $Name, $installedVersion) + $versionMatch = $false + } + } + + # Case 6 - Version matches but need to check if the module is from the right repository. + if ($versionMatch) { + # A user does not provide Repository, we are good + if (-not $PSBoundParameters.ContainsKey('Repository')) { + Write-Verbose -Message ($localizedData.ModuleFound -f "$Name $installedVersion") + $returnVal += $m + } + else { + # Check if the Repository matches + $sourceName = Get-ModuleRepositoryName -Module $m + + if ($Repository -ieq $sourceName) { + Write-Verbose -Message ($localizedData.ModuleFound -f "$Name $installedVersion") + $returnVal += $m + } + else { + Write-Verbose -Message ($localizedData.RepositoryMismatch -f $($Name), $($sourceName)) + } + } + } + } # foreach + + return $returnVal +} + +<# + .SYNOPSIS + This is a helper function that returns the module's repository name. + + .PARAMETER Module + Specifies the name of the PowerShell module. +#> +function Get-ModuleRepositoryName { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.Object] + $Module + ) + + <# + RepositorySourceLocation property is supported in PS V5 only. To work with the earlier + PowerShell version, we need to do a different way. PSGetModuleInfo.xml exists for any + PowerShell modules downloaded through PSModule provider. + #> + $psGetModuleInfoFileName = 'PSGetModuleInfo.xml' + $psGetModuleInfoPath = Microsoft.PowerShell.Management\Join-Path -Path $Module.ModuleBase -ChildPath $psGetModuleInfoFileName + + Write-Verbose -Message ($localizedData.FoundModulePath -f $psGetModuleInfoPath) + + if (Microsoft.PowerShell.Management\Test-path -Path $psGetModuleInfoPath) { + $psGetModuleInfo = Microsoft.PowerShell.Utility\Import-Clixml -Path $psGetModuleInfoPath + + return $psGetModuleInfo.Repository + } +} + +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCig4H1kVMWxA7U +# uX1JaRBJmDsHSZHmuBUTkDBwWHafCqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg2t+ikpO2 +# 0WdDEf/JRKsEEOWFz2O6CEnKmWwPbrvPFuIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQBaj/JIwAbud/IMJN3SHVwenJwEjzrmM3cPznYlvkyE +# rnB6RiDYnohm+72wkCmVWobuualV9Xw8OGA9w7MvJjZuEnFP9TwDjlWinR4FpdVl +# YX49u4g50iMR2isaKTxMA2nA2LbyFpTLMqi6ynPLnv251xTdumj/XRLZjHdpr4Pt +# p6HQz45/jMWRtlh8x3piJPrgpINuJtmO91tnMQnykF++QgteDqKSboXOu2Wg0oCn +# Ao0eOifIujyUp2ez9Cqx2hzkMOqNa5jZD4JetiDTWBTkN8XHRbsfCVwyEQzxrpXn +# wAFT7jMzuI8o8xdCuCvuDokIzcnm+rKZJIy6Rwp6Cn11oYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIL/3GmTbcOXlRJe8Pu+1/QVBo0Mk83o/PGK7iApB +# aAutAgZfYQoJ7SUYEzIwMjAwOTIyMjIxOTUxLjg4NlowBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjo4OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABLCKvRZd1+RvuAAAA +# AAEsMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTUwM1oXDTIxMDMxNzAxMTUwM1owgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4OTdB +# LUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPK1zgSSq+MxAYo3qpCt +# QDxSMPPJy6mm/wfEJNjNUnYtLFBwl1BUS5trEk/t41ldxITKehs+ABxYqo4Qxsg3 +# Gy1ugKiwHAnYiiekfC+ZhptNFgtnDZIn45zC0AlVr/6UfLtsLcHCh1XElLUHfEC0 +# nBuQcM/SpYo9e3l1qY5NdMgDGxCsmCKdiZfYXIu+U0UYIBhdzmSHnB3fxZOBVcr5 +# htFHEBBNt/rFJlm/A4yb8oBsp+Uf0p5QwmO/bCcdqB15JpylOhZmWs0sUfJKlK9E +# rAhBwGki2eIRFKsQBdkXS9PWpF1w2gIJRvSkDEaCf+lbGTPdSzHSbfREWOF9wY3i +# Yj8CAwEAAaOCARswggEXMB0GA1UdDgQWBBRRahZSGfrCQhCyIyGH9DkiaW7L0zAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBPFxHIwi4vAH49w9Svmz6K3tM55RlW +# 5pPeULXdut2Rqy6Ys0+VpZsbuaEoxs6Z1C3hMbkiqZFxxyltxJpuHTyGTg61zfNI +# F5n6RsYF3s7IElDXNfZznF1/2iWc6uRPZK8rxxUJ/7emYXZCYwuUY0XjsCpP9pbR +# RKeJi6r5arSyI+NfKxvgoM21JNt1BcdlXuAecdd/k8UjxCscffanoK2n6LFw1PcZ +# lEO7NId7o+soM2C0QY5BYdghpn7uqopB6ixyFIIkDXFub+1E7GmAEwfU6VwEHL7y +# 9rNE8bd+JrQs+yAtkkHy9FmXg/PsGq1daVzX1So7CJ6nyphpuHSN3VfTMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo4 +# OTdBLUUzNTYtMTcwMTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUADE5OKSMoNx/mYxYWap1RTOohbJ2ggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMUwsMwIhgPMjAyMDA5MjIyMjM2NTFaGA8yMDIwMDkyMzIyMzY1MVowdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA4xTCwwIBADAKAgEAAgIlfwIB/zAHAgEAAgIRzTAK +# AgUA4xYUQwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEmZCk5PsMA6oG40 +# YulPUpxDtQA+JAuXyNrLI4wJcdmv34Bzmj9gk3gv6ZxQZxlvZjA1O1P3lcKGIYco +# 9UkRdAeP6Zzfim6U3UoiUz1AHKuSm6iLux7Z/hNtgEs2ZpYXxOK5JZ/9Pa2I5mg0 +# VxsGdXBxl6TOZqbB6aGNG1ZKytRWMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEsIq9Fl3X5G+4AAAAAASwwDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQgioHn9SQtTXybUUTzgPmt61UPBGskDV9AErEC62Y8+H4wgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCBbn/0uFFh42hTM5XOoKdXevBaiSxmYK9Ilcn9n +# u5ZH4TCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# LCKvRZd1+RvuAAAAAAEsMCIEIJmkMFyuUW5yiXAemMCWHyk8T1EMMlB1cEiVD4/E +# +vhqMA0GCSqGSIb3DQEBCwUABIIBAOyFm0zpO+VogyMfTuCFeJjMyyDnAJRy/KhW +# jtq11pPg3e11HU0LuNa+ZpCrJXbSWlF+tLkU9eQXPpGPzO7ITGhxhnq5wyhfzA8a +# 9TRGlDMUNHp+6WhH6BZ86zUqMpcYx5zN0H9TaJTpC8JJ7HrNTW8zp032ocr6IXVr +# M6mOa0MUZsE366+OXRpCCm0fokT50uvXWbcRdngS9ZT5xmz134T1iS4Fds7ZOC5W +# m0VofKEuaTYbTc5aF0WN/OGMvowHRxlJhG7zhMX8ZuXGvbgzANq/o7tZu0HmGFTN +# rPs5smC3VEWVpNWgOTfrO3bfaWkAdsuBeLVXZUog+M4wmTDFSPs= +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mfl b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mfl index 57273364ab..d7ad67437c 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mfl +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mfl @@ -1,24 +1,24 @@ -#pragma namespace("\\\\.\\root\\default") -instance of __namespace{ name="MS_409";}; -#pragma namespace("\\\\.\\root\\default\\MS_409") - -[AMENDMENT, LOCALE("MS_409")] -class MSFT_PSModule : OMI_BaseResource -{ - [Key,Description("Name of the module\n") : Amended] String Name; - [Description("Whether the module is to be installed or uninstalled.\nPresent {default} \nAbsent \n") : Amended] String Ensure; - [Description("The name of the module source where the module can be found.\n") : Amended] String Repository; - [Description("Whether the package is trusted or untrusted.\nTrusted {default} \nUntrusted \n") : Amended] String InstallationPolicy; - [Description("The required version of the module.\n") : Amended] String RequiredVersion; - [Description("The minimum version of the module.\n") : Amended] String MinimumVersion; - [Description("The maximum version of the module.\n") : Amended] String MaximumVersion; - [Description("Forces the installation of the module.\n" : Amended] Boolean Force; - [Description("Allows installation when existing cmdlets of the same name exist.\n" : Amended] Boolean AllowClobber; - [Description("Allows installation when module is not signed.\n" : Amended] Boolean SkipPublisherCheck; - [Description("The brief description of the module.\n") : Amended] string Description; - [Description("The version of the module that is installed.\n") : Amended] String InstalledVersion; - [Description("The identifier of the module.\n") : Amended] String Guid; - [Description("The base location where the module is installed.\n") : Amended] String ModuleBase; - [Description("The type of the module.\n") : Amended] String ModuleType; - [Description("The author of the module.\n") : Amended] String Author; -}; +#pragma namespace("\\\\.\\root\\default") +instance of __namespace{ name="MS_409";}; +#pragma namespace("\\\\.\\root\\default\\MS_409") + +[AMENDMENT, LOCALE("MS_409")] +class MSFT_PSModule : OMI_BaseResource +{ + [Key,Description("Name of the module\n") : Amended] String Name; + [Description("Whether the module is to be installed or uninstalled.\nPresent {default} \nAbsent \n") : Amended] String Ensure; + [Description("The name of the module source where the module can be found.\n") : Amended] String Repository; + [Description("Whether the package is trusted or untrusted.\nTrusted {default} \nUntrusted \n") : Amended] String InstallationPolicy; + [Description("The required version of the module.\n") : Amended] String RequiredVersion; + [Description("The minimum version of the module.\n") : Amended] String MinimumVersion; + [Description("The maximum version of the module.\n") : Amended] String MaximumVersion; + [Description("Forces the installation of the module.\n" : Amended] Boolean Force; + [Description("Allows installation when existing cmdlets of the same name exist.\n" : Amended] Boolean AllowClobber; + [Description("Allows installation when module is not signed.\n" : Amended] Boolean SkipPublisherCheck; + [Description("The brief description of the module.\n") : Amended] string Description; + [Description("The version of the module that is installed.\n") : Amended] String InstalledVersion; + [Description("The identifier of the module.\n") : Amended] String Guid; + [Description("The base location where the module is installed.\n") : Amended] String ModuleBase; + [Description("The type of the module.\n") : Amended] String ModuleType; + [Description("The author of the module.\n") : Amended] String Author; +}; diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mof b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mof index 5f0b967801..5f957c69a5 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mof +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/MSFT_PSModule.schema.mof @@ -1,214 +1,214 @@ - -[ClassVersion("1.0.0.0"),FriendlyName("PSModule")] -class MSFT_PSModule : OMI_BaseResource -{ - [Key] String Name; - [Write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; - [Write] String Repository; - [Write,ValueMap{"Trusted", "Untrusted"},Values{"Trusted", "Untrusted"}] String InstallationPolicy; - [Write] String RequiredVersion; - [Write] String MaximumVersion; - [Write] String MinimumVersion; - [Write] Boolean Force; - [Write] Boolean AllowClobber; - [Write] Boolean SkipPublisherCheck; - [Read] string Description; - [Read] String InstalledVersion; - [Read] String Guid; - [Read] String ModuleBase; - [Read] String ModuleType; - [Read] String Author; -}; - -/* SIG # Begin signature block */ -/* MIIjjwYJKoZIhvcNAQcCoIIjgDCCI3wCAQExDzANBglghkgBZQMEAgEFADB5Bgor */ -/* BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG */ -/* KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCaI9+NOjQE85sM */ -/* /KcWYxUhOUYZXXI8vIpWzKiXV2ZNwqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX */ -/* chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD */ -/* VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy */ -/* b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p */ -/* bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw */ -/* CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u */ -/* ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy */ -/* b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB */ -/* AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB */ -/* znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH */ -/* sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d */ -/* weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ */ -/* itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV */ -/* Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE */ -/* AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw */ -/* UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 */ -/* ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu */ -/* ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu */ -/* bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w */ -/* Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 */ -/* Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx */ -/* MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy */ -/* S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K */ -/* NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV */ -/* BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr */ -/* qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx */ -/* zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe */ -/* yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g */ -/* yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf */ -/* AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI */ -/* 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 */ -/* GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea */ -/* jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS */ -/* AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK */ -/* V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 */ -/* IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 */ -/* ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla */ -/* MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS */ -/* ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT */ -/* H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB */ -/* AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG */ -/* OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S */ -/* 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz */ -/* y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 */ -/* 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u */ -/* M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 */ -/* X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl */ -/* XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP */ -/* 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB */ -/* l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF */ -/* RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM */ -/* CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ */ -/* BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud */ -/* DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO */ -/* 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 */ -/* LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ -/* Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p */ -/* Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ -/* Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB */ -/* FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw */ -/* cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA */ -/* XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY */ -/* 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj */ -/* 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd */ -/* d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ */ -/* Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf */ -/* wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ */ -/* aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j */ -/* NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B */ -/* xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 */ -/* eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 */ -/* r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I */ -/* RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZDCCFWACAQEwgZUwfjELMAkG */ -/* A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx */ -/* HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z */ -/* b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN */ -/* BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor */ -/* BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgbSsvKN6e */ -/* SGOiJuBXioZwDwxnu8TBuBkBb03bbCVXW+owQgYKKwYBBAGCNwIBDDE0MDKgFIAS */ -/* AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN */ -/* BgkqhkiG9w0BAQEFAASCAQBSFT3/XsOM9n4ZVz3k681dMHXA/TGCr6vmQTOG0Cp6 */ -/* Djy4e61zDfO7lsVtGoDqeKYpSUFXc8FZfmegYDcOXrylItYtw3g9iva1sT0zTvCn */ -/* OOZZKJMqa9ZLfxiS9kTOXR0WWbypJmpfgRgqVPdrg5Heh8vfo3NmWhWXwMqX6iUL */ -/* u5HZcE20KAqgiyOqFasvdF+cMut2RzS0UypvroI9LVboBhmQLdTT4ELi65MtvwdH */ -/* gpJ25Mi3uHvhQ9R2TH/Oh8qCsPJqwiEy+CnvArN4TKJ+q1T23NqMEo5R8zLBhUsa */ -/* FcMjYglszXUbCSUSj07/CAOF5adQ1AZoPNdOOuMeLnJOoYIS7jCCEuoGCisGAQQB */ -/* gjcDAwExghLaMIIS1gYJKoZIhvcNAQcCoIISxzCCEsMCAQMxDzANBglghkgBZQME */ -/* AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB */ -/* MDEwDQYJYIZIAWUDBAIBBQAEILnfg6zi07buNel3T6pFx+y5i6dvEoPLGn0OX8/q */ -/* AfPcAgZfYQkjShsYEzIwMjAwOTIyMjIxOTUxLjA4NlowBIACAfSggdSkgdEwgc4x */ -/* CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt */ -/* b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p */ -/* Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg */ -/* VFNTIEVTTjpGNzdGLUUzNTYtNUJBRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt */ -/* U3RhbXAgU2VydmljZaCCDkEwggT1MIID3aADAgECAhMzAAABKugXlviGp++jAAAA */ -/* AAEqMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo */ -/* aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y */ -/* cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw */ -/* MB4XDTE5MTIxOTAxMTUwMloXDTIxMDMxNzAxMTUwMlowgc4xCzAJBgNVBAYTAlVT */ -/* MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK */ -/* ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy */ -/* YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGNzdG */ -/* LUUzNTYtNUJBRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj */ -/* ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/flYGkhdJtxSsHBu9l */ -/* mXF/UXxPF7L45nEhmtd01KDosWbY8y54BN7+k9DMvzqToP39v8/Z+NtEzKj8Bf5E */ -/* QoG1/pJfpzCJe80HZqyqMo0oQ9EugVY6YNVNa2T1u51d96q1hFmu1dgxt8uD2g7I */ -/* pBQdhS2tpc3j3HEzKvV/vwEr7/BcTuwqUHqrrBgHc971epVR4o5bNKsjikawmMw9 */ -/* D/tyrTciy3F9Gq9pEgk8EqJfOdAabkanuAWTjlmBhZtRiO9W1qFpwnu9G5qVvdNK */ -/* RKxQdtxMC04pWGfnxzDac7+jIql532IEC5QSnvY84szEpxw31QW/LafSiDmAtYWH */ -/* pm8CAwEAAaOCARswggEXMB0GA1UdDgQWBBRw9MUtdCs/rhN2y9EkE6ZI9O8TaTAf */ -/* BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH */ -/* hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU */ -/* aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF */ -/* BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 */ -/* YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG */ -/* AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCKwDT0CnHVo46OWyUbrPIj8QIcf+PT */ -/* jBVYpKg1K2D15Z6xEuvmf+is6N8gj9f1nkFIALvh+iGkx8GgGa/oA9IhXNEFYPNF */ -/* aHwHan/UEw1P6Tjdaqy3cvLC8f8zE1CR1LhXNofq6xfoT9HLGFSg9skPLM1TQ+RA */ -/* QX9MigEm8FFlhhsQ1iGB1399x8d92h9KspqGDnO96Z9Aj7ObDtdU6RoZrsZkiRQN */ -/* nXmnX1I+RuwtLu8MN8XhJLSl5wqqHM3rqaaMvSAISVtKySpzJC5Zh+5kJlqFdSiI */ -/* HW8Q+8R6EWG8ILb9Pf+w/PydyK3ZTkVXUpFA+JhWjcyzphVGw9ffj0YKMIIGcTCC */ -/* BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC */ -/* VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV */ -/* BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv */ -/* b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN */ -/* MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv */ -/* bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 */ -/* aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw */ -/* DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 */ -/* VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw */ -/* RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe */ -/* dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx */ -/* Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G */ -/* kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA */ -/* AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 */ -/* fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC */ -/* AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX */ -/* zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v */ -/* cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI */ -/* KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j */ -/* b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g */ -/* AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 */ -/* d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB */ -/* BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA */ -/* bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh */ -/* IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS */ -/* +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK */ -/* kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon */ -/* /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi */ -/* PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ */ -/* fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII */ -/* YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 */ -/* cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a */ -/* KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ */ -/* cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ */ -/* NR4Iuto229Nfj950iEkSoYICzzCCAjgCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT */ -/* AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD */ -/* VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP */ -/* cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpG */ -/* NzdGLUUzNTYtNUJBRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy */ -/* dmljZaIjCgEBMAcGBSsOAwIaAxUA6rLmrKHyIMP76ePl321xKUJ3YX+ggYMwgYCk */ -/* fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH */ -/* UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD */ -/* Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF */ -/* AOMUwdcwIhgPMjAyMDA5MjIyMjMyNTVaGA8yMDIwMDkyMzIyMzI1NVowdDA6Bgor */ -/* BgEEAYRZCgQBMSwwKjAKAgUA4xTB1wIBADAHAgEAAgIOXDAHAgEAAgIRhjAKAgUA */ -/* 4xYTVwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID */ -/* B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAFJC6fwWgNxSFO+SkoKy */ -/* mrM+JaRXiSnNavdEe/YjzI5HVrpNB8zCRPz/pvX9TbuSnQ2/WN4ZPI2AoBMGQpWk */ -/* aG1jl9uC0QwsFTL8Bz97Tpk7GmS9yzjX0+srsaxIVCGg9geXr7A4IoUp0WVxR8JR */ -/* MNinkAYlZ0lpKG2lFOA0GBPWMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMx */ -/* EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT */ -/* FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt */ -/* U3RhbXAgUENBIDIwMTACEzMAAAEq6BeW+Ian76MAAAAAASowDQYJYIZIAWUDBAIB */ -/* BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx */ -/* IgQgFyTxUrHDtuJBsfu1xDMA0kEsv2/5h+RllgO7I/JdUFowgfoGCyqGSIb3DQEJ */ -/* EAIvMYHqMIHnMIHkMIG9BCBDmDWEWvc6fhs5t4Woo5Q+FMFCcaIgV4yUP4CpuBmL */ -/* mTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw */ -/* DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x */ -/* JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABKugX */ -/* lviGp++jAAAAAAEqMCIEIPYicL/1lyN2zZsc2WZ8NPhhliGEGAUOVDFr1il2rjsl */ -/* MA0GCSqGSIb3DQEBCwUABIIBAAXT1QnIHmDbDzSQJOywe0TeYzVPNHWyEx/vc0nu */ -/* EaUOByznS0km1nwRtIdc9btdG7R7Y5O4sHdlVemnnCRChyie8tQQXYpBsDYZ3x9r */ -/* cZEdyjgqJg5LUf4F9BBLLA1kTgP63a/tDOkF1KqImsHXog+nTECkzC1RxCauEFvn */ -/* pMuCOa8D1dBptSXhUxaDRBYtSXlT76E+7RtoTLLojBl+a/h+MIpSvzg9Tj8rWD5O */ -/* lBq8D1cFSDYk8wGti6d+LbDIokFmh8UB25pmQ6dFqAL3seIOZ0bA3bXOqDo7qZ0x */ -/* Mday0m5Qc9ghm0X1Pgls3bGM9fu08AmUKJXNpcIMg75MNgA= */ -/* SIG # End signature block */ + +[ClassVersion("1.0.0.0"),FriendlyName("PSModule")] +class MSFT_PSModule : OMI_BaseResource +{ + [Key] String Name; + [Write,ValueMap{"Present", "Absent"},Values{"Present", "Absent"}] String Ensure; + [Write] String Repository; + [Write,ValueMap{"Trusted", "Untrusted"},Values{"Trusted", "Untrusted"}] String InstallationPolicy; + [Write] String RequiredVersion; + [Write] String MaximumVersion; + [Write] String MinimumVersion; + [Write] Boolean Force; + [Write] Boolean AllowClobber; + [Write] Boolean SkipPublisherCheck; + [Read] string Description; + [Read] String InstalledVersion; + [Read] String Guid; + [Read] String ModuleBase; + [Read] String ModuleType; + [Read] String Author; +}; + +/* SIG # Begin signature block */ +/* MIIjjwYJKoZIhvcNAQcCoIIjgDCCI3wCAQExDzANBglghkgBZQMEAgEFADB5Bgor */ +/* BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG */ +/* KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCaI9+NOjQE85sM */ +/* /KcWYxUhOUYZXXI8vIpWzKiXV2ZNwqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX */ +/* chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD */ +/* VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy */ +/* b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p */ +/* bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw */ +/* CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u */ +/* ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy */ +/* b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB */ +/* AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB */ +/* znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH */ +/* sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d */ +/* weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ */ +/* itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV */ +/* Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE */ +/* AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw */ +/* UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 */ +/* ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu */ +/* ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu */ +/* bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w */ +/* Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 */ +/* Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx */ +/* MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy */ +/* S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K */ +/* NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV */ +/* BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr */ +/* qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx */ +/* zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe */ +/* yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g */ +/* yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf */ +/* AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI */ +/* 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 */ +/* GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea */ +/* jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS */ +/* AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK */ +/* V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 */ +/* IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 */ +/* ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla */ +/* MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS */ +/* ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT */ +/* H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB */ +/* AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG */ +/* OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S */ +/* 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz */ +/* y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 */ +/* 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u */ +/* M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 */ +/* X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl */ +/* XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP */ +/* 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB */ +/* l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF */ +/* RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM */ +/* CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ */ +/* BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud */ +/* DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO */ +/* 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 */ +/* LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ +/* Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p */ +/* Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ +/* Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB */ +/* FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw */ +/* cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA */ +/* XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY */ +/* 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj */ +/* 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd */ +/* d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ */ +/* Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf */ +/* wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ */ +/* aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j */ +/* NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B */ +/* xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 */ +/* eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 */ +/* r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I */ +/* RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZDCCFWACAQEwgZUwfjELMAkG */ +/* A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx */ +/* HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z */ +/* b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN */ +/* BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor */ +/* BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgbSsvKN6e */ +/* SGOiJuBXioZwDwxnu8TBuBkBb03bbCVXW+owQgYKKwYBBAGCNwIBDDE0MDKgFIAS */ +/* AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN */ +/* BgkqhkiG9w0BAQEFAASCAQBSFT3/XsOM9n4ZVz3k681dMHXA/TGCr6vmQTOG0Cp6 */ +/* Djy4e61zDfO7lsVtGoDqeKYpSUFXc8FZfmegYDcOXrylItYtw3g9iva1sT0zTvCn */ +/* OOZZKJMqa9ZLfxiS9kTOXR0WWbypJmpfgRgqVPdrg5Heh8vfo3NmWhWXwMqX6iUL */ +/* u5HZcE20KAqgiyOqFasvdF+cMut2RzS0UypvroI9LVboBhmQLdTT4ELi65MtvwdH */ +/* gpJ25Mi3uHvhQ9R2TH/Oh8qCsPJqwiEy+CnvArN4TKJ+q1T23NqMEo5R8zLBhUsa */ +/* FcMjYglszXUbCSUSj07/CAOF5adQ1AZoPNdOOuMeLnJOoYIS7jCCEuoGCisGAQQB */ +/* gjcDAwExghLaMIIS1gYJKoZIhvcNAQcCoIISxzCCEsMCAQMxDzANBglghkgBZQME */ +/* AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB */ +/* MDEwDQYJYIZIAWUDBAIBBQAEILnfg6zi07buNel3T6pFx+y5i6dvEoPLGn0OX8/q */ +/* AfPcAgZfYQkjShsYEzIwMjAwOTIyMjIxOTUxLjA4NlowBIACAfSggdSkgdEwgc4x */ +/* CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt */ +/* b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p */ +/* Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg */ +/* VFNTIEVTTjpGNzdGLUUzNTYtNUJBRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt */ +/* U3RhbXAgU2VydmljZaCCDkEwggT1MIID3aADAgECAhMzAAABKugXlviGp++jAAAA */ +/* AAEqMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo */ +/* aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y */ +/* cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw */ +/* MB4XDTE5MTIxOTAxMTUwMloXDTIxMDMxNzAxMTUwMlowgc4xCzAJBgNVBAYTAlVT */ +/* MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK */ +/* ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy */ +/* YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGNzdG */ +/* LUUzNTYtNUJBRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj */ +/* ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ/flYGkhdJtxSsHBu9l */ +/* mXF/UXxPF7L45nEhmtd01KDosWbY8y54BN7+k9DMvzqToP39v8/Z+NtEzKj8Bf5E */ +/* QoG1/pJfpzCJe80HZqyqMo0oQ9EugVY6YNVNa2T1u51d96q1hFmu1dgxt8uD2g7I */ +/* pBQdhS2tpc3j3HEzKvV/vwEr7/BcTuwqUHqrrBgHc971epVR4o5bNKsjikawmMw9 */ +/* D/tyrTciy3F9Gq9pEgk8EqJfOdAabkanuAWTjlmBhZtRiO9W1qFpwnu9G5qVvdNK */ +/* RKxQdtxMC04pWGfnxzDac7+jIql532IEC5QSnvY84szEpxw31QW/LafSiDmAtYWH */ +/* pm8CAwEAAaOCARswggEXMB0GA1UdDgQWBBRw9MUtdCs/rhN2y9EkE6ZI9O8TaTAf */ +/* BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH */ +/* hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU */ +/* aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF */ +/* BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 */ +/* YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG */ +/* AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCKwDT0CnHVo46OWyUbrPIj8QIcf+PT */ +/* jBVYpKg1K2D15Z6xEuvmf+is6N8gj9f1nkFIALvh+iGkx8GgGa/oA9IhXNEFYPNF */ +/* aHwHan/UEw1P6Tjdaqy3cvLC8f8zE1CR1LhXNofq6xfoT9HLGFSg9skPLM1TQ+RA */ +/* QX9MigEm8FFlhhsQ1iGB1399x8d92h9KspqGDnO96Z9Aj7ObDtdU6RoZrsZkiRQN */ +/* nXmnX1I+RuwtLu8MN8XhJLSl5wqqHM3rqaaMvSAISVtKySpzJC5Zh+5kJlqFdSiI */ +/* HW8Q+8R6EWG8ILb9Pf+w/PydyK3ZTkVXUpFA+JhWjcyzphVGw9ffj0YKMIIGcTCC */ +/* BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC */ +/* VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV */ +/* BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv */ +/* b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN */ +/* MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv */ +/* bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 */ +/* aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw */ +/* DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 */ +/* VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw */ +/* RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe */ +/* dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx */ +/* Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G */ +/* kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA */ +/* AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 */ +/* fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC */ +/* AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX */ +/* zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v */ +/* cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI */ +/* KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j */ +/* b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g */ +/* AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 */ +/* d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB */ +/* BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA */ +/* bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh */ +/* IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS */ +/* +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK */ +/* kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon */ +/* /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi */ +/* PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ */ +/* fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII */ +/* YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 */ +/* cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a */ +/* KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ */ +/* cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ */ +/* NR4Iuto229Nfj950iEkSoYICzzCCAjgCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT */ +/* AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD */ +/* VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP */ +/* cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpG */ +/* NzdGLUUzNTYtNUJBRTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy */ +/* dmljZaIjCgEBMAcGBSsOAwIaAxUA6rLmrKHyIMP76ePl321xKUJ3YX+ggYMwgYCk */ +/* fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH */ +/* UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD */ +/* Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF */ +/* AOMUwdcwIhgPMjAyMDA5MjIyMjMyNTVaGA8yMDIwMDkyMzIyMzI1NVowdDA6Bgor */ +/* BgEEAYRZCgQBMSwwKjAKAgUA4xTB1wIBADAHAgEAAgIOXDAHAgEAAgIRhjAKAgUA */ +/* 4xYTVwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID */ +/* B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAFJC6fwWgNxSFO+SkoKy */ +/* mrM+JaRXiSnNavdEe/YjzI5HVrpNB8zCRPz/pvX9TbuSnQ2/WN4ZPI2AoBMGQpWk */ +/* aG1jl9uC0QwsFTL8Bz97Tpk7GmS9yzjX0+srsaxIVCGg9geXr7A4IoUp0WVxR8JR */ +/* MNinkAYlZ0lpKG2lFOA0GBPWMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMx */ +/* EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT */ +/* FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt */ +/* U3RhbXAgUENBIDIwMTACEzMAAAEq6BeW+Ian76MAAAAAASowDQYJYIZIAWUDBAIB */ +/* BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx */ +/* IgQgFyTxUrHDtuJBsfu1xDMA0kEsv2/5h+RllgO7I/JdUFowgfoGCyqGSIb3DQEJ */ +/* EAIvMYHqMIHnMIHkMIG9BCBDmDWEWvc6fhs5t4Woo5Q+FMFCcaIgV4yUP4CpuBmL */ +/* mTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw */ +/* DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x */ +/* JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABKugX */ +/* lviGp++jAAAAAAEqMCIEIPYicL/1lyN2zZsc2WZ8NPhhliGEGAUOVDFr1il2rjsl */ +/* MA0GCSqGSIb3DQEBCwUABIIBAAXT1QnIHmDbDzSQJOywe0TeYzVPNHWyEx/vc0nu */ +/* EaUOByznS0km1nwRtIdc9btdG7R7Y5O4sHdlVemnnCRChyie8tQQXYpBsDYZ3x9r */ +/* cZEdyjgqJg5LUf4F9BBLLA1kTgP63a/tDOkF1KqImsHXog+nTECkzC1RxCauEFvn */ +/* pMuCOa8D1dBptSXhUxaDRBYtSXlT76E+7RtoTLLojBl+a/h+MIpSvzg9Tj8rWD5O */ +/* lBq8D1cFSDYk8wGti6d+LbDIokFmh8UB25pmQ6dFqAL3seIOZ0bA3bXOqDo7qZ0x */ +/* Mday0m5Qc9ghm0X1Pgls3bGM9fu08AmUKJXNpcIMg75MNgA= */ +/* SIG # End signature block */ diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 index e87f5cf831..8903182706 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSModule/en-US/MSFT_PSModule.strings.psd1 @@ -1,228 +1,228 @@ -# -# Copyright (c) Microsoft Corporation. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# culture = "en-US" -ConvertFrom-StringData -StringData @' - FailToUninstall = Failed to uninstall the module '{0}'. - FailToInstall = Failed to install the module '{0}'. - InDesiredState = Resource '{0}' is in the desired state. - NotInDesiredState = Resource '{0}' is not in the desired state. - ModuleFound = Module '{0}' is found on the node. - ModuleNotFound = Module '{0}' is not found on the node. - ModuleWithRightPropertyNotFound = Module '{0}' with the right version or other properties not found in the node. - ModuleNotFoundInRepository = Module '{0}' with the right version or other properties not found in the repository. - StartGetModule = Begin invoking Get-Module '{0}'. - StartFindModule = Begin invoking Find-Module '{0}'. - StartInstallModule = Begin invoking Install-Module '{0}' version '{1}' from '{2}' repository. - StartUnInstallModule = Begin invoking Remove-Item to remove the module '{0}' from the file system. - InstalledSuccess = Successfully installed the module '{0}' - UnInstalledSuccess = Successfully uninstalled the module '{0}' - VersionMismatch = The installed module '{0}' has the version: '{1}' - RepositoryMismatch = The installed module '{0}' is from the '{1}' repository. - FoundModulePath = Found the module path: '{0}'. - InstallationPolicyWarning = The module '{0}' was installed from the untrusted repository' {1}'. The InstallationPolicy is set to '{2}' to override the repository installation policy. If you trust the repository, set the repository installation policy to 'Trusted', that will also remove this warning. - InstallationPolicyFailed = The current installation policy do not allow installation from this repository. Your current installation policy is '{0}' and the repository installation policy is '{1}'. If you trust the repository, either change the repository installation policy, or set the parameter InstallationPolicy to 'Trusted' to override the repository installation policy. - GetTargetResourceMessage = Getting the current state of the module '{0}'. - TestTargetResourceMessage = Determining if the module '{0}' is in the desired state. -'@ - -# SIG # Begin signature block -# MIIjjwYJKoZIhvcNAQcCoIIjgDCCI3wCAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD3aU/LXFNAmh3o -# ImcuJKT1RWLilph3AMcVu5/5cons4KCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZDCCFWACAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQggnuHAoEc -# 77NUAlz1iYFplyTNHrObqDxEEH6I0sx83rwwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQDHiQmHNyxnMkjG/Ta0iocARMREpmraxXw0YVRwVDWu -# 7Fwk6AHjvaq4lG4wBThP2nMw1pepa8wp9vvq+p0v6/HneS0RTMpnKOnioidAvbtx -# zYcqGW4OBzW7TP2pPJc1pLP5CcpndQKxuWPuop//9GT3qH/yw5MqKfV+NEE/13Dd -# zGEL7MVwjq8YWr7hoc77YdIw7A/xmY7gCTn15q0pWMmoBMOllKdBWTRB8QY186SL -# B4SCrN4XLUQlxTx81UkQG4mYCkCEvyRk4ljM4mcGlL391r0pl+MoFgp3EnePeieZ -# ihN3IHuyg1SkqmUKcFv7b4C5XxfSZxyNE49bx2rfSADAoYIS7jCCEuoGCisGAQQB -# gjcDAwExghLaMIIS1gYJKoZIhvcNAQcCoIISxzCCEsMCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIB/oVtJoO9r6dLRNmcUzxfDW4EtVcph4CRbFLdpd -# e6cDAgZfYPebj+4YEzIwMjAwOTIyMjIxOTUyLjMzMVowBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkEwggT1MIID3aADAgECAhMzAAABJt+6SyK5goIHAAAA -# AAEmMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2MEJD -# LUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4wvoacTvMNlXQTtfF/ -# Cx5Ol3X0fcjUNMvjLgTmO5+WHYJFbp725P3+qvFKDRQHWEI1Sz0gB24urVDIjXjB -# h5NVNJVMQJI2tltv7M4/4IbhZJb3xzQW7LolEoZYUZanBTUuyly9osCg4o5joViT -# 2GtmyxK+Fv5kC20l2opeaeptd/E7ceDAFRM87hiNCsK/KHyC+8+swnlg4gTOey6z -# QqhzgNsG6HrjLBuDtDs9izAMwS2yWT0T52QA9h3Q+B1C9ps2fMKMe+DHpG+0c61D -# 94Yh6cV2XHib4SBCnwIFZAeZE2UJ4qPANSYozI8PH+E5rCT3SVqYvHou97HsXvP2 -# I3MCAwEAAaOCARswggEXMB0GA1UdDgQWBBRJq6wfF7B+mEKN0VimX8ajNA5hQTAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBAlvudaOlv9Cfzv56bnX41czF6tLtH -# LB46l6XUch+qNN45ZmOTFwLot3JjwSrn4oycQ9qTET1TFDYd1QND0LiXmKz9OqBX -# ai6S8XdyCQEZvfL82jIAs9pwsAQ6XvV9jNybPStRgF/sOAM/Deyfmej9Tg9FcRwX -# ank2qgzdZZNb8GoEze7f1orcTF0Q89IUXWIlmwEwQFYF1wjn87N4ZxL9Z/xA2m/R -# 1zizFylWP/mpamCnVfZZLkafFLNUNVmcvc+9gM7vceJs37d3ydabk4wR6ObR34sW -# aLppmyPlsI1Qq5Lu6bJCWoXzYuWpkoK6oEep1gML6SRC3HKVS3UscZhtMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYICzzCCAjgCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2 -# MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUACmcyOWmZxErpq06B8dy6oMZ6//yggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMUsDowIhgPMjAyMDA5MjIyMTE3NDZaGA8yMDIwMDkyMzIxMTc0NlowdDA6Bgor -# BgEEAYRZCgQBMSwwKjAKAgUA4xSwOgIBADAHAgEAAgII+zAHAgEAAgIRDTAKAgUA -# 4xYBugIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID -# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAGFtTfLVXgP5nTlAW4+t -# qfeH51GOeG4NIbJTmRivKyHJM/AjTJCfaazyngryVGsVed/19YBgxoB07IS1Jlmm -# KNq9XofCcAXBUcoBsPLnGHr8vBVOpAPDLDYgr7ME8Qgofa5CjOmCQMYZyjsW2q5j -# 2OkaGPVN1YtLAZq82zgDGI+QMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMx -# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT -# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgUENBIDIwMTACEzMAAAEm37pLIrmCggcAAAAAASYwDQYJYIZIAWUDBAIB -# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx -# IgQg4R9WsNqSRA41TdlwJ+Gl9iyX+HsrWWraUjisH0d+K7IwgfoGCyqGSIb3DQEJ -# EAIvMYHqMIHnMIHkMIG9BCA2/c/vnr1ecAzvapOWZ2xGfAkzrkfpGcrvMW07CQl1 -# DzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw -# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x -# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJt+6 -# SyK5goIHAAAAAAEmMCIEILioZM02FaPhX5O1/7sR3BeH9w6NUS1x/c+Q88996cSH -# MA0GCSqGSIb3DQEBCwUABIIBAJe/stVSoax/AYbLjdRsdlN60oiL97/W3FU+joo2 -# WRwoqLTFscKwlFxQSK5dRXEnn0gU1IQCMsWTeiRrLPV8gtaiUxtczpdK4QvuXJnd -# 8sdla5oQNu3vuEVSjnFLl8w6pFBai4ztvz76ZGEJlUfiXF8aCMDHoiA9kqhu3iSm -# Ky46sAfmEYRDjfIH14LzEbL+KRr3bM5G74fv125+4DyCbRcphNAHQ++gQ1WJkXF4 -# 8BUAbKTWLjIDQAgq15jE0JSeBJ4RxuTVt+Ha8NAnvmDpC1fUWvldymt4TZKd3EE9 -# iujPnESHGHysaE+G1MZfl/BrpYKq9o3khCHwUBdWzUtSDHw= -# SIG # End signature block +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# culture = "en-US" +ConvertFrom-StringData -StringData @' + FailToUninstall = Failed to uninstall the module '{0}'. + FailToInstall = Failed to install the module '{0}'. + InDesiredState = Resource '{0}' is in the desired state. + NotInDesiredState = Resource '{0}' is not in the desired state. + ModuleFound = Module '{0}' is found on the node. + ModuleNotFound = Module '{0}' is not found on the node. + ModuleWithRightPropertyNotFound = Module '{0}' with the right version or other properties not found in the node. + ModuleNotFoundInRepository = Module '{0}' with the right version or other properties not found in the repository. + StartGetModule = Begin invoking Get-Module '{0}'. + StartFindModule = Begin invoking Find-Module '{0}'. + StartInstallModule = Begin invoking Install-Module '{0}' version '{1}' from '{2}' repository. + StartUnInstallModule = Begin invoking Remove-Item to remove the module '{0}' from the file system. + InstalledSuccess = Successfully installed the module '{0}' + UnInstalledSuccess = Successfully uninstalled the module '{0}' + VersionMismatch = The installed module '{0}' has the version: '{1}' + RepositoryMismatch = The installed module '{0}' is from the '{1}' repository. + FoundModulePath = Found the module path: '{0}'. + InstallationPolicyWarning = The module '{0}' was installed from the untrusted repository' {1}'. The InstallationPolicy is set to '{2}' to override the repository installation policy. If you trust the repository, set the repository installation policy to 'Trusted', that will also remove this warning. + InstallationPolicyFailed = The current installation policy do not allow installation from this repository. Your current installation policy is '{0}' and the repository installation policy is '{1}'. If you trust the repository, either change the repository installation policy, or set the parameter InstallationPolicy to 'Trusted' to override the repository installation policy. + GetTargetResourceMessage = Getting the current state of the module '{0}'. + TestTargetResourceMessage = Determining if the module '{0}' is in the desired state. +'@ + +# SIG # Begin signature block +# MIIjjwYJKoZIhvcNAQcCoIIjgDCCI3wCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCD3aU/LXFNAmh3o +# ImcuJKT1RWLilph3AMcVu5/5cons4KCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZDCCFWACAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQggnuHAoEc +# 77NUAlz1iYFplyTNHrObqDxEEH6I0sx83rwwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQDHiQmHNyxnMkjG/Ta0iocARMREpmraxXw0YVRwVDWu +# 7Fwk6AHjvaq4lG4wBThP2nMw1pepa8wp9vvq+p0v6/HneS0RTMpnKOnioidAvbtx +# zYcqGW4OBzW7TP2pPJc1pLP5CcpndQKxuWPuop//9GT3qH/yw5MqKfV+NEE/13Dd +# zGEL7MVwjq8YWr7hoc77YdIw7A/xmY7gCTn15q0pWMmoBMOllKdBWTRB8QY186SL +# B4SCrN4XLUQlxTx81UkQG4mYCkCEvyRk4ljM4mcGlL391r0pl+MoFgp3EnePeieZ +# ihN3IHuyg1SkqmUKcFv7b4C5XxfSZxyNE49bx2rfSADAoYIS7jCCEuoGCisGAQQB +# gjcDAwExghLaMIIS1gYJKoZIhvcNAQcCoIISxzCCEsMCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIB/oVtJoO9r6dLRNmcUzxfDW4EtVcph4CRbFLdpd +# e6cDAgZfYPebj+4YEzIwMjAwOTIyMjIxOTUyLjMzMVowBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkEwggT1MIID3aADAgECAhMzAAABJt+6SyK5goIHAAAA +# AAEmMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2MEJD +# LUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4wvoacTvMNlXQTtfF/ +# Cx5Ol3X0fcjUNMvjLgTmO5+WHYJFbp725P3+qvFKDRQHWEI1Sz0gB24urVDIjXjB +# h5NVNJVMQJI2tltv7M4/4IbhZJb3xzQW7LolEoZYUZanBTUuyly9osCg4o5joViT +# 2GtmyxK+Fv5kC20l2opeaeptd/E7ceDAFRM87hiNCsK/KHyC+8+swnlg4gTOey6z +# QqhzgNsG6HrjLBuDtDs9izAMwS2yWT0T52QA9h3Q+B1C9ps2fMKMe+DHpG+0c61D +# 94Yh6cV2XHib4SBCnwIFZAeZE2UJ4qPANSYozI8PH+E5rCT3SVqYvHou97HsXvP2 +# I3MCAwEAAaOCARswggEXMB0GA1UdDgQWBBRJq6wfF7B+mEKN0VimX8ajNA5hQTAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBAlvudaOlv9Cfzv56bnX41czF6tLtH +# LB46l6XUch+qNN45ZmOTFwLot3JjwSrn4oycQ9qTET1TFDYd1QND0LiXmKz9OqBX +# ai6S8XdyCQEZvfL82jIAs9pwsAQ6XvV9jNybPStRgF/sOAM/Deyfmej9Tg9FcRwX +# ank2qgzdZZNb8GoEze7f1orcTF0Q89IUXWIlmwEwQFYF1wjn87N4ZxL9Z/xA2m/R +# 1zizFylWP/mpamCnVfZZLkafFLNUNVmcvc+9gM7vceJs37d3ydabk4wR6ObR34sW +# aLppmyPlsI1Qq5Lu6bJCWoXzYuWpkoK6oEep1gML6SRC3HKVS3UscZhtMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYICzzCCAjgCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2 +# MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUACmcyOWmZxErpq06B8dy6oMZ6//yggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMUsDowIhgPMjAyMDA5MjIyMTE3NDZaGA8yMDIwMDkyMzIxMTc0NlowdDA6Bgor +# BgEEAYRZCgQBMSwwKjAKAgUA4xSwOgIBADAHAgEAAgII+zAHAgEAAgIRDTAKAgUA +# 4xYBugIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID +# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAGFtTfLVXgP5nTlAW4+t +# qfeH51GOeG4NIbJTmRivKyHJM/AjTJCfaazyngryVGsVed/19YBgxoB07IS1Jlmm +# KNq9XofCcAXBUcoBsPLnGHr8vBVOpAPDLDYgr7ME8Qgofa5CjOmCQMYZyjsW2q5j +# 2OkaGPVN1YtLAZq82zgDGI+QMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgUENBIDIwMTACEzMAAAEm37pLIrmCggcAAAAAASYwDQYJYIZIAWUDBAIB +# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx +# IgQg4R9WsNqSRA41TdlwJ+Gl9iyX+HsrWWraUjisH0d+K7IwgfoGCyqGSIb3DQEJ +# EAIvMYHqMIHnMIHkMIG9BCA2/c/vnr1ecAzvapOWZ2xGfAkzrkfpGcrvMW07CQl1 +# DzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJt+6 +# SyK5goIHAAAAAAEmMCIEILioZM02FaPhX5O1/7sR3BeH9w6NUS1x/c+Q88996cSH +# MA0GCSqGSIb3DQEBCwUABIIBAJe/stVSoax/AYbLjdRsdlN60oiL97/W3FU+joo2 +# WRwoqLTFscKwlFxQSK5dRXEnn0gU1IQCMsWTeiRrLPV8gtaiUxtczpdK4QvuXJnd +# 8sdla5oQNu3vuEVSjnFLl8w6pFBai4ztvz76ZGEJlUfiXF8aCMDHoiA9kqhu3iSm +# Ky46sAfmEYRDjfIH14LzEbL+KRr3bM5G74fv125+4DyCbRcphNAHQ++gQ1WJkXF4 +# 8BUAbKTWLjIDQAgq15jE0JSeBJ4RxuTVt+Ha8NAnvmDpC1fUWvldymt4TZKd3EE9 +# iujPnESHGHysaE+G1MZfl/BrpYKq9o3khCHwUBdWzUtSDHw= +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.psm1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.psm1 index 3f61e9cab0..ac9b7559ab 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.psm1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.psm1 @@ -1,501 +1,501 @@ -$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent - -# Import localization helper functions. -$helperName = 'PowerShellGet.LocalizationHelper' -$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" -Import-Module -Name $dscResourcesFolderFilePath - -$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_PSRepository' -ScriptRoot $PSScriptRoot - -# Import resource helper functions. -$helperName = 'PowerShellGet.ResourceHelper' -$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" -Import-Module -Name $dscResourcesFolderFilePath - -<# - .SYNOPSIS - Returns the current state of the repository. - - .PARAMETER Name - Specifies the name of the repository to manage. -#> -function Get-TargetResource { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.String] - $Name - ) - - $returnValue = @{ - Ensure = 'Absent' - Name = $Name - SourceLocation = $null - ScriptSourceLocation = $null - PublishLocation = $null - ScriptPublishLocation = $null - InstallationPolicy = $null - PackageManagementProvider = $null - Trusted = $false - Registered = $false - } - - Write-Verbose -Message ($localizedData.GetTargetResourceMessage -f $Name) - - $repository = Get-PSRepository -Name $Name -ErrorAction 'SilentlyContinue' - - if ($repository) { - $returnValue.Ensure = 'Present' - $returnValue.SourceLocation = $repository.SourceLocation - $returnValue.ScriptSourceLocation = $repository.ScriptSourceLocation - $returnValue.PublishLocation = $repository.PublishLocation - $returnValue.ScriptPublishLocation = $repository.ScriptPublishLocation - $returnValue.InstallationPolicy = $repository.InstallationPolicy - $returnValue.PackageManagementProvider = $repository.PackageManagementProvider - $returnValue.Trusted = $repository.Trusted - $returnValue.Registered = $repository.Registered - } - else { - Write-Verbose -Message ($localizedData.RepositoryNotFound -f $Name) - } - - return $returnValue -} - -<# - .SYNOPSIS - Determines if the repository is in the desired state. - - .PARAMETER Ensure - If the repository should be present or absent on the server - being configured. Default values is 'Present'. - - .PARAMETER Name - Specifies the name of the repository to manage. - - .PARAMETER SourceLocation - Specifies the URI for discovering and installing modules from - this repository. A URI can be a NuGet server feed, HTTP, HTTPS, - FTP or file location. - - .PARAMETER ScriptSourceLocation - Specifies the URI for the script source location. - - .PARAMETER PublishLocation - Specifies the URI of the publish location. For example, for - NuGet-based repositories, the publish location is similar - to http://someNuGetUrl.com/api/v2/Packages. - - .PARAMETER ScriptPublishLocation - Specifies the URI for the script publish location. - - .PARAMETER InstallationPolicy - Specifies the installation policy. Valid values are 'Trusted' - or 'Untrusted'. The default value is 'Untrusted'. - - .PARAMETER PackageManagementProvider - Specifies a OneGet package provider. Default value is 'NuGet'. -#> -function Test-TargetResource { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [CmdletBinding()] - [OutputType([System.Boolean])] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $SourceLocation, - - [Parameter()] - [System.String] - $ScriptSourceLocation, - - [Parameter()] - [System.String] - $PublishLocation, - - [Parameter()] - [System.String] - $ScriptPublishLocation, - - [Parameter()] - [ValidateSet('Trusted', 'Untrusted')] - [System.String] - $InstallationPolicy = 'Untrusted', - - [Parameter()] - [System.String] - $PackageManagementProvider = 'NuGet' - ) - - Write-Verbose -Message ($localizedData.TestTargetResourceMessage -f $Name) - - $returnValue = $false - - $getTargetResourceResult = Get-TargetResource -Name $Name - - if ($Ensure -eq $getTargetResourceResult.Ensure) { - if ($getTargetResourceResult.Ensure -eq 'Present' ) { - $returnValue = Test-DscParameterState ` - -CurrentValues $getTargetResourceResult ` - -DesiredValues $PSBoundParameters ` - -ValuesToCheck @( - 'SourceLocation' - 'ScriptSourceLocation' - 'PublishLocation' - 'ScriptPublishLocation' - 'InstallationPolicy' - 'PackageManagementProvider' - ) - } - else { - $returnValue = $true - } - } - - if ($returnValue) { - Write-Verbose -Message ($localizedData.InDesiredState -f $Name) - } - else { - Write-Verbose -Message ($localizedData.NotInDesiredState -f $Name) - } - - return $returnValue -} - -<# - .SYNOPSIS - Creates, removes or updates the repository. - - .PARAMETER Ensure - If the repository should be present or absent on the server - being configured. Default values is 'Present'. - - .PARAMETER Name - Specifies the name of the repository to manage. - - .PARAMETER SourceLocation - Specifies the URI for discovering and installing modules from - this repository. A URI can be a NuGet server feed, HTTP, HTTPS, - FTP or file location. - - .PARAMETER ScriptSourceLocation - Specifies the URI for the script source location. - - .PARAMETER PublishLocation - Specifies the URI of the publish location. For example, for - NuGet-based repositories, the publish location is similar - to http://someNuGetUrl.com/api/v2/Packages. - - .PARAMETER ScriptPublishLocation - Specifies the URI for the script publish location. - - .PARAMETER InstallationPolicy - Specifies the installation policy. Valid values are 'Trusted' - or 'Untrusted'. The default value is 'Untrusted'. - - .PARAMETER PackageManagementProvider - Specifies a OneGet package provider. Default value is 'NuGet'. -#> -function Set-TargetResource { - <# - These suppressions are added because this repository have other Visual Studio Code workspace - settings than those in DscResource.Tests DSC test framework. - Only those suppression that contradict this repository guideline is added here. - #> - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] - [CmdletBinding()] - param - ( - [Parameter()] - [ValidateSet('Present', 'Absent')] - [System.String] - $Ensure = 'Present', - - [Parameter(Mandatory = $true)] - [System.String] - $Name, - - [Parameter()] - [System.String] - $SourceLocation, - - [Parameter()] - [System.String] - $ScriptSourceLocation, - - [Parameter()] - [System.String] - $PublishLocation, - - [Parameter()] - [System.String] - $ScriptPublishLocation, - - [Parameter()] - [ValidateSet('Trusted', 'Untrusted')] - [System.String] - $InstallationPolicy = 'Untrusted', - - [Parameter()] - [System.String] - $PackageManagementProvider = 'NuGet' - ) - - $getTargetResourceResult = Get-TargetResource -Name $Name - - # Determine if the repository should be present or absent. - if ($Ensure -eq 'Present') { - $repositoryParameters = New-SplatParameterHashTable ` - -FunctionBoundParameters $PSBoundParameters ` - -ArgumentNames @( - 'Name' - 'SourceLocation' - 'ScriptSourceLocation' - 'PublishLocation' - 'ScriptPublishLocation' - 'InstallationPolicy' - 'PackageManagementProvider' - ) - - # Determine if the repository is already present. - if ($getTargetResourceResult.Ensure -eq 'Present') { - Write-Verbose -Message ($localizedData.RepositoryExist -f $Name) - - # Repository exist, update the properties. - Set-PSRepository @repositoryParameters -ErrorAction 'Stop' - } - else { - Write-Verbose -Message ($localizedData.RepositoryDoesNotExist -f $Name) - - # Repository did not exist, create the repository. - Register-PSRepository @repositoryParameters -ErrorAction 'Stop' - } - } - else { - if ($getTargetResourceResult.Ensure -eq 'Present') { - Write-Verbose -Message ($localizedData.RemoveExistingRepository -f $Name) - - # Repository did exist, remove the repository. - Unregister-PSRepository -Name $Name -ErrorAction 'Stop' - } - } -} - -# SIG # Begin signature block -# MIIjjgYJKoZIhvcNAQcCoIIjfzCCI3sCAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBlZKKmsIDI2MiW -# t2Bt6XXxjORDDSKWDjB5NwOM5+2tUaCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVYzCCFV8CAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgs+RU2J5w -# NacSfRVCp+ToPyznw3pclb/0hPnXb8LPCq4wQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQA8PvaOO0+Ef6lk0kKMSevhLul270IbDPTD4V/sXD20 -# rOpGDeRISfE2EWaZD3nv9H3icTFeFOTguBphXaQNh2PImj+7qzf3/lKP2ETu0xvI -# ZJii75+Pe25WL3ozwCd2eKaAG5ceSmAuCXCcEyw1BR0OW1mWTA25ya0tIa5v1466 -# sbykvU3vNEC4eN6dO56w6Y2WmziZx9YFn5feMjCSdJigMxN6SONXkVMTTe9x0s2+ -# I2Sjc8I+MPxDHfjwOtA7pHNk6KEve4UE3pWzVYJBe0JdXHIaxBUwcti2bue7DDDN -# xa0QnEzVd+eD55gR8rudUbiD30fQQzDzl+KvHmgrgdAVoYIS7TCCEukGCisGAQQB -# gjcDAwExghLZMIIS1QYJKoZIhvcNAQcCoIISxjCCEsICAQMxDzANBglghkgBZQME -# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEINsdx97zcSFVxkNkK+d8VvGiJ0Fsuev3S05KAcoZ -# Uxj9AgZfYQkjSiYYEjIwMjAwOTIyMjIxOTUxLjM2WjAEgAIB9KCB1KSB0TCBzjEL -# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v -# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj -# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU -# U1MgRVNOOkY3N0YtRTM1Ni01QkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T -# dGFtcCBTZXJ2aWNloIIOQTCCBPUwggPdoAMCAQICEzMAAAEq6BeW+Ian76MAAAAA -# ASowDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp -# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw -# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw -# HhcNMTkxMjE5MDExNTAyWhcNMjEwMzE3MDExNTAyWjCBzjELMAkGA1UEBhMCVVMx -# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT -# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh -# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkY3N0Yt -# RTM1Ni01QkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl -# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn9+VgaSF0m3FKwcG72WZ -# cX9RfE8XsvjmcSGa13TUoOixZtjzLngE3v6T0My/OpOg/f2/z9n420TMqPwF/kRC -# gbX+kl+nMIl7zQdmrKoyjShD0S6BVjpg1U1rZPW7nV33qrWEWa7V2DG3y4PaDsik -# FB2FLa2lzePccTMq9X+/ASvv8FxO7CpQequsGAdz3vV6lVHijls0qyOKRrCYzD0P -# +3KtNyLLcX0ar2kSCTwSol850BpuRqe4BZOOWYGFm1GI71bWoWnCe70bmpW900pE -# rFB23EwLTilYZ+fHMNpzv6MiqXnfYgQLlBKe9jzizMSnHDfVBb8tp9KIOYC1hYem -# bwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFHD0xS10Kz+uE3bL0SQTpkj07xNpMB8G -# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG -# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp -# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH -# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh -# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB -# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAIrANPQKcdWjjo5bJRus8iPxAhx/49OM -# FVikqDUrYPXlnrES6+Z/6Kzo3yCP1/WeQUgAu+H6IaTHwaAZr+gD0iFc0QVg80Vo -# fAdqf9QTDU/pON1qrLdy8sLx/zMTUJHUuFc2h+rrF+hP0csYVKD2yQ8szVND5EBB -# f0yKASbwUWWGGxDWIYHXf33Hx33aH0qymoYOc73pn0CPs5sO11TpGhmuxmSJFA2d -# eadfUj5G7C0u7ww3xeEktKXnCqoczeuppoy9IAhJW0rJKnMkLlmH7mQmWoV1KIgd -# bxD7xHoRYbwgtv09/7D8/J3IrdlORVdSkUD4mFaNzLOmFUbD19+PRgowggZxMIIE -# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV -# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE -# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v -# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y -# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN -# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU -# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE -# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50 -# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd -# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR -# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB -# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8 -# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB -# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO -# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w -# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr -# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv -# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB -# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF -# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt -# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh -# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7 -# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR -# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9 -# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8 -# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+ -# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh -# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy -# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo -# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx -# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341 -# Hgi62jbb01+P3nSISRKhggLPMIICOAIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w -# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkY3 -# N0YtRTM1Ni01QkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2 -# aWNloiMKAQEwBwYFKw4DAhoDFQDqsuasofIgw/vp4+XfbXEpQndhf6CBgzCBgKR+ -# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT -# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA -# 4xTB1zAiGA8yMDIwMDkyMjIyMzI1NVoYDzIwMjAwOTIzMjIzMjU1WjB0MDoGCisG -# AQQBhFkKBAExLDAqMAoCBQDjFMHXAgEAMAcCAQACAg5cMAcCAQACAhGGMAoCBQDj -# FhNXAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH -# oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAUkLp/BaA3FIU75KSgrKa -# sz4lpFeJKc1q90R79iPMjkdWuk0HzMJE/P+m9f1Nu5KdDb9Y3hk8jYCgEwZClaRo -# bWOX24LRDCwVMvwHP3tOmTsaZL3LONfT6yuxrEhUIaD2B5evsDgihSnRZXFHwlEw -# 2KeQBiVnSWkobaUU4DQYE9YxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJVUzET -# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV -# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T -# dGFtcCBQQ0EgMjAxMAITMwAAASroF5b4hqfvowAAAAABKjANBglghkgBZQMEAgEF -# AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi -# BCAOGPARROnr2oqQEu2OY1Bf2p1llcPXBS/KQIsljnOGKjCB+gYLKoZIhvcNAQkQ -# Ai8xgeowgecwgeQwgb0EIEOYNYRa9zp+Gzm3haijlD4UwUJxoiBXjJQ/gKm4GYuZ -# MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO -# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm -# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEq6BeW -# +Ian76MAAAAAASowIgQg9iJwv/WXI3bNmxzZZnw0+GGWIYQYBQ5UMWvWKXauOyUw -# DQYJKoZIhvcNAQELBQAEggEAQmz84vhytrXC6N3xTngSm+jXmE60CX4FWbIY7/sc -# aLR29t/ayGUM7tb48SpnUgk8Ogt32xMS02zZjvjGqGraktCUIeD+P5lDz1x9ouBd -# Oe4yHI6FV7Do6jIz7aje8R66z7TKkSSX/5z5i51aRuOOGmp31ZVSW58MtAkNy3WR -# 4i9F7OEqt0uZaokblXF2Cl3gMAYqMScQyAWDwc2IxLH3JbNTUAB2Vtdp0MUTJKqo -# RUoIlB3ZJd77W+kHkJk/kLLCWUy7Xr6T7ZFw3AfoXDfsdKOpci3YRpZci4zg95R8 -# Pf6al6GKk9Ge7Rus7feYG7PItce08fWvT3SWOT+HNUNwKg== -# SIG # End signature block +$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent + +# Import localization helper functions. +$helperName = 'PowerShellGet.LocalizationHelper' +$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" +Import-Module -Name $dscResourcesFolderFilePath + +$script:localizedData = Get-LocalizedData -ResourceName 'MSFT_PSRepository' -ScriptRoot $PSScriptRoot + +# Import resource helper functions. +$helperName = 'PowerShellGet.ResourceHelper' +$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" +Import-Module -Name $dscResourcesFolderFilePath + +<# + .SYNOPSIS + Returns the current state of the repository. + + .PARAMETER Name + Specifies the name of the repository to manage. +#> +function Get-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $Name + ) + + $returnValue = @{ + Ensure = 'Absent' + Name = $Name + SourceLocation = $null + ScriptSourceLocation = $null + PublishLocation = $null + ScriptPublishLocation = $null + InstallationPolicy = $null + PackageManagementProvider = $null + Trusted = $false + Registered = $false + } + + Write-Verbose -Message ($localizedData.GetTargetResourceMessage -f $Name) + + $repository = Get-PSRepository -Name $Name -ErrorAction 'SilentlyContinue' + + if ($repository) { + $returnValue.Ensure = 'Present' + $returnValue.SourceLocation = $repository.SourceLocation + $returnValue.ScriptSourceLocation = $repository.ScriptSourceLocation + $returnValue.PublishLocation = $repository.PublishLocation + $returnValue.ScriptPublishLocation = $repository.ScriptPublishLocation + $returnValue.InstallationPolicy = $repository.InstallationPolicy + $returnValue.PackageManagementProvider = $repository.PackageManagementProvider + $returnValue.Trusted = $repository.Trusted + $returnValue.Registered = $repository.Registered + } + else { + Write-Verbose -Message ($localizedData.RepositoryNotFound -f $Name) + } + + return $returnValue +} + +<# + .SYNOPSIS + Determines if the repository is in the desired state. + + .PARAMETER Ensure + If the repository should be present or absent on the server + being configured. Default values is 'Present'. + + .PARAMETER Name + Specifies the name of the repository to manage. + + .PARAMETER SourceLocation + Specifies the URI for discovering and installing modules from + this repository. A URI can be a NuGet server feed, HTTP, HTTPS, + FTP or file location. + + .PARAMETER ScriptSourceLocation + Specifies the URI for the script source location. + + .PARAMETER PublishLocation + Specifies the URI of the publish location. For example, for + NuGet-based repositories, the publish location is similar + to http://someNuGetUrl.com/api/v2/Packages. + + .PARAMETER ScriptPublishLocation + Specifies the URI for the script publish location. + + .PARAMETER InstallationPolicy + Specifies the installation policy. Valid values are 'Trusted' + or 'Untrusted'. The default value is 'Untrusted'. + + .PARAMETER PackageManagementProvider + Specifies a OneGet package provider. Default value is 'NuGet'. +#> +function Test-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + [OutputType([System.Boolean])] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $SourceLocation, + + [Parameter()] + [System.String] + $ScriptSourceLocation, + + [Parameter()] + [System.String] + $PublishLocation, + + [Parameter()] + [System.String] + $ScriptPublishLocation, + + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] + [System.String] + $InstallationPolicy = 'Untrusted', + + [Parameter()] + [System.String] + $PackageManagementProvider = 'NuGet' + ) + + Write-Verbose -Message ($localizedData.TestTargetResourceMessage -f $Name) + + $returnValue = $false + + $getTargetResourceResult = Get-TargetResource -Name $Name + + if ($Ensure -eq $getTargetResourceResult.Ensure) { + if ($getTargetResourceResult.Ensure -eq 'Present' ) { + $returnValue = Test-DscParameterState ` + -CurrentValues $getTargetResourceResult ` + -DesiredValues $PSBoundParameters ` + -ValuesToCheck @( + 'SourceLocation' + 'ScriptSourceLocation' + 'PublishLocation' + 'ScriptPublishLocation' + 'InstallationPolicy' + 'PackageManagementProvider' + ) + } + else { + $returnValue = $true + } + } + + if ($returnValue) { + Write-Verbose -Message ($localizedData.InDesiredState -f $Name) + } + else { + Write-Verbose -Message ($localizedData.NotInDesiredState -f $Name) + } + + return $returnValue +} + +<# + .SYNOPSIS + Creates, removes or updates the repository. + + .PARAMETER Ensure + If the repository should be present or absent on the server + being configured. Default values is 'Present'. + + .PARAMETER Name + Specifies the name of the repository to manage. + + .PARAMETER SourceLocation + Specifies the URI for discovering and installing modules from + this repository. A URI can be a NuGet server feed, HTTP, HTTPS, + FTP or file location. + + .PARAMETER ScriptSourceLocation + Specifies the URI for the script source location. + + .PARAMETER PublishLocation + Specifies the URI of the publish location. For example, for + NuGet-based repositories, the publish location is similar + to http://someNuGetUrl.com/api/v2/Packages. + + .PARAMETER ScriptPublishLocation + Specifies the URI for the script publish location. + + .PARAMETER InstallationPolicy + Specifies the installation policy. Valid values are 'Trusted' + or 'Untrusted'. The default value is 'Untrusted'. + + .PARAMETER PackageManagementProvider + Specifies a OneGet package provider. Default value is 'NuGet'. +#> +function Set-TargetResource { + <# + These suppressions are added because this repository have other Visual Studio Code workspace + settings than those in DscResource.Tests DSC test framework. + Only those suppression that contradict this repository guideline is added here. + #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-FunctionBlockBraces', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('DscResource.AnalyzerRules\Measure-IfStatement', '')] + [CmdletBinding()] + param + ( + [Parameter()] + [ValidateSet('Present', 'Absent')] + [System.String] + $Ensure = 'Present', + + [Parameter(Mandatory = $true)] + [System.String] + $Name, + + [Parameter()] + [System.String] + $SourceLocation, + + [Parameter()] + [System.String] + $ScriptSourceLocation, + + [Parameter()] + [System.String] + $PublishLocation, + + [Parameter()] + [System.String] + $ScriptPublishLocation, + + [Parameter()] + [ValidateSet('Trusted', 'Untrusted')] + [System.String] + $InstallationPolicy = 'Untrusted', + + [Parameter()] + [System.String] + $PackageManagementProvider = 'NuGet' + ) + + $getTargetResourceResult = Get-TargetResource -Name $Name + + # Determine if the repository should be present or absent. + if ($Ensure -eq 'Present') { + $repositoryParameters = New-SplatParameterHashTable ` + -FunctionBoundParameters $PSBoundParameters ` + -ArgumentNames @( + 'Name' + 'SourceLocation' + 'ScriptSourceLocation' + 'PublishLocation' + 'ScriptPublishLocation' + 'InstallationPolicy' + 'PackageManagementProvider' + ) + + # Determine if the repository is already present. + if ($getTargetResourceResult.Ensure -eq 'Present') { + Write-Verbose -Message ($localizedData.RepositoryExist -f $Name) + + # Repository exist, update the properties. + Set-PSRepository @repositoryParameters -ErrorAction 'Stop' + } + else { + Write-Verbose -Message ($localizedData.RepositoryDoesNotExist -f $Name) + + # Repository did not exist, create the repository. + Register-PSRepository @repositoryParameters -ErrorAction 'Stop' + } + } + else { + if ($getTargetResourceResult.Ensure -eq 'Present') { + Write-Verbose -Message ($localizedData.RemoveExistingRepository -f $Name) + + # Repository did exist, remove the repository. + Unregister-PSRepository -Name $Name -ErrorAction 'Stop' + } + } +} + +# SIG # Begin signature block +# MIIjjgYJKoZIhvcNAQcCoIIjfzCCI3sCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBlZKKmsIDI2MiW +# t2Bt6XXxjORDDSKWDjB5NwOM5+2tUaCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVYzCCFV8CAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgs+RU2J5w +# NacSfRVCp+ToPyznw3pclb/0hPnXb8LPCq4wQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQA8PvaOO0+Ef6lk0kKMSevhLul270IbDPTD4V/sXD20 +# rOpGDeRISfE2EWaZD3nv9H3icTFeFOTguBphXaQNh2PImj+7qzf3/lKP2ETu0xvI +# ZJii75+Pe25WL3ozwCd2eKaAG5ceSmAuCXCcEyw1BR0OW1mWTA25ya0tIa5v1466 +# sbykvU3vNEC4eN6dO56w6Y2WmziZx9YFn5feMjCSdJigMxN6SONXkVMTTe9x0s2+ +# I2Sjc8I+MPxDHfjwOtA7pHNk6KEve4UE3pWzVYJBe0JdXHIaxBUwcti2bue7DDDN +# xa0QnEzVd+eD55gR8rudUbiD30fQQzDzl+KvHmgrgdAVoYIS7TCCEukGCisGAQQB +# gjcDAwExghLZMIIS1QYJKoZIhvcNAQcCoIISxjCCEsICAQMxDzANBglghkgBZQME +# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEINsdx97zcSFVxkNkK+d8VvGiJ0Fsuev3S05KAcoZ +# Uxj9AgZfYQkjSiYYEjIwMjAwOTIyMjIxOTUxLjM2WjAEgAIB9KCB1KSB0TCBzjEL +# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v +# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj +# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU +# U1MgRVNOOkY3N0YtRTM1Ni01QkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T +# dGFtcCBTZXJ2aWNloIIOQTCCBPUwggPdoAMCAQICEzMAAAEq6BeW+Ian76MAAAAA +# ASowDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw +# HhcNMTkxMjE5MDExNTAyWhcNMjEwMzE3MDExNTAyWjCBzjELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh +# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkY3N0Yt +# RTM1Ni01QkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl +# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAn9+VgaSF0m3FKwcG72WZ +# cX9RfE8XsvjmcSGa13TUoOixZtjzLngE3v6T0My/OpOg/f2/z9n420TMqPwF/kRC +# gbX+kl+nMIl7zQdmrKoyjShD0S6BVjpg1U1rZPW7nV33qrWEWa7V2DG3y4PaDsik +# FB2FLa2lzePccTMq9X+/ASvv8FxO7CpQequsGAdz3vV6lVHijls0qyOKRrCYzD0P +# +3KtNyLLcX0ar2kSCTwSol850BpuRqe4BZOOWYGFm1GI71bWoWnCe70bmpW900pE +# rFB23EwLTilYZ+fHMNpzv6MiqXnfYgQLlBKe9jzizMSnHDfVBb8tp9KIOYC1hYem +# bwIDAQABo4IBGzCCARcwHQYDVR0OBBYEFHD0xS10Kz+uE3bL0SQTpkj07xNpMB8G +# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG +# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp +# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH +# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh +# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB +# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAIrANPQKcdWjjo5bJRus8iPxAhx/49OM +# FVikqDUrYPXlnrES6+Z/6Kzo3yCP1/WeQUgAu+H6IaTHwaAZr+gD0iFc0QVg80Vo +# fAdqf9QTDU/pON1qrLdy8sLx/zMTUJHUuFc2h+rrF+hP0csYVKD2yQ8szVND5EBB +# f0yKASbwUWWGGxDWIYHXf33Hx33aH0qymoYOc73pn0CPs5sO11TpGhmuxmSJFA2d +# eadfUj5G7C0u7ww3xeEktKXnCqoczeuppoy9IAhJW0rJKnMkLlmH7mQmWoV1KIgd +# bxD7xHoRYbwgtv09/7D8/J3IrdlORVdSkUD4mFaNzLOmFUbD19+PRgowggZxMIIE +# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v +# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y +# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN +# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU +# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE +# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50 +# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd +# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR +# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB +# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8 +# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB +# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO +# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w +# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr +# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv +# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB +# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF +# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt +# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh +# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7 +# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR +# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9 +# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8 +# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+ +# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh +# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy +# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo +# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx +# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341 +# Hgi62jbb01+P3nSISRKhggLPMIICOAIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w +# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOkY3 +# N0YtRTM1Ni01QkFFMSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2 +# aWNloiMKAQEwBwYFKw4DAhoDFQDqsuasofIgw/vp4+XfbXEpQndhf6CBgzCBgKR+ +# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT +# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA +# 4xTB1zAiGA8yMDIwMDkyMjIyMzI1NVoYDzIwMjAwOTIzMjIzMjU1WjB0MDoGCisG +# AQQBhFkKBAExLDAqMAoCBQDjFMHXAgEAMAcCAQACAg5cMAcCAQACAhGGMAoCBQDj +# FhNXAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEAAgMH +# oSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAUkLp/BaA3FIU75KSgrKa +# sz4lpFeJKc1q90R79iPMjkdWuk0HzMJE/P+m9f1Nu5KdDb9Y3hk8jYCgEwZClaRo +# bWOX24LRDCwVMvwHP3tOmTsaZL3LONfT6yuxrEhUIaD2B5evsDgihSnRZXFHwlEw +# 2KeQBiVnSWkobaUU4DQYE9YxggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJVUzET +# MBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMV +# TWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1T +# dGFtcCBQQ0EgMjAxMAITMwAAASroF5b4hqfvowAAAAABKjANBglghkgBZQMEAgEF +# AKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJBDEi +# BCAOGPARROnr2oqQEu2OY1Bf2p1llcPXBS/KQIsljnOGKjCB+gYLKoZIhvcNAQkQ +# Ai8xgeowgecwgeQwgb0EIEOYNYRa9zp+Gzm3haijlD4UwUJxoiBXjJQ/gKm4GYuZ +# MIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO +# BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEm +# MCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEq6BeW +# +Ian76MAAAAAASowIgQg9iJwv/WXI3bNmxzZZnw0+GGWIYQYBQ5UMWvWKXauOyUw +# DQYJKoZIhvcNAQELBQAEggEAQmz84vhytrXC6N3xTngSm+jXmE60CX4FWbIY7/sc +# aLR29t/ayGUM7tb48SpnUgk8Ogt32xMS02zZjvjGqGraktCUIeD+P5lDz1x9ouBd +# Oe4yHI6FV7Do6jIz7aje8R66z7TKkSSX/5z5i51aRuOOGmp31ZVSW58MtAkNy3WR +# 4i9F7OEqt0uZaokblXF2Cl3gMAYqMScQyAWDwc2IxLH3JbNTUAB2Vtdp0MUTJKqo +# RUoIlB3ZJd77W+kHkJk/kLLCWUy7Xr6T7ZFw3AfoXDfsdKOpci3YRpZci4zg95R8 +# Pf6al6GKk9Ge7Rus7feYG7PItce08fWvT3SWOT+HNUNwKg== +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl index 76ec99a5a2..4e3610b732 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mfl @@ -1,18 +1,18 @@ -#pragma namespace("\\\\.\\root\\default") -instance of __namespace{ name="MS_409";}; -#pragma namespace("\\\\.\\root\\default\\MS_409") - -[AMENDMENT, LOCALE("MS_409")] -class MSFT_PSModule : OMI_BaseResource -{ - [Key, Description("Specifies the name of the repository to manage.") : Amended] String Name; - [Description("If the repository should be present or absent on the server being configured. Default values is 'Present'.") : Amended] String Ensure; - [Description("Specifies the URI for discovering and installing modules from this repository. A URI can be a NuGet server feed, HTTP, HTTPS, FTP or file location.") : Amended] String SourceLocation; - [Description("Specifies the URI for the script source location.") : Amended] String ScriptSourceLocation; - [Description("Specifies the URI of the publish location. For example, for NuGet-based repositories, the publish location is similar to http://someNuGetUrl.com/api/v2/Packages.") : Amended] String PublishLocation; - [Description("Specifies the URI for the script publish location.") : Amended] String ScriptPublishLocation; - [Description("Specifies the installation policy. Valid values are 'Trusted' or 'Untrusted'. The default value is 'Untrusted'.") : Amended] String InstallationPolicy; - [Description("Specifies a OneGet package provider. Default value is 'NuGet'.") : Amended] String PackageManagementProvider; - [Description("Specifies if the repository is trusted.") : Amended] Boolean Trusted; - [Description("Specifies if the repository is registered.") : Amended] Boolean Registered; -}; +#pragma namespace("\\\\.\\root\\default") +instance of __namespace{ name="MS_409";}; +#pragma namespace("\\\\.\\root\\default\\MS_409") + +[AMENDMENT, LOCALE("MS_409")] +class MSFT_PSModule : OMI_BaseResource +{ + [Key, Description("Specifies the name of the repository to manage.") : Amended] String Name; + [Description("If the repository should be present or absent on the server being configured. Default values is 'Present'.") : Amended] String Ensure; + [Description("Specifies the URI for discovering and installing modules from this repository. A URI can be a NuGet server feed, HTTP, HTTPS, FTP or file location.") : Amended] String SourceLocation; + [Description("Specifies the URI for the script source location.") : Amended] String ScriptSourceLocation; + [Description("Specifies the URI of the publish location. For example, for NuGet-based repositories, the publish location is similar to http://someNuGetUrl.com/api/v2/Packages.") : Amended] String PublishLocation; + [Description("Specifies the URI for the script publish location.") : Amended] String ScriptPublishLocation; + [Description("Specifies the installation policy. Valid values are 'Trusted' or 'Untrusted'. The default value is 'Untrusted'.") : Amended] String InstallationPolicy; + [Description("Specifies a OneGet package provider. Default value is 'NuGet'.") : Amended] String PackageManagementProvider; + [Description("Specifies if the repository is trusted.") : Amended] Boolean Trusted; + [Description("Specifies if the repository is registered.") : Amended] Boolean Registered; +}; diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof index ec3bd01e12..648c36323d 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/MSFT_PSRepository.schema.mof @@ -1,207 +1,207 @@ -[ClassVersion("1.0.0.0"),FriendlyName("PSRepository")] -class MSFT_PSRepository : OMI_BaseResource -{ - [Key] String Name; - [Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; - [Write] String SourceLocation; - [Write] String ScriptSourceLocation; - [Write] String PublishLocation; - [Write] String ScriptPublishLocation; - [Write, ValueMap{"Trusted","Untrusted"}, Values{"Trusted","Untrusted"}] String InstallationPolicy; - [Write] String PackageManagementProvider; - [Read] Boolean Trusted; - [Read] Boolean Registered; -}; - -/* SIG # Begin signature block */ -/* MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor */ -/* BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG */ -/* KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAUB2bP/4Ied6bW */ -/* IFA1D31XybyRSF4DCKxuisoZB3v8laCCDYEwggX/MIID56ADAgECAhMzAAABh3IX */ -/* chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD */ -/* VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy */ -/* b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p */ -/* bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw */ -/* CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u */ -/* ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy */ -/* b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB */ -/* AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB */ -/* znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH */ -/* sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d */ -/* weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ */ -/* itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV */ -/* Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE */ -/* AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw */ -/* UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 */ -/* ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu */ -/* ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu */ -/* bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w */ -/* Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 */ -/* Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx */ -/* MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy */ -/* S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K */ -/* NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV */ -/* BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr */ -/* qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx */ -/* zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe */ -/* yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g */ -/* yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf */ -/* AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI */ -/* 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 */ -/* GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea */ -/* jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS */ -/* AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK */ -/* V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 */ -/* IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 */ -/* ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla */ -/* MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS */ -/* ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT */ -/* H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB */ -/* AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG */ -/* OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S */ -/* 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz */ -/* y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 */ -/* 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u */ -/* M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 */ -/* X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl */ -/* XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP */ -/* 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB */ -/* l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF */ -/* RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM */ -/* CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ */ -/* BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud */ -/* DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO */ -/* 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 */ -/* LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ -/* Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p */ -/* Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ -/* Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB */ -/* FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw */ -/* cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA */ -/* XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY */ -/* 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj */ -/* 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd */ -/* d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ */ -/* Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf */ -/* wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ */ -/* aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j */ -/* NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B */ -/* xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 */ -/* eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 */ -/* r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I */ -/* RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG */ -/* A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx */ -/* HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z */ -/* b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN */ -/* BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor */ -/* BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgMC/orGY1 */ -/* gfB1hsQjG1lyTu9MAGbhJ+T22w7TA3J3lU8wQgYKKwYBBAGCNwIBDDE0MDKgFIAS */ -/* AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN */ -/* BgkqhkiG9w0BAQEFAASCAQCjrMwnWdiDhFgDpoWhqWMJ473gHqjSpuGt18jSbPyW */ -/* whPOmBvwYOb/CAiQ/kX32DaYn7EQJOKdReWwiZrqtf2TqrAXYeaoC2qCqkZOqqMC */ -/* c+79Ov9NQ4MsxTFiRa023+eWg5KE266QIh/WJaQaBSPpoXbIcHcEnQ8lHdb/DO9v */ -/* yZ/Zp/t8PuXOap+Go6jkSeD/zU4B8FswbqSJXWrLCzEWSjAQUksoZXYVsiUvb6+w */ -/* 9t79CK4ugNLJS6ILDxn5ncOQfHv47d1e4H2Y+OWOP/Z45kcD1fl4QRnM+WQEa859 */ -/* spOcWWk9pnTuvfCo5X86LOP+jdQa304GVxDeAhJjtvquoYIS8TCCEu0GCisGAQQB */ -/* gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME */ -/* AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB */ -/* MDEwDQYJYIZIAWUDBAIBBQAEIBPLNFthVEbZ8wwcxOxfPOwIYuIklub6TutRWTzR */ -/* A5CWAgZfYPphXqQYEzIwMjAwOTIyMjIxOTUxLjI0OVowBIACAfSggdSkgdEwgc4x */ -/* CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt */ -/* b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p */ -/* Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg */ -/* VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt */ -/* U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJy9uo++RqBmoAAAA */ -/* AAEnMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo */ -/* aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y */ -/* cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw */ -/* MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT */ -/* MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK */ -/* ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy */ -/* YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 */ -/* LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj */ -/* ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPgB3nERnk6fS40vvWeD */ -/* 3HCgM9Ep4xTIQiPnJXE9E+HkZVtTsPemoOyhfNAyF95E/rUvXOVTUcJFL7Xb16jT */ -/* KPXONsCWY8DCixSDIiid6xa30TiEWVcIZRwiDlcx29D467OTav5rA1G6TwAEY5rQ */ -/* jhUHLrOoJgfJfakZq6IHjd+slI0/qlys7QIGakFk2OB6mh/ln/nS8G4kNRK6Do4g */ -/* xDtnBSFLNfhsSZlRSMDJwFvrZ2FCkaoexd7rKlUNOAAScY411IEqQeI1PwfRm3aW */ -/* bS8IvAfJPC2Ah2LrtP8sKn5faaU8epexje7vZfcZif/cbxgUKStJzqbdvTBNc93n */ -/* /Z8CAwEAAaOCARswggEXMB0GA1UdDgQWBBTl9JZVgF85MSRbYlOJXbhY022V8jAf */ -/* BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH */ -/* hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU */ -/* aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF */ -/* BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 */ -/* YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG */ -/* AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAKyo180VXHBqVnjZwQy7NlzXbo2+W5 */ -/* qfHxR7ANV5RBkRkdGamkwUcDNL+DpHObFPJHa0oTeYKE0Zbl1MvvfS8RtGGdhGYG */ -/* CJf+BPd/gBCs4+dkZdjvOzNyuVuDPGlqQ5f7HS7iuQ/cCyGHcHYJ0nXVewF2Lk+J */ -/* lrWykHpTlLwPXmCpNR+gieItPi/UMF2RYTGwojW+yIVwNyMYnjFGUxEX5/DtJjRZ */ -/* mg7PBHMrENN2DgO6wBelp4ptyH2KK2EsWT+8jFCuoKv+eJby0QD55LN5f8SrUPRn */ -/* K86fh7aVOfCglQofo5ABZIGiDIrg4JsV4k6p0oBSIFOAcqRAhiH+1spCMIIGcTCC */ -/* BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC */ -/* VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV */ -/* BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv */ -/* b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN */ -/* MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv */ -/* bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 */ -/* aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw */ -/* DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 */ -/* VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw */ -/* RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe */ -/* dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx */ -/* Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G */ -/* kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA */ -/* AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 */ -/* fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC */ -/* AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX */ -/* zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v */ -/* cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI */ -/* KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j */ -/* b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g */ -/* AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 */ -/* d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB */ -/* BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA */ -/* bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh */ -/* IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS */ -/* +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK */ -/* kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon */ -/* /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi */ -/* PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ */ -/* fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII */ -/* YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 */ -/* cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a */ -/* KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ */ -/* cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ */ -/* NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT */ -/* AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD */ -/* VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP */ -/* cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow */ -/* QTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy */ -/* dmljZaIjCgEBMAcGBSsOAwIaAxUAs5W4TmyDHMRM7iz6mgGojqvXHzOggYMwgYCk */ -/* fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH */ -/* UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD */ -/* Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF */ -/* AOMUsu8wIhgPMjAyMDA5MjIyMTI5MTlaGA8yMDIwMDkyMzIxMjkxOVowdzA9Bgor */ -/* BgEEAYRZCgQBMS8wLTAKAgUA4xSy7wIBADAKAgEAAgIVPgIB/zAHAgEAAgIRtjAK */ -/* AgUA4xYEbwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB */ -/* AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEMD4esQRMLwQdhk */ -/* Co1zgvmclcwl3lYYpk1oMh1ndsU3+97Rt6FV3adS4Hezc/K94oQKjcxtMVzLzQhG */ -/* agM6XlqB31VD8n2nxVuaWD1yp2jm/0IvfL9nFMHJRhgANMiBdHqvqNrd86c/Kryq */ -/* sI0Ch0sOx9wg3BozzqQhmdNjf9c6MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC */ -/* VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV */ -/* BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp */ -/* bWUtU3RhbXAgUENBIDIwMTACEzMAAAEnL26j75GoGagAAAAAAScwDQYJYIZIAWUD */ -/* BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B */ -/* CQQxIgQg/U1ThwYjl65L4baBOXDEDb7//jQrQlYA0cYE9FlfO3swgfoGCyqGSIb3 */ -/* DQEJEAIvMYHqMIHnMIHkMIG9BCAbkuhLEoYdahb/BUyVszO2VDi6kB3MSaof/+8u */ -/* 7SM+IjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u */ -/* MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp */ -/* b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB */ -/* Jy9uo++RqBmoAAAAAAEnMCIEIK4r6N3NISekswMCG1kSBJCCCePrlLDQWbMKz0wt */ -/* Lj6CMA0GCSqGSIb3DQEBCwUABIIBACDLuq6BoGBriDoXannpgEinZyZN3Q033i3n */ -/* dqST50fVElRabEBHd70IaqgBBovB75obPAEecVLMP24ti411wt2k4gs/zor+3I34 */ -/* r/hFZRQEJbMESNsRDpCDFVm5fT55uwFUTg6esiQgsMXcvYlpkypKmq8fr4I2GT4V */ -/* leW9JafateqTA7f4oQeLvSq+Y/QrMVQIf08db1vXi3J0crsIFeVRmAB34kW9rrqT */ -/* pvQKpJHm8SKqBQMMfQXxyomEbwDeJ8Eu/9/FwL+NfvzdsNwxWzjA05IgZdEelwun */ -/* f6B4E0kWDJvgeedsXIvsQ92hAoQubWQ2F9pYDygONBjoxnqijzs= */ -/* SIG # End signature block */ +[ClassVersion("1.0.0.0"),FriendlyName("PSRepository")] +class MSFT_PSRepository : OMI_BaseResource +{ + [Key] String Name; + [Write, ValueMap{"Present","Absent"}, Values{"Present","Absent"}] String Ensure; + [Write] String SourceLocation; + [Write] String ScriptSourceLocation; + [Write] String PublishLocation; + [Write] String ScriptPublishLocation; + [Write, ValueMap{"Trusted","Untrusted"}, Values{"Trusted","Untrusted"}] String InstallationPolicy; + [Write] String PackageManagementProvider; + [Read] Boolean Trusted; + [Read] Boolean Registered; +}; + +/* SIG # Begin signature block */ +/* MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor */ +/* BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG */ +/* KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAUB2bP/4Ied6bW */ +/* IFA1D31XybyRSF4DCKxuisoZB3v8laCCDYEwggX/MIID56ADAgECAhMzAAABh3IX */ +/* chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD */ +/* VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy */ +/* b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p */ +/* bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw */ +/* CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u */ +/* ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy */ +/* b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB */ +/* AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB */ +/* znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH */ +/* sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d */ +/* weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ */ +/* itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV */ +/* Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE */ +/* AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw */ +/* UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 */ +/* ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu */ +/* ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu */ +/* bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w */ +/* Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 */ +/* Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx */ +/* MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy */ +/* S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K */ +/* NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV */ +/* BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr */ +/* qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx */ +/* zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe */ +/* yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g */ +/* yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf */ +/* AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI */ +/* 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 */ +/* GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea */ +/* jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS */ +/* AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK */ +/* V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 */ +/* IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 */ +/* ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla */ +/* MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS */ +/* ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT */ +/* H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB */ +/* AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG */ +/* OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S */ +/* 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz */ +/* y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 */ +/* 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u */ +/* M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 */ +/* X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl */ +/* XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP */ +/* 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB */ +/* l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF */ +/* RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM */ +/* CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ */ +/* BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud */ +/* DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO */ +/* 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 */ +/* LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ +/* Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p */ +/* Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y */ +/* Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB */ +/* FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw */ +/* cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA */ +/* XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY */ +/* 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj */ +/* 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd */ +/* d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ */ +/* Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf */ +/* wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ */ +/* aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j */ +/* NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B */ +/* xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 */ +/* eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 */ +/* r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I */ +/* RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG */ +/* A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx */ +/* HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z */ +/* b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN */ +/* BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor */ +/* BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgMC/orGY1 */ +/* gfB1hsQjG1lyTu9MAGbhJ+T22w7TA3J3lU8wQgYKKwYBBAGCNwIBDDE0MDKgFIAS */ +/* AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN */ +/* BgkqhkiG9w0BAQEFAASCAQCjrMwnWdiDhFgDpoWhqWMJ473gHqjSpuGt18jSbPyW */ +/* whPOmBvwYOb/CAiQ/kX32DaYn7EQJOKdReWwiZrqtf2TqrAXYeaoC2qCqkZOqqMC */ +/* c+79Ov9NQ4MsxTFiRa023+eWg5KE266QIh/WJaQaBSPpoXbIcHcEnQ8lHdb/DO9v */ +/* yZ/Zp/t8PuXOap+Go6jkSeD/zU4B8FswbqSJXWrLCzEWSjAQUksoZXYVsiUvb6+w */ +/* 9t79CK4ugNLJS6ILDxn5ncOQfHv47d1e4H2Y+OWOP/Z45kcD1fl4QRnM+WQEa859 */ +/* spOcWWk9pnTuvfCo5X86LOP+jdQa304GVxDeAhJjtvquoYIS8TCCEu0GCisGAQQB */ +/* gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME */ +/* AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB */ +/* MDEwDQYJYIZIAWUDBAIBBQAEIBPLNFthVEbZ8wwcxOxfPOwIYuIklub6TutRWTzR */ +/* A5CWAgZfYPphXqQYEzIwMjAwOTIyMjIxOTUxLjI0OVowBIACAfSggdSkgdEwgc4x */ +/* CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt */ +/* b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p */ +/* Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg */ +/* VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt */ +/* U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJy9uo++RqBmoAAAA */ +/* AAEnMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo */ +/* aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y */ +/* cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw */ +/* MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT */ +/* MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK */ +/* ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy */ +/* YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 */ +/* LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj */ +/* ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPgB3nERnk6fS40vvWeD */ +/* 3HCgM9Ep4xTIQiPnJXE9E+HkZVtTsPemoOyhfNAyF95E/rUvXOVTUcJFL7Xb16jT */ +/* KPXONsCWY8DCixSDIiid6xa30TiEWVcIZRwiDlcx29D467OTav5rA1G6TwAEY5rQ */ +/* jhUHLrOoJgfJfakZq6IHjd+slI0/qlys7QIGakFk2OB6mh/ln/nS8G4kNRK6Do4g */ +/* xDtnBSFLNfhsSZlRSMDJwFvrZ2FCkaoexd7rKlUNOAAScY411IEqQeI1PwfRm3aW */ +/* bS8IvAfJPC2Ah2LrtP8sKn5faaU8epexje7vZfcZif/cbxgUKStJzqbdvTBNc93n */ +/* /Z8CAwEAAaOCARswggEXMB0GA1UdDgQWBBTl9JZVgF85MSRbYlOJXbhY022V8jAf */ +/* BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH */ +/* hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU */ +/* aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF */ +/* BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 */ +/* YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG */ +/* AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAKyo180VXHBqVnjZwQy7NlzXbo2+W5 */ +/* qfHxR7ANV5RBkRkdGamkwUcDNL+DpHObFPJHa0oTeYKE0Zbl1MvvfS8RtGGdhGYG */ +/* CJf+BPd/gBCs4+dkZdjvOzNyuVuDPGlqQ5f7HS7iuQ/cCyGHcHYJ0nXVewF2Lk+J */ +/* lrWykHpTlLwPXmCpNR+gieItPi/UMF2RYTGwojW+yIVwNyMYnjFGUxEX5/DtJjRZ */ +/* mg7PBHMrENN2DgO6wBelp4ptyH2KK2EsWT+8jFCuoKv+eJby0QD55LN5f8SrUPRn */ +/* K86fh7aVOfCglQofo5ABZIGiDIrg4JsV4k6p0oBSIFOAcqRAhiH+1spCMIIGcTCC */ +/* BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC */ +/* VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV */ +/* BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv */ +/* b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN */ +/* MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv */ +/* bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 */ +/* aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw */ +/* DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 */ +/* VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw */ +/* RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe */ +/* dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx */ +/* Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G */ +/* kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA */ +/* AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 */ +/* fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC */ +/* AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX */ +/* zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v */ +/* cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI */ +/* KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j */ +/* b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g */ +/* AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 */ +/* d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB */ +/* BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA */ +/* bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh */ +/* IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS */ +/* +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK */ +/* kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon */ +/* /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi */ +/* PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ */ +/* fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII */ +/* YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 */ +/* cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a */ +/* KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ */ +/* cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ */ +/* NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT */ +/* AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD */ +/* VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP */ +/* cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow */ +/* QTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy */ +/* dmljZaIjCgEBMAcGBSsOAwIaAxUAs5W4TmyDHMRM7iz6mgGojqvXHzOggYMwgYCk */ +/* fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH */ +/* UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD */ +/* Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF */ +/* AOMUsu8wIhgPMjAyMDA5MjIyMTI5MTlaGA8yMDIwMDkyMzIxMjkxOVowdzA9Bgor */ +/* BgEEAYRZCgQBMS8wLTAKAgUA4xSy7wIBADAKAgEAAgIVPgIB/zAHAgEAAgIRtjAK */ +/* AgUA4xYEbwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB */ +/* AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEMD4esQRMLwQdhk */ +/* Co1zgvmclcwl3lYYpk1oMh1ndsU3+97Rt6FV3adS4Hezc/K94oQKjcxtMVzLzQhG */ +/* agM6XlqB31VD8n2nxVuaWD1yp2jm/0IvfL9nFMHJRhgANMiBdHqvqNrd86c/Kryq */ +/* sI0Ch0sOx9wg3BozzqQhmdNjf9c6MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC */ +/* VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV */ +/* BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp */ +/* bWUtU3RhbXAgUENBIDIwMTACEzMAAAEnL26j75GoGagAAAAAAScwDQYJYIZIAWUD */ +/* BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B */ +/* CQQxIgQg/U1ThwYjl65L4baBOXDEDb7//jQrQlYA0cYE9FlfO3swgfoGCyqGSIb3 */ +/* DQEJEAIvMYHqMIHnMIHkMIG9BCAbkuhLEoYdahb/BUyVszO2VDi6kB3MSaof/+8u */ +/* 7SM+IjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u */ +/* MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp */ +/* b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB */ +/* Jy9uo++RqBmoAAAAAAEnMCIEIK4r6N3NISekswMCG1kSBJCCCePrlLDQWbMKz0wt */ +/* Lj6CMA0GCSqGSIb3DQEBCwUABIIBACDLuq6BoGBriDoXannpgEinZyZN3Q033i3n */ +/* dqST50fVElRabEBHd70IaqgBBovB75obPAEecVLMP24ti411wt2k4gs/zor+3I34 */ +/* r/hFZRQEJbMESNsRDpCDFVm5fT55uwFUTg6esiQgsMXcvYlpkypKmq8fr4I2GT4V */ +/* leW9JafateqTA7f4oQeLvSq+Y/QrMVQIf08db1vXi3J0crsIFeVRmAB34kW9rrqT */ +/* pvQKpJHm8SKqBQMMfQXxyomEbwDeJ8Eu/9/FwL+NfvzdsNwxWzjA05IgZdEelwun */ +/* f6B4E0kWDJvgeedsXIvsQ92hAoQubWQ2F9pYDygONBjoxnqijzs= */ +/* SIG # End signature block */ diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 index 24ce2b2bf3..aa5537280f 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/DscResources/MSFT_PSRepository/en-US/MSFT_PSRepository.strings.psd1 @@ -1,215 +1,215 @@ -# -# Copyright (c) Microsoft Corporation. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# culture = "en-US" -ConvertFrom-StringData -StringData @' - GetTargetResourceMessage = Return the current state of the repository '{0}'. - RepositoryNotFound = The repository '{0}' was not found. - TestTargetResourceMessage = Determining if the repository '{0}' is in the desired state. - InDesiredState = Repository '{0}' is in the desired state. - NotInDesiredState = Repository '{0}' is not in the desired state. - RepositoryExist = Updating the properties of the repository '{0}'. - RepositoryDoesNotExist = Creating the repository '{0}'. - RemoveExistingRepository = Removing the repository '{0}'. -'@ - -# SIG # Begin signature block -# MIIjjwYJKoZIhvcNAQcCoIIjgDCCI3wCAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDIsy85n1MNq+kW -# xzRs7tzuriysf98xKvvABEQcTZkh4aCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZDCCFWACAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgmA+n9QXz -# 8dT2ub5mX+3kqHOkIOPzbPjen0qPL4vqih4wQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQBPossKE/JJRgLki6udUal6kKvvd+cj8yT23YFiJ+GG -# 7YvWIt/u67qqWA+PSN0PCH8bY1HCUkMGat4UTqKJ/dALKx5oOlfsbUSACyX1BU9x -# mXDPzak96AnHy+oQICcENAWYBT4deS8jrFv1InsRT6lg4pEGQvCx+fZbdAe5HRp5 -# cAlIVYapHXxDBQvAbpMoMMi0TertRaBx9t3bWWLGguBUR92ra8a3HFOt7KHT04Xa -# 7JgLsQoTUTlavt67fWUABX5pSVwP25v9IXUSclgz5l1xXrBHTmZPGRsjBu881s+M -# 168vsc6P9RW8JjsKwDajs2oWkRY3F68wb7xLa2ZQ4X5koYIS7jCCEuoGCisGAQQB -# gjcDAwExghLaMIIS1gYJKoZIhvcNAQcCoIISxzCCEsMCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIHikZhYxRsySx5WAopqolokj5LdR1qw/Qj19bR6x -# FpA5AgZfYPebj+kYEzIwMjAwOTIyMjIxOTUxLjQ1NVowBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkEwggT1MIID3aADAgECAhMzAAABJt+6SyK5goIHAAAA -# AAEmMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2MEJD -# LUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4wvoacTvMNlXQTtfF/ -# Cx5Ol3X0fcjUNMvjLgTmO5+WHYJFbp725P3+qvFKDRQHWEI1Sz0gB24urVDIjXjB -# h5NVNJVMQJI2tltv7M4/4IbhZJb3xzQW7LolEoZYUZanBTUuyly9osCg4o5joViT -# 2GtmyxK+Fv5kC20l2opeaeptd/E7ceDAFRM87hiNCsK/KHyC+8+swnlg4gTOey6z -# QqhzgNsG6HrjLBuDtDs9izAMwS2yWT0T52QA9h3Q+B1C9ps2fMKMe+DHpG+0c61D -# 94Yh6cV2XHib4SBCnwIFZAeZE2UJ4qPANSYozI8PH+E5rCT3SVqYvHou97HsXvP2 -# I3MCAwEAAaOCARswggEXMB0GA1UdDgQWBBRJq6wfF7B+mEKN0VimX8ajNA5hQTAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBAlvudaOlv9Cfzv56bnX41czF6tLtH -# LB46l6XUch+qNN45ZmOTFwLot3JjwSrn4oycQ9qTET1TFDYd1QND0LiXmKz9OqBX -# ai6S8XdyCQEZvfL82jIAs9pwsAQ6XvV9jNybPStRgF/sOAM/Deyfmej9Tg9FcRwX -# ank2qgzdZZNb8GoEze7f1orcTF0Q89IUXWIlmwEwQFYF1wjn87N4ZxL9Z/xA2m/R -# 1zizFylWP/mpamCnVfZZLkafFLNUNVmcvc+9gM7vceJs37d3ydabk4wR6ObR34sW -# aLppmyPlsI1Qq5Lu6bJCWoXzYuWpkoK6oEep1gML6SRC3HKVS3UscZhtMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYICzzCCAjgCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2 -# MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUACmcyOWmZxErpq06B8dy6oMZ6//yggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMUsDowIhgPMjAyMDA5MjIyMTE3NDZaGA8yMDIwMDkyMzIxMTc0NlowdDA6Bgor -# BgEEAYRZCgQBMSwwKjAKAgUA4xSwOgIBADAHAgEAAgII+zAHAgEAAgIRDTAKAgUA -# 4xYBugIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID -# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAGFtTfLVXgP5nTlAW4+t -# qfeH51GOeG4NIbJTmRivKyHJM/AjTJCfaazyngryVGsVed/19YBgxoB07IS1Jlmm -# KNq9XofCcAXBUcoBsPLnGHr8vBVOpAPDLDYgr7ME8Qgofa5CjOmCQMYZyjsW2q5j -# 2OkaGPVN1YtLAZq82zgDGI+QMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMx -# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT -# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgUENBIDIwMTACEzMAAAEm37pLIrmCggcAAAAAASYwDQYJYIZIAWUDBAIB -# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx -# IgQgTg7YbUxqwEPryPvBnm6tKeHXdMGcW0dFqzF8v32xEgQwgfoGCyqGSIb3DQEJ -# EAIvMYHqMIHnMIHkMIG9BCA2/c/vnr1ecAzvapOWZ2xGfAkzrkfpGcrvMW07CQl1 -# DzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw -# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x -# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJt+6 -# SyK5goIHAAAAAAEmMCIEILioZM02FaPhX5O1/7sR3BeH9w6NUS1x/c+Q88996cSH -# MA0GCSqGSIb3DQEBCwUABIIBAEZEbtwLgdlFNIt4umG1gw1nrQVpGXIkyojlPtcb -# hQu757SIEuY7tS0MmwFimKjHmCbeVS12PJ1PHjxXs8YmpOusFH9wtCgPPHHm3IEx -# naynU0x6jl4MXGtmTIF5E8iHA1y0YeSoeeinaez2Lc78G+jHN6uzf+TJozwpqkle -# aZIVhuqPAqJZvfVDT3Bd7N4J2j4D6H0lxJ+yn/ENLkNEl4jVMutEMDQuDSikB8/V -# hpcMRx83R4M8gEIrFIORJVwqT5axQI5C9HQuWFB9xyTvU5Q/OaU4ybRj5jUTPNZs -# 3/emFZ3aTnt8arfvfztIvFw2NbnM6rIPVjm4YDT9YUD2xNM= -# SIG # End signature block +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# culture = "en-US" +ConvertFrom-StringData -StringData @' + GetTargetResourceMessage = Return the current state of the repository '{0}'. + RepositoryNotFound = The repository '{0}' was not found. + TestTargetResourceMessage = Determining if the repository '{0}' is in the desired state. + InDesiredState = Repository '{0}' is in the desired state. + NotInDesiredState = Repository '{0}' is not in the desired state. + RepositoryExist = Updating the properties of the repository '{0}'. + RepositoryDoesNotExist = Creating the repository '{0}'. + RemoveExistingRepository = Removing the repository '{0}'. +'@ + +# SIG # Begin signature block +# MIIjjwYJKoZIhvcNAQcCoIIjgDCCI3wCAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCDIsy85n1MNq+kW +# xzRs7tzuriysf98xKvvABEQcTZkh4aCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZDCCFWACAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgmA+n9QXz +# 8dT2ub5mX+3kqHOkIOPzbPjen0qPL4vqih4wQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQBPossKE/JJRgLki6udUal6kKvvd+cj8yT23YFiJ+GG +# 7YvWIt/u67qqWA+PSN0PCH8bY1HCUkMGat4UTqKJ/dALKx5oOlfsbUSACyX1BU9x +# mXDPzak96AnHy+oQICcENAWYBT4deS8jrFv1InsRT6lg4pEGQvCx+fZbdAe5HRp5 +# cAlIVYapHXxDBQvAbpMoMMi0TertRaBx9t3bWWLGguBUR92ra8a3HFOt7KHT04Xa +# 7JgLsQoTUTlavt67fWUABX5pSVwP25v9IXUSclgz5l1xXrBHTmZPGRsjBu881s+M +# 168vsc6P9RW8JjsKwDajs2oWkRY3F68wb7xLa2ZQ4X5koYIS7jCCEuoGCisGAQQB +# gjcDAwExghLaMIIS1gYJKoZIhvcNAQcCoIISxzCCEsMCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIHikZhYxRsySx5WAopqolokj5LdR1qw/Qj19bR6x +# FpA5AgZfYPebj+kYEzIwMjAwOTIyMjIxOTUxLjQ1NVowBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjo2MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkEwggT1MIID3aADAgECAhMzAAABJt+6SyK5goIHAAAA +# AAEmMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2MEJD +# LUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ4wvoacTvMNlXQTtfF/ +# Cx5Ol3X0fcjUNMvjLgTmO5+WHYJFbp725P3+qvFKDRQHWEI1Sz0gB24urVDIjXjB +# h5NVNJVMQJI2tltv7M4/4IbhZJb3xzQW7LolEoZYUZanBTUuyly9osCg4o5joViT +# 2GtmyxK+Fv5kC20l2opeaeptd/E7ceDAFRM87hiNCsK/KHyC+8+swnlg4gTOey6z +# QqhzgNsG6HrjLBuDtDs9izAMwS2yWT0T52QA9h3Q+B1C9ps2fMKMe+DHpG+0c61D +# 94Yh6cV2XHib4SBCnwIFZAeZE2UJ4qPANSYozI8PH+E5rCT3SVqYvHou97HsXvP2 +# I3MCAwEAAaOCARswggEXMB0GA1UdDgQWBBRJq6wfF7B+mEKN0VimX8ajNA5hQTAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBAlvudaOlv9Cfzv56bnX41czF6tLtH +# LB46l6XUch+qNN45ZmOTFwLot3JjwSrn4oycQ9qTET1TFDYd1QND0LiXmKz9OqBX +# ai6S8XdyCQEZvfL82jIAs9pwsAQ6XvV9jNybPStRgF/sOAM/Deyfmej9Tg9FcRwX +# ank2qgzdZZNb8GoEze7f1orcTF0Q89IUXWIlmwEwQFYF1wjn87N4ZxL9Z/xA2m/R +# 1zizFylWP/mpamCnVfZZLkafFLNUNVmcvc+9gM7vceJs37d3ydabk4wR6ObR34sW +# aLppmyPlsI1Qq5Lu6bJCWoXzYuWpkoK6oEep1gML6SRC3HKVS3UscZhtMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYICzzCCAjgCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo2 +# MEJDLUUzODMtMjYzNTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUACmcyOWmZxErpq06B8dy6oMZ6//yggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMUsDowIhgPMjAyMDA5MjIyMTE3NDZaGA8yMDIwMDkyMzIxMTc0NlowdDA6Bgor +# BgEEAYRZCgQBMSwwKjAKAgUA4xSwOgIBADAHAgEAAgII+zAHAgEAAgIRDTAKAgUA +# 4xYBugIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIBAAID +# B6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAGFtTfLVXgP5nTlAW4+t +# qfeH51GOeG4NIbJTmRivKyHJM/AjTJCfaazyngryVGsVed/19YBgxoB07IS1Jlmm +# KNq9XofCcAXBUcoBsPLnGHr8vBVOpAPDLDYgr7ME8Qgofa5CjOmCQMYZyjsW2q5j +# 2OkaGPVN1YtLAZq82zgDGI+QMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgUENBIDIwMTACEzMAAAEm37pLIrmCggcAAAAAASYwDQYJYIZIAWUDBAIB +# BQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0BCQQx +# IgQgTg7YbUxqwEPryPvBnm6tKeHXdMGcW0dFqzF8v32xEgQwgfoGCyqGSIb3DQEJ +# EAIvMYHqMIHnMIHkMIG9BCA2/c/vnr1ecAzvapOWZ2xGfAkzrkfpGcrvMW07CQl1 +# DzCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAw +# DgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24x +# JjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAABJt+6 +# SyK5goIHAAAAAAEmMCIEILioZM02FaPhX5O1/7sR3BeH9w6NUS1x/c+Q88996cSH +# MA0GCSqGSIb3DQEBCwUABIIBAEZEbtwLgdlFNIt4umG1gw1nrQVpGXIkyojlPtcb +# hQu757SIEuY7tS0MmwFimKjHmCbeVS12PJ1PHjxXs8YmpOusFH9wtCgPPHHm3IEx +# naynU0x6jl4MXGtmTIF5E8iHA1y0YeSoeeinaez2Lc78G+jHN6uzf+TJozwpqkle +# aZIVhuqPAqJZvfVDT3Bd7N4J2j4D6H0lxJ+yn/ENLkNEl4jVMutEMDQuDSikB8/V +# hpcMRx83R4M8gEIrFIORJVwqT5axQI5C9HQuWFB9xyTvU5Q/OaU4ybRj5jUTPNZs +# 3/emFZ3aTnt8arfvfztIvFw2NbnM6rIPVjm4YDT9YUD2xNM= +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.LocalizationHelper/PowerShellGet.LocalizationHelper.psm1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.LocalizationHelper/PowerShellGet.LocalizationHelper.psm1 index ffc5003528..5ab787a096 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.LocalizationHelper/PowerShellGet.LocalizationHelper.psm1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.LocalizationHelper/PowerShellGet.LocalizationHelper.psm1 @@ -1,443 +1,443 @@ -<# - .SYNOPSIS - Creates and throws an invalid argument exception. - - .PARAMETER Message - The message explaining why this error is being thrown. - - .PARAMETER ArgumentName - The name of the invalid argument that is causing this error to be thrown. -#> -function New-InvalidArgumentException { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ArgumentName - ) - - $argumentException = New-Object -TypeName 'ArgumentException' ` - -ArgumentList @($Message, $ArgumentName) - - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @($argumentException, $ArgumentName, 'InvalidArgument', $null) - } - - $errorRecord = New-Object @newObjectParameters - - throw $errorRecord -} - -<# - .SYNOPSIS - Creates and throws an invalid operation exception. - - .PARAMETER Message - The message explaining why this error is being thrown. - - .PARAMETER ErrorRecord - The error record containing the exception that is causing this terminating error. -#> -function New-InvalidOperationException { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [Parameter()] - [ValidateNotNull()] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - if ($null -eq $ErrorRecord) { - $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` - -ArgumentList @($Message) - } - else { - $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` - -ArgumentList @($Message, $ErrorRecord.Exception) - } - - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( - $invalidOperationException.ToString(), - 'MachineStateIncorrect', - 'InvalidOperation', - $null - ) - } - - $errorRecordToThrow = New-Object @newObjectParameters - - throw $errorRecordToThrow -} - -<# - .SYNOPSIS - Creates and throws an object not found exception. - - .PARAMETER Message - The message explaining why this error is being thrown. - - .PARAMETER ErrorRecord - The error record containing the exception that is causing this terminating error. -#> -function New-ObjectNotFoundException { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [Parameter()] - [ValidateNotNull()] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - if ($null -eq $ErrorRecord) { - $exception = New-Object -TypeName 'System.Exception' ` - -ArgumentList @($Message) - } - else { - $exception = New-Object -TypeName 'System.Exception' ` - -ArgumentList @($Message, $ErrorRecord.Exception) - } - - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( - $exception.ToString(), - 'MachineStateIncorrect', - 'ObjectNotFound', - $null - ) - } - - $errorRecordToThrow = New-Object @newObjectParameters - - throw $errorRecordToThrow -} - -<# - .SYNOPSIS - Creates and throws an invalid result exception. - - .PARAMETER Message - The message explaining why this error is being thrown. - - .PARAMETER ErrorRecord - The error record containing the exception that is causing this terminating error. -#> -function New-InvalidResultException { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Message, - - [Parameter()] - [ValidateNotNull()] - [System.Management.Automation.ErrorRecord] - $ErrorRecord - ) - - if ($null -eq $ErrorRecord) { - $exception = New-Object -TypeName 'System.Exception' ` - -ArgumentList @($Message) - } - else { - $exception = New-Object -TypeName 'System.Exception' ` - -ArgumentList @($Message, $ErrorRecord.Exception) - } - - $newObjectParameters = @{ - TypeName = 'System.Management.Automation.ErrorRecord' - ArgumentList = @( - $exception.ToString(), - 'MachineStateIncorrect', - 'InvalidResult', - $null - ) - } - - $errorRecordToThrow = New-Object @newObjectParameters - - throw $errorRecordToThrow -} - -<# - .SYNOPSIS - Retrieves the localized string data based on the machine's culture. - Falls back to en-US strings if the machine's culture is not supported. - - .PARAMETER ResourceName - The name of the resource as it appears before '.strings.psd1' of the localized string file. - For example: - For WindowsOptionalFeature: MSFT_WindowsOptionalFeature - For Service: MSFT_ServiceResource - For Registry: MSFT_RegistryResource - For Helper: SqlServerDscHelper - - .PARAMETER ScriptRoot - Optional. The root path where to expect to find the culture folder. This is only needed - for localization in helper modules. This should not normally be used for resources. -#> -function Get-LocalizedData { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ResourceName, - - [Parameter()] - [ValidateNotNullOrEmpty()] - [System.String] - $ScriptRoot - ) - - if ( -not $ScriptRoot ) { - $resourceDirectory = Join-Path -Path $PSScriptRoot -ChildPath $ResourceName - $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture - } - else { - $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath $PSUICulture - } - - if (-not (Test-Path -Path $localizedStringFileLocation)) { - # Fallback to en-US - if ( -not $ScriptRoot ) { - $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' - } - else { - $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath 'en-US' - } - } - - Import-LocalizedData ` - -BindingVariable 'localizedData' ` - -FileName "$ResourceName.strings.psd1" ` - -BaseDirectory $localizedStringFileLocation - - return $localizedData -} - -Export-ModuleMember -Function @( - 'New-InvalidArgumentException', - 'New-InvalidOperationException', - 'New-ObjectNotFoundException', - 'New-InvalidResultException', - 'Get-LocalizedData' -) - -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCjPo12DZaAMOkK -# EoYmV1li2n5EmeiwcqQzHkVYm5pRSqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg2UNl19jp -# ZkbG4N4XLYPrxRhH3aoWga/DT446FyrX8kUwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQBRCB3fJQ7H+bgUBGyRzfnLylN75t/0j3IsTW+XQtm1 -# d/Sjzt0SMKSEo5OHh910EmAVWdld50KeAHRY3uQ1mWTBHRUaHNTxJg5kDfPPhMGy -# q2PxhW6oQStS1SYUacVtJByF4XWHFSXezrJ7NJn0S9mRiC0dA8pSY4eQ+NuAIpMU -# VvXeeb1/OrBXeghR6oagGfVlfEnH2HR6W9RQi8+9qzXFNZRfx9YewEcNsV21iA85 -# QK1J9X/StSqGw1T9Zj5g8bDYLY6x876yrOGNV22NkLVh12bpifZ550iO4LPdEm/e -# 29md4h0srONw1+Ov/I68VDm8tEuMPtkbUIamCfFmmUB7oYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEINFMLSXQQ6FisqDCVPG1elrJ+Eq+frhsWlbuloDJ -# TRWiAgZfYPq2eAgYEzIwMjAwOTIyMjIxOTUxLjA0OFowBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjo3ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABKKAOgeE21U/CAAAA -# AAEoMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTUwMFoXDTIxMDMxNzAxMTUwMFowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3ODgw -# LUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ2Rsdb3VNuGPs2/Dgpc -# 9gt77LG0JPkD4VWTlEJLkqznTJl+RoZfiOwN6iWfPu4k/kj8nwY7pvLs1OsBy494 -# yusg4rHLwHNUJPtw1Tc54MOLgdcosA4Nxki73fDyqWwDtjOdk6H7kNczBPqADD6B -# 98ot77/wSACBJIxm9qAUudquS5fczCF0++aWUavDu46U3cv6HEjIdV2ZdJTUKg4W -# UIdTYMQXI082+qSs45WBZjcK98/tIfx8uq8q8ksWF9+zUjGTFiMaKHhn7cSCoEj7 -# E1tVmW08ISpS678WFP2+A0OQwaWcJKNACK+J+La7Lz2bGupCidOGz5XDewc1lD9n -# LPcCAwEAAaOCARswggEXMB0GA1UdDgQWBBSE4vKD8X61N5vUAcNOdH9QBMum8jAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCLX2ZHGIULgDk/iccHWUywjDyAsBHl -# hkmtmBp4lldwL3dNo0bXZZHiSZB+c2KzvPqY64BlECjS/Pqur2m9UaT1N0BeUowR -# HQT88wdzd94gYqKXmLDbVR8yeVgBkcP/JiVWbXdQzcz1ETHgWrh+uzA8BwUgAaHJ -# w+nXYccIuDgPJM1UTeNl9R5Ovf+6zR2E5ZI4DrIqvS4jH4QsoMPTn27AjN7VZt4a -# moRxMLEcQAS7vPT1JUUaRFpFHmkUYVln1YMsw///6968aRvy3cmClS44uxkkaILb -# hh1h09ejZjHhrEn+k9McVkWiuY724jJ/57tylM7A/jzIWNj1F8VlhkyyMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3 -# ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUAMT1LG/KAEj0XsiL9n7mxmX1afZuggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMUs1YwIhgPMjAyMDA5MjIyMTMxMDJaGA8yMDIwMDkyMzIxMzEwMlowdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA4xSzVgIBADAKAgEAAgIhfwIB/zAHAgEAAgIRnTAK -# AgUA4xYE1gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBACCIjMz039kePvi0 -# +QSEAAGe16AJMB2ZvhrAKxWUoyZpnRrWDZizu7wON92D59Wr+LVSvcGrHYflMWt0 -# KGA+A/i0fhPG8HWUfiRkGSg5MY5jl8DyE6MMkbWEDwmvZkzuIiQ3/4IsaRgc7vLI -# DM10XS4g59T/1aPBaVnQCn2i8+D+MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEooA6B4TbVT8IAAAAAASgwDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQgEnw4Ge50R6cM9Hq5+HXFUxJ9mTDd+r571wbhdC3OQJkwgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCC8RWqLrwVSd+/cGxDfBqS4b1tPXhoPFrC615vV -# 1ugU2jCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# KKAOgeE21U/CAAAAAAEoMCIEIETuk5SkV1/sElaASDGqqK9PNF5z7kvwZ0c4bkPK -# DQQkMA0GCSqGSIb3DQEBCwUABIIBAJnsZe3Pa/JdSEJL3mXUC7341+wXLrKreqxn -# K1RdfgfUitNX5Kv7kxzUrm81hN3TMIjQwj7o2sqdGuQIThRoTCWIBaw4oPafeFsV -# LWaz9BvhRgRjsu36ofO57eS2JlwzZi/zMIWF3G9931glwdTYRhmcDqrOXwVTyno5 -# dWqxg36RI3B2DIqNMGDrVBwHbwKQmfRXuZqvw+efCFlQyktjCo0j4ngZLNw2v9J3 -# 6S0AXqHMk7M/Wipf/qbp/A11CcUqZ4LnAtiF2vBVyFqrvTGWfKtG+xb7A8aP+rSq -# YpqvL27egErpjjIS1YaF0UF5voxrJqp4POcWLJJ/y88oQgBMoiY= -# SIG # End signature block +<# + .SYNOPSIS + Creates and throws an invalid argument exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ArgumentName + The name of the invalid argument that is causing this error to be thrown. +#> +function New-InvalidArgumentException { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ArgumentName + ) + + $argumentException = New-Object -TypeName 'ArgumentException' ` + -ArgumentList @($Message, $ArgumentName) + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @($argumentException, $ArgumentName, 'InvalidArgument', $null) + } + + $errorRecord = New-Object @newObjectParameters + + throw $errorRecord +} + +<# + .SYNOPSIS + Creates and throws an invalid operation exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error. +#> +function New-InvalidOperationException { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) { + $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` + -ArgumentList @($Message) + } + else { + $invalidOperationException = New-Object -TypeName 'InvalidOperationException' ` + -ArgumentList @($Message, $ErrorRecord.Exception) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $invalidOperationException.ToString(), + 'MachineStateIncorrect', + 'InvalidOperation', + $null + ) + } + + $errorRecordToThrow = New-Object @newObjectParameters + + throw $errorRecordToThrow +} + +<# + .SYNOPSIS + Creates and throws an object not found exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error. +#> +function New-ObjectNotFoundException { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message) + } + else { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message, $ErrorRecord.Exception) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $exception.ToString(), + 'MachineStateIncorrect', + 'ObjectNotFound', + $null + ) + } + + $errorRecordToThrow = New-Object @newObjectParameters + + throw $errorRecordToThrow +} + +<# + .SYNOPSIS + Creates and throws an invalid result exception. + + .PARAMETER Message + The message explaining why this error is being thrown. + + .PARAMETER ErrorRecord + The error record containing the exception that is causing this terminating error. +#> +function New-InvalidResultException { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Message, + + [Parameter()] + [ValidateNotNull()] + [System.Management.Automation.ErrorRecord] + $ErrorRecord + ) + + if ($null -eq $ErrorRecord) { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message) + } + else { + $exception = New-Object -TypeName 'System.Exception' ` + -ArgumentList @($Message, $ErrorRecord.Exception) + } + + $newObjectParameters = @{ + TypeName = 'System.Management.Automation.ErrorRecord' + ArgumentList = @( + $exception.ToString(), + 'MachineStateIncorrect', + 'InvalidResult', + $null + ) + } + + $errorRecordToThrow = New-Object @newObjectParameters + + throw $errorRecordToThrow +} + +<# + .SYNOPSIS + Retrieves the localized string data based on the machine's culture. + Falls back to en-US strings if the machine's culture is not supported. + + .PARAMETER ResourceName + The name of the resource as it appears before '.strings.psd1' of the localized string file. + For example: + For WindowsOptionalFeature: MSFT_WindowsOptionalFeature + For Service: MSFT_ServiceResource + For Registry: MSFT_RegistryResource + For Helper: SqlServerDscHelper + + .PARAMETER ScriptRoot + Optional. The root path where to expect to find the culture folder. This is only needed + for localization in helper modules. This should not normally be used for resources. +#> +function Get-LocalizedData { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ResourceName, + + [Parameter()] + [ValidateNotNullOrEmpty()] + [System.String] + $ScriptRoot + ) + + if ( -not $ScriptRoot ) { + $resourceDirectory = Join-Path -Path $PSScriptRoot -ChildPath $ResourceName + $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath $PSUICulture + } + else { + $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath $PSUICulture + } + + if (-not (Test-Path -Path $localizedStringFileLocation)) { + # Fallback to en-US + if ( -not $ScriptRoot ) { + $localizedStringFileLocation = Join-Path -Path $resourceDirectory -ChildPath 'en-US' + } + else { + $localizedStringFileLocation = Join-Path -Path $ScriptRoot -ChildPath 'en-US' + } + } + + Import-LocalizedData ` + -BindingVariable 'localizedData' ` + -FileName "$ResourceName.strings.psd1" ` + -BaseDirectory $localizedStringFileLocation + + return $localizedData +} + +Export-ModuleMember -Function @( + 'New-InvalidArgumentException', + 'New-InvalidOperationException', + 'New-ObjectNotFoundException', + 'New-InvalidResultException', + 'Get-LocalizedData' +) + +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCjPo12DZaAMOkK +# EoYmV1li2n5EmeiwcqQzHkVYm5pRSqCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg2UNl19jp +# ZkbG4N4XLYPrxRhH3aoWga/DT446FyrX8kUwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQBRCB3fJQ7H+bgUBGyRzfnLylN75t/0j3IsTW+XQtm1 +# d/Sjzt0SMKSEo5OHh910EmAVWdld50KeAHRY3uQ1mWTBHRUaHNTxJg5kDfPPhMGy +# q2PxhW6oQStS1SYUacVtJByF4XWHFSXezrJ7NJn0S9mRiC0dA8pSY4eQ+NuAIpMU +# VvXeeb1/OrBXeghR6oagGfVlfEnH2HR6W9RQi8+9qzXFNZRfx9YewEcNsV21iA85 +# QK1J9X/StSqGw1T9Zj5g8bDYLY6x876yrOGNV22NkLVh12bpifZ550iO4LPdEm/e +# 29md4h0srONw1+Ov/I68VDm8tEuMPtkbUIamCfFmmUB7oYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEINFMLSXQQ6FisqDCVPG1elrJ+Eq+frhsWlbuloDJ +# TRWiAgZfYPq2eAgYEzIwMjAwOTIyMjIxOTUxLjA0OFowBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjo3ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABKKAOgeE21U/CAAAA +# AAEoMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTUwMFoXDTIxMDMxNzAxMTUwMFowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3ODgw +# LUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ2Rsdb3VNuGPs2/Dgpc +# 9gt77LG0JPkD4VWTlEJLkqznTJl+RoZfiOwN6iWfPu4k/kj8nwY7pvLs1OsBy494 +# yusg4rHLwHNUJPtw1Tc54MOLgdcosA4Nxki73fDyqWwDtjOdk6H7kNczBPqADD6B +# 98ot77/wSACBJIxm9qAUudquS5fczCF0++aWUavDu46U3cv6HEjIdV2ZdJTUKg4W +# UIdTYMQXI082+qSs45WBZjcK98/tIfx8uq8q8ksWF9+zUjGTFiMaKHhn7cSCoEj7 +# E1tVmW08ISpS678WFP2+A0OQwaWcJKNACK+J+La7Lz2bGupCidOGz5XDewc1lD9n +# LPcCAwEAAaOCARswggEXMB0GA1UdDgQWBBSE4vKD8X61N5vUAcNOdH9QBMum8jAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCLX2ZHGIULgDk/iccHWUywjDyAsBHl +# hkmtmBp4lldwL3dNo0bXZZHiSZB+c2KzvPqY64BlECjS/Pqur2m9UaT1N0BeUowR +# HQT88wdzd94gYqKXmLDbVR8yeVgBkcP/JiVWbXdQzcz1ETHgWrh+uzA8BwUgAaHJ +# w+nXYccIuDgPJM1UTeNl9R5Ovf+6zR2E5ZI4DrIqvS4jH4QsoMPTn27AjN7VZt4a +# moRxMLEcQAS7vPT1JUUaRFpFHmkUYVln1YMsw///6968aRvy3cmClS44uxkkaILb +# hh1h09ejZjHhrEn+k9McVkWiuY724jJ/57tylM7A/jzIWNj1F8VlhkyyMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo3 +# ODgwLUUzOTAtODAxNDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAMT1LG/KAEj0XsiL9n7mxmX1afZuggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMUs1YwIhgPMjAyMDA5MjIyMTMxMDJaGA8yMDIwMDkyMzIxMzEwMlowdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA4xSzVgIBADAKAgEAAgIhfwIB/zAHAgEAAgIRnTAK +# AgUA4xYE1gIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBACCIjMz039kePvi0 +# +QSEAAGe16AJMB2ZvhrAKxWUoyZpnRrWDZizu7wON92D59Wr+LVSvcGrHYflMWt0 +# KGA+A/i0fhPG8HWUfiRkGSg5MY5jl8DyE6MMkbWEDwmvZkzuIiQ3/4IsaRgc7vLI +# DM10XS4g59T/1aPBaVnQCn2i8+D+MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEooA6B4TbVT8IAAAAAASgwDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQgEnw4Ge50R6cM9Hq5+HXFUxJ9mTDd+r571wbhdC3OQJkwgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCC8RWqLrwVSd+/cGxDfBqS4b1tPXhoPFrC615vV +# 1ugU2jCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# KKAOgeE21U/CAAAAAAEoMCIEIETuk5SkV1/sElaASDGqqK9PNF5z7kvwZ0c4bkPK +# DQQkMA0GCSqGSIb3DQEBCwUABIIBAJnsZe3Pa/JdSEJL3mXUC7341+wXLrKreqxn +# K1RdfgfUitNX5Kv7kxzUrm81hN3TMIjQwj7o2sqdGuQIThRoTCWIBaw4oPafeFsV +# LWaz9BvhRgRjsu36ofO57eS2JlwzZi/zMIWF3G9931glwdTYRhmcDqrOXwVTyno5 +# dWqxg36RI3B2DIqNMGDrVBwHbwKQmfRXuZqvw+efCFlQyktjCo0j4ngZLNw2v9J3 +# 6S0AXqHMk7M/Wipf/qbp/A11CcUqZ4LnAtiF2vBVyFqrvTGWfKtG+xb7A8aP+rSq +# YpqvL27egErpjjIS1YaF0UF5voxrJqp4POcWLJJ/y88oQgBMoiY= +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 index 1c85f76ada..ec67e9f110 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/PowerShellGet.ResourceHelper.psm1 @@ -1,570 +1,570 @@ -# -# Copyright (c) Microsoft Corporation. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# - -<# - Helper functions for PowerShellGet DSC Resources. -#> - -# Import localization helper functions. -$helperName = 'PowerShellGet.LocalizationHelper' -$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent -$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" -Import-Module -Name $dscResourcesFolderFilePath - -# Import Localization Strings -$script:localizedData = Get-LocalizedData -ResourceName 'PowerShellGet.ResourceHelper' -ScriptRoot $PSScriptRoot - -<# - .SYNOPSIS - This is a helper function that extract the parameters from a given table. - - .PARAMETER FunctionBoundParameters - Specifies the hash table containing a set of parameters to be extracted. - - .PARAMETER ArgumentNames - Specifies a list of arguments you want to extract. -#> -function New-SplatParameterHashTable { - [CmdletBinding()] - [OutputType([System.Collections.Hashtable])] - param - ( - [Parameter(Mandatory = $true)] - [System.Collections.Hashtable] - $FunctionBoundParameters, - - [Parameter(Mandatory = $true)] - [System.String[]] - $ArgumentNames - ) - - Write-Verbose -Message ($script:localizedData.CallingFunction -f $($MyInvocation.MyCommand)) - - $returnValue = @{} - - foreach ($arg in $ArgumentNames) { - if ($FunctionBoundParameters.ContainsKey($arg)) { - # Found an argument we are looking for, so we add it to return collection. - $returnValue.Add($arg, $FunctionBoundParameters[$arg]) - } - } - - return $returnValue -} - -<# - .SYNOPSIS - This is a helper function that validate that a value is correct and used correctly. - - .PARAMETER Value - Specifies the value to be validated. - - .PARAMETER Type - Specifies the type of argument. - - .PARAMETER Type - Specifies the name of the provider. - - .OUTPUTS - None. Throws an error if the test fails. -#> -function Test-ParameterValue { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Value, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $Type, - - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] - $ProviderName - ) - - Write-Verbose -Message ($script:localizedData.CallingFunction -f $($MyInvocation.MyCommand)) - - switch ($Type) { - 'SourceUri' { - # Checks whether given URI represents specific scheme - # Most common schemes: file, http, https, ftp - $scheme = @('http', 'https', 'file', 'ftp') - - $newUri = $Value -as [System.URI] - $returnValue = ($newUri -and $newUri.AbsoluteURI -and ($scheme -icontains $newUri.Scheme)) - - if ($returnValue -eq $false) { - $errorMessage = $script:localizedData.InValidUri -f $Value - New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage - } - } - - 'DestinationPath' { - $returnValue = Test-Path -Path $Value - - if ($returnValue -eq $false) { - $errorMessage = $script:localizedData.PathDoesNotExist -f $Value - New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage - } - } - - 'PackageSource' { - # Value can be either the package source Name or source Uri. - - # Check if the source is a Uri. - $uri = $Value -as [System.URI] - - if ($uri -and $uri.AbsoluteURI) { - # Check if it's a valid Uri. - Test-ParameterValue -Value $Value -Type 'SourceUri' -ProviderName $ProviderName - } - else { - # Check if it's a registered package source name. - $source = PackageManagement\Get-PackageSource -Name $Value -ProviderName $ProviderName -ErrorVariable ev - - if ((-not $source) -or $ev) { - # We do not need to throw error here as Get-PackageSource does already. - Write-Verbose -Message ($script:localizedData.SourceNotFound -f $source) - } - } - } - - default { - $errorMessage = $script:localizedData.UnexpectedArgument -f $Type - New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage - } - } -} - -<# - .SYNOPSIS - This is a helper function that does the version validation. - - .PARAMETER RequiredVersion - Provides the required version. - - .PARAMETER MaximumVersion - Provides the maximum version. - - .PARAMETER MinimumVersion - Provides the minimum version. -#> -function Test-VersionParameter { - [CmdletBinding()] - param - ( - [Parameter()] - [System.String] - $RequiredVersion, - - [Parameter()] - [System.String] - $MinimumVersion, - - [Parameter()] - [System.String] - $MaximumVersion - ) - - Write-Verbose -Message ($localizedData.CallingFunction -f $($MyInvocation.MyCommand)) - - $isValid = $false - - # Case 1: No further check required if a user provides either none or one of these: minimumVersion, maximumVersion, and requiredVersion. - if ($PSBoundParameters.Count -le 1) { - return $true - } - - # Case 2: #If no RequiredVersion is provided. - if (-not $PSBoundParameters.ContainsKey('RequiredVersion')) { - # If no RequiredVersion, both MinimumVersion and MaximumVersion are provided. Otherwise fall into the Case #1. - $isValid = $PSBoundParameters['MinimumVersion'] -le $PSBoundParameters['MaximumVersion'] - } - - # Case 3: RequiredVersion is provided. - # In this case MinimumVersion and/or MaximumVersion also are provided. Otherwise fall in to Case #1. - # This is an invalid case. When RequiredVersion is provided, others are not allowed. so $isValid is false, which is already set in the init. - - if ($isValid -eq $false) { - $errorMessage = $script:localizedData.VersionError - New-InvalidArgumentException ` - -ArgumentName 'RequiredVersion, MinimumVersion or MaximumVersion' ` - -Message $errorMessage - } -} - -<# - .SYNOPSIS - This is a helper function that retrieves the InstallationPolicy from the given repository. - - .PARAMETER RepositoryName - Provides the repository Name. -#> -function Get-InstallationPolicy { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [ValidateNotNullOrEmpty()] - [System.String] $RepositoryName - ) - - Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.MyCommand)) - - $repositoryObject = PackageManagement\Get-PackageSource -Name $RepositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue - - if ($repositoryObject) { - return $repositoryObject.IsTrusted - } -} - -<# - .SYNOPSIS - This method is used to compare current and desired values for any DSC resource. - - .PARAMETER CurrentValues - This is hash table of the current values that are applied to the resource. - - .PARAMETER DesiredValues - This is a PSBoundParametersDictionary of the desired values for the resource. - - .PARAMETER ValuesToCheck - This is a list of which properties in the desired values list should be checked. - If this is empty then all values in DesiredValues are checked. -#> -function Test-DscParameterState { - [CmdletBinding()] - param - ( - [Parameter(Mandatory = $true)] - [System.Collections.Hashtable] - $CurrentValues, - - [Parameter(Mandatory = $true)] - [System.Object] - $DesiredValues, - - [Parameter()] - [System.Array] - $ValuesToCheck - ) - - $returnValue = $true - - if (($DesiredValues.GetType().Name -ne 'HashTable') ` - -and ($DesiredValues.GetType().Name -ne 'CimInstance') ` - -and ($DesiredValues.GetType().Name -ne 'PSBoundParametersDictionary')) { - $errorMessage = $script:localizedData.PropertyTypeInvalidForDesiredValues -f $($DesiredValues.GetType().Name) - New-InvalidArgumentException -ArgumentName 'DesiredValues' -Message $errorMessage - } - - if (($DesiredValues.GetType().Name -eq 'CimInstance') -and ($null -eq $ValuesToCheck)) { - $errorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck - New-InvalidArgumentException -ArgumentName 'ValuesToCheck' -Message $errorMessage - } - - if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) { - $keyList = $DesiredValues.Keys - } - else { - $keyList = $ValuesToCheck - } - - $keyList | ForEach-Object -Process { - if (($_ -ne 'Verbose')) { - if (($CurrentValues.ContainsKey($_) -eq $false) ` - -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` - -or (($DesiredValues.GetType().Name -ne 'CimInstance' -and $DesiredValues.ContainsKey($_) -eq $true) -and ($null -ne $DesiredValues.$_ -and $DesiredValues.$_.GetType().IsArray))) { - if ($DesiredValues.GetType().Name -eq 'HashTable' -or ` - $DesiredValues.GetType().Name -eq 'PSBoundParametersDictionary') { - $checkDesiredValue = $DesiredValues.ContainsKey($_) - } - else { - # If DesiredValue is a CimInstance. - $checkDesiredValue = $false - if (([System.Boolean]($DesiredValues.PSObject.Properties.Name -contains $_)) -eq $true) { - if ($null -ne $DesiredValues.$_) { - $checkDesiredValue = $true - } - } - } - - if ($checkDesiredValue) { - $desiredType = $DesiredValues.$_.GetType() - $fieldName = $_ - if ($desiredType.IsArray -eq $true) { - if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` - -or ($null -eq $CurrentValues.$fieldName)) { - Write-Verbose -Message ($script:localizedData.PropertyValidationError -f $fieldName) -Verbose - - $returnValue = $false - } - else { - $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` - -DifferenceObject $DesiredValues.$fieldName - if ($null -ne $arrayCompare) { - Write-Verbose -Message ($script:localizedData.PropertiesDoesNotMatch -f $fieldName) -Verbose - - $arrayCompare | ForEach-Object -Process { - Write-Verbose -Message ($script:localizedData.PropertyThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose - } - - $returnValue = $false - } - } - } - else { - switch ($desiredType.Name) { - 'String' { - if (-not [System.String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` - -not [System.String]::IsNullOrEmpty($DesiredValues.$fieldName)) { - Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` - -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose - - $returnValue = $false - } - } - - 'Int32' { - if (-not ($DesiredValues.$fieldName -eq 0) -or ` - -not ($null -eq $CurrentValues.$fieldName)) { - Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` - -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose - - $returnValue = $false - } - } - - { $_ -eq 'Int16' -or $_ -eq 'UInt16'} { - if (-not ($DesiredValues.$fieldName -eq 0) -or ` - -not ($null -eq $CurrentValues.$fieldName)) { - Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` - -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose - - $returnValue = $false - } - } - - default { - Write-Warning -Message ($script:localizedData.UnableToCompareProperty ` - -f $fieldName, $desiredType.Name) - - $returnValue = $false - } - } - } - } - } - } - } - - return $returnValue -} - -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBH3QB1YDOZOOwJ -# V+KQVBF6PeRCZ7EOOlwmL67tD15ypKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgAchP/Cco -# Y2JAjut9Hy3IC65/Rf67ea66bxFJosTcn0EwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQCA7tKfqWfEy22S/6wr4OyvRkvECIOlgOTLpZGyP7iT -# 16UqNis22r/tHfj0nQMVembrz5WOUvHZPrU72+fJAZqMdCB5H0wHmeBOmie166xn -# CDvlSHtJr5sPZeRDNQFeJN2nN+I47eFwThr5ndx6Gp548DoyDWBnFQz2tgvVpIWA -# TBFrWf8ZulTYcMH3QhzlgEmmgEsU2vT5nbBmmK2QICDzcWA3tIzHSQ7jQxqL+9Oj -# 9EVvuJTf0e81f05xNUmunrwWuoHzIYOF7LmpZqWFp4m8rg5Davtum5Rq60TcofSH -# gKzh10SD1+hAHm7/aqz640BMXsMJCBHofYDzXY0Un7sWoYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIJeyBGsDoIyvzKdclhGjO92lMFh4eXEiupSWXMgA -# yrYhAgZfYQdYOoMYEzIwMjAwOTIyMjIxOTUwLjk4M1owBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjpGODdBLUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABL7GnF3lWlBeHAAAA -# AAEvMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTUwNloXDTIxMDMxNzAxMTUwNlowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGODdB -# LUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKh8VkvVIwXD8sn0zT2n -# EdyEZ9UNHY7ACbOZA4obAHvD1hauw9K1Z2lRWG+m8Ars9l35GoMXdPgshM3hZKQW -# fhrLnF9/GDZoilhc2LhMqNPXs06rAJ8YODB6i0Cg1CFCYnyOYvywXKY3xGJN09Dg -# PXWfczEm2P/a3rmrXMrK5EFc3ahxrC51c+UuAMKV9xJyzJVLShPwPBJl+CjdMDPJ -# f24DZXIYec3gCN2xean1DFCI0gaqJprMeL4Om1KY2AZMIgBPEkoY1N7AI5e7ybkI -# L8+Mz3inijb4rDTkXk86ztUwy4bdc1MyKe2j2odT+QIDA2+M8cMTIGlKn7EyD2NN -# XU8CAwEAAaOCARswggEXMB0GA1UdDgQWBBSml/VRpBNFkAMDiqcoqWi85j/qljAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQB4q6ilv2SlGvJD/7dbfoIKZBO2i6YA -# wckw57TpCrt2+SAx2dcF7JvRMCPhLCSgqjyNcJRs40cEXPbLdzZMJHzcv73AF7L6 -# mWZXg2aBjG1Sc5qM4jjE/nwIX+C6/odm5/asU4JIlFCuUZjzqdir18HkRVQve2Hw -# V0lCXHQs+V3m9DyyA9b6LSIk3GOFZu7F11Wyx/5dVXisPPTPwh9JXfMD9W173M1+ -# ZZycmO03lUc4G1FilgpxWNdgWn/DO9ZhoW5yN6+BUddnJ4cCcCjcg8sB5rktPP8p -# VZAQ7aUqkAeqo+FuCkAUAdJRESCpR5wgSPtVvFPMjONE36DbKtfzkfiHMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpG -# ODdBLUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUAM/CZCUpclQ9qfr/r3y9osIIPSmSggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMUwAMwIhgPMjAyMDA5MjIyMjI1MDdaGA8yMDIwMDkyMzIyMjUwN1owdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA4xTAAwIBADAKAgEAAgIeewIB/zAHAgEAAgIQ0jAK -# AgUA4xYRgwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAB7Ib0ORN4cnD/0V -# iEDrzl7vxjx6981A8Z/8I+X0n7qQHdeGFJcXGEEO5pDD62VMfd28ZrxPWgMd0Chx -# mj5EdvR3U6+gRYzyYY5Vi5srADcWf3pMoYsEQhZuYeohtFQuYFJt2t26GskSlEy3 -# qXFkFqIoBCjz1SsmfA5nTtE2zXHSMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEvsacXeVaUF4cAAAAAAS8wDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQgDY7vkk/uubZSKGprOnZ2WnFZqhRkEJ7KVJ5R6hogo/AwgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCBC5RecGZvugnvVXg80zlrGv1uV35LNk+H9dBj3 -# ChFPvTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# L7GnF3lWlBeHAAAAAAEvMCIEIAt7o8jjQ4zf04GvFGrnuSSwfsToGMPMzYw+3a3k -# vP0dMA0GCSqGSIb3DQEBCwUABIIBABIE9hcJhxcSYL9zQrK7AunfYI2qZBNrD9dB -# FIYwBnLNOYAPgpjC5Nxrp/4fSrWPyWFkXzwB6rjofda3zWpOywWKMc4lK0yHRYrO -# 7r0FmXd9zxdaJKpbHpc8xG2vb6pI9dh/eXFGNS8KRu+oKZFX2/l5IxRAz4QRMxq6 -# /heHcZpWjeIknMhP+bMAqaMV9h3jEojCli0hdwawbTpq8Oyvo6wlwNqLvmtBc7Xs -# 8of9f6I/zTCn6yhbyDEVXxPhn3qjrQzi790XXe3xvmAKkkEW/NO2ZOS/2EpfjFms -# wYhh6tKYg2k2j6mfCGmwvx2cx32zR/m4/ieGClCeKPiRlPvgip0= -# SIG # End signature block +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# + +<# + Helper functions for PowerShellGet DSC Resources. +#> + +# Import localization helper functions. +$helperName = 'PowerShellGet.LocalizationHelper' +$resourceModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent +$dscResourcesFolderFilePath = Join-Path -Path $resourceModuleRoot -ChildPath "Modules\$helperName\$helperName.psm1" +Import-Module -Name $dscResourcesFolderFilePath + +# Import Localization Strings +$script:localizedData = Get-LocalizedData -ResourceName 'PowerShellGet.ResourceHelper' -ScriptRoot $PSScriptRoot + +<# + .SYNOPSIS + This is a helper function that extract the parameters from a given table. + + .PARAMETER FunctionBoundParameters + Specifies the hash table containing a set of parameters to be extracted. + + .PARAMETER ArgumentNames + Specifies a list of arguments you want to extract. +#> +function New-SplatParameterHashTable { + [CmdletBinding()] + [OutputType([System.Collections.Hashtable])] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $FunctionBoundParameters, + + [Parameter(Mandatory = $true)] + [System.String[]] + $ArgumentNames + ) + + Write-Verbose -Message ($script:localizedData.CallingFunction -f $($MyInvocation.MyCommand)) + + $returnValue = @{} + + foreach ($arg in $ArgumentNames) { + if ($FunctionBoundParameters.ContainsKey($arg)) { + # Found an argument we are looking for, so we add it to return collection. + $returnValue.Add($arg, $FunctionBoundParameters[$arg]) + } + } + + return $returnValue +} + +<# + .SYNOPSIS + This is a helper function that validate that a value is correct and used correctly. + + .PARAMETER Value + Specifies the value to be validated. + + .PARAMETER Type + Specifies the type of argument. + + .PARAMETER Type + Specifies the name of the provider. + + .OUTPUTS + None. Throws an error if the test fails. +#> +function Test-ParameterValue { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Value, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $Type, + + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] + $ProviderName + ) + + Write-Verbose -Message ($script:localizedData.CallingFunction -f $($MyInvocation.MyCommand)) + + switch ($Type) { + 'SourceUri' { + # Checks whether given URI represents specific scheme + # Most common schemes: file, http, https, ftp + $scheme = @('http', 'https', 'file', 'ftp') + + $newUri = $Value -as [System.URI] + $returnValue = ($newUri -and $newUri.AbsoluteURI -and ($scheme -icontains $newUri.Scheme)) + + if ($returnValue -eq $false) { + $errorMessage = $script:localizedData.InValidUri -f $Value + New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage + } + } + + 'DestinationPath' { + $returnValue = Test-Path -Path $Value + + if ($returnValue -eq $false) { + $errorMessage = $script:localizedData.PathDoesNotExist -f $Value + New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage + } + } + + 'PackageSource' { + # Value can be either the package source Name or source Uri. + + # Check if the source is a Uri. + $uri = $Value -as [System.URI] + + if ($uri -and $uri.AbsoluteURI) { + # Check if it's a valid Uri. + Test-ParameterValue -Value $Value -Type 'SourceUri' -ProviderName $ProviderName + } + else { + # Check if it's a registered package source name. + $source = PackageManagement\Get-PackageSource -Name $Value -ProviderName $ProviderName -ErrorVariable ev + + if ((-not $source) -or $ev) { + # We do not need to throw error here as Get-PackageSource does already. + Write-Verbose -Message ($script:localizedData.SourceNotFound -f $source) + } + } + } + + default { + $errorMessage = $script:localizedData.UnexpectedArgument -f $Type + New-InvalidArgumentException -ArgumentName $Type -Message $errorMessage + } + } +} + +<# + .SYNOPSIS + This is a helper function that does the version validation. + + .PARAMETER RequiredVersion + Provides the required version. + + .PARAMETER MaximumVersion + Provides the maximum version. + + .PARAMETER MinimumVersion + Provides the minimum version. +#> +function Test-VersionParameter { + [CmdletBinding()] + param + ( + [Parameter()] + [System.String] + $RequiredVersion, + + [Parameter()] + [System.String] + $MinimumVersion, + + [Parameter()] + [System.String] + $MaximumVersion + ) + + Write-Verbose -Message ($localizedData.CallingFunction -f $($MyInvocation.MyCommand)) + + $isValid = $false + + # Case 1: No further check required if a user provides either none or one of these: minimumVersion, maximumVersion, and requiredVersion. + if ($PSBoundParameters.Count -le 1) { + return $true + } + + # Case 2: #If no RequiredVersion is provided. + if (-not $PSBoundParameters.ContainsKey('RequiredVersion')) { + # If no RequiredVersion, both MinimumVersion and MaximumVersion are provided. Otherwise fall into the Case #1. + $isValid = $PSBoundParameters['MinimumVersion'] -le $PSBoundParameters['MaximumVersion'] + } + + # Case 3: RequiredVersion is provided. + # In this case MinimumVersion and/or MaximumVersion also are provided. Otherwise fall in to Case #1. + # This is an invalid case. When RequiredVersion is provided, others are not allowed. so $isValid is false, which is already set in the init. + + if ($isValid -eq $false) { + $errorMessage = $script:localizedData.VersionError + New-InvalidArgumentException ` + -ArgumentName 'RequiredVersion, MinimumVersion or MaximumVersion' ` + -Message $errorMessage + } +} + +<# + .SYNOPSIS + This is a helper function that retrieves the InstallationPolicy from the given repository. + + .PARAMETER RepositoryName + Provides the repository Name. +#> +function Get-InstallationPolicy { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [ValidateNotNullOrEmpty()] + [System.String] $RepositoryName + ) + + Write-Verbose -Message ($LocalizedData.CallingFunction -f $($MyInvocation.MyCommand)) + + $repositoryObject = PackageManagement\Get-PackageSource -Name $RepositoryName -ErrorAction SilentlyContinue -WarningAction SilentlyContinue + + if ($repositoryObject) { + return $repositoryObject.IsTrusted + } +} + +<# + .SYNOPSIS + This method is used to compare current and desired values for any DSC resource. + + .PARAMETER CurrentValues + This is hash table of the current values that are applied to the resource. + + .PARAMETER DesiredValues + This is a PSBoundParametersDictionary of the desired values for the resource. + + .PARAMETER ValuesToCheck + This is a list of which properties in the desired values list should be checked. + If this is empty then all values in DesiredValues are checked. +#> +function Test-DscParameterState { + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable] + $CurrentValues, + + [Parameter(Mandatory = $true)] + [System.Object] + $DesiredValues, + + [Parameter()] + [System.Array] + $ValuesToCheck + ) + + $returnValue = $true + + if (($DesiredValues.GetType().Name -ne 'HashTable') ` + -and ($DesiredValues.GetType().Name -ne 'CimInstance') ` + -and ($DesiredValues.GetType().Name -ne 'PSBoundParametersDictionary')) { + $errorMessage = $script:localizedData.PropertyTypeInvalidForDesiredValues -f $($DesiredValues.GetType().Name) + New-InvalidArgumentException -ArgumentName 'DesiredValues' -Message $errorMessage + } + + if (($DesiredValues.GetType().Name -eq 'CimInstance') -and ($null -eq $ValuesToCheck)) { + $errorMessage = $script:localizedData.PropertyTypeInvalidForValuesToCheck + New-InvalidArgumentException -ArgumentName 'ValuesToCheck' -Message $errorMessage + } + + if (($null -eq $ValuesToCheck) -or ($ValuesToCheck.Count -lt 1)) { + $keyList = $DesiredValues.Keys + } + else { + $keyList = $ValuesToCheck + } + + $keyList | ForEach-Object -Process { + if (($_ -ne 'Verbose')) { + if (($CurrentValues.ContainsKey($_) -eq $false) ` + -or ($CurrentValues.$_ -ne $DesiredValues.$_) ` + -or (($DesiredValues.GetType().Name -ne 'CimInstance' -and $DesiredValues.ContainsKey($_) -eq $true) -and ($null -ne $DesiredValues.$_ -and $DesiredValues.$_.GetType().IsArray))) { + if ($DesiredValues.GetType().Name -eq 'HashTable' -or ` + $DesiredValues.GetType().Name -eq 'PSBoundParametersDictionary') { + $checkDesiredValue = $DesiredValues.ContainsKey($_) + } + else { + # If DesiredValue is a CimInstance. + $checkDesiredValue = $false + if (([System.Boolean]($DesiredValues.PSObject.Properties.Name -contains $_)) -eq $true) { + if ($null -ne $DesiredValues.$_) { + $checkDesiredValue = $true + } + } + } + + if ($checkDesiredValue) { + $desiredType = $DesiredValues.$_.GetType() + $fieldName = $_ + if ($desiredType.IsArray -eq $true) { + if (($CurrentValues.ContainsKey($fieldName) -eq $false) ` + -or ($null -eq $CurrentValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.PropertyValidationError -f $fieldName) -Verbose + + $returnValue = $false + } + else { + $arrayCompare = Compare-Object -ReferenceObject $CurrentValues.$fieldName ` + -DifferenceObject $DesiredValues.$fieldName + if ($null -ne $arrayCompare) { + Write-Verbose -Message ($script:localizedData.PropertiesDoesNotMatch -f $fieldName) -Verbose + + $arrayCompare | ForEach-Object -Process { + Write-Verbose -Message ($script:localizedData.PropertyThatDoesNotMatch -f $_.InputObject, $_.SideIndicator) -Verbose + } + + $returnValue = $false + } + } + } + else { + switch ($desiredType.Name) { + 'String' { + if (-not [System.String]::IsNullOrEmpty($CurrentValues.$fieldName) -or ` + -not [System.String]::IsNullOrEmpty($DesiredValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + 'Int32' { + if (-not ($DesiredValues.$fieldName -eq 0) -or ` + -not ($null -eq $CurrentValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + { $_ -eq 'Int16' -or $_ -eq 'UInt16'} { + if (-not ($DesiredValues.$fieldName -eq 0) -or ` + -not ($null -eq $CurrentValues.$fieldName)) { + Write-Verbose -Message ($script:localizedData.ValueOfTypeDoesNotMatch ` + -f $desiredType.Name, $fieldName, $($CurrentValues.$fieldName), $($DesiredValues.$fieldName)) -Verbose + + $returnValue = $false + } + } + + default { + Write-Warning -Message ($script:localizedData.UnableToCompareProperty ` + -f $fieldName, $desiredType.Name) + + $returnValue = $false + } + } + } + } + } + } + } + + return $returnValue +} + +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBH3QB1YDOZOOwJ +# V+KQVBF6PeRCZ7EOOlwmL67tD15ypKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgAchP/Cco +# Y2JAjut9Hy3IC65/Rf67ea66bxFJosTcn0EwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQCA7tKfqWfEy22S/6wr4OyvRkvECIOlgOTLpZGyP7iT +# 16UqNis22r/tHfj0nQMVembrz5WOUvHZPrU72+fJAZqMdCB5H0wHmeBOmie166xn +# CDvlSHtJr5sPZeRDNQFeJN2nN+I47eFwThr5ndx6Gp548DoyDWBnFQz2tgvVpIWA +# TBFrWf8ZulTYcMH3QhzlgEmmgEsU2vT5nbBmmK2QICDzcWA3tIzHSQ7jQxqL+9Oj +# 9EVvuJTf0e81f05xNUmunrwWuoHzIYOF7LmpZqWFp4m8rg5Davtum5Rq60TcofSH +# gKzh10SD1+hAHm7/aqz640BMXsMJCBHofYDzXY0Un7sWoYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIJeyBGsDoIyvzKdclhGjO92lMFh4eXEiupSWXMgA +# yrYhAgZfYQdYOoMYEzIwMjAwOTIyMjIxOTUwLjk4M1owBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjpGODdBLUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABL7GnF3lWlBeHAAAA +# AAEvMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTUwNloXDTIxMDMxNzAxMTUwNlowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpGODdB +# LUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKh8VkvVIwXD8sn0zT2n +# EdyEZ9UNHY7ACbOZA4obAHvD1hauw9K1Z2lRWG+m8Ars9l35GoMXdPgshM3hZKQW +# fhrLnF9/GDZoilhc2LhMqNPXs06rAJ8YODB6i0Cg1CFCYnyOYvywXKY3xGJN09Dg +# PXWfczEm2P/a3rmrXMrK5EFc3ahxrC51c+UuAMKV9xJyzJVLShPwPBJl+CjdMDPJ +# f24DZXIYec3gCN2xean1DFCI0gaqJprMeL4Om1KY2AZMIgBPEkoY1N7AI5e7ybkI +# L8+Mz3inijb4rDTkXk86ztUwy4bdc1MyKe2j2odT+QIDA2+M8cMTIGlKn7EyD2NN +# XU8CAwEAAaOCARswggEXMB0GA1UdDgQWBBSml/VRpBNFkAMDiqcoqWi85j/qljAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQB4q6ilv2SlGvJD/7dbfoIKZBO2i6YA +# wckw57TpCrt2+SAx2dcF7JvRMCPhLCSgqjyNcJRs40cEXPbLdzZMJHzcv73AF7L6 +# mWZXg2aBjG1Sc5qM4jjE/nwIX+C6/odm5/asU4JIlFCuUZjzqdir18HkRVQve2Hw +# V0lCXHQs+V3m9DyyA9b6LSIk3GOFZu7F11Wyx/5dVXisPPTPwh9JXfMD9W173M1+ +# ZZycmO03lUc4G1FilgpxWNdgWn/DO9ZhoW5yN6+BUddnJ4cCcCjcg8sB5rktPP8p +# VZAQ7aUqkAeqo+FuCkAUAdJRESCpR5wgSPtVvFPMjONE36DbKtfzkfiHMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjpG +# ODdBLUUzNzQtRDdCOTElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAM/CZCUpclQ9qfr/r3y9osIIPSmSggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMUwAMwIhgPMjAyMDA5MjIyMjI1MDdaGA8yMDIwMDkyMzIyMjUwN1owdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA4xTAAwIBADAKAgEAAgIeewIB/zAHAgEAAgIQ0jAK +# AgUA4xYRgwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAB7Ib0ORN4cnD/0V +# iEDrzl7vxjx6981A8Z/8I+X0n7qQHdeGFJcXGEEO5pDD62VMfd28ZrxPWgMd0Chx +# mj5EdvR3U6+gRYzyYY5Vi5srADcWf3pMoYsEQhZuYeohtFQuYFJt2t26GskSlEy3 +# qXFkFqIoBCjz1SsmfA5nTtE2zXHSMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEvsacXeVaUF4cAAAAAAS8wDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQgDY7vkk/uubZSKGprOnZ2WnFZqhRkEJ7KVJ5R6hogo/AwgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCBC5RecGZvugnvVXg80zlrGv1uV35LNk+H9dBj3 +# ChFPvTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# L7GnF3lWlBeHAAAAAAEvMCIEIAt7o8jjQ4zf04GvFGrnuSSwfsToGMPMzYw+3a3k +# vP0dMA0GCSqGSIb3DQEBCwUABIIBABIE9hcJhxcSYL9zQrK7AunfYI2qZBNrD9dB +# FIYwBnLNOYAPgpjC5Nxrp/4fSrWPyWFkXzwB6rjofda3zWpOywWKMc4lK0yHRYrO +# 7r0FmXd9zxdaJKpbHpc8xG2vb6pI9dh/eXFGNS8KRu+oKZFX2/l5IxRAz4QRMxq6 +# /heHcZpWjeIknMhP+bMAqaMV9h3jEojCli0hdwawbTpq8Oyvo6wlwNqLvmtBc7Xs +# 8of9f6I/zTCn6yhbyDEVXxPhn3qjrQzi790XXe3xvmAKkkEW/NO2ZOS/2EpfjFms +# wYhh6tKYg2k2j6mfCGmwvx2cx32zR/m4/ieGClCeKPiRlPvgip0= +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 index ca0e07e038..eefe2fa7b4 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/Modules/PowerShellGet.ResourceHelper/en-US/PowerShellGet.ResourceHelper.strings.psd1 @@ -1,222 +1,222 @@ -# -# Copyright (c) Microsoft Corporation. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# culture = "en-US" -ConvertFrom-StringData -StringData @' -###PSLOC - InValidUri = InValid Uri: '{0}'. A sample valid uri: https://www.powershellgallery.com/api/v2/. - PathDoesNotExist = Path: '{0}' does not exist. - VersionError = MinimumVersion should be less than the MaximumVersion. The MinimumVersion or MaximumVersion cannot be used with the RequiredVersion in the same command. - UnexpectedArgument = Unexpected argument type: '{0}'. - SourceNotFound = Source '{0}' not found. Please make sure you register it. - CallingFunction = Calling function '{0}'. - PropertyTypeInvalidForDesiredValues = Property 'DesiredValues' must be either a [System.Collections.Hashtable], [CimInstance] or [PSBoundParametersDictionary]. The type detected was {0}. - PropertyTypeInvalidForValuesToCheck = If 'DesiredValues' is a CimInstance, then property 'ValuesToCheck' must contain a value. - PropertyValidationError = Expected to find an array value for property {0} in the current values, but it was either not present or was null. This has caused the test method to return false. - PropertiesDoesNotMatch = Found an array for property {0} in the current values, but this array does not match the desired state. Details of the changes are below. - PropertyThatDoesNotMatch = {0} - {1} - ValueOfTypeDoesNotMatch = {0} value for property {1} does not match. Current state is '{2}' and desired state is '{3}'. - UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-SQLDSCParameterState cmdlet. -###PSLOC -'@ - -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBIoCdfhdi5Cjss -# ngqx6qsfpPMDALYWOvgm/jaEU9DaSKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgRtNNlbVF -# dlkMIAy8Jt0BeSlkgxnR3ktF5+8UaxbiK1gwQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQBikiCJbAZhOZ7PLbYTjUnSEz9oaiBq80NUlYUDz8p+ -# uGQfC0LU5aqPrFJ2lwmhVkhLmfAaP3OseRq1JdSHe3xxBndAN9Oh2HTlXm47fL5H -# zJmnEdFq5YaXoWBoUn1O7sE4zMWU+JQm3I9YtgHVSgGzxX9ao/BjFW2s3wtfcXmp -# 3zhN+cbAfBY/8u6J4Zui1mYzlMplYSwb66ikLo6kkSs3OnKN9frtB0McoazPtOxn -# M4bae/0xVVJyt7vT3Vru3bDBIFOmEHnYPAhdz+QDxS8wLXCnKNWgMizg3s/iFVDO -# G1VtPErpnu9oraXqkmHOMy8naFl85xuR/hhRpkiooEfKoYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIMtX3JJD1gPDRiGuV8rCirgOBq8Lt6eGHeZdjPKC -# pH3qAgZfYRqWlCoYEzIwMjAwOTIyMjIxOTUyLjU2NlowBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjo0NjJGLUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJMvNAqEXcFyaAAAA -# AAEkMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTQ1N1oXDTIxMDMxNzAxMTQ1N1owgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0NjJG -# LUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJCbKjgNnhjvMlnRNAtx -# 7X3N5ZZkSfUFULyWsVJ1pnyMsSITimg1q3OQ1Ikf0/3gg8UG5TIRm7wH8sjBtoB3 -# nuzFz11CegIFcanYnt050JvnrUTKeAPUR5pLpTeP3QEgL+CWOc4lTg/XxjnQv01F -# D7TTn9DEuO3kp0GQ87Mjd5ssxK0K1q4IWNFAyRpx5n8Vm3Vm1iiVL5FMDUKsl5G/ -# SqQdiEDn8cqYbqWMVzWH94PdKdw1mIHToBRCNsR9BHHWzNkSS+R0WRipBSSorKT7 -# cuLlEBYhDo8AY3uMGlv0kLRLHASZ+sz2nfkpW2CVt+bHhVmM6/5qiu2f7eYoTYJu -# cFECAwEAAaOCARswggEXMB0GA1UdDgQWBBS7HdFyrGKIhDxvypLA1lD/wGRSsDAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBo3QzNuNRgzdflwA4U7f3e2CcGlwdg -# 6ii498cBiSrTpWKO3qqz5pvgHAk4hh6y/FLY80R59inLwcVuyD24S3JEdSie4y1t -# C5JptweR1qlxRJCRM4vG7nPtIC4eAMKcXgovu0mTFv7xpFAVpRuvuepR91gIde32 -# 8lv1HTTJCV/LBBk83Xi7nCGPF59FxeIrcE32xt4YJgEpEAikeMqvWCTMyPqlmvx9 -# J92fxU3cQcw2j2EWwqOD5T3Nz2HWfPV80sihD1A6Y5HhjpS9taDPs7CI58I211F3 -# ysegNyOesG3MTrSJHyPMLKYFDxcG1neV0liktv+TW927sUOVczcSUhQLMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0 -# NjJGLUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUAlwPlNCq+Un54UfxLe/wKS1Xc4nqggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMU004wIhgPMjAyMDA5MjIyMzQ3MjZaGA8yMDIwMDkyMzIzNDcyNlowdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA4xTTTgIBADAKAgEAAgIgZwIB/zAHAgEAAgIS3TAK -# AgUA4xYkzgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJQEuwCens9exo3y -# oRx0EpWssZYEoYGDA5Vo9cCDxGOjhqFjMJu/dmJcoeWqqCmHy66EAonaDmopQyNg -# lRRfmDjEiQhc8am2i9RlvZac+vEFY6AP1GPEj+toN+LsaPXRU53/6LK/MR3BkTCm -# iOD4gr6KZBDzEEjT2HA2pS7pSHevMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEky80CoRdwXJoAAAAAASQwDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQgjlF6gXuuf73GPP9MP86hJ0HAK3FFZWGekdUUKTNpWjkwgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCBiOOHoohqL+X7Xa/25jp1wTrQxYlYGLszis/nA -# TirjIDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# JMvNAqEXcFyaAAAAAAEkMCIEINH5vtu0jUcUjiR+HX2ME9dlInYB6kXi3msE3oBJ -# sB5sMA0GCSqGSIb3DQEBCwUABIIBABh16uxHs4RlKV2M0xneFNXFfIQNS8R1n2eb -# YRGKKZ9fTl1xaJvd7S6pCc5Rb8aBCmnXOpz5f/fB1EJgfhR4BIBZmDwWE8tppqVL -# 1nEM7CZkWPmo001AQrX19vPIQ3ukJk/Wn/9cD83zxWGBLAw3+YIm7zfYZOPoif5p -# r04h63L9MtLQI99OF9XQM+T7o5bz5jTOt6Bcw3vRPCY41l1SGTrWjL1QQXDbkuDv -# 3VmM//FceCvjkqFcxYwC24sCKjnig2x9ujnG5C4IiB+pTsUdmrC+tneCXXQkOT6V -# M5UZDs3GY4Yqt/1aFcr4EZE0VaZoSuZn8hKlyXxXJLM7UlCPLxc= -# SIG # End signature block +# +# Copyright (c) Microsoft Corporation. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +# +# culture = "en-US" +ConvertFrom-StringData -StringData @' +###PSLOC + InValidUri = InValid Uri: '{0}'. A sample valid uri: https://www.powershellgallery.com/api/v2/. + PathDoesNotExist = Path: '{0}' does not exist. + VersionError = MinimumVersion should be less than the MaximumVersion. The MinimumVersion or MaximumVersion cannot be used with the RequiredVersion in the same command. + UnexpectedArgument = Unexpected argument type: '{0}'. + SourceNotFound = Source '{0}' not found. Please make sure you register it. + CallingFunction = Calling function '{0}'. + PropertyTypeInvalidForDesiredValues = Property 'DesiredValues' must be either a [System.Collections.Hashtable], [CimInstance] or [PSBoundParametersDictionary]. The type detected was {0}. + PropertyTypeInvalidForValuesToCheck = If 'DesiredValues' is a CimInstance, then property 'ValuesToCheck' must contain a value. + PropertyValidationError = Expected to find an array value for property {0} in the current values, but it was either not present or was null. This has caused the test method to return false. + PropertiesDoesNotMatch = Found an array for property {0} in the current values, but this array does not match the desired state. Details of the changes are below. + PropertyThatDoesNotMatch = {0} - {1} + ValueOfTypeDoesNotMatch = {0} value for property {1} does not match. Current state is '{2}' and desired state is '{3}'. + UnableToCompareProperty = Unable to compare property {0} as the type {1} is not handled by the Test-SQLDSCParameterState cmdlet. +###PSLOC +'@ + +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBIoCdfhdi5Cjss +# ngqx6qsfpPMDALYWOvgm/jaEU9DaSKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgRtNNlbVF +# dlkMIAy8Jt0BeSlkgxnR3ktF5+8UaxbiK1gwQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQBikiCJbAZhOZ7PLbYTjUnSEz9oaiBq80NUlYUDz8p+ +# uGQfC0LU5aqPrFJ2lwmhVkhLmfAaP3OseRq1JdSHe3xxBndAN9Oh2HTlXm47fL5H +# zJmnEdFq5YaXoWBoUn1O7sE4zMWU+JQm3I9YtgHVSgGzxX9ao/BjFW2s3wtfcXmp +# 3zhN+cbAfBY/8u6J4Zui1mYzlMplYSwb66ikLo6kkSs3OnKN9frtB0McoazPtOxn +# M4bae/0xVVJyt7vT3Vru3bDBIFOmEHnYPAhdz+QDxS8wLXCnKNWgMizg3s/iFVDO +# G1VtPErpnu9oraXqkmHOMy8naFl85xuR/hhRpkiooEfKoYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIMtX3JJD1gPDRiGuV8rCirgOBq8Lt6eGHeZdjPKC +# pH3qAgZfYRqWlCoYEzIwMjAwOTIyMjIxOTUyLjU2NlowBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjo0NjJGLUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJMvNAqEXcFyaAAAA +# AAEkMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTQ1N1oXDTIxMDMxNzAxMTQ1N1owgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0NjJG +# LUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJCbKjgNnhjvMlnRNAtx +# 7X3N5ZZkSfUFULyWsVJ1pnyMsSITimg1q3OQ1Ikf0/3gg8UG5TIRm7wH8sjBtoB3 +# nuzFz11CegIFcanYnt050JvnrUTKeAPUR5pLpTeP3QEgL+CWOc4lTg/XxjnQv01F +# D7TTn9DEuO3kp0GQ87Mjd5ssxK0K1q4IWNFAyRpx5n8Vm3Vm1iiVL5FMDUKsl5G/ +# SqQdiEDn8cqYbqWMVzWH94PdKdw1mIHToBRCNsR9BHHWzNkSS+R0WRipBSSorKT7 +# cuLlEBYhDo8AY3uMGlv0kLRLHASZ+sz2nfkpW2CVt+bHhVmM6/5qiu2f7eYoTYJu +# cFECAwEAAaOCARswggEXMB0GA1UdDgQWBBS7HdFyrGKIhDxvypLA1lD/wGRSsDAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQBo3QzNuNRgzdflwA4U7f3e2CcGlwdg +# 6ii498cBiSrTpWKO3qqz5pvgHAk4hh6y/FLY80R59inLwcVuyD24S3JEdSie4y1t +# C5JptweR1qlxRJCRM4vG7nPtIC4eAMKcXgovu0mTFv7xpFAVpRuvuepR91gIde32 +# 8lv1HTTJCV/LBBk83Xi7nCGPF59FxeIrcE32xt4YJgEpEAikeMqvWCTMyPqlmvx9 +# J92fxU3cQcw2j2EWwqOD5T3Nz2HWfPV80sihD1A6Y5HhjpS9taDPs7CI58I211F3 +# ysegNyOesG3MTrSJHyPMLKYFDxcG1neV0liktv+TW927sUOVczcSUhQLMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjo0 +# NjJGLUUzMTktM0YyMDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAlwPlNCq+Un54UfxLe/wKS1Xc4nqggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMU004wIhgPMjAyMDA5MjIyMzQ3MjZaGA8yMDIwMDkyMzIzNDcyNlowdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA4xTTTgIBADAKAgEAAgIgZwIB/zAHAgEAAgIS3TAK +# AgUA4xYkzgIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAJQEuwCens9exo3y +# oRx0EpWssZYEoYGDA5Vo9cCDxGOjhqFjMJu/dmJcoeWqqCmHy66EAonaDmopQyNg +# lRRfmDjEiQhc8am2i9RlvZac+vEFY6AP1GPEj+toN+LsaPXRU53/6LK/MR3BkTCm +# iOD4gr6KZBDzEEjT2HA2pS7pSHevMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEky80CoRdwXJoAAAAAASQwDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQgjlF6gXuuf73GPP9MP86hJ0HAK3FFZWGekdUUKTNpWjkwgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCBiOOHoohqL+X7Xa/25jp1wTrQxYlYGLszis/nA +# TirjIDCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# JMvNAqEXcFyaAAAAAAEkMCIEINH5vtu0jUcUjiR+HX2ME9dlInYB6kXi3msE3oBJ +# sB5sMA0GCSqGSIb3DQEBCwUABIIBABh16uxHs4RlKV2M0xneFNXFfIQNS8R1n2eb +# YRGKKZ9fTl1xaJvd7S6pCc5Rb8aBCmnXOpz5f/fB1EJgfhR4BIBZmDwWE8tppqVL +# 1nEM7CZkWPmo001AQrX19vPIQ3ukJk/Wn/9cD83zxWGBLAw3+YIm7zfYZOPoif5p +# r04h63L9MtLQI99OF9XQM+T7o5bz5jTOt6Bcw3vRPCY41l1SGTrWjL1QQXDbkuDv +# 3VmM//FceCvjkqFcxYwC24sCKjnig2x9ujnG5C4IiB+pTsUdmrC+tneCXXQkOT6V +# M5UZDs3GY4Yqt/1aFcr4EZE0VaZoSuZn8hKlyXxXJLM7UlCPLxc= +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Format.ps1xml b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Format.ps1xml index ad6f75abc9..4439620f65 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Format.ps1xml +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Format.ps1xml @@ -1,409 +1,409 @@ - - - - - PSRepositoryItemInfo - - Microsoft.PowerShell.Commands.PSRepositoryItemInfo - - - - - 20 - - - 35 - - - 20 - - - - - - - - Version - - - Name - - - Repository - - - Description - - - - - - - - PSRepository - - Microsoft.PowerShell.Commands.PSRepository - - - - - 25 - - - 20 - - - - - - - - Name - - - InstallationPolicy - - - SourceLocation - - - - - - - - PSScriptInfo - - Microsoft.PowerShell.Commands.PSScriptInfo - - - - - 20 - - - 25 - - - 20 - - - - - - - - Version - - - Name - - - Author - - - Description - - - - - - - - PSGetDscResourceInfo - - Microsoft.PowerShell.Commands.PSGetCommandInfo - Microsoft.PowerShell.Commands.PSGetDscResourceInfo - - - - - 35 - - - 10 - - - 35 - - - - - - - - Name - - - Version - - - ModuleName - - - Repository - - - - - - - - PSGetRoleCapabilityInfo - - Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo - - - - - 35 - - - 10 - - - 35 - - - - - - - - Name - - - Version - - - ModuleName - - - Repository - - - - - - - - PSGetPath - - Microsoft.PowerShell.Commands.PSGetPath - - - - - - - AllUsersModules - - - AllUsersScripts - - - CurrentUserModules - - - CurrentUserScripts - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + PSRepositoryItemInfo + + Microsoft.PowerShell.Commands.PSRepositoryItemInfo + + + + + 20 + + + 35 + + + 20 + + + + + + + + Version + + + Name + + + Repository + + + Description + + + + + + + + PSRepository + + Microsoft.PowerShell.Commands.PSRepository + + + + + 25 + + + 20 + + + + + + + + Name + + + InstallationPolicy + + + SourceLocation + + + + + + + + PSScriptInfo + + Microsoft.PowerShell.Commands.PSScriptInfo + + + + + 20 + + + 25 + + + 20 + + + + + + + + Version + + + Name + + + Author + + + Description + + + + + + + + PSGetDscResourceInfo + + Microsoft.PowerShell.Commands.PSGetCommandInfo + Microsoft.PowerShell.Commands.PSGetDscResourceInfo + + + + + 35 + + + 10 + + + 35 + + + + + + + + Name + + + Version + + + ModuleName + + + Repository + + + + + + + + PSGetRoleCapabilityInfo + + Microsoft.PowerShell.Commands.PSGetRoleCapabilityInfo + + + + + 35 + + + 10 + + + 35 + + + + + + + + Name + + + Version + + + ModuleName + + + Repository + + + + + + + + PSGetPath + + Microsoft.PowerShell.Commands.PSGetPath + + + + + + + AllUsersModules + + + AllUsersScripts + + + CurrentUserModules + + + CurrentUserScripts + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Resource.psd1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Resource.psd1 index 2315e1087e..f81171897b 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Resource.psd1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGet.Resource.psd1 @@ -1,466 +1,466 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Localized PSGet.Resource.psd1 -# -######################################################################################### - -ConvertFrom-StringData @' -###PSLOC - InstallModulewhatIfMessage=Version '{1}' of module '{0}' - InstallScriptwhatIfMessage=Version '{1}' of script '{0}' - UpdateModulewhatIfMessage=Version '__OLDVERSION__' of module '{0}', updating to version '{1}' - UpdateScriptwhatIfMessage=Version '__OLDVERSION__' of script '{0}', updating to version '{1}' - PublishModulewhatIfMessage=Version '{0}' of module '{1}' - PublishScriptwhatIfMessage=Version '{0}' of script '{1}' - NewScriptFileInfowhatIfMessage=Creating the '{0}' PowerShell Script file - UpdateScriptFileInfowhatIfMessage=Updating the '{0}' PowerShell Script file - NameShouldNotContainWildcardCharacters=The specified name '{0}' should not contain any wildcard characters, please correct it and try again. - AllVersionsCannotBeUsedWithOtherVersionParameters=You cannot use the parameter AllVersions with RequiredVersion, MinimumVersion or MaximumVersion in the same command. - VersionRangeAndRequiredVersionCannotBeSpecifiedTogether=You cannot use the parameters RequiredVersion and either MinimumVersion or MaximumVersion in the same command. Specify only one of these parameters in your command. - RequiredVersionAllowedOnlyWithSingleModuleName=The RequiredVersion parameter is allowed only when a single module name is specified as the value of the Name parameter, without any wildcard characters. - MinimumVersionIsGreaterThanMaximumVersion=The specified MinimumVersion '{0}' is greater than the specified MaximumVersion '{1}'. - AllowPrereleaseRequiredToUsePrereleaseStringInVersion=The '-AllowPrerelease' parameter must be specified when using the Prerelease string in MinimumVersion, MaximumVersion, or RequiredVersion. - UpdateModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to update modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or update '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - InstallModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - InstallScriptAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install scripts in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - AdministratorRightsNeededOrSpecifyCurrentUserScope=Administrator rights are required to install or update. Log on to the computer with an account that has Administrator rights, and then try again, or install by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - VersionParametersAreAllowedOnlyWithSingleName=The RequiredVersion, MinimumVersion, MaximumVersion, AllVersions or AllowPrerelease parameters are allowed only when you specify a single name as the value of the Name parameter, without any wildcard characters. - PathIsNotADirectory=The specified path '{0}' is not a valid directory. - ModuleAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Module, and add the -Force parameter. - ScriptAlreadyInstalled=Version '{0}' of script '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Script, and add the -Force parameter. - CommandAlreadyAvailable=A command with name '{0}' is already available on this system. This script '{0}' may override the existing command. If you still want to install this script '{0}', use -Force parameter. - ModuleAlreadyInstalledSxS=Version '{0}' of module '{1}' is already installed at '{2}'. To install version '{3}', run Install-Module and add the -Force parameter, this command will install version '{5}' side-by-side with version '{4}'. - ModuleAlreadyInstalledVerbose=Version '{0}' of module '{1}' is already installed at '{2}'. - ScriptAlreadyInstalledVerbose=Version '{0}' of script '{1}' is already installed at '{2}'. - ModuleWithRequiredVersionAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To reinstall this version '{3}', run Install-Module or Updated-Module cmdlet with the -Force parameter. - InvalidPSModule=The module '{0}' cannot be installed or updated because it is not a properly-formed module. - InvalidPowerShellScriptFile=The script '{0}' cannot be installed or updated because it is not a properly-formed script. - InvalidAuthenticodeSignature=The module '{0}' cannot be installed or updated because the Authenticode signature for the file '{1}' is not valid. - ModuleNotInstalledOnThisMachine=Module '{0}' was not updated because no valid module was found in the module directory. Verify that the module is located in the folder specified by $env:PSModulePath. - ScriptNotInstalledOnThisMachine=Script '{0}' was not updated because no valid script was found in the script directories '{1}' and '{2}'. - AdminPrivilegesRequiredForUpdate=Module '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that directory. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - AdminPrivilegesRequiredForScriptUpdate=Script '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that script. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - ModuleNotInstalledUsingPowerShellGet=Module '{0}' was not installed by using Install-Module, so it cannot be updated. - ScriptNotInstalledUsingPowerShellGet=Script '{0}' was not installed by using Install-Script, so it cannot be updated. - DownloadingModuleFromGallery=Downloading module '{0}' with version '{1}' from the repository '{2}'. - DownloadingScriptFromGallery=Downloading script '{0}' with version '{1}' from the repository '{2}'. - NoUpdateAvailable=No updates were found for module '{0}'. - NoScriptUpdateAvailable=No updates were found for script '{0}'. - FoundModuleUpdate=An update for the module '{0}' was found with version '{1}'. - FoundScriptUpdate=An update for the script '{0}' was found with version '{1}'. - InvalidPSModuleDuringUpdate= Module '{0}' was not updated because the module in the repository '{1}' is not a valid Windows PowerShell module. - ModuleGotUpdated=Module '{0}' has been updated successfully. - TestingModuleInUse=Testing if the module to update is in use. - ModuleDestination= The specified module will be installed in '{0}'. - ScriptDestination= The specified script will be installed in '{0}' and its dependent modules will be installed in '{1}'. - ModuleIsInUse=Module '{0}' is in currently in use or you don't have the required permissions. - ModuleInstalledSuccessfully=Module '{0}' was installed successfully to path '{1}'. - ModuleSavedSuccessfully=Module '{0}' was saved successfully to path '{1}'. - ScriptInstalledSuccessfully=Script '{0}' was installed successfully to path '{1}'. - ScriptSavedSuccessfully=Script '{0}' was saved successfully to path '{1}'. - CheckingForModuleUpdate= Checking for updates for module '{0}'. - CheckingForScriptUpdate= Checking for updates for script '{0}'. - ModuleInUseWithProcessDetails=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the following applications: '{2}'. - ModuleVersionInUse=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the applications. - ModuleNotAvailableLocally=The specified module '{0}' was not published because no module with that name was found in any module directory. - InvalidModulePathToPublish=The specified module with path '{0}' was not published because no valid module was found with that path. - ModuleWithRequiredVersionNotAvailableLocally= The specified module '{0}' with version '{1}' was not published because no module with that name and version was found in any module directory. - AmbiguousModuleName=Modules with the name '{0}' are available under multiple paths. Add the -RequiredVersion parameter or the -Path parameter to specify the module to publish. - AmbiguousModulePath=Multiple versions are available under the specified module path '{0}'. Specify the full path to the module to be published. - PublishModuleLocation=Module '{0}' was found in '{1}'. - InvalidModuleToPublish=Module '{0}' cannot be published because it does not have a module manifest file. Run New-ModuleManifest -Path to create a module manifest with metadata before publishing. - MissingRequiredManifestKeys=Module '{0}' cannot be published because it is missing required metadata. Verify that the module manifest specifies Description and Author. - InvalidCharactersInPrereleaseString=The Prerelease string '{0}' contains invalid characters. Please ensure that only characters 'a-zA-Z0-9' and possibly hyphen ('-') at the beginning are in the Prerelease string. - IncorrectVersionPartsCountForPrereleaseStringUsage=Version '{0}' must have exactly 3 parts for a Prerelease string to be used. - ModuleVersionShouldBeGreaterThanGalleryVersion=Module '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. - ModuleVersionIsAlreadyAvailableInTheGallery=The module '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. - CouldNotInstallNuGetProvider=NuGet provider is required to interact with NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed. - CouldNotInstallNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. - CouldNotUpgradeNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that required version of NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. - CouldNotFindDotnetCommand=For publish operations, dotnet command version '{0}' or newer is required to interact with the NuGet-based repositories. Please ensure that dotnet command version '{0}' or newer is installed and available under one of the paths specified in PATH environment variable value. You can also install the dotnet command by following the instructions specified at '{1}'. - CouldNotInstallNuGetBinaries2=PowerShellGet requires NuGet.exe (or dotnet command) and NuGet provider version '{0}' or newer to interact with the NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed and NuGet.exe (or dotnet command) is available under one of the paths specified in PATH environment variable value. - InstallNugetBinariesUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{2}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to upgrade NuGet.exe to the latest version and install the NuGet provider now? - InstallNuGetBinariesShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{3}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to install both the latest NuGet.exe and NuGet provider now? - InstallNugetExeUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to upgrade to the latest version of NuGet.exe now? - InstallNuGetExeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to install the latest version of NuGet.exe now? - InstallNuGetProviderShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{1}' or '{2}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. Do you want PowerShellGet to install and import the NuGet provider now? - InstallNuGetBinariesUpgradeShouldContinueCaption=NuGet.exe upgrade and NuGet provider installation are required to continue - InstallNuGetBinariesShouldContinueCaption=NuGet.exe and NuGet provider installation are required to continue - InstallNuGetExeUpgradeShouldContinueCaption=NuGet.exe upgrade is required to continue - InstallNuGetExeShouldContinueCaption=NuGet.exe is required to continue - InstallNuGetProviderShouldContinueCaption=NuGet provider is required to continue - DownloadingNugetExe=Installing NuGet.exe. - DownloadingNugetProvider=Installing NuGet provider. - ModuleNotFound=Module '{0}' was not found. - NoMatchFound=No match was found for the specified search criteria and module names '{0}'. - NoMatchFoundForScriptName=No match was found for the specified search criteria and script names '{0}'. - MatchInvalidType=The name '{0}' is a {1} not a {2}. - FailedToCreateCompressedModule=Failed to generate the compressed file for module '{0}'. - FailedToPublish=Failed to publish module '{0}': '{1}'. - PublishedSuccessfully=Successfully published module '{0}' to the module publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. - InvalidWebUri=The specified Uri '{0}' for parameter '{1}' is an invalid Web Uri. Please ensure that it meets the Web Uri requirements. - RepositoryAlreadyRegistered=The repository could not be registered because there exists a registered repository with Name '{0}' and SourceLocation '{1}'. To register another repository with Name '{2}', please unregister the existing repository using the Unregister-PSRepository cmdlet. - RepositoryToBeUnregisteredNotFound=The repository '{0}' was not removed because no repository was found with that name. Please run Get-PSRepository and ensure that a repository of that name is present. - RepositoryCannotBeUnregistered=The specified repository '{0}' cannot be unregistered. - RepositoryNotFound=No repository with the name '{0}' was found. - PSGalleryNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. Try again after specifying a valid repository name. You can use 'Register-PSRepository -Default' to register the PSGallery repository. - ParameterIsNotAllowedWithPSGallery=The PSGallery repository has pre-defined locations. The '{0}' parameter is not allowed, try again after removing the '{0}' parameter. - UseDefaultParameterSetOnRegisterPSRepository=Use 'Register-PSRepository -Default' to register the PSGallery repository. - RepositoryNameContainsWildCards=The repository name '{0}' should not have wildcards, correct it and try again. - InvalidRepository=The specified repository '{0}' is not a valid registered repository name. Please ensure that '{1}' is a registered repository. - RepositoryCannotBeRegistered=The specified repository '{0}' is unauthorized and cannot be registered. Try running with -Credential. - RepositoryRegistered=Successfully registered the repository '{0}' with source location '{1}'. - RepositoryUnregistered=Successfully unregistered the repository '{0}'. - PSGalleryPublishLocationIsMissing=The specified repository '{0}' does not have a valid PublishLocation. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - PSRepositoryScriptPublishLocationIsMissing=The specified repository '{0}' does not have a valid ScriptPublishLocation. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - ScriptSourceLocationIsMissing=The specified repository '{0}' does not have a valid ScriptSourceLocation. Retry after setting the ScriptSourceLocation for repository '{0}' to a valid NuGet endpoint for scripts using the Set-PSRepository cmdlet. - PublishModuleSupportsOnlyNuGetBasedPublishLocations=Publish-Module only supports the NuGet-based publish locations. The PublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - PublishScriptSupportsOnlyNuGetBasedPublishLocations=Publish-Script only supports the NuGet-based publish locations. The ScriptPublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - DynamicParameterHelpMessage=The dynamic parameter '{0}' is required for Find-Module and Install-Module when using the PackageManagement provider '{1}' and source location '{2}'. Please enter your value for the '{3}' dynamic parameter: - ProviderApiDebugMessage=In PowerShellGet Provider - '{0}'. - ModuleUninstallNotSupported=Module uninstallation is not supported. To remove a module, please delete the module folder. - FastPackageReference=The FastPackageReference is '{0}'. - PackageManagementProviderIsNotAvailable=The specified PackageManagement provider '{0}' is not available. - SpecifiedSourceName=Using the specified source names : '{0}'. - SpecifiedLocationAndOGP=The specified Location is '{0}' and PackageManagementProvider is '{1}'. - NoSourceNameIsSpecified=The -Repository parameter was not specified. PowerShellGet will use all of the registered repositories. - GettingPackageManagementProviderObject=Getting the provider object for the PackageManagement Provider '{0}'. - InvalidInputObjectValue=Invalid value is specified for InputObject parameter. - SpecifiedInstallationScope=The installation scope is specified to be '{0}'. - SourceLocationValueForPSGalleryCannotBeChanged=The SourceLocation value for the PSGallery repository can not be changed. - PublishLocationValueForPSGalleryCannotBeChanged=The PublishLocation value for the PSGallery repository can not be changed. - SpecifiedProviderName=The specified PackageManagement provider name '{0}'. - ProviderNameNotSpecified=User did not specify the PackageManagement provider name, trying with the provider name '{0}'. - SpecifiedProviderNotAvailable=The specified PackageManagement provider '{0}' is not available. - SpecifiedProviderDoesnotSupportPSModules=The specified PackageManagement Provider '{0}' does not support PowerShell Modules. PackageManagement Providers must support the 'supports-powershell-modules' feature. - PollingPackageManagementProvidersForLocation=Polling available PackageManagement Providers to find one that can support the specified source location '{0}'. - PollingSingleProviderForLocation=Resolving the source location '{0}' with PackageManagement Provider '{1}'. - FoundProviderForLocation=The PackageManagement provider '{0}' supports the source location '{1}'. - SpecifiedLocationCannotBeRegistered=The specified location '{0}' cannot be registered. - RepositoryDetails=Repository details, Name = '{0}', Location = '{1}'; IsTrusted = '{2}'; IsRegistered = '{3}'. - NotSupportedPowerShellGetFormatVersion=The specified module '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this module, '{2}'. - NotSupportedPowerShellGetFormatVersionScripts=The specified script '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this script, '{2}'. - PathNotFound=Cannot find the path '{0}' because it does not exist. - ModuleIsNotTrusted=Untrusted module '{0}'. - ScriptIsNotTrusted=Untrusted script '{0}'. - SkippedModuleDependency=Because dependent module '{0}' was skipped in the module dependencies list, users might not know how to install it. - MissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current module '{1}', ensure that its dependent module '{2}' is installed. - ExternallyManagedModuleDependencyIsInstalled=The externally managed, dependent module '{0}' is already installed on this computer. - ScriptMissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent module '{2}' is installed. - ScriptMissingExternallyManagedScriptDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent script '{2}' is installed. - ScriptExternallyManagedScriptDependencyIsInstalled=The externally managed, dependent script '{0}' is already installed on this computer. - UnableToResolveModuleDependency=PowerShellGet cannot resolve the module dependency '{0}' of the module '{1}' on the repository '{2}'. Verify that the dependent module '{3}' is available in the repository '{4}'. If this dependent module '{5}' is managed externally, add it to the ExternalModuleDependencies entry in the PSData section of the module manifest. - FindingModuleDependencies=Finding module dependencies for version '{1}' of the module '{0}' from repository '{2}'. - InstallingDependencyModule=Installing the dependency module '{0}' with version '{1}' for the module '{2}'. - InstallingDependencyScript=Installing the dependency script '{0}' with version '{1}' for the script '{2}'. - SavingDependencyModule=Saving the dependency module '{0}' with version '{1}' for the module '{2}'. - SavingDependencyScript=Saving the dependency script '{0}' with version '{1}' for the script '{2}'. - ModuleUninstallationSucceeded=Successfully uninstalled the module '{0}' from module base '{1}'. - ScriptUninstallationSucceeded=Successfully uninstalled the script '{0}' from script base '{1}'. - AdminPrivilegesRequiredForUninstall=You cannot uninstall the module '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - AdminPrivilegesRequiredForScriptUninstall=You cannot uninstall the script '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - ModuleUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Module '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. - ScriptUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Script '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. - UnableToUninstallModuleVersion=The module '{0}' of version '{1}' in module base folder '{2}' was installed without side-by-side version support. Some versions are installed in this module base with side-by-side version support. Uninstall other versions of this module before uninstalling the most current version. - UnableToUninstallAsOtherModulesNeedThisModule=The module '{0}' of version '{1}' in module base folder '{2}' cannot be uninstalled, because one or more other modules '{3}' are dependent on this module. Uninstall the modules that depend on this module before uninstalling module '{4}'. - UnableToUninstallAsOtherScriptsNeedThisScript=The script '{0}' of version '{1}' in script base folder '{2}' cannot be uninstalled, because one or more other scripts '{3}' are dependent on this script. Uninstall the scripts that depend on this script before uninstalling script '{4}'. - RepositoryIsNotTrusted=Untrusted repository - QueryInstallUntrustedPackage=You are installing the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from '{1}'? - QueryInstallUntrustedScriptPackage=You are installing the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the scripts from '{1}'? - QuerySaveUntrustedPackage=You are downloading the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the modules from '{1}'? - QuerySaveUntrustedScriptPackage=You are downloading the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the scripts from '{1}'? - SourceNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. - PSGalleryApiV2Deprecated=PowerShell Gallery v2 has been deprecated. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. - PSGalleryApiV2Discontinued=PowerShell Gallery v2 has been discontinued. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. - PowerShellGalleryUnavailable=PowerShell Gallery is currently unavailable. Please try again later. - PowerShellGetModuleIsNotInstalledProperly=The PowerShellGet module was not installed properly. Be sure that only one instance or version of the PowerShellGet module is installed in the path '{0}'. - PowerShelLGetModuleGotUpdated=The PowerShellGet module was updated successfully. Restart the process to use the updated version of the PowerShellGet module. - TagsShouldBeIncludedInManifestFile=Tags are now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest tag changes. You can run Update-ModuleManifest -Tags to update the manifest with tags. - ReleaseNotesShouldBeIncludedInManifestFile=ReleaseNotes is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ReleaseNotes changes. You can run Update-ModuleManifest -ReleaseNotes to update the manifest with ReleaseNotes. - LicenseUriShouldBeIncludedInManifestFile=LicenseUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' with the newest LicenseUri changes. You can run Update-ModuleManifest -LicenseUri to update the manifest with LicenseUri. - IconUriShouldBeIncludedInManifestFile=IconUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest IconUri changes. You can run Update-ModuleManifest -IconUri to update the manifest with IconUri. - ProjectUriShouldBeIncludedInManifestFile=ProjectUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ProjectUri changes. You can run Update-ModuleManifest -ProjectUri to update the manifest with ProjectUri. - ShouldIncludeFunctionsToExport=This module '{0}' has exported functions. As a best practice, include exported functions in the module manifest file(.psd1). You can run Update-ModuleManifest -FunctionsToExport to update the manifest with ExportedFunctions field. - ShouldIncludeCmdletsToExport=This module '{0}' has exported cmdlets. As a best practice, include exported cmdlets in the module manifest file(.psd1). You can run Update-ModuleManifest -CmdletsToExport to update the manifest with ExportedCmdlets field. - ShouldIncludeDscResourcesToExport=This module '{0}' has exported DscResources. As a best practice, include exported DSC resources in the module manifest file(.psd1). If your PowerShell version is higher than 5.0, run Update-ModuleManifest -DscResourcesToExport to update the manifest with ExportedDscResources field. - UpdateModuleManifestPathCannotFound=Cannot load the manifest file '{0}' properly. Please specify the correct manifest path. - UpdatedModuleManifestNotValid=Cannot update the manifest file '{0}' because the manifest is not valid. Verify that the manifest file is valid, and then try again.'{1}' - ExportedDscResourcesNotSupportedOnLowerPowerShellVersion=The ExportedDscResources property is not supported in module manifests on PowerShell versions that are older than 5.0. Remove the value for the parameter 'DscResourcesToExport', and then try again. - CompatiblePSEditionsNotSupportedOnLowerPowerShellVersion=The CompatiblePSEditions property is not supported in module manifests on PowerShell versions that are older than 5.1. Remove the value for the parameter 'CompatiblePSEditions', and then try again. - ExternalModuleDependenciesNotSpecifiedInRequiredOrNestedModules='{0}' is listed in ExternalModuleDependencies, but it is not found in either the RequiredModules or NestedModules properties. Verify that this module is required for ExternalModuleDependencies, and then add it to NestedModules or RequiredModules. - TestModuleManifestFail=Cannot update the manifest properly. '{0}' - PackageManagementProvidersNotInModuleBaseFolder=PackageManagementProvider '{0}' is not found in the module base '{1}'. Verify that the PackageManagementProvider specified is within the module base. - UpdateManifestContentMessage=Update manifest file with new contents: - InvalidPackageManagementProviderValue=The PackageManagementProvider value cannot be '{0}'. Valid values for provider names include '{1}', and the default value for this parameter is '{2}'. - PowerShellGetUpdateIsNotSupportedOnLowerPSVersions=Self update of the PowerShellGet module is supported only in PowerShell 5.0 and newer releases. It is not supported in PowerShell 3.0 or 4.0. - ScriptVersionShouldBeGreaterThanGalleryVersion=Script '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. - ScriptVersionIsAlreadyAvailableInTheGallery=The script '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. - ScriptPrereleaseStringShouldBeGreaterThanGalleryPrereleaseString=Script '{0}' with version '{1}' and prerelease '{2}' cannot be published. The prerelease string must exceed the current prerelease string '{3}' that exists in the repository '{4}', or you must specify -Force. - ScriptParseError=The specified script file '{0}' has parse errors, try again after fixing the parse errors. - InvalidScriptToPublish=Script file '{0}' cannot be published because it does not have the required script metadata. Run Update-ScriptFileInfo -Path '{1}' to add the script metadata. - FailedToCreateCompressedScript=Failed to generate the compressed file for script '{0}'. - FailedToPublishScript=Failed to publish script '{0}': '{1}'. - PublishedScriptSuccessfully=Successfully published script '{0}' to the publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. - UnableToResolveScriptDependency=PowerShellGet cannot resolve the {0} dependency '{1}' of the script '{2}' on the repository '{3}'. Verify that the dependent {0} '{1}' is available in the repository '{3}'. If this dependent {0} '{1}' is managed externally, add it to the '{4}' entry in the script metadata. - InvalidVersion=Cannot convert value '{0}' to type 'System.Version'. - InvalidGuid=Cannot convert value '{0}' to type 'System.Guid'. - InvalidParameterValue=The specified value '{0}' for the parameter '{1}' is invalid. Ensure that it does not contain '<#' or '#>'. - MissingPSScriptInfo=PSScriptInfo is not specified in the script file '{0}'. You can use the Update-ScriptFileInfo with -Force or New-ScriptFileInfo cmdlet to add the PSScriptInfo to the script file. - MissingRequiredPSScriptInfoProperties=Script '{0}' is missing required metadata properties. Verify that the script file has Version, Guid, Description and Author properties. You can use the Update-ScriptFileInfo or New-ScriptFileInfo cmdlet to add or update the PSScriptInfo to the script file. - SkippedScriptDependency=Because dependent script '{0}' was skipped in the script dependencies list, users might not know how to install it. - SourceLocationPathsForModulesAndScriptsShouldBeEqual=SourceLocation '{0}' and ScriptSourceLocation '{1}' should be same for SMB Share or Local directory based repositories. - PublishLocationPathsForModulesAndScriptsShouldBeEqual=PublishLocation '{0}' and ScriptPublishLocation '{1}' should be same for SMB Share or Local directory based repositories. - SpecifiedNameIsAlearyUsed=The specified name '{0}' is already used for a different item on the specified repository '{1}'. Run '{2} -Name {0} -Repository {1}' to check whether the specified name '{0}' is already taken. - InvalidScriptFilePath=The script file path '{0}' is not valid. The value of the Path argument must resolve to a single file that has a '.ps1' extension. Change the value of the Path argument to point to a valid ps1 file, and then try again. - NuGetApiKeyIsRequiredForNuGetBasedGalleryService=NuGetApiKey is required for publishing a module or script file to the specified repository '{0}' whose publish location is '{1}'. Try again after specifying a valid value for the NuGetApiKey parameter. To get your API key, view your profile page. - ScriptFileExist=The specified script file '{0}' already exists. - InvalidEnvironmentVariableName=The specified environment variable name '{0}' exceeded the allowed limit of '{1}' characters. - PublishLocation=Publish Location:'{0}'. - ScriptPATHPromptCaption=PATH Environment Variable Change - ScriptPATHPromptQuery=Your system has not been configured with a default script installation path yet, which means you can only run a script by specifying the full path to the script file. This action places the script into the folder '{0}', and adds that folder to your PATH environment variable. Do you want to add the script installation path '{0}' to the PATH environment variable? - AddedScopePathToProcessSpecificPATHVariable=Added scripts installation location '{0}' for '{1}' scope to process specific PATH environment variable. - AddedScopePathToPATHVariable=Added scripts installation location '{0}' for '{1}' scope to PATH environment variable. - FilePathInFileListNotWithinModuleBase=Path '{0}' defined in FileList is not within module base '{1}'. Provide the correct FileList parameters and then try again. - ManifestFileReadWritePermissionDenied=The current user does not have read-write permissions for the file:'{0}'. Check the file permissions and then try again. - MissingTheRequiredPathOrPassThruParameter=The Path or PassThru parameter is required for creating the script file info. A new script file will be created with the script file info when the Path parameter is specified. Script file info will be returned if the PassThru parameter is specified. Try again after specifying the required parameter. - DescriptionParameterIsMissingForAddingTheScriptFileInfo=Description parameter is missing for adding the metadata to the script file. Try again after specifying the description. - UnableToAddPSScriptInfo=Unable to add PSScriptInfo to the script file '{0}'. You can use the New-ScriptFileInfo cmdlet to add the metadata to the existing script file. - RegisterVSTSFeedAsNuGetPackageSource=Publishing to a VSTS package management feed '{0}' requires it to be registered as a NuGet package source. Retry after adding this source '{0}' as NuGet package source by following the instructions specified at '{1}' - InvalidModuleAuthenticodeSignature=The module '{0}' cannot be installed or updated because the authenticode signature of the file '{1}' is not valid. - InvalidCatalogSignature=The module '{0}' cannot be installed because the catalog signature in '{1}' does not match the hash generated from the module. - AuthenticodeIssuerMismatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' from root certificate authority '{3}' is not matching with the authenticode issuer '{4}' of the previously-installed module '{5}' with version '{6}' from root certificate authority '{7}'. If you still want to install or update, use -SkipPublisherCheck parameter. - ModuleCommandAlreadyAvailable=The following commands are already available on this system:'{0}'. This module '{1}' may override the existing commands. If you still want to install this module '{1}', use -AllowClobber parameter. - CatalogFileFound=Found the catalog file '{0}' in the module '{1}' contents. - CatalogFileNotFoundInAvailableModule=Catalog file '{0}' is not found in the contents of the previously-installed module '{1}' with the same name. - CatalogFileNotFoundInNewModule=Catalog file '{0}' is not found in the contents of the module '{1}' being installed. - ValidAuthenticodeSignature=Valid authenticode signature found in the catalog file '{0}' for the module '{1}'. - ValidAuthenticodeSignatureInFile=Valid authenticode signature found in the file '{0}' for the module '{1}'. - ValidatingCatalogSignature=Validating the '{0}' module files for catalog signing using the catalog file '{1}'. - AuthenticodeIssuerMatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' matches with the authenticode issuer '{3}' of the previously-installed module '{4}' with version '{5}'. - ValidCatalogSignature=The catalog signature in '{0}' of the module '{1}' is valid and matches with the hash generated from the module contents. - SkippingPublisherCheck=Skipping the Publisher check for the version '{0}' of module '{1}'. - SourceModuleDetailsForPublisherValidation=For publisher validation, using the previously-installed module '{0}' with version '{1}' under '{2}' with publisher name '{3}' from root certificate authority '{4}'. Is this module signed by Microsoft: '{5}'. - NewModuleVersionDetailsForPublisherValidation=For publisher validation, current module '{0}' with version '{1}' with publisher name '{2}' from root certificate authority '{3}'. Is this module signed by Microsoft: '{4}'. - PublishersMatch=Publisher '{0}' of the new module '{1}' with version '{2}' matches with the publisher '{3}' of the previously-installed module '{4}' with version '{5}'. Both versions are signed with a Microsoft root certificate. - PublishersMismatch=A Microsoft-signed module named '{0}' with version '{1}' that was previously installed conflicts with the new module '{2}' from publisher '{3}' with version '{4}'. Installing the new module may result in system instability. If you still want to install or update, use -SkipPublisherCheck parameter. - ModuleIsNotCatalogSigned=The version '{0}' of the module '{1}' being installed is not catalog signed. Ensure that the version '{0}' of the module '{1}' has the catalog file '{2}' and signed with the same publisher '{3}' as the previously-installed module '{1}' with version '{4}' under the directory '{5}'. If you still want to install or update, use -SkipPublisherCheck parameter. - SentEnvironmentVariableChangeMessage=Successfully broadcasted the Environment variable changes. - UnableToSendEnvironmentVariableChangeMessage=Error in broadcasting the Environment variable changes. - LicenseUriNotSpecified='LicenseUri' is not specified. 'LicenseUri' must be provided when user license acceptance is required. - LicenseTxtNotFound=License.txt not Found. License.txt must be provided when user license acceptance is required. - LicenseTxtEmpty=License.txt is empty. - requireLicenseAcceptanceNotSupported=Require License Acceptance is not supported on Format version '{0}'. - AcceptanceLicenseQuery=Do you accept the license terms for module '{0}'. - ForceAcceptLicense=License Acceptance is required for module '{0}'. Please specify '-AcceptLicense' to perform this operation. - InvalidValueBoolean=The specified value '{0}' for the parameter '{1}' is invalid. It should be $true or $false. - UserDeclinedLicenseAcceptance=User declined license acceptance. - AcceptLicense=License Acceptance - RequiredScriptVersion=REQUIREDSCRIPTS: Required version of script '{0}' is '{1}'. - RequiredScriptVersoinFormat=, :, :[], :[,], :[,] - FailedToParseRequiredScripts=Cannot parse REQUIREDSCRIPTS '{0}'. Acceptable formats are: '{1}'. - FailedToParseRequiredScriptsVersion=Version format error: {0}, '{1}'. Acceptable formats are: '{2}'. - PublishersMismatchAsWarning=Module '{0}' version '{1}' published by '{2}' will be superceded by version '{3}' published by '{4}'. If you do not trust the new publisher, uninstall the module. - UnableToDownloadThePackage=The PackageManagement provider '{0}' is unable to download the package '{1}' version '{2}' to '{3}' path. - ValidatingTheModule=Validating the '{0}' module contents under '{1}' path. - ModuleValidationFailed=Unable to validate the '{0}' module contents under '{1}' path. - ValidatedModuleManifestFile=Test-ModuleManifest successfully validated the module manifest file '{0}'. - ValidateModuleAuthenticodeSignature=Validating the authenticode signature and publisher of the catalog file or module manifest file of the module '{0}'. - ValidateModuleCommandAlreadyAvailable=Checking for possible command collisions for the module '{0}' commands. - UnauthorizedAccessError=Access to the path '{0}' is denied. -###PSLOC -'@ - -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAUMj4wb0r0V933 -# Kxwf6eV800i5N8GnGMPseytvd1lnEKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLIu7f8le -# gQ0ndTd8gyAEe0fnNBrhCsEUfHD+upEyb7owQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQCWjkfbjU+QuaidvjCSfT9ZWmVRt7eVzD7wasyZEXwC -# uPWX5GwoR4xPMYzA/ohAElfSdrP6HWH9R+2sdXnUCJK+7w6N9wbB+S9Da0r3UcHj -# acV7JEftJK6ZwZ9EyebTgHv6Oa0xhuaRh/BINH4OhfIZEy3A2Lg4KhvNMjGKHTi2 -# agmGkKLUqySkyv48c/HCApLbZ3ugNqI06C284kostGCqpuSg6HRDcGPDzZSkT6l4 -# u1veNMmcpeGzd3NTNy5fkt8jQt3OUQ9NmfYN5nxh1BgDB1kyDm5lWMiZqFqlLS9T -# sG/jgYnqnprYVqTa8rYsSdmn8y1idSkQ1M715PQlOYNuoYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIOjiV1RbUVB0CleqHpkpsACXwys4XtATfTNyD7Bu -# Zv+1AgZfYPphXsEYEzIwMjAwOTIyMjIxOTUxLjg2N1owBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJy9uo++RqBmoAAAA -# AAEnMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 -# LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPgB3nERnk6fS40vvWeD -# 3HCgM9Ep4xTIQiPnJXE9E+HkZVtTsPemoOyhfNAyF95E/rUvXOVTUcJFL7Xb16jT -# KPXONsCWY8DCixSDIiid6xa30TiEWVcIZRwiDlcx29D467OTav5rA1G6TwAEY5rQ -# jhUHLrOoJgfJfakZq6IHjd+slI0/qlys7QIGakFk2OB6mh/ln/nS8G4kNRK6Do4g -# xDtnBSFLNfhsSZlRSMDJwFvrZ2FCkaoexd7rKlUNOAAScY411IEqQeI1PwfRm3aW -# bS8IvAfJPC2Ah2LrtP8sKn5faaU8epexje7vZfcZif/cbxgUKStJzqbdvTBNc93n -# /Z8CAwEAAaOCARswggEXMB0GA1UdDgQWBBTl9JZVgF85MSRbYlOJXbhY022V8jAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAKyo180VXHBqVnjZwQy7NlzXbo2+W5 -# qfHxR7ANV5RBkRkdGamkwUcDNL+DpHObFPJHa0oTeYKE0Zbl1MvvfS8RtGGdhGYG -# CJf+BPd/gBCs4+dkZdjvOzNyuVuDPGlqQ5f7HS7iuQ/cCyGHcHYJ0nXVewF2Lk+J -# lrWykHpTlLwPXmCpNR+gieItPi/UMF2RYTGwojW+yIVwNyMYnjFGUxEX5/DtJjRZ -# mg7PBHMrENN2DgO6wBelp4ptyH2KK2EsWT+8jFCuoKv+eJby0QD55LN5f8SrUPRn -# K86fh7aVOfCglQofo5ABZIGiDIrg4JsV4k6p0oBSIFOAcqRAhiH+1spCMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow -# QTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUAs5W4TmyDHMRM7iz6mgGojqvXHzOggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMUsu8wIhgPMjAyMDA5MjIyMTI5MTlaGA8yMDIwMDkyMzIxMjkxOVowdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA4xSy7wIBADAKAgEAAgIVPgIB/zAHAgEAAgIRtjAK -# AgUA4xYEbwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEMD4esQRMLwQdhk -# Co1zgvmclcwl3lYYpk1oMh1ndsU3+97Rt6FV3adS4Hezc/K94oQKjcxtMVzLzQhG -# agM6XlqB31VD8n2nxVuaWD1yp2jm/0IvfL9nFMHJRhgANMiBdHqvqNrd86c/Kryq -# sI0Ch0sOx9wg3BozzqQhmdNjf9c6MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEnL26j75GoGagAAAAAAScwDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQg6wLE4god37E/zRxYUqM58DbGQBLJxEvt6c6xU1jsZqYwgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCAbkuhLEoYdahb/BUyVszO2VDi6kB3MSaof/+8u -# 7SM+IjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# Jy9uo++RqBmoAAAAAAEnMCIEIK4r6N3NISekswMCG1kSBJCCCePrlLDQWbMKz0wt -# Lj6CMA0GCSqGSIb3DQEBCwUABIIBACY3cZHtQ2k6zfVG4NotND24Zp0mlZAFe4A+ -# BFDISaaIXS86mO142Y9Hkm9rAMBCIJbKB/Yj1yf1gbygqNCoJDHFxZbdmx5XbShg -# PVfAQl00TuyQ86VYnFJtvdDODUnwuxErAFR1zyiGGBKYtQWhXNRmbhb1kRkWp5Fg -# s9mwzZUoGI2UzXDtYb6HkjNIOMxCbWW/27YfCBnOkiP2tJMIU79R4xY4vwQeWkKW -# dgtyHv+d7eBOd1aOi8HvqCstH6x06RC3DsYfZ/NbYSGuZpjSaSDDC7A1KvdPo6ti -# UYayEGi9iXfqnAv4Iyzo3OWIN2M2iqepP2xwL8Ne2q1ImLpiZF0= -# SIG # End signature block +######################################################################################### +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Localized PSGet.Resource.psd1 +# +######################################################################################### + +ConvertFrom-StringData @' +###PSLOC + InstallModulewhatIfMessage=Version '{1}' of module '{0}' + InstallScriptwhatIfMessage=Version '{1}' of script '{0}' + UpdateModulewhatIfMessage=Version '__OLDVERSION__' of module '{0}', updating to version '{1}' + UpdateScriptwhatIfMessage=Version '__OLDVERSION__' of script '{0}', updating to version '{1}' + PublishModulewhatIfMessage=Version '{0}' of module '{1}' + PublishScriptwhatIfMessage=Version '{0}' of script '{1}' + NewScriptFileInfowhatIfMessage=Creating the '{0}' PowerShell Script file + UpdateScriptFileInfowhatIfMessage=Updating the '{0}' PowerShell Script file + NameShouldNotContainWildcardCharacters=The specified name '{0}' should not contain any wildcard characters, please correct it and try again. + AllVersionsCannotBeUsedWithOtherVersionParameters=You cannot use the parameter AllVersions with RequiredVersion, MinimumVersion or MaximumVersion in the same command. + VersionRangeAndRequiredVersionCannotBeSpecifiedTogether=You cannot use the parameters RequiredVersion and either MinimumVersion or MaximumVersion in the same command. Specify only one of these parameters in your command. + RequiredVersionAllowedOnlyWithSingleModuleName=The RequiredVersion parameter is allowed only when a single module name is specified as the value of the Name parameter, without any wildcard characters. + MinimumVersionIsGreaterThanMaximumVersion=The specified MinimumVersion '{0}' is greater than the specified MaximumVersion '{1}'. + AllowPrereleaseRequiredToUsePrereleaseStringInVersion=The '-AllowPrerelease' parameter must be specified when using the Prerelease string in MinimumVersion, MaximumVersion, or RequiredVersion. + UpdateModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to update modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or update '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + InstallModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + InstallScriptAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install scripts in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + AdministratorRightsNeededOrSpecifyCurrentUserScope=Administrator rights are required to install or update. Log on to the computer with an account that has Administrator rights, and then try again, or install by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + VersionParametersAreAllowedOnlyWithSingleName=The RequiredVersion, MinimumVersion, MaximumVersion, AllVersions or AllowPrerelease parameters are allowed only when you specify a single name as the value of the Name parameter, without any wildcard characters. + PathIsNotADirectory=The specified path '{0}' is not a valid directory. + ModuleAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Module, and add the -Force parameter. + ScriptAlreadyInstalled=Version '{0}' of script '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Script, and add the -Force parameter. + CommandAlreadyAvailable=A command with name '{0}' is already available on this system. This script '{0}' may override the existing command. If you still want to install this script '{0}', use -Force parameter. + ModuleAlreadyInstalledSxS=Version '{0}' of module '{1}' is already installed at '{2}'. To install version '{3}', run Install-Module and add the -Force parameter, this command will install version '{5}' side-by-side with version '{4}'. + ModuleAlreadyInstalledVerbose=Version '{0}' of module '{1}' is already installed at '{2}'. + ScriptAlreadyInstalledVerbose=Version '{0}' of script '{1}' is already installed at '{2}'. + ModuleWithRequiredVersionAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To reinstall this version '{3}', run Install-Module or Updated-Module cmdlet with the -Force parameter. + InvalidPSModule=The module '{0}' cannot be installed or updated because it is not a properly-formed module. + InvalidPowerShellScriptFile=The script '{0}' cannot be installed or updated because it is not a properly-formed script. + InvalidAuthenticodeSignature=The module '{0}' cannot be installed or updated because the Authenticode signature for the file '{1}' is not valid. + ModuleNotInstalledOnThisMachine=Module '{0}' was not updated because no valid module was found in the module directory. Verify that the module is located in the folder specified by $env:PSModulePath. + ScriptNotInstalledOnThisMachine=Script '{0}' was not updated because no valid script was found in the script directories '{1}' and '{2}'. + AdminPrivilegesRequiredForUpdate=Module '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that directory. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + AdminPrivilegesRequiredForScriptUpdate=Script '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that script. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + ModuleNotInstalledUsingPowerShellGet=Module '{0}' was not installed by using Install-Module, so it cannot be updated. + ScriptNotInstalledUsingPowerShellGet=Script '{0}' was not installed by using Install-Script, so it cannot be updated. + DownloadingModuleFromGallery=Downloading module '{0}' with version '{1}' from the repository '{2}'. + DownloadingScriptFromGallery=Downloading script '{0}' with version '{1}' from the repository '{2}'. + NoUpdateAvailable=No updates were found for module '{0}'. + NoScriptUpdateAvailable=No updates were found for script '{0}'. + FoundModuleUpdate=An update for the module '{0}' was found with version '{1}'. + FoundScriptUpdate=An update for the script '{0}' was found with version '{1}'. + InvalidPSModuleDuringUpdate= Module '{0}' was not updated because the module in the repository '{1}' is not a valid Windows PowerShell module. + ModuleGotUpdated=Module '{0}' has been updated successfully. + TestingModuleInUse=Testing if the module to update is in use. + ModuleDestination= The specified module will be installed in '{0}'. + ScriptDestination= The specified script will be installed in '{0}' and its dependent modules will be installed in '{1}'. + ModuleIsInUse=Module '{0}' is in currently in use or you don't have the required permissions. + ModuleInstalledSuccessfully=Module '{0}' was installed successfully to path '{1}'. + ModuleSavedSuccessfully=Module '{0}' was saved successfully to path '{1}'. + ScriptInstalledSuccessfully=Script '{0}' was installed successfully to path '{1}'. + ScriptSavedSuccessfully=Script '{0}' was saved successfully to path '{1}'. + CheckingForModuleUpdate= Checking for updates for module '{0}'. + CheckingForScriptUpdate= Checking for updates for script '{0}'. + ModuleInUseWithProcessDetails=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the following applications: '{2}'. + ModuleVersionInUse=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the applications. + ModuleNotAvailableLocally=The specified module '{0}' was not published because no module with that name was found in any module directory. + InvalidModulePathToPublish=The specified module with path '{0}' was not published because no valid module was found with that path. + ModuleWithRequiredVersionNotAvailableLocally= The specified module '{0}' with version '{1}' was not published because no module with that name and version was found in any module directory. + AmbiguousModuleName=Modules with the name '{0}' are available under multiple paths. Add the -RequiredVersion parameter or the -Path parameter to specify the module to publish. + AmbiguousModulePath=Multiple versions are available under the specified module path '{0}'. Specify the full path to the module to be published. + PublishModuleLocation=Module '{0}' was found in '{1}'. + InvalidModuleToPublish=Module '{0}' cannot be published because it does not have a module manifest file. Run New-ModuleManifest -Path to create a module manifest with metadata before publishing. + MissingRequiredManifestKeys=Module '{0}' cannot be published because it is missing required metadata. Verify that the module manifest specifies Description and Author. + InvalidCharactersInPrereleaseString=The Prerelease string '{0}' contains invalid characters. Please ensure that only characters 'a-zA-Z0-9' and possibly hyphen ('-') at the beginning are in the Prerelease string. + IncorrectVersionPartsCountForPrereleaseStringUsage=Version '{0}' must have exactly 3 parts for a Prerelease string to be used. + ModuleVersionShouldBeGreaterThanGalleryVersion=Module '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. + ModuleVersionIsAlreadyAvailableInTheGallery=The module '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. + CouldNotInstallNuGetProvider=NuGet provider is required to interact with NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed. + CouldNotInstallNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. + CouldNotUpgradeNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that required version of NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. + CouldNotFindDotnetCommand=For publish operations, dotnet command version '{0}' or newer is required to interact with the NuGet-based repositories. Please ensure that dotnet command version '{0}' or newer is installed and available under one of the paths specified in PATH environment variable value. You can also install the dotnet command by following the instructions specified at '{1}'. + CouldNotInstallNuGetBinaries2=PowerShellGet requires NuGet.exe (or dotnet command) and NuGet provider version '{0}' or newer to interact with the NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed and NuGet.exe (or dotnet command) is available under one of the paths specified in PATH environment variable value. + InstallNugetBinariesUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{2}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to upgrade NuGet.exe to the latest version and install the NuGet provider now? + InstallNuGetBinariesShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{3}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to install both the latest NuGet.exe and NuGet provider now? + InstallNugetExeUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to upgrade to the latest version of NuGet.exe now? + InstallNuGetExeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to install the latest version of NuGet.exe now? + InstallNuGetProviderShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{1}' or '{2}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. Do you want PowerShellGet to install and import the NuGet provider now? + InstallNuGetBinariesUpgradeShouldContinueCaption=NuGet.exe upgrade and NuGet provider installation are required to continue + InstallNuGetBinariesShouldContinueCaption=NuGet.exe and NuGet provider installation are required to continue + InstallNuGetExeUpgradeShouldContinueCaption=NuGet.exe upgrade is required to continue + InstallNuGetExeShouldContinueCaption=NuGet.exe is required to continue + InstallNuGetProviderShouldContinueCaption=NuGet provider is required to continue + DownloadingNugetExe=Installing NuGet.exe. + DownloadingNugetProvider=Installing NuGet provider. + ModuleNotFound=Module '{0}' was not found. + NoMatchFound=No match was found for the specified search criteria and module names '{0}'. + NoMatchFoundForScriptName=No match was found for the specified search criteria and script names '{0}'. + MatchInvalidType=The name '{0}' is a {1} not a {2}. + FailedToCreateCompressedModule=Failed to generate the compressed file for module '{0}'. + FailedToPublish=Failed to publish module '{0}': '{1}'. + PublishedSuccessfully=Successfully published module '{0}' to the module publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. + InvalidWebUri=The specified Uri '{0}' for parameter '{1}' is an invalid Web Uri. Please ensure that it meets the Web Uri requirements. + RepositoryAlreadyRegistered=The repository could not be registered because there exists a registered repository with Name '{0}' and SourceLocation '{1}'. To register another repository with Name '{2}', please unregister the existing repository using the Unregister-PSRepository cmdlet. + RepositoryToBeUnregisteredNotFound=The repository '{0}' was not removed because no repository was found with that name. Please run Get-PSRepository and ensure that a repository of that name is present. + RepositoryCannotBeUnregistered=The specified repository '{0}' cannot be unregistered. + RepositoryNotFound=No repository with the name '{0}' was found. + PSGalleryNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. Try again after specifying a valid repository name. You can use 'Register-PSRepository -Default' to register the PSGallery repository. + ParameterIsNotAllowedWithPSGallery=The PSGallery repository has pre-defined locations. The '{0}' parameter is not allowed, try again after removing the '{0}' parameter. + UseDefaultParameterSetOnRegisterPSRepository=Use 'Register-PSRepository -Default' to register the PSGallery repository. + RepositoryNameContainsWildCards=The repository name '{0}' should not have wildcards, correct it and try again. + InvalidRepository=The specified repository '{0}' is not a valid registered repository name. Please ensure that '{1}' is a registered repository. + RepositoryCannotBeRegistered=The specified repository '{0}' is unauthorized and cannot be registered. Try running with -Credential. + RepositoryRegistered=Successfully registered the repository '{0}' with source location '{1}'. + RepositoryUnregistered=Successfully unregistered the repository '{0}'. + PSGalleryPublishLocationIsMissing=The specified repository '{0}' does not have a valid PublishLocation. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + PSRepositoryScriptPublishLocationIsMissing=The specified repository '{0}' does not have a valid ScriptPublishLocation. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + ScriptSourceLocationIsMissing=The specified repository '{0}' does not have a valid ScriptSourceLocation. Retry after setting the ScriptSourceLocation for repository '{0}' to a valid NuGet endpoint for scripts using the Set-PSRepository cmdlet. + PublishModuleSupportsOnlyNuGetBasedPublishLocations=Publish-Module only supports the NuGet-based publish locations. The PublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + PublishScriptSupportsOnlyNuGetBasedPublishLocations=Publish-Script only supports the NuGet-based publish locations. The ScriptPublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + DynamicParameterHelpMessage=The dynamic parameter '{0}' is required for Find-Module and Install-Module when using the PackageManagement provider '{1}' and source location '{2}'. Please enter your value for the '{3}' dynamic parameter: + ProviderApiDebugMessage=In PowerShellGet Provider - '{0}'. + ModuleUninstallNotSupported=Module uninstallation is not supported. To remove a module, please delete the module folder. + FastPackageReference=The FastPackageReference is '{0}'. + PackageManagementProviderIsNotAvailable=The specified PackageManagement provider '{0}' is not available. + SpecifiedSourceName=Using the specified source names : '{0}'. + SpecifiedLocationAndOGP=The specified Location is '{0}' and PackageManagementProvider is '{1}'. + NoSourceNameIsSpecified=The -Repository parameter was not specified. PowerShellGet will use all of the registered repositories. + GettingPackageManagementProviderObject=Getting the provider object for the PackageManagement Provider '{0}'. + InvalidInputObjectValue=Invalid value is specified for InputObject parameter. + SpecifiedInstallationScope=The installation scope is specified to be '{0}'. + SourceLocationValueForPSGalleryCannotBeChanged=The SourceLocation value for the PSGallery repository can not be changed. + PublishLocationValueForPSGalleryCannotBeChanged=The PublishLocation value for the PSGallery repository can not be changed. + SpecifiedProviderName=The specified PackageManagement provider name '{0}'. + ProviderNameNotSpecified=User did not specify the PackageManagement provider name, trying with the provider name '{0}'. + SpecifiedProviderNotAvailable=The specified PackageManagement provider '{0}' is not available. + SpecifiedProviderDoesnotSupportPSModules=The specified PackageManagement Provider '{0}' does not support PowerShell Modules. PackageManagement Providers must support the 'supports-powershell-modules' feature. + PollingPackageManagementProvidersForLocation=Polling available PackageManagement Providers to find one that can support the specified source location '{0}'. + PollingSingleProviderForLocation=Resolving the source location '{0}' with PackageManagement Provider '{1}'. + FoundProviderForLocation=The PackageManagement provider '{0}' supports the source location '{1}'. + SpecifiedLocationCannotBeRegistered=The specified location '{0}' cannot be registered. + RepositoryDetails=Repository details, Name = '{0}', Location = '{1}'; IsTrusted = '{2}'; IsRegistered = '{3}'. + NotSupportedPowerShellGetFormatVersion=The specified module '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this module, '{2}'. + NotSupportedPowerShellGetFormatVersionScripts=The specified script '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this script, '{2}'. + PathNotFound=Cannot find the path '{0}' because it does not exist. + ModuleIsNotTrusted=Untrusted module '{0}'. + ScriptIsNotTrusted=Untrusted script '{0}'. + SkippedModuleDependency=Because dependent module '{0}' was skipped in the module dependencies list, users might not know how to install it. + MissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current module '{1}', ensure that its dependent module '{2}' is installed. + ExternallyManagedModuleDependencyIsInstalled=The externally managed, dependent module '{0}' is already installed on this computer. + ScriptMissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent module '{2}' is installed. + ScriptMissingExternallyManagedScriptDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent script '{2}' is installed. + ScriptExternallyManagedScriptDependencyIsInstalled=The externally managed, dependent script '{0}' is already installed on this computer. + UnableToResolveModuleDependency=PowerShellGet cannot resolve the module dependency '{0}' of the module '{1}' on the repository '{2}'. Verify that the dependent module '{3}' is available in the repository '{4}'. If this dependent module '{5}' is managed externally, add it to the ExternalModuleDependencies entry in the PSData section of the module manifest. + FindingModuleDependencies=Finding module dependencies for version '{1}' of the module '{0}' from repository '{2}'. + InstallingDependencyModule=Installing the dependency module '{0}' with version '{1}' for the module '{2}'. + InstallingDependencyScript=Installing the dependency script '{0}' with version '{1}' for the script '{2}'. + SavingDependencyModule=Saving the dependency module '{0}' with version '{1}' for the module '{2}'. + SavingDependencyScript=Saving the dependency script '{0}' with version '{1}' for the script '{2}'. + ModuleUninstallationSucceeded=Successfully uninstalled the module '{0}' from module base '{1}'. + ScriptUninstallationSucceeded=Successfully uninstalled the script '{0}' from script base '{1}'. + AdminPrivilegesRequiredForUninstall=You cannot uninstall the module '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + AdminPrivilegesRequiredForScriptUninstall=You cannot uninstall the script '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + ModuleUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Module '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. + ScriptUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Script '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. + UnableToUninstallModuleVersion=The module '{0}' of version '{1}' in module base folder '{2}' was installed without side-by-side version support. Some versions are installed in this module base with side-by-side version support. Uninstall other versions of this module before uninstalling the most current version. + UnableToUninstallAsOtherModulesNeedThisModule=The module '{0}' of version '{1}' in module base folder '{2}' cannot be uninstalled, because one or more other modules '{3}' are dependent on this module. Uninstall the modules that depend on this module before uninstalling module '{4}'. + UnableToUninstallAsOtherScriptsNeedThisScript=The script '{0}' of version '{1}' in script base folder '{2}' cannot be uninstalled, because one or more other scripts '{3}' are dependent on this script. Uninstall the scripts that depend on this script before uninstalling script '{4}'. + RepositoryIsNotTrusted=Untrusted repository + QueryInstallUntrustedPackage=You are installing the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from '{1}'? + QueryInstallUntrustedScriptPackage=You are installing the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the scripts from '{1}'? + QuerySaveUntrustedPackage=You are downloading the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the modules from '{1}'? + QuerySaveUntrustedScriptPackage=You are downloading the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the scripts from '{1}'? + SourceNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. + PSGalleryApiV2Deprecated=PowerShell Gallery v2 has been deprecated. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. + PSGalleryApiV2Discontinued=PowerShell Gallery v2 has been discontinued. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. + PowerShellGalleryUnavailable=PowerShell Gallery is currently unavailable. Please try again later. + PowerShellGetModuleIsNotInstalledProperly=The PowerShellGet module was not installed properly. Be sure that only one instance or version of the PowerShellGet module is installed in the path '{0}'. + PowerShelLGetModuleGotUpdated=The PowerShellGet module was updated successfully. Restart the process to use the updated version of the PowerShellGet module. + TagsShouldBeIncludedInManifestFile=Tags are now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest tag changes. You can run Update-ModuleManifest -Tags to update the manifest with tags. + ReleaseNotesShouldBeIncludedInManifestFile=ReleaseNotes is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ReleaseNotes changes. You can run Update-ModuleManifest -ReleaseNotes to update the manifest with ReleaseNotes. + LicenseUriShouldBeIncludedInManifestFile=LicenseUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' with the newest LicenseUri changes. You can run Update-ModuleManifest -LicenseUri to update the manifest with LicenseUri. + IconUriShouldBeIncludedInManifestFile=IconUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest IconUri changes. You can run Update-ModuleManifest -IconUri to update the manifest with IconUri. + ProjectUriShouldBeIncludedInManifestFile=ProjectUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ProjectUri changes. You can run Update-ModuleManifest -ProjectUri to update the manifest with ProjectUri. + ShouldIncludeFunctionsToExport=This module '{0}' has exported functions. As a best practice, include exported functions in the module manifest file(.psd1). You can run Update-ModuleManifest -FunctionsToExport to update the manifest with ExportedFunctions field. + ShouldIncludeCmdletsToExport=This module '{0}' has exported cmdlets. As a best practice, include exported cmdlets in the module manifest file(.psd1). You can run Update-ModuleManifest -CmdletsToExport to update the manifest with ExportedCmdlets field. + ShouldIncludeDscResourcesToExport=This module '{0}' has exported DscResources. As a best practice, include exported DSC resources in the module manifest file(.psd1). If your PowerShell version is higher than 5.0, run Update-ModuleManifest -DscResourcesToExport to update the manifest with ExportedDscResources field. + UpdateModuleManifestPathCannotFound=Cannot load the manifest file '{0}' properly. Please specify the correct manifest path. + UpdatedModuleManifestNotValid=Cannot update the manifest file '{0}' because the manifest is not valid. Verify that the manifest file is valid, and then try again.'{1}' + ExportedDscResourcesNotSupportedOnLowerPowerShellVersion=The ExportedDscResources property is not supported in module manifests on PowerShell versions that are older than 5.0. Remove the value for the parameter 'DscResourcesToExport', and then try again. + CompatiblePSEditionsNotSupportedOnLowerPowerShellVersion=The CompatiblePSEditions property is not supported in module manifests on PowerShell versions that are older than 5.1. Remove the value for the parameter 'CompatiblePSEditions', and then try again. + ExternalModuleDependenciesNotSpecifiedInRequiredOrNestedModules='{0}' is listed in ExternalModuleDependencies, but it is not found in either the RequiredModules or NestedModules properties. Verify that this module is required for ExternalModuleDependencies, and then add it to NestedModules or RequiredModules. + TestModuleManifestFail=Cannot update the manifest properly. '{0}' + PackageManagementProvidersNotInModuleBaseFolder=PackageManagementProvider '{0}' is not found in the module base '{1}'. Verify that the PackageManagementProvider specified is within the module base. + UpdateManifestContentMessage=Update manifest file with new contents: + InvalidPackageManagementProviderValue=The PackageManagementProvider value cannot be '{0}'. Valid values for provider names include '{1}', and the default value for this parameter is '{2}'. + PowerShellGetUpdateIsNotSupportedOnLowerPSVersions=Self update of the PowerShellGet module is supported only in PowerShell 5.0 and newer releases. It is not supported in PowerShell 3.0 or 4.0. + ScriptVersionShouldBeGreaterThanGalleryVersion=Script '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. + ScriptVersionIsAlreadyAvailableInTheGallery=The script '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. + ScriptPrereleaseStringShouldBeGreaterThanGalleryPrereleaseString=Script '{0}' with version '{1}' and prerelease '{2}' cannot be published. The prerelease string must exceed the current prerelease string '{3}' that exists in the repository '{4}', or you must specify -Force. + ScriptParseError=The specified script file '{0}' has parse errors, try again after fixing the parse errors. + InvalidScriptToPublish=Script file '{0}' cannot be published because it does not have the required script metadata. Run Update-ScriptFileInfo -Path '{1}' to add the script metadata. + FailedToCreateCompressedScript=Failed to generate the compressed file for script '{0}'. + FailedToPublishScript=Failed to publish script '{0}': '{1}'. + PublishedScriptSuccessfully=Successfully published script '{0}' to the publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. + UnableToResolveScriptDependency=PowerShellGet cannot resolve the {0} dependency '{1}' of the script '{2}' on the repository '{3}'. Verify that the dependent {0} '{1}' is available in the repository '{3}'. If this dependent {0} '{1}' is managed externally, add it to the '{4}' entry in the script metadata. + InvalidVersion=Cannot convert value '{0}' to type 'System.Version'. + InvalidGuid=Cannot convert value '{0}' to type 'System.Guid'. + InvalidParameterValue=The specified value '{0}' for the parameter '{1}' is invalid. Ensure that it does not contain '<#' or '#>'. + MissingPSScriptInfo=PSScriptInfo is not specified in the script file '{0}'. You can use the Update-ScriptFileInfo with -Force or New-ScriptFileInfo cmdlet to add the PSScriptInfo to the script file. + MissingRequiredPSScriptInfoProperties=Script '{0}' is missing required metadata properties. Verify that the script file has Version, Guid, Description and Author properties. You can use the Update-ScriptFileInfo or New-ScriptFileInfo cmdlet to add or update the PSScriptInfo to the script file. + SkippedScriptDependency=Because dependent script '{0}' was skipped in the script dependencies list, users might not know how to install it. + SourceLocationPathsForModulesAndScriptsShouldBeEqual=SourceLocation '{0}' and ScriptSourceLocation '{1}' should be same for SMB Share or Local directory based repositories. + PublishLocationPathsForModulesAndScriptsShouldBeEqual=PublishLocation '{0}' and ScriptPublishLocation '{1}' should be same for SMB Share or Local directory based repositories. + SpecifiedNameIsAlearyUsed=The specified name '{0}' is already used for a different item on the specified repository '{1}'. Run '{2} -Name {0} -Repository {1}' to check whether the specified name '{0}' is already taken. + InvalidScriptFilePath=The script file path '{0}' is not valid. The value of the Path argument must resolve to a single file that has a '.ps1' extension. Change the value of the Path argument to point to a valid ps1 file, and then try again. + NuGetApiKeyIsRequiredForNuGetBasedGalleryService=NuGetApiKey is required for publishing a module or script file to the specified repository '{0}' whose publish location is '{1}'. Try again after specifying a valid value for the NuGetApiKey parameter. To get your API key, view your profile page. + ScriptFileExist=The specified script file '{0}' already exists. + InvalidEnvironmentVariableName=The specified environment variable name '{0}' exceeded the allowed limit of '{1}' characters. + PublishLocation=Publish Location:'{0}'. + ScriptPATHPromptCaption=PATH Environment Variable Change + ScriptPATHPromptQuery=Your system has not been configured with a default script installation path yet, which means you can only run a script by specifying the full path to the script file. This action places the script into the folder '{0}', and adds that folder to your PATH environment variable. Do you want to add the script installation path '{0}' to the PATH environment variable? + AddedScopePathToProcessSpecificPATHVariable=Added scripts installation location '{0}' for '{1}' scope to process specific PATH environment variable. + AddedScopePathToPATHVariable=Added scripts installation location '{0}' for '{1}' scope to PATH environment variable. + FilePathInFileListNotWithinModuleBase=Path '{0}' defined in FileList is not within module base '{1}'. Provide the correct FileList parameters and then try again. + ManifestFileReadWritePermissionDenied=The current user does not have read-write permissions for the file:'{0}'. Check the file permissions and then try again. + MissingTheRequiredPathOrPassThruParameter=The Path or PassThru parameter is required for creating the script file info. A new script file will be created with the script file info when the Path parameter is specified. Script file info will be returned if the PassThru parameter is specified. Try again after specifying the required parameter. + DescriptionParameterIsMissingForAddingTheScriptFileInfo=Description parameter is missing for adding the metadata to the script file. Try again after specifying the description. + UnableToAddPSScriptInfo=Unable to add PSScriptInfo to the script file '{0}'. You can use the New-ScriptFileInfo cmdlet to add the metadata to the existing script file. + RegisterVSTSFeedAsNuGetPackageSource=Publishing to a VSTS package management feed '{0}' requires it to be registered as a NuGet package source. Retry after adding this source '{0}' as NuGet package source by following the instructions specified at '{1}' + InvalidModuleAuthenticodeSignature=The module '{0}' cannot be installed or updated because the authenticode signature of the file '{1}' is not valid. + InvalidCatalogSignature=The module '{0}' cannot be installed because the catalog signature in '{1}' does not match the hash generated from the module. + AuthenticodeIssuerMismatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' from root certificate authority '{3}' is not matching with the authenticode issuer '{4}' of the previously-installed module '{5}' with version '{6}' from root certificate authority '{7}'. If you still want to install or update, use -SkipPublisherCheck parameter. + ModuleCommandAlreadyAvailable=The following commands are already available on this system:'{0}'. This module '{1}' may override the existing commands. If you still want to install this module '{1}', use -AllowClobber parameter. + CatalogFileFound=Found the catalog file '{0}' in the module '{1}' contents. + CatalogFileNotFoundInAvailableModule=Catalog file '{0}' is not found in the contents of the previously-installed module '{1}' with the same name. + CatalogFileNotFoundInNewModule=Catalog file '{0}' is not found in the contents of the module '{1}' being installed. + ValidAuthenticodeSignature=Valid authenticode signature found in the catalog file '{0}' for the module '{1}'. + ValidAuthenticodeSignatureInFile=Valid authenticode signature found in the file '{0}' for the module '{1}'. + ValidatingCatalogSignature=Validating the '{0}' module files for catalog signing using the catalog file '{1}'. + AuthenticodeIssuerMatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' matches with the authenticode issuer '{3}' of the previously-installed module '{4}' with version '{5}'. + ValidCatalogSignature=The catalog signature in '{0}' of the module '{1}' is valid and matches with the hash generated from the module contents. + SkippingPublisherCheck=Skipping the Publisher check for the version '{0}' of module '{1}'. + SourceModuleDetailsForPublisherValidation=For publisher validation, using the previously-installed module '{0}' with version '{1}' under '{2}' with publisher name '{3}' from root certificate authority '{4}'. Is this module signed by Microsoft: '{5}'. + NewModuleVersionDetailsForPublisherValidation=For publisher validation, current module '{0}' with version '{1}' with publisher name '{2}' from root certificate authority '{3}'. Is this module signed by Microsoft: '{4}'. + PublishersMatch=Publisher '{0}' of the new module '{1}' with version '{2}' matches with the publisher '{3}' of the previously-installed module '{4}' with version '{5}'. Both versions are signed with a Microsoft root certificate. + PublishersMismatch=A Microsoft-signed module named '{0}' with version '{1}' that was previously installed conflicts with the new module '{2}' from publisher '{3}' with version '{4}'. Installing the new module may result in system instability. If you still want to install or update, use -SkipPublisherCheck parameter. + ModuleIsNotCatalogSigned=The version '{0}' of the module '{1}' being installed is not catalog signed. Ensure that the version '{0}' of the module '{1}' has the catalog file '{2}' and signed with the same publisher '{3}' as the previously-installed module '{1}' with version '{4}' under the directory '{5}'. If you still want to install or update, use -SkipPublisherCheck parameter. + SentEnvironmentVariableChangeMessage=Successfully broadcasted the Environment variable changes. + UnableToSendEnvironmentVariableChangeMessage=Error in broadcasting the Environment variable changes. + LicenseUriNotSpecified='LicenseUri' is not specified. 'LicenseUri' must be provided when user license acceptance is required. + LicenseTxtNotFound=License.txt not Found. License.txt must be provided when user license acceptance is required. + LicenseTxtEmpty=License.txt is empty. + requireLicenseAcceptanceNotSupported=Require License Acceptance is not supported on Format version '{0}'. + AcceptanceLicenseQuery=Do you accept the license terms for module '{0}'. + ForceAcceptLicense=License Acceptance is required for module '{0}'. Please specify '-AcceptLicense' to perform this operation. + InvalidValueBoolean=The specified value '{0}' for the parameter '{1}' is invalid. It should be $true or $false. + UserDeclinedLicenseAcceptance=User declined license acceptance. + AcceptLicense=License Acceptance + RequiredScriptVersion=REQUIREDSCRIPTS: Required version of script '{0}' is '{1}'. + RequiredScriptVersoinFormat=, :, :[], :[,], :[,] + FailedToParseRequiredScripts=Cannot parse REQUIREDSCRIPTS '{0}'. Acceptable formats are: '{1}'. + FailedToParseRequiredScriptsVersion=Version format error: {0}, '{1}'. Acceptable formats are: '{2}'. + PublishersMismatchAsWarning=Module '{0}' version '{1}' published by '{2}' will be superceded by version '{3}' published by '{4}'. If you do not trust the new publisher, uninstall the module. + UnableToDownloadThePackage=The PackageManagement provider '{0}' is unable to download the package '{1}' version '{2}' to '{3}' path. + ValidatingTheModule=Validating the '{0}' module contents under '{1}' path. + ModuleValidationFailed=Unable to validate the '{0}' module contents under '{1}' path. + ValidatedModuleManifestFile=Test-ModuleManifest successfully validated the module manifest file '{0}'. + ValidateModuleAuthenticodeSignature=Validating the authenticode signature and publisher of the catalog file or module manifest file of the module '{0}'. + ValidateModuleCommandAlreadyAvailable=Checking for possible command collisions for the module '{0}' commands. + UnauthorizedAccessError=Access to the path '{0}' is denied. +###PSLOC +'@ + +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAUMj4wb0r0V933 +# Kxwf6eV800i5N8GnGMPseytvd1lnEKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLIu7f8le +# gQ0ndTd8gyAEe0fnNBrhCsEUfHD+upEyb7owQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQCWjkfbjU+QuaidvjCSfT9ZWmVRt7eVzD7wasyZEXwC +# uPWX5GwoR4xPMYzA/ohAElfSdrP6HWH9R+2sdXnUCJK+7w6N9wbB+S9Da0r3UcHj +# acV7JEftJK6ZwZ9EyebTgHv6Oa0xhuaRh/BINH4OhfIZEy3A2Lg4KhvNMjGKHTi2 +# agmGkKLUqySkyv48c/HCApLbZ3ugNqI06C284kostGCqpuSg6HRDcGPDzZSkT6l4 +# u1veNMmcpeGzd3NTNy5fkt8jQt3OUQ9NmfYN5nxh1BgDB1kyDm5lWMiZqFqlLS9T +# sG/jgYnqnprYVqTa8rYsSdmn8y1idSkQ1M715PQlOYNuoYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIOjiV1RbUVB0CleqHpkpsACXwys4XtATfTNyD7Bu +# Zv+1AgZfYPphXsEYEzIwMjAwOTIyMjIxOTUxLjg2N1owBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJy9uo++RqBmoAAAA +# AAEnMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 +# LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPgB3nERnk6fS40vvWeD +# 3HCgM9Ep4xTIQiPnJXE9E+HkZVtTsPemoOyhfNAyF95E/rUvXOVTUcJFL7Xb16jT +# KPXONsCWY8DCixSDIiid6xa30TiEWVcIZRwiDlcx29D467OTav5rA1G6TwAEY5rQ +# jhUHLrOoJgfJfakZq6IHjd+slI0/qlys7QIGakFk2OB6mh/ln/nS8G4kNRK6Do4g +# xDtnBSFLNfhsSZlRSMDJwFvrZ2FCkaoexd7rKlUNOAAScY411IEqQeI1PwfRm3aW +# bS8IvAfJPC2Ah2LrtP8sKn5faaU8epexje7vZfcZif/cbxgUKStJzqbdvTBNc93n +# /Z8CAwEAAaOCARswggEXMB0GA1UdDgQWBBTl9JZVgF85MSRbYlOJXbhY022V8jAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAKyo180VXHBqVnjZwQy7NlzXbo2+W5 +# qfHxR7ANV5RBkRkdGamkwUcDNL+DpHObFPJHa0oTeYKE0Zbl1MvvfS8RtGGdhGYG +# CJf+BPd/gBCs4+dkZdjvOzNyuVuDPGlqQ5f7HS7iuQ/cCyGHcHYJ0nXVewF2Lk+J +# lrWykHpTlLwPXmCpNR+gieItPi/UMF2RYTGwojW+yIVwNyMYnjFGUxEX5/DtJjRZ +# mg7PBHMrENN2DgO6wBelp4ptyH2KK2EsWT+8jFCuoKv+eJby0QD55LN5f8SrUPRn +# K86fh7aVOfCglQofo5ABZIGiDIrg4JsV4k6p0oBSIFOAcqRAhiH+1spCMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow +# QTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAs5W4TmyDHMRM7iz6mgGojqvXHzOggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMUsu8wIhgPMjAyMDA5MjIyMTI5MTlaGA8yMDIwMDkyMzIxMjkxOVowdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA4xSy7wIBADAKAgEAAgIVPgIB/zAHAgEAAgIRtjAK +# AgUA4xYEbwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEMD4esQRMLwQdhk +# Co1zgvmclcwl3lYYpk1oMh1ndsU3+97Rt6FV3adS4Hezc/K94oQKjcxtMVzLzQhG +# agM6XlqB31VD8n2nxVuaWD1yp2jm/0IvfL9nFMHJRhgANMiBdHqvqNrd86c/Kryq +# sI0Ch0sOx9wg3BozzqQhmdNjf9c6MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEnL26j75GoGagAAAAAAScwDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQg6wLE4god37E/zRxYUqM58DbGQBLJxEvt6c6xU1jsZqYwgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCAbkuhLEoYdahb/BUyVszO2VDi6kB3MSaof/+8u +# 7SM+IjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# Jy9uo++RqBmoAAAAAAEnMCIEIK4r6N3NISekswMCG1kSBJCCCePrlLDQWbMKz0wt +# Lj6CMA0GCSqGSIb3DQEBCwUABIIBACY3cZHtQ2k6zfVG4NotND24Zp0mlZAFe4A+ +# BFDISaaIXS86mO142Y9Hkm9rAMBCIJbKB/Yj1yf1gbygqNCoJDHFxZbdmx5XbShg +# PVfAQl00TuyQ86VYnFJtvdDODUnwuxErAFR1zyiGGBKYtQWhXNRmbhb1kRkWp5Fg +# s9mwzZUoGI2UzXDtYb6HkjNIOMxCbWW/27YfCBnOkiP2tJMIU79R4xY4vwQeWkKW +# dgtyHv+d7eBOd1aOi8HvqCstH6x06RC3DsYfZ/NbYSGuZpjSaSDDC7A1KvdPo6ti +# UYayEGi9iXfqnAv4Iyzo3OWIN2M2iqepP2xwL8Ne2q1ImLpiZF0= +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGetModuleInfo.xml b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGetModuleInfo.xml index 6208cf9160..68f348f2bd 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGetModuleInfo.xml +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PSGetModuleInfo.xml @@ -1,212 +1,212 @@ - - - - Microsoft.PowerShell.Commands.PSRepositoryItemInfo - System.Management.Automation.PSCustomObject - System.Object - - - PowerShellGet - 2.2.5 - Module - PowerShell module with commands for discovering, installing, updating and publishing the PowerShell artifacts like Modules, DSC Resources, Role Capabilities and Scripts. - Microsoft Corporation - - - System.Object[] - System.Array - System.Object - - - PowerShellTeam - alerickson - anamnavi - - - (c) Microsoft Corporation. All rights reserved. -
2020-09-22T22:42:00-07:00
- - - https://go.microsoft.com/fwlink/?LinkId=829061 - https://go.microsoft.com/fwlink/?LinkId=828955 - - - - - Packagemanagement - Provider - PSEdition_Desktop - PSEdition_Core - Linux - Mac - PSModule - - - - - System.Collections.Hashtable - System.Object - - - - Command - - - - Find-Command - Find-DSCResource - Find-Module - Find-RoleCapability - Find-Script - Get-CredsFromCredentialProvider - Get-InstalledModule - Get-InstalledScript - Get-PSRepository - Install-Module - Install-Script - New-ScriptFileInfo - Publish-Module - Publish-Script - Register-PSRepository - Save-Module - Save-Script - Set-PSRepository - Test-ScriptFileInfo - Uninstall-Module - Uninstall-Script - Unregister-PSRepository - Update-Module - Update-ModuleManifest - Update-Script - Update-ScriptFileInfo - - - - - Function - - - - Find-Command - Find-DSCResource - Find-Module - Find-RoleCapability - Find-Script - Get-CredsFromCredentialProvider - Get-InstalledModule - Get-InstalledScript - Get-PSRepository - Install-Module - Install-Script - New-ScriptFileInfo - Publish-Module - Publish-Script - Register-PSRepository - Save-Module - Save-Script - Set-PSRepository - Test-ScriptFileInfo - Uninstall-Module - Uninstall-Script - Unregister-PSRepository - Update-Module - Update-ModuleManifest - Update-Script - Update-ScriptFileInfo - - - - - Cmdlet - - - - - - - DscResource - - - - PSModule - PSRepository - - - - - Workflow - - - - RoleCapability - - - - - - ### 2.2.5_x000D__x000A_- Security patch for code injection bug_x000D__x000A__x000D__x000A_### 2.2.4.1_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_### 2.2.3_x000D__x000A_- Update `HelpInfoUri` to point to the latest content (#560)_x000D__x000A_- Improve discovery of usable nuget.exe binary (Thanks bwright86!) (#558)_x000D__x000A__x000D__x000A_### 2.2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Update casing of DscResources output_x000D__x000A__x000D__x000A_### 2.2.1_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Allow DscResources to work on case sensitive platforms (#521)_x000D__x000A_- Fix for failure to return credential provider when using private feeds (#521)_x000D__x000A__x000D__x000A_## 2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for prompting for credentials when passing in -Credential parameter when using Register-PSRepository_x000D__x000A__x000D__x000A_## 2.1.5_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Add and remove nuget based repositories as a nuget source when nuget client tool is installed (#498)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for 'Failed to publish module' error thrown when publishing modules (#497)_x000D__x000A__x000D__x000A_## 2.1.4_x000D__x000A_- Fixed hang while publishing some packages (#478)_x000D__x000A__x000D__x000A_## 2.1.3_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Added -Scope parameter to Update-Module (Thanks @lwajswaj!) (#471)_x000D__x000A_- Added -Exclude parameter to Publish-Module (Thanks @Benny1007!) (#191)_x000D__x000A_- Added -SkipAutomaticTags parameter to Publish-Module (Thanks @awickham10!) (#452)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fixed issue with finding modules using macOS and .NET Core 3.0_x000D__x000A__x000D__x000A_## 2.1.2_x000D__x000A__x000D__x000A_New Feature_x000D__x000A__x000D__x000A_- Added support for registering repositories with special characters_x000D__x000A__x000D__x000A_## 2.1.1_x000D__x000A__x000D__x000A_- Fix DSC resource folder structure_x000D__x000A__x000D__x000A_## 2.1.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A__x000D__x000A_- Default installation scope for Update-Module and Update-Script has changed to match Install-Module and Install-Script. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. (#421)_x000D__x000A__x000D__x000A_Bug Fixes_x000D__x000A__x000D__x000A_- Update-ModuleManifest no longer clears FunctionsToExport, AliasesToExport, nor NestModules (#415 & #425) (Thanks @pougetat and @tnieto88!)_x000D__x000A_- Update-Module no longer changes repository URL (#407)_x000D__x000A_- Update-ModuleManifest no longer preprends 'PSGet_' to module name (#403) (Thanks @ThePoShWolf)_x000D__x000A_- Update-ModuleManifest now throws error and fails to update when provided invalid entries (#398) (Thanks @pougetat!)_x000D__x000A_- Ignore files no longer being included when uploading modules (#396)_x000D__x000A__x000D__x000A_New Features_x000D__x000A__x000D__x000A_- New DSC resource, PSRepository (#426) (Thanks @johlju!)_x000D__x000A_- Piping of PS respositories (#420)_x000D__x000A_- utf8 support for .nuspec (#419)_x000D__x000A__x000D__x000A_## 2.0.4_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A_* Remove PSGallery availability checks (#374)_x000D__x000A__x000D__x000A_## 2.0.3_x000D__x000A__x000D__x000A_Bug fixes and Improvements_x000D__x000A_* Fix CommandAlreadyAvailable error for PackageManagement module (#333)_x000D__x000A_* Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner)_x000D__x000A_* Expanded aliases for improved readability (#338) (Thanks @lazywinadmin)_x000D__x000A_* Improvements for Catalog tests (#343)_x000D__x000A_* Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88)_x000D__x000A_* Import modules with many commands faster (#351)_x000D__x000A__x000D__x000A_New Features_x000D__x000A_* Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci)_x000D__x000A__x000D__x000A_## 2.0.1_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Publish-Module doesn't report error but fails to publish module (#316)_x000D__x000A_- Resolved CommandAlreadyAvailable error while installing the latest version of PackageManagement module (#333)_x000D__x000A__x000D__x000A_## 2.0.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- Default installation scope for Install-Module, Install-Script, and Install-Package has changed. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser._x000D__x000A__x000D__x000A_## 1.6.7_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Install/Save-Module error in PSCore 6.1.0-preview.4 on Ubuntu 18.04 OS (WSL/Azure) (#313)_x000D__x000A_- Updated error message in Save-Module cmdlet when the specified path is not accessible (#313)_x000D__x000A_- Added few additional verbose messages (#313)_x000D__x000A__x000D__x000A_## 1.6.6_x000D__x000A__x000D__x000A_Dependency Updates_x000D__x000A_* Add dependency on version 4.1.0 or newer of NuGet.exe_x000D__x000A_* Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe_x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Improved error handling in network connectivity tests._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Change Update-ModuleManifest so that prefix is not added to CmdletsToExport._x000D__x000A_- Change Update-ModuleManifest so that parameters will not reset to default values._x000D__x000A_- Specify AllowPrereleseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets._x000D__x000A__x000D__x000A_## 1.6.5_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258)_x000D__x000A_ - Whitelist installation of non-Microsoft signed Pester and PSReadline over Microsoft signed Pester and PSReadline._x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Splitting of functions (#229) (Thanks @Benny1007)_x000D__x000A_ - Moves private functions into respective private folder._x000D__x000A_ - Moves public functions as defined in PSModule.psd1 into respective public folder._x000D__x000A_ - Removes all functions from PSModule.psm1 file._x000D__x000A_ - Dot sources the functions from PSModule.psm1 file._x000D__x000A_ - Uses Export-ModuleMember to export the public functions from PSModule.psm1 file._x000D__x000A__x000D__x000A_* Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007)_x000D__x000A_ - Merged public and private functions into one .psm1 file to increase load time performance._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Fix null parameter error caused by MinimumVersion in Publish-PackageUtility (#201)_x000D__x000A_- Change .ExternalHelp link from PSGet.psm1-help.xml to PSModule-help.xml in PSModule.psm1 file (#215)_x000D__x000A_- Change Publish-* to allow version comparison instead of string comparison (#219)_x000D__x000A_- Ensure Get-InstalledScript -RequiredVersion works when versions have a leading 0 (#260)_x000D__x000A_- Add positional path to Save-Module and Save-Script (#264, #266)_x000D__x000A_- Ensure that Get-AuthenticodePublisher verifies publisher and that installing or updating a module checks for approprite catalog signature (#272)_x000D__x000A_- Update HelpInfoURI to 'http://go.microsoft.com/fwlink/?linkid=855963' (#274)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.6.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Prerelease Version Support (#185)_x000D__x000A_ - Implemented prerelease versions functionality in PowerShellGet cmdlets._x000D__x000A_ - Enables publishing, discovering, and installing the prerelease versions of modules and scripts from the PowerShell Gallery._x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/PrereleaseModule)_x000D__x000A__x000D__x000A_* Enabled publish cmdlets on PWSH and Nano Server (#196)_x000D__x000A_ - Dotnet command version 2.0.0 or newer should be installed by the user prior to using the publish cmdlets on PWSH and Windows Nano Server._x000D__x000A_ - Users can install the dotnet command by following the instructions specified at https://aka.ms/dotnet-install-script._x000D__x000A_ - On Windows, users can install the dotnet command by running *Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile '.\dotnet-install.ps1'; & '.\dotnet-install.ps1' -Channel Current -Version '2.0.0'*_x000D__x000A_ - Publish cmdlets on Windows PowerShell supports using the dotnet command for publishing operations._x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- PWSH: Changed the installation location of AllUsers scope to the parent of $PSHOME instead of $PSHOME. It is the SHARED_MODULES folder on PWSH._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Update HelpInfoURI to 'https://go.microsoft.com/fwlink/?linkid=855963' (#195)_x000D__x000A_- Ensure MyDocumentsPSPath path is correct (#179) (Thanks @lwsrbrts)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.5.0.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added support for modules requiring license acceptance (#150)_x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/RequireLicenseAcceptance)_x000D__x000A__x000D__x000A_* Added version for REQUIREDSCRIPTS (#162)_x000D__x000A_ - Enabled following scenarios for REQUIREDSCRIPTS_x000D__x000A_ - [1.0] - RequiredVersion_x000D__x000A_ - [1.0,2.0] - Min and Max Version_x000D__x000A_ - (,1.0] - Max Version_x000D__x000A_ - 1.0 - Min Version_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_* Fixed empty version value in nuspec (#157)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.1.3.2_x000D__x000A_* Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153)_x000D__x000A_* Fixed for DateTime format serialization issue. (#141)_x000D__x000A_* Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129)_x000D__x000A__x000D__x000A_## 1.1.3.1_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added `PrivateData` field to ScriptFileInfo. (#119)_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.2.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.1.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.0.0_x000D__x000A__x000D__x000A_* Initial release from GitHub._x000D__x000A_* PowerShellCore support._x000D__x000A__x000D__x000A_## For full history of release notes see changelog:_x000D__x000A_https://github.com/PowerShell/PowerShellGet/blob/master/CHANGELOG.md - - - - - - System.Collections.Specialized.OrderedDictionary - System.Object - - - - Name - PackageManagement - - - MinimumVersion - 1.4.4 - - - CanonicalId - nuget:PackageManagement/1.4.4 - - - - - - https://www.powershellgallery.com/api/v2 - PSGallery - NuGet - - - System.Management.Automation.PSCustomObject - System.Object - - - (c) Microsoft Corporation. All rights reserved. - PowerShell module with commands for discovering, installing, updating and publishing the PowerShell artifacts like Modules, DSC Resources, Role Capabilities and Scripts. - False - ### 2.2.5_x000D__x000A_- Security patch for code injection bug_x000D__x000A__x000D__x000A_### 2.2.4.1_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_### 2.2.3_x000D__x000A_- Update `HelpInfoUri` to point to the latest content (#560)_x000D__x000A_- Improve discovery of usable nuget.exe binary (Thanks bwright86!) (#558)_x000D__x000A__x000D__x000A_### 2.2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Update casing of DscResources output_x000D__x000A__x000D__x000A_### 2.2.1_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Allow DscResources to work on case sensitive platforms (#521)_x000D__x000A_- Fix for failure to return credential provider when using private feeds (#521)_x000D__x000A__x000D__x000A_## 2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for prompting for credentials when passing in -Credential parameter when using Register-PSRepository_x000D__x000A__x000D__x000A_## 2.1.5_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Add and remove nuget based repositories as a nuget source when nuget client tool is installed (#498)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for 'Failed to publish module' error thrown when publishing modules (#497)_x000D__x000A__x000D__x000A_## 2.1.4_x000D__x000A_- Fixed hang while publishing some packages (#478)_x000D__x000A__x000D__x000A_## 2.1.3_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Added -Scope parameter to Update-Module (Thanks @lwajswaj!) (#471)_x000D__x000A_- Added -Exclude parameter to Publish-Module (Thanks @Benny1007!) (#191)_x000D__x000A_- Added -SkipAutomaticTags parameter to Publish-Module (Thanks @awickham10!) (#452)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fixed issue with finding modules using macOS and .NET Core 3.0_x000D__x000A__x000D__x000A_## 2.1.2_x000D__x000A__x000D__x000A_New Feature_x000D__x000A__x000D__x000A_- Added support for registering repositories with special characters_x000D__x000A__x000D__x000A_## 2.1.1_x000D__x000A__x000D__x000A_- Fix DSC resource folder structure_x000D__x000A__x000D__x000A_## 2.1.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A__x000D__x000A_- Default installation scope for Update-Module and Update-Script has changed to match Install-Module and Install-Script. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. (#421)_x000D__x000A__x000D__x000A_Bug Fixes_x000D__x000A__x000D__x000A_- Update-ModuleManifest no longer clears FunctionsToExport, AliasesToExport, nor NestModules (#415 & #425) (Thanks @pougetat and @tnieto88!)_x000D__x000A_- Update-Module no longer changes repository URL (#407)_x000D__x000A_- Update-ModuleManifest no longer preprends 'PSGet_' to module name (#403) (Thanks @ThePoShWolf)_x000D__x000A_- Update-ModuleManifest now throws error and fails to update when provided invalid entries (#398) (Thanks @pougetat!)_x000D__x000A_- Ignore files no longer being included when uploading modules (#396)_x000D__x000A__x000D__x000A_New Features_x000D__x000A__x000D__x000A_- New DSC resource, PSRepository (#426) (Thanks @johlju!)_x000D__x000A_- Piping of PS respositories (#420)_x000D__x000A_- utf8 support for .nuspec (#419)_x000D__x000A__x000D__x000A_## 2.0.4_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A_* Remove PSGallery availability checks (#374)_x000D__x000A__x000D__x000A_## 2.0.3_x000D__x000A__x000D__x000A_Bug fixes and Improvements_x000D__x000A_* Fix CommandAlreadyAvailable error for PackageManagement module (#333)_x000D__x000A_* Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner)_x000D__x000A_* Expanded aliases for improved readability (#338) (Thanks @lazywinadmin)_x000D__x000A_* Improvements for Catalog tests (#343)_x000D__x000A_* Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88)_x000D__x000A_* Import modules with many commands faster (#351)_x000D__x000A__x000D__x000A_New Features_x000D__x000A_* Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci)_x000D__x000A__x000D__x000A_## 2.0.1_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Publish-Module doesn't report error but fails to publish module (#316)_x000D__x000A_- Resolved CommandAlreadyAvailable error while installing the latest version of PackageManagement module (#333)_x000D__x000A__x000D__x000A_## 2.0.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- Default installation scope for Install-Module, Install-Script, and Install-Package has changed. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser._x000D__x000A__x000D__x000A_## 1.6.7_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Install/Save-Module error in PSCore 6.1.0-preview.4 on Ubuntu 18.04 OS (WSL/Azure) (#313)_x000D__x000A_- Updated error message in Save-Module cmdlet when the specified path is not accessible (#313)_x000D__x000A_- Added few additional verbose messages (#313)_x000D__x000A__x000D__x000A_## 1.6.6_x000D__x000A__x000D__x000A_Dependency Updates_x000D__x000A_* Add dependency on version 4.1.0 or newer of NuGet.exe_x000D__x000A_* Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe_x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Improved error handling in network connectivity tests._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Change Update-ModuleManifest so that prefix is not added to CmdletsToExport._x000D__x000A_- Change Update-ModuleManifest so that parameters will not reset to default values._x000D__x000A_- Specify AllowPrereleseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets._x000D__x000A__x000D__x000A_## 1.6.5_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258)_x000D__x000A_ - Whitelist installation of non-Microsoft signed Pester and PSReadline over Microsoft signed Pester and PSReadline._x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Splitting of functions (#229) (Thanks @Benny1007)_x000D__x000A_ - Moves private functions into respective private folder._x000D__x000A_ - Moves public functions as defined in PSModule.psd1 into respective public folder._x000D__x000A_ - Removes all functions from PSModule.psm1 file._x000D__x000A_ - Dot sources the functions from PSModule.psm1 file._x000D__x000A_ - Uses Export-ModuleMember to export the public functions from PSModule.psm1 file._x000D__x000A__x000D__x000A_* Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007)_x000D__x000A_ - Merged public and private functions into one .psm1 file to increase load time performance._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Fix null parameter error caused by MinimumVersion in Publish-PackageUtility (#201)_x000D__x000A_- Change .ExternalHelp link from PSGet.psm1-help.xml to PSModule-help.xml in PSModule.psm1 file (#215)_x000D__x000A_- Change Publish-* to allow version comparison instead of string comparison (#219)_x000D__x000A_- Ensure Get-InstalledScript -RequiredVersion works when versions have a leading 0 (#260)_x000D__x000A_- Add positional path to Save-Module and Save-Script (#264, #266)_x000D__x000A_- Ensure that Get-AuthenticodePublisher verifies publisher and that installing or updating a module checks for approprite catalog signature (#272)_x000D__x000A_- Update HelpInfoURI to 'http://go.microsoft.com/fwlink/?linkid=855963' (#274)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.6.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Prerelease Version Support (#185)_x000D__x000A_ - Implemented prerelease versions functionality in PowerShellGet cmdlets._x000D__x000A_ - Enables publishing, discovering, and installing the prerelease versions of modules and scripts from the PowerShell Gallery._x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/PrereleaseModule)_x000D__x000A__x000D__x000A_* Enabled publish cmdlets on PWSH and Nano Server (#196)_x000D__x000A_ - Dotnet command version 2.0.0 or newer should be installed by the user prior to using the publish cmdlets on PWSH and Windows Nano Server._x000D__x000A_ - Users can install the dotnet command by following the instructions specified at https://aka.ms/dotnet-install-script._x000D__x000A_ - On Windows, users can install the dotnet command by running *Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile '.\dotnet-install.ps1'; & '.\dotnet-install.ps1' -Channel Current -Version '2.0.0'*_x000D__x000A_ - Publish cmdlets on Windows PowerShell supports using the dotnet command for publishing operations._x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- PWSH: Changed the installation location of AllUsers scope to the parent of $PSHOME instead of $PSHOME. It is the SHARED_MODULES folder on PWSH._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Update HelpInfoURI to 'https://go.microsoft.com/fwlink/?linkid=855963' (#195)_x000D__x000A_- Ensure MyDocumentsPSPath path is correct (#179) (Thanks @lwsrbrts)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.5.0.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added support for modules requiring license acceptance (#150)_x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/RequireLicenseAcceptance)_x000D__x000A__x000D__x000A_* Added version for REQUIREDSCRIPTS (#162)_x000D__x000A_ - Enabled following scenarios for REQUIREDSCRIPTS_x000D__x000A_ - [1.0] - RequiredVersion_x000D__x000A_ - [1.0,2.0] - Min and Max Version_x000D__x000A_ - (,1.0] - Max Version_x000D__x000A_ - 1.0 - Min Version_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_* Fixed empty version value in nuspec (#157)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.1.3.2_x000D__x000A_* Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153)_x000D__x000A_* Fixed for DateTime format serialization issue. (#141)_x000D__x000A_* Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129)_x000D__x000A__x000D__x000A_## 1.1.3.1_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added `PrivateData` field to ScriptFileInfo. (#119)_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.2.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.1.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.0.0_x000D__x000A__x000D__x000A_* Initial release from GitHub._x000D__x000A_* PowerShellCore support._x000D__x000A__x000D__x000A_## For full history of release notes see changelog:_x000D__x000A_https://github.com/PowerShell/PowerShellGet/blob/master/CHANGELOG.md - True - False - 65804037 - 114539696 - 270249 - 9/22/2020 10:42:00 PM -07:00 - 9/22/2020 10:42:00 PM -07:00 - 3/21/2023 6:34:18 PM -07:00 - Packagemanagement Provider PSEdition_Desktop PSEdition_Core Linux Mac PSModule - False - 2023-03-21T18:34:18Z - 2.2.5 - Microsoft Corporation - false - Module - PowerShellGet.nuspec|PowerShellGet.psd1|PSGet.Format.ps1xml|PSGet.Resource.psd1|PSModule.psm1|DscResources\MSFT_PSModule\MSFT_PSModule.psm1|DscResources\MSFT_PSModule\MSFT_PSModule.schema.mfl|DscResources\MSFT_PSModule\MSFT_PSModule.schema.mof|DscResources\MSFT_PSModule\en-US\MSFT_PSModule.strings.psd1|DscResources\MSFT_PSRepository\MSFT_PSRepository.psm1|DscResources\MSFT_PSRepository\MSFT_PSRepository.schema.mfl|DscResources\MSFT_PSRepository\MSFT_PSRepository.schema.mof|DscResources\MSFT_PSRepository\en-US\MSFT_PSRepository.strings.psd1|en-US\PSGet.Resource.psd1|Modules\PowerShellGet.LocalizationHelper\PowerShellGet.LocalizationHelper.psm1|Modules\PowerShellGet.ResourceHelper\PowerShellGet.ResourceHelper.psm1|Modules\PowerShellGet.ResourceHelper\en-US\PowerShellGet.ResourceHelper.strings.psd1 - Find-Command Find-DSCResource Find-Module Find-RoleCapability Find-Script Get-CredsFromCredentialProvider Get-InstalledModule Get-InstalledScript Get-PSRepository Install-Module Install-Script New-ScriptFileInfo Publish-Module Publish-Script Register-PSRepository Save-Module Save-Script Set-PSRepository Test-ScriptFileInfo Uninstall-Module Uninstall-Script Unregister-PSRepository Update-Module Update-ModuleManifest Update-Script Update-ScriptFileInfo - PSModule PSRepository - 1d73a601-4a6c-43c5-ba3f-619b18bbb404 - 3.0 - Microsoft Corporation - - - D:\mspkg\src\PowerShell\ExternalModules\PowerShellGet\2.2.5 -
-
-
+ + + + Microsoft.PowerShell.Commands.PSRepositoryItemInfo + System.Management.Automation.PSCustomObject + System.Object + + + PowerShellGet + 2.2.5 + Module + PowerShell module with commands for discovering, installing, updating and publishing the PowerShell artifacts like Modules, DSC Resources, Role Capabilities and Scripts. + Microsoft Corporation + + + System.Object[] + System.Array + System.Object + + + PowerShellTeam + alerickson + anamnavi + + + (c) Microsoft Corporation. All rights reserved. +
2020-09-22T22:42:00-07:00
+ + + https://go.microsoft.com/fwlink/?LinkId=829061 + https://go.microsoft.com/fwlink/?LinkId=828955 + + + + + Packagemanagement + Provider + PSEdition_Desktop + PSEdition_Core + Linux + Mac + PSModule + + + + + System.Collections.Hashtable + System.Object + + + + Command + + + + Find-Command + Find-DSCResource + Find-Module + Find-RoleCapability + Find-Script + Get-CredsFromCredentialProvider + Get-InstalledModule + Get-InstalledScript + Get-PSRepository + Install-Module + Install-Script + New-ScriptFileInfo + Publish-Module + Publish-Script + Register-PSRepository + Save-Module + Save-Script + Set-PSRepository + Test-ScriptFileInfo + Uninstall-Module + Uninstall-Script + Unregister-PSRepository + Update-Module + Update-ModuleManifest + Update-Script + Update-ScriptFileInfo + + + + + Function + + + + Find-Command + Find-DSCResource + Find-Module + Find-RoleCapability + Find-Script + Get-CredsFromCredentialProvider + Get-InstalledModule + Get-InstalledScript + Get-PSRepository + Install-Module + Install-Script + New-ScriptFileInfo + Publish-Module + Publish-Script + Register-PSRepository + Save-Module + Save-Script + Set-PSRepository + Test-ScriptFileInfo + Uninstall-Module + Uninstall-Script + Unregister-PSRepository + Update-Module + Update-ModuleManifest + Update-Script + Update-ScriptFileInfo + + + + + Cmdlet + + + + + + + DscResource + + + + PSModule + PSRepository + + + + + Workflow + + + + RoleCapability + + + + + + ### 2.2.5_x000D__x000A_- Security patch for code injection bug_x000D__x000A__x000D__x000A_### 2.2.4.1_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_### 2.2.3_x000D__x000A_- Update `HelpInfoUri` to point to the latest content (#560)_x000D__x000A_- Improve discovery of usable nuget.exe binary (Thanks bwright86!) (#558)_x000D__x000A__x000D__x000A_### 2.2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Update casing of DscResources output_x000D__x000A__x000D__x000A_### 2.2.1_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Allow DscResources to work on case sensitive platforms (#521)_x000D__x000A_- Fix for failure to return credential provider when using private feeds (#521)_x000D__x000A__x000D__x000A_## 2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for prompting for credentials when passing in -Credential parameter when using Register-PSRepository_x000D__x000A__x000D__x000A_## 2.1.5_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Add and remove nuget based repositories as a nuget source when nuget client tool is installed (#498)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for 'Failed to publish module' error thrown when publishing modules (#497)_x000D__x000A__x000D__x000A_## 2.1.4_x000D__x000A_- Fixed hang while publishing some packages (#478)_x000D__x000A__x000D__x000A_## 2.1.3_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Added -Scope parameter to Update-Module (Thanks @lwajswaj!) (#471)_x000D__x000A_- Added -Exclude parameter to Publish-Module (Thanks @Benny1007!) (#191)_x000D__x000A_- Added -SkipAutomaticTags parameter to Publish-Module (Thanks @awickham10!) (#452)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fixed issue with finding modules using macOS and .NET Core 3.0_x000D__x000A__x000D__x000A_## 2.1.2_x000D__x000A__x000D__x000A_New Feature_x000D__x000A__x000D__x000A_- Added support for registering repositories with special characters_x000D__x000A__x000D__x000A_## 2.1.1_x000D__x000A__x000D__x000A_- Fix DSC resource folder structure_x000D__x000A__x000D__x000A_## 2.1.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A__x000D__x000A_- Default installation scope for Update-Module and Update-Script has changed to match Install-Module and Install-Script. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. (#421)_x000D__x000A__x000D__x000A_Bug Fixes_x000D__x000A__x000D__x000A_- Update-ModuleManifest no longer clears FunctionsToExport, AliasesToExport, nor NestModules (#415 & #425) (Thanks @pougetat and @tnieto88!)_x000D__x000A_- Update-Module no longer changes repository URL (#407)_x000D__x000A_- Update-ModuleManifest no longer preprends 'PSGet_' to module name (#403) (Thanks @ThePoShWolf)_x000D__x000A_- Update-ModuleManifest now throws error and fails to update when provided invalid entries (#398) (Thanks @pougetat!)_x000D__x000A_- Ignore files no longer being included when uploading modules (#396)_x000D__x000A__x000D__x000A_New Features_x000D__x000A__x000D__x000A_- New DSC resource, PSRepository (#426) (Thanks @johlju!)_x000D__x000A_- Piping of PS respositories (#420)_x000D__x000A_- utf8 support for .nuspec (#419)_x000D__x000A__x000D__x000A_## 2.0.4_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A_* Remove PSGallery availability checks (#374)_x000D__x000A__x000D__x000A_## 2.0.3_x000D__x000A__x000D__x000A_Bug fixes and Improvements_x000D__x000A_* Fix CommandAlreadyAvailable error for PackageManagement module (#333)_x000D__x000A_* Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner)_x000D__x000A_* Expanded aliases for improved readability (#338) (Thanks @lazywinadmin)_x000D__x000A_* Improvements for Catalog tests (#343)_x000D__x000A_* Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88)_x000D__x000A_* Import modules with many commands faster (#351)_x000D__x000A__x000D__x000A_New Features_x000D__x000A_* Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci)_x000D__x000A__x000D__x000A_## 2.0.1_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Publish-Module doesn't report error but fails to publish module (#316)_x000D__x000A_- Resolved CommandAlreadyAvailable error while installing the latest version of PackageManagement module (#333)_x000D__x000A__x000D__x000A_## 2.0.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- Default installation scope for Install-Module, Install-Script, and Install-Package has changed. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser._x000D__x000A__x000D__x000A_## 1.6.7_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Install/Save-Module error in PSCore 6.1.0-preview.4 on Ubuntu 18.04 OS (WSL/Azure) (#313)_x000D__x000A_- Updated error message in Save-Module cmdlet when the specified path is not accessible (#313)_x000D__x000A_- Added few additional verbose messages (#313)_x000D__x000A__x000D__x000A_## 1.6.6_x000D__x000A__x000D__x000A_Dependency Updates_x000D__x000A_* Add dependency on version 4.1.0 or newer of NuGet.exe_x000D__x000A_* Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe_x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Improved error handling in network connectivity tests._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Change Update-ModuleManifest so that prefix is not added to CmdletsToExport._x000D__x000A_- Change Update-ModuleManifest so that parameters will not reset to default values._x000D__x000A_- Specify AllowPrereleseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets._x000D__x000A__x000D__x000A_## 1.6.5_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258)_x000D__x000A_ - Whitelist installation of non-Microsoft signed Pester and PSReadline over Microsoft signed Pester and PSReadline._x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Splitting of functions (#229) (Thanks @Benny1007)_x000D__x000A_ - Moves private functions into respective private folder._x000D__x000A_ - Moves public functions as defined in PSModule.psd1 into respective public folder._x000D__x000A_ - Removes all functions from PSModule.psm1 file._x000D__x000A_ - Dot sources the functions from PSModule.psm1 file._x000D__x000A_ - Uses Export-ModuleMember to export the public functions from PSModule.psm1 file._x000D__x000A__x000D__x000A_* Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007)_x000D__x000A_ - Merged public and private functions into one .psm1 file to increase load time performance._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Fix null parameter error caused by MinimumVersion in Publish-PackageUtility (#201)_x000D__x000A_- Change .ExternalHelp link from PSGet.psm1-help.xml to PSModule-help.xml in PSModule.psm1 file (#215)_x000D__x000A_- Change Publish-* to allow version comparison instead of string comparison (#219)_x000D__x000A_- Ensure Get-InstalledScript -RequiredVersion works when versions have a leading 0 (#260)_x000D__x000A_- Add positional path to Save-Module and Save-Script (#264, #266)_x000D__x000A_- Ensure that Get-AuthenticodePublisher verifies publisher and that installing or updating a module checks for approprite catalog signature (#272)_x000D__x000A_- Update HelpInfoURI to 'http://go.microsoft.com/fwlink/?linkid=855963' (#274)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.6.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Prerelease Version Support (#185)_x000D__x000A_ - Implemented prerelease versions functionality in PowerShellGet cmdlets._x000D__x000A_ - Enables publishing, discovering, and installing the prerelease versions of modules and scripts from the PowerShell Gallery._x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/PrereleaseModule)_x000D__x000A__x000D__x000A_* Enabled publish cmdlets on PWSH and Nano Server (#196)_x000D__x000A_ - Dotnet command version 2.0.0 or newer should be installed by the user prior to using the publish cmdlets on PWSH and Windows Nano Server._x000D__x000A_ - Users can install the dotnet command by following the instructions specified at https://aka.ms/dotnet-install-script._x000D__x000A_ - On Windows, users can install the dotnet command by running *Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile '.\dotnet-install.ps1'; & '.\dotnet-install.ps1' -Channel Current -Version '2.0.0'*_x000D__x000A_ - Publish cmdlets on Windows PowerShell supports using the dotnet command for publishing operations._x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- PWSH: Changed the installation location of AllUsers scope to the parent of $PSHOME instead of $PSHOME. It is the SHARED_MODULES folder on PWSH._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Update HelpInfoURI to 'https://go.microsoft.com/fwlink/?linkid=855963' (#195)_x000D__x000A_- Ensure MyDocumentsPSPath path is correct (#179) (Thanks @lwsrbrts)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.5.0.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added support for modules requiring license acceptance (#150)_x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/RequireLicenseAcceptance)_x000D__x000A__x000D__x000A_* Added version for REQUIREDSCRIPTS (#162)_x000D__x000A_ - Enabled following scenarios for REQUIREDSCRIPTS_x000D__x000A_ - [1.0] - RequiredVersion_x000D__x000A_ - [1.0,2.0] - Min and Max Version_x000D__x000A_ - (,1.0] - Max Version_x000D__x000A_ - 1.0 - Min Version_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_* Fixed empty version value in nuspec (#157)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.1.3.2_x000D__x000A_* Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153)_x000D__x000A_* Fixed for DateTime format serialization issue. (#141)_x000D__x000A_* Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129)_x000D__x000A__x000D__x000A_## 1.1.3.1_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added `PrivateData` field to ScriptFileInfo. (#119)_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.2.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.1.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.0.0_x000D__x000A__x000D__x000A_* Initial release from GitHub._x000D__x000A_* PowerShellCore support._x000D__x000A__x000D__x000A_## For full history of release notes see changelog:_x000D__x000A_https://github.com/PowerShell/PowerShellGet/blob/master/CHANGELOG.md + + + + + + System.Collections.Specialized.OrderedDictionary + System.Object + + + + Name + PackageManagement + + + MinimumVersion + 1.4.4 + + + CanonicalId + nuget:PackageManagement/1.4.4 + + + + + + https://www.powershellgallery.com/api/v2 + PSGallery + NuGet + + + System.Management.Automation.PSCustomObject + System.Object + + + (c) Microsoft Corporation. All rights reserved. + PowerShell module with commands for discovering, installing, updating and publishing the PowerShell artifacts like Modules, DSC Resources, Role Capabilities and Scripts. + False + ### 2.2.5_x000D__x000A_- Security patch for code injection bug_x000D__x000A__x000D__x000A_### 2.2.4.1_x000D__x000A_- Remove catalog file_x000D__x000A__x000D__x000A_### 2.2.3_x000D__x000A_- Update `HelpInfoUri` to point to the latest content (#560)_x000D__x000A_- Improve discovery of usable nuget.exe binary (Thanks bwright86!) (#558)_x000D__x000A__x000D__x000A_### 2.2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Update casing of DscResources output_x000D__x000A__x000D__x000A_### 2.2.1_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Allow DscResources to work on case sensitive platforms (#521)_x000D__x000A_- Fix for failure to return credential provider when using private feeds (#521)_x000D__x000A__x000D__x000A_## 2.2_x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for prompting for credentials when passing in -Credential parameter when using Register-PSRepository_x000D__x000A__x000D__x000A_## 2.1.5_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Add and remove nuget based repositories as a nuget source when nuget client tool is installed (#498)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fix for 'Failed to publish module' error thrown when publishing modules (#497)_x000D__x000A__x000D__x000A_## 2.1.4_x000D__x000A_- Fixed hang while publishing some packages (#478)_x000D__x000A__x000D__x000A_## 2.1.3_x000D__x000A_New Features_x000D__x000A__x000D__x000A_- Added -Scope parameter to Update-Module (Thanks @lwajswaj!) (#471)_x000D__x000A_- Added -Exclude parameter to Publish-Module (Thanks @Benny1007!) (#191)_x000D__x000A_- Added -SkipAutomaticTags parameter to Publish-Module (Thanks @awickham10!) (#452)_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A__x000D__x000A_- Fixed issue with finding modules using macOS and .NET Core 3.0_x000D__x000A__x000D__x000A_## 2.1.2_x000D__x000A__x000D__x000A_New Feature_x000D__x000A__x000D__x000A_- Added support for registering repositories with special characters_x000D__x000A__x000D__x000A_## 2.1.1_x000D__x000A__x000D__x000A_- Fix DSC resource folder structure_x000D__x000A__x000D__x000A_## 2.1.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A__x000D__x000A_- Default installation scope for Update-Module and Update-Script has changed to match Install-Module and Install-Script. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. (#421)_x000D__x000A__x000D__x000A_Bug Fixes_x000D__x000A__x000D__x000A_- Update-ModuleManifest no longer clears FunctionsToExport, AliasesToExport, nor NestModules (#415 & #425) (Thanks @pougetat and @tnieto88!)_x000D__x000A_- Update-Module no longer changes repository URL (#407)_x000D__x000A_- Update-ModuleManifest no longer preprends 'PSGet_' to module name (#403) (Thanks @ThePoShWolf)_x000D__x000A_- Update-ModuleManifest now throws error and fails to update when provided invalid entries (#398) (Thanks @pougetat!)_x000D__x000A_- Ignore files no longer being included when uploading modules (#396)_x000D__x000A__x000D__x000A_New Features_x000D__x000A__x000D__x000A_- New DSC resource, PSRepository (#426) (Thanks @johlju!)_x000D__x000A_- Piping of PS respositories (#420)_x000D__x000A_- utf8 support for .nuspec (#419)_x000D__x000A__x000D__x000A_## 2.0.4_x000D__x000A__x000D__x000A_Bug Fix_x000D__x000A_* Remove PSGallery availability checks (#374)_x000D__x000A__x000D__x000A_## 2.0.3_x000D__x000A__x000D__x000A_Bug fixes and Improvements_x000D__x000A_* Fix CommandAlreadyAvailable error for PackageManagement module (#333)_x000D__x000A_* Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner)_x000D__x000A_* Expanded aliases for improved readability (#338) (Thanks @lazywinadmin)_x000D__x000A_* Improvements for Catalog tests (#343)_x000D__x000A_* Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88)_x000D__x000A_* Import modules with many commands faster (#351)_x000D__x000A__x000D__x000A_New Features_x000D__x000A_* Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci)_x000D__x000A__x000D__x000A_## 2.0.1_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Publish-Module doesn't report error but fails to publish module (#316)_x000D__x000A_- Resolved CommandAlreadyAvailable error while installing the latest version of PackageManagement module (#333)_x000D__x000A__x000D__x000A_## 2.0.0_x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- Default installation scope for Install-Module, Install-Script, and Install-Package has changed. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times._x000D__x000A_ For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser._x000D__x000A__x000D__x000A_## 1.6.7_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Resolved Install/Save-Module error in PSCore 6.1.0-preview.4 on Ubuntu 18.04 OS (WSL/Azure) (#313)_x000D__x000A_- Updated error message in Save-Module cmdlet when the specified path is not accessible (#313)_x000D__x000A_- Added few additional verbose messages (#313)_x000D__x000A__x000D__x000A_## 1.6.6_x000D__x000A__x000D__x000A_Dependency Updates_x000D__x000A_* Add dependency on version 4.1.0 or newer of NuGet.exe_x000D__x000A_* Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe_x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Improved error handling in network connectivity tests._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Change Update-ModuleManifest so that prefix is not added to CmdletsToExport._x000D__x000A_- Change Update-ModuleManifest so that parameters will not reset to default values._x000D__x000A_- Specify AllowPrereleseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets._x000D__x000A__x000D__x000A_## 1.6.5_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258)_x000D__x000A_ - Whitelist installation of non-Microsoft signed Pester and PSReadline over Microsoft signed Pester and PSReadline._x000D__x000A__x000D__x000A_Build and Code Cleanup Improvements_x000D__x000A_* Splitting of functions (#229) (Thanks @Benny1007)_x000D__x000A_ - Moves private functions into respective private folder._x000D__x000A_ - Moves public functions as defined in PSModule.psd1 into respective public folder._x000D__x000A_ - Removes all functions from PSModule.psm1 file._x000D__x000A_ - Dot sources the functions from PSModule.psm1 file._x000D__x000A_ - Uses Export-ModuleMember to export the public functions from PSModule.psm1 file._x000D__x000A__x000D__x000A_* Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007)_x000D__x000A_ - Merged public and private functions into one .psm1 file to increase load time performance._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Fix null parameter error caused by MinimumVersion in Publish-PackageUtility (#201)_x000D__x000A_- Change .ExternalHelp link from PSGet.psm1-help.xml to PSModule-help.xml in PSModule.psm1 file (#215)_x000D__x000A_- Change Publish-* to allow version comparison instead of string comparison (#219)_x000D__x000A_- Ensure Get-InstalledScript -RequiredVersion works when versions have a leading 0 (#260)_x000D__x000A_- Add positional path to Save-Module and Save-Script (#264, #266)_x000D__x000A_- Ensure that Get-AuthenticodePublisher verifies publisher and that installing or updating a module checks for approprite catalog signature (#272)_x000D__x000A_- Update HelpInfoURI to 'http://go.microsoft.com/fwlink/?linkid=855963' (#274)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.6.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Prerelease Version Support (#185)_x000D__x000A_ - Implemented prerelease versions functionality in PowerShellGet cmdlets._x000D__x000A_ - Enables publishing, discovering, and installing the prerelease versions of modules and scripts from the PowerShell Gallery._x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/PrereleaseModule)_x000D__x000A__x000D__x000A_* Enabled publish cmdlets on PWSH and Nano Server (#196)_x000D__x000A_ - Dotnet command version 2.0.0 or newer should be installed by the user prior to using the publish cmdlets on PWSH and Windows Nano Server._x000D__x000A_ - Users can install the dotnet command by following the instructions specified at https://aka.ms/dotnet-install-script._x000D__x000A_ - On Windows, users can install the dotnet command by running *Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile '.\dotnet-install.ps1'; & '.\dotnet-install.ps1' -Channel Current -Version '2.0.0'*_x000D__x000A_ - Publish cmdlets on Windows PowerShell supports using the dotnet command for publishing operations._x000D__x000A__x000D__x000A_Breaking Change_x000D__x000A_- PWSH: Changed the installation location of AllUsers scope to the parent of $PSHOME instead of $PSHOME. It is the SHARED_MODULES folder on PWSH._x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_- Update HelpInfoURI to 'https://go.microsoft.com/fwlink/?linkid=855963' (#195)_x000D__x000A_- Ensure MyDocumentsPSPath path is correct (#179) (Thanks @lwsrbrts)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.5.0.0_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added support for modules requiring license acceptance (#150)_x000D__x000A_ - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/RequireLicenseAcceptance)_x000D__x000A__x000D__x000A_* Added version for REQUIREDSCRIPTS (#162)_x000D__x000A_ - Enabled following scenarios for REQUIREDSCRIPTS_x000D__x000A_ - [1.0] - RequiredVersion_x000D__x000A_ - [1.0,2.0] - Min and Max Version_x000D__x000A_ - (,1.0] - Max Version_x000D__x000A_ - 1.0 - Min Version_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A_* Fixed empty version value in nuspec (#157)_x000D__x000A__x000D__x000A__x000D__x000A_## 1.1.3.2_x000D__x000A_* Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153)_x000D__x000A_* Fixed for DateTime format serialization issue. (#141)_x000D__x000A_* Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129)_x000D__x000A__x000D__x000A_## 1.1.3.1_x000D__x000A__x000D__x000A_New features_x000D__x000A_* Added `PrivateData` field to ScriptFileInfo. (#119)_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.2.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.1.0_x000D__x000A__x000D__x000A_Bug fixes_x000D__x000A__x000D__x000A_## 1.1.0.0_x000D__x000A__x000D__x000A_* Initial release from GitHub._x000D__x000A_* PowerShellCore support._x000D__x000A__x000D__x000A_## For full history of release notes see changelog:_x000D__x000A_https://github.com/PowerShell/PowerShellGet/blob/master/CHANGELOG.md + True + False + 65804037 + 114539696 + 270249 + 9/22/2020 10:42:00 PM -07:00 + 9/22/2020 10:42:00 PM -07:00 + 3/21/2023 6:34:18 PM -07:00 + Packagemanagement Provider PSEdition_Desktop PSEdition_Core Linux Mac PSModule + False + 2023-03-21T18:34:18Z + 2.2.5 + Microsoft Corporation + false + Module + PowerShellGet.nuspec|PowerShellGet.psd1|PSGet.Format.ps1xml|PSGet.Resource.psd1|PSModule.psm1|DscResources\MSFT_PSModule\MSFT_PSModule.psm1|DscResources\MSFT_PSModule\MSFT_PSModule.schema.mfl|DscResources\MSFT_PSModule\MSFT_PSModule.schema.mof|DscResources\MSFT_PSModule\en-US\MSFT_PSModule.strings.psd1|DscResources\MSFT_PSRepository\MSFT_PSRepository.psm1|DscResources\MSFT_PSRepository\MSFT_PSRepository.schema.mfl|DscResources\MSFT_PSRepository\MSFT_PSRepository.schema.mof|DscResources\MSFT_PSRepository\en-US\MSFT_PSRepository.strings.psd1|en-US\PSGet.Resource.psd1|Modules\PowerShellGet.LocalizationHelper\PowerShellGet.LocalizationHelper.psm1|Modules\PowerShellGet.ResourceHelper\PowerShellGet.ResourceHelper.psm1|Modules\PowerShellGet.ResourceHelper\en-US\PowerShellGet.ResourceHelper.strings.psd1 + Find-Command Find-DSCResource Find-Module Find-RoleCapability Find-Script Get-CredsFromCredentialProvider Get-InstalledModule Get-InstalledScript Get-PSRepository Install-Module Install-Script New-ScriptFileInfo Publish-Module Publish-Script Register-PSRepository Save-Module Save-Script Set-PSRepository Test-ScriptFileInfo Uninstall-Module Uninstall-Script Unregister-PSRepository Update-Module Update-ModuleManifest Update-Script Update-ScriptFileInfo + PSModule PSRepository + 1d73a601-4a6c-43c5-ba3f-619b18bbb404 + 3.0 + Microsoft Corporation + + + D:\mspkg\src\PowerShell\ExternalModules\PowerShellGet\2.2.5 +
+
+
diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PowerShellGet.psd1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PowerShellGet.psd1 index 9a7b744731..9af0f1413c 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PowerShellGet.psd1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/PowerShellGet.psd1 @@ -1,13 +1,13 @@ -@{ - RootModule = 'PSModule.psm1' - ModuleVersion = '2.2.5' - GUID = '1d73a601-4a6c-43c5-ba3f-619b18bbb404' - Author = 'Microsoft Corporation' - CompanyName = 'Microsoft Corporation' - Copyright = '(c) Microsoft Corporation. All rights reserved.' - Description = 'PowerShell module with commands for discovering, installing, updating and publishing the PowerShell artifacts like Modules, DSC Resources, Role Capabilities and Scripts.' - PowerShellVersion = '3.0' - FormatsToProcess = 'PSGet.Format.ps1xml' +@{ + RootModule = 'PSModule.psm1' + ModuleVersion = '2.2.5' + GUID = '1d73a601-4a6c-43c5-ba3f-619b18bbb404' + Author = 'Microsoft Corporation' + CompanyName = 'Microsoft Corporation' + Copyright = '(c) Microsoft Corporation. All rights reserved.' + Description = 'PowerShell module with commands for discovering, installing, updating and publishing the PowerShell artifacts like Modules, DSC Resources, Role Capabilities and Scripts.' + PowerShellVersion = '3.0' + FormatsToProcess = 'PSGet.Format.ps1xml' FunctionsToExport = @( 'Find-Command', 'Find-DSCResource', @@ -35,447 +35,447 @@ FunctionsToExport = @( 'Update-ModuleManifest', 'Update-Script', 'Update-ScriptFileInfo') - - VariablesToExport = 'PSGetPath' - AliasesToExport = @('inmo', 'fimo', 'upmo', 'pumo') - FileList = @('PSModule.psm1', - 'PSGet.Format.ps1xml', - 'PSGet.Resource.psd1') - RequiredModules = @(@{ModuleName = 'PackageManagement'; ModuleVersion = '1.4.4' }) - PrivateData = @{ - "PackageManagementProviders" = 'PSModule.psm1' - "SupportedPowerShellGetFormatVersions" = @('1.x', '2.x') - PSData = @{ - Tags = @('Packagemanagement', - 'Provider', - 'PSEdition_Desktop', - 'PSEdition_Core', - 'Linux', - 'Mac') - ProjectUri = 'https://go.microsoft.com/fwlink/?LinkId=828955' - LicenseUri = 'https://go.microsoft.com/fwlink/?LinkId=829061' - ReleaseNotes = @' -### 2.2.5 -- Security patch for code injection bug - -### 2.2.4.1 -- Remove catalog file - -### 2.2.3 -- Update `HelpInfoUri` to point to the latest content (#560) -- Improve discovery of usable nuget.exe binary (Thanks bwright86!) (#558) - -### 2.2.2 -Bug Fix - -- Update casing of DscResources output - -### 2.2.1 -Bug Fix - -- Allow DscResources to work on case sensitive platforms (#521) -- Fix for failure to return credential provider when using private feeds (#521) - -## 2.2 -Bug Fix - -- Fix for prompting for credentials when passing in -Credential parameter when using Register-PSRepository - -## 2.1.5 -New Features - -- Add and remove nuget based repositories as a nuget source when nuget client tool is installed (#498) - -Bug Fix - -- Fix for 'Failed to publish module' error thrown when publishing modules (#497) - -## 2.1.4 -- Fixed hang while publishing some packages (#478) - -## 2.1.3 -New Features - -- Added -Scope parameter to Update-Module (Thanks @lwajswaj!) (#471) -- Added -Exclude parameter to Publish-Module (Thanks @Benny1007!) (#191) -- Added -SkipAutomaticTags parameter to Publish-Module (Thanks @awickham10!) (#452) - -Bug Fix - -- Fixed issue with finding modules using macOS and .NET Core 3.0 - -## 2.1.2 - -New Feature - -- Added support for registering repositories with special characters - -## 2.1.1 - -- Fix DSC resource folder structure - -## 2.1.0 - -Breaking Change - -- Default installation scope for Update-Module and Update-Script has changed to match Install-Module and Install-Script. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times. - For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. (#421) - -Bug Fixes - -- Update-ModuleManifest no longer clears FunctionsToExport, AliasesToExport, nor NestModules (#415 & #425) (Thanks @pougetat and @tnieto88!) -- Update-Module no longer changes repository URL (#407) -- Update-ModuleManifest no longer preprends 'PSGet_' to module name (#403) (Thanks @ThePoShWolf) -- Update-ModuleManifest now throws error and fails to update when provided invalid entries (#398) (Thanks @pougetat!) -- Ignore files no longer being included when uploading modules (#396) - -New Features - -- New DSC resource, PSRepository (#426) (Thanks @johlju!) -- Piping of PS respositories (#420) -- utf8 support for .nuspec (#419) - -## 2.0.4 - -Bug Fix -* Remove PSGallery availability checks (#374) - -## 2.0.3 - -Bug fixes and Improvements -* Fix CommandAlreadyAvailable error for PackageManagement module (#333) -* Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner) -* Expanded aliases for improved readability (#338) (Thanks @lazywinadmin) -* Improvements for Catalog tests (#343) -* Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88) -* Import modules with many commands faster (#351) - -New Features -* Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci) - -## 2.0.1 - -Bug fixes -- Resolved Publish-Module doesn't report error but fails to publish module (#316) -- Resolved CommandAlreadyAvailable error while installing the latest version of PackageManagement module (#333) - -## 2.0.0 - -Breaking Change -- Default installation scope for Install-Module, Install-Script, and Install-Package has changed. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times. - For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. - -## 1.6.7 - -Bug fixes -- Resolved Install/Save-Module error in PSCore 6.1.0-preview.4 on Ubuntu 18.04 OS (WSL/Azure) (#313) -- Updated error message in Save-Module cmdlet when the specified path is not accessible (#313) -- Added few additional verbose messages (#313) - -## 1.6.6 - -Dependency Updates -* Add dependency on version 4.1.0 or newer of NuGet.exe -* Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe - -Build and Code Cleanup Improvements -* Improved error handling in network connectivity tests. - -Bug fixes -- Change Update-ModuleManifest so that prefix is not added to CmdletsToExport. -- Change Update-ModuleManifest so that parameters will not reset to default values. -- Specify AllowPrereleseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets. - -## 1.6.5 - -New features -* Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258) - - Whitelist installation of non-Microsoft signed Pester and PSReadline over Microsoft signed Pester and PSReadline. - -Build and Code Cleanup Improvements -* Splitting of functions (#229) (Thanks @Benny1007) - - Moves private functions into respective private folder. - - Moves public functions as defined in PSModule.psd1 into respective public folder. - - Removes all functions from PSModule.psm1 file. - - Dot sources the functions from PSModule.psm1 file. - - Uses Export-ModuleMember to export the public functions from PSModule.psm1 file. - -* Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007) - - Merged public and private functions into one .psm1 file to increase load time performance. - -Bug fixes -- Fix null parameter error caused by MinimumVersion in Publish-PackageUtility (#201) -- Change .ExternalHelp link from PSGet.psm1-help.xml to PSModule-help.xml in PSModule.psm1 file (#215) -- Change Publish-* to allow version comparison instead of string comparison (#219) -- Ensure Get-InstalledScript -RequiredVersion works when versions have a leading 0 (#260) -- Add positional path to Save-Module and Save-Script (#264, #266) -- Ensure that Get-AuthenticodePublisher verifies publisher and that installing or updating a module checks for approprite catalog signature (#272) -- Update HelpInfoURI to 'http://go.microsoft.com/fwlink/?linkid=855963' (#274) - - -## 1.6.0 - -New features -* Prerelease Version Support (#185) - - Implemented prerelease versions functionality in PowerShellGet cmdlets. - - Enables publishing, discovering, and installing the prerelease versions of modules and scripts from the PowerShell Gallery. - - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/PrereleaseModule) - -* Enabled publish cmdlets on PWSH and Nano Server (#196) - - Dotnet command version 2.0.0 or newer should be installed by the user prior to using the publish cmdlets on PWSH and Windows Nano Server. - - Users can install the dotnet command by following the instructions specified at https://aka.ms/dotnet-install-script. - - On Windows, users can install the dotnet command by running *Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile '.\dotnet-install.ps1'; & '.\dotnet-install.ps1' -Channel Current -Version '2.0.0'* - - Publish cmdlets on Windows PowerShell supports using the dotnet command for publishing operations. - -Breaking Change -- PWSH: Changed the installation location of AllUsers scope to the parent of $PSHOME instead of $PSHOME. It is the SHARED_MODULES folder on PWSH. - -Bug fixes -- Update HelpInfoURI to 'https://go.microsoft.com/fwlink/?linkid=855963' (#195) -- Ensure MyDocumentsPSPath path is correct (#179) (Thanks @lwsrbrts) - - -## 1.5.0.0 - -New features -* Added support for modules requiring license acceptance (#150) - - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/RequireLicenseAcceptance) - -* Added version for REQUIREDSCRIPTS (#162) - - Enabled following scenarios for REQUIREDSCRIPTS - - [1.0] - RequiredVersion - - [1.0,2.0] - Min and Max Version - - (,1.0] - Max Version - - 1.0 - Min Version - -Bug fixes -* Fixed empty version value in nuspec (#157) - - -## 1.1.3.2 -* Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153) -* Fixed for DateTime format serialization issue. (#141) -* Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129) - -## 1.1.3.1 - -New features -* Added `PrivateData` field to ScriptFileInfo. (#119) - -Bug fixes - -## 1.1.2.0 - -Bug fixes - -## 1.1.1.0 - -Bug fixes - -## 1.1.0.0 - -* Initial release from GitHub. -* PowerShellCore support. - -## For full history of release notes see changelog: -https://github.com/PowerShell/PowerShellGet/blob/master/CHANGELOG.md -'@ - } - } - - HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=2113539' -} - - -# SIG # Begin signature block -# MIIjkQYJKoZIhvcNAQcCoIIjgjCCI34CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCRNShWem0qs5De -# OoNTMpUMZmnlgRrJHCT4bl+44h9Jy6CCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZjCCFWICAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg3TxELXso -# oRSF8xKPexxNhyDHV3uwUf4mRHTftOyVvf4wQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQC2MdGNwwt17U4YHP6Gn/7I/Z5/XKA08BGacJ6NSvXC -# 7BK55Looz5q9JuOjKgY0J5Zv3s3cBCBiABOF/E+kfjMqCtK1x9xC0dz+ZXGjKyFb -# MFuuzueIFHKnZWjLXueiAXmXuhQEV1lmw2X2Cv5lCV7fhoUyYP47yJvS+nHk5c2u -# 8OSZ4iVgBTcazWLWZTDu4zbVKk2g2HawdlGEwsCBn21gwhyqIelNUa/Cs4ab+a30 -# Ach8Jd12YE/ZmrSsoUV1hVDFA+/Z7oeZfuENa2mUUlrZ7Y+uLpbw8GZ4vge7TO23 -# r2cFGEV5g6q1/ykxFisUUheyYfjfsWqPVhXe6+9Mvk94oYIS8DCCEuwGCisGAQQB -# gjcDAwExghLcMIIS2AYJKoZIhvcNAQcCoIISyTCCEsUCAQMxDzANBglghkgBZQME -# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIFoqO98TuKz1Zz96t7blTSp0BuwKzzxkArukjb8S -# 0+wRAgZfYPq2eAYYEjIwMjAwOTIyMjIxOTUwLjkzWjAEgAIB9KCB1KSB0TCBzjEL -# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v -# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj -# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU -# U1MgRVNOOjc4ODAtRTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T -# dGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAEooA6B4TbVT8IAAAAA -# ASgwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp -# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw -# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw -# HhcNMTkxMjE5MDExNTAwWhcNMjEwMzE3MDExNTAwWjCBzjELMAkGA1UEBhMCVVMx -# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT -# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh -# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjc4ODAt -# RTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl -# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnZGx1vdU24Y+zb8OClz2 -# C3vssbQk+QPhVZOUQkuSrOdMmX5Ghl+I7A3qJZ8+7iT+SPyfBjum8uzU6wHLj3jK -# 6yDiscvAc1Qk+3DVNzngw4uB1yiwDg3GSLvd8PKpbAO2M52TofuQ1zME+oAMPoH3 -# yi3vv/BIAIEkjGb2oBS52q5Ll9zMIXT75pZRq8O7jpTdy/ocSMh1XZl0lNQqDhZQ -# h1NgxBcjTzb6pKzjlYFmNwr3z+0h/Hy6ryrySxYX37NSMZMWIxooeGftxIKgSPsT -# W1WZbTwhKlLrvxYU/b4DQ5DBpZwko0AIr4n4trsvPZsa6kKJ04bPlcN7BzWUP2cs -# 9wIDAQABo4IBGzCCARcwHQYDVR0OBBYEFITi8oPxfrU3m9QBw050f1AEy6byMB8G -# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG -# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp -# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH -# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh -# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB -# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAItfZkcYhQuAOT+JxwdZTLCMPICwEeWG -# Sa2YGniWV3Avd02jRtdlkeJJkH5zYrO8+pjrgGUQKNL8+q6vab1RpPU3QF5SjBEd -# BPzzB3N33iBiopeYsNtVHzJ5WAGRw/8mJVZtd1DNzPURMeBauH67MDwHBSABocnD -# 6ddhxwi4OA8kzVRN42X1Hk69/7rNHYTlkjgOsiq9LiMfhCygw9OfbsCM3tVm3hqa -# hHEwsRxABLu89PUlRRpEWkUeaRRhWWfVgyzD///r3rxpG/LdyYKVLji7GSRogtuG -# HWHT16NmMeGsSf6T0xxWRaK5jvbiMn/nu3KUzsD+PMhY2PUXxWWGTLIwggZxMIIE -# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV -# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE -# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v -# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y -# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN -# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU -# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE -# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50 -# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd -# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR -# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB -# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8 -# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB -# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO -# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w -# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr -# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv -# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB -# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3 -# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF -# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt -# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh -# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7 -# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR -# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9 -# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8 -# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+ -# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh -# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy -# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo -# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx -# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341 -# Hgi62jbb01+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w -# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjc4 -# ODAtRTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2 -# aWNloiMKAQEwBwYFKw4DAhoDFQAxPUsb8oASPReyIv2fubGZfVp9m6CBgzCBgKR+ -# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT -# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA -# 4xSzVjAiGA8yMDIwMDkyMjIxMzEwMloYDzIwMjAwOTIzMjEzMTAyWjB3MD0GCisG -# AQQBhFkKBAExLzAtMAoCBQDjFLNWAgEAMAoCAQACAiF/AgH/MAcCAQACAhGdMAoC -# BQDjFgTWAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA -# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAIIiMzPTf2R4++LT5 -# BIQAAZ7XoAkwHZm+GsArFZSjJmmdGtYNmLO7vA433YPn1av4tVK9wasdh+Uxa3Qo -# YD4D+LR+E8bwdZR+JGQZKDkxjmOXwPITowyRtYQPCa9mTO4iJDf/gixpGBzu8sgM -# zXRdLiDn1P/Vo8FpWdAKfaLz4P4xggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJV -# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE -# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt -# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAASigDoHhNtVPwgAAAAABKDANBglghkgBZQME -# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ -# BDEiBCDvMyofVUL0InlfJG07Yi8ZRMCLjc3rNDrU8nhvTqbq+DCB+gYLKoZIhvcN -# AQkQAi8xgeowgecwgeQwgb0EILxFaouvBVJ379wbEN8GpLhvW09eGg8WsLrXm9XW -# 6BTaMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x -# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv -# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEo -# oA6B4TbVT8IAAAAAASgwIgQgRO6TlKRXX+wSVoBIMaqor080XnPuS/BnRzhuQ8oN -# BCQwDQYJKoZIhvcNAQELBQAEggEAZM529j3yYoEo0YZVvE7ZwiKlil8xSVU3JNZE -# gHE391SCUFpeLu012jhMt3ikeJjApyUuhuQpyWkU1pkIMJHP78SOq4VejIMhm57l -# iIUo1txBy4P6NZcNmIihQa/R484b4k1Zz1GP7jx+Wmvq65FHPv3SFlmJOs6GuE1R -# lIhZdc8VNW7DsyGShMOIKx5lYb68+rsUES5bRExAxAj0ykGPpmiQt3QRmBNlDEgc -# 7GF2FGWFD7ZdAn6+W66UmqxkU7MpBrVwggRi92YXMbZI2A6ts8UR8G8JeJZbaHRz -# hnAaK3NPwhZhfD/2oRN172Rr7uRQtH9FesspG0TlfkjEe6P9zw== -# SIG # End signature block + + VariablesToExport = 'PSGetPath' + AliasesToExport = @('inmo', 'fimo', 'upmo', 'pumo') + FileList = @('PSModule.psm1', + 'PSGet.Format.ps1xml', + 'PSGet.Resource.psd1') + RequiredModules = @(@{ModuleName = 'PackageManagement'; ModuleVersion = '1.4.4' }) + PrivateData = @{ + "PackageManagementProviders" = 'PSModule.psm1' + "SupportedPowerShellGetFormatVersions" = @('1.x', '2.x') + PSData = @{ + Tags = @('Packagemanagement', + 'Provider', + 'PSEdition_Desktop', + 'PSEdition_Core', + 'Linux', + 'Mac') + ProjectUri = 'https://go.microsoft.com/fwlink/?LinkId=828955' + LicenseUri = 'https://go.microsoft.com/fwlink/?LinkId=829061' + ReleaseNotes = @' +### 2.2.5 +- Security patch for code injection bug + +### 2.2.4.1 +- Remove catalog file + +### 2.2.3 +- Update `HelpInfoUri` to point to the latest content (#560) +- Improve discovery of usable nuget.exe binary (Thanks bwright86!) (#558) + +### 2.2.2 +Bug Fix + +- Update casing of DscResources output + +### 2.2.1 +Bug Fix + +- Allow DscResources to work on case sensitive platforms (#521) +- Fix for failure to return credential provider when using private feeds (#521) + +## 2.2 +Bug Fix + +- Fix for prompting for credentials when passing in -Credential parameter when using Register-PSRepository + +## 2.1.5 +New Features + +- Add and remove nuget based repositories as a nuget source when nuget client tool is installed (#498) + +Bug Fix + +- Fix for 'Failed to publish module' error thrown when publishing modules (#497) + +## 2.1.4 +- Fixed hang while publishing some packages (#478) + +## 2.1.3 +New Features + +- Added -Scope parameter to Update-Module (Thanks @lwajswaj!) (#471) +- Added -Exclude parameter to Publish-Module (Thanks @Benny1007!) (#191) +- Added -SkipAutomaticTags parameter to Publish-Module (Thanks @awickham10!) (#452) + +Bug Fix + +- Fixed issue with finding modules using macOS and .NET Core 3.0 + +## 2.1.2 + +New Feature + +- Added support for registering repositories with special characters + +## 2.1.1 + +- Fix DSC resource folder structure + +## 2.1.0 + +Breaking Change + +- Default installation scope for Update-Module and Update-Script has changed to match Install-Module and Install-Script. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times. + For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. (#421) + +Bug Fixes + +- Update-ModuleManifest no longer clears FunctionsToExport, AliasesToExport, nor NestModules (#415 & #425) (Thanks @pougetat and @tnieto88!) +- Update-Module no longer changes repository URL (#407) +- Update-ModuleManifest no longer preprends 'PSGet_' to module name (#403) (Thanks @ThePoShWolf) +- Update-ModuleManifest now throws error and fails to update when provided invalid entries (#398) (Thanks @pougetat!) +- Ignore files no longer being included when uploading modules (#396) + +New Features + +- New DSC resource, PSRepository (#426) (Thanks @johlju!) +- Piping of PS respositories (#420) +- utf8 support for .nuspec (#419) + +## 2.0.4 + +Bug Fix +* Remove PSGallery availability checks (#374) + +## 2.0.3 + +Bug fixes and Improvements +* Fix CommandAlreadyAvailable error for PackageManagement module (#333) +* Remove trailing whitespace when value is not provided for Get-PSScriptInfoString (#337) (Thanks @thomasrayner) +* Expanded aliases for improved readability (#338) (Thanks @lazywinadmin) +* Improvements for Catalog tests (#343) +* Fix Update-ScriptInfoFile to preserve PrivateData (#346) (Thanks @tnieto88) +* Import modules with many commands faster (#351) + +New Features +* Tab completion for -Repository parameter (#339) and for Publish-Module -Name (#359) (Thanks @matt9ucci) + +## 2.0.1 + +Bug fixes +- Resolved Publish-Module doesn't report error but fails to publish module (#316) +- Resolved CommandAlreadyAvailable error while installing the latest version of PackageManagement module (#333) + +## 2.0.0 + +Breaking Change +- Default installation scope for Install-Module, Install-Script, and Install-Package has changed. For Windows PowerShell (version 5.1 or below), the default scope is AllUsers when running in an elevated session, and CurrentUser at all other times. + For PowerShell version 6.0.0 and above, the default installation scope is always CurrentUser. + +## 1.6.7 + +Bug fixes +- Resolved Install/Save-Module error in PSCore 6.1.0-preview.4 on Ubuntu 18.04 OS (WSL/Azure) (#313) +- Updated error message in Save-Module cmdlet when the specified path is not accessible (#313) +- Added few additional verbose messages (#313) + +## 1.6.6 + +Dependency Updates +* Add dependency on version 4.1.0 or newer of NuGet.exe +* Update NuGet.exe bootstrap URL to https://aka.ms/psget-nugetexe + +Build and Code Cleanup Improvements +* Improved error handling in network connectivity tests. + +Bug fixes +- Change Update-ModuleManifest so that prefix is not added to CmdletsToExport. +- Change Update-ModuleManifest so that parameters will not reset to default values. +- Specify AllowPrereleseVersions provider option only when AllowPrerelease is specified on the PowerShellGet cmdlets. + +## 1.6.5 + +New features +* Allow Pester/PSReadline installation when signed by non-Microsoft certificate (#258) + - Whitelist installation of non-Microsoft signed Pester and PSReadline over Microsoft signed Pester and PSReadline. + +Build and Code Cleanup Improvements +* Splitting of functions (#229) (Thanks @Benny1007) + - Moves private functions into respective private folder. + - Moves public functions as defined in PSModule.psd1 into respective public folder. + - Removes all functions from PSModule.psm1 file. + - Dot sources the functions from PSModule.psm1 file. + - Uses Export-ModuleMember to export the public functions from PSModule.psm1 file. + +* Add build step to construct a single .psm1 file (#242) (Thanks @Benny1007) + - Merged public and private functions into one .psm1 file to increase load time performance. + +Bug fixes +- Fix null parameter error caused by MinimumVersion in Publish-PackageUtility (#201) +- Change .ExternalHelp link from PSGet.psm1-help.xml to PSModule-help.xml in PSModule.psm1 file (#215) +- Change Publish-* to allow version comparison instead of string comparison (#219) +- Ensure Get-InstalledScript -RequiredVersion works when versions have a leading 0 (#260) +- Add positional path to Save-Module and Save-Script (#264, #266) +- Ensure that Get-AuthenticodePublisher verifies publisher and that installing or updating a module checks for approprite catalog signature (#272) +- Update HelpInfoURI to 'http://go.microsoft.com/fwlink/?linkid=855963' (#274) + + +## 1.6.0 + +New features +* Prerelease Version Support (#185) + - Implemented prerelease versions functionality in PowerShellGet cmdlets. + - Enables publishing, discovering, and installing the prerelease versions of modules and scripts from the PowerShell Gallery. + - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/PrereleaseModule) + +* Enabled publish cmdlets on PWSH and Nano Server (#196) + - Dotnet command version 2.0.0 or newer should be installed by the user prior to using the publish cmdlets on PWSH and Windows Nano Server. + - Users can install the dotnet command by following the instructions specified at https://aka.ms/dotnet-install-script. + - On Windows, users can install the dotnet command by running *Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile '.\dotnet-install.ps1'; & '.\dotnet-install.ps1' -Channel Current -Version '2.0.0'* + - Publish cmdlets on Windows PowerShell supports using the dotnet command for publishing operations. + +Breaking Change +- PWSH: Changed the installation location of AllUsers scope to the parent of $PSHOME instead of $PSHOME. It is the SHARED_MODULES folder on PWSH. + +Bug fixes +- Update HelpInfoURI to 'https://go.microsoft.com/fwlink/?linkid=855963' (#195) +- Ensure MyDocumentsPSPath path is correct (#179) (Thanks @lwsrbrts) + + +## 1.5.0.0 + +New features +* Added support for modules requiring license acceptance (#150) + - [Documentation](https://docs.microsoft.com/en-us/powershell/gallery/psget/module/RequireLicenseAcceptance) + +* Added version for REQUIREDSCRIPTS (#162) + - Enabled following scenarios for REQUIREDSCRIPTS + - [1.0] - RequiredVersion + - [1.0,2.0] - Min and Max Version + - (,1.0] - Max Version + - 1.0 - Min Version + +Bug fixes +* Fixed empty version value in nuspec (#157) + + +## 1.1.3.2 +* Disabled PowerShellGet Telemetry on PS Core as PowerShell Telemetry APIs got removed in PowerShell Core beta builds. (#153) +* Fixed for DateTime format serialization issue. (#141) +* Update-ModuleManifest should add ExternalModuleDependencies value as a collection. (#129) + +## 1.1.3.1 + +New features +* Added `PrivateData` field to ScriptFileInfo. (#119) + +Bug fixes + +## 1.1.2.0 + +Bug fixes + +## 1.1.1.0 + +Bug fixes + +## 1.1.0.0 + +* Initial release from GitHub. +* PowerShellCore support. + +## For full history of release notes see changelog: +https://github.com/PowerShell/PowerShellGet/blob/master/CHANGELOG.md +'@ + } + } + + HelpInfoURI = 'http://go.microsoft.com/fwlink/?linkid=2113539' +} + + +# SIG # Begin signature block +# MIIjkQYJKoZIhvcNAQcCoIIjgjCCI34CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCCRNShWem0qs5De +# OoNTMpUMZmnlgRrJHCT4bl+44h9Jy6CCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZjCCFWICAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQg3TxELXso +# oRSF8xKPexxNhyDHV3uwUf4mRHTftOyVvf4wQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQC2MdGNwwt17U4YHP6Gn/7I/Z5/XKA08BGacJ6NSvXC +# 7BK55Looz5q9JuOjKgY0J5Zv3s3cBCBiABOF/E+kfjMqCtK1x9xC0dz+ZXGjKyFb +# MFuuzueIFHKnZWjLXueiAXmXuhQEV1lmw2X2Cv5lCV7fhoUyYP47yJvS+nHk5c2u +# 8OSZ4iVgBTcazWLWZTDu4zbVKk2g2HawdlGEwsCBn21gwhyqIelNUa/Cs4ab+a30 +# Ach8Jd12YE/ZmrSsoUV1hVDFA+/Z7oeZfuENa2mUUlrZ7Y+uLpbw8GZ4vge7TO23 +# r2cFGEV5g6q1/ykxFisUUheyYfjfsWqPVhXe6+9Mvk94oYIS8DCCEuwGCisGAQQB +# gjcDAwExghLcMIIS2AYJKoZIhvcNAQcCoIISyTCCEsUCAQMxDzANBglghkgBZQME +# AgEFADCCAVQGCyqGSIb3DQEJEAEEoIIBQwSCAT8wggE7AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIFoqO98TuKz1Zz96t7blTSp0BuwKzzxkArukjb8S +# 0+wRAgZfYPq2eAYYEjIwMjAwOTIyMjIxOTUwLjkzWjAEgAIB9KCB1KSB0TCBzjEL +# MAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1v +# bmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWlj +# cm9zb2Z0IE9wZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBU +# U1MgRVNOOjc4ODAtRTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1T +# dGFtcCBTZXJ2aWNloIIORDCCBPUwggPdoAMCAQICEzMAAAEooA6B4TbVT8IAAAAA +# ASgwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hp +# bmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jw +# b3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTAw +# HhcNMTkxMjE5MDExNTAwWhcNMjEwMzE3MDExNTAwWjCBzjELMAkGA1UEBhMCVVMx +# EzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoT +# FU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9wZXJh +# dGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjc4ODAt +# RTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2aWNl +# MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnZGx1vdU24Y+zb8OClz2 +# C3vssbQk+QPhVZOUQkuSrOdMmX5Ghl+I7A3qJZ8+7iT+SPyfBjum8uzU6wHLj3jK +# 6yDiscvAc1Qk+3DVNzngw4uB1yiwDg3GSLvd8PKpbAO2M52TofuQ1zME+oAMPoH3 +# yi3vv/BIAIEkjGb2oBS52q5Ll9zMIXT75pZRq8O7jpTdy/ocSMh1XZl0lNQqDhZQ +# h1NgxBcjTzb6pKzjlYFmNwr3z+0h/Hy6ryrySxYX37NSMZMWIxooeGftxIKgSPsT +# W1WZbTwhKlLrvxYU/b4DQ5DBpZwko0AIr4n4trsvPZsa6kKJ04bPlcN7BzWUP2cs +# 9wIDAQABo4IBGzCCARcwHQYDVR0OBBYEFITi8oPxfrU3m9QBw050f1AEy6byMB8G +# A1UdIwQYMBaAFNVjOlyKMZDzQ3t8RhvFM2hahW1VMFYGA1UdHwRPME0wS6BJoEeG +# RWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Rp +# bVN0YVBDQV8yMDEwLTA3LTAxLmNybDBaBggrBgEFBQcBAQROMEwwSgYIKwYBBQUH +# MAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbS9wa2kvY2VydHMvTWljVGltU3Rh +# UENBXzIwMTAtMDctMDEuY3J0MAwGA1UdEwEB/wQCMAAwEwYDVR0lBAwwCgYIKwYB +# BQUHAwgwDQYJKoZIhvcNAQELBQADggEBAItfZkcYhQuAOT+JxwdZTLCMPICwEeWG +# Sa2YGniWV3Avd02jRtdlkeJJkH5zYrO8+pjrgGUQKNL8+q6vab1RpPU3QF5SjBEd +# BPzzB3N33iBiopeYsNtVHzJ5WAGRw/8mJVZtd1DNzPURMeBauH67MDwHBSABocnD +# 6ddhxwi4OA8kzVRN42X1Hk69/7rNHYTlkjgOsiq9LiMfhCygw9OfbsCM3tVm3hqa +# hHEwsRxABLu89PUlRRpEWkUeaRRhWWfVgyzD///r3rxpG/LdyYKVLji7GSRogtuG +# HWHT16NmMeGsSf6T0xxWRaK5jvbiMn/nu3KUzsD+PMhY2PUXxWWGTLIwggZxMIIE +# WaADAgECAgphCYEqAAAAAAACMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9v +# dCBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkgMjAxMDAeFw0xMDA3MDEyMTM2NTVaFw0y +# NTA3MDEyMTQ2NTVaMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMIIBIjAN +# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqR0NvHcRijog7PwTl/X6f2mUa3RU +# ENWlCgCChfvtfGhLLF/Fw+Vhwna3PmYrW/AVUycEMR9BGxqVHc4JE458YTBZsTBE +# D/FgiIRUQwzXTbg4CLNC3ZOs1nMwVyaCo0UN0Or1R4HNvyRgMlhgRvJYR4YyhB50 +# YWeRX4FUsc+TTJLBxKZd0WETbijGGvmGgLvfYfxGwScdJGcSchohiq9LZIlQYrFd +# /XcfPfBXday9ikJNQFHRD5wGPmd/9WbAA5ZEfu/QS/1u5ZrKsajyeioKMfDaTgaR +# togINeh4HLDpmc085y9Euqf03GS9pAHBIAmTeM38vMDJRF1eFpwBBU8iTQIDAQAB +# o4IB5jCCAeIwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFNVjOlyKMZDzQ3t8 +# RhvFM2hahW1VMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1UdDwQEAwIB +# hjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFNX2VsuP6KJcYmjRPZSQW9fO +# mhjEMFYGA1UdHwRPME0wS6BJoEeGRWh0dHA6Ly9jcmwubWljcm9zb2Z0LmNvbS9w +# a2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNybDBaBggr +# BgEFBQcBAQROMEwwSgYIKwYBBQUHMAKGPmh0dHA6Ly93d3cubWljcm9zb2Z0LmNv +# bS9wa2kvY2VydHMvTWljUm9vQ2VyQXV0XzIwMTAtMDYtMjMuY3J0MIGgBgNVHSAB +# Af8EgZUwgZIwgY8GCSsGAQQBgjcuAzCBgTA9BggrBgEFBQcCARYxaHR0cDovL3d3 +# dy5taWNyb3NvZnQuY29tL1BLSS9kb2NzL0NQUy9kZWZhdWx0Lmh0bTBABggrBgEF +# BQcCAjA0HjIgHQBMAGUAZwBhAGwAXwBQAG8AbABpAGMAeQBfAFMAdABhAHQAZQBt +# AGUAbgB0AC4gHTANBgkqhkiG9w0BAQsFAAOCAgEAB+aIUQ3ixuCYP4FxAz2do6Eh +# b7Prpsz1Mb7PBeKp/vpXbRkws8LFZslq3/Xn8Hi9x6ieJeP5vO1rVFcIK1GCRBL7 +# uVOMzPRgEop2zEBAQZvcXBf/XPleFzWYJFZLdO9CEMivv3/Gf/I3fVo/HPKZeUqR +# UgCvOA8X9S95gWXZqbVr5MfO9sp6AG9LMEQkIjzP7QOllo9ZKby2/QThcJ8ySif9 +# Va8v/rbljjO7Yl+a21dA6fHOmWaQjP9qYn/dxUoLkSbiOewZSnFjnXshbcOco6I8 +# +n99lmqQeKZt0uGc+R38ONiU9MalCpaGpL2eGq4EQoO4tYCbIjggtSXlZOz39L9+ +# Y1klD3ouOVd2onGqBooPiRa6YacRy5rYDkeagMXQzafQ732D8OE7cQnfXXSYIghh +# 2rBQHm+98eEA3+cxB6STOvdlR3jo+KhIq/fecn5ha293qYHLpwmsObvsxsvYgrRy +# zR30uIUBHoD7G4kqVDmyW9rIDVWZeodzOwjmmC3qjeAzLhIp9cAvVCch98isTtoo +# uLGp25ayp0Kiyc8ZQU3ghvkqmqMRZjDTu3QyS99je/WZii8bxyGvWbWu3EQ8l1Bx +# 16HSxVXjad5XwdHeMMD9zOZN+w2/XU/pnR4ZOC+8z1gFLu8NoFA12u8JJxzVs341 +# Hgi62jbb01+P3nSISRKhggLSMIICOwIBATCB/KGB1KSB0TCBzjELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEpMCcGA1UECxMgTWljcm9zb2Z0IE9w +# ZXJhdGlvbnMgUHVlcnRvIFJpY28xJjAkBgNVBAsTHVRoYWxlcyBUU1MgRVNOOjc4 +# ODAtRTM5MC04MDE0MSUwIwYDVQQDExxNaWNyb3NvZnQgVGltZS1TdGFtcCBTZXJ2 +# aWNloiMKAQEwBwYFKw4DAhoDFQAxPUsb8oASPReyIv2fubGZfVp9m6CBgzCBgKR+ +# MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xJjAkBgNVBAMT +# HU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwMA0GCSqGSIb3DQEBBQUAAgUA +# 4xSzVjAiGA8yMDIwMDkyMjIxMzEwMloYDzIwMjAwOTIzMjEzMTAyWjB3MD0GCisG +# AQQBhFkKBAExLzAtMAoCBQDjFLNWAgEAMAoCAQACAiF/AgH/MAcCAQACAhGdMAoC +# BQDjFgTWAgEAMDYGCisGAQQBhFkKBAIxKDAmMAwGCisGAQQBhFkKAwKgCjAIAgEA +# AgMHoSChCjAIAgEAAgMBhqAwDQYJKoZIhvcNAQEFBQADgYEAIIiMzPTf2R4++LT5 +# BIQAAZ7XoAkwHZm+GsArFZSjJmmdGtYNmLO7vA433YPn1av4tVK9wasdh+Uxa3Qo +# YD4D+LR+E8bwdZR+JGQZKDkxjmOXwPITowyRtYQPCa9mTO4iJDf/gixpGBzu8sgM +# zXRdLiDn1P/Vo8FpWdAKfaLz4P4xggMNMIIDCQIBATCBkzB8MQswCQYDVQQGEwJV +# UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UE +# ChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGlt +# ZS1TdGFtcCBQQ0EgMjAxMAITMwAAASigDoHhNtVPwgAAAAABKDANBglghkgBZQME +# AgEFAKCCAUowGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMC8GCSqGSIb3DQEJ +# BDEiBCDvMyofVUL0InlfJG07Yi8ZRMCLjc3rNDrU8nhvTqbq+DCB+gYLKoZIhvcN +# AQkQAi8xgeowgecwgeQwgb0EILxFaouvBVJ379wbEN8GpLhvW09eGg8WsLrXm9XW +# 6BTaMIGYMIGApH4wfDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +# EDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlv +# bjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRpbWUtU3RhbXAgUENBIDIwMTACEzMAAAEo +# oA6B4TbVT8IAAAAAASgwIgQgRO6TlKRXX+wSVoBIMaqor080XnPuS/BnRzhuQ8oN +# BCQwDQYJKoZIhvcNAQELBQAEggEAZM529j3yYoEo0YZVvE7ZwiKlil8xSVU3JNZE +# gHE391SCUFpeLu012jhMt3ikeJjApyUuhuQpyWkU1pkIMJHP78SOq4VejIMhm57l +# iIUo1txBy4P6NZcNmIihQa/R484b4k1Zz1GP7jx+Wmvq65FHPv3SFlmJOs6GuE1R +# lIhZdc8VNW7DsyGShMOIKx5lYb68+rsUES5bRExAxAj0ykGPpmiQt3QRmBNlDEgc +# 7GF2FGWFD7ZdAn6+W66UmqxkU7MpBrVwggRi92YXMbZI2A6ts8UR8G8JeJZbaHRz +# hnAaK3NPwhZhfD/2oRN172Rr7uRQtH9FesspG0TlfkjEe6P9zw== +# SIG # End signature block diff --git a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/en-US/PSGet.Resource.psd1 b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/en-US/PSGet.Resource.psd1 index 2315e1087e..f81171897b 100644 --- a/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/en-US/PSGet.Resource.psd1 +++ b/src/PowerShell/ExternalModules/PowerShellGet/2.2.5/en-US/PSGet.Resource.psd1 @@ -1,466 +1,466 @@ -######################################################################################### -# -# Copyright (c) Microsoft Corporation. All rights reserved. -# -# Localized PSGet.Resource.psd1 -# -######################################################################################### - -ConvertFrom-StringData @' -###PSLOC - InstallModulewhatIfMessage=Version '{1}' of module '{0}' - InstallScriptwhatIfMessage=Version '{1}' of script '{0}' - UpdateModulewhatIfMessage=Version '__OLDVERSION__' of module '{0}', updating to version '{1}' - UpdateScriptwhatIfMessage=Version '__OLDVERSION__' of script '{0}', updating to version '{1}' - PublishModulewhatIfMessage=Version '{0}' of module '{1}' - PublishScriptwhatIfMessage=Version '{0}' of script '{1}' - NewScriptFileInfowhatIfMessage=Creating the '{0}' PowerShell Script file - UpdateScriptFileInfowhatIfMessage=Updating the '{0}' PowerShell Script file - NameShouldNotContainWildcardCharacters=The specified name '{0}' should not contain any wildcard characters, please correct it and try again. - AllVersionsCannotBeUsedWithOtherVersionParameters=You cannot use the parameter AllVersions with RequiredVersion, MinimumVersion or MaximumVersion in the same command. - VersionRangeAndRequiredVersionCannotBeSpecifiedTogether=You cannot use the parameters RequiredVersion and either MinimumVersion or MaximumVersion in the same command. Specify only one of these parameters in your command. - RequiredVersionAllowedOnlyWithSingleModuleName=The RequiredVersion parameter is allowed only when a single module name is specified as the value of the Name parameter, without any wildcard characters. - MinimumVersionIsGreaterThanMaximumVersion=The specified MinimumVersion '{0}' is greater than the specified MaximumVersion '{1}'. - AllowPrereleaseRequiredToUsePrereleaseStringInVersion=The '-AllowPrerelease' parameter must be specified when using the Prerelease string in MinimumVersion, MaximumVersion, or RequiredVersion. - UpdateModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to update modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or update '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - InstallModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - InstallScriptAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install scripts in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - AdministratorRightsNeededOrSpecifyCurrentUserScope=Administrator rights are required to install or update. Log on to the computer with an account that has Administrator rights, and then try again, or install by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - VersionParametersAreAllowedOnlyWithSingleName=The RequiredVersion, MinimumVersion, MaximumVersion, AllVersions or AllowPrerelease parameters are allowed only when you specify a single name as the value of the Name parameter, without any wildcard characters. - PathIsNotADirectory=The specified path '{0}' is not a valid directory. - ModuleAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Module, and add the -Force parameter. - ScriptAlreadyInstalled=Version '{0}' of script '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Script, and add the -Force parameter. - CommandAlreadyAvailable=A command with name '{0}' is already available on this system. This script '{0}' may override the existing command. If you still want to install this script '{0}', use -Force parameter. - ModuleAlreadyInstalledSxS=Version '{0}' of module '{1}' is already installed at '{2}'. To install version '{3}', run Install-Module and add the -Force parameter, this command will install version '{5}' side-by-side with version '{4}'. - ModuleAlreadyInstalledVerbose=Version '{0}' of module '{1}' is already installed at '{2}'. - ScriptAlreadyInstalledVerbose=Version '{0}' of script '{1}' is already installed at '{2}'. - ModuleWithRequiredVersionAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To reinstall this version '{3}', run Install-Module or Updated-Module cmdlet with the -Force parameter. - InvalidPSModule=The module '{0}' cannot be installed or updated because it is not a properly-formed module. - InvalidPowerShellScriptFile=The script '{0}' cannot be installed or updated because it is not a properly-formed script. - InvalidAuthenticodeSignature=The module '{0}' cannot be installed or updated because the Authenticode signature for the file '{1}' is not valid. - ModuleNotInstalledOnThisMachine=Module '{0}' was not updated because no valid module was found in the module directory. Verify that the module is located in the folder specified by $env:PSModulePath. - ScriptNotInstalledOnThisMachine=Script '{0}' was not updated because no valid script was found in the script directories '{1}' and '{2}'. - AdminPrivilegesRequiredForUpdate=Module '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that directory. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - AdminPrivilegesRequiredForScriptUpdate=Script '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that script. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - ModuleNotInstalledUsingPowerShellGet=Module '{0}' was not installed by using Install-Module, so it cannot be updated. - ScriptNotInstalledUsingPowerShellGet=Script '{0}' was not installed by using Install-Script, so it cannot be updated. - DownloadingModuleFromGallery=Downloading module '{0}' with version '{1}' from the repository '{2}'. - DownloadingScriptFromGallery=Downloading script '{0}' with version '{1}' from the repository '{2}'. - NoUpdateAvailable=No updates were found for module '{0}'. - NoScriptUpdateAvailable=No updates were found for script '{0}'. - FoundModuleUpdate=An update for the module '{0}' was found with version '{1}'. - FoundScriptUpdate=An update for the script '{0}' was found with version '{1}'. - InvalidPSModuleDuringUpdate= Module '{0}' was not updated because the module in the repository '{1}' is not a valid Windows PowerShell module. - ModuleGotUpdated=Module '{0}' has been updated successfully. - TestingModuleInUse=Testing if the module to update is in use. - ModuleDestination= The specified module will be installed in '{0}'. - ScriptDestination= The specified script will be installed in '{0}' and its dependent modules will be installed in '{1}'. - ModuleIsInUse=Module '{0}' is in currently in use or you don't have the required permissions. - ModuleInstalledSuccessfully=Module '{0}' was installed successfully to path '{1}'. - ModuleSavedSuccessfully=Module '{0}' was saved successfully to path '{1}'. - ScriptInstalledSuccessfully=Script '{0}' was installed successfully to path '{1}'. - ScriptSavedSuccessfully=Script '{0}' was saved successfully to path '{1}'. - CheckingForModuleUpdate= Checking for updates for module '{0}'. - CheckingForScriptUpdate= Checking for updates for script '{0}'. - ModuleInUseWithProcessDetails=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the following applications: '{2}'. - ModuleVersionInUse=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the applications. - ModuleNotAvailableLocally=The specified module '{0}' was not published because no module with that name was found in any module directory. - InvalidModulePathToPublish=The specified module with path '{0}' was not published because no valid module was found with that path. - ModuleWithRequiredVersionNotAvailableLocally= The specified module '{0}' with version '{1}' was not published because no module with that name and version was found in any module directory. - AmbiguousModuleName=Modules with the name '{0}' are available under multiple paths. Add the -RequiredVersion parameter or the -Path parameter to specify the module to publish. - AmbiguousModulePath=Multiple versions are available under the specified module path '{0}'. Specify the full path to the module to be published. - PublishModuleLocation=Module '{0}' was found in '{1}'. - InvalidModuleToPublish=Module '{0}' cannot be published because it does not have a module manifest file. Run New-ModuleManifest -Path to create a module manifest with metadata before publishing. - MissingRequiredManifestKeys=Module '{0}' cannot be published because it is missing required metadata. Verify that the module manifest specifies Description and Author. - InvalidCharactersInPrereleaseString=The Prerelease string '{0}' contains invalid characters. Please ensure that only characters 'a-zA-Z0-9' and possibly hyphen ('-') at the beginning are in the Prerelease string. - IncorrectVersionPartsCountForPrereleaseStringUsage=Version '{0}' must have exactly 3 parts for a Prerelease string to be used. - ModuleVersionShouldBeGreaterThanGalleryVersion=Module '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. - ModuleVersionIsAlreadyAvailableInTheGallery=The module '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. - CouldNotInstallNuGetProvider=NuGet provider is required to interact with NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed. - CouldNotInstallNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. - CouldNotUpgradeNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that required version of NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. - CouldNotFindDotnetCommand=For publish operations, dotnet command version '{0}' or newer is required to interact with the NuGet-based repositories. Please ensure that dotnet command version '{0}' or newer is installed and available under one of the paths specified in PATH environment variable value. You can also install the dotnet command by following the instructions specified at '{1}'. - CouldNotInstallNuGetBinaries2=PowerShellGet requires NuGet.exe (or dotnet command) and NuGet provider version '{0}' or newer to interact with the NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed and NuGet.exe (or dotnet command) is available under one of the paths specified in PATH environment variable value. - InstallNugetBinariesUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{2}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to upgrade NuGet.exe to the latest version and install the NuGet provider now? - InstallNuGetBinariesShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{3}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to install both the latest NuGet.exe and NuGet provider now? - InstallNugetExeUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to upgrade to the latest version of NuGet.exe now? - InstallNuGetExeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to install the latest version of NuGet.exe now? - InstallNuGetProviderShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{1}' or '{2}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. Do you want PowerShellGet to install and import the NuGet provider now? - InstallNuGetBinariesUpgradeShouldContinueCaption=NuGet.exe upgrade and NuGet provider installation are required to continue - InstallNuGetBinariesShouldContinueCaption=NuGet.exe and NuGet provider installation are required to continue - InstallNuGetExeUpgradeShouldContinueCaption=NuGet.exe upgrade is required to continue - InstallNuGetExeShouldContinueCaption=NuGet.exe is required to continue - InstallNuGetProviderShouldContinueCaption=NuGet provider is required to continue - DownloadingNugetExe=Installing NuGet.exe. - DownloadingNugetProvider=Installing NuGet provider. - ModuleNotFound=Module '{0}' was not found. - NoMatchFound=No match was found for the specified search criteria and module names '{0}'. - NoMatchFoundForScriptName=No match was found for the specified search criteria and script names '{0}'. - MatchInvalidType=The name '{0}' is a {1} not a {2}. - FailedToCreateCompressedModule=Failed to generate the compressed file for module '{0}'. - FailedToPublish=Failed to publish module '{0}': '{1}'. - PublishedSuccessfully=Successfully published module '{0}' to the module publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. - InvalidWebUri=The specified Uri '{0}' for parameter '{1}' is an invalid Web Uri. Please ensure that it meets the Web Uri requirements. - RepositoryAlreadyRegistered=The repository could not be registered because there exists a registered repository with Name '{0}' and SourceLocation '{1}'. To register another repository with Name '{2}', please unregister the existing repository using the Unregister-PSRepository cmdlet. - RepositoryToBeUnregisteredNotFound=The repository '{0}' was not removed because no repository was found with that name. Please run Get-PSRepository and ensure that a repository of that name is present. - RepositoryCannotBeUnregistered=The specified repository '{0}' cannot be unregistered. - RepositoryNotFound=No repository with the name '{0}' was found. - PSGalleryNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. Try again after specifying a valid repository name. You can use 'Register-PSRepository -Default' to register the PSGallery repository. - ParameterIsNotAllowedWithPSGallery=The PSGallery repository has pre-defined locations. The '{0}' parameter is not allowed, try again after removing the '{0}' parameter. - UseDefaultParameterSetOnRegisterPSRepository=Use 'Register-PSRepository -Default' to register the PSGallery repository. - RepositoryNameContainsWildCards=The repository name '{0}' should not have wildcards, correct it and try again. - InvalidRepository=The specified repository '{0}' is not a valid registered repository name. Please ensure that '{1}' is a registered repository. - RepositoryCannotBeRegistered=The specified repository '{0}' is unauthorized and cannot be registered. Try running with -Credential. - RepositoryRegistered=Successfully registered the repository '{0}' with source location '{1}'. - RepositoryUnregistered=Successfully unregistered the repository '{0}'. - PSGalleryPublishLocationIsMissing=The specified repository '{0}' does not have a valid PublishLocation. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - PSRepositoryScriptPublishLocationIsMissing=The specified repository '{0}' does not have a valid ScriptPublishLocation. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - ScriptSourceLocationIsMissing=The specified repository '{0}' does not have a valid ScriptSourceLocation. Retry after setting the ScriptSourceLocation for repository '{0}' to a valid NuGet endpoint for scripts using the Set-PSRepository cmdlet. - PublishModuleSupportsOnlyNuGetBasedPublishLocations=Publish-Module only supports the NuGet-based publish locations. The PublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - PublishScriptSupportsOnlyNuGetBasedPublishLocations=Publish-Script only supports the NuGet-based publish locations. The ScriptPublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. - DynamicParameterHelpMessage=The dynamic parameter '{0}' is required for Find-Module and Install-Module when using the PackageManagement provider '{1}' and source location '{2}'. Please enter your value for the '{3}' dynamic parameter: - ProviderApiDebugMessage=In PowerShellGet Provider - '{0}'. - ModuleUninstallNotSupported=Module uninstallation is not supported. To remove a module, please delete the module folder. - FastPackageReference=The FastPackageReference is '{0}'. - PackageManagementProviderIsNotAvailable=The specified PackageManagement provider '{0}' is not available. - SpecifiedSourceName=Using the specified source names : '{0}'. - SpecifiedLocationAndOGP=The specified Location is '{0}' and PackageManagementProvider is '{1}'. - NoSourceNameIsSpecified=The -Repository parameter was not specified. PowerShellGet will use all of the registered repositories. - GettingPackageManagementProviderObject=Getting the provider object for the PackageManagement Provider '{0}'. - InvalidInputObjectValue=Invalid value is specified for InputObject parameter. - SpecifiedInstallationScope=The installation scope is specified to be '{0}'. - SourceLocationValueForPSGalleryCannotBeChanged=The SourceLocation value for the PSGallery repository can not be changed. - PublishLocationValueForPSGalleryCannotBeChanged=The PublishLocation value for the PSGallery repository can not be changed. - SpecifiedProviderName=The specified PackageManagement provider name '{0}'. - ProviderNameNotSpecified=User did not specify the PackageManagement provider name, trying with the provider name '{0}'. - SpecifiedProviderNotAvailable=The specified PackageManagement provider '{0}' is not available. - SpecifiedProviderDoesnotSupportPSModules=The specified PackageManagement Provider '{0}' does not support PowerShell Modules. PackageManagement Providers must support the 'supports-powershell-modules' feature. - PollingPackageManagementProvidersForLocation=Polling available PackageManagement Providers to find one that can support the specified source location '{0}'. - PollingSingleProviderForLocation=Resolving the source location '{0}' with PackageManagement Provider '{1}'. - FoundProviderForLocation=The PackageManagement provider '{0}' supports the source location '{1}'. - SpecifiedLocationCannotBeRegistered=The specified location '{0}' cannot be registered. - RepositoryDetails=Repository details, Name = '{0}', Location = '{1}'; IsTrusted = '{2}'; IsRegistered = '{3}'. - NotSupportedPowerShellGetFormatVersion=The specified module '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this module, '{2}'. - NotSupportedPowerShellGetFormatVersionScripts=The specified script '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this script, '{2}'. - PathNotFound=Cannot find the path '{0}' because it does not exist. - ModuleIsNotTrusted=Untrusted module '{0}'. - ScriptIsNotTrusted=Untrusted script '{0}'. - SkippedModuleDependency=Because dependent module '{0}' was skipped in the module dependencies list, users might not know how to install it. - MissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current module '{1}', ensure that its dependent module '{2}' is installed. - ExternallyManagedModuleDependencyIsInstalled=The externally managed, dependent module '{0}' is already installed on this computer. - ScriptMissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent module '{2}' is installed. - ScriptMissingExternallyManagedScriptDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent script '{2}' is installed. - ScriptExternallyManagedScriptDependencyIsInstalled=The externally managed, dependent script '{0}' is already installed on this computer. - UnableToResolveModuleDependency=PowerShellGet cannot resolve the module dependency '{0}' of the module '{1}' on the repository '{2}'. Verify that the dependent module '{3}' is available in the repository '{4}'. If this dependent module '{5}' is managed externally, add it to the ExternalModuleDependencies entry in the PSData section of the module manifest. - FindingModuleDependencies=Finding module dependencies for version '{1}' of the module '{0}' from repository '{2}'. - InstallingDependencyModule=Installing the dependency module '{0}' with version '{1}' for the module '{2}'. - InstallingDependencyScript=Installing the dependency script '{0}' with version '{1}' for the script '{2}'. - SavingDependencyModule=Saving the dependency module '{0}' with version '{1}' for the module '{2}'. - SavingDependencyScript=Saving the dependency script '{0}' with version '{1}' for the script '{2}'. - ModuleUninstallationSucceeded=Successfully uninstalled the module '{0}' from module base '{1}'. - ScriptUninstallationSucceeded=Successfully uninstalled the script '{0}' from script base '{1}'. - AdminPrivilegesRequiredForUninstall=You cannot uninstall the module '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - AdminPrivilegesRequiredForScriptUninstall=You cannot uninstall the script '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). - ModuleUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Module '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. - ScriptUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Script '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. - UnableToUninstallModuleVersion=The module '{0}' of version '{1}' in module base folder '{2}' was installed without side-by-side version support. Some versions are installed in this module base with side-by-side version support. Uninstall other versions of this module before uninstalling the most current version. - UnableToUninstallAsOtherModulesNeedThisModule=The module '{0}' of version '{1}' in module base folder '{2}' cannot be uninstalled, because one or more other modules '{3}' are dependent on this module. Uninstall the modules that depend on this module before uninstalling module '{4}'. - UnableToUninstallAsOtherScriptsNeedThisScript=The script '{0}' of version '{1}' in script base folder '{2}' cannot be uninstalled, because one or more other scripts '{3}' are dependent on this script. Uninstall the scripts that depend on this script before uninstalling script '{4}'. - RepositoryIsNotTrusted=Untrusted repository - QueryInstallUntrustedPackage=You are installing the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from '{1}'? - QueryInstallUntrustedScriptPackage=You are installing the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the scripts from '{1}'? - QuerySaveUntrustedPackage=You are downloading the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the modules from '{1}'? - QuerySaveUntrustedScriptPackage=You are downloading the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the scripts from '{1}'? - SourceNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. - PSGalleryApiV2Deprecated=PowerShell Gallery v2 has been deprecated. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. - PSGalleryApiV2Discontinued=PowerShell Gallery v2 has been discontinued. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. - PowerShellGalleryUnavailable=PowerShell Gallery is currently unavailable. Please try again later. - PowerShellGetModuleIsNotInstalledProperly=The PowerShellGet module was not installed properly. Be sure that only one instance or version of the PowerShellGet module is installed in the path '{0}'. - PowerShelLGetModuleGotUpdated=The PowerShellGet module was updated successfully. Restart the process to use the updated version of the PowerShellGet module. - TagsShouldBeIncludedInManifestFile=Tags are now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest tag changes. You can run Update-ModuleManifest -Tags to update the manifest with tags. - ReleaseNotesShouldBeIncludedInManifestFile=ReleaseNotes is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ReleaseNotes changes. You can run Update-ModuleManifest -ReleaseNotes to update the manifest with ReleaseNotes. - LicenseUriShouldBeIncludedInManifestFile=LicenseUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' with the newest LicenseUri changes. You can run Update-ModuleManifest -LicenseUri to update the manifest with LicenseUri. - IconUriShouldBeIncludedInManifestFile=IconUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest IconUri changes. You can run Update-ModuleManifest -IconUri to update the manifest with IconUri. - ProjectUriShouldBeIncludedInManifestFile=ProjectUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ProjectUri changes. You can run Update-ModuleManifest -ProjectUri to update the manifest with ProjectUri. - ShouldIncludeFunctionsToExport=This module '{0}' has exported functions. As a best practice, include exported functions in the module manifest file(.psd1). You can run Update-ModuleManifest -FunctionsToExport to update the manifest with ExportedFunctions field. - ShouldIncludeCmdletsToExport=This module '{0}' has exported cmdlets. As a best practice, include exported cmdlets in the module manifest file(.psd1). You can run Update-ModuleManifest -CmdletsToExport to update the manifest with ExportedCmdlets field. - ShouldIncludeDscResourcesToExport=This module '{0}' has exported DscResources. As a best practice, include exported DSC resources in the module manifest file(.psd1). If your PowerShell version is higher than 5.0, run Update-ModuleManifest -DscResourcesToExport to update the manifest with ExportedDscResources field. - UpdateModuleManifestPathCannotFound=Cannot load the manifest file '{0}' properly. Please specify the correct manifest path. - UpdatedModuleManifestNotValid=Cannot update the manifest file '{0}' because the manifest is not valid. Verify that the manifest file is valid, and then try again.'{1}' - ExportedDscResourcesNotSupportedOnLowerPowerShellVersion=The ExportedDscResources property is not supported in module manifests on PowerShell versions that are older than 5.0. Remove the value for the parameter 'DscResourcesToExport', and then try again. - CompatiblePSEditionsNotSupportedOnLowerPowerShellVersion=The CompatiblePSEditions property is not supported in module manifests on PowerShell versions that are older than 5.1. Remove the value for the parameter 'CompatiblePSEditions', and then try again. - ExternalModuleDependenciesNotSpecifiedInRequiredOrNestedModules='{0}' is listed in ExternalModuleDependencies, but it is not found in either the RequiredModules or NestedModules properties. Verify that this module is required for ExternalModuleDependencies, and then add it to NestedModules or RequiredModules. - TestModuleManifestFail=Cannot update the manifest properly. '{0}' - PackageManagementProvidersNotInModuleBaseFolder=PackageManagementProvider '{0}' is not found in the module base '{1}'. Verify that the PackageManagementProvider specified is within the module base. - UpdateManifestContentMessage=Update manifest file with new contents: - InvalidPackageManagementProviderValue=The PackageManagementProvider value cannot be '{0}'. Valid values for provider names include '{1}', and the default value for this parameter is '{2}'. - PowerShellGetUpdateIsNotSupportedOnLowerPSVersions=Self update of the PowerShellGet module is supported only in PowerShell 5.0 and newer releases. It is not supported in PowerShell 3.0 or 4.0. - ScriptVersionShouldBeGreaterThanGalleryVersion=Script '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. - ScriptVersionIsAlreadyAvailableInTheGallery=The script '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. - ScriptPrereleaseStringShouldBeGreaterThanGalleryPrereleaseString=Script '{0}' with version '{1}' and prerelease '{2}' cannot be published. The prerelease string must exceed the current prerelease string '{3}' that exists in the repository '{4}', or you must specify -Force. - ScriptParseError=The specified script file '{0}' has parse errors, try again after fixing the parse errors. - InvalidScriptToPublish=Script file '{0}' cannot be published because it does not have the required script metadata. Run Update-ScriptFileInfo -Path '{1}' to add the script metadata. - FailedToCreateCompressedScript=Failed to generate the compressed file for script '{0}'. - FailedToPublishScript=Failed to publish script '{0}': '{1}'. - PublishedScriptSuccessfully=Successfully published script '{0}' to the publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. - UnableToResolveScriptDependency=PowerShellGet cannot resolve the {0} dependency '{1}' of the script '{2}' on the repository '{3}'. Verify that the dependent {0} '{1}' is available in the repository '{3}'. If this dependent {0} '{1}' is managed externally, add it to the '{4}' entry in the script metadata. - InvalidVersion=Cannot convert value '{0}' to type 'System.Version'. - InvalidGuid=Cannot convert value '{0}' to type 'System.Guid'. - InvalidParameterValue=The specified value '{0}' for the parameter '{1}' is invalid. Ensure that it does not contain '<#' or '#>'. - MissingPSScriptInfo=PSScriptInfo is not specified in the script file '{0}'. You can use the Update-ScriptFileInfo with -Force or New-ScriptFileInfo cmdlet to add the PSScriptInfo to the script file. - MissingRequiredPSScriptInfoProperties=Script '{0}' is missing required metadata properties. Verify that the script file has Version, Guid, Description and Author properties. You can use the Update-ScriptFileInfo or New-ScriptFileInfo cmdlet to add or update the PSScriptInfo to the script file. - SkippedScriptDependency=Because dependent script '{0}' was skipped in the script dependencies list, users might not know how to install it. - SourceLocationPathsForModulesAndScriptsShouldBeEqual=SourceLocation '{0}' and ScriptSourceLocation '{1}' should be same for SMB Share or Local directory based repositories. - PublishLocationPathsForModulesAndScriptsShouldBeEqual=PublishLocation '{0}' and ScriptPublishLocation '{1}' should be same for SMB Share or Local directory based repositories. - SpecifiedNameIsAlearyUsed=The specified name '{0}' is already used for a different item on the specified repository '{1}'. Run '{2} -Name {0} -Repository {1}' to check whether the specified name '{0}' is already taken. - InvalidScriptFilePath=The script file path '{0}' is not valid. The value of the Path argument must resolve to a single file that has a '.ps1' extension. Change the value of the Path argument to point to a valid ps1 file, and then try again. - NuGetApiKeyIsRequiredForNuGetBasedGalleryService=NuGetApiKey is required for publishing a module or script file to the specified repository '{0}' whose publish location is '{1}'. Try again after specifying a valid value for the NuGetApiKey parameter. To get your API key, view your profile page. - ScriptFileExist=The specified script file '{0}' already exists. - InvalidEnvironmentVariableName=The specified environment variable name '{0}' exceeded the allowed limit of '{1}' characters. - PublishLocation=Publish Location:'{0}'. - ScriptPATHPromptCaption=PATH Environment Variable Change - ScriptPATHPromptQuery=Your system has not been configured with a default script installation path yet, which means you can only run a script by specifying the full path to the script file. This action places the script into the folder '{0}', and adds that folder to your PATH environment variable. Do you want to add the script installation path '{0}' to the PATH environment variable? - AddedScopePathToProcessSpecificPATHVariable=Added scripts installation location '{0}' for '{1}' scope to process specific PATH environment variable. - AddedScopePathToPATHVariable=Added scripts installation location '{0}' for '{1}' scope to PATH environment variable. - FilePathInFileListNotWithinModuleBase=Path '{0}' defined in FileList is not within module base '{1}'. Provide the correct FileList parameters and then try again. - ManifestFileReadWritePermissionDenied=The current user does not have read-write permissions for the file:'{0}'. Check the file permissions and then try again. - MissingTheRequiredPathOrPassThruParameter=The Path or PassThru parameter is required for creating the script file info. A new script file will be created with the script file info when the Path parameter is specified. Script file info will be returned if the PassThru parameter is specified. Try again after specifying the required parameter. - DescriptionParameterIsMissingForAddingTheScriptFileInfo=Description parameter is missing for adding the metadata to the script file. Try again after specifying the description. - UnableToAddPSScriptInfo=Unable to add PSScriptInfo to the script file '{0}'. You can use the New-ScriptFileInfo cmdlet to add the metadata to the existing script file. - RegisterVSTSFeedAsNuGetPackageSource=Publishing to a VSTS package management feed '{0}' requires it to be registered as a NuGet package source. Retry after adding this source '{0}' as NuGet package source by following the instructions specified at '{1}' - InvalidModuleAuthenticodeSignature=The module '{0}' cannot be installed or updated because the authenticode signature of the file '{1}' is not valid. - InvalidCatalogSignature=The module '{0}' cannot be installed because the catalog signature in '{1}' does not match the hash generated from the module. - AuthenticodeIssuerMismatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' from root certificate authority '{3}' is not matching with the authenticode issuer '{4}' of the previously-installed module '{5}' with version '{6}' from root certificate authority '{7}'. If you still want to install or update, use -SkipPublisherCheck parameter. - ModuleCommandAlreadyAvailable=The following commands are already available on this system:'{0}'. This module '{1}' may override the existing commands. If you still want to install this module '{1}', use -AllowClobber parameter. - CatalogFileFound=Found the catalog file '{0}' in the module '{1}' contents. - CatalogFileNotFoundInAvailableModule=Catalog file '{0}' is not found in the contents of the previously-installed module '{1}' with the same name. - CatalogFileNotFoundInNewModule=Catalog file '{0}' is not found in the contents of the module '{1}' being installed. - ValidAuthenticodeSignature=Valid authenticode signature found in the catalog file '{0}' for the module '{1}'. - ValidAuthenticodeSignatureInFile=Valid authenticode signature found in the file '{0}' for the module '{1}'. - ValidatingCatalogSignature=Validating the '{0}' module files for catalog signing using the catalog file '{1}'. - AuthenticodeIssuerMatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' matches with the authenticode issuer '{3}' of the previously-installed module '{4}' with version '{5}'. - ValidCatalogSignature=The catalog signature in '{0}' of the module '{1}' is valid and matches with the hash generated from the module contents. - SkippingPublisherCheck=Skipping the Publisher check for the version '{0}' of module '{1}'. - SourceModuleDetailsForPublisherValidation=For publisher validation, using the previously-installed module '{0}' with version '{1}' under '{2}' with publisher name '{3}' from root certificate authority '{4}'. Is this module signed by Microsoft: '{5}'. - NewModuleVersionDetailsForPublisherValidation=For publisher validation, current module '{0}' with version '{1}' with publisher name '{2}' from root certificate authority '{3}'. Is this module signed by Microsoft: '{4}'. - PublishersMatch=Publisher '{0}' of the new module '{1}' with version '{2}' matches with the publisher '{3}' of the previously-installed module '{4}' with version '{5}'. Both versions are signed with a Microsoft root certificate. - PublishersMismatch=A Microsoft-signed module named '{0}' with version '{1}' that was previously installed conflicts with the new module '{2}' from publisher '{3}' with version '{4}'. Installing the new module may result in system instability. If you still want to install or update, use -SkipPublisherCheck parameter. - ModuleIsNotCatalogSigned=The version '{0}' of the module '{1}' being installed is not catalog signed. Ensure that the version '{0}' of the module '{1}' has the catalog file '{2}' and signed with the same publisher '{3}' as the previously-installed module '{1}' with version '{4}' under the directory '{5}'. If you still want to install or update, use -SkipPublisherCheck parameter. - SentEnvironmentVariableChangeMessage=Successfully broadcasted the Environment variable changes. - UnableToSendEnvironmentVariableChangeMessage=Error in broadcasting the Environment variable changes. - LicenseUriNotSpecified='LicenseUri' is not specified. 'LicenseUri' must be provided when user license acceptance is required. - LicenseTxtNotFound=License.txt not Found. License.txt must be provided when user license acceptance is required. - LicenseTxtEmpty=License.txt is empty. - requireLicenseAcceptanceNotSupported=Require License Acceptance is not supported on Format version '{0}'. - AcceptanceLicenseQuery=Do you accept the license terms for module '{0}'. - ForceAcceptLicense=License Acceptance is required for module '{0}'. Please specify '-AcceptLicense' to perform this operation. - InvalidValueBoolean=The specified value '{0}' for the parameter '{1}' is invalid. It should be $true or $false. - UserDeclinedLicenseAcceptance=User declined license acceptance. - AcceptLicense=License Acceptance - RequiredScriptVersion=REQUIREDSCRIPTS: Required version of script '{0}' is '{1}'. - RequiredScriptVersoinFormat=, :, :[], :[,], :[,] - FailedToParseRequiredScripts=Cannot parse REQUIREDSCRIPTS '{0}'. Acceptable formats are: '{1}'. - FailedToParseRequiredScriptsVersion=Version format error: {0}, '{1}'. Acceptable formats are: '{2}'. - PublishersMismatchAsWarning=Module '{0}' version '{1}' published by '{2}' will be superceded by version '{3}' published by '{4}'. If you do not trust the new publisher, uninstall the module. - UnableToDownloadThePackage=The PackageManagement provider '{0}' is unable to download the package '{1}' version '{2}' to '{3}' path. - ValidatingTheModule=Validating the '{0}' module contents under '{1}' path. - ModuleValidationFailed=Unable to validate the '{0}' module contents under '{1}' path. - ValidatedModuleManifestFile=Test-ModuleManifest successfully validated the module manifest file '{0}'. - ValidateModuleAuthenticodeSignature=Validating the authenticode signature and publisher of the catalog file or module manifest file of the module '{0}'. - ValidateModuleCommandAlreadyAvailable=Checking for possible command collisions for the module '{0}' commands. - UnauthorizedAccessError=Access to the path '{0}' is denied. -###PSLOC -'@ - -# SIG # Begin signature block -# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor -# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG -# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAUMj4wb0r0V933 -# Kxwf6eV800i5N8GnGMPseytvd1lnEKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX -# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD -# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p -# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw -# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u -# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy -# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB -# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH -# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d -# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ -# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV -# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE -# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw -# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 -# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu -# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu -# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w -# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 -# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx -# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy -# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K -# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV -# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr -# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx -# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe -# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g -# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf -# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI -# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 -# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea -# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS -# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK -# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 -# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 -# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla -# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS -# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT -# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB -# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG -# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S -# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz -# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 -# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u -# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 -# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl -# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP -# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB -# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF -# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM -# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ -# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud -# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO -# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 -# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p -# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y -# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB -# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw -# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA -# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY -# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj -# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd -# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ -# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf -# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ -# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j -# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B -# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 -# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 -# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I -# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG -# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx -# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z -# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN -# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor -# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLIu7f8le -# gQ0ndTd8gyAEe0fnNBrhCsEUfHD+upEyb7owQgYKKwYBBAGCNwIBDDE0MDKgFIAS -# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN -# BgkqhkiG9w0BAQEFAASCAQCWjkfbjU+QuaidvjCSfT9ZWmVRt7eVzD7wasyZEXwC -# uPWX5GwoR4xPMYzA/ohAElfSdrP6HWH9R+2sdXnUCJK+7w6N9wbB+S9Da0r3UcHj -# acV7JEftJK6ZwZ9EyebTgHv6Oa0xhuaRh/BINH4OhfIZEy3A2Lg4KhvNMjGKHTi2 -# agmGkKLUqySkyv48c/HCApLbZ3ugNqI06C284kostGCqpuSg6HRDcGPDzZSkT6l4 -# u1veNMmcpeGzd3NTNy5fkt8jQt3OUQ9NmfYN5nxh1BgDB1kyDm5lWMiZqFqlLS9T -# sG/jgYnqnprYVqTa8rYsSdmn8y1idSkQ1M715PQlOYNuoYIS8TCCEu0GCisGAQQB -# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME -# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB -# MDEwDQYJYIZIAWUDBAIBBQAEIOjiV1RbUVB0CleqHpkpsACXwys4XtATfTNyD7Bu -# Zv+1AgZfYPphXsEYEzIwMjAwOTIyMjIxOTUxLjg2N1owBIACAfSggdSkgdEwgc4x -# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt -# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p -# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg -# VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt -# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJy9uo++RqBmoAAAA -# AAEnMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo -# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y -# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw -# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT -# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK -# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy -# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 -# LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj -# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPgB3nERnk6fS40vvWeD -# 3HCgM9Ep4xTIQiPnJXE9E+HkZVtTsPemoOyhfNAyF95E/rUvXOVTUcJFL7Xb16jT -# KPXONsCWY8DCixSDIiid6xa30TiEWVcIZRwiDlcx29D467OTav5rA1G6TwAEY5rQ -# jhUHLrOoJgfJfakZq6IHjd+slI0/qlys7QIGakFk2OB6mh/ln/nS8G4kNRK6Do4g -# xDtnBSFLNfhsSZlRSMDJwFvrZ2FCkaoexd7rKlUNOAAScY411IEqQeI1PwfRm3aW -# bS8IvAfJPC2Ah2LrtP8sKn5faaU8epexje7vZfcZif/cbxgUKStJzqbdvTBNc93n -# /Z8CAwEAAaOCARswggEXMB0GA1UdDgQWBBTl9JZVgF85MSRbYlOJXbhY022V8jAf -# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH -# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU -# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF -# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 -# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG -# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAKyo180VXHBqVnjZwQy7NlzXbo2+W5 -# qfHxR7ANV5RBkRkdGamkwUcDNL+DpHObFPJHa0oTeYKE0Zbl1MvvfS8RtGGdhGYG -# CJf+BPd/gBCs4+dkZdjvOzNyuVuDPGlqQ5f7HS7iuQ/cCyGHcHYJ0nXVewF2Lk+J -# lrWykHpTlLwPXmCpNR+gieItPi/UMF2RYTGwojW+yIVwNyMYnjFGUxEX5/DtJjRZ -# mg7PBHMrENN2DgO6wBelp4ptyH2KK2EsWT+8jFCuoKv+eJby0QD55LN5f8SrUPRn -# K86fh7aVOfCglQofo5ABZIGiDIrg4JsV4k6p0oBSIFOAcqRAhiH+1spCMIIGcTCC -# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv -# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN -# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv -# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 -# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw -# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 -# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw -# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe -# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx -# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G -# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA -# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 -# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC -# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX -# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v -# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI -# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j -# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g -# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 -# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB -# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA -# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh -# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS -# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK -# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon -# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi -# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ -# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII -# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 -# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a -# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ -# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ -# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT -# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD -# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP -# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow -# QTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy -# dmljZaIjCgEBMAcGBSsOAwIaAxUAs5W4TmyDHMRM7iz6mgGojqvXHzOggYMwgYCk -# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH -# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD -# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF -# AOMUsu8wIhgPMjAyMDA5MjIyMTI5MTlaGA8yMDIwMDkyMzIxMjkxOVowdzA9Bgor -# BgEEAYRZCgQBMS8wLTAKAgUA4xSy7wIBADAKAgEAAgIVPgIB/zAHAgEAAgIRtjAK -# AgUA4xYEbwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB -# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEMD4esQRMLwQdhk -# Co1zgvmclcwl3lYYpk1oMh1ndsU3+97Rt6FV3adS4Hezc/K94oQKjcxtMVzLzQhG -# agM6XlqB31VD8n2nxVuaWD1yp2jm/0IvfL9nFMHJRhgANMiBdHqvqNrd86c/Kryq -# sI0Ch0sOx9wg3BozzqQhmdNjf9c6MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC -# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV -# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp -# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEnL26j75GoGagAAAAAAScwDQYJYIZIAWUD -# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B -# CQQxIgQg6wLE4god37E/zRxYUqM58DbGQBLJxEvt6c6xU1jsZqYwgfoGCyqGSIb3 -# DQEJEAIvMYHqMIHnMIHkMIG9BCAbkuhLEoYdahb/BUyVszO2VDi6kB3MSaof/+8u -# 7SM+IjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u -# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp -# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB -# Jy9uo++RqBmoAAAAAAEnMCIEIK4r6N3NISekswMCG1kSBJCCCePrlLDQWbMKz0wt -# Lj6CMA0GCSqGSIb3DQEBCwUABIIBACY3cZHtQ2k6zfVG4NotND24Zp0mlZAFe4A+ -# BFDISaaIXS86mO142Y9Hkm9rAMBCIJbKB/Yj1yf1gbygqNCoJDHFxZbdmx5XbShg -# PVfAQl00TuyQ86VYnFJtvdDODUnwuxErAFR1zyiGGBKYtQWhXNRmbhb1kRkWp5Fg -# s9mwzZUoGI2UzXDtYb6HkjNIOMxCbWW/27YfCBnOkiP2tJMIU79R4xY4vwQeWkKW -# dgtyHv+d7eBOd1aOi8HvqCstH6x06RC3DsYfZ/NbYSGuZpjSaSDDC7A1KvdPo6ti -# UYayEGi9iXfqnAv4Iyzo3OWIN2M2iqepP2xwL8Ne2q1ImLpiZF0= -# SIG # End signature block +######################################################################################### +# +# Copyright (c) Microsoft Corporation. All rights reserved. +# +# Localized PSGet.Resource.psd1 +# +######################################################################################### + +ConvertFrom-StringData @' +###PSLOC + InstallModulewhatIfMessage=Version '{1}' of module '{0}' + InstallScriptwhatIfMessage=Version '{1}' of script '{0}' + UpdateModulewhatIfMessage=Version '__OLDVERSION__' of module '{0}', updating to version '{1}' + UpdateScriptwhatIfMessage=Version '__OLDVERSION__' of script '{0}', updating to version '{1}' + PublishModulewhatIfMessage=Version '{0}' of module '{1}' + PublishScriptwhatIfMessage=Version '{0}' of script '{1}' + NewScriptFileInfowhatIfMessage=Creating the '{0}' PowerShell Script file + UpdateScriptFileInfowhatIfMessage=Updating the '{0}' PowerShell Script file + NameShouldNotContainWildcardCharacters=The specified name '{0}' should not contain any wildcard characters, please correct it and try again. + AllVersionsCannotBeUsedWithOtherVersionParameters=You cannot use the parameter AllVersions with RequiredVersion, MinimumVersion or MaximumVersion in the same command. + VersionRangeAndRequiredVersionCannotBeSpecifiedTogether=You cannot use the parameters RequiredVersion and either MinimumVersion or MaximumVersion in the same command. Specify only one of these parameters in your command. + RequiredVersionAllowedOnlyWithSingleModuleName=The RequiredVersion parameter is allowed only when a single module name is specified as the value of the Name parameter, without any wildcard characters. + MinimumVersionIsGreaterThanMaximumVersion=The specified MinimumVersion '{0}' is greater than the specified MaximumVersion '{1}'. + AllowPrereleaseRequiredToUsePrereleaseStringInVersion=The '-AllowPrerelease' parameter must be specified when using the Prerelease string in MinimumVersion, MaximumVersion, or RequiredVersion. + UpdateModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to update modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or update '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + InstallModuleAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install modules in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + InstallScriptAdminPrivilegeRequiredForAllUsersScope=Administrator rights are required to install scripts in '{0}'. Log on to the computer with an account that has Administrator rights, and then try again, or install '{1}' by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + AdministratorRightsNeededOrSpecifyCurrentUserScope=Administrator rights are required to install or update. Log on to the computer with an account that has Administrator rights, and then try again, or install by adding "-Scope CurrentUser" to your command. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + VersionParametersAreAllowedOnlyWithSingleName=The RequiredVersion, MinimumVersion, MaximumVersion, AllVersions or AllowPrerelease parameters are allowed only when you specify a single name as the value of the Name parameter, without any wildcard characters. + PathIsNotADirectory=The specified path '{0}' is not a valid directory. + ModuleAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Module, and add the -Force parameter. + ScriptAlreadyInstalled=Version '{0}' of script '{1}' is already installed at '{2}'. To delete version '{3}' and install version '{4}', run Install-Script, and add the -Force parameter. + CommandAlreadyAvailable=A command with name '{0}' is already available on this system. This script '{0}' may override the existing command. If you still want to install this script '{0}', use -Force parameter. + ModuleAlreadyInstalledSxS=Version '{0}' of module '{1}' is already installed at '{2}'. To install version '{3}', run Install-Module and add the -Force parameter, this command will install version '{5}' side-by-side with version '{4}'. + ModuleAlreadyInstalledVerbose=Version '{0}' of module '{1}' is already installed at '{2}'. + ScriptAlreadyInstalledVerbose=Version '{0}' of script '{1}' is already installed at '{2}'. + ModuleWithRequiredVersionAlreadyInstalled=Version '{0}' of module '{1}' is already installed at '{2}'. To reinstall this version '{3}', run Install-Module or Updated-Module cmdlet with the -Force parameter. + InvalidPSModule=The module '{0}' cannot be installed or updated because it is not a properly-formed module. + InvalidPowerShellScriptFile=The script '{0}' cannot be installed or updated because it is not a properly-formed script. + InvalidAuthenticodeSignature=The module '{0}' cannot be installed or updated because the Authenticode signature for the file '{1}' is not valid. + ModuleNotInstalledOnThisMachine=Module '{0}' was not updated because no valid module was found in the module directory. Verify that the module is located in the folder specified by $env:PSModulePath. + ScriptNotInstalledOnThisMachine=Script '{0}' was not updated because no valid script was found in the script directories '{1}' and '{2}'. + AdminPrivilegesRequiredForUpdate=Module '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that directory. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + AdminPrivilegesRequiredForScriptUpdate=Script '{0}' (installed at '{1}') cannot be updated because Administrator rights are required to change that script. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + ModuleNotInstalledUsingPowerShellGet=Module '{0}' was not installed by using Install-Module, so it cannot be updated. + ScriptNotInstalledUsingPowerShellGet=Script '{0}' was not installed by using Install-Script, so it cannot be updated. + DownloadingModuleFromGallery=Downloading module '{0}' with version '{1}' from the repository '{2}'. + DownloadingScriptFromGallery=Downloading script '{0}' with version '{1}' from the repository '{2}'. + NoUpdateAvailable=No updates were found for module '{0}'. + NoScriptUpdateAvailable=No updates were found for script '{0}'. + FoundModuleUpdate=An update for the module '{0}' was found with version '{1}'. + FoundScriptUpdate=An update for the script '{0}' was found with version '{1}'. + InvalidPSModuleDuringUpdate= Module '{0}' was not updated because the module in the repository '{1}' is not a valid Windows PowerShell module. + ModuleGotUpdated=Module '{0}' has been updated successfully. + TestingModuleInUse=Testing if the module to update is in use. + ModuleDestination= The specified module will be installed in '{0}'. + ScriptDestination= The specified script will be installed in '{0}' and its dependent modules will be installed in '{1}'. + ModuleIsInUse=Module '{0}' is in currently in use or you don't have the required permissions. + ModuleInstalledSuccessfully=Module '{0}' was installed successfully to path '{1}'. + ModuleSavedSuccessfully=Module '{0}' was saved successfully to path '{1}'. + ScriptInstalledSuccessfully=Script '{0}' was installed successfully to path '{1}'. + ScriptSavedSuccessfully=Script '{0}' was saved successfully to path '{1}'. + CheckingForModuleUpdate= Checking for updates for module '{0}'. + CheckingForScriptUpdate= Checking for updates for script '{0}'. + ModuleInUseWithProcessDetails=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the following applications: '{2}'. + ModuleVersionInUse=The version '{0}' of module '{1}' is currently in use. Retry the operation after closing the applications. + ModuleNotAvailableLocally=The specified module '{0}' was not published because no module with that name was found in any module directory. + InvalidModulePathToPublish=The specified module with path '{0}' was not published because no valid module was found with that path. + ModuleWithRequiredVersionNotAvailableLocally= The specified module '{0}' with version '{1}' was not published because no module with that name and version was found in any module directory. + AmbiguousModuleName=Modules with the name '{0}' are available under multiple paths. Add the -RequiredVersion parameter or the -Path parameter to specify the module to publish. + AmbiguousModulePath=Multiple versions are available under the specified module path '{0}'. Specify the full path to the module to be published. + PublishModuleLocation=Module '{0}' was found in '{1}'. + InvalidModuleToPublish=Module '{0}' cannot be published because it does not have a module manifest file. Run New-ModuleManifest -Path to create a module manifest with metadata before publishing. + MissingRequiredManifestKeys=Module '{0}' cannot be published because it is missing required metadata. Verify that the module manifest specifies Description and Author. + InvalidCharactersInPrereleaseString=The Prerelease string '{0}' contains invalid characters. Please ensure that only characters 'a-zA-Z0-9' and possibly hyphen ('-') at the beginning are in the Prerelease string. + IncorrectVersionPartsCountForPrereleaseStringUsage=Version '{0}' must have exactly 3 parts for a Prerelease string to be used. + ModuleVersionShouldBeGreaterThanGalleryVersion=Module '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. + ModuleVersionIsAlreadyAvailableInTheGallery=The module '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. + CouldNotInstallNuGetProvider=NuGet provider is required to interact with NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed. + CouldNotInstallNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. + CouldNotUpgradeNuGetExe=NuGet.exe version '{0}' or newer, or dotnet command version '{1}' or newer is required to interact with NuGet-based repositories. Please ensure that required version of NuGet.exe or dotnet command is available under one of the paths specified in PATH environment variable value. + CouldNotFindDotnetCommand=For publish operations, dotnet command version '{0}' or newer is required to interact with the NuGet-based repositories. Please ensure that dotnet command version '{0}' or newer is installed and available under one of the paths specified in PATH environment variable value. You can also install the dotnet command by following the instructions specified at '{1}'. + CouldNotInstallNuGetBinaries2=PowerShellGet requires NuGet.exe (or dotnet command) and NuGet provider version '{0}' or newer to interact with the NuGet-based repositories. Please ensure that '{0}' or newer version of NuGet provider is installed and NuGet.exe (or dotnet command) is available under one of the paths specified in PATH environment variable value. + InstallNugetBinariesUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{2}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to upgrade NuGet.exe to the latest version and install the NuGet provider now? + InstallNuGetBinariesShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe and minimum version '{1}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{3}' or '{3}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. NuGet.exe must be available in '{4}' or '{5}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://go.microsoft.com/fwlink/?linkid=875534. Do you want PowerShellGet to install both the latest NuGet.exe and NuGet provider now? + InstallNugetExeUpgradeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to upgrade to the latest version of NuGet.exe now? + InstallNuGetExeShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet.exe to publish an item to the NuGet-based repositories. NuGet.exe must be available in '{1}' or '{2}', or under one of the paths specified in PATH environment variable value. NuGet.exe can be downloaded from https://aka.ms/psget-nugetexe. For more information, see https://aka.ms/installing-powershellget . Do you want PowerShellGet to install the latest version of NuGet.exe now? + InstallNuGetProviderShouldContinueQuery=This version of PowerShellGet requires minimum version '{0}' of NuGet provider to publish an item to NuGet-based repositories. The NuGet provider must be available in '{1}' or '{2}'. You can also install the NuGet provider by running 'Install-PackageProvider -Name NuGet -MinimumVersion {0} -Force'. Do you want PowerShellGet to install and import the NuGet provider now? + InstallNuGetBinariesUpgradeShouldContinueCaption=NuGet.exe upgrade and NuGet provider installation are required to continue + InstallNuGetBinariesShouldContinueCaption=NuGet.exe and NuGet provider installation are required to continue + InstallNuGetExeUpgradeShouldContinueCaption=NuGet.exe upgrade is required to continue + InstallNuGetExeShouldContinueCaption=NuGet.exe is required to continue + InstallNuGetProviderShouldContinueCaption=NuGet provider is required to continue + DownloadingNugetExe=Installing NuGet.exe. + DownloadingNugetProvider=Installing NuGet provider. + ModuleNotFound=Module '{0}' was not found. + NoMatchFound=No match was found for the specified search criteria and module names '{0}'. + NoMatchFoundForScriptName=No match was found for the specified search criteria and script names '{0}'. + MatchInvalidType=The name '{0}' is a {1} not a {2}. + FailedToCreateCompressedModule=Failed to generate the compressed file for module '{0}'. + FailedToPublish=Failed to publish module '{0}': '{1}'. + PublishedSuccessfully=Successfully published module '{0}' to the module publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. + InvalidWebUri=The specified Uri '{0}' for parameter '{1}' is an invalid Web Uri. Please ensure that it meets the Web Uri requirements. + RepositoryAlreadyRegistered=The repository could not be registered because there exists a registered repository with Name '{0}' and SourceLocation '{1}'. To register another repository with Name '{2}', please unregister the existing repository using the Unregister-PSRepository cmdlet. + RepositoryToBeUnregisteredNotFound=The repository '{0}' was not removed because no repository was found with that name. Please run Get-PSRepository and ensure that a repository of that name is present. + RepositoryCannotBeUnregistered=The specified repository '{0}' cannot be unregistered. + RepositoryNotFound=No repository with the name '{0}' was found. + PSGalleryNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. Try again after specifying a valid repository name. You can use 'Register-PSRepository -Default' to register the PSGallery repository. + ParameterIsNotAllowedWithPSGallery=The PSGallery repository has pre-defined locations. The '{0}' parameter is not allowed, try again after removing the '{0}' parameter. + UseDefaultParameterSetOnRegisterPSRepository=Use 'Register-PSRepository -Default' to register the PSGallery repository. + RepositoryNameContainsWildCards=The repository name '{0}' should not have wildcards, correct it and try again. + InvalidRepository=The specified repository '{0}' is not a valid registered repository name. Please ensure that '{1}' is a registered repository. + RepositoryCannotBeRegistered=The specified repository '{0}' is unauthorized and cannot be registered. Try running with -Credential. + RepositoryRegistered=Successfully registered the repository '{0}' with source location '{1}'. + RepositoryUnregistered=Successfully unregistered the repository '{0}'. + PSGalleryPublishLocationIsMissing=The specified repository '{0}' does not have a valid PublishLocation. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + PSRepositoryScriptPublishLocationIsMissing=The specified repository '{0}' does not have a valid ScriptPublishLocation. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + ScriptSourceLocationIsMissing=The specified repository '{0}' does not have a valid ScriptSourceLocation. Retry after setting the ScriptSourceLocation for repository '{0}' to a valid NuGet endpoint for scripts using the Set-PSRepository cmdlet. + PublishModuleSupportsOnlyNuGetBasedPublishLocations=Publish-Module only supports the NuGet-based publish locations. The PublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the PublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + PublishScriptSupportsOnlyNuGetBasedPublishLocations=Publish-Script only supports the NuGet-based publish locations. The ScriptPublishLocation '{0}' of the repository '{1}' is not a NuGet-based publish location. Retry after setting the ScriptPublishLocation for repository '{1}' to a valid NuGet publishing endpoint using the Set-PSRepository cmdlet. + DynamicParameterHelpMessage=The dynamic parameter '{0}' is required for Find-Module and Install-Module when using the PackageManagement provider '{1}' and source location '{2}'. Please enter your value for the '{3}' dynamic parameter: + ProviderApiDebugMessage=In PowerShellGet Provider - '{0}'. + ModuleUninstallNotSupported=Module uninstallation is not supported. To remove a module, please delete the module folder. + FastPackageReference=The FastPackageReference is '{0}'. + PackageManagementProviderIsNotAvailable=The specified PackageManagement provider '{0}' is not available. + SpecifiedSourceName=Using the specified source names : '{0}'. + SpecifiedLocationAndOGP=The specified Location is '{0}' and PackageManagementProvider is '{1}'. + NoSourceNameIsSpecified=The -Repository parameter was not specified. PowerShellGet will use all of the registered repositories. + GettingPackageManagementProviderObject=Getting the provider object for the PackageManagement Provider '{0}'. + InvalidInputObjectValue=Invalid value is specified for InputObject parameter. + SpecifiedInstallationScope=The installation scope is specified to be '{0}'. + SourceLocationValueForPSGalleryCannotBeChanged=The SourceLocation value for the PSGallery repository can not be changed. + PublishLocationValueForPSGalleryCannotBeChanged=The PublishLocation value for the PSGallery repository can not be changed. + SpecifiedProviderName=The specified PackageManagement provider name '{0}'. + ProviderNameNotSpecified=User did not specify the PackageManagement provider name, trying with the provider name '{0}'. + SpecifiedProviderNotAvailable=The specified PackageManagement provider '{0}' is not available. + SpecifiedProviderDoesnotSupportPSModules=The specified PackageManagement Provider '{0}' does not support PowerShell Modules. PackageManagement Providers must support the 'supports-powershell-modules' feature. + PollingPackageManagementProvidersForLocation=Polling available PackageManagement Providers to find one that can support the specified source location '{0}'. + PollingSingleProviderForLocation=Resolving the source location '{0}' with PackageManagement Provider '{1}'. + FoundProviderForLocation=The PackageManagement provider '{0}' supports the source location '{1}'. + SpecifiedLocationCannotBeRegistered=The specified location '{0}' cannot be registered. + RepositoryDetails=Repository details, Name = '{0}', Location = '{1}'; IsTrusted = '{2}'; IsRegistered = '{3}'. + NotSupportedPowerShellGetFormatVersion=The specified module '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this module, '{2}'. + NotSupportedPowerShellGetFormatVersionScripts=The specified script '{0}' with PowerShellGetFormatVersion '{1}' is not supported by the current version of PowerShellGet. Get the latest version of the PowerShellGet module to install this script, '{2}'. + PathNotFound=Cannot find the path '{0}' because it does not exist. + ModuleIsNotTrusted=Untrusted module '{0}'. + ScriptIsNotTrusted=Untrusted script '{0}'. + SkippedModuleDependency=Because dependent module '{0}' was skipped in the module dependencies list, users might not know how to install it. + MissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current module '{1}', ensure that its dependent module '{2}' is installed. + ExternallyManagedModuleDependencyIsInstalled=The externally managed, dependent module '{0}' is already installed on this computer. + ScriptMissingExternallyManagedModuleDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent module '{2}' is installed. + ScriptMissingExternallyManagedScriptDependency=The externally managed, dependent module '{0}' is not installed on this computer. To use the current script '{1}', ensure that its dependent script '{2}' is installed. + ScriptExternallyManagedScriptDependencyIsInstalled=The externally managed, dependent script '{0}' is already installed on this computer. + UnableToResolveModuleDependency=PowerShellGet cannot resolve the module dependency '{0}' of the module '{1}' on the repository '{2}'. Verify that the dependent module '{3}' is available in the repository '{4}'. If this dependent module '{5}' is managed externally, add it to the ExternalModuleDependencies entry in the PSData section of the module manifest. + FindingModuleDependencies=Finding module dependencies for version '{1}' of the module '{0}' from repository '{2}'. + InstallingDependencyModule=Installing the dependency module '{0}' with version '{1}' for the module '{2}'. + InstallingDependencyScript=Installing the dependency script '{0}' with version '{1}' for the script '{2}'. + SavingDependencyModule=Saving the dependency module '{0}' with version '{1}' for the module '{2}'. + SavingDependencyScript=Saving the dependency script '{0}' with version '{1}' for the script '{2}'. + ModuleUninstallationSucceeded=Successfully uninstalled the module '{0}' from module base '{1}'. + ScriptUninstallationSucceeded=Successfully uninstalled the script '{0}' from script base '{1}'. + AdminPrivilegesRequiredForUninstall=You cannot uninstall the module '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + AdminPrivilegesRequiredForScriptUninstall=You cannot uninstall the script '{0}' from '{1}' because Administrator rights are required to uninstall from that folder. Log on to the computer with an account that has Administrator rights, and then try again. You can also try running the Windows PowerShell session with elevated rights (Run as Administrator). + ModuleUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Module '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. + ScriptUninstallationNotPossibleAsItIsNotInstalledUsingPowerShellGet=Script '{0}' was not installed on this computer by using either the PowerShellGet cmdlets or the PowerShellGet provider, so it cannot be uninstalled. + UnableToUninstallModuleVersion=The module '{0}' of version '{1}' in module base folder '{2}' was installed without side-by-side version support. Some versions are installed in this module base with side-by-side version support. Uninstall other versions of this module before uninstalling the most current version. + UnableToUninstallAsOtherModulesNeedThisModule=The module '{0}' of version '{1}' in module base folder '{2}' cannot be uninstalled, because one or more other modules '{3}' are dependent on this module. Uninstall the modules that depend on this module before uninstalling module '{4}'. + UnableToUninstallAsOtherScriptsNeedThisScript=The script '{0}' of version '{1}' in script base folder '{2}' cannot be uninstalled, because one or more other scripts '{3}' are dependent on this script. Uninstall the scripts that depend on this script before uninstalling script '{4}'. + RepositoryIsNotTrusted=Untrusted repository + QueryInstallUntrustedPackage=You are installing the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the modules from '{1}'? + QueryInstallUntrustedScriptPackage=You are installing the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to install the scripts from '{1}'? + QuerySaveUntrustedPackage=You are downloading the modules from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the modules from '{1}'? + QuerySaveUntrustedScriptPackage=You are downloading the scripts from an untrusted repository. If you trust this repository, change its InstallationPolicy value by running the Set-PSRepository cmdlet. Are you sure you want to download the scripts from '{1}'? + SourceNotFound=Unable to find repository '{0}'. Use Get-PSRepository to see all available repositories. + PSGalleryApiV2Deprecated=PowerShell Gallery v2 has been deprecated. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. + PSGalleryApiV2Discontinued=PowerShell Gallery v2 has been discontinued. Please run 'Update-Module -Name PowerShellGet' to update to PowerShell Gallery v3. For more information, please visit our website at 'https://www.powershellgallery.com'. + PowerShellGalleryUnavailable=PowerShell Gallery is currently unavailable. Please try again later. + PowerShellGetModuleIsNotInstalledProperly=The PowerShellGet module was not installed properly. Be sure that only one instance or version of the PowerShellGet module is installed in the path '{0}'. + PowerShelLGetModuleGotUpdated=The PowerShellGet module was updated successfully. Restart the process to use the updated version of the PowerShellGet module. + TagsShouldBeIncludedInManifestFile=Tags are now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest tag changes. You can run Update-ModuleManifest -Tags to update the manifest with tags. + ReleaseNotesShouldBeIncludedInManifestFile=ReleaseNotes is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ReleaseNotes changes. You can run Update-ModuleManifest -ReleaseNotes to update the manifest with ReleaseNotes. + LicenseUriShouldBeIncludedInManifestFile=LicenseUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' with the newest LicenseUri changes. You can run Update-ModuleManifest -LicenseUri to update the manifest with LicenseUri. + IconUriShouldBeIncludedInManifestFile=IconUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest IconUri changes. You can run Update-ModuleManifest -IconUri to update the manifest with IconUri. + ProjectUriShouldBeIncludedInManifestFile=ProjectUri is now supported in the module manifest file (.psd1). Update the module manifest file of module '{0}' in '{1}' with the newest ProjectUri changes. You can run Update-ModuleManifest -ProjectUri to update the manifest with ProjectUri. + ShouldIncludeFunctionsToExport=This module '{0}' has exported functions. As a best practice, include exported functions in the module manifest file(.psd1). You can run Update-ModuleManifest -FunctionsToExport to update the manifest with ExportedFunctions field. + ShouldIncludeCmdletsToExport=This module '{0}' has exported cmdlets. As a best practice, include exported cmdlets in the module manifest file(.psd1). You can run Update-ModuleManifest -CmdletsToExport to update the manifest with ExportedCmdlets field. + ShouldIncludeDscResourcesToExport=This module '{0}' has exported DscResources. As a best practice, include exported DSC resources in the module manifest file(.psd1). If your PowerShell version is higher than 5.0, run Update-ModuleManifest -DscResourcesToExport to update the manifest with ExportedDscResources field. + UpdateModuleManifestPathCannotFound=Cannot load the manifest file '{0}' properly. Please specify the correct manifest path. + UpdatedModuleManifestNotValid=Cannot update the manifest file '{0}' because the manifest is not valid. Verify that the manifest file is valid, and then try again.'{1}' + ExportedDscResourcesNotSupportedOnLowerPowerShellVersion=The ExportedDscResources property is not supported in module manifests on PowerShell versions that are older than 5.0. Remove the value for the parameter 'DscResourcesToExport', and then try again. + CompatiblePSEditionsNotSupportedOnLowerPowerShellVersion=The CompatiblePSEditions property is not supported in module manifests on PowerShell versions that are older than 5.1. Remove the value for the parameter 'CompatiblePSEditions', and then try again. + ExternalModuleDependenciesNotSpecifiedInRequiredOrNestedModules='{0}' is listed in ExternalModuleDependencies, but it is not found in either the RequiredModules or NestedModules properties. Verify that this module is required for ExternalModuleDependencies, and then add it to NestedModules or RequiredModules. + TestModuleManifestFail=Cannot update the manifest properly. '{0}' + PackageManagementProvidersNotInModuleBaseFolder=PackageManagementProvider '{0}' is not found in the module base '{1}'. Verify that the PackageManagementProvider specified is within the module base. + UpdateManifestContentMessage=Update manifest file with new contents: + InvalidPackageManagementProviderValue=The PackageManagementProvider value cannot be '{0}'. Valid values for provider names include '{1}', and the default value for this parameter is '{2}'. + PowerShellGetUpdateIsNotSupportedOnLowerPSVersions=Self update of the PowerShellGet module is supported only in PowerShell 5.0 and newer releases. It is not supported in PowerShell 3.0 or 4.0. + ScriptVersionShouldBeGreaterThanGalleryVersion=Script '{0}' with version '{1}' cannot be published. The version must exceed the current version '{2}' that exists in the repository '{3}', or you must specify -Force. + ScriptVersionIsAlreadyAvailableInTheGallery=The script '{0}' with version '{1}' cannot be published as the current version '{2}' is already available in the repository '{3}'. + ScriptPrereleaseStringShouldBeGreaterThanGalleryPrereleaseString=Script '{0}' with version '{1}' and prerelease '{2}' cannot be published. The prerelease string must exceed the current prerelease string '{3}' that exists in the repository '{4}', or you must specify -Force. + ScriptParseError=The specified script file '{0}' has parse errors, try again after fixing the parse errors. + InvalidScriptToPublish=Script file '{0}' cannot be published because it does not have the required script metadata. Run Update-ScriptFileInfo -Path '{1}' to add the script metadata. + FailedToCreateCompressedScript=Failed to generate the compressed file for script '{0}'. + FailedToPublishScript=Failed to publish script '{0}': '{1}'. + PublishedScriptSuccessfully=Successfully published script '{0}' to the publish location '{1}'. Please allow few minutes for '{2}' to show up in the search results. + UnableToResolveScriptDependency=PowerShellGet cannot resolve the {0} dependency '{1}' of the script '{2}' on the repository '{3}'. Verify that the dependent {0} '{1}' is available in the repository '{3}'. If this dependent {0} '{1}' is managed externally, add it to the '{4}' entry in the script metadata. + InvalidVersion=Cannot convert value '{0}' to type 'System.Version'. + InvalidGuid=Cannot convert value '{0}' to type 'System.Guid'. + InvalidParameterValue=The specified value '{0}' for the parameter '{1}' is invalid. Ensure that it does not contain '<#' or '#>'. + MissingPSScriptInfo=PSScriptInfo is not specified in the script file '{0}'. You can use the Update-ScriptFileInfo with -Force or New-ScriptFileInfo cmdlet to add the PSScriptInfo to the script file. + MissingRequiredPSScriptInfoProperties=Script '{0}' is missing required metadata properties. Verify that the script file has Version, Guid, Description and Author properties. You can use the Update-ScriptFileInfo or New-ScriptFileInfo cmdlet to add or update the PSScriptInfo to the script file. + SkippedScriptDependency=Because dependent script '{0}' was skipped in the script dependencies list, users might not know how to install it. + SourceLocationPathsForModulesAndScriptsShouldBeEqual=SourceLocation '{0}' and ScriptSourceLocation '{1}' should be same for SMB Share or Local directory based repositories. + PublishLocationPathsForModulesAndScriptsShouldBeEqual=PublishLocation '{0}' and ScriptPublishLocation '{1}' should be same for SMB Share or Local directory based repositories. + SpecifiedNameIsAlearyUsed=The specified name '{0}' is already used for a different item on the specified repository '{1}'. Run '{2} -Name {0} -Repository {1}' to check whether the specified name '{0}' is already taken. + InvalidScriptFilePath=The script file path '{0}' is not valid. The value of the Path argument must resolve to a single file that has a '.ps1' extension. Change the value of the Path argument to point to a valid ps1 file, and then try again. + NuGetApiKeyIsRequiredForNuGetBasedGalleryService=NuGetApiKey is required for publishing a module or script file to the specified repository '{0}' whose publish location is '{1}'. Try again after specifying a valid value for the NuGetApiKey parameter. To get your API key, view your profile page. + ScriptFileExist=The specified script file '{0}' already exists. + InvalidEnvironmentVariableName=The specified environment variable name '{0}' exceeded the allowed limit of '{1}' characters. + PublishLocation=Publish Location:'{0}'. + ScriptPATHPromptCaption=PATH Environment Variable Change + ScriptPATHPromptQuery=Your system has not been configured with a default script installation path yet, which means you can only run a script by specifying the full path to the script file. This action places the script into the folder '{0}', and adds that folder to your PATH environment variable. Do you want to add the script installation path '{0}' to the PATH environment variable? + AddedScopePathToProcessSpecificPATHVariable=Added scripts installation location '{0}' for '{1}' scope to process specific PATH environment variable. + AddedScopePathToPATHVariable=Added scripts installation location '{0}' for '{1}' scope to PATH environment variable. + FilePathInFileListNotWithinModuleBase=Path '{0}' defined in FileList is not within module base '{1}'. Provide the correct FileList parameters and then try again. + ManifestFileReadWritePermissionDenied=The current user does not have read-write permissions for the file:'{0}'. Check the file permissions and then try again. + MissingTheRequiredPathOrPassThruParameter=The Path or PassThru parameter is required for creating the script file info. A new script file will be created with the script file info when the Path parameter is specified. Script file info will be returned if the PassThru parameter is specified. Try again after specifying the required parameter. + DescriptionParameterIsMissingForAddingTheScriptFileInfo=Description parameter is missing for adding the metadata to the script file. Try again after specifying the description. + UnableToAddPSScriptInfo=Unable to add PSScriptInfo to the script file '{0}'. You can use the New-ScriptFileInfo cmdlet to add the metadata to the existing script file. + RegisterVSTSFeedAsNuGetPackageSource=Publishing to a VSTS package management feed '{0}' requires it to be registered as a NuGet package source. Retry after adding this source '{0}' as NuGet package source by following the instructions specified at '{1}' + InvalidModuleAuthenticodeSignature=The module '{0}' cannot be installed or updated because the authenticode signature of the file '{1}' is not valid. + InvalidCatalogSignature=The module '{0}' cannot be installed because the catalog signature in '{1}' does not match the hash generated from the module. + AuthenticodeIssuerMismatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' from root certificate authority '{3}' is not matching with the authenticode issuer '{4}' of the previously-installed module '{5}' with version '{6}' from root certificate authority '{7}'. If you still want to install or update, use -SkipPublisherCheck parameter. + ModuleCommandAlreadyAvailable=The following commands are already available on this system:'{0}'. This module '{1}' may override the existing commands. If you still want to install this module '{1}', use -AllowClobber parameter. + CatalogFileFound=Found the catalog file '{0}' in the module '{1}' contents. + CatalogFileNotFoundInAvailableModule=Catalog file '{0}' is not found in the contents of the previously-installed module '{1}' with the same name. + CatalogFileNotFoundInNewModule=Catalog file '{0}' is not found in the contents of the module '{1}' being installed. + ValidAuthenticodeSignature=Valid authenticode signature found in the catalog file '{0}' for the module '{1}'. + ValidAuthenticodeSignatureInFile=Valid authenticode signature found in the file '{0}' for the module '{1}'. + ValidatingCatalogSignature=Validating the '{0}' module files for catalog signing using the catalog file '{1}'. + AuthenticodeIssuerMatch=Authenticode issuer '{0}' of the new module '{1}' with version '{2}' matches with the authenticode issuer '{3}' of the previously-installed module '{4}' with version '{5}'. + ValidCatalogSignature=The catalog signature in '{0}' of the module '{1}' is valid and matches with the hash generated from the module contents. + SkippingPublisherCheck=Skipping the Publisher check for the version '{0}' of module '{1}'. + SourceModuleDetailsForPublisherValidation=For publisher validation, using the previously-installed module '{0}' with version '{1}' under '{2}' with publisher name '{3}' from root certificate authority '{4}'. Is this module signed by Microsoft: '{5}'. + NewModuleVersionDetailsForPublisherValidation=For publisher validation, current module '{0}' with version '{1}' with publisher name '{2}' from root certificate authority '{3}'. Is this module signed by Microsoft: '{4}'. + PublishersMatch=Publisher '{0}' of the new module '{1}' with version '{2}' matches with the publisher '{3}' of the previously-installed module '{4}' with version '{5}'. Both versions are signed with a Microsoft root certificate. + PublishersMismatch=A Microsoft-signed module named '{0}' with version '{1}' that was previously installed conflicts with the new module '{2}' from publisher '{3}' with version '{4}'. Installing the new module may result in system instability. If you still want to install or update, use -SkipPublisherCheck parameter. + ModuleIsNotCatalogSigned=The version '{0}' of the module '{1}' being installed is not catalog signed. Ensure that the version '{0}' of the module '{1}' has the catalog file '{2}' and signed with the same publisher '{3}' as the previously-installed module '{1}' with version '{4}' under the directory '{5}'. If you still want to install or update, use -SkipPublisherCheck parameter. + SentEnvironmentVariableChangeMessage=Successfully broadcasted the Environment variable changes. + UnableToSendEnvironmentVariableChangeMessage=Error in broadcasting the Environment variable changes. + LicenseUriNotSpecified='LicenseUri' is not specified. 'LicenseUri' must be provided when user license acceptance is required. + LicenseTxtNotFound=License.txt not Found. License.txt must be provided when user license acceptance is required. + LicenseTxtEmpty=License.txt is empty. + requireLicenseAcceptanceNotSupported=Require License Acceptance is not supported on Format version '{0}'. + AcceptanceLicenseQuery=Do you accept the license terms for module '{0}'. + ForceAcceptLicense=License Acceptance is required for module '{0}'. Please specify '-AcceptLicense' to perform this operation. + InvalidValueBoolean=The specified value '{0}' for the parameter '{1}' is invalid. It should be $true or $false. + UserDeclinedLicenseAcceptance=User declined license acceptance. + AcceptLicense=License Acceptance + RequiredScriptVersion=REQUIREDSCRIPTS: Required version of script '{0}' is '{1}'. + RequiredScriptVersoinFormat=, :, :[], :[,], :[,] + FailedToParseRequiredScripts=Cannot parse REQUIREDSCRIPTS '{0}'. Acceptable formats are: '{1}'. + FailedToParseRequiredScriptsVersion=Version format error: {0}, '{1}'. Acceptable formats are: '{2}'. + PublishersMismatchAsWarning=Module '{0}' version '{1}' published by '{2}' will be superceded by version '{3}' published by '{4}'. If you do not trust the new publisher, uninstall the module. + UnableToDownloadThePackage=The PackageManagement provider '{0}' is unable to download the package '{1}' version '{2}' to '{3}' path. + ValidatingTheModule=Validating the '{0}' module contents under '{1}' path. + ModuleValidationFailed=Unable to validate the '{0}' module contents under '{1}' path. + ValidatedModuleManifestFile=Test-ModuleManifest successfully validated the module manifest file '{0}'. + ValidateModuleAuthenticodeSignature=Validating the authenticode signature and publisher of the catalog file or module manifest file of the module '{0}'. + ValidateModuleCommandAlreadyAvailable=Checking for possible command collisions for the module '{0}' commands. + UnauthorizedAccessError=Access to the path '{0}' is denied. +###PSLOC +'@ + +# SIG # Begin signature block +# MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor +# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG +# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAUMj4wb0r0V933 +# Kxwf6eV800i5N8GnGMPseytvd1lnEKCCDYEwggX/MIID56ADAgECAhMzAAABh3IX +# chVZQMcJAAAAAAGHMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD +# VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p +# bmcgUENBIDIwMTEwHhcNMjAwMzA0MTgzOTQ3WhcNMjEwMzAzMTgzOTQ3WjB0MQsw +# CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u +# ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy +# b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +# AQDOt8kLc7P3T7MKIhouYHewMFmnq8Ayu7FOhZCQabVwBp2VS4WyB2Qe4TQBT8aB +# znANDEPjHKNdPT8Xz5cNali6XHefS8i/WXtF0vSsP8NEv6mBHuA2p1fw2wB/F0dH +# sJ3GfZ5c0sPJjklsiYqPw59xJ54kM91IOgiO2OUzjNAljPibjCWfH7UzQ1TPHc4d +# weils8GEIrbBRb7IWwiObL12jWT4Yh71NQgvJ9Fn6+UhD9x2uk3dLj84vwt1NuFQ +# itKJxIV0fVsRNR3abQVOLqpDugbr0SzNL6o8xzOHL5OXiGGwg6ekiXA1/2XXY7yV +# Fc39tledDtZjSjNbex1zzwSXAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE +# AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUhov4ZyO96axkJdMjpzu2zVXOJcsw +# UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 +# ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDU4Mzg1MB8GA1UdIwQYMBaAFEhu +# ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu +# bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w +# Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 +# Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx +# MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAixmy +# S6E6vprWD9KFNIB9G5zyMuIjZAOuUJ1EK/Vlg6Fb3ZHXjjUwATKIcXbFuFC6Wr4K +# NrU4DY/sBVqmab5AC/je3bpUpjtxpEyqUqtPc30wEg/rO9vmKmqKoLPT37svc2NV +# BmGNl+85qO4fV/w7Cx7J0Bbqk19KcRNdjt6eKoTnTPHBHlVHQIHZpMxacbFOAkJr +# qAVkYZdz7ikNXTxV+GRb36tC4ByMNxE2DF7vFdvaiZP0CVZ5ByJ2gAhXMdK9+usx +# zVk913qKde1OAuWdv+rndqkAIm8fUlRnr4saSCg7cIbUwCCf116wUJ7EuJDg0vHe +# yhnCeHnBbyH3RZkHEi2ofmfgnFISJZDdMAeVZGVOh20Jp50XBzqokpPzeZ6zc1/g +# yILNyiVgE+RPkjnUQshd1f1PMgn3tns2Cz7bJiVUaqEO3n9qRFgy5JuLae6UweGf +# AeOo3dgLZxikKzYs3hDMaEtJq8IP71cX7QXe6lnMmXU/Hdfz2p897Zd+kU+vZvKI +# 3cwLfuVQgK2RZ2z+Kc3K3dRPz2rXycK5XCuRZmvGab/WbrZiC7wJQapgBodltMI5 +# GMdFrBg9IeF7/rP4EqVQXeKtevTlZXjpuNhhjuR+2DMt/dWufjXpiW91bo3aH6Ea +# jOALXmoxgltCp1K7hrS6gmsvj94cLRf50QQ4U8Qwggd6MIIFYqADAgECAgphDpDS +# AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK +# V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 +# IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 +# ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla +# MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS +# ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT +# H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB +# AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG +# OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S +# 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz +# y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 +# 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u +# M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 +# X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl +# XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP +# 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB +# l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF +# RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM +# CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ +# BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud +# DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO +# 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 +# LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p +# Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y +# Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB +# FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw +# cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA +# XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY +# 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj +# 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd +# d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ +# Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf +# wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ +# aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j +# NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B +# xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 +# eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 +# r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I +# RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG +# A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx +# HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z +# b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAYdyF3IVWUDHCQAAAAABhzAN +# BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor +# BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgLIu7f8le +# gQ0ndTd8gyAEe0fnNBrhCsEUfHD+upEyb7owQgYKKwYBBAGCNwIBDDE0MDKgFIAS +# AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN +# BgkqhkiG9w0BAQEFAASCAQCWjkfbjU+QuaidvjCSfT9ZWmVRt7eVzD7wasyZEXwC +# uPWX5GwoR4xPMYzA/ohAElfSdrP6HWH9R+2sdXnUCJK+7w6N9wbB+S9Da0r3UcHj +# acV7JEftJK6ZwZ9EyebTgHv6Oa0xhuaRh/BINH4OhfIZEy3A2Lg4KhvNMjGKHTi2 +# agmGkKLUqySkyv48c/HCApLbZ3ugNqI06C284kostGCqpuSg6HRDcGPDzZSkT6l4 +# u1veNMmcpeGzd3NTNy5fkt8jQt3OUQ9NmfYN5nxh1BgDB1kyDm5lWMiZqFqlLS9T +# sG/jgYnqnprYVqTa8rYsSdmn8y1idSkQ1M715PQlOYNuoYIS8TCCEu0GCisGAQQB +# gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME +# AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB +# MDEwDQYJYIZIAWUDBAIBBQAEIOjiV1RbUVB0CleqHpkpsACXwys4XtATfTNyD7Bu +# Zv+1AgZfYPphXsEYEzIwMjAwOTIyMjIxOTUxLjg2N1owBIACAfSggdSkgdEwgc4x +# CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt +# b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p +# Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg +# VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt +# U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABJy9uo++RqBmoAAAA +# AAEnMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo +# aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y +# cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw +# MB4XDTE5MTIxOTAxMTQ1OVoXDTIxMDMxNzAxMTQ1OVowgc4xCzAJBgNVBAYTAlVT +# MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK +# ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy +# YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 +# LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj +# ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPgB3nERnk6fS40vvWeD +# 3HCgM9Ep4xTIQiPnJXE9E+HkZVtTsPemoOyhfNAyF95E/rUvXOVTUcJFL7Xb16jT +# KPXONsCWY8DCixSDIiid6xa30TiEWVcIZRwiDlcx29D467OTav5rA1G6TwAEY5rQ +# jhUHLrOoJgfJfakZq6IHjd+slI0/qlys7QIGakFk2OB6mh/ln/nS8G4kNRK6Do4g +# xDtnBSFLNfhsSZlRSMDJwFvrZ2FCkaoexd7rKlUNOAAScY411IEqQeI1PwfRm3aW +# bS8IvAfJPC2Ah2LrtP8sKn5faaU8epexje7vZfcZif/cbxgUKStJzqbdvTBNc93n +# /Z8CAwEAAaOCARswggEXMB0GA1UdDgQWBBTl9JZVgF85MSRbYlOJXbhY022V8jAf +# BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH +# hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU +# aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF +# BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 +# YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG +# AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQAKyo180VXHBqVnjZwQy7NlzXbo2+W5 +# qfHxR7ANV5RBkRkdGamkwUcDNL+DpHObFPJHa0oTeYKE0Zbl1MvvfS8RtGGdhGYG +# CJf+BPd/gBCs4+dkZdjvOzNyuVuDPGlqQ5f7HS7iuQ/cCyGHcHYJ0nXVewF2Lk+J +# lrWykHpTlLwPXmCpNR+gieItPi/UMF2RYTGwojW+yIVwNyMYnjFGUxEX5/DtJjRZ +# mg7PBHMrENN2DgO6wBelp4ptyH2KK2EsWT+8jFCuoKv+eJby0QD55LN5f8SrUPRn +# K86fh7aVOfCglQofo5ABZIGiDIrg4JsV4k6p0oBSIFOAcqRAhiH+1spCMIIGcTCC +# BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv +# b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN +# MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv +# bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 +# aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw +# DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 +# VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw +# RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe +# dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx +# Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G +# kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA +# AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 +# fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC +# AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX +# zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v +# cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI +# KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j +# b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g +# AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 +# d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB +# BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA +# bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh +# IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS +# +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK +# kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon +# /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi +# PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ +# fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII +# YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 +# cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a +# KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ +# cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ +# NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT +# AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD +# VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP +# cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow +# QTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy +# dmljZaIjCgEBMAcGBSsOAwIaAxUAs5W4TmyDHMRM7iz6mgGojqvXHzOggYMwgYCk +# fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH +# UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD +# Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF +# AOMUsu8wIhgPMjAyMDA5MjIyMTI5MTlaGA8yMDIwMDkyMzIxMjkxOVowdzA9Bgor +# BgEEAYRZCgQBMS8wLTAKAgUA4xSy7wIBADAKAgEAAgIVPgIB/zAHAgEAAgIRtjAK +# AgUA4xYEbwIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB +# AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBAEMD4esQRMLwQdhk +# Co1zgvmclcwl3lYYpk1oMh1ndsU3+97Rt6FV3adS4Hezc/K94oQKjcxtMVzLzQhG +# agM6XlqB31VD8n2nxVuaWD1yp2jm/0IvfL9nFMHJRhgANMiBdHqvqNrd86c/Kryq +# sI0Ch0sOx9wg3BozzqQhmdNjf9c6MYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC +# VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV +# BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp +# bWUtU3RhbXAgUENBIDIwMTACEzMAAAEnL26j75GoGagAAAAAAScwDQYJYIZIAWUD +# BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B +# CQQxIgQg6wLE4god37E/zRxYUqM58DbGQBLJxEvt6c6xU1jsZqYwgfoGCyqGSIb3 +# DQEJEAIvMYHqMIHnMIHkMIG9BCAbkuhLEoYdahb/BUyVszO2VDi6kB3MSaof/+8u +# 7SM+IjCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u +# MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp +# b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB +# Jy9uo++RqBmoAAAAAAEnMCIEIK4r6N3NISekswMCG1kSBJCCCePrlLDQWbMKz0wt +# Lj6CMA0GCSqGSIb3DQEBCwUABIIBACY3cZHtQ2k6zfVG4NotND24Zp0mlZAFe4A+ +# BFDISaaIXS86mO142Y9Hkm9rAMBCIJbKB/Yj1yf1gbygqNCoJDHFxZbdmx5XbShg +# PVfAQl00TuyQ86VYnFJtvdDODUnwuxErAFR1zyiGGBKYtQWhXNRmbhb1kRkWp5Fg +# s9mwzZUoGI2UzXDtYb6HkjNIOMxCbWW/27YfCBnOkiP2tJMIU79R4xY4vwQeWkKW +# dgtyHv+d7eBOd1aOi8HvqCstH6x06RC3DsYfZ/NbYSGuZpjSaSDDC7A1KvdPo6ti +# UYayEGi9iXfqnAv4Iyzo3OWIN2M2iqepP2xwL8Ne2q1ImLpiZF0= +# SIG # End signature block